diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..0a443884a1 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Database +DB_HOSTNAME=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=postgres +DB_DATABASE_NAME=app + +# Redis +REDIS_HOSTNAME=localhost +REDIS_PORT=6379 + +# Server +IMMICH_PORT=2283 +IMMICH_ENV=development diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 8759cf2357..0000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,7 +0,0 @@ -/.github/ @bo0tzz -/docker/ @bo0tzz -/server/ @danieldietzler -/web/ @danieldietzler -/machine-learning/ @mertalev -/e2e/ @danieldietzler -/mobile/ @shenlong-tanwen diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 1484498281..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,134 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation -in our community a harassment-free experience for everyone, regardless -of age, body size, visible or invisible disability, ethnicity, sex -characteristics, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, -race, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, -welcoming, diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for -our community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our - mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or - political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in - a professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our -standards of acceptable behavior and will take appropriate and fair -corrective action in response to any behavior that they deem -inappropriate, threatening, offensive, or harmful. - -Community leaders have the right and responsibility to remove, edit, -or reject comments, commits, code, wiki edits, issues, and other -contributions that are not aligned to this Code of Conduct, and will -communicate reasons for moderation decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also -applies when an individual is officially representing the community in -public spaces. Examples of representing our community include using an -official e-mail address, posting via an official social media account, -or acting as an appointed representative at an online or offline -event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior -may be reported to the community leaders responsible for enforcement -at our Discord channel. All complaints -will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and -security of the reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in -determining the consequences for any action they deem in violation of -this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior -deemed unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, -providing clarity around the nature of the violation and an -explanation of why the behavior was inappropriate. A public apology -may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued -behavior. No interaction with the people involved, including -unsolicited interaction with those enforcing the Code of Conduct, for -a specified period of time. This includes avoiding interactions in -community spaces as well as external channels like social -media. Violating these terms may lead to a temporary or permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, -including sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or -public communication with the community for a specified period of -time. No public or private interaction with the people involved, -including unsolicited interaction with those enforcing the Code of -Conduct, is allowed during this period. Violating these terms may lead -to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of -community standards, including sustained inappropriate behavior, -harassment of an individual, or aggression toward or disparagement of -classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction -within the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor -Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of -conduct enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the -FAQ at https://www.contributor-covenant.org/faq. Translations are -available at https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 109708cc6e..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributing to Immich - -We appreciate every contribution, and we're happy about every new contributor. So please feel invited to help make Immich a better product! - -## Getting started - -To get you started quickly we have detailed guides for the dev setup on our [website](https://docs.immich.app/developer/setup). If you prefer, you can also use [Devcontainers](https://docs.immich.app/developer/devcontainers). -There are also additional resources about Immich's architecture, database migrations, the use of OpenAPI, and more in our [developer documentation](https://docs.immich.app/developer/architecture). - -## General - -Please try to keep pull requests as focused as possible. A PR should do exactly one thing and not bleed into other, unrelated areas. The smaller a PR, the fewer changes are likely needed, and the quicker it will likely be merged. For larger/more impactful PRs, please reach out to us first to discuss your plans. The best way to do this is through our [Discord](https://discord.immich.app). We have a dedicated `#contributing` channel there. Additionally, please fill out the entire template when opening a PR. - -## Finding work - -If you are looking for something to work on, there are discussions and issues with a `good-first-issue` label on them. These are always a good starting point. If none of them sound interesting or fit your skill set, feel free to reach out on our Discord. We're happy to help you find something to work on! - -## Use of generative AI - -We generally discourage PRs entirely generated by an LLM. For any part generated by an LLM, please put extra effort into your self-review. By using generative AI without proper self-review, the time you save ends up being more work we need to put in for proper reviews and code cleanup. Please keep that in mind when submitting code by an LLM. Clearly state the use of LLMs/(generative) AI in your pull request as requested by the template. - -## Feature freezes - -From time to time, we put a feature freeze on parts of the codebase. For us, this means we won't accept most PRs that make changes in that area. Exempted from this are simple bug fixes that require only minor changes. We will close feature PRs that target a feature-frozen area, even if that feature is highly requested and you put a lot of work into it. Please keep that in mind, and if you're ever uncertain if a PR would be accepted, reach out to us first (e.g., in the aforementioned `#contributing` channel). We hate to throw away work. Currently, we have feature freezes on: - -- Sharing/Asset ownership -- (External) libraries - -## Non-code contributions - -If you want to contribute to Immich but you don't feel comfortable programming in our tech stack, there are other ways you can help the team. - -### Translations - -All our translations are done through [Weblate](https://hosted.weblate.org/projects/immich). These rely entirely on the community; if you speak a language that isn't fully translated yet, submitting translations there is greatly appreciated! - -### Datasets - -Help us improve our [Immich Datasets](https://datasets.immich.app) by submitting photos and videos taken from a variety of devices, including smartphones, DSLRs, and action cameras, as well as photos with unique features, such as panoramas, burst photos, and photo spheres. These datasets will be publically available for anyone to use, do not submit private/sensitive photos. - -### Community support - -If you like helping others, answering Q&A discussions here on GitHub and replying to people on our Discord is also always appreciated. diff --git a/Makefile b/Makefile index 2fc1c5d801..0fc0bb0b4c 100644 --- a/Makefile +++ b/Makefile @@ -1,152 +1,68 @@ +DOCKER_COMPOSE_FILE := docker/docker-compose.yml + +.PHONY: dev dev-down open-api open-api-dart open-api-typescript \ + build-server build-web test-server test-medium test-e2e \ + lint-server lint-web format + +# Full development environment dev: - @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans + @echo "Starting Docker services (Postgres 18 + Redis)..." + docker compose -f $(DOCKER_COMPOSE_FILE) up -d + @echo "Waiting for services to be healthy..." + @sleep 3 + @echo "Starting web dev server (background)..." + pnpm --filter web dev & + @echo "Starting NestJS server with hot reload..." + pnpm --filter immich start:dev dev-down: - docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans + docker compose -f $(DOCKER_COMPOSE_FILE) down --remove-orphans + @-pkill -f "nest start" 2>/dev/null || true + @-pkill -f "vite dev" 2>/dev/null || true + @echo "All services stopped." -dev-update: - @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans - -dev-scale: - @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans - -dev-docs: - npm --prefix docs run start - -.PHONY: e2e -e2e: - @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans - -e2e-dev: - @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans - -e2e-update: - @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans - -e2e-down: - docker compose -f ./e2e/docker-compose.yml down --remove-orphans - -prod: - @trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans - -prod-down: - docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans - -prod-scale: - @trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans - -.PHONY: open-api +# OpenAPI SDK generation open-api: - cd ./open-api && bash ./bin/generate-open-api.sh + ./open-api/bin/generate-open-api.sh open-api-dart: - cd ./open-api && bash ./bin/generate-open-api.sh dart + cd open-api && npx --yes @openapitools/openapi-generator-cli generate -g dart -i ../server/immich-openapi-specs.json -o ../mobile/openapi open-api-typescript: - cd ./open-api && bash ./bin/generate-open-api.sh typescript + cd open-api && npx --yes oazapfts ../server/immich-openapi-specs.json --optimistic > typescript-sdk/src/fetch-client.ts -sql: - pnpm --filter immich run sync:sql +# Build targets +build-server: + pnpm --filter immich build -attach-server: - docker exec -it docker_immich-server_1 sh +build-web: + pnpm --filter web build -renovate: - LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset +# Test targets +test-server: + pnpm --filter immich test -# Directories that need to be created for volumes or build output -VOLUME_DIRS = \ - ./.pnpm-store \ - ./web/.svelte-kit \ - ./web/node_modules \ - ./web/coverage \ - ./e2e/node_modules \ - ./docs/node_modules \ - ./server/node_modules \ - ./open-api/typescript-sdk/node_modules \ - ./.github/node_modules \ - ./node_modules \ - ./cli/node_modules - -# Include .env file if it exists --include docker/.env - -MODULES = e2e server web cli sdk docs .github - -# directory to package name mapping function -# cli = @immich/cli -# docs = documentation -# e2e = immich-e2e -# open-api/typescript-sdk = @immich/sdk -# server = immich -# web = immich-web -map-package = $(subst sdk,@immich/sdk,$(subst cli,@immich/cli,$(subst docs,documentation,$(subst e2e,immich-e2e,$(subst server,immich,$(subst web,immich-web,$1)))))) - -audit-%: - pnpm --filter $(call map-package,$*) audit fix -install-%: - pnpm --filter $(call map-package,$*) install $(if $(FROZEN),--frozen-lockfile) $(if $(OFFLINE),--offline) -build-cli: build-sdk -build-web: build-sdk -build-%: install-% - pnpm --filter $(call map-package,$*) run build -format-%: - pnpm --filter $(call map-package,$*) run format:fix -lint-%: - pnpm --filter $(call map-package,$*) run lint:fix -check-%: - pnpm --filter $(call map-package,$*) run check -check-web: - pnpm --filter immich-web run check:typescript - pnpm --filter immich-web run check:svelte -test-%: - pnpm --filter $(call map-package,$*) run test -test-e2e: - docker compose -f ./e2e/docker-compose.yml build - pnpm --filter immich-e2e run test - pnpm --filter immich-e2e run test:web test-medium: - docker run \ - --rm \ - -v ./server/src:/usr/src/app/src \ - -v ./server/test:/usr/src/app/test \ - -v ./server/vitest.config.medium.mjs:/usr/src/app/vitest.config.medium.mjs \ - -v ./server/tsconfig.json:/usr/src/app/tsconfig.json \ - -e NODE_ENV=development \ - immich-server:latest \ - -c "pnpm test:medium -- --run" -test-medium-dev: - docker exec -it immich_server /bin/sh -c "pnpm run test:medium" + pnpm --filter immich test:medium -install-all: - pnpm -r --filter '!documentation' install +test-e2e: + pnpm --filter immich-e2e test -build-all: $(foreach M,$(filter-out e2e docs .github,$(MODULES)),build-$M) ; +# Lint & format +lint-server: + pnpm --filter immich lint -check-all: - pnpm -r --filter '!documentation' run "/^(check|check\:svelte|check\:typescript)$/" -lint-all: - pnpm -r --filter '!documentation' run lint:fix -format-all: - pnpm -r --filter '!documentation' run format:fix -audit-all: - pnpm -r --filter '!documentation' audit fix -hygiene-all: audit-all - pnpm -r --filter '!documentation' run "/(format:fix|check|check:svelte|check:typescript|sql)/" +lint-web: + pnpm --filter web lint -test-all: - pnpm -r --filter '!documentation' run "/^test/" +format: + pnpm format +# Install dependencies +install: + pnpm install + +# Clean everything clean: - find . -name "node_modules" -type d -prune -exec rm -rf {} + - find . -name "dist" -type d -prune -exec rm -rf '{}' + - find . -name "build" -type d -prune -exec rm -rf '{}' + - find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' + - find . -name "coverage" -type d -prune -exec rm -rf '{}' + - find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' + - command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true - command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true - - -setup-server-dev: install-server -setup-web-dev: install-sdk build-sdk install-web + docker compose -f $(DOCKER_COMPOSE_FILE) down -v --remove-orphans + rm -rf node_modules server/node_modules web/node_modules e2e/node_modules diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 1877440a8d..0000000000 --- a/SECURITY.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -Please report security issues to `security@immich.app` diff --git a/cli/.editorconfig b/cli/.editorconfig deleted file mode 100644 index eaf1b2fe43..0000000000 --- a/cli/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -charset = utf-8 -trim_trailing_whitespace = true - -[*.{ts,js}] -quote_type = single - -[*.{md,mdx}] -max_line_length = off -trim_trailing_whitespace = false - -[*.{yml,yaml}] -quote_type = double \ No newline at end of file diff --git a/cli/.gitignore b/cli/.gitignore deleted file mode 100644 index a26b03fe6f..0000000000 --- a/cli/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -*-debug.log -*-error.log -/.nyc_output -/dist -/lib -/tmp -/yarn.lock -node_modules -oclif.manifest.json - -.vscode -.idea -/coverage/ -.reverse-geocoding-dump/ -upload/ \ No newline at end of file diff --git a/cli/.npmignore b/cli/.npmignore deleted file mode 100644 index fab798db68..0000000000 --- a/cli/.npmignore +++ /dev/null @@ -1,15 +0,0 @@ -**/*.spec.js -coverage/** -src/** -upload/** -.editorconfig -.eslintignore -.eslintrc.cjs -.gitignore -.prettierignore -.prettierrc -Dockerfile -package-lock.json -tsconfig.json -vite.config.ts -vitest.config.ts diff --git a/cli/.nvmrc b/cli/.nvmrc deleted file mode 100644 index 3fe3b1570a..0000000000 --- a/cli/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.13.0 diff --git a/cli/.prettierignore b/cli/.prettierignore deleted file mode 100644 index 27a20b3f50..0000000000 --- a/cli/.prettierignore +++ /dev/null @@ -1,17 +0,0 @@ -.DS_Store -node_modules -/build -/package -.env -.env.* -!.env.example -*.md -*.json -coverage -dist -**/migrations/** - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/cli/.prettierrc b/cli/.prettierrc deleted file mode 100644 index cde9f154ce..0000000000 --- a/cli/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "printWidth": 120, - "semi": true, - "plugins": ["prettier-plugin-organize-imports"], - "organizeImportsSkipDestructiveCodeActions": true -} diff --git a/cli/Dockerfile b/cli/Dockerfile deleted file mode 100644 index d56190ee16..0000000000 --- a/cli/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core - -WORKDIR /usr/src/app -COPY package* pnpm* .pnpmfile.cjs ./ -COPY ./cli ./cli/ -COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/ -RUN corepack enable pnpm && \ - pnpm install --filter @immich/sdk --filter @immich/cli --frozen-lockfile && \ - pnpm --filter @immich/sdk build && \ - pnpm --filter @immich/cli build - -WORKDIR /import - -ENTRYPOINT ["node", "/usr/src/app/cli/dist"] diff --git a/cli/LICENSE b/cli/LICENSE deleted file mode 100644 index 94ae15a0fc..0000000000 --- a/cli/LICENSE +++ /dev/null @@ -1,620 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index b9d61fce09..0000000000 --- a/cli/README.md +++ /dev/null @@ -1,38 +0,0 @@ -A command-line interface for interfacing with the self-hosted photo manager [Immich](https://immich.app/). - -Please see the [Immich CLI documentation](https://docs.immich.app/features/command-line-interface). - -# For developers - -Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder: - - $ pnpm install - $ pnpm run build - -Then, to build the open-api client run the following in the open-api folder: - - $ ./bin/generate-open-api.sh - -## Run from build - -Go to the cli folder and build it: - - $ pnpm install - $ pnpm run build - $ node dist/index.js - -## Run and Debug from source (VSCode) - -With VScode you can run and debug the Immich CLI. Go to the launch.json file, find the Immich CLI config and change this with the command you need to debug - -`"args": ["upload", "--help"],` - -replace that for the command of your choice. - -## Install from build - -You can also build and install the CLI using - - $ pnpm run build - $ pnpm install -g . -**** diff --git a/cli/bin/immich b/cli/bin/immich deleted file mode 100755 index 924fff1230..0000000000 --- a/cli/bin/immich +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -import '../dist/index.js'; diff --git a/cli/eslint.config.mjs b/cli/eslint.config.mjs deleted file mode 100644 index 101d91bea4..0000000000 --- a/cli/eslint.config.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import js from '@eslint/js'; -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; -import eslintPluginUnicorn from 'eslint-plugin-unicorn'; -import globals from 'globals'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import typescriptEslint from 'typescript-eslint'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -export default typescriptEslint.config([ - eslintPluginUnicorn.configs.recommended, - eslintPluginPrettierRecommended, - js.configs.recommended, - typescriptEslint.configs.recommended, - { - ignores: ['eslint.config.mjs', 'dist'], - }, - { - languageOptions: { - globals: { - ...globals.node, - }, - - parser: typescriptEslint.parser, - ecmaVersion: 5, - sourceType: 'module', - - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - }, - }, - - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-floating-promises': 'error', - 'unicorn/prefer-module': 'off', - 'unicorn/prevent-abbreviations': 'off', - 'unicorn/no-process-exit': 'off', - 'unicorn/import-style': 'off', - curly: 2, - 'prettier/prettier': 0, - 'object-shorthand': ['error', 'always'], - }, - }, -]); diff --git a/cli/mise.toml b/cli/mise.toml deleted file mode 100644 index 740184e03d..0000000000 --- a/cli/mise.toml +++ /dev/null @@ -1,29 +0,0 @@ -[tasks.install] -run = "pnpm install --filter @immich/cli --frozen-lockfile" - -[tasks.build] -env._.path = "./node_modules/.bin" -run = "vite build" - -[tasks.test] -env._.path = "./node_modules/.bin" -run = "vite" - -[tasks.lint] -env._.path = "./node_modules/.bin" -run = "eslint \"src/**/*.ts\" --max-warnings 0" - -[tasks."lint-fix"] -run = { task = "lint --fix" } - -[tasks.format] -env._.path = "./node_modules/.bin" -run = "prettier --check ." - -[tasks."format-fix"] -env._.path = "./node_modules/.bin" -run = "prettier --write ." - -[tasks.check] -env._.path = "./node_modules/.bin" -run = "tsc --noEmit" diff --git a/cli/package.json b/cli/package.json deleted file mode 100644 index 3d3bae1914..0000000000 --- a/cli/package.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "name": "@immich/cli", - "version": "2.5.5", - "description": "Command Line Interface (CLI) for Immich", - "type": "module", - "exports": "./dist/index.js", - "bin": { - "immich": "./bin/immich" - }, - "license": "GNU Affero General Public License version 3", - "keywords": [ - "immich", - "cli" - ], - "devDependencies": { - "@eslint/js": "^9.8.0", - "@immich/sdk": "file:../open-api/typescript-sdk", - "@types/byte-size": "^8.1.0", - "@types/cli-progress": "^3.11.0", - "@types/lodash-es": "^4.17.12", - "@types/micromatch": "^4.0.9", - "@types/mock-fs": "^4.13.1", - "@types/node": "^24.10.9", - "@vitest/coverage-v8": "^3.0.0", - "byte-size": "^9.0.0", - "cli-progress": "^3.12.0", - "commander": "^12.0.0", - "eslint": "^9.14.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-unicorn": "^62.0.0", - "globals": "^16.0.0", - "mock-fs": "^5.2.0", - "prettier": "^3.7.4", - "prettier-plugin-organize-imports": "^4.0.0", - "typescript": "^5.3.3", - "typescript-eslint": "^8.28.0", - "vite": "^7.0.0", - "vite-tsconfig-paths": "^6.0.0", - "vitest": "^3.0.0", - "vitest-fetch-mock": "^0.4.0", - "yaml": "^2.3.1" - }, - "scripts": { - "build": "vite build", - "build:dev": "vite build --sourcemap true", - "lint": "eslint \"src/**/*.ts\" --max-warnings 0", - "lint:fix": "npm run lint -- --fix", - "prepack": "npm run build", - "test": "vitest", - "test:cov": "vitest --coverage", - "format": "prettier --check .", - "format:fix": "prettier --write .", - "check": "tsc --noEmit" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/immich-app/immich.git", - "directory": "cli" - }, - "engines": { - "node": ">=20.0.0" - }, - "dependencies": { - "chokidar": "^4.0.3", - "fast-glob": "^3.3.2", - "fastq": "^1.17.1", - "lodash-es": "^4.17.21", - "micromatch": "^4.0.8" - }, - "volta": { - "node": "24.13.0" - } -} diff --git a/cli/src/commands/asset.spec.ts b/cli/src/commands/asset.spec.ts deleted file mode 100644 index 7dce135985..0000000000 --- a/cli/src/commands/asset.spec.ts +++ /dev/null @@ -1,311 +0,0 @@ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; -import { setTimeout as sleep } from 'node:timers/promises'; -import { describe, expect, it, MockedFunction, vi } from 'vitest'; - -import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk'; -import createFetchMock from 'vitest-fetch-mock'; - -import { checkForDuplicates, getAlbumName, startWatch, uploadFiles, UploadOptionsDto } from 'src/commands/asset'; - -vi.mock('@immich/sdk'); - -describe('getAlbumName', () => { - it('should return a non-undefined value', () => { - if (os.platform() === 'win32') { - // This is meaningless for Unix systems. - expect(getAlbumName(String.raw`D:\test\Filename.txt`, {} as UploadOptionsDto)).toBe('test'); - } - expect(getAlbumName('D:/parentfolder/test/Filename.txt', {} as UploadOptionsDto)).toBe('test'); - }); - - it('has higher priority to return `albumName` in `options`', () => { - expect(getAlbumName('/parentfolder/test/Filename.txt', { albumName: 'example' } as UploadOptionsDto)).toBe( - 'example', - ); - }); -}); - -describe('uploadFiles', () => { - const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-')); - const testFilePath = path.join(testDir, 'test.png'); - const testFileData = 'test'; - const baseUrl = 'http://example.com'; - const apiKey = 'key'; - const retry = 3; - - const fetchMocker = createFetchMock(vi); - - beforeEach(() => { - // Create a test file - fs.writeFileSync(testFilePath, testFileData); - - // Defaults - vi.mocked(defaults).baseUrl = baseUrl; - vi.mocked(defaults).headers = { 'x-api-key': apiKey }; - - fetchMocker.enableMocks(); - fetchMocker.resetMocks(); - }); - - it('returns new assets when upload file is successful', async () => { - fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => { - return { - status: 200, - body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }), - }; - }); - - await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([ - { - filepath: testFilePath, - id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', - }, - ]); - }); - - it('returns new assets when upload file retry is successful', async () => { - let counter = 0; - fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => { - counter++; - if (counter < retry) { - throw new Error('Network error'); - } - - return { - status: 200, - body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }), - }; - }); - - await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([ - { - filepath: testFilePath, - id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', - }, - ]); - }); - - it('returns new assets when upload file retry is failed', async () => { - fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => { - throw new Error('Network error'); - }); - - await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([]); - }); -}); - -describe('checkForDuplicates', () => { - const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-')); - const testFilePath = path.join(testDir, 'test.png'); - const testFileData = 'test'; - const testFileChecksum = 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'; // SHA1 - const retry = 3; - - beforeEach(() => { - // Create a test file - fs.writeFileSync(testFilePath, testFileData); - }); - - it('checks duplicates', async () => { - vi.mocked(checkBulkUpload).mockResolvedValue({ - results: [ - { - action: Action.Accept, - id: testFilePath, - }, - ], - }); - - await checkForDuplicates([testFilePath], { concurrency: 1 }); - - expect(checkBulkUpload).toHaveBeenCalledWith({ - assetBulkUploadCheckDto: { - assets: [ - { - checksum: testFileChecksum, - id: testFilePath, - }, - ], - }, - }); - }); - - it('returns duplicates when check duplicates is rejected', async () => { - vi.mocked(checkBulkUpload).mockResolvedValue({ - results: [ - { - action: Action.Reject, - id: testFilePath, - assetId: 'fc5621b1-86f6-44a1-9905-403e607df9f5', - reason: Reason.Duplicate, - }, - ], - }); - - await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({ - duplicates: [ - { - filepath: testFilePath, - id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', - }, - ], - newFiles: [], - }); - }); - - it('returns new assets when check duplicates is accepted', async () => { - vi.mocked(checkBulkUpload).mockResolvedValue({ - results: [ - { - action: Action.Accept, - id: testFilePath, - }, - ], - }); - - await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({ - duplicates: [], - newFiles: [testFilePath], - }); - }); - - it('returns results when check duplicates retry is successful', async () => { - let mocked = vi.mocked(checkBulkUpload); - for (let i = 1; i < retry; i++) { - mocked = mocked.mockRejectedValueOnce(new Error('Network error')); - } - mocked.mockResolvedValue({ - results: [ - { - action: Action.Accept, - id: testFilePath, - }, - ], - }); - - await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({ - duplicates: [], - newFiles: [testFilePath], - }); - }); - - it('returns results when check duplicates retry is failed', async () => { - vi.mocked(checkBulkUpload).mockRejectedValue(new Error('Network error')); - - await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({ - duplicates: [], - newFiles: [], - }); - }); -}); - -describe('startWatch', () => { - let testFolder: string; - let checkBulkUploadMocked: MockedFunction; - - beforeEach(async () => { - vi.restoreAllMocks(); - - vi.mocked(getSupportedMediaTypes).mockResolvedValue({ - image: ['.jpg'], - sidecar: ['.xmp'], - video: ['.mp4'], - }); - - testFolder = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'test-startWatch-')); - checkBulkUploadMocked = vi.mocked(checkBulkUpload); - checkBulkUploadMocked.mockResolvedValue({ - results: [], - }); - }); - - it('should start watching a directory and upload new files', async () => { - const testFilePath = path.join(testFolder, 'test.jpg'); - - await startWatch([testFolder], { concurrency: 1 }, { batchSize: 1, debounceTimeMs: 10 }); - await sleep(100); // to debounce the watcher from considering the test file as a existing file - await fs.promises.writeFile(testFilePath, 'testjpg'); - - await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000); - expect(checkBulkUpload).toHaveBeenCalledWith({ - assetBulkUploadCheckDto: { - assets: [ - expect.objectContaining({ - id: testFilePath, - }), - ], - }, - }); - }); - - it('should filter out unsupported files', async () => { - const testFilePath = path.join(testFolder, 'test.jpg'); - const unsupportedFilePath = path.join(testFolder, 'test.txt'); - - await startWatch([testFolder], { concurrency: 1 }, { batchSize: 1, debounceTimeMs: 10 }); - await sleep(100); // to debounce the watcher from considering the test file as a existing file - await fs.promises.writeFile(testFilePath, 'testjpg'); - await fs.promises.writeFile(unsupportedFilePath, 'testtxt'); - - await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000); - expect(checkBulkUpload).toHaveBeenCalledWith({ - assetBulkUploadCheckDto: { - assets: expect.arrayContaining([ - expect.objectContaining({ - id: testFilePath, - }), - ]), - }, - }); - - expect(checkBulkUpload).not.toHaveBeenCalledWith({ - assetBulkUploadCheckDto: { - assets: expect.arrayContaining([ - expect.objectContaining({ - id: unsupportedFilePath, - }), - ]), - }, - }); - }); - - it('should filter out ignored patterns', async () => { - const testFilePath = path.join(testFolder, 'test.jpg'); - const ignoredPattern = 'ignored'; - const ignoredFolder = path.join(testFolder, ignoredPattern); - await fs.promises.mkdir(ignoredFolder, { recursive: true }); - const ignoredFilePath = path.join(ignoredFolder, 'ignored.jpg'); - - await startWatch([testFolder], { concurrency: 1, ignore: ignoredPattern }, { batchSize: 1, debounceTimeMs: 10 }); - await sleep(100); // to debounce the watcher from considering the test file as a existing file - await fs.promises.writeFile(testFilePath, 'testjpg'); - await fs.promises.writeFile(ignoredFilePath, 'ignoredjpg'); - - await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000); - expect(checkBulkUpload).toHaveBeenCalledWith({ - assetBulkUploadCheckDto: { - assets: expect.arrayContaining([ - expect.objectContaining({ - id: testFilePath, - }), - ]), - }, - }); - - expect(checkBulkUpload).not.toHaveBeenCalledWith({ - assetBulkUploadCheckDto: { - assets: expect.arrayContaining([ - expect.objectContaining({ - id: ignoredFilePath, - }), - ]), - }, - }); - }); - - afterEach(async () => { - await fs.promises.rm(testFolder, { recursive: true, force: true }); - }); -}); diff --git a/cli/src/commands/asset.ts b/cli/src/commands/asset.ts deleted file mode 100644 index ff7b609eef..0000000000 --- a/cli/src/commands/asset.ts +++ /dev/null @@ -1,538 +0,0 @@ -import { - Action, - AssetBulkUploadCheckItem, - AssetBulkUploadCheckResult, - AssetMediaResponseDto, - AssetMediaStatus, - addAssetsToAlbum, - checkBulkUpload, - createAlbum, - defaults, - getAllAlbums, - getSupportedMediaTypes, -} from '@immich/sdk'; -import byteSize from 'byte-size'; -import { Matcher, watch as watchFs } from 'chokidar'; -import { MultiBar, Presets, SingleBar } from 'cli-progress'; -import { chunk } from 'lodash-es'; -import micromatch from 'micromatch'; -import { Stats, createReadStream } from 'node:fs'; -import { stat, unlink } from 'node:fs/promises'; -import path, { basename } from 'node:path'; -import { Queue } from 'src/queue'; -import { BaseOptions, Batcher, authenticate, crawl, sha1 } from 'src/utils'; - -const UPLOAD_WATCH_BATCH_SIZE = 100; -const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 10_000; - -const s = (count: number) => (count === 1 ? '' : 's'); - -// TODO figure out why `id` is missing -type AssetBulkUploadCheckResults = Array; -type Asset = { id: string; filepath: string }; - -export interface UploadOptionsDto { - recursive?: boolean; - ignore?: string; - dryRun?: boolean; - skipHash?: boolean; - delete?: boolean; - deleteDuplicates?: boolean; - album?: boolean; - albumName?: string; - includeHidden?: boolean; - concurrency: number; - progress?: boolean; - watch?: boolean; - jsonOutput?: boolean; -} - -class UploadFile extends File { - constructor( - private filepath: string, - private _size: number, - ) { - super([], basename(filepath)); - } - - get size() { - return this._size; - } - - stream() { - return createReadStream(this.filepath) as any; - } -} - -const uploadBatch = async (files: string[], options: UploadOptionsDto) => { - const { newFiles, duplicates } = await checkForDuplicates(files, options); - const newAssets = await uploadFiles(newFiles, options); - if (options.jsonOutput) { - console.log(JSON.stringify({ newFiles, duplicates, newAssets }, undefined, 4)); - } - await updateAlbums([...newAssets, ...duplicates], options); - - await deleteFiles(newAssets, duplicates, options); -}; - -export const startWatch = async ( - paths: string[], - options: UploadOptionsDto, - { - batchSize = UPLOAD_WATCH_BATCH_SIZE, - debounceTimeMs = UPLOAD_WATCH_DEBOUNCE_TIME_MS, - }: { batchSize?: number; debounceTimeMs?: number } = {}, -) => { - const watcherIgnored: Matcher[] = []; - const { image, video } = await getSupportedMediaTypes(); - const extensions = new Set([...image, ...video]); - - if (options.ignore) { - watcherIgnored.push((path) => micromatch.contains(path, `**/${options.ignore}`)); - } - - const pathsBatcher = new Batcher({ - batchSize, - debounceTimeMs, - onBatch: async (paths: string[]) => { - const uniquePaths = [...new Set(paths)]; - await uploadBatch(uniquePaths, options); - }, - }); - - const onFile = async (path: string, stats?: Stats) => { - if (stats?.isDirectory()) { - return; - } - const ext = '.' + path.split('.').pop()?.toLowerCase(); - if (!ext || !extensions.has(ext)) { - return; - } - - if (!options.progress) { - // logging when progress is disabled as it can cause issues with the progress bar rendering - console.log(`Change detected: ${path}`); - } - pathsBatcher.add(path); - }; - const fsWatcher = watchFs(paths, { - ignoreInitial: true, - ignored: watcherIgnored, - alwaysStat: true, - awaitWriteFinish: true, - depth: options.recursive ? undefined : 1, - persistent: true, - }) - .on('add', onFile) - .on('change', onFile) - .on('error', (error) => console.error(`Watcher error: ${error}`)); - - process.on('SIGINT', async () => { - console.log('Exiting...'); - await fsWatcher.close(); - process.exit(); - }); -}; - -export const upload = async (paths: string[], baseOptions: BaseOptions, options: UploadOptionsDto) => { - await authenticate(baseOptions); - - const scanFiles = await scan(paths, options); - - if (scanFiles.length === 0) { - if (options.watch) { - console.log('No files found initially.'); - } else { - console.log('No files found, exiting'); - return; - } - } - - if (options.watch) { - console.log('Watching for changes...'); - await startWatch(paths, options); - // watcher does not handle the initial scan - // as the scan() is a more efficient quick start with batched results - } - - await uploadBatch(scanFiles, options); -}; - -const scan = async (pathsToCrawl: string[], options: UploadOptionsDto) => { - const { image, video } = await getSupportedMediaTypes(); - - console.log('Crawling for assets...'); - const files = await crawl({ - pathsToCrawl, - recursive: options.recursive, - exclusionPattern: options.ignore, - includeHidden: options.includeHidden, - extensions: [...image, ...video], - }); - - return files; -}; - -export const checkForDuplicates = async (files: string[], { concurrency, skipHash, progress }: UploadOptionsDto) => { - if (skipHash) { - console.log('Skipping hash check, assuming all files are new'); - return { newFiles: files, duplicates: [] }; - } - - let multiBar: MultiBar | undefined; - - if (progress) { - multiBar = new MultiBar( - { format: '{message} | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' }, - Presets.shades_classic, - ); - } else { - console.log(`Received ${files.length} files, hashing...`); - } - - const hashProgressBar = multiBar?.create(files.length, 0, { message: 'Hashing files ' }); - const checkProgressBar = multiBar?.create(files.length, 0, { message: 'Checking for duplicates' }); - - const newFiles: string[] = []; - const duplicates: Asset[] = []; - - const checkBulkUploadQueue = new Queue( - async (assets: AssetBulkUploadCheckItem[]) => { - const response = await checkBulkUpload({ assetBulkUploadCheckDto: { assets } }); - - const results = response.results as AssetBulkUploadCheckResults; - - for (const { id: filepath, assetId, action } of results) { - if (action === Action.Accept) { - newFiles.push(filepath); - } else { - // rejects are always duplicates - duplicates.push({ id: assetId as string, filepath }); - } - } - - checkProgressBar?.increment(assets.length); - }, - { concurrency, retry: 3 }, - ); - - const results: { id: string; checksum: string }[] = []; - let checkBulkUploadRequests: AssetBulkUploadCheckItem[] = []; - - const queue = new Queue( - async (filepath: string): Promise => { - const dto = { id: filepath, checksum: await sha1(filepath) }; - - results.push(dto); - checkBulkUploadRequests.push(dto); - if (checkBulkUploadRequests.length === 5000) { - const batch = checkBulkUploadRequests; - checkBulkUploadRequests = []; - void checkBulkUploadQueue.push(batch); - } - - hashProgressBar?.increment(); - return results; - }, - { concurrency, retry: 3 }, - ); - - for (const item of files) { - void queue.push(item); - } - - await queue.drained(); - - if (checkBulkUploadRequests.length > 0) { - void checkBulkUploadQueue.push(checkBulkUploadRequests); - } - - await checkBulkUploadQueue.drained(); - - multiBar?.stop(); - - console.log(`Found ${newFiles.length} new files and ${duplicates.length} duplicate${s(duplicates.length)}`); - - // Report failures - const failedTasks = queue.tasks.filter((task) => task.status === 'failed'); - if (failedTasks.length > 0) { - console.log(`Failed to verify ${failedTasks.length} file${s(failedTasks.length)}:`); - for (const task of failedTasks) { - console.log(`- ${task.data} - ${task.error}`); - } - } - - return { newFiles, duplicates }; -}; - -export const uploadFiles = async ( - files: string[], - { dryRun, concurrency, progress }: UploadOptionsDto, -): Promise => { - if (files.length === 0) { - console.log('All assets were already uploaded, nothing to do.'); - return []; - } - - // Compute total size first - let totalSize = 0; - const statsMap = new Map(); - for (const filepath of files) { - const stats = await stat(filepath); - statsMap.set(filepath, stats); - totalSize += stats.size; - } - - if (dryRun) { - console.log(`Would have uploaded ${files.length} asset${s(files.length)} (${byteSize(totalSize)})`); - return files.map((filepath) => ({ id: '', filepath })); - } - - let uploadProgress: SingleBar | undefined; - - if (progress) { - uploadProgress = new SingleBar( - { - format: 'Uploading assets | {bar} | {percentage}% | ETA: {eta_formatted} | {value_formatted}/{total_formatted}', - }, - Presets.shades_classic, - ); - } else { - console.log(`Uploading ${files.length} asset${s(files.length)} (${byteSize(totalSize)})`); - } - uploadProgress?.start(totalSize, 0); - uploadProgress?.update({ value_formatted: 0, total_formatted: byteSize(totalSize) }); - - let duplicateCount = 0; - let duplicateSize = 0; - let successCount = 0; - let successSize = 0; - - const newAssets: Asset[] = []; - - const queue = new Queue( - async (filepath: string) => { - const stats = statsMap.get(filepath); - if (!stats) { - throw new Error(`Stats not found for ${filepath}`); - } - - const response = await uploadFile(filepath, stats); - newAssets.push({ id: response.id, filepath }); - if (response.status === AssetMediaStatus.Duplicate) { - duplicateCount++; - duplicateSize += stats.size ?? 0; - } else { - successCount++; - successSize += stats.size ?? 0; - } - - uploadProgress?.update(successSize, { value_formatted: byteSize(successSize + duplicateSize) }); - - return response; - }, - { concurrency, retry: 3 }, - ); - - for (const item of files) { - void queue.push(item); - } - - await queue.drained(); - - uploadProgress?.stop(); - - console.log(`Successfully uploaded ${successCount} new asset${s(successCount)} (${byteSize(successSize)})`); - if (duplicateCount > 0) { - console.log(`Skipped ${duplicateCount} duplicate asset${s(duplicateCount)} (${byteSize(duplicateSize)})`); - } - - // Report failures - const failedTasks = queue.tasks.filter((task) => task.status === 'failed'); - if (failedTasks.length > 0) { - console.log(`Failed to upload ${failedTasks.length} asset${s(failedTasks.length)}:`); - for (const task of failedTasks) { - console.log(`- ${task.data} - ${task.error}`); - } - } - - return newAssets; -}; - -const uploadFile = async (input: string, stats: Stats): Promise => { - const { baseUrl, headers } = defaults; - - const assetPath = path.parse(input); - const noExtension = path.join(assetPath.dir, assetPath.name); - - const sidecarsFiles = await Promise.all( - // XMP sidecars can come in two filename formats. For a photo named photo.ext, the filenames are photo.ext.xmp and photo.xmp - [`${noExtension}.xmp`, `${input}.xmp`].map(async (sidecarPath) => { - try { - const stats = await stat(sidecarPath); - return new UploadFile(sidecarPath, stats.size); - } catch { - return false; - } - }), - ); - - const sidecarData = sidecarsFiles.find((file): file is UploadFile => file !== false); - - const formData = new FormData(); - formData.append('deviceAssetId', `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, '')); - formData.append('deviceId', 'CLI'); - formData.append('fileCreatedAt', stats.mtime.toISOString()); - formData.append('fileModifiedAt', stats.mtime.toISOString()); - formData.append('fileSize', String(stats.size)); - formData.append('isFavorite', 'false'); - formData.append('assetData', new UploadFile(input, stats.size)); - - if (sidecarData) { - formData.append('sidecarData', sidecarData); - } - - const response = await fetch(`${baseUrl}/assets`, { - method: 'post', - redirect: 'error', - headers: headers as Record, - body: formData, - }); - if (response.status !== 200 && response.status !== 201) { - throw new Error(await response.text()); - } - - return response.json(); -}; - -const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: UploadOptionsDto): Promise => { - let fileCount = 0; - if (options.delete) { - fileCount += uploaded.length; - } - - if (options.deleteDuplicates) { - fileCount += duplicates.length; - } - - if (options.dryRun) { - console.log(`Would have deleted ${fileCount} local asset${s(fileCount)}`); - return; - } - - if (fileCount === 0) { - return; - } - - console.log('Deleting assets that have been uploaded...'); - const deletionProgress = new SingleBar( - { format: 'Deleting local assets | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' }, - Presets.shades_classic, - ); - deletionProgress.start(fileCount, 0); - - const chunkDelete = async (files: Asset[]) => { - for (const assetBatch of chunk(files, options.concurrency)) { - await Promise.all(assetBatch.map((input: Asset) => unlink(input.filepath))); - deletionProgress.update(assetBatch.length); - } - }; - - try { - if (options.delete) { - await chunkDelete(uploaded); - } - - if (options.deleteDuplicates) { - await chunkDelete(duplicates); - } - } finally { - deletionProgress.stop(); - } -}; - -const updateAlbums = async (assets: Asset[], options: UploadOptionsDto) => { - if (!options.album && !options.albumName) { - return; - } - const { dryRun, concurrency } = options; - - const albums = await getAllAlbums({}); - const existingAlbums = new Map(albums.map((album) => [album.albumName, album.id])); - const newAlbums: Set = new Set(); - for (const { filepath } of assets) { - const albumName = getAlbumName(filepath, options); - if (albumName && !existingAlbums.has(albumName)) { - newAlbums.add(albumName); - } - } - - if (dryRun) { - // TODO print asset counts for new albums - console.log(`Would have created ${newAlbums.size} new album${s(newAlbums.size)}`); - console.log(`Would have updated albums of ${assets.length} asset${s(assets.length)}`); - return; - } - - const progressBar = new SingleBar( - { format: 'Creating albums | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} albums' }, - Presets.shades_classic, - ); - progressBar.start(newAlbums.size, 0); - - try { - for (const albumNames of chunk([...newAlbums], concurrency)) { - const items = await Promise.all( - albumNames.map((albumName: string) => createAlbum({ createAlbumDto: { albumName } })), - ); - for (const { id, albumName } of items) { - existingAlbums.set(albumName, id); - } - progressBar.increment(albumNames.length); - } - } finally { - progressBar.stop(); - } - - console.log(`Successfully created ${newAlbums.size} new album${s(newAlbums.size)}`); - console.log(`Successfully updated ${assets.length} asset${s(assets.length)}`); - - const albumToAssets = new Map(); - for (const asset of assets) { - const albumName = getAlbumName(asset.filepath, options); - if (!albumName) { - continue; - } - const albumId = existingAlbums.get(albumName); - if (albumId) { - if (!albumToAssets.has(albumId)) { - albumToAssets.set(albumId, []); - } - albumToAssets.get(albumId)?.push(asset.id); - } - } - - const albumUpdateProgress = new SingleBar( - { format: 'Adding assets to albums | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' }, - Presets.shades_classic, - ); - albumUpdateProgress.start(assets.length, 0); - - try { - for (const [albumId, assets] of albumToAssets.entries()) { - for (const assetBatch of chunk(assets, Math.min(1000 * concurrency, 65_000))) { - await addAssetsToAlbum({ id: albumId, bulkIdsDto: { ids: assetBatch } }); - albumUpdateProgress.increment(assetBatch.length); - } - } - } finally { - albumUpdateProgress.stop(); - } -}; - -// `filepath` valid format: -// - Windows: `D:\\test\\Filename.txt` or `D:/test/Filename.txt` -// - Unix: `/test/Filename.txt` -export const getAlbumName = (filepath: string, options: UploadOptionsDto) => { - return options.albumName ?? path.basename(path.dirname(filepath)); -}; diff --git a/cli/src/commands/auth.ts b/cli/src/commands/auth.ts deleted file mode 100644 index f0011c6a24..0000000000 --- a/cli/src/commands/auth.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { getMyUser } from '@immich/sdk'; -import { existsSync } from 'node:fs'; -import { mkdir, unlink } from 'node:fs/promises'; -import { BaseOptions, connect, getAuthFilePath, logError, withError, writeAuthFile } from 'src/utils'; - -export const login = async (url: string, key: string, options: BaseOptions) => { - console.log(`Logging in to ${url}`); - - const { configDirectory: configDir } = options; - - await connect(url, key); - - const [error, user] = await withError(getMyUser()); - if (error) { - logError(error, 'Failed to load user info'); - process.exit(1); - } - - console.log(`Logged in as ${user.email}`); - - if (!existsSync(configDir)) { - // Create config folder if it doesn't exist - const created = await mkdir(configDir, { recursive: true }); - if (!created) { - console.log(`Failed to create config folder: ${configDir}`); - return; - } - } - - await writeAuthFile(configDir, { url, key }); - - console.log(`Wrote auth info to ${getAuthFilePath(configDir)}`); -}; - -export const logout = async (options: BaseOptions) => { - console.log('Logging out...'); - - const { configDirectory: configDir } = options; - - const authFile = getAuthFilePath(configDir); - - if (existsSync(authFile)) { - await unlink(authFile); - console.log(`Removed auth file: ${authFile}`); - } - - console.log('Successfully logged out'); -}; diff --git a/cli/src/commands/server-info.ts b/cli/src/commands/server-info.ts deleted file mode 100644 index bea49231c9..0000000000 --- a/cli/src/commands/server-info.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getAssetStatistics, getMyUser, getServerVersion, getSupportedMediaTypes } from '@immich/sdk'; -import { BaseOptions, authenticate } from 'src/utils'; - -export const serverInfo = async (options: BaseOptions) => { - const { url } = await authenticate(options); - - const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([ - getServerVersion(), - getSupportedMediaTypes(), - getAssetStatistics({}), - getMyUser(), - ]); - - console.log(`Server Info (via ${userInfo.email})`); - console.log(` Url: ${url}`); - console.log(` Version: ${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}`); - console.log(` Formats:`); - console.log(` Images: ${mediaTypes.image.map((extension) => extension.replace('.', ''))}`); - console.log(` Videos: ${mediaTypes.video.map((extension) => extension.replace('.', ''))}`); - console.log(` Statistics:`); - console.log(` Images: ${stats.images}`); - console.log(` Videos: ${stats.videos}`); - console.log(` Total: ${stats.total}`); -}; diff --git a/cli/src/index.ts b/cli/src/index.ts deleted file mode 100644 index 0bc2e5de37..0000000000 --- a/cli/src/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -#! /usr/bin/env node -import { Command, Option } from 'commander'; -import os from 'node:os'; -import path from 'node:path'; -import { upload } from 'src/commands/asset'; -import { login, logout } from 'src/commands/auth'; -import { serverInfo } from 'src/commands/server-info'; -import { version } from '../package.json'; - -const defaultConfigDirectory = path.join(os.homedir(), '.config/immich/'); -const defaultConcurrency = Math.max(1, os.cpus().length - 1); - -const program = new Command() - .name('immich') - .version(version) - .description('Command line interface for Immich') - .addOption( - new Option('-d, --config-directory ', 'Configuration directory where auth.yml will be stored') - .env('IMMICH_CONFIG_DIR') - .default(defaultConfigDirectory), - ) - .addOption(new Option('-u, --url [url]', 'Immich server URL').env('IMMICH_INSTANCE_URL')) - .addOption(new Option('-k, --key [key]', 'Immich API key').env('IMMICH_API_KEY')); - -program - .command('login') - .alias('login-key') - .description('Login using an API key') - .argument('url', 'Immich server URL') - .argument('key', 'Immich API key') - .action((url, key) => login(url, key, program.opts())); - -program - .command('logout') - .description('Remove stored credentials') - .action(() => logout(program.opts())); - -program - .command('server-info') - .description('Display server information') - .action(() => serverInfo(program.opts())); - -program - .command('upload') - .description('Upload assets') - .usage('[paths...] [options]') - .addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false)) - .addOption(new Option('-i, --ignore ', 'Pattern to ignore').env('IMMICH_IGNORE_PATHS')) - .addOption(new Option('-h, --skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false)) - .addOption(new Option('-H, --include-hidden', 'Include hidden folders').env('IMMICH_INCLUDE_HIDDEN').default(false)) - .addOption( - new Option('-a, --album', 'Automatically create albums based on folder name') - .env('IMMICH_AUTO_CREATE_ALBUM') - .default(false), - ) - .addOption( - new Option('-A, --album-name ', 'Add all assets to specified album') - .env('IMMICH_ALBUM_NAME') - .conflicts('album'), - ) - .addOption( - new Option('-n, --dry-run', "Don't perform any actions, just show what will be done") - .env('IMMICH_DRY_RUN') - .default(false) - .conflicts('skipHash'), - ) - .addOption( - new Option('-c, --concurrency ', 'Number of assets to upload at the same time') - .env('IMMICH_UPLOAD_CONCURRENCY') - .default(defaultConcurrency), - ) - .addOption( - new Option('-j, --json-output', 'Output detailed information in json format') - .env('IMMICH_JSON_OUTPUT') - .default(false), - ) - .addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS')) - .addOption( - new Option('--delete-duplicates', 'Delete local assets that are duplicates (already exist on server)').env( - 'IMMICH_DELETE_DUPLICATES', - ), - ) - .addOption(new Option('--no-progress', 'Hide progress bars').env('IMMICH_PROGRESS_BAR').default(true)) - .addOption( - new Option('--watch', 'Watch for changes and upload automatically') - .env('IMMICH_WATCH_CHANGES') - .default(false) - .implies({ progress: false }), - ) - .argument('[paths...]', 'One or more paths to assets to be uploaded') - .action((paths, options) => upload(paths, program.opts(), options)); - -program.parse(process.argv); diff --git a/cli/src/queue.ts b/cli/src/queue.ts deleted file mode 100644 index 0b6d628146..0000000000 --- a/cli/src/queue.ts +++ /dev/null @@ -1,131 +0,0 @@ -import * as fastq from 'fastq'; -import { uniqueId } from 'lodash-es'; - -export type Task = { - readonly id: string; - status: 'idle' | 'processing' | 'succeeded' | 'failed'; - data: T; - error: unknown | undefined; - count: number; - // TODO: Could be useful to adding progress property. - // TODO: Could be useful to adding start_at/end_at/duration properties. - result: undefined | R; -}; - -export type QueueOptions = { - verbose?: boolean; - concurrency?: number; - retry?: number; - // TODO: Could be useful to adding timeout property for retry. -}; - -export type ComputedQueueOptions = Required; - -export const defaultQueueOptions = { - concurrency: 1, - retry: 0, - verbose: false, -}; - -/** - * An in-memory queue that processes tasks in parallel with a given concurrency. - * @see {@link https://www.npmjs.com/package/fastq} - * @template T - The type of the worker task data. - * @template R - The type of the worker output data. - */ -export class Queue { - private readonly queue: fastq.queueAsPromised>; - private readonly store = new Map>(); - readonly options: ComputedQueueOptions; - readonly worker: (data: T) => Promise; - - /** - * Create a new queue. - * @param worker - The worker function that processes the task. - * @param options - The queue options. - */ - constructor(worker: (data: T) => Promise, options?: QueueOptions) { - this.options = { ...defaultQueueOptions, ...options }; - this.worker = worker; - this.store = new Map>(); - this.queue = this.buildQueue(); - } - - get tasks(): Task[] { - const tasks: Task[] = []; - for (const task of this.store.values()) { - tasks.push(task); - } - return tasks; - } - - getTask(id: string): Task { - const task = this.store.get(id); - if (!task) { - throw new Error(`Task with id ${id} not found`); - } - return task; - } - - /** - * Wait for the queue to be empty. - * @returns Promise - The returned Promise will be resolved when all tasks in the queue have been processed by a worker. - * This promise could be ignored as it will not lead to a `unhandledRejection`. - */ - drained(): Promise { - return this.queue.drained(); - } - - /** - * Add a task at the end of the queue. - * @see {@link https://www.npmjs.com/package/fastq} - * @param data - * @returns Promise - A Promise that will be fulfilled (rejected) when the task is completed successfully (unsuccessfully). - * This promise could be ignored as it will not lead to a `unhandledRejection`. - */ - async push(data: T): Promise> { - const id = uniqueId(); - const task: Task = { id, status: 'idle', error: undefined, count: 0, data, result: undefined }; - this.store.set(id, task); - return this.queue.push(id); - } - - // TODO: Support more function delegation to fastq. - - private buildQueue(): fastq.queueAsPromised> { - return fastq.promise((id: string) => { - const task = this.getTask(id); - return this.work(task); - }, this.options.concurrency); - } - - private async work(task: Task): Promise> { - task.count += 1; - task.error = undefined; - task.status = 'processing'; - if (this.options.verbose) { - console.log('[task] processing:', task); - } - try { - task.result = await this.worker(task.data); - task.status = 'succeeded'; - if (this.options.verbose) { - console.log('[task] succeeded:', task); - } - return task; - } catch (error) { - task.error = error; - task.status = 'failed'; - if (this.options.verbose) { - console.log('[task] failed:', task); - } - if (this.options.retry > 0 && task.count < this.options.retry) { - if (this.options.verbose) { - console.log('[task] retry:', task); - } - return this.work(task); - } - return task; - } - } -} diff --git a/cli/src/utils.spec.ts b/cli/src/utils.spec.ts deleted file mode 100644 index 3924f9ecb9..0000000000 --- a/cli/src/utils.spec.ts +++ /dev/null @@ -1,341 +0,0 @@ -import mockfs from 'mock-fs'; -import { readFileSync } from 'node:fs'; -import { Batcher, CrawlOptions, crawl } from 'src/utils'; -import { Mock } from 'vitest'; - -interface Test { - test: string; - options: Omit; - files: Record; - skipOnWin32?: boolean; -} - -const cwd = process.cwd(); - -const readContent = (path: string) => { - return readFileSync(path).toString(); -}; - -const extensions = [ - '.jpg', - '.jpeg', - '.png', - '.heif', - '.heic', - '.tif', - '.nef', - '.webp', - '.tiff', - '.dng', - '.gif', - '.mov', - '.mp4', - '.webm', -]; - -const tests: Test[] = [ - { - test: 'should return empty when crawling an empty path list', - options: { - pathsToCrawl: [], - }, - files: {}, - }, - { - test: 'should crawl a single folder', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - }, - }, - { - test: 'should crawl folders with quotes', - options: { - pathsToCrawl: ["/photo's/", '/photo"s/', '/photo`s/'], - }, - files: { - "/photo's/image1.jpg": true, - '/photo"s/image2.jpg': true, - '/photo`s/image3.jpg': true, - }, - skipOnWin32: true, // single quote interferes with mockfs root on Windows - }, - { - test: 'should crawl a single file', - options: { - pathsToCrawl: ['/photos/image.jpg'], - }, - files: { - '/photos/image.jpg': true, - }, - }, - { - test: 'should crawl a single file and a folder', - options: { - pathsToCrawl: ['/photos/image.jpg', '/images/'], - }, - files: { - '/photos/image.jpg': true, - '/images/image2.jpg': true, - }, - }, - { - test: 'should exclude by file extension', - options: { - pathsToCrawl: ['/photos/'], - exclusionPattern: '**/*.tif', - }, - files: { - '/photos/image.jpg': true, - '/photos/image.tif': false, - }, - }, - { - test: 'should exclude by file extension without case sensitivity', - options: { - pathsToCrawl: ['/photos/'], - exclusionPattern: '**/*.TIF', - }, - files: { - '/photos/image.jpg': true, - '/photos/image.tif': false, - }, - }, - { - test: 'should exclude by folder', - options: { - pathsToCrawl: ['/photos/'], - exclusionPattern: '**/raw/**', - recursive: true, - }, - files: { - '/photos/image.jpg': true, - '/photos/raw/image.jpg': false, - '/photos/raw2/image.jpg': true, - '/photos/folder/raw/image.jpg': false, - '/photos/crawl/image.jpg': true, - }, - }, - { - test: 'should crawl multiple paths', - options: { - pathsToCrawl: ['/photos/', '/images/', '/albums/'], - }, - files: { - '/photos/image1.jpg': true, - '/images/image2.jpg': true, - '/albums/image3.jpg': true, - }, - }, - - { - test: 'should crawl a single path without trailing slash', - options: { - pathsToCrawl: ['/photos'], - }, - files: { - '/photos/image.jpg': true, - }, - }, - { - test: 'should crawl a single path', - options: { - pathsToCrawl: ['/photos/'], - recursive: true, - }, - files: { - '/photos/image.jpg': true, - '/photos/subfolder/image1.jpg': true, - '/photos/subfolder/image2.jpg': true, - '/image1.jpg': false, - }, - }, - { - test: 'should filter file extensions', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.txt': false, - '/photos/1': false, - }, - }, - { - test: 'should include photo and video extensions', - options: { - pathsToCrawl: ['/photos/', '/videos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.jpeg': true, - '/photos/image.heic': true, - '/photos/image.heif': true, - '/photos/image.png': true, - '/photos/image.gif': true, - '/photos/image.tif': true, - '/photos/image.tiff': true, - '/photos/image.webp': true, - '/photos/image.dng': true, - '/photos/image.nef': true, - '/videos/video.mp4': true, - '/videos/video.mov': true, - '/videos/video.webm': true, - }, - }, - { - test: 'should check file extensions without case sensitivity', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.Jpg': true, - '/photos/image.jpG': true, - '/photos/image.JPG': true, - '/photos/image.jpEg': true, - '/photos/image.TIFF': true, - '/photos/image.tif': true, - '/photos/image.dng': true, - '/photos/image.NEF': true, - }, - }, - { - test: 'should normalize the path', - options: { - pathsToCrawl: ['/photos/1/../2'], - }, - files: { - '/photos/1/image.jpg': false, - '/photos/2/image.jpg': true, - }, - }, - { - test: 'should return absolute paths', - options: { - pathsToCrawl: ['photos'], - }, - files: { - [`${cwd}/photos/1.jpg`]: true, - [`${cwd}/photos/2.jpg`]: true, - [`/photos/3.jpg`]: false, - }, - }, - { - test: 'should support ignoring full filename', - options: { - pathsToCrawl: ['/photos'], - exclusionPattern: '**/image2.jpg', - }, - files: { - '/photos/image1.jpg': true, - '/photos/image2.jpg': false, - '/photos/image3.jpg': true, - }, - }, - { - test: 'should support ignoring file extensions', - options: { - pathsToCrawl: ['/photos'], - exclusionPattern: '**/*.png', - }, - files: { - '/photos/image1.jpg': true, - '/photos/image2.png': false, - '/photos/image3.jpg': true, - }, - }, - { - test: 'should support ignoring folder names', - options: { - pathsToCrawl: ['/photos'], - recursive: true, - exclusionPattern: '**/raw/**', - }, - files: { - '/photos/image1.jpg': true, - '/photos/image/image1.jpg': true, - '/photos/raw/image2.dng': false, - '/photos/raw/image3.dng': false, - '/photos/notraw/image3.jpg': true, - }, - }, - { - test: 'should support ignoring absolute paths', - options: { - // Currently, fast-glob has some caveat when dealing with `/`. - pathsToCrawl: ['/*s'], - recursive: true, - exclusionPattern: '/images/**', - }, - files: { - '/photos/image1.jpg': true, - '/images/image2.jpg': false, - '/assets/image3.jpg': true, - }, - }, -]; - -describe('crawl', () => { - afterEach(() => { - mockfs.restore(); - }); - - describe('crawl', () => { - for (const { test: name, options, files, skipOnWin32 } of tests) { - if (process.platform === 'win32' && skipOnWin32) { - test.skip(name); - continue; - } - it(name, async () => { - // The file contents is the same as the path. - mockfs(Object.fromEntries(Object.keys(files).map((file) => [file, file]))); - - const actual = await crawl({ ...options, extensions }); - const expected = Object.entries(files) - .filter((entry) => entry[1]) - .map(([file]) => file); - - // Compare file's content instead of path since a file can be represent in multiple ways. - expect(actual.map((path) => readContent(path)).toSorted()).toEqual(expected.toSorted()); - }); - } - }); -}); - -describe('Batcher', () => { - let batcher: Batcher; - let onBatch: Mock; - beforeEach(() => { - onBatch = vi.fn(); - batcher = new Batcher({ batchSize: 2, onBatch }); - }); - - it('should trigger onBatch() when a batch limit is reached', async () => { - batcher.add('a'); - batcher.add('b'); - batcher.add('c'); - expect(onBatch).toHaveBeenCalledOnce(); - expect(onBatch).toHaveBeenCalledWith(['a', 'b']); - }); - - it('should trigger onBatch() when flush() is called', async () => { - batcher.add('a'); - batcher.flush(); - expect(onBatch).toHaveBeenCalledOnce(); - expect(onBatch).toHaveBeenCalledWith(['a']); - }); - - it('should trigger onBatch() when debounce time reached', async () => { - vi.useFakeTimers(); - batcher = new Batcher({ batchSize: 2, debounceTimeMs: 100, onBatch }); - batcher.add('a'); - expect(onBatch).not.toHaveBeenCalled(); - vi.advanceTimersByTime(200); - expect(onBatch).toHaveBeenCalledOnce(); - expect(onBatch).toHaveBeenCalledWith(['a']); - vi.useRealTimers(); - }); -}); diff --git a/cli/src/utils.ts b/cli/src/utils.ts deleted file mode 100644 index 9ef20b3679..0000000000 --- a/cli/src/utils.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { getMyUser, init, isHttpError } from '@immich/sdk'; -import { convertPathToPattern, glob } from 'fast-glob'; -import { createHash } from 'node:crypto'; -import { createReadStream } from 'node:fs'; -import { readFile, stat, writeFile } from 'node:fs/promises'; -import { platform } from 'node:os'; -import { join, resolve } from 'node:path'; -import yaml from 'yaml'; - -export interface BaseOptions { - configDirectory: string; - key?: string; - url?: string; -} - -export type AuthDto = { url: string; key: string }; -type OldAuthDto = { instanceUrl: string; apiKey: string }; - -export const authenticate = async (options: BaseOptions): Promise => { - const { configDirectory: configDir, url, key } = options; - - // provided in command - if (url && key) { - return connect(url, key); - } - - // fallback to auth file - const config = await readAuthFile(configDir); - const auth = await connect(config.url, config.key); - if (auth.url !== config.url) { - await writeAuthFile(configDir, auth); - } - - return auth; -}; - -export const connect = async (url: string, key: string) => { - const wellKnownUrl = new URL('.well-known/immich', url); - try { - const wellKnown = await fetch(wellKnownUrl).then((response) => response.json()); - const endpoint = new URL(wellKnown.api.endpoint, url).toString(); - if (endpoint !== url) { - console.debug(`Discovered API at ${endpoint}`); - } - url = endpoint; - } catch { - // noop - } - - init({ baseUrl: url, apiKey: key }); - - const [error] = await withError(getMyUser()); - if (isHttpError(error)) { - logError(error, 'Failed to connect to server'); - process.exit(1); - } - - return { url, key }; -}; - -export const logError = (error: unknown, message: string) => { - if (isHttpError(error)) { - console.error(`${message}: ${error.status}`); - console.error(JSON.stringify(error.data, undefined, 2)); - } else { - console.error(`${message} - ${error}`); - } -}; - -export const getAuthFilePath = (dir: string) => join(dir, 'auth.yml'); - -export const readAuthFile = async (dir: string) => { - try { - const data = await readFile(getAuthFilePath(dir)); - // TODO add class-transform/validation - const auth = yaml.parse(data.toString()) as AuthDto | OldAuthDto; - const { instanceUrl, apiKey } = auth as OldAuthDto; - if (instanceUrl && apiKey) { - return { url: instanceUrl, key: apiKey }; - } - return auth as AuthDto; - } catch (error: Error | any) { - if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { - console.log('No auth file exists. Please login first.'); - process.exit(1); - } - throw error; - } -}; - -export const writeAuthFile = async (dir: string, auth: AuthDto) => - writeFile(getAuthFilePath(dir), yaml.stringify(auth), { mode: 0o600 }); - -export const withError = async (promise: Promise): Promise<[Error, undefined] | [undefined, T]> => { - try { - const result = await promise; - return [undefined, result]; - } catch (error: Error | any) { - return [error, undefined]; - } -}; - -export interface CrawlOptions { - pathsToCrawl: string[]; - recursive?: boolean; - includeHidden?: boolean; - exclusionPattern?: string; - extensions: string[]; -} - -const convertPathToPatternOnWin = (path: string) => { - return platform() === 'win32' ? convertPathToPattern(path) : path; -}; - -export const crawl = async (options: CrawlOptions): Promise => { - const { extensions: extensionsWithPeriod, recursive, pathsToCrawl, exclusionPattern, includeHidden } = options; - const extensions = extensionsWithPeriod.map((extension) => extension.replace('.', '')); - - if (pathsToCrawl.length === 0) { - return []; - } - - const patterns: string[] = []; - const crawledFiles: string[] = []; - - for await (const currentPath of pathsToCrawl) { - try { - const absolutePath = resolve(currentPath); - const stats = await stat(absolutePath); - if (stats.isFile() || stats.isSymbolicLink()) { - crawledFiles.push(absolutePath); - } else { - patterns.push(convertPathToPatternOnWin(absolutePath)); - } - } catch (error: any) { - if (error.code === 'ENOENT') { - patterns.push(convertPathToPatternOnWin(currentPath)); - } else { - throw error; - } - } - } - - if (patterns.length === 0) { - return crawledFiles; - } - - const searchPatterns = patterns.map((pattern) => { - let escapedPattern = pattern.replaceAll("'", "[']").replaceAll('"', '["]').replaceAll('`', '[`]'); - if (recursive) { - escapedPattern = escapedPattern + '/**'; - } - return `${escapedPattern}/*.{${extensions.join(',')}}`; - }); - - const globbedFiles = await glob(searchPatterns, { - absolute: true, - caseSensitiveMatch: false, - dot: includeHidden, - ignore: [`**/${exclusionPattern}`], - }); - globbedFiles.push(...crawledFiles); - return globbedFiles.toSorted(); -}; - -export const sha1 = (filepath: string) => { - const hash = createHash('sha1'); - return new Promise((resolve, reject) => { - const rs = createReadStream(filepath); - rs.on('error', reject); - rs.on('data', (chunk) => hash.update(chunk)); - rs.on('end', () => resolve(hash.digest('hex'))); - }); -}; - -/** - * Batches items and calls onBatch to process them - * when the batch size is reached or the debounce time has passed. - */ -export class Batcher { - private items: T[] = []; - private readonly batchSize: number; - private readonly debounceTimeMs?: number; - private readonly onBatch: (items: T[]) => void; - private debounceTimer?: NodeJS.Timeout; - - constructor({ - batchSize, - debounceTimeMs, - onBatch, - }: { - batchSize: number; - debounceTimeMs?: number; - onBatch: (items: T[]) => Promise; - }) { - this.batchSize = batchSize; - this.debounceTimeMs = debounceTimeMs; - this.onBatch = onBatch; - } - - private setDebounceTimer() { - if (this.debounceTimer) { - clearTimeout(this.debounceTimer); - } - if (this.debounceTimeMs) { - this.debounceTimer = setTimeout(() => this.flush(), this.debounceTimeMs); - } - } - - private clearDebounceTimer() { - if (this.debounceTimer) { - clearTimeout(this.debounceTimer); - this.debounceTimer = undefined; - } - } - - add(item: T) { - this.items.push(item); - this.setDebounceTimer(); - if (this.items.length >= this.batchSize) { - this.flush(); - } - } - - flush() { - this.clearDebounceTimer(); - if (this.items.length === 0) { - return; - } - - this.onBatch(this.items); - - this.items = []; - } -} diff --git a/cli/tsconfig.json b/cli/tsconfig.json deleted file mode 100644 index d3a02a8257..0000000000 --- a/cli/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "module": "esnext", - "moduleResolution": "bundler", - "strict": true, - "declaration": true, - "removeComments": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "resolveJsonModule": true, - "target": "es2023", - "sourceMap": true, - "outDir": "./dist", - "incremental": true, - "skipLibCheck": true, - "esModuleInterop": true, - "baseUrl": "./", - "types": ["vitest/globals"] - }, - "exclude": ["dist", "node_modules"] -} diff --git a/cli/vite.config.ts b/cli/vite.config.ts deleted file mode 100644 index f538a9a357..0000000000 --- a/cli/vite.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from 'vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; - -export default defineConfig({ - resolve: { alias: { src: '/src' } }, - build: { - rollupOptions: { - input: 'src/index.ts', - output: { - dir: 'dist', - }, - }, - ssr: true, - }, - ssr: { - // bundle everything except for Node built-ins - noExternal: /^(?!node:).*$/, - }, - plugins: [tsconfigPaths()], -}); diff --git a/cli/vitest.config.ts b/cli/vitest.config.ts deleted file mode 100644 index 7382f40e7d..0000000000 --- a/cli/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - globals: true, - }, -}); diff --git a/deployment/.env b/deployment/.env deleted file mode 100644 index f6ce050d29..0000000000 --- a/deployment/.env +++ /dev/null @@ -1,4 +0,0 @@ -export CLOUDFLARE_ACCOUNT_ID="op://tf/cloudflare/account_id" -export CLOUDFLARE_API_TOKEN="op://tf/cloudflare/api_token" -export TF_STATE_POSTGRES_CONN_STR="op://tf/tf_state/postgres_conn_str" -export TF_VAR_env=$ENVIRONMENT diff --git a/deployment/.gitignore b/deployment/.gitignore deleted file mode 100644 index 653d60a3f1..0000000000 --- a/deployment/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# OpenTofu - -# Local .terraform directories -**/.terraform/* - -# .tfstate files -*.tfstate -*.tfstate.* - -# Crash log files -crash.log -crash.*.log - -# Ignore override files as they are usually used to override resources locally and so -# are not checked in -override.tf -override.tf.json -*_override.tf -*_override.tf.json - -# Include override files you do wish to add to version control using negated pattern -# !example_override.tf - -# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan -# example: *tfplan* - -# Ignore CLI configuration files -.terraformrc -terraform.rc - -# Terragrunt - -# terragrunt cache directories -**/.terragrunt-cache/* - -# Terragrunt debug output file (when using `--terragrunt-debug` option) -# See: https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-debug -terragrunt-debug.tfvars.json diff --git a/deployment/mise.toml b/deployment/mise.toml deleted file mode 100644 index d77ec84125..0000000000 --- a/deployment/mise.toml +++ /dev/null @@ -1,20 +0,0 @@ -[tools] -terragrunt = "0.98.0" -opentofu = "1.11.4" - -[tasks."tg:fmt"] -run = "terragrunt hclfmt" -description = "Format terragrunt files" - -[tasks.tf] -run = "terragrunt run --all" -description = "Wrapper for terragrunt run-all" -dir = "{{cwd}}" - -[tasks."tf:fmt"] -run = "tofu fmt -recursive tf/" -description = "Format terraform files" - -[tasks."tf:init"] -run = { task = "tf init -- -reconfigure" } -dir = "{{cwd}}" diff --git a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl deleted file mode 100644 index 0869dd28bc..0000000000 --- a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl +++ /dev/null @@ -1,38 +0,0 @@ -# This file is maintained automatically by "tofu init". -# Manual edits may be lost in future updates. - -provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.5" - constraints = "4.52.5" - hashes = [ - "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", - "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", - "h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=", - "h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=", - "h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=", - "h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=", - "h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=", - "h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=", - "h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=", - "h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=", - "h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=", - "h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=", - "h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=", - "h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=", - "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", - "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", - "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", - "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", - "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", - "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", - "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", - "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", - "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", - "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", - "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", - "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", - "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", - ] -} diff --git a/deployment/modules/cloudflare/docs-release/config.tf b/deployment/modules/cloudflare/docs-release/config.tf deleted file mode 100644 index 63347cf67e..0000000000 --- a/deployment/modules/cloudflare/docs-release/config.tf +++ /dev/null @@ -1,11 +0,0 @@ -terraform { - backend "pg" {} - required_version = "~> 1.7" - - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "4.52.5" - } - } -} diff --git a/deployment/modules/cloudflare/docs-release/domain.tf b/deployment/modules/cloudflare/docs-release/domain.tf deleted file mode 100644 index 3a6f479a74..0000000000 --- a/deployment/modules/cloudflare/docs-release/domain.tf +++ /dev/null @@ -1,14 +0,0 @@ -resource "cloudflare_pages_domain" "immich_app_release_domain" { - account_id = var.cloudflare_account_id - project_name = data.terraform_remote_state.cloudflare_account.outputs.immich_app_archive_pages_project_name - domain = "docs.immich.app" -} - -resource "cloudflare_record" "immich_app_release_domain" { - name = "docs.immich.app" - proxied = true - ttl = 1 - type = "CNAME" - content = data.terraform_remote_state.cloudflare_immich_app_docs.outputs.immich_app_branch_pages_hostname - zone_id = data.terraform_remote_state.cloudflare_account.outputs.immich_app_zone_id -} diff --git a/deployment/modules/cloudflare/docs-release/providers.tf b/deployment/modules/cloudflare/docs-release/providers.tf deleted file mode 100644 index 65d0883a9d..0000000000 --- a/deployment/modules/cloudflare/docs-release/providers.tf +++ /dev/null @@ -1,3 +0,0 @@ -provider "cloudflare" { - api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_docs -} diff --git a/deployment/modules/cloudflare/docs-release/remote-state.tf b/deployment/modules/cloudflare/docs-release/remote-state.tf deleted file mode 100644 index 3db42dbfff..0000000000 --- a/deployment/modules/cloudflare/docs-release/remote-state.tf +++ /dev/null @@ -1,27 +0,0 @@ -data "terraform_remote_state" "api_keys_state" { - backend = "pg" - - config = { - conn_str = var.tf_state_postgres_conn_str - schema_name = "prod_cloudflare_api_keys" - } -} - -data "terraform_remote_state" "cloudflare_account" { - backend = "pg" - - config = { - conn_str = var.tf_state_postgres_conn_str - schema_name = "prod_cloudflare_account" - } -} - -data "terraform_remote_state" "cloudflare_immich_app_docs" { - backend = "pg" - - config = { - conn_str = var.tf_state_postgres_conn_str - schema_name = "prod_cloudflare_immich_app_docs_${var.prefix_name}" - } -} - diff --git a/deployment/modules/cloudflare/docs-release/terragrunt.hcl b/deployment/modules/cloudflare/docs-release/terragrunt.hcl deleted file mode 100644 index c3a6f6acae..0000000000 --- a/deployment/modules/cloudflare/docs-release/terragrunt.hcl +++ /dev/null @@ -1,20 +0,0 @@ -terraform { - source = "." - - extra_arguments custom_vars { - commands = get_terraform_commands_that_need_vars() - } -} - -include { - path = find_in_parent_folders("state.hcl") -} - -remote_state { - backend = "pg" - - config = { - conn_str = get_env("TF_STATE_POSTGRES_CONN_STR") - schema_name = "prod_cloudflare_immich_app_docs_release" - } -} diff --git a/deployment/modules/cloudflare/docs-release/variables.tf b/deployment/modules/cloudflare/docs-release/variables.tf deleted file mode 100644 index 6a219bf4ec..0000000000 --- a/deployment/modules/cloudflare/docs-release/variables.tf +++ /dev/null @@ -1,4 +0,0 @@ -variable "cloudflare_account_id" {} -variable "tf_state_postgres_conn_str" {} - -variable "prefix_name" {} diff --git a/deployment/modules/cloudflare/docs/.terraform.lock.hcl b/deployment/modules/cloudflare/docs/.terraform.lock.hcl deleted file mode 100644 index 0869dd28bc..0000000000 --- a/deployment/modules/cloudflare/docs/.terraform.lock.hcl +++ /dev/null @@ -1,38 +0,0 @@ -# This file is maintained automatically by "tofu init". -# Manual edits may be lost in future updates. - -provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.5" - constraints = "4.52.5" - hashes = [ - "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", - "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", - "h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=", - "h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=", - "h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=", - "h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=", - "h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=", - "h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=", - "h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=", - "h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=", - "h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=", - "h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=", - "h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=", - "h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=", - "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", - "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", - "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", - "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", - "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", - "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", - "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", - "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", - "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", - "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", - "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", - "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", - "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", - "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", - ] -} diff --git a/deployment/modules/cloudflare/docs/config.tf b/deployment/modules/cloudflare/docs/config.tf deleted file mode 100644 index 63347cf67e..0000000000 --- a/deployment/modules/cloudflare/docs/config.tf +++ /dev/null @@ -1,11 +0,0 @@ -terraform { - backend "pg" {} - required_version = "~> 1.7" - - required_providers { - cloudflare = { - source = "cloudflare/cloudflare" - version = "4.52.5" - } - } -} diff --git a/deployment/modules/cloudflare/docs/domain.tf b/deployment/modules/cloudflare/docs/domain.tf deleted file mode 100644 index c5f77de6b4..0000000000 --- a/deployment/modules/cloudflare/docs/domain.tf +++ /dev/null @@ -1,26 +0,0 @@ -resource "cloudflare_pages_domain" "immich_app_branch_domain" { - account_id = var.cloudflare_account_id - project_name = local.is_release ? data.terraform_remote_state.cloudflare_account.outputs.immich_app_archive_pages_project_name : data.terraform_remote_state.cloudflare_account.outputs.immich_app_preview_pages_project_name - domain = "docs.${var.prefix_name}.${local.deploy_domain_prefix}.immich.app" -} - -resource "cloudflare_record" "immich_app_branch_subdomain" { - name = "docs.${var.prefix_name}.${local.deploy_domain_prefix}.immich.app" - proxied = true - ttl = 1 - type = "CNAME" - content = "${replace(var.prefix_name, "/\\/|\\./", "-")}.${local.is_release ? data.terraform_remote_state.cloudflare_account.outputs.immich_app_archive_pages_project_subdomain : data.terraform_remote_state.cloudflare_account.outputs.immich_app_preview_pages_project_subdomain}" - zone_id = data.terraform_remote_state.cloudflare_account.outputs.immich_app_zone_id -} - -output "immich_app_branch_subdomain" { - value = cloudflare_record.immich_app_branch_subdomain.hostname -} - -output "immich_app_branch_pages_hostname" { - value = cloudflare_record.immich_app_branch_subdomain.content -} - -output "pages_project_name" { - value = cloudflare_pages_domain.immich_app_branch_domain.project_name -} diff --git a/deployment/modules/cloudflare/docs/locals.tf b/deployment/modules/cloudflare/docs/locals.tf deleted file mode 100644 index d830687791..0000000000 --- a/deployment/modules/cloudflare/docs/locals.tf +++ /dev/null @@ -1,7 +0,0 @@ -locals { - domain_name = "immich.app" - preview_prefix = contains(["branch", "pr"], var.prefix_event_type) ? "preview" : "" - archive_prefix = contains(["release"], var.prefix_event_type) ? "archive" : "" - deploy_domain_prefix = coalesce(local.preview_prefix, local.archive_prefix) - is_release = contains(["release"], var.prefix_event_type) -} diff --git a/deployment/modules/cloudflare/docs/providers.tf b/deployment/modules/cloudflare/docs/providers.tf deleted file mode 100644 index 65d0883a9d..0000000000 --- a/deployment/modules/cloudflare/docs/providers.tf +++ /dev/null @@ -1,3 +0,0 @@ -provider "cloudflare" { - api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_docs -} diff --git a/deployment/modules/cloudflare/docs/remote-state.tf b/deployment/modules/cloudflare/docs/remote-state.tf deleted file mode 100644 index cf9d99f8f5..0000000000 --- a/deployment/modules/cloudflare/docs/remote-state.tf +++ /dev/null @@ -1,17 +0,0 @@ -data "terraform_remote_state" "api_keys_state" { - backend = "pg" - - config = { - conn_str = var.tf_state_postgres_conn_str - schema_name = "prod_cloudflare_api_keys" - } -} - -data "terraform_remote_state" "cloudflare_account" { - backend = "pg" - - config = { - conn_str = var.tf_state_postgres_conn_str - schema_name = "prod_cloudflare_account" - } -} diff --git a/deployment/modules/cloudflare/docs/terragrunt.hcl b/deployment/modules/cloudflare/docs/terragrunt.hcl deleted file mode 100644 index 95d7b6879b..0000000000 --- a/deployment/modules/cloudflare/docs/terragrunt.hcl +++ /dev/null @@ -1,24 +0,0 @@ -terraform { - source = "." - - extra_arguments custom_vars { - commands = get_terraform_commands_that_need_vars() - } -} - -include { - path = find_in_parent_folders("state.hcl") -} - -locals { - prefix_name = get_env("TF_VAR_prefix_name") -} - -remote_state { - backend = "pg" - - config = { - conn_str = get_env("TF_STATE_POSTGRES_CONN_STR") - schema_name = "prod_cloudflare_immich_app_docs_${local.prefix_name}" - } -} diff --git a/deployment/modules/cloudflare/docs/variables.tf b/deployment/modules/cloudflare/docs/variables.tf deleted file mode 100644 index 9cce2ba770..0000000000 --- a/deployment/modules/cloudflare/docs/variables.tf +++ /dev/null @@ -1,5 +0,0 @@ -variable "cloudflare_account_id" {} -variable "tf_state_postgres_conn_str" {} - -variable "prefix_name" {} -variable "prefix_event_type" {} diff --git a/deployment/state.hcl b/deployment/state.hcl deleted file mode 100644 index 5c3fc7cfa9..0000000000 --- a/deployment/state.hcl +++ /dev/null @@ -1,20 +0,0 @@ -locals { - cloudflare_account_id = get_env("CLOUDFLARE_ACCOUNT_ID") - cloudflare_api_token = get_env("CLOUDFLARE_API_TOKEN") - - tf_state_postgres_conn_str = get_env("TF_STATE_POSTGRES_CONN_STR") -} - -remote_state { - backend = "pg" - - config = { - conn_str = local.tf_state_postgres_conn_str - } -} - -inputs = { - cloudflare_account_id = local.cloudflare_account_id - cloudflare_api_token = local.cloudflare_api_token - tf_state_postgres_conn_str = local.tf_state_postgres_conn_str -} diff --git a/design/.DS_Store b/design/.DS_Store deleted file mode 100644 index 65ee281c91..0000000000 Binary files a/design/.DS_Store and /dev/null differ diff --git a/design/immich-logo-inline-dark.png b/design/immich-logo-inline-dark.png deleted file mode 100644 index cc6cb23b62..0000000000 Binary files a/design/immich-logo-inline-dark.png and /dev/null differ diff --git a/design/immich-logo-inline-dark.svg b/design/immich-logo-inline-dark.svg deleted file mode 100644 index 024337c2e3..0000000000 --- a/design/immich-logo-inline-dark.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/design/immich-logo-inline-light.png b/design/immich-logo-inline-light.png deleted file mode 100644 index b910b37904..0000000000 Binary files a/design/immich-logo-inline-light.png and /dev/null differ diff --git a/design/immich-logo-inline-light.svg b/design/immich-logo-inline-light.svg deleted file mode 100644 index 216466f58d..0000000000 --- a/design/immich-logo-inline-light.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/design/immich-logo-stacked-dark.png b/design/immich-logo-stacked-dark.png deleted file mode 100644 index 6b357f1d5e..0000000000 Binary files a/design/immich-logo-stacked-dark.png and /dev/null differ diff --git a/design/immich-logo-stacked-dark.svg b/design/immich-logo-stacked-dark.svg deleted file mode 100644 index 7f8381869a..0000000000 --- a/design/immich-logo-stacked-dark.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/design/immich-logo-stacked-light.png b/design/immich-logo-stacked-light.png deleted file mode 100644 index 3df5e04e18..0000000000 Binary files a/design/immich-logo-stacked-light.png and /dev/null differ diff --git a/design/immich-logo-stacked-light.svg b/design/immich-logo-stacked-light.svg deleted file mode 100644 index 8c4505d97e..0000000000 --- a/design/immich-logo-stacked-light.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/design/immich-logo.png b/design/immich-logo.png deleted file mode 100644 index 297f8a926c..0000000000 Binary files a/design/immich-logo.png and /dev/null differ diff --git a/design/immich-logo.svg b/design/immich-logo.svg deleted file mode 100644 index 376fa6f3e8..0000000000 --- a/design/immich-logo.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/design/immich-screenshots.png b/design/immich-screenshots.png deleted file mode 100644 index 6123279f2d..0000000000 Binary files a/design/immich-screenshots.png and /dev/null differ diff --git a/design/immich-text-dark.png b/design/immich-text-dark.png deleted file mode 100644 index 215687af8f..0000000000 Binary files a/design/immich-text-dark.png and /dev/null differ diff --git a/design/immich-text-light.png b/design/immich-text-light.png deleted file mode 100644 index 478158d39c..0000000000 Binary files a/design/immich-text-light.png and /dev/null differ diff --git a/docker/.gitignore b/docker/.gitignore deleted file mode 100644 index 2eea525d88..0000000000 --- a/docker/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.env \ No newline at end of file diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 437aa274cb..0000000000 --- a/docker/README.md +++ /dev/null @@ -1,5 +0,0 @@ -> [!CAUTION] -> Make sure to use the docker-compose.yml of the current release: -> https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml -> -> The compose file on main may not be compatible with the latest release. diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml deleted file mode 100644 index 244fc74dba..0000000000 --- a/docker/docker-compose.dev.yml +++ /dev/null @@ -1,186 +0,0 @@ -# -# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose -# -# Make sure to use the docker-compose.yml of the current release: -# -# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml -# -# The compose file on main may not be compatible with the latest release. - -# For development see: -# - https://docs.immich.app/developer/setup -# - https://docs.immich.app/developer/troubleshooting - -name: immich-dev - -services: - immich-server: - container_name: immich_server - command: ['immich-dev'] - image: immich-server-dev:latest - # extends: - # file: hwaccel.transcoding.yml - # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding - build: - context: ../ - dockerfile: server/Dockerfile.dev - target: dev - restart: unless-stopped - volumes: - - ..:/usr/src/app - - ${UPLOAD_LOCATION}/photos:/data - - /etc/localtime:/etc/localtime:ro - - pnpm-store:/usr/src/app/.pnpm-store - - server-node_modules:/usr/src/app/server/node_modules - - web-node_modules:/usr/src/app/web/node_modules - - github-node_modules:/usr/src/app/.github/node_modules - - cli-node_modules:/usr/src/app/cli/node_modules - - docs-node_modules:/usr/src/app/docs/node_modules - - e2e-node_modules:/usr/src/app/e2e/node_modules - - sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules - - app-node_modules:/usr/src/app/node_modules - - sveltekit:/usr/src/app/web/.svelte-kit - - coverage:/usr/src/app/web/coverage - - ../plugins:/build/corePlugin - env_file: - - .env - environment: - IMMICH_REPOSITORY: immich-app/immich - IMMICH_REPOSITORY_URL: https://github.com/immich-app/immich - IMMICH_SOURCE_REF: local - IMMICH_SOURCE_COMMIT: af2efbdbbddc27cd06142f22253ccbbbbeec1f55 - IMMICH_SOURCE_URL: https://github.com/immich-app/immich/commit/af2efbdbbddc27cd06142f22253ccbbbbeec1f55 - IMMICH_BUILD: '9654404849' - IMMICH_BUILD_URL: https://github.com/immich-app/immich/actions/runs/9654404849 - IMMICH_BUILD_IMAGE: development - IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server - IMMICH_THIRD_PARTY_SOURCE_URL: https://github.com/immich-app/immich/ - IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues - IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://docs.immich.app - IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides - ports: - - 9230:9230 - - 9231:9231 - - 2283:2283 - depends_on: - redis: - condition: service_started - database: - condition: service_started - healthcheck: - disable: false - - immich-web: - container_name: immich_web - image: immich-web-dev:latest - build: - context: ../ - dockerfile: server/Dockerfile.dev - target: dev - command: ['immich-web'] - env_file: - - .env - ports: - - 3000:3000 - - 24678:24678 - volumes: - - ..:/usr/src/app - - pnpm-store:/usr/src/app/.pnpm-store - - server-node_modules:/usr/src/app/server/node_modules - - web-node_modules:/usr/src/app/web/node_modules - - github-node_modules:/usr/src/app/.github/node_modules - - cli-node_modules:/usr/src/app/cli/node_modules - - docs-node_modules:/usr/src/app/docs/node_modules - - e2e-node_modules:/usr/src/app/e2e/node_modules - - sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules - - app-node_modules:/usr/src/app/node_modules - - sveltekit:/usr/src/app/web/.svelte-kit - - coverage:/usr/src/app/web/coverage - restart: unless-stopped - depends_on: - immich-server: - condition: service_started - - immich-machine-learning: - container_name: immich_machine_learning - image: immich-machine-learning-dev:latest - # extends: - # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - build: - context: ../machine-learning - dockerfile: Dockerfile - args: - - DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - ports: - - 3003:3003 - volumes: - - ../machine-learning/immich_ml:/usr/src/immich_ml - - model-cache:/cache - env_file: - - .env - depends_on: - - database - restart: unless-stopped - healthcheck: - disable: false - - redis: - container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63 - healthcheck: - test: redis-cli ping || exit 1 - - database: - container_name: immich_postgres - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 - env_file: - - .env - environment: - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_DB: ${DB_DATABASE_NAME} - POSTGRES_INITDB_ARGS: '--data-checksums' - volumes: - - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data - ports: - - 5432:5432 - shm_size: 128mb - healthcheck: - disable: false - # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics - # immich-prometheus: - # container_name: immich_prometheus - # ports: - # - 9090:9090 - # image: prom/prometheus - # volumes: - # - ./prometheus.yml:/etc/prometheus/prometheus.yml - # - prometheus-data:/prometheus - - # first login uses admin/admin - # add data source for http://immich-prometheus:9090 to get started - # immich-grafana: - # container_name: immich_grafana - # command: ['./run.sh', '-disable-reporting'] - # ports: - # - 3000:3000 - # image: grafana/grafana:10.3.3-ubuntu - # volumes: - # - grafana-data:/var/lib/grafana - -volumes: - model-cache: - prometheus-data: - grafana-data: - pnpm-store: - server-node_modules: - web-node_modules: - github-node_modules: - cli-node_modules: - docs-node_modules: - e2e-node_modules: - sdk-node_modules: - app-node_modules: - sveltekit: - coverage: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml deleted file mode 100644 index 217ec08030..0000000000 --- a/docker/docker-compose.prod.yml +++ /dev/null @@ -1,107 +0,0 @@ -# -# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose -# -# Make sure to use the docker-compose.yml of the current release: -# -# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml -# -# The compose file on main may not be compatible with the latest release. - -name: immich-prod - -services: - immich-server: - container_name: immich_server - image: immich-server:latest - # extends: - # file: hwaccel.transcoding.yml - # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding - build: - context: ../ - dockerfile: server/Dockerfile - volumes: - - ${UPLOAD_LOCATION}/photos:/data - - /etc/localtime:/etc/localtime:ro - env_file: - - .env - ports: - - 2283:2283 - depends_on: - - redis - - database - restart: always - healthcheck: - disable: false - - immich-machine-learning: - container_name: immich_machine_learning - image: immich-machine-learning:latest - # extends: - # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - build: - context: ../machine-learning - dockerfile: Dockerfile - args: - - DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - ports: - - 3003:3003 - volumes: - - model-cache:/cache - env_file: - - .env - restart: always - healthcheck: - disable: false - - redis: - container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63 - healthcheck: - test: redis-cli ping || exit 1 - restart: always - - database: - container_name: immich_postgres - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 - env_file: - - .env - environment: - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_DB: ${DB_DATABASE_NAME} - POSTGRES_INITDB_ARGS: '--data-checksums' - volumes: - - ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data - ports: - - 5432:5432 - shm_size: 128mb - restart: always - healthcheck: - disable: false - - # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics - immich-prometheus: - container_name: immich_prometheus - ports: - - 9090:9090 - image: prom/prometheus@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702 - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml - - prometheus-data:/prometheus - - # first login uses admin/admin - # add data source for http://immich-prometheus:9090 to get started - immich-grafana: - container_name: immich_grafana - command: ['./run.sh', '-disable-reporting'] - ports: - - 3000:3000 - image: grafana/grafana:12.3.2-ubuntu@sha256:6cca4b429a1dc0d37d401dee54825c12d40056c3c6f3f56e3f0d6318ce77749b - volumes: - - grafana-data:/var/lib/grafana - -volumes: - model-cache: - prometheus-data: - grafana-data: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b8668cc91a..fbba33c022 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,76 +1,37 @@ -# -# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose -# -# Make sure to use the docker-compose.yml of the current release: -# -# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml -# -# The compose file on main may not be compatible with the latest release. - -name: immich +name: app services: - immich-server: - container_name: immich_server - image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} - # extends: - # file: hwaccel.transcoding.yml - # service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding - volumes: - # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file - - ${UPLOAD_LOCATION}:/data - - /etc/localtime:/etc/localtime:ro - env_file: - - .env - ports: - - '2283:2283' - depends_on: - - redis - - database - restart: always - healthcheck: - disable: false - - immich-machine-learning: - container_name: immich_machine_learning - # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. - # Example tag: ${IMMICH_VERSION:-release}-cuda - image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} - # extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration - # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable - volumes: - - model-cache:/cache - env_file: - - .env - restart: always - healthcheck: - disable: false - - redis: - container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63 - healthcheck: - test: redis-cli ping || exit 1 - restart: always - database: - container_name: immich_postgres - image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 + container_name: app_postgres + image: pgvector/pgvector:pg18 environment: - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_DB: ${DB_DATABASE_NAME} + POSTGRES_USER: ${DB_USERNAME:-postgres} + POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} + POSTGRES_DB: ${DB_DATABASE_NAME:-app} POSTGRES_INITDB_ARGS: '--data-checksums' - # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs - # DB_STORAGE_TYPE: 'HDD' volumes: - # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file - - ${DB_DATA_LOCATION}:/var/lib/postgresql/data + - pgdata:/var/lib/postgresql/data + ports: + - '5432:5432' shm_size: 128mb restart: always healthcheck: - disable: false + test: pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_DATABASE_NAME:-app} + interval: 10s + timeout: 5s + retries: 5 + + redis: + container_name: app_redis + image: docker.io/valkey/valkey:9 + ports: + - '6379:6379' + healthcheck: + test: redis-cli ping || exit 1 + interval: 10s + timeout: 5s + retries: 5 + restart: always volumes: - model-cache: + pgdata: diff --git a/docker/example.env b/docker/example.env deleted file mode 100644 index 6641cceaaa..0000000000 --- a/docker/example.env +++ /dev/null @@ -1,22 +0,0 @@ -# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables - -# The location where your uploaded files are stored -UPLOAD_LOCATION=./library - -# The location where your database files are stored. Network shares are not supported for the database -DB_DATA_LOCATION=./postgres - -# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List -# TZ=Etc/UTC - -# The Immich version to use. You can pin this to a specific version like "v2.1.0" -IMMICH_VERSION=v2 - -# Connection secret for postgres. You should change it to a random password -# Please use only the characters `A-Za-z0-9`, without special characters or spaces -DB_PASSWORD=postgres - -# The values below this line do not need to be changed -################################################################################### -DB_USERNAME=postgres -DB_DATABASE_NAME=immich diff --git a/docker/hwaccel.ml.yml b/docker/hwaccel.ml.yml deleted file mode 100644 index c95ac7ee4c..0000000000 --- a/docker/hwaccel.ml.yml +++ /dev/null @@ -1,57 +0,0 @@ -# Configurations for hardware-accelerated machine learning - -# If using Unraid or another platform that doesn't allow multiple Compose files, -# you can inline the config for a backend by copying its contents -# into the immich-machine-learning service in the docker-compose.yml file. - -# See https://docs.immich.app/features/ml-hardware-acceleration for info on usage. - -services: - armnn: - devices: - - /dev/mali0:/dev/mali0 - volumes: - - /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver) - - /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required) - - rknn: - security_opt: - - systempaths=unconfined - - apparmor=unconfined - devices: - - /dev/dri:/dev/dri - - cpu: {} - - cuda: - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: - - gpu - - rocm: - group_add: - - video - devices: - - /dev/dri:/dev/dri - - /dev/kfd:/dev/kfd - - openvino: - device_cgroup_rules: - - 'c 189:* rmw' - devices: - - /dev/dri:/dev/dri - volumes: - - /dev/bus/usb:/dev/bus/usb - - openvino-wsl: - devices: - - /dev/dri:/dev/dri - - /dev/dxg:/dev/dxg - volumes: - - /dev/bus/usb:/dev/bus/usb - - /usr/lib/wsl:/usr/lib/wsl diff --git a/docker/hwaccel.transcoding.yml b/docker/hwaccel.transcoding.yml deleted file mode 100644 index 0857faf465..0000000000 --- a/docker/hwaccel.transcoding.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Configurations for hardware-accelerated transcoding - -# If using Unraid or another platform that doesn't allow multiple Compose files, -# you can inline the config for a backend by copying its contents -# into the immich-microservices service in the docker-compose.yml file. - -# See https://docs.immich.app/features/hardware-transcoding for more info on using hardware transcoding. - -services: - cpu: {} - - nvenc: - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: - - gpu - - compute - - video - - quicksync: - devices: - - /dev/dri:/dev/dri - - rkmpp: - security_opt: # enables full access to /sys and /proc, still far better than privileged: true - - systempaths=unconfined - - apparmor=unconfined - group_add: - - video - devices: - - /dev/rga:/dev/rga - - /dev/dri:/dev/dri - - /dev/dma_heap:/dev/dma_heap - - /dev/mpp_service:/dev/mpp_service - #- /dev/mali0:/dev/mali0 # only required to enable OpenCL-accelerated HDR -> SDR tonemapping - volumes: - #- /etc/OpenCL:/etc/OpenCL:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping - #- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping - - vaapi: - devices: - - /dev/dri:/dev/dri - - vaapi-wsl: # use this for VAAPI if you're running Immich in WSL2 - devices: - - /dev/dri:/dev/dri - - /dev/dxg:/dev/dxg - volumes: - - /usr/lib/wsl:/usr/lib/wsl - environment: - - LIBVA_DRIVER_NAME=d3d12 diff --git a/docker/prometheus.yml b/docker/prometheus.yml deleted file mode 100644 index 3b18e53450..0000000000 --- a/docker/prometheus.yml +++ /dev/null @@ -1,12 +0,0 @@ -global: - scrape_interval: 15s - evaluation_interval: 15s - -scrape_configs: - - job_name: immich_api - static_configs: - - targets: ['immich-server:8081'] - - - job_name: immich_microservices - static_configs: - - targets: ['immich-server:8082'] diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index fbb000246e..0000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Dependencies -/node_modules - -# Production -/build - -# Generated files -.docusaurus -.cache-loader - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* -yarn.lock - -/static/openapi.json diff --git a/docs/.nvmrc b/docs/.nvmrc deleted file mode 100644 index 3fe3b1570a..0000000000 --- a/docs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.13.0 diff --git a/docs/.prettierignore b/docs/.prettierignore deleted file mode 100644 index 580649a5a5..0000000000 --- a/docs/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -.docusaurus/ diff --git a/docs/.prettierrc b/docs/.prettierrc deleted file mode 100644 index 7cebf3813c..0000000000 --- a/docs/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "printWidth": 120, - "semi": true -} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 547cc5e6c4..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Website - -This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. - -### Installation - -``` -$ pnpm install -``` - -### Local Development - -``` -$ pnpm run start -``` - -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. - -### Build - -``` -$ pnpm run build -``` - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - -### Deployment - -Using SSH: - -``` -$ USE_SSH=true pnpm run deploy -``` - -Not using SSH: - -``` -$ GIT_USER= pnpm run deploy -``` - -If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/docs/babel.config.js b/docs/babel.config.js deleted file mode 100644 index e00595dae7..0000000000 --- a/docs/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx deleted file mode 100644 index 2fa8fd12b0..0000000000 --- a/docs/docs/FAQ.mdx +++ /dev/null @@ -1,539 +0,0 @@ -# FAQ - -## Commercial Guidelines - -### Are you open to commercial partnerships and collaborations? - -We are working to commercialize Immich and we'd love for you to help us by making Immich better. FUTO is dedicated to developing sustainable models for developing open source software for our customers. We want our customers to be delighted by the products our engineers deliver, and we want our engineers to be paid when they succeed. - -If you wish to use Immich in a commercial product not owned by FUTO, we have the following requirements: - -- Plugin Integrations: Integrations for other platforms are typically approved, provided proper notification is given. - -- Reseller Partnerships: Must adhere to the guidelines outlined below regarding trademark usage, and proper representation. - -- Strategic Collaborations: We welcome discussions about mutually beneficial partnerships that enhance the value proposition for both organizations. - -### What are your guidelines for resellers and trademark usage? - -For organizations seeking to resell Immich, we have established the following guidelines to protect our brand integrity and ensure proper representation. - -- We request that resellers do not display our trademarks on their websites or marketing materials. If such usage is discovered, we will contact you to request removal. - -- Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team. - -- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase product keys directly from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work. - -When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app - -## User - -### How can I reset the admin password? - -The admin password can be reset by running the [reset-admin-password](/administration/server-commands.md) command on the immich-server. - -### How can I see a list of all users in Immich? - -You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server. - ---- - -## Mobile App - -### What is the difference between the cloud icons on the mobile app? - -| Icon | Description | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| ![cloud](/img/cloud.svg) | Asset is only available on the server and was uploaded from some other device (like the web client) or was deleted from this device after upload | -| ![cloud-cross](/img/cloud-off.svg) | Asset is only available locally and has not yet been backed up | -| ![cloud-done](/img/cloud-done.svg) | Asset was uploaded from this device and is now backed up to the server; the original file is still on the device | - -### I cannot log into the application after an update. What can I do? - -Verify that the mobile app and server are both running the same version (major and minor). - -:::note -App store updates sometimes take longer because the stores (Google Play Store and Apple App Store) -need to approve the update first, and it can take some time. -::: - -If you still cannot log in to the app, try the following: - -- Check the mobile logs -- Make sure login credentials are correct by logging in on the web app - -### Why does foreground backup stop when I navigate away from the app? Shouldn't it transfer the job to background backup? - -Foreground backup and background backup are two separate mechanisms. They don't communicate or interact with each other. - -Foreground backup is controlled by the user's action, while background backup is controlled by your device's operating system. When the app is put in the background, the invocation of background tasks is delegated to the device's operating system scheduler. It decides when the background task can run and how long it can run. - -The behaviors differ based on your device manufacturer and operating system, but most are related to battery-saving policies. - -### Why is background backup on iOS not working? - -On iOS (iPhone and iPad), the operating system determines if a particular app can invoke background tasks based on multiple factors, most of which the Immich app has no control over. To increase the likelihood that the background backup task is run, follow the steps below: - -- Enable Background App Refresh for Immich in the iOS settings at `Settings > General > Background App Refresh`. -- Disable **Low Power Mode** when not needed, as this can prevent apps from running in the background. -- Disable Background App Refresh for apps that don't need background tasks to run. This will reduce the competition for background task invocation for Immich. -- Use the Immich app more often. - -### Why are features in the mobile app not working with a self-signed certificate, Basic Auth, custom headers, or mutual TLS? - -These network features are experimental. They often do not work with video playback, asset upload or download, and other features. -Many of these limitations are tracked in [#15230](https://github.com/immich-app/immich/issues/15230). -Instead of these experimental features, we recommend using the URL switching feature, a VPN, or a [free trusted SSL certificate](https://letsencrypt.org/) for your domain. - -We are not actively developing these features and will not be able to provide support, but welcome contributions to improve them. -Please discuss any large PRs with our dev team to ensure your time is not wasted. - -### Why isn't the mobile app updated yet? - -The app stores can take a few days to approve new builds of the app. If you're impatient, android APKs can be downloaded from the GitHub releases. - ---- - -## Assets - -### Does Immich change the file? - -No, Immich does not modify the original files. -All edited metadata is saved in companion `.xmp` sidecar files and the database. -However, Immich will delete original files that have been trashed when the trash is emptied in the Immich UI. - -### Why do my file names appear as a random string in the file manager? - -When Storage Template is off (default) Immich saves the file names in a random string (also known as random UUIDs) to prevent duplicate file names. -To retrieve the original file names, you must enable the Storage Template and then run the STORAGE TEMPLATE MIGRATION job. -It is recommended to read about [Storage Template](/administration/storage-template) before activation. - -### Can I add my existing photo library? - -Yes, with an [External Library](/features/libraries.md). - -### What happens to existing files after I choose a new [Storage Template](/administration/storage-template.mdx)? - -Template changes will only apply to _new_ assets. To retroactively apply the template to previously uploaded assets, run the Storage Migration Job, available on the [Jobs](/administration/jobs-workers/#jobs) page. - -### Why are only photos and not videos being uploaded to Immich? - -This often happens when using a reverse proxy in front of Immich. -Make sure to [set your reverse proxy](/administration/reverse-proxy/) to allow large requests. -Also, check the disk space of your reverse proxy. -In some cases, proxies cache requests to disk before passing them on, and if disk space runs out, the request fails. - -If you are using Cloudflare Tunnel, please know that they set a maximum filesize of 100 MB that cannot be changed. -At times, files larger than this may work, potentially up to 1 GB. However, the official limit is 100 MB. -If you are having issues, we recommend switching to a different network deployment. - -### Why are some photos stored in the file system with the wrong date? - -There are a few different scenarios that can lead to this situation. The solution is to rerun the storage migration job. -The job is only automatically run once per asset after upload. If metadata extraction originally failed, the jobs were cleared/canceled, etc., -the job may not have run automatically the first time. - -### How can I hide a photo or video from the timeline? - -You can _archive_ them. This will hide the asset from the main timeline and folder view, but it will still show up in searches. All archived assets can be found in the _Archive_ view - -### How can I backup data from Immich? - -See [Backup and Restore](/administration/backup-and-restore.md). - -### Does Immich support reading existing face tag metadata? - -Yes, it creates new faces and persons from the imported asset metadata. For details see the [feature request #4348](https://github.com/immich-app/immich/discussions/4348) and [PR #6455](https://github.com/immich-app/immich/pull/6455). - -### Does Immich support the filtering of NSFW images? - -No, it currently does not. There is an [open feature request on Github](https://github.com/immich-app/immich/discussions/2451). - -### Why are there so many thumbnail generation jobs? - -There are three thumbnail jobs for each asset: - -- Blurred (thumbhash) -- Preview (Webp) -- Thumbnail (Jpeg) - -Also, there are additional jobs for person (face) thumbnails. - -### Why do files from WhatsApp not appear with the correct date? - -Files sent on WhatsApp are saved without metadata on the file. Therefore, Immich has no way of knowing the original date of the file when files are uploaded from WhatsApp, -not the order of arrival on the device. [See #9116](https://github.com/immich-app/immich/discussions/9116). - -### What happens if an asset exists in more than one account? - -There are no requirements for assets to be unique across users. If multiple users upload the same image, it is processed as if it were a distinct asset, and jobs run and thumbnails are generated accordingly. - -### Why do HDR videos appear pale in Immich player but look normal after download? - -Immich uses a player with known HDR color display issues. We are experimenting with a different player that provides better color profiles for HDR content for future improvements. - -### Why does Immich transcode my videos to a lower quality? - -Immich always keeps your original files. Alongside that, it generates a transcoded version for compatibility and performance reasons. - -### How can I delete transcoded videos without deleting the original? - -The transcoded version of an asset can be deleted by setting a transcode policy that makes it unnecessary and then running a transcoding job for that asset. This can be done on a per-asset basis by starting a transcoding job for a single asset with the _Refresh encoded videos_ button in the asset viewer options or for all assets by running transcoding jobs for all assets from the administration page. - -To update the transcode policy, navigate to Administration > Video Transcoding Settings > Transcoding Policy and select a policy from the drop-down. This policy will determine whether an existing transcode will be deleted or overwritten in the transcoding job. If a video should be transcoded according to this policy, an existing transcode is overwritten. If not, then it is deleted. - -:::note -For example, say you have existing transcodes with the policy "Videos higher than normal resolution or not in the desired format" and switch to a narrower policy: "Videos not in the desired format." If an asset was only transcoded due to its resolution, running a transcoding job for it will delete the existing transcode. This is because resolution is no longer part of the transcode policy and the transcode is unnecessary. Likewise, if you set the policy to "Don't transcode any videos" and run transcoding jobs for all assets, this will delete all existing transcodes as they are unnecessary. -::: - -### Is it possible to compress images during backup? - -No. Our design principle is that the original assets should always be untouched. - -### How can I mount a CIFS/Samba volume within Docker? - -If you aren't able to or prefer not to mount Samba on the host (such as Windows environment), you can mount the volume within Docker. -Below is an example in the `docker-compose.yml`. - -Change your username, password, local IP, and share name, and see below where the line `- originals:/usr/src/app/originals`, -correlates to the section where the volume `originals` was created. You can call this whatever you like, and map it to the docker container as you like. -For example you could change `originals:` to `Photos:`, and change `- originals:/usr/src/app/originals` to `Photos:/usr/src/app/photos`. - -```diff -... -services: - immich-server: -... - volumes: - # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file - - ${UPLOAD_LOCATION}:/data - - /etc/localtime:/etc/localtime:ro -+ - originals:/usr/src/app/originals -... -volumes: - model-cache: -+ originals: -+ driver_opts: -+ type: cifs -+ o: 'iocharset=utf8,username=USERNAMEHERE,password=PASSWORDHERE,rw' # change to `ro` if read only desired -+ device: '//localipaddress/sharename' -``` - ---- - -## Albums - -### Can I keep my existing album structure while importing assets into Immich? - -Yes, by using the [Immich CLI](/features/command-line-interface) along with the `--album` flag. - -### Is there a way to reorder photos within an album? - -No, not yet. For updates on this planned feature, follow the [GitHub discussion](https://github.com/immich-app/immich/discussions/1689). - ---- - -## External Library - -### Can I add an external library while keeping the existing album structure? - -We haven't implemented an official mechanism for creating albums from external libraries, but there are some [workarounds from the community](https://github.com/immich-app/immich/discussions/4279) to help you achieve that. - -### What happens to duplicates in external libraries? - -Duplicate checking only exists for upload libraries, using the file hash. Furthermore, duplicate checking is not global, but _per library_. Therefore, a situation where the same file appears twice in the timeline is possible, especially for external libraries. - -### Why are my edits to files not being saved in read-only external libraries? - -Images in read-write external libraries (the default) can be edited as normal. -In read-only libraries (`:ro` in the `docker-compose.yml`), Immich is unable to create the `.xmp` sidecar files to store edited file metadata. -For this reason, the metadata (timestamp, location, description, star rating, etc.) cannot be edited for files in read-only external libraries. - -### How are deletions of files handled in external libraries? - -Immich will attempt to delete original files that have been trashed when the trash is emptied. -In read-write external libraries (the default), Immich will delete the original file. -In read-only libraries (`:ro` in the `docker-compose.yml`), files can still be trashed in the UI. -However, when the trash is emptied, the files will re-appear in the main timeline since Immich is unable to delete the original file. - ---- - -## Machine Learning - -### How does smart search work? - -Immich uses CLIP models. An ML model converts each image to an "embedding", which is essentially a string of numbers that semantically encodes what is in the image. The same is done for the text that you enter when you do a search, and that text embedding is then compared with those of the images to find similar ones. As such, there are no "tags", "labels", or "descriptions" generated that you can look at. For more information about CLIP and its capabilities, read about it [here](https://openai.com/research/clip). - -### How does facial recognition work? - -See [How Facial Recognition Works](/features/facial-recognition#how-facial-recognition-works) for details. - -### How can I disable machine learning? - -:::info -Disabling machine learning will result in a poor experience for searching and the 'Explore' page, as these are reliant on it to work as intended. -::: - -Machine learning can be disabled under Administration > Settings > Machine Learning Settings, either entirely or by model type. For instance, you can choose to disable smart search with CLIP, but keep facial recognition enabled. This means that the machine learning service will only process the enabled jobs. - -However, disabling all jobs will not disable the machine learning service itself. To prevent it from starting up at all in this case, you can comment out the `immich-machine-learning` section of the docker-compose.yml. - -### I'm getting errors about models being corrupt or failing to download. What do I do? - -You can delete the model cache volume, where models are downloaded. This will give the service a clean environment to download the model again. If models are failing to download entirely, you can manually download them from [Hugging Face][huggingface] and place them in the cache folder. - -### Can I use a custom CLIP model? - -No, this is not supported. Only models listed in the [Hugging Face][huggingface] page are compatible. Feel free to make a feature request if there's a model not listed here that you think should be added. - -### I want to be able to search in other languages besides English. How can I do that? - -You can change to a multilingual CLIP model. See [here](/features/searching#clip-models) for instructions. - -### Does Immich support Facial Recognition for videos? - -Immich's machine learning feature operates on the generated thumbnail. If a face is visible in the video's thumbnail it will be picked up by facial recognition. -Scanning the entire video for faces may be implemented in the future. - -### Does Immich have animal recognition? - -No. -:::tip -You can use [Smart Search](/features/searching.md) for this to some extent. For example, if you have a Golden Retriever and a Chihuahua, type these words in the smart search and watch the results. -::: - -### I'm getting a lot of "faces" that aren't faces, what can I do? - -You can increase the MIN DETECTION SCORE to 0.8 to help prevent bad thumbnails. Setting the score too high (above 0.9) might filter out too many real faces depending on the library used. If you just want to hide specific faces, you can adjust the 'MIN FACES DETECTED' setting in the administration panel -to increase the bar for what the algorithm considers a "core face" for that person, reducing the chance of bad thumbnails being chosen. - -### The immich_model-cache volume takes up a lot of space, what could be the problem? - -If you installed several models and chose not to use some of them, it might be worth deleting the old models that are in immich_model-cache. To do this you can mount the model cache and remove the undesired models. - -
-Steps - -```bash -docker run -it --rm -v immich_model-cache:/mnt-models alpine sh -cd /mnt-models -ls clip/ facial-recognition/ -# rm -r clip/ABC facial-recognition/DEF # delete unused models -``` - -
- ---- - -## Performance - -### Why is Immich slow on low-memory systems like the Raspberry Pi? - -Immich optionally uses transcoding and machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/FAQ#can-i-lower-cpu-and-ram-usage) this or host Immich's machine-learning container on a [more powerful system](/guides/remote-machine-learning), or [disable](/FAQ#how-can-i-disable-machine-learning) machine learning entirely. - -### Can I lower CPU and RAM usage? - -The initial backup is the most intensive due to the number of jobs running. The most CPU-intensive ones are transcoding and machine learning jobs (Smart Search, Face Detection), and to a lesser extent thumbnail generation. Here are some ways to lower their CPU usage: - -- Lower the job concurrency for these jobs to 1. -- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2. -- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good. - - For facial recognition on new images to work properly, You must re-run the Face Detection job for all images after this. -- At the container level, you can [set resource constraints](/FAQ#can-i-limit-cpu-and-ram-usage) to lower usage further. - - It's recommended to only apply these constraints _after_ taking some of the measures here for best performance. -- If these changes are not enough, see [above](/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning. - -### Can I limit CPU and RAM usage? - -By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. To limit this, you can add the following to the `docker-compose.yml` block of any containers that you want to have limited resources. - -
-docker-compose.yml - -```yaml -deploy: - resources: - limits: - # Number of CPU threads - cpus: '1.00' - # Gigabytes of memory - memory: '1G' -``` - -
-For more details, you can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit). - -Note that memory constraints work by terminating the container, so this can introduce instability if set too low. - -### How can I boost machine learning speed? - -:::note -This advice improves throughput, not latency. This is to say that it will make Smart Search jobs process more quickly, but it won't make searching faster. -::: - -You can increase throughput by increasing the job concurrency for machine learning jobs (Smart Search, Face Detection). With higher concurrency, the host will work on more assets in parallel. You can do this by navigating to Administration > Settings > Job Settings and increasing concurrency as needed. - -:::danger -On a normal machine, 2 or 3 concurrent jobs can probably max the CPU. Storage speed and latency can quickly become the limiting factor beyond this, particularly when using HDDs. - -The concurrency can be increased more comfortably with a GPU, but should still not be above 16 in most cases. - -Do not exaggerate with the job concurrency because you're probably thoroughly overloading the server. -::: - -### My server shows Server Status Offline | Version Unknown. What can I do? - -You need to [enable WebSockets](/administration/reverse-proxy/) on your reverse proxy. - ---- - -## Docker - -### How can I see Immich logs? - -Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/guides/docker-help.md). - -### How can I reduce the log verbosity of Redis? - -To decrease Redis logs, you can add the following line to the `redis:` section of the `docker-compose.yml`: - -` command: redis-server --loglevel warning` - -### How can I run Immich as a non-root user? - -You can change the user in the container by setting the `user` argument in `docker-compose.yml` for each service. -You may need to add mount points or docker volumes for the following internal container paths: - -- `immich-machine-learning:/.config` -- `immich-machine-learning:/.cache` -- `redis:/data` - -The non-root user/group needs read/write access to the volume mounts, including `UPLOAD_LOCATION` and `/cache` for machine-learning. - -:::note Docker Compose Volumes -The Docker Compose top level volume element does not support non-root access, all of the above volumes must be local volume mounts. -::: - -For a further hardened system, you can add the following block to every container. - -
-docker-compose.yml - -```yaml -security_opt: - # Prevent escalation of privileges after the container is started - - no-new-privileges:true -cap_drop: - # Prevent access to raw network traffic - - NET_RAW -``` - -
- -### How can I purge data from Immich? - -Data for Immich comes in two forms: - -1. **Metadata** stored in a Postgres database, stored in the `DB_DATA_LOCATION` folder (previously `pg_data` Docker volume). -2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder, more [info](/administration/backup-and-restore#asset-types-and-storage-locations). - -:::warning -This will destroy your database and reset your instance, meaning that you start from scratch. -::: - -```bash title="Remove Immich (containers and volumes)" -docker compose down -v -``` - -After removing the containers and volumes, there are a few directories that need to be deleted to reset Immich to a new installation. Once they are deleted, Immich can be started back up and will be a fresh installation. - -- `DB_DATA_LOCATION` contains the database, media info, and settings. -- `UPLOAD_LOCATION` contains all the media uploaded to Immich. - -:::note Portainer -If you use portainer, bring down the stack in portainer. Go into the volumes section -and remove all the volumes related to immich then restart the stack. -::: - -### Why does the machine learning service report workers crashing? - -:::note -If the error says the worker is exiting, then this is normal. This is a feature intended to reduce RAM consumption when the service isn't being used. -::: - -There are a few reasons why this can happen. - -If the error mentions SIGKILL or error code 137, it most likely means the service is running out of memory. -Consider either increasing the server's RAM or moving the service to a server with more RAM. - -If it mentions SIGILL (note the lack of a K) or error code 132, it most likely means your server's CPU is incompatible with Immich. - -## Database - -### Why am I getting database ownership errors? - -If you get database errors such as `FATAL: data directory "/var/lib/postgresql/data" has wrong ownership` upon database startup, this is likely due to an issue with your filesystem. -NTFS and ex/FAT/32 filesystems are not supported. See [here](/install/requirements#special-requirements-for-windows-users) for more details. - -### How can I verify the integrity of my database? - -Database checksums are enabled by default for new installations since v1.104.0. You can check if they are enabled by running the following command. -A result of `on` means that checksums are enabled. - -
-Check if checksums are enabled - -```bash -docker exec -it immich_postgres psql --dbname=postgres --username= --command="show data_checksums" - data_checksums ----------------- - on -(1 row) -``` - -
- -If checksums are enabled, you can check the status of the database with the following command. A normal result is all `0`s. - -
-Check for database corruption - -```bash -docker exec -it immich_postgres psql --dbname=postgres --username= --command="SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database WHERE datname IS NOT NULL" - datname | checksum_failures | checksum_last_failure ------------+-------------------+----------------------- - postgres | 0 | - immich | 0 | - template1 | 0 | - template0 | 0 | -(4 rows) -``` - -
- -You can also scan the Postgres database file structure for errors: - -
-Scan for file structure errors -```bash -docker exec -it immich_postgres pg_amcheck --username= --heapallindexed --parent-check --rootdescend --progress --all --install-missing -``` - -A normal result will end something like this and return with an exit code of `0`: - -```bash -7470/8832 relations (84%), 730829/734735 pages (99%) -8425/8832 relations (95%), 734367/734735 pages (99%) -8832/8832 relations (100%), 734735/734735 pages (100%) -``` - -
- -If corruption is detected, you should immediately make a backup before performing any other work in the database. -To do so, you may need to set the `zero_damaged_pages=on` flag for the database server to allow `pg_dumpall` to succeed. -After taking a backup, the recommended next step is to restore the database from a healthy backup before corruption was detected. -The damaged database dump can be used to manually recover any changes made since the last backup, if needed. - -The causes of possible corruption are many, but can include unexpected poweroffs or unmounts, use of a network share for Postgres data, or a poor storage medium such an SD card or failing HDD/SSD. - -[huggingface]: https://huggingface.co/immich-app diff --git a/docs/docs/administration/_category_.json b/docs/docs/administration/_category_.json deleted file mode 100644 index 749de63f59..0000000000 --- a/docs/docs/administration/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Administration", - "position": 4 -} diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md deleted file mode 100644 index ae605f8462..0000000000 --- a/docs/docs/administration/backup-and-restore.md +++ /dev/null @@ -1,321 +0,0 @@ -# Backup and Restore - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import { mdiAlertCircle, mdiCheckCircle } from '@mdi/js'; -import Icon from '@mdi/react'; - -A [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution. This page provides an overview on how to backup the database and the location of user-uploaded pictures and videos. A template bash script that can be run as a cron job is provided [here](/guides/template-backup-script.md) - -:::danger -The instructions on this page show you how to prepare your Immich instance to be backed up, and which files to take a backup of. You still need to take care of using an actual backup tool to make a backup yourself. -::: - -## Database - -Immich stores [file paths](https://github.com/immich-app/immich/discussions/3299) and user metadata in the database. It does not scan the library folder, so database backups are essential. - -### Automatic Database Backups - -Immich automatically creates database backups for disaster-recovery purposes. These backups are stored in `UPLOAD_LOCATION/backups` and can be managed through the web interface. - -You can adjust the backup schedule and retention settings in **Administration > Settings > Backup** (default: keep last 14 backups, create daily at 2:00 AM). - -:::caution -Database backups do **not** contain photos or videos — only metadata. They must be used together with a copy of the files in `UPLOAD_LOCATION` as outlined below. -::: - -#### Creating a Backup - -You can trigger a database backup manually: - -1. Go to **Administration > Job Queues** -2. Click **Create job** in the top right -3. Select **Create Database Backup** and click **Confirm** - -The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit. - -### Restoring a Database Backup - -Immich provides two ways to restore a database backup: through the web interface or via the command line. The web interface is the recommended method for most users. - -#### Restore from Settings {#restore-from-settings} - -If you have an existing Immich installation: - - - -1. Go to **Administration > Maintenance** -2. Expand the **Restore database backup** section -3. You'll see a list of available backups with their version and creation date -4. Click **Restore** next to the backup you want to restore -5. Confirm the restore operation - -:::info -Restoring a backup will wipe the current database and replace it with the backup. A restore point is automatically created before the operation begins, allowing rollback if the restore fails. -::: - -#### Restore from Onboarding {#restore-from-onboarding} - -If you're setting up Immich on a fresh installation and want to restore from an existing backup: - -1. Download and populate `.env` and `docker-compose.yml` as per the [installation instructions](/install/docker-compose). -2. Move the previous's instance data directories containing `backups`, `encoded-video`, `library`, `profile`, `thumbs` and `upload` into the new `UPLOAD_LOCATION` -3. **(For external libraries)** If you used external library feature in your previous instance, make sure that the mount settings in your new `docker-compose.yml` reflect the same structure. You may need to move files accordingly. - -:::info Example - -Assuming your previous `UPLOAD_LOCATION` was `UPLOAD_LOCATION=/my-broken-instance/media` and your new one is `UPLOAD_LOCATION=/a-brand-new-instance/data`, you will need to perform the following file moves: - -``` -/my-broken-instance/media/backups -> /a-brand-new-instance/data/backups -/my-broken-instance/media/encoded-video -> /a-brand-new-instance/data/encoded-video -/my-broken-instance/media/library -> /a-brand-new-instance/data/library -/my-broken-instance/media/profile -> /a-brand-new-instance/data/profile -/my-broken-instance/media/thumbs -> /a-brand-new-instance/data/thumbs -/my-broken-instance/media/upload -> /a-brand-new-instance/data/upload -``` - -::: - -4. Start the Immich services with `docker compose up -d` - - - -5. On the welcome screen, click **Restore from backup** -6. Immich will enter maintenance mode and display integrity checks for your storage folders -7. Review the folder status to ensure your library files are accessible -8. Click **Next** to proceed to backup selection -9. Select a backup from the list or upload a backup file (`.sql.gz`) -10. Click **Restore** to begin the restoration process - -:::tip -Before restoring, ensure your `UPLOAD_LOCATION` folders contain the same files that existed when the backup was created. The integrity check will show you which folders are readable/writable and how many files they contain. -::: - -### Uploading a Backup File {#uploading-backup} - -You can upload a database backup file directly: - -1. In the **Restore database backup** section, click **Select from computer** -2. Choose a `.sql.gz` file -3. The uploaded backup will appear in the list with an `uploaded-` prefix -4. Click **Restore** to restore from the uploaded file - -### Backup Version Compatibility {#backup-compatibility} - -When viewing backups, Immich displays compatibility indicators based on the current version and the information from the filename: - -- Backup version matches current Immich version -- Backup was created with a different Immich version -- Could not determine backup version - -:::warning -Restoring a backup from a different Immich version may require database migrations. The restore process will attempt to run migrations automatically, but you should ensure you're restoring to a compatible version when possible. -::: - -### Restore Process {#restore-process} - -During restoration, Immich will: - -1. Create a backup of the current database (restore point) -2. Restore the selected backup -3. Run database migrations if needed -4. Perform a health check to verify the restore succeeded - -If the restore fails (e.g., corrupted backup or missing admin user), Immich will automatically roll back to the restore point. - -### Restore via Command Line {#restore-cli} - -For advanced users or automated recovery scenarios, you can restore a database backup using the command line. - - - - -```bash title='Backup' -# Replace with the database username - usually postgres unless you have changed it. -# Replace with the database name - usually immich unless you have changed it. -docker exec -t immich_postgres pg_dump --clean --if-exists --dbname= --username= | gzip > "/path/to/backup/dump.sql.gz" -``` - -```bash title='Restore' -docker compose down -v # CAUTION! Deletes all Immich data to start from scratch -## Uncomment the next line and replace DB_DATA_LOCATION with your Postgres path to permanently reset the Postgres database -# rm -rf DB_DATA_LOCATION # CAUTION! Deletes all Immich data to start from scratch -docker compose pull # Update to latest version of Immich (if desired) -docker compose create # Create Docker containers for Immich apps without running them -docker start immich_postgres # Start Postgres server -sleep 10 # Wait for Postgres server to start up -# Check the database user if you deviated from the default -# Replace with the database username - usually postgres unless you have changed it. -# Replace with the database name - usually immich unless you have changed it. -gunzip --stdout "/path/to/backup/dump.sql.gz" \ -| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \ -| docker exec -i immich_postgres psql --dbname= --username= --single-transaction --set ON_ERROR_STOP=on # Restore Backup -docker compose up -d # Start remainder of Immich apps -``` - - - - -```powershell title='Backup' -# Replace with the database username - usually postgres unless you have changed it. -# Replace with the database name - usually immich unless you have changed it. -[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dump --clean --if-exists --dbname= --username=)) -``` - -```powershell title='Restore' -docker compose down -v # CAUTION! Deletes all Immich data to start from scratch -## Uncomment the next line and replace DB_DATA_LOCATION with your Postgres path to permanently reset the Postgres database -# Remove-Item -Recurse -Force DB_DATA_LOCATION # CAUTION! Deletes all Immich data to start from scratch -## You should mount the backup (as a volume, example: `- 'C:\path\to\backup\dump.sql:/dump.sql'`) into the immich_postgres container using the docker-compose.yml -docker compose pull # Update to latest version of Immich (if desired) -docker compose create # Create Docker containers for Immich apps without running them -docker start immich_postgres # Start Postgres server -sleep 10 # Wait for Postgres server to start up -docker exec -it immich_postgres bash # Enter the Docker shell and run the following command -# If your backup ends in `.gz`, replace `cat` with `gunzip --stdout` -# Replace with the database username - usually postgres unless you have changed it. -# Replace with the database name - usually immich unless you have changed it. - -cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname= --username= --single-transaction --set ON_ERROR_STOP=on -exit # Exit the Docker shell -docker compose up -d # Start remainder of Immich apps -``` - - - - -:::warning -The backup and restore process changed in v2.5.0, if you have a backup created with an older version of Immich, use the documentation version selector to find manual restore instructions for your backup. -::: - -:::note -For the database restore to proceed properly, it requires a completely fresh install (i.e., the Immich server has never run since creating the Docker containers). If the Immich app has run, you may encounter Postgres conflicts (relation already exists, violated foreign key constraints, etc.). In this case, delete the `DB_DATA_LOCATION` folder to reset the database. -::: - -:::tip -Some deployment methods make it difficult to start the database without also starting the server. In these cases, set the environment variable `DB_SKIP_MIGRATIONS=true` before starting the services. This prevents the server from running migrations that interfere with the restore process. Remove this variable and restart services after the database is restored. -::: - -:::tip -The provided restore process ensures your database is never in a broken state by committing all changes in one transaction. This may be undesirable behaviour in some circumstances, you can disable it by removing `--single-transaction --set ON_ERROR_STOP=on` from the command. -::: - -## Filesystem - -Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders: - -1. `UPLOAD_LOCATION/library` -2. `UPLOAD_LOCATION/upload` -3. `UPLOAD_LOCATION/profile` - -If you choose to back up only those folders, you will need to rerun the transcoding and thumbnail generation jobs for all assets after you restore from a backup. - -:::caution -If you moved some of these folders onto a different storage device, such as `profile/`, make sure to adjust the backup path to match your setup -::: - -### Asset Types and Storage Locations - -Some storage locations are impacted by the Storage Template. See below for more details. - - - - -:::note -The `UPLOAD_LOCATION/library` folder is not used by default on new machines running version 1.92.0. It is used only if the system administrator activated the storage template engine, -for more info read the [release notes](https://github.com/immich-app/immich/releases/tag/v1.92.0#:~:text=the%20partner%E2%80%99s%20assets.-,Hardening%20storage%20template). -::: - -**1. User-Specific Folders:** - -- Each user has a unique string representing them. -- You can find your user ID in Account Account Settings -> Account -> User ID. - -**2. Asset Types and Storage Locations:** - -- **Source Assets:** - - Original assets uploaded through the browser interface & mobile & CLI. - - Stored in `UPLOAD_LOCATION/upload/`. -- **Avatar Images:** - - User profile images. - - Stored in `UPLOAD_LOCATION/profile/`. -- **Thumbs Images:** - - Preview images (small thumbnails and large previews) for each asset and thumbnails for recognized faces. - - Stored in `UPLOAD_LOCATION/thumbs/`. -- **Encoded Assets:** - - Videos that have been re-encoded from the original for wider compatibility. The original is not removed. - - Stored in `UPLOAD_LOCATION/encoded-video/`. -- **Database Dump Backups:** - - Automatic database backups created by Immich for disaster recovery. - - Stored in `UPLOAD_LOCATION/backups/`. -- **Postgres** - - The Immich database containing all the information to allow the system to function properly. - **Note:** This folder will only appear to users who have made the changes mentioned in [v1.102.0](https://github.com/immich-app/immich/discussions/8930) (an optional, non-mandatory change) or who started with this version. - - Stored in `DB_DATA_LOCATION`. - - - - -:::note -If you choose to activate the storage template engine, it will move all assets to `UPLOAD_LOCATION/library/`. - -When you turn off the storage template engine, it will leave the assets in `UPLOAD_LOCATION/library/` and will not return them to `UPLOAD_LOCATION/upload`. -**New assets** will be saved to `UPLOAD_LOCATION/upload`. -::: - -**1. User-Specific Folders:** - -- Each user has a unique string representing them. - - The administrator can set a Storage Label for a user, which will be used instead of `` for the `library/` folder. - - The Admin has a default storage label of `admin`. -- You can find your user ID and Storage Label in Account Account Settings -> Account -> User ID. - -**2. Asset Types and Storage Locations:** - -- **Source Assets:** - - Original assets uploaded through the browser interface, mobile, and CLI. - - Stored in `UPLOAD_LOCATION/library/`. -- **Avatar Images:** - - User profile images. - - Stored in `UPLOAD_LOCATION/profile/`. -- **Thumbs Images:** - - Preview images (blurred, small, large) for each asset and thumbnails for recognized faces. - - Stored in `UPLOAD_LOCATION/thumbs/`. -- **Encoded Assets:** - - Videos that have been re-encoded from the original for wider compatibility. The original is not removed. - - Stored in `UPLOAD_LOCATION/encoded-video/`. -- **Files in Upload Queue (Mobile):** - - Files uploaded through mobile apps. - - Temporarily located in `UPLOAD_LOCATION/upload/`. - - Transferred to `UPLOAD_LOCATION/library/` upon successful upload. -- **Database Dump Backups:** - - Automatic database backups created by Immich for disaster recovery. - - Stored in `UPLOAD_LOCATION/backups/`. -- **Postgres** - - The Immich database containing all the information to allow the system to function properly. - **Note:** This folder will only appear to users who have made the changes mentioned in [v1.102.0](https://github.com/immich-app/immich/discussions/8930) (an optional, non-mandatory change) or who started with this version. - - Stored in `DB_DATA_LOCATION`. - - - - - -:::danger -Do not touch the files inside these folders under any circumstances except taking a backup. Changing or removing an asset can cause untracked and missing files. -You can think of it as App-Which-Must-Not-Be-Named, the only access to viewing, changing and deleting assets is only through the mobile or browser interface. -::: - -## Backup ordering - -A backup of Immich should contain both the database and the asset files. When backing these up it's possible for them to get out of sync, potentially resulting in broken assets after you restore. -The best way of dealing with this is to stop the immich-server container while you take a backup. If nothing is changing then the backup will always be in sync. - -If stopping the container is not an option, then the recommended order is to back up the database first, and the filesystem second. This way, the worst case scenario is that there are files on the filesystem that the database doesn't know about. If necessary, these can be (re)uploaded manually after a restore. If the backup is done the other way around, with the filesystem first and the database second, it's possible for the restored database to reference files that aren't in the filesystem backup, thus resulting in broken assets. diff --git a/docs/docs/administration/email-notification.mdx b/docs/docs/administration/email-notification.mdx deleted file mode 100644 index 0da132161f..0000000000 --- a/docs/docs/administration/email-notification.mdx +++ /dev/null @@ -1,27 +0,0 @@ -# Email Notifications - -Immich supports the option to send notifications via Email for the following events: - -- Creating a new user -- Notifying a user when they get added to a shared album -- Informing other users about the addition of new assets to a shared album - -## SMTP settings - -You can access the settings panel from the web at `Administration -> Settings -> Notification settings`. - -Under Email, enter the required details to connect with an SMTP server. - -You can use [this guide](/guides/smtp-gmail) to use Gmail's SMTP server. - -## User's notifications settings - -Users can manage their email notification settings from their account settings page on the web. They can choose to turn email notifications on or off for the following events: - - - -## Notification templates - -You can override the default notification text with custom templates in HTML format. You can use tags to show dynamic tags in your templates. - - diff --git a/docs/docs/administration/img/admin-jobs.webp b/docs/docs/administration/img/admin-jobs.webp deleted file mode 100644 index c9863d163a..0000000000 Binary files a/docs/docs/administration/img/admin-jobs.webp and /dev/null differ diff --git a/docs/docs/administration/img/admin-nightly-tasks.webp b/docs/docs/administration/img/admin-nightly-tasks.webp deleted file mode 100644 index e95aa56a7b..0000000000 Binary files a/docs/docs/administration/img/admin-nightly-tasks.webp and /dev/null differ diff --git a/docs/docs/administration/img/administration-panel.webp b/docs/docs/administration/img/administration-panel.webp deleted file mode 100644 index f2f171914b..0000000000 Binary files a/docs/docs/administration/img/administration-panel.webp and /dev/null differ diff --git a/docs/docs/administration/img/authentik-redirect-uris-example.webp b/docs/docs/administration/img/authentik-redirect-uris-example.webp deleted file mode 100644 index ae54dbd293..0000000000 Binary files a/docs/docs/administration/img/authentik-redirect-uris-example.webp and /dev/null differ diff --git a/docs/docs/administration/img/authentik-redirect.webp b/docs/docs/administration/img/authentik-redirect.webp deleted file mode 100644 index f3aa10fd23..0000000000 Binary files a/docs/docs/administration/img/authentik-redirect.webp and /dev/null differ diff --git a/docs/docs/administration/img/delete-user.webp b/docs/docs/administration/img/delete-user.webp deleted file mode 100644 index 9d03e5a94a..0000000000 Binary files a/docs/docs/administration/img/delete-user.webp and /dev/null differ diff --git a/docs/docs/administration/img/disable-password-login.webp b/docs/docs/administration/img/disable-password-login.webp deleted file mode 100644 index 5e95177392..0000000000 Binary files a/docs/docs/administration/img/disable-password-login.webp and /dev/null differ diff --git a/docs/docs/administration/img/enable-password-login.webp b/docs/docs/administration/img/enable-password-login.webp deleted file mode 100644 index d1401f2afe..0000000000 Binary files a/docs/docs/administration/img/enable-password-login.webp and /dev/null differ diff --git a/docs/docs/administration/img/google-redirect-uris-example.webp b/docs/docs/administration/img/google-redirect-uris-example.webp deleted file mode 100644 index 742e77cd37..0000000000 Binary files a/docs/docs/administration/img/google-redirect-uris-example.webp and /dev/null differ diff --git a/docs/docs/administration/img/immediately-remove-user.webp b/docs/docs/administration/img/immediately-remove-user.webp deleted file mode 100644 index 0960548f1d..0000000000 Binary files a/docs/docs/administration/img/immediately-remove-user.webp and /dev/null differ diff --git a/docs/docs/administration/img/immich-email-notefaction.webp b/docs/docs/administration/img/immich-email-notefaction.webp deleted file mode 100644 index 8da19bdc2b..0000000000 Binary files a/docs/docs/administration/img/immich-email-notefaction.webp and /dev/null differ diff --git a/docs/docs/administration/img/list-users.webp b/docs/docs/administration/img/list-users.webp deleted file mode 100644 index 05b7fb3aa7..0000000000 Binary files a/docs/docs/administration/img/list-users.webp and /dev/null differ diff --git a/docs/docs/administration/img/password-login-settings.webp b/docs/docs/administration/img/password-login-settings.webp deleted file mode 100644 index fd61a18d03..0000000000 Binary files a/docs/docs/administration/img/password-login-settings.webp and /dev/null differ diff --git a/docs/docs/administration/img/reset-admin-password.webp b/docs/docs/administration/img/reset-admin-password.webp deleted file mode 100644 index 5062d24a6b..0000000000 Binary files a/docs/docs/administration/img/reset-admin-password.webp and /dev/null differ diff --git a/docs/docs/administration/img/restore-from-onboarding.webp b/docs/docs/administration/img/restore-from-onboarding.webp deleted file mode 100644 index d09454ef19..0000000000 Binary files a/docs/docs/administration/img/restore-from-onboarding.webp and /dev/null differ diff --git a/docs/docs/administration/img/restore-from-settings.webp b/docs/docs/administration/img/restore-from-settings.webp deleted file mode 100644 index f205e7ec6d..0000000000 Binary files a/docs/docs/administration/img/restore-from-settings.webp and /dev/null differ diff --git a/docs/docs/administration/img/send-user-email-notification.webp b/docs/docs/administration/img/send-user-email-notification.webp deleted file mode 100644 index 779461d269..0000000000 Binary files a/docs/docs/administration/img/send-user-email-notification.webp and /dev/null differ diff --git a/docs/docs/administration/img/server-stats.webp b/docs/docs/administration/img/server-stats.webp deleted file mode 100644 index 33ffa1353b..0000000000 Binary files a/docs/docs/administration/img/server-stats.webp and /dev/null differ diff --git a/docs/docs/administration/img/user-edit-menu.webp b/docs/docs/administration/img/user-edit-menu.webp deleted file mode 100644 index 5dd7edd298..0000000000 Binary files a/docs/docs/administration/img/user-edit-menu.webp and /dev/null differ diff --git a/docs/docs/administration/img/user-management-update.webp b/docs/docs/administration/img/user-management-update.webp deleted file mode 100644 index 00dda77ce3..0000000000 Binary files a/docs/docs/administration/img/user-management-update.webp and /dev/null differ diff --git a/docs/docs/administration/img/user-notifications-settings.webp b/docs/docs/administration/img/user-notifications-settings.webp deleted file mode 100644 index 964556e928..0000000000 Binary files a/docs/docs/administration/img/user-notifications-settings.webp and /dev/null differ diff --git a/docs/docs/administration/img/user-notifications-templates.webp b/docs/docs/administration/img/user-notifications-templates.webp deleted file mode 100644 index 5c5f68ac5e..0000000000 Binary files a/docs/docs/administration/img/user-notifications-templates.webp and /dev/null differ diff --git a/docs/docs/administration/img/user-quota-size.webp b/docs/docs/administration/img/user-quota-size.webp deleted file mode 100644 index d35fca571b..0000000000 Binary files a/docs/docs/administration/img/user-quota-size.webp and /dev/null differ diff --git a/docs/docs/administration/img/user-storage-label.webp b/docs/docs/administration/img/user-storage-label.webp deleted file mode 100644 index 661dd2b23f..0000000000 Binary files a/docs/docs/administration/img/user-storage-label.webp and /dev/null differ diff --git a/docs/docs/administration/jobs-workers.md b/docs/docs/administration/jobs-workers.md deleted file mode 100644 index 74025f8ae8..0000000000 --- a/docs/docs/administration/jobs-workers.md +++ /dev/null @@ -1,73 +0,0 @@ -# Jobs and Workers - -## Workers - -### Architecture - -The `immich-server` container contains multiple workers: - -- `api`: responds to API requests for data and files for the web and mobile app. -- `microservices`: handles most other work, such as thumbnail generation and video encoding, in the form of _jobs_. Simply put, a job is a request to process data in the background. - -## Split workers - -If you prefer to throttle or distribute the workers, you can do this using the [environment variables](/install/environment-variables) to specify which container should pick up which tasks. - -For example, for a simple setup with one container for the Web/API and one for all other microservices, you can do the following: - -Copy the entire `immich-server` block as a new service and make the following changes to the **copy**: - -```diff -- immich-server: -- container_name: immich_server -... -- ports: -- - 2283:2283 -+ immich-microservices: -+ container_name: immich_microservices -``` - -Once you have two copies of the immich-server service, make the following changes to each one. This will allow one container to only serve the web UI and API, and the other one to handle all other tasks. - -```diff -services: - immich-server: - ... -+ environment: -+ IMMICH_WORKERS_INCLUDE: 'api' - - immich-microservices: - ... -+ environment: -+ IMMICH_WORKERS_EXCLUDE: 'api' -``` - -## Jobs - -When a new asset is uploaded it kicks off a series of jobs, which include metadata extraction, thumbnail generation, machine learning tasks, and storage template migration, if enabled. To view the status of a job navigate to the Administration -> Jobs page. - - - -Additionally, some jobs (such as memories generation) run on a schedule, which is every night at midnight by default. To change when they run or enable/disable a job navigate to System Settings -> [Nightly Tasks Settings](https://my.immich.app/admin/system-settings?isOpen=nightly-tasks). - - - -:::note -Some jobs ([External Libraries](/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings. -::: - -## Job processing order - -The below diagram shows the job run order for newly uploaded files - -```mermaid -graph TD - A[Asset Upload] --> B[Metadata Extraction] - B --> C[Storage Template Migration] - C --> D["Thumbnail Generation (Large, small, blurred and person)"] - D --> E[Smart Search] - D --> F[Face Detection] - D --> G[Video Transcoding] - E --> H[Duplicate Detection] - F --> I[Facial Recognition] -``` diff --git a/docs/docs/administration/maintenance-mode.md b/docs/docs/administration/maintenance-mode.md deleted file mode 100644 index 47848bef42..0000000000 --- a/docs/docs/administration/maintenance-mode.md +++ /dev/null @@ -1,18 +0,0 @@ -# Maintenance Mode - -Maintenance mode is used to perform administrative tasks such as restoring backups to Immich. - -You can enter maintenance mode by either: - -- Selecting "Switch to maintenance mode" in `Maintenance` tab in administration. -- Running the enable maintenance mode [administration command](./server-commands.md). - -## Logging in during maintenance - -Maintenance mode uses a separate login system which is handled automatically behind the scenes in most cases. Enabling maintenance mode in settings will automatically log you into maintenance mode when the server comes back up. - -If you find that you've been logged out, you can: - -- Open the logs for the Immich server and look for _"🚧 Immich is in maintenance mode, you can log in using the following URL:"_ -- Run the enable maintenance mode [administration command](./server-commands.md) again, this will give you a new URL to login with. -- Run the disable maintenance mode [administration command](./server-commands.md) then re-enter through system settings. diff --git a/docs/docs/administration/oauth.md b/docs/docs/administration/oauth.md deleted file mode 100644 index d0a9ce733e..0000000000 --- a/docs/docs/administration/oauth.md +++ /dev/null @@ -1,256 +0,0 @@ -# OAuth Authentication - -This page contains details about using OAuth in Immich. - -:::tip -Unable to set `app.immich:///oauth-callback` as a valid redirect URI? See [Mobile Redirect URI](#mobile-redirect-uri) for an alternative solution. -::: - -## Overview - -Immich supports 3rd party authentication via [OpenID Connect][oidc] (OIDC), an identity layer built on top of OAuth2. OIDC is supported by most identity providers, including: - -- [Authentik](https://integrations.goauthentik.io/media/immich/) -- [Authelia](https://www.authelia.com/integration/openid-connect/immich/) -- [Okta](https://www.okta.com/openid-connect/) -- [Google](https://developers.google.com/identity/openid-connect/openid-connect) - -## Prerequisites - -Before enabling OAuth in Immich, a new client application needs to be configured in the 3rd-party authentication server. While the specifics of this setup vary from provider to provider, the general approach should be the same. - -1. Create a new (Client) Application - 1. The **Provider** type should be `OpenID Connect` or `OAuth2` - 2. The **Client type** should be `Confidential` - 3. The **Application** type should be `Web` - 4. The **Grant** type should be `Authorization Code` - -2. Configure Redirect URIs/Origins - - The **Sign-in redirect URIs** should include: - - `app.immich:///oauth-callback` - for logging in with OAuth from the [Mobile App](/features/mobile-app.mdx) - - `http://DOMAIN:PORT/auth/login` - for logging in with OAuth from the Web Client - - `http://DOMAIN:PORT/user-settings` - for manually linking OAuth in the Web Client - - Redirect URIs should contain all the domains you will be using to access Immich. Some examples include: - - Mobile - - `app.immich:///oauth-callback` (You **MUST** include this for iOS and Android mobile apps to work properly) - - Localhost - - `http://localhost:2283/auth/login` - - `http://localhost:2283/user-settings` - - Local IP - - `http://192.168.0.200:2283/auth/login` - - `http://192.168.0.200:2283/user-settings` - - Hostname - - `https://immich.example.com/auth/login` - - `https://immich.example.com/user-settings` - -## Enable OAuth - -Once you have a new OAuth client application configured, Immich can be configured using the Administration Settings page, available on the web (Administration -> Settings). - -| Setting | Type | Default | Description | -| ---------------------------------------------------- | ------- | -------------------- | ----------------------------------------------------------------------------------- | -| Enabled | boolean | false | Enable/disable OAuth | -| `issuer_url` | URL | (required) | Required. Self-discovery URL for client (from previous step) | -| `client_id` | string | (required) | Required. Client ID (from previous step) | -| `client_secret` | string | (required) | Required. Client Secret (previous step) | -| `scope` | string | openid email profile | Full list of scopes to send with the request (space delimited) | -| `id_token_signed_response_alg` | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) | -| `userinfo_signed_response_alg` | string | none | The algorithm used to sign the userinfo response (examples: RS256, HS256) | -| Request timeout | string | 30,000 (30 seconds) | Number of milliseconds to wait for http requests to complete before giving up | -| Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**¹** | -| Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**¹** | -| Storage Quota Claim | string | immich_quota | Claim mapping for the user's storage**¹** | -| Default Storage Quota (GiB) | number | 0 | Default quota for user without storage quota claim (empty for unlimited quota) | -| Button Text | string | Login with OAuth | Text for the OAuth button on the web | -| Auto Register | boolean | true | When true, will automatically register a user the first time they sign in | -| [Auto Launch](#auto-launch) | boolean | false | When true, will skip the login page and automatically start the OAuth login process | -| [Mobile Redirect URI Override](#mobile-redirect-uri) | URL | (empty) | Http(s) alternative mobile redirect URI | - -:::note Claim Options [1] - -Claim is only used on user creation and not synchronized after that. - -::: - -:::info -The Issuer URL should look something like the following, and return a valid json document. - -- `https://accounts.google.com/.well-known/openid-configuration` -- `http://localhost:9000/application/o/immich/.well-known/openid-configuration` - -The `.well-known/openid-configuration` part of the url is optional and will be automatically added during discovery. -::: - -## Auto Launch - -When Auto Launch is enabled, the login page will automatically redirect the user to the OAuth authorization url, to login with OAuth. To access the login screen again, use the browser's back button, or navigate directly to `/auth/login?autoLaunch=0`. -Auto Launch can also be enabled on a per-request basis by navigating to `/auth/login?autoLaunch=1`, this can be useful in situations where Immich is called from e.g. Nextcloud using the _External sites_ app and the _oidc_ app so as to enable users to directly interact with a logged-in instance of Immich. - -## Mobile Redirect URI - -The redirect URI for the mobile app is `app.immich:///oauth-callback`, which is a [Custom Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app). If this custom scheme is an invalid redirect URI for your OAuth Provider, you can work around this by doing the following: - -1. Configure an http(s) endpoint to forwards requests to `app.immich:///oauth-callback` -2. Whitelist the new endpoint as a valid redirect URI with your provider. -3. Specify the new endpoint as the `Mobile Redirect URI Override`, in the OAuth settings. - -With these steps in place, you should be able to use OAuth from the [Mobile App](/features/mobile-app.mdx) without a custom scheme redirect URI. - -:::info -Immich has a route (`/api/oauth/mobile-redirect`) that is already configured to forward requests to `app.immich:///oauth-callback`, and can be used for step 1. -::: - -## Example Configuration - -
-Authelia Example - -### Authelia Example - -Here's an example of OAuth configured for Authelia: - -This assumes there exist an attribute `immichquota` in the user schema, which is used to set the user's storage quota in Immich. -The configuration concerning the quota is optional. - -```yaml -authentication_backend: - ldap: - # The LDAP server configuration goes here. - # See: https://www.authelia.com/c/ldap - attributes: - extra: - immichquota: # The attribute name from LDAP - name: 'immich_quota' - multi_valued: false - value_type: 'integer' -identity_providers: - oidc: - ## The other portions of the mandatory OpenID Connect 1.0 configuration go here. - ## See: https://www.authelia.com/c/oidc - claims_policies: - immich_policy: - custom_claims: - immich_quota: - attribute: 'immich_quota' - scopes: - immich_scope: - claims: - - 'immich_quota' - - clients: - - client_id: 'immich' - client_name: 'Immich' - # https://www.authelia.com/integration/openid-connect/frequently-asked-questions/#how-do-i-generate-a-client-identifier-or-client-secret - client_secret: $pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' - public: false - require_pkce: false - redirect_uris: - - 'https://example.immich.app/auth/login' - - 'https://example.immich.app/user-settings' - - 'app.immich:///oauth-callback' - scopes: - - 'openid' - - 'profile' - - 'email' - - 'immich_scope' - claims_policy: 'immich_policy' - response_types: - - 'code' - grant_types: - - 'authorization_code' - id_token_signed_response_alg: 'RS256' - userinfo_signed_response_alg: 'RS256' - token_endpoint_auth_method: 'client_secret_post' -``` - -Configuration of OAuth in Immich System Settings - -| Setting | Value | -| ---------------------------------- | ------------------------------------------------------------------- | -| Issuer URL | `https://example.immich.app/.well-known/openid-configuration` | -| Client ID | immich | -| Client Secret | 0v89FXkQOWO\***\*\*\*\*\***\*\*\***\*\*\*\*\***mprbvXD549HH6s1iw... | -| Token Endpoint Auth Method | client_secret_post | -| Scope | openid email profile immich_scope | -| ID Token Signed Response Algorithm | RS256 | -| Userinfo Signed Response Algorithm | RS256 | -| Storage Label Claim | uid | -| Storage Quota Claim | immich_quota | -| Default Storage Quota (GiB) | 0 (empty for unlimited quota) | -| Button Text | Sign in with Authelia (optional) | -| Auto Register | Enabled (optional) | -| Auto Launch | Enabled (optional) | -| Mobile Redirect URI Override | Disable | -| Mobile Redirect URI | | - -
- -
-Authentik Example - -### Authentik Example - -Here's an example of OAuth configured for Authentik: - -Configuration of Authorised redirect URIs (Authentik OAuth2/OpenID Provider) - - - -Configuration of OAuth in Immich System Settings - -| Setting | Value | -| ---------------------------- | ---------------------------------------------------------------------------------- | -| Issuer URL | `https://example.immich.app/application/o/immich/.well-known/openid-configuration` | -| Client ID | AFCj2rM1f4rps**\*\*\*\***\***\*\*\*\***lCLEum6hH9... | -| Client Secret | 0v89FXkQOWO\***\*\*\*\*\***\*\*\***\*\*\*\*\***mprbvXD549HH6s1iw... | -| Scope | openid email profile | -| Signing Algorithm | RS256 | -| Storage Label Claim | preferred_username | -| Storage Quota Claim | immich_quota | -| Default Storage Quota (GiB) | 0 (empty for unlimited quota) | -| Button Text | Sign in with Authentik (optional) | -| Auto Register | Enabled (optional) | -| Auto Launch | Enabled (optional) | -| Mobile Redirect URI Override | Disable | -| Mobile Redirect URI | | - -
- -
-Google Example - -### Google Example - -Here's an example of OAuth configured for Google: - -Configuration of Authorised redirect URIs (Google Console) - - - -Configuration of OAuth in Immich System Settings - -| Setting | Value | -| ---------------------------- | ---------------------------------------------------------------------------- | -| Issuer URL | `https://accounts.google.com` | -| Client ID | 7\***\*\*\*\*\*\*\***\*\*\***\*\*\*\*\*\*\***vuls.apps.googleusercontent.com | -| Client Secret | G\***\*\*\*\*\*\*\***\*\*\***\*\*\*\*\*\*\***OO | -| Scope | openid email profile | -| Signing Algorithm | RS256 | -| Storage Label Claim | preferred_username | -| Storage Quota Claim | immich_quota | -| Default Storage Quota (GiB) | 0 (empty for unlimited quota) | -| Button Text | Sign in with Google (optional) | -| Auto Register | Enabled (optional) | -| Auto Launch | Enabled | -| Mobile Redirect URI Override | Enabled (required) | -| Mobile Redirect URI | `https://example.immich.app/api/oauth/mobile-redirect` | - -
- -[oidc]: https://openid.net/connect/ diff --git a/docs/docs/administration/postgres-standalone.md b/docs/docs/administration/postgres-standalone.md deleted file mode 100644 index 84681fdfa6..0000000000 --- a/docs/docs/administration/postgres-standalone.md +++ /dev/null @@ -1,158 +0,0 @@ -# Pre-existing Postgres - -While not officially recommended, it is possible to run Immich using a pre-existing Postgres server. To use this setup, you should have a baseline level of familiarity with Postgres and the Linux command line. If you do not have these, we recommend using the default setup with a dedicated Postgres container. - -By default, Immich expects superuser permission on the Postgres database and requires certain extensions to be installed. This guide outlines the steps required to prepare a pre-existing Postgres server to be used by Immich. - -:::tip -Running with a pre-existing Postgres server can unlock powerful administrative features, including logical replication and streaming write-ahead log backups using programs like pgBackRest or Barman. -::: - -## Prerequisites - -You must install pgvector as it is a prerequisite for VectorChord. -The easiest way to do this on Debian/Ubuntu is by adding the [PostgreSQL Apt repository][pg-apt] and then -running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`). - -You must install VectorChord into your instance of Postgres using their [instructions][vchord-install]. After installation, add `shared_preload_libraries = 'vchord.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vchord.so'`. - -:::note Supported versions -Immich is known to work with Postgres versions `>= 14, < 19`. - -VectorChord is known to work with pgvector versions `>= 0.7, < 0.9`. - -The Immich server will check the VectorChord version on startup to ensure compatibility, and refuse to start if a compatible version is not found. -The current accepted range for VectorChord is `>= 0.3, < 2.0`. -::: - -## Specifying the connection URL - -You can connect to your pre-existing Postgres server by setting the `DB_URL` environment variable in the `.env` file. - -``` -DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename' - -# require a SSL connection to Postgres -# DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename?sslmode=require' - -# require a SSL connection, but don't enforce checking the certificate name -# DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename?sslmode=require&sslmode=no-verify' -``` - -## With superuser permission - -Typically Immich expects superuser permission in the database, which you can grant by running `ALTER USER WITH SUPERUSER;` at the `psql` console. If you prefer not to grant superuser permissions, follow the instructions in the next section. - -## Without superuser permission - -:::caution -This method is recommended for **advanced users only** and often requires manual intervention when updating Immich. -::: - -:::danger -Currently, automated backups require superuser permission due to the usage of `pg_dumpall`. -::: - -Immich can run without superuser permissions by following the below instructions at the `psql` prompt to prepare the database. - -```sql title="Set up Postgres for Immich" -CREATE DATABASE ; -\c -BEGIN; -ALTER DATABASE OWNER TO ; -CREATE EXTENSION vchord CASCADE; -CREATE EXTENSION earthdistance CASCADE; -COMMIT; -``` - -### Updating VectorChord - -When installing a new version of VectorChord, you will need to manually update the extension and reindex by connecting to the Immich database and running: - -``` -ALTER EXTENSION vchord UPDATE; -REINDEX INDEX face_index; -REINDEX INDEX clip_index; -``` - -## Migrating to VectorChord - -VectorChord is the successor extension to pgvecto.rs, allowing for higher performance, lower memory usage and higher quality results for smart search and facial recognition. - -### Migrating from pgvecto.rs - -Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so. - -The easiest option is to have both extensions installed during the migration: - -
-Migration steps (automatic) -1. Ensure you still have pgvecto.rs installed -2. Install `pgvector` (`>= 0.7, < 0.9`). The easiest way to do this is on Debian/Ubuntu by adding the [PostgreSQL Apt repository][pg-apt] and then running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres version (e.g., `16`) -3. [Install VectorChord][vchord-install] -4. Add `shared_preload_libraries= 'vchord.so, vectors.so'` to your `postgresql.conf`, making sure to include _both_ `vchord.so` and `vectors.so`. You may include other libraries here as well if needed -5. Restart the Postgres database -6. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;` using psql or your choice of database client -7. Start Immich and wait for the logs `Reindexed face_index` and `Reindexed clip_index` to be output -8. If Immich does not have superuser permissions, run the SQL command `DROP EXTENSION vectors;` -9. Drop the old schema by running `DROP SCHEMA vectors;` -10. Remove the `vectors.so` entry from the `shared_preload_libraries` setting -11. Restart the Postgres database -12. Uninstall pgvecto.rs (e.g. `apt-get purge vectors-pg14` on Debian-based environments, replacing `pg14` as appropriate). `pgvector` must remain installed as it provides the data types used by `vchord` - -
- -If it is not possible to have both VectorChord and pgvecto.rs installed at the same time, you can perform the migration with more manual steps: - -
-Migration steps (manual) -1. While pgvecto.rs is still installed, run the following SQL command using psql or your choice of database client. Take note of the number outputted by this command as you will need it later - -```sql -SELECT atttypmod as dimsize - FROM pg_attribute f - JOIN pg_class c ON c.oid = f.attrelid - WHERE c.relkind = 'r'::char - AND f.attnum > 0 - AND c.relname = 'smart_search'::text - AND f.attname = 'embedding'::text; -``` - -2. Remove references to pgvecto.rs using the below SQL commands - -```sql -DROP INDEX IF EXISTS clip_index; -DROP INDEX IF EXISTS face_index; -ALTER TABLE smart_search ALTER COLUMN embedding SET DATA TYPE real[]; -ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE real[]; -``` - -3. [Install VectorChord][vchord-install] -4. Change the columns back to the appropriate vector types, replacing `` with the number from step 1 - -```sql -CREATE EXTENSION IF NOT EXISTS vchord CASCADE; -ALTER TABLE smart_search ALTER COLUMN embedding SET DATA TYPE vector(); -ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512); -``` - -5. Start Immich and let it create new indices using VectorChord - -
- -### Migrating from pgvector - -
-Migration steps -1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client -2. Follow the Prerequisites to install VectorChord -3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;` -4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set -5. Start Immich and let it create new indices using VectorChord - -
- -Note that VectorChord itself uses pgvector types, so you should not uninstall pgvector after following these steps. - -[vchord-install]: https://docs.vectorchord.ai/vectorchord/getting-started/installation.html -[pg-apt]: https://www.postgresql.org/download/linux/#generic diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md deleted file mode 100644 index b53356139f..0000000000 --- a/docs/docs/administration/reverse-proxy.md +++ /dev/null @@ -1,120 +0,0 @@ -# Reverse Proxy - -Users can deploy a custom reverse proxy that forwards requests to Immich. This way, the reverse proxy can handle TLS termination, load balancing, or other advanced features. All reverse proxies between Immich and the user must forward all headers and set the `Host`, `X-Real-IP`, `X-Forwarded-Proto` and `X-Forwarded-For` headers to their appropriate values. Additionally, your reverse proxy should allow for big enough uploads. By following these practices, you ensure that all custom reverse proxies are fully compatible with Immich. - -:::caution -Immich does not support being served on a sub-path such as `location /immich {`. It has to be served on the root path of a (sub)domain. -::: - -:::info -If your reverse proxy uses the [Let's Encrypt](https://letsencrypt.org/) [http-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge), you may want to verify that the Immich well-known endpoint (`/.well-known/immich`) gets correctly routed to Immich, otherwise it will likely be routed elsewhere and the mobile app may run into connection issues. -::: - -### Nginx example config - -Below is an example config for nginx. Make sure to set `public_url` to the front-facing URL of your instance, and `backend_url` to the path of the Immich server. - -```nginx -server { - server_name ; - - # allow large file uploads - client_max_body_size 50000M; - - # disable buffering uploads to prevent OOM on reverse proxy server and make uploads twice as fast (no pause) - proxy_request_buffering off; - - # increase body buffer to avoid limiting upload speed - client_body_buffer_size 1024k; - - # Set headers - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # enable websockets: http://nginx.org/en/docs/http/websocket.html - proxy_http_version 1.1; - proxy_redirect off; - - # set timeout - proxy_read_timeout 600s; - proxy_send_timeout 600s; - send_timeout 600s; - - location / { - proxy_pass http://:2283; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - # useful when using Let's Encrypt http-01 challenge - # location = /.well-known/immich { - # proxy_pass http://:2283; - # } -} -``` - -### Caddy example config - -As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config. - -``` -immich.example.org { - reverse_proxy http://:2283 -} -``` - -### Apache example config - -Below is an example config for Apache2 site configuration. - -```ApacheConf - - ServerName - ProxyRequests Off - # set timeout in seconds - ProxyPass / http://127.0.0.1:2283/ timeout=600 upgrade=websocket - ProxyPassReverse / http://127.0.0.1:2283/ - ProxyPreserveHost On - -``` - -### Traefik Proxy example config - -The example below is for Traefik version 3. - -The most important is to increase the `respondingTimeouts` of the entrypoint used by immich. In this example of entrypoint `websecure` for port `443`. Per default it's set to 60s which leeds to videos stop uploading after 1 minute (Error Code 499). With this config it will fail after 10 minutes which is in most cases enough. Increase it if needed. - -`traefik.yaml` - -```yaml -[...] -entryPoints: - websecure: - address: :443 - # this section needs to be added - transport: - respondingTimeouts: - readTimeout: 600s - idleTimeout: 600s -``` - -The second part is in the `docker-compose.yml` file where immich is in. Add the Traefik specific labels like in the example. - -`docker-compose.yml` - -```yaml -services: - immich-server: - [...] - labels: - traefik.enable: true - # increase readingTimeouts for the entrypoint used here - traefik.http.routers.immich.entrypoints: websecure - traefik.http.routers.immich.rule: Host(`immich.your-domain.com`) - traefik.http.services.immich.loadbalancer.server.port: 2283 -``` - -Keep in mind, that Traefik needs to communicate with the network where immich is in, usually done -by adding the Traefik network to the `immich-server`. diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md deleted file mode 100644 index cbd029296f..0000000000 --- a/docs/docs/administration/server-commands.md +++ /dev/null @@ -1,128 +0,0 @@ -# Server Commands - -The `immich-server` docker image comes preinstalled with an administrative CLI (`immich-admin`) that supports the following commands: - -| Command | Description | -| -------------------------- | ------------------------------------------------------------- | -| `help` | Display help | -| `reset-admin-password` | Reset the password for the admin user | -| `disable-password-login` | Disable password login | -| `enable-password-login` | Enable password login | -| `disable-maintenance-mode` | Disable maintenance mode | -| `enable-maintenance-mode` | Enable maintenance mode | -| `enable-oauth-login` | Enable OAuth login | -| `disable-oauth-login` | Disable OAuth login | -| `list-users` | List Immich users | -| `version` | Print Immich version | -| `change-media-location` | Change database file paths to align with a new media location | - -## How to run a command - -To run a command, [connect](/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich-admin `. - -## Examples - -Reset Admin Password - -``` -immich-admin reset-admin-password -Found Admin: -- ID=e65e6f88-2a30-4dbe-8dd9-1885f4889b53 -- OAuth ID= -- Email=admin@example.com -- Name=Immich Admin -? Please choose a new password (optional) immich-is-cool -The admin password has been updated. -``` - -Disable Password Login - -``` -immich-admin disable-password-login -Password login has been disabled. -``` - -Enable Password Login - -``` -immich-admin enable-password-login -Password login has been enabled. -``` - -Disable Maintenance Mode - -``` -immich-admin disable-maintenance-mode -Maintenance mode has been disabled. -``` - -Enable Maintenance Mode - -``` -immich-admin enable-maintenance-mode -Maintenance mode has been enabled. - -Log in using the following URL: -https://my.immich.app/maintenance?token= -``` - -Enable OAuth login - -``` -immich-admin enable-oauth-login -OAuth login has been enabled. -``` - -Disable OAuth login - -``` -immich-admin disable-oauth-login -OAuth login has been disabled. -``` - -List Users - -``` -immich-admin list-users -[ - { - id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53', - email: 'immich@example.com.com', - name: 'Immich Admin', - storageLabel: 'admin', - externalPath: null, - profileImagePath: 'upload/profile/e65e6f88-2a30-4dbe-8dd9-1885f4889b53/e65e6f88-2a30-4dbe-8dd9-1885f4889b53.jpg', - shouldChangePassword: true, - isAdmin: true, - createdAt: 2023-07-11T20:12:20.602Z, - deletedAt: null, - updatedAt: 2023-09-21T15:42:28.129Z, - oauthId: '', - } -] -``` - -Print Immich Version - -``` -immich-admin version -v1.129.0 -``` - -Change media location - -``` -immich-admin change-media-location -? Enter the previous value of IMMICH_MEDIA_LOCATION: /data -? Enter the new value of IMMICH_MEDIA_LOCATION: /my-data -... - Previous value: /data - Current value: /my-data - - Changing database paths from "/data/*" to "/my-data/*" - -? Do you want to proceed? [Y/n] y - -Database file paths updated successfully! 🎉 -... -``` diff --git a/docs/docs/administration/server-stats.md b/docs/docs/administration/server-stats.md deleted file mode 100644 index 245056e2ea..0000000000 --- a/docs/docs/administration/server-stats.md +++ /dev/null @@ -1,9 +0,0 @@ -# Server Stats - -Server statistics to show the total number of videos, photos, usage and quota per user. - -:::info External library -External libraries are not included in the storage quota due to custom mount points. -::: - - diff --git a/docs/docs/administration/storage-template.mdx b/docs/docs/administration/storage-template.mdx deleted file mode 100644 index 9eb9d2b8fe..0000000000 --- a/docs/docs/administration/storage-template.mdx +++ /dev/null @@ -1,5 +0,0 @@ -import StorageTemplate from '/docs/partials/_storage-template.md'; - -# Storage Template - - diff --git a/docs/docs/administration/system-integrity.md b/docs/docs/administration/system-integrity.md deleted file mode 100644 index 2b373134a9..0000000000 --- a/docs/docs/administration/system-integrity.md +++ /dev/null @@ -1,49 +0,0 @@ -# System Integrity - -## Folder checks - -:::info -The folders considered for these checks include: `upload/`, `library/`, `thumbs/`, `encoded-video/`, `profile/`, `backups/` -::: - -When Immich starts, it performs a series of checks in order to validate that it can read and write files to the volume mounts used by the storage system. If it cannot perform all the required operations, it will fail to start. The checks include: - -- Creating an initial hidden file (`.immich`) in each folder -- Reading a hidden file (`.immich`) in each folder -- Overwriting a hidden file (`.immich`) in each folder - -The checks are designed to catch the following situations: - -- Incorrect permissions (cannot read/write files) -- Missing volume mount (`.immich` files should exist, but are missing) - -### Common issues - -:::note -`.immich` files serve as markers and help keep track of volume mounts being used by Immich. Except for the situations listed below, they should never be manually created or deleted. -::: - -#### Missing `.immich` files - -``` -Verifying system mount folder checks (enabled=true) -... -ENOENT: no such file or directory, open 'upload/encoded-video/.immich' -``` - -The above error messages show that the server has previously (successfully) written `.immich` files to each folder, but now does not detect them. This could be because any of the following: - -- Permission error - unable to read the file, but it exists -- File does not exist - volume mount has changed and should be corrected -- File does not exist - user manually deleted it and should be manually re-created (`touch .immich`) -- File does not exist - user restored from a backup, but did not restore each folder (user should restore all folders or manually create `.immich` in any missing folders) - -### Ignoring the checks - -:::warning -The checks are designed to catch common problems that we have seen users have in the past, and often indicate there's something wrong that you should solve. If you know what you're doing and you want to disable them you can set the following environment variable: -::: - -``` -IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true -``` diff --git a/docs/docs/administration/system-settings.md b/docs/docs/administration/system-settings.md deleted file mode 100644 index fdfdad29ea..0000000000 --- a/docs/docs/administration/system-settings.md +++ /dev/null @@ -1,279 +0,0 @@ -# System Settings - -The admin user can manage settings for the Immich instance here. - -:::tip -You can always return to the default settings by clicking the `Reset to default` button. -::: - -## Authentication Settings - -Manage password, OAuth, and other authentication settings - -### OAuth Authentication - -Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/administration/oauth). - -### Password Authentication - -The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts. - -:::tip -You can always use the [Server CLI](/administration/server-commands) to re-enable password login. -::: - -## Image Settings (thumbnails and previews) - -- Thumbnails - Used in the main timeline. -- Previews - Used in the asset viewer. - -By default Immich creates 3 thumbnails for each asset, -Blurred (thumbhash) , Small - thumbnails (webp) , and Large - previews (jpeg/webp), using these settings you can change the quality for the thumbnails and previews files that are created. - -**Thumbnail format** -Allows you to choose the type of format you want for the Thumbnail images, Webp produces smaller files than jpeg, but is slower to encode. - -:::tip -You can read in detail about the advantages and disadvantages of using webp over jpeg on [Adobe's website](https://www.adobe.com/creativecloud/file-types/image/raster/webp-file.html) -::: - -**Thumbnail resolution** -Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness. - -**Preview format** -Allows you to choose the type of format you want for the Preview images, Webp produces smaller files than jpeg, but is slower to encode. - -**Preview resolution** -Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness. - -**Quality** -Image quality from 1-100. Higher is better for quality but produces larger files, this option affects the Preview and Thumbnail images. - -**Prefer wide gamut** -Use Display P3 for thumbnails. This better preserves the vibrance of images with wide colorspaces, but images may appear differently on old devices with an old browser version. sRGB images are kept as sRGB to avoid color shifts. - -**Prefer embedded preview** -Use embedded previews in RAW photos as the input to image processing when available. This can produce more accurate colors for some images, but the quality of the preview is camera-dependent and the image may have more compression artifacts. - -:::tip -The default resolution for Large thumbnails can be lowered from 1440p (default) to 1080p or 720p to save storage space. -::: - -## Job Settings - -Using these settings, you can determine the amount of work that will run concurrently for each task in microservices. Some tasks can be set to higher values on computers with powerful hardware and storage with good I/O capabilities. - -With higher concurrency, the host will work on more assets in parallel, -this advice improves throughput, not latency, for example, it will make Smart Search jobs process more quickly, but it won't make searching faster. - -It is important to remember that jobs like Smart Search, Face Detection, Facial Recognition, and Transcode Videos require a **lot** of processing power and therefore do not exaggerate the amount of jobs because you're probably thoroughly overloading the server. - -:::danger IMPORTANT -If you increase the concurrency from the defaults we set, especially for thumbnail generation, make sure you do not increase them past the amount of CPU cores you have available. -Doing so can impact API responsiveness with no gain in thumbnail generation speed. -::: - -:::info Facial Recognition Concurrency -The Facial Recognition Concurrency value cannot be changed because -[DBSCAN](https://www.youtube.com/watch?v=RDZUdRSDOok) is traditionally sequential, but there are parallel implementations of it out there. Our implementation isn't parallel. -::: - -## External Library - -### Library watching (EXPERIMENTAL) - -External libraries can automatically import changed files without a full rescan. It will import the file whenever the operating system reports a file change. If your photos are mounted over the network, this does not work. - -### Periodic Scanning - -You can define a custom interval for the trigger external library rescan under Administration -> Settings -> Library. -You can set the scanning interval using the preset or cron format. For more information please refer to e.g. [Crontab Guru](https://crontab.guru/). - -## Logging - -The default Immich log level is `Log` (commonly known as `Info`). The Immich administrator can choose a higher or lower log level according to personal preference or as requested by the Immich support team. - -## Machine Learning Settings - -Through this setting, you can manage all the settings related to machine learning in Immich, from the setting of remote machine learning to the model and its parameters -You can choose to disable a certain type of machine learning, for example smart search or facial recognition. - -### URL - -The built in (`http://immich-machine-learning:3003`) machine learning server will be configured by default, but you can change this or add additional servers. - -Hosting the `immich-machine-learning` container on a machine with a more powerful GPU can be helpful to for processing a large number of photos (such as during batch import) or for faster search. - -If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online. - -### Smart Search - -The [smart search](/features/searching) settings allow you to change the [CLIP model](https://openai.com/research/clip). Larger models will typically provide [more accurate search results](https://github.com/immich-app/immich/discussions/11862) but consume more processing power and RAM. When [changing the CLIP model](/FAQ#can-i-use-a-custom-clip-model) it is mandatory to re-run the Smart Search job on all images to fully apply the change. - -:::info Internet connection -Changing models requires a connection to the Internet to download the model. -After downloading, there is no need for Immich to connect to the network -Unless version checking has been enabled in the settings. -::: - -### Duplicate Detection - -Use CLIP embeddings to find likely duplicates. The maximum detection distance can be configured in order to improve / reduce the level of accuracy. - -- **Maximum detection distance -** Maximum distance between two images to consider them duplicates, ranging from 0.001-0.1. Higher values will detect more duplicates, but may result in false positives. - -### Facial Recognition - -Under these settings, you can change the facial recognition settings -Editable settings: - -- **Facial Recognition Model** -- **Min Detection Score** -- **Max Recognition Distance** -- **Min Recognized Faces** - -You can learn more about these options on the [Facial Recognition page](/features/facial-recognition#how-face-detection-works) - -:::info -When changing the values in Min Detection Score, Max Recognition Distance, and Min Recognized Faces. -You will have to restart **only** the job FACIAL RECOGNITION - ALL. - -If you replace the Facial Recognition Model, you will have to run the job FACE DETECTION - ALL. -::: - -:::tip identical twins -If you have twins, you might want to lower the Max Recognition Distance value, decreasing this a **bit** can make it distinguish between them. -::: - -## Map & GPS Settings - -### Map Settings - -In these settings, you can change the appearance of the map in night and day modes according to your personal preference and according to the supported options. -The map can be adjusted via [OpenMapTiles](https://openmaptiles.org/styles/) for example. - -### Reverse Geocoding Settings - -Immich supports [Reverse Geocoding](/features/reverse-geocoding) using data from the [GeoNames](https://www.geonames.org/) geographical database. - -## Notification Settings - -SMTP server setup, for user creation notifications, new albums, etc. More information can be found [here](/administration/email-notification) - -## Notification Templates - -Override the default notifications text with notification templates. More information can be found [here](/administration/email-notification) - -## Server Settings - -### External Domain - -Overrides the domain name in shared links and email notifications. The URL should not include a trailing slash. - -### Welcome Message - -The administrator can set a custom message on the login screen (the message will be displayed to all users). - -## Storage Template - -Immich supports a custom [Storage Template](/administration/storage-template). Learn more about this feature and its configuration [here](/administration/storage-template). - -## Theme Settings - -You can write custom CSS that will get loaded in the web application for all users. This enables administrators to change fonts, colors, and other styles. - -For example: - -```css title='Custom CSS' -p { - color: green; -} -``` - -## Trash Settings - -In the system administrator's option to set a trash for deleted files, these files will remain in the trash until the deletion date 30 days (default) or as defined by the system administrator. - -The trash can be disabled, however this is not recommended as future files that are deleted will be permanently deleted. - -:::tip Keyboard shortcut for permanently deletion -You can select assets and press Ctrl + Del from the timeline for quick permanent deletion without the trash option. -::: - -## User Settings - -### Delete delay - -The system administrator can choose to delete users through the administration panel, the system administrator can delete users immediately or alternatively delay the deletion for users (7 days by default) this action permanently delete a user's account and assets. The user deletion job runs at midnight to check for users that are ready for deletion. Changes to this setting will be evaluated at the next execution. - -## Version Check - -When this option is enabled the `immich-server` will periodically make requests to GitHub to check for new releases. - -## Video Transcoding Settings - -The system administrator can configure which video files will be converted to different formats. The settings can be changed in depth, to learn more about the terminology used here, refer to FFmpeg documentation for [H.264](https://trac.ffmpeg.org/wiki/Encode/H.264) codec, [HEVC](https://trac.ffmpeg.org/wiki/Encode/H.265) codec and [VP9](https://trac.ffmpeg.org/wiki/Encode/VP9) codec. - -Which streams of a video file will be transcoded is determined by the [Transcode Policy](#ffmpeg.transcode). Streams that are transcoded use the following settings (config file name in brackets). Streams that are not transcoded are untouched and preserve their original settings. - -### Accepted containers (`ffmpeg.acceptedContainers`) {#ffmpeg.acceptedContainers} - -If the video asset's container format is not in this list, it will be remuxed to MP4 even if no streams need to be transcoded. - -The default set of accepted container formats is `mov`, `ogg` and `webm`. - -### Preset (`ffmpeg.preset`) {#ffmpeg.preset} - -The amount of "compute effort" to put into transcoding. These use [the preset names from h264](https://trac.ffmpeg.org/wiki/Encode/H.264#Preset) and will be converted to appropriate values for encoders that configure effort in different ways. - -The default value is `ultrafast`. - -### Audio codec (`ffmpeg.targetAudioCodec`) {#ffmpeg.targetAudioCodec} - -Which audio codec to use when the audio stream is being transcoded. Can be one of `mp3`, `aac`, `libopus`. - -The default value is `aac`. - -### Video Codec (`ffmpeg.targetVideoCodec`) {#ffmpeg.targetVideoCodec} - -Which video codec to use when the video stream is being transcoded. Can be one of `h264`, `hevc`, `vp9` or `av1`. - -The default value is `h264`. - -### Target resolution (`ffmpeg.targetResolution`) {#ffmpeg.targetResolution} - -When transcoding a video stream, downscale the largest dimension to this value while preserving aspect ratio. Videos are never upscaled. - -The default value is `720`. - -### Transcode policy (`ffmpeg.transcode`) {#ffmpeg.transcode} - -The transcoding policy configures which streams of a video asset will be transcoded. The transcoding decision is made independently for video streams and audio streams. This means that if a video stream needs to be transcoded, but an audio stream does not, then the video stream will be transcoded while the audio stream will be copied. If the transcoding policy does not require any stream to be transcoded and does not require the video to be remuxed, then no separate video file will be created. - -The default policy is `required`. - -#### All videos (`all`) {#ffmpeg.transcode-all} - -Videos are always transcoded. This ensures consistency during video playback. - -#### Don't transcode any videos (`disabled`) {#ffmpeg.transcode-disabled} - -Videos are never transcoded. This saves space and resources on the server, but may prevent playback on devices that don't support the source format (especially web browsers) or result in high bandwidth usage when playing high-bitrate files. - -#### Only videos not in an accepted format (`required`) {#ffmpeg.transcode-required} - -Video streams are transcoded when any of the following conditions are met: - -- The video is HDR. -- The video is not in the yuv420p pixel format. -- The video codec is not in `acceptedVideoCodecs`. - -Audio is transcoded if the audio codec is not in `acceptedAudioCodecs`. - -#### Videos higher than max bitrate or not in an accepted format (`bitrate`) {#ffmpeg.transcode-bitrate} - -In addition to the conditions in `required`, video streams are also transcoded if their bitrate is over `maxBitrate`. - -#### Videos higher than target resolution or not in an accepted format (`optimal`) {#ffmpeg.transcode-optimal} - -In addition to the conditions in `required`, video streams are also transcoded if the horizontal **and** vertical dimensions are higher than [`targetResolution`](#ffmpeg.targetResolution). diff --git a/docs/docs/administration/user-management.mdx b/docs/docs/administration/user-management.mdx deleted file mode 100644 index 6d2b2f9062..0000000000 --- a/docs/docs/administration/user-management.mdx +++ /dev/null @@ -1,79 +0,0 @@ -import RegisterAdminUser from '/docs/partials/_register-admin.md'; -import UserCreate from '/docs/partials/_user-create.md'; - -# User Management - -Immich supports multiple users, each with their own library. - -## Register the Admin User - - - -## Create a New User - - - -## Send new user email notification - -:::note -This option is only available if an SMTP server has been configured in the administrator settings. -::: - -Admin can send a welcome email if the Email option is set, you can learn here how to set up the SMTP server in Immich. - - - -## Set Storage Quota For User - -Admin can specify the storage quota for the user as the instance's admin; once the limit is reached, the user won't be able to upload to the instance anymore. - -In order to select a storage quota, click on the edit user icon and enter the storage quota in GiB. You can choose an unlimited quota by leaving it empty (default). - -:::tip -The system administrator can see the usage quota percentage of all users in Server Stats page. -::: - -:::info -External libraries don't take up space from the storage quota. -::: - - - -## Set Storage Label For User - -The admin can add a custom label for each user, so instead of `upload/{userId}/your-template` it will be `upload/{custom_user_label}/your-template`. -To apply a storage template, go to the `Administration > Users`, then click on the context menu button next to the user. -:::note -To apply the Storage Label to previously uploaded assets, run the Storage Migration Job. -::: - - - -## Password Reset - - - -To reset a user's password, go to `Administration > Users`, then click on the context menu button next to the user, then click "Reset Password". The user's password will be reset to a random password and they have to change it next time they sign in. - -## Delete a User - -If you need to remove a user from Immich, go to `Administration > Users`, then click on the context menu button next to the user. The user account will immediately become disabled and their library and all associated data will be removed after 7 days by default. - -### Delete Delay - -You can customize the time of the deletion of the users from `Administration -> Settings -> User Settings`. -:::info user deletion job -The user deletion job runs at midnight to check for users that are ready for deletion. Changes to this setting will be evaluated at the next execution. -::: - -### Immediately Remove User - -You can choose to delete a user immediately by checking the box -`Queue user and assets for immediate deletion` in the deletion process, this will immediately remove the user and all assets. -This cannot be undone and the files cannot be recovered. - - diff --git a/docs/docs/developer/_category_.json b/docs/docs/developer/_category_.json deleted file mode 100644 index 502009dc7c..0000000000 --- a/docs/docs/developer/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Developer", - "position": 5 -} diff --git a/docs/docs/developer/architecture.mdx b/docs/docs/developer/architecture.mdx deleted file mode 100644 index 42d9c1b974..0000000000 --- a/docs/docs/developer/architecture.mdx +++ /dev/null @@ -1,117 +0,0 @@ ---- -sidebar_position: 1 ---- - -import AppArchitecture from './img/app-architecture.webp'; -import MobileArchitecture from './img/immich_mobile_architecture.svg'; - -# Architecture - -Immich uses a traditional client-server design, with a dedicated database for data persistence. The frontend clients communicate with backend services over HTTP using REST APIs. Below is a high level diagram of the architecture. - -## High Level Diagram - -Immich Architecture - -The diagram shows clients communicating with the server's API via REST. The server communicates with downstream systems (i.e. Redis, Postgres, Machine Learning, file system) through repository interfaces. Not shown in the diagram, is that the server is split into two separate containers `immich-server` and `immich-microservices`. The microservices container does not handle API requests or schedule cron jobs, but primarily handles incoming job requests from Redis. - -## Clients - -Immich has three main clients: - -1. Mobile app - Android, iOS -2. Web app - Responsive website -3. CLI - Command-line utility for bulk upload - -:::info -All three clients use [OpenAPI](./open-api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](./open-api.md). -::: - -### Mobile App - -The mobile app is written in [Dart](https://dart.dev/) using [Flutter](https://flutter.dev/). Below is an architecture overview: - - - -The diagrams shows the target architecture, the current state of the code-base is not always following the architecture yet. New code and contributions should follow this architecture. -Currently, it uses [Isar Database](https://isar.dev/) for a local database and [Riverpod](https://riverpod.dev/) for state management (providers). -Entities and Models are the two types of data classes used. While entities are stored in the on-device database, models are ephemeral and only kept in memory. -The Repositories should be the only place where other data classes are used internally (such as OpenAPI DTOs). However, their interfaces must not use foreign data classes! - -### Web Client - -The web app is a [TypeScript](https://www.typescriptlang.org/) project that uses [SvelteKit](https://kit.svelte.dev) and [Tailwindcss](https://tailwindcss.com/). - -### CLI - -The Immich CLI is an [npm](https://www.npmjs.com/) package that lets users control their Immich instance from the command line. It uses the API to perform various tasks, especially uploading assets. See the [CLI documentation](/features/command-line-interface.md) for more information. - -## Server - -The Immich backend is divided into several services, which are run as individual docker containers. - -1. `immich-server` - Handle and respond to REST API requests, execute background jobs (thumbnail generation, metadata extraction, transcoding, etc.) -1. `immich-machine-learning` - Execute machine learning models -1. `postgres` - Persistent data storage -1. `redis`- Queue management for background jobs - -### Immich Server - -The Immich Server is a [TypeScript](https://www.typescriptlang.org/) project written for [Node.js](https://nodejs.org/). It uses the [Nest.js](https://nestjs.com) framework, [Express](https://expressjs.com/) server, and the query builder [Kysely](https://kysely.dev/). The server codebase also loosely follows the [Hexagonal Architecture](). Specifically, we aim to separate technology specific implementations (`src/repositories`) from core business logic (`src/services`). - -### API Endpoints - -An incoming HTTP request is mapped to a controller (`src/controllers`). Controllers are collections of HTTP endpoints. Each controller usually implements the following CRUD operations for its respective resource type: - -- `POST` `/` - **Create** -- `GET` `/` - **Read** (all) -- `GET` `//:id` - **Read** (by id) -- `PUT` `//:id` - **Updated** (by id) -- `DELETE` `//:id` - **Delete** (by id) - -### Domain Transfer Objects (DTOs) - -The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client. - -### Background Jobs - -Immich uses a [worker](https://github.com/immich-app/immich/blob/main/server/src/utils/misc.ts#L266) to run background jobs. These jobs include: - -- Thumbnail Generation -- Metadata Extraction -- Video Transcoding -- Smart Search -- Facial Recognition -- Storage Template Migration -- Sidecar (see [XMP Sidecars](/features/xmp-sidecars.md)) -- Background jobs (file deletion, user deletion) - -:::info -This list closely matches what is available on the [Administration > Jobs](/administration/jobs-workers/#jobs) page, which provides some remote queue management capabilities. -::: - -### Machine Learning - -The machine learning service is written in [Python](https://www.python.org/) and uses [FastAPI](https://fastapi.tiangolo.com/) for HTTP communication. - -All machine learning related operations have been externalized to this service, `immich-machine-learning`. Python is a natural choice for AI and machine learning. It also has some pretty specific hardware requirements. Running it as a separate container makes it possible to run the container on a separate machine, or easily disable it entirely. - -Each request to the machine learning service contains the relevant metadata for the model task, model name, and so on. These settings are stored in Postgres along with other system configs. For each request, the microservices container fetches these settings in order to attach them to the request. - -Internally, the machine learning service downloads, loads and configures the specified model for a given request before processing the text or image payload with it. Models that have been loaded are cached and reused across requests. A thread pool is used to process each request in a different thread so as not to block the async event loop. - -All models are in ONNX format. This format has wide industry support, meaning that most other model formats can be exported to it and many hardware APIs support it. It's also quite fast. - -Machine learning models are also quite _large_, requiring _quite a bit_ of memory. We are always looking for ways to improve and optimize this aspect of this container specifically. - -### Postgres - -Immich persists data in Postgres, which includes information about access and authorization, users, albums, asset, sharing settings, etc. - -:::info -See [Database Migrations](./database-migrations.md) for more information about how to modify the database to create an index, modify a table, add a new column, etc. -::: - -### Redis - -Immich uses [Redis](https://redis.com/) via [BullMQ](https://docs.bullmq.io/) to manage job queues. Some jobs trigger subsequent jobs. For example, Smart Search and Facial Recognition relies on thumbnail generation and automatically run after one is generated. diff --git a/docs/docs/developer/database-migrations.md b/docs/docs/developer/database-migrations.md deleted file mode 100644 index a73e7e747c..0000000000 --- a/docs/docs/developer/database-migrations.md +++ /dev/null @@ -1,24 +0,0 @@ -# Database Migrations - -After making any changes in the `server/src/schema`, a database migration need to run in order to register the changes in the database. Follow the steps below to create a new migration. - -1. Run the command - -```bash -pnpm run migrations:generate -``` - -2. Check if the migration file makes sense. -3. Move the migration file to folder `./server/src/schema/migrations` in your code editor. - -The server will automatically detect `*.ts` file changes and restart. Part of the server start-up process includes running any new migrations, so it will be applied immediately. - -## Reverting a Migration - -If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run: - -```bash -pnpm run migrations:revert -``` - -This command rolls back the latest migration and brings the database schema back to its previous state. diff --git a/docs/docs/developer/devcontainers.md b/docs/docs/developer/devcontainers.md deleted file mode 100644 index f50ec62d8a..0000000000 --- a/docs/docs/developer/devcontainers.md +++ /dev/null @@ -1,481 +0,0 @@ ---- -title: Devcontainers -sidebar_position: 3 ---- - -# Development with Dev Containers - -Dev Containers provide a consistent, reproducible development environment using Docker containers. With a single click, you can get started with an Immich development environment on Mac, Linux, Windows, or in the cloud using GitHub Codespaces. - -Get started fast! - -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/immich-app/immich/) - -[Learn more about Dev Containers](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers) - -## Prerequisites - -Before getting started, ensure you have: - -- **Docker Desktop** (latest version) - - [Mac](https://docs.docker.com/desktop/install/mac-install/) - - [Windows](https://docs.docker.com/desktop/install/windows-install/) (with WSL2 backend recommended) - - [Linux](https://docs.docker.com/desktop/install/linux-install/) -- **Visual Studio Code** with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) -- **Git** for cloning the repository -- At least **8GB of RAM** (16GB recommended) -- **20GB of free disk space** - -:::tip Alternative Development Environments -While this guide focuses on VS Code, you have many options for Dev Container development: - -**Local Editors:** - -- [IntelliJ IDEA](https://www.jetbrains.com/help/idea/connect-to-devcontainer.html) - Full JetBrains IDE support -- [neovim](https://github.com/jamestthompson3/nvim-remote-containers) - Lightweight terminal-based editor -- [Emacs](https://github.com/emacs-lsp/lsp-docker) - Extensible text editor -- [DevContainer CLI](https://github.com/devcontainers/cli) - Command-line interface - -**Cloud-Based Solutions:** - -- [GitHub Codespaces](https://github.com/features/codespaces) - Fully integrated with GitHub, excellent devcontainer.json support -- [GitPod](https://www.gitpod.io) - SaaS platform with recent Dev Container support (historically used gitpod.yml) - -**Self-Hostable Options:** - -- [Coder](https://coder.com) - Enterprise-focused, requires Terraform knowledge, self-managed -- [DevPod](https://devpod.sh) - Client-only tool with excellent devcontainer.json support, works with any provider (local, cloud, or on-premise) - ::: - -## Dev Container Services - -The Dev Container environment consists of the following services: - -| Service | Container Name | Description | Ports | -| ---------------- | ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------- | -| Server & Web | `immich-server` | Runs both API server and web frontend in development mode | 2283 (API)
3000 (Web)
9230 (Workers Debug)
9231 (API Debug) | -| Database | `database` | PostgreSQL database | 5432 | -| Cache | `redis` | Valkey cache server | 6379 | -| Machine Learning | `immich-machine-learning` | Immich ML model inference server | 3003 | - -## Getting Started - -### Step 1: Clone the Repository - -```bash -git clone https://github.com/immich-app/immich.git -cd immich -``` - -### Step 2: Configure Environment Variables - -The immich dev containers read environment variables from your shell environment, not from `.env` files. This allows them to work in cloud environments without pre-configuration. - -:::important Configuration -When running locally, and if you want to create (or use an existing) DB and/or photo storage folder, you must set the `UPLOAD_LOCATION` variable in your shell environment before launching the Dev Container. This determines where uploaded files are stored and also where the DB stores it data. - -```bash -# Set temporarily for current session -export UPLOAD_LOCATION=/opt/dev_upload_folder - -# Or add to your shell profile for persistence -# (~/.bashrc, ~/.zshrc, ~/.bash_profile, etc.) -echo 'export UPLOAD_LOCATION=/opt/dev_upload_folder' >> ~/.bashrc -source ~/.bashrc -``` - -::: - -### Step 3: Launch the Dev Container - -:::tip -Immich development makes extensive use of specialized [base images](https://github.com/immich-app/base-images) for its docker-compose based development. For this reason, you won't be able to use VSCode's **_Clone Repository in a Container Volume_** command. -::: - -#### Using VS Code UI: - -1. Open the cloned repository in VS Code -2. Press `F1` or `Ctrl/Cmd+Shift+P` to open the command palette -3. Type and select "Dev Containers: Rebuild and Reopen in Container" -4. Select "Immich - Backend, Frontend and ML" from the list -5. Wait for the container to build and start (this may take several minutes on first run) - -#### Using VS Code Quick Actions: - -1. Open the repository in VS Code -2. You should see a popup asking if you want to reopen in a container -3. Click "Reopen in Container" - -#### Using Command Line: - -```bash -# Using the DevContainer CLI -devcontainer up --workspace-folder . -``` - -## Environment Variable Details - -### How Dev Containers Handle Environment Variables - -Unlike the Immich developer setup based on Docker Compose which uses `.env` files, Immich Dev Containers read environment variables from your shell environment. This is configured in `.devcontainer/devcontainer.json`: - -```json -"remoteEnv": { - "UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}", - "DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}", - "DB_USERNAME": "${localEnv:DB_USERNAME:postgres}", - "DB_DATABASE_NAME": "${localEnv:DB_DATABASE_NAME:immich}" -} -``` - -The `${localEnv:VARIABLE:default}` syntax reads from your shell environment with optional defaults. - -### Upload Location Path Resolution - -The `UPLOAD_LOCATION` environment variable controls where files are stored: - -**Default:** `./Library` (relative to the `docker` directory) -**Resolved to:** `/docker/Library` - -**Bind Mounts Created:** - -```yaml -# From .devcontainer/server/container-compose-overrides.yml -- ${UPLOAD_LOCATION-./Library}/photos:/workspaces/immich/server/upload -- ${UPLOAD_LOCATION-./Library}/postgres:/var/lib/postgresql/data -``` - -### Database Configuration - -These variables have sensible defaults (for development) but can be customized: - -| Variable | Default | Description | -| ------------------ | ---------- | ------------------- | -| `DB_PASSWORD` | `postgres` | PostgreSQL password | -| `DB_USERNAME` | `postgres` | PostgreSQL username | -| `DB_DATABASE_NAME` | `immich` | Database name | - -### Setting Environment Variables - -Add these to your shell profile (`~/.bashrc`, `~/.zshrc`, `~/.bash_profile`, etc.): - -```bash -# Required -export UPLOAD_LOCATION=./Library # or absolute path - -# Optional (only if using non-default values) -export DB_PASSWORD=your_password -export DB_USERNAME=your_username -export DB_DATABASE_NAME=your_database -``` - -Remember to reload your shell configuration: - -```bash -source ~/.bashrc # or ~/.zshrc, etc. -``` - -## Git Configuration - -### SSH Keys and Authentication - -To use your SSH keys for GitHub access inside the Dev Container: - -1. **Start SSH Agent** on your host machine: - - ```bash - eval "$(ssh-agent -s)" - ssh-add ~/.ssh/id_rsa # or your key path - ``` - -2. **VS Code automatically forwards your SSH agent** to the container - -For detailed instructions, see the [VS Code guide on sharing Git credentials](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials). - -### Commit Signing - -To use your SSH key for commit signing, see the [GitHub guide on SSH commit signing](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key#telling-git-about-your-ssh-key). - -## Development Workflow - -### Automatic Setup - -When the Dev Container starts, it automatically: - -1. **Runs post-create script** (`container-server-post-create.sh`): - - Adjusts file permissions for the `node` user - - Installs dependencies: `pnpm install` in all packages - - Builds TypeScript SDK: `pnpm run build` in `open-api/typescript-sdk` - -2. **Starts development servers** via VS Code tasks: - - `Immich API Server (Nest)` - API server with hot-reloading on port 2283 - - `Immich Web Server (Vite)` - Web frontend with hot-reloading on port 3000 - - Both servers watch for file changes and recompile automatically - -3. **Configures port forwarding**: - - Web UI: http://localhost:3000 (opens automatically) - - API: http://localhost:2283 - - Debug ports: 9230 (workers), 9231 (API) - -:::info -The Dev Container setup replaces the `make dev` command from the traditional setup. All services start automatically when you open the container. -::: - -### Accessing Services - -Once running, you can access: - -| Service | URL | Description | -| -------- | --------------------- | ---------------------------------------------------------------------------------------------- | -| Web UI | http://localhost:3000 | Main web interface | -| API | http://localhost:2283 | REST API endpoints (Not used directly, web UI will expose this over http://localhost:3000/api) | -| Database | localhost:5432 | PostgreSQL (username: `postgres`) (Not used directly) | - -### Connecting Mobile Apps - -To connect the mobile app to your Dev Container: - -1. Find your machine's IP address -2. In the mobile app, use: `http://YOUR_IP:3000/api` -3. Ensure your firewall allows connections on port 2283 - -### Making Code Changes - -- **Server code** (`/server`): Changes trigger automatic restart -- **Web code** (`/web`): Changes trigger hot module replacement -- **Database migrations**: Run `pnpm run sync:sql` in the server directory -- **API changes**: Regenerate TypeScript SDK with `make open-api` - -## Testing - -### Running Tests - -The Dev Container supports multiple ways to run tests: - -#### Using Make Commands (Recommended) - -```bash -# Run tests for specific components -make test-server # Server unit tests -make test-web # Web unit tests -make test-e2e # End-to-end tests -make test-cli # CLI tests - -# Run all tests -make test-all # Runs tests for all components - -# Medium tests (integration tests) -make test-medium-dev # End-to-end tests -``` - -#### Using PNPM Directly - -```bash -# Server tests -cd /workspaces/immich/server -pnpm test # Run all tests -pnpm run test:medium # Medium tests (integration tests) -pnpm run test:watch # Watch mode -pnpm run test:cov # Coverage report - -# Web tests -cd /workspaces/immich/web -pnpm test # Run all tests -pnpm run test:watch # Watch mode - -# E2E tests -cd /workspaces/immich/e2e -pnpm run test # Run API tests -pnpm run test:web # Run web UI tests -``` - -### Code Quality Commands - -```bash -# Linting -make lint-server # Lint server code -make lint-web # Lint web code -make lint-all # Lint all components - -# Formatting -make format-server # Format server code -make format-web # Format web code -make format-all # Format all code - -# Type checking -make check-server # Type check server -make check-web # Type check web -make check-all # Check all components - -# Complete hygiene check -make hygiene-all # Run lint, format, check, SQL sync, and audit -``` - -### Additional Make Commands - -```bash -# Build commands -make build-server # Build server -make build-web # Build web app -make build-all # Build everything - -# API generation -make open-api # Generate OpenAPI specs -make open-api-typescript # Generate TypeScript SDK -make open-api-dart # Generate Dart SDK - -# Database -make sql # Sync database schema - -# Dependencies -make install-server # Install server dependencies -make install-web # Install web dependencies -make install-all # Install all dependencies -``` - -### Debugging - -The Dev Container is pre-configured for debugging: - -1. **API Server Debugging**: - - Set breakpoints in VS Code - - Press `F5` or use "Run and Debug" panel - - Select "Attach to Server" configuration - - Debug port: 9231 - -2. **Worker Debugging**: - - Use "Attach to Workers" configuration - - Debug port: 9230 - -3. **Web Debugging**: - - Use browser DevTools - - VS Code debugger for Chrome/Edge extensions supported - -## Troubleshooting - -### Common Issues - -#### Permission Errors - -**Problem**: `EACCES` or permission denied errors -**Solution**: - -- The Dev Container runs as the `node` user (UID 1000) -- If your host UID differs, you may see permission issues -- Try rebuilding the container: "Dev Containers: Rebuild Container" - -#### Container Won't Start - -**Problem**: Dev Container fails to start or build -**Solution**: - -1. Check Docker is running: `docker ps` -2. Clean Docker resources: `docker system prune -a` -3. Check available disk space -4. Review Docker Desktop resource limits - -#### Port Already in Use - -**Problem**: "Port 3000/2283 is already in use" -**Solution**: - -1. Check for conflicting services: `lsof -i :3000` (macOS/Linux) -2. Stop conflicting services or change port mappings -3. Restart Docker Desktop - -#### Upload Location Not Set - -**Problem**: Errors about missing UPLOAD_LOCATION -**Solution**: - -1. Set the environment variable: `export UPLOAD_LOCATION=./Library` -2. Add to your shell profile for persistence -3. Restart your terminal and VS Code - -#### Database Connection Failed - -**Problem**: Cannot connect to PostgreSQL -**Solution**: - -1. Ensure all containers are running: `docker ps` -2. Check logs: "Dev Containers: Show Container Log" -3. Verify database credentials match environment variables - -### Getting Help - -If you encounter issues: - -1. Check container logs: View → Output → Select "Dev Containers" -2. Rebuild without cache: "Dev Containers: Rebuild Container Without Cache" -3. Review [common Docker issues](https://docs.docker.com/desktop/troubleshoot/) -4. Ask in [Discord](https://discord.immich.app) `#help-desk-support` channel - -## Mobile Development - -While the Dev Container focuses on server and web development, you can connect mobile apps for testing: - -### Connecting iOS/Android Apps - -1. **Ensure API is accessible**: - - ```bash - # Find your machine's IP - # macOS - ipconfig getifaddr en0 - # Linux - hostname -I - # Windows (in WSL2) - ip addr show eth0 - ``` - -2. **Configure mobile app**: - - Server URL: `http://YOUR_IP:2283/api` - - Ensure firewall allows port 2283 - -3. **For full mobile development**, see the [mobile development guide](/developer/setup) which covers: - - Flutter setup - - Running on simulators/devices - - Mobile-specific debugging - -## Advanced Configuration - -### Custom VS Code Extensions - -Add extensions to `.devcontainer/devcontainer.json`: - -```json -"customizations": { - "vscode": { - "extensions": [ - "your.extension-id" - ] - } -} -``` - -### Additional Services - -To add services (e.g., Redis Commander), modify: - -1. `/docker/docker-compose.dev.yml` - Add service definition -2. `/.devcontainer/server/container-compose-overrides.yml` - Add overrides if needed - -### Resource Limits - -Adjust Docker Desktop resources: - -- **macOS/Windows**: Docker Desktop → Settings → Resources -- **Linux**: Modify Docker daemon configuration - -Recommended minimums: - -- CPU: 4 cores -- Memory: 8GB -- Disk: 20GB - -## Next Steps - -- Read the [architecture overview](/developer/architecture) -- Learn about [database migrations](/developer/database-migrations) -- Explore [API documentation](https://api.immich.app/) -- Join `#immich` on [Discord](https://discord.immich.app) diff --git a/docs/docs/developer/directories.md b/docs/docs/developer/directories.md deleted file mode 100644 index 409353e2c4..0000000000 --- a/docs/docs/developer/directories.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Directories ---- - -# Repository Folder Structure - -Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](https://en.wikipedia.org/wiki/Monorepo) and includes the following folders: - -| Folder | Description | -| :------------------ | :------------------------------------------------------------------- | -| `.github/` | Github templates and action workflows | -| `.vscode/` | VSCode debug launch profiles | -| `cli/` | Source code for the work-in-progress CLI rewrite | -| `docker/` | Docker compose resources for dev, test, production | -| `design/` | Screenshots and logos for the README | -| `docs/` | Source code for the [https://immich.app](https://immich.app) website | -| `machine-learning/` | Source code for the `immich-machine-learning` docker image | -| `misc/release/` | Scripts for version bumps and draft releases | -| `mobile/` | Source code for the mobile app, both Android and iOS | -| `server/` | Source code for the `immich-server` docker image | -| `web/` | Source code for the `web` | diff --git a/docs/docs/developer/img/app-architecture.drawio.xml b/docs/docs/developer/img/app-architecture.drawio.xml deleted file mode 100644 index f30abb62e6..0000000000 --- a/docs/docs/developer/img/app-architecture.drawio.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/docs/developer/img/app-architecture.webp b/docs/docs/developer/img/app-architecture.webp deleted file mode 100644 index 365369c62d..0000000000 Binary files a/docs/docs/developer/img/app-architecture.webp and /dev/null differ diff --git a/docs/docs/developer/img/immich_mobile_architecture.drawio b/docs/docs/developer/img/immich_mobile_architecture.drawio deleted file mode 100644 index 548cda0938..0000000000 --- a/docs/docs/developer/img/immich_mobile_architecture.drawio +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/docs/developer/img/immich_mobile_architecture.svg b/docs/docs/developer/img/immich_mobile_architecture.svg deleted file mode 100644 index 71f28235bf..0000000000 --- a/docs/docs/developer/img/immich_mobile_architecture.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
Mobile App
Mobile App
Services
Services
Repositories
Repositories
Providers
Providers
Pages
Pages
Widgets
Widgets
User
User
platform
system
platform...
on-device
database
on-device...
server
server
OpenAPI
OpenAPI
UI part
UI part
non-UI part
non-UI part
Models
Models
Entities
Entities
\ No newline at end of file diff --git a/docs/docs/developer/open-api.md b/docs/docs/developer/open-api.md deleted file mode 100644 index f627b2c459..0000000000 --- a/docs/docs/developer/open-api.md +++ /dev/null @@ -1,13 +0,0 @@ -# OpenAPI - -Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/). - -## Generator - -OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK. - -```bash -make open-api -``` - -You can find the generated client SDK in the `open-api/typescript-sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK. diff --git a/docs/docs/developer/pr-checklist.md b/docs/docs/developer/pr-checklist.md deleted file mode 100644 index e68567bc8f..0000000000 --- a/docs/docs/developer/pr-checklist.md +++ /dev/null @@ -1,60 +0,0 @@ -# PR Checklist - -A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment. -:::warning -The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`make dev`, ....). Feel free to contribute! -::: -When contributing code through a pull request, please check the following: - -## Web Checks - -- [ ] `pnpm run lint` (linting via ESLint) -- [ ] `pnpm run format` (formatting via Prettier) -- [ ] `pnpm run check:svelte` (Type checking via SvelteKit) -- [ ] `pnpm run check:typescript` (check typescript) -- [ ] `pnpm test` (unit tests) - -:::tip AIO -Run all web checks with `pnpm run check:all` -::: - -## Documentation - -- [ ] `pnpm run format` (formatting via Prettier) -- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation. - -## Server Checks - -- [ ] `pnpm run lint` (linting via ESLint) -- [ ] `pnpm run format` (formatting via Prettier) -- [ ] `pnpm run check` (Type checking via `tsc`) -- [ ] `pnpm test` (unit tests) - -:::tip AIO -Run all server checks with `pnpm run check:all` -::: - -:::info Auto Fix -You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`. -::: - -## Mobile Checks - -The following commands must be executed from within the mobile app directory of the codebase. - -- [ ] `make build` (auto-generate files using build_runner) -- [ ] `make analyze` (static analysis via Dart Analyzer and DCM) -- [ ] `make format` (formatting via Dart Formatter) -- [ ] `make test` (unit tests) - -:::info Auto Fix -You can use `dart fix --apply` and `dcm fix lib` to potentially correct some issues automatically for `make analyze`. -::: - -## OpenAPI - -The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/developer/open-api.md) for more details. - -## Database Migrations - -A database migration needs to be generated whenever there are changes to `server/src/infra/src/entities`. See [Database Migration](/developer/database-migrations.md) for more details. diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md deleted file mode 100644 index 4bbf71dd89..0000000000 --- a/docs/docs/developer/setup.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Setup - -:::warning -Make sure to read the [`CONTRIBUTING.md`](https://github.com/immich-app/immich/blob/main/CONTRIBUTING.md) before you dive into the code. -::: - -:::note -If there's a feature you're planning to work on, just give us a heads up in [#contributing](https://discord.com/channels/979116623879368755/1071165397228855327) on [our Discord](https://discord.immich.app) so we can: - -1. Let you know if it's something we would accept into Immich -2. Provide any guidance on how something like that would ideally be implemented -3. Ensure nobody is already working on that issue/feature so we don't duplicate effort - -Thanks for being interested in contributing 😊 -::: - -## Environment - -### Services - -This environment includes the services below. Additional details are available in each service's README. - -- Server - [`/server`](https://github.com/immich-app/immich/tree/main/server) -- Web app - [`/web`](https://github.com/immich-app/immich/tree/main/web) -- Machine learning - [`/machine-learning`](https://github.com/immich-app/immich/tree/main/machine-learning) -- Redis -- PostgreSQL development database with exposed port `5432` so you can use any database client to access it - -All the services are packaged to run as with single Docker Compose command. - -### Server and web apps - -1. Clone the project repo. -2. Run `cp docker/example.env docker/.env`. -3. Edit `docker/.env` to provide values for the required variable `UPLOAD_LOCATION`. -4. Install dependencies - `pnpm i` -5. From the root directory, run: - -```bash title="Start development server" -make dev # required Makefile installed on the system. -``` - -5. Access the dev instance in your browser at http://localhost:3000, or connect via the mobile app. - -All the services will be started with hot-reloading enabled for a quick feedback loop. - -You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000/api` - -**Notes:** - -- The "web" development container runs with uid 1000. If that uid does not have read/write permissions on the mounted volumes, you may encounter errors - -#### Connect web to a remote backend - -If you only want to do web development connected to an existing, remote backend, follow these steps: - -1. Build the Immich SDK - `cd open-api/typescript-sdk && pnpm i && pnpm run build && cd -` -2. Enter the web directory - `cd web/` -3. Install web dependencies - `pnpm i` -4. Start the web development server - -```bash -IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev -``` - -If you're using PowerShell on Windows you may need to set the env var separately like so: - -```powershell -$env:IMMICH_SERVER_URL = "https://demo.immich.app/" -pnpm run dev -``` - -#### `@immich/ui` - -To see local changes to `@immich/ui` in Immich, do the following: - -1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui` -2. Build the `@immich/ui` project via `pnpm run build` -3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`) -4. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`) -5. Uncomment the import statement in `web/src/app.css` file `@import '/usr/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';` -6. Start up the stack via `make dev` -7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`) - -### Mobile app - -#### Setup - -1. [Install mise](https://mise.jdx.dev/installing-mise.html). -2. Change to the immich (root) directory and trust the mise config with `mise trust`. -3. Install tools with mise: `mise install`. -4. Change to the `mobile/` directory. -5. Run `flutter pub get` to install the dependencies. -6. Run `make translation` to generate the translation file. -7. Run `flutter run` to start the app. - -#### Translation - -To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run - -```bash -make translation -``` - -The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above. - -## IDE setup - -### Lint / format extensions - -Setting these in the IDE give a better developer experience, auto-formatting code on save, and providing instant feedback on lint issues. - -### Dart Code Metrics - -The mobile app uses DCM (Dart Code Metrics) for linting and metrics calculation. Please refer to the [Getting Started](https://dcm.dev/docs/) page for more information on setting up DCM - -Note: Activating the license is not required. - -### VSCode - -Install `Flutter`, `DCM`, `Prettier`, `ESLint` and `Svelte` extensions. These extensions are listed in the `extensions.json` file under `.vscode/` and should appear as workspace recommendations. - -Here are the settings we use, they should be active as workspace settings (`settings.json`): - -```json title="settings.json" -{ - "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.tabSize": 2 - }, - "[dart]": { - "editor.defaultFormatter": "Dart-Code.dart-code", - "editor.formatOnSave": true, - "editor.selectionHighlight": false, - "editor.suggest.snippetsPreventQuickSuggestions": false, - "editor.suggestSelection": "first", - "editor.tabCompletion": "onlySnippets", - "editor.wordBasedSuggestions": "off" - }, - "[javascript]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit", - "source.removeUnusedImports": "explicit" - }, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.tabSize": 2 - }, - "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.tabSize": 2 - }, - "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.tabSize": 2 - }, - "[svelte]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit", - "source.removeUnusedImports": "explicit" - }, - "editor.defaultFormatter": "svelte.svelte-vscode", - "editor.formatOnSave": true, - "editor.tabSize": 2 - }, - "[typescript]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit", - "source.removeUnusedImports": "explicit" - }, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.tabSize": 2 - }, - "cSpell.words": ["immich"], - "editor.formatOnSave": true, - "eslint.validate": ["javascript", "svelte"], - "explorer.fileNesting.enabled": true, - "explorer.fileNesting.patterns": { - "*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart", - "*.ts": "${capture}.spec.ts,${capture}.mock.ts" - }, - "svelte.enable-ts-plugin": true, - "typescript.preferences.importModuleSpecifier": "non-relative" -} -``` diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md deleted file mode 100644 index d7c9edcd31..0000000000 --- a/docs/docs/developer/testing.md +++ /dev/null @@ -1,38 +0,0 @@ -# Testing - -## Server - -### Unit tests - -Unit are run by calling `pnpm run test` from the `server/` directory. -You need to run `pnpm install` (in `server/`) before _once_. - -### End to end tests - -The e2e tests can be run by first starting up a test production environment via: - -```bash -make e2e -``` - -Before you can run the tests, you need to run the following commands _once_: - -- `pnpm install` (in `e2e/`) -- `pnpm run build` (in `cli/`) -- `make open-api` (in the project root `/`) - -Once the test environment is running, the e2e tests can be run via: - -```bash -cd e2e/ -pnpm test -``` - -The tests check various things including: - -- Authentication and authorization -- Query param, body, and url validation -- Response codes -- Thumbnail generation -- Metadata extraction -- Library scanning diff --git a/docs/docs/developer/translations.md b/docs/docs/developer/translations.md deleted file mode 100644 index 6c4403adec..0000000000 --- a/docs/docs/developer/translations.md +++ /dev/null @@ -1,21 +0,0 @@ -# Translations - -:::tip -You can request a new language [here](https://hosted.weblate.org/new-lang/immich/immich/). -::: - -## Weblate - -[Weblate](https://weblate.org/) is a "libre software web-based continuous localization system". Immich localization efforts are managed on their [hosted platform](https://hosted.weblate.org/projects/immich/immich/). - -## International message format - -Plurals, numbers, dates and other locale specific message formats can be handled by using the [ICU message format](https://unicode-org.github.io/icu/userguide/format_parse/messages/). Internally, this is handled by the [intl-messageformat](https://www.npmjs.com/package/intl-messageformat) library. Their [documentation](https://formatjs.io/docs/intl-messageformat/) includes common, editable examples via a "live editor" feature, which can be useful to test and debug message formats. - -## Progress - -Immich currently supports the following languages: - - -Translation status - diff --git a/docs/docs/developer/troubleshooting.md b/docs/docs/developer/troubleshooting.md deleted file mode 100644 index f7989af57d..0000000000 --- a/docs/docs/developer/troubleshooting.md +++ /dev/null @@ -1,15 +0,0 @@ -# Troubleshooting - -:::tip -A great option to get assistance with troubleshooting is to join our [Discord](https://discord.immich.app) server, where we have a dedicated channel for `#contributing`. -::: - -## Known Issues - -### Running on Windows - -Running Immich on Windows can be frustrating and there are lots of ways it can go wrong. Where possible we recommend using Docker on Linux. However, several people have had success running Immich on Windows using Docker via WSL2. - -### NTFS Mounted Volumes - -The docker-compose.dev.yml and docker-compose.prod.yml use volume mounts for the postgres database. On start-up, postgres will try to `chown` the data directory, but fail. See [this post](https://forums.docker.com/t/data-directory-var-lib-postgresql-data-pgdata-has-wrong-ownership/17963/24) for more information about this issue and possible solutions. diff --git a/docs/docs/features/_category_.json b/docs/docs/features/_category_.json deleted file mode 100644 index 4aaeb07417..0000000000 --- a/docs/docs/features/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Features", - "position": 3 -} diff --git a/docs/docs/features/casting.md b/docs/docs/features/casting.md deleted file mode 100644 index 2a6785dc6c..0000000000 --- a/docs/docs/features/casting.md +++ /dev/null @@ -1,19 +0,0 @@ -# Chromecast support - -Immich supports the Google's Cast protocol so that photos and videos can be cast to devices such as a Chromecast and a Nest Hub. This feature is considered experimental and has several important limitations listed below. Currently, this feature is only supported by the web client, support on Android and iOS is planned for the future. - -## Enable Google Cast Support - -Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retrieve them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in. - -You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast` - - - -## Limitations - -To use casting with Immich, there are a few prerequisites: - -1. Your instance must be accessed via an HTTPS connection in order for the casting menu to show. -2. Your instance must be publicly accessible via HTTPS and a DNS record for the server must be accessible via Google's DNS servers (`8.8.8.8` and `8.8.4.4`) -3. Videos must be in a format that is compatible with Google Cast. For more info, check out [Google's documentation](https://developers.google.com/cast/docs/media) diff --git a/docs/docs/features/command-line-interface.md b/docs/docs/features/command-line-interface.md deleted file mode 100644 index 03e96e5080..0000000000 --- a/docs/docs/features/command-line-interface.md +++ /dev/null @@ -1,195 +0,0 @@ -# The Immich CLI - -Immich has a command line interface (CLI) that allows you to perform certain actions from the command line. - -## Features - -- Upload photos and videos to Immich -- Check server version - -More features are planned for the future. - -:::tip Google Photos Takeout -If you are looking to import your Google Photos takeout, we recommend this community maintained tool [immich-go](https://github.com/simulot/immich-go) -::: - -## Requirements - -- Node.js 20 or above -- Npm - -If you can't install node/npm, there is also a Docker version available below. - -## Installation (NPM) - -```bash -npm i -g @immich/cli -``` - -NOTE: if you previously installed the legacy CLI, you will need to uninstall it first: - -```bash -npm uninstall -g immich -``` - -## Installation (Docker) - -If npm is not available on your system you can try the Docker version - -```bash -docker run -it -v "$(pwd)":/import:ro -e IMMICH_INSTANCE_URL=https://your-immich-instance/api -e IMMICH_API_KEY=your-api-key ghcr.io/immich-app/immich-cli:latest -``` - -Please modify the `IMMICH_INSTANCE_URL` and `IMMICH_API_KEY` environment variables as suitable. You can also use a Docker env file to store your sensitive API key. - -This `docker run` command will directly run the command `immich` inside the container. You can directly append the desired parameters (see under "usage") to the commandline like this: - -```bash -docker run -it -v "$(pwd)":/import:ro -e IMMICH_INSTANCE_URL=https://your-immich-instance/api -e IMMICH_API_KEY=your-api-key ghcr.io/immich-app/immich-cli:latest upload -a -c 5 --recursive directory/ -``` - -## Usage - -
-Usage - -``` -$ immich -Usage: immich [options] [command] - -Command line interface for Immich - -Options: - -V, --version output the version number - -d, --config-directory Configuration directory where auth.yml will be stored (default: "~/.config/immich/", env: - IMMICH_CONFIG_DIR) - -u, --url [url] Immich server URL (env: IMMICH_INSTANCE_URL) - -k, --key [key] Immich API key (env: IMMICH_API_KEY) - -h, --help display help for command - -Commands: - login|login-key Login using an API key - logout Remove stored credentials - server-info Display server information - upload [options] [paths...] Upload assets - help [command] display help for command -``` - -
- -## Commands - -The upload command supports the following options: - -
-Options - -``` -Usage: immich upload [paths...] [options] - -Upload assets - -Arguments: - paths One or more paths to assets to be uploaded - -Options: - -r, --recursive Recursive (default: false, env: IMMICH_RECURSIVE) - -i, --ignore Pattern to ignore (env: IMMICH_IGNORE_PATHS) - -h, --skip-hash Don't hash files before upload (default: false, env: IMMICH_SKIP_HASH) - -H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN) - -a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM) - -A, --album-name Add all assets to specified album (env: IMMICH_ALBUM_NAME) - -n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN) - -c, --concurrency Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY) - -j, --json-output Output detailed information in json format (default: false, env: IMMICH_JSON_OUTPUT) - --delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS) - --delete-duplicates Delete local assets that are duplicates (already exist on server) (env: IMMICH_DELETE_DUPLICATES) - --no-progress Hide progress bars (env: IMMICH_PROGRESS_BAR) - --watch Watch for changes and upload automatically (default: false, env: IMMICH_WATCH_CHANGES) - --help display help for command -``` - -
- -Note that the above options can read from environment variables as well. - -## Quick Start - -You begin by authenticating to your Immich server. For instance: - -```bash -# immich login [url] [key] -immich login http://192.168.1.216:2283/api HFEJ38DNSDUEG -``` - -This will store your credentials in a `auth.yml` file in the configuration directory which defaults to `~/.config/immich/`. The directory can be set with the `-d` option or the environment variable `IMMICH_CONFIG_DIR`. Please keep the file secure, either by performing the logout command after you are done, or deleting it manually. - -Once you are authenticated, you can upload assets to your Immich server. - -```bash -immich upload file1.jpg file2.jpg -``` - -By default, subfolders are not included. To upload a directory including subfolder, use the --recursive option: - -```bash -immich upload --recursive directory/ -``` - -If you are unsure what will happen, you can use the `--dry-run` option to see what would happen without actually performing any actions. - -```bash -immich upload --dry-run --recursive directory/ -``` - -By default, the upload command will hash the files before uploading them. This is to avoid uploading the same file multiple times. If you are sure that the files are unique, you can skip this step by passing the `--skip-hash` option. Note that Immich always performs its own deduplication through hashing, so this is merely a performance consideration. If you have good bandwidth it might be faster to skip hashing. - -```bash -immich upload --skip-hash --recursive directory/ -``` - -You can automatically create albums based on the folder name by passing the `--album` option. This will automatically create albums for each uploaded asset based on the name of the folder they are in. - -```bash -immich upload --album --recursive directory/ -``` - -You can also choose to upload all assets to a specific album with the `--album-name` option. - -```bash -immich upload --album-name "My summer holiday" --recursive directory/ -``` - -It is possible to skip assets matching a glob pattern by passing the `--ignore` option. See [the library documentation](docs/features/libraries.md) on how to use glob patterns. You can add several exclusion patterns if needed. - -```bash -immich upload --ignore **/Raw/** --recursive directory/ -``` - -```bash -immich upload --ignore **/Raw/** **/*.tif --recursive directory/ -``` - -By default, hidden files are skipped. If you want to include hidden files, use the `--include-hidden` option: - -```bash -immich upload --include-hidden --recursive directory/ -``` - -You can use the `--json-output` option to get a json printed which includes -three keys: `newFiles`, `duplicates` and `newAssets`. Due to some logging -output you will need to strip the first three lines of output to get the json. -For example to get a list of files that would be uploaded for further -processing: - -```bash -immich upload --dry-run --json-output . | tail -n +6 | jq .newFiles[] -``` - -### Obtain the API Key - -The API key can be obtained in the user setting panel on the web interface. You can also specify permissions for the key to limit its access. - -![Obtain Api Key](./img/obtain-api-key.webp) - -![Specify permissions for the key](./img/obtain-api-key-2.webp) diff --git a/docs/docs/features/editing.mdx b/docs/docs/features/editing.mdx deleted file mode 100644 index 5d51798e15..0000000000 --- a/docs/docs/features/editing.mdx +++ /dev/null @@ -1,19 +0,0 @@ -# Editing - -Immich supports non-destructive editing of photos. This means that any edits you make to an asset do not modify the original file, but instead create a new version of the asset with the edits applied. You can always revert back to the original asset if needed. - -## Supported Edits - -Currently, Immich supports the following types of edits: - -- Cropping -- Rotation -- Mirroring - - - -## Download - -When you download an edited asset, Immich provides the edited version of the asset by default. However, you can choose to download the original version if needed. - - diff --git a/docs/docs/features/facial-recognition.md b/docs/docs/features/facial-recognition.md deleted file mode 100644 index cb896ca19e..0000000000 --- a/docs/docs/features/facial-recognition.md +++ /dev/null @@ -1,94 +0,0 @@ -# Facial Recognition - -## Overview - -Immich recognizes faces in your photos and videos and groups them together into people. You can then assign names to these people and search for them. - -The list of people is shown in the Explore page. - - - -Upon clicking on a person, a list of assets that contain their face will be shown. - - - -The asset detail view will also show the faces that are recognized in the asset. - - - -## Actions - -Additional actions you can do include: - -- Changing the feature photo of the person -- Hiding the faces of a person from the Explore page and detail view -- Setting a person's date of birth, so that the age of the person can be shown at the time the photo was taken -- Merging two or more detected people into one person -- Favoriting a person to pin them to the top of the list - -It can be found from the app bar when you access the detail view of a person. - - - -## How Face Detection Works - -Face detection sends the generated preview image to the machine learning service for processing. The service checks if it has the relevant model downloaded and downloads it if not. The image is decoded, pre-processed and passed to the face detection model (with hardware acceleration if configured). The bounding boxes and scores outputted from this model are used to crop and preprocess the image once again to be passed to a facial recognition model (also accelerated if configured). The embeddings from the recognition model, together with the bounding boxes and scores from the face detection model, are then sent back to the server to be added to the database. The embeddings in particular are indexed so they can be searched quickly during facial recognition clustering. - -## How Facial Recognition Works - -The facial recognition algorithm we use is derived from [DBSCAN](https://www.youtube.com/watch?v=RDZUdRSDOok), a popular clustering algorithm. It essentially treats each detected face as a point in a graph and aims to group points that are close to each other. - -:::note -An important concept is whether something is a _core point_. A core point has a minimum number of points around it within a certain distance. A non-core point can only be assigned to a cluster if it can reach a core point; a non-core point can't be used to extend a cluster even if it's part of one. In Immich, the _Minimum Recognized Faces_ setting controls the threshold to be considered a core point. -::: - -For each face, it looks around it to find other faces within a certain distance. Faces within this distance are considered similar, so it then checks if any of these faces are associated with a person. - -If there is an existing person, it assigns the person of the most similar face to the face being processed. - -If there is none, then it has to determine something from the DBSCAN algorithm: whether the face is a _core point_. If there are a certain number of similar faces (by default 3, including the face being considered), then this face is a core point. A new person is created for this face and the face is assigned to it. When other faces are processed, if they're similar to this face, they'll see that it has an associated person and can be assigned to that person. - -However, if there aren't enough similar faces, no new person will be created. Instead, the face will wait for all the other faces to be processed to see if any matches that previously didn't have an associated person now do. If they do, then the face will be assigned to that person. If not, this face will be considered an outlier, such as a stranger in the background of an image. - -The algorithm has some subtle differences compared to DBSCAN: - -- DBSCAN doesn't have a concept of incremental clustering: it clusters all points at once. In contrast, facial recognition has to evolve as more assets are added without re-clustering everything each time. - - The algorithm described above works within a set of queued assets. Once these faces are processed and a new round of faces are detected, the behavior will not be the same as traditional DBSCAN since it preserves the clusters (people) generated from the previous round. - - Facial recognition tries to wait for face detection and thumbnail generation to complete before starting for this reason: the larger the set of faces in the queue, the better the results will be. - - Re-running facial recognition on all assets afterwards does behave like DBSCAN, however. -- DBSCAN is designed for range-based searches (i.e. points within a distance), but high-dimensional vector indices are generally optimized for getting the closest K results. The recognition algorithm doesn't try to get _all_ similar faces within a distance for performance reasons. Instead, it searches for a small number of matches for each face. The end result should be very similar if not identical, but with possibly different performance characteristics. - - Because of this, part of the recognition process is handled during a nightly job to ensure that unassigned faces with potential matches can be recognized. - -:::tip -If you didn't import your assets at once or if the server was able to process jobs faster than you could upload them, it's possible that the clustering was suboptimal. If you haven't put effort into the current results, it may be worth re-running facial recognition on all assets for the best starting point. If it's too late for that, you can also manually assign a selection of unassigned faces and queue _Missing_ for Facial Recognition to help it learn and assign more faces automatically. -::: - -## Configuration - -Navigating to Administration > Settings > Machine Learning Settings > Facial Recognition will show the options available. - -:::tip -It's better to only tweak the parameters here than to set them to something very different unless you're ready to test a variety of options. If you do need to set a parameter to a strict setting, relaxing other settings can be a good option to compensate, and vice versa. - -You can learn how the tune the result in this [Guide](/guides/better-facial-clusters) -::: - -### Facial recognition model - -There are a few different models available; the default is typically considered the best. On more constrained systems where the default is too intensive, you can choose a smaller model instead. - -### Minimum detection score - -This setting affects whether a result from the face detection model is filtered out as a false positive. It may seem tempting to set this low to detect more faces, but it can lead to false positives that are difficult to deal with and can harm facial recognition. It is strongly recommended not to go below 0.5 for this setting. Setting it to a very high number like 0.9 is also not recommended: the default is already biased toward precision, so a threshold that high leads to many undetected faces. - -After changing this setting, it will only apply to new face detection jobs. To apply the new setting to all assets, you need to re-run face detection for all assets. - -### Maximum recognition distance - -The distance threshold described in How Facial Recognition Works. The default works well for most people, but it may be worth lowering it if the library has twins or otherwise very similar looking people. A threshold that's too low just means needing to merge duplicate people after facial recognition, whereas a threshold too high can produce unsalvageable results. It is strongly recommended not to go below 0.3 or above 0.7. - -### Minimum recognized faces - -The core point threshold described in How Facial Recognition Works. This setting has a few implications. First, it takes effect immediately in that people with fewer faces than this are hidden from view. Secondly, it makes clustering more robust as it prevents loosely-related faces from being linked to each other by requiring a certain level of density. - -Increasing this setting is a good idea if you increase the recognition distance or reduce the minimum detection score. Setting it to 1 effectively disables the concept of core points, but can be an option if you prefer a more hands-on approach. diff --git a/docs/docs/features/folder-view.md b/docs/docs/features/folder-view.md deleted file mode 100644 index 3d8613a042..0000000000 --- a/docs/docs/features/folder-view.md +++ /dev/null @@ -1,17 +0,0 @@ -# Folder View - -Folder view provides an additional view besides the timeline that is similar to a file explorer. It allows you to navigate through the folders and files in the library. This feature is handy for a highly curated and customized external library or a nicely configured storage template. - -You can enable this feature under [`Account Settings > Features > Folders`](https://my.immich.app/user-settings?isOpen=feature+folders) - -## Enable folder view - - - -## Usage - -You can then navigate to the view from the sidebar to explore the folders and files in your library. - - - - diff --git a/docs/docs/features/hardware-transcoding.md b/docs/docs/features/hardware-transcoding.md deleted file mode 100644 index e68f6f6983..0000000000 --- a/docs/docs/features/hardware-transcoding.md +++ /dev/null @@ -1,142 +0,0 @@ -# Hardware Transcoding - -This feature allows you to use a GPU to accelerate transcoding and reduce CPU load. -Note that hardware transcoding produces significantly larger videos than software transcoding with similar settings, typically with lower quality. Using slow presets and preferring more efficient codecs can narrow this gap. -As this is a new feature, it is still experimental and may not work on all systems. - -:::info -You do not need to redo any transcoding jobs after enabling hardware acceleration. The acceleration device will be used for any jobs that run after enabling it. -::: - -## Supported APIs - -- NVENC (NVIDIA) -- Quick Sync (Intel) -- RKMPP (Rockchip) -- VAAPI (AMD / NVIDIA / Intel) - -## Limitations - -- The instructions and configurations here are specific to Docker Compose. Other container engines may require different configuration. -- Only Linux and Windows (through WSL2) servers are supported. -- WSL2 does not support Quick Sync. -- Raspberry Pi is currently not supported. -- Two-pass mode is only supported for NVENC. Other APIs will ignore this setting. -- By default, only encoding is currently hardware accelerated. This means the CPU is still used for software decoding and tone-mapping. - - You can benefit from end-to-end acceleration by enabling hardware decoding in the video transcoding settings. -- Hardware dependent - - Codec support varies, but H.264 and HEVC are usually supported. - - Notably, NVIDIA and AMD GPUs do not support VP9 encoding. - - Newer devices tend to have higher transcoding quality. - -## Prerequisites - -#### NVENC - -- You must have the official NVIDIA driver installed on the server. -- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed. - -#### QSV - -- For VP9 to work: - - You must have a 9th gen Intel CPU or newer - - If you have an 11th gen CPU or older, then you may need to follow [these][jellyfin-lp] instructions as Low-Power mode is required - - Additionally, if the server specifically has an 11th gen CPU and is running kernel 5.15 (shipped with Ubuntu 22.04 LTS), then you will need to upgrade this kernel (from [Jellyfin docs][jellyfin-kernel-bug]) - -#### RKMPP - -For RKMPP to work: - -- You must have a supported Rockchip ARM SoC. -- Only RK3588 supports hardware tonemapping, other SoCs use slower software tonemapping while still using hardware encoding. -- Tonemapping requires `/usr/lib/aarch64-linux-gnu/libmali.so.1` to be present on your host system. Install the [`libmali`][libmali-rockchip] release that corresponds to your Mali GPU (`libmali-valhall-g610-g13p0-gbm` on RK3588) and modify the [`hwaccel.transcoding.yml`][hw-file] file: - - under `rkmpp` uncomment the 3 lines required for OpenCL tonemapping by removing the `#` symbol at the beginning of each line - - `- /dev/mali0:/dev/mali0` - - `- /etc/OpenCL:/etc/OpenCL:ro` - - `- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro` - -## Setup - -#### Basic Setup - -1. If you do not already have it, download the latest [`hwaccel.transcoding.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`. -2. In the `docker-compose.yml` under `immich-server`, uncomment the `extends` section and change `cpu` to the appropriate backend. - - Note: For VAAPI on WSL2, be sure to use `vaapi-wsl` rather than `vaapi` - -3. Redeploy the `immich-server` container with these updated settings. -4. In the Admin page under `Video transcoding settings`, change the hardware acceleration setting to the appropriate option and save. - - Note: For Jasper Lake and Elkhart Lake CPUs, you will need to set the `Hardware Acceleration` -> `Constant quality mode` to `CQP` - -5. (Optional) Enable hardware decoding for optimal performance. - -
-immich.json - -If you use a [configuration file](/install/config-file.md), use the `accel` option to select the hardware (e.g. `qsv` for Intel or `nvenc` for Nvidia). Set `accelDecode` to `true` if you want hardware decoding. - -```json -{ - "ffmpeg": { - "accel": "qsv", - "accelDecode": true - } -} -``` - -
- -#### Single Compose File - -Some platforms, including Unraid and Portainer, do not support multiple Compose files as of writing. As an alternative, you can "inline" the relevant contents of the [`hwaccel.transcoding.yml`][hw-file] file into the `immich-server` service directly. - -For example, the `qsv` section in this file is: - -```yaml -devices: - - /dev/dri:/dev/dri -``` - -You can add this to the `immich-server` service instead of extending from `hwaccel.transcoding.yml`: - -```yaml -immich-server: - container_name: immich_server - image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} - # Note the lack of an `extends` section - devices: - - /dev/dri:/dev/dri - volumes: - ... -``` - -Once this is done, you can continue to step 3 of "Basic Setup". - -#### All-In-One - Unraid Setup - -##### QSV - -1. Unraid > Docker > (Stop) Immich container > Edit -2. Scroll down and select `Add another Path, Port, Variable, Label or Device` -3. In the drop-down menu, select `Device` and an entry with any name and the value `/dev/dri`. -4. Continue to step 4 of "Basic Setup". - -##### NVENC - -1. In the container app, add this environmental variable: Key=`NVIDIA_VISIBLE_DEVICES` Value=`all` -2. While still in the container app, change the container from Basic Mode to Advanced Mode and add the following parameter to the Extra Parameters field: `--runtime=nvidia` -3. Restart the container app. -4. Continue to step 4 of "Basic Setup". - -## Tips - -- You may want to choose a slower preset than for software transcoding to maintain quality and efficiency -- While you can use VAAPI with NVIDIA and Intel devices, prefer the more specific APIs since they're more optimized for their respective devices -- You can confirm the device is being recognized and used by checking its utilization (via `nvtop` for NVIDIA, `intel_gpu_top` for Intel, etc.) when transcoding. A lack of error logs when transcoding also indicates that it's being used. - -[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml -[nvct]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html -[jellyfin-lp]: https://jellyfin.org/docs/general/post-install/transcoding/hardware-acceleration/intel#low-power-encoding -[jellyfin-kernel-bug]: https://jellyfin.org/docs/general/post-install/transcoding/hardware-acceleration/intel#known-issues-and-limitations-on-linux -[libmali-rockchip]: https://github.com/tsukumijima/libmali-rockchip/releases diff --git a/docs/docs/features/img/advanced-search-filters.webp b/docs/docs/features/img/advanced-search-filters.webp deleted file mode 100644 index 2d56ccad15..0000000000 Binary files a/docs/docs/features/img/advanced-search-filters.webp and /dev/null differ diff --git a/docs/docs/features/img/android-backup-options.webp b/docs/docs/features/img/android-backup-options.webp deleted file mode 100644 index aa5364d812..0000000000 Binary files a/docs/docs/features/img/android-backup-options.webp and /dev/null differ diff --git a/docs/docs/features/img/background-app-refresh.webp b/docs/docs/features/img/background-app-refresh.webp deleted file mode 100644 index d98e922d60..0000000000 Binary files a/docs/docs/features/img/background-app-refresh.webp and /dev/null differ diff --git a/docs/docs/features/img/backup-album-selection.webp b/docs/docs/features/img/backup-album-selection.webp deleted file mode 100644 index 8c978c678e..0000000000 Binary files a/docs/docs/features/img/backup-album-selection.webp and /dev/null differ diff --git a/docs/docs/features/img/backup-album-sync.webp b/docs/docs/features/img/backup-album-sync.webp deleted file mode 100644 index 1a05ef0584..0000000000 Binary files a/docs/docs/features/img/backup-album-sync.webp and /dev/null differ diff --git a/docs/docs/features/img/backup-options.webp b/docs/docs/features/img/backup-options.webp deleted file mode 100644 index 7fdccd27fb..0000000000 Binary files a/docs/docs/features/img/backup-options.webp and /dev/null differ diff --git a/docs/docs/features/img/backup-settings-access.webp b/docs/docs/features/img/backup-settings-access.webp deleted file mode 100644 index 06243af799..0000000000 Binary files a/docs/docs/features/img/backup-settings-access.webp and /dev/null differ diff --git a/docs/docs/features/img/enable-backup-button.webp b/docs/docs/features/img/enable-backup-button.webp deleted file mode 100644 index d3d4bb29e5..0000000000 Binary files a/docs/docs/features/img/enable-backup-button.webp and /dev/null differ diff --git a/docs/docs/features/img/facial-recognition-1.webp b/docs/docs/features/img/facial-recognition-1.webp deleted file mode 100644 index 6d8f90f8e5..0000000000 Binary files a/docs/docs/features/img/facial-recognition-1.webp and /dev/null differ diff --git a/docs/docs/features/img/facial-recognition-2.webp b/docs/docs/features/img/facial-recognition-2.webp deleted file mode 100644 index 363dd7e9bc..0000000000 Binary files a/docs/docs/features/img/facial-recognition-2.webp and /dev/null differ diff --git a/docs/docs/features/img/facial-recognition-3.webp b/docs/docs/features/img/facial-recognition-3.webp deleted file mode 100644 index c094617452..0000000000 Binary files a/docs/docs/features/img/facial-recognition-3.webp and /dev/null differ diff --git a/docs/docs/features/img/facial-recognition-4.webp b/docs/docs/features/img/facial-recognition-4.webp deleted file mode 100644 index 94c48320fd..0000000000 Binary files a/docs/docs/features/img/facial-recognition-4.webp and /dev/null differ diff --git a/docs/docs/features/img/folder-access.webp b/docs/docs/features/img/folder-access.webp deleted file mode 100644 index aad9443559..0000000000 Binary files a/docs/docs/features/img/folder-access.webp and /dev/null differ diff --git a/docs/docs/features/img/folder-view-1.webp b/docs/docs/features/img/folder-view-1.webp deleted file mode 100644 index be4a55873d..0000000000 Binary files a/docs/docs/features/img/folder-view-1.webp and /dev/null differ diff --git a/docs/docs/features/img/folder-view-enable.webp b/docs/docs/features/img/folder-view-enable.webp deleted file mode 100644 index 46477b120e..0000000000 Binary files a/docs/docs/features/img/folder-view-enable.webp and /dev/null differ diff --git a/docs/docs/features/img/free-up-space.webp b/docs/docs/features/img/free-up-space.webp deleted file mode 100644 index 603a088e99..0000000000 Binary files a/docs/docs/features/img/free-up-space.webp and /dev/null differ diff --git a/docs/docs/features/img/gcast-enable.webp b/docs/docs/features/img/gcast-enable.webp deleted file mode 100644 index a39c83dd84..0000000000 Binary files a/docs/docs/features/img/gcast-enable.webp and /dev/null differ diff --git a/docs/docs/features/img/library-custom-scan-interval.webp b/docs/docs/features/img/library-custom-scan-interval.webp deleted file mode 100644 index a383c480dd..0000000000 Binary files a/docs/docs/features/img/library-custom-scan-interval.webp and /dev/null differ diff --git a/docs/docs/features/img/me.webp b/docs/docs/features/img/me.webp deleted file mode 100644 index a177086ec2..0000000000 Binary files a/docs/docs/features/img/me.webp and /dev/null differ diff --git a/docs/docs/features/img/mobile-upload-open-photo.webp b/docs/docs/features/img/mobile-upload-open-photo.webp deleted file mode 100644 index fafa4c9048..0000000000 Binary files a/docs/docs/features/img/mobile-upload-open-photo.webp and /dev/null differ diff --git a/docs/docs/features/img/mobile-upload-selected-photos.webp b/docs/docs/features/img/mobile-upload-selected-photos.webp deleted file mode 100644 index fa032752cb..0000000000 Binary files a/docs/docs/features/img/mobile-upload-selected-photos.webp and /dev/null differ diff --git a/docs/docs/features/img/obtain-api-key-2.webp b/docs/docs/features/img/obtain-api-key-2.webp deleted file mode 100644 index 3f946f2ea8..0000000000 Binary files a/docs/docs/features/img/obtain-api-key-2.webp and /dev/null differ diff --git a/docs/docs/features/img/obtain-api-key.webp b/docs/docs/features/img/obtain-api-key.webp deleted file mode 100644 index daba7d8b4b..0000000000 Binary files a/docs/docs/features/img/obtain-api-key.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-1.webp b/docs/docs/features/img/partner-sharing-1.webp deleted file mode 100644 index 0c8e96be34..0000000000 Binary files a/docs/docs/features/img/partner-sharing-1.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-2.webp b/docs/docs/features/img/partner-sharing-2.webp deleted file mode 100644 index 394302d6b0..0000000000 Binary files a/docs/docs/features/img/partner-sharing-2.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-3.webp b/docs/docs/features/img/partner-sharing-3.webp deleted file mode 100644 index 86e1fcb986..0000000000 Binary files a/docs/docs/features/img/partner-sharing-3.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-4.webp b/docs/docs/features/img/partner-sharing-4.webp deleted file mode 100644 index 15e2204d39..0000000000 Binary files a/docs/docs/features/img/partner-sharing-4.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-5.webp b/docs/docs/features/img/partner-sharing-5.webp deleted file mode 100644 index d648cc717a..0000000000 Binary files a/docs/docs/features/img/partner-sharing-5.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-6.webp b/docs/docs/features/img/partner-sharing-6.webp deleted file mode 100644 index 6b1f12d577..0000000000 Binary files a/docs/docs/features/img/partner-sharing-6.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-7.webp b/docs/docs/features/img/partner-sharing-7.webp deleted file mode 100644 index 0db1a1aeb5..0000000000 Binary files a/docs/docs/features/img/partner-sharing-7.webp and /dev/null differ diff --git a/docs/docs/features/img/partner-sharing-8.webp b/docs/docs/features/img/partner-sharing-8.webp deleted file mode 100644 index d3f37ecc26..0000000000 Binary files a/docs/docs/features/img/partner-sharing-8.webp and /dev/null differ diff --git a/docs/docs/features/img/public-shared-link-album.webp b/docs/docs/features/img/public-shared-link-album.webp deleted file mode 100644 index 6f54ef95f0..0000000000 Binary files a/docs/docs/features/img/public-shared-link-album.webp and /dev/null differ diff --git a/docs/docs/features/img/public-shared-link-form.webp b/docs/docs/features/img/public-shared-link-form.webp deleted file mode 100644 index b3ccc732ed..0000000000 Binary files a/docs/docs/features/img/public-shared-link-form.webp and /dev/null differ diff --git a/docs/docs/features/img/public-shared-link-individual.webp b/docs/docs/features/img/public-shared-link-individual.webp deleted file mode 100644 index c7463060f6..0000000000 Binary files a/docs/docs/features/img/public-shared-link-individual.webp and /dev/null differ diff --git a/docs/docs/features/img/read-only-mode.webp b/docs/docs/features/img/read-only-mode.webp deleted file mode 100644 index cb1694f609..0000000000 Binary files a/docs/docs/features/img/read-only-mode.webp and /dev/null differ diff --git a/docs/docs/features/img/reverse-geocoding-mobile1.webp b/docs/docs/features/img/reverse-geocoding-mobile1.webp deleted file mode 100644 index 6a16b4c433..0000000000 Binary files a/docs/docs/features/img/reverse-geocoding-mobile1.webp and /dev/null differ diff --git a/docs/docs/features/img/reverse-geocoding-mobile2.webp b/docs/docs/features/img/reverse-geocoding-mobile2.webp deleted file mode 100644 index 5c2a3c3364..0000000000 Binary files a/docs/docs/features/img/reverse-geocoding-mobile2.webp and /dev/null differ diff --git a/docs/docs/features/img/reverse-geocoding-mobile3.webp b/docs/docs/features/img/reverse-geocoding-mobile3.webp deleted file mode 100644 index 2bd78c778b..0000000000 Binary files a/docs/docs/features/img/reverse-geocoding-mobile3.webp and /dev/null differ diff --git a/docs/docs/features/img/shared-album-mobile.webp b/docs/docs/features/img/shared-album-mobile.webp deleted file mode 100644 index 26bf7793f9..0000000000 Binary files a/docs/docs/features/img/shared-album-mobile.webp and /dev/null differ diff --git a/docs/docs/features/img/shared-album-user-selection.webp b/docs/docs/features/img/shared-album-user-selection.webp deleted file mode 100644 index 1e7e3203f9..0000000000 Binary files a/docs/docs/features/img/shared-album-user-selection.webp and /dev/null differ diff --git a/docs/docs/features/img/shared-album.webp b/docs/docs/features/img/shared-album.webp deleted file mode 100644 index 506219e1ee..0000000000 Binary files a/docs/docs/features/img/shared-album.webp and /dev/null differ diff --git a/docs/docs/features/img/sidecar-jobs.webp b/docs/docs/features/img/sidecar-jobs.webp deleted file mode 100644 index fd369b06cf..0000000000 Binary files a/docs/docs/features/img/sidecar-jobs.webp and /dev/null differ diff --git a/docs/docs/features/img/tag-creation.webp b/docs/docs/features/img/tag-creation.webp deleted file mode 100644 index 9fe5aaffd4..0000000000 Binary files a/docs/docs/features/img/tag-creation.webp and /dev/null differ diff --git a/docs/docs/features/img/tag-enable.webp b/docs/docs/features/img/tag-enable.webp deleted file mode 100644 index 523d5fc1ec..0000000000 Binary files a/docs/docs/features/img/tag-enable.webp and /dev/null differ diff --git a/docs/docs/features/img/tag-form.webp b/docs/docs/features/img/tag-form.webp deleted file mode 100644 index c73c59dd6f..0000000000 Binary files a/docs/docs/features/img/tag-form.webp and /dev/null differ diff --git a/docs/docs/features/img/tag-view.webp b/docs/docs/features/img/tag-view.webp deleted file mode 100644 index 25f842b879..0000000000 Binary files a/docs/docs/features/img/tag-view.webp and /dev/null differ diff --git a/docs/docs/features/img/user-change-password.webp b/docs/docs/features/img/user-change-password.webp deleted file mode 100644 index a5d36ea2d9..0000000000 Binary files a/docs/docs/features/img/user-change-password.webp and /dev/null differ diff --git a/docs/docs/features/img/user-popup.webp b/docs/docs/features/img/user-popup.webp deleted file mode 100644 index f3d3c08e5f..0000000000 Binary files a/docs/docs/features/img/user-popup.webp and /dev/null differ diff --git a/docs/docs/features/img/user-profile.webp b/docs/docs/features/img/user-profile.webp deleted file mode 100644 index bba9499557..0000000000 Binary files a/docs/docs/features/img/user-profile.webp and /dev/null differ diff --git a/docs/docs/features/img/user-settings-1.webp b/docs/docs/features/img/user-settings-1.webp deleted file mode 100644 index 0bbaab1415..0000000000 Binary files a/docs/docs/features/img/user-settings-1.webp and /dev/null differ diff --git a/docs/docs/features/img/user-settings-2.webp b/docs/docs/features/img/user-settings-2.webp deleted file mode 100644 index a806b71c60..0000000000 Binary files a/docs/docs/features/img/user-settings-2.webp and /dev/null differ diff --git a/docs/docs/features/img/user-settings-3.webp b/docs/docs/features/img/user-settings-3.webp deleted file mode 100644 index 8f866eca0a..0000000000 Binary files a/docs/docs/features/img/user-settings-3.webp and /dev/null differ diff --git a/docs/docs/features/img/web-edit-download.webp b/docs/docs/features/img/web-edit-download.webp deleted file mode 100644 index 07b0ebfcb5..0000000000 Binary files a/docs/docs/features/img/web-edit-download.webp and /dev/null differ diff --git a/docs/docs/features/img/web-edit-interface.webp b/docs/docs/features/img/web-edit-interface.webp deleted file mode 100644 index d3b73a4607..0000000000 Binary files a/docs/docs/features/img/web-edit-interface.webp and /dev/null differ diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md deleted file mode 100644 index 2fb5a1c56a..0000000000 --- a/docs/docs/features/libraries.md +++ /dev/null @@ -1,170 +0,0 @@ -# External Libraries - -:::info -Currently an external library can only belong to a single user which is selected when the library is initially created. -::: - -External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up. - -If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost. - -:::caution - -If you add metadata to an external asset in any way (i.e. add it to an album or edit the description), that metadata is only stored inside Immich and will not be persisted to the external asset file. If you move an asset to another location within the library all such metadata will be lost upon rescan. This is because the asset is considered a new asset after the move. This is a known issue and will be fixed in a future release. - -::: - -:::caution - -Due to aggressive caching it can take some time for a refreshed asset to appear correctly in the web view. You need to clear the cache in your browser to see the changes. This is a known issue and will be fixed in a future release. In Chrome, you need to open the developer console with F12, then reload the page with F5, and finally right click on the reload button and select "Empty Cache and Hard Reload". - -::: - -### Import Paths - -External libraries use import paths to determine which files to scan. Each library can have multiple import paths so that files from different locations can be added to the same library. Import paths are scanned recursively, and if a file is in multiple import paths, it will only be added once. Each import file must be a readable directory that exists on the filesystem; the import path dialog will alert you of any paths that are not accessible. - -If the import paths are edited in a way that an external file is no longer in any import path, it will be removed from the library in the same way a deleted file would. If the file is moved back to an import path, it will be added again as if it was a new file. - -### Troubleshooting - -Sometimes, an external library will not scan correctly. This can happen if Immich can't access the files. Here are some things to check: - -- In the docker-compose file, are the volumes mounted correctly? -- Are the volumes also mounted to any worker containers? -- Are the import paths set correctly, and do they match the path set in docker-compose file? -- Make sure you don't use symlinks in your import libraries, and that you aren't linking across docker mounts. -- Are the permissions set correctly? -- Make sure you are using forward slashes (`/`) and not backward slashes. - -To validate that Immich can reach your external library, start a shell inside the container. Run `docker exec -it immich_server bash` to a bash shell. If your import path is `/mnt/photos`, check it with `ls /mnt/photos`. If you are using a dedicated microservices container, make sure to add the same mount point and check for availability within the microservices container as well. - -### Exclusion Patterns - -By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library. - -Some basic examples: - -- `**/*.tif` will exclude all files with the extension `.tif` -- `**/hidden.jpg` will exclude all files named `hidden.jpg` -- `**/Raw/**` will exclude all files in any directory named `Raw` -- `**/*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg` - -Special characters such as @ should be escaped, for instance: - -- `**/\@eaDir/**` will exclude all files in any directory named `@eaDir` - -:::info -Internally, Immich uses the [glob](https://www.npmjs.com/package/glob) package to process exclusion patterns, and sometimes those patterns are translated into [Postgres LIKE patterns](https://www.postgresql.org/docs/current/functions-matching.html). The intention is to support basic folder exclusions but we recommend against advanced usage since those can't reliably be translated to the Postgres syntax. Please refer to the [glob documentation](https://github.com/isaacs/node-glob#glob-primer) for a basic overview on glob patterns. -::: - -### Automatic watching (EXPERIMENTAL) - -This feature is considered experimental and for advanced users only. If enabled, it will allow automatic watching of the filesystem which means new assets are automatically imported to Immich without needing to rescan. - -If your photos are on a network drive, automatic file watching likely won't work. In that case, you will have to rely on a [periodic library refresh](#set-custom-scan-interval) to pull in your changes. - -#### Troubleshooting - -If you encounter an `ENOSPC` error, you need to increase your file watcher limit. In sysctl, this key is called `fs.inotify.max_user_watches` and has a default value of 8192. Increase this number to a suitable value greater than the number of files you will be watching. Note that Immich has to watch all files in your import paths including any ignored files. - -``` -ERROR [LibraryService] Library watcher for library c69faf55-f96d-4aa0-b83b-2d80cbc27d98 encountered error: Error: ENOSPC: System limit for number of file watchers reached, watch '/media/photo.jpg' -``` - -In rare cases, the library watcher can hang, preventing Immich from starting up. In this case, disable the library watcher in the configuration file. If the watcher is enabled from within Immich, the app must be started without the microservices. Disable the microservices in the docker compose file, start Immich, disable the library watcher in the admin settings, close Immich, re-enable the microservices, and then Immich can be started normally. - -### Nightly job - -There is an automatic scan job that is scheduled to run once a day. Its schedule is configurable, see [Set Custom Scan Interval](#set-custom-scan-interval). - -This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page. - -## Usage - -Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add: - -- `/home/user/old-pics`: a folder containing childhood photos. -- `/mnt/nas/christmas-trip`: photos from a christmas trip. The subfolder `/mnt/nas/christmas-trip/Raw` contains the raw files directly from the DSLR. We don't want to import the raw files to Immich -- `/mnt/media/videos`: Videos from the same christmas trip. - -First, we need to plan how we want to organize the libraries. The christmas trip photos should belong to its own library since we want to exclude the raw files. The videos and old photos can be in the same library since we want to import all files. We could also add all three folders to the same library if there are no files matching the Raw exclusion pattern in the other folders. - -### Mount Docker Volumes - -The `immich-server` container will need access to the gallery. Modify your docker compose file as follows - -```diff title="docker-compose.yml" - immich-server: - volumes: - - ${UPLOAD_LOCATION}:/data -+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro -+ - /home/user/old-pics:/mnt/media/old-pics:ro -+ - /mnt/media/videos:/mnt/media/videos:ro -+ - /mnt/media/videos2:/mnt/media/videos2 # WARNING: Immich will be able to delete the files in this folder, as it does not end with :ro -+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system. -``` - -:::tip -The `ro` flag at the end only gives read-only access to the volumes. -This will disallow the images from being deleted in the web UI, or adding metadata to the library ([XMP sidecars](/features/xmp-sidecars)). -::: - -:::info -_Remember to run `docker compose up -d` to register the changes. Make sure you can see the mounted path in the container._ -::: - -### Create A New Library - -These actions must be performed by the Immich administrator. - -- Click on your avatar in the upper right corner. -- Click on `Administration -> External Libraries`. -- Click on `Create Library`. -- Select which user owns the library, this **can not** be changed later -- You are now entering the library management page. -- Click on `Add` in the `Folders` section. -- Enter `/mnt/media/christmas-trip` then click Add. -- Click on `Edit` Library and rename it to "Christmas Trip". - -NOTE: We have to use the `/mnt/media/christmas-trip` path and not the `/mnt/nas/christmas-trip` path since all paths have to be what the Docker containers see. - -Next, we'll add an exclusion pattern to filter out raw files. - -- Click on `Add` in the `Exclusion Patterns` section. -- Enter `**/Raw/**` and click Add. -- Click on `Scan` - -The christmas trip library will now be scanned in the background. In the meantime, let's add the videos and old photos to another library. - -- Go back to `Administration -> External Libraries`. -- Click on `Create Library`. -- Select which user owns the library, -- You are now entering the library management page. -- Click on `Add` in the `Folders` section. -- Enter `/mnt/media/old-pics` then click Add -- Click on `Add` in the `Folders` section. -- Enter `/mnt/media/videos` then click Add -- Click on `Scan` -- Click on `Edit` Library and rename it to "Old videos and photos". - -Within seconds, the assets from the old-pics and videos folders should show up in the main timeline. - -### Folder view - -Folder view provides an additional view besides the timeline that is similar to a file explorer. It allows you to navigate through the folders and files in the library. This feature is handy for a highly curated and customized external library or a nicely configured storage template. - -You can enable this feature under [`Account Settings > Features > Folders`](https://my.immich.app/user-settings?isOpen=feature+folders) - - - -### Set Custom Scan Interval - -:::note -Only an admin can do this. -::: - -You can define a custom interval for the trigger external library rescan under Administration -> Settings -> External Library. -You can set the scanning interval using the preset or cron format. For more information you can refer to [Crontab Guru](https://crontab.guru/). - - diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md deleted file mode 100644 index 685f23932c..0000000000 --- a/docs/docs/features/ml-hardware-acceleration.md +++ /dev/null @@ -1,174 +0,0 @@ -# Hardware-Accelerated Machine Learning - -This feature allows you to use a GPU to accelerate machine learning tasks, such as Smart Search and Facial Recognition, while reducing CPU load. -As this is a new feature, it is still experimental and may not work on all systems. - -:::info -You do not need to redo any machine learning jobs after enabling hardware acceleration. The acceleration device will be used for any jobs that run after enabling it. -::: - -## Supported Backends - -- ARM NN (Mali) -- CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher) -- ROCm (AMD GPUs) -- OpenVINO (Intel GPUs such as Iris Xe and Arc) -- RKNN (Rockchip) - -## Limitations - -- The instructions and configurations here are specific to Docker Compose. Other container engines may require different configuration. -- Only Linux and Windows (through WSL2) servers are supported. -- ARM NN is only supported on devices with Mali GPUs. Other Arm devices are not supported. -- Some models may not be compatible with certain backends. CUDA is the most reliable. -- Search latency isn't improved by ARM NN due to model compatibility issues preventing its use. However, smart search jobs do make use of ARM NN. - -## Prerequisites - -#### ARM NN - -- Make sure you have the appropriate linux kernel driver installed - - This is usually pre-installed on the device vendor's Linux images -- `/dev/mali0` must be available in the host server - - You may confirm this by running `ls /dev` to check that it exists -- You must have the closed-source `libmali.so` firmware (possibly with an additional firmware file) - - Where and how you can get this file depends on device and vendor, but typically, the device vendor also supplies these - - The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere - - The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file -- Optional: Configure your `.env` file, see [environment variables](/install/environment-variables) for ARM NN specific settings - - In particular, the `MACHINE_LEARNING_ANN_FP16_TURBO` can significantly improve performance at the cost of very slightly lower accuracy - -#### CUDA - -- The GPU must have compute capability 5.2 or greater. -- The server must have the official NVIDIA driver installed. -- The installed driver must be >= 545 (it must support CUDA 12.3). -- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed. - -#### ROCm - -- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`. -- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached. -- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting). - -#### OpenVINO - -- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM. -- Ensure the server's kernel version is new enough to use the device for hardware acceleration. -- Expect higher RAM usage when using OpenVINO compared to CPU processing. - -#### OpenVINO-WSL - -- Ensure your container can access the /dev/dri directory, you can verify this by doing `docker exec -t immich_machine_learning ls -la /dev/dri`. If this is not the case execute `getent group render` and `getent group video` on the WSL host, then add those groups to hwaccel.ml.yaml - ```yaml - openvino-wsl: - devices: - - /dev/dri:/dev/dri - - /dev/dxg:/dev/dxg - volumes: - - /dev/bus/usb:/dev/bus/usb - - /usr/lib/wsl:/usr/lib/wsl - group_add: - - 44 # Replace this number with the number you found with getent group video - - 992 # Replace this number with the number you found with getent group render - ``` - -#### RKNN - -- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment. -- Make sure you have the appropriate linux kernel driver installed - - This is usually pre-installed on the device vendor's Linux images -- RKNPU driver V0.9.8 or later must be available in the host server - - You may confirm this by running `cat /sys/kernel/debug/rknpu/version` to check the version -- Optional: Configure your `.env` file, see [environment variables](/install/environment-variables) for RKNN specific settings - - In particular, setting `MACHINE_LEARNING_RKNN_THREADS` to 2 or 3 can _dramatically_ improve performance for RK3576 and RK3588 compared to the default of 1, at the expense of multiplying the amount of RAM each model uses by that amount. - -## Setup - -1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`. -2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend. -3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino, rknn] to the `image` section's tag at the end of the line. -4. Redeploy the `immich-machine-learning` container with these updated settings. - -### Confirming Device Usage - -You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel, `intel_gpu_top` for Intel, and `radeontop` for AMD. - -You can also check the logs of the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, or when you search with text in Immich, you should either see a log for `Available ORT providers` containing the relevant provider (e.g. `CUDAExecutionProvider` in the case of CUDA), or a `Loaded ANN model` log entry without errors in the case of ARM NN. - -#### Single Compose File - -Some platforms, including Unraid and Portainer, do not support multiple Compose files as of writing. As an alternative, you can "inline" the relevant contents of the [`hwaccel.ml.yml`][hw-file] file into the `immich-machine-learning` service directly. - -For example, the `cuda` section in this file is: - -```yaml -deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: - - gpu -``` - -You can add this to the `immich-machine-learning` service instead of extending from `hwaccel.ml.yml`: - -```yaml -immich-machine-learning: - container_name: immich_machine_learning - # Note the `-cuda` at the end - image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-cuda - # Note the lack of an `extends` section - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: - - gpu - volumes: - - model-cache:/cache - env_file: - - .env - restart: always -``` - -Once this is done, you can redeploy the `immich-machine-learning` container. - -#### Multi-GPU - -If you want to utilize multiple NVIDIA or Intel GPUs, you can set the `MACHINE_LEARNING_DEVICE_IDS` environmental variable to a comma-separated list of device IDs and set `MACHINE_LEARNING_WORKERS` to the number of listed devices. You can run a command such as `nvidia-smi -L` or `glxinfo -B` to see the currently available devices and their corresponding IDs. - -For example, if you have devices 0 and 1, set the values as follows: - -``` -MACHINE_LEARNING_DEVICE_IDS=0,1 -MACHINE_LEARNING_WORKERS=2 -``` - -In this example, the machine learning service will spawn two workers, one of which will allocate models to device 0 and the other to device 1. Different requests will be processed by one worker or the other. - -This approach can be used to simply specify a particular device as well. For example, setting `MACHINE_LEARNING_DEVICE_IDS=1` will ensure device 1 is always used instead of device 0. - -Note that you should increase job concurrencies to increase overall utilization and more effectively distribute work across multiple GPUs. Additionally, each GPU must be able to load all models. It is not possible to distribute a single model to multiple GPUs that individually have insufficient VRAM, or to delegate a specific model to one GPU. - -[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml -[nvct]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html - -## Tips - -- If you encounter an error when a model is running, try a different model to see if the issue is model-specific. -- You may want to increase concurrency past the default for higher utilization. However, keep in mind that this will also increase VRAM consumption. -- Larger models benefit more from hardware acceleration, if you have the VRAM for them. -- Compared to ARM NN, RKNPU has: - - Wider model support (including for search, which ARM NN does not accelerate) - - Less heat generation - - Very slightly lower accuracy (RKNPU always uses FP16, while ARM NN by default uses higher precision FP32 unless `MACHINE_LEARNING_ANN_FP16_TURBO` is enabled) - - Varying speed (tested on RK3588): - - If `MACHINE_LEARNING_RKNN_THREADS` is at the default of 1, RKNPU will have substantially lower throughput for ML jobs than ARM NN in most cases, but similar latency (such as when searching) - - If `MACHINE_LEARNING_RKNN_THREADS` is set to 3, it will be somewhat faster than ARM NN at FP32, but somewhat slower than ARM NN if `MACHINE_LEARNING_ANN_FP16_TURBO` is enabled - - When other tasks also use the GPU (like transcoding), RKNPU has a significant advantage over ARM NN as it uses the otherwise idle NPU instead of competing for GPU usage - - Lower RAM usage if `MACHINE_LEARNING_RKNN_THREADS` is at the default of 1, but significantly higher if greater than 1 (which is necessary for it to fully utilize the NPU and hence be comparable in speed to ARM NN) diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx deleted file mode 100644 index 02b5d492f4..0000000000 --- a/docs/docs/features/mobile-app.mdx +++ /dev/null @@ -1,132 +0,0 @@ -import Icon from '@mdi/react'; -import { mdiCloudOffOutline, mdiCloudCheckOutline } from '@mdi/js'; -import MobileAppDownload from '/docs/partials/_mobile-app-download.md'; -import MobileAppLogin from '/docs/partials/_mobile-app-login.md'; -import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; - -# Mobile App - -## Download - - - -:::info Android verification -Below are the SHA-256 fingerprints for the certificates signing the android applications. - -- Playstore / Github releases: - `86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20` -- F-Droid releases: - `FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF` - -::: - -## Login - - - -## Backup - - - -## Sync only selected photos - -If you have a large number of photos on the device, and you would prefer not to backup all the photos, then it might be prudent to only backup selected photos from device to the Immich server. - -First, you need to enable the Storage Indicator in your app's settings. Navigate to **Settings -> Photo Grid** and enable **`Show Storage indicator on asset tiles`**; this makes it easy to distinguish local-only assets and synced assets. - -:::note - -This will enable a small cloud icon on the bottom right corner of the asset tile, indicating that the asset is synced to the server: - -1. - Local-only asset; not synced to the server -2. - Asset is synced to the server - -::: - -Now make sure that the local album is selected in the backup screen (steps 1-2 above). You can find these albums listed in **Library -> On this device**. To selectively upload photos from these albums, simply select the local-only photos and tap on the `Upload` button in the dynamic bottom menu. - - - -## Free Up Space - -**Free Up Space** allows you to remove local media files from your device that have already been successfully backed up to your Immich server (and are not in Immich trash). This helps reclaim storage on your mobile device without losing your memories. - -### How it works - - - -1. **Configuration:** - - **Cutoff date:** Free Up Space will only look for photos and videos **on or before** this date. Photos removed from the device don't show up in other (messaging) apps and have to be shared from Immich in order to send them. - - **Keep favorites:** This works the same way `Keep albums` does. By default, favorited assets are preserved on your device. - - **Keep albums:** Hold all photos and videos in the selected albums on your device, regardless of other settings. By default, `WhatsApp` [related albums](#external-app-dependencies) are selected to be kept on the device. Assets not already on the device will not be re-downloaded. - - **Keep on device:** You can choose to restrict removal to `Always keep` **All photos** or **All videos**, regardless of other settings. This setting can hamper freeing up space significantly — with 80 GB of videos and 40 GB photos, selecting `Always keep photos` retains thousands of photos on your device. - -2. **Scan & Review:** Before any files are removed, you are presented with a review screen to verify which items will be deleted and how much storage is reclamable. -3. **Deletion:** Confirmed items are moved to your device's native Trash/Recycle Bin. - -:::info reclaim storage -To use the reclaimed space right away, you must empty the system/gallery trash manually outside of Immich. -::: - -Provided the server is healthy and [backed up](/administration/backup-and-restore.md), assets removed by Free Up Space can always be accessed in the Immich app. - -### iCloud Photos - -If you use **iCloud Photos** alongside Immich, it is vital to understand how deletion affects your data. After using **Free Up Space**, the photo will be stored **only** on your Immich server (and your phone's "Recently Deleted" folder for 30 days). - -Assets that are part of an **iCloud Shared Album** are automatically excluded from the cleanup scan because iCloud does not allow removing the items in Shared Album from the device. - -:::warning iCloud & Backups -If, in addition to Immich, you rely on iCloud as a secondary backup (as part of your [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup strategy), you should instead use `Optimize iPhone Storage` in [iCloud Photos](https://support.apple.com/en-us/105061). - -iCloud utilizes a two-way sync; this means deleting a photo, or using Free Up Space from your iPhone will **also delete it from iCloud** and all other devices (Mac, iPad) where you're signed in with the same Apple Account. See [Apple Support](https://support.apple.com/en-us/108922#iCloud_photo_library) for more info. -::: - -### External App Dependencies (WhatsApp, etc.) \{#external-app-dependencies\} - -Android applications like **WhatsApp** rely on local files to display media in chat history. - -If Immich backs up your WhatsApp folder and you run **Free Up Space**, the local copies of these images will be deleted. Consequently, **media in your WhatsApp chats will appear blurry or missing.** You will only be able to view these photos inside the Immich app; they will no longer be visible within the WhatsApp interface. - -**Recommendation:** If keeping chat history intact is important, exclude WhatsApp with `Keep albums` in Free Up Space and review the deletion list carefully. You have to enable [Album Sync](#album-sync) for WhatsApp to show up in the list. Alternatively, don't [back up](#backup) WhatsApp with Immich. - -## Album Sync - -You can sync or mirror an album from your phone to the Immich server on your account. For example, if you select Recents, Camera and Videos album for backup, the corresponding album with the same name will be created on the server. Once the assets from those albums are uploaded, they will be put into the target albums automatically. - -### Album Synchronization Highlights - -- **One-Way Sync:** Synchronization is one-way, from the device to the server. - -- **Name Matching:** If an album on the server has the same name as the album on the device, images from the device will be merged with the existing images in the server album. - -- **Shared Albums:** If the matching album on the server is shared, the new photos merged into the album will also be shared. - -- **Album Structure:** When an album is created for the first time, its structure is based on the initial state. Future updates made on the phone (such as deleting or repositioning photos) will not be reflected in Immich. - -- **User-Specific Sync:** Album synchronization is unique to each server user and does not sync between different users or partners. - -- **Mobile-Only Feature:** Album synchronization is currently only available on mobile. For similar options on a computer, refer to [Libraries](/features/libraries) for further details. - -### Synchronizing albums from the past - - - -Albums can be synchronized to the server even if they did not exist on the server before. You can enable this feature at any time and use the **Reorganize into album** button to backfill existing uploads into their corresponding albums. - -:::info Sync albums delete/move photos -If you delete/move photos in the local album on your device, it will not be reflected in the album on the server **even if** you click Sync albums -It will only reflect files you add. -::: - -## Read-only/kid Mode - -You can set the app to read-only mode to prevent accidental deletion of photos from your device, and only allow viewing photos on the timeline. - -To toggle this feature, long-press the profile icon or go to `Settings > Advanced > Read-only Mode`. - - diff --git a/docs/docs/features/mobile-backup.md b/docs/docs/features/mobile-backup.md deleted file mode 100644 index f3eb1a359c..0000000000 --- a/docs/docs/features/mobile-backup.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Mobile Backup - -## Overview - -Immich supports uploading photos and videos from your mobile device to the server automatically. - -When backup is enabled, Immich will upload new photos and videos from selected albums when you open or resume the app, as well as periodically in the background. - - - -## General Features - -### Backup albums selection - - - -You can select which albums on your mobile device to back up to the server. You can also exclude specific albums (by double-tapping on them) from being backed up. This is useful for iOS users since assets can belong to multiple albums. For example, you may want to back up all assets except those in the "Videos" album. - -### Deduplication - -When you first select albums for backup, Immich calculates a checksum for each file's content. This checksum identifies assets already on the server—whether uploaded via CLI, web interface, or another device. Files matching existing assets are skipped, preventing duplicate uploads and saving bandwidth. - -### Networking requirements - -By default, Immich will only upload photos and videos when connected to Wi-Fi. You can change this behavior in the backup settings page. - - - -### Backup album synchronization - - - -When enabled, Immich automatically creates albums on the server that mirror the albums on your mobile device. Photos and videos are organized into these server-side albums to match your device's album structure, making it easy to find and browse your content the same way you do on your phone. - -This is a one-way sync from your device to the server. You can enable this feature at any time and use the **Reorganize into album** button to backfill existing uploads into their corresponding albums. - -## Platform Specific Features - -### Android - - - -- It is a well-known problem that some Android models are very strict with battery optimization settings, which can cause a problem with the background worker. Please visit [Don't kill my app](https://dontkillmyapp.com/) for a guide on disabling this setting on your phone. -- You can allow the background task to run only when the device is charging. -- You can set the minimum delay from the time a photo is taken to when the background upload task will run. - -### iOS - -- You must enable **Background App Refresh** for the app to work in the background. You can enable it in the Settings app under General > Background App Refresh. - -
- -
- -- iOS automatically manages background tasks; the app cannot control when the background upload task will run. The more frequently you open the app, the more often background tasks will run. - -#### iCloud Backup - -Local albums containing assets from iCloud and marked for backup in Immich will be pulled from iCloud and temporarily stored in the app's cache folder. Once the hashing and uploading process is completed, the temporary files will be emptied. - -This process may consume additional data and storage space on your device, especially if you have a large number of iCloud photos and videos. Please ensure you have sufficient storage space and monitor your data usage if you are not connected to Wi-Fi. diff --git a/docs/docs/features/monitoring.md b/docs/docs/features/monitoring.md deleted file mode 100644 index 46063fded6..0000000000 --- a/docs/docs/features/monitoring.md +++ /dev/null @@ -1,151 +0,0 @@ -# Monitoring - -## Overview - -Immich provides a variety of performance metrics to allow for local monitoring and insights. This integration is primarily in the form of Prometheus metrics. However, exporting traces is also possible due to the use of OpenTelemetry instrumentation. - -:::note -This is an opt-in feature intended for you to monitor immich's performance. This data isn't sent anywhere beyond what you've configured. -::: - -## Prometheus - -Prometheus is a tool that collects metrics from a number of sources you configure. It operates in a "pull" strategy - that is, it periodically requests metrics from each defined source. This means that the source doesn't send anything until it's requested. It also means that the source -- immich, in this case -- has to expose an endpoint for Prometheus to target when it requests metrics. - -### Metrics - -These metrics come in a variety of forms: - -- Counters, which can only increase. Example: the number of times an endpoint has been called. -- Gauges, which can increase or decrease within a certain range. Example: CPU utilization. -- Histograms, where each observation is assigned to a certain number of "buckets". Example: response time, where each bucket is a number of milliseconds. This one is a bit more complicated. - - Buckets in this case are _cumulative_; that is, an observation is placed not only into the smallest bucket that contains it, but also to all buckets larger than this. For example, if a histogram has three buckets for 1ms, 5ms and 10ms, an observation of 3ms will be bucketed into both 5ms and 10ms. - -The metrics in immich are grouped into API (endpoint calls and response times), host (memory and CPU utilization), and IO (internal database queries, image processing, and so on). Each group of metrics can be enabled or disabled independently. - -### Configuration - -Immich will not expose an endpoint for metrics by default. To enable this endpoint, you can add the `IMMICH_TELEMETRY_INCLUDE=all` environmental variable to your `.env` file. Note that only the server container currently use this variable. - -:::tip -`IMMICH_TELEMETRY_INCLUDE=all` enables all metrics. For a more granular configuration you can enumerate the telemetry metrics that should be included as a comma separated list (e.g. `IMMICH_TELEMETRY_INCLUDE=repo,api`). Alternatively, you can also exclude specific metrics with `IMMICH_TELEMETRY_EXCLUDE`. For more information refer to the [environment section](/install/environment-variables.md#prometheus). -::: - -The next step is to configure a new or existing Prometheus instance to scrape this endpoint. The following steps assume that you do not have an existing Prometheus instance, but the steps will be similar either way. - -You can start by defining a Prometheus service in the Compose file: - -```yaml -immich-prometheus: - container_name: immich_prometheus - ports: - # this exposes the default port for Prometheus so you can interact with it - - 9090:9090 - image: prom/prometheus - volumes: - # the Prometheus configuration file - a barebones one is provided to get started - - ./prometheus.yml:/etc/prometheus/prometheus.yml - # a named volume defined in the bottom of the Compose file; it can also be a mounted folder - - prometheus-data:/prometheus -``` - -You will also need to add `prometheus-data` to the list of volumes in the bottom of the Compose file: - -```yaml -volumes: - model-cache: - prometheus-data: -``` - -The last piece is the [configuration file][prom-file]. This file defines (among other things) the sources Prometheus should target. Download it and place it in the same folder as the Compose file. - -:::tip -The provided file is just a starting point. There are a ton of ways to configure Prometheus, so feel free to experiment! -::: - -After bringing down the containers with `docker compose down` and back up with `docker compose up -d`, a Prometheus instance will now collect metrics from the immich server and microservices containers. Note that we didn't need to expose any new ports for these containers - the communication is handled in the internal Docker network. - -:::note -To see exactly what metrics are made available, you can additionally add `8081:8081` (API metrics) and `8082:8082` (microservices metrics) to the immich_server container's ports. -Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects. -To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](/install/environment-variables/#general). -::: - -### Usage - -So after setting up Prometheus, how do you actually view the metrics? The simplest way is to use Prometheus directly. Visiting Prometheus will show you a web UI where you can search for and visualize metrics. You can also view the status of your data sources and configure settings, but this is beyond the scope of this guide. - -## Grafana - -For a dedicated tool with nice presentation, you can use Grafana instead. This connects to Prometheus (and possibly other sources) for sophisticated data visualization. - -Setting up Grafana is similar to Prometheus. You can add a service for it: - -```yaml -immich-grafana: - container_name: immich_grafana - command: ['./run.sh', '-disable-reporting'] # this is to disable Grafana's telemetry - ports: - - 3000:3000 - image: grafana/grafana - volumes: - # stores your pretty dashboards and panels - - grafana-data:/var/lib/grafana -``` - -And add another volume for it: - -```yaml -volumes: - model-cache: - prometheus-data: - grafana-data: -``` - -After bringing down the services and back up again, you can now visit Grafana to view your metrics. On the first login, enter `admin` for both username and password and update your password. You can then go to the settings and add a data source with `http://immich-prometheus:9090` to point Grafana to your Prometheus instance. - -### Usage - -You can make your first dashboard to get started. Don't forget to save it frequently, or you'll lose all your progress! - -You can then make a new panel, specifying Prometheus as the data source for it. - --- TODO: add images and more details here - -## Structured Logging - -In addition to Prometheus metrics, Immich supports structured JSON logging which is ideal for log aggregation systems like Grafana Loki, ELK Stack, Datadog, Splunk, and others. - -### Configuration - -By default, Immich outputs human-readable console logs. To enable JSON logging, set the `IMMICH_LOG_FORMAT` environment variable: - -```bash -IMMICH_LOG_FORMAT=json -``` - -:::tip -The default is `IMMICH_LOG_FORMAT=console` for human-readable logs with colors during development. For production deployments using log aggregation, use `IMMICH_LOG_FORMAT=json`. -::: - -### JSON Log Format - -When enabled, logs are output in structured JSON format: - -```json -{"level":"log","pid":36,"timestamp":1766533331507,"message":"Initialized websocket server","context":"WebsocketRepository"} -{"level":"warn","pid":48,"timestamp":1766533331629,"message":"Unable to open /build/www/index.html, skipping SSR.","context":"ApiService"} -{"level":"error","pid":36,"timestamp":1766533331690,"message":"Failed to load plugin immich-core:","context":"Error"} -``` - -This format includes: - -- `level`: Log level (log, warn, error, etc.) -- `pid`: Process ID -- `timestamp`: Unix timestamp in milliseconds -- `message`: Log message -- `context`: Service or component that generated the log - -For more information on log formats, see [`IMMICH_LOG_FORMAT`](/install/environment-variables.md#general). - -[prom-file]: https://github.com/immich-app/immich/releases/latest/download/prometheus.yml diff --git a/docs/docs/features/partner-sharing.md b/docs/docs/features/partner-sharing.md deleted file mode 100644 index 1934344d01..0000000000 --- a/docs/docs/features/partner-sharing.md +++ /dev/null @@ -1,60 +0,0 @@ -# Partner Sharing - -Immich allows you to share your library with other users. They can then view your library and download the assets. You can manage Partner Sharing from the [User Settings](docs/features/user-settings.md) page on the web. - -Partner sharing includes: - -- Access to all non-archived and trashed photos and videos. -- Access to all metadata, including GPS information. -- Access to share assets via shared links, albums, etc. - -Partner sharing does _not_ include: - -- Already existing partner albums -- If an asset is favorited -- People and facial recognition data - -:::note - -- Partner sharing is one-way. To view your partner's assets, they must also share them with you. -- Partner sharing may result in displaying duplicate assets on the main timeline, as duplicates are only detected on a per-user basis. - -::: - -## Sharing with a Partner - - - - - - - -## Viewing Partner Assets - -Access partner assets via the Sharing page. - - - -## Timeline Integration - -Partner shared photos can be displayed in the main timeline. This feature can be enabled on a per-partner basis and can be viewed and updated on both the web and mobile app. - -### Web - -The option can be found at [`Account Settings > Partner Sharing > Show in timeline`](https://my.immich.app/user-settings?isOpen=partner-sharing) - - - -### Mobile App - -From the partner’s view, toggle the button - - - - - -## Removing Access - -In order to remove a partner, you can go to `User > Account Settings > Sharing` and click on the X button. - - diff --git a/docs/docs/features/reverse-geocoding.md b/docs/docs/features/reverse-geocoding.md deleted file mode 100644 index b1aee74a99..0000000000 --- a/docs/docs/features/reverse-geocoding.md +++ /dev/null @@ -1,15 +0,0 @@ -# Reverse Geocoding - -Immich supports local [Reverse Geocoding](https://en.wikipedia.org/wiki/Reverse_geocoding) using data from the [GeoNames](https://www.geonames.org/) geographical database. This data is loaded into the Postgres database on each minor version upgrade, allowing all queries to be run on your own server. - -## Extraction - -During Exif Extraction, assets with latitudes and longitudes are reverse geocoded to determine their City, State, and Country. - -## Usage - -Data from a reverse geocode is displayed in the image details, and used in [Smart Search](/features/searching.md). - - - - diff --git a/docs/docs/features/searching.md b/docs/docs/features/searching.md deleted file mode 100644 index 7360787127..0000000000 --- a/docs/docs/features/searching.md +++ /dev/null @@ -1,1205 +0,0 @@ -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Searching - -Immich uses Postgres as its search database for both metadata and contextual CLIP search. - -Contextual CLIP search is powered by the [VectorChord](https://github.com/tensorchord/VectorChord) extension, utilizing machine learning models like [CLIP](https://openai.com/research/clip) to provide relevant search results. This allows for freeform searches without requiring specific keywords in the image or video metadata. - -## Advanced Search Filters - -In addition, Immich offers advanced search functionality, allowing you to find specific content using customizable search filters. These filters include location, one or more faces, specific albums, and more. You can try out the search filters on the [Demo site](https://demo.immich.app). - -You can search the following types of content: - -| Type | Description | -| ----------------------------------- | ----------------------------------------------------- | -| People | Faces that are recognized in your photos/videos. | -| Contextual | Content of the photos and videos. | -| File name or extension | Full or partial file's name, or file's extension | -| Description | Description added to assets. | -| Optical Character Recognition (OCR) | Text in images | -| Locations | Cities, states, and countries from reverse geocoding. | -| Tags | Tags assigned or extracted from assets. | -| Camera | make, model and lens model | -| Time frame | Start and end date of a specific time bucket | -| Media type | Image or video or both | -| Display options | In Archive, in Favorites or Not in any album | -| Start rating | User-assigned start rating | - - - -## Configuration - -Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available. - -### CLIP models - -The default search model is fast, but there are many other options that can provide better search results. The tradeoff of using these models is that they're slower and/or use more memory (both when indexing images with background Smart Search jobs and when searching). - -The first step of choosing the right model for you is to know which languages your users will search in. - -If your users will only search in English, then the [CLIP][huggingface-clip] section is the first place to look. This is a curated list of the models that generally perform the best for their size class. The models here are ordered from higher to lower quality. This means that the top models will generally rank the most relevant results higher and have a higher capacity to understand descriptive, detailed, and/or niche queries. The models are also generally ordered from larger to smaller, so consider the impact on memory usage, job processing and search speed when deciding on one. The smaller models in this list are not too different in quality and many times faster. - -[Multilingual models][huggingface-multilingual-clip] are also available so users can search in their native language. Use these models if you expect non-English searches to be common. They can be separated into two search patterns: - -- `nllb` models expect the search query to be in the language specified in the user settings -- `xlm` and `siglip2` models understand search text regardless of the current language setting - -`nllb` models tend to perform the best and are recommended when users primarily searches in their native, non-English language. `xlm` and `siglip2` models are more flexible and are recommended for mixed language search, where the same user might search in different languages at different times. - -For more details, check the tables below to see how they compare in memory usage, speed and quality by language. - -Once you've chosen a model, follow these steps: - -1. Copy the name of the model (e.g. `ViT-B-16-SigLIP__webli`) -2. Go to the [Smart Search settings][smart-search-settings] -3. Paste the model name into the Model Name section -4. Save the settings -5. Go to the [Job Status page][job-status-page] -6. Click "All" next to "Smart Search" to begin re-processing your assets with the new model -7. (Optional) Confirm that the logs for the server and machine learning service don't have relevant errors - -In rare instances, changing the model might leave bits of the old model's incompatible data in the database, causing errors when processing Smart Search jobs. If you notice errors like this in the logs, you can change the model back to the previous one and save, then repeat steps 3-7. - -Please note that memory and execution time values are only _estimates_: actual usage will be different depending on many factors. As such, it's mainly intended as a way to compare the relative tradeoffs of each model. - -
-Reference - -Memory and execution time estimates were obtained without acceleration on a 7800x3D processor running bare metal Linux. All testing and evaluation was done at f32 precision (the default in Immich). - -**Execution Time (ms)**: After warming up the model with one pass, the mean execution time of 100 passes with the same input. - -**Memory (MiB)**: The peak RSS usage of the process after performing the above timing benchmark. Does not include image decoding, concurrent processing, the web server, etc., which are relatively constant factors. - -**Recall (%)**: Evaluated on Crossmodal-3600, the average of the recall@1, recall@5 and recall@10 results for zeroshot image retrieval. Chinese (Simplified), English, French, German, Italian, Japanese, Korean, Polish, Russian, Spanish and Turkish are additionally tested on XTD-10. Chinese (Simplified) and English are additionally tested on Flickr30k. The recall metrics are the average across all tested datasets. - -**Pareto Optimal**: Whether the model is not completely outclassed by another model. Try to use models that are optimal for the languages relevant to you. Specifically, for a given model and language, if there's another model that's better for that language in at least one respect (memory usage, execution time, recall) while being at least as good for that language in every other way, then the model is not optimal for that language. - -
- ---- - -
-English -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 85.99 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 85.96 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 85.96 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 85.93 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 85.78 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 85.75 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 85.62 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 85.53 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 85.48 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 85.47 | ✅ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 85.09 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 85.03 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 84.86 | ✅ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 84.61 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 84.17 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 83.51 | ❌ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 83.28 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.24 | ❌ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.23 | ❌ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 83.19 | ✅ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 82.54 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 82.43 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 82.36 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 82.28 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 81.9 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 81.9 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 80.82 | ❌ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 80.65 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 80.16 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 79.78 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 78.64 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 78.6 | ❌ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 78.06 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 78.06 | ❌ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 77.62 | ✅ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 77.47 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 76.91 | ❌ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 76.43 | ✅ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 76.35 | ❌ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 73.83 | ✅ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 73.62 | ❌ | -| RN50x64__openai | 5079 | 48.79 | 73.34 | ❌ | -| ViT-L-14__openai | 2212 | 19.91 | 72.99 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 72.76 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 72.59 | ❌ | -| RN50x4__openai | 1416 | 5.85 | 70.8 | ❌ | -| ViT-B-16__openai | 985 | 5.03 | 70.01 | ❌ | -| ViT-B-32__openai | 1004 | 2.26 | 69.9 | ✅ | -| RN101__openai | 1111 | 3.21 | 69.3 | ❌ | -| RN50__openai | 913 | 2.39 | 69.02 | ✅ | -| RN50__cc12m | 914 | 2.37 | 64.59 | ✅ | -| RN101__yfcc15m | 1111 | 3.22 | 55.21 | ❌ | -| RN50__yfcc15m | 908 | 2.34 | 53.63 | ✅ | -
-
-Arabic -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 77.3 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 76.44 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 74.03 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 73.19 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 69.31 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 69.29 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 69.29 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 68.64 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 68.35 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 68.25 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 68.23 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 67.56 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 67.28 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 66.89 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 66.52 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 64.1 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 61.71 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 60.7 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 59.66 | ✅ | -
-
-Bengali -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|----------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 76.16 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 75.83 | ❌ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 73.75 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 73.34 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 36.43 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 26.56 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 26.54 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 26.19 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 26.19 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 25.92 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 25.15 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 24.18 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 21.44 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 21.11 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 20.94 | ✅ | -
-
-Chinese (Simplified) -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 79.7 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 78.94 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 75.22 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 74.8 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 73.91 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 72.8 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 72.77 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 72.41 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 72.36 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 71.59 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 71.37 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 71.3 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 71.11 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 70.95 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 70.51 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 67.48 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 66.84 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 65.7 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 63.38 | ❌ | -
-
-Croatian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 87.46 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 87.19 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 82.98 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 82.92 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 81.93 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 73.77 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 73.21 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 73.2 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 72.95 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 72.89 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 72.88 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 72.85 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 72.69 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 70.73 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 70.45 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 70.43 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 69.97 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 54.31 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 53.3 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 35.64 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 35.17 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 33.65 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 33.55 | ❌ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 20.05 | ✅ | -
-
-Cusco Quechua -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|-----------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 38.08 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 37.87 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 33.41 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 33.06 | ✅ | -
-
-Czech -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 73.76 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 71.57 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 69.86 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 67.49 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 67.15 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 63.62 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 63.35 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 63.09 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 63.07 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 62.98 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 62.82 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 62.73 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 62.29 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 62.12 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 61.74 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 61.52 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 61.01 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 54.81 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 54.31 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 33.58 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 33.48 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 32.38 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 32.32 | ❌ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 22.89 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 22.66 | ❌ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 22.6 | ❌ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 22.25 | ❌ | -
-
-Danish -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 87.16 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 86.88 | ❌ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 84.18 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 84.03 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 83.75 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 83.32 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 83.25 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 82.3 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 82.19 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 81.87 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 81.44 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 81.42 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 80.0 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 79.82 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 79.08 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 75.07 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 74.84 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 67.68 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 67.2 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 65.59 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 65.36 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 42.31 | ✅ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 41.46 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 40.52 | ❌ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 31.31 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 30.97 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 30.87 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 30.51 | ❌ | -
-
-Dutch -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 80.05 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 79.81 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 79.72 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 79.72 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 79.64 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 79.49 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 79.41 | ❌ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 79.31 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 78.92 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 78.48 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 78.22 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 78.0 | ✅ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 77.22 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 76.69 | ❌ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 75.94 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 75.6 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 75.33 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 75.04 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 72.97 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 72.72 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 72.06 | ✅ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 72.06 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 70.81 | ✅ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 69.82 | ✅ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 67.54 | ❌ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 66.77 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 66.6 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 65.67 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 65.29 | ✅ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 41.1 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 34.29 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 29.65 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 29.56 | ❌ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 29.54 | ✅ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 29.36 | ❌ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 27.76 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 27.76 | ❌ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 25.67 | ✅ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 25.59 | ❌ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 25.53 | ❌ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 25.52 | ✅ | -| ViT-L-14__openai | 2212 | 19.91 | 22.31 | ❌ | -| RN50x64__openai | 5079 | 48.79 | 22.27 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 21.8 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 20.69 | ❌ | -
-
-Filipino -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|----------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 67.57 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 65.64 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 61.21 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 59.42 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 36.81 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 35.72 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 34.75 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 34.63 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 34.39 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 34.27 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 34.14 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 33.98 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 30.57 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 30.57 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 30.05 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 24.92 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 24.02 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 23.37 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 22.69 | ✅ | -
-
-Finnish -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 84.27 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.93 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 79.41 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 78.94 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 75.49 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 63.46 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 63.16 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 63.08 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 63.03 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 62.28 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 61.92 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 61.81 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 61.76 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 61.05 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 57.8 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 57.69 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 57.05 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 40.26 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 40.06 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 31.75 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 31.74 | ❌ | -
-
-French -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 86.5 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 86.5 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 86.39 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 86.15 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 86.1 | ❌ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 86.07 | ❌ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 86.06 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 85.89 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 85.67 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 85.67 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 85.63 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 85.39 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 85.35 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 84.97 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 83.8 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 82.96 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 82.91 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 82.52 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 81.21 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 80.23 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 79.85 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 79.47 | ✅ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 79.3 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 77.49 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 76.82 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 75.94 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 75.3 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 75.24 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 69.33 | ❌ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 64.41 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 62.86 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 59.27 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 59.09 | ❌ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 58.25 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 58.25 | ❌ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 56.97 | ✅ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 56.21 | ✅ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 53.36 | ✅ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 53.33 | ✅ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 53.26 | ❌ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 53.22 | ❌ | -| ViT-L-14__openai | 2212 | 19.91 | 46.34 | ❌ | -| RN50x64__openai | 5079 | 48.79 | 46.3 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 45.95 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 45.69 | ❌ | -| RN50x4__openai | 1416 | 5.85 | 42.48 | ❌ | -| RN101__openai | 1111 | 3.21 | 40.16 | ❌ | -| ViT-B-16__openai | 985 | 5.03 | 40.1 | ❌ | -| ViT-B-32__openai | 1004 | 2.26 | 38.27 | ✅ | -| RN50__openai | 913 | 2.39 | 37.8 | ✅ | -
-
-German -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 87.32 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 87.29 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 87.29 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 87.21 | ✅ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 87.18 | ❌ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 87.14 | ❌ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 87.07 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 86.83 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 86.81 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 86.75 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 86.74 | ✅ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 86.68 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 86.56 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 86.16 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 84.54 | ❌ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 84.41 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 84.25 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 83.8 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 82.59 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 81.53 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 81.34 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 81.15 | ✅ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 81.05 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 78.35 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 76.56 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 76.0 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 75.21 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 75.14 | ❌ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 65.86 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 56.87 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 47.19 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 43.36 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 43.0 | ❌ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 41.81 | ✅ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 40.43 | ✅ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 40.41 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 40.41 | ❌ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 37.71 | ✅ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 37.64 | ✅ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 36.04 | ✅ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 35.9 | ❌ | -| RN50x64__openai | 5079 | 48.79 | 34.19 | ❌ | -| ViT-L-14__openai | 2212 | 19.91 | 33.1 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 32.25 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 30.56 | ❌ | -| RN50x4__openai | 1416 | 5.85 | 29.2 | ❌ | -| ViT-B-16__openai | 985 | 5.03 | 25.77 | ❌ | -| RN101__openai | 1111 | 3.21 | 25.46 | ❌ | -| RN50__openai | 913 | 2.39 | 24.92 | ✅ | -| ViT-B-32__openai | 1004 | 2.26 | 24.13 | ✅ | -
-
-Greek -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 74.58 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 73.28 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 71.28 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 69.16 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 68.21 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 64.69 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 61.64 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 61.03 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 60.63 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 60.41 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 60.1 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 60.06 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 60.06 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 59.44 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 59.44 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 59.43 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 58.78 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 53.42 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 53.24 | ❌ | -
-
-Hebrew -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 88.04 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 87.09 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 83.93 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 83.84 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 80.78 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 74.59 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 72.73 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 72.25 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 72.19 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 72.15 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 72.08 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 72.07 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 72.06 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 71.78 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 70.55 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 70.03 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 69.34 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 60.33 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 58.49 | ❌ | -
-
-Hindi -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 62.02 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 61.67 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 58.68 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 58.54 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 38.54 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 36.95 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 36.62 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 36.06 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 35.76 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 35.34 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 35.17 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 34.94 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 34.91 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 34.19 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 33.56 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 32.06 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 31.85 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 27.87 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 27.08 | ❌ | -
-
-Hungarian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 85.59 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 85.25 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 81.74 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 80.34 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 80.14 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 74.94 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 74.2 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 74.03 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 73.96 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 73.95 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 73.9 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 73.59 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 73.12 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 72.5 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 72.33 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 71.83 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 70.57 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 58.31 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 56.74 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 38.26 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 37.97 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 28.75 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 28.26 | ❌ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 24.88 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 24.39 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 24.29 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 24.16 | ❌ | -
-
-Indonesian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 85.46 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 85.12 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 85.01 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 84.99 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 84.65 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 84.62 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 84.58 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 84.11 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 84.1 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 84.06 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 83.69 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 83.61 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 82.31 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 81.97 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 80.93 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 79.84 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 77.12 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 77.02 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 74.15 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 71.44 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 69.94 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 65.87 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 65.19 | ❌ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 59.95 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 59.38 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 57.88 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 57.52 | ✅ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 54.11 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 50.02 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 23.25 | ❌ | -
-
-Italian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 87.17 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 86.91 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 86.83 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 86.77 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 86.67 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 86.42 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 86.35 | ✅ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 86.34 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 86.18 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 86.17 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 85.84 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 85.8 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 85.7 | ✅ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 85.67 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 83.32 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 82.95 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 82.73 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 82.72 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 81.07 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 80.8 | ✅ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 80.6 | ✅ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 80.35 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 78.79 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 76.62 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 76.51 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 76.08 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 75.29 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 75.29 | ❌ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 74.84 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 56.32 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 47.25 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 43.09 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 42.99 | ❌ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 40.29 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 40.29 | ❌ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 39.67 | ✅ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 39.03 | ✅ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 36.14 | ✅ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 35.89 | ❌ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 35.59 | ❌ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 35.56 | ✅ | -| RN50x64__openai | 5079 | 48.79 | 33.53 | ❌ | -| ViT-L-14__openai | 2212 | 19.91 | 32.19 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 30.95 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 28.85 | ❌ | -| RN50x4__openai | 1416 | 5.85 | 25.75 | ❌ | -| ViT-B-16__openai | 985 | 5.03 | 25.18 | ❌ | -| RN101__openai | 1111 | 3.21 | 24.48 | ❌ | -| RN50__openai | 913 | 2.39 | 23.89 | ✅ | -| ViT-B-32__openai | 1004 | 2.26 | 23.39 | ✅ | -
-
-Japanese -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 83.95 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 82.21 | ❌ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 81.55 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 78.72 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 78.53 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 75.93 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 66.86 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 65.59 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 65.48 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 65.36 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 64.47 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 64.17 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 64.08 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 63.69 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 63.33 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 63.02 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 58.39 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 56.38 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 53.16 | ❌ | -
-
-Korean -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 80.56 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 80.53 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 77.09 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 77.08 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 76.97 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 76.92 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 76.58 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 76.2 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 75.95 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 75.86 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 75.67 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 75.49 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 74.6 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 74.52 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 73.88 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 71.09 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 68.87 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 67.94 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 66.39 | ✅ | -
-
-Maori -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|-----------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 48.43 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 46.12 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 42.8 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 40.85 | ✅ | -
-
-Norwegian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 81.36 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 80.96 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 77.65 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 76.39 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 75.97 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 75.44 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 75.31 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 75.0 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 74.96 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 74.92 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 74.44 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 74.37 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 73.11 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 72.63 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 71.71 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 67.81 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 65.55 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 62.56 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 60.94 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 59.62 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 59.49 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 46.3 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 45.75 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 42.55 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 35.33 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 35.01 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 34.94 | ❌ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 34.39 | ❌ | -
-
-Persian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 79.52 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 78.99 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 76.32 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 76.3 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 76.11 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 75.56 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 75.38 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 74.92 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 74.86 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 74.73 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 74.32 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 74.31 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 73.42 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 72.56 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 71.9 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 69.79 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 68.55 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 68.26 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 65.16 | ❌ | -
-
-Polish -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.49 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 83.45 | ❌ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.11 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 82.99 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 82.96 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 82.93 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 82.61 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 82.26 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 82.24 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 82.03 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 82.03 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 81.92 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 81.27 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 80.0 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 79.65 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 76.75 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 76.52 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 75.1 | ✅ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 73.9 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 65.03 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 64.89 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 51.6 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 51.29 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 46.15 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 41.55 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 41.17 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 40.9 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 40.76 | ✅ | -
-
-Portuguese -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 82.12 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 81.84 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 81.69 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 81.69 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 81.54 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 81.39 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 80.56 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 80.34 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 80.02 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 79.99 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 79.93 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 79.61 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 79.12 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 78.87 | ❌ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 78.85 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 77.54 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 75.31 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 75.26 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 74.82 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 74.48 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 74.47 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 73.92 | ✅ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 73.58 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 73.02 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 71.44 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 71.16 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 69.69 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 69.32 | ✅ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 59.86 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 45.49 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 37.86 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 36.01 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 35.75 | ❌ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 33.25 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 33.25 | ❌ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 32.83 | ✅ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 32.62 | ✅ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 30.86 | ❌ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 30.8 | ✅ | -| RN50x64__openai | 5079 | 48.79 | 30.58 | ❌ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 30.18 | ✅ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 29.93 | ❌ | -| ViT-L-14__openai | 2212 | 19.91 | 28.88 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 28.49 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 23.9 | ❌ | -| RN50x4__openai | 1416 | 5.85 | 22.94 | ❌ | -| ViT-B-16__openai | 985 | 5.03 | 22.55 | ❌ | -| RN50__openai | 913 | 2.39 | 21.85 | ✅ | -| ViT-B-32__openai | 1004 | 2.26 | 21.3 | ✅ | -| RN101__openai | 1111 | 3.21 | 21.14 | ❌ | -
-
-Romanian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 89.38 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 88.86 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 85.37 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 84.92 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 84.49 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 77.92 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 74.98 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 74.33 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 74.05 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 74.03 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 73.94 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 73.27 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 73.22 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 72.91 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 72.43 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 71.93 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 71.5 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 58.28 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 56.54 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 56.12 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 55.53 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 34.96 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 26.33 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 26.05 | ❌ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 21.32 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 21.04 | ❌ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 20.76 | ❌ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 20.56 | ✅ | -
-
-Russian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 84.54 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 84.41 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 84.36 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 84.31 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 84.22 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 83.9 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 83.69 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 83.5 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.31 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 83.21 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 83.11 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 82.7 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 82.69 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 80.91 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 79.75 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 79.35 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 78.91 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 78.06 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 76.44 | ✅ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 42.81 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 42.1 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 24.95 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 24.25 | ❌ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 20.85 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 20.44 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 20.41 | ❌ | -
-
-Spanish -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 85.47 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 85.44 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 85.32 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 85.22 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 85.15 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 84.81 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 84.68 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 84.6 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 84.55 | ✅ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 84.27 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 84.15 | ✅ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 83.87 | ❌ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.74 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 83.61 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.15 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 81.7 | ❌ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 80.91 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 80.73 | ✅ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 80.69 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 80.3 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 79.8 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 79.71 | ✅ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 79.64 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 78.0 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 77.83 | ❌ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 76.87 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 76.66 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 75.99 | ✅ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 71.96 | ❌ | -| ViT-H-14__laion2b-s32b-b79k | 4676 | 39.06 | 62.06 | ❌ | -| ViT-L-14__laion2b-s32b-b82k | 2233 | 20.56 | 53.78 | ❌ | -| ViT-L-14__laion400m_e32 | 2218 | 19.73 | 50.13 | ❌ | -| ViT-L-14__laion400m_e31 | 2183 | 19.87 | 50.0 | ❌ | -| ViT-B-16-plus-240__laion400m_e32 | 1246 | 6.95 | 47.39 | ❌ | -| ViT-B-16-plus-240__laion400m_e31 | 1263 | 6.94 | 47.39 | ❌ | -| ViT-B-32__laion2b_e16 | 1004 | 2.38 | 46.47 | ✅ | -| ViT-B-32__laion2b-s34b-b79k | 1001 | 2.29 | 45.68 | ✅ | -| ViT-B-16__laion400m_e31 | 991 | 5.04 | 44.0 | ✅ | -| ViT-B-16__laion400m_e32 | 975 | 4.98 | 43.98 | ✅ | -| ViT-B-32__laion400m_e32 | 1003 | 2.35 | 43.8 | ❌ | -| ViT-B-32__laion400m_e31 | 999 | 2.28 | 43.73 | ✅ | -| RN50x64__openai | 5079 | 48.79 | 43.01 | ❌ | -| ViT-L-14__openai | 2212 | 19.91 | 42.96 | ❌ | -| ViT-L-14-336__openai | 2616 | 43.45 | 41.67 | ❌ | -| RN50x16__openai | 2221 | 15.87 | 40.21 | ❌ | -| RN50x4__openai | 1416 | 5.85 | 36.06 | ❌ | -| ViT-B-16__openai | 985 | 5.03 | 35.67 | ❌ | -| RN101__openai | 1111 | 3.21 | 34.62 | ❌ | -| ViT-B-32__openai | 1004 | 2.26 | 32.6 | ✅ | -| RN50__openai | 913 | 2.39 | 31.79 | ✅ | -
-
-Swahili -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|---------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 69.51 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 68.44 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 66.09 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 63.98 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 21.64 | ✅ | -
-
-Swedish -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 77.12 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 76.37 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 73.41 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 72.83 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 72.51 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 72.2 | ❌ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 72.1 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 72.06 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 71.84 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 71.7 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 71.7 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 71.61 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 71.51 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 71.45 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 71.23 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 67.48 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 66.93 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 66.37 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 64.86 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 62.35 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 61.51 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 56.74 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 55.92 | ❌ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 48.5 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 48.38 | ✅ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 48.06 | ❌ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 47.99 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 47.93 | ❌ | -| ViT-SO400M-14-SigLIP-384__webli | 4417 | 72.19 | 29.98 | ❌ | -
-
-Telugu -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|-----------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 64.32 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 62.34 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 60.72 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 58.8 | ✅ | -
-
-Thai -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 79.99 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 79.07 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 76.13 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 75.23 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 74.04 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 66.03 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 45.87 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 45.69 | ❌ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 45.52 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 44.96 | ❌ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 44.75 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 44.66 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 43.99 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 43.91 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 43.06 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 41.86 | ❌ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 41.1 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 37.35 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 35.28 | ❌ | -
-
-Turkish -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.91 | ✅ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.74 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 81.26 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 80.21 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 79.34 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 79.22 | ✅ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 78.9 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 78.85 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 78.29 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 78.27 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 78.0 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 77.81 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 77.67 | ✅ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 77.33 | ✅ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 76.42 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 72.44 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 69.84 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 69.83 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 67.13 | ❌ | -| ViT-H-14-378-quickgelu__dfn5b | 5049 | 108.4 | 44.43 | ❌ | -| ViT-H-14-quickgelu__dfn5b | 4701 | 38.74 | 43.87 | ❌ | -| ViT-L-16-SigLIP-384__webli | 3396 | 47.6 | 35.1 | ❌ | -| ViT-L-16-SigLIP-256__webli | 3160 | 23.84 | 34.92 | ❌ | -| ViT-L-14-quickgelu__dfn2b | 2212 | 20.49 | 25.2 | ✅ | -| ViT-B-16-SigLIP-512__webli | 1828 | 26.17 | 24.55 | ✅ | -| ViT-B-16-SigLIP__webli | 1081 | 5.77 | 24.13 | ✅ | -| ViT-B-16-SigLIP-384__webli | 1128 | 13.53 | 24.08 | ❌ | -| ViT-B-16-SigLIP-256__webli | 1102 | 7.11 | 23.95 | ❌ | -
-
-Ukrainian -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.92 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.88 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 83.2 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 79.99 | ✅ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 79.31 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 78.73 | ✅ | -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 78.33 | ✅ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 77.95 | ❌ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 77.56 | ✅ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 77.49 | ✅ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 77.02 | ❌ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 76.87 | ❌ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 76.31 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 75.91 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 75.75 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 75.1 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 73.3 | ✅ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 65.28 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 63.95 | ❌ | -
-
-Vietnamese -| Model | Memory (MiB) | Execution Time (ms) | Recall (%) | Pareto Optimal | -|------------------------------------------------------|--------------|---------------------|------------|----------------| -| ViT-SO400M-16-SigLIP2-384__webli | 3854 | 56.57 | 85.86 | ✅ | -| ViT-SO400M-14-SigLIP2-378__webli | 3940 | 72.25 | 85.73 | ❌ | -| ViT-SO400M-16-SigLIP2-512__webli | 4050 | 107.67 | 85.67 | ❌ | -| ViT-gopt-16-SigLIP2-384__webli | 6585 | 146.84 | 85.5 | ❌ | -| ViT-L-16-SigLIP2-384__webli | 3057 | 51.7 | 84.93 | ✅ | -| ViT-SO400M-16-SigLIP2-256__webli | 3611 | 27.84 | 84.84 | ✅ | -| ViT-L-16-SigLIP2-512__webli | 3358 | 92.59 | 84.78 | ❌ | -| ViT-SO400M-14-SigLIP2__webli | 3622 | 27.63 | 84.34 | ✅ | -| ViT-gopt-16-SigLIP2-256__webli | 6475 | 64.51 | 84.33 | ❌ | -| ViT-L-16-SigLIP2-256__webli | 2830 | 23.77 | 83.93 | ✅ | -| nllb-clip-large-siglip__mrl | 4248 | 75.44 | 83.69 | ❌ | -| nllb-clip-large-siglip__v1 | 4226 | 75.05 | 83.19 | ❌ | -| XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k | 4014 | 39.14 | 81.88 | ❌ | -| ViT-B-16-SigLIP2__webli | 3038 | 5.81 | 80.88 | ✅ | -| nllb-clip-base-siglip__mrl | 4696 | 16.95 | 79.79 | ❌ | -| nllb-clip-base-siglip__v1 | 4675 | 15.17 | 79.38 | ❌ | -| ViT-B-32-SigLIP2-256__webli | 3061 | 3.31 | 77.73 | ✅ | -| XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k | 3030 | 3.2 | 75.18 | ✅ | -| ViT-B-16-SigLIP-i18n-256__webli | 3029 | 6.87 | 73.05 | ✅ | -
- -:::note -Feel free to make a feature request if there's a model you want to use that we don't currently support. -::: - -[huggingface-clip]: https://huggingface.co/collections/immich-app/clip-654eaefb077425890874cd07 -[huggingface-multilingual-clip]: https://huggingface.co/collections/immich-app/multilingual-clip-654eb08c2382f591eeb8c2a7 -[smart-search-settings]: https://my.immich.app/admin/system-settings?isOpen=machine-learning+smart-search -[job-status-page]: https://my.immich.app/admin/queues diff --git a/docs/docs/features/sharing.md b/docs/docs/features/sharing.md deleted file mode 100644 index a884884bee..0000000000 --- a/docs/docs/features/sharing.md +++ /dev/null @@ -1,51 +0,0 @@ -# Sharing - -Immich supports local sharing, with users on the same Immich instance, and public sharing via public links. - -## Local sharing - -### Albums - -Albums can be shared between users on the same Immich instance. The shared users can view and add their own photos and videos to the shared album. - -After creating an album, you can access the sharing options by clicking on the share icon. When sharing an album, you can select the users you want to share the album with and assign them permissions either as editors (read-write) or viewers (read-only). - -#### Web - - - - - -#### Mobile App - - - -### Partners - -Partner sharing allows you to share your _entire_ library with other users of your choice. They can then view your library and download the assets. - -You can read this guide to learn more about [partner sharing](/features/partner-sharing). - -## Public sharing - -You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a variety of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account. - -The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance. - -``` -https://my.immich.app/share/JUckRMxlgpo7F9BpyqGk_cZEwDzaU_U5LU5_oNZp1ETIBa9dpQ0b5ghNm_22QVJfn3k -``` - -### Creating a public share link - -You can create a public share link by selecting the photos or videos, or from the share icon on an album. - - - - - -### Customizing the public share link - -You can customize the public share link by setting an expiration date, password protection, allow what actions can be performed on the shared assets, and more. - - diff --git a/docs/docs/features/supported-formats.md b/docs/docs/features/supported-formats.md deleted file mode 100644 index 16f1ab0b6b..0000000000 --- a/docs/docs/features/supported-formats.md +++ /dev/null @@ -1,43 +0,0 @@ -# Supported Media Formats - -Immich supports a number of image and video formats, the most common of which are outlined here. - -:::note -For the full list, refer to the [Immich source code](https://github.com/immich-app/immich/blob/main/server/src/utils/mime-types.ts). -::: - -## Image formats - -| Format | Extension(s) | Supported? | Notes | -| :---------- | :---------------------------- | :----------------: | :-------------- | -| `AVIF` | `.avif` | :white_check_mark: | | -| `BMP` | `.bmp` | :white_check_mark: | | -| `GIF` | `.gif` | :white_check_mark: | | -| `HEIC` | `.heic` | :white_check_mark: | | -| `HEIF` | `.heif` | :white_check_mark: | | -| `JPEG 2000` | `.jp2` | :white_check_mark: | | -| `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | | -| `JPEG XL` | `.jxl` | :white_check_mark: | | -| `PNG` | `.png` | :white_check_mark: | | -| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | -| `RAW` | `.raw` | :white_check_mark: | | -| `RW2` | `.rw2` | :white_check_mark: | | -| `SVG` | `.svg` | :white_check_mark: | | -| `TIFF` | `.tif` `.tiff` | :white_check_mark: | | -| `WEBP` | `.webp` | :white_check_mark: | | - -## Video formats - -| Format | Extension(s) | Supported? | Notes | -| :---------- | :-------------------- | :----------------: | :---- | -| `3GPP` | `.3gp` `.3gpp` | :white_check_mark: | | -| `AVI` | `.avi` | :white_check_mark: | | -| `FLV` | `.flv` | :white_check_mark: | | -| `M4V` | `.m4v` | :white_check_mark: | | -| `MATROSKA` | `.mkv` | :white_check_mark: | | -| `MP2T` | `.mts` `.m2ts` `.m2t` | :white_check_mark: | | -| `MP4` | `.mp4` `.insv` | :white_check_mark: | | -| `MPEG` | `.mpg` `.mpe` `.mpeg` | :white_check_mark: | | -| `QUICKTIME` | `.mov` | :white_check_mark: | | -| `WEBM` | `.webm` | :white_check_mark: | | -| `WMV` | `.wmv` | :white_check_mark: | | diff --git a/docs/docs/features/tags.md b/docs/docs/features/tags.md deleted file mode 100644 index 79a9696d9a..0000000000 --- a/docs/docs/features/tags.md +++ /dev/null @@ -1,25 +0,0 @@ -# Tags - -Immich supports hierarchical tags, with the ability to read existing tags from the XMP `TagsList` field and IPTC `Keywords` field. Any changes to tags made through Immich are also written back to a [sidecar](/features/xmp-sidecars) file. You can re-run the metadata extraction jobs for all assets to import your existing tags. - -## Enable tags feature - -You can enable this feature from the [`Account Settings > Features > Tags`](https://my.immich.app/user-settings?isOpen=feature+tags). - - - -## Creating tags - -Tags can be created and added to a photo or a video by clicking on the `+ Add` button at the bottom of the info panel. - - - -The tag prompt will appear, and you find and select a tag, or type in a new tag to create it. - - - -## Viewing tags - -You can navigate to the `Tags` view from the side navigation bar or by clicking on the tag in the info panel. - - diff --git a/docs/docs/features/user-settings.md b/docs/docs/features/user-settings.md deleted file mode 100644 index 402105cd43..0000000000 --- a/docs/docs/features/user-settings.md +++ /dev/null @@ -1,23 +0,0 @@ -# User Settings - -Immich gives each user the ability to manage their own settings. This includes being able to update their profile, toggle certain feature, generate API keys, manage the logged in devices, a view of account usage statistics, and more. - - - -You can access the [user settings](https://my.immich.app/user-settings) by clicking on the user icon on the top right corner of the screen and selecting the `Account Settings` option. - - - -
- - - ---- - -:::tip Reset Password -The admin can reset a user password through the [User Management](/administration/user-management.mdx) screen. -::: - -:::tip Reset Admin Password -The admin password can be reset using a [Server Command](/administration/server-commands.md) -::: diff --git a/docs/docs/features/xmp-sidecars.md b/docs/docs/features/xmp-sidecars.md deleted file mode 100644 index 3536777d8a..0000000000 --- a/docs/docs/features/xmp-sidecars.md +++ /dev/null @@ -1,68 +0,0 @@ -# XMP Sidecars - -Immich supports XMP sidecar files — external `.xmp` files that store metadata for an image or video in XML format. During the metadata extraction job Immich will read & import metadata from `.xmp` files, and during the Sidecar Write job it will _write_ metadata back to `.xmp`. - -:::tip -Tools like Lightroom, Darktable, digiKam and other applications can also be configured to write changes to `.xmp` files, in order to avoid modifying the original file. -::: - -## Metadata Fields - -Immich does not support _all_ metadata fields. Below is a table showing what fields Immich can _read_ and _write_. It's important to note that writes do not replace the entire file contents, but are merged together with any existing fields. - -:::info -Immich automatically queues a Sidecar Write job after editing the description, rating, or updating tags. -::: - -| Metadata | Immich writes to XMP | Immich reads from XMP | -| --------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Description** | `dc:description`, `tiff:ImageDescription` | `dc:description`, `tiff:ImageDescription` | -| **Rating** | `xmp:Rating` | `xmp:Rating` | -| **DateTime** | `exif:DateTimeOriginal`, `photoshop:DateCreated` | In prioritized order:
`exif:SubSecDateTimeOriginal`
`exif:DateTimeOriginal`
`xmp:SubSecCreateDate`
`xmp:CreateDate`
`xmp:CreationDate`
`xmp:MediaCreateDate`
`xmp:SubSecMediaCreateDate`
`xmp:DateTimeCreated` | -| **Location** | `exif:GPSLatitude`, `exif:GPSLongitude` | `exif:GPSLatitude`, `exif:GPSLongitude` | -| **Tags** | `digiKam:TagsList` | In prioritized order:
`digiKam:TagsList`
`lr:HierarchicalSubject`
`IPTC:Keywords` | - -:::note -All other fields (e.g. `Creator`, `Source`, IPTC, Lightroom edits) remain in the `.xmp` file and are **not searchable** in Immich. -::: - -## File Naming Rules - -A sidecar must share the base name of the media file: - -- ✅ `IMG_0001.jpg.xmp` ← preferred -- ✅ `IMG_0001.xmp` ← fallback -- ❌ `myphoto_meta.xmp` ← not recognized - -If both `.jpg.xmp` and `.xmp` are present, Immich uses the **`.jpg.xmp`** file. - -## CLI Support - -1. **Detect** – Immich looks for a `.xmp` file placed next to each media file during upload. -2. **Copy** – Both the media and the sidecar file are copied into Immich’s internal library folder. - The sidecar is renamed to match the internal filename template, e.g.: - `upload/library//YYYY/YYYY-MM-DD/IMG_0001.jpg` - `upload/library//YYYY/YYYY-MM-DD/IMG_0001.jpg.xmp` -3. **Extract** – Selected metadata (title, description, date, rating, tags) is parsed from the sidecar and saved to the database. -4. **Write-back** – If you later update tags, rating, or description in the web UI, Immich will update **both** the database _and_ the copied `.xmp` file to stay in sync. - -## External Library (Mounted Folder) Support - -1. **Detect** – The `DISCOVER` job automatically associates `.xmp` files that sit next to existing media files in your mounted folder. No files are moved or renamed. -2. **Extract** – Immich reads and saves the same metadata fields from the sidecar to the database. -3. **Write-back** – If Immich has **write access** to the mount, any future metadata edits (e.g., rating or tags) are also written back to the original `.xmp` file on disk. - -:::danger -If the mount is **read-only**, Immich cannot update either the sidecar **or** the database — **metadata edits will silently fail** with no warning see issue [#10538](https://github.com/immich-app/immich/issues/10538) for more details. -::: - -## Admin Jobs - -Immich provides two admin jobs for managing sidecars: - -| Job | What it does | -| ---------- | ------------------------------------------------------------------------------------------------- | -| `DISCOVER` | Finds new `.xmp` files next to media that don’t already have one linked | -| `SYNC` | Re-reads existing `.xmp` files and refreshes metadata in the database (e.g. after external edits) | - -![Sidecar Admin Jobs](./img/sidecar-jobs.webp) diff --git a/docs/docs/guides/_category_.json b/docs/docs/guides/_category_.json deleted file mode 100644 index 6b51b67c73..0000000000 --- a/docs/docs/guides/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Guides", - "position": 6 -} diff --git a/docs/docs/guides/assets/20231219_183007_add-new-server-option.png b/docs/docs/guides/assets/20231219_183007_add-new-server-option.png deleted file mode 100644 index 934c8c792f..0000000000 Binary files a/docs/docs/guides/assets/20231219_183007_add-new-server-option.png and /dev/null differ diff --git a/docs/docs/guides/better-facial-clusters.md b/docs/docs/guides/better-facial-clusters.md deleted file mode 100644 index 40796983a5..0000000000 --- a/docs/docs/guides/better-facial-clusters.md +++ /dev/null @@ -1,72 +0,0 @@ -# Better Facial Recognition Clusters - -## Purpose - -This guide explains how to optimize facial recognition in systems with large image libraries. By following these steps, you'll achieve better clustering of faces, reducing the need for manual merging. - ---- - -## Important Notes - -- **Best Suited For:** Large image libraries after importing a significant number of images. -- **Warning:** This method deletes all previously assigned names. -- **Tip:** **Always take a [backup](/administration/backup-and-restore#database) before proceeding!** - ---- - -## Step-by-Step Instructions - -### Objective - -To enhance face clustering and ensure the model effectively identifies faces using qualitative initial data. - ---- - -### Steps - -#### 1. Adjust Machine Learning Settings - -Navigate to: -**Admin → Administration → Settings → Machine Learning Settings** - -Make the following changes: - -- **Maximum recognition distance (Optional):** - Lower this value, e.g., to **0.4**, if the library contains people with similar facial features. -- **Minimum recognized faces:** - Set this to a **high value** (e.g., 20 For libraries with a large amount of assets (~100K+), and 10 for libraries with medium amount of assets (~40K+)). - > A high value ensures clusters only include faces that appear at least 20/`value` times in the library, improving the initial clustering process. - ---- - -#### 2. Run Reset Jobs - -Go to: -**Admin → Administration → Settings → Jobs** - -Perform the following: - -1. **FACIAL RECOGNITION → Reset** - -> These reset jobs rebuild the recognition model based on the new settings. - ---- - -#### 3. Refine Recognition with Lower Thresholds - -Once the reset jobs are complete, refine the recognition as follows: - -- **Step 1:** - Return to **Minimum recognized faces** in Machine Learning Settings and lower the value to **10** (In medium libraries we will lower the value from 10 to 5). - - > Run the job: **FACIAL RECOGNITION → MISSING Mode** - -- **Step 2:** - Lower the value again to **3**. - > Run the job: **FACIAL RECOGNITION → MISSING Mode** - -:::tip try different values -For certain libraries with a larger or smaller amount of assets, other settings will be better or worse. It is recommended to try different values **​​before assigning names** and see which settings work best for your library. -::: - ---- diff --git a/docs/docs/guides/custom-locations.md b/docs/docs/guides/custom-locations.md deleted file mode 100644 index e0274d3bd9..0000000000 --- a/docs/docs/guides/custom-locations.md +++ /dev/null @@ -1,52 +0,0 @@ -# Files Custom Locations - -This guide explains how to store generated and raw files with docker's volume mount in different locations. - -:::caution Backup -It is important to remember to update the backup settings after following the guide to back up the new backup paths if using automatic backup tools, especially `profile/`. -::: - -In our `.env` file, we will define the paths we want to use. Note that you don't have to define all of these: UPLOAD_LOCATION will be the base folder that files are stored in by default, with the other paths acting as overrides. - -```diff title=".env" -# You can find documentation for all the supported environment variables [here](/install/environment-variables) - -# Custom location where your uploaded, thumbnails, and transcoded video files are stored -- UPLOAD_LOCATION=./library -+ UPLOAD_LOCATION=/custom/path/immich/immich_files -+ THUMB_LOCATION=/custom/path/immich/thumbs -+ ENCODED_VIDEO_LOCATION=/custom/path/immich/encoded-video -+ PROFILE_LOCATION=/custom/path/immich/profile -+ BACKUP_LOCATION=/custom/path/immich/backups -... -``` - -After defining the locations of these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container. These paths are where the mount attaches inside of the container, so don't change those. - -```diff title="docker-compose.yml" -services: - immich-server: - volumes: - - ${UPLOAD_LOCATION}:/data -+ - ${THUMB_LOCATION}:/data/thumbs -+ - ${ENCODED_VIDEO_LOCATION}:/data/encoded-video -+ - ${PROFILE_LOCATION}:/data/profile -+ - ${BACKUP_LOCATION}:/data/backups - - /etc/localtime:/etc/localtime:ro -``` - -After making this change, you have to move the files over to the new folders to make sure Immich can find everything it needs. If you haven't uploaded anything important yet, you can also reset Immich entirely by deleting the database folder. -Then restart Immich to register the changes: - -``` -docker compose up -d -``` - -:::note -Because of the underlying properties of docker bind mounts, it is not recommended to mount the `upload/` and `library/` folders as separate bind mounts if they are on the same device. -For this reason, we mount the HDD or the network storage (NAS) to `/data` and then mount the folders we want to access under that folder. - -The `thumbs/` folder contains both the small thumbnails displayed in the timeline and the larger previews shown when clicking into an image. These cannot be separated. - -The storage metrics of the Immich server will track available storage at `UPLOAD_LOCATION`, so the administrator must set up some sort of monitoring to ensure the storage does not run out of space. The `profile/` folder is much smaller, usually less than 1 MB. -::: diff --git a/docs/docs/guides/custom-map-styles.md b/docs/docs/guides/custom-map-styles.md deleted file mode 100644 index 1a61afc324..0000000000 --- a/docs/docs/guides/custom-map-styles.md +++ /dev/null @@ -1,27 +0,0 @@ -# Custom Map Styles - -You may decide that you'd like to modify the style document which is used to -draw the maps in Immich. In addition to visual customization, this also allows -you to pick your own map tile provider instead of the default one. The default -`style.json` for [light theme](https://github.com/immich-app/immich/tree/main/server/resources/style-light.json) -and [dark theme](https://github.com/immich-app/immich/blob/main/server/resources/style-dark.json) -can be used as a basis for creating your own style. - -There are several sources for already-made `style.json` map themes, as well as -online generators you can use. - -1. In **Immich**, navigate to **Administration --> Settings --> Map & GPS Settings** and expand the **Map Settings** subsection. -2. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.) -3. Save your selections. Reload the map, and enjoy your custom map style! - -## Use MapTiler to build a custom style - -Customizing the map style can be done easily using MapTiler, if you do not want to write an entire JSON document by hand. - -1. Create a free account at https://cloud.maptiler.com -2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there. -3. The **editor** interface is self-explanatory. You can change colors, remove visible layers, or add optional layers (e.g., administrative, topo, hydro, etc.) in the composer. -4. Once you have your map composed, click on **Save** at the top right. Give it a unique name to save it to your account. -5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. MapTiler will present an interactive side-by-side map with the original and your changes prior to publication.
![MapTiler Publication Settings](img/immich_map_styles_publish.webp) -6. MapTiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay. -7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to MapTiler. diff --git a/docs/docs/guides/database-gui.md b/docs/docs/guides/database-gui.md deleted file mode 100644 index 67b658f838..0000000000 --- a/docs/docs/guides/database-gui.md +++ /dev/null @@ -1,53 +0,0 @@ -# Database GUI - -A short guide on connecting [pgAdmin](https://www.pgadmin.org/) to Immich. - -## 1. Install pgAdmin - -Add a file `docker-compose-pgadmin.yml` next to your `docker-compose.yml` with the following content: - -``` -name: immich - -services: - pgadmin: - image: dpage/pgadmin4 - container_name: pgadmin4_container - restart: always - ports: - - "8888:80" - environment: - PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com - PGADMIN_DEFAULT_PASSWORD: strong-password - volumes: - - pgadmin-data:/var/lib/pgadmin - -volumes: - pgadmin-data: -``` - -Change the values of `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` in this file. - -Run `docker compose -f docker-compose.yml -f docker-compose-pgadmin.yml up` to start immich along with `pgAdmin`. - -## 2. Add a Server - -Open [localhost:8888](http://localhost:8888) and login with the default credentials from above. - -Right click on `Servers` and click on `Register >> Server..` then enter the values below in the `Connection` tab. - - - -:::note -The parameters used here match those specified in the example `.env` file. If you have changed your `.env` file, you'll need to adjust accordingly. -::: - -| Name | Value | -| -------------------- | ----------------- | -| Host name/address | `immich_postgres` | -| Port | `5432` | -| Maintenance database | `immich` | -| Username | `postgres` | -| Password | `postgres` | - -Click on "Save" to connect to the Immich database. diff --git a/docs/docs/guides/database-queries.md b/docs/docs/guides/database-queries.md deleted file mode 100644 index c2328d2bb8..0000000000 --- a/docs/docs/guides/database-queries.md +++ /dev/null @@ -1,164 +0,0 @@ -# Database Queries - -:::danger -Keep in mind that mucking around in the database might set the Moon on fire. Avoid modifying the database directly when possible, and always have current backups. -::: - -:::tip -Run `docker exec -it immich_postgres psql --dbname= --username=` to connect to the database via the container directly. - -(Replace `` and `` with the values from your [`.env` file](/install/environment-variables#database)). -::: - -## Assets - -### Name - -:::note -The `"originalFileName"` column is the name of the file at time of upload, including the extension. -::: - -```sql title="Find by original filename" -SELECT * FROM "asset" WHERE "originalFileName" = 'PXL_20230903_232542848.jpg'; -SELECT * FROM "asset" WHERE "originalFileName" LIKE 'PXL_%'; -- all files starting with PXL_ -SELECT * FROM "asset" WHERE "originalFileName" LIKE '%_2023_%'; -- all files with _2023_ in the middle -``` - -```sql title="Find by path" -SELECT * FROM "asset" WHERE "originalPath" = 'upload/library/admin/2023/2023-09-03/PXL_2023.jpg'; -SELECT * FROM "asset" WHERE "originalPath" LIKE 'upload/library/admin/2023/%'; -``` - -### ID - -```sql title="Find by ID" -SELECT * FROM "asset" WHERE "id" = '9f94e60f-65b6-47b7-ae44-a4df7b57f0e9'; -``` - -```sql title="Find by partial ID" -SELECT * FROM "asset" WHERE "id"::text LIKE '%ab431d3a%'; -``` - -### Checksum - -:::note -You can calculate the checksum for a particular file by using the command `sha1sum `. -::: - -```sql title="Find by checksum (SHA-1)" -SELECT encode("checksum", 'hex') FROM "asset"; -SELECT * FROM "asset" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e033bf74dd1', 'hex'); -SELECT * FROM "asset" WHERE "checksum" = '\x69de19c87658c4c15d9cacb9967b8e033bf74dd1'; -- alternate notation -``` - -```sql title="Find duplicate assets with identical checksum (SHA-1) (excluding trashed files)" -SELECT T1."checksum", array_agg(T2."id") ids FROM "asset" T1 - INNER JOIN "asset" T2 ON T1."checksum" = T2."checksum" AND T1."id" != T2."id" AND T2."deletedAt" IS NULL - WHERE T1."deletedAt" IS NULL GROUP BY T1."checksum"; -``` - -### Metadata - -```sql title="Live photos" -SELECT * FROM "asset" WHERE "livePhotoVideoId" IS NOT NULL; -``` - -```sql title="By description" -SELECT "asset".*, "asset_exif"."description" FROM "asset_exif" - JOIN "asset" ON "asset"."id" = "asset_exif"."assetId" - WHERE TRIM("asset_exif"."description") <> ''; -- all files with a description -SELECT "asset".*, "asset_exif"."description" FROM "asset_exif" - JOIN "asset" ON "asset"."id" = "asset_exif"."assetId" - WHERE "asset_exif"."description" ILIKE '%string to match%'; -- search by string -``` - -```sql title="Without metadata" -SELECT "asset".* FROM "asset_exif" - LEFT JOIN "asset" ON "asset"."id" = "asset_exif"."assetId" - WHERE "asset_exif"."assetId" IS NULL; -``` - -```sql title="size < 100,000 bytes, smallest to largest" -SELECT * FROM "asset" - JOIN "asset_exif" ON "asset"."id" = "asset_exif"."assetId" - WHERE "asset_exif"."fileSizeInByte" < 100000 - ORDER BY "asset_exif"."fileSizeInByte" ASC; -``` - -### Type - -```sql title="By type" -SELECT * FROM "asset" WHERE "asset"."type" = 'VIDEO'; -SELECT * FROM "asset" WHERE "asset"."type" = 'IMAGE'; -``` - -```sql title="Count by type" -SELECT "asset"."type", COUNT(*) FROM "asset" GROUP BY "asset"."type"; -``` - -```sql title="Count by type (per user)" -SELECT "user"."email", "asset"."type", COUNT(*) FROM "asset" - JOIN "user" ON "asset"."ownerId" = "user"."id" - GROUP BY "asset"."type", "user"."email" ORDER BY "user"."email"; -``` - -## Tags - -```sql title="Count by tag" -SELECT "t"."value" AS "tag_name", COUNT(*) AS "number_assets" FROM "tag" "t" - JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagId" JOIN "asset" "a" ON "ta"."assetId" = "a"."id" - WHERE "a"."visibility" != 'hidden' - GROUP BY "t"."value" ORDER BY "number_assets" DESC; -``` - -```sql title="Count by tag (per user)" -SELECT "t"."value" AS "tag_name", "u"."email" as "user_email", COUNT(*) AS "number_assets" FROM "tag" "t" - JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagId" JOIN "asset" "a" ON "ta"."assetId" = "a"."id" JOIN "user" "u" ON "a"."ownerId" = "u"."id" - WHERE "a"."visibility" != 'hidden' - GROUP BY "t"."value", "u"."email" ORDER BY "number_assets" DESC; -``` - -## Users - -```sql title="List all users" -SELECT * FROM "user"; -``` - -```sql title="Get owner info from asset ID" -SELECT "user".* FROM "user" JOIN "asset" ON "user"."id" = "asset"."ownerId" WHERE "asset"."id" = 'fa310b01-2f26-4b7a-9042-d578226e021f'; -``` - -## Persons - -```sql title="Delete person and unset it for the faces it was associated with" -DELETE FROM "person" WHERE "name" = 'PersonNameHere'; -``` - -## System - -### Config - -```sql title="Custom settings" -SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config'; -``` - -(Only used when not using the [config file](/install/config-file)) - -### File properties - -```sql title="Without thumbnails" -SELECT * FROM "asset" -WHERE (NOT EXISTS (SELECT 1 FROM "asset_file" WHERE "asset"."id" = "asset_file"."assetId" AND "asset_file"."type" = 'thumbnail') - OR NOT EXISTS (SELECT 1 FROM "asset_file" WHERE "asset"."id" = "asset_file"."assetId" AND "asset_file"."type" = 'preview')) -AND "asset"."visibility" = 'timeline'; -``` - -```sql title="Failed file movements" -SELECT * FROM "move_history"; -``` - -## Postgres internal - -```sql title="Change DB_PASSWORD" -ALTER USER WITH ENCRYPTED PASSWORD 'newpasswordhere'; -``` diff --git a/docs/docs/guides/docker-help.md b/docs/docs/guides/docker-help.md deleted file mode 100644 index 5f03de2d04..0000000000 --- a/docs/docs/guides/docker-help.md +++ /dev/null @@ -1,29 +0,0 @@ -# Docker Help - -## Containers - -```bash -docker ps # see a list of running containers -docker ps -a # see a list of running and stopped containers -``` - -## Attach to a Container - -```bash -docker exec -it # attach to a container with a command -docker exec -it immich_server bash -docker exec -it immich_machine_learning bash -``` - -## Logs - -```bash -docker logs # see the logs for a specific container (by id or name) - -docker logs immich_server -docker logs immich_machine_learning -``` - -:::tip Follow a log -Adding `--follow` to a `docker logs ` command will stream new logs, instead of immediately exiting, which is often useful for debugging. -::: diff --git a/docs/docs/guides/external-library.md b/docs/docs/guides/external-library.md deleted file mode 100644 index a1c8092732..0000000000 --- a/docs/docs/guides/external-library.md +++ /dev/null @@ -1,57 +0,0 @@ -# External Library - -This guide walks you through adding an [External Library](/features/libraries). -This guide assumes you are running Immich in Docker and that the files you wish to access are stored -in a directory on the same machine. - -# Mount the directory into the containers. - -Edit `docker-compose.yml` to add one or more new mount points in the section `immich-server:` under `volumes:`. -If you want Immich to be able to delete the images in the external library or add metadata ([XMP sidecars](/features/xmp-sidecars)), remove `:ro` from the end of the mount point. - -```diff -immich-server: - volumes: - - ${UPLOAD_LOCATION}:/data -+ - /home/user/photos1:/home/user/photos1:ro -+ - /mnt/photos2:/mnt/photos2:ro # you can delete this line if you only have one mount point, or you can add more lines if you have more than two -``` - -Restart Immich by running `docker compose up -d`. - -# Create the library - -:::info -External library management requires administrator access and the steps below assume you are using an admin account. -::: - -In the Immich web UI: - -- click the **Administration** link in the upper right corner. - - -- Select the **External Libraries** tab and click the **Create Library** button - - -- In the dialog, select which user should own the new library - - -- You are now entering the library management page. - - -- Click `Add` in the Folder section to specify a path for scanning and enter **/home/user/photos1** as the path and click Add - - -- Click the three-dots menu and select **Scan New Library Files** - - -# Confirm stuff is happening - -- Click **Administration** - - -- Select the **Jobs** tab - - -- You should see non-zero Active jobs for - Library, Generate Thumbnails, and Extract Metadata. diff --git a/docs/docs/guides/img/add-path-button.webp b/docs/docs/guides/img/add-path-button.webp deleted file mode 100644 index 014999069f..0000000000 Binary files a/docs/docs/guides/img/add-path-button.webp and /dev/null differ diff --git a/docs/docs/guides/img/add-path-field.webp b/docs/docs/guides/img/add-path-field.webp deleted file mode 100644 index 6ac5a33e30..0000000000 Binary files a/docs/docs/guides/img/add-path-field.webp and /dev/null differ diff --git a/docs/docs/guides/img/administration-link.webp b/docs/docs/guides/img/administration-link.webp deleted file mode 100644 index dc0b6cd63a..0000000000 Binary files a/docs/docs/guides/img/administration-link.webp and /dev/null differ diff --git a/docs/docs/guides/img/create-external-library.webp b/docs/docs/guides/img/create-external-library.webp deleted file mode 100644 index 90c38af077..0000000000 Binary files a/docs/docs/guides/img/create-external-library.webp and /dev/null differ diff --git a/docs/docs/guides/img/edit-import-path.webp b/docs/docs/guides/img/edit-import-path.webp deleted file mode 100644 index c07ae7b7fc..0000000000 Binary files a/docs/docs/guides/img/edit-import-path.webp and /dev/null differ diff --git a/docs/docs/guides/img/edit-import-paths.webp b/docs/docs/guides/img/edit-import-paths.webp deleted file mode 100644 index 70cc1cf7cf..0000000000 Binary files a/docs/docs/guides/img/edit-import-paths.webp and /dev/null differ diff --git a/docs/docs/guides/img/email-settings.webp b/docs/docs/guides/img/email-settings.webp deleted file mode 100644 index 6911d6efc3..0000000000 Binary files a/docs/docs/guides/img/email-settings.webp and /dev/null differ diff --git a/docs/docs/guides/img/google-app-password.webp b/docs/docs/guides/img/google-app-password.webp deleted file mode 100644 index 4621b8d9bd..0000000000 Binary files a/docs/docs/guides/img/google-app-password.webp and /dev/null differ diff --git a/docs/docs/guides/img/immich_map_styles_publish.webp b/docs/docs/guides/img/immich_map_styles_publish.webp deleted file mode 100644 index 52d4cd175f..0000000000 Binary files a/docs/docs/guides/img/immich_map_styles_publish.webp and /dev/null differ diff --git a/docs/docs/guides/img/jobs-tab.webp b/docs/docs/guides/img/jobs-tab.webp deleted file mode 100644 index 4cd5ec5026..0000000000 Binary files a/docs/docs/guides/img/jobs-tab.webp and /dev/null differ diff --git a/docs/docs/guides/img/libraries-dropdown.webp b/docs/docs/guides/img/libraries-dropdown.webp deleted file mode 100644 index fe9d1fcf47..0000000000 Binary files a/docs/docs/guides/img/libraries-dropdown.webp and /dev/null differ diff --git a/docs/docs/guides/img/library-management-page.webp b/docs/docs/guides/img/library-management-page.webp deleted file mode 100644 index dc81ece2d7..0000000000 Binary files a/docs/docs/guides/img/library-management-page.webp and /dev/null differ diff --git a/docs/docs/guides/img/library-owner.webp b/docs/docs/guides/img/library-owner.webp deleted file mode 100644 index 9a3ccb7778..0000000000 Binary files a/docs/docs/guides/img/library-owner.webp and /dev/null differ diff --git a/docs/docs/guides/img/path-save.webp b/docs/docs/guides/img/path-save.webp deleted file mode 100644 index df0256a3df..0000000000 Binary files a/docs/docs/guides/img/path-save.webp and /dev/null differ diff --git a/docs/docs/guides/img/pgadmin-add-new-server.webp b/docs/docs/guides/img/pgadmin-add-new-server.webp deleted file mode 100644 index 25aa33478a..0000000000 Binary files a/docs/docs/guides/img/pgadmin-add-new-server.webp and /dev/null differ diff --git a/docs/docs/guides/img/scan-new-library-files.webp b/docs/docs/guides/img/scan-new-library-files.webp deleted file mode 100644 index f5ef481db8..0000000000 Binary files a/docs/docs/guides/img/scan-new-library-files.webp and /dev/null differ diff --git a/docs/docs/guides/img/user-avatar.webp b/docs/docs/guides/img/user-avatar.webp deleted file mode 100644 index 78a8fd9270..0000000000 Binary files a/docs/docs/guides/img/user-avatar.webp and /dev/null differ diff --git a/docs/docs/guides/python-file-upload.md b/docs/docs/guides/python-file-upload.md deleted file mode 100644 index 684524f9c4..0000000000 --- a/docs/docs/guides/python-file-upload.md +++ /dev/null @@ -1,42 +0,0 @@ -# Python File Upload - -```python -#!/usr/bin/python3 - -import requests -import os -from datetime import datetime - -API_KEY = 'YOUR_API_KEY' # replace with a valid api key -BASE_URL = 'http://127.0.0.1:2283/api' # replace as needed - - -def upload(file): - stats = os.stat(file) - - headers = { - 'Accept': 'application/json', - 'x-api-key': API_KEY - } - - data = { - 'deviceAssetId': f'{file}-{stats.st_mtime}', - 'deviceId': 'python', - 'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime), - 'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime), - 'isFavorite': 'false', - } - - files = { - 'assetData': open(file, 'rb') - } - - response = requests.post( - f'{BASE_URL}/assets', headers=headers, data=data, files=files) - - print(response.json()) - # {'id': 'ef96f635-61c7-4639-9e60-61a11c4bbfba', 'duplicate': False} - - -upload('./test.jpg') -``` diff --git a/docs/docs/guides/remote-access.md b/docs/docs/guides/remote-access.md deleted file mode 100644 index 518b003c3a..0000000000 --- a/docs/docs/guides/remote-access.md +++ /dev/null @@ -1,63 +0,0 @@ -# Remote Access - -This page gives a few pointers on how to access your Immich instance from outside your LAN. -You can read the [full discussion in Discord](https://discord.com/channels/979116623879368755/1122615710846308484) - -:::danger -Never forward port 2283 directly to the internet without additional configuration. This will expose the web interface via http to the internet, making you susceptible to [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks. -::: - -## Option 1: VPN to home network - -You may use a VPN service to open an encrypted connection to your Immich instance. OpenVPN and Wireguard are two popular VPN solutions. Here is a guide on setting up VPN access to your server - [Pihole documentation](https://docs.pi-hole.net/guides/vpn/wireguard/overview/) - -### Pros - -- Simple to set up and very secure. -- Single point of potential failure, i.e., the VPN software itself. Even if there is a zero-day vulnerability on Immich, you will not be at risk. -- Both Wireguard and OpenVPN are independently security-audited, so the risk of serious zero-day exploits are minimal. - -### Cons - -- If you don't have a static IP address, you would need to set up a [Dynamic DNS](https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/). [DuckDNS](https://www.duckdns.org/) is a free DDNS provider. -- VPN software needs to be installed and active on both server-side and client-side. -- Requires you to open a port on your router to your server. - -## Option 2: Tailscale - -If you are unable to open a port on your router for Wireguard or OpenVPN to your server, [Tailscale](https://tailscale.com/) is a good option. Tailscale mediates a peer-to-peer wireguard tunnel between your server and remote device, even if one or both of them are behind a [NAT firewall](https://en.wikipedia.org/wiki/Network_address_translation). - -:::tip Video tutorial -You can learn how to set up Tailscale together with Immich with the [tutorial video](https://www.youtube.com/watch?v=Vt4PDUXB_fg) they created. -::: - -### Pros - -- Minimal configuration needed on server and client sides. -- You are protected against zero-day vulnerabilities on Immich. - -### Cons - -- The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022. -- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices. -- Tailscale needs to be installed and running on both server-side and client-side. - -## Option 3: Reverse Proxy - -A reverse proxy is a service that sits between web servers and clients. A reverse proxy can either be hosted on the server itself or remotely. Clients can connect to the reverse proxy via https, and the proxy relays data to Immich. This setup makes most sense if you have your own domain and want to access your Immich instance just like any other website, from outside your LAN. You can also use a DDNS provider like DuckDNS or no-ip if you don't have a domain. This configuration allows the Immich Android and iphone apps to connect to your server without a VPN or tailscale app on the client side. - -If you're hosting your own reverse proxy, [Nginx](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) is a great option. An example configuration for Nginx is provided [here](/administration/reverse-proxy.md). - -You'll also need your own certificate to authenticate https connections. If you're making Immich publicly accessible, [Let's Encrypt](https://letsencrypt.org/) can provide a free certificate for your domain and is the recommended option. Alternatively, a [self-signed certificate](https://en.wikipedia.org/wiki/Self-signed_certificate) allows you to encrypt your connection to Immich, but it raises a security warning on the client's browser. - -A remote reverse proxy like [Cloudflare](https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/) increases security by hiding the server IP address, which makes targeted attacks like [DDoS](https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/) harder. - -### Pros - -- No additional software needs to be installed client-side -- If you only need access to the web interface remotely, it is possible to set up access controls that shield you from zero-day vulnerabilities on Immich. [Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) has a generous free tier. - -### Cons - -- Complex configuration -- Depending on your configuration, both the Immich web interface and API may be exposed to the internet. Immich is under very active development and the existence of severe security vulnerabilities cannot be ruled out. diff --git a/docs/docs/guides/remote-machine-learning.md b/docs/docs/guides/remote-machine-learning.md deleted file mode 100644 index b677d83b0d..0000000000 --- a/docs/docs/guides/remote-machine-learning.md +++ /dev/null @@ -1,64 +0,0 @@ -# Remote Machine Learning - -To alleviate [performance issues on low-memory systems](/FAQ.mdx#why-is-immich-slow-on-low-memory-systems-like-the-raspberry-pi) like the Raspberry Pi, you may also host Immich's machine learning container on a more powerful system, such as your laptop or desktop computer. The server container will send requests containing the image preview to the remote machine learning container for processing. The machine learning container does not persist this data or associate it with a particular user. - -:::info -Smart Search and Face Detection will use this feature, but Facial Recognition will not. This is because Facial Recognition uses the _outputs_ of these models that have already been saved to the database. As such, its processing is between the server container and the database. -::: - -:::danger -Image previews are sent to the remote machine learning container. Use this option carefully when running this on a public computer or a paid processing cloud. Additionally, as an internal service, the machine learning container has no security measures whatsoever. Please be mindful of where it's deployed and who can access it. -::: - -1. Ensure the remote server has Docker installed -2. Copy the following `docker-compose.yml` to the remote server - -:::info -If using hardware acceleration, the [hwaccel.ml.yml](https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml) file also needs to be added and the `docker-compose.yml` needs to be configured as described in the [hardware acceleration documentation](/features/ml-hardware-acceleration) -::: - -```yaml -name: immich_remote_ml - -services: - immich-machine-learning: - container_name: immich_machine_learning - # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. - # Example tag: ${IMMICH_VERSION:-release}-cuda - image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} - # extends: - # file: hwaccel.ml.yml - # service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable - volumes: - - model-cache:/cache - restart: always - ports: - - 3003:3003 - -volumes: - model-cache: -``` - -3. Start the remote machine learning container by running `docker compose up -d` - -:::info -Version mismatches between both hosts may cause bugs and instability, so remember to update this container as well when updating the local Immich instance. -::: - -4. Navigate to the [Machine Learning Settings](https://my.immich.app/admin/system-settings?isOpen=machine-learning) -5. Click _Add URL_ -6. Fill the new field with the URL to the remote machine learning container, e.g. `http://ip:port` - -## Forcing remote processing - -Adding a new URL to the settings is recommended over replacing the existing URL. This is because it will allow machine learning tasks to be processed successfully when the remote server is down by falling back to the local machine learning container. If you do not want machine learning tasks to be processed locally when the remote server is not available, you can instead replace the existing URL and only provide the remote container's URL. If doing this, you can remove the `immich-machine-learning` section of the local `docker-compose.yml` file to save resources, as this service will never be used. - -Do note that this will mean that Smart Search and Face Detection jobs will fail to be processed when the remote instance is not available. This in turn means that tasks dependent on these features—Duplicate Detection and Facial Recognition—will not run for affected assets. If this occurs, you must manually click the _Missing_ button next to Smart Search and Face Detection in the [Job Status](http://my.immich.app/admin/queues) page for the jobs to be retried. - -## Load balancing - -While several URLs can be provided in the settings, they are tried sequentially; there is no attempt to distribute load across multiple containers. It is recommended to use a dedicated load balancer for such use-cases and specify it as the only URL. Among other things, it may enable the use of different APIs on the same server by running multiple containers with different configurations. For example, one might run an OpenVINO container in addition to a CUDA container, or run a standard release container to maximize both CPU and GPU utilization. - -:::tip -The machine learning container can be shared among several Immich instances regardless of the models a particular instance uses. However, using different models will lead to higher peak memory usage. -::: diff --git a/docs/docs/guides/scaling-immich.md b/docs/docs/guides/scaling-immich.md deleted file mode 100644 index a8d916ae2a..0000000000 --- a/docs/docs/guides/scaling-immich.md +++ /dev/null @@ -1,19 +0,0 @@ -# Scaling Immich - -Immich is built with modern deployment practices in mind, and the backend is designed to be able to run multiple instances in parallel. When doing this, the only requirement you need to be aware of is that every instance needs to be connected to the shared infrastructure. That means they should all have access to the same Postgres and Redis instances, and have the same files mounted into the containers. - -Scaling can be useful for many reasons. Maybe you have a gaming PC that you want to use for transcoding and thumbnail generation, or perhaps you run a Kubernetes cluster across a handful of powerful servers that you want to make use of. - -:::info -If you only have a single machine to run Immich on, scaling to multiple containers is unlikely to provide any benefit. An Immich container will run multiple background tasks at once, and you can increase their number from the admin panel. -::: - -The details of how to scale across multiple machines will vary widely between different environments and require some knowledge to set up, and as such this guide gives no specific instructions. In some cases scaling up can be as easy as incrementing the amount of replicas on a Kubernetes deployment, in others it might need you to configure network tunnels or NFS mounts. The details are left as an exercise for the reader ;) - -## Workers - -By default, each running `immich-server` container comes with multiple internal workers. If you're scaling up only to handle more background tasks, you can choose to disable the worker responsible for the API. See [workers](../administration/jobs-workers.md) for more detail. - -## Scaling down - -In the same way you can scale up to multiple containers, you can also choose to scale down. All state is stored in Postgres, Redis, and the filesystem so there is no risk in stopping a running immich-server container, for example if you want to use your GPU to play some games. As long as there is an API worker running you will still be able to browse Immich, and jobs will wait to be processed until there is a worker available for them. diff --git a/docs/docs/guides/smtp-gmail.md b/docs/docs/guides/smtp-gmail.md deleted file mode 100644 index 58c598c308..0000000000 --- a/docs/docs/guides/smtp-gmail.md +++ /dev/null @@ -1,20 +0,0 @@ -# SMTP settings using Gmail - -This guide walks you through how to get the information you need to set up your Immich instance to send emails using Gmail's SMTP server. - -## Create an app password - -From your Google account settings - -- Add [2-Step Verification](https://support.google.com/accounts/answer/185839) to your Google account (Required) -- [Create an app password](https://myaccount.google.com/apppasswords). - -At the end of creating your app passwords, a password will be displayed; save it, it will be used for the password field when setting up the SMTP server in Immich. - - - -## Entering the SMTP credential in Immich - -Entering your credential in Immich's email notification settings at `Administration -> Settings -> Notification Settings` - - diff --git a/docs/docs/guides/template-backup-script.md b/docs/docs/guides/template-backup-script.md deleted file mode 100644 index 19647d4ae1..0000000000 --- a/docs/docs/guides/template-backup-script.md +++ /dev/null @@ -1,90 +0,0 @@ -# Backup Script - -[Borg](https://www.borgbackup.org/) is a feature-rich deduplicating archiving software with built-in versioning. We provide a template bash script that can be run daily/weekly as a [cron](https://wiki.archlinux.org/title/cron) job to back up your files and database. We encourage you to read the quick-start guide for Borg before running this script. - -This script assumes you have a second hard drive connected to your server for on-site backup and ssh access to a remote machine for your third off-site copy. [BorgBase](https://www.borgbase.com/) is an alternative option for off-site backups with a competitive pricing structure. You may choose to skip off-site backups entirely by removing the relevant lines from the template script. - -The database is saved to your Immich upload folder in the `database-backup` subdirectory. The database is then backed up and versioned with your assets by Borg. This ensures that the database backup is in sync with your assets in every snapshot. - -:::info -This script makes backups of your database along with your photo/video library. This is redundant with the [automatic database backup tool](/administration/backup-and-restore#automatic-database-dumps) built into Immich. Using this script to backup your database has two advantages over the built-in backup tool: - -- This script uses storage more efficiently by versioning your backups instead of making multiple copies. -- The database backups are performed at the same time as the library backup, ensuring that the backups of your database and the library are always in sync. - -If you are using this script, it is therefore safe to turn off the built-in automatic database backups from your admin panel to save storage space. -::: - -### Prerequisites - -- Borg needs to be installed on your server as well as the remote machine. You can find instructions to install Borg [here](https://borgbackup.readthedocs.io/en/latest/installation.html). -- (Optional) To run this script as a non-root user, you should [add your username to the docker group](https://docs.docker.com/engine/install/linux-postinstall/). -- To run this script non-interactively, set up [passwordless ssh](https://www.redhat.com/sysadmin/passwordless-ssh) to your remote machine from your server. If you skipped the previous step, make sure this step is done from your root account. - -To initialize the borg repository, run the following commands once. - -```bash title='Borg set-up' -UPLOAD_LOCATION="/path/to/immich/directory" # Immich database location, as set in your .env file -BACKUP_PATH="/path/to/local/backup/directory" - -mkdir "$UPLOAD_LOCATION/database-backup" -borg init --encryption=none "$BACKUP_PATH/immich-borg" - -## Remote set up -REMOTE_HOST="remote_host@IP" -REMOTE_BACKUP_PATH="/path/to/remote/backup/directory" - -borg init --encryption=none "$REMOTE_HOST:$REMOTE_BACKUP_PATH/immich-borg" -``` - -Edit the following script as necessary and add it to your crontab. Note that this script assumes there are no `:`, `@`, or `"` characters in your paths. If these characters exist, you will need to escape and/or rename the paths. - -```bash title='Borg backup template' -#!/bin/sh - -# Paths -UPLOAD_LOCATION="/path/to/immich/directory" -BACKUP_PATH="/path/to/local/backup/directory" -REMOTE_HOST="remote_host@IP" -REMOTE_BACKUP_PATH="/path/to/remote/backup/directory" - - -### Local - -# Backup Immich database -docker exec -t immich_postgres pg_dumpall --clean --if-exists --username= > "$UPLOAD_LOCATION"/database-backup/immich-database.sql -# For deduplicating backup programs such as Borg or Restic, compressing the content can increase backup size by making it harder to deduplicate. If you are using a different program or still prefer to compress, you can use the following command instead: -# docker exec -t immich_postgres pg_dumpall --clean --if-exists --username= | /usr/bin/gzip --rsyncable > "$UPLOAD_LOCATION"/database-backup/immich-database.sql.gz - -### Append to local Borg repository -borg create "$BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/ -borg prune --keep-weekly=4 --keep-monthly=3 "$BACKUP_PATH"/immich-borg -borg compact "$BACKUP_PATH"/immich-borg - - -### Append to remote Borg repository -borg create "$REMOTE_HOST:$REMOTE_BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/ -borg prune --keep-weekly=4 --keep-monthly=3 "$REMOTE_HOST:$REMOTE_BACKUP_PATH"/immich-borg -borg compact "$REMOTE_HOST:$REMOTE_BACKUP_PATH"/immich-borg -``` - -### Restoring - -To restore from a backup, use the `borg mount` command. - -```bash title='Restore from local backup' -BACKUP_PATH="/path/to/local/backup/directory" -mkdir /tmp/immich-mountpoint -borg mount "$BACKUP_PATH"/immich-borg /tmp/immich-mountpoint -cd /tmp/immich-mountpoint -``` - -```bash title='Restore from remote backup' -REMOTE_HOST="remote_host@IP" -REMOTE_BACKUP_PATH="/path/to/remote/backup/directory" -mkdir /tmp/immich-mountpoint -borg mount "$REMOTE_HOST:$REMOTE_BACKUP_PATH"/immich-borg /tmp/immich-mountpoint -cd /tmp/immich-mountpoint -``` - -You can find available snapshots in separate sub-directories at `/tmp/immich-mountpoint`. Restore the files you need, and unmount the Borg repository using `borg umount /tmp/immich-mountpoint` diff --git a/docs/docs/install/_category_.json b/docs/docs/install/_category_.json deleted file mode 100644 index 6346441660..0000000000 --- a/docs/docs/install/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Install", - "position": 2 -} diff --git a/docs/docs/install/all-in-one.md b/docs/docs/install/all-in-one.md deleted file mode 100644 index 4d37952179..0000000000 --- a/docs/docs/install/all-in-one.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -sidebar_position: 70 ---- - -# All-In-One [Community] - -:::note -This is a community contribution and not officially supported by the Immich team, but included here for convenience. - -**Please report issues to the corresponding [Github Repository][github].** -::: - -## Installation - -For installation instructions, refer to the [Github Repository][github]. - -## Issues - -For issues, open an issue on the associated [GitHub Repository][github]. - -[github]: https://github.com/imagegenius/docker-immich/ diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md deleted file mode 100644 index a6aaae149b..0000000000 --- a/docs/docs/install/config-file.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -sidebar_position: 100 ---- - -# Config File - -A config file can be provided as an alternative to the UI configuration. - -### Step 1 - Create a new config file - -In JSON format, create a new config file (e.g. `immich.json`) and put it in a location that can be accessed by Immich. -The default configuration looks like this: - -
-immich.json - -```json -{ - "backup": { - "database": { - "cronExpression": "0 02 * * *", - "enabled": true, - "keepLastAmount": 14 - } - }, - "ffmpeg": { - "accel": "disabled", - "accelDecode": false, - "acceptedAudioCodecs": ["aac", "mp3", "libopus"], - "acceptedContainers": ["mov", "ogg", "webm"], - "acceptedVideoCodecs": ["h264"], - "bframes": -1, - "cqMode": "auto", - "crf": 23, - "gopSize": 0, - "maxBitrate": "0", - "preferredHwDevice": "auto", - "preset": "ultrafast", - "refs": 0, - "targetAudioCodec": "aac", - "targetResolution": "720", - "targetVideoCodec": "h264", - "temporalAQ": false, - "threads": 0, - "tonemap": "hable", - "transcode": "required", - "twoPass": false - }, - "image": { - "colorspace": "p3", - "extractEmbedded": false, - "fullsize": { - "enabled": false, - "format": "jpeg", - "quality": 80 - }, - "preview": { - "format": "jpeg", - "quality": 80, - "size": 1440 - }, - "thumbnail": { - "format": "webp", - "quality": 80, - "size": 250 - } - }, - "job": { - "backgroundTask": { - "concurrency": 5 - }, - "faceDetection": { - "concurrency": 2 - }, - "library": { - "concurrency": 5 - }, - "metadataExtraction": { - "concurrency": 5 - }, - "migration": { - "concurrency": 5 - }, - "notifications": { - "concurrency": 5 - }, - "ocr": { - "concurrency": 1 - }, - "search": { - "concurrency": 5 - }, - "sidecar": { - "concurrency": 5 - }, - "smartSearch": { - "concurrency": 2 - }, - "thumbnailGeneration": { - "concurrency": 3 - }, - "videoConversion": { - "concurrency": 1 - } - }, - "library": { - "scan": { - "cronExpression": "0 0 * * *", - "enabled": true - }, - "watch": { - "enabled": false - } - }, - "logging": { - "enabled": true, - "level": "log" - }, - "machineLearning": { - "availabilityChecks": { - "enabled": true, - "interval": 30000, - "timeout": 2000 - }, - "clip": { - "enabled": true, - "modelName": "ViT-B-32__openai" - }, - "duplicateDetection": { - "enabled": true, - "maxDistance": 0.01 - }, - "enabled": true, - "facialRecognition": { - "enabled": true, - "maxDistance": 0.5, - "minFaces": 3, - "minScore": 0.7, - "modelName": "buffalo_l" - }, - "ocr": { - "enabled": true, - "maxResolution": 736, - "minDetectionScore": 0.5, - "minRecognitionScore": 0.8, - "modelName": "PP-OCRv5_mobile" - }, - "urls": ["http://immich-machine-learning:3003"] - }, - "map": { - "darkStyle": "https://tiles.immich.cloud/v1/style/dark.json", - "enabled": true, - "lightStyle": "https://tiles.immich.cloud/v1/style/light.json" - }, - "metadata": { - "faces": { - "import": false - } - }, - "newVersionCheck": { - "enabled": true - }, - "nightlyTasks": { - "clusterNewFaces": true, - "databaseCleanup": true, - "generateMemories": true, - "missingThumbnails": true, - "startTime": "00:00", - "syncQuotaUsage": true - }, - "notifications": { - "smtp": { - "enabled": false, - "from": "", - "replyTo": "", - "transport": { - "host": "", - "ignoreCert": false, - "password": "", - "port": 587, - "secure": false, - "username": "" - } - } - }, - "oauth": { - "autoLaunch": false, - "autoRegister": true, - "buttonText": "Login with OAuth", - "clientId": "", - "clientSecret": "", - "defaultStorageQuota": null, - "enabled": false, - "issuerUrl": "", - "mobileOverrideEnabled": false, - "mobileRedirectUri": "", - "profileSigningAlgorithm": "none", - "roleClaim": "immich_role", - "scope": "openid email profile", - "signingAlgorithm": "RS256", - "storageLabelClaim": "preferred_username", - "storageQuotaClaim": "immich_quota", - "timeout": 30000, - "tokenEndpointAuthMethod": "client_secret_post" - }, - "passwordLogin": { - "enabled": true - }, - "reverseGeocoding": { - "enabled": true - }, - "server": { - "externalDomain": "", - "loginPageMessage": "", - "publicUsers": true - }, - "storageTemplate": { - "enabled": false, - "hashVerificationEnabled": true, - "template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}" - }, - "templates": { - "email": { - "albumInviteTemplate": "", - "albumUpdateTemplate": "", - "welcomeTemplate": "" - } - }, - "theme": { - "customCss": "" - }, - "trash": { - "days": 30, - "enabled": true - }, - "user": { - "deleteDelay": 7 - } -} -``` - -
- -:::tip -In Administration > Settings is a button to copy the current configuration to your clipboard. -So you can just grab it from there, paste it into a file and you're pretty much good to go. -::: - -### Step 2 - Specify the file location - -In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config. -For more information, refer to the [Environment Variables](/install/environment-variables.md) section. - -:::tip -YAML-formatted config files are also supported. -::: diff --git a/docs/docs/install/docker-compose.mdx b/docs/docs/install/docker-compose.mdx deleted file mode 100644 index 46b144eb4a..0000000000 --- a/docs/docs/install/docker-compose.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebar_position: 30 ---- - -# Docker Compose [Recommended] - -Docker Compose is the recommended method to run Immich in production. Below are the steps to deploy Immich with Docker Compose. - -import DockerComposeSteps from '/docs/partials/_docker-compose-install-steps.mdx'; - - - -:::info Docker version -If you get an error such as `unknown shorthand flag: 'd' in -d` or `open : permission denied`, you are probably running the wrong Docker version. (This happens, for example, with the docker.io package in Ubuntu 22.04.3 LTS.) You can correct the problem by following the complete [Docker Engine install](https://docs.docker.com/engine/install/) procedure for your distribution, crucially the "Uninstall old versions" and "Install using the apt/rpm repository" sections. These replace the distro's Docker packages with Docker's official ones. - -Note that the correct command really is `docker compose`, not `docker-compose`. If you try the latter on vanilla Ubuntu 22.04, it will fail in a different way: - -``` -The Compose file './docker-compose.yml' is invalid because: -'name' does not match any of the regexes: '^x-' -``` - -See the previous paragraph about installing from the official Docker repository. -::: - -:::info Health check start interval -If you get an error `can't set healthcheck.start_interval as feature require Docker Engine v25 or later`, it helps to comment out the line for `start_interval` in the `database` section of the `docker-compose.yml` file. -::: - -## Next Steps - -Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md). diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md deleted file mode 100644 index 07b37f0e41..0000000000 --- a/docs/docs/install/environment-variables.md +++ /dev/null @@ -1,231 +0,0 @@ ---- -sidebar_position: 90 ---- - -# Environment Variables - -:::caution - -To change environment variables, you must recreate the Immich containers. -Just restarting the containers does not replace the environment within the container! - -In order to recreate the container using docker compose, run `docker compose up -d`. -In most cases docker will recognize that the `.env` file has changed and recreate the affected containers. -If this does not work, try running `docker compose up -d --force-recreate`. - -::: - -## Docker Compose - -| Variable | Description | Default | Containers | -| :----------------- | :------------------------------ | :-----: | :----------------------- | -| `IMMICH_VERSION` | Image tags | `v2` | server, machine learning | -| `UPLOAD_LOCATION` | Host path for uploads | | server | -| `DB_DATA_LOCATION` | Host path for Postgres database | | database | - -:::tip -These environment variables are used by the `docker-compose.yml` file and do **NOT** affect the containers directly. -::: - -## General - -| Variable | Description | Default | Containers | Workers | -| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | -| `TZ` | Timezone | \*1 | server | microservices | -| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | -| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | -| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | -| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**\*2⚠️ | `/data` | server | api, microservices | -| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | -| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | -| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | -| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | -| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | -| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | -| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | -| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | -| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api | - -\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`. -`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution. - -\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead. - -## Workers - -| Variable | Description | Default | Containers | -| :----------------------- | :--------------------------------------------------------------------------------------------------- | :-----: | :--------- | -| `IMMICH_WORKERS_INCLUDE` | Only run these workers. | | server | -| `IMMICH_WORKERS_EXCLUDE` | Do not run these workers. Matches against default workers, or `IMMICH_WORKERS_INCLUDE` if specified. | | server | - -:::info -Information on the current workers can be found [here](/administration/jobs-workers). -::: - -## Ports - -| Variable | Description | Default | Containers | -| :------------ | :------------- | :----------------------------------------: | :----------------------- | -| `IMMICH_HOST` | Listening host | `0.0.0.0` | server, machine learning | -| `IMMICH_PORT` | Listening port | `2283` (server), `3003` (machine learning) | server, machine learning | - -## Database - -| Variable | Description | Default | Containers | -| :---------------------------------- | :------------------------------------------------------------------------------------- | :--------: | :----------------------------- | -| `DB_URL` | Database URL | | server | -| `DB_HOSTNAME` | Database host | `database` | server | -| `DB_PORT` | Database port | `5432` | server | -| `DB_USERNAME` | Database user | `postgres` | server, database\*1 | -| `DB_PASSWORD` | Database password | `postgres` | server, database\*1 | -| `DB_DATABASE_NAME` | Database name | `immich` | server, database\*1 | -| `DB_SSL_MODE` | Database SSL mode | | server | -| `DB_VECTOR_EXTENSION`\*2 | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server | -| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server | -| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])\*3 | `SSD` | database | - -\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`. - -\*2: If not provided, the appropriate extension to use is auto-detected at startup by introspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvecto.rs, pgvector. - -\*3: Uses either [`postgresql.ssd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.ssd.conf) or [`postgresql.hdd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.hdd.conf) which mainly controls the Postgres `effective_io_concurrency` setting to allow for concurrenct IO on SSDs and sequential IO on HDDs. - -:::info - -All `DB_` variables must be provided to all Immich workers, including `api` and `microservices`. - -`DB_URL` must be in the format `postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename`. -You can require SSL by adding `?sslmode=require` to the end of the `DB_URL` string, or require SSL and skip certificate verification by adding `?sslmode=require&uselibpqcompat=true`. This allows both immich and `pg_dumpall` (the utility used for database backups) to [properly connect](https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string#tcp-connections) to your database. - -When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD` and `DB_DATABASE_NAME` database variables are ignored. - -::: - -## Redis - -| Variable | Description | Default | Containers | -| :--------------- | :------------- | :-----: | :--------- | -| `REDIS_URL` | Redis URL | | server | -| `REDIS_SOCKET` | Redis socket | | server | -| `REDIS_HOSTNAME` | Redis host | `redis` | server | -| `REDIS_PORT` | Redis port | `6379` | server | -| `REDIS_USERNAME` | Redis username | | server | -| `REDIS_PASSWORD` | Redis password | | server | -| `REDIS_DBINDEX` | Redis DB index | `0` | server | - -:::info -All `REDIS_` variables must be provided to all Immich workers, including `api` and `microservices`. - -`REDIS_URL` must start with `ioredis://` and then include a `base64` encoded JSON string for the configuration. -More information can be found in the upstream [ioredis] documentation. - -When `REDIS_URL` or `REDIS_SOCKET` are defined, the `REDIS_HOSTNAME`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`, and `REDIS_DBINDEX` variables are ignored. -::: - -Redis (Sentinel) URL example JSON before encoding: - -
-JSON - -```json -{ - "sentinels": [ - { - "host": "redis-sentinel-node-0", - "port": 26379 - }, - { - "host": "redis-sentinel-node-1", - "port": 26379 - }, - { - "host": "redis-sentinel-node-2", - "port": 26379 - } - ], - "name": "redis-sentinel" -} -``` - -
- -## Machine Learning - -| Variable | Description | Default | Containers | -| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | -| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | -| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | -| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | -| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | -| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | -| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | -| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | -| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | -| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | -| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning | -| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning | -| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning | - -\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. - -\*2: Since each process duplicates models in memory, changing this is not recommended unless you have abundant memory to go around. - -\*3: For scenarios like HPA in K8S. https://github.com/immich-app/immich/discussions/12064 - -\*4: Using multiple GPUs requires `MACHINE_LEARNING_WORKERS` to be set greater than 1. A single device is assigned to each worker in round-robin priority. - -:::info - -While the `textual` model is the only one required for smart search, some users may experience slow first searches -due to backups triggering loading of the other models into memory, which blocks other requests until completed. -To avoid this, you can preload the other models (`visual`, `recognition`, and `detection`) if you have enough RAM to do so. - -Additional machine learning parameters can be tuned from the admin UI. - -::: - -## Prometheus - -| Variable | Description | Default | Containers | Workers | -| :------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :-----: | :--------- | :----------------- | -| `IMMICH_TELEMETRY_INCLUDE` | Collect these telemetries. List of `host`, `api`, `io`, `repo`, `job`. Note: You can also specify `all` to enable all | | server | api, microservices | -| `IMMICH_TELEMETRY_EXCLUDE` | Do not collect these telemetries. List of `host`, `api`, `io`, `repo`, `job` | | server | api, microservices | - -## Secrets - -The following variables support reading from files, either via [Systemd Credentials][systemd-creds] or [Docker secrets][docker-secrets] for additional security. - -To use any of these, either set `CREDENTIALS_DIRECTORY` to a directory that contains files whose name is the “regular variable” name, and whose content is the secret. If using Docker Secrets, setting `CREDENTIALS_DIRECTORY=/run/secrets` will cause all secrets present to be used. Alternatively, replace the regular variable with the equivalent `_FILE` environment variable as below. The value of the `_FILE` variable should be set to the path of a file containing the variable value. - -| Regular Variable | Equivalent Docker Secrets '\_FILE' Variable | -| :----------------- | :------------------------------------------ | -| `DB_HOSTNAME` | `DB_HOSTNAME_FILE`\*1 | -| `DB_DATABASE_NAME` | `DB_DATABASE_NAME_FILE`\*1 | -| `DB_USERNAME` | `DB_USERNAME_FILE`\*1 | -| `DB_PASSWORD` | `DB_PASSWORD_FILE`\*1 | -| `DB_URL` | `DB_URL_FILE`\*1 | -| `REDIS_PASSWORD` | `REDIS_PASSWORD_FILE`\*2 | - -\*1: See the [official documentation][docker-secrets-docs] for -details on how to use Docker Secrets in the Postgres image. - -\*2: See [this comment][docker-secrets-example] for an example of how -to use a Docker secret for the password in the Redis container. - -[tz-list]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List -[docker-secrets-example]: https://github.com/docker-library/redis/issues/46#issuecomment-335326234 -[docker-secrets-docs]: https://github.com/docker-library/docs/tree/master/postgres#docker-secrets -[docker-secrets]: https://docs.docker.com/engine/swarm/secrets/ -[ioredis]: https://ioredis.readthedocs.io/en/latest/README/#connect-to-redis -[systemd-creds]: https://systemd.io/CREDENTIALS/ diff --git a/docs/docs/install/img/dot-env.webp b/docs/docs/install/img/dot-env.webp deleted file mode 100644 index d8e34358a0..0000000000 Binary files a/docs/docs/install/img/dot-env.webp and /dev/null differ diff --git a/docs/docs/install/img/env-1.webp b/docs/docs/install/img/env-1.webp deleted file mode 100644 index bd2035136f..0000000000 Binary files a/docs/docs/install/img/env-1.webp and /dev/null differ diff --git a/docs/docs/install/img/env-2.webp b/docs/docs/install/img/env-2.webp deleted file mode 100644 index 86bbff0629..0000000000 Binary files a/docs/docs/install/img/env-2.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas00.webp b/docs/docs/install/img/truenas/truenas00.webp deleted file mode 100644 index 4f3db057b0..0000000000 Binary files a/docs/docs/install/img/truenas/truenas00.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas01.webp b/docs/docs/install/img/truenas/truenas01.webp deleted file mode 100644 index f299ef8e14..0000000000 Binary files a/docs/docs/install/img/truenas/truenas01.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas02.webp b/docs/docs/install/img/truenas/truenas02.webp deleted file mode 100644 index cbcac69082..0000000000 Binary files a/docs/docs/install/img/truenas/truenas02.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas03.webp b/docs/docs/install/img/truenas/truenas03.webp deleted file mode 100644 index 2870fb90b6..0000000000 Binary files a/docs/docs/install/img/truenas/truenas03.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas04.webp b/docs/docs/install/img/truenas/truenas04.webp deleted file mode 100644 index 73ca9ee727..0000000000 Binary files a/docs/docs/install/img/truenas/truenas04.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas05.webp b/docs/docs/install/img/truenas/truenas05.webp deleted file mode 100644 index a4dceee100..0000000000 Binary files a/docs/docs/install/img/truenas/truenas05.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas06.webp b/docs/docs/install/img/truenas/truenas06.webp deleted file mode 100644 index f13e04f424..0000000000 Binary files a/docs/docs/install/img/truenas/truenas06.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas07.webp b/docs/docs/install/img/truenas/truenas07.webp deleted file mode 100644 index 24cceb7faa..0000000000 Binary files a/docs/docs/install/img/truenas/truenas07.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas08.webp b/docs/docs/install/img/truenas/truenas08.webp deleted file mode 100644 index 6956eba48b..0000000000 Binary files a/docs/docs/install/img/truenas/truenas08.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas09.webp b/docs/docs/install/img/truenas/truenas09.webp deleted file mode 100644 index 5ed2ba0182..0000000000 Binary files a/docs/docs/install/img/truenas/truenas09.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas10.webp b/docs/docs/install/img/truenas/truenas10.webp deleted file mode 100644 index 21a8b2588a..0000000000 Binary files a/docs/docs/install/img/truenas/truenas10.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas11.webp b/docs/docs/install/img/truenas/truenas11.webp deleted file mode 100644 index 2fd202c6d2..0000000000 Binary files a/docs/docs/install/img/truenas/truenas11.webp and /dev/null differ diff --git a/docs/docs/install/img/truenas/truenas12.webp b/docs/docs/install/img/truenas/truenas12.webp deleted file mode 100644 index a44a2b33d6..0000000000 Binary files a/docs/docs/install/img/truenas/truenas12.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid01.webp b/docs/docs/install/img/unraid01.webp deleted file mode 100644 index 52013bef4c..0000000000 Binary files a/docs/docs/install/img/unraid01.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid02.webp b/docs/docs/install/img/unraid02.webp deleted file mode 100644 index a5a3b2c573..0000000000 Binary files a/docs/docs/install/img/unraid02.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid03.webp b/docs/docs/install/img/unraid03.webp deleted file mode 100644 index 6532905749..0000000000 Binary files a/docs/docs/install/img/unraid03.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid04.webp b/docs/docs/install/img/unraid04.webp deleted file mode 100644 index 3b6992ce05..0000000000 Binary files a/docs/docs/install/img/unraid04.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid05.webp b/docs/docs/install/img/unraid05.webp deleted file mode 100644 index 2df2f7852f..0000000000 Binary files a/docs/docs/install/img/unraid05.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid06.webp b/docs/docs/install/img/unraid06.webp deleted file mode 100644 index 97f42defec..0000000000 Binary files a/docs/docs/install/img/unraid06.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid07.webp b/docs/docs/install/img/unraid07.webp deleted file mode 100644 index 1aaf755d13..0000000000 Binary files a/docs/docs/install/img/unraid07.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid08.webp b/docs/docs/install/img/unraid08.webp deleted file mode 100644 index 5a22e0b1d5..0000000000 Binary files a/docs/docs/install/img/unraid08.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid09.webp b/docs/docs/install/img/unraid09.webp deleted file mode 100644 index 8b5a48b360..0000000000 Binary files a/docs/docs/install/img/unraid09.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid10.webp b/docs/docs/install/img/unraid10.webp deleted file mode 100644 index 65496c90ad..0000000000 Binary files a/docs/docs/install/img/unraid10.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid11.webp b/docs/docs/install/img/unraid11.webp deleted file mode 100644 index ce7671f1a3..0000000000 Binary files a/docs/docs/install/img/unraid11.webp and /dev/null differ diff --git a/docs/docs/install/img/unraid12.webp b/docs/docs/install/img/unraid12.webp deleted file mode 100644 index 1c9d68a370..0000000000 Binary files a/docs/docs/install/img/unraid12.webp and /dev/null differ diff --git a/docs/docs/install/kubernetes.md b/docs/docs/install/kubernetes.md deleted file mode 100644 index 34062c1286..0000000000 --- a/docs/docs/install/kubernetes.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -sidebar_position: 40 ---- - -# Kubernetes - -You can deploy Immich on Kubernetes using [the official Helm chart](https://github.com/immich-app/immich-charts/blob/main/README.md). - -You can view some [examples](https://kubesearch.dev/#/immich) of how other people run Immich on Kubernetes, using the official chart or otherwise. - -:::caution DNS in Alpine containers -Immich makes use of Alpine container images. These can encounter [a DNS resolution bug](https://stackoverflow.com/a/65593511) on Kubernetes clusters if the host -nodes have a search domain set, like: - -``` -$ cat /etc/resolv.conf -search home.lan -nameserver 192.168.1.1 -``` - -::: diff --git a/docs/docs/install/one-click.md b/docs/docs/install/one-click.md deleted file mode 100644 index 53fcb20d21..0000000000 --- a/docs/docs/install/one-click.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebar_position: 65 ---- - -# One-Click [Cloud Service] - -:::note -This version of Immich is provided via cloud service providers' one-click marketplaces. Hosting costs are set by the cloud service providers. -Support for these are provided by the individual cloud service providers. - -**Please report issues to the corresponding [Github Repository][github].** -::: - -## Installation - -Go to the provider's marketplace and choose Immich, then follow the provided instructions. - -## One-Click Immich marketplace providers - -### DigitalOcean - -https://marketplace.digitalocean.com/apps/immich - -### Vultr - -https://www.vultr.com/marketplace/apps/immich - -## Issues - -For issues, open an issue on the associated [GitHub Repository][github]. - -[github]: https://github.com/immich-app/immich/ diff --git a/docs/docs/install/portainer.md b/docs/docs/install/portainer.md deleted file mode 100644 index 07fd255292..0000000000 --- a/docs/docs/install/portainer.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -sidebar_position: 50 ---- - -# Portainer - -Install Immich using Portainer's Stack feature. - -1. Go to "**Stacks**" in the left sidebar. -2. Click on "**Add stack**". -3. Give the stack a name (i.e. immich), and select "**Web Editor**" as the build method. -4. Copy the content of the `docker-compose.yml` file from the [GitHub repository](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml). -5. Replace `.env` with `stack.env` for all containers that need to use environment variables in the web editor. - - - -8. Click on "**Advanced Mode**" in the **Environment Variables** section. - - - -9. Copy the content of the `example.env` file from the [GitHub repository](https://github.com/immich-app/immich/releases/latest/download/example.env) and paste into the editor. -10. Switch back to "**Simple Mode**". - - - -- Change the default `DB_PASSWORD`, and add custom database connection information if necessary. -- Change `DB_DATA_LOCATION` to a folder (absolute path) where the database will be saved to disk. -- Change `UPLOAD_LOCATION` to a folder (absolute path) where media (uploaded and generated) will be stored. - -11. Click on "**Deploy the stack**". - -:::tip -For more information on how to use the application, please refer to the [Post Installation](/install/post-install.mdx) guide. -::: diff --git a/docs/docs/install/post-install.mdx b/docs/docs/install/post-install.mdx deleted file mode 100644 index b30e91f3cd..0000000000 --- a/docs/docs/install/post-install.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -sidebar_position: 90 ---- - -import RegisterAdminUser from '/docs/partials/_register-admin.md'; -import UserCreate from '/docs/partials/_user-create.md'; -import StorageTemplate from '/docs/partials/_storage-template.md'; -import MobileAppDownload from '/docs/partials/_mobile-app-download.md'; -import MobileAppLogin from '/docs/partials/_mobile-app-login.md'; -import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; -import ServerBackup from '/docs/partials/_server-backup.md'; - -# Post installation steps - -A list of common steps to take after installing Immich include: - -## Step 1 - Register the admin user - - - -## Step 2 - Create a new user (optional) - - - -## Step 3 - Update the storage template - - - -## Step 4 - Download the Mobile App - - - -## Step 5 - Login to the Mobile App - - - -## Step 6 - Upload Your Library - - - -## Step 7 - Setup Server Backups - - - -## Setting up optional features - -- [External Libraries](/features/libraries.md): Adding your existing photo library to Immich -- [Hardware Transcoding](/features/hardware-transcoding.md): Speeding up video transcoding -- [Hardware-Accelerated Machine Learning](/features/ml-hardware-acceleration.md): Speeding up various machine learning tasks in Immich diff --git a/docs/docs/install/requirements.md b/docs/docs/install/requirements.md deleted file mode 100644 index ee5db45c9a..0000000000 --- a/docs/docs/install/requirements.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -sidebar_position: 10 ---- - -# Requirements - -Hardware and software requirements for Immich: - -## Hardware - -- **OS**: Recommended Linux or \*nix operating system (Ubuntu, Debian, etc). - - Non-Linux OSes tend to provide a poor Docker experience and are strongly discouraged. - Our ability to assist with setup or troubleshooting on non-Linux OSes will be severely reduced. - If you still want to try to use a non-Linux OS, you can set it up as follows: - - Windows: [Docker Desktop on Windows](https://docs.docker.com/desktop/install/windows-install/) or [WSL 2](https://docs.docker.com/desktop/wsl/). - - macOS: [Docker Desktop on Mac](https://docs.docker.com/desktop/install/mac-install/). - - Immich runs well in a virtualized environment when running in a full virtual machine. - The use of Docker in LXC containers is [not recommended](https://pve.proxmox.com/wiki/Linux_Container), but may be possible for advanced users. - If you have issues, we recommend that you switch to a supported VM deployment. -- **RAM**: Minimum 6GB, recommended 8GB. -- **CPU**: Minimum 2 cores, recommended 4 cores. -- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions. - - The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average. - -:::note RAM requirements -For a smooth experience, especially during asset upload, Immich requires at least 6GB of RAM. -For systems with only 4GB of RAM, Immich can be run with machine learning features disabled. -::: - -:::tip Postgres setup -Good performance and a stable connection to the Postgres database is critical to a smooth Immich experience. -The Postgres database files are typically between 1-3 GB in size. -For this reason, the Postgres database (`DB_DATA_LOCATION`) should ideally use local SSD storage, and never a network share of any kind. -Additionally, if Docker resource limits are used, the Postgres database requires at least 2GB of RAM. -Windows users may run into issues with non-Unix-compatible filesystems, see below for more details. -::: - -## Software - -Immich requires [**Docker**](https://docs.docker.com/get-started/get-docker/) with the **Docker Compose plugin**: - -- **Docker Engine**: This CLI variant is designed for Linux servers (or Windows via WSL2). -- **Docker Desktop**: This GUI variant is **not recommended** for Linux, but is available for Windows or macOS. - -The Compose plugin will be installed by both Docker Engine and Desktop by following the linked installation guides; it can also be [separately installed](https://docs.docker.com/compose/install/). - -:::note -Immich requires the command `docker compose`; the similarly named `docker-compose` is [deprecated](https://docs.docker.com/compose/migrate/) and is no longer supported by Immich. -::: - -### Special requirements for Windows users - -
-Database storage on Windows systems - -The Immich Postgres database (`DB_DATA_LOCATION`) must be located on a filesystem that supports user/group -ownership and permissions (EXT2/3/4, ZFS, APFS, BTRFS, XFS, etc.). It will not work on any filesystem formatted in NTFS or ex/FAT/32. -It will not work in WSL (Windows Subsystem for Linux) when using a mounted host directory (commonly under `/mnt`). -If this is an issue, you can change the bind mount to a Docker volume instead as follows: - -Make the following change to `.env`: - -```diff -- DB_DATA_LOCATION=./postgres -+ DB_DATA_LOCATION=pgdata -``` - -Add the following line to the bottom of `docker-compose.yml`: - -```diff -volumes: - model-cache: -+ pgdata: -``` - -
diff --git a/docs/docs/install/script.md b/docs/docs/install/script.md deleted file mode 100644 index ce05dc82d9..0000000000 --- a/docs/docs/install/script.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -sidebar_position: 20 ---- - -# Install script [Experimental] - -:::caution -This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/install/docker-compose.mdx). -::: - -## Requirements - -Follow the [requirements page](/install/requirements) to get started. - -The install script only supports Linux operating systems and requires Docker to be already installed on the system. - -## Steps - -In the shell, from a directory of your choice, run the following command: - -```bash -curl -o- https://raw.githubusercontent.com/immich-app/immich/main/install.sh | bash -``` - -The script will perform the following actions: - -1. Download [docker-compose.yml](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml), and the [.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file from the main branch of the [repository](https://github.com/immich-app/immich). -2. Start the containers. - -The web application and mobile app will be available at `http://:2283` - -The directory which is used to store the library files is `./immich-app` relative to the current directory. - -:::tip -For common next steps, see [Post Install Steps](/install/post-install.mdx). -::: diff --git a/docs/docs/install/synology.md b/docs/docs/install/synology.md deleted file mode 100644 index 3e5b780db2..0000000000 --- a/docs/docs/install/synology.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 85 ---- - -# Synology [Community] - -:::note -This is a community contribution and not officially supported by the Immich team, but included here for convenience. - -Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/). - -**Please report app issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).** -::: - -Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager. - -## Step 1 - Download the required files - -Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`. - -Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`. - -When you're all done, you should have the following: - -- `./docker/immich-app/postgres` -- `./docker/immich-app/library` - -Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`. Note: If you plan to use the Synology Text editor to edit the `.env` file on the NAS within File Station, you will need to rename it to a temporary name (e.g. `example.txt`) in order to see 'Open with Text Editor' in the file context menu. Once saved, rename it back to `.env`. - -## Step 2 - Populate the .env file with custom values - -Follow [Step 2 in Docker Compose](/install/docker-compose#step-2---populate-the-env-file-with-custom-values) for instructions on customizing the `.env` file, and then return back to this guide to continue. - -## Step 3 - Create a new project in Container Manager - -Open Container Manager, and select the "**Project**" action on the left navigation bar and then click "**Create**". -![Create project](../../static/img/synology-container-manager-create-project.png) - -In the settings of your new project, set "**Project name**" to a name you'll remember, such as _immich-app_. When setting the "**Path**", select the `./docker/immich-app` directory you created earlier. Doing so will prompt a message to use the existing `docker-compose.yml` already present in the directory for your project. Click "**OK**" to continue. - -![Set path](../../static/img/synology-container-manager-set-path.png) - -The following screen will give you the option to further customize your `docker-compose.yml` file. Take note of `DB_STORAGE_TYPE: 'HDD'` and uncomment if applicable for your Synology setup. - -![DB storage](../../static/img/synology-container-manager-customize-docker-compose.png) - -Skip the section asking to set-up a portal for Web Station, and then complete the wizard which will build and start the containers for your project. - -Once your containers are successfully running, navigate to the "**Container**" section of Container Manager, right-click on the "**immich-server**" container, and choose the "**Details**". - -Scroll to the bottom of the "**Details**" section and find the `IP Address` listed in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**. - -![Container details](../../static/img/synology-container-manager-container-details.png) - -## Step 4 - Configure Firewall Settings - -Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS. - -Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**" - -![Firewall rules](../../static/img/synology-firewall-rules.png) - -Click "**Edit Rules**" and add the following firewall rules: - -- Add a "**Source IP**" rule for the IP address of your container that you obtained in Step 3 above - -![IP address rule](../../static/img/synology-ipaddress-firewall-rule.png) - -- Add a "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283` - -![Custom port rule](../../static/img/synology-custom-port-firewall-rule.png) - -## Next Steps - -Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md). - -
- Updating Immich using Container Manager -Check the post installation and upgrade instructions at the links above before proceeding with this section. - -## Step 1. Backup - -Ensure your photos and videos are backed up. Your `.env` settings will define where they are stored. There is no need to delete any files or folders within the `docker` folder when doing a release upgrade unless instructed in the release notes. - -## Step 2. Check release notes - -Always check the [release notes](https://github.com/immich-app/immich/releases) before proceeding with an update! - -## Step 3. Stop containers & clean up - -Open **Container Manager**. Select **Project** then your Immich app - -![Select project](../../static/img/synology-select-proj.png) - -Select **Stop** - -![Stop project](../../static/img/synology-project-stop.png) - -Select **Action** then **Clean**. This removes the containers. - -![Clean project](../../static/img/synology-action-clean.png) - -Go to **Image** and select **Remove Unused Images**. - -![Remove unused](../../static/img/synology-remove-unused.png) - -## Step 4. Build - -Go to **Project**, select **Action** then **Build**. This will download, unpack, install and start the containers. - -![Build](../../static/img/synology-build.png) - -## Step 5. Update firewall rule - -The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address. - -Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address. -![Container IP](../../static/img/synology-container-ip.png) - -Go to Synology **Control Panel**. Select **Security** and **Firewall**. - -![Firewall](../../static/img/synology-fw-rules.png) - -In this example, the IP addresses mismatch and the firewall rule needs to be edited to match above. - -![Edit IP](../../static/img/synology-fw-ipedit.png) - -
diff --git a/docs/docs/install/truenas.md b/docs/docs/install/truenas.md deleted file mode 100644 index 9135b72fe6..0000000000 --- a/docs/docs/install/truenas.md +++ /dev/null @@ -1,482 +0,0 @@ ---- -sidebar_position: 80 ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# TrueNAS [Community] - -:::note -This is a community contribution and not officially supported by the Immich team, but included here for convenience. - -Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/). - -**Please report app issues to the corresponding [GitHub Repository](https://github.com/truenas/apps/tree/master/trains/community/immich).** -::: - -:::warning -This guide covers the installation of Immich on TrueNAS Community Edition 24.10.2.2 (Electric Eel) and later. - -We recommend keeping TrueNAS Community Edition and Immich relatively up to date with the latest versions to avoid any issues. - -If you are using an older version of TrueNAS, we ask that you upgrade to the latest version before installing Immich. Check the [TrueNAS Community Edition Release Notes](https://www.truenas.com/docs/softwarereleases/) for more information on breaking changes, new features, and how to upgrade your system. -::: - -Immich can easily be installed on TrueNAS Community Edition via the **Community** train application. -Consider reviewing the TrueNAS [Apps resources](https://apps.truenas.com/getting-started/) if you have not previously configured applications on your system. - -## First Steps - -### Setting up Storage Datasets - -Before beginning app installation, [create the datasets](https://www.truenas.com/docs/scale/scaletutorials/storage/datasets/datasetsscale/) to use in the **Storage Configuration** section during installation. - -In TrueNAS, Immich requires 2 datasets for the application to function correctly: `data` and `pgData`. You can set the datasets to any names to match your naming conventions or preferences. -You can organize these as one parent with two child datasets, for example `/mnt/tank/immich/data` and `/mnt/tank/immich/pgData`. - - - -:::info Datasets Permissions - -The **pgData** dataset must be owned by the user `netdata` (UID 999) for Postgres to start. - -The `data` dataset must have given the **_modify_** permission to the user who will run Immich. - -Since TrueNAS Community Edition 24.10.2.2 and later, Immich can be run as any user and group, the default user being `apps` (UID 568) and the default group being `apps` (GID 568). This user, either `apps` or another user you choose, must have **_modify_** permissions on the **data** dataset. - -For an easy setup: - -- Create the parent dataset `immich` keeping the default **Generic** preset. -- Select `Dataset Preset` **Apps** instead of **Generic** when creating the `data` dataset. This will automatically give the correct permissions to the dataset. If you want to use another user for Immich, you can keep the **Generic** preset, but you will need to give the **_modify_** permission to that other user. -- For the `pgData` dataset, you can keep the default preset **Generic** as permissions can be set during the installation of the Immich app (See [Storage Configuration](#storage-configuration) section). - ::: - -:::tip -To improve performance, Immich recommends using SSDs for the database. If you have a pool made of SSDs, you can create the `pgData` dataset on that pool. - -Thumbnails can also be stored on the SSDs for faster access. This is an advanced option and not required for Immich to run. More information on how you can use multiple datasets to manage Immich storage in a finer-grained manner can be found in the [Additional Storage: Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section below. -::: - -:::warning -If you just created the datasets using the **Apps** preset, you can skip this warning section. - -If the **data** dataset uses ACL it must have [ACL mode](https://www.truenas.com/docs/scale/scaletutorials/datasets/permissionsscale/) set to `Passthrough` if you plan on using a [storage template](/administration/storage-template.mdx) and the dataset is configured for network sharing (its ACL type is set to `SMB/NFSv4`). When the template is applied and files need to be moved from **upload** to **library** (internal folder created by Immich within the **data** dataset), Immich performs `chmod` internally and must be allowed to execute the command. [More info.](https://github.com/immich-app/immich/pull/13017) - -To change or verify the ACL mode, go to the **Datasets** screen, select the **library** dataset, click on the **Edit** button next to **Dataset Details**, then click on the **Advanced Options** tab, scroll down to the **ACL Mode** section, and select `Passthrough` from the dropdown menu. Click **Save** to apply the changes. If the option is greyed out, set the **ACL Type** to `SMB/NFSv4` first, then you can change the **ACL Mode** to `Passthrough`. -::: - -## Installing the Immich Application - -To install the **Immich** application, go to **Apps**, click **Discover Apps**, and either begin typing Immich into the search field or scroll down to locate the **Immich** application widget. - -
- -Click on the widget to open the **Immich** application details screen. - - -
- -
- -Click **Install** to open the Immich application configuration screen. - - -
- -Application configuration settings are presented in several sections, each explained below. -To find specific fields, click in the **Search Input Fields** search field, scroll down to a particular section, or click on the section heading on the navigation area in the upper-right corner. - -### Application Name and Version - - - -Keep the default value or enter a name in the **Application Name** field. -Change it if you’re deploying a second instance. - -Immich version within TrueNAS catalog (Different from Immich release version). - -### Immich Configuration - - - -The **Timezone** is set to the system default, which usually matches your local timezone. You can change it to another timezone if you prefer. - -**Enable Machine Learning** is enabled by default. It allows Immich to use machine learning features such as face recognition, image search, and smart duplicate detection. Untick this option if you do not want to use these features. - -Select the **Machine Learning Image Type** based on the hardware you have. More details here: [Hardware-Accelerated Machine Learning](/features/ml-hardware-acceleration.md) - -**Database Password** should be set to a custom value using only the characters `A-Za-z0-9`. This password is used to secure the Postgres database. - -**Redis Password** should be set to a custom value using only the characters `A-Za-z0-9`. Preferably, use a different password from the database password. - -Keep the **Log Level** to the default `Log` value. - -Leave **Hugging Face Endpoint** blank. (This is used to download ML models from a different source.) - -Set **Database Storage Type** to the type of storage (**HDD** or **SSD**) that the pool where the **pgData** dataset is located uses. - -**Additional Environment Variables** can be left blank. - -
-Advanced users: Adding Environment Variables - -Environment variables can be set by clicking the **Add** button and filling in the **Name** and **Value** fields. - - - -These are used to add custom configuration options or to enable specific features. -More information on available environment variables can be found in the **[environment variables documentation](/install/environment-variables/)**. - -:::info -Some environment variables are not available for the TrueNAS Community Edition app as they can be configured through GUI options in the [Edit Immich screen](#edit-app-settings). - -Some examples are: `IMMICH_VERSION`, `UPLOAD_LOCATION`, `DB_DATA_LOCATION`, `TZ`, `IMMICH_LOG_LEVEL`, `DB_PASSWORD`, `REDIS_PASSWORD`. -::: - -
- -### User and Group Configuration - -Application in TrueNAS runs as a specific user and group. Immich uses the default user `apps` (UID 568) and the default group `apps` (GID 568). - - - -- **User ID**: Keep the default value `apps` (UID 568) or define a different one if needed. - -- **Group ID**: Keep the default value `apps` (GID 568) or define a different one if needed. - -:::warning -If you change the user or group, make sure that the datasets you created for Immich data storage have the correct permissions set for that user and group as specified in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above. -::: - -### Network Configuration - - - -- **Port Bind Mode**: This lets you expose the port to the host system, allowing you to access Immich from outside the TrueNAS system. Keep the default **_Publish port on the host for external access_** value unless you have a specific reason to change it. - -- **Port Number**: Keep the default port `30041` or enter a custom port number. - -- **Host IPs**: Leave the default blank value. - -### Storage Configuration - -:::danger Default Settings (Not recommended) -The default setting for datasets is **ixVolume (dataset created automatically by the system)**. This is not recommended as this results in your data being harder to access manually and can result in data loss if you delete the immich app. It is also harder to manage snapshots and replication tasks. It is recommended to use the **Host Path (Path that already exists on the system)** option instead. -::: - -The storage configuration section allows you to set up the storage locations for Immich data. You can select the datasets created in the previous step. - - - -For the Data Storage, select **Host Path (Path that already exists on the system)** and then select the dataset you created for Immich data storage, for example, `data`. - -The Machine Learning cache can be left with default _Temporary_ - -For the Postgres Data Storage, select **Host Path (Path that already exists on the system)** and then select the dataset you created for Postgres data storage, for example, `pgData`. - -:::info -**Postgres Data Storage** -Once **Host Path** is selected, a checkbox appears with **_Automatic Permissions_**. If you have not set the ownership of the **pgData** dataset to `netdata` (UID 999), tick this box as it will set the user ownership to `netdata` (UID 999) and the group ownership to `docker` (GID 999) automatically. If you have set the ownership of the **pgData** dataset to `netdata` (UID 999), you can leave this box unticked. -::: - -### Additional Storage (Advanced Users) - -
-External Libraries - -:::danger Advanced Users Only -This feature should only be used by advanced users. If this is your first time installing Immich, then DO NOT mount an external library until you have a working setup. -::: - - - -You may configure [external libraries](/features/libraries) by mounting them using **Additional Storage**. - -The dataset that contains your external library files must at least give **read** access to the user running Immich (Default: `apps` (UID 568), `apps` (GID 568)). -If you want to be able to delete files or edit metadata in the external library using Immich, you will need to give the **modify** permission to the user running Immich. - -- **Mount Path** is the location you will need to copy and paste into the external library settings within Immich. -- **Host Path** is the location on the TrueNAS Community Edition server where your external library is located. -- **Read Only** is a checkbox that you can tick if you want to prevent Immich from modifying the files in the external library. This is useful if you want to use Immich to view and search your external library without modifying it. - -:::warning -Each mount path MUST be something unique and should NOT be your library or upload location or a Linux directory like `/lib`. - -A general recommendation is to mount any external libraries to a path beginning with `/mnt` or `/media` followed by a unique name, such as `/mnt/external-libraries` or `/media/my-external-libraries`. If you plan to mount multiple external libraries, you can use paths like `/mnt/external-libraries/library1`, `/mnt/external-libraries/library2`, etc. -::: - -
- -
-Multiple Datasets for Immich Storage - -:::danger Advanced Users Only -This feature should only be used by advanced users. -::: - -Immich can use multiple datasets for its storage, allowing you to manage your data more granularly, similar to the old storage configuration. This is useful if you want to separate your data into different datasets for performance or organizational reasons. There is a general guide for this [here](/guides/custom-locations), but read on for the TrueNAS guide. - -Each additional dataset has to give the permission **_modify_** to the user who will run Immich (Default: `apps` (UID 568), `apps` (GID 568)) -As described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, you have to create the datasets with the **Apps** preset to ensure the correct permissions are set, or you can set the permissions manually after creating the datasets. - -Immich uses 6 folders for its storage: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. You can create a dataset for each of these folders or only for some of them. - -To mount these datasets: - -1. Add an **Additional Storage** entry for each dataset you want to use. -2. Select **Type** as **Host Path (Path that already exists on the system)**. -3. Enter the **Mount Path** with `/data/`. The `` is the name of the folder you want to mount, for example, `library`, `upload`, `thumbs`, `profile`, `encoded-video`, or `backups`. - :::danger Important - You have to write the full path, including `/data/`, as Immich expects the data to be in that location. - If you do not include this path, Immich will not be able to find the data and will not write the data to the location you specified. - ::: -4. Select the **Host Path** as the dataset you created for that folder, for example, `/mnt/tank/immich/library`, `/mnt/tank/immich/upload`, etc. - - - -
- - - -### Resources Configuration - - - -- **CPU**: Depending on your system resources, you can keep the default value of `2` threads or specify a different number. Immich recommends at least `8` threads. - -- **Memory**: Limit in MB of RAM. Immich recommends at least 6000 MB (6GB). If you selected **Enable Machine Learning** in **Immich Configuration**, you should probably set this above 8000 MB. - -Both **CPU** and **Memory** are limits, not reservations. This means that Immich can use up to the specified amount of CPU threads and RAM, but it will not reserve that amount of resources at all times. The system will allocate resources as needed, and Immich will use less than the specified amount most of the time. - -- Enable **GPU Configuration** options if you have a GPU or CPU with integrated graphics that you will use for [Hardware Transcoding](/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/features/ml-hardware-acceleration.md). - -The process for NVIDIA GPU passthrough requires additional steps. -More details here: [GPU Passthrough Docs for TrueNAS Apps](https://apps.truenas.com/managing-apps/installing-apps/#gpu-passthrough) - -### Install - -Finally, click **Install**. -The system opens the **Installed Applications** screen with the Immich app in the **Deploying** state. -When the installation completes, it changes to **Running**. - - - -Click **Web Portal** on the **Application Info** widget, or go to the URL `http://:30041` in your web browser to open the Immich web interface. This will show you the onboarding process to set up your first user account, which will be an administrator account. - -After that, you can start using Immich to upload and manage your photos and videos. - -:::tip -For more information on how to use the application once installed, please refer to the [Post Install](/install/post-install.mdx) guide. -::: - -## Edit App Settings - -- Go to the **Installed Applications** screen and select Immich from the list of installed applications. -- Click **Edit** on the **Application Info** widget to open the **Edit Immich** screen. -- Change any settings you would like to change. - - The settings on the edit screen are the same as on the install screen. -- Click **Update** at the very bottom of the page to save changes. - - TrueNAS automatically updates, recreates, and redeploys the Immich container with the updated settings. - -## Updating the App - -:::danger -Make sure to read the general [upgrade instructions](/install/upgrading.md). -::: - -When updates become available, TrueNAS alerts and provides easy updates. -To update the app to the latest version: - -- Go to the **Installed Applications** screen and select Immich from the list of installed applications. -- Click **Update** on the **Application Info** widget from the **Installed Applications** screen. -- This opens an update window with some options - - You may select an Image update too. - - You may view the Changelog. -- Click **Upgrade** to begin the process and open a counter dialog that shows the upgrade progress. - - When complete, the update badge and buttons disappear and the application Update state on the Installed screen changes from Update Available to Up to date. - -## Migration - -:::danger -Perform a backup of your Immich data before proceeding with the migration steps below. This is crucial to prevent any data loss if something goes wrong during the migration process. - -The migration should also be performed when the Immich app is not running to ensure no data is being written while you are copying the data. -::: - -### Migration from Old Storage Configuration - -There are two ways to migrate from the old storage configuration to the new one, depending on whether you want to keep the old multiple datasets or if you want to move to a double dataset configuration with a single dataset for Immich data storage and a single dataset for Postgres data storage. - -:::note Old TrueNAS Versions Permissions -If you were using an older version of TrueNAS (before 24.10.2.2), the datasets, except the one for **pgData** had only to be owned by the `root` user (UID 0). You might have to add the **modify** permission to the `apps` user (UID 568) or the user you want to run Immich as, to all of them, except **pgData**. The steps to add or change ACL permissions are described in the [TrueNAS documentation](https://www.truenas.com/docs/scale/scaletutorials/datasets/permissionsscale/). -::: - - - - -To migrate from the old storage configuration to the new one, you will need to create a new dataset for the Immich data storage and copy the data from the old datasets to the new ones. The steps are as follows: - -1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are copying the data. -2. **Create a new dataset** for the Immich data storage, for example, `data`. As described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, create the dataset with the **Apps** preset to ensure the correct permissions are set. -3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples: - -```bash -sudo rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/ -sudo rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/ -sudo rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/ -sudo rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/ -sudo rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/ -sudo rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/ -``` - -Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset. - -:::tip -If you were using **ixVolume (dataset created automatically by the system)** for some of Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example: - -```bash -sudo rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/ -``` - -If you also were storing your files in the **ixVolume**, the **_upload_** folder is named `uploads` instead of `upload`, so the command to run should be: - -```bash -sudo rsync -av /mnt/.ix-apps/app_mounts/immich/uploads/ /mnt/tank/immich/data/upload/ -``` - -This means that depending on your old storage configuration, you might have to use a mix of paths in the `rsync` commands above. - -If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data: - -```bash -sudo rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/ -``` - -::: - -:::warning -Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example: - -```bash -sudo rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/ -``` - -Replace `library` with the name of the folder where you are copying the file. -::: - -4. **Update the permissions** as the permissions of the data that have been copied has been preserved, to ensure that the `apps` user (UID 568) has the correct permissions on all the copied data. If you just created the dataset with the **Apps** preset, from the TrueNAS web interface, go to the **Datasets** screen, select the **data** dataset, click on the **Edit** button next to **Permissions**, tick the "Apply permissions recursively" checkbox, and click **Save**. This will apply the correct permissions to all the copied data. -5. **Update the Immich app** to use the new dataset: - - Go to the **Installed Applications** screen and select Immich from the list of installed applications. - - Click **Edit** on the **Application Info** widget. - - In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox. - - For the **Data Storage**, select **Host Path (Path that already exists on the system)** and then select the new dataset you created for Immich data storage, for example, `data`. - - For the **Postgres Data Storage**, verify that it is still set to the dataset you created for Postgres data storage, for example, `pgData`. - - Click **Update** at the bottom of the page to save changes. - -6. **Start the Immich app** from the TrueNAS web interface. - -This will recreate the Immich container with the new storage configuration and start the app. - -If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface. - -:::tip -If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands: - -```bash -sudo rm -r /mnt/.ix-apps/app_mounts/immich/* -``` - -::: - - - - -To migrate from the old storage configuration to the new one without creating new datasets. - -1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app. -2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have **_modify_** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with **_modify_** permissions. -3. **Update the Immich app** to use the existing datasets: - - Go to the **Installed Applications** screen and select Immich from the list of installed applications. - - Click **Edit** on the **Application Info** widget. - - In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox. - - For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`. - - For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`. - - Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings: - - **Type**: `Host Path (Path that already exists on the system)` - - **Mount Path**: `/data/` (e.g. `/data/library`) - - **Host Path**: `/mnt//` (e.g. `/mnt/tank/immich/library`) - :::danger Ensure using the correct paths names - Make sure to replace `` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `` and `` with the actual names of your pool and dataset. - ::: - - **Read Only**: Keep it unticked as Immich needs to write to these datasets. - - Click **Update** at the bottom of the page to save changes. -4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible. - - - diff --git a/docs/docs/install/unraid.md b/docs/docs/install/unraid.md deleted file mode 100644 index ca7263a1e8..0000000000 --- a/docs/docs/install/unraid.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -sidebar_position: 60 ---- - -# Unraid - -Immich can easily be installed and updated on Unraid via: - -1. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps -2. Community made template on the Unraid Community Apps - -## Community Applications Template - -:::info - -- The Unraid template uses a community made image and is not officially supported by Immich - -::: - -In order to install Immich from the Unraid CA, you will need an existing Redis and PostgreSQL 14 container, If you do not already have Redis or PostgreSQL you can install them from the Unraid CA, just make sure you choose PostgreSQL **14**. - -Once you have Redis and PostgreSQL running, search for Immich on the Unraid CA, choose either of the templates listed and fill out the example variables. - -For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup) - -## Docker-Compose Method (Official) - -:::info - -- Guide was written using Unraid v6.12.10. -- Requires you to have installed the plugin: [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) -- An Unraid share created for your images -- There has been a [report](https://forums.unraid.net/topic/130006-errortraps-traps-node27707-trap-invalid-opcode-ip14fcfc8d03c0-sp7fff32889dd8-more/#comment-1189395) of this not working if your Unraid server doesn't support AVX _(e.g. using a T610)_ - -::: - -## Installation Steps - -1. Go to "**Plugins**" and click on "**Compose.Manager**" -2. Click "**Add New Stack**" and when prompted for a label enter "**Immich**" - - - -3. Select the cogwheel ⚙️ next to Immich and click "**Edit Stack**" -4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default. Note that Unraid v6.12.10 uses version 24.0.9 of the Docker Engine, which does not support healthcheck `start_interval` as defined in the `database` service of the Docker compose file (version 25 or higher is needed). This parameter defines an initial waiting period before starting health checks, to give the container time to start up. Commenting out the `start_interval` and `start_period` parameters will allow the containers to start up normally. The only downside to this is that the database container will not receive an initial health check until `interval` time has passed. - -
- Using an existing Postgres container? Click me! Otherwise proceed to step 5. -
    -
  • Comment out the database service
  • - Comment out database service in the compose file -
  • Comment out the database dependency for each service (example in screenshot below only shows 2 of the services - ensure you do this for all services)
  • - Comment out every reference to the database service in the compose file -
  • Comment out the volumes
  • - Comment out database volume -
-
- -5. Click "**Save Changes**", you will be prompted to edit stack UI labels, just leave this blank and click "**Ok**" -6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**" -7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following: - - `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION` - - `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata/postgresql/data`). This uses the `appdata` share. Do also create the `postgresql` folder, by running `mkdir /mnt/user/{share_location}/postgresql/data`. If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting. - - Absolute location of where you want immich images stored - -
- Using an existing Postgres container? Click me! Otherwise proceed to step 8. -

Update the following database variables as relevant to your Postgres container:

-
    -
  • DB_HOSTNAME
  • -
  • DB_USERNAME
  • -
  • DB_PASSWORD
  • -
  • DB_DATABASE_NAME
  • -
  • DB_PORT
  • -
-
- -8. Click "**Save Changes**" followed by "**Compose Up**" and Unraid will begin to create the Immich containers in a popup window. Once complete you will see a message on the popup window stating _"Connection Closed"_. Click "**Done**" and go to the Unraid "**Docker**" page - - > Note: This can take several minutes depending on your Internet speed and Unraid hardware - -9. Once on the Docker page you will see several Immich containers, one of them will be labelled `immich_server` and will have a port mapping. Visit the `IP:PORT` displayed in your web browser and you should see the Immich admin setup page. - - - -
- Using the FolderView plugin for organizing your Docker containers? Click me! Otherwise you're complete! -

If you are using the FolderView plugin go the Docker tab and select "New Folder".
Label it "Immich" and use this URL as the logo: https://raw.githubusercontent.com/immich-app/immich/main/design/immich-logo.png
Then simply select all the Immich related containers before clicking "Submit"

- Go to Docker Tab and visit the address listed next to immich-web - Go to Docker Tab and visit the address listed next to immich-web - -
- -:::tip -For more information on how to use the application once installed, please refer to the [Post Install](/install/post-install.mdx) guide. -::: - -## Updating Steps - -:::danger -Make sure to read the general [upgrade instructions](/install/upgrading.md). -::: - -Updating is extremely easy however it's important to be aware that containers managed via the Docker Compose Manager plugin do not integrate with Unraid's native dockerman UI, the label "_update ready_" will always be present on containers installed via the Docker Compose Manager. - - - -You should ignore the "_update ready_" on the Unraid WebUI and update when you receive the notification within the Immich WebUI. - - -1. Go to the "**Docker**" tab and scroll to the Compose section -2. Next to Immich click the "**Update Stack**" button and Unraid will begin to update all Immich related containers - > Note: **Do not** select Compose Down first, it is unnecessary. -3. Once complete you will see a "_Connection Closed_" message, select "**Done**". - Wait for Connection Closed and click Done -4. Return back to the Immich WebUI and you will see the version has been updated to the latest - Wait for Connection Closed and click Done diff --git a/docs/docs/install/upgrading.md b/docs/docs/install/upgrading.md deleted file mode 100644 index bf788cb680..0000000000 --- a/docs/docs/install/upgrading.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -sidebar_position: 95 ---- - -# Upgrading - -:::tip Breaking changes -You can see versions that had breaking changes [here][breaking]. -::: - -When a new version of Immich is [released][releases], you should read the release notes and account for any breaking changes noted (as mentioned above). -If you use `IMMICH_VERSION` in your `.env` file, it will need to be updated to the latest or desired version. -After that, the application can be upgraded and restarted with the following commands, run in the directory with the `docker-compose.yml` file: - -```bash title="Upgrade and restart Immich" -docker compose pull && docker compose up -d -``` - -To clean up disk space, the old version's obsolete container images can be deleted with the following command: - -```bash title="Clean up unused Docker images" -docker image prune -``` - -[watchtower]: https://containrrr.dev/watchtower/ -[breaking]: https://github.com/immich-app/immich/discussions?discussions_q=label%3Achangelog%3Abreaking-change+sort%3Adate_created -[releases]: https://github.com/immich-app/immich/releases - -## Migrating to VectorChord - -:::info -If you deploy Immich using Docker Compose, see `ghcr.io/immich-app/postgres` in the `docker-compose.yml` file and have not explicitly set the `DB_VECTOR_EXTENSION` environmental variable, your Immich database is already using VectorChord and this section does not apply to you. -::: - -:::important -If you do not deploy Immich using Docker Compose and see a deprecation warning for pgvecto.rs on server startup, you should refer to the maintainers of the Immich distribution for guidance (if using a turnkey solution) or adapt the instructions for your specific setup. -::: - -Immich has migrated off of the deprecated pgvecto.rs database extension to its successor, [VectorChord](https://github.com/tensorchord/VectorChord), which comes with performance improvements in almost every aspect. This section will guide you on how to make this change in a Docker Compose setup. - -Before making any changes, please [back up your database](/administration/backup-and-restore). While every effort has been made to make this migration as smooth as possible, there’s always a chance that something can go wrong. - -After making a backup, please modify your `docker-compose.yml` file with the following information. - -```diff - [...] - - database: - container_name: immich_postgres -- image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52 -+ image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 - environment: - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_DB: ${DB_DATABASE_NAME} - POSTGRES_INITDB_ARGS: '--data-checksums' -+ # Uncomment the DB_STORAGE_TYPE: 'HDD' var if your database isn't stored on SSDs -+ # DB_STORAGE_TYPE: 'HDD' - volumes: - # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file - - ${DB_DATA_LOCATION}:/var/lib/postgresql/data -- healthcheck: -- test: >- -- pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; -- Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align -- --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; -- echo "checksum failure count is $$Chksum"; -- [ "$$Chksum" = '0' ] || exit 1 -- interval: 5m -- start_interval: 30s -- start_period: 5m -- command: >- -- postgres -- -c shared_preload_libraries=vectors.so -- -c 'search_path="$$user", public, vectors' -- -c logging_collector=on -- -c max_wal_size=2GB -- -c shared_buffers=512MB -- -c wal_compression=on -+ shm_size: 128mb - restart: always - - [...] -``` - -:::important -If you deviated from the defaults of pg14 or pgvectors0.2.0, you must adjust the pg major version and pgvecto.rs version. If you are still using the default `docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0` image, you can just follow the changes above. For example, if the previous image is `docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0`, the new image should be `ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvectors0.3.0` instead of the image specified in the diff. -::: - -After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index` for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time. - -:::danger -After switching to VectorChord, you should not downgrade Immich below 1.133.0. -::: - -Please don’t hesitate to contact us on [GitHub](https://github.com/immich-app/immich/discussions) or [Discord](https://discord.immich.app/) if you encounter migration issues. - -### VectorChord FAQ - -#### I have a separate PostgreSQL instance shared with multiple services. How can I switch to VectorChord? - -Please see the [standalone PostgreSQL documentation](/administration/postgres-standalone#migrating-to-vectorchord) for migration instructions. The migration path will be different depending on whether you’re currently using pgvecto.rs or pgvector, as well as whether Immich has superuser DB permissions. - -#### Why are so many lines removed from the `docker-compose.yml` file? Does this mean the health check is removed? - -These lines are now incorporated into the image itself along with some additional tuning. - -#### What does this change mean for my existing DB backups? - -The new DB image includes pgvector and pgvecto.rs in addition to VectorChord, so you can use this image to restore from existing backups that used either of these extensions. However, backups made after switching to VectorChord require an image containing VectorChord to restore successfully. - -#### Do I still need pgvecto.rs installed after migrating to VectorChord? - -pgvecto.rs only needs to be available during the migration, or if you need to restore from a backup that used pgvecto.rs. For a leaner DB and a smaller image, you can optionally switch to an image variant that doesn’t have pgvecto.rs installed after you’ve performed the migration and started Immich: `ghcr.io/immich-app/postgres:14-vectorchord0.4.3`, changing the PostgreSQL version as appropriate. - -#### Why does it matter whether my database is on an SSD or an HDD? - -These storage mediums have different performance characteristics. As a result, the optimal settings for an SSD are not the same as those for an HDD. Either configuration is compatible with SSD and HDD, but using the right configuration will make Immich snappier. As a general tip, we recommend users store the database on an SSD whenever possible. - -#### Can I use the new database image as a general PostgreSQL image outside of Immich? - -It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image. - -#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord? - -VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption. diff --git a/docs/docs/overview/_category_.json b/docs/docs/overview/_category_.json deleted file mode 100644 index e224ed81cd..0000000000 --- a/docs/docs/overview/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Overview", - "position": 1 -} diff --git a/docs/docs/overview/comparison.md b/docs/docs/overview/comparison.md deleted file mode 100644 index b843562c6e..0000000000 --- a/docs/docs/overview/comparison.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Comparison - -If you're new here and came from other asset self-hosting alternatives you might want to look at a comparison between Immich and your current solution. -Here you can see a [comparison between the various OpenSource Photo Libraries](https://meichthys.github.io/foss_photo_libraries/) including Immich. diff --git a/docs/docs/overview/help.md b/docs/docs/overview/help.md deleted file mode 100644 index e6523547fa..0000000000 --- a/docs/docs/overview/help.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Help Me! - -Running into an issue or have a question? Try the following: - -1. Check the [FAQs](/FAQ.mdx). -2. Read through the [Release Notes][github-releases]. -3. Search through existing [GitHub Issues][github-issues]. -4. Open a help ticket on [Discord][discord-link]. - -[github-issues]: https://github.com/immich-app/immich/issues -[github-releases]: https://github.com/immich-app/immich/releases -[discord-link]: https://discord.immich.app diff --git a/docs/docs/overview/img/alex-picture.jpeg b/docs/docs/overview/img/alex-picture.jpeg deleted file mode 100644 index c99523a987..0000000000 Binary files a/docs/docs/overview/img/alex-picture.jpeg and /dev/null differ diff --git a/docs/docs/overview/img/appicon.webp b/docs/docs/overview/img/appicon.webp deleted file mode 100644 index ffbfc1383d..0000000000 Binary files a/docs/docs/overview/img/appicon.webp and /dev/null differ diff --git a/docs/docs/overview/img/feature-panel.webp b/docs/docs/overview/img/feature-panel.webp deleted file mode 100644 index 5f10198ed3..0000000000 Binary files a/docs/docs/overview/img/feature-panel.webp and /dev/null differ diff --git a/docs/docs/overview/img/upload-button.webp b/docs/docs/overview/img/upload-button.webp deleted file mode 100644 index f40e9cba03..0000000000 Binary files a/docs/docs/overview/img/upload-button.webp and /dev/null differ diff --git a/docs/docs/overview/quick-start.mdx b/docs/docs/overview/quick-start.mdx deleted file mode 100644 index 521d0a232c..0000000000 --- a/docs/docs/overview/quick-start.mdx +++ /dev/null @@ -1,93 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Quick start - -Here is a quick, no-choices path to install Immich and take it for a test drive. -Once you've tried it, you might use one of the many other ways -to install and use it. - -## Requirements - -- A system with at least 6GB of RAM and 2 CPU cores. -- [Docker](https://docs.docker.com/engine/install/) - -> For a more detailed list of requirements, see the [requirements page](/install/requirements). - ---- - -## Set up the server - -import DockerComposeSteps from '/docs/partials/_docker-compose-install-steps.mdx'; - - - ---- - -## Try the web app - -import RegisterAdminUser from '/docs/partials/_register-admin.md'; - - - -Try uploading a picture from your browser. - - - ---- - -## Try the mobile app - -### Download the Mobile App - -import MobileAppDownload from '/docs/partials/_mobile-app-download.md'; - - - -### Login to the Mobile App - -import MobileAppLogin from '/docs/partials/_mobile-app-login.md'; - - - -In the mobile app, you should see the photo you uploaded from the web UI. - -### Transfer Photos from Your Mobile Device - -import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; - - - -The backup time differs depending on how many photos are on your mobile device. Large uploads may -take quite a while. -To quickly get going, you can selectively upload few photos first, by following this [guide](/features/mobile-app#sync-only-selected-photos). - -You can select the **Job Queues** tab to see Immich processing your photos. - - - ---- - -## Review the database backup and restore process - -Immich has built-in database backups. You can refer to the -[database backup](/administration/backup-and-restore) for more information. - -:::danger -The database only contains metadata and user information. You must setup manual backups of the images and videos stored in `UPLOAD_LOCATION`. -::: - ---- - -## Where to go from here? - -You may decide you'd like to install the server a different way; the Install category on the left menu provides many options. - -You may decide you'd like to add the _rest_ of your photos from Google Photos, even those not on your mobile device, via Google Takeout. You can use [immich-go](https://github.com/simulot/immich-go) for this. - -You may want to [upload photos from your own archive](/features/command-line-interface). - -You may want to incorporate a pre-existing archive of photos from an [External Library](/features/libraries); there's a [guide](/guides/external-library) for that. - -You may want your mobile device to [back photos up to your server automatically](/features/mobile-backup). diff --git a/docs/docs/overview/support-the-project.md b/docs/docs/overview/support-the-project.md deleted file mode 100644 index ae24a3f1ce..0000000000 --- a/docs/docs/overview/support-the-project.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Support The Project - -## Report issues - -By far the easiest way to help make Immich better it to use it and report issues and bugs. Found a bug? [Open an issue on GitHub][github-issue]. - -## Translations - -Support the project by localizing on [Weblate](https://hosted.weblate.org/projects/immich/immich/). For more information, see the [Translations](/developer/translations) section. - -## Development - -If you are a programmer or developer, take a look at Immich's [technology stack](/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/developer/architecture.mdx) section. - -## Purchase Immich - -You can also [purchase Immich](https://buy.immich.app), for either one user or your entire server. Building Immich takes a lot of time and effort, and we have full-time engineers working on it to make it as good as we possibly can, so any support is greatly appreciated. Don't worry, all features will be free, forever! Nothing will ever be put behind any paywalls. - -[github-issue]: https://github.com/immich-app/immich/issues/new/choose -[github-langs]: https://github.com/immich-app/immich/tree/main/mobile/assets/i18n diff --git a/docs/docs/partials/_docker-compose-install-steps.mdx b/docs/docs/partials/_docker-compose-install-steps.mdx deleted file mode 100644 index c538a6051e..0000000000 --- a/docs/docs/partials/_docker-compose-install-steps.mdx +++ /dev/null @@ -1,43 +0,0 @@ -import CodeBlock from '@theme/CodeBlock'; -import ExampleEnv from '!!raw-loader!../../../docker/example.env'; - -### Step 1 - Download the required files - -Create a directory of your choice (e.g. `./immich-app`) to hold the `docker-compose.yml` and `.env` files. - -```bash title="Move to the directory you created" -mkdir ./immich-app -cd ./immich-app -``` - -Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) by running the following commands: - -```bash title="Get docker-compose.yml file" -wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml -``` - -```bash title="Get .env file" -wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env -``` - -You can alternatively download these two files from your browser and move them to the directory that you created, in which case ensure that you rename `example.env` to `.env`. - -### Step 2 - Populate the .env file with custom values - - - {ExampleEnv} - - -- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets. It should be a new directory on the server with enough free space. -- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publicly exposed, so this password is only used for local authentication. - To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`. `pwgen` is a handy utility for this. -- Set your timezone by uncommenting the `TZ=` line. -- Populate custom database information if necessary. - -### Step 3 - Start the containers - -From the directory you created in Step 1 (which should now contain your customized `docker-compose.yml` and `.env` files), run the following command to start Immich as a background service: - -```bash title="Start the containers" -docker compose up -d -``` diff --git a/docs/docs/partials/_mobile-app-backup.md b/docs/docs/partials/_mobile-app-backup.md deleted file mode 100644 index 777a989334..0000000000 --- a/docs/docs/partials/_mobile-app-backup.md +++ /dev/null @@ -1,13 +0,0 @@ -1. Navigate to the backup screen by clicking on the cloud icon in the top right corner of the screen. - - - -2. You can select which album(s) you want to back up to the Immich server from the backup screen. - - - -3. Scroll down to the bottom and press "**Enable Backup**" to start the backup process. This will upload all the assets in the selected albums. - -:::info -You can read more about backup options [here](/features/mobile-backup.md). -::: diff --git a/docs/docs/partials/_mobile-app-download.md b/docs/docs/partials/_mobile-app-download.md deleted file mode 100644 index 31cf62bf81..0000000000 --- a/docs/docs/partials/_mobile-app-download.md +++ /dev/null @@ -1,7 +0,0 @@ -The mobile app can be downloaded from the following places: - -- Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities). -- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich) -- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652) -- [F-Droid](https://f-droid.org/packages/app.alextran.immich) -- [GitHub Releases (apk)](https://github.com/immich-app/immich/releases) diff --git a/docs/docs/partials/_mobile-app-login.md b/docs/docs/partials/_mobile-app-login.md deleted file mode 100644 index 3dc8f30933..0000000000 --- a/docs/docs/partials/_mobile-app-login.md +++ /dev/null @@ -1,3 +0,0 @@ -Login to the mobile app with the server endpoint URL at `http://:2283` - - diff --git a/docs/docs/partials/_register-admin.md b/docs/docs/partials/_register-admin.md deleted file mode 100644 index b20542e12a..0000000000 --- a/docs/docs/partials/_register-admin.md +++ /dev/null @@ -1,7 +0,0 @@ -The first user to register will be the admin user. The admin user will be able to add other users to the application. - -To register for the admin user, access the web application at `http://:2283` and click on the **Getting Started** button. - - - -Follow the prompts to register as the admin user and log in to the application. diff --git a/docs/docs/partials/_server-backup.md b/docs/docs/partials/_server-backup.md deleted file mode 100644 index 34e09670e9..0000000000 --- a/docs/docs/partials/_server-backup.md +++ /dev/null @@ -1,6 +0,0 @@ -Now that you have imported some pictures, you should setup server backups to preserve your memories. -You can do so by following our [backup guide](/administration/backup-and-restore.md). - -:::info -A 3-2-1 backup strategy is still crucial. The team has the responsibility to ensure that the application doesn’t cause loss of your precious memories; however, we cannot guarantee that hard drives will not fail, or an electrical event causes unexpected shutdown of your server/system, leading to data loss. Therefore, we still encourage users to follow best practices when safeguarding their data. Keep multiple copies of your most precious data: at least two local copies and one copy offsite in cold storage. -::: diff --git a/docs/docs/partials/_storage-template.md b/docs/docs/partials/_storage-template.md deleted file mode 100644 index 84236e0ac1..0000000000 --- a/docs/docs/partials/_storage-template.md +++ /dev/null @@ -1,38 +0,0 @@ -Immich allows the admin user to set the uploaded filename pattern at the directory and filename level as well as the [storage label for a user](/administration/user-management/#set-storage-label-for-user). - -:::tip -You can read more about the differences between storage template engine on and off [here](/administration/backup-and-restore#asset-types-and-storage-locations) -::: - -The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text. If the template produces [multiple files with the same filename, they won't be overwritten](https://github.com/immich-app/immich/discussions/3324) as a sequence number is appended to the filename. - -```bash title="Default template" -Year/Year-Month-Day/Filename.Extension -``` - -If you want to change the storage template during the initial setup, first enable the feature. - - - -Then, customize your storage template. - - - -:::info -The `Storage Template Migration` job can be run after enabling this feature or changing the template, in order to apply the changes to the existing library. - - - -::: - -:::tip -If an asset is in multiple albums, `{{album}}` will be set to the name of the album which was most recently created. By default, special characters will be converted to an HTML entity (for example, `&` -> `&`). To prevent this, wrap the variable in an extra set of braces (for example, `{{{album}}}`). You can learn more about this [here](https://handlebarsjs.com/guide/expressions.html#html-escaping) and [here](https://github.com/immich-app/immich/issues/4917). -::: - -Immich also provides a mechanism to migrate between templates so that if the template you set now doesn't work in the future, you can always migrate all the existing files to the new template. The mechanism is run as a job on the Job page. - -If you want to store assets in album folders, but you also have assets that do not belong to any album, you can use `{{#if album}}`, `{{else}}` and `{{/if}}` to create a conditional statement. For example, the following template will store assets in album folders if they belong to an album, and in a folder named "Other/Month" if they do not belong to an album: - -``` -{{y}}/{{#if album}}{{album}}{{else}}Other{{/if}}/{{MM}}/{{filename}} -``` diff --git a/docs/docs/partials/_user-create.md b/docs/docs/partials/_user-create.md deleted file mode 100644 index 8856b8f2e9..0000000000 --- a/docs/docs/partials/_user-create.md +++ /dev/null @@ -1,7 +0,0 @@ -If you have friends or family members who want to use the application as well, you can create additional accounts for them. - - - -On the **Administration > Users** page, you can click on the **Create user** button, and you'll be presented with the following dialog: - - diff --git a/docs/docs/partials/img/admin-registration-form.webp b/docs/docs/partials/img/admin-registration-form.webp deleted file mode 100644 index 5300a888f8..0000000000 Binary files a/docs/docs/partials/img/admin-registration-form.webp and /dev/null differ diff --git a/docs/docs/partials/img/album-selection.webp b/docs/docs/partials/img/album-selection.webp deleted file mode 100644 index 8c81350e0c..0000000000 Binary files a/docs/docs/partials/img/album-selection.webp and /dev/null differ diff --git a/docs/docs/partials/img/backup-header.webp b/docs/docs/partials/img/backup-header.webp deleted file mode 100644 index dcc4357807..0000000000 Binary files a/docs/docs/partials/img/backup-header.webp and /dev/null differ diff --git a/docs/docs/partials/img/create-new-user-dialog.webp b/docs/docs/partials/img/create-new-user-dialog.webp deleted file mode 100644 index 058abc698d..0000000000 Binary files a/docs/docs/partials/img/create-new-user-dialog.webp and /dev/null differ diff --git a/docs/docs/partials/img/create-new-user.webp b/docs/docs/partials/img/create-new-user.webp deleted file mode 100644 index c4497aa3dc..0000000000 Binary files a/docs/docs/partials/img/create-new-user.webp and /dev/null differ diff --git a/docs/docs/partials/img/enable-storage-template.webp b/docs/docs/partials/img/enable-storage-template.webp deleted file mode 100644 index d27ed59379..0000000000 Binary files a/docs/docs/partials/img/enable-storage-template.webp and /dev/null differ diff --git a/docs/docs/partials/img/sign-in-phone.webp b/docs/docs/partials/img/sign-in-phone.webp deleted file mode 100644 index 45265bed39..0000000000 Binary files a/docs/docs/partials/img/sign-in-phone.webp and /dev/null differ diff --git a/docs/docs/partials/img/storage-template-migration-job.webp b/docs/docs/partials/img/storage-template-migration-job.webp deleted file mode 100644 index b6d07300f7..0000000000 Binary files a/docs/docs/partials/img/storage-template-migration-job.webp and /dev/null differ diff --git a/docs/docs/partials/img/storage-template.webp b/docs/docs/partials/img/storage-template.webp deleted file mode 100644 index 07cf05dfed..0000000000 Binary files a/docs/docs/partials/img/storage-template.webp and /dev/null differ diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js deleted file mode 100644 index 147f981aff..0000000000 --- a/docs/docusaurus.config.js +++ /dev/null @@ -1,207 +0,0 @@ -// @ts-check -// Note: type annotations allow type checking and IDEs autocompletion - -const prism = require('prism-react-renderer'); - -/** @type {import('@docusaurus/types').Config} */ -const config = { - title: 'Immich', - tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone', - url: 'https://docs.immich.app', - baseUrl: '/', - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', - favicon: 'img/favicon.png', - - // GitHub pages deployment config. - // If you aren't using GitHub pages, you don't need these. - organizationName: 'immich-app', // Usually your GitHub org/user name. - projectName: 'immich', // Usually your repo name. - deploymentBranch: 'main', - // Even if you don't use internalization, you can use this field to set useful - // metadata like html lang. For example, if your site is Chinese, you may want - // to replace "en" with "zh-Hans". - i18n: { - defaultLocale: 'en', - locales: ['en'], - }, - - // Mermaid diagrams - markdown: { - mermaid: true, - }, - themes: ['@docusaurus/theme-mermaid'], - - plugins: [ - async function myPlugin(context, options) { - return { - name: 'docusaurus-tailwindcss', - configurePostCss(postcssOptions) { - // Appends TailwindCSS and AutoPrefixer. - postcssOptions.plugins.push(require('tailwindcss')); - postcssOptions.plugins.push(require('autoprefixer')); - return postcssOptions; - }, - }; - }, - require.resolve('docusaurus-lunr-search'), - ], - presets: [ - [ - 'classic', - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - showLastUpdateAuthor: true, - showLastUpdateTime: true, - routeBasePath: '/', - - sidebarPath: require.resolve('./sidebars.js'), - // Please change this to your repo. - // Remove this to remove the "edit this page" links. - editUrl: 'https://github.com/immich-app/immich/tree/main/docs/', - }, - theme: { - customCss: require.resolve('./src/css/custom.css'), - }, - }), - ], - ], - - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - docs: { - sidebar: { - autoCollapseCategories: false, - }, - }, - tableOfContents: { - minHeadingLevel: 2, - maxHeadingLevel: 4, - }, - navbar: { - logo: { - alt: 'Immich Logo', - src: 'img/immich-logo-inline-light.png', - srcDark: 'img/immich-logo-inline-dark.png', - className: 'rounded-none', - }, - items: [ - { - type: 'custom-versionSwitcher', - position: 'right', - }, - { - to: '/overview/quick-start', - position: 'right', - label: 'Docs', - }, - { - href: 'https://immich.app/roadmap', - position: 'right', - label: 'Roadmap', - }, - { - href: 'https://api.immich.app/', - position: 'right', - label: 'API', - }, - { - href: 'https://immich.store', - position: 'right', - label: 'Merch', - }, - { - href: 'https://github.com/immich-app/immich', - label: 'GitHub', - position: 'right', - }, - { - href: 'https://discord.immich.app', - label: 'Discord', - position: 'right', - }, - { - type: 'html', - position: 'right', - value: - '
', - }, - ], - }, - footer: { - style: 'light', - links: [ - { - title: 'Overview', - items: [ - { - label: 'Quick start', - to: '/overview/quick-start', - }, - { - label: 'Installation', - to: '/install/requirements', - }, - { - label: 'Contributing', - to: '/overview/support-the-project', - }, - { - label: 'Privacy Policy', - to: '/privacy-policy', - }, - ], - }, - { - title: 'Documentation', - items: [ - { - label: 'Roadmap', - href: 'https://immich.app/roadmap', - }, - { - label: 'API', - href: 'https://api.immich.app/', - }, - { - label: 'Cursed Knowledge', - href: 'https://immich.app/cursed-knowledge', - }, - ], - }, - { - title: 'Links', - items: [ - { - label: 'GitHub', - href: 'https://github.com/immich-app/immich', - }, - { - label: 'YouTube', - href: 'https://www.youtube.com/@immich-app', - }, - { - label: 'Discord', - href: 'https://discord.immich.app', - }, - { - label: 'Reddit', - href: 'https://www.reddit.com/r/immich/', - }, - ], - }, - ], - copyright: `Immich is available as open source under the terms of the GNU AGPL v3 License.`, - }, - prism: { - theme: prism.themes.github, - darkTheme: prism.themes.dracula, - additionalLanguages: ['sql', 'diff', 'bash', 'powershell', 'nginx'], - }, - image: 'img/feature-panel.png', - }), -}; - -module.exports = config; diff --git a/docs/mise.toml b/docs/mise.toml deleted file mode 100644 index 4ffb7d5cce..0000000000 --- a/docs/mise.toml +++ /dev/null @@ -1,25 +0,0 @@ -[tasks.install] -run = "pnpm install --filter documentation --frozen-lockfile" - -[tasks.start] -env._.path = "./node_modules/.bin" -run = "docusaurus --port 3005" - -[tasks.build] -env._.path = "./node_modules/.bin" -run = [ - "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0", - "docusaurus build", -] - -[tasks.preview] -env._.path = "./node_modules/.bin" -run = "docusaurus serve" - -[tasks.format] -env._.path = "./node_modules/.bin" -run = "prettier --check ." - -[tasks."format-fix"] -env._.path = "./node_modules/.bin" -run = "prettier --write ." diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 87b0b3fccd..0000000000 --- a/docs/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "documentation", - "version": "0.0.0", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "format": "prettier --check .", - "format:fix": "prettier --write .", - "start": "docusaurus start --port 3005", - "copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0", - "build": "npm run copy:openapi && docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "~3.9.0", - "@docusaurus/preset-classic": "~3.9.0", - "@docusaurus/theme-common": "~3.9.0", - "@docusaurus/theme-mermaid": "~3.9.0", - "@mdi/js": "^7.3.67", - "@mdi/react": "^1.6.1", - "@mdx-js/react": "^3.0.0", - "autoprefixer": "^10.4.17", - "docusaurus-lunr-search": "^3.3.2", - "lunr": "^2.3.9", - "postcss": "^8.4.25", - "prism-react-renderer": "^2.3.1", - "raw-loader": "^4.0.2", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "tailwindcss": "^3.2.4", - "url": "^0.11.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "~3.9.0", - "@docusaurus/tsconfig": "^3.7.0", - "@docusaurus/types": "^3.7.0", - "prettier": "^3.7.4", - "typescript": "^5.1.6" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "engines": { - "node": ">=20" - }, - "volta": { - "node": "24.13.0" - } -} diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index ef1bd11a19..0000000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], - - // But you can create a sidebar manually - /* - tutorialSidebar: [ - 'intro', - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], - */ -}; - -module.exports = sidebars; diff --git a/docs/src/components/timeline.tsx b/docs/src/components/timeline.tsx deleted file mode 100644 index 32b15edb59..0000000000 --- a/docs/src/components/timeline.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import useIsBrowser from '@docusaurus/useIsBrowser'; -import { mdiCheckboxBlankCircle, mdiCheckboxMarkedCircle } from '@mdi/js'; -import Icon from '@mdi/react'; -import React from 'react'; - -export type Item = { - icon: string; - iconColor: string; - title: string; - description?: string; - link?: { url: string; text: string }; - done?: false; - getDateLabel: (language: string) => string; -}; - -interface Props { - items: Item[]; -} - -export function Timeline({ items }: Props): JSX.Element { - const isBrowser = useIsBrowser(); - - return ( -
    - {items.map((item, index) => { - const isFirst = index === 0; - const isLast = index === items.length - 1; - const done = item.done ?? true; - const dateLabel = item.getDateLabel(isBrowser ? navigator.language : 'en-US'); - const timelineIcon = done ? mdiCheckboxMarkedCircle : mdiCheckboxBlankCircle; - const cardIcon = item.icon; - - return ( -
  • -
    - {dateLabel} -
    -
    -
    -
    -
    - {} -
    -
    -
    -
    - {cardIcon === 'immich' ? ( - - ) : ( - - )} -

    - {item.title} -

    -
    -

    {item.description}

    -
    -
    - - {item.link && ( - - [{item.link.text}] - - )} - -
    {dateLabel}
    -
    -
    -
  • - ); - })} -
- ); -} diff --git a/docs/src/components/version-switcher.tsx b/docs/src/components/version-switcher.tsx deleted file mode 100644 index 739d7bd001..0000000000 --- a/docs/src/components/version-switcher.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useWindowSize } from '@docusaurus/theme-common'; -import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'; -import React, { useEffect, useState } from 'react'; - -export default function VersionSwitcher(): JSX.Element { - const [versions, setVersions] = useState([]); - const [activeLabel, setLabel] = useState('Versions'); - - const windowSize = useWindowSize(); - - useEffect(() => { - async function getVersions() { - try { - let baseUrl = 'https://docs.immich.app'; - if (window.location.origin === 'http://localhost:3005') { - baseUrl = window.location.origin; - } - - const response = await fetch(`${baseUrl}/archived-versions.json`); - - const archiveVersions = await response.json(); - - const allVersions = [ - { label: 'Next', url: 'https://docs.main.preview.immich.app' }, - { label: 'Latest', url: 'https://docs.immich.app' }, - ...archiveVersions, - ].map(({ label, url, rootPath }) => ({ - label, - url: new URL(url), - rootPath, - })); - setVersions(allVersions); - - const activeVersion = allVersions.find((version) => version.url.origin === window.location.origin); - if (activeVersion) { - setLabel(activeVersion.label); - } - } catch (error) { - console.error('Failed to fetch versions', error); - } - } - - if (versions.length === 0) { - getVersions(); - } - }, []); - - return ( - versions.length > 0 && ( - { - let path = location.pathname + location.search + location.hash; - if (rootPath && !path.startsWith(rootPath)) { - path = rootPath + path; - } - return { - label, - to: new URL(path, url).href, - target: '_self', - className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func - }; - })} - /> - ) - ); -} diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css deleted file mode 100644 index 665bc8fd55..0000000000 --- a/docs/src/css/custom.css +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@font-face { - font-family: 'GoogleSans'; - src: url('/fonts/GoogleSans/GoogleSans.ttf') format('truetype-variations'); - font-weight: 410 900; - font-style: normal; - ascent-override: 106.25%; - size-adjust: 106.25%; -} - -@font-face { - font-family: 'GoogleSansCode'; - src: url('/fonts/GoogleSansCode/GoogleSansCode.ttf') format('truetype-variations'); - font-weight: 1 900; - font-style: monospace; - ascent-override: 106.25%; - size-adjust: 106.25%; -} - -.breadcrumbs__link { - font-weight: 500; - font-size: 0.875rem; - padding: 8px 16px 8px 16px; -} - -img { - border-radius: 15px; -} - -/* You can override the default Infima variables here. */ -:root { - font-family: 'GoogleSans', sans-serif; - letter-spacing: 0.1px; - --ifm-color-primary: #4250af; - --ifm-color-primary-dark: #4250af; - --ifm-color-primary-darker: #4250af; - --ifm-color-primary-darkest: #4250af; - --ifm-color-primary-light: #4250af; - --ifm-color-primary-lighter: #4250af; - --ifm-color-primary-lightest: #4250af; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: 'GoogleSans', sans-serif; - letter-spacing: 0.1px; -} - -/* For readability concerns, you should choose a lighter palette in dark mode. */ -[data-theme='dark'] { - --ifm-color-primary: #adcbfa; - --ifm-color-primary-dark: #85b2f8; - --ifm-color-primary-darker: #71a5f6; - --ifm-color-primary-darkest: #357ff3; - --ifm-color-primary-light: #d5e4fc; - --ifm-color-primary-lighter: #e9f1fe; - --ifm-color-primary-lightest: #ffffff; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); - --ifm-navbar-background-color: #0c0c0c; - --ifm-footer-background-color: #0c0c0c; -} - -[data-theme='dark'] body, -[data-theme='dark'] .main-wrapper { - background-color: #070707; -} - -div[class^='announcementBar_'] { - min-height: 2rem; - background-color: #2b3336; - color: white; -} - -.menu__link { - padding: 10px 10px 10px 16px; - border-radius: 24px; - margin-right: 16px; - font-weight: 500; -} - -.menu__list-item-collapsible { - margin-right: 16px; - border-radius: 24px; - font-weight: 500; -} - -.menu__link--active { - font-weight: 600; -} - -.table-of-contents__link { - font-size: 14px; - font-weight: 450; -} - -/* workaround for version switcher PR 15894 */ -div[class*='navbar__items'] > li:has(a[class*='version-switcher-34ab39']) { - display: none; -} - -code { - font-weight: 500; - font-family: 'GoogleSansCode'; -} - -.buy-button { - padding: 8px 14px; - border: 1px solid transparent; - font-family: 'GoogleSans', sans-serif; - font-weight: 500; - cursor: pointer; - box-shadow: 0 0 5px 2px rgba(181, 206, 254, 0.4); -} diff --git a/docs/src/pages/errors.md b/docs/src/pages/errors.md deleted file mode 100644 index fed72f21c7..0000000000 --- a/docs/src/pages/errors.md +++ /dev/null @@ -1,34 +0,0 @@ -# Errors - -## TypeORM Upgrade - -If you encountered "Migrations failed: Error: Invalid upgrade path" then perform an intermediate upgrade to `v1.132.3` first. - -:::tip -We recommend users upgrade to `v1.132.3` since it does not have any breaking changes or bugs on this upgrade path. -::: - -In order to update to Immich `v1.137.0` or above, the application must be started at least once on a version in the range between `1.132.0` and `1.136.0`. Doing so will complete database schema upgrades that are required for `v1.137.0` (and above). After Immich has successfully updated to a version in this range, you can now attempt to update to `v1.137.0` (or above). - -:::caution -Avoid `v1.136.0` if upgrading from `v1.131.0` (or earlier) due to a bug blocking this upgrade in some installations. -::: - -## Inconsistent Media Location - -:::caution -This error is related to the location of media files _inside the container_. Never move files on the host system when you run into this error message. -::: - -Immich automatically tries to detect where your Immich data is located. On start up, it compares the detected media location with the file paths in the database and throws an Inconsistent Media Location error when they do not match. - -To fix this issue, verify that the `IMMICH_MEDIA_LOCATION` environment variable and `UPLOAD_LOCATION` volume mount are in sync with the database paths. - -If you would like to migrate from one media location to another, simply successfully start Immich on `v1.136.0` or later, then do the following steps: - -1. Stop Immich -2. Update `IMMICH_MEDIA_LOCATION` to the new location -3. Update the right-hand side of the `UPLOAD_LOCATION` volume mount to the new location -4. Start up Immich - -After version `1.136.0`, Immich can detect when a media location has moved and will automatically update the database paths to keep them in sync. diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx deleted file mode 100644 index 37455cde16..0000000000 --- a/docs/src/pages/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Redirect } from '@docusaurus/router'; - -export default function Home(): JSX.Element { - return ; -} diff --git a/docs/src/pages/privacy-policy.tsx b/docs/src/pages/privacy-policy.tsx deleted file mode 100644 index 36ac76945d..0000000000 --- a/docs/src/pages/privacy-policy.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; -import Layout from '@theme/Layout'; -function HomepageHeader() { - return ( -
-
-
-

Privacy Policy

-

Last updated: July 31st 2024

-

- Welcome to Immich. We are committed to respecting your privacy. This Privacy Policy sets out how we collect, - use, and share information when you use our Immich app. -

-
- - {/* 1. Scope of This Policy */} -
-

1. Scope of This Policy

-

- This Privacy Policy applies to the Immich app ("we", "our", or "us") and covers our collection, use, and - disclosure of your information. This Policy does not cover any third-party websites, services, or - applications that can be accessed through our app, or third-party services you may access through Immich. -

-
- - {/* 2. Information We Collect */} -
-

2. Information We Collect

-
-

- Locally Stored Data: Immich stores all your photos, albums, settings, and locally on your - device. We do not have access to this data, nor do we transmit or store it on any of our servers. -

-
- -
-

- Purchase Information: When you make a purchase within the{' '} - https://buy.immich.app, we collect the following information for tax - calculation purposes: -

-
    -
  • Country of origin
  • -
  • Postal code (if the user is from Canada or the United States)
  • -
-
-
- - {/* 3. Use of Your Information */} -
-

3. Use of Your Information

-

- Tax Calculation: The country of origin and postal code (for users from Canada or the United - States) are collected solely for determining the applicable tax rates on your purchase. -

-
- - {/* 4. Sharing of Your Information */} -
-

4. Sharing of Your Information

-
    -
  • - Tax Authorities: The purchase information may be shared with tax authorities as required - by law. -
  • -
  • - Payment Providers: The purchase information may be shared with payment providers where - required. -
  • -
-
- - {/* 5. Changes to This Policy */} -
-

5. Changes to This Policy

-

- We may update our Privacy Policy from time to time. If we make any changes, we will notify you by revising - the "Last updated" date at the top of this policy. It's encouraged that users frequently check this page for - any changes to stay informed about how we are helping to protect the personal information we collect. -

-
- - {/* 6. Contact Us */} -
-

6. Contact Us

-

- If you have any questions about this Privacy Policy, please contact us at{' '} - immich@futo.org -

-
-
-
- ); -} - -export default function Home(): JSX.Element { - return ( - - -
-

This project is available under GNU AGPL v3 license.

-

Privacy should not be a luxury

-
-
- ); -} diff --git a/docs/src/theme/NavbarItem/ComponentTypes.js b/docs/src/theme/NavbarItem/ComponentTypes.js deleted file mode 100644 index eb4d4e2aa5..0000000000 --- a/docs/src/theme/NavbarItem/ComponentTypes.js +++ /dev/null @@ -1,7 +0,0 @@ -import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes'; -import VersionSwitcher from '@site/src/components/version-switcher'; - -export default { - ...ComponentTypes, - 'custom-versionSwitcher': VersionSwitcher, -}; diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/static/CNAME b/docs/static/CNAME deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/static/_redirects b/docs/static/_redirects deleted file mode 100644 index 5d4ad14f00..0000000000 --- a/docs/static/_redirects +++ /dev/null @@ -1,37 +0,0 @@ -/ /overview/quick-start 307 -/mobile-app-beta-program /features/mobile-app 307 -/contribution-guidelines /overview/support-the-project#contributing 307 -/install /install/docker-compose 307 -/installation/one-step-installation /install/script 307 -/installation/portainer-installation /install/portainer 307 -/installation/recommended-installation /install/docker-compose 307 -/installation/unraid /install/unraid 307 -/installation/requirements /install/requirements 307 -/overview/logo-meaning /overview/logo 307 -/overview/technology-stack /developer/architecture 307 -/usage/automatic-backup /features/automatic-backup 307 -/usage/bulk-upload /features/command-line-interface 307 -/features/bulk-upload /features/command-line-interface 307 -/usage/oauth /administration/oauth 307 -/usage/post-installation /install/post-install 307 -/usage/update /install/docker-compose#step-4---upgrading 307 -/usage/server-commands /administration/server-commands 307 -/features/jobs /administration/jobs 307 -/features/oauth /administration/oauth 307 -/features/password-login /administration/password-login 307 -/features/server-commands /administration/server-commands 307 -/features/storage-template /administration/storage-template 307 -/features/user-management /administration/user-management 307 -/developer/contributing /developer/pr-checklist 307 -/guides/machine-learning /guides/remote-machine-learning 307 -/administration/password-login /administration/system-settings 307 -/features/search /features/searching 307 -/features/smart-search /features/searching 307 -/guides/api-album-sync https://awesome.immich.app/ 307 -/guides/remove-offline-files https://awesome.immich.app/ 307 -/community-guides https://awesome.immich.app/ 307 -/community-projects https://awesome.immich.app/ 307 -/overview/introduction /overview/quick-start 307 -/overview/welcome /overview/quick-start 307 -/docs/* /:splat 307 -/features/automatic-backup /features/mobile-backup 307 diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json deleted file mode 100644 index 795926ac89..0000000000 --- a/docs/static/archived-versions.json +++ /dev/null @@ -1,249 +0,0 @@ -[ - { - "label": "v2.5.5", - "url": "https://docs.v2.5.5.archive.immich.app" - }, - { - "label": "v2.4.1", - "url": "https://docs.v2.4.1.archive.immich.app" - }, - { - "label": "v2.3.1", - "url": "https://docs.v2.3.1.archive.immich.app" - }, - { - "label": "v2.2.3", - "url": "https://docs.v2.2.3.archive.immich.app" - }, - { - "label": "v2.1.0", - "url": "https://docs.v2.1.0.archive.immich.app" - }, - { - "label": "v2.0.1", - "url": "https://docs.v2.0.1.archive.immich.app" - }, - { - "label": "v1.144.1", - "url": "https://docs.v1.144.1.archive.immich.app" - }, - { - "label": "v1.143.1", - "url": "https://docs.v1.143.1.archive.immich.app" - }, - { - "label": "v1.142.1", - "url": "https://v1.142.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.141.1", - "url": "https://v1.141.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.140.1", - "url": "https://v1.140.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.139.4", - "url": "https://v1.139.4.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.138.1", - "url": "https://v1.138.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.137.3", - "url": "https://v1.137.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.136.0", - "url": "https://v1.136.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.135.3", - "url": "https://v1.135.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.134.0", - "url": "https://v1.134.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.133.1", - "url": "https://v1.133.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.132.3", - "url": "https://v1.132.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.131.3", - "url": "https://v1.131.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.130.3", - "url": "https://v1.130.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.129.0", - "url": "https://v1.129.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.128.0", - "url": "https://v1.128.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.127.0", - "url": "https://v1.127.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.126.1", - "url": "https://v1.126.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.125.7", - "url": "https://v1.125.7.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.124.2", - "url": "https://v1.124.2.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.123.0", - "url": "https://v1.123.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.122.3", - "url": "https://v1.122.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.121.0", - "url": "https://v1.121.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.120.2", - "url": "https://v1.120.2.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.119.1", - "url": "https://v1.119.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.118.2", - "url": "https://v1.118.2.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.117.0", - "url": "https://v1.117.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.116.2", - "url": "https://v1.116.2.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.115.0", - "url": "https://v1.115.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.114.0", - "url": "https://v1.114.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.113.1", - "url": "https://v1.113.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.112.1", - "url": "https://v1.112.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.111.0", - "url": "https://v1.111.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.110.0", - "url": "https://v1.110.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.109.2", - "url": "https://v1.109.2.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.108.0", - "url": "https://v1.108.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.107.2", - "url": "https://v1.107.2.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.106.4", - "url": "https://v1.106.4.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.105.1", - "url": "https://v1.105.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.104.0", - "url": "https://v1.104.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.103.1", - "url": "https://v1.103.1.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.102.3", - "url": "https://v1.102.3.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.101.0", - "url": "https://v1.101.0.archive.immich.app", - "rootPath": "/docs" - }, - { - "label": "v1.100.0", - "url": "https://v1.100.0.archive.immich.app", - "rootPath": "/docs" - } -] diff --git a/docs/static/fonts/GoogleSans/GoogleSans.ttf b/docs/static/fonts/GoogleSans/GoogleSans.ttf deleted file mode 100644 index 5d9102f856..0000000000 Binary files a/docs/static/fonts/GoogleSans/GoogleSans.ttf and /dev/null differ diff --git a/docs/static/fonts/GoogleSansCode/GoogleSansCode.ttf b/docs/static/fonts/GoogleSansCode/GoogleSansCode.ttf deleted file mode 100644 index b68d037edf..0000000000 Binary files a/docs/static/fonts/GoogleSansCode/GoogleSansCode.ttf and /dev/null differ diff --git a/docs/static/img/app-qr-code-dark.svg b/docs/static/img/app-qr-code-dark.svg deleted file mode 100644 index c2d593ea2a..0000000000 --- a/docs/static/img/app-qr-code-dark.svg +++ /dev/null @@ -1,378 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/app-qr-code-light.svg b/docs/static/img/app-qr-code-light.svg deleted file mode 100644 index d5d225201e..0000000000 --- a/docs/static/img/app-qr-code-light.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/static/img/cloud-done.svg b/docs/static/img/cloud-done.svg deleted file mode 100644 index 8ab8770a1d..0000000000 --- a/docs/static/img/cloud-done.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/static/img/cloud-off.svg b/docs/static/img/cloud-off.svg deleted file mode 100644 index 310266ddb2..0000000000 --- a/docs/static/img/cloud-off.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/static/img/cloud.svg b/docs/static/img/cloud.svg deleted file mode 100644 index 860fb6a7d8..0000000000 --- a/docs/static/img/cloud.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/static/img/color-logo.png b/docs/static/img/color-logo.png deleted file mode 100644 index 30004c54a0..0000000000 Binary files a/docs/static/img/color-logo.png and /dev/null differ diff --git a/docs/static/img/download-apk-github.svg b/docs/static/img/download-apk-github.svg deleted file mode 100644 index 3fad724350..0000000000 --- a/docs/static/img/download-apk-github.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico deleted file mode 100644 index 6082fc3bd1..0000000000 Binary files a/docs/static/img/favicon.ico and /dev/null differ diff --git a/docs/static/img/favicon.png b/docs/static/img/favicon.png deleted file mode 100644 index 4e642631a3..0000000000 Binary files a/docs/static/img/favicon.png and /dev/null differ diff --git a/docs/static/img/feature-panel.png b/docs/static/img/feature-panel.png deleted file mode 100644 index 8c39fe0d40..0000000000 Binary files a/docs/static/img/feature-panel.png and /dev/null differ diff --git a/docs/static/img/google-play-badge.png b/docs/static/img/google-play-badge.png deleted file mode 100644 index 131f3acaa2..0000000000 Binary files a/docs/static/img/google-play-badge.png and /dev/null differ diff --git a/docs/static/img/immich-logo-inline-dark.png b/docs/static/img/immich-logo-inline-dark.png deleted file mode 100644 index cc6cb23b62..0000000000 Binary files a/docs/static/img/immich-logo-inline-dark.png and /dev/null differ diff --git a/docs/static/img/immich-logo-inline-light.png b/docs/static/img/immich-logo-inline-light.png deleted file mode 100644 index b910b37904..0000000000 Binary files a/docs/static/img/immich-logo-inline-light.png and /dev/null differ diff --git a/docs/static/img/immich-logo-stacked-dark.svg b/docs/static/img/immich-logo-stacked-dark.svg deleted file mode 100644 index 7f8381869a..0000000000 --- a/docs/static/img/immich-logo-stacked-dark.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/immich-logo-stacked-light.svg b/docs/static/img/immich-logo-stacked-light.svg deleted file mode 100644 index 8c4505d97e..0000000000 --- a/docs/static/img/immich-logo-stacked-light.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/immich-logo.svg b/docs/static/img/immich-logo.svg deleted file mode 100644 index 376fa6f3e8..0000000000 --- a/docs/static/img/immich-logo.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - diff --git a/docs/static/img/ios-app-store-badge.png b/docs/static/img/ios-app-store-badge.png deleted file mode 100644 index 817c13af0d..0000000000 Binary files a/docs/static/img/ios-app-store-badge.png and /dev/null differ diff --git a/docs/static/img/ios-app-store-badge.svg b/docs/static/img/ios-app-store-badge.svg deleted file mode 100755 index 072b425a1a..0000000000 --- a/docs/static/img/ios-app-store-badge.svg +++ /dev/null @@ -1,46 +0,0 @@ - - Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/logomark-dark-with-futo.svg b/docs/static/img/logomark-dark-with-futo.svg deleted file mode 100644 index 5672d0f7fe..0000000000 --- a/docs/static/img/logomark-dark-with-futo.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/logomark-dark.svg b/docs/static/img/logomark-dark.svg deleted file mode 100644 index 51f92109d4..0000000000 --- a/docs/static/img/logomark-dark.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/docs/static/img/logomark-light-with-futo.svg b/docs/static/img/logomark-light-with-futo.svg deleted file mode 100644 index 9fa1ce3605..0000000000 --- a/docs/static/img/logomark-light-with-futo.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/static/img/logomark-light.svg b/docs/static/img/logomark-light.svg deleted file mode 100644 index 497fbdcf14..0000000000 --- a/docs/static/img/logomark-light.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/docs/static/img/screenshot-dark.webp b/docs/static/img/screenshot-dark.webp deleted file mode 100644 index a6a7d0e1d6..0000000000 Binary files a/docs/static/img/screenshot-dark.webp and /dev/null differ diff --git a/docs/static/img/screenshot-light.webp b/docs/static/img/screenshot-light.webp deleted file mode 100644 index 0d88697f47..0000000000 Binary files a/docs/static/img/screenshot-light.webp and /dev/null differ diff --git a/docs/static/img/synology-action-clean.png b/docs/static/img/synology-action-clean.png deleted file mode 100644 index 4d168b0bd8..0000000000 Binary files a/docs/static/img/synology-action-clean.png and /dev/null differ diff --git a/docs/static/img/synology-build.png b/docs/static/img/synology-build.png deleted file mode 100644 index 50c4d0fc98..0000000000 Binary files a/docs/static/img/synology-build.png and /dev/null differ diff --git a/docs/static/img/synology-container-ip.png b/docs/static/img/synology-container-ip.png deleted file mode 100644 index 21617d8c72..0000000000 Binary files a/docs/static/img/synology-container-ip.png and /dev/null differ diff --git a/docs/static/img/synology-container-manager-container-details.png b/docs/static/img/synology-container-manager-container-details.png deleted file mode 100644 index caac0ffe73..0000000000 Binary files a/docs/static/img/synology-container-manager-container-details.png and /dev/null differ diff --git a/docs/static/img/synology-container-manager-create-project.png b/docs/static/img/synology-container-manager-create-project.png deleted file mode 100644 index 5daed6599c..0000000000 Binary files a/docs/static/img/synology-container-manager-create-project.png and /dev/null differ diff --git a/docs/static/img/synology-container-manager-customize-docker-compose.png b/docs/static/img/synology-container-manager-customize-docker-compose.png deleted file mode 100644 index 2c0a40def0..0000000000 Binary files a/docs/static/img/synology-container-manager-customize-docker-compose.png and /dev/null differ diff --git a/docs/static/img/synology-container-manager-set-path.png b/docs/static/img/synology-container-manager-set-path.png deleted file mode 100644 index 2c8da91c1b..0000000000 Binary files a/docs/static/img/synology-container-manager-set-path.png and /dev/null differ diff --git a/docs/static/img/synology-custom-port-firewall-rule.png b/docs/static/img/synology-custom-port-firewall-rule.png deleted file mode 100644 index 26ee17785c..0000000000 Binary files a/docs/static/img/synology-custom-port-firewall-rule.png and /dev/null differ diff --git a/docs/static/img/synology-firewall-rules.png b/docs/static/img/synology-firewall-rules.png deleted file mode 100644 index 869b33765f..0000000000 Binary files a/docs/static/img/synology-firewall-rules.png and /dev/null differ diff --git a/docs/static/img/synology-fw-ipedit.png b/docs/static/img/synology-fw-ipedit.png deleted file mode 100644 index 7f4e561395..0000000000 Binary files a/docs/static/img/synology-fw-ipedit.png and /dev/null differ diff --git a/docs/static/img/synology-fw-rules.png b/docs/static/img/synology-fw-rules.png deleted file mode 100644 index 2ec43a682f..0000000000 Binary files a/docs/static/img/synology-fw-rules.png and /dev/null differ diff --git a/docs/static/img/synology-ipaddress-firewall-rule.png b/docs/static/img/synology-ipaddress-firewall-rule.png deleted file mode 100644 index d1982b053d..0000000000 Binary files a/docs/static/img/synology-ipaddress-firewall-rule.png and /dev/null differ diff --git a/docs/static/img/synology-project-stop.png b/docs/static/img/synology-project-stop.png deleted file mode 100644 index 8a77446dc2..0000000000 Binary files a/docs/static/img/synology-project-stop.png and /dev/null differ diff --git a/docs/static/img/synology-remove-unused.png b/docs/static/img/synology-remove-unused.png deleted file mode 100644 index 9b1a217902..0000000000 Binary files a/docs/static/img/synology-remove-unused.png and /dev/null differ diff --git a/docs/static/img/synology-select-proj.png b/docs/static/img/synology-select-proj.png deleted file mode 100644 index 21642d8713..0000000000 Binary files a/docs/static/img/synology-select-proj.png and /dev/null differ diff --git a/docs/tailwind.config.js b/docs/tailwind.config.js deleted file mode 100644 index 9a654487cc..0000000000 --- a/docs/tailwind.config.js +++ /dev/null @@ -1,27 +0,0 @@ -// tailwind.config.js -/** @type {import('tailwindcss').Config} */ -module.exports = { - corePlugins: { - preflight: false, // disable Tailwind's reset - }, - content: ['./src/**/*.{js,jsx,ts,tsx}', './{docs,blog}/**/*.{md,mdx}'], // my markdown stuff is in ../docs, not /src - darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settings - theme: { - extend: { - colors: { - // Light Theme - 'immich-primary': '#4250af', - 'immich-bg': '#f9f8fb', - 'immich-fg': 'black', - 'immich-gray': '#F6F6F4', - - // Dark Theme - 'immich-dark-primary': '#adcbfa', - 'immich-dark-bg': '#000000', - 'immich-dark-fg': '#e5e7eb', - 'immich-dark-gray': '#111111', - }, - }, - }, - plugins: [], -}; diff --git a/docs/tsconfig.json b/docs/tsconfig.json deleted file mode 100644 index 674c46e46d..0000000000 --- a/docs/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - // This file is not used in compilation. It is here just for a nice editor experience. - "extends": "@docusaurus/tsconfig", - - "compilerOptions": { - "baseUrl": "." - } -} diff --git a/e2e-auth-server/Dockerfile b/e2e-auth-server/Dockerfile deleted file mode 100644 index aa7527c483..0000000000 --- a/e2e-auth-server/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 -RUN corepack enable -ADD package.json *.ts ./ -RUN pnpm install -EXPOSE 2286 -CMD ["pnpm", "run", "start"] diff --git a/e2e-auth-server/auth-server.ts b/e2e-auth-server/auth-server.ts deleted file mode 100644 index a190ecd023..0000000000 --- a/e2e-auth-server/auth-server.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { exportJWK, generateKeyPair } from 'jose'; -import Provider from 'oidc-provider'; - -export enum OAuthClient { - DEFAULT = 'client-default', - RS256_TOKENS = 'client-RS256-tokens', - RS256_PROFILE = 'client-RS256-profile', -} - -export enum OAuthUser { - NO_EMAIL = 'no-email', - NO_NAME = 'no-name', - WITH_QUOTA = 'with-quota', - WITH_USERNAME = 'with-username', - WITH_ROLE = 'with-role', -} - -const claims = [ - { sub: OAuthUser.NO_EMAIL }, - { - sub: OAuthUser.NO_NAME, - email: 'oauth-no-name@immich.app', - email_verified: true, - }, - { - sub: OAuthUser.WITH_USERNAME, - email: 'oauth-with-username@immich.app', - email_verified: true, - immich_username: 'user-username', - }, - { - sub: OAuthUser.WITH_QUOTA, - email: 'oauth-with-quota@immich.app', - email_verified: true, - preferred_username: 'user-quota', - immich_quota: 25, - }, - { - sub: OAuthUser.WITH_ROLE, - email: 'oauth-with-role@immich.app', - email_verified: true, - immich_role: 'admin', - }, -]; - -const withDefaultClaims = (sub: string) => ({ - sub, - email: `${sub}@immich.app`, - name: 'OAuth User', - given_name: `OAuth`, - family_name: 'User', - email_verified: true, -}); - -const getClaims = (sub: string) => claims.find((user) => user.sub === sub) || withDefaultClaims(sub); - -const setup = async () => { - const { privateKey, publicKey } = await generateKeyPair('RS256'); - - const redirectUris = ['http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect']; - const port = 2286; - const host = '0.0.0.0'; - const oidc = new Provider(`http://${host}:${port}`, { - renderError: async (ctx, out, error) => { - console.error(out); - console.error(error); - ctx.body = 'Internal Server Error'; - }, - findAccount: (ctx, sub) => ({ accountId: sub, claims: () => getClaims(sub) }), - scopes: ['openid', 'email', 'profile'], - claims: { - openid: ['sub'], - email: ['email', 'email_verified'], - profile: [ - 'name', - 'given_name', - 'family_name', - 'preferred_username', - 'immich_quota', - 'immich_username', - 'immich_role', - ], - }, - features: { - jwtUserinfo: { - enabled: true, - }, - }, - cookies: { - names: { - session: 'oidc.session', - interaction: 'oidc.interaction', - resume: 'oidc.resume', - state: 'oidc.state', - }, - }, - pkce: { - required: () => false, - }, - jwks: { keys: [await exportJWK(privateKey)] }, - clients: [ - { - client_id: OAuthClient.DEFAULT, - client_secret: OAuthClient.DEFAULT, - redirect_uris: redirectUris, - grant_types: ['authorization_code'], - response_types: ['code'], - }, - { - client_id: OAuthClient.RS256_TOKENS, - client_secret: OAuthClient.RS256_TOKENS, - redirect_uris: redirectUris, - grant_types: ['authorization_code'], - id_token_signed_response_alg: 'RS256', - jwks: { keys: [await exportJWK(publicKey)] }, - }, - { - client_id: OAuthClient.RS256_PROFILE, - client_secret: OAuthClient.RS256_PROFILE, - redirect_uris: redirectUris, - grant_types: ['authorization_code'], - userinfo_signed_response_alg: 'RS256', - jwks: { keys: [await exportJWK(publicKey)] }, - }, - ], - }); - - const onStart = () => console.log(`[e2e-auth-server] http://${host}:${port}/.well-known/openid-configuration`); - const app = oidc.listen(port, host, onStart); - return () => app.close(); -}; - -export default setup; diff --git a/e2e-auth-server/package.json b/e2e-auth-server/package.json deleted file mode 100644 index 73ede1b7c4..0000000000 --- a/e2e-auth-server/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@immich/e2e-auth-server", - "version": "0.1.0", - "type": "module", - "main": "auth-server.ts", - "scripts": { - "start": "tsx startup.ts" - }, - "devDependencies": { - "jose": "^5.6.3", - "@types/oidc-provider": "^9.0.0", - "oidc-provider": "^9.0.0", - "tsx": "^4.20.6" - } -} diff --git a/e2e-auth-server/startup.ts b/e2e-auth-server/startup.ts deleted file mode 100644 index 442cf6dfc2..0000000000 --- a/e2e-auth-server/startup.ts +++ /dev/null @@ -1,8 +0,0 @@ -import setup from './auth-server' - -const teardown = await setup() -process.on('exit', () => { - teardown() - console.log('[e2e-auth-server] stopped') - process.exit(0) -}) diff --git a/fastlane/README.md b/fastlane/README.md deleted file mode 100644 index c0511dd40d..0000000000 --- a/fastlane/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This directory exists because of the F-Droid build process. F-Droid is using the same directory structure as Fastlane for the app metadata. - -Because F-Droid expects the metadata to be located in the root of the repository we need to have this symlink. \ No newline at end of file diff --git a/fastlane/metadata b/fastlane/metadata deleted file mode 120000 index 06b0b3e4d3..0000000000 --- a/fastlane/metadata +++ /dev/null @@ -1 +0,0 @@ -../mobile/android/fastlane/metadata \ No newline at end of file diff --git a/i18n/af.json b/i18n/af.json index 9e2bad48ab..0967ef424b 100644 --- a/i18n/af.json +++ b/i18n/af.json @@ -1,207 +1 @@ -{ - "about": "Oor", - "account": "Rekening", - "account_settings": "Rekeninginstellings", - "acknowledge": "Erken", - "action": "Aksie", - "action_common_update": "Opdateur", - "actions": "Aksies", - "active": "Aktief", - "activity": "Aktiwiteite", - "activity_changed": "Aktiwiteit is {enabled, select, true {aangeskakel} other {afgeskakel}}", - "add": "Voegby", - "add_a_description": "Voeg 'n beskrywing by", - "add_a_location": "Voeg 'n ligging by", - "add_a_name": "Voeg 'n naam by", - "add_a_title": "Voeg 'n titel by", - "add_birthday": "Voeg 'n verjaarsdag by", - "add_endpoint": "Voeg Koppelvlakpunt by", - "add_exclusion_pattern": "Voeg uitsgluitingspatrone by", - "add_location": "Voeg ligging by", - "add_more_users": "Voeg meer gebruikers by", - "add_partner": "Voeg vennoot by", - "add_path": "Voeg pad by", - "add_photos": "Voeg foto's by", - "add_tag": "Voeg tag by", - "add_to": "Voeg by…", - "add_to_album": "Voeg na album", - "add_to_album_bottom_sheet_added": "By {album} bygevoeg", - "add_to_album_bottom_sheet_already_exists": "Reeds in {album}", - "add_to_albums": "Voeg by albums", - "add_to_albums_count": "Voeg by ({count}) albums", - "add_to_shared_album": "Voeg toe aan gedeelde album", - "add_url": "Voeg URL by", - "added_to_archive": "By argief toegevoegd", - "added_to_favorites": "By gunstelinge toegevoegd", - "added_to_favorites_count": "Het {count, number} by gunstelinge toegevoegd", - "admin": { - "add_exclusion_pattern_description": "Voeg uitsluitingspatrone by. Globbing met *, ** en ? word ondersteun. Om alle lêers in enige lêergids genaamd \"Raw\" te ignoreer, gebruik \"**/Raw/**\". Om alle lêers wat op \".tif\" eindig, te ignoreer, gebruik \"**/*.tif\". Om 'n absolute pad te ignoreer, gebruik \"/path/to/ignore/**\".", - "admin_user": "Admin gebruiker", - "asset_offline_description": "Hierdie eksterne biblioteekbate word nie meer op skyf gevind nie en is na die asblik geskuif. As die lêer binne die biblioteek geskuif is, gaan jou tydlyn na vir die nuwe ooreenstemmende bate. Om hierdie bate te herstel, maak asseblief seker dat die lêerpad hieronder deur Immich verkry kan word en skandeer die biblioteek.", - "authentication_settings": "Verifikasie instellings", - "authentication_settings_description": "Bestuur wagwoord, OAuth en ander verifikasie instellings", - "authentication_settings_disable_all": "Is jy seker jy wil alle aanmeldmetodes deaktiveer? Aanmelding sal heeltemal gedeaktiveer word.", - "authentication_settings_reenable": "Om te heraktiveer, gebruik 'n Server Command.", - "background_task_job": "Agtergrondtake", - "backup_database": "Skep Datastortlêer", - "backup_database_enable_description": "Aktiveer databasisrugsteun", - "backup_keep_last_amount": "Aantal vorige rugsteune om te hou", - "backup_onboarding_3_description": "totale kopieë van jou data, insluitende die oorspronklikke lêers. Dit sluit in 1 kopie op 'n ander perseel en 2 kopieë om die huidige rekenaar.", - "backup_onboarding_description": "'N 3-2-1 rugsteun strategie word sterk aanbeveel om jou data veilig te hou. Hou kopieë van jou fotos/videos so wel as die Immich databasis vir 'n volledige rugsteun oplossing.", - "backup_onboarding_footer": "Vir meer inligting oor hoe om 'n rugsteun kopie van Immich te maak, gaan lees asseblief hierdie dokument.", - "backup_onboarding_parts_title": "'N 3-2-1 rugsteun sluit in:", - "backup_onboarding_title": "Rugsteun kopieë", - "backup_settings": "Rugsteun instellings", - "backup_settings_description": "Bestuur databasis rugsteun instellings.", - "cleared_jobs": "Poste gevee vir: {job}", - "config_set_by_file": "Config word tans deur 'n konfigurasielêer gestel", - "confirm_delete_library": "Is jy seker jy wil {library}-biblioteek uitvee?", - "confirm_delete_library_assets": "Is jy seker jy wil hierdie biblioteek uitvee? Dit sal {count, plural, one {# bevatte base} other {# bevatte bates}} uit Immich uitvee en kan nie ongedaan gemaak word nie. Lêers sal op skyf bly.", - "confirm_email_below": "Om te bevestig, tik \"{email}\" hieronder", - "confirm_reprocess_all_faces": "Is jy seker jy wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.", - "confirm_user_password_reset": "Is jy seker jy wil {user} se wagwoord terugstel?", - "confirm_user_pin_code_reset": "Is jy seker jy wil {user} se PIN kode herstel?", - "create_job": "Skep werk", - "cron_expression": "Cron uitdrukking", - "cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Vir meer inligting verwys asseblief na bv. Crontab Guru", - "cron_expression_presets": "Cron uitdrukking voorafinstellings", - "disable_login": "Deaktiveer aanmelding", - "duplicate_detection_job_description": "Begin masjienleer op bates om soortgelyke beelde op te spoor. Maak staat op Smart Search", - "exclusion_pattern_description": "Met uitsluitingspatrone kan jy lêers en vouers ignoreer wanneer jy jou biblioteek skandeer. Dit is nuttig as jy vouers het wat lêers bevat wat jy nie wil invoer nie, soos RAW-lêers.", - "face_detection": "Gesig herkenning", - "face_detection_description": "Identifiseer die gesigte in media deur middel van masjienleer. Vir videos word slegs die duimnaelskets oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas bates in die tou wat nog nie verwerk is nie. Geidentifiseerde gesigte sal ná voltooiing van Gesigidentifikasie vir Gesigherkenning in die tou geplaas word, om hulle in bestaande of nuwe persone te groepeer.", - "facial_recognition_job_description": "Groepeer gesigte in mense in. Die stap is vinniger nadat Gesig Deteksie klaar is. \"Herstel\" (her-)groepeer alle gesigte. \"Vermiste\" plaas gesigte in ry wat nie 'n persoon gekoppel het nie.", - "failed_job_command": "Opdrag {command} het misluk vir werk: {job}", - "force_delete_user_warning": "WAARSKUWING: Dit sal onmiddellik die gebruiker en alle bates verwyder. Dit kan nie ontdoen word nie en die lêers kan nie herstel word nie.", - "image_format": "Formaat", - "image_format_description": "WebP produseer kleiner lêers as JPEG, maar is stadiger om te enkodeer.", - "image_fullsize_description": "Vol grote prent met geen metadata, gebruik wanner ingezoem", - "image_fullsize_enabled": "Skakel aan vol grote prent generasie", - "image_prefer_embedded_preview": "Verkies ingebedde voorskou", - "image_prefer_wide_gamut": "Verkies wide gamut", - "image_prefer_wide_gamut_setting_description": "Gebruik Display P3 vir kleinkiekies. Dit behou die lewendheid van beelde met wye kleurruimtes beter, maar beelde kan anders verskyn op ou apparate met 'n ou blaaierweergawe. sRGB-beelde gebruik steeds sRGB om kleurverskuiwings te voorkom.", - "image_preview_description": "Mediumgrootte prent met gestroopte metadata, wat gebruik word wanneer 'n enkele bate bekyk word en vir masjienleer", - "image_preview_quality_description": "Voorskou kwaliteit van 1-100. Hoër is beter, maar produseer groter lêers en kan app-reaksie verminder. Die stel van 'n lae waarde kan masjienleerkwaliteit beïnvloed.", - "image_preview_title": "Voorskou Instellings", - "image_quality": "Kwaliteit", - "image_resolution": "Resolusie", - "image_resolution_description": "Hoër resolusies kan meer detail bewaar, maar neem langer om te enkodeer, het groter lêergroottes en kan app-reaksie verminder.", - "image_settings": "Prent Instellings", - "image_settings_description": "Bestuur die kwaliteit en resolusie van gegenereerde beelde", - "image_thumbnail_description": "Klein kleinkiekies sonder metadata, gebruik om groepe foto's soos die tydlyn te bekyk", - "image_thumbnail_quality_description": "Kleinkiekiekwaliteit van 1-100. Hoër is beter, maar produseer groter lêers en kan die toepassing vertraag.", - "image_thumbnail_title": "Kleinkiekie-instellings", - "job_concurrency": "{job} gelyktydigheid", - "job_created": "Taak gemaak", - "job_not_concurrency_safe": "Hierdie taak kan nie gelyktydig uitgevoer word nie.", - "job_settings": "Agtergrondtaakinstellings", - "job_settings_description": "Bestuur werkgelyktydigheid", - "library_created": "Biblioteek geskep: {library}", - "library_deleted": "Biblioteek verwyder", - "library_scanning": "Periodieke Soek", - "library_scanning_description": "Stel periodieke deursoek van biblioteek in", - "library_scanning_enable_description": "Aktiveer periodieke biblioteekskandering", - "library_settings": "Eksterne Biblioteek", - "library_settings_description": "Eksterne biblioteek verstellings", - "library_tasks_description": "Deursoek eksterne biblioteke vir nuwe of veranderde bates", - "library_watching_enable_description": "Hou eksterne biblioteke dop vir leer veranderinge", - "library_watching_settings": "Biblioteek dop hou (EKSPERIMENTEEL)", - "library_watching_settings_description": "Hou automaties dop vir veranderinge", - "logging_enable_description": "Aktifeer \"logging\"", - "logging_level_description": "Wanneer aktief, watter vlak van \"logs\" om te skep.", - "logging_settings": "\"Logs\"", - "machine_learning_clip_model": "CLIP model", - "machine_learning_duplicate_detection": "Duplikaat herkenning", - "machine_learning_duplicate_detection_enabled": "Aktifeer duplikaat herkenning", - "machine_learning_enabled": "Aktifeer masjienleer", - "machine_learning_facial_recognition": "Gesigsherkenning", - "machine_learning_facial_recognition_description": "Herken, identifiseer en groepeer gesigte in fotos", - "machine_learning_facial_recognition_model": "Gesigsherkennings model", - "machine_learning_facial_recognition_setting": "Aktifeer gesigsherkenning", - "machine_learning_max_detection_distance": "Maksimum herkennings afstand", - "map_settings": "Kaart", - "migration_job": "Migrasie", - "oauth_settings": "OAuth", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_preferred_hardware_device": "Verkiesde hardeware" - }, - "administration": "Administrasie", - "advanced": "Gevorderde", - "albums": "Albums", - "all": "Alle", - "anti_clockwise": "Anti-kloksgewys", - "archive": "Argief", - "asset_skipped": "Oorgeslaan", - "asset_uploaded": "Opgelaai", - "asset_uploading": "Oplaai…", - "assets": "Bates", - "back": "Terug", - "backward": "Agteruit", - "build": "Bou", - "camera": "Kamera", - "cancel": "Kanselleer", - "city": "Stad", - "clockwise": "Kloksgewys", - "close": "Maak toe", - "color": "Kleur", - "confirm": "Bevestig", - "contain": "Bevat", - "context": "Konteks", - "continue": "Gaan voort", - "country": "Land", - "cover": "Bedek", - "create": "Skep", - "created": "Geskep", - "dark": "Donker", - "day": "Dag", - "delete": "Verwyder", - "description": "Beskrywing", - "details": "Besonderhede", - "direction": "Rigting", - "discover": "Ontdek", - "documentation": "Dokumentasie", - "done": "Klaar", - "download": "Aflaai", - "download_settings": "Aflaai", - "duplicates": "Duplikate", - "duration": "Duur", - "edit": "Wysig", - "search_by_description": "Soek by beskrywing", - "search_by_description_example": "Stapdag in Sapa", - "version": "Weergawe", - "version_announcement_closing": "Jou friend, Alex", - "version_history": "Weergawegeskiedenis", - "version_history_item": "{version} geinstaleerd op {date}", - "video": "Video", - "videos": "Video's", - "view": "Bekyk", - "view_album": "Bekyk Album", - "view_all": "Bekyk alle", - "view_all_users": "Bekyk alle gebruikers", - "view_in_timeline": "Bekyk in tydlyn", - "view_link": "Bekyk skakel", - "view_links": "Bekyk skakels", - "view_name": "Bekyk", - "view_next_asset": "Bekyk volgende bate", - "view_previous_asset": "Bekyk vorige bate", - "view_qr_code": "Bekyk QR-kode", - "view_stack": "Bekyk stapel", - "view_user": "Bekyk gebruiker", - "viewer_remove_from_stack": "Verwyder van stapel", - "viewer_stack_use_as_main_asset": "Gebruik as hoofbate", - "viewer_unstack": "Ontstapel", - "visibility_changed": "Sigbaarheid verander voor {count, plural, one {# person} other {# people}}", - "waiting": "Wag", - "warning": "Waaskuwing", - "week": "Week", - "welcome": "Welkom", - "welcome_to_immich": "Welkom by Immich", - "wifi_name": "Wi-Fi Naam", - "wrong_pin_code": "Verkeerde PIN-kode", - "year": "Jaar", - "years_ago": "{years, plural, one {# year} other {# years}} gelede", - "yes": "Ja", - "you_dont_have_any_shared_links": "Jy het geen gedeelde skakels", - "your_wifi_name": "Jou Wi-Fi naam", - "zoom_image": "Vergroot Prent" -} +{} diff --git a/i18n/ar.json b/i18n/ar.json index 6702d4c695..0967ef424b 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -1,2401 +1 @@ -{ - "about": "حول", - "account": "حساب", - "account_settings": "إعدادات الحساب", - "acknowledge": "أُدرك ذلك", - "action": "عملية", - "action_common_update": "تحديث", - "action_description": "مجموعة من الفعاليات التي ستنفذ على الأصول التي تم تصفيتها", - "actions": "عمليات", - "active": "نشط", - "active_count": "فعال: {count}", - "activity": "نشاط", - "activity_changed": "النشاط {enabled, select, true {مُفْعل} other {معطّل}}", - "add": "إضافة", - "add_a_description": "إضافة وصف", - "add_a_location": "إضافة موقع", - "add_a_name": "إضافة إسم", - "add_a_title": "إضافة عنوان", - "add_action": "اضف فعالية", - "add_action_description": "اضغط لإضافة فعالية لتنفيذها", - "add_assets": "اضف اصول", - "add_birthday": "أضف تاريخ الميلاد", - "add_endpoint": "اضف نقطة نهاية", - "add_exclusion_pattern": "إضافة نمط إستثناء", - "add_filter": "اضف تصفية", - "add_filter_description": "اضغط لاضافة شرط تصفية", - "add_location": "إضافة موقع", - "add_more_users": "إضافة مستخدمين آخرين", - "add_partner": "أضف شريكًا", - "add_path": "إضافة مسار", - "add_photos": "إضافة صور", - "add_tag": "اضف علامة", - "add_to": "إضافة إلى…", - "add_to_album": "إضافة إلى ألبوم", - "add_to_album_bottom_sheet_added": "تمت الاضافة الى {album}", - "add_to_album_bottom_sheet_already_exists": "موجود مسبقا في {album}", - "add_to_album_bottom_sheet_some_local_assets": "لا يمكن إضافة بعض الأصول المحلية إلى الألبوم", - "add_to_album_toggle": "تبديل التحديد لـ{album}", - "add_to_albums": "إضافة الى البومات", - "add_to_albums_count": "إضافه إلى البومات ({count})", - "add_to_bottom_bar": "اضافه الى", - "add_to_shared_album": "إضافة إلى ألبوم مشارك", - "add_upload_to_stack": "اضف رفع الى حزمة", - "add_url": "إضافة رابط", - "add_workflow_step": "اضف خطوة سير عمل", - "added_to_archive": "أُضيفت للأرشيف", - "added_to_favorites": "أُضيفت للمفضلات", - "added_to_favorites_count": "تم إضافة {count, number} إلى المفضلات", - "admin": { - "add_exclusion_pattern_description": "إضافة أنماط الاستبعاد. يدعم التمويه باستخدام *، **، و؟. لتجاهل جميع الملفات في أي دليل يسمى \"Raw\"، استخدم \"**/Raw/**\". لتجاهل جميع الملفات التي تنتهي بـ \".tif\"، استخدم \"**/*.tif\". لتجاهل مسار مطلق، استخدم \"/path/to/ignore/**\".", - "admin_user": "مستخدم مسؤول", - "asset_offline_description": "لم يعد هذا الأصل الخاص بالمكتبة الخارجية موجودًا على القرص وتم نقله إلى سلة المهملات. إذا تم نقل الملف داخل المكتبة، فتحقق من الجدول الزمني الخاص بك لمعرفة الأصل الجديد المقابل. لاستعادة هذا الأصل، يرجى التأكد من إمكانية الوصول إلى مسار الملف أدناه بواسطة Immich ومن ثم قم بمسح المكتبة.", - "authentication_settings": "إعدادات المصادقة", - "authentication_settings_description": "إدارة كلمة المرور وOAuth وإعدادات المصادقة الأُخرى", - "authentication_settings_disable_all": "هل أنت متأكد أنك تريد تعطيل جميع وسائل تسجيل الدخول؟ سيتم تعطيل تسجيل الدخول بالكامل.", - "authentication_settings_reenable": "لإعادة التفعيل، استخدم أمر الخادم.", - "background_task_job": "المهام في الخلفية", - "backup_database": "انشاء تفريغ قاعدة البيانات", - "backup_database_enable_description": "تمكين تفريغ قاعدة البيانات", - "backup_keep_last_amount": "مقدار التفريغات السابقة للاحتفاظ بها", - "backup_onboarding_1_description": "نسخة خارج الموقع في موقع آخر.", - "backup_onboarding_2_description": "نسخ محلية على أجهزة مختلفة. يشمل ذلك الملفات الرئيسية ونسخة احتياطية محلية منها.", - "backup_onboarding_3_description": "إجمالي نُسخ بياناتك، بما في ذلك الملفات الأصلية. يشمل ذلك نسخةً واحدةً خارج الموقع ونسختين محليتين.", - "backup_onboarding_description": "يُنصح باتباع استراتيجية النسخ الاحتياطي 3-2-1 لحماية بياناتك. احتفظ بنسخ احتياطية من صورك/فيديوهاتك المحمّلة، بالإضافة إلى قاعدة بيانات Immich، لضمان حل نسخ احتياطي شامل.", - "backup_onboarding_footer": "لمزيد من المعلومات حول النسخ الاحتياطي لـ Immich، يرجى الرجوع إلى التعليمات .", - "backup_onboarding_parts_title": "يتضمن النسخ الاحتياطي 3-2-1 ما يلي:", - "backup_onboarding_title": "النسخ الاحتياطية", - "backup_settings": "إعدادات تفريغ قاعدة البيانات", - "backup_settings_description": "إدارة إعدادات تفريغ قاعدة البيانات.", - "cleared_jobs": "تم إخلاء مهام ل: {job}", - "config_set_by_file": "الإعدادات حاليًا معينة عن طريق ملف الاعدادات", - "confirm_delete_library": "هل أنت متأكد أنك تريد حذف مكتبة {library}؟", - "confirm_delete_library_assets": "هل أنت متأكد أنك تريد حذف هذه المكتبة؟ سيؤدي ذلك إلى حذف {count, plural, one {# محتوى موجود} other {جميع # المحتويات الموجودة}} من Immich ولا يمكن التراجع عنه. ستظل الملفات موجودة على القرص.", - "confirm_email_below": "للتأكيد، اكتب \"{email}\" بالأسفل", - "confirm_reprocess_all_faces": "هل أنت متأكد أنك تريد إعادة معالجة جميع الوجوه؟ سيخلي هذا كل الأشخاص الذين سَميتَهم.", - "confirm_user_password_reset": "هل أنت متأكد أنك تريد إعادة تعيين كلمة المرور ل {user}؟", - "confirm_user_pin_code_reset": "هل انت متاكد من اعادة ضبط رمز PIN الخاص ب {user}؟", - "copy_config_to_clipboard_description": "انسخ اعدادات النظام الحالية بتنسيق JSON الى الحافظة", - "create_job": "إنشاء وظيفة", - "cron_expression": "تعبير Cron", - "cron_expression_description": "اضبط الفاصل الزمني للفحص باستخدام تنسيق cron. لمزيد من المعلومات يُرجى الرجوع إلى Crontab Guru على سبيل المثال", - "cron_expression_presets": "الإعدادات المسبقة لتعبير Cron", - "disable_login": "تعطيل تسجيل الدخول", - "duplicate_detection_job_description": "بدء التعلم الآلي على المحتوى للعثور على الصور المتشابهة. يعتمد على البحث الذكي", - "exclusion_pattern_description": "تتيح لك أنماط الاستبعاد تجاهل الملفات والمجلدات عند فحص مكتبتك. يعد هذا مفيدًا إذا كان لديك مجلدات تحتوي على ملفات لا تريد استيرادها، مثل ملفات RAW.", - "export_config_as_json_description": "تحميل اعدادات النظام الحالية كملف بصيغة JSON", - "external_libraries_page_description": "مشرف صفحة مكتبة خارجية", - "face_detection": "إ‏كتشاف الوجوه", - "face_detection_description": "اكتشف الوجوه في الأصول باستخدام التعلم الآلي. بالنسبة لمقاطع الفيديو، يتم اعتبار الصورة المصغرة فقط. \"تحديث\" (إعادة) معالجة جميع الأصول. \"إعادة تعيين\" تمسح أيضًا جميع بيانات الوجوه الحالية. \"مفقود\" يضع الأصول التي لم تتم معالجتها بعد في قائمة الانتظار. سيتم وضع الوجوه المكتشفة في قائمة الانتظار للتعرف على الوجه بعد اكتمال اكتشاف الوجه، وتجميعها في أشخاص موجودين أو جدد.", - "facial_recognition_job_description": "تجميع الوجوه المكتشفة كأشخاص. يتم تنفيذ هذه الخطوة بعد اكتمال اكتشاف الوجه. خيار \"إعادة التعيين\" يعيد تجميع جميع الوجوه. خيار \"المفقود\" يضع في قائمة الانتظار الوجوه التي لم يتم تعيين شخص لها.", - "failed_job_command": "فشل الأمر {command} للمهمة: {job}", - "force_delete_user_warning": "تحذير: سيؤدي ذلك إلى إزالة المستخدم وجميع محتوياته على الفور. لا يمكن التراجع عن هذا الإجراء ولا يمكن استرداد الملفات.", - "image_format": "التنسيق", - "image_format_description": "يُنتج WebP ملفات أصغر حجمًا من ملفات JPEG، ولكنه أبطأ في عملية الترميز.", - "image_fullsize_description": "صورة بحجم كامل مع ازالة البيانات الوصفية، تستخدم عند التكبير", - "image_fullsize_enabled": "تمكين توليد الصور بحجم كامل", - "image_fullsize_enabled_description": "توليد صور بحجم كامل للصيغ الغير صديقة للويب. عند تفعيل \"تفضيل العرض المدمج\" ، العروض المدمجه تستخدم بشكل مباشر بدون تحويل. لا يؤثر على الصيغ الصديقة للويب مثل JPEG.", - "image_fullsize_quality_description": "صور بدقة كاملة من ١-١٠٠. الاعلى افضل ولكن ينتج ملفات بحجم اكبر.", - "image_fullsize_title": "اعدادات الصور بحجم كامل", - "image_prefer_embedded_preview": "تفضيل المعاينة المدمجة", - "image_prefer_embedded_preview_setting_description": "استخدم المعاينات المضمنة في صور RAW كمدخل لمعالجة الصور عندما تكون متاحة. ينتج عنه انتاج ألوان أكثر دقة لبعض الصور، لكن جودة المعاينة تعتمد على الكاميرا وقد تحتوي الصورة على شوائب ضغطٍ أكثر.", - "image_prefer_wide_gamut": "تفضيل تدرج الألوان الواسع", - "image_prefer_wide_gamut_setting_description": "استخدم عرض P3 للصور المصغرة. يحافظ هذا على حيوية الصور ذات مساحات الألوان الواسعة بشكل أفضل، ولكن قد تظهر الصور بشكل مختلف على الأجهزة القديمة ذات إصدار متصفح قديم. يتم الاحتفاظ بصور sRGB بتنسيق sRGB لتجنب تغيرات اللون.", - "image_preview_description": "صورة متوسطة الحجم مع بيانات وصفية مجردة، تُستخدم عند عرض أصل واحد وللتعلم الآلي", - "image_preview_quality_description": "جودة المعاينة من 1 إلى 100. كلما كانت القيمة أعلى كان ذلك أفضل، ولكنها تنتج ملفات أكبر وقد تقلل من استجابة التطبيق. قد يؤثر ضبط قيمة منخفضة على جودة التعلم الآلي.", - "image_preview_title": "إعدادات المعاينة", - "image_progressive": "متدرج", - "image_progressive_description": "ترميز صور JPEG تدريجياً لعرضها بشكل تدريجي. هذا لا يؤثر على صور WebP.", - "image_quality": "الجودة", - "image_resolution": "الدقة", - "image_resolution_description": "يمكن للدقة العالية الحفاظ على مزيد من التفاصيل ولكنها تستغرق وقتًا أطول للترميز، وتحتوي على أحجام ملفات أكبر ويمكن أن تقلل من استجابة التطبيق.", - "image_settings": "إعدادات الصور", - "image_settings_description": "إدارة جودة ودقة الصور التي تم إنشاؤها", - "image_thumbnail_description": "صورة مصغرة صغيرة مع بيانات وصفية مجردة، تُستخدم عند عرض مجموعات من الصور مثل الجدول الزمني الرئيسي", - "image_thumbnail_quality_description": "تتراوح جودة الصورة المصغرة من 1 إلى 100. كلما كانت الجودة أعلى كان ذلك أفضل، ولكنها تنتج ملفات أكبر وقد تقلل من استجابة التطبيق.", - "image_thumbnail_title": "إعدادات الصورة المصغرة", - "import_config_from_json_description": "استيراد اعدادات النظام بتحميل ملف اعدادات بصيغة JSON", - "job_concurrency": "تزامن {job}", - "job_created": "تم إنشاء الوظيفة", - "job_not_concurrency_safe": "هذه الوظيفة غير آمنة للتشغيل المتزامن.", - "job_settings": "إعدادات الوظائف", - "job_settings_description": "إدارة تزامن الوظائف", - "jobs_delayed": "{jobCount, plural, other {# مؤجلة}}", - "jobs_failed": "{jobCount, plural, other {# فشلت}}", - "jobs_over_time": "الوظائف بمرور الوقت", - "library_created": "تم إنشاء المكتبة: {library}", - "library_deleted": "تم حذف المكتبة", - "library_details": "تفاصيل المكتبة", - "library_folder_description": "حدد مجلد للاستيراد. هذا المجلد مع المجلدات الفرعية، سيتم تفحصهم للصور والفديوات.", - "library_remove_exclusion_pattern_prompt": "هل انت متاكد انك تريد ازالة نمط الاستبعاد هذا؟", - "library_remove_folder_prompt": "هل انت متاكد انك تريد ازالة مجلد الاستيراد هذا؟", - "library_scanning": "المسح الدوري", - "library_scanning_description": "إعداد مسح المكتبة الدوري", - "library_scanning_enable_description": "تفعيل مسح المكتبة الدوري", - "library_settings": "المكتبة الخارجية", - "library_settings_description": "إدارة إعدادات المكتبة الخارجية", - "library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة", - "library_updated": "مكتبة محدثة", - "library_watching_enable_description": "مراقبة المكتبات الخارجية لاكتشاف تغييرات الملفات", - "library_watching_settings": "مراقبة المكتبات [تجريبي]", - "library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات", - "logging_enable_description": "تفعيل تسجيل الأحداث", - "logging_level_description": "عند التفعيل، أي مستوى تسجيل سيستخدم.", - "logging_settings": "السجلات", - "machine_learning_availability_checks": "تحقق من التوفر", - "machine_learning_availability_checks_description": "تحديد خوادم التعلم الآلي المتاحة تلقائيًا وإعطاءها الأولوية", - "machine_learning_availability_checks_enabled": "تفعيل عمليات فحص التوفر", - "machine_learning_availability_checks_interval": "فترة التحقق", - "machine_learning_availability_checks_interval_description": "الفترة الزمنية بالمللي ثانية بين عمليات فحص التوفر", - "machine_learning_availability_checks_timeout": "انتهت مدة انتظار الطلب", - "machine_learning_availability_checks_timeout_description": "مدة انتظار (بالمللي ثانية) لاختبارات توفر الخدمة", - "machine_learning_clip_model": "نموذج CLIP", - "machine_learning_clip_model_description": "اسم نموذج CLIP مدرجٌ هنا. يرجى ملاحظة أنه يجب إعادة تشغيل وظيفة \"البحث الذكي\" لجميع الصور بعد تغيير النموذج.", - "machine_learning_duplicate_detection": "كشف التكرار", - "machine_learning_duplicate_detection_enabled": "تفعيل كشف التكرار", - "machine_learning_duplicate_detection_enabled_description": "إذا تم تعطيله، سيُستمر التخلص من تكرارات المحتويات المتطابقة تماماً.", - "machine_learning_duplicate_detection_setting_description": "استخدم تضمينات CLIP للعثور على النسخ المتطابقة المحتملة", - "machine_learning_enabled": "تفعيل التعلم الآلي", - "machine_learning_enabled_description": "إذا تم تعطيله، سيتم تعطيل جميع ميزات التعلم الآلي بغض النظر عن الإعدادات أدناه.", - "machine_learning_facial_recognition": "التعرف على الوجوه", - "machine_learning_facial_recognition_description": "الاكتشاف، التعرف على، وتجميع الوجوه في الصور", - "machine_learning_facial_recognition_model": "نموذج التعرف على الوجوه", - "machine_learning_facial_recognition_model_description": "النماذج مدرجة بترتيب تنازلي حسب الحجم. النماذج الأكبر حجماً أبطأ وتستخدم ذاكرة أكثر، ولكنها تنتج نتائج أفضل. يرجى ملاحظة أنه يجب إعادة تشغيل وظيفة الكشف عن الوجوه لجميع الصور بعد تغيير النموذج.", - "machine_learning_facial_recognition_setting": "تفعيل التعرف على الوجوه", - "machine_learning_facial_recognition_setting_description": "إذا تم تعطيله، فلن يتم ترميز الصور للتعرف على الوجوه ولن تظهر في قسم الأشخاص في صفحة الاستكشاف.", - "machine_learning_max_detection_distance": "الحد الأقصى لمسافة الكشف", - "machine_learning_max_detection_distance_description": "الحد الأقصى للمسافة بين صورتين لاعتبارهما مكررتين، بتراوحٍ بين 0.001 إلى 0.1. القيم الأعلى ستكشف عن مزيد من الصور المكررة، ولكن قد تؤدي إلى إيجابيات خاطئة.", - "machine_learning_max_recognition_distance": "الحد الأقصى لمسافة التعرف", - "machine_learning_max_recognition_distance_description": "الحد الأقصى للمسافة بين وجهين لاعتبارهما نفس الشخص، تتراوح بين 0 إلى 2. تقليل هذا الرقم يمكن أن يمنع تسمية شخصين كنفس الشخص، بينما رفعه يمكن أن يمنع تسمية نفس الشخص كشخصين مختلفين. يرجى ملاحظة أنه من الأسهل دمج شخصين معًا من فصل شخص واحد إلى شخصين، لذا حاول الاحتفاظ بعتبة أدنى إذا كان ذلك ممكنًا.", - "machine_learning_min_detection_score": "الحد الأدنى من نقاط الكشف", - "machine_learning_min_detection_score_description": "الحد الأدنى لنقطة الثقة لاكتشاف الوجه، تتراوح من 0 إلى 1. القيم الأقل ستكشف عن المزيد من الوجوه ولكن قد تؤدي إلى نتائج إيجابية خاطئة.", - "machine_learning_min_recognized_faces": "الحد الأدنى لعدد الوجوه المتعرف عليها", - "machine_learning_min_recognized_faces_description": "الحد الأدنى لعدد الوجوه المتعرف عليها لإنشاء شخص. زيادة هذا الرقم يجعل التعرف على الوجوه أكثر دقة على حساب زيادة احتمال عدم تعيين الوجه لشخص ما.", - "machine_learning_ocr": "التعرف البصري على الحروف", - "machine_learning_ocr_description": "استخدم التعلم الآلي للتعرف على النصوص في الصور", - "machine_learning_ocr_enabled": "تفعيل التعرف البصري على الحروف", - "machine_learning_ocr_enabled_description": "في حال تعطيل هذه الميزة، لن تخضع الصور لعملية التعرف على النصوص.", - "machine_learning_ocr_max_resolution": "أقصى دقة", - "machine_learning_ocr_max_resolution_description": "سيتم تغيير حجم المعاينات التي تتجاوز هذه الدقة مع الحفاظ على نسبة العرض إلى الارتفاع. القيم الأعلى توفر دقة أكبر، ولكنها تستغرق وقتًا أطول للمعالجة وتستهلك المزيد من الذاكرة.", - "machine_learning_ocr_min_detection_score": "الحد الأدنى لدرجة الكشف", - "machine_learning_ocr_min_detection_score_description": "لحد الأدنى لدرجة الثقة المطلوبة لاكتشاف النص، وتتراوح قيمتها من 0 إلى 1. ستؤدي القيم الأقل إلى اكتشاف المزيد من النصوص ولكنها قد تؤدي إلى نتائج إيجابية خاطئة.", - "machine_learning_ocr_min_recognition_score": "الحد الأدنى لدرجة التعرّف", - "machine_learning_ocr_min_score_recognition_description": "الحد الأدنى لدرجة الثقة المطلوبة للنصوص المكتشفة ليتم التعرف عليها، وتتراوح من 0 إلى 1. ستؤدي القيم الأقل إلى التعرف على المزيد من النصوص ولكنها قد تؤدي إلى نتائج إيجابية خاطئة.", - "machine_learning_ocr_model": "نموذج التعرف البصري على الحروف", - "machine_learning_ocr_model_description": "تتميز نماذج الخوادم بدقة أكبر من نماذج الأجهزة المحمولة، ولكنها تستغرق وقتًا أطول في المعالجة وتستهلك ذاكرة أكبر.", - "machine_learning_settings": "إعدادات التعلم الآلي", - "machine_learning_settings_description": "إدارة ميزات وإعدادات التعلم الآلي", - "machine_learning_smart_search": "البحث الذكي", - "machine_learning_smart_search_description": "البحث عن الصور بشكل دلالي باستخدام تضمينات CLIP", - "machine_learning_smart_search_enabled": "تفعيل البحث الذكي", - "machine_learning_smart_search_enabled_description": "إذا تم تعطيله، فلن يتم ترميز الصور للبحث الذكي.", - "machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL واحد، سيتم محاولة الاتصال بكل خادم على حدة حتى يستجيب أحدهم بنجاح، بدءًا من الأول إلى الأخير. سيتم تجاهل الخوادم التي لا تستجيب مؤقتًا حتى تعود للعمل.", - "maintenance_delete_backup": "حذف النسخ الاحتياطي", - "maintenance_delete_backup_description": "هذا الملف سيتم حذفه بشكل لا رجعه فيه.", - "maintenance_delete_error": "فشل حذف النسخ الاحتياطي.", - "maintenance_restore_backup": "استعادة النسخ الاحتياطي", - "maintenance_restore_backup_description": "سيتم مسح بيانات Immich واستعادتها من النسخة الاحتياطي المختار. سيتم إنشاء نسخة احتياطية قبل المتابعة.", - "maintenance_restore_backup_different_version": "هذا النسخ الاحتياطي تم انشائه باستخدام اصدار مختلف من Immich!", - "maintenance_restore_backup_unknown_version": "لا يمكن التحقق من اصدار النسخ الاحتياطي.", - "maintenance_restore_database_backup": "استعادة النسخ الاحتياطي لقاعدة البيانات", - "maintenance_restore_database_backup_description": "استعادة حالة قاعدة البيانات السابقة باستخدام ملف النسخ الاحتياطي", - "maintenance_settings": "صيانة", - "maintenance_settings_description": "ضع Immich في وضع الصيانة.", - "maintenance_start": "التحزيل الى وضع الصيانة", - "maintenance_start_error": "فشل البدء في وضع الصيانة.", - "maintenance_upload_backup": "رفع ملف النسخ الاحتياطي لقاعدة البيانات", - "maintenance_upload_backup_error": "لم يتم رفع الخزن الاحتياطي, هل الملف بصيغة .sql/.sql.gz?", - "manage_concurrency": "إدارة التزامن", - "manage_concurrency_description": "انتقل الى صفحة الاعمال لادارة تزامن المهام", - "manage_log_settings": "إدارة إعدادات السجلات", - "map_dark_style": "النمط الداكن", - "map_enable_description": "تفعيل ميزات الخرائط", - "map_gps_settings": "إعدادات الخريطة ونظام تحديد المواقع", - "map_gps_settings_description": "إدارة إعدادات الخريطة و نظام تحديد المواقع (عكس الترميز الجغرافي)", - "map_implications": "تعتمد ميزة الخريطة على خدمة خارجية (tiles.immich.cloud)", - "map_light_style": "النمط الفاتح", - "map_manage_reverse_geocoding_settings": "إدارة إعدادات التكوين الجغرافي المعكوس", - "map_reverse_geocoding": "عكس الترميز الجغرافي", - "map_reverse_geocoding_enable_description": "تفعيل الترميز الجغرافي العكسي", - "map_reverse_geocoding_settings": "إعدادات الترميز الجغرافي العكسي", - "map_settings": "الخارطة", - "map_settings_description": "إدارة إعدادات الخارطة", - "map_style_description": "عنوان URL لسمة الخريطة style.json", - "memory_cleanup_job": "تنظيف الذاكرة", - "memory_generate_job": "توليد الذاكرة", - "metadata_extraction_job": "استخراج البيانات الوصفية", - "metadata_extraction_job_description": "استخراج معلومات البيانات الوصفية من كل أصل، مثل إحداثيات الموقع, الوجوه والدقة", - "metadata_faces_import_setting": "تمكين استيراد الوجه", - "metadata_faces_import_setting_description": "استيراد الوجوه من بيانات EXIF للصور وملفات Sidecar", - "metadata_settings": "إعدادات البيانات الوصفية", - "metadata_settings_description": "إدارة إعدادات البيانات الوصفية", - "migration_job": "ترحيل", - "migration_job_description": "ترحيل الصور المصغرة للمحتويات والوجوه إلى أحدث هيكل مجلدات", - "nightly_tasks_cluster_faces_setting_description": "قم بتشغيل التعرف على الوجه على الوجوه المكتشفة حديثا", - "nightly_tasks_cluster_new_faces_setting": "مجموعة الوجوه الجديدة", - "nightly_tasks_database_cleanup_setting": "مهام تنظيف قاعدة البيانات", - "nightly_tasks_database_cleanup_setting_description": "قم بتنظيف البيانات القديمة منتهية الصلاحية من قاعدة البيانات", - "nightly_tasks_generate_memories_setting": "إنشاء الذكريات", - "nightly_tasks_generate_memories_setting_description": "إنشاء ذكريات جديدة من الأصول", - "nightly_tasks_missing_thumbnails_setting": "إنشاء صور مصغرة مفقودة", - "nightly_tasks_missing_thumbnails_setting_description": "أصول قائمة الانتظار بدون صور مصغرة لإنشاء الصور المصغرة", - "nightly_tasks_settings": "إعدادات المهام الليلية", - "nightly_tasks_settings_description": "إدارة المهام الليلية", - "nightly_tasks_start_time_setting": "وقت البدء", - "nightly_tasks_start_time_setting_description": "الوقت الذي يبدأ فيه الخادم في تشغيل المهام الليلية", - "nightly_tasks_sync_quota_usage_setting": "مزامنة حصة الاستخدام", - "nightly_tasks_sync_quota_usage_setting_description": "تحديث حصة تخزين المستخدم، بناء على الاستخدام الحالي", - "no_paths_added": "لم يتم إضافة أي مسارات", - "no_pattern_added": "لم يتم إضافة أي أنماط", - "note_apply_storage_label_previous_assets": "ملاحظة: لتطبيق سمية التخزين على المحتويات التي تم رفعها سابقًا، قم بتشغيل", - "note_cannot_be_changed_later": "ملاحظة: لا يمكن تغيير هذا لاحقًا!", - "notification_email_from_address": "عنوان المرسل", - "notification_email_from_address_description": "عنوان البريد الإلكتروني للمرسل، على سبيل المثال: \"Immich Photo Server noreply@example.com\". تاكد من استخدام عنوان بريد الكتروني يسمح لك بارسال البريد الالكتروني منه.", - "notification_email_host_description": "عنوان خادم البريد الإلكتروني (مثل smtp.immich.app)", - "notification_email_ignore_certificate_errors": "تجاهل أخطاء الشهادة", - "notification_email_ignore_certificate_errors_description": "تجاهل أخطاء التحقق من صحة شهادة TLS (غير مستحسن)", - "notification_email_password_description": "كلمة المرور المستخدمة للمصادقة مع خادم البريد الإلكتروني", - "notification_email_port_description": "منفذ خادم البريد الإلكتروني (مثلاً 25، 465، أو 587)", - "notification_email_secure": "بروتوكول نقل البريد البسيط الآمن SMTPS", - "notification_email_secure_description": "استخدم بروتوكول SMTPS (بروتوكول SMTP عبر TLS)", - "notification_email_sent_test_email_button": "إرسال بريد إلكتروني تجريبي وحفظ التعديلات", - "notification_email_setting_description": "إعدادات إرسال إشعارات البريد الإلكتروني", - "notification_email_test_email": "إرسال بريد تجريبي", - "notification_email_test_email_failed": "فشل في إرسال البريد الإلكتروني التجريبي، يرجى التحقق من القيم الخاصة بك", - "notification_email_test_email_sent": "تم إرسال بريد إلكتروني تجريبي إلى {email}. يرجى التحقق من صندوق الوارد الخاص بك.", - "notification_email_username_description": "اسم المستخدم المُستخدَم للمصادقة مع خادم البريد الإلكتروني", - "notification_enable_email_notifications": "تفعيل إشعارات البريد الإلكتروني", - "notification_settings": "إعدادات الإشعارات", - "notification_settings_description": "إدارة إعدادات الإشعارات، بما في ذلك البريد الإلكتروني", - "oauth_auto_launch": "التشغيل التلقائي", - "oauth_auto_launch_description": "ابدأ تدفق تسجيل الدخول OAuth تلقائيًا عند الانتقال إلى صفحة تسجيل الدخول", - "oauth_auto_register": "التسجيل التلقائي", - "oauth_auto_register_description": "التسجيل التلقائي للمستخدمين الجدد بعد تسجيل الدخول باستخدام OAuth", - "oauth_button_text": "نص الزر", - "oauth_client_secret_description": "مطلوب للعميل السري، او اذا PKCE(مفتاح الاثبات لتبادل الكود) ليس مدعوم من العميل العام.", - "oauth_enable_description": "تسجيل الدخول باستخدام OAuth", - "oauth_mobile_redirect_uri": "عنوان URI لإعادة التوجيه على الهاتف", - "oauth_mobile_redirect_uri_override": "تجاوز عنوان URI لإعادة التوجيه على الهاتف", - "oauth_mobile_redirect_uri_override_description": "قم بتفعيله عندما لا يسمح موفر OAuth بمعرف URI للجوال، مثل ''{callback}''", - "oauth_role_claim": "المطالبة بالدور(صلاحيات)", - "oauth_role_claim_description": "منح وصول المسؤول تلقائيًا بناءً على وجود هذا الطلب. قد يكون الطلب إما 'مستخدم' أو 'مسؤول'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "إدارة إعدادات تسجيل الدخول OAuth", - "oauth_settings_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى الوثائق.", - "oauth_storage_label_claim": "المطالبة بسمة التخزين", - "oauth_storage_label_claim_description": "قم تلقائيًا بتعيين سمة التخزين الخاص بالمستخدم على قيمة هذه المطالبة.", - "oauth_storage_quota_claim": "المطالبة بحصة التخزين", - "oauth_storage_quota_claim_description": "قم تلقائيًا بتعيين حصة التخزين للمستخدم على قيمة هذه المطالبة.", - "oauth_storage_quota_default": "حصة التخزين الافتراضية (جيجابايت)", - "oauth_storage_quota_default_description": "الحصة بالجيجابايت التي سيتم استخدامها عندما لا يتم توفير مطالبة.", - "oauth_timeout": "نفاذ وقت الطلب", - "oauth_timeout_description": "نفاذ وقت الطلب بالميلي ثانية", - "ocr_job_description": "استخدم التعلم الآلي للتعرف على النصوص في الصور", - "password_enable_description": "تسجيل الدخول باستخدام البريد الكتروني وكلمة المرور", - "password_settings": "تسجيل الدخول بكلمة المرور", - "password_settings_description": "إدارة تسجيل الدخول بكلمة المرور", - "paths_validated_successfully": "تم التحقق من صحة كافة المسارات بنجاح", - "person_cleanup_job": "تنظيف الشخص", - "queue_details": "تفاصيل الطابور", - "queues": "طوابير الوظائف", - "queues_page_description": "صفحة طوابير وظائف المدير", - "quota_size_gib": "حجم الحصة (جيجابايت)", - "refreshing_all_libraries": "تحديث كافة المكتبات", - "registration": "تسجيل المدير", - "registration_description": "بما أنك أول مستخدم في النظام، سيتم تعيينك كمسؤول وستكون مسؤولًا عن المهام الإدارية، وسيتم إنشاء مستخدمين إضافيين بواسطتك.", - "remove_failed_jobs": "ازالة العمليات التي فشلت", - "require_password_change_on_login": "الطلب من المستخدم تغيير كلمة المرور عند تسجيل الدخول الأول", - "reset_settings_to_default": "إعادة ضبط الإعدادات إلى الوضع الافتراضي", - "reset_settings_to_recent_saved": "إعادة ضبط الإعدادات إلى الإعدادات المحفوظة مؤخرًا", - "scanning_library": "مسح المكتبة", - "search_jobs": "البحث عن وظائف…", - "send_welcome_email": "إرسال بريد ترحيبي", - "server_external_domain_settings": "إسم النطاق الخارجي", - "server_external_domain_settings_description": "إسم النطاق لروابط المشاركة العامة، بما في ذلك http(s)://", - "server_public_users": "المستخدمون العامون", - "server_public_users_description": "يتم إدراج جميع المستخدمين (الاسم والبريد الإلكتروني) عند إضافة مستخدم إلى الألبومات المشتركة. عند تعطيل هذه الميزة، ستكون قائمة المستخدمين متاحة فقط لمستخدمي الإدارة.", - "server_settings": "إعدادات الخادم", - "server_settings_description": "إدارة إعدادات الخادم", - "server_stats_page_description": "صفحة إحصائيات مسؤول الخادم", - "server_welcome_message": "الرسالة الترحيبية", - "server_welcome_message_description": "رسالة تُعرض على صفحة تسجيل الدخول.", - "settings_page_description": "صفخة اعدادات المسؤول", - "sidecar_job": "البيانات الوصفية الجانبية", - "sidecar_job_description": "اكتشاف أو مزامنة البيانات التعريفية الجانبية من نظام الملفات", - "slideshow_duration_description": "عدد الثواني لعرض كل صورة", - "smart_search_job_description": "قم بتشغيل التعلم الآلي على المحتويات لدعم البحث الذكي", - "storage_template_date_time_description": "يتم استخدام الطابع الزمني لإنشاء الأصل للحصول على معلومات التاريخ والوقت", - "storage_template_date_time_sample": "عينة عن الوقت {date}", - "storage_template_enable_description": "تفعيل محرك قالب التخزين", - "storage_template_hash_verification_enabled": "تم تقعيل التحقق من الهاش", - "storage_template_hash_verification_enabled_description": "تفعيل التحقق من الهاش، لا تعطل هذا إلا إذا كنت متأكدًا من تأثيراته", - "storage_template_migration": "تهجير قالب التخزين", - "storage_template_migration_description": "قم بتطبيق القالب الحالي {template} على المحتويات التي تم رفعها سابقًا", - "storage_template_migration_info": "تغييرات النموذج الخزني ستغير جميع الصيغ الى احرف صغيرة. تغييرات النموذج ستنطبق فقط على المحتويات الجديدة. لتطبيق النموذج على المحتويات التي تم رفعها سابقًا، قم بتشغيل {job}.", - "storage_template_migration_job": "وظيفة تهجير قالب التخزين", - "storage_template_more_details": "لمزيد من التفاصيل حول هذه الميزة، يرجى الرجوع إلى Storage Template وimplications", - "storage_template_onboarding_description_v2": "عند التفعيل. هذه الخاصية ستقوم بالترتيب التلقائي للملفات بناء على نموذج معرف من قبل المستخدم. رجاء اطلع على التوثيق.", - "storage_template_path_length": "الحد التقريبي لطول المسار: {length, number}/{limit, number}", - "storage_template_settings": "قالب التخزين", - "storage_template_settings_description": "إدارة هيكل المجلد واسم الملف للأصول المرفوعة", - "storage_template_user_label": "{label} هو سمة التخزين الخاصة بالمستخدم", - "system_settings": "إعدادات النظام", - "tag_cleanup_job": "تنظيف العلامة", - "template_email_available_tags": "يمكنك استخدام المتغيرات التالية في القالب الخاص بك: {tags}", - "template_email_if_empty": "إذا كان القالب فارغا، فسيتم استخدام البريد الإلكتروني الافتراضي.", - "template_email_invite_album": "قالب دعوة الألبوم", - "template_email_preview": "عرض مسبق", - "template_email_settings": "نماذج البريد الالكتروني", - "template_email_update_album": "تحديث قالب الألبوم", - "template_email_welcome": "قالب البريد الإلكتروني الترحيبي", - "template_settings": "قوالب الإشعارات", - "template_settings_description": "إدارة القوالب المخصصة للإشعارات", - "theme_custom_css_settings": "CSS مخصص", - "theme_custom_css_settings_description": "أوراق الأنماط المتتالية تسمح بتخصيص تصميم Immich.", - "theme_settings": "إعدادات السمة", - "theme_settings_description": "إدارة تخصيص واجهة ويب Immich", - "thumbnail_generation_job": "إنشاء الصور المصغرة", - "thumbnail_generation_job_description": "إنشاء صور مصغرة كبيرة وصغيرة وغير واضحة لكل أصل، بالإضافة إلى صور مصغرة لكل شخص", - "transcoding_acceleration_api": "تسريع API", - "transcoding_acceleration_api_description": "API التي ستتفاعل مع جهازك لتسريع التحويل. هذا الإعداد هو \"أفضل محاولة\": سيعود إلى التحويل البرمجي في حالة الفشل. قد لا يعمل VP9 اعتمادًا على عتادك.", - "transcoding_acceleration_nvenc": "NVENC (يتطلب GPU من NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (يتطلب معالج Intel من الجيل السابع أو أحدث)", - "transcoding_acceleration_rkmpp": "RKMPP (فقط على شرائح Rockchip SOC)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "أكواد الصوت المقبولة", - "transcoding_accepted_audio_codecs_description": "حدد أي أكواد صوتية لا تحتاج إلى تحويل الشفرة. يُستخدم ذلك فقط لبعض سياسات التحويل.", - "transcoding_accepted_containers": "الحاويات المقبولة", - "transcoding_accepted_containers_description": "حدد تنسيقات الحاوية التي لا تحتاج إلى إعادة دمجها إلى MP4. يستخدم فقط لسياسات تحويل معينة.", - "transcoding_accepted_video_codecs": "الأكواد المقبولة للفيديو", - "transcoding_accepted_video_codecs_description": "حدد أي أكواد فيديو لا تحتاج إلى تحويل الشفرة. يُستخدم ذلك فقط لبعض سياسات التحويل.", - "transcoding_advanced_options_description": "الخيارات التي لا يجب على معظم المستخدمين تغييرها", - "transcoding_audio_codec": "كود الصوت", - "transcoding_audio_codec_description": "Opus هو الخيار ذو أعلى جودة، ولكنه يتمتع بتوافق أقل مع الأجهزة أو البرمجيات القديمة.", - "transcoding_bitrate_description": "مقاطع الفيديو التي يتجاوز معدل البت أقصى قيمة أو التي لا تكون في تنسيق مقبول", - "transcoding_codecs_learn_more": "لمعرفة المزيد حول المصطلحات المستخدمة هنا، يرجى الرجوع إلى وثائق FFmpeg للH.264 codec, HEVC codec and VP9 codec.", - "transcoding_constant_quality_mode": "وضع الجودة الثابتة", - "transcoding_constant_quality_mode_description": "ICQ أفضل من CQP، ولكن بعض أجهزة عتاد التسريع لا تدعم هذا الوضع. تعيين هذا الخيار يسجعل الأفضلية للوضع المحدد عند استخدام الترميز بناءً على الجودة. يتم تجاهله بواسطة NVENC لأنه لا يدعم ICQ.", - "transcoding_constant_rate_factor": "عامل معدل الجودة الثابت (-crf)", - "transcoding_constant_rate_factor_description": "مستوى جودة الفيديو. القيم النموذجية هي 23 لـ H.264، 28 لـ HEVC، 31 لـ VP9، و 35 لـ AV1. كلما كانت القيمة أقل كان ذلك أفضل، ولكن يؤدي إلى ملفات أكبر.", - "transcoding_disabled_description": "لا تقم بتحويل أي مقاطع فيديو، قد تؤدي إلى عدم تشغيلها على بعض العملاء", - "transcoding_encoding_options": "خيارات الترميز", - "transcoding_encoding_options_description": "اضبط برامج الترميز والدقة والجودة والخيارات الأخرى لمقاطع الفيديو المشفرة", - "transcoding_hardware_acceleration": "التسريع العتادي", - "transcoding_hardware_acceleration_description": "تجريبي: ترميز اسرع لكن قد يقلل من الجودة مع معدل بت اقل", - "transcoding_hardware_decoding": "فك تشفير الأجهزة", - "transcoding_hardware_decoding_setting_description": "يُمكّن من تسريع من البداية إلى النهاية بدلاً من تسريع عملية التشفير فقط. قد لا يعمل مع جميع مقاطع الفيديو.", - "transcoding_max_b_frames": "أقصى عدد من الإطارات B", - "transcoding_max_b_frames_description": "القيم الأعلى تعزز كفاءة الضغط، ولكنها تبطئ عملية الترميز. قد لا تكون متوافقة مع التسريع العتادي على الأجهزة القديمة. قيمة 0 تعطل إطارات B، بينما تضبط القيمة -1 هذا القيمة تلقائيًا.", - "transcoding_max_bitrate": "الحد الأقصى لمعدل البت", - "transcoding_max_bitrate_description": "يتيح تعيين معدل البت الأقصى التحكم في حجم الملف مع تأثير طفيف على الجودة.عند دقة 720p، القيم المقترحة هي 2600 كيلوبت/ثانية لـ VP9 أو HEVC، و4500 كيلوبت/ثانية لـ H.264.يتم تعطيل الإعداد عند القيمة 0. إذا لم تُحدَّد وحدة، يُفترض k (كيلوبت/ثانية)؛ لذا فإن 5000، 5000k، و5M متكافئة.", - "transcoding_max_keyframe_interval": "الحد الأقصى للفاصل الزمني للإطار الرئيسي", - "transcoding_max_keyframe_interval_description": "يضبط الحد الأقصى لمسافة الإطار بين الإطارات الرئيسية. تؤدي القيم المنخفضة إلى زيادة سوء كفاءة الضغط، ولكنها تعمل على تحسين أوقات البحث وقد تعمل على تحسين الجودة في المشاهد ذات الحركة السريعة. 0 يضبط هذه القيمة تلقائيًا.", - "transcoding_optimal_description": "مقاطع الفيديو ذات الدقة الأعلى من الدقة المستهدفة أو بتنسيق غير مقبول", - "transcoding_policy": "سياسة تحويل الترميز", - "transcoding_policy_description": "اضبط متى سيتم تحويل ترميز الفيديو", - "transcoding_preferred_hardware_device": "الجهاز المفضل", - "transcoding_preferred_hardware_device_description": "ينطبق فقط على VAAPI وQSV. يضبط عقدة dri المستخدمة لتحويل ترميز الأجهزة.", - "transcoding_preset_preset": "الضبط المُسبق (-preset)", - "transcoding_preset_preset_description": "سرعة الضغط. تؤدي الإعدادات المسبقة الأبطأ إلى إنتاج ملفات أصغر حجمًا، وزيادة الجودة عند استهداف معدل بت معين. يتجاهل VP9 السرعات الأعلى من 'الأسرع'.", - "transcoding_reference_frames": "الإطارات المرجعية", - "transcoding_reference_frames_description": "عدد الإطارات التي يجب الرجوع إليها عند ضغط إطار معين. تعمل القيم الأعلى على تحسين كفاءة الضغط، ولكنها تبطئ عملية التشفير. 0 يضبط هذه القيمة تلقائيًا.", - "transcoding_required_description": "فقط مقاطع الفيديو ذات التنسيق غير المقبول", - "transcoding_settings": "إعدادات تحويل ترميز الفيديو", - "transcoding_settings_description": "إدارة مقاطع الفيديو التي يجب تحويل ترميزها وكيفية معالجتها", - "transcoding_target_resolution": "القرار المستهدف", - "transcoding_target_resolution_description": "يمكن أن تحافظ الدقة الأعلى على المزيد من التفاصيل ولكنها تستغرق وقتًا أطول للتشفير، ولها أحجام ملفات أكبر، ويمكن أن تقلل من استجابة التطبيق.", - "transcoding_temporal_aq": "التكميم التكيفي الزمني", - "transcoding_temporal_aq_description": "ينطبق فقط على NVENC. تعمل \"الكمّية التكيفية الزمنية\" على تحسين جودة المشاهد ذات التفاصيل الدقيقة والحركة البطيئة. قد لا يكون هذا الخيار متوافقًا مع الأجهزة القديمة.", - "transcoding_threads": "الخيوط", - "transcoding_threads_description": "تؤدي القيم الأعلى إلى تشفير أسرع، ولكنها تترك مساحة أقل للخادم لمعالجة المهام الأخرى أثناء النشاط. يجب ألا تزيد هذه القيمة عن عدد مراكز وحدة المعالجة المركزية. يزيد من الإستغلال إذا تم ضبطه على 0.", - "transcoding_tone_mapping": "رسم الخرائط النغمية", - "transcoding_tone_mapping_description": "تحاول الحفاظ على مظهر مقاطع الفيديو HDR عند تحويلها إلى SDR. يقدم كل خوارزمية تنازلات مختلفة بين اللون والتفاصيل والسطوع. Hable تحافظ على التفاصيل، Mobius تحافظ على الألوان، و Reinhard تحافظ على السطوع.", - "transcoding_transcode_policy": "سياسة الترميز", - "transcoding_transcode_policy_description": "سياسة تحديد متى يجب ترميز الفيديو. سيتم دائمًا ترميز مقاطع الفيديو HDR (ما لم يتم تعطيل الترميز).", - "transcoding_two_pass_encoding": "الترميز بمرورين", - "transcoding_two_pass_encoding_setting_description": "ترميز بمرورين لإنتاج مقاطع فيديو بترميز أفضل. عند تمكين الحد الأقصى لمعدل البت (مطلوب لكي يعمل مع H.264 و HEVC)، يستخدم هذا الوضع نطاق معدل البت استنادًا إلى الحد الأقصى لمعدل البت ويتجاهل CRF. بالنسبة لـ VP9، يمكن استخدام CRF إذا تم تعطيل الحد الأقصى لمعدل البت.", - "transcoding_video_codec": "ترميز الفيديو", - "transcoding_video_codec_description": "يتمتع VP9 بكفاءة عالية وتوافق مع الويب، ولكنه يستغرق وقتًا أطول في تحويل التعليمات البرمجية. يعمل HEVC بشكل مشابه، لكن توافقه مع الويب أقل. H.264 متوافق على نطاق واسع وسريع في تحويل التعليمات البرمجية، ولكنه ينتج ملفات أكبر بكثير. AV1 هو برنامج الترميز الأكثر كفاءة ولكنه يفتقر إلى الدعم على الأجهزة القديمة.", - "trash_enabled_description": "تفعيل ميزات سلة المهملات", - "trash_number_of_days": "عدد الأيام", - "trash_number_of_days_description": "عدد أيام الاحتفاظ بالمحتويات في سلة المهملات قبل حذفها نهائيًا", - "trash_settings": "إعدادات سلة المهملات", - "trash_settings_description": "إدارة إعدادات سلة المهملات", - "unlink_all_oauth_accounts": "ازالة ربط جميع حسابات OAuth", - "unlink_all_oauth_accounts_description": "تذكّر ان تزيل ربط جميع حسابات OAuth قبل ان تنقل الى مزود جديد.", - "unlink_all_oauth_accounts_prompt": "هل انت متأكد من ازالة ربط جميع حسابات OAuth؟ هذا سيقوم باعادة ضبط الID الخاص بالOAuth لكل مستخدم ولا يمكن التراجع عن العملية.", - "user_cleanup_job": "تنظيف المستخدم", - "user_delete_delay": "سيتم جدولة حساب {user} ومحتوياته للحذف النهائي في غضون {delay, plural, one {# يوم} other {# أيام}}.", - "user_delete_delay_settings": "فترة التأخير قبل الحذف", - "user_delete_delay_settings_description": "عدد الأيام بعد الإزالة لحذف حساب المستخدم ومحتوياته بشكل دائم. تقوم وظيفة حذف المستخدم بالتشغيل في منتصف الليل للتحقق من المستخدمين الجاهزين للحذف. سيتم تقييم التغييرات على هذا الإعداد في التنفيذ القادم.", - "user_delete_immediately": "سيتم وضع حساب {user} ومحتوياته في قائمة الانتظار للحذف الدائم على الفور.", - "user_delete_immediately_checkbox": "قائمة انتظار المستخدم والمحتويات للحذف الفوري", - "user_details": "تفاصيل المستخدم", - "user_management": "إدارة المستخدم", - "user_password_has_been_reset": "تمت إعادة تعيين كلمة المرور الخاصة بالمستخدم:", - "user_password_reset_description": "يرجى تزويد المستخدم بكلمة المرور المؤقتة وإبلاغه بأنه سيحتاج إلى تغيير كلمة المرور عند تسجيل الدخول التالي.", - "user_restore_description": "ستتم استعادة حساب {user}.", - "user_restore_scheduled_removal": "استعادة المستخدم - تمت جدولة الإزالة في {date, date, long}", - "user_settings": "إعدادات المستخدم", - "user_settings_description": "إدارة إعدادات المستخدم", - "user_successfully_removed": "المستخدم {email} تمت ازالته بنجاح.", - "users_page_description": "صفحة ادارة المستخدمين", - "version_check_enabled_description": "تفعيل التحقق من الإصدارات الجديدة", - "version_check_implications": "تعتمد ميزة التحقق من الإصدار على التواصل الدوري مع github.com", - "version_check_settings": "التحقق من الإصدار", - "version_check_settings_description": "تفعيل/تعطيل الإشعار لإصدار جديد", - "video_conversion_job": "تحويل أشرطة الفيديو", - "video_conversion_job_description": "تحويل مقاطع الفيديو إلى توافق أوسع مع المتصفحات والأجهزة" - }, - "admin_email": "البريد الإلكتروني للمشرف", - "admin_password": "كلمة سر المشرف", - "administration": "الإدارة", - "advanced": "متقدم", - "advanced_settings_clear_image_cache": "مسح ذاكرة التخزين المؤقت للصور", - "advanced_settings_clear_image_cache_error": "فشل مسح ذاكرة التخزين المؤقت للصور", - "advanced_settings_clear_image_cache_success": "تم المسح بنجاح {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "استخدم هذا الخيار لتصفية الوسائط اثناء المزامنه بناء على معايير بديلة. جرب هذا الخيار فقط كان لديك مشاكل مع التطبيق بالكشف عن جميع الالبومات.", - "advanced_settings_enable_alternate_media_filter_title": "[تجريبي] استخدم جهاز تصفية مزامنه البومات بديل", - "advanced_settings_log_level_title": "مستوى السجل: {level}", - "advanced_settings_prefer_remote_subtitle": "تكون بعض الأجهزة بطيئة للغاية في تحميل الصور المصغرة من الأصول المحلية. قم بتفعيل هذا الخيار لتحميل الصور البعيدة بدلاً من ذلك.", - "advanced_settings_prefer_remote_title": "تفضل الصور البعيدة", - "advanced_settings_proxy_headers_subtitle": "عرف عناوين الوكيل التي يستخدمها Immich لارسال كل طلب شبكي", - "advanced_settings_proxy_headers_title": "عناوين الوكيل المخصصة [تجريبية]", - "advanced_settings_readonly_mode_subtitle": "تتيح هذه الميزة وضع العرض فقط، حيث يمكن للمستخدم معاينة الصور فقط، بينما يتم تعطيل جميع الخيارات الأخرى مثل تحديد عدة صور، أو مشاركتها، أو بثها، أو حذفها. يمكن تفعيل/تعطيل وضع العرض فقط من خلال صورة المستخدم في الشاشة الرئيسية", - "advanced_settings_readonly_mode_title": "وضع القراءة فقط", - "advanced_settings_self_signed_ssl_subtitle": "تخطي التحقق من شهادة SSL لخادم النقطة النهائي. مكلوب للشهادات الموقعة ذاتيا.", - "advanced_settings_self_signed_ssl_title": "السماح بشهادات SSL الموقعة ذاتيًا [تجريبية]", - "advanced_settings_sync_remote_deletions_subtitle": "حذف او استعادة تلقائي للاصول على هذا الجهاز عند تنفيذ العملية على الويب", - "advanced_settings_sync_remote_deletions_title": "مزامنة عمليات الحذف عن بعد [تجريبي]", - "advanced_settings_tile_subtitle": "إعدادات المستخدم المتقدمة", - "advanced_settings_troubleshooting_subtitle": "تمكين الميزات الإضافية لاستكشاف الأخطاء وإصلاحها", - "advanced_settings_troubleshooting_title": "استكشاف الأخطاء وإصلاحها", - "age_months": "عمر {months, plural, one {# شهر} other {# أشهر}}", - "age_year_months": "عمر سنة واحدة، {months, plural, one {# شهر} other {# أشهر}}", - "age_years": "{years, plural, other {العمر #}}", - "album": "البوم", - "album_added": "تمت إضافة الألبوم", - "album_added_notification_setting_description": "تلقي إشعارًا بالبريد الإلكتروني عند إضافتك إلى ألبوم مشترك", - "album_cover_updated": "تم تحديث غلاف الألبوم", - "album_delete_confirmation": "هل أنت متأكد أنك تريد حذف الألبوم {album}؟", - "album_delete_confirmation_description": "إذا تمت مشاركة هذا الألبوم، فلن يتمكن المستخدمون الآخرون من الوصول إليه بعد الآن.", - "album_deleted": "تم حذف الالبوم", - "album_info_card_backup_album_excluded": "مستبعد", - "album_info_card_backup_album_included": "متضمنة", - "album_info_updated": "تم تحديث معلومات الألبوم", - "album_leave": "هل تريد مغادرة الألبوم؟", - "album_leave_confirmation": "هل أنت متأكد أنك تريد مغادرة {album}؟", - "album_name": "اسم الألبوم", - "album_options": "إعدادات الألبوم", - "album_remove_user": "هل ترغب في إزالة المستخدم؟", - "album_remove_user_confirmation": "هل أنت متأكد أنك تريد إزالة {user}؟", - "album_search_not_found": "لم يتم ايجاد البوم مطابق لبحثك", - "album_selected": "اختير البوم", - "album_share_no_users": "يبدو أنك قمت بمشاركة هذا الألبوم مع جميع المستخدمين أو ليس لديك أي مستخدم للمشاركة معه.", - "album_summary": "ملخص الألبوم", - "album_updated": "تم تحديث الألبوم", - "album_updated_setting_description": "تلقي إشعارًا عبر البريد الإلكتروني عندما يحتوي الألبوم المشترك على محتويات جديدة", - "album_upload_assets": "رفع الاصول من جهاز الكومبيوتر الخاص بك و اضافتها الى البوم", - "album_user_left": "تم ترك {album}", - "album_user_removed": "تم إزالة {user}", - "album_viewer_appbar_delete_confirm": "هل أنت متأكد أنك تريد حذف هذا الألبوم من حسابك؟", - "album_viewer_appbar_share_err_delete": "فشل في حذف الألبوم", - "album_viewer_appbar_share_err_leave": "فشل في ترك الألبوم", - "album_viewer_appbar_share_err_remove": "هناك مشاكل في إزالة الأصول من الألبوم", - "album_viewer_appbar_share_err_title": "فشل في تغيير عنوان الألبوم", - "album_viewer_appbar_share_leave": "ترك الألبوم", - "album_viewer_appbar_share_to": "حصة ل", - "album_viewer_page_share_add_users": "اضافة مستخدمين", - "album_with_link_access": "السماح لأي شخص لديه الرابط برؤية الصور والأشخاص الموجودين في هذا الألبوم.", - "albums": "الألبومات", - "albums_count": "{count, plural, one {{count, number} ألبوم} other {{count, number} ألبومات}}", - "albums_default_sort_order": "ترتيب الألبوم الافتراضي", - "albums_default_sort_order_description": "ترتيب فرز الأصول الأولي عند إنشاء ألبومات جديدة.", - "albums_feature_description": "مجموعة من الأصول التي يمكن مشاركتها مع مستخدمين آخرين.", - "albums_on_device_count": "عدد الالبومات على الجهاز ({count})", - "albums_selected": "{count, plural, one {# البوم مختار} other {# البومات مختارة}}", - "all": "الكل", - "all_albums": "جميع الألبومات", - "all_people": "جميع الأشخاص", - "all_photos": "جميع الصور", - "all_videos": "جميع الفيديوهات", - "allow_dark_mode": "السماح بالوضع المعتم", - "allow_edits": "إسمح بالتعديل", - "allow_public_user_to_download": "السماح لأي مستخدم عام بالتنزيل", - "allow_public_user_to_upload": "السماح للمستخدم العام بالرفع", - "allowed": "مسموح", - "alt_text_qr_code": "صورة رمز الاستجابة السريعة (QR)", - "always_keep": "دائما حافظ على", - "always_keep_photos_hint": "سيحتفظ تحرير المساحة بجميع الصور على هذا الجهاز.", - "always_keep_videos_hint": "سيحتفظ تحرير المساحة بجميع الفديوات على هذا الجهاز.", - "anti_clockwise": "عكس اتجاه عقارب الساعة", - "api_key": "مفتاح API", - "api_key_description": "سيتم عرض هذه القيمة مرة واحدة فقط. يرجى التأكد من نسخها قبل إغلاق النافذة.", - "api_key_empty": "يجب ألا يكون اسم مفتاح API فارغًا", - "api_keys": "مفاتيح API", - "app_architecture_variant": "متغير (الهندسة المعمارية)", - "app_bar_signout_dialog_content": "هل أنت متأكد أنك تريد تسجيل الخروج؟", - "app_bar_signout_dialog_ok": "نعم", - "app_bar_signout_dialog_title": "خروج", - "app_download_links": "روابط تحميل التطبيق", - "app_settings": "إعدادات التطبيق", - "app_stores": "متاجر التطبيقات", - "app_update_available": "تحديث التطبيق متاح", - "appears_in": "يظهر في", - "apply_count": "تطبيق ({count, number})", - "archive": "الأرشيف", - "archive_action_prompt": "{count} اضيف إلى الارشيف", - "archive_or_unarchive_photo": "أرشفة الصورة أو إلغاء أرشفتها", - "archive_page_no_archived_assets": "لم يتم العثور على الأصول المؤرشفة", - "archive_page_title": "ارشيف ({count})", - "archive_size": "حجم الأرشيف", - "archive_size_description": "تكوين حجم الأرشيف للتنزيلات (بالجيجابايت)", - "archived": "مؤرشفة", - "archived_count": "{count, plural, other {الأرشيف #}}", - "are_these_the_same_person": "هل هؤلاء هم نفس الشخص؟", - "are_you_sure_to_do_this": "هل انت متأكد من أنك تريد أن تفعل هذا؟", - "array_field_not_fully_supported": "حقول المصفوفة تتطلب تعديل يدوي لJSON", - "asset_action_delete_err_read_only": "لا يمكن حذف الأصول ذات للقراءة فقط، وسوف يتم التخطي", - "asset_action_share_err_offline": "لا يمكن جلب الأصول غير المتصلة بالإنترنت، وسوف يتم التخطي", - "asset_added_to_album": "تمت إضافته إلى الألبوم", - "asset_adding_to_album": "جارٍ الإضافة إلى الألبوم…", - "asset_created": "انشئ اصل", - "asset_description_updated": "تم تحديث وصف المحتوى", - "asset_filename_is_offline": "الأصل {filename} غير متصل", - "asset_has_unassigned_faces": "يحتوي الأصل على وجوه غير مخصصة", - "asset_hashing": "التجزئة…", - "asset_list_group_by_sub_title": "تنظيم بواسطة", - "asset_list_layout_settings_dynamic_layout_title": "تخطيط ديناميكي", - "asset_list_layout_settings_group_automatically": "تلقائي", - "asset_list_layout_settings_group_by": "مجموعة الأصول حسب", - "asset_list_layout_settings_group_by_month_day": "شهر + يوم", - "asset_list_layout_sub_title": "تصميم", - "asset_list_settings_subtitle": "إعدادات تخطيط شبكة الصور", - "asset_list_settings_title": "شبكة الصور", - "asset_not_found_on_device_android": "الاصل لم يتم ايجاده في الجهاز", - "asset_not_found_on_device_ios": "الأصل لم يتم ايجاده في الجهاز. اذا تستخدم خدمة iCloud, فالأصل قد لا يتم الوصول له بسبب ملف متضارب مخزون في iCloud", - "asset_not_found_on_icloud": "الأصل لم يتم ايجاده في الجهاز, الأصل قد لا يتم الوصول له بسبب ملف متضارب مخزون في iCloud", - "asset_offline": "المحتوى غير اتصال", - "asset_offline_description": "لم يعد هذا الأصل الخارجي موجودًا على القرص. يرجى الاتصال بمسؤول Immich للحصول على المساعدة.", - "asset_restored_successfully": "تم استعادة الاصل بنجاح", - "asset_skipped": "تم تخطيه", - "asset_skipped_in_trash": "في سلة المهملات", - "asset_trashed": "اصول محذوفة", - "asset_troubleshoot": "استكشاف مشاكل الأصول", - "asset_uploaded": "تم الرفع", - "asset_uploading": "جارٍ الرفع…", - "asset_viewer_settings_subtitle": "إدارة إعدادات عارض المعرض الخاص بك", - "asset_viewer_settings_title": "عارض الأصول", - "assets": "المحتويات", - "assets_added_count": "تمت إضافة {count, plural, one {# محتوى} other {# محتويات}}", - "assets_added_to_album_count": "تمت إضافة {count, plural, one {# الأصل} other {# الأصول}} إلى الألبوم", - "assets_added_to_albums_count": "تمت اضافة {assetTotal, plural, one {# اصل} other {# اصول}} to {albumTotal, plural, one {# البوم} other {# البومات}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} لايمكن اضافته الى الالبوم", - "assets_cannot_be_added_to_albums": "{count, plural, one {اصل} other {اصول}} لا يمكن إضافته إلى أي من الألبومات", - "assets_count": "{count, plural, one {# محتوى} other {# محتويات}}", - "assets_deleted_permanently": "{count} الاص(و)ل المحذوف(ه) بشكل دائم", - "assets_deleted_permanently_from_server": "{count} الاص(و)ل المحذوف(ه) بشكل دائمي من خادم Immich", - "assets_downloaded_failed": "{count, plural, one {تم التحميل # ملف - {error} ملف فشل} other {تم التحميل # ملفات - {error} ملفات فشلت}}", - "assets_downloaded_successfully": "{count, plural, one {تم التحميل # ملف بنجاح} other {تم التحميل # ملفات بنجاح}}", - "assets_moved_to_trash_count": "تم نقل {count, plural, one {# محتوى} other {# محتويات}} إلى سلة المهملات", - "assets_permanently_deleted_count": "تم حذف {count, plural, one {# هذا المحتوى} other {# هذه المحتويات}} بشكل دائم", - "assets_removed_count": "تمت إزالة {count, plural, one {# محتوى} other {# محتويات}}", - "assets_removed_permanently_from_device": "{count} الاص(و)ل محذوف(ه) من الجهاز", - "assets_restore_confirmation": "هل أنت متأكد من أنك تريد استعادة جميع الأصول المحذوفة؟ لا يمكنك التراجع عن هذا الإجراء! لاحظ أنه لا يمكن استعادة أي أصول غير متصلة بهذه الطريقة.", - "assets_restored_count": "تمت استعادة {count, plural, one {# محتوى} other {# محتويات}}", - "assets_restored_successfully": "{count} الاص(و)ل المستعاد(ه) بنجاح", - "assets_trashed": "{count} الاصل(و) ل المنقوله الى سلة المهملات", - "assets_trashed_count": "تم إرسال {count, plural, one {# محتوى} other {# محتويات}} إلى سلة المهملات", - "assets_trashed_from_server": "{count} الاص(و)ل المنقولة الى سلة المهملات من خادم Immich", - "assets_were_part_of_album_count": "{count, plural, one {هذا المحتوى} other {هذه المحتويات}} في الألبوم بالفعل", - "assets_were_part_of_albums_count": "{count, plural, one {اصل هو} other {اصول هي}}بالفعل جزء من الألبومات", - "authorized_devices": "الأجهزه المخولة", - "automatic_endpoint_switching_subtitle": "اتصل محليا من خلال شبكه Wi-Fi عند توفرها و استخدم اتصالات بديله في الاماكن الاخرى", - "automatic_endpoint_switching_title": "تبديل URL تلقائي", - "autoplay_slideshow": "تشغيل تلقائي لعرض الشرائح", - "back": "خلف", - "back_close_deselect": "الرجوع أو الإغلاق أو إلغاء التحديد", - "background_backup_running_error": "يتم تشغيل النسخ الاحتياطي في الخلفية حاليًا، ولا يمكن بدء النسخ الاحتياطي اليدوي", - "background_location_permission": "اذن الوصول للموقع في الخلفية", - "background_location_permission_content": "للتمكن من تبديل الشبكه بالخلفية، Immich يحتاج*دائما* للحصول على موقع دقيق ليتمكن التطبيق من قرائة اسم شبكة الWi-Fi", - "background_options": "خيارات الخلفية", - "backup": "نسخ احتياطي", - "backup_album_selection_page_albums_device": "الالبومات على الجهاز ({count})", - "backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء", - "backup_album_selection_page_assets_scatter": "يمكن أن تنتشر الأصول عبر ألبومات متعددة. وبالتالي، يمكن تضمين الألبومات أو استبعادها أثناء عملية النسخ الاحتياطي.", - "backup_album_selection_page_select_albums": "حدد الألبومات", - "backup_album_selection_page_selection_info": "معلومات الاختيار", - "backup_album_selection_page_total_assets": "إجمالي الأصول الفريدة", - "backup_albums_sync": "مزامنة ألبومات النسخ الاحتياطي", - "backup_all": "الجميع", - "backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة…", - "backup_background_service_complete_notification": "تم الانتهاء من النسخ الاحتياطي للأصول", - "backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة…", - "backup_background_service_current_upload_notification": "تحميل {filename}", - "backup_background_service_default_notification": "التحقق من الأصول الجديدة…", - "backup_background_service_error_title": "خطأ في النسخ الاحتياطي", - "backup_background_service_in_progress_notification": "النسخ الاحتياطي للأصول الخاصة بك…", - "backup_background_service_upload_failure_notification": "فشل تحميل {filename}", - "backup_controller_page_albums": "ألبومات احتياطية", - "backup_controller_page_background_app_refresh_disabled_content": "قم بتمكين تحديث تطبيق الخلفية في الإعدادات > عام > تحديث تطبيق الخلفية لاستخدام النسخ الاحتياطي في الخلفية.", - "backup_controller_page_background_app_refresh_disabled_title": "تم تعطيل تحديث التطبيق في الخلفية", - "backup_controller_page_background_app_refresh_enable_button_text": "اذهب للاعدادات", - "backup_controller_page_background_battery_info_link": "أرني كيف", - "backup_controller_page_background_battery_info_message": "للحصول على أفضل تجربة نسخ احتياطي في الخلفية، يرجى تعطيل أي تحسينات للبطارية تقيد نشاط الخلفية لـ تطبيق.\n\nنظرًا لأن هذا خاص بالجهاز، يرجى البحث عن المعلومات المطلوبة للشركة المصنعة لجهازك.", - "backup_controller_page_background_battery_info_ok": "نعم", - "backup_controller_page_background_battery_info_title": "تحسين البطارية", - "backup_controller_page_background_charging": "فقط أثناء الشحن", - "backup_controller_page_background_configure_error": "فشل في تكوين خدمة الخلفية", - "backup_controller_page_background_delay": "تاخير الخزن التلقائي للاصول: {duration}", - "backup_controller_page_background_description": "قم بتشغيل خدمة الخلفية لإجراء نسخ احتياطي لأي أصول جديدة تلقائيًا دون الحاجة إلى فتح التطبيق", - "backup_controller_page_background_is_off": "تم إيقاف النسخ الاحتياطي التلقائي للخلفية", - "backup_controller_page_background_is_on": "النسخ الاحتياطي التلقائي للخلفية قيد التشغيل", - "backup_controller_page_background_turn_off": "قم بإيقاف تشغيل خدمة الخلفية", - "backup_controller_page_background_turn_on": "قم بتشغيل خدمة الخلفية", - "backup_controller_page_background_wifi": "فقط على Wi-Fi", - "backup_controller_page_backup": "نسخ احتياطي", - "backup_controller_page_backup_selected": "المحدد: ", - "backup_controller_page_backup_sub": "النسخ الاحتياطي للصور ومقاطع الفيديو", - "backup_controller_page_created": "انشئ في :{date}", - "backup_controller_page_desc_backup": "قم بتشغيل النسخ الاحتياطي الأمامي لتحميل الأصول الجديدة تلقائيًا إلى الخادم عند فتح التطبيق.", - "backup_controller_page_excluded": "مستبعد: ", - "backup_controller_page_failed": "فشل ({count})", - "backup_controller_page_filename": "اسم الملف : {filename} [{size}]", - "backup_controller_page_id": "هوية: {id}", - "backup_controller_page_info": "معلومات النسخ الاحتياطي", - "backup_controller_page_none_selected": "لم يتم التحديد", - "backup_controller_page_remainder": "بقية", - "backup_controller_page_remainder_sub": "الصور ومقاطع الفيديو المتبقية للنسخ الاحتياطي من التحديد", - "backup_controller_page_server_storage": "ذاكرة الجهاز", - "backup_controller_page_start_backup": "بدء النسخ الاحتياطي", - "backup_controller_page_status_off": "النسخة الاحتياطية التلقائية غير فعالة", - "backup_controller_page_status_on": "النسخة الاحتياطية التلقائية فعالة", - "backup_controller_page_storage_format": "{used} من {total} مستخدم", - "backup_controller_page_to_backup": "الألبومات التي سيتم نسخها احتياطيا", - "backup_controller_page_total_sub": "جميع الصور ومقاطع الفيديو الفريدة من ألبومات مختارة", - "backup_controller_page_turn_off": "قم بإيقاف تشغيل النسخ الاحتياطي المقدمة", - "backup_controller_page_turn_on": "قم بتشغيل النسخ الاحتياطي المقدمة", - "backup_controller_page_uploading_file_info": "تحميل معلومات الملف", - "backup_err_only_album": "لا يمكن إزالة الألبوم الوحيد", - "backup_error_sync_failed": "فشل المزامنة. لا يمكن معالجة النسخ الاحتياطي.", - "backup_info_card_assets": "أصول", - "backup_manual_cancelled": "ملغي", - "backup_manual_in_progress": "قيد التحميل حاول مره اخرى", - "backup_manual_success": "نجاح", - "backup_manual_title": "حالة التحميل", - "backup_options": "خيارات النسخ الاحتياطي", - "backup_options_page_title": "خيارات النسخ الاحتياطي", - "backup_setting_subtitle": "ادارة اعدادات التحميل في الخلفية والمقدمة", - "backup_settings_subtitle": "إدارة إعدادات التحميل", - "backup_upload_details_page_more_details": "اضغط لتفاصيل اضافية", - "backward": "الى الوراء", - "biometric_auth_enabled": "المصادقة البايومترية مفعله", - "biometric_locked_out": "لقد قفلت عنك المصادقة البيومترية", - "biometric_no_options": "لا توجد خيارات بايومترية متوفرة", - "biometric_not_available": "االمصادقة البيومترية غير متاحة على هذا الجهاز", - "birthdate_saved": "تم حفظ تاريخ الميلاد بنجاح", - "birthdate_set_description": "يتم استخدام تاريخ الميلاد لحساب عمر هذا الشخص وقت التقاط الصورة.", - "blurred_background": "خلفية مشوشة", - "bugs_and_feature_requests": "الأخطاء وطلبات الميزات", - "build": "يبني", - "build_image": "بناء الصورة", - "bulk_delete_duplicates_confirmation": "هل أنت متأكد من أنك تريد حذف {count, plural, one {# محتوى مكرر} other {# محتويات مكررة}} بالجملة؟ سيحتفظ هذا بأكبر محتوى من كل مجموعة ويحذف جميع النسخ المكررة الأخرى بشكل دائم. لا يمكنك التراجع عن هذا الإجراء!", - "bulk_keep_duplicates_confirmation": "هل أنت متأكد من أنك تريد الاحتفاظ بـ {count, plural, one {# محتوى مكرر} other {# محتويات مكررة}}؟ سيؤدي هذا إلى حل جميع مجموعات النسخ المكررة دون حذف أي شيء.", - "bulk_trash_duplicates_confirmation": "هل أنت متأكد من أنك تريد إرسال {count, plural, one {# محتوى مكرر} other {# محتويات مكررة}} إلى سلة المهملات ؟ سيحتفظ هذا بأكبر محتوى من كل مجموعة ويرسل جميع النسخ المكررة الأخرى إلى سلة المهملات.", - "buy": "شراء immich", - "cache_settings_clear_cache_button": "مسح ذاكرة التخزين المؤقت", - "cache_settings_clear_cache_button_title": "يقوم بمسح ذاكرة التخزين المؤقت للتطبيق.سيؤثر هذا بشكل كبير على أداء التطبيق حتى إعادة بناء ذاكرة التخزين المؤقت.", - "cache_settings_duplicated_assets_clear_button": "واضح", - "cache_settings_duplicated_assets_subtitle": "الصور والفيديوهات اللتي تم تجاهلها في التطبيق", - "cache_settings_duplicated_assets_title": "الاصول المكررة ({count})", - "cache_settings_statistics_album": "مكتبه الصور المصغره", - "cache_settings_statistics_full": "صور كاملة", - "cache_settings_statistics_shared": "صورة ألبوم مشتركة", - "cache_settings_statistics_thumbnail": "الصورة المصغرة", - "cache_settings_statistics_title": "استخدام ذاكرة التخزين المؤقت", - "cache_settings_subtitle": "تحكم في سلوك التخزين المؤقت لتطبيق Immich الجوال", - "cache_settings_tile_subtitle": "التحكم في سلوك التخزين المحلي", - "cache_settings_tile_title": "التخزين المحلي", - "cache_settings_title": "إعدادات التخزين المؤقت", - "camera": "الكاميرا", - "camera_brand": "علامة الكاميرا التجارية", - "camera_model": "طراز الكاميرا", - "cancel": "إلغاء", - "cancel_search": "الغاء البحث", - "canceled": "تم الالغاء", - "canceling": "جارِ الالغاء", - "cannot_merge_people": "لا يمكن دمج الأشخاص", - "cannot_undo_this_action": "لا يمكنك التراجع عن هذا الإجراء!", - "cannot_update_the_description": "لا يمكن تحديث الوصف", - "cast": "بث", - "cast_description": "ضبط وجهات البث المتوفرة", - "change_date": "غيّر التاريخ", - "change_description": "تغيير الوصف", - "change_display_order": "تغيير ترتيب العرض", - "change_expiration_time": "تغيير وقت انتهاء الصلاحية", - "change_location": "غيّر الموقع", - "change_name": "تغيير الإسم", - "change_name_successfully": "تم تغيير الاسم بنجاح", - "change_password": "تغيير كلمة المرور", - "change_password_description": "هذه إما هي المرة الأولى التي تقوم فيها بتسجيل الدخول إلى النظام أو أنه تم تقديم طلب لتغيير كلمة المرور الخاصة بك. الرجاء إدخال كلمة المرور الجديدة أدناه.", - "change_password_form_confirm_password": "تأكيد كلمة المرور", - "change_password_form_description": "مرحبًا {name}،\n\nاما ان تكون هذه هي المرة الأولى التي تقوم فيها بالتسجيل في النظام أو تم تقديم طلب لتغيير كلمة المرور الخاصة بك. الرجاء إدخال كلمة المرور الجديدة أدناه.", - "change_password_form_log_out": "تسجيل الخروج من جميع الأجهزة الأخرى", - "change_password_form_log_out_description": "يُنصح بتسجيل الخروج من جميع الأجهزة الأخرى", - "change_password_form_new_password": "كلمة المرور الجديدة", - "change_password_form_password_mismatch": "كلمة المرور غير مطابقة", - "change_password_form_reenter_new_password": "أعد إدخال كلمة مرور جديدة", - "change_pin_code": "تغيير رمز PIN", - "change_trigger": "تغيير المفعل", - "change_trigger_prompt": "هل انت متاكد انك تريد تغيير المفعل؟ هذا سيزيل كل الاجرائات والتصفيات.", - "change_your_password": "غير كلمة المرور الخاصة بك", - "changed_visibility_successfully": "تم تغيير الرؤية بنجاح", - "charging": "الشحن", - "charging_requirement_mobile_backup": "يتطلب النسخ الاحتياطي في الخلفية أن يكون الجهاز قيد الشحن", - "check_corrupt_asset_backup": "التحقق من وجود نسخ احتياطية فاسدة للاصول", - "check_corrupt_asset_backup_button": "اجراء فحص", - "check_corrupt_asset_backup_description": "قم بإجراء هذا الفحص فقط عبر شبكة Wi-Fi وبعد نسخ جميع الأصول احتياطيًا. قد يستغرق الإجراء بضع دقائق.", - "check_logs": "تحقق من السجلات", - "checksum": "مجموع التحقق", - "choose_matching_people_to_merge": "اختر الأشخاص المتطابقين لدمجهم", - "city": "المدينة", - "cleanup_confirm_description": "Immich وجد {count} اصول (انشئت قبل {date}) تم خزنها احتياطيا الى الخادم. ازالة النسخ المحلية من هذا الجهاز?", - "cleanup_confirm_prompt_title": "ازالة من هذا الجهاز؟", - "cleanup_deleted_assets": "تم نقل {count} اصول الى سلة المهملات", - "cleanup_deleting": "جاري النقل الى المهملات...", - "cleanup_found_assets": "تم ايجاد {count} اصول تم خزنها احتياطيا", - "cleanup_found_assets_with_size": "تم العثور عل {count} عناصر تم خزنها احتياطيا ({size})", - "cleanup_icloud_shared_albums_excluded": "البومات iCloud المشاركة مستثناة من البحث", - "cleanup_no_assets_found": "­لم يتم ايجاد اصول تطابق المعايير. بالاضافه. تحرير المساحة يمكن ان يحذف فقط العناصر التي تم خزنها احتياطياً الى الخادم", - "cleanup_preview_title": "اصول ليتم ازالتها ({count})", - "cleanup_step3_description": "ابحث عن اصول تم خزنها احتياطيا تطابق بياناتك و احتفظ بالاعدادات.", - "cleanup_step4_summary": "{count} اصول (أنشأت قبل {date}) ليتم ازالتها من جهازك المحلي. ستظل الصور متاحة من خلال تطبيق Immich .", - "cleanup_trash_hint": "لاستعادة مساحة التخزين بالكامل، افتح تطبيق معرض النظام وأفرغ سلة المهملات", - "clear": "إخلاء", - "clear_all": "إخلاء الكل", - "clear_all_recent_searches": "مسح جميع عمليات البحث الأخيرة", - "clear_file_cache": "مسح ذاكرة التخزين المؤقت للملفات", - "clear_message": "إخلاء الرسالة", - "clear_value": "إخلاء القيمة", - "client_cert_dialog_msg_confirm": "حسنا", - "client_cert_enter_password": "ادخل كلمة سر", - "client_cert_import": "استيراد", - "client_cert_import_success_msg": "تم استيراد شهادة العميل", - "client_cert_invalid_msg": "ملف شهادة عميل غير صالحة او كلمة سر غير صحيحة", - "client_cert_remove_msg": "تم ازالة شهادة العميل", - "client_cert_subtitle": "يدعم صيغ PKCS12 (.p12, .pfx)فقط. استيراد/ازالة الشهادات متاح فقط قبل تسجيل الدخول", - "client_cert_title": "شهادة مستخدم SSL [تجريبية]", - "clockwise": "باتجاه عقارب الساعة", - "close": "إغلاق", - "collapse": "طي", - "collapse_all": "طيّ الكل", - "color": "اللون", - "color_theme": "نمط الألوان", - "command": "امر", - "comment_deleted": "تم حذف التعليق", - "comment_options": "خيارات التعليق", - "comments_and_likes": "التعليقات والإعجابات", - "comments_are_disabled": "التعليقات معطلة", - "common_create_new_album": "إنشاء ألبوم جديد", - "completed": "اكتمل", - "confirm": "تأكيد", - "confirm_admin_password": "تأكيد كلمة مرور المسؤول", - "confirm_delete_face": "هل أنت متأكد من حذف وجه {name} من الأصول؟", - "confirm_delete_shared_link": "هل أنت متأكد أنك تريد حذف هذا الرابط المشترك؟", - "confirm_keep_this_delete_others": "سيتم حذف جميع الأصول الأخرى في المجموعة باستثناء هذا الأصل. هل أنت متأكد من أنك تريد المتابعة؟", - "confirm_new_pin_code": "ثبت رمز PIN الجديد", - "confirm_password": "تأكيد كلمة المرور", - "confirm_tag_face": "هل تريد وضع علامة على هذا الوجه {name}؟", - "confirm_tag_face_unnamed": "هل تريد وضع علامة على هذا الوجه؟", - "connected_device": "جهاز متصل", - "connected_to": "متصل ب", - "contain": "محتواة", - "context": "السياق", - "continue": "متابعة", - "control_bottom_app_bar_create_new_album": "إنشاء ألبوم جديد", - "control_bottom_app_bar_delete_from_immich": "حذف من Immich", - "control_bottom_app_bar_delete_from_local": "حذف من الجهاز", - "control_bottom_app_bar_edit_location": "تحديد الوجهة", - "control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت", - "control_bottom_app_bar_share_link": "مشاركة رابط", - "control_bottom_app_bar_share_to": "مشاركة إلى", - "control_bottom_app_bar_trash_from_immich": "حذفه ونقله في سله المهملات", - "copied_image_to_clipboard": "تم نسخ الصورة إلى الحافظة.", - "copied_to_clipboard": "نسخ إلى الحافظة!", - "copy_error": "نسخ الخطأ", - "copy_file_path": "نسخ مسار الملف", - "copy_image": "نسخ الصورة", - "copy_link": "نسخ الرابط", - "copy_link_to_clipboard": "انسخ الرابط إلى الحافظة", - "copy_password": "نسخ كلمة المرور", - "copy_to_clipboard": "نسخ إلى الحافظة", - "country": "الدولة", - "cover": "تغطي", - "covers": "أغلفة", - "create": "انشاء", - "create_album": "إنشاء ألبوم", - "create_album_page_untitled": "بدون اسم", - "create_api_key": "إنشاء مفتاح API", - "create_first_workflow": "إنشاء سير العمل الأول", - "create_library": "إنشاء مكتبة", - "create_link": "إنشاء رابط", - "create_link_to_share": "إنشاء رابط للمشاركة", - "create_link_to_share_description": "السماح لأي شخص لديه الرابط بمشاهدة الصورة (الصور) المحددة", - "create_new": "انشاء جديد", - "create_new_person": "إنشاء شخص جديد", - "create_new_person_hint": "تعيين المحتويات المحددة لشخص جديد", - "create_new_user": "إنشاء مستخدم جديد", - "create_shared_album_page_share_add_assets": "إضافة الأصول", - "create_shared_album_page_share_select_photos": "حدد الصور", - "create_shared_link": "انشاء رابط مشترك", - "create_tag": "إنشاء علامة", - "create_tag_description": "أنشئ علامة جديدة. بالنسبة للعلامات المتداخلة، يرجى إدخال المسار الكامل للعلامة بما في ذلك الخطوط المائلة للأمام.", - "create_user": "إنشاء مستخدم", - "create_workflow": "إنشاء سير العمل", - "created": "تم الإنشاء", - "created_at": "مخلوق", - "creating_linked_albums": "جاري إنشاء الألبومات المرتبطة...", - "crop": "قص", - "crop_aspect_ratio_fixed": "تم الاصلاح", - "crop_aspect_ratio_free": "حر", - "crop_aspect_ratio_original": "اصلي", - "curated_object_page_title": "أشياء", - "current_device": "الجهاز الحالي", - "current_pin_code": "رمز PIN الحالي", - "current_server_address": "عنوان الخادم الحالي", - "custom_date": "تاريخ مخصص", - "custom_locale": "لغة مخصصة", - "custom_locale_description": "تنسيق التواريخ والأرقام بناءً على اللغة والمنطقة", - "custom_url": "رابط مخصص", - "cutoff_date_description": "احتفظ بالصور من آخر…", - "cutoff_day": "{count, plural, one {يوم} other {ايام}}", - "cutoff_year": "{count, plural, one {سنة} other {سنوات}}", - "daily_title_text_date": "E ، MMM DD", - "daily_title_text_date_year": "E ، MMM DD ، yyyy", - "dark": "معتم", - "dark_theme": "تبديل المظهر الداكن", - "date": "تاريخ", - "date_after": "التارخ بعد", - "date_and_time": "التاريخ و الوقت", - "date_before": "التاريخ قبل", - "date_format": "E ، Lll D ، Y • H: MM A", - "date_of_birth_saved": "تم حفظ تاريخ الميلاد بنجاح", - "date_range": "نطاق الموعد", - "day": "يوم", - "days": "ايام", - "deduplicate_all": "إلغاء تكرار الكل", - "deduplication_criteria_1": "حجم الصورة بوحدات البايت", - "deduplication_criteria_2": "عدد بيانات EXIF", - "deduplication_info": "معلومات إلغاء البيانات المكررة", - "deduplication_info_description": "لتحديد الأصول مسبقا تلقائيا وإزالة التكرارات بكميات كبيرة، ننظر إلى:", - "default_locale": "اللغة الافتراضية", - "default_locale_description": "تنسيق التواريخ والأرقام بناءً على لغة المتصفح الخاص بك", - "delete": "حذف", - "delete_action_confirmation_message": "هل انت متأكد من حذف هذا الملف؟ هذا سؤدي الى نقل الملف الى سلة مهملات الخادم وسيتم اشعارك ان كنت تريد حذفه على الجهاز", - "delete_action_prompt": "تم حذف {count}", - "delete_album": "حذف الألبوم", - "delete_api_key_prompt": "هل أنت متأكد أنك تريد حذف مفتاح API هذا؟", - "delete_dialog_alert": "هذه العناصر سيتم حذفها بشكل دائم من Immich و من جهازك", - "delete_dialog_alert_local": "العناصر التي سيتم حذفها من جهازك ولكن تبقى موجوده في خادم Immich", - "delete_dialog_alert_local_non_backed_up": "بعض العناصر غير مدعومة بنسخة احتياطية على Immich وسيتم إزالتها نهائيًا من جهازك", - "delete_dialog_alert_remote": "العناصر التي سيتم حذفها بشكل دائم من تطبيق", - "delete_dialog_ok_force": "احذف على أي حال", - "delete_dialog_title": "الحذف بشكل نهائي", - "delete_duplicates_confirmation": "هل أنت متأكد أنك تريد حذف هذه التكرارات نهائيًا؟", - "delete_face": "حذف الوجه", - "delete_key": "حذف المفتاح", - "delete_library": "حذف المكتبة", - "delete_link": "حذف الرابط", - "delete_local_action_prompt": "تم حذف {count} من الجهاز", - "delete_local_dialog_ok_backed_up_only": "حذف النسخة الاحتياطية فقط", - "delete_local_dialog_ok_force": "احذف على أي حال", - "delete_others": "حذف الأخرى", - "delete_permanently": "حذف بشكل نهائي", - "delete_permanently_action_prompt": "تم حذف {count} بشكل نهائي", - "delete_shared_link": "حذف الرابط المشترك", - "delete_shared_link_dialog_title": "حذف الرابط المشترك", - "delete_tag": "حذف العلامة", - "delete_tag_confirmation_prompt": "هل أنت متأكد أنك تريد حذف العلامة {tagName}؟", - "delete_user": "حذف المستخدم", - "deleted_shared_link": "تم حذف الرابط المشارك", - "deletes_missing_assets": "حذف الأصول المفقودة من القرص", - "description": "وصف", - "description_input_hint_text": "اضف وصفا...", - "description_input_submit_error": "خطأ تحديث الوصف ، تحقق من السجل لمزيد من التفاصيل", - "deselect_all": "الغاء تحديد الكل", - "details": "تفاصيل", - "direction": "الإتجاه", - "disable": "ابطال", - "disabled": "معطل", - "disallow_edits": "منع التعديلات", - "discord": "دسكورد", - "discover": "اكتشف", - "discovered_devices": "اجهزة مكتشفة", - "dismiss_all_errors": "تجاهل كافة الأخطاء", - "dismiss_error": "تجاهل الخطأ", - "display_options": "عرض الخيارات", - "display_order": "عرض الترتيب", - "display_original_photos": "عرض الصور الأصلية", - "display_original_photos_setting_description": "فضل عرض الصورة الأصلية عند عرض المحتويات بدلاً من الصور المصغرة عندما يكون المحتوى الأصلي متوافقًا مع الويب. قد يؤدي ذلك إلى بطءٍ في سرعات عرض الصور.", - "do_not_show_again": "لا تُظهر هذه الرسالة مرة آخرى", - "documentation": "الوثائق", - "done": "تم", - "download": "تنزيل", - "download_action_prompt": "يتم تنزيل {count} ملف", - "download_canceled": "الغي التنزيل", - "download_complete": "اكتمل التنزيل", - "download_enqueue": "تنزيل في قائمة الانتظار", - "download_error": "خطا في التنزيل", - "download_failed": "فشل التنزيل", - "download_finished": "انتهى التنزيل", - "download_include_embedded_motion_videos": "مقاطع الفيديو المدمجة", - "download_include_embedded_motion_videos_description": "تضمين مقاطع الفيديو المضمنة في الصور المتحركة كملف منفصل", - "download_notfound": "لم يعثر على التنزيل", - "download_original": "تحميل الأصلي", - "download_paused": "توقف التنزيل", - "download_settings": "التنزيل", - "download_settings_description": "إدارة الإعدادات المتعلقة بتنزيل المحتويات", - "download_started": "بدأ التنزيل", - "download_sucess": "نجح التنزيل", - "download_sucess_android": "تم تحميل الوسائط الى DCIM/Immich", - "download_waiting_to_retry": "الانتظار لاعادة المحاولة", - "downloading": "جارٍ التنزيل", - "downloading_asset_filename": "جاري تنزيل الاصل {filename}", - "downloading_from_icloud": "التنزيل من iCloud", - "downloading_media": "تنزيل الوسائط", - "drop_files_to_upload": "قم بإسقاط الملفات في أي مكان لرفعها", - "duplicates": "التكرارات", - "duplicates_description": "قم بحل كل مجموعة من خلال الإشارة إلى التكرارات، إن وجدت", - "duration": "المدة", - "edit": "تعديل", - "edit_album": "تعديل الألبوم", - "edit_avatar": "تعديل الصورة الشخصية", - "edit_birthday": "تعديل تاريخ الميلاد", - "edit_date": "تعديل التاريخ", - "edit_date_and_time": "تعديل التاريخ والوقت", - "edit_date_and_time_action_prompt": "تم تعديل التاريخ والوقت ل{count} ملف(ات)", - "edit_date_and_time_by_offset": "تعديل التاريخ حسب قيمة ازاحة معينة", - "edit_date_and_time_by_offset_interval": "نطاق التاريخ الجديد: {from} - {to}", - "edit_description": "تعديل الوصف", - "edit_description_prompt": "الرجاء اختيار وصف جديد:", - "edit_exclusion_pattern": "تعديل نمط الاستبعاد", - "edit_faces": "تعديل الوجوه", - "edit_key": "تعديل المفتاح", - "edit_link": "تغيير الرابط", - "edit_location": "تعديل الموقع", - "edit_location_action_prompt": "{count} موقع تم تعديله", - "edit_location_dialog_title": "موقع", - "edit_name": "تعديل الاسم", - "edit_people": "تعديل الأشخاص", - "edit_tag": "تعديل العلامة", - "edit_title": "تعديل العنوان", - "edit_user": "تعديل المستخدم", - "edit_workflow": "تعديل سير العمل", - "editor": "محرر", - "editor_close_without_save_prompt": "لن يتم حفظ التغييرات", - "editor_close_without_save_title": "إغلاق المحرر؟", - "editor_confirm_reset_all_changes": "هل أنت متأكد من إعادة ضبط جميع التغييرات؟", - "editor_flip_horizontal": "اقلب أفقيًا", - "editor_flip_vertical": "اقلب عموديًا", - "editor_orientation": "اتجاه", - "editor_reset_all_changes": "اعادة ظبط التغييرات", - "editor_rotate_left": "أدر 90° عكس اتجاه عقارب الساعة", - "editor_rotate_right": "ادر 90° باتجاه عقارب الساعة", - "email": "البريد الإلكتروني", - "email_notifications": "تنبيهات البريد الالكتروني", - "empty_folder": "هذا المجلد فارغ", - "empty_trash": "أفرغ سلة المهملات", - "empty_trash_confirmation": "هل أنت متأكد أنك تريد إفراغ سلة المهملات؟ سيؤدي هذا إلى إزالة جميع المحتويات الموجودة في سلة المهملات بشكل نهائي من Immich.\nلا يمكنك التراجع عن هذا الإجراء!", - "enable": "تفعيل", - "enable_backup": "تشغيل النسخ الاحتياطي", - "enable_biometric_auth_description": "أدخل رمز PIN الخاص بك لتمكين المصادقة البيومترية", - "enabled": "مفعَل", - "end_date": "تاريخ الإنتهاء", - "enqueued": "مُدرج في الطابور", - "enter_wifi_name": "ادخل اسم Wi-Fi", - "enter_your_pin_code": "أدخل رمز PIN الخاص بك", - "enter_your_pin_code_subtitle": "أدخل رمز PIN الخاص بك للوصول إلى المجلد المقفل", - "error": "خطأ", - "error_change_sort_album": "فشل في تغيير ترتيب الألبوم", - "error_delete_face": "حدث خطأ في حذف الوجه من الأصول", - "error_getting_places": "خطأ أثناء استرجاع بيانات المواقع", - "error_loading_albums": "خطأ في تحميل الالبومات", - "error_loading_image": "حدث خطأ أثناء تحميل الصورة", - "error_loading_partners": "خطأ بتحميل بيانات الشركاء: {error}", - "error_retrieving_asset_information": "خطأ في استعادة معلومات الاصل", - "error_saving_image": "خطأ: {error}", - "error_tag_face_bounding_box": "خطأ في وضع علامة على الوجه - لا يمكن الحصول على إحداثيات المربع المحيط", - "error_title": "خطأ - حدث خللٌ ما", - "error_while_navigating": "حدث خطأ أثناء الانتقال إلى الأصل", - "errors": { - "cannot_navigate_next_asset": "لا يمكن الانتقال إلى المحتوى التالي", - "cannot_navigate_previous_asset": "لا يمكن الانتقال إلى المحتوى السابق", - "cant_apply_changes": "لا يمكن تطبيق التغييرات", - "cant_change_activity": "لا يمكن {enabled, select, true {تعطيل} other {تفعيل}} النشاط", - "cant_change_asset_favorite": "لا يمكن تغيير المفضلة لمحتوى", - "cant_change_metadata_assets_count": "لا يمكن تغيير البيانات الوصفية لـ {count, plural, one {# محتوى} other {# محتويات}}", - "cant_get_faces": "لا يمكن الحصول على الوجوه", - "cant_get_number_of_comments": "لا يمكن الحصول على عدد التعليقات", - "cant_search_people": "لا يمكن البحث عن الناس", - "cant_search_places": "لا يمكن البحث عن الأماكن", - "error_adding_assets_to_album": "حدث خطأٌ أثناء إضافة المحتويات إلى الألبوم", - "error_adding_users_to_album": "حدث خطأٌ أثناء إضافة المستخدمين إلى الألبوم", - "error_deleting_shared_user": "حدث خطأٌ أثناء حذف المستخدم المشترك", - "error_downloading": "خطأٌ في تنزيل {filename}", - "error_hiding_buy_button": "خطأ في إخفاء زر الشراء", - "error_removing_assets_from_album": "خطأٌّ في إزالة المحتويات من الألبوم، تحقق من وحدة التحكم للحصول على مزيدٍ من التفاصيل", - "error_selecting_all_assets": "خطأٌ في تحديد جميع المحتويات", - "exclusion_pattern_already_exists": "نمط الاستبعاد هذا موجود مسبقًا.", - "failed_to_create_album": "فشل إنشاء الألبوم", - "failed_to_create_shared_link": "فشل إنشاء رابط مشترك", - "failed_to_edit_shared_link": "فشل تعديل الرابط المشترك", - "failed_to_get_people": "فشل في الحصول على الناس", - "failed_to_keep_this_delete_others": "فشل في الاحتفاظ بهذا الأصل وحذف الأصول الأخرى", - "failed_to_load_asset": "فشل تحميل المحتوى", - "failed_to_load_assets": "فشل تحميل المحتويات", - "failed_to_load_notifications": "فشل تحميل الإشعارات", - "failed_to_load_people": "فشل تحميل الأشخاص", - "failed_to_remove_product_key": "تعذر إزالة مفتاح المنتج", - "failed_to_reset_pin_code": "فشل اعادة تعيين رمز الPIN", - "failed_to_stack_assets": "فشل في تكديس المحتويات", - "failed_to_unstack_assets": "فشل في فصل المحتويات", - "failed_to_update_notification_status": "فشل في تحديث حالة الإشعار", - "incorrect_email_or_password": "بريد أو كلمة مرور غير صحيحة", - "library_folder_already_exists": "مسار الاستيراد موجود بالفعل.", - "paths_validation_failed": "فشل في التحقق من {paths, plural, one {# مسار} other {# مسارات}}", - "profile_picture_transparent_pixels": "لا يمكن أن تحتوي صور الملف الشخصي على أجزاء/بكسلات شفافة. يرجى التكبير و/أو تحريك الصورة.", - "quota_higher_than_disk_size": "لقد قمت بتعيين حصة نسبية أعلى من حجم القرص", - "something_went_wrong": "حدث خطأ ما", - "unable_to_add_album_users": "تعذر إضافة مستخدمين إلى الألبوم", - "unable_to_add_assets_to_shared_link": "تعذر إضافة المحتويات إلى الرابط المشترك", - "unable_to_add_comment": "تعذر إضافة التعليق", - "unable_to_add_exclusion_pattern": "تعذر إضافة نمط الإستبعاد", - "unable_to_add_partners": "تعذر إضافة الشركاء", - "unable_to_add_remove_archive": "تعذر {archived, select, true {إزالة المحتوى من} other {إضافة المحتوى إلى}} الأرشيف", - "unable_to_add_remove_favorites": "تعذر {favorite, select, true {إضافة المحتوى إلى} other {إزالة المحتوى من}} المفضلة", - "unable_to_archive_unarchive": "تعذر {archived, select, true {الأرشفة} other {الإخراج من الأرشيف}}", - "unable_to_change_album_user_role": "غير قادر على تغيير دور مستخدم الألبوم", - "unable_to_change_date": "غير قادر على تغيير التاريخ", - "unable_to_change_description": "غير قادر على تغيير الوصف", - "unable_to_change_favorite": "غير قادر على تغيير المفضلة لمحتوى", - "unable_to_change_location": "غير قادر على تغيير الموقع", - "unable_to_change_password": "غير قادر على تغيير كلمة المرور", - "unable_to_change_visibility": "غير قادر على تغيير الظهور لـ {count, plural, one {# شخص} other {# أشخاص}}", - "unable_to_complete_oauth_login": "غير قادر على إكمال تسجيل الدخول عبر OAuth", - "unable_to_connect": "غير قادر على الإتصال", - "unable_to_copy_to_clipboard": "لا يمكن النسخ إلى الحافظة، تأكد من استخدامك للصفحة عبر https", - "unable_to_create": "تعذر إنشاء سير العمل", - "unable_to_create_admin_account": "غير قادر على إنشاء حساب المسؤول", - "unable_to_create_api_key": "غير قادر على إنشاء مفتاح API جديد", - "unable_to_create_library": "غير قادر على إنشاء مكتبة", - "unable_to_create_user": "غير قادر على إنشاء المستخدم", - "unable_to_delete_album": "غير قادر على حذف الألبوم", - "unable_to_delete_asset": "غير قادر على حذف المحتوى", - "unable_to_delete_assets": "حدث خطأ أثناء حذف المحتويات", - "unable_to_delete_exclusion_pattern": "غير قادر على حذف نمط الاستبعاد", - "unable_to_delete_shared_link": "غير قادر على حذف الرابط المشترك", - "unable_to_delete_user": "غير قادر على حذف المستخدم", - "unable_to_delete_workflow": "تعذر حذف سير العمل", - "unable_to_download_files": "غير قادر على تنزيل الملفات", - "unable_to_edit_exclusion_pattern": "غير قادر على تعديل نمط الاستبعاد", - "unable_to_empty_trash": "غير قادر على إفراغ سلة المهملات", - "unable_to_enter_fullscreen": "غير قادر على الدخول إلى وضع ملء الشاشة", - "unable_to_exit_fullscreen": "غير قادر على الخروج من وضع ملء الشاشة", - "unable_to_get_comments_number": "غير قادر على الحصول على عدد التعليقات", - "unable_to_get_shared_link": "فشل الحصول على الرابط المشترك", - "unable_to_hide_person": "غير قادر على إخفاء الشخص", - "unable_to_link_motion_video": "غير قادر على ربط فيديو الحركة", - "unable_to_link_oauth_account": "غير قادر على ربط حساب OAuth", - "unable_to_log_out_all_devices": "غير قادر على تسجيل الخروج من جميع الأجهزة", - "unable_to_log_out_device": "غير قادر على تسجيل الخروج من الجهاز", - "unable_to_login_with_oauth": "غير قادر على تسجيل الدخول باستخدام OAuth", - "unable_to_play_video": "غير قادر على تشغيل الفيديو", - "unable_to_reassign_assets_existing_person": "تعذر إعادة تعيين المحتويات إلى {name, select, null {شخص موجود} other {{name}}}", - "unable_to_reassign_assets_new_person": "تعذر إعادة تعيين المحتويات لشخص جديد", - "unable_to_refresh_user": "تعذر تحديث المستخدم", - "unable_to_remove_album_users": "تعذر إزالة المستخدمين من الألبوم", - "unable_to_remove_api_key": "تعذر إزالة مفتاح API", - "unable_to_remove_assets_from_shared_link": "غير قادر على إزالة المحتويات من الرابط المشترك", - "unable_to_remove_library": "غير قادر على إزالة المكتبة", - "unable_to_remove_partner": "غير قادر على إزالة الشريك", - "unable_to_remove_reaction": "غير قادر على إزالة رد الفعل", - "unable_to_reset_password": "غير قادر على إعادة تعيين كلمة المرور", - "unable_to_reset_pin_code": "غير قادر على إعادة تعيين رمز PIN", - "unable_to_resolve_duplicate": "غير قادر على حل التكرارات", - "unable_to_restore_assets": "غير قادر على استعادة المحتويات", - "unable_to_restore_trash": "غير قادر على استعادة سلة المهملات", - "unable_to_restore_user": "غير قادر على استعادة المستخدم", - "unable_to_save_album": "غير قادر على حفظ الألبوم", - "unable_to_save_api_key": "غير قادر على حفظ مفتاح API", - "unable_to_save_date_of_birth": "غير قادر على حفظ تاريخ الميلاد", - "unable_to_save_name": "غير قادر على حفظ الاسم", - "unable_to_save_profile": "غير قادر على حفظ الملف الشخصي", - "unable_to_save_settings": "غير قادر على حفظ الإعدادات", - "unable_to_scan_libraries": "غير قادر على فحص المكتبات", - "unable_to_scan_library": "غير قادر على فحص المكتبة", - "unable_to_set_feature_photo": "غير قادر على تعيين الصورة المميزة", - "unable_to_set_profile_picture": "غير قادر على تعيين صورة الملف الشخصي", - "unable_to_set_rating": "تعذر تحديد التقييم", - "unable_to_submit_job": "غير قادر على تقديم الوظيفة", - "unable_to_trash_asset": "غير قادر على نقل المحتويات إلى سلة المهملات", - "unable_to_unlink_account": "غير قادر على إلغاء ربط الحساب", - "unable_to_unlink_motion_video": "غير قادر على إلغاء ربط فيديو الحركة", - "unable_to_update_album_cover": "غير قادر على تحديث غلاف الألبوم", - "unable_to_update_album_info": "غير قادر على تحديث معلومات الألبوم", - "unable_to_update_library": "غير قادر على تحديث المكتبة", - "unable_to_update_location": "غير قادر على تحديث الموقع", - "unable_to_update_settings": "غير قادر على تحديث الإعدادات", - "unable_to_update_timeline_display_status": "غير قادر على تحديث حالة عرض المخطط الزمني", - "unable_to_update_user": "غير قادر على تحديث المستخدم", - "unable_to_update_workflow": "تعذر تحديث سير العمل", - "unable_to_upload_file": "تعذر رفع الملف" - }, - "errors_text": "اخطاء", - "exclusion_pattern": "نمط استبعاد", - "exif": "Exif (صيغة ملف صوري قابل للتبادل)", - "exif_bottom_sheet_description": "اضف وصفا...", - "exif_bottom_sheet_description_error": "خطأ في تحديث الوصف", - "exif_bottom_sheet_details": "تفاصيل", - "exif_bottom_sheet_location": "موقع", - "exif_bottom_sheet_no_description": "لا يوجد وصف", - "exif_bottom_sheet_people": "الناس", - "exif_bottom_sheet_person_add_person": "اضف اسما", - "exit_slideshow": "خروج من العرض التقديمي", - "expand_all": "توسيع الكل", - "experimental_settings_new_asset_list_subtitle": "أعمال جارية", - "experimental_settings_new_asset_list_title": "تمكين شبكة الصور التجريبية", - "experimental_settings_subtitle": "استخدام على مسؤوليتك الخاصة!", - "experimental_settings_title": "تجريبي", - "expire_after": "تنتهي بعد", - "expired": "منتهي الصلاحية", - "expires_date": "تنتهي الصلاحية في {date}", - "explore": "استكشاف", - "explorer": "المستكشف", - "export": "تصدير", - "export_as_json": "تصدير كـ JSON", - "export_database": "تصدير قاعدة البيانات", - "export_database_description": "تصدير قاعدة البيانات من نوع SQLite", - "extension": "الإمتداد", - "external": "خارجي", - "external_libraries": "المكتبات الخارجية", - "external_network": "شبكة خارجية", - "external_network_sheet_info": "عندما لا يتواجد على شبكة Wi-Fi المفضلة، فإنه سيتصل بالخادم من خلال أول عناوين URL أدناه التي يمكنه الوصول إليها، بدءًا من الأعلى إلى الأسفل", - "face_unassigned": "غير معين", - "failed": "فشل", - "failed_count": "فشل: {count}", - "failed_to_authenticate": "فشل في المصادقة", - "failed_to_load_assets": "فشل تحميل الأصول", - "failed_to_load_folder": "فشل تحميل المجلد", - "favorite": "مفضل", - "favorite_action_prompt": "{count} اضيف إلى المفضلات", - "favorite_or_unfavorite_photo": "تفضيل أو إلغاء تفضيل الصورة", - "favorites": "المفضلة", - "favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة", - "feature_photo_updated": "تم تحديث الصورة المميزة", - "features": "الميزات", - "features_in_development": "الميزات قيد التطوير", - "features_setting_description": "إدارة ميزات التطبيق", - "file_name_or_extension": "اسم الملف أو امتداده", - "file_size": "حجم الملف", - "filename": "اسم الملف", - "filetype": "نوع الملف", - "filter": "تصفية", - "filter_description": "شروط تصفية الأصول المستهدفة", - "filter_people": "تصفية الاشخاص", - "filter_places": "تصفية الاماكن", - "filters": "التصفيات", - "find_them_fast": "يمكنك العثور عليها بسرعة بالاسم من خلال البحث", - "first": "الاول", - "fix_incorrect_match": "إصلاح المطابقة غير الصحيحة", - "folder": "مجلد", - "folder_not_found": "لم يتم العثور على المجلد", - "folders": "المجلدات", - "folders_feature_description": "تصفح عرض المجلد للصور ومقاطع الفيديو الموجودة على نظام الملفات", - "forgot_pin_code_question": "هل نسيت رمز الPIN الخاص بك؟", - "forward": "إلى الأمام", - "free_up_space": "تحرير المساحة", - "free_up_space_description": "نقل الصور والفديوات التي تم خزنها احتياطياالى سلة المهملات الخاصه بجهازك لتحرير المساحة. نسخك على اىخادم ستبقى بأمان.", - "free_up_space_settings_subtitle": "تحرير خزن الجهاز", - "full_path": "مسار كامل:{path}", - "gcast_enabled": "كوكل كاست", - "gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.", - "general": "عام", - "geolocation_instruction_location": "انقر على الاصل الذي يحتوي على إحداثيات نظام تحديد المواقع لاستخدام موقعه، أو اختر الموقع مباشرة من الخريطة", - "get_help": "الحصول على المساعدة", - "get_people_error": "خطأ استعادة الأشخاص", - "get_wifiname_error": "تعذر الحصول على اسم شبكة Wi-Fi. تأكد من منح الأذونات اللازمة واتصالك بشبكة Wi-Fi", - "getting_started": "البدء", - "go_back": "الرجوع للخلف", - "go_to_folder": "اذهب إلى المجلد", - "go_to_search": "اذهب إلى البحث", - "gps": "نظام تحديد المواقع", - "gps_missing": "لا يوجد نظام تحديد المواقع", - "grant_permission": "منح الاذن", - "group_albums_by": "تجميع الألبومات حسب...", - "group_country": "مجموعة البلد", - "group_no": "بدون تجميع", - "group_owner": "تجميع حسب المالك", - "group_places_by": "تجميع الأماكن حسب...", - "group_year": "تجميع حسب السنة", - "haptic_feedback_switch": "تمكين ردود الفعل اللمسية", - "haptic_feedback_title": "ردود فعل لمسية", - "has_quota": "محدد بحصة", - "hash_asset": "عمل Hash للأصل (للملف)", - "hashed_assets": "أصول (ملفات) تم عمل Hash لها", - "hashing": "يتم عمل Hash", - "header_settings_add_header_tip": "إضافة رأس الصفحة", - "header_settings_field_validator_msg": "القيمة لا يمكن ان تكون فارغة", - "header_settings_header_name_input": "اسم الرأس", - "header_settings_header_value_input": "قيمة الرأس", - "headers_settings_tile_title": "رؤوس وكيل مخصصة", - "height": "الطول", - "hi_user": "مرحبا {name} ({email})", - "hide_all_people": "إخفاء جميع الأشخاص", - "hide_gallery": "اخفاء المعرض", - "hide_named_person": "إخفاء الشخص {name}", - "hide_password": "اخفاء كلمة المرور", - "hide_person": "اخفاء الشخص", - "hide_schema": "اخفاء المخطط", - "hide_text_recognition": "اخفاء التعرف على النص", - "hide_unnamed_people": "إخفاء الأشخاص بدون إسم", - "home_page_add_to_album_conflicts": "تمت إضافة {added} أصول إلى الألبوم {album}. {failed} أصول موجودة بالفعل في الألبوم.", - "home_page_add_to_album_err_local": "لا يمكن إضافة الأصول المحلية إلى الألبومات حتى الآن ، سوف يتخطى", - "home_page_add_to_album_success": "تمت إضافة {added} أصول إلى الألبوم {album}.", - "home_page_album_err_partner": "لا يمكن إضافة أصول شريكة إلى ألبوم حتى الآن ، سوف يتخطى", - "home_page_archive_err_local": "لا يمكن أرشفة الأصول المحلية حتى الآن ، سوف يتخطى", - "home_page_archive_err_partner": "لا يمكن أرشفة الأصول الشريكة ، سوف يتخطى", - "home_page_building_timeline": "بناء الجدول الزمني", - "home_page_delete_err_partner": "لا يمكن حذف الأصول الشريكة ,سوف يتخطى", - "home_page_delete_remote_err_local": "الأصول المحلية في التحديد البعيد المحذوف، سوف يتخطى", - "home_page_favorite_err_local": "لا يمكن تفضيل الأصول المحلية بعد، سوف يتخطى", - "home_page_favorite_err_partner": "لا يمكن الأصول الشريكة المفضلة بعد ، سوف يتخطى", - "home_page_first_time_notice": "إذا كانت هذه هي المرة الأولى التي تستخدم فيها التطبيق، فيرجى التأكد من اختيار ألبوم (ألبومات) احتياطية حتى يتمكن المخطط الزمني من ملء الصور ومقاطع الفيديو فيه", - "home_page_locked_error_local": "لا يمكن نقل الأصول المحلية إلى المجلد المقفل، يتم التخطي", - "home_page_locked_error_partner": "لا يمكن نقل أصول الشريك إلى المجلد المقفل، يتم التخطي", - "home_page_share_err_local": "لا يمكن مشاركة الأصول المحلية عبر الرابط ، سوف يتخطى", - "home_page_upload_err_limit": "لا يمكن إلا تحميل 30 أحد الأصول في وقت واحد ، سوف يتخطى", - "host": "المضيف", - "hour": "ساعة", - "hours": "ساعات", - "id": "المعرف", - "idle": "خامل", - "ignore_icloud_photos": "تجاهل صور iCloud", - "ignore_icloud_photos_description": "الصور المخزنة في Cloud لن يتم تحميلها إلى خادم Immich", - "image": "صورة", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} تم التقاطها في {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} تم التقاطها مع {person1} في {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} تم التقاطها مع {person1} و{person2} في {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} تم التقاطها مع {person1} و{person2} و{person3} في {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} تم التقاطها مع {person1} و{person2} و{additionalCount, number} آخرين في {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} تم التقاطها في {city}، {country} في {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} تم التقاطها في {city}، {country} مع {person1} في {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} تم التقاطها في {city}، {country} مع {person1} و{person2} في {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} تم التقاطها في {city}، {country} مع {person1}، {person2}، و{person3} في {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} تم التقاطها في {city}, {country} with {person1}, {person2}, مع {additionalCount, number} آخرين في {date}", - "image_saved_successfully": "الصور حُفظت", - "image_viewer_page_state_provider_download_started": "بدأ التنزيل", - "image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح", - "image_viewer_page_state_provider_share_error": "خطأ في المشاركة", - "immich_logo": "شعار immich", - "immich_web_interface": "واجهة ويب immich", - "import_from_json": "استيراد من JSON", - "import_path": "مسار الاستيراد", - "in_albums": "في {count, plural, one {# ألبوم } other {# ألبومات}}", - "in_archive": "في الأرشيف", - "in_year": "في {year}", - "in_year_selector": "في", - "include_archived": "تشمل الأرشفة", - "include_shared_albums": "تضمين الألبومات المشتركة", - "include_shared_partner_assets": "تضمين محتويات الشريك المشتركة", - "individual_share": "حصة فردية", - "individual_shares": "المشاركات الفردية", - "info": "معلومات", - "interval": { - "day_at_onepm": "كل يوم الساعة الواحدة ظهرا", - "hours": "كل {hours, plural, one {ساعة} other {{hours, number} ساعة}}", - "night_at_midnight": "كل ليلة عند منتصف الليل", - "night_at_twoam": "كل ليلة الساعة 2 صباحا" - }, - "invalid_date": "تاريخ غير صالح", - "invalid_date_format": "صيغة تاريخ غير صالحة", - "invite_people": "دعوة الأشخاص", - "invite_to_album": "دعوة إلى الألبوم", - "ios_debug_info_fetch_ran_at": "جرت عملية الجلب في {dateTime}", - "ios_debug_info_last_sync_at": "اخر مزامنة {dateTime}", - "ios_debug_info_no_processes_queued": "لا توجد عمليات خلفية في قائمة الانتظار", - "ios_debug_info_no_sync_yet": "لم يتم تشغيل أي مهمة مزامنة في الخلفية حتى الآن", - "ios_debug_info_processes_queued": "{count, plural, one {{count} عملية خلفية ادخلتةفي طابور} other {{count} عمليات خلفية ادخلت في طابور}}", - "ios_debug_info_processing_ran_at": "المعالجة جرت في {dateTime}", - "items_count": "{count, plural, one {# عنصر} other {# عناصر}}", - "jobs": "الوظائف", - "json_editor": "محرر JSON", - "json_error": "خطأ JSON", - "keep": "احتفظ", - "keep_albums": "الاحتفاظ بالالبومات", - "keep_albums_count": "الاحتفاظ ب{count} {count, plural, one {البوم} other {البومات}}", - "keep_all": "احتفظ بالكل", - "keep_description": "اختر ما يبقى على جهازك عند تحرير المساحة.", - "keep_favorites": "الاحتفاظ بالمفضلات", - "keep_on_device": "احتفظ على الجهاز", - "keep_on_device_hint": "اختر العناصر التي تريد ابقائها على الجهاز", - "keep_this_delete_others": "احتفظ بهذا، واحذف الآخرين", - "keeping": "الاحتفاظ ب: {items}", - "kept_this_deleted_others": "تم الاحتفاظ بهذا الأصل وحذف {count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "اختصارات لوحة المفاتيح", - "language": "اللغة", - "language_no_results_subtitle": "حاول تعديل مصطلح البحث", - "language_no_results_title": "لم يتم العثور على لغات", - "language_search_hint": "البحث عن لغات...", - "language_setting_description": "اختر لغتك المفضلة", - "large_files": "ملفات كبيرة", - "last": "الاخير", - "last_months": "{count, plural, one {شهر فائت} other {فائتة # اشهر}}", - "last_seen": "اخر ظهور", - "latest_version": "احدث اصدار", - "latitude": "خط العرض", - "leave": "مغادرة", - "leave_album": "اترك الالبوم", - "lens_model": "نموذج العدسات", - "let_others_respond": "دع الآخرين يستجيبون", - "level": "المستوى", - "library": "مكتبة", - "library_add_folder": "اضافة مجلد", - "library_edit_folder": "تعديل مجلد", - "library_options": "خيارات المكتبة", - "library_page_device_albums": "ألبومات على الجهاز", - "library_page_new_album": "البوم جديد", - "library_page_sort_asset_count": "عدد الأصول", - "library_page_sort_created": "تاريخ الإنشاء", - "library_page_sort_last_modified": "آخر تعديل", - "library_page_sort_title": "عنوان الألبوم", - "licenses": "رُخَص", - "light": "المضيئ", - "like": "اعجاب", - "like_deleted": "تم حذف الإعجاب", - "link_motion_video": "رابط فيديو الحركة", - "link_to_oauth": "الربط مع OAuth", - "linked_oauth_account": "حساب مرتبط بـ OAuth", - "list": "قائمة", - "loading": "تحميل", - "loading_search_results_failed": "فشل تحميل نتائج البحث", - "local": "محلّي", - "local_asset_cast_failed": "غير قادر على بث أصل لم يتم تحميله إلى الخادم", - "local_assets": "أُصول (ملفات) محلية", - "local_id": "الهوية المحلية", - "local_media_summary": "ملخص الملفات المحلية", - "local_network": "شبكة محلية", - "local_network_sheet_info": "سيتصل التطبيق بالخادم من خلال عنوان URL هذا عند استخدام شبكة Wi-Fi المحددة", - "location": "موقع", - "location_permission": "اذن الموقع", - "location_permission_content": "من أجل استخدام ميزة التبديل التلقائي، يحتاج Immich إلى إذن موقع دقيق حتى يتمكن من قراءة اسم شبكة Wi-Fi الحالية", - "location_picker_choose_on_map": "اختر على الخريطة", - "location_picker_latitude_error": "أدخل خط عرض صالح", - "location_picker_latitude_hint": "أدخل خط العرض الخاص بك هنا", - "location_picker_longitude_error": "أدخل خط الطول الصحيح", - "location_picker_longitude_hint": "أدخل خط الطول هنا", - "lock": "قفل", - "locked_folder": "مجلد مقفول", - "log_detail_title": "تفاصيل السجل", - "log_out": "تسجيل خروج", - "log_out_all_devices": "تسجيل الخروج من كافة الأجهزة", - "logged_in_as": "تم تسجيل الدخول باسم {user}", - "logged_out_all_devices": "تم تسجيل الخروج من جميع الأجهزة", - "logged_out_device": "تم تسجيل الخروج من الجهاز", - "login": "تسجيل الدخول", - "login_disabled": "تم تعطيل تسجيل الدخول", - "login_form_api_exception": "استثناء API. يرجى التحقق من عنوان URL الخادم والمحاولة مرة أخرى.", - "login_form_back_button_text": "الرجوع للخلف", - "login_form_email_hint": "yoursemail@email.com", - "login_form_endpoint_hint": "http://المنفذ:عنوان‫-ip-الخادم", - "login_form_endpoint_url": "url نقطة نهاية الخادم", - "login_form_err_http": "يرجى تحديد http:// أو https://", - "login_form_err_invalid_email": "بريد إلكتروني خاطئ", - "login_form_err_invalid_url": "URL غير صالح", - "login_form_err_leading_whitespace": "قيادة المساحة البيضاء", - "login_form_err_trailing_whitespace": "زائدة بيضاء", - "login_form_failed_get_oauth_server_config": "تسجيل الخطأ باستخدام OAUTH ، تحقق من عنوان URL لخادم", - "login_form_failed_get_oauth_server_disable": "ميزة OAuth غير متوفرة على هذا الخادم", - "login_form_failed_login": "خطأ في تسجيل الدخول ، تحقق من عنوان URL للخادم والبريد الإلكتروني وكلمة المرور", - "login_form_handshake_exception": "كان هناك استثناء مصافحة مع الخادم.تمكين دعم الشهادة الموقعة ذاتيا في الإعدادات إذا كنت تستخدم شهادة موقعة ذاتيا.", - "login_form_password_hint": "كلمة المرور", - "login_form_save_login": "ابق متصلا", - "login_form_server_empty": "أدخل عنوان URL الخادم.", - "login_form_server_error": "لا يمكن الاتصال بالخادم.", - "login_has_been_disabled": "تم تعطيل تسجيل الدخول.", - "login_password_changed_error": "كان هناك خطأ في تحديث كلمة المرور الخاصة بك", - "login_password_changed_success": "تم تحديث كلمة السر بنجاح", - "logout_all_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من جميع الأجهزة؟", - "logout_this_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من هذا الجهاز؟", - "logs": "السجلات", - "longitude": "خط الطول", - "look": "الشكل", - "loop_videos": "تكرار مقاطع الفيديو", - "loop_videos_description": "فَعْل لتكرار مقطع فيديو تلقائيًا في عارض التفاصيل.", - "main_branch_warning": "أنت تستخدم إصداراً قيد التطوير؛ ونحن نوصي بشدة باستخدام إصدار النشر!", - "main_menu": "القائمة الرئيسية", - "maintenance_action_restore": "استعادة قاعدة البيانات", - "maintenance_description": "يجب وضع Immich في وضع الصيانة وضع الصيانة.", - "maintenance_end": "انهاء وضع الصيانة", - "maintenance_end_error": "فشل في انهاء وضع الصيانة.", - "maintenance_logged_in_as": "حاليا مسجل باسم {user}", - "maintenance_restore_from_backup": "استعادة من الخزن الاحتياطي", - "maintenance_restore_library": "استعادة المكتبه الخاصة بك", - "maintenance_restore_library_confirm": "إذا بدا هذا صحيحا، فتابع عملية استعادة النسخة الاحتياطية!", - "maintenance_restore_library_description": "استعادة قاعدة البيانات", - "maintenance_restore_library_folder_has_files": "{folder} يحتوي {count} مجلد(ات)", - "maintenance_restore_library_folder_no_files": "{folder} لا يحتوي على ملفات!", - "maintenance_restore_library_folder_pass": "قابل للقراءة والكتابة", - "maintenance_restore_library_folder_read_fail": "غير قابل للقراءة", - "maintenance_restore_library_folder_write_fail": "غير قابل للكتابة", - "maintenance_restore_library_hint_missing_files": "قد تكون بعض الملفات المهمة مفقودة", - "maintenance_restore_library_hint_regenerate_later": "يمكنك إعادة إنشاء هذه لاحقًا في الإعدادات", - "maintenance_restore_library_hint_storage_template_missing_files": "هل تستخدم قالب تخزين؟ قد تكون بعض الملفات مفقودة", - "maintenance_restore_library_loading": "جارٍ تحميل فحوصات السلامة والأساليب الاستدلالية…", - "maintenance_task_backup": "جاري انشاء نسخة احتياطية لقاعدة البيانات الموجودة…", - "maintenance_task_migrations": "تشغيل عمليات ترحيل قواعد البيانات…", - "maintenance_task_restore": "جارٍ استعادة النسخة الاحتياطية المختارة…", - "maintenance_task_rollback": "فشلت عملية الاستعادة، جارٍ التراجع إلى نقطة الاستعادة…", - "maintenance_title": "غير متوفر مؤقتا", - "make": "صنع", - "manage_geolocation": "إدارة الموقع", - "manage_media_access_rationale": "الاذن المطلوب للتعامل السليم لنقل الاصول الى سلة المهملات واعادتها منها.", - "manage_media_access_settings": "فتح الاعدادات", - "manage_media_access_subtitle": "السماح لبرنامج Immich بإدارة ونقل ملفات الوسائط.", - "manage_media_access_title": "وصول ادارة الوسائط", - "manage_shared_links": "إدارة الروابط المشتركة", - "manage_sharing_with_partners": "إدارة المشاركة مع الشركاء", - "manage_the_app_settings": "إدارة إعدادات التطبيق", - "manage_your_account": "إدارة حسابك", - "manage_your_api_keys": "إدارة مفاتيح API الخاصة بك", - "manage_your_devices": "إدارة الأجهزة التي تم تسجيل الدخول إليها", - "manage_your_oauth_connection": "إدارة اتصال OAuth الخاص بك", - "map": "الخريطة", - "map_assets_in_bounds": "{count, plural, =0 {لايوجد صور في هذه المنطقة} one {# صورة} other {# صور}}", - "map_cannot_get_user_location": "لا يمكن الحصول على موقع المستخدم", - "map_location_dialog_yes": "نعم", - "map_location_picker_page_use_location": "استخدم هذا الموقع", - "map_location_service_disabled_content": "يجب تمكين خدمة الموقع لعرض الأصول من موقعك الحالي.هل تريد تمكينه الآن؟", - "map_location_service_disabled_title": "خدمة الموقع معطل", - "map_marker_for_images": "علامة الخريطة للصور الملتقطة في {city}، {country}", - "map_marker_with_image": "علامة الخريطة مع الصورة", - "map_no_location_permission_content": "هناك حاجة إلى إذن الموقع لعرض الأصول من موقعك الحالي.هل تريد السماح به الآن؟", - "map_no_location_permission_title": "تم رفض إذن الموقع", - "map_settings": "إعدادات الخريطة", - "map_settings_dark_mode": "الوضع المظلم", - "map_settings_date_range_option_day": "24 ساعة الماضية", - "map_settings_date_range_option_days": "الايام {days} الماضية", - "map_settings_date_range_option_year": "السنة الفائتة", - "map_settings_date_range_option_years": "السنوات {years} الماضية", - "map_settings_dialog_title": "إعدادات الخريطة", - "map_settings_include_show_archived": "تشمل الأرشفة", - "map_settings_include_show_partners": "تضمين الشركاء", - "map_settings_only_show_favorites": "اظهار المفضلة فقط", - "map_settings_theme_settings": "مظهر الخريطة", - "map_zoom_to_see_photos": "قم بتصغيرها لرؤية الصور", - "mark_all_as_read": "تحديد الكل كمقروء", - "mark_as_read": "تحديد كمقروء", - "marked_all_as_read": "تم تحديد الكل كمقروء", - "matches": "تطابقات", - "matching_assets": "‏الاصول المطابقة", - "media_type": "نوع الوسائط", - "memories": "الذكريات", - "memories_all_caught_up": "كل شيء محدث", - "memories_check_back_tomorrow": "التحقق مرة أخرى غدا لمزيد من الذكريات", - "memories_setting_description": "إدارة ما تراه في ذكرياتك", - "memories_start_over": "ابدأ من جديد", - "memories_swipe_to_close": "اسحب لأعلى للإغلاق", - "memory": "ذكرى", - "memory_lane_title": "ذكرياتٌ من {title}", - "menu": "القائمة", - "merge": "الدمج", - "merge_people": "دمج الأشخاص", - "merge_people_limit": "يمكنك دمج حتى 5 وجوه فقط في المرة الواحدة", - "merge_people_prompt": "هل تريد دمج هؤلاء الناس؟ هذا الإجراء لا رجعة فيه.", - "merge_people_successfully": "تم دمج الأشخاص بنجاح", - "merged_people_count": "دمج {count, plural, one {شخص واحد} other {# أشخاص}}", - "minimize": "تصغير", - "minute": "دقيقة", - "minutes": "دقائق", - "mirror_horizontal": "افقي", - "mirror_vertical": "عمودي", - "missing": "المفقودة", - "mobile_app": "تطبيق الجوال", - "mobile_app_download_onboarding_note": "قم بتنزيل التطبيق المصاحب للهاتف المحمول باستخدام الخيارات التالية", - "model": "نموذج", - "month": "شهر", - "monthly_title_text_date_format": "ط ط ط", - "more": "المزيد", - "move": "تحريك", - "move_down": "انزل الى الاسفل", - "move_off_locked_folder": "تحريك خارج المجلد المقفل", - "move_to": "نقل الى", - "move_to_device_trash": "نقل إلى سلة مهملات الجهاز", - "move_to_lock_folder_action_prompt": "{count} اضيف إلى المجلد المقفل", - "move_to_locked_folder": "النقل الى مجلد مغلق", - "move_to_locked_folder_confirmation": "هذه الصور والفديوات ستتم ازالتها من جميع الالبومات، ويمكنان تتم مشاهدتها فقط من خلال المجلد المقفل", - "move_up": "تحرك الى الاعلى", - "moved_to_archive": "تم نقل {count, plural, one {# اصل} other {# اصول}} الى الارشيف", - "moved_to_library": "تم نقل {count, plural, one {# اصل} other {# اصول}} الى المكتبة", - "moved_to_trash": "تم النقل إلى سلة المهملات", - "multiselect_grid_edit_date_time_err_read_only": "لا يمكن تعديل تاريخ الأصول (المواد) للقراءة فقط، سوف يتخطى", - "multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى", - "mute_memories": "كتم الذكريات", - "my_albums": "ألبوماتي", - "name": "الاسم", - "name_or_nickname": "الاسم أو اللقب", - "name_required": "الاسم مطلوب", - "navigate": "التنقل", - "navigate_to_time": "انتقل إلى الوقت", - "network_requirement_photos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية للصور", - "network_requirement_videos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية لمقاطع الفيديو", - "network_requirements": "متطلبات الشبكة", - "network_requirements_updated": "تم تغيير متطلبات الشبكة، يتم إعادة تعيين قائمة انتظار النسخ الاحتياطي", - "networking_settings": "الشبكات", - "networking_subtitle": "إدارة إعدادات نقطة الخادم النهائية", - "never": "أبداً", - "new_album": "البوم جديد", - "new_api_key": "مفتاح API جديد", - "new_date_range": "نطاق تاريخ جديد", - "new_password": "كلمة المرور الجديدة", - "new_person": "شخص جديد", - "new_pin_code": "رمز PIN الجديد", - "new_pin_code_subtitle": "هذه أول مرة تدخل فيها إلى المجلد المقفل. أنشئ رمزًا PIN للوصول بامان إلى هذه الصفحة", - "new_timeline": "الخط الزمني الجديد", - "new_update": "تحديث جديد", - "new_user_created": "تم إنشاء مستخدم جديد", - "new_version_available": "إصدار جديد متاح", - "newest_first": "الأحدث أولاً", - "next": "التالي", - "next_memory": "الذكرى التالية", - "no": "لا", - "no_actions_added": "لم تتم إضافة إجراءات حتى الان", - "no_albums_found": "لم يتم ايجاد البومات", - "no_albums_message": "قم بإنشاء ألبوم لتنظيم الصور ومقاطع الفيديو الخاصة بك", - "no_albums_with_name_yet": "يبدو أنه ليس لديك أي ألبومات بهذا الاسم حتى الآن.", - "no_albums_yet": "يبدو أنه ليس لديك أي ألبومات حتى الآن.", - "no_archived_assets_message": "أرشفة الصور ومقاطع الفيديو لإخفائها من عرض الصور لديك", - "no_assets_message": "انقر لتحميل صورتك الأولى", - "no_assets_to_show": "لا توجد أصول لعرضها", - "no_cast_devices_found": "لم يتم ايجاد جهاز بث", - "no_checksum_local": "لا توجد بيانات تحقق متاحة - يتعذر تحميل الاصول المحلية", - "no_checksum_remote": "لا يوجد رمز تحقق متاح - يتعذر تحميل الاصل من الموقع البعيد", - "no_configuration_needed": "لا حاجة إلى أي إعدادات", - "no_devices": "لا يوجد اجهزة مرخصة", - "no_duplicates_found": "لم يتم العثور على أي تكرارات.", - "no_exif_info_available": "لا تتوفر معلومات exif", - "no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.", - "no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو", - "no_filters_added": "لم تتم إضافة أي فلتر بعد", - "no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك", - "no_local_assets_found": "لم يتم العثور على أي اصول محلية تتطابق مع قيمة التحقق هذه", - "no_location_set": "لم يتم تحديد موقع", - "no_locked_photos_message": "الصور والفديوهات في المجلد المقفل مخفية ولن تصهر في التصفح او البحث في مكتبتك.", - "no_name": "لا اسم", - "no_notifications": "لا توجد تنبيهات", - "no_people_found": "لم يتم العثور على اشخاص مطابقين", - "no_places": "لا أماكن", - "no_remote_assets_found": "لم يتم العثور على أي اصول بعيدة تتطابق مع رمز التحقق هذل", - "no_results": "لا يوجد نتائج", - "no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية", - "no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك", - "no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع", - "none": "لا يوجد", - "not_allowed": "غير مسموح", - "not_available": "غير متاح", - "not_in_any_album": "ليست في أي ألبوم", - "not_selected": "لم يختار", - "note_apply_storage_label_to_previously_uploaded assets": "ملاحظة: لتطبيق سمة التخزين على المحتويات التي تم رفعها مسبقًا، قم بتشغيل", - "notes": "ملاحظات", - "nothing_here_yet": "لا يوجد شيء هنا بعد", - "notification_permission_dialog_content": "لتمكين الإخطارات ، انتقل إلى الإعدادات و اختار السماح.", - "notification_permission_list_tile_content": "منح إذن لتمكين الإخطارات.", - "notification_permission_list_tile_enable_button": "تمكين الإخطارات", - "notification_permission_list_tile_title": "إذن الإخطار", - "notification_toggle_setting_description": "تفعيل إشعارات البريد الإلكتروني", - "notifications": "إشعارات", - "notifications_setting_description": "إدارة الإشعارات", - "oauth": "OAuth", - "obtainium_configurator": "مُهيئ Obtainium", - "obtainium_configurator_instructions": "استخدم Obtainium لتثبيت تطبيق Android وتحديثه مباشرةً من صفحة إصدارات Immich على GitHub. أنشئ مفتاح API واختر الإصدار المناسب لإنشاء رابط تهيئة Obtainium الخاص بك", - "ocr": "التعرف البصري على الحروف", - "official_immich_resources": "الموارد الرسمية لشركة Immich", - "offline": "غير متصل", - "offset": "ازاحة", - "ok": "نعم", - "oldest_first": "الأقدم أولا", - "on_this_device": "على هذا الجهاز", - "onboarding": "الإعداد الأولي", - "onboarding_locale_description": "اختر لغتك المفضلة. يمكنك تغييرها فيما بعد في الاعدادات.", - "onboarding_privacy_description": "تعتمد الميزات التالية (اختياري) على خدمات خارجية، ويمكن تعطيلها في أي وقت في الاعدادات.", - "onboarding_server_welcome_description": "لنقم باعداد نسختك من البرنامج مع بعض الاعدادات الشائعة.", - "onboarding_theme_description": "اختر نسق الألوان للنسخة الخاصة بك. يمكنك تغيير ذلك لاحقًا في إعداداتك.", - "onboarding_user_welcome_description": "لنساعدك على البدء!", - "onboarding_welcome_user": "مرحبا، {user}", - "online": "متصل", - "only_favorites": "المفضلة فقط", - "open": "فتح", - "open_in_map_view": "فتح في عرض الخريطة", - "open_in_openstreetmap": "فتح في OpenStreetMap", - "open_the_search_filters": "افتح مرشحات البحث", - "options": "خيارات", - "or": "أو", - "organize_into_albums": "ترتيب في ألبومات", - "organize_into_albums_description": "أضف الصور الموجودة إلى الألبومات باستخدام إعدادات النسخ المتزامن الحالية", - "organize_your_library": "تنظيم مكتبتك", - "original": "أصلي", - "other": "أخرى", - "other_devices": "أجهزة أخرى", - "other_entities": "كيانات أخرى", - "other_variables": "متغيرات أخرى", - "owned": "مملوكة", - "owner": "المالك", - "page": "صفحة", - "partner": "شريك", - "partner_can_access": "يستطيع {partner} الوصول", - "partner_can_access_assets": "جميع الصور ومقاطع الفيديو الخاصة بك باستثناء تلك الموجودة في المؤرشفة والمحذوفة", - "partner_can_access_location": "الموقع الذي تم التقاط صورك فيه", - "partner_list_user_photos": "صور {user}", - "partner_list_view_all": "عرض الكل", - "partner_page_empty_message": "لم يتم مشاركة صورك بعد مع أي شريك.", - "partner_page_no_more_users": "لا مزيد من المستخدمين لإضافة", - "partner_page_partner_add_failed": "فشل في إضافة شريك", - "partner_page_select_partner": "حدد شريكًا", - "partner_page_shared_to_title": "مشترك ل", - "partner_page_stop_sharing_content": "{partner} لن يعود قادرا على الوصوف الى صورك.", - "partner_sharing": "مشاركة الشركاء", - "partners": "الشركاء", - "password": "كلمة المرور", - "password_does_not_match": "كلمة السر غير متطابقة", - "password_required": "كلمة المرور مطلوبة", - "password_reset_success": "تم إعادة تعيين كلمة المرور بنجاح", - "past_durations": { - "days": "سابقة {days, plural, one {day} other {# أيام }}", - "hours": "السابقة {hours, plural, one {hour} other {# الساعات }}", - "years": "السابقة {years, plural, one {year} other {# السنوات }}" - }, - "path": "المسار", - "pattern": "النمط", - "pause": "إيقاف مؤقت", - "pause_memories": "إيقاف الذكريات مؤقتا", - "paused": "تم الإيقاف مؤقتًا", - "pending": "قيد الانتظار", - "people": "الأشخاص", - "people_edits_count": "تم تعديل {count, plural, one {# شخص } other {# أشخاص }}", - "people_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب الأشخاص", - "people_selected": "{count, plural, one {# شخص مختار} other {# اشخاص مختارين}}", - "people_sidebar_description": "عرض رابط للأشخاص في الشريط الجانبي", - "permanent_deletion_warning": "تحذير الحذف الدائم", - "permanent_deletion_warning_setting_description": "إظهار تحذير عند حذف المحتويات نهائيًا", - "permanently_delete": "حذف بشكل دائم", - "permanently_delete_assets_count": "حذف {count, plural, one {المحتوى} other {المحتويات}} نهائيًا", - "permanently_delete_assets_prompt": "هل أنت متأكد أنك تريد حذف {count, plural, one {هذا العنصر؟} other {هذه العناصر #؟}} سيتم أيضًا إزالته {count, plural, one {من ألبومه} other {من ألبوماتهم}}.", - "permanently_deleted_asset": "تم حذف الأصل بشكل نهائي", - "permanently_deleted_assets_count": "تم حذف {count, plural, one {# محتوى} other {# المحتويات}} نهائيًا", - "permission": "اذن", - "permission_empty": "الاذن الخاص بك يجب ان لا يكون فارغا", - "permission_onboarding_back": "خلف", - "permission_onboarding_continue_anyway": "تواصل على أي حال", - "permission_onboarding_get_started": "البدء", - "permission_onboarding_go_to_settings": "اذهب للاعدادات", - "permission_onboarding_permission_denied": "تم رفض الإذن. لاستخدام التطبيق، قم بمنح أذونات الصور والفيديو في الإعدادات.", - "permission_onboarding_permission_granted": "تم تأمين التصريح! وضعك تمام.", - "permission_onboarding_permission_limited": "إذن محدود. للسماح بالنسخ الاحتياطي للتطبيق وإدارة مجموعة المعرض بالكامل، امنح أذونات الصور والفيديو في الإعدادات.", - "permission_onboarding_request": "يتطلب التطبيق إذنًا لعرض الصور ومقاطع الفيديو الخاصة بك.", - "person": "شخص", - "person_age_months": "{months, plural, one {# شهر} other {# اشهر}} من العمر", - "person_age_year_months": "1 عام, {months, plural, one {# شهر} other {# اشهر}} من العمر", - "person_age_years": "{years, plural, other {# اعوام}} من العمر", - "person_birthdate": "ولد في {date}", - "person_hidden": "{name}{hidden, select, true { (مخفي)} other {}}", - "person_recognized": "شخص تم التعرف عليه", - "person_selected": "شخص مختار", - "photo_shared_all_users": "يبدو أنك شاركت صورك مع جميع المستخدمين أو ليس لديك أي مستخدم للمشاركة معه.", - "photos": "الصور", - "photos_and_videos": "الصور ومقاطع الفيديو", - "photos_count": "{count, plural, one {{count, number} صورة} other {{count, number} صور}}", - "photos_from_previous_years": "صور من السنوات السابقة", - "photos_only": "صور فقط", - "pick_a_location": "اختر موقعًا", - "pick_custom_range": "نطاق مخصص", - "pick_date_range": "حدد نطاق التاريخ", - "pin_code_changed_successfully": "تم تغير رمز PIN بنجاح", - "pin_code_reset_successfully": "تم اعادة تعيين رمز PIN بنجاح", - "pin_code_setup_successfully": "تم انشاء رمز PIN بنجاح", - "pin_verification": "التحقق برمز PIN", - "place": "مكان", - "places": "الأماكن", - "places_count": "{count, plural, one {{count, number} مكان} other {{count, number} أماكن}}", - "play": "تشغيل", - "play_memories": "تشغيل الذكريات", - "play_motion_photo": "تشغيل الصور المتحركة", - "play_or_pause_video": "تشغيل الفيديو أو إيقافه مؤقتًا", - "play_original_video": "تشغيل الفيديو الأصلي", - "play_original_video_setting_description": "تفضيل تشغيل مقاطع الفيديو الأصلية بدلاً من مقاطع الفيديو المحولة. إذا لم يكن الملف الأصلي متوافقًا، فقد لا يتم تشغيله بشكل صحيح.", - "play_transcoded_video": "تشغيل الفيديو المُعاد ترميزه", - "please_auth_to_access": "الرجاء القيام بالمصادقة للوصول", - "port": "المنفذ", - "preferences_settings_subtitle": "ادارة تفضيلات التطبيق", - "preferences_settings_title": "التفضيلات", - "preparing": "قيد التحضير", - "preset": "الإعداد المسبق", - "preview": "معاينة", - "previous": "السابق", - "previous_memory": "الذكرى السابقة", - "previous_or_next_day": "يوم تالي/سابق", - "previous_or_next_month": "شهر تالي/سابق", - "previous_or_next_photo": "صورة تالية/سابقة", - "previous_or_next_year": "سنة تالية/سابقة", - "primary": "أساسي", - "privacy": "الخصوصية", - "profile": "حساب تعريفي", - "profile_drawer_app_logs": "السجلات", - "profile_drawer_client_server_up_to_date": "العميل والخادم محدثان", - "profile_drawer_github": "Github", - "profile_drawer_readonly_mode": "تم تفعيل وضع القراءة فقط. اضغط مطولا على رمز صورة المستخدم للخروج.", - "profile_image_of_user": "صورة الملف الشخصي لـ {user}", - "profile_picture_set": "مجموعة الصور الشخصية.", - "public_album": "الألبوم العام", - "public_share": "مشاركة عامة", - "purchase_account_info": "داعم", - "purchase_activated_subtitle": "شكرًا لك على دعمك لـ Immich والبرمجيات مفتوحة المصدر", - "purchase_activated_time": "تم التفعيل في {date}", - "purchase_activated_title": "لقد تم تفعيل مفتاحك بنجاح", - "purchase_button_activate": "تنشيط", - "purchase_button_buy": "شراء", - "purchase_button_buy_immich": "شراء Immich", - "purchase_button_never_show_again": "لا تظهر مرة أخرى أبدا", - "purchase_button_reminder": "ذكّرني بعد 30 يومًا", - "purchase_button_remove_key": "إزالة المفتاح", - "purchase_button_select": "تحديد", - "purchase_failed_activation": "فشل التنشيط! يرجى التحقق من بريدك الإلكتروني للحصول على مفتاح المنتج الصحيح!", - "purchase_individual_description_1": "للفرد", - "purchase_individual_description_2": "حالة الداعم", - "purchase_individual_title": "فردي", - "purchase_input_suggestion": "هل لديك مفتاح المنتج؟ أدخل المفتاح أدناه", - "purchase_license_subtitle": "قم بشراء Immich لدعم التطوير المستمر للخدمة", - "purchase_lifetime_description": "الشراء لمدى الحياة", - "purchase_option_title": "خيارات الشراء", - "purchase_panel_info_1": "يتطلب بناء Immich الكثير من الوقت والجهد، ولدينا مهندسون يعملون بدوام كامل لجعله أفضل ما يمكن. مهمتنا هي أن تصبح البرمجيات مفتوحة المصدر وممارسات العمل الأخلاقية مصدر دخل مستدام للمطورين وإنشاء نظام بيئي يحترم الخصوصية مع بدائل حقيقية للخدمات السحابية الاستغلالية.", - "purchase_panel_info_2": "نظرًا لأننا ملتزمون بعدم إضافة حاجز دفع، فإن هذا الشراء لن يمنحك أي ميزات إضافية في Immich. نحن نعتمد على المستخدمين مثلك لدعم التطوير المستمر لـ Immich.", - "purchase_panel_title": "ادعم المشروع", - "purchase_per_server": "لكل خادم", - "purchase_per_user": "لكل مستخدم", - "purchase_remove_product_key": "إزالة مفتاح المنتج", - "purchase_remove_product_key_prompt": "هل أنت متأكد أنك تريد إزالة مفتاح المنتج؟", - "purchase_remove_server_product_key": "إزالة مفتاح منتج الخادم", - "purchase_remove_server_product_key_prompt": "هل أنت متأكد أنك تريد إزالة مفتاح منتج الخادم؟", - "purchase_server_description_1": "للخادم بأكمله", - "purchase_server_description_2": "حالة الداعم", - "purchase_server_title": "الخادم", - "purchase_settings_server_activated": "يتم إدارة مفتاح منتج الخادم من قبل مدير النظام", - "query_asset_id": "استعلام عن معرف الأصل", - "queue_status": "يتم الاضافة الى قائمة انتظار النسخ الاحتياطي {count}/{total}", - "rate_asset": "تقييم الاصل", - "rating": "تقييم نجمي", - "rating_clear": "مسح التقييم", - "rating_count": "{count, plural, one {# نجمة} other {# نجوم}}", - "rating_description": "‫‌اعرض تقييم EXIF في لوحة المعلومات", - "rating_set": "تم تحديد التصنيف {rating, plural, one {# نجمة} other {# نجوم}}", - "reaction_options": "خيارات رد الفعل", - "read_changelog": "قراءة سجل التغيير", - "readonly_mode_disabled": "تم تعطيل وضع القراءة فقط", - "readonly_mode_enabled": "تم تفعيل وضع القراءة فقط", - "ready_for_upload": "جاهز للرفع", - "reassign": "إعادة التعيين", - "reassigned_assets_to_existing_person": "تمت إعادة تعيين {count, plural, one {# الأصل} other {# الاصول}} إلى {name, select, null {شخص موجود } other {{name}}}", - "reassigned_assets_to_new_person": "تمت إعادة تعيين {count, plural, one {# المحتوى} other {# المحتويات}} إلى شخص جديد", - "reassing_hint": "تعيين المحتويات المحددة لشخص موجود", - "recent": "حديث", - "recent-albums": "ألبومات الحديثة", - "recent_searches": "عمليات البحث الأخيرة", - "recently_added": "اضيف مؤخرا", - "recently_added_page_title": "أضيف مؤخرا", - "recently_taken": "تم التقاطها مؤخرًا", - "recently_taken_page_title": "تم التقاطها مؤخرًا", - "refresh": "تحديث", - "refresh_encoded_videos": "تحديث مقاطع الفيديو المشفرة", - "refresh_faces": "تحديث الوجوه", - "refresh_metadata": "تحديث البيانات الوصفية", - "refresh_thumbnails": "تحديث الصور المصغرة", - "refreshed": "اعادة تحميل", - "refreshes_every_file": "إعادة قراءة كافة الملفات الموجودة والجديدة", - "refreshing_encoded_video": "جارٍ تحديث الفيديو المرمز", - "refreshing_faces": "جاري تحديث الوجوه", - "refreshing_metadata": "جارٍ تحديث البيانات الوصفية", - "regenerating_thumbnails": "جارٍ تجديد الصور المصغرة", - "remote": "بعيد", - "remote_assets": "الأُصول البعيدة", - "remote_media_summary": "ملخص الملفات البعيدة", - "remove": "إزالة", - "remove_assets_album_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من الألبوم ؟", - "remove_assets_shared_link_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من رابط المشاركة هذا؟", - "remove_assets_title": "هل تريد إزالة المحتويات؟", - "remove_custom_date_range": "إزالة النطاق الزمني المخصص", - "remove_deleted_assets": "إزالة الملفات الغير متصلة", - "remove_from_album": "إزالة من الألبوم", - "remove_from_album_action_prompt": "تم ازالة {count} من الالبوم", - "remove_from_favorites": "إزالة من المفضلة", - "remove_from_lock_folder_action_prompt": "{count} أويل من المجلد المقفل", - "remove_from_locked_folder": "ازالة من المجلد المقفل", - "remove_from_locked_folder_confirmation": "هل انت متأكد من ازالة هذه الصور والفيديوهات من المجلد المقفل؟ سيكونون مرئيين في المكتبة الخاصة بك.", - "remove_from_shared_link": "إزالة من الرابط المشترك", - "remove_memory": "إزالة الذاكرة", - "remove_photo_from_memory": "إزالة الصورة من هذه الذكرى", - "remove_tag": "ازالة علامة", - "remove_url": "إزالة عنوان URL", - "remove_user": "إزالة المستخدم", - "removed_api_key": "تم إزاة مفتاح API: ‪‫{name}", - "removed_from_archive": "تمت إزالتها من الأرشيف", - "removed_from_favorites": "تمت الإزالة من المفضلة", - "removed_from_favorites_count": "{count, plural, other {أُزيلت #}} من التفضيلات", - "removed_memory": "الذاكرة المحذوفة", - "removed_photo_from_memory": "تم إزالة الصورة من الذاكرة", - "removed_tagged_assets": "تمت إزالة العلامة من {count, plural, one {# الأصل} other {# الأصول}}", - "rename": "إعادة تسمية", - "repair": "إصلاح", - "repair_no_results_message": "ستظهر هنا الملفات المفقودة وأيضاً التي لم يتم تعقبها", - "replace_with_upload": "استبدال بالرفع", - "repository": "المستودع", - "require_password": "يتطلب كلمة المرور", - "require_user_to_change_password_on_first_login": "مطالبة المستخدم بتغيير كلمة المرور عند تسجيل الدخول لأول مرة", - "rescan": "إعادة المسح", - "reset": "إعادة ضبط", - "reset_password": "إعادة تعيين كلمة المرور", - "reset_people_visibility": "إعادة ضبط ظهور الأشخاص", - "reset_pin_code": "اعادة تعيين رمز PIN", - "reset_pin_code_description": "اذا نسيت رمز الPIN الخاص بك، بامكانك التواصل مع مدير الخادم لديك لاعادة تعيينه", - "reset_pin_code_success": "تم اعادة تعيين رمز الPIN بنجاح", - "reset_pin_code_with_password": "يمكنك دائما اعادة تعيين رمز الPIN الخاص بك عن طريق كلمة المرور الخاصة بك", - "reset_sqlite": "إعادة تعيين قاعدة بيانات SQLite", - "reset_sqlite_confirmation": "هل أنت متأكد من رغبتك في إعادة ضبط قاعدة بيانات SQLite؟ ستحتاج إلى تسجيل الخروج ثم تسجيل الدخول مرة أخرى لإعادة مزامنة البيانات", - "reset_sqlite_success": "تم إعادة تعيين قاعدة بيانات SQLite بنجاح", - "reset_to_default": "إعادة التعيين إلى الافتراضي", - "resolution": "دقة", - "resolve_duplicates": "معالجة النسخ المكررة", - "resolved_all_duplicates": "تم حل جميع التكرارات", - "restore": "الاستعاده من سلة المهملات", - "restore_all": "استعادة الكل", - "restore_trash_action_prompt": "تم استعادة {count} من المهملات", - "restore_user": "استعادة المستخدم", - "restored_asset": "المحتويات المستعادة", - "resume": "استئناف", - "resume_paused_jobs": "استكمال {count, plural, one {# وظيفة معلقة} other {# وظائف معلقة}}", - "retry_upload": "أعد محاولة الرفع", - "review_duplicates": "مراجعة التكرارات", - "review_large_files": "مراجعة الملفات الكبيرة", - "role": "الدور", - "role_editor": "المحرر", - "role_viewer": "العارض", - "running": "قيد التشغيل", - "save": "حفظ", - "save_to_gallery": "حفظ الى المعرض", - "saved": "تم الحفظ", - "saved_api_key": "تم حفظ مفتاح الـ API", - "saved_profile": "تم حفظ الملف", - "saved_settings": "تم حفظ الإعدادات", - "say_something": "قل شيئًا", - "scaffold_body_error_occurred": "حدث خطأ", - "scan": "بحث", - "scan_all_libraries": "فحص كل المكتبات", - "scan_library": "مسح", - "scan_settings": "إعدادات الفحص", - "scanning": "جاري البحث", - "scanning_for_album": "جارٍ الفحص عن ألبوم...", - "search": "البحث", - "search_albums": "البحث في الألبومات", - "search_by_context": "البحث حسب السياق", - "search_by_description": "البحث حسب الوصف", - "search_by_description_example": "يوم المشي لمسافات طويلة في سابا", - "search_by_filename": "البحث بإسم الملف أو نوعه", - "search_by_filename_example": "كـ IMG_1234.JPG أو PNG", - "search_by_ocr": "البحث عن طريق التعرف البصري على الحروف", - "search_by_ocr_example": "لاتيه", - "search_camera_lens_model": "بحث نموذج العدسة...", - "search_camera_make": "البحث حسب الشركة المصنعة للكاميرا...", - "search_camera_model": "البحث حسب موديل الكاميرا...", - "search_city": "البحث حسب المدينة...", - "search_country": "البحث حسب الدولة...", - "search_filter_apply": "اختار الفلتر", - "search_filter_camera_title": "اختر نوع الكاميرا", - "search_filter_date": "تاريخ", - "search_filter_date_interval": "{start} الى {end}", - "search_filter_date_title": "حدد نطاق التاريخ", - "search_filter_display_option_not_in_album": "ليس في الألبوم", - "search_filter_display_options": "خيارات العرض", - "search_filter_filename": "بحث باستخدام الاسم", - "search_filter_location": "الموقع", - "search_filter_location_title": "اختر الموقع", - "search_filter_media_type": "نوع الوسائط", - "search_filter_media_type_title": "اختر نوع الوسائط", - "search_filter_ocr": "البحث عن طريق التعرف البصري على الحروف", - "search_filter_people_title": "اختر الاشخاص", - "search_filter_star_rating": "تقييم النجوم", - "search_for": "البحث عن", - "search_for_existing_person": "البحث عن شخص موجود", - "search_no_more_result": "لا توجد نتائج اضافية", - "search_no_people": "لا يوجد أشخاص", - "search_no_people_named": "لا يوجد أشخاص بالاسم \"{name}\"", - "search_no_result": "لا توجد نتائج، حاول استخدام مصطلح بحث مختلف او تركيبة", - "search_options": "خيارات البحث", - "search_page_categories": "فئات", - "search_page_motion_photos": "الصور المتحركه", - "search_page_no_objects": "لا توجد معلومات عن أشياء متاحة", - "search_page_no_places": "لا توجد معلومات متوفرة للأماكن", - "search_page_screenshots": "لقطات الشاشة", - "search_page_search_photos_videos": "ابحث عن صورك او فديوهاتك", - "search_page_selfies": "صور ذاتيه", - "search_page_things": "أشياء", - "search_page_view_all_button": "عرض الكل", - "search_page_your_activity": "نشاطك", - "search_page_your_map": "خريطتك", - "search_people": "البحث عن الأشخاص", - "search_places": "البحث عن الأماكن", - "search_rating": "البحث حسب التقييم...", - "search_result_page_new_search_hint": "بحث جديد", - "search_settings": "إعدادات البحث", - "search_state": "البحث حسب الولاية...", - "search_suggestion_list_smart_search_hint_1": "يتم تمكين البحث الذكي افتراضيًا ، للبحث عن البيانات الوصفية ، استخدم بناء الجملة. ", - "search_suggestion_list_smart_search_hint_2": "م: البحث الخاص بك", - "search_tags": "البحث عن العلامات...", - "search_timezone": "البحث حسب المنطقة الزمنية...", - "search_type": "نوع البحث", - "search_your_photos": "البحث عن صورك", - "searching_locales": "جارٍ البحث في اللغات...", - "second": "ثانية", - "see_all_people": "عرض جميع الأشخاص", - "select": "إختر", - "select_album": "اختر البوم", - "select_album_cover": "تحديد غلاف الألبوم", - "select_albums": "اختر البومات", - "select_all": "تحديد الكل", - "select_all_duplicates": "تحديد جميع النسخ المكررة", - "select_all_in": "اختر الكل في {group}", - "select_avatar_color": "تحديد لون الصورة الشخصية", - "select_count": "{count, plural, one {اختر #} other {اختر #}}", - "select_cutoff_date": "حدد تاريخ القطع", - "select_face": "تحديد وجه", - "select_featured_photo": "تحديد الصورة المميزة", - "select_from_computer": "تحديد من الحاسب الآلي", - "select_keep_all": "تحديد الأحتفاظ بالكل", - "select_library_owner": "تحديد مالِك المكتبة", - "select_new_face": "تحديد وجه جديد", - "select_people": "اختر الاشخاص", - "select_person": "اختر شخص", - "select_person_to_tag": "اختر شخص لوضع علامة", - "select_photos": "تحديد الصور", - "select_trash_all": "تحديد حذف الكلِ", - "select_user_for_sharing_page_err_album": "فشل في إنشاء ألبوم", - "selected": "التحديد", - "selected_count": "{count, plural, other {# محددة }}", - "selected_gps_coordinates": "إحداثيات نظام تحديد المواقع المختارة", - "send_message": "‏إرسال رسالة", - "send_welcome_email": "إرسال بريدًا إلكترونيًا ترحيبيًا", - "server_endpoint": "نقطة نهاية الخادم", - "server_info_box_app_version": "نسخة التطبيق", - "server_info_box_server_url": "عنوان URL الخادم", - "server_offline": "الخادم غير متصل", - "server_online": "الخادم متصل", - "server_privacy": "خصوصية الخادم", - "server_restarting_description": "سيتم تحديث هذه الصفحة بعد لحضات.", - "server_restarting_title": "يتم اعادة تشغيل الخادم", - "server_stats": "إحصائيات الخادم", - "server_update_available": "تحديث الخادم متاح", - "server_version": "إصدار الخادم", - "set": "‏تحديد", - "set_as_album_cover": "تحديد كغلاف للألبوم", - "set_as_featured_photo": "تحديد كصورة مميزة", - "set_as_profile_picture": "تحديد كصورة الملف الشخصي", - "set_date_of_birth": "تحديد تاريخ الميلاد", - "set_profile_picture": "تحديد صورة الملف الشخصي", - "set_slideshow_to_fullscreen": "تحديد عرض الشرائح على وضع ملء الشاشة", - "set_stack_primary_asset": "تعيين كأصل اساسي", - "setting_image_viewer_help": "يقوم عارض التفاصيل بتحميل الصورة المصغرة الصغيرة أولاً ، ثم يقوم بتحميل المعاينة متوسطة الحجم (إذا تم تمكينها) ، ويقوم أخيرًا بتحميل الأصل (إذا تم تمكينه).", - "setting_image_viewer_original_subtitle": "تمكين تحميل الصورة الكاملة الدقة الأصلية (كبيرة!).تعطيل لتقليل استخدام البيانات (كل من الشبكة وعلى ذاكرة التخزين المؤقت للجهاز).", - "setting_image_viewer_original_title": "تحميل الصورة الأصلية", - "setting_image_viewer_preview_subtitle": "تمكين تحميل صورة متوسطة الدقة.تعطيل إما لتحميل مباشرة أو استخدام الصورة المصغرة مباشرة.", - "setting_image_viewer_preview_title": "تحميل صورة معاينة", - "setting_image_viewer_title": "الصور", - "setting_languages_apply": "تغيير الإعدادات", - "setting_languages_subtitle": "تغيير لغة التطبيق", - "setting_notifications_notify_failures_grace_period": "التنبيه بفشل النسخ الاحتياطي في الخلفية: {duration}", - "setting_notifications_notify_hours": "{count} ساعات", - "setting_notifications_notify_immediately": "في الحال", - "setting_notifications_notify_minutes": "{count} دقائق", - "setting_notifications_notify_never": "أبداً", - "setting_notifications_notify_seconds": "{count} ثواني", - "setting_notifications_single_progress_subtitle": "معلومات التقدم التفصيلية تحميل لكل أصل", - "setting_notifications_single_progress_title": "إظهار تقدم التفاصيل الاحتياطية الخلفية", - "setting_notifications_subtitle": "اضبط تفضيلات الإخطار", - "setting_notifications_total_progress_subtitle": "التقدم التحميل العام (تم القيام به/إجمالي الأصول)", - "setting_notifications_total_progress_title": "إظهار النسخ الاحتياطي الخلفية التقدم المحرز", - "setting_video_viewer_auto_play_subtitle": "بدء تشغيل مقاطع الفيديو تلقائيًا عند فتحها", - "setting_video_viewer_auto_play_title": "تشغيل الفيديوهات تلقائيًا", - "setting_video_viewer_looping_title": "تكرار مقطع فيديو تلقائيًا", - "setting_video_viewer_original_video_subtitle": "عند بث فيديو من الخادم، شغّل النسخة الأصلية حتى مع توفر ترميز بديل. قد يؤدي ذلك إلى تقطيع اثناء العرض . تُشغّل الفيديوهات المتوفرة محليًا بجودة أصلية بغض النظر عن هذا الإعداد.", - "setting_video_viewer_original_video_title": "اجبار عرض الفديو الاصلي", - "settings": "الإعدادات", - "settings_require_restart": "يرجى إعادة تشغيل لتطبيق هذا الإعداد", - "settings_saved": "تم حفظ الإعدادات", - "setup_pin_code": "تحديد رمز PIN", - "share": "مشاركة", - "share_action_prompt": "تم مشاركة {count} أصل (ملف)", - "share_add_photos": "إضافة الصور", - "share_assets_selected": "اختيار {count}", - "share_dialog_preparing": "تحضير...", - "share_link": "مشاركة رابط", - "shared": "مُشتَرك", - "shared_album_activities_input_disable": "التعليق معطل", - "shared_album_activity_remove_content": "هل تريد حذف هذا النشاط؟", - "shared_album_activity_remove_title": "حذف النشاط", - "shared_album_section_people_action_error": "خطأ ترك/إزالة من الألبوم", - "shared_album_section_people_action_leave": "إزالة المستخدم من الألبوم", - "shared_album_section_people_action_remove_user": "إزالة المستخدم من الألبوم", - "shared_album_section_people_title": "الناس", - "shared_by": "تمت مشاركته بواسطة", - "shared_by_user": "تمت المشاركة بواسطة {user}", - "shared_by_you": "تمت مشاركته من قِبلك", - "shared_from_partner": "صور من {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} تم رفع", - "shared_link_app_bar_title": "روابط مشتركة", - "shared_link_clipboard_copied_massage": "نسخ إلى الحافظة", - "shared_link_clipboard_text": "رابط: {link}\nكلمة المرور: {password}", - "shared_link_create_error": "خطأ أثناء إنشاء رابط مشترك", - "shared_link_custom_url_description": "الوصول إلى هذا الرابط المشترك باستخدام عنوان URL مخصص", - "shared_link_edit_description_hint": "أدخل وصف المشاركة", - "shared_link_edit_expire_after_option_day": "يوم 1", - "shared_link_edit_expire_after_option_days": "{count} ايام", - "shared_link_edit_expire_after_option_hour": "1 ساعة", - "shared_link_edit_expire_after_option_hours": "{count} ساعات", - "shared_link_edit_expire_after_option_minute": "1 دقيقة", - "shared_link_edit_expire_after_option_minutes": "{count} دقائق", - "shared_link_edit_expire_after_option_months": "{count} اشهر", - "shared_link_edit_expire_after_option_year": "{count} سنة", - "shared_link_edit_password_hint": "أدخل كلمة مرور المشاركة", - "shared_link_edit_submit_button": "تحديث الرابط", - "shared_link_error_server_url_fetch": "لا يمكن جلب عنوان الخادم", - "shared_link_expires_day": "تنتهي صلاحيته في {count} يوم", - "shared_link_expires_days": "تنتهي صلاحيته في {count} ايام", - "shared_link_expires_hour": "تنتهي صلاحية في {count} ساعة", - "shared_link_expires_hours": "تنتهي صلاحيته في {count} ساعات", - "shared_link_expires_minute": "تنتهي صلاحيته في {count} دقيقة", - "shared_link_expires_minutes": "تنتهي صلاحيته في {count} دقائق", - "shared_link_expires_never": "تنتهي ∞", - "shared_link_expires_second": "تنتهي صلاحيته في {count} ثانية", - "shared_link_expires_seconds": "تنتهي صلاحيته في {count} ثواني", - "shared_link_individual_shared": "مشاركة فردية", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "إدارة الروابط المشتركة", - "shared_link_options": "خيارات الرابط المشترك", - "shared_link_password_description": "طلب كلمة مرور للوصول إلى هذا الرابط المشترك", - "shared_links": "روابط مشتركة", - "shared_links_description": "وصف الروابط المشتركة", - "shared_photos_and_videos_count": "{assetCount, plural, other {# الصور ومقاطع الفيديو المُشارَكة.}}", - "shared_with_me": "تمت مشاركتها معي", - "shared_with_partner": "تمت المشاركة مع {partner}", - "sharing": "مشاركة", - "sharing_enter_password": "الرجاء إدخال كلمة المرور لعرض هذه الصفحة.", - "sharing_page_album": "ألبومات مشتركة", - "sharing_page_description": "قم بإنشاء ألبومات مشتركة لمشاركة الصور ومقاطع الفيديو مع أشخاص في شبكتك.", - "sharing_page_empty_list": "قائمة فارغة", - "sharing_sidebar_description": "اعرض رابطًا للمشاركة في الشريط الجانبي", - "sharing_silver_appbar_create_shared_album": "ألبوم مشترك جديد", - "sharing_silver_appbar_share_partner": "شارك مع الشريك", - "shift_to_permanent_delete": "اضغط على ⇧ لحذف المحتوى نهائيًا", - "show_album_options": "إظهار خيارات الألبوم", - "show_albums": "إظهار الألبومات", - "show_all_people": "إظهار جميع الأشخاص", - "show_and_hide_people": "إظهار وإخفاء الأشخاص", - "show_file_location": "إظهار موقع الملف", - "show_gallery": "إظهار المعرض", - "show_hidden_people": "إظهار الأشخاص المخفيين", - "show_in_timeline": "إظهار في المخطط الزمني", - "show_in_timeline_setting_description": "إظهار الصور ومقاطع الفيديو من هذا المستخدم في المخطط الزمني الخاص بك", - "show_keyboard_shortcuts": "إظهار اختصارات لوحة المفاتيح", - "show_metadata": "إظهار البيانات الوصفية", - "show_or_hide_info": "إظهار أو إخفاء المعلومات", - "show_password": "إظهار كلمة المرور", - "show_person_options": "إظهار خيارات الشخص", - "show_progress_bar": "إظهار شريط التقدم", - "show_schema": "أظهر المخطط", - "show_search_options": "إظهار خيارات البحث", - "show_shared_links": "عرض الروابط المشتركة", - "show_slideshow_transition": "إظهار انتقال عرض الشرائح", - "show_supporter_badge": "شارة المؤيد", - "show_supporter_badge_description": "إظهار شارة المؤيد", - "show_text_recognition": "اضهار التعرف على النصوص", - "show_text_search_menu": "عرض قائمة خيارات البحث في النص", - "shuffle": "خلط", - "sidebar": "الشريط الجانبي", - "sidebar_display_description": "عرض رابط للعرض في الشريط الجانبي", - "sign_out": "خروج", - "sign_up": "التسجيل", - "size": "الحجم", - "skip_to_content": "تخطي إلى المحتوى", - "skip_to_folders": "تخطي إلى المجلدات", - "skip_to_tags": "تخطي إلى العلامات", - "slideshow": "عرض الشرائح", - "slideshow_repeat": "اعادة عرض الشرائح", - "slideshow_repeat_description": "العودة إلى البداية عند انتهاء عرض الشرائح", - "slideshow_settings": "إعدادات عرض الشرائح", - "sort_albums_by": "رتب الألبومات حسب...", - "sort_created": "تاريخ الإنشاء", - "sort_items": "عدد العناصر", - "sort_modified": "تم تعديل التاريخ", - "sort_newest": "احدث صورة", - "sort_oldest": "أقدم صورة", - "sort_people_by_similarity": "رتب الأشخاص حسب التشابه", - "sort_recent": "أحدث صورة", - "sort_title": "العنوان", - "source": "المصدر", - "stack": "تجميع", - "stack_action_prompt": "{count} مكدسة", - "stack_duplicates": "تجميع النسخ المكررة", - "stack_select_one_photo": "حدد صورة رئيسية واحدة للمجموعة", - "stack_selected_photos": "كدس الصور المحددة", - "stacked_assets_count": "تم تكديس {count, plural, one {# المحتوى} other {# المحتويات}}", - "stacktrace": "تتّبُع التكديس", - "start": "ابدأ", - "start_date": "تاريخ البدء", - "start_date_before_end_date": "يجب أن يكون تاريخ بدء الفترة قبل تاريخ نهايتها", - "state": "الولاية", - "status": "الحالة", - "stop_casting": "ايقاف البث", - "stop_motion_photo": "إيقاف حركة الصورة", - "stop_photo_sharing": "توقف عن مشاركة صورك؟", - "stop_photo_sharing_description": "لن يتمكن {partner} من الوصول إلى صورك بعد الآن.", - "stop_sharing_photos_with_user": "توقف عن مشاركة صورك مع هذا المستخدم", - "storage": "مساحة التخزين", - "storage_label": "سمة التخزين", - "storage_quota": "حصة الخزن", - "storage_usage": "{used} من {available} مُستخْدم", - "submit": "إرسال", - "success": "تم بنجاح", - "suggestions": "اقتراحات", - "sunrise_on_the_beach": "شروق الشمس على الشاطئ", - "support": "الدعم", - "support_and_feedback": "الدعم والتعليقات", - "support_third_party_description": "تم حزم تثبيت immich الخاص بك بواسطة جهة خارجية. قد تكون المشكلات التي تواجهها ناجمة عن هذه الحزمة، لذا يرجى طرح المشكلات معهم في المقام الأول باستخدام الروابط أدناه.", - "swap_merge_direction": "تبديل اتجاه الدمج", - "sync": "مزامنة", - "sync_albums": "مزامنة الالبومات", - "sync_albums_manual_subtitle": "مزامنة جميع الفديوهات والصور المرفوعة الى البومات الخزن الاحتياطي المختارة", - "sync_local": "مزامنة الملفات المحلية", - "sync_remote": "مزامنة الملفات البعيدة", - "sync_status": "حالة النسخ المتزامن", - "sync_status_subtitle": "عرض وإدارة نظام النسخ المتزامن", - "sync_upload_album_setting_subtitle": "انشئ و ارفع صورك و فديوهاتك الالبومات المختارة في Immich", - "tag": "العلامة", - "tag_assets": "أصول العلامة", - "tag_created": "تم إنشاء العلامة: {tag}", - "tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية", - "tag_not_found_question": "لا يمكن العثور على علامة؟ قم بإنشاء علامة جديدة.", - "tag_people": "علِّم الأشخاص", - "tag_updated": "تم تحديث العلامة: {tag}", - "tagged_assets": "تم وضع علامة {count, plural, one {# asset} other {# assets}}", - "tags": "العلامات", - "tap_to_run_job": "انقر لتشغيل المهمة", - "template": "النموذج", - "text_recognition": "التعرف على النصوص", - "theme": "مظهر", - "theme_selection": "اختيار السمة", - "theme_selection_description": "قم بتعيين السمة تلقائيًا على اللون الفاتح أو الداكن بناءً على تفضيلات نظام المتصفح الخاص بك", - "theme_setting_asset_list_storage_indicator_title": "عرض مؤشر التخزين على بلاط الأصول", - "theme_setting_asset_list_tiles_per_row_title": "عدد الاصول في كل صف({count})", - "theme_setting_colorful_interface_subtitle": "تطبيق اللون الأساسي على الأسطح في الخلفية.", - "theme_setting_colorful_interface_title": "واجهه ملونة", - "theme_setting_image_viewer_quality_subtitle": "اضبط جودة عارض الصورة التفصيلية", - "theme_setting_image_viewer_quality_title": "جودة عارض الصورة", - "theme_setting_primary_color_subtitle": "اختر لون للعمليات الاساسية والمكمله.", - "theme_setting_primary_color_title": "اللون الاساسي", - "theme_setting_system_primary_color_title": "استخدم لون النظام", - "theme_setting_system_theme_switch": "تلقائي (اتبع إعداد النظام)", - "theme_setting_theme_subtitle": "اختر إعدادات مظهر التطبيق", - "theme_setting_three_stage_loading_subtitle": "قد يزيد التحميل من ثلاث مراحل من أداء التحميل ولكنه يسبب تحميل شبكة أعلى بكثير", - "theme_setting_three_stage_loading_title": "تمكين تحميل ثلاث مراحل", - "then": "ثم", - "they_will_be_merged_together": "سيتم دمجهم معًا", - "third_party_resources": "موارد الطرف الثالث", - "time": "وقت", - "time_based_memories": "ذكريات استنادًا للوقت", - "time_based_memories_duration": "عدد الثواني لاظهار كل صورة.", - "timeline": "الخط الزمني", - "timezone": "المنطقة الزمنية", - "to_archive": "أرشفة", - "to_change_password": "تغيير كلمة المرور", - "to_favorite": "تفضيل", - "to_login": "تسجيل الدخول", - "to_multi_select": "للتحديد المتعدد", - "to_parent": "انتقل إلى الوالد", - "to_select": "للتحديد", - "to_trash": "حذف", - "toggle_settings": "الإعدادات", - "toggle_theme_description": "تبديل السمة", - "total": "الإجمالي", - "total_usage": "الاستخدام الإجمالي", - "trash": "المهملات", - "trash_action_prompt": "{count} نقل الى سلة المهملات", - "trash_all": "نقل الكل إلى سلة المهملات", - "trash_count": "سلة المحملات {count, number}", - "trash_delete_asset": "حذف/نقل المحتوى إلى سلة المهملات", - "trash_emptied": "سبة مهملا مفرغة", - "trash_no_results_message": "ستظهر هنا الصور ومقاطع الفيديو المحذوفة.", - "trash_page_delete_all": "حذف الكل", - "trash_page_empty_trash_dialog_content": "هل تريد تفريغ أصولك المهملة؟ ستتم إزالة هذه العناصر نهائيًا من التطبيق", - "trash_page_info": "العناصر المنقولة الى سلة المهملات سيتم حذفها بشكل نهائي بعد {days} ايام", - "trash_page_no_assets": "لا توجد اصول في سله المهملات", - "trash_page_restore_all": "استعادة الكل", - "trash_page_select_assets_btn": "اختر الأصول", - "trash_page_title": "سلة المهملات ({count})", - "trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.", - "trigger": "مفعِل", - "trigger_asset_uploaded": "تم رفع الاصل", - "trigger_asset_uploaded_description": "يتم تفعيله عند تحميل أصل جديد", - "trigger_description": "حدث يبدأ سير العمل", - "trigger_person_recognized": "تم التعرف على شخص", - "trigger_person_recognized_description": "يتم تفعيله عند اكتشاف شخص", - "trigger_type": "نوع المفعل", - "troubleshoot": "استكشاف المشاكل", - "type": "النوع", - "unable_to_change_pin_code": "تفيير رمز PIN غير ممكن", - "unable_to_check_version": "تعذر التحقق من إصدار التطبيق أو الخادم", - "unable_to_setup_pin_code": "انشاء رمز PIN غير ممكن", - "unarchive": "أخرج من الأرشيف", - "unarchive_action_prompt": "{count} ازيل من الارشيف", - "unarchived_count": "{count, plural, other {غير مؤرشفة #}}", - "undo": "تراجع", - "unfavorite": "أزل التفضيل", - "unfavorite_action_prompt": "{count} ازيل من المفضلات", - "unhide_person": "أظهر الشخص", - "unknown": "غير معروف", - "unknown_country": "بلد غير معروف", - "unknown_date": "تاريخ غير معروف", - "unknown_year": "سنة غير معروفة", - "unlimited": "غير محدود", - "unlink_motion_video": "إلغاء ربط فيديو الحركة", - "unlink_oauth": "إلغاء ربط OAuth", - "unlinked_oauth_account": "تم إلغاء ربط حساب OAuth", - "unmute_memories": "تشغيل الصوت للذكريات", - "unnamed_album": "ألبوم بلا إسم", - "unnamed_album_delete_confirmation": "هل أنت متأكد أنك تريد حذف هذا الألبوم؟", - "unnamed_share": "مشاركة بلا إسم", - "unsaved_change": "تغيير غير محفوظ", - "unselect_all": "إلغاء تحديد الكل", - "unselect_all_duplicates": "إلغاء تحديد كافة النسخ المكررة", - "unselect_all_in": "إلغاء تحديد الكل في {group}", - "unstack": "فك الكومه", - "unstack_action_prompt": "تم ازالة تكديس {count}", - "unstacked_assets_count": "تم إخراج {count, plural, one {# الأصل} other {# الأصول}} من التكديس", - "unsupported_field_type": "نوع حقل غير مدعوم", - "untagged": "غير مُعَلَّم", - "untitled_workflow": "خطة سير عمل بدون عنوان", - "up_next": "التالي", - "update_location_action_prompt": "تحديث موقع {count} عناصر محددة على النحو التالي:", - "updated_at": "تم التحديث", - "updated_password": "تم تحديث كلمة المرور", - "upload": "رفع", - "upload_concurrency": "الرفع المتزامن", - "upload_details": "تفاصيل الرفع", - "upload_dialog_info": "هل تريد النسخ الاحتياطي للأصول (الأصول) المحددة إلى الخادم؟", - "upload_dialog_title": "تحميل الأصول", - "upload_error_with_count": "خطأ في رفع {count, plural, one {# اصل} other {# اصول}}", - "upload_errors": "إكتمل الرفع مع {count, plural, one {# خطأ} other {# أخطاء}}, قم بتحديث الصفحة لرؤية المحتويات الجديدة التي تم رفعها.", - "upload_finished": "تم الانتهاء من الرفع", - "upload_progress": "متبقية {remaining, number} - معالجة {processed, number}/{total, number}", - "upload_skipped_duplicates": "تم تخطي {count, plural, one {# محتوى مكرر} other {# محتويات مكررة }}", - "upload_status_duplicates": "التكرارات", - "upload_status_errors": "الأخطاء", - "upload_status_uploaded": "تم الرفع", - "upload_success": "تم الرفع بنجاح، قم بتحديث الصفحة لرؤية المحتويات المرفوعة الجديدة.", - "upload_to_immich": "الرفع الىImmich ‎ ‏ ({count})", - "uploading": "جاري الرفع", - "uploading_media": "رفع الوسائط", - "url": "عنوان URL", - "usage": "الاستخدام", - "use_biometric": "استخدم البايومتري", - "use_current_connection": "استخدم الاتصال الحالي", - "use_custom_date_range": "استخدم النطاق الزمني المخصص بدلاً من ذلك", - "user": "مستخدم", - "user_has_been_deleted": "هذا المستخدم تم حذفه.", - "user_id": "معرف المستخدم", - "user_liked": "قام {user} بالإعجاب {type, select, photo {بهذه الصورة} video {بهذا الفيديو} asset {بهذا المحتوى} other {بها}}", - "user_pin_code_settings": "رمز PIN", - "user_pin_code_settings_description": "تغير رمز PIN", - "user_privacy": "خصوصية المستخدم", - "user_purchase_settings": "الشراء", - "user_purchase_settings_description": "إدارة عملية الشراء الخاصة بك", - "user_role_set": "قم بتعيين {user} كـ {role}", - "user_usage_detail": "تفاصيل استخدام المستخدم", - "user_usage_stats": "إحصائيات استخدام الحساب", - "user_usage_stats_description": "عرض إحصائيات استخدام الحساب", - "username": "اسم المستخدم", - "users": "المستخدمين", - "users_added_to_album_count": "تم اضافة{count, plural, one {# مستخدم} other {# مستخدمين}} الى الالبوم", - "utilities": "أدوات", - "validate": "تحقْق", - "validate_endpoint_error": "الرجاء ادخال عنوان URL صالح", - "validation_error": "خطأ في التحقق", - "variables": "المتغيرات", - "version": "الإصدار", - "version_announcement_closing": "صديقك، أليكس", - "version_announcement_message": "مرحبًا! يتوفر إصدار جديد من Immich. يُرجى تخصيص بعض الوقت لقراءة ملاحظات الإصدار للتأكد من تحديث إعداداتك لمنع أي أخطاء في التكوين، خاصة إذا كنت تستخدم WatchTower أو أي آلية تتولى تحديث مثيل Immich الخاص بك تلقائيًا.", - "version_history": "تاريخ الإصدار", - "version_history_item": "تم تثبيت {version} في {date}", - "video": "فيديو", - "video_hover_setting": "تشغيل الصورة المصغرة للفيديو عند التمرير", - "video_hover_setting_description": "تشغيل الصورة المصغرة للفيديو عند تحريك الماوس فوق العنصر. حتى عند التعطيل، يمكن بدء التشغيل عن طريق التمرير فوق رمز التشغيل.", - "videos": "فيديوهات", - "videos_count": "{count, plural, one {# مقطع فيديو } other {# مقاطع الفيديو }}", - "videos_only": "الفديوات فقط", - "view": "عرض", - "view_album": "عرض الألبوم", - "view_all": "عرض الكل", - "view_all_users": "عرض كافة المستخدمين", - "view_asset_owners": "عرض مالكي الأصول", - "view_details": "رؤية التفاصيل", - "view_in_timeline": "عرض في الجدول الزمني", - "view_link": "عرض الرابط", - "view_links": "عرض الروابط", - "view_name": "عرض", - "view_next_asset": "عرض المحتوى التالي", - "view_previous_asset": "عرض المحتوى السابق", - "view_qr_code": "­عرض رمز الاستجابة السريعة", - "view_similar_photos": "عرض صور مشابهة", - "view_stack": "عرض التكديس", - "view_user": "عرض المستخدم", - "viewer_remove_from_stack": "حذف من الكومه أو المجموعة", - "viewer_stack_use_as_main_asset": "استخدم كأصل رئيسي", - "viewer_unstack": "فك الكومه", - "visibility_changed": "الرؤية تغيرت لـ {count, plural, one {شخص واحد} other {# عدة أشخاص}}", - "visual": "مرئي", - "visual_builder": "اداة نشاء مرئية", - "waiting": "في الانتظار", - "waiting_count": "الانتظار: {count}", - "warning": "تحذير", - "week": "أسبوع", - "welcome": "مرحباً", - "welcome_to_immich": "مرحباً بك في Immich", - "width": "عُرض", - "wifi_name": "اسم شبكة Wi-Fi", - "workflow_delete_prompt": "هل أنت متأكد من حذف سير العمل هذا؟", - "workflow_deleted": "تم حذف سير العمل", - "workflow_description": "وصف سير العمل", - "workflow_info": "معلومات سير العمل", - "workflow_json": "ملف JSON لسير العمل", - "workflow_json_help": "قم بتعديل إعدادات سير العمل بصيغة JSON. ستتم مزامنة التغييرات مع أداة الإنشاء المرئية.", - "workflow_name": "اسم سير العمل", - "workflow_navigation_prompt": "هل انت متاكد من المغادرة بدون حفظ التغييرات؟", - "workflow_summary": "ملخص سير العمل", - "workflow_update_success": "تم تحديث سير العمل بنجاح", - "workflow_updated": "تم تحديث سير العمل", - "workflows": "سير العمل", - "workflows_help_text": "تعمل سير العمل على أتمتة الإجراءات على أصولك بناءً على المفعلات والفلاتر", - "wrong_pin_code": "رمز التعريف الشخصي خاطئ", - "year": "سنة", - "years_ago": "{years, plural, one {# سنة} other {# سنوات}} مضت", - "yes": "نعم", - "you_dont_have_any_shared_links": "ليس لديك أي روابط مشتركة", - "your_wifi_name": "اسم شبكة الاتصال اللاسلكي الخاص بك", - "zero_to_clear_rating": "اضغط 0 لمسح تصنيف الاصول", - "zoom_image": "تكبير الصورة", - "zoom_to_bounds": "تكبير حتى حدود المنطقة" -} +{} diff --git a/i18n/az.json b/i18n/az.json index 310fe831dd..0967ef424b 100644 --- a/i18n/az.json +++ b/i18n/az.json @@ -1,112 +1 @@ -{ - "about": "Haqqında", - "account": "Hesab", - "account_settings": "Hesab Parametrləri", - "acknowledge": "Təsdiq et", - "action": "Əməliyyat", - "action_common_update": "Yenilə", - "actions": "Əməliyyatlar", - "active": "Aktiv", - "active_count": "Aktiv: {count}", - "activity": "Fəaliyyət", - "activity_changed": "Fəaliyyət {enabled, select, true {aktivdir} other {aktiv deyil}}", - "add": "Əlavə et", - "add_a_description": "Təsviri əlavə et", - "add_a_location": "Məkan əlavə et", - "add_a_name": "Ad əlavə et", - "add_a_title": "Başlıq əlavə et", - "add_birthday": "Doğum günü əlavə et", - "add_endpoint": "Son nöqtə əlavə et", - "add_exclusion_pattern": "Çıxarma nümunəsi əlavə et", - "add_location": "Məkan əlavə et", - "add_more_users": "Daha çox istifadəçi əlavə et", - "add_partner": "Partnyor əlavə et", - "add_path": "Yol əlavə et", - "add_photos": "Şəkillər əlavə et", - "add_tag": "Etiket əlavə et", - "add_to": "Bura əlavə et…", - "add_to_album": "Alboma əlavə et", - "add_to_album_bottom_sheet_added": "{album} albomuna əlavə edildi", - "add_to_album_bottom_sheet_already_exists": "Artıq {album} albomunda var", - "add_to_album_bottom_sheet_some_local_assets": "Bəzi lokal resurslar alboma əlavə edilə bilmədi", - "add_to_album_toggle": "{album} üçün seçimi dəyişin", - "add_to_albums": "Albomlara əlavə et", - "add_to_albums_count": "({count}) albomlarına əlavə et", - "add_to_bottom_bar": "Əlavə et", - "add_to_shared_album": "Paylaşılan alboma əlavə et", - "add_upload_to_stack": "Yeni yüklənmə əlavə et", - "add_url": "URL əlavə et", - "added_to_archive": "Arxivə əlavə edildi", - "added_to_favorites": "Sevimlilələrə əlavə edildi", - "added_to_favorites_count": "{count, number} şəkil sevimlilələrə əlavə edildi", - "admin": { - "add_exclusion_pattern_description": "Çıxarma nümunələri əlavə et. *, ** və ? istifadə edilərək globbing dəstəklənir. Hər hansı bir \"Raw\" adlı qovluqdakı bütün faylları görməməzlikdən gəlmək üçün **/Raw/** istifadə et. “.tif” ilə bitən bütün faylları görməməzlikdən gəlmək üçün **/*.tif istifadə et. Tam yolu görməməzlikdən gəlmək üçün /path/to/ignore/** istifadə et.", - "admin_user": "İdarəçi İstifadəçi", - "asset_offline_description": "Bu xarici kitabxana varlığı diskdə artıq tapılmadı və zibil qutusuna köçürüldü. Əgər fayl kitabxana içərisində köçürülübsə, zaman şkalanızı yeni uyğun gələn varlıq üçün yoxlayın. Varlığı yenidən qaytarmaq üçün aşağıda verilmiş fayl yolunun Immich tərəfindən əlçatan olduğundan əmin olduqdan sonra kitabxananı skan edin.", - "authentication_settings": "Səlahiyyətləndirmə parametrləri", - "authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri", - "authentication_settings_disable_all": "Bütün giriş etmə metodlarını söndürmək istədiyinizdən əminsinizmi? Giriş etmə funksiyası tamamilə söndürüləcəkdir.", - "authentication_settings_reenable": "Yenidən aktiv etmək üçün Server Əmri -ni istifadə edin.", - "background_task_job": "Arxa plan tapşırıqları", - "backup_database": "Verilənlər bazasının dump-ını yaradın", - "backup_database_enable_description": "Verilənlər bazasının artıq nüsxələrini aktiv et", - "backup_keep_last_amount": "Tutulması gərəkən nüsxələrin sayı", - "backup_onboarding_1_description": "buludda və ya başqa fiziki yerdə saytdan kənar surət.", - "backup_onboarding_2_description": "müxtəlif cihazlarda yerli nüsxələr. Bura əsas fayllar və həmin faylların ehtiyat lokal nüsxəsi daxildir.", - "backup_onboarding_3_description": "orijinal fayllar da daxil olmaqla məlumatlarınızın ümumi surətləri. Buraya 1 kənar nüsxə və 2 lokal nüsxə daxildir.", - "backup_onboarding_footer": "Immich-in ehtiyat nüsxəsini çıxarmaq haqqında ətraflı məlumat üçün sənədlərə müraciət edin.", - "backup_onboarding_parts_title": "3-2-1 ehtiyat nüsxəsinə aşağıdakılar daxildir:", - "backup_onboarding_title": "Ehtiyat surətlər", - "backup_settings": "Bazanın Dump Parametrləri", - "backup_settings_description": "Verilənlər bazasının ehtiyat nüsxə parametrlərini idarə et", - "cleared_jobs": "{job} üçün tapşırıqlar silindi", - "config_set_by_file": "Konfiqurasiya hal-hazırda konfiqurasiya faylı ilə təyin olunub", - "confirm_delete_library": "{library} kitabxanasını silmək istədiyinizdən əminmisiniz?", - "confirm_email_below": "Təsdiqləmək üçün aşağıya {email} yazın", - "confirm_user_password_reset": "{user} adlı istifadəçinin şifrəsini sıfırlamaq istədiyinizdən əminmisiniz?", - "disable_login": "Giriş etməni söndür", - "duplicate_detection_job_description": "Bənzər şəkilləri tapmaq üçün maşın öyrənməsini işə salın. Bu prosses Smart Search funksiyasına əsaslanır", - "face_detection": "Üz tanıma", - "force_delete_user_warning": "XƏBƏRDARLIQ: Bu əməliyyat istifadəçi və bütün məlumatları siləcəkdir. Bu prossesi və silinən faylları geri qaytarmaq olmaz.", - "image_format_description": "WebP, JPEG faylına görə daha kiçik həcmə sahibdir, lakin onu kodlaşdırmaq daha çox vaxt alır.", - "image_preview_title": "Önizləmə parametrləri", - "image_quality": "Keyfiyyət", - "image_resolution": "Çözümlülük", - "image_resolution_description": "Yüksək çözümlülükdə daha çox detallar vardır, lakin onları kodlaşdırmaq da daha çox vaxt alır, daha böyük həcmə sahib olurlar və tətbiqin işləmə sürətini yavaşladır.", - "image_settings": "Şəklin parametrləri", - "image_settings_description": "Hazırlanan şəkillərin keyfiyyətini və çözümlülüyünü idarə et", - "image_thumbnail_title": "Önizləmə parametrləri", - "job_concurrency": "{job}paralellik", - "job_created": "Tapşırıq yaradıldı", - "job_not_concurrency_safe": "Bu iş eyni vaxtda icra üçün təhlükəsiz deyil.", - "job_settings": "Tapşırıq parametrləri", - "job_settings_description": "Parallel şəkildə fəaliyyət göstərən tapşırıqları idarə et", - "jobs_delayed": "{jobCount, plural, other {# gecikməli}}", - "jobs_failed": "{jobCount, plural, other {# uğursuz}}", - "library_created": "{library} kitabxanası yaradıldı", - "library_deleted": "Kitabxana silindi", - "library_scanning": "Periodik skan", - "library_scanning_description": "Periodik kitabxana skanını confiqurasiya et", - "library_scanning_enable_description": "Periodik kitabxana skanını aktivləşdir", - "library_settings": "Xarici kitabxana", - "library_settings_description": "Xarici kitabxana parametrlərini idarə et", - "library_tasks_description": "Kitabxana tapşırıqlarını yerinə yetir", - "library_watching_enable_description": "Fayl dəyişiklikləri üçün xarici kitabxanalara baxış keçirin", - "library_watching_settings": "Kitabxana nəzarəti (EKSPERİMENTAL)", - "library_watching_settings_description": "Dəyişdirilən faylları avtomatik olaraq yoxla", - "logging_enable_description": "Jurnalı aktivləşdir", - "logging_level_description": "Aktiv edildikdə hansı jurnal səviyyəsi istifadə olunur.", - "machine_learning_clip_model": "CLIP modeli", - "machine_learning_clip_model_description": "Buradaqeyd olunan CLIP modelinin adı. Modeli dəyişdirdikdən sonra bütün şəkillər üçün 'Ağıllı Axtarış' funksiyasını yenidən işə salmalısınız.", - "machine_learning_duplicate_detection": "Dublikat Aşkarlama", - "machine_learning_duplicate_detection_enabled": "Dublikat aşkarlamanı aktiv etmək", - "machine_learning_duplicate_detection_enabled_description": "Əgər deaktiv edilibsə, birə-bir eyni fayllar yenədə silinəcək.", - "machine_learning_duplicate_detection_setting_description": "Bir-birinin dublikatı olan faylları tapmaq üçün CLIP-dən istifadə edin", - "machine_learning_enabled": "Maşın öyrənməsini aktiv edin", - "machine_learning_enabled_description": "Əgər deaktiv edilərsə, aşağıdakı parametrlərdən asılı olmayaq, bütün Maşın Öyrənmə funksiyaları deaktiv ediləcək.", - "machine_learning_facial_recognition": "Üz Tanıma", - "machine_learning_facial_recognition_description": "Şəkillərdəki üzləri aşkarla, tanı və qruplaşdır", - "machine_learning_facial_recognition_model": "Üz tanıma modeli" - }, - "timeline": "Zaman şkalası" -} +{} diff --git a/i18n/be.json b/i18n/be.json index 13ac6747f1..0967ef424b 100644 --- a/i18n/be.json +++ b/i18n/be.json @@ -1,570 +1 @@ -{ - "about": "Аб прадукце", - "account": "Уліковы запіс", - "account_settings": "Налады ўліковага запісу", - "acknowledge": "Пацвердзіць", - "action": "Дзеянне", - "action_common_update": "Абнавіць", - "action_description": "Дзеянні, якія выконваюцца з адабранымі аб’ектамі", - "actions": "Дзеянні", - "active": "Апрацоўваюцца", - "active_count": "Апрацоўваюцца: {count}", - "activity": "Актыўнасць", - "activity_changed": "Актыўнасць {enabled, select, true {уключана} other {адключана}}", - "add": "Дадаць", - "add_a_description": "Дадаць апісанне", - "add_a_location": "Дадаць месца", - "add_a_name": "Дадаць імя", - "add_a_title": "Дадаць загаловак", - "add_action": "Дадаць дзеянне", - "add_action_description": "Націсніце для дадання дзеяння", - "add_assets": "Дадаць аб’екты", - "add_birthday": "Дадаць дзень нараджэння", - "add_endpoint": "Дадаць кропку доступу", - "add_exclusion_pattern": "Дадаць шаблон выключэння", - "add_filter": "Дадаць фільтр", - "add_filter_description": "Націсніце для дадання ўмовы адбору", - "add_location": "Дадаць месца", - "add_more_users": "Дадаць больш карыстальнікаў", - "add_partner": "Дадаць партнёра", - "add_path": "Дадаць шлях", - "add_photos": "Дадаць фота", - "add_tag": "Дадаць тэг", - "add_to": "Дадаць у…", - "add_to_album": "Дадаць у альбом", - "add_to_album_bottom_sheet_added": "Дададзена да {album}", - "add_to_album_bottom_sheet_already_exists": "Ужо знаходзіцца ў {album}", - "add_to_album_bottom_sheet_some_local_assets": "Некаторыя лакальныя аб’екты не могуць быць дададзены ў альбом", - "add_to_album_toggle": "Пераключыць выбар для {album}", - "add_to_albums": "Дадаць у альбомы", - "add_to_albums_count": "Дадаць у альбомы ({count})", - "add_to_bottom_bar": "Дадаць у", - "add_to_shared_album": "Дадаць у агульны альбом", - "add_upload_to_stack": "Запампаваць і дадаць у набор", - "add_url": "Дадаць URL", - "add_workflow_step": "Дадаць крок працоўнага працэсу", - "added_to_archive": "Дададзена ў архіў", - "added_to_favorites": "Дададзена ў абраныя", - "added_to_favorites_count": "Дададзена {count, number} да абранага", - "admin": { - "add_exclusion_pattern_description": "Дадайце шаблоны выключэнняў. Падтрымліваецца выкарыстанне сімвалаў * , ** і ?. Каб ігнараваць усе файлы ў любой дырэкторыі з назвай \"Raw\", выкарыстоўвайце \"**/Raw/**\". Каб ігнараваць усе файлы, якія заканчваюцца на \".tif\", выкарыстоўвайце \"**/.tif\". Каб ігнараваць абсолютны шлях, выкарыстоўвайце \"/path/to/ignore/**\".", - "admin_user": "Адміністратар", - "asset_offline_description": "Гэты знешні бібліятэчны актыў больш не знойдзены на дыску і быў перамешчаны ў сметніцу. Калі файл быў перамешчаны ў межах бібліятэкі, праверце вашу хроніку для новага адпаведнага актыва. Каб аднавіць гэты актыў, пераканайцеся, што шлях да файла ніжэй даступны для Immich і адскануйце бібліятэку.", - "authentication_settings": "Налады аўтэнтыфікацыі", - "authentication_settings_description": "Кіраванне паролямі, OAuth і іншыя налады аўтэнтыфікацыі", - "authentication_settings_disable_all": "Вы ўпэўнены, што хочаце адключыць усе спосабы ўваходу? Уваход будзе цалкам адключаны.", - "authentication_settings_reenable": "Каб зноў уключыць, выкарыстайце Каманду сервера.", - "background_task_job": "Фонавыя заданні", - "backup_database": "Стварыць рэзервовую копію базы даных", - "backup_database_enable_description": "Уключыць стварэнне дампаў базы даных", - "backup_keep_last_amount": "Колькасць папярэдніх рэзервовых копій для захавання", - "backup_onboarding_1_description": "зняшняя копія ў воблаку або ў іншым фізічным месцы.", - "backup_onboarding_2_description": "лакальныя копіі на іншых прыладах. Гэта ўключае ў сябе асноўныя файлы і лакальную рэзервовую копію гэтых файлаў.", - "backup_onboarding_3_description": "поўная колькасць копій вашых данных, у тым ліку зыходных файлаў. Гэта ўключае 1 пазаштатную копію і 2 лакальныя копіі.", - "backup_onboarding_description": " стратэгія рэзервавання 3-2-1 рэкамендавана для аховы вашых данных. Вы павінны захоўваць копіі вашых загружаных фота / відэа гэтак жа добра, як базу данных Immich для вычарпальна поўнага рэзервовага капіявання.", - "backup_onboarding_footer": "Каб атрымаць дадатковую інфармацыю пра рэзервовае капіраванне Immich, звярніцеся да дакументацыі.", - "backup_onboarding_parts_title": "Рэзервовая копія «3-2-1» уключае ў сябе:", - "backup_onboarding_title": "Рэзервовыя копіі", - "backup_settings": "Налады рэзервовага капіявання", - "backup_settings_description": "Кіраванне наладамі рэзервавання базы даных.", - "cleared_jobs": "Ачышчаны заданні для: {job}", - "config_set_by_file": "Канфігурацыя зараз усталявана праз файл канфігурацыі", - "confirm_delete_library": "Вы ўпэўнены што хочаце выдаліць бібліятэку {library}?", - "confirm_delete_library_assets": "Вы ўпэўнены, што хочаце выдаліць гэтую бібліятэку? Гэта прывядзе да выдалення {count, plural, one {# актыву} other {усіх # актываў}}, якія змяшчаюцца ў Immich, і гэта дзеянне немагчыма будзе адмяніць. Файлы застануцца на дыску.", - "confirm_email_below": "Каб пацвердзіць, увядзіце \"{email}\" ніжэй", - "confirm_reprocess_all_faces": "Вы ўпэўнены, што хочаце пераапрацаваць усе твары? Гэта таксама прывядзе да выдалення імён людзей.", - "confirm_user_password_reset": "Вы ўпэўнены ў тым, што хочаце скінуць пароль {user}?", - "confirm_user_pin_code_reset": "Вы ўпэўнены ў тым, што хочаце скінуць PIN-код {user}?", - "copy_config_to_clipboard_description": "Капіраваць бягучую канфігурацыю сістэмы ў JSON у буфер абмену", - "create_job": "Стварыць заданне", - "cron_expression": "Выраз Cron", - "cron_expression_description": "Задайце інтэрвал сканавання, выкарыстоўваючы фармат cron. Для атрымання дадатковай інфармацыі, звярніцеся, напрыклад, да Crontab Guru", - "cron_expression_presets": "Прадустаноўкі выразаў Cron", - "disable_login": "Адключыць уваход", - "duplicate_detection_job_description": "Запусціць машыннае навучанне на актывах для выяўлення падобных выяў. Залежыць ад Smart Search", - "exclusion_pattern_description": "Шаблоны выключэння дазваляюць ігнараваць файлы і папкі пры сканаванні вашай бібліятэкі. Гэта карысна, калі ў вас ёсць папкі, якія змяшчаюць файлы, якія вы не хочаце імпартаваць, напрыклад, файлы RAW.", - "export_config_as_json_description": "Захаваць бягучую канфігурацыю сістэмы ў файл JSON", - "external_libraries_page_description": "Кіраванне знешнімі бібліятэкамі", - "face_detection": "Выяўленне твараў", - "face_detection_description": "Выяўляць твары на фотаздымках і відэа з дапамогай машыннага навучання. Для відэа ўлічваецца толькі мініяцюра. \"Абнавіць\" (пера)апрацоўвае ўсе медыя. \"Скінуць\" дадаткова ачышчае ўсе бягучыя даныя пра твары. \"Адсутнічае\" ставіць у чаргу медыя, якія яшчэ не былі апрацаваныя. Выяўленыя твары будуць пастаўлены ў чаргу для распазнавання асоб пасля завяршэння выяўлення твараў, з групаваннем іх па існуючых або новых людзях.", - "facial_recognition_job_description": "Групаваць выяўленыя твары па асобах. Гэты этап выконваецца пасля завяршэння выяўлення твараў. \"Скінуць\" (паўторна) перагрупоўвае ўсе твары. \"Адсутнічае\" ставіць у чаргу твары, якія яшчэ не прыпісаныя да якой-небудзь асобы.", - "failed_job_command": "Каманда {command} не выканалася для задання: {job}", - "force_delete_user_warning": "ПАПЯРЭДЖАННЕ: Гэта дзеянне неадкладна выдаліць карыстальніка і ўсе аб'екты. Гэта дзеянне не можа быць адроблена і файлы немагчыма будзе аднавіць.", - "image_format": "Фармат", - "image_format_description": "WebP стварае меншыя файлы, чым JPEG, але павольней кадуе.", - "image_fullsize_description": "Выява ў поўным памеры без метаданых, выкарыстоўваецца пры павелічэнні", - "image_fullsize_enabled": "Уключыць стварэнне выявы ў поўным памеры", - "image_fullsize_enabled_description": "Ствараць выяву ў поўным памеры для фарматаў, што не прыдатныя для вэб. Калі ўключана опцыя \"Аддаваць перавагу ўбудаванай праяве\", прагляды выкарыстоўваюцца непасрэдна без канвертацыі. Не ўплывае на вэб-прыдатныя фарматы, такія як JPEG.", - "image_fullsize_quality_description": "Якасць выявы ў поўным памеры ад 1 да 100. Больш высокае значэнне лепшае, але прыводзіць да павелічэння памеру файла.", - "image_fullsize_title": "Налады выявы ў поўным памеры", - "image_prefer_embedded_preview": "Аддаваць перавагу ўбудаванай праяве", - "image_prefer_embedded_preview_setting_description": "Выкарыстоўваць убудаваныя праявы ў RAW-фотаздымках ў якасці ўваходных даных для апрацоўкі малюнкаў, калі магчыма. Гэта дазваляе атрымаць больш дакладныя колеры для некаторых відарысаў, але ж якасць праяў залежыць ад камеры, і на відарысе можа быць больш артэфактаў сціску.", - "image_prefer_wide_gamut": "Аддаць перавагу шырокай гаме", - "image_prefer_wide_gamut_setting_description": "Выкарыстоўвайце Display P3 для мініяцюр. Гэта лепей захоўвае яркасць відарысаў з шырокай колеравай прасторай, але відарысы могуць выглядаць па-іншаму на старых прыладах са старай версіяй браузера. Відарысы sRGB захоўваюцца ў фармаце sRGB, што дазваляе пазбегнуць колеравых зрухаў.", - "image_preview_description": "Відарыс сярэдняга памеру з выдаленымі метаданымі, выкарыстоўваецца пры праглядзе асобнага рэсурсу і для машыннага навучання", - "image_preview_quality_description": "Якасць праявы ад 1 да 100. Чым вышэй, тым лепш, але пры гэтым ствараюцца файлы большага памеру і можа знізіцца хуткасць водгуку прыкладання. Ўстаноўка нізкага значэння можа паўплываць на якасць машыннага навучання.", - "image_preview_title": "Налады папярэдняга прагляду", - "image_quality": "Якасць", - "image_resolution": "Раздзяляльнасць", - "image_resolution_description": "Больш высокая раздзяляльнасць дазваляе захаваць больш дэталяў, але патрабуе больш часу для кадавання, прыводзіць да павялічвання памеру файлаў і можа знізіць хуткасць водгуку дадатку.", - "image_settings": "Налады відарыса", - "image_settings_description": "Кіруйце якасцю і раздзяляльнасцю сгенерыраваных відарысаў", - "image_thumbnail_description": "Маленькая мініяцюра з выдаленымі метададзенымі, якая выкарыстоўваецца пры праглядзе груп фатаграфій, такіх як асноўная хроніка", - "image_thumbnail_quality_description": "Якасць мініяцюр ад 1 да 100. Чым вышэй якасць, тым лепш, але пры гэтым ствараюцца файлы большага памеру і можа знізіцца хуткасць водгуку прыкладання.", - "image_thumbnail_title": "Налады мініяцюр", - "import_config_from_json_description": "Імпартаваць канфігурацыю сістэмы праз запампоўванне JSON файла настроек", - "job_concurrency": "Колькасць паралельных патокаў задання {job}", - "job_created": "Заданне створана", - "job_not_concurrency_safe": "Гэта заданне небяспечнае для паралельнага выканання.", - "job_settings": "Налады заданняў", - "job_settings_description": "Кіраваць наладамі паралельнага выканання заданняў", - "jobs_delayed": "{jobCount, plural, other {# адкладзена}}", - "jobs_failed": "{jobCount, plural, other {# не выканалася}}", - "library_created": "Створана бібліятэка: {library}", - "library_deleted": "Бібліятэка выдалена", - "library_details": "Параметры бібліятэкі", - "library_folder_description": "Вызначце папку для імпарту. Гэта папка, уключаючы падпапкі, будзе прасканавана на наяўнасць фота і відэа.", - "library_remove_exclusion_pattern_prompt": "Вы упэўнены, што хочаце выдаліць гэты шаблон выключэння?", - "library_remove_folder_prompt": "Вы упэўнены, што хочаце выдаліць гэту папку імпарту?", - "library_scanning": "Сканаванне па раскладзе", - "library_scanning_description": "Наладзьце параметры сканавання вашай бібліятэкі", - "library_scanning_enable_description": "Уключыць перыядычнае сканаванне бібліятэкі", - "library_settings": "Знешняя бібліятэка", - "library_settings_description": "Наладзьце параметры знешняй бібліятэкі", - "library_tasks_description": "Сканаваць знешнія бібліятэкі на наяўнасць новых і/або змененых рэсурсаў", - "library_updated": "Бібліятэка абноўлена", - "library_watching_enable_description": "Назіраць за зменамі файлаў у знешніх бібліятэках", - "library_watching_settings": "[ЭКСПЕРЫМЕНТАЛЬНА] Сачыць за бібліятэкай", - "library_watching_settings_description": "Аўтаматычна сачыць за зменамі ў файлах", - "logging_enable_description": "Уключыць вядзенне журнала", - "logging_level_description": "Калі уключана, які ўзровень журналявання выкарыстоўваць.", - "logging_settings": "Вядзенне журнала", - "machine_learning_availability_checks": "Праверка даступнасці", - "machine_learning_availability_checks_description": "Аўтаматычна выяўляць і надаваць перавагу даступным серверам машыннага навучання", - "machine_learning_availability_checks_enabled": "Уключыць праверку даступнасці", - "machine_learning_availability_checks_interval": "Інтэрвал праверкі", - "machine_learning_availability_checks_interval_description": "Інтэрвал у мілісекундах паміж праверкамі даступнасці", - "machine_learning_availability_checks_timeout": "Час чакання запыту", - "machine_learning_availability_checks_timeout_description": "Час чакання ў мілісекундах для праверкі даступнасці", - "machine_learning_clip_model": "CLIP мадэль", - "machine_learning_clip_model_description": "Назва CLIP мадэлі паказана тут. Звярніце ўвагу, што пры змене мадэлі неабходна паўторна запусціць заданне \"Smart Search\" для ўсіх відарысаў.", - "machine_learning_duplicate_detection": "Выяўленне падобных", - "machine_learning_duplicate_detection_enabled": "Уключыць выяўленне дублікатаў", - "machine_learning_duplicate_detection_enabled_description": "Калі адключана, абсалютна ідэнтычныя файлы ўсё роўна не будуць запампоўвацца.", - "machine_learning_duplicate_detection_setting_description": "Выкарыстанне ўбудаванняў CLIP для пошуку верагодных дублікатаў", - "machine_learning_enabled": "Уключыць машыннае навучанне", - "machine_learning_enabled_description": "Калі адключана, усе функцыі машыннага навучання будуць адключаны незалежна ад налад ніжэй.", - "machine_learning_facial_recognition": "Распазнаванне твараў", - "machine_learning_facial_recognition_description": "Выяўленне, распазнаванне і групаванне твараў на відарысах", - "machine_learning_facial_recognition_model": "Мадэль распазнавання твараў", - "machine_learning_facial_recognition_model_description": "Мадэлі пералічаны ў парадку ўбывання іх памеру. Большыя мадэлі павольней і выкарыстоўваюць больш памяці, але даюць лепшыя вынікі. Звярніце увагу, што пасля змены мадэлі трэба зноў запусціць заданне распазнавання твараў для ўсіх відарысаў.", - "machine_learning_facial_recognition_setting": "Уключыць распазнаванне твараў", - "machine_learning_facial_recognition_setting_description": "Калі адключана, відарысы не будуць кадавацца для распазнавання твараў, і не будзе запаўняцца раздзел \"Людзі\" на старонцы \"Агляд\".", - "machine_learning_ocr_max_resolution": "Максімальная раздзяляльнасць", - "machine_learning_ocr_max_resolution_description": "Відарысы з раздзяляльнасцю больш гэтай будуць паменшаны з захаваннем суадносіны бакоў. Больш высокія значэнні павышаюць дакладнасць распазнавання, але патрабуюць больш часу на апрацоўку і выкарыстоўваюць больш памяці.", - "map_dark_style": "Цёмны стыль", - "map_enable_description": "Уключыць функцыі карты", - "map_gps_settings": "Налады карты і GPS", - "map_light_style": "Светлы стыль", - "map_settings": "Карта", - "map_settings_description": "Кіраванне наладамі карты", - "map_style_description": "URL-адрас style.json тэмы карты", - "metadata_extraction_job_description": "Выняць метаданыя з файлаў, такія як месцазнаходжанне, твары і раздзяляльнасць", - "metadata_settings": "Налады метаданых", - "oauth_button_text": "Тэкст кнопкі", - "oauth_settings": "OAuth", - "refreshing_all_libraries": "Абнаўленне ўсіх бібліятэк", - "registration": "Рэгістрацыя адміністратара", - "registration_description": "Вы з'яўляецеся першым карыстальнікам сістэмы, таму вы будзеце прызначаны адміністратарам. Вы будзеце адказваць за адміністрацыйныя задачы, а таксама ствараць новых карыстальнікаў.", - "require_password_change_on_login": "Патрабаваць змяніць пароль пры першым уваходзе ў сістэму", - "reset_settings_to_default": "Скінуць налады да прадвызначаных", - "reset_settings_to_recent_saved": "Скінуць налады да нядаўна захаваных", - "scanning_library": "Сканіраванне бібліятэкі", - "server_external_domain_settings": "Знешні дамен", - "server_settings": "Налады сервера", - "server_settings_description": "Кіраванне наладамі сервера", - "server_welcome_message": "Прывітальнае паведамленне", - "server_welcome_message_description": "Паведамленне, якое адлюстроўваецца на старонцы ўваходу.", - "system_settings": "Сістэмныя налады", - "tag_cleanup_job": "Ачыстка тэгаў", - "template_email_preview": "Перадпрагляд", - "theme_settings": "Налады тэмы", - "transcoding_acceleration_nvenc": "NVENC (патрабуецца відэакарта NVIDIA)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_containers": "Прынятыя кантэйнеры", - "transcoding_accepted_video_codecs": "Прынятыя відэакодэкі", - "transcoding_advanced_options_description": "Параметры, якія большасці карыстальнікаў не трэба змяняць", - "transcoding_audio_codec": "Аудыякодэк", - "transcoding_encoding_options": "Параметры кадавання", - "transcoding_encoding_options_description": "Задайце кодэкі, раздзяляльнасць, якасць і іншыя параметры для кадавання відэа", - "transcoding_optimal_description": "Відэа з раздзяляльнасцю вышэй мэтавай ці ў непрынятым фармаце", - "transcoding_target_resolution": "Мэтавая раздзяляльнасць", - "transcoding_target_resolution_description": "Вышэйшыя раздзяляльнасці могуць захаваць больш дэталей, але патрабуюць больш часу для кадавання, маюць большы памер файлаў і могуць зменшыць хуткасць адказу праграмы.", - "transcoding_video_codec": "Відэакодэк", - "trash_enabled_description": "Уключыць функцыі сметніцы", - "trash_number_of_days": "Колькасць дзён", - "trash_settings": "Налады сметніцы", - "trash_settings_description": "Кіраванне наладамі сметніцы", - "user_cleanup_job": "Ачыстка карыстальніка", - "user_delete_delay": "Уліковы запіс {user} і актывы будуць запланаваны для канчатковага выдалення праз {delay, plural, one {# дзень} few {# дні} many {# дзён} other {# дзён}}.", - "user_delete_delay_settings_description": "Колькасць дзён пасля выдалення, па заканчэнні якіх уліковы запіс карыстальніка і яго актывы будуць выдаленыя незваротна. Заданне на выдаленне карыстальніка запускаецца апоўначы для праверкі гатоўнасці карыстальнікаў да выдалення. Змены ў гэтым параметры будуць улічаныя пры наступным выкананні.", - "user_delete_immediately": "Уліковы запіс {user} і актывы будуць неадкладна змешчаны ў чаргу на канчатковае выдаленне.", - "user_management": "Кіраванне карыстальнікамі", - "user_password_has_been_reset": "Пароль карыстальніка быў скінуты:", - "user_password_reset_description": "Задайце карыстальніку часовы пароль і паведаміце яму, што пры наступным уваходзе ў сістэму яму трэба будзе змяніць пароль.", - "user_restore_description": "Уліковы запіс карыстальніка {user} будзе адноўлены.", - "user_settings": "Налады карыстальніка", - "user_settings_description": "Кіраванне наладамі карыстальніка", - "version_check_enabled_description": "Уключыць праверку версіі", - "version_check_implications": "Функцыя праверкі версіі перыядычна звяртаецца да github.com", - "version_check_settings": "Праверка версіі", - "version_check_settings_description": "Уключыць/адключыць апавяшчэнні аб новай версіі" - }, - "admin_email": "Электронная пошта адміністратара", - "admin_password": "Пароль адміністратара", - "administration": "Кіраванне серверам", - "advanced": "Пашыраныя", - "advanced_settings_log_level_title": "Узровень вядзення журнала: {level}", - "advanced_settings_proxy_headers_title": "[ЭКСПЕРЫМЕНТАЛЬНА] Уласныя загалоўкі проксі", - "advanced_settings_tile_subtitle": "Пашыраныя налады карыстальніка", - "advanced_settings_troubleshooting_subtitle": "Уключыць дадатковыя функцыі для выпраўлення непаладак", - "advanced_settings_troubleshooting_title": "Выпраўленне непаладак", - "age_months": "Узрост {months, plural, one {# месяц} few {# месяцы} many {# месяцаў} other {# месяцаў}}", - "age_year_months": "Узрост 1 год, {months, plural, one {# месяц} few {# месяцы} many {# месяцаў} other {# месяцаў}}", - "age_years": "{years, plural, other {Узрост #}}", - "album_added": "Альбом дададзены", - "album_cover_updated": "Вокладка альбома абноўлена", - "album_delete_confirmation": "Вы ўпэўнены, што хочаце выдаліць альбом {album}?", - "album_delete_confirmation_description": "Калі гэты альбом абагулены, іншыя карыстальнікі больш не змогуць атрымаць да яго доступ.", - "album_deleted": "Альбом выдалены", - "album_info_card_backup_album_excluded": "ВЫКЛЮЧАНЫ", - "album_info_card_backup_album_included": "УКЛЮЧАНЫ", - "album_info_updated": "Інфармацыя пра альбом абноўлена", - "album_leave": "Пакінуць альбом?", - "album_leave_confirmation": "Вы ўпэўнены, што хочаце пакінуць {album}?", - "album_name": "Назва альбома", - "album_options": "Параметры альбома", - "album_remove_user": "Выдаліць карыстальніка?", - "album_remove_user_confirmation": "Вы ўпэўнены, што хочаце выдаліць {user}?", - "album_search_not_found": "Па вашым запыце не знойдзена альбомаў", - "album_share_no_users": "Здаецца, вы падзяліліся гэтым альбомам з усімі карыстальнікамі, або ў вас няма ніводнага карыстальніка, з якім можна падзяліцца.", - "album_updated": "Альбом абноўлены", - "album_user_left": "Вы пакінулі {album}", - "album_user_removed": "Карыстальнік {user} выдалены", - "album_viewer_appbar_delete_confirm": "Вы ўпэўнены, што хочаце выдаліць гэты альбом са свайго ўліковага запісу?", - "album_viewer_appbar_share_err_delete": "Не ўдалося выдаліць альбом", - "album_viewer_appbar_share_err_leave": "Не ўдалося пакінуць альбом", - "album_viewer_appbar_share_err_title": "Не ўдалося змяніць назву альбома", - "album_viewer_appbar_share_leave": "Пакінуць альбом", - "album_viewer_appbar_share_to": "Абагуліць з", - "album_viewer_page_share_add_users": "Дадаць карыстальнікаў", - "album_with_link_access": "Дазволіць усім, хто мае спасылку, бачыць фота і людзей у гэтым альбоме.", - "albums": "Альбомы", - "albums_count": "{count, plural, one {1 альбом} few {{count, number} альбомы} many {{count, number} альбомаў} other {{count, number} альбомаў}}", - "albums_default_sort_order": "Прадвызначаны парадак сартавання альбомаў", - "albums_on_device_count": "Альбомы на прыладзе ({count})", - "all": "Усе", - "all_albums": "Усе альбомы", - "all_people": "Усе людзі", - "all_videos": "Усе відэа", - "allow_dark_mode": "Дазволіць цёмны рэжым", - "allow_edits": "Дазволіць рэдагаванне", - "alt_text_qr_code": "Відарыс QR-кода", - "anti_clockwise": "Супраць гадзіннікавай стрэлкі", - "api_key": "Ключ API", - "api_key_empty": "Назва ключа API не павінна быць пустой", - "api_keys": "Ключы API", - "app_bar_signout_dialog_content": "Вы ўпэўнены, што хочаце выйсці?", - "app_bar_signout_dialog_ok": "Так", - "app_bar_signout_dialog_title": "Выйсці", - "app_settings": "Налады праграмы", - "archive": "Архіў", - "archive_page_title": "Архіў ({count})", - "archive_size": "Памер архіва", - "are_these_the_same_person": "Ці гэта адзін і той жа чалавек?", - "are_you_sure_to_do_this": "Вы ўпэўнены, што хочаце гэта зрабіць?", - "asset_added_to_album": "Дададзена ў альбом", - "asset_adding_to_album": "Дадаванне ў альбом…", - "asset_skipped": "Прапушчана", - "asset_skipped_in_trash": "У сметніцы", - "asset_uploaded": "Запампавана", - "asset_uploading": "Запампоўванне…", - "assets_were_part_of_albums_count": "{count, plural, one {Актыў ужо быў} other {Актывы ужо былі}} часткай альбому", - "authorized_devices": "Аўтарызаваныя прылады", - "automatic_endpoint_switching_subtitle": "Падключацца лакальна па вылучаным Wi-Fi, калі гэта магчыма, і выкарыстоўваць альтэрнатыўныя падключэння ў іншых месцах", - "automatic_endpoint_switching_title": "Аўтаматычнае пераключэнне URL", - "back": "Назад", - "backup_album_selection_page_albums_device": "Альбомы на прыладзе ({count})", - "backup_all": "Усе", - "backup_controller_page_background_wifi": "Толькі праз Wi-Fi", - "buy": "Купіць Immich", - "cache_settings_clear_cache_button": "Ачысціць кэш", - "cache_settings_tile_title": "Лакальнае сховішча", - "cancel": "Скасаваць", - "cancel_search": "Скасаваць пошук", - "canceled": "Скасавана", - "city": "Горад", - "clear": "Ачысціць", - "clear_all": "Ачысціць усё", - "client_cert_dialog_msg_confirm": "ОК", - "client_cert_enter_password": "Увядзіце пароль", - "client_cert_import": "Імпарт", - "close": "Закрыць", - "collapse": "Згарнуць", - "collapse_all": "Згарнуць усё", - "color": "Колер", - "color_theme": "Колеравая тэма", - "continue": "Працягнуць", - "control_bottom_app_bar_create_new_album": "Стварыць новы альбом", - "control_bottom_app_bar_delete_from_immich": "Выдаліць з Immich", - "control_bottom_app_bar_delete_from_local": "Выдаліць з прылады", - "control_bottom_app_bar_edit_location": "Рэдагаваць месцазнаходжанне", - "country": "Краіна", - "cover": "Вокладка", - "covers": "Вокладкі", - "create": "Стварыць", - "create_album": "Стварыць альбом", - "create_album_page_untitled": "Без назвы", - "create_library": "Стварыць бібліятэку", - "create_link": "Стварыць спасылку", - "create_new_user": "Стварыць новага карыстальніка", - "create_tag": "Стварыць тэг", - "create_user": "Стварыць карыстальніка", - "dark": "Цёмная", - "day": "Дзень", - "delete": "Выдаліць", - "delete_album": "Выдаліць альбом", - "delete_dialog_ok_force": "Усё адно выдаліць", - "delete_dialog_title": "Выдаліць назаўжды", - "delete_face": "Выдаліць твар", - "delete_key": "Выдаліць ключ", - "delete_library": "Выдаліць бібліятэку", - "delete_link": "Выдаліць спасылку", - "delete_local_dialog_ok_force": "Усё адно выдаліць", - "delete_others": "Выдаліць іншыя", - "delete_tag": "Выдаліць тэг", - "delete_user": "Выдаліць карыстальніка", - "discord": "Discord", - "documentation": "Дакументацыя", - "done": "Гатова", - "download": "Спампаваць", - "download_canceled": "Спампоўванне скасавана", - "download_complete": "Спампоўванне завершана", - "download_enqueue": "Спампоўванне дададзена ў чаргу", - "downloading": "Спампоўванне", - "edit": "Рэдагаваць", - "edit_album": "Рэдагаваць альбом", - "edit_avatar": "Рэдагаваць аватар", - "edit_date": "Рэдагаваць дату", - "edit_date_and_time": "Рэдагаваь дату і час", - "edit_description": "Рэдагаваць апісанне", - "edit_description_prompt": "Выберыце новае апісанне:", - "edit_faces": "Рэдагаваць твары", - "edit_key": "Рэдагаваць ключ", - "edit_link": "Рэдагаваць спасылку", - "edit_location": "Рэдагаваць месцазнаходжанне", - "edit_location_dialog_title": "Месцазнаходжанне", - "edit_name": "Рэдагаваць назву", - "edit_people": "Рэдагаваць людзей", - "edit_tag": "Рэдагаваць тэг", - "edit_title": "Рэдагаваць загаловак", - "edit_user": "Рэдагаваць карыстальніка", - "editor": "Рэдактар", - "editor_close_without_save_prompt": "Змены не будуць захаваны", - "editor_close_without_save_title": "Закрыць рэдактар?", - "error": "Памылка", - "error_saving_image": "Памылка: {error}", - "exif": "Exif", - "exif_bottom_sheet_description": "Дадаць апісанне...", - "explore": "Агляд", - "favorite": "У абраным", - "favorite_or_unfavorite_photo": "Дадаць або выдаліць фота з абранага", - "favorites": "Абраныя", - "filename": "Назва файла", - "filetype": "Тып файла", - "filter": "Фільтр", - "forward": "Наперад", - "gcast_enabled": "Google Cast", - "general": "Агульныя", - "go_back": "Назад", - "go_to_folder": "Перайсці да папкі", - "hi_user": "Вітаем, {name} ({email})", - "hide_all_people": "Схаваць усіх людзей", - "hide_gallery": "Схаваць галерэю", - "hide_named_person": "Схаваць {name}", - "hide_password": "Схаваць пароль", - "hide_person": "Схаваць чалавека", - "image_viewer_page_state_provider_download_started": "Спампоўванне пачалося", - "immich_logo": "Лагатып Immich", - "interval": { - "day_at_onepm": "Кожны дзень а 13-й гадзіне", - "hours": "{hours, plural, one {Кожную гадзіну} few {Кожныя {hours, number} гадзіны} many {Кожныя {hours, number} гадзін} other {Кожныя {hours, number} гадзін}}", - "night_at_midnight": "Кожную ноч апоўначы", - "night_at_twoam": "Кожную ноч а 2-й гадзіне" - }, - "language": "Мова", - "library": "Бібліятэка", - "light": "Светлая", - "login_form_back_button_text": "Назад", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_password_hint": "пароль", - "login_form_save_login": "Заставацца ў сістэме", - "main_menu": "Галоўнае меню", - "map_location_dialog_yes": "Так", - "map_settings_dark_mode": "Цёмны рэжым", - "map_settings_date_range_option_day": "Апошнія 24 гадзіны", - "map_settings_date_range_option_days": "Апошніх дзён: {days}", - "map_settings_date_range_option_year": "Апошні год", - "map_settings_date_range_option_years": "Апошніх год: {years}", - "map_settings_dialog_title": "Налады карты", - "map_settings_theme_settings": "Тэма карты", - "menu": "Меню", - "minute": "Хвіліна", - "month": "Месяц", - "monthly_title_text_date_format": "MMMM y", - "my_albums": "Мае альбомы", - "name": "Імя", - "name_or_nickname": "Імя або псеўданім", - "next": "Далей", - "no": "Не", - "offline": "Па-за сеткай", - "ok": "ОК", - "online": "У сетцы", - "open": "Адкрыць", - "or": "або", - "partner_list_user_photos": "Фота карыстальніка {user}", - "pause": "Прыпыніць", - "people": "Людзі", - "permanent_deletion_warning": "Папярэджанне аб канчатковым выдаленні", - "permanent_deletion_warning_setting_description": "Паказаць папярэджанне пры канчатковым выдаленні рэсурсаў", - "permission_onboarding_back": "Назад", - "permission_onboarding_continue_anyway": "Усё адно працягнуць", - "photos": "Фота", - "photos_and_videos": "Фота і відэа", - "place": "Месца", - "places": "Месцы", - "port": "Порт", - "previous": "Папярэдняе", - "profile": "Профіль", - "profile_drawer_app_logs": "Журналы", - "profile_drawer_github": "GitHub", - "purchase_button_buy": "Купіць", - "purchase_button_buy_immich": "Купіць Immich", - "purchase_button_select": "Выбраць", - "readonly_mode_disabled": "Выключаны рэжым толькі для чытання", - "readonly_mode_enabled": "Уключаны рэжым толькі для чытання", - "reassign": "Перапрызначыць", - "reassing_hint": "Прыпісаць выбраныя актывы існуючай асобе", - "recent": "Нядаўні", - "recent-albums": "Нядаўнія альбомы", - "recent_searches": "Нядаўнія пошукі", - "recently_added": "Нядаўна дададзена", - "refresh_faces": "Абнавіць твары", - "remove": "Выдаліць", - "remove_from_album": "Выдаліць з альбома", - "remove_from_favorites": "Выдаліць з абраных", - "remove_tag": "Выдаліць тэг", - "remove_url": "Выдаліць URL-адрас", - "remove_user": "Выдаліць карыстальніка", - "rename": "Перайменаваць", - "repository": "Рэпазіторый", - "reset": "Скінуць", - "reset_password": "Скінуць пароль", - "resolution": "Раздзяляльнасць", - "restore": "Аднавіць", - "restore_all": "Аднавіць усё", - "restore_user": "Аднавіць карыстальніка", - "resume": "Узнавіць", - "role": "Роля", - "role_editor": "Рэдактар", - "role_viewer": "Глядач", - "save": "Захаваць", - "save_to_gallery": "Захаваць у галерэю", - "search_filter_date": "Дата", - "search_filter_location": "Месцазнаходжанне", - "search_filter_location_title": "Выберыце месцазнаходжанне", - "search_filter_media_type": "Тып медыя", - "search_filter_media_type_title": "Выберыце тып медыя", - "search_page_screenshots": "Здымкі экрана", - "search_page_selfies": "Сэлфі", - "search_page_things": "Рэчы", - "search_page_your_map": "Ваша карта", - "second": "Секунда", - "send_message": "Адправіць паведамленне", - "setting_image_viewer_original_subtitle": "Уключыце для запампавання зыходнага відарыса у поўнай раздзяляльнасці (шмат!). Адключыце каб зменшыць выкарыстанне трафіка (як сеткі, так і кэша прылады).", - "setting_image_viewer_preview_subtitle": "Уключыце для запампавання відарыса сярэдняй раздзяляльнасці. Адключыце, каб загружаць толькі арыгінал ці мініяцюру.", - "setting_languages_apply": "Ужыць", - "setting_notifications_notify_never": "ніколі", - "settings": "Налады", - "share_add_photos": "Дадаць фота", - "shared_album_section_people_title": "ЛЮДЗІ", - "shared_link_info_chip_metadata": "EXIF", - "sharing_page_empty_list": "ПУСТЫ СПІС", - "sign_out": "Выйсці", - "sign_up": "Зарэгістравацца", - "size": "Памер", - "sort_title": "Загаловак", - "source": "Крыніца", - "tag": "Тэг", - "tags": "Тэгі", - "theme": "Тэма", - "theme_selection": "Выбар тэмы", - "timeline": "Хроніка", - "total": "Усяго", - "trash": "Сметніца", - "trash_page_delete_all": "Выдаліць усе", - "trash_page_restore_all": "Аднавіць усе", - "trash_page_title": "Сметніца ({count})", - "type": "Тып", - "undo": "Адрабіць", - "upload": "Запампаваць", - "upload_status_errors": "Памылкі", - "uploading": "Запампоўванне", - "url": "URL-адрас", - "user": "Карыстальнік", - "user_has_been_deleted": "Гэты карыстальнік быў выдалены.", - "user_id": "ID карыстальніка", - "user_purchase_settings": "Купля", - "user_purchase_settings_description": "Кіруйце пакупкамі", - "user_role_set": "Прызначыць {user} як {role}", - "user_usage_detail": "Падрабязнасці выкарыстання карыстальнікам", - "user_usage_stats": "Статыстыка карыстання ўліковага запісу", - "user_usage_stats_description": "Прагледзець статыстыку карыстання ўліковага запісу", - "username": "Імя карыстальніка", - "users": "Карыстальнікі", - "utilities": "Утыліты", - "validate": "Праверыць", - "variables": "Пераменныя", - "version": "Версія", - "version_announcement_closing": "Твой сябар, Алекс", - "version_announcement_message": "Вітаем! Даступная новая версія Immich. Калі ласка, знайдзіце час, каб прачытаць нататкі да выпуску, каб пераканацца, што ваша налада актуальная і пазбегнуць магчымых памылак канфігурацыі, асабліва калі вы карыстаецеся WatchTower або іншымі механізмамі, якія аўтаматычна абнаўляюць вашу інстанцыю Immich.", - "version_history": "Гісторыя версій", - "version_history_item": "Усталявана версія {version} на {date}", - "video": "Відэа", - "video_hover_setting": "Прайграванне мініяцюры відэа пры навядзенні курсора", - "video_hover_setting_description": "Прайграванне мініяцюры відэа пры навядзенні курсора на элемент. Нават калі функцыя адключана, прайграванне можна пачаць, навёўшы курсор на значок прайгравання.", - "videos": "Відэа", - "videos_count": "{count, plural, one {# відэа} other {# відэа}}", - "view": "Прагляд", - "view_album": "Праглядзець альбом", - "view_all": "Праглядзець усё", - "view_all_users": "Праглядзець усех карыстальнікаў", - "view_in_timeline": "Паглядзець хроніку", - "view_links": "Праглядзець спасылкі", - "view_name": "Прагляд", - "view_next_asset": "Паказаць наступны аб'ект", - "view_previous_asset": "Праглядзець папярэдні аб'ект", - "view_stack": "Прагляд стэка", - "visibility_changed": "Бачнасць змянілася для {count, plural, one {# чалавека} other {# чалавек}}", - "waiting": "Чакаюць", - "warning": "Папярэджанне", - "week": "Тыдзень", - "welcome": "Вітаем", - "welcome_to_immich": "Вітаем у Immich", - "year": "Год", - "years_ago": "{years, plural, one {# год} few {# гады} many {# гадоў} other {# гадоў}} таму", - "yes": "Так", - "you_dont_have_any_shared_links": "У вас няма абагуленых спасылак", - "zoom_image": "Павялічыць відарыс" -} +{} diff --git a/i18n/bg.json b/i18n/bg.json index c1cf0abdf5..0967ef424b 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -1,2401 +1 @@ -{ - "about": "За Immich", - "account": "Акаунт", - "account_settings": "Настройки на профила", - "acknowledge": "Потвърждавам", - "action": "Действие", - "action_common_update": "Обнови", - "action_description": "Действия за изпълнение с филтрираните обекти", - "actions": "Действия", - "active": "Активни", - "active_count": "Активни: {count}", - "activity": "Дейност", - "activity_changed": "Дейността е {enabled, select, true {включена} other {изключена}}", - "add": "Добави", - "add_a_description": "Добави описание", - "add_a_location": "Добави местоположение", - "add_a_name": "Добави име", - "add_a_title": "Добaви заглавие", - "add_action": "Добави действие", - "add_action_description": "Натиснете за да добавите действие", - "add_assets": "Добавяне на обекти", - "add_birthday": "Добави дата на раждане", - "add_endpoint": "Добави крайна точка", - "add_exclusion_pattern": "Добави модел за изключване", - "add_filter": "Добави филтър", - "add_filter_description": "Натиснете за да добавите условие за филтър", - "add_location": "Дoбави местоположение", - "add_more_users": "Добави още потребители", - "add_partner": "Добави партньор", - "add_path": "Добави път", - "add_photos": "Добави снимки", - "add_tag": "Добави маркер", - "add_to": "Добави към…", - "add_to_album": "Добави към албум", - "add_to_album_bottom_sheet_added": "Добавено в {album}", - "add_to_album_bottom_sheet_already_exists": "Вече е в {album}", - "add_to_album_bottom_sheet_some_local_assets": "Някои локални файлове не успяха да се добавят към албума", - "add_to_album_toggle": "Сменете избора за {album}", - "add_to_albums": "Добавяне в албуми", - "add_to_albums_count": "Добавяне в албуми ({count})", - "add_to_bottom_bar": "Добави към", - "add_to_shared_album": "Добави към споделен албум", - "add_upload_to_stack": "Добави качените в група", - "add_url": "Добави URL", - "add_workflow_step": "Добави стъпка от работния процес", - "added_to_archive": "Добавено към архива", - "added_to_favorites": "Добавени към любимите ви", - "added_to_favorites_count": "Добавени {count, number} към любими", - "admin": { - "add_exclusion_pattern_description": "Добави модели за изключване. Поддържа се \"globbing\" с помощта на *, ** и ?. За да игнорирате всички файлове в директория с име \"Raw\", използвайте \"**/Raw/**\". За да игнорирате всички файлове, завършващи на \".tif\", използвайте \"**/*.tif\". За да игнорирате абсолютен път, използвайте \"/path/to/ignore/**\".", - "admin_user": "Администратор", - "asset_offline_description": "Този външен библиотечен елемент не може да бъде открит на диска и е преместен в кошчето за боклук. Ако файлът е преместен в библиотеката, проверете вашата история за нов съответстващ елемент. За да възстановите елемента, моля проверете дали файловият път отдолу може да бъде достъпен от Immich и сканирайте библиотеката.", - "authentication_settings": "Настройки за удостоверяване", - "authentication_settings_description": "Управление на парола, OAuth и други настройки за удостоверяване", - "authentication_settings_disable_all": "Сигурни ли сте, че искате да деактивирате всички методи за вписване? Вписването ще бъде напълно деактивирано.", - "authentication_settings_reenable": "За да реактивирате, използвайте Server Command.", - "background_task_job": "Процеси на заден фон", - "backup_database": "Създай резервна база данни", - "backup_database_enable_description": "Разреши резервни копия на базата данни", - "backup_keep_last_amount": "Брой запазени резервни копия", - "backup_onboarding_1_description": "копие на облака или друго физическо място.", - "backup_onboarding_2_description": "локални копия на различни устройства. Това включва основните файлове и локални архиви на тези файлове.", - "backup_onboarding_3_description": "общо копия на вашите данни, включитено оригиналните файлове. Това включва 1 копие извън системата и 2 локални копия.", - "backup_onboarding_description": "За надеждна защита препоръчваме стратегията 3-2-1. Правете архивни копия както на качените снимки/видеа, така и на базата данни на Immich.", - "backup_onboarding_footer": "За подробна информация относно архивирането в Immich, моля вижте в документацията.", - "backup_onboarding_parts_title": "Стратегията 3-2-1 включва:", - "backup_onboarding_title": "Резервни копия", - "backup_settings": "Настройка на резервни копия на базата данни", - "backup_settings_description": "Управление на настройките за резервно копие на базата данни.", - "cleared_jobs": "Изчистени задачи от тип: {job}", - "config_set_by_file": "Конфигурацията е зададена от файл", - "confirm_delete_library": "Сигурни ли сте че искате да изтриете библиотеката - {library} ?", - "confirm_delete_library_assets": "Сигурни ли сте че искате да изтриете тази библиотека? Това ще изтрие {count, plural, one {# съдържания елемент} other {всички # съдържани елементи}} от Immich и е необратимо. Файловете остават на диска.", - "confirm_email_below": "За потвърждение, моля въведете \"{email}\" отдолу", - "confirm_reprocess_all_faces": "Сигурни ли сте, че искате да се обработят лицата отново? Това ще изчисти всички именувани хора.", - "confirm_user_password_reset": "Сигурни ли сте, че искате да нулирате паролата на {user}?", - "confirm_user_pin_code_reset": "Наистина ли искате да смените PIN кода на потребителя {user}?", - "copy_config_to_clipboard_description": "Копирай текущата системна конфигурация като JSON обект в клипборда", - "create_job": "Създайте задача", - "cron_expression": "Cron израз", - "cron_expression_description": "Настрой интервала на сканиране използвайки cron формата. За повече информация Crontab Guru", - "cron_expression_presets": "Примерни Cron изрази", - "disable_login": "Изключете вписването", - "duplicate_detection_job_description": "Стартиране машинно обучение върху елементи, за откриване на подобни изображения. Разчита на Интелигентно Търсене", - "exclusion_pattern_description": "Модели за изключване позволяват да игнорирате файлове и папки, когато сканирате вашата библиотека. Това е потребно, ако имате папки, които съдържат файлове, които не искате да импортирате. Примерно - RAW файлове.", - "export_config_as_json_description": "Запази текущата системна конфигурация като JSON файл", - "external_libraries_page_description": "Администриране на външната страница на библиотеката", - "face_detection": "Откриване на лица", - "face_detection_description": "Да се разпознават лица в елементи чрез машинно обучение. За видеата се използва само миниатюрата. \"Всички\" обработва отново всички елементи. \"Липсващи\" зарежда за обработка елементи, които на се обработени все още. Откритите лица ще бъдат подредени в опашка за разпознаване на лица след завършване на функцията за откриване на лица, като се групират в съществуващи или нови хора.", - "facial_recognition_job_description": "Групирайте откритите лица в хора. Тази стъпка се изпълнява след завършване на разпознаването на лица. „Нулиране“ прегрупира всички лица. „Липсващи“ поставя в опашка лицата, които нямат назначен човек.", - "failed_job_command": "Командата {command} е неуспешна за задача: {job}", - "force_delete_user_warning": "ВНИМАНИЕ: Това веднага ще изтрие потребителя и всичките му елементи. Действието е необратимо и файловете не могат да бъдат възстановени.", - "image_format": "Формат", - "image_format_description": "WebP създава по-малки файлове от JPEG, но ги кодира по-бавно.", - "image_fullsize_description": "Изображение в пълен размер, без метаданни. Използва се при увеличаване", - "image_fullsize_enabled": "Разреши генериране на изображение в пълен размер", - "image_fullsize_enabled_description": "Генерирай пълноразмерно изображение за формати, които не са предназначени за уеб. Когато е избрано \"Предпочитай вградено превю\", вграденото превю ще се ползва директно, без преобразуване. Не оказва влияние на уеб-съвместимите формати като JPEG.", - "image_fullsize_quality_description": "Качество на изображението в пълен размер от 1 до 100. По-голяма стойност - по-високо качество, но и по-голям размер на файла.", - "image_fullsize_title": "Настройки на изображение в пълен размер", - "image_prefer_embedded_preview": "Предпочитане на вградените прегледи", - "image_prefer_embedded_preview_setting_description": "Използване на вградените миниатюри в RAW снимките като входни за обработка на изображенията, когато има такива. Това може да доведе до по-точни цветове за някои изображения, но качеството на прегледите зависи от камерата и изображението може да има повече компресионни артефакти.", - "image_prefer_wide_gamut": "Предпочитане на широка гама", - "image_prefer_wide_gamut_setting_description": "Използване на Display P3 за миниатюри. Това запазва по-добре жизнеността на изображенията с широки цветови пространства, но изображенията може да изглеждат по различен начин на стари устройства със стара версия на браузъра. sRGB изображенията се запазват като sRGB, за да се избегнат цветови промени.", - "image_preview_description": "Среден размер на изображението с премахнати метаданни, използвано при преглед на един елемент и за машинно обучение", - "image_preview_quality_description": "Качество на предварителния преглед от 1 до 100. По-високата стойност е по-добра, но води до по-големи файлове и може да намали бързодействието на приложението. Задаването на ниска стойност може да повлияе на качеството на машинното обучение.", - "image_preview_title": "Настройки на прегледа", - "image_progressive": "Прогресивен JPEG", - "image_progressive_description": "Изображенията, кодирани в прогресивен JPEG формат, се зареждат по-бързо, с постепенно подобряващо се качество. Това няма влияние на кодираните като WebP изображения.", - "image_quality": "Качество", - "image_resolution": "Резолюция", - "image_resolution_description": "По-високите резолюции могат да запазят повече детайли, но изискват повече време за кодиране, имат по-големи размери на файловете и могат да намалят бързодействието на приложението.", - "image_settings": "Настройки за изображенията", - "image_settings_description": "Управляване качеството и резолюцията на създадените изображения", - "image_thumbnail_description": "Малка миниатюра с премахнати метаданни, използвана при преглед на групи снимки, като основния екран", - "image_thumbnail_quality_description": "Качество на миниатюрата от 1 до 100. По-високата стойност е по-добра, но води до по-големи файлове и може да намали бързодействието на приложението.", - "image_thumbnail_title": "Настройки на миниатюрите", - "import_config_from_json_description": "Импорт на системна конфигурация чрез качване на JSON файл", - "job_concurrency": "Паралелност на {job}", - "job_created": "Задачата е създадена", - "job_not_concurrency_safe": "Тази задача не е безопасна за паралелно изпълнение.", - "job_settings": "Настройки за задачите", - "job_settings_description": "Управление на паралелността на задачите", - "jobs_delayed": "{jobCount, plural, other {# забавени}}", - "jobs_failed": "{jobCount, plural, other {# неуспешни}}", - "jobs_over_time": "Работа във времето", - "library_created": "Създадена библиотека: {library}", - "library_deleted": "Библиотека е изтрита", - "library_details": "Подробности за библиотеката", - "library_folder_description": "Изберете папка за импортиране. Папката и подпапките в нея ще бъдат сканирани за изображения и видеа.", - "library_remove_exclusion_pattern_prompt": "Сигурни ли сте, че искате да премахнете този шаблон за игнориране?", - "library_remove_folder_prompt": "Сигурни ли сте, че искате да премахнете тази папка за импортиране?", - "library_scanning": "Периодично сканиране", - "library_scanning_description": "Конфигурирай периодично сканиране на библиотеката", - "library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката", - "library_settings": "Външна библиотека", - "library_settings_description": "Управление на настройките за външна библиотека", - "library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени елементи", - "library_updated": "Обновена библиотека", - "library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека", - "library_watching_settings": "Наблюдаване на библиотеката [ЕКСПЕРИМЕНТАЛНО]", - "library_watching_settings_description": "Автоматично наблюдавай за променени файлове", - "logging_enable_description": "Включване на запис (логове)", - "logging_level_description": "Когато е включено, какво ниво на записване да се използва.", - "logging_settings": "Записване", - "machine_learning_availability_checks": "Проверки за наличност", - "machine_learning_availability_checks_description": "Автоматично откриване и предпочитане на налични сървъри за машинно обучение", - "machine_learning_availability_checks_enabled": "Активиране на проверки за наличност", - "machine_learning_availability_checks_interval": "Интервал на проверяване", - "machine_learning_availability_checks_interval_description": "Време в милисекунди между проверките за наличност", - "machine_learning_availability_checks_timeout": "Време за изчакване на отговор", - "machine_learning_availability_checks_timeout_description": "Време за изчакване на отговор в милисекунди при проверка на наличност", - "machine_learning_clip_model": "CLIP модел", - "machine_learning_clip_model_description": "Името на CLIP модела, посочен тук. Имайте предвид, че при промяна на модела трябва да стартирате отново задачата \"Интелигентно Търсене\" за всички изображения.", - "machine_learning_duplicate_detection": "Откриване на дубликати", - "machine_learning_duplicate_detection_enabled": "Включване на откриването на дубликати", - "machine_learning_duplicate_detection_enabled_description": "Напълно идентични елементи ще бъдат дедублирани дори когато е изключено.", - "machine_learning_duplicate_detection_setting_description": "Използване на CLIP вграждания за откриване на вероятни дублирания", - "machine_learning_enabled": "Включване на машинното обучение", - "machine_learning_enabled_description": "Ако е изключено, всички функции на машинно обучение ще бъдат деактивирани, независимо от посочените по-долу настройки.", - "machine_learning_facial_recognition": "Лицево разпознаване", - "machine_learning_facial_recognition_description": "Откриване, разпознаване и групиране на лица в изображенията", - "machine_learning_facial_recognition_model": "Модел за лицево разпознаване", - "machine_learning_facial_recognition_model_description": "Моделите са изброени в низходящ ред по размер. По-големите модели са по-бавни и използват повече памет, но дават по-добри резултати. Имайте предвид, че при промяна на модела, трябва да стартирате отново задачата за откриване на лица за всички изображения.", - "machine_learning_facial_recognition_setting": "Включване на лицево разпознаване", - "machine_learning_facial_recognition_setting_description": "Когато е изключено, изображенията няма да бъдат кодирани за лицево разпознаване и секцията \"Хора\", в страницата \"Разгледай\", няма да бъде попълнена.", - "machine_learning_max_detection_distance": "Максимално разстояние за откриване", - "machine_learning_max_detection_distance_description": "Максимална разстояние между две изображения, за да се считат за дубликати, в диапазона 0,001-0,1. По-високите стойности ще открият повече дубликати, но могат да доведат до фалшиви положителни резултати.", - "machine_learning_max_recognition_distance": "Максимално разстояние за разпознаване", - "machine_learning_max_recognition_distance_description": "Максимално разстояние между две лица, за да се считат за едно и също лице, в диапазона 0-2. Намаляването му може да предотврати определянето на две лица като едно и също лице, а увеличаването му може да предотврати определянето на едно и също лице като две различни лица. Имайте предвид, че е по-лесно да се слеят две лица, отколкото да се раздели едно лице на две, така че по възможност изберете по-ниска стойност.", - "machine_learning_min_detection_score": "Минимална оценка за откриване", - "machine_learning_min_detection_score_description": "Минимална оценка на доверие, за да бъде считано лице като открито - от 0 до 1. По-ниските стойности ще открият повече лица, но може да доведат до фалшиви положителни резултати.", - "machine_learning_min_recognized_faces": "Минимум разпознати лица", - "machine_learning_min_recognized_faces_description": "Минималният брой разпознати лица, необходими за създаването на лице. Увеличаването му прави разпознаването на лица по-прецизно за сметка на увеличаването на вероятността дадено лице да не бъде причислено към лице.", - "machine_learning_ocr": "Оптично разпознаване на текст", - "machine_learning_ocr_description": "Използвайте машинно обучение за разпознаване на текст в изображенията", - "machine_learning_ocr_enabled": "Включи разпознаване на текст", - "machine_learning_ocr_enabled_description": "Ако е забранено, няма да се прави разпознаване на текст в изображенията.", - "machine_learning_ocr_max_resolution": "Максимална резолюция", - "machine_learning_ocr_max_resolution_description": "Изображения с резолюция над зададената ще бъдат преоразмерени при запазване на пропорцията. Голяма стойност позволява по-прецизно разпознаване, но обработката използва повече време и повече памет.", - "machine_learning_ocr_min_detection_score": "Минимална оценка за откриванe", - "machine_learning_ocr_min_detection_score_description": "Минималната оценка на доверие за откриване на текст може да бъде между 0 и 1. По-ниска стойност ще открива повече текст, но може да доведе до грешни резултати.", - "machine_learning_ocr_min_recognition_score": "Минимална оценкa за откриване", - "machine_learning_ocr_min_score_recognition_description": "Минимална оценка на доверие, за да бъде считан текст като открит - от 0 до 1. По-ниските стойности ще открият повече текст, но може да доведат до фалшиви положителни резултати.", - "machine_learning_ocr_model": "Модел за разпознаване на текст", - "machine_learning_ocr_model_description": "Сървърните модели са по-точни от мобилните модели, но изискват повече време и използват повече памет.", - "machine_learning_settings": "Настройки на машинното обучение", - "machine_learning_settings_description": "Управление на функциите и настройките за машинно обучение", - "machine_learning_smart_search": "Интелигентно Търсене", - "machine_learning_smart_search_description": "Семантично търсене на изображения с помощта на CLIP вграждания", - "machine_learning_smart_search_enabled": "Включване на Интелигентно Търсене", - "machine_learning_smart_search_enabled_description": "Ако е деактивирано, изображенията няма да бъдат кодирани за Интелигентно Търсене.", - "machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един отговори успешно, в реда от първия до последния. Сървъри, които не отговорят, ще бъдат временно игнорирани, докато не се върнат онлайн.", - "maintenance_delete_backup": "Изтриване на архив", - "maintenance_delete_backup_description": "Този файл ще бъде безвъзвратно изтрит.", - "maintenance_delete_error": "Неуспешно изтриване на архив.", - "maintenance_restore_backup": "Възстановяване на архив", - "maintenance_restore_backup_description": "Immich ще изтрие всички текущи данни и после ще възстанови данните от избрания архив. Първо ще направи нов архив.", - "maintenance_restore_backup_different_version": "Този архив е създаден с различна версия на Immich!", - "maintenance_restore_backup_unknown_version": "Неуспешно определяне на версията на архива.", - "maintenance_restore_database_backup": "Възстановяване на данните от архив", - "maintenance_restore_database_backup_description": "Връщане към предишно състояние на базата данни чрез използване на файл-архив", - "maintenance_settings": "Обслужване", - "maintenance_settings_description": "Преквлючване на сървъра Immich в режим на обслужване.", - "maintenance_start": "Премини към режим на обслужване", - "maintenance_start_error": "Неуспешно преминаване в режим на обслужване.", - "maintenance_upload_backup": "Зареди файл-архив на базата данни", - "maintenance_upload_backup_error": "Неуспешно зареждане на архив, това файл .sql/.sql.gz ли е?", - "manage_concurrency": "Управление на паралелност", - "manage_concurrency_description": "Отидете на страницата със задачи, за да управлявате едновременността им", - "manage_log_settings": "Управление на настройките на записване", - "map_dark_style": "Тъмен стил", - "map_enable_description": "Активиране на картата", - "map_gps_settings": "Настройки на картата и GPS", - "map_gps_settings_description": "Управление на настройките на картата и GPS (обратно геокодиране)", - "map_implications": "Функцията за карта разчита на външна услуга (tiles.immich.cloud)", - "map_light_style": "Светъл стил", - "map_manage_reverse_geocoding_settings": "Управление на настройките за обратно геокодиране", - "map_reverse_geocoding": "Обратно геокодиране", - "map_reverse_geocoding_enable_description": "Включване на обратно геокодиране", - "map_reverse_geocoding_settings": "Настройки на опбратно геокодиране", - "map_settings": "Карта", - "map_settings_description": "Управление на настройките на картата", - "map_style_description": "URL адрес към файл \"style.json\" за задаване на стил на картата", - "memory_cleanup_job": "Почистване на паметта", - "memory_generate_job": "Генериране на паметта", - "metadata_extraction_job": "Извличане на метаданни", - "metadata_extraction_job_description": "Извличане на метаданни от всеки елемент, като GPS локация, лица и резолюция на файловете", - "metadata_faces_import_setting": "Включи импорт на лице", - "metadata_faces_import_setting_description": "Импортирай лица от EXIF данни и помощни файлове", - "metadata_settings": "Опции за метаданни", - "metadata_settings_description": "Управление на настройките за метаданни", - "migration_job": "Миграция", - "migration_job_description": "Мигриране на миниатюрите за елементи и лица към най-новата структура на папките", - "nightly_tasks_cluster_faces_setting_description": "Изпълни разпознаване на лице за открити нови лица", - "nightly_tasks_cluster_new_faces_setting": "Разпознаване на нови лица", - "nightly_tasks_database_cleanup_setting": "Задачи по почистване на базата данни", - "nightly_tasks_database_cleanup_setting_description": "Премахни стари, ненужни записи от базата данни", - "nightly_tasks_generate_memories_setting": "Създаване на спомени", - "nightly_tasks_generate_memories_setting_description": "Създаване на нови спомени от съществуващи обекти", - "nightly_tasks_missing_thumbnails_setting": "Генериране на липсващи миниатюри", - "nightly_tasks_missing_thumbnails_setting_description": "Добавяне на обекти без миниатюра в опашката за създаване на миниатюра", - "nightly_tasks_settings": "Настройка на задачи за през нощта", - "nightly_tasks_settings_description": "Управление на задачите, изпълнявани през нощта", - "nightly_tasks_start_time_setting": "Време за начало", - "nightly_tasks_start_time_setting_description": "Време, когато сървъра ще започне изпълнение на нощни задачи", - "nightly_tasks_sync_quota_usage_setting": "Квота за синхронизация", - "nightly_tasks_sync_quota_usage_setting_description": "Обновяване на квотата според текущото потребление", - "no_paths_added": "Няма добавени пътища", - "no_pattern_added": "Няма добавен модел", - "note_apply_storage_label_previous_assets": "Забележка: За да приложите етикета за съхранение към предварително качени файлове, стартирайте", - "note_cannot_be_changed_later": "ВНИМАНИЕ: Това не може да бъде променено по-късно!", - "notification_email_from_address": "От адрес", - "notification_email_from_address_description": "Електронна поща на изпращача, например: \"Immich Photo Server \". Използвайте адрес, от който може да изпращате имейли.", - "notification_email_host_description": "Хост на сървъра за електронна поща (например: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Игнорирайте сертификационни грешки", - "notification_email_ignore_certificate_errors_description": "Игнорирай грешки свързани с валидация на TLS сертификат (не се препоръчва)", - "notification_email_password_description": "Парола използвана за удостоверяване пред сървъра за електронна поща", - "notification_email_port_description": "Порт на сървъра за електронна поща (например 25, 465 или 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Използвай SMTPS (SMTP по TLS)", - "notification_email_sent_test_email_button": "Изпрати тестов имейл и запази", - "notification_email_setting_description": "Настройки за изпращане на имейл известия", - "notification_email_test_email": "Изпрати тестов имейл", - "notification_email_test_email_failed": "Неуспешно изпращане на тестов имейл, проверете настройките", - "notification_email_test_email_sent": "Тестов имейл беше изпратен на {email}. Проверете входящата си пощa.", - "notification_email_username_description": "Потребителско име за удостоверяване пред имейл сървъра", - "notification_enable_email_notifications": "Включване на имейл известията", - "notification_settings": "Настройки на известията", - "notification_settings_description": "Управление на настройките за известия, вкл. имейл", - "oauth_auto_launch": "Автоматично стартиране", - "oauth_auto_launch_description": "Автоматично стартиране на вход чрез OAuth, когато се отвори страницата за вход", - "oauth_auto_register": "Автоматична регистрация", - "oauth_auto_register_description": "Автоматично регистриране на нови потребители след влизане с OAuth", - "oauth_button_text": "Текст на бутона", - "oauth_client_secret_description": "Задължително за поверителен клиент или когато PKCE (Proof Key for Code Exchange) не се поддържа за публичен клиент.", - "oauth_enable_description": "Влизане с OAuth", - "oauth_mobile_redirect_uri": "URI за мобилно пренасочване", - "oauth_mobile_redirect_uri_override": "URI пренасочване за мобилни устройства", - "oauth_mobile_redirect_uri_override_description": "Разреши когато доставчика за OAuth удостоверяване не позволява за мобилни URI идентификатори, като ''{callback}''", - "oauth_role_claim": "Потвърждение на роля", - "oauth_role_claim_description": "Автоматично предоставяне на административни права при наличие на това потвържение. Потвърждението може да има стойност 'user' или 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Управление на настройките за вход с OAuth", - "oauth_settings_more_details": "За повече информация за функционалността, се потърсете в docs.", - "oauth_storage_label_claim": "Заявка за етикет за съхранение", - "oauth_storage_label_claim_description": "Автоматично задайте етикета за съхранение на потребителя със стойността от тази заявка.", - "oauth_storage_quota_claim": "Заявка за квота за съхранение", - "oauth_storage_quota_claim_description": "Автоматично задайте квотата за съхранение на потребителя със стойността от тази заявка.", - "oauth_storage_quota_default": "Стандартна квота за съхранение (GiB)", - "oauth_storage_quota_default_description": "Квота в GiB, която да се използва, когато не е посочено друго.", - "oauth_timeout": "Време на изчакване при заявка", - "oauth_timeout_description": "Време за изчакване на отговор на заявка, в милисекунди", - "ocr_job_description": "Използване на машинно обучение за разпознаване на текст в изображенията", - "password_enable_description": "Влизане с имейл и парола", - "password_settings": "Вписване с парола", - "password_settings_description": "Управление на настройките за влизане с парола", - "paths_validated_successfully": "Всички пътища са валидирани успешно", - "person_cleanup_job": "Почистване на лица", - "queue_details": "Детайли по Опашката", - "queues": "Опашки за задачи", - "queues_page_description": "Страница с опашки за администраторски задачи", - "quota_size_gib": "Размер на квотата (GiB)", - "refreshing_all_libraries": "Опресняване на всички библиотеки", - "registration": "Администраторска регистрация", - "registration_description": "Тъй като сте първият потребител в системата, ще бъдете назначен като администратор и ще отговаряте за административните задачи, а допълнителните потребители ще бъдат създадени от вас.", - "remove_failed_jobs": "Премахване на неуспешни задачи", - "require_password_change_on_login": "Изискване за промяна паролата при първо влизане", - "reset_settings_to_default": "Възстановяване на настройките по подразбиране", - "reset_settings_to_recent_saved": "Възстановяване на настройките до последните запазени настройки", - "scanning_library": "Сканиране на библиотеката", - "search_jobs": "Търсене на задачи…", - "send_welcome_email": "Изпращане на имейл за добре дошли", - "server_external_domain_settings": "Външен домейн", - "server_external_domain_settings_description": "Домейн за публични споделени връзки, включително http(s)://", - "server_public_users": "Публични потребители", - "server_public_users_description": "Всички потребители (име и имейл) са изброени при добавяне на потребител в споделени албуми. Когато е деактивирано, списъкът с потребители ще бъде достъпен само за администраторите.", - "server_settings": "Настройки на сървъра", - "server_settings_description": "Управление на настройките на сървъра", - "server_stats_page_description": "Администраторска страница със статистика за сървъра", - "server_welcome_message": "Поздравително съобщение", - "server_welcome_message_description": "Съобщение, показващо се на страницата за вход.", - "settings_page_description": "Страница с настройки за администратора", - "sidecar_job": "Метаданни от свързани (sidecar) файлове", - "sidecar_job_description": "Откриване или синхронизиране на странични (sidecar) метаданни от файловата система", - "slideshow_duration_description": "Брой секунди за показване на всяко изображение", - "smart_search_job_description": "Извършване на машинно обучение върху елементи за подпомагане на Интелигентното Търсене", - "storage_template_date_time_description": "Времевата марка на създаване на файла се използва за информация за дата и час", - "storage_template_date_time_sample": "Време на проба {date}", - "storage_template_enable_description": "Активиране на механизма за шаблони за съхранение", - "storage_template_hash_verification_enabled": "Разрешена проверка с хеш", - "storage_template_hash_verification_enabled_description": "Активиране на проверката на хеш, не деактивирайте тази опция, освен ако не сте сигурни в последствията", - "storage_template_migration": "Миграция на шаблона за съхранение", - "storage_template_migration_description": "Прилагане на текущия {template} към предишно качените файлове", - "storage_template_migration_info": "Шаблона ще преобразува всички разширения на имената на файловете в долен регистър. Промените в шаблоните ще се прилагат само за нови елементи. За да приложите принудително шаблона към вече качени елементи, изпълнете {job}.", - "storage_template_migration_job": "Задача за миграция на шаблона за съхранение", - "storage_template_more_details": "За повече подробности относно тази функция се обърнете към шаблона Storage Template и неговите последствия ", - "storage_template_onboarding_description_v2": "Когато е разрешена, тази функция ще организира автоматично файловете, според шаблон, дефиниран от потребителя. За допълнителна информация, моля вижте документацията.", - "storage_template_path_length": "Ограничение на дължината на пътя: {length, number}/{limit, number}", - "storage_template_settings": "Шаблон за съхранение", - "storage_template_settings_description": "Управление на структурата на папките и името на файла за качване", - "storage_template_user_label": "{label} е етикетът за съхранение на потребителя", - "system_settings": "Системни настройки", - "tag_cleanup_job": "Почистване на тагове", - "template_email_available_tags": "Можете да използвате следните променливи в шаблона си: {tags}", - "template_email_if_empty": "Ако шаблонът е празен, ще се използва имейлът по подразбиране.", - "template_email_invite_album": "Шаблон за покана за албум", - "template_email_preview": "Преглед", - "template_email_settings": "Шаблони за имейли", - "template_email_update_album": "Шаблон за обновяване на албум", - "template_email_welcome": "Шаблон за приветстващ имейл", - "template_settings": "Шаблони за известия", - "template_settings_description": "Управление на шаблони за известия", - "theme_custom_css_settings": "Персонализиран CSS", - "theme_custom_css_settings_description": "Каскадните стилови таблици позволяват персонализиране на дизайна на Immich.", - "theme_settings": "Настройки на темата", - "theme_settings_description": "Управление на персонализирането на уеб интерфейса на Immich", - "thumbnail_generation_job": "Генериране на миниатюри", - "thumbnail_generation_job_description": "Генерирайте големи, малки и замъглени миниатюри за всеки актив, както и миниатюри за всеки човек", - "transcoding_acceleration_api": "API за ускоряване", - "transcoding_acceleration_api_description": "API интерфейсът, който ще взаимодейства с вашето устройство, за да ускори транскодирането. Тази настройка е „възможно най-доброто“: тя ще се върне към софтуерно транскодиране при повреда. VP9 може и да не работи в зависимост от вашия хардуер.", - "transcoding_acceleration_nvenc": "NVENC (необходим NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (необходим 7th поколение Intel CPU или по-ново)", - "transcoding_acceleration_rkmpp": "RKMPP (само на Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Допустими аудио кодеци", - "transcoding_accepted_audio_codecs_description": "Изберете кои аудио кодеци не са нужни за разкодиране. Използва се само за определени правила за разкодиране.", - "transcoding_accepted_containers": "Приети контейнери", - "transcoding_accepted_containers_description": "Изберете кои формати на контейнери не е нужно да бъдат преобразувани в MP4 формат. Използва се само за определени правила за разкодиране.", - "transcoding_accepted_video_codecs": "Приети видео кодеци", - "transcoding_accepted_video_codecs_description": "Изберете кои видео кодеци не трябват за разкодиране. Използва се само за определени правила за разкодиране.", - "transcoding_advanced_options_description": "Опции, които повечето потребители не трябва да променят", - "transcoding_audio_codec": "Аудио кодек", - "transcoding_audio_codec_description": "Opus е опцията с най-високо качество, но има по-ниска съвместимост със стари устройства или софтуер.", - "transcoding_bitrate_description": "Видеоклипове с по-висок от максималния битрейт или не в приет формат", - "transcoding_codecs_learn_more": "За да научите повече за използваната терминология, вижте документацията на FFmpeg за кодек H.264, кодек HEVC и VP9 кодек.", - "transcoding_constant_quality_mode": "Режим на постоянно качество", - "transcoding_constant_quality_mode_description": "ICQ е по-добър от CQP, но някои устройства за хардуерно ускоряване не поддържат този режим. С задаването на тази опция ще предпочете посочения режим при използване на базирано на качество кодиране. Игнорирано от NVENC, тъй като не поддържа ICQ.", - "transcoding_constant_rate_factor": "Коефициент на постоянна скорост (-crf)", - "transcoding_constant_rate_factor_description": "Ниво на качество на видеото. Типичните стойности са 23 за H.264, 28 за HEVC, 31 за VP9 и 35 за AV1. По-ниското е по-добро, но създава по-големи файлове.", - "transcoding_disabled_description": "Не разкодирай видеоклиповете, може да наруши възпроизвеждането на някои клиенти", - "transcoding_encoding_options": "Опции за кодиране", - "transcoding_encoding_options_description": "Задайте кодеци, резолюция, качество и други опции за кодираните видеоклипове", - "transcoding_hardware_acceleration": "Хардуерно ускорение", - "transcoding_hardware_acceleration_description": "Експериментално: много по-бързо транскодиране, но може да понижи качеството при същия битрейт", - "transcoding_hardware_decoding": "Хардуерно декодиране", - "transcoding_hardware_decoding_setting_description": "Активира ускорение от край до край, вместо само да ускорява кодирането. Може да не работи с всички видеоклипове.", - "transcoding_max_b_frames": "Максимални B-фрейма", - "transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.", - "transcoding_max_bitrate": "Максимален битрейт", - "transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0. Когато не е зададена мерна единица, подразбира се k (kbit/s); така 5000, 5000k и 5M (Mbit/s) са еквивалентни.", - "transcoding_max_keyframe_interval": "Максимален интервал между ключовите кадри", - "transcoding_max_keyframe_interval_description": "Задава максималното разстояние между ключовите кадри. По-ниските стойности влошават ефективността на компресията, но подобряват времето за търсене и могат да подобрят качеството в сцени с бързо движение. 0 задава тази стойност автоматично.", - "transcoding_optimal_description": "Видеоклипове с по-висока от целевата разделителна способност или не в приетия формат", - "transcoding_policy": "Политика за транскодиране", - "transcoding_policy_description": "Задайте кога видеоклипът ще бъде транскодиран", - "transcoding_preferred_hardware_device": "Предпочитано хардуерно устройство", - "transcoding_preferred_hardware_device_description": "Прилага се само за VAAPI и QSV. Задава dri възела, използван за хардуерно транскодиране.", - "transcoding_preset_preset": "Предварително зададени(-preset)", - "transcoding_preset_preset_description": "Скорост на компресия. По-бавните предварително зададени настройки създават по-малки файлове и повишават качеството при насочване към определен битрейт. VP9 игнорира скорости над „по-бързо“.", - "transcoding_reference_frames": "Референтни кадри", - "transcoding_reference_frames_description": "Броят кадри за препратка при компресиране на даден кадър. По-високите стойности подобряват ефективността на компресията, но забавят кодирането. 0 задава тази стойност автоматично.", - "transcoding_required_description": "Само видеа, които не са в приет формат", - "transcoding_settings": "Настройки за транскодиране на видеоклипове", - "transcoding_settings_description": "Управлявайте кои видеоклипове да бъдат транскодирани и как да бъдат обработени", - "transcoding_target_resolution": "Целева резолюция", - "transcoding_target_resolution_description": "По-високите разделителни способности могат да представят повече детайли, но отнемат повече време за разкодиране, имат по-големи размери на файловете и могат да намалят отзивчивостта на приложението.", - "transcoding_temporal_aq": "Темпорален AQ", - "transcoding_temporal_aq_description": "Само за NVENC. Повишава качеството на сцени с висока детайлност и малко движение. Може да не е съвместимо с по-стари устройства.", - "transcoding_threads": "Нишки", - "transcoding_threads_description": "По-високите стойности водят до по-бързо разкодиране, но оставят по-малко място за сървъра да обработва други задачи, докато е активен. Тази стойност не трябва да надвишава броя на процесорните ядра. Увеличава максимално използването, ако е зададено на 0.", - "transcoding_tone_mapping": "Тонално картографиране", - "transcoding_tone_mapping_description": "Опитва се да запази външния вид на HDR видеоклипове, когато се преобразува в SDR. Всеки алгоритъм прави различни компромиси за цвят, детайлност и яркост. Hable запазва детайлите, Mobius запазва цвета, а Reinhard запазва яркостта.", - "transcoding_transcode_policy": "Правила за транскодиране", - "transcoding_transcode_policy_description": "Правила за това кога видеоклипът трябва да бъде транскодиран. HDR видеоклиповете винаги ще бъдат транскодирани (освен ако транскодирането е деактивирано).", - "transcoding_two_pass_encoding": "Кодиране с двойно минаване", - "transcoding_two_pass_encoding_setting_description": "Транскодирането с две минавания създава по-добре кодиране видеа. Когато максималния битрейт е включен (задължително е да се работи с H.264 и HEVC), тази опция използва диапазон на битрейта базиран на максималния битрейт и игнорира CRF. За VP9, CRF може да се използва ако максималният битрейт е изключен.", - "transcoding_video_codec": "Видеокодек", - "transcoding_video_codec_description": "VP9 има висока ефективност и уеб съвместимост, но отнема повече време за транскодиране. HEVC работи по подобен начин, но има по-ниска уеб съвместимост. H.264 е широко съвместим и бърз за разкодиране, но създава много по-големи файлове. AV1 е най-ефективният кодек, но му липсва поддръжка на по-стари устройства.", - "trash_enabled_description": "Активирайте функциите за кошче", - "trash_number_of_days": "Брой дни", - "trash_number_of_days_description": "Брой дни, в които файловете да се съхраняват на боклука, преди да бъдат окончателно премахнати", - "trash_settings": "Настройки на кошчето", - "trash_settings_description": "Управление на настройките на кошчето", - "unlink_all_oauth_accounts": "Прекрати вписването на всички OAuth профили", - "unlink_all_oauth_accounts_description": "Не забравяйте да прекратите вписването на всички OAuth профили преди да мигрирате към нов доставчик.", - "unlink_all_oauth_accounts_prompt": "Сигурни ли сте, че искате да отпишете всички OAuth профили? Това ще нулира OAuth ID за всеки потребител и не може да бъде отменено.", - "user_cleanup_job": "Почистване на потребители", - "user_delete_delay": "{user} aкаунтът и файловете на потребителя ще бъдат планирани за постоянно изтриване след {delay, plural, one {# ден} other {# дни}}.", - "user_delete_delay_settings": "Забавяне на изтриване", - "user_delete_delay_settings_description": "Брой дни след окончателно изтриване акаунта на потребителя. Задачата за изтриване на потребител се изпълнява в полунощ, за да се провери за потребители, които са готови за изтриване. Промените на тази настройка ще влязат в сила при следващото изпълнение.", - "user_delete_immediately": "{user} акаунтът и файловете на потребителя ще бъдат включени в опашката за окончателно изтриване незабавно.", - "user_delete_immediately_checkbox": "Заявка на потребител и активи в опашка за незабавно изтриване", - "user_details": "Данни за потребителя", - "user_management": "Управление на потребителите", - "user_password_has_been_reset": "Паролата на потребителя е променена:", - "user_password_reset_description": "Моля, предоставете временната парола на потребителя и го информирайте, че ще трябва да я смени при следващото си влизане в системата.", - "user_restore_description": "{user} aкаунтът ще бъде възстановен.", - "user_restore_scheduled_removal": "Възстановяване на потребител – с насрочено премахване на {date, date, long}", - "user_settings": "Настройки на потребителя", - "user_settings_description": "Управление на потребителските настройки", - "user_successfully_removed": "Потребител {email} е успешно премахнат.", - "users_page_description": "Страница за администриране на потребители", - "version_check_enabled_description": "Активирай проверка на версията", - "version_check_implications": "Функцията за проверка на версията разчита на периодична комуникация с github.com", - "version_check_settings": "Проверка на версията", - "version_check_settings_description": "Активирайте/деактивирайте известието за нова версия", - "video_conversion_job": "Транскодиране на видеоклиповете", - "video_conversion_job_description": "Транскодирай видеоклипове за по-широка съвместимост с браузъри и устройства" - }, - "admin_email": "Администраторски имейл адрес", - "admin_password": "Администраторска парола", - "administration": "Администрация", - "advanced": "Разширено", - "advanced_settings_clear_image_cache": "Изчисти кеша за изображения", - "advanced_settings_clear_image_cache_error": "Неуспешно изчистване на кеша за изображения", - "advanced_settings_clear_image_cache_success": "Успешно изчистени {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "При синхронизация, използвайте тази опция като филтър, основан на промяна на даден критерии. Опитайте само в случай, че приложението има проблем с откриване на всички албуми.", - "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНТАЛНО] Използвай филтъра на алтернативното устройство за синхронизация на албуми", - "advanced_settings_log_level_title": "Ниво на запис в дневника: {level}", - "advanced_settings_prefer_remote_subtitle": "Някои устройства са твърде бавни за да генерират миниатюри. Активирай тази опция за да се зареждат винаги от сървъра.", - "advanced_settings_prefer_remote_title": "Предпочитай изображенията на сървъра", - "advanced_settings_proxy_headers_subtitle": "Дефиниране на прокси хедъри, които Immich трябва да изпраща с всяка мрежова заявка", - "advanced_settings_proxy_headers_title": "Прокси хедъри [ЕКСПЕРИМЕНТАЛНО]", - "advanced_settings_readonly_mode_subtitle": "Активира режима \"само за четене\", при който снимките могат да бъдат разглеждани, но неща като избор на няколко изображения, споделяне, изтриване са забранени. Активиране/деактивиране на режима само за четене става от картинката-аватар на потребителя от основния екран", - "advanced_settings_readonly_mode_title": "Режим само за четене", - "advanced_settings_self_signed_ssl_subtitle": "Пропуска проверката на SSL-сертификата на сървъра. Изисква се при самоподписани сертификати.", - "advanced_settings_self_signed_ssl_title": "Разреши самоподписани SSL сертификати [ЕКСПЕРИМЕНТАЛНО]", - "advanced_settings_sync_remote_deletions_subtitle": "Автоматично изтрии или възстанови обект на това устройство, когато действието е извършено през уеб-интерфейса", - "advanced_settings_sync_remote_deletions_title": "Синхронизация на дистанционни изтривания [ЕКСПЕРИМЕНТАЛНО]", - "advanced_settings_tile_subtitle": "Разширени потребителски настройки", - "advanced_settings_troubleshooting_subtitle": "Разреши допълнителни възможности за отстраняване на проблеми", - "advanced_settings_troubleshooting_title": "Отстраняванe на проблеми", - "age_months": "Възраст {months, plural, one {# месец} other {# месеци}}", - "age_year_months": "Възраст 1 година, {months, plural, one {# месец} other {# месеци}}", - "age_years": "{years, plural, other {Година #}}", - "album": "Албум", - "album_added": "Албумът е добавен", - "album_added_notification_setting_description": "Получавайте известие по имейл, когато бъдете добавени към споделен албум", - "album_cover_updated": "Обложката на албума е обновена", - "album_delete_confirmation": "Сигурни ли сте, че искате да изтриете албума {album}?", - "album_delete_confirmation_description": "Ако този албум е споделен, други потребители вече няма да имат достъп до него.", - "album_deleted": "Албума е изтрит", - "album_info_card_backup_album_excluded": "ИЗКЛЮЧЕН", - "album_info_card_backup_album_included": "ВКЛЮЧЕН", - "album_info_updated": "Информацията за албума е обновена", - "album_leave": "Да напусна ли албума?", - "album_leave_confirmation": "Сигурни ли сте, че искате да напуснете {album}?", - "album_name": "Име на албума", - "album_options": "Настройки на албума", - "album_remove_user": "Премахване на потребител?", - "album_remove_user_confirmation": "Сигурни ли сте, че искате да премахнете {user}?", - "album_search_not_found": "Няма намерени албуми, отговарящи на търсенето ви", - "album_selected": "Албума е избран", - "album_share_no_users": "Изглежда, че сте споделили този албум с всички потребители или нямате друг потребител, с когото да го споделите.", - "album_summary": "Обобщение на албума", - "album_updated": "Албумът е обновен", - "album_updated_setting_description": "Получавайте известие по имейл, когато споделен албум има нови файлове", - "album_upload_assets": "Заредете обекти от компютъра в сървъра и ги добавете в албум", - "album_user_left": "Напусна {album}", - "album_user_removed": "Премахнат {user}", - "album_viewer_appbar_delete_confirm": "Сигурни ли сте, че искате да изтриете този албум от своя профил?", - "album_viewer_appbar_share_err_delete": "Неуспешно изтриване на албум", - "album_viewer_appbar_share_err_leave": "Неуспешно напускане на албум", - "album_viewer_appbar_share_err_remove": "Проблем при пермахване на обекти от албума", - "album_viewer_appbar_share_err_title": "Неуспешна промяна на името на албума", - "album_viewer_appbar_share_leave": "Напускане на албума", - "album_viewer_appbar_share_to": "Сподели с", - "album_viewer_page_share_add_users": "Добави потребители", - "album_with_link_access": "Нека всеки с линк вижда снимки и хора в този албум.", - "albums": "Албуми", - "albums_count": "{count, plural, one {{count, number} Албум} other {{count, number} Албуми}}", - "albums_default_sort_order": "Ред по подразбиране за сортиране на албуми", - "albums_default_sort_order_description": "Първоначален ред на сортиране при създаване на нов албум.", - "albums_feature_description": "Колекции от обекти, които могат да бъдат споделяни с други поребители.", - "albums_on_device_count": "Албуми на устройството ({count})", - "albums_selected": "{count, plural, one {Избран е # албум} other {Избрани са # албума}}", - "all": "Всички", - "all_albums": "Всички албуми", - "all_people": "Всички хора", - "all_photos": "Всички снимки", - "all_videos": "Всички видеоклипове", - "allow_dark_mode": "Разреши тъмен режим", - "allow_edits": "Позволяване на редакции", - "allow_public_user_to_download": "Позволете на публичен потребител да може да изтегля", - "allow_public_user_to_upload": "Позволете на публичния потребител да може да качва", - "allowed": "Разрешено", - "alt_text_qr_code": "Изображение на QR код", - "always_keep": "Винаги пази", - "always_keep_photos_hint": "При освобождаване на място ще бъдат запазени всички снимки на това устройство.", - "always_keep_videos_hint": "При освобождаване на място ще бъдат запазени всички видеа на това устройство.", - "anti_clockwise": "Обратно на часовниковата стрелка", - "api_key": "API ключ", - "api_key_description": "Тази стойност ще бъде показана само веднъж. Моля, не забравяйте да го копирате, преди да затворите прозореца.", - "api_key_empty": "Името на вашия API ключ не трябва да е празно", - "api_keys": "API ключове", - "app_architecture_variant": "Вариант (Ахитектура)", - "app_bar_signout_dialog_content": "Наистина ли искате да излезете?", - "app_bar_signout_dialog_ok": "Да", - "app_bar_signout_dialog_title": "Излез от профила", - "app_download_links": "Линкове за сваляне на приложението", - "app_settings": "Настройки ма приложението", - "app_stores": "Магазини за приложения", - "app_update_available": "Налична е нова версия", - "appears_in": "Излиза в", - "apply_count": "Приложи ({count, number})", - "archive": "Архив", - "archive_action_prompt": "{count} са добавени в Архива", - "archive_or_unarchive_photo": "Архивиране или деархивиране на снимка", - "archive_page_no_archived_assets": "Не са намерени обекти в архива", - "archive_page_title": "Архив ({count})", - "archive_size": "Размер на архива", - "archive_size_description": "Конфигурирайте размера на архива за изтегляния (в GiB)", - "archived": "Архивирани", - "archived_count": "{count, plural, other {Архивирани #}}", - "are_these_the_same_person": "Това едно и също лице ли е?", - "are_you_sure_to_do_this": "Сигурни ли сте, че искате да направите това?", - "array_field_not_fully_supported": "Полетата на масива изискват ръчно редактиране на JSON", - "asset_action_delete_err_read_only": "Не могат да се изтриват обекти само-за-четене, пропускане", - "asset_action_share_err_offline": "Неуспешно получаване на офлайн обект/и, пропускаме", - "asset_added_to_album": "Добавено в албум", - "asset_adding_to_album": "Добавяне в албум…", - "asset_created": "Обектът е създаден", - "asset_description_updated": "Описанието на елемента е обновено", - "asset_filename_is_offline": "Активът {filename} е офлайн", - "asset_has_unassigned_faces": "Елементът има незададени лица", - "asset_hashing": "Хеширане…", - "asset_list_group_by_sub_title": "Групиране по", - "asset_list_layout_settings_dynamic_layout_title": "Динамично разполагане", - "asset_list_layout_settings_group_automatically": "Автоматично", - "asset_list_layout_settings_group_by": "Групирай обектите по", - "asset_list_layout_settings_group_by_month_day": "Месец и Ден", - "asset_list_layout_sub_title": "Разположение", - "asset_list_settings_subtitle": "Настройки на мрежата на разполагане на снимки", - "asset_list_settings_title": "Разполагане на снимки", - "asset_not_found_on_device_android": "Активът не е намерен на устройството", - "asset_not_found_on_device_ios": "Обектът не е намерен на устройството. Ако използвате iCloud, обектът може да е недостъпен поради повреден файл, съхранен в iCloud", - "asset_not_found_on_icloud": "Обектът не е намерен в iCloud. Обектът може да е недостъпен поради повреден файл, съхранен в iCloud", - "asset_offline": "Елементът е офлайн", - "asset_offline_description": "Този външен актив вече не се намира на диска. Моля, свържете се с администратора на Immich за помощ.", - "asset_restored_successfully": "Успешно възстановен обект", - "asset_skipped": "Пропуснато", - "asset_skipped_in_trash": "В кошчето", - "asset_trashed": "Обектът е изхвърлен", - "asset_troubleshoot": "Поправка на грешки с обекта", - "asset_uploaded": "Качено", - "asset_uploading": "Качване…", - "asset_viewer_settings_subtitle": "Управление на настройките за изглед", - "asset_viewer_settings_title": "Преглед на изображения", - "assets": "Елементи", - "assets_added_count": "Добавено {count, plural, one {# asset} other {# assets}}", - "assets_added_to_album_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} в албума", - "assets_added_to_albums_count": "{assetTotal, plural, one {# обект е добавен} other {# обекта са добавени}} в {albumTotal, plural, one {# албум} other {# албума}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Обекта не може да се добави} other {Обектите не може да се добавят}} в албума", - "assets_cannot_be_added_to_albums": "{count, plural, one {обект не може да бъде добавен} other {обекта не могат да бъдат добавени}} в никой от албумите", - "assets_count": "{count, plural, one {# актив} other {# актива}}", - "assets_deleted_permanently": "{count} обекта са изтрити завинаги", - "assets_deleted_permanently_from_server": "{count} обекта са изтити от Immich сървъра завинаги", - "assets_downloaded_failed": "{count, plural, one {Зареден # файл} many {Заредени # файла} other {заредени # файла}}, {error} - неуспешно", - "assets_downloaded_successfully": "Успешно {count, plural, one {е качен # файл} many {са качени # файла} other {са качени # файла}}", - "assets_moved_to_trash_count": "Преместен(и) са {count, plural, one {# актив} other {# актива}} в кошчето", - "assets_permanently_deleted_count": "Постоянно изтрит(и) са {count, plural, one {# актив} other {# актива}}", - "assets_removed_count": "Премахнат(и) са {count, plural, one {# актив} other {# актива}}", - "assets_removed_permanently_from_device": "{count} обекта са премахнати завинаги от устройството", - "assets_restore_confirmation": "Сигурни ли сте, че искате да възстановите всички активи в кошчето? Не можете да отмените това действие! Имайте предвид, че активи, които са офлайн, не могат да бъдат възстановени по този начин.", - "assets_restored_count": "Възстановен(и) са {count, plural, one {# актив} other {# актива}}", - "assets_restored_successfully": "{count} обекта са успешно възстановени", - "assets_trashed": "{count} обекта са преместени в коша", - "assets_trashed_count": "Възстановен(и) са {count, plural, one {# файл} other {# файла}}", - "assets_trashed_from_server": "{count} обекта са преместени в коша на Immich сървъра", - "assets_were_part_of_album_count": "{count, plural, one {Файлът е} other {Файловете са}} вече част от албума", - "assets_were_part_of_albums_count": "{count, plural, one {обект вече е} other {обекта вече са}} част от албумите", - "authorized_devices": "Удостоверени устройства", - "automatic_endpoint_switching_subtitle": "Когато е достъпна, използвай посочената Wi-Fi мрежа, иначе използвай алтернативни връзки", - "automatic_endpoint_switching_title": "Автоматично превключване на URL", - "autoplay_slideshow": "Автоматична смяна на слайдовете", - "back": "Назад", - "back_close_deselect": "Назад, затваряне или премахване на избора", - "background_backup_running_error": "Стартирано е фоново архивиране, не може да се пусне ръчно архивиране", - "background_location_permission": "Разрешение за достъп до местоположението във фонов режим", - "background_location_permission_content": "За да може да чете имената на Wi-Fi мрежите и да ги превключва при работа във фонов режим, Immich трябва *винаги* да има достъп до точното местоположение", - "background_options": "Опции за фоновите задачи", - "backup": "Архивиране", - "backup_album_selection_page_albums_device": "Албуми на устройството ({count})", - "backup_album_selection_page_albums_tap": "Натисни за да включиш, двойно за да изключиш", - "backup_album_selection_page_assets_scatter": "Обектите могат да бъдат разпръснати в няколко албума. По този начин албумите могат да бъдат включени или изключени по време на процеса на архивиране.", - "backup_album_selection_page_select_albums": "Избор на албуми", - "backup_album_selection_page_selection_info": "Информация за избраното", - "backup_album_selection_page_total_assets": "Уникални обекти общо", - "backup_albums_sync": "Синхронизиране на архивите", - "backup_all": "Всичко", - "backup_background_service_backup_failed_message": "Неуспешно архивиране. Нов опит…", - "backup_background_service_complete_notification": "Завърши архивирането на обектите", - "backup_background_service_connection_failed_message": "Неуспешно свързване към сървъра. Нов опит…", - "backup_background_service_current_upload_notification": "Зареждам {filename}", - "backup_background_service_default_notification": "Търсене на нови обекти…", - "backup_background_service_error_title": "Грешка при архивиране", - "backup_background_service_in_progress_notification": "Архивиране на обектите…", - "backup_background_service_upload_failure_notification": "Неуспешно зареждане на {filename}", - "backup_controller_page_albums": "Архивиране на албуми", - "backup_controller_page_background_app_refresh_disabled_content": "За да ползвате архивиране във фонов режим трябва да разрешите в Настройки > Общи > Обновяване във фонов режим.", - "backup_controller_page_background_app_refresh_disabled_title": "Фоново обновяване е изключено", - "backup_controller_page_background_app_refresh_enable_button_text": "Иди в настройки", - "backup_controller_page_background_battery_info_link": "Покажи ми как", - "backup_controller_page_background_battery_info_message": "За успешно архивиране във фонов режим, моля изключете оптимизациите на батерията, ограничаващи фоновата активност на Immich.\n\nТази настройка е според устройството, моля потърсете информация според производителя на устройството.", - "backup_controller_page_background_battery_info_ok": "Ок", - "backup_controller_page_background_battery_info_title": "Оптимизация на батерията", - "backup_controller_page_background_charging": "Само при зареждане", - "backup_controller_page_background_configure_error": "Неуспешна настройка на фоновата услуга", - "backup_controller_page_background_delay": "Изчакване за архивиране на нов обект: {duration}", - "backup_controller_page_background_description": "Включи фоновата услуга за автоматично архивиране на нови обекти без нужда да отваряш приложението", - "backup_controller_page_background_is_off": "Автоматичното фоново архивиране е изключено", - "backup_controller_page_background_is_on": "Автоматичното фоново архивиране е включено", - "backup_controller_page_background_turn_off": "Изключи фоновата услуга", - "backup_controller_page_background_turn_on": "Включи фоновата услуга", - "backup_controller_page_background_wifi": "Само през Wi-Fi", - "backup_controller_page_backup": "Архивиране", - "backup_controller_page_backup_selected": "Избрано: ", - "backup_controller_page_backup_sub": "Архивиране на снимки и видео", - "backup_controller_page_created": "Създаден на: {date}", - "backup_controller_page_desc_backup": "Включи архивиране в активен режим за автоматично копиране на нови обекти при отваряне на приложението.", - "backup_controller_page_excluded": "Изключени: ", - "backup_controller_page_failed": "Неуспешни ({count})", - "backup_controller_page_filename": "Файл: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Информация за архивирането", - "backup_controller_page_none_selected": "Няма избрано", - "backup_controller_page_remainder": "Остават", - "backup_controller_page_remainder_sub": "Оставащи за архивиране от избраните", - "backup_controller_page_server_storage": "Хранилище на сървър", - "backup_controller_page_start_backup": "Започни архивиране", - "backup_controller_page_status_off": "Автоматичното архивиране в активен режим е изключено", - "backup_controller_page_status_on": "Автоматичното архивиране в активен режим е включено", - "backup_controller_page_storage_format": "използвани {used} от {total}", - "backup_controller_page_to_backup": "Албуми за архивиране", - "backup_controller_page_total_sub": "Всички уникални снимки и видеа от избраните албуми", - "backup_controller_page_turn_off": "Изключи архивиране в активен режим", - "backup_controller_page_turn_on": "Включи архивиране в активен режим", - "backup_controller_page_uploading_file_info": "Инфо за архивирания файл", - "backup_err_only_album": "Не може да се премахне единствения албум", - "backup_error_sync_failed": "Синхронизацията е неуспешна. Резервното копие не може да се обработи.", - "backup_info_card_assets": "обекта", - "backup_manual_cancelled": "Отменено", - "backup_manual_in_progress": "Върви архивиране. Опитай след малко", - "backup_manual_success": "Успешно", - "backup_manual_title": "Състояние на архивирането", - "backup_options": "Опции за архивиране", - "backup_options_page_title": "Настройки за архивиране", - "backup_setting_subtitle": "Управлявай настройките за архивиране в активен и фонов режим", - "backup_settings_subtitle": "Управление на настройките за качване", - "backup_upload_details_page_more_details": "Повече подробности", - "backward": "Назад", - "biometric_auth_enabled": "Включена биометрично удостоверяване", - "biometric_locked_out": "Няма достъп до биометрично удостоверяване", - "biometric_no_options": "Няма биометрична автентикация", - "biometric_not_available": "На това устройство не е налично биометрично удостоверяване", - "birthdate_saved": "Датата на раждане е запазена успешно", - "birthdate_set_description": "Датата на раждане се използва за изчисляване на възрастта на този човек към момента на снимката.", - "blurred_background": "Замъглен заден фон", - "bugs_and_feature_requests": "Бъгове и заявки за функции", - "build": "Версия", - "build_image": "Docker версия", - "bulk_delete_duplicates_confirmation": "Сигурни ли сте, че искате да изтриете масово {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще запази най-големия файл от всяка група и ще изтрие трайно всички други дубликати. Не можете да отмените това действие!", - "bulk_keep_duplicates_confirmation": "Сигурни ли сте, че искате да запазите {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще потвърди всички групи дубликати, без да изтрива нищо.", - "bulk_trash_duplicates_confirmation": "Сигурни ли сте, че искате да преместите в кошчето масово {count, plural, one {# дублиран файл} other {# дублирани файла}}? Това ще запази най-големия файл от всяка група и ще премести в кошчето всички други дубликати.", - "buy": "Купете Immich", - "cache_settings_clear_cache_button": "Изчисти кеша", - "cache_settings_clear_cache_button_title": "Изчиства кеша на приложението. Това ще повлияе производителността на приложението докато кеша не бъде създаден отново.", - "cache_settings_duplicated_assets_clear_button": "ИЗЧИСТИ", - "cache_settings_duplicated_assets_subtitle": "Снимки и видеа, които са в Списъка за игнориране от приложението", - "cache_settings_duplicated_assets_title": "Дублирани обекти ({count})", - "cache_settings_statistics_album": "Библиотека с миниатюри", - "cache_settings_statistics_full": "Пълни изображения", - "cache_settings_statistics_shared": "Миниатюри на споделените албуми", - "cache_settings_statistics_thumbnail": "Миниатюри", - "cache_settings_statistics_title": "Използване на кеша", - "cache_settings_subtitle": "Управление на кеширането в мобилното приложение Immich", - "cache_settings_tile_subtitle": "Управление на локалното хранилище", - "cache_settings_tile_title": "Локално хранилище", - "cache_settings_title": "Настройки на кеширане", - "camera": "Камера", - "camera_brand": "Марка на камерата", - "camera_model": "Модел на камерата", - "cancel": "Откажи", - "cancel_search": "Отмени търсенето", - "canceled": "Отменено", - "canceling": "Анулиране", - "cannot_merge_people": "Не може да обединява хора", - "cannot_undo_this_action": "Не можете да отмените това действие!", - "cannot_update_the_description": "Описанието не може да бъде обновено", - "cast": "Поточно предаване", - "cast_description": "Настройка на наличните цели за предаване", - "change_date": "Промени датата", - "change_description": "Промени описанието", - "change_display_order": "Промени реда на показване", - "change_expiration_time": "Променете времето на изтичане", - "change_location": "Промени локацията", - "change_name": "Промени името", - "change_name_successfully": "Успешна промяна на името", - "change_password": "Промени паролата", - "change_password_description": "Това е или първият път, когато влизате в системата, или е направена заявка за промяна на паролата ви. Моля, въведете новата парола по-долу.", - "change_password_form_confirm_password": "Потвърди паролата", - "change_password_form_description": "Здравейте {name},\n\nТова или е първото ви вписване в системата или има подадена заявка за смяна на паролата. Моля, въведете нова парола в полето по-долу.", - "change_password_form_log_out": "Излизане от профила на всички други устройства", - "change_password_form_log_out_description": "Препоръчваме да се излезе от профила на всички други устройства", - "change_password_form_new_password": "Нова парола", - "change_password_form_password_mismatch": "Паролите не съвпадат", - "change_password_form_reenter_new_password": "Повтори новата парола", - "change_pin_code": "Смени PIN кода", - "change_trigger": "Промяна на тригера", - "change_trigger_prompt": "Наистина ли искате да промените тригера? Това ще премахне всички налични действия и филтри.", - "change_your_password": "Променете паролата си", - "changed_visibility_successfully": "Видимостта е променена успешно", - "charging": "При зареждане", - "charging_requirement_mobile_backup": "Фоново архивиране само при зареждане на устройството", - "check_corrupt_asset_backup": "Провери за повредени архивни копия", - "check_corrupt_asset_backup_button": "Провери", - "check_corrupt_asset_backup_description": "Изпълни тази проверка само при Wi-Fi и след архивиране на всички обекти. Процедурата може да продължи няколко минути.", - "check_logs": "Провери логовете", - "checksum": "Контролна сума", - "choose_matching_people_to_merge": "Изберете подходящи хора за сливане", - "city": "Град", - "cleanup_confirm_description": "Immich намери {count} обекта (създадени преди {date}), които са архивирани на сървъра. Да се премахнат ли локалните копия от това устройство?", - "cleanup_confirm_prompt_title": "Да се премахнат ли от това устройство?", - "cleanup_deleted_assets": "В кошчето са преместени {count} обекта", - "cleanup_deleting": "Преместване в кошчето...", - "cleanup_found_assets": "Намерени са {count} архивирани на сървъра обекта", - "cleanup_found_assets_with_size": "Намерени са {count} архива с размер ({size})", - "cleanup_icloud_shared_albums_excluded": "Споделените iCloud албуми са изключени от сканирането", - "cleanup_no_assets_found": "Не са намерени обекти, които да отговарят на зададените критерии. За освобождване на място може да се премават само архивирани на сървъра обекти", - "cleanup_preview_title": "Обекти за премахване ({count})", - "cleanup_step3_description": "Сканиране за архивирани на сървъра снимки и видеа, според избраната дата и зададените опции на филтъра.", - "cleanup_step4_summary": "{count} обекта (създадени преди {date}) за премахване от това устройство. Снимките ще останат достъпни чрез приложението Immich.", - "cleanup_trash_hint": "За да освободите напълно мястото за съхранение, отворете системното приложение „Галерия“ и изпразнете кошчето", - "clear": "Изчисти", - "clear_all": "Изчисти всичко", - "clear_all_recent_searches": "Изчистете всички скорошни търсения", - "clear_file_cache": "Изчистване на кеша на файловете", - "clear_message": "Изчисти съобщението", - "clear_value": "Изчисти стойността", - "client_cert_dialog_msg_confirm": "ОК", - "client_cert_enter_password": "Въведи парола", - "client_cert_import": "Импорт", - "client_cert_import_success_msg": "Клиентския сертификат е импортиран", - "client_cert_invalid_msg": "Невалиден сертификат или грешна парола", - "client_cert_remove_msg": "Клиентския сертификат е премахнат", - "client_cert_subtitle": "Поддържа се само формат PKCS12 (.p12, .pfx). Импорт/премахване на сертификат може само преди вписване в системата", - "client_cert_title": "Клиентски SSL сертификат [ЕКСПЕРИМЕНТАЛНО]", - "clockwise": "По часовниковата стрелка", - "close": "Затвори", - "collapse": "Свиване", - "collapse_all": "Свиване на всичко", - "color": "Цвят", - "color_theme": "Цветова тема", - "command": "Команда", - "comment_deleted": "Коментарът е изтрит", - "comment_options": "Опции за коментар", - "comments_and_likes": "Коментари и харесвания", - "comments_are_disabled": "Коментарите са деактивирани", - "common_create_new_album": "Създай нов албум", - "completed": "Завършено", - "confirm": "Потвърди", - "confirm_admin_password": "Потвърждаване на паролата на администратора", - "confirm_delete_face": "Сигурни ли сте, че искате да изтриете лицето на {name} от актива?", - "confirm_delete_shared_link": "Сигурни ли сте, че искате да изтриете тази споделена връзка?", - "confirm_keep_this_delete_others": "Всички останали файлове в стека ще бъдат изтрити, с изключение на този файл. Сигурни ли сте, че искате да продължите?", - "confirm_new_pin_code": "Потвърди новия PIN код", - "confirm_password": "Потвърдете паролата", - "confirm_tag_face": "Искате ли да отбележите това лице като {name}?", - "confirm_tag_face_unnamed": "Искате ли да отбележите това лице?", - "connected_device": "Свързано устройство", - "connected_to": "Свързан към", - "contain": "В рамките на", - "context": "Контекст", - "continue": "Продължи", - "control_bottom_app_bar_create_new_album": "Създай нов албум", - "control_bottom_app_bar_delete_from_immich": "Премахни от Immich съръра", - "control_bottom_app_bar_delete_from_local": "Премахни от устройството", - "control_bottom_app_bar_edit_location": "Редактирай местоположението", - "control_bottom_app_bar_edit_time": "Редактирай Дата и Час", - "control_bottom_app_bar_share_link": "Сподели връзка", - "control_bottom_app_bar_share_to": "Сподели с", - "control_bottom_app_bar_trash_from_immich": "Премести в коша", - "copied_image_to_clipboard": "Изображението е копирано в клипборда.", - "copied_to_clipboard": "Копирано в клипборда!", - "copy_error": "Грешка при копирането", - "copy_file_path": "Копирай пътя на файла", - "copy_image": "Копиране на изображението", - "copy_link": "Копиране на линк", - "copy_link_to_clipboard": "Копиране на връзката в клипборда", - "copy_password": "Копиране на парола", - "copy_to_clipboard": "Копиране в клипборда", - "country": "Държава", - "cover": "Покрий", - "covers": "Обложка", - "create": "Създай", - "create_album": "Създай албум", - "create_album_page_untitled": "Без заглавие", - "create_api_key": "Създайте API ключ", - "create_first_workflow": "Създайте първи работен процес", - "create_library": "Създай библиотека", - "create_link": "Създай линк", - "create_link_to_share": "Създаване на линк за споделяне", - "create_link_to_share_description": "Позволете на всеки, който има линк, да види избраната(ите) снимка(и)", - "create_new": "СЪЗДАЙ НОВ", - "create_new_person": "Създаване на ново лице", - "create_new_person_hint": "Присвойте избраните файлове на нов човек", - "create_new_user": "Създаване на нов потребител", - "create_shared_album_page_share_add_assets": "ДОБАВИ ОБЕКТИ", - "create_shared_album_page_share_select_photos": "Избери снимки", - "create_shared_link": "Създай линк за споделяне", - "create_tag": "Създай таг", - "create_tag_description": "Създайте нов таг. За вложени тагове, моля, въведете пълния път на тага, включително наклонените черти.", - "create_user": "Създай потребител", - "create_workflow": "Създайте работен процес", - "created": "Създадено", - "created_at": "Създаден", - "creating_linked_albums": "Създаване на свързани албуми...", - "crop": "Изрежи", - "crop_aspect_ratio_fixed": "Фиксиран", - "crop_aspect_ratio_free": "Свободен", - "crop_aspect_ratio_original": "Оригинален", - "curated_object_page_title": "Неща", - "current_device": "Текущо устройство", - "current_pin_code": "Сегашен PIN код", - "current_server_address": "Настоящ адрес на сървъра", - "custom_date": "Персонализирана дата", - "custom_locale": "Персонализиран локал", - "custom_locale_description": "Форматиране на дати и числа в зависимост от езика и региона", - "custom_url": "Персонализиран URL адрес", - "cutoff_date_description": "Запазване на снимки от последните…", - "cutoff_day": "{count, plural, one {ден} other {дни}}", - "cutoff_year": "{count, plural, one {година} other {години}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM yyyy", - "dark": "Тъмен", - "dark_theme": "Тъмна тема", - "date": "Дата", - "date_after": "Дата след", - "date_and_time": "Дата и час", - "date_before": "Дата преди", - "date_format": "E, d LLL y • h:mm a", - "date_of_birth_saved": "Дата на раждане е записана успешно", - "date_range": "Период от време", - "day": "Ден", - "days": "Дни", - "deduplicate_all": "Дедупликиране на всички", - "deduplication_criteria_1": "Размер на снимката в байтове", - "deduplication_criteria_2": "Брой EXIF данни", - "deduplication_info": "Информация за дедупликацията", - "deduplication_info_description": "За автоматично предварително избиране на ресурси и премахване на дубликати на едро, разглеждаме:", - "default_locale": "Локализация по подразбиране", - "default_locale_description": "Форматиране на дати и числа в зависимост от езиковата настройка на браузъра", - "delete": "Изтрий", - "delete_action_confirmation_message": "Сигурни ли сте, че искате да изтриете този обект? Следва преместване на обекта в коша за отпадъци на сървъра и ще получите предложение обекта да бъде изтрит локално", - "delete_action_prompt": "{count} са изтрити", - "delete_album": "Изтрий албум", - "delete_api_key_prompt": "Сигурни ли сте, че искате да изтриете този API ключ?", - "delete_dialog_alert": "Тези обекти ще бъдат изтрити завинаги и от Immich сървъра и от устройството", - "delete_dialog_alert_local": "Тези обекти ще бъдат премахнати от устройството, но ще останат на Immich сървъра", - "delete_dialog_alert_local_non_backed_up": "Някои от тези обекти не са архивирани на Immich сървъра и ще бъдат премахнати завинаги от устройството", - "delete_dialog_alert_remote": "Тези обекти ще бъдат изтрити завинаги от Immich сървъра", - "delete_dialog_ok_force": "Въпреки това изтрий", - "delete_dialog_title": "Изтрий завинаги", - "delete_duplicates_confirmation": "Сигурни ли сте, че искате да изтриете окончателно тези дубликати?", - "delete_face": "Изтрий лице", - "delete_key": "Изтрий ключ", - "delete_library": "Изтрий библиотека", - "delete_link": "Изтрий линк", - "delete_local_action_prompt": "{count} са изтрити локално", - "delete_local_dialog_ok_backed_up_only": "Изтрий локално само архивираните", - "delete_local_dialog_ok_force": "Въпреки това изтрий", - "delete_others": "Изтрий останалите", - "delete_permanently": "Изтрий за постоянно", - "delete_permanently_action_prompt": "{count} изтрити за постоянно", - "delete_shared_link": "Изтриване на споделен линк", - "delete_shared_link_dialog_title": "Изтрий споделената връзка", - "delete_tag": "Изтрий таг", - "delete_tag_confirmation_prompt": "Сигурни ли сте, че искате да изтриете тага {tagName}?", - "delete_user": "Изтрий потребител", - "deleted_shared_link": "Изтрит споделен линк", - "deletes_missing_assets": "Изтрива файлове, които липсват на диска", - "description": "Описание", - "description_input_hint_text": "Добави описание...", - "description_input_submit_error": "Неуспешно обновяване на описанието. За подробности вижте в дневника", - "deselect_all": "Премахни избора от всички", - "details": "Детайли", - "direction": "Посока", - "disable": "Забрани", - "disabled": "Изключено", - "disallow_edits": "Забраняване на редакциите", - "discord": "Намери ни в Discord", - "discover": "Открий", - "discovered_devices": "Открити устройства", - "dismiss_all_errors": "Отхвърляне на всички грешки", - "dismiss_error": "Отхвърляне на грешка", - "display_options": "Опции за показване", - "display_order": "Ред на показване", - "display_original_photos": "Показване на оригинални снимки", - "display_original_photos_setting_description": "Показване на оригиналната снимка вместо миниатюри, когато оригиналният актив е съвместим с мрежата. Това може да доведе до по-бавни скорости на показване на снимки.", - "do_not_show_again": "Не показвайте това съобщение отново", - "documentation": "Документация", - "done": "Готово", - "download": "Изтегли", - "download_action_prompt": "Зареждане на {count} обекта", - "download_canceled": "Изтеглянето е отменено", - "download_complete": "Изтеглянето завърши", - "download_enqueue": "Изтеглянето е добавено в опашката", - "download_error": "Грешка при изтегляне", - "download_failed": "Неуспешно изтегляне", - "download_finished": "Изтеглянето завърши", - "download_include_embedded_motion_videos": "Вградени видеа", - "download_include_embedded_motion_videos_description": "Включете видеата, вградени в динамични снимки, като отделен файл", - "download_notfound": "Не е намерено за изтегляне", - "download_original": "Сваляне на оригинал", - "download_paused": "Изтеглянето е на пауза", - "download_settings": "Изтегли", - "download_settings_description": "Управление на настройките, свързани с изтеглянето на файлове", - "download_started": "Изтеглянето започна", - "download_sucess": "Успешно изтегляне", - "download_sucess_android": "Файловете са изтеглени в DCIM/Immich", - "download_waiting_to_retry": "Изчакване за повторение", - "downloading": "Изтегляне", - "downloading_asset_filename": "Изтегляне на файл {filename}", - "downloading_from_icloud": "Сваляне от iCloud", - "downloading_media": "Изтегляне на медия", - "drop_files_to_upload": "Пуснете файловете, за да ги качите", - "duplicates": "Дубликати", - "duplicates_description": "Изберете всяка група, като посочите кои, ако има такива, са дубликати", - "duration": "Продължителност", - "edit": "Редактиране", - "edit_album": "Редактиране на албум", - "edit_avatar": "Редактиране на аватар", - "edit_birthday": "Редактиране на рожден ден", - "edit_date": "Редактиране на дата", - "edit_date_and_time": "Редактиране на дата и час", - "edit_date_and_time_action_prompt": "{count} дата и време са редактирани", - "edit_date_and_time_by_offset": "Промяна на датата чрез отместване", - "edit_date_and_time_by_offset_interval": "Нов период от време: {from} - {to}", - "edit_description": "Редактирай описание", - "edit_description_prompt": "Моля, избери ново описание:", - "edit_exclusion_pattern": "Редактиране на шаблон за изключване", - "edit_faces": "Редактиране на лица", - "edit_key": "Редактиране на ключ", - "edit_link": "Редактиране на линк", - "edit_location": "Редактиране на местоположението", - "edit_location_action_prompt": "{count} локации са редактирани", - "edit_location_dialog_title": "Местоположение", - "edit_name": "Редактиране на име", - "edit_people": "Редактиране на хора", - "edit_tag": "Редактирай таг", - "edit_title": "Редактиране на заглавието", - "edit_user": "Редактиране на потребител", - "edit_workflow": "Редактиране на работен процес", - "editor": "Редактор", - "editor_close_without_save_prompt": "Промените няма да бъдат запазени", - "editor_close_without_save_title": "Затваряне на редактора?", - "editor_confirm_reset_all_changes": "Сигурни ли сте, че искате да възстановите всички промени?", - "editor_flip_horizontal": "Обърни хоризонтално", - "editor_flip_vertical": "Обърни вертикално", - "editor_orientation": "Ориентация", - "editor_reset_all_changes": "Възстанови всички промени", - "editor_rotate_left": "Завърти 90° обратно на часовниковата стрелка", - "editor_rotate_right": "Завърти 90° по часовниковата стрелка", - "email": "Имейл", - "email_notifications": "Известия на имейл", - "empty_folder": "Тази папка е празна", - "empty_trash": "Изпразване на кош", - "empty_trash_confirmation": "Сигурни ли сте, че искате да изпразните кошчето? Това ще премахне всичко в кошчето за постоянно от Immich.\nНе можете да отмените това действие!", - "enable": "Включване", - "enable_backup": "Включи резервното копиране", - "enable_biometric_auth_description": "Въведете вашия PIN код, за да разрешите биометрично удостоверяване", - "enabled": "Включено", - "end_date": "Крайна дата", - "enqueued": "Наредено в опашката", - "enter_wifi_name": "Въведи име на Wi-Fi", - "enter_your_pin_code": "Въведете вашия PIN код", - "enter_your_pin_code_subtitle": "Въвеждане на PIN код, за достъп до заключена папка", - "error": "Грешка", - "error_change_sort_album": "Неуспешна промяна на реда на сортиране на албум", - "error_delete_face": "Грешка при изтриване на лице от актива", - "error_getting_places": "Грешка при събиране на местата", - "error_loading_albums": "Грешка при зареждане на албуми", - "error_loading_image": "Грешка при зареждане на изображението", - "error_loading_partners": "Грешка при зареждане на партньори: {error}", - "error_retrieving_asset_information": "Грешка при получаване на информация за обект", - "error_saving_image": "Грешка: {error}", - "error_tag_face_bounding_box": "Грешка при отбелязване на лице - неуспешно получаване на координати на рамката", - "error_title": "Грешка - нещо се обърка", - "error_while_navigating": "Грешка при навигиране към обект", - "errors": { - "cannot_navigate_next_asset": "Не можете да преминете към следващия файл", - "cannot_navigate_previous_asset": "Не можете да преминете към предишния актив", - "cant_apply_changes": "Не могат да се приложат промените", - "cant_change_activity": "Не може {enabled, select, true {да се деактивира} other {да се активира}} дейността", - "cant_change_asset_favorite": "Не може да промени любими за файл", - "cant_change_metadata_assets_count": "Не може да се промени метаданните на {count, plural, one {# обект} other {# обекта}}", - "cant_get_faces": "Не мога да намеря лица", - "cant_get_number_of_comments": "Не може да се получи броят на коментарите", - "cant_search_people": "Не може да търси хора", - "cant_search_places": "Не може да търси места", - "error_adding_assets_to_album": "Грешка при добавянето на файловете в албума", - "error_adding_users_to_album": "Грешка при добавяне на потребители в албум", - "error_deleting_shared_user": "Грешка при изтриване на споделен потребител", - "error_downloading": "Грешка при изтегляне на {filename}", - "error_hiding_buy_button": "Грешка при скриването на бутона за купуване", - "error_removing_assets_from_album": "Грешка при премахването на файловете от албума, проверете конзолата за повече информация", - "error_selecting_all_assets": "Грешка при избора на всички файлове", - "exclusion_pattern_already_exists": "Този модел за изключване вече съществува.", - "failed_to_create_album": "Неуспешно създаване на албум", - "failed_to_create_shared_link": "Неуспешно създаване на спoделена връзка", - "failed_to_edit_shared_link": "Неуспешно редактиране на споделена връзка", - "failed_to_get_people": "Неуспешно зареждане на хора", - "failed_to_keep_this_delete_others": "Неуспешно запазване на този обект и изтриване на останалите обекти", - "failed_to_load_asset": "Неуспешно зареждане на файл", - "failed_to_load_assets": "Неуспешно зареждане на файлове", - "failed_to_load_notifications": "Неуспешно зареждане на известия", - "failed_to_load_people": "Неуспешно зареждане на хора", - "failed_to_remove_product_key": "Неуспешно премахване на продуктовия ключ", - "failed_to_reset_pin_code": "Неуспешно нулиране на ПИН кода", - "failed_to_stack_assets": "Неуспешно подреждане на обекти", - "failed_to_unstack_assets": "Неуспешно премахване на подредбата на обекти", - "failed_to_update_notification_status": "Неуспешно обновяване на състоянието на известията", - "incorrect_email_or_password": "Неправилен имейл или парола", - "library_folder_already_exists": "Тази папка вече съществува.", - "paths_validation_failed": "{paths, plural, one {# път} other {# пътища}} не преминаха валидация", - "profile_picture_transparent_pixels": "Профилните снимки не могат да имат прозрачни пиксели. Моля, увеличете и/или преместете изображението.", - "quota_higher_than_disk_size": "Зададена е квота, по-голяма от размера на диска", - "something_went_wrong": "Нещо се обърка", - "unable_to_add_album_users": "Неуспешно добавяне на потребители в албум", - "unable_to_add_assets_to_shared_link": "Неуспешно добавяне на обекти в споделен линк", - "unable_to_add_comment": "Неуспешно добавяне на коментар", - "unable_to_add_exclusion_pattern": "Неуспешно добавяне на шаблон за изключение", - "unable_to_add_partners": "Неуспешно добавяне на партньори", - "unable_to_add_remove_archive": "Неуспешно {archived, select, true {премахване на обект от} other {добавяне на обект в}} архива", - "unable_to_add_remove_favorites": "Неуспешно {favorite, select, true {добавяне на обект в} other {премахване на обект от}} любими", - "unable_to_archive_unarchive": "Неуспешно {archived, select, true {архивиране} other {разархивиране}}", - "unable_to_change_album_user_role": "Не може да се промени ролята на потребителя на албума", - "unable_to_change_date": "Не може да се промени датата", - "unable_to_change_description": "Неуспешна промяна на описанието", - "unable_to_change_favorite": "Не може да промени фаворит за актив", - "unable_to_change_location": "Не може да се промени местоположението", - "unable_to_change_password": "Не може да се промени паролата", - "unable_to_change_visibility": "Не може да се промени видимостта за {count, plural, one {# човек} other {# човека}}", - "unable_to_complete_oauth_login": "Не може да се завърши OAuth влизане", - "unable_to_connect": "Не може да се свърже", - "unable_to_copy_to_clipboard": "Не може да се копира в клипборда, уверете се, че имате достъп до страницата през https", - "unable_to_create": "Неуспешно създаване на работен процес", - "unable_to_create_admin_account": "Не може да създаде администраторски акаунт", - "unable_to_create_api_key": "Не може да се създаде нов API ключ", - "unable_to_create_library": "Не може да се създаде библиотека", - "unable_to_create_user": "Не може да се създаде потребител", - "unable_to_delete_album": "Не може да изтрие албума", - "unable_to_delete_asset": "Не може да изтрие файла", - "unable_to_delete_assets": "Грешка при изтриване на файлове", - "unable_to_delete_exclusion_pattern": "Не може да изтрие шаблон за изключване", - "unable_to_delete_shared_link": "Споделената връзка не може да се изтрие", - "unable_to_delete_user": "Не може да изтрие потребител", - "unable_to_delete_workflow": "Неуспешно премахване на работен процес", - "unable_to_download_files": "Не могат да се изтеглят файловете", - "unable_to_edit_exclusion_pattern": "Не може да се редактира шаблон за изключване", - "unable_to_empty_trash": "Неуспешно изпразване на кошчето", - "unable_to_enter_fullscreen": "Не може да се отвори в цял екран", - "unable_to_exit_fullscreen": "Не може да излезе от цял екран", - "unable_to_get_comments_number": "Не може да получи брой коментари", - "unable_to_get_shared_link": "Неуспешно създаване на споделена връзка", - "unable_to_hide_person": "Не може да скрие човек", - "unable_to_link_motion_video": "Неуспешно свързване на видео с движение", - "unable_to_link_oauth_account": "Неуспешно свързване на OAuth акаунт", - "unable_to_log_out_all_devices": "Неуспешно излизане от всички устройства", - "unable_to_log_out_device": "Неуспешно излизане от устройството", - "unable_to_login_with_oauth": "Неуспешно влизане с OAuth", - "unable_to_play_video": "Неуспешно възпроизвеждане на видеото", - "unable_to_reassign_assets_existing_person": "Неуспешно пренасочване на елементи към {name, select, null {съществуващо лице} other {{name}}}", - "unable_to_reassign_assets_new_person": "Неуспешно пренасочване на елементи към нов човек", - "unable_to_refresh_user": "Неуспешно обновяване на потребителя", - "unable_to_remove_album_users": "Неуспешно премахване на потребители от албума", - "unable_to_remove_api_key": "Неуспешно премахване на API Key", - "unable_to_remove_assets_from_shared_link": "Неуспешно премахване на елементи от споделената връзка", - "unable_to_remove_library": "Неуспешно премахване на библиотеката", - "unable_to_remove_partner": "Неуспешно премахване на партньор", - "unable_to_remove_reaction": "Неуспешно премахване на реакцията", - "unable_to_reset_password": "Неуспешно смяна на паролата", - "unable_to_reset_pin_code": "Неуспешно нулиране на PIN кода", - "unable_to_resolve_duplicate": "Неуспешно справяне с дублирането", - "unable_to_restore_assets": "Неуспешно възстановяване на елементи", - "unable_to_restore_trash": "Неуспешно възстановяване от кошчето", - "unable_to_restore_user": "Неуспешно възстановяване на потребителя", - "unable_to_save_album": "Неуспешно запаметяване на албума", - "unable_to_save_api_key": "Неуспешно запаметяване на API Key", - "unable_to_save_date_of_birth": "Неуспешно запаметяване на датата на раждане", - "unable_to_save_name": "Неуспешна промяна на името", - "unable_to_save_profile": "Неуспешно запаметяване на профила", - "unable_to_save_settings": "Неуспешно запаметяване на настройките", - "unable_to_scan_libraries": "Неуспешно сканиране на библиотеките", - "unable_to_scan_library": "Неуспешно сканиране на библиотеката", - "unable_to_set_feature_photo": "Неуспешно задаване на представителна снимка", - "unable_to_set_profile_picture": "Неуспешно задаване на профилна снимка", - "unable_to_set_rating": "Неуспешно задаване на рейтинг", - "unable_to_submit_job": "Неуспешно задаване на задача", - "unable_to_trash_asset": "Неуспешно премахване на файла", - "unable_to_unlink_account": "Неуспешно отделяне на акаунта", - "unable_to_unlink_motion_video": "Неуспешно отделяне на видеото", - "unable_to_update_album_cover": "Неуспешно обновяване на корицата на албума", - "unable_to_update_album_info": "Неуспешно обновяване на информацията за албума", - "unable_to_update_library": "Неуспешно обновяване на библиотеката", - "unable_to_update_location": "Неуспешно обновяване на локацията", - "unable_to_update_settings": "Неуспешно обновяване на настройките", - "unable_to_update_timeline_display_status": "Невъзможно е обноваване на състоянието на дисплея на времевата линия", - "unable_to_update_user": "Неуспешно обновяване на потребителя", - "unable_to_update_workflow": "Неуспешно обновяване на работния процес", - "unable_to_upload_file": "Неуспешно качване на файл" - }, - "errors_text": "Грешки", - "exclusion_pattern": "Шаблон за изключение", - "exif": "Еxif", - "exif_bottom_sheet_description": "Добави Описание...", - "exif_bottom_sheet_description_error": "Неуспешно обновяване на описание", - "exif_bottom_sheet_details": "ПОДРОБНОСТИ", - "exif_bottom_sheet_location": "МЯСТО", - "exif_bottom_sheet_no_description": "Няма описание", - "exif_bottom_sheet_people": "ХОРА", - "exif_bottom_sheet_person_add_person": "Добави име", - "exit_slideshow": "Изход от слайдшоуто", - "expand_all": "Разшири всички", - "experimental_settings_new_asset_list_subtitle": "В развитие", - "experimental_settings_new_asset_list_title": "Включи експериментална подредба на снимки", - "experimental_settings_subtitle": "Използвайте на свой риск!", - "experimental_settings_title": "Експериментални", - "expire_after": "Изтича след", - "expired": "Изтекло", - "expires_date": "Изтича {date}", - "explore": "Разгледай", - "explorer": "Преглед", - "export": "Експорт", - "export_as_json": "Експортиране като JSON", - "export_database": "Експорт на базата данни", - "export_database_description": "Експорт на базата данни SQLite", - "extension": "Разширение", - "external": "Външно", - "external_libraries": "Външни библиотеки", - "external_network": "Външна мрежа", - "external_network_sheet_info": "Когато няма връзка с предпочитаната Wi-Fi мрежа, приложението ще опитва да се свърже със сървъра чрез първия достъпен URL адрес, започвайки отгоре надолу", - "face_unassigned": "Незададено", - "failed": "Неуспешно", - "failed_count": "Неуспешни: {count}", - "failed_to_authenticate": "Неуспешна автентикация", - "failed_to_load_assets": "Неуспешно зареждане на елементи", - "failed_to_load_folder": "Неуспешно зареждане на папка", - "favorite": "Любим", - "favorite_action_prompt": "{count} са добавени в Любими", - "favorite_or_unfavorite_photo": "Добави или премахни снимка от Любими", - "favorites": "Любими", - "favorites_page_no_favorites": "Не са намерени любими обекти", - "feature_photo_updated": "Представителната снимка е обновена", - "features": "Функции", - "features_in_development": "Функции в процес на разработка", - "features_setting_description": "Управление на функциите на приложението", - "file_name_or_extension": "Име на файл или разширение", - "file_size": "Размер на файла", - "filename": "Име на файл", - "filetype": "Тип на файл", - "filter": "Филтър", - "filter_description": "Условия за филтриране на обекти", - "filter_people": "Филтриране на хора", - "filter_places": "Филтър по място", - "filters": "Филтри", - "find_them_fast": "Намерете ги бързо по име с търсене", - "first": "Първи", - "fix_incorrect_match": "Поправяне на неправилно съвпадение", - "folder": "Папка", - "folder_not_found": "Папката не е намерена", - "folders": "Папки", - "folders_feature_description": "Преглеждане на папката за снимките и видеоклиповете в файловата система", - "forgot_pin_code_question": "Забравили сте своя ПИН код?", - "forward": "Напред", - "free_up_space": "Освобождаване на място", - "free_up_space_description": "Преместете архивираните снимки и видеа в кошчето на устройството, за да освободите място. Копията на сървъра ще бъдат запазени.", - "free_up_space_settings_subtitle": "Освобождаване на място за съхранение на устройството", - "full_path": "Пълен път: {path}", - "gcast_enabled": "Gооgle Cast", - "gcast_enabled_description": "За да работи тази функция зарежда външни ресурси от Google.", - "general": "Общи", - "geolocation_instruction_location": "Изберете обект с GPS координати за да използвате тях или изберете място директно от картата", - "get_help": "Помощ", - "get_people_error": "Грешка при получаване на хора", - "get_wifiname_error": "Неуспешно получаване името на Wi-Fi мрежата. Моля, убедете се, че са предоставени нужните разрешения на приложението и има връзка с Wi-Fi", - "getting_started": "Как да започнем", - "go_back": "Връщане назад", - "go_to_folder": "Отиди в папката", - "go_to_search": "Преминаване към търсене", - "gps": "GPS координати", - "gps_missing": "Няма GPS координати", - "grant_permission": "Дай разрешение", - "group_albums_by": "Групирай албум по...", - "group_country": "Групирай по държава", - "group_no": "Няма група", - "group_owner": "Групиране по собственик", - "group_places_by": "Групирай места по…", - "group_year": "Групиране по година", - "haptic_feedback_switch": "Включи тактилна обратна връзка", - "haptic_feedback_title": "Тактилна обратна връзка", - "has_quota": "Лимит", - "hash_asset": "Обект с хеш", - "hashed_assets": "Хеширани обекти", - "hashing": "Хеширане", - "header_settings_add_header_tip": "Добави заглавие", - "header_settings_field_validator_msg": "Недопустимо е да няма стойност", - "header_settings_header_name_input": "Име на заглавието", - "header_settings_header_value_input": "Стойност на заглавието", - "headers_settings_tile_title": "Потребителски прокси заглавия", - "height": "Височина", - "hi_user": "Здравей, {name} {email}", - "hide_all_people": "Скрий всички хора", - "hide_gallery": "Скрий галерия", - "hide_named_person": "Скрий човек {name}", - "hide_password": "Скрий парола", - "hide_person": "Скрий човек", - "hide_schema": "Скриване на схемата", - "hide_text_recognition": "Скрий разпознатия текст", - "hide_unnamed_people": "Скрий неназовани хора", - "home_page_add_to_album_conflicts": "Добавени са {added} обекта в албума {album}. Вече има {failed} обекта.", - "home_page_add_to_album_err_local": "Все още не е възможно да се добавят локални обекти в албумите, пропускане", - "home_page_add_to_album_success": "Добавени са {added} обекта в албума {album}.", - "home_page_album_err_partner": "Все още не е възможно да се добавят обекти на партньори в албум, пропускане", - "home_page_archive_err_local": "Все още не могат да се архивират локални обекти, пропускане", - "home_page_archive_err_partner": "Не могат да се архивират обекти на партньор, пропускане", - "home_page_building_timeline": "Изграждане на времевата линия", - "home_page_delete_err_partner": "Не може да се изтриват обекти на партньор, пропускане", - "home_page_delete_remote_err_local": "Локални обекти не могат да се изтриват от сървъра, пропускане", - "home_page_favorite_err_local": "Локални обекти все още не могат да се правят любими, пропускане", - "home_page_favorite_err_partner": "Партньорски обекти все още не могат да се правят любими, пропускане", - "home_page_first_time_notice": "Ако за първи път използвате приложението, моля изберете албум за архивиране, за да може обектите от времевата линия да се записват в него", - "home_page_locked_error_local": "Локални обекти не могат да се преместят в заключена папка, пропускане", - "home_page_locked_error_partner": "Партньорски обекти не могат да се преместят в заключена папка, пропускане", - "home_page_share_err_local": "Локални обекти не могат да се споделят чрез връзка, пропускане", - "home_page_upload_err_limit": "Може да качвате максимум 30 обекта едновременно, пропускане", - "host": "Хост", - "hour": "Час", - "hours": "Часа", - "id": "ID", - "idle": "Бездействие", - "ignore_icloud_photos": "Пропусни снимки от iCloud", - "ignore_icloud_photos_description": "Снимки, които са запазени в iCloud няма да се качват в Immich сървъра", - "image": "Изображение", - "image_alt_text_date": "{isVideo, select, true {Видео} other {Снимка}} заснето на {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Видео} other {Снимка}} заснето с {person1} на {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Видео} other {Снимка}} заснето с {person1} и {person2} на {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Видео} other {Снимка}} заснето с {person1}, {person2}, и {person3} на {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Видео} other {Снимка}} заснето с {person1}, {person2}, и {additionalCount, number} други на {date}", - "image_alt_text_date_place": "{isVideo, select, true {Видео} other {Снимка}} заснето в {city}, {country} на {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Видео} other {Снимка}} заснето в {city}, {country} с {person1} на {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Видео} other {Снимка}} заснето в {city}, {country} с {person1} и {person2} на {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Видео} other {Снимка}} заснето с {city}, {country} и {person1}, {person2}, и {person3} на {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Видео} other {Снимка}} заснето в {city}, {country} с {person1}, {person2}, и {additionalCount, number} други на{date}", - "image_saved_successfully": "Изображението е запазено", - "image_viewer_page_state_provider_download_started": "Започна изтегляне", - "image_viewer_page_state_provider_download_success": "Успешно изтегляне", - "image_viewer_page_state_provider_share_error": "Грешка при споделяне", - "immich_logo": "Immich лого", - "immich_web_interface": "Уеб интерфейс на Immich", - "import_from_json": "Импортиране от JSON", - "import_path": "Път за импортиране", - "in_albums": "В {count, plural, one {# албум} other {# албума}}", - "in_archive": "В архив", - "in_year": "{year} г.", - "in_year_selector": "През", - "include_archived": "Включване на архивирани", - "include_shared_albums": "Включване на споделени албуми", - "include_shared_partner_assets": "Включване на споделените с партньор елементи", - "individual_share": "Индивидуално споделяне", - "individual_shares": "Индивидуални споделяния", - "info": "Информация", - "interval": { - "day_at_onepm": "Всеки ден в 13:00", - "hours": "Всеки {hours, plural, one {час} other {{hours, number} часа}}", - "night_at_midnight": "Всяка вечер в полунощ", - "night_at_twoam": "Всяка нощ в 2 часа сутринта" - }, - "invalid_date": "Невалидна дата", - "invalid_date_format": "Невалиден формат за дата", - "invite_people": "Покани хора", - "invite_to_album": "Покани в албум", - "ios_debug_info_fetch_ran_at": "Изтегляне на {dateTime}", - "ios_debug_info_last_sync_at": "Синхронизирано на {dateTime}", - "ios_debug_info_no_processes_queued": "Няма фонови процеси в опашката", - "ios_debug_info_no_sync_yet": "Все още не е изпълнявана задача за фонова синхронизация", - "ios_debug_info_processes_queued": "{count, plural, one {{count} фонов процес} many {{count} фонови процеса} other {{count} фонови процеса}} в опашката", - "ios_debug_info_processing_ran_at": "Започната обработка на {dateTime}", - "items_count": "{count, plural, one {# елемент} other {# елементи}}", - "jobs": "Задачи", - "json_editor": "JSON редактор", - "json_error": "Грешка в JSON", - "keep": "Задръж", - "keep_albums": "Запази албуми", - "keep_albums_count": "Запазване на {count} {count, plural, one {албум} other {албума}}", - "keep_all": "Задръж всички", - "keep_description": "Изберете какво да остане на устройството при освобождаване на място.", - "keep_favorites": "Запазване на любими", - "keep_on_device": "Запази на устройството", - "keep_on_device_hint": "Изберете обектите, които да бъдат запазени на устройството", - "keep_this_delete_others": "Запази това, изтрий другите", - "keeping": "Запазване: {items}", - "kept_this_deleted_others": "Запази този елемент и другите изтрити {count, plural, one {# елемент} other {# елемента}}", - "keyboard_shortcuts": "Бързи клавишни комбинации", - "language": "Език", - "language_no_results_subtitle": "Опитайте да коригирате термина си за търсене", - "language_no_results_title": "Не са намерени езици", - "language_search_hint": "Търсене на езици...", - "language_setting_description": "Изберете предпочитан език", - "large_files": "Големи файлове", - "last": "Последен", - "last_months": "{count, plural, one {Последния месец} other {Последните # месеца}}", - "last_seen": "Последно видяно", - "latest_version": "Последна версия", - "latitude": "Ширина", - "leave": "Излез", - "leave_album": "Напускане на албума", - "lens_model": "Модел леща", - "let_others_respond": "Позволете на другите да отговорят", - "level": "Ниво", - "library": "Библиотека", - "library_add_folder": "Добави папка", - "library_edit_folder": "Редактиране на папка", - "library_options": "Опции на библиотеката", - "library_page_device_albums": "Албуми в устройството", - "library_page_new_album": "Нов албум", - "library_page_sort_asset_count": "Брой обекти", - "library_page_sort_created": "Дата на създаване", - "library_page_sort_last_modified": "Последна промяна", - "library_page_sort_title": "Заглавие на албума", - "licenses": "Лицензи", - "light": "Светло", - "like": "Харесайте", - "like_deleted": "Като изтрит", - "link_motion_video": "Линк към видео", - "link_to_oauth": "Линк към OAuth", - "linked_oauth_account": "Свързан OAuth акаунт", - "list": "Лист", - "loading": "Зареждане", - "loading_search_results_failed": "Зареждането на резултатите от търсенето е неуспешно", - "local": "Локално", - "local_asset_cast_failed": "Не може да се предава обект, който още не е качен на сървъра", - "local_assets": "Локални обекти", - "local_id": "Идентификатор", - "local_media_summary": "Обобщение на локалните файлове", - "local_network": "Локална мрежа", - "local_network_sheet_info": "Приложението ще се свърже със сървъра на този URL, когато устройството е свързано към зададената Wi-Fi мрежа", - "location": "Място", - "location_permission": "Разрешение за местоположение", - "location_permission_content": "За да работи функцията автоматично превключване, Immich се нуждае от разрешение за точно местоположение, за да може да чете името на текущата Wi-Fi мрежа", - "location_picker_choose_on_map": "Избери на карта", - "location_picker_latitude_error": "Въведи правилна ширина", - "location_picker_latitude_hint": "Въведете географска ширина тук", - "location_picker_longitude_error": "Въведи правилна дължина", - "location_picker_longitude_hint": "Въведете географска дължина тук", - "lock": "Заключи", - "locked_folder": "Заключена папка", - "log_detail_title": "Подробности от дневника", - "log_out": "Излизане", - "log_out_all_devices": "Излизане с всички устройства", - "logged_in_as": "Вписан като {user}", - "logged_out_all_devices": "Успешно излизане от всички устройства", - "logged_out_device": "Успешно излизане от устройство", - "login": "Вписване", - "login_disabled": "Вписването е забранено", - "login_form_api_exception": "Грешка в комуникацията. Моля, провери URL на сървъра и опитай пак.", - "login_form_back_button_text": "Обратно", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://yоur-server-ip:port", - "login_form_endpoint_url": "URL адрес на сървъра", - "login_form_err_http": "Моля, определи протокола http:// или https://", - "login_form_err_invalid_email": "Невалиден имейл адрес", - "login_form_err_invalid_url": "Невалиден URL", - "login_form_err_leading_whitespace": "Интервал отпред", - "login_form_err_trailing_whitespace": "Интервал в края", - "login_form_failed_get_oauth_server_config": "Грешка при вписване с OAuth, провери URL на сървъра", - "login_form_failed_get_oauth_server_disable": "На този сървър OAuth не е достъпна", - "login_form_failed_login": "Грешка при вписване, проверете URL, имейла и паролата", - "login_form_handshake_exception": "Грешка при договаряне на връзката със сървъра. Ако използвате самоподписан сертификат, разрешете в настройкте използване на самоподписан сертификат.", - "login_form_password_hint": "парола", - "login_form_save_login": "Остани вписан", - "login_form_server_empty": "Въведи URL на сървъра.", - "login_form_server_error": "Не можах да се свържа със сървъра.", - "login_has_been_disabled": "Влизането е деактивирано.", - "login_password_changed_error": "Грешка при обновяване на паролата", - "login_password_changed_success": "Успешно обновена парола", - "logout_all_device_confirmation": "Сигурни ли сте, че искате да излезете от всички устройства?", - "logout_this_device_confirmation": "Сигурни ли сте, че искате да излезете от това устройство?", - "logs": "Дневник", - "longitude": "Дължина", - "look": "Изглед", - "loop_videos": "Повтаряне на видеата", - "loop_videos_description": "Позволи автоматично повтаряне на видеото в изгледа на детайлите.", - "main_branch_warning": "Използвате версия за разработчици, силно препоръчваме да използвате официална версия!", - "main_menu": "Главно меню", - "maintenance_action_restore": "Възвстановяване на базата данни", - "maintenance_description": "Сървъра Immich е поставен в режим на обслужване.", - "maintenance_end": "Край на режима на обслужване", - "maintenance_end_error": "Неуспешно завършване на режима на обслужване.", - "maintenance_logged_in_as": "Текущия потребител е {user}", - "maintenance_restore_from_backup": "Възстановяване от архив", - "maintenance_restore_library": "Възстановяване на библиотека", - "maintenance_restore_library_confirm": "Ако това изглежда правилно, направете възстановяване от архив!", - "maintenance_restore_library_description": "Възстановяване на базата данни", - "maintenance_restore_library_folder_has_files": "{folder} има {count} папки", - "maintenance_restore_library_folder_no_files": "В {folder} няма файлове!", - "maintenance_restore_library_folder_pass": "за четене и за запис", - "maintenance_restore_library_folder_read_fail": "не е читаем", - "maintenance_restore_library_folder_write_fail": "не е записваем", - "maintenance_restore_library_hint_missing_files": "Може да липсват важни файлове", - "maintenance_restore_library_hint_regenerate_later": "Можете да ги генерирате отново по-късно в настройките", - "maintenance_restore_library_hint_storage_template_missing_files": "Използвате ли шаблон за съхранение? Може да липсват файлове", - "maintenance_restore_library_loading": "Зареждане на проверки за цялост и евристика…", - "maintenance_task_backup": "Създаване на архив на съществуващата база данни…", - "maintenance_task_migrations": "Изпълняват се миграции на базата данни…", - "maintenance_task_restore": "Възстановяване от избрания архив…", - "maintenance_task_rollback": "Възстановяването не е успешно, връщане към начална позиция…", - "maintenance_title": "Временно недостъпен", - "make": "Марка", - "manage_geolocation": "Управление на местоположенията", - "manage_media_access_rationale": "Това разрешение е необходимо за правилно преместване на обекти в кошчето и за възстановяване от там.", - "manage_media_access_settings": "Отвори Настройки", - "manage_media_access_subtitle": "Разрешете приложението Immich да управлява и мести медийни файлове.", - "manage_media_access_title": "Управление на медийни файлове", - "manage_shared_links": "Управление на споделени връзки", - "manage_sharing_with_partners": "Управление на споделянето с партньори", - "manage_the_app_settings": "Управление на настройките на приложението", - "manage_your_account": "Управление на профила ви", - "manage_your_api_keys": "Управление на вашите API ключове", - "manage_your_devices": "Управление на влезлите в системата устройства", - "manage_your_oauth_connection": "Управление на OAuth връзката", - "map": "Карта", - "map_assets_in_bounds": "{count, plural, =0 {Няма снимки} one {# снимка} other {# снимки}}", - "map_cannot_get_user_location": "Не можах да получа местоположението", - "map_location_dialog_yes": "Да", - "map_location_picker_page_use_location": "Използвай това местоположение", - "map_location_service_disabled_content": "За да се показват обектите от текущото място, трябва да бъде включена услугата за местоположение. Искате ли да я включите сега?", - "map_location_service_disabled_title": "Услугата за местоположение е изключена", - "map_marker_for_images": "Маркери на картата за снимки направени в {city}, {country}", - "map_marker_with_image": "Маркер на картата с изображение", - "map_no_location_permission_content": "За да се показват обектите от текущото място, трябва разрешение за определяне на местоположението. Искате ли да предоставите разрешение сега?", - "map_no_location_permission_title": "Отказан достъп до местоположение", - "map_settings": "Настройки на картата", - "map_settings_dark_mode": "Тъмен режим", - "map_settings_date_range_option_day": "Последните 24 часа", - "map_settings_date_range_option_days": "Последните {days} дни", - "map_settings_date_range_option_year": "Изминалата година", - "map_settings_date_range_option_years": "Миналите {years} години", - "map_settings_dialog_title": "Настройки на картата", - "map_settings_include_show_archived": "Покажи архивираните", - "map_settings_include_show_partners": "Покажи партньорите", - "map_settings_only_show_favorites": "Покажи само любими", - "map_settings_theme_settings": "Цветова тема на картата", - "map_zoom_to_see_photos": "Намали мащаба, за да видиш снимките", - "mark_all_as_read": "Маркирай всички като четени", - "mark_as_read": "Маркирай като четено", - "marked_all_as_read": "Всички маркирани като прочетени", - "matches": "Съвпадения", - "matching_assets": "Съвпадащи обекти", - "media_type": "Вид медия", - "memories": "Спомени", - "memories_all_caught_up": "Това е всичко за днес", - "memories_check_back_tomorrow": "За още спомени погледни утре пак", - "memories_setting_description": "Управление на това, което виждате в спомени", - "memories_start_over": "Започни отново", - "memories_swipe_to_close": "За да затвориш плъзни нагоре", - "memory": "Памет", - "memory_lane_title": "Спомени за {title}", - "menu": "Меню", - "merge": "Обединяване", - "merge_people": "Сливане на хора", - "merge_people_limit": "Можете да обединявате само до 5 лица в даден момент", - "merge_people_prompt": "Искате ли да слеете тези хора? Това действие е необратимо.", - "merge_people_successfully": "Успешно сливане на хора", - "merged_people_count": "Слят {count, plural, one {# човек} other {# човека}}", - "minimize": "Минимизиране", - "minute": "Минута", - "minutes": "Минути", - "mirror_horizontal": "Хоризонтално", - "mirror_vertical": "Вертикално", - "missing": "Липсващи", - "mobile_app": "Мобилно приложение", - "mobile_app_download_onboarding_note": "Свалете мобилното приложение Immich с някоя от следните опции", - "model": "Модел", - "month": "Месец", - "monthly_title_text_date_format": "MMMM г", - "more": "Още", - "move": "Премести", - "move_down": "Премести надолу", - "move_off_locked_folder": "Извади от заключената папка", - "move_to": "Премести към", - "move_to_device_trash": "Преместване в кошчето на устройството", - "move_to_lock_folder_action_prompt": "{count} са добавени в заключената папка", - "move_to_locked_folder": "Премести в заключена папка", - "move_to_locked_folder_confirmation": "Тези снимки и видеа ще бъдат изтрити от всички албуми и ще са достъпни само в заключената папка", - "move_up": "Премести нагоре", - "moved_to_archive": "{count, plural, one {# обект е преместен} many {# обекта са преместени} other {# обекта са преместени}} в архива", - "moved_to_library": "{count, plural, one {# обект е преместен} many {# обекта са преместени} other {# обекта са преместени}} в библиотеката", - "moved_to_trash": "Преместено в кошчето", - "multiselect_grid_edit_date_time_err_read_only": "Не може да се редактира датата на обект само за четене, пропускане", - "multiselect_grid_edit_gps_err_read_only": "Не може да се редактира местоположението на обект само за четене, пропускане", - "mute_memories": "Изключване на звука на спомените", - "my_albums": "Мои албуми", - "name": "Име", - "name_or_nickname": "Име или прякор", - "name_required": "Задължително е Име", - "navigate": "Придвижване", - "navigate_to_time": "Придвижване до момент във времето", - "network_requirement_photos_upload": "Използвай мобилни данни за архивиране на снимки", - "network_requirement_videos_upload": "Използвай мобилни данни за архивиране на видео", - "network_requirements": "Изисквания към мрежата", - "network_requirements_updated": "Мрежовите настройки са променени, нулиране на опашката за архивиране", - "networking_settings": "Мрежа", - "networking_subtitle": "Управление на настройките за връзка със сървъра", - "never": "Никога", - "new_album": "Нов Албум", - "new_api_key": "Нов API ключ", - "new_date_range": "Нов период от време", - "new_password": "Нова парола", - "new_person": "Нов човек", - "new_pin_code": "Нов PIN код", - "new_pin_code_subtitle": "Това е първи достъп до заключена папка. Създайте PIN код за защитен достъп до тази страница", - "new_timeline": "Нова времева линия", - "new_update": "Ново обновление", - "new_user_created": "Създаден нов потребител", - "new_version_available": "НАЛИЧНА НОВА ВЕРСИЯ", - "newest_first": "Най-новите първи", - "next": "Следващо", - "next_memory": "Следващ спомен", - "no": "Не", - "no_actions_added": "Все още не са добавени действия", - "no_albums_found": "Не са намерени албуми", - "no_albums_message": "Създайте албум за организиране на снимки и видеоклипове", - "no_albums_with_name_yet": "Изглежда, че все още нямате албуми с това име.", - "no_albums_yet": "Изглежда, че все още нямате албуми.", - "no_archived_assets_message": "Архивирайте снимки и видеоклипове, за да ги скриете от изгледа на Снимки", - "no_assets_message": "Кликнете, за да качите първата снимка", - "no_assets_to_show": "Няма обекти за показване", - "no_cast_devices_found": "Няма намерени устройства за предаване", - "no_checksum_local": "Липсват контролни суми - не може да се получат локални обекти", - "no_checksum_remote": "Липсват контролни суми - не може да се получат обекти от сървъра", - "no_configuration_needed": "Не е нужна конфигурация", - "no_devices": "Няма оторизирани устройства", - "no_duplicates_found": "Не бяха открити дубликати.", - "no_exif_info_available": "Няма exif информация", - "no_explore_results_message": "Качете още снимки, за да разгледате колекцията си.", - "no_favorites_message": "Добавете в любими, за да намирате бързо най-добрите си снимки и видеоклипове", - "no_filters_added": "Все още не са добавени филтри", - "no_libraries_message": "Създайте външна библиотека за да разглеждате снимки и видеоклипове", - "no_local_assets_found": "Не е намерен локален обект с такава контролна сума", - "no_location_set": "Не е зададено местоположение", - "no_locked_photos_message": "Снимките и видеата в заключената папка са скрити и не се показват при разглеждане на библиотеката.", - "no_name": "Без име", - "no_notifications": "Няма известия", - "no_people_found": "Не са намерени съответстващи хора", - "no_places": "Няма места", - "no_remote_assets_found": "Не е намерен обект на сървъра с такава контролна сума", - "no_results": "Няма резултати", - "no_results_description": "Опитайте със синоним или по-обща ключова дума", - "no_shared_albums_message": "Създайте албум, за да споделяте снимки и видеоклипове с хората в мрежата си", - "no_uploads_in_progress": "Няма качване в момента", - "none": "Нищо", - "not_allowed": "Не е разрешено", - "not_available": "Неналично", - "not_in_any_album": "Не е в никой албум", - "not_selected": "Не е избрано", - "note_apply_storage_label_to_previously_uploaded assets": "Забележка: За да приложите етикета за съхранение към предварително качени активи, стартирайте", - "notes": "Бележки", - "nothing_here_yet": "Засега тук няма нищо", - "notification_permission_dialog_content": "За да включиш известията, отиди в Настройки и избери Разреши.", - "notification_permission_list_tile_content": "Дай разрешение за активиране на известията.", - "notification_permission_list_tile_enable_button": "Разреши известията", - "notification_permission_list_tile_title": "Разрешаване на известия", - "notification_toggle_setting_description": "Активиране на известия по имейл", - "notifications": "Известия", - "notifications_setting_description": "Управление на известията", - "oauth": "OAuth", - "obtainium_configurator": "Конфигуратор за получаване", - "obtainium_configurator_instructions": "Използвайте Obtainium за инсталация и обновяване на приложението за Android директно от GitHub на Immich. Създайте API ключ и изберете вариант за да създадете Obtainium конфигурационен линк", - "ocr": "Оптично разпознаване на текст", - "official_immich_resources": "Официална информация за Immich", - "offline": "Офлайн", - "offset": "Отместване", - "ok": "Добре", - "oldest_first": "Най-старите първи", - "on_this_device": "На това устройство", - "onboarding": "Въвеждане", - "onboarding_locale_description": "Изберете предпочитан език. По-късно може да го промените в Настройки.", - "onboarding_privacy_description": "Следните (незадължителни) функции разчитат на външни услуги и могат да бъдат деактивирани по всяко време в настройките.", - "onboarding_server_welcome_description": "Да направим общите настройки на вашата инсталация.", - "onboarding_theme_description": "Изберете цветова тема. Може да я промените по-късно в настройките.", - "onboarding_user_welcome_description": "Да започваме!", - "onboarding_welcome_user": "Добре дошъл, {user}", - "online": "Онлайн", - "only_favorites": "Само любими", - "open": "Отвори", - "open_in_map_view": "Отвори изглед на карта", - "open_in_openstreetmap": "Отвори в OpenStreetMap", - "open_the_search_filters": "Отвари филтрите за търсене", - "options": "Настройки", - "or": "или", - "organize_into_albums": "Organitzar per àlbums", - "organize_into_albums_description": "Posar les fotos existents dins dels àlbums fent servir la configuració de sincronització", - "organize_your_library": "Организиране на вашата библиотека", - "original": "оригинал", - "other": "Други", - "other_devices": "Други устройства", - "other_entities": "Други обекти", - "other_variables": "Други променливи", - "owned": "Моите", - "owner": "Собственик", - "page": "Страница", - "partner": "Партньор", - "partner_can_access": "{partner} има достъп", - "partner_can_access_assets": "Всички ваши снимки и видеоклипове, с изключение на тези в Архивирани и Изтрити", - "partner_can_access_location": "Мястото, където са направени снимките", - "partner_list_user_photos": "Снимки на {user}", - "partner_list_view_all": "Покажи всички", - "partner_page_empty_message": "Вашите снимки все още не са споделени.", - "partner_page_no_more_users": "Няма повече потребители за добавяне", - "partner_page_partner_add_failed": "Неуспешно добавяне на партньор", - "partner_page_select_partner": "Избери партньор", - "partner_page_shared_to_title": "Споделено с", - "partner_page_stop_sharing_content": "{partner} вече няма да има достъп до вашите снимки.", - "partner_sharing": "Споделяне с партньори", - "partners": "Партньори", - "password": "Парола", - "password_does_not_match": "Паролата не съвпада", - "password_required": "Изисква се парола", - "password_reset_success": "Успешно възстановяване на паролата", - "past_durations": { - "days": "Преди {days, plural, one {ден} other {# дни}}", - "hours": "Преди {hours, plural, one {час} other {# часа}}", - "years": "Преди {years, plural, one {година} other {# години}}" - }, - "path": "Път", - "pattern": "Шаблон", - "pause": "Пауза", - "pause_memories": "Паузиране на спомените", - "paused": "Паузирано", - "pending": "В изчакване", - "people": "Хора", - "people_edits_count": "Промени {count, plural, one {# човек} other {# човека}}", - "people_feature_description": "Преглеждане на снимки и видеоклипове, групирани по хора", - "people_selected": "{count, plural, one {Избран е # човек} other {Избрани са # човека}}", - "people_sidebar_description": "Показване на връзка към хората в страничната лента", - "permanent_deletion_warning": "Предупреждение за трайно изтриване", - "permanent_deletion_warning_setting_description": "Показване на предупреждение при трайно изтриване на активи", - "permanently_delete": "Трайно изтриване", - "permanently_delete_assets_count": "Изтрий завинаги {count, plural, one {елемент} other {елемента}}", - "permanently_delete_assets_prompt": "Сигурни ли сте, че искате да изтриете завинаги {count, plural, one {този елемент?} other {тези # елементи?}} Това ще премахне {count, plural, one {от неговия} other {от техните}} албум(и).", - "permanently_deleted_asset": "Изтрит завинаги елемент", - "permanently_deleted_assets_count": "Изтрит(и) завинаги{count, plural, one {# елемент} other {# елемента}}", - "permission": "Разрешения", - "permission_empty": "Твоето разрешение не трябва да е празно", - "permission_onboarding_back": "Назад", - "permission_onboarding_continue_anyway": "Въпреки това продължи", - "permission_onboarding_get_started": "Да започваме", - "permission_onboarding_go_to_settings": "Отиди в настройки", - "permission_onboarding_permission_denied": "Отказан достъп. За да ползваш Immich, разреши в настройките достъп до снимки и видео.", - "permission_onboarding_permission_granted": "Предоставено е разрешение! Всичко е готово.", - "permission_onboarding_permission_limited": "Ограничен достъп. За да може Immich да архивира и управлява галерията, предоставете достъп до снимки и видео в настройките.", - "permission_onboarding_request": "Immich се нуждае от разрешение за преглед на снимки и видео.", - "person": "Човек", - "person_age_months": "{months, plural, one {# месец} other {# месеца}}", - "person_age_year_months": "1 година и {months, plural, one {# месец} other {# месеца}}", - "person_age_years": "{years, plural, other {# години}}", - "person_birthdate": "Дата на раждане {date}", - "person_hidden": "{name}{hidden, select, true { (скрит)} other {}}", - "person_recognized": "Разпознато e лице", - "person_selected": "Избрано е лице", - "photo_shared_all_users": "Изглежда, че сте споделили снимките си с всички потребители или нямате потребители, с които да споделяте.", - "photos": "Снимки", - "photos_and_videos": "Снимки и Видеа", - "photos_count": "{count, plural, one {{count, number} Снимка} other {{count, number} Снимки}}", - "photos_from_previous_years": "Снимки от предходни години", - "photos_only": "Само снимки", - "pick_a_location": "Избери локация", - "pick_custom_range": "Произволен период", - "pick_date_range": "Изберете период", - "pin_code_changed_successfully": "Успешно сменен PIN код", - "pin_code_reset_successfully": "Успешно нулиран PIN код", - "pin_code_setup_successfully": "Успешно зададен PIN код", - "pin_verification": "Проверка на PIN кода", - "place": "Местоположение", - "places": "Местоположения", - "places_count": "{count, plural, one {{count, number} Място} other {{count, number} Места}}", - "play": "Възпроизвеждане", - "play_memories": "Възпроизвеждане на спомени", - "play_motion_photo": "Възпроизведи Motion Photo", - "play_or_pause_video": "Възпроизвеждане или пауза на видео", - "play_original_video": "Пусни оригиналното видео", - "play_original_video_setting_description": "Предпочитане на показване на оригиналното видео, вместо транскодирани. Ако формата на оригиналния файл не се поддържа, възпроизвеждането може да бъде неправилно.", - "play_transcoded_video": "Покажи транскодирано видео", - "please_auth_to_access": "Моля, удостовери за достъп", - "port": "Порт", - "preferences_settings_subtitle": "Управление на предпочитанията на приложението", - "preferences_settings_title": "Предпочитания", - "preparing": "Подготовка", - "preset": "Шаблон", - "preview": "Прегледи", - "previous": "Предишно", - "previous_memory": "Предходен спомен", - "previous_or_next_day": "Ден напред/назад", - "previous_or_next_month": "Месец напред/назад", - "previous_or_next_photo": "Предишна или следваща снимка", - "previous_or_next_year": "Година напред/назад", - "primary": "Основно", - "privacy": "Поверителност", - "profile": "Профил", - "profile_drawer_app_logs": "Дневник", - "profile_drawer_client_server_up_to_date": "Клиента и сървъра са обновени", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Режима само за четене е активиран. С дълго натискане върху картиката-аватар на потребителя ще деактивирате само за четене.", - "profile_image_of_user": "Профилна снимка на {user}", - "profile_picture_set": "Профилната снимка е сложена.", - "public_album": "Публичен албум", - "public_share": "Публично споделяне", - "purchase_account_info": "Поддръжник", - "purchase_activated_subtitle": "Благодарим ви, че подкрепяте Immich и софтуера с отворен код", - "purchase_activated_time": "Активиран на {date}", - "purchase_activated_title": "Вашият ключ беше успешно активиран", - "purchase_button_activate": "Активирай", - "purchase_button_buy": "Купи", - "purchase_button_buy_immich": "Купи Immich", - "purchase_button_never_show_again": "Не показвай повече", - "purchase_button_reminder": "Припомни ми след 30 дни", - "purchase_button_remove_key": "Махни ключа", - "purchase_button_select": "Избери", - "purchase_failed_activation": "Неуспешна активация! Моля, проверете имейла си за правилния продуктов ключ!", - "purchase_individual_description_1": "За индивидуален потребител", - "purchase_individual_description_2": "Статус на поддръжник", - "purchase_individual_title": "Индивидуален", - "purchase_input_suggestion": "Имате продуктов ключ? Въведете ключа по-долу", - "purchase_license_subtitle": "Закупете Immich, за да подкрепите продължаващото развитие на услугата", - "purchase_lifetime_description": "Покупка за цял живот", - "purchase_option_title": "ОПЦИИ ЗА ЗАКУПУВАНЕ", - "purchase_panel_info_1": "Създаването на Immich отнема много време и усилия, и имаме инженери на пълно работно време, които работят по него, за да го направим възможно най-добро. Нашата мисия е софтуерът с отворен код и етичните бизнес практики да се превърнат в устойчив източник на доходи за разработчиците и да създадем екосистема, която уважава личната неприкосновеност и предлага истински алтернативи на експлоататорските облачни услуги.", - "purchase_panel_info_2": "Тъй като сме обещали да не добавяме платени функции, тази покупка няма да ви предостави допълнителни функции в Immich. Ние разчитаме на потребители като вас, за да подкрепите продължаване на развитието на Immich.", - "purchase_panel_title": "Поддържайте проекта", - "purchase_per_server": "на сървър", - "purchase_per_user": "на потребител", - "purchase_remove_product_key": "Премахни Продуктовият Ключ", - "purchase_remove_product_key_prompt": "Сигурни ли сте, че искате да премахнете продуктовия ключ?", - "purchase_remove_server_product_key": "Премахни продуктовия ключ на сървъра", - "purchase_remove_server_product_key_prompt": "Сигурни ли сте, че искате да премахнете продуктовия ключ на сървъра?", - "purchase_server_description_1": "За целият сървър", - "purchase_server_description_2": "Статус на поддръжник", - "purchase_server_title": "Сървър", - "purchase_settings_server_activated": "Продуктовият ключ на сървъра се управлява от администратора", - "query_asset_id": "Buscar item per ID", - "queue_status": "В опашка {count} от {total}", - "rate_asset": "Задаване на рейтинг", - "rating": "Оценка със звезди", - "rating_clear": "Изчисти оценката", - "rating_count": "{count, plural, one {# звезда} other {# звезди}}", - "rating_description": "Покажи EXIF оценката в панела с информация", - "rating_set": "Зададен е рейтинг {rating, plural, one {# звезда} other {# звезди}}", - "reaction_options": "Избор на реакция", - "read_changelog": "Прочети промените", - "readonly_mode_disabled": "Режима само за четене е деактивиран", - "readonly_mode_enabled": "Режима само за четене е активиран", - "ready_for_upload": "Готово за качване", - "reassign": "Преназначаване", - "reassigned_assets_to_existing_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на {name, select, null {съществуващ човек} other {{name}}}", - "reassigned_assets_to_new_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на нов човек", - "reassing_hint": "Назначи избраните елементи на съществуващо лице", - "recent": "Скорошни", - "recent-albums": "Скорошни Албуми", - "recent_searches": "Скорошни търсения", - "recently_added": "Наскоро добавено", - "recently_added_page_title": "Наскоро добавено", - "recently_taken": "Наскоро снимано", - "recently_taken_page_title": "Наскоро снимано", - "refresh": "Опресняване", - "refresh_encoded_videos": "Опресни кодирани видеоклипове", - "refresh_faces": "Опресни лицата", - "refresh_metadata": "Опресни метаданните", - "refresh_thumbnails": "Опресни миниатюрите", - "refreshed": "Опреснено", - "refreshes_every_file": "Прочита отново всички съществуващи и нови файлове", - "refreshing_encoded_video": "Опресняване на кодираното видео", - "refreshing_faces": "Опресняване на лицата", - "refreshing_metadata": "Опресняване на метаданните", - "regenerating_thumbnails": "Пресъздаване на миниатюрите", - "remote": "На сървъра", - "remote_assets": "Обекти на сървъра", - "remote_media_summary": "Обобщение на файловете на сървъра", - "remove": "Премахни", - "remove_assets_album_confirmation": "Сигурни ли сте, че искате да премахнете {count, plural, one {# елемент} other {# елемента}} от албума?", - "remove_assets_shared_link_confirmation": "Сигурни ли сте, че искате да премахнете {count, plural, one {# елемент} other {# елемента}} от този споеделен линк?", - "remove_assets_title": "Премахване на елементите?", - "remove_custom_date_range": "Премахни зададения диапазон от дати", - "remove_deleted_assets": "Премахни Изтритите Елементи", - "remove_from_album": "Премахни от албума", - "remove_from_album_action_prompt": "{count} са премахнати от албума", - "remove_from_favorites": "Премахни от Любими", - "remove_from_lock_folder_action_prompt": "{count} са премахнати от заключената папка", - "remove_from_locked_folder": "Махни от заключената папка", - "remove_from_locked_folder_confirmation": "Сигурни ли си, че искате тези снимки и видеа да бъдат извадени от заключената папка? Те ще бъдат видими в библиотеката.", - "remove_from_shared_link": "Премахни от споделения линк", - "remove_memory": "Премахни спомените", - "remove_photo_from_memory": "Премахни снимките от спомените", - "remove_tag": "Премахни маркера", - "remove_url": "Премахни URL", - "remove_user": "Премахни потребителя", - "removed_api_key": "Премахат API ключ: {name}", - "removed_from_archive": "Премахни от архива", - "removed_from_favorites": "Премахнато от любими", - "removed_from_favorites_count": "{count, plural, other {Премахнати #}} от Любими", - "removed_memory": "Премахнат спомен", - "removed_photo_from_memory": "Премахната снимка от спомен", - "removed_tagged_assets": "Премахнат е етикетът от {count, plural, one {# елемент} other {# елемента}}", - "rename": "Преименувай", - "repair": "Поправи", - "repair_no_results_message": "Неследените и липсващи файлове ще се покажат тук", - "replace_with_upload": "Смени с каченото", - "repository": "Хранилище", - "require_password": "Изискай парола", - "require_user_to_change_password_on_first_login": "Изисквай потребителят да промени паролата си при първото влизане", - "rescan": "Пресканиране", - "reset": "Нулиране", - "reset_password": "Нулиране на паролата", - "reset_people_visibility": "Нулиране на видимостта на хората", - "reset_pin_code": "Нулирай PIN кода", - "reset_pin_code_description": "Ако сте си забравили ПИН кода, може да се обърнете към администратора на сървъра за да го нулира", - "reset_pin_code_success": "Успешно нулиран ПИН код", - "reset_pin_code_with_password": "С вашата парола можете винаги да нулирате своя ПИН код", - "reset_sqlite": "Нулиране на базата данни SQLite", - "reset_sqlite_confirmation": "Наистина ли искате да нулирате базата данни SQLite? Ще трябва да излезете от системата и да се впишете отново за нова синхронизация на данните", - "reset_sqlite_success": "Успешно нулиране на базата данни SQLite", - "reset_to_default": "Връщане на фабрични настройки", - "resolution": "Резолюция", - "resolve_duplicates": "Реши дубликатите", - "resolved_all_duplicates": "Всички дубликати са решени", - "restore": "Възстановяване", - "restore_all": "Възстанови всички", - "restore_trash_action_prompt": "{count} възстановени от коша", - "restore_user": "Възстанови потребител", - "restored_asset": "Възстановен елемент", - "resume": "Продължаване", - "resume_paused_jobs": "Продължи изпълнението на {count, plural, one {# задача} other {# задачи}}", - "retry_upload": "Опитай качването отново", - "review_duplicates": "Разгледай дубликатите", - "review_large_files": "Преглед на големи файлове", - "role": "Роля", - "role_editor": "Редактор", - "role_viewer": "Зрител", - "running": "Изпълняване", - "save": "Запази", - "save_to_gallery": "Запази в галерията", - "saved": "Записано", - "saved_api_key": "Запазен API Key", - "saved_profile": "Запазен профил", - "saved_settings": "Запазени настройки", - "say_something": "Кажи нещо", - "scaffold_body_error_occurred": "Възникна грешка", - "scan": "Сканиранe", - "scan_all_libraries": "Сканирай всички библиотеки", - "scan_library": "Сканирай", - "scan_settings": "Сканирай настройките", - "scanning": "Сканиране", - "scanning_for_album": "Сканирай за албум...", - "search": "Търсене", - "search_albums": "Търси албуми", - "search_by_context": "Търси по контекст", - "search_by_description": "Търси по описание", - "search_by_description_example": "Разходка в Сапа", - "search_by_filename": "Търси по име на файла или разширение", - "search_by_filename_example": "например IMG_1234.JPG или PNG", - "search_by_ocr": "Търсене на текст", - "search_by_ocr_example": "Lattе", - "search_camera_lens_model": "Търсене на модел на обектива...", - "search_camera_make": "Търси производител на камерата...", - "search_camera_model": "Търси модел на камерата...", - "search_city": "Търси град...", - "search_country": "Търси държава...", - "search_filter_apply": "Приложи филтъра", - "search_filter_camera_title": "Избери тип на камерата", - "search_filter_date": "Дата", - "search_filter_date_interval": "{start} до {end}", - "search_filter_date_title": "Избери период между дати", - "search_filter_display_option_not_in_album": "Не в албум", - "search_filter_display_options": "Опции за показване", - "search_filter_filename": "Търсене по име на файла", - "search_filter_location": "Място", - "search_filter_location_title": "Избери място", - "search_filter_media_type": "Тип на файла", - "search_filter_media_type_title": "Избери тип на файла", - "search_filter_ocr": "Търсене нa текст", - "search_filter_people_title": "Избери хора", - "search_filter_star_rating": "Класация със звезди", - "search_for": "Търси за", - "search_for_existing_person": "Търси съществуващ човек", - "search_no_more_result": "Няма други резултати", - "search_no_people": "Няма хора", - "search_no_people_named": "Няма хора на име \"{name}\"", - "search_no_result": "Няма намерени резултати, опитай с друг термин или комбинация за търсене", - "search_options": "Опции за търсене", - "search_page_categories": "Категории", - "search_page_motion_photos": "Снимки с движение", - "search_page_no_objects": "Няма информация за обектите", - "search_page_no_places": "Няма информация за местата", - "search_page_screenshots": "Екранни снимки", - "search_page_search_photos_videos": "Търсене в снимките и видеата", - "search_page_selfies": "Селфита", - "search_page_things": "Предмети", - "search_page_view_all_button": "Виж всички", - "search_page_your_activity": "Вашите действия", - "search_page_your_map": "Вашата карта", - "search_people": "Търсете на хора", - "search_places": "Търсене на места", - "search_rating": "Търси по рейтинг…", - "search_result_page_new_search_hint": "Ново търсене", - "search_settings": "Търсене на настройки", - "search_state": "Търсене на щат...", - "search_suggestion_list_smart_search_hint_1": "Умното търсене е включено по подразбиране, за търсене в метаданните използвай специален синтакс ", - "search_suggestion_list_smart_search_hint_2": "m:вашия-термин-за-търсене", - "search_tags": "Търсене на етикети...", - "search_timezone": "Търсене на часова зона...", - "search_type": "Тип на търсене", - "search_your_photos": "Търсете вашите снимки", - "searching_locales": "Търсене на локали...", - "second": "Секунда", - "see_all_people": "Вижте всички хора", - "select": "Избери", - "select_album": "Изберете албум", - "select_album_cover": "Изберете обложка на албум", - "select_albums": "Изберете албуми", - "select_all": "Изберете всички", - "select_all_duplicates": "Избери всички дубликати", - "select_all_in": "Избери всички от групата {group}", - "select_avatar_color": "Изберете цвят на аватара", - "select_count": "{count, plural, one {Избран е #} other {Избрани са #}}", - "select_cutoff_date": "Изберете крайна дата", - "select_face": "Изберете лице", - "select_featured_photo": "Избери представителна снимка", - "select_from_computer": "Изберете от компютъра", - "select_keep_all": "Избери \"задръж всички\"", - "select_library_owner": "Изберете собственик на библиотека", - "select_new_face": "Изберете ново лице", - "select_people": "Изберете лица", - "select_person": "Изберете човек", - "select_person_to_tag": "Избери лице, което да маркираш", - "select_photos": "Изберете снимки", - "select_trash_all": "Изберете всичко за кошчето", - "select_user_for_sharing_page_err_album": "Създаването на албум не бе успешно", - "selected": "Избрано", - "selected_count": "{count, plural, other {# избрани}}", - "selected_gps_coordinates": "Избрани GPS координати", - "send_message": "Изпратете съобщение", - "send_welcome_email": "Изпратете имейл за добре дошли", - "server_endpoint": "Адрес на сървъра", - "server_info_box_app_version": "Врсия на приложението", - "server_info_box_server_url": "URL на сървъра", - "server_offline": "Сървър офлайн", - "server_online": "Сървър онлайн", - "server_privacy": "Поверителност на сървъра", - "server_restarting_description": "Страницата ще се обнови всеки момент.", - "server_restarting_title": "Рестартиране на сървъра", - "server_stats": "Статус на сървъра", - "server_update_available": "Налична е нова версия за сървъра", - "server_version": "Версия на сървъра", - "set": "Задай", - "set_as_album_cover": "Задаване като обложка на албум", - "set_as_featured_photo": "Задай като представителна снимка", - "set_as_profile_picture": "Задаване като профилна снимка", - "set_date_of_birth": "Задайте дата на раждане", - "set_profile_picture": "Задайте профилна снимка", - "set_slideshow_to_fullscreen": "Задайте Слайдшоу на цял екран", - "set_stack_primary_asset": "Задай като основни обекти", - "setting_image_viewer_help": "При показване на обект първо се зарежда миниатюра, после изображение със средно качество (ако е разрешено) и накрая оригинала (ако е разрешено).", - "setting_image_viewer_original_subtitle": "Разреши за да се зарежда оригиналното изображение в пълен размер (голям!). Забрани за да се намали обема на данните (по мрежата и в кеша на устройството).", - "setting_image_viewer_original_title": "Зареждане на оригинално изображение", - "setting_image_viewer_preview_subtitle": "Разреши за да се зарежда изображение със средно качество (и размер). Забрани за да се зарежда само оригинал и миниатюра.", - "setting_image_viewer_preview_title": "Зареждане на миниатюра", - "setting_image_viewer_title": "Изображения", - "setting_languages_apply": "Приложи", - "setting_languages_subtitle": "Смени езика на приложението", - "setting_notifications_notify_failures_grace_period": "Период без известия за грешка при архивиране във фонов режим: {duration}", - "setting_notifications_notify_hours": "{count} часа", - "setting_notifications_notify_immediately": "веднага", - "setting_notifications_notify_minutes": "{count} минути", - "setting_notifications_notify_never": "никога", - "setting_notifications_notify_seconds": "{count} секунди", - "setting_notifications_single_progress_subtitle": "Подобна информация за напредъка на зареждане за всеки обект", - "setting_notifications_single_progress_title": "Показване на напредъка на архивиране във фонов режим", - "setting_notifications_subtitle": "Настройка на известията", - "setting_notifications_total_progress_subtitle": "Общ напредък на зареждане (готово/всички обекти)", - "setting_notifications_total_progress_title": "Показване на общия напредък на архивиране във фонов режим", - "setting_video_viewer_auto_play_subtitle": "Автоматично започни възпроизвеждане на видео при отваряне", - "setting_video_viewer_auto_play_title": "Автоматично възпроизвеждане на видео", - "setting_video_viewer_looping_title": "Циклично", - "setting_video_viewer_original_video_subtitle": "При показване на видео от сървъра показвай оригиналния файл, дори и да има транскодирана версия. Може да използва буфериране. Локално наличните видеа се показват винаги в оригинал, независимо от тази настройка.", - "setting_video_viewer_original_video_title": "Само оригинално видео", - "settings": "Настройки", - "settings_require_restart": "Моля, за да се приложи настройката рестартирай Immich", - "settings_saved": "Настройките са запазени", - "setup_pin_code": "Задай PIN код", - "share": "Споделяне", - "share_action_prompt": "{count} споделени обекта", - "share_add_photos": "Добави снимки", - "share_assets_selected": "{count} избрани", - "share_dialog_preparing": "Подготовка...", - "share_link": "Връзка за споделяне", - "shared": "Споделено", - "shared_album_activities_input_disable": "Коментарите са изключени", - "shared_album_activity_remove_content": "Искате ли да изтриете тази активност?", - "shared_album_activity_remove_title": "Изтрий", - "shared_album_section_people_action_error": "Грешка при напускане/премахване от албума", - "shared_album_section_people_action_leave": "Премахване на потребител от албума", - "shared_album_section_people_action_remove_user": "Премахване на потребител от албума", - "shared_album_section_people_title": "ХОРА", - "shared_by": "Споделено от", - "shared_by_user": "Споделено от {user}", - "shared_by_you": "Споделено от вас", - "shared_from_partner": "Снимки от {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Заредено", - "shared_link_app_bar_title": "Споделени връзки", - "shared_link_clipboard_copied_massage": "Копирано в клипборда", - "shared_link_clipboard_text": "Връзка: {link}\nПарола: {password}", - "shared_link_create_error": "Грешка при създаване на споделена връзка", - "shared_link_custom_url_description": "Достъпете споделения линк с персонализиран URL адрес", - "shared_link_edit_description_hint": "Въведи описание на споделеното", - "shared_link_edit_expire_after_option_day": "1 ден", - "shared_link_edit_expire_after_option_days": "{count} дни", - "shared_link_edit_expire_after_option_hour": "1час", - "shared_link_edit_expire_after_option_hours": "{count} часа", - "shared_link_edit_expire_after_option_minute": "1 минута", - "shared_link_edit_expire_after_option_minutes": "{count} минути", - "shared_link_edit_expire_after_option_months": "{count} месеца", - "shared_link_edit_expire_after_option_year": "{count} години", - "shared_link_edit_password_hint": "Въведи парола за достъп до споделен ресурс", - "shared_link_edit_submit_button": "Обнови връзката", - "shared_link_error_server_url_fetch": "Не може да се извлече URL адресът на сървъра", - "shared_link_expires_day": "Изтича след {count} ден", - "shared_link_expires_days": "Изтича след {count} дни", - "shared_link_expires_hour": "Изтича след {count} час", - "shared_link_expires_hours": "Изтича след {count} часа", - "shared_link_expires_minute": "Изтича след {count} минута", - "shared_link_expires_minutes": "Изтича след {count} минути", - "shared_link_expires_never": "Неограничено", - "shared_link_expires_second": "Изтича след {count} секунда", - "shared_link_expires_seconds": "Изтича след {count} секунди", - "shared_link_individual_shared": "Индивидуално споделено", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Управление на споделените връзки", - "shared_link_options": "Опции за споделена връзка", - "shared_link_password_description": "Изискване на парола за достъп до споделения линк", - "shared_links": "Споделени връзки", - "shared_links_description": "Сподели снимки и видеа с линк", - "shared_photos_and_videos_count": "{assetCount, plural, other {# споделени снимки и видеа.}}", - "shared_with_me": "Споделено с мен", - "shared_with_partner": "Споделено с {partner}", - "sharing": "Споделени", - "sharing_enter_password": "Моля, въведете паролата, за да видите тази страница.", - "sharing_page_album": "Споделени албуми", - "sharing_page_description": "Създайте споделени албуми за да споделите снимки и видеа с хора от вашата мрежа.", - "sharing_page_empty_list": "ПРАЗЕН СПИСЪК", - "sharing_sidebar_description": "Покажи връзка към Споделяне в страничната лента", - "sharing_silver_appbar_create_shared_album": "Нов споделен албум", - "sharing_silver_appbar_share_partner": "Сподели с партньор", - "shift_to_permanent_delete": "Натиснете ⇧, за да изтриете завинаги елемента", - "show_album_options": "Показване опции за албум", - "show_albums": "Покажи албуми", - "show_all_people": "Покажи всички хора", - "show_and_hide_people": "Показване и скриване на хора", - "show_file_location": "Покажи местоположението на файла", - "show_gallery": "Покажи галерия", - "show_hidden_people": "Показване на скритите хора", - "show_in_timeline": "Показване във времевата линия", - "show_in_timeline_setting_description": "Показване на снимки и видеа от този потребител във времевата линия", - "show_keyboard_shortcuts": "Покажи клавишни комбинации", - "show_metadata": "Покажи метаданни", - "show_or_hide_info": "Покажи или скрий информацията", - "show_password": "Покажи паролата", - "show_person_options": "Показване на опции за лица", - "show_progress_bar": "Показване на прогрес бара", - "show_schema": "Покажи схема", - "show_search_options": "Показване на опциите за търсене", - "show_shared_links": "Покажи споделени линкове", - "show_slideshow_transition": "Покажи прехода на слайдшоуто", - "show_supporter_badge": "Значка поддръжник", - "show_supporter_badge_description": "Покажи значка поддръжник", - "show_text_recognition": "Покажи разпознатия текст", - "show_text_search_menu": "Покажи менюто за търсене на текст", - "shuffle": "Разбъркване", - "sidebar": "Странична лента", - "sidebar_display_description": "Показване на връзка към изгледа в страничната лента", - "sign_out": "Отписване", - "sign_up": "Запиши се", - "size": "Размер", - "skip_to_content": "Премини към съдържанието", - "skip_to_folders": "Премини към папките", - "skip_to_tags": "Премини към етикетите", - "slideshow": "Слайдшоу", - "slideshow_repeat": "Повтаряй слайдшоуто", - "slideshow_repeat_description": "Започвай отново, когато слайдшоуто приключи", - "slideshow_settings": "Настройки за слайдшоу", - "sort_albums_by": "Сортиране на албуми по...", - "sort_created": "Дата на създаване", - "sort_items": "Брой елементи", - "sort_modified": "Дата на промяна", - "sort_newest": "Най-нови снимки", - "sort_oldest": "Най-старата снимка", - "sort_people_by_similarity": "Сортиране на хора по прилика", - "sort_recent": "Най-новата снимка", - "sort_title": "Заглавие", - "source": "Код", - "stack": "Събери", - "stack_action_prompt": "{count} са групирани", - "stack_duplicates": "Подреждане на дубликати", - "stack_select_one_photo": "Избери една главна снимка за събраните снимки", - "stack_selected_photos": "Подреждане на избрани снимки", - "stacked_assets_count": "Събрани {count, plural, one {# елемент} other {# елементи}}", - "stacktrace": "Следа на събраните", - "start": "Старт", - "start_date": "Начална дата", - "start_date_before_end_date": "Началната дата трябва да бъде преди крайната дата", - "state": "Щат", - "status": "Статус", - "stop_casting": "Спри предаването", - "stop_motion_photo": "Снимка със стоп кадър", - "stop_photo_sharing": "Да спра ли споделянето на вашите снимки?", - "stop_photo_sharing_description": "{partner} вече няма достъп до вашите снимки.", - "stop_sharing_photos_with_user": "Прекратете споделянето на снимки с този потребител", - "storage": "Пространство на хранилището", - "storage_label": "Наименование на хранилището", - "storage_quota": "Квота на хранилището", - "storage_usage": "Използвани {used} от {available}", - "submit": "Изпращане", - "success": "Успешно", - "suggestions": "Предложения", - "sunrise_on_the_beach": "Изгрев на плажа", - "support": "Поддръжка", - "support_and_feedback": "Поддръжка и обратна връзка", - "support_third_party_description": "Вашата инсталация на Immich е пакетирана от трета страна. Проблемите, които изпитвате, може да са причинени от този пакет, затова моля, първо подавайте проблемите си към тях чрез линковете по-долу.", - "swap_merge_direction": "Размяна посоката на сливане", - "sync": "Синхронизиране", - "sync_albums": "Синхронизиране на албуми", - "sync_albums_manual_subtitle": "Синхронизирай всички заредени видеа и снимки в избраните архивни албуми", - "sync_local": "Локална синхронизация", - "sync_remote": "Синхронизация със сървъра", - "sync_status": "Състояние на синхронизацията", - "sync_status_subtitle": "Преглед и управление на системата за синхронизация", - "sync_upload_album_setting_subtitle": "Създавайте и зареждайте снимки и видеа в избрани албуми в Immich", - "tag": "Таг", - "tag_assets": "Тагни елементи", - "tag_created": "Създаден етикет: {tag}", - "tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове", - "tag_not_found_question": "Не можете да намерите етикет? Създайте такъв тук", - "tag_people": "Отбележи Хора", - "tag_updated": "Обновен етикет: {tag}", - "tagged_assets": "Тагнати {count, plural, one {# елемент} other {# елементи}}", - "tags": "Етикет", - "tap_to_run_job": "Докоснете, за да стартирате задачата", - "template": "Шаблон", - "text_recognition": "Разпознаване на текст", - "theme": "Тема", - "theme_selection": "Избор на тема", - "theme_selection_description": "Автоматично задаване на светла или тъмна тема спрямо системните предпочитания на браузъра ви", - "theme_setting_asset_list_storage_indicator_title": "Показвай индикатор за хранилището в заглавията на обектите", - "theme_setting_asset_list_tiles_per_row_title": "Брой обекти на ред ({count})", - "theme_setting_colorful_interface_subtitle": "Нанеси основен цвят върху фоновите повърхности.", - "theme_setting_colorful_interface_title": "Цветове на интерфейса", - "theme_setting_image_viewer_quality_subtitle": "Регулиране на качеството на програмата за преглед на детайлни изображения", - "theme_setting_image_viewer_quality_title": "Качество на прегледа на изображения", - "theme_setting_primary_color_subtitle": "Избери цвят за основните действия и акценти.", - "theme_setting_primary_color_title": "Основен цвят", - "theme_setting_system_primary_color_title": "Използвай от системата", - "theme_setting_system_theme_switch": "Автоматично (Според системната настройка)", - "theme_setting_theme_subtitle": "Задай настройки на цветовата тема на приложението", - "theme_setting_three_stage_loading_subtitle": "Три-степенното зареждане може да увеличи производителността, но ще увеличи значително и мрежовия трафик", - "theme_setting_three_stage_loading_title": "Включи три-степенно зареждане", - "then": "След това", - "they_will_be_merged_together": "Те ще бъдат обединени", - "third_party_resources": "Ресурси от трети страни", - "time": "Време", - "time_based_memories": "Спомени, базирани на времето", - "time_based_memories_duration": "Продължителност в секунди за показване на всяка картина.", - "timeline": "Хронология", - "timezone": "Часова зона", - "to_archive": "Архивирай", - "to_change_password": "Промяна на паролата", - "to_favorite": "Любим", - "to_login": "Вписване", - "to_multi_select": "за избор на няколко", - "to_parent": "Отиди към родителския елемент", - "to_select": "за избор", - "to_trash": "Кошче", - "toggle_settings": "Превключване на настройките", - "toggle_theme_description": "Превключване на темата", - "total": "Общо", - "total_usage": "Общо използвано", - "trash": "Кошче", - "trash_action_prompt": "{count} са преместени в коша", - "trash_all": "Изхвърли всички", - "trash_count": "В Кошчето {count, number}", - "trash_delete_asset": "Вкарай в Кошчето/Изтрий елемент", - "trash_emptied": "Коша е изпразнен", - "trash_no_results_message": "Изтритите снимки и видеоклипове ще се показват тук.", - "trash_page_delete_all": "Изтрий всичко", - "trash_page_empty_trash_dialog_content": "Искате ли да изпразня коша? Тези обекти ще бъдат изтрити завинаги от Immich", - "trash_page_info": "Обектите в коша ще бъдат изтривани завинаги след {days} дни", - "trash_page_no_assets": "Коша е празен", - "trash_page_restore_all": "Възстановяване на всички", - "trash_page_select_assets_btn": "Избери обекти", - "trash_page_title": "В коша ({count})", - "trashed_items_will_be_permanently_deleted_after": "Изхвърлените в кошчето елементи ще бъдат изтрити за постоянно след {days, plural, one {# ден} other {# дни}}.", - "trigger": "Тригер", - "trigger_asset_uploaded": "Обектът е зареден", - "trigger_asset_uploaded_description": "Сработва при зареждане на нов обект", - "trigger_description": "Събитие, което стартира работния процес", - "trigger_person_recognized": "Разпознато е лице", - "trigger_person_recognized_description": "Сработва при разпознаване на лице", - "trigger_type": "Тип на тригера", - "troubleshoot": "Отстраняване на проблеми", - "type": "Тип", - "unable_to_change_pin_code": "Невъзможна промяна на PIN кода", - "unable_to_check_version": "Невъзможна проверка на версията на приложението или сървъра", - "unable_to_setup_pin_code": "Неуспешно задаване на PIN кода", - "unarchive": "Разархивирай", - "unarchive_action_prompt": "{count} са премахнати от Архива", - "unarchived_count": "{count, plural, other {Неархивирани #}}", - "undo": "Отмени", - "unfavorite": "Премахване от любимите", - "unfavorite_action_prompt": "{count} са премахнати от Любими", - "unhide_person": "Покажи отново човека", - "unknown": "Неизвестно", - "unknown_country": "Непозната Държава", - "unknown_date": "Неизвестна дата", - "unknown_year": "Неизвестна година", - "unlimited": "Неограничено", - "unlink_motion_video": "Премахни връзката с видео", - "unlink_oauth": "Премахни OAuth", - "unlinked_oauth_account": "Премахни OAuth акаунт", - "unmute_memories": "Включване на звука на спомените", - "unnamed_album": "Албум без име", - "unnamed_album_delete_confirmation": "Сигурни ли сте, че искате да изтриете този албум?", - "unnamed_share": "Споделяне без име", - "unsaved_change": "Незапазена промяна", - "unselect_all": "Деселектирайте всички", - "unselect_all_duplicates": "От маркирай всички дубликати", - "unselect_all_in": "Премахни избора на всички от групата {group}", - "unstack": "Разкачи", - "unstack_action_prompt": "{count} са разгрупирани", - "unstacked_assets_count": "Разкачени {count, plural, one {# елемент} other {# елементи}}", - "unsupported_field_type": "Типа на полето не се поддържа", - "untagged": "Немаркирани", - "untitled_workflow": "Работен процес без име", - "up_next": "Следващ", - "update_location_action_prompt": "Обнови координатите на {count} избрани обекта с:", - "updated_at": "Обновено", - "updated_password": "Паролата е променена", - "upload": "Качване", - "upload_concurrency": "Успоредни качвания", - "upload_details": "Детайли за качването", - "upload_dialog_info": "Искате ли да архивирате на сървъра избраните обекти?", - "upload_dialog_title": "Качи обект", - "upload_error_with_count": "Грешка при зареждане на {count, plural, one {# обект} other {# обекта}}", - "upload_errors": "Качването е завъшено с {count, plural, one {# грешка} other {# грешки}}, обновете страницата за да видите новите елементи.", - "upload_finished": "Качването завърши", - "upload_progress": "Остават {remaining, number} - Обработени {processed, number}/{total, number}", - "upload_skipped_duplicates": "Прескочени {count, plural, one {# дублиран елемент} other {# дублирани елементи}}", - "upload_status_duplicates": "Дубликати", - "upload_status_errors": "Грешки", - "upload_status_uploaded": "Качено", - "upload_success": "Качването е успешно, опреснете страницата, за да видите новите файлове.", - "upload_to_immich": "Казване в Immich ({count})", - "uploading": "Качваме", - "uploading_media": "Качване на медийни файлове", - "url": "URL", - "usage": "Потребление", - "use_biometric": "Използвай биометрия", - "use_current_connection": "Използвай текущата връзка", - "use_custom_date_range": "Използвайте собствен диапазон от дати вместо това", - "user": "Потребител", - "user_has_been_deleted": "Този потребител е премахнат.", - "user_id": "Потребител ИД", - "user_liked": "{user} хареса {type, select, photo {тази снимка} video {това видео} asset {този елемент} other {}}", - "user_pin_code_settings": "PIN код", - "user_pin_code_settings_description": "Управлявайте настройките на PIN кода", - "user_privacy": "Поверителност на потребителите", - "user_purchase_settings": "Покупка", - "user_purchase_settings_description": "Управлявайте покупката си", - "user_role_set": "Задай {user} като {role}", - "user_usage_detail": "Подробности за използването на потребителя", - "user_usage_stats": "Статистика за използването на акаунта", - "user_usage_stats_description": "Преглед на статистиката за използването на акаунта", - "username": "Потребителско име", - "users": "Потребители", - "users_added_to_album_count": "{count, plural, one {Добавен е # потребител} other {Добавени са # потребителя}} на албума", - "utilities": "Инструменти", - "validate": "Валидиране", - "validate_endpoint_error": "Моля, въведи правилен URL", - "validation_error": "Грешка при валидиране", - "variables": "Променливи", - "version": "Версия", - "version_announcement_closing": "Твой приятел, Алекс", - "version_announcement_message": "Здравейте! Налична е нова версия на Immich. Моля, отделете време да прочетете бележките за изданието, за да се уверите, че настройката ви е актуална и да предотвратите всякакви неправилни конфигурации, особено ако използвате WatchTower или друг механизъм за автоматично обновяване на вашата инстанция на Immich.", - "version_history": "История на версиите", - "version_history_item": "Инсталирана {version} на {date}", - "video": "Видеоклип", - "video_hover_setting": "Възпроизвеждане на видеоклип при посочване с мишката", - "video_hover_setting_description": "Възпроизвеждане на видеоклипа, когато мишката се движи над елемента. Дори когато е деактивирано, възпроизвеждането може да бъде стартирано чрез задържане на курсора на мишката върху иконата за възпроизвеждане.", - "videos": "Видеоклипове", - "videos_count": "{count, plural, one {# Видео} other {# Видеа}}", - "videos_only": "Само видеа", - "view": "Преглед", - "view_album": "Разгледай албума", - "view_all": "Преглед на всички", - "view_all_users": "Преглед на всички потребители", - "view_asset_owners": "Преглед на собствениците на активи", - "view_details": "Подробности за изгледа", - "view_in_timeline": "Покажи във времева линия", - "view_link": "Преглед на връзката", - "view_links": "Преглед на връзките", - "view_name": "Прегледай", - "view_next_asset": "Преглед на следващия файл", - "view_previous_asset": "Преглед на предишния файл", - "view_qr_code": "Виж QR кода", - "view_similar_photos": "Виж подобни снимки", - "view_stack": "Покажи в стек", - "view_user": "Виж потребителя", - "viewer_remove_from_stack": "Премахване от опашката", - "viewer_stack_use_as_main_asset": "Използвай като основен", - "viewer_unstack": "Премахни от опашката", - "visibility_changed": "Видимостта е променена за {count, plural, one {# човек} other {# човека}}", - "visual": "Визуален", - "visual_builder": "Визуален конструктор", - "waiting": "в изчакване", - "waiting_count": "В изчакване: {count}", - "warning": "Внимание", - "week": "Седмица", - "welcome": "Добре дошли", - "welcome_to_immich": "Добре дошли в Immich", - "width": "Ширинa", - "wifi_name": "Wi-Fi мрежа", - "workflow_delete_prompt": "Наистина ли искате да изтриете този работен процес?", - "workflow_deleted": "Работния процес е изтрит", - "workflow_description": "Описание на работния процес", - "workflow_info": "Информация за работния процес", - "workflow_json": "JSON на работния процес", - "workflow_json_help": "Редактиране на конфигурацията на работния процес в JSON формат. Промените ще бъдат синхронизирани с визуалния конструктор.", - "workflow_name": "Име на работния процес", - "workflow_navigation_prompt": "Наистина ли искате да излезете без да съхраните промените?", - "workflow_summary": "Обобщение за работния процес", - "workflow_update_success": "Работният процес е успешно обновен", - "workflow_updated": "Работният процес е обновен", - "workflows": "Работни процеси", - "workflows_help_text": "Работните процеси автоматизират действията с вашите обекти чрез тригери и филтри", - "wrong_pin_code": "Грешен PIN код", - "year": "Година", - "years_ago": "преди {years, plural, one {# година} other {# години}}", - "yes": "Да", - "you_dont_have_any_shared_links": "Нямате споделени връзки", - "your_wifi_name": "Вашата Wi-Fi мрежа", - "zero_to_clear_rating": "натиснете 0, за да премахнете рейтинга", - "zoom_image": "Увеличаване на изображението", - "zoom_to_bounds": "Приближи до събиране в границите" -} +{} diff --git a/i18n/bi.json b/i18n/bi.json index c5c9edbbb1..0967ef424b 100644 --- a/i18n/bi.json +++ b/i18n/bi.json @@ -1,39 +1 @@ -{ - "about": "abaot", - "account": "Akaont", - "account_settings": "Seting blo Akaont", - "acknowledge": "Akcept", - "active": "Stap Mekem", - "activity": "Wanem hemi Mekem", - "activity_changed": "WAnem hemi Mekem hemi", - "add": "Ad", - "add_a_description": "Putem Description blo hem", - "add_a_location": "Putem place blo hem", - "add_a_name": "Putem nam blo hem", - "add_a_title": "Putem wan name blo hem", - "add_exclusion_pattern": "Putem wan paten wae hemi karem aot", - "add_location": "Putem wan place blo hem", - "add_more_users": "Putem mor man", - "readonly_mode_enabled": "Mod blo yu no save janjem i on", - "reassigned_assets_to_new_person": "Janjem{count, plural, one {# asset} other {# assets}} blo nu man", - "reassing_hint": "janjem ol sumtin yu bin joos i go blo wan man", - "recent-albums": "album i no old tu mas", - "recent_searches": "lukabout wea i no old tu mas", - "time_based_memories_duration": "hao mus second blo wan wan imij i stap lo scrin.", - "timezone": "taemzon", - "to_change_password": "janjem pasword", - "to_login": "Login", - "to_multi_select": "to jusem mani", - "to_parent": "go lo parent", - "to_select": "to selectem", - "to_trash": "toti", - "toggle_settings": "sho settings", - "total": "Total", - "trash": "Toti", - "trash_action_prompt": "{count} igo lo plaes lo toti", - "trash_all": "Putem ol i go lo toti", - "trash_count": "Toti {count, number}", - "trash_emptied": "basket blo toti i empti nomo", - "trash_no_results_message": "Foto mo video lo basket blo toti yu save lukem lo plaes ia.", - "trash_page_delete_all": "Delete oli ol" -} +{} diff --git a/i18n/bn.json b/i18n/bn.json index 2ed2b39338..0967ef424b 100644 --- a/i18n/bn.json +++ b/i18n/bn.json @@ -1,336 +1 @@ -{ - "about": "সম্পর্কে", - "account": "অ্যাকাউন্ট", - "account_settings": "অ্যাকাউন্ট সেটিংস", - "acknowledge": "স্বীকৃতি", - "action": "কার্য", - "action_common_update": "আপডেট", - "action_description": "বাছাইকৃত সম্পদসমূহের উপর সম্পাদনযোগ্য কাজের তালিকা", - "actions": "কর্ম", - "active": "সচল", - "active_count": "Active: {count}", - "activity": "কার্যকলাপ", - "activity_changed": "একটিভিটি এখন {enabled, select, true {enabled} other {disabled}} আছে", - "add": "যোগ করুন", - "add_a_description": "একটি বিবরণ যোগ করুন", - "add_a_location": "একটি অবস্থান যোগ করুন", - "add_a_name": "একটি নাম যোগ করুন", - "add_a_title": "একটি শিরোনাম যোগ করুন", - "add_action": "কর্ম যোগ করুন", - "add_action_description": "সম্পাদন করার জন্য একটি কাজ যোগ করতে ক্লিক করুন", - "add_assets": "সম্পদ যোগ করুন", - "add_birthday": "জন্মদিন যোগ করুন", - "add_endpoint": "এন্ডপয়েন্ট যোগ করুন", - "add_exclusion_pattern": "বহির্ভূতকরণ নমুনা", - "add_filter": "ফিল্টার যোগ করুন", - "add_filter_description": "একটি ফিল্টার শর্ত যোগ করতে ক্লিক করুন", - "add_location": "অবস্থান যুক্ত করুন", - "add_more_users": "আরো ব্যবহারকারী যুক্ত করুন", - "add_partner": "অংশীদার যোগ করুন", - "add_path": "পাথ যুক্ত করুন", - "add_photos": "ছবি যুক্ত করুন", - "add_tag": "ট্যাগ যুক্ত করুন", - "add_to": "যুক্ত করুন…", - "add_to_album": "এলবাম এ যোগ করুন", - "add_to_album_bottom_sheet_added": "{album} এ যোগ করা হয়েছে", - "add_to_album_bottom_sheet_already_exists": "{album} এ আগে থেকেই আছে", - "add_to_album_bottom_sheet_some_local_assets": "কিছু স্থানীয় ছবি বা ভিডিও অ্যালবামে যোগ করা যায়নি", - "add_to_album_toggle": "{album} - এর নির্বাচন পরিবর্তন করুন", - "add_to_albums": "অ্যালবামে যোগ করুন", - "add_to_albums_count": "অ্যালবামে যোগ করুন ({count})", - "add_to_bottom_bar": "এ যোগ করুন", - "add_to_shared_album": "শেয়ার করা অ্যালবামে যোগ করুন", - "add_upload_to_stack": "আপলোড স্ট্যাকে যোগ করুন", - "add_url": "লিঙ্ক যোগ করুন", - "add_workflow_step": "কাজের ধাপ যোগ করুন", - "added_to_archive": "আর্কাইভ এ যোগ করা হয়েছে", - "added_to_favorites": "ফেভারিটে যোগ করা হয়েছে", - "added_to_favorites_count": "পছন্দের তালিকায় {count, number} যোগ করা হয়েছে", - "admin": { - "add_exclusion_pattern_description": "এক্সক্লুশন প্যাটার্ন যোগ করুন। *, **, এবং ? ব্যবহার করে গ্লোবিং করা সম্ভব। \"Raw\" নামের যেকোনো ডিরেক্টরিতে থাকা সমস্ত ফাইল বাদ দিতে \"**/Raw/**\" ব্যবহার করুন। \".tif\" দিয়ে শেষ হওয়া সমস্ত ফাইল বাদ দিতে \"**/*.tif\" ব্যবহার করুন। একটি সম্পূর্ণ পাথ বাদ দিতে, \"/path/to/ignore/**\" ব্যবহার করুন।", - "admin_user": "এডমিন ইউজার", - "asset_offline_description": "এই বহিরাগত লাইব্রেরি সম্পদটি আর ডিস্কে পাওয়া যাচ্ছে না এবং ট্র্যাশে সরানো হয়েছে। যদি ফাইলটি লাইব্রেরির মধ্যে সরানো হয়ে থাকে, তাহলে নতুন সংশ্লিষ্ট সম্পদের জন্য আপনার টাইমলাইন পরীক্ষা করুন। এই সম্পদটি পুনরুদ্ধার করতে, দয়া করে নিশ্চিত করুন যে নীচের ফাইল পাথটি Immich দ্বারা অ্যাক্সেস করা যেতে পারে এবং লাইব্রেরিটি স্ক্যান করুন।", - "authentication_settings": "প্রমাণীকরণ সেটিংস", - "authentication_settings_description": "পাসওয়ার্ড, OAuth এবং অন্যান্য প্রমাণীকরণ সেটিংস পরিচালনা করুন", - "authentication_settings_disable_all": "আপনি কি নিশ্চিত যে আপনি সমস্ত লগইন পদ্ধতি অক্ষম করতে চান? লগইন সম্পূর্ণরূপে অক্ষম করা হবে।", - "authentication_settings_reenable": "পুনরায় সক্ষম করতে, একটি সার্ভার কমান্ড ব্যবহার করুন।", - "background_task_job": "ব্যাকগ্রাউন্ড টাস্ক", - "backup_database": "ডাটাবেস ডাম্প তৈরি করুন", - "backup_database_enable_description": "ডাটাবেস ডাম্প সক্রিয় করুন", - "backup_keep_last_amount": "আগের ডাম্পের পরিমাণ রাখা হবে", - "backup_onboarding_1_description": "অফসাইট কপি ক্লাউডে অথবা অন্য কোনও ভৌত স্থানে।", - "backup_onboarding_2_description": "বিভিন্ন ডিভাইসে স্থানীয় কপি। এর মধ্যে রয়েছে প্রধান ফাইল এবং স্থানীয়ভাবে সেই ফাইলগুলির ব্যাকআপ।", - "backup_onboarding_3_description": "মূল ফাইল সহ আপনার ডেটার মোট কপি। এর মধ্যে রয়েছে ১টি অফসাইট কপি এবং ২টি স্থানীয় কপি।", - "backup_onboarding_description": "আপনার ডেটা সুরক্ষিত রাখার জন্য একটি 3-2-1 ব্যাকআপ কৌশল সুপারিশ করা হয়। একটি বিস্তৃত ব্যাকআপ সমাধানের জন্য আপনার আপলোড করা ফটো/ভিডিওগুলির কপি এবং Immich ডাটাবেস রাখা উচিত।", - "backup_onboarding_footer": "Immich এর ব্যাকআপ নেওয়ার বিষয়ে আরও তথ্যের জন্য, অনুগ্রহ করে ডকুমেন্টেশন দেখুন।", - "backup_onboarding_parts_title": "৩-২-১ ব্যাকআপের মধ্যে রয়েছে:", - "backup_onboarding_title": "ব্যাকআপ", - "backup_settings": "ডাটাবেস ডাম্প সেটিংস", - "backup_settings_description": "ডাটাবেস ডাম্প সেটিংস পরিচালনা করুন।", - "cleared_jobs": "{job} এর জন্য jobs খালি করা হয়েছে", - "config_set_by_file": "কনফিগ বর্তমানে একটি কনফিগ ফাইল দ্বারা সেট করা আছে", - "confirm_delete_library": "আপনি কি নিশ্চিত যে আপনি {library} লাইব্রেরি মুছে ফেলতে চান?", - "confirm_delete_library_assets": "আপনি কি নিশ্চিত যে আপনি এই লাইব্রেরিটি মুছে ফেলতে চান? এটি Immich থেকে {count, plural, one {# contained asset} other {all # contained asset}} মুছে ফেলবে এবং পূর্বাবস্থায় ফেরানো যাবে না। ফাইলগুলি ডিস্কে থাকবে।", - "confirm_email_below": "নিশ্চিত করতে, নিচে \"{email}\" টাইপ করুন", - "confirm_reprocess_all_faces": "আপনি কি নিশ্চিত যে আপনি সমস্ত মুখ পুনরায় প্রক্রিয়া করতে চান? এটি নামযুক্ত ব্যক্তিদেরও মুছে ফেলবে।", - "confirm_user_password_reset": "আপনি কি নিশ্চিত যে আপনি {user} এর পাসওয়ার্ড রিসেট করতে চান?", - "confirm_user_pin_code_reset": "আপনি কি নিশ্চিত যে আপনি {user} এর পিন কোড রিসেট করতে চান?", - "copy_config_to_clipboard_description": "বর্তমান সিস্টেম কনফিগারেশন একটি JSON অবজেক্ট হিসেবে ক্লিপবোর্ডে কপি করুন", - "create_job": "job তৈরি করুন", - "cron_expression": "ক্রোন এক্সপ্রেশন", - "cron_expression_description": "ক্রোন ফর্ম্যাট ব্যবহার করে স্ক্যানিং ব্যবধান সেট করুন। আরও তথ্যের জন্য দয়া করে দেখুন যেমন Crontab Guru", - "cron_expression_presets": "ক্রোন এক্সপ্রেশন প্রিসেট", - "disable_login": "লগইন অক্ষম করুন", - "duplicate_detection_job_description": "অনুরূপ ছবি সনাক্ত করতে সম্পদগুলিতে মেশিন লার্নিং চালান। স্মার্ট অনুসন্ধানের উপর নির্ভর করে", - "exclusion_pattern_description": "এক্সক্লুশন প্যাটার্ন ব্যবহার করে আপনি আপনার লাইব্রেরি স্ক্যান করার সময় ফাইল এবং ফোল্ডারগুলিকে উপেক্ষা করতে পারবেন। যদি আপনার এমন ফোল্ডার থাকে যেখানে এমন ফাইল থাকে যা আপনি আমদানি করতে চান না, যেমন RAW ফাইল।", - "export_config_as_json_description": "বর্তমান সিস্টেম কনফিগারেশন একটি JSON ফাইল হিসেবে ডাউনলোড করুন", - "external_libraries_page_description": "অ্যাডমিন external লাইব্রেরি পেজ", - "face_detection": "মুখ সনাক্তকরণ", - "face_detection_description": "মেশিন লার্নিং ব্যবহার করে অ্যাসেটে থাকা মুখ/চেহারা গুলি সনাক্ত করুন। ভিডিও গুলির জন্য, শুধুমাত্র থাম্বনেইল বিবেচনা করা হয়। \"রিফ্রেশ\" (পুনরায়) সমস্ত অ্যাসেট প্রক্রিয়া করে। \"রিসেট\" করার মাধ্যমে অতিরিক্তভাবে সমস্ত বর্তমান মুখের ডেটা সাফ করে। \"অনুপস্থিত\" অ্যাসেটগুলিকে সারিবদ্ধ করে যা এখনও প্রক্রিয়া করা হয়নি। সনাক্ত করা মুখগুলিকে ফেসিয়াল রিকগনিশনের জন্য সারিবদ্ধ করা হবে, ফেসিয়াল ডিটেকশন সম্পূর্ণ হওয়ার পরে, বিদ্যমান বা নতুন ব্যক্তিদের মধ্যে গোষ্ঠীবদ্ধ করে।", - "facial_recognition_job_description": "শনাক্ত করা মুখগুলিকে মানুষের মধ্যে গোষ্ঠীভুক্ত/গ্রুপ করুন। মুখ সনাক্তকরণ সম্পূর্ণ হওয়ার পরে এই ধাপটি চলে। \"রিসেট\" (পুনরায়) সমস্ত মুখকে ক্লাস্টার করে। \"অনুপস্থিত/মিসিং\" মুখগুলিকে সারিতে রাখে যেগুলো কোনও ব্যক্তিকে এসাইন/বরাদ্দ করা হয়নি।", - "failed_job_command": "কমান্ড {command} কাজের জন্য ব্যর্থ হয়েছে: {job}", - "force_delete_user_warning": "সতর্কতা: এটি ব্যবহারকারী এবং সমস্ত সম্পদ অবিলম্বে সরিয়ে ফেলবে। এটি পূর্বাবস্থায় ফেরানো যাবে না এবং ফাইলগুলি পুনরুদ্ধার করা যাবে না।", - "image_format": "ফরম্যাট", - "image_format_description": "WebP JPEG এর তুলনায় ছোট ফাইল তৈরি করে, কিন্তু এনকোড করতে ধীর।", - "image_fullsize_description": "জুম ইন করার সময় ব্যবহৃত স্ট্রিপড মেটাডেটা সহ পূর্ণ আকারের ছবি", - "image_fullsize_enabled": "পূর্ণ-আকারের ছবি তৈরি সক্ষম করুন", - "image_fullsize_enabled_description": "ওয়েব-বান্ধব নয় এমন ফর্ম্যাটের জন্য পূর্ণ-আকারের ছবি তৈরি করুন। \"এমবেডেড প্রিভিউ পছন্দ করুন\" সক্ষম করা থাকলে, রূপান্তর ছাড়াই এমবেডেড প্রিভিউ সরাসরি ব্যবহার করা হয়। JPEG-এর মতো ওয়েব-বান্ধব ফর্ম্যাটগুলিকে প্রভাবিত করে না।", - "image_fullsize_quality_description": "পূর্ণ-আকারের ছবির মান ১-১০০। উচ্চতর হলে ভালো, কিন্তু আরও বড় ফাইল তৈরি হয়।", - "image_fullsize_title": "পূর্ণ-আকারের চিত্র সেটিংস", - "image_prefer_embedded_preview": "এম্বেড করা প্রিভিউ পছন্দ করুন", - "image_prefer_embedded_preview_setting_description": "যদি পাওয়া যায়, RAW ছবির ভেতরে থাকা প্রিভিউ ব্যবহার করুন। এতে কিছু ছবির রঙ আরও সঠিক দেখা যেতে পারে, তবে মান ক্যামেরার ওপর নির্ভর করে এবং ছবিতে বাড়তি কমপ্রেশন আর্টিফ্যাক্ট দেখা যেতে পারে।", - "image_prefer_wide_gamut": "প্রশস্ত পরিসর পছন্দ করুন", - "image_prefer_wide_gamut_setting_description": "থাম্বনেইলের জন্য Display P3 ব্যবহার করুন। এটি ওয়াইড কালারস্পেস ছবির উজ্জ্বলতা ও প্রাণবন্ত রঙ ভালোভাবে ধরে রাখে, তবে পুরনো ডিভাইস বা ব্রাউজারে ছবিগুলো ভিন্নভাবে দেখা যেতে পারে। sRGB ছবিগুলো রঙের পরিবর্তন এড়াতে sRGB হিসেবেই রাখা হবে।", - "image_preview_description": "স্ট্রিপড মেটাডেটা সহ মাঝারি আকারের ছবি, একটি একক সম্পদ দেখার সময় এবং মেশিন লার্নিংয়ের জন্য ব্যবহৃত হয়", - "image_preview_quality_description": "১-১০০ এর মধ্যে প্রিভিউ কোয়ালিটি। বেশি হলে ভালো, কিন্তু বড় ফাইল তৈরি হয় এবং অ্যাপের প্রতিক্রিয়াশীলতা কমাতে পারে। কম মান সেট করলে মেশিন লার্নিং কোয়ালিটির উপর প্রভাব পড়তে পারে।", - "image_preview_title": "প্রিভিউ সেটিংস", - "image_progressive": "প্রগ্রেসিভ", - "image_progressive_description": "ধীরে ধীরে লোড হওয়ার সুবিধার্থে JPEG ছবিগুলো প্রগ্রেসিভভাবে এনকোড করুন। WebP ছবির ক্ষেত্রে এটি কোনো প্রভাব ফেলবে না", - "image_quality": "গুণমান", - "image_resolution": "রেজোলিউশন", - "image_resolution_description": "উচ্চ রেজোলিউশনের ক্ষেত্রে আরও বিস্তারিত তথ্য সংরক্ষণ করা সম্ভব কিন্তু এনকোড করতে বেশি সময় লাগে, ফাইলের আকার বড় হয় এবং অ্যাপের প্রতিক্রিয়াশীলতা কমাতে পারে।", - "image_settings": "চিত্র সেটিংস", - "image_settings_description": "তৈরি করা ছবির মান এবং রেজোলিউশন পরিচালনা করুন", - "image_thumbnail_description": "মেটাডেটা বাদ দেওয়া ছোট থাম্বনেইল, মূল টাইমলাইনের মতো ছবির গ্রুপ দেখার সময় ব্যবহৃত হয়", - "image_thumbnail_quality_description": "থাম্বনেইলের মান ১-১০০। বেশি হলে ভালো, কিন্তু বড় ফাইল তৈরি হয় এবং অ্যাপের প্রতিক্রিয়াশীলতা কমাতে পারে।", - "image_thumbnail_title": "থাম্বনেল সেটিংস", - "import_config_from_json_description": "একটি JSON কনফিগ ফাইল আপলোড করে সিস্টেম কনফিগারেশন ইমপোর্ট করুন।", - "job_concurrency": "{job} কনকারেন্সি", - "job_created": "Job তৈরি হয়েছে", - "job_not_concurrency_safe": "এই কাজটি সমান্তরালভাবে চালানো নিরাপদ নয়", - "job_settings": "কাজের সেটিংস", - "job_settings_description": "কাজের সমান্তরালতা পরিচালনা করুন", - "jobs_delayed": "{jobCount, plural, other {# বিলম্বিত}}", - "jobs_failed": "{jobCount, plural, other {# ব্যর্থ}}", - "jobs_over_time": "সময় অনুযায়ী কাজসমূহ", - "library_created": "লাইব্রেরি তৈরি করা হয়েছেঃ {library}", - "library_deleted": "লাইব্রেরি মুছে ফেলা হয়েছে", - "library_details": "লাইব্রেরির বিবরণ", - "library_folder_description": "ইমপোর্ট করার জন্য একটি ফোল্ডার নির্দিষ্ট করুন। এই ফোল্ডার এবং এর ভেতরের সমস্ত ফোল্ডার ছবি ও ভিডিওর জন্য স্ক্যান করা হবে।", - "library_remove_exclusion_pattern_prompt": "আপনি কি নিশ্চিত যে আপনি এই এক্সক্লুশন প্যাটার্নটি মুছে ফেলতে চান?", - "library_remove_folder_prompt": "আপনি কি নিশ্চিত যে আপনি এই ইমপোর্ট ফোল্ডারটি মুছে ফেলতে চান?", - "library_scanning": "পর্যায়ক্রমিক স্ক্যানিং", - "library_scanning_description": "পর্যায়ক্রমিক লাইব্রেরি স্ক্যানিং কনফিগার করুন", - "library_scanning_enable_description": "পর্যায়ক্রমিক লাইব্রেরি স্ক্যানিং সক্ষম করুন", - "library_settings": "বহিরাগত লাইব্রেরি", - "library_settings_description": "বহিরাগত লাইব্রেরি সেটিংস পরিচালনা করুন", - "library_tasks_description": "নতুন এবং/অথবা পরিবর্তিত সম্পদের জন্য বহিরাগত লাইব্রেরি স্ক্যান করুন", - "library_updated": "আপডেটকৃত লাইব্রেরি।", - "library_watching_enable_description": "ফাইল পরিবর্তনের জন্য বহিরাগত লাইব্রেরিগুলি দেখুন", - "library_watching_settings": "লাইব্রেরি দেখা (পরীক্ষামূলক)", - "library_watching_settings_description": "পরিবর্তিত ফাইলগুলির জন্য স্বয়ংক্রিয়ভাবে নজর রাখুন", - "logging_enable_description": "লগিং এনাবল/সক্ষম করুন", - "logging_level_description": "সক্রিয় থাকাকালীন, কোন লগ স্তর ব্যবহার করতে হবে।", - "logging_settings": "লগিং", - "machine_learning_availability_checks": "প্রাপ্যতা পরীক্ষা", - "machine_learning_availability_checks_description": "স্বয়ংক্রিয়ভাবে উপলব্ধ মেশিন লার্নিং সার্ভারগুলি সনাক্ত করুন এবং পছন্দ করুন", - "machine_learning_availability_checks_enabled": "প্রাপ্যতা পরীক্ষা সক্ষম করুন", - "machine_learning_availability_checks_interval": "চেক ব্যবধান", - "machine_learning_availability_checks_interval_description": "প্রাপ্যতা পরীক্ষাগুলির মধ্যে ব্যবধান মিলিসেকেন্ডে", - "machine_learning_availability_checks_timeout": "অনুরোধের সময়সীমা শেষ", - "machine_learning_availability_checks_timeout_description": "প্রাপ্যতার পরীক্ষার জন্য মিলিসেকেন্ডে সময়সীমা।", - "machine_learning_clip_model": "CLIP মডেল", - "machine_learning_clip_model_description": "এখানে তালিকাভুক্ত একটি CLIP মডেলের নাম। মনে রাখবেন, মডেল পরিবর্তনের পর সব ছবির জন্য অবশ্যই ‘Smart Search’ কাজটি আবার চালাতে হবে।", - "machine_learning_duplicate_detection": "পুনরাবৃত্তি সনাক্তকরণ", - "machine_learning_duplicate_detection_enabled": "পুনরাবৃত্তি শনাক্তকরণ চালু করুন", - "machine_learning_duplicate_detection_enabled_description": "নিষ্ক্রিয় থাকলেও হুবহু একই সম্পদগুলোর ডুপ্লিকেট সরিয়ে ফেলা হবে।", - "machine_learning_duplicate_detection_setting_description": "সম্ভাব্য ডুপ্লিকেট খুঁজে বের করতে CLIP এম্বেডিং ব্যবহার করুন।", - "machine_learning_enabled": "Machine Learning সক্ষম করুন", - "machine_learning_enabled_description": "নিষ্ক্রিয় থাকলে নিচের সেটিংস নির্বিশেষে সমস্ত ML বৈশিষ্ট্য নিষ্ক্রিয় করা হবে।", - "machine_learning_facial_recognition": "ফেসিয়াল রিকগনিশন", - "machine_learning_facial_recognition_description": "ছবিতে মুখ সনাক্ত করুন, চিনুন এবং গ্রুপ করুন।", - "machine_learning_facial_recognition_model": "ফেসিয়াল রিকগনিশন মডেল", - "machine_learning_facial_recognition_model_description": "মডেলগুলি আকারের অধঃক্রম অনুযায়ী তালিকাভুক্ত করা হয়েছে। বড় মডেলগুলি ধীরগতির এবং বেশি মেমরি ব্যবহার করে, তবে উন্নত ফলাফল প্রদান করে। মনে রাখবেন যে একটি মডেল পরিবর্তন করার পর আপনাকে সমস্ত ছবির জন্য ফেস ডিটেকশন (Face Detection) কাজটি পুনরায় চালাতে হবে।", - "machine_learning_facial_recognition_setting": "ফেসিয়াল রিকগনিশন সক্ষম করুন", - "machine_learning_facial_recognition_setting_description": "নিষ্ক্রিয় থাকলে, ফেসিয়াল রিকগনিশনের জন্য ছবিগুলো এনকোড করা হবে না এবং এক্সপ্লোর পেজের পিপল (People) সেকশনটি পূর্ণ হবে না।", - "machine_learning_max_detection_distance": "সর্বোচ্চ শনাক্তকরণ দূরত্ব", - "machine_learning_max_detection_distance_description": "দুটি ছবিকে ডুপ্লিকেট হিসেবে গণ্য করার জন্য তাদের মধ্যকার সর্বোচ্চ দূরত্ব, যার পরিসীমা ০.০০১-০.১। মান যত বেশি হবে তত বেশি ডুপ্লিকেট শনাক্ত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।", - "machine_learning_max_recognition_distance": "সর্বোচ্চ চিহ্নিতকরণ দূরত্ব", - "machine_learning_max_recognition_distance_description": "দুটি মুখকে একই ব্যক্তি হিসেবে গণ্য করার জন্য তাদের মধ্যকার সর্বোচ্চ দূরত্ব, যার পরিসীমা ০-২। এই মান কমালে দু’জন ভিন্ন ব্যক্তিকে একই ব্যক্তি হিসেবে চিহ্নিত করার সম্ভাবনা কমে, আর মান বাড়ালে একই ব্যক্তিকে দু’জন ভিন্ন ব্যক্তি হিসেবে চিহ্নিত করার সম্ভাবনা কমে। মনে রাখবেন যে, দু’জন ব্যক্তিকে একত্রিত করা (merge) অপেক্ষাকৃত সহজ কিন্তু একজনকে দু’ভাগে ভাগ করা কঠিন, তাই সম্ভব হলে থ্রেশহোল্ড (threshold) কম রাখাই ভালো।", - "machine_learning_min_detection_score": "সর্বনিম্ন শনাক্তকরণ স্কোর", - "machine_learning_min_detection_score_description": "ছবিতে মুখ শনাক্ত করার জন্য ০-১ এর মধ্যে সর্বনিম্ন কনফিডেন্স স্কোর। মান যত কম হবে তত বেশি মুখ শনাক্ত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।", - "machine_learning_min_recognized_faces": "সর্বনিম্ন স্বীকৃত মুখের সংখ্যা", - "machine_learning_min_recognized_faces_description": "একজন ব্যক্তি হিসেবে তৈরি হওয়ার জন্য স্বীকৃত মুখের সর্বনিম্ন সংখ্যা। এটি বাড়ালে ফেসিয়াল রিকগনিশন আরও নিখুঁত হয়, তবে এতে কোনো মুখ কোনো ব্যক্তির সাথে সংযুক্ত না হওয়ার সম্ভাবনাও বৃদ্ধি পায়।", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "ছবিতে টেক্সট (Text) শনাক্ত করতে মেশিন লার্নিং ব্যবহার করুন।", - "machine_learning_ocr_enabled": "OCR সক্ষম করুন", - "machine_learning_ocr_enabled_description": "নিষ্ক্রিয় থাকলে, ছবিগুলোতে টেক্সট শনাক্তকরণ করা হবে না।", - "machine_learning_ocr_max_resolution": "সর্বোচ্চ রেজোলিউশন(Resolution)", - "machine_learning_ocr_max_resolution_description": "এই রেজোলিউশনের উপরের প্রিভিউগুলোর অ্যাসপেক্ট রেশিও (আকার ও অনুপাত) ঠিক রেখে রিসাইজ করা হবে। মান যত বেশি হবে ফলাফল তত বেশি নিখুঁত হবে, তবে এটি প্রসেস করতে সময় বেশি লাগবে এবং মেমরি বেশি ব্যবহার করবে।", - "machine_learning_ocr_min_detection_score": "সর্বনিম্ন শনাক্তকরণ স্কোর", - "machine_learning_ocr_min_detection_score_description": "টেক্সট শনাক্ত করার জন্য ০-১ এর মধ্যে ন্যূনতম কনফিডেন্স স্কোর। মান যত কম হবে তত বেশি টেক্সট শনাক্ত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।", - "machine_learning_ocr_min_recognition_score": "সর্বনিম্ন চিহ্নিতকরণ (Recognition)স্কোর", - "machine_learning_ocr_min_score_recognition_description": "শনাক্তকৃত টেক্সট চিহ্নিত করার জন্য ০-১ এর মধ্যে ন্যূনতম কনফিডেন্স স্কোর। মান যত কম হবে তত বেশি টেক্সট চিহ্নিত হবে, তবে এতে ভুল শনাক্তকরণের (false positives) সম্ভাবনা থাকতে পারে।", - "machine_learning_ocr_model": "OCR মডেল", - "machine_learning_ocr_model_description": "সার্ভার মডেলগুলো মোবাইল মডেলের তুলনায় বেশি নির্ভুল, তবে এগুলো প্রসেস করতে সময় বেশি লাগে এবং মেমরি বেশি ব্যবহার করে।", - "machine_learning_settings": "মেশিন লার্নিং সেটিংস (Machine Learning Settings)", - "machine_learning_settings_description": "মেশিন লার্নিং বৈশিষ্ট্য এবং সেটিংস পরিচালনা করুন", - "machine_learning_smart_search": "স্মার্ট সার্চ (Smart Search)", - "machine_learning_smart_search_description": "CLIP এমবেডিং (embeddings) ব্যবহার করে ছবির বিষয়বস্তু অনুযায়ী অনুসন্ধান করুন", - "machine_learning_smart_search_enabled": "স্মার্ট সার্চ সক্ষম করুন", - "machine_learning_smart_search_enabled_description": "নিষ্ক্রিয় থাকলে, স্মার্ট সার্চের জন্য ছবিগুলো এনকোড (encode) করা হবে না।", - "machine_learning_url_description": "মেশিন লার্নিং সার্ভারের URL। যদি একের বেশি URL প্রদান করা হয়, তবে একটি সফলভাবে সাড়া না দেওয়া পর্যন্ত প্রতিটি সার্ভারে এক এক করে চেষ্টা করা হবে (প্রথম থেকে শেষ ক্রমানুসারে)। যে সার্ভারগুলো সাড়া দেবে না, সেগুলো পুনরায় সচল হওয়া পর্যন্ত সাময়িকভাবে উপেক্ষা করা হবে।", - "maintenance_delete_backup": "ব্যাকআপ (Backup)মুছুন", - "maintenance_delete_backup_description": "এই ফাইলটি চিরতরে মুছে ফেলা হবে।", - "maintenance_delete_error": "ব্যাকআপ মুছতে ব্যর্থ হয়েছে।", - "maintenance_restore_backup": "ব্যাকআপ পুনরুদ্ধার(Restore) করুন", - "maintenance_restore_backup_description": "Immich মুছে ফেলা হবে এবং নির্বাচিত ব্যাকআপ থেকে পুনরুদ্ধার করা হবে। কার্যক্রম চালিয়ে যাওয়ার আগে একটি ব্যাকআপ তৈরি করা হবে।", - "maintenance_restore_backup_different_version": "এই ব্যাকআপটি Immich-এর একটি ভিন্ন সংস্করণের মাধ্যমে তৈরি করা হয়েছিল!", - "maintenance_restore_backup_unknown_version": "ব্যাকআপ সংস্করণ নির্ধারণ করা সম্ভব হয়নি।", - "maintenance_restore_database_backup": "ডেটাবেস ব্যাকআপ পুনরুদ্ধার করুন", - "maintenance_restore_database_backup_description": "একটি ব্যাকআপ ফাইল ব্যবহার করে ডেটাবেসকে পূর্ববর্তী অবস্থায় ফিরিয়ে আনুন।", - "maintenance_settings": "রক্ষণাবেক্ষণ (Maintenance)", - "maintenance_settings_description": "Immich-কে রক্ষণাবেক্ষণ মোডে (maintenance mode) রাখুন।", - "maintenance_start": "রক্ষণাবেক্ষণ মোডে পরিবর্তন করুন", - "maintenance_start_error": "রক্ষণাবেক্ষণ মোড চালু করতে ব্যর্থ হয়েছে।", - "maintenance_upload_backup": "ডেটাবেস ব্যাকআপ ফাইল আপলোড করুন", - "maintenance_upload_backup_error": "ব্যাকআপ আপলোড করা যায়নি, এটি কি কোনো .sql/.sql.gz ফাইল?", - "manage_concurrency": "কনকারেন্সি পরিচালনা করুন (Manage Concurrency)", - "manage_concurrency_description": "জব কনকারেন্সি পরিচালনা করতে 'জবস' (Jobs) পাতায় যান।", - "manage_log_settings": "লগ সেটিংস পরিচালনা করুন", - "map_dark_style": "ডার্ক স্টাইল (Dark style)", - "map_enable_description": "ম্যাপ ফিচারগুলো সক্রিয় করুন (Enable map features)", - "map_gps_settings": "ম্যাপ এবং জিপিএস সেটিংস (Map & GPS Settings)", - "map_gps_settings_description": "ম্যাপ এবং জিপিএস (রিভার্স জিওকোডিং) সেটিংস পরিচালনা করুন (Manage Map & GPS (Reverse Geocoding) Settings)", - "map_implications": "ম্যাপ ফিচারটি একটি এক্সটার্নাল টাইল সার্ভিসের (tiles.immich.cloud) ওপর নির্ভর করে।", - "map_light_style": "লাইট স্টাইল (Light style)", - "map_manage_reverse_geocoding_settings": "রিভার্স জিওকোডিং সেটিংস পরিচালনা করুন", - "map_reverse_geocoding": "রিভার্স জিওকোডিং (Reverse Geocoding)", - "map_reverse_geocoding_enable_description": "রিভার্স জিওকোডিং সক্রিয় করুন (Enable reverse geocoding)", - "map_reverse_geocoding_settings": "রিভার্স জিওকোডিং সেটিংস (Reverse Geocoding Settings)", - "map_settings": "মানচিত্র (Map)", - "map_settings_description": "মানচিত্রের সেটিংস পরিচালনা করুন (Manage map settings)", - "map_style_description": "একটি style.json ম্যাপ থিমের URL (URL to a style.json map theme)", - "memory_cleanup_job": "মেমরি ক্লিনআপ (Memory cleanup)", - "memory_generate_job": "স্মৃতি তৈরি করা(Memory generation)", - "metadata_extraction_job": "মেটাডেটা এক্সট্র্যাক্ট করুন (Extract metadata)", - "metadata_extraction_job_description": "প্রতিটি অ্যাসেট (Asset) থেকে মেটাডেটা তথ্য এক্সট্র্যাক্ট করুন, যেমন: জিপিএস (GPS), চেহারা (faces) এবং রেজোলিউশন (resolution)।", - "metadata_faces_import_setting": "ফেস ইম্পোর্ট সক্রিয় করুন (Enable face import)", - "metadata_faces_import_setting_description": "ছবির EXIF ডেটা এবং সাইডকার (sidecar) ফাইল থেকে চেহারা (faces) ইম্পোর্ট করুন।", - "metadata_settings": "মেটাডেটা সেটিংস (Metadata Settings)", - "metadata_settings_description": "মেটাডেটা সেটিংস পরিচালনা করুন (Manage metadata settings)", - "migration_job": "মাইগ্রেশন (Migration)", - "migration_job_description": "অ্যাসেট এবং ফেস থাম্বনেইলগুলোকে সর্বশেষ ফোল্ডার স্ট্রাকচারে মাইগ্রেট করুন। (Migrate thumbnails for assets and faces to the latest folder structure)", - "nightly_tasks_database_cleanup_setting": "ডেটাবেস ক্লিনআপ টাস্কসমূহ (Database cleanup tasks)", - "nightly_tasks_database_cleanup_setting_description": "ডেটাবেস থেকে পুরোনো এবং মেয়াদোত্তীর্ণ ডেটা মুছে ফেলুন", - "nightly_tasks_generate_memories_setting": "মেমোরিজ তৈরি করুন (Generate memories)", - "nightly_tasks_generate_memories_setting_description": "অ্যাসেটগুলো থেকে নতুন মেমোরিজ তৈরি করুন", - "nightly_tasks_missing_thumbnails_setting": "হারিয়ে যাওয়া থাম্বনেইলগুলো তৈরি করুন", - "nightly_tasks_missing_thumbnails_setting_description": "থাম্বনেইল নেই এমন ফাইলগুলোকে কিউতে (Queue) যোগ করুন", - "nightly_tasks_settings": "নাইটলি টাস্ক সেটিংস (Nightly Tasks Settings)", - "nightly_tasks_settings_description": "নাইটলি টাস্ক পরিচালনা করুন (Manage nightly tasks)", - "nightly_tasks_start_time_setting": "শুরু করার সময় (Start time)", - "nightly_tasks_start_time_setting_description": "সার্ভার যখন নাইটলি টাস্ক (nightly tasks) চালানো শুরু করে সেই সময়", - "nightly_tasks_sync_quota_usage_setting": "কোটা ব্যবহারের তথ্য সিঙ্ক করুন (Sync quota usage)", - "nightly_tasks_sync_quota_usage_setting_description": "বর্তমান ব্যবহারের ওপর ভিত্তি করে ব্যবহারকারীর স্টোরেজ কোটা আপডেট করুন।", - "no_paths_added": "কোনো পাথ যোগ করা হয়নি (No paths added)", - "no_pattern_added": "কোনো প্যাটার্ন যোগ করা হয়নি (No pattern added)", - "note_apply_storage_label_previous_assets": "দ্রষ্টব্য: পূর্বে আপলোড করা অ্যাসেটগুলোতে স্টোরেজ লেবেল (Storage Label) প্রয়োগ করতে নিচের কমান্ডটি রান করুন—", - "note_cannot_be_changed_later": "সতর্কবার্তা: এটি পরবর্তীতে পরিবর্তন করা যাবে না!", - "notification_email_from_address": "প্রেরকের ঠিকানা (From address)", - "notification_email_from_address_description": "প্রেরকের ইমেল ঠিকানা, উদাহরণস্বরূপ: \"Immich Photo Server noreply@example.com\"। নিশ্চিত করুন যে আপনি এমন একটি ঠিকানা ব্যবহার করছেন যা থেকে ইমেল পাঠানোর অনুমতি আপনার আছে।", - "notification_email_host_description": "ইমেল সার্ভারের হোস্ট (যেমন: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "সার্টিফিকেট ত্রুটিগুলো উপেক্ষা করুন (Ignore certificate errors)", - "notification_email_ignore_certificate_errors_description": "TLS সার্টিফিকেট ভ্যালিডেশন ত্রুটিগুলো উপেক্ষা করুন (প্রস্তাবিত নয়)", - "notification_email_password_description": "ইমেল সার্ভারে অথেন্টিকেশন বা সত্যতা যাচাইয়ের জন্য ব্যবহৃত পাসওয়ার্ড", - "notification_email_port_description": "ইমেল সার্ভারের পোর্ট (যেমন: ২৫, ৪৬৫, অথবা ৫৮৭)", - "notification_email_secure": "SMTPS (স্মার্ট মেইল ট্রান্সফার প্রোটোকল সিকিউর)", - "notification_email_secure_description": "SMTPS (SMTP over TLS) ব্যবহার করুন", - "notification_email_sent_test_email_button": "টেস্ট ইমেল পাঠান এবং সেভ করুন", - "oauth_enable_description": "OAuth-এর মাধ্যমে লগইন করুন", - "oauth_mobile_redirect_uri": "মোবাইল রিডাইরেক্ট ইউআরআই (URI)", - "oauth_mobile_redirect_uri_override": "মোবাইল রিডাইরেক্ট ইউআরআই (URI) ওভাররাইড", - "oauth_mobile_redirect_uri_override_description": "যখন OAuth প্রোভাইডার মোবাইল ইউআরআই (URI) অনুমতি দেয় না, যেমন ''{callback}'', তখন এটি সক্রিয় করুন।", - "oauth_role_claim": "রোল ক্লেইম (Role Claim)", - "oauth_role_claim_description": "এই ক্লেইমটির উপস্থিতির ওপর ভিত্তি করে স্বয়ংক্রিয়ভাবে অ্যাডমিন অ্যাক্সেস প্রদান করুন। ক্লেইমটিতে 'user' অথবা 'admin' যেকোনো একটি থাকতে পারে।", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth লগইন সেটিংস ম্যানেজ করুন", - "oauth_settings_more_details": "এই ফিচারের ব্যাপারে আরও বিস্তারিত জানতে, ডকুমেন্টস দেখুন।", - "oauth_storage_label_claim": "স্টোরেজ লেবেল ক্লেইম (Storage label claim)", - "oauth_storage_label_claim_description": "এই ক্লেইম-এর ভ্যালু অনুযায়ী ব্যবহারকারীর স্টোরেজ লেবেল স্বয়ংক্রিয়ভাবে সেট করুন।", - "oauth_storage_quota_claim": "স্টোরেজ কোটা ক্লেইম (Storage quota claim)", - "oauth_storage_quota_claim_description": "এই ক্লেইম-এর ভ্যালু অনুযায়ী ব্যবহারকারীর স্টোরেজ কোটা স্বয়ংক্রিয়ভাবে সেট করুন।", - "oauth_storage_quota_default": "ডিফল্ট স্টোরেজ কোটা (GiB)", - "oauth_storage_quota_default_description": "ক্লেইম না দেওয়া থাকলে যে স্টোরেজ কোটা (GiB-তে) ব্যবহার করা হবে।", - "oauth_timeout": "রিকোয়েস্ট টাইম-আউট (Request Timeout)", - "oauth_timeout_description": "মিলিসেকেন্ডে রিকোয়েস্টের টাইম-আউট (Timeout for requests in milliseconds)", - "ocr_job_description": "ছবি থেকে টেক্সট শনাক্ত করতে মেশিন লার্নিং ব্যবহার করুন", - "password_enable_description": "ইমেল এবং পাসওয়ার্ড দিয়ে লগইন করুন", - "password_settings": "পাসওয়ার্ড লগইন (Password Login)", - "password_settings_description": "পাসওয়ার্ড লগইন সেটিংস ম্যানেজ করুন", - "paths_validated_successfully": "সবগুলো পাথ (path) সফলভাবে যাচাই করা হয়েছে", - "person_cleanup_job": "পারসন ক্লিনআপ (Person Cleanup)", - "queue_details": "কিউ ডিটেইলস (Queue Details)", - "queues": "জব কিউ (Job Queues)", - "queues_page_description": "অ্যাডমিন জব কিউ (Job Queues) পেজ", - "quota_size_gib": "কোটা সাইজ (GiB)", - "refreshing_all_libraries": "সবগুলো লাইব্রেরি রিফ্রেশ করা হচ্ছে", - "registration": "অ্যাডমিন রেজিস্ট্রেশন (Admin Registration)", - "registration_description": "যেহেতু আপনি এই সিস্টেমের প্রথম ব্যবহারকারী, তাই আপনাকে অ্যাডমিন (Admin) হিসেবে নিযুক্ত করা হবে। আপনি সমস্ত প্রশাসনিক কাজের জন্য দায়ী থাকবেন এবং পরবর্তী ব্যবহারকারীরা আপনার মাধ্যমেই তৈরি হবে।", - "remove_failed_jobs": "ব্যর্থ হওয়া কাজগুলো মুছে ফেলুন (Remove failed jobs)", - "require_password_change_on_login": "প্রথমবার লগইন করার সময় ব্যবহারকারীর পাসওয়ার্ড পরিবর্তন করা বাধ্যতামূলক করুন", - "reset_settings_to_default": "সেটিংস রিসেট করে ডিফল্ট অবস্থায় ফিরিয়ে আনুন (Reset settings to default)", - "reset_settings_to_recent_saved": "সম্প্রতি সেভ করা সেটিংসে রিসেট করুন (Reset settings to the recent saved settings)", - "scanning_library": "লাইব্রেরি স্ক্যান করা হচ্ছে (Scanning library)", - "search_jobs": "জব সার্চ করুন…", - "send_welcome_email": "স্বাগত ইমেল পাঠান", - "server_external_domain_settings": "এক্সটার্নাল ডোমেইন (External Domain)", - "server_external_domain_settings_description": "পাবলিক শেয়ারিং লিঙ্কের জন্য ডোমেইন (http(s):// সহ)", - "server_public_users": "পাবলিক ইউজার (Public Users)", - "server_public_users_description": "শেয়ার করা অ্যালবামে কোনো ব্যবহারকারীকে যোগ করার সময় সমস্ত ব্যবহারকারীর (নাম এবং ইমেল) তালিকা দেখানো হয়। এটি নিষ্ক্রিয় (Disabled) করা হলে, ব্যবহারকারীর তালিকা শুধুমাত্র অ্যাডমিনদের জন্য উপলব্ধ হবে।", - "server_settings": "সার্ভার সেটিংস (Server Settings)", - "server_settings_description": "সার্ভার সেটিংস ম্যানেজ করুন (Manage server settings)", - "server_stats_page_description": "অ্যাডমিন সার্ভার স্ট্যাটিস্টিকস (Server Statistics) পেজ", - "server_welcome_message": "স্বাগত বার্তা (Welcome message)", - "server_welcome_message_description": "লগইন পেজে প্রদর্শিত একটি বার্তা।", - "settings_page_description": "অ্যাডমিন সেটিংস পেজ", - "sidecar_job": "সাইডকার মেটাডেটা (Sidecar Metadata)", - "sidecar_job_description": "ফাইলসিস্টেম থেকে সাইডকার মেটাডেটা অনুসন্ধান বা সিঙ্ক্রোনাইজ করুন", - "slideshow_duration_description": "প্রতিটি ছবি দেখানোর সময়কাল (সেকেন্ডে)", - "smart_search_job_description": "স্মার্ট সার্চের সুবিধার্থে অ্যাসেটগুলোর ওপর মেশিন লার্নিং পরিচালনা করুন", - "storage_template_date_time_description": "অ্যাসেট তৈরির সময়কাল (Timestamp) তারিখ ও সময়ের তথ্যের জন্য ব্যবহৃত হয়", - "storage_template_date_time_sample": "নমুনা সময় {date}", - "storage_template_enable_description": "স্টোরেজ টেমপ্লেট ইঞ্জিন সক্রিয় করুন", - "storage_template_hash_verification_enabled": "হ্যাশ ভেরিফিকেশন (Hash Verification) সক্রিয় করা হয়েছে", - "storage_template_hash_verification_enabled_description": "হ্যাশ ভেরিফিকেশন (Hash Verification) সক্রিয় করে; এর প্রভাব সম্পর্কে নিশ্চিত না হয়ে এটি নিষ্ক্রিয় করবেন না", - "storage_template_migration": "স্টোরেজ টেমপ্লেট মাইগ্রেশন (Storage Template Migration)", - "storage_template_migration_description": "পূর্বে আপলোড করা অ্যাসেটগুলোতে বর্তমান {template} প্রয়োগ করুন", - "storage_template_migration_info": "স্টোরেজ টেমপ্লেটটি সমস্ত এক্সটেনশনকে ছোট হাতের অক্ষরে (lowercase) রূপান্তর করবে। টেমপ্লেটের পরিবর্তনগুলো কেবল নতুন অ্যাসেটগুলোর ক্ষেত্রে প্রযোজ্য হবে। পূর্বে আপলোড করা অ্যাসেটগুলোতে এই টেমপ্লেটটি ভূতাপেক্ষভাবে (retroactively) প্রয়োগ করতে {job} রান করুন।", - "storage_template_migration_job": "স্টোরেজ টেমপ্লেট মাইগ্রেশন জব", - "storage_template_more_details": "এই ফিচারটি সম্পর্কে আরও বিস্তারিত জানতে, Storage Template এবং এর প্রভাবগুলো (implications) দেখুন।", - "storage_template_onboarding_description_v2": "এটি সক্রিয় থাকলে, ফিচারটি ব্যবহারকারীর নির্ধারিত টেমপ্লেট অনুযায়ী ফাইলগুলোকে স্বয়ংক্রিয়ভাবে অর্গানাইজ (Auto-organize) করবে। আরও তথ্যের জন্য অনুগ্রহ করে ডকুমেন্টেশন দেখুন।", - "storage_template_path_length": "আনুমানিক পাথ লেন্থ লিমিট (Path length limit): {length, number}/{limit, number}", - "storage_template_settings": "স্টোরেজ টেমপ্লেট (Storage Template)", - "storage_template_settings_description": "আপলোড করা অ্যাসেটের ফোল্ডার স্ট্রাকচার এবং ফাইল নেম ম্যানেজ করুন", - "storage_template_user_label": "{label} হলো ব্যবহারকারীর স্টোরেজ লেবেল (Storage Label)", - "theme_settings_description": "ইমিচ (Immich) ওয়েব ইন্টারফেসের কাস্টমাইজেশন ম্যানেজ করুন", - "thumbnail_generation_job": "থাম্বনেইল তৈরি করুন (Generate Thumbnails)", - "thumbnail_generation_job_description": "প্রতিটি অ্যাসেটের জন্য বড়, ছোট এবং ব্লার (অস্পষ্ট) থাম্বনেইল তৈরি করুন, সেই সাথে প্রতিটি ব্যক্তির জন্যও থাম্বনেইল তৈরি করুন।" - }, - "yes": "হ্যাঁ", - "you_dont_have_any_shared_links": "আপনার কোনো শেয়ার করা লিঙ্ক নেই (You don't have any shared links)", - "your_wifi_name": "আপনার ওয়াই-ফাই এর নাম (Your Wi-Fi name)", - "zero_to_clear_rating": "অ্যাসেট রেটিং মুছে ফেলতে ০ চাপুন", - "zoom_image": "ছবি জুম করুন (Zoom Image)", - "zoom_to_bounds": "বাউন্ডস অনুযায়ী জুম করুন (Zoom to bounds)" -} +{} diff --git a/i18n/ca.json b/i18n/ca.json index 737bb5bce8..0967ef424b 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -1,2401 +1 @@ -{ - "about": "Quant a", - "account": "Compte", - "account_settings": "Configuració del compte", - "acknowledge": "Base de coneixement", - "action": "Acció", - "action_common_update": "Actualitzar", - "action_description": "Un conjunt d'accions a realitzar sobre els recursos filtrats", - "actions": "Accions", - "active": "Actiu", - "active_count": "Activat: {count}", - "activity": "Activitat", - "activity_changed": "L'activitat està {enabled, select, true {activada} other {desactivada}}", - "add": "Afegir", - "add_a_description": "Afegiu una descripció", - "add_a_location": "Afegiu una ubicació", - "add_a_name": "Afegir un nom", - "add_a_title": "Afegir un títol", - "add_action": "Afegir acció", - "add_action_description": "Feu clic per afegir una acció a realitzar", - "add_assets": "Afegir recursos", - "add_birthday": "Afegeix la data de naixement", - "add_endpoint": "afegir endpoint", - "add_exclusion_pattern": "Afegir un patró d'exclusió", - "add_filter": "Afegir filtre", - "add_filter_description": "Feu clic per afegir una condició de filtre", - "add_location": "Afegir la ubicació", - "add_more_users": "Afegir més usuaris", - "add_partner": "Afegir company/a", - "add_path": "Afegir una ruta", - "add_photos": "Afegir fotografies", - "add_tag": "Afegir una etiqueta", - "add_to": "Afegir a…", - "add_to_album": "Afegir a un l'àlbum", - "add_to_album_bottom_sheet_added": "Afegit a {album}", - "add_to_album_bottom_sheet_already_exists": "Ja està a {album}", - "add_to_album_bottom_sheet_some_local_assets": "Alguns recursos locals no s'han pogut afegir a l'àlbum", - "add_to_album_toggle": "Commutar selecció de {album}", - "add_to_albums": "Afegir als àlbums", - "add_to_albums_count": "Afegir als àlbums ({count})", - "add_to_bottom_bar": "Afegir a", - "add_to_shared_album": "Afegir a un àlbum compartit", - "add_upload_to_stack": "Afegeix la càrrega a la pila", - "add_url": "Afegir URL", - "add_workflow_step": "Afegeix un pas del flux de treball", - "added_to_archive": "Afegir a l'arxiu", - "added_to_favorites": "Afegit als preferits", - "added_to_favorites_count": "{count, number} afegits als preferits", - "admin": { - "add_exclusion_pattern_description": "Afegeix patrons d'exclusió. Es permet englobar fent ús de *, **, i ?. Per a ignorar els fitxers de qualsevol directori anomenat \"Raw\" introduïu \"**/Raw/**\". Per a ignorar els fitxers acabats en \".tif\" introduïu \"**/*.tif\". Per a ignorar una ruta absoluta, utilitzeu \"/ruta/a/ignorar/**\".", - "admin_user": "Administrador", - "asset_offline_description": "Aquest recurs de la biblioteca externa ja no es troba al disc i s'ha mogut a la paperera. Si el fitxer s'ha mogut dins de la biblioteca, comproveu la vostra línia de temps per trobar el nou recurs corresponent. Per restaurar aquest recurs, assegureu-vos que Immich pugui accedir a la ruta del fitxer següent i escanegeu la biblioteca.", - "authentication_settings": "Configuració de l'autenticació", - "authentication_settings_description": "Gestiona la contrasenya, OAuth i altres configuracions de l'autenticació", - "authentication_settings_disable_all": "Estàs segur que vols desactivar tots els mètodes d'inici de sessió? L'inici de sessió quedarà completament desactivat.", - "authentication_settings_reenable": "Per a tornar a habilitar, empra una Comanda de Servidor.", - "background_task_job": "Tasques en segon pla", - "backup_database": "Fer un bolcat de la base de dades", - "backup_database_enable_description": "Habilitar bolcat de la base de dades", - "backup_keep_last_amount": "Quantitat de bolcats anteriors per conservar", - "backup_onboarding_1_description": "còpia externa al núvol o en una altra ubicació física.", - "backup_onboarding_2_description": "còpies locals en diferents dispositius. Això inclou els fitxers principals i una còpia de seguretat d'aquests fitxers localment.", - "backup_onboarding_3_description": "còpies totals de les vostres dades, inclosos els fitxers originals. Això inclou 1 còpia externa i 2 còpies locals.", - "backup_onboarding_description": "Es recomana una estratègia de còpia de seguretat 3-2-1 per protegir les vostres dades. Hauríeu de conservar còpies de les vostres fotos/vídeos penjats, així com de la base de dades Immich per obtenir una solució de còpia de seguretat completa.", - "backup_onboarding_footer": "Per obtenir més informació sobre com fer còpies de seguretat d'Immich, consulteu la documentation.", - "backup_onboarding_parts_title": "Una còpia de seguretat 3-2-1 inclou:", - "backup_onboarding_title": "Còpies de seguretat", - "backup_settings": "Configuració dels bolcats", - "backup_settings_description": "Gestionar la configuració de bolcats de la base de dades. Nota: els treballs no es monitoritzen ni es notifiquen els errors.", - "cleared_jobs": "Tasques esborrades per a: {job}", - "config_set_by_file": "La configuració està definida per un fitxer de configuració", - "confirm_delete_library": "Esteu segurs que voleu eliminar la llibreria {library}?", - "confirm_delete_library_assets": "Esteu segurs que voleu esborrar aquesta llibreria? Això esborrarà {count, plural, one {# contained asset} other {all # contained assets}} d'Immich i no es podrà desfer. Els fitxers romandran al disc.", - "confirm_email_below": "Per a confirmar, escriviu \"{email}\" a sota", - "confirm_reprocess_all_faces": "Esteu segurs que voleu reprocessar totes les cares? Això també esborrarà la gent que heu anomenat.", - "confirm_user_password_reset": "Esteu segur que voleu reinicialitzar la contrasenya de l'usuari {user}?", - "confirm_user_pin_code_reset": "Esteu segur que voleu restablir el codi PIN de {user}?", - "copy_config_to_clipboard_description": "Copia la configuració actual del sistema com a objecte JSON al porta-retalls", - "create_job": "Crear tasca", - "cron_expression": "Expressió Cron", - "cron_expression_description": "Estableix l'interval d'escaneig amb el format cron. Per obtenir més informació, consulteu, p.e Crontab Guru", - "cron_expression_presets": "Ajustos predefinits d'expressions Cron", - "disable_login": "Deshabiliteu l'inici de sessió", - "duplicate_detection_job_description": "Executa l'aprenentatge automàtic en els elements per a detectar imatges semblants. Fa servir la cerca intel·ligent", - "exclusion_pattern_description": "Els patrons d'exclusió permeten ignorar fitxers i carpetes quan escanegeu una llibreria. Això és útil si teniu carpetes que contenen fitxer que no voleu importar, com els fitxers RAW.", - "export_config_as_json_description": "Baixa la configuració actual del sistema com a fitxer JSON", - "external_libraries_page_description": "Pàgina de la biblioteca externa de l'administrador", - "face_detection": "Detecció de cares", - "face_detection_description": "Detecta les cares fent servir aprenentatge automàtic. Per a videos només és té en compte la miniatura. \"Actualitzar\" reprocessa tots els elements. \"Resetejar\" esborra tota la informació de cares actuals. \"Pendent\" afegeix a la cua els elements que encara no han estat processats. Les cares detectades s'afegiran a la cua per al Reconeixement Facial després de completar la Detecció Facial, tot agrupant-les entre noves persones o les ja existents.", - "facial_recognition_job_description": "Agrupa les cares detectades per persona. Aquest pas s'executa després de completar la detecció de cares. \"Resetejar\" reagrupa totes les cares. \"Pendent\" afegeix a la cua les cares que no tenen cap persona assignada.", - "failed_job_command": "La comanda {command} ha fallat per la tasca: {job}", - "force_delete_user_warning": "COMPTE: Aquesta acció eliminara immediatament l'usuari i els seus elements. Aquesta acció és irreversible i els fitxers no es poden recuperar.", - "image_format": "Format", - "image_format_description": "WebP genera fitxers més petits que JPEG, però codifica més lentament.", - "image_fullsize_description": "Imatges a tamany complet sense metadades, utilitzades quan es fa zoom", - "image_fullsize_enabled": "Activa la generació d'imatges a tamany complet", - "image_fullsize_enabled_description": "Genera imatges a tamany complet per formats no compatibles amb la web. Quan \"Prefereix vista prèvia incrustada\" està activat, les vistes prèvies incrustades s'utilitzen directament sense conversió. No afecta els formats compatibles amb la web com JPEG.", - "image_fullsize_quality_description": "De 1 a 100, qualitat de l'imatge a tamany complet. Un valor més alt és millor, però resulta en fitxers de major tamany.", - "image_fullsize_title": "Configuració de les imatges a tamany complet", - "image_prefer_embedded_preview": "Prefereix vista prèvia incrustada", - "image_prefer_embedded_preview_setting_description": "Empra vista prèvia incrustada en les fotografies RAW com a entrada per al processament d'imatge, quan sigui possible. Aquesta acció pot produir colors més acurats en algunes imatges, però la qualitat de la vista prèvia depèn de la càmera i la imatge pot tenir més artefactes de compressió.", - "image_prefer_wide_gamut": "Prefereix la gamma àmplia", - "image_prefer_wide_gamut_setting_description": "Uitlitza Display P3 per a les miniatures. Això preserva més bé la vitalitat de les imatges amb espais de color àmplis, però les imatges es poden veure diferent en aparells antics amb una versió antiga del navegador. Les imatges sRGB romandran com a sRGB per a evitar canvis de color.", - "image_preview_description": "Imatge de mida mitjana amb metadades eliminades, que s'utilitza quan es visualitza un sol recurs i per a l'aprenentatge automàtic", - "image_preview_quality_description": "Vista prèvia de la qualitat de l'1 al 100. Més alt és millor, però produeix fitxers més grans i pot reduir la capacitat de resposta de l'aplicació. Establir un valor baix pot afectar la qualitat de l'aprenentatge automàtic.", - "image_preview_title": "Paràmetres de previsualització", - "image_progressive": "Progressiu", - "image_progressive_description": "Codifica les imatges JPEG progressivament per a una visualització amb càrrega gradual. Això no té cap efecte sobre les imatges WebP.", - "image_quality": "Qualitat", - "image_resolution": "Resolució", - "image_resolution_description": "Les resolucions més altes poden conservar més detalls però triguen més a codificar-se, tenen mides de fitxer més grans i poden reduir la capacitat de resposta de l'aplicació.", - "image_settings": "Configuració de les imatges", - "image_settings_description": "Gestiona la qualitat i resolució de les imatges generades", - "image_thumbnail_description": "Miniatura petita amb metadades eliminades, que s'utilitza quan es visualitzen grups de fotos com la línia de temps principal", - "image_thumbnail_quality_description": "Qualitat de miniatura d'1 a 100. Més alt és millor, però produeix fitxers més grans i pot reduir la capacitat de resposta de l'aplicació.", - "image_thumbnail_title": "Configuració de les miniatures", - "import_config_from_json_description": "Importa la configuració del sistema pujant un fitxer de configuració JSON", - "job_concurrency": "{job} simultàniament", - "job_created": "Tasca creada", - "job_not_concurrency_safe": "Aquesta tasca no és segura per a la conconcurrència.", - "job_settings": "Configuració de les tasques", - "job_settings_description": "Gestiona la concurrència de tasques", - "jobs_delayed": "{jobCount, plural, other {# posposades}}", - "jobs_failed": "{jobCount, plural, other {# fallides}}", - "jobs_over_time": "Feines al llarg del temps", - "library_created": "Bilbioteca creada: {library}", - "library_deleted": "Bilbioteca eliminada", - "library_details": "Detalls de la llibreria", - "library_folder_description": "Especifiqueu una carpeta per importar. Aquesta carpeta, incloses les subcarpetes, s'escanejarà a la recerca d'imatges i vídeos.", - "library_remove_exclusion_pattern_prompt": "Esteu segur que voleu eliminar aquest patró d'exclusió?", - "library_remove_folder_prompt": "Esteu segur que voleu eliminar aquesta carpeta d'importació?", - "library_scanning": "Escaneig periòdic", - "library_scanning_description": "Configurar l'escaneig periòdic de bilbioteques", - "library_scanning_enable_description": "Habilita l'escaneig periòdic de biblioteques", - "library_settings": "Llibreria externes", - "library_settings_description": "Gestiona la configuració de les llibreries externes", - "library_tasks_description": "Escaneja les biblioteques externes per trobar arxius nous o canviats", - "library_updated": "Llibreria actualitzada", - "library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers", - "library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)", - "library_watching_settings_description": "Monitorització automàtica de fitxers modificats", - "logging_enable_description": "Habilitar el registrament", - "logging_level_description": "Quan està habilitat, quin nivell de registre es vol emprar.", - "logging_settings": "Registre", - "machine_learning_availability_checks": "Comprovacions de disponibilitat", - "machine_learning_availability_checks_description": "Detectar i preferir automàticament els servidors d'aprenentatge automàtic disponibles", - "machine_learning_availability_checks_enabled": "Habilita les comprovacions de disponibilitat", - "machine_learning_availability_checks_interval": "Interval de comprovació", - "machine_learning_availability_checks_interval_description": "Interval en mil·lisegons entre comprovacions de disponibilitat", - "machine_learning_availability_checks_timeout": "Temps d'espera de la sol·licitud", - "machine_learning_availability_checks_timeout_description": "Temps d'espera en mil·lisegons per a les comprovacions de disponibilitat", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "El nom d'un model CLIP que apareix a aquí. Tingues en compte que has de tornar a executar la cerca intel·ligent per a totes les imatges quan es canvia de model.", - "machine_learning_duplicate_detection": "Detecció de duplicats", - "machine_learning_duplicate_detection_enabled": "Activa la detecció de duplicats", - "machine_learning_duplicate_detection_enabled_description": "Si està desactivada, els elements idèntics encara es desduplicaran.", - "machine_learning_duplicate_detection_setting_description": "Usa incrustacions CLIP per a trobar prossibles duplicats", - "machine_learning_enabled": "Activa l'aprenentatge automàtic", - "machine_learning_enabled_description": "Si està desactivat, totes les funcions ML es deshabilitaran sense tenir en compte la configuració següent.", - "machine_learning_facial_recognition": "Reconeixement facial", - "machine_learning_facial_recognition_description": "Detecta, reconeix i agrupa cares a les imatges", - "machine_learning_facial_recognition_model": "Model de reconeixement facial", - "machine_learning_facial_recognition_model_description": "Els models es llisten en ordre descent segons la mida. Els models més grans són més lents i usen més memòria però produeixen millors resultats. Tingueu en compte que després de canviar un model haureu de tornar a executar la tasca de detecció de cares per a totes les imatges.", - "machine_learning_facial_recognition_setting": "Activa el reconeixement facial", - "machine_learning_facial_recognition_setting_description": "Si està desactivat, les imatges no es codificaran pel reconeixement facial i no s'afegiran a la secció Persones de la pàgina Explorar.", - "machine_learning_max_detection_distance": "Distància màxima de detecció", - "machine_learning_max_detection_distance_description": "Diferència màxima entre dues imatges per a considerar-les duplicades, en un rang d'entre 0.001-0.1. Com més elevat el valor més detecció de duplicats, però pot resultar en falsos positius.", - "machine_learning_max_recognition_distance": "Màxima diferència de reconeixement", - "machine_learning_max_recognition_distance_description": "La distància màxima entre dues cares per considerar-les la mateixa persona, que oscil·la entre 0-2. Reduir aquest valor pot evitar etiquetar dues persones com la mateixa, mentre que augmentar-lo pot evitar etiquetar la mateixa persona com dues diferents. Tingues en compte que és més fàcil fusionar dues persones que dividir-ne una en dues, així que, si és possible, és millor optar per un llindar més baix.", - "machine_learning_min_detection_score": "Puntuació mínima de detecció", - "machine_learning_min_detection_score_description": "La puntuació mínima de confiança per detectar una cara és de 0 a 1. Valors més baixos detectaran més cares, però poden donar lloc a falsos positius.", - "machine_learning_min_recognized_faces": "Nombre mínim de cares reconegudes", - "machine_learning_min_recognized_faces_description": "El nombre mínim de cares reconegudes per crear una persona. Augmentar aquest valor fa que el reconeixement facial sigui més precís, però augmenta la possibilitat que una cara no sigui assignada a una persona.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Fes servir machine learning per reconèixer text a imatges", - "machine_learning_ocr_enabled": "Activar OCR", - "machine_learning_ocr_enabled_description": "Si està desactivat, les imatges no seran objecte de reconeixement de text.", - "machine_learning_ocr_max_resolution": "Màxima resolució", - "machine_learning_ocr_max_resolution_description": "Vista prèvia per sobre d'aquesta resolució serà reescalada per preservar la relació d'aspecte. Resolucions altes són més precises, però triguen més i gasten més memòria.", - "machine_learning_ocr_min_detection_score": "Puntuació mínima de detecció", - "machine_learning_ocr_min_detection_score_description": "Puntuació de mínima confiança per la detecció del text entre 0-1. Valors baixos detectaran més text pero pot donar falsos positius.", - "machine_learning_ocr_min_recognition_score": "Puntuació mínima de reconeixement", - "machine_learning_ocr_min_score_recognition_description": "Puntuació de confiança mínima pel reconeixement del text entre 0-1. Valors baixos reconeixen més text però pot donar falsos positius.", - "machine_learning_ocr_model": "Model OCR", - "machine_learning_ocr_model_description": "Models de servidor són més precisos que els de móbil, pero triguen més a processar i usen més memòria.", - "machine_learning_settings": "Configuració d'aprenentatge automàtic", - "machine_learning_settings_description": "Gestiona funcions i configuració d'aprenentatge automàtic", - "machine_learning_smart_search": "Cerca intel·ligent", - "machine_learning_smart_search_description": "Cerca imatges semànticament emprant incrustacions CLIP", - "machine_learning_smart_search_enabled": "Activa la cerca intel·ligent", - "machine_learning_smart_search_enabled_description": "Si està desactivada, les imatges no es codificaran per la cerca intel·ligent.", - "machine_learning_url_description": "L'URL del servidor d'aprenentatge automàtic. Si es proporciona més d'un URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.", - "maintenance_delete_backup": "Elimina la còpia de seguretat", - "maintenance_delete_backup_description": "Aquest fitxer s'eliminarà de forma permanent.", - "maintenance_delete_error": "No s'ha pogut suprimir la còpia de seguretat.", - "maintenance_restore_backup": "Restaura la còpia de seguretat", - "maintenance_restore_backup_description": "Immich s'esborrarà i es restaurarà des de la còpia de seguretat escollida. Es crearà una còpia de seguretat abans de continuar.", - "maintenance_restore_backup_different_version": "Aquesta còpia de seguretat s'ha creat amb una versió diferent d'Immich!", - "maintenance_restore_backup_unknown_version": "No s'ha pogut determinar la versió de la còpia de seguretat.", - "maintenance_restore_database_backup": "Restaurar la còpia de seguretat de la base de dades", - "maintenance_restore_database_backup_description": "Reverteix a un estat anterior de la base de dades mitjançant un fitxer de còpia de seguretat", - "maintenance_settings": "En manteniment", - "maintenance_settings_description": "Posar Immich en mode de manteniment.", - "maintenance_start": "Canviar al mode de manteniment", - "maintenance_start_error": "Error en iniciar el mode de manteniment.", - "maintenance_upload_backup": "Puja el fitxer de còpia de seguretat de la base de dades", - "maintenance_upload_backup_error": "No s'ha pogut carregar la còpia de seguretat, és un fitxer .sql/.sql.gz?", - "manage_concurrency": "Gestiona la concurrència", - "manage_concurrency_description": "Ves a la pàgina de tasques per gestionar la concurrència de tasques", - "manage_log_settings": "Gestiona la configuració del registre", - "map_dark_style": "Tema fosc", - "map_enable_description": "Habilita característiques del mapa", - "map_gps_settings": "Configuració del mapa i GPS", - "map_gps_settings_description": "Gestiona la configuració del mapa i GPS (Geocodificació inversa)", - "map_implications": "La funció mapa depèn del servei extern de tesel·les (tiles.immich.cloud)", - "map_light_style": "Tema clar", - "map_manage_reverse_geocoding_settings": "Gestiona els paràmetres de geocodificació inversa", - "map_reverse_geocoding": "Geocodificació inversa", - "map_reverse_geocoding_enable_description": "Habilita la geocodificació inversa", - "map_reverse_geocoding_settings": "Configuració de Geocodificació Inversa", - "map_settings": "Mapa", - "map_settings_description": "Gestiona la configuració del mapa", - "map_style_description": "URL a un tema del mapa style.json", - "memory_cleanup_job": "Neteja de memòries", - "memory_generate_job": "Generació de memòries", - "metadata_extraction_job": "Extreure metadades", - "metadata_extraction_job_description": "Extreu la informació de metadades de cada element, com per exemple el GPS i la resolució", - "metadata_faces_import_setting": "Activar la importació de cares", - "metadata_faces_import_setting_description": "Importar cares des de les metadades EXIF de les imatges i arxius auxiliars", - "metadata_settings": "Configuració de les metadades", - "metadata_settings_description": "Administrar la configuració de les metadades", - "migration_job": "Migració", - "migration_job_description": "Migra les miniatures d'elements i cares cap a la nova estructura de carpetes", - "nightly_tasks_cluster_faces_setting_description": "Executar el reconeixement facial en cares recentment detectades", - "nightly_tasks_cluster_new_faces_setting": "Agrupa cares noves", - "nightly_tasks_database_cleanup_setting": "Tasques de neteja de la base de dades", - "nightly_tasks_database_cleanup_setting_description": "Netegeu les dades antigues i caducades de la base de dades", - "nightly_tasks_generate_memories_setting": "Generar memòries", - "nightly_tasks_generate_memories_setting_description": "Crear nous records a partir de les fotos penjades", - "nightly_tasks_missing_thumbnails_setting": "Generar les miniatures restants", - "nightly_tasks_missing_thumbnails_setting_description": "Posar en cua les fotos penjades sense miniatures per a la generació de la seva miniatura", - "nightly_tasks_settings": "Configuració de les tasques nocturnes", - "nightly_tasks_settings_description": "Gestionar les tasques nocturnes", - "nightly_tasks_start_time_setting": "Hora d'inici", - "nightly_tasks_start_time_setting_description": "Hora en què el servidor comença a executar les tasques nocturnes", - "nightly_tasks_sync_quota_usage_setting": "Sincronitzar l'ús de la quota", - "nightly_tasks_sync_quota_usage_setting_description": "Actualitzar la quota d'emmagatzematge de l'usuari segons l'ús actual", - "no_paths_added": "No s'ha afegit cap ruta", - "no_pattern_added": "Cap patró aplicat", - "note_apply_storage_label_previous_assets": "Nota: Per aplicar l'etiquetatge d'emmagatzematge a elements pujats prèviament, executeu la", - "note_cannot_be_changed_later": "NOTA: Això és irreversible!", - "notification_email_from_address": "Des de l'adreça", - "notification_email_from_address_description": "Adreça de correu electrònic del remitent, per exemple: \"Immich Photo Server \". Assegureu-vos d'utilitzar una adreça des de la qual tingueu permís per enviar correus electrònics.", - "notification_email_host_description": "Amfitrió del servidor de correu electrònic (p.ex. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignora els errors de certificat", - "notification_email_ignore_certificate_errors_description": "Ignora els errors de validació de certificat TLS (no recomanat)", - "notification_email_password_description": "Contrasenya per a autenticar-se amb el servidor de correu electrònic", - "notification_email_port_description": "Port del servidor de correu electrònic (p.ex. 25, 465 o 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Fes servir SMTPS (SMTP sobre TLS)", - "notification_email_sent_test_email_button": "Envia correu de prova i desa", - "notification_email_setting_description": "Configuració per l'enviament de notificacions per correu electrònic", - "notification_email_test_email": "Envia correu de prova", - "notification_email_test_email_failed": "L'enviament del correu de prova ha fallat, comproveu els valors introduïts", - "notification_email_test_email_sent": "Un correu de prova ha estat enviat a {email}. Si us plau, comproveu la safata d'entrada.", - "notification_email_username_description": "Usuari a fer servir a l'autenticar-se al servidor de correu electrònic", - "notification_enable_email_notifications": "Habilita les notificacions de correu electrònic", - "notification_settings": "Configuració de notificacions", - "notification_settings_description": "Gestiona la configuració de notificacions, incloent-hi el correu electrònic", - "oauth_auto_launch": "Execució automàtica", - "oauth_auto_launch_description": "Inicia el flux d'inici de sessió OAuth automàticament en accedir a la pàgina d'inici de sessió", - "oauth_auto_register": "Registre automàtic", - "oauth_auto_register_description": "Registra nous usuaris automàticament després d'iniciar sessió amb OAuth", - "oauth_button_text": "Text del botó", - "oauth_client_secret_description": "Requerit per clients confidencials, o si PKCE (Proof Key for Code Exchange) no està suportat pel client públic.", - "oauth_enable_description": "Iniciar sessió amb OAuth", - "oauth_mobile_redirect_uri": "URI de redirecció mòbil", - "oauth_mobile_redirect_uri_override": "Sobreescriu l'URI de redirecció mòbil", - "oauth_mobile_redirect_uri_override_description": "Habilita quan el proveïdor d'OAuth no permet una URI mòbil, com ara ''{callback}''", - "oauth_role_claim": "Concessió de rol", - "oauth_role_claim_description": "Atorgar accés d'administrador automàticament segons la presència d'aquesta concessió. La concessió pot ser 'usuari' o 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Gestiona la configuració de l'inici de sessió OAuth", - "oauth_settings_more_details": "Per a més detalls sobre aquesta funcionalitat, consulteu la documentació.", - "oauth_storage_label_claim": "Petició d'etiquetatge d'emmagatzematge", - "oauth_storage_label_claim_description": "Estableix automàticament l'etiquetatge d'emmagatzematge de l'usuari a aquest valor.", - "oauth_storage_quota_claim": "Quota d'emmagatzematge reclamada", - "oauth_storage_quota_claim_description": "Estableix automàticament la quota d'emmagatzematge de l'usuari al valor d'aquest paràmetre.", - "oauth_storage_quota_default": "Quota d'emmagatzematge predeterminada (GiB)", - "oauth_storage_quota_default_description": "Quota en GiB que s'utilitzarà quan no es proporcioni cap valor específic.", - "oauth_timeout": "Solicitud caducada", - "oauth_timeout_description": "Timeout per a sol·licituds en mil·lisegons", - "ocr_job_description": "Fes servir machine learning per reconèixer text a les imatges", - "password_enable_description": "Inicia sessió amb correu electrònic i contrasenya", - "password_settings": "Inici de sessió amb contrasenya", - "password_settings_description": "Gestiona la configuració de l'inici de sessió amb contrasenya", - "paths_validated_successfully": "Totes les rutes han estat validades amb èxit", - "person_cleanup_job": "Neteja de persona", - "queue_details": "Detall de les cues", - "queues": "Cues de treball", - "queues_page_description": "Pàgina de cues de treballs d'administrador", - "quota_size_gib": "Tamany de la quota (GiB)", - "refreshing_all_libraries": "Actualitzant totes les biblioteques", - "registration": "Registre d'administrador", - "registration_description": "Com que ets el primer usuari del sistema, seràs designat com a administrador i seràs responsable de les tasques administratives. També seràs l'encarregat de crear usuaris addicionals.", - "remove_failed_jobs": "Eliminar treballs fallits", - "require_password_change_on_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessió", - "reset_settings_to_default": "Restablir configuracions per defecte", - "reset_settings_to_recent_saved": "Restablir la configuració guardada més recent", - "scanning_library": "Escanejant biblioteca", - "search_jobs": "Cercar treballs…", - "send_welcome_email": "Enviar correu electrònic de benvinguda", - "server_external_domain_settings": "Domini extern", - "server_external_domain_settings_description": "Domini per enllaços públics compartits, incloent http(s)://", - "server_public_users": "Usuaris públics", - "server_public_users_description": "Tots els usuaris (nom i correu electrònic) apareixen a la llista a l'afegir un usuari als àlbums compartits. Si es desactiva, la llista només serà disponible pels usuaris administradors.", - "server_settings": "Configuració del servidor", - "server_settings_description": "Gestiona la configuració del servidor", - "server_stats_page_description": "Pàgina d'estadístiques del servidor de l'administrador", - "server_welcome_message": "Missatge de benvinguda", - "server_welcome_message_description": "Missatge que es mostra a la pàgina d'inici de sessió.", - "settings_page_description": "Pàgina de configuració de l'administrador", - "sidecar_job": "Metadades auxiliars", - "sidecar_job_description": "Descobreix o sincronitza metadades auxiliars des del sistema de fitxers", - "slideshow_duration_description": "Segons per mostrar cada imatge", - "smart_search_job_description": "Executa aprenentatge automàtic sobre els elements per donar suport a la cerca intel·ligent", - "storage_template_date_time_description": "La data de creació del recurs s'utilitza per a la informació de la data i l'hora", - "storage_template_date_time_sample": "Temps d'exemple: {date}", - "storage_template_enable_description": "Habilitar el motor de plantilles d'emmagatzematge", - "storage_template_hash_verification_enabled": "Verificació de hash habilitada", - "storage_template_hash_verification_enabled_description": "Activa la verificació de hash. No la desactiveu a menys que estigueu segurs de les implicacions", - "storage_template_migration": "Migració de plantilles d'emmagatzematge", - "storage_template_migration_description": "Aplica la {template} actual als elements pujats prèviament", - "storage_template_migration_info": "Les extensions es convertiran a minúscules. Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la {job}.", - "storage_template_migration_job": "Tasca de migració de la plantilla d'emmagatzematge", - "storage_template_more_details": "Per obtenir més detalls sobre aquesta funció, consulteu la Storage Template i les seves implications", - "storage_template_onboarding_description_v2": "Un cop habilitada, aquesta funció organitzarà automàticament els fitxers a partir d'una plantilla definida per l'usuari. Per a més informació, podeu consultar la documentació.", - "storage_template_path_length": "Límit aproximat de longitud de la ruta: {length, number}/{limit, number}", - "storage_template_settings": "Plantilla d'emmagatzematge", - "storage_template_settings_description": "Gestiona l'estructura de les carpetes i el nom del fitxers dels elements pujats", - "storage_template_user_label": "{label} és l'etiqueta d'emmagatzematge de l'usuari", - "system_settings": "Configuració del sistema", - "tag_cleanup_job": "Neteja d'etiqueta", - "template_email_available_tags": "Pots fer servir les següents variables a la teva plantilla: {tags}", - "template_email_if_empty": "Si la plantilla està buida, es farà servir el correu electrònic predeterminat.", - "template_email_invite_album": "Plantilla per l'àlbum d'invitacions", - "template_email_preview": "Vista prèvia", - "template_email_settings": "Plantilles de correu electrònic", - "template_email_update_album": "Actualitzar la plantilla de l'àlbum", - "template_email_welcome": "Plantilla del correu de benvinguda", - "template_settings": "Plantilles de notificació", - "template_settings_description": "Gestiona les plantilles personalitzades per les notificacions", - "theme_custom_css_settings": "CSS personalitzat", - "theme_custom_css_settings_description": "Els fulls d'estil en cascada permeten personalitzar el disseny d'Immich.", - "theme_settings": "Configuració del tema", - "theme_settings_description": "Gestiona la personalització de la interfície web Immich", - "thumbnail_generation_job": "Generar miniatures", - "thumbnail_generation_job_description": "Genera miniatures grans, petites i borroses per a cada element, així com miniatures per a cada persona", - "transcoding_acceleration_api": "API d'acceleració", - "transcoding_acceleration_api_description": "L'API que interactuarà amb el vostre dispositiu per accelerar la transcodificació. Aquesta configuració és \"millor esforç\": tornarà a la transcodificació del programari en cas d'error. VP9 pot funcionar o no depenent del vostre maquinari.", - "transcoding_acceleration_nvenc": "NVENC (requereix GPU d'NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (requereix GPU d'Intel de 7a generació o posterior)", - "transcoding_acceleration_rkmpp": "RKMPP (requereix SoC de Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Còdecs d'àudio acceptats", - "transcoding_accepted_audio_codecs_description": "Seleccioneu quins còdecs d'àudio no s'han de transcodificar. Només s'utilitza per a determinades polítiques de transcodificació.", - "transcoding_accepted_containers": "Contenidors acceptats", - "transcoding_accepted_containers_description": "Seleccioneu quins formats de contenidor no s'han de redistribuir a MP4. Només s'utilitza per a determinades polítiques de transcodificació.", - "transcoding_accepted_video_codecs": "Còdecs de vídeo acceptats", - "transcoding_accepted_video_codecs_description": "Seleccioneu quins còdecs de vídeo no s'han de transcodificar. Només s'utilitza per a determinades polítiques de transcodificació.", - "transcoding_advanced_options_description": "Opcions que la majoria dels usuaris no haurien de canviar", - "transcoding_audio_codec": "Còdec d'àudio", - "transcoding_audio_codec_description": "Opus és l'opció de màxima qualitat, però té menor compatibilitat amb dispositius o programari antics.", - "transcoding_bitrate_description": "Vídeos superiors a la taxa de bits màxima o que no tenen un format acceptat", - "transcoding_codecs_learn_more": "Per obtenir més informació sobre la terminologia utilitzada, consulteu la documentació de FFmpeg per al còdec H.264, còdec HEVC i còdec VP9.", - "transcoding_constant_quality_mode": "Mode de qualitat constant", - "transcoding_constant_quality_mode_description": "ICQ és millor que CQP, però alguns dispositius d'acceleració de maquinari no admeten aquest mode. Establir aquesta opció preferirà el mode especificat quan utilitzeu la codificació basada en la qualitat. Ignorat per NVENC perquè no és compatible amb ICQ.", - "transcoding_constant_rate_factor": "Factor de taxa constant (-crf)", - "transcoding_constant_rate_factor_description": "Nivell de qualitat del vídeo. Els valors típics són 23 per a H.264, 28 per a HEVC, 31 per a VP9 i 35 per a AV1. Més baix és millor, però produeix fitxers més grans.", - "transcoding_disabled_description": "No transcodifiqueu cap vídeo, pot interrompre la reproducció en alguns clients", - "transcoding_encoding_options": "Opcions de codificació", - "transcoding_encoding_options_description": "Establiu còdecs, resolució, qualitat i altres opcions per als vídeos codificats", - "transcoding_hardware_acceleration": "Acceleració de maquinari", - "transcoding_hardware_acceleration_description": "Experimental: transcodificació més ràpida però pot perdre qualitat amb la mateixa tasa de bits", - "transcoding_hardware_decoding": "Descodificació de maquinari", - "transcoding_hardware_decoding_setting_description": "Habilita l'acceleració d'extrem a extrem en lloc d'accelerar només la codificació. És possible que no funcioni en tots els vídeos.", - "transcoding_max_b_frames": "Nombre màxim de B-frames", - "transcoding_max_b_frames_description": "Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. És possible que no sigui compatible amb l'acceleració de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.", - "transcoding_max_bitrate": "Taxa de bits màxima", - "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Desactivat si s'estableix a 0. Quan no s'especifica, s'assumeix kbit/s; per tant 5000 i 5000k i 5M son equivalents.", - "transcoding_max_keyframe_interval": "Interval màxim de fotogrames clau", - "transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors més baixos empitjoren l'eficiència de la compressió, però milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.", - "transcoding_optimal_description": "Vídeos superiors a la resolució objectiu o que no tenen un format acceptat", - "transcoding_policy": "Política de transcodificació", - "transcoding_policy_description": "Estableix quan es transcodificarà un vídeo", - "transcoding_preferred_hardware_device": "Dispositiu de maquinari preferit", - "transcoding_preferred_hardware_device_description": "S'aplica només a VAAPI i QSV. Estableix el node dri utilitzat per a la transcodificació de maquinari.", - "transcoding_preset_preset": "Preestablert (-preset)", - "transcoding_preset_preset_description": "Velocitat de compressió. Els valors predefinits més lents produeixen fitxers més petits i augmenten la qualitat quan s'orienta a una taxa de bits determinada. VP9 ignora les velocitats superiors a 'més ràpides'.", - "transcoding_reference_frames": "Fotogrames de referència", - "transcoding_reference_frames_description": "El nombre de fotogrames a fer referència en comprimir un fotograma determinat. Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. 0 estableix aquest valor automàticament.", - "transcoding_required_description": "Només vídeos que no tenen un format acceptat", - "transcoding_settings": "Configuració de transcodificació de vídeo", - "transcoding_settings_description": "Gestionar quins vídeos transcodificar i com processar-los", - "transcoding_target_resolution": "Resolució objectiu", - "transcoding_target_resolution_description": "Les resolucions més altes poden conservar més detalls, però triguen més temps a codificar-se, tenen mides de fitxer més grans i poden reduir la capacitat de resposta de l'aplicació.", - "transcoding_temporal_aq": "AQ temporal", - "transcoding_temporal_aq_description": "S'aplica només a NVENC. Quantització adaptativa temporal augmenta la qualitat de les escenes de baix moviment i alt detall. És possible que no sigui compatible amb dispositius antics.", - "transcoding_threads": "Fils", - "transcoding_threads_description": "Els valors més alts condueixen a una codificació més ràpida, però deixen menys espai perquè el servidor processi altres tasques mentre està actiu. Aquest valor no hauria de ser superior al nombre de nuclis de CPU. Maximitza la utilització si s'estableix a 0.", - "transcoding_tone_mapping": "Mapeig de to", - "transcoding_tone_mapping_description": "Intenta preservar l'aspecte dels vídeos HDR quan es converteixen a SDR. Cada algorisme fa diferents compensacions pel color, el detall i la brillantor. Hable conserva els detalls, Mobius conserva el color i Reinhard conserva la brillantor.", - "transcoding_transcode_policy": "Política de transcodificació", - "transcoding_transcode_policy_description": "Política sobre quan s'ha de transcodificar un vídeo. Els vídeos HDR sempre es transcodificaran (excepte si la transcodificació està desactivada).", - "transcoding_two_pass_encoding": "Codificació de dues passades", - "transcoding_two_pass_encoding_setting_description": "Transcodifica en dos passos per produir vídeos millor codificats. Quan la taxa de bits màxima està habilitada (necessari perquè funcioni amb H.264 i HEVC), aquest mode utilitza un interval de velocitat de bits basat en la taxa de bits màxima i ignora CRF. Per a VP9, es pot utilitzar CRF si la taxa de bits màxima està desactivada.", - "transcoding_video_codec": "Còdec de video", - "transcoding_video_codec_description": "VP9 té una alta eficiència i compatibilitat web, però triga més a transcodificar-se. HEVC funciona de manera similar, però té una compatibilitat web inferior. H.264 és àmpliament compatible i de transcodificació ràpida, però produeix fitxers molt més grans. AV1 és el còdec més eficient, però no té suport en dispositius antics.", - "trash_enabled_description": "Activa les funcions de la paperera", - "trash_number_of_days": "Nombre de dies", - "trash_number_of_days_description": "Nombre de dies per mantenir els recursos a la paperera abans de suprimir-los permanentment", - "trash_settings": "Configuració de la paperera", - "trash_settings_description": "Gestiona la configuració de la paperera", - "unlink_all_oauth_accounts": "Desvincula tots els comptes d'OAuth", - "unlink_all_oauth_accounts_description": "Recorda desvincular tots els comptes d'OAuth abans de migrar a un proveïdor nou.", - "unlink_all_oauth_accounts_prompt": "Estàs segur que vols desvincular tots els comptes d'OAuth? Això restablirà l'identificador d'OAuth per a cada usuari i no es pot tornar enrere.", - "user_cleanup_job": "Neteja d'usuari", - "user_delete_delay": "El compte i els recursos de {user} es programaran per a la supressió permanent en {delay, plural, one {# dia} other {# dies}}.", - "user_delete_delay_settings": "Retard de la supressió", - "user_delete_delay_settings_description": "Nombre de dies després de la supressió per eliminar permanentment el compte i els elements d'un usuari. El treball de supressió d'usuaris s'executa a mitjanit per comprovar si hi ha usuaris preparats per eliminar. Els canvis en aquesta configuració s'avaluaran en la propera execució.", - "user_delete_immediately": "El compte i els recursos de {user} es posaran a la cua per suprimir-los permanentment immediatament.", - "user_delete_immediately_checkbox": "Posa en cua l'usuari i els recursos per suprimir-los immediatament", - "user_details": "Detalls d'usuari", - "user_management": "Gestió d'usuaris", - "user_password_has_been_reset": "La contrasenya de l'usuari ha estat restablida:", - "user_password_reset_description": "Si us plau, proporcioneu la contrasenya temporal a l'usuari i informeu-los que haurà de canviar la contrasenya en el proper inici de sessió.", - "user_restore_description": "Es restaurarà el compte {user} .", - "user_restore_scheduled_removal": "Restaura l'usuari - eliminació programada el {date, date, long}", - "user_settings": "Configuració d'usuaris", - "user_settings_description": "Gestiona la configuració dels usuaris", - "user_successfully_removed": "L'usuari {email} s'ha eliminat correctament.", - "users_page_description": "Pàgina d'usuaris de l'administrador", - "version_check_enabled_description": "Activa la comprovació de la versió", - "version_check_implications": "La funció de comprovació de versions depèn de comunicacions periòdiques amb github.com", - "version_check_settings": "Comprovació de versió", - "version_check_settings_description": "Activa/desactiva la notificació de nova versió", - "video_conversion_job": "Transcodificació de vídeos", - "video_conversion_job_description": "Transcodifica vídeos per a major compatibilitat amb navegadors i dispositius" - }, - "admin_email": "Correu de l'administrador", - "admin_password": "Contrasenya de l'administrador", - "administration": "Administració", - "advanced": "Avançat", - "advanced_settings_clear_image_cache": "Esborra la memòria cau de les imatges", - "advanced_settings_clear_image_cache_error": "No s'ha pogut esborrar la memòria cau de les imatges", - "advanced_settings_clear_image_cache_success": "S'ha esborrat correctament {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Feu servir aquesta opció per filtrar els continguts multimèdia durant la sincronització segons criteris alternatius. Només proveu-ho si teniu problemes amb l'aplicació per detectar tots els àlbums.", - "advanced_settings_enable_alternate_media_filter_title": "Utilitza el filtre de sincronització d'àlbums de dispositius alternatius", - "advanced_settings_log_level_title": "Nivell de registre: {level}", - "advanced_settings_prefer_remote_subtitle": "Alguns dispositius són molt lents en carregar miniatures dels elements locals. Activeu aquest paràmetre per carregar imatges remotes en el seu lloc.", - "advanced_settings_prefer_remote_title": "Prefereix imatges remotes", - "advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa", - "advanced_settings_proxy_headers_title": "Capçaleres de proxy particulars [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Habilita el només de lectura mode on les fotos poden ser només vist, a coses els agrada seleccionant imatges múltiples, compartint, càsting, elimina és tot discapacitat. Habilita/Desactiva només de lectura via avatar d'usuari des de la pantalla major", - "advanced_settings_readonly_mode_title": "Mode de només lectura", - "advanced_settings_self_signed_ssl_subtitle": "Omet la verificació del certificat SSL del servidor. Requerit per a certificats autosignats.", - "advanced_settings_self_signed_ssl_title": "Permet certificats SSL autosignats [EXPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Suprimeix o restaura automàticament un actiu en aquest dispositiu quan es realitzi aquesta acció al web", - "advanced_settings_sync_remote_deletions_title": "Sincronitza les eliminacions remotes", - "advanced_settings_tile_subtitle": "Configuració avançada de l'usuari", - "advanced_settings_troubleshooting_subtitle": "Habilita funcions addicionals per a la resolució de problemes", - "advanced_settings_troubleshooting_title": "Resolució de problemes", - "age_months": "{months, plural, one {# mes} other {# mesos}}", - "age_year_months": "Un any i {months, plural, one {# mes} other {# mesos}}", - "age_years": "{years, plural, one {# any} other {# anys}}", - "album": "Àlbum", - "album_added": "Àlbum afegit", - "album_added_notification_setting_description": "Rep una notificació per correu quan siguis afegit a un àlbum compartit", - "album_cover_updated": "Portada de l'àlbum actualitzada", - "album_delete_confirmation": "Esteu segur que voleu suprimir l'àlbum {album}?", - "album_delete_confirmation_description": "Si aquest àlbum es comparteix, els altres usuaris ja no podran accedir-hi.", - "album_deleted": "S'ha suprimit l'àlbum", - "album_info_card_backup_album_excluded": "Exclosos", - "album_info_card_backup_album_included": "Inclosos", - "album_info_updated": "Informació de l'àlbum actualitzada", - "album_leave": "Sortir de l'àlbum?", - "album_leave_confirmation": "N'esteu segur que voleu sortir de {album}?", - "album_name": "Nom de l'àlbum", - "album_options": "Opcions de l'àlbum", - "album_remove_user": "Eliminar l'usuari?", - "album_remove_user_confirmation": "Esteu segurs que voleu eliminar {user}?", - "album_search_not_found": "No s'ha trobat cap àlbum que coincideixi amb la teva cerca", - "album_selected": "Àlbum seleccionat", - "album_share_no_users": "Sembla que has compartit aquest àlbum amb tots els usuaris o no tens cap usuari amb qui compartir-ho.", - "album_summary": "Resum de l'àlbum", - "album_updated": "Àlbum actualitzat", - "album_updated_setting_description": "Rep una notificació per correu electrònic quan un àlbum compartit tingui recursos nous", - "album_upload_assets": "Carrega recursos des del teu ordinador i afegeix-los a l'àlbum", - "album_user_left": "Surt de {album}", - "album_user_removed": "{user} eliminat", - "album_viewer_appbar_delete_confirm": "Confirmes que vols suprimir aquest àlbum del teu compte?", - "album_viewer_appbar_share_err_delete": "Error al esborrar l'àlbum", - "album_viewer_appbar_share_err_leave": "Error al sortir de l'àlbum", - "album_viewer_appbar_share_err_remove": "Hi ha hagut problemes al treure elements de l'àlbum", - "album_viewer_appbar_share_err_title": "Error al modificar el títol de l'àlbum", - "album_viewer_appbar_share_leave": "Surt de l'àlbum", - "album_viewer_appbar_share_to": "Comparteix amb", - "album_viewer_page_share_add_users": "Afegeix usuaris", - "album_with_link_access": "Permet que qualsevol persona que tingui l'enllaç vegi fotos i persones d'aquest àlbum.", - "albums": "Àlbums", - "albums_count": "{count, plural, one {{count, number} Àlbum} other {{count, number} Àlbums}}", - "albums_default_sort_order": "Ordre per defecte de l'àlbum", - "albums_default_sort_order_description": "Ordre de classificació inicial dels recursos al crear àlbums nous.", - "albums_feature_description": "Col·leccions d'actius que es poden compartir amb altres usuaris.", - "albums_on_device_count": "Àlbums al dispositiu ({count})", - "albums_selected": "{count, plural, one {# àlbum seleccionat} other {# àlbums seleccionats}}", - "all": "Tots", - "all_albums": "Tots els àlbum", - "all_people": "Tota la gent", - "all_photos": "Totes les fotografies", - "all_videos": "Tots els vídeos", - "allow_dark_mode": "Permet el tema fosc", - "allow_edits": "Permet editar", - "allow_public_user_to_download": "Permet que l'usuari públic pugui descarregar", - "allow_public_user_to_upload": "Permet que l'usuari públic pugui carregar", - "allowed": "Permès", - "alt_text_qr_code": "Codi QR", - "always_keep": "Mantenir sempre", - "always_keep_photos_hint": "Allibera espai manté totes les fotos en aquest dispositiu.", - "always_keep_videos_hint": "Allibera espai manté tots els vídeos en aquest dispositiu.", - "anti_clockwise": "En sentit antihorari", - "api_key": "Clau API", - "api_key_description": "Aquest valor només es mostrarà una vegada. Assegureu-vos de copiar-lo abans de tancar la finestra.", - "api_key_empty": "El nom de la clau de l'API no pot estar buit", - "api_keys": "Claus API", - "app_architecture_variant": "Variant (Arquitectura)", - "app_bar_signout_dialog_content": "Estàs segur que vols tancar la sessió?", - "app_bar_signout_dialog_ok": "Sí", - "app_bar_signout_dialog_title": "Tanca la sessió", - "app_download_links": "Enllaços de descàrrega de l'App", - "app_settings": "Configuració de l'app", - "app_stores": "Botiga App", - "app_update_available": "Actualització App disponible", - "appears_in": "Apareix a", - "apply_count": "Aplicar ({count, number})", - "archive": "Arxiu", - "archive_action_prompt": "{count} afegit a Arxiu", - "archive_or_unarchive_photo": "Arxivar o desarxivar fotografia", - "archive_page_no_archived_assets": "No s'ha trobat res arxivat", - "archive_page_title": "Arxiu({count})", - "archive_size": "Mida de l'arxiu", - "archive_size_description": "Configureu la mida de l'arxiu de les descàrregues (en GiB)", - "archived": "Arxivat", - "archived_count": "{count, plural, one {Arxivat #} other {Arxivats #}}", - "are_these_the_same_person": "Són la mateixa persona?", - "are_you_sure_to_do_this": "Esteu segurs que voleu fer-ho?", - "array_field_not_fully_supported": "Els camps de matriu requereixen edició JSON manual", - "asset_action_delete_err_read_only": "No es poden esborrar el fitxer(s) de només lectura, ometent", - "asset_action_share_err_offline": "No s'ha pogut obtenir el fitxer(s) sense connexió, ometent", - "asset_added_to_album": "Afegit a l'àlbum", - "asset_adding_to_album": "Afegint a l'àlbum…", - "asset_created": "Recurs creat", - "asset_description_updated": "La descripció del recurs s'ha actualitzat", - "asset_filename_is_offline": "L'element {filename} està fora de línia", - "asset_has_unassigned_faces": "L'element té cares no assignades", - "asset_hashing": "Hasheant…", - "asset_list_group_by_sub_title": "Agrupar per", - "asset_list_layout_settings_dynamic_layout_title": "Disseny dinàmic", - "asset_list_layout_settings_group_automatically": "Automàtic", - "asset_list_layout_settings_group_by": "Agrupa elements per", - "asset_list_layout_settings_group_by_month_day": "Mes + dia", - "asset_list_layout_sub_title": "Disseny", - "asset_list_settings_subtitle": "Configuració del disseny de la graella de fotos", - "asset_list_settings_title": "Graella de fotos", - "asset_not_found_on_device_android": "No s'ha trobat l'actiu al dispositiu", - "asset_not_found_on_device_ios": "No s'ha trobat l'element al dispositiu. Si utilitzes l'iCloud, pot ser que no s'hi pugui accedir perquè el fitxer guardat a l'iCloud és corrupte", - "asset_not_found_on_icloud": "No s'ha trobat l'element a l'iCloud. Pot ser que no s'hi pugui accedir perquè el fitxer guardat a l'iCloud és corrupte", - "asset_offline": "Element fora de línia", - "asset_offline_description": "Aquest recurs extern ja no es troba al disc. Poseu-vos en contacte amb el vostre administrador d'Immich per obtenir ajuda.", - "asset_restored_successfully": "Element recuperat correctament", - "asset_skipped": "Saltat", - "asset_skipped_in_trash": "A la paperera", - "asset_trashed": "Recurs a la paperera", - "asset_troubleshoot": "Diagnòstic de l'element", - "asset_uploaded": "Carregat", - "asset_uploading": "S'està carregant…", - "asset_viewer_settings_subtitle": "Gestiona la configuració del visualitzador de la galeria", - "asset_viewer_settings_title": "Visor d'arxius", - "assets": "Elements", - "assets_added_count": "{count, plural, one {Afegit un element} other {Afegits # elements}}", - "assets_added_to_album_count": "{count, plural, one {Afegit un element} other {Afegits # elements}} a l'àlbum", - "assets_added_to_albums_count": "Afegits {assetTotal, plural, one {# recurs} other {# recursos}} a {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} no es pot afegir a l'àlbum", - "assets_cannot_be_added_to_albums": "{count, plural, one {El recurs} other {Els recursos}} no poden ser afegits a cap dels àlbums", - "assets_count": "{count, plural, one {# recurs} other {# recursos}}", - "assets_deleted_permanently": "{count} element(s) esborrats permanentment", - "assets_deleted_permanently_from_server": "{count} element(s) esborrats permanentment del servidor d'Immich", - "assets_downloaded_failed": "{count, plural, one {S'ha baixat un arxiu - {error} l'arxiu ha fallat} other {S'han baixat # arxius - {error} els arxius han fallat}}", - "assets_downloaded_successfully": "{count, plural, one {S'ha baixat un arxiu amb èxit} other {S'han baixat # arxius amb èxit}}", - "assets_moved_to_trash_count": "{count, plural, one {# recurs mogut} other {# recursos moguts}} a la paperera", - "assets_permanently_deleted_count": "{count, plural, one {# recurs esborrat} other {# recursos esborrats}} permanentment", - "assets_removed_count": "{count, plural, one {# element eliminat} other {# elements eliminats}}", - "assets_removed_permanently_from_device": "{count} element(s) esborrat permanentment del dispositiu", - "assets_restore_confirmation": "Esteu segurs que voleu restaurar tots els teus actius? Aquesta acció no es pot desfer! Tingueu en compte que els recursos fora de línia no es poden restaurar d'aquesta manera.", - "assets_restored_count": "{count, plural, one {# element restaurat} other {# elements restaurats}}", - "assets_restored_successfully": "{count} element(s) recuperats correctament", - "assets_trashed": "{count} element(s) enviat a la paperera", - "assets_trashed_count": "{count, plural, one {# element enviat} other {# elements enviats}} a la paperera", - "assets_trashed_from_server": "{count} element(s) enviat a la paperera del servidor d'Immich", - "assets_were_part_of_album_count": "{count, plural, one {L'element ja és} other {Els elements ja són}} part de l'àlbum", - "assets_were_part_of_albums_count": "{count, plural, one {El recurs ja formava} other {Els recursos ja formaven}} part dels àlbums", - "authorized_devices": "Dispositius autoritzats", - "automatic_endpoint_switching_subtitle": "Connecteu-vos localment a través de la Wi-Fi designada quan estigui disponible i utilitzeu connexions alternatives en altres llocs", - "automatic_endpoint_switching_title": "Canvi automàtic d'URL", - "autoplay_slideshow": "Reprodueix automàticament les diapositives", - "back": "Enrere", - "back_close_deselect": "Tornar, tancar o anul·lar la selecció", - "background_backup_running_error": "La còpia de seguretat en segon pla s'està executant actualment, no es pot iniciar la còpia de seguretat manual", - "background_location_permission": "Permís d'ubicació en segon pla", - "background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accés a la ubicació precisa perquè l'aplicació pugui llegir el nom de la xarxa Wi-Fi", - "background_options": "Opcions en segon pla", - "backup": "Còpia", - "backup_album_selection_page_albums_device": "Àlbums al dispositiu ({count})", - "backup_album_selection_page_albums_tap": "Un toc per incloure, doble toc per excloure", - "backup_album_selection_page_assets_scatter": "Els elements poden dispersar-se en diversos àlbums. Per tant, els àlbums es poden incloure o excloure durant el procés de còpia de seguretat.", - "backup_album_selection_page_select_albums": "Selecciona àlbums", - "backup_album_selection_page_selection_info": "Informació de la selecció", - "backup_album_selection_page_total_assets": "Total d'elements únics", - "backup_albums_sync": "Sincronització de la Còpia de Seguretat d'Àlbums", - "backup_all": "Tots", - "backup_background_service_backup_failed_message": "No s'ha pogut copiar els elements. Tornant a intentar…", - "backup_background_service_complete_notification": "Backup completat d'actius", - "backup_background_service_connection_failed_message": "No s'ha pogut connectar al servidor. Tornant a intentar…", - "backup_background_service_current_upload_notification": "Pujant {filename}", - "backup_background_service_default_notification": "Cercant nous elements…", - "backup_background_service_error_title": "Error copiant", - "backup_background_service_in_progress_notification": "Copiant els teus elements…", - "backup_background_service_upload_failure_notification": "Error en pujar {filename}", - "backup_controller_page_albums": "Copia els àlbums", - "backup_controller_page_background_app_refresh_disabled_content": "Activa l'actualització en segon pla de l'aplicació a Configuració > General > Actualització en segon pla per utilitzar la copia de seguretat en segon pla.", - "backup_controller_page_background_app_refresh_disabled_title": "Actualització en segon pla desactivada", - "backup_controller_page_background_app_refresh_enable_button_text": "Vés a configuració", - "backup_controller_page_background_battery_info_link": "Mostra'm com", - "backup_controller_page_background_battery_info_message": "Per obtenir la millor experiència de còpia de seguretat en segon pla, desactiveu qualsevol optimització de bateria que restringeixi l'activitat en segon pla per a Immich.\n\nAtès que això és específic del dispositiu, busqueu la informació necessària per al fabricant del vostre dispositiu.", - "backup_controller_page_background_battery_info_ok": "D'acord", - "backup_controller_page_background_battery_info_title": "Optimitzacions de bateria", - "backup_controller_page_background_charging": "Només mentre es carrega", - "backup_controller_page_background_configure_error": "No s'ha pogut configurar el servei en segon pla", - "backup_controller_page_background_delay": "Retard en la còpia de seguretat de nous elements: {duration}", - "backup_controller_page_background_description": "Activeu el servei en segon pla per copiar automàticament tots els nous elements sense haver d'obrir l'aplicació", - "backup_controller_page_background_is_off": "La còpia automàtica en segon pla està desactivada", - "backup_controller_page_background_is_on": "La còpia automàtica en segon pla està activada", - "backup_controller_page_background_turn_off": "Desactiva el servei en segon pla", - "backup_controller_page_background_turn_on": "Activa el servei en segon pla", - "backup_controller_page_background_wifi": "Només amb Wi-Fi", - "backup_controller_page_backup": "Còpia", - "backup_controller_page_backup_selected": "Seleccionat: ", - "backup_controller_page_backup_sub": "Fotografies i vídeos copiats", - "backup_controller_page_created": "Creat el: {date}", - "backup_controller_page_desc_backup": "Activeu la còpia de seguretat per pujar automàticament els nous elements al servidor en obrir l'aplicació.", - "backup_controller_page_excluded": "Exclosos: ", - "backup_controller_page_failed": "Fallats ({count})", - "backup_controller_page_filename": "Nom de l'arxiu: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informació de la còpia", - "backup_controller_page_none_selected": "Cap seleccionat", - "backup_controller_page_remainder": "Restant", - "backup_controller_page_remainder_sub": "Fotografies i vídeos restants per copiar de la selecció", - "backup_controller_page_server_storage": "Emmagatzematge del servidor", - "backup_controller_page_start_backup": "Inicia la còpia", - "backup_controller_page_status_off": "La copia de seguretat està desactivada", - "backup_controller_page_status_on": "La copia de seguretat està activada", - "backup_controller_page_storage_format": "{used} de {total} utilitzats", - "backup_controller_page_to_backup": "Àlbums a copiar", - "backup_controller_page_total_sub": "Totes les fotografies i vídeos dels àlbums seleccionats", - "backup_controller_page_turn_off": "Desactiva la còpia de seguretat", - "backup_controller_page_turn_on": "Activa la còpia de seguretat", - "backup_controller_page_uploading_file_info": "S'està pujant la informació del fitxer", - "backup_err_only_album": "No es pot eliminar l'únic àlbum", - "backup_error_sync_failed": "Sincronització malament, No es pot fer backup.", - "backup_info_card_assets": "elements", - "backup_manual_cancelled": "Cancel·lat", - "backup_manual_in_progress": "La pujada ja està en curs. Torneu-ho a provar més tard", - "backup_manual_success": "Èxit", - "backup_manual_title": "Estat de pujada", - "backup_options": "Opcions de Còpia de Seguretat", - "backup_options_page_title": "Opcions de còpia de seguretat", - "backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla", - "backup_settings_subtitle": "Administra la configuració de pujada", - "backup_upload_details_page_more_details": "Toqueu per obtenir més detalls", - "backward": "Enrere", - "biometric_auth_enabled": "Autentificació biomètrica activada", - "biometric_locked_out": "Esteu bloquejats fora de l'autenticació biomètrica", - "biometric_no_options": "No hi ha opcions biomètriques disponibles", - "biometric_not_available": "L'autenticació biomètrica no està disponible en aquest dispositiu", - "birthdate_saved": "Data de naixement guardada amb èxit", - "birthdate_set_description": "La data de naixement s'utilitza per calcular l'edat d'aquesta persona en el moment d'una foto.", - "blurred_background": "Fons difuminat", - "bugs_and_feature_requests": "Errors i sol·licituds de funcions", - "build": "Construeix", - "build_image": "Construeix la imatge", - "bulk_delete_duplicates_confirmation": "Esteu segurs que voleu suprimir de manera massiva {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això mantindrà el recurs més gran de cada grup i esborrarà permanentment tots els altres duplicats. No podeu desfer aquesta acció!", - "bulk_keep_duplicates_confirmation": "Esteu segur que voleu mantenir {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això resoldrà tots els grups duplicats sense eliminar res.", - "bulk_trash_duplicates_confirmation": "Esteu segur que voleu enviar a les escombraries {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Això mantindrà el recurs més gran de cada grup i eliminarà la resta de duplicats.", - "buy": "Comprar Immich", - "cache_settings_clear_cache_button": "Neteja la memòria cau", - "cache_settings_clear_cache_button_title": "Neteja la memòria cau de l'aplicació. Això impactarà significativament el rendiment fins que la memòria cau es torni a reconstruir.", - "cache_settings_duplicated_assets_clear_button": "NETEJA", - "cache_settings_duplicated_assets_subtitle": "Fotos i vídeos que estan a la llista ignorada de l'aplicació", - "cache_settings_duplicated_assets_title": "Elements duplicats ({count})", - "cache_settings_statistics_album": "Miniatures de la biblioteca", - "cache_settings_statistics_full": "Imatges completes", - "cache_settings_statistics_shared": "Miniatures d'àlbums compartits", - "cache_settings_statistics_thumbnail": "Miniatures", - "cache_settings_statistics_title": "Ús de memòria cau", - "cache_settings_subtitle": "Controla el comportament de la memòria cau de l'aplicació mòbil Immich", - "cache_settings_tile_subtitle": "Controla el comportament de l'emmagatzematge local", - "cache_settings_tile_title": "Emmagatzematge local", - "cache_settings_title": "Configuració de la memòria cau", - "camera": "Càmera", - "camera_brand": "Marca de la càmera", - "camera_model": "Model de càmera", - "cancel": "Cancel·la", - "cancel_search": "Cancel·la la cerca", - "canceled": "Cancel·lat", - "canceling": "Cancel·lant", - "cannot_merge_people": "No es pot fusionar gent", - "cannot_undo_this_action": "Aquesta acció no es pot desfer!", - "cannot_update_the_description": "No es pot actualitzar la descripció", - "cast": "Emet", - "cast_description": "Configurar les destinacions de transmissió disponibles", - "change_date": "Canvia la data", - "change_description": "Canvia la descripció", - "change_display_order": "Canvia l'ordre de visualització", - "change_expiration_time": "Canvia la data d'expiració", - "change_location": "Canvia la ubicació", - "change_name": "Canvia el nom", - "change_name_successfully": "Nom canviat amb èxit", - "change_password": "Canvia la contrasenya", - "change_password_description": "Aquesta és la primera vegada que inicieu la sessió al sistema o s'ha fet una sol·licitud per canviar la contrasenya. Introduïu la nova contrasenya a continuació.", - "change_password_form_confirm_password": "Confirma la contrasenya", - "change_password_form_description": "Hola {name},\n\nAquesta és la primera vegada que inicies sessió al sistema o bé s'ha sol·licitat canviar la teva contrasenya. Si us plau, introdueix la nova contrasenya a continuació.", - "change_password_form_log_out": "Fer fora de tots els altres dispositius", - "change_password_form_log_out_description": "Es recomana fer fora de tots els altres dispositius", - "change_password_form_new_password": "Nova contrasenya", - "change_password_form_password_mismatch": "Les contrasenyes no coincideixen", - "change_password_form_reenter_new_password": "Torna a introduir la nova contrasenya", - "change_pin_code": "Canviar el codi PIN", - "change_trigger": "Canvia el desencadenant", - "change_trigger_prompt": "Esteu segur que voleu canviar el disparador? Això eliminarà totes les accions i filtres existents.", - "change_your_password": "Canvia la teva contrasenya", - "changed_visibility_successfully": "Visibilitat canviada amb èxit", - "charging": "Carregant", - "charging_requirement_mobile_backup": "La còpia de seguretat en segon pla requereix que el dispositiu estigui carregant", - "check_corrupt_asset_backup": "Comprovar les còpies de seguretat corruptes", - "check_corrupt_asset_backup_button": "Realitzar comprovació", - "check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.", - "check_logs": "Comprovar els registres", - "checksum": "Suma de control", - "choose_matching_people_to_merge": "Trieu les persones que coincideixin per combinar-les", - "city": "Ciutat", - "cleanup_confirm_description": "Immich ha trobat {count} recursos (creats abans del {date}) carregats adequadament al servidor. Eliminar les còpies locals d'aquest dispositiu?", - "cleanup_confirm_prompt_title": "Eliminar d'aquest dispositiu?", - "cleanup_deleted_assets": "S'han mogut {count} recursos a la paperera del dispositiu", - "cleanup_deleting": "Movent a la paperera...", - "cleanup_found_assets": "S'han trobat {count} recursos amb còpia", - "cleanup_found_assets_with_size": "S'han trobat {count} elements copiats ({size})", - "cleanup_icloud_shared_albums_excluded": "Els àlbums compartits d'iCloud s'exclouen de la cerca", - "cleanup_no_assets_found": "No s'han trobat recursos que coincideixin amb el criteri de sobre. Allibera Espai només pot esborrar elements que s'hagin copiat al servidor", - "cleanup_preview_title": "Recursos a eliminar ({count})", - "cleanup_step3_description": "Cerca fotos i vídeos que ja tinguin una còpia al servidor amb la data de tall i manté els filtres seleccionats.", - "cleanup_step4_summary": "{count} recursos (creats abans del {date}) esborrats del dispositiu local. Les fotografies estaran disponibles a l'aplicació Immich.", - "cleanup_trash_hint": "Per a reclamar l'espai completament, obre la galeria del dispositiu i buida la paperera", - "clear": "Buida", - "clear_all": "Neteja-ho tot", - "clear_all_recent_searches": "Esborra totes les cerques recents", - "clear_file_cache": "Buida la memòria cau de fitxers", - "clear_message": "Neteja el missatge", - "clear_value": "Neteja el valor", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Introdueix la contrasenya", - "client_cert_import": "Importar", - "client_cert_import_success_msg": "S'ha importat el certificat del client", - "client_cert_invalid_msg": "Fitxer de certificat no vàlid o contrasenya incorrecta", - "client_cert_remove_msg": "S'ha eliminat el certificat del client", - "client_cert_subtitle": "Només admet el format PKCS12 (.p12, .pfx). La importació/eliminació de certificats només està disponible abans d'iniciar sessió", - "client_cert_title": "Certificat de client SSL", - "clockwise": "En sentit horari", - "close": "Tanca", - "collapse": "Tanca", - "collapse_all": "Redueix-ho tot", - "color": "Color", - "color_theme": "Tema de color", - "command": "Ordre", - "comment_deleted": "Comentari esborrat", - "comment_options": "Opcions de comentari", - "comments_and_likes": "Comentaris i agradaments", - "comments_are_disabled": "Els comentaris estan desactivats", - "common_create_new_album": "Crea un àlbum nou", - "completed": "Completat", - "confirm": "Confirmar", - "confirm_admin_password": "Confirmeu la contrasenya d'administrador", - "confirm_delete_face": "Estàs segur que vols eliminar la cara de {name} de les cares reconegudes?", - "confirm_delete_shared_link": "Esteu segurs que voleu eliminar aquest enllaç compartit?", - "confirm_keep_this_delete_others": "Excepte aquest element, tots els altres de la pila se suprimiran. Esteu segur que voleu continuar?", - "confirm_new_pin_code": "Confirma el nou codi PIN", - "confirm_password": "Confirmació de contrasenya", - "confirm_tag_face": "Vols etiquetar aquesta cara com a {name}?", - "confirm_tag_face_unnamed": "Com vols etiquetar aquesta cara?", - "connected_device": "Dispositiu connectat", - "connected_to": "Connectat a", - "contain": "Contingut", - "context": "Context", - "continue": "Continuar", - "control_bottom_app_bar_create_new_album": "Crea un àlbum nou", - "control_bottom_app_bar_delete_from_immich": "Suprimeix del Immich", - "control_bottom_app_bar_delete_from_local": "Suprimeix del dispositiu", - "control_bottom_app_bar_edit_location": "Edita la ubicació", - "control_bottom_app_bar_edit_time": "Edita data i hora", - "control_bottom_app_bar_share_link": "Comparteix l'enllaç", - "control_bottom_app_bar_share_to": "Comparteix a", - "control_bottom_app_bar_trash_from_immich": "Mou a paperera", - "copied_image_to_clipboard": "Imatge copiada a porta-retalls.", - "copied_to_clipboard": "Copiada a porta-retalls!", - "copy_error": "Error de còpia", - "copy_file_path": "Copia la ruta del fitxer", - "copy_image": "Còpia imatge", - "copy_link": "Còpia l'enllaç", - "copy_link_to_clipboard": "Còpia l'enllaç al porta-retalls", - "copy_password": "Còpia la contrasenya", - "copy_to_clipboard": "Copiar al porta-retalls", - "country": "País", - "cover": "Portada", - "covers": "Portades", - "create": "Crea", - "create_album": "Crear un àlbum", - "create_album_page_untitled": "Sense títol", - "create_api_key": "Crear clau API", - "create_first_workflow": "Crea el primer flux de treball", - "create_library": "Crea una llibreria", - "create_link": "Crear enllaç", - "create_link_to_share": "Crear enllaç per compartir", - "create_link_to_share_description": "Deixa que qualsevol persona amb l'enllaç vegi les fotos seleccionades", - "create_new": "CREAR NOU", - "create_new_person": "Crea una nova persona", - "create_new_person_hint": "Assigna els elements seleccionats a una persona nova", - "create_new_user": "Crea un usuari nou", - "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", - "create_shared_album_page_share_select_photos": "Escull fotografies", - "create_shared_link": "Crea un enllaç compartit", - "create_tag": "Crear etiqueta", - "create_tag_description": "Crear una nova etiqueta. Per les etiquetes aniuades, escriu la ruta comperta de l'etiqueta, incloses les barres diagonals.", - "create_user": "Crea un usuari", - "create_workflow": "Crea un flux de treball", - "created": "Creat", - "created_at": "Creat", - "creating_linked_albums": "Creant àlbums enllaçats...", - "crop": "Retalla", - "crop_aspect_ratio_fixed": "Fixat", - "crop_aspect_ratio_free": "Lliure", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Coses", - "current_device": "Dispositiu actual", - "current_pin_code": "Codi PIN actual", - "current_server_address": "Adreça actual del servidor", - "custom_date": "Data personalitzada", - "custom_locale": "Localització personalitzada", - "custom_locale_description": "Format de dates i números segons la llengua i regió", - "custom_url": "URL personalitzada", - "cutoff_date_description": "Manté fotos des de l'últim…", - "cutoff_day": "{count, plural, one {dia} other {dies}}", - "cutoff_year": "{count, plural, one {any} other {anys}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Fosc", - "dark_theme": "Canviar a tema fosc", - "date": "Data", - "date_after": "Data posterior a", - "date_and_time": "Data i hora", - "date_before": "Data anterior a", - "date_format": "E, d LLL, y • hh:mm", - "date_of_birth_saved": "Data de naixement guardada amb èxit", - "date_range": "Interval de dates", - "day": "Dia", - "days": "Dies", - "deduplicate_all": "Desduplica-ho tot", - "deduplication_criteria_1": "Mida d'imatge en bytes", - "deduplication_criteria_2": "Quantitat de dades EXIF", - "deduplication_info": "Informació de deduplicació", - "deduplication_info_description": "Per preseleccionar recursos automàticament i eliminar els duplicats de manera massiva, ens fixem en:", - "default_locale": "Localització predeterminada", - "default_locale_description": "Format de dates i números segons la configuració del navegador", - "delete": "Esborrar", - "delete_action_confirmation_message": "Segur que vols eliminar aquest recurs? Aquesta acció el mourà a la paperera del servidor, i et preguntarà si el vols eliminar localment", - "delete_action_prompt": "{count} eliminats", - "delete_album": "Esborra l'àlbum", - "delete_api_key_prompt": "Esteu segurs que voleu eliminar aquesta clau API?", - "delete_dialog_alert": "Aquests elements seran eliminats de manera permanent d'Immich i del vostre dispositiu", - "delete_dialog_alert_local": "Aquests elements s'eliminaran permanentment del vostre dispositiu, però encara estaran disponibles al servidor Immich", - "delete_dialog_alert_local_non_backed_up": "Alguns dels elements no tenen còpia de seguretat a Immich i s'eliminaran permanentment del dispositiu", - "delete_dialog_alert_remote": "Aquests elements s'eliminaran permanentment del servidor Immich", - "delete_dialog_ok_force": "Suprimeix de totes maneres", - "delete_dialog_title": "Esborra permanentment", - "delete_duplicates_confirmation": "Esteu segurs que voleu eliminar aquests duplicats permanentment?", - "delete_face": "Esborrar cara", - "delete_key": "Suprimeix la clau", - "delete_library": "Suprimeix la Llibreria", - "delete_link": "Esborra l'enllaç", - "delete_local_action_prompt": "{count} eliminats localment", - "delete_local_dialog_ok_backed_up_only": "Esborrar només les que tinguin còpia de seguretat", - "delete_local_dialog_ok_force": "Suprimeix de totes maneres", - "delete_others": "Suprimeix altres", - "delete_permanently": "Eliminar permanentment", - "delete_permanently_action_prompt": "{count} eliminats permanentment", - "delete_shared_link": "Odstranit sdílený odkaz", - "delete_shared_link_dialog_title": "Suprimeix l'enllaç compartit", - "delete_tag": "Eliminar etiqueta", - "delete_tag_confirmation_prompt": "Estàs segur que vols eliminar l'etiqueta {tagName}?", - "delete_user": "Suprimeix l'usuari", - "deleted_shared_link": "Suprimeix l'enllaç compartit", - "deletes_missing_assets": "Elimina els actius que falten del disc", - "description": "Descripció", - "description_input_hint_text": "Afegeix descripció...", - "description_input_submit_error": "S'ha produït un error en actualitzar la descripció, comproveu el registre per a més detalls", - "deselect_all": "Deseleccionar Tots", - "details": "Detalls", - "direction": "Direcció", - "disable": "Desactiva", - "disabled": "Desactivat", - "disallow_edits": "No permetre les edicions", - "discord": "Discord", - "discover": "Descobreix", - "discovered_devices": "Dispositius descoberts", - "dismiss_all_errors": "Descarta tots els errors", - "dismiss_error": "Descarta l'error", - "display_options": "Opcions de visualització", - "display_order": "Ordre de visualització", - "display_original_photos": "Mostra les fotografies originals", - "display_original_photos_setting_description": "Preferiu mostrar la foto original quan visualitzeu un recurs en lloc de miniatures quan el recurs original és compatible amb el web. Això pot provocar una velocitat de visualització de fotos més lenta.", - "do_not_show_again": "No tornis a mostrar aquest missatge", - "documentation": "Documentació", - "done": "Fet", - "download": "Descarregar", - "download_action_prompt": "Baixant {count} recursos", - "download_canceled": "Descàrrega cancel·lada", - "download_complete": "Descàrrega completada", - "download_enqueue": "Descàrrega en cua", - "download_error": "Error de descàrrega", - "download_failed": "Descàrrega ha fallat", - "download_finished": "Descàrrega acabada", - "download_include_embedded_motion_videos": "Vídeos incrustats", - "download_include_embedded_motion_videos_description": "Incloure vídeos incrustats en fotografies en moviment com un arxiu separat", - "download_notfound": "No s'ha trobat la descàrrega", - "download_original": "Descarregar original", - "download_paused": "Descàrrega pausada", - "download_settings": "Descarregar", - "download_settings_description": "Gestioneu la configuració relacionada amb la descàrrega de recursos", - "download_started": "Descàrrega ha començat", - "download_sucess": "Descarregat amb èxit", - "download_sucess_android": "El multimedia s'ha descarregat a DCIM/Immich", - "download_waiting_to_retry": "Esperant per tornar-ho a intentar", - "downloading": "Baixant", - "downloading_asset_filename": "Descarregant l'element {filename}", - "downloading_from_icloud": "Descarregant des d'iCloud", - "downloading_media": "Descàrrega multimèdia", - "drop_files_to_upload": "Deixeu els fitxers a qualsevol lloc per pujar-los", - "duplicates": "Duplicats", - "duplicates_description": "Resol cada grup indicant, si n'hi ha, quins són duplicats", - "duration": "Durada", - "edit": "Editar", - "edit_album": "Edita l'àlbum", - "edit_avatar": "Edita l'avatar", - "edit_birthday": "Edita l'aniversari", - "edit_date": "Edita la data", - "edit_date_and_time": "Edita la data i l'hora", - "edit_date_and_time_action_prompt": "{count} dates i hores editades", - "edit_date_and_time_by_offset": "Canvia la data mitjançant diferència", - "edit_date_and_time_by_offset_interval": "Nou rang de dates: {from}-{to}", - "edit_description": "Edita la descripció", - "edit_description_prompt": "Si us plau, selecciona una nova descripció:", - "edit_exclusion_pattern": "Edita patró d'exclusió", - "edit_faces": "Edita les cares", - "edit_key": "Edita clau", - "edit_link": "Edita enllaç", - "edit_location": "Edita ubicació", - "edit_location_action_prompt": "{count} ubicacions editades", - "edit_location_dialog_title": "Ubicació", - "edit_name": "Edita el nom", - "edit_people": "Edita la gent", - "edit_tag": "Editar etiqueta", - "edit_title": "Edita títol", - "edit_user": "Edita l'usuari", - "edit_workflow": "Edita el flux de treball", - "editor": "Editor", - "editor_close_without_save_prompt": "No es desaran els canvis", - "editor_close_without_save_title": "Tancar l'editor?", - "editor_confirm_reset_all_changes": "Segur que vols reiniciar tots els canvis?", - "editor_flip_horizontal": "Capgira horitzontalment", - "editor_flip_vertical": "Capgira verticalment", - "editor_orientation": "Orientació", - "editor_reset_all_changes": "Reiniciar canvis", - "editor_rotate_left": "Rota 90º al contrari de les agulles", - "editor_rotate_right": "Rota 90º en el sentit de les agulles", - "email": "Correu electrònic", - "email_notifications": "Correu electrònic de notificacions", - "empty_folder": "Aquesta carpeta és buida", - "empty_trash": "Buidar la paperera", - "empty_trash_confirmation": "Esteu segur que voleu buidar la paperera? Això eliminarà tots els recursos a la paperera permanentment d'Immich.\nNo podeu desfer aquesta acció!", - "enable": "Activar", - "enable_backup": "Còpia de Seguretat", - "enable_biometric_auth_description": "Introduïu el codi PIN per a habilitar l'autenticació biomètrica", - "enabled": "Activat", - "end_date": "Data final", - "enqueued": "En cua", - "enter_wifi_name": "Introdueix el nom de Wi-Fi", - "enter_your_pin_code": "Introduïu el codi PIN", - "enter_your_pin_code_subtitle": "Introduïu el codi PIN per a accedir a la carpeta protegida", - "error": "Error", - "error_change_sort_album": "No s'ha pogut canviar l'ordre d'ordenació dels àlbums", - "error_delete_face": "Error esborrant cara de les cares reconegudes", - "error_getting_places": "S'ha produït un error en obtenir els llocs", - "error_loading_albums": "Error en carregar àlbums", - "error_loading_image": "Error carregant la imatge", - "error_loading_partners": "No s'han pogut carregar les parelles: {error}", - "error_retrieving_asset_information": "Error en recuperar la informació de l'actiu", - "error_saving_image": "Error: {error}", - "error_tag_face_bounding_box": "Error a l'etiquetar la cara - no s'han pogut obtenir les coordenades de l'àrea", - "error_title": "Error - Quelcom ha anat malament", - "error_while_navigating": "Error en navegar fins a l'actiu", - "errors": { - "cannot_navigate_next_asset": "No es pot navegar a l'element següent", - "cannot_navigate_previous_asset": "No es pot navegar a l'element anterior", - "cant_apply_changes": "No es poden aplicar els canvis", - "cant_change_activity": "No es pot {enabled, select, true {desactivar} other {activar}} aquesta activitat", - "cant_change_asset_favorite": "No es pot canviar el favorit per a aquest recurs", - "cant_change_metadata_assets_count": "No es poden canviar les metadades {count, plural, one {de l'element} other {dels # elements}}", - "cant_get_faces": "No es poden obtenir les cares", - "cant_get_number_of_comments": "No es pot obtenir el nombre de comentaris", - "cant_search_people": "No es poden cercar persones", - "cant_search_places": "No es poden cercar llocs", - "error_adding_assets_to_album": "Error afegint elements a l'àlbum", - "error_adding_users_to_album": "Error afegint usuaris a l'àlbum", - "error_deleting_shared_user": "S'ha produït un error en suprimir l'usuari compartit", - "error_downloading": "Error descarregant {filename}", - "error_hiding_buy_button": "S'ha produït un error en amagar el botó de compra", - "error_removing_assets_from_album": "Error eliminant els elements de l'àlbum, consulteu la consola per obtenir més detalls", - "error_selecting_all_assets": "Error seleccionant tots els elements", - "exclusion_pattern_already_exists": "Aquest patró d’exclusió ja existeix.", - "failed_to_create_album": "No s'ha pogut crear l'àlbum", - "failed_to_create_shared_link": "No s'ha pogut crear l'enllaç compartit", - "failed_to_edit_shared_link": "No s'ha pogut editar l'enllaç compartit", - "failed_to_get_people": "No s'han pogut aconseguir persones", - "failed_to_keep_this_delete_others": "No s'ha pogut conservar aquest element i suprimir els altres", - "failed_to_load_asset": "No s'ha pogut carregar l'element", - "failed_to_load_assets": "No s'han pogut carregar els elements", - "failed_to_load_notifications": "Error en carregar les notificacions", - "failed_to_load_people": "No s'han pogut carregar les persones", - "failed_to_remove_product_key": "No s'ha pogut eliminar la clau del producte", - "failed_to_reset_pin_code": "No s'ha pogut reiniciar el codi PIN", - "failed_to_stack_assets": "No s'han pogut apilar els elements", - "failed_to_unstack_assets": "No s'han pogut desapilar els elements", - "failed_to_update_notification_status": "Error en actualitzar l'estat de les notificacions", - "incorrect_email_or_password": "Correu electrònic o contrasenya incorrectes", - "library_folder_already_exists": "Aquesta ruta d'importació ja existeix.", - "paths_validation_failed": "{paths, plural, one {# ruta} other {# rutes}} no ha pogut validar", - "profile_picture_transparent_pixels": "Les fotos de perfil no poden tenir píxels transparents. Per favor, feu zoom in, mogueu la imatge o ambdues.", - "quota_higher_than_disk_size": "Heu establert una quota més gran que la mida de disc", - "something_went_wrong": "Alguna cosa ha anat malament", - "unable_to_add_album_users": "No es poden afegir usuaris a l'àlbum", - "unable_to_add_assets_to_shared_link": "No s'han pogut afegir els elements a l'enllaç compartit", - "unable_to_add_comment": "No es pot afegir el comentari", - "unable_to_add_exclusion_pattern": "No s'ha pogut afegir el patró d’exclusió", - "unable_to_add_partners": "No es poden afegir companys", - "unable_to_add_remove_archive": "No s'ha pogut {archived, select, true {eliminar l'element de} other {afegir l'element a}} l'arxiu", - "unable_to_add_remove_favorites": "No s'ha pogut {favorite, select, true {afegir l'element als} other {eliminar l'element dels}} preferits", - "unable_to_archive_unarchive": "No es pot {archived, select, true {arxivar} other {desarxivar}}", - "unable_to_change_album_user_role": "No es pot canviar el rol d'usuari de l'àlbum", - "unable_to_change_date": "No es pot canviar la data", - "unable_to_change_description": "No s'ha pogut canviar la descripció", - "unable_to_change_favorite": "No es pot canviar el favorit per a aquest recurs", - "unable_to_change_location": "No es pot canviar la ubicació", - "unable_to_change_password": "No es pot canviar la contrasenya", - "unable_to_change_visibility": "No es pot canviar la visibilitat de {count, plural, one {# persona} other {# persones}}", - "unable_to_complete_oauth_login": "No es pot completar l'inici de sessió OAuth", - "unable_to_connect": "No pot connectar", - "unable_to_copy_to_clipboard": "No es pot copiar al porta-retalls, assegureu-vos que esteu accedint a la pàgina mitjançant https", - "unable_to_create": "No s'ha pogut crear el flux de treball", - "unable_to_create_admin_account": "No es pot crear un compte d'administrador", - "unable_to_create_api_key": "No es pot crear una clau d'API nova", - "unable_to_create_library": "No es pot crear la llibreria", - "unable_to_create_user": "No es pot crear l'usuari", - "unable_to_delete_album": "No es pot eliminar l'àlbum", - "unable_to_delete_asset": "No es pot suprimir el recurs", - "unable_to_delete_assets": "S'ha produït un error en suprimir recursos", - "unable_to_delete_exclusion_pattern": "No es pot suprimir el patró d'exclusió", - "unable_to_delete_shared_link": "No es pot suprimir l'enllaç compartit", - "unable_to_delete_user": "No es pot eliminar l'usuari", - "unable_to_delete_workflow": "No es pot suprimir el flux de treball", - "unable_to_download_files": "No es poden descarregar fitxers", - "unable_to_edit_exclusion_pattern": "No es pot editar el patró d'exclusió", - "unable_to_empty_trash": "No es pot buidar la paperera", - "unable_to_enter_fullscreen": "No es pot entrar a la pantalla completa", - "unable_to_exit_fullscreen": "No es pot sortir de la pantalla completa", - "unable_to_get_comments_number": "No es pot obtenir el nombre de comentaris", - "unable_to_get_shared_link": "No s'ha pogut obtenir l'enllaç compartit", - "unable_to_hide_person": "No es pot amagar la persona", - "unable_to_link_motion_video": "No es pot enllaçar el vídeo en moviment", - "unable_to_link_oauth_account": "No es pot enllaçar el compte OAuth", - "unable_to_log_out_all_devices": "No es poden tancar la sessió de tots els dispositius", - "unable_to_log_out_device": "No es pot tancar la sessió del dispositiu", - "unable_to_login_with_oauth": "No es pot iniciar sessió amb OAuth", - "unable_to_play_video": "No es pot reproduir el vídeo", - "unable_to_reassign_assets_existing_person": "No es poden reassignar recursos a {name, select, null {una persona existent} other {{name}}}", - "unable_to_reassign_assets_new_person": "No es poden reassignar recursos a una persona nova", - "unable_to_refresh_user": "No es pot actualitzar l'usuari", - "unable_to_remove_album_users": "No es poden eliminar usuaris de l'àlbum", - "unable_to_remove_api_key": "No es pot eliminar la clau de l'API", - "unable_to_remove_assets_from_shared_link": "No es poden eliminar recursos de l'enllaç compartit", - "unable_to_remove_library": "No es pot eliminar la biblioteca", - "unable_to_remove_partner": "No es pot eliminar company/a", - "unable_to_remove_reaction": "No es pot eliminar la reacció", - "unable_to_reset_password": "No es pot restablir la contrasenya", - "unable_to_reset_pin_code": "No es pot restablir el codi PIN", - "unable_to_resolve_duplicate": "No es pot resoldre el duplicat", - "unable_to_restore_assets": "No es poden restaurar els recursos", - "unable_to_restore_trash": "No es pot restaurar la paperera", - "unable_to_restore_user": "No es pot restaurar l'usuari", - "unable_to_save_album": "No es pot desar l'àlbum", - "unable_to_save_api_key": "No es pot desar la clau de l'API", - "unable_to_save_date_of_birth": "No es pot desar la data de naixement", - "unable_to_save_name": "No es pot desar el nom", - "unable_to_save_profile": "No es pot desar el perfil", - "unable_to_save_settings": "No es pot desar la configuració", - "unable_to_scan_libraries": "No es poden escanejar les biblioteques", - "unable_to_scan_library": "No es pot escanejar la biblioteca", - "unable_to_set_feature_photo": "No s'ha pogut configurar la foto destacada", - "unable_to_set_profile_picture": "No es pot configurar la foto de perfil", - "unable_to_set_rating": "No s'ha pogut establir la valoració", - "unable_to_submit_job": "No es pot enviar la tasca", - "unable_to_trash_asset": "No es pot eliminar el recurs a la paperera", - "unable_to_unlink_account": "No es pot desenllaçar el compte", - "unable_to_unlink_motion_video": "No es pot desvincular el vídeo en moviment", - "unable_to_update_album_cover": "No es pot actualitzar la portada de l'àlbum", - "unable_to_update_album_info": "No es pot actualitzar la informació de l'àlbum", - "unable_to_update_library": "No es pot actualitzar la biblioteca", - "unable_to_update_location": "No es pot actualitzar la ubicació", - "unable_to_update_settings": "No es pot actualitzar la configuració", - "unable_to_update_timeline_display_status": "No es pot actualitzar l'estat de visualització de la cronologia", - "unable_to_update_user": "No es pot actualitzar l'usuari", - "unable_to_update_workflow": "No es pot actualitzar el flux de treball", - "unable_to_upload_file": "No es pot carregar el fitxer" - }, - "errors_text": "Errors", - "exclusion_pattern": "Patró d'exclusió", - "exif": "Exif", - "exif_bottom_sheet_description": "Afegeix descripció...", - "exif_bottom_sheet_description_error": "No s'ha pogut actualitzar la descripció", - "exif_bottom_sheet_details": "DETALLS", - "exif_bottom_sheet_location": "UBICACIÓ", - "exif_bottom_sheet_no_description": "Sense descrioció", - "exif_bottom_sheet_people": "PERSONES", - "exif_bottom_sheet_person_add_person": "Afegir nom", - "exit_slideshow": "Surt de la presentació de diapositives", - "expand_all": "Ampliar-ho tot", - "experimental_settings_new_asset_list_subtitle": "Treball en curs", - "experimental_settings_new_asset_list_title": "Habilita la graella de fotos experimental", - "experimental_settings_subtitle": "Utilitzeu-ho sota la vostra responsabilitat!", - "experimental_settings_title": "Experimental", - "expire_after": "Caduca després de", - "expired": "Caducat", - "expires_date": "Caduca el {date}", - "explore": "Explorar", - "explorer": "Explorador", - "export": "Exporta", - "export_as_json": "Exportar com a JSON", - "export_database": "Exportar base de dades", - "export_database_description": "Exportar la base de dades SQLite", - "extension": "Extensió", - "external": "Extern", - "external_libraries": "Llibreries externes", - "external_network": "Xarxa externa", - "external_network_sheet_info": "Quan no estigui a la xarxa Wi-Fi preferida, l'aplicació es connectarà al servidor mitjançant el primer dels URL següents a què pot arribar, començant de dalt a baix", - "face_unassigned": "Sense assignar", - "failed": "Fallat", - "failed_count": "Fallits: {count}", - "failed_to_authenticate": "No s'ha pogut autenticar", - "failed_to_load_assets": "Error carregant recursos", - "failed_to_load_folder": "No s'ha pogut carregar la carpeta", - "favorite": "Preferit", - "favorite_action_prompt": "{count} afegit a Favorits", - "favorite_or_unfavorite_photo": "Foto preferida o no preferida", - "favorites": "Preferits", - "favorites_page_no_favorites": "No s'han trobat preferits", - "feature_photo_updated": "Foto destacada actualitzada", - "features": "Característiques", - "features_in_development": "Funcions en desenvolupament", - "features_setting_description": "Administrar les funcions de l'aplicació", - "file_name_or_extension": "Nom de l'arxiu o extensió", - "file_size": "Mida del fitxer", - "filename": "Nom del fitxer", - "filetype": "Tipus d'arxiu", - "filter": "Filtrar", - "filter_description": "Condicions per filtrar els actius de destinació", - "filter_people": "Filtra persones", - "filter_places": "Filtrar per llocs", - "filters": "Filtres", - "find_them_fast": "Trobeu-los ràpidament pel nom amb la cerca", - "first": "Primer", - "fix_incorrect_match": "Corregiu la coincidència incorrecta", - "folder": "Carpeta", - "folder_not_found": "Carpeta no trobada", - "folders": "Carpetes", - "folders_feature_description": "Explorar la vista de carpetes per les fotos i vídeos del sistema d'arxius", - "forgot_pin_code_question": "Has oblidat el teu PIN?", - "forward": "Endavant", - "free_up_space": "Alliberar Espai", - "free_up_space_description": "Mou fotos i videos que ja tinguen còpia al servidor a la paperera del teu dispositiu per alliberar espai. Les còpies del servidor no es modificaran.", - "free_up_space_settings_subtitle": "Alliberar espai del dispositiu", - "full_path": "Ruta completa: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Aquesta funció carrega recursos externs de Google per funcionar.", - "general": "General", - "geolocation_instruction_location": "Fes click en un element amb coordinades GPS per utilitzar la seva ubicació o selecciona una ubicació des del mapa", - "get_help": "Aconseguir ajuda", - "get_people_error": "S'ha produït un error en aconseguir persones", - "get_wifiname_error": "No s'ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi", - "getting_started": "Començant", - "go_back": "Torna", - "go_to_folder": "Anar al directori", - "go_to_search": "Vés a cercar", - "gps": "GPS", - "gps_missing": "Sense GPS", - "grant_permission": "Concedir permís", - "group_albums_by": "Agrupa àlbums per...", - "group_country": "Agrupar per país", - "group_no": "Cap agrupació", - "group_owner": "Agrupar per propietari", - "group_places_by": "Agrupar llocs per...", - "group_year": "Agrupar per any", - "haptic_feedback_switch": "Activa la resposta hàptica", - "haptic_feedback_title": "Resposta Hàptica", - "has_quota": "Quota", - "hash_asset": "Hash del recurs", - "hashed_assets": "Recursos amb hash", - "hashing": "Generant el hash", - "header_settings_add_header_tip": "Afegeix Capçalera", - "header_settings_field_validator_msg": "El valor no pot estar buit", - "header_settings_header_name_input": "Nom de la capçalera", - "header_settings_header_value_input": "Valor de la capçalera", - "headers_settings_tile_title": "Capçaleres proxy personalitzades", - "height": "Alçada", - "hi_user": "Hola {name} ({email})", - "hide_all_people": "Amaga totes les persones", - "hide_gallery": "Amaga la galeria", - "hide_named_person": "Amaga la persona {name}", - "hide_password": "Amaga la contrasenya", - "hide_person": "Amaga la persona", - "hide_schema": "Amaga l'esquema", - "hide_text_recognition": "Oculta el reconeixement de text", - "hide_unnamed_people": "Amaga persones sense nom", - "home_page_add_to_album_conflicts": "S'han afegit {added} elements a l'àlbum {album}. {failed} elements ja existeixen a l'àlbum.", - "home_page_add_to_album_err_local": "Encara no es poden afegir elements locals als àlbums, ometent", - "home_page_add_to_album_success": "S'han afegit {added} elements a l'àlbum {album}.", - "home_page_album_err_partner": "Encara no es poden afegir elements dels companys als àlbums, ometent", - "home_page_archive_err_local": "Encara no es poden arxivar elements locals, ometent", - "home_page_archive_err_partner": "No es poden arxivar els elements de companys, ometent", - "home_page_building_timeline": "Construint la línia de temps", - "home_page_delete_err_partner": "No es poden suprimir els elements de companys, ometent", - "home_page_delete_remote_err_local": "Elements locals a la selecció d'eliminació remota, ometent", - "home_page_favorite_err_local": "Encara no es pot afegir a preferits elements locals, ometent", - "home_page_favorite_err_partner": "Encara no es pot afegir a preferits elements de companys, ometent", - "home_page_first_time_notice": "Si és la primera vegada que utilitzes l'app, si us plau, assegura't d'escollir un àlbum de còpia de seguretat perquè la línia de temps pugui carregar fotos i vídeos als àlbums", - "home_page_locked_error_local": "No s'han pogut moure els recursos locals a la carpeta bloquejada, saltant", - "home_page_locked_error_partner": "No s'han pogut moure els recursos de la parella a la carpeta bloquejada, saltant", - "home_page_share_err_local": "No es poden compartir els elements locals a través d'un enllaç, ometent", - "home_page_upload_err_limit": "Només es poden pujar un màxim de 30 elements alhora, ometent", - "host": "Amfitrió", - "hour": "Hora", - "hours": "Hores", - "id": "ID", - "idle": "En espera", - "ignore_icloud_photos": "Ignora fotos d'iCloud", - "ignore_icloud_photos_description": "Les fotos emmagatzemades a iCloud no es penjaran al servidor Immich", - "image": "Imatge", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} presa el {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1} el {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} pres/a {person1} amb {person2} el {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1}, {person2}, i {person3} el {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1}, {person2}, i {additionalCount, number} altres el {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} el {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1} el {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1} i {person2} el {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1}, {person2}, i {person3} el {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1}, {person2}, i {additionalCount, number} altres el {date}", - "image_saved_successfully": "Imatge desada", - "image_viewer_page_state_provider_download_started": "Baixada començada", - "image_viewer_page_state_provider_download_success": "Baixada amb èxit", - "image_viewer_page_state_provider_share_error": "Error en compartir", - "immich_logo": "Logotip d'Immich", - "immich_web_interface": "Interfície web Immich", - "import_from_json": "Importar des de JSON", - "import_path": "Ruta d'importació", - "in_albums": "A {count, plural, one {# àlbum} other {# àlbums}}", - "in_archive": "En arxiu", - "in_year": "En {year}", - "in_year_selector": "En", - "include_archived": "Incloure arxivats", - "include_shared_albums": "Inclou àlbums compartits", - "include_shared_partner_assets": "Incloure elements dels companys", - "individual_share": "Compartit individualment", - "individual_shares": "Espais individuals", - "info": "Informació", - "interval": { - "day_at_onepm": "Cada dia a les 13h", - "hours": "Cada {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "Cada mitjanit", - "night_at_twoam": "Cada nit a les 2 del matí" - }, - "invalid_date": "Data invàlida", - "invalid_date_format": "Format de data invàlid", - "invite_people": "Convida gent", - "invite_to_album": "Convida a l'àlbum", - "ios_debug_info_fetch_ran_at": "La recuperació s'ha executat {dateTime}", - "ios_debug_info_last_sync_at": "Darrera sincronització {dateTime}", - "ios_debug_info_no_processes_queued": "No hi ha processos en segon pla en cua", - "ios_debug_info_no_sync_yet": "Encara no s'ha executat cap tasca de sincronització en segon pla", - "ios_debug_info_processes_queued": "{count, plural, one {Un procés en segon pla a la cua} other {{count} processos en segon pla a la cua}}", - "ios_debug_info_processing_ran_at": "El processament s'ha executat {dateTime}", - "items_count": "{count, plural, one {# element} other {# elements}}", - "jobs": "Tasques", - "json_editor": "Editor JSON", - "json_error": "Error en el JSON", - "keep": "Mantenir", - "keep_albums": "Conserva els àlbums", - "keep_albums_count": "Conservant {count} {count, plural, one {àlbum} other {àlbums}}", - "keep_all": "Mantenir-ho tot", - "keep_description": "Tria què es conserva al dispositiu quan s'allibera espai.", - "keep_favorites": "Mantindre els preferits", - "keep_on_device": "Mantén al dispositiu", - "keep_on_device_hint": "Selecciona els elements que vulguis conservar en aquest dispositiu", - "keep_this_delete_others": "Conserveu-ho, suprimiu-ne els altres", - "keeping": "Mantenint: {items}", - "kept_this_deleted_others": "S'ha conservat aquest element i s'han suprimit {count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "Dreceres de teclat", - "language": "Idioma", - "language_no_results_subtitle": "Prova d'ajustar el terme de cerca", - "language_no_results_title": "No s'han trobat idiomes", - "language_search_hint": "Cerca idiomes...", - "language_setting_description": "Seleccioneu el vostre idioma", - "large_files": "Fitxers Grans", - "last": "Últim", - "last_months": "{count, plural, one {Últim mes} other {Últims # mesos}}", - "last_seen": "Vist per últim cop", - "latest_version": "Última versió", - "latitude": "Latitud", - "leave": "Marxar", - "leave_album": "Abandonar àlbum", - "lens_model": "Model de lents", - "let_others_respond": "Deixa que els altres responguin", - "level": "Nivell", - "library": "Bibilioteca", - "library_add_folder": "Afegir carpeta", - "library_edit_folder": "Editar carpeta", - "library_options": "Opcions de biblioteca", - "library_page_device_albums": "Àlbums al Dispositiu", - "library_page_new_album": "Nou àlbum", - "library_page_sort_asset_count": "Quantitat d'elements", - "library_page_sort_created": "Creat més recentment", - "library_page_sort_last_modified": "Darrera modificació", - "library_page_sort_title": "Títol de l'àlbum", - "licenses": "Llicències", - "light": "Llum", - "like": "M'agrada", - "like_deleted": "M'agrada suprimit", - "link_motion_video": "Enllaçar vídeo en moviment", - "link_to_oauth": "Enllaç a OAuth", - "linked_oauth_account": "Compte OAuth enllaçat", - "list": "Llista", - "loading": "Carregant", - "loading_search_results_failed": "No s'han pogut carregar els resultats de la cerca", - "local": "Local", - "local_asset_cast_failed": "No es pot convertir un actiu que no s'ha penjat al servidor", - "local_assets": "Recursos Locals", - "local_id": "ID local", - "local_media_summary": "Resum de Mitjans Locals", - "local_network": "Xarxa local", - "local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada", - "location": "Localització", - "location_permission": "Permís d'ubicació", - "location_permission_content": "Per utilitzar la funció de canvi automàtic, Immich necessita un permís d'ubicació precisa perquè pugui llegir el nom de la xarxa Wi-Fi actual", - "location_picker_choose_on_map": "Escollir en el mapa", - "location_picker_latitude_error": "Introdueix una latitud vàlida", - "location_picker_latitude_hint": "Introdueix aquí la latitud", - "location_picker_longitude_error": "Introdueix una longitud vàlida", - "location_picker_longitude_hint": "Introdueix aquí la longitud", - "lock": "Bloqueja", - "locked_folder": "Carpeta bloquejada", - "log_detail_title": "Detall de Registres", - "log_out": "Tanca la sessió", - "log_out_all_devices": "Tanqueu la sessió de tots els dispositius", - "logged_in_as": "Sessió iniciada com a {user}", - "logged_out_all_devices": "S'ha tancat la sessió de tots els dispositius", - "logged_out_device": "Dispositiu tancat", - "login": "Iniciar sessió", - "login_disabled": "S'ha desactivat l'inici de sessió", - "login_form_api_exception": "Excepció de l'API. Comproveu l'URL del servidor i torneu-ho a provar.", - "login_form_back_button_text": "Enrere", - "login_form_email_hint": "elteu@correu.cat", - "login_form_endpoint_hint": "http://ip-del-servidor:port", - "login_form_endpoint_url": "URL del punt final del servidor", - "login_form_err_http": "Especifica http:// o https://", - "login_form_err_invalid_email": "Adreça de correu electrònic no vàlida", - "login_form_err_invalid_url": "URL no vàlid", - "login_form_err_leading_whitespace": "Espai en blanc al principi", - "login_form_err_trailing_whitespace": "Espai en blanc al final", - "login_form_failed_get_oauth_server_config": "Error en iniciar sessió amb OAuth, comprova l'URL del servidor", - "login_form_failed_get_oauth_server_disable": "La funcionalitat OAuth no està disponible en aquest servidor", - "login_form_failed_login": "Error en iniciar sessió, comprova l'URL del servidor, el correu electrònic i la contrasenya", - "login_form_handshake_exception": "S'ha produït una excepció de handshake amb el servidor. Activa el suport per certificats autofirmats a la configuració si estàs fent servir un certificat autofirmat.", - "login_form_password_hint": "contrasenya", - "login_form_save_login": "Mantingues identificat", - "login_form_server_empty": "Introdueix l'URL del servidor.", - "login_form_server_error": "No s'ha pogut connectar al servidor.", - "login_has_been_disabled": "L'inici de sessió s'ha desactivat.", - "login_password_changed_error": "S'ha produït un error en actualitzar la contrasenya", - "login_password_changed_success": "La contrasenya s'ha canviat correctament", - "logout_all_device_confirmation": "Esteu segur que voleu tancar la sessió de tots els dispositius?", - "logout_this_device_confirmation": "Esteu segur que voleu tancar la sessió d'aquest dispositiu?", - "logs": "Registres", - "longitude": "Longitud", - "look": "Aspecte", - "loop_videos": "Vídeos en bucle", - "loop_videos_description": "Habilita la reproducció en bucle del vídeo en els detalls.", - "main_branch_warning": "Esteu utilitzant una versió en desenvolupament; Recomanem fer servir una versió publicada!", - "main_menu": "Menú principal", - "maintenance_action_restore": "Restaurant la base de dades", - "maintenance_description": "Immich ha estat posat en mode de manteniment.", - "maintenance_end": "Finalitzar el mode de manteniment", - "maintenance_end_error": "Error al finalitzar el mode de manteniment.", - "maintenance_logged_in_as": "Actualment la sessió esta iniciada per {user}", - "maintenance_restore_from_backup": "Restaurar des d'una còpia de seguretat", - "maintenance_restore_library": "Restaura la teva biblioteca", - "maintenance_restore_library_confirm": "Si això sembla correcte, continua restaurant una còpia de seguretat!", - "maintenance_restore_library_description": "Restaurant la còpia de seguretat", - "maintenance_restore_library_folder_has_files": "{folder} conté {count} carpeta/es", - "maintenance_restore_library_folder_no_files": "A {folder} li falten fitxers!", - "maintenance_restore_library_folder_pass": "llegible i escrivible", - "maintenance_restore_library_folder_read_fail": "no llegible", - "maintenance_restore_library_folder_write_fail": "no escrivible", - "maintenance_restore_library_hint_missing_files": "Potser et falten fitxers importants", - "maintenance_restore_library_hint_regenerate_later": "Pots regenerar-los més tard a la configuració", - "maintenance_restore_library_hint_storage_template_missing_files": "Fas servir una plantilla d'emmagatzematge? Potser et falten fitxers", - "maintenance_restore_library_loading": "S'estan carregant les comprovacions d'integritat i heurístiques…", - "maintenance_task_backup": "Creant una còpia de seguretat de la base de dades existent…", - "maintenance_task_migrations": "Executant migracions de bases de dades…", - "maintenance_task_restore": "Restaurant la còpia de seguretat escollida…", - "maintenance_task_rollback": "La restauració ha fallat, s'està tornant al punt de restauració…", - "maintenance_title": "Temporalment inaccessible", - "make": "Fabricant", - "manage_geolocation": "Gestioneu la vostra ubicació", - "manage_media_access_rationale": "Aquest permís es necessari per a la correcta gestió dels actius que es mouen a la paperera i es restauren d'ella.", - "manage_media_access_settings": "Configuració oberta", - "manage_media_access_subtitle": "Permet a l'Immich gestionar i moure fitxers multimèdia.", - "manage_media_access_title": "Accés a la gestió de mitjans", - "manage_shared_links": "Administrar enllaços compartits", - "manage_sharing_with_partners": "Gestiona la compartició amb els companys", - "manage_the_app_settings": "Gestioneu la configuració de l'aplicació", - "manage_your_account": "Gestiona el teu compte", - "manage_your_api_keys": "Gestioneu les vostres claus API", - "manage_your_devices": "Gestioneu els vostres dispositius connectats", - "manage_your_oauth_connection": "Gestioneu la vostra connexió OAuth", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {No hi ha fotos en aquesta àrea} one {# foto} other {# fotos}}", - "map_cannot_get_user_location": "No es pot obtenir la ubicació de l'usuari", - "map_location_dialog_yes": "Sí", - "map_location_picker_page_use_location": "Utilitzar aquesta ubicació", - "map_location_service_disabled_content": "El servei de localització s'ha d'activar per mostrar els elements de la teva ubicació actual. Vols activar-lo ara?", - "map_location_service_disabled_title": "Servei de localització desactivat", - "map_marker_for_images": "Marcador de mapa per a imatges fetes a {city}, {country}", - "map_marker_with_image": "Marcador de mapa amb imatge", - "map_no_location_permission_content": "Es necessita el permís de localització per mostrar els elements de la teva ubicació actual. Vols permetre-ho ara?", - "map_no_location_permission_title": "Permís de localització denegat", - "map_settings": "Paràmetres de mapa", - "map_settings_dark_mode": "Mode fosc", - "map_settings_date_range_option_day": "Últimes 24 hores", - "map_settings_date_range_option_days": "Darrers {days} dies", - "map_settings_date_range_option_year": "Any passat", - "map_settings_date_range_option_years": "Darrers {years} anys", - "map_settings_dialog_title": "Configuració del mapa", - "map_settings_include_show_archived": "Incloure arxivats", - "map_settings_include_show_partners": "Incloure companys", - "map_settings_only_show_favorites": "Mostra només preferits", - "map_settings_theme_settings": "Tema del Mapa", - "map_zoom_to_see_photos": "Allunya per veure fotos", - "mark_all_as_read": "Marcar-ho tot com a llegit", - "mark_as_read": "Marcar com ha llegit", - "marked_all_as_read": "Marcat tot com a llegit", - "matches": "Coincidències", - "matching_assets": "Recursos Coincidents", - "media_type": "Tipus de mitjà", - "memories": "Records", - "memories_all_caught_up": "Posat al dia", - "memories_check_back_tomorrow": "Torna demà per veure més records", - "memories_setting_description": "Gestiona el que veus als teus records", - "memories_start_over": "Torna a començar", - "memories_swipe_to_close": "Llisca per tancar", - "memory": "Record", - "memory_lane_title": "Línia de records {title}", - "menu": "Menú", - "merge": "Combinar", - "merge_people": "Combinar persones", - "merge_people_limit": "Només pots combinar fins a 5 cares alhora", - "merge_people_prompt": "Vols combinar aquestes persones? Aquesta acció és irreversible.", - "merge_people_successfully": "Persones combinades amb èxit", - "merged_people_count": "Combinades {count, plural, one {# persona} other {# persones}}", - "minimize": "Minimitza", - "minute": "Minut", - "minutes": "Minuts", - "mirror_horizontal": "Horitzontal", - "mirror_vertical": "Vertical", - "missing": "Restants", - "mobile_app": "Aplicació mòbil", - "mobile_app_download_onboarding_note": "Descarregar la App de mòbil fent servir les seguents opcions", - "model": "Model", - "month": "Mes", - "monthly_title_text_date_format": "MMMM y", - "more": "Més", - "move": "Moure", - "move_down": "Moure cap avall", - "move_off_locked_folder": "Moure fora de la carpeta bloquejada", - "move_to": "Moure a", - "move_to_device_trash": "Mou a la paperera del dispositiu", - "move_to_lock_folder_action_prompt": "{count} afegides a la carpeta protegida", - "move_to_locked_folder": "Moure a la carpeta bloquejada", - "move_to_locked_folder_confirmation": "Aquestes fotos i vídeos seran eliminades de tots els àlbums, i només podran ser vistes des de la carpeta bloquejada", - "move_up": "Puja", - "moved_to_archive": "S'han mogut {count, plural, one {# asset} other {# assets}} a l'arxiu", - "moved_to_library": "S'ha mogut {count, plural, one {# asset} other {# assets}} a la llibreria", - "moved_to_trash": "S'ha mogut a la paperera", - "multiselect_grid_edit_date_time_err_read_only": "No es pot canviar la data del fitxer(s) de només lectura, ometent", - "multiselect_grid_edit_gps_err_read_only": "No es pot canviar la localització de fitxers de només lectura, saltant", - "mute_memories": "Silenciar records", - "my_albums": "Els meus àlbums", - "name": "Nom", - "name_or_nickname": "Nom o sobrenom", - "name_required": "El nom és obligatori", - "navigate": "Navegar", - "navigate_to_time": "Navegar a un punt en el temps", - "network_requirement_photos_upload": "Fes servir dades mòbils per a còpies de seguretat de fotos", - "network_requirement_videos_upload": "Fes servir dades mòbils per a còpies de seguretat de videos", - "network_requirements": "Requeriments de Xarxa", - "network_requirements_updated": "Han canviat els requeriments de xarxa, reiniciant la cua", - "networking_settings": "Xarxes", - "networking_subtitle": "Gestiona la configuració del endpoint del servidor", - "never": "Mai", - "new_album": "Nou Àlbum", - "new_api_key": "Nova clau de l'API", - "new_date_range": "Navegar a un reng de dates", - "new_password": "Nova contrasenya", - "new_person": "Persona nova", - "new_pin_code": "Nou codi PIN", - "new_pin_code_subtitle": "Aquesta és la primera vegada que accedeixes a la carpeta bloquejada. Crea una codi PIN i accedeix de manera segura a aquesta pàgina", - "new_timeline": "Nova Línia de Temps", - "new_update": "Nova actualització", - "new_user_created": "Nou usuari creat", - "new_version_available": "NOVA VERSIÓ DISPONIBLE", - "newest_first": "El més nou primer", - "next": "Següent", - "next_memory": "Següent record", - "no": "No", - "no_actions_added": "Encara no s'han afegit accions", - "no_albums_found": "No s'han trobat àlbums", - "no_albums_message": "Creeu un àlbum per organitzar les vostres fotos i vídeos", - "no_albums_with_name_yet": "Sembla que encara no tens cap àlbum amb aquest nom.", - "no_albums_yet": "Sembla que encara no tens cap àlbum.", - "no_archived_assets_message": "Arxiveu fotos i vídeos per ocultar-los de Fotos", - "no_assets_message": "Fes clic per pujar la teva primera foto", - "no_assets_to_show": "No hi ha elements per mostrar", - "no_cast_devices_found": "No s'han trobat dispositius per transmetre", - "no_checksum_local": "Cap checksum disponible - no s'han pogut carregar els recursos locals", - "no_checksum_remote": "Cap checksum disponible - no s'ha pogut obtenir el recurs remot", - "no_configuration_needed": "No cal configuració", - "no_devices": "No hi ha dispositius autoritzats", - "no_duplicates_found": "No s'han trobat duplicats.", - "no_exif_info_available": "No hi ha informació d'exif disponible", - "no_explore_results_message": "Penja més fotos per explorar la teva col·lecció.", - "no_favorites_message": "Afegiu preferits per trobar les millors fotos i vídeos a l'instant", - "no_filters_added": "Encara no s'han afegit filtres", - "no_libraries_message": "Creeu una llibreria externa per veure les vostres fotos i vídeos", - "no_local_assets_found": "No s'ha trobat cap recurs local amb aquest checksum", - "no_location_set": "No s'ha definit cap ubicació", - "no_locked_photos_message": "Les fotos i vídeos d'aquesta carpeta estan ocultes, i no es mostraran a mesura que navegues o cerques a la teva biblioteca.", - "no_name": "Sense nom", - "no_notifications": "No hi ha notificacions", - "no_people_found": "No s'han trobat coincidències de persones", - "no_places": "No hi ha llocs", - "no_remote_assets_found": "No s'ha trobat cap recurs remot amb aquest checksum", - "no_results": "Sense resultats", - "no_results_description": "Proveu un sinònim o una paraula clau més general", - "no_shared_albums_message": "Creeu un àlbum per compartir fotos i vídeos amb persones a la vostra xarxa", - "no_uploads_in_progress": "Cap pujada en progrés", - "none": "Cap", - "not_allowed": "No permès", - "not_available": "N/A", - "not_in_any_album": "En cap àlbum", - "not_selected": "No seleccionat", - "note_apply_storage_label_to_previously_uploaded assets": "Nota: per aplicar l'etiqueta d'emmagatzematge als actius penjats anteriorment, executeu el", - "notes": "Notes", - "nothing_here_yet": "No hi ha res encara", - "notification_permission_dialog_content": "Per activar les notificacions, aneu a Configuració i seleccioneu permet.", - "notification_permission_list_tile_content": "Atorga permís per a activar les notificacions.", - "notification_permission_list_tile_enable_button": "Activa les notificacions", - "notification_permission_list_tile_title": "Permís de notificacions", - "notification_toggle_setting_description": "Activa les notificacions per correu electrònic", - "notifications": "Notificacions", - "notifications_setting_description": "Gestiona les notificacions", - "oauth": "OAuth", - "obtainium_configurator": "Configurador Obtainium", - "obtainium_configurator_instructions": "Utilitza Obtainium per instal·lar una actualització a la app directament des de Github-Immich. Crear una clau API i seleccionar una variant per crear un enllaç a la configuració Obtainium", - "ocr": "OCR", - "official_immich_resources": "Recursos oficials d'Immich", - "offline": "Fora de línia", - "offset": "Diferència", - "ok": "D'acord", - "oldest_first": "El més vell primer", - "on_this_device": "En aquest dispositiu", - "onboarding": "Incorporació", - "onboarding_locale_description": "Tria el teu llenguatge preferit. Pots canviar aquesta opció mes tard a la configuració.", - "onboarding_privacy_description": "Les següents funcions (opcionals) depenen de serveis externs i poden desactivarse en qualsevol moment des de la configuració.", - "onboarding_server_welcome_description": "Configurem la instància amb alguns paràmetres comuns.", - "onboarding_theme_description": "Trieu un tema de color per a la vostra instància. Podeu canviar-ho més endavant a la vostra configuració.", - "onboarding_user_welcome_description": "Comencem!", - "onboarding_welcome_user": "Benvingut, {user}", - "online": "En línia", - "only_favorites": "Només preferits", - "open": "Obrir", - "open_in_map_view": "Obrir a la vista del mapa", - "open_in_openstreetmap": "Obre a OpenStreetMap", - "open_the_search_filters": "Obriu els filtres de cerca", - "options": "Opcions", - "or": "o", - "organize_into_albums": "Organitzar en àlbums", - "organize_into_albums_description": "Posar fotos existents en àlbums utilitzant la configuració de sincronització actual", - "organize_your_library": "Organitzeu la llibreria", - "original": "original", - "other": "Altres", - "other_devices": "Altres dispositius", - "other_entities": "Altres entitats", - "other_variables": "Altres variables", - "owned": "Propi", - "owner": "Propietari", - "page": "Pàgina", - "partner": "Company/a", - "partner_can_access": "{partner} hi té accés", - "partner_can_access_assets": "Totes les vostres fotos i vídeos excepte les arxivades i eliminades", - "partner_can_access_location": "Ubicació en què s'han fet les fotos", - "partner_list_user_photos": "fotos de {user}", - "partner_list_view_all": "Veure tot", - "partner_page_empty_message": "Les teves fotos encara no estan compartides amb cap company.", - "partner_page_no_more_users": "No hi ha més usuaris a afegir", - "partner_page_partner_add_failed": "No s'ha pogut afegir el company", - "partner_page_select_partner": "Escull company", - "partner_page_shared_to_title": "Compartit amb", - "partner_page_stop_sharing_content": "{partner} ja no podrà accedir a les teves fotos.", - "partner_sharing": "Compartició amb companys", - "partners": "Companys", - "password": "Contrasenya", - "password_does_not_match": "La contrasenya no coincideix", - "password_required": "Contrasenya requerida", - "password_reset_success": "El restabliment de la contrasenya ha estat correcte", - "past_durations": { - "days": "{days, plural, one {El dia anterior} other {Els # dies anteriors}}", - "hours": "{hours, plural, one {L'última hora} other {Les darreres # hours}}", - "years": "{years, plural, one {L'any passat} other {Els passats # anys}}" - }, - "path": "Ruta", - "pattern": "Patró", - "pause": "Pausa", - "pause_memories": "Pausa els records", - "paused": "En pausa", - "pending": "Pendent", - "people": "Persones", - "people_edits_count": "{count, plural, one {# persona editada} other {# persones editades}}", - "people_feature_description": "Explorar fotos i vídeos agrupades per persona", - "people_selected": "{count, plural, one {# persona seleccionada} other {# persones seleccionades}}", - "people_sidebar_description": "Mostrar un enllaç a Persones a la barra lateral", - "permanent_deletion_warning": "Avís d'eliminació permanent", - "permanent_deletion_warning_setting_description": "Mostrar un avís quan s'eliminin els elements permanentment", - "permanently_delete": "Eliminar permanentment", - "permanently_delete_assets_count": "Eliminar permanentment {count, plural, one {l'element} other {els elements}}", - "permanently_delete_assets_prompt": "Esteu segur que voleu suprimir permanentment {count, plural, one {aquest recurs?} other {aquests # recursos?}} Això també {count, plural, one {el} other {els}} suprimirà del seu àlbum.", - "permanently_deleted_asset": "Element eliminat permanentment", - "permanently_deleted_assets_count": "{count, plural, one {S'ha eliminat # element} other {S'han eliminat # elements}} permanentment", - "permission": "Permís", - "permission_empty": "El seu permís no hauria d'estar buit", - "permission_onboarding_back": "Torna", - "permission_onboarding_continue_anyway": "Continua de totes maneres", - "permission_onboarding_get_started": "Comença", - "permission_onboarding_go_to_settings": "Ves a la configuració", - "permission_onboarding_permission_denied": "S'ha denegat el permís. Per utilitzar Immich, concediu permisos de fotos i vídeos a Configuració.", - "permission_onboarding_permission_granted": "Permís concedit! Tot a punt.", - "permission_onboarding_permission_limited": "Permís limitat. Per a permetre que Immich faci còpies de seguretat i gestioni tota la col·lecció de la galeria, concediu permisos de fotos i vídeos a Configuració.", - "permission_onboarding_request": "Immich requereix permís per veure les vostres fotos i vídeos.", - "person": "Persona", - "person_age_months": "{months, plural, one {# mes} other {# mesos}} d'antiguitat", - "person_age_year_months": "1 any, {months, plural, one {# mes} other {# mesos}} d'antiguitat", - "person_age_years": "{years, plural, other {# anys}} d'antiguitat", - "person_birthdate": "Nascut a {date}", - "person_hidden": "{name}{hidden, select, true { (ocultat)} other {}}", - "person_recognized": "Persona reconeguda", - "person_selected": "Persona seleccionada", - "photo_shared_all_users": "Sembla que has compartit les teves fotos amb tots els usuaris o no tens cap usuari amb qui compartir-les.", - "photos": "Fotos", - "photos_and_videos": "Fotos i vídeos", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", - "photos_from_previous_years": "Fotos d'anys anteriors", - "photos_only": "Només fotos", - "pick_a_location": "Triar una ubicació", - "pick_custom_range": "Rang personalitzat", - "pick_date_range": "Seleccioni un rang de dates", - "pin_code_changed_successfully": "Codi PIN canviat correctament", - "pin_code_reset_successfully": "S'ha restablert correctament el codi PIN", - "pin_code_setup_successfully": "S'ha configurat correctament un codi PIN", - "pin_verification": "Verificació de codi PIN", - "place": "Lloc", - "places": "Llocs", - "places_count": "{count, plural, one {{count, number} Lloc} other {{count, number} Llocs}}", - "play": "Reprodueix", - "play_memories": "Reproduir records", - "play_motion_photo": "Reproduir Fotos en Moviment", - "play_or_pause_video": "Reproduir o posar en pausa el vídeo", - "play_original_video": "Veure el video original", - "play_original_video_setting_description": "Preferir la reproducció del video original sobre el video recodificat. Si el video original no es compatible potser no es reprodueixi correctament.", - "play_transcoded_video": "Veure el video recodificat", - "please_auth_to_access": "Per favor, autentica't per accedir", - "port": "Port", - "preferences_settings_subtitle": "Gestiona les preferències de l'aplicació", - "preferences_settings_title": "Preferències", - "preparing": "Preparant", - "preset": "Preestablert", - "preview": "Previsualització", - "previous": "Anterior", - "previous_memory": "Memòria anterior", - "previous_or_next_day": "Dia endavant/enrere", - "previous_or_next_month": "Mes endavant/enrere", - "previous_or_next_photo": "Foto endavant/enrere", - "previous_or_next_year": "Any endavant/enrere", - "primary": "Primària", - "privacy": "Privacitat", - "profile": "Perfil", - "profile_drawer_app_logs": "Registres", - "profile_drawer_client_server_up_to_date": "El client i el servidor estan actualitzats", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mode només lectura. Feu pulsació llarga a la icona de l'avatar d'usuari per sortir.", - "profile_image_of_user": "Imatge de perfil de {user}", - "profile_picture_set": "Imatge de perfil configurada.", - "public_album": "Àlbum públic", - "public_share": "Compartit públicament", - "purchase_account_info": "Contribuent", - "purchase_activated_subtitle": "Gràcies per donar suport a Immich i al programari de codi obert", - "purchase_activated_time": "Activat el {date}", - "purchase_activated_title": "La teva clau s'ha activat correctament", - "purchase_button_activate": "Activar", - "purchase_button_buy": "Comprar", - "purchase_button_buy_immich": "Compra Immich", - "purchase_button_never_show_again": "No mostrar mai més", - "purchase_button_reminder": "Recordar en 30 dies", - "purchase_button_remove_key": "Elimina la clau", - "purchase_button_select": "Seleccioneu", - "purchase_failed_activation": "No s'ha pogut activar! Si us plau, comproveu el vostre correu electrònic per trobar la clau de producte correcta!", - "purchase_individual_description_1": "Per a un particular", - "purchase_individual_description_2": "Estat de la contribució", - "purchase_individual_title": "Individual", - "purchase_input_suggestion": "Tens una clau de producte? Introduïu la clau a continuació", - "purchase_license_subtitle": "Compra Immich per donar suport al desenvolupament continuat del servei", - "purchase_lifetime_description": "Compra de per vida", - "purchase_option_title": "OPCIONS DE COMPRA", - "purchase_panel_info_1": "Crear Immich requereix molt de temps i esforç, tenim enginyers a temps complet treballant-hi per fer-ho tan bo com sigui possible. La nostra missió és que el programari de codi obert i les pràctiques empresarials ètiques es converteixin en una font d'ingressos sostenible per als desenvolupadors i creïn un ecosistema que respecti la privacitat amb alternatives reals als serveis cloud explotadors.", - "purchase_panel_info_2": "Estem compromesos a no afegir murs de pagament, aquesta compra no us otorgarà cap funció addicional a Immich. Confiem en usuaris com tu per donar suport al desenvolupament continuat d'Immich.", - "purchase_panel_title": "Donar suport al projecte", - "purchase_per_server": "Per servidor", - "purchase_per_user": "Per usuari", - "purchase_remove_product_key": "Elimina la clau del producte", - "purchase_remove_product_key_prompt": "Esteu segur que voleu eliminar la clau del producte?", - "purchase_remove_server_product_key": "Elimina la clau de producte del servidor", - "purchase_remove_server_product_key_prompt": "Esteu segur que voleu eliminar la clau de producte del servidor?", - "purchase_server_description_1": "Per a tot el servidor", - "purchase_server_description_2": "Estat del contribuent", - "purchase_server_title": "Servidor", - "purchase_settings_server_activated": "La clau de producte del servidor la gestiona l'administrador", - "query_asset_id": "Consulta d'identificació d'actius", - "queue_status": "En cua {count}/{total}", - "rate_asset": "Valorar Recurs", - "rating": "Valoració", - "rating_clear": "Esborrar valoració", - "rating_count": "{count, plural, one {# estrella} other {# estrelles}}", - "rating_description": "Mostrar la valoració EXIF al panell d'informació", - "rating_set": "Valoració establerta a {rating, plural, one {# estrella} other {# estrelles}}", - "reaction_options": "Opcions de reacció", - "read_changelog": "Llegeix el registre de canvis", - "readonly_mode_disabled": "Mode de només lectura desactivat", - "readonly_mode_enabled": "Mode de només lectura activat", - "ready_for_upload": "Llest per a pujar", - "reassign": "Reassignar", - "reassigned_assets_to_existing_person": "{count, plural, one {S'ha reassignat # recurs} other {S'han reassignat # recursos}} a {name, select, null {una persona existent} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {S'ha reassignat # recurs} other {S'han reassignat # recursos}} a una persona nova", - "reassing_hint": "Assignar els elements seleccionats a una persona existent", - "recent": "Recent", - "recent-albums": "Àlbums recents", - "recent_searches": "Cerques recents", - "recently_added": "Afegit recentment", - "recently_added_page_title": "Afegit recentment", - "recently_taken": "Fet recentment", - "recently_taken_page_title": "Fet recentment", - "refresh": "Actualitzar", - "refresh_encoded_videos": "Actualitza vídeos codificats", - "refresh_faces": "Actualitzar cares", - "refresh_metadata": "Actualitzar les metadades", - "refresh_thumbnails": "Actualitzar la miniatura", - "refreshed": "Actualitzat", - "refreshes_every_file": "Torna a llegir tots els fitxers existents i nous", - "refreshing_encoded_video": "S'està actualitzant el vídeo codificat", - "refreshing_faces": "Refrescant cares", - "refreshing_metadata": "Actualitzant les metadades", - "regenerating_thumbnails": "Regenerant les miniatures", - "remote": "Remot", - "remote_assets": "Recursos Remots", - "remote_media_summary": "Resum de Mitjans Remots", - "remove": "Eliminar", - "remove_assets_album_confirmation": "Confirmes que vols eliminar {count, plural, one {# recurs} other {# recursos}} de l'àlbum?", - "remove_assets_shared_link_confirmation": "Esteu segur que voleu eliminar {count, plural, one {# recurs} other {# recursos}} d'aquest enllaç compartit?", - "remove_assets_title": "Eliminar els elements?", - "remove_custom_date_range": "Elimina l'interval de dates personalitzat", - "remove_deleted_assets": "Suprimeix fitxers fora de línia", - "remove_from_album": "Treu de l'àlbum", - "remove_from_album_action_prompt": "{count} eliminats de l'àlbum", - "remove_from_favorites": "Eliminar dels preferits", - "remove_from_lock_folder_action_prompt": "{count} eliminades de la carpeta protegida", - "remove_from_locked_folder": "Elimina de la carpeta bloquejada", - "remove_from_locked_folder_confirmation": "Segur que vols moure aquestes fotos i vídeos fora de la carpeta bloquejada? Seran visibles a la teva biblioteca.", - "remove_from_shared_link": "Eliminar de l'enllaç compartit", - "remove_memory": "Eliminar memòria", - "remove_photo_from_memory": "Traieu la foto d'aquesta memòria", - "remove_tag": "Elimina l'etiqueta", - "remove_url": "Eliminar URL", - "remove_user": "Eliminar l'usuari", - "removed_api_key": "Eliminada la clau d'API: {name}", - "removed_from_archive": "Eliminat de l'arxiu", - "removed_from_favorites": "Eliminat dels preferits", - "removed_from_favorites_count": "{count, plural, other {# eliminats}} dels preferits", - "removed_memory": "Memòria esborrada", - "removed_photo_from_memory": "Eliminat foto de la memòria", - "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# actiu} other {# actius}}", - "rename": "Canviar nom", - "repair": "Reparació", - "repair_no_results_message": "Els fitxers sense seguiment o que falten es mostraran aquí", - "replace_with_upload": "Substitueix amb una pujada", - "repository": "Repositori", - "require_password": "Requereix contrasenya", - "require_user_to_change_password_on_first_login": "Demanar que l'usuari canviï la contrasenya en el primer inici de sessió", - "rescan": "Tornar a escanejar", - "reset": "Restablir", - "reset_password": "Restablir contrasenya", - "reset_people_visibility": "Restablir la visibilitat de les persones", - "reset_pin_code": "Restablir el codi PIN", - "reset_pin_code_description": "Si has oblidat el teu codi PIN, pots contactar amb l'administrador del servidor per a reiniciar-lo", - "reset_pin_code_success": "Codi PIN reiniciat correctament", - "reset_pin_code_with_password": "Sempre pots reiniciar el codi PIN amb la teva contrasenya", - "reset_sqlite": "Reiniciar base de dades SQLite", - "reset_sqlite_confirmation": "Segur que vols reiniciar la base de dades SQLite? Hauràs de tancar la sessió i tornar a accedir per a resincronitzar les dades", - "reset_sqlite_success": "S'ha reiniciat la base de dades correctament", - "reset_to_default": "Restableix els valors predeterminats", - "resolution": "Resolució", - "resolve_duplicates": "Resoldre duplicats", - "resolved_all_duplicates": "Tots els duplicats resolts", - "restore": "Recupera", - "restore_all": "Restaurar-ho tot", - "restore_trash_action_prompt": "{count} recuperats de la paperera", - "restore_user": "Restaurar l'usuari", - "restored_asset": "Element restaurat", - "resume": "Reprendre", - "resume_paused_jobs": "Reprèn {count, plural, one {# treball pausat} other {# treballs pausats}}", - "retry_upload": "Torna a provar de pujar", - "review_duplicates": "Revisar duplicats", - "review_large_files": "Revisar fitxers grans", - "role": "Rol", - "role_editor": "Editor", - "role_viewer": "Visor", - "running": "En execució", - "save": "Desa", - "save_to_gallery": "Desa a galeria", - "saved": "Guardat", - "saved_api_key": "Clau d'API guardada", - "saved_profile": "Perfil guardat", - "saved_settings": "Configuració guardada", - "say_something": "Digues quelcom", - "scaffold_body_error_occurred": "S'ha produït un error", - "scan": "Escaneja", - "scan_all_libraries": "Escanejar totes les llibreries", - "scan_library": "Escaneja", - "scan_settings": "Configuració d'escaneig", - "scanning": "Escanejant", - "scanning_for_album": "S'està buscant l'àlbum...", - "search": "Cerca", - "search_albums": "Buscar àlbums", - "search_by_context": "Buscar per context", - "search_by_description": "Cercar per descripció", - "search_by_description_example": "Jornada de senderisme a Sapa", - "search_by_filename": "Cerca per nom de fitxer o extensió", - "search_by_filename_example": "per exemple IMG_1234.JPG o PNG", - "search_by_ocr": "Buscar per OCR", - "search_by_ocr_example": "Després", - "search_camera_lens_model": "Buscar model de lents....", - "search_camera_make": "Buscar per fabricant de càmara...", - "search_camera_model": "Buscar per model de càmera...", - "search_city": "Buscar per ciutat...", - "search_country": "Buscar per país...", - "search_filter_apply": "Aplicar filtre", - "search_filter_camera_title": "Selecciona el tipus de càmera", - "search_filter_date": "Data", - "search_filter_date_interval": "{start} a {end}", - "search_filter_date_title": "Selecciona un rang de dates", - "search_filter_display_option_not_in_album": "No en àlbum", - "search_filter_display_options": "Opcions de Visualització", - "search_filter_filename": "Cerca pel nom del fitxer", - "search_filter_location": "Ubicació", - "search_filter_location_title": "Selecciona l'ubicació", - "search_filter_media_type": "Tipus de multimèdia", - "search_filter_media_type_title": "Selecciona tipus de multimèdia", - "search_filter_ocr": "Buscar per OCR", - "search_filter_people_title": "Selecciona persones", - "search_filter_star_rating": "Classificació per estrelles", - "search_for": "Cercar", - "search_for_existing_person": "Busca una persona existent", - "search_no_more_result": "No més resultats", - "search_no_people": "Cap persona", - "search_no_people_named": "Cap persona anomenada \"{name}\"", - "search_no_result": "No s'han trobat resultats, proveu un terme de cerca o una combinació diferents", - "search_options": "Opcions de cerca", - "search_page_categories": "Categories", - "search_page_motion_photos": "Fotografies animades", - "search_page_no_objects": "No hi ha informació d'objectes disponibles", - "search_page_no_places": "No hi ha informació de llocs disponibles", - "search_page_screenshots": "Captures de pantalla", - "search_page_search_photos_videos": "Cerca les teves fotos i vídeos", - "search_page_selfies": "Autofotos", - "search_page_things": "Coses", - "search_page_view_all_button": "Veure tot", - "search_page_your_activity": "La teva activitat", - "search_page_your_map": "El teu mapa", - "search_people": "Buscar persones", - "search_places": "Buscar llocs", - "search_rating": "Buscar per qualificació...", - "search_result_page_new_search_hint": "Cerca nova", - "search_settings": "Configuració de cerca", - "search_state": "Buscar per regió...", - "search_suggestion_list_smart_search_hint_1": "La cerca intel·ligent està habilitada per defecte, per a cercar metadades utilitzeu la sintaxi ", - "search_suggestion_list_smart_search_hint_2": "m:el-teu-terme-de-cerca", - "search_tags": "Cercant etiquetes...", - "search_timezone": "Buscar per fus horari...", - "search_type": "Buscar per tipus", - "search_your_photos": "Cerca les teves fotos", - "searching_locales": "S'estan cercant localitzacions...", - "second": "Segon", - "see_all_people": "Veure totes les persones", - "select": "Selecciona", - "select_album": "Seleccionar àlbum", - "select_album_cover": "Seleccionar la portada de l'àlbum", - "select_albums": "Seleccionar àlbums", - "select_all": "Selecciona-ho tot", - "select_all_duplicates": "Seleccioneu tots els duplicats", - "select_all_in": "Selecciona tot en {group}", - "select_avatar_color": "Tria color de l'avatar", - "select_count": "{count, plural, one {Selecciona #} other {Selecciona #}}", - "select_cutoff_date": "Seleccionar data de tall", - "select_face": "Selecciona cara", - "select_featured_photo": "Selecciona foto principal", - "select_from_computer": "Seleccionar des de l'ordinador", - "select_keep_all": "Mantén tota la selecció", - "select_library_owner": "Selecciona el propietari de la bilbioteca", - "select_new_face": "Selecciona nova cara", - "select_people": "Seleccionar persones", - "select_person": "Seleccionar persona", - "select_person_to_tag": "Selecciona una persona per etiquetar", - "select_photos": "Tria fotografies", - "select_trash_all": "Envia la selecció a la paperera", - "select_user_for_sharing_page_err_album": "Error al crear l'àlbum", - "selected": "Seleccionat", - "selected_count": "{count, plural, one {# seleccionat} other {# seleccionats}}", - "selected_gps_coordinates": "Seleccio de coordinades GPS", - "send_message": "Envia missatge", - "send_welcome_email": "Envia correu de benvinguda", - "server_endpoint": "Endpoint de Servidor", - "server_info_box_app_version": "Versió de l'aplicació", - "server_info_box_server_url": "URL del servidor", - "server_offline": "Servidor fora de línia", - "server_online": "Servidor en línia", - "server_privacy": "Privadesa del servidor", - "server_restarting_description": "Aquesta pàgina es refrescarà momentàniament.", - "server_restarting_title": "El servidor s'està reiniciant", - "server_stats": "Estadístiques del servidor", - "server_update_available": "Actualització del servidor disponible", - "server_version": "Versió del servidor", - "set": "Establir", - "set_as_album_cover": "Establir com a portada de l'àlbum", - "set_as_featured_photo": "Estableix com a foto destacada", - "set_as_profile_picture": "Establir com a imatge de perfil", - "set_date_of_birth": "Establir data de naixement", - "set_profile_picture": "Establir imatge de perfil", - "set_slideshow_to_fullscreen": "Mostra Diapositives en pantalla completa", - "set_stack_primary_asset": "Estableix com a actiu principal", - "setting_image_viewer_help": "El visor de detalls carrega primer la miniatura petita, després carrega la vista prèvia de mida mitjana (si està habilitada), finalment carrega l'original (si està habilitada).", - "setting_image_viewer_original_subtitle": "Activa per carregar la imatge en resolució original (molt gran!). Desactiva per reduir el consum de dades (tant de xarxa com de memòria cau).", - "setting_image_viewer_original_title": "Carrega la imatge original", - "setting_image_viewer_preview_subtitle": "Activa per carregar una imatge de resolució mitjana. Desactiva per carregar directament la imatge original, o bé utilitzar només la miniatura.", - "setting_image_viewer_preview_title": "Carrega la imatge de vista prèvia", - "setting_image_viewer_title": "Imatges", - "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Canvia el llenguatge de l'aplicació", - "setting_notifications_notify_failures_grace_period": "Notifica les fallades de la còpia de seguretat en segon pla: {duration}", - "setting_notifications_notify_hours": "{count} hores", - "setting_notifications_notify_immediately": "immediatament", - "setting_notifications_notify_minutes": "{count} minuts", - "setting_notifications_notify_never": "mai", - "setting_notifications_notify_seconds": "{count} segons", - "setting_notifications_single_progress_subtitle": "Informació detallada del progrés de la pujada de cada fitxer", - "setting_notifications_single_progress_title": "Mostra el progrés detallat de la còpia de seguretat en segon pla", - "setting_notifications_subtitle": "Ajusta les preferències de notificació", - "setting_notifications_total_progress_subtitle": "Progrés general de la pujada (elements completats/total)", - "setting_notifications_total_progress_title": "Mostra el progrés total de la còpia de seguretat en segon pla", - "setting_video_viewer_auto_play_subtitle": "Comença a veure videos quan s'obrin", - "setting_video_viewer_auto_play_title": "Veure videos automàticament", - "setting_video_viewer_looping_title": "Bucle", - "setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificació disponible. Pot conduir a l'amortització. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuració.", - "setting_video_viewer_original_video_title": "Força el vídeo original", - "settings": "Configuració", - "settings_require_restart": "Si us plau, reinicieu Immich per a aplicar aquest canvi", - "settings_saved": "Configuració desada", - "setup_pin_code": "Configurar un codi PIN", - "share": "Compartir", - "share_action_prompt": "Compartits {count} recursos", - "share_add_photos": "Afegeix fotografies", - "share_assets_selected": "{count} seleccionats", - "share_dialog_preparing": "S'està preparant...", - "share_link": "Compartir Enllaç", - "shared": "Compartit", - "shared_album_activities_input_disable": "Els comentaris estan desactivats", - "shared_album_activity_remove_content": "Voleu eliminar aquesta activitat?", - "shared_album_activity_remove_title": "Elimina l'activitat", - "shared_album_section_people_action_error": "S'ha produït un error en retirar-se/eliminar l'àlbum", - "shared_album_section_people_action_leave": "Elimina l'usuari de l'àlbum", - "shared_album_section_people_action_remove_user": "Elimina l'usuari de l'àlbum", - "shared_album_section_people_title": "PERSONES", - "shared_by": "Compartit per", - "shared_by_user": "Compartit per {user}", - "shared_by_you": "Compartit per tu", - "shared_from_partner": "Fotos de {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Pujat", - "shared_link_app_bar_title": "Enllaços compartits", - "shared_link_clipboard_copied_massage": "S'ha copiat al porta-retalls", - "shared_link_clipboard_text": "Enllaç: {link}\nContrasenya: {password}", - "shared_link_create_error": "S'ha produït un error en crear l'enllaç compartit", - "shared_link_custom_url_description": "Accedeix a aquest enllaç compartit amb una URL personalitzada", - "shared_link_edit_description_hint": "Introduïu la descripció de compartició", - "shared_link_edit_expire_after_option_day": "1 dia", - "shared_link_edit_expire_after_option_days": "{count} dies", - "shared_link_edit_expire_after_option_hour": "1 hora", - "shared_link_edit_expire_after_option_hours": "{count} hores", - "shared_link_edit_expire_after_option_minute": "1 minut", - "shared_link_edit_expire_after_option_minutes": "{count} minuts", - "shared_link_edit_expire_after_option_months": "{count} mesos", - "shared_link_edit_expire_after_option_year": "any {count}", - "shared_link_edit_password_hint": "Introduïu la contrasenya de compartició", - "shared_link_edit_submit_button": "Actualitza l'enllaç", - "shared_link_error_server_url_fetch": "No s'ha pogut obtenir l'URL del servidor", - "shared_link_expires_day": "Caduca d'aquí a {count} dia", - "shared_link_expires_days": "Caduca d'aquí a {count} dies", - "shared_link_expires_hour": "Caduca d'aquí a {count} hora", - "shared_link_expires_hours": "Caduca d'aquí a {count} hores", - "shared_link_expires_minute": "Caduca d'aquí a {count} minut", - "shared_link_expires_minutes": "Caduca d'aquí a {count} minuts", - "shared_link_expires_never": "Caduca ∞", - "shared_link_expires_second": "Caduca d'aquí a {count} segon", - "shared_link_expires_seconds": "Caduca d'aquí a {count} segons", - "shared_link_individual_shared": "Individual compartit", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Gestiona els enllaços compartits", - "shared_link_options": "Opcions d'enllaços compartits", - "shared_link_password_description": "Requereix una contrasenya per accedir a aquest enllaç compartit", - "shared_links": "Enllaços compartits", - "shared_links_description": "Comparteix fotos i vídeos amb un enllaç", - "shared_photos_and_videos_count": "{assetCount, plural, other {# fotos i vídeos compartits.}}", - "shared_with_me": "Compartit amb mi", - "shared_with_partner": "Compartit amb {partner}", - "sharing": "Compartit", - "sharing_enter_password": "Introduïu la contrasenya per veure aquesta pàgina.", - "sharing_page_album": "Àlbums compartits", - "sharing_page_description": "Crea àlbums compartits per compartir fotos i vídeos amb persones de la teva xarxa.", - "sharing_page_empty_list": "LLISTA BUIDA", - "sharing_sidebar_description": "Mostra un enllaç a Compartit a la barra lateral", - "sharing_silver_appbar_create_shared_album": "Crea àlbum compartit", - "sharing_silver_appbar_share_partner": "Comparteix amb un company", - "shift_to_permanent_delete": "premeu ⇧ per suprimir el recurs permanentment", - "show_album_options": "Mostra les opcions d'àlbum", - "show_albums": "Mostrar àlbums", - "show_all_people": "Veure totes les persones", - "show_and_hide_people": "Mostra i amaga persones", - "show_file_location": "Mostra l'ubicació del fitxer", - "show_gallery": "Mostra la galeria", - "show_hidden_people": "Mostra persones ocultes", - "show_in_timeline": "Mostra a la cronologia", - "show_in_timeline_setting_description": "Mostra fotos i vídeos d'aquest usuari a la cronologia", - "show_keyboard_shortcuts": "Mostra dreceres de teclat", - "show_metadata": "Mostra metadades", - "show_or_hide_info": "Mostra o amaga informació", - "show_password": "Mostra contrasenya", - "show_person_options": "Mostra opcions de la persona", - "show_progress_bar": "Mostra barra de progrés", - "show_schema": "Mostrar esquema", - "show_search_options": "Mostra opcions de cerca", - "show_shared_links": "Mostra els enllaços compartits", - "show_slideshow_transition": "Mostra la transició de la presentació de diapositives", - "show_supporter_badge": "Insígnia de contribuent", - "show_supporter_badge_description": "Mostra una insígnia de contributor", - "show_text_recognition": "Mostra el reconeixement de text", - "show_text_search_menu": "Mostra el menú de cerca amb text", - "shuffle": "Mescla", - "sidebar": "Barra lateral", - "sidebar_display_description": "Mostra un enllaç a la vista a la barra lateral", - "sign_out": "Tanca sessió", - "sign_up": "Registrar-se", - "size": "Mida", - "skip_to_content": "Salta al contingut", - "skip_to_folders": "Anar a carpetes", - "skip_to_tags": "Anar a etiquetes", - "slideshow": "Diapositives", - "slideshow_repeat": "Repeteix la presentació de diapositives", - "slideshow_repeat_description": "Torna al principi quan acaba la presentació de diapositives", - "slideshow_settings": "Configuració de diapositives", - "sort_albums_by": "Ordena àlbums per...", - "sort_created": "Data de creació", - "sort_items": "Quantitat d'elements", - "sort_modified": "Data de modificació", - "sort_newest": "Foto més nova", - "sort_oldest": "Foto més antiga", - "sort_people_by_similarity": "Ordenar personar per semblança", - "sort_recent": "Foto més recent", - "sort_title": "Títol", - "source": "Font", - "stack": "Apila", - "stack_action_prompt": "{count} apilats", - "stack_duplicates": "Aplica duplicats", - "stack_select_one_photo": "Selecciona una imatge principal per la pila", - "stack_selected_photos": "Apila les fotos seleccionades", - "stacked_assets_count": "Apilats {count, plural, one {# element} other {# elements}}", - "stacktrace": "Traça de pila", - "start": "Inicia", - "start_date": "Data inicial", - "start_date_before_end_date": "La data d'inici ha de ser abans de la data de fi", - "state": "Regió", - "status": "Estat", - "stop_casting": "Atura la transmisió", - "stop_motion_photo": "Atura foto en moviment", - "stop_photo_sharing": "Deixar de compartir les teves fotos?", - "stop_photo_sharing_description": "{partner} no podrà tornar a accedir a les vostres fotos.", - "stop_sharing_photos_with_user": "Deixa de compartir les fotos amb aquest usuari", - "storage": "Emmagatzematge", - "storage_label": "Etiquetatge d'emmagatzematge", - "storage_quota": "Quota d'emmagatzematge", - "storage_usage": "{used} de {available} en ús", - "submit": "Envia", - "success": "Amb èxit", - "suggestions": "Suggeriments", - "sunrise_on_the_beach": "Albada a la platja", - "support": "Suport", - "support_and_feedback": "Suport i comentaris", - "support_third_party_description": "La vostra instal·lació immich la va empaquetar un tercer. Els problemes que experimenteu poden ser causats per aquest paquet així que, si us plau, plantegeu els poblemes amb ells en primer lloc mitjançant els enllaços següents.", - "swap_merge_direction": "Canvia la direcció d'unió", - "sync": "Sincronitza", - "sync_albums": "Sincronitzar àlbums", - "sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de còpia de seguretat seleccionats", - "sync_local": "Sincronitza Local", - "sync_remote": "Sincronitza Remot", - "sync_status": "Estat de sincronització", - "sync_status_subtitle": "Observa i administra el sistema de sincronització", - "sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich", - "tag": "Etiqueta", - "tag_assets": "Etiquetar actius", - "tag_created": "Etiqueta creada: {tag}", - "tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques", - "tag_not_found_question": "No trobeu una etiqueta? Crear una nova etiqueta", - "tag_people": "Etiquetar personas", - "tag_updated": "Etiqueta actualizada: {tag}", - "tagged_assets": "{count, plural, one {#Etiquetat} other {#Etiquetats}} {count, plural, one {# actiu} other {# actius}}", - "tags": "Etiquetes", - "tap_to_run_job": "Toca per executar el treball", - "template": "Plantilla", - "text_recognition": "Reconeixement de text", - "theme": "Tema", - "theme_selection": "Selecció de tema", - "theme_selection_description": "Activa automàticament el tema fosc o clar en funció de les preferències del sistema del navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostra l'indicador d'emmagatzematge als títols dels elements", - "theme_setting_asset_list_tiles_per_row_title": "Nombre d'elements per fila ({count})", - "theme_setting_colorful_interface_subtitle": "Apliqueu color primari a les superfícies de fons.", - "theme_setting_colorful_interface_title": "Interfície colorida", - "theme_setting_image_viewer_quality_subtitle": "Ajusta la qualitat del visor de detalls d'imatges", - "theme_setting_image_viewer_quality_title": "Qualitat del visor d'imatges", - "theme_setting_primary_color_subtitle": "Trieu un color per a les accions i els accents principals.", - "theme_setting_primary_color_title": "Color primari", - "theme_setting_system_primary_color_title": "Utilitza color de sistema", - "theme_setting_system_theme_switch": "Automàtic (Segueix la configuració del sistema)", - "theme_setting_theme_subtitle": "Trieu la configuració del tema de l'aplicació", - "theme_setting_three_stage_loading_subtitle": "La càrrega en tres etapes podria augmentar el rendiment de càrrega, però causa un consum de xarxa significativament més alt", - "theme_setting_three_stage_loading_title": "Activa la càrrega en tres etapes", - "then": "Aleshores", - "they_will_be_merged_together": "Es combinaran", - "third_party_resources": "Recursos de tercers", - "time": "Temps", - "time_based_memories": "Records basats en el temps", - "time_based_memories_duration": "Quants segons es mostrarà cada imatge.", - "timeline": "Cronologia", - "timezone": "Fus horari", - "to_archive": "Arxivar", - "to_change_password": "Canviar la contrasenya", - "to_favorite": "Prefereix", - "to_login": "Iniciar sessió", - "to_multi_select": "per multi-seleccionar", - "to_parent": "Anar als pares", - "to_select": "per seleccionar", - "to_trash": "Paperera", - "toggle_settings": "Canvia configuració", - "toggle_theme_description": "Commuta el tema", - "total": "Total", - "total_usage": "Ús total", - "trash": "Paperera", - "trash_action_prompt": "{count} mogudes a la brossa", - "trash_all": "Envia-ho tot a la paperera", - "trash_count": "Paperera {count, number}", - "trash_delete_asset": "Esborra/Elimina element", - "trash_emptied": "Paperera buidada", - "trash_no_results_message": "Les imatges i vídeos que s'enviïn a la paperera es mostraran aquí.", - "trash_page_delete_all": "Eliminar-ho tot", - "trash_page_empty_trash_dialog_content": "Segur que voleu eliminar els elements? Aquests elements seran eliminats permanentment de Immich", - "trash_page_info": "Els elements que s'enviïn a la paperera s'eliminaran permanentment després de {days} dies", - "trash_page_no_assets": "No hi ha elements a la paperera", - "trash_page_restore_all": "Restaura-ho tot", - "trash_page_select_assets_btn": "Selecciona elements", - "trash_page_title": "Paperera ({count})", - "trashed_items_will_be_permanently_deleted_after": "Els elements que s'enviïn a la paperera s'eliminaran permanentment després de {days, plural, one {# dia} other {# dies}}.", - "trigger": "Disparador", - "trigger_asset_uploaded": "Mitjà Carregat", - "trigger_asset_uploaded_description": "Es dispara quan un nou mitjà es puge al servidor", - "trigger_description": "L'esdeveniment que inicia l'automatització", - "trigger_person_recognized": "Persona identificada", - "trigger_person_recognized_description": "Es dispara quan es detecta una persona", - "trigger_type": "Tipus de disparador", - "troubleshoot": "Solució de problemes", - "type": "Tipus", - "unable_to_change_pin_code": "No es pot canviar el codi PIN", - "unable_to_check_version": "No es pot comprovar la versió de l'aplicació ni del servidor", - "unable_to_setup_pin_code": "No s'ha pogut configurar el codi PIN", - "unarchive": "Desarxivar", - "unarchive_action_prompt": "{count} eliminades de l'arxiu", - "unarchived_count": "{count, plural, other {# elements desarxivats}}", - "undo": "Desfer", - "unfavorite": "Reverteix preferit", - "unfavorite_action_prompt": "{count} eliminades de preferits", - "unhide_person": "Mostra persona", - "unknown": "Desconegut", - "unknown_country": "País Desconegut", - "unknown_date": "Data desconeguda", - "unknown_year": "Any desconegut", - "unlimited": "Il·limitat", - "unlink_motion_video": "Desvincular vídeo en moviment", - "unlink_oauth": "Desvincula OAuth", - "unlinked_oauth_account": "Compte Oauth desvinculat", - "unmute_memories": "Activar el so dels records", - "unnamed_album": "Àlbum sense nom", - "unnamed_album_delete_confirmation": "Segur que voleu esborrar aquest àlbum?", - "unnamed_share": "Compartit sense nom", - "unsaved_change": "Canvi no desat", - "unselect_all": "Deselecciona-ho tot", - "unselect_all_duplicates": "Desmarqueu tots els duplicats", - "unselect_all_in": "Desseleccionar tots els elements de {group}", - "unstack": "Desapila", - "unstack_action_prompt": "{count} sense apilar", - "unstacked_assets_count": "No apilat {count, plural, one {# recurs} other {# recursos}}", - "unsupported_field_type": "Tipus de camp no suportat", - "untagged": "Sense etiqueta", - "untitled_workflow": "Automatització sense títol", - "up_next": "Pròxim", - "update_location_action_prompt": "Actualitza la ubicació de {count} elements seleccionats amb:", - "updated_at": "Actualitzat", - "updated_password": "Contrasenya actualitzada", - "upload": "Pujar", - "upload_concurrency": "Concurrència de pujades", - "upload_details": "Detalls de la Pujada", - "upload_dialog_info": "Vols fer còpia de seguretat dels elements seleccionats al servidor?", - "upload_dialog_title": "Puja elements", - "upload_error_with_count": "Error en la càrrega de {count, plural, one {# actiu} other {# actius}}", - "upload_errors": "Càrrega completada amb {count, plural, one {# error} other {# errors}}, actualitzeu la pàgina per veure els nous elements carregats.", - "upload_finished": "Pujada finalitzada", - "upload_progress": "Restant {remaining, number} - Processat {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {S'ha omès # recurs duplicat} other {S'han omès # recursos duplicats}}", - "upload_status_duplicates": "Duplicats", - "upload_status_errors": "Errors", - "upload_status_uploaded": "Carregat", - "upload_success": "Pujada correcta, actualitza la pàgina per veure nous recursos de pujada.", - "upload_to_immich": "Puja a Immich ({count})", - "uploading": "Pujant", - "uploading_media": "Pujant mitjans", - "url": "URL", - "usage": "Ús", - "use_biometric": "Empra biometria", - "use_current_connection": "Utilitza la connexió actual", - "use_custom_date_range": "Fes servir un rang de dates personalitzat", - "user": "Usuari", - "user_has_been_deleted": "Aquest usuari ha sigut eliminat.", - "user_id": "ID d'usuari", - "user_liked": "A {user} li ha agradat {type, select, photo {aquesta foto} video {aquest vídeo} asset {aquest recurs} other {}}", - "user_pin_code_settings": "Codi PIN", - "user_pin_code_settings_description": "Gestiona el teu codi PIN", - "user_privacy": "Privadesa d'Usuari", - "user_purchase_settings": "Compra", - "user_purchase_settings_description": "Gestiona la teva compra", - "user_role_set": "Establir {user} com a {role}", - "user_usage_detail": "Detall d'ús d'usuari", - "user_usage_stats": "Estadístiques d'ús de del compte", - "user_usage_stats_description": "Veure les estadístiques d'ús del compte", - "username": "Nom d'usuari", - "users": "Usuaris", - "users_added_to_album_count": "{count, plural, one {S'ha afegit # usuari} other {S'han afegit # usuaris}} a l'àlbum", - "utilities": "Utilitats", - "validate": "Valida", - "validate_endpoint_error": "Per favor introdueix un URL vàlid", - "validation_error": "Error de validació", - "variables": "Variables", - "version": "Versió", - "version_announcement_closing": "El teu amic Alex", - "version_announcement_message": "Hola! Hi ha una nova versió d'Immich, si us plau, preneu-vos una estona per llegir les notes de llançament per assegurar que la teva configuració estigui actualitzada per evitar qualsevol error de configuració, especialment si utilitzeu WatchTower o qualsevol mecanisme que gestioni l'actualització automàtica de la vostra instància Immich.", - "version_history": "Historial de versions", - "version_history_item": "Instal·lat {version} el {date}", - "video": "Vídeo", - "video_hover_setting": "Reprodueix la miniatura en passar el ratolí", - "video_hover_setting_description": "Reprodueix la miniatura quan el ratolí plana sobre l'element. Fins i tot quan estigui deshabilitat, la reproducció s'iniciarà planant sobre el botó de reproducció.", - "videos": "Vídeos", - "videos_count": "{count, plural, one {# vídeo} other {# vídeos}}", - "videos_only": "Només videos", - "view": "Veure", - "view_album": "Veure l'àlbum", - "view_all": "Veure tot", - "view_all_users": "Mostra tot els usuaris", - "view_asset_owners": "Veure els propietaris dels actius", - "view_details": "Veure Detalls", - "view_in_timeline": "Mostrar a la línia de temps", - "view_link": "Veure enllaç", - "view_links": "Mostra enllaços", - "view_name": "Veure", - "view_next_asset": "Mostra el següent element", - "view_previous_asset": "Mostra l'element anterior", - "view_qr_code": "Veure codi QR", - "view_similar_photos": "Veure fotos similars", - "view_stack": "Veure la pila", - "view_user": "Veure Usuari", - "viewer_remove_from_stack": "Elimina de la pila", - "viewer_stack_use_as_main_asset": "Fes servir com a element principal", - "viewer_unstack": "Desapila", - "visibility_changed": "La visibilitat ha canviat per {count, plural, one {# persona} other {# persones}}", - "visual": "Visual", - "visual_builder": "Constructor visual", - "waiting": "Esperant", - "waiting_count": "Esperant: {count}", - "warning": "Avís", - "week": "Setmana", - "welcome": "Benvingut", - "welcome_to_immich": "Benvingut a immich", - "width": "Amplada", - "wifi_name": "Nom Wi-Fi", - "workflow_delete_prompt": "Segur que vols eliminar aquesta automatització?", - "workflow_deleted": "Automatització eliminada", - "workflow_description": "Descripció de l'automatització", - "workflow_info": "Informació de l'automatització", - "workflow_json": "JSON de l'automatització", - "workflow_json_help": "Edita la configuració de l'automatització en format JSON. Els canvis es sincronitzaran amb el constructor visual.", - "workflow_name": "Nom de l'automatització", - "workflow_navigation_prompt": "Segur que vols sortir sense desar els canvis?", - "workflow_summary": "Resum de l'automatització", - "workflow_update_success": "Automatització actualitzada amb èxit", - "workflow_updated": "Automatització actualitzada", - "workflows": "Automatitzacions", - "workflows_help_text": "Les automatitzacions realitzen accions automàticament sobre els teus mitjans basant-se en disparadors i filtres", - "wrong_pin_code": "Codi PIN incorrecte", - "year": "Any", - "years_ago": "Fa {years, plural, one {# any} other {# anys}}", - "yes": "Sí", - "you_dont_have_any_shared_links": "No tens cap enllaç compartit", - "your_wifi_name": "Nom del teu Wi-Fi", - "zero_to_clear_rating": "prem 0 per a buidar la valoració", - "zoom_image": "Ampliar Imatge", - "zoom_to_bounds": "Amplia als límits" -} +{} diff --git a/i18n/cs.json b/i18n/cs.json index f72b9b164c..0967ef424b 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -1,2401 +1 @@ -{ - "about": "O aplikaci", - "account": "Účet", - "account_settings": "Nastavení účtu", - "acknowledge": "Rozumím", - "action": "Akce", - "action_common_update": "Aktualizovat", - "action_description": "Sada akcí, které se mají provést na filtrovaných položkách", - "actions": "Akce", - "active": "Aktivní", - "active_count": "Aktivní: {count}", - "activity": "Aktivita", - "activity_changed": "Aktivita je {enabled, select, true {povolena} other {zakázána}}", - "add": "Přidat", - "add_a_description": "Přidat popis", - "add_a_location": "Přidat polohu", - "add_a_name": "Přidat jméno", - "add_a_title": "Přidat název", - "add_action": "Přidat akci", - "add_action_description": "Kliknutím přidejte akci, kterou chcete provést", - "add_assets": "Přidat položky", - "add_birthday": "Přidat datum narození", - "add_endpoint": "Přidat koncový bod", - "add_exclusion_pattern": "Přidat vzor vyloučení", - "add_filter": "Přidat filtr", - "add_filter_description": "Kliknutím přidejte podmínku filtru", - "add_location": "Přidat polohu", - "add_more_users": "Přidat další uživatele", - "add_partner": "Přidat partnera", - "add_path": "Přidat cestu", - "add_photos": "Přidat fotky", - "add_tag": "Přidat značku", - "add_to": "Přidat do…", - "add_to_album": "Přidat do alba", - "add_to_album_bottom_sheet_added": "Přidáno do {album}", - "add_to_album_bottom_sheet_already_exists": "Je již v {album}", - "add_to_album_bottom_sheet_some_local_assets": "Některé místní položky nebylo možné přidat do alba", - "add_to_album_toggle": "Přepnout výběr pro {album}", - "add_to_albums": "Přidat do alb", - "add_to_albums_count": "Přidat do alb ({count})", - "add_to_bottom_bar": "Přidat do", - "add_to_shared_album": "Přidat do sdíleného alba", - "add_upload_to_stack": "Přidat nahrané do zásobníku", - "add_url": "Přidat URL", - "add_workflow_step": "Přidat krok pracovního postupu", - "added_to_archive": "Přidáno do archivu", - "added_to_favorites": "Přidáno do oblíbených", - "added_to_favorites_count": "Přidáno {count, number} do oblíbených", - "admin": { - "add_exclusion_pattern_description": "Přidání vzorů vyloučení. Podporováno je globování pomocí *, ** a ?. Chcete-li ignorovat všechny soubory v jakémkoli adresáři s názvem \"Raw\", použijte \"**/Raw/**\". Chcete-li ignorovat všechny soubory končící na \".tif\", použijte \"**/*.tif\". Chcete-li ignorovat absolutní cestu, použijte příkaz \"/path/to/ignore/**\".", - "admin_user": "Administrátor", - "asset_offline_description": "Tato položka externí knihovny se již na disku nenachází a byla přesunuta do koše. Pokud byl soubor přesunut v rámci knihovny, zkontrolujte časovou osu a vyhledejte nové odpovídající položku. Chcete-li tuto položku obnovit, ujistěte se, že je cesta k níže uvedenému souboru přístupná pomocí aplikace Immich a prohledejte knihovnu.", - "authentication_settings": "Přihlašování", - "authentication_settings_description": "Správa hesel, OAuth a dalších nastavení ověření", - "authentication_settings_disable_all": "Opravdu chcete zakázat všechny metody přihlášení? Přihlašování bude úplně zakázáno.", - "authentication_settings_reenable": "Pro opětovné povolení použijte příkaz Příkaz serveru.", - "background_task_job": "Úkoly na pozadí", - "backup_database": "Vytvořit výpis databáze", - "backup_database_enable_description": "Povolit výpisy z databáze", - "backup_keep_last_amount": "Počet předchozích výpisů, které se mají ponechat", - "backup_onboarding_1_description": "kopie v cloudu nebo na jiném fyzickém místě.", - "backup_onboarding_2_description": "místní kopie na různých zařízeních. To zahrnuje hlavní soubory a jejich místní zálohu.", - "backup_onboarding_3_description": "kompletní kopie vašich dat, včetně původních souborů. To zahrnuje 1 kopii na jiném místě a 2 místní kopie.", - "backup_onboarding_description": "K ochraně vašich dat doporučujeme strategii zálohování 3-2-1. Pro komplexní zálohování byste měli uchovávat kopie nahraných fotografií/videí i databáze Immich.", - "backup_onboarding_footer": "Další informace o zálohování Immiche naleznete v dokumentaci.", - "backup_onboarding_parts_title": "Záloha 3-2-1 zahrnuje:", - "backup_onboarding_title": "Zálohy", - "backup_settings": "Zálohování databáze", - "backup_settings_description": "Správa nastavení výpisu databáze.", - "cleared_jobs": "Hotové úlohy pro: {job}", - "config_set_by_file": "Konfigurace je aktuálně prováděna konfiguračním souborem", - "confirm_delete_library": "Opravdu chcete odstranit knihovnu {library}?", - "confirm_delete_library_assets": "Opravdu chcete tuto knihovnu odstranit? Tím se z Immiche odstraní {count, plural, one {# obsažená položka} few {všechny # obsažené položky} other {všech # obsažených položek}} a nelze to vrátit zpět. Soubory zůstanou na disku.", - "confirm_email_below": "Pro potvrzení zadejte níže \"{email}\"", - "confirm_reprocess_all_faces": "Opravdu chcete znovu zpracovat všechny obličeje? Tím se vymažou i pojmenované osoby.", - "confirm_user_password_reset": "Opravdu chcete obnovit heslo uživatele {user}?", - "confirm_user_pin_code_reset": "Opravdu chcete resetovat PIN kód uživatele {user}?", - "copy_config_to_clipboard_description": "Zkopírujte aktuální konfiguraci systému jako JSON objekt do schránky", - "create_job": "Vytvořit úlohu", - "cron_expression": "Výraz cron", - "cron_expression_description": "Nastavte interval prohledávání pomocí cron formátu. Další informace naleznete např. v Crontab Guru", - "cron_expression_presets": "Předvolby výrazů cron", - "disable_login": "Zakázat přihlášení", - "duplicate_detection_job_description": "Spuštění strojového učení na položkách za účelem detekce podobných obrázků. Spoléhá na Chytré vyhledávání", - "exclusion_pattern_description": "Vzory vyloučení umožňují při prohledávání knihovny ignorovat soubory a složky. To je užitečné, pokud máte složky obsahující soubory, které nechcete importovat, například RAW soubory.", - "export_config_as_json_description": "Stáhněte si aktuální konfiguraci systému jako JSON soubor", - "external_libraries_page_description": "Stránka externí knihovny správce", - "face_detection": "Detekce obličejů", - "face_detection_description": "Detekce obličejů v obrázcích pomocí strojového učení. U videí se bere v úvahu pouze miniatura. „Obnovit“ znovu zpracuje všechny položky. „Resetovat“ navíc vymaže všechna aktuální data obličejů. „Chybějící“ zařadí do fronty položky, které ještě nebyly zpracovány. Zjištěné obličeje budou po dokončení funkce Rozpoznávání obličejů zařazeny do fronty a seskupeny do stávajících nebo nových osob.", - "facial_recognition_job_description": "Seskupí nalezené obličeje do osob. Tento krok se spustí po dokončení detekce obličejů. „Resetovat“ znovu seskupí všechny obličeje. „Chybějící“ zpracuje obličeje, které nemají přiřazenou osobu.", - "failed_job_command": "Příkaz {command} se nezdařil pro úlohu: {job}", - "force_delete_user_warning": "UPOZORNĚNÍ: Tímto okamžitě odstraníte uživatele a všechny jeho položky. Tento krok nelze vrátit zpět a soubory nelze obnovit.", - "image_format": "Formát", - "image_format_description": "WebP vytváří menší soubory než JPEG, ale je pomalejší při kódování.", - "image_fullsize_description": "Obrázek v plné velikosti s odstraněnými metadaty, použito při přiblížení", - "image_fullsize_enabled": "Povolit generování obrázků v plné velikosti", - "image_fullsize_enabled_description": "Generovat obrázky v plné velikosti pro formáty, které nejsou vhodné pro web. Pokud je povolena možnost „Preferovat vložený náhled“, budou přímo použity vložené náhledy bez převodu. Neovlivňuje formáty vhodné pro web, jako je JPEG.", - "image_fullsize_quality_description": "Kvalita obrázku v plné velikosti od 1 do 100. Vyšší je lepší, ale vytváří větší soubory.", - "image_fullsize_title": "Nastavení obrázků v plné velikosti", - "image_prefer_embedded_preview": "Preferovat vložený náhled", - "image_prefer_embedded_preview_setting_description": "Použít vložené náhledy z RAW fotografií jako vstup pro zpracování snímků a pokud jsou k dispozici. U některých snímků tak lze dosáhnout přesnějších barev, ale kvalita náhledu závisí na fotoaparátu a snímek může obsahovat více kompresních artefaktů.", - "image_prefer_wide_gamut": "Preferovat široký gamut", - "image_prefer_wide_gamut_setting_description": "Použít Display P3 pro miniatury. To lépe zachovává živost obrázků s širokým barevným prostorem, ale obrázky se mohou na starých zařízeních se starou verzí prohlížeče zobrazovat jinak. sRGB obrázky jsou ponechány jako sRGB, aby se zabránilo posunům barev.", - "image_preview_description": "Středně velký obrázek se zbavenými metadaty, který se používá při prohlížení jedné položky a pro strojové učení", - "image_preview_quality_description": "Kvalita náhledu od 1 do 100. Vyšší je lepší, ale vytváří větší soubory a může snížit responzivitu aplikace. Nastavení nízké hodnoty může ovlivnit kvalitu strojového učení.", - "image_preview_title": "Náhledy", - "image_progressive": "Progresivní", - "image_progressive_description": "Kódujte JPEG obrázky progresivně pro postupné načítání zobrazení. Na WebP obrázky to nemá žádný vliv.", - "image_quality": "Kvalita", - "image_resolution": "Rozlišení", - "image_resolution_description": "Vyšší rozlišení mohou zachovat více detailů, ale jejich kódování trvá déle, mají větší velikost souboru a mohou snížit odezvu aplikace.", - "image_settings": "Obrázky", - "image_settings_description": "Správa kvality a rozlišení generovaných obrázků", - "image_thumbnail_description": "Malá miniatura s odstraněnými metadaty, který se používá při prohlížení skupin fotografií, jako je hlavní časová osa", - "image_thumbnail_quality_description": "Kvalita miniatur od 1 do 100. Vyšší je lepší, ale vytváří větší soubory a může snížit odezvu aplikace.", - "image_thumbnail_title": "Miniatury", - "import_config_from_json_description": "Importujte konfiguraci systému nahráním konfiguračního JSON souboru", - "job_concurrency": "Souběžnost úlohy {job}", - "job_created": "Úloha vytvořena", - "job_not_concurrency_safe": "Tato úloha není bezpečená pro souběh.", - "job_settings": "Úlohy", - "job_settings_description": "Správa souběžnosti úloh", - "jobs_delayed": "{jobCount, plural, one {# zpožděný} few {# zpožděné} other {# zpožděných}}", - "jobs_failed": "{jobCount, plural, one {# neúspěšný} few {# neúspěšné} other {# neúspěšných}}", - "jobs_over_time": "Úlohy v průběhu času", - "library_created": "Vytvořena knihovna: {library}", - "library_deleted": "Knihovna smazána", - "library_details": "Podrobnosti o knihovně", - "library_folder_description": "Zadejte složku, kterou chcete importovat. Tato složka, včetně podsložek, bude prohledána pro obrázky a videa.", - "library_remove_exclusion_pattern_prompt": "Opravdu chcete odstranit tento vzor vyloučení?", - "library_remove_folder_prompt": "Opravdu chcete odstranit tuto složku importu?", - "library_scanning": "Pravidelné prohledávání", - "library_scanning_description": "Nastavení pravidelného prohledávání knihovny", - "library_scanning_enable_description": "Povolit pravidelné prohledávání knihovny", - "library_settings": "Externí knihovna", - "library_settings_description": "Správa nastavení externí knihovny", - "library_tasks_description": "Vyhledávání nových nebo změněných položek v externích knihovnách", - "library_updated": "Knihovna aktualizována", - "library_watching_enable_description": "Sledovat změny souborů v externích knihovnách", - "library_watching_settings": "Sledování knihovny [EXPERIMENTÁLNÍ]", - "library_watching_settings_description": "Automatické sledování změněných souborů", - "logging_enable_description": "Povolit protokolování", - "logging_level_description": "Když je povoleno, jakou úroveň protokolu použít.", - "logging_settings": "Protokolování", - "machine_learning_availability_checks": "Kontroly dostupnosti", - "machine_learning_availability_checks_description": "Automaticky zvolit a preferovat dostupné servery strojového učení", - "machine_learning_availability_checks_enabled": "Povolit kontroly dostupnosti", - "machine_learning_availability_checks_interval": "Interval kontrol", - "machine_learning_availability_checks_interval_description": "Interval v milisekundách mezi kontrolami dostupnosti", - "machine_learning_availability_checks_timeout": "Časový limit požadavku", - "machine_learning_availability_checks_timeout_description": "Časový limit v milisekundách pro kontrolu dostupnosti", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Název CLIP modelu je uvedený zde. Pamatujte, že při změně modelu je nutné znovu spustit úlohu 'Chytré vyhledávání' pro všechny obrázky.", - "machine_learning_duplicate_detection": "Kontrola duplicit", - "machine_learning_duplicate_detection_enabled": "Povolit kontrolu duplicit", - "machine_learning_duplicate_detection_enabled_description": "Pokud je tato funkce vypnuta, budou identické položky stále deduplikovány.", - "machine_learning_duplicate_detection_setting_description": "Použít CLIP embeddings k nalezení pravděpodobných duplicit", - "machine_learning_enabled": "Povolit strojové učení", - "machine_learning_enabled_description": "Pokud je vypnuto, budou všechny funkce strojového učení vypnuty bez ohledu na níže uvedená nastavení.", - "machine_learning_facial_recognition": "Rozpoznávání obličejů", - "machine_learning_facial_recognition_description": "Detekce, rozpoznávání a seskupování obličejů na obrázcích", - "machine_learning_facial_recognition_model": "Model rozpoznávání obličejů", - "machine_learning_facial_recognition_model_description": "Modely jsou seřazeny sestupně podle velikosti. Větší modely jsou pomalejší a využívají více paměti, ale poskytují lepší výsledky. Pamatujte, že při změně modelu je nutné znovu spustit úlohu detekce obličeje pro všechny snímky.", - "machine_learning_facial_recognition_setting": "Povolit rozpoznávání obličejů", - "machine_learning_facial_recognition_setting_description": "Pokud je vypnuto, obrázky nebudou kódovány pro rozpoznávání obličeje a nebudou se zobrazovat sekci Lidé na stránce Prozkoumat.", - "machine_learning_max_detection_distance": "Maximální detekční odchylka", - "machine_learning_max_detection_distance_description": "Maximální odchylka mezi dvěma snímky, aby byly považovány za duplicitní, v rozmezí 0,001-0,1. Vyšší hodnoty odhalí více duplicit, ale mohou vést k falešně pozitivním výsledkům.", - "machine_learning_max_recognition_distance": "Maximální rozpoznávací vzdálenost", - "machine_learning_max_recognition_distance_description": "Maximální vzdálenost mezi dvěma obličeji, které se považují za stejnou osobu, v rozmezí 0-2. Snížením této hodnoty lze zabránit označení dvou osob za stejnou osobu, zatímco jejím zvýšením lze zabránit označení stejné osoby za dvě různé osoby. Pamatujte, že je snazší sloučit dvě osoby než rozdělit jednu osobu na dvě, proto se pokud možno přiklánějte k nižšímu prahu.", - "machine_learning_min_detection_score": "Minimální detekční skóre", - "machine_learning_min_detection_score_description": "Minimální skóre důvěryhodnosti pro detekci obličeje od 0 do 1. Nižší hodnoty odhalí více tváří, ale mohou vést k falešně pozitivním výsledkům.", - "machine_learning_min_recognized_faces": "Mininum rozpoznaných obličejů", - "machine_learning_min_recognized_faces_description": "Minimální počet rozpoznaných obličejů pro vytvoření osoby. Zvýšení tohoto počtu zpřesňuje rozpoznávání obličejů za cenu zvýšení pravděpodobnosti, že obličej nebude přiřazen k osobě.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Použijte strojové učení k rozpoznávání textu v obrázcích", - "machine_learning_ocr_enabled": "Povolit OCR", - "machine_learning_ocr_enabled_description": "Pokud je tato funkce vypnuta, obrázky nebudou podrobeny rozpoznávání textu.", - "machine_learning_ocr_max_resolution": "Maximální rozlišení", - "machine_learning_ocr_max_resolution_description": "Náhledy nad tímto rozlišením budou změněny tak, aby byl zachován poměr stran. Vyšší hodnoty jsou přesnější, ale jejich zpracování trvá déle a zabírají více paměti.", - "machine_learning_ocr_min_detection_score": "Minimální detekční skóre", - "machine_learning_ocr_min_detection_score_description": "Minimální skóre spolehlivosti pro detekci textu v rozmezí 0–1. Nižší hodnoty detekují více textu, ale mohou vést k falešným pozitivním výsledkům.", - "machine_learning_ocr_min_recognition_score": "Minimální počet bodů pro rozpoznání", - "machine_learning_ocr_min_score_recognition_description": "Minimální skóre spolehlivosti pro rozpoznání detekovaného textu v rozmezí 0–1. Nižší hodnoty rozpoznají více textu, ale mohou vést k falešným pozitivům.", - "machine_learning_ocr_model": "OCR model", - "machine_learning_ocr_model_description": "Serverové modely jsou přesnější než mobilní modely, ale jejich zpracování trvá déle a zabírají více paměti.", - "machine_learning_settings": "Strojové učení", - "machine_learning_settings_description": "Správa funkcí a nastavení strojového učení", - "machine_learning_smart_search": "Chytré vyhledávání", - "machine_learning_smart_search_description": "Sémantické vyhledávání obrázků pomocí CLIP embeddings", - "machine_learning_smart_search_enabled": "Povolit chytré vyhledávání", - "machine_learning_smart_search_enabled_description": "Pokud je vypnuto, obrázky nebudou kódovány pro inteligentní vyhledávání.", - "machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu. Servery, které neodpoví, budou dočasně ignorovány, dokud nebudou opět online.", - "maintenance_delete_backup": "Smazat zálohu", - "maintenance_delete_backup_description": "Tento soubor bude trvale smazán.", - "maintenance_delete_error": "Nepodařilo se smazat zálohu.", - "maintenance_restore_backup": "Obnovit zálohu", - "maintenance_restore_backup_description": "Immich bude vymazán a obnoven z vybrané zálohy. Před pokračováním bude vytvořena záloha.", - "maintenance_restore_backup_different_version": "Tato záloha byla vytvořena pomocí jiné verze aplikace Immich!", - "maintenance_restore_backup_unknown_version": "Nelze určit verzi zálohy.", - "maintenance_restore_database_backup": "Obnovit zálohu databáze", - "maintenance_restore_database_backup_description": "Obnovení předchozího stavu databáze pomocí záložního souboru", - "maintenance_settings": "Údržba", - "maintenance_settings_description": "Přepnout Immich do režimu údržby.", - "maintenance_start": "Přepnout do režimu údržby", - "maintenance_start_error": "Nepodařilo se zahájit režim údržby.", - "maintenance_upload_backup": "Nahrát záložní soubor databáze", - "maintenance_upload_backup_error": "Nelze nahrát zálohu, jedná se o soubor .sql/.sql.gz?", - "manage_concurrency": "Správa souběžnosti", - "manage_concurrency_description": "Přejděte na stránku úloh a spravujte souběžnost úloh", - "manage_log_settings": "Správa nastavení protokolu", - "map_dark_style": "Tmavý motiv", - "map_enable_description": "Povolit funkce mapy", - "map_gps_settings": "Mapa a GPS", - "map_gps_settings_description": "Správa nastavení mapy a GPS (Reverzní geokódování)", - "map_implications": "Funkce mapy závisí na externí dlaždicové službě (tiles.immich.cloud)", - "map_light_style": "Světlý motiv", - "map_manage_reverse_geocoding_settings": "Správa nastavení Reverzního geokódování", - "map_reverse_geocoding": "Reverzní geokódování", - "map_reverse_geocoding_enable_description": "Povolit reverzní geokódování", - "map_reverse_geocoding_settings": "Reverzní geokódování", - "map_settings": "Mapa", - "map_settings_description": "Správa nastavení mapy", - "map_style_description": "URL na style.json motivu", - "memory_cleanup_job": "Promazání vzpomínek", - "memory_generate_job": "Vytvoření vzpomínek", - "metadata_extraction_job": "Extrakce metadat", - "metadata_extraction_job_description": "Získání informací o metadatech z každého snímku, jako je GPS, obličeje a rozlišení", - "metadata_faces_import_setting": "Povolit import obličeje", - "metadata_faces_import_setting_description": "Import obličejů z EXIF dat obrázků a sidecar souborů", - "metadata_settings": "Metadata", - "metadata_settings_description": "Správa nastavení metadat", - "migration_job": "Migrace", - "migration_job_description": "Migrace miniatur snímků a obličejů do nejnovější struktury složek", - "nightly_tasks_cluster_faces_setting_description": "Spustit rozpoznávání obličeje na nově nalezených obličejích", - "nightly_tasks_cluster_new_faces_setting": "Seskupit nové tváře", - "nightly_tasks_database_cleanup_setting": "Úlohy čištění databáze", - "nightly_tasks_database_cleanup_setting_description": "Vyčistit databázi od starých dat, jejichž platnost vypršela", - "nightly_tasks_generate_memories_setting": "Vytváření vzpomínek", - "nightly_tasks_generate_memories_setting_description": "Vytváření nových vzpomínek z položek", - "nightly_tasks_missing_thumbnails_setting": "Generovat chybějící miniatury", - "nightly_tasks_missing_thumbnails_setting_description": "Řadit položky bez miniatur do fronty pro generování miniatur", - "nightly_tasks_settings": "Noční úlohy", - "nightly_tasks_settings_description": "Správa nočních úkolů", - "nightly_tasks_start_time_setting": "Čas zahájení", - "nightly_tasks_start_time_setting_description": "Čas, kdy server spustí noční úlohy", - "nightly_tasks_sync_quota_usage_setting": "Synchronizace využití kvóty", - "nightly_tasks_sync_quota_usage_setting_description": "Aktualizovat kvótu úložiště uživatele na základě aktuálního využití", - "no_paths_added": "Nebyly přidány žádné cesty", - "no_pattern_added": "Nebyl přidán žádný vzor", - "note_apply_storage_label_previous_assets": "Upozornění: Pro uplatnění Štítku úložiště na dříve nahrané položky spusťte", - "note_cannot_be_changed_later": "UPOZORNĚNÍ: Toto nelze později změnit!", - "notification_email_from_address": "Adresa Od", - "notification_email_from_address_description": "E-mailová adresa odesílatele, např.: „Immich Photo Server “. Ujistěte se, že používáte adresu, ze které smíte odesílat e-maily.", - "notification_email_host_description": "Adresa e-mailového serveru (např. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorovat chyby certifikátů", - "notification_email_ignore_certificate_errors_description": "Ignorovat chyby ověření certifikátu TLS (nedoporučuje se)", - "notification_email_password_description": "Heslo pro ověření na e-mailovém serveru", - "notification_email_port_description": "Port e-mailového serveru (např. 25, 465 nebo 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Používat SMTPS (SMTP přes TLS)", - "notification_email_sent_test_email_button": "Odeslat testovací e-mail a uložit", - "notification_email_setting_description": "Nastavení pro zasílání e-mailových oznámení", - "notification_email_test_email": "Odeslat testovací e-mail", - "notification_email_test_email_failed": "Nepodařilo se odeslat testovací e-mail, zkontrolujte hodnoty", - "notification_email_test_email_sent": "Na adresu {email} byl odeslán testovací e-mail. Zkontrolujte prosím svou schránku.", - "notification_email_username_description": "Uživatelské jméno, které se použije při ověřování na e-mailovém serveru", - "notification_enable_email_notifications": "Povolení e-mailových oznámení", - "notification_settings": "Oznámení", - "notification_settings_description": "Správa nastavení oznámení včetně e-mailu", - "oauth_auto_launch": "Automatické zahájení", - "oauth_auto_launch_description": "Automatické zahájení přihlašovacího toku OAuth po přechodu na přihlašovací stránku", - "oauth_auto_register": "Automatická registrace", - "oauth_auto_register_description": "Automaticky registrovat nové uživatele po přihlášení pomocí OAuth", - "oauth_button_text": "Text tlačítka", - "oauth_client_secret_description": "Vyžadováno pro důvěrné klienty nebo pokud PKCE (Proof Key for Code Exchange) není podporováno pro veřejné klienty.", - "oauth_enable_description": "Přihlásit pomocí OAuth", - "oauth_mobile_redirect_uri": "Mobilní přesměrování URI", - "oauth_mobile_redirect_uri_override": "Přepsat mobilní přesměrování URI", - "oauth_mobile_redirect_uri_override_description": "Povolit, pokud poskytovatel OAuth nepovoluje mobilní URI, například ''{callback}''", - "oauth_role_claim": "Deklarace Role", - "oauth_role_claim_description": "Automaticky udělit přístup správce na základě přítomnosti této deklarace. Deklarace může mít hodnotu 'user' nebo 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Správa nastavení OAuth přihlášení", - "oauth_settings_more_details": "Další podrobnosti o této funkci naleznete v dokumentaci.", - "oauth_storage_label_claim": "Deklarace štítku úložiště", - "oauth_storage_label_claim_description": "Automaticky nastavit štítek úložiště uživatele na hodnotu této deklarace.", - "oauth_storage_quota_claim": "Deklarace kvóty úložiště", - "oauth_storage_quota_claim_description": "Automaticky nastavit kvótu úložiště uživatele na hodnotu této deklarace.", - "oauth_storage_quota_default": "Výchozí kvóta úložiště (GiB)", - "oauth_storage_quota_default_description": "Kvóta v GiB, která se použije, pokud není poskytnuta žádná deklarace.", - "oauth_timeout": "Časový limit požadavku", - "oauth_timeout_description": "Časový limit pro požadavky v milisekundách", - "ocr_job_description": "Použijte strojové učení k rozpoznávání textu v obrázcích", - "password_enable_description": "Přihlášení pomocí e-mailu a hesla", - "password_settings": "Přihlášení heslem", - "password_settings_description": "Správa nastavení přihlašování pomocí hesla", - "paths_validated_successfully": "Všechny cesty byly úspěšně ověřeny", - "person_cleanup_job": "Promazání osob", - "queue_details": "Podrobnosti o frontě", - "queues": "Fronty úloh", - "queues_page_description": "Stránka fronty úloh správce", - "quota_size_gib": "Velikost kvóty (GiB)", - "refreshing_all_libraries": "Obnovení všech knihoven", - "registration": "Registrace správce", - "registration_description": "Vzhledem k tomu, že jste prvním uživatelem v systému, budete přiřazen jako správce a budete zodpovědný za úkoly správy a další uživatelé budou vytvořeni vámi.", - "remove_failed_jobs": "Odebrat neúspěšné úlohy", - "require_password_change_on_login": "Požadovat, aby si uživatel při prvním přihlášení změnil heslo", - "reset_settings_to_default": "Obnovení výchozího nastavení", - "reset_settings_to_recent_saved": "Obnovit poslední uložené nastavení", - "scanning_library": "Prohledat knihovnu", - "search_jobs": "Hledat úlohy…", - "send_welcome_email": "Odeslat uvítací e-mail", - "server_external_domain_settings": "Externí doména", - "server_external_domain_settings_description": "Doména pro veřejně sdílené odkazy, včetně http(s)://", - "server_public_users": "Veřejní uživatelé", - "server_public_users_description": "Všichni uživatelé (jméno a e-mail) jsou uvedeni při přidávání uživatele do sdílených alb. Pokud je tato funkce vypnuta, bude seznam uživatelů dostupný pouze uživatelům z řad správců.", - "server_settings": "Server", - "server_settings_description": "Správa nastavení serveru", - "server_stats_page_description": "Stránka statistik administrátorského serveru", - "server_welcome_message": "Uvítací zpráva", - "server_welcome_message_description": "Zpráva, která se zobrazí na přihlašovací stránce.", - "settings_page_description": "Stránka nastavení administrátora", - "sidecar_job": "Postranní metadata", - "sidecar_job_description": "Objevování nebo synchronizace sidecar metadat ze systému souborů", - "slideshow_duration_description": "Počet sekund pro zobrazení každého obrázku", - "smart_search_job_description": "Strojové učení na objektech pro podporu inteligentního vyhledávání", - "storage_template_date_time_description": "Jako časový údaj se použije čas vytvoření položky", - "storage_template_date_time_sample": "Ukázkový čas {date}", - "storage_template_enable_description": "Povolení šablony úložiště", - "storage_template_hash_verification_enabled": "Povolení ověřování hashe", - "storage_template_hash_verification_enabled_description": "Povolí ověřování hashe, nevypínejte ji, pokud si nejste jisti důsledky", - "storage_template_migration": "Migrace šablony úložiště", - "storage_template_migration_description": "Použít aktuální {template} na dříve nahrané položky", - "storage_template_migration_info": "Šablona úložiště převede všechny přípony na malá písmena. Změny šablon se uplatní pouze u nových položek. Chcete-li šablonu zpětně použít na dříve nahrané položky, spusťte {job}.", - "storage_template_migration_job": "Úloha migrace šablony úložiště", - "storage_template_more_details": "Další podrobnosti o této funkci naleznete v sekci Šablona úložiště včetně jejích důsledků", - "storage_template_onboarding_description_v2": "Pokud je tato funkce povolena, automaticky uspořádá soubory na základě uživatelem definované šablony. Další informace naleznete v dokumentaci.", - "storage_template_path_length": "Přibližný limit délky cesty: {length, number}/{limit, number}", - "storage_template_settings": "Šablona úložiště", - "storage_template_settings_description": "Správa struktury složek a názvů nahraných souborů", - "storage_template_user_label": "{label} je štítek úložiště uživatele", - "system_settings": "Systémová nastavení", - "tag_cleanup_job": "Promazání značek", - "template_email_available_tags": "V šabloně můžete použít následující proměnné: {tags}", - "template_email_if_empty": "Pokud je šablona prázdná, použije se výchozí e-mail.", - "template_email_invite_album": "Šablona pozvánky do alba", - "template_email_preview": "Náhled", - "template_email_settings": "Šablony e-mailů", - "template_email_update_album": "Šablona aktualizace alba", - "template_email_welcome": "Šablona uvítacího e-mailu", - "template_settings": "Šablony oznámení", - "template_settings_description": "Správa vlastních šablon oznámení", - "theme_custom_css_settings": "Vlastní CSS", - "theme_custom_css_settings_description": "Kaskádové styly umožňují přizpůsobit design aplikace Immich.", - "theme_settings": "Motivy", - "theme_settings_description": "Správa přizpůsobení webového rozhraní Immich", - "thumbnail_generation_job": "Generování miniatur", - "thumbnail_generation_job_description": "Generování velkých, malých a rozmazaných miniatur pro každý obrázek a miniatur pro každou osobu", - "transcoding_acceleration_api": "API pro akceleraci", - "transcoding_acceleration_api_description": "Rozhraní, které bude komunikovat se zařízením a urychlovat překódování. Toto nastavení je 'best effort': při selhání se vrátí k softwarovému překódování. VP9 může, ale nemusí fungovat v závislosti na vašem hardwaru.", - "transcoding_acceleration_nvenc": "NVENC (vyžaduje NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (vyžaduje procesor Intel 7. generace nebo novější)", - "transcoding_acceleration_rkmpp": "RKMPP (pouze u SOC Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Akceptované zvukové kodeky", - "transcoding_accepted_audio_codecs_description": "Vyberte, které zvukové kodeky není třeba překódovat. Používá se pouze pro určité zásady překódování.", - "transcoding_accepted_containers": "Akceptované kontejnery", - "transcoding_accepted_containers_description": "Vyberte, které formáty kontejnerů není třeba remuxovat do formátu MP4. Používá se pouze pro určité zásady překódování.", - "transcoding_accepted_video_codecs": "Akceptované video kodeky", - "transcoding_accepted_video_codecs_description": "Vyberte, které kodeky videa není třeba překódovat. Používá se pouze pro určité zásady překódování.", - "transcoding_advanced_options_description": "Možnosti, které by většina uživatelů neměla potřebovat měnit", - "transcoding_audio_codec": "Zvukový kodek", - "transcoding_audio_codec_description": "Opus je nejkvalitnější možností, ale má nižší kompatibilitu se starými zařízeními nebo softwarem.", - "transcoding_bitrate_description": "Videa s vyšším než maximálním datovým tokem nebo videa, která nejsou v akceptovaném formátu", - "transcoding_codecs_learn_more": "Další informace o zde používané terminologii naleznete v dokumentaci FFmpeg pro kodek H.264, kodek HEVC a kodek VP9.", - "transcoding_constant_quality_mode": "Režim konstantní kvality", - "transcoding_constant_quality_mode_description": "ICQ je lepší než CQP, ale některá zařízení pro hardwarovou akceleraci tento režim nepodporují. Nastavením této volby se při použití kódování založeného na kvalitě upřednostní zadaný režim. Ignorováno NVENC, protože nepodporuje ICQ.", - "transcoding_constant_rate_factor": "Faktor konstantní rychlosti (-crf)", - "transcoding_constant_rate_factor_description": "Úroveň kvality videa. Typické hodnoty jsou 23 pro H.264, 28 pro HEVC, 31 pro VP9 a 35 pro AV1. Nižší je lepší, ale vytváří větší soubory.", - "transcoding_disabled_description": "Nepřekódovávat žádná videa, u některých klientů může dojít k znemožnění přehrávání", - "transcoding_encoding_options": "Možnosti kódování", - "transcoding_encoding_options_description": "Nastavte kodeky, rozlišení, kvalitu a další možnosti pro kódovaná videa", - "transcoding_hardware_acceleration": "Hardwarová akcelerace", - "transcoding_hardware_acceleration_description": "Experimentální: rychlejší kódování, ale při stejném datovém toku může mít nižší kvalitu", - "transcoding_hardware_decoding": "Hardwarové dekódování", - "transcoding_hardware_decoding_setting_description": "Povoluje kompletní akceleraci namísto akcelerace pouze kódování. Nemusí fungovat u všech videí.", - "transcoding_max_b_frames": "Maximální počet B-snímků", - "transcoding_max_b_frames_description": "Vyšší hodnoty zvyšují účinnost komprese, ale zpomalují kódování. Nemusí být kompatibilní s hardwarovou akcelerací na starších zařízeních. Hodnota 0 zakáže B-snímky, zatímco -1 tuto hodnotu nastaví automaticky.", - "transcoding_max_bitrate": "Maximální datový tok", - "transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600 kbit/s pro VP9 nebo HEVC nebo 4500 kbit/s pro H.264. Pokud je nastaveno na 0, je zakázáno. Pokud není zadána žádná jednotka, předpokládá se k (pro kbit/s); proto jsou 5000, 5000k a 5M (pro Mbit/s) ekvivalentní.", - "transcoding_max_keyframe_interval": "Maximální interval klíčových snímků", - "transcoding_max_keyframe_interval_description": "Nastavuje maximální vzdálenost mezi klíčovými snímky. Nižší hodnoty zhoršují účinnost komprese, ale zlepšují rychlost při přeskakování a mohou zlepšit kvalitu ve scénách s rychlým pohybem. Hodnota 0 nastavuje tuto hodnotu automaticky.", - "transcoding_optimal_description": "Videa s vyšším než cílovým rozlišením nebo videa, která nejsou v akceptovaném formátu", - "transcoding_policy": "Politika překódovávání", - "transcoding_policy_description": "Nastavte po překódování videa", - "transcoding_preferred_hardware_device": "Preferované hardwarové zařízení", - "transcoding_preferred_hardware_device_description": "Platí pouze pro VAAPI a QSV. Nastaví dri uzel použitý pro hardwarové překódování.", - "transcoding_preset_preset": "Předvolba (-preset)", - "transcoding_preset_preset_description": "Rychlost komprese. Pomalejší předvolby vytvářejí menší soubory a zvyšují kvalitu při dosažení určitého datového toku. VP9 ignoruje rychlosti vyšší než 'faster'.", - "transcoding_reference_frames": "Referenční snímky", - "transcoding_reference_frames_description": "Počet referenčních snímků při kompresi daného snímku. Vyšší hodnoty zvyšují účinnost komprese, ale zpomalují kódování. Hodnota 0 toto nastavuje automaticky.", - "transcoding_required_description": "Pouze videa, která nejsou v akceptovaném formátu", - "transcoding_settings": "Překódování videa", - "transcoding_settings_description": "Správa rozlišení a kódování videosouborů", - "transcoding_target_resolution": "Cílové rozlišení", - "transcoding_target_resolution_description": "Vyšší rozlišení mohou zachovat více detailů, ale jejich kódování trvá déle, mají větší velikost souboru a mohou snížit odezvu aplikace.", - "transcoding_temporal_aq": "Časové AQ", - "transcoding_temporal_aq_description": "Platí pouze pro NVENC. Časová adaptivní kvantizace zvyšuje kvalitu scén s vysokým rozlišením a malým pohybem. Nemusí být kompatibilní se staršími zařízeními.", - "transcoding_threads": "Vlákna", - "transcoding_threads_description": "Vyšší hodnoty vedou k rychlejšímu kódování, ale ponechávají serveru méně prostoru pro zpracování jiných úloh. Tato hodnota by neměla být vyšší než počet jader procesoru. Maximalizuje využití, pokud je nastavena na 0.", - "transcoding_tone_mapping": "Mapování tónů", - "transcoding_tone_mapping_description": "Snaží se zachovat vzhled videí HDR při převodu na SDR. Každý algoritmus dělá různé kompromisy v oblasti barev, detailů a jasu. Hable zachovává detaily, Mobius zachovává barvy a Reinhard zachovává jas.", - "transcoding_transcode_policy": "Zásady překódování", - "transcoding_transcode_policy_description": "Zásady, kdy má být video překódováno. Videa HDR budou překódována vždy (kromě případů, kdy je překódování zakázáno).", - "transcoding_two_pass_encoding": "Dvouprůchodové kódování", - "transcoding_two_pass_encoding_setting_description": "Překódováním ve dvou průchodech získáte lépe zakódovaná videa. Pokud je povolen maximální datový tok (nutný pro práci s H.264 a HEVC), tento režim používá rozsah datového toku založený na maximálním datovém toku a ignoruje CRF. U VP9 lze CRF použít, pokud je max. datový tok zakázán.", - "transcoding_video_codec": "Video kodek", - "transcoding_video_codec_description": "VP9 má vysokou účinnost a kompatibilitu s webem, ale překódování trvá déle. HEVC má podobnou výkonnost, ale nižší kompatibilitu s webem. H.264 je široce kompatibilní a rychle se překódovává, ale vytváří mnohem větší soubory. AV1 je nejúčinnější kodek, ale chybí mu podpora ve starších zařízeních.", - "trash_enabled_description": "Povolení funkce koše", - "trash_number_of_days": "Počet dní", - "trash_number_of_days_description": "Počet dní, po které je třeba položku ponechat v koši, než bude trvale odstraněna", - "trash_settings": "Koš", - "trash_settings_description": "Správa nastavení koše", - "unlink_all_oauth_accounts": "Odpojit všechny OAuth účty", - "unlink_all_oauth_accounts_description": "Nezapomeňte odpojit všechny OAuth účty před přechodem k novému poskytovateli.", - "unlink_all_oauth_accounts_prompt": "Opravdu chcete odpojit všechny OAuth účty? Tím se resetuje OAuth ID pro každého uživatele a tento úkon nelze vrátit zpět.", - "user_cleanup_job": "Promazání uživatelů", - "user_delete_delay": "Účet a položky uživatele {user} budou trvale smazány za {delay, plural, one {# den} few {# dny} other {# dní}}.", - "user_delete_delay_settings": "Odložení odstranění", - "user_delete_delay_settings_description": "Počet dní po odstranění, po kterých bude odstraněn účet a položky uživatele. Úloha odstraňování uživatelů se spouští o půlnoci a kontroluje uživatele, kteří jsou připraveni k odstranění. Změny tohoto nastavení se vyhodnotí při dalším spuštění.", - "user_delete_immediately": "Účet a položky uživatele {user} budou zařazeny do fronty k trvalému smazání okamžitě.", - "user_delete_immediately_checkbox": "Uživatele a položky zařadit do fronty k okamžitému smazání", - "user_details": "Podrobnosti o uživateli", - "user_management": "Správa uživatelů", - "user_password_has_been_reset": "Heslo uživatele bylo obnoveno:", - "user_password_reset_description": "Poskytněte uživateli dočasné heslo a informujte ho, že si ho bude muset při příštím přihlášení změnit.", - "user_restore_description": "Účet uživatele {user} bude obnoven.", - "user_restore_scheduled_removal": "Obnovit uživatele - plánované odstranění {date, date, long}", - "user_settings": "Uživatelé", - "user_settings_description": "Správa nastavení uživatelů", - "user_successfully_removed": "Uživatel {email} byl úspěšně odstraněn.", - "users_page_description": "Stránka správců", - "version_check_enabled_description": "Povolit kontrolu verzí", - "version_check_implications": "Kontrola verze je založena na pravidelné komunikaci s github.com", - "version_check_settings": "Kontrola verze", - "version_check_settings_description": "Povolení/zakázání oznámení o nové verzi", - "video_conversion_job": "Překódování videí", - "video_conversion_job_description": "Překódování videí pro širší kompatibilitu s prohlížeči a zařízeními" - }, - "admin_email": "E-mail správce", - "admin_password": "Heslo správce", - "administration": "Administrace", - "advanced": "Pokročilé", - "advanced_settings_clear_image_cache": "Vyčistit mezipaměť obrázků", - "advanced_settings_clear_image_cache_error": "Chyba při čištění mezipaměti obrázků", - "advanced_settings_clear_image_cache_success": "Úspěšně vyčištěno {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Tuto možnost použijte k filtrování médií během synchronizace na základě alternativních kritérií. Tuto možnost vyzkoušejte pouze v případě, že máte problémy s detekcí všech alb v aplikaci.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTÁLNÍ] Použít alternativní filtr pro synchronizaci alb zařízení", - "advanced_settings_log_level_title": "Úroveň protokolování: {level}", - "advanced_settings_prefer_remote_subtitle": "U některých zařízení je načítání miniatur z lokálních prostředků velmi pomalé. Aktivujte toto nastavení, aby se místo toho načítaly vzdálené obrázky.", - "advanced_settings_prefer_remote_title": "Preferovat vzdálené obrázky", - "advanced_settings_proxy_headers_subtitle": "Definice hlaviček proxy serveru, které by měl Immich odesílat s každým síťovým požadavkem", - "advanced_settings_proxy_headers_title": "Vlastní proxy hlavičky [EXPERIMENTÁLNÍ]", - "advanced_settings_readonly_mode_subtitle": "Povoluje režim pouze pro čtení, ve kterém lze fotografie pouze prohlížet, ale funkce jako výběr více obrázků, sdílení, přenos, mazání jsou zakázány. Povolení/zakázání režimu pouze pro čtení pomocí avatara uživatele na hlavní obrazovce", - "advanced_settings_readonly_mode_title": "Režim pouze pro čtení", - "advanced_settings_self_signed_ssl_subtitle": "Vynechá ověření SSL certifikátu serveru. Vyžadováno pro self-signed certifikáty.", - "advanced_settings_self_signed_ssl_title": "Povolit self-signed SSL certifikáty [EXPERIMENTÁLNÍ]", - "advanced_settings_sync_remote_deletions_subtitle": "Automaticky odstranit nebo obnovit položku v tomto zařízení, když je tato akce provedena na webu", - "advanced_settings_sync_remote_deletions_title": "Synchronizace vzdáleného mazání [EXPERIMENTÁLNÍ]", - "advanced_settings_tile_subtitle": "Pokročilé uživatelské nastavení", - "advanced_settings_troubleshooting_subtitle": "Zobrazit dodatečné vlastnosti pro řešení problémů", - "advanced_settings_troubleshooting_title": "Řešení problémů", - "age_months": "{months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}", - "age_year_months": "1 rok a {months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}", - "age_years": "{years, plural, one {# rok} few {# roky} other {# let}}", - "album": "Album", - "album_added": "Přidáno album", - "album_added_notification_setting_description": "Dostávat e-mailové oznámení, když jste přidáni do sdíleného alba", - "album_cover_updated": "Obal alba aktualizován", - "album_delete_confirmation": "Opravdu chcete album {album} odstranit?", - "album_delete_confirmation_description": "Pokud je toto album sdíleno, ostatní uživatelé k němu již nebudou mít přístup.", - "album_deleted": "Album smazáno", - "album_info_card_backup_album_excluded": "VYLOUČENO", - "album_info_card_backup_album_included": "ZAHRNUTO", - "album_info_updated": "Informace o albu aktualizovány", - "album_leave": "Opustit album?", - "album_leave_confirmation": "Opravdu chcete opustit {album}?", - "album_name": "Název alba", - "album_options": "Možnosti alba", - "album_remove_user": "Odebrat uživatele?", - "album_remove_user_confirmation": "Opravdu chcete odebrat uživatele {user}?", - "album_search_not_found": "Nebyla nalezena žádná alba odpovídající vašemu hledání", - "album_selected": "Album vybráno", - "album_share_no_users": "Zřejmě jste toto album sdíleli se všemi uživateli, nebo nemáte žádného uživatele, se kterým byste ho mohli sdílet.", - "album_summary": "Souhrn alba", - "album_updated": "Album aktualizováno", - "album_updated_setting_description": "Dostávat e-mailová oznámení o nových položkách sdíleného alba", - "album_upload_assets": "Nahrajte soubory z počítače a přidejte je do alba", - "album_user_left": "Opustil {album}", - "album_user_removed": "Uživatel {user} odebrán", - "album_viewer_appbar_delete_confirm": "Opravdu chcete toto album odstranit ze svého účtu?", - "album_viewer_appbar_share_err_delete": "Nepodařilo se smazat album", - "album_viewer_appbar_share_err_leave": "Nepodařilo se opustit album", - "album_viewer_appbar_share_err_remove": "Při odstraňování položek z alba se vyskytly problémy", - "album_viewer_appbar_share_err_title": "Nepodařilo se změnit název alba", - "album_viewer_appbar_share_leave": "Opustit album", - "album_viewer_appbar_share_to": "Sdílet na", - "album_viewer_page_share_add_users": "Přidat uživatele", - "album_with_link_access": "Nechte kohokoli s odkazem zobrazit fotografie a lidi v tomto albu.", - "albums": "Alba", - "albums_count": "{count, plural, one {{count, number} album} few {{count, number} alba} other {{count, number} alb}}", - "albums_default_sort_order": "Výchozí řazení alb", - "albums_default_sort_order_description": "Výchozí řazení položek při vytváření nových alb.", - "albums_feature_description": "Sbírky položek, které lze sdílet s ostatními uživateli.", - "albums_on_device_count": "Alba v zařízení ({count})", - "albums_selected": "{count, plural, one {# album vybráno} few {# alba vybrány} other {# alb vybráno}}", - "all": "Vše", - "all_albums": "Všechna alba", - "all_people": "Všichni lidé", - "all_photos": "Všechny fotky", - "all_videos": "Všechna videa", - "allow_dark_mode": "Povolit tmavý režim", - "allow_edits": "Povolit úpravy", - "allow_public_user_to_download": "Povolit veřejnosti stahovat", - "allow_public_user_to_upload": "Povolit veřejnosti nahrávat", - "allowed": "Povoleno", - "alt_text_qr_code": "Obrázek QR kódu", - "always_keep": "Pokaždé ponechat", - "always_keep_photos_hint": "Uvolnění místa ponechá všechny fotky na tomto zařízení.", - "always_keep_videos_hint": "Uvolnění místa ponechá všechny videa na tomto zařízení.", - "anti_clockwise": "Proti směru hodinových ručiček", - "api_key": "API klíč", - "api_key_description": "Tato hodnota se zobrazí pouze jednou. Před zavřením okna ji nezapomeňte zkopírovat.", - "api_key_empty": "Název klíče API by neměl být prázdný", - "api_keys": "API klíče", - "app_architecture_variant": "Varianta (architektura)", - "app_bar_signout_dialog_content": "Určitě se chcete odhlásit?", - "app_bar_signout_dialog_ok": "Ano", - "app_bar_signout_dialog_title": "Odhlásit se", - "app_download_links": "Odkazy ke stažení aplikace", - "app_settings": "Aplikace", - "app_stores": "Obchody s aplikacemi", - "app_update_available": "K dispozici je aktualizace aplikace", - "appears_in": "Vyskytuje se v", - "apply_count": "Použít ({count, number})", - "archive": "Archiv", - "archive_action_prompt": "{count} přidaných do archivu", - "archive_or_unarchive_photo": "Přidat nebo odebrat fotku z archivu", - "archive_page_no_archived_assets": "Nebyla nalezena žádná archivovaná média", - "archive_page_title": "Archiv ({count})", - "archive_size": "Velikost archivu", - "archive_size_description": "Nastavte velikost archivu pro stahování (v GiB)", - "archived": "Archiv", - "archived_count": "{count, plural, other {Archivováno #}}", - "are_these_the_same_person": "Jedná se o stejnou osobu?", - "are_you_sure_to_do_this": "Opravdu to chcete udělat?", - "array_field_not_fully_supported": "Prvky pole vyžadují ruční úpravy JSON", - "asset_action_delete_err_read_only": "Nelze odstranit položky pouze pro čtení, přeskakuji", - "asset_action_share_err_offline": "Nelze načíst offline položky, přeskakuji", - "asset_added_to_album": "Přidáno do alba", - "asset_adding_to_album": "Přidávání do alba…", - "asset_created": "Položka vytvořena", - "asset_description_updated": "Popis položky byl aktualizován", - "asset_filename_is_offline": "Položka {filename} je offline", - "asset_has_unassigned_faces": "Položka má nepřiřazené obličeje", - "asset_hashing": "Hashování…", - "asset_list_group_by_sub_title": "Seskupit podle", - "asset_list_layout_settings_dynamic_layout_title": "Dynamické rozložení", - "asset_list_layout_settings_group_automatically": "Automaticky", - "asset_list_layout_settings_group_by": "Seskupit položky podle", - "asset_list_layout_settings_group_by_month_day": "Měsíc + den", - "asset_list_layout_sub_title": "Rozložení", - "asset_list_settings_subtitle": "Nastavení rozložení mřížky fotografií", - "asset_list_settings_title": "Mřížka fotografií", - "asset_not_found_on_device_android": "Položka nebyla nalezena na zařízení", - "asset_not_found_on_device_ios": "Položka nebyla nalezena na zařízení. Pokud používáte iCloud, položka může být nepřístupná kvůli poškozenému souboru uloženému na iCloudu", - "asset_not_found_on_icloud": "Položka nebyla nalezena na iCloudu. Položka může být nepřístupná kvůli poškozenému souboru uloženému na iCloudu", - "asset_offline": "Offline položka", - "asset_offline_description": "Toto externí položka se již na disku nenachází. Obraťte se na správce Immich a požádejte o pomoc.", - "asset_restored_successfully": "Položka úspěšně obnovena", - "asset_skipped": "Přeskočeno", - "asset_skipped_in_trash": "V koši", - "asset_trashed": "Položka vyhozena", - "asset_troubleshoot": "Řešení problémů s položkami", - "asset_uploaded": "Nahráno", - "asset_uploading": "Nahrávání…", - "asset_viewer_settings_subtitle": "Správa nastavení prohlížeče galerie", - "asset_viewer_settings_title": "Prohlížeč", - "assets": "Položky", - "assets_added_count": "{count, plural, one {Přidána # položka} few {Přidány # položky} other {Přidáno # položek}}", - "assets_added_to_album_count": "Do alba {count, plural, one {byla přidána # položka} few {byly přidány # položky} other {bylo přidáno # položek}}", - "assets_added_to_albums_count": "{assetTotal, plural, one {Přidána # položka} few{Přidány # položky} other {Přidáno # položek}} do {albumTotal, plural, one {# alba} other {# alb}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Položku} other {Položky}} nelze přidat do alba", - "assets_cannot_be_added_to_albums": "{count, plural, one {Položku} other {Položky}} nelze přidat do žádného z alb", - "assets_count": "{count, plural, one {# položka} few {# položky} other {# položek}}", - "assets_deleted_permanently": "{count} položek trvale odstraněno", - "assets_deleted_permanently_from_server": "{count} položek trvale odstraněno z Immich serveru", - "assets_downloaded_failed": "{count, plural, one {Stažen # soubor - {error} souborů selhalo} few {Staženy # soubory - {error} souborů selhalo} other {Staženo # souborů - {error} souborů selhalo}}", - "assets_downloaded_successfully": "{count, plural, one {Úspěšně stažen # soubor} few {Úspěšně staženy # soubory} other {Úspěšně staženo # souborů}}", - "assets_moved_to_trash_count": "{count, plural, one {# položka přesunuta} few {# položky přesunuty} other {# položek přesunuto}} do koše", - "assets_permanently_deleted_count": "Trvale {count, plural, one {smazána # položka} few {smazány # položky} other {smazáno # položek}}", - "assets_removed_count": "{count, plural, one {Odstraněna # položka} few {Odstraněny # položky} other {Odstraněno # položek}}", - "assets_removed_permanently_from_device": "{count} položek trvale odstraněno z vašeho zařízení", - "assets_restore_confirmation": "Opravdu chcete obnovit všechny vyhozené položky? Tuto akci nelze vrátit zpět! Upozorňujeme, že tímto způsobem nelze obnovit žádné offline položky.", - "assets_restored_count": "{count, plural, one {Obnovena # položka} few {Obnoveny # položky} other {Obnoveno # položek}}", - "assets_restored_successfully": "{count} položek úspěšně obnoveno", - "assets_trashed": "{count} položek vyhozeno do koše", - "assets_trashed_count": "{count, plural, one {Vyhozena # položka} few {Vyhozeny # položky} other {Vyhozeno # položek}}", - "assets_trashed_from_server": "{count} položek vyhozeno do koše na Immich serveru", - "assets_were_part_of_album_count": "{count, plural, one {Položka byla} other {Položky byly}} součástí alba", - "assets_were_part_of_albums_count": "{count, plural, one {Položka již byla} other {Položky již byly}} součástí alb", - "authorized_devices": "Autorizovaná zařízení", - "automatic_endpoint_switching_subtitle": "Připojit se místně přes určenou Wi-Fi, pokud je k dispozici, a používat alternativní připojení jinde", - "automatic_endpoint_switching_title": "Automatické přepínání URL", - "autoplay_slideshow": "Automatické přehrávání prezentace", - "back": "Zpět", - "back_close_deselect": "Zpět, zavřít nebo zrušit výběr", - "background_backup_running_error": "Právě probíhá zálohování na pozadí, nelze spustit ruční zálohování", - "background_location_permission": "Povolení polohy na pozadí", - "background_location_permission_content": "Aby bylo možné přepínat sítě při běhu na pozadí, musí mít Immich *vždy* přístup k přesné poloze, aby mohl zjistit název Wi-Fi sítě", - "background_options": "Možnosti běhu na pozadí", - "backup": "Záloha", - "backup_album_selection_page_albums_device": "Alba v zařízení ({count})", - "backup_album_selection_page_albums_tap": "Klepnutím na položku ji zahrnete, opětovným klepnutím ji vyloučíte", - "backup_album_selection_page_assets_scatter": "Položky mohou být roztroušeny ve více albech. To umožňuje zahrnout nebo vyloučit alba během procesu zálohování.", - "backup_album_selection_page_select_albums": "Vybraná alba", - "backup_album_selection_page_selection_info": "Informace o výběru", - "backup_album_selection_page_total_assets": "Celkový počet jedinečných položek", - "backup_albums_sync": "Synchronizace zálohovaných alb", - "backup_all": "Vše", - "backup_background_service_backup_failed_message": "Zálohování médií selhalo. Zkouším to znovu…", - "backup_background_service_complete_notification": "Zálohování položek dokončeno", - "backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. Zkouším to znovu…", - "backup_background_service_current_upload_notification": "Nahrávání {filename}", - "backup_background_service_default_notification": "Kontrola nových médií…", - "backup_background_service_error_title": "Chyba zálohování", - "backup_background_service_in_progress_notification": "Zálohování vašich médií…", - "backup_background_service_upload_failure_notification": "Nepodařilo se nahrát {filename}", - "backup_controller_page_albums": "Zálohovaná alba", - "backup_controller_page_background_app_refresh_disabled_content": "Povolte obnovení aplikace na pozadí v Nastavení > Obecné > Obnovení aplikace na pozadí, abyste mohli používat zálohování na pozadí.", - "backup_controller_page_background_app_refresh_disabled_title": "Obnovování aplikací na pozadí je vypnuté", - "backup_controller_page_background_app_refresh_enable_button_text": "Přejít do nastavení", - "backup_controller_page_background_battery_info_link": "Ukaž mi jak", - "backup_controller_page_background_battery_info_message": "Chcete-li dosáhnout nejlepších výsledků při zálohování na pozadí, vypněte všechny optimalizace baterie, které omezují aktivitu na pozadí pro Immich ve vašem zařízení. \n\nJelikož je to závislé na typu zařízení, vyhledejte požadované informace pro výrobce vašeho zařízení.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Optimalizace baterie", - "backup_controller_page_background_charging": "Pouze během nabíjení", - "backup_controller_page_background_configure_error": "Nepodařilo se nakonfigurovat službu na pozadí", - "backup_controller_page_background_delay": "Zpoždění zálohování nových médií: {duration}", - "backup_controller_page_background_description": "Povolte službu na pozadí pro automatické zálohování všech nových položek bez nutnosti otevření aplikace", - "backup_controller_page_background_is_off": "Automatické zálohování na pozadí je vypnuto", - "backup_controller_page_background_is_on": "Automatické zálohování na pozadí je zapnuto", - "backup_controller_page_background_turn_off": "Vypnout zálohování na pozadí", - "backup_controller_page_background_turn_on": "Povolit zálohování na pozadí", - "backup_controller_page_background_wifi": "Jen na Wi-Fi", - "backup_controller_page_backup": "Zálohování", - "backup_controller_page_backup_selected": "Vybrané: ", - "backup_controller_page_backup_sub": "Zálohované fotografie a videa", - "backup_controller_page_created": "Vytvořeno: {date}", - "backup_controller_page_desc_backup": "Zapněte zálohování na popředí, aby se nové položky automaticky nahrávaly na server při otevření aplikace.", - "backup_controller_page_excluded": "Vyloučeno: ", - "backup_controller_page_failed": "Nepodařilo se ({count})", - "backup_controller_page_filename": "Název souboru: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informace o zálohování", - "backup_controller_page_none_selected": "Žádné vybrané", - "backup_controller_page_remainder": "Zbývá", - "backup_controller_page_remainder_sub": "Zbývající fotografie a videa, která se mají zálohovat z vybraných alb", - "backup_controller_page_server_storage": "Serverové úložiště", - "backup_controller_page_start_backup": "Spustit zálohování", - "backup_controller_page_status_off": "Automatické zálohování na popředí je vypnuto", - "backup_controller_page_status_on": "Automatické zálohování na popředí je zapnuto", - "backup_controller_page_storage_format": "{used} z {total} použitých", - "backup_controller_page_to_backup": "Alba, která mají být zálohována", - "backup_controller_page_total_sub": "Všechny jedinečné fotografie a videa z vybraných alb", - "backup_controller_page_turn_off": "Vypnout zálohování na popředí", - "backup_controller_page_turn_on": "Povolit zálohování na popředí", - "backup_controller_page_uploading_file_info": "Informace o nahraném souboru", - "backup_err_only_album": "Nelze odstranit jediné vybrané album", - "backup_error_sync_failed": "Synchronizace selhala. Nelze zpracovat zálohu.", - "backup_info_card_assets": "položek", - "backup_manual_cancelled": "Zrušeno", - "backup_manual_in_progress": "Nahrávání již probíhá. Zkuste to znovu později", - "backup_manual_success": "Úspěch", - "backup_manual_title": "Stav nahrávání", - "backup_options": "Možnosti zálohování", - "backup_options_page_title": "Nastavení záloh", - "backup_setting_subtitle": "Správa nastavení zálohování na pozadí a na popředí", - "backup_settings_subtitle": "Správa nastavení nahrávání", - "backup_upload_details_page_more_details": "Klepněte pro více informací", - "backward": "Pozpátku", - "biometric_auth_enabled": "Biometrické ověřování je povoleno", - "biometric_locked_out": "Jste vyloučeni z biometrického ověřování", - "biometric_no_options": "Biometrické možnosti nejsou k dispozici", - "biometric_not_available": "Biometrické ověřování není na tomto zařízení k dispozici", - "birthdate_saved": "Datum narození úspěšně uloženo", - "birthdate_set_description": "Datum narození se používá k výpočtu věku osoby v době pořízení fotografie.", - "blurred_background": "Rozmazané pozadí", - "bugs_and_feature_requests": "Chyby a návrhy na funkce", - "build": "Sestavení", - "build_image": "Sestavení obrazu", - "bulk_delete_duplicates_confirmation": "Opravdu chcete hromadně odstranit {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se zachová největší položka z každé skupiny a všechny ostatní duplicity se trvale odstraní. Tuto akci nelze vrátit zpět!", - "bulk_keep_duplicates_confirmation": "Opravdu si chcete ponechat {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se vyřeší všechny duplicitní skupiny, aniž by se cokoli odstranilo.", - "bulk_trash_duplicates_confirmation": "Opravdu chcete hromadně vyhodit {count, plural, one {# duplicitní položku} few {# duplicitní položky} other {# duplicitních položek}}? Tím se zachová největší položka z každé skupiny a všechny ostatní duplikáty se vyhodí.", - "buy": "Zakoupit Immich", - "cache_settings_clear_cache_button": "Vymazat vyrovnávací paměť", - "cache_settings_clear_cache_button_title": "Vymaže vyrovnávací paměť aplikace. To výrazně ovlivní výkon aplikace, dokud se vyrovnávací paměť neobnoví.", - "cache_settings_duplicated_assets_clear_button": "VYMAZAT", - "cache_settings_duplicated_assets_subtitle": "Fotografie a videa, které aplikace ignoruje", - "cache_settings_duplicated_assets_title": "Duplicitní položky ({count})", - "cache_settings_statistics_album": "Knihovna náhledů", - "cache_settings_statistics_full": "Kompletní fotografie", - "cache_settings_statistics_shared": "Sdílené náhledy alb", - "cache_settings_statistics_thumbnail": "Náhledy", - "cache_settings_statistics_title": "Použití vyrovnávací paměti", - "cache_settings_subtitle": "Ovládání chování mobilní aplikace Immich v mezipaměti", - "cache_settings_tile_subtitle": "Ovládání chování místního úložiště", - "cache_settings_tile_title": "Místní úložiště", - "cache_settings_title": "Nastavení vyrovnávací paměti", - "camera": "Fotoaparát", - "camera_brand": "Značka fotoaparátu", - "camera_model": "Model fotoaparátu", - "cancel": "Zrušit", - "cancel_search": "Zrušit vyhledávání", - "canceled": "Zrušeno", - "canceling": "Rušení", - "cannot_merge_people": "Nelze sloučit osoby", - "cannot_undo_this_action": "Tuto akci nelze vrátit zpět!", - "cannot_update_the_description": "Nelze aktualizovat popis", - "cast": "Odeslat do zařízení", - "cast_description": "Nastavení dostupných cílů přenosu", - "change_date": "Změnit datum", - "change_description": "Změnit popis", - "change_display_order": "Změnit pořadí zobrazení", - "change_expiration_time": "Změna konce platnosti", - "change_location": "Změna polohy", - "change_name": "Změnit jméno", - "change_name_successfully": "Jméno bylo úspěšně změněno", - "change_password": "Změna hesla", - "change_password_description": "Buď se do systému přihlašujete poprvé, nebo jste byli požádáni o změnu hesla. Zadejte prosím nové heslo níže.", - "change_password_form_confirm_password": "Potvrďte heslo", - "change_password_form_description": "Dobrý den, {name}\n\nje to buď poprvé, co se přihlašujete do systému, nebo byl vytvořen požadavek na změnu hesla. Níže zadejte nové heslo.", - "change_password_form_log_out": "Odhlásit všechna ostatní zařízení", - "change_password_form_log_out_description": "Doporučujeme se odhlásit ze všech ostatních zařízení", - "change_password_form_new_password": "Nové heslo", - "change_password_form_password_mismatch": "Hesla se neshodují", - "change_password_form_reenter_new_password": "Znovu zadejte nové heslo", - "change_pin_code": "Změnit PIN kód", - "change_trigger": "Spouštěč změny", - "change_trigger_prompt": "Opravdu chcete změnit spouštěč? Tím se odstraní všechny existující akce a filtry.", - "change_your_password": "Změna vašeho hesla", - "changed_visibility_successfully": "Změna viditelnosti proběhla úspěšně", - "charging": "Nabíjení", - "charging_requirement_mobile_backup": "Zálohování na pozadí vyžaduje, aby bylo zařízení nabíjeno", - "check_corrupt_asset_backup": "Kontrola poškozených záloh položek", - "check_corrupt_asset_backup_button": "Provést kontrolu", - "check_corrupt_asset_backup_description": "Tuto kontrolu provádějte pouze přes Wi-Fi a po zálohování všech prostředků. Takto operace může trvat několik minut.", - "check_logs": "Zkontrolujte protokoly", - "checksum": "Kontrolní součet", - "choose_matching_people_to_merge": "Zvolte odpovídající osoby ke sloučení", - "city": "Město", - "cleanup_confirm_description": "Immich našel {count} položek (vytvořených před {date}), které jsou bezpečně zálohovány na serveru. Chcete odstranit místní kopie z tohoto zařízení?", - "cleanup_confirm_prompt_title": "Odstranit z tohoto zařízení?", - "cleanup_deleted_assets": "Přesunuto {count} položek do koše zařízení", - "cleanup_deleting": "Přesun do koše...", - "cleanup_found_assets": "Nalezeno {count} zálohovaných položek", - "cleanup_found_assets_with_size": "Nalezeno {count} založeno {size} položek", - "cleanup_icloud_shared_albums_excluded": "Sdílená iCloud alba jsou vyloučena z prohledávání", - "cleanup_no_assets_found": "Nebyly nalezeny žádné položky odpovídající výše uvedeným kritériím. Funkce Uvolnit místo může odstranit pouze položky, které byly zálohovány na server", - "cleanup_preview_title": "Položky k odstranění ({count})", - "cleanup_step3_description": "Vyhledat zálohované položky odpovídající vašemu datu a zachovat nastavení.", - "cleanup_step4_summary": "{count} položek (vytvořených před {date}) je zařazeno do fronty k odstranění ze zařízení. Fotky zůstanou přístupné z aplikace Immich.", - "cleanup_trash_hint": "Pro úplné uvolnění úložného prostoru otevřete aplikaci systémové galerie a vyprázdněte koš", - "clear": "Vymazat", - "clear_all": "Vymazat vše", - "clear_all_recent_searches": "Vymazat všechna nedávná vyhledávání", - "clear_file_cache": "Vymazat mezipaměť souborů", - "clear_message": "Vymazat zprávu", - "clear_value": "Vymazat hodnotu", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Zadejte heslo", - "client_cert_import": "Importovat", - "client_cert_import_success_msg": "Klientský certifikát je importován", - "client_cert_invalid_msg": "Neplatný soubor certifikátu nebo špatné heslo", - "client_cert_remove_msg": "Klientský certifikát je odstraněn", - "client_cert_subtitle": "Podporuje pouze formát PKCS12 (.p12, .pfx). Import/odstranění certifikátu je možné pouze před přihlášením", - "client_cert_title": "Klientský SSL certifikát [EXPERIMENTÁLNÍ]", - "clockwise": "Po směru hodinových ručiček", - "close": "Zavřít", - "collapse": "Sbalit", - "collapse_all": "Sbalit vše", - "color": "Barva", - "color_theme": "Barevný motiv", - "command": "Příkaz", - "comment_deleted": "Komentář odstraněn", - "comment_options": "Možnosti komentáře", - "comments_and_likes": "Komentáře a lajky", - "comments_are_disabled": "Komentáře jsou vypnuty", - "common_create_new_album": "Vytvořit nové album", - "completed": "Dokončeno", - "confirm": "Potvrdit", - "confirm_admin_password": "Potvrzení hesla správce", - "confirm_delete_face": "Opravdu chcete z položky odstranit obličej osoby {name}?", - "confirm_delete_shared_link": "Opravdu chcete odstranit tento sdílený odkaz?", - "confirm_keep_this_delete_others": "Všechny ostatní položky v tomto uskupení mimo této budou odstraněny. Opravdu chcete pokračovat?", - "confirm_new_pin_code": "Potvrzení nového PIN kódu", - "confirm_password": "Potvrzení hesla", - "confirm_tag_face": "Opravdu chcete označit tento obličej jako {name}?", - "confirm_tag_face_unnamed": "Opravdu chcete označit tento obličej?", - "connected_device": "Připojené zařízení", - "connected_to": "Připojeno k", - "contain": "Obsah", - "context": "Kontext", - "continue": "Pokračovat", - "control_bottom_app_bar_create_new_album": "Vytvořit nové album", - "control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich", - "control_bottom_app_bar_delete_from_local": "Smazat ze zařízení", - "control_bottom_app_bar_edit_location": "Upravit polohu", - "control_bottom_app_bar_edit_time": "Upravit datum a čas", - "control_bottom_app_bar_share_link": "Sdílet odkaz", - "control_bottom_app_bar_share_to": "Sdílet v", - "control_bottom_app_bar_trash_from_immich": "Přesunout do koše", - "copied_image_to_clipboard": "Obrázek zkopírován do schránky.", - "copied_to_clipboard": "Zkopírováno do schránky!", - "copy_error": "Chyba kopírování", - "copy_file_path": "Kopírovat cestu k souboru", - "copy_image": "Kopírovat obrázek", - "copy_link": "Kopírovat odkaz", - "copy_link_to_clipboard": "Kopírovat odkaz do schránky", - "copy_password": "Kopírovat heslo", - "copy_to_clipboard": "Kopírovat do schránky", - "country": "Země", - "cover": "Překrýt", - "covers": "Obaly", - "create": "Vytvořit", - "create_album": "Vytvořit album", - "create_album_page_untitled": "Bez názvu", - "create_api_key": "Vytvořit API klíč", - "create_first_workflow": "Vytvořte první pracovní postup", - "create_library": "Vytvořit knihovnu", - "create_link": "Vytvořit odkaz", - "create_link_to_share": "Vytvořit odkaz pro sdílení", - "create_link_to_share_description": "Umožnit každému, kdo má odkaz, zobrazit vybrané fotografie", - "create_new": "VYTVOŘIT NOVÉ", - "create_new_person": "Vytvořit novou osobu", - "create_new_person_hint": "Přiřadit vybrané položky nové osobě", - "create_new_user": "Vytvořit nového uživatele", - "create_shared_album_page_share_add_assets": "PŘIDAT POLOŽKY", - "create_shared_album_page_share_select_photos": "Vybrat fotografie", - "create_shared_link": "Vytvořit sdílený odkaz", - "create_tag": "Vytvořit značku", - "create_tag_description": "Vytvoření nové značky. U vnořených značek zadejte celou cestu ke značce včetně dopředných lomítek.", - "create_user": "Vytvořit uživatele", - "create_workflow": "Vytvořit pracovní postup", - "created": "Vytvořeno", - "created_at": "Vytvořeno", - "creating_linked_albums": "Vytváření propojených alb...", - "crop": "Oříznout", - "crop_aspect_ratio_fixed": "Pevný", - "crop_aspect_ratio_free": "Volný", - "crop_aspect_ratio_original": "Původní", - "curated_object_page_title": "Věci", - "current_device": "Současné zařízení", - "current_pin_code": "Aktuální PIN kód", - "current_server_address": "Aktuální adresa serveru", - "custom_date": "Vlastní datum", - "custom_locale": "Vlastní lokalizace", - "custom_locale_description": "Formátovat datumy a čísla podle jazyka a oblasti", - "custom_url": "Vlastní URL", - "cutoff_date_description": "Zanechat fotografie a videa z posledních…", - "cutoff_day": "{count, plural, one {den} few {dny} other {dnů}}", - "cutoff_year": "{count, plural, one {rok} few {roky} other {let}}", - "daily_title_text_date": "EEEE, d. MMMM", - "daily_title_text_date_year": "EEEE, d. MMMM y", - "dark": "Tmavý", - "dark_theme": "Přepnout tmavý motiv", - "date": "Datum", - "date_after": "Datum po", - "date_and_time": "Datum a čas", - "date_before": "Datum před", - "date_format": "EEEE, d. MMMM y • H:mm", - "date_of_birth_saved": "Datum narození úspěšně uloženo", - "date_range": "Rozsah dat", - "day": "Den", - "days": "Dnů", - "deduplicate_all": "Odstranit všechny duplicity", - "deduplication_criteria_1": "Velikost obrázku v bajtech", - "deduplication_criteria_2": "Počet EXIF dat", - "deduplication_info": "Informace o deduplikaci", - "deduplication_info_description": "Pro automatický předvýběr položek a hromadné odstranění duplicit se zohledňuje:", - "default_locale": "Výchozí jazyk", - "default_locale_description": "Formátovat datumy a čísla podle místního prostředí prohlížeče", - "delete": "Smazat", - "delete_action_confirmation_message": "Opravdu chcete odstranit tuto položku? Tato akce přesune položku do serverového koše a zeptá se vás, zda ji chcete odstranit lokálně", - "delete_action_prompt": "{count} smazáno", - "delete_album": "Smazat album", - "delete_api_key_prompt": "Opravdu chcete tento API klíč odstranit?", - "delete_dialog_alert": "Tyto položky budou trvale smazány z aplikace Immich i z vašeho zařízení", - "delete_dialog_alert_local": "Tyto položky budou z vašeho zařízení trvale smazány, ale budou stále k dispozici na Immich serveru", - "delete_dialog_alert_local_non_backed_up": "Některé položky nejsou zálohovány na Immich server a budou ze zařízení trvale smazány", - "delete_dialog_alert_remote": "Tyto položky budou trvale smazány z Immich serveru", - "delete_dialog_ok_force": "Přesto smazat", - "delete_dialog_title": "Smazat trvale", - "delete_duplicates_confirmation": "Opravdu chcete tyto duplicity trvale odstranit?", - "delete_face": "Odstranit obličej", - "delete_key": "Smazat klíč", - "delete_library": "Smazat knihovnu", - "delete_link": "Smazat odkaz", - "delete_local_action_prompt": "{count} smazáno lokálně", - "delete_local_dialog_ok_backed_up_only": "Smazat pouze zálohované", - "delete_local_dialog_ok_force": "Přesto smazat", - "delete_others": "Smazat ostatní", - "delete_permanently": "Trvale smazat", - "delete_permanently_action_prompt": "{count} trvale smazáno", - "delete_shared_link": "Smazat sdílený odkaz", - "delete_shared_link_dialog_title": "Odstranit sdílený odkaz", - "delete_tag": "Smazat značku", - "delete_tag_confirmation_prompt": "Opravdu chcete odstranit značku {tagName}?", - "delete_user": "Odstranit uživatele", - "deleted_shared_link": "Smazat sdílený odkaz", - "deletes_missing_assets": "Odstraní položky chybějící na disku", - "description": "Popis", - "description_input_hint_text": "Přidat popis...", - "description_input_submit_error": "Chyba aktualizace popisu, další podrobnosti najdete v logu", - "deselect_all": "Zrušit výběr všech", - "details": "Podrobnosti", - "direction": "Směr", - "disable": "Zakázat", - "disabled": "Zakázáno", - "disallow_edits": "Zakázat úpravy", - "discord": "Discord", - "discover": "Objevit", - "discovered_devices": "Nalezená zařízení", - "dismiss_all_errors": "Zrušit všechny chyby", - "dismiss_error": "Zrušit chybu", - "display_options": "Možnosti zobrazení", - "display_order": "Pořadí zobrazení", - "display_original_photos": "Zobrazit originální fotky", - "display_original_photos_setting_description": "Preferovat zobrazení původních fotek při prohlížení položek namísto miniatur, pokud je originální položka kompatibilní s webem. To může mít za následek nižší rychlost zobrazení fotek.", - "do_not_show_again": "Tuto zprávu již nezobrazovat", - "documentation": "Dokumentace", - "done": "Hotovo", - "download": "Stáhnout", - "download_action_prompt": "Stahování {count} položek", - "download_canceled": "Stahování zrušeno", - "download_complete": "Stahování kompletní", - "download_enqueue": "Stahování ve frontě", - "download_error": "Chyba při stahování", - "download_failed": "Stahování selhalo", - "download_finished": "Stahování dokončeno", - "download_include_embedded_motion_videos": "Vložená videa", - "download_include_embedded_motion_videos_description": "Zahrnout videa vložená do pohyblivých fotografií jako samostatný soubor", - "download_notfound": "Stahování nebylo nalezeno", - "download_original": "Stáhnout originál", - "download_paused": "Stahování pozastaveno", - "download_settings": "Stahování", - "download_settings_description": "Správa nastavení souvisejících se stahováním", - "download_started": "Stahování zahájeno", - "download_sucess": "Stažení úspěšné", - "download_sucess_android": "Média byla stažena do DCIM/Immich", - "download_waiting_to_retry": "Čekání na opakovaný pokus", - "downloading": "Stahování", - "downloading_asset_filename": "Stahování položky {filename}", - "downloading_from_icloud": "Stahování z iCloudu", - "downloading_media": "Stahování média", - "drop_files_to_upload": "Pro nahrání sem přetáhněte soubory", - "duplicates": "Duplicity", - "duplicates_description": "Vyřešte každou skupinu tak, že uvedete, které skupiny jsou duplicitní", - "duration": "Doba trvání", - "edit": "Upravit", - "edit_album": "Upravit album", - "edit_avatar": "Upravit avatar", - "edit_birthday": "Upravit datum narození", - "edit_date": "Upravit datum", - "edit_date_and_time": "Upravit datum a čas", - "edit_date_and_time_action_prompt": "{count} časových údajů upraveno", - "edit_date_and_time_by_offset": "Posunout datum", - "edit_date_and_time_by_offset_interval": "Nový rozsah dat: {from} - {to}", - "edit_description": "Upravit popis", - "edit_description_prompt": "Vyberte nový popis:", - "edit_exclusion_pattern": "Upravit vzor vyloučení", - "edit_faces": "Upravit obličeje", - "edit_key": "Upravit klíč", - "edit_link": "Upravit odkaz", - "edit_location": "Upravit polohu", - "edit_location_action_prompt": "{count} upravených poloh", - "edit_location_dialog_title": "Poloha", - "edit_name": "Upravit jméno", - "edit_people": "Upravit lidi", - "edit_tag": "Upravit značku", - "edit_title": "Upravit název", - "edit_user": "Upravit uživatele", - "edit_workflow": "Upravit pracovní postup", - "editor": "Editor", - "editor_close_without_save_prompt": "Změny nebudou uloženy", - "editor_close_without_save_title": "Zavřít editor?", - "editor_confirm_reset_all_changes": "Opravdu chcete zrušit všechny změny?", - "editor_flip_horizontal": "Otočit vodorovně", - "editor_flip_vertical": "Otočit svisle", - "editor_orientation": "Orientace", - "editor_reset_all_changes": "Zrušit změny", - "editor_rotate_left": "Otočit o 90° doleva", - "editor_rotate_right": "Otočit o 90° doprava", - "email": "E-mail", - "email_notifications": "E-mailová oznámení", - "empty_folder": "Tato složka je prázdná", - "empty_trash": "Vyprázdnit koš", - "empty_trash_confirmation": "Opravdu chcete vysypat koš? Tím se z Immiche trvale odstraní všechny položky v koši.\nTuto akci nelze vrátit zpět!", - "enable": "Povolit", - "enable_backup": "Povolit zálohování", - "enable_biometric_auth_description": "Zadejte váš PIN kód pro povolení biometrického ověřování", - "enabled": "Povoleno", - "end_date": "Konečné datum", - "enqueued": "Ve frontě", - "enter_wifi_name": "Zadejte název Wi-Fi", - "enter_your_pin_code": "Zadejte PIN kód", - "enter_your_pin_code_subtitle": "Zadejte PIN kód pro přístup k uzamčené složce", - "error": "Chyba", - "error_change_sort_album": "Nepodařilo se změnit pořadí alba", - "error_delete_face": "Chyba při odstraňování obličeje z položky", - "error_getting_places": "Chyba při zjišťování míst", - "error_loading_albums": "Chyba načítaní alb", - "error_loading_image": "Chyba při načítání obrázku", - "error_loading_partners": "Chyba při načítání partnerů: {error}", - "error_retrieving_asset_information": "Chyba při získávání informací o položce", - "error_saving_image": "Chyba: {error}", - "error_tag_face_bounding_box": "Chyba při označování obličeje - nelze získat souřadnice ohraničujícího rámečku", - "error_title": "Chyba - Něco se pokazilo", - "error_while_navigating": "Chyba při načítání položky", - "errors": { - "cannot_navigate_next_asset": "Nelze přejít na další položku", - "cannot_navigate_previous_asset": "Nelze přejít na předchozí položku", - "cant_apply_changes": "Nelze použít změny", - "cant_change_activity": "Nelze {enabled, select, true {zakázat} other {povolit}} aktivitu", - "cant_change_asset_favorite": "Nelze změnit oblíbenou položku", - "cant_change_metadata_assets_count": "Nelze změnit metadata {count, plural, one {# položky} other {# položek}}", - "cant_get_faces": "Nelze načíst obličeje", - "cant_get_number_of_comments": "Nelze načíst počet komentářů", - "cant_search_people": "Nelze vyhledávat lidi", - "cant_search_places": "Nelze vyhledávat místa", - "error_adding_assets_to_album": "Chyba při přidávání položek do alba", - "error_adding_users_to_album": "Chyba při přidávání uživatelů do alba", - "error_deleting_shared_user": "Chyba při odstraňování sdíleného uživatele", - "error_downloading": "Chyba při stahování {filename}", - "error_hiding_buy_button": "Chyba při skrývání tlačítka koupit", - "error_removing_assets_from_album": "Chyba při odstraňování položek z alba, další podrobnosti najdete v konzoli", - "error_selecting_all_assets": "Chyba při výběru všech položek", - "exclusion_pattern_already_exists": "Tento vzor vyloučení již existuje.", - "failed_to_create_album": "Nepodařilo se vytvořit album", - "failed_to_create_shared_link": "Nepodařilo se vytvořit sdílený odkaz", - "failed_to_edit_shared_link": "Nepodařilo se upravit sdílený odkaz", - "failed_to_get_people": "Nepodařilo se načíst lidi", - "failed_to_keep_this_delete_others": "Nepodařilo se zachovat tuto položku a odstranit ostatní položky", - "failed_to_load_asset": "Nepodařilo se načíst položku", - "failed_to_load_assets": "Nepodařilo se načíst položky", - "failed_to_load_notifications": "Nepodařilo se načíst oznámení", - "failed_to_load_people": "Chyba načítání osob", - "failed_to_remove_product_key": "Nepodařilo se odebrat klíč produktu", - "failed_to_reset_pin_code": "Nepodařilo se resetovat PIN kód", - "failed_to_stack_assets": "Nepodařilo se seskupit položky", - "failed_to_unstack_assets": "Nepodařilo se zrušit seskupení položek", - "failed_to_update_notification_status": "Nepodařilo se aktualizovat stav oznámení", - "incorrect_email_or_password": "Nesprávný e-mail nebo heslo", - "library_folder_already_exists": "Tato importní cesta již existuje.", - "paths_validation_failed": "{paths, plural, one {# cesta neprošla} few {# cesty neprošly} other {# cest neprošlo}} kontrolou", - "profile_picture_transparent_pixels": "Profilové obrázky nemohou mít průhledné pixely. Obrázek si prosím zvětšete nebo posuňte.", - "quota_higher_than_disk_size": "Nastavili jste kvótu vyšší, než je velikost disku", - "something_went_wrong": "Něco se pokazilo", - "unable_to_add_album_users": "Nelze přidat uživatele do alba", - "unable_to_add_assets_to_shared_link": "Nelze přidat položky do sdíleného odkazu", - "unable_to_add_comment": "Nelze přidat komentář", - "unable_to_add_exclusion_pattern": "Nelze přidat vzor vyloučení", - "unable_to_add_partners": "Nelze přidat partnery", - "unable_to_add_remove_archive": "Nelze {archived, select, true {odstranit položku z} other {přidat položku do}} archivu", - "unable_to_add_remove_favorites": "Nelze {favorite, select, true {oblíbit položku} other {zrušit oblíbení položky}}", - "unable_to_archive_unarchive": "Nelze {archived, select, true {archivovat} other {odebrat z archivu}}", - "unable_to_change_album_user_role": "Nelze změnit roli uživatele alba", - "unable_to_change_date": "Nelze změnit datum", - "unable_to_change_description": "Nelze změnit popis", - "unable_to_change_favorite": "Nelze změnit oblíbení položky", - "unable_to_change_location": "Nelze změnit polohu", - "unable_to_change_password": "Nelze změnit heslo", - "unable_to_change_visibility": "Nelze změnit viditelnost u {count, plural, one {# osoby} few {# osob} other {# lidí}}", - "unable_to_complete_oauth_login": "Nelze dokončit OAuth přihlášení", - "unable_to_connect": "Nelze se připojit", - "unable_to_copy_to_clipboard": "Nelze zkopírovat do schránky, ujistěte se, že na stránku přistupujete přes https", - "unable_to_create": "Nelze vytvořit pracovní postup", - "unable_to_create_admin_account": "Nelze vytvořit účet správce", - "unable_to_create_api_key": "Nelze vytvořit nový API klíč", - "unable_to_create_library": "Nelze vytvořit knihovnu", - "unable_to_create_user": "Nelze vytvořit uživatele", - "unable_to_delete_album": "Nelze smazat album", - "unable_to_delete_asset": "Nelze odstranit položku", - "unable_to_delete_assets": "Chyba při odstraňování položek", - "unable_to_delete_exclusion_pattern": "Nelze odstranit vzor vyloučení", - "unable_to_delete_shared_link": "Nepodařilo se odstranit sdílený odkaz", - "unable_to_delete_user": "Nelze odstranit uživatele", - "unable_to_delete_workflow": "Nelze odstranit pracovní postup", - "unable_to_download_files": "Nelze stáhnout soubory", - "unable_to_edit_exclusion_pattern": "Nelze upravit vzor vyloučení", - "unable_to_empty_trash": "Nelze vyprázdnit koš", - "unable_to_enter_fullscreen": "Nelze přejít do režimu celé obrazovky", - "unable_to_exit_fullscreen": "Nelze ukončit zobrazení na celou obrazovku", - "unable_to_get_comments_number": "Nelze načíst počet komentářů", - "unable_to_get_shared_link": "Nepodařilo se získat sdílený odkaz", - "unable_to_hide_person": "Nelze skrýt osobu", - "unable_to_link_motion_video": "Nelze připojit pohyblivé video", - "unable_to_link_oauth_account": "Nelze propojit OAuth účet", - "unable_to_log_out_all_devices": "Nelze odhlásit všechna zařízení", - "unable_to_log_out_device": "Nelze odhlásit zařízení", - "unable_to_login_with_oauth": "Nelze se přihlásit pomocí OAuth", - "unable_to_play_video": "Nelze přehrát video", - "unable_to_reassign_assets_existing_person": "Nelze přeřadit položky na {name, select, null {existující osobu} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nelze přeřadit položku na novou osobu", - "unable_to_refresh_user": "Nelze aktualizovat uživatele", - "unable_to_remove_album_users": "Nelze odebrat uživatele z alba", - "unable_to_remove_api_key": "Nelze odstranit API klíč", - "unable_to_remove_assets_from_shared_link": "Nelze odstranit položky ze sdíleného odkazu", - "unable_to_remove_library": "Nelze odstranit knihovnu", - "unable_to_remove_partner": "Nelze odebrat partnera", - "unable_to_remove_reaction": "Nelze odstranit reakci", - "unable_to_reset_password": "Nelze obnovit heslo", - "unable_to_reset_pin_code": "Nelze resetovat PIN kód", - "unable_to_resolve_duplicate": "Nelze vyřešit duplicitu", - "unable_to_restore_assets": "Nelze obnovit položky", - "unable_to_restore_trash": "Nelze obnovit koš", - "unable_to_restore_user": "Nelze obnovit uživatele", - "unable_to_save_album": "Nelze uložit album", - "unable_to_save_api_key": "Nelze uložit API klíč", - "unable_to_save_date_of_birth": "Nepodařilo se uložit datum narození", - "unable_to_save_name": "Nelze uložit jméno", - "unable_to_save_profile": "Nelze uložit profil", - "unable_to_save_settings": "Nelze uložit nastavení", - "unable_to_scan_libraries": "Nelze prohledat knihovny", - "unable_to_scan_library": "Nelze prohledat knihovnu", - "unable_to_set_feature_photo": "Nelze nastavit hlavní fotografii", - "unable_to_set_profile_picture": "Nelze nastavit profilový obrázek", - "unable_to_set_rating": "Nelze nastavit hodnocení", - "unable_to_submit_job": "Nelze odeslat úlohu", - "unable_to_trash_asset": "Nelze vyhodit položku do koše", - "unable_to_unlink_account": "Nelze zrušit propojení účtu", - "unable_to_unlink_motion_video": "Nelze odpojit pohyblivé video", - "unable_to_update_album_cover": "Nelze aktualizovat obal alba", - "unable_to_update_album_info": "Nelze aktualizovat informace o albu", - "unable_to_update_library": "Nelze aktualizovat knihovnu", - "unable_to_update_location": "Nelze aktualizovat polohu", - "unable_to_update_settings": "Nelze aktualizovat nastavení", - "unable_to_update_timeline_display_status": "Nelze aktualizovat stav zobrazení časové osy", - "unable_to_update_user": "Nelze aktualizovat uživatele", - "unable_to_update_workflow": "Nelze aktualizovat pracovní postup", - "unable_to_upload_file": "Nepodařilo se nahrát soubor" - }, - "errors_text": "Chyby", - "exclusion_pattern": "Vzor vyloučení", - "exif": "Exif", - "exif_bottom_sheet_description": "Přidat popis...", - "exif_bottom_sheet_description_error": "Chyba při aktualizaci popisu", - "exif_bottom_sheet_details": "PODROBNOSTI", - "exif_bottom_sheet_location": "POLOHA", - "exif_bottom_sheet_no_description": "Žádný popisek", - "exif_bottom_sheet_people": "LIDÉ", - "exif_bottom_sheet_person_add_person": "Přidat jméno", - "exit_slideshow": "Ukončit prezentaci", - "expand_all": "Rozbalit vše", - "experimental_settings_new_asset_list_subtitle": "Zpracovávám", - "experimental_settings_new_asset_list_title": "Povolení experimentální mřížky fotografií", - "experimental_settings_subtitle": "Používejte na vlastní riziko!", - "experimental_settings_title": "Experimentální", - "expire_after": "Platnost končí", - "expired": "Vypršela platnost", - "expires_date": "Platnost končí {date}", - "explore": "Prozkoumat", - "explorer": "Průzkumník", - "export": "Export", - "export_as_json": "Exportovat jako JSON", - "export_database": "Exportovat databázi", - "export_database_description": "Exportovat databázi SQLite", - "extension": "Přípona", - "external": "Externí", - "external_libraries": "Externí knihovny", - "external_network": "Externí síť", - "external_network_sheet_info": "Pokud nejste v preferované síti Wi-Fi, aplikace se připojí k serveru prostřednictvím první z níže uvedených adres URL, které může dosáhnout, počínaje shora dolů", - "face_unassigned": "Nepřiřazena", - "failed": "Selhalo", - "failed_count": "Selhalo: {count}", - "failed_to_authenticate": "Ověření se nezdařilo", - "failed_to_load_assets": "Nepodařilo se načíst položky", - "failed_to_load_folder": "Nepodařilo se načíst složku", - "favorite": "Oblíbit", - "favorite_action_prompt": "{count} přidáno do Oblíbených", - "favorite_or_unfavorite_photo": "Oblíbit nebo zrušit oblíbení fotky", - "favorites": "Oblíbené", - "favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média", - "feature_photo_updated": "Hlavní fotka aktualizována", - "features": "Funkce", - "features_in_development": "Funkce ve vývoji", - "features_setting_description": "Správa funkcí aplikace", - "file_name_or_extension": "Název nebo přípona souboru", - "file_size": "Velikost souboru", - "filename": "Název souboru", - "filetype": "Typ souboru", - "filter": "Filtr", - "filter_description": "Podmínky pro filtrování cílových položek", - "filter_people": "Filtrovat lidi", - "filter_places": "Filtrovat místa", - "filters": "Filtry", - "find_them_fast": "Najděte je rychle vyhledáním jejich jména", - "first": "První", - "fix_incorrect_match": "Opravit nesprávnou shodu", - "folder": "Složka", - "folder_not_found": "Složka nebyla nalezena", - "folders": "Složky", - "folders_feature_description": "Procházení zobrazení složek s fotografiemi a videi v souborovém systému", - "forgot_pin_code_question": "Zapomněli jste PIN?", - "forward": "Dopředu", - "free_up_space": "Uvolnit místo", - "free_up_space_description": "Přesunout zálohované fotografie a videa do koše zařízení, abyste uvolnili místo. Vaše kopie na serveru zůstanou v bezpečí.", - "free_up_space_settings_subtitle": "Uvolnit úložiště zařízení", - "full_path": "Úplná cesta: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Tato funkce načítá externí zdroje z Googlu, aby mohla fungovat.", - "general": "Obecné", - "geolocation_instruction_location": "Klikněte na položku s GPS souřadnicemi, abyste mohli použít její polohu, nebo vyberte polohu přímo z mapy", - "get_help": "Získat pomoc", - "get_people_error": "Chyba při načítání lidí", - "get_wifiname_error": "Nepodařilo se získat název Wi-Fi. Zkontrolujte, zda jste udělili potřebná oprávnění a zda jste připojeni k Wi-Fi síti", - "getting_started": "Začínáme", - "go_back": "Přejít zpět", - "go_to_folder": "Přejít do složky", - "go_to_search": "Přejít na vyhledávání", - "gps": "GPS", - "gps_missing": "Bez GPS", - "grant_permission": "Udělit oprávnění", - "group_albums_by": "Seskupit alba podle...", - "group_country": "Seskupit podle země", - "group_no": "Neseskupovat", - "group_owner": "Seskupit podle uživatele", - "group_places_by": "Seskupit místa podle...", - "group_year": "Seskupit podle roku", - "haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu", - "haptic_feedback_title": "Dotyková zpětná vazba", - "has_quota": "Má kvótu", - "hash_asset": "Hash položky", - "hashed_assets": "Hashované položky", - "hashing": "Hashování", - "header_settings_add_header_tip": "Přidat hlavičku", - "header_settings_field_validator_msg": "Hodnota nemůže být prázdná", - "header_settings_header_name_input": "Název hlavičky", - "header_settings_header_value_input": "Hodnota hlavičky", - "headers_settings_tile_title": "Vlastní proxy hlavičky", - "height": "Výška", - "hi_user": "Ahoj {name} ({email})", - "hide_all_people": "Skrýt všechny lidi", - "hide_gallery": "Skrýt galerii", - "hide_named_person": "Skrýt osobu {name}", - "hide_password": "Skrýt heslo", - "hide_person": "Skrýt osobu", - "hide_schema": "Skrýt schéma", - "hide_text_recognition": "Skrýt rozpoznávání textu", - "hide_unnamed_people": "Skrýt nejmenované lidi", - "home_page_add_to_album_conflicts": "Přidáno {added} položek do alba {album}. {failed} položek je již v albu.", - "home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuji", - "home_page_add_to_album_success": "Přidáno {added} položek do alba {album}.", - "home_page_album_err_partner": "Položky partnera nelze zatím přidat do alba, přeskakuji", - "home_page_archive_err_local": "Zatím nemohu archivovat lokální média, přeskakuji", - "home_page_archive_err_partner": "Položky partnera nelze archivovat, přeskakuji", - "home_page_building_timeline": "Vytváření časové osy", - "home_page_delete_err_partner": "Položky partnera nelze smazat, přeskakuji", - "home_page_delete_remote_err_local": "Místní položky ve vzdáleném výběru pro smazání, přeskakuji", - "home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuji", - "home_page_favorite_err_partner": "Položky partnera nelze označit jako oblíbené, přeskakuji", - "home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných alb", - "home_page_locked_error_local": "Místní položky nelze přesunout do uzamčené složky, přeskočí se", - "home_page_locked_error_partner": "Položky partnera nelze přesunout do uzamčené složky, přeskočí se", - "home_page_share_err_local": "Nelze sdílet místní položky prostřednictvím odkazu, přeskakuji", - "home_page_upload_err_limit": "Lze nahrát nejvýše 30 položek najednou, přeskakuji", - "host": "Hostitel", - "hour": "Hodina", - "hours": "Hodin", - "id": "ID", - "idle": "Nečinnost", - "ignore_icloud_photos": "Ignorovat fotografie na iCloudu", - "ignore_icloud_photos_description": "Fotografie uložené na iCloudu se nebudou nahrávat na Immich server", - "image": "Obrázek", - "image_alt_text_date": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživatelem {person1}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživateli {person1} a {person2}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživateli {person1}, {person2} a {person3}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video pořízeno} other {Obrázek pořízen}} {date} uživateli {person1}, {person2} a {additionalCount, plural, one {dalším # uživatelem} other {dalšími # uživateli}}", - "image_alt_text_date_place": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživatelem {person1}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživateli {person1} a {person2}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživateli {person1}, {person2} a {person3}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video pořízeno} other {Obrázek požízen}} {date} v místě {city}, {country} uživateli {person1}, {person2} a {additionalCount, plural, one {dalším # uživatelem} other {dalšími # uživateli}}", - "image_saved_successfully": "Obrázek uložen", - "image_viewer_page_state_provider_download_started": "Stahování zahájeno", - "image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné", - "image_viewer_page_state_provider_share_error": "Chyba sdílení", - "immich_logo": "Immich Logo", - "immich_web_interface": "Webové rozhraní Immich", - "import_from_json": "Import z JSONu", - "import_path": "Cesta importu", - "in_albums": "{count, plural, one {V # albu} few {Ve # albech} other {V # albech}}", - "in_archive": "V archivu", - "in_year": "V roce {year}", - "in_year_selector": "V roce", - "include_archived": "Včetně archivovaných", - "include_shared_albums": "Včetně sdílených alb", - "include_shared_partner_assets": "Včetně sdílených položek partnera", - "individual_share": "Sdílení jednotlivých položek", - "individual_shares": "Sdílení jednotlivých položek", - "info": "Informace", - "interval": { - "day_at_onepm": "Každý den ve 13:00", - "hours": "{hours, plural, one {Každou hodinu} few {Každé {hours, number} hodiny} other {Každých {hours, number} hodin}}", - "night_at_midnight": "Každý den o půlnoci", - "night_at_twoam": "Každou noc ve 2:00" - }, - "invalid_date": "Chybné datum", - "invalid_date_format": "Chybný formát data", - "invite_people": "Pozvat lidi", - "invite_to_album": "Pozvat do alba", - "ios_debug_info_fetch_ran_at": "Data načtena {dateTime}", - "ios_debug_info_last_sync_at": "Poslední synchronizace {dateTime}", - "ios_debug_info_no_processes_queued": "Žádné procesy na pozadí ve frontě", - "ios_debug_info_no_sync_yet": "Dosud nebyla spuštěna žádná úloha synchronizace na pozadí", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proces na pozadí ve frontě} few {{count} procesy na pozadí ve frontě} other {{count} procesů na pozadí ve frontě}}", - "ios_debug_info_processing_ran_at": "Zpracování spuštěno {dateTime}", - "items_count": "{count, plural, one {# položka} few {# položky} other {# položek}}", - "jobs": "Úlohy", - "json_editor": "JSON editor", - "json_error": "Chyba JSON", - "keep": "Ponechat", - "keep_albums": "Ponechat alba", - "keep_albums_count": "Ponechání {count} {count, plural, one {alba} other {alb}}", - "keep_all": "Ponechat vše", - "keep_description": "Vyberte co po uvolnění místa zůstane na vašem zařízení.", - "keep_favorites": "Zachovat oblíbené", - "keep_on_device": "Ponechat na zařízení", - "keep_on_device_hint": "Vyberte položky které chcete zachovat na tomto zařízení", - "keep_this_delete_others": "Ponechat tuto, odstranit ostatní", - "keeping": "Ponechat: {items}", - "kept_this_deleted_others": "Ponechána tato položka a {count, plural, one {odstraněna # položka} few {odstraněny # položky} other {odstraněno # položek}}", - "keyboard_shortcuts": "Klávesové zkratky", - "language": "Jazyk", - "language_no_results_subtitle": "Zkuste upravit hledaný výraz", - "language_no_results_title": "Nebyly nalezeny žádné jazyky", - "language_search_hint": "Vyhledat jazyk...", - "language_setting_description": "Vyberte upřednostňovaný jazyk", - "large_files": "Velké soubory", - "last": "Poslední", - "last_months": "{count, plural, one {Poslední měsíc} few {Poslední # měsíce} other {Posledních # měsíců}}", - "last_seen": "Naposledy viděno", - "latest_version": "Nejnovější verze", - "latitude": "Zeměpisná šířka", - "leave": "Opustit", - "leave_album": "Opustit album", - "lens_model": "Model objektivu", - "let_others_respond": "Nechte ostatní reagovat", - "level": "Úroveň", - "library": "Knihovna", - "library_add_folder": "Přidat složku", - "library_edit_folder": "Upravit složku", - "library_options": "Možnosti knihovny", - "library_page_device_albums": "Alba v zařízení", - "library_page_new_album": "Nové album", - "library_page_sort_asset_count": "Počet položek", - "library_page_sort_created": "Naposledy vytvořené", - "library_page_sort_last_modified": "Naposledy upraveno", - "library_page_sort_title": "Podle názvu alba", - "licenses": "Licence", - "light": "Světlý", - "like": "Líbí se mi", - "like_deleted": "Oblíbení smazáno", - "link_motion_video": "Připojit pohyblivé video", - "link_to_oauth": "Propojit s OAuth", - "linked_oauth_account": "Propojený OAuth účet", - "list": "Seznam", - "loading": "Načítání", - "loading_search_results_failed": "Načítání výsledků vyhledávání se nezdařilo", - "local": "Místní", - "local_asset_cast_failed": "Nelze odeslat položku, která není nahraná na serveru", - "local_assets": "Místní položky", - "local_id": "Místní ID", - "local_media_summary": "Souhrn místních médií", - "local_network": "Místní síť", - "local_network_sheet_info": "Aplikace se při použití zadané sítě Wi-Fi připojí k serveru prostřednictvím tohoto URL", - "location": "Poloha", - "location_permission": "Oprávnění polohy", - "location_permission_content": "Aby bylo možné používat funkci automatického přepínání, potřebuje Immich oprávnění k přesné poloze, aby mohl přečíst název aktuální sítě Wi-Fi", - "location_picker_choose_on_map": "Vybrat na mapě", - "location_picker_latitude_error": "Zadejte platnou zeměpisnou šířku", - "location_picker_latitude_hint": "Zadejte vlastní zeměpisnou šířku", - "location_picker_longitude_error": "Zadejte platnou zeměpisnou délku", - "location_picker_longitude_hint": "Zadejte vlastní zeměpisnou délku", - "lock": "Zamknout", - "locked_folder": "Uzamčená složka", - "log_detail_title": "Podrobnosti protokolu", - "log_out": "Odhlásit", - "log_out_all_devices": "Odhlásit všechna zařízení", - "logged_in_as": "Přihlášen jako {user}", - "logged_out_all_devices": "Všechna zařízení odhlášena", - "logged_out_device": "Zařízení odhlášeno", - "login": "Přihlášení", - "login_disabled": "Přihlášení bylo zakázáno", - "login_form_api_exception": "Výjimka API. Zkontrolujte URL serveru a zkuste to znovu.", - "login_form_back_button_text": "Zpět", - "login_form_email_hint": "tvůje-mail@email.com", - "login_form_endpoint_hint": "http://ip-tvého-serveru:port", - "login_form_endpoint_url": "URL adresa serveru", - "login_form_err_http": "Prosím, uveďte http:// nebo https://", - "login_form_err_invalid_email": "Neplatný e-mail", - "login_form_err_invalid_url": "Neplatná URL", - "login_form_err_leading_whitespace": "Úvodní mezera", - "login_form_err_trailing_whitespace": "Koncová mezera", - "login_form_failed_get_oauth_server_config": "Chyba přihlášení pomocí OAuth, zkontrolujte adresu URL serveru", - "login_form_failed_get_oauth_server_disable": "Funkce OAuth není na tomto serveru dostupná", - "login_form_failed_login": "Chyba přihlášení, zkontrolujte URL adresu serveru, e-mail a heslo", - "login_form_handshake_exception": "Došlo k výjimce Handshake se serverem. Pokud používáte self-signed certifikát, povolte v nastavení podporu self-signed certifikátu.", - "login_form_password_hint": "heslo", - "login_form_save_login": "Zůstat přihlášen", - "login_form_server_empty": "Zadejte URL serveru.", - "login_form_server_error": "Není možné se připojit k serveru.", - "login_has_been_disabled": "Přihlášení bylo zakázáno.", - "login_password_changed_error": "Při aktualizaci vašeho hesla došlo k chybě", - "login_password_changed_success": "Heslo bylo úspěšně aktualizováno", - "logout_all_device_confirmation": "Opravdu chcete odhlásit všechna zařízení?", - "logout_this_device_confirmation": "Opravdu chcete odhlásit toto zařízení?", - "logs": "Protokoly", - "longitude": "Zeměpisná délka", - "look": "Zobrazení", - "loop_videos": "Videa ve smyčce", - "loop_videos_description": "Povolit automatickou smyčku videa v prohlížeči.", - "main_branch_warning": "Používáte vývojovou verzi; důrazně doporučujeme používat verzi z vydání!", - "main_menu": "Hlavní nabídka", - "maintenance_action_restore": "Obnovení databáze", - "maintenance_description": "Immich byl přepnut do režimu údržby.", - "maintenance_end": "Ukončit režim údržby", - "maintenance_end_error": "Nepodařilo se ukončit režim údržby.", - "maintenance_logged_in_as": "Aktuálně přihlášen jako {user}", - "maintenance_restore_from_backup": "Obnovit ze zálohy", - "maintenance_restore_library": "Obnovte svou knihovnu", - "maintenance_restore_library_confirm": "Pokud vše vypadá správně, pokračujte v obnovení zálohy!", - "maintenance_restore_library_description": "Obnovení databáze", - "maintenance_restore_library_folder_has_files": "{folder} obsahuje {count} složek", - "maintenance_restore_library_folder_no_files": "V složce {folder} chybí soubory!", - "maintenance_restore_library_folder_pass": "čitelné a zapisovatelné", - "maintenance_restore_library_folder_read_fail": "nečitelné", - "maintenance_restore_library_folder_write_fail": "nezapisovatelné", - "maintenance_restore_library_hint_missing_files": "Mohou vám chybět důležité soubory", - "maintenance_restore_library_hint_regenerate_later": "Tyto můžete později obnovit v nastavení", - "maintenance_restore_library_hint_storage_template_missing_files": "Používáte šablonu úložiště? Mohou vám chybět soubory", - "maintenance_restore_library_loading": "Načítání kontrol integrity a heuristiky…", - "maintenance_task_backup": "Vytváření zálohy existující databáze…", - "maintenance_task_migrations": "Probíhá migrace databáze…", - "maintenance_task_restore": "Obnovení vybrané zálohy…", - "maintenance_task_rollback": "Obnova se nezdařila, návrat k bodu obnovení…", - "maintenance_title": "Dočasně nedostupné", - "make": "Výrobce", - "manage_geolocation": "Spravovat polohu", - "manage_media_access_rationale": "Toto oprávnění je vyžadováno pro správné zacházení s přesunem položek do koše a jejich obnovováním z něj.", - "manage_media_access_settings": "Otevřít nastavení", - "manage_media_access_subtitle": "Povolte aplikaci Immich spravovat a přesouvat soubory médií.", - "manage_media_access_title": "Přístup ke správě médií", - "manage_shared_links": "Spravovat sdílené odkazy", - "manage_sharing_with_partners": "Správa sdílení s partnery", - "manage_the_app_settings": "Správa nastavení aplikace", - "manage_your_account": "Správa účtu", - "manage_your_api_keys": "Správa API klíčů", - "manage_your_devices": "Správa přihlášených zařízení", - "manage_your_oauth_connection": "Správa OAuth propojení", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {Žádná fotka v této oblasti} one {# fotka} few{# fotky} other {# fotek}}", - "map_cannot_get_user_location": "Nelze zjistit polohu uživatele", - "map_location_dialog_yes": "Ano", - "map_location_picker_page_use_location": "Použít tuto polohu", - "map_location_service_disabled_content": "Pro zobrazení fotek z vaší aktuální polohy musí být povolena služba určování polohy. Chcete ji nyní povolit?", - "map_location_service_disabled_title": "Služba určování polohy je zakázána", - "map_marker_for_images": "Značka na mapě pro snímky pořízené v {city}, {country}", - "map_marker_with_image": "Značka mapy s obrázkem", - "map_no_location_permission_content": "Oprávnění polohy je nutné pro zobrazení fotek z vaší aktuální polohy. Chcete oprávnění nyní povolit?", - "map_no_location_permission_title": "Oprávnění polohy zamítnuto", - "map_settings": "Nastavení mapy", - "map_settings_dark_mode": "Tmavý režim", - "map_settings_date_range_option_day": "Posledních 24 hodin", - "map_settings_date_range_option_days": "Posledních {days} dní", - "map_settings_date_range_option_year": "Poslední rok", - "map_settings_date_range_option_years": "Poslední {years} roky", - "map_settings_dialog_title": "Nastavení map", - "map_settings_include_show_archived": "Zahrnout archivované", - "map_settings_include_show_partners": "Včetně partnerů", - "map_settings_only_show_favorites": "Zobrazit pouze oblíbené", - "map_settings_theme_settings": "Motiv mapy", - "map_zoom_to_see_photos": "Oddálit pro zobrazení fotografií", - "mark_all_as_read": "Označit vše jako přečtené", - "mark_as_read": "Označit jako přečtené", - "marked_all_as_read": "Vše označeno jako přečtené", - "matches": "Shody", - "matching_assets": "Odpovídající položky", - "media_type": "Typ média", - "memories": "Vzpomínky", - "memories_all_caught_up": "To je všechno", - "memories_check_back_tomorrow": "Zítra se podívejte na další vzpomínky", - "memories_setting_description": "Správa toho, co vidíte ve svých vzpomínkách", - "memories_start_over": "Začít znovu", - "memories_swipe_to_close": "Přejetím nahoru zavřete", - "memory": "Vzpomínka", - "memory_lane_title": "Řada vzpomínek {title}", - "menu": "Nabídka", - "merge": "Sloučit", - "merge_people": "Sloučit osoby", - "merge_people_limit": "Najednou můžete sloučit pouze 5 obličejů", - "merge_people_prompt": "Chcete tyto lidi sloučit? Tato akce je nevratná.", - "merge_people_successfully": "Sloučení osob proběhlo úspěšně", - "merged_people_count": "{count, plural, one {Sloučena # osoba} few {Sloučeny # osoby} other {Sloučeno # lidí}}", - "minimize": "Minimalizovat", - "minute": "Minuta", - "minutes": "Minut", - "mirror_horizontal": "Vodorovně", - "mirror_vertical": "Svisle", - "missing": "Chybějící", - "mobile_app": "Mobilní aplikace", - "mobile_app_download_onboarding_note": "Stáhněte si doprovodnou mobilní aplikaci pomocí následujících možností", - "model": "Model", - "month": "Měsíc", - "monthly_title_text_date_format": "LLLL y", - "more": "Více", - "move": "Přesunout", - "move_down": "Přesunout dolů", - "move_off_locked_folder": "Přesunout z uzamčené složky", - "move_to": "Přesunout do", - "move_to_device_trash": "Přesunout do koše zařízení", - "move_to_lock_folder_action_prompt": "{count} přidaných do uzamčené složky", - "move_to_locked_folder": "Přesunout do uzamčené složky", - "move_to_locked_folder_confirmation": "Tyto fotky a videa budou odstraněny ze všech alb a bude je možné zobrazit pouze v uzamčené složce", - "move_up": "Přesunout nahoru", - "moved_to_archive": "{count, plural, one {# položka přesunuta} few {# položky přesunuty} other {# položek přesunuto}} do archivu", - "moved_to_library": "{count, plural, one {# položka přesunuta} few {# položky přesunuty} other {# položek přesunuto}} do knihovny", - "moved_to_trash": "Přesunuto do koše", - "multiselect_grid_edit_date_time_err_read_only": "Nelze upravit datum položek pouze pro čtení, přeskakuji", - "multiselect_grid_edit_gps_err_read_only": "Nelze upravit polohu položek pouze pro čtení, přeskakuji", - "mute_memories": "Ztlumit vzpomínky", - "my_albums": "Moje alba", - "name": "Jméno", - "name_or_nickname": "Jméno nebo přezdívka", - "name_required": "Jméno je povinné", - "navigate": "Navigovat", - "navigate_to_time": "Navigovat na čas", - "network_requirement_photos_upload": "Pro zálohování fotografií používat mobilní data", - "network_requirement_videos_upload": "Pro zálohování videí používat mobilní data", - "network_requirements": "Požadavky na síť", - "network_requirements_updated": "Požadavky na síť se změnily, fronta zálohování se vytvoří znovu", - "networking_settings": "Síť", - "networking_subtitle": "Správa nastavení koncového bodu serveru", - "never": "Nikdy", - "new_album": "Nové album", - "new_api_key": "Nový API klíč", - "new_date_range": "Nový rozsah dat", - "new_password": "Nové heslo", - "new_person": "Nová osoba", - "new_pin_code": "Nový PIN kód", - "new_pin_code_subtitle": "Poprvé přistupujete k uzamčené složce. Vytvořte si kód PIN pro bezpečný přístup na tuto stránku", - "new_timeline": "Nová časová osa", - "new_update": "Nová aktualizace", - "new_user_created": "Vytvořen nový uživatel", - "new_version_available": "NOVÁ VERZE K DISPOZICI", - "newest_first": "Nejnovější první", - "next": "Další", - "next_memory": "Další vzpomínka", - "no": "Ne", - "no_actions_added": "Zatím nebyly přidány žádné akce", - "no_albums_found": "Žádná alba nenalezena", - "no_albums_message": "Vytvořte si album pro uspořádání fotografií a videí", - "no_albums_with_name_yet": "Vypadá to, že zatím nemáte žádná alba s tímto názvem.", - "no_albums_yet": "Vypadá to, že ještě nemáte žádná alba.", - "no_archived_assets_message": "Archivujte fotografie a videa a skryjte je ze zobrazení v sekci Fotky", - "no_assets_message": "Klikněte pro nahrání první fotografie", - "no_assets_to_show": "Žádné položky k zobrazení", - "no_cast_devices_found": "Nebyla nalezena žádná zařízení", - "no_checksum_local": "Není k dispozici kontrolní součet - nelze načíst místní položky", - "no_checksum_remote": "Není k dispozici kontrolní součet - nelze načíst vzdálenou položku", - "no_configuration_needed": "Není nutná žádná konfigurace", - "no_devices": "Žádná autorizovaná zařízení", - "no_duplicates_found": "Nebyly nalezeny žádné duplicity.", - "no_exif_info_available": "Exif není k dispozici", - "no_explore_results_message": "Nahrajte další fotografie a prozkoumejte svou sbírku.", - "no_favorites_message": "Přidejte si oblíbené položky a rychle najděte své nejlepší obrázky a videa", - "no_filters_added": "Zatím nebyly přidány žádné filtry", - "no_libraries_message": "Vytvořte si externí knihovnu pro zobrazení fotografií a videí", - "no_local_assets_found": "Nebyly nalezeny žádné místní položky s tímto kontrolním součtem", - "no_location_set": "Není nastavena poloha", - "no_locked_photos_message": "Fotky a videa v uzamčené složce jsou skryté a při procházení nebo vyhledávání v knihovně se nezobrazují.", - "no_name": "Bez jména", - "no_notifications": "Žádná oznámení", - "no_people_found": "Nebyli nalezeni žádní odpovídající lidé", - "no_places": "Žádná místa", - "no_remote_assets_found": "Nebyly nalezeny žádné vzdálené položky s tímto kontrolním součtem", - "no_results": "Žádné výsledky", - "no_results_description": "Zkuste použít synonymum nebo obecnější klíčové slovo", - "no_shared_albums_message": "Vytvořte si album a sdílejte fotografie a videa s lidmi ve své síti", - "no_uploads_in_progress": "Neprobíhá žádné nahrávání", - "none": "Žádné", - "not_allowed": "Nepovoleno", - "not_available": "Není k dispozici", - "not_in_any_album": "Bez alba", - "not_selected": "Není vybráno", - "note_apply_storage_label_to_previously_uploaded assets": "Upozornění: Chcete-li použít štítek úložiště na dříve nahrané položky, spusťte příkaz", - "notes": "Poznámky", - "nothing_here_yet": "Zatím zde nic není", - "notification_permission_dialog_content": "Chcete-li povolit oznámení, přejděte do nastavení a vyberte možnost povolit.", - "notification_permission_list_tile_content": "Udělte oprávnění k aktivaci oznámení.", - "notification_permission_list_tile_enable_button": "Povolit oznámení", - "notification_permission_list_tile_title": "Povolení oznámení", - "notification_toggle_setting_description": "Povolení e-mailových oznámení", - "notifications": "Oznámení", - "notifications_setting_description": "Správa oznámení", - "oauth": "OAuth", - "obtainium_configurator": "Konfigurátor Obtainium", - "obtainium_configurator_instructions": "Pomocí aplikace Obtainium nainstalujte a aktualizujte aplikaci pro Android přímo z vydání na GitHubu Immich. Vytvořte API klíč a vyberte variantu pro vytvoření konfiguračního odkazu pro Obtainium", - "ocr": "OCR", - "official_immich_resources": "Oficiální zdroje Immich", - "offline": "Offline", - "offset": "Posun", - "ok": "Ok", - "oldest_first": "Nejstarší první", - "on_this_device": "V tomto zařízení", - "onboarding": "Zahájení", - "onboarding_locale_description": "Vyberte preferovaný jazyk. Tento výběr můžete později změnit v nastavení.", - "onboarding_privacy_description": "Následující (volitelné) funkce jsou závislé na externích službách a lze je kdykoli zakázat v nastavení.", - "onboarding_server_welcome_description": "Pojďme nastavit vaši instanci pomocí několika běžných nastavení.", - "onboarding_theme_description": "Zvolte si barevný motiv pro svou instanci. Můžete to později změnit v nastavení.", - "onboarding_user_welcome_description": "Pojďme na to!", - "onboarding_welcome_user": "Vítej, {user}", - "online": "Online", - "only_favorites": "Pouze oblíbené", - "open": "Otevřít", - "open_in_map_view": "Otevřít v zobrazení mapy", - "open_in_openstreetmap": "Otevřít v OpenStreetMap", - "open_the_search_filters": "Otevřít vyhledávací filtry", - "options": "Možnosti", - "or": "nebo", - "organize_into_albums": "Organizovat do alb", - "organize_into_albums_description": "Umístit existující fotky do alb s použitím aktuálního nastavení synchronizace", - "organize_your_library": "Uspořádejte si knihovnu", - "original": "originál", - "other": "Ostatní", - "other_devices": "Ostatní zařízení", - "other_entities": "Ostatní entity", - "other_variables": "Další proměnné", - "owned": "Vlastní", - "owner": "Vlastník", - "page": "Stránka", - "partner": "Partner", - "partner_can_access": "{partner} má přístup", - "partner_can_access_assets": "Všechny vaše fotky a videa kromě těch, které jsou v sekcích Archivováno a Smazáno", - "partner_can_access_location": "Místo, kde byly vaše fotografie pořízeny", - "partner_list_user_photos": "Fotografie uživatele {user}", - "partner_list_view_all": "Zobrazit všechny", - "partner_page_empty_message": "Vaše fotografie zatím nejsou sdíleny s žádným partnerem.", - "partner_page_no_more_users": "Žádní další uživatelé k přidání", - "partner_page_partner_add_failed": "Nepodařilo se přidat partnera", - "partner_page_select_partner": "Vyberte partnera", - "partner_page_shared_to_title": "Sdíleno", - "partner_page_stop_sharing_content": "{partner} již nebude mít přístup k vašim fotografiím.", - "partner_sharing": "Sdílení mezi partnery", - "partners": "Partneři", - "password": "Heslo", - "password_does_not_match": "Heslo se neshoduje", - "password_required": "Vyžadováno heslo", - "password_reset_success": "Úspěšné obnovení hesla", - "past_durations": { - "days": "{days, plural, one {Včera} few {Poslední # dny} other {Posledních # dní}}", - "hours": "{hours, plural, one {Poslední hodinu} few {Poslední # hodiny} other {Posledních # hodin}}", - "years": "{years, plural, one {Poslední rok} few {Poslední # roky} other {Posledních # let}}" - }, - "path": "Cesta", - "pattern": "Vzor", - "pause": "Pozastavit", - "pause_memories": "Pozastavit vzpomínky", - "paused": "Pozastaveno", - "pending": "Čekající", - "people": "Lidé", - "people_edits_count": "Upraveno {count, plural, one {# osoba} few {# osoby} other {# lidí}}", - "people_feature_description": "Procházení fotografií a videí seskupených podle osob", - "people_selected": "{count, plural, one {# osoba vybrána} few {# osob vybráno} other {# lidí vybráno}}", - "people_sidebar_description": "Zobrazit sekci Lidé v postranním panelu", - "permanent_deletion_warning": "Upozornění na trvalé smazání", - "permanent_deletion_warning_setting_description": "Zobrazit varování při trvalém odstranění položek", - "permanently_delete": "Trvale odstranit", - "permanently_delete_assets_count": "Trvale smazat {count, plural, one {položku} other {položky}}", - "permanently_delete_assets_prompt": "Opravdu chcete trvale smazat {count, plural, one {tento soubor?} other {tyto # soubory?}} Tím se také odstraní {count, plural, one {z jeho} other {z jejich}} alba.", - "permanently_deleted_asset": "Položka trvale odstraněna", - "permanently_deleted_assets_count": "{count, plural, one {Položka trvale vymazána} other {Položky trvale vymazány}}", - "permission": "Oprávnění", - "permission_empty": "Vaše oprávnění by nemělo být prázdné", - "permission_onboarding_back": "Zpět", - "permission_onboarding_continue_anyway": "Přesto pokračovat", - "permission_onboarding_get_started": "Začít", - "permission_onboarding_go_to_settings": "Přejít do nastavení", - "permission_onboarding_permission_denied": "Přístup odepřen. Pro používání Immich je nutné povolit přístup k fotkám a videím v nastavení.", - "permission_onboarding_permission_granted": "Přístup povolen! Vše je připraveno.", - "permission_onboarding_permission_limited": "Přístup omezen. Chcete-li používat Immich k zálohování a správě celé vaší kolekce galerií, povolte v nastavení přístup k fotkám a videím.", - "permission_onboarding_request": "Immich potřebuje přístup k zobrazení vašich fotek a videí.", - "person": "Osoba", - "person_age_months": "{months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}", - "person_age_year_months": "1 rok a {months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}", - "person_age_years": "{years, plural, one {# rok} few {# roky} other {# let}}", - "person_birthdate": "Narozen(a) {date}", - "person_hidden": "{name}{hidden, select, true { (skryto)} other {}}", - "person_recognized": "Osoba rozpoznána", - "person_selected": "Osoba vybrána", - "photo_shared_all_users": "Vypadá to, že jste fotky sdíleli se všemi uživateli, nebo nemáte žádného uživatele, se kterým byste je mohli sdílet.", - "photos": "Fotky", - "photos_and_videos": "Fotky a videa", - "photos_count": "{count, plural, one {{count, number} fotka} few {{count, number} fotky} other {{count, number} fotek}}", - "photos_from_previous_years": "Fotky z předchozích let", - "photos_only": "Pouze fotografie", - "pick_a_location": "Vyberte polohu", - "pick_custom_range": "Vlastní rozsah", - "pick_date_range": "Vyberte rozsah dat", - "pin_code_changed_successfully": "PIN kód byl úspěšně změněn", - "pin_code_reset_successfully": "PIN kód úspěšně resetován", - "pin_code_setup_successfully": "PIN kód úspěšně nastaven", - "pin_verification": "Ověření PIN kódu", - "place": "Místo", - "places": "Místa", - "places_count": "{count, plural, one {{count, number} místo} few {{count, number} místa} other {{count, number} míst}}", - "play": "Přehrávat", - "play_memories": "Přehrát vzpomníky", - "play_motion_photo": "Přehrát pohybovou fotografii", - "play_or_pause_video": "Přehrát nebo pozastavit video", - "play_original_video": "Přehrát původní video", - "play_original_video_setting_description": "Upřednostňujte přehrávání originálních videí před překódovanými videi. Pokud originální soubor není kompatibilní, nemusí se přehrávat správně.", - "play_transcoded_video": "Přehrát překódované video", - "please_auth_to_access": "Pro přístup se prosím ověřte", - "port": "Port", - "preferences_settings_subtitle": "Správa předvoleb aplikace", - "preferences_settings_title": "Předvolby", - "preparing": "Příprava", - "preset": "Přednastavení", - "preview": "Náhled", - "previous": "Předchozí", - "previous_memory": "Předchozí vzpomínka", - "previous_or_next_day": "Následující/předchozí den", - "previous_or_next_month": "Následující/předchozí měsíc", - "previous_or_next_photo": "Následující/předchozí fotografie", - "previous_or_next_year": "Následující/předchozí rok", - "primary": "Primární", - "privacy": "Soukromí", - "profile": "Profil", - "profile_drawer_app_logs": "Logy", - "profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Režim jen pro čtení. Ukončíte ho dlouhým podržením ikony avataru.", - "profile_image_of_user": "Profilový obrázek uživatele {user}", - "profile_picture_set": "Profilový obrázek nastaven.", - "public_album": "Veřejné album", - "public_share": "Veřejné sdílení", - "purchase_account_info": "Podporovatel", - "purchase_activated_subtitle": "Děkujeme vám za podporu aplikace Immich a softwaru s otevřeným zdrojovým kódem", - "purchase_activated_time": "Aktivováno dne {date}", - "purchase_activated_title": "Váš klíč byl úspěšně aktivován", - "purchase_button_activate": "Aktivovat", - "purchase_button_buy": "Koupit", - "purchase_button_buy_immich": "Koupit Immich", - "purchase_button_never_show_again": "Nikdy již nezobrazovat", - "purchase_button_reminder": "Připomenout za 30 dní", - "purchase_button_remove_key": "Odstranit klíč", - "purchase_button_select": "Vybrat", - "purchase_failed_activation": "Aktivace se nezdařila! Zkontrolujte prosím svůj e-mail zda je zadaný produktový klíč bez chyb!", - "purchase_individual_description_1": "Pro jednotlivce", - "purchase_individual_description_2": "Stav podporovatele", - "purchase_individual_title": "Individuální", - "purchase_input_suggestion": "Máte produktový klíč? Zadejte ho níže", - "purchase_license_subtitle": "Koupit Immich a podpořit další rozvoj služby", - "purchase_lifetime_description": "Doživotní platnost", - "purchase_option_title": "MOŽNOSTI ZAKOUPENÍ", - "purchase_panel_info_1": "Tvorba aplikace Immich vyžaduje spoustu času a úsilí, a proto na ní pracují vývojáři na plný úvazek, aby byla co nejlepší. Naším cílem je, aby se software s otevřeným zdrojovým kódem a etické obchodní postupy staly udržitelným zdrojem příjmů pro vývojáře a aby vznikl ekosystém respektující soukromí se skutečnými alternativami k ziskuchtivým službám.", - "purchase_panel_info_2": "Protože jsme se zavázali, že nebudeme zavádět paywally, nezískáte tímto nákupem žádné další funkce v aplikaci Immich. Spoléháme na uživatele, jako jste vy, že podpoří neustálý vývoj aplikace.", - "purchase_panel_title": "Podpořit projekt", - "purchase_per_server": "Za server", - "purchase_per_user": "Za uživatele", - "purchase_remove_product_key": "Odstranění produktového klíče", - "purchase_remove_product_key_prompt": "Opravdu chcete odebrat produktový klíč?", - "purchase_remove_server_product_key": "Odstranění serverového produktového klíče", - "purchase_remove_server_product_key_prompt": "Opravdu chcete odebrat serverový produktový klíč?", - "purchase_server_description_1": "Pro celý server", - "purchase_server_description_2": "Stav podporovatele", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Produktový klíč serveru spravuje správce", - "query_asset_id": "ID položky dotazu", - "queue_status": "Ve frontě {count}/{total}", - "rate_asset": "Hodnotit položku", - "rating": "Hodnocení hvězdičkami", - "rating_clear": "Vyčistit hodnocení", - "rating_count": "{count, plural, one {# hvězdička} few {# hvězdičky} other {# hvězdček}}", - "rating_description": "Zobrazit EXIF hodnocení v informačním panelu", - "rating_set": "Hodnocení nastaveno na {rating, plural, one {# hvězdičku} few {# hvězdičky} other {# hvězdiček}}", - "reaction_options": "Možnosti reakce", - "read_changelog": "Přečtěte si seznam změn", - "readonly_mode_disabled": "Režim pouze pro čtení je deaktivován", - "readonly_mode_enabled": "Režim pouze pro čtení povolen", - "ready_for_upload": "Připraveno k nahrání", - "reassign": "Přeřadit", - "reassigned_assets_to_existing_person": "Přeřadit {count, plural, one {# položku} few {# položky} other {# položek}} na {name, select, null {existující osobu} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {Přeřazena # položka} few {Přeřazeny # položky} other {Přeřazeno # položek}} na novou osobu", - "reassing_hint": "Přiřazení vybraných položek existující osobě", - "recent": "Nedávné", - "recent-albums": "Nedávná alba", - "recent_searches": "Nedávná vyhledávání", - "recently_added": "Nedávno přidané", - "recently_added_page_title": "Nedávno přidané", - "recently_taken": "Nedávno pořízené", - "recently_taken_page_title": "Nedávno pořízené", - "refresh": "Obnovit", - "refresh_encoded_videos": "Obnovit kódovaná videa", - "refresh_faces": "Obnovit obličeje", - "refresh_metadata": "Obnovit metadata", - "refresh_thumbnails": "Obnovit miniatury", - "refreshed": "Obnoveno", - "refreshes_every_file": "Znovu načte všechny stávající a nové soubory", - "refreshing_encoded_video": "Obnovování kódovaného videa", - "refreshing_faces": "Obnovování obličejů", - "refreshing_metadata": "Obnovování metadat", - "regenerating_thumbnails": "Regenerace miniatur", - "remote": "Vzdálený", - "remote_assets": "Vzdálené položky", - "remote_media_summary": "Souhrn vzdálených médií", - "remove": "Odstranit", - "remove_assets_album_confirmation": "Opravdu chcete z alba odstranit {count, plural, one {# položku} few {# položky} other {# položek}}?", - "remove_assets_shared_link_confirmation": "Opravdu chcete ze sdíleného odkazu odstranit {count, plural, one {# položku} few {# položky} other {# položek}}?", - "remove_assets_title": "Odstranit položky?", - "remove_custom_date_range": "Odstranit vlastní rozsah dat", - "remove_deleted_assets": "Odstranit offline soubory", - "remove_from_album": "Odstranit z alba", - "remove_from_album_action_prompt": "{count} odstraněných z alba", - "remove_from_favorites": "Odstranit z oblíbených", - "remove_from_lock_folder_action_prompt": "{count} odebraných z uzamčené složky", - "remove_from_locked_folder": "Odstranit z uzamčené složky", - "remove_from_locked_folder_confirmation": "Opravdu chcete tyto fotky a videa přesunout z uzamčené složky? Budou viditelné ve vaší knihovně.", - "remove_from_shared_link": "Odstranit ze sdíleného odkazu", - "remove_memory": "Odstranit vzpomínku", - "remove_photo_from_memory": "Odstranit fotografii z této vzpomínky", - "remove_tag": "Odstranit značku", - "remove_url": "Odstranit URL", - "remove_user": "Odebrat uživatele", - "removed_api_key": "Odstraněn API klíč: {name}", - "removed_from_archive": "Odstraněno z archivu", - "removed_from_favorites": "Odstraněno z oblíbených", - "removed_from_favorites_count": "{count, plural, one {Odstraněn #} few {Odstraněny #} other {Odstraněno #}} z oblíbených", - "removed_memory": "Vzpomínka odstraněna", - "removed_photo_from_memory": "Fotografie odstraněna ze vzpomínky", - "removed_tagged_assets": "Odstraněná značka z {count, plural, one {# položky} other {# položek}}", - "rename": "Přejmenovat", - "repair": "Opravy", - "repair_no_results_message": "Zde se zobrazí neznámé a chybějící soubory", - "replace_with_upload": "Nahradit nahráním", - "repository": "Repozitář", - "require_password": "Požadovat heslo", - "require_user_to_change_password_on_first_login": "Požadovat, aby si uživatel při prvním přihlášení změnil heslo", - "rescan": "Znovu prohledat", - "reset": "Výchozí", - "reset_password": "Obnovit heslo", - "reset_people_visibility": "Obnovit viditelnost lidí", - "reset_pin_code": "Resetovat PIN kód", - "reset_pin_code_description": "Pokud jste zapomněli svůj PIN kód, obraťte se na správce serveru pro jeho resetování", - "reset_pin_code_success": "PIN kód úspěšně resetován", - "reset_pin_code_with_password": "Svůj PIN kód můžete vždy resetovat pomocí hesla", - "reset_sqlite": "Obnovit databázi SQLite", - "reset_sqlite_confirmation": "Jste si jisti, že chcete obnovit databázi SQLite? Pro opětovnou synchronizaci dat se budete muset odhlásit a znovu přihlásit", - "reset_sqlite_success": "Obnovení SQLite databáze proběhlo úspěšně", - "reset_to_default": "Obnovit výchozí nastavení", - "resolution": "Rozlišení", - "resolve_duplicates": "Vyřešit duplicity", - "resolved_all_duplicates": "Vyřešeny všechny duplicity", - "restore": "Obnovit", - "restore_all": "Obnovit vše", - "restore_trash_action_prompt": "{count} obnoveno z koše", - "restore_user": "Obnovit uživatele", - "restored_asset": "Položka obnovena", - "resume": "Pokračovat", - "resume_paused_jobs": "Pokračovat {count, plural, one {v # pozastavené úloze} few {ve # pozastavených úlohách} other {v # pozastavených úlohách}}", - "retry_upload": "Opakování nahrávání", - "review_duplicates": "Kontrola duplicit", - "review_large_files": "Kontrola velkých souborů", - "role": "Role", - "role_editor": "Editor", - "role_viewer": "Divák", - "running": "Probíhá", - "save": "Uložit", - "save_to_gallery": "Uložit do galerie", - "saved": "Uloženo", - "saved_api_key": "API klíč uložen", - "saved_profile": "Profil uložen", - "saved_settings": "Nastavení uloženo", - "say_something": "Napište něco", - "scaffold_body_error_occurred": "Došlo k chybě", - "scan": "Prohledat", - "scan_all_libraries": "Prohledat všechny knihovny", - "scan_library": "Prohledat", - "scan_settings": "Nastavení prohledávání", - "scanning": "Prohládává se", - "scanning_for_album": "Prohledávání alba...", - "search": "Hledat", - "search_albums": "Vyhledávejte alba", - "search_by_context": "Vyhledávání podle obsahu", - "search_by_description": "Vyhledávat podle popisu", - "search_by_description_example": "Pěší turistika v Sapě", - "search_by_filename": "Vyhledávání podle názvu nebo přípony souboru", - "search_by_filename_example": "např. IMG_1234.JPG nebo PNG", - "search_by_ocr": "Hledat pomocí OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Vyhledat model objektivu...", - "search_camera_make": "Vyhledat výrobce fotoaparátu...", - "search_camera_model": "Vyhledat model fotoaparátu...", - "search_city": "Vyhledat město...", - "search_country": "Vyhledat zemi...", - "search_filter_apply": "Použít filtr", - "search_filter_camera_title": "Výběr typu fotoaparátu", - "search_filter_date": "Datum", - "search_filter_date_interval": "{start} až {end}", - "search_filter_date_title": "Výběr rozsahu dat", - "search_filter_display_option_not_in_album": "Není v albu", - "search_filter_display_options": "Možnost zobrazení", - "search_filter_filename": "Vyhledávat podle názvu souboru", - "search_filter_location": "Poloha", - "search_filter_location_title": "Výběr polohy", - "search_filter_media_type": "Typ média", - "search_filter_media_type_title": "Výběr typu média", - "search_filter_ocr": "Hledat pomocí OCR", - "search_filter_people_title": "Výběr lidí", - "search_filter_star_rating": "Hodnocení hvězdičkami", - "search_for": "Vyhledat", - "search_for_existing_person": "Vyhledat existující osobu", - "search_no_more_result": "Žádné další výsledky", - "search_no_people": "Žádní lidé", - "search_no_people_named": "Žádní lidé se jménem \"{name}\"", - "search_no_result": "Nebyly nalezeny žádné výsledky, zkuste zadat jiný hledaný výraz nebo kombinaci", - "search_options": "Možnosti vyhledávání", - "search_page_categories": "Kategorie", - "search_page_motion_photos": "Pohyblivé fotky", - "search_page_no_objects": "Informace o objektech nejsou k dispozici", - "search_page_no_places": "Informace o místě nejsou k dispozici", - "search_page_screenshots": "Snímky obrazovky", - "search_page_search_photos_videos": "Vyhledávejte svoje fotky a videa", - "search_page_selfies": "Autoportréty", - "search_page_things": "Věci", - "search_page_view_all_button": "Zobrazit vše", - "search_page_your_activity": "Vaše aktivita", - "search_page_your_map": "Vaše mapa", - "search_people": "Vyhledat lidi", - "search_places": "Vyhledat místa", - "search_rating": "Vyhledávání podle hodnocení...", - "search_result_page_new_search_hint": "Nové vyhledávání", - "search_settings": "Hledat nastavení", - "search_state": "Vyhledat stát...", - "search_suggestion_list_smart_search_hint_1": "Ve výchozím nastavení je chytré vyhledávání zapnuto, pro vyhledávání metadat použijte syntaxi ", - "search_suggestion_list_smart_search_hint_2": "m:vaše-vyhledávaná-fráze", - "search_tags": "Vyhledávat značky...", - "search_timezone": "Vyhledat časové pásmo...", - "search_type": "Typ vyhledávání", - "search_your_photos": "Vyhledávejte svoje fotky", - "searching_locales": "Vyhledávání jazyků...", - "second": "Sekunda", - "see_all_people": "Zobrazit všechny lidi", - "select": "Vybrat", - "select_album": "Vybrat album", - "select_album_cover": "Vybrat obal alba", - "select_albums": "Vybrat alba", - "select_all": "Vybrat vše", - "select_all_duplicates": "Vybrat všechny duplicity", - "select_all_in": "Vybrat vše ve skupině {group}", - "select_avatar_color": "Vyberte barvu avatara", - "select_count": "{count, plural, one {Vybrat #} other {Vybrat #}}", - "select_cutoff_date": "Vybrat mezní datum", - "select_face": "Vybrat obličej", - "select_featured_photo": "Vybrat hlavní fotografii", - "select_from_computer": "Vybrat z počítače", - "select_keep_all": "Vybrat ponechat vše", - "select_library_owner": "Vyberte vlastníka knihovny", - "select_new_face": "Výběr nového obličeje", - "select_people": "Vybrat lidi", - "select_person": "Vybrat osobu", - "select_person_to_tag": "Vyberte osobu, kterou chcete označit", - "select_photos": "Vybrat fotky", - "select_trash_all": "Vybrat vyhodit vše", - "select_user_for_sharing_page_err_album": "Nepodařilo se vytvořit album", - "selected": "Vybráno", - "selected_count": "{count, plural, one {# vybraný} few {# vybrané} other {# vybraných}}", - "selected_gps_coordinates": "Vybrané GPS souřadnice", - "send_message": "Odeslat zprávu", - "send_welcome_email": "Poslat uvítací e-mail", - "server_endpoint": "Koncový bod serveru", - "server_info_box_app_version": "Verze aplikace", - "server_info_box_server_url": "URL serveru", - "server_offline": "Server offline", - "server_online": "Server online", - "server_privacy": "Ochrana soukromí serveru", - "server_restarting_description": "Tato stránka se za chvíli obnoví.", - "server_restarting_title": "Server se restartuje", - "server_stats": "Statistiky serveru", - "server_update_available": "K dispozici je aktualizace serveru", - "server_version": "Verze serveru", - "set": "Nastavit", - "set_as_album_cover": "Nastavit jako obal alba", - "set_as_featured_photo": "Nastavit jako hlavní fotografii", - "set_as_profile_picture": "Nastavit jako profilový obrázek", - "set_date_of_birth": "Nastavit datum narození", - "set_profile_picture": "Nastavit profilový obrázek", - "set_slideshow_to_fullscreen": "Nastavit prezentaci na celou obrazovku", - "set_stack_primary_asset": "Nastavit jako hlavní položku", - "setting_image_viewer_help": "V prohlížeči detailů se nejprve načte malá miniatura, poté se načte náhled střední velikosti (je-li povolen) a nakonec se načte originál (je-li povolen).", - "setting_image_viewer_original_subtitle": "Umožňuje načíst původní obrázek v plném rozlišení (velký!). Zakažte pro snížení využití dat (v síti i v mezipaměti zařízení).", - "setting_image_viewer_original_title": "Načíst původní obrázek", - "setting_image_viewer_preview_subtitle": "Umožňuje načíst obrázek se středním rozlišením. Zakažte, pokud chcete přímo načíst originál nebo použít pouze miniaturu.", - "setting_image_viewer_preview_title": "Načíst náhled obrázku", - "setting_image_viewer_title": "Obrázky", - "setting_languages_apply": "Použít", - "setting_languages_subtitle": "Změna jazyka aplikace", - "setting_notifications_notify_failures_grace_period": "Oznámení o selhání zálohování na pozadí: {duration}", - "setting_notifications_notify_hours": "{count} hodin", - "setting_notifications_notify_immediately": "okamžitě", - "setting_notifications_notify_minutes": "{count} minut", - "setting_notifications_notify_never": "nikdy", - "setting_notifications_notify_seconds": "{count} sekund", - "setting_notifications_single_progress_subtitle": "Podrobné informace o průběhu nahrávání položky", - "setting_notifications_single_progress_title": "Zobrazit průběh detailů zálohování na pozadí", - "setting_notifications_subtitle": "Přizpůsobení předvoleb oznámení", - "setting_notifications_total_progress_subtitle": "Celkový průběh nahrání (hotovo/celkově)", - "setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí", - "setting_video_viewer_auto_play_subtitle": "Automaticky spustit přehrávání videí při jejich otevření", - "setting_video_viewer_auto_play_title": "Automatické přehrávání videí", - "setting_video_viewer_looping_title": "Smyčka", - "setting_video_viewer_original_video_subtitle": "Při streamování videa ze serveru přehrávat originál, i když je k dispozici překódovaná verze. Může vést k bufferování. Videa dostupná lokálně se přehrávají v původní kvalitě bez ohledu na toto nastavení.", - "setting_video_viewer_original_video_title": "Vynutit původní video", - "settings": "Nastavení", - "settings_require_restart": "Pro použití tohoto nastavení restartujte Immich", - "settings_saved": "Nastavení uloženo", - "setup_pin_code": "Nastavení PIN kódu", - "share": "Sdílet", - "share_action_prompt": "Sdíleno {count} položek", - "share_add_photos": "Přidat fotografie", - "share_assets_selected": "{count} vybráno", - "share_dialog_preparing": "Připravuji...", - "share_link": "Sdílet odkaz", - "shared": "Sdílené", - "shared_album_activities_input_disable": "Komentář je vypnutý", - "shared_album_activity_remove_content": "Chcete odstranit tuto aktivitu?", - "shared_album_activity_remove_title": "Odstranit aktivitu", - "shared_album_section_people_action_error": "Chyba při opuštění/odebrání z alba", - "shared_album_section_people_action_leave": "Odebrat uživatele z alba", - "shared_album_section_people_action_remove_user": "Odebrat uživatele z alba", - "shared_album_section_people_title": "LIDÉ", - "shared_by": "Sdílel(a)", - "shared_by_user": "Sdílel(a) {user}", - "shared_by_you": "Sdíleli jste", - "shared_from_partner": "Fotky od {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} nahráno", - "shared_link_app_bar_title": "Sdílené odkazy", - "shared_link_clipboard_copied_massage": "Zkopírováno do schránky", - "shared_link_clipboard_text": "Odkaz: {link}\nHeslo: {password}", - "shared_link_create_error": "Chyba při vytváření sdíleného odkazu", - "shared_link_custom_url_description": "Přístup k tomuto sdílenému odkazu pomocí vlastního URL", - "shared_link_edit_description_hint": "Zadejte popis sdílení", - "shared_link_edit_expire_after_option_day": "1 den", - "shared_link_edit_expire_after_option_days": "{count} dní", - "shared_link_edit_expire_after_option_hour": "1 hodina", - "shared_link_edit_expire_after_option_hours": "{count} hodin", - "shared_link_edit_expire_after_option_minute": "1 minuta", - "shared_link_edit_expire_after_option_minutes": "{count} minut", - "shared_link_edit_expire_after_option_months": "{count} měsíce", - "shared_link_edit_expire_after_option_year": "{count} rok", - "shared_link_edit_password_hint": "Zadejte heslo pro sdílení", - "shared_link_edit_submit_button": "Aktualizovat odkaz", - "shared_link_error_server_url_fetch": "Nelze načíst url serveru", - "shared_link_expires_day": "Vyprší za {count} den", - "shared_link_expires_days": "Vyprší za {count} dní", - "shared_link_expires_hour": "Vyprší za {count} hodinu", - "shared_link_expires_hours": "Vyprší za {count} hodin", - "shared_link_expires_minute": "Vyprší za {count} minutu", - "shared_link_expires_minutes": "Vyprší za {count} minut", - "shared_link_expires_never": "Platnost ∞", - "shared_link_expires_second": "Vyprší za {count} sekundu", - "shared_link_expires_seconds": "Vyprší za {count} sekund", - "shared_link_individual_shared": "Individuální sdílení", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Spravovat sdílené odkazy", - "shared_link_options": "Možnosti sdíleného odkazu", - "shared_link_password_description": "Vyžadovat heslo pro přístup k tomuto sdílenému odkazu", - "shared_links": "Sdílené odkazy", - "shared_links_description": "Sdílet fotky a videa pomocí odkazu", - "shared_photos_and_videos_count": "{assetCount, plural, one {# sdílená fotografie a video.} few {# sdílené fotografie a videa.} other {# sdílených fotografií a videí.}}", - "shared_with_me": "Sdílené se mnou", - "shared_with_partner": "Sdíleno s {partner}", - "sharing": "Sdílení", - "sharing_enter_password": "Pro zobrazení této stránky zadejte heslo.", - "sharing_page_album": "Sdílená alba", - "sharing_page_description": "Vytvářejte sdílená alba a sdílejte fotografie a videa s lidmi ve vaší síti.", - "sharing_page_empty_list": "PRÁZDNÝ SEZNAM", - "sharing_sidebar_description": "Zobrazit sekci Sdílení v postranním panelu", - "sharing_silver_appbar_create_shared_album": "Vytvořit sdílené album", - "sharing_silver_appbar_share_partner": "Sdílet s partnerem", - "shift_to_permanent_delete": "stiskněte ⇧ pro trvalé odstranění položky", - "show_album_options": "Zobrazit možnosti alba", - "show_albums": "Zobrazit alba", - "show_all_people": "Zobrazit všechny lidi", - "show_and_hide_people": "Zobrazit a skrýt osoby", - "show_file_location": "Zobrazit umístění souboru", - "show_gallery": "Zobrazit galerii", - "show_hidden_people": "Zobrazit skryté lidi", - "show_in_timeline": "Zobrazit na časové ose", - "show_in_timeline_setting_description": "Zobrazit fotky a videa tohoto uživatele na časové ose", - "show_keyboard_shortcuts": "Zobrazit klávesové zkratky", - "show_metadata": "Zobrazit metadata", - "show_or_hide_info": "Zobrazit nebo skrýt informace", - "show_password": "Zobrazit heslo", - "show_person_options": "Zobrazit možnosti osoby", - "show_progress_bar": "Zobrazit ukazatel průběhu", - "show_schema": "Zobrazit schéma", - "show_search_options": "Zobrazit možnosti vyhledávání", - "show_shared_links": "Zobrazit sdílené odkazy", - "show_slideshow_transition": "Zobrazit přechod prezentace", - "show_supporter_badge": "Odznak podporovatele", - "show_supporter_badge_description": "Zobrazit odznak podporovatele", - "show_text_recognition": "Zobrazit rozpoznávání textu", - "show_text_search_menu": "Zobrazit nabídku pro vyhledávání textu", - "shuffle": "Náhodný výběr", - "sidebar": "Postranní panel", - "sidebar_display_description": "Zobrazení odkazu na zobrazení v postranním panelu", - "sign_out": "Odhlásit se", - "sign_up": "Zaregistrovat se", - "size": "Velikost", - "skip_to_content": "Přejít na obsah", - "skip_to_folders": "Přeskočit na složky", - "skip_to_tags": "Přeskočit na značky", - "slideshow": "Prezentace", - "slideshow_repeat": "Opakovat prezentaci", - "slideshow_repeat_description": "Po skončení prezentace se vrátit na začátek", - "slideshow_settings": "Nastavení prezentace", - "sort_albums_by": "Seřadit alba podle...", - "sort_created": "Datum vytvoření", - "sort_items": "Počet položek", - "sort_modified": "Datum modifikace", - "sort_newest": "Nejnovější fotka", - "sort_oldest": "Nejstarší fotka", - "sort_people_by_similarity": "Seřadit lidi podle podobnosti", - "sort_recent": "Nejnovější fotka", - "sort_title": "Název alba", - "source": "Zdroj", - "stack": "Seskupit", - "stack_action_prompt": "{count} seskupeno", - "stack_duplicates": "Seskupit duplicity", - "stack_select_one_photo": "Vyberte jednu hlavní fotografii pro seskupení", - "stack_selected_photos": "Seskupení vybraných fotografií", - "stacked_assets_count": "{count, plural, one {Seskupena # položka} few {Seskupeny # položky} other {Seskupeno # položek}}", - "stacktrace": "Výpis zásobníku", - "start": "Start", - "start_date": "Počáteční datum", - "start_date_before_end_date": "Počáteční datum se musí nacházet před konečným datem", - "state": "Stát", - "status": "Stav", - "stop_casting": "Zastavit odesílání", - "stop_motion_photo": "Zastavit pohyblivou fotografii", - "stop_photo_sharing": "Přestat sdílet své fotografie?", - "stop_photo_sharing_description": "{partner} již nebude mít přístup k vašim fotkám.", - "stop_sharing_photos_with_user": "Přestat sdílet své fotky s tímto uživatelem", - "storage": "Velikost úložiště", - "storage_label": "Štítek úložiště", - "storage_quota": "Kvóta úložiště", - "storage_usage": "Využito {used} z {available}", - "submit": "Odeslat", - "success": "Úspěch", - "suggestions": "Návrhy", - "sunrise_on_the_beach": "Východ slunce na pláži", - "support": "Podpora", - "support_and_feedback": "Podpora a zpětná vazba", - "support_third_party_description": "Vaše Immich instalace byla připravena třetí stranou. Problémy, které se u vás vyskytly, mohou být způsobeny tímto balíčkem, proto se na ně obraťte v první řadě pomocí níže uvedených odkazů.", - "swap_merge_direction": "Obrátit směr sloučení", - "sync": "Synchronizovat", - "sync_albums": "Synchronizovat alba", - "sync_albums_manual_subtitle": "Synchronizovat všechna nahraná videa a fotografie do vybraných záložních alb", - "sync_local": "Synchronizovat místní", - "sync_remote": "Synchronizovat vzdálené", - "sync_status": "Stav synchronizace", - "sync_status_subtitle": "Zobrazit a spravovat synchronizační systém", - "sync_upload_album_setting_subtitle": "Vytvořit a nahrát fotografie a videa do vybraných alb na Immich", - "tag": "Značka", - "tag_assets": "Přiřadit značku", - "tag_created": "Vytvořena značka: {tag}", - "tag_feature_description": "Procházení fotografií a videí seskupených podle témat logických značek", - "tag_not_found_question": "Nemůžete najít značku? Vytvořte novou.", - "tag_people": "Označit lidi", - "tag_updated": "Aktualizována značka: {tag}", - "tagged_assets": "Přiřazena značka {count, plural, one {# položce} other {# položkám}}", - "tags": "Značky", - "tap_to_run_job": "Klepnutím spustíte úlohu", - "template": "Šablona", - "text_recognition": "Rozpoznávání textu", - "theme": "Motiv", - "theme_selection": "Výběr motivu", - "theme_selection_description": "Automatické nastavení světlého nebo tmavého motivu podle systémových preferencí prohlížeče", - "theme_setting_asset_list_storage_indicator_title": "Zobrazit indikátor úložiště na dlaždicích položek", - "theme_setting_asset_list_tiles_per_row_title": "Počet položek na řádek ({count})", - "theme_setting_colorful_interface_subtitle": "Použít hlavní barvu na povrchy pozadí.", - "theme_setting_colorful_interface_title": "Barevné rozhraní", - "theme_setting_image_viewer_quality_subtitle": "Přizpůsobení kvality detailů prohlížeče obrázků", - "theme_setting_image_viewer_quality_title": "Kvalita prohlížeče obrázků", - "theme_setting_primary_color_subtitle": "Zvolte barvu pro hlavní akce a zvýraznění.", - "theme_setting_primary_color_title": "Hlavní barva", - "theme_setting_system_primary_color_title": "Použít systémovou barvu", - "theme_setting_system_theme_switch": "Automaticky (podle systemového nastavení)", - "theme_setting_theme_subtitle": "Vyberte nastavení tématu aplikace", - "theme_setting_three_stage_loading_subtitle": "Třístupňové načítání může zvýšit výkonnost načítání, ale vede k výrazně vyššímu zatížení sítě", - "theme_setting_three_stage_loading_title": "Povolení třístupňového načítání", - "then": "Pak", - "they_will_be_merged_together": "Budou sloučeny dohromady", - "third_party_resources": "Zdroje třetích stran", - "time": "Čas", - "time_based_memories": "Časové vzpomínky", - "time_based_memories_duration": "Počet sekund k zobrazení každého obrázku.", - "timeline": "Časová osa", - "timezone": "Časové pásmo", - "to_archive": "Archivovat", - "to_change_password": "Změnit heslo", - "to_favorite": "Oblíbit", - "to_login": "Přihlásit", - "to_multi_select": "na vícenásobný výběr", - "to_parent": "Přejít k rodiči", - "to_select": "vybrat", - "to_trash": "Vyhodit", - "toggle_settings": "Přepnout nastavení", - "toggle_theme_description": "Přepnout motiv", - "total": "Celkem", - "total_usage": "Celkové využití", - "trash": "Koš", - "trash_action_prompt": "{count} přesunutých do koše", - "trash_all": "Vyhodit vše", - "trash_count": "Vyhodit {count, number}", - "trash_delete_asset": "Vyhodit/Smazat položku", - "trash_emptied": "Koš vyprázdněn", - "trash_no_results_message": "Zde se zobrazí odstraněné fotky a videa.", - "trash_page_delete_all": "Smazat všechny", - "trash_page_empty_trash_dialog_content": "Chcete vyprázdnit svoje vyhozené položky? Tyto položky budou trvale odstraněny z aplikace", - "trash_page_info": "Vyhozené položky budou trvale smazány po {days} dnech", - "trash_page_no_assets": "Žádné vyhozené položky", - "trash_page_restore_all": "Obnovit všechny", - "trash_page_select_assets_btn": "Vybrat položky", - "trash_page_title": "Koš ({count})", - "trashed_items_will_be_permanently_deleted_after": "Smazané položky budou trvale odstraněny po {days, plural, one {# dni} other {# dnech}}.", - "trigger": "Spouštěč", - "trigger_asset_uploaded": "Položka nahrána", - "trigger_asset_uploaded_description": "Spustí se při nahrání nového souboru", - "trigger_description": "Událost, která spustí pracovní postup", - "trigger_person_recognized": "Osoba rozpoznána", - "trigger_person_recognized_description": "Spustí se, když je objevena osoba", - "trigger_type": "Typ spouštěče", - "troubleshoot": "Diagnostika", - "type": "Typ", - "unable_to_change_pin_code": "Nelze změnit PIN kód", - "unable_to_check_version": "Nepodařilo se zjistit verzi aplikace nebo serveru", - "unable_to_setup_pin_code": "Nelze nastavit PIN kód", - "unarchive": "Odebrat z archivu", - "unarchive_action_prompt": "{count} odstraněných z archivu", - "unarchived_count": "{count, plural, one {Odarchivována #} few {Odarchivovány #} other {Odarchivováno #}}", - "undo": "Vrátit zpět", - "unfavorite": "Zrušit oblíbení", - "unfavorite_action_prompt": "{count} odstraněných z oblíbených", - "unhide_person": "Zrušit skrytí osoby", - "unknown": "Neznámý", - "unknown_country": "Neznámá země", - "unknown_date": "Neznámé datum", - "unknown_year": "Neznámý rok", - "unlimited": "Neomezeně", - "unlink_motion_video": "Odpojit pohyblivé video", - "unlink_oauth": "Zrušit OAuth propojení", - "unlinked_oauth_account": "OAuth účet odpojen", - "unmute_memories": "Zrušit ztlumení vzpomínek", - "unnamed_album": "Nepojmenované album", - "unnamed_album_delete_confirmation": "Opravdu chcete toto album smazat?", - "unnamed_share": "Nepojmenované sdílení", - "unsaved_change": "Neuložená změna", - "unselect_all": "Zrušit výběr všech", - "unselect_all_duplicates": "Zrušit výběr všech duplicit", - "unselect_all_in": "Zrušit výběr ve skupině {group}", - "unstack": "Zrušit seskupení", - "unstack_action_prompt": "{count} seskupených zrušeno", - "unstacked_assets_count": "{count, plural, one {Rozložená # položka} few {Rozložené # položky} other {Rozložených # položek}}", - "unsupported_field_type": "Nepodporovaný typ pole", - "untagged": "Neoznačeno", - "untitled_workflow": "Pracovní postup bez názvu", - "up_next": "To je prozatím vše", - "update_location_action_prompt": "Aktualizovat polohu {count} vybraných položek pomocí:", - "updated_at": "Aktualizováno", - "updated_password": "Heslo aktualizováno", - "upload": "Nahrát", - "upload_concurrency": "Souběžnost nahrávání", - "upload_details": "Detaily nahrávání", - "upload_dialog_info": "Chcete zálohovat vybrané položky na server?", - "upload_dialog_title": "Nahrát položku", - "upload_error_with_count": "Chyba při nahrávání {count, plural, one {# položky} other {# položek}}", - "upload_errors": "Nahrávání bylo dokončeno s {count, plural, one {# chybou} other {# chybami}}, obnovte stránku pro zobrazení nových položek.", - "upload_finished": "Nahrávání dokončeno", - "upload_progress": "Zbývá {remaining, number} - Zpracováno {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {Přeskočena # duplicitní položka} few {Přeskočeny # duplicitní položky} other {Přeskočeno # duplicitních položek}}", - "upload_status_duplicates": "Duplicity", - "upload_status_errors": "Chyby", - "upload_status_uploaded": "Nahráno", - "upload_success": "Nahrání proběhlo úspěšně, obnovením stránky se zobrazí nově nahrané položky.", - "upload_to_immich": "Nahrát do Immich ({count})", - "uploading": "Nahrávání", - "uploading_media": "Nahrávání médií", - "url": "URL", - "usage": "Využití", - "use_biometric": "Použít biometrické údaje", - "use_current_connection": "Použít aktuální připojení", - "use_custom_date_range": "Použít vlastní rozsah dat", - "user": "Uživatel", - "user_has_been_deleted": "Tento uživatel byl smazán.", - "user_id": "ID uživatele", - "user_liked": "Uživateli {user} se {type, select, photo {líbila tato fotka} video {líbilo toto video} asset {líbila tato položka} other {to líbilo}}", - "user_pin_code_settings": "PIN kód", - "user_pin_code_settings_description": "Správa vašeho PIN kódu", - "user_privacy": "Ochrana soukromí uživatelů", - "user_purchase_settings": "Nákup", - "user_purchase_settings_description": "Správa vašeho nákupu", - "user_role_set": "Uživatel {user} nastaven jako {role}", - "user_usage_detail": "Podrobnosti využití uživatelů", - "user_usage_stats": "Statistiky používání účtu", - "user_usage_stats_description": "Zobrazit statistiky používání účtu", - "username": "Uživateleské jméno", - "users": "Uživatelé", - "users_added_to_album_count": "{count, plural, one {Přidán # uživatel} few {Přidány # uživatelé} other {Přidáno # uživatelů}} do alba", - "utilities": "Nástroje", - "validate": "Ověřit", - "validate_endpoint_error": "Zadejte platné URL", - "validation_error": "Chyba ověření", - "variables": "Proměnné", - "version": "Verze", - "version_announcement_closing": "Váš přítel Alex", - "version_announcement_message": "Ahoj! K dispozici je nová verze aplikace Immich. Věnujte prosím chvíli přečtení poznámek k vydání a ujistěte se, že je vaše nastavení aktuální, abyste předešli případným chybným konfiguracím, zejména pokud používáte WatchTower nebo jiný mechanismus, který se stará o automatickou aktualizaci instance aplikace Immich.", - "version_history": "Historie verzí", - "version_history_item": "Verze {version} nainstalována dne {date}", - "video": "Video", - "video_hover_setting": "Přehrávat miniaturu videa po najetí myší", - "video_hover_setting_description": "Přehrát miniaturu videa při najetí myší na položku. I když je přehrávání vypnuto, lze jej spustit najetím na ikonu přehrávání.", - "videos": "Videa", - "videos_count": "{count, plural, one {# video} few {# videa} other {# videí}}", - "videos_only": "Pouze videa", - "view": "Zobrazit", - "view_album": "Zobrazit album", - "view_all": "Zobrazit vše", - "view_all_users": "Zobrazit všechny uživatele", - "view_asset_owners": "Zobrazit vlastníky položek", - "view_details": "Zobrazit podrobnosti", - "view_in_timeline": "Zobrazit na časové ose", - "view_link": "Zobrazit odkaz", - "view_links": "Zobrazit odkazy", - "view_name": "Zobrazit", - "view_next_asset": "Zobrazit další položku", - "view_previous_asset": "Zobrazit předchozí položku", - "view_qr_code": "Zobrazit QR kód", - "view_similar_photos": "Zobrazit podobné fotky", - "view_stack": "Zobrazit seskupení", - "view_user": "Zobrazit uživatele", - "viewer_remove_from_stack": "Odstranit ze zásobníku", - "viewer_stack_use_as_main_asset": "Použít jako hlavní položku", - "viewer_unstack": "Zrušit zásobník", - "visibility_changed": "Viditelnost změněna u {count, plural, one {# osoby} few {# osob} other {# lidí}}", - "visual": "Vizuální", - "visual_builder": "Vizuální návrhář", - "waiting": "Čekající", - "waiting_count": "Čekající: {count}", - "warning": "Upozornění", - "week": "Týden", - "welcome": "Vítejte", - "welcome_to_immich": "Vítejte v Immichi", - "width": "Šířka", - "wifi_name": "Název Wi-Fi", - "workflow_delete_prompt": "Opravdu chcete tento pracovní postup smazat?", - "workflow_deleted": "Pracovní postup smazán", - "workflow_description": "Popis pracovního postupu", - "workflow_info": "Informace o pracovním postupu", - "workflow_json": "JSON pracovního postupu", - "workflow_json_help": "Upravte konfiguraci pracovního postupu ve formátu JSON. Změny se synchronizují s vizuálním návrhářem.", - "workflow_name": "Název pracovního postupu", - "workflow_navigation_prompt": "Opravdu chcete odejít bez uložení změn?", - "workflow_summary": "Shrnutí pracovního postupu", - "workflow_update_success": "Pracovní postup byl úspěšně aktualizován", - "workflow_updated": "Pracovní postup aktualizován", - "workflows": "Pracovní postupy", - "workflows_help_text": "Pracovní postupy automatizují akce týkající se vašich položek na základě spouštěčů a filtrů", - "wrong_pin_code": "Chybný PIN kód", - "year": "Rok", - "years_ago": "Před {years, plural, one {rokem} other {# lety}}", - "yes": "Ano", - "you_dont_have_any_shared_links": "Nemáte žádné sdílené odkazy", - "your_wifi_name": "Název vaší Wi-Fi", - "zero_to_clear_rating": "stiskněte 0 pro vymazání hodnocení položky", - "zoom_image": "Zvětšit obrázek", - "zoom_to_bounds": "Přiblížit na okraje" -} +{} diff --git a/i18n/cv.json b/i18n/cv.json index 52008a176f..0967ef424b 100644 --- a/i18n/cv.json +++ b/i18n/cv.json @@ -1,96 +1 @@ -{ - "about": "Ҫинчен", - "account": "Шута ҫырни", - "account_settings": "Шута ҫырни ӗнерленӳ", - "acknowledge": "Çирӗплет", - "action": "Ӗçлени", - "action_common_update": "Ҫӗнет", - "actions": "Ӗҫсем", - "active": "Хастар", - "activity": "Хастарлӑх", - "activity_changed": "Хастарлӑха {enabled, select, true {кӗртнӗ} other {сӳнтернӗ}}", - "add": "Хуш", - "add_a_description": "Ҫырса кӑтартни хуш", - "add_a_location": "Вырӑн хуш", - "add_a_name": "Ятне хуш", - "add_a_title": "Ят хуш", - "add_birthday": "Ҫуралнӑ кун хушӑр", - "add_endpoint": "Вӗҫӗмлӗ пӑнчӑ хушар", - "add_exclusion_pattern": "Кӑларса пӑрахмалли йӗрке хуш", - "add_location": "Вырӑн хуш", - "add_more_users": "Усӑҫсем ытларах хуш", - "add_partner": "Мӑшӑр хуш", - "add_path": "Ҫулне хуш", - "add_photos": "Сӑнӳкерчӗксем хуш", - "add_tag": "Тег хуш", - "add_to": "Мӗн те пулин хуш…", - "add_to_album": "Альбома хуш", - "add_to_shared_album": "Пӗрлехи альбома хуш", - "add_url": "URL хушӑр", - "added_to_archive": "Архива хушнӑ", - "added_to_favorites": "Суйласа илнине хушнӑ", - "added_to_favorites_count": "Суйласа илнине {count, number} хушнӑ", - "admin": { - "admin_user": "Усӑҫ админ", - "asset_offline_description": "Библиотекӑн ҫак тулаш файлне дискра урӑх тупайман, карҫинккана куҫарнӑ. Енчен те файла вулавӑш ӑшне куҫарнӑ пулсан, тивӗҫлӗ ҫӗнӗ ресурс тупас тесен хӑвӑрӑн вӑхӑтлӑх шкалӑна тӗрӗслӗр. Ҫак файла ҫӗнӗрен чӗртес тесен файл патне каймалли ҫула Immich валли аяларах ҫитернине курса ӗненӗр, библиотекӑна сканерланине пурнӑҫлӑр.", - "authentication_settings": "Ауттентихвикатси ӗнерленӳсем", - "authentication_settings_disable_all": "Эсир кӗмелли пур меслетсене те чарса лартасшӑн тесе шутлатӑр-и? Кӗмелли шӑтӑка пӗтӗмпех уҫаҫҫӗ.", - "background_task_job": "Курăнман ӗҫсем", - "backup_database": "Пĕлĕм пухмачĕ туса", - "backup_onboarding_title": "Сыхлӑх копписем", - "cleared_jobs": "Ӗҫсене тасатнӑ:{job}", - "confirm_email_below": "Ҫирӗплетес тесен, аяларах «{email}» кӗртӗр", - "confirm_reprocess_all_faces": "Пӗтӗм сӑнӗсене тепӗр хут палӑртас килет тесе шанатӑр-и? Ҫавӑн пекех ятсене пур ҫынран та хуратӗҫ.", - "create_job": "Ӗҫе ту", - "disable_login": "Кӗме чарӑр", - "duplicate_detection_job_description": "Пӗр пек ӳкерчӗксене тупма машинӑллӑ вӗренӗве ӗҫлеттерӗр. Ӑслӑ шыравпа усӑ кураҫҫӗ", - "face_detection": "Пит-куҫа тупасси", - "force_delete_user_warning": "ПУЛТАРУЛӐХ: Ку усӑ куракана тата мӗнпур ресурса ҫийӗнчех кӑларса пӑрахасси патне илсе ҫитерӗ. Кӑна пӑрахӑҫлама май ҫук, файлсене те юсаса пӗтереймеҫҫӗ.", - "image_format": "Тулашлăх", - "image_preview_description": "Вӑтам пысӑкӑш ӳкерчӗк, уйрӑм метаданнӑйсем, пӗр объекта пӑхнӑ чухне тата машинӑллӑ вӗренӳре усӑ кураҫҫӗ", - "image_preview_quality_description": "1-100 таран малтанхи пахалӑх. Ҫӳллӗреххи лайӑхрах, анчах та пысӑкрах файлсем туса кӑларать тата приложенисен хуравлӑхне чакарма пултарать. Пӗчӗк хак лартни машинӑллӑ вӗренӳ пахалӑхне витӗм кӳме пултарать.", - "image_preview_title": "Малтанлӑха пӑхмалли ӗнерлевсем", - "image_quality": "Пахалӑх", - "image_resolution": "Виҫе", - "image_settings": "Сӑнӳкерчӗк ӗнерленӳсем", - "image_thumbnail_title": "Пӗчӗк ӳкерчӗксен ӗнерленӳсем", - "map_gps_settings": "Карттӑ тата GPS ĕнерленĕвĕ", - "map_gps_settings_description": "Карттӑпа GPS (каялла геоюмлани) ӗнерленисене йӗркелесе тӑрӑр", - "map_settings": "Карттӑ" - }, - "albums": "Албумсем", - "albums_count": "{count, plural, one {{count, number} албум} other {{count, number} албумсем}}", - "all": "Пурте", - "all_albums": "Пурте албумсем", - "explore": "Тишкер", - "explorer": "Тишкерӳҫӗ", - "favorite": "Юратнӑ", - "favorite_or_unfavorite_photo": "Юратнӑ е юратман сӑнӳкерчӗк", - "favorites": "Юратнисем", - "feature_photo_updated": "Уйрӑм сӑнӳкерчӗк ҫӗнетнӗ", - "manage_sharing_with_partners": "Партнерсемпе пайланассине йӗркелесе пырӑр", - "map": "Карттӑ", - "map_marker_for_images": "{city}, {country} ҫинче ӳкернӗ ӳкерчӗксем валли карттӑ маркерӗ", - "map_marker_with_image": "Карттӑ маркерӗ ӳкерчӗкпе", - "map_settings": "Карттӑ ĕнерленĕвĕ", - "no_explore_results_message": "Хӑвӑр коллекципе киленмешкӗн сӑнӳкерчӗксем ытларах тийӗр.", - "open_in_openstreetmap": "OpenStreetMap-па уҫ", - "organize_your_library": "Хӑвӑн вулавӑшна йӗркеле", - "partner_sharing": "Партнер пайланӑвӗ", - "people": "Ҫынсем", - "photos": "Сӑнӳкерчӗксем", - "photos_and_videos": "Сӑнӳкерчӗксем тете Видеосем", - "photos_count": "{count, plural, one {{count, number} Сӑнӳкерчӗк} other {{count, number} Сӑнӳкерчӗксем}}", - "photos_from_previous_years": "Иртнӗ ҫулсенчи сӑнӳкерчӗксем", - "place": "Тӗл", - "places": "Тӗлсем", - "play": "Выля", - "play_memories": "Асаилӳсем выля", - "search_your_photos": "Сӑнӳкерчӗксене шырӑр", - "select_photos": "Сӑнӳкерчӗксем суйлӑр", - "sharing": "Пайлани", - "sharing_enter_password": "Ку питне курма пароль кӗртӗр.", - "user_usage_stats": "Шута ҫырни усӑ курмалли статистика", - "user_usage_stats_description": "Шута ҫырни усӑ курмалли статистикӑна пӑхасси", - "utilities": "Пулӑшакансем" -} +{} diff --git a/i18n/da.json b/i18n/da.json index 7f2b77dc28..0967ef424b 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -1,2368 +1 @@ -{ - "about": "Om os", - "account": "Konto", - "account_settings": "Kontoindstillinger", - "acknowledge": "Accepter", - "action": "Handling", - "action_common_update": "Opdater", - "action_description": "Et sæt handlinger, der skal udføres på de filtrerede mediefiler", - "actions": "Handlinger", - "active": "Aktiv", - "active_count": "Aktiv: {count}", - "activity": "Aktivitet", - "activity_changed": "Aktivitet er {enabled, select, true {aktiveret} other {deaktiveret}}", - "add": "Tilføj", - "add_a_description": "Tilføj en beskrivelse", - "add_a_location": "Tilføj en placering", - "add_a_name": "Tilføj et navn", - "add_a_title": "Tilføj en titel", - "add_action": "Tilføj handling", - "add_action_description": "Klik for at tilføje en handling, der skal udføres", - "add_assets": "Tilføj ressourcer", - "add_birthday": "Tilføj en fødselsdag", - "add_endpoint": "Tilføj endepunkt", - "add_exclusion_pattern": "Tilføj udelukkelsesmønster", - "add_filter": "Tilføj filter", - "add_filter_description": "Klik for at tilføje en filterbetingelse", - "add_location": "Tilføj placering", - "add_more_users": "Tilføj flere brugere", - "add_partner": "Tilføj partner", - "add_path": "Tilføj sti", - "add_photos": "Tilføj billeder", - "add_tag": "Tilføj tag", - "add_to": "Tilføj til…", - "add_to_album": "Tilføj til album", - "add_to_album_bottom_sheet_added": "Tilføjet til {album}", - "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", - "add_to_album_bottom_sheet_some_local_assets": "Nogle lokale elementer kunne ikke føjes til albummet", - "add_to_album_toggle": "Skift selektion for {album}", - "add_to_albums": "Tilføj til albummer", - "add_to_albums_count": "Tilføj til albummer({count})", - "add_to_bottom_bar": "Tilføj til", - "add_to_shared_album": "Tilføj til delt album", - "add_upload_to_stack": "Tilføj upload til stack", - "add_url": "Tilføj URL", - "add_workflow_step": "Tilføj workflow-trin", - "added_to_archive": "Tilføjet til arkiv", - "added_to_favorites": "Tilføjet til favoritter", - "added_to_favorites_count": "Tilføjede {count, number} til favoritter", - "admin": { - "add_exclusion_pattern_description": "Tilføj udelukkelsesmønstre. Globbing ved hjælp af *, ** og ? understøttes. For at ignorere alle filer i enhver mappe med navnet \"Raw\", brug \"**/Raw/**\". For at ignorere alle filer, der slutter på \".tif\", brug \"**/*.tif\". For at ignorere en absolut sti, brug \"/sti/til/ignoreret/**\".", - "admin_user": "Administratorbruger", - "asset_offline_description": "Denne eksterne biblioteksressource findes ikke længere på disken og er blevet flyttet til papirkurven. Hvis filen blev flyttet inde i biblioteket, skal du tjekke din tidslinje for den nye tilsvarende ressource. For at gendanne denne ressource skal du sikre, at filstien nedenfor kan tilgås af Immich og scanne biblioteket.", - "authentication_settings": "Godkendelsesindstillinger", - "authentication_settings_description": "Administrer adgangskode, OAuth og andre godkendelsesindstillinger", - "authentication_settings_disable_all": "Er du sikker på at du vil deaktivere alle loginmuligheder? Login vil blive helt deaktiveret.", - "authentication_settings_reenable": "Brug en server-kommando for at genaktivere.", - "background_task_job": "Baggrundsopgaver", - "backup_database": "Lav Database Dump", - "backup_database_enable_description": "Slå database-backup til", - "backup_keep_last_amount": "Mængde af tidligere backups, der skal gemmes", - "backup_onboarding_1_description": "kopi på en anden fysisk lokation eller i skyen.", - "backup_onboarding_2_description": "lokale kopier på separate enheder. Dette inkluderer de originale filer og en lokal backup af disse.", - "backup_onboarding_3_description": "kopier af din data i alt, inklusiv de originale filer. Dette inkluderer 1 kopi på en anden fysisk lokation, og 2 lokale kopier.", - "backup_onboarding_description": "En 3-2-1 backup strategy anbefales for at beskytte dine data. En altomfattende backupløsning skulle gerne have kopier af dine uploadede billeder og videoer, samt Immich databasen.", - "backup_onboarding_footer": "Referer venligst til dokumentationen for mere information om hvordan Immich backes op.", - "backup_onboarding_parts_title": "En 3-2-1 backup inkluderer:", - "backup_onboarding_title": "Backupper", - "backup_settings": "Database Backup-indstillinger", - "backup_settings_description": "Administrer backupindstillinger for database.", - "cleared_jobs": "Ryddet jobs til: {job}", - "config_set_by_file": "konfigurationen er i øjeblikket indstillet af en konfigurations fil", - "confirm_delete_library": "Er du sikker på, at du vil slette {library} bibliotek?", - "confirm_delete_library_assets": "Er du sikker på, at du vil slette dette bibliotek? Dette vil slette {count, plural, one {# indeholdt mediefil} other {alle # indeholdte mediefiler}} fra Immich og kan ikke gøres om. Filerne forbliver på disken.", - "confirm_email_below": "For at bekræfte, skriv \"{email}\" herunder", - "confirm_reprocess_all_faces": "Er du sikker på, at du vil genbehandle alle ansigter? Dette vil også rydde navngivne personer.", - "confirm_user_password_reset": "Er du sikker på, at du vil nulstille {user}s adgangskode?", - "confirm_user_pin_code_reset": "Er du sikker på at du vil nulstille {user}'s PIN kode?", - "copy_config_to_clipboard_description": "Kopier den aktuelle systemkonfiguration som et JSON-objekt til udklipsholderen", - "create_job": "Opret job", - "cron_expression": "Cron formel", - "cron_expression_description": "Indstil skannings intervallet i cron format. For mere information se: Crontab Guru", - "cron_expression_presets": "cron predefinerede indstillinger", - "disable_login": "Deaktiver login", - "duplicate_detection_job_description": "Kør maskinlæring på mediefiler for at opdage lignende billeder. Er afhængig af Smart Søgning", - "exclusion_pattern_description": "Ekskluderingsmønstre lader dig ignorere filer og mapper, når du scanner dit bibliotek. Dette er nyttigt, hvis du har mapper, der indeholder filer, du ikke vil importere, såsom RAW-filer.", - "export_config_as_json_description": "Download den aktuelle systemkonfiguration som en JSON-fil", - "external_libraries_page_description": "Admin ekstern biblioteksside", - "face_detection": "Ansigtsopdagelse", - "face_detection_description": "Genkend ansigterne i mediefiler via maskinlæring. For videoer er det kun miniaturebilledet som tages hensyn til. \"Alle\" (gen-)behandler alle mediefiler. \"Mangler\" sætter mediefiler i kø, som ikke er blevet behandlet endnu. Opdagede ansigter vil blive sat i kø til Ansigtsgenkendelse efter Ansigtsopdagelse er færdig, hvilket grupperer dem til eksisterende eller nye personer.", - "facial_recognition_job_description": "Grupper opdagede ansigter i personer. Dette trin kører efter Ansigtsopdagelse er færdig. \"Alle\" (gen-)klumper alle ansigter sammen. \"Mangler\" sætter ansigter i kø, som ikke har en person tildelt.", - "failed_job_command": "Kommando {command} mislykkedes for job: {job}", - "force_delete_user_warning": "ADVARSEL: Dette vil øjeblikkeligt fjerne brugeren og alle Billeder/Videoer. Dette kan ikke fortrydes, og filerne kan ikke gendannes.", - "image_format": "Format", - "image_format_description": "WebP producerer mindre filer end JPEG, men er langsommere at komprimere.", - "image_fullsize_description": "Fuld størrelses billede uden metadata, brugt når zoomet ind", - "image_fullsize_enabled": "Aktiver fuld størrelses billede generering", - "image_fullsize_enabled_description": "Generer fuld-størrelses billede for ikke-web-venlige formater. Når \"Foretræk indlejret forhåndsvisning\" er slået til, bliver indlejrede forhåndsvisninger brugt direkte uden konvertering. Påvirker ikke web-venlige formater såsom JPEG.", - "image_fullsize_quality_description": "Fuld-størrelses billede kvalitet fra 1-100. Højere er bedre, men producerer større filer.", - "image_fullsize_title": "Full-størrelses billede indstillinger", - "image_prefer_embedded_preview": "Foretræk indlejret forhåndsvisning", - "image_prefer_embedded_preview_setting_description": "Brug indlejrede forhåndsvisninger i RAW fotos som input til billedbehandling og når det er tilgængeligt. Dette kan give mere nøjagtige farver for nogle billeder, men kvaliteten af forhåndsvisningen er kameraafhængig, og billedet kan have flere komprimeringsartefakter.", - "image_prefer_wide_gamut": "Foretrækker bred farveskala", - "image_prefer_wide_gamut_setting_description": "Brug Display P3 til miniaturebilleder. Dette bevarer billeder med brede farveskalaers dynamik bedre, men billeder kan komme til at se anderledes ud på gamle enheder med en gammel browserversion. sRGB-billeder bliver beholdt som sRGB for at undgå farveskift.", - "image_preview_description": "Mellemstørrelse billede med fjernet metadata, der bruges, når du ser en enkelt mediefil og til machine learning", - "image_preview_quality_description": "Kvalitet af forhåndsvisning fra 1-100. Højere er bedre, men producerer større filer og kan reducere apprespons. Valg af en lav værdi kan påvirke kvaliteten af maskin læring.", - "image_preview_title": "Indstillinger for forhåndsvisning", - "image_progressive": "Progressivt", - "image_progressive_description": "Indkod JPEG-billeder progressivt for gradvis indlæsning. Dette har ingen effekt på WebP-billeder.", - "image_quality": "Kvalitet", - "image_resolution": "Opløsning", - "image_resolution_description": "Højere opløsning indeholder flere detaljer, men tager længere tid at processerer, giver større filer og sænker svartiderne i applikationen.", - "image_settings": "Billedindstillinger", - "image_settings_description": "Administrer kvaliteten og opløsningen af genererede billeder", - "image_thumbnail_description": "Små miniaturer uden metadata, bruges når der ses samlinger eller den primære tidslinie", - "image_thumbnail_quality_description": "Miniaturer kvaliteten indstilles fra 1 til 100. Nu højre, nu bedre kvalitet. Men giver større filer og påvirker programmets svartider.", - "image_thumbnail_title": "Thumbnail-indstillinger", - "import_config_from_json_description": "Importer systemkonfiguration ved at uploade en JSON-konfigurationsfil", - "job_concurrency": "{job} samtidighed", - "job_created": "opgaven er skabt", - "job_not_concurrency_safe": "Denne opgave er ikke sikker at køre samtidigt med andre.", - "job_settings": "Jobindstillinger", - "job_settings_description": "Administrér samtidige opgaver", - "jobs_delayed": "{jobCount, plural, one {# forsinket} other {# forsinkede}}", - "jobs_failed": "{jobCount, plural, one {# fejlet} other {# fejlede}}", - "jobs_over_time": "Opgaver over tid", - "library_created": "Skabte bibliotek: {library}", - "library_deleted": "Bibliotek slettet", - "library_details": "Bibliotek detaljer", - "library_folder_description": "Angiv en mappe, der skal importeres. Denne mappe, inklusive undermapper, scannes for billeder og videoer.", - "library_remove_exclusion_pattern_prompt": "Er du sikker på, at du vil fjerne dette udelukkelsesmønster?", - "library_remove_folder_prompt": "Er du sikker på, at du vil fjerne denne importmappe?", - "library_scanning": "Periodisk scanning", - "library_scanning_description": "Konfigurer periodisk biblioteksscanning", - "library_scanning_enable_description": "Aktiver periodisk biblioteksscanning", - "library_settings": "Eksternt bibliotek", - "library_settings_description": "Administrer eksterne biblioteksindstillinger", - "library_tasks_description": "Scan eksterne biblioteker for nye og/eller ændrede mediefiler", - "library_updated": "Opdateret bibliotek", - "library_watching_enable_description": "Overvåg eksterne biblioteker for filændringer", - "library_watching_settings": "Biblioteks overvågning [EKSPERIMENTEL]", - "library_watching_settings_description": "Tjek automatisk for ændrede filer", - "logging_enable_description": "Aktiver logning", - "logging_level_description": "Når slået til, hvilket logniveau, der skal bruges.", - "logging_settings": "Logning", - "machine_learning_availability_checks": "Tilgængelighedstjek", - "machine_learning_availability_checks_description": "Opdag og foretræk automatisk tilgængelige maskinlæringsservere", - "machine_learning_availability_checks_enabled": "Aktivér tilgængelighedstjek", - "machine_learning_availability_checks_interval": "Kontroller interval", - "machine_learning_availability_checks_interval_description": "Interval i millisekunder mellem tilgængelighedstjeks", - "machine_learning_availability_checks_timeout": "Timeout på anmodning", - "machine_learning_availability_checks_timeout_description": "Timeout i millisekunder på tilgængelighedstjeks", - "machine_learning_clip_model": "CLIP-model", - "machine_learning_clip_model_description": "Navnet på CLIP-modellen på listen her. Bemærk at du skal genkøre \"Smart Søgning\"-jobbet for alle billeder, hvis du skifter model.", - "machine_learning_duplicate_detection": "Dubletdetektion", - "machine_learning_duplicate_detection_enabled": "Aktiver dubletdetektion", - "machine_learning_duplicate_detection_enabled_description": "Når slået fra, vil nøjagtigt identiske mediefiler stadig blive de-duplikerede.", - "machine_learning_duplicate_detection_setting_description": "Brug CLIP-indlejringer til at finde sandsynlige dubletter", - "machine_learning_enabled": "Aktivér maskinlæring", - "machine_learning_enabled_description": "Hvis deaktiveret, vil alle ML-funktioner blive deaktiveret uanset nedenstående indstillinger.", - "machine_learning_facial_recognition": "Ansigtsgenkendelse", - "machine_learning_facial_recognition_description": "Opdag, genkend og gruppér ansigter i billeder", - "machine_learning_facial_recognition_model": "Ansigtsgenkendelsesmodel", - "machine_learning_facial_recognition_model_description": "Modellerne er listet i faldende størrelsesorden. Større modeller er langsommere og bruger mere hukommelse, men giver bedre resultater. Bemærk, at du skal køre ansigtsopdagelsesopgaven igen for alle billeder, når du ændrer en model.", - "machine_learning_facial_recognition_setting": "Aktivér ansigtgenkendelse", - "machine_learning_facial_recognition_setting_description": "Hvis deaktiveret, vil billeder ikke blive kodet til ansigtsgenkendelse og vil ikke udfylde sektionen Personer på siden Udforsk.", - "machine_learning_max_detection_distance": "Maksimal detektionsafstand", - "machine_learning_max_detection_distance_description": "Maksimal forskel mellem to billeder for at betragte dem som dubletter, spænder fra 0,001-0,1. Højere forskelle vil registrere flere dubletter, men kan resultere i falske positiver.", - "machine_learning_max_recognition_distance": "Maksimal genkendelsesforskel", - "machine_learning_max_recognition_distance_description": "Maksimal afstand mellem to ansigter for at de stadig bliver genkendt som samme person, fra 0 til 2. At mindske denne værdi kan forhindre at to personer bliver markeret som den samme person, mens at øge den kan forhindre at den samme person bliver markeret som to forskellige personer. Bemærk, at det er nemmere at sammenflette to personer, end det er at splitte en person i to, så brug en lavere værdi når det er muligt for at være på den sikre side.", - "machine_learning_min_detection_score": "Minimum detektionsscore", - "machine_learning_min_detection_score_description": "Minimum tillidsscore for et ansigt, der kan registreres fra 0-1. Lavere værdier vil registrere flere ansigter, men kan resultere i falske positiver.", - "machine_learning_min_recognized_faces": "Minimum genkendte ansigter", - "machine_learning_min_recognized_faces_description": "Minimumsantallet af genkendte ansigter for en person, før denne person bliver oprettet. At øge dette gør ansigtsgenkendelse mere præcis på bekostning af at øge chancen for, at et ansigt ikke er tildelt en person.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Brug maskinlæring til at genkende tekst i billeder", - "machine_learning_ocr_enabled": "Aktiver OCR", - "machine_learning_ocr_enabled_description": "Hvis deaktiveret, vil tekstgenkendelse ikke blive udført på billederne.", - "machine_learning_ocr_max_resolution": "Maksimum opløsning", - "machine_learning_ocr_max_resolution_description": "Forhåndsvisninger over denne opløsning ændres i størrelse, mens billedformatet bevares. Højere værdier er mere nøjagtige, men tager længere tid at behandle og bruger mere hukommelse.", - "machine_learning_ocr_min_detection_score": "Minimum detektionsscore", - "machine_learning_ocr_min_detection_score_description": "Minimums konfidensscore for tekst, der skal detekteres, fra 0-1. Lavere værdier vil detektere mere tekst, men kan resultere i falsk positiver.", - "machine_learning_ocr_min_recognition_score": "Minimum genkendelsesscore", - "machine_learning_ocr_min_score_recognition_description": "Minimum konfidensscore for genkendelse af registreret tekst er fra 0-1. Lavere værdier vil genkende mere tekst, men kan resultere i falsk positiver.", - "machine_learning_ocr_model": "OCR model", - "machine_learning_ocr_model_description": "Server modeller er mere præcise end mobil modeller, men tager længer tid at processere og bruger mere hukommelse.", - "machine_learning_settings": "Maskinlæringsindstillinger", - "machine_learning_settings_description": "Administrer maskinlæringsfunktioner og indstillinger", - "machine_learning_smart_search": "Smart søgning", - "machine_learning_smart_search_description": "Søg semantisk efter billeder ved hjælp af CLIP-indlejringer", - "machine_learning_smart_search_enabled": "Aktiver smart søgning", - "machine_learning_smart_search_enabled_description": "Hvis deaktiveret, vil billeder ikke blive kodet til smart søgning.", - "machine_learning_url_description": "URL’en for maskinlæringsserveren. Hvis mere end én URL angives, vil hver server blive forsøgt én ad gangen, indtil en svarer succesfuldt, i rækkefølge fra første til sidste. Servere, der ikke svarer, vil midlertidigt blive ignoreret, indtil de kommer online igen.", - "maintenance_delete_backup": "Slet Backup", - "maintenance_delete_backup_description": "Denne fil vil blive slettet permanent.", - "maintenance_delete_error": "Sletning af backup fejlede.", - "maintenance_restore_backup": "Genskab backup", - "maintenance_restore_backup_description": "Immich bliver slettet og genskabt fra den valgte backup. Der vil blive taget en backup før du fortsætter.", - "maintenance_restore_backup_different_version": "Denne backup blev lavet med en anden version af Immich!", - "maintenance_restore_backup_unknown_version": "Kunne ikke bestemme versionen af backup'en.", - "maintenance_restore_database_backup": "Genskab databasebackup", - "maintenance_restore_database_backup_description": "Gendan en tidligere databasetilstand ved hjælp af en sikkerhedskopifil", - "maintenance_settings": "Vedligeholdelse", - "maintenance_settings_description": "Sæt Immich i vedligeholdelsestilstand.", - "maintenance_start": "Start vedligeholdelsestilstand", - "maintenance_start_error": "Vedligeholdelsestilstand kunne ikke startes.", - "maintenance_upload_backup": "Upload databasebackupfil", - "maintenance_upload_backup_error": "Kunne ikke uploade backup, er det en .sql/.sql.gz fil?", - "manage_concurrency": "Administrer antallet af samtidige opgaver", - "manage_concurrency_description": "Naviger til jobsiden for at administrere jobsamtidighed", - "manage_log_settings": "Administrer logindstillinger", - "map_dark_style": "Mørk tema", - "map_enable_description": "Aktivér kortfunktioner", - "map_gps_settings": "Kort- og GPS-indstillinger", - "map_gps_settings_description": "Håndter indstillinger for Kort og GPS (Omvendt Geokodning)", - "map_implications": "Kortfunktionen afhænger af en ekstern tile-service (tiles.immich.cloud)", - "map_light_style": "Lyst tema", - "map_manage_reverse_geocoding_settings": "Håndtér indstillinger for Omvendt Geokoding", - "map_reverse_geocoding": "Omvendt geokodning", - "map_reverse_geocoding_enable_description": "Aktiver omvendt geokodning", - "map_reverse_geocoding_settings": "Omvendt geokodningsindstillinger", - "map_settings": "Kort", - "map_settings_description": "Administrer kortindstillinger", - "map_style_description": "URL til en style.json for et korttema", - "memory_cleanup_job": "Mindeoprydning", - "memory_generate_job": "Mindegeneration", - "metadata_extraction_job": "Udtræk metadata", - "metadata_extraction_job_description": "Udtræk metadataoplysninger fra hvert Billede/Video, såsom GPS og opløsning", - "metadata_faces_import_setting": "Aktivér for at importere ansigter", - "metadata_faces_import_setting_description": "Importerer ansigter fra billed EXIF-data og forbandt filer", - "metadata_settings": "Metadatainstillinger", - "metadata_settings_description": "Håndtér metadataindstillinger", - "migration_job": "Migrering", - "migration_job_description": "Migrér miniaturebilleder for aktiver og ansigter til den seneste mappestruktur", - "nightly_tasks_cluster_faces_setting_description": "Kør ansigtsgenkendelse på nye ansigter", - "nightly_tasks_cluster_new_faces_setting": "Gruppér nye ansigter", - "nightly_tasks_database_cleanup_setting": "Databaseoprydning opgaver", - "nightly_tasks_database_cleanup_setting_description": "Ryd op i gamle, udløbne data fra databasen", - "nightly_tasks_generate_memories_setting": "Generer minder", - "nightly_tasks_generate_memories_setting_description": "Skab nye minder ud fra dine medier", - "nightly_tasks_missing_thumbnails_setting": "Generer manglende miniaturebilleder", - "nightly_tasks_missing_thumbnails_setting_description": "Sæt medier uden thumbnails i kø til generering af thumbnails", - "nightly_tasks_settings": "Indstillinger for opgaver om natten", - "nightly_tasks_settings_description": "Administrér opgaver om natten", - "nightly_tasks_start_time_setting": "Starttidspunkt", - "nightly_tasks_start_time_setting_description": "Tidspunktet hvor serveren begynder at køre opgaver om natten", - "nightly_tasks_sync_quota_usage_setting": "Synkronisér kvoteforbrug", - "nightly_tasks_sync_quota_usage_setting_description": "Opdater brugerens lagerkvote baseret på aktuelt forbrug", - "no_paths_added": "Ingen stier tilføjet", - "no_pattern_added": "Intet mønster tilføjet", - "note_apply_storage_label_previous_assets": "Bemærk: For at anvende Lagringsmærkatet på tidligere uploadede mediefiler, kør", - "note_cannot_be_changed_later": "BEMÆRK: Dette kan ikke ændres senere!", - "notification_email_from_address": "Fra adressse", - "notification_email_from_address_description": "Afsenderemailadresse, for eksempel: \"Immich Billedserver \". Vær sikker på du bruger en email du kan sende emails fra.", - "notification_email_host_description": "Host af emailserver (fx smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorér certifikatfejl", - "notification_email_ignore_certificate_errors_description": "Ignorér TLS-certifikatgodkendelsesfejl (ikke anbefalet)", - "notification_email_password_description": "Adgangskode til brug ved autentificering med e-mailserveren", - "notification_email_port_description": "Emailserverens port (fx 25, 465 eller 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Brug SMTPS (SMTP over TLS)", - "notification_email_sent_test_email_button": "Send test-email og gem", - "notification_email_setting_description": "Indstillinger for sending af emailnotifikationer", - "notification_email_test_email": "Send test-email", - "notification_email_test_email_failed": "Fejl ved afsendelse af test-email, tjek dine værdier", - "notification_email_test_email_sent": "En test-email er blevet sendt til {email}. Venligst tjek din inbox.", - "notification_email_username_description": "Brugernavn til brug ved autentificering med e-mailserveren", - "notification_enable_email_notifications": "Slå emailnotifikationer til", - "notification_settings": "Notifikationsindstillinger", - "notification_settings_description": "Administrer notifikationsindstillinger, inklusiv email", - "oauth_auto_launch": "Auto-opstart", - "oauth_auto_launch_description": "Påbegynd OAuth login-flow automatisk når loginsiden tilgås", - "oauth_auto_register": "Autoregistrér", - "oauth_auto_register_description": "Registrér automatisk nye brugere efter at have logget ind med OAuth", - "oauth_button_text": "Knaptekst", - "oauth_client_secret_description": "Påkrævet hvis PKCE (Proof Key for Code Exchange) ikke er supporteret af OAuth-udbyderen", - "oauth_enable_description": "Log ind med OAuth", - "oauth_mobile_redirect_uri": "Mobilomdiregerings-URL", - "oauth_mobile_redirect_uri_override": "Tilsidesættelse af mobil omdiregerings-URL", - "oauth_mobile_redirect_uri_override_description": "Aktiver, når OAuth-udbyderen ikke tillader en mobil URI, som ''{callback}''", - "oauth_role_claim": "Rolle attribut", - "oauth_role_claim_description": "Tildel automatisk admin adgang på basis af forekomst af denne påstand. Dén kan være enten 'user' eller 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Administrer OAuth login-indstillinger", - "oauth_settings_more_details": "Læs flere detaljer om funktionen i dokumentationen.", - "oauth_storage_label_claim": "Lagringsmærkat fordring", - "oauth_storage_label_claim_description": "Sæt automatisk brugerens lagringsmærkat til denne fordrings værdi.", - "oauth_storage_quota_claim": "Lagringskvotefordring", - "oauth_storage_quota_claim_description": "Sæt automatisk brugeres lagringskvote til denne nye fordrings værdi.", - "oauth_storage_quota_default": "Standard lagringskvote (GiB)", - "oauth_storage_quota_default_description": "Kvote i GiB som bruges, når der ikke bliver oplyst en fordring.", - "oauth_timeout": "Forespørgslen udløb", - "oauth_timeout_description": "Udløbstid for forespørgsel i milisekunder", - "ocr_job_description": "Brug maskinlæring til at genkende tekst i billeder", - "password_enable_description": "Log ind med email og adgangskode", - "password_settings": "Adgangskodelogin", - "password_settings_description": "Administrer indstillinger for adgangskodelogin", - "paths_validated_successfully": "Alle stier valideret med succes", - "person_cleanup_job": "Person-oprydning", - "queue_details": "Kø-detaljer", - "queues": "Opgavekøer", - "queues_page_description": "Side med administrator-opgavekøer", - "quota_size_gib": "Kvotestørrelse (GiB)", - "refreshing_all_libraries": "Opdaterer alle biblioteker", - "registration": "Administratorregistrering", - "registration_description": "Da du er den første bruger i systemet, får du tildelt rollen som administrator og ansvar for administration og oprettelsen af nye brugere.", - "remove_failed_jobs": "Fjern mislykkede opgaver", - "require_password_change_on_login": "Kræv at brugeren skifter adgangskode ved første login", - "reset_settings_to_default": "Nulstil indstillingerne til standard", - "reset_settings_to_recent_saved": "Nulstil indstillinger til de senest gemte indstillinger", - "scanning_library": "Scanner bibliotek", - "search_jobs": "Søg opgaver…", - "send_welcome_email": "Send velkomst-email", - "server_external_domain_settings": "Eksternt domæne", - "server_external_domain_settings_description": "Domæne til offentligt delte links, inklusiv http(s)://", - "server_public_users": "Offentlige brugere", - "server_public_users_description": "Alle brugere (navn og e-mail) vises, når en bruger tilføjes til delte album. Når den er deaktiveret, vil brugerlisten kun være tilgængelig for administratorbrugere.", - "server_settings": "Serverindstillinger", - "server_settings_description": "Administrér serverindstillinger", - "server_stats_page_description": "Admin server statistikside", - "server_welcome_message": "Velkomstbesked", - "server_welcome_message_description": "En besked som bliver vist på loginsiden.", - "settings_page_description": "Admin-indstillinger side", - "sidecar_job": "Medfølgende metadata", - "sidecar_job_description": "Opdag eller synkroniser medfølgende metadata fra filsystemet", - "slideshow_duration_description": "Antal sekunder at vise hvert billede", - "smart_search_job_description": "Kør maskinlæring på mediefiler for at understøtte smart søgning", - "storage_template_date_time_description": "Mediefilens oprettelsesregistrering er grundlag for dato-tid-informationen", - "storage_template_date_time_sample": "Eksempel tid {date}", - "storage_template_enable_description": "Slå lagringsskabelonredskab til", - "storage_template_hash_verification_enabled": "Hash-verifikation slået til", - "storage_template_hash_verification_enabled_description": "Slår hash-verifikation til, slå ikke dette fra med mindre du er sikker på dets konsekvenser", - "storage_template_migration": "Lagringsskabelonmigration", - "storage_template_migration_description": "Anvend den nuværende {template} på tidligere uploadede mediefiler", - "storage_template_migration_info": "Lager-skabelonen vil konvertere alle filendelser til små bogstaver. Skabelonændringer vil kun gælde for nye mediefiler. For at anvende skabelonen retroaktivt på tidligere uploadede mediefiler skal du køre {job}.", - "storage_template_migration_job": "Lager Skabelon Migreringsjob", - "storage_template_more_details": "For flere detaljer om denne funktion, referer til Lager Skabelonen og dens implikationer", - "storage_template_onboarding_description_v2": "Når aktiveret, så vil denne funktion auto-organisere filer på grundlag af en brugerdefineret skabelon. For nærmere, se dokumentation.", - "storage_template_path_length": "Anslået sti-længde begrænsning {length, number}/{limit, number}", - "storage_template_settings": "Lagringsskabelon", - "storage_template_settings_description": "Administrer mappestrukturen og filnavnet for den uploadede mediefil", - "storage_template_user_label": "{label} er brugerens Lagringsmærkat", - "system_settings": "Systemindstillinger", - "tag_cleanup_job": "\"Tag\"-oprydning", - "template_email_available_tags": "Du kan bruge følgende variabler i din skabelon: {tags}", - "template_email_if_empty": "Hvis skabelonen er tom, vil standard-e-mailen blive brugt.", - "template_email_invite_album": "Inviterings albumskabelon", - "template_email_preview": "Forhåndsvisning", - "template_email_settings": "Email skabeloner", - "template_email_update_album": "Opdater albumskabelon", - "template_email_welcome": "Velkomst e-mail skabelon", - "template_settings": "Notifikations skabeloner", - "template_settings_description": "Administrer tilpassede skabeloner for notifikationer", - "theme_custom_css_settings": "Brugerdefineret CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets tillader at give Immich et brugerdefineret look.", - "theme_settings": "Temaindstillinger", - "theme_settings_description": "Administrér brugertilpasningen af Immich's webinterface", - "thumbnail_generation_job": "Generér miniaturebilleder", - "thumbnail_generation_job_description": "Generér store, små og slørede miniaturebilleder for hver mediefil, såvel som miniaturebilleder for hver person", - "transcoding_acceleration_api": "Accelerations-API", - "transcoding_acceleration_api_description": "API'en som interagerer med din enhed for at accelerere transkodning. Denne er indstilling er \"i bedste fald\": Den vil falde tilbage til software-transkodning ved svigt. VP9 virker måske, måske ikke, afhængigt af dit hardware.", - "transcoding_acceleration_nvenc": "NVENC (kræver NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (kræver 7. generation Intel CPU eller senere)", - "transcoding_acceleration_rkmpp": "RKMPP (kun på Rockchip SOC'er)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Accepterede lyd-codecs", - "transcoding_accepted_audio_codecs_description": "Vælg hvilke lyd-codecs der ikke behøver at blive transkodet. Bruges kun ved bestemte transkodningspolitikker.", - "transcoding_accepted_containers": "Accepterede containere", - "transcoding_accepted_containers_description": "Vælg hvilke containerformater der ikke skal remuxes til MP4. Bruges kun for visse transkodningspolitiker.", - "transcoding_accepted_video_codecs": "Accepterede video-codecs", - "transcoding_accepted_video_codecs_description": "Vælg hvilke video-codec der ikke behøver at bliver transkodet. Bruges kun ved bestemte transkodningspolitikker.", - "transcoding_advanced_options_description": "Indstillinger de fleste brugere ikke behøver at ændre på", - "transcoding_audio_codec": "Lyd-codec", - "transcoding_audio_codec_description": "Opus er den indstillingen med højest kvalitet, men har mindre kompatibilitet med gamle enheder og software.", - "transcoding_bitrate_description": "Videoer med højere end maksimumbitrate eller ikke i et godkendt format", - "transcoding_codecs_learn_more": "For at lære mere om den terminologi der bruges her, henvises til FFmpeg dokumentationen for H.264 codec, HEVC codec og VP9 codec.", - "transcoding_constant_quality_mode": "Konstant kvalitetstilstand", - "transcoding_constant_quality_mode_description": "ICQ er bedre end CQP, men nogle hardwareaccelerationsenheder understøtter ikke denne tilstand. At slå denne indstilling til vil foretrække den specificerede tilstand når kvalitetsbaseret indkodning bruges. Ignoreret at NVENC, da det ikke understøtter ICQ.", - "transcoding_constant_rate_factor": "Konstant ratefaktor (-crf)", - "transcoding_constant_rate_factor_description": "Videokvalitetsniveau. Typiske værdier er 23 for H.264, 28 for HEVC, 31 for VP9 og 35 for AV1. Lavere er bedre, men producerer større filer.", - "transcoding_disabled_description": "Lad være med at transkode nogen videoer, kan ødelægge playback hos nogle clients", - "transcoding_encoding_options": "Kodningsmuligheder", - "transcoding_encoding_options_description": "Indstil codecs, opløsning, kvalitet og andre muligheder for de kodede videoer", - "transcoding_hardware_acceleration": "Hardwareacceleration", - "transcoding_hardware_acceleration_description": "Eksperimentel: hurtigere transkodning men kan sænke kvaliteten ved samme bitrate", - "transcoding_hardware_decoding": "Hardware-afkodning", - "transcoding_hardware_decoding_setting_description": "Gælder kun NVENC, QSV og RKMPP. Slår ende-til-ende acceleration til i stedet for kun at accelerere indkodning. Virker måske ikke på alle videoer.", - "transcoding_max_b_frames": "Maksimum B-frames", - "transcoding_max_b_frames_description": "Højere værdier forbedrer kompressionseffektivitet, men kan gøre indkodning langsommere. Er måske ikke kompatibelt med hardware-acceleration på ældre enheder. 0 slår B-frames fra, mens -1 sætter denne værdi automatisk.", - "transcoding_max_bitrate": "Maksimal bitrate", - "transcoding_max_bitrate_description": "Indstilling af en maksimal bitrate kan gøre filstørrelser mere forudsigelige, men med et mindre fald i kvaliteten. Ved 720p er typiske værdier 2600 kbit/s for VP9 eller HEVC eller 4500 kbit/s for H.264. Deaktiveret, hvis den er indstillet til 0. Når der ikke er angivet nogen enhed, antages k (for kbit/s); derfor er 5000, 5000k og 5M (for Mbit/s) ækvivalente.", - "transcoding_max_keyframe_interval": "Maksimal keyframe-interval", - "transcoding_max_keyframe_interval_description": "Sætter den maksimale frameafstand mellem keyframes. Lavere værdier forringer kompressionseffektiviteten, men forbedrer søgetider og kan forbedre kvaliteten i scener med hurtig bevægelse. 0 sætter denne værdi automatisk.", - "transcoding_optimal_description": "Videoer højere end målopløsningen eller ikke i et godkendt format", - "transcoding_policy": "Omkodningspolitik", - "transcoding_policy_description": "Indstil, hvornår en video skal omkodes", - "transcoding_preferred_hardware_device": "Foretrukne hardwareenhed", - "transcoding_preferred_hardware_device_description": "Gælder kun VAAPI og QSV. Sætter dri node'n som bruges til hardware-transkodning.", - "transcoding_preset_preset": "Forudindstilling (-preset)", - "transcoding_preset_preset_description": "Kompressionshastighed. Langsommere forudindstillinger producerer mindre filer, og øger kvalitet når der gås efter en specifik bitrate. VP9 ignorerer hastigheder hurtigere end \"hurtigere\".", - "transcoding_reference_frames": "Referencerammer", - "transcoding_reference_frames_description": "Antallet af frames, der skal refereres til, når en given frame komprimeres. Højere værdier forbedrer kompressionseffektiviteten, men gør indkodning langsommere. 0 sætter denne værdi automatisk.", - "transcoding_required_description": "Kun videoer ikke i et godkendt format", - "transcoding_settings": "Videotranskodningsindstillinger", - "transcoding_settings_description": "Administrer, hvilke videoer der skal omkodes, og hvordan de behandles", - "transcoding_target_resolution": "Målopløsning", - "transcoding_target_resolution_description": "Højere opløsninger kan bevare flere detaljer, men tager længere tid at indkode, har større filstørrelser og kan gøre appen mere sløv.", - "transcoding_temporal_aq": "Tidsmæssig AQ", - "transcoding_temporal_aq_description": "Gælder kun NVENC. Øger kvaliteten af scener med høj detalje, lav bevægelse. Er muligvis ikke kompatibel med ældre enheder.", - "transcoding_threads": "Tråde", - "transcoding_threads_description": "Højere værdier medfører hurtigere indkodning, men efterlader mindre plads til at serveren kan foretage andre opgaver når aktiv. Denne værdi bør ikke være større end antallet af CPU-kerner. Maksimerer udnyttelse hvis sat til 0.", - "transcoding_tone_mapping": "Tone-kortlægning", - "transcoding_tone_mapping_description": "Forsøger at bevare HDR-videoers udseende når konverteret til SDR. Hver algoritme har forskellige afvejninger af farve, detalje og lysstyrke. Hable bevarer farve og Reinhard bevarer lysstyrke.", - "transcoding_transcode_policy": "Transkodningspolitik", - "transcoding_transcode_policy_description": "Politik for hvornår en video skal transkodes. HDR videoer vil altid blive transkodet (bortset fra, hvis transkodning er slået fra).", - "transcoding_two_pass_encoding": "To-omgangsindkodning", - "transcoding_two_pass_encoding_setting_description": "Transkoder af to omgange for at producere bedre indkodede videoer. Når den maksimale bitrate er slået til (som det kræver for at det fungerer med H.264 og HEVC), bruger denne tilstand en bitrateinterval baseret på den maksimale birate og ignorerer CRF. For VP9, kan CRF bruges hvis den maksimale bitrate er slået fra.", - "transcoding_video_codec": "Videocodec", - "transcoding_video_codec_description": "VP9 har en højere effektivitet og webkompatibilitet, men indkodningen tager længere tid. HEVC har lignende ydelse, men har lavere webkompatibilitet og er hurtig at transkode, men giver meget større filer. AV1 er det mest effektive codec, men mangler understøttelse på ældre enheder.", - "trash_enabled_description": "Aktivér \"Papirkurvs\"-funktioner", - "trash_number_of_days": "Antal dage", - "trash_number_of_days_description": "Antal dage elementer i papirkurven skal beholdes inden de fjernes permanent", - "trash_settings": "Papirkurvs-indstillinger", - "trash_settings_description": "Administrér papirkurvs-indstillinger", - "unlink_all_oauth_accounts": "Ophæv link til alle OAuth konti", - "unlink_all_oauth_accounts_description": "Husk at fjerne linket til alle OAuth konti før du migrerer til en ny udbyder.", - "unlink_all_oauth_accounts_prompt": "Er du sikker på, at du vil ophæve link til alle OAuth konti? Dette vil nulstille OAuth ID for hver bruger og kan ikke fortrydes.", - "user_cleanup_job": "Bruger-oprydning", - "user_delete_delay": "{user}'s konto og mediefiler vil blive planlagt til permanent sletning om {delay, plural, one {# dag} other {# dage}}.", - "user_delete_delay_settings": "Slet forsinkelse", - "user_delete_delay_settings_description": "Antal dage efter fjernelse for permanent at slette en brugers konto og mediefiler. Opgaven for sletning af brugere kører ved midnat for at tjekke efter brugere, der er klar til sletning. Ændringer i denne indstilling vil blive evalueret ved næste udførelse.", - "user_delete_immediately": "{user}'s konto og aktiver vil blive sat i kø til permanent sletning med det samme.", - "user_delete_immediately_checkbox": "Sæt bruger og aktiver i kø til øjeblikkelig sletning", - "user_details": "Brugeroplysninger", - "user_management": "Brugeradministration", - "user_password_has_been_reset": "Brugerens adgangskode er blevet nulstillet:", - "user_password_reset_description": "Venligst oplys brugeren om den midlertidige adgangskode og informér dem, at de vil være nødt til at ændre adgangskoden ved næste login.", - "user_restore_description": "{user}'s konto vil blive gendannet.", - "user_restore_scheduled_removal": "Gendan bruger - planlagt fjernelse den {date, date, long}", - "user_settings": "Brugerindstillinger", - "user_settings_description": "Administrér brugerindstillinger", - "user_successfully_removed": "Bruger {email} er blevet fjernet med succes.", - "users_page_description": "Admin-brugere side", - "version_check_enabled_description": "Aktivér versionstjek", - "version_check_implications": "Funktionen til versionstjek er afhængig af periodisk kommunikation med github.com", - "version_check_settings": "Versionstjek", - "version_check_settings_description": "Aktiver/deaktiverer notifikation for den nye version", - "video_conversion_job": "Transkod videoer", - "video_conversion_job_description": "Transkod videoer for bredere kompatibilitet med browsere og enheder" - }, - "admin_email": "Administrator-email", - "admin_password": "Administratoradgangskode", - "administration": "Administration", - "advanced": "Avanceret", - "advanced_settings_clear_image_cache": "Ryd billedcache", - "advanced_settings_clear_image_cache_error": "Billedcachen kunne ikke ryddes", - "advanced_settings_clear_image_cache_success": "Ryddet {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Brug denne valgmulighed for at filtrere media under synkronisering baseret på alternative kriterier. Prøv kun denne, hvis du har problemer med, at appen ikke opdager alle albums.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTEL] Brug alternativ enheds album synkroniserings filter", - "advanced_settings_log_level_title": "Logniveau: {level}", - "advanced_settings_prefer_remote_subtitle": "Nogle enheder er meget lang tid om at indlæse miniaturebilleder af lokale elementer. Aktiver denne indstilling for at indlæse elementer fra serveren i stedet.", - "advanced_settings_prefer_remote_title": "Foretræk elementer på serveren", - "advanced_settings_proxy_headers_subtitle": "Definer proxy headers Immich skal sende med hver netværks forespørgsel", - "advanced_settings_proxy_headers_title": "Tilpasset proxy headere [EKSPERIMENTALT]", - "advanced_settings_readonly_mode_subtitle": "Aktiverer skrivebeskyttet tilstand, hvor billederne alene kan vises. Ting som at vælge flere billeder, dele, caste og slette er alle deaktiveret. Aktiver skrivebeskyttet tilstand via en bruger avatar fra hovedskærmen", - "advanced_settings_readonly_mode_title": "Skrivebeskyttet tilstand", - "advanced_settings_self_signed_ssl_subtitle": "Spring verificering af SSL-certifikat over for serverens endelokation. Kræves for selvsignerede certifikater.", - "advanced_settings_self_signed_ssl_title": "Tillad selvsignerede SSL certifikater [EKSPERIMENTALT]", - "advanced_settings_sync_remote_deletions_subtitle": "Slet eller gendan automatisk en mediefil på denne enhed, når denne handling foretages på Immich webinterface", - "advanced_settings_sync_remote_deletions_title": "Synkroniser fjernsletninger [EKSPERIMENTELT]", - "advanced_settings_tile_subtitle": "Avancerede brugerindstillinger", - "advanced_settings_troubleshooting_subtitle": "Slå ekstra funktioner for fejlsøgning til", - "advanced_settings_troubleshooting_title": "Fejlsøgning", - "age_months": "{months, plural, one {# måned} other {# måneder}} gammel", - "age_year_months": "1 år, {months, plural, one {# måned} other {# måneder}} gammel", - "age_years": "{years, plural, other {# år}}", - "album": "Album", - "album_added": "Album tilføjet", - "album_added_notification_setting_description": "Modtag en emailnotifikation når du bliver tilføjet til en delt album", - "album_cover_updated": "Albumcover opdateret", - "album_delete_confirmation": "Er du sikker på at du vil slette albummet {album}?", - "album_delete_confirmation_description": "Hvis dette album er delt, vil andre brugere ikke længere kunne få adgang til det.", - "album_deleted": "Album slettet", - "album_info_card_backup_album_excluded": "EKSKLUDERET", - "album_info_card_backup_album_included": "INKLUDERET", - "album_info_updated": "Albuminfo opdateret", - "album_leave": "Forlad albummet?", - "album_leave_confirmation": "Er du sikker på at du vil forlade {album}?", - "album_name": "Albumnavn", - "album_options": "Albumindstillinger", - "album_remove_user": "Fjern bruger?", - "album_remove_user_confirmation": "Er du sikker på at du vil fjerne {user}?", - "album_search_not_found": "Ingen album fundet som matcher din søgning", - "album_selected": "Album valgt", - "album_share_no_users": "Det ser ud til at du har delt denne album med alle brugere, eller du har ikke nogen brugere til at dele med.", - "album_summary": "Albumoversigt", - "album_updated": "Album opdateret", - "album_updated_setting_description": "Modtag en emailnotifikation når et delt album får nye mediefiler", - "album_upload_assets": "Upload filer fra din computer og tilføj dem til album", - "album_user_left": "Forlod {album}", - "album_user_removed": "Fjernede {user}", - "album_viewer_appbar_delete_confirm": "Er du sikker på, du vil slette dette album fra din bruger?", - "album_viewer_appbar_share_err_delete": "Fejlede sletning af album", - "album_viewer_appbar_share_err_leave": "Fejlede i at forlade album", - "album_viewer_appbar_share_err_remove": "Der er problemer med at fjerne elementer fra album", - "album_viewer_appbar_share_err_title": "Fejlede i at ændre albumtitel", - "album_viewer_appbar_share_leave": "Forlad album", - "album_viewer_appbar_share_to": "Del til", - "album_viewer_page_share_add_users": "Tilføj brugere", - "album_with_link_access": "Lad alle med linket se billeder og personer i dette album.", - "albums": "Albummer", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albummer}}", - "albums_default_sort_order": "Standard album sortering", - "albums_default_sort_order_description": "Grundlæggende sortering ved oprettelse af nyt album.", - "albums_feature_description": "Samling af billeder der kan deles med andre brugere.", - "albums_on_device_count": "Albummer på enheden ({count})", - "albums_selected": "{count, plural, one {# album valgt} other {# valgte albummer}}", - "all": "Alt", - "all_albums": "Alle albummer", - "all_people": "Alle personer", - "all_photos": "Alle billeder", - "all_videos": "Alle videoer", - "allow_dark_mode": "Tillad mørk tilstand", - "allow_edits": "Tillad redigeringer", - "allow_public_user_to_download": "Tillad offentlige brugere til at hente", - "allow_public_user_to_upload": "Tillad offentlige brugere til at uploade", - "allowed": "Tilladt", - "alt_text_qr_code": "QR-kode billede", - "always_keep": "Opbevar altid", - "always_keep_photos_hint": "Frigør plads vil bevare alle billeder på denne enhed.", - "always_keep_videos_hint": "Frigør plads vil bevare alle videoer på denne enhed.", - "anti_clockwise": "Mod uret", - "api_key": "API-nøgle", - "api_key_description": "Denne værdi vises kun én gang. Venligst kopiér den før du lukker vinduet.", - "api_key_empty": "Din API-nøgle-navn burde ikke være tom", - "api_keys": "API-nøgler", - "app_architecture_variant": "Variant (Arkitektur)", - "app_bar_signout_dialog_content": "Er du sikker på, du vil logge ud?", - "app_bar_signout_dialog_ok": "Ja", - "app_bar_signout_dialog_title": "Log ud", - "app_download_links": "App Download Links", - "app_settings": "Appindstillinger", - "app_stores": "App Butikker", - "app_update_available": "App opdatering er tilgængelig", - "appears_in": "Optræder i", - "apply_count": "Brug ({count, number})", - "archive": "Arkiv", - "archive_action_prompt": "{count} føjet til arkiv", - "archive_or_unarchive_photo": "Arkivér eller fjern billede fra arkiv", - "archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet", - "archive_page_title": "Arkivér ({count})", - "archive_size": "Arkivstørrelse", - "archive_size_description": "Konfigurer arkivstørrelsen for downloads (i GiB)", - "archived": "Arkiveret", - "archived_count": "{count, plural, other {# arkiveret}}", - "are_these_the_same_person": "Er disse den samme person?", - "are_you_sure_to_do_this": "Er du sikker på, at du vil gøre det her?", - "array_field_not_fully_supported": "Arrayfelter kræver manuel JSON-redigering", - "asset_action_delete_err_read_only": "Kan ikke slette kun læselige elementer. Springer over", - "asset_action_share_err_offline": "Kan ikke hente offline element(er). Springer over", - "asset_added_to_album": "Tilføjet til album", - "asset_adding_to_album": "Tilføjer til album…", - "asset_created": "Mediefil oprettet", - "asset_description_updated": "Mediefilsbeskrivelse er blevet opdateret", - "asset_filename_is_offline": "Mediefil {filename} er offline", - "asset_has_unassigned_faces": "Aktivet har ikke-tildelte ansigter", - "asset_hashing": "Hasher…", - "asset_list_group_by_sub_title": "Gruppér efter", - "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", - "asset_list_layout_settings_group_automatically": "Automatisk", - "asset_list_layout_settings_group_by": "Grupper elementer efter", - "asset_list_layout_settings_group_by_month_day": "Måned + dag", - "asset_list_layout_sub_title": "Udseende", - "asset_list_settings_subtitle": "Indstillinger for billedgitterlayout", - "asset_list_settings_title": "Billedgitter", - "asset_offline": "Mediefil offline", - "asset_offline_description": "Denne eksterne mediefil kan ikke længere findes på drevet. Kontakt venligst din Immich-administrator for hjælp.", - "asset_restored_successfully": "Elementet blev gendannet succesfuldt", - "asset_skipped": "Sprunget over", - "asset_skipped_in_trash": "I papirkurv", - "asset_trashed": "Objekt kasseret", - "asset_troubleshoot": "Fejlsøg på objekt", - "asset_uploaded": "Uploadet", - "asset_uploading": "Uploader…", - "asset_viewer_settings_subtitle": "Administrer indstillinger for gallerifremviser", - "asset_viewer_settings_title": "Billedviser", - "assets": "Objekter", - "assets_added_count": "Tilføjet {count, plural, one {# mediefil} other {# mediefiler}}", - "assets_added_to_album_count": "{count, plural, one {# mediefil} other {# mediefiler}} tilføjet til albummet", - "assets_added_to_albums_count": "Tilføjet {assetTotal, plural, one {# asset} other {# assets}} til {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Billed} other {Billeder}} kan ikke blive tilføjet til album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} kan ikke føjes til i nogen af albummerne", - "assets_count": "{count, plural, one {# mediefil} other {# mediefiler}}", - "assets_deleted_permanently": "{count} element(er) blev fjernet permanent", - "assets_deleted_permanently_from_server": "{count} element(er) blev fjernet permanent fra Immich serveren", - "assets_downloaded_failed": "{count, plural, one {Downloaded # fil - {error} fil fejlede} other {Downloaded # filer - {error} filer fejlede}}", - "assets_downloaded_successfully": "{count, plural, one {Downloaded # fil med succes} other {Downloaded # filer med succes}}", - "assets_moved_to_trash_count": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til papirkurven", - "assets_permanently_deleted_count": "{count, plural, one {# mediefil} other {# mediefiler}} slettet permanent", - "assets_removed_count": "Fjernede {count, plural, one {# mediefil} other {# mediefiler}}", - "assets_removed_permanently_from_device": "{count} element(er) blev fjernet permanent fra din enhed", - "assets_restore_confirmation": "Er du sikker på, at du vil gendanne alle dine mediafiler i papirkurven? Du kan ikke fortryde denne handling! Bemærk, at offline mediefiler ikke kan gendannes på denne måde.", - "assets_restored_count": "{count, plural, one {# mediefil} other {# mediefiler}} gendannet", - "assets_restored_successfully": "{count} element(er) blev gendannet succesfuldt", - "assets_trashed": "{count} element(er) blev smidt i papirkurven", - "assets_trashed_count": "{count, plural, one {# mediefil} other {# mediefiler}} smidt i papirkurven", - "assets_trashed_from_server": "{count} element(er) blev smidt i Immich serverens papirkurv", - "assets_were_part_of_album_count": "mediefil{count, plural, one {mediefil} other {mediefiler}} er allerede en del af albummet", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} er allerede en del af albummerne", - "authorized_devices": "Tilladte enheder", - "automatic_endpoint_switching_subtitle": "Forbind lokalt over det anviste WiFi, når det er tilgængeligt og brug alternative forbindelser andre stæder", - "automatic_endpoint_switching_title": "Automatisk skift af URL", - "autoplay_slideshow": "Afspil slideshow automatisk", - "back": "Tilbage", - "back_close_deselect": "Tilbage, luk eller fravælg", - "background_backup_running_error": "Backup kører lige nu i baggrund; kan ikke starte manuel backup", - "background_location_permission": "Tilladelse til baggrundsplacering", - "background_location_permission_content": "For at skifte netværk, når appen kører i baggrunden, skal Immich *altid* have præcis placeringsadgang, så appen kan læse WiFi-netværkets navn", - "background_options": "Baggrundsmuligheder", - "backup": "Sikkerhedskopier", - "backup_album_selection_page_albums_device": "Albummer på enheden ({count})", - "backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere", - "backup_album_selection_page_assets_scatter": "Elementer kan være spredt på tværs af flere albummer. Albummer kan således inkluderes eller udelukkes under sikkerhedskopieringsprocessen.", - "backup_album_selection_page_select_albums": "Vælg albummer", - "backup_album_selection_page_selection_info": "Oplysninger om valgte", - "backup_album_selection_page_total_assets": "Samlede unikke elementer", - "backup_albums_sync": "Synkronisering af backupalbums", - "backup_all": "Alt", - "backup_background_service_backup_failed_message": "Sikkerhedskopiering af elementer fejlede. Forsøger igen…", - "backup_background_service_complete_notification": "Sikkerhedskopiering af aktiver fuldført", - "backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igen…", - "backup_background_service_current_upload_notification": "Uploader {filename}", - "backup_background_service_default_notification": "Søger efter nye elementer…", - "backup_background_service_error_title": "Fejl med sikkerhedskopiering", - "backup_background_service_in_progress_notification": "Tager sikkerhedskopi af dine elementer…", - "backup_background_service_upload_failure_notification": "Fejlede med uploade af {filename}", - "backup_controller_page_albums": "Sikkerhedskopiér albummer", - "backup_controller_page_background_app_refresh_disabled_content": "Slå baggrundsopdatering af applikationen til i Indstillinger > Generelt > Baggrundsopdatering af applikationer, for at bruge sikkerhedskopi i baggrunden.", - "backup_controller_page_background_app_refresh_disabled_title": "Baggrundsopdatering af app er slået fra", - "backup_controller_page_background_app_refresh_enable_button_text": "Gå til indstillinger", - "backup_controller_page_background_battery_info_link": "Vis mig hvordan", - "backup_controller_page_background_battery_info_message": "For den bedste oplevelse med sikkerhedskopiering i baggrunden, bør du slå batterioptimering, der begrænder baggrundsaktivitet, fra.\n\nSiden dette er afhængigt af enheden, bør du undersøge denne information leveret af din enheds producent.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Batterioptimering", - "backup_controller_page_background_charging": "Kun under opladning", - "backup_controller_page_background_configure_error": "Fejlede konfigureringen af sikkerhedskopiering i baggrunden", - "backup_controller_page_background_delay": "Udskyd sikkerhedskopi af nye elementer: {duration}", - "backup_controller_page_background_description": "Slå sikkerhedskopiering i baggrunden til, for automatisk at tage sikkerhedskopi af nye elementer, uden at skulle åbne appen", - "backup_controller_page_background_is_off": "Automatisk sikkerhedskopiering i baggrunden er slået fra", - "backup_controller_page_background_is_on": "Automatisk sikkerhedskopiering i baggrunden er slået til", - "backup_controller_page_background_turn_off": "Slå sikkerhedskopiering i baggrunden fra", - "backup_controller_page_background_turn_on": "Slå sikkerhedskopiering i baggrunden til", - "backup_controller_page_background_wifi": "Kun med Wi-Fi", - "backup_controller_page_backup": "Sikkerhedskopier", - "backup_controller_page_backup_selected": "Valgte: ", - "backup_controller_page_backup_sub": "Sikkerhedskopierede billeder og videoer", - "backup_controller_page_created": "Oprettet den: {date}", - "backup_controller_page_desc_backup": "Slå sikkerhedskopiering til automatisk at uploade nye elementer til serveren.", - "backup_controller_page_excluded": "Ekskluderet: ", - "backup_controller_page_failed": "Fejlet ({count})", - "backup_controller_page_filename": "Filnavn: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Sikkerhedskopieringsinformation", - "backup_controller_page_none_selected": "Ingen valgte", - "backup_controller_page_remainder": "Tilbageværende", - "backup_controller_page_remainder_sub": "Tilbageværende billeder og albummer, at sikkerhedskopiere, fra valgte", - "backup_controller_page_server_storage": "Serverplads", - "backup_controller_page_start_backup": "Start sikkerhedskopiering", - "backup_controller_page_status_off": "Sikkerhedskopiering er slået fra", - "backup_controller_page_status_on": "Sikkerhedskopiering er slået til", - "backup_controller_page_storage_format": "{used} af {total} brugt", - "backup_controller_page_to_backup": "Albummer at sikkerhedskopiere", - "backup_controller_page_total_sub": "Alle unikke billeder og videoer fra valgte albummer", - "backup_controller_page_turn_off": "Slå sikkerhedskopiering fra", - "backup_controller_page_turn_on": "Slå sikkerhedskopiering til", - "backup_controller_page_uploading_file_info": "Uploader filinformation", - "backup_err_only_album": "Kan ikke slette det eneste album", - "backup_error_sync_failed": "Synkroniseringen mislykkedes. Sikkerhedskopieringen kunne ikke behandles.", - "backup_info_card_assets": "objekter", - "backup_manual_cancelled": "Annulleret", - "backup_manual_in_progress": "Upload er allerede undervejs. Prøv igen efter noget tid", - "backup_manual_success": "Succes", - "backup_manual_title": "Uploadstatus", - "backup_options": "Backup indstillinger", - "backup_options_page_title": "Backupindstillinger", - "backup_setting_subtitle": "Administrer indstillnger for upload i forgrund og baggrund", - "backup_settings_subtitle": "Håndtere upload indstillinger", - "backup_upload_details_page_more_details": "Tryk for flere detaljer", - "backward": "Baglæns", - "biometric_auth_enabled": "Biometrisk adgangskontrol slået til", - "biometric_locked_out": "Du er låst ude af biometrisk adgangskontrol", - "biometric_no_options": "Ingen biometrisk adgangskontrol tilgængelig", - "biometric_not_available": "Biometrisk adgangskontrol er ikke tilgængelig på denne enhed", - "birthdate_saved": "Fødselsdatoen blev gemt", - "birthdate_set_description": "Fødselsdato bruges til at beregne denne persons alder på det tidspunkt, et billede er taget.", - "blurred_background": "Sløret baggrund", - "bugs_and_feature_requests": "Fejl & forbedringsønsker", - "build": "Byg", - "build_image": "Byggefil", - "bulk_delete_duplicates_confirmation": "Er du sikker på, at du vil slette alle {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil beholde den største fil i hver gruppe og slette alle dubletter. Denne handling kan ikke fortrydes!", - "bulk_keep_duplicates_confirmation": "Er du sikker på, at du vil beholde {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil løse alle dubletgrupper uden at slette noget.", - "bulk_trash_duplicates_confirmation": "Er du sikker på, at du vil masseslette {count, plural, one {# duplikeret objekt} other {# duplikerede objekter}}? Dette vil beholde det største objekt i hver gruppe og slette alle andre dubletter.", - "buy": "Køb Immich", - "cache_settings_clear_cache_button": "Fjern cache", - "cache_settings_clear_cache_button_title": "Fjern appens cache. Dette vil i stor grad påvirke appens ydeevne indtil cachen er genopbygget.", - "cache_settings_duplicated_assets_clear_button": "RYD", - "cache_settings_duplicated_assets_subtitle": "Billeder og videoer der er ignoreres af appen", - "cache_settings_duplicated_assets_title": "Dublikerede elementer ({count})", - "cache_settings_statistics_album": "Biblioteksminiaturer", - "cache_settings_statistics_full": "Fulde billeder", - "cache_settings_statistics_shared": "Miniaturebilleder til delte albummer", - "cache_settings_statistics_thumbnail": "Miniaturebilleder", - "cache_settings_statistics_title": "Cacheforbrug", - "cache_settings_subtitle": "Håndter cache-adfærden for Immich-appen", - "cache_settings_tile_subtitle": "Kontroller den lokale lagerplads", - "cache_settings_tile_title": "Lokal lagerplads", - "cache_settings_title": "Cache-indstillinger", - "camera": "Kamera", - "camera_brand": "Kameramærke", - "camera_model": "Kameramodel", - "cancel": "Annullér", - "cancel_search": "Annullér søgning", - "canceled": "Annulleret", - "canceling": "Annullerer", - "cannot_merge_people": "Kan ikke sammenflette personer", - "cannot_undo_this_action": "Du kan ikke fortryde denne handling!", - "cannot_update_the_description": "Kan ikke opdatere beskrivelsen", - "cast": "Caste", - "cast_description": "Konfigurer tilgængelige cast destinationer", - "change_date": "Ændr dato", - "change_description": "Ændr beskrivelse", - "change_display_order": "Ændrer visningsrækkefølge", - "change_expiration_time": "Ændr udløbstidspunkt", - "change_location": "Ændr sted", - "change_name": "Ændr navn", - "change_name_successfully": "Navneændring lykkedes", - "change_password": "Skift kodeord", - "change_password_description": "Dette er enten første gang du tilmelder dig, eller en ændring af kodeordet blev bestilt. Indtast dit nye kodeord herunder.", - "change_password_form_confirm_password": "Bekræft kodeord", - "change_password_form_description": "Hej {name},\n\nDette er enten første gang du logger ind eller også er der lavet en anmodning om at ændre dit kodeord. Indtast venligst et nyt kodeord nedenfor.", - "change_password_form_log_out": "Log ud af alle andre enheder", - "change_password_form_log_out_description": "Det er anbefalet at logge ud af alle andre enheder", - "change_password_form_new_password": "Nyt kodeord", - "change_password_form_password_mismatch": "Kodeord er ikke ens", - "change_password_form_reenter_new_password": "Gentag nyt kodeord", - "change_pin_code": "Skift PIN kode", - "change_trigger": "Skift udløser", - "change_trigger_prompt": "Er du sikker på, at du vil ændre udløseren? Dette vil fjerne alle eksisterende handlinger og filtre.", - "change_your_password": "Skift dit kodeord", - "changed_visibility_successfully": "Synlighed blev ændret", - "charging": "Lader", - "charging_requirement_mobile_backup": "Baggrundsbackup kræver, at enheden er tilsluttet oplader", - "check_corrupt_asset_backup": "Tjek for korrupte sikkerhedskopier af elementer", - "check_corrupt_asset_backup_button": "Foretag kontrol", - "check_corrupt_asset_backup_description": "Kør kun denne kontrol via Wi-Fi, og når alle elementer er blevet sikkerhedskopieret. Proceduren kan tage et par minutter.", - "check_logs": "Tjek logfiler", - "checksum": "Checksum", - "choose_matching_people_to_merge": "Vælg matchende personer til sammenfletning", - "city": "By", - "cleanup_confirm_description": "Immich fandt {count} assets (oprettet før {date}) sikkert sikkerhedskopieret til serveren. Fjern de lokale kopier fra denne enhed?", - "cleanup_confirm_prompt_title": "Fjern fra denne enhed?", - "cleanup_deleted_assets": "Flyttede {count} filer til enhedens skraldespand", - "cleanup_deleting": "Flytter til skraldespand...", - "cleanup_found_assets": "Fandt {count} sikkerhedskopierede filer", - "cleanup_found_assets_with_size": "Fundet {count} sikkerhedskopierede objekter ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud delte albummer er udelukket fra scanningen", - "cleanup_no_assets_found": "Ingen sikkerhedskopierede filer fundet der matcher dine kriterier", - "cleanup_preview_title": "Filer at fjerne ({count})", - "cleanup_step3_description": "Scan efter fotos og videoer, der er blevet sikkerhedskopieret til serveren med den valgte stop-dato og filtermuligheder", - "cleanup_step4_summary": "{count} filer lavet før {date} er i kø for at blive fjernet fra denne enhed", - "cleanup_trash_hint": "For at genvinde lagringsplads helt, skal du åbne din indbyggede galleriapp og tømme papirkurven", - "clear": "Ryd", - "clear_all": "Ryd alle", - "clear_all_recent_searches": "Ryd alle seneste søgninger", - "clear_file_cache": "Ryd filcache", - "clear_message": "Ryd bedsked", - "clear_value": "Ryd værdi", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Indtast Kodeord", - "client_cert_import": "Importer", - "client_cert_import_success_msg": "Klient certifikat er importeret", - "client_cert_invalid_msg": "Invalid certifikat fil eller forkert adgangskode", - "client_cert_remove_msg": "Klient certifikat er fjernet", - "client_cert_subtitle": "Supportere kun PKCS12 (.p12, .pfx) format. Certifikat importering/fjernelse er kun tilgængeligt før login", - "client_cert_title": "SSL Klient Certifikat [EKSPERIMENTAL]", - "clockwise": "Med uret", - "close": "Luk", - "collapse": "Klap sammen", - "collapse_all": "Klap alle sammen", - "color": "Farve", - "color_theme": "Farvetema", - "command": "Kommando", - "comment_deleted": "Kommentar slettet", - "comment_options": "Kommentarindstillinger", - "comments_and_likes": "Kommentarer og likes", - "comments_are_disabled": "Kommentarer er slået fra", - "common_create_new_album": "Opret et nyt album", - "completed": "Fuldført", - "confirm": "Bekræft", - "confirm_admin_password": "Bekræft administratoradgangskode", - "confirm_delete_face": "Er du sikker på, du vil slette {name}s ansigt fra denne mediefil?", - "confirm_delete_shared_link": "Er du sikker på, at du vil slette dette delte link?", - "confirm_keep_this_delete_others": "Alle andre aktiver i stakken vil blive slettet undtagen dette aktiv. Er du sikker på, at du vil fortsætte?", - "confirm_new_pin_code": "Bekræft ny PIN kode", - "confirm_password": "Bekræft adgangskode", - "confirm_tag_face": "Vil du markere dette ansigt som {name}?", - "confirm_tag_face_unnamed": "Vil du markere dette ansigt?", - "connected_device": "Forbundne enheder", - "connected_to": "Forbundet til", - "contain": "Inddæm", - "context": "Kontekst", - "continue": "Fortsæt", - "control_bottom_app_bar_create_new_album": "Opret nyt album", - "control_bottom_app_bar_delete_from_immich": "Slet fra Immich", - "control_bottom_app_bar_delete_from_local": "Slet fra enhed", - "control_bottom_app_bar_edit_location": "Rediger placering", - "control_bottom_app_bar_edit_time": "Rediger tid og dato", - "control_bottom_app_bar_share_link": "Del Link", - "control_bottom_app_bar_share_to": "Del til", - "control_bottom_app_bar_trash_from_immich": "Flyt til papirkurv", - "copied_image_to_clipboard": "Kopierede billede til clipboard.", - "copied_to_clipboard": "Kopieret til udklipsholder!", - "copy_error": "Kopifejl", - "copy_file_path": "Kopiér filsti", - "copy_image": "Kopiér billede", - "copy_link": "Kopiér link", - "copy_link_to_clipboard": "Kopiér link til udklipsholder", - "copy_password": "Kopier adgangskode", - "copy_to_clipboard": "Kopiér til udklipsholder", - "country": "Land", - "cover": "Omslag", - "covers": "Omslag", - "create": "Opret", - "create_album": "Opret album", - "create_album_page_untitled": "Uden titel", - "create_api_key": "Opret API nøgle", - "create_first_workflow": "Opret første workflow", - "create_library": "Opret bibliotek", - "create_link": "Opret link", - "create_link_to_share": "Opret link for at dele", - "create_link_to_share_description": "Tillad alle med linket at se de(t) valgte billede(r)", - "create_new": "OPRET NY", - "create_new_person": "Opret ny person", - "create_new_person_hint": "Tildel valgte aktiver til en ny person", - "create_new_user": "Opret ny bruger", - "create_shared_album_page_share_add_assets": "TILFØJ ELEMENT", - "create_shared_album_page_share_select_photos": "Vælg Billeder", - "create_shared_link": "Opret delt link", - "create_tag": "Opret tag", - "create_tag_description": "Opret et nyt tag. For indlejrede tags skal du indtaste den fulde sti til tagget inklusive skråstreger.", - "create_user": "Opret bruger", - "create_workflow": "Opret workflow", - "created": "Oprettet", - "created_at": "Oprettet", - "creating_linked_albums": "Opretter sammenkædede albums...", - "crop": "Beskær", - "crop_aspect_ratio_fixed": "Fikset", - "crop_aspect_ratio_free": "Gratis", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Ting", - "current_device": "Nuværende enhed", - "current_pin_code": "Nuværende PIN kode", - "current_server_address": "Nuværende serveraddresse", - "custom_date": "Brugerdefineret dato", - "custom_locale": "Brugerdefineret lokale", - "custom_locale_description": "Formatér datoer og tal baseret på sproget og regionen", - "custom_url": "Tilpasset URL", - "cutoff_date_description": "Fjern fotos og videoer ældre end", - "cutoff_day": "{antal, flertal, en {day} andre {days}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Mørk", - "dark_theme": "Skift til mørkt tema", - "date": "Dato", - "date_after": "Dato efter", - "date_and_time": "Dato og klokkeslæt", - "date_before": "Dato før", - "date_format": "E d. LLL y • hh:mm", - "date_of_birth_saved": "Fødselsdatoen blev gemt korrekt", - "date_range": "Datointerval", - "day": "Dag", - "days": "Dage", - "deduplicate_all": "Kopier alle", - "deduplication_criteria_1": "Billedstørrelse i bytes", - "deduplication_criteria_2": "Antal EXIF-data", - "deduplication_info": "Deduplikerings info", - "deduplication_info_description": "For automatisk at forudvælge emner og fjerne dubletter i bulk ser vi på:", - "default_locale": "Standardlokalitet", - "default_locale_description": "Formatér datoer og tal baseret på din browsers regions indstillinger", - "delete": "Slet", - "delete_action_confirmation_message": "Er du sikker på, at du vil slette dette objekt? Denne handling vil flytte objektet til serverens papirkurv, og vil spørge dig, om du vil slette den lokalt", - "delete_action_prompt": "{count} slettet", - "delete_album": "Slet album", - "delete_api_key_prompt": "Er du sikker på, at du vil slette denne API-nøgle?", - "delete_dialog_alert": "Disse elementer vil blive slettet permanent fra Immich og din enhed", - "delete_dialog_alert_local": "Disse elementer slettes permanent fra din enhed, men vil stadig være tilgængelige på serveren", - "delete_dialog_alert_local_non_backed_up": "Nogle af elementerne har ingen backup på serveren og vil blive slettet permanent fra din enhed", - "delete_dialog_alert_remote": "Disse elementer slettes permanent fra serveren", - "delete_dialog_ok_force": "Slet alligevel", - "delete_dialog_title": "Slet permanent", - "delete_duplicates_confirmation": "Er du sikker på, at du vil slette disse dubletter permanent?", - "delete_face": "Slet ansigt", - "delete_key": "Slet nøgle", - "delete_library": "Slet bibliotek", - "delete_link": "Slet link", - "delete_local_action_prompt": "{count} slettet lokalt", - "delete_local_dialog_ok_backed_up_only": "Slet kun backup", - "delete_local_dialog_ok_force": "Slet alligevel", - "delete_others": "Slet andre", - "delete_permanently": "Slet permanent", - "delete_permanently_action_prompt": "{count} slettet permanent", - "delete_shared_link": "Slet delt link", - "delete_shared_link_dialog_title": "Slet delt link", - "delete_tag": "Slet tag", - "delete_tag_confirmation_prompt": "Er du sikker på, at du vil slette {tagName}-tagget?", - "delete_user": "Slet bruger", - "deleted_shared_link": "Slettede delt link", - "deletes_missing_assets": "Sletter aktiver, der mangler fra disken", - "description": "Beskrivelse", - "description_input_hint_text": "Tilføj en beskrivelse...", - "description_input_submit_error": "Fejl med at opdatere beskrivelsen. Tjek loggen for flere detaljer", - "deselect_all": "Afmarkér alt", - "details": "DETALJER", - "direction": "Retning", - "disable": "Deaktiver", - "disabled": "Deaktiveret", - "disallow_edits": "Deaktivér redigeringer", - "discord": "Discord", - "discover": "Opdag", - "discovered_devices": "Opdaget enheder", - "dismiss_all_errors": "Afvis alle fejl", - "dismiss_error": "Afvis fejl", - "display_options": "Display-indstillinger", - "display_order": "Display-rækkefølge", - "display_original_photos": "Vis originale billeder", - "display_original_photos_setting_description": "Foretræk at vise det originale billede frem for miniaturebilleder når den originale fil er web-kompatibelt. Dette kan gøre billedvisning langsommere.", - "do_not_show_again": "Vis ikke denne besked igen", - "documentation": "Dokumentation", - "done": "Færdig", - "download": "Hent", - "download_action_prompt": "Downloader {count} objekter", - "download_canceled": "Download annulleret", - "download_complete": "Download fuldført", - "download_enqueue": "Donload sat i kø", - "download_error": "Fejl med download", - "download_failed": "Download mislykkes", - "download_finished": "Download afsluttet", - "download_include_embedded_motion_videos": "Indlejrede videoer", - "download_include_embedded_motion_videos_description": "Inkluder videoer indlejret i levende billeder som en separat fil", - "download_notfound": "Download ikke fundet", - "download_original": "Download original", - "download_paused": "Download pauset", - "download_settings": "Download", - "download_settings_description": "Administrer indstillinger relateret til mediefil-downloads", - "download_started": "Download startet", - "download_sucess": "Download færdig", - "download_sucess_android": "Mediet er blevet downloadet til DCIM/Immich", - "download_waiting_to_retry": "Afventer at prøve igen", - "downloading": "Downloader", - "downloading_asset_filename": "Downloader mediefil {filename}", - "downloading_from_icloud": "Downloading fra iCloud", - "downloading_media": "Download medier", - "drop_files_to_upload": "Slip filer hvor som helst for at uploade dem", - "duplicates": "Duplikater", - "duplicates_description": "Løs hver gruppe ved at angive, hvilke, hvis nogen, er dubletter", - "duration": "Varighed", - "edit": "Rediger", - "edit_album": "Redigér album", - "edit_avatar": "Redigér avatar", - "edit_birthday": "Rediger fødselsdag", - "edit_date": "Redigér dato", - "edit_date_and_time": "Redigér dato og tid", - "edit_date_and_time_action_prompt": "{count} dato og tid redigeret", - "edit_date_and_time_by_offset": "Forskyde dato med offset", - "edit_date_and_time_by_offset_interval": "Nyt datointerval: {from} - {to}", - "edit_description": "Rediger beskrivelse", - "edit_description_prompt": "Vælg venligst en ny beskrivelse:", - "edit_exclusion_pattern": "Redigér udelukkelsesmønster", - "edit_faces": "Redigér ansigter", - "edit_key": "Redigér nøgle", - "edit_link": "Rediger link", - "edit_location": "Rediger placering", - "edit_location_action_prompt": "{count} geolokation redigeret", - "edit_location_dialog_title": "Placering", - "edit_name": "Rediger navn", - "edit_people": "Redigér personer", - "edit_tag": "Rediger tag", - "edit_title": "Redigér titel", - "edit_user": "Redigér bruger", - "edit_workflow": "Rediger workflow", - "editor": "Redaktør", - "editor_close_without_save_prompt": "Ændringerne vil ikke blive gemt", - "editor_close_without_save_title": "Luk editor?", - "editor_confirm_reset_all_changes": "Er du sikker på, at du vil nulstille alle ændringer?", - "editor_flip_horizontal": "Vend horisontalt", - "editor_flip_vertical": "Flip vertikal", - "editor_orientation": "Orientering", - "editor_reset_all_changes": "Nulstil ændringer", - "editor_rotate_left": "Rotér 90° mod uret", - "editor_rotate_right": "Rotér 90° med uret", - "email": "E-mail", - "email_notifications": "Email notifikationer", - "empty_folder": "Denne mappe er tom", - "empty_trash": "Tøm papirkurv", - "empty_trash_confirmation": "Er du sikker på, at du vil tømme papirkurven? Dette vil fjerne alle objekter i papirkurven permanent fra Immich.\nDu kan ikke fortryde denne handling!", - "enable": "Aktivér", - "enable_backup": "Aktiver backup", - "enable_biometric_auth_description": "Indtast din PIN kode for at slå biometrisk adgangskontrol til", - "enabled": "Aktiveret", - "end_date": "Slutdato", - "enqueued": "I kø", - "enter_wifi_name": "Indtast Wi-Fi-navn", - "enter_your_pin_code": "Indtast din PIN kode", - "enter_your_pin_code_subtitle": "Indtast din PIN kode for at tilgå den låste mappe", - "error": "Fejl", - "error_change_sort_album": "Ændring af sorteringsrækkefølgen mislykkedes", - "error_delete_face": "Fejl ved sletning af ansigt fra mediefil", - "error_getting_places": "Fejl ved hentning af steder", - "error_loading_albums": "Fejl ved indlæsning af album", - "error_loading_image": "Fejl ved indlæsning af billede", - "error_loading_partners": "Fejl ved indlæsning af partnere: {error}", - "error_saving_image": "Fejl: {error}", - "error_tag_face_bounding_box": "Fejl ved tagging af ansigt - kan ikke finde koordinator for afgrænsningskasse", - "error_title": "Fejl - Noget gik galt", - "errors": { - "cannot_navigate_next_asset": "Kan ikke navigere til næste mediefil", - "cannot_navigate_previous_asset": "Kan ikke navigere til forrige mediefil", - "cant_apply_changes": "Ændringerne kan ikke anvendes", - "cant_change_activity": "Kan ikke {enabled, select, true {disable} other {enable}} aktivitet", - "cant_change_asset_favorite": "Kan ikke ændre favorit til mediefil", - "cant_change_metadata_assets_count": "Kan ikke ændre metadata for {count, plural, one {# objekt} other {# objekter}}", - "cant_get_faces": "Kan ikke hente ansigter", - "cant_get_number_of_comments": "Kan ikke få antallet af kommentarer", - "cant_search_people": "Kan ikke søge efter personer", - "cant_search_places": "Kan ikke søge efter steder", - "error_adding_assets_to_album": "Fejl i tilføjelse af mediefiler til album", - "error_adding_users_to_album": "Fejl i tilføjelse af brugere til album", - "error_deleting_shared_user": "Fejl i sletning af delt bruger", - "error_downloading": "Fejl i download af {filename}", - "error_hiding_buy_button": "Fejl i skjulning af køb-knap", - "error_removing_assets_from_album": "Fejl i fjernelse af mediefiler fra album. Tjek konsol for flere detaljer", - "error_selecting_all_assets": "Fejl ved valg af alle mediefiler", - "exclusion_pattern_already_exists": "Denne udelukkelsesmønster findes allerede.", - "failed_to_create_album": "Oprettelse af album mislykkedes", - "failed_to_create_shared_link": "Oprettelse af delt link mislykkedes", - "failed_to_edit_shared_link": "Redigering af delt link mislykkedes", - "failed_to_get_people": "Det lykkedes ikke at hente personer", - "failed_to_keep_this_delete_others": "Kunne ikke beholde denne mediefil og slette de andre mediefiler", - "failed_to_load_asset": "Indlæsning af mediefil mislykkedes", - "failed_to_load_assets": "Indlæsning af mediefiler mislykkedes", - "failed_to_load_notifications": "Kunne ikke indlæse notifikationer", - "failed_to_load_people": "Indlæsning af personer mislykkedes", - "failed_to_remove_product_key": "Fjernelse af produktnøgle mislykkedes", - "failed_to_reset_pin_code": "Kunne ikke resette PIN-koden", - "failed_to_stack_assets": "Det lykkedes ikke at stable mediefiler", - "failed_to_unstack_assets": "Det lykkedes ikke at fjerne gruperingen af mediefiler", - "failed_to_update_notification_status": "Kunne ikke uploade notifikations status", - "incorrect_email_or_password": "Forkert email eller kodeord", - "library_folder_already_exists": "Denne import sti findes allerede.", - "paths_validation_failed": "{paths, plural, one {# sti} other {# stier}} slog fejl ved validering", - "profile_picture_transparent_pixels": "Profilbilleder kan ikke have gennemsigtige pixels. Zoom venligst ind og/eller flyt billedet.", - "quota_higher_than_disk_size": "Du har sat en kvote der er større end disken", - "something_went_wrong": "Noget gik galt", - "unable_to_add_album_users": "Ikke i stand til at tilføje brugere til album", - "unable_to_add_assets_to_shared_link": "Kan ikke tilføje mediefiler til det delte link", - "unable_to_add_comment": "Ikke i stand til at tilføje kommentar", - "unable_to_add_exclusion_pattern": "Kunne ikke tilføje udelukkelsesmønster", - "unable_to_add_partners": "Ikke i stand til at tilføje partnere", - "unable_to_add_remove_archive": "Kan ikke {archived, select, true {fjerne aktiv fra} other {tilføje aktiv til}} Arkiv", - "unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {tilføje aktiv til} other {fjerne aktiv fra}} favoritter", - "unable_to_archive_unarchive": "Ude af stand til at {archived, select, true {arkivere} other {fjerne fra arkiv}}", - "unable_to_change_album_user_role": "Ikke i stand til at ændre albumbrugerens rolle", - "unable_to_change_date": "Ikke i stand til at ændre dato", - "unable_to_change_description": "Kunne ikke ændre beskrivelsen", - "unable_to_change_favorite": "Kan ikke ændre favorit for mediefil", - "unable_to_change_location": "Ikke i stand til at ændre sted", - "unable_to_change_password": "Kunne ikke ændre adgangskode", - "unable_to_change_visibility": "Kan ikke ændre synligheden for {count, plural, one {# person} other {# personer}}", - "unable_to_complete_oauth_login": "Kan ikke fuldføre OAuth-login", - "unable_to_connect": "Kan ikke oprette forbindelse", - "unable_to_copy_to_clipboard": "Kan ikke kopiere til udklipsholder, sørg for at du tilgår siden gennem https", - "unable_to_create": "Kan ikke oprette workflow", - "unable_to_create_admin_account": "Kan ikke oprette en administratorkonto", - "unable_to_create_api_key": "Kunne ikke oprette ny API-nøgle", - "unable_to_create_library": "Ikke i stand til at oprette bibliotek", - "unable_to_create_user": "Ikke i stand til at oprette bruger", - "unable_to_delete_album": "Ikke i stand til at slette album", - "unable_to_delete_asset": "Kan ikke slette mediefil", - "unable_to_delete_assets": "Fejl i sletning af mediefiler", - "unable_to_delete_exclusion_pattern": "Kunne ikke slette udelukkelsesmønster", - "unable_to_delete_shared_link": "Kunne ikke slette delt link", - "unable_to_delete_user": "Ikke i stand til at slette bruger", - "unable_to_delete_workflow": "Kan ikke slette workflow", - "unable_to_download_files": "Kan ikke downloade filer", - "unable_to_edit_exclusion_pattern": "Kunne ikke redigere udelukkelsesmønster", - "unable_to_empty_trash": "Ikke i stand til at tømme papirkurv", - "unable_to_enter_fullscreen": "Kan ikke aktivere fuldskærmstilstand", - "unable_to_exit_fullscreen": "Kan ikke forlade fuldskærmstilstand", - "unable_to_get_comments_number": "Kan ikke få antallet af kommentarer", - "unable_to_get_shared_link": "Kunne ikke hente delt link", - "unable_to_hide_person": "Ikke i stand til at gemme person", - "unable_to_link_motion_video": "Kan ikke linke bevægelsesvideo", - "unable_to_link_oauth_account": "Kunne ikke tilkoble OAuth-konto", - "unable_to_log_out_all_devices": "Kan ikke logge af alle enheder", - "unable_to_log_out_device": "Enheden kunne ikke logges af", - "unable_to_login_with_oauth": "Kan ikke logge på med OAuth", - "unable_to_play_video": "Ikke i stand til at afspille video", - "unable_to_reassign_assets_existing_person": "Kunne ikke tildele mediafiler til {name, select, null {en eksisterende person} other {{name}}}", - "unable_to_reassign_assets_new_person": "Kan ikke omfordele objekter til en ny person", - "unable_to_refresh_user": "Ikke i stand til at genopfriske bruger", - "unable_to_remove_album_users": "Ikke i stand til at fjerne brugere fra album", - "unable_to_remove_api_key": "Kunne ikke fjerne API-nøgle", - "unable_to_remove_assets_from_shared_link": "Kan ikke fjerne aktiver fra delt link", - "unable_to_remove_library": "Ikke i stand til at fjerne bibliotek", - "unable_to_remove_partner": "Ikke i stand til at fjerne partner", - "unable_to_remove_reaction": "Ikke i stand til at fjerne reaktion", - "unable_to_reset_password": "Ikke i stand til at nulstille adgangskode", - "unable_to_reset_pin_code": "Kunne ikke nulstille din PIN kode", - "unable_to_resolve_duplicate": "Kunne ikke opklare duplikat", - "unable_to_restore_assets": "Kunne ikke gendanne medierfil", - "unable_to_restore_trash": "Ikke i stand til at gendanne fra papirkurv", - "unable_to_restore_user": "Ikke i stand til at gendanne bruger", - "unable_to_save_album": "Ikke i stand til at gemme album", - "unable_to_save_api_key": "Kunne ikke gemme API-nøgle", - "unable_to_save_date_of_birth": "Kunne ikke gemme fødselsdatoen", - "unable_to_save_name": "Ikke i stand til at gemme navn", - "unable_to_save_profile": "Ikke i stand til at gemme profil", - "unable_to_save_settings": "Ikke i stand til at gemme indstillinger", - "unable_to_scan_libraries": "Ikke i stand til at skanne biblioteker", - "unable_to_scan_library": "Ikke i stand til at skanne bibliotek", - "unable_to_set_feature_photo": "Det var ikke muligt at indstille et fremhævet billede", - "unable_to_set_profile_picture": "Ikke i stand til at sætte profilbillede", - "unable_to_set_rating": "Ikke i stand til at angive vurdering", - "unable_to_submit_job": "Ikke i stand til at indsende opgave", - "unable_to_trash_asset": "Kunne ikke slette medie", - "unable_to_unlink_account": "Ikke i stand til at frakoble konto", - "unable_to_unlink_motion_video": "Kunne ikke fjerne linket til bevægelsesvideo", - "unable_to_update_album_cover": "Albumomslaget kunne ikke opdateres", - "unable_to_update_album_info": "Albumsoplysningerne kunne ikke opdateres", - "unable_to_update_library": "Ikke i stand til at opdatere bibliotek", - "unable_to_update_location": "Ikke i stand til at opdatere sted", - "unable_to_update_settings": "Ikke i stand til at opdatere indstillinger", - "unable_to_update_timeline_display_status": "Kunne ikke opdate status for tidslinjevisning", - "unable_to_update_user": "Ikke i stand til at opdatere bruger", - "unable_to_update_workflow": "Kan ikke opdatere workflow", - "unable_to_upload_file": "Filen kunne ikke uploades" - }, - "errors_text": "Fejl", - "exclusion_pattern": "Udelukkelsesmønster", - "exif": "Exif", - "exif_bottom_sheet_description": "Tilføj beskrivelse...", - "exif_bottom_sheet_description_error": "Fejl ved opdatering af beskrivelsen", - "exif_bottom_sheet_details": "DETALJER", - "exif_bottom_sheet_location": "LOKATION", - "exif_bottom_sheet_no_description": "Ingen beskrivelse", - "exif_bottom_sheet_people": "PERSONER", - "exif_bottom_sheet_person_add_person": "Tilføj navn", - "exit_slideshow": "Afslut slideshow", - "expand_all": "Udvid alle", - "experimental_settings_new_asset_list_subtitle": "Under udarbejdelse", - "experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter", - "experimental_settings_subtitle": "Brug på eget ansvar!", - "experimental_settings_title": "Eksperimentelle", - "expire_after": "Udløb efter", - "expired": "Udløbet", - "expires_date": "Udløber {date}", - "explore": "Udforsk", - "explorer": "Udforske", - "export": "Eksportér", - "export_as_json": "Eksportér som JSON", - "export_database": "Eksporter database", - "export_database_description": "Eksporter SQLite databasen", - "extension": "Udvidelse", - "external": "Ekstern", - "external_libraries": "Eksterne biblioteker", - "external_network": "Eksternt netværk", - "external_network_sheet_info": "Nå der er ikke er forbundet til det foretrukne Wi-Fi netværk, vil appen forbinde til den første URL den kan forbinde til, på listen nedenfor. Startende fra toppen", - "face_unassigned": "Ikke tildelt", - "failed": "Fejlet", - "failed_count": "Fejlede: {count}", - "failed_to_authenticate": "Kunne ikke godkendes", - "failed_to_load_assets": "Kunne ikke indlæse mediefiler", - "failed_to_load_folder": "Kunne ikke indlæse mappe", - "favorite": "Favorit", - "favorite_action_prompt": "{count} føjet til favoritter", - "favorite_or_unfavorite_photo": "Tilføj eller fjern fra yndlingsbilleder", - "favorites": "Favoritter", - "favorites_page_no_favorites": "Ingen favoritter blev fundet", - "feature_photo_updated": "Forsidebillede uploadet", - "features": "Funktioner", - "features_in_development": "Funktioner under udvikling", - "features_setting_description": "Administrer app-funktioner", - "file_name_or_extension": "Filnavn eller filtype", - "file_size": "Fil størrelse", - "filename": "Filnavn", - "filetype": "Filtype", - "filter": "Filter", - "filter_description": "Betingelser for filtrering af valgte mediefiler", - "filter_people": "Filtrér personer", - "filter_places": "Filtrer steder", - "filters": "Filtre", - "find_them_fast": "Find dem hurtigt med søgning via navn", - "first": "Første", - "fix_incorrect_match": "Fix forkert match", - "folder": "Mappe", - "folder_not_found": "Mappe ikke fundet", - "folders": "Mapper", - "folders_feature_description": "Gennemse mappevisningen efter fotos og videoer på filsystemet", - "forgot_pin_code_question": "Har du glemt PIN-koden?", - "forward": "Fremad", - "free_up_space": "Frigør plads", - "free_up_space_description": "Flyt sikkerhedskopierede fotos og videoer til din enheds skraldepapir for at frigøre plads. Dine kopier på serveren forbliver sikre", - "free_up_space_settings_subtitle": "Frigør enhedslagerplads", - "full_path": "Fuld sti: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Denne funktion indlæser eksterne ressourcer fra Google for at virke.", - "general": "Generel", - "geolocation_instruction_location": "Klik på et objekt med GPS-koordinater for at bruge dettes position, eller vælg position direkte på kortet", - "get_help": "Få hjælp", - "get_people_error": "Fejl ved indhentning af personer", - "get_wifiname_error": "Kunne ikke hente Wi-Fi-navn. Sørg for, at du har givet de nødvendige tilladelser og er forbundet til et Wi-Fi-netværk", - "getting_started": "Kom godt i gang", - "go_back": "Gå tilbage", - "go_to_folder": "Gå til mappe", - "go_to_search": "Gå til søgning", - "gps": "GPS", - "gps_missing": "Ingen GPS", - "grant_permission": "Giv tilladelse", - "group_albums_by": "Gruppér albummer efter...", - "group_country": "Gruppér efter land", - "group_no": "Ingen gruppering", - "group_owner": "Grupper efter ejer", - "group_places_by": "Gruppér steder efter...", - "group_year": "Grupper efter år", - "haptic_feedback_switch": "Slå haptisk feedback til", - "haptic_feedback_title": "Haptisk feedback", - "has_quota": "Har kvote", - "hash_asset": "Hash objekter", - "hashed_assets": "Hashede objekter", - "hashing": "Hasher", - "header_settings_add_header_tip": "Tilføj header", - "header_settings_field_validator_msg": "Værdi kan ikke være tom", - "header_settings_header_name_input": "Header navn", - "header_settings_header_value_input": "Header værdi", - "headers_settings_tile_title": "Brugerdefineret proxy headers", - "height": "Højde", - "hi_user": "Hej {name} ({email})", - "hide_all_people": "Skjul alle personer", - "hide_gallery": "Skjul galleri", - "hide_named_person": "Skjul person {name}", - "hide_password": "Skjul adgangskode", - "hide_person": "Skjul person", - "hide_schema": "Skjul skema", - "hide_text_recognition": "Skjul tekstgenkendelse", - "hide_unnamed_people": "Skjul unavngivne personer", - "home_page_add_to_album_conflicts": "Tilføjede {added} elementer til album {album}. {failed} elementer er allerede i albummet.", - "home_page_add_to_album_err_local": "Kan endnu ikke tilføje lokale elementer til album. Springer over", - "home_page_add_to_album_success": "Tilføjede {added} elementer til album {album}.", - "home_page_album_err_partner": "Kan endnu ikke tilføje partners elementer til album. Springer over", - "home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu. Springer over", - "home_page_archive_err_partner": "Kan endnu ikke arkivere partners elementer. Springer over", - "home_page_building_timeline": "Bygger tidslinjen", - "home_page_delete_err_partner": "Kan endnu ikke slette partners elementer. Springer over", - "home_page_delete_remote_err_local": "Lokale elementer i fjernsletningssektion. Springer over", - "home_page_favorite_err_local": "Det er ikke muligt at gøre lokale elementer til favoritter endnu, springer over", - "home_page_favorite_err_partner": "Kan endnu ikke tilføje partners elementer som favoritter. Springer over", - "home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vælge en sikkerhedskopi af albummer så tidlinjen kan blive fyldt med billeder og videoer fra albummerne", - "home_page_locked_error_local": "Kan ikke flytte lokale mediefiler til låst mappe, springer over", - "home_page_locked_error_partner": "Kan ikke flytte partners mediefiler til låst mappe, springer over", - "home_page_share_err_local": "Kan ikke dele lokale elementer via link, springer over", - "home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over", - "host": "Host", - "hour": "Time", - "hours": "Timer", - "id": "ID", - "idle": "Inaktiv", - "ignore_icloud_photos": "Ignorer iCloud-billeder", - "ignore_icloud_photos_description": "Billeder der er gemt på iCloud vil ikke blive uploadet til Immich-serveren", - "image": "Billede", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taget den {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taget med {person1} den {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} taget med {person1} og {person2} den {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} taget med {person1}, {person2}, og {person3} den {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taget med {person1}, {person2}, og {additionalCount, number} andre den {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} den {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1} den {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1} og {person2} den {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1}, {person2}, og {person3} den {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1}, {person2}, og {additionalCount, number} andre den {date}", - "image_saved_successfully": "Billede gemt", - "image_viewer_page_state_provider_download_started": "Download startet", - "image_viewer_page_state_provider_download_success": "Download succesfuld", - "image_viewer_page_state_provider_share_error": "Delingsfejl", - "immich_logo": "Immich logo", - "immich_web_interface": "Immich webinterface", - "import_from_json": "Importér fra JSON", - "import_path": "Import-sti", - "in_albums": "I {count, plural, one {# album} other {# albummer}}", - "in_archive": "I arkiv", - "in_year": "I {year}", - "in_year_selector": "I", - "include_archived": "Inkluder arkiverede", - "include_shared_albums": "Inkludér delte albummer", - "include_shared_partner_assets": "Inkludér delte partnermedier", - "individual_share": "Individuel andel", - "individual_shares": "Individuelle delinger", - "info": "Info", - "interval": { - "day_at_onepm": "Hver dag kl. 13", - "hours": "Hver {hours, plural, one {time} other {{hours, number} timer}}", - "night_at_midnight": "Hver nat ved midnat", - "night_at_twoam": "Hver nat kl. 2" - }, - "invalid_date": "Ugyldig dato", - "invalid_date_format": "Ugyldigt dato format", - "invite_people": "Inviter personer", - "invite_to_album": "Inviter til album", - "ios_debug_info_fetch_ran_at": "Hent kørte {dateTime}", - "ios_debug_info_last_sync_at": "Sidste sync skete {dateTime}", - "ios_debug_info_no_processes_queued": "Ingen baggrundsprocesser i kø", - "ios_debug_info_no_sync_yet": "Der er endnu ikke kørt noget baggrundssynkroniseringsjob", - "ios_debug_info_processes_queued": "{count, plural, one {{count} baggrundsproces i kø} other {{count} baggrundsprocesser i kø}}", - "ios_debug_info_processing_ran_at": "Behandlingen kørte {dateTime}", - "items_count": "{count, plural, one {# element} other {# elementer}}", - "jobs": "Opgaver", - "json_editor": "JSON editor", - "json_error": "JSON fejl", - "keep": "Behold", - "keep_all": "Behold alle", - "keep_favorites": "Behold favoritter", - "keep_this_delete_others": "Behold dette, slet andre", - "kept_this_deleted_others": "Beholdt denne mediefil og slettede {count, plural, one {# aktiv} other {# aktiver}}", - "keyboard_shortcuts": "Tastaturgenveje", - "language": "Sprog", - "language_no_results_subtitle": "Prøv at justere dit søgeord", - "language_no_results_title": "Ingen sprog fundet", - "language_search_hint": "Vælg sprog...", - "language_setting_description": "Vælg dit foretrukne sprog", - "large_files": "Store filer", - "last": "Sidste", - "last_months": "{count, plural, one {Sidste måned} other {Sidste # måneder}}", - "last_seen": "Sidst set", - "latest_version": "Seneste version", - "latitude": "Breddegrad", - "leave": "Forlad", - "leave_album": "Forlad album", - "lens_model": "Objektivmodel", - "let_others_respond": "Lad andre svare", - "level": "Niveau", - "library": "Bibliotek", - "library_add_folder": "Tilføj mappe", - "library_edit_folder": "Rediger mappe", - "library_options": "Biblioteksindstillinger", - "library_page_device_albums": "Albummer på enhed", - "library_page_new_album": "Nyt album", - "library_page_sort_asset_count": "Antal af elementer", - "library_page_sort_created": "Senest oprettet", - "library_page_sort_last_modified": "Sidst redigeret", - "library_page_sort_title": "Albumtitel", - "licenses": "Licenser", - "light": "Lys", - "like": "Synes om", - "like_deleted": "Ligesom slettet", - "link_motion_video": "Link bevægelsesvideo", - "link_to_oauth": "Link til OAuth", - "linked_oauth_account": "Tilsluttet OAuth-konto", - "list": "Liste", - "loading": "Indlæser", - "loading_search_results_failed": "Indlæsning af søgeresultater fejlede", - "local": "Lokal", - "local_asset_cast_failed": "Kan ikke caste et aktiv, der ikke er uploadet til serveren", - "local_assets": "Lokale objekter", - "local_id": "Lokal ID", - "local_media_summary": "Opsummering af lokale media", - "local_network": "Lokalt netværk", - "local_network_sheet_info": "Appen vil oprette forbindelse til serveren via denne URL, når du bruger det angivne WiFi-netværk", - "location": "Lokation", - "location_permission": "Tilladelse til placering", - "location_permission_content": "For automatisk at skifte netværk, skal Immich *altid* have præcis placeringsadgang, så appen kan læse Wi-Fi netværkets navn", - "location_picker_choose_on_map": "Vælg på kort", - "location_picker_latitude_error": "Indtast en gyldig breddegrad", - "location_picker_latitude_hint": "Indtast din breddegrad her", - "location_picker_longitude_error": "Indtast en gyldig længdegrad", - "location_picker_longitude_hint": "Indtast din længdegrad her", - "lock": "Lås", - "locked_folder": "Låst mappe", - "log_detail_title": "Logdetaljer", - "log_out": "Log ud", - "log_out_all_devices": "Log ud af alle enheder", - "logged_in_as": "Logget ind som {user}", - "logged_out_all_devices": "Logget ud af alle enheder", - "logged_out_device": "Logget ud af enhed", - "login": "Log ind", - "login_disabled": "Login er blevet deaktiveret", - "login_form_api_exception": "API-undtagelse. Tjek serverens URL og prøv igen.", - "login_form_back_button_text": "Tilbage", - "login_form_email_hint": "din-e-mail@e-mail.com", - "login_form_endpoint_hint": "http://din-server-ip:port", - "login_form_endpoint_url": "Server endepunkt URL", - "login_form_err_http": "Angiv venligst http:// eller https://", - "login_form_err_invalid_email": "Ugyldig e-mail", - "login_form_err_invalid_url": "Ugyldig webadresse", - "login_form_err_leading_whitespace": "Mellemrum før", - "login_form_err_trailing_whitespace": "Mellemrum efter", - "login_form_failed_get_oauth_server_config": "Fejl med at logge på med OAuth. Tjek serveres webadresse", - "login_form_failed_get_oauth_server_disable": "OAuth er ikke tilgængelig på denne server", - "login_form_failed_login": "Der opstod en vejl ved at logge ind. Tjek server webadressen, e-mailen og kodeordet", - "login_form_handshake_exception": "Der opstod en fejl med at oprette forbindelse til serveren. Aktiver selvsignerede certifikater i indstillingerne, hvis du bruger et selv signeret certifikat.", - "login_form_password_hint": "kodeord", - "login_form_save_login": "Forbliv logget ind", - "login_form_server_empty": "Indtast server-URL.", - "login_form_server_error": "Kunne ikke forbinde til serveren.", - "login_has_been_disabled": "Login er blevet deaktiveret.", - "login_password_changed_error": "Der opstod en fejl i opdateringen af dit kodeord", - "login_password_changed_success": "Kodeordet blev opdateret", - "logout_all_device_confirmation": "Er du sikker på, at du vil logge ud af alle enheder?", - "logout_this_device_confirmation": "Er du sikker på, at du vil logge denne enhed ud?", - "logs": "Logs", - "longitude": "Længdegrad", - "look": "Kig", - "loop_videos": "Gentag videoer", - "loop_videos_description": "Aktivér for at genafspille videoer automatisk i detaljeret visning.", - "main_branch_warning": "Du bruger en udviklingsversion; vi anbefaler kraftigt at bruge en udgivelsesversion!", - "main_menu": "Hovedmenu", - "maintenance_description": "Immich er blevet sat i vedligeholdelsestilstand.", - "maintenance_end": "Afslut vedligeholdelsestilstand", - "maintenance_end_error": "Vedligeholdelsestilstand kunne ikke afsluttes.", - "maintenance_logged_in_as": "Aktuelt logget ind som {user}", - "maintenance_title": "Midlertidigt Utilgængelig", - "make": "Producent", - "manage_geolocation": "Administrer placering", - "manage_media_access_rationale": "Denne tilladelse er påkrævet for korrekt håndtering af flytning af elementer til papirkurven og gendannelse af dem fra den.", - "manage_media_access_settings": "Åben instillinger", - "manage_media_access_subtitle": "Tillad Immich appen at administrere og flytte mediefiler.", - "manage_media_access_title": "Mediestyringsadgang", - "manage_shared_links": "Håndter delte links", - "manage_sharing_with_partners": "Administrér deling med partnere", - "manage_the_app_settings": "Administrer appindstillinger", - "manage_your_account": "Administrér din konto", - "manage_your_api_keys": "Administrér dine API-nøgler", - "manage_your_devices": "Administrér dine enheder der er logget ind", - "manage_your_oauth_connection": "Administrér din OAuth-tilslutning", - "map": "Kort", - "map_assets_in_bounds": "{count, plural, =0 {Ingen billeder i dette område} one {# billede} other {# billeder}}", - "map_cannot_get_user_location": "Kan ikke finde brugerens placering", - "map_location_dialog_yes": "Ja", - "map_location_picker_page_use_location": "Brug denne placering", - "map_location_service_disabled_content": "Placeringstjenesten skal aktiveres for at vise elementer fra din nuværende placering. Vil du aktivere den nu?", - "map_location_service_disabled_title": "Placeringstjenesten er deaktiveret", - "map_marker_for_images": "Kortmarkør for billeder taget i {city}, {country}", - "map_marker_with_image": "Kortmarkør med billede", - "map_no_location_permission_content": "Der kræves tilladelse til placeringen for at vise elementer fra din nuværende placering. Vil du give tilladelse?", - "map_no_location_permission_title": "Placeringstilladelse blev afvist", - "map_settings": "Kortindstillinger", - "map_settings_dark_mode": "Mørk tilstand", - "map_settings_date_range_option_day": "Sidste 24 timer", - "map_settings_date_range_option_days": "Sidste {days} dage", - "map_settings_date_range_option_year": "Sidste år", - "map_settings_date_range_option_years": "Sidste {years} år", - "map_settings_dialog_title": "Kortindstillinger", - "map_settings_include_show_archived": "Inkluder arkiverede", - "map_settings_include_show_partners": "Inkluder partnere", - "map_settings_only_show_favorites": "Vis kun favoritter", - "map_settings_theme_settings": "Korttema", - "map_zoom_to_see_photos": "Zoom ud for at vise billeder", - "mark_all_as_read": "Marker alle som læst", - "mark_as_read": "Marker som læst", - "marked_all_as_read": "Markerede alle som læst", - "matches": "Parringer", - "matching_assets": "Matchende objekter", - "media_type": "Medietype", - "memories": "Minder", - "memories_all_caught_up": "Ajour", - "memories_check_back_tomorrow": "Kom tilbage i morgen for at se nye minder", - "memories_setting_description": "Administrér hvad du ser i dine minder", - "memories_start_over": "Start forfra", - "memories_swipe_to_close": "Stryg op for at lukke", - "memory": "Minde", - "memory_lane_title": "Minder {title}", - "menu": "Menu", - "merge": "Sammenflet", - "merge_people": "Sammenflet personer", - "merge_people_limit": "Du kan kun flette op til 5 ansigter ad gangen", - "merge_people_prompt": "Vil du flette disse mennesker sammen? Denne handling er uigenkaldelig.", - "merge_people_successfully": "Personer sammenflettet med succes", - "merged_people_count": "{count, plural, one {# person} other {# personer}} lagt sammen", - "minimize": "Minimér", - "minute": "Minut", - "minutes": "Minutter", - "mirror_horizontal": "Horisontalt", - "mirror_vertical": "Vertikal", - "missing": "Mangler", - "mobile_app": "Mobil App", - "mobile_app_download_onboarding_note": "Hent den tilhørende mobilapp via en af følgende muligheder", - "model": "Model", - "month": "Måned", - "monthly_title_text_date_format": "MMMM å", - "more": "Mere", - "move": "Flyt", - "move_down": "Flyt ned", - "move_off_locked_folder": "Flyt ud af låst mappe", - "move_to": "Flyt til", - "move_to_device_trash": "Flyt til enheds skraldespand", - "move_to_lock_folder_action_prompt": "{count} føjet til den låste mappe", - "move_to_locked_folder": "Flyt til låst mappe", - "move_to_locked_folder_confirmation": "Disse billeder og videoer vil blive fjernet fra alle albums, og vil kun være synlig fra den låste mappe", - "move_up": "Flyt op", - "moved_to_archive": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til arkivet", - "moved_to_library": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til biblioteket", - "moved_to_trash": "Flyttet til papirkurv", - "multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på skrivebeskyttet elementer. Springer over", - "multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af skrivebeskyttet elementer. Springer over", - "mute_memories": "Dæmp minder", - "my_albums": "Mine albummer", - "name": "Navn", - "name_or_nickname": "Navn eller kaldenavn", - "name_required": "Navn er påkrævet", - "navigate": "Naviger", - "navigate_to_time": "Naviger til tid", - "network_requirement_photos_upload": "Benyt mobildatanettet for at sikkerhedskopiere dine fotos", - "network_requirement_videos_upload": "Benyt mobildatanettet for at sikkerhedskopiere dine videoer", - "network_requirements": "Netværkskrav", - "network_requirements_updated": "Netværkskravene er ændret, backup-køen nulstilles", - "networking_settings": "Netværk", - "networking_subtitle": "Administrer serverens endepunktindstillinger", - "never": "Aldrig", - "new_album": "Nyt album", - "new_api_key": "Ny API-nøgle", - "new_date_range": "Nyt datointerval", - "new_password": "Ny adgangskode", - "new_person": "Ny person", - "new_pin_code": "Ny PIN kode", - "new_pin_code_subtitle": "Dette er første gang du tilgår den låste mappe. Lav en PIN kode for sikkert at tilgå denne side", - "new_timeline": "Ny tidslinje", - "new_update": "Ny opdatering", - "new_user_created": "Ny bruger oprettet", - "new_version_available": "NY VERSION TILGÆNGELIG", - "newest_first": "Nyeste først", - "next": "Næste", - "next_memory": "Næste minde", - "no": "Nej", - "no_actions_added": "Ingen handlinger tilføjet endnu", - "no_albums_found": "Ingen album fundet", - "no_albums_message": "Opret et album for at organisere dine billeder og videoer", - "no_albums_with_name_yet": "Det ser ud til, at du ikke har noget album med dette navn endnu.", - "no_albums_yet": "Det ser ud til, at du ikke har nogen album endnu.", - "no_archived_assets_message": "Arkivér billeder og videoer for at gemme dem væk fra din billedoversigt", - "no_assets_message": "KLIK FOR AT UPLOADE DIT FØRSTE BILLEDE", - "no_assets_to_show": "Ingen elementer at vise", - "no_cast_devices_found": "Ingen Cast-enheder fundet", - "no_checksum_local": "Ingen checksum tilgængelig – kan ikke hente lokale objekter", - "no_checksum_remote": "Ingen checksum tilgængelig – kan ikke hente eksterne objekter", - "no_configuration_needed": "Ingen konfiguration nødvendig", - "no_devices": "Ingen godkendte enheder", - "no_duplicates_found": "Ingen duplikater fundet.", - "no_exif_info_available": "Ingen tilgængelig exif information", - "no_explore_results_message": "Upload flere billeder for at udforske din samling.", - "no_favorites_message": "Tilføj favoritter for hurtigt at finde dine bedst billeder og videoer", - "no_filters_added": "Ingen filtre tilføjet endnu", - "no_libraries_message": "Opret et eksternt bibliotek for at se dine billeder og videoer", - "no_local_assets_found": "Ingen lokale objekter fundet med denne checksum", - "no_location_set": "Ingen placering sat", - "no_locked_photos_message": "Billeder og videoer i den låste mappe er skjulte og vil ikke blive vist i dit bibliotek.", - "no_name": "Intet navn", - "no_notifications": "Ingen notifikationer", - "no_people_found": "Ingen tilsvarende personer fundet", - "no_places": "Ingen steder", - "no_remote_assets_found": "Ingen eksterne objekter fundet med denne checksum", - "no_results": "Ingen resultater", - "no_results_description": "Prøv et synonym eller et mere generelt søgeord", - "no_shared_albums_message": "Opret et album for at dele billeder og videoer med personer i dit netværk", - "no_uploads_in_progress": "Ingen upload i gang", - "none": "Ingen", - "not_allowed": "Ikke tilladt", - "not_available": "ikke tilgængelig", - "not_in_any_album": "Ikke i noget album", - "not_selected": "Ikke valgt", - "note_apply_storage_label_to_previously_uploaded assets": "Bemærk: For at anvende Lagringsmærkat på tidligere uploadede medier, kør opgaven igen", - "notes": "Noter", - "nothing_here_yet": "Intet her endnu", - "notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.", - "notification_permission_list_tile_content": "Tillad at bruge notifikationer.", - "notification_permission_list_tile_enable_button": "Slå notifikationer til", - "notification_permission_list_tile_title": "Notifikationstilladelser", - "notification_toggle_setting_description": "Aktivér emailnotifikationer", - "notifications": "Notifikationer", - "notifications_setting_description": "Administrér notifikationer", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium-konfigurator", - "obtainium_configurator_instructions": "Brug Obtainium til at installere og opdatere Android-appen direkte fra Immich-udgivelsen på GitHub. Opret en API-nøgle, og vælg en variant for at generere dit Obtainium-konfigurationslink", - "ocr": "OCR", - "official_immich_resources": "Officielle Immich-ressourcer", - "offline": "Offline", - "offset": "Forskydning", - "ok": "Ok", - "oldest_first": "Ældste først", - "on_this_device": "På denne enhed", - "onboarding": "Introduktion", - "onboarding_locale_description": "Vælg dit foretrukne sprog. Du kan ændre dette senere i dine indstillinger.", - "onboarding_privacy_description": "Følgende (valgfrie) funktioner er afhængige af eksterne tjenester, og kan til enhver tid deaktiveres i indstillingerne.", - "onboarding_server_welcome_description": "Lad os konfigurere din instans med nogle almindelige indstillinger.", - "onboarding_theme_description": "Vælg et farvetema til din instans. Du kan ændre dette senere i dine indstillinger.", - "onboarding_user_welcome_description": "Lad os komme i gang!", - "onboarding_welcome_user": "Velkommen, {user}", - "online": "Online", - "only_favorites": "Kun favoritter", - "open": "Åben", - "open_in_map_view": "Åben i kortvisning", - "open_in_openstreetmap": "Åben i OpenStreetMap", - "open_the_search_filters": "Åbn søgefiltre", - "options": "Handlinger", - "or": "eller", - "organize_into_albums": "Organiser i album", - "organize_into_albums_description": "Sæt eksisterende billeder i albummer ved hjælp af aktuelle synkroniseringsindstillinger", - "organize_your_library": "Organisér dit bibliotek", - "original": "original", - "other": "Andet", - "other_devices": "Andre enheder", - "other_entities": "Andre enheder", - "other_variables": "Andre variable", - "owned": "Egne", - "owner": "Ejer", - "page": "Side", - "partner": "Partner", - "partner_can_access": "{partner} kan tilgå", - "partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkiv og Slettet", - "partner_can_access_location": "Stedet, hvor dine billeder blev taget", - "partner_list_user_photos": "{user}s billeder", - "partner_list_view_all": "Se alle", - "partner_page_empty_message": "Dine billeder er endnu ikke delt med en partner.", - "partner_page_no_more_users": "Der er ikke flere brugere at tilføje", - "partner_page_partner_add_failed": "Kunne ikke tilføje en partner", - "partner_page_select_partner": "Vælg partner", - "partner_page_shared_to_title": "Delt til", - "partner_page_stop_sharing_content": "{partner} vil ikke længere have adgang til dine billeder.", - "partner_sharing": "Partnerdeling", - "partners": "Partnere", - "password": "Kodeord", - "password_does_not_match": "Adgangskoder stemmer ikke overens", - "password_required": "Adgangskode påkrævet", - "password_reset_success": "Nulstilling af adgangskode succes", - "past_durations": { - "days": "Sidste {days, plural, one {dag} other {# dage}}", - "hours": "Sidste {hours, plural, one {time} other {# timer}}", - "years": "Sidste {years, plural, one {år} other {# år}}" - }, - "path": "Sti", - "pattern": "Mønster", - "pause": "Sæt på pause", - "pause_memories": "Sæt minder på pause", - "paused": "Sat på pause", - "pending": "Afventer", - "people": "Personer", - "people_edits_count": "Redigeret {count, plural, one {# person} other {# people}}", - "people_feature_description": "Gennemse billeder og videoer grupperet efter personer", - "people_selected": "{count, plural, one {# person vagt} other {# personer valgt}}", - "people_sidebar_description": "Vis et link til Personer i sidepanelet", - "permanent_deletion_warning": "Advarsel om permanent sletning", - "permanent_deletion_warning_setting_description": "Vis en advarsel, når medier slettes permanent", - "permanently_delete": "Slet permanent", - "permanently_delete_assets_count": "Slet permanent {count, plural, one {asset} other {assets}}", - "permanently_delete_assets_prompt": "Er du sikker på, at du permanent vil slette {count, plural, one {dette aktiv?} other {disse # aktiver?}} Dette vil også fjerne {count, plural, one {det fra dets} other {dem fra deres}} album(er).", - "permanently_deleted_asset": "Permanent slettet medie", - "permanently_deleted_assets_count": "{count, plural, one {# aktiv} other {# aktiver}} permanent slettet", - "permission": "Tilladelse", - "permission_empty": "Din tilladelse må ikke være tom", - "permission_onboarding_back": "Tilbage", - "permission_onboarding_continue_anyway": "Fortsæt alligevel", - "permission_onboarding_get_started": "Kom i gang", - "permission_onboarding_go_to_settings": "Gå til indstillinger", - "permission_onboarding_permission_denied": "Tilladelse afvist. For at bruge Immich, skal der gives tilladelse til at se billeder og videoer i indstillinger.", - "permission_onboarding_permission_granted": "Tilladelse givet! Du er nu klar.", - "permission_onboarding_permission_limited": "Tilladelse begrænset. For at lade Immich lave sikkerhedskopi og styre hele dit galleri, skal der gives tilladelse til billeder og videoer i indstillinger.", - "permission_onboarding_request": "Immich kræver tilladelse til at se dine billeder og videoer.", - "person": "Person", - "person_age_months": "{months, plural, one {# month} other {# months}} gammel", - "person_age_year_months": "1 år, {months, plural, one {# month} other {# months}} gammel", - "person_age_years": "{years, plural, other {# years}} gammel", - "person_birthdate": "Født den {date}", - "person_hidden": "{name}{hidden, select, true { (skjult)} other {}}", - "person_recognized": "Person genkendt", - "person_selected": "Person valgt", - "photo_shared_all_users": "Det ser ud til, at du har delt dine billeder med alle brugere, eller også har du ikke nogen bruger at dele med.", - "photos": "Billeder", - "photos_and_videos": "Billeder og videoer", - "photos_count": "{count, plural, one {{count, number} Billede} other {{count, number} Billeder}}", - "photos_from_previous_years": "Billeder fra tidligere år", - "photos_only": "Kun fotos", - "pick_a_location": "Vælg et sted", - "pick_custom_range": "Brugerdefineret periode", - "pick_date_range": "Vælg et datointerval", - "pin_code_changed_successfully": "Ændring af PIN kode lykkedes", - "pin_code_reset_successfully": "Nulstilling af PIN kode lykkedes", - "pin_code_setup_successfully": "Opsætning af PIN kode var vellykket", - "pin_verification": "PIN kode verifikation", - "place": "Sted", - "places": "Steder", - "places_count": "{count, plural, one {{count, number} Sted} other {{count, number} Steder}}", - "play": "Afspil", - "play_memories": "Afspil minder", - "play_motion_photo": "Afspil bevægelsesbillede", - "play_or_pause_video": "Afspil eller pause video", - "play_original_video": "Afspil original video", - "play_original_video_setting_description": "Foretrækker afspilning af originale videoer frem for transkodede videoer. Hvis det originale element ikke er kompatibelt, afspilles det muligvis ikke korrekt.", - "play_transcoded_video": "Afspil transkodet video", - "please_auth_to_access": "Log venligst ind for at tilgå", - "port": "Port", - "preferences_settings_subtitle": "Administrer appens indstillinger", - "preferences_settings_title": "Præferencer", - "preparing": "Forberedelse", - "preset": "Forudindstilling", - "preview": "Forhåndsvisning", - "previous": "Forrige", - "previous_memory": "Forrige minde", - "previous_or_next_day": "Dag frem/tilbage", - "previous_or_next_month": "Måned frem/tilbage", - "previous_or_next_photo": "Forrige eller næste billede", - "previous_or_next_year": "År frem/tilbage", - "primary": "Primære", - "privacy": "Privatliv", - "profile": "Profil", - "profile_drawer_app_logs": "Log", - "profile_drawer_client_server_up_to_date": "Klient og server er ajour", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Skrivebeskyttet tilstand aktiveret. Lang tryk på bruger avatar ikonet for at afslutte.", - "profile_image_of_user": "Profilbillede af {user}", - "profile_picture_set": "Profilbillede indstillet.", - "public_album": "Offentligt album", - "public_share": "Offentlig deling", - "purchase_account_info": "Supporter", - "purchase_activated_subtitle": "Tak fordi du støtter Immich og open source-software", - "purchase_activated_time": "Aktiveret den {date}", - "purchase_activated_title": "Din nøgle er blevet aktiveret", - "purchase_button_activate": "Aktiver", - "purchase_button_buy": "Køb", - "purchase_button_buy_immich": "Køb Immich", - "purchase_button_never_show_again": "Vis aldrig igen", - "purchase_button_reminder": "Påmind mig om 30 dage", - "purchase_button_remove_key": "Fjern nøgle", - "purchase_button_select": "Vælg", - "purchase_failed_activation": "Kunne ikke aktiveres! Tjek venligst din e-mail for den korrekte produktnøgle!", - "purchase_individual_description_1": "For en enkeltperson", - "purchase_individual_description_2": "Supporterstatus", - "purchase_individual_title": "Individuel", - "purchase_input_suggestion": "Har du en produktnøgle? Indtast nøglen nedenfor", - "purchase_license_subtitle": "Køb Immich for at understøtte den fortsatte udvikling af tjenesten", - "purchase_lifetime_description": "Livsvarigt køb", - "purchase_option_title": "KØBSMULIGHEDER", - "purchase_panel_info_1": "At bygge Immich tager meget tid og kræfter, og vi har fuldtidsudviklere, der arbejder på det for at gøre det så godt, som vi overhovedet kan. Vores mission er, at open source-software og etisk forretningspraksis bliver en bæredygtig indtægtskilde for udviklere og at skabe et privatlivsrespekterende økosystem med reelle alternativer til udnyttende cloud-tjenester.", - "purchase_panel_info_2": "Da vi er forpligtet til ikke at tilføje betalingsvægge, vil dette køb ikke give dig yderligere funktioner i Immich. Vi er afhængige af, at brugere som dig støtter Immichs løbende udvikling.", - "purchase_panel_title": "Støt projektet", - "purchase_per_server": "Pr. server", - "purchase_per_user": "Pr. bruger", - "purchase_remove_product_key": "Fjern produktnøgle", - "purchase_remove_product_key_prompt": "Er du sikker på, at du vil fjerne produktnøglen?", - "purchase_remove_server_product_key": "Fjern serverens produktnøgle", - "purchase_remove_server_product_key_prompt": "Er du sikker på, at du vil fjerne serverproduktnøglen?", - "purchase_server_description_1": "For hele serveren", - "purchase_server_description_2": "Supporterstatus", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Serverens produktnøgle administreres af administratoren", - "query_asset_id": "Forespørgsels Asset ID", - "queue_status": "Kø {count}/{total}", - "rate_asset": "Vurder filer", - "rating": "Stjernebedømmelse", - "rating_clear": "Nulstil vurdering", - "rating_count": "{count, plural, one {# stjerne} other {# stjerner}}", - "rating_description": "Vis EXIF-klassificeringen i infopanelet", - "reaction_options": "Reaktionsindstillinger", - "read_changelog": "Læs ændringslog", - "readonly_mode_disabled": "Skrivebeskyttet tilstand deaktiveret", - "readonly_mode_enabled": "Skrivebeskyttet tilstand aktiveret", - "ready_for_upload": "Klar til upload", - "reassign": "Gentildel", - "reassigned_assets_to_existing_person": "{count, plural, one {# mediefil} other {# mediefiler}} er blevet gentildelt til {name, select, null {en eksisterende person} other {{name}}}", - "reassigned_assets_to_new_person": "Gentildelt {count, plural, one {# aktiv} other {# aktiver}} til en ny person", - "reassing_hint": "Tildel valgte mediefiler til en eksisterende person", - "recent": "For nylig", - "recent-albums": "Seneste albums", - "recent_searches": "Seneste søgninger", - "recently_added": "Senest tilføjet", - "recently_added_page_title": "Nyligt tilføjet", - "recently_taken": "Taget for nylig", - "recently_taken_page_title": "Taget For nylig", - "refresh": "Opdatér", - "refresh_encoded_videos": "Opdater kodede videoer", - "refresh_faces": "Opdater ansigter", - "refresh_metadata": "Opdater metadata", - "refresh_thumbnails": "Opdater forhåndsvisning", - "refreshed": "Opdateret", - "refreshes_every_file": "Opdaterer alle filer", - "refreshing_encoded_video": "Opdaterer kodet video", - "refreshing_faces": "Opdaterer ansigter", - "refreshing_metadata": "Opdaterer metadata", - "regenerating_thumbnails": "Regenererer forhåndsvisninger", - "remote": "Eksternt", - "remote_assets": "Eksterne objekter", - "remote_media_summary": "Oversigt over eksterne media", - "remove": "Fjern", - "remove_assets_album_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra albummet?", - "remove_assets_shared_link_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra dette delte link?", - "remove_assets_title": "Fjern mediefiler?", - "remove_custom_date_range": "Fjern tilpasset datointerval", - "remove_deleted_assets": "Fjern slettede mediefiler", - "remove_from_album": "Fjern fra album", - "remove_from_album_action_prompt": "{count} fjernet fra albummet", - "remove_from_favorites": "Fjern fra favoritter", - "remove_from_lock_folder_action_prompt": "{count} fjernet fra den låste mappe", - "remove_from_locked_folder": "Fjern fra låst mappe", - "remove_from_locked_folder_confirmation": "Er du sikker på at du vil flytte disse billeder og videoer ud af den låste mappe? De vil være synlige i dit bibliotek.", - "remove_from_shared_link": "Fjern fra delt link", - "remove_memory": "Fjern minde", - "remove_photo_from_memory": "Fjern foto fra dette minde", - "remove_tag": "Fjern tag", - "remove_url": "Fjern URL", - "remove_user": "Fjern bruger", - "removed_api_key": "Fjernede API-nøgle: {name}", - "removed_from_archive": "Fjernet fra arkiv", - "removed_from_favorites": "Fjernet fra favoritter", - "removed_from_favorites_count": "{count, plural, other {Fjernede #}} fra favoritter", - "removed_memory": "Fjernede minde", - "removed_photo_from_memory": "Fjernede foto fra minde", - "removed_tagged_assets": "Fjernede tag fra {count, plural, one {# aktiv} other {# aktiver}}", - "rename": "Omdøb", - "repair": "Reparér", - "repair_no_results_message": "Usporede og manglende filer vil blive vist her", - "replace_with_upload": "Erstat med upload", - "repository": "Depot", - "require_password": "Kræv adgangskode", - "require_user_to_change_password_on_first_login": "Kræv at bruger skifter adgangskode ved første login", - "rescan": "Genopfrisk", - "reset": "Nulstil", - "reset_password": "Nulstil adgangskode", - "reset_people_visibility": "Nulstil personsynlighed", - "reset_pin_code": "Nulstil PIN kode", - "reset_pin_code_description": "Hvis du har glemt din PIN-kode, kan du kontakte serveradministratoren for at få den nulstillet", - "reset_pin_code_success": "PIN-koden er Nulstillet", - "reset_pin_code_with_password": "Du kan altid nulstille din PIN-kode med dit password", - "reset_sqlite": "Reset SQLite Databasen", - "reset_sqlite_confirmation": "Er du sikker på, at du vil nulstille SQLite databasen? Du er nødt til at logge ud og ind igen for at gensynkronisere dine data", - "reset_sqlite_success": "Vellykket reset af SQLite databasen", - "reset_to_default": "Nulstil til standard", - "resolution": "Opløsning", - "resolve_duplicates": "Løs dubletter", - "resolved_all_duplicates": "Alle dubletter løst", - "restore": "Gendan", - "restore_all": "Gendan alle", - "restore_trash_action_prompt": "{count} genskabt fra papirkurven", - "restore_user": "Gendan bruger", - "restored_asset": "Gendannet mediefilen", - "resume": "Genoptag", - "resume_paused_jobs": "Fortsæt {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Forsøg upload igen", - "review_duplicates": "Gennemgå dubletter", - "review_large_files": "Gennemgå store filer", - "role": "Rolle", - "role_editor": "Redaktør", - "role_viewer": "Seer", - "running": "Kører", - "save": "Gem", - "save_to_gallery": "Gem til galleri", - "saved": "Gemt", - "saved_api_key": "Gemt API-nøgle", - "saved_profile": "Gemt profil", - "saved_settings": "Gemte indstillinger", - "say_something": "Skriv noget", - "scaffold_body_error_occurred": "Der opstod en fejl", - "scan": "Scan", - "scan_all_libraries": "Skan alle biblioteker", - "scan_library": "Skan", - "scan_settings": "Skanningsindstillinger", - "scanning": "Scanning", - "scanning_for_album": "Skanner efter albummer...", - "search": "Søg", - "search_albums": "Søg i albummer", - "search_by_context": "Søg efter kontekst", - "search_by_description": "Søg efter beskrivelse", - "search_by_description_example": "Vandredag i Paris", - "search_by_filename": "Søg efter filnavn eller filtypenavn", - "search_by_filename_example": "dvs. IMG_1234.JPG eller PNG", - "search_by_ocr": "Søg via OCR", - "search_by_ocr_example": "Søg efter tekst i dine billeder", - "search_camera_lens_model": "Søg objektiv model...", - "search_camera_make": "Søg efter kameraproducent...", - "search_camera_model": "Søg efter kameramodel...", - "search_city": "Søg efter by...", - "search_country": "Søg efter land...", - "search_filter_apply": "Tilføj filter", - "search_filter_camera_title": "Vælg type af kamera", - "search_filter_date": "Dato", - "search_filter_date_interval": "{start} til {end}", - "search_filter_date_title": "Vælg et datointerval", - "search_filter_display_option_not_in_album": "Ikke i album", - "search_filter_display_options": "Visningsindstillinger", - "search_filter_filename": "Søg efter filnavn", - "search_filter_location": "Lokation", - "search_filter_location_title": "Vælg lokation", - "search_filter_media_type": "Medietype", - "search_filter_media_type_title": "Vælg medietype", - "search_filter_ocr": "Søg via OCR", - "search_filter_people_title": "Vælg personer", - "search_filter_star_rating": "Stjerne Vurdering", - "search_for": "Søg efter", - "search_for_existing_person": "Søg efter eksisterende person", - "search_no_more_result": "Ikke flere resultater", - "search_no_people": "Ingen personer", - "search_no_people_named": "Ingen personer med navnet \"{name}\"", - "search_no_result": "Ingen resultater fundet, prøv en anden søgestreng eller kombination", - "search_options": "Søgemuligheder", - "search_page_categories": "Kategorier", - "search_page_motion_photos": "Bevægelsesbilleder", - "search_page_no_objects": "Ingen elementinfomation er tilgængelig", - "search_page_no_places": "Ingen placeringsinformation er tilgængelig", - "search_page_screenshots": "Skærmbilleder", - "search_page_search_photos_videos": "Søg i dine billeder og videoer", - "search_page_selfies": "Selfier", - "search_page_things": "Ting", - "search_page_view_all_button": "Vis alt", - "search_page_your_activity": "Din aktivitet", - "search_page_your_map": "Dit kort", - "search_people": "Søg i personer", - "search_places": "Søg i steder", - "search_rating": "Søg efter vurdering...", - "search_result_page_new_search_hint": "Ny søgning", - "search_settings": "søgeindstillinger", - "search_state": "Søg efter lansdel...", - "search_suggestion_list_smart_search_hint_1": "Smart søgnining er slået til som standard. For at søge efter metadata brug syntaksen ", - "search_suggestion_list_smart_search_hint_2": "m:dit-søgeord", - "search_tags": "Søg tags...", - "search_timezone": "Søg i tidszone...", - "search_type": "Søg efter type", - "search_your_photos": "Søg i dine billeder", - "searching_locales": "Søger lokaler...", - "second": "Sekund", - "see_all_people": "Se alle personer", - "select": "Vælg", - "select_album": "Vælg album", - "select_album_cover": "Vælg albumcover", - "select_albums": "Vælg albummer", - "select_all": "Vælg alle", - "select_all_duplicates": "Vælg alle dubletter", - "select_all_in": "Vælg alt i {group}", - "select_avatar_color": "Vælg avatarfarve", - "select_count": "{count, plural, one {Vælg #} other {Vælg #}}", - "select_cutoff_date": "Vælg stop-dato", - "select_face": "Vælg ansigt", - "select_featured_photo": "Vælg forsidebillede", - "select_from_computer": "Vælg fra computer", - "select_keep_all": "Vælg gem alle", - "select_library_owner": "Vælg biblioteksejer", - "select_new_face": "Vælg nyt ansigt", - "select_people": "Vælg personer", - "select_person": "Vælg person", - "select_person_to_tag": "Vælg en person at tagge", - "select_photos": "Vælg billeder", - "select_trash_all": "Vælg smid alle ud", - "select_user_for_sharing_page_err_album": "Fejlede i at oprette et nyt album", - "selected": "Valgt", - "selected_count": "{count, plural, one {# valgt} other {# valgte}}", - "selected_gps_coordinates": "Udvalgte GPS Koordinater", - "send_message": "Send besked", - "send_welcome_email": "Send velkomstemail", - "server_endpoint": "Server endepunkt", - "server_info_box_app_version": "Applikationsversion", - "server_info_box_server_url": "Server URL", - "server_offline": "Server offline", - "server_online": "Server online", - "server_privacy": "Serverens privatliv", - "server_restarting_description": "Denne side opdateres om et øjeblik.", - "server_restarting_title": "Serveren genstarter", - "server_stats": "Serverstatus", - "server_update_available": "Serveropdatering er tilgængelig", - "server_version": "Server version", - "set": "Indstil", - "set_as_album_cover": "Indstil som albumcover", - "set_as_featured_photo": "Indstil som fremhævet billede", - "set_as_profile_picture": "Indstil som profilbillede", - "set_date_of_birth": "Indstil fødselsdato", - "set_profile_picture": "Indstil profilbillede", - "set_slideshow_to_fullscreen": "Sæt diasshow til fuldskærmsvisning", - "set_stack_primary_asset": "Angiv som primært billede", - "setting_image_viewer_help": "Detaljeret visning indlæser miniaturebilleder først. Herefter indlæses mediumstørrelse forhåndsvisning af billedet (hvis dette er slået til), for til sidst at vise originalen (hvis dette er slået til).", - "setting_image_viewer_original_subtitle": "Slå indlæsning af originalbillede i fuld størrelse til (stort!). Deaktiver for at reducere dataforbruget (både på netværket og for enhedscache).", - "setting_image_viewer_original_title": "Indlæs originalbillede", - "setting_image_viewer_preview_subtitle": "Slå indlæsning af et mediumstørrelse billede til. Slå fra for enten direkte at indlæse originalen eller kun at bruge miniaturebilledet.", - "setting_image_viewer_preview_title": "Indlæs forhåndsvisning af billedet", - "setting_image_viewer_title": "Billeder", - "setting_languages_apply": "Anvend", - "setting_languages_subtitle": "Ændr app-sprog", - "setting_notifications_notify_failures_grace_period": "Giv besked om fejl med sikkerhedskopiering i baggrunden: {duration}", - "setting_notifications_notify_hours": "{count} timer", - "setting_notifications_notify_immediately": "med det samme", - "setting_notifications_notify_minutes": "{count} minutter", - "setting_notifications_notify_never": "aldrig", - "setting_notifications_notify_seconds": "{count} sekunder", - "setting_notifications_single_progress_subtitle": "Detaljeret uploadstatus pr. element", - "setting_notifications_single_progress_title": "Vis detaljeret baggrundsuploadstatus", - "setting_notifications_subtitle": "Tilpas dine notifikationspræferencer", - "setting_notifications_total_progress_subtitle": "Samlet uploadstatus (færdige/samlet antal elementer)", - "setting_notifications_total_progress_title": "Vis samlet baggrunds upload status", - "setting_video_viewer_auto_play_subtitle": "Begynd automatisk at afspille videoer, når de åbnes", - "setting_video_viewer_auto_play_title": "Automatisk afspilning af videoer", - "setting_video_viewer_looping_title": "Genafspilning", - "setting_video_viewer_original_video_subtitle": "Når der streames video fra serveren, afspil da den originale selv når en omkodet udgave er tilgængelig. Kan føre til buffering. Videoer, der er tilgængelige lokalt, afspilles i original kvalitet uanset denne indstilling.", - "setting_video_viewer_original_video_title": "Tving original video", - "settings": "Indstillinger", - "settings_require_restart": "Genstart venligst Immich for at anvende denne ændring", - "settings_saved": "Indstillinger er gemt", - "setup_pin_code": "Indstil en PIN kode", - "share": "Del", - "share_action_prompt": "Delte {count} objekter", - "share_add_photos": "Tilføj billeder", - "share_assets_selected": "{count} valgt", - "share_dialog_preparing": "Forbereder...", - "share_link": "Del link", - "shared": "Delt", - "shared_album_activities_input_disable": "Kommentarer er deaktiveret", - "shared_album_activity_remove_content": "Vil du slette denne aktivitet?", - "shared_album_activity_remove_title": "Slet aktivitet", - "shared_album_section_people_action_error": "Der opstod en fejl i fjernelsen fra albummet", - "shared_album_section_people_action_leave": "Fjern brugere fra albummet", - "shared_album_section_people_action_remove_user": "Fjern brugere fra albummet", - "shared_album_section_people_title": "PERSONER", - "shared_by": "Delt af", - "shared_by_user": "Delt af {user}", - "shared_by_you": "Delt af dig", - "shared_from_partner": "Billeder fra {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Uploadet", - "shared_link_app_bar_title": "Delte links", - "shared_link_clipboard_copied_massage": "Kopieret til udklipsholderen", - "shared_link_clipboard_text": "Link: {link}\nAdgangskode: {password}", - "shared_link_create_error": "Der opstod en fejl i oprettelsen af et delt link", - "shared_link_custom_url_description": "Adgang til dette delte link med en selvdefineret URL", - "shared_link_edit_description_hint": "Indtast beskrivelse", - "shared_link_edit_expire_after_option_day": "1 dag", - "shared_link_edit_expire_after_option_days": "{count} dage", - "shared_link_edit_expire_after_option_hour": "1 time", - "shared_link_edit_expire_after_option_hours": "{count} timer", - "shared_link_edit_expire_after_option_minute": "1 minut", - "shared_link_edit_expire_after_option_minutes": "{count} minutter", - "shared_link_edit_expire_after_option_months": "{count} måneder", - "shared_link_edit_expire_after_option_year": "{count} år", - "shared_link_edit_password_hint": "Indtast kodeordet", - "shared_link_edit_submit_button": "Opdater link", - "shared_link_error_server_url_fetch": "Kan ikke finde server URL", - "shared_link_expires_day": "Udløber om {count} dag", - "shared_link_expires_days": "Udløber om {count} dage", - "shared_link_expires_hour": "Udløber om {count} time", - "shared_link_expires_hours": "Udløber om {count} timer", - "shared_link_expires_minute": "Udløber om {count} minut", - "shared_link_expires_minutes": "Udløber om {count} minutter", - "shared_link_expires_never": "Udløber aldrig", - "shared_link_expires_second": "Udløber om {count} sekund", - "shared_link_expires_seconds": "Udløber om {count} sekunder", - "shared_link_individual_shared": "Individuelt delt", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Håndter delte links", - "shared_link_options": "Muligheder for delt link", - "shared_link_password_description": "Kodeord krævet for at få adgang til dette delte link", - "shared_links": "Delte links", - "shared_links_description": "Del billeder og videoer med et link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# delte billeder & videoer.}}", - "shared_with_me": "Delt med mig", - "shared_with_partner": "Delt med {partner}", - "sharing": "Delte", - "sharing_enter_password": "Indtast venligst adgangskoden for at se denne side.", - "sharing_page_album": "Delt albums", - "sharing_page_description": "Opret delte albummer for at dele billeder og video med personer på dit netværk.", - "sharing_page_empty_list": "TOM LISTE", - "sharing_sidebar_description": "Vis et link til deling i sidemenuen", - "sharing_silver_appbar_create_shared_album": "Opret delt album", - "sharing_silver_appbar_share_partner": "Del med partner", - "shift_to_permanent_delete": "tryk på ⇧ for at slette aktiv permanent", - "show_album_options": "Vis albumindstillinger", - "show_albums": "Vis albummer", - "show_all_people": "Vis alle personer", - "show_and_hide_people": "Vis & skjul personer", - "show_file_location": "Vis filplacering", - "show_gallery": "Vis galleri", - "show_hidden_people": "Vis skjulte personer", - "show_in_timeline": "Vis på tidslinje", - "show_in_timeline_setting_description": "Vis billeder og videoer fra denne bruger på din tidslinje", - "show_keyboard_shortcuts": "Vis tastaturgenveje", - "show_metadata": "Vis metadata", - "show_or_hide_info": "Vis eller skjul info", - "show_password": "Vis adgangskode", - "show_person_options": "Vis personindstillinger", - "show_progress_bar": "Vis statuslinje", - "show_schema": "Vis skema", - "show_search_options": "Vis søgeindstillinger", - "show_shared_links": "Vis delte links", - "show_slideshow_transition": "Vis overgang til diasshow", - "show_supporter_badge": "Supporter skilt", - "show_supporter_badge_description": "Vis et supporter ikon", - "show_text_recognition": "Vis tekstgenkendelse", - "show_text_search_menu": "Vis tekstsøgningsmenu", - "shuffle": "Bland", - "sidebar": "Sidebjælke", - "sidebar_display_description": "Vis et link til visningen i sidebjælken", - "sign_out": "Log af", - "sign_up": "Tilmeld", - "size": "Størrelse", - "skip_to_content": "Spring frem til indhold", - "skip_to_folders": "Spring til mapper", - "skip_to_tags": "Spring til tags", - "slideshow": "Diasshow", - "slideshow_repeat": "Gentag diasshow", - "slideshow_repeat_description": "Hop tilbage til begyndelsen når diasshow stopper", - "slideshow_settings": "Diasshowindstillinger", - "sort_albums_by": "Sortér albummer efter...", - "sort_created": "Dato oprettet", - "sort_items": "Antal genstande", - "sort_modified": "Ændret dato", - "sort_newest": "Nyeste foto", - "sort_oldest": "Ældste foto", - "sort_people_by_similarity": "Sorter efter personer der ligner hinanden", - "sort_recent": "Seneste foto", - "sort_title": "Titel", - "source": "Kilde", - "stack": "Stak", - "stack_action_prompt": "{count} stakket", - "stack_duplicates": "Stak dubletter", - "stack_select_one_photo": "Vælg ét hovedbillede til stakken", - "stack_selected_photos": "Stak valgte billeder", - "stacked_assets_count": "Stablet {count, plural, one {# aktiv} other {# aktiver}}", - "stacktrace": "Stacktrace", - "start": "Start", - "start_date": "Startdato", - "start_date_before_end_date": "Startdato skal ligge før slutdato", - "state": "Stat", - "status": "Status", - "stop_casting": "Stop casting", - "stop_motion_photo": "Stopmotionbillede", - "stop_photo_sharing": "Stop med at dele dine billeder?", - "stop_photo_sharing_description": "{partner} vil ikke længere kunne tilgå dine billeder.", - "stop_sharing_photos_with_user": "Afslut deling af dine fotos med denne bruger", - "storage": "Lagringsplads", - "storage_label": "Lagringsmærkat", - "storage_quota": "Lagringskvota", - "storage_usage": "{used} ud af {available} brugt", - "submit": "Indsend", - "success": "Vellykket", - "suggestions": "Anbefalinger", - "sunrise_on_the_beach": "Solopgang på stranden", - "support": "Support", - "support_and_feedback": "Support og feedback", - "support_third_party_description": "Din Immich-installation blev sammensat af en tredjepart. Problemer, du oplever, kan være forårsaget af denne udvikler, så rejs venligst problemer med dem i første omgang ved at bruge nedenstående links.", - "swap_merge_direction": "Byt retning for sammenfletning", - "sync": "Synkronisér", - "sync_albums": "Synkroniser albummer", - "sync_albums_manual_subtitle": "Synkroniser alle uploadet billeder og videoer til de valgte backupalbummer", - "sync_local": "Synkroniser lokalt", - "sync_remote": "Synkroniser eksternt", - "sync_status": "Synkroniserings Status", - "sync_status_subtitle": "Se og administrér synkroniseringssystemet", - "sync_upload_album_setting_subtitle": "Opret og upload dine billeder og videoer til de valgte albummer i Immich", - "tag": "Tag", - "tag_assets": "Tag mediefiler", - "tag_created": "Oprettet tag: {tag}", - "tag_feature_description": "Gennemse billeder og videoer grupperet efter logiske tag-emner", - "tag_not_found_question": "Kan du ikke finde et tag? Opret et nyt tag.", - "tag_people": "Tag personer", - "tag_updated": "Opdateret tag: {tag}", - "tagged_assets": "Tagget {count, plural, one {# aktiv} other {# aktiver}}", - "tags": "Tags", - "tap_to_run_job": "Tryk for at køre jobbet", - "template": "Skabelon", - "text_recognition": "Tekst genkendelse", - "theme": "Tema", - "theme_selection": "Temavalg", - "theme_selection_description": "Indstil automatisk temaet til lyst eller mørkt baseret på din browsers systempræference", - "theme_setting_asset_list_storage_indicator_title": "Vis opbevaringsindikator på filer", - "theme_setting_asset_list_tiles_per_row_title": "Antal elementer per række ({count})", - "theme_setting_colorful_interface_subtitle": "Tilføj primær farve til baggrundsoverflader.", - "theme_setting_colorful_interface_title": "Farverig grænseflade", - "theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten i billedfremviseren", - "theme_setting_image_viewer_quality_title": "Billedfremviserkvalitet", - "theme_setting_primary_color_subtitle": "Vælg en farve til primære handlinger og accenter.", - "theme_setting_primary_color_title": "Primær farve", - "theme_setting_system_primary_color_title": "Brug systemfarver", - "theme_setting_system_theme_switch": "Automatisk (Følg systemindstillinger)", - "theme_setting_theme_subtitle": "Vælg appens temaindstilling", - "theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning", - "theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til", - "they_will_be_merged_together": "De vil blive slået sammen", - "third_party_resources": "Tredjepartsressourcer", - "time": "Tid", - "time_based_memories": "Tidsbaserede minder", - "time_based_memories_duration": "Antal sekunder, hvert billede skal vises.", - "timeline": "Tidslinje", - "timezone": "Tidszone", - "to_archive": "Arkivér", - "to_change_password": "Skift adgangskode", - "to_favorite": "Gør til favorit", - "to_login": "Login", - "to_multi_select": "for at vælge flere", - "to_parent": "Gå et niveau op", - "to_select": "for at vælge", - "to_trash": "Papirkurv", - "toggle_settings": "Skift indstillinger", - "toggle_theme_description": "Skift tema", - "total": "Total", - "total_usage": "Samlet forbrug", - "trash": "Papirkurv", - "trash_action_prompt": "{count} flyttet til papirkurven", - "trash_all": "Smid alle ud", - "trash_count": "Slet {count, number}", - "trash_delete_asset": "Flyt mediefil til Papirkurv", - "trash_emptied": "Tømte papirkurven", - "trash_no_results_message": "Billeder og videoer markeret til sletning vil blive vist her.", - "trash_page_delete_all": "Slet alt", - "trash_page_empty_trash_dialog_content": "Vil du tømme papirkurven? Disse elementer vil blive permanent fjernet fra Immich", - "trash_page_info": "Slettede elementer vil blive slettet permanent efter {days} dage", - "trash_page_no_assets": "Ingen slettede elementer", - "trash_page_restore_all": "Gendan alt", - "trash_page_select_assets_btn": "Vælg elementer", - "trash_page_title": "Papirkurv ({count})", - "trashed_items_will_be_permanently_deleted_after": "Mediefiler i papirkurven vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.", - "trigger": "Udløser", - "trigger_asset_uploaded": "Mediefil uploaded", - "trigger_asset_uploaded_description": "Udløses, når et nyt asset bliver uploaded", - "trigger_description": "En begivenhed, der starter en arbejdsgang", - "trigger_person_recognized": "Peron genkendt", - "trigger_person_recognized_description": "Udløses, når en person er detekteret", - "trigger_type": "Udløsertype", - "troubleshoot": "Fejlfinding", - "type": "Type", - "unable_to_change_pin_code": "Kunne ikke ændre PIN kode", - "unable_to_check_version": "Kan ikke tjekke app- eller serverversion", - "unable_to_setup_pin_code": "Kunne ikke sætte PIN kode", - "unarchive": "Fjern fra arkiv", - "unarchive_action_prompt": "{count} slettet fra Arkiv", - "unarchived_count": "{count, plural, other {Uarkiveret #}}", - "undo": "Fortryd", - "unfavorite": "Fjern favorit", - "unfavorite_action_prompt": "{count} slettet fra Favoritter", - "unhide_person": "Stop med at skjule person", - "unknown": "Ukendt", - "unknown_country": "Ukendt land", - "unknown_date": "Ukendt dato", - "unknown_year": "Ukendt år", - "unlimited": "Ubegrænset", - "unlink_motion_video": "Fjern link til bevægelsesvideo", - "unlink_oauth": "Frakobl OAuth", - "unlinked_oauth_account": "Frakoblede OAuth-konto", - "unmute_memories": "Ophæv dæmpning af minder", - "unnamed_album": "Unavngivet album", - "unnamed_album_delete_confirmation": "Er du sikker på, at du vil slette dette album?", - "unnamed_share": "Unavngivet deling", - "unsaved_change": "Ændring, der ikke er gemt", - "unselect_all": "Fravælg alle", - "unselect_all_duplicates": "Fjern markeringen af alle dubletter", - "unselect_all_in": "Afmarkér alle i {group}", - "unstack": "Fjern fra stak", - "unstack_action_prompt": "{count} ustakket", - "unstacked_assets_count": "Ikke-stablet {count, plural, one {# aktiv} other {# aktiver}}", - "unsupported_field_type": "Ikke-understøttet felttype", - "untagged": "Umærket", - "untitled_workflow": "Unavngivet arbejdsgang", - "up_next": "Næste", - "update_location_action_prompt": "Opdater lokationen for {count} valgte objekter med:", - "updated_at": "Opdateret", - "updated_password": "Opdaterede adgangskode", - "upload": "Upload", - "upload_concurrency": "Upload samtidighed", - "upload_details": "Upload detaljer", - "upload_dialog_info": "Vil du sikkerhedskopiere de(t) valgte element(er) til serveren?", - "upload_dialog_title": "Upload element", - "upload_errors": "Upload afsluttet med {count, plural, one {# fejl} other {# fejl}}. Opdater siden for at se nye uploadaktiver.", - "upload_finished": "Upload fuldført", - "upload_progress": "Resterende {remaining, number} - Behandlet {processed, number}/{total, number}", - "upload_skipped_duplicates": "Sprang over {count, plural, one {# duplet aktiv} other {# duplikerede aktiver}}", - "upload_status_duplicates": "Dubletter", - "upload_status_errors": "Fejl", - "upload_status_uploaded": "Uploadet", - "upload_success": "Upload gennemført. Opdater siden for at se nye uploadaktiver.", - "upload_to_immich": "Upload til Immich ({count})", - "uploading": "Uploader", - "uploading_media": "Uploader media", - "url": "URL", - "usage": "Forbrug", - "use_biometric": "Brug biometrisk", - "use_current_connection": "Brug nuværende forbindelse", - "use_custom_date_range": "Brug tilpasset datointerval i stedet", - "user": "Bruger", - "user_has_been_deleted": "Denne bruger er slettet.", - "user_id": "Bruger-ID", - "user_liked": "{user} kunne lide {type, select, photo {dette billede} video {denne video} asset {dette aktiv} other {det}}", - "user_pin_code_settings": "PIN Kode", - "user_pin_code_settings_description": "Administrer din PIN kode", - "user_privacy": "Brugerprivatliv", - "user_purchase_settings": "Køb", - "user_purchase_settings_description": "Administrer dit køb", - "user_role_set": "Indstil {user} som {role}", - "user_usage_detail": "Detaljer om brugers forbrug", - "user_usage_stats": "Kontoens anvendelsesstatistik", - "user_usage_stats_description": "Vis konto anvendelsesstatistik", - "username": "Brugernavn", - "users": "Brugere", - "users_added_to_album_count": "Tilføjet {count, plural, one {# bruker} other {# brukere}} til albummet", - "utilities": "Værktøjer", - "validate": "Validér", - "validate_endpoint_error": "Indtast en gyldig URL", - "validation_error": "Validerings fejl", - "variables": "Variabler", - "version": "Version", - "version_announcement_closing": "Din ven, Alex", - "version_announcement_message": "Hej! En ny version af Immich er tilgængelig. Brug venligst lidt tid på at læse udgivelsesbemærkningerne for at sikre, at din opsætning er opdateret for at forhindre fejlkonfigurationer, især hvis du bruger WatchTower eller en mekanisme, der håndterer automatisk opdatering af din Immich-instans.", - "version_history": "Versionshistorik", - "version_history_item": "Installerede {version} den {date}", - "video": "Video", - "video_hover_setting": "Afspil miniaturevisning af video når musemarkøren er over den", - "video_hover_setting_description": "Afspil miniaturevisning for videoer når musemarkøren holdes over elementet. Selv når det er deaktiveret, kan afspilning startes ved at holde musen over afspilningsikonet.", - "videos": "Videoer", - "videos_count": "{count, plural, one {# Video} other {# Videoer}}", - "videos_only": "Kun videoer", - "view": "Se", - "view_album": "Se album", - "view_all": "Se alle", - "view_all_users": "Se alle brugere", - "view_asset_owners": "Se element ejere", - "view_details": "Vis detaljer", - "view_in_timeline": "Se på tidslinjen", - "view_link": "Vis Link", - "view_links": "Vis links", - "view_name": "Se", - "view_next_asset": "Se næste medie", - "view_previous_asset": "Se forrige medie", - "view_qr_code": "Vis QR kode", - "view_similar_photos": "Se lignende billeder", - "view_stack": "Vis stak", - "view_user": "Vis bruger", - "viewer_remove_from_stack": "Fjern fra stak", - "viewer_stack_use_as_main_asset": "Brug som hovedelement", - "viewer_unstack": "Fjern fra stak", - "visibility_changed": "Synlighed ændret for {count, plural, one {# person} other {# personer}}", - "visual": "Visuel", - "visual_builder": "Visuel builder", - "waiting": "Venter", - "waiting_count": "Venter: {count}", - "warning": "Advarsel", - "week": "Uge", - "welcome": "Velkommen", - "welcome_to_immich": "Velkommen til Immich", - "width": "Bredde", - "wifi_name": "Wi-Fi navn", - "workflow_delete_prompt": "Er du sikker på, at du vil slette denne arbejdsgang?", - "workflow_deleted": "Arbejdsgang slettet", - "workflow_description": "Arbejdsgangsbeskrivelse", - "workflow_info": "Information om arbejdsgang", - "workflow_json": "Arbejdsgang JSON", - "workflow_json_help": "Rediger arbejdsgangskonfiguration i JSON-format. Ændringer vil synkroniseres til den visuelle opbygger.", - "workflow_name": "Navn på arbejdsgang", - "workflow_navigation_prompt": "Er du sikker på, at du vil forlade uden at gemme dine ændringer?", - "workflow_summary": "Arbejdsgangsoversigt", - "workflow_update_success": "Arbejdsgang opdateret korrekt", - "workflow_updated": "Arbejdsgang opdateret", - "workflows": "Arbejdsgange", - "workflows_help_text": "Arbejdsgange automatiserer handlinger på dine filer baseret på udløsere og filtre", - "wrong_pin_code": "Forkert PIN kode", - "year": "År", - "years_ago": "{years, plural, one {# år} other {# år}} siden", - "yes": "Ja", - "you_dont_have_any_shared_links": "Du har ikke nogen delte links", - "your_wifi_name": "Dit Wi-Fi navn", - "zero_to_clear_rating": "Tryk på 0 for at fjerne fil vurderingen", - "zoom_image": "Zoom billede", - "zoom_to_bounds": "Zoom til grænserne" -} +{} diff --git a/i18n/de.json b/i18n/de.json index 8959e20831..0967ef424b 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -1,2401 +1 @@ -{ - "about": "Über", - "account": "Konto", - "account_settings": "Kontoeinstellungen", - "acknowledge": "Bestätigen", - "action": "Aktion", - "action_common_update": "Aktualisieren", - "action_description": "Eine Reihe von Aktionen, die an den gefilterten Assets ausgeführt werden sollen", - "actions": "Aktionen", - "active": "Aktiv", - "active_count": "Aktive:{count}", - "activity": "Aktivität", - "activity_changed": "Aktivität ist {enabled, select, true {aktiviert} other {deaktiviert}}", - "add": "Hinzufügen", - "add_a_description": "Beschreibung hinzufügen", - "add_a_location": "Standort hinzufügen", - "add_a_name": "Name hinzufügen", - "add_a_title": "Titel hinzufügen", - "add_action": "Aktion hinzufügen", - "add_action_description": "Klicken um eine Aktion hinzuzufügen", - "add_assets": "Assets hinzufügen", - "add_birthday": "Geburtsdatum hinzufügen", - "add_endpoint": "Endpunkt hinzufügen", - "add_exclusion_pattern": "Ausschlussmuster hinzufügen", - "add_filter": "Filter hinzufügen", - "add_filter_description": "Klicken um eine Filterbedingung hinzuzufügen", - "add_location": "Standort hinzufügen", - "add_more_users": "Weitere Nutzer hinzufügen", - "add_partner": "Partner hinzufügen", - "add_path": "Pfad hinzufügen", - "add_photos": "Fotos hinzufügen", - "add_tag": "Tag hinzufügen", - "add_to": "Hinzufügen zu …", - "add_to_album": "Zu Album hinzufügen", - "add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt", - "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Einige lokale Dateien konnten nicht zum Album hinzugefügt werden", - "add_to_album_toggle": "Auswahl umschalten für {album}", - "add_to_albums": "Zu Alben hinzufügen", - "add_to_albums_count": "Zu Alben hinzufügen ({count})", - "add_to_bottom_bar": "Hinzufügen zu", - "add_to_shared_album": "Zu geteiltem Album hinzufügen", - "add_upload_to_stack": "Upload zum Stapel hinzufügen", - "add_url": "URL hinzufügen", - "add_workflow_step": "Workflow-Schritt hinzufügen", - "added_to_archive": "Zum Archiv hinzugefügt", - "added_to_favorites": "Zu Favoriten hinzugefügt", - "added_to_favorites_count": "{count, number} zu Favoriten hinzugefügt", - "admin": { - "add_exclusion_pattern_description": "Ausschlussmuster hinzufügen. Platzhalter, wie *, **, und ? werden unterstützt. Um alle Dateien in einem Verzeichnis namens „Raw\" zu ignorieren, „**/Raw/**“ verwenden. Um alle Dateien zu ignorieren, die auf „.tif“ enden, „**/*.tif“ verwenden. Um einen absoluten Pfad zu ignorieren, „/pfad/zum/ignorieren/**“ verwenden.", - "admin_user": "Administrator", - "asset_offline_description": "Diese Datei einer externen Bibliothek befindet sich nicht mehr auf der Festplatte und wurde in den Papierkorb verschoben. Falls die Datei innerhalb der Bibliothek verschoben wurde, überprüfe deine Zeitleiste auf die neue entsprechende Datei. Um diese Datei wiederherzustellen, stelle bitte sicher, dass Immich auf den unten stehenden Dateipfad zugreifen kann und scanne die Bibliothek.", - "authentication_settings": "Authentifizierungseinstellungen", - "authentication_settings_description": "Passwort-, OAuth- und sonstige Authentifizierungseinstellungen verwalten", - "authentication_settings_disable_all": "Bist du sicher, dass du alle Anmeldemethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.", - "authentication_settings_reenable": "Nutze einen Server-Befehl zur Reaktivierung.", - "background_task_job": "Hintergrundaufgaben", - "backup_database": "Datenbanksicherung erstellen", - "backup_database_enable_description": "Datenbank regelmäßig sichern", - "backup_keep_last_amount": "Anzahl der aufzubewahrenden früheren Sicherungen", - "backup_onboarding_1_description": "Offsite-Kopie in der Cloud oder an einem anderen physischen Ort.", - "backup_onboarding_2_description": "lokale Kopien auf verschiedenen Geräten. Dazu gehören die Hauptdateien und eine lokale Sicherung dieser Dateien.", - "backup_onboarding_3_description": "Kopien deiner Daten inklusive Originaldateien. Dies umfasst 1 Kopie an einem anderen Ort und 2 lokale Kopien.", - "backup_onboarding_description": "Eine 3-2-1 Sicherungsstrategie wird empfohlen, um deine Daten zu schützen. Du solltest sowohl Kopien deiner hochgeladenen Fotos/Videos als auch der Immich-Datenbank aufbewahren, um eine umfassende Sicherungslösung zu haben.", - "backup_onboarding_footer": "Weitere Informationen zum Sichern von Immich findest du in der Dokumentation.", - "backup_onboarding_parts_title": "Eine 3-2-1-Sicherung umfasst:", - "backup_onboarding_title": "Sicherungen", - "backup_settings": "Einstellungen für Datenbanksicherung", - "backup_settings_description": "Einstellungen zur regelmäßigen Sicherung der Datenbank. Hinweis: Diese Jobs werden nicht überwacht und du wirst nicht über Fehler informiert.", - "cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}", - "config_set_by_file": "Ist derzeit in einer Konfigurationsdatei festgelegt", - "confirm_delete_library": "Bist du sicher, dass du die Bibliothek {library} löschen willst?", - "confirm_delete_library_assets": "Bist du sicher, dass du diese Bibliothek löschen willst? Dies löscht {count, plural, one {# enthaltenes Objekt} other {alle # enthaltenen Objekte}} aus Immich und kann nicht rückgängig gemacht werden. Die Dateien bleiben auf der Festplatte erhalten.", - "confirm_email_below": "Bestätige, indem du unten \"{email}\" eingibst", - "confirm_reprocess_all_faces": "Bist du sicher, dass du alle Gesichter erneut verarbeiten möchtest? Dies löscht auch alle bereits benannten Personen.", - "confirm_user_password_reset": "Bist du sicher, dass du das Passwort für {user} zurücksetzen möchtest?", - "confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN-Code von {user} zurücksetzen möchtest?", - "copy_config_to_clipboard_description": "Kopieren Sie die aktuelle Systemkonfiguration als JSON-Objekt in die Zwischenablage", - "create_job": "Aufgabe erstellen", - "cron_expression": "Cron-Zeitangabe", - "cron_expression_description": "Setze das Scanintervall im Cron-Format. Hilfe mit dem Format bietet dir dabei z. B. der Crontab Guru", - "cron_expression_presets": "Vorlagen für Cron-Zeitangabe", - "disable_login": "Login deaktivieren", - "duplicate_detection_job_description": "Diese Aufgabe führt das maschinelle Lernen für jede Datei aus, um Duplikate zu finden. Diese Aufgabe beruht auf der intelligenten Suche", - "exclusion_pattern_description": "Mit Ausschlussmustern können Dateien und Ordner beim Scannen Ihrer Bibliothek ignoriert werden. Dies ist nützlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren möchtest, wie z. B. RAW-Dateien.", - "export_config_as_json_description": "Laden Sie die aktuelle Systemkonfiguration als JSON-Datei herunter", - "external_libraries_page_description": "Externe Bibliotheksseite für Administratoren", - "face_detection": "Gesichtserfassung", - "face_detection_description": "Diese Aufgabe erfasst Gesichter in Dateien mittels maschinellen Lernens. Bei Videos wird nur die Miniaturansicht verwendet. „Aktualisieren“ verarbeitet alle Dateien neu. „Zurücksetzen“ setzt zusätzlich alle Gesichter zurück. „Fehlende“ stellt nur nicht verarbeitete Dateien in die Warteschlange. Erfasste Gesichter werden zur Gesichtsidentifizierung in die Warteschlange gestellt, um sie in bestehende oder neue Personen zu gruppieren.", - "facial_recognition_job_description": "Diese Aufgabe gruppiert im Anschluss an die Gesichtserfassung die erfassten Gesichter zu Personen. „Zurücksetzen“ gruppiert alle Gesichter neu, während „Fehlende“ Gesichter ohne Zuordnung in die Warteschlange stellt.", - "failed_job_command": "Befehl {command} ist für Aufgabe {job} fehlgeschlagen", - "force_delete_user_warning": "WARNUNG: Diese Aktion löscht sofort den Benutzer und all seine Dateien. Dies kann nicht rückgängig gemacht werden und die Dateien können nicht wiederhergestellt werden.", - "image_format": "Format", - "image_format_description": "WebP erzeugt kleinere Dateien als JPEG, ist aber etwas langsamer in der Erstellung.", - "image_fullsize_description": "Hochauflösendes Bild mit entfernten Metadaten, das beim Zoomen verwendet wird", - "image_fullsize_enabled": "Hochauflösende Vorschaubilder aktivieren", - "image_fullsize_enabled_description": "Generiere Hochauflösende Vorschaubilder in Originalauflösung für nicht web-kompatibel Formate. Wenn \"Eingebettete Vorschau bevorzugen\" aktiviert ist, werden eingebettete Vorschaubilder direkt verwendet. Hat keinen Einfluss auf web-kompatible Formate wie JPEG.", - "image_fullsize_quality_description": "Qualität der Hochauflösenden Vorschaubilder von 1-100. Höher ist besser, erzeugt aber größere Dateien.", - "image_fullsize_title": "Hochauflösende Vorschaueinstellungen", - "image_prefer_embedded_preview": "Eingebettete Vorschau bevorzugen", - "image_prefer_embedded_preview_setting_description": "Verwende eingebettete Vorschaubilder in RAW-Fotos als Grundlage für die Bildverarbeitung, sofern diese zur Verfügung stehen. Dies kann bei einigen Bildern genauere Farben erzeugen, allerdings ist die Qualität der Vorschau kameraabhängig und das Bild kann mehr Kompressionsartefakte aufweisen.", - "image_prefer_wide_gamut": "Breites Spektrum bevorzugen", - "image_prefer_wide_gamut_setting_description": "Verwendung von Display P3 (DCI-P3) für Miniaturansichten. Dadurch bleibt die Lebendigkeit von Bildern mit breiten Farbräumen besser erhalten, aber die Bilder können auf älteren Geräten mit einer älteren Browserversion etwas anders aussehen. sRGB-Bilder werden im sRGB-Format belassen, um Farbverschiebungen zu vermeiden.", - "image_preview_description": "Mittelgroßes Bild mit entfernten Metadaten, das bei der Betrachtung einer einzelnen Datei und für maschinelles Lernen verwendet wird", - "image_preview_quality_description": "Vorschauqualität von 1-100. Ein höherer Wert ist besser, erzeugt dadurch aber größere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen. Die Einstellung eines niedrigen Wertes kann dafür aber die Qualität des maschinellen Lernens beeinträchtigen.", - "image_preview_title": "Vorschaueinstellungen", - "image_progressive": "Fortschrittlich", - "image_progressive_description": "JPEG-Bilder werden schrittweise kodiert, um ein stufenweises Laden zu ermöglichen. Dies hat keine Auswirkungen auf WebP-Bilder.", - "image_quality": "Qualität", - "image_resolution": "Auflösung", - "image_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Kodierung, haben größere Dateigrößen und können die Reaktionsfähigkeit von Anwendungen beeinträchtigen.", - "image_settings": "Bildeinstellungen", - "image_settings_description": "Qualität und Auflösung von generierten Bildern verwalten", - "image_thumbnail_description": "Kleine Miniaturansicht mit entfernten Metadaten, die bei der Anzeige von Sammlungen von Fotos wie der Zeitleiste verwendet wird", - "image_thumbnail_quality_description": "Qualität der Miniaturansicht von 1-100. Höher ist besser, erzeugt aber größere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen.", - "image_thumbnail_title": "Miniaturansicht-Einstellungen", - "import_config_from_json_description": "Importieren Sie die Systemkonfiguration, indem Sie eine JSON-Konfigurationsdatei hochladen", - "job_concurrency": "{job} (Anzahl gleichzeitiger Prozesse)", - "job_created": "Aufgabe erstellt", - "job_not_concurrency_safe": "Diese Aufgabe ist nicht parallelisierungssicher.", - "job_settings": "Aufgabeneinstellungen", - "job_settings_description": "Die gleichzeitige Ausführung von Aufgaben verwalten", - "jobs_delayed": "{jobCount, plural, other {# verzögert}}", - "jobs_failed": "{jobCount, plural, other {# fehlgeschlagen}}", - "jobs_over_time": "Jobs im Laufe der Zeit", - "library_created": "Bibliothek erstellt: {library}", - "library_deleted": "Bibliothek gelöscht", - "library_details": "Bibliotheksdetails", - "library_folder_description": "Spezifiziere einen Ordner zum Importieren. Dieser Ordner wird einschließlich aller Unterordner nach Bildern und Videos durchsucht.", - "library_remove_exclusion_pattern_prompt": "Bist du sicher, dass du dieses Ausschlussmuster entfernen möchtest?", - "library_remove_folder_prompt": "Bist du sicher, dass du diesen Import-Ordner entfernen möchtest?", - "library_scanning": "Periodisches Scannen", - "library_scanning_description": "Regelmäßiges Durchsuchen der Bibliothek einstellen", - "library_scanning_enable_description": "Regelmäßiges Scannen der Bibliothek aktivieren", - "library_settings": "Externe Bibliothek", - "library_settings_description": "Einstellungen externer Bibliotheken verwalten", - "library_tasks_description": "Überprüfe externe Bibliotheken auf neue und/oder veränderte Medien", - "library_updated": "Aktualisierte Bibliothek", - "library_watching_enable_description": "Überwache externe Bibliotheken auf Dateiänderungen", - "library_watching_settings": "Überwache Bibliothek [EXPERIMENTELL]", - "library_watching_settings_description": "Automatisch auf geänderte Dateien prüfen", - "logging_enable_description": "Aktiviere Logging", - "logging_level_description": "Wenn aktiviert, welches Log-Level genutzt wird.", - "logging_settings": "Protokollierung", - "machine_learning_availability_checks": "Verfügbarkeitschecks", - "machine_learning_availability_checks_description": "Erkenne und bevorzuge verfügbare Machine Learning Server", - "machine_learning_availability_checks_enabled": "Verfügbarkeitschecks einschalten", - "machine_learning_availability_checks_interval": "Überprüfungsinterval", - "machine_learning_availability_checks_interval_description": "Interval in Millisekunden zwischen Verfügbarkeitschecks", - "machine_learning_availability_checks_timeout": "Anfragenzeitüberschreitung", - "machine_learning_availability_checks_timeout_description": "Zeitüberschreitung in Millisekunden für Verfügbarkeitschecks", - "machine_learning_clip_model": "CLIP-Modell", - "machine_learning_clip_model_description": "Der Name eines CLIP-Modells, welches hier aufgeführt ist. Beachte, dass du die Aufgabe \"Intelligente Suche\" für alle Bilder erneut ausführen musst, wenn du das Modell wechselst.", - "machine_learning_duplicate_detection": "Duplikaterkennung", - "machine_learning_duplicate_detection_enabled": "Duplikaterkennung aktivieren", - "machine_learning_duplicate_detection_enabled_description": "Falls diese Option deaktiviert ist, werden exakt identische Dateien dennoch de-dupliziert.", - "machine_learning_duplicate_detection_setting_description": "Verwendung von CLIP-Embeddings zum Erkennen möglicher Duplikate", - "machine_learning_enabled": "Maschinelles Lernen aktivieren", - "machine_learning_enabled_description": "Wenn diese Option deaktiviert ist, werden alle ML-Funktionen unabhängig von den unten aufgeführten Einstellungen deaktiviert.", - "machine_learning_facial_recognition": "Gesichtserkennung", - "machine_learning_facial_recognition_description": "Erfasse, identifiziere und gruppiere Gesichter in Bildern", - "machine_learning_facial_recognition_model": "Gesichtserkennungs-Modell", - "machine_learning_facial_recognition_model_description": "Die Modelle sind in absteigender Reihenfolge ihrer Größe aufgeführt. Größere Modelle sind langsamer und verbrauchen mehr Speicher, liefern aber bessere Ergebnisse. Bitte beachte dabei, dass du die Gesichtserfassungsaufgabe für alle Bilder neu starten musst, wenn du ein Modell änderst.", - "machine_learning_facial_recognition_setting": "Gesichtserkennung aktivieren", - "machine_learning_facial_recognition_setting_description": "Wenn diese Option deaktiviert ist, werden die Bilder nicht für die Gesichtserkennung kodiert und der Abschnitt „Personen“ auf der Seite „Erkunden“ wird nicht dargestellt.", - "machine_learning_max_detection_distance": "Maximaler Erfassungsabstand", - "machine_learning_max_detection_distance_description": "Maximaler Unterschied zwischen zwei Bildern, um sie als Duplikate zu betrachten, im Bereich von 0,001-0,1. Bei höheren Werten werden mehr Duplikate erkannt, aber es kann zu falsch-positiven Ergebnissen kommen.", - "machine_learning_max_recognition_distance": "Maximaler Erkennungsabstand", - "machine_learning_max_recognition_distance_description": "Maximaler Abstand zwischen zwei Gesichtern, die als dieselbe Person angesehen werden, von 0-2. Ein niedrigerer Wert kann verhindern, dass zwei Personen als dieselbe Person eingestuft werden, während ein höherer Wert verhindern kann, dass ein und dieselbe Person als zwei verschiedene Personen eingestuft wird. Bitte beachte dabei, dass es einfacher ist, zwei Personen zu verschmelzen, als eine Person in zwei zu teilen, also wähle nach Möglichkeit einen niedrigeren Schwellenwert.", - "machine_learning_min_detection_score": "Mindest-Erfassungswert", - "machine_learning_min_detection_score_description": "Minimale Konfidenzrate für die Erfassung eines Gesichts von 0-1. Bei niedrigeren Werten werden mehr Gesichter erfasst, aber es kann zu falsch-positiven Ergebnissen kommen.", - "machine_learning_min_recognized_faces": "Mindestens erkannte Gesichter", - "machine_learning_min_recognized_faces_description": "Die Mindestanzahl von erkannten Gesichtern, damit eine Person erstellt werden kann. Eine Erhöhung dieses Wertes macht die Gesichtserkennung präziser, erhöht aber die Wahrscheinlichkeit, dass ein Gesicht nicht zu einer Person zugeordnet wird.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Maschinelles Lernen nutzen um Texte in Bildern zu erkennen", - "machine_learning_ocr_enabled": "OCR aktivieren", - "machine_learning_ocr_enabled_description": "Wenn deaktiviert, werden die Bilder nicht von der Texterkennung bearbeitet.", - "machine_learning_ocr_max_resolution": "Maximale Auflösung", - "machine_learning_ocr_max_resolution_description": "Vorschauen über dieser Auflösung werden unter Beibehaltung des Seitenverhältnisses verkleinert. Höhere Werte sind genauer, benötigen jedoch mehr Zeit für die Verarbeitung und verbrauchen mehr Speicher.", - "machine_learning_ocr_min_detection_score": "Mindest-Erfassungswert", - "machine_learning_ocr_min_detection_score_description": "Minimale Konfidenzrate für die Texterkennung von 0–1. Niedrigere Werte führen dazu, dass mehr Text erkannt wird, können jedoch zu falsch-positiven Ergebnissen führen.", - "machine_learning_ocr_min_recognition_score": "Mindest-Erkennungswert", - "machine_learning_ocr_min_score_recognition_description": "Minimale Konfidenzrate für die Erkennung von erkanntem Text von 0–1. Niedrigere Werte führen dazu, dass mehr Text erkannt wird, können jedoch zu falsch-positiven Ergebnissen führen.", - "machine_learning_ocr_model": "OCR Modell", - "machine_learning_ocr_model_description": "Server Modelle sind genauer als mobile Modelle, brauchen aber länger zur Verarbeitung und brauchen mehr Speicher.", - "machine_learning_settings": "Einstellungen für maschinelles Lernen", - "machine_learning_settings_description": "Funktionen und Einstellungen des maschinellen Lernens verwalten", - "machine_learning_smart_search": "Intelligente Suche", - "machine_learning_smart_search_description": "Semantische Bildsuche mittels CLIP-Einbettungen", - "machine_learning_smart_search_enabled": "Intelligente Suche aktivieren", - "machine_learning_smart_search_enabled_description": "Ist diese Option deaktiviert, werden die Bilder nicht für die intelligente Suche verwendet.", - "machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten. Server die nicht antworten werden temporär ignoriert, bis sie wieder verfügbar sind.", - "maintenance_delete_backup": "Backup löschen", - "maintenance_delete_backup_description": "Diese Datei wird irreversibel gelöscht.", - "maintenance_delete_error": "Die Löschung der Sicherungskopie ist fehlgeschlagen.", - "maintenance_restore_backup": "Sicherungskopie wiederherstellen", - "maintenance_restore_backup_description": "Immich wird zurückgesetzt und von der ausgewählten Sicherungskopie wiederhergestellt. Ein Backup wird erstellt, bevor es weitergeht.", - "maintenance_restore_backup_different_version": "Diese Sicherungskopie wurde mit einer anderen Version von Immich erstellt!", - "maintenance_restore_backup_unknown_version": "Konnte Version der Sicherungskopie nicht erkennen.", - "maintenance_restore_database_backup": "Stelle Datenbankbackup wieder her", - "maintenance_restore_database_backup_description": "Zurückrollen zu einem vorherigen Datenbankzustand mit einem Backup", - "maintenance_settings": "Wartung", - "maintenance_settings_description": "Immich in den Wartungsmodus versetzen.", - "maintenance_start": "In Wartungsmodus umschalten", - "maintenance_start_error": "Wartungsmodus konnte nicht gestartet werden.", - "maintenance_upload_backup": "Lade Datenbankbackup hoch", - "maintenance_upload_backup_error": "Konnte Backup nicht hochladen. Ist es eine .sql/.sql.gz Datei?", - "manage_concurrency": "Gleichzeitige Ausführungen verwalten", - "manage_concurrency_description": "Navigieren Sie zur Job-Seite, um die Job-Parallelität zu verwalten", - "manage_log_settings": "Log-Einstellungen verwalten", - "map_dark_style": "Dunkler Stil", - "map_enable_description": "Kartenfunktionen aktivieren", - "map_gps_settings": "Karten- & GPS-Einstellungen", - "map_gps_settings_description": "Karten- & GPS-Einstellungen verwalten", - "map_implications": "Die Kartenfunktion verwendet einen externen Tile-Service (tiles.immich.cloud)", - "map_light_style": "Heller Stil", - "map_manage_reverse_geocoding_settings": "Einstellungen für die umgekehrte Geokodierung verwalten", - "map_reverse_geocoding": "Umgekehrte Geokodierung", - "map_reverse_geocoding_enable_description": "Umgekehrte Geokodierung aktivieren", - "map_reverse_geocoding_settings": "Einstellungen für umgekehrte Geokodierung", - "map_settings": "Karte", - "map_settings_description": "Karten- und GPS-Einstellungen verwalten", - "map_style_description": "URL zu einem style.json Karten-Theme", - "memory_cleanup_job": "Erinnerungen aufräumen", - "memory_generate_job": "Erinnerungen Generierung", - "metadata_extraction_job": "Metadaten extrahieren", - "metadata_extraction_job_description": "Extrahieren von Metadaten, wie zum Beispiel GPS, Gesichtern und Auflösung aus jeder Datei", - "metadata_faces_import_setting": "Import von Gesichtern aktivieren", - "metadata_faces_import_setting_description": "Gesichter aus EXIF-Daten des Bildes und Sidecar-Dateien importieren", - "metadata_settings": "Metadaten-Einstellungen", - "metadata_settings_description": "Metadaten-Einstellungen verwalten", - "migration_job": "Migration", - "migration_job_description": "Diese Aufgabe migriert Miniaturansichten für Dateien und Gesichter in die neueste Ordnerstruktur", - "nightly_tasks_cluster_faces_setting_description": "Gesichtsidentifizierung auf neu erfassten Gesichtern ausführen", - "nightly_tasks_cluster_new_faces_setting": "Neue Gesichter gruppieren", - "nightly_tasks_database_cleanup_setting": "Datenbankbereinigungs-Aufgaben", - "nightly_tasks_database_cleanup_setting_description": "Alte, abgelaufene Daten aus der Datenbank bereinigen", - "nightly_tasks_generate_memories_setting": "Erinnerungen generieren", - "nightly_tasks_generate_memories_setting_description": "Neue Erinnerungen aus Dateien erstellen", - "nightly_tasks_missing_thumbnails_setting": "Fehlende Miniaturansichten generieren", - "nightly_tasks_missing_thumbnails_setting_description": "Dateien ohne Miniaturansicht in die Warteschlange zur Miniaturansicht-Generierung hinzufügen", - "nightly_tasks_settings": "Einstellungen für nächtliche Aufgaben", - "nightly_tasks_settings_description": "Nächtliche Aufgaben verwalten", - "nightly_tasks_start_time_setting": "Startzeit", - "nightly_tasks_start_time_setting_description": "Die Zeit, zu welcher der Server mit der Ausführung der nächtlichen Aufgaben beginnt", - "nightly_tasks_sync_quota_usage_setting": "Kontingentnutzung synchronisieren", - "nightly_tasks_sync_quota_usage_setting_description": "Benutzerspeicherkontingent basierend auf der aktuellen Nutzung aktualisieren", - "no_paths_added": "Keine Pfade hinzugefügt", - "no_pattern_added": "Kein Ausschlussmuster hinzugefügt", - "note_apply_storage_label_previous_assets": "Hinweis: Um den Speicherpfad auf die vorher hochgeladenen Dateien anzuwenden, starte den", - "note_cannot_be_changed_later": "HINWEIS: Dies kann später nicht mehr geändert werden!", - "notification_email_from_address": "Absenderadresse", - "notification_email_from_address_description": "E-Mail-Adresse des Senders, zum Beispiel: \"Immich Photo Server \". Stelle sicher, dass du eine Adresse nutzt, die du berechtigt bist zu nutzen.", - "notification_email_host_description": "Host des E-Mail-Servers (z.B. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignoriere Zertifikats-Fehler", - "notification_email_ignore_certificate_errors_description": "TLS-Zertifikatsvalidierungsfehler ignorieren (nicht empfohlen)", - "notification_email_password_description": "Passwort für die Anmeldung am E-Mail-Server", - "notification_email_port_description": "Port des E-Mail-Servers (z.B. 25, 465, oder 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Benutze SMTPS (SMTP über TLS)", - "notification_email_sent_test_email_button": "Test-E-Mail versenden und speichern", - "notification_email_setting_description": "Einstellungen für E-Mail-Benachrichtigungen", - "notification_email_test_email": "Test-E-Mail senden", - "notification_email_test_email_failed": "Die Test-E-Mail konnte nicht versendet werden, bitte überprüfe deine Angaben", - "notification_email_test_email_sent": "Es wurde eine Test-E-Mail an {email} versandt. Bitte überprüfe deinen Posteingang.", - "notification_email_username_description": "Benutzername, der bei der Anmeldung am E-Mail-Server verwendet wird", - "notification_enable_email_notifications": "E-Mail-Benachrichtigungen aktivieren", - "notification_settings": "Benachrichtigungseinstellungen", - "notification_settings_description": "Benachrichtigungseinstellungen (inkl. E-Mail) verwalten", - "oauth_auto_launch": "Auto-Start", - "oauth_auto_launch_description": "Automatischer Start des OAuth-Anmeldevorgangs beim Aufrufen der Anmeldeseite", - "oauth_auto_register": "Automatische Registrierung", - "oauth_auto_register_description": "Automatische Registrierung neuer Benutzer nach der OAuth-Anmeldung", - "oauth_button_text": "Button-Text", - "oauth_client_secret_description": "Erforderlich für Confidential Clients oder wenn PKCE (Proof Key for Code Exchange) nicht für Public Clients unterstützt wird.", - "oauth_enable_description": "Anmeldung mit OAuth", - "oauth_mobile_redirect_uri": "Mobile Umleitungs-URI", - "oauth_mobile_redirect_uri_override": "Mobile Umleitungs-URI überschreiben", - "oauth_mobile_redirect_uri_override_description": "Einschalten, wenn der OAuth-Anbieter keine mobile URI wie ''{callback}'' erlaubt", - "oauth_role_claim": "Rollen-Claim", - "oauth_role_claim_description": "Gewähre automatisch Admin-Zugriff basierend auf dem Vorhandensein dieses Claims. Der Claim kann entweder 'user' oder 'admin' sein.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth-Anmeldeeinstellungen verwalten", - "oauth_settings_more_details": "Weitere Informationen zu dieser Funktion findest du in der Dokumentation.", - "oauth_storage_label_claim": "Speicherpfadbezeichnung", - "oauth_storage_label_claim_description": "Die Speicherpfadbezeichnung des Benutzers automatisch auf den Wert dieser Eingabe setzen.", - "oauth_storage_quota_claim": "Speicherkontingentangabe", - "oauth_storage_quota_claim_description": "Setzen Sie das Speicherkontingent des Benutzers automatisch auf den angegebenen Wert.", - "oauth_storage_quota_default": "Standard-Speicherplatzkontingent (GiB)", - "oauth_storage_quota_default_description": "Kontingent in GiB, das verwendet werden soll, wenn keines übermittelt wird.", - "oauth_timeout": "Zeitüberschreitung bei Anfrage", - "oauth_timeout_description": "Zeitüberschreitung für Anfragen in Millisekunden", - "ocr_job_description": "Verwende Machine Learning zur Erkennung von Text in Bildern", - "password_enable_description": "Mit E-Mail und Passwort anmelden", - "password_settings": "Passwort-Anmeldung", - "password_settings_description": "Passwort-Anmeldeeinstellungen verwalten", - "paths_validated_successfully": "Alle Pfade erfolgreich überprüft", - "person_cleanup_job": "Personen aufräumen", - "queue_details": "Warteschlangendetails", - "queues": "Auftrags-Warteschlangen", - "queues_page_description": "Auftragswarteschlangen-Adminseite", - "quota_size_gib": "Kontingent (GiB)", - "refreshing_all_libraries": "Alle Bibliotheken aktualisieren", - "registration": "Admin-Registrierung", - "registration_description": "Da du der erste Benutzer im System bist, wird dir die Rolle des Administrators zugewiesen, womit du für die Verwaltungsaufgaben verantwortlich bist. Weitere Benutzer werden von dir erstellt.", - "remove_failed_jobs": "Entferne fehlgeschlagene Aufgaben", - "require_password_change_on_login": "Benutzer muss das Passwort beim ersten Login ändern", - "reset_settings_to_default": "Einstellungen auf Standard zurücksetzen", - "reset_settings_to_recent_saved": "Einstellungen auf die zuletzt gespeicherten Einstellungen zurücksetzen", - "scanning_library": "Bibliothek scannen", - "search_jobs": "Suchaufgaben…", - "send_welcome_email": "Begrüssungsmail senden", - "server_external_domain_settings": "Externe Domain", - "server_external_domain_settings_description": "Domäne für öffentlich freigegebene Links, einschließlich http(s)://", - "server_public_users": "Öffentliche Benutzer", - "server_public_users_description": "Beim Hinzufügen eines Benutzers zu freigegebenen Alben werden alle Benutzer (Name und E-Mail) aufgelistet. Wenn diese Option deaktiviert ist, steht die Benutzerliste nur Administratoren zur Verfügung.", - "server_settings": "Servereinstellungen", - "server_settings_description": "Servereinstellungen verwalten", - "server_stats_page_description": "Server Statistikseite für Administratoren", - "server_welcome_message": "Willkommensnachricht", - "server_welcome_message_description": "Eine Mitteilung, welche auf der Anmeldeseite angezeigt wird.", - "settings_page_description": "Seite mit den Admin-Einstellungen", - "sidecar_job": "Sidecar Metadaten", - "sidecar_job_description": "Durch diese Aufgabe werden Filialdatei-Metadaten im Dateisystem entdeckt oder synchronisiert", - "slideshow_duration_description": "Dauer der Anzeige jedes Bildes in Sekunden", - "smart_search_job_description": "Diese Aufgabe wendet das maschinelle Lernen auf Dateien an, um die intelligente Suche zu ermöglichen", - "storage_template_date_time_description": "Der Erstellungszeitstempel der Datei wird für die Datums- und Uhrzeitinformation verwendet", - "storage_template_date_time_sample": "Beispielzeitpunkt {date}", - "storage_template_enable_description": "Speichervorlagen-Engine aktivieren", - "storage_template_hash_verification_enabled": "Hash-Prüfung aktiviert", - "storage_template_hash_verification_enabled_description": "Aktiviert die Hash-Verifizierung. Deaktiviere diese Option nur, wenn du dir über die damit verbundenen Auswirkungen im Klaren bist", - "storage_template_migration": "Migration von Speichervorlagen", - "storage_template_migration_description": "Diese Aufgabe wendet die aktuelle {template} auf zuvor hochgeladene Dateien an", - "storage_template_migration_info": "Die Speichervorlage wird alle Dateierweiterungen in Kleinbuchstaben umwandeln. Vorlagenänderungen gelten nur für neue Dateien. Um die Vorlage rückwirkend auf bereits hochgeladene Assets anzuwenden, führe den {job} aus.", - "storage_template_migration_job": "Speichervorlagenmigrations-Aufgabe", - "storage_template_more_details": "Weitere Details zu dieser Funktion findest du unter Speichervorlage und dessen Implikationen", - "storage_template_onboarding_description_v2": "Wenn aktiviert, werden Dateien automatisch nach einer benutzerdefinierten Vorlage organisiert. Für mehr Informationen siehe die Dokumentation.", - "storage_template_path_length": "Ungefähres Pfadlängen-Limit: {length, number}/{limit, number}", - "storage_template_settings": "Speichervorlage", - "storage_template_settings_description": "Die Ordnerstruktur und den Dateinamen der hochgeladenen Datei verwalten", - "storage_template_user_label": "{label} is die Speicherpfadbezeichnung des Benutzers", - "system_settings": "Systemeinstellungen", - "tag_cleanup_job": "Tags aufräumen", - "template_email_available_tags": "Du kannst die folgenden Variablen in deiner Vorlage verwenden: {tags}", - "template_email_if_empty": "Wenn die Vorlage leer ist, wird die Standard-E-Mail-Vorlage verwendet.", - "template_email_invite_album": "Einladung zu Album", - "template_email_preview": "Vorschau", - "template_email_settings": "E-Mail-Vorlagen", - "template_email_update_album": "Aktualisiertes Album", - "template_email_welcome": "Willkommens-E-Mail", - "template_settings": "Benachrichtigungsvorlagen", - "template_settings_description": "Benutzerdefinierte Vorlagen für Benachrichtigungen verwalten", - "theme_custom_css_settings": "Benutzerdefiniertes CSS", - "theme_custom_css_settings_description": "Mit Cascading Style Sheets (CSS) kann das Design von Immich angepasst werden.", - "theme_settings": "Theme-Einstellungen", - "theme_settings_description": "Anpassung der Immich-Web-Oberfläche", - "thumbnail_generation_job": "Miniaturansichten generieren", - "thumbnail_generation_job_description": "Diese Aufgabe erzeugt große, kleine und unscharfe Miniaturansichten für jede einzelne Datei, sowie Miniaturansichten für jede Person", - "transcoding_acceleration_api": "Beschleunigungs-API", - "transcoding_acceleration_api_description": "Die Schnittstelle welche mit dem Gerät interagiert, um die Transkodierung zu beschleunigen. Bei dieser Einstellung handelt es sich um die \"bestmögliche Lösung\": Bei einem Fehler wird auf die Software-Transkodierung zurückgegriffen. Abhängig von der verwendeten Hardware kann VP9 funktionieren oder auch nicht.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA-GPU erforderlich)", - "transcoding_acceleration_qsv": "Quick Sync (erfordert eine Intel CPU der 7. Generation oder höher)", - "transcoding_acceleration_rkmpp": "RKMPP (nur bei Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Zugelassene Audio-Codecs", - "transcoding_accepted_audio_codecs_description": "Auswahl der Audiocodecs, die nicht transkodiert werden müssen. Wird nur für bestimmte Transkodierungsrichtlinien verwendet.", - "transcoding_accepted_containers": "Akzeptierte Container", - "transcoding_accepted_containers_description": "Wähle, welche Container nicht zu MP4 geremuxt werden sollen. Wird nur für bestimmte Transkodierungsregeln verwendet.", - "transcoding_accepted_video_codecs": "Akzeptierte Video-Codecs", - "transcoding_accepted_video_codecs_description": "Auswahl der Videocodecs, die nicht transkodiert werden müssen. Wird nur für bestimmte Transkodierungsrichtlinien verwendet.", - "transcoding_advanced_options_description": "Einstellungen, die von den meisten Benutzern nicht geändert werden müssen", - "transcoding_audio_codec": "Audio-Codec", - "transcoding_audio_codec_description": "Opus ist die hochwertigste Option, hat aber eine geringere Kompatibilität mit alten Geräten oder alter Software.", - "transcoding_bitrate_description": "Videos, welche die maximale Bitrate überschreiten oder in einem nicht akzeptierten Format vorliegen", - "transcoding_codecs_learn_more": "Um mehr über die hier verwendete Terminologie zu erfahren, sieh dir die FFmpeg-Dokumentation für den H.264-Codec, den HEVC-Codec und den VP9-Codec an.", - "transcoding_constant_quality_mode": "Modus für konstante Qualität", - "transcoding_constant_quality_mode_description": "ICQ ist besser als CQP, aber einige Hardware-Beschleunigungsgeräte unterstützen diesen Modus nicht. Wenn diese Option gesetzt wird, wird der angegebene Modus bevorzugt, sobald qualitätsbasierte Kodierung verwendet wird. Wird von NVENC ignoriert, da es ICQ nicht unterstützt.", - "transcoding_constant_rate_factor": "Faktor der konstanten Rate (-crf)", - "transcoding_constant_rate_factor_description": "Videoqualitätsstufe. Typische Werte sind 23 für H.264, 28 für HEVC, 31 für VP9 und 35 für AV1. Ein niedrigerer Wert ist besser, erzeugt aber größere Dateien.", - "transcoding_disabled_description": "Videos nicht transkodieren, dies kann die Wiedergabe auf manchen Geräten beeinträchtigen", - "transcoding_encoding_options": "Kodierungsoptionen", - "transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für die kodierten Videos", - "transcoding_hardware_acceleration": "Hardware-Beschleunigung", - "transcoding_hardware_acceleration_description": "Experimentell: schnellere Transcodierung, kann aber die Qualität bei gleicher Bitrate verringern", - "transcoding_hardware_decoding": "Hardware-Dekodierung", - "transcoding_hardware_decoding_setting_description": "Ermöglicht eine Ende-zu-Ende-Beschleunigung, anstatt nur die Codierung zu beschleunigen. Dies funktioniert möglicherweise nicht bei allen Videos.", - "transcoding_max_b_frames": "Maximale B-Frames", - "transcoding_max_b_frames_description": "Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. Ist möglicherweise nicht mit der Hardware-Beschleunigung älterer Geräte kompatibel. 0 deaktiviert die B-Frames, während -1 diesen Wert automatisch setzt.", - "transcoding_max_bitrate": "Maximale Bitrate", - "transcoding_max_bitrate_description": "Das Festlegen einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600 kbit/s für VP9 oder HEVC oder 4500 kbit/s für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist. Wenn keine Einheit angegeben wird, wird von k (für kbit/s) ausgegangen; also sind 5000, 5000k und 5M (für Mbit/s) identisch.", - "transcoding_max_keyframe_interval": "Maximales Keyframe-Intervall", - "transcoding_max_keyframe_interval_description": "Legt den maximalen Frame-Abstand zwischen Keyframes fest. Niedrigere Werte verschlechtern die Komprimierungseffizienz, verbessern aber die Suchzeiten und können die Qualität in Szenen mit schnellen Bewegungen verbessern. Bei 0 wird dieser Wert automatisch eingestellt.", - "transcoding_optimal_description": "Videos mit einer höheren Auflösung als der Zielauflösung oder in einem nicht akzeptierten Format", - "transcoding_policy": "Transkodierungsrichtlinie", - "transcoding_policy_description": "Bestimme, wann ein Video transkodiert wird", - "transcoding_preferred_hardware_device": "Bevorzugtes Hardwaregerät", - "transcoding_preferred_hardware_device_description": "Gilt nur für VAAPI und QSV. Legt den für die Hardware-Transkodierung verwendeten dri-Node fest.", - "transcoding_preset_preset": "Voreinstellung (-preset)", - "transcoding_preset_preset_description": "Komprimierungsgeschwindigkeit. Eine langsamere Voreinstellungen erzeugt kleinere Dateien und erhöht die Qualität, wenn man eine gewisse Bitrate anstrebt. VP9 ignoriert Geschwindigkeiten über „Schneller“.", - "transcoding_reference_frames": "Referenz-Frames", - "transcoding_reference_frames_description": "Die Anzahl der Bilder, auf die bei der Komprimierung eines bestimmten Bildes Bezug genommen wird. Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. 0 setzt diesen Wert automatisch.", - "transcoding_required_description": "Nur Videos in einem nicht akzeptierten Format", - "transcoding_settings": "Einstellungen für die Videotranskodierung", - "transcoding_settings_description": "Verwalten welche Videos transkodiert und wie diese verarbeitet werden", - "transcoding_target_resolution": "Ziel-Auflösung", - "transcoding_target_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Codierung, haben größere Dateigrößen und können die Reaktionszeit der Anwendung beeinträchtigen.", - "transcoding_temporal_aq": "Temporäre AQ", - "transcoding_temporal_aq_description": "Gilt nur für NVENC. Zeitlich adaptive Quantisierung verbessert die Qualität von Szenen mit hohem Detailreichtum und geringen Bewegungen. Dies ist möglicherweise nicht mit älteren Geräten kompatibel.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Höhere Werte führen zu einer schnelleren Kodierung, lassen dem Server jedoch weniger Spielraum für die Verarbeitung anderer Aufgaben im aktiven Zustand. Dieser Wert sollte nicht höher sein als die Anzahl der CPU-Kerne. Maximiert die Auslastung, wenn der Wert auf 0 gesetzt wird.", - "transcoding_tone_mapping": "Farbton-Mapping", - "transcoding_tone_mapping_description": "Versucht, das Aussehen von HDR-Videos bei der Konvertierung in SDR beizubehalten. Jeder Algorithmus geht unterschiedliche Kompromisse bei Farbe, Details und Helligkeit ein. Hable bewahrt Details, Mobius bewahrt die Farbe und Reinhard bewahrt die Helligkeit.", - "transcoding_transcode_policy": "Transcodierungsrichtlinie", - "transcoding_transcode_policy_description": "Richtlinie, wann ein Video transkodiert werden soll. HDR-Videos werden immer transkodiert (außer wenn die Transkodierung deaktiviert ist).", - "transcoding_two_pass_encoding": "Two-Pass Codierung", - "transcoding_two_pass_encoding_setting_description": "Führt eine Transkodierung in zwei Durchgängen durch, um besser kodierte Videos zu erzeugen. Wenn die maximale Bitrate aktiviert ist (erforderlich für die Verwendung mit H.264 und HEVC), verwendet dieser Modus einen Bitratenbereich, der auf der maximalen Bitrate basiert, und ignoriert CRF. Für VP9 kann CRF verwendet werden, wenn die maximale Bitrate deaktiviert ist.", - "transcoding_video_codec": "Video-Codec", - "transcoding_video_codec_description": "VP9 hat eine hohe Effizienz und Webkompatibilität, braucht aber länger für die Transkodierung. HEVC bietet eine ähnliche Leistung, ist aber weniger web-kompatibel. H.264 ist weitgehend kompatibel und lässt sich schnell transkodieren, erzeugt aber viel größere Dateien. AV1 ist der effizienteste Codec, wird aber von älteren Geräten nicht unterstützt.", - "trash_enabled_description": "Papierkorbfunktionen aktivieren", - "trash_number_of_days": "Anzahl der Tage", - "trash_number_of_days_description": "Anzahl der Tage, welche die Objekte im Papierkorb verbleiben, bevor sie endgültig entfernt werden", - "trash_settings": "Papierkorbeinstellungen", - "trash_settings_description": "Papierkorbeinstellungen verwalten", - "unlink_all_oauth_accounts": "Aus allen OAuth Konten ausloggen", - "unlink_all_oauth_accounts_description": "Denken Sie daran, alle OAuth Konten zu deaktivieren, bevor Sie zu einem neuen Anbieter migrieren.", - "unlink_all_oauth_accounts_prompt": "Sind Sie sich sicher, dass Sie alle OAuth Konten deaktivieren möchten? Diese Aktion kann nicht rückgängig gemacht werden und wird außerdem die OAuth ID aller Benutzer zurücksetzen.", - "user_cleanup_job": "Benutzer aufräumen", - "user_delete_delay": "Das Konto und die Dateien von {user} werden in {delay, plural, one {einem Tag} other {# Tagen}} für eine permanente Löschung geplant.", - "user_delete_delay_settings": "Verzögerung für das Löschen von Benutzern", - "user_delete_delay_settings_description": "Gibt die Anzahl der Tage bis zur endgültigen Löschung eines Kontos und seiner Dateien an. Der Benutzerlöschauftrag wird täglich um Mitternacht ausgeführt, um zu überprüfen, ob Nutzer zur Löschung bereit sind. Änderungen an dieser Einstellung werden erst bei der nächsten Ausführung berücksichtigt.", - "user_delete_immediately": "Das Konto und die Dateien von {user} werden sofort für eine permanente Löschung in die Warteschlange gestellt.", - "user_delete_immediately_checkbox": "Benutzer und Dateien zur sofortigen Löschung in die Warteschlange stellen", - "user_details": "Benutzerdetails", - "user_management": "Benutzerverwaltung", - "user_password_has_been_reset": "Das Passwort des Benutzers wurde zurückgesetzt:", - "user_password_reset_description": "Bitte gib dem Benutzer das temporäre Passwort und informiere ihn, dass das Passwort beim nächsten Login geändert werden muss.", - "user_restore_description": "Das Konto von {user} wird wiederhergestellt.", - "user_restore_scheduled_removal": "Wiederherstellung des Benutzers - geplante Entfernung am {date, date, long}", - "user_settings": "Benutzereinstellungen", - "user_settings_description": "Benutzereinstellungen verwalten", - "user_successfully_removed": "Der Benutzer {email} wurde erfolgreich entfernt.", - "users_page_description": "Administrator-Benutzerseite", - "version_check_enabled_description": "Versionsprüfung aktivieren", - "version_check_implications": "Die Funktion zur Versionsprüfung basiert auf regelmäßiger Kommunikation mit GitHub.com", - "version_check_settings": "Versionsprüfung", - "version_check_settings_description": "Aktivieren/Deaktivieren der Benachrichtigung über neue Versionen", - "video_conversion_job": "Videos transkodieren", - "video_conversion_job_description": "Diese Aufgabe transkodiert Videos, um die Kompatibilität mit Browsern und Geräten zu verbessern" - }, - "admin_email": "Administrator E-Mail", - "admin_password": "Administrator Passwort", - "administration": "Verwaltung", - "advanced": "Erweitert", - "advanced_settings_clear_image_cache": "Lösche Bildercache", - "advanced_settings_clear_image_cache_error": "Löschung des Bildercaches misslungen", - "advanced_settings_clear_image_cache_success": "Erfolgreich gelöscht {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Verwende diese Option, um Medien während der Synchronisierung nach anderen Kriterien zu filtern. Versuchen dies nur, wenn Probleme mit der Erkennung aller Alben durch die App auftreten.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTELL] Benutze alternativen Filter für Synchronisierung der Gerätealben", - "advanced_settings_log_level_title": "Log-Level: {level}", - "advanced_settings_prefer_remote_subtitle": "Einige Geräte sind sehr langsam beim Laden von lokalen Vorschaubildern. Aktivieren Sie diese Einstellung, um stattdessen die Server-Bilder zu laden.", - "advanced_settings_prefer_remote_title": "Server-Bilder bevorzugen", - "advanced_settings_proxy_headers_subtitle": "Definiere einen Proxy-Header, den Immich bei jeder Netzwerkanfrage mitschicken soll", - "advanced_settings_proxy_headers_title": "Benutzerdefinierte Proxy-Header [Experimentell]", - "advanced_settings_readonly_mode_subtitle": "Aktiviert den schreibgeschützten Modus, in dem die Fotos nur angezeigt werden können. Funktionen wie das Auswählen mehrerer Bilder, das Teilen, das Übertragen und das Löschen sind deaktiviert. Aktivieren/Deaktiviere den schreibgeschützten Modus über den Benutzer-Avatar auf dem Hauptbildschirm", - "advanced_settings_readonly_mode_title": "Schreibgeschützter Modus", - "advanced_settings_self_signed_ssl_subtitle": "Verifizierung von SSL-Zertifikaten vom Server überspringen. Notwendig bei selbstsignierten Zertifikaten.", - "advanced_settings_self_signed_ssl_title": "Selbstsignierte SSL-Zertifikate erlauben [Experimentell]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatisches Löschen oder Wiederherstellen einer Datei auf diesem Gerät, wenn diese Aktion im Web durchgeführt wird", - "advanced_settings_sync_remote_deletions_title": "Mit Server-Löschungen synchronisieren [Experimentell]", - "advanced_settings_tile_subtitle": "Erweiterte Benutzereinstellungen", - "advanced_settings_troubleshooting_subtitle": "Erweiterte Funktionen zur Fehlersuche aktivieren", - "advanced_settings_troubleshooting_title": "Fehlersuche", - "age_months": "Alter {months, plural, one {# Monat} other {# Monate}}", - "age_year_months": "Alter 1 Jahr, {months, plural, one {# Monat} other {# Monate}}", - "age_years": "Alter {years, plural, one {# Jahr} other {# Jahre}}", - "album": "Album", - "album_added": "Album hinzugefügt", - "album_added_notification_setting_description": "Erhalte eine E-Mail-Benachrichtigung, wenn du zu einem freigegebenen Album hinzugefügt wurdest", - "album_cover_updated": "Album-Cover aktualisiert", - "album_delete_confirmation": "Bist du sicher, dass du das Album {album} löschen willst?", - "album_delete_confirmation_description": "Falls dieses Album geteilt wurde, können andere Benutzer nicht mehr darauf zugreifen.", - "album_deleted": "Album gelöscht", - "album_info_card_backup_album_excluded": "AUSGESCHLOSSEN", - "album_info_card_backup_album_included": "EINGESCHLOSSEN", - "album_info_updated": "Album-Infos aktualisiert", - "album_leave": "Album verlassen?", - "album_leave_confirmation": "Bist du sicher, dass du das Album {album} verlassen willst?", - "album_name": "Albumname", - "album_options": "Albumoptionen", - "album_remove_user": "Nutzer entfernen?", - "album_remove_user_confirmation": "Bist du sicher, dass du {user} entfernen willst?", - "album_search_not_found": "Keine Alben gefunden, die zur Suche passen", - "album_selected": "Album ausgewählt", - "album_share_no_users": "Es sieht so aus, als hättest du dieses Album mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.", - "album_summary": "Album Zusammenfassung", - "album_updated": "Album aktualisiert", - "album_updated_setting_description": "Erhalte eine E-Mail-Benachrichtigung, wenn ein freigegebenes Album neue Dateien enthält", - "album_upload_assets": "Assets vom Computer hochladen und zu Album hinzufügen", - "album_user_left": "{album} verlassen", - "album_user_removed": "{user} entfernt", - "album_viewer_appbar_delete_confirm": "Bist du sicher, dass du dieses Album aus deinem Konto löschen möchtest?", - "album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden", - "album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden", - "album_viewer_appbar_share_err_remove": "Beim Löschen von Elementen aus dem Album ist ein Problem aufgetreten", - "album_viewer_appbar_share_err_title": "Der Titel konnte nicht geändert werden", - "album_viewer_appbar_share_leave": "Album verlassen", - "album_viewer_appbar_share_to": "Teile über", - "album_viewer_page_share_add_users": "Nutzer hinzufügen", - "album_with_link_access": "Lass jeden mit dem Link die Fotos und Personen in diesem Album sehen.", - "albums": "Alben", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Alben}}", - "albums_default_sort_order": "Standard Album Sortierung", - "albums_default_sort_order_description": "Sortierreihenfolge der Dateien bei der Erstellung neuer Alben.", - "albums_feature_description": "Sammlung an Alben die mit anderen Benutzern geteilt werden können.", - "albums_on_device_count": "Alben auf dem Gerät ({count})", - "albums_selected": "{count, plural, one {# Album ausgewählt} other {# Alben ausgewählt}}", - "all": "Alle", - "all_albums": "Alle Alben", - "all_people": "Alle Personen", - "all_photos": "Alle Fotos", - "all_videos": "Alle Videos", - "allow_dark_mode": "Dunkel-Modus erlauben", - "allow_edits": "Bearbeiten erlauben", - "allow_public_user_to_download": "Erlaube öffentlichen Benutzern, herunterzuladen", - "allow_public_user_to_upload": "Erlaube öffentlichen Benutzern, hochzuladen", - "allowed": "Erlaubt", - "alt_text_qr_code": "QR-Code Bild", - "always_keep": "Immer behalten", - "always_keep_photos_hint": "Speicherfreigabe wird alle Fotos auf dem Gerät behalten.", - "always_keep_videos_hint": "Speicherfreigabe wird alle Videos auf dem Gerät behalten.", - "anti_clockwise": "Gegen den Uhrzeigersinn", - "api_key": "API-Schlüssel", - "api_key_description": "Dieser Wert wird nur einmal angezeigt. Bitte kopiere ihn, bevor du das Fenster schließt.", - "api_key_empty": "Dein API-Schlüssel-Name darf nicht leer sein", - "api_keys": "API-Schlüssel", - "app_architecture_variant": "Variante (Architektur)", - "app_bar_signout_dialog_content": "Bist du dir sicher, dass du dich abmelden möchtest?", - "app_bar_signout_dialog_ok": "Ja", - "app_bar_signout_dialog_title": "Abmelden", - "app_download_links": "App Download-Links", - "app_settings": "App-Einstellungen", - "app_stores": "App Stores", - "app_update_available": "App Update verfügbar", - "appears_in": "Erscheint in", - "apply_count": "Anwenden ({count, number})", - "archive": "Archiv", - "archive_action_prompt": "{count} zum Archiv hinzugefügt", - "archive_or_unarchive_photo": "Foto archivieren bzw. Archivierung aufheben", - "archive_page_no_archived_assets": "Keine archivierten Inhalte gefunden", - "archive_page_title": "Archiv ({count})", - "archive_size": "Archivgröße", - "archive_size_description": "Archivgröße für Downloads konfigurieren (in GiB)", - "archived": "Archiviert", - "archived_count": "{count, plural, other {# archiviert}}", - "are_these_the_same_person": "Ist das dieselbe Person?", - "are_you_sure_to_do_this": "Bist du sicher, dass du das tun willst?", - "array_field_not_fully_supported": "Array-Felder erfordern manuelle JSON-Bearbeitung", - "asset_action_delete_err_read_only": "Schreibgeschützte Inhalte können nicht gelöscht werden, überspringen", - "asset_action_share_err_offline": "Die Offline-Inhalte konnten nicht gelesen werden, überspringen", - "asset_added_to_album": "Zum Album hinzugefügt", - "asset_adding_to_album": "Hinzufügen zum Album…", - "asset_created": "Datei erstellt", - "asset_description_updated": "Die Beschreibung der Datei wurde aktualisiert", - "asset_filename_is_offline": "Datei {filename} ist offline", - "asset_has_unassigned_faces": "Datei hat nicht zugewiesene Gesichter", - "asset_hashing": "Berechne Prüfsumme…", - "asset_list_group_by_sub_title": "Gruppieren nach", - "asset_list_layout_settings_dynamic_layout_title": "Dynamisches Layout", - "asset_list_layout_settings_group_automatically": "Automatisch", - "asset_list_layout_settings_group_by": "Gruppiere Elemente nach", - "asset_list_layout_settings_group_by_month_day": "Monat + Tag", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Einstellungen für das Fotogitter-Layout", - "asset_list_settings_title": "Fotogitter", - "asset_not_found_on_device_android": "Datei auf Gerät nicht gefunden", - "asset_not_found_on_device_ios": "Datei auf Gerät nicht gefunden. Wenn Du iCloud verwendest, kann die Datei möglicherweise nicht auffindbar sein aufgrund schlechter Dateispeicherung von iCloud", - "asset_not_found_on_icloud": "Datei in iCloud nicht gefunden. Die Datei kann möglicherweise nicht auffindbar sein aufgrund schlechter Dateispeicherung in iCloud", - "asset_offline": "Datei offline", - "asset_offline_description": "Diese externe Datei ist nicht mehr auf dem Datenträger vorhanden. Bitte wende dich an deinen Immich-Administrator, um Hilfe zu erhalten.", - "asset_restored_successfully": "Datei erfolgreich wiederhergestellt", - "asset_skipped": "Übersprungen", - "asset_skipped_in_trash": "Im Papierkorb", - "asset_trashed": "Datei Gelöscht", - "asset_troubleshoot": "Datei Fehlerbehebung", - "asset_uploaded": "Hochgeladen", - "asset_uploading": "Hochladen…", - "asset_viewer_settings_subtitle": "Verwaltung der Einstellungen für die Fotoanzeige", - "asset_viewer_settings_title": "Fotoanzeige", - "assets": "Dateien", - "assets_added_count": "{count, plural, one {# Datei} other {# Dateien}} hinzugefügt", - "assets_added_to_album_count": "{count, plural, one {# Datei} other {# Dateien}} zum Album hinzugefügt", - "assets_added_to_albums_count": "{assetTotal, plural, one {# Datei} other {# Dateien}} zu {albumTotal, plural, one {# Album} other {# Alben}} hinzugefügt", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Datei kann}other {Dateien können}} nicht zum Album hinzugefügt werden", - "assets_cannot_be_added_to_albums": "{count, plural, one {Datei kann} other {Dateien können}} nicht zu den Alben hinzugefügt werden", - "assets_count": "{count, plural, one {# Datei} other {# Dateien}}", - "assets_deleted_permanently": "{count} Element(e) permanent gelöscht", - "assets_deleted_permanently_from_server": "{count} Element(e) permanent vom Immich-Server gelöscht", - "assets_downloaded_failed": "{count, plural, one {# Datei heruntergeladen - {error} fehlgeschlagen} other {# Dateien heruntergeladen - {error} fehlgeschlagen}}", - "assets_downloaded_successfully": "{count, plural, one {# Datei erfolgreich heruntergeladen} other {# Dateien erfolgreich heruntergeladen}}", - "assets_moved_to_trash_count": "{count, plural, one {# Datei} other {# Dateien}} in den Papierkorb verschoben", - "assets_permanently_deleted_count": "{count, plural, one {# Datei} other {# Dateien}} endgültig gelöscht", - "assets_removed_count": "{count, plural, one {# Datei} other {# Dateien}} entfernt", - "assets_removed_permanently_from_device": "{count} Element(e) permanent von Ihrem Gerät gelöscht", - "assets_restore_confirmation": "Bist du sicher, dass du alle Dateien aus dem Papierkorb wiederherstellen willst? Diese Aktion kann nicht rückgängig gemacht werden! Beachte, dass Offline-Dateien auf diese Weise nicht wiederhergestellt werden können.", - "assets_restored_count": "{count, plural, one {# Datei} other {# Dateien}} wiederhergestellt", - "assets_restored_successfully": "{count} Element(e) erfolgreich wiederhergestellt", - "assets_trashed": "{count} Element(e) gelöscht", - "assets_trashed_count": "{count, plural, one {# Datei} other {# Dateien}} in den Papierkorb verschoben", - "assets_trashed_from_server": "{count} Element(e) vom Immich-Server gelöscht", - "assets_were_part_of_album_count": "{count, plural, one {# Datei ist} other {# Dateien sind}} bereits im Album vorhanden", - "assets_were_part_of_albums_count": "{count, plural, one {Datei war} other {Dateien waren}} bereits in den Alben", - "authorized_devices": "Verwendete Geräte", - "automatic_endpoint_switching_subtitle": "Verbinden Sie sich lokal über ein bestimmtes WiFi, wenn es verfügbar ist, und verwenden Sie andere Verbindungsmöglichkeiten", - "automatic_endpoint_switching_title": "Automatische URL-Umschaltung", - "autoplay_slideshow": "Automatische Diashow", - "back": "Zurück", - "back_close_deselect": "Zurück, Schließen oder Abwählen", - "background_backup_running_error": "Sicherung läuft im Hintergrund. Manuelle Sicherung kann nicht gestartet werden", - "background_location_permission": "Hintergrund Standortfreigabe", - "background_location_permission_content": "Um im Hintergrund zwischen den Netzwerken wechseln zu können, muss Immich *immer* Zugriff auf den genauen Standort haben, damit die App den Namen des WiFi-Netzwerks ermitteln kann", - "background_options": "Hintergrund Optionen", - "backup": "Sicherung", - "backup_album_selection_page_albums_device": "Alben auf dem Gerät ({count})", - "backup_album_selection_page_albums_tap": "Antippen zum sichern, erneut antippen zum Ausschließen", - "backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden.", - "backup_album_selection_page_select_albums": "Alben auswählen", - "backup_album_selection_page_selection_info": "Auswahlinformation", - "backup_album_selection_page_total_assets": "Elemente gesamt", - "backup_albums_sync": "Synchronisation der Sicherungsalben", - "backup_all": "Alle", - "backup_background_service_backup_failed_message": "Es trat ein Fehler bei der Sicherung auf. Erneuter Versuch…", - "backup_background_service_complete_notification": "Datei Backup abgeschlossen", - "backup_background_service_connection_failed_message": "Es konnte keine Verbindung zum Server hergestellt werden. Erneuter Versuch…", - "backup_background_service_current_upload_notification": "Lädt {filename} hoch", - "backup_background_service_default_notification": "Suche nach neuen Elementen…", - "backup_background_service_error_title": "Fehler bei der Sicherung", - "backup_background_service_in_progress_notification": "Elemente werden gesichert…", - "backup_background_service_upload_failure_notification": "Konnte {filename} nicht hochladen", - "backup_controller_page_albums": "Gesicherte Alben", - "backup_controller_page_background_app_refresh_disabled_content": "Aktiviere Hintergrundaktualisierungen in Einstellungen -> Allgemein -> Hintergrundaktualisierungen um Sicherungen im Hintergrund zu ermöglichen.", - "backup_controller_page_background_app_refresh_disabled_title": "Hintergrundaktualisierungen sind deaktiviert", - "backup_controller_page_background_app_refresh_enable_button_text": "Gehe zu Einstellungen", - "backup_controller_page_background_battery_info_link": "Zeige mir wie", - "backup_controller_page_background_battery_info_message": "Für die besten Ergebnisse für Sicherungen im Hintergrund, deaktiviere alle Batterieoptimierungen und Einschränkungen für die Hintergrundaktivitäten von Immich.\n\nDa dies gerätespezifisch ist, schlage diese Informationen für deinen Gerätehersteller nach.", - "backup_controller_page_background_battery_info_ok": "Ok", - "backup_controller_page_background_battery_info_title": "Batterieoptimierungen", - "backup_controller_page_background_charging": "Nur während des Ladens", - "backup_controller_page_background_configure_error": "Konnte Hintergrundservice nicht konfigurieren", - "backup_controller_page_background_delay": "Sicherung neuer Elemente verzögern um: {duration}", - "backup_controller_page_background_description": "Schalte den Hintergrundservice ein, um neue Elemente automatisch im Hintergrund zu sichern ohne die App zu öffnen", - "backup_controller_page_background_is_off": "Automatische Sicherung im Hintergrund ist deaktiviert", - "backup_controller_page_background_is_on": "Automatische Sicherung im Hintergrund ist aktiviert", - "backup_controller_page_background_turn_off": "Hintergrundservice ausschalten", - "backup_controller_page_background_turn_on": "Hintergrundservice einschalten", - "backup_controller_page_background_wifi": "Nur im WiFi", - "backup_controller_page_backup": "Sicherung", - "backup_controller_page_backup_selected": "Ausgewählt: ", - "backup_controller_page_backup_sub": "Gesicherte Fotos und Videos", - "backup_controller_page_created": "Erstellt am: {date}", - "backup_controller_page_desc_backup": "Aktiviere die Sicherung, um Elemente immer automatisch auf den Server zu laden, während du die App benutzt.", - "backup_controller_page_excluded": "Ausgeschlossen: ", - "backup_controller_page_failed": "Fehlgeschlagen ({count})", - "backup_controller_page_filename": "Dateiname: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informationen zur Sicherung", - "backup_controller_page_none_selected": "Keine ausgewählt", - "backup_controller_page_remainder": "Verbleibend", - "backup_controller_page_remainder_sub": "Noch zu sichernde Fotos und Videos", - "backup_controller_page_server_storage": "Server-Speicher", - "backup_controller_page_start_backup": "Sicherung starten", - "backup_controller_page_status_off": "Sicherung im Vordergrund ist inaktiv", - "backup_controller_page_status_on": "Sicherung im Vordergrund ist aktiv", - "backup_controller_page_storage_format": "{used} von {total} genutzt", - "backup_controller_page_to_backup": "Zu sichernde Alben", - "backup_controller_page_total_sub": "Alle Fotos und Videos", - "backup_controller_page_turn_off": "Sicherung im Vordergrund ausschalten", - "backup_controller_page_turn_on": "Sicherung im Vordergrund einschalten", - "backup_controller_page_uploading_file_info": "Informationen", - "backup_err_only_album": "Das einzige Album kann nicht entfernt werden", - "backup_error_sync_failed": "Synchronisierung fehlgeschlagen. Sicherung kann nicht verarbeitet werden.", - "backup_info_card_assets": "Elemente", - "backup_manual_cancelled": "Abgebrochen", - "backup_manual_in_progress": "Sicherung läuft bereits. Bitte versuche es später erneut", - "backup_manual_success": "Erfolgreich", - "backup_manual_title": "Sicherungsstatus", - "backup_options": "Backup-Optionen", - "backup_options_page_title": "Sicherungsoptionen", - "backup_setting_subtitle": "Verwaltung der Upload-Einstellungen im Hintergrund und im Vordergrund", - "backup_settings_subtitle": "Upload-Einstellungen verwalten", - "backup_upload_details_page_more_details": "Tippen für weitere Details", - "backward": "Rückwärts", - "biometric_auth_enabled": "Biometrische Authentifizierung aktiviert", - "biometric_locked_out": "Du bist von der biometrischen Authentifizierung ausgeschlossen", - "biometric_no_options": "Keine biometrischen Optionen verfügbar", - "biometric_not_available": "Die biometrische Authentifizierung ist auf diesem Gerät nicht verfügbar", - "birthdate_saved": "Geburtsdatum erfolgreich gespeichert", - "birthdate_set_description": "Das Geburtsdatum wird verwendet, um das Alter dieser Person zum Zeitpunkt eines Fotos zu berechnen.", - "blurred_background": "Unscharfer Hintergrund", - "bugs_and_feature_requests": "Fehler & Verbesserungsvorschläge", - "build": "Build", - "build_image": "Build Abbild", - "bulk_delete_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien gemeinsam}} löschen möchtest? Dabei wird die größte Datei jeder Gruppe behalten und alle anderen Duplikate endgültig gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!", - "bulk_keep_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} behalten möchtest? Dies wird alle Duplikat-Gruppen auflösen ohne etwas zu löschen.", - "bulk_trash_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien gemeinsam}} in den Papierkorb verschieben möchtest? Dies wird die größte Datei jeder Gruppe behalten und alle anderen Duplikate in den Papierkorb verschieben.", - "buy": "Immich erwerben", - "cache_settings_clear_cache_button": "Zwischenspeicher löschen", - "cache_settings_clear_cache_button_title": "Löscht den Zwischenspeicher der App. Dies wird die Leistungsfähigkeit der App deutlich einschränken, bis der Zwischenspeicher wieder aufgebaut wurde.", - "cache_settings_duplicated_assets_clear_button": "LEEREN", - "cache_settings_duplicated_assets_subtitle": "Fotos und Videos, die von der App blockiert werden", - "cache_settings_duplicated_assets_title": "Duplikate ({count})", - "cache_settings_statistics_album": "Vorschaubilder der Bibliothek", - "cache_settings_statistics_full": "Originalbilder", - "cache_settings_statistics_shared": "Vorschaubilder geteilter Alben", - "cache_settings_statistics_thumbnail": "Vorschaubilder", - "cache_settings_statistics_title": "Zwischenspeicher-Nutzung", - "cache_settings_subtitle": "Kontrollieren, wie Immich den Zwischenspeicher nutzt", - "cache_settings_tile_subtitle": "Lokalen Speicher verwalten", - "cache_settings_tile_title": "Lokaler Speicher", - "cache_settings_title": "Zwischenspeicher Einstellungen", - "camera": "Kamera", - "camera_brand": "Kamera-Marke", - "camera_model": "Kamera-Modell", - "cancel": "Abbrechen", - "cancel_search": "Suche abbrechen", - "canceled": "Abgebrochen", - "canceling": "Abbrechen", - "cannot_merge_people": "Personen können nicht zusammengeführt werden", - "cannot_undo_this_action": "Diese Aktion kann nicht rückgängig gemacht werden!", - "cannot_update_the_description": "Beschreibung kann nicht aktualisiert werden", - "cast": "Übertragen", - "cast_description": "Konfiguration verfügbarer Ziele", - "change_date": "Datum ändern", - "change_description": "Beschreibung anpassen", - "change_display_order": "Anzeigereihenfolge ändern", - "change_expiration_time": "Verfallszeitpunkt ändern", - "change_location": "Standort ändern", - "change_name": "Name ändern", - "change_name_successfully": "Name wurde erfolgreich geändert", - "change_password": "Passwort ändern", - "change_password_description": "Dies ist entweder das erste Mal, dass du dich im System anmeldest, oder es wurde eine Anfrage zur Änderung deines Passworts gestellt. Bitte gib unten dein neues Passwort ein.", - "change_password_form_confirm_password": "Passwort bestätigen", - "change_password_form_description": "Hallo {name}\n\nDas ist entweder das erste Mal dass du dich einloggst oder es wurde eine Anfrage zur Änderung deines Passwortes gestellt. Bitte gib das neue Passwort ein.", - "change_password_form_log_out": "Von allen Geräte abmelden", - "change_password_form_log_out_description": "Es wird empfohlen, alle anderen Geräte abzumelden", - "change_password_form_new_password": "Neues Passwort", - "change_password_form_password_mismatch": "Passwörter stimmen nicht überein", - "change_password_form_reenter_new_password": "Passwort erneut eingeben", - "change_pin_code": "PIN-Code ändern", - "change_trigger": "Auslöser ändern", - "change_trigger_prompt": "Bist du sicher, dass du den Auslöser ändern willst? Dies entfernt alle bestehenden Aktionen und Filter.", - "change_your_password": "Ändere dein Passwort", - "changed_visibility_successfully": "Die Sichtbarkeit wurde erfolgreich geändert", - "charging": "Aufladen", - "charging_requirement_mobile_backup": "Backup im Hintergrund erfordert Aufladen des Geräts", - "check_corrupt_asset_backup": "Auf beschädigte Asset-Backups überprüfen", - "check_corrupt_asset_backup_button": "Überprüfung durchführen", - "check_corrupt_asset_backup_description": "Führe diese Prüfung nur mit aktivierten WiFi durch, nachdem alle Dateien gesichert worden sind. Dieser Vorgang kann ein paar Minuten dauern.", - "check_logs": "Logs prüfen", - "checksum": "Prüfsumme", - "choose_matching_people_to_merge": "Wähle passende Personen zum Zusammenführen", - "city": "Stadt", - "cleanup_confirm_description": "Immich hat {count} Dateien (vor dem {date} erstellt) sicher auf dem Server gefunden. Sollen die lokalen Kopien von diesem Gerät gelöscht werden?", - "cleanup_confirm_prompt_title": "Von diesem Gerät entfernen?", - "cleanup_deleted_assets": "{count} Dateien in den lokalen Papierkorb verschoben", - "cleanup_deleting": "In den Papierkorb verschieben…", - "cleanup_found_assets": "{count} hochgeladene Dateien gefunden", - "cleanup_found_assets_with_size": "{count} gesicherte Dateien gefunden ({size})", - "cleanup_icloud_shared_albums_excluded": "Geteilte Alben aus iCloud sind vom Scan ausgeschlossen", - "cleanup_no_assets_found": "Keine passenden Assets gefunden. Speicherbereinigung kann nur auf Assets angewendet werden, die bereits auf den Server gesichert wurden", - "cleanup_preview_title": "Zu löschende Assets ({count})", - "cleanup_step3_description": "Nach gesicherten Mediendateien scannen, die mit den Filterkriterien und gespeicherten Einstellungen übereinstimmen.", - "cleanup_step4_summary": "{count} Assets, die vor dem {date} erstellt wurden, warten auf Löschung von Ihrem Gerät. Die Photos werden auch weiterhin über die Immich-App verfügbar sein.", - "cleanup_trash_hint": "Um den Speicher vollständig freizugeben, öffnen Sie die Galerie-App und leeren Sie den Papierkorb", - "clear": "Leeren", - "clear_all": "Alles leeren", - "clear_all_recent_searches": "Alle letzten Suchvorgänge löschen", - "clear_file_cache": "Dateien-Cache leeren", - "clear_message": "Nachrichten leeren", - "clear_value": "Wert leeren", - "client_cert_dialog_msg_confirm": "Ok", - "client_cert_enter_password": "Passwort eingeben", - "client_cert_import": "Importieren", - "client_cert_import_success_msg": "Client Zertifikat wurde importiert", - "client_cert_invalid_msg": "Ungültige Zertifikatsdatei oder falsches Passwort", - "client_cert_remove_msg": "Client Zertifikat wurde entfernt", - "client_cert_subtitle": "Unterstützt nur das PKCS12 (.p12, .pfx) Format. Zertifikatsimporte oder -entfernungen sind nur vor dem Login möglich", - "client_cert_title": "SSL-Client-Zertifikat [Experimentell]", - "clockwise": "Im Uhrzeigersinn", - "close": "Schließen", - "collapse": "Zusammenklappen", - "collapse_all": "Alle zusammenklappen", - "color": "Farbe", - "color_theme": "Farb-Theme", - "command": "Befehl", - "comment_deleted": "Kommentar gelöscht", - "comment_options": "Kommentaroptionen", - "comments_and_likes": "Kommentare & Likes", - "comments_are_disabled": "Kommentare sind deaktiviert", - "common_create_new_album": "Neues Album erstellen", - "completed": "Abgeschlossen", - "confirm": "Bestätigen", - "confirm_admin_password": "Administrator Passwort bestätigen", - "confirm_delete_face": "Bist du sicher dass du das Gesicht von {name} aus der Datei entfernen willst?", - "confirm_delete_shared_link": "Bist du sicher, dass du diesen geteilten Link löschen willst?", - "confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?", - "confirm_new_pin_code": "Neuen PIN-Code bestätigen", - "confirm_password": "Passwort bestätigen", - "confirm_tag_face": "Wollen Sie dieses Gesicht mit {name} markieren?", - "confirm_tag_face_unnamed": "Möchten Sie dieses Gesicht markieren?", - "connected_device": "Verbundenes Gerät", - "connected_to": "Verbunden mit", - "contain": "Vollständig", - "context": "Kontext", - "continue": "Fortsetzen", - "control_bottom_app_bar_create_new_album": "Neues Album erstellen", - "control_bottom_app_bar_delete_from_immich": "Aus Immich löschen", - "control_bottom_app_bar_delete_from_local": "Vom Gerät löschen", - "control_bottom_app_bar_edit_location": "Standort bearbeiten", - "control_bottom_app_bar_edit_time": "Datum und Uhrzeit bearbeiten", - "control_bottom_app_bar_share_link": "Link teilen", - "control_bottom_app_bar_share_to": "Teilen mit", - "control_bottom_app_bar_trash_from_immich": "in den Papierkorb schieben", - "copied_image_to_clipboard": "Das Bild wurde in die Zwischenablage kopiert.", - "copied_to_clipboard": "In die Zwischenablage kopiert!", - "copy_error": "Kopier-Fehler", - "copy_file_path": "Dateipfad kopieren", - "copy_image": "Bild kopieren", - "copy_link": "Link kopieren", - "copy_link_to_clipboard": "Link in die Zwischenablage kopieren", - "copy_password": "Passwort kopieren", - "copy_to_clipboard": "In die Zwischenablage kopieren", - "country": "Land", - "cover": "Randlos", - "covers": "Albumcover", - "create": "Erstellen", - "create_album": "Album erstellen", - "create_album_page_untitled": "Unbenannt", - "create_api_key": "API Key erstellen", - "create_first_workflow": "Ersten Workflow erstellen", - "create_library": "Bibliothek erstellen", - "create_link": "Link erstellen", - "create_link_to_share": "Link zum Teilen erstellen", - "create_link_to_share_description": "Lass jeden mit dem Link die ausgewählten Fotos sehen", - "create_new": "NEUES ERSTELLEN", - "create_new_person": "Neue Person anlegen", - "create_new_person_hint": "Ausgewählte Dateien einer neuen Person zuweisen", - "create_new_user": "Neuen Nutzer erstellen", - "create_shared_album_page_share_add_assets": "INHALTE HINZUFÜGEN", - "create_shared_album_page_share_select_photos": "Fotos auswählen", - "create_shared_link": "Geteilten Link erstellen", - "create_tag": "Tag erstellen", - "create_tag_description": "Erstelle einen neuen Tag. Für verschachtelte Tags, gib den gesamten Pfad inklusive Schrägstrich an.", - "create_user": "Nutzer erstellen", - "create_workflow": "Workflow erstellen", - "created": "Erstellt", - "created_at": "Erstellt", - "creating_linked_albums": "Erstelle verknüpfte Alben...", - "crop": "Zuschneiden", - "crop_aspect_ratio_fixed": "Fixiert", - "crop_aspect_ratio_free": "Frei", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Dinge", - "current_device": "Aktuelles Gerät", - "current_pin_code": "Aktueller PIN-Code", - "current_server_address": "Aktuelle Serveradresse", - "custom_date": "Benutzerdefiniertes Datum", - "custom_locale": "Benutzerdefinierte Sprache", - "custom_locale_description": "Datumsangaben und Zahlen je nach Sprache und Land formatieren", - "custom_url": "Benutzerdefinierte URL", - "cutoff_date_description": "Behalte Fotos der letzten…", - "cutoff_day": "{count, plural, one {Tag} other {Tage}}", - "cutoff_year": "{count, plural, one {Jahr} other {Jahre}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Dunkel", - "dark_theme": "Dunkle Ansicht umschalten", - "date": "Datum", - "date_after": "Datum nach", - "date_and_time": "Datum und Zeit", - "date_before": "Datum vor", - "date_format": "E d. LLL y • hh:mm", - "date_of_birth_saved": "Das Geburtsdatum wurde erfolgreich gespeichert", - "date_range": "Datumsbereich", - "day": "Tag", - "days": "Tage", - "deduplicate_all": "Alle Duplikate entfernen", - "deduplication_criteria_1": "Bildgröße in Bytes", - "deduplication_criteria_2": "Anzahl der EXIF-Daten", - "deduplication_info": "Deduplizierungsinformationen", - "deduplication_info_description": "Für die automatische Datei-Vorauswahl und das Deduplizieren aller Dateien berücksichtigen wir:", - "default_locale": "Standard-Sprache", - "default_locale_description": "Datumsangaben und Zahlen basierend auf dem Gebietsschema des Browsers formatieren", - "delete": "Löschen", - "delete_action_confirmation_message": "Bist du sicher, dass du dieses Objekt löschen willst? Diese Aktion wird das Objekt in den Papierkorb des Servers verschieben und fragen, ob du es lokal löschen willst", - "delete_action_prompt": "{count} gelöscht", - "delete_album": "Album löschen", - "delete_api_key_prompt": "Bist du sicher, dass du diesen API-Schlüssel löschen willst?", - "delete_dialog_alert": "Diese Elemente werden unwiderruflich von Immich und dem Gerät entfernt", - "delete_dialog_alert_local": "Diese Inhalte werden vom Gerät gelöscht, bleiben aber auf dem Immich-Server", - "delete_dialog_alert_local_non_backed_up": "Einige Inhalte sind nicht in Immich gesichert und werden dauerhaft vom Gerät gelöscht", - "delete_dialog_alert_remote": "Diese Inhalte werden dauerhaft vom Immich-Server gelöscht", - "delete_dialog_ok_force": "Trotzdem löschen", - "delete_dialog_title": "Endgültig löschen", - "delete_duplicates_confirmation": "Bist du sicher, dass du diese Duplikate endgültig löschen willst?", - "delete_face": "Gesicht löschen", - "delete_key": "Schlüssel löschen", - "delete_library": "Bibliothek löschen", - "delete_link": "Link löschen", - "delete_local_action_prompt": "{count} lokal gelöscht", - "delete_local_dialog_ok_backed_up_only": "Nur gesicherte Inhalte löschen", - "delete_local_dialog_ok_force": "Trotzdem löschen", - "delete_others": "Andere löschen", - "delete_permanently": "Endgültig löschen", - "delete_permanently_action_prompt": "{count} endgültig gelöscht", - "delete_shared_link": "geteilten Link löschen", - "delete_shared_link_dialog_title": "Geteilten Link löschen", - "delete_tag": "Tag löschen", - "delete_tag_confirmation_prompt": "Bist du sicher, dass der Tag {tagName} gelöscht werden soll?", - "delete_user": "Nutzer löschen", - "deleted_shared_link": "Geteilten Link gelöscht", - "deletes_missing_assets": "Löscht Dateien, die auf der Festplatte fehlen", - "description": "Beschreibung", - "description_input_hint_text": "Beschreibung hinzufügen...", - "description_input_submit_error": "Beschreibung konnte nicht geändert werden, bitte im Log für mehr Details nachsehen", - "deselect_all": "Alle abwählen", - "details": "Details", - "direction": "Richtung", - "disable": "Deaktivieren", - "disabled": "Deaktiviert", - "disallow_edits": "Bearbeitungen verbieten", - "discord": "Discord", - "discover": "Entdecken", - "discovered_devices": "Gefundene Geräte", - "dismiss_all_errors": "Alle Fehler ignorieren", - "dismiss_error": "Fehler ignorieren", - "display_options": "Anzeigeoptionen", - "display_order": "Anzeigereihenfolge", - "display_original_photos": "Originale Fotos anzeigen", - "display_original_photos_setting_description": "Bei der Anzeige eines Bildes wird bevorzugt das Originalfoto statt der Miniaturansicht angezeigt, sofern das Original webkompatibel ist. Dies kann zu einer längeren Ladezeit der Fotos führen.", - "do_not_show_again": "Diese Nachricht nicht erneut anzeigen", - "documentation": "Dokumentation", - "done": "Fertig", - "download": "Herunterladen", - "download_action_prompt": "Herunterladen von {count} Dateien", - "download_canceled": "Download abgebrochen", - "download_complete": "Download vollständig", - "download_enqueue": "Download in die Warteschlange gesetzt", - "download_error": "Download fehlerhaft", - "download_failed": "Download fehlerhaft", - "download_finished": "Download abgeschlossen", - "download_include_embedded_motion_videos": "Eingebettete Videos", - "download_include_embedded_motion_videos_description": "Videos, die in Bewegungsfotos eingebettet sind, als separate Datei einfügen", - "download_notfound": "Download nicht gefunden", - "download_original": "Original herunterladen", - "download_paused": "Download pausiert", - "download_settings": "Download", - "download_settings_description": "Einstellungen für das Herunterladen von Dateien verwalten", - "download_started": "Download gestartet", - "download_sucess": "Download erfolgreich", - "download_sucess_android": "Die Datei wurde nach DCIM/Immich heruntergeladen", - "download_waiting_to_retry": "Warte auf erneuten Versuch", - "downloading": "Herunterladen", - "downloading_asset_filename": "Datei {filename} wird heruntergeladen", - "downloading_from_icloud": "von iCloud herunterladen", - "downloading_media": "Medien werden heruntergeladen", - "drop_files_to_upload": "Lade Dateien hoch, indem du sie hierhin ziehst", - "duplicates": "Duplikate", - "duplicates_description": "Löse jede Gruppe auf, indem du angibst, welche, wenn überhaupt, Duplikate sind", - "duration": "Dauer", - "edit": "Bearbeiten", - "edit_album": "Album bearbeiten", - "edit_avatar": "Avatar bearbeiten", - "edit_birthday": "Geburtsdatum bearbeiten", - "edit_date": "Datum bearbeiten", - "edit_date_and_time": "Datum und Uhrzeit bearbeiten", - "edit_date_and_time_action_prompt": "{count} Daten und Zeiten geändert", - "edit_date_and_time_by_offset": "Datum ändern um Versatz", - "edit_date_and_time_by_offset_interval": "Neuer Datumsbereich: {from} - {to}", - "edit_description": "Beschreibung bearbeiten", - "edit_description_prompt": "Bitte wähle eine neue Beschreibung:", - "edit_exclusion_pattern": "Ausschlussmuster bearbeiten", - "edit_faces": "Gesichter bearbeiten", - "edit_key": "Schlüssel bearbeiten", - "edit_link": "Link bearbeiten", - "edit_location": "Standort bearbeiten", - "edit_location_action_prompt": "{count} Geolokationen angepasst", - "edit_location_dialog_title": "Standort bearbeiten", - "edit_name": "Name bearbeiten", - "edit_people": "Personen bearbeiten", - "edit_tag": "Tag bearbeiten", - "edit_title": "Titel bearbeiten", - "edit_user": "Nutzer bearbeiten", - "edit_workflow": "Workflow bearbeiten", - "editor": "Bearbeiten", - "editor_close_without_save_prompt": "Die Änderungen werden nicht gespeichert", - "editor_close_without_save_title": "Editor schließen?", - "editor_confirm_reset_all_changes": "Alle Änderungen zurücksetzen?", - "editor_flip_horizontal": "Horizontal spiegeln", - "editor_flip_vertical": "Vertikal spiegeln", - "editor_orientation": "Ausrichtung", - "editor_reset_all_changes": "Änderungen zurücksetzen", - "editor_rotate_left": "Um 90° gegen den Uhrzeigersinn drehen", - "editor_rotate_right": "Um 90° im Uhrzeigersinn drehen", - "email": "E-Mail", - "email_notifications": "E-Mail Benachrichtigungen", - "empty_folder": "Dieser Ordner ist leer", - "empty_trash": "Papierkorb leeren", - "empty_trash_confirmation": "Bist du sicher, dass du den Papierkorb leeren willst?\nDies entfernt alle Dateien im Papierkorb endgültig aus Immich und kann nicht rückgängig gemacht werden!", - "enable": "Aktivieren", - "enable_backup": "Sicherung aktivieren", - "enable_biometric_auth_description": "Gib deinen PIN-Code ein, um die biometrische Authentifizierung zu aktivieren", - "enabled": "Aktiviert", - "end_date": "Enddatum", - "enqueued": "Eingereiht", - "enter_wifi_name": "WiFi-Name eingeben", - "enter_your_pin_code": "PIN-Code eingeben", - "enter_your_pin_code_subtitle": "Gib deinen PIN-Code ein, um auf den gesperrten Ordner zuzugreifen", - "error": "Fehler", - "error_change_sort_album": "Ändern der Anzeigereihenfolge fehlgeschlagen", - "error_delete_face": "Fehler beim Löschen des Gesichts", - "error_getting_places": "Fehler beim Abrufen der Orte", - "error_loading_albums": "Fehler beim Laden der Alben", - "error_loading_image": "Fehler beim Laden des Bildes", - "error_loading_partners": "Fehler beim Laden der Partner: {error}", - "error_retrieving_asset_information": "Fehler beim Abruf der Dateiinformationen", - "error_saving_image": "Fehler: {error}", - "error_tag_face_bounding_box": "Fehler beim Markieren des Gesichts - Begrenzungen können nicht abgerufen werden", - "error_title": "Fehler - Etwas ist schief gelaufen", - "error_while_navigating": "Fehler beim Navigieren zur Datei", - "errors": { - "cannot_navigate_next_asset": "Kann nicht zur nächsten Datei navigieren", - "cannot_navigate_previous_asset": "Kann nicht zur vorherigen Datei navigieren", - "cant_apply_changes": "Änderungen können nicht übernommen werden", - "cant_change_activity": "Aktivität kann nicht {enabled, select, true {deaktiviert} other {aktiviert}} werden", - "cant_change_asset_favorite": "Kann Favorit für Datei nicht ändern", - "cant_change_metadata_assets_count": "Kann Metadaten für {count, plural, one {# Datei} other {# Dateien}} nicht ändern", - "cant_get_faces": "Gesichter konnten nicht abgerufen werden", - "cant_get_number_of_comments": "Anzahl der Kommentare konnte nicht abgerufen werden", - "cant_search_people": "Personen konnten nicht gesucht werden", - "cant_search_places": "Orte konnten nicht gesucht werden", - "error_adding_assets_to_album": "Fehler beim Hinzufügen von Dateien zum Album", - "error_adding_users_to_album": "Fehler beim Hinzufügen von Benutzern zum Album", - "error_deleting_shared_user": "Fehler beim Löschen des geteilten Benutzers", - "error_downloading": "Fehler beim Herunterladen von {filename}", - "error_hiding_buy_button": "Fehler beim Ausblenden der Kaufen Schaltfläche", - "error_removing_assets_from_album": "Fehler beim Entfernen von Dateien aus dem Album, siehe Konsole für weitere Details", - "error_selecting_all_assets": "Fehler beim Auswählen aller Dateien", - "exclusion_pattern_already_exists": "Dieses Ausschlussmuster existiert bereits.", - "failed_to_create_album": "Album konnte nicht erstellt werden", - "failed_to_create_shared_link": "Geteilter Link konnte nicht erstellt werden", - "failed_to_edit_shared_link": "Geteilter Link konnte nicht bearbeitet werden", - "failed_to_get_people": "Personen konnten nicht abgerufen werden", - "failed_to_keep_this_delete_others": "Fehler beim Löschen der anderen Dateien", - "failed_to_load_asset": "Fehler beim Laden der Datei", - "failed_to_load_assets": "Fehler beim Laden der Dateien", - "failed_to_load_notifications": "Fehler beim Laden der Benachrichtigungen", - "failed_to_load_people": "Fehler beim Laden von Personen", - "failed_to_remove_product_key": "Fehler beim Entfernen des Produktschlüssels", - "failed_to_reset_pin_code": "Zurücksetzen des PIN-Codes fehlgeschlagen", - "failed_to_stack_assets": "Dateien konnten nicht gestapelt werden", - "failed_to_unstack_assets": "Dateien konnten nicht entstapelt werden", - "failed_to_update_notification_status": "Benachrichtigungsstatus aktualisieren fehlgeschlagen", - "incorrect_email_or_password": "Ungültige E-Mail oder Passwort", - "library_folder_already_exists": "Dieser Importpfad existiert bereits.", - "paths_validation_failed": "{paths, plural, one {# Pfad konnte} other {# Pfade konnten}} nicht validiert werden", - "profile_picture_transparent_pixels": "Profilbilder dürfen keine transparenten Pixel haben. Bitte zoome heran und/oder verschiebe das Bild.", - "quota_higher_than_disk_size": "Dein festgelegtes Kontingent ist größer als der verfügbare Speicher", - "something_went_wrong": "Ein Fehler ist eingetreten", - "unable_to_add_album_users": "Benutzer konnten nicht zum Album hinzugefügt werden", - "unable_to_add_assets_to_shared_link": "Datei konnte nicht zum geteilten Link hinzugefügt werden", - "unable_to_add_comment": "Es kann kein Kommentar hinzufügt werden", - "unable_to_add_exclusion_pattern": "Ausschlussmuster konnte nicht hinzugefügt werden", - "unable_to_add_partners": "Es können keine Partner hinzufügt werden", - "unable_to_add_remove_archive": "Datei konnte nicht {archived, select, true {aus dem Archiv entfernt} other {zum Archiv hinzugefügt}} werden", - "unable_to_add_remove_favorites": "Datei konnte nicht {favorite, select, true {von den Favoriten entfernt} other {zu den Favoriten hinzugefügt}} werden", - "unable_to_archive_unarchive": "Konnte nicht {archived, select, true {archivieren} other {entarchivieren}}", - "unable_to_change_album_user_role": "Die Rolle des Albumbenutzers kann nicht geändert werden", - "unable_to_change_date": "Datum kann nicht verändert werden", - "unable_to_change_description": "Ändern der Beschreibung nicht möglich", - "unable_to_change_favorite": "Es konnte der Favoritenstatus für diese Datei nicht geändert werden", - "unable_to_change_location": "Standort kann nicht verändert werden", - "unable_to_change_password": "Passwort konnte nicht geändert werden", - "unable_to_change_visibility": "Sichtbarkeit von {count, plural, one {einer Person} other {# Personen}} konnte nicht geändert werden", - "unable_to_complete_oauth_login": "OAuth-Anmeldung konnte nicht abgeschlossen werden", - "unable_to_connect": "Verbindung konnte nicht hergestellt werden", - "unable_to_copy_to_clipboard": "Konnte nicht in die Zwischenablage kopieren, stelle sicher, dass du per https auf die Seite zugreifst", - "unable_to_create": "Workflow konnte nicht erstellt werden", - "unable_to_create_admin_account": "Administratorkonto konnte nicht erstellt werden", - "unable_to_create_api_key": "Es konnte kein API-Schlüssel erstellt werden", - "unable_to_create_library": "Bibliothek konnte nicht erstellt werden", - "unable_to_create_user": "Nutzer konnte nicht erstellt werden", - "unable_to_delete_album": "Album konnte nicht gelöscht werden", - "unable_to_delete_asset": "Datei konnte nicht gelöscht werden", - "unable_to_delete_assets": "Fehler beim Löschen von Dateien", - "unable_to_delete_exclusion_pattern": "Ausschlussmuster konnte nicht gelöscht werden", - "unable_to_delete_shared_link": "Geteilter Link kann nicht gelöscht werden", - "unable_to_delete_user": "Nutzer konnte nicht gelöscht werden", - "unable_to_delete_workflow": "Workflow konnte nicht gelöscht werden", - "unable_to_download_files": "Dateien konnten nicht heruntergeladen werden", - "unable_to_edit_exclusion_pattern": "Ausschlussmuster konnte nicht bearbeitet werden", - "unable_to_empty_trash": "Papierkorb konnte nicht geleert werden", - "unable_to_enter_fullscreen": "Vollbildmodus kann nicht aktiviert werden", - "unable_to_exit_fullscreen": "Vollbildmodus kann nicht deaktiviert werden", - "unable_to_get_comments_number": "Anzahl der Kommentare konnte nicht abgerufen werden", - "unable_to_get_shared_link": "Fehler beim Abrufen des Freigabelinks", - "unable_to_hide_person": "Person kann nicht versteckt werden", - "unable_to_link_motion_video": "Bewegungsvideo kann nicht verknüpft werden", - "unable_to_link_oauth_account": "OAuth-Konto kann nicht verknüpft werden", - "unable_to_log_out_all_devices": "Konnte nicht von allen Geräten abmelden", - "unable_to_log_out_device": "Konnte nicht vom Gerät abmelden", - "unable_to_login_with_oauth": "Anmeldung mit OAuth nicht möglich", - "unable_to_play_video": "Das Video kann nicht wiedergegeben werden", - "unable_to_reassign_assets_existing_person": "Kann Dateien nicht {name, select, null {einer vorhandenen Person} other {{name}}} zuweisen", - "unable_to_reassign_assets_new_person": "Dateien konnten nicht einer neuen Person zugeordnet werden", - "unable_to_refresh_user": "Der Benutzer kann nicht aktualisiert werden", - "unable_to_remove_album_users": "Mitglieder der Alben können nicht entfernt werden", - "unable_to_remove_api_key": "API-Schlüssel konnte nicht entfernt werden", - "unable_to_remove_assets_from_shared_link": "Dateien konnten nicht von geteiltem Link entfernt werden", - "unable_to_remove_library": "Bibliothek kann nicht entfernt werden", - "unable_to_remove_partner": "Partner kann nicht entfernt werden", - "unable_to_remove_reaction": "Reaktion kann nicht entfernt werden", - "unable_to_reset_password": "Passwort kann nicht zurückgesetzt werden", - "unable_to_reset_pin_code": "Zurücksetzen des PIN-Code nicht möglich", - "unable_to_resolve_duplicate": "Duplikate können nicht aufgelöst werden", - "unable_to_restore_assets": "Dateien konnten nicht wiederhergestellt werden", - "unable_to_restore_trash": "Papierkorb kann nicht wiederhergestellt werden", - "unable_to_restore_user": "Benutzer kann nicht wiederhergestellt werden", - "unable_to_save_album": "Album kann nicht gespeichert werden", - "unable_to_save_api_key": "API-Schlüssel konnte nicht gespeichert werden", - "unable_to_save_date_of_birth": "Das Geburtsdatum konnte nicht gespeichert werden", - "unable_to_save_name": "Name konnte nicht gespeichert werden", - "unable_to_save_profile": "Profil konnte nicht gespeichert werden", - "unable_to_save_settings": "Einstellungen konnten nicht gespeichert werden", - "unable_to_scan_libraries": "Bibliotheken konnten nicht gescannt werden", - "unable_to_scan_library": "Bibliothek konnte nicht gescannt werden", - "unable_to_set_feature_photo": "Hauptfoto konnte nicht festgelegt werden", - "unable_to_set_profile_picture": "Profilbild konnte nicht gesetzt werden", - "unable_to_set_rating": "Bewertung konnte nicht gespeichert werden", - "unable_to_submit_job": "Aufgabe konnte nicht eingereicht werden", - "unable_to_trash_asset": "Objekte konnten nicht gelöscht werden", - "unable_to_unlink_account": "Die Verknüpfung des Kontos kann nicht aufgehoben werden", - "unable_to_unlink_motion_video": "Verknüpfung zum Bewegungsvideo kann nicht aufgehoben werden", - "unable_to_update_album_cover": "Album-Cover konnte nicht aktualisiert werden", - "unable_to_update_album_info": "Album-Info konnte nicht aktualisiert werden", - "unable_to_update_library": "Die Bibliothek konnte nicht aktualisiert werden", - "unable_to_update_location": "Der Standort konnte nicht aktualisiert werden", - "unable_to_update_settings": "Die Einstellungen konnten nicht aktualisiert werden", - "unable_to_update_timeline_display_status": "Status der Zeitleistenanzeige konnte nicht aktualisiert werden", - "unable_to_update_user": "Der Nutzer konnte nicht aktualisiert werden", - "unable_to_update_workflow": "Workflow konnte nicht aktualisiert werden", - "unable_to_upload_file": "Datei konnte nicht hochgeladen werden" - }, - "errors_text": "Fehler", - "exclusion_pattern": "Ausschlussmuster", - "exif": "EXIF", - "exif_bottom_sheet_description": "Beschreibung hinzufügen...", - "exif_bottom_sheet_description_error": "Fehler bei der Aktualisierung der Beschreibung", - "exif_bottom_sheet_details": "DETAILS", - "exif_bottom_sheet_location": "STANDORT", - "exif_bottom_sheet_no_description": "Keine Beschreibung", - "exif_bottom_sheet_people": "PERSONEN", - "exif_bottom_sheet_person_add_person": "Namen hinzufügen", - "exit_slideshow": "Diashow beenden", - "expand_all": "Alle aufklappen", - "experimental_settings_new_asset_list_subtitle": "In Arbeit", - "experimental_settings_new_asset_list_title": "Experimentelles Fotogitter aktivieren", - "experimental_settings_subtitle": "Benutzung auf eigene Gefahr!", - "experimental_settings_title": "Experimentell", - "expire_after": "Verfällt nach", - "expired": "Verfallen", - "expires_date": "Läuft ab: {date}", - "explore": "Erkunden", - "explorer": "Datei-Explorer", - "export": "Exportieren", - "export_as_json": "Als JSON exportieren", - "export_database": "Datenbank exportieren", - "export_database_description": "Exportiert die SQLite Datenbank", - "extension": "Erweiterung", - "external": "Extern", - "external_libraries": "Externe Bibliotheken", - "external_network": "Externes Netzwerk", - "external_network_sheet_info": "Wenn sich die App nicht im bevorzugten WLAN-Netzwerk befindet, verbindet sie sich mit dem Server über die erste der folgenden URLs, die sie erreichen kann (von oben nach unten)", - "face_unassigned": "Nicht zugewiesen", - "failed": "Fehlgeschlagen", - "failed_count": "Fehlgeschlagen: {count}", - "failed_to_authenticate": "Authentifizierung fehlgeschlagen", - "failed_to_load_assets": "Laden der Assets fehlgeschlagen", - "failed_to_load_folder": "Fehler beim Laden des Ordners", - "favorite": "Favorit", - "favorite_action_prompt": "{count} zu den Favoriten hinzugefügt", - "favorite_or_unfavorite_photo": "Favorisiertes oder nicht favorisiertes Foto", - "favorites": "Favoriten", - "favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden", - "feature_photo_updated": "Profilbild aktualisiert", - "features": "Funktionen", - "features_in_development": "Feature in Entwicklung", - "features_setting_description": "Funktionen der App verwalten", - "file_name_or_extension": "Dateiname oder -erweiterung", - "file_size": "Dateigröße", - "filename": "Dateiname", - "filetype": "Dateityp", - "filter": "Filter", - "filter_description": "Bedingungen zur Filterung der betreffenden Dateien", - "filter_people": "Personen filtern", - "filter_places": "Orte filtern", - "filters": "Filter", - "find_them_fast": "Finde sie schneller mit der Suche nach Namen", - "first": "Erste", - "fix_incorrect_match": "Fehlerhafte Übereinstimmung beheben", - "folder": "Ordner", - "folder_not_found": "Ordner nicht gefunden", - "folders": "Ordner", - "folders_feature_description": "Durchsuchen der Ordneransicht für Fotos und Videos im Dateisystem", - "forgot_pin_code_question": "PIN-Code vergessen?", - "forward": "Vorwärts", - "free_up_space": "Speicherplatz freigeben", - "free_up_space_description": "Bewege Fotos und Videos, die bereits gesichert wurden, in den Papierkorb auf deinem Gerät. Die Kopie auf dem Server bleibt unberührt.", - "free_up_space_settings_subtitle": "Gerätespeicher freigeben", - "full_path": "Vollständiger Pfad: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Diese Funktion lädt externe Quellen von Google, um zu funktionieren.", - "general": "Allgemein", - "geolocation_instruction_location": "Klicke auf eine Datei mit GPS Koordinaten um diesen Standort zu verwenden oder wähle einen Standort direkt auf der Karte", - "get_help": "Hilfe erhalten", - "get_people_error": "Fehler beim Laden der Personen", - "get_wifiname_error": "WiFi-Name konnte nicht ermittelt werden. Vergewissere dich, dass die erforderlichen Berechtigungen erteilt wurden und du mit einem WiFi-Netzwerk verbunden bist", - "getting_started": "Erste Schritte", - "go_back": "Zurück", - "go_to_folder": "Gehe zu Ordner", - "go_to_search": "Zur Suche gehen", - "gps": "GPS", - "gps_missing": "Kein GPS", - "grant_permission": "Erlaubnis gewähren", - "group_albums_by": "Alben gruppieren nach...", - "group_country": "Nach Land gruppieren", - "group_no": "Keine Gruppierung", - "group_owner": "Gruppierung nach Besitzer", - "group_places_by": "Orte gruppieren nach...", - "group_year": "Gruppierung nach Jahr", - "haptic_feedback_switch": "Haptisches Feedback aktivieren", - "haptic_feedback_title": "Haptisches Feedback", - "has_quota": "Kontingent", - "hash_asset": "Dateihash", - "hashed_assets": "Gehashte Dateien", - "hashing": "Hashen", - "header_settings_add_header_tip": "Header hinzufügen", - "header_settings_field_validator_msg": "Der Wert darf nicht leer sein", - "header_settings_header_name_input": "Header-Name", - "header_settings_header_value_input": "Header-Wert", - "headers_settings_tile_title": "Benutzerdefinierte Proxy-Header", - "height": "Höhe", - "hi_user": "Hallo {name} ({email})", - "hide_all_people": "Alle Personen verbergen", - "hide_gallery": "Galerie verbergen", - "hide_named_person": "Person {name} verbergen", - "hide_password": "Passwort verbergen", - "hide_person": "Person verbergen", - "hide_schema": "Schema ausblenden", - "hide_text_recognition": "Texterkennung verbergen", - "hide_unnamed_people": "Unbenannte Personen verbergen", - "home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugefügt. {failed} Elemente sind bereits vorhanden.", - "home_page_add_to_album_err_local": "Es können lokale Elemente noch nicht zu Alben hinzugefügt werden, überspringen", - "home_page_add_to_album_success": "{added} Elemente zu {album} hinzugefügt.", - "home_page_album_err_partner": "Inhalte von Partnern können derzeit nicht zu Alben hinzugefügt werden", - "home_page_archive_err_local": "Kann lokale Elemente nicht archvieren, überspringen", - "home_page_archive_err_partner": "Inhalte von Partnern können nicht archiviert werden", - "home_page_building_timeline": "Zeitachse wird erstellt", - "home_page_delete_err_partner": "Inhalte von Partnern können nicht gelöscht werden, überspringe", - "home_page_delete_remote_err_local": "Lokale Elemente in der Auswahl zum Entfernen von Remote-Elementen, Überspringe", - "home_page_favorite_err_local": "Kann lokale Elemente noch nicht favorisieren, überspringen", - "home_page_favorite_err_partner": "Inhalte von Partnern können nicht favorisiert werden, überspringe", - "home_page_first_time_notice": "Wenn Sie die App zum ersten Mal verwenden, wählen Sie bitte ein Album zur Sicherung aus, damit die Zeitachse mit Fotos und Videos gefüllt werden kann", - "home_page_locked_error_local": "Lokale Dateien können nicht in den gesperrten Ordner verschoben werden, überspringe", - "home_page_locked_error_partner": "Dateien von Partnern können nicht in den gesperrten Ordner verschoben werden, überspringe", - "home_page_share_err_local": "Lokale Inhalte können nicht per Link geteilt werden, überspringe", - "home_page_upload_err_limit": "Es können max. 30 Elemente gleichzeitig hochgeladen werden, überspringen", - "host": "Host", - "hour": "Stunde", - "hours": "Stunden", - "id": "ID", - "idle": "Untätig", - "ignore_icloud_photos": "iCloud Fotos ignorieren", - "ignore_icloud_photos_description": "Fotos, die in der iCloud gespeichert sind, werden nicht auf den immich Server hochgeladen", - "image": "Bild", - "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} aufgenommen am {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1} am {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1} und {person2} am {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1}, {person2} und {person3} am {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1}, {person2}, und {additionalCount, number} anderen am {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} am {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1} am {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1} und {person2} am {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1}, {person2}, und {person3} am {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1}, {person2}, und {additionalCount, number} anderen am {date}", - "image_saved_successfully": "Bild gespeichert", - "image_viewer_page_state_provider_download_started": "Download gestartet", - "image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen", - "image_viewer_page_state_provider_share_error": "Fehler beim Teilen", - "immich_logo": "Immich-Logo", - "immich_web_interface": "Immich-Web-Oberfläche", - "import_from_json": "Aus JSON importieren", - "import_path": "Importpfad", - "in_albums": "In {count, plural, one {# Album} other {# Alben}}", - "in_archive": "Im Archiv", - "in_year": "Im Jahr {year}", - "in_year_selector": "Im Jahr", - "include_archived": "Archivierte Dateien einbeziehen", - "include_shared_albums": "Freigegebene Alben einbeziehen", - "include_shared_partner_assets": "Geteilte Partner-Dateien mit einbeziehen", - "individual_share": "Individuelle Freigabe", - "individual_shares": "Individuelles Teilen", - "info": "Info", - "interval": { - "day_at_onepm": "Täglich um 13:00 Uhr", - "hours": "{hours, plural, one {Jede Stunde} other {Alle {hours, number} Stunden}}", - "night_at_midnight": "Täglich um Mitternacht", - "night_at_twoam": "Täglich nachts um 2:00 Uhr" - }, - "invalid_date": "Ungültiges Datum", - "invalid_date_format": "Ungültiges Datumsformat", - "invite_people": "Personen einladen", - "invite_to_album": "Zum Album einladen", - "ios_debug_info_fetch_ran_at": "Abruf läuft {dateTime}", - "ios_debug_info_last_sync_at": "Zuletzt aktualisiert {dateTime}", - "ios_debug_info_no_processes_queued": "Keine Hintergrundprozesse in der Warteschlange", - "ios_debug_info_no_sync_yet": "Noch kein Hintergrundsynchronisierungsauftrag ausgeführt", - "ios_debug_info_processes_queued": "{count, plural, one {{count} Hintergrundprozess in der Warteschlange} other {{count} Hintergrundprozesse in der Warteschlange}}", - "ios_debug_info_processing_ran_at": "Prozess läuft {dateTime}", - "items_count": "{count, plural, one {# Eintrag} other {# Einträge}}", - "jobs": "Aufgaben", - "json_editor": "JSON-Editor", - "json_error": "JSON-Fehler", - "keep": "Behalten", - "keep_albums": "Alben behalten", - "keep_albums_count": "Behalte {count} {count, plural, one {album} other {albums}}", - "keep_all": "Alle behalten", - "keep_description": "Wähle aus, was beim Speicher freigeben auf dem Gerät behalten werden soll.", - "keep_favorites": "Favoriten behalten", - "keep_on_device": "Auf Gerät behalten", - "keep_on_device_hint": "Wähle die Elemente, die auf dem Gerät bleiben sollen", - "keep_this_delete_others": "Dieses behalten, andere löschen", - "keeping": "Behalte: {items}", - "kept_this_deleted_others": "Diese Datei behalten und {count, plural, one {# Datei} other {# Dateien}} gelöscht", - "keyboard_shortcuts": "Tastenkürzel", - "language": "Sprache", - "language_no_results_subtitle": "Probiere es mit einem anderen Suchbegriff", - "language_no_results_title": "Keine Sprachen gefunden", - "language_search_hint": "Sprachen durchsuchen...", - "language_setting_description": "Wähle deine bevorzugte Sprache", - "large_files": "Große Dateien", - "last": "Letzte", - "last_months": "{count, plural, one {Letzter Monat} other {Letzte # Monate}}", - "last_seen": "Zuletzt gesehen", - "latest_version": "Aktuelle Version", - "latitude": "Breitengrad", - "leave": "Verlassen", - "leave_album": "Album verlassen", - "lens_model": "Objektivmodell", - "let_others_respond": "Antworten zulassen", - "level": "Level", - "library": "Bibliothek", - "library_add_folder": "Ordner hinzufügen", - "library_edit_folder": "Ordner bearbeiten", - "library_options": "Bibliotheksoptionen", - "library_page_device_albums": "Alben auf dem Gerät", - "library_page_new_album": "Neues Album", - "library_page_sort_asset_count": "Anzahl der Inhalte", - "library_page_sort_created": "Zuletzt erstellt", - "library_page_sort_last_modified": "Zuletzt bearbeitet", - "library_page_sort_title": "Titel des Albums", - "licenses": "Lizenzen", - "light": "Hell", - "like": "Gefällt mir", - "like_deleted": "Like gelöscht", - "link_motion_video": "Bewegungsvideo verknüpfen", - "link_to_oauth": "Mit OAuth verknüpfen", - "linked_oauth_account": "Verknüpftes OAuth-Konto", - "list": "Liste", - "loading": "Laden", - "loading_search_results_failed": "Laden von Suchergebnissen fehlgeschlagen", - "local": "Lokal", - "local_asset_cast_failed": "Eine Datei, die nicht auf den Server hochgeladen wurde, kann nicht gecastet werden", - "local_assets": "Lokale Dateien", - "local_id": "Lokale ID", - "local_media_summary": "Zusammenfassung der lokalen Medien", - "local_network": "Lokales Netzwerk", - "local_network_sheet_info": "Die App stellt über diese URL eine Verbindung zum Server her, wenn sie das angegebene WLAN-Netzwerk verwendet", - "location": "Standort", - "location_permission": "Standort Genehmigung", - "location_permission_content": "Um die automatische Umschaltfunktion nutzen zu können, benötigt Immich genaue Standortberechtigung, damit es den Namen des aktuellen WiFi-Netzwerks ermitteln kann", - "location_picker_choose_on_map": "Auf der Karte auswählen", - "location_picker_latitude_error": "Gültigen Breitengrad eingeben", - "location_picker_latitude_hint": "Breitengrad eingeben", - "location_picker_longitude_error": "Gültigen Längengrad eingeben", - "location_picker_longitude_hint": "Längengrad eingeben", - "lock": "Sperren", - "locked_folder": "Gesperrter Ordner", - "log_detail_title": "Protokoll Details", - "log_out": "Abmelden", - "log_out_all_devices": "Alle Geräte abmelden", - "logged_in_as": "Angemeldet als {user}", - "logged_out_all_devices": "Alle Geräte abgemeldet", - "logged_out_device": "Gerät abgemeldet", - "login": "Anmelden", - "login_disabled": "Login ist deaktiviert", - "login_form_api_exception": "API Fehler. Bitte die Serveradresse überprüfen und erneut versuchen.", - "login_form_back_button_text": "Zurück", - "login_form_email_hint": "deine@email.de", - "login_form_endpoint_hint": "http://deine-server-ip:port", - "login_form_endpoint_url": "Server-URL", - "login_form_err_http": "Bitte gebe http:// oder https:// an", - "login_form_err_invalid_email": "Ungültige E-Mail", - "login_form_err_invalid_url": "Ungültige URL", - "login_form_err_leading_whitespace": "Leerzeichen am Anfang", - "login_form_err_trailing_whitespace": "Leerzeichen am Ende", - "login_form_failed_get_oauth_server_config": "Fehler beim Login per OAuth, bitte Server-URL überprüfen", - "login_form_failed_get_oauth_server_disable": "Die OAuth-Funktion ist auf diesem Server nicht verfügbar", - "login_form_failed_login": "Fehler beim Login, bitte überprüfe die Server-URL, deine E-Mail oder das Passwort", - "login_form_handshake_exception": "Fehler beim Verbindungsaufbau mit dem Server. Falls du ein selbstsigniertes Zertifikat verwendest, aktiviere die Unterstützung dafür in den Einstellungen.", - "login_form_password_hint": "Passwort", - "login_form_save_login": "Angemeldet bleiben", - "login_form_server_empty": "Serveradresse eingeben.", - "login_form_server_error": "Es Konnte sich nicht mit dem Server verbunden werden.", - "login_has_been_disabled": "Die Anmeldung wurde deaktiviert.", - "login_password_changed_error": "Fehler beim Ändern deines Passwortes", - "login_password_changed_success": "Passwort erfolgreich geändert", - "logout_all_device_confirmation": "Bist du sicher, dass du alle Geräte abmelden willst?", - "logout_this_device_confirmation": "Bist du sicher, dass du dieses Gerät abmelden willst?", - "logs": "Protokolle", - "longitude": "Längengrad", - "look": "Erscheinungsbild", - "loop_videos": "Loop-Videos", - "loop_videos_description": "Aktiviere diese Option, um eine automatische Videoschleife in der Detailansicht zu erstellen.", - "main_branch_warning": "Du benutzt eine Entwicklungsversion. Wir empfehlen dringend, eine Release-Version zu verwenden!", - "main_menu": "Hauptmenü", - "maintenance_action_restore": "Datenbank wird wiederhergestellt", - "maintenance_description": "Immich wurde in den Wartungsmodus versetzt.", - "maintenance_end": "Wartungsmodus beenden", - "maintenance_end_error": "Wartungsmodus konnte nicht beendet werden.", - "maintenance_logged_in_as": "Aktuell angemeldet als {user}", - "maintenance_restore_from_backup": "Von Datenbank wiederherstellen", - "maintenance_restore_library": "Deine Bibliothek wiederherstellen", - "maintenance_restore_library_confirm": "Wenn das korrekt aussieht, mache weiter mit der Wiederherstellung des Backups!", - "maintenance_restore_library_description": "Datenbank wird wiederhergestellt", - "maintenance_restore_library_folder_has_files": "{folder} hat {count} Ordner", - "maintenance_restore_library_folder_no_files": "{folder} fehlen Dateien!", - "maintenance_restore_library_folder_pass": "lesbar und schreibbar", - "maintenance_restore_library_folder_read_fail": "nicht lesbar", - "maintenance_restore_library_folder_write_fail": "nicht schreibbar", - "maintenance_restore_library_hint_missing_files": "Es könnten dir wichtige Dateien fehlen", - "maintenance_restore_library_hint_regenerate_later": "Sie können diese später in den Einstellungen erneut generieren", - "maintenance_restore_library_hint_storage_template_missing_files": "Speichervorlage verwendet? Es könnten wichtige Dateien fehlen", - "maintenance_restore_library_loading": "Lade Integritätsprüfungen und Heuristiken…", - "maintenance_task_backup": "Erstelle ein Backup der vorhandenen Datenbank…", - "maintenance_task_migrations": "Datenbankmigrationen laufen…", - "maintenance_task_restore": "Ausgewählte Sicherungskopie wird wiederhergestellt…", - "maintenance_task_rollback": "Wiederherstellen scheiterte, zurück zu Wiederherstellungspunkt…", - "maintenance_title": "Vorrübergehend nicht verfügbar", - "make": "Marke", - "manage_geolocation": "Standort verwalten", - "manage_media_access_rationale": "Diese Berechtigung wird benötigt, um Dateien ordnungsgemäß in den Papierkorb schieben und daraus wiederherstellen zu können.", - "manage_media_access_settings": "Einstellungen öffnen", - "manage_media_access_subtitle": "Erlaube Immich, Mediendateien zu verwalten und zu verschieben.", - "manage_media_access_title": "Verwaltung von Mediendateien", - "manage_shared_links": "Freigegebene Links verwalten", - "manage_sharing_with_partners": "Gemeinsame Nutzung mit Partnern verwalten", - "manage_the_app_settings": "App-Einstellungen verwalten", - "manage_your_account": "Dein Konto verwalten", - "manage_your_api_keys": "Deine API-Schlüssel verwalten", - "manage_your_devices": "Deine eingeloggten Geräte verwalten", - "manage_your_oauth_connection": "Deine OAuth-Verknüpfung verwalten", - "map": "Karte", - "map_assets_in_bounds": "{count, plural, =0 {Keine Fotos in diesem Gebiet} one {# Foto} other {# Fotos}}", - "map_cannot_get_user_location": "Standort konnte nicht ermittelt werden", - "map_location_dialog_yes": "Ja", - "map_location_picker_page_use_location": "Aufnahmeort verwenden", - "map_location_service_disabled_content": "Ortungsdienste müssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste jetzt aktivieren?", - "map_location_service_disabled_title": "Ortungsdienste deaktiviert", - "map_marker_for_images": "Kartenmarkierung für Bilder, die in {city}, {country} aufgenommen wurden", - "map_marker_with_image": "Kartenmarkierung mit Bild", - "map_no_location_permission_content": "Ortungsdienste müssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste jetzt aktivieren?", - "map_no_location_permission_title": "Kein Zugriff auf den Standort", - "map_settings": "Karteneinstellungen", - "map_settings_dark_mode": "Dunkler Modus", - "map_settings_date_range_option_day": "Letzte 24 Stunden", - "map_settings_date_range_option_days": "Letzten {days} Tage", - "map_settings_date_range_option_year": "Letztes Jahr", - "map_settings_date_range_option_years": "Letzten {years} Jahre", - "map_settings_dialog_title": "Karteneinstellungen", - "map_settings_include_show_archived": "Archivierte anzeigen", - "map_settings_include_show_partners": "Partner einbeziehen", - "map_settings_only_show_favorites": "Nur Favoriten anzeigen", - "map_settings_theme_settings": "Kartendesign", - "map_zoom_to_see_photos": "Ansicht verkleinern um Fotos zu sehen", - "mark_all_as_read": "Alle als gelesen markieren", - "mark_as_read": "Als gelesen markieren", - "marked_all_as_read": "Alle als gelesen markiert", - "matches": "Treffer", - "matching_assets": "Passende Dateien", - "media_type": "Medientyp", - "memories": "Erinnerungen", - "memories_all_caught_up": "Alles aufgeholt", - "memories_check_back_tomorrow": "Schau morgen wieder vorbei für weitere Erinnerungen", - "memories_setting_description": "Verwalte, was du in deinen Erinnerungen siehst", - "memories_start_over": "Erneut beginnen", - "memories_swipe_to_close": "Nach oben Wischen zum Schließen", - "memory": "Erinnerung", - "memory_lane_title": "Foto-Erinnerungen {title}", - "menu": "Menü", - "merge": "Zusammenführen", - "merge_people": "Personen zusammenführen", - "merge_people_limit": "Du kannst maximal 5 Gesichter auf einmal zusammenführen", - "merge_people_prompt": "Willst du diese Personen zusammenführen? Diese Aktion kann nicht rückgängig gemacht werden.", - "merge_people_successfully": "Personen erfolgreich zusammengeführt", - "merged_people_count": "{count, plural, one {# Person} other {# Personen}} zusammengefügt", - "minimize": "Minimieren", - "minute": "Minute", - "minutes": "Minuten", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertikal", - "missing": "Fehlende", - "mobile_app": "Mobile App", - "mobile_app_download_onboarding_note": "Herunterladen der mobilen Begleiter-App über einen der folgenden Möglichkeiten", - "model": "Modell", - "month": "Monat", - "monthly_title_text_date_format": "MMMM y", - "more": "Mehr", - "move": "Verschieben", - "move_down": "Nach unten", - "move_off_locked_folder": "Aus dem gesperrten Ordner verschieben", - "move_to": "Verschieben nach", - "move_to_device_trash": "In Papierkorb verschieben", - "move_to_lock_folder_action_prompt": "{count} zum gesperrten Ordner hinzugefügt", - "move_to_locked_folder": "In den gesperrten Ordner verschieben", - "move_to_locked_folder_confirmation": "Diese Fotos und Videos werden aus allen Alben entfernt und können nur noch im gesperrten Ordner angezeigt werden", - "move_up": "Nach oben", - "moved_to_archive": "{count, plural, one {# Datei} other {# Dateien}} archiviert", - "moved_to_library": "{count, plural, one {# Datei} other {# Dateien}} in die Bibliothek verschoben", - "moved_to_trash": "In den Papierkorb verschoben", - "multiselect_grid_edit_date_time_err_read_only": "Das Datum und die Uhrzeit von schreibgeschützten Inhalten kann nicht verändert werden, überspringen", - "multiselect_grid_edit_gps_err_read_only": "Der Aufnahmeort von schreibgeschützten Inhalten kann nicht verändert werden, überspringen", - "mute_memories": "Erinnerungen stumm schalten", - "my_albums": "Meine Alben", - "name": "Name", - "name_or_nickname": "Name oder Nickname", - "name_required": "Name ist erforderlich", - "navigate": "Navigation", - "navigate_to_time": "Navigiere zu Zeit", - "network_requirement_photos_upload": "Mobile Daten verwenden, um Fotos zu sichern", - "network_requirement_videos_upload": "Mobile Daten verwenden, um Videos zu sichern", - "network_requirements": "Anforderungen ans Netzwerk", - "network_requirements_updated": "Netzwerk-Abhängigkeiten haben sich geändert, Backup-Warteschlange wird zurückgesetzt", - "networking_settings": "Netzwerk", - "networking_subtitle": "Verwaltung von Server-Endpunkt-Einstellungen", - "never": "Niemals", - "new_album": "Neues Album", - "new_api_key": "Neuer API-Schlüssel", - "new_date_range": "Neuer Datumsbereich", - "new_password": "Neues Passwort", - "new_person": "Neue Person", - "new_pin_code": "Neuer PIN-Code", - "new_pin_code_subtitle": "Dies ist dein erster Zugriff auf den gesperrten Ordner. Erstelle einen PIN-Code für den sicheren Zugriff auf diese Seite", - "new_timeline": "Neue Zeitleiste", - "new_update": "Neues Update", - "new_user_created": "Neuer Benutzer wurde erstellt", - "new_version_available": "NEUE VERSION VERFÜGBAR", - "newest_first": "Neueste zuerst", - "next": "Weiter", - "next_memory": "Nächste Erinnerung", - "no": "Nein", - "no_actions_added": "Noch keine Aktionen hinzugefügt", - "no_albums_found": "Keine Alben gefunden", - "no_albums_message": "Erstelle ein Album, um deine Fotos und Videos zu organisieren", - "no_albums_with_name_yet": "Es sieht so aus, als hättest du noch keine Alben mit diesem Namen.", - "no_albums_yet": "Es sieht so aus, als hättest du noch keine Alben.", - "no_archived_assets_message": "Archiviere Fotos und Videos, um sie aus deiner Fotoansicht zu entfernen", - "no_assets_message": "Klicke, um dein erstes Foto hochzuladen", - "no_assets_to_show": "Keine Vorschau vorhanden", - "no_cast_devices_found": "Keine Geräte zum Übertragen gefunden", - "no_checksum_local": "Prüfsumme nicht verfügbar - kann lokale Datei/en nicht laden", - "no_checksum_remote": "Prüfsumme nicht verfügbar - kann entfernte Datei/en nicht laden", - "no_configuration_needed": "Keine Konfiguration benötigt", - "no_devices": "Keine verwendeten Geräte", - "no_duplicates_found": "Es wurden keine Duplikate gefunden.", - "no_exif_info_available": "Keine EXIF-Informationen vorhanden", - "no_explore_results_message": "Lade weitere Fotos hoch, um deine Sammlung zu erkunden.", - "no_favorites_message": "Füge Favoriten hinzu, um deine besten Bilder und Videos schnell zu finden", - "no_filters_added": "Noch keine Filter hinzugefügt", - "no_libraries_message": "Eine externe Bibliothek erstellen, um deine Fotos und Videos anzusehen", - "no_local_assets_found": "Keine lokale Datei mit dieser Prüfsumme gefunden", - "no_location_set": "Kein Standort festgelegt", - "no_locked_photos_message": "Fotos und Videos im gesperrten Ordner sind versteckt und werden nicht angezeigt, wenn du deine Bibliothek durchsuchst.", - "no_name": "Kein Name", - "no_notifications": "Keine Benachrichtigungen", - "no_people_found": "Keine passenden Personen gefunden", - "no_places": "Keine Orte", - "no_remote_assets_found": "Keine entfernten Dateien mit dieser Prüfsumme gefunden", - "no_results": "Keine Ergebnisse", - "no_results_description": "Versuche es mit einem Synonym oder einem allgemeineren Stichwort", - "no_shared_albums_message": "Erstelle ein Album, um Fotos und Videos mit Personen in deinem Netzwerk zu teilen", - "no_uploads_in_progress": "Kein Upload in Bearbeitung", - "none": "Keine", - "not_allowed": "Nicht erlaubt", - "not_available": "N/A", - "not_in_any_album": "In keinem Album", - "not_selected": "Nicht ausgewählt", - "note_apply_storage_label_to_previously_uploaded assets": "Hinweis: Um eine Speicherpfadbezeichnung anzuwenden, starte den", - "notes": "Notizen", - "nothing_here_yet": "Noch nichts hier", - "notification_permission_dialog_content": "Um Benachrichtigungen zu aktivieren, navigiere zu Einstellungen und klicke \"Erlauben\".", - "notification_permission_list_tile_content": "Erlaube Berechtigung für Benachrichtigungen.", - "notification_permission_list_tile_enable_button": "Aktiviere Benachrichtigungen", - "notification_permission_list_tile_title": "Benachrichtigungs-Berechtigung", - "notification_toggle_setting_description": "E-Mail-Benachrichtigungen aktivieren", - "notifications": "Benachrichtigungen", - "notifications_setting_description": "Benachrichtigungen verwalten", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium Konfigurator", - "obtainium_configurator_instructions": "Du kannst Obtainium benutzen, um die App direkt aus den Github Releases zu installieren oder zu aktualisieren. Bitte erstelle dazu einen API-Schlüssel und wähle eine Variante aus um einen Obtainium-Konfigurationslink zu erstellen", - "ocr": "OCR", - "official_immich_resources": "Offizielle Immich Quellen", - "offline": "Offline", - "offset": "Verschiebung", - "ok": "Ok", - "oldest_first": "Älteste zuerst", - "on_this_device": "Auf diesem Gerät", - "onboarding": "Einstieg", - "onboarding_locale_description": "Wählen Sie Ihre bevorzugte Sprache. Sie können diese auch später in Ihren Einstellungen ändern.", - "onboarding_privacy_description": "Die folgenden (optionalen) Funktionen hängen von externen Diensten ab und können jederzeit in den Einstellungen deaktiviert werden.", - "onboarding_server_welcome_description": "Lassen Sie uns diese Instanz mit einigen allgemeinen Einstellungen einrichten.", - "onboarding_theme_description": "Wähle ein Farbschema für deine Instanz aus. Du kannst dies später in deinen Einstellungen ändern.", - "onboarding_user_welcome_description": "Fangen wir an!", - "onboarding_welcome_user": "Willkommen, {user}", - "online": "Online", - "only_favorites": "Nur Favoriten", - "open": "Öffnen", - "open_in_map_view": "In Kartenansicht öffnen", - "open_in_openstreetmap": "In OpenStreetMap öffnen", - "open_the_search_filters": "Die Suchfilter öffnen", - "options": "Optionen", - "or": "oder", - "organize_into_albums": "In Alben organisieren", - "organize_into_albums_description": "Aktuelle Synchronisationseinstellungen verwenden, um existierende Fotos in Alben zu laden", - "organize_your_library": "Organisiere deine Bibliothek", - "original": "Original", - "other": "Sonstiges", - "other_devices": "Andere Geräte", - "other_entities": "Andere Entitäten", - "other_variables": "Sonstige Variablen", - "owned": "Eigenes", - "owner": "Besitzer", - "page": "Seite", - "partner": "Partner", - "partner_can_access": "{partner} hat Zugriff", - "partner_can_access_assets": "auf alle deine Fotos und Videos, außer die Archivierten und Gelöschten", - "partner_can_access_location": "auf den Ort, an dem deine Fotos aufgenommen wurden", - "partner_list_user_photos": "{user}s Fotos", - "partner_list_view_all": "Alle anzeigen", - "partner_page_empty_message": "Deine Fotos sind noch nicht mit einem Partner geteilt.", - "partner_page_no_more_users": "Keine weiteren Nutzer", - "partner_page_partner_add_failed": "Fehler beim Partner hinzufügen", - "partner_page_select_partner": "Partner auswählen", - "partner_page_shared_to_title": "Geteilt mit", - "partner_page_stop_sharing_content": "{partner} wird nicht mehr auf deine Fotos zugreifen können.", - "partner_sharing": "Partner-Sharing", - "partners": "Partner", - "password": "Passwort", - "password_does_not_match": "Passwort stimmt nicht überein", - "password_required": "Passwort benötigt", - "password_reset_success": "Passwort erfolgreich zurückgesetzt", - "past_durations": { - "days": "{days, plural, one {Gestern} other {Letzte # Tage}}", - "hours": "Letzte {hours, plural, one {Stunde} other {# Stunden}}", - "years": "{years, plural, one {Letztes Jahr} other {Letzte # Jahre}}" - }, - "path": "Pfad", - "pattern": "Muster", - "pause": "Pause", - "pause_memories": "Erinnerungen pausieren", - "paused": "Pausiert", - "pending": "Ausstehend", - "people": "Personen", - "people_edits_count": "{count, plural, one {# Person} other {# Personen}} bearbeitet", - "people_feature_description": "Fotos und Videos nach Personen gruppiert durchsuchen", - "people_selected": "{count, plural, one {# Person ausgewählt} other {# Personen ausgewählt}}", - "people_sidebar_description": "Eine Verknüpfung zu Personen in der Seitenleiste anzeigen", - "permanent_deletion_warning": "Warnung vor endgültiger Löschung", - "permanent_deletion_warning_setting_description": "Anzeige einer Warnung beim endgültigen Löschen von Objekten", - "permanently_delete": "Endgültig löschen", - "permanently_delete_assets_count": "{count, plural, one {Datei} other {Dateien}} endgültig löschen", - "permanently_delete_assets_prompt": "Bist du sicher, dass {count, plural, one {diese Datei} other {diese # Dateien}} endgültig gelöscht werden soll? Dadurch {count, plural, one {wird} other {werden}} diese auch aus deinen Alben entfernt.", - "permanently_deleted_asset": "Endgültig gelöschtes Objekt", - "permanently_deleted_assets_count": "{count, plural, one {# Datei} other {# Dateien}} endgültig gelöscht", - "permission": "Berechtigung", - "permission_empty": "Ihre Berechtigung sollte nicht leer sein", - "permission_onboarding_back": "Zurück", - "permission_onboarding_continue_anyway": "Trotzdem fortfahren", - "permission_onboarding_get_started": "Jetzt starten", - "permission_onboarding_go_to_settings": "Gehe zu Einstellungen", - "permission_onboarding_permission_denied": "Berechtigung verweigert. Um Immich zu benutzen, muss Zugriff auf Fotos und Videos in Einstellungen erlaubt werden.", - "permission_onboarding_permission_granted": "Berechtigung erteilt! Du bist startklar.", - "permission_onboarding_permission_limited": "Berechtigungen unzureichend. Um Immich das Sichern von ganzen Sammlungen zu ermöglichen, muss der Zugriff auf alle Fotos und Videos in den Einstellungen erlaubt werden.", - "permission_onboarding_request": "Immich benötigt Berechtigung um auf deine Fotos und Videos zuzugreifen.", - "person": "Person", - "person_age_months": "{months, plural, one {# month} other {# months}} alt", - "person_age_year_months": "1 Jahr, {months, plural, one {# month} other {# months}} alt", - "person_age_years": "{years, plural, one {# Jahr} other {# Jahre}} alt", - "person_birthdate": "Geboren am {date}", - "person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}", - "person_recognized": "Person erkannt", - "person_selected": "Person ausgewählt", - "photo_shared_all_users": "Es sieht so aus, als hättest du deine Fotos mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.", - "photos": "Fotos", - "photos_and_videos": "Fotos & Videos", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", - "photos_from_previous_years": "Fotos von vorherigen Jahren", - "photos_only": "Nur Fotos", - "pick_a_location": "Wähle einen Ort", - "pick_custom_range": "Benutzerdefinierter Zeitraum", - "pick_date_range": "Wähle einen Zeitraum", - "pin_code_changed_successfully": "PIN-Code erfolgreich geändert", - "pin_code_reset_successfully": "PIN-Code erfolgreich zurückgesetzt", - "pin_code_setup_successfully": "PIN-Code erfolgreich festgelegt", - "pin_verification": "PIN-Code Überprüfung", - "place": "Ort", - "places": "Orte", - "places_count": "{count, plural, one {{count, number} Ort} other {{count, number} Orte}}", - "play": "Abspielen", - "play_memories": "Erinnerungen abspielen", - "play_motion_photo": "Bewegte Bilder abspielen", - "play_or_pause_video": "Video abspielen oder pausieren", - "play_original_video": "Originales Video abspielen", - "play_original_video_setting_description": "Bevorzugen die Wiedergabe von Originalvideos gegenüber transkodierten Videos. Wenn das Original nicht kompatibel ist, wird es möglicherweise nicht korrekt wiedergegeben.", - "play_transcoded_video": "Transkodiertes Video abspielen", - "please_auth_to_access": "Für den Zugriff bitte Authentifizieren", - "port": "Port", - "preferences_settings_subtitle": "App-Einstellungen verwalten", - "preferences_settings_title": "Voreinstellungen", - "preparing": "Vorbereiten", - "preset": "Voreinstellung", - "preview": "Vorschau", - "previous": "Vorherige", - "previous_memory": "Vorherige Erinnerung", - "previous_or_next_day": "Tag vorwärts/rückwärts", - "previous_or_next_month": "Monat vorwärts/rückwärts", - "previous_or_next_photo": "Foto vorwärts/rückwärts", - "previous_or_next_year": "Jahr vorwärts/rückwärts", - "primary": "Primär", - "privacy": "Privatsphäre", - "profile": "Profil", - "profile_drawer_app_logs": "Logs", - "profile_drawer_client_server_up_to_date": "Die App- und Server-Versionen sind aktuell", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Schreibgeschützter Modus aktiviert. Halte das Benutzer-Avatar-Symbol gedrückt, um den Modus zu verlassen.", - "profile_image_of_user": "Profilbild von {user}", - "profile_picture_set": "Profilbild gesetzt.", - "public_album": "Öffentliches Album", - "public_share": "Öffentliche Freigabe", - "purchase_account_info": "Unterstützer", - "purchase_activated_subtitle": "Danke für die Unterstützung von Immich und Open-Source Software", - "purchase_activated_time": "Aktiviert am {date}", - "purchase_activated_title": "Dein Schlüssel wurde erfolgreich aktiviert", - "purchase_button_activate": "Aktivieren", - "purchase_button_buy": "Kaufen", - "purchase_button_buy_immich": "Immich kaufen", - "purchase_button_never_show_again": "Nicht nochmal anzeigen", - "purchase_button_reminder": "Erinnere mich in 30 Tagen", - "purchase_button_remove_key": "Schlüssel entfernen", - "purchase_button_select": "Auswählen", - "purchase_failed_activation": "Aktivieren fehlgeschlagen! Überprüfe bitte den Produktschlüssel in der E-Mail!", - "purchase_individual_description_1": "Für eine Einzelperson", - "purchase_individual_description_2": "Unterstützerstatus", - "purchase_individual_title": "Einzelperson", - "purchase_input_suggestion": "Besitzen Sie bereits einen Produktschlüssel? Bitte geben Sie diesen unten ein", - "purchase_license_subtitle": "Kaufe Immich, um die fortlaufende Entwicklung zu unterstützen", - "purchase_lifetime_description": "Lebenslange Gültigkeit", - "purchase_option_title": "KAUFOPTIONEN", - "purchase_panel_info_1": "Die Entwicklung von Immich erfordert viel Zeit und Mühe und wir haben Vollzeit-Entwickler, die daran arbeiten Immich möglichst perfekt zu machen. Unser Ziel ist es, Open-Source-Software und ethische Geschäftspraktiken zu einer verlässlichen Einkommensquelle für Entwickler zu machen und ein datenschutzfreundliches Ökosystem mit echten Alternativen zu ausbeuterischen Cloud-Diensten zu schaffen.", - "purchase_panel_info_2": "Weil wir uns dagegen entschieden haben, eine Bezahlschranke einzusetzen, wird dieser Kauf keine zusätzlichen Funktionen in Immich freischalten. Wir verlassen uns auf Nutzende wie dich, um die Entwicklung von Immich zu unterstützen.", - "purchase_panel_title": "Das Projekt unterstützen", - "purchase_per_server": "Pro Server", - "purchase_per_user": "Pro Benutzer", - "purchase_remove_product_key": "Produktschlüssel entfernen", - "purchase_remove_product_key_prompt": "Bist Du sicher, dass der Produktschlüssel entfernt werden soll?", - "purchase_remove_server_product_key": "Server-Produktschlüssel entfernen", - "purchase_remove_server_product_key_prompt": "Sicher, dass der Server-Produktschlüssel entfernt werden soll?", - "purchase_server_description_1": "Für den gesamten Server", - "purchase_server_description_2": "Unterstützerstatus", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Der Server-Produktschlüssel wird durch den Administrator verwaltet", - "query_asset_id": "Datei-ID abfragen", - "queue_status": "Warteschlange {count}/{total}", - "rate_asset": "Datei bewerten", - "rating": "Bewertung", - "rating_clear": "Bewertung löschen", - "rating_count": "{count, plural, one {# Stern} other {# Sterne}}", - "rating_description": "Stellt die EXIF-Bewertung im Informationsbereich dar", - "rating_set": "Mit {rating, plural, one {# Stern} other {# Sternen}} bewertet", - "reaction_options": "Reaktionsmöglichkeiten", - "read_changelog": "Changelog lesen", - "readonly_mode_disabled": "Schreibgeschützter Modus deaktiviert", - "readonly_mode_enabled": "Schreibgeschützter Modus aktiviert", - "ready_for_upload": "Bereit zum Hochladen", - "reassign": "Neu zuweisen", - "reassigned_assets_to_existing_person": "{count, plural, one {# Datei wurde} other {# Dateien wurden}} {name, select, null {einer vorhandenen Person} other {{name}}} zugewiesen", - "reassigned_assets_to_new_person": "{count, plural, one {# Datei wurde} other {# Dateien wurden}} einer neuen Person zugewiesen", - "reassing_hint": "Markierte Dateien einer vorhandenen Person zuweisen", - "recent": "Neueste", - "recent-albums": "Neueste Alben", - "recent_searches": "Letzte Suchen", - "recently_added": "Kürzlich hinzugefügt", - "recently_added_page_title": "Zuletzt hinzugefügt", - "recently_taken": "Kürzlich aufgenommen", - "recently_taken_page_title": "Kürzlich aufgenommen", - "refresh": "Aktualisieren", - "refresh_encoded_videos": "Kodierte Videos aktualisieren", - "refresh_faces": "Gesichter aktualisieren", - "refresh_metadata": "Metadaten aktualisieren", - "refresh_thumbnails": "Miniaturansichten aktualisieren", - "refreshed": "Aktualisiert", - "refreshes_every_file": "Alle bestehenden und neuen Dateien erneut einlesen", - "refreshing_encoded_video": "Kodierte Videos werden aktualisiert", - "refreshing_faces": "Gesichter werden aktualisiert", - "refreshing_metadata": "Metadaten werden aktualisiert", - "regenerating_thumbnails": "Miniaturansichten werden neu erstellt", - "remote": "Server", - "remote_assets": "Server-Dateien", - "remote_media_summary": "Zusammenfassung der entfernten Medien", - "remove": "Entfernen", - "remove_assets_album_confirmation": "Bist du sicher, dass du {count, plural, one {# Datei} other {# Dateien}} aus dem Album entfernen willst?", - "remove_assets_shared_link_confirmation": "Bist du sicher, dass du {count, plural, one {# Datei} other {# Dateien}} von diesem geteilten Link entfernen willst?", - "remove_assets_title": "Dateien entfernen?", - "remove_custom_date_range": "Benutzerdefinierten Datumsbereich entfernen", - "remove_deleted_assets": "Offline-Dateien entfernen", - "remove_from_album": "Aus Album entfernen", - "remove_from_album_action_prompt": "{count} vom Album entfernt", - "remove_from_favorites": "Aus Favoriten entfernen", - "remove_from_lock_folder_action_prompt": "{count} aus dem gesperrten Ordner entfernt", - "remove_from_locked_folder": "Aus gesperrtem Ordner entfernen", - "remove_from_locked_folder_confirmation": "Bist du sicher, dass du diese Fotos und Videos aus dem gesperrten Ordner entfernen möchtest? Sie werden wieder in deiner Bibliothek sichtbar sein.", - "remove_from_shared_link": "Aus geteiltem Link entfernen", - "remove_memory": "Erinnerung entfernen", - "remove_photo_from_memory": "Foto aus dieser Erinnerung entfernen", - "remove_tag": "Tag entfernen", - "remove_url": "URL entfernen", - "remove_user": "Nutzer entfernen", - "removed_api_key": "API-Schlüssel {name} wurde entfernt", - "removed_from_archive": "Aus dem Archiv entfernt", - "removed_from_favorites": "Aus den Favoriten entfernt", - "removed_from_favorites_count": "{count, plural, other {#}} aus den Favoriten entfernt", - "removed_memory": "Erinnerung entfernt", - "removed_photo_from_memory": "Foto aus Erinnerung entfernt", - "removed_tagged_assets": "Tag von {count, plural, one {# Datei} other {# Dateien}} entfernt", - "rename": "Umbenennen", - "repair": "Reparatur", - "repair_no_results_message": "Nicht auffindbare und fehlende Dateien werden hier angezeigt", - "replace_with_upload": "Durch Upload ersetzen", - "repository": "Repositorium", - "require_password": "Passwort erforderlich", - "require_user_to_change_password_on_first_login": "Benutzer muss das Passwort beim ersten Login ändern", - "rescan": "Erneut scannen", - "reset": "Zurücksetzen", - "reset_password": "Passwort zurücksetzen", - "reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen", - "reset_pin_code": "PIN-Code zurücksetzen", - "reset_pin_code_description": "Falls du deinen PIN-Code vergessen hast, kannst du dich an den Server-Administrator wenden, um ihn zurückzusetzen", - "reset_pin_code_success": "PIN-Code erfolgreich zurückgesetzt", - "reset_pin_code_with_password": "Mit deinem Passwort kannst du jederzeit deinen PIN-Code zurücksetzen", - "reset_sqlite": "SQLite Datenbank zurücksetzen", - "reset_sqlite_confirmation": "Bist du sicher, dass du die SQLite-Datenbank zurücksetzen willst? Du musst dich ab- und wieder anmelden, um die Daten neu zu synchronisieren", - "reset_sqlite_success": "SQLite Datenbank erfolgreich zurückgesetzt", - "reset_to_default": "Auf Standard zurücksetzen", - "resolution": "Auflösung", - "resolve_duplicates": "Duplikate entfernen", - "resolved_all_duplicates": "Alle Duplikate aufgelöst", - "restore": "Wiederherstellen", - "restore_all": "Alle wiederherstellen", - "restore_trash_action_prompt": "{count} aus dem Papierkorb wiederhergestellt", - "restore_user": "Nutzer wiederherstellen", - "restored_asset": "Datei wiederhergestellt", - "resume": "Fortsetzen", - "resume_paused_jobs": "{count, plural, one {# Aufgabe fortsetzen } other {# Aufgaben fortsetzen}}", - "retry_upload": "Upload wiederholen", - "review_duplicates": "Duplikate überprüfen", - "review_large_files": "Große Dateien überprüfen", - "role": "Rolle", - "role_editor": "Bearbeiter", - "role_viewer": "Betrachter", - "running": "Läuft", - "save": "Speichern", - "save_to_gallery": "In Galerie speichern", - "saved": "Gespeichert", - "saved_api_key": "API-Schlüssel wurde gespeichert", - "saved_profile": "Profil gespeichert", - "saved_settings": "Einstellungen gespeichert", - "say_something": "Etwas sagen", - "scaffold_body_error_occurred": "Ein Fehler ist aufgetreten", - "scan": "Scannen", - "scan_all_libraries": "Alle Bibliotheken scannen", - "scan_library": "Scannen", - "scan_settings": "Scan-Einstellungen", - "scanning": "Scanne", - "scanning_for_album": "Nach Alben scannen...", - "search": "Suche", - "search_albums": "Album suchen", - "search_by_context": "Suche nach Kontext", - "search_by_description": "Nach Beschreibung suchen", - "search_by_description_example": "Wandern in Sapa", - "search_by_filename": "Suche nach Dateiname oder -erweiterung", - "search_by_filename_example": "z.B. IMG_1234.JPG oder PNG", - "search_by_ocr": "Suche per OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Suche nach Kameralinse...", - "search_camera_make": "Suche nach Kameramarke...", - "search_camera_model": "Suche nach Kameramodell...", - "search_city": "Suche nach Stadt...", - "search_country": "Suche nach Land...", - "search_filter_apply": "Filter anwenden", - "search_filter_camera_title": "Kameratyp auswählen", - "search_filter_date": "Datum", - "search_filter_date_interval": "{start} bis {end}", - "search_filter_date_title": "Wähle einen Zeitraum", - "search_filter_display_option_not_in_album": "Nicht im Album", - "search_filter_display_options": "Anzeigeeinstellungen", - "search_filter_filename": "Suche nach Dateiname", - "search_filter_location": "Ort", - "search_filter_location_title": "Ort auswählen", - "search_filter_media_type": "Medientyp", - "search_filter_media_type_title": "Medientyp auswählen", - "search_filter_ocr": "Suche per OCR", - "search_filter_people_title": "Personen auswählen", - "search_filter_star_rating": "Sternebewertung", - "search_for": "Suche nach", - "search_for_existing_person": "Suche nach vorhandener Person", - "search_no_more_result": "Keine weiteren Ergebnisse", - "search_no_people": "Keine Personen", - "search_no_people_named": "Keine Person mit dem Namen \"{name}\"", - "search_no_result": "Keine Ergebnisse gefunden, probiere eine andere Kombination oder einen anderen Suchbegriff", - "search_options": "Suchoptionen", - "search_page_categories": "Kategorien", - "search_page_motion_photos": "Live-Fotos", - "search_page_no_objects": "Keine Objektinformationen verfügbar", - "search_page_no_places": "Keine Informationen über Orte verfügbar", - "search_page_screenshots": "Bildschirmfotos", - "search_page_search_photos_videos": "Nach deinen Fotos und Videos suchen", - "search_page_selfies": "Selfies", - "search_page_things": "Gegenstände und Tiere", - "search_page_view_all_button": "Alle anzeigen", - "search_page_your_activity": "Deine Aktivität", - "search_page_your_map": "Deine Karte", - "search_people": "Suche nach Personen", - "search_places": "Suche nach Orten", - "search_rating": "Suche nach Bewertung...", - "search_result_page_new_search_hint": "Neue Suche", - "search_settings": "Suche nach Einstellungen", - "search_state": "Suche nach Bundesland / Provinz...", - "search_suggestion_list_smart_search_hint_1": "Intelligente Suche ist standardmäßig aktiviert; um nach Metadaten zu suchen, folgenden Syntax benutzen: ", - "search_suggestion_list_smart_search_hint_2": "m:dein-suchbegriff", - "search_tags": "Suche nach Tags...", - "search_timezone": "Suche nach Zeitzone...", - "search_type": "Suche nach Typ", - "search_your_photos": "Durchsuche deine Fotos", - "searching_locales": "Suche nach Orten...", - "second": "Sekunde", - "see_all_people": "Alle Personen anzeigen", - "select": "Auswählen", - "select_album": "Album auswählen", - "select_album_cover": "Album-Cover auswählen", - "select_albums": "Alben auswählen", - "select_all": "Alles auswählen", - "select_all_duplicates": "Alle Duplikate auswählen", - "select_all_in": "Alle in {group} auswählen", - "select_avatar_color": "Avatar-Farbe auswählen", - "select_count": "{count, plural, one {Wähle #} other {Wähle #}}", - "select_cutoff_date": "Stichtag auswählen", - "select_face": "Gesicht auswählen", - "select_featured_photo": "Anzeigebild auswählen", - "select_from_computer": "Vom Computer auswählen", - "select_keep_all": "Alle behalten", - "select_library_owner": "Bibliotheksbesitzer auswählen", - "select_new_face": "Neues Gesicht auswählen", - "select_people": "Personen auswählen", - "select_person": "Person auswählen", - "select_person_to_tag": "Wählen Sie eine Person zum Markieren aus", - "select_photos": "Fotos auswählen", - "select_trash_all": "Alle löschen", - "select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden", - "selected": "Ausgewählt", - "selected_count": "{count, plural, other {# ausgewählt}}", - "selected_gps_coordinates": "Ausgewählte GPS-Koordinaten", - "send_message": "Nachricht senden", - "send_welcome_email": "Begrüssungsmail senden", - "server_endpoint": "Server-Endpunkt", - "server_info_box_app_version": "App-Version", - "server_info_box_server_url": "Server-URL", - "server_offline": "Server offline", - "server_online": "Server online", - "server_privacy": "Privatsphäre auf dem Server", - "server_restarting_description": "Diese Seite wird jeden Moment neu geladen.", - "server_restarting_title": "Server wird neu gestartet", - "server_stats": "Server-Statistiken", - "server_update_available": "Server Update verfügbar", - "server_version": "Server-Version", - "set": "Speichern", - "set_as_album_cover": "Als Albumcover festlegen", - "set_as_featured_photo": "Als Anzeigebild setzen", - "set_as_profile_picture": "Als Profilbild festlegen", - "set_date_of_birth": "Geburtsdatum festlegen", - "set_profile_picture": "Profilbild einstellen", - "set_slideshow_to_fullscreen": "Diashow auf Vollbild einstellen", - "set_stack_primary_asset": "Als primäre Datei festlegen", - "setting_image_viewer_help": "Der Detailbildbetrachter lädt zuerst ein (kleines) Vorschaubild, dann ein Vorschaubild in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).", - "setting_image_viewer_original_subtitle": "Aktivieren, um das Originalbild in voller Auflösung (groß!) zu laden. Deaktivieren, um den Datenverbrauch zu reduzieren (sowohl im Netzwerk als auch im Gerätespeicher).", - "setting_image_viewer_original_title": "Original laden", - "setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur das Vorschaubild zu verwenden.", - "setting_image_viewer_preview_title": "Vorschaubild laden", - "setting_image_viewer_title": "Bilder", - "setting_languages_apply": "Anwenden", - "setting_languages_subtitle": "App-Sprache ändern", - "setting_notifications_notify_failures_grace_period": "Benachrichtigung bei Fehler(n) in der Hintergrundsicherung: {duration}", - "setting_notifications_notify_hours": "{count} Stunden", - "setting_notifications_notify_immediately": "sofort", - "setting_notifications_notify_minutes": "{count} Minuten", - "setting_notifications_notify_never": "niemals", - "setting_notifications_notify_seconds": "{count} Sekunden", - "setting_notifications_single_progress_subtitle": "Detaillierter Upload-Fortschritt für jedes Element", - "setting_notifications_single_progress_title": "Zeige den detaillierten Fortschritt der Hintergrundsicherung", - "setting_notifications_subtitle": "Benachrichtigungen anpassen", - "setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)", - "setting_notifications_total_progress_title": "Zeige den Gesamtfortschritt der Hintergrundsicherung", - "setting_video_viewer_auto_play_subtitle": "Videos automatisch wiedergeben sobald sie geöffnet werden", - "setting_video_viewer_auto_play_title": "Videos automatisch wiedergeben", - "setting_video_viewer_looping_title": "Video-Wiederholung", - "setting_video_viewer_original_video_subtitle": "Beim Streaming eines Videos vom Server wird das Original abgespielt, auch wenn eine Transkodierung verfügbar ist. Kann zu Pufferung führen. Lokal verfügbare Videos werden unabhängig von dieser Einstellung in Originalqualität wiedergegeben.", - "setting_video_viewer_original_video_title": "Originalvideo erzwingen", - "settings": "Einstellungen", - "settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden", - "settings_saved": "Einstellungen gespeichert", - "setup_pin_code": "Einen PIN-Code festlegen", - "share": "Teilen", - "share_action_prompt": "{count} Dateien geteilt", - "share_add_photos": "Fotos hinzufügen", - "share_assets_selected": "{count} ausgewählt", - "share_dialog_preparing": "Vorbereiten...", - "share_link": "Link teilen", - "shared": "Geteilt", - "shared_album_activities_input_disable": "Kommentare sind deaktiviert", - "shared_album_activity_remove_content": "Möchtest du diese Aktivität entfernen?", - "shared_album_activity_remove_title": "Aktivität entfernen", - "shared_album_section_people_action_error": "Fehler beim Verlassen oder Entfernen aus dem Album", - "shared_album_section_people_action_leave": "Benutzer vom Album entfernen", - "shared_album_section_people_action_remove_user": "Benutzer von Album entfernen", - "shared_album_section_people_title": "PERSONEN", - "shared_by": "Geteilt von", - "shared_by_user": "Von {user} geteilt", - "shared_by_you": "Von dir geteilt", - "shared_from_partner": "Fotos von {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} hochgeladen", - "shared_link_app_bar_title": "Geteilte Links", - "shared_link_clipboard_copied_massage": "Link kopiert", - "shared_link_clipboard_text": "Link: {link}\nPasswort: {password}", - "shared_link_create_error": "Fehler beim Erstellen der Linkfreigabe", - "shared_link_custom_url_description": "Greife über eine benutzerdefinierte URL auf diesen Freigabelink zu", - "shared_link_edit_description_hint": "Beschreibung eingeben", - "shared_link_edit_expire_after_option_day": "1 Tag", - "shared_link_edit_expire_after_option_days": "{count} Tagen", - "shared_link_edit_expire_after_option_hour": "1 Stunde", - "shared_link_edit_expire_after_option_hours": "{count} Stunden", - "shared_link_edit_expire_after_option_minute": "1 Minute", - "shared_link_edit_expire_after_option_minutes": "{count} Minuten", - "shared_link_edit_expire_after_option_months": "{count} Monaten", - "shared_link_edit_expire_after_option_year": "{count} Jahr", - "shared_link_edit_password_hint": "Passwort eingeben", - "shared_link_edit_submit_button": "Link aktualisieren", - "shared_link_error_server_url_fetch": "Fehler beim Ermitteln der Server-URL", - "shared_link_expires_day": "Läuft ab in {count} Tag", - "shared_link_expires_days": "Läuft ab in {count} Tagen", - "shared_link_expires_hour": "Läuft ab in {count} Stunde", - "shared_link_expires_hours": "Läuft ab in {count} Stunden", - "shared_link_expires_minute": "Läuft ab in {count} Minute", - "shared_link_expires_minutes": "Läuft ab in {count} Minuten", - "shared_link_expires_never": "Läuft nie ab", - "shared_link_expires_second": "Läuft ab in {count} Sekunde", - "shared_link_expires_seconds": "Läuft ab in {count} Sekunden", - "shared_link_individual_shared": "Individuell geteilt", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Geteilte Links verwalten", - "shared_link_options": "Optionen für geteilten Link", - "shared_link_password_description": "Für den Zugriff auf diesen freigegebenen Link ist ein Passwort erforderlich", - "shared_links": "Geteilte Links", - "shared_links_description": "Teile Fotos und Videos mit einem Link", - "shared_photos_and_videos_count": "{assetCount, plural, one {# geteiltes Foto oder Video.} other {# geteilte Fotos & Videos.}}", - "shared_with_me": "Mit mir geteilt", - "shared_with_partner": "Geteilt mit {partner}", - "sharing": "Geteiltes", - "sharing_enter_password": "Bitte gib das Passwort ein, um diese Seite anzuzeigen.", - "sharing_page_album": "Geteilte Alben", - "sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.", - "sharing_page_empty_list": "LEERE LISTE", - "sharing_sidebar_description": "Eine Verknüpfung zu Geteiltem in der Seitenleiste anzeigen", - "sharing_silver_appbar_create_shared_album": "Neues geteiltes Album", - "sharing_silver_appbar_share_partner": "Mit Partner teilen", - "shift_to_permanent_delete": "Drücke ⇧, um die Datei endgültig zu löschen", - "show_album_options": "Album-Optionen anzeigen", - "show_albums": "Alben anzeigen", - "show_all_people": "Alle Personen anzeigen", - "show_and_hide_people": "Personen ein- & ausblenden", - "show_file_location": "Dateispeicherort anzeigen", - "show_gallery": "Galerie anzeigen", - "show_hidden_people": "Ausgeblendete Personen anzeigen", - "show_in_timeline": "In Zeitleiste anzeigen", - "show_in_timeline_setting_description": "Fotos und Videos dieses Benutzers in deiner Zeitleiste anzeigen", - "show_keyboard_shortcuts": "Tastaturkürzel anzeigen", - "show_metadata": "Metadaten anzeigen", - "show_or_hide_info": "Informationen ein- oder ausblenden", - "show_password": "Passwort anzeigen", - "show_person_options": "Personen-Optionen anzeigen", - "show_progress_bar": "Fortschrittsbalken anzeigen", - "show_schema": "Schema anzeigen", - "show_search_options": "Suchoptionen anzeigen", - "show_shared_links": "Zeige geteilte Links", - "show_slideshow_transition": "Slideshow-Übergang anzeigen", - "show_supporter_badge": "Unterstützerabzeichen", - "show_supporter_badge_description": "Zeige Unterstützerabzeichen", - "show_text_recognition": "Texterkennung anzeigen", - "show_text_search_menu": "Zeige Menü für Textsuche", - "shuffle": "Durchmischen", - "sidebar": "Seitenleiste", - "sidebar_display_description": "Zeige einen Link zu der Ansicht in der Seitenleiste an", - "sign_out": "Abmelden", - "sign_up": "Registrieren", - "size": "Größe", - "skip_to_content": "Zum Inhalt springen", - "skip_to_folders": "Springe zu Ordnern", - "skip_to_tags": "Springe zu Tags", - "slideshow": "Diashow", - "slideshow_repeat": "Slideshow wiederholen", - "slideshow_repeat_description": "Wenn Slideshow beendet, zum Anfang zurückkehren", - "slideshow_settings": "Diashow-Einstellungen", - "sort_albums_by": "Alben sortieren nach...", - "sort_created": "Erstellungsdatum", - "sort_items": "Anzahl der Einträge", - "sort_modified": "Änderungsdatum", - "sort_newest": "Neuestes Foto", - "sort_oldest": "Ältestes Foto", - "sort_people_by_similarity": "Personen nach Ähnlichkeit sortieren", - "sort_recent": "Neuestes Foto", - "sort_title": "Titel", - "source": "Quellcode", - "stack": "Stapel", - "stack_action_prompt": "{count} gestapelt", - "stack_duplicates": "Duplikate stapeln", - "stack_select_one_photo": "Hauptfoto für den Stapel auswählen", - "stack_selected_photos": "Ausgewählte Fotos stapeln", - "stacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} gestapelt", - "stacktrace": "Stapelaufgaben", - "start": "Starten", - "start_date": "Anfangsdatum", - "start_date_before_end_date": "Anfangsdatum muss vor dem Enddatum liegen", - "state": "Bundesland / Provinz", - "status": "Status", - "stop_casting": "Übertragung stoppen", - "stop_motion_photo": "Stop-Motion-Foto", - "stop_photo_sharing": "Deine Fotos nicht mehr teilen?", - "stop_photo_sharing_description": "{partner} wird keinen Zugriff mehr auf deine Fotos haben.", - "stop_sharing_photos_with_user": "Aufhören Fotos mit diesem Benutzer zu teilen", - "storage": "Speicherplatz", - "storage_label": "Speicherpfad", - "storage_quota": "Speicherplatz-Kontingent", - "storage_usage": "{used} von {available} verwendet", - "submit": "Bestätigen", - "success": "Erfolgreich", - "suggestions": "Vorschläge", - "sunrise_on_the_beach": "Sonnenaufgang am Strand", - "support": "Unterstützung", - "support_and_feedback": "Unterstützung & Feedback", - "support_third_party_description": "Deine Immich-Installation wurde von einem Drittanbieter zusammengestellt. Probleme, die bei dir auftreten, können durch dieses Paket verursacht werden. Bitte wende dich daher in erster Linie an diesen Anbieter, indem du die unten stehenden Links verwendest.", - "swap_merge_direction": "Vertauschen der Zusammenführungsrichtung", - "sync": "Synchronisieren", - "sync_albums": "Alben synchronisieren", - "sync_albums_manual_subtitle": "Synchronisiere alle hochgeladenen Videos und Fotos in die ausgewählten Backup-Alben", - "sync_local": "Lokal synchronisieren", - "sync_remote": "mit Server synchronisieren", - "sync_status": "Synchronisierungstatus", - "sync_status_subtitle": "Synchronisierungssystem anzeigen und bearbeiten", - "sync_upload_album_setting_subtitle": "Erstelle und lade deine ausgewählten Fotos und Videos in die ausgewählten Alben auf Immich hoch", - "tag": "Tag", - "tag_assets": "Dateien taggen", - "tag_created": "Tag erstellt: {tag}", - "tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen", - "tag_not_found_question": "Kein Tag vorhanden? Erstelle einen neuen Tag.", - "tag_people": "Personen taggen", - "tag_updated": "Tag aktualisiert: {tag}", - "tagged_assets": "{count, plural, one {# Datei} other {# Dateien}} getagged", - "tags": "Tags", - "tap_to_run_job": "Tippen, um den Job zu starten", - "template": "Vorlage", - "text_recognition": "Texterkennung", - "theme": "Theme", - "theme_selection": "Themenauswahl", - "theme_selection_description": "Automatische Einstellung des Themes auf Hell oder Dunkel, je nach Systemeinstellung des Browsers", - "theme_setting_asset_list_storage_indicator_title": "Fortschrittsbalken der Sicherung auf dem Vorschaubild", - "theme_setting_asset_list_tiles_per_row_title": "Anzahl der Elemente pro Reihe ({count})", - "theme_setting_colorful_interface_subtitle": "Primärfarbe auf App-Hintergrund anwenden.", - "theme_setting_colorful_interface_title": "Farbige Benutzeroberfläche", - "theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters", - "theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters", - "theme_setting_primary_color_subtitle": "Farbauswahl für primäre Aktionen und Akzente.", - "theme_setting_primary_color_title": "Primärfarbe", - "theme_setting_system_primary_color_title": "Systemfarbe verwenden", - "theme_setting_system_theme_switch": "Automatisch (Systemeinstellung)", - "theme_setting_theme_subtitle": "Wählen Sie die Themeneinstellung der App", - "theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich", - "theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren", - "then": "Dann", - "they_will_be_merged_together": "Sie werden zusammengeführt", - "third_party_resources": "Drittanbieter-Quellen", - "time": "Zeit", - "time_based_memories": "Zeitbasierte Erinnerungen", - "time_based_memories_duration": "Anzahl der Sekunden, die jedes Bild angezeigt wird.", - "timeline": "Zeitleiste", - "timezone": "Zeitzone", - "to_archive": "Archivieren", - "to_change_password": "Passwort ändern", - "to_favorite": "Zu Favoriten hinzufügen", - "to_login": "Anmelden", - "to_multi_select": "zur Mehrfachauswahl", - "to_parent": "Gehe zum Übergeordneten", - "to_select": "zum Auswählen", - "to_trash": "In den Papierkorb verschieben", - "toggle_settings": "Einstellungen umschalten", - "toggle_theme_description": "Design wechseln", - "total": "Gesamt", - "total_usage": "Gesamtnutzung", - "trash": "Papierkorb", - "trash_action_prompt": "{count} in den Papierkorb verschoben", - "trash_all": "Alle löschen", - "trash_count": "Papierkorb {count, number}", - "trash_delete_asset": "Datei löschen/in den Papierkorb verschieben", - "trash_emptied": "Geleerter Papierkorb", - "trash_no_results_message": "Gelöschte Fotos und Videos werden hier angezeigt.", - "trash_page_delete_all": "Alle löschen", - "trash_page_empty_trash_dialog_content": "Elemente im Papierkorb löschen? Diese Elemente werden dauerhaft aus Immich entfernt", - "trash_page_info": "Elemente im Papierkorb werden nach {days} Tagen endgültig gelöscht", - "trash_page_no_assets": "Es gibt keine Daten im Papierkorb", - "trash_page_restore_all": "Alle wiederherstellen", - "trash_page_select_assets_btn": "Elemente auswählen", - "trash_page_title": "Papierkorb ({count})", - "trashed_items_will_be_permanently_deleted_after": "Objekte im Papierkorb werden nach {days, plural, one {# Tag} other {# Tagen}} endgültig gelöscht.", - "trigger": "Auslöser", - "trigger_asset_uploaded": "Datei hochgeladen", - "trigger_asset_uploaded_description": "Löst aus, wenn eine neue Datei hochgeladen wurde", - "trigger_description": "Ein Ereignis, das den Workflow startet", - "trigger_person_recognized": "Person erkannt", - "trigger_person_recognized_description": "Löst aus, wenn eine Person erkannt wird", - "trigger_type": "Auslöser-Typ", - "troubleshoot": "Fehler beheben", - "type": "Typ", - "unable_to_change_pin_code": "PIN-Code konnte nicht geändert werden", - "unable_to_check_version": "App oder Server Versionscheck nicht möglich", - "unable_to_setup_pin_code": "PIN-Code konnte nicht festgelegt werden", - "unarchive": "Entarchivieren", - "unarchive_action_prompt": "{count} aus dem Archiv entfernt", - "unarchived_count": "{count, plural, other {# entarchiviert}}", - "undo": "Rückgängig", - "unfavorite": "Entfavorisieren", - "unfavorite_action_prompt": "{count} aus den Favoriten entfernt", - "unhide_person": "Person einblenden", - "unknown": "Unbekannt", - "unknown_country": "Unbekanntes Land", - "unknown_date": "Unbekanntes Datum", - "unknown_year": "Unbekanntes Jahr", - "unlimited": "Unlimitiert", - "unlink_motion_video": "Verknüpfung zum Bewegungsvideo aufheben", - "unlink_oauth": "OAuth entfernen", - "unlinked_oauth_account": "OAuth-Konto entfernt", - "unmute_memories": "Stummschaltung für Erinnerungen aufheben", - "unnamed_album": "Unbenanntes Album", - "unnamed_album_delete_confirmation": "Bist du sicher, dass du dieses Album löschen willst?", - "unnamed_share": "Unbenannte Freigabe", - "unsaved_change": "Ungespeicherte Änderung", - "unselect_all": "Alles abwählen", - "unselect_all_duplicates": "Alle Duplikate abwählen", - "unselect_all_in": "Alle in {group} abwählen", - "unstack": "Entstapeln", - "unstack_action_prompt": "{count} entstapelt", - "unstacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} entstapelt", - "unsupported_field_type": "Nicht unterstützter Feldtyp", - "untagged": "Ohne Tag", - "untitled_workflow": "Unbenannter Workflow", - "up_next": "Weiter", - "update_location_action_prompt": "Aktualsiere den Ort von {count} ausgewählten Dateien mit:", - "updated_at": "Aktualisiert", - "updated_password": "Passwort aktualisiert", - "upload": "Hochladen", - "upload_concurrency": "Parallelität beim Hochladen", - "upload_details": "Upload Details", - "upload_dialog_info": "Willst du die ausgewählten Elemente auf dem Server sichern?", - "upload_dialog_title": "Element hochladen", - "upload_error_with_count": "Uploadfehler für {count, plural, one {# asset} other {# assets}}", - "upload_errors": "Hochladen mit {count, plural, one {# Fehler} other {# Fehlern}} abgeschlossen, aktualisiere die Seite, um neu hochgeladene Dateien zu sehen.", - "upload_finished": "Upload fertig", - "upload_progress": "{remaining, number} verbleibend - {processed, number}/{total, number} verarbeitet", - "upload_skipped_duplicates": "{count, plural, one {# doppelte Datei} other {# doppelte Dateien}} ausgelassen", - "upload_status_duplicates": "Duplikate", - "upload_status_errors": "Fehler", - "upload_status_uploaded": "Hochgeladen", - "upload_success": "Hochladen erfolgreich. Aktualisiere die Seite, um neue hochgeladene Dateien zu sehen.", - "upload_to_immich": "Auf Immich hochladen ({count})", - "uploading": "Wird hochgeladen", - "uploading_media": "Medien werden hochgeladen", - "url": "URL", - "usage": "Verwendung", - "use_biometric": "Biometrie verwenden", - "use_current_connection": "Aktuelle Verbindung verwenden", - "use_custom_date_range": "Stattdessen einen benutzerdefinierten Datumsbereich verwenden", - "user": "Nutzer", - "user_has_been_deleted": "Dieser Benutzer wurde gelöscht.", - "user_id": "Nutzer-ID", - "user_liked": "{type, select, photo {Dieses Foto} video {Dieses Video} asset {Diese Datei} other {Dies}} gefällt {user}", - "user_pin_code_settings": "PIN-Code", - "user_pin_code_settings_description": "Verwalte deinen PIN-Code", - "user_privacy": "Datenschutzeinstellungen Nutzer", - "user_purchase_settings": "Kauf", - "user_purchase_settings_description": "Kauf verwalten", - "user_role_set": "{user} als {role} festlegen", - "user_usage_detail": "Nutzungsdetails der Nutzer", - "user_usage_stats": "Statistiken zur Kontonutzung", - "user_usage_stats_description": "Statistiken zur Kontonutzung anzeigen", - "username": "Nutzername", - "users": "Benutzer", - "users_added_to_album_count": "{count, plural, one {# Benutzer} other {# Benutzer}} zum Album hinzugefügt", - "utilities": "Werkzeuge", - "validate": "Validieren", - "validate_endpoint_error": "Bitte gib eine gültige URL ein", - "validation_error": "Validierungsfehler", - "variables": "Variablen", - "version": "Version", - "version_announcement_closing": "Dein Freund, Alex", - "version_announcement_message": "Hi! Es gibt eine neue Version von Immich. Bitte nimm dir Zeit, die Versionshinweise zu lesen, um Fehlkonfigurationen zu vermeiden, insbesondere wenn du WatchTower oder ein anderes Verfahren verwendest, das Immich automatisch aktualisiert.", - "version_history": "Versionshistorie", - "version_history_item": "{version} am {date} installiert", - "video": "Video", - "video_hover_setting": "Videovorschau beim Hovern abspielen", - "video_hover_setting_description": "Spiele die Miniaturansicht des Videos ab, wenn sich die Maus über dem Element befindet. Auch wenn die Funktion deaktiviert ist, kann die Wiedergabe gestartet werden, indem du mit der Maus über das Wiedergabesymbol fährst.", - "videos": "Videos", - "videos_count": "{count, plural, one {# Video} other {# Videos}}", - "videos_only": "Nur Videos", - "view": "Ansicht", - "view_album": "Album anzeigen", - "view_all": "Alles anzeigen", - "view_all_users": "Alle Nutzer anzeigen", - "view_asset_owners": "Dateibesitzer anzeigen", - "view_details": "Details ansehen", - "view_in_timeline": "In Zeitleiste anzeigen", - "view_link": "Link anzeigen", - "view_links": "Links anzeigen", - "view_name": "Ansicht", - "view_next_asset": "Nächste Datei anzeigen", - "view_previous_asset": "Vorherige Datei anzeigen", - "view_qr_code": "QR code anzeigen", - "view_similar_photos": "Zeige ähnliche Fotos an", - "view_stack": "Stapel anzeigen", - "view_user": "Benutzer anzeigen", - "viewer_remove_from_stack": "Aus Stapel entfernen", - "viewer_stack_use_as_main_asset": "An Stapelanfang", - "viewer_unstack": "Stapel aufheben", - "visibility_changed": "Sichtbarkeit für {count, plural, one {# Person} other {# Personen}} geändert", - "visual": "Visuell", - "visual_builder": "Visueller Editor", - "waiting": "Wartend", - "waiting_count": "In Warteschlage: {count}", - "warning": "Warnung", - "week": "Woche", - "welcome": "Willkommen", - "welcome_to_immich": "Willkommen bei Immich", - "width": "Breite", - "wifi_name": "WiFi-Name", - "workflow_delete_prompt": "Bist du sicher, dass du diesen Workflow löschen willst?", - "workflow_deleted": "Workflow gelöscht", - "workflow_description": "Workflow-Beschreibung", - "workflow_info": "Workflow-Info", - "workflow_json": "Workflow JSON", - "workflow_json_help": "Workflow-Konfiguration im JSON-Editor bearbeiten. Änderungen werden mit dem visuellen Editor synchronisiert.", - "workflow_name": "Workflow-Name", - "workflow_navigation_prompt": "Bist du sicher, dass du den Editor ohne zu speichern verlassen willst?", - "workflow_summary": "Workflow-Zusammenfassung", - "workflow_update_success": "Workflow erfolgreich aktualisiert", - "workflow_updated": "Workflow aktualisiert", - "workflows": "Workflows", - "workflows_help_text": "Workflows automatisieren Aktionen auf deinen Dateien, basierend auf Auslösern und Filtern", - "wrong_pin_code": "PIN-Code falsch", - "year": "Jahr", - "years_ago": "Vor {years, plural, one {einem Jahr} other {# Jahren}}", - "yes": "Ja", - "you_dont_have_any_shared_links": "Du hast keine geteilten Links", - "your_wifi_name": "Dein WiFi-Name", - "zero_to_clear_rating": "drücke 0 um die Dateibewertung zurückzusetzen", - "zoom_image": "Bild vergrößern", - "zoom_to_bounds": "Auf Grenzen zoomen" -} +{} diff --git a/i18n/de_CH.json b/i18n/de_CH.json index 52bff4839f..0967ef424b 100644 --- a/i18n/de_CH.json +++ b/i18n/de_CH.json @@ -1,103 +1 @@ -{ - "about": "Über", - "account": "Konto", - "account_settings": "Konto Istelligä", - "acknowledge": "Bestätige", - "action": "Aktion", - "action_common_update": "Update", - "action_description": "Eine Reihe von Aktionen, die an den gefilterten Assets ausgeführt werden sollen", - "actions": "Aktione", - "active": "Aktiv", - "active_count": "Aktivi: {count}", - "activity": "Aktivität", - "activity_changed": "Aktivität ist {enabled, select, true {aktiviert} other {deaktiviert}}", - "add": "Hinzuefüegä", - "add_a_description": "Beschriibig hinzuefüege", - "add_a_location": "Standort hinzuefüege", - "add_a_name": "Name hinzuefüege", - "add_a_title": "Titel hinzuefüege", - "add_action": "Aktion hinzuefüege", - "add_action_description": "Aklicke um en Aktion dure zfüehre", - "add_birthday": "Geburtstag hinzuefüege", - "add_endpoint": "Endpunkt hinzuefüge", - "add_exclusion_pattern": "Exklusions muster hinzuefüege", - "add_filter": "Filter hinzuefüge", - "add_filter_description": "Klicken, um eine Filterbedingung hinzuzufügen", - "add_location": "Standort hinzuefüege", - "add_more_users": "Meh Benutzer hinzuefüege", - "add_partner": "Partner hinzufügen", - "add_path": "Pfad hinzuefüege", - "add_photos": "Föteli hinzuefüege", - "add_tag": "Tag hinzufügen", - "add_to": "Hinzuefüege zu …", - "add_to_album": "Zum Album hinzuefüege", - "add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt", - "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Es hend es paar lokali Dateie nöd chöne im Album hinzuegfüegt werde", - "add_to_album_toggle": "Auswahl umschalten für {album}", - "add_to_albums": "Zu Albe hinzuefüege", - "add_to_albums_count": "Zu Alben hinzufügen ({count})", - "add_to_bottom_bar": "Hinzuefüege zu", - "add_to_shared_album": "Zum teilte Album hinzuefüege", - "add_upload_to_stack": "Upload zum Stack hinzuefüege", - "add_url": "URL hinzuefüege", - "add_workflow_step": "Workflow-Schritt hinzufügen", - "added_to_archive": "Is Archiv verschobe", - "added_to_favorites": "Zu dine Favoritä hinzuegfüegt", - "added_to_favorites_count": "{count, number} zu Favoriten hinzugefügt", - "admin": { - "add_exclusion_pattern_description": "Ausschlussmuster hinzufügen. Platzhalter, wie *, **, und ? werden unterstützt. Um alle Dateien in einem Verzeichnis namens „Raw\" zu ignorieren, „**/Raw/**“ verwenden. Um alle Dateien zu ignorieren, die auf „.tif“ enden, „**/*.tif“ verwenden. Um einen absoluten Pfad zu ignorieren, „/pfad/zum/ignorieren/**“ verwenden.", - "admin_user": "Admin Benutzer", - "asset_offline_description": "Diese Datei einer externen Bibliothek befindet sich nicht mehr auf der Festplatte und wurde in den Papierkorb verschoben. Falls die Datei innerhalb der Bibliothek verschoben wurde, überprüfe deine Zeitleiste auf die neue entsprechende Datei. Um diese Datei wiederherzustellen, stelle bitte sicher, dass Immich auf den unten stehenden Dateipfad zugreifen kann und scanne die Bibliothek.", - "authentication_settings": "Authentifizierigs Iistellige", - "authentication_settings_description": "Passwort, OAuth und anderi Authentifizierigseinstellige verwalte", - "authentication_settings_disable_all": "Bisch sicher, dass du alli Login-Methodä wotsch deaktivierä? S Login isch denn komplett deaktiviert.", - "authentication_settings_reenable": "Nutze einen Server-Befehl zur Reaktivierung.", - "background_task_job": "Hintergrund Ufgabä", - "backup_database": "Datenbank-Dump aalege", - "backup_database_enable_description": "Datenbank-Dumps aktiviere", - "backup_keep_last_amount": "Aazahl vo de vorherige Dumps, wo bhalte werde sölle", - "backup_onboarding_1_description": "Offsite-Kopie i dä Cloud oder amene andere physische Standort.", - "backup_onboarding_2_description": "Lokali Kopie uf verschiedene Grät. Das beinhaltet d Hauptdateie und e lokali Sicherig vo dene Dateie.", - "backup_onboarding_3_description": "Total aazahl vo dine Dateikopie, inklusiv d Originaldateie. Das beinhaltet 1 Offsite-Kopie und 2 lokali Kopie.", - "backup_onboarding_description": "E 3-2-1-Backup-Strategie wird empfohle, zum dini Dateie z schütze. Du söttsch sowohl Kopie vo dine ufgeladene Fotos/Videos wie au d Immich-Datenbank bhalte, für e rundum sauberi Backup-Lösig.", - "backup_onboarding_footer": "Für meh Infos zum Backup vo Immich lueg bitte i d Dokumentation.", - "backup_onboarding_parts_title": "Es 3-2-1-Backup beinhaltet:", - "backup_onboarding_title": "Backups", - "backup_settings": "Iistellige für Datenbank-Dumps", - "backup_settings_description": "Datenbank-Dump-Iistellige verwalte.", - "cleared_jobs": "Jobs glöscht für: {job}", - "config_set_by_file": "D Konfiguration isch aktuell dur e Konfigurationsdatei gsetzt", - "confirm_delete_library": "Bisch sicher, dass du d Bibliothek {library} wotsch lösche?", - "confirm_delete_library_assets": "Bisch sicher, dass du die Bibliothek wotsch lösche? Das löscht {count, plural, one {# enthaltenes Asset} other {alli # enthaltene Assets}} us Immich und chan nöd rückgängig gmacht werde. D Dateie bliibed uf em Dateträger.", - "confirm_email_below": "Zum bestätige bitte \"{email}\" une iitippe", - "confirm_reprocess_all_faces": "Bisch sicher, dass du alli Gsichter neu verarbeite wotsch? Däbii werde au benannti Persone glöscht.", - "confirm_user_password_reset": "Bist du sicher, dass du das Passwort für {user} zurücksetzen möchtest?", - "confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN-Code von {user} zurücksetzen möchtest?", - "copy_config_to_clipboard_description": "Kopiere die aktuelle Systemkonfiguration als JSON-Objekt in die Zwischenablage", - "create_job": "Aufgabe erstellen", - "cron_expression": "Cron-Zeitangabe", - "cron_expression_description": "Setze das Scanintervall im Cron-Format. Hilfe mit dem Format bietet dir dabei z. B. der Crontab Guru", - "cron_expression_presets": "Vorlagen für Cron-Ausdruck", - "disable_login": "Login deaktiviere", - "duplicate_detection_job_description": "Diese Aufgabe führt das maschinelle Lernen für jede Datei aus, um Duplikate zu finden. Diese Aufgabe beruht auf der intelligenten Suche", - "exclusion_pattern_description": "Mit Ausschlussmustern können Dateien und Ordner beim Scannen Ihrer Bibliothek ignoriert werden. Dies ist nützlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren möchtest, wie z. B. RAW-Dateien.", - "export_config_as_json_description": "Lade die aktuelle Systemkonfiguration als JSON-Datei herunter", - "external_libraries_page_description": "Externe Bibliotheksseite für Administratoren", - "face_detection": "Gsichtserkennig", - "face_detection_description": "Diese Aufgabe erfasst Gesichter in Dateien mittels maschinellen Lernens. Bei Videos wird nur die Miniaturansicht verwendet. „Aktualisieren“ verarbeitet alle Dateien neu. „Zurücksetzen“ setzt zusätzlich alle Gesichter zurück. „Fehlende“ stellt nur nicht verarbeitete Dateien in die Warteschlange. Erfasste Gesichter werden zur Gesichtsidentifizierung in die Warteschlange gestellt, um sie in bestehende oder neue Personen zu gruppieren.", - "facial_recognition_job_description": "Diese Aufgabe gruppiert im Anschluss an die Gesichtserfassung die erfassten Gesichter zu Personen. „Zurücksetzen“ gruppiert alle Gesichter neu, während „Fehlende“ Gesichter ohne Zuordnung in die Warteschlange stellt.", - "failed_job_command": "Befehl {command} ist für Aufgabe {job} fehlgeschlagen", - "force_delete_user_warning": "WARNUNG: Diese Aktion löscht sofort den Benutzer und all seine Dateien. Dies kann nicht rückgängig gemacht werden und die Dateien können nicht wiederhergestellt werden.", - "image_format": "Format", - "image_format_description": "WebP erzeugt kleinere Dateien als JPEG, ist aber etwas langsamer in der Erstellung.", - "image_fullsize_description": "Hochauflösendes Bild mit entfernten Metadaten, das beim Zoomen verwendet wird", - "image_fullsize_enabled": "Hochauflösende Vorschaubilder aktivieren", - "image_fullsize_enabled_description": "Generiere hochauflösende Vorschaubilder in Originalauflösung für nicht web-kompatibel Formate. Wenn \"Eingebettete Vorschau bevorzugen\" aktiviert ist, werden eingebettete Vorschaubilder direkt verwendet. Hat keinen Einfluss auf web-kompatible Formate wie JPEG.", - "image_fullsize_quality_description": "Qualität der hochauflösenden Vorschaubilder von 1-100. Höher ist besser, erzeugt aber grössere Dateien.", - "image_fullsize_title": "Hochauflösende Vorschaueinstellungen", - "image_prefer_embedded_preview": "Eingebettete Vorschau bevorzugen", - "image_prefer_embedded_preview_setting_description": "Verwende eingebettete Vorschaubilder in RAW-Fotos als Grundlage für die Bildverarbeitung, sofern diese zur Verfügung stehen. Dies kann bei einigen Bildern genauere Farben erzeugen, allerdings ist die Qualität der Vorschau kameraabhängig und das Bild kann mehr Kompressionsartefakte aufweisen.", - "image_prefer_wide_gamut": "Breites Spektrum bevorzugen" - } -} +{} diff --git a/i18n/el.json b/i18n/el.json index 072e283a72..0967ef424b 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -1,2375 +1 @@ -{ - "about": "Σχετικά", - "account": "Λογαριασμός", - "account_settings": "Ρυθμίσεις Λογαριασμού", - "acknowledge": "Έλαβα γνώση", - "action": "Ενέργεια", - "action_common_update": "Ενημέρωση", - "action_description": "Ενέργειες που εφαρμόζονται στα φιλτραρισμένα στοιχεία", - "actions": "Ενέργειες", - "active": "Ενεργά", - "active_count": "Ενεργά: {count}", - "activity": "Δραστηριότητα", - "activity_changed": "Η δραστηριότητα είναι {enabled, select, true {ενεργοποιημένη} other {απενεργοποιημένη}}", - "add": "Προσθήκη", - "add_a_description": "Προσθήκη περιγραφής", - "add_a_location": "Προσθήκη τοποθεσίας", - "add_a_name": "Προσθήκη ενός ονόματος", - "add_a_title": "Προσθήκη τίτλου", - "add_action": "Προσθήκη ενέργειας", - "add_action_description": "Κάντε κλικ για να προσθέσετε ενέργεια", - "add_assets": "Προσθήκη στοιχείων", - "add_birthday": "Προσθήκη γενεθλίων", - "add_endpoint": "Προσθήκη τελικού σημείου", - "add_exclusion_pattern": "Προσθήκη μοτίβου αποκλεισμού", - "add_filter": "Προσθήκη φίλτρου", - "add_filter_description": "Κάντε κλικ για να προσθέσετε συνθήκη φίλτρου", - "add_location": "Προσθήκη τοποθεσίας", - "add_more_users": "Προσθήκη επιπλέον χρηστών", - "add_partner": "Προσθήκη συνεργάτη", - "add_path": "Προσθήκη διαδρομής", - "add_photos": "Προσθήκη φωτογραφιών", - "add_tag": "Προσθήκη ετικέτας", - "add_to": "Προσθήκη σε…", - "add_to_album": "Προσθήκη σε άλμπουμ", - "add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}", - "add_to_album_bottom_sheet_already_exists": "Ήδη στο {album}", - "add_to_album_bottom_sheet_some_local_assets": "Ορισμένα τοπικά στοιχεία δεν μπόρεσαν να προστεθούν στο άλμπουμ", - "add_to_album_toggle": "Εναλλαγή επιλογής για το {album}", - "add_to_albums": "Προσθήκη στα άλμπουμ", - "add_to_albums_count": "Προσθήκη στα άλμπουμ ({count})", - "add_to_bottom_bar": "Προσθήκη σε", - "add_to_shared_album": "Προσθήκη σε κοινόχρηστο άλμπουμ", - "add_upload_to_stack": "Προσθήκη αρχείου στην ουρά", - "add_url": "Προσθήκη Συνδέσμου", - "add_workflow_step": "Προσθήκη βήματος ροής εργασίας", - "added_to_archive": "Προστέθηκε στο αρχείο", - "added_to_favorites": "Προστέθηκε στα αγαπημένα", - "added_to_favorites_count": "Προστέθηκαν {count, number} στα αγαπημένα", - "admin": { - "add_exclusion_pattern_description": "Προσθέστε μοτίβα αποκλεισμού. Υποστηρίζεται η επιλογή πολλών με *, **, και ?. Για να αγνοηθούν όλα τα αρχεία σε έναν φάκελο με το όνομα \"Raw\", χρησιμοποιήστε \"**/Raw/**\". Για να αγνοηθούν όλα τα αρχεία με κατάληξη \".tif\", χρησιμοποιήστε \"**/*.tif\". Για να αγνοηθεί μία απόλυτη διαδρομή, χρησιμοποιήστε \"/path/to/ignore/**\".", - "admin_user": "Διαχειριστής", - "asset_offline_description": "Αυτό το στοιχείο εξωτερικής βιβλιοθήκης δε βρίσκεται πλέον στο δίσκο και έχει μεταφερθεί στα απορρίμματα. Εάν το αρχείο έχει μετακινηθεί εντός της βιβλιοθήκης, ελέγξτε το χρονολόγιο φωτογραφιών σας για το νέο αντίστοιχο στοιχείο. Για να επαναφέρετε αυτό το στοιχείο, βεβαιωθείτε ότι το παρακάτω μονοπάτι αρχείου είναι προσβάσιμο από το Immich και σαρώστε τη βιβλιοθήκη.", - "authentication_settings": "Ρυθμίσεις ελέγχου ταυτότητας", - "authentication_settings_description": "Διαχείριση κωδικού πρόσβασης, OAuth και άλλων ρυθμίσεων ελέγχου ταυτότητας", - "authentication_settings_disable_all": "Είστε βέβαιοι ότι θέλετε να απενεργοποιήσετε όλες τις μεθόδους σύνδεσης; Η σύνδεση θα απενεργοποιηθεί πλήρως.", - "authentication_settings_reenable": "Για επανενεργοποίηση, χρησιμοποιήστε μία Εντολή Διακομιστή.", - "background_task_job": "Εργασίες Παρασκηνίου", - "backup_database": "Δημιουργία Dump βάσης δεδομένων", - "backup_database_enable_description": "Ενεργοποίηση dumps βάσης δεδομένων", - "backup_keep_last_amount": "Ποσότητα προηγούμενων dumps που πρέπει να διατηρηθούν", - "backup_onboarding_1_description": "αντίγραφο ασφαλείας εκτός εγκατάστασης, είτε στο cloud είτε σε άλλη φυσική τοποθεσία.", - "backup_onboarding_2_description": "τοπικά αντίγραφα σε διαφορετικές συσκευές. Αυτό περιλαμβάνει τα κύρια αρχεία και ένα τοπικό αντίγραφο ασφαλείας αυτών των αρχείων.", - "backup_onboarding_3_description": "συνολικά αντίγραφα των δεδομένων σας, συμπεριλαμβανομένων των αρχικών αρχείων. Αυτό περιλαμβάνει 1 αντίγραφο εκτός εγκατάστασης (offsite) και 2 τοπικά αντίγραφα.", - "backup_onboarding_description": "Συνιστάται η στρατηγική αντιγράφων ασφαλείας 3-2-1 για την προστασία των δεδομένων σας. Θα πρέπει να διατηρείτε αντίγραφα των ανεβασμένων φωτογραφιών/βίντεό σας, καθώς και της βάσης δεδομένων του Immich, για μια ολοκληρωμένη λύση backup.", - "backup_onboarding_footer": "Για περισσότερες πληροφορίες σχετικά με τη δημιουργία αντιγράφων ασφαλείας του Immich, ανατρέξε στον οδηγό τεκμηρίωσης.", - "backup_onboarding_parts_title": "Ένα αντίγραφο ασφαλείας τύπου 3-2-1 περιλαμβάνει:", - "backup_onboarding_title": "Αντίγραφα ασφαλείας", - "backup_settings": "Ρυθμίσεις dump βάσης δεδομένων", - "backup_settings_description": "Διαχείριση ρυθμίσεων dump της βάσης δεδομένων.", - "cleared_jobs": "Εκκαθαρίστηκαν οι εργασίες για: {job}", - "config_set_by_file": "Η παραμετροποίηση γίνεται, προς το παρόν, μέσω ενός αρχείου παραμέτρων", - "confirm_delete_library": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τη βιβλιοθήκη {library};", - "confirm_delete_library_assets": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη βιβλιοθήκη; Αυτό θα διαγράψει τα {count, plural, one {# contained asset} other {all # contained assets}} από το Immich και δεν μπορεί να αναιρεθεί. Τα αρχεία θα παραμείνουν στον δίσκο.", - "confirm_email_below": "Για επιβεβαίωση, πληκτρολογήστε \"{email}\" παρακάτω", - "confirm_reprocess_all_faces": "Είστε βέβαιοι ότι θέλετε να επεξεργαστείτε ξανά όλα τα πρόσωπα; Αυτό θα εκκαθαρίσει ακόμα και τα άτομα στα οποία έχετε ήδη ορίσει το όνομα.", - "confirm_user_password_reset": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε τον κωδικό πρόσβασης του χρήστη {user};", - "confirm_user_pin_code_reset": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε τον κωδικό PIN του χρήστη {user};", - "copy_config_to_clipboard_description": "Αντέγραψε την τρέχουσα διαμόρφωση συστήματος στο πρόχειρο, ως αντικείμενο JSON", - "create_job": "Δημιουργία εργασίας", - "cron_expression": "Σύνταξη Cron", - "cron_expression_description": "Ορίστε το διάστημα σάρωσης χρησιμοποιώντας τη μορφή cron. Για περισσότερες πληροφορίες, ανατρέξτε π.χ. στο Crontab Guru", - "cron_expression_presets": "Προκαθορισμένες εκφράσεις cron", - "disable_login": "Απενεργοποίηση σύνδεσης", - "duplicate_detection_job_description": "Εκτελέστε μηχανική μάθηση σε στοιχεία για να εντοπίσετε παρόμοιες εικόνες. Βασίζεται στην Έξυπνη Αναζήτηση", - "exclusion_pattern_description": "Τα μοτίβα αποκλεισμού σας επιτρέπουν να αγνοείται αρχεία και φακέλους κατά τη σάρωση της βιβλιοθήκης σας. Αυτό είναι χρήσιμο εάν εχετε φακέλους που περιέχουν αρχεία που δεν θέλετε να εισάγετε, όπως αρχεία RAW.", - "export_config_as_json_description": "Κατέβασε την τρέχουσα διαμόρφωση συστήματος ως αρχείο JSON", - "external_libraries_page_description": "Σελίδα εξωτερικής βιβλιοθήκης διαχειριστή", - "face_detection": "Ανίχνευση προσώπου", - "face_detection_description": "Ανιχνεύστε τα πρόσωπα σε στοιχεία χρησιμοποιώντας μηχανική μάθηση. Για βίντεο, λαμβάνεται υπόψη μόνο η μικρογραφία. Η επιλογή \"Ανανέωση\" επεξεργάζεται εκ νέου όλα τα στοιχεία. Η επιλογή \"Επαναφορά\", επιπλέον εκκαθαρίζει όλα τα δεδομένα προσώπου. Η επιλογή \"Ελλείποντα\" προσθέτει στην ουρά στοιχεία που δεν έχουν υποστεί ακόμη επεξεργασία. Τα πρόσωπα που έχουν εντοπιστεί θα μπουν στην ουρά για την Αναγνώριση Προσώπου μετά την ολοκλήρωση της Ανίχνευσης Προσώπου, ομαδοποιώντας τα σε υπάρχοντα ή νέα άτομα.", - "facial_recognition_job_description": "Ομαδοποιήστε ανιχνευμένα πρόσωπα σε άτομα. Αυτό το βήμα εκτελείται αφού ολοκληρωθεί η Ανίχνευση Προσώπου. Η επιλογή \"Επαναφορά\" ομαδοποιεί εκ νέου όλα τα πρόσωπα. Η επιλογή \"Ελλείποντα\" βάζει στην ουρά για ομαδοποίηση πρόσωπα που δεν έχουν αντιστοιχηθεί σε κάποιο άτομο.", - "failed_job_command": "Η εντολή {command} απέτυχε για την εργασία: {job}", - "force_delete_user_warning": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα αφαιρέσει άμεσα τον χρήστη και όλα τα στοιχεία. Αυτό δεν μπορεί να αναιρεθεί και τα αρχεία δεν μπορούν να ανακτηθούν.", - "image_format": "Μορφή", - "image_format_description": "Η μορφή WebP παράγει μικρότερα αρχεία από τη μορφή JPEG, αλλά είναι πιο αργή στην κωδικοποίηση.", - "image_fullsize_description": "Εικόνα πλήρους μεγέθους με απογυμνωμένα μεταδεδομένα, που χρησιμοποιείται κατά τη μεγέθυνση", - "image_fullsize_enabled": "Ενεργοποίηση δημιουργίας εικόνας πλήρους μεγέθους", - "image_fullsize_enabled_description": "Δημιουργία εικόνας πλήρους μεγέθους για μορφές που δεν είναι φιλικές προς το διαδίκτυο. Όταν είναι ενεργοποιημένη η επιλογή «Προτίμηση ενσωματωμένης προεπισκόπησης», οι ενσωματωμένες προεπισκοπήσεις χρησιμοποιούνται απευθείας χωρίς μετατροπή. Δεν επηρεάζει τις φιλικές προς το διαδίκτυο μορφές όπως το JPEG.", - "image_fullsize_quality_description": "Ποιότητα εικόνας πλήρους μεγέθους από 1-100. Η υψηλότερη είναι καλύτερη, αλλά παράγει μεγαλύτερα αρχεία.", - "image_fullsize_title": "Ρυθμίσεις εικόνας πλήρους μεγέθους", - "image_prefer_embedded_preview": "Προτίμηση ενσωματωμένης προεπισκόπησης", - "image_prefer_embedded_preview_setting_description": "Χρήση ενσωματωμένων προεπισκοπίσεων σε RAW εικόνες ως είσοδο για την επεξεργασία εικόνας εφόσον είναι διαθέσιμες. Αυτό μπορεί να δημιουργήσει πιο ακριβή χρώματα για κάποιες εικόνες, αλλά η ποιότητα των προεπισκοπίσεων εξαρτάται από την κάμερα και ενδέχεται να υπάρχουν περισσότερες αλλοιώσεις στην εικόνα λόγω συμπίεσης.", - "image_prefer_wide_gamut": "Προτίμηση ευρέος φάσματος", - "image_prefer_wide_gamut_setting_description": "Χρήση Display P3 για τις μικρογραφίες. Αυτό διατηρεί καλύτερα την ζωντάνια των χρωμάτων σε εικόνες μεγάλου χρωματικού εύρους, αλλά ενδέχεται να εμφανίζονται αλλιώς σε παλαιότερες συσκευές με παλαιότερες εκδόσεις περιηγητών. Οι εικόνες sRGB μένουν ως έχουν για να αποφευχθούν χρωματικές αλλαγές.", - "image_preview_description": "Μεσαίου μεγέθους εικόνες, χωρίς μεταδεδομένα, οι οποίες χρησιμοποιούνται στην προβολή ενός αντικειμένου και για μηχανική μάθηση", - "image_preview_quality_description": "Ποιότητα προεπισκόπησης από 1 έως 100. Όσο μεγαλύτερη τιμή τόσο καλύτερη η ποιότητα, αλλά παράγονται μεγαλύτερα αρχεία που ενδέχεται να μειώσουν την ταχύτητα απόκρισης της εφαρμογής. Οι χαμηλές τιμές μπορεί να επηρεάσουν τη ποιότητα της μηχανικής μάθησης.", - "image_preview_title": "Ρυθμίσεις Προεπισκόπισης", - "image_quality": "Ποιότητα", - "image_resolution": "Ανάλυση", - "image_resolution_description": "Υψηλότερες αναλύσεις μπορούν να διατηρήσουν περισσότερες λεπτομέρειες, αλλά χρειάζονται περισσότερο χρόνο να κωδικοποιηθούν, έχουν μεγαλύτερα μεγέθη αρχείων και μπορούν να μειώσουν την απόκριση της εφαρμογής.", - "image_settings": "Ρυθμίσεις Εικόνας", - "image_settings_description": "Διαχείριση της ποιότητας και της ανάλυσης των παραγόμενων εικόνων", - "image_thumbnail_description": "Μικρογραφία εικόνας χωρίς μεταδεδομένα, που χρησιμοποιείται όταν προβάλλονται ομάδες φωτογραφιών όπως το κύριο χρονολόγιο", - "image_thumbnail_quality_description": "Ποιότητα μικρογραφίας από 1 έως 100. Υψηλότερες τιμές είναι καλύτερες, αλλά παράγουν μεγαλύτερα αρχεία που μπορεί να μειώσουν την ταχύτητα απόκρισης της εφαρμογής.", - "image_thumbnail_title": "Ρυθμίσεις Μικρογραφίας", - "import_config_from_json_description": "Εισαγωγή διαμόρφωσης συστήματος με ανέβασμα αρχείου διαμόρφωσης JSON", - "job_concurrency": "Ταυτόχρονη εκτέλεση {job}", - "job_created": "Εργασία δημιουργήθηκε", - "job_not_concurrency_safe": "Αυτή η εργασία δεν είναι ασφαλής για ταυτόχρονη εκτέλεση.", - "job_settings": "Ρυθμίσεις εργασίας", - "job_settings_description": "Διαχείριση ταυτόχρονης εκτέλεσης εργασίας", - "jobs_delayed": "{jobCount, plural, one {# καθυστέρησε} other {# καθυστέρησαν}}", - "jobs_failed": "{jobCount, plural, one {# απέτυχε} other {# απέτυχαν}}", - "jobs_over_time": "Εργασίες με την πάροδο του χρόνου", - "library_created": "Δημιουργήθηκε η βιβλιοθήκη: {library}", - "library_deleted": "Η βιβλιοθήκη διαγράφηκε", - "library_details": "Λεπτομέρειες βιβλιοθήκης", - "library_folder_description": "Καθόρισε έναν φάκελο για εισαγωγή. Ο φάκελος αυτός, μαζί με τους υποφακέλους του, θα σαρωθεί για εικόνες και βίντεο.", - "library_remove_exclusion_pattern_prompt": "Είσαι σίγουρος ότι θέλεις να αφαιρέσεις αυτό το μοτίβο εξαίρεσης;", - "library_remove_folder_prompt": "Είσαι σίγουρος ότι θέλεις να αφαιρέσεις αυτόν τον φάκελο εισαγωγής;", - "library_scanning": "Περιοδική Σάρωση", - "library_scanning_description": "Ρύθμιση περιοδικής σάρωσης βιβλιοθήκης", - "library_scanning_enable_description": "Ενεργοποίηση περιοδικής σάρωσης βιβλιοθήκης", - "library_settings": "Εξωτερική βιβλιοθήκη", - "library_settings_description": "Διαχείριση ρυθμίσεων εξωτερικής βιβλιοθήκης", - "library_tasks_description": "Σάρωση εξωτερικών βιβλιοθηκών για νέα ή/και αλλαγμένα στοιχεία", - "library_updated": "Ενημερωμένη βιβλιοθήκη", - "library_watching_enable_description": "Παρακολούθηση εξωτερικών βιβλιοθηκών για τροποποιήσεις αρχείων", - "library_watching_settings": "Παρακολούθηση βιβλιοθήκης [ΠΕΙΡΑΜΑΤΙΚΟ]", - "library_watching_settings_description": "Αυτόματη παρακολούθηση για τροποποιημένα αρχεία", - "logging_enable_description": "Ενεργοποίηση καταγραφής συμβάντων", - "logging_level_description": "Το επίπεδο καταγραφής συμβάντων που θα εφαρμοστεί, όταν αυτή είναι ενεργοποιημένη.", - "logging_settings": "Καταγραφή συμβάντων", - "machine_learning_availability_checks": "Έλεγχοι διαθεσιμότητας", - "machine_learning_availability_checks_description": "Αυτόματος ανίχνευση και προτίμηση διαθέσιμων διακομιστών μηχανικής μάθησης", - "machine_learning_availability_checks_enabled": "Ενεργοποίηση ελέγχων διαθεσιμότητας", - "machine_learning_availability_checks_interval": "Διάστημα ελέγχου", - "machine_learning_availability_checks_interval_description": "Διάστημα σε χιλιοστά δευτερολέπτου μεταξύ των ελέγχων διαθεσιμότητας", - "machine_learning_availability_checks_timeout": "Αίτημα χρονικού ορίου λήξης", - "machine_learning_availability_checks_timeout_description": "Χρονικό όριο σε χιλιοστά δευτερολέπτου για ελέγχους διαθεσιμότητας", - "machine_learning_clip_model": "Μοντέλο CLIP", - "machine_learning_clip_model_description": "Το όνομα ενός μοντέλου CLIP που αναφέρεται εδώ. Σημειώστε ότι πρέπει να επανεκτελέσετε την εργασία 'Έξυπνη Αναζήτηση' για όλες τις εικόνες μετά την αλλαγή μοντέλου.", - "machine_learning_duplicate_detection": "Εντοπισμός Διπλότυπων", - "machine_learning_duplicate_detection_enabled": "Ενεργοποίηση εντοπισμού διπλότυπων", - "machine_learning_duplicate_detection_enabled_description": "Εάν απενεργοποιηθεί, απολύτως παρόμοια στοιχεία θα συνεχίσουν να εκκαθαρίζονται από διπλότυπα.", - "machine_learning_duplicate_detection_setting_description": "Χρησιμοποιήστε τις ενσωματώσεις CLIP για να βρείτε πιθανά διπλότυπα", - "machine_learning_enabled": "Ενεργοποίηση μηχανικής μάθησης", - "machine_learning_enabled_description": "Εάν απενεργοποιηθεί, όλες οι λειτουργίες μηχανικής μάθησης θα απενεργοποιηθούν, ανεξάρτητα από τις παρακάτω ρυθμίσεις.", - "machine_learning_facial_recognition": "Αναγνώριση Προσώπου", - "machine_learning_facial_recognition_description": "Εντοπισμός, αναγνώριση και ομαδοποίηση προσώπων που υπάρχουν σε εικόνες", - "machine_learning_facial_recognition_model": "Μοντέλο αναγνώρισης προσώπου", - "machine_learning_facial_recognition_model_description": "Τα μοντέλα παρατίθενται με φθίνουσα σειρά μεγέθους. Τα μεγαλύτερα μοντέλα είναι πιο αργά και χρησιμοποιούν περισσότερη μνήμη, αλλά παράγουν καλύτερα αποτελέσματα. Σημειώστε ότι πρέπει να επανεκτελέσετε την εργασία \"Ανίχνευση Προσώπου\" για όλες τις εικόνες μετά την αλλαγή μοντέλου.", - "machine_learning_facial_recognition_setting": "Ενεργοποίηση αναγνώρισης προσώπου", - "machine_learning_facial_recognition_setting_description": "Αν απενεργοποιηθεί, οι εικόνες δεν θα κωδικοποιούνται για αναγνώριση προσώπου και δεν θα συμπληρώνουν την ενότητα Άτομα στη σελίδα Περιήγησης.", - "machine_learning_max_detection_distance": "Μέγιστη απόσταση ανίχνευσης", - "machine_learning_max_detection_distance_description": "Η μέγιστη απόσταση μεταξύ δύο εικόνων για να θεωρηθούν διπλότυπες, που κυμαίνεται από 0,001-0,1. Οι υψηλότερες τιμές θα εντοπίσουν περισσότερες διπλότυπες, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.", - "machine_learning_max_recognition_distance": "Μέγιστη απόσταση αναγνώρισης", - "machine_learning_max_recognition_distance_description": "Η μέγιστη απόσταση μεταξύ δύο προσώπων για να θεωρείται το ίδιο άτομο, που κυμαίνεται από 0-2. Η μείωση αυτής της τιμής μπορεί να αποτρέψει την επισήμανση δύο ατόμων ως το ίδιο άτομο, ενώ η αύξηση της μπορεί να αποτρέψει την επισήμανση του ίδιου ατόμου ως δύο διαφορετικών ατόμων. Λάβετε υπόψη ότι είναι πιο εύκολο να συγχωνεύσετε δύο άτομα παρά να χωρίσετε ένα άτομο στα δύο, οπότε προτιμήστε ένα χαμηλότερο όριο, όταν αυτό είναι δυνατό.", - "machine_learning_min_detection_score": "Ελάχιστο σκορ ανίχνευσης", - "machine_learning_min_detection_score_description": "Ελάχιστο σκορ εμπιστοσύνης για ανίχνευση προσώπου από 0-1. Οι χαμηλότερες τιμές θα εντοπίσουν περισσότερα πρόσωπα, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.", - "machine_learning_min_recognized_faces": "Ελάχιστα αναγνωρισμένα πρόσωπα", - "machine_learning_min_recognized_faces_description": "Ο ελάχιστος αριθμός αναγνωρισμένων προσώπων για ένα άτομο που θα δημιουργηθεί. Η αύξηση αυτή καθιστά την Αναγνώριση Προσώπου πιο ακριβή με το κόστος να αυξηθεί η πιθανότητα να μην εκχωρηθεί ένα πρόσωπο σε ένα άτομο.", - "machine_learning_ocr": "Οπτική Αναγνώριση Κειμένου (OCR)", - "machine_learning_ocr_description": "Χρησιμοποείστε τη μηχανική μάθηση για την αναγνώριση κειμένου σε εικόνες", - "machine_learning_ocr_enabled": "Ενεργοποίηση OCR", - "machine_learning_ocr_enabled_description": "Αν είναι απενεργοποιημένο, οι εικόνες δεν θα υποβληθούν σε αναγνώριση κειμένου.", - "machine_learning_ocr_max_resolution": "Μέγιστη ανάλυση", - "machine_learning_ocr_max_resolution_description": "Οι προεπισκοπήσεις πάνω από αυτήν την ανάλυση θα αλλάξουν μέγεθος διατηρώντας τις αναλογίες. Μεγαλύτερες τιμές είναι πιο ακριβείς, αλλά χρειάζονται περισσότερο χρόνο επεξεργασίας και μνήμη.", - "machine_learning_ocr_min_detection_score": "Ελάχιστος βαθμός ανίχνευσης", - "machine_learning_ocr_min_detection_score_description": "Ελάχιστος βαθμός εμπιστοσύνης για την ανίχνευση κειμένου από 0 έως 1. Χαμηλότερες τιμές θα ανιχνεύουν περισσότερο κείμενο, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.", - "machine_learning_ocr_min_recognition_score": "Ελάχιστος βαθμός αναγνώρισης", - "machine_learning_ocr_min_score_recognition_description": "Ελάχιστος βαθμός εμπιστοσύνης για την αναγνώριση ανιχνευμένου κειμένου από 0 έως 1. Χαμηλότερες τιμές θα αναγνωρίζουν περισσότερο κείμενο, αλλά μπορεί να οδηγήσουν σε ψευδώς θετικά αποτελέσματα.", - "machine_learning_ocr_model": "Μοντέλο OCR", - "machine_learning_ocr_model_description": "Τα μοντέλα διακομιστή είναι πιο ακριβή από τα μοντέλα των κινητών, αλλά χρειάζονται περισσότερο χρόνο επεξεργασίας και χρησιμοποιούν περισσότερη μνήμη.", - "machine_learning_settings": "Ρυθμίσεις μηχανικής μάθησης", - "machine_learning_settings_description": "Διαχειριστείτε τις λειτουργίες και τις ρυθμίσεις μηχανικής μάθησης", - "machine_learning_smart_search": "Έξυπνη Αναζήτηση", - "machine_learning_smart_search_description": "Αναζητήστε εικόνες σημασιολογικά χρησιμοποιώντας ενσωματώσεις CLIP", - "machine_learning_smart_search_enabled": "Ενεργοποίηση έξυπνης αναζήτησης", - "machine_learning_smart_search_enabled_description": "Αν απενεργοποιηθεί, οι εικόνες δεν θα κωδικοποιούνται για έξυπνη αναζήτηση.", - "machine_learning_url_description": "Η διεύθυνση URL του διακομιστή μηχανικής μάθησης. Αν δοθούν περισσότερες από μία διευθύνσεις URL, κάθε διακομιστής θα δοκιμάζεται διαδοχικά μέχρι να ανταποκριθεί ένας με επιτυχία, με τη σειρά από την πρώτη έως την τελευταία. Οι διακομιστές που δεν ανταποκρίνονται θα αγνοούνται προσωρινά μέχρι να επανέλθουν σε λειτουργία.", - "maintenance_delete_backup": "Διαγραφή αντιγράφου ασφαλείας", - "maintenance_delete_backup_description": "Αυτό το αρχείο θα διαγραφεί οριστικά και χωρίς δυνατότητα επαναφοράς.", - "maintenance_delete_error": "Αποτυχία διαγραφής του αντιγράφου ασφαλείας.", - "maintenance_restore_backup": "Επαναφορά αντιγράφου ασφαλείας", - "maintenance_restore_backup_description": "Το Immich θα διαγραφεί πλήρως και θα επαναφερθεί από το επιλεγμένο αντίγραφο ασφαλείας. Θα δημιουργηθεί αντίγραφο ασφαλείας πριν τη συνέχεια.", - "maintenance_restore_backup_different_version": "Αυτό το αντίγραφο ασφαλείας δημιουργήθηκε με διαφορετική έκδοση του Immich!", - "maintenance_restore_backup_unknown_version": "Δεν ήταν δυνατός ο προσδιορισμός της έκδοσης του αντιγράφου ασφαλείας.", - "maintenance_restore_database_backup": "Επαναφορά αντιγράφου ασφαλείας της βάσης δεδομένων", - "maintenance_restore_database_backup_description": "Επαναφορά της βάσης δεδομένων σε προηγούμενη κατάσταση χρησιμοποιώντας αρχείο αντιγράφου ασφαλείας", - "maintenance_settings": "Συντήρηση", - "maintenance_settings_description": "Θέστε το Immich σε λειτουργία συντήρησης.", - "maintenance_start": "Αλλαγή σε λειτουργία συντήρησης", - "maintenance_start_error": "Αποτυχία έναρξης λειτουργίας συντήρησης.", - "maintenance_upload_backup": "Μεταφόρτωση αρχείου αντιγράφου ασφαλείας βάσης δεδομένων", - "maintenance_upload_backup_error": "Δεν ήταν δυνατή η μεταφόρτωση του αντιγράφου ασφαλείας, είναι αρχείο .sql/.sql.gz;", - "manage_concurrency": "Διαχείριση ταυτόχρονη εκτέλεσης", - "manage_concurrency_description": "Μεταβείτε στη σελίδα εργασιών για να διαχειριστείτε την ταυτόχρονη εκτέλεση εργασιών", - "manage_log_settings": "Διαχείριση ρυθμίσεων αρχείου καταγραφής", - "map_dark_style": "Σκούρο Θέμα", - "map_enable_description": "Ενεργοποίηση λειτουργιών χάρτη", - "map_gps_settings": "Ρυθμίσεις Χάρτη & GPS", - "map_gps_settings_description": "Διαχείριση Ρυθμίσεων Χάρτη & GPS (Αντίστροφη γεωκωδικοποίηση)", - "map_implications": "Η λειτουργία χάρτη βασίζεται σε εξωτερικές υπηρεσίες για τα πλακίδια (tiles.immich.cloud)", - "map_light_style": "Φωτεινό Θέμα", - "map_manage_reverse_geocoding_settings": "Διαχείριση ρυθμίσεων Αντίστροφης Γεωκωδικοποίησης", - "map_reverse_geocoding": "Αντίστροφη Γεωκωδικοποίηση", - "map_reverse_geocoding_enable_description": "Ενεργοποίηση Αντίστροφης Γεωκωδικοποίησης", - "map_reverse_geocoding_settings": "Ρυθμίσεις Αντίστροφης Γεωκωδικοποίησης", - "map_settings": "Χάρτης", - "map_settings_description": "Διαχείριση ρυθμίσεων χάρτη", - "map_style_description": "URL προς αρχείο θέματος του χάρτη style.json", - "memory_cleanup_job": "Καθαρισμός μνήμης", - "memory_generate_job": "Δημιουργία μνήμης", - "metadata_extraction_job": "Εξαγωγή μεταδεδομένων", - "metadata_extraction_job_description": "Εξαγωγή μεταδεδομένων από κάθε αρχείο, όπως τοποθεσία, πρόσωπα και ανάλυση", - "metadata_faces_import_setting": "Ενεργοποίηση εισαγωγής προσώπων", - "metadata_faces_import_setting_description": "Εισαγωγή προσώπων από EXIF εικόνων και παρόμοια αρχεία ( sidecar files)", - "metadata_settings": "Ρυθμίσεις μεταδεδομένων", - "metadata_settings_description": "Διαχείρηση ρυθμίσεων μεταδεδομένων", - "migration_job": "Μεταφορά δεδομένων (Migration)", - "migration_job_description": "Μεταφορά των εικονιδίων για αρχεία και πρόσωπα στην πιο πρόσφατη δομή αρχείων", - "nightly_tasks_cluster_faces_setting_description": "Εκτέλεση αναγνώρισης προσώπου σε νέα ανιχνευμένα πρόσωπα", - "nightly_tasks_cluster_new_faces_setting": "Ομαδοποίηση νέων προσώπων", - "nightly_tasks_database_cleanup_setting": "Εργασίες καθαρισμού βάσης δεδομένων", - "nightly_tasks_database_cleanup_setting_description": "Εκκαθάριση παλιών και ληγμένων δεδομένων από τη βάση δεδομένων", - "nightly_tasks_generate_memories_setting": "Δημιουργία αναμνήσεων", - "nightly_tasks_generate_memories_setting_description": "Δημιουργία νέων αναμνήσεων από αντικείμενα", - "nightly_tasks_missing_thumbnails_setting": "Δημιουργία ελλειπόντων μικρογραφιών", - "nightly_tasks_missing_thumbnails_setting_description": "Τοποθέτηση στη ουρά των αρχείων χωρίς μικρογραφίες για δημιουργία μικρογραφιών", - "nightly_tasks_settings": "Ρυθμίσεις για τις νυχτερινές εργασίες", - "nightly_tasks_settings_description": "Διαχείριση νυχτερινών εργασιών", - "nightly_tasks_start_time_setting": "Ώρα έναρξης", - "nightly_tasks_start_time_setting_description": "Η ώρα κατά την οποία ο διακομιστής ξεκινάει να εκτελεί τις νυχτερινές εργασίες", - "nightly_tasks_sync_quota_usage_setting": "Συγχρονισμός χρήσης χώρου", - "nightly_tasks_sync_quota_usage_setting_description": "Ενημέρωση του διαθέσιμου χώρου χρήστη, με βάση την τρέχουσα χρήση", - "no_paths_added": "Δεν προστέθηκαν διαδρομές", - "no_pattern_added": "Δεν προστέθηκε μοτίβο", - "note_apply_storage_label_previous_assets": "Σημείωση: Για να εφαρμοστεί η Ετικέτα Αποθήκευσης σε στοιχεία που είχαν αναρτηθεί παλαιότερα, εκτέλεσε το", - "note_cannot_be_changed_later": "ΣΗΜΕΊΩΣΗ: Αυτό δεν μπορεί να τροποποιηθεί αργότερα!", - "notification_email_from_address": "Διεύθυνση αποστολέα", - "notification_email_from_address_description": "Διεύθυνση αποστολέα, πχ: \"Immich Photo Server \". Βεβαιωθείτε ότι έχετε δικαίωμα χρήσης της διεύθυνσης που χρησιμοποιείτε.", - "notification_email_host_description": "Πάροχος του email server (πχ smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Παράβλεψη των σφαλμάτων πιστοποίησης", - "notification_email_ignore_certificate_errors_description": "Παράβλεψη σφαλμάτων επικύρωσης της πιστοποίησης TLS (δεν προτείνεται)", - "notification_email_password_description": "Κωδικός για την αυθεντικοποίηση με τον server του email", - "notification_email_port_description": "Θύρα του email server (πχ 25, 465, ή 587)", - "notification_email_secure": "SMTPS (ασφαλές πρωτόκολλο αποστολής email)", - "notification_email_secure_description": "Χρήση SMTPS (SMTP over TLS)", - "notification_email_sent_test_email_button": "Αποστολή test email και αποθήκευση", - "notification_email_setting_description": "Ρυθμίσεις για την αποστολή ειδοποιήσεων μέσω email", - "notification_email_test_email": "Αποστολή test email", - "notification_email_test_email_failed": "Αποτυχία αποστολής test email, ελέγξτε τις ρυθμίσεις", - "notification_email_test_email_sent": "Ένα test email στάλθηκε στην διεύθυνση {email}. Παρακαλώ ελέγξτε τα εισερχόμενα σας.", - "notification_email_username_description": "Όνομα χρήστη για την αυθεντικοποίηση με τον server του email", - "notification_enable_email_notifications": "Ενεργοποίηση ειδοποιήσεων μέσω email", - "notification_settings": "Ρυθμίσεις ειδοποιήσεων", - "notification_settings_description": "Διαχείρηση ρυθμίσεων ειδοποιήσεων, συμπεριλαμβανομένου του email", - "oauth_auto_launch": "Αυτόματη εκκίνηση", - "oauth_auto_launch_description": "Αυτόματη εκκίνιση της υπηρεσίας OAuth με την πλοήγηση στην σελίδα σύνδεσης", - "oauth_auto_register": "Αυτόματη καταχώρηση", - "oauth_auto_register_description": "Αυτόματη καταχώρηση νέου χρήστη αφού συνδεθεί με OAuth", - "oauth_button_text": "Κείμενο κουμπιού", - "oauth_client_secret_description": "Υποχρεωτικό εαν PKCE (Proof Key for Code Exchange) δεν υποστηρίζεται από τον OAuth πάροχο", - "oauth_enable_description": "Σύνδεση με OAuth", - "oauth_mobile_redirect_uri": "URI Ανακατεύθυνσης για κινητά τηλέφωνα", - "oauth_mobile_redirect_uri_override": "Προσπέλαση URI ανακατεύθυνσης για κινητά τηλέφωνα", - "oauth_mobile_redirect_uri_override_description": "Ενεργοποιήστε το όταν ο πάροχος OAuth δεν επιτρέπει μια URI για κινητά, όπως το ''{callback}''", - "oauth_role_claim": "Ανάθεση ρόλου", - "oauth_role_claim_description": "Αυτόματη παραχώρηση πρόσβασης διαχειριστή με βάση την ύπαρξη αυτής της ανάθεσης. Η ανάθεση μπορεί να είναι είτε 'χρήστης' είτε 'διαχειριστής'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Διαχείριση ρυθμίσεων σύνδεσης OAuth", - "oauth_settings_more_details": "Για περισσότερες λεπτομέρειες σχετικά με αυτήν τη δυνατότητα, ανατρέξτε στην τεκμηρίωση.", - "oauth_storage_label_claim": "Δήλωση ετικέτας αποθήκευσης", - "oauth_storage_label_claim_description": "Ορίζει αυτόματα την ετικέτα αποθήκευσης του χρήστη στη δηλωμένη τιμή.", - "oauth_storage_quota_claim": "Δήλωση ποσοστού αποθήκευσης", - "oauth_storage_quota_claim_description": "Ορίζει αυτόματα το ποσοστό αποθήκευσης του χρήστη στη δηλωμένη τιμή.", - "oauth_storage_quota_default": "Προεπιλεγμένο όριο αποθήκευσης (GiB)", - "oauth_storage_quota_default_description": "Ποσοστό σε GiB που θα χρησιμοποιηθεί όταν δεν ορίζεται από τη δηλωμένη τιμή.", - "oauth_timeout": "Χρονικό όριο Αιτήματος", - "oauth_timeout_description": "Χρονικό όριο Αιτήματος σε milliseconds", - "ocr_job_description": "Χρήση μηχανικής μάθησης για αναγνώριση κειμένου σε εικόνες", - "password_enable_description": "Σύνδεση με ηλεκτρονικό ταχυδρομείο", - "password_settings": "Σύνδεση με κωδικό", - "password_settings_description": "Διαχείριση ρυθμίσεων σύνδεσης μέσω κωδικού πρόσβασης", - "paths_validated_successfully": "Όλες οι διαδρομές επικυρώθηκαν επιτυχώς", - "person_cleanup_job": "Καθαρισμός ατόμου", - "queue_details": "Λεπτομέρειες ουράς", - "queues": "Ουρές εργασιών", - "queues_page_description": "Σελίδα ουρών εργασιών διαχειριστή", - "quota_size_gib": "Μέγεθος ορίου (GiB)", - "refreshing_all_libraries": "Επαναφόρτωση όλων των βιβλιοθηκών", - "registration": "Εγγραφή Διαχειριστή", - "registration_description": "Δεδομένου ότι είστε ο πρώτος χρήστης στο σύστημα, θα ανατεθείτε ως Διαχειριστής και θα είστε υπεύθυνος για τις διαχειριστικές εργασίες, ενώ οι επιπλέον χρήστες θα δημιουργούνται από εσάς.", - "remove_failed_jobs": "Αφαίρεση αποτυχημένων εργασιών", - "require_password_change_on_login": "Απαιτείται από τον χρήστη να αλλάξει τον κωδικό πρόσβασης κατά την πρώτη σύνδεση", - "reset_settings_to_default": "Επαναφορά προεπιλεγμένων ρυθμίσεων", - "reset_settings_to_recent_saved": "Επαναφορά ρυθμίσεων στις πρόσφατα αποθηκευμένες ρυθμίσεις", - "scanning_library": "Σάρωση βιβλιοθήκης", - "search_jobs": "Αναζήτηση εργασιών…", - "send_welcome_email": "Αποστολή email καλωσορίσματος", - "server_external_domain_settings": "Εξωτερική διεύθυνση τομέα", - "server_external_domain_settings_description": "Διεύθυνση τομέα για δημόσιους κοινούς συνδέσμους, περιλαμβανομένου του http(s)://", - "server_public_users": "Δημόσιοι Χρήστες", - "server_public_users_description": "Όλοι οι χρήστες (όνομα και email) εμφανίζονται κατά την προσθήκη ενός χρήστη σε κοινόχρηστα άλμπουμ. Όταν αυτή η επιλογή είναι απενεργοποιημένη, η λίστα χρηστών θα είναι διαθέσιμη μόνο στους διαχειριστές.", - "server_settings": "Ρυθμίσεις διακομιστή", - "server_settings_description": "Διαχείριση ρυθμίσεων διακομιστή", - "server_stats_page_description": "Σελίδα στατιστικών διακομιστή διαχειριστή", - "server_welcome_message": "Μήνυμα καλωσορίσματος", - "server_welcome_message_description": "Το μήνυμα που θα εμφανίζεται στη σελίδα σύνδεσης.", - "settings_page_description": "Σελίδα ρυθμίσεων διαχειριστή", - "sidecar_job": "Μεταδεδομένα συνοδευτικού αρχείου", - "sidecar_job_description": "Ανακάλυψη ή συγχρονισμός των μεταδεδομένων του συνοδευτικού αρχείου από το σύστημα αρχείων", - "slideshow_duration_description": "Αριθμός δευτερολέπτων για την εμφάνιση κάθε εικόνας", - "smart_search_job_description": "Εκτέλεση της μηχανικής μάθησης, σε αρχεία, για την υποστήριξη της έξυπνης αναζήτησης", - "storage_template_date_time_description": "Η χρονική σήμανση της δημιουργίας του αρχείου, χρησιμοποιείται για τις πληροφορίες ημερομηνίας και ώρας", - "storage_template_date_time_sample": "Χρόνος δείγματος {date}", - "storage_template_enable_description": "Ενεργοποίηση του μηχανισμού των προτύπων αποθήκευσης", - "storage_template_hash_verification_enabled": "Ενεργοποιημένη επαλήθευση hash", - "storage_template_hash_verification_enabled_description": "Ενεργοποιεί την επαλήθευση hash. Μην το απενεργοποιήσεις εκτός αν είσαι βέβαιος/α για τις συνέπειες", - "storage_template_migration": "Μεταφορά προτύπων αποθήκευσης", - "storage_template_migration_description": "Εφαρμογή του τρέχοντος {template} στα αρχεία που έχουν ανέβει προηγουμένως", - "storage_template_migration_info": "Το πρότυπο αποθήκευσης θα μετατρέψει όλες τις επεκτάσεις σε πεζά γράμματα. Οι αλλαγές στο πρότυπο θα ισχύουν μόνο για νέα περιουσιακά στοιχεία. Για να εφαρμόσετε αναδρομικά το πρότυπο σε περιουσιακά στοιχεία που έχουν μεταφορτωθεί προηγουμένως, εκτελέστε το {job}.", - "storage_template_migration_job": "Εργασία Μεταφοράς Προτύπων Αποθήκευσης", - "storage_template_more_details": "Για περισσότερες λεπτομέρειες σχετικά με αυτήν τη δυνατότητα, ανατρέξτε στο Πρότυπο Αποθήκευσης και στις συνέπειές του", - "storage_template_onboarding_description_v2": "Όταν είναι ενεργοποιημένη, αυτή η λειτουργία θα οργανώνει αυτόματα τα αρχεία με βάση ένα πρότυπο που ορίζεται από το χρήστη. Για περισσότερες πληροφορίες, παρακαλώ ανατρέξτε στις οδηγίες χρήσης.", - "storage_template_path_length": "Όριο μήκους διαδρομής: {length, number}/{limit, number}, κατά προσέγγιση", - "storage_template_settings": "Πρότυπο αποθήκευσης", - "storage_template_settings_description": "Διαχείριση της δομής φακέλου και του ονόματος, του ανεβασμένου αρχείου", - "storage_template_user_label": "{label} είναι η Ετικέτα Αποθήκευσης του χρήστη", - "system_settings": "Ρυθμίσεις Συστήματος", - "tag_cleanup_job": "Καθαρισμός ετικετών", - "template_email_available_tags": "Μπορείτε να χρησιμοποιήσετε τις εξής μεταβλητές στο πρότυπό σας: {tags}", - "template_email_if_empty": "Αν το πρότυπο είναι κενό, θα χρησιμοποιηθεί το προεπιλεγμένο email.", - "template_email_invite_album": "Πρότυπο άλμπουμ πρόσκλησης", - "template_email_preview": "Προεπισκόπηση", - "template_email_settings": "Πρότυπα Email", - "template_email_update_album": "Ενημέρωση πρότυπου Άλμπουμ", - "template_email_welcome": "Πρότυπο email καλωσορίσματος", - "template_settings": "Πρότυπα ειδοποιήσεων", - "template_settings_description": "Διαχείριση προσαρμοσμένων προτύπων για ειδοποιήσεις", - "theme_custom_css_settings": "Προσαρμοσμένο CSS", - "theme_custom_css_settings_description": "Τα Cascading Style Sheets(CSS) επιτρέπει την προσαρμογή του σχεδιασμού του Immich.", - "theme_settings": "Ρυθμίσεις θέματος", - "theme_settings_description": "Διαχείριση της προσαρμογής του ιστότοπου του Immich", - "thumbnail_generation_job": "Δημιουργία Μικρογραφιών", - "thumbnail_generation_job_description": "Δημιουργία μεγάλων, μικρών και θολών μικρογραφιών για κάθε αρχείο, καθώς και μικρογραφιών για κάθε άτομο", - "transcoding_acceleration_api": "Επιτάχυνση API", - "transcoding_acceleration_api_description": "Το API που θα αλληλεπιδράσει με τη συσκευή σας για να επιταχύνει τη διαδικασία μετατροπής των δεδομένων. Αυτή η ρύθμιση είναι \"κατά το καλύτερο δυνατόν\": σε περίπτωση αποτυχίας, θα επιστραφεί στη μετατροπή δεομένων μέσω λογισμικού. Το VP9 ενδέχεται να λειτουργεί ή να μην λειτουργεί, ανάλογα με το υλικό σας.", - "transcoding_acceleration_nvenc": "NVENC (απαιτεί NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (απαιτεί επεξεργαστή Intel 7ης γενιάς ή νεότερο)", - "transcoding_acceleration_rkmpp": "RKMPP (μόνο σε Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Αποδεκτοί κωδικοποιητές ήχου", - "transcoding_accepted_audio_codecs_description": "Επιλέξτε ποιοι κωδικοποιητές ήχου δεν χρειάζεται να μετατραπούν. Χρησιμοποιείται μόνο για ορισμένες πολιτικές μετατροπής.", - "transcoding_accepted_containers": "Αποδεκτά κοντέινερ", - "transcoding_accepted_containers_description": "Επιλέξτε ποιοι τύποι κοντέινερ δεν χρειάζονται ανασυγκρότηση σε MP4. Χρησιμοποιείται μόνο για ορισμένες πολιτικές μετατροπής.", - "transcoding_accepted_video_codecs": "Αποδεκτοί κωδικοποιητές βίντεο", - "transcoding_accepted_video_codecs_description": "Επιλέξτε ποιοι κωδικοποιητές βίντεο δεν χρειάζεται να μετατραπούν. Χρησιμοποιείται μόνο για ορισμένες πολιτικές μετατροπής.", - "transcoding_advanced_options_description": "Επιλογές που οι περισσότεροι χρήστες δεν χρειάζεται να αλλάξουν", - "transcoding_audio_codec": "Κωδικοποιητής ήχου", - "transcoding_audio_codec_description": "Το Opus είναι η επιλογή για την υψηλότερη ποιότητα, αλλά έχει χαμηλότερη συμβατότητα με παλιές συσκευές ή λογισμικό.", - "transcoding_bitrate_description": "Βίντεο με ρυθμό μετάδοσης μεγαλύτερο από το μέγιστο ή που δεν είναι σε αποδεκτή μορφή", - "transcoding_codecs_learn_more": "Για να μάθετε περισσότερα για την ορολογία που χρησιμοποιείται εδώ, ανατρέξτε στην τεκμηρίωση του FFmpeg για τους κωδικοποιητές H.264, HEVC και VP9.", - "transcoding_constant_quality_mode": "Λειτουργία σταθερής ποιότητας", - "transcoding_constant_quality_mode_description": "Το ICQ είναι καλύτερο από το CQP, αλλά ορισμένες συσκευές επιτάχυνσης υλικού δεν υποστηρίζουν αυτήν τη λειτουργία. Η ρύθμιση αυτής της επιλογής θα προτιμήσει την καθορισμένη λειτουργία κατά τη χρήση κωδικοποίησης βάσει ποιότητας. Αγνοείται από το NVENC, καθώς δεν υποστηρίζει το ICQ.", - "transcoding_constant_rate_factor": "Σταθερός παράγοντας ρυθμού (-crf)", - "transcoding_constant_rate_factor_description": "Επίπεδο ποιότητας βίντεο. Οι τυπικές τιμές είναι οι, 23 για το H.264, 28 για το HEVC, 31 για το VP9 και 35 για το AV1. Χαμηλότερες τιμές σημαίνουν καλύτερη ποιότητα, αλλά παράγουν μεγαλύτερα αρχεία.", - "transcoding_disabled_description": "Να μην μετατραπεί κανένα βίντεο γιατί δύναται να προκαλέσει πρόβλημα αναπαραγωγής σε ορισμένες συσκευές/εφαρμογές", - "transcoding_encoding_options": "Επιλογές κωδικοποίησης", - "transcoding_encoding_options_description": "Ορίστε τους κωδικοποιητές, την ανάλυση, την ποιότητα και άλλες επιλογές για τα κωδικοποιημένα βίντεο", - "transcoding_hardware_acceleration": "Επιτάχυνση υλικού", - "transcoding_hardware_acceleration_description": "Πειραματικό: γρήγορη κωδικοποίηση αλλά ενδέχεται να μειωθεί η ποιότητα στον ίδιο ρυθμό μετάδοσης (bitrate)", - "transcoding_hardware_decoding": "Αποκωδικοποίηση μέσω υλικού", - "transcoding_hardware_decoding_setting_description": "Ενεργοποιεί την επιτάχυνση από άκρη σε άκρη αντί για μόνο επιτάχυνση της κωδικοποίησης. Μπορεί να μην λειτουργεί σε όλα τα βίντεο.", - "transcoding_max_b_frames": "Μέγιστος αριθμός B-frames(Bidirectional Predictive Frames)", - "transcoding_max_b_frames_description": "Οι υψηλότερες τιμές βελτιώνουν την αποδοτικότητα της συμπίεσης, αλλά επιβραδύνουν την κωδικοποίηση. Ενδέχεται να μην είναι συμβατές με την επιτάχυνση υλικού σε παλαιότερες συσκευές. Η τιμή 0 απενεργοποιεί τα B-frames, ενώ η -1, τη ρυθμίζει αυτόματα.", - "transcoding_max_bitrate": "Μέγιστος ρυθμός μετάδοσης (bitrate)", - "transcoding_max_bitrate_description": "Ο καθορισμός του μέγιστου bitrate μπορεί να κάνει το μέγεθος των αρχείων πιο προβλέψιμο, με ένα μικρό κόστος στην ποιότητα. Στα 720p, οι τυπικές τιμές είναι 2600 kbit/s για VP9 ή HEVC, ή 4500 kbit/s για H.264. Αν οριστεί σε 0, η ρύθμιση απενεργοποιείται. Όταν δεν καθορίζεται, θεωρείται το k (για kbit/s)· επομένως τα 5000, 5000k και 5M (για Mbit/s) είναι ισοδύναμα.", - "transcoding_max_keyframe_interval": "Μέγιστο χρονικό διάστημα μεταξύ των καρέ αναφοράς (keyframe)", - "transcoding_max_keyframe_interval_description": "Ορίζει το μέγιστο διάστημα μεταξύ των καρέ αναφοράς. Χαμηλότερες τιμές μειώνουν την αποδοτικότητα συμπίεσης, αλλά βελτιώνουν τον χρόνο αναζήτησης και μπορεί να βελτιώσουν την ποιότητα σε σκηνές με γρήγορη κίνηση. Η τιμή 0 ρυθμίζει αυτό το διάστημα αυτόματα.", - "transcoding_optimal_description": "Βίντεο με ανώτερη ανάλυση από την επιθυμητή ή σε μη αποδεκτή μορφή", - "transcoding_policy": "Πολιτική Μετακωδικοποίησης", - "transcoding_policy_description": "Ορίστε πότε θα γίνει η μετακωδικοποίηση ενός βίντεο", - "transcoding_preferred_hardware_device": "Προτιμώμενη συσκευή", - "transcoding_preferred_hardware_device_description": "Ισχύει μόνο για VAAPI και QSV. Ορίζει τον κόμβο DRI που χρησιμοποιείται για την επιτάχυνση υλικού κατά την κωδικοποίηση.", - "transcoding_preset_preset": "Προκαθορισμένη ρύθμιση (-preset)", - "transcoding_preset_preset_description": "Ταχύτητα συμπίεσης. Οι πιο αργές προκαθορισμένες ρυθμίσεις παράγουν μικρότερα αρχεία και βελτιώνουν την ποιότητα όταν στοχεύουν σε έναν συγκεκριμένο ρυθμό μετάδοσης (bitrate). Ο VP9 αγνοεί τις ταχύτητες πάνω από την 'πιο γρήγορη' (faster).", - "transcoding_reference_frames": "Καρέ αναφοράς", - "transcoding_reference_frames_description": "Ο αριθμός των καρέ που χρησιμοποιούνται ως αναφορά κατά τη συμπίεση ενός δεδομένου καρέ. Υψηλότερες τιμές βελτιώνουν την αποδοτικότητα της συμπίεσης, αλλά επιβραδύνουν την κωδικοποίηση. Η τιμή 0 ρυθμίζει αυτό τον αριθμό, αυτόματα.", - "transcoding_required_description": "Μόνο βίντεο που δεν είναι σε αποδεκτή μορφή", - "transcoding_settings": "Ρυθμίσεις μετατροπής βίντεο", - "transcoding_settings_description": "Διαχείριση των βίντεο που θα μετακωδικοποιηθούν και του τρόπου επεξεργασίας τους", - "transcoding_target_resolution": "Επιθυμητή ανάλυση", - "transcoding_target_resolution_description": "Οι υψηλότερες αναλύσεις μπορούν να διατηρήσουν περισσότερες λεπτομέρειες, αλλά απαιτούν περισσότερο χρόνο για κωδικοποίηση, παράγουν μεγαλύτερα αρχεία και μπορεί να μειώσουν την απόκριση της εφαρμογής.", - "transcoding_temporal_aq": "Χρονική Προσαρμοστική Ποιότητα AQ(Adaptive Quantization)", - "transcoding_temporal_aq_description": "Ισχύει μόνο για το NVENC. Η Χρονική προσαρμογή ποιότητας (Temporal Adaptive Quantization) βελτιώνει την ποιότητα σε σκηνές με υψηλή λεπτομέρεια και χαμηλή κίνηση. Ενδέχεται να μην είναι συμβατή με παλαιότερες συσκευές.", - "transcoding_threads": "Νήματα (παράλληλες διεργασίες)", - "transcoding_threads_description": "Οι υψηλότερες τιμές οδηγούν σε ταχύτερη κωδικοποίηση, αλλά αφήνουν λιγότερο χώρο στον διακομιστή για να επεξεργαστεί άλλες εργασίες όσο είναι ενεργή. Αυτή η τιμή δεν πρέπει να ξεπερνά τον αριθμό των πυρήνων του επεξεργαστή. Η μέγιστη αξιοποίηση επιτυγχάνεται αν οριστεί στο 0.", - "transcoding_tone_mapping": "Χαρτογράφηση χρωματικών τόνων", - "transcoding_tone_mapping_description": "Προσπαθεί να διατηρήσει την εμφάνιση των HDR βίντεο όταν μετατρέπονται σε SDR. Κάθε αλγόριθμος κάνει διαφορετικές επιλογές σχετικά με τα χρώματα, τις λεπτομέρειες και τη φωτεινότητα. Ο αλγόριθμος Hable διατηρεί τις λεπτομέρειες, ο Mobius διατηρεί τα χρώματα και ο Reinhard διατηρεί τη φωτεινότητα.", - "transcoding_transcode_policy": "Πολιτική μετατροπής (βίντεο / ήχου)", - "transcoding_transcode_policy_description": "Πολιτική για το πότε πρέπει να μετατραπεί ένα βίντεο. Τα βίντεο HDR θα μετατρέπονται πάντα (εκτός αν η μετατροπή είναι απενεργοποιημένη).", - "transcoding_two_pass_encoding": "Κωδικοποίηση δύο περασμάτων", - "transcoding_two_pass_encoding_setting_description": "Μετατροπή σε δύο περάσματα για την παραγωγή βίντεο με καλύτερη κωδικοποίηση. Όταν είναι ενεργοποιημένος ο μέγιστος ρυθμός μετάδοσης (απαραίτητος για λειτουργία με H.264 και HEVC), αυτή η λειτουργία χρησιμοποιεί ένα εύρος ρυθμού μετάδοσης βάσει του μέγιστου ρυθμού μετάδοσης και αγνοεί το CRF. Στον κωδικοποιητή VP9, το CRF μπορεί να χρησιμοποιηθεί εάν ο μέγιστος ρυθμός μετάδοσης είναι απενεργοποιημένος.", - "transcoding_video_codec": "Κωδικοποιητής βίντεο", - "transcoding_video_codec_description": "Ο VP9 έχει υψηλή απόδοση και συμβατότητα με τον ιστότοπο, αλλά απαιτεί περισσότερο χρόνο για μετατροπή. Ο HEVC έχει παρόμοια απόδοση, αλλά χαμηλότερη συμβατότητα με τον ιστότοπο. Ο H.264 είναι ευρέως συμβατός και γρήγορος στη μετατροπή, αλλά παράγει πολύ μεγαλύτερα αρχεία. Ο AV1 είναι ο πιο αποδοτικός κωδικοποιητής, αλλά δεν υποστηρίζεται σε παλαιότερες συσκευές.", - "trash_enabled_description": "Ενεργοποίηση λειτουργιών Κάδου Απορριμμάτων", - "trash_number_of_days": "Αριθμός ημερών", - "trash_number_of_days_description": "Αριθμός ημερών παραμονής των αρχείων στον κάδο, πριν από την οριστική διαγραφή τους", - "trash_settings": "Ρυθμίσεις κάδου απορριμμάτων", - "trash_settings_description": "Διαχείριση ρυθίσεων κάδου απορριμμάτων", - "unlink_all_oauth_accounts": "Αποσύνδεση όλων των λογαριασμών OAuth", - "unlink_all_oauth_accounts_description": "Μην ξεχάσετε να αποσυνδέσετε όλους τους λογαριασμούς OAuth πριν μεταβείτε σε νέο πάροχο.", - "unlink_all_oauth_accounts_prompt": "Είστε σίγουροι ότι θέλετε να αποσυνδέσετε όλους τους λογαριασμούς OAuth; Αυτό θα επαναφέρει το OAuth ID για κάθε χρήστη και δεν μπορεί να αναιρεθεί.", - "user_cleanup_job": "Εκκαθάριση χρηστών", - "user_delete_delay": "Ο λογαριασμός και τα αρχεία του/της {user} θα προγραμματιστούν για οριστική διαγραφή σε {delay, plural, one {# ημέρα} other {# ημέρες}}.", - "user_delete_delay_settings": "Καθυστέρηση διαγραφής", - "user_delete_delay_settings_description": "Αριθμός ημερών μετά την αφαίρεση, για την οριστική διαγραφή του λογαριασμού και των αρχείων ενός χρήστη. Η εργασία διαγραφής χρηστών εκτελείται τα μεσάνυχτα, για να ελέγξει ποιοι χρήστες είναι έτοιμοι για διαγραφή. Οι αλλαγές σε αυτή τη ρύθμιση θα αξιολογηθούν κατά την επόμενη εκτέλεση.", - "user_delete_immediately": "Ο λογαριασμός και τα αρχεία του/της {user} θα μπουν στην ουρά για οριστική διαγραφή, άμεσα.", - "user_delete_immediately_checkbox": "Βάλε τον χρήστη και τα αρχεία του στην ουρά για άμεση διαγραφή", - "user_details": "Λεπτομέρειες χρήστη", - "user_management": "Διαχείριση χρηστών", - "user_password_has_been_reset": "Ο κωδικός πρόσβασης του χρήστη έχει επαναρυθμιστεί:", - "user_password_reset_description": "Παρακαλώ παρέχετε τον προσωρινό κωδικό πρόσβασης στον χρήστη και ενημερώστε τον ότι θα πρέπει να τον αλλάξει, κατά την επόμενη σύνδεσή του.", - "user_restore_description": "Ο λογαριασμός του/της {user} θα αποκατασταθεί.", - "user_restore_scheduled_removal": "Αποκατάσταση χρήστη - προγραμματισμένη διαγραφή στις {date, date, long}", - "user_settings": "Ρυθμίσεις χρήστη", - "user_settings_description": "Διαχείριση ρυθμίσεων χρήστη", - "user_successfully_removed": "Ο χρήστης {email} αφαιρέθηκε με επιτυχία.", - "users_page_description": "Σελίδα χρηστών διαχειριστή", - "version_check_enabled_description": "Ενεργοποίηση ελέγχου έκδοσης", - "version_check_implications": "Η λειτουργία ελέγχου έκδοσης, εξαρτάται από την περιοδική επικοινωνία με το github.com", - "version_check_settings": "Έλεγχος εκδοσης", - "version_check_settings_description": "Ενεργοποίηση/απενεργοποίηση της ειδοποίησης για νέα έκδοση", - "video_conversion_job": "Μετατροπή βίντεο", - "video_conversion_job_description": "Μετατροπή βίντεο για μεγαλύτερη συμβατότητα με προγράμματα περιήγησης και συσκευές" - }, - "admin_email": "Email Διαχειριστή", - "admin_password": "Κωδικός πρόσβασης Διαχειριστή", - "administration": "Διαχείριση", - "advanced": "Για προχωρημένους", - "advanced_settings_enable_alternate_media_filter_subtitle": "Χρησιμοποιήστε αυτήν την επιλογή για να φιλτράρετε τα μέσα ενημέρωσης κατά τον συγχρονισμό με βάση εναλλακτικά κριτήρια. Δοκιμάστε αυτή τη δυνατότητα μόνο αν έχετε προβλήματα με την εφαρμογή που εντοπίζει όλα τα άλμπουμ.", - "advanced_settings_enable_alternate_media_filter_title": "[ΠΕΙΡΑΜΑΤΙΚΟ] Χρήση εναλλακτικού φίλτρου συγχρονισμού άλμπουμ συσκευής", - "advanced_settings_log_level_title": "Επίπεδο σύνδεσης: {level}", - "advanced_settings_prefer_remote_subtitle": "Μερικές συσκευές αργούν πολύ να φορτώσουν μικρογραφίες από τοπικά αρχεία. Ενεργοποιήστε αυτήν τη ρύθμιση για να φορτώνονται αντί αυτού απομακρυσμένες εικόνες.", - "advanced_settings_prefer_remote_title": "Προτίμηση απομακρυσμένων εικόνων", - "advanced_settings_proxy_headers_subtitle": "Καθορισμός κεφαλίδων διακομιστή μεσολάβησης που το Immich πρέπει να στέλνει με κάθε αίτημα δικτύου", - "advanced_settings_proxy_headers_title": "Προσαρμοσμένοι proxy headers [ΠΕΙΡΑΜΑΤΙΚΟ]", - "advanced_settings_readonly_mode_subtitle": "Ενεργοποιεί τη λειτουργία μόνο-για-ανάγνωση, όπου οι φωτογραφίες μπορούν μόνο να προβληθούν. Ενέργειες όπως επιλογή πολλών εικόνων, κοινή χρήση, αποστολή (casting) και διαγραφή είναι απενεργοποιημένες. Η ενεργοποίηση/απενεργοποίηση της λειτουργίας μόνο-για-ανάγνωση γίνεται μέσω της εικόνας του χρήστη από την κεντρική οθόνη", - "advanced_settings_readonly_mode_title": "Μόνο-για-ανάγνωση", - "advanced_settings_self_signed_ssl_subtitle": "Παρακάμπτει τον έλεγχο πιστοποιητικού SSL του διακομιστή. Απαραίτητο για αυτο-υπογεγραμμένα πιστοποιητικά.", - "advanced_settings_self_signed_ssl_title": "Να επιτρέπονται αυτο-υπογεγραμμένα πιστοποιητικά SSL [ΠΕΙΡΑΜΑΤΙΚΟ]", - "advanced_settings_sync_remote_deletions_subtitle": "Αυτόματη διαγραφή ή επαναφορά ενός περιουσιακού στοιχείου σε αυτή τη συσκευή, όταν η ενέργεια αυτή πραγματοποιείται στο διαδίκτυο", - "advanced_settings_sync_remote_deletions_title": "Συγχρονισμός απομακρυσμένων διαγραφών [ΠΕΙΡΑΜΑΤΙΚΟ]", - "advanced_settings_tile_subtitle": "Ρυθμίσεις προχωρημένου χρήστη", - "advanced_settings_troubleshooting_subtitle": "Ενεργοποίηση πρόσθετων χαρακτηριστικών για αντιμετώπιση προβλημάτων", - "advanced_settings_troubleshooting_title": "Αντιμετώπιση προβλημάτων", - "age_months": "Η ηλικία {months, plural, one {# μήνας} other {# μήνες}}", - "age_year_months": "Ηλικία, 1 έτος, {months, plural, one {# μήνας} other {# μήνες}}", - "age_years": "{years, plural, other {Ηλικία #}}", - "album": "Λεύκωμα", - "album_added": "Το άλμπουμ, προστέθηκε", - "album_added_notification_setting_description": "Λάβετε ειδοποίηση μέσω email όταν προστεθείτε σε ένα κοινόχρηστο άλμπουμ", - "album_cover_updated": "Το εξώφυλλο του άλμπουμ, ενημερώθηκε", - "album_delete_confirmation": "Είστε σίγουροι ότι θέλετε να διαγράψετε το άλμπουμ {album};", - "album_delete_confirmation_description": "Εάν αυτό το άλμπουμ είναι κοινόχρηστο, οι άλλοι χρήστες δεν θα μπορούν να έχουν πρόσβαση.", - "album_deleted": "Το άλμπουμ διαγράφηκε", - "album_info_card_backup_album_excluded": "ΕΞΑΙΡΟΥΜΕΝΟ", - "album_info_card_backup_album_included": "ΣΥΜΠΕΡΙΛΑΜΒΑΝΟΜΕΝΟ", - "album_info_updated": "Οι πληροφορίες του άλμπουμ, ενημερώθηκαν", - "album_leave": "Θέλετε να αποχωρήσετε από το άλμπουμ;", - "album_leave_confirmation": "Είστε σίγουροι ότι θέλετε να αποχωρήσετε από το {album};", - "album_name": "Ονομασία άλμπουμ", - "album_options": "Επιλογές άλμπουμ", - "album_remove_user": "Διαγραφή χρήστη;", - "album_remove_user_confirmation": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε τον/την {user};", - "album_search_not_found": "Δε βρέθηκαν άλμπουμ που να ταιριάζουν με την αναζήτησή σας", - "album_selected": "Άλμπουμ επιλεγμένο", - "album_share_no_users": "Φαίνεται ότι έχετε κοινοποιήσει αυτό το άλμπουμ σε όλους τους χρήστες ή δεν έχετε χρήστες για να το κοινοποιήσετε.", - "album_summary": "Περίληψη άλμπουμ", - "album_updated": "Το άλμπουμ, ενημερώθηκε", - "album_updated_setting_description": "Λάβετε ειδοποίηση μέσω email όταν ένα κοινόχρηστο άλμπουμ έχει νέα αρχεία", - "album_upload_assets": "Μεταφόρτωση στοιχείων από τον υπολογιστή σας και προσθήκη στο άλμπουμ", - "album_user_left": "Αποχωρήσατε από το {album}", - "album_user_removed": "Αφαιρέθηκε ο/η {user}", - "album_viewer_appbar_delete_confirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το άλμπουμ από τον λογαριασμό σας;", - "album_viewer_appbar_share_err_delete": "Αποτυχία διαγραφής άλμπουμ", - "album_viewer_appbar_share_err_leave": "Αποτυχία αποχώρησης από άλμπουμ", - "album_viewer_appbar_share_err_remove": "Υπάρχουν προβλήματα στην αφαίρεση στοιχείων από το άλμπουμ", - "album_viewer_appbar_share_err_title": "Αποτυχία αλλαγής τίτλου άλμπουμ", - "album_viewer_appbar_share_leave": "Αποχώρηση από άλμπουμ", - "album_viewer_appbar_share_to": "Κοινοποίηση σε", - "album_viewer_page_share_add_users": "Προσθήκη χρηστών", - "album_with_link_access": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο, να δει τις φωτογραφίες και τα άτομα σε αυτό το άλμπουμ.", - "albums": "Άλμπουμ", - "albums_count": "{count, plural, one {{count, number} Άλμπουμ} other {{count, number} Άλμπουμ}}", - "albums_default_sort_order": "Προεπιλεγμένη ταξινόμηση άλμπουμ", - "albums_default_sort_order_description": "Αρχική ταξινόμηση κατά τη δημιουργία νέων άλμπουμ.", - "albums_feature_description": "Συλλογές στοιχείων που μπορούν να κοινοποιηθούν σε άλλους χρήστες.", - "albums_on_device_count": "Άλμπουμ στη συσκευή ({count})", - "albums_selected": "{count, plural, one {# άλμπουμ επιλέχθηκε} other {# άλμπουμ επιλέχθηκαν}}", - "all": "Όλα", - "all_albums": "Όλα τα άλμπουμ", - "all_people": "Όλα τα άτομα", - "all_videos": "Όλα τα βίντεο", - "allow_dark_mode": "Επιτρέψτε τη σκοτεινή λειτουργία", - "allow_edits": "Επιτρέψτε τις τροποποιήσεις", - "allow_public_user_to_download": "Επιτρέψτε σε δημόσιο χρήστη να κατεβάσει", - "allow_public_user_to_upload": "Επιτρέψτε στον δημόσιο χρήστη να ανεβάσει", - "allowed": "Επιτρεπόμενο", - "alt_text_qr_code": "Εικόνα κωδικού QR", - "anti_clockwise": "Αντίθετα με τη φορά του ρολογιού", - "api_key": "Κλειδί API", - "api_key_description": "Αυτή η τιμή θα εμφανιστεί μόνο μία φορά. Παρακαλώ βεβαιωθείτε ότι την έχετε αντιγράψει πριν κλείσετε το παράθυρο.", - "api_key_empty": "Το όνομα του κλειδιού API, δεν πρέπει να είναι κενό", - "api_keys": "Κλειδιά API", - "app_architecture_variant": "Παραλλαγή (Αρχιτεκτονική)", - "app_bar_signout_dialog_content": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε;", - "app_bar_signout_dialog_ok": "Ναι", - "app_bar_signout_dialog_title": "Αποσύνδεση", - "app_download_links": "Σύνδεσμοι Λήψης Εφαρμογής", - "app_settings": "Ρυθμίσεις εφαρμογής", - "app_stores": "Καταστήματα εφαρμογών", - "app_update_available": "Υπάρχει διαθέσιμη ενημέρωση εφαρμογής", - "appears_in": "Εμφανίζεται σε", - "apply_count": "Εφαρμογή ({count, number})", - "archive": "Αρχείο", - "archive_action_prompt": "Προστέθηκαν {count} στο Αρχείο", - "archive_or_unarchive_photo": "Αρχειοθέτηση ή αποαρχειοθέτηση φωτογραφίας", - "archive_page_no_archived_assets": "Δε βρέθηκαν αρχειοθετημένα στοιχεία", - "archive_page_title": "Αρχείο ({count})", - "archive_size": "Μέγεθος Αρχείου", - "archive_size_description": "Ρυθμίστε το μέγεθος του αρχείου για λήψεις (σε GiB)", - "archived": "Αρχείο", - "archived_count": "{count, plural, other {Αρχειοθετήθηκαν #}}", - "are_these_the_same_person": "Είναι το ίδιο άτομο;", - "are_you_sure_to_do_this": "Είστε σίγουροι ότι θέλετε να το κάνετε αυτό;", - "array_field_not_fully_supported": "Τα πεδία πίνακα απαιτούν χειροκίνητη επεξεργασία JSON", - "asset_action_delete_err_read_only": "Δεν είναι δυνατή η διαγραφή στοιχείων μόνο για ανάγνωση, παραλείπεται", - "asset_action_share_err_offline": "Δεν είναι δυνατή η ανάκτηση στοιχείων εκτός σύνδεσης, παραλείπεται", - "asset_added_to_album": "Προστέθηκε στο άλμπουμ", - "asset_adding_to_album": "Προστίθεται στο άλμπουμ…", - "asset_created": "Το στοιχείο δημιουργήθηκε", - "asset_description_updated": "Η περιγραφή του αντικειμένου έχει ενημερωθεί", - "asset_filename_is_offline": "Το αντικείμενο {filename} είναι εκτός σύνδεσης", - "asset_has_unassigned_faces": "Το αντικείμενο έχει μη ανατεθειμένα πρόσωπα", - "asset_hashing": "Δημιουργία κατακερματισμού…", - "asset_list_group_by_sub_title": "Ομαδοποίηση κατά", - "asset_list_layout_settings_dynamic_layout_title": "Δυναμική διάταξη", - "asset_list_layout_settings_group_automatically": "Αυτόματα", - "asset_list_layout_settings_group_by": "Ομαδοποίηση στοιχείων ανά", - "asset_list_layout_settings_group_by_month_day": "Μήνας + ημέρα", - "asset_list_layout_sub_title": "Διάταξη", - "asset_list_settings_subtitle": "Ρυθμίσεις διάταξης πλέγματος φωτογραφιών", - "asset_list_settings_title": "Πλέγμα φωτογραφιών", - "asset_offline": "Αντικείμενο εκτός σύνδεσης", - "asset_offline_description": "Αυτό το εξωτερικό αντικείμενο δεν βρέθηκε πλέον στον δίσκο. Παρακαλώ επικοινωνήστε με τον διαχειριστή του Immich για βοήθεια.", - "asset_restored_successfully": "Το στοιχείο αποκαταστάθηκε με επιτυχία", - "asset_skipped": "Παραλείφθηκε", - "asset_skipped_in_trash": "Στον κάδο απορριμμάτων", - "asset_trashed": "Το στοιχείο διαγράφηκε", - "asset_troubleshoot": "Αντιμετώπιση προβλήματος στοιχείου", - "asset_uploaded": "Ανεβάστηκε", - "asset_uploading": "Ανεβάζεται…", - "asset_viewer_settings_subtitle": "Διαχείριση ρυθμίσεων προβολής συλλογής", - "asset_viewer_settings_title": "Προβολή Στοιχείων", - "assets": "Αντικείμενα", - "assets_added_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}}", - "assets_added_to_album_count": "Προστέθηκε {count, plural, one {# αρχείο} other {# αρχεία}} στο άλμπουμ", - "assets_added_to_albums_count": "Προστέθηκαν {assetTotal, plural, one {# αρχείο} other {# αρχεία}} σε {albumTotal, plural, one {# άλμπουμ} other {# άλμπουμ}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Στοιχείο} other {Στοιχεία}} δεν μπορούν να προστεθούν στο άλμπουμ", - "assets_cannot_be_added_to_albums": "Δεν μπορεί να προστεθεί κανένα {count, plural, one {στοιχείο} other {στοιχεία}} σε κανένα από τα άλμπουμ", - "assets_count": "{count, plural, one {# αρχείο} other {# αρχεία}}", - "assets_deleted_permanently": "{count} τα στοιχεία διαγράφηκαν οριστικά", - "assets_deleted_permanently_from_server": "{count} στοιχεία διαγράφηκαν οριστικά από το διακομιστή Immich", - "assets_downloaded_failed": "{count, plural, one {Έγινε λήψη # αρχείου - {error} αρχείο απέτυχε} other {Έγινε λήψη # αρχείων - {error} αρχεία απέτυχαν}}", - "assets_downloaded_successfully": "{count, plural, one {Έγινε λήψη # αρχείου επιτυχώς} other {Έγινε λήψη # αρχείων επιτυχώς}}", - "assets_moved_to_trash_count": "Μετακινήθηκαν {count, plural, one {# αρχείο} other {# αρχεία}} στον κάδο απορριμμάτων", - "assets_permanently_deleted_count": "Διαγράφηκαν μόνιμα {count, plural, one {# αρχείο} other {# αρχεία}}", - "assets_removed_count": "Αφαιρέθηκαν {count, plural, one {# αρχείο} other {# αρχεία}}", - "assets_removed_permanently_from_device": "{count} στοιχεία καταργήθηκαν οριστικά από τη συσκευή σας", - "assets_restore_confirmation": "Είστε βέβαιοι ότι θέλετε να επαναφέρετε όλα τα στοιχεία που βρίσκονται στον κάδο απορριμμάτων; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί! Λάβετε υπόψη ότι δεν θα είναι δυνατή η επαναφορά στοιχείων εκτός σύνδεσης.", - "assets_restored_count": "Έγινε επαναφορά {count, plural, one {# στοιχείου} other {# στοιχείων}}", - "assets_restored_successfully": "{count} στοιχεία αποκαταστάθηκαν με επιτυχία", - "assets_trashed": "{count} στοιχεία μεταφέρθηκαν στον κάδο απορριμμάτων", - "assets_trashed_count": "Μετακιν. στον κάδο απορριμάτων {count, plural, one {# στοιχείο} other {# στοιχεία}}", - "assets_trashed_from_server": "{count} στοιχεία μεταφέρθηκαν στον κάδο απορριμμάτων από το διακομιστή Immich", - "assets_were_part_of_album_count": "{count, plural, one {Το στοιχείο ανήκει} other {Τα στοιχεία ανήκουν}} ήδη στο άλμπουμ", - "assets_were_part_of_albums_count": "Το/α {count, plural, one {στοιχείο ήταν} other {στοιχεία ήταν}} ήδη μέρος των άλμπουμ", - "authorized_devices": "Εξουσιοδοτημένες Συσκευές", - "automatic_endpoint_switching_subtitle": "Σύνδεση τοπικά μέσω του καθορισμένου Wi-Fi όταν είναι διαθέσιμο και χρήση εναλλακτικών συνδέσεων αλλού", - "automatic_endpoint_switching_title": "Αυτόματη εναλλαγή URL", - "autoplay_slideshow": "Αυτόματη αναπαραγωγή παρουσίασης", - "back": "Πίσω", - "back_close_deselect": "Πίσω, κλείσιμο ή αποεπιλογή", - "background_backup_running_error": "Η δημιουργία αντιγράφων ασφάλειας στο παρασκήνιο εκτελείται ήδη, δεν μπορεί να ξεκινήσετε χειροκίνητο αντίγραφο ασφάλειας", - "background_location_permission": "Άδεια τοποθεσίας στο παρασκήνιο", - "background_location_permission_content": "Το Immich για να μπορεί να αλλάζει δίκτυα όταν τρέχει στο παρασκήνιο, πρέπει *πάντα* να έχει πρόσβαση στην ακριβή τοποθεσία ώστε η εφαρμογή να μπορεί να διαβάζει το όνομα του δικτύου Wi-Fi", - "background_options": "Επιλογές παρασκηνίου", - "backup": "Αντίγραφο ασφαλείας", - "backup_album_selection_page_albums_device": "Άλμπουμ στη συσκευή ({count})", - "backup_album_selection_page_albums_tap": "Πάτημα για συμπερίληψη, διπλό πάτημα για εξαίρεση", - "backup_album_selection_page_assets_scatter": "Τα στοιχεία μπορεί να διασκορπιστούν σε πολλά άλμπουμ. Έτσι, τα άλμπουμ μπορούν να περιληφθούν ή να εξαιρεθούν κατά τη διαδικασία δημιουργίας αντιγράφων ασφαλείας.", - "backup_album_selection_page_select_albums": "Επιλογή άλμπουμ", - "backup_album_selection_page_selection_info": "Πληροφορίες επιλογής", - "backup_album_selection_page_total_assets": "Συνολικά μοναδικά στοιχεία", - "backup_albums_sync": "Συγχρονισμός Αντιγράφων Ασφαλείας Άλμπουμ", - "backup_all": "Όλα", - "backup_background_service_backup_failed_message": "Αποτυχία δημιουργίας αντιγράφων ασφαλείας. Επανάληψη…", - "backup_background_service_complete_notification": "Ολοκλήρωση αντιγράφου ασφαλείας στοιχείων", - "backup_background_service_connection_failed_message": "Αποτυχία σύνδεσης με το διακομιστή. Επανάληψη…", - "backup_background_service_current_upload_notification": "Μεταφόρτωση {filename}", - "backup_background_service_default_notification": "Έλεγχος για νέα στοιχεία…", - "backup_background_service_error_title": "Σφάλμα δημιουργίας αντιγράφων ασφαλείας", - "backup_background_service_in_progress_notification": "Δημιουργία αντιγράφων ασφαλείας των στοιχείων σας…", - "backup_background_service_upload_failure_notification": "Αποτυχία μεταφόρτωσης {filename}", - "backup_controller_page_albums": "Δημιουργία αντιγράφων ασφαλείας άλμπουμ", - "backup_controller_page_background_app_refresh_disabled_content": "Ενεργοποιήστε την ανανέωση εφαρμογής στο παρασκήνιο στις Ρυθμίσεις > Γενικά > Ανανέωση Εφαρμογής στο Παρασκήνιο για να χρησιμοποιήσετε την δημιουργία αντιγράφων ασφαλείας στο παρασκήνιο.", - "backup_controller_page_background_app_refresh_disabled_title": "Η ανανέωση εφαρμογής στο παρασκηνίο είναι απενεργοποιημένη", - "backup_controller_page_background_app_refresh_enable_button_text": "Μετάβαση στις ρυθμίσεις", - "backup_controller_page_background_battery_info_link": "Δείξε μου πως", - "backup_controller_page_background_battery_info_message": "Για την καλύτερη εμπειρία δημιουργίας αντιγράφων ασφαλείας στο παρασκήνιο, απενεργοποιήστε οποιαδήποτε βελτιστοποίηση μπαταρίας περιορίζει τη δραστηριότητα στο παρασκήνιο για το Immich. \n\nΔεδομένου ότι ο τρόπος εξαρτάται από τη συσκευή σας, παρακαλούμε ψάξτε τις απαραίτητες πληροφορίες για τον κατασκευαστή της συσκευής σας.", - "backup_controller_page_background_battery_info_ok": "ΟΚ", - "backup_controller_page_background_battery_info_title": "Βελτιστοποιήσεις μπαταρίας", - "backup_controller_page_background_charging": "Μόνο κατά τη φόρτιση", - "backup_controller_page_background_configure_error": "Αποτυχία ρύθμισης της υπηρεσίας παρασκηνίου", - "backup_controller_page_background_delay": "Καθυστέρηση δημιουργίας αντιγράφων ασφαλείας νέων στοιχείων: {duration}", - "backup_controller_page_background_description": "Ενεργοποιήστε την υπηρεσία παρασκηνίου για αυτόματη δημιουργία αντιγράφων ασφαλείας νέων στοιχείων χωρίς να χρειάζεται να ανοίξετε την εφαρμογή", - "backup_controller_page_background_is_off": "Η αυτόματη δημιουργία αντιγράφων ασφαλείας στο παρασκήνιο είναι απενεργοποιημένη", - "backup_controller_page_background_is_on": "Η αυτόματη δημιουργία αντιγράφων ασφαλείας στο παρασκήνιο είναι ενεργοποιημένη", - "backup_controller_page_background_turn_off": "Απενεργοποίηση υπηρεσίας παρασκηνίου", - "backup_controller_page_background_turn_on": "Ενεργοποίηση υπηρεσίας παρασκηνίου", - "backup_controller_page_background_wifi": "Μόνο σε σύνδεση Wi-Fi", - "backup_controller_page_backup": "Αντίγραφο ασφαλείας", - "backup_controller_page_backup_selected": "Επιλεγμένα: ", - "backup_controller_page_backup_sub": "Φωτογραφίες και βίντεο για τα οποία έχουν δημιουργηθεί αντίγραφα ασφαλείας", - "backup_controller_page_created": "Δημιουργήθηκε στις: {date}", - "backup_controller_page_desc_backup": "Ενεργοποιήστε την δημιουργία αντιγράφων ασφαλείας στο προσκήνιο για αυτόματη μεταφόρτωση νέων στοιχείων στον διακομιστή όταν ανοίγετε την εφαρμογή.", - "backup_controller_page_excluded": "Εξαιρούμενα: ", - "backup_controller_page_failed": "Αποτυχημένα ({count})", - "backup_controller_page_filename": "Όνομα αρχείου: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Πληροφορίες αντιγράφου ασφαλείας", - "backup_controller_page_none_selected": "Κανένα επιλεγμένο", - "backup_controller_page_remainder": "Υπόλοιπο", - "backup_controller_page_remainder_sub": "Υπόλοιπες φωτογραφίες και βίντεο για αντιγραφή ασφαλείας από την επιλογή", - "backup_controller_page_server_storage": "Χωρητικότητα Διακομιστή", - "backup_controller_page_start_backup": "Έναρξη δημιουργίας αντιγράφου ασφαλείας", - "backup_controller_page_status_off": "Η αυτόματη δημιουργία αντιγράφου ασφαλείας στο προσκήνιο, είναι απενεργοποιημένη", - "backup_controller_page_status_on": "Η αυτόματη δημιουργία αντιγράφου ασφαλείας στο προσκήνιο είναι ενεργοποιημένη", - "backup_controller_page_storage_format": "{used} από {total} σε χρήση", - "backup_controller_page_to_backup": "Άλμπουμ για δημιουργία αντιγράφου ασφαλείας", - "backup_controller_page_total_sub": "Όλες οι μοναδικές φωτογραφίες και βίντεο από τα επιλεγμένα άλμπουμ", - "backup_controller_page_turn_off": "Απενεργοποίηση δημιουργίας αντιγράφου ασφαλείας στο προσκήνιο", - "backup_controller_page_turn_on": "Ενεργοποίηση δημιουργίας αντιγράφου ασφαλείας στο προσκήνιο", - "backup_controller_page_uploading_file_info": "Μεταφόρτωση πληροφοριών αρχείου", - "backup_err_only_album": "Δεν είναι δυνατή η αφαίρεση του μοναδικού άλμπουμ", - "backup_error_sync_failed": "Ο συγχρονισμός απέτυχε. Δεν είναι δυνατή η επεξεργασία του αντιγράφου ασφαλείας.", - "backup_info_card_assets": "στοιχεία", - "backup_manual_cancelled": "Ακυρώθηκε", - "backup_manual_in_progress": "Μεταφόρτωση σε εξέλιξη. Δοκιμάστε αργότερα", - "backup_manual_success": "Επιτυχία", - "backup_manual_title": "Κατάσταση μεταφόρτωσης", - "backup_options": "Επιλογές αντιγράφου ασφαλείας", - "backup_options_page_title": "Επιλογές αντιγράφων ασφαλείας", - "backup_setting_subtitle": "Διαχείριση ρυθμίσεων μεταφόρτωσης στο παρασκήνιο και στο προσκήνιο", - "backup_settings_subtitle": "Διαχείριση των ρυθμίσεων μεταφόρτωσης", - "backup_upload_details_page_more_details": "Πατήστε για περισσότερες λεπτομέρειες", - "backward": "Προς τα πίσω", - "biometric_auth_enabled": "Βιομετρική ταυτοποίηση ενεργοποιήθηκε", - "biometric_locked_out": "Είστε κλειδωμένοι εκτός της βιομετρικής ταυτοποίησης", - "biometric_no_options": "Δεν υπάρχουν διαθέσιμοι τρόποι βιομετρικής ταυτοποίησης", - "biometric_not_available": "Δεν υπάρχει διαθέσιμη βιομετρική ταυτοποίηση σε αυτή τη συσκευή", - "birthdate_saved": "Η ημερομηνία γέννησης αποθηκεύτηκε επιτυχώς", - "birthdate_set_description": "Η ημερομηνία γέννησης χρησιμοποιείται για τον υπολογισμό της ηλικίας αυτού του ατόμου, τη χρονική στιγμή μιας φωτογραφίας.", - "blurred_background": "Θολό φόντο", - "bugs_and_feature_requests": "Σφάλματα & Αιτήματα Λειτουργιών", - "build": "Κατασκευή", - "build_image": "Κατασκευή Εικόνας", - "bulk_delete_duplicates_confirmation": "Είστε σίγουροι ότι θέλετε να διαγράψετε μαζικά {count, plural, one {# διπλότυπο αρχείο} other {# διπλότυπα αρχεία}}; Αυτό θα κρατήσει το μεγαλύτερο αρχείο από κάθε ομάδα και θα διαγράψει μόνιμα όλα τα υπόλοιπα διπλότυπα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί!", - "bulk_keep_duplicates_confirmation": "Είστε σίγουροι ότι θέλετε να κρατήσετε {count, plural, one {# διπλότυπο αρχείο} other {# διπλότυπα αρχεία}}; Αυτό θα επιλύσει όλες τις ομάδες διπλοτύπων χωρίς να διαγράψει τίποτα.", - "bulk_trash_duplicates_confirmation": "Είστε σίγουροι ότι θέλετε να βάλετε στον κάδο απορριμμάτων {count, plural, one {# διπλότυπο αρχείο} other {# διπλότυπα αρχεία}}; Αυτό θα κρατήσει το μεγαλύτερο αρχείο από κάθε ομάδα και θα βάλει στον κάδο απορριμμάτων όλα τα άλλα διπλότυπα.", - "buy": "Αγοράστε το Immich", - "cache_settings_clear_cache_button": "Εκκαθάριση προσωρινής μνήμης", - "cache_settings_clear_cache_button_title": "Καθαρίζει τη προσωρινή μνήμη της εφαρμογής. Αυτό θα επηρεάσει σημαντικά την απόδοση της εφαρμογής μέχρι να αναδημιουργηθεί η προσωρινή μνήμη.", - "cache_settings_duplicated_assets_clear_button": "ΕΚΚΑΘΑΡΙΣΗ", - "cache_settings_duplicated_assets_subtitle": "Φωτογραφίες και βίντεο που έχουν μπει στη μαύρη λίστα από την εφαρμογή", - "cache_settings_duplicated_assets_title": "Διπλότυπα στοιχεία ({count})", - "cache_settings_statistics_album": "Μικρογραφίες βιβλιοθήκης", - "cache_settings_statistics_full": "Πλήρεις εικόνες", - "cache_settings_statistics_shared": "Μικρογραφίες κοινοποιημένου άλμπουμ", - "cache_settings_statistics_thumbnail": "Μικρογραφίες", - "cache_settings_statistics_title": "Χρήση προσωρινής μνήμης", - "cache_settings_subtitle": "Διαχείρηση συμπεριφοράς της προσωρινής μνήμης", - "cache_settings_tile_subtitle": "Χειριστείτε τη συμπεριφορά της τοπικής αποθήκευσης", - "cache_settings_tile_title": "Τοπική Αποθήκευση", - "cache_settings_title": "Ρυθμίσεις Προσωρινής Μνήμης", - "camera": "Κάμερα", - "camera_brand": "Μάρκα κάμερας", - "camera_model": "Μοντέλο κάμερας", - "cancel": "Ακύρωση", - "cancel_search": "Ακύρωση αναζήτησης", - "canceled": "Ακυρωμένο", - "canceling": "Ακυρώνεται", - "cannot_merge_people": "Αδύνατη η συγχώνευση ατόμων", - "cannot_undo_this_action": "Δεν μπορείτε να αναιρέσετε αυτήν την ενέργεια!", - "cannot_update_the_description": "Αδύνατη η ενημέρωση της περιγραφής", - "cast": "Προβολή", - "cast_description": "Ρύθμιση των διαθέσιμων προορισμών casting", - "change_date": "Αλλαγή ημερομηνίας", - "change_description": "Αλλαγή περιγραφής", - "change_display_order": "Αλλαγή σειράς εμφάνισης", - "change_expiration_time": "Αλλαγή χρόνου λήξης", - "change_location": "Αλλαγή τοποθεσίας", - "change_name": "Αλλαγή ονομασίας", - "change_name_successfully": "Επιτυχής αλλαγή ονόματος", - "change_password": "Αλλαγή Κωδικού", - "change_password_description": "Αυτή είναι ή η πρώτη φορά που συνδέεστε στο σύστημα ή έχει γίνει αίτημα για αλλαγή του κωδικού σας. Παρακαλώ εισάγετε τον νέο κωδικό, παρακάτω.", - "change_password_form_confirm_password": "Επιβεβαίωση Κωδικού", - "change_password_form_description": "Γεια σας {name},\n\nΕίτε είναι η πρώτη φορά που συνδέεστε στο σύστημα είτε έχει γίνει αίτηση για αλλαγή του κωδικού σας. Παρακαλώ εισάγετε τον νέο κωδικό.", - "change_password_form_log_out": "Αποσύνδεση από όλες τις άλλες συσκευές", - "change_password_form_log_out_description": "Συνιστάται η αποσύνδεση από όλες τις άλλες συσκευές", - "change_password_form_new_password": "Νέος Κωδικός", - "change_password_form_password_mismatch": "Οι κωδικοί δεν ταιριάζουν", - "change_password_form_reenter_new_password": "Επανεισαγωγή Νέου Κωδικού", - "change_pin_code": "Αλλαγή κωδικού PIN", - "change_trigger": "Αλλαγή ενεργοποιητή", - "change_trigger_prompt": "Είστε σίγουροι ότι θέλετε να αλλάξετε τον ενεργοποιητή; Αυτό θα διαγράψει όλες τις υπάρχουσες ενέργειες και φίλτρα.", - "change_your_password": "Αλλάξτε τον κωδικό σας", - "changed_visibility_successfully": "Η προβολή, άλλαξε με επιτυχία", - "charging": "Φόρτιση", - "charging_requirement_mobile_backup": "Η δημιουργία αντιγράφων ασφάλειας στο παρασκήνιο απαιτεί η συσκευή να φορτίζει", - "check_corrupt_asset_backup": "Έλεγχος για κατεστραμμένα αντίγραφα ασφαλείας στοιχείων", - "check_corrupt_asset_backup_button": "Εκτέλεση ελέγχου", - "check_corrupt_asset_backup_description": "Εκτέλεσε αυτόν τον έλεγχο μόνο μέσω Wi-Fi και αφού έχουν αποθηκευτεί όλα τα αντίγραφα ασφαλείας των στοιχείων. Η διαδικασία μπορεί να διαρκέσει μερικά λεπτά.", - "check_logs": "Ελέγξτε τα αρχεία καταγραφής", - "checksum": "Έλεγχος ακεραιότητας", - "choose_matching_people_to_merge": "Επιλέξτε τα αντίστοιχα άτομα για συγχώνευση", - "city": "Πόλη", - "cleanup_confirm_description": "Το Immich εντόπισε {count} αρχεία (δημιουργήθηκαν πριν από {date}) που έχουν ασφαλώς αντιγραφεί στον διακομιστή. Να διαγραφούν τα τοπικά αντίγραφα από αυτή τη συσκευή;", - "cleanup_confirm_prompt_title": "Να διαγραφούν από αυτήν τη συσκευή;", - "cleanup_deleted_assets": "Μεταφέρθηκαν {count} αρχεία στον κάδο της συσκευής", - "cleanup_deleting": "Μεταφορά στον κάδο…", - "cleanup_found_assets": "Βρέθηκαν {count} αρχεία που έχουν αντιγραφεί ασφαλώς", - "cleanup_icloud_shared_albums_excluded": "Τα Κοινόχρηστα Άλμπουμ iCloud εξαιρούνται από τη σάρωση", - "cleanup_no_assets_found": "Δεν βρέθηκαν αντίγραφα ασφαλείας στοιχείων που να ταιριάζουν με τα κριτήρια σου", - "cleanup_preview_title": "Στοιχεία προς διαγραφή ({count})", - "cleanup_step3_description": "Σάρωση για φωτογραφίες και βίντεο που έχουν αντιγραφεί στον διακομιστή με την επιλεγμένη ημερομηνία και τα επιλεγμένα φίλτρα", - "cleanup_step4_summary": "{count} αρχεία που δημιουργήθηκαν πριν από {date} έχουν τοποθετηθεί σε σειρά για διαγραφή από τη συσκευή σας", - "cleanup_trash_hint": "Για την πλήρη απελευθέρωση του χώρου αποθήκευσης, ανοίξτε την εφαρμογή φωτογραφιών του συστήματός σας και αδειάστε τον κάδο", - "clear": "Εκκαθάριση", - "clear_all": "Εκκαθάριση όλων", - "clear_all_recent_searches": "Εκκαθάριση όλων των πρόσφατων αναζητήσεων", - "clear_file_cache": "Εκκαθάριση της Προσωρινής Μνήμης Αρχείων", - "clear_message": "Εκκαθάριση μηνύματος", - "clear_value": "Εκκαθάριση τιμής", - "client_cert_dialog_msg_confirm": "ΟΚ", - "client_cert_enter_password": "Εισαγάγετε κωδικό πρόσβασης", - "client_cert_import": "Εισαγωγή", - "client_cert_import_success_msg": "Το πιστοποιητικό πελάτη εισάγεται", - "client_cert_invalid_msg": "Μη έγκυρο αρχείο πιστοποιητικού ή λάθος κωδικός πρόσβασης", - "client_cert_remove_msg": "Το πιστοποιητικό πελάτη καταργήθηκε", - "client_cert_subtitle": "Υποστηρίζει μόνο τη μορφή PKCS12 (.p12, .pfx). Η εισαγωγή/αφαίρεση πιστοποιητικού είναι διαθέσιμη μόνο πριν από τη σύνδεση", - "client_cert_title": "Πιστοποιητικό SSL πελάτη [ΠΕΙΡΑΜΑΤΙΚΟ]", - "clockwise": "Δεξιόστροφα", - "close": "Κλείσιμο", - "collapse": "Σύμπτυξη", - "collapse_all": "Σύμπτυξη όλων", - "color": "Χρώμα", - "color_theme": "Χρώμα θέματος", - "command": "Εντολή", - "comment_deleted": "Το σχόλιο διαγράφηκε", - "comment_options": "Επιλογές σχολίου", - "comments_and_likes": "Σχόλια & αντιδράσεις (likes)", - "comments_are_disabled": "Τα σχόλια είναι απενεργοποιημένα", - "common_create_new_album": "Δημιουργία νέου άλμπουμ", - "completed": "Ολοκληρώθηκε", - "confirm": "Επιβεβαίωση", - "confirm_admin_password": "Επιβεβαίωση κωδικού Διαχειριστή", - "confirm_delete_face": "Είστε σίγουροι ότι θέλετε να διαγράψετε το πρόσωπο του/της {name} από το στοιχείο;", - "confirm_delete_shared_link": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον κοινόχρηστο σύνδεσμο;", - "confirm_keep_this_delete_others": "Όλα τα άλλα στοιχεία της στοίβας θα διαγραφούν, εκτός από αυτό το στοιχείο. Είστε σίγουροι ότι θέλετε να συνεχίσετε;", - "confirm_new_pin_code": "Επιβεβαίωση νέου κωδικού PIN", - "confirm_password": "Επιβεβαίωση κωδικού", - "confirm_tag_face": "Θέλετε να επισημάνετε αυτό το πρόσωπο ως {name};", - "confirm_tag_face_unnamed": "Θέλετε να επισημάνετε αυτό το πρόσωπο;", - "connected_device": "Συνδεδεμένη συσκευή", - "connected_to": "Συνδεδεμένο με", - "contain": "Περιέχει", - "context": "Συμφραζόμενα", - "continue": "Συνέχεια", - "control_bottom_app_bar_create_new_album": "Δημιουργία νέου άλμπουμ", - "control_bottom_app_bar_delete_from_immich": "Διαγραφή από το Immich", - "control_bottom_app_bar_delete_from_local": "Διαγραφή από τη συσκευή", - "control_bottom_app_bar_edit_location": "Επεξεργασία Τοποθεσίας", - "control_bottom_app_bar_edit_time": "Επεξεργασία Ημερομηνίας & Ώρας", - "control_bottom_app_bar_share_link": "Κοινοποιήστε το σύνδεσμο", - "control_bottom_app_bar_share_to": "Κοινοποίηση Σε", - "control_bottom_app_bar_trash_from_immich": "Μετακίνηση στα Απορρίμματα", - "copied_image_to_clipboard": "Η εικόνα αντιγράφηκε στο πρόχειρο.", - "copied_to_clipboard": "Αντιγράφηκε στο πρόχειρο!", - "copy_error": "Σφάλμα αντιγραφής", - "copy_file_path": "Αντιγραφή διαδρομής αρχείου", - "copy_image": "Αντιγραφή Εικόνας", - "copy_link": "Αντιγραφή συνδέσμου", - "copy_link_to_clipboard": "Αντιγραφή συνδέσμου στο πρόχειρο", - "copy_password": "Αντιγραφή κωδικού", - "copy_to_clipboard": "Αντιγραφή στο πρόχειρο", - "country": "Χώρα", - "cover": "Εξώφυλλο", - "covers": "Εξώφυλλα", - "create": "Δημιουργία", - "create_album": "Δημιουργία άλμπουμ", - "create_album_page_untitled": "Χωρίς τίτλο", - "create_api_key": "Δημιουργία κλειδιού API", - "create_first_workflow": "Δημιουργήστε την πρώτη ροή εργασίας", - "create_library": "Δημιουργία Βιβλιοθήκης", - "create_link": "Δημιουργία συνδέσμου", - "create_link_to_share": "Δημιουργία συνδέσμου για διαμοιρασμό", - "create_link_to_share_description": "Επιτρέψτε σε οποιονδήποτε έχει τον σύνδεσμο να δει τη/τις επιλεγμένη/ες φωτογραφία/ες", - "create_new": "ΔΗΜΙΟΥΡΓΙΑ ΝΕΟΥ", - "create_new_person": "Δημιουργία νέου προσώπου", - "create_new_person_hint": "Αντιστοίχιση των επιλεγμένων αρχείων σε ένα νέο πρόσωπο", - "create_new_user": "Δημιουργία νέου χρήστη", - "create_shared_album_page_share_add_assets": "ΠΡΟΣΘΗΚΗ ΣΤΟΙΧΕΙΩΝ", - "create_shared_album_page_share_select_photos": "Επιλέξτε Φωτογραφίες", - "create_shared_link": "Δημιουργία κοινόχρηστου συνδέσμου", - "create_tag": "Δημιουργία ετικέτας", - "create_tag_description": "Δημιουργία νέας ετικέτας. Για τις ένθετες ετικέτες, παρακαλώ εισάγετε τη πλήρη διαδρομή της, συμπεριλαμβανομένων των κάθετων διαχωριστικών.", - "create_user": "Δημιουργία χρήστη", - "create_workflow": "Δημιουργία ροής εργασίας", - "created": "Δημιουργήθηκε", - "created_at": "Δημιουργήθηκε", - "creating_linked_albums": "Δημιουργία συνδεδεμένων άλμπουμ...", - "crop": "Αποκοπή", - "crop_aspect_ratio_fixed": "Διορθώθηκε", - "crop_aspect_ratio_free": "Ελεύθερο", - "crop_aspect_ratio_original": "Αυθεντικό", - "curated_object_page_title": "Πράγματα", - "current_device": "Τρέχουσα συσκευή", - "current_pin_code": "Τρέχων κωδικός PIN", - "current_server_address": "Τρέχουσα διεύθυνση διακομιστή", - "custom_date": "Προσαρμοσμένη ημερομηνία", - "custom_locale": "Προσαρμοσμένη Τοπική Ρύθμιση", - "custom_locale_description": "Μορφοποιήστε τις ημερομηνίες και τους αριθμούς, σύμφωνα με τη γλώσσα και την περιοχή", - "custom_url": "Προσαρμοσμένη διεύθυνση URL", - "cutoff_date_description": "Διαγραφή φωτογραφιών και βίντεο παλαιότερων από", - "cutoff_day": "{count, plural, one {ημέρα} other {ημέρες}}", - "cutoff_year": "{count, plural, one {έτος} other {έτη}}", - "daily_title_text_date": "Ε, MMM dd", - "daily_title_text_date_year": "Ε, MMM dd, yyyy", - "dark": "Σκούρο", - "dark_theme": "Εναλλαγή σκοτεινής εμφάνισης", - "date": "Ημερομηνία", - "date_after": "Ημερομηνία μετά", - "date_and_time": "Ημερομηνία και ώρα", - "date_before": "Ημερομηνία πριν", - "date_format": "Ε, LLL d, y • h:mm a", - "date_of_birth_saved": "Η ημερομηνία γέννησης αποθηκεύτηκε επιτυχώς", - "date_range": "Εύρος ημερομηνιών", - "day": "Ημέρα", - "days": "Ημέρες", - "deduplicate_all": "Αφαίρεση όλων των διπλότυπων", - "deduplication_criteria_1": "Μέγεθος εικόνας σε byte", - "deduplication_criteria_2": "Αριθμός δεδομένων EXIF", - "deduplication_info": "Πληροφορίες Αφαίρεσης Διπλοτύπων", - "deduplication_info_description": "Για να προεπιλέξουμε αυτόματα τα αρχεία και να αφαιρέσουμε τα διπλότυπα σε μαζική επεξεργασία, εξετάζουμε σε:", - "default_locale": "Προεπιλεγμένη Τοπική Ρύθμιση", - "default_locale_description": "Μορφοποιήστε τις ημερομηνίες και τους αριθμούς με βάση την τοπική ρύθμιση του προγράμματος περιήγησής σας", - "delete": "Διαγραφή", - "delete_action_confirmation_message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο; Αυτή η ενέργεια θα το μετακινήσει στον κάδο απορριμμάτων του διακομιστή και θα εμφανιστεί μήνυμα για το αν θέλετε να το διαγράψετε και τοπικά", - "delete_action_prompt": "{count} διαγράφηκαν", - "delete_album": "Διαγραφή άλμπουμ", - "delete_api_key_prompt": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό κλειδί API;", - "delete_dialog_alert": "Αυτά τα αντικείμενα θα διαγραφούν οριστικά από το Immich και από τη συσκευή σας", - "delete_dialog_alert_local": "Αυτά τα αντικείμενα θα διαγραφούν οριστικά από την συσκευή σας αλλα θα παραμείνουν διαθέσιμα στον διακομιστή Immich", - "delete_dialog_alert_local_non_backed_up": "Κάποια από τα αντικείμενα δεν έχουν αντίγραφα ασφαλείας στο Immich και θα διαγραφούν οριστικά από τη συσκευή σας", - "delete_dialog_alert_remote": "Αυτά τα αντικείμενα θα διαγραφούν οριστικά από τον διακομιστή Immich", - "delete_dialog_ok_force": "Διαγραφή όπως και να έχει", - "delete_dialog_title": "Οριστική Διαγραφή", - "delete_duplicates_confirmation": "Είστε σίγουροι ότι επιθυμείτε τη μόνιμη διαγραφή αυτών των διπλότυπων;", - "delete_face": "Διαγραφή προσώπου", - "delete_key": "Διαγραφή κλειδιού", - "delete_library": "Διαγραφή Βιβλιοθήκης", - "delete_link": "Διαγραφή συνδέσμου", - "delete_local_action_prompt": "{count} διαγράφηκαν τοπικά", - "delete_local_dialog_ok_backed_up_only": "Διαγραφή μόνο των αντιγράφων ασφαλείας", - "delete_local_dialog_ok_force": "Διαγραφή όπως και να έχει", - "delete_others": "Διαγραφή υπολοίπων", - "delete_permanently": "Διαγραφή οριστικά", - "delete_permanently_action_prompt": "{count} διαγράφηκε οριστικά", - "delete_shared_link": "Διαγραφή κοινόχρηστου συνδέσμου", - "delete_shared_link_dialog_title": "Διαγραφή Κοινοποιημένου Συνδέσμου", - "delete_tag": "Διαγραφή ετικέτας", - "delete_tag_confirmation_prompt": "Είστε σίγουροι ότι θέλετε να διαγράψετε την ετικέτα {tagName};", - "delete_user": "Διαγραφή χρήστη", - "deleted_shared_link": "Ο κοινόχρηστος σύνδεσμος, διαγράφηκε", - "deletes_missing_assets": "Διαγράφει στοιχεία που λείπουν από το δίσκο", - "description": "Περιγραφή", - "description_input_hint_text": "Προσθήκη περιγραφής...", - "description_input_submit_error": "Σφάλμα κατά την ενημέρωση της περιγραφής, ελέγξτε το αρχείο καταγραφής για περισσότερες λεπτομέρειες", - "deselect_all": "Ακύρωση όλων των επιλογών", - "details": "Λεπτομέρειες", - "direction": "Κατεύθυνση", - "disable": "Απενεργοποίηση", - "disabled": "Απενεργοποιημένο", - "disallow_edits": "Απαγόρευση επεξεργασιών", - "discord": "Πλατφόρμα Discord", - "discover": "Ανίχνευση", - "discovered_devices": "Διαθέσιμες συσκευές", - "dismiss_all_errors": "Παράβλεψη όλων των σφαλμάτων", - "dismiss_error": "Παράβλεψη σφάλματος", - "display_options": "Επιλογές εμφάνισης", - "display_order": "Σειρά εμφάνισης", - "display_original_photos": "Εμφάνιση πρωτότυπων φωτογραφιών", - "display_original_photos_setting_description": "Προτίμηση εμφάνισης της αυθεντικής φωτογραφίας, κατά την προβολή ενός στοιχείου αντί για τη μικρογραφία του, όταν το αυθεντικό στοιχείο είναι συμβατό με τον ιστότοπο. Αυτό μπορεί να οδηγήσει σε πιο αργές ταχύτητες εμφάνισης φωτογραφιών.", - "do_not_show_again": "Να μην εμφανιστεί ξανά αυτό το μήνυμα", - "documentation": "Τεκμηρίωση", - "done": "Έγινε", - "download": "Λήψη", - "download_action_prompt": "Κατέβασμα {count} στοιχείων", - "download_canceled": "Η λήψη ακυρώθηκε", - "download_complete": "Η λήψη ολοκληρώθηκε", - "download_enqueue": "Η λήψη τέθηκε σε ουρά", - "download_error": "Σφάλμα λήψης", - "download_failed": "Η λήψη απέτυχε", - "download_finished": "Η λήψη ολοκληρώθηκε", - "download_include_embedded_motion_videos": "Ενσωματωμένα βίντεο", - "download_include_embedded_motion_videos_description": "Συμπεριλάβετε τα βίντεο που είναι ενσωματωμένα σε κινούμενες φωτογραφίες ως ξεχωριστό αρχείο", - "download_notfound": "Το αρχείο δεν βρέθηκε", - "download_original": "Λήψη πρωτότυπου", - "download_paused": "Η λήψη διακόπηκε", - "download_settings": "Λήψη", - "download_settings_description": "Διαχείριση ρυθμίσεων που σχετίζονται με τη λήψη στοιχείων", - "download_started": "Η λήψη ξεκίνησε", - "download_sucess": "Επιτυχία λήψης", - "download_sucess_android": "Το μέσο έχει ληφθεί στο DCIM/Immich", - "download_waiting_to_retry": "Αναμονή για επανάληψη", - "downloading": "Γίνεται λήψη", - "downloading_asset_filename": "Λήψη στοιχείου {filename}", - "downloading_from_icloud": "Λήψη από το iCloud", - "downloading_media": "Λήψη πολυμέσων", - "drop_files_to_upload": "Σύρετε αρχεία εδώ για να τα ανεβάσετε", - "duplicates": "Διπλότυπα", - "duplicates_description": "Επιλύστε κάθε ομάδα υποδεικνύοντας ποιες είναι διπλότυπες, εάν υπάρχουν", - "duration": "Διάρκεια", - "edit": "Επεξεργασία", - "edit_album": "Επεξεργασία άλμπουμ", - "edit_avatar": "Επεξεργασία άβαταρ", - "edit_birthday": "Επεξεργασία γενεθλίων", - "edit_date": "Επεξεργασία ημερομηνίας", - "edit_date_and_time": "Επεξεργασία ημερομηνίας και ώρας", - "edit_date_and_time_action_prompt": "{count} ημερομηνία και ώρα επεξεργάστηκαν", - "edit_date_and_time_by_offset": "Αλλαγή ημερομηνίας με μετατόπιση", - "edit_date_and_time_by_offset_interval": "Νέο εύρος ημερομηνιών: {from} - {to}", - "edit_description": "Επεξεργασία περιγραφής", - "edit_description_prompt": "Παρακαλώ επιλέξτε νέα περιγραφή:", - "edit_exclusion_pattern": "Επεξεργασία μοτίβου αποκλεισμού", - "edit_faces": "Επεξεργασία προσώπων", - "edit_key": "Επεξεργασία κλειδιού", - "edit_link": "Επεξεργασία συνδέσμου", - "edit_location": "Επεξεργασία τοποθεσίας", - "edit_location_action_prompt": "Επεξεργάστηκαν {count} τοποθεσίες", - "edit_location_dialog_title": "Τοποθεσία", - "edit_name": "Επεξεργασία ονόματος", - "edit_people": "Επεξεργασία ατόμων", - "edit_tag": "Επεξεργασία ετικέτας", - "edit_title": "Επεξεργασία Τίτλου", - "edit_user": "Επεξεργασία χρήστη", - "edit_workflow": "Επεξεργασία ροής εργασίας", - "editor": "Επεξεργαστής", - "editor_close_without_save_prompt": "Αυτές οι αλλαγές δεν θα αποθηκευτούν", - "editor_close_without_save_title": "Κλείσιμο επεξεργαστή;", - "editor_confirm_reset_all_changes": "Είστε σίγουροι ότι θέλετε να επαναφέρετε όλες τις αλλαγές;", - "editor_flip_horizontal": "Οριζόντια αναστροφή", - "editor_flip_vertical": "Κάθετη αναστροφή", - "editor_orientation": "Προσανατολισμός", - "editor_reset_all_changes": "Επαναφορά αλλαγών", - "editor_rotate_left": "Περιστροφή 90° αριστερόστροφα", - "editor_rotate_right": "Περιστροφή 90° δεξιόστροφα", - "email": "Email", - "email_notifications": "Ειδοποιήσεις email", - "empty_folder": "Αυτός ο φάκελος είναι κενός", - "empty_trash": "Άδειασμα κάδου απορριμμάτων", - "empty_trash_confirmation": "Είστε σίγουροι οτι θέλετε να αδειάσετε τον κάδο απορριμμάτων; Αυτό θα αφαιρέσει μόνιμα όλα τα στοιχεία του κάδου απορριμμάτων του Immich. \nΑυτή η ενέργεια δεν μπορεί να αναιρεθεί!", - "enable": "Ενεργοποίηση", - "enable_backup": "Ενεργοποίηση αντιγράφου ασφαλείας", - "enable_biometric_auth_description": "Εισάγετε τον κωδικό PIN σας για να ενεργοποιήσετε την βιομετρική ταυτοποίηση", - "enabled": "Ενεργοποιημένο", - "end_date": "Τελική ημερομηνία", - "enqueued": "Τοποθετήθηκε στη λίστα αναμονής", - "enter_wifi_name": "Εισαγωγή ονόματος Wi-Fi", - "enter_your_pin_code": "Εισάγετε τον κωδικό PIN σας", - "enter_your_pin_code_subtitle": "Εισάγετε τον κωδικό PIN σας για να εισέλθετε στον κλειδωμένο φάκελο", - "error": "Σφάλμα", - "error_change_sort_album": "Απέτυχε η αλλαγή σειράς του άλμπουμ", - "error_delete_face": "Σφάλμα διαγραφής προσώπου από το στοιχείο", - "error_getting_places": "Σφάλμα κατά την ανάκτηση τοποθεσιών", - "error_loading_image": "Σφάλμα κατά τη φόρτωση της εικόνας", - "error_loading_partners": "Σφάλμα κατά τη φόρτωση συνεργατών: {error}", - "error_retrieving_asset_information": "Σφάλμα κατά την ανάκτηση πληροφοριών στοιχείου", - "error_saving_image": "Σφάλμα: {error}", - "error_tag_face_bounding_box": "Σφάλμα επισήμανσης προσώπου - δεν μπορούν να ληφθούν οι συντεταγμένες του πλαισίου οριοθέτησης", - "error_title": "Σφάλμα - Κάτι πήγε στραβά", - "error_while_navigating": "Σφάλμα κατά την πλοήγηση στο στοιχείο", - "errors": { - "cannot_navigate_next_asset": "Δεν είναι δυνατή η πλοήγηση στο επόμενο στοιχείο", - "cannot_navigate_previous_asset": "Δεν είναι δυνατή η πλοήγηση στο προηγούμενο στοιχείο", - "cant_apply_changes": "Δεν είναι δυνατή η εφαρμογή των αλλαγών", - "cant_change_activity": "Δεν μπορείτε να {enabled, select, true {απενεργοποιήσετε} other {ενεργοποιήσετε}} τη δραστηριότητα", - "cant_change_asset_favorite": "Δεν μπορείτε να αλλάξετε το αγαπημένο για το στοιχείο", - "cant_change_metadata_assets_count": "Δεν μπορείτε να αλλάξετε τα μεταδεδομένα του {count, plural, one {# αρχείου} other {# αρχείων}}", - "cant_get_faces": "Δεν είναι δυνατή η ανάκτηση προσώπων", - "cant_get_number_of_comments": "Δεν είναι δυνατή η ανάκτηση του αριθμού των σχολίων", - "cant_search_people": "Αδύνατη η αναζήτηση ατόμων", - "cant_search_places": "Δεν μπορείτε να αναζητήσετε τοποθεσίες", - "error_adding_assets_to_album": "Σφάλμα κατά την προσθήκη στοιχείων στο άλμπουμ", - "error_adding_users_to_album": "Σφάλμα κατά την προσθήκη χρηστών στο άλμπουμ", - "error_deleting_shared_user": "Σφάλμα διαγραφής κοινόχρηστου χρήστη", - "error_downloading": "Σφάλμα λήψης {filename}", - "error_hiding_buy_button": "Σφάλμα απόκρυψης κουμπιού αγοράς", - "error_removing_assets_from_album": "Σφάλμα αφαίρεσης στοιχείων από το άλμπουμ, ελέγξτε την κονσόλα για περισσότερες λεπτομέρειες", - "error_selecting_all_assets": "Σφάλμα κατά την επιλογή όλων των στοιχείων", - "exclusion_pattern_already_exists": "Αυτό το μοτίβο αποκλεισμού υπάρχει ήδη.", - "failed_to_create_album": "Αποτυχία δημιουργίας άλμπουμ", - "failed_to_create_shared_link": "Αποτυχία δημιουργίας κοινόχρηστου συνδέσμου", - "failed_to_edit_shared_link": "Αποτυχία επεξεργασίας κοινόχρηστου συνδέσμου", - "failed_to_get_people": "Αποτυχία ανάκτησης ατόμων", - "failed_to_keep_this_delete_others": "Αποτυχία διατήρησης αυτού του στοιχείου και διαγραφής των υπόλοιπων στοιχείων", - "failed_to_load_asset": "Αποτυχία φόρτωσης στοιχείου", - "failed_to_load_assets": "Αποτυχία φόρτωσης στοιχείων", - "failed_to_load_notifications": "Αποτυχία φόρτωσης ειδοποιήσεων", - "failed_to_load_people": "Αποτυχία φόρτωσης ατόμων", - "failed_to_remove_product_key": "Αποτυχία αφαίρεσης κλειδιού προϊόντος", - "failed_to_reset_pin_code": "Αποτυχία επαναφοράς του PIN", - "failed_to_stack_assets": "Αποτυχία στην συμπίεση των στοιχείων", - "failed_to_unstack_assets": "Αποτυχία στην αποσυμπίεση των στοιχείων", - "failed_to_update_notification_status": "Αποτυχία ενημέρωσης της κατάστασης ειδοποίησης", - "incorrect_email_or_password": "Λανθασμένο email ή κωδικός πρόσβασης", - "library_folder_already_exists": "Η διαδρομή εισαγωγής υπάρχει ήδη.", - "paths_validation_failed": "{paths, plural, one {# διαδρομή} other {# διαδρομές}} απέτυχαν κατά την επικύρωση", - "profile_picture_transparent_pixels": "Οι εικόνες προφίλ δεν μπορούν να έχουν διαφανή εικονοστοιχεία. Παρακαλώ μεγεθύνετε ή/και μετακινήστε την εικόνα.", - "quota_higher_than_disk_size": "Έχετε ορίσει ένα όριο, μεγαλύτερο από το μέγεθος του δίσκου", - "something_went_wrong": "Κάτι πήγε στραβά", - "unable_to_add_album_users": "Αδυναμία προσθήκης χρήστη στο άλμπουμ", - "unable_to_add_assets_to_shared_link": "Αδυναμία προσθήκης στοιχείου στον κοινόχρηστο σύνδεσμο", - "unable_to_add_comment": "Αδυναμία προσθήκης σχολίου", - "unable_to_add_exclusion_pattern": "Αδυναμία προσθήκης μοτίβου αποκλεισμού", - "unable_to_add_partners": "Αδυναμία προσθήκης συνεργατών", - "unable_to_add_remove_archive": "Αδυναμία {archived, select, true {αφαίρεσης του στοιχείου από το} other {προσθήκης του στοιχείου στο}} αρχείο", - "unable_to_add_remove_favorites": "Αδυναμία {favorite, select, true {προσθήκης του στοιχείου στα} other {αφαίρεσης του στοιχείου από τα}} αγαπημένα", - "unable_to_archive_unarchive": "Αδυναμία {archived, select, true {αρχειοθέτησης} other {αποαρχειοθέτησης}}", - "unable_to_change_album_user_role": "Αδυναμία αλλαγής του ρόλου του χρήστη στο άλμπουμ", - "unable_to_change_date": "Αδυναμία αλλάγης της ημερομηνίας", - "unable_to_change_description": "Αδυναμία αλλαγής περιγραφής", - "unable_to_change_favorite": "Αδυναμία αλλαγής αγαπημένου για το στοιχείο", - "unable_to_change_location": "Αδυναμία αλλαγής της τοποθεσίας", - "unable_to_change_password": "Αδυναμία αλλαγής του κωδικού πρόσβασης", - "unable_to_change_visibility": "Αδυναμία αλλαγής της προβολής για {count, plural, one {# άτομο} other {# άτομα}}", - "unable_to_complete_oauth_login": "Αδυναμία ολοκλήρωσης σύνδεσης μέσω OAuth", - "unable_to_connect": "Αδυναμία σύνδεσης", - "unable_to_copy_to_clipboard": "Αδυναμία αντιγραφής στο πρόχειρο, βεβαιωθείτε ότι έχετε πρόσβαση στη σελίδα μέσω https", - "unable_to_create": "Αδυναμία δημιουργίας ροής εργασίας", - "unable_to_create_admin_account": "Αδυναμία δημιουργίας λογαριασμού διαχειριστή", - "unable_to_create_api_key": "Αδυναμία δημιουργίας ενός νέου κλειδιού API", - "unable_to_create_library": "Αδυναμία δημιουργίας βιβλιοθήκης", - "unable_to_create_user": "Αδυναμία δημιουργίας χρήστη", - "unable_to_delete_album": "Αδυναμία διαγραφής άλμπουμ", - "unable_to_delete_asset": "Αδυναμία διαγραφής στοιχείου", - "unable_to_delete_assets": "Σφάλμα κατα τη διαγραφή στοιχείων", - "unable_to_delete_exclusion_pattern": "Αδυναμία διαγραφής μοτίβου αποκλεισμού", - "unable_to_delete_shared_link": "Αδυναμία διαγραφής κοινόχρηστου συνδέσμου", - "unable_to_delete_user": "Αδυναμία διαγραφής χρήστη", - "unable_to_delete_workflow": "Αδυναμία διαγραφής ροής εργασίας", - "unable_to_download_files": "Αδυναμία λήψης αρχείων", - "unable_to_edit_exclusion_pattern": "Αδυναμία επεξεργασίας μοτίβου αποκλεισμού", - "unable_to_empty_trash": "Αδυναμία αδειάσματος του κάδου απορριμμάτων", - "unable_to_enter_fullscreen": "Αδυναμία μετάβασης σε πλήρη οθόνη", - "unable_to_exit_fullscreen": "Αδυναμία εξόδου από πλήρη οθόνη", - "unable_to_get_comments_number": "Αδυναμία ανάκτησης του αριθμού των σχολίων", - "unable_to_get_shared_link": "Αδυναμία ανάκτησης κοινόχρηστου συνδέσμου", - "unable_to_hide_person": "Αδυναμία απόκρυψης του ατόμου", - "unable_to_link_motion_video": "Αδυναμία σύνδεσης βίντεο κίνησης", - "unable_to_link_oauth_account": "Αδυναμία σύνδεσης λογαριασμού OAuth", - "unable_to_log_out_all_devices": "Αδυναμία αποσύνδεσης όλων των συσκευών", - "unable_to_log_out_device": "Αδυναμία αποσύνδεσης της συσκευής", - "unable_to_login_with_oauth": "Αδυναμία εισόδου μέσω OAuth", - "unable_to_play_video": "Αδυναμία αναπαραγωγής βίντεο", - "unable_to_reassign_assets_existing_person": "Αδυναμία επανακατηγοριοποίησης των στοιχείων στον/στην {name, select, null {υπάρχον άτομο} other {{name}}}", - "unable_to_reassign_assets_new_person": "Αδυναμία επανακατηγοριοποίησης των στοιχείων σε ένα νέο άτομο", - "unable_to_refresh_user": "Αδυναμία ανανέωσης χρήστη", - "unable_to_remove_album_users": "Αδυναμία διαγραφής χρηστών από το άλμπουμ", - "unable_to_remove_api_key": "Αδυναμία διαγραφής του κλειδιού API", - "unable_to_remove_assets_from_shared_link": "Αδυναμία διαγραφής στοιχείων από τον κοινόχρηστο σύνδεσμο", - "unable_to_remove_library": "Αδυναμία αφαίρεσης βιβλιοθήκης", - "unable_to_remove_partner": "Αδυναμία αφαίρεσης συνεργάτη", - "unable_to_remove_reaction": "Αδυναμία αφαίρεσης της αντίδρασης", - "unable_to_reset_password": "Αδυναμία επαναφοράς κωδικού πρόσβασης", - "unable_to_reset_pin_code": "Αδυναμία επαναφοράς κωδικού PIN", - "unable_to_resolve_duplicate": "Αδυναμία επίλυσης του διπλότυπου", - "unable_to_restore_assets": "Αδυναμία επαναφοράς των στοιχείων", - "unable_to_restore_trash": "Αδυναμία επαναφοράς του κάδου απορριμμάτων", - "unable_to_restore_user": "Αδυναμία επαναφοράς χρήστη", - "unable_to_save_album": "Αδυναμία αποθήκευσης άλμπουμ", - "unable_to_save_api_key": "Αδυναμία αποθήκευσης κλειδιού API", - "unable_to_save_date_of_birth": "Αδυναμία αποθήκευσης ημερομηνίας γέννησης", - "unable_to_save_name": "Αδυναμία αποθήκευσης ονόματος", - "unable_to_save_profile": "Αδυναμία αποθήκευσης προφίλ", - "unable_to_save_settings": "Αδυναμία αποθήκευσης ρυθμίσεων", - "unable_to_scan_libraries": "Αδυναμία σάρωσης βιβλιοθηκών", - "unable_to_scan_library": "Αδυναμία σάρωσης βιβλιοθήκης", - "unable_to_set_feature_photo": "Αδυναμία ορισμού φωτογραφίας χαρακτηριστικού", - "unable_to_set_profile_picture": "Αδυναμία ορισμού φωτογραφίας προφίλ", - "unable_to_set_rating": "Αδυναμία ορισμού βαθμολογίας", - "unable_to_submit_job": "Αδυναμία υποβολής εργασίας", - "unable_to_trash_asset": "Αδυναμία μετακίνησης του στοιχείου στον κάδο απορριμμάτων", - "unable_to_unlink_account": "Αδυναμία αποσύνδεσης του λογαριασμού", - "unable_to_unlink_motion_video": "Αδυναμία αποσύνδεσης βίντεο κίνησης", - "unable_to_update_album_cover": "Αδυναμία ανανέωσης του εξώφυλλου του άλμπουμ", - "unable_to_update_album_info": "Αδυναμία ανανέωσης των πληροφοριών του άλμπουμ", - "unable_to_update_library": "Αδυναμία ανανέωσης της βιβλιοθήκης", - "unable_to_update_location": "Αδυναμία ανανέωσης της τοποθεσίας", - "unable_to_update_settings": "Αδυναμία ανανέωσης των ρυθμίσεων", - "unable_to_update_timeline_display_status": "Αδυναμία ενημέρωσης κατάστασης της προβολής χρονολογίας", - "unable_to_update_user": "Αδυναμία ενημέρωσης του χρήστη", - "unable_to_update_workflow": "Αδυναμία ενημέρωσης ροής εργασίας", - "unable_to_upload_file": "Αδυναμία μεταφόρτωσης αρχείου" - }, - "errors_text": "Σφάλματα", - "exclusion_pattern": "Μοτίβο αποκλεισμού", - "exif": "Μεταδεδομένα Exif", - "exif_bottom_sheet_description": "Προσθήκη Περιγραφής...", - "exif_bottom_sheet_description_error": "Σφάλμα κατά την ενημέρωση της περιγραφής", - "exif_bottom_sheet_details": "ΛΕΠΤΟΜΕΡΕΙΕΣ", - "exif_bottom_sheet_location": "ΤΟΠΟΘΕΣΙΑ", - "exif_bottom_sheet_no_description": "Καμία περιγραφή", - "exif_bottom_sheet_people": "ΑΤΟΜΑ", - "exif_bottom_sheet_person_add_person": "Προσθήκη ονόματος", - "exit_slideshow": "Έξοδος από την παρουσίαση", - "expand_all": "Ανάπτυξη όλων", - "experimental_settings_new_asset_list_subtitle": "Σε εξέλιξη", - "experimental_settings_new_asset_list_title": "Ενεργοποίηση πειραματικού πλέγματος φωτογραφιών", - "experimental_settings_subtitle": "Χρησιμοποιείτε με δική σας ευθύνη!", - "experimental_settings_title": "Πειραματικό", - "expire_after": "Λήγει μετά από", - "expired": "Έληξε", - "expires_date": "Λήγει {date}", - "explore": "Περιήγηση", - "explorer": "Περιηγητής", - "export": "Εξαγωγή", - "export_as_json": "Εξαγωγή ως JSON", - "export_database": "Εξαγωγή βάσης δεδομένων", - "export_database_description": "Εξαγωγή της SQLite βάσης δεδομένων", - "extension": "Επέκταση", - "external": "Εξωτερικός", - "external_libraries": "Εξωτερικές βιβλιοθήκες", - "external_network": "Εξωτερικό δίκτυο", - "external_network_sheet_info": "Όταν δεν είστε συνδεδεμένοι στο προτιμώμενο δίκτυο Wi-Fi, η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω του πρώτου από τα παρακάτω URLs που μπορεί να βρει διαθέσιμο, ξεκινώντας από το πάνω προς το κάτω", - "face_unassigned": "Μη ανατεθειμένο", - "failed": "Απέτυχε", - "failed_count": "Αποτυχημένα: {count}", - "failed_to_authenticate": "Αποτυχία ταυτοποίησης", - "failed_to_load_assets": "Αποτυχία φόρτωσης στοιχείων", - "failed_to_load_folder": "Αποτυχία φόρτωσης φακέλου", - "favorite": "Αγαπημένο", - "favorite_action_prompt": "Προστέθηκαν {count} στα Αγαπημένα", - "favorite_or_unfavorite_photo": "Ορίστε μία φωτογραφία ως αγαπημένη ή αφαιρέστε την από τα αγαπημένα", - "favorites": "Αγαπημένα", - "favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία", - "feature_photo_updated": "Η φωτογραφία προβολής ενημερώθηκε", - "features": "Χαρακτηριστικά", - "features_in_development": "Λειτουργίες υπό Ανάπτυξη", - "features_setting_description": "Διαχειριστείτε τα χαρακτηριστικά της εφαρμογής", - "file_name_or_extension": "Όνομα αρχείου ή επέκταση", - "file_size": "Μέγεθος αρχείου", - "filename": "Ονομασία αρχείου", - "filetype": "Τύπος αρχείου", - "filter": "Φίλτρο", - "filter_description": "Συνθήκες για φιλτράρισμα των στοχευμένων στοιχείων", - "filter_people": "Φιλτράρισμα ατόμων", - "filter_places": "Φιλτράρισμα τοποθεσιών", - "filters": "Φίλτρα", - "find_them_fast": "Βρείτε τους γρήγορα με αναζήτηση κατά όνομα", - "first": "Αρχικά", - "fix_incorrect_match": "Διόρθωση λανθασμένης αντιστοίχισης", - "folder": "Φάκελος", - "folder_not_found": "Ο φάκελος δεν βρέθηκε", - "folders": "Φάκελοι", - "folders_feature_description": "Περιήγηση στην προβολή φακέλου για τις φωτογραφίες και τα βίντεο στο σύστημα αρχείων", - "forgot_pin_code_question": "Ξεχάσατε το PIN;", - "forward": "Προς τα εμπρός", - "free_up_space": "Απελευθέρωση χώρου", - "free_up_space_description": "Μετακινήστε τις φωτογραφίες και τα βίντεο που έχουν αντιγραφεί στον κάδο της συσκευής σας για να απελευθερώσετε χώρο. Τα αντίγραφά σας στον διακομιστή παραμένουν ασφαλή", - "free_up_space_settings_subtitle": "Απελευθέρωση χώρου στη συσκευή", - "full_path": "Πλήρης διαδρομή: {path}", - "gcast_enabled": "Μετάδοση περιεχομένου Google Cast", - "gcast_enabled_description": "Αυτό το χαρακτηριστικό φορτώνει εξωτερικούς πόρους από τη Google για να λειτουργήσει.", - "general": "Γενικά", - "geolocation_instruction_location": "Κάνε κλικ σε ένα στοιχείο με συντεταγμένες GPS για να χρησιμοποιήσεις την τοποθεσία του, ή επίλεξε απευθείας μια τοποθεσία από τον χάρτη", - "get_help": "Ζητήστε βοήθεια", - "get_people_error": "Σφάλμα ανάκτησης χρηστών", - "get_wifiname_error": "Δεν ήταν δυνατή η λήψη του ονόματος Wi-Fi. Βεβαιωθείτε ότι έχετε δώσει τις απαραίτητες άδειες και ότι είστε συνδεδεμένοι σε δίκτυο Wi-Fi", - "getting_started": "Ξεκινώντας", - "go_back": "Πηγαίνετε πίσω", - "go_to_folder": "Μετάβαση στο φάκελο", - "go_to_search": "Πηγαίνετε στην αναζήτηση", - "gps": "GPS", - "gps_missing": "Χωρίς GPS", - "grant_permission": "Επιτρέψτε την άδεια", - "group_albums_by": "Ομαδοποίηση άλμπουμ κατά...", - "group_country": "Ομαδοποίηση κατά χώρα", - "group_no": "Καμία ομοδοποίηση", - "group_owner": "Ομαδοποίηση κατά ιδιοκτήτη", - "group_places_by": "Ομοδοποίηση τοποθεσιών κατά...", - "group_year": "Ομαδοποίηση κατά έτος", - "haptic_feedback_switch": "Ενεργοποίηση απτικής ανάδρασης", - "haptic_feedback_title": "Απτική Ανάδραση", - "has_quota": "Έχει ποσόστωση", - "hash_asset": "Κατακερματισμός στοιχείου", - "hashed_assets": "Κατακερματισμένα στοιχεία", - "hashing": "Κατακερματισμός", - "header_settings_add_header_tip": "Προσθήκη header", - "header_settings_field_validator_msg": "Η τιμή δεν μπορεί να είναι κενή", - "header_settings_header_name_input": "Όνομα κεφαλίδας", - "header_settings_header_value_input": "Τιμή κεφαλίδας", - "headers_settings_tile_title": "Προσαρμοσμένες κεφαλίδες διακομιστή μεσολάβησης", - "height": "Ύψος", - "hi_user": "Γειά σου {name} {email}", - "hide_all_people": "Απόκρυψη όλων των ατόμων", - "hide_gallery": "Απόκρυψη γκαλερί", - "hide_named_person": "Απόκρυψη του ατόμου {name}", - "hide_password": "Απόκρυψη κωδικού πρόσβασης", - "hide_person": "Απόκρυψη ατόμου", - "hide_schema": "Απόκρυψη σχήματος", - "hide_text_recognition": "Απόκρυψη αναγνώρισης κειμένου", - "hide_unnamed_people": "Απόκρυψη ατόμων χωρίς όνομα", - "home_page_add_to_album_conflicts": "Προστέθηκαν {added} στοιχεία στο άλμπουμ {album}. {failed} στοιχεία υπάρχουν ήδη στο άλμπουμ.", - "home_page_add_to_album_err_local": "Δεν είναι ακόμη δυνατή η προσθήκη τοπικών στοιχείων σε άλμπουμ, παράβλεψη", - "home_page_add_to_album_success": "Προστέθηκαν {added} στοιχεία στο άλμπουμ {album}.", - "home_page_album_err_partner": "Δεν είναι δυνατή η προσθήκη στοιχείων συντρόφου σε ένα άλμπουμ ακόμα, παραλείπεται", - "home_page_archive_err_local": "Δεν είναι δυνατή η αρχειοθέτηση τοπικών στοιχείων ακόμα, παραλείπεται", - "home_page_archive_err_partner": "Δεν είναι δυνατή η αρχειοθέτηση στοιχείων συντρόφου, παραλείπεται", - "home_page_building_timeline": "Χτίζεται το χρονοδιάγραμμα", - "home_page_delete_err_partner": "Δεν είναι δυνατή η διαγραφή στοιχείων συντρόφου, παραλείπεται", - "home_page_delete_remote_err_local": "Τοπικά στοιχεία στη διαγραφή απομακρυσμένης επιλογής, παραλείπεται", - "home_page_favorite_err_local": "Δεν μπορώ ακόμα να αγαπήσω τα τοπικά στοιχεία, παραλείπεται", - "home_page_favorite_err_partner": "Δεν είναι ακόμα δυνατή η πρόσθεση στοιχείων συντρόφου στα αγαπημένα, παραλείπεται", - "home_page_first_time_notice": "Εάν αυτή είναι η πρώτη φορά που χρησιμοποιείτε την εφαρμογή, βεβαιωθείτε ότι έχετε επιλέξει ένα άλμπουμ αντίγραφου ασφαλείας, ώστε το χρονοδιάγραμμα να μπορεί να συμπληρώσει φωτογραφίες και βίντεο στα άλμπουμ", - "home_page_locked_error_local": "Δεν είναι δυνατή η μετακίνηση τοπικών στοιχείων στον κλειδωμένο φάκελο, παράβλεψη", - "home_page_locked_error_partner": "Δεν μπορείτε να μετακινήσετε τα στοιχεία συνεργάτη σε κλειδωμένο φάκελο, παράλειψη", - "home_page_share_err_local": "Δεν είναι δυνατή η κοινή χρήση τοπικών στοιχείων μέσω συνδέσμου, παραλείπεται", - "home_page_upload_err_limit": "Μπορείτε να ανεβάσετε μόνο 30 στοιχεία κάθε φορά, παραλείπεται", - "host": "Φιλοξενία", - "hour": "Ώρα", - "hours": "Ώρες", - "id": "ID", - "idle": "Αδράνεια", - "ignore_icloud_photos": "Αγνοήστε τις φωτογραφίες iCloud", - "ignore_icloud_photos_description": "Οι φωτογραφίες που είναι αποθηκευμένες στο iCloud δεν θα μεταφορτωθούν στον διακομιστή Immich", - "image": "Εικόνα", - "image_alt_text_date": "{isVideo, select, true {Βίντεο} other {Εικόνα}} που τραβήχτηκε στις {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Βίντεο} other {Εικόνα}} που τραβήχτηκε με τον/την {person1} στις {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Βίντεο} other {Εικόνα}} που τραβήχτηκε με τον/την {person1} και τον/την {person2} στις {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Βίντεο} other {Εικόνα}} που τραβήχτηκε με τον/την {person1}, {person2} και τον/την {person3} στις {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Βίντεο} other {Εικόνα}} τραβήχτηκε με τον/την {person1}, τον/την {person2} και άλλους {additionalCount, number} στις {date}", - "image_alt_text_date_place": "{isVideo, select, true {Βίντεο} other {Εικόνα}} τραβήχτηκε στον/στην {city}, {country} στις {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Βίντεο} other {Εικόνα}} τραβηγμένο στην/στον {city}, {country} με τον/την {person1} στις {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Βίντεο} other {Εικόνα}} τραβηγμένο στην/στον {city}, {country} με τον/την {person1} και τον/την {person2} στις {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Βίντεο} other {Εικόνα}} τραβηγμένο στην/στον {city}, {country} με τον/την {person1}, τον/την {person2} και τον/την {person3} στις {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Βίντεο} other {Εικόνα}} τραβηγμένο στον/στην {city}, {country} με τον/την {person1}, {person2}, και {additionalCount, number} άλλους στις {date}", - "image_saved_successfully": "Η εικόνα αποθηκεύτηκε", - "image_viewer_page_state_provider_download_started": "Ξεκίνησε Λήψη", - "image_viewer_page_state_provider_download_success": "Επιτυχία Λήψης", - "image_viewer_page_state_provider_share_error": "Σφάλμα Κοινής Χρήσης", - "immich_logo": "Λογότυπο Immich", - "immich_web_interface": "Ιστότοπος Immich", - "import_from_json": "Εισαγωγή από αρχείο JSON", - "import_path": "Εισαγωγή διαδρομής", - "in_albums": "Μέσα σε {count, plural, one {# άλμπουμ} other {# άλμπουμ}}", - "in_archive": "Μέσα στα αρχειοθετημένα", - "in_year": "Σε {year}", - "in_year_selector": "Σε", - "include_archived": "Συμπερίληψη αρχειοθετημένων", - "include_shared_albums": "Συμπερίληψη διαμοιρασμένων άλμπουμ", - "include_shared_partner_assets": "Συμπερίληψη των στοιχείων των συνεργατών που έχουν κοινοποιηθεί", - "individual_share": "Μεμονωμένος διαμοιρασμός", - "individual_shares": "Μεμονωμένες κοινοποιήσεις", - "info": "Πληροφορίες", - "interval": { - "day_at_onepm": "Κάθε μέρα στη 1μμ", - "hours": "Κάθε {hours, plural, one {ώρα} other {{hours, number} ώρες}}", - "night_at_midnight": "Κάθε βράδυ τα μεσάνυχτα", - "night_at_twoam": "Κάθε βράδυ στις 2πμ" - }, - "invalid_date": "Μη έγκυρη ημερομηνία", - "invalid_date_format": "Μη έγκυρη μορφή ημερομηνίας", - "invite_people": "Πρόσκληση Ατόμων", - "invite_to_album": "Πρόσκληση σε άλμπουμ", - "ios_debug_info_fetch_ran_at": "Η ανάκτηση εκτελέστηκε στις {dateTime}", - "ios_debug_info_last_sync_at": "Τελευταίος συγχρονισμός στις {dateTime}", - "ios_debug_info_no_processes_queued": "Δεν υπάρχουν διεργασίες παρασκηνίου σε ουρά", - "ios_debug_info_no_sync_yet": "Δεν έχει εκτελεστεί, για την ώρα, καμία εργασία συγχρονισμού στο παρασκήνιο", - "ios_debug_info_processes_queued": "{count, plural, one {{count} διεργασία παρασκηνίου σε αναμονή} other {{count} διεργασίες παρασκηνίου σε αναμονή}}", - "ios_debug_info_processing_ran_at": "Η επεξεργασία εκτελέστηκε στις {dateTime}", - "items_count": "{count, plural, one {# αντικείμενο} other {# αντικείμενα}}", - "jobs": "Εργασίες", - "json_editor": "Επεξεργαστής JSON", - "json_error": "Σφάλμα JSON", - "keep": "Διατήρηση", - "keep_all": "Διατήρηση Όλων", - "keep_favorites": "Διατήρηση αγαπημένων", - "keep_this_delete_others": "Διατήρηση αυτού, διαγραφή υπολοίπων", - "kept_this_deleted_others": "Διατηρήθηκε αυτό το στοιχείο και διαγράφηκε/καν {count, plural, one {# στοιχείο} other {# στοιχεία}}", - "keyboard_shortcuts": "Συντομεύσεις πληκτρολογίου", - "language": "Γλώσσα", - "language_no_results_subtitle": "Δοκιμάστε να αλλάξετε τον όρο αναζήτησης", - "language_no_results_title": "Δε βρέθηκαν γλώσσες", - "language_search_hint": "Αναζήτηση γλωσσών...", - "language_setting_description": "Επιλέξτε τη γλώσσα που προτιμάτε", - "large_files": "Μεγάλα Αρχεία", - "last": "Τελευταία", - "last_months": "{count, plural, one {Τον προηγούμενο μήνα} other {Τους τελευταίους # μήνες}}", - "last_seen": "Τελευταία προβολή", - "latest_version": "Τελευταία Έκδοση", - "latitude": "Γεωγραφικό πλάτος", - "leave": "Εγκατάλειψη", - "leave_album": "Αποχώρηση από το άλμπουμ", - "lens_model": "Μοντέλο φακού", - "let_others_respond": "Επέτρεψε σε άλλους να απαντήσουν", - "level": "Επίπεδο", - "library": "Βιβλιοθήκη", - "library_add_folder": "Προσθήκη φακέλου", - "library_edit_folder": "Επεξεργασία φακέλου", - "library_options": "Επιλογές βιβλιοθήκης", - "library_page_device_albums": "Άλμπουμ στη Συσκευή", - "library_page_new_album": "Νέο άλμπουμ", - "library_page_sort_asset_count": "Αριθμός στοιχείων", - "library_page_sort_created": "Ημερομηνία δημιουργίας", - "library_page_sort_last_modified": "Τελευταία τροποποίηση", - "library_page_sort_title": "Τίτλος άλμπουμ", - "licenses": "Άδειες", - "light": "Φωτεινό", - "like": "Μου αρέσει", - "like_deleted": "Το \"μου αρέσει\" διαγράφηκε", - "link_motion_video": "Σύνδεσε βίντεο κίνησης", - "link_to_oauth": "Σύνδεση στον OAuth", - "linked_oauth_account": "Ο OAuth λογαριασμός συνδέθηκε", - "list": "Λίστα", - "loading": "Φόρτωση", - "loading_search_results_failed": "Η φόρτωση αποτελεσμάτων αναζήτησης απέτυχε", - "local": "Τοπικά", - "local_asset_cast_failed": "Αδυναμία μετάδοσης στοιχείου που δεν έχει ανέβει στον διακομιστή", - "local_assets": "Τοπικά στοιχεία", - "local_id": "Τοπικό αναγνωριστικό", - "local_media_summary": "Περίληψη τοπικών πολυμέσων", - "local_network": "Τοπικό δίκτυο", - "local_network_sheet_info": "Η εφαρμογή θα συνδεθεί με τον διακομιστή μέσω αυτού του URL όταν χρησιμοποιείται το καθορισμένο δίκτυο Wi-Fi", - "location": "Τοποθεσία", - "location_permission": "Άδεια τοποθεσίας", - "location_permission_content": "Για να χρησιμοποιηθεί η λειτουργία αυτόματης εναλλαγής, το Immich χρειάζεται άδεια για την ακριβή τοποθεσία της συσκευής ώστε να μπορεί να διαβάζει το όνομα του τρέχοντος δικτύου Wi-Fi", - "location_picker_choose_on_map": "Επιλέξτε στο χάρτη", - "location_picker_latitude_error": "Εισαγάγετε ένα έγκυρο γεωγραφικό πλάτος", - "location_picker_latitude_hint": "Εισαγάγετε το γεωγραφικό πλάτος σας εδώ", - "location_picker_longitude_error": "Εισαγάγετε ένα έγκυρο γεωγραφικό μήκος", - "location_picker_longitude_hint": "Εισαγάγετε εδώ το γεωγραφικό σας μήκος", - "lock": "Κλείδωμα", - "locked_folder": "Κλειδωμένος φάκελος", - "log_detail_title": "Λεπτομέρεια καταγραφής", - "log_out": "Αποσύνδεση", - "log_out_all_devices": "Αποσύνδεση από Όλες τις Συσκευές", - "logged_in_as": "Συνδεδεμένος ως {user}", - "logged_out_all_devices": "Όλες οι συσκευές αποσυνδέθηκαν", - "logged_out_device": "Αποσυνδεδεμένη συσκευή", - "login": "Είσοδος", - "login_disabled": "Η σύνδεση έχει απενεργοποιηθεί", - "login_form_api_exception": "Εξαίρεση API. Ελέγξτε τη διεύθυνση URL του διακομιστή και δοκιμάστε ξανά.", - "login_form_back_button_text": "Πίσω", - "login_form_email_hint": "to-email-sou@email.com", - "login_form_endpoint_hint": "http://ip-tou-server-sou:porta", - "login_form_endpoint_url": "URL τελικού σημείου διακομιστή", - "login_form_err_http": "Προσδιορίστε http:// ή https://", - "login_form_err_invalid_email": "Μη έγκυρο email", - "login_form_err_invalid_url": "Μη έγκυρη διεύθυνση URL", - "login_form_err_leading_whitespace": "Κενό διάστημα στην αρχή", - "login_form_err_trailing_whitespace": "Κενό διάστημα στο τέλος", - "login_form_failed_get_oauth_server_config": "Σφάλμα καταγραφής χρησιμοποιώντας το OAuth, ελέγξτε τη διεύθυνση URL του διακομιστή", - "login_form_failed_get_oauth_server_disable": "Η δυνατότητα OAuth δεν είναι διαθέσιμη σε αυτόν τον διακομιστή", - "login_form_failed_login": "Σφάλμα κατά τη σύνδεσή σας, ελέγξτε τη διεύθυνση URL του διακομιστή, το email και τον κωδικό πρόσβασης", - "login_form_handshake_exception": "Υπήρξε σφάλμα χειραψίας με τον διακομιστή. Ενεργοποιήστε την υποστήριξη αυτο-υπογεγραμμένου πιστοποιητικού στις ρυθμίσεις εάν χρησιμοποιείτε αυτο-υπογεγραμμένο πιστοποιητικό.", - "login_form_password_hint": "κωδικός πρόσβασης", - "login_form_save_login": "Μείνετε συνδεδεμένοι", - "login_form_server_empty": "Εισαγάγετε μια διεύθυνση URL διακομιστή.", - "login_form_server_error": "Δεν ήταν δυνατή η σύνδεση με τον διακομιστή.", - "login_has_been_disabled": "Η σύνδεση έχει απενεργοποιηθεί.", - "login_password_changed_error": "Παρουσιάστηκε σφάλμα κατά την ενημέρωση του κωδικού πρόσβασής σας", - "login_password_changed_success": "Ο κωδικός πρόσβασης ενημερώθηκε με επιτυχία", - "logout_all_device_confirmation": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε από όλες τις συσκευές;", - "logout_this_device_confirmation": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε από αυτήν τη συσκευή;", - "logs": "Καταγραφές", - "longitude": "Γεωγραφικό μήκος", - "look": "Εμφάνιση", - "loop_videos": "Επανάληψη βίντεο", - "loop_videos_description": "Ενεργοποιήστε την αυτόματη επανάληψη ενός βίντεο στο πρόγραμμα προβολής λεπτομερειών.", - "main_branch_warning": "Χρησιμοποιείτε μια έκδοση σε ανάπτυξη· συνιστούμε ανεπιφύλακτα τη χρήση μιας τελικής έκδοσης!", - "main_menu": "Κύριο μενού", - "maintenance_action_restore": "Επαναφορά βάσης δεδομένων", - "maintenance_description": "Το Immich έχει τεθεί σε λειτουργία συντήρησης.", - "maintenance_end": "Τερματισμός λειτουργίας συντήρησης", - "maintenance_end_error": "Αποτυχία τερματισμού της λειτουργίας συντήρησης.", - "maintenance_logged_in_as": "Αυτήν τη στιγμή είστε συνδεδεμένος ως {user}", - "maintenance_restore_from_backup": "Επαναφορά από αντίγραφο ασφαλείας", - "maintenance_restore_library": "Επαναφορά της βιβλιοθήκης σας", - "maintenance_restore_library_confirm": "Αν όλα φαίνονται σωστά, προχωρήστε στην επαναφορά του αντιγράφου ασφαλείας!", - "maintenance_restore_library_description": "Επαναφορά βάσης δεδομένων", - "maintenance_restore_library_folder_has_files": "{folder} έχει {count} φάκελο(ους)", - "maintenance_restore_library_folder_no_files": "Στο φάκελο {folder} λείπουν αρχεία!", - "maintenance_restore_library_folder_pass": "αναγνώσιμο και εγγράψιμο", - "maintenance_restore_library_folder_read_fail": "μη αναγνώσιμο", - "maintenance_restore_library_folder_write_fail": "μη εγγράψιμο", - "maintenance_restore_library_hint_missing_files": "Μπορεί να λείπουν σημαντικά αρχεία", - "maintenance_restore_library_hint_regenerate_later": "Μπορείτε να τα επαναδημιουργήσετε αργότερα στις ρυθμίσεις", - "maintenance_restore_library_hint_storage_template_missing_files": "Χρήση πρότυπου αποθήκευσης; Μπορεί να λείπουν αρχεία", - "maintenance_restore_library_loading": "Φόρτωση ελέγχων ακεραιότητας και έξυπνων ελέγχων…", - "maintenance_task_backup": "Δημιουργία αντιγράφου ασφαλείας της υπάρχουσας βάσης δεδομένων…", - "maintenance_task_migrations": "Εκτέλεση μετατροπών/ενημερώσεων βάσης δεδομένων…", - "maintenance_task_restore": "Επαναφορά του επιλεγμένου αντιγράφου ασφαλείας…", - "maintenance_task_rollback": "Η επαναφορά απέτυχε, επιστροφή στην προηγούμενη κατάσταση…", - "maintenance_title": "Προσωρινά μη διαθέσιμο", - "make": "Κατασκευαστής", - "manage_geolocation": "Διαχείριση τοποθεσίας", - "manage_media_access_rationale": "Αυτή η άδεια απαιτείται για τη σωστή μετακίνηση των στοιχείων στον κάδο και την επαναφορά τους από αυτόν.", - "manage_media_access_settings": "Άνοιγμα ρυθμίσεων", - "manage_media_access_subtitle": "Επίτρεψτε στην εφαρμογή Immich να διαχειρίζεται και να μετακινεί αρχεία πολυμέσων.", - "manage_media_access_title": "Πρόσβαση Διαχείρισης Πολυμέσων", - "manage_shared_links": "Διαχείριση κοινόχρηστων συνδέσμων", - "manage_sharing_with_partners": "Διαχειριστείτε την κοινή χρήση με συνεργάτες", - "manage_the_app_settings": "Διαχειριστείτε τις ρυθμίσεις της εφαρμογής", - "manage_your_account": "Διαχειριστείτε τον λογαριασμό σας", - "manage_your_api_keys": "Διαχειριστείτε τα κλειδιά API", - "manage_your_devices": "Διαχειριστείτε τις συνδεδεμένες συσκευές σας", - "manage_your_oauth_connection": "Διαχειριστείτε τη σύνδεσή σας OAuth", - "map": "Χάρτης", - "map_assets_in_bounds": "{count, plural, =0 {Καμία φωτογραφία σε αυτή την περιοχή} one {# φωτογραφία} other {# φωτογραφίες}}", - "map_cannot_get_user_location": "Δεν είναι δυνατή η λήψη της τοποθεσίας του χρήστη", - "map_location_dialog_yes": "Ναι", - "map_location_picker_page_use_location": "Χρησιμοποιήστε αυτήν την τοποθεσία", - "map_location_service_disabled_content": "Η υπηρεσία τοποθεσίας πρέπει να είναι ενεργοποιημένη για την εμφάνιση στοιχείων από την τρέχουσα τοποθεσία σας. Θέλετε να το ενεργοποιήσετε τώρα;", - "map_location_service_disabled_title": "Η υπηρεσία τοποθεσίας απενεργοποιήθηκε", - "map_marker_for_images": "Δείκτης χάρτη για εικόνες που τραβήχτηκαν σε {city}, {country}", - "map_marker_with_image": "Χάρτης δείκτη με εικόνα", - "map_no_location_permission_content": "Απαιτείται άδεια τοποθεσίας για την εμφάνιση στοιχείων από την τρέχουσα τοποθεσία σας. Θέλετε να το επιτρέψετε τώρα;", - "map_no_location_permission_title": "Η άδεια τοποθεσίας απορρίφθηκε", - "map_settings": "Ρυθμίσεις χάρτη", - "map_settings_dark_mode": "Σκοτεινή λειτουργία", - "map_settings_date_range_option_day": "Προηγούμενες 24 ώρες", - "map_settings_date_range_option_days": "Προηγούμενες {days} ημέρες", - "map_settings_date_range_option_year": "Προηγούμενο έτος", - "map_settings_date_range_option_years": "Προηγούμενα {years} έτη", - "map_settings_dialog_title": "Ρυθμίσεις Χάρτη", - "map_settings_include_show_archived": "Συμπεριλάβετε Αρχειοθετημένα", - "map_settings_include_show_partners": "Συμπεριλάβετε Συντρόφους", - "map_settings_only_show_favorites": "Εμφάνιση μόνο αγαπημένων", - "map_settings_theme_settings": "Θέμα χάρτη", - "map_zoom_to_see_photos": "Σμικρύνετε για να δείτε φωτογραφίες", - "mark_all_as_read": "Επισήμανση όλων ως αναγνωσμένα", - "mark_as_read": "Επισήμανση ως αναγνωσμένο", - "marked_all_as_read": "Όλα επισημάνθηκαν ως αναγνωσμένα", - "matches": "Αντιστοιχίες", - "matching_assets": "Αντιστοιχία στοιχείων", - "media_type": "Τύπος πολυμέσου", - "memories": "Αναμνήσεις", - "memories_all_caught_up": "Συγχρονισμένα", - "memories_check_back_tomorrow": "Ελέγξτε ξανά αύριο για περισσότερες αναμνήσεις", - "memories_setting_description": "Διαχειριστείτε τι θα εμφανίζεται στις αναμνήσεις σας", - "memories_start_over": "Ξεκινήστε από την αρχή", - "memories_swipe_to_close": "Σύρετε προς τα πάνω για να κλείσετε", - "memory": "Ανάμνηση", - "memory_lane_title": "Διαδρομή Αναμνήσεων {title}", - "menu": "Μενού", - "merge": "Συγχώνευση", - "merge_people": "Συγχώνευση ατόμων", - "merge_people_limit": "Μπορείτε να συγχωνεύσετε μόνο έως και 5 πρόσωπα τη φορά", - "merge_people_prompt": "Θέλετε να συγχωνεύσετε αυτά τα άτομα; Αυτή η ενέργεια είναι μη αναστρέψιμη.", - "merge_people_successfully": "Τα άτομα συγχωνεύθηκαν με επιτυχία", - "merged_people_count": "Έγινε συγχώνευση {count, plural, one {# ατόμου} other {# ατόμων}}", - "minimize": "Ελαχιστοποίηση", - "minute": "Λεπτό", - "minutes": "Λεπτά", - "mirror_horizontal": "Οριζόντια", - "mirror_vertical": "Κάθετα", - "missing": "Όσα Λείπουν", - "mobile_app": "Εφαρμογή για κινητά", - "mobile_app_download_onboarding_note": "Κατέβασε την συνοδευτική εφαρμογή για κινητά χρησιμοποιώντας τις παρακάτω επιλογές", - "model": "Μοντέλο", - "month": "Μήνας", - "monthly_title_text_date_format": "ΜΜΜΜ y", - "more": "Περισσότερα", - "move": "Μετακίνηση", - "move_down": "Μετακίνηση προς τα κάτω", - "move_off_locked_folder": "Μετακίνηση έξω από τον κλειδωμένο φάκελο", - "move_to": "Μετακίνηση σε", - "move_to_device_trash": "Μετακίνηση στον κάδο της συσκευής", - "move_to_lock_folder_action_prompt": "Προστέθηκαν {count} στον κλειδωμένο φάκελο", - "move_to_locked_folder": "Μετακίνηση σε κλειδωμένο φάκελο", - "move_to_locked_folder_confirmation": "Αυτές οι φωτογραφίες και τα βίντεο θα αφαιρεθούν από όλα τα άλμπουμ και θα μπορούν να προβληθούν μόνο από τον κλειδωμένο φάκελο", - "move_up": "Μετακίνηση προς τα πάνω", - "moved_to_archive": "Μετακινήθηκαν {count, plural, one {# στοιχείο} other {# στοιχεία}} στο αρχείο", - "moved_to_library": "Μετακινήθηκε/αν {count, plural, one {# στοιχείο} other {# στοιχεία}} στη βιβλιοθήκη", - "moved_to_trash": "Μετακινήθηκε στον κάδο απορριμμάτων", - "multiselect_grid_edit_date_time_err_read_only": "Δεν είναι δυνατή η επεξεργασία της ημερομηνίας των στοιχείων μόνο για ανάγνωση, παραλείπεται", - "multiselect_grid_edit_gps_err_read_only": "Δεν είναι δυνατή η επεξεργασία της τοποθεσίας των στοιχείων μόνο για ανάγνωση, παραλείπεται", - "mute_memories": "Σίγαση Αναμνήσεων", - "my_albums": "Τα άλμπουμ μου", - "name": "Όνομα", - "name_or_nickname": "Όνομα ή ψευδώνυμο", - "name_required": "Απαιτείται όνομα", - "navigate": "Πλοηγηθείτε", - "navigate_to_time": "Πλοηγηθείτε στο Χρόνο", - "network_requirement_photos_upload": "Χρήση δεδομένων κινητής τηλεφωνίας για τη δημιουργία αντιγράφων ασφαλείας των φωτογραφιών", - "network_requirement_videos_upload": "Χρήση δεδομένων κινητής τηλεφωνίας για τη δημιουργία αντιγράφων ασφαλείας των βίντεο", - "network_requirements": "Απαιτήσεις Δυκτίου", - "network_requirements_updated": "Οι απαιτήσεις δικτύου άλλαξαν, γίνεται επαναφορά της ουράς αντιγράφων ασφαλείας", - "networking_settings": "Δικτύωση", - "networking_subtitle": "Διαχείριση ρυθμίσεων τελικών σημείων διακομιστή", - "never": "Ποτέ", - "new_album": "Νέο Άλμπουμ", - "new_api_key": "Νέο API Key", - "new_date_range": "Εύρος νέας ημερομηνίας", - "new_password": "Νέος κωδικός πρόσβασης", - "new_person": "Νέο άτομο", - "new_pin_code": "Νέος κωδικός PIN", - "new_pin_code_subtitle": "Αυτή είναι η πρώτη φορά που αποκτάτε πρόσβαση στον κλειδωμένο φάκελο. Δημιουργήστε έναν κωδικό PIN για ασφαλή πρόσβαση σε αυτή τη σελίδα", - "new_timeline": "Νέο Χρονολόγιο", - "new_update": "Νέα ενημέρωση", - "new_user_created": "Ο νέος χρήστης δημιουργήθηκε", - "new_version_available": "ΔΙΑΘΕΣΙΜΗ ΝΕΑ ΕΚΔΟΣΗ", - "newest_first": "Τα νεότερα πρώτα", - "next": "Επόμενο", - "next_memory": "Επόμενη ανάμνηση", - "no": "Όχι", - "no_actions_added": "Δεν έχουν προστεθεί ακόμα ενέργειες", - "no_albums_message": "Δημιουργήστε ένα άλμπουμ για να οργανώσετε τις φωτογραφίες και τα βίντεό σας", - "no_albums_with_name_yet": "Φαίνεται ότι δεν έχετε κανένα άλμπουμ με αυτό το όνομα ακόμα.", - "no_albums_yet": "Φαίνεται ότι δεν έχετε κανένα άλμπουμ ακόμα.", - "no_archived_assets_message": "Αρχειοθετήστε φωτογραφίες και βίντεο για να τα αποκρύψετε από την Προβολή Φωτογραφιών", - "no_assets_message": "Κλικάρετε για να ανεβάσετε την πρώτη σας φωτογραφία", - "no_assets_to_show": "Δεν υπάρχουν στοιχεία προς εμφάνιση", - "no_cast_devices_found": "Δε βρέθηκαν συσκευές μετάδοσης", - "no_checksum_local": "Δεν υπάρχει διαθέσιμο checksum για έλεγχο ακεραιότητας – δεν μπορούν να ανακτηθούν τα τοπικά στοιχεία", - "no_checksum_remote": "Δεν υπάρχει διαθέσιμο checksum για έλεγχο ακεραιότητας – δεν μπορούν να ανακτηθούν τα απομακρυσμένα στοιχεία", - "no_configuration_needed": "Δεν απαιτείται ρύθμιση", - "no_devices": "Δεν υπάρχουν εξουσιοδοτημένες συσκευές", - "no_duplicates_found": "Δεν βρέθηκαν διπλότυπα.", - "no_exif_info_available": "Καμία πληροφορία exif διαθέσιμη", - "no_explore_results_message": "Ανεβάστε περισσότερες φωτογραφίες για να περιηγηθείτε στη συλλογή σας.", - "no_favorites_message": "Προσθέστε αγαπημένα για να βρείτε γρήγορα τις καλύτερες φωτογραφίες και τα βίντεό σας", - "no_filters_added": "Δεν έχουν προστεθεί ακόμα φίλτρα", - "no_libraries_message": "Δημιουργήστε μια εξωτερική βιβλιοθήκη για να προβάλετε τις φωτογραφίες και τα βίντεό σας", - "no_local_assets_found": "Δεν βρέθηκαν τοπικά στοιχεία με αυτό το checksum", - "no_location_set": "Η τοποθεσία δεν έχει οριστεί", - "no_locked_photos_message": "Οι φωτογραφίες και τα βίντεο στον κλειδωμένο φάκελο, είναι κρυμμένες και δεν θα εμφανίζονται κατά την περιήγηση ή την αναζήτηση στη βιβλιοθήκη σας.", - "no_name": "Χωρίς Όνομα", - "no_notifications": "Καμία ειδοποίηση", - "no_people_found": "Δεν βρέθηκαν άτομα που να ταιριάζουν", - "no_places": "Καμία τοποθεσία", - "no_remote_assets_found": "Δεν βρέθηκαν απομακρυσμένα στοιχεία με αυτό το checksum", - "no_results": "Κανένα αποτέλεσμα", - "no_results_description": "Δοκιμάστε ένα συνώνυμο ή πιο γενική λέξη-κλειδί", - "no_shared_albums_message": "Δημιουργήστε ένα άλμπουμ για να μοιράζεστε φωτογραφίες και βίντεο με άτομα στο δίκτυό σας", - "no_uploads_in_progress": "Καμία μεταφόρτωση σε εξέλιξη", - "not_allowed": "Δεν επιτρέπεται", - "not_available": "Μ/Δ (Μη Διαθέσιμο)", - "not_in_any_album": "Σε κανένα άλμπουμ", - "not_selected": "Δεν επιλέχθηκε", - "note_apply_storage_label_to_previously_uploaded assets": "Σημείωση: Για να εφαρμόσετε την Ετικέτα Αποθήκευσης σε στοιχεία που έχουν μεταφορτωθεί προηγουμένως, εκτελέστε το", - "notes": "Σημειώσεις", - "nothing_here_yet": "Τίποτα εδώ ακόμα", - "notification_permission_dialog_content": "Για να ενεργοποιήσετε τις ειδοποιήσεις, μεταβείτε στις Ρυθμίσεις και επιλέξτε να επιτρέπεται.", - "notification_permission_list_tile_content": "Παραχωρήστε άδεια για ενεργοποίηση ειδοποιήσεων.", - "notification_permission_list_tile_enable_button": "Ενεργοποίηση Ειδοποιήσεων", - "notification_permission_list_tile_title": "Άδεια Ειδοποίησης", - "notification_toggle_setting_description": "Ενεργοποίηση ειδοποιήσεων μέσω email", - "notifications": "Ειδοποιήσεις", - "notifications_setting_description": "Διαχείριση ειδοποιήσεων", - "oauth": "OAuth", - "obtainium_configurator": "Ρυθμιστής Obtainium", - "obtainium_configurator_instructions": "Χρησιμοποίησε το Obtainium για να εγκαταστήσεις και να ενημερώσεις απευθείας την εφαρμογή Android από τα κυκλοφορίες του Immich στο GitHub. Δημιούργησε ένα API key και επέλεξε μια παραλλαγή για να δημιουργήσεις το σύνδεσμο ρύθμισης του Obtainium", - "ocr": "OCR", - "official_immich_resources": "Επίσημοι Πόροι του Immich", - "offline": "Εκτός σύνδεσης", - "offset": "Μετατόπιση", - "ok": "Έγινε", - "oldest_first": "Τα παλαιότερα πρώτα", - "on_this_device": "Σε αυτή τη συσκευή", - "onboarding": "Οδηγός εκκίνησης", - "onboarding_locale_description": "Επιλέξτε την γλώσσα που προτιμάτε. Μπορείτε να την αλλάξετε αργότερα από τις ρυθμίσεις.", - "onboarding_privacy_description": "Οι παρακάτω (προαιρετικές) λειτουργίες βασίζονται σε εξωτερικές υπηρεσίες και μπορούν να απενεργοποιηθούν ανά πάσα στιγμή από τις ρυθμίσεις.", - "onboarding_server_welcome_description": "Ας ξεκινήσουμε με μερικές συνηθισμένες ρυθμίσεις.", - "onboarding_theme_description": "Επιλέξτε ένα θέμα χρώματος για το προφίλ σας. Μπορείτε να το αλλάξετε αργότερα στις ρυθμίσεις σας.", - "onboarding_user_welcome_description": "Ας ξεκινήσουμε!", - "onboarding_welcome_user": "Καλωσόρισες, {user}", - "online": "Σε σύνδεση", - "only_favorites": "Μόνο αγαπημένα", - "open": "Άνοιγμα", - "open_in_map_view": "Άνοιγμα σε προβολή χάρτη", - "open_in_openstreetmap": "Άνοιγμα στο OpenStreetMap", - "open_the_search_filters": "Ανοίξτε τα φίλτρα αναζήτησης", - "options": "Επιλογές", - "or": "ή", - "organize_into_albums": "Οργάνωση σε άλμπουμ", - "organize_into_albums_description": "Τοποθετείστε τις υπάρχουσες φωτογραφίες σε άλμπουμ χρησιμοποιώντας τις τρέχουσες ρυθμίσεις συγχρονισμού", - "organize_your_library": "Οργανώστε τη βιβλιοθήκη σας", - "original": "πρωτότυπο", - "other": "Άλλες", - "other_devices": "Άλλες συσκευές", - "other_entities": "Άλλες οντότητες", - "other_variables": "Άλλες μεταβλητές", - "owned": "Δικά μου", - "owner": "Κάτοχος", - "page": "Σελίδα", - "partner": "Συνεργάτης", - "partner_can_access": "Ο χρήστης {partner} έχει πρόσβαση", - "partner_can_access_assets": "Όλες οι φωτογραφίες και τα βίντεό σας εκτός από αυτά που βρίσκονται στο Αρχείο και τα Διεγραμμένα", - "partner_can_access_location": "Η τοποθεσία όπου τραβήχτηκαν οι φωτογραφίες σας", - "partner_list_user_photos": "Φωτογραφίες του/της {user}", - "partner_list_view_all": "Προβολή όλων", - "partner_page_empty_message": "Οι φωτογραφίες σας δεν διαμοιράζονται ακόμα με κανέναν.", - "partner_page_no_more_users": "Δεν υπάρχουν άλλοι χρήστες για προσθήκη", - "partner_page_partner_add_failed": "Αποτυχία προσθήκης συντρόφου", - "partner_page_select_partner": "Επιλογή συντρόφου", - "partner_page_shared_to_title": "Διαμοιράζεται με", - "partner_page_stop_sharing_content": "Ο/Η {partner} δεν θα μπορεί πλέον να δει τις φωτογραφίες σας.", - "partner_sharing": "Κοινή Χρήση Συνεργατών", - "partners": "Συνεργάτες", - "password": "Κωδικός Πρόσβασης", - "password_does_not_match": "Ο κωδικός πρόσβασης δεν ταιριάζει", - "password_required": "Απαιτείται Κωδικός Πρόσβασης", - "password_reset_success": "Επιτυχής επαναφορά κωδικού πρόσβασης", - "past_durations": { - "days": "Περασμένη/νες {days, plural, one {ημέρα} other {# ημέρες}}", - "hours": "Περασμένη/νες {hours, plural, one {ώρα} other {# ώρες}}", - "years": "Περασμένος/να {years, plural, one {έτος} other {# έτη}}" - }, - "path": "Διαδρομή", - "pattern": "Μοτίβο", - "pause": "Πάυση", - "pause_memories": "Παύση αναμνήσεων", - "paused": "Σε Πάυση", - "pending": "Εκκρεμεί", - "people": "Άτομα", - "people_edits_count": "Έγινε επεξεργασία {count, plural, one {# ατόμου} other {# ατόμων}}", - "people_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο ομαδοποιημένα ανά άτομο", - "people_selected": "{count, plural, one {# άτομο επιλέχθηκε} other {# άτομα επιλέχθηκαν}}", - "people_sidebar_description": "Εμφάνιση Ατόμων στην πλαϊνή γραμμή", - "permanent_deletion_warning": "Προειδοποίηση οριστικής διαγραφής", - "permanent_deletion_warning_setting_description": "Εμφάνιση προειδοποίησης κατά την οριστική διαγραφή στοιχείων", - "permanently_delete": "Οριστική διαγραφή", - "permanently_delete_assets_count": "Οριστική διαγραφή {count, plural, one {στοιχείου} other {στοιχείων}}", - "permanently_delete_assets_prompt": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά {count, plural, one {αυτό το στοιχείο;} other {αυτά τα # στοιχεία;}} Αυτό θα {count, plural, one {το} other {τα}} αφαιρέσει επίσης από τα άλμπουμ στα οποία {count, plural, one {ανήκει} other {ανήκουν}} .", - "permanently_deleted_asset": "Οριστικά διαγραμμένο στοιχείο", - "permanently_deleted_assets_count": "Οριστική διαγραφή {count, plural, one {# στοιχείου} other {# στοιχείων}}", - "permission": "Άδεια", - "permission_empty": "Η άδειά σου δεν πρέπει να είναι κενή", - "permission_onboarding_back": "Πίσω", - "permission_onboarding_continue_anyway": "Συνέχεια", - "permission_onboarding_get_started": "Ξεκινήστε", - "permission_onboarding_go_to_settings": "Μεταβείτε στις ρυθμίσεις", - "permission_onboarding_permission_denied": "Η άδεια απορρίφθηκε. Για να χρησιμοποιήσετε το Immich, παραχωρήστε δικαιώματα φωτογραφίας και βίντεο στις Ρυθμίσεις.", - "permission_onboarding_permission_granted": "Δόθηκε άδεια! Είστε έτοιμοι.", - "permission_onboarding_permission_limited": "Περιορισμένη άδεια. Για να επιτρέψετε στο Immich να δημιουργεί αντίγραφα ασφαλείας και να διαχειρίζεται ολόκληρη τη συλλογή σας, παραχωρήστε άδειες φωτογραφιών και βίντεο στις Ρυθμίσεις.", - "permission_onboarding_request": "Το Immich απαιτεί άδεια πρόσβασεις στις φωτογραφίες και τα βίντεό σας.", - "person": "Άτομο", - "person_age_months": "{months, plural, one {# μήνας} other {# μήνες}} παλιά", - "person_age_year_months": "1 χρόνος, {months, plural, one {# μήνας} other {# μήνες}} παλιά", - "person_age_years": "{years, plural, other {# χρόνια}} παλιά", - "person_birthdate": "Γεννηθείς στις {date}", - "person_hidden": "{name}{hidden, select, true { (κρυφό)} other {}}", - "person_recognized": "Άτομο αναγνωρίστηκε", - "person_selected": "Άτομο επιλέχθηκε", - "photo_shared_all_users": "Φαίνεται ότι μοιραστήκατε τις φωτογραφίες σας με όλους τους χρήστες ή δεν έχετε κανέναν χρήστη για κοινή χρήση.", - "photos": "Φωτογραφίες", - "photos_and_videos": "Φωτογραφίες & Βίντεο", - "photos_count": "{count, plural, one {{count, number} Φωτογραφία} other {{count, number} Φωτογραφίες}}", - "photos_from_previous_years": "Φωτογραφίες προηγούμενων ετών", - "photos_only": "Μόνο φωτογραφίες", - "pick_a_location": "Επιλέξτε μια τοποθεσία", - "pick_custom_range": "Προσαρμοσμένο εύρος", - "pick_date_range": "Επιλέξτε εύρος ημερομηνιών", - "pin_code_changed_successfully": "Επιτυχής αλλαγή κωδικού PIN", - "pin_code_reset_successfully": "Επιτυχής επαναφορά κωδικού PIN", - "pin_code_setup_successfully": "Επιτυχής ρύθμιση κωδικού PIN", - "pin_verification": "Επιβεβαίωση κωδικού PIN", - "place": "Μέρος", - "places": "Τοποθεσίες", - "places_count": "{count, plural, one {{count} Τοποθεσία} other {{count} Τοποθεσίες}}", - "play": "Αναπαραγωγή", - "play_memories": "Αναπαραγωγή αναμνήσεων", - "play_motion_photo": "Αναπαραγωγή Κινούμενης Φωτογραφίας", - "play_or_pause_video": "Αναπαραγωγή ή παύση βίντεο", - "play_original_video": "Αναπαραγωγή αρχικού βίντεο", - "play_original_video_setting_description": "Προτίμησε την αναπαραγωγή των αρχικών βίντεο αντί των μετακωδικοποιημένων. Αν το αρχικό αρχείο δεν είναι συμβατό, ίσως να μην αναπαραχθεί σωστά.", - "play_transcoded_video": "Αναπαραγωγή μετακωδικοποιημένου βίντεο", - "please_auth_to_access": "Παρακαλώ πιστοποιηθείτε για να αποκτήσετε πρόσβαση", - "port": "Θύρα", - "preferences_settings_subtitle": "Διαχειριστείτε τις προτιμήσεις της εφαρμογής", - "preferences_settings_title": "Προτιμήσεις", - "preparing": "Προετοιμασία", - "preset": "Προκαθορισμένη ρύθμιση", - "preview": "Προεπισκόπηση", - "previous": "Προηγούμενο", - "previous_memory": "Προηγούμενη ανάμνηση", - "previous_or_next_day": "Ημέρα μπροστά/πίσω", - "previous_or_next_month": "Μήνας μπροστά/πίσω", - "previous_or_next_photo": "Φωτογραφία μπροστά/πίσω", - "previous_or_next_year": "Έτος μπροστά/πίσω", - "primary": "Πρωτεύων", - "privacy": "Ιδιωτικότητα", - "profile": "Προφίλ", - "profile_drawer_app_logs": "Καταγραφές", - "profile_drawer_client_server_up_to_date": "Ο πελάτης και ο διακομιστής είναι ενημερωμένοι", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Η λειτουργία μόνο-για-ανάγνωση ενεργοποιήθηκε. Κρατήστε πατημένο το εικονίδιο του χρήστη για απενεργοποίηση.", - "profile_image_of_user": "Εικόνα προφίλ του χρήστη {user}", - "profile_picture_set": "Ορισμός εικόνας προφίλ.", - "public_album": "Δημόσιο άλμπουμ", - "public_share": "Δημόσια Κοινή Χρήση", - "purchase_account_info": "Υποστηρικτής", - "purchase_activated_subtitle": "Σας ευχαριστούμε για την υποστήριξη του Immich και λογισμικών ανοιχτού κώδικα", - "purchase_activated_time": "Ενεργοποιήθηκε στις {date}", - "purchase_activated_title": "Το κλειδί σας ενεργοποιήθηκε με επιτυχία", - "purchase_button_activate": "Ενεργοποίηση", - "purchase_button_buy": "Αγορά", - "purchase_button_buy_immich": "Αγορά Immich", - "purchase_button_never_show_again": "Να μην εμφανιστεί ποτέ ξανά", - "purchase_button_reminder": "Υπενθύμιση σε 30 μέρες", - "purchase_button_remove_key": "Αφαίρεση κλειδιού", - "purchase_button_select": "Επιλέξτε", - "purchase_failed_activation": "Η ενεργοποίηση απέτυχε! Ελέγξτε το email σας για το σωστό κλειδί προϊόντος!", - "purchase_individual_description_1": "Για ένα άτομο", - "purchase_individual_description_2": "Κατάσταση υποστηρικτή", - "purchase_individual_title": "Ατομο", - "purchase_input_suggestion": "Έχετε ένα κλειδί προϊόντος; Εισαγάγετε το κλειδί παρακάτω", - "purchase_license_subtitle": "Αγοράστε το Immich για να υποστηρίξετε τη συνεχή ανάπτυξη της υπηρεσίας", - "purchase_lifetime_description": "Αγορά εφ' όρου ζωής", - "purchase_option_title": "ΕΠΙΛΟΓΕΣ ΑΓΟΡΑΣ", - "purchase_panel_info_1": "Η ανάπτυξη του Immich απαιτεί πολύ χρόνο και προσπάθεια, και έχουμε μηχανικούς πλήρους απασχόλησης που εργάζονται σε αυτό για να το κάνουμε όσο το δυνατόν καλύτερο. Η αποστολή μας είναι το λογισμικό ανοιχτού κώδικα και οι ηθικές επιχειρηματικές πρακτικές να γίνουν βιώσιμη πηγή εισοδήματος για προγραμματιστές και να δημιουργήσουμε ένα οικοσύστημα που σέβεται το απόρρητο, με πραγματικές εναλλακτικές λύσεις στις υπηρεσίες cloud που παρουσιάζουν συμπεριφορές εκμετάλλευσης.", - "purchase_panel_info_2": "Καθώς δεσμευόμαστε να μην προσθέσουμε φραγμούς με σκοπό το κέρδος, αυτή η αγορά δεν θα σας προσφέρει πρόσθετες δυνατότητες στο Immich. Βασιζόμαστε σε χρήστες όπως εσείς για την υποστήριξη της συνεχούς ανάπτυξης του Immich.", - "purchase_panel_title": "Υποστηρίξτε το πρότζεκτ", - "purchase_per_server": "Ανά διακομιστή", - "purchase_per_user": "Ανά χρήστη", - "purchase_remove_product_key": "Κατάργηση κλειδιού προϊόντος", - "purchase_remove_product_key_prompt": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε τον αριθμό-κλειδί προϊόντος;", - "purchase_remove_server_product_key": "Κατάργηση κλειδιού προϊόντος διακομιστή", - "purchase_remove_server_product_key_prompt": "Είστε βέβαιοι ότι θέλετε να καταργήσετε το κλειδί προϊόντος διακομιστή;", - "purchase_server_description_1": "Για ολόκληρο τον διακομιστή", - "purchase_server_description_2": "Κατάσταση υποστηρικτή", - "purchase_server_title": "Διακομιστής", - "purchase_settings_server_activated": "Η διαχείριση του κλειδιού προϊόντος του διακομιστή γίνεται από τον διαχειριστή", - "query_asset_id": "Αναζήτηση ID Στοιχείου", - "queue_status": "Τοποθέτηση στη ουρά {count} από {total}", - "rate_asset": "Βαθμολογήστε το στοιχείο", - "rating": "Αξιολόγηση με αστέρια", - "rating_clear": "Εκκαθάριση αξιολόγησης", - "rating_count": "{count, plural, one {# αστέρι} other {# αστέρια}}", - "rating_description": "Εμφάνιση της αξιολόγησης EXIF στον πίνακα πληροφοριών", - "rating_set": "Η βαθμολογία ορίστηκε σε {rating, plural, one {# αστέρι} other {# αστέρια}}", - "reaction_options": "Επιλογές αντίδρασης", - "read_changelog": "Διαβάστε το Αρχείο Καταγραφής Αλλαγών", - "readonly_mode_disabled": "Η λειτουργία μόνο-για-ανάγνωση απενεργοποιήθηκε", - "readonly_mode_enabled": "Η λειτουργία μόνο-για-ανάγνωση ενεργοποιήθηκε", - "ready_for_upload": "Έτοιμο για μεταφόρτωση", - "reassign": "Ανάθεση", - "reassigned_assets_to_existing_person": "Η ανάθεση {count, plural, one {# αρχείου} other {# αρχείων}} στον/στην {name, select, null {έναν/μία υπάρχοντα/ουσα χρήστη} other {{name}}}", - "reassigned_assets_to_new_person": "Η ανάθεση {count, plural, one {# αρχείου} other {# αρχείων}} σε νέο άτομο", - "reassing_hint": "Ανάθεση των επιλεγμένων στοιχείων σε υπάρχον άτομο", - "recent": "Πρόσφατα", - "recent-albums": "Πρόσφατα άλμπουμ", - "recent_searches": "Πρόσφατες αναζητήσεις", - "recently_added": "Προστέθηκαν πρόσφατα", - "recently_added_page_title": "Προστέθηκαν Πρόσφατα", - "recently_taken": "Λήφθηκαν πρόσφατα", - "recently_taken_page_title": "Λήφθηκαν Πρόσφατα", - "refresh": "Ανανέωση", - "refresh_encoded_videos": "Ανανέωση κωδικοποιημένων βίντεο", - "refresh_faces": "Ανανέωση προσώπων", - "refresh_metadata": "Ανανέωση μεταδεδομένων", - "refresh_thumbnails": "Ανανέωση μικρογραφιών", - "refreshed": "Ανανεωμένα", - "refreshes_every_file": "Επαναδιαβάζει όλα τα υπάρχοντα και νέα αρχεία", - "refreshing_encoded_video": "Ανανέωση κωδικοποιημένου βίντεο", - "refreshing_faces": "Ανανεώνονται πρόσωπα", - "refreshing_metadata": "Τα μεταδεδομένα ανανεώνονται", - "regenerating_thumbnails": "Οι μικρογραφίες αναγεννώνται", - "remote": "Απομακρυσμένος", - "remote_assets": "Απομακρυσμένα στοιχεία", - "remote_media_summary": "Περίληψη απομακρυσμένων πολυμέσων", - "remove": "Αφαίρεση", - "remove_assets_album_confirmation": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε {count, plural, one {# στοιχείο} other {# στοιχεία}} από το άλμπουμ;", - "remove_assets_shared_link_confirmation": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε {count, plural, one {# στοιχείο} other {# στοιχεία}} από αυτόν τον κοινόχρηστο σύνδεσμο;", - "remove_assets_title": "Αφαίρεση στοιχείων;", - "remove_custom_date_range": "Αφαίρεση προσαρμοσμένης χρονικής περιόδου", - "remove_deleted_assets": "Αφαίρεση Διεγραμμένων Στοιχείων", - "remove_from_album": "Αφαίρεση από το άλμπουμ", - "remove_from_album_action_prompt": "{count} αφαιρέθηκαν από το άλμπουμ", - "remove_from_favorites": "Αφαίρεση από τα αγαπημένα", - "remove_from_lock_folder_action_prompt": "{count} αφαιρέθηκαν από τον κλειδωμένο φάκελο", - "remove_from_locked_folder": "Αφαίρεση από κλειδωμένο φάκελο", - "remove_from_locked_folder_confirmation": "Είστε σίγουροι ότι θέλετε να μετακινήσετε αυτές τις φωτογραφίες και τα βίντεο από τον κλειδωμένο φάκελο; Θα είναι πλέον ορατές στη βιβλιοθήκη σας.", - "remove_from_shared_link": "Αφαίρεση από τον κοινόχρηστο σύνδεσμο", - "remove_memory": "Αφαίρεση ανάμνησης", - "remove_photo_from_memory": "Αφαίρεση φωτογραφίας από την ανάμνηση", - "remove_tag": "Αφαίρεση ετικέτας", - "remove_url": "Αφαίρεση Συνδέσμου", - "remove_user": "Αφαίρεση χρήστη", - "removed_api_key": "Αφαιρέθηκε το API Key: {name}", - "removed_from_archive": "Αφαιρέθηκε/καν από το Αρχείο", - "removed_from_favorites": "Αφαιρέθηκε από τα αγαπημένα", - "removed_from_favorites_count": "Αφαιρέθηκαν {count, plural, other {#}} από τα αγαπημένα", - "removed_memory": "Διαγραμμένη μνήμη", - "removed_photo_from_memory": "Διαγραμμένη φωτογραφία από τη μνήμη", - "removed_tagged_assets": "Αφαιρέθηκε η ετικέτα από {count, plural, one {# στοιχείο} other {# στοιχεία}}", - "rename": "Μετονομασία", - "repair": "Επισκευή", - "repair_no_results_message": "Τα αρχεία που δεν παρακολουθούνται και τα λείποντα αρχεία θα εμφανιστούν εδώ", - "replace_with_upload": "Αντικατάσταση με μεταφόρτωση", - "repository": "Αποθετήριο", - "require_password": "Απαιτείται κωδικός πρόσβασης", - "require_user_to_change_password_on_first_login": "Ο χρήστης απαιτείται να αλλάξει τον κωδικό πρόσβασής του κατά την πρώτη σύνδεση", - "rescan": "Εκ νέου σάρωση", - "reset": "Επαναφορά", - "reset_password": "Επαναφορά κωδικού πρόσβασης", - "reset_people_visibility": "Επαναφορά προβολής ατόμων", - "reset_pin_code": "Επαναφορά κωδικού PIN", - "reset_pin_code_description": "Αν ξεχάσατε τον κωδικό PIN σας, μπορείτε να επικοινωνήσετε με τον διαχειριστή του διακομιστή για να τον επαναφέρει", - "reset_pin_code_success": "Ο κωδικός PIN επαναφέρθηκε επιτυχώς", - "reset_pin_code_with_password": "Μπορείτε πάντα να επαναφέρετε τον κωδικό PIN χρησιμοποιώντας τον κωδικό πρόσβασής σας", - "reset_sqlite": "Επαναφορά SQLite βάσης δεδομένων", - "reset_sqlite_confirmation": "Είσαι σίγουρος ότι θέλεις να επαναφέρεις τη βάση δεδομένων SQLite; Θα χρειαστεί να κάνεις αποσύνδεση και επανασύνδεση για να επανασυγχρονίσεις τα δεδομένα", - "reset_sqlite_success": "Η επαναφορά της SQLite βάσης δεδομένων ολοκληρώθηκε με επιτυχία", - "reset_to_default": "Επαναφορά στις προεπιλογές", - "resolution": "Ανάλυση", - "resolve_duplicates": "Επίλυση διπλοτύπων", - "resolved_all_duplicates": "Επιλύθηκαν όλα τα διπλότυπα", - "restore": "Ανάκτηση", - "restore_all": "Ανάκτηση όλων", - "restore_trash_action_prompt": "{count} ανακτήθηκαν από τα απορρίμματα", - "restore_user": "Επαναφορά χρήστη", - "restored_asset": "Ανακτήθηκε το αρχείο", - "resume": "Συνέχιση", - "resume_paused_jobs": "Συνέχιση {count, plural, one {# σε παύση εργασία} other {# σε παύση εργασίες}}", - "retry_upload": "Επανάληψη ανεβάσματος", - "review_duplicates": "Προβολή διπλότυπων", - "review_large_files": "Επισκόπηση μεγάλων αρχείων", - "role": "Ρόλος", - "role_editor": "Επεξεργαστής", - "role_viewer": "Θεατής", - "running": "Σε λειτουργία", - "save": "Αποθήκευση", - "save_to_gallery": "Αποθήκευση στη συλλογή", - "saved": "Αποθηκευμένο", - "saved_api_key": "Αποθηκευμένο API key", - "saved_profile": "Αποθηκευμένο προφίλ", - "saved_settings": "Αποθηκευμένες ρυθμίσεις", - "say_something": "Πείτε κάτι", - "scaffold_body_error_occurred": "Παρουσιάστηκε σφάλμα", - "scan": "Σάρωση", - "scan_all_libraries": "Σάρωση Όλων των Βιβλιοθηκών", - "scan_library": "Σάρωση", - "scan_settings": "Ρυθμίσεις Σάρωσης", - "scanning": "Σαρώνεται", - "scanning_for_album": "Σάρωση για άλμπουμ...", - "search": "Αναζήτηση", - "search_albums": "Αναζήτηση άλμπουμ", - "search_by_context": "Αναζήτηση με βάση το πλαίσιο", - "search_by_description": "Αναζήτηση με βάση την περιγραφή", - "search_by_description_example": "Ημερήσια πεζοπορία στο Πάπιγκο", - "search_by_filename": "Αναζήτηση βάσει ονόματος αρχείου ή επέκτασης αρχείου", - "search_by_filename_example": "π.χ. IMG_1234.JPG ή PNG", - "search_by_ocr": "Αναζήτηση κατά OCR", - "search_by_ocr_example": "Λάτε", - "search_camera_lens_model": "Αναζήτηση μοντέλου φακού...", - "search_camera_make": "Αναζήτηση κατασκευαστή κάμερας...", - "search_camera_model": "Αναζήτηση μοντέλου κάμερας...", - "search_city": "Αναζήτηση πόλης...", - "search_country": "Αναζήτηση χώρας...", - "search_filter_apply": "Εφαρμογή φίλτρου", - "search_filter_camera_title": "Επιλέξτε τύπο κάμερας", - "search_filter_date": "Ημερομηνία", - "search_filter_date_interval": "{start} έως {end}", - "search_filter_date_title": "Επιλέξτε εύρος ημερομηνιών", - "search_filter_display_option_not_in_album": "Όχι στο άλμπουμ", - "search_filter_display_options": "Επιλογές εμφάνισης", - "search_filter_filename": "Αναζήτηση με όνομα αρχείου", - "search_filter_location": "Τοποθεσία", - "search_filter_location_title": "Επιλέξτε τοποθεσία", - "search_filter_media_type": "Τύπος Μέσου", - "search_filter_media_type_title": "Επιλέξτε τύπο μέσου", - "search_filter_ocr": "Αναζήτηση κατά OCR", - "search_filter_people_title": "Επιλέξτε άτομα", - "search_for": "Αναζήτηση για", - "search_for_existing_person": "Αναζήτηση υπάρχοντος ατόμου", - "search_no_more_result": "Δεν υπάρχουν άλλα αποτελέσματα", - "search_no_people": "Κανένα άτομο", - "search_no_people_named": "Κανένα άτομο με όνομα \"{name}\"", - "search_no_result": "Δεν βρέθηκαν αποτελέσματα, προσπαθήστε με διαφορετικές ορολογίες αναζήτησης ή συνδυασμούς", - "search_options": "Επιλογές αναζήτησης", - "search_page_categories": "Κατηγορίες", - "search_page_motion_photos": "Κινούμενες Φωτογραφίες", - "search_page_no_objects": "Μη διαθέσιμες πληροφορίες αντικειμένων", - "search_page_no_places": "Μη διαθέσιμες πληροφορίες για μέρη", - "search_page_screenshots": "Στιγμιότυπα οθόνης", - "search_page_search_photos_videos": "Αναζήτηση για τις φωτογραφίες και τα βίντεό σας", - "search_page_selfies": "Σέλφι", - "search_page_things": "Πράγματα", - "search_page_view_all_button": "Προβολή όλων", - "search_page_your_activity": "Η δραστηριότητά σας", - "search_page_your_map": "Ο χάρτης σας", - "search_people": "Αναζήτηση ατόμων", - "search_places": "Αναζήτηση τοποθεσιών", - "search_rating": "Αναζήτηση κατά βαθμολογία...", - "search_result_page_new_search_hint": "Νέα Αναζήτηση", - "search_settings": "Ρυθμίσεις αναζήτησης", - "search_state": "Αναζήτηση νομού...", - "search_suggestion_list_smart_search_hint_1": "Η έξυπνη αναζήτηση είναι ενεργοποιημένη από προεπιλογή, για αναζήτηση μεταδεδομένων χρησιμοποιήστε το συντακτικό ", - "search_suggestion_list_smart_search_hint_2": "m:όρος-αναζήτησης", - "search_tags": "Αναζήτηση ετικετών...", - "search_timezone": "Αναζήτηση ζώνης ώρας...", - "search_type": "Τύπος αναζήτησης", - "search_your_photos": "Αναζήτηση φωτογραφιών", - "searching_locales": "Αναζήτηση τοποθεσιών...", - "second": "Δευτερόλεπτο", - "see_all_people": "Προβολή όλων των ατόμων", - "select": "Επιλογή", - "select_album": "Επιλογή άλμπουμ", - "select_album_cover": "Επιλέξτε εξώφυλλο άλμπουμ", - "select_albums": "Επιλογή πολλών άλμπουμ", - "select_all": "Επιλογή όλων", - "select_all_duplicates": "Επιλογή όλων των διπλότυπων", - "select_all_in": "Επιλογή όλων στο {group}", - "select_avatar_color": "Επιλέξτε χρώμα avatar", - "select_count": "{count, plural, one {Επίλεξε #} other {Επίλεξε #}}", - "select_cutoff_date": "Επιλέξτε ημερομηνία κοπής", - "select_face": "Επιλογή προσώπου", - "select_featured_photo": "Επιλέξτε φωτογραφία για προβολή", - "select_from_computer": "Επιλέξτε από υπολογιστή", - "select_keep_all": "Επιλέξτε διατήρηση όλων", - "select_library_owner": "Επιλέξτε κάτοχο βιβλιοθήκης", - "select_new_face": "Επιλέξτε νέο πρόσωπο", - "select_people": "Επίλεξε άτομα", - "select_person": "Επίλεξε άτομο", - "select_person_to_tag": "Επιλέξτε ένα άτομο για επισήμανση", - "select_photos": "Επιλέξτε φωτογραφίες", - "select_trash_all": "Επιλέξτε διαγραφή όλων", - "select_user_for_sharing_page_err_album": "Αποτυχία δημιουργίας άλπουμ", - "selected": "Επιλεγμένοι", - "selected_count": "{count, plural, other {# επιλεγμένοι}}", - "selected_gps_coordinates": "Επιλεγμένες συντεταγμένες GPS", - "send_message": "Αποστολή μηνύματος", - "send_welcome_email": "Αποστολή email καλωσορίσματος", - "server_endpoint": "Τελικό σημείο Διακομιστή", - "server_info_box_app_version": "Έκδοση εφαρμογής", - "server_info_box_server_url": "URL διακομιστή", - "server_offline": "Διακομιστής Εκτός Σύνδεσης", - "server_online": "Διακομιστής σε σύνδεση", - "server_privacy": "Απόρρητο Διακομιστή", - "server_restarting_description": "Αυτή η σελίδα θα ανανεωθεί σε λίγο.", - "server_restarting_title": "Ο διακομιστής επανεκκινεί", - "server_stats": "Στατιστικά διακομιστή", - "server_update_available": "Υπάρχει διαθέσιμη ενημέρωση διακομιστή", - "server_version": "Έκδοση Διακομιστή", - "set": "Ορισμός", - "set_as_album_cover": "Ορισμός ως εξώφυλλο άλμπουμ", - "set_as_featured_photo": "Ορισμός ως χαρακτηριστική φωτογραφία", - "set_as_profile_picture": "Ορισμός ως εικόνα προφίλ", - "set_date_of_birth": "Ορισμός ημερομηνίας γέννησης", - "set_profile_picture": "Ορισμός εικόνας προφίλ", - "set_slideshow_to_fullscreen": "Ορίστε την παρουσίαση σε πλήρη οθόνη", - "set_stack_primary_asset": "Ορισμός ως κύριο στοιχείο", - "setting_image_viewer_help": "Το πρόγραμμα προβολής λεπτομερειών φορτώνει πρώτα τη μικρογραφία, στη συνέχεια φορτώνει την προεπισκόπηση μεσαίου μεγέθους (αν είναι ενεργοποιημένη), τέλος φορτώνει το πρωτότυπο (αν είναι ενεργοποιημένο).", - "setting_image_viewer_original_subtitle": "Ενεργοποιήστε τη φόρτωση της πρωτότυπης εικόνας πλήρους ανάλυσης (μεγάλη!). Απενεργοποιήστε για να μειώσετε τη χρήση δεδομένων (τόσο στο δίκτυο όσο και στην κρυφή μνήμη της συσκευής).", - "setting_image_viewer_original_title": "Φόρτωση πρωτότυπης εικόνας", - "setting_image_viewer_preview_subtitle": "Ενεργοποιήστε τη φόρτωση μιας εικόνας μέσης ανάλυσης. Απενεργοποιήστε είτε για να φορτώνεται απευθείας το πρωτότυπο είτε για να χρησιμοποιείται μόνο η μικρογραφία.", - "setting_image_viewer_preview_title": "Φόρτωση εικόνας προεπισκόπησης", - "setting_image_viewer_title": "Εικόνες", - "setting_languages_apply": "Εφαρμογή", - "setting_languages_subtitle": "Αλλάξτε τη γλώσσα της εφαρμογής", - "setting_notifications_notify_failures_grace_period": "Ειδοποίηση αποτυχιών δημιουργίας αντιγράφων ασφαλείας στο παρασκήνιο: {duration}", - "setting_notifications_notify_hours": "{count} ώρες", - "setting_notifications_notify_immediately": "αμέσως", - "setting_notifications_notify_minutes": "{count} λεπτά", - "setting_notifications_notify_never": "ποτέ", - "setting_notifications_notify_seconds": "{count} δευτερόλεπτα", - "setting_notifications_single_progress_subtitle": "Λεπτομερείς πληροφορίες προόδου μεταφόρτωσης ανά στοιχείο", - "setting_notifications_single_progress_title": "Εμφάνιση προόδου λεπτομερειών δημιουργίας αντιγράφων ασφαλείας παρασκηνίου", - "setting_notifications_subtitle": "Προσαρμόστε τις προτιμήσεις ειδοποίησης", - "setting_notifications_total_progress_subtitle": "Συνολική πρόοδος μεταφόρτωσης (ολοκληρώθηκε/σύνολο στοιχείων)", - "setting_notifications_total_progress_title": "Εμφάνιση συνολικής προόδου δημιουργίας αντιγράφων ασφαλείας παρασκηνίου", - "setting_video_viewer_auto_play_subtitle": "Αυτόματη αναπαραγωγή βίντεο κατά το άνοιγμά τους", - "setting_video_viewer_auto_play_title": "Αυτόματη αναπαραγωγή βίντεο", - "setting_video_viewer_looping_title": "Συνεχής Επανάληψη", - "setting_video_viewer_original_video_subtitle": "Όταν μεταδίδετε ένα βίντεο από τον διακομιστή, αναπαράγετε το αυθεντικό ακόμη και όταν υπάρχει διαθέσιμο με διαφορετική κωδικοποίηση. Μπορεί να προκαλέσει καθυστέρηση φόρτωσης. Τα βίντεο που είναι διαθέσιμα τοπικά, αναπαράγονται στην αυθεντική ποιότητα, ανεξαρτήτως αυτής της ρύθμισης.", - "setting_video_viewer_original_video_title": "Αναγκαστική αναπαραγωγή αυθεντικού βίντεο", - "settings": "Ρυθμίσεις", - "settings_require_restart": "Επανεκκινήστε το Immich για να εφαρμόσετε αυτήν τη ρύθμιση", - "settings_saved": "Οι ρυθμίσεις αποθηκεύτηκαν", - "setup_pin_code": "Ρύθμιση κωδικού PIN", - "share": "Κοινοποίηση", - "share_action_prompt": "Κοινή χρήση {count} στοιχείων", - "share_add_photos": "Προσθήκη φωτογραφιών", - "share_assets_selected": "{count} επιλεγμένα", - "share_dialog_preparing": "Προετοιμασία...", - "share_link": "Σύνδεσμος κοινοποίησης", - "shared": "Σε κοινή χρήση", - "shared_album_activities_input_disable": "Το σχόλιο είναι απενεργοποιημένο", - "shared_album_activity_remove_content": "Θέλετε να διαγράψετε αυτήν τη δραστηριότητα;", - "shared_album_activity_remove_title": "Διαγραφή Δραστηριότητας", - "shared_album_section_people_action_error": "Σφάλμα αποχώρησης/κατάργησης από το άλμπουμ", - "shared_album_section_people_action_leave": "Αποχώρηση χρήστη από το άλμπουμ", - "shared_album_section_people_action_remove_user": "Κατάργηση χρήστη από το άλμπουμ", - "shared_album_section_people_title": "ΑΤΟΜΑ", - "shared_by": "Σε κοινή χρήση από", - "shared_by_user": "Σε κοινή χρήση από {user}", - "shared_by_you": "Σε κοινή χρήση από εσάς", - "shared_from_partner": "Φωτογραφίες από {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Μεταφορτωμένα", - "shared_link_app_bar_title": "Κοινόχρηστοι Σύνδεσμοι", - "shared_link_clipboard_copied_massage": "Αντιγράφηκε στο πρόχειρο", - "shared_link_clipboard_text": "Σύνδεσμος: {link}\nΚωδικός πρόσβασης: {password}", - "shared_link_create_error": "Σφάλμα κατά τη δημιουργία κοινόχρηστου συνδέσμου", - "shared_link_custom_url_description": "Αποκτήστε πρόσβαση σε αυτόν τον κοινόχρηστο σύνδεσμο με μια προσαρμοσμένη διεύθυνση URL", - "shared_link_edit_description_hint": "Εισαγάγετε την περιγραφή της κοινής χρήσης", - "shared_link_edit_expire_after_option_day": "1 ημέρα", - "shared_link_edit_expire_after_option_days": "{count} ημέρες", - "shared_link_edit_expire_after_option_hour": "1 ώρα", - "shared_link_edit_expire_after_option_hours": "{count} ώρες", - "shared_link_edit_expire_after_option_minute": "1 λεπτό", - "shared_link_edit_expire_after_option_minutes": "{count} λεπτά", - "shared_link_edit_expire_after_option_months": "{count} μήνες", - "shared_link_edit_expire_after_option_year": "{count} έτος", - "shared_link_edit_password_hint": "Εισαγάγετε τον κωδικό πρόσβασης κοινής χρήσης", - "shared_link_edit_submit_button": "Ενημέρωση συνδέσμου", - "shared_link_error_server_url_fetch": "Δεν είναι δυνατή η ανάκτηση του URL του διακομιστή", - "shared_link_expires_day": "Λήγει σε {count} ημέρα", - "shared_link_expires_days": "Λήγει σε {count} ημέρες", - "shared_link_expires_hour": "Λήγει σε {count} ώρα", - "shared_link_expires_hours": "Λήγει σε {count} ώρες", - "shared_link_expires_minute": "Λήγει σε {count} λεπτό", - "shared_link_expires_minutes": "Λήγει σε {count} λεπτά", - "shared_link_expires_never": "Λήγει ∞", - "shared_link_expires_second": "Λήγει σε {count} δευτερόλεπτο", - "shared_link_expires_seconds": "Λήγει σε {count} δευτερόλεπτα", - "shared_link_individual_shared": "Μεμονωμένο κοινό", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Διαχείριση Κοινόχρηστων Συνδέσμων", - "shared_link_options": "Επιλογές κοινόχρηστου συνδέσμου", - "shared_link_password_description": "Απαίτηση κωδικού για πρόσβαση στον κοινοποιημένο σύνδεσμο", - "shared_links": "Κοινόχρηστοι σύνδεσμοι", - "shared_links_description": "Μοιραστείτε φωτογραφίες και βίντεο με σύνδεσμο", - "shared_photos_and_videos_count": "{assetCount, plural, other {# κοινόχρηστες φωτογραφίες & βίντεο.}}", - "shared_with_me": "Μοιρασμένα μαζί μου", - "shared_with_partner": "Σε κοινή χρήση με {partner}", - "sharing": "Κοινοποίηση", - "sharing_enter_password": "Εισαγάγετε τον κωδικό πρόσβασης για να δείτε αυτήν τη σελίδα.", - "sharing_page_album": "Κοινόχρηστα άλμπουμ", - "sharing_page_description": "Δημιουργήστε κοινόχρηστα άλμπουμ για να μοιράζεστε φωτογραφίες και βίντεο με άτομα στο δίκτυό σας.", - "sharing_page_empty_list": "ΚΕΝΗ ΛΙΣΤΑ", - "sharing_sidebar_description": "Εμφανίστε έναν σύνδεσμο για Κοινή χρήση στην πλαϊνή γραμμή", - "sharing_silver_appbar_create_shared_album": "Νέο κοινόχρηστο άλμπουμ", - "sharing_silver_appbar_share_partner": "Μοιραστείτε με τον συνεργάτη", - "shift_to_permanent_delete": "πατήστε ⇧ για οριστική διαγραφή στοιχείου", - "show_album_options": "Εμφάνιση επιλογών άλμπουμ", - "show_albums": "Προβολή των άλμπουμ", - "show_all_people": "Προβολή όλων των ατόμων", - "show_and_hide_people": "Εμφάνιση & απόκρυψη ατόμων", - "show_file_location": "Εμφάνιση θέσης αρχείου", - "show_gallery": "Εμφάνιση γκαλερί", - "show_hidden_people": "Εμφάνιση κρυμμένων ατόμων", - "show_in_timeline": "Εμφάνιση στο χρονολόγιο", - "show_in_timeline_setting_description": "Εμφάνιση φωτογραφιών και βίντεο από αυτόν τον χρήστη στο χρονολόγιό σας", - "show_keyboard_shortcuts": "Εμφάνιση συντομεύσεων πληκτρολογίου", - "show_metadata": "Εμφάνιση μεταδεδομένων", - "show_or_hide_info": "Εμφάνιση ή απόκρυψη πληροφοριών", - "show_password": "Εμφάνιση κωδικού", - "show_person_options": "Εμφάνιση επιλογών ατόμου", - "show_progress_bar": "Εμφάνιση γραμμής προόδου", - "show_schema": "Εμφάνιση σχήματος", - "show_search_options": "Εμφάνιση επιλογών αναζήτησης", - "show_shared_links": "Εμφάνιση κοινών συνδέσμων", - "show_slideshow_transition": "Εμφάνιση μετάβασης παρουσίασης", - "show_supporter_badge": "Σήμα υποστηρικτή", - "show_supporter_badge_description": "Εμφάνιση σήματος υποστηρικτή", - "show_text_recognition": "Εμφάνιση αναγνώρισης κειμένου", - "show_text_search_menu": "Εμφάνιση μενού αναζήτησης κειμένου", - "shuffle": "Ανάμειξη", - "sidebar": "Πλαϊνή μπάρα", - "sidebar_display_description": "Εμφάνιση συνδέσμου για προβολή στην πλαϊνή μπάρα", - "sign_out": "Αποσύνδεση", - "sign_up": "Εγγραφή", - "size": "Μέγεθος", - "skip_to_content": "Μετάβαση στο περιεχόμενο", - "skip_to_folders": "Παράκαμψη στους φακέλους", - "skip_to_tags": "Παράκαμψη στις ετικέτες", - "slideshow": "Παρουσίαση", - "slideshow_settings": "Ρυθμίσεις παρουσίασης", - "sort_albums_by": "Ταξινόμηση άλμπουμ κατά...", - "sort_created": "Ημερομηνία Δημιουργίας", - "sort_items": "Αριθμός αντικειμένων", - "sort_modified": "Ημερομηνία τροποποίησης", - "sort_newest": "Νεότερη φωτογραφία", - "sort_oldest": "Η πιο παλιά φωτογραφία", - "sort_people_by_similarity": "Ταξινόμηση ατόμων κατά ομοιότητα", - "sort_recent": "Η πιο πρόσφατη φωτογραφία", - "sort_title": "Τίτλος", - "source": "Πηγή", - "stack": "Στοίβα", - "stack_action_prompt": "{count} συσσωρεύτηκαν", - "stack_duplicates": "Στοίβαξη διπλότυπων", - "stack_select_one_photo": "Επιλέξτε μια κύρια φωτογραφία για τη στοίβαξη", - "stack_selected_photos": "Στοίβαγμα επιλεγμένων φωτογραφιών", - "stacked_assets_count": "Στοιβάχτηκαν {count, plural, one {# στοιχείο} other {# στοιχεία}}", - "stacktrace": "Καταγραφή στοίβας", - "start": "Έναρξη", - "start_date": "Από", - "start_date_before_end_date": "Η ημερομηνία έναρξης πρέπει να είναι πριν από την ημερομηνία λήξης", - "state": "Νομός", - "status": "Κατάσταση", - "stop_casting": "Διακοπή μετάδοσης", - "stop_motion_photo": "Διέκοψε την Φωτογραφία Κίνησης", - "stop_photo_sharing": "Διακοπή κοινής χρήσης των φωτογραφιών σας;", - "stop_photo_sharing_description": "Ο χρήστης {partner} δεν θα έχει πλέον πρόσβαση στις φωτογραφίες σας.", - "stop_sharing_photos_with_user": "Διακοπή κοινής χρήσης των φωτογραφιών σας με αυτό το χρήστη", - "storage": "Χώρος αποθήκευσης", - "storage_label": "Ετικέτα αποθήκευσης", - "storage_quota": "Ποσοστό αποθηκευτικού χώρου", - "storage_usage": "{used} από {available} σε χρήση", - "submit": "Υποβολή", - "success": "Επιτυχία", - "suggestions": "Προτάσεις", - "sunrise_on_the_beach": "Ηλιοβασίλεμα στην παραλία", - "support": "Υποστήριξη", - "support_and_feedback": "Υποστήριξη & Σχόλια", - "support_third_party_description": "Η εγκατάσταση του Immich που χρησιμοποιείτε, έχει πακεταριστεί από τρίτους. Τα προβλήματα που αντιμετωπίζετε μπορεί να οφείλονται σε αυτό το πακέτο, οπότε παρακαλούμε να αναφέρετε τα προβλήματα πρώτα σε εκείνους, χρησιμοποιώντας τους παρακάτω συνδέσμους.", - "swap_merge_direction": "Εναλλαγή κατεύθυνσης συγχώνευσης", - "sync": "Συγχρονισμός", - "sync_albums": "Συγχρονισμός άλμπουμ", - "sync_albums_manual_subtitle": "Συγχρονίστε όλα τα μεταφορτωμένα βίντεο και φωτογραφίες με τα επιλεγμένα εφεδρικά άλμπουμ", - "sync_local": "Τοπικός Συγχρονισμός", - "sync_remote": "Απομακρυσμένος Συγχρονισμός", - "sync_status": "Κατάσταση συγχρονισμού", - "sync_status_subtitle": "Προβολή και διαχείριση του συστήματος συγχρονισμού", - "sync_upload_album_setting_subtitle": "Δημιουργήστε και ανεβάστε τις φωτογραφίες και τα βίντεό σας στα επιλεγμένα άλμπουμ στο Immich", - "tag": "Ετικέτα", - "tag_assets": "Ετικετοποίηση στοιχείων", - "tag_created": "Δημιουργήθηκε ετικέτα: {tag}", - "tag_feature_description": "Περιήγηση σε φωτογραφίες και βίντεο που είναι οργανωμένα σύμφωνα με λογικά θέματα ετικετών", - "tag_not_found_question": "Δεν μπορείτε να βρείτε μια ετικέτα; Δημιουργήστε μια νέα ετικέτα.", - "tag_people": "Επισήμανση ατόμων", - "tag_updated": "Ενημερώθηκε η ετικέτα: {tag}", - "tagged_assets": "Ετικετοποιημένο/α {count, plural, one {# στοιχείο} other {# στοιχεία}}", - "tags": "Ετικέτες", - "tap_to_run_job": "Πατήστε για να ξεκινήσει η εργασία", - "template": "Πρότυπο", - "text_recognition": "Αναγνώριση κειμένου", - "theme": "Θέμα", - "theme_selection": "Επιλογή θέματος", - "theme_selection_description": "Ρυθμίστε αυτόματα το θέμα σε ανοιχτό ή σκούρο με βάση τις προτιμήσεις συστήματος του προγράμματος περιήγησής σας", - "theme_setting_asset_list_storage_indicator_title": "Εμφάνιση ένδειξης αποθήκευσης σε πλακίδια στοιχείων", - "theme_setting_asset_list_tiles_per_row_title": "Αριθμός στοιχείων ανά σειρά ({count})", - "theme_setting_colorful_interface_subtitle": "Εφαρμόστε βασικό χρώμα σε επιφάνειες φόντου.", - "theme_setting_colorful_interface_title": "Πολύχρωμη διεπαφή", - "theme_setting_image_viewer_quality_subtitle": "Προσαρμόστε την ποιότητα του προγράμματος προβολής εικόνας λεπτομερειών", - "theme_setting_image_viewer_quality_title": "Ποιότητα προβολής εικόνων", - "theme_setting_primary_color_subtitle": "Επιλέξτε ένα χρώμα για κύριες ενέργειες και τόνους.", - "theme_setting_primary_color_title": "Πρωταρχικό χρώμα", - "theme_setting_system_primary_color_title": "Χρησιμοποιήστε το χρώμα συστήματος", - "theme_setting_system_theme_switch": "Αυτόματο (Ακολουθήστε τη ρύθμιση συστήματος)", - "theme_setting_theme_subtitle": "Επιλέξτε τη ρύθμιση θέματος της εφαρμογής", - "theme_setting_three_stage_loading_subtitle": "Η φόρτωση τριών σταδίων μπορεί να αυξήσει την απόδοση φόρτωσης, αλλά προκαλεί σημαντικά υψηλότερο φόρτο δικτύου", - "theme_setting_three_stage_loading_title": "Ενεργοποιήστε τη φόρτωση τριών σταδίων", - "then": "Τότε", - "they_will_be_merged_together": "Θα συγχωνευθούν μαζί", - "third_party_resources": "Πόροι τρίτων", - "time": "Χρόνος", - "time_based_memories": "Μνήμες βασισμένες στο χρόνο", - "time_based_memories_duration": "Αριθμός δευτερολέπτων εμφάνισης κάθε εικόνας.", - "timeline": "Χρονολόγιο", - "timezone": "Ζώνη ώρας", - "to_archive": "Αρχειοθέτηση", - "to_change_password": "Αλλαγή κωδικού πρόσβασης", - "to_favorite": "Αγαπημένο", - "to_login": "Είσοδος", - "to_multi_select": "για πολλαπλή επιλογή", - "to_parent": "Μεταβείτε στο γονικό φάκελο", - "to_select": "για επιλογή", - "to_trash": "Κάδος απορριμμάτων", - "toggle_settings": "Εναλλαγή ρυθμίσεων", - "toggle_theme_description": "Εναλλαγή θέματος", - "total": "Σύνολο", - "total_usage": "Συνολικη χρηση", - "trash": "Κάδος απορριμμάτων", - "trash_action_prompt": "{count} μετακινήθηκαν στα απορρίμματα", - "trash_all": "Διαγραφή Όλων", - "trash_count": "Διαγραφή {count, number}", - "trash_delete_asset": "Διαγραφή/Οριστ. Διαγραφή Αντικειμένου", - "trash_emptied": "Αδειάστηκαν τα σκουπίδια", - "trash_no_results_message": "Οι φωτογραφίες και τα βίντεο που βρίσκονται στον κάδο απορριμμάτων θα εμφανίζονται εδώ.", - "trash_page_delete_all": "Διαγραφή όλων", - "trash_page_empty_trash_dialog_content": "Θέλετε να αδειάσετε τα περιουσιακά σας στοιχεία στον κάδο απορριμμάτων; Αυτά τα στοιχεία θα καταργηθούν οριστικά από το Immich", - "trash_page_info": "Τα στοιχεία που έχουν απορριφθεί θα διαγραφούν οριστικά μετά από {days} ημέρες", - "trash_page_no_assets": "Δεν υπάρχουν περιουσιακά στοιχεία που έχουν απορριφθεί", - "trash_page_restore_all": "Επαναφορά Όλων", - "trash_page_select_assets_btn": "Επιλέξτε στοιχεία", - "trash_page_title": "Κάδος Απορριμμάτων ({count})", - "trashed_items_will_be_permanently_deleted_after": "Τα στοιχεία που βρίσκονται στον κάδο απορριμμάτων θα διαγραφούν οριστικά μετά από {days, plural, one {# ημέρα} other {# ημέρες}}.", - "trigger": "Ενεργοποιητής", - "trigger_asset_uploaded": "Το στοιχείο ανέβηκε", - "trigger_asset_uploaded_description": "Ενεργοποιείται όταν ανεβαίνει ένα νέο στοιχείο", - "trigger_description": "Ένα συμβάν που ξεκινά τη ροή εργασίας", - "trigger_person_recognized": "Άτομο Αναγνωρίστηκε", - "trigger_person_recognized_description": "Ενεργοποιείται όταν ανιχνεύεται άτομο", - "trigger_type": "Τύπος ενεργοποιητή", - "troubleshoot": "Επίλυση προβλημάτων", - "type": "Τύπος", - "unable_to_change_pin_code": "Αδυναμία αλλαγής κωδικού PIN", - "unable_to_check_version": "Αδυναμία ελέγχου έκδοσης εφαρμογής ή έκδοσης διακομιστή", - "unable_to_setup_pin_code": "Αδυναμία ρύθμισης κωδικού PIN", - "unarchive": "Αναίρεση αρχειοθέτησης", - "unarchive_action_prompt": "{count} αφαιρέθηκαν από το Αρχείο", - "unarchived_count": "{count, plural, other {Αρχειοθετήσεις αναιρέθηκαν #}}", - "undo": "Αναίρεση", - "unfavorite": "Αποεπιλογή από τα αγαπημένα", - "unfavorite_action_prompt": "{count} αφαιρέθηκαν από τα Αγαπημένα", - "unhide_person": "Αναίρεση απόκρυψης ατόμου", - "unknown": "Άγνωστο", - "unknown_country": "Άγνωστη Χώρα", - "unknown_date": "Άγνωστη ημερομηνία", - "unknown_year": "Άγνωστο Έτος", - "unlimited": "Απεριόριστο", - "unlink_motion_video": "Αποσυνδέστε το βίντεο κίνησης", - "unlink_oauth": "Αποσύνδεση OAuth", - "unlinked_oauth_account": "Ο λογαριασμός OAuth αποσυνδέθηκε", - "unmute_memories": "Ενεργοποίηση Αναμνήσεων", - "unnamed_album": "Ανώνυμο Άλμπουμ", - "unnamed_album_delete_confirmation": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το άλμπουμ;", - "unnamed_share": "Ανώνυμη Κοινή Χρήση", - "unsaved_change": "Μη αποθηκευμένη αλλαγή", - "unselect_all": "Αποεπιλογή όλων", - "unselect_all_duplicates": "Αποεπιλογή όλων των διπλότυπων", - "unselect_all_in": "Αποεπιλογή όλων στο {group}", - "unstack": "Αποστοίβαξη", - "unstack_action_prompt": "{count} αποσυσσωρεύτηκαν", - "unstacked_assets_count": "Αποστοιβάξατε {count, plural, one {# στοιχείο} other {# στοιχεία}}", - "unsupported_field_type": "Μη υποστηριζόμενος τύπος πεδίου", - "untagged": "Χωρίς ετικέτα", - "untitled_workflow": "Νέα ροή εργασίας", - "up_next": "Ακολουθεί", - "update_location_action_prompt": "Ενημέρωση τοποθεσίας για {count} επιλεγμένα στοιχεία με:", - "updated_at": "Ενημερωμένο", - "updated_password": "Ο κωδικός πρόσβασης ενημερώθηκε", - "upload": "Μεταφόρτωση", - "upload_concurrency": "Ταυτόχρονη μεταφόρτωση", - "upload_details": "Λεπτομέρειες μεταφόρτωσης", - "upload_dialog_info": "Θέλετε να αντιγράψετε (κάνετε backup) τα επιλεγμένo(α) στοιχείο(α) στο διακομιστή;", - "upload_dialog_title": "Ανέβασμα στοιχείου", - "upload_errors": "Η μεταφόρτωση ολοκληρώθηκε με {count, plural, one {# σφάλμα} other {# σφάλματα}}, ανανεώστε τη σελίδα για να δείτε νέα στοιχεία μεταφόρτωσης.", - "upload_finished": "Ολοκλήρωση μεταφόρτωσης", - "upload_progress": "Απομένουν {remaining, number} - Ολοκληρώθηκαν {processed, number}/{total, number}", - "upload_skipped_duplicates": "Παραλείφθηκαν {count, plural, one {# διπλότυπο στοιχείο} other {# διπλότυπα στοιχεία}}", - "upload_status_duplicates": "Διπλότυπα", - "upload_status_errors": "Σφάλματα", - "upload_status_uploaded": "Μεταφορτώθηκαν", - "upload_success": "Η μεταφόρτωση ολοκληρώθηκε, ανανεώστε τη σελίδα για να δείτε τα νέα αντικείμενα.", - "upload_to_immich": "Μεταφόρτωση στο Immich ({count})", - "uploading": "Μεταφορτώνεται", - "uploading_media": "Μεταφόρτωση πολυμέσων", - "url": "URL", - "usage": "Χρήση", - "use_biometric": "Χρήση βιομετρικών στοιχείων", - "use_current_connection": "Χρήση τρέχουσας σύνδεσης", - "use_custom_date_range": "Χρήση προσαρμοσμένου εύρους ημερομηνιών", - "user": "Χρήστης", - "user_has_been_deleted": "Αυτός ο χρήστης έχει διεγραφεί.", - "user_id": "ID Χρήστη", - "user_liked": "Στο χρήστη {user} αρέσει {type, select, photo {αυτή η φωτογραφία} video {αυτό το βίντεο} asset {αυτό το αντικείμενο} other {it}}", - "user_pin_code_settings": "Κωδικός PIN", - "user_pin_code_settings_description": "Διαχειριστείτε τον κωδικό PIN σας", - "user_privacy": "Απόρρητο Χρήστη", - "user_purchase_settings": "Αγορά", - "user_purchase_settings_description": "Διαχείριση Αγοράς", - "user_role_set": "Ορισμός {user} ως {role}", - "user_usage_detail": "Λεπτομερειες χρησης του χρηστη", - "user_usage_stats": "Στατιστικά χρήσης λογαριασμού", - "user_usage_stats_description": "Προβολή στατιστικών χρήσης λογαριασμού", - "username": "Όνομα Χρήστη", - "users": "Χρήστες", - "users_added_to_album_count": "Προστέθηκε {count, plural, one {# χρήστης} other {# χρήστες}} στο άλμπουμ", - "utilities": "Βοηθητικά προγράμματα", - "validate": "Επικύρωση", - "validate_endpoint_error": "Παρακαλώ εισάγετε ένα έγκυρο URL", - "validation_error": "Σφάλμα επικύρωσης", - "variables": "Μεταβλητές", - "version": "Έκδοση", - "version_announcement_closing": "Ο φίλος σου, Alex", - "version_announcement_message": "Γειά σας! Μια νέα έκδοση του Immich είναι διαθέσιμη. Παρακαλούμε αφιερώστε λίγο χρόνο για να διαβάσετε τις σημειώσεις έκδοσης ώστε να βεβαιωθείτε ότι η ρύθμιση σας είναι ενημερωμένη και να αποφύγετε τυχόν σφάλματα, ειδικά αν χρησιμοποιείτε το WatchTower ή οποιοδήποτε μηχανισμό που διαχειρίζεται αυτόματα την ενημέρωση της εγκατάστασης του Immich σας.", - "version_history": "Ιστορικό Εκδόσεων", - "version_history_item": "Εγκαταστάθηκε {version} στις {date}", - "video": "Βίντεο", - "video_hover_setting": "Προεπισκόπηση βίντεο με το δείκτη του ποντικιού", - "video_hover_setting_description": "Προεπισκόπηση βίντεο όταν το ποντίκι βρίσκεται πάνω από το στοιχείο. Ακόμη και όταν είναι απενεργοποιημένη, η αναπαραγωγή μπορεί να ξεκινήσει τοποθετώντας το δείκτη του ποντικιού πάνω από το εικονίδιο αναπαραγωγής.", - "videos": "Βίντεο", - "videos_count": "{count, plural, one {# Βίντεο} other {# Βίντεο}}", - "videos_only": "Μόνο βίντεο", - "view": "Προβολή", - "view_album": "Προβολή Άλμπουμ", - "view_all": "Προβολή Όλων", - "view_all_users": "Προβολή όλων των χρηστών", - "view_asset_owners": "Δείτε τους ιδιοκτήτες των στοιχείων", - "view_details": "Προβολή Λεπτομερειών", - "view_in_timeline": "Προβολή στο χρονοδιάγραμμα", - "view_link": "Προβολή σύνδεσμου", - "view_links": "Προβολή συνδέσμων", - "view_name": "Προβολή", - "view_next_asset": "Προβολή επόμενου στοιχείου", - "view_previous_asset": "Προβολή προηγούμενου στοιχείου", - "view_qr_code": "Προβολή κωδικού QR", - "view_similar_photos": "Προβολή παρόμοιων φωτογραφιών", - "view_stack": "Προβολή της στοίβας", - "view_user": "Προβολή Χρήστη", - "viewer_remove_from_stack": "Κατάργηση από τη Στοίβα", - "viewer_stack_use_as_main_asset": "Χρήση ως Κύριο Στοιχείο", - "viewer_unstack": "Αποστοίβαξε", - "visibility_changed": "Η ορατότητα άλλαξε για {count, plural, one {# άτομο} other {# άτομα}}", - "visual": "Οπτικό", - "visual_builder": "Οπτικός δημιουργός", - "waiting": "Στοιχεία σε αναμονή", - "waiting_count": "Σε αναμονή: {count}", - "warning": "Προειδοποίηση", - "week": "Εβδομάδα", - "welcome": "Καλωσορίσατε", - "welcome_to_immich": "Καλωσορίσατε στο Ιmmich", - "width": "Πλάτος", - "wifi_name": "Όνομα Wi-Fi", - "workflow_delete_prompt": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτή τη ροή εργασίας;", - "workflow_deleted": "Η ροή εργασίας διαγράφηκε", - "workflow_description": "Περιγραφή ροής εργασίας", - "workflow_info": "Πληροφορίες ροής εργασίας", - "workflow_json": "JSON ροής εργασίας", - "workflow_json_help": "Επεξεργαστείτε τη ρύθμιση της ροής εργασίας σε μορφή JSON. Οι αλλαγές θα συγχρονιστούν με τον οπτικό δημιουργό.", - "workflow_name": "Όνομα ροής εργασίας", - "workflow_navigation_prompt": "Είστε σίγουροι ότι θέλετε να φύγετε χωρίς να αποθηκεύσετε τις αλλαγές σας;", - "workflow_summary": "Σύνοψη ροής εργασίας", - "workflow_update_success": "Η ροή εργασίας ενημερώθηκε με επιτυχία", - "workflow_updated": "Η ροή εργασίας ενημερώθηκε", - "workflows": "Ροές εργασίας", - "workflows_help_text": "Οι ροές εργασίας αυτοματοποιούν ενέργειες στα στοιχεία σας με βάση ενεργοποιητές και φίλτρα", - "wrong_pin_code": "Λάθος κωδικός PIN", - "year": "Έτος", - "years_ago": "πριν από {years, plural, one {# χρόνο} other {# χρόνια}}", - "yes": "Ναι", - "you_dont_have_any_shared_links": "Δεν έχετε κοινόχρηστους συνδέσμους", - "your_wifi_name": "Το όνομα του Wi-Fi σας", - "zero_to_clear_rating": "πατήστε 0 για να διαγράψετε τη βαθμολογία του στοιχείου", - "zoom_image": "Ζουμ Εικόνας", - "zoom_to_bounds": "Εστίαση στα όρια" -} +{} diff --git a/i18n/en.json b/i18n/en.json index dedbea1bfe..a51467ab6d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,2410 +1,4 @@ { - "about": "About", - "account": "Account", - "account_settings": "Account Settings", - "acknowledge": "Acknowledge", - "action": "Action", - "action_common_update": "Update", - "action_description": "A set of action to perform on the filtered assets", - "actions": "Actions", - "active": "Active", - "active_count": "Active: {count}", - "activity": "Activity", - "activity_changed": "Activity is {enabled, select, true {enabled} other {disabled}}", - "add": "Add", - "add_a_description": "Add a description", - "add_a_location": "Add a location", - "add_a_name": "Add a name", - "add_a_title": "Add a title", - "add_action": "Add action", - "add_action_description": "Click to add an action to perform", - "add_assets": "Add assets", - "add_birthday": "Add a birthday", - "add_endpoint": "Add endpoint", - "add_exclusion_pattern": "Add exclusion pattern", - "add_filter": "Add filter", - "add_filter_description": "Click to add a filter condition", - "add_location": "Add location", - "add_more_users": "Add more users", - "add_partner": "Add partner", - "add_path": "Add path", - "add_photos": "Add photos", - "add_tag": "Add tag", - "add_to": "Add to…", - "add_to_album": "Add to album", - "add_to_album_bottom_sheet_added": "Added to {album}", - "add_to_album_bottom_sheet_already_exists": "Already in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album", - "add_to_album_toggle": "Toggle selection for {album}", - "add_to_albums": "Add to albums", - "add_to_albums_count": "Add to albums ({count})", - "add_to_bottom_bar": "Add to", - "add_to_shared_album": "Add to shared album", - "add_upload_to_stack": "Add upload to stack", - "add_url": "Add URL", - "add_workflow_step": "Add workflow step", - "added_to_archive": "Added to archive", - "added_to_favorites": "Added to favorites", - "added_to_favorites_count": "Added {count, number} to favorites", - "admin": { - "add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".", - "admin_user": "Admin User", - "asset_offline_description": "This external library asset is no longer found on disk and has been moved to trash. If the file was moved within the library, check your timeline for the new corresponding asset. To restore this asset, please ensure that the file path below can be accessed by Immich and scan the library.", - "authentication_settings": "Authentication Settings", - "authentication_settings_description": "Manage password, OAuth, and other authentication settings", - "authentication_settings_disable_all": "Are you sure you want to disable all login methods? Login will be completely disabled.", - "authentication_settings_reenable": "To re-enable, use a Server Command.", - "background_task_job": "Background Tasks", - "backup_database": "Create Database Dump", - "backup_database_enable_description": "Enable database dumps", - "backup_keep_last_amount": "Amount of previous dumps to keep", - "backup_onboarding_1_description": "offsite copy in the cloud or at another physical location.", - "backup_onboarding_2_description": "local copies on different devices. This includes the main files and a backup of those files locally.", - "backup_onboarding_3_description": "total copies of your data, including the original files. This includes 1 offsite copy and 2 local copies.", - "backup_onboarding_description": "A 3-2-1 backup strategy is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution.", - "backup_onboarding_footer": "For more information about backing up Immich, please refer to the documentation.", - "backup_onboarding_parts_title": "A 3-2-1 backup includes:", - "backup_onboarding_title": "Backups", - "backup_settings": "Database Dump Settings", - "backup_settings_description": "Manage database dump settings.", - "cleared_jobs": "Cleared jobs for: {job}", - "config_set_by_file": "Config is currently set by a config file", - "confirm_delete_library": "Are you sure you want to delete {library} library?", - "confirm_delete_library_assets": "Are you sure you want to delete this library? This will delete {count, plural, one {# contained asset} other {all # contained assets}} from Immich and cannot be undone. Files will remain on disk.", - "confirm_email_below": "To confirm, type \"{email}\" below", - "confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.", - "confirm_user_password_reset": "Are you sure you want to reset {user}'s password?", - "confirm_user_pin_code_reset": "Are you sure you want to reset {user}'s PIN code?", - "copy_config_to_clipboard_description": "Copy the current system config as a JSON object to the clipboard", - "create_job": "Create job", - "cron_expression": "Cron expression", - "cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. Crontab Guru", - "cron_expression_presets": "Cron expression presets", - "disable_login": "Disable login", - "duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search", - "exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.", - "export_config_as_json_description": "Download the current system config as a JSON file", - "external_libraries_page_description": "Admin external library page", - "face_detection": "Face detection", - "face_detection_description": "Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. \"Refresh\" (re-)processes all assets. \"Reset\" additionally clears all current face data. \"Missing\" queues assets that haven't been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.", - "facial_recognition_job_description": "Group detected faces into people. This step runs after Face Detection is complete. \"Reset\" (re-)clusters all faces. \"Missing\" queues faces that don't have a person assigned.", - "failed_job_command": "Command {command} failed for job: {job}", - "force_delete_user_warning": "WARNING: This will immediately remove the user and all assets. This cannot be undone and the files cannot be recovered.", - "image_format": "Format", - "image_format_description": "WebP produces smaller files than JPEG, but is slower to encode.", - "image_fullsize_description": "Full-size image with stripped metadata, used when zoomed in", - "image_fullsize_enabled": "Enable full-size image generation", - "image_fullsize_enabled_description": "Generate full-size image for non-web-friendly formats. When \"Prefer embedded preview\" is enabled, embedded previews are used directly without conversion. Does not affect web-friendly formats like JPEG.", - "image_fullsize_quality_description": "Full-size image quality from 1-100. Higher is better, but produces larger files.", - "image_fullsize_title": "Full-size Image Settings", - "image_prefer_embedded_preview": "Prefer embedded preview", - "image_prefer_embedded_preview_setting_description": "Use embedded previews in RAW photos as the input to image processing and when available. This can produce more accurate colors for some images, but the quality of the preview is camera-dependent and the image may have more compression artifacts.", - "image_prefer_wide_gamut": "Prefer wide gamut", - "image_prefer_wide_gamut_setting_description": "Use Display P3 for thumbnails. This better preserves the vibrance of images with wide colorspaces, but images may appear differently on old devices with an old browser version. sRGB images are kept as sRGB to avoid color shifts.", - "image_preview_description": "Medium-size image with stripped metadata, used when viewing a single asset and for machine learning", - "image_preview_quality_description": "Preview quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness. Setting a low value may affect machine learning quality.", - "image_preview_title": "Preview Settings", - "image_progressive": "Progressive", - "image_progressive_description": "Encode JPEG images progressively for gradual loading display. This has no effect on WebP images.", - "image_quality": "Quality", - "image_resolution": "Resolution", - "image_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes and can reduce app responsiveness.", - "image_settings": "Image Settings", - "image_settings_description": "Manage the quality and resolution of generated images", - "image_thumbnail_description": "Small thumbnail with stripped metadata, used when viewing groups of photos like the main timeline", - "image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.", - "image_thumbnail_title": "Thumbnail Settings", - "import_config_from_json_description": "Import system config by uploading a JSON config file", - "job_concurrency": "{job} concurrency", - "job_created": "Job created", - "job_not_concurrency_safe": "This job is not concurrency-safe.", - "job_settings": "Job Settings", - "job_settings_description": "Manage job concurrency", - "jobs_delayed": "{jobCount, plural, other {# delayed}}", - "jobs_failed": "{jobCount, plural, other {# failed}}", - "jobs_over_time": "Jobs over time", - "library_created": "Created library: {library}", - "library_deleted": "Library deleted", - "library_details": "Library details", - "library_folder_description": "Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.", - "library_remove_exclusion_pattern_prompt": "Are you sure you want to remove this exclusion pattern?", - "library_remove_folder_prompt": "Are you sure you want to remove this import folder?", - "library_scanning": "Periodic Scanning", - "library_scanning_description": "Configure periodic library scanning", - "library_scanning_enable_description": "Enable periodic library scanning", - "library_settings": "External Library", - "library_settings_description": "Manage external library settings", - "library_tasks_description": "Scan external libraries for new and/or changed assets", - "library_updated": "Updated library", - "library_watching_enable_description": "Watch external libraries for file changes", - "library_watching_settings": "Library watching [EXPERIMENTAL]", - "library_watching_settings_description": "Automatically watch for changed files", - "logging_enable_description": "Enable logging", - "logging_level_description": "When enabled, what log level to use.", - "logging_settings": "Logging", - "machine_learning_availability_checks": "Availability checks", - "machine_learning_availability_checks_description": "Automatically detect and prefer available machine learning servers", - "machine_learning_availability_checks_enabled": "Enable availability checks", - "machine_learning_availability_checks_interval": "Check interval", - "machine_learning_availability_checks_interval_description": "Interval in milliseconds between availability checks", - "machine_learning_availability_checks_timeout": "Request timeout", - "machine_learning_availability_checks_timeout_description": "Timeout in milliseconds for availability checks", - "machine_learning_clip_model": "CLIP model", - "machine_learning_clip_model_description": "The name of a CLIP model listed here. Note that you must re-run the 'Smart Search' job for all images upon changing a model.", - "machine_learning_duplicate_detection": "Duplicate Detection", - "machine_learning_duplicate_detection_enabled": "Enable duplicate detection", - "machine_learning_duplicate_detection_enabled_description": "If disabled, exactly identical assets will still be de-duplicated.", - "machine_learning_duplicate_detection_setting_description": "Use CLIP embeddings to find likely duplicates", - "machine_learning_enabled": "Enable machine learning", - "machine_learning_enabled_description": "If disabled, all ML features will be disabled regardless of the below settings.", - "machine_learning_facial_recognition": "Facial Recognition", - "machine_learning_facial_recognition_description": "Detect, recognize and group faces in images", - "machine_learning_facial_recognition_model": "Facial recognition model", - "machine_learning_facial_recognition_model_description": "Models are listed in descending order of size. Larger models are slower and use more memory, but produce better results. Note that you must re-run the Face Detection job for all images upon changing a model.", - "machine_learning_facial_recognition_setting": "Enable facial recognition", - "machine_learning_facial_recognition_setting_description": "If disabled, images will not be encoded for facial recognition and will not populate the People section in the Explore page.", - "machine_learning_max_detection_distance": "Maximum detection distance", - "machine_learning_max_detection_distance_description": "Maximum distance between two images to consider them duplicates, ranging from 0.001-0.1. Higher values will detect more duplicates, but may result in false positives.", - "machine_learning_max_recognition_distance": "Maximum recognition distance", - "machine_learning_max_recognition_distance_description": "Maximum distance between two faces to be considered the same person, ranging from 0-2. Lowering this can prevent labeling two people as the same person, while raising it can prevent labeling the same person as two different people. Note that it is easier to merge two people than to split one person in two, so err on the side of a lower threshold when possible.", - "machine_learning_min_detection_score": "Minimum detection score", - "machine_learning_min_detection_score_description": "Minimum confidence score for a face to be detected from 0-1. Lower values will detect more faces but may result in false positives.", - "machine_learning_min_recognized_faces": "Minimum recognized faces", - "machine_learning_min_recognized_faces_description": "The minimum number of recognized faces for a person to be created. Increasing this makes Facial Recognition more precise at the cost of increasing the chance that a face is not assigned to a person.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Use machine learning to recognize text in images", - "machine_learning_ocr_enabled": "Enable OCR", - "machine_learning_ocr_enabled_description": "If disabled, images will not undergo text recognition.", - "machine_learning_ocr_max_resolution": "Maximum resolution", - "machine_learning_ocr_max_resolution_description": "Previews above this resolution will be resized while preserving aspect ratio. Higher values are more accurate, but take longer to process and use more memory.", - "machine_learning_ocr_min_detection_score": "Minimum detection score", - "machine_learning_ocr_min_detection_score_description": "Minimum confidence score for text to be detected from 0-1. Lower values will detect more text but may result in false positives.", - "machine_learning_ocr_min_recognition_score": "Minimum recognition score", - "machine_learning_ocr_min_score_recognition_description": "Minimum confidence score for detected text to be recognized from 0-1. Lower values will recognize more text but may result in false positives.", - "machine_learning_ocr_model": "OCR model", - "machine_learning_ocr_model_description": "Server models are more accurate than mobile models, but take longer to process and use more memory.", - "machine_learning_settings": "Machine Learning Settings", - "machine_learning_settings_description": "Manage machine learning features and settings", - "machine_learning_smart_search": "Smart Search", - "machine_learning_smart_search_description": "Search for images semantically using CLIP embeddings", - "machine_learning_smart_search_enabled": "Enable smart search", - "machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.", - "machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.", - "maintenance_delete_backup": "Delete Backup", - "maintenance_delete_backup_description": "This file will be irrevocably deleted.", - "maintenance_delete_error": "Failed to delete backup.", - "maintenance_restore_backup": "Restore Backup", - "maintenance_restore_backup_description": "Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.", - "maintenance_restore_backup_different_version": "This backup was created with a different version of Immich!", - "maintenance_restore_backup_unknown_version": "Couldn't determine backup version.", - "maintenance_restore_database_backup": "Restore database backup", - "maintenance_restore_database_backup_description": "Rollback to an earlier database state using a backup file", - "maintenance_settings": "Maintenance", - "maintenance_settings_description": "Put Immich into maintenance mode.", - "maintenance_start": "Switch to maintenance mode", - "maintenance_start_error": "Failed to start maintenance mode.", - "maintenance_upload_backup": "Upload database backup file", - "maintenance_upload_backup_error": "Could not upload backup, is it an .sql/.sql.gz file?", - "manage_concurrency": "Manage Concurrency", - "manage_concurrency_description": "Navigate to the jobs page to manage job concurrency", - "manage_log_settings": "Manage log settings", - "map_dark_style": "Dark style", - "map_enable_description": "Enable map features", - "map_gps_settings": "Map & GPS Settings", - "map_gps_settings_description": "Manage Map & GPS (Reverse Geocoding) Settings", - "map_implications": "The map feature relies on an external tile service (tiles.immich.cloud)", - "map_light_style": "Light style", - "map_manage_reverse_geocoding_settings": "Manage Reverse Geocoding settings", - "map_reverse_geocoding": "Reverse Geocoding", - "map_reverse_geocoding_enable_description": "Enable reverse geocoding", - "map_reverse_geocoding_settings": "Reverse Geocoding Settings", - "map_settings": "Map", - "map_settings_description": "Manage map settings", - "map_style_description": "URL to a style.json map theme", - "memory_cleanup_job": "Memory cleanup", - "memory_generate_job": "Memory generation", - "metadata_extraction_job": "Extract metadata", - "metadata_extraction_job_description": "Extract metadata information from each asset, such as GPS, faces and resolution", - "metadata_faces_import_setting": "Enable face import", - "metadata_faces_import_setting_description": "Import faces from image EXIF data and sidecar files", - "metadata_settings": "Metadata Settings", - "metadata_settings_description": "Manage metadata settings", - "migration_job": "Migration", - "migration_job_description": "Migrate thumbnails for assets and faces to the latest folder structure", - "nightly_tasks_cluster_faces_setting_description": "Run facial recognition on newly detected faces", - "nightly_tasks_cluster_new_faces_setting": "Cluster new faces", - "nightly_tasks_database_cleanup_setting": "Database cleanup tasks", - "nightly_tasks_database_cleanup_setting_description": "Clean up old, expired data from the database", - "nightly_tasks_generate_memories_setting": "Generate memories", - "nightly_tasks_generate_memories_setting_description": "Create new memories from assets", - "nightly_tasks_missing_thumbnails_setting": "Generate missing thumbnails", - "nightly_tasks_missing_thumbnails_setting_description": "Queue assets without thumbnails for thumbnail generation", - "nightly_tasks_settings": "Nightly Tasks Settings", - "nightly_tasks_settings_description": "Manage nightly tasks", - "nightly_tasks_start_time_setting": "Start time", - "nightly_tasks_start_time_setting_description": "The time at which the server starts running the nightly tasks", - "nightly_tasks_sync_quota_usage_setting": "Sync quota usage", - "nightly_tasks_sync_quota_usage_setting_description": "Update user storage quota, based on current usage", - "no_paths_added": "No paths added", - "no_pattern_added": "No pattern added", - "note_apply_storage_label_previous_assets": "Note: To apply the Storage Label to previously uploaded assets, run the", - "note_cannot_be_changed_later": "NOTE: This cannot be changed later!", - "notification_email_from_address": "From address", - "notification_email_from_address_description": "Sender email address, for example: \"Immich Photo Server \". Make sure to use an address you're allowed to send emails from.", - "notification_email_host_description": "Host of the email server (e.g. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignore certificate errors", - "notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)", - "notification_email_password_description": "Password to use when authenticating with the email server", - "notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Use SMTPS (SMTP over TLS)", - "notification_email_sent_test_email_button": "Send test email and save", - "notification_email_setting_description": "Settings for sending email notifications", - "notification_email_test_email": "Send test email", - "notification_email_test_email_failed": "Failed to send test email, check your values", - "notification_email_test_email_sent": "A test email has been sent to {email}. Please check your inbox.", - "notification_email_username_description": "Username to use when authenticating with the email server", - "notification_enable_email_notifications": "Enable email notifications", - "notification_settings": "Notification Settings", - "notification_settings_description": "Manage notification settings, including email", - "oauth_auto_launch": "Auto launch", - "oauth_auto_launch_description": "Start the OAuth login flow automatically upon navigating to the login page", - "oauth_auto_register": "Auto register", - "oauth_auto_register_description": "Automatically register new users after signing in with OAuth", - "oauth_button_text": "Button text", - "oauth_client_secret_description": "Required for confidential client, or if PKCE (Proof Key for Code Exchange) is not supported for public client.", - "oauth_enable_description": "Login with OAuth", - "oauth_mobile_redirect_uri": "Mobile redirect URI", - "oauth_mobile_redirect_uri_override": "Mobile redirect URI override", - "oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''", - "oauth_role_claim": "Role Claim", - "oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Manage OAuth login settings", - "oauth_settings_more_details": "For more details about this feature, refer to the docs.", - "oauth_storage_label_claim": "Storage label claim", - "oauth_storage_label_claim_description": "Automatically set the user's storage label to the value of this claim.", - "oauth_storage_quota_claim": "Storage quota claim", - "oauth_storage_quota_claim_description": "Automatically set the user's storage quota to the value of this claim.", - "oauth_storage_quota_default": "Default storage quota (GiB)", - "oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided.", - "oauth_timeout": "Request Timeout", - "oauth_timeout_description": "Timeout for requests in milliseconds", - "ocr_job_description": "Use machine learning to recognize text in images", - "password_enable_description": "Login with email and password", - "password_settings": "Password Login", - "password_settings_description": "Manage password login settings", - "paths_validated_successfully": "All paths validated successfully", - "person_cleanup_job": "Person cleanup", - "queue_details": "Queue Details", - "queues": "Job Queues", - "queues_page_description": "Admin job queues page", - "quota_size_gib": "Quota Size (GiB)", - "refreshing_all_libraries": "Refreshing all libraries", - "registration": "Admin Registration", - "registration_description": "Since you are the first user on the system, you will be assigned as the Admin and are responsible for administrative tasks, and additional users will be created by you.", - "remove_failed_jobs": "Remove failed jobs", - "require_password_change_on_login": "Require user to change password on first login", - "reset_settings_to_default": "Reset settings to default", - "reset_settings_to_recent_saved": "Reset settings to the recent saved settings", - "scanning_library": "Scanning library", - "search_jobs": "Search jobs…", - "send_welcome_email": "Send welcome email", - "server_external_domain_settings": "External domain", - "server_external_domain_settings_description": "Domain for public shared links, including http(s)://", - "server_public_users": "Public Users", - "server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.", - "server_settings": "Server Settings", - "server_settings_description": "Manage server settings", - "server_stats_page_description": "Admin server statistics page", - "server_welcome_message": "Welcome message", - "server_welcome_message_description": "A message that is displayed on the login page.", - "settings_page_description": "Admin settings page", - "sidecar_job": "Sidecar metadata", - "sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem", - "slideshow_duration_description": "Number of seconds to display each image", - "smart_search_job_description": "Run machine learning on assets to support smart search", - "storage_template_date_time_description": "Asset's creation timestamp is used for the datetime information", - "storage_template_date_time_sample": "Sample time {date}", - "storage_template_enable_description": "Enable storage template engine", - "storage_template_hash_verification_enabled": "Hash verification enabled", - "storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications", - "storage_template_migration": "Storage template migration", - "storage_template_migration_description": "Apply the current {template} to previously uploaded assets", - "storage_template_migration_info": "The storage template will convert all extensions to lowercase. Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the {job}.", - "storage_template_migration_job": "Storage Template Migration Job", - "storage_template_more_details": "For more details about this feature, refer to the Storage Template and its implications", - "storage_template_onboarding_description_v2": "When enabled, this feature will auto-organize files based on a user-defined template. For more information, please see the documentation.", - "storage_template_path_length": "Approximate path length limit: {length, number}/{limit, number}", - "storage_template_settings": "Storage Template", - "storage_template_settings_description": "Manage the folder structure and file name of the upload asset", - "storage_template_user_label": "{label} is the user's Storage Label", - "system_settings": "System Settings", - "tag_cleanup_job": "Tag cleanup", - "template_email_available_tags": "You can use the following variables in your template: {tags}", - "template_email_if_empty": "If the template is empty, the default email will be used.", - "template_email_invite_album": "Invite Album Template", - "template_email_preview": "Preview", - "template_email_settings": "Email Templates", - "template_email_update_album": "Update Album Template", - "template_email_welcome": "Welcome email template", - "template_settings": "Notification Templates", - "template_settings_description": "Manage custom templates for notifications", - "theme_custom_css_settings": "Custom CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets allow the design of Immich to be customized.", - "theme_settings": "Theme Settings", - "theme_settings_description": "Manage customization of the Immich web interface", - "thumbnail_generation_job": "Generate Thumbnails", - "thumbnail_generation_job_description": "Generate large, small and blurred thumbnails for each asset, as well as thumbnails for each person", - "transcoding_acceleration_api": "Acceleration API", - "transcoding_acceleration_api_description": "The API that will interact with your device to accelerate transcoding. This setting is 'best effort': it will fallback to software transcoding on failure. VP9 may or may not work depending on your hardware.", - "transcoding_acceleration_nvenc": "NVENC (requires NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (requires 7th gen Intel CPU or later)", - "transcoding_acceleration_rkmpp": "RKMPP (only on Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Accepted audio codecs", - "transcoding_accepted_audio_codecs_description": "Select which audio codecs do not need to be transcoded. Only used for certain transcode policies.", - "transcoding_accepted_containers": "Accepted containers", - "transcoding_accepted_containers_description": "Select which container formats do not need to be remuxed to MP4. Only used for certain transcode policies.", - "transcoding_accepted_video_codecs": "Accepted video codecs", - "transcoding_accepted_video_codecs_description": "Select which video codecs do not need to be transcoded. Only used for certain transcode policies.", - "transcoding_advanced_options_description": "Options most users should not need to change", - "transcoding_audio_codec": "Audio codec", - "transcoding_audio_codec_description": "Opus is the highest quality option, but has lower compatibility with old devices or software.", - "transcoding_bitrate_description": "Videos higher than max bitrate or not in an accepted format", - "transcoding_codecs_learn_more": "To learn more about the terminology used here, refer to FFmpeg documentation for H.264 codec, HEVC codec and VP9 codec.", - "transcoding_constant_quality_mode": "Constant quality mode", - "transcoding_constant_quality_mode_description": "ICQ is better than CQP, but some hardware acceleration devices do not support this mode. Setting this option will prefer the specified mode when using quality-based encoding. Ignored by NVENC as it does not support ICQ.", - "transcoding_constant_rate_factor": "Constant rate factor (-crf)", - "transcoding_constant_rate_factor_description": "Video quality level. Typical values are 23 for H.264, 28 for HEVC, 31 for VP9, and 35 for AV1. Lower is better, but produces larger files.", - "transcoding_disabled_description": "Don't transcode any videos, may break playback on some clients", - "transcoding_encoding_options": "Encoding Options", - "transcoding_encoding_options_description": "Set codecs, resolution, quality and other options for the encoded videos", - "transcoding_hardware_acceleration": "Hardware Acceleration", - "transcoding_hardware_acceleration_description": "Experimental: faster transcoding but may reduce quality at same bitrate", - "transcoding_hardware_decoding": "Hardware decoding", - "transcoding_hardware_decoding_setting_description": "Enables end-to-end acceleration instead of only accelerating encoding. May not work on all videos.", - "transcoding_max_b_frames": "Maximum B-frames", - "transcoding_max_b_frames_description": "Higher values improve compression efficiency, but slow down encoding. May not be compatible with hardware acceleration on older devices. 0 disables B-frames, while -1 sets this value automatically.", - "transcoding_max_bitrate": "Maximum bitrate", - "transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0. When no unit is specified, k (for kbit/s) is assumed; therefore 5000, 5000k, and 5M (for Mbit/s) are equivalent.", - "transcoding_max_keyframe_interval": "Maximum keyframe interval", - "transcoding_max_keyframe_interval_description": "Sets the maximum frame distance between keyframes. Lower values worsen compression efficiency, but improve seek times and may improve quality in scenes with fast movement. 0 sets this value automatically.", - "transcoding_optimal_description": "Videos higher than target resolution or not in an accepted format", - "transcoding_policy": "Transcode Policy", - "transcoding_policy_description": "Set when a video will be transcoded", - "transcoding_preferred_hardware_device": "Preferred hardware device", - "transcoding_preferred_hardware_device_description": "Applies only to VAAPI and QSV. Sets the dri node used for hardware transcoding.", - "transcoding_preset_preset": "Preset (-preset)", - "transcoding_preset_preset_description": "Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above 'faster'.", - "transcoding_reference_frames": "Reference frames", - "transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.", - "transcoding_required_description": "Only videos not in an accepted format", - "transcoding_settings": "Video Transcoding Settings", - "transcoding_settings_description": "Manage which videos to transcode and how to process them", - "transcoding_target_resolution": "Target resolution", - "transcoding_target_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.", - "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Applies only to NVENC. Temporal Adaptive Quantization increases quality of high-detail, low-motion scenes. May not be compatible with older devices.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximizes utilization if set to 0.", - "transcoding_tone_mapping": "Tone-mapping", - "transcoding_tone_mapping_description": "Attempts to preserve the appearance of HDR videos when converted to SDR. Each algorithm makes different tradeoffs for color, detail and brightness. Hable preserves detail, Mobius preserves color, and Reinhard preserves brightness.", - "transcoding_transcode_policy": "Transcode policy", - "transcoding_transcode_policy_description": "Policy for when a video should be transcoded. HDR videos will always be transcoded (except if transcoding is disabled).", - "transcoding_two_pass_encoding": "Two-pass encoding", - "transcoding_two_pass_encoding_setting_description": "Transcode in two passes to produce better encoded videos. When max bitrate is enabled (required for it to work with H.264 and HEVC), this mode uses a bitrate range based on the max bitrate and ignores CRF. For VP9, CRF can be used if max bitrate is disabled.", - "transcoding_video_codec": "Video codec", - "transcoding_video_codec_description": "VP9 has high efficiency and web compatibility, but takes longer to transcode. HEVC performs similarly, but has lower web compatibility. H.264 is widely compatible and quick to transcode, but produces much larger files. AV1 is the most efficient codec but lacks support on older devices.", - "trash_enabled_description": "Enable Trash features", - "trash_number_of_days": "Number of days", - "trash_number_of_days_description": "Number of days to keep the assets in trash before permanently removing them", - "trash_settings": "Trash Settings", - "trash_settings_description": "Manage trash settings", - "unlink_all_oauth_accounts": "Unlink all OAuth accounts", - "unlink_all_oauth_accounts_description": "Remember to unlink all OAuth accounts before migrating to a new provider.", - "unlink_all_oauth_accounts_prompt": "Are you sure you want to unlink all OAuth accounts? This will reset the OAuth ID for each user and cannot be undone.", - "user_cleanup_job": "User cleanup", - "user_delete_delay": "{user}'s account and assets will be scheduled for permanent deletion in {delay, plural, one {# day} other {# days}}.", - "user_delete_delay_settings": "Delete delay", - "user_delete_delay_settings_description": "Number of days after removal to permanently delete a user's account and assets. The user deletion job runs at midnight to check for users that are ready for deletion. Changes to this setting will be evaluated at the next execution.", - "user_delete_immediately": "{user}'s account and assets will be queued for permanent deletion immediately.", - "user_delete_immediately_checkbox": "Queue user and assets for immediate deletion", - "user_details": "User Details", - "user_management": "User Management", - "user_password_has_been_reset": "The user's password has been reset:", - "user_password_reset_description": "Please provide the temporary password to the user and inform them they will need to change the password at their next login.", - "user_restore_description": "{user}'s account will be restored.", - "user_restore_scheduled_removal": "Restore user - scheduled removal on {date, date, long}", - "user_settings": "User Settings", - "user_settings_description": "Manage user settings", - "user_successfully_removed": "User {email} has been successfully removed.", - "users_page_description": "Admin users page", - "version_check_enabled_description": "Enable version check", - "version_check_implications": "The version check feature relies on periodic communication with github.com", - "version_check_settings": "Version Check", - "version_check_settings_description": "Enable/disable the new version notification", - "video_conversion_job": "Transcode videos", - "video_conversion_job_description": "Transcode videos for wider compatibility with browsers and devices" - }, - "admin_email": "Admin Email", - "admin_password": "Admin Password", - "administration": "Administration", - "advanced": "Advanced", - "advanced_settings_clear_image_cache": "Clear Image Cache", - "advanced_settings_clear_image_cache_error": "Failed to clear image cache", - "advanced_settings_clear_image_cache_success": "Successfully cleared {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter", - "advanced_settings_log_level_title": "Log level: {level}", - "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.", - "advanced_settings_prefer_remote_title": "Prefer remote images", - "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", - "advanced_settings_proxy_headers_title": "Custom proxy headers [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen", - "advanced_settings_readonly_mode_title": "Read-only mode", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates [EXPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web", - "advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Advanced user's settings", - "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", - "advanced_settings_troubleshooting_title": "Troubleshooting", - "age_months": "Age {months, plural, one {# month} other {# months}}", - "age_year_months": "Age 1 year, {months, plural, one {# month} other {# months}}", - "age_years": "{years, plural, other {Age #}}", - "album": "Album", - "album_added": "Album added", - "album_added_notification_setting_description": "Receive an email notification when you are added to a shared album", - "album_cover_updated": "Album cover updated", - "album_delete_confirmation": "Are you sure you want to delete the album {album}?", - "album_delete_confirmation_description": "If this album is shared, other users will not be able to access it anymore.", - "album_deleted": "Album deleted", - "album_info_card_backup_album_excluded": "EXCLUDED", - "album_info_card_backup_album_included": "INCLUDED", - "album_info_updated": "Album info updated", - "album_leave": "Leave album?", - "album_leave_confirmation": "Are you sure you want to leave {album}?", - "album_name": "Album Name", - "album_options": "Album options", - "album_remove_user": "Remove user?", - "album_remove_user_confirmation": "Are you sure you want to remove {user}?", - "album_search_not_found": "No albums found matching your search", - "album_selected": "Album selected", - "album_share_no_users": "Looks like you have shared this album with all users or you don't have any user to share with.", - "album_summary": "Album summary", - "album_updated": "Album updated", - "album_updated_setting_description": "Receive an email notification when a shared album has new assets", - "album_upload_assets": "Upload assets from your computer and add to album", - "album_user_left": "Left {album}", - "album_user_removed": "Removed {user}", - "album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?", - "album_viewer_appbar_share_err_delete": "Failed to delete album", - "album_viewer_appbar_share_err_leave": "Failed to leave album", - "album_viewer_appbar_share_err_remove": "There are problems in removing assets from album", - "album_viewer_appbar_share_err_title": "Failed to change album title", - "album_viewer_appbar_share_leave": "Leave album", - "album_viewer_appbar_share_to": "Share To", - "album_viewer_page_share_add_users": "Add users", - "album_with_link_access": "Let anyone with the link see photos and people in this album.", - "albums": "Albums", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albums}}", - "albums_default_sort_order": "Default album sort order", - "albums_default_sort_order_description": "Initial asset sort order when creating new albums.", - "albums_feature_description": "Collections of assets that can be shared with other users.", - "albums_on_device_count": "Albums on device ({count})", - "albums_selected": "{count, plural, one {# album selected} other {# albums selected}}", - "all": "All", - "all_albums": "All albums", - "all_people": "All people", - "all_photos": "All photos", - "all_videos": "All videos", - "allow_dark_mode": "Allow dark mode", - "allow_edits": "Allow edits", - "allow_public_user_to_download": "Allow public user to download", - "allow_public_user_to_upload": "Allow public user to upload", - "allowed": "Allowed", - "alt_text_qr_code": "QR code image", - "always_keep": "Always keep", - "always_keep_photos_hint": "Free Up Space will keep all photos on this device.", - "always_keep_videos_hint": "Free Up Space will keep all videos on this device.", - "anti_clockwise": "Anti-clockwise", - "api_key": "API Key", - "api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.", - "api_key_empty": "Your API Key name shouldn't be empty", - "api_keys": "API Keys", - "app_architecture_variant": "Variant (Architecture)", - "app_bar_signout_dialog_content": "Are you sure you want to sign out?", - "app_bar_signout_dialog_ok": "Yes", - "app_bar_signout_dialog_title": "Sign out", - "app_download_links": "App Download Links", - "app_settings": "App Settings", - "app_stores": "App Stores", - "app_update_available": "App update is available", - "appears_in": "Appears in", - "apply_count": "Apply ({count, number})", - "archive": "Archive", - "archive_action_prompt": "{count} added to Archive", - "archive_or_unarchive_photo": "Archive or unarchive photo", - "archive_page_no_archived_assets": "No archived assets found", - "archive_page_title": "Archive ({count})", - "archive_size": "Archive size", - "archive_size_description": "Configure the archive size for downloads (in GiB)", - "archived": "Archived", - "archived_count": "{count, plural, other {Archived #}}", - "are_these_the_same_person": "Are these the same person?", - "are_you_sure_to_do_this": "Are you sure you want to do this?", - "array_field_not_fully_supported": "Array fields require manual JSON editing", - "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", - "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", - "asset_added_to_album": "Added to album", - "asset_adding_to_album": "Adding to album…", - "asset_created": "Asset created", - "asset_description_updated": "Asset description has been updated", - "asset_filename_is_offline": "Asset {filename} is offline", - "asset_has_unassigned_faces": "Asset has unassigned faces", - "asset_hashing": "Hashing…", - "asset_list_group_by_sub_title": "Group by", - "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", - "asset_list_layout_settings_group_automatically": "Automatic", - "asset_list_layout_settings_group_by": "Group assets by", - "asset_list_layout_settings_group_by_month_day": "Month + day", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Photo grid layout settings", - "asset_list_settings_title": "Photo Grid", - "asset_not_found_on_device_android": "Asset not found on device", - "asset_not_found_on_device_ios": "Asset not found on device. If you are using iCloud, the asset may be inaccessible due to bad file stored on iCloud", - "asset_not_found_on_icloud": "Asset not found on iCloud. the asset may be inaccessible due to bad file stored on iCloud", - "asset_offline": "Asset Offline", - "asset_offline_description": "This external asset is no longer found on disk. Please contact your Immich administrator for help.", - "asset_restored_successfully": "Asset restored successfully", - "asset_skipped": "Skipped", - "asset_skipped_in_trash": "In trash", - "asset_trashed": "Asset trashed", - "asset_troubleshoot": "Asset Troubleshoot", - "asset_uploaded": "Uploaded", - "asset_uploading": "Uploading…", - "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", - "asset_viewer_settings_title": "Asset Viewer", - "assets": "Assets", - "assets_added_count": "Added {count, plural, one {# asset} other {# assets}}", - "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", - "assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums", - "assets_count": "{count, plural, one {# asset} other {# assets}}", - "assets_deleted_permanently": "{count} asset(s) deleted permanently", - "assets_deleted_permanently_from_server": "{count} asset(s) deleted permanently from the Immich server", - "assets_downloaded_failed": "{count, plural, one {Downloaded # file - {error} file failed} other {Downloaded # files - {error} files failed}}", - "assets_downloaded_successfully": "{count, plural, one {Downloaded # file successfully} other {Downloaded # files successfully}}", - "assets_moved_to_trash_count": "Moved {count, plural, one {# asset} other {# assets}} to trash", - "assets_permanently_deleted_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", - "assets_removed_count": "Removed {count, plural, one {# asset} other {# assets}}", - "assets_removed_permanently_from_device": "{count} asset(s) removed permanently from your device", - "assets_restore_confirmation": "Are you sure you want to restore all your trashed assets? You cannot undo this action! Note that any offline assets cannot be restored this way.", - "assets_restored_count": "Restored {count, plural, one {# asset} other {# assets}}", - "assets_restored_successfully": "{count} asset(s) restored successfully", - "assets_trashed": "{count} asset(s) trashed", - "assets_trashed_count": "Trashed {count, plural, one {# asset} other {# assets}}", - "assets_trashed_from_server": "{count} asset(s) trashed from the Immich server", - "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} already part of the album", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} already part of the albums", - "authorized_devices": "Authorized Devices", - "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", - "automatic_endpoint_switching_title": "Automatic URL switching", - "autoplay_slideshow": "Autoplay slideshow", - "back": "Back", - "back_close_deselect": "Back, close, or deselect", - "background_backup_running_error": "Background backup is currently running, cannot start manual backup", - "background_location_permission": "Background location permission", - "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", - "background_options": "Background Options", - "backup": "Backup", - "backup_album_selection_page_albums_device": "Albums on device ({count})", - "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", - "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", - "backup_album_selection_page_select_albums": "Select albums", - "backup_album_selection_page_selection_info": "Selection Info", - "backup_album_selection_page_total_assets": "Total unique assets", - "backup_albums_sync": "Backup Albums Synchronization", - "backup_all": "All", - "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", - "backup_background_service_complete_notification": "Asset backup complete", - "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…", - "backup_background_service_current_upload_notification": "Uploading {filename}", - "backup_background_service_default_notification": "Checking for new assets…", - "backup_background_service_error_title": "Backup error", - "backup_background_service_in_progress_notification": "Backing up your assets…", - "backup_background_service_upload_failure_notification": "Failed to upload {filename}", - "backup_controller_page_albums": "Backup Albums", - "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", - "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", - "backup_controller_page_background_app_refresh_enable_button_text": "Go to settings", - "backup_controller_page_background_battery_info_link": "Show me how", - "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Battery optimizations", - "backup_controller_page_background_charging": "Only while charging", - "backup_controller_page_background_configure_error": "Failed to configure the background service", - "backup_controller_page_background_delay": "Delay new assets backup: {duration}", - "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", - "backup_controller_page_background_is_off": "Automatic background backup is off", - "backup_controller_page_background_is_on": "Automatic background backup is on", - "backup_controller_page_background_turn_off": "Turn off background service", - "backup_controller_page_background_turn_on": "Turn on background service", - "backup_controller_page_background_wifi": "Only on Wi-Fi", - "backup_controller_page_backup": "Backup", - "backup_controller_page_backup_selected": "Selected: ", - "backup_controller_page_backup_sub": "Backed up photos and videos", - "backup_controller_page_created": "Created on: {date}", - "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", - "backup_controller_page_excluded": "Excluded: ", - "backup_controller_page_failed": "Failed ({count})", - "backup_controller_page_filename": "File name: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Backup Information", - "backup_controller_page_none_selected": "None selected", - "backup_controller_page_remainder": "Remainder", - "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", - "backup_controller_page_server_storage": "Server Storage", - "backup_controller_page_start_backup": "Start Backup", - "backup_controller_page_status_off": "Automatic foreground backup is off", - "backup_controller_page_status_on": "Automatic foreground backup is on", - "backup_controller_page_storage_format": "{used} of {total} used", - "backup_controller_page_to_backup": "Albums to be backed up", - "backup_controller_page_total_sub": "All unique photos and videos from selected albums", - "backup_controller_page_turn_off": "Turn off foreground backup", - "backup_controller_page_turn_on": "Turn on foreground backup", - "backup_controller_page_uploading_file_info": "Uploading file info", - "backup_err_only_album": "Cannot remove the only album", - "backup_error_sync_failed": "Sync failed. Cannot process backup.", - "backup_info_card_assets": "assets", - "backup_manual_cancelled": "Cancelled", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", - "backup_options": "Backup Options", - "backup_options_page_title": "Backup options", - "backup_setting_subtitle": "Manage background and foreground upload settings", - "backup_settings_subtitle": "Manage upload settings", - "backup_upload_details_page_more_details": "Tap for more details", - "backward": "Backward", - "biometric_auth_enabled": "Biometric authentication enabled", - "biometric_locked_out": "You are locked out of biometric authentication", - "biometric_no_options": "No biometric options available", - "biometric_not_available": "Biometric authentication is not available on this device", - "birthdate_saved": "Date of birth saved successfully", - "birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.", - "blurred_background": "Blurred background", - "bugs_and_feature_requests": "Bugs & Feature Requests", - "build": "Build", - "build_image": "Build Image", - "bulk_delete_duplicates_confirmation": "Are you sure you want to bulk delete {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and permanently delete all other duplicates. You cannot undo this action!", - "bulk_keep_duplicates_confirmation": "Are you sure you want to keep {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will resolve all duplicate groups without deleting anything.", - "bulk_trash_duplicates_confirmation": "Are you sure you want to bulk trash {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and trash all other duplicates.", - "buy": "Purchase Immich", - "cache_settings_clear_cache_button": "Clear cache", - "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", - "cache_settings_duplicated_assets_clear_button": "CLEAR", - "cache_settings_duplicated_assets_subtitle": "Photos and videos that are ignore listed by the app", - "cache_settings_duplicated_assets_title": "Duplicated Assets ({count})", - "cache_settings_statistics_album": "Library thumbnails", - "cache_settings_statistics_full": "Full images", - "cache_settings_statistics_shared": "Shared album thumbnails", - "cache_settings_statistics_thumbnail": "Thumbnails", - "cache_settings_statistics_title": "Cache usage", - "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", - "cache_settings_tile_subtitle": "Control the local storage behaviour", - "cache_settings_tile_title": "Local Storage", - "cache_settings_title": "Caching Settings", - "camera": "Camera", - "camera_brand": "Camera brand", - "camera_model": "Camera model", - "cancel": "Cancel", - "cancel_search": "Cancel search", - "canceled": "Canceled", - "canceling": "Canceling", - "cannot_merge_people": "Cannot merge people", - "cannot_undo_this_action": "You cannot undo this action!", - "cannot_update_the_description": "Cannot update the description", - "cast": "Cast", - "cast_description": "Configure available cast destinations", - "change_date": "Change date", - "change_description": "Change description", - "change_display_order": "Change display order", - "change_expiration_time": "Change expiration time", - "change_location": "Change location", - "change_name": "Change name", - "change_name_successfully": "Changed name successfully", - "change_password": "Change Password", - "change_password_description": "This is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", - "change_password_form_confirm_password": "Confirm Password", - "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", - "change_password_form_log_out": "Log out all other devices", - "change_password_form_log_out_description": "It is recommended to log out of all other devices", - "change_password_form_new_password": "New Password", - "change_password_form_password_mismatch": "Passwords do not match", - "change_password_form_reenter_new_password": "Re-enter New Password", - "change_pin_code": "Change PIN code", - "change_trigger": "Change trigger", - "change_trigger_prompt": "Are you sure you want to change the trigger? This will remove all existing actions and filters.", - "change_your_password": "Change your password", - "changed_visibility_successfully": "Changed visibility successfully", - "charging": "Charging", - "charging_requirement_mobile_backup": "Background backup requires the device to be charging", - "check_corrupt_asset_backup": "Check for corrupt asset backups", - "check_corrupt_asset_backup_button": "Perform check", - "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", - "check_logs": "Check Logs", - "checksum": "Checksum", - "choose_matching_people_to_merge": "Choose matching people to merge", - "city": "City", - "cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?", - "cleanup_confirm_prompt_title": "Remove from this device?", - "cleanup_deleted_assets": "Moved {count} assets to device trash", - "cleanup_deleting": "Moving to trash...", - "cleanup_found_assets": "Found {count} backed up assets", - "cleanup_found_assets_with_size": "Found {count} backed up assets ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud Shared Albums are excluded from the scan", - "cleanup_no_assets_found": "No assets found matching the criteria above. Free Up Space can only remove assets that have been backed up to the server", - "cleanup_preview_title": "Assets to remove ({count})", - "cleanup_step3_description": "Scan for backed up assets matching your date and keep settings.", - "cleanup_step4_summary": "{count} assets (created before {date}) to remove from your local device. Photos will remain accessible from the Immich app.", - "cleanup_trash_hint": "To fully reclaim storage space, open the system gallery app and empty the trash", - "clear": "Clear", - "clear_all": "Clear all", - "clear_all_recent_searches": "Clear all recent searches", - "clear_file_cache": "Clear File Cache", - "clear_message": "Clear message", - "clear_value": "Clear value", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Enter Password", - "client_cert_import": "Import", - "client_cert_import_success_msg": "Client certificate is imported", - "client_cert_invalid_msg": "Invalid certificate file or wrong password", - "client_cert_password_message": "Enter the password for this certificate", - "client_cert_password_title": "Certificate Password", - "client_cert_remove_msg": "Client certificate is removed", - "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login", - "client_cert_title": "SSL client certificate [EXPERIMENTAL]", - "clockwise": "Сlockwise", - "close": "Close", - "collapse": "Collapse", - "collapse_all": "Collapse all", - "color": "Color", - "color_theme": "Color theme", - "command": "Command", - "comment_deleted": "Comment deleted", - "comment_options": "Comment options", - "comments_and_likes": "Comments & likes", - "comments_are_disabled": "Comments are disabled", - "common_create_new_album": "Create new album", - "completed": "Completed", - "confirm": "Confirm", - "confirm_admin_password": "Confirm Admin Password", - "confirm_delete_face": "Are you sure you want to delete {name} face from the asset?", - "confirm_delete_shared_link": "Are you sure you want to delete this shared link?", - "confirm_keep_this_delete_others": "All other assets in the stack will be deleted except for this asset. Are you sure you want to continue?", - "confirm_new_pin_code": "Confirm new PIN code", - "confirm_password": "Confirm password", - "confirm_tag_face": "Do you want to tag this face as {name}?", - "confirm_tag_face_unnamed": "Do you want to tag this face?", - "connected_device": "Connected device", - "connected_to": "Connected to", - "contain": "Contain", - "context": "Context", - "continue": "Continue", - "control_bottom_app_bar_create_new_album": "Create new album", - "control_bottom_app_bar_delete_from_immich": "Delete from Immich", - "control_bottom_app_bar_delete_from_local": "Delete from device", - "control_bottom_app_bar_edit_location": "Edit Location", - "control_bottom_app_bar_edit_time": "Edit Date & Time", - "control_bottom_app_bar_share_link": "Share Link", - "control_bottom_app_bar_share_to": "Share To", - "control_bottom_app_bar_trash_from_immich": "Move to Trash", - "copied_image_to_clipboard": "Copied image to clipboard.", - "copied_to_clipboard": "Copied to clipboard!", - "copy_error": "Copy error", - "copy_file_path": "Copy file path", - "copy_image": "Copy Image", - "copy_link": "Copy link", - "copy_link_to_clipboard": "Copy link to clipboard", - "copy_password": "Copy password", - "copy_to_clipboard": "Copy to Clipboard", - "country": "Country", - "cover": "Cover", - "covers": "Covers", - "create": "Create", - "create_album": "Create album", - "create_album_page_untitled": "Untitled", - "create_api_key": "Create API key", - "create_first_workflow": "Create first workflow", - "create_library": "Create Library", - "create_link": "Create link", - "create_link_to_share": "Create link to share", - "create_link_to_share_description": "Let anyone with the link see the selected photo(s)", - "create_new": "CREATE NEW", - "create_new_person": "Create new person", - "create_new_person_hint": "Assign selected assets to a new person", - "create_new_user": "Create new user", - "create_shared_album_page_share_add_assets": "ADD ASSETS", - "create_shared_album_page_share_select_photos": "Select Photos", - "create_shared_link": "Create shared link", - "create_tag": "Create tag", - "create_tag_description": "Create a new tag. For nested tags, please enter the full path of the tag including forward slashes.", - "create_user": "Create user", - "create_workflow": "Create workflow", - "created": "Created", - "created_at": "Created", - "creating_linked_albums": "Creating linked albums...", - "crop": "Crop", - "crop_aspect_ratio_fixed": "Fixed", - "crop_aspect_ratio_free": "Free", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Things", - "current_device": "Current device", - "current_pin_code": "Current PIN code", - "current_server_address": "Current server address", - "custom_date": "Custom date", - "custom_locale": "Custom Locale", - "custom_locale_description": "Format dates and numbers based on the language and the region", - "custom_url": "Custom URL", - "cutoff_date_description": "Keep photos from the last…", - "cutoff_day": "{count, plural, one {day} other {days}}", - "cutoff_year": "{count, plural, one {year} other {years}}", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "Dark", - "dark_theme": "Toggle dark theme", - "date": "Date", - "date_after": "Date after", - "date_and_time": "Date and Time", - "date_before": "Date before", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "Date of birth saved successfully", - "date_range": "Date range", - "day": "Day", - "days": "Days", - "deduplicate_all": "Deduplicate All", - "deduplication_criteria_1": "Image size in bytes", - "deduplication_criteria_2": "Count of EXIF data", - "deduplication_info": "Deduplication Info", - "deduplication_info_description": "To automatically preselect assets and remove duplicates in bulk, we look at:", - "default_locale": "Default Locale", - "default_locale_description": "Format dates and numbers based on your browser locale", - "delete": "Delete", - "delete_action_confirmation_message": "Are you sure you want to delete this asset? This action will move the asset to the server's trash and will prompt if you want to delete it locally", - "delete_action_prompt": "{count} deleted", - "delete_album": "Delete album", - "delete_api_key_prompt": "Are you sure you want to delete this API key?", - "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", - "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", - "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", - "delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", - "delete_dialog_ok_force": "Delete Anyway", - "delete_dialog_title": "Delete Permanently", - "delete_duplicates_confirmation": "Are you sure you want to permanently delete these duplicates?", - "delete_face": "Delete face", - "delete_key": "Delete key", - "delete_library": "Delete Library", - "delete_link": "Delete link", - "delete_local_action_prompt": "{count} deleted locally", - "delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", - "delete_local_dialog_ok_force": "Delete Anyway", - "delete_others": "Delete others", - "delete_permanently": "Delete permanently", - "delete_permanently_action_prompt": "{count} deleted permanently", - "delete_shared_link": "Delete shared link", - "delete_shared_link_dialog_title": "Delete Shared Link", - "delete_tag": "Delete tag", - "delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?", - "delete_user": "Delete user", - "deleted_shared_link": "Deleted shared link", - "deletes_missing_assets": "Deletes assets missing from disk", - "description": "Description", - "description_input_hint_text": "Add description...", - "description_input_submit_error": "Error updating description, check the log for more details", - "deselect_all": "Deselect All", - "details": "Details", - "direction": "Direction", - "disable": "Disable", - "disabled": "Disabled", - "disallow_edits": "Disallow edits", - "discord": "Discord", - "discover": "Discover", - "discovered_devices": "Discovered devices", - "dismiss_all_errors": "Dismiss all errors", - "dismiss_error": "Dismiss error", - "display_options": "Display options", - "display_order": "Display order", - "display_original_photos": "Display original photos", - "display_original_photos_setting_description": "Prefer to display the original photo when viewing an asset rather than thumbnails when the original asset is web-compatible. This may result in slower photo display speeds.", - "do_not_show_again": "Do not show this message again", - "documentation": "Documentation", - "done": "Done", - "download": "Download", - "download_action_prompt": "Downloading {count} assets", - "download_canceled": "Download canceled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", - "download_error": "Download Error", - "download_failed": "Download failed", - "download_finished": "Download finished", - "download_include_embedded_motion_videos": "Embedded videos", - "download_include_embedded_motion_videos_description": "Include videos embedded in motion photos as a separate file", - "download_notfound": "Download not found", - "download_original": "Download original", - "download_paused": "Download paused", - "download_settings": "Download", - "download_settings_description": "Manage settings related to asset download", - "download_started": "Download started", - "download_sucess": "Download success", - "download_sucess_android": "The media has been downloaded to DCIM/Immich", - "download_waiting_to_retry": "Waiting to retry", - "downloading": "Downloading", - "downloading_asset_filename": "Downloading asset {filename}", - "downloading_from_icloud": "Downloading from iCloud", - "downloading_media": "Downloading media", - "drop_files_to_upload": "Drop files anywhere to upload", - "duplicates": "Duplicates", - "duplicates_description": "Resolve each group by indicating which, if any, are duplicates", - "duration": "Duration", - "edit": "Edit", - "edit_album": "Edit album", - "edit_avatar": "Edit avatar", - "edit_birthday": "Edit birthday", - "edit_date": "Edit date", - "edit_date_and_time": "Edit date and time", - "edit_date_and_time_action_prompt": "{count} date and time edited", - "edit_date_and_time_by_offset": "Change date by offset", - "edit_date_and_time_by_offset_interval": "New date range: {from} - {to}", - "edit_description": "Edit description", - "edit_description_prompt": "Please select a new description:", - "edit_exclusion_pattern": "Edit exclusion pattern", - "edit_faces": "Edit faces", - "edit_key": "Edit key", - "edit_link": "Edit link", - "edit_location": "Edit location", - "edit_location_action_prompt": "{count} location edited", - "edit_location_dialog_title": "Location", - "edit_name": "Edit name", - "edit_people": "Edit people", - "edit_tag": "Edit tag", - "edit_title": "Edit Title", - "edit_user": "Edit user", - "edit_workflow": "Edit workflow", - "editor": "Editor", - "editor_close_without_save_prompt": "The changes will not be saved", - "editor_close_without_save_title": "Close editor?", - "editor_confirm_reset_all_changes": "Are you sure you want to reset all changes?", - "editor_discard_edits_confirm": "Discard edits", - "editor_discard_edits_prompt": "You have unsaved edits. Are you sure you want to discard them?", - "editor_discard_edits_title": "Discard edits?", - "editor_edits_applied_error": "Failed to apply edits", - "editor_edits_applied_success": "Edits applied successfully", - "editor_flip_horizontal": "Flip horizontal", - "editor_flip_vertical": "Flip vertical", - "editor_orientation": "Orientation", - "editor_reset_all_changes": "Reset changes", - "editor_rotate_left": "Rotate 90° counterclockwise", - "editor_rotate_right": "Rotate 90° clockwise", - "email": "Email", - "email_notifications": "Email notifications", - "empty_folder": "This folder is empty", - "empty_trash": "Empty trash", - "empty_trash_confirmation": "Are you sure you want to empty the trash? This will remove all the assets in trash permanently from Immich.\nYou cannot undo this action!", - "enable": "Enable", - "enable_backup": "Enable Backup", - "enable_biometric_auth_description": "Enter your PIN code to enable biometric authentication", - "enabled": "Enabled", - "end_date": "End date", - "enqueued": "Enqueued", - "enter_wifi_name": "Enter Wi-Fi name", - "enter_your_pin_code": "Enter your PIN code", - "enter_your_pin_code_subtitle": "Enter your PIN code to access the locked folder", - "error": "Error", - "error_change_sort_album": "Failed to change album sort order", - "error_delete_face": "Error deleting face from asset", - "error_getting_places": "Error getting places", - "error_loading_albums": "Error loading albums", - "error_loading_image": "Error loading image", - "error_loading_partners": "Error loading partners: {error}", - "error_retrieving_asset_information": "Error retrieving asset information", - "error_saving_image": "Error: {error}", - "error_tag_face_bounding_box": "Error tagging face - cannot get bounding box coordinates", - "error_title": "Error - Something went wrong", - "error_while_navigating": "Error while navigating to asset", - "errors": { - "cannot_navigate_next_asset": "Cannot navigate to the next asset", - "cannot_navigate_previous_asset": "Cannot navigate to previous asset", - "cant_apply_changes": "Can't apply changes", - "cant_change_activity": "Can't {enabled, select, true {disable} other {enable}} activity", - "cant_change_asset_favorite": "Can't change favorite for asset", - "cant_change_metadata_assets_count": "Can't change metadata of {count, plural, one {# asset} other {# assets}}", - "cant_get_faces": "Can't get faces", - "cant_get_number_of_comments": "Can't get number of comments", - "cant_search_people": "Can't search people", - "cant_search_places": "Can't search places", - "error_adding_assets_to_album": "Error adding assets to album", - "error_adding_users_to_album": "Error adding users to album", - "error_deleting_shared_user": "Error deleting shared user", - "error_downloading": "Error downloading {filename}", - "error_hiding_buy_button": "Error hiding buy button", - "error_removing_assets_from_album": "Error removing assets from album, check console for more details", - "error_selecting_all_assets": "Error selecting all assets", - "exclusion_pattern_already_exists": "This exclusion pattern already exists.", - "failed_to_create_album": "Failed to create album", - "failed_to_create_shared_link": "Failed to create shared link", - "failed_to_edit_shared_link": "Failed to edit shared link", - "failed_to_get_people": "Failed to get people", - "failed_to_keep_this_delete_others": "Failed to keep this asset and delete the other assets", - "failed_to_load_asset": "Failed to load asset", - "failed_to_load_assets": "Failed to load assets", - "failed_to_load_notifications": "Failed to load notifications", - "failed_to_load_people": "Failed to load people", - "failed_to_remove_product_key": "Failed to remove product key", - "failed_to_reset_pin_code": "Failed to reset PIN code", - "failed_to_stack_assets": "Failed to stack assets", - "failed_to_unstack_assets": "Failed to un-stack assets", - "failed_to_update_notification_status": "Failed to update notification status", - "incorrect_email_or_password": "Incorrect email or password", - "library_folder_already_exists": "This import path already exists.", - "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation", - "profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.", - "quota_higher_than_disk_size": "You set a quota higher than the disk size", - "something_went_wrong": "Something went wrong", - "unable_to_add_album_users": "Unable to add users to album", - "unable_to_add_assets_to_shared_link": "Unable to add assets to shared link", - "unable_to_add_comment": "Unable to add comment", - "unable_to_add_exclusion_pattern": "Unable to add exclusion pattern", - "unable_to_add_partners": "Unable to add partners", - "unable_to_add_remove_archive": "Unable to {archived, select, true {remove asset from} other {add asset to}} archive", - "unable_to_add_remove_favorites": "Unable to {favorite, select, true {add asset to} other {remove asset from}} favorites", - "unable_to_archive_unarchive": "Unable to {archived, select, true {archive} other {unarchive}}", - "unable_to_change_album_user_role": "Unable to change the album user's role", - "unable_to_change_date": "Unable to change date", - "unable_to_change_description": "Unable to change description", - "unable_to_change_favorite": "Unable to change favorite for asset", - "unable_to_change_location": "Unable to change location", - "unable_to_change_password": "Unable to change password", - "unable_to_change_visibility": "Unable to change the visibility for {count, plural, one {# person} other {# people}}", - "unable_to_complete_oauth_login": "Unable to complete OAuth login", - "unable_to_connect": "Unable to connect", - "unable_to_copy_to_clipboard": "Cannot copy to clipboard, make sure you are accessing the page through https", - "unable_to_create": "Unable to create workflow", - "unable_to_create_admin_account": "Unable to create admin account", - "unable_to_create_api_key": "Unable to create a new API Key", - "unable_to_create_library": "Unable to create library", - "unable_to_create_user": "Unable to create user", - "unable_to_delete_album": "Unable to delete album", - "unable_to_delete_asset": "Unable to delete asset", - "unable_to_delete_assets": "Error deleting assets", - "unable_to_delete_exclusion_pattern": "Unable to delete exclusion pattern", - "unable_to_delete_shared_link": "Unable to delete shared link", - "unable_to_delete_user": "Unable to delete user", - "unable_to_delete_workflow": "Unable to delete workflow", - "unable_to_download_files": "Unable to download files", - "unable_to_edit_exclusion_pattern": "Unable to edit exclusion pattern", - "unable_to_empty_trash": "Unable to empty trash", - "unable_to_enter_fullscreen": "Unable to enter fullscreen", - "unable_to_exit_fullscreen": "Unable to exit fullscreen", - "unable_to_get_comments_number": "Unable to get number of comments", - "unable_to_get_shared_link": "Failed to get shared link", - "unable_to_hide_person": "Unable to hide person", - "unable_to_link_motion_video": "Unable to link motion video", - "unable_to_link_oauth_account": "Unable to link OAuth account", - "unable_to_log_out_all_devices": "Unable to log out all devices", - "unable_to_log_out_device": "Unable to log out device", - "unable_to_login_with_oauth": "Unable to login with OAuth", - "unable_to_play_video": "Unable to play video", - "unable_to_reassign_assets_existing_person": "Unable to reassign assets to {name, select, null {an existing person} other {{name}}}", - "unable_to_reassign_assets_new_person": "Unable to reassign assets to a new person", - "unable_to_refresh_user": "Unable to refresh user", - "unable_to_remove_album_users": "Unable to remove users from album", - "unable_to_remove_api_key": "Unable to remove API Key", - "unable_to_remove_assets_from_shared_link": "Unable to remove assets from shared link", - "unable_to_remove_library": "Unable to remove library", - "unable_to_remove_partner": "Unable to remove partner", - "unable_to_remove_reaction": "Unable to remove reaction", - "unable_to_reset_password": "Unable to reset password", - "unable_to_reset_pin_code": "Unable to reset PIN code", - "unable_to_resolve_duplicate": "Unable to resolve duplicate", - "unable_to_restore_assets": "Unable to restore assets", - "unable_to_restore_trash": "Unable to restore trash", - "unable_to_restore_user": "Unable to restore user", - "unable_to_save_album": "Unable to save album", - "unable_to_save_api_key": "Unable to save API Key", - "unable_to_save_date_of_birth": "Unable to save date of birth", - "unable_to_save_name": "Unable to save name", - "unable_to_save_profile": "Unable to save profile", - "unable_to_save_settings": "Unable to save settings", - "unable_to_scan_libraries": "Unable to scan libraries", - "unable_to_scan_library": "Unable to scan library", - "unable_to_set_feature_photo": "Unable to set feature photo", - "unable_to_set_profile_picture": "Unable to set profile picture", - "unable_to_set_rating": "Unable to set rating", - "unable_to_submit_job": "Unable to submit job", - "unable_to_trash_asset": "Unable to trash asset", - "unable_to_unlink_account": "Unable to unlink account", - "unable_to_unlink_motion_video": "Unable to unlink motion video", - "unable_to_update_album_cover": "Unable to update album cover", - "unable_to_update_album_info": "Unable to update album info", - "unable_to_update_library": "Unable to update library", - "unable_to_update_location": "Unable to update location", - "unable_to_update_settings": "Unable to update settings", - "unable_to_update_timeline_display_status": "Unable to update timeline display status", - "unable_to_update_user": "Unable to update user", - "unable_to_update_workflow": "Unable to update workflow", - "unable_to_upload_file": "Unable to upload file" - }, - "errors_text": "Errors", - "exclusion_pattern": "Exclusion pattern", - "exif": "Exif", - "exif_bottom_sheet_description": "Add Description...", - "exif_bottom_sheet_description_error": "Error updating description", - "exif_bottom_sheet_details": "DETAILS", - "exif_bottom_sheet_location": "LOCATION", - "exif_bottom_sheet_no_description": "No description", - "exif_bottom_sheet_people": "PEOPLE", - "exif_bottom_sheet_person_add_person": "Add name", - "exit_slideshow": "Exit Slideshow", - "expand_all": "Expand all", - "experimental_settings_new_asset_list_subtitle": "Work in progress", - "experimental_settings_new_asset_list_title": "Enable experimental photo grid", - "experimental_settings_subtitle": "Use at your own risk!", - "experimental_settings_title": "Experimental", - "expire_after": "Expire after", - "expired": "Expired", - "expires_date": "Expires {date}", - "explore": "Explore", - "explorer": "Explorer", - "export": "Export", - "export_as_json": "Export as JSON", - "export_database": "Export Database", - "export_database_description": "Export the SQLite database", - "extension": "Extension", - "external": "External", - "external_libraries": "External Libraries", - "external_network": "External network", - "external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", - "face_unassigned": "Unassigned", - "failed": "Failed", - "failed_count": "Failed: {count}", - "failed_to_authenticate": "Failed to authenticate", - "failed_to_load_assets": "Failed to load assets", - "failed_to_load_folder": "Failed to load folder", - "favorite": "Favorite", - "favorite_action_prompt": "{count} added to Favorites", - "favorite_or_unfavorite_photo": "Favorite or unfavorite photo", - "favorites": "Favorites", - "favorites_page_no_favorites": "No favorite assets found", - "feature_photo_updated": "Feature photo updated", - "features": "Features", - "features_in_development": "Features in Development", - "features_setting_description": "Manage the app features", - "file_name_or_extension": "File name or extension", - "file_name_text": "File name", - "file_name_with_value": "File name: {file_name}", - "file_size": "File size", - "filename": "Filename", - "filetype": "Filetype", - "filter": "Filter", - "filter_description": "Conditions to filter the target assets", - "filter_people": "Filter people", - "filter_places": "Filter places", - "filters": "Filters", - "find_them_fast": "Find them fast by name with search", - "first": "First", - "fix_incorrect_match": "Fix incorrect match", - "folder": "Folder", - "folder_not_found": "Folder not found", - "folders": "Folders", - "folders_feature_description": "Browsing the folder view for the photos and videos on the file system", - "forgot_pin_code_question": "Forgot your PIN?", - "forward": "Forward", - "free_up_space": "Free Up Space", - "free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.", - "free_up_space_settings_subtitle": "Free up device storage", - "full_path": "Full path: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "This feature loads external resources from Google in order to work.", - "general": "General", - "geolocation_instruction_location": "Click on an asset with GPS coordinates to use its location, or select a location directly from the map", - "get_help": "Get Help", - "get_people_error": "Error getting people", - "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "getting_started": "Getting Started", - "go_back": "Go back", - "go_to_folder": "Go to folder", - "go_to_search": "Go to search", - "gps": "GPS", - "gps_missing": "No GPS", - "grant_permission": "Grant permission", - "group_albums_by": "Group albums by...", - "group_country": "Group by country", - "group_no": "No grouping", - "group_owner": "Group by owner", - "group_places_by": "Group places by...", - "group_year": "Group by year", - "haptic_feedback_switch": "Enable haptic feedback", - "haptic_feedback_title": "Haptic Feedback", - "has_quota": "Has quota", - "hash_asset": "Hash asset", - "hashed_assets": "Hashed assets", - "hashing": "Hashing", - "header_settings_add_header_tip": "Add header", - "header_settings_field_validator_msg": "Value cannot be empty", - "header_settings_header_name_input": "Header name", - "header_settings_header_value_input": "Header value", - "headers_settings_tile_title": "Custom proxy headers", - "height": "Height", - "hi_user": "Hi {name} ({email})", - "hide_all_people": "Hide all people", - "hide_gallery": "Hide gallery", - "hide_named_person": "Hide person {name}", - "hide_password": "Hide password", - "hide_person": "Hide person", - "hide_schema": "Hide schema", - "hide_text_recognition": "Hide text recognition", - "hide_unnamed_people": "Hide unnamed people", - "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", - "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", - "home_page_add_to_album_success": "Added {added} assets to album {album}.", - "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", - "home_page_archive_err_local": "Can not archive local assets yet, skipping", - "home_page_archive_err_partner": "Can not archive partner assets, skipping", - "home_page_building_timeline": "Building the timeline", - "home_page_delete_err_partner": "Can not delete partner assets, skipping", - "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", - "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", - "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", - "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album so that the timeline can populate photos and videos in it", - "home_page_locked_error_local": "Can not move local assets to locked folder, skipping", - "home_page_locked_error_partner": "Can not move partner assets to locked folder, skipping", - "home_page_share_err_local": "Can not share local assets via link, skipping", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", - "host": "Host", - "hour": "Hour", - "hours": "Hours", - "id": "ID", - "idle": "Idle", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", - "image": "Image", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taken on {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taken with {person1} on {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} taken with {person1} and {person2} on {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} taken with {person1}, {person2}, and {person3} on {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken with {person1}, {person2}, and {additionalCount, number} others on {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} on {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} on {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} and {person2} on {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {person3} on {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {additionalCount, number} others on {date}", - "image_saved_successfully": "Image saved", - "image_viewer_page_state_provider_download_started": "Download Started", - "image_viewer_page_state_provider_download_success": "Download Success", - "image_viewer_page_state_provider_share_error": "Share Error", - "immich_logo": "Immich Logo", - "immich_web_interface": "Immich Web Interface", - "import_from_json": "Import from JSON", - "import_path": "Import path", - "in_albums": "In {count, plural, one {# album} other {# albums}}", - "in_archive": "In archive", - "in_year": "In {year}", - "in_year_selector": "In", - "include_archived": "Include archived", - "include_shared_albums": "Include shared albums", - "include_shared_partner_assets": "Include shared partner assets", - "individual_share": "Individual share", - "individual_shares": "Individual shares", - "info": "Info", - "interval": { - "day_at_onepm": "Every day at 1pm", - "hours": "Every {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "Every night at midnight", - "night_at_twoam": "Every night at 2am" - }, - "invalid_date": "Invalid date", - "invalid_date_format": "Invalid date format", - "invite_people": "Invite People", - "invite_to_album": "Invite to album", - "ios_debug_info_fetch_ran_at": "Fetch ran {dateTime}", - "ios_debug_info_last_sync_at": "Last sync {dateTime}", - "ios_debug_info_no_processes_queued": "No background processes queued", - "ios_debug_info_no_sync_yet": "No background sync job has run yet", - "ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}", - "ios_debug_info_processing_ran_at": "Processing ran {dateTime}", - "items_count": "{count, plural, one {# item} other {# items}}", - "jobs": "Jobs", - "json_editor": "JSON editor", - "json_error": "JSON error", - "keep": "Keep", - "keep_albums": "Keep albums", - "keep_albums_count": "Keeping {count} {count, plural, one {album} other {albums}}", - "keep_all": "Keep All", - "keep_description": "Choose what stays on your device when freeing up space.", - "keep_favorites": "Keep favorites", - "keep_on_device": "Keep on device", - "keep_on_device_hint": "Select items to keep on this device", - "keep_this_delete_others": "Keep this, delete others", - "keeping": "Keeping: {items}", - "kept_this_deleted_others": "Kept this asset and deleted {count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "Keyboard shortcuts", - "language": "Language", - "language_no_results_subtitle": "Try adjusting your search term", - "language_no_results_title": "No languages found", - "language_search_hint": "Search languages...", - "language_setting_description": "Select your preferred language", - "large_files": "Large Files", - "last": "Last", - "last_months": "{count, plural, one {Last month} other {Last # months}}", - "last_seen": "Last seen", - "latest_version": "Latest Version", - "latitude": "Latitude", - "leave": "Leave", - "leave_album": "Leave album", - "lens_model": "Lens model", - "let_others_respond": "Let others respond", - "level": "Level", - "library": "Library", - "library_add_folder": "Add folder", - "library_edit_folder": "Edit folder", - "library_options": "Library options", - "library_page_device_albums": "Albums on Device", - "library_page_new_album": "New album", - "library_page_sort_asset_count": "Number of assets", - "library_page_sort_created": "Created date", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_title": "Album title", - "licenses": "Licenses", - "light": "Light", - "like": "Like", - "like_deleted": "Like deleted", - "link_motion_video": "Link motion video", - "link_to_oauth": "Link to OAuth", - "linked_oauth_account": "Linked OAuth account", - "list": "List", - "loading": "Loading", - "loading_search_results_failed": "Loading search results failed", - "local": "Local", - "local_asset_cast_failed": "Unable to cast an asset that is not uploaded to the server", - "local_assets": "Local Assets", - "local_id": "Local ID", - "local_media_summary": "Local Media Summary", - "local_network": "Local network", - "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", - "location": "Location", - "location_permission": "Location permission", - "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current Wi-Fi network's name", - "location_picker_choose_on_map": "Choose on map", - "location_picker_latitude_error": "Enter a valid latitude", - "location_picker_latitude_hint": "Enter your latitude here", - "location_picker_longitude_error": "Enter a valid longitude", - "location_picker_longitude_hint": "Enter your longitude here", - "lock": "Lock", - "locked_folder": "Locked Folder", - "log_detail_title": "Log Detail", - "log_out": "Log out", - "log_out_all_devices": "Log Out All Devices", - "logged_in_as": "Logged in as {user}", - "logged_out_all_devices": "Logged out all devices", - "logged_out_device": "Logged out device", - "login": "Login", - "login_disabled": "Login has been disabled", - "login_form_api_exception": "API exception. Please check the server URL and try again.", - "login_form_back_button_text": "Back", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "Server Endpoint URL", - "login_form_err_http": "Please specify http:// or https://", - "login_form_err_invalid_email": "Invalid Email", - "login_form_err_invalid_url": "Invalid URL", - "login_form_err_leading_whitespace": "Leading whitespace", - "login_form_err_trailing_whitespace": "Trailing whitespace", - "login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL", - "login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server", - "login_form_failed_login": "Error logging you in, check server URL, email and password", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", - "login_form_password_hint": "password", - "login_form_save_login": "Stay logged in", - "login_form_server_empty": "Enter a server URL.", - "login_form_server_error": "Could not connect to server.", - "login_has_been_disabled": "Login has been disabled.", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "logout_all_device_confirmation": "Are you sure you want to log out all devices?", - "logout_this_device_confirmation": "Are you sure you want to log out this device?", - "logs": "Logs", - "longitude": "Longitude", - "look": "Look", - "loop_videos": "Loop videos", - "loop_videos_description": "Enable to automatically loop a video in the detail viewer.", - "main_branch_warning": "You're using a development version; we strongly recommend using a release version!", - "main_menu": "Main menu", - "maintenance_action_restore": "Restoring Database", - "maintenance_description": "Immich has been put into maintenance mode.", - "maintenance_end": "End maintenance mode", - "maintenance_end_error": "Failed to end maintenance mode.", - "maintenance_logged_in_as": "Currently logged in as {user}", - "maintenance_restore_from_backup": "Restore From Backup", - "maintenance_restore_library": "Restore Your Library", - "maintenance_restore_library_confirm": "If this looks correct, continue to restoring a backup!", - "maintenance_restore_library_description": "Restoring Database", - "maintenance_restore_library_folder_has_files": "{folder} has {count} folder(s)", - "maintenance_restore_library_folder_no_files": "{folder} is missing files!", - "maintenance_restore_library_folder_pass": "readable and writable", - "maintenance_restore_library_folder_read_fail": "not readable", - "maintenance_restore_library_folder_write_fail": "not writable", - "maintenance_restore_library_hint_missing_files": "You may be missing important files", - "maintenance_restore_library_hint_regenerate_later": "You can regenerate these later in settings", - "maintenance_restore_library_hint_storage_template_missing_files": "Using storage template? You may be missing files", - "maintenance_restore_library_loading": "Loading integrity checks and heuristics…", - "maintenance_task_backup": "Creating a backup of the existing database…", - "maintenance_task_migrations": "Running database migrations…", - "maintenance_task_restore": "Restoring the chosen backup…", - "maintenance_task_rollback": "Restore failed, rolling back to restore point…", - "maintenance_title": "Temporarily Unavailable", - "make": "Make", - "manage_geolocation": "Manage location", - "manage_media_access_rationale": "This permission is required for proper handling of moving assets to the trash and restoring them from it.", - "manage_media_access_settings": "Open settings", - "manage_media_access_subtitle": "Allow the Immich app to manage and move media files.", - "manage_media_access_title": "Media Management Access", - "manage_shared_links": "Manage shared links", - "manage_sharing_with_partners": "Manage sharing with partners", - "manage_the_app_settings": "Manage the app settings", - "manage_your_account": "Manage your account", - "manage_your_api_keys": "Manage your API keys", - "manage_your_devices": "Manage your logged-in devices", - "manage_your_oauth_connection": "Manage your OAuth connection", - "map": "Map", - "map_assets_in_bounds": "{count, plural, =0 {No photos in this area} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_yes": "Yes", - "map_location_picker_page_use_location": "Use this location", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_marker_for_images": "Map marker for images taken in {city}, {country}", - "map_marker_with_image": "Map marker with image", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings": "Map settings", - "map_settings_dark_mode": "Dark mode", - "map_settings_date_range_option_day": "Past 24 hours", - "map_settings_date_range_option_days": "Past {days} days", - "map_settings_date_range_option_year": "Past year", - "map_settings_date_range_option_years": "Past {years} years", - "map_settings_dialog_title": "Map Settings", - "map_settings_include_show_archived": "Include Archived", - "map_settings_include_show_partners": "Include Partners", - "map_settings_only_show_favorites": "Show Favorite Only", - "map_settings_theme_settings": "Map Theme", - "map_zoom_to_see_photos": "Zoom out to see photos", - "mark_all_as_read": "Mark all as read", - "mark_as_read": "Mark as read", - "marked_all_as_read": "Marked all as read", - "matches": "Matches", - "matching_assets": "Matching Assets", - "media_type": "Media type", - "memories": "Memories", - "memories_all_caught_up": "All caught up", - "memories_check_back_tomorrow": "Check back tomorrow for more memories", - "memories_setting_description": "Manage what you see in your memories", - "memories_start_over": "Start Over", - "memories_swipe_to_close": "Swipe up to close", - "memory": "Memory", - "memory_lane_title": "Memory Lane {title}", - "menu": "Menu", - "merge": "Merge", - "merge_people": "Merge people", - "merge_people_limit": "You can only merge up to 5 faces at a time", - "merge_people_prompt": "Do you want to merge these people? This action is irreversible.", - "merge_people_successfully": "Merge people successfully", - "merged_people_count": "Merged {count, plural, one {# person} other {# people}}", - "minimize": "Minimize", - "minute": "Minute", - "minutes": "Minutes", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertical", - "missing": "Missing", - "mobile_app": "Mobile App", - "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", - "model": "Model", - "month": "Month", - "monthly_title_text_date_format": "MMMM y", - "more": "More", - "move": "Move", - "move_down": "Move down", - "move_off_locked_folder": "Move out of locked folder", - "move_to": "Move to", - "move_to_device_trash": "Move to device trash", - "move_to_lock_folder_action_prompt": "{count} added to the locked folder", - "move_to_locked_folder": "Move to locked folder", - "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder", - "move_up": "Move up", - "moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive", - "moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library", - "moved_to_trash": "Moved to trash", - "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", - "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", - "mute_memories": "Mute Memories", - "my_albums": "My albums", - "name": "Name", - "name_or_nickname": "Name or nickname", - "name_required": "Name is required", - "navigate": "Navigate", - "navigate_to_time": "Navigate to Time", - "network_requirement_photos_upload": "Use cellular data to backup photos", - "network_requirement_videos_upload": "Use cellular data to backup videos", - "network_requirements": "Network Requirements", - "network_requirements_updated": "Network requirements changed, resetting backup queue", - "networking_settings": "Networking", - "networking_subtitle": "Manage the server endpoint settings", - "never": "Never", - "new_album": "New Album", - "new_api_key": "New API Key", - "new_date_range": "New date range", - "new_password": "New password", - "new_person": "New person", - "new_pin_code": "New PIN code", - "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page", - "new_timeline": "New Timeline", - "new_update": "New update", - "new_user_created": "New user created", - "new_version_available": "NEW VERSION AVAILABLE", - "newest_first": "Newest first", - "next": "Next", - "next_memory": "Next memory", - "no": "No", - "no_actions_added": "No actions added yet", - "no_albums_found": "No albums found", - "no_albums_message": "Create an album to organize your photos and videos", - "no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.", - "no_albums_yet": "It looks like you do not have any albums yet.", - "no_archived_assets_message": "Archive photos and videos to hide them from your Photos view", - "no_assets_message": "Click to upload your first photo", - "no_assets_to_show": "No assets to show", - "no_cast_devices_found": "No cast devices found", - "no_checksum_local": "No checksum available - cannot fetch local assets", - "no_checksum_remote": "No checksum available - cannot fetch remote asset", - "no_configuration_needed": "No configuration needed", - "no_devices": "No authorized devices", - "no_duplicates_found": "No duplicates were found.", - "no_exif_info_available": "No exif info available", - "no_explore_results_message": "Upload more photos to explore your collection.", - "no_favorites_message": "Add favorites to quickly find your best pictures and videos", - "no_filters_added": "No filters added yet", - "no_libraries_message": "Create an external library to view your photos and videos", - "no_local_assets_found": "No local assets found with this checksum", - "no_location_set": "No location set", - "no_locked_photos_message": "Photos and videos in the locked folder are hidden and won't show up as you browse or search your library.", - "no_name": "No Name", - "no_notifications": "No notifications", - "no_people_found": "No matching people found", - "no_places": "No places", - "no_remote_assets_found": "No remote assets found with this checksum", - "no_results": "No results", - "no_results_description": "Try a synonym or more general keyword", - "no_shared_albums_message": "Create an album to share photos and videos with people in your network", - "no_uploads_in_progress": "No uploads in progress", - "none": "None", - "not_allowed": "Not allowed", - "not_available": "N/A", - "not_in_any_album": "Not in any album", - "not_selected": "Not selected", - "note_apply_storage_label_to_previously_uploaded assets": "Note: To apply the Storage Label to previously uploaded assets, run the", - "notes": "Notes", - "nothing_here_yet": "Nothing here yet", - "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", - "notification_permission_list_tile_content": "Grant permission to enable notifications.", - "notification_permission_list_tile_enable_button": "Enable Notifications", - "notification_permission_list_tile_title": "Notification Permission", - "notification_toggle_setting_description": "Enable email notifications", - "notifications": "Notifications", - "notifications_setting_description": "Manage notifications", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium Configurator", - "obtainium_configurator_instructions": "Use Obtainium to install and update the Android app directly from Immich GitHub's release. Create an API key and select a variant to create your Obtainium configuration link", - "ocr": "OCR", - "official_immich_resources": "Official Immich Resources", - "offline": "Offline", - "offset": "Offset", - "ok": "Ok", - "oldest_first": "Oldest first", - "on_this_device": "On this device", - "onboarding": "Onboarding", - "onboarding_locale_description": "Select your preferred language. You can change this later in your settings.", - "onboarding_privacy_description": "The following (optional) features rely on external services, and can be disabled at any time in settings.", - "onboarding_server_welcome_description": "Let's get your instance set up with some common settings.", - "onboarding_theme_description": "Choose a color theme for your instance. You can change this later in your settings.", - "onboarding_user_welcome_description": "Let's get you started!", - "onboarding_welcome_user": "Welcome, {user}", - "online": "Online", - "only_favorites": "Only favorites", - "open": "Open", - "open_in_map_view": "Open in map view", - "open_in_openstreetmap": "Open in OpenStreetMap", - "open_the_search_filters": "Open the search filters", - "options": "Options", - "or": "or", - "organize_into_albums": "Organize into albums", - "organize_into_albums_description": "Put existing photos into albums using current sync settings", - "organize_your_library": "Organize your library", - "original": "original", - "other": "Other", - "other_devices": "Other devices", - "other_entities": "Other entities", - "other_variables": "Other variables", - "owned": "Owned", - "owner": "Owner", - "page": "Page", - "partner": "Partner", - "partner_can_access": "{partner} can access", - "partner_can_access_assets": "All your photos and videos except those in Archived and Deleted", - "partner_can_access_location": "The location where your photos were taken", - "partner_list_user_photos": "{user}'s photos", - "partner_list_view_all": "View all", - "partner_page_empty_message": "Your photos are not yet shared with any partner.", - "partner_page_no_more_users": "No more users to add", - "partner_page_partner_add_failed": "Failed to add partner", - "partner_page_select_partner": "Select partner", - "partner_page_shared_to_title": "Shared to", - "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.", - "partner_sharing": "Partner Sharing", - "partners": "Partners", - "password": "Password", - "password_does_not_match": "Password does not match", - "password_required": "Password Required", - "password_reset_success": "Password reset success", - "past_durations": { - "days": "Past {days, plural, one {day} other {# days}}", - "hours": "Past {hours, plural, one {hour} other {# hours}}", - "years": "Past {years, plural, one {year} other {# years}}" - }, - "path": "Path", - "pattern": "Pattern", - "pause": "Pause", - "pause_memories": "Pause memories", - "paused": "Paused", - "pending": "Pending", - "people": "People", - "people_edits_count": "Edited {count, plural, one {# person} other {# people}}", - "people_feature_description": "Browsing photos and videos grouped by people", - "people_selected": "{count, plural, one {# person selected} other {# people selected}}", - "people_sidebar_description": "Display a link to People in the sidebar", - "permanent_deletion_warning": "Permanent deletion warning", - "permanent_deletion_warning_setting_description": "Show a warning when permanently deleting assets", - "permanently_delete": "Permanently delete", - "permanently_delete_assets_count": "Permanently delete {count, plural, one {asset} other {assets}}", - "permanently_delete_assets_prompt": "Are you sure you want to permanently delete {count, plural, one {this asset?} other {these # assets?}} This will also remove {count, plural, one {it from its} other {them from their}} album(s).", - "permanently_deleted_asset": "Permanently deleted asset", - "permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", - "permission": "Permission", - "permission_empty": "Your permission shouldn't be empty", - "permission_onboarding_back": "Back", - "permission_onboarding_continue_anyway": "Continue anyway", - "permission_onboarding_get_started": "Get started", - "permission_onboarding_go_to_settings": "Go to settings", - "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", - "permission_onboarding_permission_granted": "Permission granted! You are all set.", - "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", - "permission_onboarding_request": "Immich requires permission to view your photos and videos.", - "person": "Person", - "person_age_months": "{months, plural, one {# month} other {# months}} old", - "person_age_year_months": "1 year, {months, plural, one {# month} other {# months}} old", - "person_age_years": "{years, plural, other {# years}} old", - "person_birthdate": "Born on {date}", - "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", - "person_recognized": "Person recognized", - "person_selected": "Person selected", - "photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any user to share with.", - "photos": "Photos", - "photos_and_videos": "Photos & Videos", - "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", - "photos_from_previous_years": "Photos from previous years", - "photos_only": "Photos only", - "pick_a_location": "Pick a location", - "pick_custom_range": "Custom range", - "pick_date_range": "Select a date range", - "pin_code_changed_successfully": "Successfully changed PIN code", - "pin_code_reset_successfully": "Successfully reset PIN code", - "pin_code_setup_successfully": "Successfully setup a PIN code", - "pin_verification": "PIN code verification", - "place": "Place", - "places": "Places", - "places_count": "{count, plural, one {{count, number} Place} other {{count, number} Places}}", - "play": "Play", - "play_memories": "Play memories", - "play_motion_photo": "Play Motion Photo", - "play_or_pause_video": "Play or pause video", - "play_original_video": "Play original video", - "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", - "play_transcoded_video": "Play transcoded video", - "please_auth_to_access": "Please authenticate to access", - "port": "Port", - "preferences_settings_subtitle": "Manage the app's preferences", - "preferences_settings_title": "Preferences", - "preparing": "Preparing", - "preset": "Preset", - "preview": "Preview", - "previous": "Previous", - "previous_memory": "Previous memory", - "previous_or_next_day": "Day forward/back", - "previous_or_next_month": "Month forward/back", - "previous_or_next_photo": "Photo forward/back", - "previous_or_next_year": "Year forward/back", - "primary": "Primary", - "privacy": "Privacy", - "profile": "Profile", - "profile_drawer_app_logs": "Logs", - "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", - "profile_image_of_user": "Profile image of {user}", - "profile_picture_set": "Profile picture set.", - "public_album": "Public album", - "public_share": "Public Share", - "purchase_account_info": "Supporter", - "purchase_activated_subtitle": "Thank you for supporting Immich and open-source software", - "purchase_activated_time": "Activated on {date}", - "purchase_activated_title": "Your key has been successfully activated", - "purchase_button_activate": "Activate", - "purchase_button_buy": "Buy", - "purchase_button_buy_immich": "Buy Immich", - "purchase_button_never_show_again": "Never show again", - "purchase_button_reminder": "Remind me in 30 days", - "purchase_button_remove_key": "Remove key", - "purchase_button_select": "Select", - "purchase_failed_activation": "Failed to activate! Please check your email for the correct product key!", - "purchase_individual_description_1": "For an individual", - "purchase_individual_description_2": "Supporter status", - "purchase_individual_title": "Individual", - "purchase_input_suggestion": "Have a product key? Enter the key below", - "purchase_license_subtitle": "Buy Immich to support the continued development of the service", - "purchase_lifetime_description": "Lifetime purchase", - "purchase_option_title": "PURCHASE OPTIONS", - "purchase_panel_info_1": "Building Immich takes a lot of time and effort, and we have full-time engineers working on it to make it as good as we possibly can. Our mission is for open-source software and ethical business practices to become a sustainable income source for developers and to create a privacy-respecting ecosystem with real alternatives to exploitative cloud services.", - "purchase_panel_info_2": "As we're committed not to add paywalls, this purchase will not grant you any additional features in Immich. We rely on users like you to support Immich's ongoing development.", - "purchase_panel_title": "Support the project", - "purchase_per_server": "Per server", - "purchase_per_user": "Per user", - "purchase_remove_product_key": "Remove Product Key", - "purchase_remove_product_key_prompt": "Are you sure you want to remove the product key?", - "purchase_remove_server_product_key": "Remove Server product key", - "purchase_remove_server_product_key_prompt": "Are you sure you want to remove the Server product key?", - "purchase_server_description_1": "For the whole server", - "purchase_server_description_2": "Supporter status", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "The server product key is managed by the admin", - "query_asset_id": "Query Asset ID", - "queue_status": "Queuing {count}/{total}", - "rate_asset": "Rate Asset", - "rating": "Star rating", - "rating_clear": "Clear rating", - "rating_count": "{count, plural, one {# star} other {# stars}}", - "rating_description": "Display the EXIF rating in the info panel", - "rating_set": "Rating set to {rating, plural, one {# star} other {# stars}}", - "reaction_options": "Reaction options", - "read_changelog": "Read Changelog", - "readonly_mode_disabled": "Read-only mode disabled", - "readonly_mode_enabled": "Read-only mode enabled", - "ready_for_upload": "Ready for upload", - "reassign": "Reassign", - "reassigned_assets_to_existing_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to {name, select, null {an existing person} other {{name}}}", - "reassigned_assets_to_new_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to a new person", - "reassing_hint": "Assign selected assets to an existing person", - "recent": "Recent", - "recent-albums": "Recent albums", - "recent_searches": "Recent searches", - "recently_added": "Recently added", - "recently_added_page_title": "Recently Added", - "recently_taken": "Recently taken", - "recently_taken_page_title": "Recently Taken", - "refresh": "Refresh", - "refresh_encoded_videos": "Refresh encoded videos", - "refresh_faces": "Refresh faces", - "refresh_metadata": "Refresh metadata", - "refresh_thumbnails": "Refresh thumbnails", - "refreshed": "Refreshed", - "refreshes_every_file": "Re-reads all existing and new files", - "refreshing_encoded_video": "Refreshing encoded video", - "refreshing_faces": "Refreshing faces", - "refreshing_metadata": "Refreshing metadata", - "regenerating_thumbnails": "Regenerating thumbnails", - "remote": "Remote", - "remote_assets": "Remote Assets", - "remote_media_summary": "Remote Media Summary", - "remove": "Remove", - "remove_assets_album_confirmation": "Are you sure you want to remove {count, plural, one {# asset} other {# assets}} from the album?", - "remove_assets_shared_link_confirmation": "Are you sure you want to remove {count, plural, one {# asset} other {# assets}} from this shared link?", - "remove_assets_title": "Remove assets?", - "remove_custom_date_range": "Remove custom date range", - "remove_deleted_assets": "Remove Deleted Assets", - "remove_from_album": "Remove from album", - "remove_from_album_action_prompt": "{count} removed from the album", - "remove_from_favorites": "Remove from favorites", - "remove_from_lock_folder_action_prompt": "{count} removed from the locked folder", - "remove_from_locked_folder": "Remove from locked folder", - "remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of the locked folder? They will be visible in your library.", - "remove_from_shared_link": "Remove from shared link", - "remove_memory": "Remove memory", - "remove_photo_from_memory": "Remove photo from this memory", - "remove_tag": "Remove tag", - "remove_url": "Remove URL", - "remove_user": "Remove user", - "removed_api_key": "Removed API Key: {name}", - "removed_from_archive": "Removed from archive", - "removed_from_favorites": "Removed from favorites", - "removed_from_favorites_count": "{count, plural, other {Removed #}} from favorites", - "removed_memory": "Removed memory", - "removed_photo_from_memory": "Removed photo from memory", - "removed_tagged_assets": "Removed tag from {count, plural, one {# asset} other {# assets}}", - "rename": "Rename", - "repair": "Repair", - "repair_no_results_message": "Untracked and missing files will show up here", - "replace_with_upload": "Replace with upload", - "repository": "Repository", - "require_password": "Require password", - "require_user_to_change_password_on_first_login": "Require user to change password on first login", - "rescan": "Rescan", - "reset": "Reset", - "reset_password": "Reset password", - "reset_people_visibility": "Reset people visibility", - "reset_pin_code": "Reset PIN code", - "reset_pin_code_description": "If you forgot your PIN code, you can contact the server administrator to reset it", - "reset_pin_code_success": "Successfully reset PIN code", - "reset_pin_code_with_password": "You can always reset your PIN code with your password", - "reset_sqlite": "Reset SQLite Database", - "reset_sqlite_confirmation": "Are you sure you want to reset the SQLite database? You will need to log out and log in again to resync the data", - "reset_sqlite_success": "Successfully reset the SQLite database", - "reset_to_default": "Reset to default", - "resolution": "Resolution", - "resolve_duplicates": "Resolve duplicates", - "resolved_all_duplicates": "Resolved all duplicates", - "restore": "Restore", - "restore_all": "Restore all", - "restore_trash_action_prompt": "{count} restored from trash", - "restore_user": "Restore user", - "restored_asset": "Restored asset", - "resume": "Resume", - "resume_paused_jobs": "Resume {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Retry upload", - "review_duplicates": "Review duplicates", - "review_large_files": "Review large files", - "role": "Role", - "role_editor": "Editor", - "role_viewer": "Viewer", - "running": "Running", - "save": "Save", - "save_to_gallery": "Save to gallery", - "saved": "Saved", - "saved_api_key": "Saved API Key", - "saved_profile": "Saved profile", - "saved_settings": "Saved settings", - "say_something": "Say something", - "scaffold_body_error_occurred": "Error occurred", - "scan": "Scan", - "scan_all_libraries": "Scan All Libraries", - "scan_library": "Scan", - "scan_settings": "Scan Settings", - "scanning": "Scanning", - "scanning_for_album": "Scanning for album...", - "search": "Search", - "search_albums": "Search albums", - "search_by_context": "Search by context", - "search_by_description": "Search by description", - "search_by_description_example": "Hiking day in Sapa", - "search_by_filename": "Search by file name or extension", - "search_by_filename_example": "i.e. IMG_1234.JPG or PNG", - "search_by_ocr": "Search by OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Search lens model...", - "search_camera_make": "Search camera make...", - "search_camera_model": "Search camera model...", - "search_city": "Search city...", - "search_country": "Search country...", - "search_filter_apply": "Apply filter", - "search_filter_camera_title": "Select camera type", - "search_filter_date": "Date", - "search_filter_date_interval": "{start} to {end}", - "search_filter_date_title": "Select a date range", - "search_filter_display_option_not_in_album": "Not in album", - "search_filter_display_options": "Display Options", - "search_filter_filename": "Search by file name", - "search_filter_location": "Location", - "search_filter_location_title": "Select location", - "search_filter_media_type": "Media Type", - "search_filter_media_type_title": "Select media type", - "search_filter_ocr": "Search by OCR", - "search_filter_people_title": "Select people", - "search_filter_star_rating": "Star Rating", - "search_for": "Search for", - "search_for_existing_person": "Search for existing person", - "search_no_more_result": "No more results", - "search_no_people": "No people", - "search_no_people_named": "No people named \"{name}\"", - "search_no_result": "No results found, try a different search term or combination", - "search_options": "Search options", - "search_page_categories": "Categories", - "search_page_motion_photos": "Motion Photos", - "search_page_no_objects": "No Objects Info Available", - "search_page_no_places": "No Places Info Available", - "search_page_screenshots": "Screenshots", - "search_page_search_photos_videos": "Search for your photos and videos", - "search_page_selfies": "Selfies", - "search_page_things": "Things", - "search_page_view_all_button": "View all", - "search_page_your_activity": "Your activity", - "search_page_your_map": "Your Map", - "search_people": "Search people", - "search_places": "Search places", - "search_rating": "Search by rating...", - "search_result_page_new_search_hint": "New Search", - "search_settings": "Search settings", - "search_state": "Search state...", - "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", - "search_suggestion_list_smart_search_hint_2": "m:your-search-term", - "search_tags": "Search tags...", - "search_timezone": "Search timezone...", - "search_type": "Search type", - "search_your_photos": "Search your photos", - "searching_locales": "Searching locales...", - "second": "Second", - "see_all_people": "See all people", - "select": "Select", - "select_album": "Select album", - "select_album_cover": "Select album cover", - "select_albums": "Select albums", - "select_all": "Select all", - "select_all_duplicates": "Select all duplicates", - "select_all_in": "Select all in {group}", - "select_avatar_color": "Select avatar color", - "select_count": "{count, plural, one {Select #} other {Select #}}", - "select_cutoff_date": "Select cutoff date", - "select_face": "Select face", - "select_featured_photo": "Select featured photo", - "select_from_computer": "Select from computer", - "select_keep_all": "Select keep all", - "select_library_owner": "Select library owner", - "select_new_face": "Select new face", - "select_people": "Select people", - "select_person": "Select person", - "select_person_to_tag": "Select a person to tag", - "select_photos": "Select photos", - "select_trash_all": "Select trash all", - "select_user_for_sharing_page_err_album": "Failed to create album", - "selected": "Selected", - "selected_count": "{count, plural, other {# selected}}", - "selected_gps_coordinates": "Selected GPS Coordinates", - "send_message": "Send message", - "send_welcome_email": "Send welcome email", - "server_endpoint": "Server Endpoint", - "server_info_box_app_version": "App Version", - "server_info_box_server_url": "Server URL", - "server_offline": "Server Offline", - "server_online": "Server Online", - "server_privacy": "Server Privacy", - "server_restarting_description": "This page will refresh momentarily.", - "server_restarting_title": "Server is restarting", - "server_stats": "Server Stats", - "server_update_available": "Server update is available", - "server_version": "Server Version", - "set": "Set", - "set_as_album_cover": "Set as album cover", - "set_as_featured_photo": "Set as featured photo", - "set_as_profile_picture": "Set as profile picture", - "set_date_of_birth": "Set date of birth", - "set_profile_picture": "Set profile picture", - "set_slideshow_to_fullscreen": "Set Slideshow to fullscreen", - "set_stack_primary_asset": "Set as primary asset", - "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", - "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", - "setting_image_viewer_original_title": "Load original image", - "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", - "setting_image_viewer_preview_title": "Load preview image", - "setting_image_viewer_title": "Images", - "setting_languages_apply": "Apply", - "setting_languages_subtitle": "Change the app's language", - "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {duration}", - "setting_notifications_notify_hours": "{count} hours", - "setting_notifications_notify_immediately": "immediately", - "setting_notifications_notify_minutes": "{count} minutes", - "setting_notifications_notify_never": "never", - "setting_notifications_notify_seconds": "{count} seconds", - "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", - "setting_notifications_single_progress_title": "Show background backup detail progress", - "setting_notifications_subtitle": "Adjust your notification preferences", - "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", - "setting_notifications_total_progress_title": "Show background backup total progress", - "setting_video_viewer_auto_play_subtitle": "Automatically start playing videos when they are opened", - "setting_video_viewer_auto_play_title": "Auto play videos", - "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", - "settings": "Settings", - "settings_require_restart": "Please restart Immich to apply this setting", - "settings_saved": "Settings saved", - "setup_pin_code": "Setup a PIN code", - "share": "Share", - "share_action_prompt": "Shared {count} assets", - "share_add_photos": "Add photos", - "share_assets_selected": "{count} selected", - "share_dialog_preparing": "Preparing...", - "share_link": "Share Link", - "shared": "Shared", - "shared_album_activities_input_disable": "Comment is disabled", - "shared_album_activity_remove_content": "Do you want to delete this activity?", - "shared_album_activity_remove_title": "Delete Activity", - "shared_album_section_people_action_error": "Error leaving/removing from album", - "shared_album_section_people_action_leave": "Remove user from album", - "shared_album_section_people_action_remove_user": "Remove user from album", - "shared_album_section_people_title": "PEOPLE", - "shared_by": "Shared by", - "shared_by_user": "Shared by {user}", - "shared_by_you": "Shared by you", - "shared_from_partner": "Photos from {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Uploaded", - "shared_link_app_bar_title": "Shared Links", - "shared_link_clipboard_copied_massage": "Copied to clipboard", - "shared_link_clipboard_text": "Link: {link}\nPassword: {password}", - "shared_link_create_error": "Error while creating shared link", - "shared_link_custom_url_description": "Access this shared link with a custom URL", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_expire_after_option_day": "1 day", - "shared_link_edit_expire_after_option_days": "{count} days", - "shared_link_edit_expire_after_option_hour": "1 hour", - "shared_link_edit_expire_after_option_hours": "{count} hours", - "shared_link_edit_expire_after_option_minute": "1 minute", - "shared_link_edit_expire_after_option_minutes": "{count} minutes", - "shared_link_edit_expire_after_option_months": "{count} months", - "shared_link_edit_expire_after_option_year": "{count} year", - "shared_link_edit_password_hint": "Enter the share password", - "shared_link_edit_submit_button": "Update link", - "shared_link_error_server_url_fetch": "Cannot fetch the server url", - "shared_link_expires_day": "Expires in {count} day", - "shared_link_expires_days": "Expires in {count} days", - "shared_link_expires_hour": "Expires in {count} hour", - "shared_link_expires_hours": "Expires in {count} hours", - "shared_link_expires_minute": "Expires in {count} minute", - "shared_link_expires_minutes": "Expires in {count} minutes", - "shared_link_expires_never": "Expires ∞", - "shared_link_expires_second": "Expires in {count} second", - "shared_link_expires_seconds": "Expires in {count} seconds", - "shared_link_individual_shared": "Individual shared", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Manage Shared links", - "shared_link_options": "Shared link options", - "shared_link_password_description": "Require a password to access this shared link", - "shared_links": "Shared links", - "shared_links_description": "Share photos and videos with a link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}", - "shared_with_me": "Shared with me", - "shared_with_partner": "Shared with {partner}", - "sharing": "Sharing", - "sharing_enter_password": "Please enter the password to view this page.", - "sharing_page_album": "Shared albums", - "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", - "sharing_page_empty_list": "EMPTY LIST", - "sharing_sidebar_description": "Display a link to Sharing in the sidebar", - "sharing_silver_appbar_create_shared_album": "New shared album", - "sharing_silver_appbar_share_partner": "Share with partner", - "shift_to_permanent_delete": "press ⇧ to permanently delete asset", - "show_album_options": "Show album options", - "show_albums": "Show albums", - "show_all_people": "Show all people", - "show_and_hide_people": "Show & hide people", - "show_file_location": "Show file location", - "show_gallery": "Show gallery", - "show_hidden_people": "Show hidden people", - "show_in_timeline": "Show in timeline", - "show_in_timeline_setting_description": "Show photos and videos from this user in your timeline", - "show_keyboard_shortcuts": "Show keyboard shortcuts", - "show_metadata": "Show metadata", - "show_or_hide_info": "Show or hide info", - "show_password": "Show password", - "show_person_options": "Show person options", - "show_progress_bar": "Show Progress Bar", - "show_schema": "Show schema", - "show_search_options": "Show search options", - "show_shared_links": "Show shared links", - "show_slideshow_transition": "Show slideshow transition", - "show_supporter_badge": "Supporter badge", - "show_supporter_badge_description": "Show a supporter badge", - "show_text_recognition": "Show text recognition", - "show_text_search_menu": "Show text search menu", - "shuffle": "Shuffle", - "sidebar": "Sidebar", - "sidebar_display_description": "Display a link to the view in the sidebar", - "sign_out": "Sign Out", - "sign_up": "Sign up", - "size": "Size", - "skip_to_content": "Skip to content", - "skip_to_folders": "Skip to folders", - "skip_to_tags": "Skip to tags", - "slideshow": "Slideshow", - "slideshow_repeat": "Repeat slideshow", - "slideshow_repeat_description": "Loop back to beginning when slideshow ends", - "slideshow_settings": "Slideshow settings", - "sort_albums_by": "Sort albums by...", - "sort_created": "Date created", - "sort_items": "Number of items", - "sort_modified": "Date modified", - "sort_newest": "Newest photo", - "sort_oldest": "Oldest photo", - "sort_people_by_similarity": "Sort people by similarity", - "sort_recent": "Most recent photo", - "sort_title": "Title", - "source": "Source", - "stack": "Stack", - "stack_action_prompt": "{count} stacked", - "stack_duplicates": "Stack duplicates", - "stack_select_one_photo": "Select one main photo for the stack", - "stack_selected_photos": "Stack selected photos", - "stacked_assets_count": "Stacked {count, plural, one {# asset} other {# assets}}", - "stacktrace": "Stacktrace", - "start": "Start", - "start_date": "Start date", - "start_date_before_end_date": "Start date must be before end date", - "state": "State", - "status": "Status", - "stop_casting": "Stop casting", - "stop_motion_photo": "Stop Motion Photo", - "stop_photo_sharing": "Stop sharing your photos?", - "stop_photo_sharing_description": "{partner} will no longer be able to access your photos.", - "stop_sharing_photos_with_user": "Stop sharing your photos with this user", - "storage": "Storage space", - "storage_label": "Storage label", - "storage_quota": "Storage Quota", - "storage_usage": "{used} of {available} used", - "submit": "Submit", - "success": "Success", - "suggestions": "Suggestions", - "sunrise_on_the_beach": "Sunrise on the beach", - "support": "Support", - "support_and_feedback": "Support & Feedback", - "support_third_party_description": "Your Immich installation was packaged by a third-party. Issues you experience may be caused by that package, so please raise issues with them in the first instance using the links below.", - "swap_merge_direction": "Swap merge direction", - "sync": "Sync", - "sync_albums": "Sync albums", - "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", - "sync_local": "Sync Local", - "sync_remote": "Sync Remote", - "sync_status": "Sync Status", - "sync_status_subtitle": "View and manage the sync system", - "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", - "tag": "Tag", - "tag_assets": "Tag assets", - "tag_created": "Created tag: {tag}", - "tag_feature_description": "Browsing photos and videos grouped by logical tag topics", - "tag_not_found_question": "Cannot find a tag? Create a new tag.", - "tag_people": "Tag People", - "tag_updated": "Updated tag: {tag}", - "tagged_assets": "Tagged {count, plural, one {# asset} other {# assets}}", - "tags": "Tags", - "tap_to_run_job": "Tap to run job", - "template": "Template", - "text_recognition": "Text recognition", - "theme": "Theme", - "theme_selection": "Theme selection", - "theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference", - "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", - "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({count})", - "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", - "theme_setting_colorful_interface_title": "Colorful interface", - "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", - "theme_setting_image_viewer_quality_title": "Image viewer quality", - "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", - "theme_setting_primary_color_title": "Primary color", - "theme_setting_system_primary_color_title": "Use system color", - "theme_setting_system_theme_switch": "Automatic (Follow system setting)", - "theme_setting_theme_subtitle": "Choose the app's theme setting", - "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", - "theme_setting_three_stage_loading_title": "Enable three-stage loading", - "then": "Then", - "they_will_be_merged_together": "They will be merged together", - "third_party_resources": "Third-Party Resources", - "time": "Time", - "time_based_memories": "Time-based memories", - "time_based_memories_duration": "Number of seconds to display each image.", - "timeline": "Timeline", - "timezone": "Timezone", - "to_archive": "Archive", - "to_change_password": "Change password", - "to_favorite": "Favorite", - "to_login": "Login", - "to_multi_select": "to multi-select", - "to_parent": "Go to parent", - "to_select": "to select", - "to_trash": "Trash", - "toggle_settings": "Toggle settings", - "toggle_theme_description": "Toggle theme", - "total": "Total", - "total_usage": "Total usage", - "trash": "Trash", - "trash_action_prompt": "{count} moved to trash", - "trash_all": "Trash All", - "trash_count": "Trash {count, number}", - "trash_delete_asset": "Trash/Delete Asset", - "trash_emptied": "Emptied trash", - "trash_no_results_message": "Trashed photos and videos will show up here.", - "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", - "trash_page_info": "Trashed items will be permanently deleted after {days} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_title": "Trash ({count})", - "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.", - "trigger": "Trigger", - "trigger_asset_uploaded": "Asset Uploaded", - "trigger_asset_uploaded_description": "Triggered when a new asset is uploaded", - "trigger_description": "An event that kicks off the workflow", - "trigger_person_recognized": "Person Recognized", - "trigger_person_recognized_description": "Triggered when a person is detected", - "trigger_type": "Trigger type", - "troubleshoot": "Troubleshoot", - "type": "Type", - "unable_to_change_pin_code": "Unable to change PIN code", - "unable_to_check_version": "Unable to check app or server version", - "unable_to_setup_pin_code": "Unable to setup PIN code", - "unarchive": "Unarchive", - "unarchive_action_prompt": "{count} removed from Archive", - "unarchived_count": "{count, plural, other {Unarchived #}}", - "undo": "Undo", - "unfavorite": "Unfavorite", - "unfavorite_action_prompt": "{count} removed from Favorites", - "unhide_person": "Unhide person", - "unknown": "Unknown", - "unknown_country": "Unknown Country", - "unknown_date": "Unknown date", - "unknown_year": "Unknown Year", - "unlimited": "Unlimited", - "unlink_motion_video": "Unlink motion video", - "unlink_oauth": "Unlink OAuth", - "unlinked_oauth_account": "Unlinked OAuth account", - "unmute_memories": "Unmute Memories", - "unnamed_album": "Unnamed Album", - "unnamed_album_delete_confirmation": "Are you sure you want to delete this album?", - "unnamed_share": "Unnamed Share", - "unsaved_change": "Unsaved change", - "unselect_all": "Unselect all", - "unselect_all_duplicates": "Unselect all duplicates", - "unselect_all_in": "Unselect all in {group}", - "unstack": "Un-stack", - "unstack_action_prompt": "{count} unstacked", - "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", - "unsupported_field_type": "Unsupported field type", - "untagged": "Untagged", - "untitled_workflow": "Untitled workflow", - "up_next": "Up next", - "update_location_action_prompt": "Update the location of {count} selected assets with:", - "updated_at": "Updated", - "updated_password": "Updated password", - "upload": "Upload", - "upload_concurrency": "Upload concurrency", - "upload_details": "Upload Details", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_title": "Upload Asset", - "upload_error_with_count": "Upload error for {count, plural, one {# asset} other {# assets}}", - "upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.", - "upload_finished": "Upload finished", - "upload_progress": "Remaining {remaining, number} - Processed {processed, number}/{total, number}", - "upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}", - "upload_status_duplicates": "Duplicates", - "upload_status_errors": "Errors", - "upload_status_uploaded": "Uploaded", - "upload_success": "Upload success, refresh the page to see new upload assets.", - "upload_to_immich": "Upload to Immich ({count})", - "uploading": "Uploading", - "uploading_media": "Uploading media", - "url": "URL", - "usage": "Usage", - "use_biometric": "Use biometric", - "use_current_connection": "Use current connection", - "use_custom_date_range": "Use custom date range instead", - "user": "User", - "user_has_been_deleted": "This user has been deleted.", - "user_id": "User ID", - "user_liked": "{user} liked {type, select, photo {this photo} video {this video} asset {this asset} other {it}}", - "user_pin_code_settings": "PIN Code", - "user_pin_code_settings_description": "Manage your PIN code", - "user_privacy": "User Privacy", - "user_purchase_settings": "Purchase", - "user_purchase_settings_description": "Manage your purchase", - "user_role_set": "Set {user} as {role}", - "user_usage_detail": "User usage detail", - "user_usage_stats": "Account usage statistics", - "user_usage_stats_description": "View account usage statistics", - "username": "Username", - "users": "Users", - "users_added_to_album_count": "Added {count, plural, one {# user} other {# users}} to the album", - "utilities": "Utilities", - "validate": "Validate", - "validate_endpoint_error": "Please enter a valid URL", - "validation_error": "Validation error", - "variables": "Variables", - "version": "Version", - "version_announcement_closing": "Your friend, Alex", - "version_announcement_message": "Hi there! A new version of Immich is available. Please take some time to read the release notes to ensure your setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your Immich instance automatically.", - "version_history": "Version History", - "version_history_item": "Installed {version} on {date}", - "video": "Video", - "video_hover_setting": "Play video thumbnail on hover", - "video_hover_setting_description": "Play video thumbnail when mouse is hovering over item. Even when disabled, playback can be started by hovering over the play icon.", - "videos": "Videos", - "videos_count": "{count, plural, one {# Video} other {# Videos}}", - "videos_only": "Videos only", - "view": "View", - "view_album": "View Album", - "view_all": "View All", - "view_all_users": "View all users", - "view_asset_owners": "View asset owners", - "view_details": "View Details", - "view_in_timeline": "View in timeline", - "view_link": "View link", - "view_links": "View links", - "view_name": "View", - "view_next_asset": "View next asset", - "view_previous_asset": "View previous asset", - "view_qr_code": "View QR code", - "view_similar_photos": "View similar photos", - "view_stack": "View Stack", - "view_user": "View User", - "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack", - "visibility_changed": "Visibility changed for {count, plural, one {# person} other {# people}}", - "visual": "Visual", - "visual_builder": "Visual builder", - "waiting": "Waiting", - "waiting_count": "Waiting: {count}", - "warning": "Warning", - "week": "Week", - "welcome": "Welcome", - "welcome_to_immich": "Welcome to Immich", - "width": "Width", - "wifi_name": "Wi-Fi Name", - "workflow_delete_prompt": "Are you sure you want to delete this workflow?", - "workflow_deleted": "Workflow deleted", - "workflow_description": "Workflow description", - "workflow_info": "Workflow info", - "workflow_json": "Workflow JSON", - "workflow_json_help": "Edit the workflow configuration in JSON format. Changes will sync to the visual builder.", - "workflow_name": "Workflow name", - "workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?", - "workflow_summary": "Workflow summary", - "workflow_update_success": "Workflow updated successfully", - "workflow_updated": "Workflow updated", - "workflows": "Workflows", - "workflows_help_text": "Workflows automate actions on your assets based on triggers and filters", - "wrong_pin_code": "Wrong PIN code", - "year": "Year", - "years_ago": "{years, plural, one {# year} other {# years}} ago", - "yes": "Yes", - "you_dont_have_any_shared_links": "You don't have any shared links", - "your_wifi_name": "Your Wi-Fi name", - "zero_to_clear_rating": "press 0 to clear asset rating", - "zoom_image": "Zoom Image", - "zoom_to_bounds": "Zoom to bounds" + "welcome": "Welcome" } diff --git a/i18n/eo.json b/i18n/eo.json index b77577ea15..0967ef424b 100644 --- a/i18n/eo.json +++ b/i18n/eo.json @@ -1,444 +1 @@ -{ - "about": "Pri", - "account": "Konto", - "account_settings": "Agordaĵoj de konto", - "acknowledge": "Komprenite", - "action": "Ago", - "action_common_update": "Ĝisdatigi", - "action_description": "Aro de agoj por fari al filtritaj elementoj", - "actions": "Agoj", - "active": "Aktivaj", - "active_count": "Aktivaj: {count}", - "activity": "Okazaĵoj", - "activity_changed": "Aktivaĵoj estas {enabled,select,true {ŝaltitaj} other {malŝaltitaj}}", - "add": "Aldoni", - "add_a_description": "Aldoni priskribon", - "add_a_location": "Aldoni lokon", - "add_a_name": "Aldoni nomon", - "add_a_title": "Aldoni titolon", - "add_action": "Aldoni agon", - "add_action_description": "Klaku por aldoni agon por fari", - "add_assets": "Aldoni elementojn", - "add_birthday": "Aldoni naskiĝtagon", - "add_endpoint": "Aldoni finpunkton", - "add_exclusion_pattern": "Aldoni skemon de ekskludo", - "add_filter": "Aldoni filtrilon", - "add_filter_description": "Klaku por aldoni kondiĉon por filtri", - "add_location": "Aldoni lokon", - "add_more_users": "Aldoni pli da uzantoj", - "add_partner": "Aldoni partneron", - "add_path": "Aldoni vojon", - "add_photos": "Aldoni fotojn", - "add_tag": "Aldoni etikedon", - "add_to": "Aldoni al…", - "add_to_album": "Aldoni al albumo", - "add_to_album_bottom_sheet_added": "Aldonita(j) al {album}", - "add_to_album_bottom_sheet_already_exists": "Jam en {album}", - "add_to_album_bottom_sheet_some_local_assets": "Ne eblis aldoni kelkajn lokajn elementojn al la albumo", - "add_to_album_toggle": "Baskuli elekton por {album}", - "add_to_albums": "Aldoni al albumoj", - "add_to_albums_count": "Aldoni al albumoj ({count})", - "add_to_bottom_bar": "Aldoni al", - "add_to_shared_album": "Aldoni al dividita albumo", - "add_upload_to_stack": "Aldoni alŝutitajn elementojn al stako", - "add_url": "Aldoni URL-on", - "add_workflow_step": "Aldoni paŝon al laborfluo", - "added_to_archive": "Aldonita(j) al arĥivo", - "added_to_favorites": "Aldonita(j) al preferataĵoj", - "added_to_favorites_count": "Adonis {count, number} al preferataĵoj", - "admin": { - "add_exclusion_pattern_description": "Aldoni skemojn de ekskludo. Ĵokeraj signoj *, ** kaj ? funkcias. Por ignori ĉiujn dosierojn en ujo nomita \"Raw\", uzu \"**/Raw/**\". Por ignori ĉiujn dosierojn kun finaĵo \".tif\", uzu \"**/*.tif\". Por ignori iun absolutan vojon, uzu \"/vojo/por/ignori/**\".", - "admin_user": "Administranto", - "asset_offline_description": "Tiu ĉi ekstera biblioteko ne plu ĉeestas sur la disko, kaj estas movita al la rubujo. Se la dosiero estis movita ene de la biblioteko, serĉu la novan korespondan elementon en via kronologio. Por rehavi tiun elementon, kontrolu ke la ĉi-suba dosier-vojo estas atingebla de Immich por analizi la bibliotekon.", - "authentication_settings": "Agordoj pri aŭtentigo", - "authentication_settings_description": "Administri agordojn pri pasvortoj, OAuth, kaj aliaj ensalut-metodoj", - "authentication_settings_disable_all": "Ĉu vi certas, ke vi volas malebligi ĉiujn metodojn por ensaluti? Ensalutado estos tute malebligita.", - "authentication_settings_reenable": "Por re-ebligi, uzu servilan komandon.", - "background_task_job": "Fonaj taskoj", - "backup_database": "Krei kopion de la datumbazo", - "backup_database_enable_description": "Ebligi kreon de kopioj de datumbazo", - "backup_keep_last_amount": "Nombro de antaŭaj kopioj konservendaj", - "backup_onboarding_1_description": "fora kopio, ĉu en nubo ĉu en alia fizika loko.", - "backup_onboarding_2_description": "lokaj kopioj ĉe diversaj aparatoj, inkluzive ĉefajn dosierojn kaj lokan sekurkopion de tiuj dosieroj.", - "backup_onboarding_3_description": "suma nombro de kopioj de viaj datumoj, inkluzive la originajn dosierojn, t.e. 1 fora kopio kaj 2 lokaj kopioj.", - "backup_onboarding_description": "Ni rekomendas strategion de 3-2-1 por protekti viajn datumojn. Vi devus havi sekurkopiojn kaj de viaj fotoj/videoj kaj de la datumbazo de Immich por esti plene sekura.", - "backup_onboarding_footer": "Por pli da informoj pri sekurkopioj kun Immich, bonvolu legi la dokumentaron.", - "backup_onboarding_parts_title": "Sekur-kopioj laŭ strategio 3-2-1 inkluzivas:", - "backup_onboarding_title": "Sekurkopioj", - "backup_settings": "Agordaĵoj de kopiado de datumbazo", - "backup_settings_description": "Administri agordojn pri datumbazo-nekropsio.", - "cleared_jobs": "Taskoj forigitaj por: {job}", - "config_set_by_file": "La agordoj estas aktuale regitaj de agordo-dosiero", - "confirm_delete_library": "Ĉu vi certe volas forigi la biblitekon {library}?", - "confirm_delete_library_assets": "Ĉu vi certe volas forigi tiun ĉi bibliotekon? Tio forigos {count, plural, one {# la elementon, kiun} other {all # la elementojn, kiujn}} ĝi enhavas, kaj ne eblas malfari tion. La dosieroj tamen restos sur via disko.", - "confirm_email_below": "Por konfirmi, tajpu \"{email}\" ĉi-sube", - "confirm_reprocess_all_faces": "Ĉu vi certas, ke vi volas retrakti ĉiujn vizaĝojn? Tio forigos ĉies nomon.", - "confirm_user_password_reset": "Ĉu vi certe volas restarigi la pasvorton de {user}?", - "confirm_user_pin_code_reset": "Ĉu vi certe volas restarigi la PIN-kodon de {user}?", - "copy_config_to_clipboard_description": "Kopii la aktualan sistem-agordaĵaron, kiel JSON-objekton", - "create_job": "Krei taskon", - "cron_expression": "cron-esprimo", - "cron_expression_description": "Agordu la intervalon de analizado pere de la formato de cron. Por pli da informoj, legu ekzemple Crontab Guru", - "cron_expression_presets": "Antaŭagordoj pri la cron-esprimo", - "disable_login": "Malebligi ensalutadon", - "duplicate_detection_job_description": "Komenci permaŝin-lernadon por trovi similajn bildojn. Uzas 'inteligentan serĉadon'", - "exclusion_pattern_description": "Per skemo de ekskludo, vi povas ignori dosierojn kaj dosierujojn dum analizado de la biblioteko. Tio estas utila se vi havas ekz. RAW-dosierojn, kiujn vi ne volas importi.", - "export_config_as_json_description": "Elŝuti la aktualan sistem-agordaĵaron kiel JSON-dosieron", - "external_libraries_page_description": "Paĝo por administri eksterajn bibliotekojn", - "face_detection": "Detekto de vizaĝoj", - "face_detection_description": "Detekti vizaĝojn en viaj bildoj pere de maŝin-lernado. Por videoj, nur la titola bildeto estos traktata. \"Denove\" (re-)lanĉos la detektadon. \"Restartigi\" krome forigas ĉiujn aktualajn datumojn pri vizaĝoj. \"Netraktitaj\" vicigas ĉiujn bildojn ankoraŭ netraktitajn. Post la detektado, komenciĝos la rekonado, ĉu novaj ĉu jam rekonitaj homoj.", - "facial_recognition_job_description": "Kongruigi detektitajn vizaĝojn al homoj. Tiu ĉi procezo okazas post la fino de Detektado. \"Restartigi\" (re-)kongruigas ĉiujn vizaĝojn. \"Netraktitaj\" lanĉas la kongruigadon nur pri nove rekonitaj vizaĝoj.", - "failed_job_command": "La komando {command} malsukcesis por tasko: {job}", - "force_delete_user_warning": "ATENTU: tio ĉi tuj forigos la uzanton, kune kun ĉiuj ties elementoj. Ne eblas malfari tion, kaj la dosieroj ne povas estas retrovitaj poste.", - "image_format": "Formato", - "image_format_description": "WebP-dosieroj estas ĝenerale malpli grandaj ol JPEG, sed postulas pli da tempo por krei.", - "image_fullsize_description": "Bildoj je plena grandeco, sen meta-datumoj, uzataj dum zomado", - "image_fullsize_enabled": "Ŝalti kreadon de plen-grandaj bildoj", - "image_fullsize_enabled_description": "Krei bildon je plena grandeco por ne TTT-aj formatoj. Kiam la agordo \"Preferi enkorpigitan antaŭvidon\" estas ŝaltita, enkorpigitaj antaŭvidoj okazas rekte sen konvertado. Tiu ĉi agordo ne influas TTT-kongruajn formatojn kiel ekz. JPEG.", - "image_fullsize_quality_description": "Kvalito de la plen-granda bildo, inter 1 kaj 100. Pli alta numero indikas pli altkvalitan bildon, sed ankaŭ pli grandan dosieron por stoki.", - "image_fullsize_title": "Agordoj pri plen-grandaj bildoj", - "image_prefer_embedded_preview": "Preferi enkorpigitan antaŭvidon", - "image_prefer_embedded_preview_setting_description": "Uzi enkorpigitan antaŭvidon en RAW-fotoj kiel fonton por bildotraktado, kiam ĝi ekzistas. Rezulto estas pli precizaj koloroj por iuj bildoj, sed la kvalito de la antaŭvido dependas de la fotilo, kaj estas risko ke la bildo havos pli da artefaktoj de densigo.", - "image_prefer_wide_gamut": "Preferi vastan gamon", - "image_prefer_wide_gamut_setting_description": "Uzi Display P3 por bildetoj. Tio pli bone konservas la brilecon en bildoj kun vasta kolorgamo, sed bildoj povas aspekti strangaj en malnovaj aparatoj kun malnova foliumilo. Bildoj kun sRGB konserviĝas tiel por eviti kolorŝangon.", - "image_preview_description": "Mez-granda bildo, sen metadatumoj, uzata por montri unuopan bildon, kaj por maŝin-lernado", - "image_preview_quality_description": "Kvalito de antaŭvido, inter 1 kaj 100. Pli alta numero indikas pli altan kvaliton, sed ankaŭ kreas pli grandajn dosierojn, kiuj povas malrapidigi uzadon de la apo. Tro malalta numero povas noci la maŝin-lernadon.", - "image_preview_title": "Agordoj pri antaŭvidoj", - "image_progressive": "Poiome", - "image_progressive_description": "Kodigi JPEG-bildojn por poioma vidigo dum ŝargado. Tio ŝanĝas nenion por WebP-bildoj.", - "image_quality": "Kvalito", - "image_resolution": "Distingivo", - "image_resolution_description": "Alta distingivo povas konservi pli da detaloj en bildoj sed postulas pli da tempo por trakti, donas pli grandajn dosierojn por stokie, kaj povas malrapidigi uzadon de la apo.", - "image_settings": "Agordoj pri bildoj", - "image_settings_description": "Administri agordojn pri kvalito kaj distingivo de kreitaj bildoj", - "image_thumbnail_description": "Malgranda bildeto, sen metadatumoj, uzata por vidigi grupojn de fotoj, ekz. en la ĉefa tempolinio", - "image_thumbnail_quality_description": "Kvalito de bildeto, inter 1 kaj 100. Pli alta cifero indikas pli altkvalitan bildon, sed donas pli grandajn dosierojn kaj povas malrapidigi uzadon de la apo.", - "image_thumbnail_title": "Agordoj pri bildetoj", - "import_config_from_json_description": "Importi sistem-agordaĵaron de JSON-dosiero", - "job_concurrency": "{job}: nombro de samtempaj taskoj", - "job_created": "Tasko kreita", - "job_not_concurrency_safe": "Estas nesekure fari tiun ĉi taskon samtempe kun aliaj.", - "job_settings": "Agordoj pri tasko", - "job_settings_description": "Administri samtempajn taskojn", - "jobs_delayed": "{jobCount, plural, other {# prokrastitaj}}", - "jobs_failed": "{jobCount, plural, other {# malsukesis}}", - "jobs_over_time": "Taskoj dum tempo", - "library_created": "Kreis bibliotekon: {library}", - "library_deleted": "Biblioteko forigita", - "library_details": "Detaloj de biblioteko", - "library_folder_description": "Indiki dosierujon por importi. La sistemo traserĉos ĝin, inkluzive subdosierujojn, por trovi bildojn kaj videojn.", - "library_remove_exclusion_pattern_prompt": "Ĉu vi certas, ke vi volas forigi tiun ĉi skemon de ekskludo?", - "library_remove_folder_prompt": "Ĉu vi certas, ke vi volas forigi tiun ĉi import-dosieron?", - "library_scanning": "Perioda analizado", - "library_scanning_description": "Administri agordojn pri perioda analizado de la biblioteko", - "library_scanning_enable_description": "Ŝalti periodan analizadon de la biblioteko", - "library_settings": "Ekstera biblioteko", - "library_settings_description": "Administri agordojn pri eksteraj bibliotekoj", - "library_tasks_description": "Analizi eksterajn bibliotekojn por trovi novajn kaj/aŭ ŝanĝitajn elementojn", - "library_updated": "Biblioteko ĝisdatigita", - "library_watching_enable_description": "Observi eksterajn bibliotekojn por detekti ŝanĝojn", - "library_watching_settings": "Observado de bibliotekoj [EKSPERIMENTA]", - "library_watching_settings_description": "Aŭtomate observadi por ŝanĝitaj dosieroj", - "logging_enable_description": "Ŝalti protokoladon", - "logging_level_description": "Nivelo de protokolado, kiam ŝaltita.", - "logging_settings": "Protokolado", - "machine_learning_availability_checks": "Kontroloj de disponebleco", - "machine_learning_availability_checks_description": "Aŭtomate detekti kaj preferi disponeblajn servilojn por maŝin-lernado", - "machine_learning_availability_checks_enabled": "Ŝalti kontrolojn de disponebleco", - "machine_learning_availability_checks_interval": "Intervalo de kontrolo", - "machine_learning_availability_checks_interval_description": "Intervalo en milisekundoj inter kontroloj de disponebleco", - "machine_learning_availability_checks_timeout": "Tempolimo de peto", - "machine_learning_availability_checks_timeout_description": "Tempolimo (en milisekundoj) por kontrolo de disponebleco", - "machine_learning_clip_model": "Modelo CLIP", - "machine_learning_clip_model_description": "La nomo de la modelo CLIP menciita ĉi tie. Notu, ke vi devas refari la 'inteligentan serĉon' por ĉiuj bildoj post ŝanĝo de modelo.", - "machine_learning_duplicate_detection": "Detektado de duoblaĵoj", - "machine_learning_duplicate_detection_enabled": "Ŝalti detektadon de duoblaĵoj", - "machine_learning_duplicate_detection_enabled_description": "Eĉ se malŝaltita, precize identaj elementoj tamen estos malduobligitaj.", - "machine_learning_duplicate_detection_setting_description": "Uzi la lingvomodelon CLIP por trovi verŝajnajn duoblaĵojn", - "machine_learning_enabled": "Ŝalti maŝin-lernadon", - "machine_learning_enabled_description": "Se malŝaltita, ĉiuj funkcioj rilate al maŝin-lernado malŝaltiĝos, sendepende de la ĉi-subaj agordoj.", - "machine_learning_facial_recognition": "Rekonado de vizaĝoj", - "machine_learning_facial_recognition_description": "Detekti, rekoni kaj grupigi vizaĝojn en bildoj", - "machine_learning_facial_recognition_model": "Modelo de vizaĝ-rekonado", - "machine_learning_facial_recognition_model_description": "Modeloj listiĝas laŭ grandeco, kun la plej granda supre. Pli grandaj modeloj funkcias malpli rapide kaj uzas pli da memoro, sed donas pli bonajn rezultojn. Notu, ke vi devos refari detektadon de vizaĝoj en ĉiuj bildoj se vi ŝanĝas la modelon.", - "machine_learning_facial_recognition_setting": "Ŝalti rekonadon de vizaĝoj", - "machine_learning_facial_recognition_setting_description": "Se malŝaltita, bildoj ne estos kodigitaj por rekonado de vizaĝoj, kaj vizaĝoj ne aldoniĝos al la sekcio Homoj en la paĝo Esplori.", - "machine_learning_max_detection_distance": "Maksimuma distanco de detektado", - "machine_learning_max_detection_distance_description": "Maksimuma distanco inter du bildoj por konsideri ilin duoblaĵoj, inter 0.001 kaj 0.1. Pli alta valoro detektas pli da duoblaĵoj, sed povus ankaŭ trovi pli da malprave pozitivaj rezultoj.", - "machine_learning_max_recognition_distance": "Maksimuma distanco de rekonado", - "machine_learning_max_recognition_distance_description": "Maksimuma distanco inter du vizaĝoj por konsideri ilin la sama homo, inter 0 kaj 2. Pli malalta valoro emas malebligi, ke du apartaj homoj estas konsiderataj kiel la sama; pli alta valoro evitas tiun problemon, sed plialtigas la ŝancon, ke la sama homo en apartaj fotoj estos konsiderata kiel malsamaj homoj. Notu, ke estas pli facile kunfandi du identigitajn homojn al unu ol la malo, do prefere uzu pli malaltan ciferon se eblas.", - "machine_learning_min_detection_score": "Sojla numero da poentoj por sukcesa detekto", - "machine_learning_min_detection_score_description": "Minimuma valoro de fido por ke vizaĝo estu detektita, inter 0 kaj 1. Pli malalta valoro detektigas pli da vizaĝoj, sed eble ankaŭ malprave pozitivajn rezultojn.", - "machine_learning_min_recognized_faces": "Minimuma nombro da rekontigaj vizaĝoj", - "machine_learning_min_recognized_faces_description": "La minimuma nombro da rekonitaj vizaĝoj de la sama homo por krei novan homon. Pli alta valoro indikas pli precizan rekonadon de vizaĝoj, sed povus esti tiel, ke trovita vizaĝo ne konektiĝas kun konata homo.", - "machine_learning_ocr": "Optika signo-rekono", - "machine_learning_ocr_description": "Uzi maŝin-lernadon por rekoni tekston en bildoj", - "machine_learning_ocr_enabled": "Ŝalti optikan signo-rekonon", - "machine_learning_ocr_enabled_description": "Se malŝaltita, tiam optika signo-rekonado ne aplikiĝas al viaj bildoj.", - "machine_learning_ocr_max_resolution": "Maksimuma distingivo", - "machine_learning_ocr_max_resolution_description": "Antaŭvidoj kun pli granda distingivo ol tio ĉi estos ŝanĝitaj, kun konstantaj proporcioj. Pli alta valoro indikas pli da precizeco, sed postulas pli da memoro kaj funkcias malpli rapide.", - "machine_learning_ocr_min_detection_score": "Sojla numero da poentoj por sukcesa detekto", - "machine_learning_ocr_min_detection_score_description": "Minimuma valoro de fido por ke teksto estu detektita, inter 0 kaj 1. Pli malalta valoro detektigas pli da teksto, sed eble ankaŭ malprave pozitivajn rezultojn.", - "machine_learning_ocr_min_recognition_score": "Sojla nombro da poentoj por rekono", - "machine_learning_ocr_min_score_recognition_description": "Minimuma valoro de fido por ke detektita teksto estu rekonata, inter 0 kaj 1. Pli malalta valoro rekonigas pli da teksto, sed eble ankaŭ donas malprave pozitivajn rezultojn.", - "machine_learning_ocr_model": "Modelo de optika signo-rekono", - "machine_learning_ocr_model_description": "Modeloj en servilo estas pli kapablaj ol tiuj en portebla aparato, sed uzas pli da memoro kaj funkcias pli malrapide.", - "machine_learning_settings": "Agordoj pri maŝin-lernado", - "machine_learning_settings_description": "Administri agordojn pri maŝin-lernado", - "machine_learning_smart_search": "Inteligenta serĉado", - "machine_learning_smart_search_description": "Serĉi bildojn semantike laŭ enkorpigitaj CLIP-aĵoj", - "machine_learning_smart_search_enabled": "Ŝalti inteligentan serĉadon", - "machine_learning_smart_search_enabled_description": "Se malŝaltita, tiam bildoj ne estos kodigitaj por inteligenta serĉado.", - "machine_learning_url_description": "La URL-o de la maŝin-lerna servilo. Se vi donas pli ol unu URL-o, la sistemo provos ĉiun servilon unu post la alia ĝis kiam unu sukcese respondas, de la unua ĝis la lasta. Serviloj, kiuj ne respondas, estos dumtempe ignoritaj.", - "maintenance_delete_backup": "Forigi savkopion", - "maintenance_delete_backup_description": "La dosiero estos por ĉiam forigita.", - "maintenance_delete_error": "Malsukcesis forigi sekurkopion.", - "maintenance_restore_backup": "Restaŭri savkopion", - "maintenance_restore_backup_description": "Immich estos forigita kaj reinstalita de la elektita sekurkopio. Nova sekurkopio estos kreita antaŭe.", - "maintenance_restore_backup_different_version": "Tiu ĉi sekurkopio estis kreita per alia versio de Immich!", - "maintenance_restore_backup_unknown_version": "Ne eblis ektrovi version de la sekurkopio.", - "maintenance_restore_database_backup": "Restaŭri datumbazon el sekurkopio", - "maintenance_restore_database_backup_description": "Reveni al antaŭa stato de datumbazo pere de sekurkopio", - "maintenance_settings": "Funkcitenado", - "maintenance_settings_description": "Ŝalti la funkcitenadan reĝimon de Immich.", - "maintenance_start": "Ŝanĝi al funkci-tenada reĝimo", - "maintenance_start_error": "Malsukcesis ŝalti funkci-tenadan reĝimon.", - "maintenance_upload_backup": "Alŝuti dosieron de sekurkopio de datumbazo", - "maintenance_upload_backup_error": "Malsukcesis alŝuti sekurkopion, ĉu ĝi havas formaton .sql aŭ .sql.gz?", - "manage_concurrency": "Administri samtempajn taskojn", - "manage_concurrency_description": "Vizitu la paĝon Taskoj por agordi la nombron de samtempaj taskoj", - "manage_log_settings": "Administri agordojn pri protokolado", - "map_dark_style": "Malhela stilo", - "map_enable_description": "Ŝalti map-funkciojn", - "map_gps_settings": "Agordaĵoj pri mapoj kaj GPS", - "map_gps_settings_description": "Administri agordojn pri mapoj kaj GPS", - "map_implications": "Montri mapojn de dependas de ekstera servo (tiles.immich.cloud)", - "map_light_style": "Hela stilo", - "map_manage_reverse_geocoding_settings": "Administri agordojn pri inversa geo-kodigo", - "map_reverse_geocoding": "Inversa geo-kodigo", - "map_reverse_geocoding_enable_description": "Ŝalti inversan geo-kodigon", - "map_reverse_geocoding_settings": "Agordaĵoj de inversa geo-kodigo", - "map_settings": "Mapo", - "map_settings_description": "Administri agordojn pri mapoj", - "map_style_description": "URL-o de dosiero style.json por difini map-stilon", - "memory_cleanup_job": "Purigado de memoraĵoj", - "memory_generate_job": "Kreado de memoraĵoj", - "metadata_extraction_job": "Eltiri metadatumojn", - "metadata_extraction_job_description": "Eltiri metadatumojn el ĉiuj elementoj, ekz. GPS-on, vizaĝojn, kaj distingivon", - "metadata_faces_import_setting": "Ŝalti importadon de vizaĝoj", - "metadata_faces_import_setting_description": "Importi vizaĝojn el EXIF-datumoj kaj dosieroj sidecar", - "metadata_settings": "Agordoj pri metadatumoj", - "metadata_settings_description": "Administri agordojn pri metadatumoj", - "migration_job": "Migrado", - "migration_job_description": "Migrigi bildetojn pri elementoj kaj vizaĝoj al la nova strukturo de dosierujoj", - "nightly_tasks_cluster_faces_setting_description": "Ekfari nun rekonadon de nove detektitaj vizaĝoj", - "nightly_tasks_cluster_new_faces_setting": "Grupigi novajn vizaĝojn", - "nightly_tasks_database_cleanup_setting": "Taskoj pri purigado de datumbazo", - "nightly_tasks_database_cleanup_setting_description": "Forigi malnovajn, eksvalidajn datumojn de la datumbazo", - "nightly_tasks_generate_memories_setting": "Generi memoraĵojn", - "nightly_tasks_generate_memories_setting_description": "Krei novajn memoraĵojn el elementoj", - "nightly_tasks_missing_thumbnails_setting": "Generi mankantajn bildetojn", - "nightly_tasks_missing_thumbnails_setting_description": "Vicigi elementojn sen bildetoj por generado de bildetoj", - "nightly_tasks_settings": "Agordoj pri ĉiunoktaj taskoj", - "nightly_tasks_settings_description": "Administri ĉiunoktajn taskojn", - "nightly_tasks_start_time_setting": "Komencohoro", - "nightly_tasks_start_time_setting_description": "La horo kiam la servilo komencos la ĉiunoktajn taskojn", - "nightly_tasks_sync_quota_usage_setting": "Sinkronigi uzadon de kvotoj", - "nightly_tasks_sync_quota_usage_setting_description": "Ĝisdatigi kvoton de uzo de stokado, laŭ aktuala uzo", - "no_paths_added": "Neniuj vojoj aldonitaj", - "no_pattern_added": "Neniu skemo aldonita", - "note_apply_storage_label_previous_assets": "Notu: por aldoni la etikedon de stokado al antaŭe alŝutitaj elementoj, ekfaru nun la taskon de migrado de stokado.", - "note_cannot_be_changed_later": "NOTU: ne eblas poste ŝanĝi tion ĉi!", - "notification_email_from_address": "Adreso de sendanto", - "notification_email_from_address_description": "Retadreso, kiu aperos kiel \"sendinto\" de retmesaĝoj, ekz. \"Immich foto-servilo \". Uzu nur adreson, kiun vi rajtas uzi tiel.", - "notification_email_host_description": "Gastiganto de la retmesaĝa servilo (ekz. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignori erarojn pri atestiloj", - "notification_email_ignore_certificate_errors_description": "Ignori erarojn pri valideco de TLS-atestiloj (malrekomendite)", - "notification_email_password_description": "Pasvorto por uzi kun la retmesaĝa servilo", - "notification_email_port_description": "Pordo de la retmesaĝa servilo (ekz. 25, 465 aŭ 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Uzi SMTPS (SMTP pere de TLS)", - "notification_email_sent_test_email_button": "Sendi testmesaĝon kaj konservi", - "notification_email_setting_description": "Agordoj pri atentigoj per retmesaĝoj", - "notification_email_test_email": "Sendi testmesaĝon", - "notification_email_test_email_failed": "Malsukcesis sendi testmesaĝon, kontrolu la agordaĵojn", - "notification_email_test_email_sent": "Testmesaĝo estas sendita al {email}. Bonvolu kontroli ĉu ĝi bone alvenis.", - "notification_email_username_description": "Uzantonomo por uzi kun la retmesaĝa servilo", - "notification_enable_email_notifications": "Ŝalti retmesaĝajn atentigilojn", - "notification_settings": "Agordoj pri atentigiloj", - "notification_settings_description": "Administri agordojn pri atentigiloj, inkluzive tiujn per retmesaĝoj", - "oauth_auto_launch": "Startigi aŭtomate", - "oauth_auto_launch_description": "Aŭtomate startigi la OAuth-procezon tuj ĉe la ensaluta paĝo", - "oauth_auto_register": "Registri aŭtomate", - "oauth_auto_register_description": "Aŭtomate registri novajn uzantojn tuj post ensaluto per OAuth", - "oauth_button_text": "Teksto de butono", - "oauth_client_secret_description": "Bezonata por privata kliento, aŭ se PKCE (Proof Key for Code Exchange) ne estas subtenata de publika kliento.", - "oauth_enable_description": "Ensaluti per OAuth", - "oauth_mobile_redirect_uri": "Resenda URI por poŝ-aparatoj", - "oauth_mobile_redirect_uri_override": "Insisti pri resenda URI por poŝ-aparatoj", - "oauth_mobile_redirect_uri_override_description": "Ŝaltu tion ĉi kiam la provizanto de OAuth ne permesas URI-on por poŝ-aparatoj, kiel \"{callback}\"", - "oauth_role_claim": "Petita rolo", - "oauth_role_claim_description": "Aŭtomate doni rolon de administranto laŭ tiu ĉi peto. La peto povas esti aŭ 'user' (uzanto) aŭ 'admin' (administranto).", - "oauth_settings": "OAuth", - "oauth_settings_description": "Administri agordojn pri OAuth-ensalutado", - "oauth_settings_more_details": "Por pli da detaloj pri tio ĉi, bonvolu legi la dokumentaron.", - "oauth_storage_label_claim": "Petita etikedo de stokado", - "oauth_storage_label_claim_description": "Aŭtomate uzi la petitan etikedon por la stokado de la uzanto.", - "oauth_storage_quota_claim": "Petita kvoto de stokado", - "oauth_storage_quota_claim_description": "Aŭtomate doni kvoton de stokado laŭ tiu ĉi peto.", - "oauth_storage_quota_default": "Defaŭlta kvoto de stokado (GiB)", - "oauth_storage_quota_default_description": "Kvoto en GiB, uzata kiam mankas specifa peto pri tio.", - "oauth_timeout": "Tempolimo de petoj", - "oauth_timeout_description": "Tempolimo por petoj, en milisekundoj", - "ocr_job_description": "Uzi maŝin-lernadon por rekoni tekston en bildoj", - "password_enable_description": "Ensaluti per retadreso kaj pasvorto", - "password_settings": "Ensaluti per pasvorto", - "password_settings_description": "Administri agordojn pri ensalutado per pasvorto", - "paths_validated_successfully": "Ĉiuj vojoj sukcese validigitaj", - "person_cleanup_job": "Purigado de homoj", - "queue_details": "Detaloj pri la atendovico", - "queues": "Atendovicoj de taskoj", - "queues_page_description": "Administri la atendovicojn de taskoj", - "quota_size_gib": "Kvoto (GiB)", - "refreshing_all_libraries": "Aktualigado de ĉiuj bibliotekoj", - "registration": "Registrado de administranto", - "registration_description": "Vi estas la unua uzanto de tiu ĉi sistemo, do vi aŭtomate havos la rolon de administranto. Vi respondecos pri administraj taskoj, kaj vi povos krei pliajn uzantojn.", - "remove_failed_jobs": "Forigi malsukcesajn taskojn", - "require_password_change_on_login": "Devigi al uzantoj ŝanĝi pasvorton post unua ensaluto", - "reset_settings_to_default": "Restarigi agordaĵojn al defaŭltoj", - "reset_settings_to_recent_saved": "Restarigi agordaĵojn al la lastatempe konservitaj valoroj", - "scanning_library": "Analizado de biblioteko", - "search_jobs": "Serĉi taskojn…", - "send_welcome_email": "Sendi bonvenan retmesaĝon", - "server_external_domain_settings": "Ekstera domajno", - "server_external_domain_settings_description": "Domajno por publike dividitaj ligiloj, inkl. http(s)://", - "server_public_users": "Publikaj uzantoj", - "server_public_users_description": "Nomo kaj retadreso de ĉiuj uzantoj estas listigitaj kiam oni aldonas uzanton al dividita albumo. Kiam malŝaltita, la listo de uzantoj estos videbla nur por administrantoj.", - "server_settings": "Agordoj de servilo", - "server_settings_description": "Administri agordojn pri servilo", - "server_stats_page_description": "Paĝo de statistikoj pri la servilo", - "server_welcome_message": "Bonvena mesaĝo", - "server_welcome_message_description": "Mesaĝo afiŝita ĉe la ensaluta paĝo.", - "settings_page_description": "Paĝo de administraj agordaĵoj", - "sidecar_job": "Metadatumoj de sidecar-dosieroj", - "sidecar_job_description": "Trovi aŭ sinkronigi metadatumojn de sidecar-dosieroj", - "slideshow_duration_description": "Montri ĉiun bildon dum tiu nombro da sekundoj", - "smart_search_job_description": "Ekigi maŝin-lernadon pri elemetoj por ebligi uzon de inteligenta serĉo", - "storage_template_date_time_description": "La tempindiko de la elemento uziĝas por doni daton kaj horon", - "storage_template_date_time_sample": "Ekzempla horo {date}", - "storage_template_enable_description": "Ŝalti motoron de skemoj de stokado", - "storage_template_hash_verification_enabled": "Kontrolo de haketoj estas ŝaltita", - "storage_template_hash_verification_enabled_description": "Ŝaltas kontroladon de haketoj. Ne malŝaltu krom se vi certas, ke vi komprenas la konsekvencojn", - "storage_template_migration": "Migrado de skemoj de stokado", - "storage_template_migration_description": "Apliki la aktualan {template} al antaŭe alŝutitaj elementoj", - "storage_template_migration_info": "La skemo de stokado ŝanĝas ĉiun sufikson de dosiernomo al minuskloj. Tio aplikiĝos nur al novaj elementoj. Por fari tion ankaŭ al jam alŝutitaj elementoj, ekfunkciigu tiun ĉi taskon: {job}.", - "storage_template_migration_job": "Tasko de migrado de skemoj de stokado", - "storage_template_more_details": "Por pli da informoj pri tiu funkcio, rigardu la skemon de stokado kaj ĝiajn konsekvencojn", - "storage_template_onboarding_description_v2": "Tiu ĉi funkcio aŭtomate organizas dosierojn laŭ ŝablono difinita de la uzanto. Por pli da informoj, legu la dokumentaron.", - "storage_template_path_length": "Proksimuma limo de longeco de vojo: {length, number}/{limit, number}", - "storage_template_settings": "Skemo de stokado", - "storage_template_settings_description": "Administri la strukturon de dosierujoj kaj la dosiernomon de la alŝutita elemento", - "storage_template_user_label": "{label} estas la etikedo de stokado de la uzanto", - "system_settings": "Agordoj de la sistemo", - "tag_cleanup_job": "Purigado de etikedoj", - "template_email_available_tags": "Vi rajtas uzi tiujn ĉi variablojn en via ŝablono: {tags}", - "template_email_if_empty": "Se la ŝablono estas malplena, la defaŭlta retadreso estas uzita.", - "template_email_invite_album": "Ŝablono de invitilo al albumo", - "template_email_preview": "Antaŭvido", - "template_email_settings": "Ŝablonoj de retmesaĝoj", - "template_email_update_album": "Ŝablono por retmesaĝo por ĝisdatigi albumon", - "template_email_welcome": "Ŝablono de bonvena retmesaĝo", - "template_settings": "Ŝablonoj de atentigiloj", - "template_settings_description": "Administri tajloritajn skemojn por atentigiloj", - "theme_custom_css_settings": "Tajlorita CSS", - "theme_custom_css_settings_description": "Vi povas ŝanĝi la vidan aspekton de Immich per CSS.", - "theme_settings": "Agordoj de la etoso", - "theme_settings_description": "Administri tajloradon de la reta interfaco de Immich", - "thumbnail_generation_job": "Generi bildetojn", - "thumbnail_generation_job_description": "Kreas grandan, malgrandan, kaj malklaran bildetojn por ĉiu elemento, kune kun bildeto por ĉiu homo", - "transcoding_acceleration_api": "API de pliradidigo", - "transcoding_acceleration_api_description": "La API, kiu interagos kun via aparato por plirapidigi la transkodadon. Tiu ĉi agordaĵo indikas preferon – kaze de malsukceso, ĝi retropaŝas al softvara transkodado. VP9 povas funkcii aŭ ne, depende de viaj aparatoj.", - "transcoding_acceleration_nvenc": "NVENC (postulas GPU de NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (postulas ĉefprocesoron Intel de minimume 7-a generacio)", - "transcoding_acceleration_rkmpp": "RKMPP (nur por SOC-oj de Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Akceptitaj sonkodekoj", - "transcoding_accepted_audio_codecs_description": "Elektu senkodekojn, kiuj ne bezonas transkodadon. Uziĝas nur por specifaj politikoj de transkodado.", - "transcoding_accepted_containers": "Akceptitaj ujoj", - "transcoding_accepted_containers_description": "Elektu la uj-formatojn, kiuj ne bezonas esti remiksitaj al MP4. Uziĝas nur por specifaj politikoj de transkodado.", - "transcoding_accepted_video_codecs": "Akceptitaj video-kodekoj", - "transcoding_accepted_video_codecs_description": "Elektu video-kodekojn, kiuj ne bezonas transkodadon. Uziĝas nur por specifaj politikoj de transkodado.", - "transcoding_advanced_options_description": "Agordoj, kiujn plej multaj uzantoj ne bezonas ŝanĝi", - "transcoding_audio_codec": "Sonkodeko", - "transcoding_audio_codec_description": "Opus estas la plej altkvalita elekto, sed ĝi ne kongruas kun malnovaj aparatoj kaj softvaroj.", - "transcoding_bitrate_description": "Videoj kun bitrapido pli alta ol maksimumo, aŭ ne en akceptita formato", - "transcoding_codecs_learn_more": "Per lerni pli pri la terminaro uzata ĉi tie, legu la dokumentaron de FFmpeg pri kodeko H.264, kodeko HEVC kaj kodeko VP9.", - "transcoding_constant_quality_mode": "Reĝimo de konstanta kvalito", - "transcoding_constant_quality_mode_description": "ICQ estas pli bona ol CQP, sed kelkaj aparatoj de plirapidigo ne subtenas ĝin. Ŝalti tion ĉi privilegiigas la elektitan reĝimon dum uzo de kodado bazita sur kvalito. Ignorita de NVENC ĉar ĝi ne subtenas ICQ.", - "transcoding_constant_rate_factor": "Konstanta rapida faktoro (-crf)", - "transcoding_constant_rate_factor_description": "Nivelo de video-kvalito. Tipaj valoroj estas 23 por H.264, 28 por HEVC, 31 por VP9, kaj 35 por AV1. Pli malalta cifero indikas pli altan kvaliton, sed kreas pli pezajn dosierojn.", - "transcoding_disabled_description": "Ne transkodigi videojn. Tio povas perturbi vidigon en kelkaj klientoj", - "transcoding_encoding_options": "Agordoj de kodigo", - "transcoding_encoding_options_description": "Administri agordojn pri kodekoj, distingivo, kvalito, ktp. por la kodigitaj videoj", - "transcoding_hardware_acceleration": "Aparata plirapidigo", - "transcoding_hardware_acceleration_description": "Eksperimenta: pli rapida kodado, sed eble kun malpli bona kvalito je sama bitrapido", - "transcoding_hardware_decoding": "Aparata malkodado", - "transcoding_hardware_decoding_setting_description": "Ŝaltas tutvojan plirapidigon anstataŭ nur pliradidan kodadon. Povus ne funkcii por kelkaj videoj.", - "transcoding_max_b_frames": "Makimuma nombro de B-kadroj", - "transcoding_max_b_frames_description": "Pli alta valoro indikas pli efikan densigon, sed malpli rapidan kodadon. Eble ne funkcios kun pli malnova aparata plirapidigo. Valoro de 0 malŝaltas B-kadrojn. Valoro de -1 indikas aŭtomate elektitan valoron.", - "transcoding_max_bitrate": "Maksimuma bitrapido", - "transcoding_max_bitrate_description": "Agordi maksimuman bitrapidon rezultas je dosieroj kun pli antaŭvidebla grandeco, kun nur malgranda perdo de kvalito. Por 720p, tipaj valoroj estas 2600 kbit/s por VP9 aŭ HEVC, aŭ 4500 kbit/s por H.264. Valoro de 0 indikas 'malŝaltita'. Defaŭlta unuo estas k (t.e. kbit/s), do '5000', '5000k' kaj '5M' estas ekvivalentaj.", - "transcoding_max_keyframe_interval": "Maksimuma intervalo inter ĉefaj kadroj", - "transcoding_max_keyframe_interval_description": "Agordas la maksimuman distancon inter ĉefaj kadroj. Malaltaj valoroj malhelpas densigon, sed povas plibonigi kvaliton en scenoj kun rapidaj movoj. Valoro de 0 indikas aŭtomatan agordigon.", - "transcoding_optimal_description": "La videoj havas distingivon pli altan ol tiu celita, aŭ ne havas akcepteblan formaton", - "transcoding_policy": "Politiko de transkodado", - "transcoding_policy_description": "Kriterioj por indiki ĉu video estas transkodita aŭ ne", - "transcoding_preferred_hardware_device": "Preferita aparato", - "transcoding_settings_description": "Administri transkodadon de videoj", - "trash_settings_description": "Administri agordojn pri rubaĵoj", - "user_settings_description": "Administri agordojn pri uzantoj" - }, - "asset_viewer_settings_subtitle": "Administri agordojn pri vidilo de galerioj", - "backup_setting_subtitle": "Administri agordojn pri fona kaj malfona alŝutado", - "backup_settings_subtitle": "Administri agordojn pri alŝutado", - "cleanup_icloud_shared_albums_excluded": "Dividitaj albumoj ĉe iCloud estas ekskluditaj de la analizado", - "cleanup_step3_description": "Serĉi fotojn kaj videojn kun sekurkopio ĉe la servilo, laŭ la elektita limdato kaj filtriloj", - "download_settings_description": "Administri agordojn pri elŝutado de elementoj", - "edit_exclusion_pattern": "Redakti skemon de ekskludo", - "errors": { - "exclusion_pattern_already_exists": "Tiu ĉi skemo de ekskludo jam ekzistas.", - "unable_to_add_exclusion_pattern": "Ne eblas aldoni skemon de ekskludo", - "unable_to_delete_exclusion_pattern": "Ne eblas forigi skemon de ekskludo", - "unable_to_edit_exclusion_pattern": "Ne eblas redakti skemon de ekskludo", - "unable_to_scan_libraries": "Ne eblas analizi biblitekojn", - "unable_to_scan_library": "Ne eblas analizi biblitekon" - }, - "exclusion_pattern": "Skemo de ekskludo", - "explore": "Esplori", - "explorer": "Foliumilo", - "manage_media_access_settings": "Malfermi agordaĵaron", - "manage_the_app_settings": "Agordi la apon", - "missing": "Netraktitaj", - "networking_subtitle": "Administri agordojn pri finpunktoj de la servilo", - "no_explore_results_message": "Alŝutu pli da fotoj por esplori vian kolekton.", - "preferences_settings_subtitle": "Administri agordojn pri la apo", - "purchase_settings_server_activated": "La administranto respondecas pri la ŝlosilo de aŭtentikeco por la servilo", - "refresh": "Denove", - "rescan": "Reanalizi", - "reset": "Restartigi", - "scan": "Analizi", - "scan_all_libraries": "Analizi ĉiujn bibliotekojn", - "scan_library": "Analizi", - "scan_settings": "Agordoj pri analizado", - "scanning": "Analizado", - "scanning_for_album": "Serĉado de albumo...", - "search_suggestion_list_smart_search_hint_1": "Inteligenta serĉado defaŭlte estas ŝaltita. Por serĉi metadatumojn, uzu sintakson tiel ", - "upload_concurrency": "Nombro da samtempaj alŝutoj", - "user_pin_code_settings_description": "Administri vian PIN-kodon", - "user_purchase_settings_description": "Administri vian aĉeton", - "view_links": "Vidi ligilojn", - "week": "Semajno", - "wifi_name": "Nomo de Vifireto", - "year": "Jaro", - "yes": "Jes" -} +{} diff --git a/i18n/es.json b/i18n/es.json index 3f9163481d..0967ef424b 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -1,2401 +1 @@ -{ - "about": "Acerca de", - "account": "Cuenta", - "account_settings": "Ajustes de la cuenta", - "acknowledge": "Aceptar", - "action": "Acción", - "action_common_update": "Actualizar", - "action_description": "Un conjunto de acciones a realizar en los recursos filtrados", - "actions": "Acciones", - "active": "Activo", - "active_count": "Activo: {count}", - "activity": "Actividad", - "activity_changed": "La actividad está {enabled, select, true {habilitada} other {deshabilitada}}", - "add": "Añadir", - "add_a_description": "Añadir descripción", - "add_a_location": "Añadir una ubicación", - "add_a_name": "Añadir un nombre", - "add_a_title": "Añadir título", - "add_action": "Añadir acción", - "add_action_description": "Haga clic para añadir una acción a realizar", - "add_assets": "Añadir recursos", - "add_birthday": "Añadir un cumpleaños", - "add_endpoint": "Añadir punto final", - "add_exclusion_pattern": "Añadir patrón de exclusión", - "add_filter": "Añadir filtro", - "add_filter_description": "Haga clic para añadir una condición de filtro", - "add_location": "Añadir ubicación", - "add_more_users": "Añadir más usuarios", - "add_partner": "Añadir miembro", - "add_path": "Añadir ruta", - "add_photos": "Añadir fotos", - "add_tag": "Añadir etiqueta", - "add_to": "Añadir a…", - "add_to_album": "Añadir al álbum", - "add_to_album_bottom_sheet_added": "Añadido a {album}", - "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", - "add_to_album_bottom_sheet_some_local_assets": "Algunos recursos locales no se pudieron añadir al álbum", - "add_to_album_toggle": "Alternar selección para el {album}", - "add_to_albums": "Añadir a álbumes", - "add_to_albums_count": "Añadir a {count} álbumes", - "add_to_bottom_bar": "Añadir a", - "add_to_shared_album": "Añadir al álbum compartido", - "add_upload_to_stack": "Añadir subida a la cola", - "add_url": "Añadir URL", - "add_workflow_step": "Añadir paso al flujo de trabajo", - "added_to_archive": "Añadido al archivo", - "added_to_favorites": "Añadido a favoritos", - "added_to_favorites_count": "Añadido {count, number} a favoritos", - "admin": { - "add_exclusion_pattern_description": "Añade patrones de exclusión. Puedes utilizar los caracteres *, ** y ? (globbing). Ejemplos: para ignorar todos los archivos en cualquier directorio llamado \"Raw\", utiliza \"**/Raw/**\". Para ignorar todos los archivos que terminan en \".tif\", utiliza \"**/*.tif\". Para ignorar una ruta absoluta, utiliza \"/carpeta/a/ignorar/**\".", - "admin_user": "Usuario administrador", - "asset_offline_description": "Este recurso externo de la biblioteca ya no se encuentra en el disco y se ha movido a la papelera. Si el archivo se movió dentro de la biblioteca, comprueba la línea temporal para el nuevo recurso correspondiente. Para restaurar este recurso, asegúrate de que Immich puede acceder a la siguiente ruta de archivo y escanear la biblioteca.", - "authentication_settings": "Parámetros de autenticación", - "authentication_settings_description": "Gestionar contraseñas, OAuth y otros parámetros de autenticación", - "authentication_settings_disable_all": "¿Estás seguro de que deseas desactivar todos los métodos de inicio de sesión? Esto desactivará por completo el inicio de sesión.", - "authentication_settings_reenable": "Para reactivarlo, utiliza un Comando del servidor.", - "background_task_job": "Tareas en segundo plano", - "backup_database": "Crear volcado de la base de datos", - "backup_database_enable_description": "Activar volcados de la base de datos", - "backup_keep_last_amount": "Cantidad de volcados previos a mantener", - "backup_onboarding_1_description": "Copia en un lugar externo, en la nube u otra ubicación física.", - "backup_onboarding_2_description": "copias locales en diferentes dispositivos. Incluye los archivos principales y una copia de seguridad local de dichos archivos.", - "backup_onboarding_3_description": "copias totales de tu data, incluyendo los archivos originales. Incluye 1 copia fuera de sitio y 2 copias locales.", - "backup_onboarding_description": "Una estrategia de copia de seguridad 3-2-1 es recomendada para proteger tu data. Deberías mantener tanto copias de tus fotos/videos subidos como de la base de datos de Immich para tener una solución de copia de seguridad integral.", - "backup_onboarding_footer": "Para obtener más información sobre cómo hacer una copia de seguridad de Immich, consulta la documentación.", - "backup_onboarding_parts_title": "Una copia de seguridad 3-2-1 incluye:", - "backup_onboarding_title": "Copias de seguridad", - "backup_settings": "Ajustes de volcado de base de datos", - "backup_settings_description": "Administrar configuración de volcado de base de datos.", - "cleared_jobs": "Trabajos borrados para: {job}", - "config_set_by_file": "La configuración está definida por un archivo de configuración", - "confirm_delete_library": "¿Estás seguro de que quieres eliminar la biblioteca {library}?", - "confirm_delete_library_assets": "¿Estás seguro de que quieras eliminar esta biblioteca? Esto eliminará los {count, plural, one {# contained asset} other {all # contained assets}} elementos en Immich y no puede deshacerse. Los archivos permanecerán en disco.", - "confirm_email_below": "Para confirmar, escribe \"{email}\" a continuación", - "confirm_reprocess_all_faces": "¿Estás seguro de que deseas reprocesar todas las caras? Esto borrará a todas las personas que nombraste.", - "confirm_user_password_reset": "¿Estás seguro de que quieres restablecer la contraseña de {user}?", - "confirm_user_pin_code_reset": "¿Seguro que quieres restablecer el PIN de {user}?", - "copy_config_to_clipboard_description": "Copiar la configuración actual del sistema como un objeto JSON al", - "create_job": "Crear trabajo", - "cron_expression": "Expresión cron", - "cron_expression_description": "Establece el intervalo de escaneo utilizando el formato cron. Para más información puedes consultar, por ejemplo, Crontab Guru", - "cron_expression_presets": "Valores predefinidos de expresiones cron", - "disable_login": "Deshabilitar inicio de sesión", - "duplicate_detection_job_description": "Lanza el aprendizaje automático para detectar imágenes similares. Necesita tener activado \"Búsqueda Inteligente\"", - "exclusion_pattern_description": "Los patrones de exclusión te permiten ignorar archivos y carpetas al escanear tu biblioteca. Es útil si tienes carpetas que contienen archivos que no deseas importar, por ejemplo archivos RAW.", - "export_config_as_json_description": "Descargar la configuración actual del sistema como un archivo JSON", - "external_libraries_page_description": "Página de biblioteca externa del administrador", - "face_detection": "Detección de caras", - "face_detection_description": "Detecta las caras en los elementos mediante aprendizaje automático. En el caso de los vídeos, solo se tiene en cuenta la miniatura. \"Actualizar\" (re)procesará todos los elementos. \"Restablecer\" borra además todos los datos de caras actuales. \"Faltante\" pone en cola los elementos que aún no se han procesado. Las caras detectadas se pondrán en cola para el reconocimiento facial una vez finalizada la detección, agrupándolos en personas existentes o nuevas.", - "facial_recognition_job_description": "Agrupa las caras detectadas en personas. Este paso se realiza después de completar la detección de caras. \"Restablecer\" (re)agrupa todas las caras. \"Faltante\" pone en cola las caras que no tienen una persona asignada.", - "failed_job_command": "El comando {command} ha fallado para la tarea: {job}", - "force_delete_user_warning": "CUIDADO: Esta acción eliminará inmediatamente el usuario y todos los elementos. Esta accion no se puede deshacer y los archivos no pueden ser recuperados.", - "image_format": "Formato", - "image_format_description": "WebP genera archivos más pequeños que JPEG, pero es más lento al codificarlos.", - "image_fullsize_description": "Imagen de tamaño completo con metadatos removidos, usado cuando se hace zoom", - "image_fullsize_enabled": "Activar generación de imágenes a tamaño completo", - "image_fullsize_enabled_description": "Generar imágenes a tamaño completo para formatos no aptos para web. Cuando \"Preferir vista previa incrustada\" está activada, las vistas previas incrustadas se utilizan directamente sin conversión. No afecta a los formatos aptos para la web, como JPEG.", - "image_fullsize_quality_description": "De 1 a 100, calidad de imágenes de tamaño completo. Mientras más alto es mejor, pero genera archivos de mayor tamaño.", - "image_fullsize_title": "Configuraciones de imágenes de tamaño completo", - "image_prefer_embedded_preview": "Preferir vista previa embebida", - "image_prefer_embedded_preview_setting_description": "Usar vistas previas embebidas en fotos RAW como entrada para el procesamiento de imágenes y cuando estén disponibles. Esto puede producir colores más precisos en algunas imágenes, pero la calidad de la vista previa depende de la cámara y la imagen puede tener más artefactos de compresión.", - "image_prefer_wide_gamut": "Preferir 'gamut' amplio", - "image_prefer_wide_gamut_setting_description": "Usar \"Display P3\" para las miniaturas. Preserva mejor la vivacidad de las imágenes con espacios de color amplios pero las imágenes pueden aparecer de manera diferente en dispositivos antiguos con una versión antigua del navegador. Las imágenes sRGB se mantienen como sRGB para evitar cambios de color.", - "image_preview_description": "Imagen de tamaño mediano con metadatos eliminados. Es utilizado al visualizar un solo activo y para el aprendizaje automático", - "image_preview_quality_description": "Calidad de vista previa de 1 a 100. Es mejor cuanto más alta sea la calidad pero genera archivos más grandes y puede reducir la capacidad de respuesta de la aplicación. Establecer un valor bajo puede afectar la calidad del aprendizaje automático.", - "image_preview_title": "Ajustes de las vistas previas", - "image_progressive": "Progressivo", - "image_progressive_description": "Codifica imágenes JPEG progresivamente para una visualización con carga gradual. Esto no afecta a las imágenes WebP.", - "image_quality": "Calidad", - "image_resolution": "Resolución", - "image_resolution_description": "Las resoluciones más altas pueden conservar más detalles pero requieren más tiempo para codificar, tienen tamaños de archivo más grandes y pueden afectar la capacidad de respuesta de la aplicación.", - "image_settings": "Ajustes de imagen", - "image_settings_description": "Administrar la calidad y resolución de las imágenes generadas", - "image_thumbnail_description": "Miniatura pequeña con metadatos eliminados. Se utiliza al visualizar grupos de fotos como la línea temporal principal", - "image_thumbnail_quality_description": "Calidad de miniatura de 1 a 100. Es mejor cuanto más alto es el valor pero genera archivos más grandes y puede reducir la capacidad de respuesta de la aplicación.", - "image_thumbnail_title": "Ajustes de las miniaturas", - "import_config_from_json_description": "Importar la configuración del sistema subiendo un archivo JSON de configuración", - "job_concurrency": "{job}: Procesos simultáneos", - "job_created": "Tarea creada", - "job_not_concurrency_safe": "Esta tarea no es segura para la simultaneidad.", - "job_settings": "Configuración de tareas", - "job_settings_description": "Administrar tareas simultáneas", - "jobs_delayed": "{jobCount, plural, one {# retrasado} other {# retrasados}}", - "jobs_failed": "{jobCount, plural, one {# fallido} other {# fallidos}}", - "jobs_over_time": "Trabajos a lo largo del tiempo", - "library_created": "La biblioteca ha sido creada: {library}", - "library_deleted": "Biblioteca eliminada", - "library_details": "Detalles de la biblioteca", - "library_folder_description": "Especifica una carpeta para importar. Esta carpeta, incluidas sus subcarpetas, se analizará en busca de imágenes y vídeos.", - "library_remove_exclusion_pattern_prompt": "¿Estás seguro de que quieres eliminar este patrón de exclusión?", - "library_remove_folder_prompt": "¿Estás seguro de que quieres eliminar esta carpeta de importación?", - "library_scanning": "Escaneo periódico", - "library_scanning_description": "Configurar el escaneo periódico de la biblioteca", - "library_scanning_enable_description": "Activar el escaneo periódico de la biblioteca", - "library_settings": "Biblioteca externa", - "library_settings_description": "Administrar configuración biblioteca externa", - "library_tasks_description": "Buscar elementos nuevos o modificados en bibliotecas externas", - "library_updated": "Biblioteca actualizada", - "library_watching_enable_description": "Vigilar las bibliotecas externas para detectar cambios en los archivos", - "library_watching_settings": "Vigilancia de la biblioteca [EXPERIMENTAL]", - "library_watching_settings_description": "Vigilar automaticamente en busca de archivos modificados", - "logging_enable_description": "Habilitar registro", - "logging_level_description": "Indica el nivel de registro a utilizar cuando está habilitado.", - "logging_settings": "Registro", - "machine_learning_availability_checks": "Comprobaciones de disponibilidad", - "machine_learning_availability_checks_description": "Automáticamente detectar y preferir servidores de machine learning disponibles", - "machine_learning_availability_checks_enabled": "Habilitar comprobaciones de disponibilidad", - "machine_learning_availability_checks_interval": "Intervalo de comprobación", - "machine_learning_availability_checks_interval_description": "Intervalo en milisegundos entre las comprobaciones de disponibilidad", - "machine_learning_availability_checks_timeout": "Tiempo de espera de solicitud", - "machine_learning_availability_checks_timeout_description": "Tiempo de espera en milisegundos para comprobaciones de disponibilidad", - "machine_learning_clip_model": "Modelo CLIP (Contrastive Language-Image Pre-Training)", - "machine_learning_clip_model_description": "El nombre de un modelo CLIP listado aquí. Tendrás que relanzar el trabajo 'Búsqueda Inteligente' para todos los elementos al cambiar de modelo.", - "machine_learning_duplicate_detection": "Detección de duplicados", - "machine_learning_duplicate_detection_enabled": "Habilitar detección de duplicados", - "machine_learning_duplicate_detection_enabled_description": "Si está deshabilitado, los activos exactamente idénticos seguirán siendo eliminados.", - "machine_learning_duplicate_detection_setting_description": "Usa incrustaciones de CLIP (Contrastive Language-Image Pre-Training) para encontrar posibles duplicados", - "machine_learning_enabled": "Habilitar aprendizaje automático", - "machine_learning_enabled_description": "Al desactivarla todas las funciones de ML se deshabilitarán independientemente de la configuración a continuación.", - "machine_learning_facial_recognition": "Reconocimiento facial", - "machine_learning_facial_recognition_description": "Detecta, reconoce y agrupa las caras en las imágenes", - "machine_learning_facial_recognition_model": "Modelo de reconocimiento facial", - "machine_learning_facial_recognition_model_description": "Los modelos se muestran en orden descendente de tamaño. Los modelos más grandes son más lentos y utilizan más memoria pero producen mejores resultados. Ten en cuenta que debes volver a ejecutar la tarea de reconocimiento facial para todas las imágenes al cambiar de modelo.", - "machine_learning_facial_recognition_setting": "Habilitar reconocimiento facial", - "machine_learning_facial_recognition_setting_description": "Cuando está desactivado no se utlizará reconocimiento facial y no aparecerán nuevas caras en la sección Personas en la página Explorar.", - "machine_learning_max_detection_distance": "Máxima distancia de detección", - "machine_learning_max_detection_distance_description": "Distancia máxima entre dos imágenes para considerarlas duplicadas, oscilando entre 0,001-0,1. Los valores más altos detectarán más duplicados pero pueden generar falsos positivos.", - "machine_learning_max_recognition_distance": "Máxima distancia de reconocimiento", - "machine_learning_max_recognition_distance_description": "Distancia máxima entre dos caras para que se consideren una misma persona, oscilando entre 0-2. Reducirlo puede evitar etiquetar a dos personas como la misma persona, mientras que aumentarlo puede evitar etiquetar a la misma persona como dos personas diferentes. Ten en cuenta que es más fácil fusionar a dos personas que dividir a una en dos, así que opta por un umbral más bajo cuando sea posible.", - "machine_learning_min_detection_score": "Puntuación mínima de detección", - "machine_learning_min_detection_score_description": "Puntuación de confianza mínima para que se detecte una cara de 0 a 1. Los valores más bajos detectarán más rostros pero pueden generar falsos positivos.", - "machine_learning_min_recognized_faces": "Rostros mínimos reconocidos", - "machine_learning_min_recognized_faces_description": "El número mínimo de rostros reconocidos para que se cree una persona. Aumentar esto permite que el reconocimiento facial sea más preciso a costa de aumentar la posibilidad de que no se asigne una cara a una persona.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Usa el aprendizaje automático para reconocer texto en imágenes", - "machine_learning_ocr_enabled": "Activar OCR", - "machine_learning_ocr_enabled_description": "Si está desactivado, las imágenes no se someterán al reconocimiento de texto.", - "machine_learning_ocr_max_resolution": "Resolución máxima", - "machine_learning_ocr_max_resolution_description": "Las vistas previas por encima de esta resolución se redimensionarán manteniendo la relación de aspecto. Los valores más altos son más precisos, pero tardan más en procesarse y consumen más memoria.", - "machine_learning_ocr_min_detection_score": "Puntuación mínima de detección", - "machine_learning_ocr_min_detection_score_description": "Puntuación mínima de confianza para que el texto sea detectado de 0 a 1. Los valores más bajos detectarán más texto, pero pueden producir falsos positivos.", - "machine_learning_ocr_min_recognition_score": "Puntuación mínima de reconocimiento", - "machine_learning_ocr_min_score_recognition_description": "Puntuación mínima de confianza para que el texto detectado sea reconocido de 0 a 1. Los valores más bajos reconocerán más texto, pero pueden producir falsos positivos.", - "machine_learning_ocr_model": "Modelo de OCR", - "machine_learning_ocr_model_description": "Los modelos del servidor son más precisos que los modelos móviles, pero tardan más en procesar y consumen más memoria.", - "machine_learning_settings": "Configuración de aprendizaje automático", - "machine_learning_settings_description": "Administrar funciones y configuraciones de aprendizaje automático", - "machine_learning_smart_search": "Búsqueda inteligente", - "machine_learning_smart_search_description": "Busque imágenes semánticamente utilizando incrustaciones CLIP (Contrastive Language-Image Pre-Training)", - "machine_learning_smart_search_enabled": "Habilitar búsqueda inteligente", - "machine_learning_smart_search_enabled_description": "Al desactivarlo las imágenes no se procesarán para usar la búsqueda inteligente.", - "machine_learning_url_description": "La URL del servidor de aprendizaje automático. Si se proporciona más de una URL se intentará acceder a cada servidor sucesivamente hasta que uno responda correctamente en el orden especificado. Los servidores que no respondan serán ignorados temporalmente hasta que vuelvan a estar en línea.", - "maintenance_delete_backup": "Eliminar copia de seguridad", - "maintenance_delete_backup_description": "Este archivo será eliminado de forma permanente.", - "maintenance_delete_error": "Fallo al eliminar la copia de seguridad.", - "maintenance_restore_backup": "Restaurar copia de seguridad", - "maintenance_restore_backup_description": "Se borrará el historial de Immich y se restaurará desde la copia de seguridad seleccionada. Se creará una copia de seguridad antes de continuar.", - "maintenance_restore_backup_different_version": "¡Esta copia de seguridad se creó con una versión diferente de Immich!", - "maintenance_restore_backup_unknown_version": "No se pudo determinar la versión del respaldo.", - "maintenance_restore_database_backup": "Restaurar copia de seguridad de la base de datos", - "maintenance_restore_database_backup_description": "Revertir a un estado anterior de la base de datos mediante un archivo de respaldo", - "maintenance_settings": "Mantenimiento", - "maintenance_settings_description": "Poner Immich en modo de mantenimiento.", - "maintenance_start": "Cambiar al modo de mantenimiento", - "maintenance_start_error": "Error al iniciar el modo de mantenimiento.", - "maintenance_upload_backup": "Subir archivo de copia de seguridad de la base de datos", - "maintenance_upload_backup_error": "No se pudo cargar la copia de seguridad, ¿es un archivo .sql/.sql.gz?", - "manage_concurrency": "Ajustes de concurrencia", - "manage_concurrency_description": "Navegar a la página de trabajos para administrar la concurrencia de trabajos", - "manage_log_settings": "Administrar la configuración de los registros", - "map_dark_style": "Estilo oscuro", - "map_enable_description": "Habilitar características del mapa", - "map_gps_settings": "Configuración de mapas y GPS", - "map_gps_settings_description": "Administrar la configuración de mapas y GPS (geocodificación inversa)", - "map_implications": "La función de mapa depende de un servicio externo de mosaicos (tiles.immich.cloud)", - "map_light_style": "Estilo claro", - "map_manage_reverse_geocoding_settings": "Gestionar los ajustes de la geocodificación inversa", - "map_reverse_geocoding": "Geocodificación inversa", - "map_reverse_geocoding_enable_description": "Activar geocodificación inversa", - "map_reverse_geocoding_settings": "Ajustes de la geocodificación inversa", - "map_settings": "Mapa", - "map_settings_description": "Administrar la configuración del mapa", - "map_style_description": "Dirección URL a un tema de mapa (style.json)", - "memory_cleanup_job": "Limpieza de recuerdos", - "memory_generate_job": "Generación de recuerdos", - "metadata_extraction_job": "Extracción de metadatos", - "metadata_extraction_job_description": "Extraer información de metadatos de cada activo, como GPS, caras y resolución", - "metadata_faces_import_setting": "Activar importación de caras", - "metadata_faces_import_setting_description": "Importar caras desde los metadatos EXIF y auxiliares de una imagen", - "metadata_settings": "Configuración de metadatos", - "metadata_settings_description": "Administrar la configuración de metadatos", - "migration_job": "Migración", - "migration_job_description": "Migrar miniaturas de archivos y caras a la estructura de carpetas más reciente", - "nightly_tasks_cluster_faces_setting_description": "Ejecutar reconocimiento facial en caras detectadas recientemente", - "nightly_tasks_cluster_new_faces_setting": "Agrupar caras nuevas", - "nightly_tasks_database_cleanup_setting": "Tareas de limpieza de base de datos", - "nightly_tasks_database_cleanup_setting_description": "Limpiar datos antiguos y caducados de la base de datos", - "nightly_tasks_generate_memories_setting": "Generar recuerdos", - "nightly_tasks_generate_memories_setting_description": "Crear nuevos recuerdos a partir de activos", - "nightly_tasks_missing_thumbnails_setting": "Generar miniaturas faltantes", - "nightly_tasks_missing_thumbnails_setting_description": "Poner en cola a activos sin miniaturas para la generación de miniaturas", - "nightly_tasks_settings": "Configuración de Tareas Nocturnas", - "nightly_tasks_settings_description": "Gestionar Tareas Nocturnas", - "nightly_tasks_start_time_setting": "Tiempo de inicio", - "nightly_tasks_start_time_setting_description": "El tiempo cuando el servidor comienza a ejecutar las tareas nocturnas", - "nightly_tasks_sync_quota_usage_setting": "Uso de la cuota de sincronización", - "nightly_tasks_sync_quota_usage_setting_description": "Actualizar la cuota de almacenamiento del usuario, según el uso actual", - "no_paths_added": "No se han añadido rutas", - "no_pattern_added": "No se agregó ningún patrón", - "note_apply_storage_label_previous_assets": "Nota: Para aplicar la Etiqueta de Almacenamiento a los elementos previamente subidos, ejecuta la", - "note_cannot_be_changed_later": "NOTA: ¡No se puede cambiar posteriormente!", - "notification_email_from_address": "Desde", - "notification_email_from_address_description": "Dirección de correo electrónico del remitente, por ejemplo: \"Immich Photo Server \". Asegúrate de utilizar una dirección desde la que puedas enviar correos electrónicos.", - "notification_email_host_description": "Host del servidor de correo electrónico (por ejemplo: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorar errores de certificado", - "notification_email_ignore_certificate_errors_description": "Ignorar los errores de validación del certificado TLS (no recomendado)", - "notification_email_password_description": "Contraseña a utilizar al autenticarse con el servidor de correo electrónico", - "notification_email_port_description": "Puerto del servidor de correo electrónico (por ejemplo: 25, 465 o 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Usar SMTPS (SMTP sobre TLS)", - "notification_email_sent_test_email_button": "Enviar correo electrónico de prueba y guardar", - "notification_email_setting_description": "Configuraciones para enviar notificaciones por correo electrónico", - "notification_email_test_email": "Enviar email de prueba", - "notification_email_test_email_failed": "No se pudo enviar el correo electrónico de prueba, verifique sus valores", - "notification_email_test_email_sent": "Se envió un correo electrónico de prueba a {email}. Por favor, revisa tu bandeja de entrada.", - "notification_email_username_description": "Nombre de usuario a utilizar al autenticarse con el servidor de correo electrónico", - "notification_enable_email_notifications": "Habilitar notificaciones por correo electrónico", - "notification_settings": "Configuración de las notificaciones", - "notification_settings_description": "Administrar la configuración de notificaciones, incluido el correo electrónico", - "oauth_auto_launch": "Lanzamiento automático", - "oauth_auto_launch_description": "Inicie el flujo de inicio de sesión de OAuth automáticamente al acceder a la página de inicio de sesión", - "oauth_auto_register": "Registro automático", - "oauth_auto_register_description": "Registre automáticamente nuevos usuarios después de iniciar sesión con OAuth", - "oauth_button_text": "Texto del botón", - "oauth_client_secret_description": "Requerido para clientes confidenciales, o si PKCE (Prueba de clave para el intercambio de códigos) no es compatible con clientes públicos.", - "oauth_enable_description": "Iniciar sesión con OAuth", - "oauth_mobile_redirect_uri": "URI de redireccionamiento móvil", - "oauth_mobile_redirect_uri_override": "Sobreescribir URI de redirección móvil", - "oauth_mobile_redirect_uri_override_description": "Habilitar cuando el proveedor de OAuth no permite una URI móvil, como ''{callback}''", - "oauth_role_claim": "Concesión de rol", - "oauth_role_claim_description": "Otorgar acceso de administrador automáticamente según la presencia de esta concesión. La concesión puede tener \"usuario\" o \"admin\".", - "oauth_settings": "OAuth", - "oauth_settings_description": "Administrar la configuración de inicio de sesión de OAuth", - "oauth_settings_more_details": "Para más detalles acerca de esta característica, consulte la documentación.", - "oauth_storage_label_claim": "Solicitud de etiqueta de almacenamiento", - "oauth_storage_label_claim_description": "Fijar la etiqueta de almacenamiento del usuario automáticamente al valor solicitado.", - "oauth_storage_quota_claim": "Cuota de almacenamiento solicitada", - "oauth_storage_quota_claim_description": "Fijar la cuota de almacenamiento del usuario automáticamente al valor solicitado.", - "oauth_storage_quota_default": "Cuota de almacenamiento predeterminada (GiB)", - "oauth_storage_quota_default_description": "Cuota (en GiB) que se usará cuando no se solicite un valor específico.", - "oauth_timeout": "Tiempo de espera de la solicitud agotado", - "oauth_timeout_description": "Tiempo de espera de solicitudes en milisegundos", - "ocr_job_description": "Usar aprendizaje automático para reconocer texto en imágenes", - "password_enable_description": "Iniciar sesión con correo electrónico y contraseña", - "password_settings": "Contraseña de Acceso", - "password_settings_description": "Administrar la configuración de inicio de sesión con contraseña", - "paths_validated_successfully": "Todas las carpetas se han validado satisfactoriamente", - "person_cleanup_job": "Limpieza de personas", - "queue_details": "Detalles de cola", - "queues": "Colas de trabajo", - "queues_page_description": "Página de administración de colas de trabajo", - "quota_size_gib": "Tamaño de la cuota (GiB)", - "refreshing_all_libraries": "Actualizar todas las bibliotecas", - "registration": "Registrar administrador", - "registration_description": "Dado que eres el primer usuario del sistema, se te designará como administrador, tendrás a tu cargo las tareas administrativas y deberás crear los demás usuarios.", - "remove_failed_jobs": "Eliminar trabajos fallidos", - "require_password_change_on_login": "Requerir que el usuario cambie la contraseña en el primer inicio de sesión", - "reset_settings_to_default": "Restablecer la configuración predeterminada", - "reset_settings_to_recent_saved": "Restablecer la configuración a la configuración guardada recientemente", - "scanning_library": "Escaneando la biblioteca", - "search_jobs": "Buscar trabajos…", - "send_welcome_email": "Enviar correo de bienvenida", - "server_external_domain_settings": "Dominio externo", - "server_external_domain_settings_description": "Dominio para enlaces públicos compartidos, incluidos http(s)://", - "server_public_users": "Usuarios públicos", - "server_public_users_description": "Cuando se añade un usuario a los álbumes compartidos, todos los usuarios aparecen en una lista con su nombre y su correo electrónico. Si deshabilita esta opción, solo los administradores podrán ver la lista de usuarios.", - "server_settings": "Configuración del servidor", - "server_settings_description": "Administrar la configuración del servidor", - "server_stats_page_description": "Página de estadísticas de administrador del servidor", - "server_welcome_message": "Mensaje de bienvenida", - "server_welcome_message_description": "Mensaje para mostrar en la página de inicio de sesión.", - "settings_page_description": "Página de ajustes de administrador", - "sidecar_job": "Metadatos de archivos sidecar", - "sidecar_job_description": "Descubrir o sincronizar metadatos sidecar desde el sistema de archivos", - "slideshow_duration_description": "Número de segundos para mostrar cada imagen", - "smart_search_job_description": "Ejecute aprendizaje automático en archivos para respaldar la búsqueda inteligente", - "storage_template_date_time_description": "La fecha y hora de creación del elemento será usada para la información sobre la fecha", - "storage_template_date_time_sample": "Hora de la muestra {date}", - "storage_template_enable_description": "Habilitar el motor de plantillas de almacenamiento", - "storage_template_hash_verification_enabled": "Verificación de hash habilitada", - "storage_template_hash_verification_enabled_description": "Habilita la verificación de hash, no la desactive a menos que esté seguro de las implicaciones", - "storage_template_migration": "Migración de plantillas de almacenamiento", - "storage_template_migration_description": "Aplicar la {template} actual a los elementos subidos previamente", - "storage_template_migration_info": "La plantilla de almacenamiento convertirá todas las extensiones a minúscula. Los cambios en las plantillas solo se aplican a los elementos nuevos. Para aplicarlos retroactivamente a los elementos subidos previamente ejecute la {job}.", - "storage_template_migration_job": "Tarea de migración de la plantilla de almacenamiento", - "storage_template_more_details": "Para obtener más detalles sobre esta función, consulte la Plantilla de almacenamiento y sus implicaciones", - "storage_template_onboarding_description_v2": "Al habilitar esta función, los archivos se organizarán automáticamente según la plantilla definida por el usuario. Para más información, consulte la documentación.", - "storage_template_path_length": "Límite aproximado de la longitud de la ruta: {length, number}/{limit, number}", - "storage_template_settings": "Plantilla de almacenamiento", - "storage_template_settings_description": "Administrar la estructura de carpetas y el nombre de archivo del recurso subido", - "storage_template_user_label": "{label} es la etiqueta de almacenamiento del usuario", - "system_settings": "Ajustes del sistema", - "tag_cleanup_job": "Limpieza de etiquetas", - "template_email_available_tags": "Puede utilizar las siguientes variables en su plantilla: {tags}", - "template_email_if_empty": "Si la plantilla está vacía, se utilizará el correo electrónico predeterminado.", - "template_email_invite_album": "Plantilla de álbum de invitaciones", - "template_email_preview": "Vista previa", - "template_email_settings": "Modelos de correo electrónico", - "template_email_update_album": "Actualizar plantilla del álbum", - "template_email_welcome": "Plantilla de correo electrónico de bienvenida", - "template_settings": "Plantillas de notificación", - "template_settings_description": "Gestione plantillas personalizadas para las notificaciones", - "theme_custom_css_settings": "CSS Personalizado", - "theme_custom_css_settings_description": "Las Hojas de Estilo (CSS) permiten personalizar el diseño de Immich.", - "theme_settings": "Ajustes del tema", - "theme_settings_description": "Gestionar la personalización de la interfaz web de Immich", - "thumbnail_generation_job": "Generar miniaturas", - "thumbnail_generation_job_description": "Genere miniaturas grandes, pequeñas y borrosas para cada archivo, así como miniaturas para cada persona", - "transcoding_acceleration_api": "API Aceleración", - "transcoding_acceleration_api_description": "La API que interactuará con su dispositivo para acelerar la transcodificación. Esta configuración es el \"mejor esfuerzo\": recurrirá a la transcodificación del software en caso de error. VP9 puede funcionar o no dependiendo de su hardware.", - "transcoding_acceleration_nvenc": "NVENC (requiere GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (requiere procesador Intel de 7ª generación o superior)", - "transcoding_acceleration_rkmpp": "RKMPP (solo en SOC de Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Códecs de audio aceptados", - "transcoding_accepted_audio_codecs_description": "Seleccione qué códecs de audio no es necesario transcodificar. Solo se utiliza para determinadas políticas de transcodificación.", - "transcoding_accepted_containers": "Contenedores aceptados", - "transcoding_accepted_containers_description": "Selecciona qué formatos del contenedor no necesitan ser remixados a MP4. Solo se utiliza para determinadas políticas de transcodificación.", - "transcoding_accepted_video_codecs": "Códecs de vídeo aceptados", - "transcoding_accepted_video_codecs_description": "Seleccione qué códecs de vídeo no es necesario transcodificar. Solo se utiliza para determinadas políticas de transcodificación.", - "transcoding_advanced_options_description": "Opciones que la mayoría de los usuarios no deben cambiar", - "transcoding_audio_codec": "Codec de audio", - "transcoding_audio_codec_description": "Opus es la opción de mayor calidad, pero tiene menor compatibilidad con dispositivos o software antiguos.", - "transcoding_bitrate_description": "Vídeos con una tasa de bits superior a la máxima o que no están en un formato aceptado", - "transcoding_codecs_learn_more": "Para obtener más información sobre la terminología utilizada aquí, consulte la documentación de FFmpeg sobre los codecs H.264, HEVC y VP9.", - "transcoding_constant_quality_mode": "Modo de calidad constante", - "transcoding_constant_quality_mode_description": "ICQ es mejor que CQP, pero algunos dispositivos de aceleración de hardware no admiten este modo. Al configurar esta opción, se preferirá el modo especificado cuando se utilice codificación basada en calidad. NVENC lo ignora porque no es compatible con ICQ.", - "transcoding_constant_rate_factor": "Factor de tasa constante (-crf)", - "transcoding_constant_rate_factor_description": "Nivel de calidad del vídeo. Los valores típicos son 23 para H.264, 28 para HEVC, 31 para VP9 y 35 para AV1. Cuanto más bajo es mejor, pero produce archivos más grandes.", - "transcoding_disabled_description": "No transcodifique ningún vídeo; puede interrumpir la reproducción en algunos clientes", - "transcoding_encoding_options": "Opciones de codificación", - "transcoding_encoding_options_description": "Establecer códecs, resolución, calidad y otras opciones para los vídeos codificados", - "transcoding_hardware_acceleration": "Aceleración por Hardware", - "transcoding_hardware_acceleration_description": "Experimental: transcodificación más rápida, pero puede reducir la calidad con la misma tasa de bits", - "transcoding_hardware_decoding": "Decodificación por hardware", - "transcoding_hardware_decoding_setting_description": "Permite la aceleración de extremo a extremo en lugar de acelerar únicamente la codificación. Puede que no funcione en todos los vídeos.", - "transcoding_max_b_frames": "Maximos B-frames", - "transcoding_max_b_frames_description": "Los valores más altos mejoran la eficiencia de la compresión, pero ralentizan la codificación. Puede que no sea compatible con la aceleración de hardware en dispositivos más antiguos. 0 desactiva los fotogramas B, mientras que -1 establece este valor automáticamente.", - "transcoding_max_bitrate": "Máxima tasa de bits", - "transcoding_max_bitrate_description": "Establecer una tasa de bits máxima puede hacer que los tamaños de archivo sean más predecibles a un coste menor en la calidad. A 720p, los valores típicos son 2600 kbit/s para VP9 o HEVC, o 4500 kbit/s para H.264. Se desactiva si se establece en 0. Cuando no se especifica una unidad, se asume k (para kbit/s); por lo tanto, 5000, 5000k y 5M (para Mbit/s) son equivalentes.", - "transcoding_max_keyframe_interval": "Intervalo máximo de fotogramas clave", - "transcoding_max_keyframe_interval_description": "Establece la distancia máxima de fotograma entre fotogramas clave. Los valores más bajos empeoran la eficiencia de la compresión, pero mejoran los tiempos de búsqueda y pueden mejorar la calidad en escenas con movimientos rápidos. 0 establece este valor automáticamente.", - "transcoding_optimal_description": "Vídeos con una resolución superior a la fijada o que no están en un formato aceptado", - "transcoding_policy": "Política de transcodificación", - "transcoding_policy_description": "Establecer cuándo se transcodificará un vídeo", - "transcoding_preferred_hardware_device": "Dispositivo de hardware preferido", - "transcoding_preferred_hardware_device_description": "Se aplica únicamente a VAAPI y QSV. Establece el nodo dri utilizado para la transcodificación de hardware.", - "transcoding_preset_preset": "Configuración predefinida (-preset)", - "transcoding_preset_preset_description": "Velocidad de compresión. Los preajustes más lentos producen archivos más pequeños y aumentan la calidad cuando se apunta a una tasa de bits determinada. VP9 ignora las velocidades superiores al valor \"faster\" (\"más rápido\").", - "transcoding_reference_frames": "Frames de referencia", - "transcoding_reference_frames_description": "El número de fotogramas a los que hacer referencia al comprimir un fotograma determinado. Los valores más altos mejoran la eficiencia de la compresión, pero ralentizan la codificación. 0 establece este valor automáticamente.", - "transcoding_required_description": "Sólo vídeos que no estén en un formato soportado", - "transcoding_settings": "Configuración de Transcodificación de Vídeo", - "transcoding_settings_description": "Administrar qué vídeos transcodificar y cómo procesarlos", - "transcoding_target_resolution": "Resolución deseada", - "transcoding_target_resolution_description": "Las resoluciones más altas pueden conservar más detalles, pero la codificación tarda más, tienen tamaños de archivo más grandes y pueden reducir la capacidad de respuesta de la aplicación.", - "transcoding_temporal_aq": "AQ temporal", - "transcoding_temporal_aq_description": "Solo se aplica a NVENC. La Cuantificación Adaptativa Temporal aumenta la calidad de las escenas con mucho detalle y poco movimiento. Podría no ser compatible con dispositivos más antiguos.", - "transcoding_threads": "Hilos", - "transcoding_threads_description": "Los valores más altos conducen a una codificación más rápida, pero dejan menos espacio para que el servidor procese otras tareas mientras está activo. Este valor no debe ser mayor que la cantidad de núcleos de CPU. Maximiza la utilización si se establece en 0.", - "transcoding_tone_mapping": "Mapeo de tonos", - "transcoding_tone_mapping_description": "Intenta preservar la apariencia de los videos HDR cuando se convierten a SDR. Cada algoritmo realiza diferentes compensaciones en cuanto a color, detalle y brillo. Hable conserva los detalles, Mobius conserva el color y Reinhard conserva el brillo.", - "transcoding_transcode_policy": "Políticas de transcodificación", - "transcoding_transcode_policy_description": "Política sobre cuándo se debe transcodificar un vídeo. Los vídeos HDR siempre se transcodificarán (excepto si la transcodificación está desactivada).", - "transcoding_two_pass_encoding": "Codificación en dos pasadas", - "transcoding_two_pass_encoding_setting_description": "Transcodifica en dos pasadas para producir vídeos mejor codificados. Cuando la velocidad de bits máxima está habilitada (es necesaria para que funcione con H.264 y HEVC), este modo utiliza un rango de velocidad de bits basado en la velocidad de bits máxima e ignora CRF. Para VP9, se puede utilizar CRF si la tasa de bits máxima está deshabilitada.", - "transcoding_video_codec": "Códecs de Video", - "transcoding_video_codec_description": "VP9 tiene alta eficiencia y compatibilidad web, pero lleva mucho tiempo transcodificarlo. HEVC ofrece un rendimiento similar, pero tiene menor compatibilidad web. H.264 es ampliamente compatible y se transcodifica muy rápido, pero los archivos producidos son mucho más grandes. AV1 es el códec más eficiente, pero no es compatible con los dispositivos más antiguos.", - "trash_enabled_description": "Habilitar papelera", - "trash_number_of_days": "Número de días", - "trash_number_of_days_description": "Número de días para mantener los archivos en la papelera antes de eliminarlos permanentemente", - "trash_settings": "Configuración papelera", - "trash_settings_description": "Administrar la configuración de la papelera", - "unlink_all_oauth_accounts": "Desvincular todas las cuentas de OAuth", - "unlink_all_oauth_accounts_description": "Recuerda desvincular todas las cuentas de OAuth antes de migrar a un proveedor nuevo.", - "unlink_all_oauth_accounts_prompt": "¿Seguro que deseas desvincular todas las cuentas de OAuth? Se restablecerá el id. de OAuth de cada usuario. La acción no se podrá deshacer.", - "user_cleanup_job": "Limpieza de usuarios", - "user_delete_delay": "La cuenta {user} y los archivos se programarán para su eliminación permanente en {delay, plural, one {# día} other {# días}}.", - "user_delete_delay_settings": "Eliminar retardo", - "user_delete_delay_settings_description": "Número de días después de la eliminación para eliminar permanentemente la cuenta y los activos de un usuario. El trabajo de eliminación de usuarios se ejecuta a medianoche para comprobar si hay usuarios que estén listos para su eliminación. Los cambios a esta configuración se evaluarán en la próxima ejecución.", - "user_delete_immediately": "La cuenta {user} y los archivos se pondrán en cola para su eliminación permanente inmediatamente.", - "user_delete_immediately_checkbox": "Poner en cola la eliminación inmediata de usuarios y elementos", - "user_details": "Detalles del usuario", - "user_management": "Gestión de usuarios", - "user_password_has_been_reset": "La contraseña del usuario ha sido restablecida:", - "user_password_reset_description": "Proporcione una contraseña temporal al usuario e infórmele que deberá cambiar la contraseña en su próximo inicio de sesión.", - "user_restore_description": "La cuenta de {user} será restaurada.", - "user_restore_scheduled_removal": "Restaurar el usuario - eliminación programada el {date, date, long}", - "user_settings": "Ajustes de usuario", - "user_settings_description": "Administrar la configuración del usuario", - "user_successfully_removed": "El usuario {email} ha sido eliminado con éxito.", - "users_page_description": "Página de usuarios administradores", - "version_check_enabled_description": "Activar la comprobación de la versión", - "version_check_implications": "La función de comprobación de versiones depende de la comunicación periódica con github.com", - "version_check_settings": "Verificar Versión", - "version_check_settings_description": "Activar/desactivar la notificación de nueva versión", - "video_conversion_job": "Transcodificar vídeos", - "video_conversion_job_description": "Transcodifique vídeos para una mayor compatibilidad con navegadores y dispositivos" - }, - "admin_email": "Correo electrónico del administrador", - "admin_password": "Contraseña del administrador", - "administration": "Administración", - "advanced": "Avanzada", - "advanced_settings_clear_image_cache": "Borrar caché de imágenes", - "advanced_settings_clear_image_cache_error": "No se pudo borrar la caché de imágenes", - "advanced_settings_clear_image_cache_success": "Limpiado con éxito {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Usa esta opción para filtrar medios durante la sincronización según criterios alternativos. Intenta esto solo si tienes problemas con que la aplicación detecte todos los álbumes.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Usar filtro alternativo de sincronización de álbumes del dispositivo", - "advanced_settings_log_level_title": "Nivel de registro: {level}", - "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas desde los archivos locales. Activa esta opción para cargar imágenes remotas en su lugar.", - "advanced_settings_prefer_remote_title": "Preferir imágenes remotas", - "advanced_settings_proxy_headers_subtitle": "Configura encabezados HTTP que Immich incluirá en cada petición de red", - "advanced_settings_proxy_headers_title": "Cabeceras proxy personalizadas [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Habilita el modo de solo lectura donde las fotografías sólo pueden ser vistas, funciones como seleccionar múltiples imágenes, compartir, transmitir, eliminar son deshabilitadas. Habilita/Deshabilita solo lectura vía el avatar del usuario en la pantalla principal", - "advanced_settings_readonly_mode_title": "Modo solo lectura", - "advanced_settings_self_signed_ssl_subtitle": "Omitir verificación del certificado SSL del servidor. Requerido para certificados autofirmados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados [EXPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Eliminar o restaurar automáticamente un recurso en este dispositivo cuando se realice esa acción en la web", - "advanced_settings_sync_remote_deletions_title": "Sincronizar eliminaciones remotas [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Configuraciones avanzadas del usuario", - "advanced_settings_troubleshooting_subtitle": "Habilitar funciones adicionales para solución de problemas", - "advanced_settings_troubleshooting_title": "Solución de problemas", - "age_months": "Tiempo {months, plural, one {# mes} other {# meses}}", - "age_year_months": "1 año, {months, plural, one {# mes} other {# meses}}", - "age_years": "Edad {years, plural, one {# año} other {# años}}", - "album": "Álbum", - "album_added": "Álbum añadido", - "album_added_notification_setting_description": "Reciba una notificación por correo electrónico cuando lo añadan a un álbum compartido", - "album_cover_updated": "Portada del álbum actualizada", - "album_delete_confirmation": "¿Estás seguro de que deseas eliminar el álbum {album}?", - "album_delete_confirmation_description": "Si este álbum se comparte, otros usuarios ya no podrán acceder a él.", - "album_deleted": "Álbum eliminado", - "album_info_card_backup_album_excluded": "EXCLUIDOS", - "album_info_card_backup_album_included": "INCLUIDOS", - "album_info_updated": "Información del álbum actualizada", - "album_leave": "¿Abandonar el álbum?", - "album_leave_confirmation": "¿Estás seguro de que quieres dejar {album}?", - "album_name": "Nombre del Álbum", - "album_options": "Opciones del Album", - "album_remove_user": "¿Eliminar usuario?", - "album_remove_user_confirmation": "¿Estás seguro de que quieres eliminar a {user}?", - "album_search_not_found": "No se encontraron álbumes que coincidan con tu búsqueda", - "album_selected": "Álbum seleccionado", - "album_share_no_users": "Parece que has compartido este álbum con todos los usuarios o no tienes ningún usuario con quien compartirlo.", - "album_summary": "Resumen del álbum", - "album_updated": "Album actualizado", - "album_updated_setting_description": "Reciba una notificación por correo electrónico cuando un álbum compartido tenga nuevos archivos", - "album_upload_assets": "Añadir recursos desde tu computadora y añadir a un álbum", - "album_user_left": "Salida {album}", - "album_user_removed": "Eliminado a {user}", - "album_viewer_appbar_delete_confirm": "¿Estás seguro/a que quieres borrar este álbum de tu cuenta?", - "album_viewer_appbar_share_err_delete": "No ha podido eliminar el álbum", - "album_viewer_appbar_share_err_leave": "No se ha podido abandonar el álbum", - "album_viewer_appbar_share_err_remove": "Hay problemas para eliminar los elementos del álbum", - "album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum", - "album_viewer_appbar_share_leave": "Abandonar álbum", - "album_viewer_appbar_share_to": "Compartir Con", - "album_viewer_page_share_add_users": "Añadir usuarios", - "album_with_link_access": "Permitir que cualquiera que tenga el enlace vea las fotos y las personas en este álbum.", - "albums": "Álbumes", - "albums_count": "{count, plural, one {{count, number} álbum} other {{count, number} álbumes}}", - "albums_default_sort_order": "Ordenación por defecto de los álbumes", - "albums_default_sort_order_description": "Orden de clasificación inicial de los recursos al crear nuevos álbumes.", - "albums_feature_description": "Colecciones de recursos que pueden ser compartidos con otros usuarios.", - "albums_on_device_count": "Álbumes en el dispositivo ({count})", - "albums_selected": "{count, plural, one {# álbum seleccionado} other {# álbumes seleccionados}}", - "all": "Todos", - "all_albums": "Todos los álbumes", - "all_people": "Todas las personas", - "all_photos": "Todas las fotos", - "all_videos": "Todos los videos", - "allow_dark_mode": "Permitir modo oscuro", - "allow_edits": "Permitir edición", - "allow_public_user_to_download": "Permitir descargas a los usuarios públicos", - "allow_public_user_to_upload": "Permitir a los usuarios públicos subir fotos", - "allowed": "Permitido", - "alt_text_qr_code": "Código QR", - "always_keep": "Mantener siempre", - "always_keep_photos_hint": "El liberador de espacio en disco mantendrá todas las fotos en este dispositivo.", - "always_keep_videos_hint": "El liberador de espacio en disco mantendrá todos las vídeos en este dispositivo.", - "anti_clockwise": "En sentido antihorario", - "api_key": "Clave API", - "api_key_description": "Este valor sólo se mostrará una vez. Asegúrese de copiarlo antes de cerrar la ventana.", - "api_key_empty": "El nombre de su clave API no debe estar vacío", - "api_keys": "Claves API", - "app_architecture_variant": "Variante (Arquitectura)", - "app_bar_signout_dialog_content": "¿Estás seguro que quieres cerrar sesión?", - "app_bar_signout_dialog_ok": "Sí", - "app_bar_signout_dialog_title": "Cerrar sesión", - "app_download_links": "Enlaces de Descarga de la Aplicación", - "app_settings": "Ajustes de la aplicacion", - "app_stores": "Tiendas de Aplicaciones", - "app_update_available": "Actualización de aplicación está disponible", - "appears_in": "Aparece en", - "apply_count": "Aplicar ({count, number})", - "archive": "Archivo", - "archive_action_prompt": "{count} añadido(s) al archivo", - "archive_or_unarchive_photo": "Archivar o restaurar foto", - "archive_page_no_archived_assets": "No se encontraron elementos archivados", - "archive_page_title": "Archivo ({count})", - "archive_size": "Tamaño de archivo comprimido", - "archive_size_description": "Configure el tamaño del archivo para descargas (en GB)", - "archived": "Archivado", - "archived_count": "{count, plural, one {# archivado} other {# archivados}}", - "are_these_the_same_person": "¿Son la misma persona?", - "are_you_sure_to_do_this": "¿Estás seguro de que quieres hacer esto?", - "array_field_not_fully_supported": "Los campos de la matriz requieren edición manual de JSON", - "asset_action_delete_err_read_only": "No se puede borrar archivo(s) de solo lectura, omitiendo", - "asset_action_share_err_offline": "No se pudo obtener archivo(s) sin conexión, omitiendo", - "asset_added_to_album": "Añadido al álbum", - "asset_adding_to_album": "Añadiendo al álbum…", - "asset_created": "Activo creado", - "asset_description_updated": "La descripción del elemento ha sido actualizada", - "asset_filename_is_offline": "El archivo {filename} está offline", - "asset_has_unassigned_faces": "El archivo no tiene rostros asignados", - "asset_hashing": "Calculando hash…", - "asset_list_group_by_sub_title": "Agrupar por", - "asset_list_layout_settings_dynamic_layout_title": "Diseño dinámico", - "asset_list_layout_settings_group_automatically": "Automatico", - "asset_list_layout_settings_group_by": "Agrupar elementos por", - "asset_list_layout_settings_group_by_month_day": "Mes + día", - "asset_list_layout_sub_title": "Disposición", - "asset_list_settings_subtitle": "Configuraciones del diseño de la cuadrícula de fotos", - "asset_list_settings_title": "Cuadrícula de fotos", - "asset_not_found_on_device_android": "Activo no encontrado en el dispositivo", - "asset_not_found_on_device_ios": "No se encuentra el recurso en el dispositivo. Si usa iCloud, es posible que no pueda acceder al recurso debido a un archivo defectuoso almacenado en iCloud", - "asset_not_found_on_icloud": "No se ha encontrado el recurso en iCloud. Es posible que no se pueda acceder al recurso debido a un archivo defectuoso almacenado en iCloud", - "asset_offline": "Archivos sin conexión", - "asset_offline_description": "Este activo externo ya no se encuentra en el disco. Por favor, póngase en contacto con su administrador de Immich para obtener ayuda.", - "asset_restored_successfully": "Elementos restaurados exitosamente", - "asset_skipped": "Omitido", - "asset_skipped_in_trash": "En la papelera", - "asset_trashed": "Elemento eliminado", - "asset_troubleshoot": "Diagnóstico del elemento", - "asset_uploaded": "Subido", - "asset_uploading": "Subiendo…", - "asset_viewer_settings_subtitle": "Administra las configuraciones de tu visor de fotos", - "asset_viewer_settings_title": "Visor de archivos", - "assets": "elementos", - "assets_added_count": "{count, plural, one {# elemento añadido} other {# elementos añadidos}}", - "assets_added_to_album_count": "{count, plural, one {# elemento añadido} other {# elementos añadidos}} al álbum", - "assets_added_to_albums_count": "{assetTotal, plural, one {# añadido} other {# añadidos}} {albumTotal, plural, one {# al álbum} other {# a los álbumes}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {El elemento no se puede añadir al álbum} other {Los elementos no se pueden añadir al álbum}}", - "assets_cannot_be_added_to_albums": "{count, plural, one {El elemento} other {Los elementos}} no se {count, plural, one {puede} other {pueden}} añadir a ninguno de los álbumes", - "assets_count": "{count, plural, one {# activo} other {# activos}}", - "assets_deleted_permanently": "{count} elemento(s) eliminado(s) permanentemente", - "assets_deleted_permanently_from_server": "{count} recurso(s) eliminado(s) de forma permanente del servidor de Immich", - "assets_downloaded_failed": "{count, plural, one {# archivo descargado - {error} archivo fallido} other {# archivos descargados - {error} archivos fallidos}}", - "assets_downloaded_successfully": "{count, plural, one {# archivo descargado exitosamente} other {# archivos descargados exitosamente}}", - "assets_moved_to_trash_count": "{count, plural, one {# elemento movido} other {# elementos movidos}} a la papelera", - "assets_permanently_deleted_count": "Eliminado permanentemente {count, plural, one {# elemento} other {# elementos}}", - "assets_removed_count": "Eliminado {count, plural, one {# elemento} other {# elementos}}", - "assets_removed_permanently_from_device": "{count} elemento(s) eliminado(s) permanentemente de su dispositivo", - "assets_restore_confirmation": "¿Estás seguro de que quieres restaurar todos tus activos eliminados? ¡No puede deshacer esta acción! Tenga en cuenta que los archivos sin conexión no se pueden restaurar de esta manera.", - "assets_restored_count": "Restaurado {count, plural, one {# elemento} other {# elementos}}", - "assets_restored_successfully": "{count} elemento(s) restaurado(s) exitosamente", - "assets_trashed": "{count} elemento(s) eliminado(s)", - "assets_trashed_count": "Borrado {count, plural, one {# elemento} other {# elementos}}", - "assets_trashed_from_server": "{count} recurso(s) enviado(s) a la papelera desde el servidor de Immich", - "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} ya forma parte del álbum", - "assets_were_part_of_albums_count": "{count, plural, one {El elemento ya es} other {Los elementos ya son}} parte de los álbumes", - "authorized_devices": "Dispositivos autorizados", - "automatic_endpoint_switching_subtitle": "Conectarse localmente a través de la Wi-Fi designada cuando esté disponible y usar conexiones alternativas en otros lugares", - "automatic_endpoint_switching_title": "Cambio automático de URL", - "autoplay_slideshow": "Presentación con reproducción automática", - "back": "Atrás", - "back_close_deselect": "Atrás, cerrar o anular la selección", - "background_backup_running_error": "Ya se está ejecutando la copia de seguridad en segundo plano, no se puede iniciar la copia de seguridad manual", - "background_location_permission": "Permiso de ubicación en segundo plano", - "background_location_permission_content": "Para poder cambiar de red mientras se ejecuta en segundo plano, Immich debe tener *siempre* acceso a la ubicación precisa para que la aplicación pueda leer el nombre de la red Wi-Fi", - "background_options": "Opciones de segundo plano", - "backup": "Copia de seguridad", - "backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({count})", - "backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir", - "backup_album_selection_page_assets_scatter": "Los elementos pueden dispersarse en varios álbumes. De este modo, los álbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.", - "backup_album_selection_page_select_albums": "Seleccionar álbumes", - "backup_album_selection_page_selection_info": "Información sobre la Selección", - "backup_album_selection_page_total_assets": "Total de elementos únicos", - "backup_albums_sync": "Sincronización de álbumes de respaldo", - "backup_all": "Todos", - "backup_background_service_backup_failed_message": "Error al copiar elementos. Reintentando…", - "backup_background_service_complete_notification": "Copia de seguridad de activos completada", - "backup_background_service_connection_failed_message": "Error al conectar con el servidor. Reintentando…", - "backup_background_service_current_upload_notification": "Subiendo {filename}", - "backup_background_service_default_notification": "Comprobando nuevos elementos…", - "backup_background_service_error_title": "Error de copia de seguridad", - "backup_background_service_in_progress_notification": "Creando copia de seguridad de tus elementos…", - "backup_background_service_upload_failure_notification": "Error al subir {filename}", - "backup_controller_page_albums": "Álbumes de copia de seguridad", - "backup_controller_page_background_app_refresh_disabled_content": "Activa la actualización en segundo plano de la aplicación en Configuración > General > Actualización en segundo plano para usar la copia de seguridad en segundo plano.", - "backup_controller_page_background_app_refresh_disabled_title": "Actualización en segundo plano desactivada", - "backup_controller_page_background_app_refresh_enable_button_text": "Ir a configuración", - "backup_controller_page_background_battery_info_link": "Muestrame cómo", - "backup_controller_page_background_battery_info_message": "Para obtener la mejor experiencia de copia de seguridad en segundo plano, desactiva cualquier optimización de batería que restrinja la actividad en segundo plano para Immich.\n\nDado que esto es específico en cada dispositivo, busca la información necesaria de el fabricante de tu dispositivo.", - "backup_controller_page_background_battery_info_ok": "Ok", - "backup_controller_page_background_battery_info_title": "Optimizaciones de batería", - "backup_controller_page_background_charging": "Solo mientras se carga", - "backup_controller_page_background_configure_error": "Error al configurar el servicio en segundo plano", - "backup_controller_page_background_delay": "Retrasar la copia de seguridad de los nuevos elementos: {duration}", - "backup_controller_page_background_description": "Activa el servicio en segundo plano para copiar automáticamente cualquier nuevos elementos sin necesidad de abrir la aplicación", - "backup_controller_page_background_is_off": "La copia de seguridad en segundo plano automática está desactivada", - "backup_controller_page_background_is_on": "La copia de seguridad en segundo plano automática está activada", - "backup_controller_page_background_turn_off": "Desactivar el servicio en segundo plano", - "backup_controller_page_background_turn_on": "Activar el servicio en segundo plano", - "backup_controller_page_background_wifi": "Solo en Wi-Fi", - "backup_controller_page_backup": "Copia de Seguridad", - "backup_controller_page_backup_selected": "Seleccionado: ", - "backup_controller_page_backup_sub": "Fotos y videos respaldados", - "backup_controller_page_created": "Creado el: {date}", - "backup_controller_page_desc_backup": "Active la copia de seguridad para subir automáticamente los nuevos elementos al servidor cuando se abre la aplicación.", - "backup_controller_page_excluded": "Excluido: ", - "backup_controller_page_failed": "Fallidos ({count})", - "backup_controller_page_filename": "Nombre del archivo: {filename} [{size}]", - "backup_controller_page_id": "Id.: {id}", - "backup_controller_page_info": "Información de la Copia de Seguridad", - "backup_controller_page_none_selected": "Ninguno seleccionado", - "backup_controller_page_remainder": "Restante", - "backup_controller_page_remainder_sub": "Fotos y videos restantes para hacer una copia de seguridad de la selección", - "backup_controller_page_server_storage": "Almacenamiento en el servidor", - "backup_controller_page_start_backup": "Iniciar copia de seguridad", - "backup_controller_page_status_off": "La copia de seguridad está desactivada", - "backup_controller_page_status_on": "La copia de seguridad está activada", - "backup_controller_page_storage_format": "{used} de {total} usadas", - "backup_controller_page_to_backup": "Álbumes a respaldar", - "backup_controller_page_total_sub": "Todas las fotos y vídeos únicos de los álbumes seleccionados", - "backup_controller_page_turn_off": "Apagar la copia de seguridad", - "backup_controller_page_turn_on": "Activar la copia de seguridad", - "backup_controller_page_uploading_file_info": "Subiendo información del archivo", - "backup_err_only_album": "No se puede eliminar el único álbum", - "backup_error_sync_failed": "La sincronización falló. No es posible procesar la copia de seguridad.", - "backup_info_card_assets": "elementos", - "backup_manual_cancelled": "Cancelado", - "backup_manual_in_progress": "Subida ya en progreso. Vuelve a intentarlo más tarde", - "backup_manual_success": "Éxito", - "backup_manual_title": "Estado de la subida", - "backup_options": "Opciones de copia de seguridad", - "backup_options_page_title": "Opciones de Copia de Seguridad", - "backup_setting_subtitle": "Administra las configuraciones de respaldo en segundo y primer plano", - "backup_settings_subtitle": "Configura las opciones de subida", - "backup_upload_details_page_more_details": "Toca para más detalles", - "backward": "Retroceder", - "biometric_auth_enabled": "Autentificación biométrica habilitada", - "biometric_locked_out": "Estás bloqueado de la autentificación biométrica", - "biometric_no_options": "Sin opciones biométricas disponibles", - "biometric_not_available": "Autentificación biométrica no disponible en este dispositivo", - "birthdate_saved": "Fecha de nacimiento guardada con éxito", - "birthdate_set_description": "La fecha de nacimiento se utiliza para calcular la edad de esta persona en el momento de la fotografía.", - "blurred_background": "Fondo borroso", - "bugs_and_feature_requests": "Errores y solicitudes de funciones", - "build": "Compilación", - "build_image": "Imagen de compilación", - "bulk_delete_duplicates_confirmation": "¿Estás seguro de que deseas eliminar de forma masiva {count, plural, one {# elemento duplicado} other {# elementos duplicados}}? Esto mantendrá el activo más grande de cada grupo y eliminará permanentemente todos los demás duplicados. ¡Esta acción no se puede deshacer!", - "bulk_keep_duplicates_confirmation": "¿Estas seguro de que desea mantener {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto resolverá todos los grupos duplicados sin borrar nada.", - "bulk_trash_duplicates_confirmation": "¿Estas seguro de que desea eliminar masivamente {count, plural, one {# duplicate asset} other {# duplicate assets}} archivos duplicados? Esto mantendrá el archivo más grande de cada grupo y eliminará todos los demás duplicados.", - "buy": "Comprar Immich", - "cache_settings_clear_cache_button": "Borrar caché", - "cache_settings_clear_cache_button_title": "Borra la caché de la aplicación. Esto afectará significativamente el rendimiento de la aplicación hasta que se reconstruya la caché.", - "cache_settings_duplicated_assets_clear_button": "LIMPIAR", - "cache_settings_duplicated_assets_subtitle": "Fotos y vídeos ignorados por la aplicación", - "cache_settings_duplicated_assets_title": "Elementos duplicados ({count})", - "cache_settings_statistics_album": "Miniaturas de la biblioteca", - "cache_settings_statistics_full": "Imágenes completas", - "cache_settings_statistics_shared": "Miniaturas de álbumes compartidos", - "cache_settings_statistics_thumbnail": "Miniaturas", - "cache_settings_statistics_title": "Uso de caché", - "cache_settings_subtitle": "Controla el comportamiento del almacenamiento en caché de la aplicación móvil Immich", - "cache_settings_tile_subtitle": "Controla el comportamiento del almacenamiento local", - "cache_settings_tile_title": "Almacenamiento local", - "cache_settings_title": "Configuración de la caché", - "camera": "Cámara", - "camera_brand": "Fabricante de cámara", - "camera_model": "Modelo de cámara", - "cancel": "Cancelar", - "cancel_search": "Cancelar búsqueda", - "canceled": "Cancelado", - "canceling": "Cancelando", - "cannot_merge_people": "No se pueden fusionar personas", - "cannot_undo_this_action": "¡No puedes deshacer esta acción!", - "cannot_update_the_description": "No se puede actualizar la descripción", - "cast": "Transmitir", - "cast_description": "Configura los posibles destinos de retransmisión", - "change_date": "Cambiar fecha", - "change_description": "Cambiar descripción", - "change_display_order": "Cambiar orden de visualización", - "change_expiration_time": "Cambiar fecha de caducidad", - "change_location": "Cambiar ubicación", - "change_name": "Cambiar nombre", - "change_name_successfully": "Nombre cambiado exitosamente", - "change_password": "Cambiar contraseña", - "change_password_description": "Esta es la primera vez que inicia sesión en el sistema o se ha realizado una solicitud para cambiar su contraseña. Por favor ingrese la nueva contraseña a continuación.", - "change_password_form_confirm_password": "Confirmar contraseña", - "change_password_form_description": "Hola {name},\n\nEsta es la primera vez que inicias sesión en el sistema o se ha solicitado cambiar tu contraseña. Por favor, introduce la nueva contraseña a continuación.", - "change_password_form_log_out": "Cerrar sesión los demás dispositivos", - "change_password_form_log_out_description": "Se recomienda cerrar sesión en todos los demás dispositivos", - "change_password_form_new_password": "Nueva contraseña", - "change_password_form_password_mismatch": "Las contraseñas no coinciden", - "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseña", - "change_pin_code": "Cambiar PIN", - "change_trigger": "Cambiar disparador", - "change_trigger_prompt": "¿Seguro que quieres cambiar el disparador? Esto eliminará todas las acciones y filtros existentes.", - "change_your_password": "Cambia tu contraseña", - "changed_visibility_successfully": "Visibilidad cambiada correctamente", - "charging": "Cargando", - "charging_requirement_mobile_backup": "La copia de seguridad en segundo plano requiere que el dispositivo se esté cargando", - "check_corrupt_asset_backup": "Comprobar copias de seguridad de archivos corruptos", - "check_corrupt_asset_backup_button": "Realizar comprobación", - "check_corrupt_asset_backup_description": "Ejecutar esta comprobación solo por Wi-Fi y una vez que todos los archivos hayan sido respaldados. El procedimiento puede tardar unos minutos.", - "check_logs": "Comprobar Registros", - "checksum": "Suma de comprobación", - "choose_matching_people_to_merge": "Elija ocurrencias duplicadas de la misma persona para fusionar", - "city": "Ciudad", - "cleanup_confirm_description": "Immich encontró {count} recursos (creados antes de {date}) respaldados de manera segura en el servidor. ¿Desea eliminar las copias locales de este dispositivo?", - "cleanup_confirm_prompt_title": "¿Remover de este dispositivo?", - "cleanup_deleted_assets": "Moviendo {count} elementos del dispositivo a la papelera", - "cleanup_deleting": "Moviendo a la papelera...", - "cleanup_found_assets": "Se han encontrado {count} archivos respaldados", - "cleanup_found_assets_with_size": "Se encontraron {count} activos respaldados ({size})", - "cleanup_icloud_shared_albums_excluded": "Los álbumes compartidos de iCloud están excluidos del escaneo", - "cleanup_no_assets_found": "No se encontraron activos que coincidan con los criterios anteriores. Liberar espacio solo puede eliminar activos respaldados en el servidor", - "cleanup_preview_title": "{count} archivos a remover", - "cleanup_step3_description": "Busque activos respaldados que coincidan con su fecha y conserve la configuración.", - "cleanup_step4_summary": "{count} recursos (creados antes del {date}) para eliminar de tu dispositivo local. Las fotos seguirán accesibles desde la app de Immich.", - "cleanup_trash_hint": "Para completar la liberación de espacio, abra la aplicación de fotos y vacíe la papelera", - "clear": "Limpiar", - "clear_all": "Limpiar todo", - "clear_all_recent_searches": "Borrar búsquedas recientes", - "clear_file_cache": "Limpiar la caché de archivos", - "clear_message": "Limpiar mensaje", - "clear_value": "Limpiar valor", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Introduzca contraseña", - "client_cert_import": "Importar", - "client_cert_import_success_msg": "El certificado de cliente está importado", - "client_cert_invalid_msg": "Archivo de certificado no válido o contraseña incorrecta", - "client_cert_remove_msg": "El certificado de cliente se ha eliminado", - "client_cert_subtitle": "Solo se admite el formato PKCS12 (.p12, .pfx). La importación/eliminación de certificados solo está disponible antes de iniciar sesión", - "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", - "clockwise": "En el sentido de las agujas del reloj", - "close": "Cerrar", - "collapse": "Agrupar", - "collapse_all": "Desplegar todo", - "color": "Color", - "color_theme": "Color del tema", - "command": "Comando", - "comment_deleted": "Comentario borrado", - "comment_options": "Opciones de comentarios", - "comments_and_likes": "Comentarios y me gusta", - "comments_are_disabled": "Los comentarios están deshabilitados", - "common_create_new_album": "Crear nuevo álbum", - "completed": "Completado", - "confirm": "Confirmar", - "confirm_admin_password": "Confirmar contraseña del administrador", - "confirm_delete_face": "¿Estás seguro que deseas eliminar la cara de {name} del archivo?", - "confirm_delete_shared_link": "¿Estás seguro de que deseas eliminar este enlace compartido?", - "confirm_keep_this_delete_others": "Todos los demás activos de la pila se eliminarán excepto este activo. ¿Está seguro de que quiere continuar?", - "confirm_new_pin_code": "Confirmar nuevo PIN", - "confirm_password": "Confirmar contraseña", - "confirm_tag_face": "¿Quieres etiquetar esta cara como {name}?", - "confirm_tag_face_unnamed": "¿Quieres etiquetar esta cara?", - "connected_device": "Dispositivo conectado", - "connected_to": "Conectado a", - "contain": "Incluido", - "context": "Contexto", - "continue": "Continuar", - "control_bottom_app_bar_create_new_album": "Crear nuevo álbum", - "control_bottom_app_bar_delete_from_immich": "Borrar de Immich", - "control_bottom_app_bar_delete_from_local": "Borrar del dispositivo", - "control_bottom_app_bar_edit_location": "Editar ubicación", - "control_bottom_app_bar_edit_time": "Editar fecha y hora", - "control_bottom_app_bar_share_link": "Enlace para compartir", - "control_bottom_app_bar_share_to": "Compartir a", - "control_bottom_app_bar_trash_from_immich": "Mover a la papelera", - "copied_image_to_clipboard": "Imagen copiada al portapapeles.", - "copied_to_clipboard": "¡Copiado al portapapeles!", - "copy_error": "Error de copiado", - "copy_file_path": "Copiar ruta del archivo", - "copy_image": "Copiar Imagen", - "copy_link": "Copiar enlace", - "copy_link_to_clipboard": "Copiar enlace al portapapeles", - "copy_password": "Copiar contraseña", - "copy_to_clipboard": "Copiar al Portapapeles", - "country": "País", - "cover": "Portada", - "covers": "Portadas", - "create": "Crear", - "create_album": "Crear álbum", - "create_album_page_untitled": "Sin título", - "create_api_key": "Crear clave API", - "create_first_workflow": "Crear el primer flujo de trabajo", - "create_library": "Crear biblioteca", - "create_link": "Crear enlace", - "create_link_to_share": "Crear enlace compartido", - "create_link_to_share_description": "Permitir que cualquier persona con el enlace vea la(s) foto(s) seleccionada(s)", - "create_new": "Crear nuevo", - "create_new_person": "Crear nueva persona", - "create_new_person_hint": "Asignar los archivos seleccionados a una nueva persona", - "create_new_user": "Crear nuevo usuario", - "create_shared_album_page_share_add_assets": "AÑADIR ELEMENTOS", - "create_shared_album_page_share_select_photos": "Seleccionar fotos", - "create_shared_link": "Crear un enlace compartido", - "create_tag": "Crear etiqueta", - "create_tag_description": "Crear una nueva etiqueta. Para las etiquetas anidadas, ingresa la ruta completa de la etiqueta, incluidas las barras diagonales.", - "create_user": "Crear usuario", - "create_workflow": "Crear flujo de trabajo", - "created": "Creado", - "created_at": "Creado", - "creating_linked_albums": "Creando álbumes vinculados...", - "crop": "Recortar", - "crop_aspect_ratio_fixed": "Fijado", - "crop_aspect_ratio_free": "Libre", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Objetos", - "current_device": "Dispositivo actual", - "current_pin_code": "PIN actual", - "current_server_address": "Dirección actual del servidor", - "custom_date": "Fecha personalizada", - "custom_locale": "Configuración regional personalizada", - "custom_locale_description": "Formatear fechas y números según el idioma y la región", - "custom_url": "URL personalizada", - "cutoff_date_description": "Conserva fotos del último…", - "cutoff_day": "{count, plural, one {día} other {días}}", - "cutoff_year": "{count, plural, one {año} other {años}}", - "daily_title_text_date": "E dd, MMM", - "daily_title_text_date_year": "E dd de MMM, yyyy", - "dark": "Oscuro", - "dark_theme": "Alternar tema oscuro", - "date": "Fecha", - "date_after": "Fecha posterior", - "date_and_time": "Fecha y Hora", - "date_before": "Fecha anterior", - "date_format": "E d, LLL y • h:mm a", - "date_of_birth_saved": "Guardada con éxito la fecha de nacimiento", - "date_range": "Rango de fechas", - "day": "Día", - "days": "Días", - "deduplicate_all": "Deduplicar todo", - "deduplication_criteria_1": "Tamaño de imagen en bytes", - "deduplication_criteria_2": "Conteo de datos EXIF", - "deduplication_info": "Información de Deduplicación", - "deduplication_info_description": "Para automáticamente preseleccionar recursos y eliminar duplicados en conjunto, nosotros consideramos lo siguiente:", - "default_locale": "Configuración regional predeterminada", - "default_locale_description": "Formatee fechas y números según la configuración regional de su navegador", - "delete": "Eliminar", - "delete_action_confirmation_message": "¿Está seguro que desea eliminar este archivo? Esta acción lo moverá a la papelera del servidor y le preguntará si desea eliminarlo localmente", - "delete_action_prompt": "{count} eliminados", - "delete_album": "Eliminar álbum", - "delete_api_key_prompt": "¿Está seguro de que desea eliminar esta clave API?", - "delete_dialog_alert": "Estos elementos serán eliminados permanentemente de Immich y de tu dispositivo", - "delete_dialog_alert_local": "Estos elementos se eliminarán permanente de tu dispositivo pero seguirán disponibles en el servidor de Immich", - "delete_dialog_alert_local_non_backed_up": "Algunos de los elementos no tienen copia de seguridad en Immich y serán borrados permanentemente de tu dispositivo", - "delete_dialog_alert_remote": "Estas imágenes van a ser borradas permanentemente del servidor de Immich", - "delete_dialog_ok_force": "Borrar de todos modos", - "delete_dialog_title": "Eliminar Permanentemente", - "delete_duplicates_confirmation": "¿Está seguro de que desea eliminar permanentemente estos duplicados?", - "delete_face": "Eliminar cara", - "delete_key": "Eliminar clave", - "delete_library": "Eliminar biblioteca", - "delete_link": "Eliminar enlace", - "delete_local_action_prompt": "{count} eliminados localmente", - "delete_local_dialog_ok_backed_up_only": "Borrar solo las que tengan copia de seguridad", - "delete_local_dialog_ok_force": "Borrar de todos modos", - "delete_others": "Eliminar otros", - "delete_permanently": "Eliminar permanentemente", - "delete_permanently_action_prompt": "{count} eliminados permanentemente", - "delete_shared_link": "Eliminar enlace compartido", - "delete_shared_link_dialog_title": "Eliminar enlace compartido", - "delete_tag": "Eliminar etiqueta", - "delete_tag_confirmation_prompt": "¿Estás seguro de que deseas eliminar la etiqueta {tagName} ?", - "delete_user": "Eliminar usuario", - "deleted_shared_link": "Enlace compartido eliminado", - "deletes_missing_assets": "Elimina archivos que faltan en el disco duro", - "description": "Descripción", - "description_input_hint_text": "Añadir descripción...", - "description_input_submit_error": "Error al actualizar la descripción, comprueba el registro para obtener más detalles", - "deselect_all": "Deseleccionar Todo", - "details": "Detalles", - "direction": "Dirección", - "disable": "Desactivar", - "disabled": "Deshabilitado", - "disallow_edits": "Bloquear edición", - "discord": "Discord", - "discover": "Descubrir", - "discovered_devices": "Dispositivos descubiertos", - "dismiss_all_errors": "Descartar todos los errores", - "dismiss_error": "Descartar error", - "display_options": "Opciones de pantalla", - "display_order": "Orden de visualización", - "display_original_photos": "Mostrar fotos originales", - "display_original_photos_setting_description": "Preferir mostrar la foto original al ver un archivo en lugar de miniaturas cuando el archivo original es compatible con la web. Esto puede resultar en velocidades de visualización de fotografías más lentas.", - "do_not_show_again": "No volver a mostrar este mensaje otra vez", - "documentation": "Documentación", - "done": "Hecho", - "download": "Descargar", - "download_action_prompt": "Descargando {count} archivos", - "download_canceled": "Descarga cancelada", - "download_complete": "Descarga completada", - "download_enqueue": "Descarga en cola", - "download_error": "Error al descargar", - "download_failed": "Descarga fallida", - "download_finished": "Descarga completada", - "download_include_embedded_motion_videos": "Vídeos incrustados", - "download_include_embedded_motion_videos_description": "Incluir vídeos incrustados en fotografías en movimiento como un archivo separado", - "download_notfound": "Descarga no encontrada", - "download_original": "Descargar original", - "download_paused": "Descarga en pausa", - "download_settings": "Descargar", - "download_settings_description": "Administrar configuraciones relacionadas con la descarga de archivos", - "download_started": "Descarga iniciada", - "download_sucess": "Descarga Exitosa", - "download_sucess_android": "Los archivos se han descargado en DCIM/Immich", - "download_waiting_to_retry": "Esperando para reintentar", - "downloading": "Descargando", - "downloading_asset_filename": "Descargando archivo {filename}", - "downloading_from_icloud": "Descargando desde iCloud", - "downloading_media": "Descargando medios", - "drop_files_to_upload": "Suelta los archivos en cualquier lugar para subirlos", - "duplicates": "Duplicados", - "duplicates_description": "Resuelva cada grupo indicando, en cada caso, cuales están duplicados", - "duration": "Duración", - "edit": "Editar", - "edit_album": "Editar album", - "edit_avatar": "Editar avatar", - "edit_birthday": "Editar cumpleaños", - "edit_date": "Editar fecha", - "edit_date_and_time": "Editar fecha y hora", - "edit_date_and_time_action_prompt": "{count} fecha y hora editadas", - "edit_date_and_time_by_offset": "Cambiar fecha usando una desviación", - "edit_date_and_time_by_offset_interval": "Nuevo intervalo de fechas: {from} - {to}", - "edit_description": "Editar descripción", - "edit_description_prompt": "Por favor selecciona una nueva descripción:", - "edit_exclusion_pattern": "Editar patrón de exclusión", - "edit_faces": "Editar rostros", - "edit_key": "Editar clave", - "edit_link": "Editar enlace", - "edit_location": "Editar ubicación", - "edit_location_action_prompt": "{count} ubicaciones actualizadas", - "edit_location_dialog_title": "Ubicación", - "edit_name": "Cambiar nombre", - "edit_people": "Editar persona", - "edit_tag": "Editar etiqueta", - "edit_title": "Editar Titulo", - "edit_user": "Editar usuario", - "edit_workflow": "Editar flujo de trabajo", - "editor": "Editor", - "editor_close_without_save_prompt": "No se guardarán los cambios", - "editor_close_without_save_title": "¿Cerrar el editor?", - "editor_confirm_reset_all_changes": "¿Seguro que quieres restablecer los cambios?", - "editor_flip_horizontal": "Girar horizontalmente", - "editor_flip_vertical": "Girar verticalmente", - "editor_orientation": "Orientación", - "editor_reset_all_changes": "Restablecer cambios", - "editor_rotate_left": "Rotar 90º sentido antihorario", - "editor_rotate_right": "Rotar 90º sentido horario", - "email": "Correo electrónico", - "email_notifications": "Notificaciones por correo electrónico", - "empty_folder": "Esta carpeta está vacía", - "empty_trash": "Vaciar papelera", - "empty_trash_confirmation": "¿Estás seguro de que quieres vaciar la papelera? Esto eliminará permanentemente todos los archivos de la basura de Immich.\n¡No podrás deshacer esta acción!", - "enable": "Habilitar", - "enable_backup": "Habilitar Copia de Seguridad", - "enable_biometric_auth_description": "Introduce tu código PIN para habilitar la autentificación biométrica", - "enabled": "Habilitado", - "end_date": "Fecha final", - "enqueued": "Agregado a la cola", - "enter_wifi_name": "Introduce el nombre Wi-Fi", - "enter_your_pin_code": "Introduce tu código PIN", - "enter_your_pin_code_subtitle": "Introduce tu código PIN para acceder a la carpeta protegida", - "error": "Error", - "error_change_sort_album": "No se pudo cambiar el orden de visualización del álbum", - "error_delete_face": "Error al eliminar la cara del archivo", - "error_getting_places": "Error obteniendo lugares", - "error_loading_albums": "Error al cargar álbumes", - "error_loading_image": "Error al cargar la imagen", - "error_loading_partners": "Error al cargar miembros: {error}", - "error_retrieving_asset_information": "Error al recuperar la información del activo", - "error_saving_image": "Error: {error}", - "error_tag_face_bounding_box": "Error al etiquetar la cara: no se pueden obtener las coordenadas del marco", - "error_title": "Error: algo salió mal", - "error_while_navigating": "Error al navegar al activo", - "errors": { - "cannot_navigate_next_asset": "No puedes navegar al siguiente archivo", - "cannot_navigate_previous_asset": "No puedes navegar al archivo anterior", - "cant_apply_changes": "No se pueden aplicar los cambios", - "cant_change_activity": "No se puede realizar la actividad {enabled, select, true {disable} other {enable}}", - "cant_change_asset_favorite": "No se puede cambiar favorito para este archivo", - "cant_change_metadata_assets_count": "No se pueden cambiar los metadatos de {count, plural, one {# elemento} other {# elementos}}", - "cant_get_faces": "No se encuentran caras", - "cant_get_number_of_comments": "No se puede obtener la cantidad de comentarios", - "cant_search_people": "No se puede buscar a personas", - "cant_search_places": "No se pueden buscar lugares", - "error_adding_assets_to_album": "Error al añadir los elementos al álbum", - "error_adding_users_to_album": "Error al añadir los usuarios al álbum", - "error_deleting_shared_user": "Error al eliminar usuario compartido", - "error_downloading": "Error al descargar {filename}", - "error_hiding_buy_button": "Error al ocultar el botón de compra", - "error_removing_assets_from_album": "Error al eliminar archivos del álbum; consulte la consola para obtener más detalles", - "error_selecting_all_assets": "Error al seleccionar todos los archivos", - "exclusion_pattern_already_exists": "Este patrón de exclusión ya existe.", - "failed_to_create_album": "Error al crear el álbum", - "failed_to_create_shared_link": "Error al crear el enlace compartido", - "failed_to_edit_shared_link": "Error al editar el enlace compartido", - "failed_to_get_people": "No se logró conseguir gente", - "failed_to_keep_this_delete_others": "No se pudo conservar este activo y eliminar los demás", - "failed_to_load_asset": "Error al cargar el elemento", - "failed_to_load_assets": "Error al cargar los elementos", - "failed_to_load_notifications": "Error al cargar las notificaciones", - "failed_to_load_people": "Error al cargar a los usuarios", - "failed_to_remove_product_key": "No se pudo eliminar la clave del producto", - "failed_to_reset_pin_code": "No se pudo restablecer el código PIN", - "failed_to_stack_assets": "No se pudieron agrupar los archivos", - "failed_to_unstack_assets": "Error al desagrupar los archivos", - "failed_to_update_notification_status": "Error al actualizar el estado de la notificación", - "incorrect_email_or_password": "Contraseña o email incorrecto", - "library_folder_already_exists": "Esta ruta de importación ya existe.", - "paths_validation_failed": "Falló la validación en {paths, plural, one {# carpeta} other {# carpetas}}", - "profile_picture_transparent_pixels": "Las imágenes de perfil no pueden tener píxeles transparentes. Por favor amplíe y/o mueva la imagen.", - "quota_higher_than_disk_size": "Se ha establecido una cuota superior al tamaño del disco", - "something_went_wrong": "Algo salió mal", - "unable_to_add_album_users": "No se pueden añadir usuarios al álbum", - "unable_to_add_assets_to_shared_link": "No se pueden añadir archivos al enlace compartido", - "unable_to_add_comment": "No se puede añadir comentario", - "unable_to_add_exclusion_pattern": "No se puede añadir el patrón de exclusión", - "unable_to_add_partners": "No se pueden añadir miembros", - "unable_to_add_remove_archive": "No se puede archivar {archived, select, true {remove asset from} other {add asset to}}", - "unable_to_add_remove_favorites": "No se pudo {favorite, select, true {añadir el elemento a} other {eliminar el elemento de}} los favoritos", - "unable_to_archive_unarchive": "No se pudo {archived, select, true {agregar el elemento al} other {quitar el elemento del}} archivo", - "unable_to_change_album_user_role": "No se puede cambiar la función del usuario del álbum", - "unable_to_change_date": "No se puede cambiar la fecha", - "unable_to_change_description": "Imposible cambiar la descripción", - "unable_to_change_favorite": "Imposible cambiar el archivo favorito", - "unable_to_change_location": "No se puede cambiar de ubicación", - "unable_to_change_password": "No se puede cambiar la contraseña", - "unable_to_change_visibility": "No se puede cambiar la visibilidad de {count, plural, one {# persona} other {# personas}}", - "unable_to_complete_oauth_login": "No se puede completar el inicio de sesión de OAuth", - "unable_to_connect": "No puede conectarse", - "unable_to_copy_to_clipboard": "No se puede copiar al portapapeles, asegúrese de acceder a la página a través de https", - "unable_to_create": "No se puede crear el flujo de trabajo", - "unable_to_create_admin_account": "No se puede crear una cuenta de administrador", - "unable_to_create_api_key": "No se puede crear una nueva clave API", - "unable_to_create_library": "No se puede crear la biblioteca", - "unable_to_create_user": "No se puede crear usuario", - "unable_to_delete_album": "No se puede eliminar el álbum", - "unable_to_delete_asset": "No se puede eliminar el archivo", - "unable_to_delete_assets": "Error al eliminar archivos", - "unable_to_delete_exclusion_pattern": "No se puede eliminar el patrón de exclusión", - "unable_to_delete_shared_link": "No se puede eliminar el enlace compartido", - "unable_to_delete_user": "No se puede eliminar el usuario", - "unable_to_delete_workflow": "No se puede eliminar el flujo de trabajo", - "unable_to_download_files": "No se pueden descargar archivos", - "unable_to_edit_exclusion_pattern": "No se puede editar el patrón de exclusión", - "unable_to_empty_trash": "No se puede vaciar la papelera", - "unable_to_enter_fullscreen": "No se puede acceder al modo pantalla completa", - "unable_to_exit_fullscreen": "No se puede salir del modo pantalla completa", - "unable_to_get_comments_number": "No se puede obtener el número de comentarios", - "unable_to_get_shared_link": "Error al obtener el enlace compartido", - "unable_to_hide_person": "No se puede ocultar a la persona", - "unable_to_link_motion_video": "No se puede enlazar el vídeo en movimiento", - "unable_to_link_oauth_account": "No se puede vincular la cuenta OAuth", - "unable_to_log_out_all_devices": "No se pueden cerrar las sesiones en todos los dispositivos", - "unable_to_log_out_device": "No se puede cerrar la sesión en el dispositivo", - "unable_to_login_with_oauth": "No se puede iniciar sesión con OAuth", - "unable_to_play_video": "No se puede reproducir el vídeo", - "unable_to_reassign_assets_existing_person": "No se pueden reasignar a {name, select, null {an existing person} other {{name}}}", - "unable_to_reassign_assets_new_person": "No se pueden reasignar archivos a una nueva persona", - "unable_to_refresh_user": "No se puede actualizar el usuario", - "unable_to_remove_album_users": "No se pueden eliminar usuarios del álbum", - "unable_to_remove_api_key": "No se puede eliminar la clave API", - "unable_to_remove_assets_from_shared_link": "No se pueden eliminar archivos desde el enlace compartido", - "unable_to_remove_library": "No se puede eliminar la biblioteca", - "unable_to_remove_partner": "No se puede eliminar el invitado", - "unable_to_remove_reaction": "No se puede eliminar la reacción", - "unable_to_reset_password": "No se puede restablecer la contraseña", - "unable_to_reset_pin_code": "No se ha podido restablecer el PIN", - "unable_to_resolve_duplicate": "No se resolver duplicado", - "unable_to_restore_assets": "No se pueden restaurar los archivos", - "unable_to_restore_trash": "No se puede restaurar la papelera", - "unable_to_restore_user": "No se puede restaurar el usuario", - "unable_to_save_album": "No se puede guardar el álbum", - "unable_to_save_api_key": "No se puede guardar la clave API", - "unable_to_save_date_of_birth": "Error al guardar la fecha de nacimiento", - "unable_to_save_name": "No se puede guardar el nombre", - "unable_to_save_profile": "No se puede guardar el perfil", - "unable_to_save_settings": "No se pueden guardar las configuraciones", - "unable_to_scan_libraries": "No se pueden escanear bibliotecas", - "unable_to_scan_library": "No se puede escanear la biblioteca", - "unable_to_set_feature_photo": "No se puede configurar la foto seleccionada", - "unable_to_set_profile_picture": "No se puede configurar la imagen de perfil", - "unable_to_set_rating": "No se ha podido establecer la calificación", - "unable_to_submit_job": "No se puede enviar el trabajo", - "unable_to_trash_asset": "No se puede eliminar el archivo", - "unable_to_unlink_account": "No se puede desvincular la cuenta", - "unable_to_unlink_motion_video": "No se puede desvincular el vídeo en movimiento", - "unable_to_update_album_cover": "No se puede actualizar la portada del álbum", - "unable_to_update_album_info": "No se puede actualizar la información del álbum", - "unable_to_update_library": "No se puede actualizar la biblioteca", - "unable_to_update_location": "No se puede actualizar la ubicación", - "unable_to_update_settings": "No se puede actualizar la configuración", - "unable_to_update_timeline_display_status": "No se puede actualizar el estado de visualización de la línea de tiempo", - "unable_to_update_user": "No se puede actualizar el usuario", - "unable_to_update_workflow": "No se puede actualizar el flujo de trabajo", - "unable_to_upload_file": "Error al subir el archivo" - }, - "errors_text": "Errores", - "exclusion_pattern": "Patrón de exclusión", - "exif": "EXIF", - "exif_bottom_sheet_description": "Añadir descripción…", - "exif_bottom_sheet_description_error": "Error al actualizar la descripción", - "exif_bottom_sheet_details": "DETALLES", - "exif_bottom_sheet_location": "UBICACIÓN", - "exif_bottom_sheet_no_description": "Sin descripción", - "exif_bottom_sheet_people": "PERSONAS", - "exif_bottom_sheet_person_add_person": "Añadir nombre", - "exit_slideshow": "Salir de la presentación", - "expand_all": "Expandir todo", - "experimental_settings_new_asset_list_subtitle": "Trabajo en progreso", - "experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental", - "experimental_settings_subtitle": "¡Úsalo bajo tu propia responsabilidad!", - "experimental_settings_title": "Experimental", - "expire_after": "Caducar después de", - "expired": "Caducado", - "expires_date": "Caduca el {date}", - "explore": "Explorar", - "explorer": "Explorador", - "export": "Exportar", - "export_as_json": "Exportar a JSON", - "export_database": "Exportar base de datos", - "export_database_description": "Exportar la base de datos SQLite", - "extension": "Extensión", - "external": "Externo", - "external_libraries": "Bibliotecas externas", - "external_network": "Red externa", - "external_network_sheet_info": "Cuando no tengas conexión con tu red Wi-Fi preferida, la aplicación se conectará al servidor utilizando la primera de las URL siguientes a la que pueda acceder, empezando de arriba hacia abajo", - "face_unassigned": "Sin asignar", - "failed": "Fallido", - "failed_count": "Fallido: {count}", - "failed_to_authenticate": "Fallo al autentificar", - "failed_to_load_assets": "Error al cargar los activos", - "failed_to_load_folder": "No se pudo cargar la carpeta", - "favorite": "Favorito", - "favorite_action_prompt": "{count} añadido(s) a Favoritos", - "favorite_or_unfavorite_photo": "Foto favorita o no favorita", - "favorites": "Favoritos", - "favorites_page_no_favorites": "No se encontraron elementos marcados como favoritos", - "feature_photo_updated": "Foto destacada actualizada", - "features": "Características", - "features_in_development": "Funciones en Desarrollo", - "features_setting_description": "Administrar las funciones de la aplicación", - "file_name_or_extension": "Nombre del archivo o extensión", - "file_size": "Tamaño del archivo", - "filename": "Nombre del archivo", - "filetype": "Tipo de archivo", - "filter": "Filtro", - "filter_description": "Condiciones para filtrar los activos objetivo", - "filter_people": "Filtrar personas", - "filter_places": "Filtrar lugares", - "filters": "Filtros", - "find_them_fast": "Encuéntrelos rápidamente por nombre con la búsqueda", - "first": "Primero", - "fix_incorrect_match": "Corregir coincidencia incorrecta", - "folder": "Carpeta", - "folder_not_found": "Carpeta no encontrada", - "folders": "Carpetas", - "folders_feature_description": "Explorar la vista de carpetas para las fotos y los videos en el sistema de archivos", - "forgot_pin_code_question": "¿Olvidaste tu código PIN?", - "forward": "Avanzar", - "free_up_space": "Liberar espacio", - "free_up_space_description": "Elimina tus fotos y videos de tu dispositivo para liberar espacio. Los respaldos en el servidor se mantendrán seguros.", - "free_up_space_settings_subtitle": "Liberar espacio del dispositivo", - "full_path": "Ruta completa: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Esta funcionalidad carga recursos externos desde Google para poder funcionar.", - "general": "General", - "geolocation_instruction_location": "Da click en un asset con coordenadas GPS para usar su ubicacion, o selecciona una ubicacion directamente en el mapa", - "get_help": "Solicitar ayuda", - "get_people_error": "Error al obtener gente", - "get_wifiname_error": "No se pudo obtener el nombre de la red Wi-Fi. Asegúrate de haber concedido los permisos necesarios y de estar conectado a una red Wi-Fi", - "getting_started": "Comenzamos", - "go_back": "Volver atrás", - "go_to_folder": "Ir al directorio", - "go_to_search": "Ir a búsqueda", - "gps": "GPS", - "gps_missing": "Sin GPS", - "grant_permission": "Conceder permiso", - "group_albums_by": "Agrupar álbumes por...", - "group_country": "Agrupar por país", - "group_no": "Sin agrupación", - "group_owner": "Agrupar por propietario", - "group_places_by": "Agrupar lugares por...", - "group_year": "Agrupar por año", - "haptic_feedback_switch": "Activar respuesta háptica", - "haptic_feedback_title": "Respuesta háptica", - "has_quota": "Cuota asignada", - "hash_asset": "Generar hash del archivo", - "hashed_assets": "Archivos con hash generado", - "hashing": "Generando hash", - "header_settings_add_header_tip": "Añadir cabecera", - "header_settings_field_validator_msg": "El valor no puede estar vacío", - "header_settings_header_name_input": "Nombre de la cabecera", - "header_settings_header_value_input": "Valor de la cabecera", - "headers_settings_tile_title": "Cabeceras de proxy personalizadas", - "height": "Altura", - "hi_user": "Hola {name} ({email})", - "hide_all_people": "Ocultar a todas las personas", - "hide_gallery": "Ocultar galería", - "hide_named_person": "Ocultar persona {name}", - "hide_password": "Ocultar contraseña", - "hide_person": "Ocultar persona", - "hide_schema": "Ocultar esquema", - "hide_text_recognition": "Ocultar reconocimiento de texto", - "hide_unnamed_people": "Ocultar personas anónimas", - "home_page_add_to_album_conflicts": "{added} elementos añadidos al álbum {album}.{failed} elementos ya existen en el álbum.", - "home_page_add_to_album_err_local": "Aún no se pueden añadir elementos locales a álbumes, omitiendo", - "home_page_add_to_album_success": "Se añadieron {added} elementos al álbum {album}.", - "home_page_album_err_partner": "Aún no se pueden añadir elementos de un compañero a un álbum , omitiendo", - "home_page_archive_err_local": "Los elementos locales no pueden ser archivados, omitiendo", - "home_page_archive_err_partner": "No se pueden archivar los elementos de un compañero, omitiendo", - "home_page_building_timeline": "Construyendo la línea de tiempo", - "home_page_delete_err_partner": "No se pueden eliminar los elementos de un compañero, omitiendo", - "home_page_delete_remote_err_local": "Elementos locales en la selección de eliminación remota, omitiendo", - "home_page_favorite_err_local": "Aún no se pueden marcar como favoritos los elementos locales, omitiendo", - "home_page_favorite_err_partner": "Aún no se pueden marcar los como favoritos los elementos de un compañero, omitiendo", - "home_page_first_time_notice": "Si es la primera vez que usas la aplicación, asegúrate de elegir un álbum como copia de seguridad para que la línea de tiempo pueda mostrar fotos y vídeos en él", - "home_page_locked_error_local": "No se pueden mover elementos locales a una carpeta protegida, omitiendo", - "home_page_locked_error_partner": "No se pueden mover los elementos de un compañero a una carpeta protegida; omitiendo", - "home_page_share_err_local": "No se pueden compartir elementos locales a través de un enlace, omitiendo", - "home_page_upload_err_limit": "Solo se pueden subir 30 elementos simultáneamente, omitiendo", - "host": "Host", - "hour": "Hora", - "hours": "Horas", - "id": "ID", - "idle": "Inactivo", - "ignore_icloud_photos": "Ignorar fotos de iCloud", - "ignore_icloud_photos_description": "Las fotos almacenadas en iCloud no se subirán a Immich", - "image": "Imagen", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tomada el {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tomada con {person1} el {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tomada con {person1} y {person2} el {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tomada con {person1}, {person2}, y {person3} el {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tomada con {person1}, {person2}, y {additionalCount, number} más el {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} el {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1} el {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1} y {person2} el {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1}, {person2}, y {person3} el {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1}, {person2}, y {additionalCount, number} más el {date}", - "image_saved_successfully": "Imágenes guardas", - "image_viewer_page_state_provider_download_started": "Descarga Iniciada", - "image_viewer_page_state_provider_download_success": "Descarga exitosa", - "image_viewer_page_state_provider_share_error": "Error al compartir", - "immich_logo": "Logo de Immich", - "immich_web_interface": "Interfaz Web de Immich", - "import_from_json": "Importar desde JSON", - "import_path": "Importar ruta", - "in_albums": "En {count, plural, one {# álbum} other {# álbumes}}", - "in_archive": "En archivo", - "in_year": "En {year}", - "in_year_selector": "En", - "include_archived": "Incluir archivados", - "include_shared_albums": "Incluir álbumes compartidos", - "include_shared_partner_assets": "Incluir elementos compartidos por compañeros", - "individual_share": "Compartir individualmente", - "individual_shares": "Acciones individuales", - "info": "Información", - "interval": { - "day_at_onepm": "Todos los días a las 1pm", - "hours": "Cada {hours, plural, one {hora} other {{hours, number} horas}}", - "night_at_midnight": "Todos los días a medianoche", - "night_at_twoam": "Todas las noches a las 2am" - }, - "invalid_date": "Fecha incorrecta", - "invalid_date_format": "Formato de fecha incorrecto", - "invite_people": "Invitar a Personas", - "invite_to_album": "Invitar al álbum", - "ios_debug_info_fetch_ran_at": "Busca ejecución en {dateTime}", - "ios_debug_info_last_sync_at": "Última sincronización en {dateTime}", - "ios_debug_info_no_processes_queued": "Ningún proceso de fondo encolado", - "ios_debug_info_no_sync_yet": "Todavía no se ha ejecutado ningún trabajo de sincronización en segundo plano", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proceso encolado de fondo} other {{count} procesos encolados de fondo}}", - "ios_debug_info_processing_ran_at": "El procesamiento se ejecutó el {dateTime}", - "items_count": "{count, plural, one {# elemento} other {# elementos}}", - "jobs": "Tareas", - "json_editor": "Editor JSON", - "json_error": "Error JSON", - "keep": "Conservar", - "keep_albums": "Conservar álbumes", - "keep_albums_count": "Mantener {count} {count, plural, one {álbum} other {álbumes}}", - "keep_all": "Conservar Todo", - "keep_description": "Elige qué permanece en tu dispositivo al liberar espacio.", - "keep_favorites": "Mantener favoritos", - "keep_on_device": "Mantener en el dispositivo", - "keep_on_device_hint": "Seleccionar elementos para conservar en este dispositivo", - "keep_this_delete_others": "Mantener este, eliminar los otros", - "keeping": "Manteniendo: {items}", - "kept_this_deleted_others": "Mantuvo este activo y eliminó {count, plural, one {# activo} other {# activos}}", - "keyboard_shortcuts": "Atajos de teclado", - "language": "Idioma", - "language_no_results_subtitle": "Intente ajustar el término de búsqueda", - "language_no_results_title": "No se han encontrado idiomas", - "language_search_hint": "Buscar idiomas...", - "language_setting_description": "Selecciona tu idioma preferido", - "large_files": "Archivos Grandes", - "last": "Último", - "last_months": "{count, plural, one {Último mes} other {Últimos # meses}}", - "last_seen": "Ultima vez visto", - "latest_version": "Última versión", - "latitude": "Latitud", - "leave": "Abandonar", - "leave_album": "Abandonar álbum", - "lens_model": "Modelo de objetivo", - "let_others_respond": "Permitir que otros respondan", - "level": "Nivel", - "library": "Biblioteca", - "library_add_folder": "Añadir carpeta", - "library_edit_folder": "Editar carpeta", - "library_options": "Opciones de biblioteca", - "library_page_device_albums": "Álbumes en el dispositivo", - "library_page_new_album": "Nuevo álbum", - "library_page_sort_asset_count": "Número de elementos", - "library_page_sort_created": "Creado más recientemente", - "library_page_sort_last_modified": "Última modificación", - "library_page_sort_title": "Título del álbum", - "licenses": "Licencias", - "light": "Claro", - "like": "Me gusta", - "like_deleted": "Me gusta eliminado", - "link_motion_video": "Enlazar vídeo en movimiento", - "link_to_oauth": "Enlace a OAuth", - "linked_oauth_account": "Cuenta OAuth vinculada", - "list": "Lista", - "loading": "Cargando", - "loading_search_results_failed": "Error al cargar los resultados de la búsqueda", - "local": "Local", - "local_asset_cast_failed": "No es posible transmitir un recurso que no está subido al servidor", - "local_assets": "Archivos Locales", - "local_id": "ID local", - "local_media_summary": "Resumen de Medios Locales", - "local_network": "Red local", - "local_network_sheet_info": "La aplicación se conectará al servidor a través de esta URL cuando utilice la red Wi-Fi especificada", - "location": "Ubicación", - "location_permission": "Permiso de ubicación", - "location_permission_content": "Para usar la función de cambio automático, Immich necesita permiso de ubicación precisa para poder leer el nombre de la red Wi-Fi actual", - "location_picker_choose_on_map": "Elegir en el mapa", - "location_picker_latitude_error": "Introduce una latitud válida", - "location_picker_latitude_hint": "Introduce tu latitud aquí", - "location_picker_longitude_error": "Introduce una longitud válida", - "location_picker_longitude_hint": "Introduce tu longitud aquí", - "lock": "Bloquear", - "locked_folder": "Carpeta protegida", - "log_detail_title": "Detalle del registro", - "log_out": "Cerrar sesión", - "log_out_all_devices": "Cerrar sesión en todos los dispositivos", - "logged_in_as": "Sesión iniciada como {user}", - "logged_out_all_devices": "Se ha cerrado la sesión en todos los dispositivos", - "logged_out_device": "Dispositivo desconectado", - "login": "Inicio de sesión", - "login_disabled": "El inicio de sesión ha sido desactivado", - "login_form_api_exception": "Excepción producida por API. Por favor, comprueba el URL del servidor e inténtalo de nuevo.", - "login_form_back_button_text": "Atrás", - "login_form_email_hint": "tucorreo@correo.com", - "login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto", - "login_form_endpoint_url": "Enlace del punto de acceso (endpoint) del servidor", - "login_form_err_http": "Por favor, especifique http:// o https://", - "login_form_err_invalid_email": "Correo electrónico no válido", - "login_form_err_invalid_url": "URL no válida", - "login_form_err_leading_whitespace": "Espacio en blanco inicial", - "login_form_err_trailing_whitespace": "Espacio en blanco al final", - "login_form_failed_get_oauth_server_config": "Error al iniciar sesión con OAuth, comprueba la URL del servidor", - "login_form_failed_get_oauth_server_disable": "La función de OAuth no está disponible en este servidor", - "login_form_failed_login": "Error al iniciar sesión, comprueba la URL del servidor, el correo electrónico y la contraseña", - "login_form_handshake_exception": "Hubo una excepción de handshake con el servidor. Activa la compatibilidad con certificados autofirmados en la configuración si estás utilizando un certificado autofirmado.", - "login_form_password_hint": "contraseña", - "login_form_save_login": "Mantener la sesión iniciada", - "login_form_server_empty": "Agrega la URL del servidor.", - "login_form_server_error": "No se pudo conectar al servidor.", - "login_has_been_disabled": "El inicio de sesión ha sido deshabilitado.", - "login_password_changed_error": "Hubo un error actualizando la contraseña", - "login_password_changed_success": "Contraseña cambiado con éxito", - "logout_all_device_confirmation": "¿Estás seguro de que quieres cerrar sesión en todos los dispositivos?", - "logout_this_device_confirmation": "¿Estás seguro de que quieres cerrar sesión en este dispositivo?", - "logs": "Registros", - "longitude": "Longitud", - "look": "Mirar", - "loop_videos": "Vídeos en bucle", - "loop_videos_description": "Habilite la reproducción automática de un video en el visor de detalles.", - "main_branch_warning": "Está utilizando una versión de desarrollo; ¡le recomendamos encarecidamente que utilice una versión de lanzamiento!", - "main_menu": "Menú principal", - "maintenance_action_restore": "Restaurando base de datos", - "maintenance_description": "Immich se ha puesto en modo de mantenimiento.", - "maintenance_end": "Finalizar el modo de mantenimiento", - "maintenance_end_error": "Error al finalizar el modo de mantenimiento.", - "maintenance_logged_in_as": "Sesión iniciada actualmente como {user}", - "maintenance_restore_from_backup": "Restaurar desde una copia de seguridad", - "maintenance_restore_library": "Restaura tu biblioteca", - "maintenance_restore_library_confirm": "¡Si esto parece correcto, continúe restaurando una copia de seguridad!", - "maintenance_restore_library_description": "Restaurando base de datos", - "maintenance_restore_library_folder_has_files": "{folder} tiene {count} carpeta(s)", - "maintenance_restore_library_folder_no_files": "¡A {folder} le faltan archivos!", - "maintenance_restore_library_folder_pass": "legible y escribible", - "maintenance_restore_library_folder_read_fail": "no legible", - "maintenance_restore_library_folder_write_fail": "no escribible", - "maintenance_restore_library_hint_missing_files": "Es posible que le falten archivos importantes", - "maintenance_restore_library_hint_regenerate_later": "Puedes regenerarlos más tarde en la configuración", - "maintenance_restore_library_hint_storage_template_missing_files": "¿Estás usando una plantilla de almacenamiento? Es posible que te falten archivos", - "maintenance_restore_library_loading": "Cargando comprobaciones de integridad y heurísticas…", - "maintenance_task_backup": "Creando una copia de seguridad de la base de datos existente…", - "maintenance_task_migrations": "Ejecutando migraciones de bases de datos…", - "maintenance_task_restore": "Restaurando la copia de seguridad elegida…", - "maintenance_task_rollback": "La restauración falló, volviendo al punto de restauración…", - "maintenance_title": "No disponible temporalmente", - "make": "Marca", - "manage_geolocation": "Administrar ubicación", - "manage_media_access_rationale": "Este permiso se requiere para mover recursos a la papelera correctamente, así como para restaurarlos desde la misma.", - "manage_media_access_settings": "Abrir configuración", - "manage_media_access_subtitle": "Permitir a la app Immich gestionar y mover archivos multimedia.", - "manage_media_access_title": "Acceso a gestión de archivos multimedia", - "manage_shared_links": "Administrar enlaces compartidos", - "manage_sharing_with_partners": "Gestionar el uso compartido con miembros", - "manage_the_app_settings": "Administrar la configuración de la aplicación", - "manage_your_account": "Gestiona tu cuenta", - "manage_your_api_keys": "Administre sus claves API", - "manage_your_devices": "Administre sus dispositivos conectados", - "manage_your_oauth_connection": "Administra tu conexión OAuth", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {No hay fotos en esta área} one {# foto} other {# fotos}}", - "map_cannot_get_user_location": "No se pudo obtener la posición del usuario", - "map_location_dialog_yes": "Sí", - "map_location_picker_page_use_location": "Usar esta ubicación", - "map_location_service_disabled_content": "Los servicios de ubicación deben estar activados para mostrar elementos de tu ubicación actual. ¿Deseas activarlos ahora?", - "map_location_service_disabled_title": "Servicios de ubicación desactivados", - "map_marker_for_images": "Marcador de mapa para imágenes tomadas en {city}, {country}", - "map_marker_with_image": "Marcador de mapa con imagen", - "map_no_location_permission_content": "Se necesitan permisos de ubicación para mostrar elementos de tu ubicación actual. ¿Deseas activarlos ahora?", - "map_no_location_permission_title": "Permisos de ubicación denegados", - "map_settings": "Ajustes del mapa", - "map_settings_dark_mode": "Modo oscuro", - "map_settings_date_range_option_day": "Últimas 24 horas", - "map_settings_date_range_option_days": "Últimos {days} días", - "map_settings_date_range_option_year": "Último año", - "map_settings_date_range_option_years": "Últimos {years} años", - "map_settings_dialog_title": "Ajustes del mapa", - "map_settings_include_show_archived": "Incluir archivados", - "map_settings_include_show_partners": "Incluir miembros", - "map_settings_only_show_favorites": "Mostrar solo favoritas", - "map_settings_theme_settings": "Apariencia del Mapa", - "map_zoom_to_see_photos": "Alejar para ver fotos", - "mark_all_as_read": "Marcar todo como leído", - "mark_as_read": "Marcar como leído", - "marked_all_as_read": "Todos marcados como leídos", - "matches": "Coincidencias", - "matching_assets": "Elementos Coincidentes", - "media_type": "Tipo de medio", - "memories": "Recuerdos", - "memories_all_caught_up": "Puesto al día", - "memories_check_back_tomorrow": "Vuelve mañana para más recuerdos", - "memories_setting_description": "Gestiona lo que ves en tus recuerdos", - "memories_start_over": "Empezar de nuevo", - "memories_swipe_to_close": "Desliza para cerrar", - "memory": "Recuerdo", - "memory_lane_title": "Baúl de los recuerdos {title}", - "menu": "Menú", - "merge": "Fusionar", - "merge_people": "Fusionar personas", - "merge_people_limit": "Solo puedes fusionar hasta 5 caras a la vez", - "merge_people_prompt": "¿Quieres fusionar a estas personas? Esta acción es irreversible.", - "merge_people_successfully": "Personas fusionadas correctamente", - "merged_people_count": "Fusionada {count, plural, one {# persona} other {# personas}}", - "minimize": "Minimizar", - "minute": "Minuto", - "minutes": "Minutos", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertical", - "missing": "Faltante", - "mobile_app": "Aplicación Móvil", - "mobile_app_download_onboarding_note": "Descarga la aplicación móvil utilizando las siguientes opciones", - "model": "Modelo", - "month": "Mes", - "monthly_title_text_date_format": "MMMM a", - "more": "Mas", - "move": "Mover", - "move_down": "Bajar", - "move_off_locked_folder": "Sacar de la carpeta protegida", - "move_to": "Mover a", - "move_to_device_trash": "Mover a la papelera del dispositivo", - "move_to_lock_folder_action_prompt": "{count} añadido(s) a la carpeta protegida", - "move_to_locked_folder": "Mover a la carpeta protegida", - "move_to_locked_folder_confirmation": "Estas fotos y vídeos se eliminarán de todos los álbumes; solo se podrán ver en la carpeta protegida", - "move_up": "Subir", - "moved_to_archive": "Movido(s) {count, plural, one {# recurso} other {# recursos}} a archivo", - "moved_to_library": "Movido(s) {count, plural, one {# recurso} other {# recursos}} a biblioteca", - "moved_to_trash": "Movido a la papelera", - "multiselect_grid_edit_date_time_err_read_only": "No se puede cambiar la fecha del archivo(s) de solo lectura, omitiendo", - "multiselect_grid_edit_gps_err_read_only": "No se puede editar la ubicación de activos de solo lectura, omitiendo", - "mute_memories": "Silenciar Recuerdos", - "my_albums": "Mis álbumes", - "name": "Nombre", - "name_or_nickname": "Nombre o apodo", - "name_required": "El nombre es obligatorio", - "navigate": "Navegar", - "navigate_to_time": "Navegar a Hora", - "network_requirement_photos_upload": "Usar datos móviles para crear una copia de seguridad de las fotos", - "network_requirement_videos_upload": "Usar datos móviles para crear una copia de seguridad de los videos", - "network_requirements": "Requisitos de red", - "network_requirements_updated": "Los requisitos de red han cambiado, reiniciando la cola de copias de seguridad", - "networking_settings": "Red", - "networking_subtitle": "Administrar la configuración de la url del servidor", - "never": "Nunca", - "new_album": "Nuevo álbum", - "new_api_key": "Nueva clave API", - "new_date_range": "Nuevo rango de fechas", - "new_password": "Nueva contraseña", - "new_person": "Nueva persona", - "new_pin_code": "Nuevo PIN", - "new_pin_code_subtitle": "Esta es la primera vez que accedes a la carpeta protegida. Crea un código PIN seguro para acceder a esta página", - "new_timeline": "Nueva Línea de tiempo", - "new_update": "Nueva actualización", - "new_user_created": "Nuevo usuario creado", - "new_version_available": "NUEVA VERSIÓN DISPONIBLE", - "newest_first": "El más reciente primero", - "next": "Siguiente", - "next_memory": "Siguiente recuerdo", - "no": "No", - "no_actions_added": "No hay acciones añadidas aún", - "no_albums_found": "No se encontraron álbumes", - "no_albums_message": "Crea un álbum para organizar tus fotos y vídeos", - "no_albums_with_name_yet": "Parece que todavía no tienes ningún álbum con este nombre.", - "no_albums_yet": "Parece que aún no tienes ningún álbum.", - "no_archived_assets_message": "Archive fotos y videos para ocultarlos de su vista de Fotos", - "no_assets_message": "Haz clic para subir tu primera foto", - "no_assets_to_show": "No hay elementos a mostrar", - "no_cast_devices_found": "No se encontraron dispositivos de transmisión", - "no_checksum_local": "Suma de verificación no disponible. No se pueden obtener los elementos locales", - "no_checksum_remote": "Suma de verificación no disponible. No se puede obtener el elemento remoto", - "no_configuration_needed": "No se necesita configuración", - "no_devices": "Dispositivos no autorizados", - "no_duplicates_found": "No se encontraron duplicados.", - "no_exif_info_available": "No hay información exif disponible", - "no_explore_results_message": "Sube más fotos para explorar tu colección.", - "no_favorites_message": "Añade favoritos para encontrar rápidamente sus mejores fotos y videos", - "no_filters_added": "Aún no se han añadido filtros", - "no_libraries_message": "Crea una biblioteca externa para ver tus fotos y vídeos", - "no_local_assets_found": "No se encontraron elementos locales con esta suma de comprobación", - "no_location_set": "No se ha establecido ninguna ubicación", - "no_locked_photos_message": "Las fotos y los vídeos de la carpeta protegida se mantienen ocultos; no aparecerán cuando veas o busques elementos en tu biblioteca.", - "no_name": "Sin nombre", - "no_notifications": "Ninguna notificación", - "no_people_found": "No se encontraron personas coincidentes", - "no_places": "Sin lugares", - "no_remote_assets_found": "No se encontraron elementos remotos con esta suma de comprobación", - "no_results": "Sin resultados", - "no_results_description": "Pruebe con un sinónimo o una palabra clave más general", - "no_shared_albums_message": "Crea un álbum para compartir fotos y vídeos con personas de tu red", - "no_uploads_in_progress": "No hay cargas en progreso", - "none": "Ninguno", - "not_allowed": "No permitido", - "not_available": "N/D", - "not_in_any_album": "Sin álbum", - "not_selected": "No seleccionado", - "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar la etiqueta de almacenamiento a los archivos que ya se subieron, ejecute la", - "notes": "Notas", - "nothing_here_yet": "Sin nada aún", - "notification_permission_dialog_content": "Para activar las notificaciones, ve a Configuración y selecciona permitir.", - "notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.", - "notification_permission_list_tile_enable_button": "Permitir notificaciones", - "notification_permission_list_tile_title": "Permisos de Notificacion", - "notification_toggle_setting_description": "Habilitar notificaciones de correo electrónico", - "notifications": "Notificaciones", - "notifications_setting_description": "Administrar notificaciones", - "oauth": "OAuth", - "obtainium_configurator": "Configurador de Obtainium", - "obtainium_configurator_instructions": "Usa Obtainium para instalar y actualizar la aplicación de Android directamente desde las versiones publicadas en el GitHub de Immich. Crea una clave API y selecciona una variante para generar tu enlace de configuración de Obtainium", - "ocr": "OCR", - "official_immich_resources": "Recursos oficiales de Immich", - "offline": "Desconectado", - "offset": "Desviación", - "ok": "Sí", - "oldest_first": "Los más antiguos primero", - "on_this_device": "En este dispositivo", - "onboarding": "Incorporando", - "onboarding_locale_description": "Selecciona tu idioma preferido. Podrás cambiarlo después desde tu configuración.", - "onboarding_privacy_description": "Las siguientes funciones, que son opcionales, utilizan servicios externos. Puedes deshabilitarlas mediante los ajustes en cualquier momento.", - "onboarding_server_welcome_description": "Empecemos a configurar tu instancia fijando algunos ajustes comunes.", - "onboarding_theme_description": "Elija un color de tema para su instancia. Puedes cambiar esto más tarde en tu configuración.", - "onboarding_user_welcome_description": "¡Empecemos!", - "onboarding_welcome_user": "Bienvenido, {user}", - "online": "En línea", - "only_favorites": "Solo favoritos", - "open": "Abierto", - "open_in_map_view": "Abrir en la vista del mapa", - "open_in_openstreetmap": "Abrir en OpenStreetMap", - "open_the_search_filters": "Abre los filtros de búsqueda", - "options": "Opciones", - "or": "o", - "organize_into_albums": "Organizar en álbumes", - "organize_into_albums_description": "Añade fotos existentes en álbumes usando la configuración actual de sincronización", - "organize_your_library": "Organiza tu biblioteca", - "original": "original", - "other": "Otro", - "other_devices": "Otros dispositivos", - "other_entities": "Otras entidades", - "other_variables": "Otras variables", - "owned": "Propios", - "owner": "Propietario", - "page": "Página", - "partner": "Compañero", - "partner_can_access": "{partner} tiene acceso", - "partner_can_access_assets": "Todas tus fotos y vídeos excepto los Archivados y Eliminados", - "partner_can_access_location": "Ubicación donde fueron realizadas tus fotos", - "partner_list_user_photos": "Fotos de {user}", - "partner_list_view_all": "Ver todas", - "partner_page_empty_message": "Tus fotos aún no se han compartido con ningún compañero.", - "partner_page_no_more_users": "No hay más usuarios para añadir", - "partner_page_partner_add_failed": "No se pudo añadir al miembro", - "partner_page_select_partner": "Seleccionar compañero", - "partner_page_shared_to_title": "Compartido con", - "partner_page_stop_sharing_content": "{partner} ya no podrá acceder a tus fotos.", - "partner_sharing": "Compartir con compañeros", - "partners": "Miembros", - "password": "Contraseña", - "password_does_not_match": "Las contraseñas no coinciden", - "password_required": "Contraseña requerida", - "password_reset_success": "Restablecimiento de contraseña exitoso", - "past_durations": { - "days": "Pasados {days, plural, one {día} other {# días}}", - "hours": "Pasadas {hours, plural, one {hora} other {# horas}}", - "years": "Pasado(s) {years, plural, one {año} other {# años}}" - }, - "path": "Ruta", - "pattern": "Patrón", - "pause": "Pausar", - "pause_memories": "Detener recuerdos", - "paused": "Detenido", - "pending": "Pendiente", - "people": "Personas", - "people_edits_count": "Editada {count, plural, one {# persona} other {# personas}}", - "people_feature_description": "Explorar fotos y vídeos agrupados por personas", - "people_selected": "{count, plural, one {# persona seleccionada} other {# personas seleccionadas}}", - "people_sidebar_description": "Mostrar un enlace a Personas en la barra lateral", - "permanent_deletion_warning": "Advertencia de eliminación permanente", - "permanent_deletion_warning_setting_description": "Mostrar una advertencia al eliminar archivos permanentemente", - "permanently_delete": "Borrar permanentemente", - "permanently_delete_assets_count": "Eliminar permanentemente {count, plural, one {elemento} other {elementos}}", - "permanently_delete_assets_prompt": "¿Está seguro de que desea eliminar permanentemente {count, plural, one {este activo?} other {estos # activos?}} Esto también eliminará {count, plural, one {de tu} other {de tus}} álbum(es).", - "permanently_deleted_asset": "Archivo eliminado permanentemente", - "permanently_deleted_assets_count": "Eliminado permanentemente {count, plural, one {# elemento} other {# elementos}}", - "permission": "Permiso", - "permission_empty": "Tus permisos no deben estar vacíos", - "permission_onboarding_back": "Volver", - "permission_onboarding_continue_anyway": "Continuar de todos modos", - "permission_onboarding_get_started": "Empezar", - "permission_onboarding_go_to_settings": "Ir a configuración", - "permission_onboarding_permission_denied": "Permiso denegado. Para usar Immich, concede permisos de fotos y videos en Configuración.", - "permission_onboarding_permission_granted": "¡Permiso concedido! Todo listo.", - "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich haga copia de seguridad y gestione toda tu colección de galería, concede permisos de fotos y videos en Configuración.", - "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", - "person": "Persona", - "person_age_months": "hace {months, plural, one {# mes} other {# meses}}", - "person_age_year_months": "1 año y {months, plural, one {# mes} other {# meses}}", - "person_age_years": "{years, plural, other {# años}}", - "person_birthdate": "Nacido el {date}", - "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", - "person_recognized": "Persona reconocida", - "person_selected": "Persona seleccionada", - "photo_shared_all_users": "Parece que compartiste tus fotos con todos los usuarios o no tienes ningún usuario con quien compartirlas.", - "photos": "Fotos", - "photos_and_videos": "Fotos y Vídeos", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", - "photos_from_previous_years": "Fotos de años anteriores", - "photos_only": "Solo fotos", - "pick_a_location": "Elige una ubicación", - "pick_custom_range": "Rango personalizado", - "pick_date_range": "Seleccione un rango de fechas", - "pin_code_changed_successfully": "PIN cambiado exitosamente", - "pin_code_reset_successfully": "PIN restablecido exitosamente", - "pin_code_setup_successfully": "PIN establecido exitosamente", - "pin_verification": "Verificación con código PIN", - "place": "Lugar", - "places": "Lugares", - "places_count": "{count, plural, one {{count, number} Lugar} other {{count, number} Lugares}}", - "play": "Reproducir", - "play_memories": "Reproducir recuerdos", - "play_motion_photo": "Reproducir foto en movimiento", - "play_or_pause_video": "Reproducir o pausar vídeo", - "play_original_video": "Reproducir video original", - "play_original_video_setting_description": "Preferir la reproducción de videos originales en lugar de videos transcodificados. Si el recurso original no es compatible, es posible que no se reproduzca correctamente.", - "play_transcoded_video": "Reproducir video transcodificado", - "please_auth_to_access": "Por favor, autentícate para acceder", - "port": "Puerto", - "preferences_settings_subtitle": "Configuraciones de la aplicación", - "preferences_settings_title": "Preferencias", - "preparing": "Preparando", - "preset": "Preestablecido", - "preview": "Posterior", - "previous": "Anterior", - "previous_memory": "Recuerdo anterior", - "previous_or_next_day": "Día posterior/anterior", - "previous_or_next_month": "Mes posterior/anterior", - "previous_or_next_photo": "Foto posterior/anterior", - "previous_or_next_year": "Año posterior/anterior", - "primary": "Básico", - "privacy": "Privacidad", - "profile": "Perfil", - "profile_drawer_app_logs": "Registros", - "profile_drawer_client_server_up_to_date": "Cliente y Servidor están actualizados", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Modo Solo lectura habilitado. Mantén pulsado el icono del avatar del usuario para salir.", - "profile_image_of_user": "Foto de perfil de {user}", - "profile_picture_set": "Conjunto de imágenes de perfil.", - "public_album": "Álbum público", - "public_share": "Compartir públicamente", - "purchase_account_info": "Seguidor", - "purchase_activated_subtitle": "Gracias por apoyar a Immich y al software de código abierto", - "purchase_activated_time": "Activado el {date}", - "purchase_activated_title": "Su clave ha sido activada correctamente", - "purchase_button_activate": "Activar", - "purchase_button_buy": "Comprar", - "purchase_button_buy_immich": "Comprar Immich", - "purchase_button_never_show_again": "No volver a mostrar", - "purchase_button_reminder": "Recuérdamelo en 30 días", - "purchase_button_remove_key": "Quitar clave", - "purchase_button_select": "Seleccionar", - "purchase_failed_activation": "¡Error al activar! ¡Por favor, revisa tu correo electrónico para obtener la clave del producto correcta!", - "purchase_individual_description_1": "Para un usuario", - "purchase_individual_description_2": "Estado de soporte", - "purchase_individual_title": "Individual", - "purchase_input_suggestion": "¿Tiene una clave de producto? Introdúzcala a continuación", - "purchase_license_subtitle": "Compre Immich para apoyar el desarrollo continuo del servicio", - "purchase_lifetime_description": "Compra de por vida", - "purchase_option_title": "OPCIONES DE COMPRA", - "purchase_panel_info_1": "Desarrollar Immich requiere mucho tiempo y esfuerzo, y contamos con ingenieros a tiempo completo que trabajan en él para que sea lo mejor posible. Nuestra misión es que el software de código abierto y las prácticas comerciales éticas se conviertan en una fuente de ingresos sostenibles para los desarrolladores y crear un ecosistema que respete la privacidad con alternativas reales a los servicios en la nube de pago.", - "purchase_panel_info_2": "Como nos comprometemos a no añadir pagos, esta compra no le otorgará ninguna característica adicional en Immich. Confiamos en que los usuarios como usted apoyen el desarrollo continuo de Immich.", - "purchase_panel_title": "Apoya el proyecto", - "purchase_per_server": "Por servidor", - "purchase_per_user": "Por usuario", - "purchase_remove_product_key": "Eliminar clave de producto", - "purchase_remove_product_key_prompt": "¿Está seguro de que desea eliminar la clave del producto?", - "purchase_remove_server_product_key": "Eliminar la clave de producto del servidor", - "purchase_remove_server_product_key_prompt": "¿Está seguro de que desea eliminar la clave de producto del servidor?", - "purchase_server_description_1": "Para todo el servidor", - "purchase_server_description_2": "Estado del soporte", - "purchase_server_title": "Servidor", - "purchase_settings_server_activated": "La clave del producto del servidor la administra el administrador", - "query_asset_id": "Consultar ID de elemento", - "queue_status": "Poniendo en cola {count}/{total}", - "rate_asset": "Valorar activo", - "rating": "Valoración", - "rating_clear": "Borrar calificación", - "rating_count": "{count, plural, one {# estrella} other {# estrellas}}", - "rating_description": "Mostrar la clasificación exif en el panel de información", - "rating_set": "Calificación establecida en {rating, plural, one {# estrella} other {# estrellas}}", - "reaction_options": "Opciones de reacción", - "read_changelog": "Leer registro de cambios", - "readonly_mode_disabled": "Modo Solo lectura deshabilitado", - "readonly_mode_enabled": "Modo Solo lectura habilitado", - "ready_for_upload": "Listo para subir", - "reassign": "Reasignar", - "reassigned_assets_to_existing_person": "Reasignado {count, plural, one {# elemento} other {# elementos}} a {name, select, null {una persona existente} other {{name}}}", - "reassigned_assets_to_new_person": "Reasignado {count, plural, one {# elemento} other {# elementos}} a un nuevo usuario", - "reassing_hint": "Asignar archivos seleccionados a una persona existente", - "recent": "Reciente", - "recent-albums": "Últimos álbumes", - "recent_searches": "Búsquedas recientes", - "recently_added": "Añadidos recientemente", - "recently_added_page_title": "Recién añadidos", - "recently_taken": "Tomadas recientemente", - "recently_taken_page_title": "Tomadas Recientemente", - "refresh": "Actualizar", - "refresh_encoded_videos": "Recargar los vídeos codificados", - "refresh_faces": "Actualizar caras", - "refresh_metadata": "Recargar metadatos", - "refresh_thumbnails": "Recargar miniaturas", - "refreshed": "Recargado", - "refreshes_every_file": "Recargar todos los archivos nuevos y existentes", - "refreshing_encoded_video": "Recargando los videos codificados", - "refreshing_faces": "Recargando caras", - "refreshing_metadata": "Recargando metadatos", - "regenerating_thumbnails": "Recargando miniaturas", - "remote": "Remoto", - "remote_assets": "Elementos remotos", - "remote_media_summary": "Resumen de Medios Remotos", - "remove": "Eliminar", - "remove_assets_album_confirmation": "¿Estás seguro que quieres eliminar {count, plural, one {# elemento} other {# elementos}} del álbum?", - "remove_assets_shared_link_confirmation": "¿Estás seguro que quieres eliminar {count, plural, one {# elemento} other {# elementos}} del enlace compartido?", - "remove_assets_title": "¿Eliminar activos?", - "remove_custom_date_range": "Eliminar intervalo de fechas personalizado", - "remove_deleted_assets": "Eliminar archivos sin conexión", - "remove_from_album": "Eliminar del álbum", - "remove_from_album_action_prompt": "{count} eliminado del álbum", - "remove_from_favorites": "Quitar de favoritos", - "remove_from_lock_folder_action_prompt": "{count} eliminado(s) de la carpeta protegida", - "remove_from_locked_folder": "Eliminar de la carpeta protegida", - "remove_from_locked_folder_confirmation": "¿Seguro que deseas sacar estas fotos y vídeos de la carpeta protegida? Si continúas, los elementos serán visibles en tu biblioteca.", - "remove_from_shared_link": "Eliminar desde enlace compartido", - "remove_memory": "Quitar recuerdo", - "remove_photo_from_memory": "Quitar foto de este recuerdo", - "remove_tag": "Quitar etiqueta", - "remove_url": "Eliminar URL", - "remove_user": "Eliminar usuario", - "removed_api_key": "Clave API eliminada: {name}", - "removed_from_archive": "Eliminado del archivo", - "removed_from_favorites": "Eliminado de favoritos", - "removed_from_favorites_count": "{count, plural, other {Eliminados #}} de favoritos", - "removed_memory": "Recuerdo eliminado", - "removed_photo_from_memory": "Foto eliminada del recuerdo", - "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# activo} other {# activos}}", - "rename": "Renombrar", - "repair": "Reparar", - "repair_no_results_message": "Los archivos perdidos y sin seguimiento aparecerán aquí", - "replace_with_upload": "Reemplazar con subida", - "repository": "Repositorio", - "require_password": "Contraseña requerida", - "require_user_to_change_password_on_first_login": "Requerir que el usuario cambie la contraseña en el primer inicio de sesión", - "rescan": "Volver a escanear", - "reset": "Restablecer", - "reset_password": "Restablecer la contraseña", - "reset_people_visibility": "Restablecer la visibilidad de las personas", - "reset_pin_code": "Restablecer PIN", - "reset_pin_code_description": "Si olvidaste tu código PIN, puedes comunicarte con el administrador del servidor para restablecerlo", - "reset_pin_code_success": "Código PIN restablecido correctamente", - "reset_pin_code_with_password": "Siempre puedes restablecer tu código PIN usando tu contraseña", - "reset_sqlite": "Restablecer la base de datos SQLite", - "reset_sqlite_confirmation": "¿Estás seguro que deseas restablecer la base de datos SQLite? Deberás cerrar sesión y volver a iniciarla para resincronizar los datos", - "reset_sqlite_success": "Restablecer exitosamente la base de datos SQLite", - "reset_to_default": "Restablecer los valores predeterminados", - "resolution": "Resolución", - "resolve_duplicates": "Resolver duplicados", - "resolved_all_duplicates": "Todos los duplicados resueltos", - "restore": "Restaurar", - "restore_all": "Restaurar todo", - "restore_trash_action_prompt": "{count} restaurado de la papelera", - "restore_user": "Restaurar usuario", - "restored_asset": "Archivo restaurado", - "resume": "Continuar", - "resume_paused_jobs": "Reanudar {count, plural, one {# tarea en pausa} other {# tareas en pausa}}", - "retry_upload": "Reintentar subida", - "review_duplicates": "Revisar duplicados", - "review_large_files": "Revisar archivos grandes", - "role": "Rol", - "role_editor": "Editor", - "role_viewer": "Visor", - "running": "En ejecución", - "save": "Guardar", - "save_to_gallery": "Guardar en la galería", - "saved": "Guardado", - "saved_api_key": "Clave API guardada", - "saved_profile": "Perfil guardado", - "saved_settings": "Configuraciones guardadas", - "say_something": "Comenta algo", - "scaffold_body_error_occurred": "Ha ocurrido un error", - "scan": "Escanear", - "scan_all_libraries": "Escanear todas las bibliotecas", - "scan_library": "Escanear", - "scan_settings": "Configuración de escaneo", - "scanning": "Escaneando", - "scanning_for_album": "Buscando álbum...", - "search": "Buscar", - "search_albums": "Buscar álbumes", - "search_by_context": "Buscar por contexto", - "search_by_description": "Buscar por descripción", - "search_by_description_example": "Día de senderismo en Sapa", - "search_by_filename": "Buscar por nombre de archivo o extensión", - "search_by_filename_example": "es decir IMG_1234.JPG o PNG", - "search_by_ocr": "Buscar por OCR", - "search_by_ocr_example": "Café con leche", - "search_camera_lens_model": "Buscar modelo de lente...", - "search_camera_make": "Buscar fabricante de cámara...", - "search_camera_model": "Buscar modelo de cámara...", - "search_city": "Buscar ciudad...", - "search_country": "Buscar país...", - "search_filter_apply": "Aplicar filtros", - "search_filter_camera_title": "Elegir tipo de cámara", - "search_filter_date": "Fecha", - "search_filter_date_interval": "{start} al {end}", - "search_filter_date_title": "Seleccionar un intervalo de fechas", - "search_filter_display_option_not_in_album": "No en álbum", - "search_filter_display_options": "Opciones de visualización", - "search_filter_filename": "Buscar por nombre de archivo", - "search_filter_location": "Ubicación", - "search_filter_location_title": "Seleccionar una ubicación", - "search_filter_media_type": "Tipo de archivo", - "search_filter_media_type_title": "Seleccionar el tipo de archivo", - "search_filter_ocr": "Buscar por OCR", - "search_filter_people_title": "Seleccionar personas", - "search_filter_star_rating": "Clasificación de estrellas", - "search_for": "Buscar", - "search_for_existing_person": "Buscar persona existente", - "search_no_more_result": "No hay más resultados", - "search_no_people": "Ninguna persona", - "search_no_people_named": "Ninguna persona llamada \"{name}\"", - "search_no_result": "No se encontraron resultados, prueba con un término o combinación de búsqueda diferente", - "search_options": "Opciones de búsqueda", - "search_page_categories": "Categorías", - "search_page_motion_photos": "Foto en Movimiento", - "search_page_no_objects": "No hay información de objetos disponibles", - "search_page_no_places": "No hay información de lugares disponibles", - "search_page_screenshots": "Capturas de pantalla", - "search_page_search_photos_videos": "Busca tus fotos y videos", - "search_page_selfies": "Selfies", - "search_page_things": "Cosas", - "search_page_view_all_button": "Ver todo", - "search_page_your_activity": "Tu actividad", - "search_page_your_map": "Tu Mapa", - "search_people": "Buscar personas", - "search_places": "Buscar lugar", - "search_rating": "Buscar por calificación...", - "search_result_page_new_search_hint": "Nueva Búsqueda", - "search_settings": "Ajustes de búsqueda", - "search_state": "Buscar región/estado...", - "search_suggestion_list_smart_search_hint_1": "La búsqueda inteligente está habilitada por defecto, para buscar metadatos utiliza esta sintaxis ", - "search_suggestion_list_smart_search_hint_2": "m:tu-término-de-búsqueda", - "search_tags": "Buscar etiquetas...", - "search_timezone": "Buscar zona horaria...", - "search_type": "Tipo de búsqueda", - "search_your_photos": "Busca tus fotos", - "searching_locales": "Buscando sitios...", - "second": "Segundo", - "see_all_people": "Ver todas las personas", - "select": "Seleccionar", - "select_album": "Seleccionar álbum", - "select_album_cover": "Seleccionar portada del álbum", - "select_albums": "Seleccionar álbumes", - "select_all": "Seleccionar todo", - "select_all_duplicates": "Seleccionar todos los duplicados", - "select_all_in": "Seleccionar todos en {group}", - "select_avatar_color": "Seleccionar color del avatar", - "select_count": "{count, plural, one {Seleccionar #} other {Seleccionar #}}", - "select_cutoff_date": "Seleccione fecha límite", - "select_face": "Seleccionar cara", - "select_featured_photo": "Seleccionar foto principal", - "select_from_computer": "Seleccionar desde el equipo", - "select_keep_all": "Conservar todo", - "select_library_owner": "Seleccionar propietario de la biblioteca", - "select_new_face": "Seleccionar nueva cara", - "select_people": "Seleccionar gente", - "select_person": "Seleccionar persona", - "select_person_to_tag": "Elija una persona a etiquetar", - "select_photos": "Seleccionar Fotos", - "select_trash_all": "Seleccionar eliminar todo", - "select_user_for_sharing_page_err_album": "Fallo al crear el álbum", - "selected": "Seleccionado", - "selected_count": "{count, plural, one {# seleccionado} other {# seleccionados}}", - "selected_gps_coordinates": "Coordenadas GPS seleccionadas", - "send_message": "Enviar mensaje", - "send_welcome_email": "Enviar correo de bienvenida", - "server_endpoint": "Punto final del servidor", - "server_info_box_app_version": "Versión de la aplicación", - "server_info_box_server_url": "Enlace del servidor", - "server_offline": "Servidor desconectado", - "server_online": "Servidor en línea", - "server_privacy": "Privacidad del Servidor", - "server_restarting_description": "Esta página se actualizará en breve.", - "server_restarting_title": "El servidor se está reiniciando", - "server_stats": "Estadísticas del servidor", - "server_update_available": "Actualización de servidor disponible", - "server_version": "Versión del servidor", - "set": "Establecer", - "set_as_album_cover": "Establecer portada del álbum", - "set_as_featured_photo": "Establecer como foto destacada", - "set_as_profile_picture": "Seleccionar como foto de perfil", - "set_date_of_birth": "Establecer fecha de nacimiento", - "set_profile_picture": "Establecer foto de perfil", - "set_slideshow_to_fullscreen": "Mostrar diapositivas en pantalla completa", - "set_stack_primary_asset": "Establecer como recurso principal", - "setting_image_viewer_help": "El visor de detalles carga primero la miniatura pequeña, luego carga la vista previa de tamaño mediano (si está habilitada), finalmente carga la original (si está habilitada).", - "setting_image_viewer_original_subtitle": "Activar para cargar la imagen en resolución original (¡muy grande!). Deshabilitar para reducir el consumo de datos (de red y caché).", - "setting_image_viewer_original_title": "Cargar imagen original", - "setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.", - "setting_image_viewer_preview_title": "Cargar imagen de previsualización", - "setting_image_viewer_title": "Imágenes", - "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Cambia el idioma de la aplicación", - "setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {duration}", - "setting_notifications_notify_hours": "{count} horas", - "setting_notifications_notify_immediately": "inmediatamente", - "setting_notifications_notify_minutes": "{count} minutos", - "setting_notifications_notify_never": "nunca", - "setting_notifications_notify_seconds": "{count} segundos", - "setting_notifications_single_progress_subtitle": "Información detallada del progreso de subida de cada archivo", - "setting_notifications_single_progress_title": "Mostrar progreso detallado de copia de seguridad en segundo plano", - "setting_notifications_subtitle": "Ajusta tus preferencias de notificación", - "setting_notifications_total_progress_subtitle": "Progreso general de subida (elementos completados/total)", - "setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano", - "setting_video_viewer_auto_play_subtitle": "Reproducir vídeos automáticamente al abrirlos", - "setting_video_viewer_auto_play_title": "Reproducir vídeos automáticamente", - "setting_video_viewer_looping_title": "Bucle", - "setting_video_viewer_original_video_subtitle": "Al reproducir un video en streaming desde el servidor, reproducir el original incluso cuando haya una transcodificación disponible. Puede causar buffering. Los videos disponibles localmente se reproducen en calidad original independientemente de esta configuración.", - "setting_video_viewer_original_video_title": "Forzar vídeo original", - "settings": "Ajustes", - "settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste", - "settings_saved": "Ajustes guardados", - "setup_pin_code": "Establecer un PIN", - "share": "Compartir", - "share_action_prompt": "{count} recursos compartidos", - "share_add_photos": "Añadir fotos", - "share_assets_selected": "{count} seleccionado(s)", - "share_dialog_preparing": "Preparando...", - "share_link": "Compartir Enlace", - "shared": "Compartidos", - "shared_album_activities_input_disable": "Los comentarios están deshabilitados", - "shared_album_activity_remove_content": "¿Deseas eliminar esta actividad?", - "shared_album_activity_remove_title": "Eliminar Actividad", - "shared_album_section_people_action_error": "Error retirando/eliminando del album", - "shared_album_section_people_action_leave": "Eliminar usuario del álbum", - "shared_album_section_people_action_remove_user": "Eliminar usuario del álbum", - "shared_album_section_people_title": "PERSONAS", - "shared_by": "Compartido por", - "shared_by_user": "Compartido por {user}", - "shared_by_you": "Compartido por ti", - "shared_from_partner": "Fotos de {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Cargado(s)", - "shared_link_app_bar_title": "Enlaces compartidos", - "shared_link_clipboard_copied_massage": "Copiado al portapapeles", - "shared_link_clipboard_text": "Enlace: {link}\nContraseña: {password}", - "shared_link_create_error": "Error creando el enlace compartido", - "shared_link_custom_url_description": "Acceder a este enlace compartido con una URL personalizada", - "shared_link_edit_description_hint": "Introduce la descripción del enlace", - "shared_link_edit_expire_after_option_day": "1 día", - "shared_link_edit_expire_after_option_days": "{count} días", - "shared_link_edit_expire_after_option_hour": "1 hora", - "shared_link_edit_expire_after_option_hours": "{count} horas", - "shared_link_edit_expire_after_option_minute": "1 minuto", - "shared_link_edit_expire_after_option_minutes": "{count} minutos", - "shared_link_edit_expire_after_option_months": "{count} meses", - "shared_link_edit_expire_after_option_year": "{count} año", - "shared_link_edit_password_hint": "Introduce la contraseña del enlace", - "shared_link_edit_submit_button": "Actualizar enlace", - "shared_link_error_server_url_fetch": "No se puede obtener la url del servidor", - "shared_link_expires_day": "Caduca en {count} día", - "shared_link_expires_days": "Caduca en {count} días", - "shared_link_expires_hour": "Caduca en {count} hora", - "shared_link_expires_hours": "Caduca en {count} horas", - "shared_link_expires_minute": "Caduca en {count} minuto", - "shared_link_expires_minutes": "Caduca en {count} minutos", - "shared_link_expires_never": "Caduca ∞", - "shared_link_expires_second": "Caduca en {count} segundo", - "shared_link_expires_seconds": "Caduca en {count} segundos", - "shared_link_individual_shared": "Compartido individualmente", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Administrar enlaces compartidos", - "shared_link_options": "Opciones de enlaces compartidos", - "shared_link_password_description": "Requerir una contraseña para acceder a este enlace compartido", - "shared_links": "Enlaces compartidos", - "shared_links_description": "Comparte fotos y vídeos con un enlace", - "shared_photos_and_videos_count": "{assetCount, plural, other {# Fotos y vídeos compartidos.}}", - "shared_with_me": "Compartidos conmigo", - "shared_with_partner": "Compartido con {partner}", - "sharing": "Compartidos", - "sharing_enter_password": "Por favor, introduce la contraseña para ver esta página.", - "sharing_page_album": "Álbumes compartidos", - "sharing_page_description": "Crea álbumes compartidos para compartir fotos y vídeos con las personas de tu red.", - "sharing_page_empty_list": "LISTA VACIA", - "sharing_sidebar_description": "Muestra un enlace a \"Compartido\" en el menú lateral", - "sharing_silver_appbar_create_shared_album": "Crear un álbum compartido", - "sharing_silver_appbar_share_partner": "Compartir con compañero", - "shift_to_permanent_delete": "presiona ⇧ para eliminar permanentemente el archivo", - "show_album_options": "Mostrar opciones del álbum", - "show_albums": "Mostrar álbumes", - "show_all_people": "Mostrar todas las personas", - "show_and_hide_people": "Mostrar y ocultar personas", - "show_file_location": "Mostrar carpeta del archivo", - "show_gallery": "Ver galería", - "show_hidden_people": "Mostrar personas ocultas", - "show_in_timeline": "Mostrar en la línea de tiempo", - "show_in_timeline_setting_description": "Mostrar fotos y vídeos de este usuario en tu línea de tiempo", - "show_keyboard_shortcuts": "Mostrar atajos del teclado", - "show_metadata": "Mostrar metadatos", - "show_or_hide_info": "Mostrar u ocultar información", - "show_password": "Mostrar contraseña", - "show_person_options": "Mostrar opciones de la persona", - "show_progress_bar": "Mostrar barra de progreso", - "show_schema": "Mostrar esquema", - "show_search_options": "Mostrar opciones de búsqueda", - "show_shared_links": "Mostrar enlaces compartidos", - "show_slideshow_transition": "Mostrar la transición de las diapositivas", - "show_supporter_badge": "Insignia de colaborador", - "show_supporter_badge_description": "Mostrar una insignia de colaborador", - "show_text_recognition": "Mostrar reconocimiento de texto", - "show_text_search_menu": "Mostrar el menú de búsqueda", - "shuffle": "Modo aleatorio", - "sidebar": "Barra lateral", - "sidebar_display_description": "Muestra un enlace a la vista en la barra lateral", - "sign_out": "Salir", - "sign_up": "Registrarse", - "size": "Tamaño", - "skip_to_content": "Saltar al contenido", - "skip_to_folders": "Ir a las carpetas", - "skip_to_tags": "Ir a las etiquetas", - "slideshow": "Pase de diapositivas", - "slideshow_repeat": "Repetir presentación de diapositivas", - "slideshow_repeat_description": "Volver al inicio cuando finaliza la presentación de diapositivas", - "slideshow_settings": "Ajustes de diapositivas", - "sort_albums_by": "Ordenar álbumes por…", - "sort_created": "Fecha de creación", - "sort_items": "Número de archivos", - "sort_modified": "Fecha de modificación", - "sort_newest": "Foto más nueva", - "sort_oldest": "Foto más antigua", - "sort_people_by_similarity": "Ordenar personas por similitud", - "sort_recent": "Foto más reciente", - "sort_title": "Título", - "source": "Fuente", - "stack": "Apilar", - "stack_action_prompt": "{count} apilados", - "stack_duplicates": "Apilar duplicados", - "stack_select_one_photo": "Selecciona una imagen principal para la pila", - "stack_selected_photos": "Apilar fotos seleccionadas", - "stacked_assets_count": "Apilado(s) {count, plural, one {# activo} other {# activos}}", - "stacktrace": "Seguimiento de pila", - "start": "Inicio", - "start_date": "Fecha de inicio", - "start_date_before_end_date": "Fecha de inicio debe ser antes de fecha final", - "state": "Estado", - "status": "Estado", - "stop_casting": "Detener transmisión", - "stop_motion_photo": "Parar foto en movimiento", - "stop_photo_sharing": "¿Dejar de compartir tus fotos?", - "stop_photo_sharing_description": "{partner} ya no podrá acceder a tus fotos.", - "stop_sharing_photos_with_user": "Deja de compartir tus fotos con este usuario", - "storage": "Espacio de almacenamiento", - "storage_label": "Etiqueta de almacenamiento", - "storage_quota": "Cuota de almacenamiento", - "storage_usage": "{used} de {available} en uso", - "submit": "Enviar", - "success": "Éxito", - "suggestions": "Sugerencias", - "sunrise_on_the_beach": "Amanecer en la playa", - "support": "Soporte", - "support_and_feedback": "Soporte y comentarios", - "support_third_party_description": "Esta instalación de Immich fue empaquetada por un tercero. Los problemas actuales pueden ser ocasionados por ese paquete; por favor, discuta sus inconvenientes con el empaquetador antes de usar los enlaces de abajo.", - "swap_merge_direction": "Alternar dirección de mezcla", - "sync": "Sincronizar", - "sync_albums": "Sincronizar álbumes", - "sync_albums_manual_subtitle": "Sincroniza todos los videos y fotos subidos con los álbumes seleccionados a respaldar", - "sync_local": "Sincronización local", - "sync_remote": "Sincronización remota", - "sync_status": "Estado de la sincronización", - "sync_status_subtitle": "Ver y gestionar el estado de la sincronización", - "sync_upload_album_setting_subtitle": "Crea y sube tus fotos y videos a los álbumes seleccionados en Immich", - "tag": "Etiqueta", - "tag_assets": "Etiquetar activos", - "tag_created": "Etiqueta creada: {tag}", - "tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lógicas", - "tag_not_found_question": "¿No encuentra una etiqueta? Crea una nueva etiqueta.", - "tag_people": "Etiquetar personas", - "tag_updated": "Etiqueta actualizada: {tag}", - "tagged_assets": "Etiquetado(s) {count, plural, one {# activo} other {# activos}}", - "tags": "Etiquetas", - "tap_to_run_job": "Toca para ejecutar la tarea", - "template": "Plantilla", - "text_recognition": "Reconocimiento de texto", - "theme": "Tema", - "theme_selection": "Selección de tema", - "theme_selection_description": "Establece el tema automáticamente como \"claro\" u \"oscuro\" según las preferencias del sistema/navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamiento en las miniaturas de los archivos", - "theme_setting_asset_list_tiles_per_row_title": "Número de elementos por fila ({count})", - "theme_setting_colorful_interface_subtitle": "Aplicar el color primario a las superficies de fondo.", - "theme_setting_colorful_interface_title": "Color de Interfaz", - "theme_setting_image_viewer_quality_subtitle": "Ajustar la calidad del visor de detalles de imágenes", - "theme_setting_image_viewer_quality_title": "Calidad del visor de imágenes", - "theme_setting_primary_color_subtitle": "Elige un color para las acciones principales y los acentos.", - "theme_setting_primary_color_title": "Color primario", - "theme_setting_system_primary_color_title": "Usar color del sistema", - "theme_setting_system_theme_switch": "Automático (seguir ajuste del sistema)", - "theme_setting_theme_subtitle": "Elige la configuración del tema de la aplicación", - "theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor", - "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", - "then": "Entonces", - "they_will_be_merged_together": "Se fusionarán entre sí", - "third_party_resources": "Recursos de terceros", - "time": "Tiempo", - "time_based_memories": "Recuerdos basados en tiempo", - "time_based_memories_duration": "Cantidad de segundos que se mostrará cada imagen.", - "timeline": "Cronología", - "timezone": "Zona horaria", - "to_archive": "Archivar", - "to_change_password": "Cambiar contraseña", - "to_favorite": "A los favoritos", - "to_login": "Iniciar Sesión", - "to_multi_select": "para multi selección", - "to_parent": "Ir a los padres", - "to_select": "para seleccionar", - "to_trash": "Descartar", - "toggle_settings": "Alternar ajustes", - "toggle_theme_description": "Cambiar tema", - "total": "Total", - "total_usage": "Uso total", - "trash": "Papelera", - "trash_action_prompt": "{count} movidos a la papelera", - "trash_all": "Descartar todo", - "trash_count": "Descartar {count, number}", - "trash_delete_asset": "Borrar/Eliminar archivo", - "trash_emptied": "Papelera vaciada", - "trash_no_results_message": "Las fotos y videos que se envíen a la papelera aparecerán aquí.", - "trash_page_delete_all": "Eliminar todos", - "trash_page_empty_trash_dialog_content": "¿Está seguro que quiere eliminar los elementos? Estos elementos serán eliminados de Immich permanentemente", - "trash_page_info": "Los archivos en la papelera serán eliminados automáticamente de forma permanente después de {days} días", - "trash_page_no_assets": "No hay elementos en la papelera", - "trash_page_restore_all": "Restaurar todos", - "trash_page_select_assets_btn": "Seleccionar elementos", - "trash_page_title": "Papelera ({count})", - "trashed_items_will_be_permanently_deleted_after": "Los elementos en la papelera serán eliminados permanentemente tras {days, plural, one {# día} other {# días}}.", - "trigger": "Disparador", - "trigger_asset_uploaded": "Activo subido", - "trigger_asset_uploaded_description": "Se activa cuando se carga un nuevo activo", - "trigger_description": "Un evento que inicia el flujo de trabajo", - "trigger_person_recognized": "Persona reconocida", - "trigger_person_recognized_description": "Se activa cuando se detecta una persona", - "trigger_type": "Tipo de disparador", - "troubleshoot": "Solucionar problemas", - "type": "Tipo", - "unable_to_change_pin_code": "No se ha podido cambiar el PIN", - "unable_to_check_version": "No se puede comprobar la versión de la aplicación o del servidor", - "unable_to_setup_pin_code": "No se ha podido establecer el PIN", - "unarchive": "Desarchivar", - "unarchive_action_prompt": "{count} eliminados del archivo", - "unarchived_count": "{count, plural, one {# No archivado} other {# No archivados}}", - "undo": "Deshacer", - "unfavorite": "Retirar favorito", - "unfavorite_action_prompt": "{count} eliminados de favoritos", - "unhide_person": "Mostrar persona", - "unknown": "Desconocido", - "unknown_country": "País desconocido", - "unknown_date": "Fecha desconocida", - "unknown_year": "Año desconocido", - "unlimited": "Sin límites", - "unlink_motion_video": "Desvincular vídeo en movimiento", - "unlink_oauth": "Desvincular OAuth", - "unlinked_oauth_account": "Cuenta OAuth desconectada", - "unmute_memories": "Habilitar sonido recuerdos", - "unnamed_album": "Album sin nombre", - "unnamed_album_delete_confirmation": "¿Seguro que quieres borrar este álbum?", - "unnamed_share": "Compartido sin nombre", - "unsaved_change": "Cambio no guardado", - "unselect_all": "Limpiar selección", - "unselect_all_duplicates": "Deseleccionar todos los duplicados", - "unselect_all_in": "Deselecciona todos en {group}", - "unstack": "Desapilar", - "unstack_action_prompt": "{count} desapilado(s)", - "unstacked_assets_count": "Desapilado(s) {count, plural, one {# elemento} other {# elementos}}", - "unsupported_field_type": "Tipo de campo no soportado", - "untagged": "Sin etiqueta", - "untitled_workflow": "Flujo de trabajo sin título", - "up_next": "A continuación", - "update_location_action_prompt": "Actualiza la ubicación de {count} assets seleccionados con:", - "updated_at": "Actualizado", - "updated_password": "Contraseña actualizada", - "upload": "Subir", - "upload_concurrency": "Subidas simultáneas", - "upload_details": "Cargar Detalles", - "upload_dialog_info": "¿Quieres hacer una copia de seguridad al servidor de los elementos seleccionados?", - "upload_dialog_title": "Subir elementos", - "upload_error_with_count": "Error al cargar {count, plural, one {# asset} other {# assets}}", - "upload_errors": "Subida completada con {count, plural, one {# error} other {# errores}}, actualice la página para ver los nuevos recursos de la subida.", - "upload_finished": "Carga finalizada", - "upload_progress": "Restante {remaining, number} - Procesado {processed, number}/{total, number}", - "upload_skipped_duplicates": "Saltado {count, plural, one {# duplicate asset} other {# duplicate assets}}", - "upload_status_duplicates": "Duplicados", - "upload_status_errors": "Errores", - "upload_status_uploaded": "Subido", - "upload_success": "Subida realizada correctamente, actualice la página para ver los nuevos recursos de subida.", - "upload_to_immich": "Subir a Immich ({count})", - "uploading": "Subiendo", - "uploading_media": "Subiendo medios", - "url": "URL", - "usage": "Uso", - "use_biometric": "Uso biométrico", - "use_current_connection": "Utilice la conexión actual", - "use_custom_date_range": "Usa un intervalo de fechas personalizado", - "user": "Usuario", - "user_has_been_deleted": "Este usuario ha sido eliminado.", - "user_id": "Id. de usuario", - "user_liked": "{user} le gustó {type, select, photo {this photo} video {this video} asset {this asset} other {it}}", - "user_pin_code_settings": "PIN", - "user_pin_code_settings_description": "Gestione su PIN", - "user_privacy": "Privacidad del Usuario", - "user_purchase_settings": "Compra", - "user_purchase_settings_description": "Gestiona tu compra", - "user_role_set": "Establecer {user} como {role}", - "user_usage_detail": "Detalle del uso del usuario", - "user_usage_stats": "Estadísticas de uso de la cuenta", - "user_usage_stats_description": "Ver estadísticas de uso de la cuenta", - "username": "Nombre de usuario", - "users": "Usuarios", - "users_added_to_album_count": "{count, plural, one {# usuario añadido} other {# usuarios añadidos}} al álbum", - "utilities": "Utilidades", - "validate": "Validar", - "validate_endpoint_error": "Por favor, introduce una URL válida", - "validation_error": "Error de validación", - "variables": "Variables", - "version": "Versión", - "version_announcement_closing": "Tu amigo, Alex", - "version_announcement_message": "¡Hola! Hay una nueva versión de Immich disponible. Tómese un tiempo para leer las notas de la versión para asegurarse de que su configuración esté actualizada y evitar errores de configuración, especialmente si utiliza WatchTower o cualquier mecanismo que se encargue de actualizar su instancia de Immich automáticamente.", - "version_history": "Historial de versiones", - "version_history_item": "Instalada {version} el {date}", - "video": "Vídeo", - "video_hover_setting": "Iniciar vídeo al pasar por encima", - "video_hover_setting_description": "Reproducir el vídeo cuando el ratón está encima de un vídeo. Aunque esté desactivado, se iniciará cuando el cursor del ratón esté sobre el icono de \"reproducir\".", - "videos": "Vídeos", - "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", - "videos_only": "Solo vídeos", - "view": "Ver", - "view_album": "Ver Álbum", - "view_all": "Ver todas", - "view_all_users": "Mostrar todos los usuarios", - "view_asset_owners": "Ver propietarios", - "view_details": "Ver Detalles", - "view_in_timeline": "Ver en la línea de tiempo", - "view_link": "Ver enlace", - "view_links": "Mostrar enlaces", - "view_name": "Ver", - "view_next_asset": "Mostrar siguiente elemento", - "view_previous_asset": "Mostrar elemento anterior", - "view_qr_code": "Ver código QR", - "view_similar_photos": "Ver fotografías similares", - "view_stack": "Ver Pila", - "view_user": "Ver Usuario", - "viewer_remove_from_stack": "Quitar de la pila", - "viewer_stack_use_as_main_asset": "Usar como elemento principal", - "viewer_unstack": "Desapilar", - "visibility_changed": "Visibilidad cambiada para {count, plural, one {# persona} other {# personas}}", - "visual": "Visual", - "visual_builder": "Constructor visual", - "waiting": "Esperando", - "waiting_count": "Esperando: {count}", - "warning": "Advertencia", - "week": "Semana", - "welcome": "Bienvenido", - "welcome_to_immich": "Bienvenido a Immich", - "width": "Ancho", - "wifi_name": "Nombre Wi-Fi", - "workflow_delete_prompt": "¿Estás seguro de que quieres eliminar este flujo de trabajo?", - "workflow_deleted": "Flujo de trabajo eliminado", - "workflow_description": "Descripción del flujo de trabajo", - "workflow_info": "Información del flujo de trabajo", - "workflow_json": "JSON del flujo de trabajo", - "workflow_json_help": "Edite la configuración del flujo de trabajo en formato JSON. Los cambios se sincronizarán con el generador visual.", - "workflow_name": "Nombre del flujo de trabajo", - "workflow_navigation_prompt": "¿Estás seguro que deseas salir sin guardar los cambios?", - "workflow_summary": "Resumen del flujo de trabajo", - "workflow_update_success": "Flujo de trabajo actualizado con éxito", - "workflow_updated": "Flujo de trabajo actualizado", - "workflows": "Flujos de trabajo", - "workflows_help_text": "Los flujos de trabajo automatizan acciones en sus activos según activadores y filtros", - "wrong_pin_code": "Código PIN incorrecto", - "year": "Año", - "years_ago": "Hace {years, plural, one {# año} other {# años}}", - "yes": "Sí", - "you_dont_have_any_shared_links": "No tienes ningún enlace compartido", - "your_wifi_name": "El nombre de tu Wi-Fi", - "zero_to_clear_rating": "presione 0 para borrar la calificación del activo", - "zoom_image": "Acercar Imagen", - "zoom_to_bounds": "Ajustar a los límites" -} +{} diff --git a/i18n/et.json b/i18n/et.json index 61d4e5b949..0967ef424b 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -1,2405 +1 @@ -{ - "about": "Teave", - "account": "Konto", - "account_settings": "Konto seaded", - "acknowledge": "Sain aru", - "action": "Tegevus", - "action_common_update": "Uuenda", - "action_description": "Komplekt tegevusi, mida teostada filtreeritud üksustega", - "actions": "Tegevused", - "active": "Aktiivne", - "active_count": "Aktiivsed: {count}", - "activity": "Aktiivsus", - "activity_changed": "Aktiivsus on {enabled, select, true {lubatud} other {keelatud}}", - "add": "Lisa", - "add_a_description": "Lisa kirjeldus", - "add_a_location": "Lisa asukoht", - "add_a_name": "Lisa nimi", - "add_a_title": "Lisa pealkiri", - "add_action": "Lisa tegevus", - "add_action_description": "Klõpsa, et lisada teostatav tegevus", - "add_assets": "Lisa üksuseid", - "add_birthday": "Lisa sünnipäev", - "add_endpoint": "Lisa lõpp-punkt", - "add_exclusion_pattern": "Lisa välistamismuster", - "add_filter": "Lisa filter", - "add_filter_description": "Klõpsa, et lisada filtreerimistingimus", - "add_location": "Lisa asukoht", - "add_more_users": "Lisa rohkem kasutajaid", - "add_partner": "Lisa partner", - "add_path": "Lisa tee", - "add_photos": "Lisa fotosid", - "add_tag": "Lisa silt", - "add_to": "Lisa kohta…", - "add_to_album": "Lisa albumisse", - "add_to_album_bottom_sheet_added": "Lisatud albumisse {album}", - "add_to_album_bottom_sheet_already_exists": "On juba albumis {album}", - "add_to_album_bottom_sheet_some_local_assets": "Kõiki lokaalseid üksuseid ei õnnestunud albumisse lisada", - "add_to_album_toggle": "Muuda albumi {album} valikut", - "add_to_albums": "Lisa albumitesse", - "add_to_albums_count": "Lisa albumitesse ({count})", - "add_to_bottom_bar": "Lisa", - "add_to_shared_album": "Lisa jagatud albumisse", - "add_upload_to_stack": "Virnasta üleslaaditud üksus", - "add_url": "Lisa URL", - "add_workflow_step": "Lisa töövoo samm", - "added_to_archive": "Lisatud arhiivi", - "added_to_favorites": "Lisatud lemmikutesse", - "added_to_favorites_count": "{count, number} pilti lisatud lemmikutesse", - "admin": { - "add_exclusion_pattern_description": "Lisa välistamismustreid. Toetatud on metamärgid *, ** ja ?. Kõikide kataloogis nimega \"Raw\" olevate failide ignoreerimiseks kasuta \"**/Raw/**\". Kõikide \".tif\" lõpuga failide ignoreerimiseks kasuta \"**/*.tif\". Absouutse tee ignoreerimiseks kasuta \"/tee/mida/ignoreerida/**\".", - "admin_user": "Administraator", - "asset_offline_description": "Seda välise kogu üksust ei leitud kettalt ning see liigutati prügikasti. Kui faili asukoht muutus kogu siseselt, leiad vastava uue üksuse oma ajajoonelt. Üksuse taastamiseks veendu, et allpool toodud failitee on Immich'ile kättesaadav ning skaneeri kogu uuesti.", - "authentication_settings": "Autentimise seaded", - "authentication_settings_description": "Halda parooli, OAuth'i ja muid autentimise seadeid", - "authentication_settings_disable_all": "Kas oled kindel, et soovid kõik sisselogimismeetodid välja lülitada? Sisselogimine lülitatakse täielikult välja.", - "authentication_settings_reenable": "Et taas lubada, kasuta serveri käsku.", - "background_task_job": "Tausttegumid", - "backup_database": "Loo andmebaasi tõmmis", - "backup_database_enable_description": "Luba andmebaasi tõmmised", - "backup_keep_last_amount": "Eelmiste tõmmiste arv, mida alles hoida", - "backup_onboarding_1_description": "asukohaväline koopia pilves või teises füüsilises asukohas.", - "backup_onboarding_2_description": "lokaalset koopiat erinevatel seadmetel. See hõlmab põhifaile ja nende failide lokaalsed varundust.", - "backup_onboarding_3_description": "koopiat su andmetest, kaasa arvatud originaalfailid. See hõlmab üht asukohavälist ja kaht lokaalset koopiat.", - "backup_onboarding_description": "Andmete kaitsmiseks on soovituslik 3-2-1 varundusstrateegia. Põhjaliku varunduse jaoks peaksid talletama koopiaid nii oma üleslaaditud fotodest ja videotest kui ka Immich'i andmebaasist.", - "backup_onboarding_footer": "Rohkem informatsiooni Immich'i varundamise kohta leiad dokumentatsioonist.", - "backup_onboarding_parts_title": "3-2-1 varundus hõlmab:", - "backup_onboarding_title": "Varukoopiad", - "backup_settings": "Andmebaasi tõmmiste seaded", - "backup_settings_description": "Halda andmebaasi tõmmiste seadeid.", - "cleared_jobs": "Tööted eemaldatud: {job}", - "config_set_by_file": "Konfiguratsioon on määratud konfiguratsioonifaili abil", - "confirm_delete_library": "Kas oled kindel, et soovid kustutada {library} kogu?", - "confirm_delete_library_assets": "Kas oled kindel, et soovid selle kogu kustutada? Sellega kustutatakse {count, plural, one {# sisalduv üksus} other {kõik # sisalduvat üksust}} Immich'ist ning seda tegevust ei saa tagasi võtta. Failid jäävad kettale alles.", - "confirm_email_below": "Kinnitamiseks sisesta allpool \"{email}\"", - "confirm_reprocess_all_faces": "Kas oled kindel, et soovid kõik näod uuesti töödelda? See eemaldab kõik nimega isikud.", - "confirm_user_password_reset": "Kas oled kindel, et soovid kasutaja {user} parooli lähtestada?", - "confirm_user_pin_code_reset": "Kas oled kindel, et soovid kasutaja {user} PIN-koodi lähtestada?", - "copy_config_to_clipboard_description": "Kopeeri praegune süsteemi seadistus JSON-objektina lõikelauale", - "create_job": "Lisa tööde", - "cron_expression": "Cron avaldis", - "cron_expression_description": "Määra skaneerimise intervall cron formaadis. Rohkema info jaoks vaata nt. Crontab Guru", - "cron_expression_presets": "Eelseadistatud cron avaldised", - "disable_login": "Keela sisselogimine", - "duplicate_detection_job_description": "Rakenda üksustele masinõpet, et leida sarnaseid pilte. Kasutab nutiotsingut", - "exclusion_pattern_description": "Välistamismustrid võimaldavad ignoreerida faile ja kaustu selle kogu skaneerimisel. See on kasulik, kui sul on kaustu, mis sisaldavad faile, mida sa ei soovi importida, nagu RAW failid.", - "export_config_as_json_description": "Laadi praegune süsteemi seadistus JSON-failina alla", - "external_libraries_page_description": "Väliste kogude haldamise leht", - "face_detection": "Näoavastus", - "face_detection_description": "Avasta üksustest nägusid masinõppe abil. Videote puhul kasutatakse ainult pisipilti. \"Värskenda\" töötleb kõik üksused uuesti. \"Lähtesta\" kustutab lisaks kõik seni leitud näod. \"Puuduvad\" võtab ette üksused, mida pole veel töödeldud. Avastatud näod suunatakse näotuvastusse, et grupeerida nad olemasolevateks või uuteks isikuteks.", - "facial_recognition_job_description": "Grupeeri avastatud näod inimesteks. See samm käivitub siis, kui näoavastus on lõppenud. \"Lähtesta\" grupeerib kõik näod uuesti. \"Puuduvad\" võtab ette näod, mida pole isikuga seostatud.", - "failed_job_command": "Käsk {command} ebaõnnestus töötes: {job}", - "force_delete_user_warning": "HOIATUS: See kustutab koheselt kasutaja ja kõik tema üksused. Tegevust ei saa tagasi võtta ja faile ei saa taastada.", - "image_format": "Formaat", - "image_format_description": "WebP failid on väiksemad kui JPEG, aga kodeerimine on aeglasem.", - "image_fullsize_description": "Täismõõdus pilt ilma metaandmeteta, kasutatakse sisse suumimisel", - "image_fullsize_enabled": "Luba täismõõdus piltide genereerimine", - "image_fullsize_enabled_description": "Genereeri mitte-veebisõbralike formaatide jaoks täismõõdus pilt. Kui \"Eelista manustatud eelvaadet\" on lubatud, kasutatakse manustatud eelvaateid otse ilma teisendamiseta. Ei mõjuta veebisõbralikke formaate nagu JPEG.", - "image_fullsize_quality_description": "Täismõõdus pildi kvaliteet vahemikus 1-100. Kõrgem väärtus on parem, aga tulemuseks on suuremad failid.", - "image_fullsize_title": "Täismõõdus pildi seaded", - "image_prefer_embedded_preview": "Eelista manustatud eelvaadet", - "image_prefer_embedded_preview_setting_description": "Kasuta pilditöötluse sisendina võimalusel RAW fotodesse manustatud eelvaateid. See võib mõnede piltide puhul anda tulemuseks täpsemad värvid, aga eelvaate kvaliteet sõltub konkreetsest kaamerast ning pildis võib olla rohkem tihendusmüra.", - "image_prefer_wide_gamut": "Eelista laia värvigammat", - "image_prefer_wide_gamut_setting_description": "Kasuta pisipiltide jaoks Display P3. See säilitab paremini laia värviruumiga piltide erksuse, kuid vanematel seadmetel ja vanemate brauseritega võivad pildid teistsugused välja näha. sRGB pildid säilitatakse värvinihete vältimiseks.", - "image_preview_description": "Keskmise suurusega pilt ilma metaandmeteta, kasutusel üksiku üksuse vaatamise ja masinõppe jaoks", - "image_preview_quality_description": "Eelvaate kvaliteet vahemikus 1-100. Kõrgem väärtus on parem, aga tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust. Madal väärtus võib mõjutada masinõppe kvaliteeti.", - "image_preview_title": "Eelvaate seaded", - "image_progressive": "Progressiivne", - "image_progressive_description": "Kodeeri JPEG-pildid järk-järguliseks laadimiseks. See ei mõjuta WebP-pilte.", - "image_quality": "Kvaliteet", - "image_resolution": "Resolutsioon", - "image_resolution_description": "Kõrgemad resolutsioonid säilitavad rohkem detaile, aga kodeerimine võtab kauem aega, tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust.", - "image_settings": "Pildi seaded", - "image_settings_description": "Halda genereeritud piltide kvaliteeti ja resolutsiooni", - "image_thumbnail_description": "Väike pisipilt ilma metaandmeteta, kasutusel fotode grupikaupa vaatamisel, näiteks ajajoonel", - "image_thumbnail_quality_description": "Pisipildi kvaliteet vahemikus 1-100. Kõrgem väärtus on parem, aga tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust.", - "image_thumbnail_title": "Pisipildi seaded", - "import_config_from_json_description": "Impordi süsteemi seadistus JSON-faili üleslaadimise teel", - "job_concurrency": "{job} samaaegsus", - "job_created": "Tööde lisatud", - "job_not_concurrency_safe": "Seda töödet pole ohutu samaaegselt käivitada.", - "job_settings": "Töödete seaded", - "job_settings_description": "Halda töödete samaaegsust", - "jobs_delayed": "{jobCount, plural, other {# edasi lükatud}}", - "jobs_failed": "{jobCount, plural, other {# ebaõnnestus}}", - "jobs_over_time": "Tööted aja jooksul", - "library_created": "Lisatud kogu: {library}", - "library_deleted": "Kogu kustutatud", - "library_details": "Kogu üksikasjad", - "library_folder_description": "Vali kaust, mida importida. Sellest kaustast ja alamkaustadest otsitakse pilte ja videosid.", - "library_remove_exclusion_pattern_prompt": "Kas oled kindel, et soovid selle välistamismustri eemaldada?", - "library_remove_folder_prompt": "Kas oled kindel, et soovid selle impordikausta eemaldada?", - "library_scanning": "Perioodiline skaneerimine", - "library_scanning_description": "Seadista kogu perioodiline skaneerimine", - "library_scanning_enable_description": "Luba kogu perioodiline skaneerimine", - "library_settings": "Väline kogu", - "library_settings_description": "Halda välise kogu seadeid", - "library_tasks_description": "Otsi välistest kogudest uusi ja muutunud üksuseid", - "library_updated": "Kogu uuendatud", - "library_watching_enable_description": "Jälgi välises kogus failide muudatusi", - "library_watching_settings": "Kogu jälgimine [EKSPERIMENTAALNE]", - "library_watching_settings_description": "Jälgi automaatselt muutunud faile", - "logging_enable_description": "Luba logimine", - "logging_level_description": "Kui lubatud, millist logimistaset kasutada.", - "logging_settings": "Logimine", - "machine_learning_availability_checks": "Saadavuskontrollid", - "machine_learning_availability_checks_description": "Tuvasta ja eelista automaatselt saadavalolevaid masinõppeservereid", - "machine_learning_availability_checks_enabled": "Luba saadavuskontrollid", - "machine_learning_availability_checks_interval": "Kontrolli intervall", - "machine_learning_availability_checks_interval_description": "Saadavuskontrollide intervall millisekundites", - "machine_learning_availability_checks_timeout": "Päringu ajalõpp", - "machine_learning_availability_checks_timeout_description": "Saadavuskontrollide ajalõpp millisekundites", - "machine_learning_clip_model": "CLIP mudel", - "machine_learning_clip_model_description": "CLIP mudeli nimi, mis on loetletud siin. Pane tähele, et mudeli muutmisel pead kõigi piltide peal nutiotsingu tööte uuesti käivitama.", - "machine_learning_duplicate_detection": "Duplikaatide leidmine", - "machine_learning_duplicate_detection_enabled": "Luba duplikaatide leidmine", - "machine_learning_duplicate_detection_enabled_description": "Kui keelatud, dedubleeritakse siiski täpselt identsed üksused.", - "machine_learning_duplicate_detection_setting_description": "Kasuta CLIP-manuseid, et leida tõenäoliseid duplikaate", - "machine_learning_enabled": "Luba masinõpe", - "machine_learning_enabled_description": "Kui keelatud, lülitatakse kõik masinõppe funktsioonid välja, sõltumata allolevatest seadetest.", - "machine_learning_facial_recognition": "Näotuvastus", - "machine_learning_facial_recognition_description": "Avasta, tuvasta ja grupeeri piltidel näod", - "machine_learning_facial_recognition_model": "Näotuvastuse mudel", - "machine_learning_facial_recognition_model_description": "Mudelid on järjestatud suuruse järgi kahanevalt. Suuremad mudelid on aeglasemad ja kasutavad rohkem mälu, kuid annavad parema tulemuse. Mudeli muutmisel tuleb näoavastuse tööde kõigi piltide peal uuesti käivitada.", - "machine_learning_facial_recognition_setting": "Luba näotuvastus", - "machine_learning_facial_recognition_setting_description": "Kui keelatud, siis ei kodeerita pilte näotuvastuse jaoks ning isikute sektsioon Avasta lehel jääb tühjaks.", - "machine_learning_max_detection_distance": "Maksimaalne avastuskaugus", - "machine_learning_max_detection_distance_description": "Maksimaalne kaugus kahe pildi vahel, mille puhul loetakse nad duplikaatideks, vahemikus 0.001-0.1. Kõrgemad väärtused leiavad rohkem duplikaate, aga võib esineda valepositiivseid.", - "machine_learning_max_recognition_distance": "Maksimaalne tuvastuskaugus", - "machine_learning_max_recognition_distance_description": "Maksimaalne kaugus kahe näo vahel, mida tuleks lugeda samaks isikuks, vahemikus 0-2. Selle vähendamine aitab vältida erinevate inimeste samaks isikuks märkimist ja tõstmine aitab vältida sama inimese kaheks erinevaks isikuks märkimist. Pane tähele, et kaht isikut ühendada on lihtsam kui üht isikut kaheks eraldada, seega võimalusel kasuta madalamat lävendit.", - "machine_learning_min_detection_score": "Minimaalne avastusskoor", - "machine_learning_min_detection_score_description": "Minimaalne usaldusskoor näo avastamiseks, vahemikus 0-1. Madalamad väärtused leiavad rohkem nägusid, kuid võib esineda valepositiivseid.", - "machine_learning_min_recognized_faces": "Minimaalne tuvastatud nägude arv", - "machine_learning_min_recognized_faces_description": "Minimaalne tuvastatud nägude arv, mida saab isikuks grupeerida. Selle suurendamine teeb näotuvastuse täpsemaks, kuid suureneb tõenäosus, et nägu ei seostata ühegi isikuga.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Kasuta piltidelt teksti tuvastamiseks masinõpet", - "machine_learning_ocr_enabled": "Luba OCR", - "machine_learning_ocr_enabled_description": "Kui keelatud, ei rakendata piltidele tekstituvastust.", - "machine_learning_ocr_max_resolution": "Maksimaalne resolutsioon", - "machine_learning_ocr_max_resolution_description": "Eelvaated üle selle resolutsiooni vähendatakse, säilitades külgede suhte. Suuremad väärtused on täpsemad, aga töötlemine võtab kauem aega ja kasutab rohkem mälu.", - "machine_learning_ocr_min_detection_score": "Minimaalne avastusskoor", - "machine_learning_ocr_min_detection_score_description": "Minimaalne usaldusskoor teksti avastamiseks, vahemikus 0-1. Madalamad väärtused leiavad rohkem teksti, kuid võib esineda valepositiivseid.", - "machine_learning_ocr_min_recognition_score": "Minimaalne tuvastusskoor", - "machine_learning_ocr_min_score_recognition_description": "Minimaalne usaldusskoor avastatud teksti tuvastamiseks, vahemikus 0-1. Madalamad väärtused tuvastavad rohkem teksti, kuid võib esineda valepositiivseid.", - "machine_learning_ocr_model": "OCR mudel", - "machine_learning_ocr_model_description": "Serverimudelid on täpsemad kui mobiilsed mudelid, aga töötlemine võtab rohkem aega ja kasutab rohkem mälu.", - "machine_learning_settings": "Masinõppe seaded", - "machine_learning_settings_description": "Halda masinõppe funktsioone ja seadeid", - "machine_learning_smart_search": "Nutiotsing", - "machine_learning_smart_search_description": "Otsi pilte semantiliselt CLIP-manuste abil", - "machine_learning_smart_search_enabled": "Luba nutiotsing", - "machine_learning_smart_search_enabled_description": "Kui keelatud, siis ei kodeerita pilte nutiotsingu jaoks.", - "machine_learning_url_description": "Masinõppe serveri URL. Kui ette on antud rohkem kui üks URL, proovitakse neid järjest ükshaaval, kuni üks edukalt vastab. Servereid, mis ei vasta, ignoreeritakse ajutiselt, kuni ühendus taastub.", - "maintenance_delete_backup": "Kustuta varukoopia", - "maintenance_delete_backup_description": "See fail kustutatakse jäädavalt.", - "maintenance_delete_error": "Varukoopia kustutamine ebaõnnestus.", - "maintenance_restore_backup": "Taasta varukoopia", - "maintenance_restore_backup_description": "Immich lähtestatakse ning taastatakse valitud varukoopiast. Enne jätkamist tehakse uus varukoopia.", - "maintenance_restore_backup_different_version": "See varukoopia loodi erineva Immich'i versiooniga!", - "maintenance_restore_backup_unknown_version": "Varukoopia versiooni tuvastamine ebaõnnestus.", - "maintenance_restore_database_backup": "Taasta andmebaasi varukoopia", - "maintenance_restore_database_backup_description": "Pööra andmebaas tagasi varasemasse seisu varukoopia faili abil", - "maintenance_settings": "Hooldus", - "maintenance_settings_description": "Pane Immich hooldusrežiimi.", - "maintenance_start": "Lülitu hooldusrežiimi", - "maintenance_start_error": "Hooldusrežiimi käivitamine ebaõnnestus.", - "maintenance_upload_backup": "Laadi andmebaasi varukoopia fail üles", - "maintenance_upload_backup_error": "Varukoopia üleslaadimine ebaõnnestus. Kas see on .sql või .sql.gz fail?", - "manage_concurrency": "Halda samaaegsust", - "manage_concurrency_description": "Töödete samaaegsuse haldamiseks mine töödete lehele", - "manage_log_settings": "Halda logi seadeid", - "map_dark_style": "Tume stiil", - "map_enable_description": "Luba kaardi funktsioonid", - "map_gps_settings": "Kaardi ja GPS-i seaded", - "map_gps_settings_description": "Halda kaardi ja GPS-i (pöördgeokodeerimise) seadeid", - "map_implications": "Kaardifunktsioon kasutab välist kaarditeenust (tiles.immich.cloud)", - "map_light_style": "Hele stiil", - "map_manage_reverse_geocoding_settings": "Halda pöördgeokodeerimise seadeid", - "map_reverse_geocoding": "Pöördgeokodeerimine", - "map_reverse_geocoding_enable_description": "Luba pöördgeokodeerimine", - "map_reverse_geocoding_settings": "Pöördgeokodeerimise seaded", - "map_settings": "Kaart", - "map_settings_description": "Halda kaardi seadeid", - "map_style_description": "Kaarditeema style.json URL", - "memory_cleanup_job": "Mälestuste korrastamine", - "memory_generate_job": "Mälestuste genereerimine", - "metadata_extraction_job": "Metaandmete eraldamine", - "metadata_extraction_job_description": "Eralda igast üksusest metaandmed, nagu GPS-koordinaadid, näod ja resolutsioon", - "metadata_faces_import_setting": "Luba nägude import", - "metadata_faces_import_setting_description": "Impordi näod piltide EXIF andmetest ja välistest failidest", - "metadata_settings": "Metaandmete seaded", - "metadata_settings_description": "Halda metaandmete seadeid", - "migration_job": "Migratsioon", - "migration_job_description": "Migreeri üksuste ja nägude pisipildid uusimale kaustastruktuurile", - "nightly_tasks_cluster_faces_setting_description": "Käivita värskelt avastatud nägudel näotuvastus", - "nightly_tasks_cluster_new_faces_setting": "Grupeeri uued näod", - "nightly_tasks_database_cleanup_setting": "Andmebaasi puhastuse tegumid", - "nightly_tasks_database_cleanup_setting_description": "Eemalda andmebaasist vanad, aegunud andmed", - "nightly_tasks_generate_memories_setting": "Genereeri mälestused", - "nightly_tasks_generate_memories_setting_description": "Loo üksustest uued mälestused", - "nightly_tasks_missing_thumbnails_setting": "Genereeri puuduvad pisipildid", - "nightly_tasks_missing_thumbnails_setting_description": "Suuna ilma pisipiltideta üksused pisipiltide genereerimisele", - "nightly_tasks_settings": "Öiste tegumite seaded", - "nightly_tasks_settings_description": "Halda öiseid tegumeid", - "nightly_tasks_start_time_setting": "Algusaeg", - "nightly_tasks_start_time_setting_description": "Aeg, millal server alustab öiste tegumite käivitamist", - "nightly_tasks_sync_quota_usage_setting": "Sünkrooni kvoodikasutus", - "nightly_tasks_sync_quota_usage_setting_description": "Uuenda kasutaja talletuskvoot jooksva kasutuse alusel", - "no_paths_added": "Ühtegi teed pole", - "no_pattern_added": "Mustreid ei ole", - "note_apply_storage_label_previous_assets": "Märkus: Et rakendada talletussilt varem üleslaaditud üksustele, käivita", - "note_cannot_be_changed_later": "MÄRKUS: Seda ei saa hiljem muuta!", - "notification_email_from_address": "Saatja aadress", - "notification_email_from_address_description": "Saatja e-posti aadress, näiteks: \"Immich Photo Server \". Kasuta kindlasti aadressi, millelt sul on luba e-kirju saata.", - "notification_email_host_description": "E-posti serveri host (nt. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignoreeri sertifikaadi vigu", - "notification_email_ignore_certificate_errors_description": "Ignoreeri TLS sertifikaadi valideerimise vigu (mittesoovituslik)", - "notification_email_password_description": "Parool e-posti serveriga autentimiseks", - "notification_email_port_description": "E-posti serveri port (nt. 25, 465 või 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Kasuta SMTPS-i (SMTP üle TLS-i)", - "notification_email_sent_test_email_button": "Saada test e-kiri ja salvesta", - "notification_email_setting_description": "E-posti teel teavituste saatmise seaded", - "notification_email_test_email": "Saada test e-kiri", - "notification_email_test_email_failed": "Test e-kirja saatmine ebaõnnestus, kontrolli seadistust", - "notification_email_test_email_sent": "Test e-kiri saadeti aadressile {email}. Kontrolli oma kirjakasti.", - "notification_email_username_description": "Kasutajanimi e-posti serveriga autentimiseks", - "notification_enable_email_notifications": "Luba e-posti teel teavitused", - "notification_settings": "Teavituse seaded", - "notification_settings_description": "Halda teavituste seadeid, sh. e-posti teel", - "oauth_auto_launch": "Automaatne käivitamine", - "oauth_auto_launch_description": "Alusta OAuth autentimist automaatselt sisselogimise lehele jõudmisel", - "oauth_auto_register": "Automaatne registreerimine", - "oauth_auto_register_description": "Registreeri uued kasutajad automaatselt OAuth abil sisselogimisel", - "oauth_button_text": "Nupu tekst", - "oauth_client_secret_description": "Nõutud konfidentsiaalse kliendi jaoks, või avaliku kliendi jaoks, kui PKCE (Proof Key for Code Exchange) ei ole toetatud.", - "oauth_enable_description": "Sisene OAuth abil", - "oauth_mobile_redirect_uri": "Mobiilne ümbersuunamise URI", - "oauth_mobile_redirect_uri_override": "Mobiilse ümbersuunamise URI ülekirjutamine", - "oauth_mobile_redirect_uri_override_description": "Lülita sisse, kui OAuth pakkuja ei luba mobiilset URI-d, näiteks ''{callback}''", - "oauth_role_claim": "Rolli väide", - "oauth_role_claim_description": "Anna selle väite olemasolul automaatselt administraatori ligipääs. Väite väärtus võib olla 'user' või 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Halda OAuth sisselogimise seadeid", - "oauth_settings_more_details": "Selle funktsiooni kohta rohkem teada saamiseks loe dokumentatsiooni.", - "oauth_storage_label_claim": "Talletussildi väide", - "oauth_storage_label_claim_description": "Sea kasutaja talletussildiks automaatselt selle väite väärtus.", - "oauth_storage_quota_claim": "Talletuskvoodi väide", - "oauth_storage_quota_claim_description": "Sea kasutaja talletuskvoodiks automaatselt selle väite väärtus.", - "oauth_storage_quota_default": "Vaikimisi talletuskvoot (GiB)", - "oauth_storage_quota_default_description": "Kvoot (GiB), mida kasutada, kui ühtegi väidet pole esitatud.", - "oauth_timeout": "Päringu ajalõpp", - "oauth_timeout_description": "Päringute ajalõpp millisekundites", - "ocr_job_description": "Kasuta piltidelt teksti tuvastamiseks masinõpet", - "password_enable_description": "Logi sisse e-posti aadressi ja parooliga", - "password_settings": "Parooliga sisselogimine", - "password_settings_description": "Halda parooliga sisselogimise seadeid", - "paths_validated_successfully": "Kõik teed edukalt valideeritud", - "person_cleanup_job": "Isikute korrastamine", - "queue_details": "Järjekorra üksikasjad", - "queues": "Töödete järjekorrad", - "queues_page_description": "Töödete järjekordade haldamise leht", - "quota_size_gib": "Kvoot (GiB)", - "refreshing_all_libraries": "Kõikide kogude värskendamine", - "registration": "Administraatori registreerimine", - "registration_description": "Kuna sa oled süsteemis esimene kasutaja, määratakse sind administraatoriks, ning sa saad lisada täiendavaid kasutajaid.", - "remove_failed_jobs": "Eemalda ebaõnnestunud tööted", - "require_password_change_on_login": "Nõua kasutajalt esmakordsel sisenemisel parooli muutmist", - "reset_settings_to_default": "Lähtesta seaded", - "reset_settings_to_recent_saved": "Taasta hiljuti salvestatud seaded", - "scanning_library": "Kogu skaneerimine", - "search_jobs": "Otsi töödet…", - "send_welcome_email": "Saada tervituskiri", - "server_external_domain_settings": "Väline domeen", - "server_external_domain_settings_description": "Domeen avalikult jagatud linkide jaoks, k.a. http(s)://", - "server_public_users": "Avalikud kasutajad", - "server_public_users_description": "Kasutaja jagatud albumisse lisamisel kuvatakse kõiki kasutajaid (nime ja e-posti aadressiga). Kui keelatud, kuvatakse kasutajate nimekirja ainult administraatoritele.", - "server_settings": "Serveri seaded", - "server_settings_description": "Halda serveri seadeid", - "server_stats_page_description": "Serveri statistika leht", - "server_welcome_message": "Tervitusteade", - "server_welcome_message_description": "Teade, mida kuvatakse sisselogimise lehel.", - "settings_page_description": "Süsteemi seadete leht", - "sidecar_job": "Väliste failide metaandmed", - "sidecar_job_description": "Avasta või sünkroniseeri väliste failide metaandmed failisüsteemist", - "slideshow_duration_description": "Mitu sekundit igat pilti kuvada", - "smart_search_job_description": "Käivita üksuste peal masinõpe, et toetada nutiotsingut", - "storage_template_date_time_description": "Kuupäeva ja kellaaja informatsiooniks kasutatakse üksuse loomise aega", - "storage_template_date_time_sample": "Näidisaeg {date}", - "storage_template_enable_description": "Lülita sisse talletusmallimootor", - "storage_template_hash_verification_enabled": "Räsi kontroll sisse lülitatud", - "storage_template_hash_verification_enabled_description": "Lülitab sisse räsi kontrolli; ära lülita seda välja, kui sa ei ole tagajärgedest teadlik", - "storage_template_migration": "Talletusmalli migreerimine", - "storage_template_migration_description": "Rakenda praegune {template} varem üleslaaditud üksustele", - "storage_template_migration_info": "Talletusmall teeb kõik faililaiendid väiketähtedeks. Malli muudatused rakenduvad ainult uutele üksustele. Et rakendada malli tagasiulatuvalt varem üleslaaditud üksustele, käivita {job}.", - "storage_template_migration_job": "Talletusmallide migreerimise tööde", - "storage_template_more_details": "Et selle funktsiooni kohta rohkem teada saada, loe talletusmallide ja nende tagajärgede kohta", - "storage_template_onboarding_description_v2": "Kui lubatud, organiseeritakse failid automaatselt kasutaja määratud malli alusel. Rohkem infot leiad dokumentatsioonist.", - "storage_template_path_length": "Tee pikkuse umbkaudne limiit: {length, number}/{limit, number}", - "storage_template_settings": "Talletusmall", - "storage_template_settings_description": "Halda üleslaaditud üksuse kaustastruktuuri ja failinime", - "storage_template_user_label": "{label} on kasutaja talletussilt", - "system_settings": "Süsteemi seaded", - "tag_cleanup_job": "Siltide korrastamine", - "template_email_available_tags": "Saad mallis kasutada järgmisi muutujaid: {tags}", - "template_email_if_empty": "Kui mall on tühi, kasutatakse vaikimisi e-kirja.", - "template_email_invite_album": "Albumisse kutse mall", - "template_email_preview": "Eelvaade", - "template_email_settings": "E-posti mallid", - "template_email_update_album": "Albumi muutmise mall", - "template_email_welcome": "Tervituskirja mall", - "template_settings": "Teavituse mallid", - "template_settings_description": "Teavituste mallide haldamine", - "theme_custom_css_settings": "Kohandatud CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets lubab Immich'i kujunduse kohandamist.", - "theme_settings": "Teema seaded", - "theme_settings_description": "Halda Immich'i veebiliidese kohandamist", - "thumbnail_generation_job": "Pisipiltide genereerimine", - "thumbnail_generation_job_description": "Genereeri iga üksuse kohta suur, väike ja udustatud pisipilt ning iga isiku kohta pisipilt", - "transcoding_acceleration_api": "Kiirenduse API", - "transcoding_acceleration_api_description": "API, mis suhtleb su seadmega transkodeerimise kiirendamiseks. See seadistus on 'anname parima': ebaõnnestumisel kasutatakse tarkvaralist transkodeerimist. VP9 ei pruugi töötada, sõltuvalt riistvarast.", - "transcoding_acceleration_nvenc": "NVENC (vajab NVIDIA GPU-d)", - "transcoding_acceleration_qsv": "Quick Sync (vajab Inteli 7. põlvkonna või uuemat CPU-d)", - "transcoding_acceleration_rkmpp": "RKMPP (ainult Rockchip SOC-d)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Lubatud audiokoodekid", - "transcoding_accepted_audio_codecs_description": "Vali, millised audiokoodekid ei vaja transkodeerimist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", - "transcoding_accepted_containers": "Lubatud konteinerid", - "transcoding_accepted_containers_description": "Vali, millised konteineriformaadid ei vaja MP4-ks teisendamist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", - "transcoding_accepted_video_codecs": "Lubatud videokoodekid", - "transcoding_accepted_video_codecs_description": "Vali, millised videokoodekid ei vaja transkodeerimist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", - "transcoding_advanced_options_description": "Valikud, mida enamik kasutajaid ei pea muutma", - "transcoding_audio_codec": "Audiokoodek", - "transcoding_audio_codec_description": "Opus on kõrgeima kvaliteediga valik, aga on vähem ühilduv vanade seadmete või tarkvaraga.", - "transcoding_bitrate_description": "Kõrgema kui lubatud bitisagedusega või mittelubatud formaadis videod", - "transcoding_codecs_learn_more": "Siin kasutatud terminoloogia kohta rohkem teada saamiseks loe FFmpeg-i dokumentatsiooni H.264, HEVC ja VP9 koodekite kohta.", - "transcoding_constant_quality_mode": "Püsiva kvaliteedi režiim", - "transcoding_constant_quality_mode_description": "ICQ on parem kui CQP, aga mõned riistvaralise kiirenduse seadmed ei toeta seda režiimi. Selle valiku seadmisel eelistatakse kvaliteedipõhise kodeerimise puhul valitud režiimi. NVENC puhul valikut ignoreeritakse, kuna see ei toeta ICQ-d.", - "transcoding_constant_rate_factor": "Püsiv kiirusefaktor (-crf)", - "transcoding_constant_rate_factor_description": "Video kvaliteeditase. Tüüpilised väärtused on 23 (H.264), 28 (HEVC), 31 (VP9) ning 35 (AV1). Madal on parem, aga tulemuseks on suuremad failid.", - "transcoding_disabled_description": "Ära transkodeeri videosid. Võib takistada taasesitamist mõnedes seadmetes", - "transcoding_encoding_options": "Kodeerimise valikud", - "transcoding_encoding_options_description": "Määra kodeeritud videote koodek, resolutsioon, kvaliteet ja muud valikud", - "transcoding_hardware_acceleration": "Riistvaraline kiirendus", - "transcoding_hardware_acceleration_description": "Eksperimentaalne: kiirem transkodeerimine, aga võib vähendada kvaliteeti sama bitisageduse juures", - "transcoding_hardware_decoding": "Riistvaraline dekodeerimine", - "transcoding_hardware_decoding_setting_description": "Võimaldab protsessi läbivalt kiirendada, mitte ainult kodeerimist. Ei pruugi kõigi videote puhul töötada.", - "transcoding_max_b_frames": "Maksimaalne B-kaadrite arv", - "transcoding_max_b_frames_description": "Kõrgemad väärtused parandavad pakkimise efektiivsust, aga aeglustavad kodeerimist. See valik ei pruugi olla ühilduv riistvaralise kiirendusega vanematel seadmetel. 0 lülitab B-kaadrid välja, -1 määrab väärtuse automaatselt.", - "transcoding_max_bitrate": "Maksimaalne bitisagedus", - "transcoding_max_bitrate_description": "Maksimaalse bitisageduse määramine teeb failisuurused ennustatavamaks, väikese kvaliteedikao hinnaga. 720p resolutsiooni puhul on tüüpilised väärtused 2600 kbit/s (VP9 ja HEVC) või 4500 kbit/s (H.264). Väärtus 0 eemaldab piirangu. Kui ühikut pole määratud, eeldatakse k (kbit/s); seega 5000, 5000k ja 5M (Mbit/s) on samaväärsed.", - "transcoding_max_keyframe_interval": "Maksimaalne võtmekaadri intervall", - "transcoding_max_keyframe_interval_description": "Määrab maksimaalse kauguse võtmekaadrite vahel. Madalamad väärtused vähendavad pakkimise efektiivsust, aga parandavad otsimiskiirust ning võivad tõsta kiire liikumisega stseenide kvaliteeti. 0 määrab väärtuse automaatselt.", - "transcoding_optimal_description": "Kõrgema kui lubatud resolutsiooniga või mittelubatud formaadis videod", - "transcoding_policy": "Transkodeerimise reegel", - "transcoding_policy_description": "Määra, millal video transkodeeritakse", - "transcoding_preferred_hardware_device": "Eelistatud riistvaraseade", - "transcoding_preferred_hardware_device_description": "Rakendub ainult VAAPI ja QSV puhul. Määrab dri seadme, mida kasutatakse riistvaraliseks transkodeerimiseks.", - "transcoding_preset_preset": "Eelseadistus (-preset)", - "transcoding_preset_preset_description": "Pakkimiskiirus. Aeglasemad eelseadistused tekitavad väiksemaid faile ja annavad sama bitisageduse juures parema kvaliteedi. VP9 ignoreerib kiiruseid üle 'faster' taseme.", - "transcoding_reference_frames": "Viitekaadrid", - "transcoding_reference_frames_description": "Kaadrite arv, millele viidata jooksva kaadri pakkimisel. Suuremad väärtused parandavad pakkimise tõhusust, aga muudavad kodeerimise aeglasemaks. 0 määrab väärtuse automaatselt.", - "transcoding_required_description": "Ainult mittelubatud formaadis videod", - "transcoding_settings": "Video transkodeerimise seaded", - "transcoding_settings_description": "Määra, millised videod transkodeerida ja kuidas neid töödelda", - "transcoding_target_resolution": "Sihtresolutsioon", - "transcoding_target_resolution_description": "Kõrgemad resolutsioonid säilitavad rohkem detaile, aga kodeerimine võtab kauem aega, tekitab suuremaid faile ning võib mõjutada rakenduse töökiirust.", - "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Rakendub NVENC puhul. Temporal Adaptive Quantization parandab paljude detailide, aga vähese liikumisega stseenide kvaliteeti. Ei pruugi ühilduda vanemate seadmetega.", - "transcoding_threads": "Lõimed", - "transcoding_threads_description": "Kõrgem väärtus tähendab kiiremat kodeerimist, aga jätab serverile muude tegevuste jaoks vähem ressursse. See väärtus ei tohiks olla suurem kui protsessori tuumade arv. Väärtus 0 tähendab maksimaalset kasutust.", - "transcoding_tone_mapping": "Toonivastendus", - "transcoding_tone_mapping_description": "Üritab säilitada HDR videote kvaliteeti SDR-iks teisendamisel. Iga algoritm teeb värvi, detailide ja ereduse osas erinevaid kompromisse. Hable säilitab detaile, Mobius säilitab värve ning Reinhard säilitab eredust.", - "transcoding_transcode_policy": "Transkodeerimise reegel", - "transcoding_transcode_policy_description": "Reegel, millal tuleks videot transkodeerida. HDR-videosid transkodeeritakse alati (v.a. kui transkodeerimine on keelatud).", - "transcoding_two_pass_encoding": "Kahekäiguline kodeerimine", - "transcoding_two_pass_encoding_setting_description": "Transkodeeri kahes osas, et parandada kodeeritud videote kvaliteeti. Maksimaalse bitisageduse puhul (mis on vajalik H.264 ja HEVC jaoks) kasutab see režiim bitisageduse vahemikku ja ignoreerib CRF-i. VP9 puhul saab kasutada CRF-i, kui maksimaalset bitisagedust pole määratud.", - "transcoding_video_codec": "Videokoodek", - "transcoding_video_codec_description": "VP9 on võimekas ja veebiga ühilduv, aga transkodeerimine võtab kauem aega. HEVC on sarnase jõudluse, aga mitte nii hea veebiga ühilduvusega. H.264 on laialt ühilduv ja transkodeerimine on kiire, aga tulemuseks on suuremad failid. AV1 on kõige võimekam koodek, aga pole vanematel seadmetel toetatud.", - "trash_enabled_description": "Luba prügikast", - "trash_number_of_days": "Päevade arv", - "trash_number_of_days_description": "Päevade arv, kui kaua hoida üksusi prügikastis enne nende lõplikku kustutamist", - "trash_settings": "Prügikasti seaded", - "trash_settings_description": "Halda prügikasti seadeid", - "unlink_all_oauth_accounts": "Eemalda kõik OAuth kontod", - "unlink_all_oauth_accounts_description": "Ära unusta enne teenusepakkuja vahetamist kõik OAuth kontod eemaldada.", - "unlink_all_oauth_accounts_prompt": "Kas oled kindel, et soovid kõik OAuth kontod eemaldada? See lähtestab iga kasutaja OAuth ID ja seda tegevust ei saa tagasi võtta.", - "user_cleanup_job": "Kasutajate korrastamine", - "user_delete_delay": "Kasutaja {user} konto ja üksuste lõplik kustutamine on planeeritud {delay, plural, one {# päeva} other {# päeva}} pärast.", - "user_delete_delay_settings": "Kustutamise viivitus", - "user_delete_delay_settings_description": "Päevade arv, pärast mida kustutatakse eemaldatud kasutaja konto ja üksused jäädavalt. Kasutajate kustutamise tööde käivitub keskööl, et otsida kustutamiseks valmis kasutajaid. Selle seadistuse muudatused rakenduvad järgmisel käivitumisel.", - "user_delete_immediately": "Kasutaja {user} konto ja üksused suunatakse koheselt jäädavale kustutamisele.", - "user_delete_immediately_checkbox": "Suuna kasutaja ja üksused jäädavale kustutamisele", - "user_details": "Kasutaja üksikasjad", - "user_management": "Kasutajate haldus", - "user_password_has_been_reset": "Kasutaja parool on lähtestatud:", - "user_password_reset_description": "Sisesta kasutajale ajutine parool ja teavita teda, et järgmisel sisselogimisel tuleb parool ära muuta.", - "user_restore_description": "Kasutaja {user} konto taastatakse.", - "user_restore_scheduled_removal": "Taasta kasutaja - eemaldamine planeeritud {date, date, long}", - "user_settings": "Kasutajate seaded", - "user_settings_description": "Halda kasutajate seadeid", - "user_successfully_removed": "Kasutaja {email} edukalt eemaldatud.", - "users_page_description": "Kasutajate haldamise leht", - "version_check_enabled_description": "Luba versioonikontroll", - "version_check_implications": "Versioonikontroll vajab perioodilist ühendumist github.com-iga", - "version_check_settings": "Versioonikontroll", - "version_check_settings_description": "Luba/keela uue versiooni teavitus", - "video_conversion_job": "Videote transkodeerimine", - "video_conversion_job_description": "Transkodeeri videod laiema brauserite ja seadmetega ühilduvuse nimel" - }, - "admin_email": "Administraatori e-post", - "admin_password": "Administraatori parool", - "administration": "Administratsioon", - "advanced": "Täpsemad valikud", - "advanced_settings_clear_image_cache": "Tühjenda pildipuhver", - "advanced_settings_clear_image_cache_error": "Pildipuhvri tühjendamine ebaõnnestus", - "advanced_settings_clear_image_cache_success": "{size} edukalt tühjendatud", - "advanced_settings_enable_alternate_media_filter_subtitle": "Kasuta seda valikut, et filtreerida sünkroonimise ajal üksuseid alternatiivsete kriteeriumite alusel. Proovi seda ainult siis, kui rakendusel on probleeme kõigi albumite tuvastamisega.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAALNE] Kasuta alternatiivset seadme albumi sünkroonimise filtrit", - "advanced_settings_log_level_title": "Logimistase: {level}", - "advanced_settings_prefer_remote_subtitle": "Mõned seadmed laadivad lokaalsete üksuste pisipilte piinavalt aeglaselt. Aktiveeri see seadistus, et laadida selle asemel kaugpilte.", - "advanced_settings_prefer_remote_title": "Eelista kaugpilte", - "advanced_settings_proxy_headers_subtitle": "Määra vaheserveri päised, mida Immich peaks iga päringuga saatma", - "advanced_settings_proxy_headers_title": "Kohandatud vaheserveri päised [EKSPERIMENTAALNE]", - "advanced_settings_readonly_mode_subtitle": "Lülitab sisse kirjutuskaitserežiimi, milles saab fotosid ainult vaadata ning toimingud nagu mitme pildi valimine, jagamine, edastamine ja kustutamine on keelatud. Lülita kirjutuskaitserežiim sisse/välja põhiekraanil oleva avatari kaudu", - "advanced_settings_readonly_mode_title": "Kirjutuskaitserežiim", - "advanced_settings_self_signed_ssl_subtitle": "Jätab serveri lõpp-punkti SSL-sertifikaadi kontrolli vahele. Nõutud endasigneeritud sertifikaatide jaoks.", - "advanced_settings_self_signed_ssl_title": "Luba endasigneeritud SSL-sertifikaadid [EKSPERIMENTAALNE]", - "advanced_settings_sync_remote_deletions_subtitle": "Kustuta või taasta üksus selles seadmes automaatself, kui sama tegevus toimub veebis", - "advanced_settings_sync_remote_deletions_title": "Sünkrooni kaugkustutamised [EKSPERIMENTAALNE]", - "advanced_settings_tile_subtitle": "Edasijõudnud kasutajate seaded", - "advanced_settings_troubleshooting_subtitle": "Luba lisafunktsioonid tõrkeotsinguks", - "advanced_settings_troubleshooting_title": "Tõrkeotsing", - "age_months": "Vanus {months, plural, one {# kuu} other {# kuud}}", - "age_year_months": "Vanus 1 aasta, {months, plural, one {# kuu} other {# kuud}}", - "age_years": "{years, plural, other {Vanus #}}", - "album": "Album", - "album_added": "Album lisatud", - "album_added_notification_setting_description": "Saa teavitus e-posti teel, kui sind lisatakse jagatud albumisse", - "album_cover_updated": "Albumi kaanepilt muudetud", - "album_delete_confirmation": "Kas oled kindel, et soovid albumi {album} kustutada?", - "album_delete_confirmation_description": "Kui see album on jagatud, ei pääse teised kasutajad sellele enam ligi.", - "album_deleted": "Album kustutatud", - "album_info_card_backup_album_excluded": "VÄLJA JÄETUD", - "album_info_card_backup_album_included": "LISATUD", - "album_info_updated": "Albumi info muudetud", - "album_leave": "Lahku albumist?", - "album_leave_confirmation": "Kas oled kindel, et soovid albumist {album} lahkuda?", - "album_name": "Albumi nimi", - "album_options": "Albumi valikud", - "album_remove_user": "Eemalda kasutaja?", - "album_remove_user_confirmation": "Kas oled kindel, et soovid kasutaja {user} eemaldada?", - "album_search_not_found": "Otsingule vastavaid albumeid ei leitud", - "album_selected": "Album valitud", - "album_share_no_users": "Paistab, et oled seda albumit kõikide kasutajatega jaganud, või pole ühtegi kasutajat, kellega jagada.", - "album_summary": "Albumi kokkuvõte", - "album_updated": "Album muudetud", - "album_updated_setting_description": "Saa teavitus e-posti teel, kui jagatud albumis on uusi üksuseid", - "album_upload_assets": "Laadi üksused oma arvutist üles ja lisa albumisse", - "album_user_left": "Lahkutud albumist {album}", - "album_user_removed": "Kasutaja {user} eemaldatud", - "album_viewer_appbar_delete_confirm": "Kas oled kindel, et soovid selle albumi oma kontolt kustutada?", - "album_viewer_appbar_share_err_delete": "Albumi kustutamine ebaõnnestus", - "album_viewer_appbar_share_err_leave": "Albumist lahkumine ebaõnnestus", - "album_viewer_appbar_share_err_remove": "Üksuste albumist eemaldamisel tekkis probleeme", - "album_viewer_appbar_share_err_title": "Albumi pealkirja muutmine ebaõnnestus", - "album_viewer_appbar_share_leave": "Lahku albumist", - "album_viewer_appbar_share_to": "Jaga", - "album_viewer_page_share_add_users": "Lisa kasutajaid", - "album_with_link_access": "Luba kõigil, kellel on link, näha selle albumi fotosid ja isikuid.", - "albums": "Albumid", - "albums_count": "{count, plural, one {{count, number} album} other {{count, number} albumit}}", - "albums_default_sort_order": "Vaikimisi albumi järjestus", - "albums_default_sort_order_description": "Uute albumite lisamisel üksuste esialgne järjekord.", - "albums_feature_description": "Üksuste kollektsioonid, mida saab teiste kasutajatega jagada.", - "albums_on_device_count": "Albumid seadmel ({count})", - "albums_selected": "{count, plural, one {# album valitud} other {# albumit valitud}}", - "all": "Kõik", - "all_albums": "Kõik albumid", - "all_people": "Kõik isikud", - "all_photos": "Kõik fotod", - "all_videos": "Kõik videod", - "allow_dark_mode": "Luba tume teema", - "allow_edits": "Luba muutmine", - "allow_public_user_to_download": "Luba avalikul kasutajal alla laadida", - "allow_public_user_to_upload": "Luba avalikul kasutajal üles laadida", - "allowed": "Lubatud", - "alt_text_qr_code": "QR kood", - "always_keep": "Jäta alati alles", - "always_keep_photos_hint": "Talletusruumi vabastamine jätab kõik fotod selles seadmes alles.", - "always_keep_videos_hint": "Talletusruumi vabastamine jätab kõik videod selles seadmes alles.", - "anti_clockwise": "Vastupäeva", - "api_key": "API võti", - "api_key_description": "Seda väärtust kuvatakse ainult üks kord. Kopeeri see enne akna sulgemist.", - "api_key_empty": "Su API võtme nimi ei tohiks olla tühi", - "api_keys": "API võtmed", - "app_architecture_variant": "Variant (arhitektuur)", - "app_bar_signout_dialog_content": "Kas oled kindel, et soovid välja logida?", - "app_bar_signout_dialog_ok": "Jah", - "app_bar_signout_dialog_title": "Logi välja", - "app_download_links": "Rakenduse allalaadimise lingid", - "app_settings": "Rakenduse seaded", - "app_stores": "Rakendusepoed", - "app_update_available": "Rakenduse uuendus on saadaval", - "appears_in": "Albumid", - "apply_count": "Rakenda ({count, number})", - "archive": "Arhiiv", - "archive_action_prompt": "{count} lisatud arhiivi", - "archive_or_unarchive_photo": "Arhiveeri või taasta foto", - "archive_page_no_archived_assets": "Arhiveeritud üksuseid ei leitud", - "archive_page_title": "Arhiveeri ({count})", - "archive_size": "Arhiivi suurus", - "archive_size_description": "Seadista arhiivi suurus allalaadimiseks (GiB)", - "archived": "Arhiveeritud", - "archived_count": "{count, plural, other {# arhiveeritud}}", - "are_these_the_same_person": "Kas need on sama isik?", - "are_you_sure_to_do_this": "Kas oled kindel, et soovid seda teha?", - "array_field_not_fully_supported": "Massiivi väljad vajavad JSON-i käsitsi muutmist", - "asset_action_delete_err_read_only": "Kirjutuskaitstud üksuseid ei saa kustutada, jäetakse vahele", - "asset_action_share_err_offline": "Ühenduseta üksuseid ei saa pärida, jäetakse vahele", - "asset_added_to_album": "Lisatud albumisse", - "asset_adding_to_album": "Albumisse lisamine…", - "asset_created": "Üksus loodud", - "asset_description_updated": "Üksuse kirjeldus on muudetud", - "asset_filename_is_offline": "Üksus {filename} ei ole kättesaadav", - "asset_has_unassigned_faces": "Üksusel on seostamata nägusid", - "asset_hashing": "Räsimine…", - "asset_list_group_by_sub_title": "Grupeeri", - "asset_list_layout_settings_dynamic_layout_title": "Dünaamiline asetus", - "asset_list_layout_settings_group_automatically": "Automaatne", - "asset_list_layout_settings_group_by": "Grupeeri üksused", - "asset_list_layout_settings_group_by_month_day": "Kuu + päev", - "asset_list_layout_sub_title": "Asetus", - "asset_list_settings_subtitle": "Fotoruudustiku asetuse sätted", - "asset_list_settings_title": "Fotoruudustik", - "asset_not_found_on_device_android": "Üksust ei leitud seadmest", - "asset_not_found_on_device_ios": "Üksust ei leitud seadmest. Kui kasutad iCloud'i, võib üksus olla iCloud'is oleva vigase faili tõttu kättesaamatu", - "asset_not_found_on_icloud": "Üksust ei leitud iCloud'ist. Üksus võib olla iCloud'is oleva vigase faili tõttu kättesaamatu", - "asset_offline": "Üksus pole kättesaadav", - "asset_offline_description": "Seda välise kogu üksust ei leitud kettalt. Abi saamiseks palun võta ühendust oma Immich'i administraatoriga.", - "asset_restored_successfully": "Üksus edukalt taastatud", - "asset_skipped": "Vahele jäetud", - "asset_skipped_in_trash": "Prügikastis", - "asset_trashed": "Üksus liigutatud prügikasti", - "asset_troubleshoot": "Üksuse tõrkeotsing", - "asset_uploaded": "Üleslaaditud", - "asset_uploading": "Üleslaadimine…", - "asset_viewer_settings_subtitle": "Halda galeriivaaturi seadeid", - "asset_viewer_settings_title": "Üksuste vaatur", - "assets": "Üksused", - "assets_added_count": "{count, plural, one {# üksus} other {# üksust}} lisatud", - "assets_added_to_album_count": "{count, plural, one {# üksus} other {# üksust}} albumisse lisatud", - "assets_added_to_albums_count": "{assetTotal, plural, one {# üksus} other {# üksust}} lisatud {albumTotal, plural, one {# albumisse} other {# albumisse}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Üksust} other {Üksuseid}} ei saa albumisse lisada", - "assets_cannot_be_added_to_albums": "{count, plural, one {Üksust} other {Üksuseid}} ei saa lisada ühtegi albumisse", - "assets_count": "{count, plural, one {# üksus} other {# üksust}}", - "assets_deleted_permanently": "{count} üksus(t) jäädavalt kustutatud", - "assets_deleted_permanently_from_server": "{count} üksus(t) Immich'i serverist jäädavalt kustutatud", - "assets_downloaded_failed": "{count, plural, one {# fail allalaaditud - {error} fail ebaõnnestus} other {# faili allalaaditud - {error} faili ebaõnnestus}}", - "assets_downloaded_successfully": "{count, plural, one {# fail edukalt allalaaditud} other {# faili edukalt allalaaditud}}", - "assets_moved_to_trash_count": "{count, plural, one {# üksus} other {# üksust}} liigutatud prügikasti", - "assets_permanently_deleted_count": "{count, plural, one {# üksus} other {# üksust}} jäädavalt kustutatud", - "assets_removed_count": "{count, plural, one {# üksus} other {# üksust}} eemaldatud", - "assets_removed_permanently_from_device": "{count} üksus(t) seadmest jäädavalt eemaldatud", - "assets_restore_confirmation": "Kas oled kindel, et soovid oma prügikasti liigutatud üksused taastada? Seda ei saa tagasi võtta! Pane tähele, et sel meetodil ei saa taastada ühenduseta üksuseid.", - "assets_restored_count": "{count, plural, one {# üksus} other {# üksust}} taastatud", - "assets_restored_successfully": "{count} üksus(t) edukalt taastatud", - "assets_trashed": "{count} üksus(t) liigutatud prügikasti", - "assets_trashed_count": "{count, plural, one {# üksus} other {# üksust}} liigutatud prügikasti", - "assets_trashed_from_server": "{count} üksus(t) liigutatud Immich'i serveris prügikasti", - "assets_were_part_of_album_count": "{count, plural, one {Üksus oli} other {Üksused olid}} juba osa albumist", - "assets_were_part_of_albums_count": "{count, plural, one {Üksus oli} other {Üksused olid}} juba nendes albumites", - "authorized_devices": "Autoriseeritud seadmed", - "automatic_endpoint_switching_subtitle": "Ühendu lokaalselt üle valitud WiFi-võrgu, kui see on saadaval, ja kasuta mujal alternatiivseid ühendusi", - "automatic_endpoint_switching_title": "Automaatne URL-i ümberlülitamine", - "autoplay_slideshow": "Esita slaidiesitlus automaatselt", - "back": "Tagasi", - "back_close_deselect": "Tagasi, sulge või tühista valik", - "background_backup_running_error": "Taustvarundus on käimas, ei saa käsitsi varundust alustada", - "background_location_permission": "Taustal asukoha luba", - "background_location_permission_content": "Et taustal töötades võrguühendust vahetada, peab Immich'il *alati* olema täpse asukoha luba, et rakendus saaks WiFi-võrgu nime lugeda", - "background_options": "Taustavalikud", - "backup": "Varundamine", - "backup_album_selection_page_albums_device": "Albumid seadmel ({count})", - "backup_album_selection_page_albums_tap": "Puuduta kaasamiseks, topeltpuuduta välistamiseks", - "backup_album_selection_page_assets_scatter": "Üksused võivad olla jaotatud mitme albumi vahel. Seega saab albumeid varundamise protsessi kaasata või välistada.", - "backup_album_selection_page_select_albums": "Vali albumid", - "backup_album_selection_page_selection_info": "Valiku info", - "backup_album_selection_page_total_assets": "Unikaalseid üksuseid kokku", - "backup_albums_sync": "Varundusalbumite sünkroniseerimine", - "backup_all": "Kõik", - "backup_background_service_backup_failed_message": "Üksuste varundamine ebaõnnestus. Uuesti proovimine…", - "backup_background_service_complete_notification": "Üksuste varundamine lõppenud", - "backup_background_service_connection_failed_message": "Serveriga ühendumine ebaõnnestus. Uuesti proovimine…", - "backup_background_service_current_upload_notification": "{filename} üleslaadimine", - "backup_background_service_default_notification": "Uute üksuste kontrollimine…", - "backup_background_service_error_title": "Varundamise viga", - "backup_background_service_in_progress_notification": "Sinu üksuste varundamine…", - "backup_background_service_upload_failure_notification": "Faili {filename} üleslaadimine ebaõnnestus", - "backup_controller_page_albums": "Varunduse albumid", - "backup_controller_page_background_app_refresh_disabled_content": "Taustal varundamise kasutamiseks luba rakenduse taustal värskendamine: Seaded > Üldine > Rakenduse taustal värskendamine.", - "backup_controller_page_background_app_refresh_disabled_title": "Rakenduse taustal värskendamine keelatud", - "backup_controller_page_background_app_refresh_enable_button_text": "Mine seadetesse", - "backup_controller_page_background_battery_info_link": "Näita mulle, kuidas", - "backup_controller_page_background_battery_info_message": "Parima taustal varundamise kogemuse jaoks palun keela Immich'i puhul kõik taustategevust piiravad aku optimeerimised.\n\nKuna see on seadmespetsiifiline, otsi vajalikku teavet oma seadme tootja kohta.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Aku optimeerimised", - "backup_controller_page_background_charging": "Ainult laadimise ajal", - "backup_controller_page_background_configure_error": "Taustateenuse seadistamine ebaõnnestus", - "backup_controller_page_background_delay": "Oota uute üksuste varundamisega: {duration}", - "backup_controller_page_background_description": "Lülita taustateenus sisse, et uusi üksuseid automaatselt varundada, ilma et peaks rakendust avama", - "backup_controller_page_background_is_off": "Automaatne taustal varundamine on välja lülitatud", - "backup_controller_page_background_is_on": "Automaatne taustal varundamine on sisse lülitatud", - "backup_controller_page_background_turn_off": "Lülita taustateenus välja", - "backup_controller_page_background_turn_on": "Lülita taustateenus sisse", - "backup_controller_page_background_wifi": "Ainult WiFi-võrgus", - "backup_controller_page_backup": "Varundamine", - "backup_controller_page_backup_selected": "Valitud: ", - "backup_controller_page_backup_sub": "Varundatud fotod ja videod", - "backup_controller_page_created": "Lisatud: {date}", - "backup_controller_page_desc_backup": "Lülita sisse esiplaanil varundamine, et rakenduse avamisel uued üksused automaatselt serverisse üles laadida.", - "backup_controller_page_excluded": "Välistatud: ", - "backup_controller_page_failed": "Ebaõnnestunud ({count})", - "backup_controller_page_filename": "Failinimi: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Varunduse info", - "backup_controller_page_none_selected": "Ühtegi pole valitud", - "backup_controller_page_remainder": "Ootel", - "backup_controller_page_remainder_sub": "Valitud fotod ja videod, mis on veel varundamise ootel", - "backup_controller_page_server_storage": "Serveri talletusruum", - "backup_controller_page_start_backup": "Alusta varundamist", - "backup_controller_page_status_off": "Automaatne esiplaanil varundamine on välja lülitatud", - "backup_controller_page_status_on": "Automaatne esiplaanil varundamine on sisse lülitatud", - "backup_controller_page_storage_format": "{used}/{total} kasutusel", - "backup_controller_page_to_backup": "Albumid, mida varundada", - "backup_controller_page_total_sub": "Kõik unikaalsed fotod ja videod valitud albumitest", - "backup_controller_page_turn_off": "Lülita esiplaanil varundus välja", - "backup_controller_page_turn_on": "Lülita esiplaanil varundus sisse", - "backup_controller_page_uploading_file_info": "Faili info üleslaadimine", - "backup_err_only_album": "Ei saa ainsat albumit eemaldada", - "backup_error_sync_failed": "Sünkroonimine ebaõnnestus. Varundust ei saa töödelda.", - "backup_info_card_assets": "üksused", - "backup_manual_cancelled": "Tühistatud", - "backup_manual_in_progress": "Üleslaadimine juba käib. Proovi hiljem uuesti", - "backup_manual_success": "Õnnestus", - "backup_manual_title": "Üleslaadimise staatus", - "backup_options": "Varunduse valikud", - "backup_options_page_title": "Varundamise valikud", - "backup_setting_subtitle": "Halda taustal ja esiplaanil üleslaadimise seadeid", - "backup_settings_subtitle": "Halda üleslaadimise seadeid", - "backup_upload_details_page_more_details": "Puuduta rohkema info saamiseks", - "backward": "Tagasi", - "biometric_auth_enabled": "Biomeetriline autentimine lubatud", - "biometric_locked_out": "Biomeetriline autentimine on blokeeritud", - "biometric_no_options": "Biomeetrilisi valikuid ei ole", - "biometric_not_available": "Biomeetriline autentimine ei ole selles seadmes saadaval", - "birthdate_saved": "Sünnikuupäev salvestatud", - "birthdate_set_description": "Sünnikuupäeva kasutatakse isiku vanuse arvutamiseks foto tegemise hetkel.", - "blurred_background": "Udustatud taust", - "bugs_and_feature_requests": "Vearaportid ja täiendussoovid", - "build": "Kooste", - "build_image": "Koostetõmmis", - "bulk_delete_duplicates_confirmation": "Kas oled kindel, et soovid {count, plural, one {# dubleeritud üksuse} other {# dubleeritud üksust}} masskustutada? Sellega jäetakse alles iga grupi suurim üksus ning duplikaadid kustutatakse jäädavalt. Seda tegevust ei saa tagasi võtta!", - "bulk_keep_duplicates_confirmation": "Kas oled kindel, et soovid {count, plural, one {# dubleeritud üksuse} other {# dubleeritud üksust}} alles jätta? Sellega märgitakse kõik duplikaadigrupid lahendatuks ilma midagi kustutamata.", - "bulk_trash_duplicates_confirmation": "Kas oled kindel, et soovid {count, plural, one {# dubleeritud üksuse} other {# dubleeritud üksust}} masskustutada? Sellega jäetakse alles iga grupi suurim üksus ning duplikaadid liigutatakse prügikasti.", - "buy": "Osta Immich", - "cache_settings_clear_cache_button": "Tühjenda puhver", - "cache_settings_clear_cache_button_title": "Tühjendab rakenduse puhvri. See mõjutab oluliselt rakenduse jõudlust, kuni puhver uuesti täidetakse.", - "cache_settings_duplicated_assets_clear_button": "TÜHJENDA", - "cache_settings_duplicated_assets_subtitle": "Fotod ja videod, mis on rakenduse poolt ignoreeritud", - "cache_settings_duplicated_assets_title": "Dubleeritud üksused ({count})", - "cache_settings_statistics_album": "Kogu pisipildid", - "cache_settings_statistics_full": "Täismõõdus pildid", - "cache_settings_statistics_shared": "Jagatud albumite pisipildid", - "cache_settings_statistics_thumbnail": "Pisipildid", - "cache_settings_statistics_title": "Puhvri kasutus", - "cache_settings_subtitle": "Juhi Immich'i rakenduse puhverdamist", - "cache_settings_tile_subtitle": "Juhi lokaalse talletuse käitumist", - "cache_settings_tile_title": "Lokaalne talletus", - "cache_settings_title": "Puhverdamise seaded", - "camera": "Kaamera", - "camera_brand": "Kaamera mark", - "camera_model": "Kaamera mudel", - "cancel": "Katkesta", - "cancel_search": "Katkesta otsing", - "canceled": "Tühistatud", - "canceling": "Tühistamine", - "cannot_merge_people": "Ei saa isikuid ühendada", - "cannot_undo_this_action": "Seda tegevust ei saa tagasi võtta!", - "cannot_update_the_description": "Kirjelduse muutmine ebaõnnestus", - "cast": "Edasta", - "cast_description": "Seadista saadavalolevaid voogedastuse sihtpunkte", - "change_date": "Muuda kuupäeva", - "change_description": "Muuda kirjeldust", - "change_display_order": "Muuda kuva järjekorda", - "change_expiration_time": "Muuda aegumisaega", - "change_location": "Muuda asukohta", - "change_name": "Muuda nime", - "change_name_successfully": "Nimi edukalt muudetud", - "change_password": "Parooli muutmine", - "change_password_description": "See on su esimene kord süsteemi siseneda, või on tehtud taotlus parooli muutmiseks. Palun sisesta allpool uus parool.", - "change_password_form_confirm_password": "Kinnita parool", - "change_password_form_description": "Hei {name},\n\nSa kas logid süsteemi esimest korda sisse, või on esitatud taotlus sinu parooli muutmiseks. Palun sisesta allpool uus parool.", - "change_password_form_log_out": "Logi muudest seadmetest välja", - "change_password_form_log_out_description": "Soovituslik on kõigist muudest seadmetest välja logida", - "change_password_form_new_password": "Uus parool", - "change_password_form_password_mismatch": "Paroolid ei klapi", - "change_password_form_reenter_new_password": "Korda uut parooli", - "change_pin_code": "Muuda PIN-koodi", - "change_trigger": "Muuda päästikut", - "change_trigger_prompt": "Kas oled kindel, et soovid päästikut muuta? See eemaldab kõik olemasolevad tegevused ja filtrid.", - "change_your_password": "Muuda oma parooli", - "changed_visibility_successfully": "Nähtavus muudetud", - "charging": "Laadimine", - "charging_requirement_mobile_backup": "Taustal varundus vajab, et seade oleks laadimas", - "check_corrupt_asset_backup": "Otsi riknenud üksuste varukoopiaid", - "check_corrupt_asset_backup_button": "Teosta kontroll", - "check_corrupt_asset_backup_description": "Käivita see kontroll ainult WiFi-võrgus ja siis, kui kõik üksused on varundatud. See protseduur võib kesta mõne minuti.", - "check_logs": "Vaata logisid", - "checksum": "Kontrollsumma", - "choose_matching_people_to_merge": "Vali kattuvad isikud, mida ühendada", - "city": "Linn", - "cleanup_confirm_description": "Immich leidis {count} üksus(t) (lisatud enne {date}), mis on turvaliselt serverisse varundatud. Kas eemaldada sellest seadmest lokaalsed koopiad?", - "cleanup_confirm_prompt_title": "Eemalda sellest seadmest?", - "cleanup_deleted_assets": "{count} üksust liigutatud seadme prügikasti", - "cleanup_deleting": "Liigutatakse prügikasti...", - "cleanup_found_assets": "Leitud {count} varundatud üksus(t)", - "cleanup_found_assets_with_size": "Leitud {count} varundatud üksust ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud jagatud albumid jäävad otsingust välja", - "cleanup_no_assets_found": "Ülalolevatele tingimustele vastavaid üksuseid ei leitud. Talletusruumi vabastamine saab eemaldada ainult üksuseid, mis on serverisse varundatud", - "cleanup_preview_title": "Üksused, mida eemaldada ({count})", - "cleanup_step3_description": "Otsi varundatud üksuseid, mis vastavad sinu kuupäeva ja alleshoidmise seadetele.", - "cleanup_step4_summary": "{count} üksust (loodud enne {date}) eemaldatakse lokaalsest seadmest. Fotod jäävad Immich'i rakenduse kaudu kättesaadavaks.", - "cleanup_trash_hint": "Talletusruumi vabastamiseks ava galeriirakendus ja tühjenda prügikast", - "clear": "Tühjenda", - "clear_all": "Tühjenda kõik", - "clear_all_recent_searches": "Tühjenda hiljutised otsingud", - "clear_file_cache": "Tühjenda failipuhver", - "clear_message": "Tühjenda sõnum", - "clear_value": "Tühjenda väärtus", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Sisesta parool", - "client_cert_import": "Impordi", - "client_cert_import_success_msg": "Klientsertifikaat on imporditud", - "client_cert_invalid_msg": "Vigane sertifikaadi fail või vale parool", - "client_cert_password_message": "Sisesta sertifikaadi salasõna", - "client_cert_password_title": "Sertifikaadi salasõna", - "client_cert_remove_msg": "Klientsertifikaat on eemaldatud", - "client_cert_subtitle": "Toetab ainult PKCS12 (.p12, .pfx) formaati. Sertifikaadi importimine/eemaldamine on saadaval ainult enne sisselogimist", - "client_cert_title": "SSL klientsertifikaat [EKSPERIMENTAALNE]", - "clockwise": "Päripäeva", - "close": "Sulge", - "collapse": "Peida", - "collapse_all": "Peida kõik", - "color": "Värv", - "color_theme": "Värviteema", - "command": "Käsk", - "comment_deleted": "Kommentaar kustutatud", - "comment_options": "Kommentaari valikud", - "comments_and_likes": "Kommentaarid ja meeldimised", - "comments_are_disabled": "Kommentaarid on keelatud", - "common_create_new_album": "Lisa uus album", - "completed": "Lõpetatud", - "confirm": "Kinnita", - "confirm_admin_password": "Kinnita administraatori parool", - "confirm_delete_face": "Kas oled kindel, et soovid isiku {name} näo üksuselt kustutada?", - "confirm_delete_shared_link": "Kas oled kindel, et soovid selle jagatud lingi kustutada?", - "confirm_keep_this_delete_others": "Kõik muud üksused selles virnas kustutatakse. Kas oled kindel, et soovid jätkata?", - "confirm_new_pin_code": "Kinnita uus PIN-kood", - "confirm_password": "Kinnita parool", - "confirm_tag_face": "Kas tahad sildistada selle näo kui {name}?", - "confirm_tag_face_unnamed": "Kas tahad selle näo sildistada?", - "connected_device": "Ühendatud seade", - "connected_to": "Ühendatud seadmega", - "contain": "Mahuta ära", - "context": "Kontekst", - "continue": "Jätka", - "control_bottom_app_bar_create_new_album": "Lisa uus album", - "control_bottom_app_bar_delete_from_immich": "Kustuta Immich'ist", - "control_bottom_app_bar_delete_from_local": "Kustuta seadmest", - "control_bottom_app_bar_edit_location": "Muuda asukohta", - "control_bottom_app_bar_edit_time": "Muuda kuupäeva ja aega", - "control_bottom_app_bar_share_link": "Jaga linki", - "control_bottom_app_bar_share_to": "Jaga", - "control_bottom_app_bar_trash_from_immich": "Liiguta prügikasti", - "copied_image_to_clipboard": "Pilt kopeeritud lõikelauale.", - "copied_to_clipboard": "Kopeeritud lõikelauale!", - "copy_error": "Kopeeri viga", - "copy_file_path": "Kopeeri failitee", - "copy_image": "Kopeeri pilt", - "copy_link": "Kopeeri link", - "copy_link_to_clipboard": "Kopeeri link lõikelauale", - "copy_password": "Kopeeri parool", - "copy_to_clipboard": "Kopeeri lõikelauale", - "country": "Riik", - "cover": "Kata kogu ala", - "covers": "Kaanepildid", - "create": "Lisa", - "create_album": "Lisa album", - "create_album_page_untitled": "Pealkirjata", - "create_api_key": "Lisa API võti", - "create_first_workflow": "Lisa esimene töövoog", - "create_library": "Lisa kogu", - "create_link": "Lisa link", - "create_link_to_share": "Lisa jagamiseks link", - "create_link_to_share_description": "Luba kõigil, kellel on link, valitud pilte näha", - "create_new": "LISA UUS", - "create_new_person": "Lisa uus isik", - "create_new_person_hint": "Seosta valitud üksused uue isikuga", - "create_new_user": "Lisa uus kasutaja", - "create_shared_album_page_share_add_assets": "LISA ÜKSUSEID", - "create_shared_album_page_share_select_photos": "Vali fotod", - "create_shared_link": "Loo jagatud link", - "create_tag": "Lisa silt", - "create_tag_description": "Lisa uus silt. Pesastatud siltide jaoks sisesta täielik tee koos kaldkriipsudega.", - "create_user": "Lisa kasutaja", - "create_workflow": "Lisa töövoog", - "created": "Lisatud", - "created_at": "Lisatud", - "creating_linked_albums": "Lingitud albumite loomine...", - "crop": "Kärpimine", - "crop_aspect_ratio_fixed": "Fikseeritud", - "crop_aspect_ratio_free": "Vaba", - "crop_aspect_ratio_original": "Originaalne", - "curated_object_page_title": "Asjad", - "current_device": "Praegune seade", - "current_pin_code": "Praegune PIN-kood", - "current_server_address": "Praegune serveri aadress", - "custom_date": "Muu kuupäev", - "custom_locale": "Kohandatud lokaat", - "custom_locale_description": "Vorminda kuupäevad ja arvud vastavalt keelele ja regioonile", - "custom_url": "Kohandatud URL", - "cutoff_date_description": "Jäta alles fotod ja videod viimasest…", - "cutoff_day": "{count, plural, one {päev} other {päeva}}", - "cutoff_year": "{count, plural, one {aasta} other {aastat}}", - "daily_title_text_date": "d. MMMM", - "daily_title_text_date_year": "d. MMMM yyyy", - "dark": "Tume", - "dark_theme": "Lülita tume teema", - "date": "Kuupäev", - "date_after": "Kuupäev pärast", - "date_and_time": "Kuupäev ja kellaaeg", - "date_before": "Kuupäev enne", - "date_format": "d. MMMM y • HH:mm", - "date_of_birth_saved": "Sünnikuupäev salvestatud", - "date_range": "Kuupäevavahemik", - "day": "Päev", - "days": "Päeva", - "deduplicate_all": "Dedubleeri kõik", - "deduplication_criteria_1": "Pildi suurus baitides", - "deduplication_criteria_2": "EXIF andmete hulk", - "deduplication_info": "Dedubleerimise info", - "deduplication_info_description": "Üksuste automaatsel eelvalimisel ja duplikaatide eemaldamisel võetakse arvesse:", - "default_locale": "Vaikimisi lokaat", - "default_locale_description": "Vorminda kuupäevad ja numbrid vastavalt brauseri lokaadile", - "delete": "Kustuta", - "delete_action_confirmation_message": "Kas oled kindel, et soovid selle üksuse kustutada? See toiming liigutab üksuse serveri prügikasti ja küsib, kas soovid selle lokaalselt kustutada", - "delete_action_prompt": "{count} kustutatud", - "delete_album": "Kustuta album", - "delete_api_key_prompt": "Kas oled kindel, et soovid selle API võtme kustutada?", - "delete_dialog_alert": "Need üksused kustutatakse jäädavalt Immich'ist ja sinu seadmest", - "delete_dialog_alert_local": "Need üksused kustutatakse jäädavalt sinu seadmest, aga jäävad Immich'i serverisse alles", - "delete_dialog_alert_local_non_backed_up": "Mõned üksustest ei ole Immich'isse varundatud ning kustutatakse su seadmest jäädavalt", - "delete_dialog_alert_remote": "Need üksused kustutatakse jäädavalt Immich'i serverist", - "delete_dialog_ok_force": "Kustuta sellegipoolest", - "delete_dialog_title": "Kustuta jäädavalt", - "delete_duplicates_confirmation": "Kas oled kindel, et soovid need duplikaadid jäädavalt kustutada?", - "delete_face": "Kustuta nägu", - "delete_key": "Kustuta võti", - "delete_library": "Kustuta kogu", - "delete_link": "Kustuta link", - "delete_local_action_prompt": "{count} kustutatud lokaalselt", - "delete_local_dialog_ok_backed_up_only": "Kustuta ainult varundatud", - "delete_local_dialog_ok_force": "Kustuta sellegipoolest", - "delete_others": "Kustuta teised", - "delete_permanently": "Kustuta jäädavalt", - "delete_permanently_action_prompt": "{count} jäädavalt kustutatud", - "delete_shared_link": "Kustuta jagatud link", - "delete_shared_link_dialog_title": "Kustuta jagatud link", - "delete_tag": "Kustuta silt", - "delete_tag_confirmation_prompt": "Kas oled kindel, et soovid sildi {tagName} kustutada?", - "delete_user": "Kustuta kasutaja", - "deleted_shared_link": "Jagatud link kustutatud", - "deletes_missing_assets": "Kustutab üksused, mis on kettalt puudu", - "description": "Kirjeldus", - "description_input_hint_text": "Lisa kirjeldus...", - "description_input_submit_error": "Viga kirjelduse muutmisel, rohkem infot leiad logist", - "deselect_all": "Eemalda kõik valikust", - "details": "Üksikasjad", - "direction": "Suund", - "disable": "Keela", - "disabled": "Välja lülitatud", - "disallow_edits": "Keela muutmine", - "discord": "Discord", - "discover": "Avasta", - "discovered_devices": "Avastatud seadmed", - "dismiss_all_errors": "Peida kõik veateated", - "dismiss_error": "Peida veateade", - "display_options": "Kuva valikud", - "display_order": "Kuvamise järjekord", - "display_original_photos": "Kuva originaalpildid", - "display_original_photos_setting_description": "Eelista üksuse vaatamisel pisipildile algset fotot, kui see on veebiga ühilduv. See võib mõjutada fotode kuvamise kiirust.", - "do_not_show_again": "Ära näita enam seda teadet", - "documentation": "Dokumentatsioon", - "done": "Tehtud", - "download": "Laadi alla", - "download_action_prompt": "{count} üksust laaditakse alla", - "download_canceled": "Allalaadimine katkestatud", - "download_complete": "Allalaadimine lõpetatud", - "download_enqueue": "Allalaadimine ootel", - "download_error": "Allalaadimise viga", - "download_failed": "Allalaadimine ebaõnnestus", - "download_finished": "Allalaadimine lõpetatud", - "download_include_embedded_motion_videos": "Manustatud videod", - "download_include_embedded_motion_videos_description": "Lisa liikuvatesse fotodesse manustatud videod eraldi failidena", - "download_notfound": "Allalaadimist ei leitud", - "download_original": "Laadi originaal alla", - "download_paused": "Allalaadimine peatatud", - "download_settings": "Allalaadimine", - "download_settings_description": "Halda üksuste allalaadimise seadeid", - "download_started": "Allalaadimine alustatud", - "download_sucess": "Allalaadimine õnnestus", - "download_sucess_android": "Üksused laaditi alla kataloogi DCIM/Immich", - "download_waiting_to_retry": "Uuesti proovimise ootel", - "downloading": "Allalaadimine", - "downloading_asset_filename": "Üksuse {filename} allalaadimine", - "downloading_from_icloud": "iCloud'ist allalaadimine", - "downloading_media": "Üksuste allalaadimine", - "drop_files_to_upload": "Failide üleslaadimiseks sikuta need ükskõik kuhu", - "duplicates": "Duplikaadid", - "duplicates_description": "Lahenda iga grupp, valides duplikaadid, kui neid on", - "duration": "Kestus", - "edit": "Muuda", - "edit_album": "Muuda albumit", - "edit_avatar": "Muuda avatari", - "edit_birthday": "Muuda sünnipäeva", - "edit_date": "Muuda kuupäeva", - "edit_date_and_time": "Muuda kuupäeva ja kellaaega", - "edit_date_and_time_action_prompt": "{count} päev ja kellaaeg muudetud", - "edit_date_and_time_by_offset": "Nihuta kuupäeva", - "edit_date_and_time_by_offset_interval": "Uus kuupäevavahemik: {from} - {to}", - "edit_description": "Muuda kirjeldust", - "edit_description_prompt": "Palun vali uus kirjeldus:", - "edit_exclusion_pattern": "Muuda välistamismustrit", - "edit_faces": "Muuda nägusid", - "edit_key": "Muuda võtit", - "edit_link": "Muuda linki", - "edit_location": "Muuda asukohta", - "edit_location_action_prompt": "{count} asukoht muudetud", - "edit_location_dialog_title": "Asukoht", - "edit_name": "Muuda nime", - "edit_people": "Muuda isikuid", - "edit_tag": "Muuda silti", - "edit_title": "Muuda pealkirja", - "edit_user": "Muuda kasutajat", - "edit_workflow": "Muuda töövoogu", - "editor": "Redaktor", - "editor_close_without_save_prompt": "Muudatusi ei salvestata", - "editor_close_without_save_title": "Sulge redaktor?", - "editor_confirm_reset_all_changes": "Kas oled kindel, et soovid kõik muudatused tühistada?", - "editor_flip_horizontal": "Peegelda horisontaalselt", - "editor_flip_vertical": "Peegelda vertikaalselt", - "editor_orientation": "Orientatsioon", - "editor_reset_all_changes": "Tühista muudatused", - "editor_rotate_left": "Pööra 90° vastupäeva", - "editor_rotate_right": "Pööra 90° päripäeva", - "email": "E-post", - "email_notifications": "E-posti teavitused", - "empty_folder": "See kaust on tühi", - "empty_trash": "Tühjenda prügikast", - "empty_trash_confirmation": "Kas oled kindel, et soovid prügikasti tühjendada? See eemaldab kõik seal olevad üksused Immich'ist jäädavalt.\nSeda tegevust ei saa tagasi võtta!", - "enable": "Luba", - "enable_backup": "Luba varundus", - "enable_biometric_auth_description": "Biomeetrilise autentimise lubamiseks sisesta oma PIN-kood", - "enabled": "Lubatud", - "end_date": "Lõppkuupäev", - "enqueued": "Järjekorras", - "enter_wifi_name": "Sisesta WiFi-võrgu nimi", - "enter_your_pin_code": "Sisesta oma PIN-kood", - "enter_your_pin_code_subtitle": "Sisesta oma PIN-kood, et lukustatud kaustale ligi pääseda", - "error": "Viga", - "error_change_sort_album": "Albumi sorteerimisjärjestuse muutmine ebaõnnestus", - "error_delete_face": "Viga näo kustutamisel", - "error_getting_places": "Viga kohtade pärimisel", - "error_loading_albums": "Viga albumite laadimisel", - "error_loading_image": "Viga pildi laadimisel", - "error_loading_partners": "Viga partnerite laadimisel: {error}", - "error_retrieving_asset_information": "Viga üksuse info pärimisel", - "error_saving_image": "Viga: {error}", - "error_tag_face_bounding_box": "Viga näo sildistamisel - ümbritseva kasti koordinaate ei õnnestunud leida", - "error_title": "Viga - midagi läks valesti", - "error_while_navigating": "Viga üksuse juurde navigeerimisel", - "errors": { - "cannot_navigate_next_asset": "Järgmise üksuse juurde liikumine ebaõnnestus", - "cannot_navigate_previous_asset": "Eelmise üksuse juurde liikumine ebaõnnestus", - "cant_apply_changes": "Muudatusi ei õnnestunud rakendada", - "cant_change_activity": "Aktiivsuse {enabled, select, true {keelamine} other {lubamine}} ebaõnnestus", - "cant_change_asset_favorite": "Üksuse lemmiku staatust ei õnnestunud muuta", - "cant_change_metadata_assets_count": "{count, plural, one {# üksuse} other {# üksuse}} metaandmeid ei õnnestunud muuta", - "cant_get_faces": "Nägusid ei õnnestunud kätte saada", - "cant_get_number_of_comments": "Kommentaare ei õnnestunud leida", - "cant_search_people": "Isikuid ei õnnestunud otsida", - "cant_search_places": "Kohti ei õnnestunud otsida", - "error_adding_assets_to_album": "Viga üksuste albumisse lisamisel", - "error_adding_users_to_album": "Viga kasutajate albumisse lisamisel", - "error_deleting_shared_user": "Viga jagatud kasutaja kustutamisel", - "error_downloading": "Viga faili {filename} allalaadimisel", - "error_hiding_buy_button": "Viga ostmise nupu peitmisel", - "error_removing_assets_from_album": "Viga üksuste albumist eemaldamisel, rohkem infot leiad konsoolilt", - "error_selecting_all_assets": "Viga kõigi üksuste valimisel", - "exclusion_pattern_already_exists": "See välistamismuster on juba olemas.", - "failed_to_create_album": "Albumi lisamine ebaõnnestus", - "failed_to_create_shared_link": "Jagatud lingi lisamine ebaõnnestus", - "failed_to_edit_shared_link": "Jagatud lingi muutmine ebaõnnestus", - "failed_to_get_people": "Isikute pärimine ebaõnnestus", - "failed_to_keep_this_delete_others": "Selle üksuse säilitamine ja ülejäänute kustutamine ebaõnnestus", - "failed_to_load_asset": "Üksuse laadimine ebaõnnestus", - "failed_to_load_assets": "Üksuste laadimine ebaõnnestus", - "failed_to_load_notifications": "Teavituste laadimine ebaõnnestus", - "failed_to_load_people": "Isikute laadimine ebaõnnestus", - "failed_to_remove_product_key": "Tootevõtme eemaldamine ebaõnnestus", - "failed_to_reset_pin_code": "PIN-koodi lähestamine ebaõnnestus", - "failed_to_stack_assets": "Üksuste virnastamine ebaõnnestus", - "failed_to_unstack_assets": "Üksuste eraldamine ebaõnnestus", - "failed_to_update_notification_status": "Teavituste seisundi uuendamine ebaõnnestus", - "incorrect_email_or_password": "Vale e-posti aadress või parool", - "library_folder_already_exists": "See imporditee on juba olemas.", - "paths_validation_failed": "{paths, plural, one {# tee} other {# teed}} ei valideerunud", - "profile_picture_transparent_pixels": "Profiilipildis ei tohi olla läbipaistvaid piksleid. Palun suumi sisse ja/või liiguta pilti.", - "quota_higher_than_disk_size": "Määratud kvoot on suurem kui kettamaht", - "something_went_wrong": "Midagi läks valesti", - "unable_to_add_album_users": "Kasutajate lisamine albumisse ebaõnnestus", - "unable_to_add_assets_to_shared_link": "Üksuste jagatud lingile lisamine ebaõnnestus", - "unable_to_add_comment": "Kommentaari lisamine ebaõnnestus", - "unable_to_add_exclusion_pattern": "Välistamismustri lisamine ebaõnnestus", - "unable_to_add_partners": "Partnerite lisamine ebaõnnestus", - "unable_to_add_remove_archive": "{archived, select, true {Üksuse arhiivist taastamine} other {Üksuse arhiveerimine}} ebaõnnestus", - "unable_to_add_remove_favorites": "Üksuse {favorite, select, true {lemmikuks lisamine} other {lemmikutest eemaldamine}} ebaõnnestus", - "unable_to_archive_unarchive": "{archived, select, true {Arhiveerimine} other {Arhiivist taastamine}} ebaõnnestus", - "unable_to_change_album_user_role": "Kasutaja rolli albumis muutmine ebaõnnestus", - "unable_to_change_date": "Kuupäeva muutmine ebaõnnestus", - "unable_to_change_description": "Kirjelduse muutmine ebaõnnestus", - "unable_to_change_favorite": "Üksuse lemmiku staatuse muutmine ebaõnnestus", - "unable_to_change_location": "Asukoha muutmine ebaõnnestus", - "unable_to_change_password": "Parooli muutmine ebaõnnestus", - "unable_to_change_visibility": "{count, plural, one {# isiku} other {# isiku}} nähtavuse muutmine ebaõnnestus", - "unable_to_complete_oauth_login": "OAuth sisselogimine ebaõnnestus", - "unable_to_connect": "Ühendumine ebaõnnestus", - "unable_to_copy_to_clipboard": "Ei saanud kopeerida lõikelauale, kontrolli, kas kasutad lehte üle https-i", - "unable_to_create": "Töövoo lisamine ebaõnnestus", - "unable_to_create_admin_account": "Administraatori konto loomine ebaõnnestus", - "unable_to_create_api_key": "Uue API võtme lisamine ebaõnnestus", - "unable_to_create_library": "Kogu lisamine ebaõnnestus", - "unable_to_create_user": "Kasutaja lisamine ebaõnnestus", - "unable_to_delete_album": "Albumi kustutamine ebaõnnestus", - "unable_to_delete_asset": "Üksuse kustutamine ebaõnnestus", - "unable_to_delete_assets": "Viga üksuste kustutamisel", - "unable_to_delete_exclusion_pattern": "Välistamismustri kustutamine ebaõnnestus", - "unable_to_delete_shared_link": "Jagatud lingi kustutamine ebaõnnestus", - "unable_to_delete_user": "Kasutaja kustutamine ebaõnnestus", - "unable_to_delete_workflow": "Töövoo kustutamine ebaõnnestus", - "unable_to_download_files": "Failide allalaadimine ebaõnnestus", - "unable_to_edit_exclusion_pattern": "Välistamismustri muutmine ebaõnnestus", - "unable_to_empty_trash": "Prügikasti tühjendamine ebaõnnestus", - "unable_to_enter_fullscreen": "Täisekraanile lülitamine ebaõnnestus", - "unable_to_exit_fullscreen": "Täisekraanilt väljumine ebaõnnestus", - "unable_to_get_comments_number": "Kommentaaride arvu leidmine ebaõnnestus", - "unable_to_get_shared_link": "Jagamise lingi loomine ebaõnnestus", - "unable_to_hide_person": "Isiku peitmine ebaõnnestus", - "unable_to_link_motion_video": "Liikuva video linkimine ebaõnnestus", - "unable_to_link_oauth_account": "OAuth konto ühendamine ebaõnnestus", - "unable_to_log_out_all_devices": "Kõigist seadmetest väljalogimine ebaõnnestus", - "unable_to_log_out_device": "Seadmest väljalogimine ebaõnnestus", - "unable_to_login_with_oauth": "OAuth abil sisselogimine ebaõnnestus", - "unable_to_play_video": "Video esitamine ebaõnnestus", - "unable_to_reassign_assets_existing_person": "Üksuste {name, select, null {olemasoleva isikuga} other {isikuga {name}}} seostamine ebaõnnestus", - "unable_to_reassign_assets_new_person": "Üksuste uue isikuga seostamine ebaõnnestus", - "unable_to_refresh_user": "Kasutaja värskendamine ebaõnnestus", - "unable_to_remove_album_users": "Kasutajate albumist eemaldamine ebaõnnestus", - "unable_to_remove_api_key": "API võtme eemaldamine ebaõnnestus", - "unable_to_remove_assets_from_shared_link": "Üksuste jagatud lingilt eemaldamine ebaõnnestus", - "unable_to_remove_library": "Kogu eemaldamine ebaõnnestus", - "unable_to_remove_partner": "Partneri eemaldamine ebaõnnestus", - "unable_to_remove_reaction": "Reaktsiooni eemaldamine ebaõnnestus", - "unable_to_reset_password": "Parooli lähtestamine ebaõnnestus", - "unable_to_reset_pin_code": "PIN-koodi lähtestamine ebaõnnestus", - "unable_to_resolve_duplicate": "Duplikaadi lahendamine ebaõnnestus", - "unable_to_restore_assets": "Üksuste taastamine ebaõnnestus", - "unable_to_restore_trash": "Prügikastist taastamine ebaõnnestus", - "unable_to_restore_user": "Kasutaja taastamine ebaõnnestus", - "unable_to_save_album": "Albumi salvestamine ebaõnnestus", - "unable_to_save_api_key": "API võtme salvestamine ebaõnnestus", - "unable_to_save_date_of_birth": "Sünnikuupäeva salvestamine ebaõnnestus", - "unable_to_save_name": "Nime salvestamine ebaõnnestus", - "unable_to_save_profile": "Profiili salvestamine ebaõnnestus", - "unable_to_save_settings": "Seadete salvestamine ebaõnnestus", - "unable_to_scan_libraries": "Kogude skaneerimine ebaõnnestus", - "unable_to_scan_library": "Kogu skaneerimine ebaõnnestus", - "unable_to_set_feature_photo": "Esiletõstetud foto seadmine ebaõnnestus", - "unable_to_set_profile_picture": "Profiilipildi seadmine ebaõnnestus", - "unable_to_set_rating": "Hinnangu seadmine ebaõnnestus", - "unable_to_submit_job": "Tööte edastamine ebaõnnestus", - "unable_to_trash_asset": "Üksuse prügikasti liigutamine ebaõnnestus", - "unable_to_unlink_account": "Konto lahtiühendamine ebaõnnestus", - "unable_to_unlink_motion_video": "Liikuva video linkimise tühistamine ebaõnnestus", - "unable_to_update_album_cover": "Albumi kaanepildi muutmine ebaõnnestus", - "unable_to_update_album_info": "Albumi info muutmine ebaõnnestus", - "unable_to_update_library": "Kogu uuendamine ebaõnnestus", - "unable_to_update_location": "Asukoha muutmine ebaõnnestus", - "unable_to_update_settings": "Seadete muutmine ebaõnnestus", - "unable_to_update_timeline_display_status": "Ajajoonel kuvamise uuendamine ebaõnnestus", - "unable_to_update_user": "Kasutaja muutmine ebaõnnestus", - "unable_to_update_workflow": "Töövoo uuendamine ebaõnnestus", - "unable_to_upload_file": "Faili üleslaadimine ebaõnnestus" - }, - "errors_text": "Vead", - "exclusion_pattern": "Välistamismuster", - "exif": "Exif", - "exif_bottom_sheet_description": "Lisa kirjeldus...", - "exif_bottom_sheet_description_error": "Viga kirjelduse muutmisel", - "exif_bottom_sheet_details": "ÜKSIKASJAD", - "exif_bottom_sheet_location": "ASUKOHT", - "exif_bottom_sheet_no_description": "Kirjeldus puudub", - "exif_bottom_sheet_people": "ISIKUD", - "exif_bottom_sheet_person_add_person": "Lisa nimi", - "exit_slideshow": "Sulge slaidiesitlus", - "expand_all": "Näita kõik", - "experimental_settings_new_asset_list_subtitle": "Töös", - "experimental_settings_new_asset_list_title": "Luba eksperimentaalne fotoruudistik", - "experimental_settings_subtitle": "Kasuta omal vastutusel!", - "experimental_settings_title": "Eksperimentaalne", - "expire_after": "Aegub", - "expired": "Aegunud", - "expires_date": "Aegub {date}", - "explore": "Avasta", - "explorer": "Brauser", - "export": "Ekspordi", - "export_as_json": "Ekspordi JSON-formaati", - "export_database": "Ekspordi andmebaas", - "export_database_description": "Ekspordi SQLite andmebaas", - "extension": "Laiend", - "external": "Väline", - "external_libraries": "Välised kogud", - "external_network": "Väline võrk", - "external_network_sheet_info": "Kui seade ei ole eelistatud WiFi-võrgus, ühendub rakendus serveriga allolevatest URL-idest esimese kättesaadava kaudu, alustades ülevalt", - "face_unassigned": "Seostamata", - "failed": "Ebaõnnestus", - "failed_count": "Ebaõnnestunud: {count}", - "failed_to_authenticate": "Autentimine ebaõnnestus", - "failed_to_load_assets": "Üksuste laadimine ebaõnnestus", - "failed_to_load_folder": "Kausta laadimine ebaõnnestus", - "favorite": "Lemmik", - "favorite_action_prompt": "{count} lisatud lemmikutesse", - "favorite_or_unfavorite_photo": "Lisa foto lemmikutesse või eemalda lemmikutest", - "favorites": "Lemmikud", - "favorites_page_no_favorites": "Lemmikuid üksuseid ei leitud", - "feature_photo_updated": "Esiletõstetud foto muudetud", - "features": "Funktsioonid", - "features_in_development": "Arendusjärgus olevad funktsioonid", - "features_setting_description": "Halda rakenduse funktsioone", - "file_name_or_extension": "Failinimi või -laiend", - "file_name_text": "Faili nimi", - "file_name_with_value": "Faili nimi: {file_name}", - "file_size": "Failisuurus", - "filename": "Failinimi", - "filetype": "Failitüüp", - "filter": "Filter", - "filter_description": "Tingimused, mille alusel üksuseid filtreerida", - "filter_people": "Filtreeri isikuid", - "filter_places": "Filtreeri kohti", - "filters": "Filtrid", - "find_them_fast": "Leia teda kiiresti nime järgi otsides", - "first": "Esimene", - "fix_incorrect_match": "Paranda ebaõige vaste", - "folder": "Kaust", - "folder_not_found": "Kausta ei leitud", - "folders": "Kaustad", - "folders_feature_description": "Kaustavaate abil failisüsteemis olevate fotode ja videote sirvimine", - "forgot_pin_code_question": "Unustasid oma PIN-koodi?", - "forward": "Edasi", - "free_up_space": "Vabasta talletusruumi", - "free_up_space_description": "Liiguta varundatud fotod ja videod prügikasti, et talletusruumi vabastada. Serveris olevad koopiad jäävad alles.", - "free_up_space_settings_subtitle": "Vabasta seadme talletusruumi", - "full_path": "Täielik tee: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "See funktsionaalsus laadib töötamiseks Google'st väliseid ressursse.", - "general": "Üldine", - "geolocation_instruction_location": "Klõpsa GPS-koordinaatidega üksusel, et kasutada selle asukohta, või vali asukoht otse kaardilt", - "get_help": "Küsi abi", - "get_people_error": "Viga isikute pärimisel", - "get_wifiname_error": "WiFi-võrgu nime ei õnnestunud lugeda. Veendu, et oled andnud vajalikud load ja oled WiFi-võrguga ühendatud", - "getting_started": "Alustamine", - "go_back": "Tagasi", - "go_to_folder": "Mine kausta", - "go_to_search": "Otsingusse", - "gps": "GPS", - "gps_missing": "GPS puudub", - "grant_permission": "Anna luba", - "group_albums_by": "Grupeeri albumid...", - "group_country": "Grupeeri riigi kaupa", - "group_no": "Ära grupeeri", - "group_owner": "Grupeeri omaniku kaupa", - "group_places_by": "Grupeeri kohad...", - "group_year": "Grupeeri aasta kaupa", - "haptic_feedback_switch": "Luba haptiline tagasiside", - "haptic_feedback_title": "Haptiline tagasiside", - "has_quota": "On kvoot", - "hash_asset": "Arvuta üksuse räsi", - "hashed_assets": "Räsiga üksused", - "hashing": "Räsi arvutamine", - "header_settings_add_header_tip": "Lisa päis", - "header_settings_field_validator_msg": "Väärtus ei saa olla tühi", - "header_settings_header_name_input": "Päise nimi", - "header_settings_header_value_input": "Päise väärtus", - "headers_settings_tile_title": "Kohandatud vaheserveri päised", - "height": "Kõrgus", - "hi_user": "Tere {name} ({email})", - "hide_all_people": "Peida kõik isikud", - "hide_gallery": "Peida galerii", - "hide_named_person": "Peida isik {name}", - "hide_password": "Peida parool", - "hide_person": "Peida isik", - "hide_schema": "Peida skeem", - "hide_text_recognition": "Peida tekstituvastus", - "hide_unnamed_people": "Peida nimetud isikud", - "home_page_add_to_album_conflicts": "{added} üksust lisati albumisse {album}. {failed} üksust oli juba albumis.", - "home_page_add_to_album_err_local": "Lokaalseid üksuseid ei saa veel albumisse lisada, jäetakse vahele", - "home_page_add_to_album_success": "{added} üksust lisati albumisse {album}.", - "home_page_album_err_partner": "Partneri üksuseid ei saa veel albumisse lisada, jäetakse vahele", - "home_page_archive_err_local": "Lokaalseid üksuseid ei saa veel arhiveerida, jäetakse vahele", - "home_page_archive_err_partner": "Partneri üksuseid ei saa arhiveerida, jäetakse vahele", - "home_page_building_timeline": "Ajajoone koostamine", - "home_page_delete_err_partner": "Partneri üksuseid ei saa kustutada, jäetakse vahele", - "home_page_delete_remote_err_local": "Kaugkustutamise valikus on lokaalsed üksused, jäetakse vahele", - "home_page_favorite_err_local": "Lokaalseid üksuseid ei saa lemmikuks märkida, jäetakse vahele", - "home_page_favorite_err_partner": "Partneri üksuseid ei saa lemmikuks märkida, jäetakse vahele", - "home_page_first_time_notice": "Kui see on su esimene kord rakendust kasutada, vali varunduse album, et ajajoon saaks sellest fotosid ja videosid kuvada", - "home_page_locked_error_local": "Lokaalseid üksuseid ei saa lukustatud kausta liigutada, jäetakse vahele", - "home_page_locked_error_partner": "Partneri üksuseid ei saa lukustatud kausta lisada, jäetakse vahele", - "home_page_share_err_local": "Lokaalseid üksuseid ei saa lingiga jagada, jäetakse vahele", - "home_page_upload_err_limit": "Korraga saab üles laadida ainult 30 üksust, jäetakse vahele", - "host": "Host", - "hour": "Tund", - "hours": "Tundi", - "id": "ID", - "idle": "Jõude", - "ignore_icloud_photos": "Ignoreeri iCloud fotosid", - "ignore_icloud_photos_description": "Fotosid, mis on iCloud'is, ei laadita üles Immich'i serverisse", - "image": "Pilt", - "image_alt_text_date": "{isVideo, select, true {Video} other {Pilt}} tehtud {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} koos isikuga {person1}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} koos isikutega {person1} ja {person2}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} koos isikutega {person1}, {person2} ja {person3}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} koos {person1}, {person2} ja veel {additionalCount, number} isikuga", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos isikuga {person1}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos isikutega {person1} ja {person2}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos isikutega {person1}, {person2} ja {person3}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos {person1}, {person2} ja veel {additionalCount, number} isikuga", - "image_saved_successfully": "Pilt salvestatud", - "image_viewer_page_state_provider_download_started": "Allalaadimine alustatud", - "image_viewer_page_state_provider_download_success": "Allalaadimine õnnestus", - "image_viewer_page_state_provider_share_error": "Jagamise viga", - "immich_logo": "Immich'i logo", - "immich_web_interface": "Immich'i veebiliides", - "import_from_json": "Impordi JSON-formaadist", - "import_path": "Imporditee", - "in_albums": "{count, plural, one {# albumis} other {# albumis}}", - "in_archive": "Arhiivis", - "in_year": "Aastal {year}", - "in_year_selector": "Aastal", - "include_archived": "Kaasa arhiveeritud", - "include_shared_albums": "Kaasa jagatud albumid", - "include_shared_partner_assets": "Kaasa partneri jagatud üksused", - "individual_share": "Jagatud üksus", - "individual_shares": "Jagatud üksused", - "info": "Info", - "interval": { - "day_at_onepm": "Iga päev kell 13", - "hours": "Iga {hours, plural, one {tunni} other {{hours, number} tunni}} tagant", - "night_at_midnight": "Iga päev keskööl", - "night_at_twoam": "Iga öö kell 2" - }, - "invalid_date": "Vigane kuupäev", - "invalid_date_format": "Vigane kuupäevaformaat", - "invite_people": "Kutsu inimesi", - "invite_to_album": "Kutsu albumisse", - "ios_debug_info_fetch_ran_at": "Andmed laaditi {dateTime}", - "ios_debug_info_last_sync_at": "Viimane sünkroonimine {dateTime}", - "ios_debug_info_no_processes_queued": "Taustaprotsesse pole järjekorras", - "ios_debug_info_no_sync_yet": "Taustal sünkroonimise tööde pole veel käinud", - "ios_debug_info_processes_queued": "{count, plural, one {{count} taustaprotsess järjekorras} other {{count} taustaprotsessi järjekorras}}", - "ios_debug_info_processing_ran_at": "Töötlemine toimus {dateTime}", - "items_count": "{count, plural, one {# üksus} other {# üksust}}", - "jobs": "Tööted", - "json_editor": "JSON-redaktor", - "json_error": "JSON-i viga", - "keep": "Jäta alles", - "keep_albums": "Jäta albumid alles", - "keep_albums_count": "{count} {count, plural, one {album} other {albumit}} jäetakse alles", - "keep_all": "Jäta kõik alles", - "keep_description": "Vali, mis talletusruumi vabastamise käigus su seadmesse alles jääb.", - "keep_favorites": "Jäta lemmikud alles", - "keep_on_device": "Hoia seadmes", - "keep_on_device_hint": "Vali üksused, mida selles seadmes hoida", - "keep_this_delete_others": "Säilita see, kustuta ülejäänud", - "keeping": "Jäetakse alles: {items}", - "kept_this_deleted_others": "See üksus säilitatud ning {count, plural, one {# üksus} other {# üksust}} kustutatud", - "keyboard_shortcuts": "Kiirklahvid", - "language": "Keel", - "language_no_results_subtitle": "Proovi otsinguterminit muuta", - "language_no_results_title": "Ühtegi keelt ei leitud", - "language_search_hint": "Otsi keeli...", - "language_setting_description": "Vali oma eelistatud keel", - "large_files": "Suured failid", - "last": "Viimane", - "last_months": "{count, plural, one {Eelmine kuu} other {Eelmised # kuud}}", - "last_seen": "Viimati nähtud", - "latest_version": "Uusim versioon", - "latitude": "Laiuskraad", - "leave": "Lahku", - "leave_album": "Lahku albumist", - "lens_model": "Läätse mudel", - "let_others_respond": "Luba teistel vastata", - "level": "Tase", - "library": "Kogu", - "library_add_folder": "Lisa kaust", - "library_edit_folder": "Muuda kausta", - "library_options": "Kogu seaded", - "library_page_device_albums": "Albumid seadmes", - "library_page_new_album": "Uus album", - "library_page_sort_asset_count": "Üksuste arv", - "library_page_sort_created": "Loomise aeg", - "library_page_sort_last_modified": "Viimase muutmise aeg", - "library_page_sort_title": "Albumi pealkiri", - "licenses": "Litsentsid", - "light": "Hele", - "like": "Meeldib", - "like_deleted": "Meeldimine kustutatud", - "link_motion_video": "Lingi liikuv video", - "link_to_oauth": "Ühenda OAuth", - "linked_oauth_account": "OAuth konto ühendatud", - "list": "Loend", - "loading": "Laadimine", - "loading_search_results_failed": "Otsitulemuste laadimine ebaõnnestus", - "local": "Lokaalsed", - "local_asset_cast_failed": "Ei saa edastada üksust, mis pole serverisse üles laaditud", - "local_assets": "Lokaalsed üksused", - "local_id": "Lokaalne ID", - "local_media_summary": "Lokaalsete üksuste kokkuvõte", - "local_network": "Kohalik võrk", - "local_network_sheet_info": "Rakendus ühendub valitud Wi-Fi võrgus olles serveriga selle URL-i kaudu", - "location": "Asukoht", - "location_permission": "Asukoha luba", - "location_permission_content": "Automaatseks ümberlülitumiseks vajab Immich täpse asukoha luba, et saaks lugeda aktiivse WiFi-võrgu nime", - "location_picker_choose_on_map": "Vali kaardil", - "location_picker_latitude_error": "Sisesta korrektne laiuskraad", - "location_picker_latitude_hint": "Sisesta laiuskraad siia", - "location_picker_longitude_error": "Sisesta korrektne pikkuskraad", - "location_picker_longitude_hint": "Sisesta pikkuskraad siia", - "lock": "Lukusta", - "locked_folder": "Lukustatud kaust", - "log_detail_title": "Logi detailid", - "log_out": "Logi välja", - "log_out_all_devices": "Logi kõigist seadmetest välja", - "logged_in_as": "Logitud sisse kasutajana {user}", - "logged_out_all_devices": "Kõigist seadmetest välja logitud", - "logged_out_device": "Seadmest välja logitud", - "login": "Logi sisse", - "login_disabled": "Sisselogimine on keelatud", - "login_form_api_exception": "API viga. Kontrolli serveri URL-i ja proovi uuesti.", - "login_form_back_button_text": "Tagasi", - "login_form_email_hint": "sinunimi@email.com", - "login_form_endpoint_hint": "http://serveri-ip:port", - "login_form_endpoint_url": "Serveri lõpp-punkti URL", - "login_form_err_http": "Palun täpsusta http:// või https://", - "login_form_err_invalid_email": "Vigane e-posti aadress", - "login_form_err_invalid_url": "Vigane URL", - "login_form_err_leading_whitespace": "Eelnevad tühikud", - "login_form_err_trailing_whitespace": "Järgnevad tühikud", - "login_form_failed_get_oauth_server_config": "Viga OAuth abil sisenemisel, kontrolli serveri URL-i", - "login_form_failed_get_oauth_server_disable": "OAuth funktsionaalsus ei ole selles serveris saadaval", - "login_form_failed_login": "Viga sisselogimisel, kontrolli serveri URL-i, e-posti aadressi ja parooli", - "login_form_handshake_exception": "Serveriga suhtlemisel tekkis kätlemise viga. Kui kasutad endasigneeritud sertifikaati, luba seadetes endasigneeritud sertifikaatide tugi.", - "login_form_password_hint": "parool", - "login_form_save_login": "Jää sisselogituks", - "login_form_server_empty": "Sisesta serveri URL.", - "login_form_server_error": "Serveriga ühendumine ebaõnnestus.", - "login_has_been_disabled": "Sisselogimine on keelatud.", - "login_password_changed_error": "Parooli muutmisel tekkis viga", - "login_password_changed_success": "Parool edukalt uuendatud", - "logout_all_device_confirmation": "Kas oled kindel, et soovid kõigist seadmetest välja logida?", - "logout_this_device_confirmation": "Kas oled kindel, et soovid sellest seadmest välja logida?", - "logs": "Logid", - "longitude": "Pikkuskraad", - "look": "Välimus", - "loop_videos": "Taasesita videod", - "loop_videos_description": "Lülita sisse, et detailvaates videot automaatselt taasesitada.", - "main_branch_warning": "Sa kasutad arendusversiooni; soovitame tungivalt kasutada väljalaskeversiooni!", - "main_menu": "Peamenüü", - "maintenance_action_restore": "Andmebaasi taastamine", - "maintenance_description": "Immich on hooldusrežiimis.", - "maintenance_end": "Lõpeta hooldusrežiim", - "maintenance_end_error": "Hooldusrežiimi lõpetamine ebaõnnestus.", - "maintenance_logged_in_as": "Logitud sisse kasutajana {user}", - "maintenance_restore_from_backup": "Taasta varukoopiast", - "maintenance_restore_library": "Taasta oma kogu", - "maintenance_restore_library_confirm": "Kui kõik tundub õige, jätka varukoopiast taastamisega!", - "maintenance_restore_library_description": "Andmebaasi taastamine", - "maintenance_restore_library_folder_has_files": "Kaustas {folder} on {count} kaust(a)", - "maintenance_restore_library_folder_no_files": "Kaustas {folder} ei ole faile!", - "maintenance_restore_library_folder_pass": "loetav ja kirjutatav", - "maintenance_restore_library_folder_read_fail": "mitteloetav", - "maintenance_restore_library_folder_write_fail": "mittekirjutatav", - "maintenance_restore_library_hint_missing_files": "Olulised failid võivad puudu olla", - "maintenance_restore_library_hint_regenerate_later": "Saad need hiljem seadetes taastekitada", - "maintenance_restore_library_hint_storage_template_missing_files": "Kasutad talletusmalli? Faile võib puudu olla", - "maintenance_restore_library_loading": "Tervikluskontrollide ja heuristika laadimine…", - "maintenance_task_backup": "Olemasoleva andmebaasi varukoopia loomine…", - "maintenance_task_migrations": "Andmebaasi migratsioonide käivitamine…", - "maintenance_task_restore": "Valitud varukoopiast taastamine…", - "maintenance_task_rollback": "Taaste ebaõnnestus, pöördutakse tagasi taastepunkti…", - "maintenance_title": "Ajutiselt mittesaadaval", - "make": "Mark", - "manage_geolocation": "Halda asukohta", - "manage_media_access_rationale": "Seda luba on vaja üksuste prügikasti liigutamiseks ja sealt taastamiseks.", - "manage_media_access_settings": "Ava seaded", - "manage_media_access_subtitle": "Luba Immich'i rakendusel multimeediafaile hallata ja liigutada.", - "manage_media_access_title": "Üksuste haldamise ligipääs", - "manage_shared_links": "Halda jagatud linke", - "manage_sharing_with_partners": "Halda partneritega jagamist", - "manage_the_app_settings": "Halda rakenduse seadeid", - "manage_your_account": "Halda oma kontot", - "manage_your_api_keys": "Halda oma API võtmeid", - "manage_your_devices": "Halda oma autenditud seadmeid", - "manage_your_oauth_connection": "Halda oma OAuth ühendust", - "map": "Kaart", - "map_assets_in_bounds": "{count, plural, =0 {Selles piirkonnas fotosid pole} one {# foto} other {# fotot}}", - "map_cannot_get_user_location": "Ei saa kasutaja asukohta tuvastada", - "map_location_dialog_yes": "Jah", - "map_location_picker_page_use_location": "Kasuta seda asukohta", - "map_location_service_disabled_content": "Praeguse asukoha üksuste kuvamiseks tuleb lubada asukoha teenus. Kas soovid seda praegu lubada?", - "map_location_service_disabled_title": "Asukoha teenus keelatud", - "map_marker_for_images": "Kaardimarker kohas {city}, {country} tehtud piltide jaoks", - "map_marker_with_image": "Kaardimarker pildiga", - "map_no_location_permission_content": "Praeguse asukoha üksuste kuvamiseks on vaja asukoha luba. Kas soovid seda praegu lubada?", - "map_no_location_permission_title": "Asukoha luba keelatud", - "map_settings": "Kaardi seaded", - "map_settings_dark_mode": "Tume režiim", - "map_settings_date_range_option_day": "Viimased 24 tundi", - "map_settings_date_range_option_days": "Viimased {days} päeva", - "map_settings_date_range_option_year": "Viimane aasta", - "map_settings_date_range_option_years": "Viimased {years} aastat", - "map_settings_dialog_title": "Kaardi seaded", - "map_settings_include_show_archived": "Kaasa arhiveeritud", - "map_settings_include_show_partners": "Kaasa partnerid", - "map_settings_only_show_favorites": "Kuva ainult lemmikud", - "map_settings_theme_settings": "Kaardi teema", - "map_zoom_to_see_photos": "Fotode nägemiseks suumi välja", - "mark_all_as_read": "Märgi kõik loetuks", - "mark_as_read": "Märgi loetuks", - "marked_all_as_read": "Kõik märgiti loetuks", - "matches": "Ühtivad failid", - "matching_assets": "Ühtivad üksused", - "media_type": "Üksuse tüüp", - "memories": "Mälestused", - "memories_all_caught_up": "Ongi kõik", - "memories_check_back_tomorrow": "Vaata homme juba uusi mälestusi", - "memories_setting_description": "Halda, mida sa oma mälestustes näed", - "memories_start_over": "Alusta uuesti", - "memories_swipe_to_close": "Sulgemiseks pühi üles", - "memory": "Mälestus", - "memory_lane_title": "Mälestus {title}", - "menu": "Menüü", - "merge": "Ühenda", - "merge_people": "Ühenda isikud", - "merge_people_limit": "Korraga saab ühendada kuni 5 nägu", - "merge_people_prompt": "Kas soovid need isikud ühendada? Seda tegevust ei saa tagasi võtta.", - "merge_people_successfully": "Isikud ühendatud", - "merged_people_count": "Ühendatud {count, plural, one {# isik} other {# isikut}}", - "minimize": "Minimeeri", - "minute": "Minut", - "minutes": "Minutit", - "mirror_horizontal": "Horisontaalne", - "mirror_vertical": "Vertikaalne", - "missing": "Puuduvad", - "mobile_app": "Mobiilirakendus", - "mobile_app_download_onboarding_note": "Mobiilirakenduse allalaadimiseks kasuta järgnevaid valikuid", - "model": "Mudel", - "month": "Kuu", - "monthly_title_text_date_format": "MMMM y", - "more": "Rohkem", - "move": "Liiguta", - "move_down": "Liiguta alla", - "move_off_locked_folder": "Liiguta lukustatud kaustast välja", - "move_to": "Liiguta", - "move_to_device_trash": "Liiguta seadme prügikasti", - "move_to_lock_folder_action_prompt": "{count} lisatud lukustatud kausta", - "move_to_locked_folder": "Liiguta lukustatud kausta", - "move_to_locked_folder_confirmation": "Need fotod ja videod eemaldatakse kõigist albumitest ning nad on nähtavad ainult lukustatud kaustas", - "move_up": "Liiguta üles", - "moved_to_archive": "{count, plural, one {# üksus} other {# üksust}} liigutatud arhiivi", - "moved_to_library": "{count, plural, one {# üksus} other {# üksust}} liigutatud kogusse", - "moved_to_trash": "Liigutatud prügikasti", - "multiselect_grid_edit_date_time_err_read_only": "Kirjutuskaitsega üksus(t)e kuupäeva ei saa muuta, jäetakse vahele", - "multiselect_grid_edit_gps_err_read_only": "Kirjutuskaitsega üksus(t)e asukohta ei saa muuta, jäetakse vahele", - "mute_memories": "Vaigista mälestused", - "my_albums": "Minu albumid", - "name": "Nimi", - "name_or_nickname": "Nimi või hüüdnimi", - "name_required": "Nimi on nõutud", - "navigate": "Navigeeri", - "navigate_to_time": "Navigeeri aega", - "network_requirement_photos_upload": "Kasuta fotode varundamiseks mobiilset andmesidet", - "network_requirement_videos_upload": "Kasuta videote varundamiseks mobiilset andmesidet", - "network_requirements": "Võrgu nõuded", - "network_requirements_updated": "Võrgu nõuded muutusid, varundamise järjekord lähtestatakse", - "networking_settings": "Võrguühendus", - "networking_subtitle": "Halda serveri lõpp-punkti seadeid", - "never": "Mitte kunagi", - "new_album": "Uus album", - "new_api_key": "Uus API võti", - "new_date_range": "Uus kuupäevavahemik", - "new_password": "Uus parool", - "new_person": "Uus isik", - "new_pin_code": "Uus PIN-kood", - "new_pin_code_subtitle": "See on sul esimene kord lukustatud kausta kasutada. Turvaliseks ligipääsuks loo PIN-kood", - "new_timeline": "Uus ajajoon", - "new_update": "Uus versioon", - "new_user_created": "Uus kasutaja lisatud", - "new_version_available": "UUS VERSIOON SAADAVAL", - "newest_first": "Uuemad eespool", - "next": "Järgmine", - "next_memory": "Järgmine mälestus", - "no": "Ei", - "no_actions_added": "Ühtegi tegevust pole veel lisatud", - "no_albums_found": "Albumeid ei leitud", - "no_albums_message": "Lisa album fotode ja videote organiseerimiseks", - "no_albums_with_name_yet": "Paistab, et sul pole veel ühtegi selle nimega albumit.", - "no_albums_yet": "Paistab, et sul pole veel ühtegi albumit.", - "no_archived_assets_message": "Arhiveeri fotod ja videod, et neid Fotod vaatest peita", - "no_assets_message": "Kliki esimese foto üleslaadimiseks", - "no_assets_to_show": "Pole üksuseid, mida kuvada", - "no_cast_devices_found": "Edastamise seadmeid ei leitud", - "no_checksum_local": "Kontrollsumma pole saadaval - lokaalse üksuse pärimine ebaõnnestus", - "no_checksum_remote": "Kontrollsumma pole saadaval - kaugüksuse pärimine ebaõnnestus", - "no_configuration_needed": "Seadistus pole vajalik", - "no_devices": "Autoriseeritud seadmeid pole", - "no_duplicates_found": "Ühtegi duplikaati ei leitud.", - "no_exif_info_available": "Exif info pole saadaval", - "no_explore_results_message": "Oma kogu avastamiseks laadi üles rohkem fotosid.", - "no_favorites_message": "Lisa lemmikud, et oma parimaid fotosid ja videosid kiiresti leida", - "no_filters_added": "Ühtegi filtrit pole veel lisatud", - "no_libraries_message": "Lisa väline kogu oma fotode ja videote vaatamiseks", - "no_local_assets_found": "Selle kontrollsummaga lokaalseid üksuseid ei leitud", - "no_location_set": "Asukoht pole määratud", - "no_locked_photos_message": "Lukustatud kaustas olevad fotod ja videod on peidetud ning need pole kogu sirvimisel ja otsimisel nähtavad.", - "no_name": "Nimetu", - "no_notifications": "Teavitusi pole", - "no_people_found": "Kattuvaid isikuid ei leitud", - "no_places": "Kohti ei ole", - "no_remote_assets_found": "Selle kontrollsummaga kaugüksuseid ei leitud", - "no_results": "Vasteid pole", - "no_results_description": "Proovi sünonüümi või üldisemat märksõna", - "no_shared_albums_message": "Lisa album, et fotosid ja videosid teistega jagada", - "no_uploads_in_progress": "Üleslaadimisi käimas ei ole", - "none": "Puudub", - "not_allowed": "Keelatud", - "not_available": "Pole saadaval", - "not_in_any_album": "Pole üheski albumis", - "not_selected": "Ei ole valitud", - "note_apply_storage_label_to_previously_uploaded assets": "Märkus: Et rakendada talletussilt varem üleslaaditud üksustele, käivita", - "notes": "Märkused", - "nothing_here_yet": "Siin pole veel midagi", - "notification_permission_dialog_content": "Teavituste lubamiseks mine Seadetesse ja vali lubamine.", - "notification_permission_list_tile_content": "Anna luba teavituste saatmiseks.", - "notification_permission_list_tile_enable_button": "Luba teavitused", - "notification_permission_list_tile_title": "Teavituste luba", - "notification_toggle_setting_description": "Luba e-posti teel teavitused", - "notifications": "Teavitused", - "notifications_setting_description": "Halda teavitusi", - "oauth": "OAuth", - "obtainium_configurator": "Obtainiumi seadistamine", - "obtainium_configurator_instructions": "Androidi rakenduse otse GitHub'ist paigaldamiseks ja uuendamiseks kasuta Obtainiumit. Seadistamise lingi loomiseks lisa API võti ja vali rakenduse variant", - "ocr": "OCR", - "official_immich_resources": "Ametlikud Immich'i ressursid", - "offline": "Ühendus puudub", - "offset": "Nihe", - "ok": "OK", - "oldest_first": "Vanemad eespool", - "on_this_device": "Sellel seadmel", - "onboarding": "Kasutuselevõtt", - "onboarding_locale_description": "Vali oma eelistatud keel. Saad seda hiljem seadetes muuta.", - "onboarding_privacy_description": "Järgnevad (valikulised) funktsioonid sõltuvad välistest teenustest ning neid saab igal ajal seadetest välja lülitada.", - "onboarding_server_welcome_description": "Alustamiseks rakendame su serveril mõned levinumad seaded.", - "onboarding_theme_description": "Vali oma serverile värviteema. Saad seda hiljem seadetes muuta.", - "onboarding_user_welcome_description": "Alustame!", - "onboarding_welcome_user": "Tere tulemast, {user}", - "online": "Ühendatud", - "only_favorites": "Ainult lemmikud", - "open": "Ava", - "open_in_map_view": "Ava kaardi vaates", - "open_in_openstreetmap": "Ava OpenStreetMap", - "open_the_search_filters": "Ava otsingufiltrid", - "options": "Valikud", - "or": "või", - "organize_into_albums": "Organiseeri albumitesse", - "organize_into_albums_description": "Pane olemasolevad fotod albumitesse, kasutades jooksvaid sünkroonimise seadeid", - "organize_your_library": "Korrasta oma kogu", - "original": "originaal", - "other": "Muud", - "other_devices": "Muud seadmed", - "other_entities": "Muud objektid", - "other_variables": "Muud muutujad", - "owned": "Minu omad", - "owner": "Omanik", - "page": "Leht", - "partner": "Partner", - "partner_can_access": "{partner} pääseb ligi", - "partner_can_access_assets": "Kõik su fotod ja videod, välja arvatud arhiveeritud ja kustutatud", - "partner_can_access_location": "Asukohad, kus su fotod tehti", - "partner_list_user_photos": "Kasutaja {user} fotod", - "partner_list_view_all": "Vaata kõiki", - "partner_page_empty_message": "Su fotod pole veel ühegi partneriga jagatud.", - "partner_page_no_more_users": "Pole rohkem kasutajaid, keda lisada", - "partner_page_partner_add_failed": "Partneri lisamine ebaõnnestus", - "partner_page_select_partner": "Vali partner", - "partner_page_shared_to_title": "Jagatud", - "partner_page_stop_sharing_content": "{partner} ei pääse rohkem su fotodele ligi.", - "partner_sharing": "Partneriga jagamine", - "partners": "Partnerid", - "password": "Parool", - "password_does_not_match": "Parool ei klapi", - "password_required": "Parool on nõutud", - "password_reset_success": "Parooli lähtestamine õnnestus", - "past_durations": { - "days": "{days, plural, one {Viimane päev} other {Viimased # päeva}}", - "hours": "{hours, plural, one {Viimane tund} other {Viimased # tundi}}", - "years": "{years, plural, one {Viimane aasta} other {Viimased # aastat}}" - }, - "path": "Tee", - "pattern": "Muster", - "pause": "Peata", - "pause_memories": "Peata mälestused", - "paused": "Peatatud", - "pending": "Ootel", - "people": "Isikud", - "people_edits_count": "{count, plural, one {# isik} other {# isikut}} muudetud", - "people_feature_description": "Fotode ja videote sirvimine inimeste kaupa grupeeritult", - "people_selected": "{count, plural, one {# isik valitud} other {# isikut valitud}}", - "people_sidebar_description": "Kuva külgmenüüs Isikute link", - "permanent_deletion_warning": "Jäädavalt kustutamise hoiatus", - "permanent_deletion_warning_setting_description": "Kuva hoiatust üksuste jäädaval kustutamisel", - "permanently_delete": "Kustuta jäädavalt", - "permanently_delete_assets_count": "Kustuta {count, plural, one {üksus} other {üksused}} jäädavalt", - "permanently_delete_assets_prompt": "Kas oled kindel, et soovid {count, plural, one {selle üksuse} other {need # üksust}} jäädavalt kustutada? Sellega eemaldatakse {count, plural, one {see} other {need}} ka oma albumi(te)st.", - "permanently_deleted_asset": "Üksus jäädavalt kustutatud", - "permanently_deleted_assets_count": "{count, plural, one {# üksus} other {# üksust}} jäädavalt kustutatud", - "permission": "Luba", - "permission_empty": "Luba ei tohiks olla tühi", - "permission_onboarding_back": "Tagasi", - "permission_onboarding_continue_anyway": "Jätka sellegipoolest", - "permission_onboarding_get_started": "Alusta", - "permission_onboarding_go_to_settings": "Mine seadetesse", - "permission_onboarding_permission_denied": "Luba keelatud. Immich'i kasutamiseks anna Seadetes fotode ja videote load.", - "permission_onboarding_permission_granted": "Luba antud! Oled valmis.", - "permission_onboarding_permission_limited": "Piiratud luba. Et Immich saaks tervet su galeriid varundada ja hallata, anna Seadetes luba fotodele ja videotele.", - "permission_onboarding_request": "Immich'il on vaja luba su fotode ja videote vaatamiseks.", - "person": "Isik", - "person_age_months": "{months, plural, one {# kuu} other {# kuud}} vana", - "person_age_year_months": "1 aasta {months, plural, one {# kuu} other {# kuud}} vana", - "person_age_years": "{years, plural, other {# aastat}} vana", - "person_birthdate": "Sündinud {date}", - "person_hidden": "{name}{hidden, select, true { (peidetud)} other {}}", - "person_recognized": "Isik tuvastatud", - "person_selected": "Isik valitud", - "photo_shared_all_users": "Paistab, et oled oma fotosid kõigi kasutajatega jaganud, või pole ühtegi kasutajat, kellega jagada.", - "photos": "Fotod", - "photos_and_videos": "Fotod ja videod", - "photos_count": "{count, plural, one {{count, number} foto} other {{count, number} fotot}}", - "photos_from_previous_years": "Fotod varasematest aastatest", - "photos_only": "Ainult fotod", - "pick_a_location": "Vali asukoht", - "pick_custom_range": "Kohandatud vahemik", - "pick_date_range": "Vali kuupäevavahemik", - "pin_code_changed_successfully": "PIN-kood edukalt muudetud", - "pin_code_reset_successfully": "PIN-kood edukalt lähtestatud", - "pin_code_setup_successfully": "PIN-kood edukalt seadistatud", - "pin_verification": "PIN-koodi kinnitus", - "place": "Asukoht", - "places": "Kohad", - "places_count": "{count, plural, one {{count, number} koht} other {{count, number} kohta}}", - "play": "Esita", - "play_memories": "Esita mälestused", - "play_motion_photo": "Esita liikuv foto", - "play_or_pause_video": "Esita või peata video", - "play_original_video": "Taasesita algne video", - "play_original_video_setting_description": "Eelista transkodeeritud video asemel algse video taasesitamist. Kui algne üksus ei ole ühilduv, võib taasesitamine ebaõnnestuda.", - "play_transcoded_video": "Taasesita transkodeeritud video", - "please_auth_to_access": "Ligipääsemiseks palun autendi", - "port": "Port", - "preferences_settings_subtitle": "Halda rakenduse eelistusi", - "preferences_settings_title": "Eelistused", - "preparing": "Ettevalmistamine", - "preset": "Eelseadistus", - "preview": "Eelvaade", - "previous": "Eelmine", - "previous_memory": "Eelmine mälestus", - "previous_or_next_day": "Päev edasi/tagasi", - "previous_or_next_month": "Kuu edasi/tagasi", - "previous_or_next_photo": "Foto edasi/tagasi", - "previous_or_next_year": "Aasta edasi/tagasi", - "primary": "Peamine", - "privacy": "Privaatsus", - "profile": "Profiil", - "profile_drawer_app_logs": "Logid", - "profile_drawer_client_server_up_to_date": "Klient ja server on uuendatud", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Kirjutuskaitserežiim sisse lülitatud. Väljumiseks puuduta pikalt avatari ikooni.", - "profile_image_of_user": "Kasutaja {user} profiilipilt", - "profile_picture_set": "Profiilipilt määratud.", - "public_album": "Avalik album", - "public_share": "Avalik jagamine", - "purchase_account_info": "Toetaja", - "purchase_activated_subtitle": "Aitäh, et toetad Immich'it ja avatud lähtekoodiga tarkvara", - "purchase_activated_time": "Aktiveeritud {date}", - "purchase_activated_title": "Sinu võtme aktiveerimine õnnestus", - "purchase_button_activate": "Aktiveeri", - "purchase_button_buy": "Osta", - "purchase_button_buy_immich": "Osta Immich", - "purchase_button_never_show_again": "Ära näita enam", - "purchase_button_reminder": "Tuleta mulle 30 päeva pärast meelde", - "purchase_button_remove_key": "Eemalda võti", - "purchase_button_select": "Vali", - "purchase_failed_activation": "Aktiveerimine ebaõnnestus! Kontrolli oma kirjakastist õiget tootevõtit!", - "purchase_individual_description_1": "Üksikisikule", - "purchase_individual_description_2": "Toetaja staatus", - "purchase_individual_title": "Individuaalne", - "purchase_input_suggestion": "Sul on juba tootevõti? Sisesta see allpool", - "purchase_license_subtitle": "Osta Immich, et toetada selle jätkuvat arendust", - "purchase_lifetime_description": "Eluaegne ost", - "purchase_option_title": "OSTMISE VALIKUD", - "purchase_panel_info_1": "Immich'i arendamine nõuab palju aega ja vaeva ning meie täiskohaga insenerid töötavad selle nimel, et teha see nii heaks kui vähegi võimalik. Meie missiooniks on muuta avatud lähtekoodiga tarkvara ja eetilised äritavad arendajatele jätkusuutlikuks sissetulekuallikaks ning luua privaatsust austav ökosüsteem, mis pakub tõelisi alternatiive ekspluatatiivsetele pilveteenustele.", - "purchase_panel_info_2": "Kuna oleme otsustanud maksumüüre mitte lisada, ei anna see ost sulle Immich'is lisavõimalusi. Me loodame Immich'i jätkuvaks arenduseks sinusuguste kasutajate toetusele.", - "purchase_panel_title": "Toeta projekti", - "purchase_per_server": "Serveri kohta", - "purchase_per_user": "Kasutaja kohta", - "purchase_remove_product_key": "Eemalda tootevõti", - "purchase_remove_product_key_prompt": "Kas oled kindel, et soovid tootevõtme eemaldada?", - "purchase_remove_server_product_key": "Eemalda serveri tootevõti", - "purchase_remove_server_product_key_prompt": "Kas oled kindel, et soovid serveri tootevõtme eemaldada?", - "purchase_server_description_1": "Kogu serveri jaoks", - "purchase_server_description_2": "Toetaja staatus", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Serveri tootevõtit haldab administraator", - "query_asset_id": "Päringu üksuse ID", - "queue_status": "Järjekorras {count}/{total}", - "rate_asset": "Hinda üksust", - "rating": "Hinnang", - "rating_clear": "Tühjenda hinnang", - "rating_count": "{count, plural, one {# tärn} other {# tärni}}", - "rating_description": "Kuva infopaneelis EXIF hinnangut", - "rating_set": "Hinnanguks seatud {rating, plural, one {# tärn} other {# tärni}}", - "reaction_options": "Reaktsiooni valikud", - "read_changelog": "Vaata muudatuste ülevaadet", - "readonly_mode_disabled": "Kirjutuskaitserežiim välja lülitatud", - "readonly_mode_enabled": "Kirjutuskaitserežiim sisse lülitatud", - "ready_for_upload": "Valmis üleslaadimiseks", - "reassign": "Määra uuesti", - "reassigned_assets_to_existing_person": "{count, plural, one {# üksus} other {# üksust}} seostatud {name, select, null {olemasoleva isikuga} other {isikuga {name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {# üksus} other {# üksust}} seostatud uue isikuga", - "reassing_hint": "Seosta valitud üksused olemasoleva isikuga", - "recent": "Hiljutine", - "recent-albums": "Hiljutised albumid", - "recent_searches": "Hiljutised otsingud", - "recently_added": "Hiljuti lisatud", - "recently_added_page_title": "Hiljuti lisatud", - "recently_taken": "Hiljuti tehtud", - "recently_taken_page_title": "Hiljuti tehtud", - "refresh": "Värskenda", - "refresh_encoded_videos": "Värskenda kodeeritud videod", - "refresh_faces": "Värskenda näod", - "refresh_metadata": "Värskenda metaandmed", - "refresh_thumbnails": "Värskenda pisipildid", - "refreshed": "Värskendatud", - "refreshes_every_file": "Loeb kõik olemasolevad ja uued failid uuesti", - "refreshing_encoded_video": "Kodeeritud videote värskendamine", - "refreshing_faces": "Nägude värskendamine", - "refreshing_metadata": "Metaandmete värskendamine", - "regenerating_thumbnails": "Pisipiltide uuesti genereerimine", - "remote": "Serveris", - "remote_assets": "Kaugüksused", - "remote_media_summary": "Kaugüksuste kokkuvõte", - "remove": "Eemalda", - "remove_assets_album_confirmation": "Kas oled kindel, et soovid {count, plural, one {# üksuse} other {# üksust}} albumist eemaldada?", - "remove_assets_shared_link_confirmation": "Kas oled kindel, et soovid eemaldada {count, plural, one {# üksuse} other {# üksust}} sellelt jagatud lingilt?", - "remove_assets_title": "Eemalda üksused?", - "remove_custom_date_range": "Eemalda kohandatud kuupäevavahemik", - "remove_deleted_assets": "Eemalda kustutatud üksused", - "remove_from_album": "Eemalda albumist", - "remove_from_album_action_prompt": "{count} eemaldatud albumist", - "remove_from_favorites": "Eemalda lemmikutest", - "remove_from_lock_folder_action_prompt": "{count} eemaldatud lukustatud kaustast", - "remove_from_locked_folder": "Eemalda lukustatud kaustast", - "remove_from_locked_folder_confirmation": "Kas oled kindel, et soovid need fotod ja videod lukustatud kaustast välja liigutada? Need muutuvad su kogus nähtavaks.", - "remove_from_shared_link": "Eemalda jagatud lingist", - "remove_memory": "Eemalda mälestus", - "remove_photo_from_memory": "Eemalda foto sellest mälestusest", - "remove_tag": "Eemalda silt", - "remove_url": "Eemalda URL", - "remove_user": "Eemalda kasutaja", - "removed_api_key": "API võti eemaldatud: {name}", - "removed_from_archive": "Arhiivist eemaldatud", - "removed_from_favorites": "Lemmikutest eemaldatud", - "removed_from_favorites_count": "{count, plural, other {# eemaldatud}} lemmikutest", - "removed_memory": "Mäletus eemaldatud", - "removed_photo_from_memory": "Foto mälestustest eemaldatud", - "removed_tagged_assets": "Silt eemaldatud {count, plural, one {# üksuselt} other {# üksuselt}}", - "rename": "Nimeta ümber", - "repair": "Parandus", - "repair_no_results_message": "Mittejälgitavad ja puuduvad failid kuvatakse siin", - "replace_with_upload": "Asenda üleslaadimisega", - "repository": "Koodihoidla", - "require_password": "Nõua parooli", - "require_user_to_change_password_on_first_login": "Nõua kasutajalt esmakordsel sisenemisel parooli muutmist", - "rescan": "Skaneeri uuesti", - "reset": "Lähtesta", - "reset_password": "Lähtesta parool", - "reset_people_visibility": "Lähtesta isikute nähtavus", - "reset_pin_code": "Lähtesta PIN-kood", - "reset_pin_code_description": "Kui unustasid oma PIN-koodi, võta selle lähtestamiseks ühendust serveri administraatoriga", - "reset_pin_code_success": "PIN-kood edukalt lähtestatud", - "reset_pin_code_with_password": "Saad oma PIN-koodi alati oma parooli abil lähtestada", - "reset_sqlite": "Lähtesta SQLite andmebaas", - "reset_sqlite_confirmation": "Kas oled kindel, et soovid SQLite andmebaasi lähtestada? Andmete uuesti sünkroonimiseks pead välja ja jälle sisse logima", - "reset_sqlite_success": "SQLite andmebaas edukalt lähtestatud", - "reset_to_default": "Lähtesta", - "resolution": "Resolutsioon", - "resolve_duplicates": "Lahenda duplikaadid", - "resolved_all_duplicates": "Kõik duplikaadid lahendatud", - "restore": "Taasta", - "restore_all": "Taasta kõik", - "restore_trash_action_prompt": "{count} prügikastust taastatud", - "restore_user": "Taasta kasutaja", - "restored_asset": "Üksus taastatud", - "resume": "Jätka", - "resume_paused_jobs": "Jätka {count, plural, one {# peatatud tööde} other {# peatatud töödet}}", - "retry_upload": "Proovi üleslaadimist uuesti", - "review_duplicates": "Vaata duplikaadid läbi", - "review_large_files": "Vaata suured failid läbi", - "role": "Roll", - "role_editor": "Muutja", - "role_viewer": "Vaataja", - "running": "Käimas", - "save": "Salvesta", - "save_to_gallery": "Salvesta galeriisse", - "saved": "Salvestatud", - "saved_api_key": "API võti salvestatud", - "saved_profile": "Profiil salvestatud", - "saved_settings": "Seaded salvestatud", - "say_something": "Ütle midagi", - "scaffold_body_error_occurred": "Tekkis viga", - "scan": "Otsi", - "scan_all_libraries": "Skaneeri kõik kogud", - "scan_library": "Skaneeri", - "scan_settings": "Skaneerimise seaded", - "scanning": "Otsimine", - "scanning_for_album": "Albumi skaneerimine...", - "search": "Otsi", - "search_albums": "Otsi albumeid", - "search_by_context": "Otsi konteksti alusel", - "search_by_description": "Otsi kirjelduse alusel", - "search_by_description_example": "Matkapäev Sapas", - "search_by_filename": "Otsi failinime või -laiendi järgi", - "search_by_filename_example": "st. IMG_1234.JPG või PNG", - "search_by_ocr": "Otsi OCR-i abil", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Otsi läätse mudelit...", - "search_camera_make": "Otsi kaamera marki...", - "search_camera_model": "Otsi kaamera mudelit...", - "search_city": "Otsi linna...", - "search_country": "Otsi riiki...", - "search_filter_apply": "Rakenda filter", - "search_filter_camera_title": "Vali kaamera tüüp", - "search_filter_date": "Kuupäev", - "search_filter_date_interval": "{start} kuni {end}", - "search_filter_date_title": "Vali kuupäevavahemik", - "search_filter_display_option_not_in_album": "Pole albumis", - "search_filter_display_options": "Kuva valikud", - "search_filter_filename": "Otsi failinime alusel", - "search_filter_location": "Asukoht", - "search_filter_location_title": "Vali asukoht", - "search_filter_media_type": "Üksuse tüüp", - "search_filter_media_type_title": "Vali üksuse tüüp", - "search_filter_ocr": "Otsi OCR-i abil", - "search_filter_people_title": "Vali isikud", - "search_filter_star_rating": "Hinnang", - "search_for": "Otsi", - "search_for_existing_person": "Otsi olemasolevat isikut", - "search_no_more_result": "Rohkem vasteid pole", - "search_no_people": "Isikuid ei ole", - "search_no_people_named": "Ei ole isikuid nimega \"{name}\"", - "search_no_result": "Vasteid ei leitud, proovi muud otsinguterminit või kombinatsiooni", - "search_options": "Otsingu valikud", - "search_page_categories": "Kategooriad", - "search_page_motion_photos": "Liikuvad fotod", - "search_page_no_objects": "Objektide info pole saadaval", - "search_page_no_places": "Kohtade info pole saadaval", - "search_page_screenshots": "Ekraanipildid", - "search_page_search_photos_videos": "Otsi oma fotosid ja videosid", - "search_page_selfies": "Selfid", - "search_page_things": "Asjad", - "search_page_view_all_button": "Vaata kõiki", - "search_page_your_activity": "Sinu aktiivsus", - "search_page_your_map": "Sinu kaart", - "search_people": "Otsi inimesi", - "search_places": "Otsi kohti", - "search_rating": "Otsi hinnangu järgi...", - "search_result_page_new_search_hint": "Uus otsing", - "search_settings": "Otsi seadeid", - "search_state": "Otsi osariiki...", - "search_suggestion_list_smart_search_hint_1": "Nutiotsing on vaikimisi lubatud, metaandmete otsimiseks kasuta süntaksit ", - "search_suggestion_list_smart_search_hint_2": "m:sinu-otsingu-termin", - "search_tags": "Otsi silte...", - "search_timezone": "Otsi ajavööndit...", - "search_type": "Otsingu tüüp", - "search_your_photos": "Otsi oma fotosid", - "searching_locales": "Lokaatide otsimine...", - "second": "Sekund", - "see_all_people": "Vaata kõiki isikuid", - "select": "Vali", - "select_album": "Vali album", - "select_album_cover": "Vali albumi kaanepilt", - "select_albums": "Vali albumid", - "select_all": "Vali kõik", - "select_all_duplicates": "Vali kõik duplikaadid", - "select_all_in": "Vali kõik grupis {group}", - "select_avatar_color": "Vali avatari värv", - "select_count": "{count, plural, one {Vali #} other {Vali #}}", - "select_cutoff_date": "Vali kuupäev", - "select_face": "Vali nägu", - "select_featured_photo": "Vali esiletõstetud foto", - "select_from_computer": "Vali arvutist", - "select_keep_all": "Vali jäta kõik alles", - "select_library_owner": "Vali kogu omanik", - "select_new_face": "Vali uus nägu", - "select_people": "Vali isikud", - "select_person": "Vali isik", - "select_person_to_tag": "Vali sildistamiseks isik", - "select_photos": "Vali fotod", - "select_trash_all": "Vali kõik prügikasti", - "select_user_for_sharing_page_err_album": "Albumi lisamine ebaõnnestus", - "selected": "Valitud", - "selected_count": "{count, plural, other {# valitud}}", - "selected_gps_coordinates": "Valitud GPS-koordinaadid", - "send_message": "Saada sõnum", - "send_welcome_email": "Saada tervituskiri", - "server_endpoint": "Serveri lõpp-punkt", - "server_info_box_app_version": "Rakenduse versioon", - "server_info_box_server_url": "Serveri URL", - "server_offline": "Serveriga ühendus puudub", - "server_online": "Server ühendatud", - "server_privacy": "Serveri privaatsus", - "server_restarting_description": "Leht värskendatakse hetkeliselt.", - "server_restarting_title": "Server taaskäivitub", - "server_stats": "Serveri statistika", - "server_update_available": "Serveri uuendus on saadaval", - "server_version": "Serveri versioon", - "set": "Määra", - "set_as_album_cover": "Sea albumi kaanepildiks", - "set_as_featured_photo": "Sea esiletõstetud fotoks", - "set_as_profile_picture": "Sea profiilipildiks", - "set_date_of_birth": "Määra sünnikuupäev", - "set_profile_picture": "Sea profiilipilt", - "set_slideshow_to_fullscreen": "Kuva slaidiesitlus täisekraanil", - "set_stack_primary_asset": "Sea peamiseks üksuseks", - "setting_image_viewer_help": "Detailivaatur laadib kõigepealt väikese pisipildi, seejärel keskmises mõõdus eelvaate (kui lubatud) ja lõpuks originaalpildi (kui lubatud).", - "setting_image_viewer_original_subtitle": "Lülita sisse, et laadida algne täisresolutsiooniga pilt (suur!). Lülita välja, et vähendada andmekasutust (nii võrgu kui seadme puhvri).", - "setting_image_viewer_original_title": "Laadi algne pilt", - "setting_image_viewer_preview_subtitle": "Luba keskmise resolutsiooniga pildi laadimine. Keela, et laadida kohe originaalpilt või kasutada ainult pisipilti.", - "setting_image_viewer_preview_title": "Laadi pildi eelvaade", - "setting_image_viewer_title": "Pildid", - "setting_languages_apply": "Rakenda", - "setting_languages_subtitle": "Muuda rakenduse keelt", - "setting_notifications_notify_failures_grace_period": "Teavita taustal varundamise vigadest: {duration}", - "setting_notifications_notify_hours": "{count} tundi", - "setting_notifications_notify_immediately": "kohe", - "setting_notifications_notify_minutes": "{count} minutit", - "setting_notifications_notify_never": "mitte kunagi", - "setting_notifications_notify_seconds": "{count} sekundit", - "setting_notifications_single_progress_subtitle": "Detailne üleslaadimise edenemise info üksuse kohta", - "setting_notifications_single_progress_title": "Kuva taustal varundamise detailset edenemist", - "setting_notifications_subtitle": "Halda oma teavituste eelistusi", - "setting_notifications_total_progress_subtitle": "Üldine üleslaadimise edenemine (üksuseid tehtud/kokku)", - "setting_notifications_total_progress_title": "Kuva taustal varundamise üldist edenemist", - "setting_video_viewer_auto_play_subtitle": "Alusta videote avamisel automaatselt taasesitust", - "setting_video_viewer_auto_play_title": "Esita videod automaatselt", - "setting_video_viewer_looping_title": "Taasesitus", - "setting_video_viewer_original_video_subtitle": "Esita serverist video voogedastamisel originaal, isegi kui transkodeeritud video on saadaval. Võib põhjustada puhverdamist. Lokaalselt saadaolevad videod mängitakse originaalkvaliteediga sõltumata sellest seadest.", - "setting_video_viewer_original_video_title": "Sunni originaalvideo", - "settings": "Seaded", - "settings_require_restart": "Selle seade rakendamiseks palun taaskäivita Immich", - "settings_saved": "Seaded salvestatud", - "setup_pin_code": "Seadista PIN-kood", - "share": "Jaga", - "share_action_prompt": "Jagatud {count} üksust", - "share_add_photos": "Lisa fotosid", - "share_assets_selected": "{count} valitud", - "share_dialog_preparing": "Ettevalmistamine...", - "share_link": "Jaga linki", - "shared": "Jagatud", - "shared_album_activities_input_disable": "Kommentaarid on keelatud", - "shared_album_activity_remove_content": "Kas soovid selle tegevuse kustutada?", - "shared_album_activity_remove_title": "Kustuta tegevus", - "shared_album_section_people_action_error": "Viga albumist eemaldamisel/lahkumisel", - "shared_album_section_people_action_leave": "Eemalda kasutaja albumist", - "shared_album_section_people_action_remove_user": "Eemalda kasutaja albumist", - "shared_album_section_people_title": "ISIKUD", - "shared_by": "Jagas", - "shared_by_user": "Jagas {user}", - "shared_by_you": "Jagasid sina", - "shared_from_partner": "Fotod partnerilt {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} üles laaditud", - "shared_link_app_bar_title": "Jagatud lingid", - "shared_link_clipboard_copied_massage": "Kopeeritud lõikelauale", - "shared_link_clipboard_text": "Link: {link}\nParool: {password}", - "shared_link_create_error": "Viga jagatud lingi loomisel", - "shared_link_custom_url_description": "Ligipääs jagatud lingile kohandatud URL-i kaudu", - "shared_link_edit_description_hint": "Sisesta jagatud lingi kirjeldus", - "shared_link_edit_expire_after_option_day": "1 päev", - "shared_link_edit_expire_after_option_days": "{count} päeva", - "shared_link_edit_expire_after_option_hour": "1 tund", - "shared_link_edit_expire_after_option_hours": "{count} tundi", - "shared_link_edit_expire_after_option_minute": "1 minut", - "shared_link_edit_expire_after_option_minutes": "{count} minutit", - "shared_link_edit_expire_after_option_months": "{count} kuud", - "shared_link_edit_expire_after_option_year": "{count} aasta", - "shared_link_edit_password_hint": "Sisesta jagatud lingi parool", - "shared_link_edit_submit_button": "Muuda link", - "shared_link_error_server_url_fetch": "Serveri URL-i ei leitud", - "shared_link_expires_day": "Aegub {count} päeva pärast", - "shared_link_expires_days": "Aegub {count} päeva pärast", - "shared_link_expires_hour": "Aegub {count} tunni pärast", - "shared_link_expires_hours": "Aegub {count} tunni pärast", - "shared_link_expires_minute": "Aegub {count} minuti pärast", - "shared_link_expires_minutes": "Aegub {count} minuti pärast", - "shared_link_expires_never": "Ei aegu", - "shared_link_expires_second": "Aegub {count} sekundi pärast", - "shared_link_expires_seconds": "Aegub {count} sekundi pärast", - "shared_link_individual_shared": "Individuaalselt jagatud", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Halda jagatud linke", - "shared_link_options": "Jagatud lingi valikud", - "shared_link_password_description": "Nõua jagatud lingile ligi pääsemiseks parooli", - "shared_links": "Jagatud lingid", - "shared_links_description": "Jaga fotosid ja videosid lingiga", - "shared_photos_and_videos_count": "{assetCount, plural, other {# jagatud fotot ja videot.}}", - "shared_with_me": "Minuga jagatud", - "shared_with_partner": "Jagatud partneriga {partner}", - "sharing": "Jagamine", - "sharing_enter_password": "Palun sisesta selle lehe vaatamiseks salasõna.", - "sharing_page_album": "Jagatud albumid", - "sharing_page_description": "Loo jagatud albumeid, et jagada fotosid ja videosid inimestega oma võrgustikus.", - "sharing_page_empty_list": "TÜHI LOEND", - "sharing_sidebar_description": "Kuva külgmenüüs Jagamise linki", - "sharing_silver_appbar_create_shared_album": "Uus jagatud album", - "sharing_silver_appbar_share_partner": "Jaga partneriga", - "shift_to_permanent_delete": "vajuta ⇧, et üksus jäädavalt kustutada", - "show_album_options": "Näita albumi valikuid", - "show_albums": "Näita albumeid", - "show_all_people": "Näita kõiki isikuid", - "show_and_hide_people": "Näita ja peida isikuid", - "show_file_location": "Näita faili asukohta", - "show_gallery": "Näita galeriid", - "show_hidden_people": "Kuva peidetud inimesed", - "show_in_timeline": "Näita ajajoonel", - "show_in_timeline_setting_description": "Kuva oma ajajoonel selle kasutaja fotosid ja videosid", - "show_keyboard_shortcuts": "Kuva kiirklahvid", - "show_metadata": "Kuva metaandmed", - "show_or_hide_info": "Kuva või peida info", - "show_password": "Kuva parooli", - "show_person_options": "Näita isiku valikuid", - "show_progress_bar": "Kuva edenemisriba", - "show_schema": "Kuva skeem", - "show_search_options": "Kuva otsingu valikud", - "show_shared_links": "Näita jagatud linke", - "show_slideshow_transition": "Kuva slaidiesitluse üleminekud", - "show_supporter_badge": "Toetaja märk", - "show_supporter_badge_description": "Kuva toetaja märki", - "show_text_recognition": "Kuva tekstituvastust", - "show_text_search_menu": "Kuva tekstiotsingu menüüd", - "shuffle": "Juhuslik", - "sidebar": "Külgmenüü", - "sidebar_display_description": "Kuva külgmenüüs linki vaatele", - "sign_out": "Logi välja", - "sign_up": "Registreeru", - "size": "Suurus", - "skip_to_content": "Sisu juurde", - "skip_to_folders": "Kaustade juurde", - "skip_to_tags": "Siltide juurde", - "slideshow": "Slaidiesitlus", - "slideshow_repeat": "Korda slaidiesitlust", - "slideshow_repeat_description": "Mine slaidiesitluse lõppedes tagasi algusesse", - "slideshow_settings": "Slaidiesitluse seaded", - "sort_albums_by": "Järjesta albumid...", - "sort_created": "Loomise aeg", - "sort_items": "Üksuste arv", - "sort_modified": "Muutmise aeg", - "sort_newest": "Uusim foto", - "sort_oldest": "Vanim foto", - "sort_people_by_similarity": "Sorteeri isikud sarnasuse järgi", - "sort_recent": "Uusim foto", - "sort_title": "Pealkiri", - "source": "Lähtekood", - "stack": "Virnasta", - "stack_action_prompt": "{count} virnastatud", - "stack_duplicates": "Virnasta duplikaadid", - "stack_select_one_photo": "Vali virnale kaanefoto", - "stack_selected_photos": "Virnasta valitud fotod", - "stacked_assets_count": "{count, plural, one {# üksus} other {# üksust}} virnastatud", - "stacktrace": "Pinujälg", - "start": "Alusta", - "start_date": "Alguskuupäev", - "start_date_before_end_date": "Alguskuupäev peab olema varasem kui lõppkuupäev", - "state": "Osariik", - "status": "Staatus", - "stop_casting": "Lõpeta edastamine", - "stop_motion_photo": "Peata liikuv foto", - "stop_photo_sharing": "Lõpeta oma fotode jagamine?", - "stop_photo_sharing_description": "{partner} ei pääse rohkem su fotodele ligi.", - "stop_sharing_photos_with_user": "Lõpeta oma fotode selle kasutajaga jagamine", - "storage": "Talletusruum", - "storage_label": "Talletussilt", - "storage_quota": "Talletuskvoot", - "storage_usage": "{used}/{available} kasutatud", - "submit": "Saada", - "success": "Õnnestus", - "suggestions": "Soovitused", - "sunrise_on_the_beach": "Päikesetõus rannal", - "support": "Tugi", - "support_and_feedback": "Tugi ja tagasiside", - "support_third_party_description": "Sinu Immich'i install on kolmanda osapoole pakendatud. Probleemid, mida täheldad, võivad olla põhjustatud selle pakendamise poolt, seega võta esmajärjekorras nendega ühendust, kasutades allolevaid linke.", - "swap_merge_direction": "Muuda ühendamise suunda", - "sync": "Sünkrooni", - "sync_albums": "Sünkrooni albumid", - "sync_albums_manual_subtitle": "Sünkrooni kõik üleslaaditud videod ja fotod valitud varundusalbumitesse", - "sync_local": "Sünkrooni lokaalsed üksused", - "sync_remote": "Sünkrooni kaugüksused", - "sync_status": "Sünkroonimise staatus", - "sync_status_subtitle": "Vaata ja halda sünkroonimissüsteemi", - "sync_upload_album_setting_subtitle": "Loo ja laadi oma pildid ja videod üles Immich'isse valitud albumitesse", - "tag": "Silt", - "tag_assets": "Sildista üksuseid", - "tag_created": "Lisatud silt: {tag}", - "tag_feature_description": "Fotode ja videote lehitsemine siltide kaupa grupeeritult", - "tag_not_found_question": "Ei leia silti? Lisa uus silt.", - "tag_people": "Sildista inimesi", - "tag_updated": "Muudetud silt: {tag}", - "tagged_assets": "{count, plural, one {# üksus} other {# üksust}} sildistatud", - "tags": "Sildid", - "tap_to_run_job": "Puuduta tööte käivitamiseks", - "template": "Mall", - "text_recognition": "Tekstituvastus", - "theme": "Teema", - "theme_selection": "Teema valik", - "theme_selection_description": "Sea automaatselt hele või tume teema vastavalt veebilehitseja eelistustele", - "theme_setting_asset_list_storage_indicator_title": "Kuva üksuste ruutudel talletusindikaatorit", - "theme_setting_asset_list_tiles_per_row_title": "Üksuste arv reas ({count})", - "theme_setting_colorful_interface_subtitle": "Rakenda taustapindadele põhivärv.", - "theme_setting_colorful_interface_title": "Värviline kasutajaliides", - "theme_setting_image_viewer_quality_subtitle": "Kohanda detailvaaturi kvaliteeti", - "theme_setting_image_viewer_quality_title": "Pildivaaturi kvaliteet", - "theme_setting_primary_color_subtitle": "Vali värv põhitegevuste ja aktsentide jaoks.", - "theme_setting_primary_color_title": "Põhivärv", - "theme_setting_system_primary_color_title": "Kasuta süsteemset värvi", - "theme_setting_system_theme_switch": "Automaatne (järgi süsteemi seadet)", - "theme_setting_theme_subtitle": "Vali rakenduse teema seade", - "theme_setting_three_stage_loading_subtitle": "Kolmeastmeline laadimine võib parandada laadimise jõudlust, aga põhjustab oluliselt suuremat võrgukoormust", - "theme_setting_three_stage_loading_title": "Luba kolmeastmeline laadimine", - "then": "Siis", - "they_will_be_merged_together": "Nad ühendatakse kokku", - "third_party_resources": "Kolmanda osapoole ressursid", - "time": "Aeg", - "time_based_memories": "Ajapõhised mälestused", - "time_based_memories_duration": "Aeg sekundites, kui kaua igat pilti kuvada.", - "timeline": "Ajajoon", - "timezone": "Ajavöönd", - "to_archive": "Arhiivi", - "to_change_password": "Muuda parool", - "to_favorite": "Lemmik", - "to_login": "Logi sisse", - "to_multi_select": "vali mitu", - "to_parent": "Tase üles", - "to_select": "vali", - "to_trash": "Prügikasti", - "toggle_settings": "Kuva/peida seaded", - "toggle_theme_description": "Vaheta teema", - "total": "Kokku", - "total_usage": "Kogukasutus", - "trash": "Prügikast", - "trash_action_prompt": "{count} liigutatud prügikasti", - "trash_all": "Kõik prügikasti", - "trash_count": "Liiguta {count, number} prügikasti", - "trash_delete_asset": "Kustuta üksus", - "trash_emptied": "Prügikast tühjendatud", - "trash_no_results_message": "Siia ilmuvad prügikasti liigutatud fotod ja videod.", - "trash_page_delete_all": "Kustuta kõik", - "trash_page_empty_trash_dialog_content": "Kas soovid prügikasti liigutatud üksused kustutada? Need eemaldatakse Immich'ist jäädavalt", - "trash_page_info": "Prügikasti liigutatud üksused kustutatakse jäädavalt {days} päeva pärast", - "trash_page_no_assets": "Prügikastis üksuseid pole", - "trash_page_restore_all": "Taasta kõik", - "trash_page_select_assets_btn": "Vali üksused", - "trash_page_title": "Prügikast ({count})", - "trashed_items_will_be_permanently_deleted_after": "Prügikasti tõstetud üksused kustutatakse jäädavalt {days, plural, one {# päeva} other {# päeva}} pärast.", - "trigger": "Päästik", - "trigger_asset_uploaded": "Üksus üles laaditud", - "trigger_asset_uploaded_description": "Käivitub uue üksuse üleslaadimisel", - "trigger_description": "Sündmus, mis käivitab töövoo", - "trigger_person_recognized": "Isik tuvastatud", - "trigger_person_recognized_description": "Käivitub isiku tuvastamisel", - "trigger_type": "Päästiku tüüp", - "troubleshoot": "Tõrkeotsing", - "type": "Tüüp", - "unable_to_change_pin_code": "PIN-koodi muutmine ebaõnnestus", - "unable_to_check_version": "Rakenduse või serveri versiooni kontrollimine ebaõnnestus", - "unable_to_setup_pin_code": "PIN-koodi seadistamine ebaõnnestus", - "unarchive": "Taasta arhiivist", - "unarchive_action_prompt": "{count} eemaldatud arhiivist", - "unarchived_count": "{count, plural, other {# arhiivist taastatud}}", - "undo": "Võta tagasi", - "unfavorite": "Eemalda lemmikutest", - "unfavorite_action_prompt": "{count} eemaldatud lemmikutest", - "unhide_person": "Ära peida isikut", - "unknown": "Teadmata", - "unknown_country": "Tundmatu riik", - "unknown_date": "Tundmatu kuupäev", - "unknown_year": "Teadmata aasta", - "unlimited": "Piiramatu", - "unlink_motion_video": "Tühista liikuva video linkimine", - "unlink_oauth": "Eemalda OAuth ühendus", - "unlinked_oauth_account": "OAuth ühendus eemaldatud", - "unmute_memories": "Tühista mälestuste vaigistamine", - "unnamed_album": "Nimetu album", - "unnamed_album_delete_confirmation": "Kas oled kindel, et soovid selle albumi kustutada?", - "unnamed_share": "Nimetu jagamine", - "unsaved_change": "Salvestamata muudatus", - "unselect_all": "Ära vali ühtegi", - "unselect_all_duplicates": "Ära vali duplikaate", - "unselect_all_in": "Ära vali ühtegi grupis {group}", - "unstack": "Eralda", - "unstack_action_prompt": "{count} eraldatud", - "unstacked_assets_count": "{count, plural, one {# üksus} other {# üksust}} eraldatud", - "unsupported_field_type": "Mittetoetatud välja tüüp", - "untagged": "Sildistamata", - "untitled_workflow": "Pealkirjata töövoog", - "up_next": "Järgmine", - "update_location_action_prompt": "Uuenda {count} valitud üksuse asukoht:", - "updated_at": "Uuendatud", - "updated_password": "Parool muudetud", - "upload": "Laadi üles", - "upload_concurrency": "Üleslaadimise samaaegsus", - "upload_details": "Üleslaadimise üksikasjad", - "upload_dialog_info": "Kas soovid valitud üksuse(d) serverisse varundada?", - "upload_dialog_title": "Üksuse üleslaadimine", - "upload_error_with_count": "Viga {count, plural, one {# üksuse} other {# üksuse}} üleslaadimisel", - "upload_errors": "Üleslaadimine lõpetatud {count, plural, one {# veaga} other {# veaga}}, uute üksuste nägemiseks värskenda lehte.", - "upload_finished": "Üleslaadimine lõpetatud", - "upload_progress": "Ootel {remaining, number} - Töödeldud {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {# dubleeritud üksus} other {# dubleeritud üksust}} vahele jäetud", - "upload_status_duplicates": "Duplikaadid", - "upload_status_errors": "Vead", - "upload_status_uploaded": "Üleslaaditud", - "upload_success": "Üleslaadimine õnnestus, uute üksuste nägemiseks värskenda lehte.", - "upload_to_immich": "Laadi Immich'isse ({count})", - "uploading": "Üleslaadimine", - "uploading_media": "Üksuste üleslaadimine", - "url": "URL", - "usage": "Kasutus", - "use_biometric": "Kasuta biomeetriat", - "use_current_connection": "Kasuta praegust ühendust", - "use_custom_date_range": "Kasuta kohandatud kuupäevavahemikku", - "user": "Kasutaja", - "user_has_been_deleted": "See kasutaja on kustutatud.", - "user_id": "Kasutaja ID", - "user_liked": "Kasutajale {user} meeldis {type, select, photo {see foto} video {see video} asset {see üksus} other {see}}", - "user_pin_code_settings": "PIN-kood", - "user_pin_code_settings_description": "Halda oma PIN-koodi", - "user_privacy": "Kasutaja privaatsus", - "user_purchase_settings": "Ost", - "user_purchase_settings_description": "Halda oma ostu", - "user_role_set": "Määra kasutajale {user} roll {role}", - "user_usage_detail": "Kasutajate kasutusandmed", - "user_usage_stats": "Konto kasutuse statistika", - "user_usage_stats_description": "Vaata konto kasutuse statistikat", - "username": "Kasutajanimi", - "users": "Kasutajad", - "users_added_to_album_count": "{count, plural, one {# kasutaja} other {# kasutajat}} lisatud albumisse", - "utilities": "Tööriistad", - "validate": "Valideeri", - "validate_endpoint_error": "Sisesta korrektne URL", - "validation_error": "Valideerimise viga", - "variables": "Muutujad", - "version": "Versioon", - "version_announcement_closing": "Sinu sõber Alex", - "version_announcement_message": "Hei! Saadaval on uus Immich'i versioon. Palun võta aega, et lugeda väljalasketeadet ja veenduda, et su seadistus on ajakohane, vältimaks konfiguratsiooniprobleeme, eriti kui kasutad WatchTower'it või muud mehhanismi, mis Immich'it automaatselt uuendab.", - "version_history": "Versiooniajalugu", - "version_history_item": "Versioon {version} paigaldatud {date}", - "video": "Video", - "video_hover_setting": "Esita hõljutamisel video eelvaade", - "video_hover_setting_description": "Esita video eelvaade, kui hiirt selle kohal hõljutada. Isegi kui keelatud, saab taasesituse alustada taasesitusnupu kohal hõljutades.", - "videos": "Videod", - "videos_count": "{count, plural, one {# video} other {# videot}}", - "videos_only": "Ainult videod", - "view": "Vaata", - "view_album": "Vaata albumit", - "view_all": "Vaata kõiki", - "view_all_users": "Vaata kõiki kasutajaid", - "view_asset_owners": "Vaata üksuse omanikke", - "view_details": "Vaata üksikasju", - "view_in_timeline": "Vaata ajajoonel", - "view_link": "Vaata linki", - "view_links": "Vaata linke", - "view_name": "Vaade", - "view_next_asset": "Vaata järgmist üksust", - "view_previous_asset": "Vaata eelmist üksust", - "view_qr_code": "Vaata QR-koodi", - "view_similar_photos": "Vaata sarnaseid fotosid", - "view_stack": "Vaata virna", - "view_user": "Vaata kasutajat", - "viewer_remove_from_stack": "Eemalda virnast", - "viewer_stack_use_as_main_asset": "Kasuta peamise üksusena", - "viewer_unstack": "Eralda", - "visibility_changed": "{count, plural, one {# isiku} other {# isiku}} nähtavus muudetud", - "visual": "Visuaalne", - "visual_builder": "Visuaalne koostaja", - "waiting": "Ootel", - "waiting_count": "Ootel: {count}", - "warning": "Hoiatus", - "week": "Nädal", - "welcome": "Tere tulemast", - "welcome_to_immich": "Tere tulemast Immich'isse", - "width": "Laius", - "wifi_name": "WiFi-võrgu nimi", - "workflow_delete_prompt": "Kas oled kindel, et soovid selle töövoo kustutada?", - "workflow_deleted": "Töövoog kustutatud", - "workflow_description": "Töövoo kirjeldus", - "workflow_info": "Töövoo info", - "workflow_json": "Töövoo JSON", - "workflow_json_help": "Muuda töövoo seadistust JSON-formaadis. Muudatused sünkroonitakse visuaalsesse koostajasse.", - "workflow_name": "Töövoo nimi", - "workflow_navigation_prompt": "Kas oled kindel, et soovid lahkuda ilma muudatusi salvestamata?", - "workflow_summary": "Töövoo kokkuvõte", - "workflow_update_success": "Töövoog edukalt uuendatud", - "workflow_updated": "Töövoog uuendatud", - "workflows": "Töövood", - "workflows_help_text": "Töövood automatiseerivad tegevusi üksustega päästikute ja filtrite alusel", - "wrong_pin_code": "Vale PIN-kood", - "year": "Aasta", - "years_ago": "{years, plural, one {# aasta} other {# aastat}} tagasi", - "yes": "Jah", - "you_dont_have_any_shared_links": "Sul pole ühtegi jagatud linki", - "your_wifi_name": "Sinu WiFi-võrgu nimi", - "zero_to_clear_rating": "üksuse hinnangu tühistamiseks vajuta 0", - "zoom_image": "Suumi pilti", - "zoom_to_bounds": "Suumi piiridesse" -} +{} diff --git a/i18n/eu.json b/i18n/eu.json index b335f837ca..0967ef424b 100644 --- a/i18n/eu.json +++ b/i18n/eu.json @@ -1,107 +1 @@ -{ - "about": "Honi buruz", - "account": "Kontua", - "account_settings": "Kontuaren Ezarpenak", - "acknowledge": "Onartu", - "action": "Ekintza", - "action_common_update": "Eguneratu", - "actions": "Ekintzak", - "active": "Aktibo", - "activity": "Jarduera", - "activity_changed": "Jarduera {enabled, select, true {ezarrita dago} other {ez dago ezarrita}}", - "add": "Gehitu", - "add_a_description": "Azalpena gehitu", - "add_a_location": "Kokapena gehitu", - "add_a_name": "Izena gehitu", - "add_a_title": "Izenburua gehitu", - "add_birthday": "Urtebetetzea gehitu", - "add_endpoint": "Endpoint-a gehitu", - "add_exclusion_pattern": "Bazterketa eredua gehitu", - "add_location": "Kokapena gehitu", - "add_more_users": "Erabiltzaile gehiago gehitu", - "add_partner": "Kidea gehitu", - "add_path": "Bidea gehitu", - "add_photos": "Argazkiak gehitu", - "add_tag": "Etiketa gehitu", - "add_to": "Hona gehitu…", - "add_to_album": "Albumera gehitu", - "add_to_album_bottom_sheet_added": "{album} -(e)ra gehitu", - "add_to_album_bottom_sheet_already_exists": "Dagoeneko {album} albumenean", - "add_to_albums": "Albumetara gehitu", - "add_to_albums_count": "Albumetara gehitu ({count})", - "add_to_shared_album": "Gehitu partekatutako albumera", - "add_url": "URL-a gehitu", - "added_to_archive": "Artxibategira gehituta", - "added_to_favorites": "Faboritoetara gehituta", - "added_to_favorites_count": "{count, number} faboritoetara gehituta", - "admin": { - "add_exclusion_pattern_description": "Gehitu baztertze patroiak. *, ** eta ? karakterak erabil ditzazkezu (globbing). Adibideak: \"Raw\" izeneko edozein direktorioko fitxategi guztiak baztertzeko, erabili \"**/Raw/**\". \".tif\" amaitzen diren fitxategi guztiak baztertzeko, erabili \"**/*.tif\". Bide absolutu bat baztertzeko, erabili \"/baztertu/beharreko/bidea/**\".", - "admin_user": "Administradore erabiltzailea", - "authentication_settings": "Segurtasun Ezarpenak", - "authentication_settings_description": "Kudeatu pasahitza, OAuth edo beste segurtasun konfigurazio bat", - "authentication_settings_disable_all": "Seguru zaude saioa hasteko modu guztiak desgaitu nahi dituzula? Saioa hastea guztiz desgaitua izango da.", - "authentication_settings_reenable": "Berriro gaitzeko, erabili Server Command.", - "background_task_job": "Atzealdeko Lanak", - "backup_onboarding_footer": "Immich-en babes kopiei buruzko informazio gehiago nahi baduzu, mesedez irakurri dokumentazioa.", - "backup_onboarding_title": "Babes Kopiak", - "confirm_delete_library": "Seguru zaude {library} ezabatu nahi duzula?", - "confirm_email_below": "Konfirmatzeko, idatzi \"{email}\" azpian", - "confirm_reprocess_all_faces": "Seguru zaude aurpegi guztiak berriro prozesatu nahi dituzula? Erabakiak jendearen izenak ere borratuko ditu.", - "confirm_user_password_reset": "Seguru zaude {user}-ren pasahitza berrezarri nahi duzula?", - "confirm_user_pin_code_reset": "Seguru zaude {user}-ren PIN kodea berrezarri nahi duzula?", - "create_job": "Gehitu zeregina", - "disable_login": "Desgaitu saio hastea", - "face_detection": "Aurpegi detekzioa", - "failed_job_command": "{command} komandoak hutsegin du {job} zereginerako", - "image_format": "Formatua", - "image_format_description": "WebP ereduak JPEG baino fitxategi txikiagoak sortzen ditu, baina motelagoa da kodifikatzen.", - "image_preview_title": "Aurreikusiaen Konfigurazioa", - "image_quality": "Kalitatea", - "image_resolution": "Erresoluzioa", - "image_settings": "Argazkien Konfigurazioa", - "image_thumbnail_title": "Argazki Txikien Konfigurazioa", - "job_created": "Zeregina sortuta", - "job_settings": "Zereginaren konfigurazioa", - "logging_enable_description": "Gaitu erregistroak", - "logging_level_description": "Erregistroak gaituta daudenean, nolako erregistro maila erabili.", - "logging_settings": "Erregistroak", - "machine_learning_duplicate_detection": "Bizkoizketa Detekzioa", - "machine_learning_duplicate_detection_enabled": "Gaitu bikoizketa detekezioa", - "machine_learning_facial_recognition": "Aurpegi-Ezagutza", - "machine_learning_facial_recognition_description": "Detektatu, ezagutu eta aurpegiak banatu argazkietan", - "machine_learning_facial_recognition_model": "Aurpegi-Ezagutza eredua", - "machine_learning_facial_recognition_setting": "Aurpegi-Ezagutza Gaitu", - "machine_learning_smart_search_enabled": "Gaitu bilaketa arina", - "manage_log_settings": "Kudeatu erregistroen konfigurazioa", - "map_dark_style": "Beltz estiloa", - "map_gps_settings": "Mapa eta GPS Konfigurazioa", - "map_light_style": "Zuri estiloa", - "map_settings": "Mapa", - "metadata_faces_import_setting": "Gaitu aurpegien inportazioa", - "metadata_settings": "Metadata Konfigurazioa", - "metadata_settings_description": "Kudeatu metadaten konfigurazioa", - "migration_job": "Migrazio", - "oauth_settings": "OAuth", - "transcoding_acceleration_vaapi": "VAAPI" - }, - "advanced": "Aurreratua", - "advanced_settings_readonly_mode_title": "Irakurri-bakarrik Modua", - "apply_count": "Ezarri ({count, number})", - "assets_added_to_albums_count": "Gehituta {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} ezin izan da albumetara gehitu", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} dagoeneko albumean dago", - "first": "Lehenengo «Lehenik»", - "gps": "GPS", - "gps_missing": "Ez dago GPS", - "last": "Azkena", - "like": "Gustoko", - "manage_geolocation": "Kudeatu kokapena", - "organize_into_albums": "Albumetan antolatu", - "query_asset_id": "Aztertu aukeratutako ID-a", - "readonly_mode_disabled": "Irakurri-bakarrik modua desgaituta", - "readonly_mode_enabled": "Irakurri-bakarrik modua gaituta", - "selected_gps_coordinates": "GPS Koordenadak Aukeratuta", - "sort_newest": "Argazkirik berriena", - "to_select": "aukeratzeko", - "view_similar_photos": "Ikusi antzeko argazkiak" -} +{} diff --git a/i18n/fa.json b/i18n/fa.json index e7d681d92f..0967ef424b 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -1,833 +1 @@ -{ - "about": "درباره", - "account": "حساب کاربری", - "account_settings": "تنظیمات حساب کاربری", - "acknowledge": "متوجه شدم", - "action": "عملکرد", - "action_common_update": "به‌ روز‌رسانی", - "actions": "عملکرد", - "active": "فعال", - "active_count": "فعال: {count}", - "activity": "فعالیت", - "add": "افزودن", - "add_a_description": "توضیحات", - "add_a_location": "افزودن یک مکان", - "add_a_name": "افزودن نام", - "add_a_title": "افزودن عنوان", - "add_birthday": "افزودن تاریخ تولد", - "add_exclusion_pattern": "افزودن الگوی استثنا", - "add_location": "افزودن مکان", - "add_more_users": "افزودن کاربرهای بیشتر", - "add_partner": "افزودن شریک", - "add_path": "افزودن مسیر", - "add_photos": "افزودن عکس ها", - "add_tag": "افزودن تگ", - "add_to": "افزودن به …", - "add_to_album": "افزودن به آلبوم", - "add_to_album_bottom_sheet_added": "به آلبوم {album} اضافه شد", - "add_to_album_bottom_sheet_already_exists": "قبلا در آلبوم {album} موجود است", - "add_to_album_bottom_sheet_some_local_assets": "برخی از محتواهای محلی را نشد به آلبوم اضافه کرد", - "add_to_albums": "افزودن به آلبوم", - "add_to_albums_count": "افزودن به آلبوم ها {count}", - "add_to_bottom_bar": "افزودن به", - "add_to_shared_album": "افزودن به آلبوم اشتراکی", - "add_upload_to_stack": "افزودن فایل ارسالی به مجموعه", - "add_url": "افزودن آدرس URL", - "added_to_archive": "به آرشیو اضافه شد", - "added_to_favorites": "به علاقه مندی ها اضافه شد", - "added_to_favorites_count": "{count, number} تا به علاقه مندی ها اضافه شد", - "admin": { - "add_exclusion_pattern_description": "الگوهای استثنا را اضافه کنید. پشتیبانی از گلابینگ با استفاده از *, ** و ? وجود دارد. برای نادیده گرفتن تمام فایل‌ها در هر دایرکتوری با نام \"Raw\"، از \"**/Raw/**\" استفاده کنید. برای نادیده گرفتن تمام فایل‌هایی که با \".tif\" پایان می‌یابند، از \"**/*.tif\" استفاده کنید. برای نادیده گرفتن یک مسیر مطلق، از \"/path/to/ignore/**\" استفاده کنید.", - "admin_user": "ادمین", - "authentication_settings": "تنظیمات احراز هویت", - "authentication_settings_description": "مدیریت رمز عبور، OAuth، و سایر تنظیمات احراز هویت", - "authentication_settings_disable_all": "آیا مطمئن هستید که می‌خواهید تمام روش‌های ورود را غیرفعال کنید؟ ورود به طور کامل غیرفعال خواهد شد.", - "authentication_settings_reenable": "برای فعال سازی مجدد از دستور سرور استفاده کنید.", - "background_task_job": "وظایف پس‌زمینه", - "backup_onboarding_footer": "برای اطلاعات بیشتر درباره بک آپ گیری از Immich، لطفا به مستندات مراجعه کنید.", - "backup_onboarding_title": "بک آپ ها", - "cleared_jobs": "وظایف پاک شده برای:{job}", - "config_set_by_file": "تنظیم فعلی توسط یک فایل پیکربندی انجام شده است", - "confirm_delete_library": "آیا مطمئن هستید که می‌خواهید کتابخانه {library} را حذف کنید؟", - "confirm_delete_library_assets": "آیا مطمئن هستید که می‌خواهید این کتابخانه را حذف کنید؟ این عمل باعث حذف {count, plural, one {# فایل داخلی} other {همه # فایل داخلی}} از Immich خواهد شد و قابل بازگشت نیست. فایل‌ها بر روی دیسک باقی خواهند ماند.", - "confirm_email_below": "برای تأیید، \"{email}\" را در زیر تایپ کنید", - "confirm_reprocess_all_faces": "آیا مطمئن هستید که می‌خواهید تمام چهره‌ها را مجددا پردازش کنید؟ این عمل باعث پاک شدن افراد مشخص شده نیز خواهد شد.", - "confirm_user_password_reset": "آیا مطمئن هستید که می‌خواهید رمز عبور {user} را بازنشانی کنید؟", - "confirm_user_pin_code_reset": "آیا مطمئن هستید که می‌خواهید کد PIN ‏{user} را بازنشانی کنید؟", - "copy_config_to_clipboard_description": "کپی کانفیگ فعلی سیستم در قالب یک آبجکت JSON در کلیپ بورد", - "create_job": "ایجاد جاب", - "disable_login": "غیرفعال کردن ورود", - "duplicate_detection_job_description": "اجرای یادگیری ماشین بر روی فایل‌ها برای شناسایی تصاویر مشابه. این وابسته به جستجوی هوشمند است", - "exclusion_pattern_description": "الگوهای استثنا به شما امکان می‌دهد هنگام اسکن کتابخانه خود فایل‌ها و پوشه‌ها را نادیده بگیرید . این مفید است اگر پوشه‌هایی دارید که فایل‌هایی را شامل می‌شوند که نمی‌خواهید وارد کنید، مانند فایل‌های RAW.", - "export_config_as_json_description": "دانلود کانفیگ فعلی سیستم در قالب یک فایل JSON", - "face_detection": "تشخیص چهره", - "face_detection_description": "تشخیص چهره‌ها در فایل‌ها با استفاده از یادگیری ماشین. برای ویدیوها، تنها تصویر بندانگشتی در نظر گرفته می‌شود. گزینه \"همه\" تمام فایل‌ها را (مجددا) پردازش می‌کند. گزینه \"گمشده\" فایل‌ها را در صف قرار می‌دهد که هنوز پردازش نشده‌اند. چهره‌های تشخیص داده شده پس از اتمام تشخیص چهره، برای تشخیص چهره به صورت صف انتظار قرار می‌گیرند، آن‌ها را به افراد موجود یا جدید گروه‌بندی می‌کند.", - "facial_recognition_job_description": "گروه‌بندی چهره‌های تشخیص داده شده به افراد. این مرحله پس از تشخیص چهره انجام می‌شود. گزینه \"همه\" تمام چهره‌ها را (مجددا) دسته بندی می‌کند. گزینه \"گمشده\" چهره‌ها را در صف قرار می‌دهد که به هیچ فردی اختصاص داده نشده‌اند.", - "failed_job_command": "دستور {command} برای کار: {job} ناموفق بود", - "force_delete_user_warning": "هشدار: این عمل باعث حذف فوری کاربر و تمام فایل‌ها می‌شود. این عمل قابل بازگشت نیست و فایل‌ها قابل بازیابی نیستند.", - "image_format_description": "فرمت WebP فایل‌های کوچکتری نسبت به JPEG ایجاد می‌کند، اما زمان کدگذاری آن کندتر است.", - "image_fullsize_description": "تصویر با اندازه کامل و بدون فراداده، مورد استفاده هنگام بزرگ‌نمایی", - "image_fullsize_enabled": "فعال‌سازی تولید تصویر با اندازه کامل", - "image_fullsize_enabled_description": "تولید تصویر با اندازه کامل برای فرمت‌های غیرسازگار با وب. هنگامی که گزینه «استفاده از پیش‌نمایش تعبیه‌شده» فعال باشد، پیش‌نمایش‌های تعبیه‌شده مستقیماً بدون تبدیل استفاده می‌شوند. این تنظیم بر فرمت‌های سازگار با وب مانند JPEG تأثیری ندارد.", - "image_fullsize_quality_description": "کیفیت تصویر با اندازه کامل از ۱ تا ۱۰۰. هرچه بالاتر باشد، کیفیت بهتر است، اما فایل‌های بزرگ‌تری ایجاد می‌کند.", - "image_fullsize_title": "تنظیمات تصویر با اندازه کامل", - "image_prefer_embedded_preview": "ترجیحات پیش‌نمایش تعبیه‌شده", - "image_prefer_embedded_preview_setting_description": "استفاده از پیش‌نمایش‌های تعبیه‌شده در عکس‌های RAW به‌عنوان ورودی برای پردازش تصویر، در صورت موجود بودن. این می‌تواند رنگ‌های دقیق‌تری برای برخی تصاویر ایجاد کند، اما کیفیت پیش‌نمایش به دوربین بستگی دارد و ممکن است تصویر دارای نویزهای فشرده‌سازی بیشتری باشد.", - "image_prefer_wide_gamut": "ترجیحات گستره رنگی وسیع", - "image_prefer_wide_gamut_setting_description": "برای تصاویر کوچک از فضای رنگی Display P3 استفاده کنید. این کار باعث حفظ زنده بودن رنگ‌ها در تصاویر با گستره رنگی وسیع می‌شود، اما ممکن است تصاویر در دستگاه‌های قدیمی با نسخه‌های قدیمی مرورگر به شکل متفاوتی نمایش داده شوند. تصاویر با فضای رنگی sRGB به همان حالت sRGB نگه داشته می‌شوند تا از تغییرات رنگی جلوگیری شود.", - "image_preview_description": "تصویر با اندازه متوسط و بدون فراداده، مورد استفاده هنگام مشاهده یک دارایی و برای یادگیری ماشین", - "image_preview_quality_description": "کیفیت پیش‌نمایش از ۱ تا ۱۰۰. هرچه بالاتر باشد، کیفیت بهتر است، اما فایل‌های بزرگ‌تری ایجاد می‌کند و ممکن است پاسخ‌گویی برنامه کاهش یابد. تنظیم مقدار پایین می‌تواند بر کیفیت یادگیری ماشین تأثیر بگذارد.", - "image_preview_title": "تنظیمات پیش‌نمایش", - "image_quality": "کیفیت", - "image_resolution": "وضوح تصویر", - "image_resolution_description": "وضوح بالاتر می‌تواند جزئیات بیشتری را حفظ کند، اما تبدیل آن زمان بیشتری می‌برد، حجم فایل‌ها را افزایش می‌دهد و ممکن است پاسخ‌گویی برنامه را کاهش دهد.", - "image_settings": "تنظیمات عکس", - "image_settings_description": "مدیریت کیفیت و وضوح تصاویر تولید شده", - "import_config_from_json_description": "وارد کردن کانفیگ سیستم با آپلود یک فایل JSON", - "job_concurrency": "همزمانی {job}", - "job_created": "جاب ساخته شد", - "job_not_concurrency_safe": "این کار ایمنی همزمانی را تضمین نمی‌کند.", - "job_settings": "تنظیمات کار", - "job_settings_description": "مدیریت همزمانی کار", - "library_created": "کتابخانه ایجاد شده: {library}", - "library_deleted": "کتابخانه حذف شد", - "library_folder_description": "یک پوشه برای ایمپورت مشخص کنید. این پوشه و پوشه های داخل آن، برای عکس ها و ویدیو ها اسکن می شوند.", - "library_remove_folder_prompt": "آیا از حذف این پوشه ایمپورت مطمئن هستید؟", - "library_scanning": "اسکن دوره ای", - "library_scanning_description": "تنظیم اسکن دوره‌ای کتابخانه", - "library_scanning_enable_description": "فعال کردن اسکن دوره‌ای کتابخانه", - "library_settings": "کتابخانه خارجی", - "library_settings_description": "مدیریت تنظیمات کتابخانه خارجی", - "library_tasks_description": "اسکن کتابخانه های خارجی برای فایل های جدید و/یا فایل های تغییر کرده", - "library_updated": "کتابخانه آپدیت شده", - "library_watching_enable_description": "نظارت بر تغییرات فایل در کتابخانه‌های خارجی", - "library_watching_settings": "نظارت بر کتابخانه [آزمایشی]", - "library_watching_settings_description": "نظارت خودکار بر فایل‌های تغییر یافته", - "logging_enable_description": "فعال سازی ورود", - "logging_level_description": "وقتی فعال باشد، از چه سطح گزارش استفاده شود.", - "logging_settings": "گزارشات", - "machine_learning_availability_checks": "بررسی های دسترس پذیری", - "machine_learning_availability_checks_description": "تشخیص خودکار و ترجیح سرور های یادگیری ماشین موجود", - "machine_learning_availability_checks_enabled": "فعال سازی بررسی های دسترس پذیزی", - "machine_learning_availability_checks_interval": "وقفه بررسی", - "machine_learning_availability_checks_interval_description": "مدت وقفه بصورت میلی ثانیه بین بررسی های دسترس پذیری", - "machine_learning_availability_checks_timeout": "تایم اوت درخواست", - "machine_learning_availability_checks_timeout_description": "زمان تایم اوت بصورت میلی ثانیه برای بررسی دسترس پذیری", - "machine_learning_clip_model": "مدل CLIP", - "machine_learning_clip_model_description": "نام یک مدل CLIP که در اینجا فهرست شده است. توجه داشته باشید که پس از تغییر مدل، باید کار 'جستجوی هوشمند' را برای همه تصاویر دوباره اجرا کنید.", - "machine_learning_duplicate_detection": "تشخیص تکراری ها", - "machine_learning_duplicate_detection_enabled": "فعال سازی تشخیص تکراری ها", - "machine_learning_duplicate_detection_enabled_description": "اگر غیرفعال باشد، تصاویر یا دارایی‌های دقیقاً یکسان همچنان حذف می‌شوند.", - "machine_learning_duplicate_detection_setting_description": "از تعبیه‌های CLIP برای یافتن تکراری‌های احتمالی استفاده کنید", - "machine_learning_enabled": "فعال سازی یادگیری ماشین", - "machine_learning_enabled_description": "اگر غیرفعال شود، تمامی ویژگی‌های یادگیری ماشین بدون توجه به تنظیمات زیر غیرفعال خواهند شد.", - "machine_learning_facial_recognition": "تشخیص چهره", - "machine_learning_facial_recognition_description": "تشخیص، شناسایی و گروه‌بندی چهره‌ها در تصاویر", - "machine_learning_facial_recognition_model": "مدل تشخیص چهره", - "machine_learning_facial_recognition_model_description": "مدل‌ها به ترتیب اندازه،نزولی فهرست شده‌اند. مدل‌های بزرگتر کندتر هستند و از حافظه بیشتری استفاده می‌کنند، اما نتایج بهتری تولید می‌کنند. توجه داشته باشید که پس از تغییر مدل، باید کار تشخیص چهره را برای تمامی تصاویر دوباره اجرا کنید.", - "machine_learning_facial_recognition_setting": "فعال سازی تشخیص چهره", - "machine_learning_facial_recognition_setting_description": "اگر غیرفعال شود، تصاویر برای تشخیص چهره کدگذاری نخواهند شد و بخش افراد در صفحه کاوش پر نخواهد شد.", - "machine_learning_max_detection_distance": "حداکثر فاصله تشخیص", - "machine_learning_max_detection_distance_description": "حداکثر فاصله بین دو تصویر برای در نظر گرفتن آنها به عنوان تکراری، در محدوده 0.001 تا 0.1 است. مقادیر بالاتر تکراری‌های بیشتری را شناسایی می‌کند، اما ممکن است منجر به تشخیص‌های اشتباه شود.", - "machine_learning_max_recognition_distance": "حداکثر فاصله تشخیص", - "machine_learning_max_recognition_distance_description": "حداکثر فاصله بین دو چهره برای در نظر گرفتن آنها به عنوان یک شخص، در محدوده 0 تا 2 است. کاهش این مقدار می‌تواند از برچسب زدن دو نفر به عنوان یک شخص جلوگیری کند، در حالی که افزایش آن می‌تواند از برچسب زدن یک نفر به عنوان دو شخص مختلف جلوگیری کند. توجه داشته باشید که ادغام دو نفر به عنوان یک نفر آسان‌تر از تقسیم یک نفر به دو نفر است، بنابراین در صورت امکان مبنای یک آستانه کمتر را انتخاب کنید.", - "machine_learning_min_detection_score": "حداقل امتیاز تشخیص", - "machine_learning_min_detection_score_description": "حداقل امتیاز اعتماد برای تشخیص یک چهره، در بازه 0 تا 1 قرار دارد. مقادیر کمتر باعث تشخیص بیشتر چهره می‌شود، اما ممکن است منجر به تشخیص‌های اشتباه شود.", - "machine_learning_min_recognized_faces": "حداقل چهره های شناخته شده", - "machine_learning_min_recognized_faces_description": "حداقل تعداد چهره‌های تشخیص داده شده برای ایجاد یک شخص. افزایش این مقدار باعث دقیق‌تر شدن تشخیص چهره می‌شود، اما همزمان باعث افزایش احتمال این می‌شود که یک چهره به یک شخص نسبت داده نشود.", - "machine_learning_ocr_description": "استفاده از یادگیری ماشین برای تشخیص متن داخل عکس ها", - "machine_learning_ocr_enabled": "فعال سازی OCR", - "machine_learning_ocr_enabled_description": "اگر غیر فعال باشد، تشخیص متن روی عکس ها انجام نمی شود.", - "machine_learning_ocr_max_resolution": "رزولوشن ماکسیمم", - "machine_learning_ocr_max_resolution_description": "پیش نمایش های بالای این رزولوشن با حفظ نسبت تصویر تغییر اندازه داده می شوند. مقادیر بزرگتر دقت بالاتری دارند، ولی زمان و حافظه بیشتری برای پردازش نیاز دارند.", - "machine_learning_ocr_min_detection_score": "حداقل امتیاز تشخیص", - "machine_learning_ocr_min_detection_score_description": "حداقل امتیاز اطمینان بین 0 تا 1 برای متن ها تا تشخیص داده بشوند. مقادیر کمتر متن بیشتری تشخیص می دهند ولی تشخیص های اشتباه بیشتر می شوند.", - "machine_learning_ocr_min_recognition_score": "حداقل امتیاز شناسایی", - "machine_learning_ocr_min_score_recognition_description": "حداقل امتیاز اطمینان بین 0 تا 1 برای متن ها تا شناسایی شوند. مقادیر کمتر متون بیشتری را شناسایی می کنند ولی شناسایی های اشتباه آن ها بیشتر می شود.", - "machine_learning_ocr_model": "مدل OCR", - "machine_learning_ocr_model_description": "مدل های سرور دقت بالاتری از مدل های موبایل هستند، اما پردازش آنها طولانی تر است و حافظه بیشتری مصرف می کنند.", - "machine_learning_settings": "تنظیمات یادگیری ماشین", - "machine_learning_settings_description": "مدیریت ویژگی‌ها و تنظیمات یادگیری ماشین", - "machine_learning_smart_search": "جستجوی هوشمند", - "machine_learning_smart_search_description": "جستجوی تصاویر با استفاده از تعبیه‌های CLIP به صورت معنایی", - "machine_learning_smart_search_enabled": "فعال سازی جستجوی هوشمند", - "machine_learning_smart_search_enabled_description": "اگر غیرفعال باشد، تصاویر برای جستجوی هوشمند رمزگذاری نخواهند شد.", - "machine_learning_url_description": "آدرس سرور یادگیری ماشین. اگر بیش از یک آدرس داده شود، هر سرور بصورت یکی در لحظه امتحان می شوند تا زمانی که یکی از آنها با موفقیت پاسخ دهد، از اول به آخر. سرور هایی که پاسخ ندهند بصورت موقت نادیده گرفته می شوند تا زمانی که به وضعیت آنلاین برگردند.", - "manage_concurrency": "مدیریت همزمانی", - "manage_concurrency_description": "رفتن به صفحه جاب ها برای مدیریت همزمانی جاب ها", - "manage_log_settings": "مدیریت تنظیمات گزارش", - "map_dark_style": "حالت تیره", - "map_enable_description": "فعال سازی ویژگی های نقشه", - "map_gps_settings": "تنظیمات نقشه و جی پی اس", - "map_gps_settings_description": "تنظیمات نقشه و جی‌پی‌اس (ژئوکدینگ معکوس) را مدیریت کنید", - "map_implications": "قابلیت نقشه به یک سرویس tile خارجی نیاز دارد (tiles.immich.cloud)", - "map_light_style": "حالت روشن", - "map_manage_reverse_geocoding_settings": "مدیریت تنظیمات کدگذاری مکانی معکوس ", - "map_reverse_geocoding": "ژئوکدینگ معکوس", - "map_reverse_geocoding_enable_description": "فعال سازی ژئوکدینگ معکوس", - "map_reverse_geocoding_settings": "تنظیمات ژئوکدینگ معکوس", - "map_settings": "تنظیمات نقشه و مکانهای روی نقشه", - "map_settings_description": "مدیریت تنظیمات نقشه", - "map_style_description": "آدرس اینترنتی (style.json) نوع نمایش نقشه", - "memory_cleanup_job": "پاک سازی حافظه", - "metadata_extraction_job": "استخراج فرا داده", - "metadata_extraction_job_description": "استخراج اطلاعات ابرداده، مانند موقعیت جغرافیایی و کیفیت از هر فایل", - "metadata_faces_import_setting": "فعال سازی ایمپورت صورت", - "metadata_faces_import_setting_description": "ایمپورت صورت ها از اطلاعات EXIF عکس و فایل های sidecar", - "metadata_settings": "تنظیمات Metadata", - "metadata_settings_description": "مدیریت تنظیمات metadata", - "migration_job": "مهاجرت", - "nightly_tasks_cluster_faces_setting_description": "اجرای شناسایی چهره روی چهره های تازه تشخیص داده شده", - "nightly_tasks_cluster_new_faces_setting": "گروه بندی چهره های جدید", - "nightly_tasks_database_cleanup_setting": "تسک های پاک سازی پایگاه داده", - "nightly_tasks_database_cleanup_setting_description": "پاک سازی داده های قدیمی، منقضی شده از پایگاه داده", - "nightly_tasks_generate_memories_setting": "ایجاد خاطرات", - "nightly_tasks_generate_memories_setting_description": "ایجاد خاطرات جدید از فایل ها", - "nightly_tasks_start_time_setting": "زمان شروع", - "nightly_tasks_sync_quota_usage_setting": "همگام سازی میزان استفاده از سهمیه", - "nightly_tasks_sync_quota_usage_setting_description": "بروزرسانی سهمیه ذخیره سازی کاربر، بر اساس استفاده فعلی", - "no_paths_added": "هیچ مسیری اضافه نشده", - "no_pattern_added": "هیچ الگوی اضافه نشده", - "note_apply_storage_label_previous_assets": "توجه: برای اعمال برچسب ذخیره سازی به دارایی هایی که قبلاً بارگذاری شده اند، دستور زیر را اجرا کنید", - "note_cannot_be_changed_later": "توجه: این را نمی توان بعداً تغییر داد!", - "notification_email_from_address": "آدرس فرستنده", - "notification_email_from_address_description": "آدرس ایمیل فرستنده، به عنوان مثال:\"سرور عکس Immich \". مطمئن باشید از آدرسی استفاده کنید که اجازه ارسال ایمیل از آن را دارید.", - "notification_email_host_description": "میزبان سرور ایمیل (مثلاً smtp.immich.app)", - "notification_email_ignore_certificate_errors": "خطاهای گواهی را نادیده بگیر", - "notification_email_ignore_certificate_errors_description": "خطاهای اعتبارسنجی گواهی TLS را نادیده بگیر (توصیه نمی‌شود)", - "notification_email_password_description": "رمز عبور برای استفاده در هنگام احراز هویت با سرور ایمیل", - "notification_email_port_description": "پورت سرور ایمیل (مثلاً 25، 465 یا 587)", - "notification_email_sent_test_email_button": "ایمیل آزمایشی بفرست و ذخیره کن", - "notification_email_setting_description": "تنظیمات برای ارسال اعلان های ایمیل", - "notification_email_test_email": "ارسال ایمیل آزمایشی", - "notification_email_test_email_failed": "ارسال ایمیل آزمایشی با شکست مواجه شد، لطفا مقادیر را بررسی کنید", - "notification_email_test_email_sent": "یک ایمیل آزمایشی به {email} ارسال شد. لطفا صندوق ورودی خود را بررسی کنید.", - "notification_email_username_description": "نام کاربری برای استفاده هنگام احراز هویت با سرور ایمیل", - "notification_enable_email_notifications": "فعال کردن اعلان های ایمیل", - "notification_settings": "تنظیمات اعلان", - "notification_settings_description": "مدیریت تنظیمات اعلان ها، از جمله ایمیل", - "oauth_auto_launch": "راه اندازی خودکار", - "oauth_auto_launch_description": "جریان ورود به سیستم OAuth را به طور خودکار هنگام هدایت به صفحه ورود شروع کن", - "oauth_auto_register": "ثبت خودکار", - "oauth_auto_register_description": "کاربران جدید را پس از ورود با OAuth به طور خودکار ثبت نام کن", - "oauth_button_text": "متن دکمه", - "oauth_enable_description": "ورود توسط OAuth", - "oauth_mobile_redirect_uri": "تغییر مسیر URI موبایل", - "oauth_mobile_redirect_uri_override": "تغییر مسیر URI تلفن همراه", - "oauth_mobile_redirect_uri_override_description": "زمانی که ارائه دهنده OAuth اجازه استفاده از آدرس موبایل، مانند ''{callback}'' را نمی دهد", - "oauth_settings": "OAuth", - "oauth_settings_description": "مدیریت تنظیمات ورود به سیستم OAuth", - "oauth_settings_more_details": "برای جزئیات بیشتر در مورد این ویژگی، به مستندات مراجعه کنید.", - "oauth_storage_label_claim": "درخواست برچسب فضای ذخیره سازی", - "oauth_storage_label_claim_description": "تنظیم خودکار برچسب فضای ذخیره‌سازی کاربر به مقدار درخواست شده.", - "oauth_storage_quota_claim": "درخواست سهمیه فضای ذخیره سازی", - "oauth_storage_quota_claim_description": "تنظیم خودکار سهمیه ذخیره‌سازی کاربر به مقدار درخواست شده.", - "oauth_storage_quota_default": "مقدار سهمیه ذخیره‌سازی پیش‌فرض (گیگابایت)", - "oauth_storage_quota_default_description": "سهمیه به گیگابایت هنگامی که درخواستی ارائه نشده باشد", - "oauth_timeout": "تایم اوت درخواست", - "oauth_timeout_description": "زمان تایم اوت برای درخواست ها بصورت میلی ثانیه", - "ocr_job_description": "استفاده از یادگیری ماشین برای شناسایی متن در عکس ها", - "password_enable_description": "ورود با ایمیل و گذرواژه", - "password_settings": "گذرواژه ورود", - "password_settings_description": "مدیریت تنظیمات گذرواژه ورود", - "paths_validated_successfully": "تمامی مسیرها با موفقیت تأیید شدند", - "queue_details": "جزئیات صف", - "queues": "صف های جاب", - "queues_page_description": "صفحه ادمین صف های جاب", - "quota_size_gib": "مقدار سهمیه (گیگابایت)", - "refreshing_all_libraries": "بروز رسانی همه کتابخانه ها", - "registration": "ثبت نام مدیر", - "registration_description": "از آنجایی که شما اولین کاربر در سیستم هستید، به عنوان مدیر تعیین شده‌اید و مسئولیت انجام وظایف مدیریتی بر عهده شما خواهد بود و کاربران اضافی توسط شما ایجاد خواهند شد.", - "remove_failed_jobs": "حذف جاب های ناموفق", - "require_password_change_on_login": "الزام کاربر به تغییر گذرواژه در اولین ورود", - "reset_settings_to_default": "بازنشانی تنظیمات به حالت پیش‌فرض", - "reset_settings_to_recent_saved": "بازنشانی تنظیمات به آخرین تنظیمات ذخیره شده", - "search_jobs": "جاب های جستجو…", - "send_welcome_email": "ارسال ایمیل خوش آمد گویی", - "server_external_domain_settings": "دامنه خارجی", - "server_external_domain_settings_description": "دامنه برای لینک های عمومی به اشتراک گذاشته شده، شامل //:(s)http", - "server_public_users": "کاربران عمومی", - "server_public_users_description": "تمامی کاربران (اسم و ایمیل) هنگام اضافه کردن یک کاربر به یک آلبوم مشترک لیست می شوند. وقتی غیر فعال باشد، لیست کاربران فقط برای ادمین قابل مشاهده است.", - "server_settings": "تنظیمات سرور", - "server_settings_description": "مدیریت تنظیمات سرور", - "server_welcome_message": "پیام خوش آمد گویی", - "server_welcome_message_description": "پیامی که در صفحه ورود به سیستم نمایش داده می شود.", - "settings_page_description": "صفحه تنظیمات ادمین", - "sidecar_job": "اطلاعات جانبی", - "sidecar_job_description": "یافتن یا همگام‌سازی اطلاعات جانبی از فایل سیستم", - "slideshow_duration_description": "زمان ( به ثانیه ) نشان دادن هر عکس", - "smart_search_job_description": "اجرای یادگیری ماشینی بر روی دارایی‌ها برای پشتیبانی از جستجوی هوشمند", - "storage_template_date_time_description": "زمان‌بندی ایجاد دارایی برای اطلاعات تاریخ و زمان استفاده می‌شود", - "storage_template_date_time_sample": "نمونه زمان {date}", - "storage_template_hash_verification_enabled": "تأیید هَش فعال شد", - "storage_template_hash_verification_enabled_description": "تأیید هَش را فعال می‌کند؛ این گزینه را غیرفعال نکنید مگر اینکه از عواقب آن مطمئن باشید", - "storage_template_migration": "انتقال الگوی ذخیره سازی", - "storage_template_migration_description": "قالب فعلی {template} را به دارایی‌های بارگذاری شده قبلی اعمال کنید", - "storage_template_migration_info": "تغییرات قالب فقط به دارایی‌های جدید اعمال خواهد شد. برای اعمال قالب به دارایی‌های بارگذاری شده قبلی، باید {job} را اجرا کنید.", - "storage_template_migration_job": "وظیفه مهاجرت الگوی ذخیره‌سازی", - "storage_template_more_details": "برای جزئیات بیشتر درباره این ویژگی، به قالب ذخیره‌سازی و مفاهیم آن مراجعه کنید", - "storage_template_path_length": "حداکثر طول مسیر تقریبی: {length, number}/{limit, number}", - "storage_template_settings": "قالب ذخیره‌سازی", - "storage_template_settings_description": "مدیریت ساختار پوشه و نام فایل دارایی بارگذاری شده", - "storage_template_user_label": "{label} برچسب ذخیره‌سازی کاربر است", - "system_settings": "تنظیمات سیستم", - "tag_cleanup_job": "پاک سازی تگ", - "template_email_available_tags": "شما میتوانید از متغیر های روبرو در قالب خود استفاده کنید: {tags}", - "template_email_if_empty": "اگر قالب خالی باشد، ایمیل پیشفرض استفاده خواهد شد.", - "template_email_preview": "پیش نمایش", - "template_email_settings": "قالب های ایمیل", - "template_email_update_album": "قالب بروزرسانی آلبوم", - "template_email_welcome": "قالب ایمیل خوش آمد گویی", - "template_settings": "قالب های اعلان ها", - "template_settings_description": "مدیریت قالب های سفارشی برای اعلان ها", - "theme_custom_css_settings": "CSS سفارشی", - "theme_custom_css_settings_description": "برگه‌های سبک آبشاری (CSS) امکان سفارشی‌سازی طراحی Immich را فراهم می‌کنند.", - "theme_settings": "تنظیمات پوسته", - "theme_settings_description": "مدیریت سفارشی‌سازی رابط کاربری وب Immich", - "thumbnail_generation_job": "ایجاد تصاویر بندانگشتی", - "thumbnail_generation_job_description": "ایجاد تصاویر بندانگشتی بزرگ، کوچک و تار برای هر دارایی، همچنین تصاویر بندانگشتی برای هر فرد", - "transcoding_acceleration_api": "API شتاب‌دهنده", - "transcoding_acceleration_api_description": "API که با دستگاه شما تعامل خواهد داشت تا فرایند تبدیل (ترنسکودینگ) را تسریع کند. این تنظیم به‌صورت «بهترین تلاش» عمل می‌کند: در صورت شکست، به تبدیل نرم‌افزاری بازمی‌گردد. عملکرد VP9 بسته به سخت‌افزار شما ممکن است کار کند یا نکند.", - "transcoding_acceleration_nvenc": "NVENC ( کارت گرافیک NVIDIA لازم است )", - "transcoding_acceleration_qsv": "همگام سازی سریع (نیاز به پردازنده اینتل نسل هفتم یا بالاتر)", - "transcoding_acceleration_rkmpp": "RKMPP (فقط بر روی Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "کدک‌های صوتی پذیرفته شده", - "transcoding_accepted_audio_codecs_description": "انتخاب کدک‌های صوتی که نیازی به تبدیل (ترنسکود) ندارند. فقط برای برخی سیاست‌های رمزگشایی استفاده می‌شود.", - "transcoding_accepted_containers": "کانتینرهای پذیرفته شده", - "transcoding_accepted_containers_description": "انتخاب قالب‌های محتوایی که نیازی به تغییر به MP4 ندارند. فقط برای برخی سیاست‌های رمزگشایی استفاده می‌شود.", - "transcoding_accepted_video_codecs": "کدک‌های ویدیویی پذیرفته شده", - "transcoding_accepted_video_codecs_description": "انتخاب کدک‌های ویدیویی که نیازی به تبدیل (ترنسکود) ندارند. فقط برای برخی سیاست‌های رمزگشایی استفاده می‌شود.", - "transcoding_advanced_options_description": "گزینه‌هایی که بیشتر کاربران نیازی به تغییر آن‌ها ندارند", - "transcoding_audio_codec": "کدک صوتی", - "transcoding_audio_codec_description": "OPUS بهترین گزینه از نظر کیفیت است، اما با دستگاه‌ها یا نرم‌افزارهای قدیمی‌تر سازگاری کمتری دارد.", - "transcoding_bitrate_description": "ویدیوهایی که بالاتر از حداکثر بیت‌ریت هستند یا در فرمت پذیرفته‌ شده نیستند", - "transcoding_codecs_learn_more": "برای آشنایی بیشتر با اصطلاحات استفاده شده در اینجا، به مستندات FFmpeg برای کدک‌های H.264، HEVC و VP9 مراجعه کنید.", - "transcoding_constant_quality_mode": "حالت کیفیت ثابت", - "transcoding_constant_quality_mode_description": "ICQ بهتر از CQP است، اما برخی از دستگاه‌های تسریع سخت‌افزاری از این حالت پشتیبانی نمی‌کنند. تنظیم این گزینه باعث می‌شود که حالت مشخص شده در هنگام استفاده از کدگذاری مبتنی بر کیفیت ترجیح داده شود. این گزینه توسط NVENC نادیده گرفته می‌شود زیرا از ICQ پشتیبانی نمی‌کند.", - "transcoding_constant_rate_factor": "ضریب نرخ ثابت ( crf- )", - "transcoding_constant_rate_factor_description": "سطح کیفیت ویدیو. هرچه عدد کمتر باشد، کیفیت بهتر است، اما فایل‌های بزرگ‌تری تولید می‌کند. مقادیر معمول عبارتند از: (23 <-- H.264) - (28 --> HEVC) - (31 --> VP9) - (35 --> AV1).", - "transcoding_disabled_description": "هیچ ویدیویی را تبدیل فرمت نکنید، زیرا ممکن است پخش در برخی از کلاینت‌ها را مختل کند", - "transcoding_hardware_acceleration": "شتاب دهنده سخت افزاری", - "transcoding_hardware_acceleration_description": "آزمایشی: Transcoding سریع تر اما ممکن است در bitrate یکسان کیفیت را کاهش دهد", - "transcoding_hardware_decoding": "رمزگشایی سخت افزاری", - "transcoding_max_b_frames": "بیشترین B-frames", - "transcoding_max_b_frames_description": "مقادیر بالاتر کارایی فشرده سازی را بهبود می‌بخشند، اما کدگذاری را کند می‌کنند. ممکن است با شتاب دهی سخت‌افزاری در دستگاه‌های قدیمی سازگار نباشد. مقدار( 0 ) B-frames را غیرفعال می‌کند، در حالی که مقدار ( 1 ) این مقدار را به صورت خودکار تنظیم می‌کند.", - "transcoding_max_bitrate": "بیشترین بیت ریت", - "transcoding_max_bitrate_description": "تنظیم حداکثر بیت‌ریت می‌تواند اندازه فایل‌ها را در حدی قابل پیش‌بینی‌تر کند، هرچند که هزینه کمی برای کیفیت دارد. در وضوح 720p، مقادیر معمول 2600 kbit/s برای VP9 یا HEVC و 4500 kbit/s برای H.264 است. اگر به 0 تنظیم شود، غیرفعال می‌شود. زمانی که واحد اندازه فایل مشخص نشود، کیلوبایت در نظر گرفته میشود; در نتیجه 5000، 5000k , 5M (برای Mbit/s) یکسانند.", - "transcoding_max_keyframe_interval": "حداکثر فاصله کلید فریم", - "transcoding_max_keyframe_interval_description": "حداکثر فاصله فریم بین کلیدفریم‌ها را تنظیم می‌کند. مقادیر پایین‌تر کارایی فشرده‌سازی را کاهش می‌دهند، اما زمان جستجو را بهبود می‌بخشند و ممکن است کیفیت را در صحنه‌های با حرکت سریع بهبود دهند. مقدار 0 این مقدار را به‌طور خودکار تنظیم می‌کند.", - "transcoding_optimal_description": "ویدیوهایی که از رزولوشن هدف بالاتر هستند یا در قالب پذیرفته شده نیستند", - "transcoding_preferred_hardware_device": "دستگاه سخت‌افزاری ترجیحی", - "transcoding_preferred_hardware_device_description": "این گزینه فقط به VAAPI و QSV اعمال می‌شود. DRI node مورد استفاده برای تبدیل فرمت سخت‌افزاری را تنظیم می‌کند.", - "transcoding_preset_preset": "پیش‌تنظیم (preset-)", - "transcoding_preset_preset_description": "سرعت فشرده‌سازی. پیش‌تنظیم‌های کندتر فایل‌های کوچک‌تری تولید می‌کنند و کیفیت را هنگام هدف‌گذاری بر روی یک بیت‌ریت خاص افزایش می‌دهند. VP9 سرعت‌های بالاتر از 'faster' را نادیده می‌گیرد.", - "transcoding_reference_frames": "فریم‌های مرجع", - "transcoding_reference_frames_description": "تعداد فریم‌هایی که هنگام فشرده‌سازی یک فریم مشخص به آن‌ها ارجاع داده می‌شود. مقادیر بالاتر کارایی فشرده‌سازی را بهبود می‌بخشند، اما کدگذاری را کندتر می‌کنند. مقدار 0 این مقدار را به‌طور خودکار تنظیم می‌کند.", - "transcoding_required_description": "فقط ویدیوهایی که در فرمت پذیرفته‌شده نیستند", - "transcoding_settings": "تنظیمات تبدیل ویدیو", - "transcoding_settings_description": "مدیریت اینکه کدام ویدیو ها transcode شوند و چگونگی پردازش آنها", - "transcoding_target_resolution": "وضوح هدف", - "transcoding_target_resolution_description": "وضوح‌های بالاتر می‌توانند جزئیات بیشتری را حفظ کنند، اما زمان بیشتری برای کدگذاری نیاز دارند، اندازه فایل‌های بزرگ‌تری دارند و ممکن است باعث کاهش پاسخگویی برنامه شوند.", - "transcoding_temporal_aq": "AQ موقتی", - "transcoding_temporal_aq_description": "این مورد فقط برای NVENC اعمال می شود. Temporal Adaptive Quantization کیفیت صحنه های با جزئیات بالا، تحرک کم را افزایش می دهد. ممکن است با دستگاه های قدیمی تر سازگار نباشد.", - "transcoding_threads": "رشته ها ( موضوعات )", - "transcoding_threads_description": "مقادیر بالاتر منجر به رمزگذاری سریع تر می شود، اما فضای کمتری برای پردازش سایر وظایف سرور در حین فعالیت باقی می گذارد. این مقدار نباید بیشتر از تعداد هسته های CPU باشد. اگر روی 0 تنظیم شود، بیشترین استفاده را خواهد داشت.", - "transcoding_tone_mapping_description": "تلاش برای حفظ ظاهر ویدیوهای HDR هنگام تبدیل به SDR. هر الگوریتم تعادل های متفاوتی را برای رنگ، جزئیات و روشنایی ایجاد می کند. Hable جزئیات را حفظ می کند، Mobius رنگ را حفظ می کند و Reinhard روشنایی را حفظ می کند.", - "transcoding_transcode_policy": "سیاست رمزگذاری", - "transcoding_transcode_policy_description": "سیاست برای زمانی که ویدیویی باید مجددا تبدیل (رمزگذاری) شود. ویدیوهای HDR همیشه تبدیل (رمزگذاری) مجدد خواهند شد (مگر رمزگذاری مجدد غیرفعال باشد).", - "transcoding_two_pass_encoding": "تبدیل (رمزگذاری) دو مرحله ای", - "transcoding_two_pass_encoding_setting_description": "تبدیل (رمزگذاری) ویدیو در دو مرحله برای تولید ویدیوهای رمزگذاری شده بهتر. وقتی حداکثر نرخ بیت فعال باشد (برای کار با H.264 و HEVC لازم است)، این حالت از یک محدوده نرخ بیت بر اساس حداکثر نرخ بیت استفاده می کند و CRF را نادیده می گیرد. برای VP9، اگر حداکثر نرخ بیت غیرفعال باشد، می توان از CRF استفاده کرد.", - "transcoding_video_codec": "Codec ویدیویی", - "transcoding_video_codec_description": "VP9 کارایی بالا و سازگاری وب را دارد، اما تبدیل (رمزگذاری) مجدد آن زمان بیشتری می گیرد. HEVC عملکرد مشابهی دارد، اما سازگاری وب کمتری دارد. H.264 سازگاری گسترده و رمزگذاری سریع دارد، اما فایل های بزرگتری تولید می کند. AV1 کدک کارآمدترین است، اما از پشتیبانی در دستگاه های قدیمی تر برخوردار نیست.", - "trash_enabled_description": "فعال سازی ویژگی های سطل بازیافت (سطل زباله)", - "trash_number_of_days": "تعداد روزها", - "trash_number_of_days_description": "تعداد روزهایی که دارایی ها(عکسها و فیملها) در زباله دان(سطل بازیافت) قبل از حذف دائمی نگهداری میشوند", - "trash_settings": "تنظیمات سطل بازیافت (سطل زباله)", - "trash_settings_description": "مدیریت تنظیمات سطل بازیافت (سطل زباله)", - "unlink_all_oauth_accounts": "جداسازی تمامی اکانت های OAuth", - "unlink_all_oauth_accounts_description": "به یاد داشته باشید حتما قبل از انتقال به ارائه دهنده جدید تمامی اکانت های OAuth را جداسازی کنید.", - "unlink_all_oauth_accounts_prompt": "آیا اطمینان دارید میخواهید تمامی اکانت های OAuth را جدا سازی کنید؟ اینکار OAuth ID تمامی کاربران را ریست می کند و قابل برگشت نیست.", - "user_cleanup_job": "پاک سازی کاربر", - "user_delete_delay": "{user}'s حساب کاربری و دارایی ها(عکس و فیلم) برای حذف دائمی در {delay, plural, one {# روز} other {# روز}} برنامه ریزی خواهند شد.", - "user_delete_delay_settings": "تأخیر در حذف", - "user_delete_delay_settings_description": "تعداد روزهایی که پس از حذف، حساب کاربری و دارایی های(عکس و فیلم) کاربر به طور دائمی حذف می شوند. کار حذف کاربر در نیمه شب اجرا می شود تا کاربرانی که آماده حذف هستند را بررسی کند. تغییرات در این تنظیم در اجرای بعدی ارزیابی خواهند شد.", - "user_delete_immediately": "{user}'s حساب کاربری و دارایی ها (عکس و فیلم) فوراً برای حذف دائمی در صف قرار خواهند گرفت.", - "user_delete_immediately_checkbox": "کاربر و دارایی ها (عکس و فیلم) را برای حذف فوری در صف قرار بده", - "user_details": "جزئیات کاربر", - "user_management": "مدیریت کاربر", - "user_password_has_been_reset": "رمز عبور کاربر بازنشانی شد:", - "user_password_reset_description": "لطفاً رمز عبور موقت را به کاربر ارائه دهید و به او اطلاع دهید که باید در ورود بعدی رمز عبور خود را تغییر دهد.", - "user_restore_description": "{user}'s حساب کاربری بازیابی خواهد شد.", - "user_restore_scheduled_removal": "بازیابی کاربر - حذف برنامه ریزی شده در {date, date, long}", - "user_settings": "تنظیمات کاربر", - "user_settings_description": "مدیریت تنظیمات کاربر", - "user_successfully_removed": "کاربر {email} با موفقیت حذف شد.", - "users_page_description": "صفحه مدیریت کاربران", - "version_check_enabled_description": "فعال‌سازی بررسی نسخه", - "version_check_implications": "ویژگی بررسی نسخه به ارتباط دوره ای با github.com متکی است", - "version_check_settings": "بررسی نسخه", - "version_check_settings_description": "فعال یا غیرفعال کردن اعلان نسخه جدید", - "video_conversion_job": "تبدیل (رمزگذاری) ویدیوها", - "video_conversion_job_description": "تبدیل (رمزگذاری)ویدیوها برای سازگاری بیشتر با مرورگرها و دستگاه‌ها" - }, - "admin_email": "ایمیل مدیر", - "admin_password": "رمز عبور مدیر", - "administration": "مدیریت", - "advanced": "پیشرفته", - "advanced_settings_proxy_headers_title": "هدر های پروکسی سفارشی [آزمایشی]", - "album_added": "آلبوم اضافه شد", - "album_cover_updated": "جلد آلبوم به‌روزرسانی شد", - "album_info_updated": "اطلاعات آلبوم به‌روزرسانی شد", - "album_name": "نام آلبوم", - "album_options": "گزینه‌های آلبوم", - "album_updated": "آلبوم به‌روزرسانی شد", - "albums": "آلبوم‌ها", - "all": "همه", - "all_people": "همه افراد", - "allow_dark_mode": "اجازه دادن به حالت تاریک", - "allow_edits": "اجازه ویرایش", - "api_key": "کلید API", - "api_keys": "کلیدهای API", - "app_settings": "تنظیمات برنامه", - "appears_in": "ظاهر می‌شود در", - "archive": "بایگانی", - "archive_size": "اندازه بایگانی", - "asset_offline": "محتوا آفلاین", - "assets": "محتواها", - "authorized_devices": "دستگاه‌های مجاز", - "back": "بازگشت", - "backward": "عقب", - "blurred_background": "پس‌زمینه محو", - "camera": "دوربین", - "camera_brand": "برند دوربین", - "camera_model": "مدل دوربین", - "cancel": "لغو", - "cancel_search": "لغو جستجو", - "cannot_merge_people": "نمی‌توان افراد را ادغام کرد", - "cannot_update_the_description": "نمی‌توان توضیحات را به‌روزرسانی کرد", - "change_date": "تغییر تاریخ", - "change_expiration_time": "تغییر زمان انقضا", - "change_location": "تغییر مکان", - "change_name": "تغییر نام", - "change_name_successfully": "نام با موفقیت تغییر یافت", - "change_password": "تغییر رمز عبور", - "change_password_form_password_mismatch": "رمز عبور ها مطابقت ندارند", - "change_password_form_reenter_new_password": "تکرار رمز عبور جدید", - "change_your_password": "رمز عبور خود را تغییر دهید", - "check_logs": "بررسی لاگ‌ها", - "city": "شهر", - "clear": "پاک کردن", - "clear_all": "پاک کردن همه", - "clear_message": "پاک کردن پیام", - "clear_value": "پاک کردن مقدار", - "close": "بستن", - "collapse_all": "جمع کردن همه", - "color_theme": "تم رنگ", - "comment_options": "گزینه‌های نظر", - "comments_are_disabled": "نظرات غیرفعال هستند", - "confirm": "تأیید", - "confirm_admin_password": "تأیید رمز عبور مدیر", - "confirm_password": "تأیید رمز عبور", - "contain": "شامل", - "context": "زمینه", - "continue": "ادامه", - "copied_image_to_clipboard": "تصویر به کلیپ‌بورد کپی شد.", - "copied_to_clipboard": "به کلیپ‌بورد کپی شد!", - "copy_error": "خطا در کپی", - "copy_file_path": "کپی مسیر فایل", - "copy_image": "کپی تصویر", - "copy_link": "کپی لینک", - "copy_link_to_clipboard": "کپی لینک به کلیپ‌بورد", - "copy_password": "کپی رمز عبور", - "copy_to_clipboard": "کپی به کلیپ‌بورد", - "country": "کشور", - "cover": "جلد", - "covers": "جلدها", - "create": "ایجاد", - "create_album": "ایجاد آلبوم", - "create_library": "ایجاد کتابخانه", - "create_link": "ایجاد لینک", - "create_link_to_share": "ایجاد لینک برای اشتراک‌گذاری", - "create_new_person": "ایجاد فرد جدید", - "create_new_user": "ایجاد کاربر جدید", - "create_user": "ایجاد کاربر", - "created": "ایجاد شد", - "current_device": "دستگاه فعلی", - "dark": "تاریک", - "date_after": "تاریخ پس از", - "date_and_time": "تاریخ و زمان", - "date_before": "تاریخ قبل از", - "date_range": "بازه زمانی", - "day": "روز", - "deduplicate_all": "حذف تکراری‌ها به صورت کامل", - "delete": "حذف", - "delete_album": "حذف آلبوم", - "delete_key": "حذف کلید", - "delete_library": "حذف کتابخانه", - "delete_link": "حذف لینک", - "delete_shared_link": "حذف لینک اشتراکی", - "delete_user": "حذف کاربر", - "deleted_shared_link": "لینک اشتراکی حذف شد", - "description": "توضیحات", - "details": "جزئیات", - "direction": "جهت", - "disabled": "غیرفعال", - "disallow_edits": "عدم اجازه ویرایش", - "discover": "کشف کردن", - "dismiss_all_errors": "رد تمام خطاها", - "dismiss_error": "رد خطا", - "display_options": "گزینه‌های نمایش", - "display_order": "ترتیب نمایش", - "display_original_photos": "نمایش عکس‌های اصلی", - "done": "انجام شد", - "download": "دانلود", - "download_settings": "تنظیمات دانلود", - "download_settings_description": "مدیریت تنظیمات مرتبط با دانلود محتوا", - "downloading": "در حال دانلود", - "duplicates": "تکراری‌ها", - "duration": "مدت زمان", - "edit_album": "ویرایش آلبوم", - "edit_avatar": "ویرایش آواتار", - "edit_date": "ویرایش تاریخ", - "edit_date_and_time": "ویرایش تاریخ و زمان", - "edit_exclusion_pattern": "ویرایش الگوی استثناء", - "edit_faces": "ویرایش چهره‌ها", - "edit_key": "ویرایش کلید", - "edit_link": "ویرایش لینک", - "edit_location": "ویرایش مکان", - "edit_name": "ویرایش نام", - "edit_people": "ویرایش افراد", - "edit_title": "ویرایش عنوان", - "edit_user": "ویرایش کاربر", - "editor": "ویرایشگر", - "email": "ایمیل", - "empty_trash": "خالی کردن سطل زباله", - "end_date": "تاریخ پایان", - "error": "خطا", - "error_loading_image": "خطا در بارگذاری تصویر", - "exit_slideshow": "خروج از نمایش اسلاید", - "expand_all": "باز کردن همه", - "expire_after": "منقضی شدن بعد از", - "expired": "منقضی شده", - "explore": "کاوش کردن", - "export": "صادر کردن", - "export_as_json": "صادر کردن به‌صورت JSON", - "extension": "پسوند", - "external": "خارجی", - "external_libraries": "کتابخانه‌های خارجی", - "favorite": "علاقه‌مندی", - "favorites": "علاقه‌مندی‌ها", - "file_name_or_extension": "نام فایل یا پسوند", - "filename": "نام فایل", - "filetype": "نوع فایل", - "filter_people": "فیلتر افراد", - "fix_incorrect_match": "رفع تطابق نادرست", - "forward": "جلو", - "general": "عمومی", - "get_help": "دریافت کمک", - "getting_started": "شروع به کار", - "go_back": "بازگشت", - "go_to_search": "رفتن به جستجو", - "group_albums_by": "گروه‌بندی آلبوم‌ها براساس...", - "has_quota": "دارای سهمیه", - "hide_gallery": "پنهان کردن گالری", - "hide_password": "پنهان کردن رمز عبور", - "hide_person": "پنهان کردن فرد", - "host": "میزبان", - "hour": "ساعت", - "image": "تصویر", - "immich_logo": "لوگوی Immich", - "immich_web_interface": "رابط وب Immich", - "import_from_json": "وارد کردن از JSON", - "import_path": "مسیر وارد کردن", - "in_archive": "در بایگانی", - "include_archived": "شامل بایگانی شده‌ها", - "include_shared_albums": "شامل آلبوم‌های اشتراکی", - "individual_share": "اشتراک فردی", - "info": "اطلاعات", - "invite_people": "دعوت افراد", - "invite_to_album": "دعوت به آلبوم", - "jobs": "وظایف", - "keep": "نگه داشتن", - "keep_all": "نگه داشتن همه", - "keyboard_shortcuts": "میانبرهای صفحه‌کلید", - "language": "زبان", - "language_setting_description": "انتخاب زبان دلخواه شما", - "last_seen": "آخرین مشاهده", - "leave": "ترک کردن", - "let_others_respond": "اجازه به دیگران برای پاسخ‌گویی", - "level": "سطح", - "library": "کتابخانه", - "library_options": "گزینه‌های کتابخانه", - "light": "روشن", - "link_to_oauth": "اتصال به OAuth", - "linked_oauth_account": "حساب OAuth متصل شده", - "list": "لیست", - "loading": "در حال بارگذاری", - "loading_search_results_failed": "بارگذاری نتایج جستجو ناموفق بود", - "log_out": "خروج از سیستم", - "log_out_all_devices": "خروج از همه دستگاه‌ها", - "login_has_been_disabled": "ورود غیرفعال شده است.", - "look": "نگاه کردن", - "loop_videos": "پخش مداوم ویدئوها", - "main_branch_warning": "شما در حال استفاده از نسخه توسعه‌دهندگان هستید که آزمایشی و ناپایدار است. اکیداً توصیه می‌کنیم از نسخه رسمی استفاده کنید!", - "main_menu": "منوی اصلی", - "make": "ساختن", - "manage_shared_links": "مدیریت لینک‌های اشتراکی", - "manage_sharing_with_partners": "مدیریت محتوای مشترک با همسر", - "manage_the_app_settings": "مدیریت تنظیمات برنامه", - "manage_your_account": "مدیریت حساب کاربری", - "manage_your_api_keys": "مدیریت کلیدهای API", - "manage_your_devices": "مدیریت دستگاه‌های متصل", - "manage_your_oauth_connection": "مدیریت اتصال OAuth", - "map": "نقشه", - "map_assets_in_bounds": "{count} عکس ها", - "map_cannot_get_user_location": "موقعیت مکانی در دسترس نیست", - "map_location_dialog_yes": "بله", - "map_location_picker_page_use_location": "استفاده از این موقعیت مکانی", - "map_location_service_disabled_content": "برای نمایش دارایی‌ها بر اساس موقعیت مکانی، نیاز به فعال‌سازی سرویس مکان‌یابی دارید. می‌خواهید همین حالا فعال شود؟", - "map_location_service_disabled_title": "سرویس مکان‌یابی غیرفعال است", - "map_marker_for_images": "نشانگر روی نقشه برای عکس‌های گرفته‌شده در {city}, {country}", - "map_marker_with_image": "علامت‌گذاری نقشه با عکس", - "map_no_location_permission_content": "برای نمایش عکس‌های اطرافتان، برنامه نیاز به دسترسی به موقعیت مکانی دارد. اجازه دسترسی می‌دهید؟", - "map_no_location_permission_title": "دسترسی به موقعیت شما فعال نیست", - "map_settings": "تنظیمات نقشه", - "map_settings_dark_mode": "حالت تاریک", - "map_settings_date_range_option_day": "۲۴ ساعت گذشته", - "map_settings_date_range_option_days": "{days} روز گذشته", - "map_settings_date_range_option_year": "پارسال", - "map_settings_date_range_option_years": "{years} سال گذشته", - "map_settings_dialog_title": "تنظیمات نقشه", - "map_settings_include_show_archived": "شامل موارد بایگانی شده", - "map_settings_include_show_partners": "شامل همسر", - "matches": "تطابق‌ها", - "media_type": "نوع رسانه", - "memories": "خاطرات", - "memory": "خاطره", - "menu": "منو", - "merge": "ادغام", - "merge_people": "ادغام افراد", - "merge_people_successfully": "ادغام افراد با موفقیت انجام شد", - "minimize": "کوچک کردن", - "minute": "دقیقه", - "missing": "گمشده", - "model": "مدل", - "month": "ماه", - "more": "بیشتر", - "moved_to_trash": "به سطل زباله منتقل شد", - "my_albums": "آلبوم‌های من", - "name": "نام", - "name_or_nickname": "نام یا لقب", - "never": "هرگز", - "new_api_key": "کلید API جدید", - "new_password": "رمز عبور جدید", - "new_person": "فرد جدید", - "new_user_created": "کاربر جدید ایجاد شد", - "newest_first": "جدیدترین ابتدا", - "next": "بعدی", - "next_memory": "خاطره بعدی", - "no": "خیر", - "no_duplicates_found": "هیچ تکراری یافت نشد.", - "no_exif_info_available": "اطلاعات EXIF موجود نیست", - "no_name": "بدون نام", - "no_places": "مکانی یافت نشد", - "no_results": "نتیجه‌ای یافت نشد", - "not_in_any_album": "در هیچ آلبومی نیست", - "notes": "یادداشت‌ها", - "notification_toggle_setting_description": "اعلان‌های ایمیلی را فعال کنید", - "notifications": "اعلان‌ها", - "notifications_setting_description": "مدیریت اعلان‌ها", - "offline": "آفلاین", - "ok": "تأیید", - "oldest_first": "قدیمی‌ترین ابتدا", - "online": "آنلاین", - "only_favorites": "فقط علاقه‌مندی‌ها", - "open_the_search_filters": "باز کردن فیلترهای جستجو", - "options": "گزینه‌ها", - "organize_your_library": "کتابخانه خود را سازماندهی کنید", - "other": "دیگر", - "other_devices": "دستگاه‌های دیگر", - "other_variables": "متغیرهای دیگر", - "owned": "مالکیت", - "owner": "مالک", - "partner": "شریک", - "partner_can_access": "{partner} می‌تواند دسترسی داشته باشد", - "partner_can_access_location": "مکان‌هایی که عکس‌های شما گرفته شده‌اند", - "partner_sharing": "اشتراک‌گذاری با شریک", - "partners": "شرکا", - "password": "رمز عبور", - "password_does_not_match": "رمز عبور مطابقت ندارد", - "password_required": "رمز عبور مورد نیاز است", - "password_reset_success": "بازنشانی رمز عبور موفقیت‌آمیز بود", - "path": "مسیر", - "pattern": "الگو", - "pause": "توقف", - "pause_memories": "توقف خاطرات", - "paused": "متوقف شده", - "pending": "در انتظار", - "people": "افراد", - "permanent_deletion_warning": "هشدار حذف دائمی", - "permanent_deletion_warning_setting_description": "نمایش هشدار هنگام حذف دائمی محتواها", - "permanently_delete": "حذف دائمی", - "permanently_deleted_asset": "محتوای حذف شده دائمی", - "person": "فرد", - "photos": "عکس‌ها", - "photos_from_previous_years": "عکس‌های سال‌های گذشته", - "pick_a_location": "یک مکان انتخاب کنید", - "place": "مکان", - "places": "مکان‌ها", - "play": "پخش", - "play_memories": "پخش خاطرات", - "play_motion_photo": "پخش عکس متحرک", - "play_or_pause_video": "پخش یا توقف ویدیو", - "port": "پورت", - "preset": "پیش‌فرض", - "preview": "پیش‌نمایش", - "previous": "قبلی", - "previous_memory": "خاطره قبلی", - "previous_or_next_photo": "عکس قبلی یا بعدی", - "primary": "اصلی", - "profile_picture_set": "تصویر پروفایل تنظیم شد.", - "public_share": "اشتراک عمومی", - "reaction_options": "گزینه‌های واکنش", - "read_changelog": "مطالعه تغییرات نسخه", - "recent": "اخیر", - "recent_searches": "جستجوهای اخیر", - "refresh": "تازه سازی", - "refreshed": "تازه سازی شد", - "remove": "حذف", - "remove_deleted_assets": "حذف محتواهای حذف‌شده", - "remove_from_album": "حذف از آلبوم", - "remove_from_favorites": "حذف از علاقه‌مندی‌ها", - "rename": "تغییر نام", - "repair": "تعمیر", - "replace_with_upload": "جایگزینی با آپلود", - "reset": "بازنشانی", - "reset_password": "بازنشانی رمز عبور", - "restore": "بازیابی", - "restore_all": "بازیابی همه", - "restore_user": "بازیابی کاربر", - "resume": "ادامه", - "review_duplicates": "بررسی تکراری‌ها", - "role": "نقش", - "save": "ذخیره", - "saved_profile": "پروفایل ذخیره شد", - "saved_settings": "تنظیمات ذخیره شد", - "say_something": "چیزی بگویید", - "scan_all_libraries": "اسکن همه کتابخانه‌ها", - "scan_settings": "تنظیمات اسکن", - "search": "جستجو", - "search_albums": "جستجوی آلبوم‌ها", - "search_by_context": "جستجو براساس زمینه", - "search_camera_make": "جستجوی برند دوربین...", - "search_camera_model": "جستجوی مدل دوربین...", - "search_city": "جستجوی شهر...", - "search_country": "جستجوی کشور...", - "search_for_existing_person": "جستجوی فرد موجود", - "search_people": "جستجوی افراد", - "search_places": "جستجوی مکان‌ها", - "search_state": "جستجوی ایالت...", - "search_timezone": "جستجوی منطقه زمانی...", - "search_type": "نوع جستجو", - "second": "ثانیه", - "select_album_cover": "انتخاب جلد آلبوم", - "select_all": "انتخاب همه", - "select_avatar_color": "انتخاب رنگ آواتار", - "select_face": "انتخاب چهره", - "select_featured_photo": "انتخاب عکس ویژه", - "select_keep_all": "انتخاب نگهداری همه", - "select_library_owner": "انتخاب مالک کتابخانه", - "select_new_face": "انتخاب چهره جدید", - "select_photos": "انتخاب عکس‌ها", - "selected": "انتخاب شده", - "send_message": "ارسال پیام", - "send_welcome_email": "ارسال ایمیل خوش‌آمدگویی", - "server_stats": "آمار سرور", - "set": "تنظیم", - "set_date_of_birth": "تنظیم تاریخ تولد", - "set_profile_picture": "تنظیم تصویر پروفایل", - "settings": "تنظیمات", - "settings_saved": "تنظیمات ذخیره شد", - "share": "اشتراک‌گذاری", - "shared": "مشترک", - "shared_by": "مشترک توسط", - "shared_from_partner": "عکس‌ها از {partner}", - "shared_links": "لینک‌های اشتراکی", - "shared_with_partner": "مشترک با {partner}", - "sharing": "اشتراک‌گذاری", - "show_album_options": "نمایش گزینه‌های آلبوم", - "show_file_location": "نمایش مسیر فایل", - "show_gallery": "نمایش گالری", - "show_hidden_people": "نمایش افراد پنهان", - "show_metadata": "نمایش اطلاعات متا", - "show_password": "نمایش رمز عبور", - "show_progress_bar": "نمایش نوار پیشرفت", - "show_search_options": "نمایش گزینه‌های جستجو", - "shuffle": "تصادفی", - "sign_out": "خروج", - "sign_up": "ثبت‌نام", - "size": "اندازه", - "skip_to_content": "رفتن به محتوا", - "slideshow": "نمایش اسلاید", - "slideshow_settings": "تنظیمات نمایش اسلاید", - "stack": "پشته", - "start": "شروع", - "start_date": "تاریخ شروع", - "state": "ایالت", - "status": "وضعیت", - "stop_motion_photo": "توقف عکس متحرک", - "storage": "فضای ذخیره‌سازی", - "storage_label": "برچسب فضای ذخیره‌سازی", - "submit": "ارسال", - "suggestions": "پیشنهادات", - "swap_merge_direction": "تغییر جهت ادغام", - "sync": "همگام‌سازی", - "template": "الگو", - "theme": "تم", - "theme_selection": "انتخاب تم", - "timezone": "منطقه زمانی", - "to_archive": "بایگانی", - "to_favorite": "به علاقه‌مندی‌ها", - "toggle_settings": "تغییر تنظیمات", - "total_usage": "استفاده کلی", - "trash": "سطل زباله", - "type": "نوع", - "unfavorite": "حذف از علاقه‌مندی‌ها", - "unhide_person": "آشکار کردن فرد", - "unknown": "ناشناخته", - "unknown_year": "سال نامشخص", - "unlimited": "نامحدود", - "unlink_oauth": "لغو اتصال OAuth", - "unnamed_album": "آلبوم بدون نام", - "unnamed_share": "اشتراک بدون نام", - "unselect_all": "لغو انتخاب همه", - "up_next": "مورد بعدی", - "upload": "آپلود", - "upload_concurrency": "تعداد آپلود همزمان", - "url": "آدرس", - "usage": "استفاده", - "user": "کاربر", - "user_id": "شناسه کاربر", - "user_usage_detail": "جزئیات استفاده کاربر", - "username": "نام کاربری", - "users": "کاربران", - "utilities": "ابزارها", - "validate": "اعتبارسنجی", - "variables": "متغیرها", - "version": "نسخه", - "video": "ویدیو", - "videos": "ویدیوها", - "view": "مشاهده", - "view_all": "مشاهده همه", - "view_all_users": "مشاهده همه کاربران", - "view_links": "مشاهده لینک‌ها", - "view_next_asset": "مشاهده محتوای بعدی", - "view_previous_asset": "مشاهده محتوای قبلی", - "waiting": "در انتظار", - "week": "هفته", - "welcome": "خوش آمدید", - "year": "سال", - "yes": "بله", - "zoom_image": "بزرگنمایی تصویر" -} +{} diff --git a/i18n/fi.json b/i18n/fi.json index a47cd53933..0967ef424b 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -1,2350 +1 @@ -{ - "about": "Tietoja", - "account": "Tili", - "account_settings": "Tilin asetukset", - "acknowledge": "Hyväksy", - "action": "Toiminta", - "action_common_update": "Päivitä", - "actions": "Toimintoja", - "active": "Aktiivinen", - "active_count": "Aktiivisia: {count}", - "activity": "Tapahtumat", - "activity_changed": "Toiminto {enabled, select, true {otettu käyttöön} other {poistettu käytöstä}}", - "add": "Lisää", - "add_a_description": "Lisää kuvaus", - "add_a_location": "Lisää sijainti", - "add_a_name": "Lisää nimi", - "add_a_title": "Lisää otsikko", - "add_action": "Lisää toiminto", - "add_action_description": "Klikkaa lisätäksesi suoritettava toiminto", - "add_birthday": "Lisää syntymäpäivä", - "add_endpoint": "Lisää päätepiste", - "add_exclusion_pattern": "Lisää poissulkemismalli", - "add_filter": "Lisää suodatin", - "add_filter_description": "Klikkaa lisätäksesi suodatinehto", - "add_location": "Lisää sijainti", - "add_more_users": "Lisää käyttäjiä", - "add_partner": "Lisää kumppani", - "add_path": "Lisää polku", - "add_photos": "Lisää kuvia", - "add_tag": "Lisää tunniste", - "add_to": "Lisää…", - "add_to_album": "Lisää albumiin", - "add_to_album_bottom_sheet_added": "Lisätty albumiin {album}", - "add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}", - "add_to_album_bottom_sheet_some_local_assets": "Joitakin osia paikallisesta sisällöstä ei pystytty lisäämään albumiin", - "add_to_album_toggle": "Vaihda albumin {album} valintaa", - "add_to_albums": "Lisää albumeihin", - "add_to_albums_count": "Lisää albumeihin ({count})", - "add_to_bottom_bar": "Lisää", - "add_to_shared_album": "Lisää jaettuun albumiin", - "add_upload_to_stack": "Lisää kuvapinoon", - "add_url": "Lisää URL", - "add_workflow_step": "Lisää työnkulun vaihe", - "added_to_archive": "Lisätty arkistoon", - "added_to_favorites": "Lisätty suosikkeihin", - "added_to_favorites_count": "{count, number} lisätty suosikkeihin", - "admin": { - "add_exclusion_pattern_description": "Lisää mallit, jonka mukaan jätetään tiedostoja pois. Jokerimerkit *, ** ja ? ovat tuettuna. Jättääksesi pois kaikki tiedostot mistä tahansa löytyvästä kansiosta \"Raw\" käytä \"**/Raw/**\". Jättääksesi pois kaikki \". tif\" päätteiset tiedot, käytä \"**/*.tif\". Jättääksesi pois tarkan tiedostopolun, käytä \"/path/to/ignore/**\".", - "admin_user": "Ylläpitäjä", - "asset_offline_description": "Ulkoista kirjaston resurssia ei enää löydy levyltä, ja se on siirretty roskakoriin. Jos tiedosto siirrettiin kirjaston sisällä, tarkista aikajanaltasi uusi vastaava resurssi. Palauttaaksesi tämän resurssin, varmista, että alla oleva tiedostopolku on Immichin käytettävissä ja skannaa kirjasto uudelleen.", - "authentication_settings": "Autentikointiasetukset", - "authentication_settings_description": "Hallitse salasana-, OAuth- ja muut autentikoinnin asetukset", - "authentication_settings_disable_all": "Haluatko varmasti poistaa kaikki kirjautumistavat käytöstä? Kirjautuminen on tämän jälkeen mahdotonta.", - "authentication_settings_reenable": "Ottaaksesi uudestaan käyttöön, käytä Palvelin Komentoa.", - "background_task_job": "Taustatyöt", - "backup_database": "Luo tietokantavedos", - "backup_database_enable_description": "Ota tietokantavedokset käyttöön", - "backup_keep_last_amount": "Säilytettävien tietokantavedosten määrä", - "backup_onboarding_1_description": "Kopio pilvipalvelussa tai toisessa fyysisessä sijainnissa.", - "backup_onboarding_2_description": "Paikalliset kopiot eri laitteilla. Tämä sisältää sekä alkuperäiset tiedostot että niiden varmuuskopiot paikallisesti.", - "backup_onboarding_3_description": "Kopiot tiedoistasi, mukaan lukien alkuperäiset tiedostot. Tähän sisältyy yksi etäsijainnissa oleva kopio ja kaksi paikallista kopiota.", - "backup_onboarding_description": "Suosittelemme 3-2-1-varmuuskopiointistrategiaa tietojesi suojaamiseksi. Säilytä kopiot sekä ladatuista valokuvista ja videoista että Immichin tietokannasta kattavaa varmuuskopiointiratkaisua varten.", - "backup_onboarding_footer": "Lisätietoja Immich varmuuskopioinnista löydät dokumentaatiosta.", - "backup_onboarding_parts_title": "3-2-1-varmuuskopio sisältää:", - "backup_onboarding_title": "Varmuuskopiot", - "backup_settings": "Tietokantavedosten asetukset", - "backup_settings_description": "Hallitse tietokannan vedosasetuksia.", - "cleared_jobs": "Työn {job} tehtävät tyhjennetty", - "config_set_by_file": "Asetukset on tällä hetkellä määritelty tiedostosta", - "confirm_delete_library": "Haluatko varmasti poistaa kirjaston {library}?", - "confirm_delete_library_assets": "Haluatko varmasti poistaa tämän kirjaston? Tämä poistaa {count, plural, one {# kohteen} other {# kohdetta}} Immichistä eikä sitä voida perua. Tiedostot jäävät levylle.", - "confirm_email_below": "Kirjota \"{email}\" vahvistaaksesi", - "confirm_reprocess_all_faces": "Haluatko varmasti käsitellä uudelleen kaikki kasvot? Tämä poistaa myös nimetyt henkilöt.", - "confirm_user_password_reset": "Haluatko varmasti nollata käyttäjän {user} salasanan?", - "confirm_user_pin_code_reset": "Haluatko varmasti nollata käyttäjän {user} PIN-koodin?", - "copy_config_to_clipboard_description": "Kopio järjestelmän asetukset JSON-objektina leikepöydälle", - "create_job": "Luo tehtävä", - "cron_expression": "Cron-lauseke", - "cron_expression_description": "Aseta skannausväli käyttämällä cron-formaattia. Lisätietoja linkistä. Crontab Guru", - "cron_expression_presets": "Esiasetetut Cron-lausekkeet", - "disable_login": "Poista kirjautuminen käytöstä", - "duplicate_detection_job_description": "Tunnista samankaltaiset kuvat käyttäen koneoppimista. Tukeutuu Smart Search:iin", - "exclusion_pattern_description": "Poissulkemismallit mahdollistavat tiettyjen tiedostojen ja kansioiden jättämisen pois kirjastoasi skannatessa. Tästä on hyötyä jos kansiot sisältävät tiedostoja mitä et halua tuoda, kuten RAW-tiedostot.", - "export_config_as_json_description": "Lataa nykyiset järjestelmän asetukset JSON-tiedostona", - "external_libraries_page_description": "Pääkäyttäjän ulkoisen kirjaston sivu", - "face_detection": "Kasvojen havaitseminen", - "face_detection_description": "Tunnista sisällön kasvoja käyttäen koneoppimista. Videoiden osalta vain pikkukuva tunnistetaan. \"Päivitä\" (uudelleen)prosessoi koko sisällön.\"Nollaa\" lisäksi puhdistaa kaiken kasvo-datan. \"Puuttuvat\" prosessoi sisällön, jota ei vielä ole käyty läpi. Havaitut kasvot ryhmitellään jo tunnistettujen kanssa, tai lisätään uusina henkilöinä.", - "facial_recognition_job_description": "Ryhmitä havaitut kasvot henkilöihin. Tämä vaihe suoritetaan, kun kasvot on ensin havaittu. \"Nollaus\" (uudelleen-)ryhmittelee kaikki kasvot. \"Puuttuvat\" vain ne, joille ei ole määritetty henkilöä.", - "failed_job_command": "Komento {command} epäonnistui työlle {job}", - "force_delete_user_warning": "VAROITUS: Tämä poistaa käyttäjän ja kaikki mediat. Tätä ei voi perua, eikä tiedostoja voi palauttaa.", - "image_format": "Tiedostomuoto", - "image_format_description": "WebP tuottaa pienempiä tiedostoja kuin JPEG, mutta on hitaampi pakata.", - "image_fullsize_description": "Täysikokoinen kuva ilman metatietoja, käytetään zoomattaessa", - "image_fullsize_enabled": "Ota käyttöön täysikokoisen kuvan luonti", - "image_fullsize_enabled_description": "Luo täysikokoinen kuva ei-verkkoystävällisille formaateille. Jos \"Suosi upotettua esikatselua\" on käytössä, upotettuja esikatseluja käytetään suoraan ilman muuntamista. Ei vaikuta verkkoyhteensopiviin formaatteihin, kuten JPEG:hen.", - "image_fullsize_quality_description": "Täysikokoisen kuvan laatualue 1–100. Suurempi arvo on parempi, mutta johtaa suurempiin tiedostoihin.", - "image_fullsize_title": "Täysikokoisen kuvan asetukset", - "image_prefer_embedded_preview": "Suosi upotettua esikatselua", - "image_prefer_embedded_preview_setting_description": "Käytä RAW-kuviin upotettuja esikatseluja kuvankäsittelyn syötteenä ja aina kun mahdollista. Tämä voi tarjota tarkempia värejä joillekin kuville, mutta esikatselun laatu riippuu kamerasta ja kuvassa voi olla enemmän pakkausartefakteja.", - "image_prefer_wide_gamut": "Suosi laajaa väriskaalaa", - "image_prefer_wide_gamut_setting_description": "Käytä Display P3 -nimiavaruutta pikkukuville. Tämä säilöö värien vivahteet paremmin, mutta kuvat saattavat näyttää erilaisilta vanhemmissa laitteissa. sRGB-kuvat pidetään muuttumattomina, jottei värit muuttuisi.", - "image_preview_description": "Keskikokoinen kuva, josta metatiedot on poistettu, käytetään yksittäisen resurssin katseluun ja koneoppimiseen", - "image_preview_quality_description": "Esikatselulaatu 1-100. Korkeampi arvo on parempi, mutta tuottaa suurempia tiedostoja ja voi heikentää sovelluksen reagointikykyä. Matalan arvon asettaminen voi vaikuttaa koneoppimisen laatuun.", - "image_preview_title": "Esikatselun asetukset", - "image_progressive": "Progressiivinen", - "image_quality": "Laatu", - "image_resolution": "Resoluutio", - "image_resolution_description": "Korkeammat resoluutiot voivat säilyttää enemmän yksityiskohtia, mutta niiden koodaus kestää kauemmin, tiedostokoot ovat suurempia ja ne voivat heikentää sovelluksen reagointikykyä.", - "image_settings": "Kuva-asetukset", - "image_settings_description": "Hallitse luotujen kuvien laatua ja resoluutiota", - "image_thumbnail_description": "Pieni pikkukuva, josta metatiedot on poistettu, käytetään valokuvaryhmien katseluun, kuten pääaikajanalla", - "image_thumbnail_quality_description": "Pikkukuvan laatu 1-100. Korkeampi arvo on parempi, mutta tuottaa suurempia tiedostoja ja voi heikentää sovelluksen reagointikykyä.", - "image_thumbnail_title": "Pikkukuva-asetukset", - "import_config_from_json_description": "Tuo järjestelmän asetukset JSON-tiedostosta", - "job_concurrency": "Tehtävän \"{job}\" samanaikaisuus", - "job_created": "Tehtävä luotu", - "job_not_concurrency_safe": "Tätä tehtävää ei ole turvallista ajaa yhtäaikaisesti.", - "job_settings": "Tehtävän asetukset", - "job_settings_description": "Hallitse työn samanaikaisuusasetuksia", - "jobs_delayed": "{jobCount, plural, other {# viivästynyttä}}", - "jobs_failed": "{jobCount, plural, other {# epäonnistunutta}}", - "jobs_over_time": "Tehtävät ajan kuluessa", - "library_created": "Kirjasto {library} luotu", - "library_deleted": "Kirjasto poistettu", - "library_details": "Kirjaston tiedot", - "library_folder_description": "Ilmoita kansio tuotavaksi. Tämä kansio ja sen alakansiot skannataan kuvia ja videoita varten.", - "library_remove_exclusion_pattern_prompt": "Oletko varma että haluat poistaa tämän poissulkemismenetelmän?", - "library_remove_folder_prompt": "Oletko varma että haluat poistaa tämän tuodun kansion?", - "library_scanning": "Ajoittainen skannaus", - "library_scanning_description": "Määritä ajoittaiset kirjastojen skannaukset", - "library_scanning_enable_description": "Ota käyttöön ajoittaiset kirjastojen skannaukset", - "library_settings": "Ulkoinen kirjasto", - "library_settings_description": "Hallitse ulkoisen kirjaston asetuksia", - "library_tasks_description": "Skannaa ulkoisia kirjastoja uusien ja/tai muutoksien varalta", - "library_updated": "Kirjasto päivitetty", - "library_watching_enable_description": "Tarkkaile tiedostojen muuttumisia ulkoisissa kirjastoissa", - "library_watching_settings": "Kirjaston tarkkailu (KOKEELLINEN)", - "library_watching_settings_description": "Tarkkaile muuttuvia tiedostoja automaattisesti", - "logging_enable_description": "Ota lokikirjaus käyttöön", - "logging_level_description": "Kun käytössä, mitä lokituksen tasoa käytetään.", - "logging_settings": "Lokit", - "machine_learning_availability_checks": "Saatavuustarkastukset", - "machine_learning_availability_checks_description": "Automaattisesti havaitse ja suosi vapaita koneoppimisen palvelimia", - "machine_learning_availability_checks_enabled": "Laita päälle saatavuus tarkistukset", - "machine_learning_availability_checks_interval": "Tarkastusväli", - "machine_learning_availability_checks_interval_description": "Aikaväli millisekunneissa saavutettavuus tarkistuksille", - "machine_learning_availability_checks_timeout": "Pyynnön aikakatkaisu", - "machine_learning_availability_checks_timeout_description": "Aikakatkaisu aika millisekunneissa saatavuus tarkistuksille", - "machine_learning_clip_model": "CLIP-malli", - "machine_learning_clip_model_description": "Käytettävän CLIP-mallin nimi toimivien mallien listasta. Huomaa että sinun täytyy suorittaa \"Älykäs etsintä\"-työ uudelleen vaihdettuasi käytettävää mallia.", - "machine_learning_duplicate_detection": "Kaksoiskappaleiden tunnistus", - "machine_learning_duplicate_detection_enabled": "Ota käyttöön kaksoiskappaleiden tunnistus", - "machine_learning_duplicate_detection_enabled_description": "Jos ei käytössä, täsmälleen samojen aineistojen kaksoiskappaleet tullaan silti poistamaan.", - "machine_learning_duplicate_detection_setting_description": "Etsi todennäköisiä kaksoiskappaleita CLIP-upotuksien avulla", - "machine_learning_enabled": "Ota käyttöön koneoppiminen", - "machine_learning_enabled_description": "Jos poistettu käytöstä, kaikki koneoppimistoiminnot ovat pois käytöstä riippumatta alla olevista asetuksista.", - "machine_learning_facial_recognition": "Kasvojen tunnistus", - "machine_learning_facial_recognition_description": "Havaitse, tunnista ja ryhmitä kuvien kasvoja", - "machine_learning_facial_recognition_model": "Kasvojen tunnistusmalli", - "machine_learning_facial_recognition_model_description": "Mallit ovat listattuna kokonsa mukaan laskevaan järjestykseen. Suuremmat mallit ovat hitaampia ja käyttävät enemmän muistia, mutta tuottavat parempia tuloksia. Huomaa että sinun pitää ajaa kasvojen havaitseminen uudelleen kaikille kuville kun vaihdat mallia.", - "machine_learning_facial_recognition_setting": "Ota käyttöön kasvojen tunnistus", - "machine_learning_facial_recognition_setting_description": "Jos ei käytössä, kuvia ei käsitellä kasvojen tunnistusta varten, eikä \"Tutki\" -sivun \"Ihmiset\" osiota täytetä.", - "machine_learning_max_detection_distance": "Tunnistamisen enimmäiseroavaisuus", - "machine_learning_max_detection_distance_description": "Kahden kuvan suurin eroavaisuus, milloin ne vielä määritellään kaksoiskappaleiksi, välillä 0.001-0.1. Korkeampi arvo havaitsee enemmän kaksoiskappaleita, mutta voi tuottaa virheellisiä havaintoja.", - "machine_learning_max_recognition_distance": "Suurin kasvojen eroavaisuus", - "machine_learning_max_recognition_distance_description": "Kahden kasvon suurin eroavaisuus, milloin ne vielä mielletään samaksi henkilöksi, välillä 0-2. Arvoa alentamalla voidaan ehkäistä kahden saman näköisen henkilön mieltäminen samaksi henkilöksi, kun taas korottamalla voidaan ehkäistä saman henkilön mieltäminen kahdeksi erilliseksi henkilöksi. Huomaa että on helpompaa yhdistää kaksi, kuin erottaa, joten suosi mahdollisimman matalaa arvoa.", - "machine_learning_min_detection_score": "Tunnistuksen vähimmäistulos", - "machine_learning_min_detection_score_description": "Pienin kasvojen tunnistamisessa saatu vahvuusarvo välillä 0-1. Matalammalla arvolla havaitaan enemmän kasvoja, mutta voi lisätä virhearvioiden määrää.", - "machine_learning_min_recognized_faces": "Tunnistettujen kasvojen vähimmäismäärä", - "machine_learning_min_recognized_faces_description": "Luotavan käyttäjän kasvojen vähimmäismäärä. Arvoa nostamalla kasvojentunnistuksen tarkkuus paranee, mutta todennäköisyys sille, että kasvoja ei osata yhdistää henkilöön kasvaa.", - "machine_learning_ocr": "Tekstintunnistus (OCR)", - "machine_learning_ocr_description": "Käytä koneoppimista tekstin tunnistamiseen kuvista", - "machine_learning_ocr_enabled": "Aktivoi OCR", - "machine_learning_ocr_enabled_description": "Jos asetus on pois päältä, kuvia ei prosessoida tekstin tunnistamiseksi.", - "machine_learning_ocr_max_resolution": "Maksimiresoluutio", - "machine_learning_ocr_max_resolution_description": "Tätä suuremmat esikatselukuvat tullaan pienentämään samassa kuvasuhteessa. Suuremmat arvot ovat tarkempia, mutta kestävät pidempään prosessoida ja käyttävät enemmän muistia.", - "machine_learning_ocr_min_detection_score": "Tunnistuksen vähimmäispistemäärä", - "machine_learning_ocr_min_detection_score_description": "Tekstin tunnistuksen vähimmäisluottamusarvo (0–1). Pienemmät arvot tunnistavat enemmän tekstiä, mutta voivat johtaa virheellisiin osumiin.", - "machine_learning_ocr_min_recognition_score": "Pienin tunnistuksen pistemäärä", - "machine_learning_ocr_min_score_recognition_description": "Pienin arvo tekstin tunnistuksen varmuudelle välillä 0-1. Pienemmät arvot tunnistavat enemmän tekstiä, mutta saattavat johtaa useampaan väärään positiiviseen.", - "machine_learning_ocr_model": "OCR-malli", - "machine_learning_ocr_model_description": "Palvelinmallit ovat tarkempia kuin mobiilimallit, mutta prosessointi kestää pidempään ja käyttää enemmän muistia.", - "machine_learning_settings": "Koneoppimisen asetukset", - "machine_learning_settings_description": "Koneoppimisen ominaisuudet ja asetukset", - "machine_learning_smart_search": "Älykäs etsintä", - "machine_learning_smart_search_description": "Etsi kuvia merkityksellisemmin käyttäen CLIP-upotuksia", - "machine_learning_smart_search_enabled": "Ota käyttöön älykäs haku", - "machine_learning_smart_search_enabled_description": "Jos ei käytössä, kuvia ei koodata älykkäälle etsinnälle.", - "machine_learning_url_description": "Koneoppimispalvelimen URL-osoite. Jos lisätään useampi kuin yksi URL-osoite, kutakin osoitetta kohden yritetään kerran, kunnes yksi niistä vastaa. Yritykset tehdään järjestyksessä ensimmäisestä viimeiseen. Palvelimet, jotka eivät vastaa, ohitetaan tilapäisesti, kunnes ne ovat taas tavoitettavissa.", - "maintenance_delete_backup": "Poista varmuuskopio", - "maintenance_delete_backup_description": "Tämä tiedosto poistetaan pysyvästi.", - "maintenance_delete_error": "Varmuuskopion poistaminen epäonnistui.", - "maintenance_restore_backup": "Palauta varmuuskopio", - "maintenance_restore_backup_description": "Immich tyhjennetään ja palautetaan valitusta varmuuskopiosta. Ennen jatkamista luodaan varmuuskopio.", - "maintenance_restore_backup_different_version": "Tämä varmuuskopio luotiin Immichin eri versiolla!", - "maintenance_restore_backup_unknown_version": "Varmuuskopion versiota ei voitu määrittää.", - "maintenance_restore_database_backup": "Palauta tietokannan varmuuskopio", - "maintenance_restore_database_backup_description": "Palaa takaisin tietokannan aiempaan tilaan käyttäen varmuuskopiotiedostoa", - "maintenance_settings": "Ylläpito", - "maintenance_settings_description": "Laita Immich ylläpitotilaan.", - "maintenance_start": "Vaihda ylläpitotilaan", - "maintenance_start_error": "Ylläpitotilan käynnistys epäonnistui.", - "maintenance_upload_backup": "Lähetä tietokannan varmuuskopiotiedosto", - "maintenance_upload_backup_error": "Varmuuskopiota ei voitu lähettää, onhan se .sql-/.sql.gz-tiedosto?", - "manage_concurrency": "Hallitse yhtäaikaisia toimintoja", - "manage_concurrency_description": "Mene töiden sivulle muuttamaan töiden yhtäaikaisuutta", - "manage_log_settings": "Hallitse lokien asetuksia", - "map_dark_style": "Tumma teema", - "map_enable_description": "Ota käyttöön karttatoiminnot", - "map_gps_settings": "Kartta- ja GPS-asetukset", - "map_gps_settings_description": "Hallitse kartan ja GPS:n (käänteisen geokoodauksen) asetuksia", - "map_implications": "Karttaominaisuus käyttää ulkoista karttapalvelua (tiles.immich.cloud)", - "map_light_style": "Vaalea teema", - "map_manage_reverse_geocoding_settings": "Hallitse käänteisen geokoodauksen asetuksia", - "map_reverse_geocoding": "Käänteinen Geokoodaus", - "map_reverse_geocoding_enable_description": "Ota käyttöön osoitteiden poiminta karttakoordinaateista", - "map_reverse_geocoding_settings": "Käänteisen geokoodauksen asetukset", - "map_settings": "Kartta", - "map_settings_description": "Hallitse kartan asetuksia", - "map_style_description": "style.json-karttateeman URL", - "memory_cleanup_job": "Muistin siivous", - "memory_generate_job": "Muistin generointi", - "metadata_extraction_job": "Kerää metadata", - "metadata_extraction_job_description": "Poimi metatiedot aineistoista, kuten GPS, kasvot ja resoluutio", - "metadata_faces_import_setting": "Ota käyttöön kasvojen tuonti", - "metadata_faces_import_setting_description": "Tuo kasvot kuvan EXIF- ja kylkiäistiedostoista", - "metadata_settings": "Metatietoasetukset", - "metadata_settings_description": "Hallitse metatietoja", - "migration_job": "Migraatio", - "migration_job_description": "Migratoi aineiston pikkukuvat ja kasvot uusimpaan kansiorakenteeseen", - "nightly_tasks_cluster_faces_setting_description": "Aja kasvojen tunnistus uusiin tunnistettuihin kasvoihin", - "nightly_tasks_cluster_new_faces_setting": "Kokoa uudet kasvot", - "nightly_tasks_database_cleanup_setting": "Tietokannan puhdistuksen tehtävät", - "nightly_tasks_database_cleanup_setting_description": "Siivoa vanhentunut data tietokannasta", - "nightly_tasks_generate_memories_setting": "Luo muistoja", - "nightly_tasks_generate_memories_setting_description": "Luo kohteista uusia muistoja", - "nightly_tasks_missing_thumbnails_setting": "Luo puuttuvat pikkukuvat", - "nightly_tasks_missing_thumbnails_setting_description": "Laita ilman pikkukuvia olevat kohteet jonoon pikkukuvien luontia varten", - "nightly_tasks_settings": "Yöllisten tehtävien asetukset", - "nightly_tasks_settings_description": "Hallitse yöllisiä tehtäviä", - "nightly_tasks_start_time_setting": "Aloitusaika", - "nightly_tasks_start_time_setting_description": "Aika jolloin palvelin aloittaa yöllisten tehtävien ajon", - "nightly_tasks_sync_quota_usage_setting": "Synkronointikiintiön käyttö", - "nightly_tasks_sync_quota_usage_setting_description": "Päivitä käyttäjän tallennustilan kiintiö nykyisen käytön mukaan", - "no_paths_added": "Polkuja ei asetettu", - "no_pattern_added": "Kaavoja ei lisättynä", - "note_apply_storage_label_previous_assets": "Huom: Asettaaksesi nimikkeen aiemmin ladatulle aineistolle, aja", - "note_cannot_be_changed_later": "Huom: Tätä ei voi enää myöhemmin vaihtaa!", - "notification_email_from_address": "Lähettäjän osoite", - "notification_email_from_address_description": "Lähettäjän sähköpostiosoite. Esimerkiksi \"Immich-kuvapalvelin \". Varmista, että käytetystä osoitteesta on lupa lähettää sähköposteja.", - "notification_email_host_description": "Sähköpostipalvelin (esim. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Älä huomioi varmennevirheitä", - "notification_email_ignore_certificate_errors_description": "Älä huomioi TLS-varmenteiden validointivirheitä (ei suositeltu)", - "notification_email_password_description": "Sähköpostipalvelimen salasana", - "notification_email_port_description": "Sähköpostipalvelimen portti (esim. 25, 465, tai 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Käytä SMTPS:ää (SMTP over TLS)", - "notification_email_sent_test_email_button": "Lähetä testaussähköposti ja tallenna", - "notification_email_setting_description": "Sähköposti-ilmoitusten asetukset", - "notification_email_test_email": "Lähetä testisähköposti", - "notification_email_test_email_failed": "Testaussähköpostin lähettäminen ei onnistunut. Tarkista arvot", - "notification_email_test_email_sent": "Testisähköposti on lähetetty osoitteeseen {email}. Tarkistathan saapuneet sähköpostisi.", - "notification_email_username_description": "Sähköpostipalvelimen käyttäjätunnus", - "notification_enable_email_notifications": "Ota käyttöön sähköposti-ilmoitukset", - "notification_settings": "Ilmoitusasetukset", - "notification_settings_description": "Hallitse ilmoitusasetuksia, myös sähköpostin", - "oauth_auto_launch": "Automaattinen käynnistys", - "oauth_auto_launch_description": "Aloita OAuth-kirjautumisvuo heti kun saavutaan kirjautumissivulle", - "oauth_auto_register": "Automaattinen rekisteröinti", - "oauth_auto_register_description": "Rekisteröi uudet OAuth:lla kirjautuvat käyttäjät automaattisesti", - "oauth_button_text": "Painikkeen teksti", - "oauth_client_secret_description": "Vaaditaan, jos OAuth-palveluntarjoaja ei tue PKCE:tä (Proof Key for Code Exchange)", - "oauth_enable_description": "Kirjaudu käyttäen OAuthia", - "oauth_mobile_redirect_uri": "Mobiilin uudelleenohjaus-URI", - "oauth_mobile_redirect_uri_override": "Ohita mobiilin uudelleenohjaus-URI", - "oauth_mobile_redirect_uri_override_description": "Ota käyttöön kun OAuth-tarjoaja ei salli mobiili-URI:a, kuten ''{callback}''", - "oauth_role_claim": "Roolin vaatimus", - "oauth_role_claim_description": "Salli pääkäyttäjän pääsyoikeus automaattisesti tämän vaatimuksen perusteella. Vaatimus voi sisältää, joko 'käyttäjän' tai 'pääkäyttäjän'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Hallitse OAuth-kirjautumisen asetuksia", - "oauth_settings_more_details": "Saadaksesi lisätietoja tästä toiminnosta, katso dokumentaatio.", - "oauth_storage_label_claim": "Tallennustilan nimikkeen valtuutusväittämä (claim)", - "oauth_storage_label_claim_description": "Määriä käyttäjän tallennustilan nimike tämän väittämän arvoksi automaattisesti.", - "oauth_storage_quota_claim": "Tallennustilan kiintiön väittämä (claim)", - "oauth_storage_quota_claim_description": "Aseta automaattisesti käyttäjien tallennustilan määrä tähän arvoon.", - "oauth_storage_quota_default": "Tallennustilan oletuskiintiö (Gt)", - "oauth_storage_quota_default_description": "Käytettävä kiintiön määrä gigatavuissa, kun väittämää ei ole annettu.", - "oauth_timeout": "Pyynnön aikakatkaisu", - "oauth_timeout_description": "Pyyntöjen aikakatkaisu millisekunteina", - "ocr_job_description": "Käytä koneoppimista tunnistamaan tekstiä kuvista", - "password_enable_description": "Kirjaudu käyttäen sähköpostiosoitetta ja salasanaa", - "password_settings": "Kirjaudu salasanalla", - "password_settings_description": "Hallitse salasanakirjautumisen asetuksia", - "paths_validated_successfully": "Kaikki polut validoitu", - "person_cleanup_job": "Henkilöpuhdistus", - "queue_details": "Jonon tiedot", - "queues": "Tehtäväjonot", - "queues_page_description": "Tehtäväjonojen ylläpitosivu", - "quota_size_gib": "Kiintiön koko (Gt)", - "refreshing_all_libraries": "Virkistetään kaikki kirjastot", - "registration": "Pääkäyttäjän rekisteröinti", - "registration_description": "Pääkäyttäjänä olet vastuussa järjestelmän hallinnallisista tehtävistä ja uusien käyttäjien luomisesta.", - "remove_failed_jobs": "Poista epäonnistuneet tehtävät", - "require_password_change_on_login": "Vaadi käyttäjää vaihtamaan salasana ensimmäisellä kirjautumiskerralla", - "reset_settings_to_default": "Nollaa asetukset oletuksille", - "reset_settings_to_recent_saved": "Palauta aiemmin tallennetut asetukset", - "scanning_library": "Kirjastoa skannataan", - "search_jobs": "Etsi tehtäviä…", - "send_welcome_email": "Lähetä tervetuloviesti", - "server_external_domain_settings": "Ulkoinen osoite", - "server_external_domain_settings_description": "Osoite julkisille linkeille, http(s):// mukaan lukien", - "server_public_users": "Julkiset käyttäjät", - "server_public_users_description": "Kaikki käyttäjät (nimi ja sähköpostiosoite) luetellaan, kun käyttäjä lisätään jaettuihin albumeihin. Kun toiminto on poistettu käytöstä, käyttäjäluettelo on vain pääkäyttäjien käytettävissä.", - "server_settings": "Palvelimen asetukset", - "server_settings_description": "Ylläpidä palvelimen asetuksia", - "server_stats_page_description": "Ylläpitäjän palvelimen tilastosivu", - "server_welcome_message": "Tervetuloviesti", - "server_welcome_message_description": "Viesti, joka näytetään kirjautumissivulla.", - "settings_page_description": "Ylläpitäjän asetuksien sivu", - "sidecar_job": "Kylkiäismetadata", - "sidecar_job_description": "Havaitse tai synkronoi tiedostojen kylkiäismetadatat", - "slideshow_duration_description": "Montako sekuntia kuvaa näytetään", - "smart_search_job_description": "Aja aineiston koneoppiminen, jolla tuet älykästä hakua", - "storage_template_date_time_description": "Kohteen luontipäivän aikaleimaa käytetään datetime tietoa varten", - "storage_template_date_time_sample": "Esimerkki päivämäärä {date}", - "storage_template_enable_description": "Ota käyttöön tallennustilan mallit", - "storage_template_hash_verification_enabled": "Tarkistussumman varmennus käytössä", - "storage_template_hash_verification_enabled_description": "Ottaa käyttöön tarkistussummien laskennan. Älä poista käytöstä, ellet ole aivan varma seurauksista", - "storage_template_migration": "Tallennustilan mallien migraatio", - "storage_template_migration_description": "Käytä nykyistä {template}a aikaisemmin lähetettyihin kohteisiin", - "storage_template_migration_info": "Tallennusmalli muuntaa kaikki tiedostopäätteet pieniksi kirjaimiksi. Mallipohjan muutokset koskevat vain uusia resursseja. Jos haluat käyttää mallipohjaa takautuvasti aiemmin ladattuihin resursseihin, suorita {job}.", - "storage_template_migration_job": "Tallennustilan mallin muutostyö", - "storage_template_more_details": "Saadaksesi lisätietoa tästä ominaisuudesta, katso Tallennustilan Mallit sekä mihin se vaikuttaa", - "storage_template_onboarding_description_v2": "Päälle kytkettynä toiminto järjestelee tiedostot automaattisesti käyttäjän määrittämän mallin mukaisesti. Lisätietoja dokumentaatiosta..", - "storage_template_path_length": "Arvioitu tiedostopolun pituusrajoitus: {length, number}/{limit, number}", - "storage_template_settings": "Tallennustilan malli", - "storage_template_settings_description": "Hallitse palvelimelle ladatun aineiston kansiorakennetta ja tiedostonimiä", - "storage_template_user_label": "{label} on käyttäjän Tallennustilan Tunniste", - "system_settings": "Järjestelmäasetukset", - "tag_cleanup_job": "Merkintäpuhdistus", - "template_email_available_tags": "Voit käyttää seuraavia muuttujia mallissasi: {tags}", - "template_email_if_empty": "Jos malli on tyhjä, käytetään oletussähköpostia.", - "template_email_invite_album": "Albumikutsun malli", - "template_email_preview": "Esikatselu", - "template_email_settings": "Sähköpostimalli", - "template_email_update_album": "Albumipäivityksen malli", - "template_email_welcome": "Tervetulosähköpostin malli", - "template_settings": "Ilmoitusmallit", - "template_settings_description": "Hallitse yksilöllisten ilmoitusten malleja", - "theme_custom_css_settings": "Mukautettu CSS", - "theme_custom_css_settings_description": "Mukauta Immichin ulkoasua CSS:llä.", - "theme_settings": "Teeman asetukset", - "theme_settings_description": "Kustomoi Immichin web-käyttöliittymää", - "thumbnail_generation_job": "Luo pikkukuvat", - "thumbnail_generation_job_description": "Generoi isot, pienet sekä sumeat pikkukuvat jokaisesta aineistosta, kuten myös henkilöistä", - "transcoding_acceleration_api": "Kiihdytysrajapinta", - "transcoding_acceleration_api_description": "Rajapinta, jolla keskustellaan laitteesi kanssa nopeuttaaksemme koodausta. Tämä asetus on paras mahdollinen: Mikäli ongelmia ilmenee, palataan käyttämään ohjelmistopohjaista koodausta. VP9 voi toimia tai ei, riippuen laitteistosi kokoonpanosta.", - "transcoding_acceleration_nvenc": "NVENC (vaatii NVIDIA:n grafiikkasuorittimen)", - "transcoding_acceleration_qsv": "Quick Sync (Vaatii vähintään gen7 Intel prosessorin)", - "transcoding_acceleration_rkmpp": "RKMPP (vain Rockchip SOCt)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Sallitut äänikoodekit", - "transcoding_accepted_audio_codecs_description": "Valitse, mitä äänikoodekkeja ei tarvitse muuntaa. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", - "transcoding_accepted_containers": "Sallitut säiliömuodot", - "transcoding_accepted_containers_description": "Valitse, mitä säiliömuotoja ei tarvitse muuntaa MP4-muotoon. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", - "transcoding_accepted_video_codecs": "Sallitut videokoodekit", - "transcoding_accepted_video_codecs_description": "Valitse, mitä videokoodekkeja ei tarvitse muuntaa. Käytetään vain tiettyjen koodauskäytäntöjen kanssa.", - "transcoding_advanced_options_description": "Asetukset, joita useimpien käyttäjien ei tulisi muuttaa", - "transcoding_audio_codec": "Äänikoodekki", - "transcoding_audio_codec_description": "Opus on paras laadultaan, mutta ei välttämättä ole yhteensopiva vanhempien laitteiden tai sovellusten kanssa.", - "transcoding_bitrate_description": "Videot, jotka ylittävät enimmäisbittinopeuden tai eivät ole hyväksytyssä muodossa", - "transcoding_codecs_learn_more": "Oppiaksesi lisää käytetystä terminologiasta, tutustu FFmpeg-dokumentaatioon: H.264-koodaaja, HEVC-koodaaja ja VP9-koodaaja.", - "transcoding_constant_quality_mode": "Tasaisen laadun tyyppi", - "transcoding_constant_quality_mode_description": "ICQ on parempi kuin CQP, mutta jotkut laitteistokiihdyttimet eivät tue sitä. Tätä asetusta käytetään oletuksena laatuun pohjautuvissa muunnoksissa, paitsi NVENC mikä ei tue ICQ:ta.", - "transcoding_constant_rate_factor": "Vakionopeustekijä", - "transcoding_constant_rate_factor_description": "Videon laatu. Yleisimmät arvot ovat 23 H.264:lle, 28 HEVC:lle, 31 VP9:lle ja 35 AV1:lle. Matalampi arvo on parempi, mutta tekee isompia tiedostoja.", - "transcoding_disabled_description": "Älä muunna videoita. Voi joissakin päätelaitteissa aiheuttaa videotoiston toimimattomuutta", - "transcoding_encoding_options": "Enkoodausasetukset", - "transcoding_encoding_options_description": "Aseta koodekit, tarkkuus, laatu ja muut asetukset enkoodatuille videoille", - "transcoding_hardware_acceleration": "Laitteistokiihdytys", - "transcoding_hardware_acceleration_description": "Kokeellinen: Mahdollistaa nopeamman transkoodauksen, mutta saattaa alentaa laatua samalla tiedonsiirtonopeudella", - "transcoding_hardware_decoding": "Laitteiston dekoodaus", - "transcoding_hardware_decoding_setting_description": "Ottaa käyttöön end-to-end kiihdytyksen pelkän muuntamisen sijasta. Ei välttämättä toimi kaikissa videoissa.", - "transcoding_max_b_frames": "B-kehysten enimmäismäärä", - "transcoding_max_b_frames_description": "Korkeampi arvo parantaa pakkausta, mutta hidastaa enkoodausta. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa. 0 poistaa B-kehykset käytöstä, -1 määrittää arvon automaattisesti.", - "transcoding_max_bitrate": "Suurin bittinopeus", - "transcoding_max_bitrate_description": "Suurimman sallitun bittinopeuden asettaminen tekee tiedostojen koosta ennustettavampaa vaikka laatu voi hieman heiketä. 720p videossa tyypilliset arvot ovat 2600 kbit/s VP9:lle ja HEVC:lle, tai 4500 kbit/s H.254:lle. Jos 0, ei käytössä. Jos yksikköä ei ole annettu, oletus on k (kbit/s). Eli 5000, 5000k ja 5M ovat yhtä suuria.", - "transcoding_max_keyframe_interval": "Suurin avainkehysten väli", - "transcoding_max_keyframe_interval_description": "Asettaa avainkehysten välin maksimiarvon. Alempi arvo huonontaa pakkauksen tehoa, mutta parantaa hakuaikoja ja voi parantaa laatua nopealiikkeisissä kohtauksissa. 0 asettaa arvon automaattisesti.", - "transcoding_optimal_description": "Videot, joiden resoluutio on korkeampi kuin kohteen, tai ei hyväksytyssä formaatissa", - "transcoding_policy": "Transkoodauskäytäntö", - "transcoding_policy_description": "Aseta milloin video transkoodataan", - "transcoding_preferred_hardware_device": "Ensisijainen laite", - "transcoding_preferred_hardware_device_description": "On voimassa vain VAAPI- ja QSV-määritteille. Asettaa laitteistokoodauksessa käytetyn DRI-noodin.", - "transcoding_preset_preset": "Esiasetus (-asetus)", - "transcoding_preset_preset_description": "Pakkausnopeus. Hitaampi tuottaa pienempiä tiedostoja ja parantaa laatua, kun kohdistetaan tiettyyn bittinopeuteen. VP9 ei huomioi korkeampaa kuin 'faster'.", - "transcoding_reference_frames": "Kehysviitteet", - "transcoding_reference_frames_description": "Viittaavien kehysten määrä kun tiettyä kehystä pakataan. Korkeampi arvo parantaa pakkausta mutta hidastaa enkoodausta. 0 määrittää arvon automaattisesti.", - "transcoding_required_description": "Vain videoille, jotka eivät ole hyväksytyssä muodossa", - "transcoding_settings": "Videoiden transkoodausasetukset", - "transcoding_settings_description": "Hallitse, mitkä videot transkoodataan ja miten niitä käsitellään", - "transcoding_target_resolution": "Kohderesoluutio", - "transcoding_target_resolution_description": "Korkeampi resoluutio on tarkempi, mutta kestää kauemmin enkoodata, vie enemmän tilaa ja voi hidastaa sovelluksen responsiivisuutta.", - "transcoding_temporal_aq": "Väliaikainen AQ", - "transcoding_temporal_aq_description": "Vaikuttaa vain NVENC:lle. Aikaperusteinen adaptiivinen kvantisointi parantaa laatua kohtauksissa, joissa on paljon yksityiskohtia ja vähän liikettä. Ei välttämättä ole yhteensopiva vanhempien laitteiden kanssa.", - "transcoding_threads": "Säikeet", - "transcoding_threads_description": "Korkeampi arvo nopeuttaa enkoodausta, mutta vie tilaa palvelimen muilta tehtäviltä. Tämä arvo ei tulisi olla suurempi mitä suorittimen ytimien määrä. Suurin mahdollinen käyttö, mikäli arvo on 0.", - "transcoding_tone_mapping": "Sävykartoitus", - "transcoding_tone_mapping_description": "Pyrkii säilömään HDR-kuvien ulkonäön, kun muunnetaan peruskuvaksi. Jokaisella algoritmilla on omat heikkoutensa värien, yksityiskohtien tai kirkkauksien kesken. Hable säilöö yksityiskohdat, Mobius värit ja Reinhard kirkkaudet.", - "transcoding_transcode_policy": "Transkoodauskäytäntö", - "transcoding_transcode_policy_description": "Käytäntö, miten video tulisi transkoodata. HDR-videot transkoodataan aina, paitsi jos transkoodaus on poistettu käytöstä.", - "transcoding_two_pass_encoding": "Two-pass enkoodaus", - "transcoding_two_pass_encoding_setting_description": "Transkoodaa kahdessa vaiheessa tuottaaksesi paremmin koodattuja videoita. Kun maksimibittinopeus on käytössä (vaaditaan H.264- ja HEVC-koodaukselle), tämä tila käyttää bittinopeusaluetta, joka perustuu maksimibittinopeuteen ja ohittaa CRF. VP9 osalta CRF:ää voidaan käyttää, jos maksimibittinopeus on poistettu käytöstä.", - "transcoding_video_codec": "Videokoodekki", - "transcoding_video_codec_description": "VP9 on tehokkain ja web-yhteensopiva, mutta muuntaminen kestää kauemmin. HEVC suoriutuu yhtäläisesti, mutta ei ole ihan yhtä yhteensopiva. H.264 on hyvin yhteensopiva ja nopea muuntaa, mutta tuottaa paljon suurempia tiedostoja. AV1 on kaikkein tehokkain koodekki, mutta vanhemmat laitteet eivät sitä tue.", - "trash_enabled_description": "Ota käyttöön roskakori", - "trash_number_of_days": "Päivien lukumäärä", - "trash_number_of_days_description": "Kuinka monta päivää aineistoja pidetään roskakorissa ennen pysyvää poistamista", - "trash_settings": "Roskakorin asetukset", - "trash_settings_description": "Hallitse roskakoriasetuksia", - "unlink_all_oauth_accounts": "Irrota kaikki OAuth-tilit", - "unlink_all_oauth_accounts_description": "Muista irrottaa kaikki OAuth-tilit ennen uuteen palveluntarjoajaan siirtymistä.", - "unlink_all_oauth_accounts_prompt": "Haluatko varmasti irrottaa kaikki OAuth-tilit? Tämä nollaa OAuth-tunnistautumisen kaikille käyttäjille eikä sitä voi perua.", - "user_cleanup_job": "Käyttäjien puhdistus", - "user_delete_delay": "Käyttäjän {user} tili ja aineistot aikataulutetaan poistettavaksi ajan kuluttua: {delay, plural, one {# day} other {# days}}.", - "user_delete_delay_settings": "Poiston viive", - "user_delete_delay_settings_description": "Kuinka monta päivää poistamisen jälkeen käyttäjä ja hänen aineistonsa poistetaan pysyvästi. Joka keskiyö käydään läpi poistettavaksi merkityt käyttäjät. Tämä muutos astuu voimaan seuraavalla ajokerralla.", - "user_delete_immediately": "{user}:n tili ja sen kohteet on ajastettu poistettavaksi heti.", - "user_delete_immediately_checkbox": "Aseta tili ja sen kohteet jonoon välitöntä poistoa varten", - "user_details": "Käyttäjätiedot", - "user_management": "Käyttäjien hallinta", - "user_password_has_been_reset": "Käyttäjän salasana on nollattu:", - "user_password_reset_description": "Anna väliaikainen salasana ja ohjeista käyttäjää vaihtamaan se seuraavan kirjautumisen yhteydessä.", - "user_restore_description": "{user}:n tili palautetaan.", - "user_restore_scheduled_removal": "Palauta käyttäjä - Aikataulutettu poisto tapahtuu {date, date, long}", - "user_settings": "Käyttäjäasetukset", - "user_settings_description": "Hallitse käyttäjäasetuksia", - "user_successfully_removed": "Käyttäjä {email} on poistettu onnistuneesti.", - "users_page_description": "Ylläpitäjän käyttäjien lista", - "version_check_enabled_description": "Ota käyttöön versiotarkastus", - "version_check_implications": "Versiotarkistus vaatii säännöllisen yhteyden github.comiin", - "version_check_settings": "Versiotarkistus", - "version_check_settings_description": "Ota käyttöön ilmoitukset, kun uusi versio on saatavilla", - "video_conversion_job": "Transkoodaa videot", - "video_conversion_job_description": "Transkoodaa videot mahdollistaaksesi selainten ja laitteiden laajemman tuen" - }, - "admin_email": "Ylläpitäjän sähköpostiosoite", - "admin_password": "Ylläpitäjän salasana", - "administration": "Ylläpito", - "advanced": "Edistyneet", - "advanced_settings_clear_image_cache": "Tyhjennä kuvien välimuisti", - "advanced_settings_clear_image_cache_error": "Kuvien välimuistin tyhjentäminen epäonnistui", - "advanced_settings_clear_image_cache_success": "Tyhjennettiin onnistuneesti {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Käytä tätä vaihtoehtoa suodattaaksesi mediaa synkronoinnin aikana vaihtoehtoisten kriteerien perusteella. Kokeile tätä vain, jos sovelluksessa on ongelmia kaikkien albumien tunnistamisessa.", - "advanced_settings_enable_alternate_media_filter_title": "[KOKEELLINEN] Käytä vaihtoehtoisen laitteen albumin synkronointisuodatinta", - "advanced_settings_log_level_title": "Kirjaustaso: {level}", - "advanced_settings_prefer_remote_subtitle": "Jotkut laitteet ovat erittäin hitaita lataamaan esikatselukuvia paikallisista kohteista. Aktivoi tämä asetus käyttääksesi etäkuvia.", - "advanced_settings_prefer_remote_title": "Suosi etäkuvia", - "advanced_settings_proxy_headers_subtitle": "Määritä välityspalvelimen otsikot(proxy headers), jotka Immichin tulisi lähettää jokaisen verkkopyynnön mukana", - "advanced_settings_proxy_headers_title": "Mukautetut välityspalvelimen otsikot [KOKEELLINEN]", - "advanced_settings_readonly_mode_subtitle": "Aktivoi vain luku -tilan, jolloin valokuvia voi ainoastaan selata. Toiminnot kuten useiden kuvien valitseminen, jakaminen, siirtäminen toistolaitteelle ja poistaminen ovat pois käytöstä. Laita vain luku -tila päälle tai pois päältä päävalikon käyttäjäkuvakkeesta", - "advanced_settings_readonly_mode_title": "Vain luku -tila", - "advanced_settings_self_signed_ssl_subtitle": "Ohita SSL sertifikaattivarmennus palvelimen päätepisteellä. Vaaditaan self-signed -sertifikaateissa.", - "advanced_settings_self_signed_ssl_title": "Salli self-signed SSL -sertifikaatit [KOKEELLINEN]", - "advanced_settings_sync_remote_deletions_subtitle": "Poista tai palauta kohde automaattisesti tällä laitteella, kun kyseinen toiminto suoritetaan verkossa", - "advanced_settings_sync_remote_deletions_title": "Synkronoi etäpoistot [KOKEELLINEN]", - "advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset", - "advanced_settings_troubleshooting_subtitle": "Ota vianetsinnän lisäominaisuudet käyttöön", - "advanced_settings_troubleshooting_title": "Vianetsintä", - "age_months": "Ikä {months, plural, one {# kuukausi} other {# kuukautta}}", - "age_year_months": "Ikä 1 vuosi, {months, plural, one {# kuukausi} other {# kuukautta}}", - "age_years": "{years, plural, other {Ikä #v}}", - "album": "Albumi", - "album_added": "Albumi lisätty", - "album_added_notification_setting_description": "Saa sähköpostia kun sinut lisätään jaettuun albumiin", - "album_cover_updated": "Albumin kansikuva päivitetty", - "album_delete_confirmation": "Haluatko varmasti poistaa albumin {album}?", - "album_delete_confirmation_description": "Jos albumi on jaettu, muut eivät pääse siihen enää.", - "album_deleted": "Albumi poistettu", - "album_info_card_backup_album_excluded": "JÄTETTY POIS", - "album_info_card_backup_album_included": "SISÄLLYTETTY", - "album_info_updated": "Albumin tiedot päivitetty", - "album_leave": "Poistu albumista?", - "album_leave_confirmation": "Haluatko varmasti poistua albumista {album}?", - "album_name": "Albumin nimi", - "album_options": "Albumin asetukset", - "album_remove_user": "Poista käyttäjä?", - "album_remove_user_confirmation": "Oletko varma että haluat poistaa {user}?", - "album_search_not_found": "Haullasi ei löytynyt yhtään albumia", - "album_selected": "Albumi valittu", - "album_share_no_users": "Näyttää että olet jakanut tämän albumin kaikkien kanssa, tai sinulla ei ole käyttäjiä joille jakaa.", - "album_summary": "Albumi tiivistelmä", - "album_updated": "Albumi päivitetty", - "album_updated_setting_description": "Saa sähköpostia kun jaetussa albumissa on uutta sisältöä", - "album_user_left": "Poistuttiin albumista {album}", - "album_user_removed": "{user} poistettu", - "album_viewer_appbar_delete_confirm": "Haluatko varmasti poistaa tämän albumin tililtäsi?", - "album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui", - "album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui", - "album_viewer_appbar_share_err_remove": "Ongelmia kohteiden poistamisessa albumista", - "album_viewer_appbar_share_err_title": "Albumin nimen muuttaminen epäonnistui", - "album_viewer_appbar_share_leave": "Poistu albumista", - "album_viewer_appbar_share_to": "Jaa", - "album_viewer_page_share_add_users": "Lisää käyttäjiä", - "album_with_link_access": "Anna kenen tahansa nähdä linkin kautta tämän albumin valokuvat ja henkilöt.", - "albums": "Albumit", - "albums_count": "{count, plural, one {{count, number} albumi} other {{count, number} albumia}}", - "albums_default_sort_order": "Albumin oletuslajittelujärjestys", - "albums_default_sort_order_description": "Kohteiden ensisijainen lajittelujärjestys uusia albumeja luotaessa.", - "albums_feature_description": "Kokoelma kohteita, jotka voidaan jakaa muille käyttäjille.", - "albums_on_device_count": "({count}) albumia laitteella", - "albums_selected": "{count, plural, one {# albumi valittu} other {# albumia valittu}}", - "all": "Kaikki", - "all_albums": "Kaikki albumit", - "all_people": "Kaikki henkilöt", - "all_photos": "Kaikki kuvat", - "all_videos": "Kaikki videot", - "allow_dark_mode": "Salli tumma tila", - "allow_edits": "Salli muutokset", - "allow_public_user_to_download": "Salli julkisten käyttäjien ladata tiedostoja", - "allow_public_user_to_upload": "Salli julkisten käyttäjien lähettää tiedostoja", - "allowed": "Sallittu", - "alt_text_qr_code": "QR-koodi", - "always_keep": "Säilytä aina", - "always_keep_photos_hint": "Tilan vapauttaminen säilyttää kaikki kuvat tällä laitteella.", - "always_keep_videos_hint": "Tilan vapauttaminen säilyttää kaikki videot tällä laitteella.", - "anti_clockwise": "Vastapäivään", - "api_key": "API-avain", - "api_key_description": "Tämä arvo näytetään vain kerran. Varmista, että olet kopioinut sen ennen kuin suljet ikkunan.", - "api_key_empty": "API-avaimesi ei pitäisi olla tyhjä", - "api_keys": "API-avaimet", - "app_architecture_variant": "Variantti (Arkkitehtuuri)", - "app_bar_signout_dialog_content": "Haluatko varmasti kirjautua ulos?", - "app_bar_signout_dialog_ok": "Kyllä", - "app_bar_signout_dialog_title": "Kirjaudu ulos", - "app_download_links": "Sovelluksen latauslinkit", - "app_settings": "Sovellusasetukset", - "app_stores": "Sovelluskaupat", - "app_update_available": "Sovellukseen on saatavilla päivitys", - "appears_in": "Esiintyy albumeissa", - "apply_count": "Aseta {count, number}", - "archive": "Arkisto", - "archive_action_prompt": "{count} lisätty arkistoon", - "archive_or_unarchive_photo": "Arkistoi kuva tai palauta arkistosta", - "archive_page_no_archived_assets": "Arkistoituja kohteita ei löytynyt", - "archive_page_title": "Arkisto ({count})", - "archive_size": "Arkiston koko", - "archive_size_description": "Määritä arkiston koko latauksissa (Gt)", - "archived": "Arkistoitu", - "archived_count": "{count, plural, other {Arkistoitu #}}", - "are_these_the_same_person": "Ovatko he sama henkilö?", - "are_you_sure_to_do_this": "Haluatko varmasti tehdä tämän?", - "array_field_not_fully_supported": "Taulukkokentät vaativat JSON:in manuaalista muokkaamista", - "asset_action_delete_err_read_only": "Vain luku -tilassa olevia kohteita ei voitu poistaa, ohitetaan", - "asset_action_share_err_offline": "Verkottomassa tilassa olevia kohteita ei voitu noutaa, ohitetaan", - "asset_added_to_album": "Lisätty albumiin", - "asset_adding_to_album": "Lisätään albumiin…", - "asset_created": "Kohde luotu", - "asset_description_updated": "Kohteen kuvaus on päivitetty", - "asset_filename_is_offline": "Kohde {filename} on offline-tilassa", - "asset_has_unassigned_faces": "Kohteella on määrittämättömiä kasvoja", - "asset_hashing": "Hajautetaan…", - "asset_list_group_by_sub_title": "Ryhmittele", - "asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma", - "asset_list_layout_settings_group_automatically": "Automaattisesti", - "asset_list_layout_settings_group_by": "Ryhmittele", - "asset_list_layout_settings_group_by_month_day": "Kuukauden ja päivän mukaan", - "asset_list_layout_sub_title": "Asettelu", - "asset_list_settings_subtitle": "Kuvaruudukon asettelu", - "asset_list_settings_title": "Kuvaruudukko", - "asset_offline": "Aineisto offline-tilassa", - "asset_offline_description": "Tätä ulkoista resurssia ei enää löydy levyltä. Ole hyvä ja ota yhteyttä Immich-järjestelmänvalvojaan saadaksesi apua.", - "asset_restored_successfully": "Kohde palautettu onnistuneesti", - "asset_skipped": "Ohitettu", - "asset_skipped_in_trash": "Roskakorissa", - "asset_trashed": "Kohde poistettu", - "asset_troubleshoot": "Sisällön vian paikannus", - "asset_uploaded": "Lähetetty", - "asset_uploading": "Ladataan…", - "asset_viewer_settings_subtitle": "Galleriakatseluohjelman asetusten hallinta", - "asset_viewer_settings_title": "Katselin", - "assets": "Kohteet", - "assets_added_count": "Lisätty {count, plural, one {# kohde} other {# kohdetta}}", - "assets_added_to_album_count": "Albumiin lisätty {count, plural, one {# kohde} other {# kohdetta}}", - "assets_added_to_albums_count": "Lisätty {assetTotal, plural, one {# kohde} other {# kohdetta}} {albumTotal, plural, one {# albumiin} other {# albumiin}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Kohdetta} other {Kohdetta}} ei voida lisätä albumiin", - "assets_cannot_be_added_to_albums": "{count, plural, one {Aineisto} other {Aineistoa}} ei voi lisätä mihinkään albumiin", - "assets_count": "{count, plural, one {# media} other {# mediaa}}", - "assets_deleted_permanently": "{count} kohdetta poistettu pysyvästi", - "assets_deleted_permanently_from_server": "{count} objektia poistettu pysyvästi Immich-palvelimelta", - "assets_downloaded_failed": "{count, plural, one {Ladattu # tiedosto - {error} tiedosto epäonnistui} other {ladattu # tiedostoa - {error} tiedostot epäonnistuivat}}", - "assets_downloaded_successfully": "{count, plural, one {Ladattu # tiedosto onnistuneesti} other {Ladattu # tiedostoa onnistuneesti}}", - "assets_moved_to_trash_count": "Siirretty {count, plural, one {# media} other {# mediaa}} roskakoriin", - "assets_permanently_deleted_count": "{count, plural, one {# media} other {# mediaa}} poistettu pysyvästi", - "assets_removed_count": "{count, plural, one {# media} other {# mediaa}} poistettu", - "assets_removed_permanently_from_device": "{count} kohdetta on poistettu pysyvästi laitteeltasi", - "assets_restore_confirmation": "Haluatko varmasti palauttaa kaikki roskakoriisi siirretyt kohteet? Tätä toimintoa ei voi peruuttaa! Huomaa, että offline-kohteita ei voida palauttaa tällä tavalla.", - "assets_restored_count": "{count, plural, one {# media} other {# mediaa}} palautettu", - "assets_restored_successfully": "{count} kohdetta palautettu onnistuneesti", - "assets_trashed": "{count} kohdetta siirretty roskakoriin", - "assets_trashed_count": "{count, plural, one {# media} other {# mediaa}} siirretty roskakoriin", - "assets_trashed_from_server": "{count} kohdetta siirretty roskakoriin Immich-palvelimelta", - "assets_were_part_of_album_count": "{count, plural, one {Media oli} other {Mediat olivat}} jo albumissa", - "assets_were_part_of_albums_count": "{count, plural, one {Aineisto} other {Aineistoa}} oli jo albumeissa", - "authorized_devices": "Valtuutetut laitteet", - "automatic_endpoint_switching_subtitle": "Yhdistä paikallisesti nimetyn Wi-Fi-yhteyden kautta, kun se on saatavilla, ja käytä vaihtoehtoisia yhteyksiä muualla", - "automatic_endpoint_switching_title": "Automaattinen URL-osoitteen vaihto", - "autoplay_slideshow": "Toista diaesitys automaattisesti", - "back": "Takaisin", - "back_close_deselect": "Palaa, sulje tai poista valinnat", - "background_backup_running_error": "Tausta varmuuskopiointi on aktiivinen, ei voida aloittaa manuaalista varmuuskopiointia", - "background_location_permission": "Taustasijainnin käyttöoikeus", - "background_location_permission_content": "Jotta sovellus voi vaihtaa verkkoa taustalla toimiessaan, Immichillä on *aina* oltava pääsy tarkkaan sijaintiin, jotta se voi lukea Wi-Fi-verkon nimen", - "background_options": "Tausta valinnat", - "backup": "Varmuuskopiointi", - "backup_album_selection_page_albums_device": "Laitteen albumit ({count})", - "backup_album_selection_page_albums_tap": "Napauta sisällyttääksesi, kaksoisnapauta jättääksesi pois", - "backup_album_selection_page_assets_scatter": "Kohteet voivat olla hajaantuneina useisiin albumeihin. Albumeita voidaan sisällyttää varmuuskopiointiin tai jättää siitä pois.", - "backup_album_selection_page_select_albums": "Valitse albumit", - "backup_album_selection_page_selection_info": "Valintatiedot", - "backup_album_selection_page_total_assets": "Ainulaatuisia kohteita yhteensä", - "backup_albums_sync": "Varmuuskopioitujen albumeiden synkronointi", - "backup_all": "Kaikki", - "backup_background_service_backup_failed_message": "Kohteiden varmuuskopiointi epäonnistui. Yritetään uudelleen…", - "backup_background_service_complete_notification": "Kohteiden varmuuskopiointi valmis", - "backup_background_service_connection_failed_message": "Palvelimeen ei saatu yhteyttä. Yritetään uudelleen…", - "backup_background_service_current_upload_notification": "Lähetetään {filename}", - "backup_background_service_default_notification": "Tarkistetaan uusia kohteita…", - "backup_background_service_error_title": "Virhe varmuuskopioinnissa", - "backup_background_service_in_progress_notification": "Varmuuskopioidaan kohteita…", - "backup_background_service_upload_failure_notification": "Lähetys epäonnistui {filename}", - "backup_controller_page_albums": "Varmuuskopioi albumit", - "backup_controller_page_background_app_refresh_disabled_content": "Salli sovelluksen päivittäminen taustalla suorittaaksesi varmuuskopiointia taustalla: Asetukset > Yleiset > Appien päivitys taustalla.", - "backup_controller_page_background_app_refresh_disabled_title": "Sovelluksen päivittäminen taustalla on pois päältä", - "backup_controller_page_background_app_refresh_enable_button_text": "Siirry asetuksiin", - "backup_controller_page_background_battery_info_link": "Näytä minulle miten", - "backup_controller_page_background_battery_info_message": "Kytke pois päältä kaikki Immichin taustatyöskentelyyn liittyvät akun optimoinnit, jotta varmistat taustavarmuuskopioinnin parhaan mahdollisen toiminnan.\n\nKoska tämä on laitekohtaista, tarkista tarvittavat toimet laitevalmistajan ohjeista.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Akun optimointi", - "backup_controller_page_background_charging": "Vain laitteen ollessa kytkettynä laturiin", - "backup_controller_page_background_configure_error": "Taustapalvelun asettaminen epäonnistui", - "backup_controller_page_background_delay": "Viivästytä uusien kohteiden varmuuskopiointia: {duration}", - "backup_controller_page_background_description": "Kytke taustapalvelu päälle varmuuskopioidaksesi uudet kohteet automaattisesti, ilman sovelluksen avaamista", - "backup_controller_page_background_is_off": "Automaattinen varmuuskopiointi taustalla on pois päältä", - "backup_controller_page_background_is_on": "Automaattinen varmuuskopiointi taustalla on päällä", - "backup_controller_page_background_turn_off": "Kytke taustapalvelu pois päältä", - "backup_controller_page_background_turn_on": "Kytke taustapalvelu päälle", - "backup_controller_page_background_wifi": "Vain WiFi-verkossa", - "backup_controller_page_backup": "Varmuuskopiointi", - "backup_controller_page_backup_selected": "Valittu: ", - "backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot", - "backup_controller_page_created": "Luotu: {date}", - "backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle lähettääksesi uudet kohteet palvelimelle automaattisesti.", - "backup_controller_page_excluded": "Poissuljettu: ", - "backup_controller_page_failed": "Epäonnistui ({count})", - "backup_controller_page_filename": "Tiedostonimi: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Varmuuskopioinnin tiedot", - "backup_controller_page_none_selected": "Ei mitään", - "backup_controller_page_remainder": "Jäljellä", - "backup_controller_page_remainder_sub": "Varmuuskopiointia odottavat kuvat ja videot", - "backup_controller_page_server_storage": "Palvelimen tallennustila", - "backup_controller_page_start_backup": "Aloita varmuuskopiointi", - "backup_controller_page_status_off": "Varmuuskopiointi on pois päältä", - "backup_controller_page_status_on": "Varmuuskopiointi on päällä", - "backup_controller_page_storage_format": "{used} / {total} käytetty", - "backup_controller_page_to_backup": "Varmuuskopioitavat albumit", - "backup_controller_page_total_sub": "Kaikki uniikit kuvat ja videot valituista albumeista", - "backup_controller_page_turn_off": "Varmuuskopiointi pois päältä", - "backup_controller_page_turn_on": "Varmuuskopiointi päälle", - "backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot", - "backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna", - "backup_error_sync_failed": "Synkronointi epäonnistui. Varmuuskopion käsittely ei onnistu.", - "backup_info_card_assets": "kohdetta", - "backup_manual_cancelled": "Peruutettu", - "backup_manual_in_progress": "Lähetys palvelimelle on jo käynnissä. Kokeile myöhemmin uudelleen", - "backup_manual_success": "Onnistui", - "backup_manual_title": "Lähetyksen tila", - "backup_options": "Varmuuskopiointiasetukset", - "backup_options_page_title": "Varmuuskopioinnin asetukset", - "backup_setting_subtitle": "Hallinnoi aktiivisia ja taustalla olevia lähetysasetuksia", - "backup_settings_subtitle": "Hallitse lähetysasetuksia", - "backup_upload_details_page_more_details": "Paina saadaksesi lisätietoja", - "backward": "Taaksepäin", - "biometric_auth_enabled": "Biometrinen tunnistautuminen käytössä", - "biometric_locked_out": "Sinulta on evätty pääsy biometriseen tunnistautumiseen", - "biometric_no_options": "Ei biometrisiä vaihtoehtoja", - "biometric_not_available": "Biometrinen tunnistautuminen ei ole käytettävissä tässä laitteessa", - "birthdate_saved": "Syntymäaika tallennettu", - "birthdate_set_description": "Syntymäaikaa käytetään laskemaan henkilön ikä kuvanottohetkellä.", - "blurred_background": "Sumennettu tausta", - "bugs_and_feature_requests": "Bugit ja ominaisuuspyynnöt", - "build": "Koontiversio", - "build_image": "Koontiversion kuva", - "bulk_delete_duplicates_confirmation": "Haluatko varmasti poistaa {count, plural, one {# kaksoiskappaleen} other {# kaksoiskappaleet}} kerralla? Tämä säilyttää kustakin mediasta kookkaimman ja poistaa loput pysyvästi. Et voi perua tätä!", - "bulk_keep_duplicates_confirmation": "Haluatko varmasti säilyttää {count, plural, one {# kaksoiskappaleen} other {# kaksoiskappaleet}}? Tämä merkitsee kaikki kaksoiskappaleet ratkaistuiksi, eikä poista mitään.", - "bulk_trash_duplicates_confirmation": "Haluatko varmasti siirtää {count, plural, one {# kaksoiskappaleen} other {# kaksoiskappaleet}} roskakoriin? Tämä säilyttää kustakin mediasta kookkaimman ja siirtää loput roskakoriin.", - "buy": "Osta lisenssi Immich:iin", - "cache_settings_clear_cache_button": "Tyhjennä välimuisti", - "cache_settings_clear_cache_button_title": "Tyhjennä sovelluksen välimuisti. Tämä vaikuttaa merkittävästi sovelluksen suorituskykyyn, kunnes välimuisti on rakennettu uudelleen.", - "cache_settings_duplicated_assets_clear_button": "Tyhjennä", - "cache_settings_duplicated_assets_subtitle": "Sovelluksen sivuutettavaksi merkitsemät valokuvat ja videot", - "cache_settings_duplicated_assets_title": "Kaksoiskappaleet ({count})", - "cache_settings_statistics_album": "Kirjaston esikatselukuvat", - "cache_settings_statistics_full": "Täysikokoiset kuvat", - "cache_settings_statistics_shared": "Jaettujen albumien esikatselukuvat", - "cache_settings_statistics_thumbnail": "Esikatselukuvat", - "cache_settings_statistics_title": "Välimuistin käyttö", - "cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttöä", - "cache_settings_tile_subtitle": "Hallitse paikallista tallennustilaa", - "cache_settings_tile_title": "Paikallinen tallennustila", - "cache_settings_title": "Välimuistin asetukset", - "camera": "Kamera", - "camera_brand": "Kameran merkki", - "camera_model": "Kameran malli", - "cancel": "Peruuta", - "cancel_search": "Peru haku", - "canceled": "Peruutettu", - "canceling": "Peruutetaan", - "cannot_merge_people": "Ihmisiä ei voitu yhdistää", - "cannot_undo_this_action": "Et voi perua tätä toimintoa!", - "cannot_update_the_description": "Kuvausta ei voi päivittää", - "cast": "Suoratoisto", - "cast_description": "Määritä saatavilla olevat suoratoistopalvelut", - "change_date": "Vaihda päiväys", - "change_description": "Muuta kuvausta", - "change_display_order": "Muuta näyttöjärjestystä", - "change_expiration_time": "Muuta erääntymisaikaa", - "change_location": "Vaihda sijainti", - "change_name": "Vaihda nimi", - "change_name_successfully": "Nimi vaihdettu", - "change_password": "Vaihda Salasana", - "change_password_description": "Tämä on joko ensimmäinen kertasi kun kirjaudut järjestelmään, tai salasanasi on pyydetty vaihtamaan. Määritä uusi salasana alle.", - "change_password_form_confirm_password": "Vahvista salasana", - "change_password_form_description": "Hei {name},\n\nTämä on joko ensimmäinen kerta, kun kirjaudut järjestelmään, tai sinulta on pyydetty salasanan vaihtoa. Ole hyvä ja syötä uusi salasana alle.", - "change_password_form_log_out": "Kirjaudu ulos kaikilta muilta laitteilta", - "change_password_form_log_out_description": "On suositeltavaa kirjautua ulos kaikilta laitteilta", - "change_password_form_new_password": "Uusi salasana", - "change_password_form_password_mismatch": "Salasanat eivät täsmää", - "change_password_form_reenter_new_password": "Uusi salasana uudelleen", - "change_pin_code": "Vaihda PIN-koodi", - "change_trigger": "Vaihda laukaisin", - "change_trigger_prompt": "Haluatko varmasti vaihtaa laukaisimen? Tämä poistaa kaikki olemassa olevat toiminnot ja suodattimet.", - "change_your_password": "Vaihda salasanasi", - "changed_visibility_successfully": "Näkyvyys vaihdettu", - "charging": "Ladataan laitetta", - "charging_requirement_mobile_backup": "Varmuuskopiointi taustalla vaatii laitteen latautumista", - "check_corrupt_asset_backup": "Vioittuneiden varmuuskopioiden tarkistaminen", - "check_corrupt_asset_backup_button": "Suorita tarkistus", - "check_corrupt_asset_backup_description": "Suorita tämä tarkistus vain Wi-Fi-yhteyden kautta ja vasta, kun kaikki kohteet on varmuuskopioitu. Toimenpide voi kestää muutamia minuutteja.", - "check_logs": "Katso lokeja", - "checksum": "Tarkistussumma", - "choose_matching_people_to_merge": "Valitse henkilöt joka yhdistetään", - "city": "Kaupunki", - "cleanup_confirm_description": "Immich löysi {count} turvallisesti palvelimelle varmuuskopioitua kohdetta (luotu ennen {date}). Poistetaanko paikalliset kopiot tästä laitteesta?", - "cleanup_confirm_prompt_title": "Poistetaanko tästä laitteesta?", - "cleanup_deleted_assets": "Siirretty {count} kohdetta laitteen roskakoriin", - "cleanup_deleting": "Siirretään roskakoriin...", - "cleanup_found_assets": "Löytyi {count} varmuuskopioitua kohdetta", - "cleanup_icloud_shared_albums_excluded": "Jaettuja iCloud-albumeja ei skannata", - "cleanup_no_assets_found": "Ehtojasi vastaavia varmuuskopioituja kohteita ei löytynyt", - "cleanup_preview_title": "Poistettavia kohteita {count}", - "clear": "Tyhjennä", - "clear_all": "Tyhjennä kaikki", - "clear_all_recent_searches": "Tyhjennä viimeisimmät haut", - "clear_file_cache": "Tyhjennä tiedostovälimuisti", - "clear_message": "Tyhjennä viesti", - "clear_value": "Tyhjää arvo", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Anna salasana", - "client_cert_import": "Tuo", - "client_cert_import_success_msg": "Asiakasvarmenne tuotu", - "client_cert_invalid_msg": "Virheellinen varmennetiedosto tai väärä salasana", - "client_cert_remove_msg": "Asiakassertifikaatti on poistettu", - "client_cert_subtitle": "Vain PKCS12 (.p12, .pfx) -muotoa tuetaan. Varmenteen tuonti/poisto on käytettävissä vain ennen sisäänkirjautumista", - "client_cert_title": "SSL-asiakassertifikaatti [KOKEELLINEN]", - "clockwise": "Myötäpäivään", - "close": "Sulje", - "collapse": "Supista", - "collapse_all": "Sulje kaikki", - "color": "Väri", - "color_theme": "Väriteema", - "command": "Komento", - "comment_deleted": "Kommentti poistettu", - "comment_options": "Kommentin valinnat", - "comments_and_likes": "Kommentit ja tykkäykset", - "comments_are_disabled": "Kommentointi ei käytössä", - "common_create_new_album": "Luo uusi albumi", - "completed": "Valmis", - "confirm": "Vahvista", - "confirm_admin_password": "Vahvista ylläpitäjän salasana", - "confirm_delete_face": "Haluatko poistaa {name} kasvot kohteesta?", - "confirm_delete_shared_link": "Haluatko varmasti poistaa tämän jaetun linkin?", - "confirm_keep_this_delete_others": "Kuvapinon muut kuvat tätä lukuunottamatta poistetaan. Oletko varma, että haluat jatkaa?", - "confirm_new_pin_code": "Vahvista uusi PIN-koodi", - "confirm_password": "Vahvista salasana", - "confirm_tag_face": "Haluatko merkitä nämä kasvot nimellä {name}?", - "confirm_tag_face_unnamed": "Merkitäänkö nämä kasvot?", - "connected_device": "Yhdistetty laite", - "connected_to": "Yhdistetty", - "contain": "Mahduta", - "context": "Konteksti", - "continue": "Jatka", - "control_bottom_app_bar_create_new_album": "Luo uusi albumi", - "control_bottom_app_bar_delete_from_immich": "Poista Immichistä", - "control_bottom_app_bar_delete_from_local": "Poista laitteelta", - "control_bottom_app_bar_edit_location": "Muokkaa sijaintia", - "control_bottom_app_bar_edit_time": "Muokkaa aikaa", - "control_bottom_app_bar_share_link": "Jaa linkki", - "control_bottom_app_bar_share_to": "Jaa", - "control_bottom_app_bar_trash_from_immich": "Siirrä roskakoriin", - "copied_image_to_clipboard": "Kuva kopioitu leikepöydälle.", - "copied_to_clipboard": "Kopioitu leikepöydälle!", - "copy_error": "Kopiointivirhe", - "copy_file_path": "Kopioi tiedostopolku", - "copy_image": "Kopioi kuva", - "copy_link": "Kopioi linkki", - "copy_link_to_clipboard": "Kopioi linkki leikepöydälle", - "copy_password": "Kopioi salasana", - "copy_to_clipboard": "Kopioi leikepöydälle", - "country": "Maa", - "cover": "Täytä", - "covers": "Kannet", - "create": "Luo", - "create_album": "Luo albumi", - "create_album_page_untitled": "Nimetön", - "create_api_key": "Luo API-avain", - "create_first_workflow": "Luo ensimmäinen työnkulku", - "create_library": "Luo uusi kirjasto", - "create_link": "Luo linkki", - "create_link_to_share": "Luo linkki jaettavaksi", - "create_link_to_share_description": "Salli kaikkien linkin saaneiden nähdä valitut kuvat", - "create_new": "LUO UUSI", - "create_new_person": "Luo uusi henkilö", - "create_new_person_hint": "Määritä valitut mediat uudelle henkilölle", - "create_new_user": "Luo uusi käyttäjä", - "create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA", - "create_shared_album_page_share_select_photos": "Valitse kuvat", - "create_shared_link": "Luo jakolinkki", - "create_tag": "Luo tunniste", - "create_tag_description": "Luo uusi tunniste. Sisäkkäisiä tunnisteita varten syötä tunnisteen täydellinen polku kauttaviivat mukaan luettuna.", - "create_user": "Luo käyttäjä", - "create_workflow": "Luo työnkulku", - "created": "Luotu", - "created_at": "Luotu", - "creating_linked_albums": "Luodaan linkattuja albumeita...", - "crop": "Rajaa", - "crop_aspect_ratio_fixed": "Kiinteä", - "crop_aspect_ratio_original": "Alkuperäinen", - "curated_object_page_title": "Asiat", - "current_device": "Nykyinen laite", - "current_pin_code": "Nykyinen PIN-koodi", - "current_server_address": "Nykyinen palvelinosoite", - "custom_date": "Mukautettu päivä", - "custom_locale": "Muokatut maa-asetukset", - "custom_locale_description": "Muotoile päivämäärät ja numerot perustuen alueen kieleen", - "custom_url": "Mukautettu URL", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Tumma", - "dark_theme": "Vaihda tumma teema", - "date": "Päivämäärä", - "date_after": "Päivämäärän jälkeen", - "date_and_time": "Päivämäärä ja aika", - "date_before": "Päivä ennen", - "date_format": "E d. LLL y • hh:mm", - "date_of_birth_saved": "Syntymäaika tallennettu", - "date_range": "Päivämäärän rajaus", - "day": "Päivä", - "days": "Päivää", - "deduplicate_all": "Poista kaikkien kaksoiskappaleet", - "deduplication_criteria_1": "Kuvan koko tavuina", - "deduplication_criteria_2": "EXIF-datan määrä", - "deduplication_info": "Deduplikaatiotieto", - "deduplication_info_description": "Jotta voimme automaattisesti esivalita aineistot ja poistaa kaksoiskappaleet suurina erinä, tarkastelemme:", - "default_locale": "Oletuskieliasetus", - "default_locale_description": "Muotoile päivämäärät ja numerot selaimesi kielen mukaan", - "delete": "Poista", - "delete_action_confirmation_message": "Haluatko varmasti poistaa tämän aineiston? Tämä toiminto siirtää aineiston palvelimen roskakoriin ja kysyy, haluatko poistaa sen myös paikallisesti", - "delete_action_prompt": "{count} poistettu", - "delete_album": "Poista albumi", - "delete_api_key_prompt": "Haluatko varmasti poistaa tämän API-avaimen?", - "delete_dialog_alert": "Nämä kohteet poistetaan pysyvästi Immich:stä ja laitteeltasi", - "delete_dialog_alert_local": "Kohteet poistetaan pysyvästi laitteelta, mutta ovat saatavilla Immich-palvelimella", - "delete_dialog_alert_local_non_backed_up": "Joitain kohteista ei ole varmuuskopioitu Immichiin ja ne poistetaan laitteelta pysyvästi", - "delete_dialog_alert_remote": "Kohteet poistetaan pysyvästi Immich-palvelimelta", - "delete_dialog_ok_force": "Poista kuitenkin", - "delete_dialog_title": "Poista pysyvästi", - "delete_duplicates_confirmation": "Haluatko varmasti poistaa nämä kaksoiskappaleet pysyvästi?", - "delete_face": "Poista kasvot", - "delete_key": "Poista avain", - "delete_library": "Poista kirjasto", - "delete_link": "Poista linkki", - "delete_local_action_prompt": "{count} poistettu paikallisesti", - "delete_local_dialog_ok_backed_up_only": "Poista vain varmuuskopioidut", - "delete_local_dialog_ok_force": "Poista kuitenkin", - "delete_others": "Poista muut", - "delete_permanently": "Poista pysyvästi", - "delete_permanently_action_prompt": "{count} poistettu pysyvästi", - "delete_shared_link": "Poista jaettu linkki", - "delete_shared_link_dialog_title": "Poista jaettu linkki", - "delete_tag": "Poista tunniste", - "delete_tag_confirmation_prompt": "Haluatko varmasti poistaa tunnisteen {tagName}?", - "delete_user": "Poista käyttäjä", - "deleted_shared_link": "Jaettu linkki poistettu", - "deletes_missing_assets": "Poistaa levyltä puuttuvat kohteet", - "description": "Kuvaus", - "description_input_hint_text": "Lisää kuvaus...", - "description_input_submit_error": "Virhe kuvauksen päivittämisessä, tarkista lisätiedot lokista", - "deselect_all": "Poista valinnat", - "details": "Tiedot", - "direction": "Suunta", - "disable": "Poista käytöstä", - "disabled": "Poistettu käytöstä", - "disallow_edits": "Älä salli muokkauksia", - "discord": "Discord", - "discover": "Tutki", - "discovered_devices": "Löydetyt laitteet", - "dismiss_all_errors": "Sivuuta kaikki virheet", - "dismiss_error": "Sivuuta virhe", - "display_options": "Näyttöasetukset", - "display_order": "Näyttöjärjestys", - "display_original_photos": "Näytä alkuperäiset kuvat", - "display_original_photos_setting_description": "Näytä mieluiten alkuperäinen kuva esikatselukuvan sijasta, kun alkuperäinen kuva on web-yhteensopiva. Tämä voi aiheuttaa kuvien näyttämisen hitautta.", - "do_not_show_again": "Älä näytä tätä enää", - "documentation": "Dokumentaatio", - "done": "Valmis", - "download": "Lataa", - "download_action_prompt": "Ladataan {count} kohdetta", - "download_canceled": "Lataus peruutettu", - "download_complete": "Lataus valmis", - "download_enqueue": "Latausjonossa", - "download_error": "Latausvirhe", - "download_failed": "Lataus epäonnistui", - "download_finished": "Lataus valmis", - "download_include_embedded_motion_videos": "Upotetut videot", - "download_include_embedded_motion_videos_description": "Sisällytä liikekuviin upotetut videot erillisinä tiedostoina", - "download_notfound": "Latausta ei löytynyt", - "download_original": "Lataa alkuperäinen", - "download_paused": "Lataus keskeytetty", - "download_settings": "Lataukset", - "download_settings_description": "Hallitse aineiston lataukseen liittyviä asetuksia", - "download_started": "Lataus aloitettu", - "download_sucess": "Lataus onnistui", - "download_sucess_android": "Media on ladattu DCIM/Immichiin", - "download_waiting_to_retry": "Odotetaan uudelleenyritystä", - "downloading": "Ladataan", - "downloading_asset_filename": "Ladataan mediaa {filename}", - "downloading_from_icloud": "Ladataan iCloudista", - "downloading_media": "Median lataaminen", - "drop_files_to_upload": "Pudota tiedostot mihin tahansa ladataksesi ne", - "duplicates": "Kaksoiskappaleet", - "duplicates_description": "Selvitä jokaisen kohdalla mitkä (jos mitkään) ovat kaksoiskappaleita", - "duration": "Kesto", - "edit": "Muokkaa", - "edit_album": "Muokkaa albumia", - "edit_avatar": "Muokkaa avataria", - "edit_birthday": "Muokkaa syntymäpäivää", - "edit_date": "Muokkaa päiväystä", - "edit_date_and_time": "Muokkaa päivämäärää ja kellonaikaa", - "edit_date_and_time_action_prompt": "{count} päivämäärää ja aikaa muutettu", - "edit_date_and_time_by_offset": "Muuta päivämäärää siirtymällä", - "edit_date_and_time_by_offset_interval": "Uusi päivämääräväli: {from} - {to}", - "edit_description": "Muokkaa kuvausta", - "edit_description_prompt": "Valitse uusi kuvaus:", - "edit_exclusion_pattern": "Muokkaa poissulkemismallia", - "edit_faces": "Muokkaa kasvoja", - "edit_key": "Muokkaa avainta", - "edit_link": "Muokkaa linkkiä", - "edit_location": "Muokkaa sijaintia", - "edit_location_action_prompt": "{count} sijaintia muokattu", - "edit_location_dialog_title": "Sijainti", - "edit_name": "Muokkaa nimeä", - "edit_people": "Muokkaa henkilöitä", - "edit_tag": "Muokkaa tunnistetta", - "edit_title": "Muokkaa otsikkoa", - "edit_user": "Muokkaa käyttäjää", - "edit_workflow": "Muokkaa työnkulkua", - "editor": "Muokkaaja", - "editor_close_without_save_prompt": "Muutoksia ei tallenneta", - "editor_close_without_save_title": "Suljetaanko editori?", - "editor_confirm_reset_all_changes": "Haluatko varmasti nollata kaikki muutokset?", - "editor_flip_horizontal": "Käännä vaakatasossa", - "editor_flip_vertical": "Käännä pystytasossa", - "editor_orientation": "Suunta", - "editor_reset_all_changes": "Nollaa muutokset", - "editor_rotate_left": "Kierrä 90° vastapäivään", - "editor_rotate_right": "Kierrä 90° myötäpäivään", - "email": "Sähköposti", - "email_notifications": "Sähköposti-ilmoitukset", - "empty_folder": "Kansio on tyhjä", - "empty_trash": "Tyhjennä roskakori", - "empty_trash_confirmation": "Haluatko varmasti tyhjentää roskakorin? Tämä poistaa pysyvästi kaikki tiedostot Immich:stä.\nToimintoa ei voi perua!", - "enable": "Ota käyttöön", - "enable_backup": "Ota varmuuskopiointi käyttöön", - "enable_biometric_auth_description": "Syötä PIN-koodisi ottaaksesi biometrisen tunnistautumisen käyttöön", - "enabled": "Käytössä", - "end_date": "Päättymispäivä", - "enqueued": "Lisätty jonoon", - "enter_wifi_name": "Anna Wi-Fi-verkon nimi", - "enter_your_pin_code": "Syötä PIN-koodi", - "enter_your_pin_code_subtitle": "Syötä PIN-koodi päästäksesi lukittuun kansioon", - "error": "Virhe", - "error_change_sort_album": "Albumin lajittelujärjestyksen muuttaminen epäonnistui", - "error_delete_face": "Virhe kasvojen poistamisessa kohteesta", - "error_getting_places": "Ongelma paikkojen haussa", - "error_loading_albums": "Virhe albumeita ladatessa", - "error_loading_image": "Kuvan lataus ei onnistunut", - "error_loading_partners": "Ongelma partnerin haussa: {error}", - "error_saving_image": "Virhe: {error}", - "error_tag_face_bounding_box": "Kasvojen merkitseminen epäonnistui – rajausruudun koordinaatteja ei löydy", - "error_title": "Virhe - Jotain meni pieleen", - "errors": { - "cannot_navigate_next_asset": "Seuraavaan mediaan ei voi siirtyä", - "cannot_navigate_previous_asset": "Edelliseen mediaan ei voi siirtyä", - "cant_apply_changes": "Muutoksia ei voitu tallettaa", - "cant_change_activity": "Aktiviteettia ei voitu {enabled, select, true {poistaa käytöstä} other {ottaa käyttöön}}", - "cant_change_asset_favorite": "Suosikkiasetusta ei voitu vaihtaa", - "cant_change_metadata_assets_count": "{count, plural, one {# median} other {# median}} metadataa ei voitu vaihtaa", - "cant_get_faces": "Kasvoja ei voitu hakea", - "cant_get_number_of_comments": "Kommenttien määrää ei voitu hakea", - "cant_search_people": "Ihmisiä ei voitu hakea", - "cant_search_places": "Sijainteja ei voitu hakea", - "error_adding_assets_to_album": "Medioita ei voitu lisätä albumiin", - "error_adding_users_to_album": "Käyttäjiä ei voitu lisätä albumiin", - "error_deleting_shared_user": "Jaettua käyttäjää ei voitu poistaa", - "error_downloading": "Tiedostoa {filename} ei voitu ladata", - "error_hiding_buy_button": "Virhe osta-painikkeen piilottamisessa", - "error_removing_assets_from_album": "Medioiden poisto epäonnistui. Katso konsolista lisätietoja", - "error_selecting_all_assets": "Kaikkia medioita ei voitu valita", - "exclusion_pattern_already_exists": "Tämä poissulkemismalli on jo olemassa.", - "failed_to_create_album": "Albumin luonti epäonnistui", - "failed_to_create_shared_link": "Jaetun linkin luonti epäonnistui", - "failed_to_edit_shared_link": "Jaetun linkin muokkaus epäonnistui", - "failed_to_get_people": "Henkilöiden haku epäonnistui", - "failed_to_keep_this_delete_others": "Muiden kohteiden poisto epäonnistui", - "failed_to_load_asset": "Kohteen lataus epäonnistui", - "failed_to_load_assets": "Kohteiden lataus epäonnistui", - "failed_to_load_notifications": "Ilmoitusten lataaminen epäonnistui", - "failed_to_load_people": "Henkilöiden lataus epäonnistui", - "failed_to_remove_product_key": "Tuoteavaimen poistaminen epäonnistui", - "failed_to_reset_pin_code": "PIN-koodin nollaus epäonnistui", - "failed_to_stack_assets": "Medioiden pinoaminen epäonnistui", - "failed_to_unstack_assets": "Medioiden pinoamisen purku epäonnistui", - "failed_to_update_notification_status": "Ilmoituksen tilan päivittäminen epäonnistui", - "incorrect_email_or_password": "Väärä sähköpostiosoite tai salasana", - "library_folder_already_exists": "Tämä tuonnin polku on jo olemassa.", - "paths_validation_failed": "{paths, plural, one {# polun} other {# polun}} validointi epäonnistui", - "profile_picture_transparent_pixels": "Profiilikuvassa ei voi olla läpinäkyviä pikseleitä. Zoomaa lähemmäs ja/tai siirrä kuvaa.", - "quota_higher_than_disk_size": "Asettamasi kiintiö on suurempi kuin levyn koko", - "something_went_wrong": "Jotain meni pieleen", - "unable_to_add_album_users": "Käyttäjiä ei voi lisätä albumiin", - "unable_to_add_assets_to_shared_link": "Medioiden lisääminen jaettuun linkkiin epäonnistui", - "unable_to_add_comment": "Kommentin lisääminen epäonnistui", - "unable_to_add_exclusion_pattern": "Ei voida lisätä poissulkemismallia", - "unable_to_add_partners": "Kumppaneita ei voitu lisätä", - "unable_to_add_remove_archive": "Ei voida {archived, select, true {poistaa kohdetta arkistosta} other {lisätä kohdetta arkistoon}}", - "unable_to_add_remove_favorites": "Ei voida {favorite, select, true {lisätä kohdetta suosikkeihin} other {poistaa kohdetta suosikeista}}", - "unable_to_archive_unarchive": "Ei voida {archived, select, true {arkistoida} other {poistaa arkistosta}}", - "unable_to_change_album_user_role": "Albumin käyttäjän roolia ei voitu muuttaa", - "unable_to_change_date": "Päivämäärää ei voitu muuttaa", - "unable_to_change_description": "Kuvausta ei voi muuttaa", - "unable_to_change_favorite": "Ei voida muuttaa suosikkia kohteelle", - "unable_to_change_location": "Sijainnin muuttaminen epäonnistui", - "unable_to_change_password": "Salasanan vaihto epäonnistui", - "unable_to_change_visibility": "Ei voida muuttaa näkyvyyttä {count, plural, one {# henkilölle} other {# henkilölle}}", - "unable_to_complete_oauth_login": "OAuth-kirjautumista ei voitu suorittaa loppuun", - "unable_to_connect": "Yhteyttä ei voitu muodostaa", - "unable_to_copy_to_clipboard": "Leikepöydälle ei voitu kopioida, varmista että käytät sivua https-yhteyden kautta", - "unable_to_create": "Työnkulun luominen ei onnistunut", - "unable_to_create_admin_account": "Pääkäyttäjän luominen epäonnistui", - "unable_to_create_api_key": "Uuden API-avaimen luominen epäonnistui", - "unable_to_create_library": "Kirjaston luominen epäonnistui", - "unable_to_create_user": "Käyttäjän luominen epäonnistui", - "unable_to_delete_album": "Albumin poistaminen epäonnistui", - "unable_to_delete_asset": "Kohteen poistaminen epäonnistui", - "unable_to_delete_assets": "Virhe kohteen poistamisessa", - "unable_to_delete_exclusion_pattern": "Ei voida poistaa poissulkemismallia", - "unable_to_delete_shared_link": "Jaetun linkin poistaminen epäonnistui", - "unable_to_delete_user": "Käyttäjän poistaminen epäonnistui", - "unable_to_delete_workflow": "Työnkulun poistaminen ei onnistunut", - "unable_to_download_files": "Tiedostojen lataaminen epäonnistui", - "unable_to_edit_exclusion_pattern": "Ei voida muokata poissulkemismallia", - "unable_to_empty_trash": "Roskakorin tyhjentäminen epäonnistui", - "unable_to_enter_fullscreen": "Koko ruudun tilaan siirtyminen epäonnistui", - "unable_to_exit_fullscreen": "Koko ruudun tilasta poistuminen epäonnistui", - "unable_to_get_comments_number": "Kommenttien määrän hakeminen epäonnistui", - "unable_to_get_shared_link": "Jaetun linkin hakeminen epäonnistui", - "unable_to_hide_person": "Henkilön piilottaminen epäonnistui", - "unable_to_link_motion_video": "Liikekuvan linkitys epäonnistui", - "unable_to_link_oauth_account": "OAuth-tilin linkittäminen epäonnistui", - "unable_to_log_out_all_devices": "Kaikkien laitteiden uloskirjautuminen epäonnistui", - "unable_to_log_out_device": "Laitteen uloskirjautuminen epäonnistui", - "unable_to_login_with_oauth": "OAuth-kirjautuminen epäonnistui", - "unable_to_play_video": "Videon toistaminen epäonnistui", - "unable_to_reassign_assets_existing_person": "Ei voida siirtää kohteita {name, select, null {olemassa olevalle henkilölle} other {{name}}}", - "unable_to_reassign_assets_new_person": "Ei voida siirtää kohteita uudelle henkilölle", - "unable_to_refresh_user": "Käyttäjän päivittäminen epäonnistui", - "unable_to_remove_album_users": "Käyttäjien poistaminen albumista epäonnistui", - "unable_to_remove_api_key": "API-avaimen poistaminen epäonnistui", - "unable_to_remove_assets_from_shared_link": "kohteiden poistaminen jaetusta linkistä epäonnistui", - "unable_to_remove_library": "Kirjaston poistaminen epäonnistui", - "unable_to_remove_partner": "Kumppanin poistaminen epäonnistui", - "unable_to_remove_reaction": "Reaktion poistaminen epäonnistui", - "unable_to_reset_password": "Salasanan nollaaminen epäonnistui", - "unable_to_reset_pin_code": "PIN-koodin nollaaminen epäonnistui", - "unable_to_resolve_duplicate": "Kaksoiskappaleen ratkaiseminen epäonnistui", - "unable_to_restore_assets": "Kohteen palauttaminen epäonnistui", - "unable_to_restore_trash": "Kohteiden palauttaminen epäonnistui", - "unable_to_restore_user": "Käyttäjän palauttaminen epäonnistui", - "unable_to_save_album": "Albumin tallentaminen epäonnistui", - "unable_to_save_api_key": "API-avaimen tallentaminen epäonnistui", - "unable_to_save_date_of_birth": "Syntymäajan tallentaminen epäonnistui", - "unable_to_save_name": "Nimen tallentaminen epäonnistui", - "unable_to_save_profile": "Profiilin tallentaminen epäonnistui", - "unable_to_save_settings": "Asetusten tallentaminen epäonnistui", - "unable_to_scan_libraries": "Kirjastojen skannaaminen epäonnistui", - "unable_to_scan_library": "Kirjaston skannaaminen epäonnistui", - "unable_to_set_feature_photo": "Ei voida asettaa ominaiskuvaa", - "unable_to_set_profile_picture": "Profiilikuvan asetus epäonnistui", - "unable_to_submit_job": "Työtä ei voitu lähettää", - "unable_to_trash_asset": "Median siirto roskakoriin epäonnistui", - "unable_to_unlink_account": "Tunnuksen irroitus epäonnistui", - "unable_to_unlink_motion_video": "Ei voida irrottaa liikevideota", - "unable_to_update_album_cover": "Albumin kannen päivitys epäonnistui", - "unable_to_update_album_info": "Albumin tietojen päivitys epäonnistui", - "unable_to_update_library": "Kirjaston päivitys epäonnistui", - "unable_to_update_location": "Sijainnin päivitys epäonnistui", - "unable_to_update_settings": "Asetusten päivitys epäonnistui", - "unable_to_update_timeline_display_status": "Aikajanalla näyttämisen asetusta ei voitu tallettaa", - "unable_to_update_user": "Käyttäjän muokkaus epäonnistui", - "unable_to_update_workflow": "Työnkulun päivittäminen ei onnistunut", - "unable_to_upload_file": "Tiedostoa ei voitu ladata" - }, - "errors_text": "Virheet", - "exclusion_pattern": "Poissulkemismenetelmä", - "exif": "Exif", - "exif_bottom_sheet_description": "Lisää kuvaus…", - "exif_bottom_sheet_description_error": "Kuvauksen muuttaminen epäonnistui", - "exif_bottom_sheet_details": "TIEDOT", - "exif_bottom_sheet_location": "SIJAINTI", - "exif_bottom_sheet_no_description": "Ei kuvausta", - "exif_bottom_sheet_people": "IHMISET", - "exif_bottom_sheet_person_add_person": "Lisää nimi", - "exit_slideshow": "Poistu diaesityksestä", - "expand_all": "Laajenna kaikki", - "experimental_settings_new_asset_list_subtitle": "Työn alla", - "experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko", - "experimental_settings_subtitle": "Käyttö omalla vastuulla!", - "experimental_settings_title": "Kokeellinen", - "expire_after": "Umpeutuu", - "expired": "Voimassaolo päättynyt", - "expires_date": "Vanhenee {date}", - "explore": "Tutki", - "explorer": "Selain", - "export": "Vie", - "export_as_json": "Vie JSON-muodossa", - "export_database": "Vie tietokanta", - "export_database_description": "Vie SQLite-tietokanta", - "extension": "Tiedostopääte", - "external": "Ulkoisesta", - "external_libraries": "Ulkoiset kirjastot", - "external_network": "Ulkoinen verkko", - "external_network_sheet_info": "Kun laite ei ole yhteydessä valittuun Wi-Fi-verkkoon, sovellus yrittää muodostaa yhteyden palvelimeen alla olevista URL-osoitteista ylhäältä alas, kunnes yhteys muodostuu", - "face_unassigned": "Ei määritelty", - "failed": "Epäonnistui", - "failed_count": "Epäonnistuneita: {count}", - "failed_to_authenticate": "Todennus epäonnistui", - "failed_to_load_assets": "Kohteiden lataus epäonnistui", - "failed_to_load_folder": "Kansion lataaminen epäonnistui", - "favorite": "Suosikki", - "favorite_action_prompt": "{count} lisätty suosikkeihin", - "favorite_or_unfavorite_photo": "Lisää tai poista kuva suosikeista", - "favorites": "Suosikit", - "favorites_page_no_favorites": "Suosikkikohteita ei löytynyt", - "feature_photo_updated": "Kansikuva ladattu", - "features": "Ominaisuudet", - "features_in_development": "Kehityksessä olevat ominaisuudet", - "features_setting_description": "Hallitse sovelluksen ominaisuuksia", - "file_name_or_extension": "Tiedostonimi tai tiedostopääte", - "file_size": "Tiedostokoko", - "filename": "Tiedostonimi", - "filetype": "Tiedostotyyppi", - "filter": "Suodatin", - "filter_people": "Suodata henkilöt", - "filter_places": "Suodata paikkoja", - "filters": "Suodattimet", - "find_them_fast": "Löydä nopeasti hakemalla nimellä", - "first": "Ensimmäinen", - "fix_incorrect_match": "Korjaa virheellinen osuma", - "folder": "Kansio", - "folder_not_found": "Kansiota ei löytynyt", - "folders": "Kansiot", - "folders_feature_description": "Käytetään kansionäkymää valokuvien ja videoiden selaamiseen järjestelmässä", - "forgot_pin_code_question": "Unohditko PIN-koodisi?", - "forward": "Eteenpäin", - "full_path": "Koko polku: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ominaisuus lataa ulkoisia resursseja Googlelta toimiakseen.", - "general": "Yleinen", - "geolocation_instruction_location": "Napsauta kuvaa, jossa on GPS-koordinaatit, käyttääksesi sen sijaintia, tai valitse sijainti suoraan kartalta", - "get_help": "Hae apua", - "get_wifiname_error": "Wi-Fi-verkon nimen hakeminen epäonnistui. Varmista, että olet myöntänyt tarvittavat käyttöoikeudet ja että olet yhteydessä Wi-Fi-verkkoon", - "getting_started": "Aloittaminen", - "go_back": "Palaa", - "go_to_folder": "Mene kansioon", - "go_to_search": "Siirry hakuun", - "gps": "GPS", - "gps_missing": "Ei GPS:ää", - "grant_permission": "Myönnä lupa", - "group_albums_by": "Ryhmitä albumi...", - "group_country": "Ryhmitä maan mukaan", - "group_no": "Ei ryhmitystä", - "group_owner": "Ryhmitä omistajan mukaan", - "group_places_by": "Ryhmitä paikat...", - "group_year": "Ryhmitä vuoden mukaan", - "haptic_feedback_switch": "Ota haptinen palaute käyttöön", - "haptic_feedback_title": "Haptinen palaute", - "has_quota": "On kiintiö", - "hash_asset": "Tiivistetty kohde", - "hashed_assets": "Tiivistettyä kohdetta", - "hashing": "Tiivistys", - "header_settings_add_header_tip": "Lisää otsikko", - "header_settings_field_validator_msg": "Arvo ei voi olla tyhjä", - "header_settings_header_name_input": "Otsikon nimi", - "header_settings_header_value_input": "Otsikon arvo", - "headers_settings_tile_title": "Mukautettu proxy headers", - "height": "Korkeus", - "hi_user": "Hei {name} ({email})", - "hide_all_people": "Piilota kaikki henkilöt", - "hide_gallery": "Piilota galleria", - "hide_named_person": "Piilota henkilön {name}", - "hide_password": "Piilota salasana", - "hide_person": "Piilota henkilö", - "hide_schema": "Piilota skeema", - "hide_text_recognition": "Piilota tekstin tunnistus", - "hide_unnamed_people": "Piilota nimeämättömät henkilöt", - "home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.", - "home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan", - "home_page_add_to_album_success": "Lisätty {added} kohdetta albumiin {album}.", - "home_page_album_err_partner": "Kumppanin kohteita ei voi vielä lisätä albumiin, ohitetaan", - "home_page_archive_err_local": "Paikallisten kohteiden arkistointi ei ole mahdollista, ohitetaan", - "home_page_archive_err_partner": "Kumppanin kohteita ei voi arkistoida, ohitetaan", - "home_page_building_timeline": "Rakennetaan aikajanaa", - "home_page_delete_err_partner": "Kumppanin kohteita ei voi poistaa, ohitetaan", - "home_page_delete_remote_err_local": "Paikallisia kohteita etäkohdevalintojen joukossa, ohitetaan", - "home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan", - "home_page_favorite_err_partner": "Kumppanin kohteita ei voi vielä merkitä suosikiksi, ohitetaan", - "home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita", - "home_page_locked_error_local": "Paikallisten kohteiden siirto lukittuun kansioon ei onnistu, ohitetaan", - "home_page_locked_error_partner": "Kumppanin kohteita ei voi siirtää lukittuun kansioon, ohitetaan", - "home_page_share_err_local": "Paikallisia kohteita ei voitu jakaa linkkien avulla, ohitetaan", - "home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan", - "host": "Isäntä", - "hour": "Tunti", - "hours": "Tunnit", - "id": "ID", - "idle": "Toimeton", - "ignore_icloud_photos": "Ohita iCloud-kuvat", - "ignore_icloud_photos_description": "iCloudiin tallennettuja kuvia ei ladata Immich-palvelimelle", - "image": "Kuva", - "image_alt_text_date": "{isVideo, select, true {Video} other {Kuva}} otettu {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Kuva}} otettu {person1} kanssa {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Kuva}} otettu {person1}n ja {person2}n kanssa {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Kuva}} otettu {person1}n, {person2}n ja {person3}n kanssa {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Kuva}} otettu {person1}n, {person2}n ja {additionalCount, number} muissa kanssa {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n kanssa {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n ja {person2}n kanssa {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n, {person2}n ja {person3}n kanssa {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n, {person2}n ja {additionalCount, number} muun kanssa {date}", - "image_saved_successfully": "Kuva tallennettu", - "image_viewer_page_state_provider_download_started": "Lataaminen aloitettu", - "image_viewer_page_state_provider_download_success": "Lataus onnistui", - "image_viewer_page_state_provider_share_error": "Jakovirhe", - "immich_logo": "Immich-logo", - "immich_web_interface": "Immich-verkkokäyttöliittymä", - "import_from_json": "Tuo JSON-tiedostosta", - "import_path": "Tuontipolku", - "in_albums": "{count, plural, one {# albumissa} other {# albumissa}}", - "in_archive": "Arkistossa", - "in_year": "Vuonna {year}", - "in_year_selector": "Vuosi", - "include_archived": "Sisällytä arkistoidut", - "include_shared_albums": "Sisällytä jaetut albumit", - "include_shared_partner_assets": "Sisällytä jaetut kumppanikohteet", - "individual_share": "Yksittäinen jako", - "individual_shares": "Yksittäiset jaot", - "info": "Lisätietoja", - "interval": { - "day_at_onepm": "Joka päivä klo 13.00", - "hours": "Joka {hours, plural, one {tunti} other {{hours, number} tuntia}}", - "night_at_midnight": "Joka yö keskiyöllä", - "night_at_twoam": "Joka yö klo 2.00" - }, - "invalid_date": "Virheellinen päivämäärä", - "invalid_date_format": "Virheellinen päivämäärämuoto", - "invite_people": "Kutsu ihmisiä", - "invite_to_album": "Kutsu albumiin", - "ios_debug_info_fetch_ran_at": "Noudettu {dateTime}", - "ios_debug_info_last_sync_at": "Viimeisin synkronisointi {dateTime}", - "ios_debug_info_no_processes_queued": "Ei taustaprosesseja jonossa", - "ios_debug_info_no_sync_yet": "Taustasynkronisointia ei ole suoritettu vielä", - "ios_debug_info_processes_queued": "{count, plural, one {{count} taustaprosessi jonossa} other {{count} taustaprosessia jonossa}}", - "ios_debug_info_processing_ran_at": "Prosessi valmistui {dateTime}", - "items_count": "{count, plural, one {# kpl} other {# kpl}}", - "jobs": "Taustatehtävät", - "json_editor": "JSON-muokkain", - "json_error": "JSON-virhe", - "keep": "Säilytä", - "keep_albums": "Säilytä albumit", - "keep_all": "Säilytä kaikki", - "keep_description": "Valitse, mitä laitteella säilytetään tilan vapautuksen yhteydessä.", - "keep_favorites": "Säilytä suosikit", - "keep_on_device": "Säilytä laitteella", - "keep_on_device_hint": "Valitse laitteella säilytettävät kohteet", - "keep_this_delete_others": "Säilytä tämä, poista muut", - "keeping": "Säilytetään: {items}", - "kept_this_deleted_others": "Tämä kohde säilytettiin. {count, plural, one {# asset} other {# assets}} poistettiin", - "keyboard_shortcuts": "Pikanäppäimet", - "language": "Kieli", - "language_no_results_subtitle": "Yritä säätää hakuehtoja", - "language_no_results_title": "Kieliä ei löydetty", - "language_search_hint": "Etsi kieliä...", - "language_setting_description": "Valitse suosimasi kieli", - "large_files": "Suuret tiedostot", - "last": "Viimeinen", - "last_months": "{count, plural, one {Viime kuukausi} other {Viimeiset # kuukautta}}", - "last_seen": "Viimeksi nähty", - "latest_version": "Viimeisin versio", - "latitude": "Leveysaste", - "leave": "Poistu", - "leave_album": "Poistu albumista", - "lens_model": "Objektiivin malli", - "let_others_respond": "Anna muiden vastata", - "level": "Taso", - "library": "Kirjasto", - "library_add_folder": "Lisää kansio", - "library_edit_folder": "Muokkaa kansiota", - "library_options": "Kirjastovaihtoehdot", - "library_page_device_albums": "Laitteen albumit", - "library_page_new_album": "Uusi albumi", - "library_page_sort_asset_count": "Kohteiden lukumäärä", - "library_page_sort_created": "Viimeisin luotu", - "library_page_sort_last_modified": "Viimeksi muokattu", - "library_page_sort_title": "Albumin otsikko", - "licenses": "Lisenssit", - "light": "Vaalea", - "like": "Tykkää", - "like_deleted": "Tykkäys poistettu", - "link_motion_video": "Linkitä liikevideo", - "link_to_oauth": "Linkki OAuth", - "linked_oauth_account": "Linkitetty OAuth-tili", - "list": "Lista", - "loading": "Ladataan", - "loading_search_results_failed": "Hakutulosten lataaminen epäonnistui", - "local": "Paikallinen", - "local_asset_cast_failed": "Kohdetta, joka ei ole ladattuna palvelimelle, ei voida striimata", - "local_assets": "Paikalliset kohteet", - "local_media_summary": "Paikallisen median yhteenveto", - "local_network": "Lähiverkko", - "local_network_sheet_info": "Sovellus muodostaa yhteyden palvelimeen tämän URL-osoitteen kautta, kun käytetään määritettyä Wi-Fi-verkkoa", - "location": "Sijainti", - "location_permission": "Sijainnin käyttöoikeus", - "location_permission_content": "Automaattisen vaihtotoiminnon käyttämiseksi Immich tarvitsee tarkan sijainnin käyttöoikeuden, jotta se voi lukea nykyisen Wi-Fi-verkon nimen", - "location_picker_choose_on_map": "Valitse kartalta", - "location_picker_latitude_error": "Lisää kelvollinen leveysaste", - "location_picker_latitude_hint": "Syötä leveysaste", - "location_picker_longitude_error": "Lisää kelvollinen pituusaste", - "location_picker_longitude_hint": "Syötä pituusaste", - "lock": "Lukitse", - "locked_folder": "Lukittu kansio", - "log_detail_title": "Lokin yksityiskohtaisuus", - "log_out": "Kirjaudu ulos", - "log_out_all_devices": "Kirjaudu ulos kaikilta laitteilta", - "logged_in_as": "Kirjautunut käyttäjänä {user}", - "logged_out_all_devices": "Kaikki laitteet kirjattu ulos", - "logged_out_device": "Laite kirjattu ulos", - "login": "Kirjaudu", - "login_disabled": "Kirjautuminen on poistettu käytöstä", - "login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.", - "login_form_back_button_text": "Takaisin", - "login_form_email_hint": "sahkopostisi@esimerkki.fi", - "login_form_endpoint_hint": "http://palvelimesi-osoite:portti", - "login_form_endpoint_url": "Palvelimen URL", - "login_form_err_http": "Lisää http:// tai https://", - "login_form_err_invalid_email": "Virheellinen sähköpostiosoite", - "login_form_err_invalid_url": "Virheellinen URL", - "login_form_err_leading_whitespace": "Alussa välilyönti", - "login_form_err_trailing_whitespace": "Lopussa välilyönti", - "login_form_failed_get_oauth_server_config": "Virhe kirjauduttaessa OAuth:lla, tarkista palvelimen URL", - "login_form_failed_get_oauth_server_disable": "OAuth-ominaisuus ei ole käytössä tällä palvelimella", - "login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähköpostiosoite ja salasana", - "login_form_handshake_exception": "Tapahtui poikkeus kättelyssä palvelimen kanssa. Kytke päälle self-signed -sertifikaattituki asetuksista, mikäli käytät self-signed -sertifikaatteja.", - "login_form_password_hint": "salasana", - "login_form_save_login": "Pysy kirjautuneena", - "login_form_server_empty": "Syötä palvelimen URL-osoite.", - "login_form_server_error": "Palvelimeen ei saatu yhteyttä.", - "login_has_been_disabled": "Kirjautuminen on otettu pois käytöstä.", - "login_password_changed_error": "Salasanan päivityksessä tapahtui virhe", - "login_password_changed_success": "Salasan päivitetty onnistuneesti", - "logout_all_device_confirmation": "Haluatko varmasti kirjautua ulos kaikilta laitteilta?", - "logout_this_device_confirmation": "Haluatko varmasti kirjautua ulos näiltä laitteilta?", - "logs": "Loki", - "longitude": "Pituusaste", - "look": "Tyyli", - "loop_videos": "Toista videot uudelleen", - "loop_videos_description": "Ota käyttöön jatkuva videotoisto tarkemmassa näkymässä.", - "main_branch_warning": "Käytät kehitysversiota; suosittelemme vahvasti käyttämään julkaisuversiota!", - "main_menu": "Päävalikko", - "maintenance_action_restore": "Palautetaan tietokanta", - "maintenance_description": "Immich on asetettu ylläpitotilaan.", - "maintenance_end": "Poistu ylläpitotilasta", - "maintenance_end_error": "Poistuminen ylläpitotilasta epäonnistui.", - "maintenance_logged_in_as": "Kirjautuneena käyttäjänä {user}", - "maintenance_restore_from_backup": "Palauta varmuuskopiosta", - "maintenance_restore_library": "Palauta kirjastosta", - "maintenance_restore_library_confirm": "Jos tämä vaikuttaa oikealta, jatka varmuuskopion palauttamista!", - "maintenance_restore_library_folder_pass": "luettavissa ja kirjoitettavissa", - "maintenance_restore_library_folder_read_fail": "ei luettavissa", - "maintenance_restore_library_folder_write_fail": "ei kirjoitettavissa", - "maintenance_restore_library_hint_missing_files": "Sinulta saattaa puuttua tärkeitä tiedostoja", - "maintenance_restore_library_hint_regenerate_later": "Voit luoda ne uudelleen myöhemmin asetuksissa", - "maintenance_restore_library_loading": "Ladataan eheystarkistuksia ja heurestiikkaa…", - "maintenance_task_backup": "Luodaan varmuuskopiota olemassa olevasta tietokannasta…", - "maintenance_task_migrations": "Suoritetaan tietokantamigraatioita…", - "maintenance_task_restore": "Palautetaan valittu varmuuskopio…", - "maintenance_task_rollback": "Palauttaminen epäonnistui, palataan takaisin palautuspisteeseen…", - "maintenance_title": "Tilapäisesti ei saatavilla", - "make": "Valmistaja", - "manage_geolocation": "Muokkaa sijaintia", - "manage_media_access_rationale": "Tämä lupa tarvitaan sisällön siirtämiseen roskakoriin ja sisällön palauttamiseen sieltä.", - "manage_media_access_settings": "Avaa asetukset", - "manage_media_access_subtitle": "Anna Immich-sovellukselle lupa hallinoida ja siirtää mediatiedostoja.", - "manage_media_access_title": "Lupa median hallinnointiin", - "manage_shared_links": "Hallitse jaettuja linkkejä", - "manage_sharing_with_partners": "Hallitse jakamista kumppaneille", - "manage_the_app_settings": "Hallitse sovelluksen asetuksia", - "manage_your_account": "Hallitse tiliäsi", - "manage_your_api_keys": "Hallitse API-avaimiasi", - "manage_your_devices": "Hallitse sisäänkirjautuneita laitteitasi", - "manage_your_oauth_connection": "Hallitse OAuth-yhteyttäsi", - "map": "Kartta", - "map_assets_in_bounds": "{count, plural, =0 {Ei kuvia tällä alueella} one {# kuva} other {# kuvaa}}", - "map_cannot_get_user_location": "Käyttäjän sijaintia ei voitu määrittää", - "map_location_dialog_yes": "Kyllä", - "map_location_picker_page_use_location": "Käytä tätä sijaintia", - "map_location_service_disabled_content": "Paikannuspalvelun pitää olla kytkettynä päälle, jotta nykyisen sijaintisi kohteita voidaan näyttää. Haluatko kytkeä sen päälle nyt?", - "map_location_service_disabled_title": "Paikannuspalvelu pois päältä", - "map_marker_for_images": "Karttamarkerointi kuville, jotka on otettu kaupungissa {city}, maassa {country}", - "map_marker_with_image": "Karttamarkerointi kuvalla", - "map_no_location_permission_content": "Paikannuslupa tarvitaan, jotta nykyisen sijainnin kohteita voidaan näyttää. Haluatko sallia pääsyn sijaintiin?", - "map_no_location_permission_title": "Paikannuslupa estetty", - "map_settings": "Kartta-asetukset", - "map_settings_dark_mode": "Tumma tila", - "map_settings_date_range_option_day": "Viimeiset 24 tuntia", - "map_settings_date_range_option_days": "Viimeiset {days} päivää", - "map_settings_date_range_option_year": "Viimeisin vuosi", - "map_settings_date_range_option_years": "Viimeiset {years} vuotta", - "map_settings_dialog_title": "Kartta-asetukset", - "map_settings_include_show_archived": "Sisällytä arkistoidut", - "map_settings_include_show_partners": "Sisällytä kumppanit", - "map_settings_only_show_favorites": "Näytä vain suosikit", - "map_settings_theme_settings": "Kartan teema", - "map_zoom_to_see_photos": "Tarkenna nähdäksesi kuvat", - "mark_all_as_read": "Merkitse kaikki luetuiksi", - "mark_as_read": "Merkitse luetuksi", - "marked_all_as_read": "Merkitty kaikki luetuiksi", - "matches": "Osumia", - "matching_assets": "Vastaava sisältö", - "media_type": "Median tyyppi", - "memories": "Muistoja", - "memories_all_caught_up": "Kaikki ajan tasalla", - "memories_check_back_tomorrow": "Palaa huomenna nähdäskesi lisää muistoja", - "memories_setting_description": "Hallitse mitä näet muistoissasi", - "memories_start_over": "Aloita alusta", - "memories_swipe_to_close": "Pyyhkäise ylös sulkeaksesi", - "memory": "Muisto", - "memory_lane_title": "Muistojen polku {title}", - "menu": "Valikko", - "merge": "Yhdistä", - "merge_people": "Yhdistä henkilöt", - "merge_people_limit": "Voit yhdistää vain enintään 5 kasvoa kerrallaan", - "merge_people_prompt": "Haluatko yhdistää nämä henkilöt? Tätä valintaa ei voi peruuttaa.", - "merge_people_successfully": "Henkilöt yhdistetty", - "merged_people_count": "{count, plural, one {# Henkilö} other {# henkilöä}} yhdistetty", - "minimize": "Pienennä", - "minute": "Minuutti", - "minutes": "Minuutit", - "missing": "Puuttuvat", - "mobile_app": "Mobiilisovellus", - "mobile_app_download_onboarding_note": "Lataa mobiilisovellus käyttämällä seuraavia vaihtoehtoja", - "model": "Malli", - "month": "Kuukauden mukaan", - "monthly_title_text_date_format": "MMMM y", - "more": "Enemmän", - "move": "Siirrä", - "move_off_locked_folder": "Siirrä pois lukitusta kansiosta", - "move_to": "Siirrä kohteeseen", - "move_to_lock_folder_action_prompt": "{count} lisätty lukittuun kansioon", - "move_to_locked_folder": "Siirrä lukittuun kansioon", - "move_to_locked_folder_confirmation": "Nämä kuvat ja videot poistetaan kaikista albumeista, ja ne ovat nähtävissä vain lukitussa kansiossa", - "moved_to_archive": "Siirretty {count, plural, one {# kohde} other {# kohdetta}} arkistoon", - "moved_to_library": "Siirretty {count, plural, one {# kohde} other {# kohdetta}} kirjastoon", - "moved_to_trash": "Siirretty roskakoriin", - "multiselect_grid_edit_date_time_err_read_only": "Vain luku -tilassa olevien kohteiden päivämäärää ei voitu muokata, ohitetaan", - "multiselect_grid_edit_gps_err_read_only": "Vain luku -tilassa olevien kohteiden sijantitietoja ei voitu muokata, ohitetaan", - "mute_memories": "Mykistä muistot", - "my_albums": "Omat albumit", - "name": "Nimi", - "name_or_nickname": "Nimi tai lempinimi", - "name_required": "Nimi on pakollinen", - "navigate": "Navigoi", - "navigate_to_time": "Navigoi aikaan", - "network_requirement_photos_upload": "Käytä mobiiliverkkoa kuvien varmuuskopioimiseksi", - "network_requirement_videos_upload": "Käytä mobiiliverkkoa videoiden varmuuskopioimiseksi", - "network_requirements": "Verkkovaatimukset", - "network_requirements_updated": "Verkkovaatimukset muuttuivat, nollataan varmuuskopiointijono", - "networking_settings": "Verkko", - "networking_subtitle": "Hallitse palvelinasetuksia", - "never": "ei koskaan", - "new_album": "Uusi Albumi", - "new_api_key": "Uusi API-avain", - "new_date_range": "Uusi aikaväli", - "new_password": "Uusi salasana", - "new_person": "Uusi henkilö", - "new_pin_code": "Uusi PIN-koodi", - "new_pin_code_subtitle": "Tämä on ensimmäinen kerta, kun käytät lukittua kansiota. Luo PIN-koodi päästäksesi tähän sisältöön turvallisesti", - "new_timeline": "Uusi aikajana", - "new_update": "Uusi päivitys", - "new_user_created": "Uusi käyttäjä lisätty", - "new_version_available": "UUSI VERSIO SAATAVILLA", - "newest_first": "Uusin ensin", - "next": "Seuraava", - "next_memory": "Seuraava muisto", - "no": "Ei", - "no_actions_added": "Toimintoja ei ole vielä lisätty", - "no_albums_found": "Albumeja ei löytynyt", - "no_albums_message": "Luo albumi pitääksesi kuvat ja videot järjestyksessä", - "no_albums_with_name_yet": "Näyttää siltä, ettei sinulla ole yhtään tämän nimistä albumia.", - "no_albums_yet": "Näyttää siltä, ettei sinulla ole vielä yhtään albumia.", - "no_archived_assets_message": "Arkistoi kuvia ja videoita piilottaaksesi ne kuvat näkymästä", - "no_assets_message": "Napsauta lähettääksesi ensimmäisen kuvasi", - "no_assets_to_show": "Ei näytettäviä kohteita", - "no_cast_devices_found": "Cast-laitteita ei löytynyt", - "no_checksum_local": "Ei tarkistussummaa - paikallista sisältöä ei voida hakea", - "no_checksum_remote": "Ei tarkistussummaa - etänä olevaa sisältöä ei voida hakea", - "no_devices": "Ei valtuutettuja laitteita", - "no_duplicates_found": "Kaksoiskappaleita ei löytynyt.", - "no_exif_info_available": "EXIF-tietoa ei saatavilla", - "no_explore_results_message": "Lataa lisää kuvia tutkiaksesi kokoelmaasi.", - "no_favorites_message": "Lisää suosikkeja löytääksesi nopeasti parhaat kuvasi ja videosi", - "no_filters_added": "Suodattimia ei ole vielä lisätty", - "no_libraries_message": "Luo ulkoinen kirjasto nähdäksesi valokuvasi ja videot", - "no_local_assets_found": "Paikallista sisältöä ei löytynyt tällä tarkistussummalla", - "no_location_set": "Ei sijaintia asetettuna", - "no_locked_photos_message": "Kuvat ja videot lukitussa kansiossa ovat piilotettuja, eivätkä ne näy selatessasi tai etsiessäsi kirjastoasi.", - "no_name": "Ei nimeä", - "no_notifications": "Ei ilmoituksia", - "no_people_found": "Ei vastaavia henkilöitä", - "no_places": "Ei paikkoja", - "no_remote_assets_found": "Etänä olevaa sisältöä ei löytynyt tällä tarkistussummalla", - "no_results": "Ei tuloksia", - "no_results_description": "Kokeile synonyymiä tai yleisempää avainsanaa", - "no_shared_albums_message": "Luo albumi, jotta voit jakaa kuvia ja videoita toisille", - "no_uploads_in_progress": "Ei käynnissä olevia latauksia", - "not_allowed": "Ei sallittu", - "not_available": "N/A", - "not_in_any_album": "Ei yhdessäkään albumissa", - "not_selected": "Ei valittu", - "note_apply_storage_label_to_previously_uploaded assets": "Huom: Jotta voit soveltaa tallennustunnistetta aiemmin ladattuihin kohteisiin, suorita", - "notes": "Muistiinpanot", - "nothing_here_yet": "Ei vielä mitään", - "notification_permission_dialog_content": "Ottaaksesi ilmoitukset käyttöön, siirry asetuksiin ja valitse 'salli'.", - "notification_permission_list_tile_content": "Myönnä käyttöoikeus ottaaksesi ilmoitukset käyttöön.", - "notification_permission_list_tile_enable_button": "Ota ilmoitukset käyttöön", - "notification_permission_list_tile_title": "Ilmoitusten käyttöoikeus", - "notification_toggle_setting_description": "Ota sähköposti-ilmoitukset käyttöön", - "notifications": "Ilmoitukset", - "notifications_setting_description": "Hallitse ilmoituksia", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium-määritystyökalu", - "obtainium_configurator_instructions": "Käytä Obtainiumia asentaaksesi ja päivittääksesi Android-sovelluksen suoraan Immichin GitHubin julkaisukanavasta. Luo API-avain ja valitse variantti luodaksesi Obtainium-määrityslinkin", - "ocr": "OCR (Tekstintunnistus)", - "official_immich_resources": "Viralliset Immich-resurssit", - "offline": "Offline", - "offset": "Ero", - "ok": "Ok", - "oldest_first": "Vanhin ensin", - "on_this_device": "Laitteella", - "onboarding": "Käyttöönotto", - "onboarding_locale_description": "Valitse haluamasi kieli. Voit muuttaa kieliasetuksia myöhemmin asetuksista.", - "onboarding_privacy_description": "Seuraavat (valinnaiset) ominaisuudet perustuvat ulkoisiin palveluihin ja ne voidaan milloin tahansa poistaa käytöstä asetuksista.", - "onboarding_server_welcome_description": "Määritellään seuraavaksi järjestelmäsi muutamalla yleisellä asetuksella.", - "onboarding_theme_description": "Valitse väriteema istunnollesi. Voit muuttaa tämän myöhemmin asetuksistasi.", - "onboarding_user_welcome_description": "Aloitetaan!", - "onboarding_welcome_user": "Tervetuloa {user}", - "online": "Online", - "only_favorites": "Vain suosikit", - "open": "Avaa", - "open_in_map_view": "Avaa karttanäkymässä", - "open_in_openstreetmap": "Avaa OpenStreetMapissa", - "open_the_search_filters": "Avaa hakusuodattimet", - "options": "Vaihtoehdot", - "or": "tai", - "organize_into_albums": "Järjestä albumeihin", - "organize_into_albums_description": "Siirrä olemassa olevat kuvat albumeihin käyttäen nykyisiä synkronointiasetuksia", - "organize_your_library": "Järjestele kirjastosi", - "original": "alkuperäinen", - "other": "Muut", - "other_devices": "Toiset laitteet", - "other_entities": "Muut entiteetit", - "other_variables": "Muut muuttujat", - "owned": "Omistettu", - "owner": "Omistaja", - "page": "Sivu", - "partner": "Kumppani", - "partner_can_access": "{partner} voi päästä", - "partner_can_access_assets": "Kaikki valokuvasi ja videosi, lukuun ottamatta arkistoituja ja poistettuja", - "partner_can_access_location": "Sijainti, jossa kuvasi on otettu", - "partner_list_user_photos": "Käyttäjän {user} kuvat", - "partner_list_view_all": "Näytä kaikki", - "partner_page_empty_message": "Kuviasi ei ole vielä jaettu kenenkään kumppanin kanssa.", - "partner_page_no_more_users": "Ei enempää käyttäjiä lisättäväksi", - "partner_page_partner_add_failed": "Kumppanin lisääminen epäonnistui", - "partner_page_select_partner": "Valitse kumppani", - "partner_page_shared_to_title": "Jaettu henkilöille", - "partner_page_stop_sharing_content": "{partner} ei voi enää käyttää kuviasi.", - "partner_sharing": "Kumppanijako", - "partners": "Kumppanit", - "password": "Salasana", - "password_does_not_match": "Salasanat eivät täsmää", - "password_required": "Salasana vaaditaan", - "password_reset_success": "Salasanan nollaus onnistui", - "past_durations": { - "days": "Viime {days, plural, one {päivä} other {# päivää}}", - "hours": "Viime {hours, plural, one {tunti} other {# tuntia}}", - "years": "{years, plural, one {Viimeisin vuosi} other {Viimeiset # vuotta}}" - }, - "path": "Polku", - "pattern": "Kaava", - "pause": "Tauko", - "pause_memories": "Pysäytä muistot", - "paused": "Tauotettu", - "pending": "Odottaa", - "people": "Ihmiset", - "people_edits_count": "Muokattu {count, plural, one {# henkilö} other {# henkilöä}}", - "people_feature_description": "Selataan valokuvia ja videoita, jotka on ryhmitelty henkilöiden mukaan", - "people_sidebar_description": "Näytä linkki Henkilöihin sivupalkissa", - "permanent_deletion_warning": "Pysyvän poiston varoitus", - "permanent_deletion_warning_setting_description": "Näytä varoitus, kun poistat kohteita pysyvästi", - "permanently_delete": "Poista pysyvästi", - "permanently_delete_assets_count": "Poista pysyvästi {count, plural, one {kohde} other {kohteita}}", - "permanently_delete_assets_prompt": "Haluatko varmasti poistaa pysyvästi {count, plural, one {tämän kohteen?} other {nämä # kohteet?}} Tämä poistaa {count, plural, one {sen} other {ne}} myös kaikista albumeista.", - "permanently_deleted_asset": "Media poistettu pysyvästi", - "permanently_deleted_assets_count": "{count, plural, one {# media} other {# mediaa}} poistettu pysyvästi", - "permission": "Käyttöoikeus", - "permission_empty": "Käyttöoikeus ei saa olla tyhjä", - "permission_onboarding_back": "Takaisin", - "permission_onboarding_continue_anyway": "Jatka silti", - "permission_onboarding_get_started": "Aloittaminen", - "permission_onboarding_go_to_settings": "Siirry asetuksiin", - "permission_onboarding_permission_denied": "Kielletty käyttöoikeus. Käyttääksesi Immichiä, myönnä oikeus kuviin ja videoihin asetuksista.", - "permission_onboarding_permission_granted": "Käyttöoikeus myönnetty! Kaikki valmista.", - "permission_onboarding_permission_limited": "Rajoitettu käyttöoikeus. Salliaksesi Immichin varmuuskopioida ja hallita koko kuvakirjastoasi, myönnä oikeus kuviin ja videoihin asetuksista.", - "permission_onboarding_request": "Immich vaatii käyttöoikeuden kuvien ja videoiden käyttämiseen.", - "person": "Henkilö", - "person_age_months": "{months, plural, one {# kuukauden} other {# kuukauden}} ikäinen", - "person_age_year_months": "1 vuosi ja {months, plural, one {# kuukauden} other {# kuukautta}} vanha", - "person_age_years": "{years, plural, other {# vuotta}} vanha", - "person_birthdate": "Syntynyt {date}", - "person_hidden": "{name}{hidden, select, true { (piilotettu)} other {}}", - "person_recognized": "Henkilö tunnistettu", - "person_selected": "Henkilö valittu", - "photo_shared_all_users": "Näyttää että olet jakanut kuvasi kaikkien käyttäjien kanssa, tai sinulla ei ole käyttäjää kenelle jakaa.", - "photos": "Kuvat", - "photos_and_videos": "Kuvat ja videot", - "photos_count": "{count, plural, one {{count, number} Kuva} other {{count, number} kuvaa}}", - "photos_from_previous_years": "Kuvia edellisiltä vuosilta", - "photos_only": "Vain kuvat", - "pick_a_location": "Valitse sijainti", - "pick_custom_range": "Mukautettu väli", - "pick_date_range": "Valitse päivämäärien väli", - "pin_code_changed_successfully": "PIN-koodin vaihto onnistui", - "pin_code_reset_successfully": "PIN-koodin nollaus onnistui", - "pin_code_setup_successfully": "PIN-koodin asettaminen onnistui", - "pin_verification": "PIN-koodin vahvistus", - "place": "Paikka", - "places": "Paikat", - "places_count": "{count, plural, one {{count, number} Paikka} other {{count, number} Paikkaa}}", - "play": "Toista", - "play_memories": "Toista muistot", - "play_motion_photo": "Toista Liikekuva", - "play_or_pause_video": "Toista tai keskeytä video", - "play_original_video": "Toista alkuperäinen video", - "play_original_video_setting_description": "Suosi alkuperäisten videoiden toistoa transkoodattujen videoiden sijaan. Jos alkuperäinen tiedosto ei ole yhteensopiva, se ei välttämättä toistu oikein.", - "play_transcoded_video": "Toista transkoodattu video", - "please_auth_to_access": "Ole hyvä ja kirjaudu sisään", - "port": "Portti", - "preferences_settings_subtitle": "Hallitse sovelluksen asetuksia", - "preferences_settings_title": "Asetukset", - "preparing": "Valmistellaan", - "preset": "Asetus", - "preview": "Esikatselu", - "previous": "Edellinen", - "previous_memory": "Edellinen muisto", - "previous_or_next_day": "Päivä seuraava/edellinen", - "previous_or_next_month": "Kuukausi seuraava/edellinen", - "previous_or_next_photo": "Kuva seuraava/edellinen", - "previous_or_next_year": "Vuosi seuraava/edellinen", - "primary": "Ensisijainen", - "privacy": "Tietosuoja", - "profile": "Profiili", - "profile_drawer_app_logs": "Lokit", - "profile_drawer_client_server_up_to_date": "Asiakasohjelma ja palvelin ovat ajan tasalla", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Muokkaus on estetty. Paina käyttäjäkuvaketta pitkään palataksesi muokkaustilaan.", - "profile_image_of_user": "Käyttäjän {user} profiilikuva", - "profile_picture_set": "Profiilikuva asetettu.", - "public_album": "Julkinen albumi", - "public_share": "Julkinen jako", - "purchase_account_info": "Tukija", - "purchase_activated_subtitle": "Kiitos Immichin ja avoimen lähdekoodin ohjelmiston tukemisesta", - "purchase_activated_time": "Aktivoitu {date}", - "purchase_activated_title": "Avaimesi on aktivoitu onnistuneesti", - "purchase_button_activate": "Aktivoi", - "purchase_button_buy": "Osta", - "purchase_button_buy_immich": "Osta Immich", - "purchase_button_never_show_again": "Älä näytä koskaan uudelleen", - "purchase_button_reminder": "Muistuta minua 30 päivän kuluessa", - "purchase_button_remove_key": "Poista avain", - "purchase_button_select": "Valitse", - "purchase_failed_activation": "Aktivointi epäonnistui! Tarkista sähköpostisi oikean tuoteavaimen varalta!", - "purchase_individual_description_1": "Yksittäiselle henkilölle", - "purchase_individual_description_2": "Tukijan tila", - "purchase_individual_title": "Yksittäinen", - "purchase_input_suggestion": "Onko sinulla tuoteavain? Syötä avain alle", - "purchase_license_subtitle": "Osta Immich tukeaksesi palvelun jatkuvaa kehittämistä", - "purchase_lifetime_description": "Elinikäinen osto", - "purchase_option_title": "OSTOVAIHTOEHDOT", - "purchase_panel_info_1": "Immichin rakentaminen vie paljon aikaa ja vaivannäköä, ja meillä on kokopäiväisiä insinöörejä työskentelemässä sen parissa, jotta voimme tehdä siitä mahdollisimman hyvän. Missiomme on, että avoimen lähdekoodin ohjelmistosta ja eettisistä liiketoimintakäytännöistä tulee kestävä tulonlähde kehittäjille, sekä luoda yksityisyyttä kunnioittava ekosysteemi, jossa on todellisia vaihtoehtoja hyväksikäyttöön perustuville pilvipalveluille.", - "purchase_panel_info_2": "Koska olemme sitoutuneet siihen, ettemme lisää maksumuuria, tämä osto ei anna sinulle mitään lisäominaisuuksia Immichissa. Luotamme kaltaisiisi käyttäjiin tukeaksemme Immichin jatkuvaa kehittämistä.", - "purchase_panel_title": "Tue projektia", - "purchase_per_server": "Per palvelin", - "purchase_per_user": "Per käyttäjä", - "purchase_remove_product_key": "Poista Tuoteavain", - "purchase_remove_product_key_prompt": "Haluatko varmasti poistaa tuoteavaimen?", - "purchase_remove_server_product_key": "Poista palvelimen tuoteavain", - "purchase_remove_server_product_key_prompt": "Haluatko varmasti poistaa palvelimen tuoteavaimen?", - "purchase_server_description_1": "Koko palvelimelle", - "purchase_server_description_2": "Tukijan tila", - "purchase_server_title": "Palvelin", - "purchase_settings_server_activated": "Palvelimen tuoteavainta hallinnoi ylläpitäjä", - "query_asset_id": "Kysy sisällön ID:tä", - "queue_status": "Jonossa {count}/{total}", - "rating": "Tähtiarvostelu", - "rating_clear": "Tyhjennä arvostelu", - "rating_count": "{count, plural, one {# tähti} other {# tähteä}}", - "rating_description": "Näytä EXIF-arvosana lisätietopaneelissa", - "reaction_options": "Reaktioasetukset", - "read_changelog": "Lue muutosloki", - "readonly_mode_disabled": "Muokkaustila päällä", - "readonly_mode_enabled": "Muokkaustila pois päältä", - "ready_for_upload": "Valmis lähetystä varten", - "reassign": "Määritä uudelleen", - "reassigned_assets_to_existing_person": "Uudelleen määritetty {count, plural, one {# kohde} other {# kohdetta}} {name, select, null {olemassa olevalle henkilölle} other {{name}}}", - "reassigned_assets_to_new_person": "Määritetty {count, plural, one {# media} other {# mediaa}} uudelle henkilölle", - "reassing_hint": "Määritä valitut mediat käyttäjälle", - "recent": "Viimeisin", - "recent-albums": "Viimeisimmät albumit", - "recent_searches": "Edelliset haut", - "recently_added": "Viimeksi lisätty", - "recently_added_page_title": "Viimeksi lisätyt", - "recently_taken": "Viimeksi otettu", - "recently_taken_page_title": "Viimeksi Otettu", - "refresh": "Päivitä", - "refresh_encoded_videos": "Päivitä enkoodatut videot", - "refresh_faces": "Päivitä kasvot", - "refresh_metadata": "Päivitä metadata", - "refresh_thumbnails": "Päivitä pikkukuvat", - "refreshed": "Päivitetty", - "refreshes_every_file": "Lukee uudelleen kaikki olemassa olevat ja uudet tiedostot", - "refreshing_encoded_video": "Päivitetään enkoodattu video", - "refreshing_faces": "Päivitetään kasvoja", - "refreshing_metadata": "Päivitetään metadata", - "regenerating_thumbnails": "Regeneroidaan pikkukuvia", - "remote": "Etä", - "remote_assets": "Etäkohteet", - "remote_media_summary": "Yhteenveto etänä olevasta mediasta", - "remove": "Poista", - "remove_assets_album_confirmation": "Haluatko varmasti poistaa {count, plural, one {# median} other {# mediaa}} albumista?", - "remove_assets_shared_link_confirmation": "Haluatko varmasti poistaa {count, plural, one {# median} other {# mediaa}} tästä jakolinkistä?", - "remove_assets_title": "Poistetaanko?", - "remove_custom_date_range": "Poista aikaväliltä", - "remove_deleted_assets": "Poista Offline-tiedostot", - "remove_from_album": "Poista albumista", - "remove_from_album_action_prompt": "{count} poistettu albumista", - "remove_from_favorites": "Poista suosikeista", - "remove_from_lock_folder_action_prompt": "{count} poistettu lukitusta albumista", - "remove_from_locked_folder": "Poista lukitusta kansiosta", - "remove_from_locked_folder_confirmation": "Haluatko varmasti siirtää nämä kuvat ja videot pois lukitusta kansiosta? Ne näkyvät sen jälkeen kirjastossasi.", - "remove_from_shared_link": "Poista jakolinkistä", - "remove_memory": "Tyhjennä muisti", - "remove_photo_from_memory": "Poista kuva muistista", - "remove_tag": "Poista tunniste", - "remove_url": "Poista URL", - "remove_user": "Poista käyttäjä", - "removed_api_key": "API-avain {name} poistettu", - "removed_from_archive": "Poistettu arkistosta", - "removed_from_favorites": "Poistettu suosikeista", - "removed_from_favorites_count": "{count, plural, other {Poistettu #}} suosikeista", - "removed_memory": "Poistettu muistista", - "removed_photo_from_memory": "Kuva poistettu muistista", - "removed_tagged_assets": "Poistettu tunniste {count, plural, one {# kohteesta} other {# kohteesta}}", - "rename": "Nimeä uudelleen", - "repair": "Korjaa", - "repair_no_results_message": "Seuraamattomat ja puuttuvat tiedostot näkyvät täällä", - "replace_with_upload": "Korvaa tiedostolla", - "repository": "Tietovarasto", - "require_password": "Vaadi salasana", - "require_user_to_change_password_on_first_login": "Vaadi käyttäjää vaihtamaan salasana seuraavalla kirjautumiskerralla", - "rescan": "Skannaa uudelleen", - "reset": "Nollaa", - "reset_password": "Nollaa salasana", - "reset_people_visibility": "Nollaa henkilöiden näkyvyysasetukset", - "reset_pin_code": "Nollaa PIN-koodi", - "reset_pin_code_description": "Jos olet unohtanut PIN-koodisi, ole yhteydessä järjestelmänvalvojaan", - "reset_pin_code_success": "PIN-koodi nollattu onnistuneesti", - "reset_pin_code_with_password": "Voit aina nollata PIN-koodisi salasanan avulla", - "reset_sqlite": "Nollaa SQLite Tietokanta", - "reset_sqlite_confirmation": "Haluatko varmasti nollata SQLite tietokannan? Sinun tulee kirjautua sovelluksesta ulos ja takaisin sisään uudelleensynkronoidaksesi datan", - "reset_sqlite_success": "SQLite Tietokanta nollattu onnistuneesti", - "reset_to_default": "Palauta oletusasetukset", - "resolution": "Resoluutio", - "resolve_duplicates": "Ratkaise kaksoiskappaleet", - "resolved_all_duplicates": "Kaikki kaksoiskappaleet selvitetty", - "restore": "Palauta", - "restore_all": "Palauta kaikki", - "restore_trash_action_prompt": "{count} palautettu roskakorista", - "restore_user": "Palauta käyttäjä", - "restored_asset": "Palautettu media", - "resume": "Jatka", - "resume_paused_jobs": "Jatka {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Yritä latausta uudelleen", - "review_duplicates": "Tarkastele kaksoiskappaleita", - "review_large_files": "Tarkista suuret tiedostot", - "role": "Rooli", - "role_editor": "Muokkaaja", - "role_viewer": "Katsoja", - "running": "Käynnissä", - "save": "Tallenna", - "save_to_gallery": "Tallenna galleriaan", - "saved": "Tallennettu", - "saved_api_key": "API-avain tallennettu", - "saved_profile": "Profiili tallennettu", - "saved_settings": "Asetukset tallennettu", - "say_something": "Sano jotain", - "scaffold_body_error_occurred": "Tapahtui virhe", - "scan_all_libraries": "Skannaa kaikki kirjastot", - "scan_library": "Skannaa", - "scan_settings": "Skannausasetukset", - "scanning_for_album": "Etsitään albumia...", - "search": "Haku", - "search_albums": "Etsi albumeita", - "search_by_context": "Etsi kontekstin perusteella", - "search_by_description": "Etsi kuvauksen perusteella", - "search_by_description_example": "Vaelluspäivä Sapassa", - "search_by_filename": "Hae tiedostonimen tai -päätteen mukaan", - "search_by_filename_example": "esim. IMG_1234.JPG tai PNG", - "search_by_ocr": "Etsi tekstintunnistuksella (OCR)", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Etsi linssin mallia...", - "search_camera_make": "Etsi kameramerkkiä...", - "search_camera_model": "Etsi kameramallia...", - "search_city": "Etsi kaupunkia...", - "search_country": "Etsi maata...", - "search_filter_apply": "Käytä", - "search_filter_camera_title": "Valitse kameratyyppi", - "search_filter_date": "Päivämäärä", - "search_filter_date_interval": "{start} – {end}", - "search_filter_date_title": "Valitse aikaväli", - "search_filter_display_option_not_in_album": "Ei kuulu albumiin", - "search_filter_display_options": "Näyttöasetukset", - "search_filter_filename": "Etsi tiedostonimellä", - "search_filter_location": "Sijainti", - "search_filter_location_title": "Valitse sijainti", - "search_filter_media_type": "Mediatyyppi", - "search_filter_media_type_title": "Valitse mediatyyppi", - "search_filter_ocr": "Hae tekstintunnistuksella (OCR)", - "search_filter_people_title": "Valitse ihmiset", - "search_for": "Hae", - "search_for_existing_person": "Etsi olemassa olevaa henkilöä", - "search_no_more_result": "Ei enää tuloksia", - "search_no_people": "Ei henkilöitä", - "search_no_people_named": "Ei \"{name}\" nimisiä henkilöitä", - "search_no_result": "Ei tuloksia, kokeile toista hakusanaa tai -yhdistelmää", - "search_options": "Hakuvaihtoehdot", - "search_page_categories": "Kategoriat", - "search_page_motion_photos": "Liikekuvat", - "search_page_no_objects": "Objektitietoja ei ole saatavilla", - "search_page_no_places": "Paikkatietoja ei ole saatavilla", - "search_page_screenshots": "Näyttökuvat", - "search_page_search_photos_videos": "Hae kuvia tai videoita", - "search_page_selfies": "Selfiet", - "search_page_things": "Asiat", - "search_page_view_all_button": "Näytä kaikki", - "search_page_your_activity": "Toimintasi", - "search_page_your_map": "Sinun karttasi", - "search_people": "Etsi ihmisiä", - "search_places": "Etsi paikkoja", - "search_rating": "Hae luokituksen mukaan...", - "search_result_page_new_search_hint": "Uusi haku", - "search_settings": "Etsi asetuksia", - "search_state": "Etsi maakuntaa...", - "search_suggestion_list_smart_search_hint_1": "Älykäs haku on oletuksena käytössä. Käytä metatietojen etsimiseen syntaksia ", - "search_suggestion_list_smart_search_hint_2": "m:hakusana", - "search_tags": "Etsi tunnisteita...", - "search_timezone": "Etsi aikavyöhyke...", - "search_type": "Etsinnän tyyppi", - "search_your_photos": "Etsi kuvia", - "searching_locales": "Etsitään lokaaleja...", - "second": "Toinen", - "see_all_people": "Näytä kaikki henkilöt", - "select": "Valitse", - "select_album": "Valitse albumi", - "select_album_cover": "Valitse albumin kansi", - "select_albums": "Valitse albumit", - "select_all": "Valitse kaikki", - "select_all_duplicates": "Valitse kaikki kaksoiskappaleet", - "select_all_in": "Valitse kaikki {group}", - "select_avatar_color": "Valitse avatarin väri", - "select_face": "Valitse kasvo", - "select_featured_photo": "Valitse esittelykuva", - "select_from_computer": "Valitse koneeltasi", - "select_keep_all": "Valitse pidä kaikki", - "select_library_owner": "Valitse kirjaston omistaja", - "select_new_face": "Valitse uudet kasvot", - "select_person": "Valitse henkilö", - "select_person_to_tag": "Valitse henkilö, jonka haluat merkitä", - "select_photos": "Valitse kuvat", - "select_trash_all": "Valitse kaikki roskakoriin", - "select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui", - "selected": "Valittu", - "selected_count": "{count, plural, other {# valittu}}", - "selected_gps_coordinates": "Valitut GPS-koordinaatit", - "send_message": "Lähetä viesti", - "send_welcome_email": "Lähetä tervetuloviesti", - "server_endpoint": "Palvelinosoite", - "server_info_box_app_version": "Sovelluksen versio", - "server_info_box_server_url": "Palvelimen URL-osoite", - "server_offline": "Palvelin Offline-tilassa", - "server_online": "Palvelin Online-tilassa", - "server_privacy": "Palvelimen tietosuoja", - "server_restarting_description": "Tämä sivu latautuu uudelleen hetken kuluttua.", - "server_restarting_title": "Palvelin käynnistyy uudelleen", - "server_stats": "Palvelimen tilastot", - "server_update_available": "Palvelimeen on saatavilla päivitys", - "server_version": "Palvelimen versio", - "set": "Aseta", - "set_as_album_cover": "Aseta albumin kanneksi", - "set_as_featured_photo": "Käytä esittelykuvana", - "set_as_profile_picture": "Aseta profiilikuvaksi", - "set_date_of_birth": "Aseta syntymäaika", - "set_profile_picture": "Aseta profiilikuva", - "set_slideshow_to_fullscreen": "Näytä diaesitys koko ruudulla", - "set_stack_primary_asset": "Aseta pääkohteeksi", - "setting_image_viewer_help": "Kuvaa katseltaessa ensin ladataan pikkukuva, sitten keskilaatuinen pikkukuva (jos käytössä) ja lopuksi alkuperäinen (jos käytössä).", - "setting_image_viewer_original_subtitle": "Ota käyttöön ladataksesi alkuperäinen täysitarkkuuksinen kuva (suuri!). Poista käytöstä vähentääksesi datan käyttöä (sekä verkossa että laitteen välimuistissa).", - "setting_image_viewer_original_title": "Lataa alkuperäinen kuva", - "setting_image_viewer_preview_subtitle": "Ota käyttöön ladataksesi keskitarkkuuksinen kuva. Poista käytöstä ladataksesi alkuperäinen kuva tai käyttääksesi vain esikatselukuvaa.", - "setting_image_viewer_preview_title": "Lataa esikatselukuva", - "setting_image_viewer_title": "Kuvat", - "setting_languages_apply": "Käytä", - "setting_languages_subtitle": "Vaihda sovelluksen kieli", - "setting_notifications_notify_failures_grace_period": "Ilmoita taustalla tapahtuvista varmuuskopiointivirheistä: {duration}", - "setting_notifications_notify_hours": "{count} tuntia", - "setting_notifications_notify_immediately": "heti", - "setting_notifications_notify_minutes": "{count} minuuttia", - "setting_notifications_notify_never": "ei koskaan", - "setting_notifications_notify_seconds": "{count} sekuntia", - "setting_notifications_single_progress_subtitle": "Yksityiskohtainen tieto palvelimelle lähettämisen edistymisestä kohteittain", - "setting_notifications_single_progress_title": "Näytä taustavarmuuskopioinnin edistyminen", - "setting_notifications_subtitle": "Ilmoitusasetusten määrittely", - "setting_notifications_total_progress_subtitle": "Lähetyksen yleinen edistyminen (kohteita lähetetty/yhteensä)", - "setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen", - "setting_video_viewer_auto_play_subtitle": "Aloita videoiden toistaminen automaattisesti kun ne avataan", - "setting_video_viewer_auto_play_title": "Toista videoita automaattisesti", - "setting_video_viewer_looping_title": "Silmukkatoisto", - "setting_video_viewer_original_video_subtitle": "Kun toistat videota palvelimelta, toista alkuperäinen, vaikka transkoodattu versio olisi saatavilla. Tämä voi johtaa puskurointiin. Paikalliset videot toistetaan aina alkuperäislaadulla.", - "setting_video_viewer_original_video_title": "Pakota alkuperäinen video", - "settings": "Asetukset", - "settings_require_restart": "Käynnistä Immich uudelleen ottaaksesi tämä asetus käyttöön", - "settings_saved": "Asetukset tallennettu", - "setup_pin_code": "Määritä PIN-koodi", - "share": "Jaa", - "share_action_prompt": "Jaettu {count} kohdetta", - "share_add_photos": "Lisää kuvia", - "share_assets_selected": "{count} valittu", - "share_dialog_preparing": "Valmistellaan...", - "share_link": "Jaa linkki", - "shared": "Jaettu", - "shared_album_activities_input_disable": "Kommentointi on kytketty pois päältä", - "shared_album_activity_remove_content": "Haluatko poistaa tämän aktiviteetin?", - "shared_album_activity_remove_title": "Poista aktiviteetti", - "shared_album_section_people_action_error": "Virhe poistuttaessa/poistaessa kohdetta albumista", - "shared_album_section_people_action_leave": "Poista käyttäjä albumista", - "shared_album_section_people_action_remove_user": "Poista käyttäjä albumista", - "shared_album_section_people_title": "HENKILÖT", - "shared_by": "Jakanut", - "shared_by_user": "Käyttäjän {user} jakama", - "shared_by_you": "Sinun jakamasi", - "shared_from_partner": "Kumppanin {partner} kuvia", - "shared_intent_upload_button_progress_text": "{current} / {total} Lähetetty", - "shared_link_app_bar_title": "Jaetut linkit", - "shared_link_clipboard_copied_massage": "Kopioitu leikepöydältä", - "shared_link_clipboard_text": "Linkki: {link}\nSalasana: {password}", - "shared_link_create_error": "Jaetun linkin luomisessa tapahtui virhe", - "shared_link_custom_url_description": "Avaa tämä jaettu linkki mukautetulla URL-osoitteella", - "shared_link_edit_description_hint": "Lisää jaon kuvaus", - "shared_link_edit_expire_after_option_day": "1 päivä", - "shared_link_edit_expire_after_option_days": "{count} päivää", - "shared_link_edit_expire_after_option_hour": "1 tunti", - "shared_link_edit_expire_after_option_hours": "{count} tuntia", - "shared_link_edit_expire_after_option_minute": "1 minuutti", - "shared_link_edit_expire_after_option_minutes": "{count} minuuttia", - "shared_link_edit_expire_after_option_months": "{count} kuukautta", - "shared_link_edit_expire_after_option_year": "{count} vuosi", - "shared_link_edit_password_hint": "Syötä jaon salasana", - "shared_link_edit_submit_button": "Päivitä linkki", - "shared_link_error_server_url_fetch": "Palvelimen URL-osoitetta ei voitu hakea", - "shared_link_expires_day": "Vanhenee {count} päivässä", - "shared_link_expires_days": "Vanhenee {count} päivässä", - "shared_link_expires_hour": "Vanhenee {count} tunnissa", - "shared_link_expires_hours": "Vanhenee {count} tunnissa", - "shared_link_expires_minute": "Vanhenee {count} minuutissa", - "shared_link_expires_minutes": "Vanhenee {count} minuutissa", - "shared_link_expires_never": "Voimassaolo päättyy ∞", - "shared_link_expires_second": "Vanhenee {count} sekunnissa", - "shared_link_expires_seconds": "Vanhenee {count} sekunnissa", - "shared_link_individual_shared": "Yksilöllisesti jaettu", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Hallitse jaettuja linkkejä", - "shared_link_options": "Jaetun linkin vaihtoehdot", - "shared_link_password_description": "Vaadi salasana tämän jakolinkin käyttämiseksi", - "shared_links": "Jaetut linkit", - "shared_links_description": "Jaa kuvia ja videoita linkin avulla", - "shared_photos_and_videos_count": "{assetCount, plural, other {# jaettua kuvaa ja videota.}}", - "shared_with_me": "Jaettu minulle", - "shared_with_partner": "Jaa kumppanin {partner} kanssa", - "sharing": "Jakaminen", - "sharing_enter_password": "Nähdäksesi sivun sinun tulee antaa salasana.", - "sharing_page_album": "Jaetut albumit", - "sharing_page_description": "Luo jaettuja albumeja jakaaksesi kuvia ja videoita läheisillesi.", - "sharing_page_empty_list": "TYHJÄ LISTA", - "sharing_sidebar_description": "Näytä jakamislinkki sivupalkissa", - "sharing_silver_appbar_create_shared_album": "Luo jaettu albumi", - "sharing_silver_appbar_share_partner": "Jaa kumppanille", - "shift_to_permanent_delete": "Paina ⇧ poistaaksesi media pysyvästi", - "show_album_options": "Näytä albumin asetukset", - "show_albums": "Näytä albumit", - "show_all_people": "Näytä kaikki henkilöt", - "show_and_hide_people": "Näytä / piilota henkilöitä", - "show_file_location": "Näytä tiedostosijainti", - "show_gallery": "Näytä galleria", - "show_hidden_people": "Näytä piilotetut ihmiset", - "show_in_timeline": "Näytä aikajanalla", - "show_in_timeline_setting_description": "Näytä tämän käyttäjän kuvat ja videot aikajanallasi", - "show_keyboard_shortcuts": "Näytä näppäinoikotiet", - "show_metadata": "Näytä metadata", - "show_or_hide_info": "Näytä tai piilota tiedot", - "show_password": "Näytä salasana", - "show_person_options": "Näytä henkilöasetukset", - "show_progress_bar": "Näytä eteneminen", - "show_schema": "Näytä skeema", - "show_search_options": "Näytä hakuvaihtoehdot", - "show_shared_links": "Näytä jaetut linkit", - "show_slideshow_transition": "Näytä diaesitys siirtymä", - "show_supporter_badge": "Kannattajan merkki", - "show_supporter_badge_description": "Näytä kannattajan merkki", - "show_text_recognition": "Näytä tekstin tunnistus", - "show_text_search_menu": "Näytä tekstihakuvalikko", - "shuffle": "Sekoita", - "sidebar": "Sivupalkki", - "sidebar_display_description": "Näytä linkki näkymään sivupalkissa", - "sign_out": "Kirjaudu ulos", - "sign_up": "Rekisteröidy", - "size": "Koko", - "skip_to_content": "Siirry sisältöön", - "skip_to_folders": "Siirry kansioihin", - "skip_to_tags": "Siirry tunnisteisiin", - "slideshow": "Diaesitys", - "slideshow_repeat": "Kertaa diaesitys", - "slideshow_repeat_description": "Palaa takaisin alkuun diaesityksen päättyessä", - "slideshow_settings": "Diaesityksen asetukset", - "sort_albums_by": "Järjestä albumit...", - "sort_created": "Luontipäivä", - "sort_items": "Tietueiden määrä", - "sort_modified": "Muokkauspäivä", - "sort_newest": "Uusin kuva", - "sort_oldest": "Vanhin kuva", - "sort_people_by_similarity": "Lajittele ihmiset samankaltaisuuden mukaan", - "sort_recent": "Tuorein kuva", - "sort_title": "Otsikko", - "source": "Lähdekoodi", - "stack": "Pinoa", - "stack_action_prompt": "{count} pinottu", - "stack_duplicates": "Pinoa kaksoiskappaleet", - "stack_select_one_photo": "Valitse yksi pääkuva pinolle", - "stack_selected_photos": "Pinoa valitut kuvat", - "stacked_assets_count": "Pinottu {count, plural, one {# media} other {# mediaa}}", - "stacktrace": "Vianetsintätiedot", - "start": "Aloita", - "start_date": "Alkupäivä", - "start_date_before_end_date": "Aloituspäivämäärän pitää olla ennen lopetuspäivämäärää", - "state": "Maakunta", - "status": "Tila", - "stop_casting": "Lopeta suoratoisto", - "stop_motion_photo": "Pysäytä liikkuva kuva", - "stop_photo_sharing": "Lopetetaanko kuvien jakaminen?", - "stop_photo_sharing_description": "{partner} ei enää pääse kuviisi.", - "stop_sharing_photos_with_user": "Päätä kuviesi jakaminen tämän käyttäjän kanssa", - "storage": "Tallennustila", - "storage_label": "Tallennustilan nimike", - "storage_quota": "Tallennuskiintiö", - "storage_usage": "{used} / {available} käytetty", - "submit": "Lähetä", - "success": "Onnistui", - "suggestions": "Ehdotukset", - "sunrise_on_the_beach": "Auringonnousu rannalla", - "support": "Tuki", - "support_and_feedback": "Tuki ja palaute", - "support_third_party_description": "Immich-asennuksesi on pakattu kolmannen osapuolen toimesta. Kohtaamasi ongelmat saattavat johtua tästä paketista, joten ilmoita niistä ensisijaisesti heille alla olevien linkkien kautta.", - "swap_merge_direction": "Käännä yhdistämissuunta", - "sync": "Synkronoi", - "sync_albums": "Synkronoi albumit", - "sync_albums_manual_subtitle": "Synkronoi kaikki ladatut videot ja valokuvat valittuihin varmuuskopioalbumeihin", - "sync_local": "Synkronoi paikallinen", - "sync_remote": "Synkronoi etä", - "sync_status": "Synkronoinnin status", - "sync_status_subtitle": "Näytä ja hallinnoi synkronointijärjestelmää", - "sync_upload_album_setting_subtitle": "Luo ja lataa valokuvasi ja videosi valittuihin albumeihin Immichissä", - "tag": "Tunniste", - "tag_assets": "Lisää tunnisteita", - "tag_created": "Luotu tunniste: {tag}", - "tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan", - "tag_not_found_question": "Etkö löydä tunnistetta? Luo uusi tunniste ", - "tag_people": "Merkitse henkilö tunnisteella", - "tag_updated": "Päivitetty tunniste: {tag}", - "tagged_assets": "Tunnistettu {count, plural, one {# kohde} other {# kohdetta}}", - "tags": "Tunnisteet", - "tap_to_run_job": "Napauta suorittaaksesi tehtävän", - "template": "Nimeämismalli", - "text_recognition": "Tekstin tunnistus", - "theme": "Teema", - "theme_selection": "Teeman valinta", - "theme_selection_description": "Aseta vaalea tai tumma tila automaattisesti perustuen selaimesi asetuksiin", - "theme_setting_asset_list_storage_indicator_title": "Näytä tallennustilan ilmaisin kohteiden kuvakkeissa", - "theme_setting_asset_list_tiles_per_row_title": "Kohteiden määrä rivillä ({count})", - "theme_setting_colorful_interface_subtitle": "Levitä pääväri taustalle.", - "theme_setting_colorful_interface_title": "Värikäs käyttöliittymä", - "theme_setting_image_viewer_quality_subtitle": "Säädä kuvien katselun laatua", - "theme_setting_image_viewer_quality_title": "Kuvien katseluohjelman laatu", - "theme_setting_primary_color_subtitle": "Valitse sovelluksen pääväri.", - "theme_setting_primary_color_title": "Väriteema", - "theme_setting_system_primary_color_title": "Käytä järjestelmän väriteemaa", - "theme_setting_system_theme_switch": "Automaattinen (seuraa järjestelmän asetusta)", - "theme_setting_theme_subtitle": "Valitse sovelluksen teema-asetukset", - "theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataus saattaa parantaa lataustehoa, mutta aiheuttaa huomattavasti suuremman verkon kuormituksen", - "theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön", - "they_will_be_merged_together": "Nämä tullaan yhdistämään", - "third_party_resources": "Kolmannen osapuolen resurssit", - "time": "Aika", - "time_based_memories": "Aikaan perustuvat muistot", - "time_based_memories_duration": "Kuvien näyttöaika sekunteina.", - "timeline": "Aikajana", - "timezone": "Aikavyöhyke", - "to_archive": "Arkistoi", - "to_change_password": "Vaihda salasana", - "to_favorite": "Aseta suosikiksi", - "to_login": "Kirjaudu sisään", - "to_multi_select": "usean valitsemiseksi", - "to_parent": "Siirry vanhempaan", - "to_select": "valitsemiseksi", - "to_trash": "Roskakoriin", - "toggle_settings": "Määritä asetukset", - "toggle_theme_description": "Vaihda teemaa", - "total": "Yhteensä", - "total_usage": "Käyttö yhteensä", - "trash": "Roskakori", - "trash_action_prompt": "{count} siirretty roskakoriin", - "trash_all": "Vie kaikki roskakoriin", - "trash_count": "Vie {count, number} roskakoriin", - "trash_delete_asset": "Poista / vie roskakoriin", - "trash_emptied": "Roskakori tyhjennetty", - "trash_no_results_message": "Roskakorissa olevat kuvat ja videot näytetään täällä.", - "trash_page_delete_all": "Poista kaikki", - "trash_page_empty_trash_dialog_content": "Haluatko tyhjentää roskakorin? Kohteet poistetaan lopullisesti Immich:sta", - "trash_page_info": "Roskakorissa olevat kohteet poistetaan pysyvästi {days} päivän kuluttua", - "trash_page_no_assets": "Ei poistettuja kohteita", - "trash_page_restore_all": "Palauta kaikki", - "trash_page_select_assets_btn": "Valitse kohteet", - "trash_page_title": "Roskakori ({count})", - "trashed_items_will_be_permanently_deleted_after": "Roskakorin kohteet poistetaan pysyvästi {days, plural, one {# päivän} other {# päivän}} päästä.", - "trigger": "Laukaisin", - "trigger_description": "Työnkulun aloittava tapahtuma", - "trigger_person_recognized": "Henkilö tunnistettu", - "trigger_person_recognized_description": "Laukaistaan kun henkilö tunnistetaan", - "trigger_type": "Laukaisimen tyyppi", - "troubleshoot": "Vianetsintä", - "type": "Tyyppi", - "unable_to_change_pin_code": "PIN-koodin vaihtaminen epäonnistui", - "unable_to_check_version": "Sovelluksen tai palvelimen versiota ei voitu tarkistaa", - "unable_to_setup_pin_code": "PIN-koodin määrittäminen epäonnistui", - "unarchive": "Palauta arkistosta", - "unarchive_action_prompt": "{count} poistettu arkistosta", - "unarchived_count": "{count, plural, other {# poistettu arkistosta}}", - "undo": "Kumoa", - "unfavorite": "Poista suosikeista", - "unfavorite_action_prompt": "{count} poistettu suosikeista", - "unhide_person": "Poista henkilö piilosta", - "unknown": "Tuntematon", - "unknown_country": "Tuntematon maa", - "unknown_date": "Tuntematon päiväys", - "unknown_year": "Tuntematon vuosi", - "unlimited": "Rajoittamaton", - "unlink_motion_video": "Poista liikevideon linkitys", - "unlink_oauth": "Poista OAuth-linkitys", - "unlinked_oauth_account": "Linkittämätön OAuth-tili", - "unmute_memories": "Poista muistojen mykistys", - "unnamed_album": "Nimetön albumi", - "unnamed_album_delete_confirmation": "Haluatko varmasti poistaa tämän albumin?", - "unnamed_share": "Nimetön jako", - "unsaved_change": "Tallentamaton muutos", - "unselect_all": "Poista valinnat", - "unselect_all_duplicates": "Poista kaikkien kaksoiskappaleiden valinta", - "unselect_all_in": "Poista kaikki valinnat {group}", - "unstack": "Pura pino", - "unstack_action_prompt": "{count} purettu pinosta", - "unstacked_assets_count": "Poistettu pinosta {count, plural, one {# kohde} other {# kohdetta}}", - "unsupported_field_type": "Ei-tuettu kentän tyyppi", - "untagged": "Ilman tunnistetta", - "untitled_workflow": "Nimetön työnkulku", - "up_next": "Seuraavaksi", - "update_location_action_prompt": "Päivitä {count} kohteen sijaintia:", - "updated_at": "Päivitetty", - "updated_password": "Salasana päivitetty", - "upload": "Siirrä palvelimelle", - "upload_concurrency": "Latausten samanaikaisuus", - "upload_details": "Lähetyksen tiedot", - "upload_dialog_info": "Haluatko varmuuskopioida valitut kohteet palvelimelle?", - "upload_dialog_title": "Lähetä kohde", - "upload_errors": "Lataus valmistui {count, plural, one {# virheen} other {# virheen}} kanssa. Päivitä sivu nähdäksesi ladatut tiedot.", - "upload_finished": "Lähetys valmistui", - "upload_progress": "Jäljellä {remaining, number} - Käsitelty {processed, number}/{total, number}", - "upload_skipped_duplicates": "Ohitettiin {count, plural, one {# kaksoiskappale} other {# kaksoiskappaletta}}", - "upload_status_duplicates": "Kaksoiskappaleet", - "upload_status_errors": "Virheet", - "upload_status_uploaded": "Ladattu", - "upload_success": "Lataus onnistui. Päivitä sivu jotta näet latauksesi.", - "upload_to_immich": "Lähetä Immichiin ({count})", - "uploading": "Lähettää", - "uploading_media": "Lähetetään mediaa", - "url": "URL", - "usage": "Käyttö", - "use_biometric": "Käytä biometriikkaa", - "use_current_connection": "Käytä nykyistä yhteyttä", - "use_custom_date_range": "Käytä omaa aikaväliä", - "user": "Käyttäjä", - "user_has_been_deleted": "Käyttäjä on poistettu.", - "user_id": "Käyttäjän ID", - "user_liked": "{user} tykkäsi {type, select, photo {kuvasta} video {videosta} asset {mediasta} other {tästä}}", - "user_pin_code_settings": "PIN-koodi", - "user_pin_code_settings_description": "Hallinnoi PIN-koodiasi", - "user_privacy": "Käyttäjän tietosuoja", - "user_purchase_settings": "Osta", - "user_purchase_settings_description": "Hallitse ostostasi", - "user_role_set": "Tee käyttäjästä {user} {role}", - "user_usage_detail": "Käyttäjän käytön tiedot", - "user_usage_stats": "Tilin käyttötilastot", - "user_usage_stats_description": "Näytä tilin käyttötilastot", - "username": "Käyttäjänimi", - "users": "Käyttäjät", - "users_added_to_album_count": "{count, plural, one {# käyttäjä} other {# käyttäjää}} lisätty albumiin", - "utilities": "Apuohjelmat", - "validate": "Validoi", - "validate_endpoint_error": "Anna kelvollinen URL-osoite", - "validation_error": "Validointivirhe", - "variables": "Muuttujat", - "version": "Versio", - "version_announcement_closing": "Ystäväsi Alex", - "version_announcement_message": "Hei! Sovelluksen uusi versio on saatavilla. Käythän vilkaisemassa julkaisun tiedot ja varmistathan, että ohjelman määritykset ovat ajan tasalla. Erityisesti, jos käytössä on Watchtower tai jokin muu mekanismi Immich-sovelluksen automaattista päivitystä varten.", - "version_history": "Versiohistoria", - "version_history_item": "Asennettu {version} päivänä {date}", - "video": "Video", - "video_hover_setting": "Toista esikatselun video kun kursori viedään sen päälle", - "video_hover_setting_description": "Toista videon esikatselukuva kun kursori on kuvan päällä. Vaikka toiminto on pois käytöstä, toiston voi aloittaa viemällä kursori toistokuvakkeen päälle.", - "videos": "Videot", - "videos_count": "{count, plural, one {# video} other {# videota}}", - "videos_only": "Vain videot", - "view": "Katso", - "view_album": "Näytä albumi", - "view_all": "Näytä kaikki", - "view_all_users": "Näytä kaikki käyttäjät", - "view_asset_owners": "Näytä omistajat", - "view_details": "Näytä tiedot", - "view_in_timeline": "Näytä aikajanalla", - "view_link": "Näytä linkki", - "view_links": "Näytä linkit", - "view_name": "Näkymä", - "view_next_asset": "Näytä seuraava", - "view_previous_asset": "Näytä edellinen", - "view_qr_code": "Näytä QR-koodi", - "view_similar_photos": "Näytä samankaltaiset kuvat", - "view_stack": "Näytä pinona", - "view_user": "Näytä käyttäjä", - "viewer_remove_from_stack": "Poista pinosta", - "viewer_stack_use_as_main_asset": "Käytä pääkohteena", - "viewer_unstack": "Pura pino", - "visibility_changed": "{count, plural, one {# henkilön} other {# henkilöiden}} näkyvyys vaihdettu", - "visual": "Visuaalinen", - "visual_builder": "Visuaalinen koostaja", - "waiting": "Odottaa", - "waiting_count": "Odottaa: {count}", - "warning": "Varoitus", - "week": "Viikko", - "welcome": "Tervetuloa", - "welcome_to_immich": "Tervetuloa Immichiin", - "width": "Leveys", - "wifi_name": "Wi-Fi-verkon nimi", - "workflow_delete_prompt": "Haluatko varmasti poistaa tämän työnkulun?", - "workflow_deleted": "Työnkulku poistettu", - "workflow_description": "Työnkulun kuvaus", - "workflow_info": "Työnkulut tiedot", - "workflow_json": "Työnkulun JSON", - "workflow_json_help": "Muokkaa työnkulun kokoonpanoa JSON-muodossa. Muutokset synkronoidaan visuaaliseen koostajaan.", - "workflow_name": "Työnkulun nimi", - "workflow_navigation_prompt": "Haluatko varmasti poistua tallentamatta muutoksia?", - "workflow_summary": "Työnkulun yhteenveto", - "workflow_update_success": "Työnkulku päivitetty onnistuneesti", - "workflow_updated": "Työnkulku päivitetty", - "workflows": "Työnkulut", - "wrong_pin_code": "Väärä PIN-koodi", - "year": "Vuosi", - "years_ago": "{years, plural, one {# vuosi} other {# vuotta}} sitten", - "yes": "Kyllä", - "you_dont_have_any_shared_links": "Sinulla ei ole jaettuja linkkejä", - "your_wifi_name": "Wi-Fi-verkkosi nimi", - "zoom_image": "Zoomaa kuvaa", - "zoom_to_bounds": "Zoomaa reunoihin" -} +{} diff --git a/i18n/fil.json b/i18n/fil.json index c3340f2c8f..0967ef424b 100644 --- a/i18n/fil.json +++ b/i18n/fil.json @@ -1,93 +1 @@ -{ - "about": "Tungkol sa app", - "account": "Account", - "account_settings": "Mga Setting ng Account", - "acknowledge": "Tanggapin", - "action": "Aksyon", - "action_common_update": "I-update", - "actions": "Mga Aksyon", - "active": "Tumatakbo", - "activity": "Mga Aktibidad", - "activity_changed": "Ang aktibidad ay {enabled, select, true {naka-enable} other {hindi naka-enable}}", - "add": "Mag dagdag", - "add_a_description": "Dagdagan ng deskripsyon", - "add_a_location": "Dagdagan ng lugar", - "add_a_name": "Dagdagan ng pangalan", - "add_a_title": "Dagdagan ng pamagat", - "add_birthday": "Maglagay ng kaarawan", - "add_endpoint": "Dagdagan ng dulo", - "add_location": "Magdagdag ng lugar", - "add_more_users": "Magdagdag ng mga user", - "add_partner": "Magdagdag ng kasangga", - "add_path": "Magdagdag ng path", - "add_photos": "Magdagdag ng litrato", - "add_tag": "Magdagdag ng tag", - "add_to": "Idagdag sa…", - "add_to_album": "Idagdag sa album", - "add_to_album_bottom_sheet_added": "Naidagdag sa {album}", - "add_to_album_bottom_sheet_already_exists": "Nasa {album} na", - "add_to_albums": "Idagdag sa mga album", - "add_to_albums_count": "Idagdag sa mga album ({count})", - "add_to_shared_album": "Idagdag sa shared album", - "add_url": "Magdagdag ng URL", - "added_to_archive": "Naidagdag sa archive", - "added_to_favorites": "Naidagdag sa mga paborito", - "added_to_favorites_count": "Naidagdag ang {count, number} sa mga paborito", - "admin": { - "add_exclusion_pattern_description": "Dagdagan ng pattern para maibukod. Supportado ang pag-tutugma gamit ang *, **, at ?. Para hindi maisama ang mga file sa direktoryo na may pangalang \"Raw\", gamitin ang \"**/Raw/**\". Para hindi maisama ang lahat ng mga file na nagtatapos sa \".tif\", gamitin ang \"**/*.tif\". Para hindi maisama ang isang tiyak na folder, gamitin ang \"/path/to/ignore/**\".", - "asset_offline_description": "Ang external library asset na ito ay hindi na makikita sa disk at nailipat na sa basurahan. Kung ang file ay nailipat sa loob ng library, tignan ang iyong timeline para sa kaukulang asset. Para maibalik ang asset na ito, siguraduhin na ang file ay maa-access ng Immich at muling i-scan ang library.", - "authentication_settings": "Setting ng mga Pagkakakilanlan", - "authentication_settings_description": "Pamahalaan ang password, OAuth, and iba pang setting ng pagkakakilanlan", - "authentication_settings_disable_all": "Sigurado ka bang gusto mong i-disable lahat ng paraan ng pag-login? Ang pag-login ay ganap na idi-disable.", - "authentication_settings_reenable": "Para i-enable muli, gamitin ang Server Command.", - "background_task_job": "Mga Backround na Gawain", - "backup_database": "Gumawa ng Dump ng Database", - "backup_database_enable_description": "Paganahin ang Database Dumps", - "backup_keep_last_amount": "Bilang ng mga itatagong nakaraang dump", - "backup_settings": "Setting ng mga Database Dump", - "backup_settings_description": "Pamahalaan ang mga setting ng database dump.", - "cleared_jobs": "Tinanggal na ang mga trabaho para sa {job}", - "config_set_by_file": "Ang mga setting ay kasalukuyang naka-set mula sa config file", - "confirm_delete_library": "Sigurado ka na gusto mo burahin ang {library} library?", - "confirm_delete_library_assets": "Sigurado ka bang gusto mong burahin ang library na ito? Ang {count, plural, one {# na lamang asset} other {lahat ng # na nilalamang asset}} mula sa Immich ay mabubura at hindi maibabalik. Ang mga file ay mananatili sa disk.", - "confirm_email_below": "Para isigurado, i-type ito sa baba: \"{email}\"", - "confirm_reprocess_all_faces": "Sigurado ka bang gusto mong i-process muli lahat ng mga mukha? Mabubura nito ang mga taong napangalanan na.", - "confirm_user_password_reset": "Sigurado ka bang gusto mo i-reset ang password ni {user}?", - "confirm_user_pin_code_reset": "Sigurado ka bang gusto mo i-reset ang PIN code ni {user}?", - "create_job": "Gumawa ng trabaho", - "cron_expression": "Ekspresyon na Cron", - "cron_expression_description": "I-set ang pagitan ng pag-scan gamit ang cron na format. Maaaring basahin ang Crontab Guru para sa karagdagang impormasyon", - "cron_expression_presets": "Mga preset na ekspresyong Cron", - "disable_login": "I-disable ang login", - "duplicate_detection_job_description": "Hanapin ang mga magkakatulad na imahe gamit ang machine learning. Umaasa sa Smart Search", - "exclusion_pattern_description": "Maaaring gamitin ang mga pattern na pangbukod para hindi pansinin ang ilang file o folder habang binabasa ang iyong library. Mainam itong solusyon para sa mga folder na may file na ayaw niyong ma-import, tulad ng mga RAW na file.", - "force_delete_user_warning": "BABALA: Tatanggalin itong user at lahat ng asset nila, Hindi ito mababawi at ang kanilang files ay hindi na mababalik", - "image_format": "Format", - "note_cannot_be_changed_later": "TANDAAN: Hindi na ito pwede baguhin sa susunod!", - "server_welcome_message_description": "Mensahe na ipapakita sa login page.", - "user_restore_description": "Ang account ni {user} ay maibabalik." - }, - "album_user_left": "Umalis sa {album}", - "all_albums": "Lahat ng albums", - "all_people": "Lahat ng tao", - "all_videos": "Lahat ng video", - "api_key_description": "Isang beses lamang na ipapakita itong value. Siguraduhin na ikopya itong value bago iclose ang window na ito.", - "are_these_the_same_person": "Itong tao na ito ay parehas?", - "asset_adding_to_album": "Dinadagdag sa album...", - "asset_filename_is_offline": "Offline ang asset {filename}", - "asset_uploading": "Ina-upload...", - "create_album_page_untitled": "Walang pamagat", - "documentation": "Dokumentasyion", - "done": "Tapos na", - "download": "I-download", - "edit": "I-edit", - "editor_close_without_save_title": "Isara ang editor?", - "explore": "I-explore", - "export": "I-export", - "has_quota": "May quota", - "hour": "Oras", - "jobs": "Mga trabaho", - "language": "Wika", - "leave": "Umalis", - "no_results": "Walang resulta" -} +{} diff --git a/i18n/fr.json b/i18n/fr.json index 6086a3f88c..0967ef424b 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -1,2401 +1 @@ -{ - "about": "À propos", - "account": "Compte", - "account_settings": "Paramètres du compte", - "acknowledge": "Compris", - "action": "Action", - "action_common_update": "Mettre à jour", - "action_description": "Un ensemble d'actions applicables sur des médias filtrés", - "actions": "Actions", - "active": "En cours", - "active_count": "Actif : {count}", - "activity": "Activité", - "activity_changed": "Activité {enabled, select, true {activée} other {désactivée}}", - "add": "Ajouter", - "add_a_description": "Ajouter une description", - "add_a_location": "Ajouter une localisation", - "add_a_name": "Ajouter un nom", - "add_a_title": "Ajouter un titre", - "add_action": "Ajouter une action", - "add_action_description": "Cliquez pour ajouter une action à réaliser", - "add_assets": "Ajouter des médias", - "add_birthday": "Ajouter un anniversaire", - "add_endpoint": "Ajouter une adresse", - "add_exclusion_pattern": "Ajouter un schéma d'exclusion", - "add_filter": "Ajouter un filtre", - "add_filter_description": "Cliquez pour ajouter une condition au filtre", - "add_location": "Ajouter une localisation", - "add_more_users": "Ajouter plus d'utilisateurs", - "add_partner": "Ajouter un partenaire", - "add_path": "Ajouter un chemin", - "add_photos": "Ajouter des photos", - "add_tag": "Ajouter une étiquette", - "add_to": "Ajouter à…", - "add_to_album": "Ajouter à l'album", - "add_to_album_bottom_sheet_added": "Ajouté à {album}", - "add_to_album_bottom_sheet_already_exists": "Déjà dans {album}", - "add_to_album_bottom_sheet_some_local_assets": "Certains médias n'ont pas pu être ajoutés à l'album", - "add_to_album_toggle": "Basculer la sélection pour {album}", - "add_to_albums": "Ajouter aux albums", - "add_to_albums_count": "Ajouter aux albums ({count})", - "add_to_bottom_bar": "Ajouter à", - "add_to_shared_album": "Ajouter à l'album partagé", - "add_upload_to_stack": "Ajouter les éléments téléversés à la pile", - "add_url": "Ajouter l'URL", - "add_workflow_step": "Ajouter une étape de flux de traitement", - "added_to_archive": "Ajouté à l'archive", - "added_to_favorites": "Ajouté aux favoris", - "added_to_favorites_count": "{count, number} ajouté(s) aux favoris", - "admin": { - "add_exclusion_pattern_description": "Ajouter des schémas d'exclusion. Les caractères génériques *, ** et ? sont pris en charge. Pour ignorer tous les fichiers dans un répertoire nommé « Raw », utilisez « **/Raw/** ». Pour ignorer tous les fichiers se terminant par « .tif », utilisez « **/*.tif ». Pour ignorer un chemin absolu, utilisez « /chemin/à/ignorer/** ».", - "admin_user": "Administrateur", - "asset_offline_description": "Ce média de la bibliothèque externe n'est plus présent sur le disque et a été déplacé vers la corbeille. Si le fichier a été déplacé dans la bibliothèque, vérifiez votre chronologie pour le nouveau média correspondant. Pour restaurer ce média, veuillez vous assurer que le chemin du fichier ci-dessous peut être accédé par Immich et lancez l'analyse de la bibliothèque.", - "authentication_settings": "Paramètres d'authentification", - "authentication_settings_description": "Gérer le mot de passe, l'authentification OAuth et d'autres paramètres d'authentification", - "authentication_settings_disable_all": "Êtes-vous sûr de vouloir désactiver toutes les méthodes de connexion ? La connexion sera complètement désactivée.", - "authentication_settings_reenable": "Pour réactiver, utilisez une Commande Serveur.", - "background_task_job": "Tâches de fond", - "backup_database": "Création d'une image de la base de données", - "backup_database_enable_description": "Activer la création d'images de la base de données", - "backup_keep_last_amount": "Nombre d'images à conserver", - "backup_onboarding_1_description": "copie hors site dans le cloud ou sur un site distant.", - "backup_onboarding_2_description": "copies locales sur différents appareils. Cela inclut les fichiers principaux ainsi qu'une sauvegarde locale de ces fichiers.", - "backup_onboarding_3_description": "copies total de vos données, incluant les fichiers originaux. Cela inclut 1 copie hors site ainsi que 2 copies locales.", - "backup_onboarding_description": "Une stratégie de sauvegarde 3-2-1 est recommandé pour protéger vos données. Vous devriez conserver des copies de vos photos/vidéos téléversés ainsi que de la base de données d'Immich pour une solution de sauvegarde cohérente.", - "backup_onboarding_footer": "Pour plus d'information sur la sauvegarde d'Immich, merci de vous référer à la documentation.", - "backup_onboarding_parts_title": "Une sauvegarde 3-2-1 inclut :", - "backup_onboarding_title": "Sauvegardes", - "backup_settings": "Paramètres de création d'images de la base de données", - "backup_settings_description": "Gérer les paramètres de création d'images de la base de données.", - "cleared_jobs": "Tâches supprimées pour : {job}", - "config_set_by_file": "La configuration est actuellement définie par un fichier de configuration", - "confirm_delete_library": "Êtes-vous sûr de vouloir supprimer la bibliothèque {library} ?", - "confirm_delete_library_assets": "Êtes-vous sûr de vouloir supprimer cette bibliothèque ? Cette opération supprimera d'Immich {count, plural, one {le média} other {les # médias}} qu'elle contient et ne pourra pas être annulée. Les fichiers resteront sur le disque.", - "confirm_email_below": "Pour confirmer, tapez « {email} » ci-dessous", - "confirm_reprocess_all_faces": "Êtes-vous sûr de vouloir retraiter tous les visages ? Cela effacera également les personnes déjà identifiées.", - "confirm_user_password_reset": "Êtes-vous sûr de vouloir réinitialiser le mot de passe de {user} ?", - "confirm_user_pin_code_reset": "Êtes-vous sûr de vouloir réinitialiser le code PIN de l'utilisateur {user} ?", - "copy_config_to_clipboard_description": "Copier la configuration du système actuelle au format JSON dans le presse-papier", - "create_job": "Créer une tâche", - "cron_expression": "Expression cron", - "cron_expression_description": "Définir l'intervalle d'analyse à l'aide d'une expression cron. Pour plus d'informations, voir Crontab Guru", - "cron_expression_presets": "Préréglages d'expression cron", - "disable_login": "Désactiver la connexion", - "duplicate_detection_job_description": "Lancement de l'apprentissage automatique sur les médias pour détecter les images similaires. Se base sur la recherche intelligente", - "exclusion_pattern_description": "Les schémas d'exclusion vous permettent d'ignorer des fichiers et des dossiers lors de l'analyse de votre bibliothèque. Cette fonction est utile si des dossiers contiennent des fichiers que vous ne souhaitez pas importer, tels que des fichiers RAW.", - "export_config_as_json_description": "Télécharger la configuration actuelle du système en tant que fichier JSON", - "external_libraries_page_description": "Page d'administration des bibliothèques externes", - "face_detection": "Détection des visages", - "face_detection_description": "Détection des visages dans les médias à l'aide de l'apprentissage automatique. Pour les vidéos, seule la miniature est prise en compte. « Actualiser » (re)traite tous les médias. « Réinitialiser » retraite tous les visages en repartant de zéro. « Manquant » met en file d'attente les médias qui n'ont pas encore été traités. Lorsque la détection est terminée, les visages détectés seront mis en file d'attente pour la reconnaissance faciale.", - "facial_recognition_job_description": "Regrouper les visages détectés en personnes. Cette étape est exécutée une fois la détection des visages terminée. « Réinitialiser » (re)regroupe tous les visages. « Manquant » met en file d'attente les visages auxquels aucune personne n'a été attribuée.", - "failed_job_command": "La commande {command} a échoué pour la tâche : {job}", - "force_delete_user_warning": "ATTENTION : Cette opération entraîne la suppression immédiate de l'utilisateur et de tous ses médias. Cette opération ne peut être annulée et les fichiers ne peuvent être récupérés.", - "image_format": "Format", - "image_format_description": "WebP produit des fichiers plus petits que JPEG, mais son encodage est plus lent.", - "image_fullsize_description": "Image en taille réelle, sans métadonnées, utilisée lors d'un zoom", - "image_fullsize_enabled": "Activer la génération d'image en taille d'origine", - "image_fullsize_enabled_description": "Générer une image en taille réelle pour les formats non compatibles avec le web. Lorsque l'option « Préférer l'aperçu intégré » est activée, les aperçus intégrés sont utilisés directement sans conversion. Cette option n'affecte pas les formats compatibles avec le web tels que JPEG.", - "image_fullsize_quality_description": "Qualité de l'image en taille réelle de 1 à 100. Une valeur plus élevée est meilleure, mais produit des fichiers plus volumineux.", - "image_fullsize_title": "Paramètres des images en taille réelle", - "image_prefer_embedded_preview": "Préférer l'aperçu intégré", - "image_prefer_embedded_preview_setting_description": "Utiliser les miniatures intégrées dans les photos au format RAW comme entrées pour le traitement d'image quand elles sont disponibles. Cela peut donner des couleurs plus justes pour certaines images, mais la qualité des miniatures est dépendant de l'appareil photo et l'image peut avoir des artéfacts de compression.", - "image_prefer_wide_gamut": "Préférer une gamme de couleurs étendue", - "image_prefer_wide_gamut_setting_description": "Utiliser Display P3 pour les miniatures. Cela préserve mieux la vivacité des images avec des espaces colorimétriques étendus, mais les images peuvent apparaître différemment sur les anciens appareils avec une ancienne version du navigateur. Conserver les images sRGB en sRGB pour éviter les décalages de couleur.", - "image_preview_description": "Image de taille moyenne avec métadonnées retirées, utilisée lors de la visualisation d'un seul média et pour l'apprentissage automatique", - "image_preview_quality_description": "Qualité de l'aperçu : de 1 à 100. Une valeur plus élevée produit de meilleurs résultats, mais elle produit des fichiers plus volumineux et peut réduire la réactivité de l'application. Une valeur trop basse peut affecter la qualité de l'apprentissage automatique.", - "image_preview_title": "Paramètres de prévisualisation", - "image_progressive": "Progressive", - "image_progressive_description": "Encode les images JPEG de manière progressive pour un affichage graduel. Cela n'a pas d'effet sur les images en WebP.", - "image_quality": "Qualité", - "image_resolution": "Résolution", - "image_resolution_description": "Les résolutions plus élevées permettent de préserver davantage de détails, mais l'encodage est plus long, les fichiers sont plus volumineux et la réactivité de l'application peut s'en trouver réduite.", - "image_settings": "Paramètres d'image", - "image_settings_description": "Gestion de la qualité et résolution des images générées", - "image_thumbnail_description": "Petite miniature avec les métadonnées retirées, utilisée lors de la visualisation de groupes de photos comme sur la vue chronologique principale", - "image_thumbnail_quality_description": "Qualité des miniatures : de 1 à 100. Une valeur élevée produit de meilleurs résultats, mais elle produit des fichiers plus volumineux et peut réduire la réactivité de l'application.", - "image_thumbnail_title": "Paramètres des miniatures", - "import_config_from_json_description": "Importer la configuration système en envoyant un fichier de configuration JSON", - "job_concurrency": "{job} : nombre de tâches simultanées", - "job_created": "Tâche créée", - "job_not_concurrency_safe": "Cette tâche ne peut pas être exécutée en multitâche de façon sûre.", - "job_settings": "Paramètres des tâches", - "job_settings_description": "Gestion des tâches simultanées", - "jobs_delayed": "{jobCount, plural, other {# retardés}}", - "jobs_failed": "{jobCount, plural, other {# en échec}}", - "jobs_over_time": "Tâches au fil du temps", - "library_created": "Bibliothèque créée : {library}", - "library_deleted": "Bibliothèque supprimée", - "library_details": "Détails de la bibliothèque", - "library_folder_description": "Renseignez un dossier à importer. Ce dossier et ses sous-dossiers seront scannés pour leurs images et vidéos.", - "library_remove_exclusion_pattern_prompt": "Êtes-vous sûr de vouloir supprimer ce schéma d'exclusion ?", - "library_remove_folder_prompt": "Êtes-vous sûr de vouloir supprimer ce dossier d'import ?", - "library_scanning": "Analyse périodique", - "library_scanning_description": "Configurer l'analyse périodique de la bibliothèque", - "library_scanning_enable_description": "Activer l'analyse périodique de la bibliothèque", - "library_settings": "Bibliothèque externe", - "library_settings_description": "Gestion des paramètres des bibliothèques externes", - "library_tasks_description": "Scanner les bibliothèques externes pour les nouveaux et/ou les éléments modifiés", - "library_updated": "Bibliothèque mise à jour", - "library_watching_enable_description": "Surveiller les modifications de fichiers dans les bibliothèques externes", - "library_watching_settings": "Surveillance de bibliothèque [EXPÉRIMENTAL]", - "library_watching_settings_description": "Surveiller automatiquement les fichiers modifiés", - "logging_enable_description": "Activer la journalisation", - "logging_level_description": "Niveau de journalisation lorsque cette option est activée.", - "logging_settings": "Journalisation", - "machine_learning_availability_checks": "Vérifications de disponibilité", - "machine_learning_availability_checks_description": "Détecte automatiquement et choisit les serveurs d'apprentissage machine disponibles", - "machine_learning_availability_checks_enabled": "Activer les vérifications de disponibilité", - "machine_learning_availability_checks_interval": "Intervalle de vérification", - "machine_learning_availability_checks_interval_description": "Intervalle en millisecondes entre les vérifications de disponibilité", - "machine_learning_availability_checks_timeout": "Délai d'expiration de la requête", - "machine_learning_availability_checks_timeout_description": "Délai d'expiration en millisecondes pour les vérifications de disponibilité", - "machine_learning_clip_model": "Modèle de langage CLIP", - "machine_learning_clip_model_description": "Le nom d'un modèle CLIP listé ici. Notez que vous devez réexécuter la tâche 'Recherche intelligente' pour toutes les images après avoir changé de modèle.", - "machine_learning_duplicate_detection": "Détection des doublons", - "machine_learning_duplicate_detection_enabled": "Activer la détection des doublons", - "machine_learning_duplicate_detection_enabled_description": "Si désactivé, les médias totalement identiques seront quand même dédupliqués.", - "machine_learning_duplicate_detection_setting_description": "Utiliser les intégrations du modèle de langage CLIP pour trouver des doublons probables", - "machine_learning_enabled": "Activer l'apprentissage automatique", - "machine_learning_enabled_description": "Si désactivé, toutes les fonctionnalités d'apprentissage automatique seront désactivées, quels que soient les paramètres ci-dessous.", - "machine_learning_facial_recognition": "Reconnaissance faciale", - "machine_learning_facial_recognition_description": "Détecter, reconnaître et regrouper les visages dans les images", - "machine_learning_facial_recognition_model": "Modèle de reconnaissance faciale", - "machine_learning_facial_recognition_model_description": "Les modèles sont répertoriés par ordre décroissant de taille. Les modèles plus grands sont plus lents et utilisent plus de mémoire, mais produisent de meilleurs résultats. Notez que vous devez relancer la tâche de détection de visages pour toutes les images après avoir changé de modèle.", - "machine_learning_facial_recognition_setting": "Activer la reconnaissance faciale", - "machine_learning_facial_recognition_setting_description": "Si désactivé, les images ne seront pas encodées pour la reconnaissance faciale et ne rempliront pas la section Personnes dans la page Explorer.", - "machine_learning_max_detection_distance": "Écart maximal de détection", - "machine_learning_max_detection_distance_description": "Écart maximal entre deux images pour les considérer comme des doublons, allant de 0,001 à 0,1. Les valeurs plus élevées détecteront plus de doublons, mais peuvent entraîner des faux positifs.", - "machine_learning_max_recognition_distance": "Distance maximale de reconnaissance", - "machine_learning_max_recognition_distance_description": "Écart maximal (tolérance) entre deux visages pour que ceux-ci soient considérés comme appartenant à la même personne, allant de 0 à 2. Abaisser cette valeur peut empêcher l'étiquetage de deux visages comme appartenant à la même personne, tandis que l'augmenter peut empêcher l'étiquetage de deux visages comme appartenant à des personnes différentes. Notez qu'il est plus facile de fusionner deux personnes que de diviser une personne en deux, donc optez pour un seuil plus bas lorsque c'est possible.", - "machine_learning_min_detection_score": "Score minimal de détection", - "machine_learning_min_detection_score_description": "Score de confiance minimal pour qu'un visage soit détecté, allant de 0 à 1. Des valeurs plus basses détecteront plus de visages mais peuvent entraîner des faux positifs.", - "machine_learning_min_recognized_faces": "Nombre minimal de visages reconnus", - "machine_learning_min_recognized_faces_description": "Nombre minimal de visages reconnus pour qu'une personne soit créée. Augmenter cette valeur rend la reconnaissance faciale plus précise au détriment d'augmenter la chance qu'un visage ne soit pas attribué à une personne.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Utiliser l'apprentissage automatique pour reconnaître le texte dans les images", - "machine_learning_ocr_enabled": "Activer la reconnaissance de caractères", - "machine_learning_ocr_enabled_description": "Si désactivé, la reconnaissance de texte ne s'appliquera pas aux images.", - "machine_learning_ocr_max_resolution": "Résolution maximale", - "machine_learning_ocr_max_resolution_description": "Les prévisualisations au-dessus de cette résolution seront retaillées en conservant leur ratio. Des valeurs plus grandes sont plus précises, mais sont plus lentes et utilisent plus de mémoire.", - "machine_learning_ocr_min_detection_score": "Score minimum de détection", - "machine_learning_ocr_min_detection_score_description": "Score de confiance minimum pour la détection du textew entre 0 et 1. Des valeurs faibles permettront de reconnaître davantage de texte mais peuvent entraîner des faux positifs.", - "machine_learning_ocr_min_recognition_score": "Score de reconnaissance minimum", - "machine_learning_ocr_min_score_recognition_description": "Score de confiance minimum pour la reconnaissance du texte, entre 0 et 1. Des valeurs faible permettront de reconnaître davantage de texte, mais peuvent entraîner des faux positifs.", - "machine_learning_ocr_model": "Modèle de Reconnaissance Optique de Caractères", - "machine_learning_ocr_model_description": "Les modèles du serveur sont plus précis que les modèles mobiles, mais ils sont plus lents et utilisent plus de mémoire.", - "machine_learning_settings": "Paramètres d'apprentissage automatique", - "machine_learning_settings_description": "Gérer les fonctionnalités et les paramètres d'apprentissage automatique", - "machine_learning_smart_search": "Recherche intelligente", - "machine_learning_smart_search_description": "Rechercher des images de manière sémantique en utilisant les intégrations CLIP", - "machine_learning_smart_search_enabled": "Activer la recherche intelligente", - "machine_learning_smart_search_enabled_description": "Si cette option est désactivée, les images ne seront pas encodées pour la recherche intelligente.", - "machine_learning_url_description": "L’URL du serveur d'apprentissage automatique. Si plusieurs URL sont fournies, chaque serveur sera essayé un par un jusqu’à ce que l’un d’eux réponde avec succès, dans l’ordre de la première à la dernière. Les serveurs ne répondant pas seront temporairement ignorés jusqu'à ce qu'ils soient de nouveau opérationnels.", - "maintenance_delete_backup": "Supprimer la sauvegarde", - "maintenance_delete_backup_description": "Ce fichier sera définitivement supprimé.", - "maintenance_delete_error": "Échec de la suppression de la sauvegarde.", - "maintenance_restore_backup": "Restaurer la sauvegarde", - "maintenance_restore_backup_description": "Immich sera effacé et restauré à partir de la sauvegarde choisie. Une sauvegarde sera créée avant de continuer.", - "maintenance_restore_backup_different_version": "Cette sauvegarde a été créée avec une version différente de Immich !", - "maintenance_restore_backup_unknown_version": "Impossible de déterminer la version de sauvegarde.", - "maintenance_restore_database_backup": "Restaurer la sauvegarde de la base de données", - "maintenance_restore_database_backup_description": "Revenir à un état antérieur de la base de données à l'aide d'un fichier de sauvegarde", - "maintenance_settings": "Maintenance", - "maintenance_settings_description": "Mettre Immich en mode maintenance.", - "maintenance_start": "Passer en mode maintenance", - "maintenance_start_error": "Échec du démarrage du mode maintenance.", - "maintenance_upload_backup": "Télécharger le fichier de sauvegarde de la base de données", - "maintenance_upload_backup_error": "Impossible de télécharger la sauvegarde, s'agit-il d'un fichier .sql/.sql.gz ?", - "manage_concurrency": "Gérer du multitâche", - "manage_concurrency_description": "Naviguer vers la pages des tâches pour gérer le multitâche", - "manage_log_settings": "Gérer les paramètres de journalisation", - "map_dark_style": "Thème sombre", - "map_enable_description": "Activer la carte", - "map_gps_settings": "Paramètres de la carte et GPS", - "map_gps_settings_description": "Gérer les paramètres de la Carte & GPS", - "map_implications": "La carte repose sur un service de tuiles externe (tiles.immich.cloud)", - "map_light_style": "Thème clair", - "map_manage_reverse_geocoding_settings": "Gérer les paramètres de géocodage inversé", - "map_reverse_geocoding": "Géocodage inversé", - "map_reverse_geocoding_enable_description": "Activer le géocodage inversé", - "map_reverse_geocoding_settings": "Paramètres de géocodage inversé", - "map_settings": "Carte", - "map_settings_description": "Gérer les paramètres de la carte", - "map_style_description": "URL vers un thème de carte au format style.json", - "memory_cleanup_job": "Nettoyage des souvenirs", - "memory_generate_job": "Génération des souvenirs", - "metadata_extraction_job": "Extraction des métadonnées", - "metadata_extraction_job_description": "Extraction des informations des métadonnées de chaque média, telles que la position GPS, les visages et la résolution", - "metadata_faces_import_setting": "Active l'importation des visages", - "metadata_faces_import_setting_description": "Importation de visages à partir des données EXIF des images et de fichiers sidecar", - "metadata_settings": "Paramètres des métadonnées", - "metadata_settings_description": "Gestion des paramètres de métadonnées", - "migration_job": "Migration", - "migration_job_description": "Migration des miniatures pour les médias et les visages vers la dernière structure de dossiers", - "nightly_tasks_cluster_faces_setting_description": "Démarrer la reconnaissance faciale sur les visages nouvellement détectés", - "nightly_tasks_cluster_new_faces_setting": "Regrouper les nouveaux visages", - "nightly_tasks_database_cleanup_setting": "Tâches de nettoyage de la base de données", - "nightly_tasks_database_cleanup_setting_description": "Nettoyage ancien, données de la base de données expirées", - "nightly_tasks_generate_memories_setting": "Générer les souvenirs", - "nightly_tasks_generate_memories_setting_description": "Créer des souvenirs à partir des éléments", - "nightly_tasks_missing_thumbnails_setting": "Générer les miniatures manquantes", - "nightly_tasks_missing_thumbnails_setting_description": "Mettre en file d'attente les éléments sans miniature pour la création de miniature", - "nightly_tasks_settings": "Paramètres des tâches de nuit", - "nightly_tasks_settings_description": "Gérer les tâches de nuit", - "nightly_tasks_start_time_setting": "Heure de démarrage", - "nightly_tasks_start_time_setting_description": "Heure à laquelle le serveur commence à exécuter les tâches de nuit", - "nightly_tasks_sync_quota_usage_setting": "Synchroniser les quota d'usage", - "nightly_tasks_sync_quota_usage_setting_description": "Mettre à jour les quota d'usage de l'utilisateur, en se basant sur l'utilisation actuelle", - "no_paths_added": "Aucun chemin n'a été ajouté", - "no_pattern_added": "Aucun schéma d'exclusion n'a été ajouté", - "note_apply_storage_label_previous_assets": "Remarque : pour appliquer l'étiquette de stockage à des médias précédemment envoyés, exécutez", - "note_cannot_be_changed_later": "REMARQUE : Il n'est pas possible de modifier ce paramètre ultérieurement !", - "notification_email_from_address": "Depuis l'adresse", - "notification_email_from_address_description": "Adresse courriel de l'expéditeur, par exemple : « Serveur de photos Immich  ». Assurez-vous d'utiliser une adresse à partir de laquelle vous pouvez envoyer des courriels.", - "notification_email_host_description": "Hôte du serveur de messagerie électronique (par exemple, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorer les erreurs de certificat", - "notification_email_ignore_certificate_errors_description": "Ignorer les erreurs de validation du certificat TLS (non recommandé)", - "notification_email_password_description": "Mot de passe à utiliser lors de l'authentification avec le serveur de messagerie", - "notification_email_port_description": "Port du serveur de messagerie (par exemple 25, 465 ou 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Utilise SMTPS (SMTP via TLS)", - "notification_email_sent_test_email_button": "Envoyer un courriel de test et enregistrer", - "notification_email_setting_description": "Paramètres pour l'envoi de notifications par courriel", - "notification_email_test_email": "Envoyer un courriel de test", - "notification_email_test_email_failed": "Échec de l'envoi du courriel de test, vérifiez vos informations", - "notification_email_test_email_sent": "Un courriel de test a été envoyé à {email}. Veuillez vérifier votre boîte de réception.", - "notification_email_username_description": "Nom d'utilisateur à utiliser lors de l'authentification avec le serveur de messagerie", - "notification_enable_email_notifications": "Activer les notifications par courriel", - "notification_settings": "Paramètres de notification", - "notification_settings_description": "Gérer les paramètres de notification, y compris les courriels", - "oauth_auto_launch": "Lancement automatique", - "oauth_auto_launch_description": "Démarrer automatiquement le flux de connexion OAuth lors de la navigation vers la page de connexion", - "oauth_auto_register": "Inscription automatique", - "oauth_auto_register_description": "Inscrire automatiquement de nouveaux utilisateurs après leur connexion avec OAuth", - "oauth_button_text": "Texte du bouton", - "oauth_client_secret_description": "Nécessaire pour un client confidentiel, ou si le protocole PKCE (Proof Key for Code Exchange) n'est pas supporté par le client public.", - "oauth_enable_description": "Connexion avec OAuth", - "oauth_mobile_redirect_uri": "URI de redirection mobile", - "oauth_mobile_redirect_uri_override": "Remplacer l'URI de redirection mobile", - "oauth_mobile_redirect_uri_override_description": "Activer quand le fournisseur d'OAuth ne permet pas un URI mobile, comme ''{callback}''", - "oauth_role_claim": "Attribut de rôle", - "oauth_role_claim_description": "Donne automatiquement un accès en tant qu'admin, en se basant sur la présence de cet attribut. L'attribut peut avoir soit 'user' (utilisateur) soit 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Gérer les paramètres de connexion OAuth", - "oauth_settings_more_details": "Pour plus de détails sur cette fonctionnalité, consultez ce lien.", - "oauth_storage_label_claim": "Demande d'étiquette de stockage", - "oauth_storage_label_claim_description": "Définir automatiquement l'étiquette de stockage de l'utilisateur sur la valeur de cette revendication.", - "oauth_storage_quota_claim": "Demande de quota de stockage", - "oauth_storage_quota_claim_description": "Définir automatiquement le quota de stockage de l'utilisateur par la valeur de cette demande.", - "oauth_storage_quota_default": "Quota de stockage par défaut (Go)", - "oauth_storage_quota_default_description": "Quota en Gio à utiliser lorsqu'aucune valeur n'est précisée.", - "oauth_timeout": "Expiration de la durée de la requête", - "oauth_timeout_description": "Délai d'expiration des requêtes en millisecondes", - "ocr_job_description": "Utiliser un modèle d'apprentissage automatique pour reconnaitre le texte dans les images", - "password_enable_description": "Connexion avec courriel et mot de passe", - "password_settings": "Connexion par mot de passe", - "password_settings_description": "Gérer les paramètres de connexion par mot de passe", - "paths_validated_successfully": "Tous les chemins ont été validés avec succès", - "person_cleanup_job": "Nettoyage des personnes", - "queue_details": "Détails de la file d'attente", - "queues": "Files d'attente des tâches", - "queues_page_description": "Page des files d'attente des tâches d'administration", - "quota_size_gib": "Taille du quota (Go)", - "refreshing_all_libraries": "Actualisation de toutes les bibliothèques", - "registration": "Enregistrement de l'administrateur", - "registration_description": "Puisque vous êtes le premier utilisateur sur le système, vous serez désigné en tant qu'administrateur et responsable des tâches administratives, et vous pourrez alors créer d'autres utilisateurs.", - "remove_failed_jobs": "Supprimer les tâches en erreur", - "require_password_change_on_login": "Demander à l'utilisateur de changer son mot de passe lors de sa première connexion", - "reset_settings_to_default": "Réinitialiser les paramètres par défaut", - "reset_settings_to_recent_saved": "Paramètres réinitialisés avec les derniers paramètres enregistrés", - "scanning_library": "Analyse de la bibliothèque", - "search_jobs": "Recherche des tâches…", - "send_welcome_email": "Envoyer un courriel de bienvenue", - "server_external_domain_settings": "Domaine externe", - "server_external_domain_settings_description": "Nom de domaine pour les liens partagés publics, y compris http(s)://", - "server_public_users": "Utilisateurs publics", - "server_public_users_description": "Tous les utilisateurs (nom et courriel) sont listés lors de l'ajout d'un utilisateur à des albums partagés. Quand cela est désactivé, la liste des utilisateurs est uniquement disponible pour les comptes administrateurs.", - "server_settings": "Paramètres du serveur", - "server_settings_description": "Gérer les paramètres du serveur", - "server_stats_page_description": "Page administrateur des statistiques du serveur", - "server_welcome_message": "Message de bienvenue", - "server_welcome_message_description": "Ce message est affiché sur la page de connexion.", - "settings_page_description": "Page d'administration des paramètres", - "sidecar_job": "Métadonnées annexes (fichiers xmp)", - "sidecar_job_description": "Recherche ou synchronisation des métadonnées annexes (fichiers xmp) sur le système de fichiers", - "slideshow_duration_description": "Nombre de secondes d'affichage de chaque image", - "smart_search_job_description": "Exécution de l'apprentissage automatique sur les médias pour prendre en charge la recherche intelligente", - "storage_template_date_time_description": "L'horodatage de la création du média est utilisé pour l'information sur la date", - "storage_template_date_time_sample": "Exemple d'heure {date}", - "storage_template_enable_description": "Activer le moteur de modèle de stockage", - "storage_template_hash_verification_enabled": "Vérification du hachage activée", - "storage_template_hash_verification_enabled_description": "Active la vérification du hachage, ne désactivez pas cette option à moins d'être sûr de ce que vous faites", - "storage_template_migration": "Migration du modèle de stockage", - "storage_template_migration_description": "Appliquer le modèle courant {template} aux médias précédemment envoyés", - "storage_template_migration_info": "L'enregistrement des modèles va convertir toutes les extensions en minuscule. Les changements de modèle ne s'appliqueront qu'aux nouveaux médias. Pour appliquer rétroactivement le modèle aux médias précédemment envoyés, exécutez la tâche {job}.", - "storage_template_migration_job": "Tâche de migration du modèle de stockage", - "storage_template_more_details": "Pour plus de détails sur cette fonctionnalité, reportez-vous au Modèle de stockage et à ses implications", - "storage_template_onboarding_description_v2": "Quand elle est activée, cette fonctionnalité organise automatiquement les fichiers, sur base d'un modèle défini par l'utilisateur. Pour plus d'informations, se référer à la documentation.", - "storage_template_path_length": "Limite approximative de la longueur du chemin : {length, number}/{limit, number}", - "storage_template_settings": "Modèle de stockage", - "storage_template_settings_description": "Gérer la structure des dossiers et le nom des fichiers du média envoyé", - "storage_template_user_label": "{label} est l'étiquette de stockage de l'utilisateur", - "system_settings": "Paramètres du système", - "tag_cleanup_job": "Nettoyage des étiquettes", - "template_email_available_tags": "Vous pouvez utiliser les variables suivantes dans votre modèle : {tags}", - "template_email_if_empty": "Si le modèle est vide, l’e-mail par défaut sera utilisé.", - "template_email_invite_album": "Modèle d'invitation à un album", - "template_email_preview": "Prévisualiser", - "template_email_settings": "Modèles de courriel", - "template_email_update_album": "Mettre à jour le modèle d’album", - "template_email_welcome": "Modèle de courriel de bienvenue", - "template_settings": "Modèles de notifications", - "template_settings_description": "Gérer les modèles personnalisés pour les notifications", - "theme_custom_css_settings": "CSS personnalisé", - "theme_custom_css_settings_description": "Les feuilles de style en cascade (CSS) permettent de personnaliser l'apparence d'Immich.", - "theme_settings": "Paramètres du thème", - "theme_settings_description": "Gérer la personnalisation de l'interface web d'Immich", - "thumbnail_generation_job": "Génération des miniatures", - "thumbnail_generation_job_description": "Génération des miniatures pour chaque média ainsi que pour les visages détectés", - "transcoding_acceleration_api": "API d'accélération", - "transcoding_acceleration_api_description": "Il s'agit de l'API qui interagira avec votre appareil pour accélérer le transcodage. Ce paramètre fait au mieux : il basculera vers le transcodage logiciel en cas d'échec. Le codec vidéo VP9 peut fonctionner ou non selon votre matériel.", - "transcoding_acceleration_nvenc": "NVENC (nécessite un GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (nécessite un processeur Intel de 7ème génération ou supérieur)", - "transcoding_acceleration_rkmpp": "RKMPP (uniquement sur les SOCs Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codecs audio acceptés", - "transcoding_accepted_audio_codecs_description": "Sélectionnez les codecs audio qui n'ont pas besoin d'être transcodés. Utilisé uniquement pour certaines politiques de transcodage.", - "transcoding_accepted_containers": "Conteneurs acceptés", - "transcoding_accepted_containers_description": "Sélectionnez les formats de conteneurs qui n'ont pas besoin d'être remuxés en MP4. Utilisé uniquement pour certaines politiques de transcodage.", - "transcoding_accepted_video_codecs": "Codecs vidéo acceptés", - "transcoding_accepted_video_codecs_description": "Sélectionnez les codecs vidéo qui n'ont pas besoin d'être transcodés. Utilisé uniquement pour certaines politiques de transcodage.", - "transcoding_advanced_options_description": "Options que la plupart des utilisateurs ne devraient pas avoir besoin de modifier", - "transcoding_audio_codec": "Codec audio", - "transcoding_audio_codec_description": "Le codec audio Opus est l'option produisant la plus haute qualité, mais elle est moins compatible avec les anciens appareils ou logiciels.", - "transcoding_bitrate_description": "Les vidéos dont le débit binaire est supérieur au maximum autorisé ou qui ne sont pas dans un format accepté", - "transcoding_codecs_learn_more": "Pour en savoir plus sur la terminologie utilisée ici, consultez la documentation FFmpeg sur le codec H.264, le codec HEVC et le codec VP9.", - "transcoding_constant_quality_mode": "Mode de qualité constante", - "transcoding_constant_quality_mode_description": "ICQ est meilleur que CQP, mais certains dispositifs d'accélération matérielle ne prennent pas en charge ce mode. En définissant cette option, le mode spécifié sera privilégié lors de l'utilisation de l'encodage basé sur la qualité. Ignoré par NVENC car il ne prend pas en charge ICQ.", - "transcoding_constant_rate_factor": "Facteur de taux constant (-crf)", - "transcoding_constant_rate_factor_description": "Niveau de qualité vidéo. Les valeurs typiques sont 23 pour H.264, 28 pour HEVC, 31 pour VP9 et 35 pour AV1. Plus la valeur est basse, meilleure est la qualité, mais les fichiers produits sont plus grands.", - "transcoding_disabled_description": "Ne pas transcoder les vidéos. Cela peut perturber la lecture sur certains logiciels ou appareils", - "transcoding_encoding_options": "Options d'encodage", - "transcoding_encoding_options_description": "Configure codecs, résolution, qualité et autres options pour les vidéos encodées", - "transcoding_hardware_acceleration": "Accélération matérielle", - "transcoding_hardware_acceleration_description": "Expérimental  : transcodage plus rapide, mais peut réduire la qualité pour un même débit binaire", - "transcoding_hardware_decoding": "Décodage matériel", - "transcoding_hardware_decoding_setting_description": "Active l'accélération de bout en bout au lieu d'accélérer uniquement l'encodage. Peut ne pas fonctionner sur toutes les vidéos.", - "transcoding_max_b_frames": "Nombre maximum de trames B", - "transcoding_max_b_frames_description": "Des valeurs plus élevées améliorent l'efficacité de la compression, mais ralentissent l'encodage. Elles peuvent ne pas être compatibles avec l'accélération matérielle sur les anciens appareils. Une valeur de 0 désactive les trames B, tandis qu'une valeur de -1 définit automatiquement ce paramètre.", - "transcoding_max_bitrate": "Débit binaire maximal", - "transcoding_max_bitrate_description": "Définir un débit binaire maximal peut rendre la taille des fichiers plus prévisible, au prix d’une légère perte de qualité. En 720p, les valeurs typiques sont de 2600 kbit/s pour du VP9 ou du HEVC, ou de 4500 kbit/s pour du H.264. Désactivé si le débit binaire est fixé à 0. Lorsqu’aucune unité n’est spécifiée, k (pour kbit/s) est supposée ; ainsi, 5000, 5000k et 5M (pour Mbit/s) sont équivalents.", - "transcoding_max_keyframe_interval": "Intervalle maximal entre les images clés", - "transcoding_max_keyframe_interval_description": "Définit la distance maximale de trames entre les images clés. Les valeurs plus basses diminuent l'efficacité de la compression, mais améliorent les temps de recherche et peuvent améliorer la qualité dans les scènes avec des mouvements rapides. Une valeur de 0 définit automatiquement ce paramètre.", - "transcoding_optimal_description": "Les vidéos dont la résolution est supérieure à celle attendue ou celles qui ne sont pas dans un format accepté", - "transcoding_policy": "Politique de transcodage", - "transcoding_policy_description": "Configure quand une vidéo va être transcodée", - "transcoding_preferred_hardware_device": "Matériel préféré", - "transcoding_preferred_hardware_device_description": "S'applique uniquement à VAAPI et QSV. Définit le nœud DRI utilisé pour le transcodage matériel.", - "transcoding_preset_preset": "Présélection (-preset)", - "transcoding_preset_preset_description": "Vitesse de compression. Les préréglages les plus lents produisent des fichiers plus petits, et augmentent la qualité lorsqu'un certain débit est défini. Le codec vidéo VP9 ignore les vitesses supérieures à « rapide (faster) ».", - "transcoding_reference_frames": "Trames de référence", - "transcoding_reference_frames_description": "Le nombre d'images à prendre en référence lors de la compression d'une image donnée. Des valeurs élevées améliorent l'efficacité de la compression, mais ralentissent l'encodage. 0 fixe cette valeur automatiquement.", - "transcoding_required_description": "Seulement les vidéos dans un format non accepté", - "transcoding_settings": "Paramètres de transcodage vidéo", - "transcoding_settings_description": "Gère quelles vidéos transcoder et comment les traiter", - "transcoding_target_resolution": "Résolution cible", - "transcoding_target_resolution_description": "Des résolutions plus élevées peuvent préserver plus de détails, mais prennent plus de temps à encoder, ont de plus grandes tailles de fichiers, et peuvent réduire la réactivité de l'application.", - "transcoding_temporal_aq": "Quantification adaptative temporelle (temporal AQ)", - "transcoding_temporal_aq_description": "S'applique uniquement à NVENC. La quantification adaptative temporelle améliore la qualité des scènes riches en détails et à faible mouvement. Peut ne pas être compatible avec les anciens appareils.", - "transcoding_threads": "Processus", - "transcoding_threads_description": "Une valeur plus élevée entraîne un encodage plus rapide, mais laisse moins de place au serveur pour traiter d'autres tâches pendant son activité. Cette valeur ne doit pas être supérieure au nombre de cœurs de CPU. Une valeur égale à 0 maximise l'utilisation.", - "transcoding_tone_mapping": "Mappage tonal", - "transcoding_tone_mapping_description": "Tente de préserver l'apparence des vidéos HDR lorsqu'elles sont converties en SDR. Chaque algorithme effectue différents compromis pour la couleur, les détails et la luminosité. Hable préserve les détails, Mobius préserve la couleur, et Reinhard préserve la luminosité.", - "transcoding_transcode_policy": "Politique de transcodage", - "transcoding_transcode_policy_description": "Politique indiquant quand une vidéo doit être transcodée. Les vidéos HDR seront toujours transcodées (sauf si le transcodage est désactivé).", - "transcoding_two_pass_encoding": "Encodage en deux passes", - "transcoding_two_pass_encoding_setting_description": "Transcoder en deux passes pour produire des vidéos mieux encodées. Lorsque le débit binaire maximum est activé (obligatoire pour qu'il fonctionne avec H.264 et HEVC), ce mode utilise une plage de débit binaire basée sur le débit binaire maximum et ignore le CRF. Pour VP9, CRF peut être utilisé si le débit binaire maximum est désactivé.", - "transcoding_video_codec": "Codec vidéo", - "transcoding_video_codec_description": "Le codec vidéo VP9 est très efficace et compatible avec le web, mais son transcodage est plus long. HEVC a des performances similaires, mais sa compatibilité web est plus faible. H.264 est largement compatible et rapide à transcoder, mais produit des fichiers beaucoup plus volumineux. AV1 est le codec le plus efficace, mais il n'est pas pris en charge par les anciens appareils.", - "trash_enabled_description": "Activer la corbeille", - "trash_number_of_days": "Nombre de jours", - "trash_number_of_days_description": "Nombre de jours de rétention des médias dans la corbeille avant leur suppression définitive", - "trash_settings": "Corbeille", - "trash_settings_description": "Gérer les paramètres de la corbeille", - "unlink_all_oauth_accounts": "Délier tous les comptes OAuth", - "unlink_all_oauth_accounts_description": "Pensez à délier tous les comptes OAuth avant de migrer vers un nouveau fournisseur.", - "unlink_all_oauth_accounts_prompt": "Êtes-vous sûr de vouloir délier tous les comptes OAuth ? Cela va réinitialiser l'identifiant OAuth de chaque utilisateur et est irrévocable.", - "user_cleanup_job": "Nettoyage des utilisateurs", - "user_delete_delay": "La suppression définitive du compte et des médias de {user} sera programmée dans {delay, plural, one {# jour} other {# jours}}.", - "user_delete_delay_settings": "Délai de suppression", - "user_delete_delay_settings_description": "Nombre de jours après la validation pour supprimer définitivement le compte et les médias d'un utilisateur. La suppression des utilisateurs se lance à minuit. Les modifications apportées à ce paramètre seront pris en compte lors de la prochaine exécution.", - "user_delete_immediately": "Le compte et les médias de {user} seront mis en file d'attente en vue d'une suppression permanente immédiatement.", - "user_delete_immediately_checkbox": "Mise en file d'attente d'un utilisateur et de médias en vue d'une suppression immédiate", - "user_details": "Détails utilisateur", - "user_management": "Gestion des utilisateurs", - "user_password_has_been_reset": "Le mot de passe de l'utilisateur a été réinitialisé :", - "user_password_reset_description": "Veuillez fournir le mot de passe temporaire à l'utilisateur et informez-le qu'il devra le changer à sa première connexion.", - "user_restore_description": "Le compte de {user} sera restauré.", - "user_restore_scheduled_removal": "Restaurer l'utilisateur - suppression programmée le {date, date, long}", - "user_settings": "Paramètres utilisateur", - "user_settings_description": "Gérer les paramètres utilisateur", - "user_successfully_removed": "L'utilisateur {email} a été supprimé avec succès.", - "users_page_description": "Page d'administration des utilisateurs", - "version_check_enabled_description": "Activer la vérification périodique de nouvelle version", - "version_check_implications": "Le contrôle de version repose sur une communication périodique avec github.com", - "version_check_settings": "Vérification de la version", - "version_check_settings_description": "Gérer la vérification de nouvelle version d'Immich", - "video_conversion_job": "Transcodage des vidéos", - "video_conversion_job_description": "Transcodage des vidéos pour une compatibilité améliorée avec les navigateurs et les différents appareils" - }, - "admin_email": "Courriel Admin", - "admin_password": "Mot de passe Admin", - "administration": "Administration", - "advanced": "Avancé", - "advanced_settings_clear_image_cache": "Vider le cache des images", - "advanced_settings_clear_image_cache_error": "Erreur au vidage du cache des images", - "advanced_settings_clear_image_cache_success": "Vidage avec succès de {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Utilisez cette option pour filtrer les média durant la synchronisation avec des critères alternatifs. N'utilisez cela que lorsque l'application n'arrive pas à détecter tous les albums.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPÉRIMENTAL] Utiliser le filtre de synchronisation d'album alternatif", - "advanced_settings_log_level_title": "Niveau de journalisation : {level}", - "advanced_settings_prefer_remote_subtitle": "Certains appareils sont très lents à charger des miniatures à partir de ressources locales. Activez ce paramètre pour charger des images externes à la place.", - "advanced_settings_prefer_remote_title": "Préférer les images externes", - "advanced_settings_proxy_headers_subtitle": "Ajoutez des en-têtes personnalisés à chaque requête réseau", - "advanced_settings_proxy_headers_title": "En-têtes de proxy personnalisés [EXPÉRIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Active le mode lecture seule, où les photos peuvent seulement être visualisées, et les actions comme les sélections multiples, le partage, la diffusion, la suppression sont désactivées. Activer/désactiver la lecture seule via l'image de l'utilisateur depuis l'écran d'accueil", - "advanced_settings_readonly_mode_title": "Mode lecture seule", - "advanced_settings_self_signed_ssl_subtitle": "Permet d'ignorer la vérification du certificat SSL pour le point d'accès du serveur. Requis pour les certificats auto-signés.", - "advanced_settings_self_signed_ssl_title": "Autoriser les certificats SSL auto-signés [EXPÉRIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Supprimer ou restaurer automatiquement un média sur cet appareil lorsqu'une action a été faite sur le web", - "advanced_settings_sync_remote_deletions_title": "Synchroniser les suppressions depuis le serveur [EXPÉRIMENTAL]", - "advanced_settings_tile_subtitle": "Paramètres d'utilisateur avancés", - "advanced_settings_troubleshooting_subtitle": "Activer des fonctions supplémentaires pour le dépannage", - "advanced_settings_troubleshooting_title": "Dépannage", - "age_months": "Âge {months, plural, one {# mois} other {# mois}}", - "age_year_months": "Âge 1 an, {months, plural, one {# mois} other {# mois}}", - "age_years": "Âge {years, plural, one {# an} other {# ans}}", - "album": "Album", - "album_added": "Album ajouté", - "album_added_notification_setting_description": "Recevoir une notification par courriel lorsque vous êtes ajouté(e) à un album partagé", - "album_cover_updated": "Couverture de l'album mise à jour", - "album_delete_confirmation": "Êtes-vous sûr de vouloir supprimer l'album {album} ?", - "album_delete_confirmation_description": "Si cet album est partagé, les autres utilisateurs ne pourront plus y accéder.", - "album_deleted": "Album supprimé", - "album_info_card_backup_album_excluded": "EXCLUS", - "album_info_card_backup_album_included": "INCLUS", - "album_info_updated": "Détails de l'album mis à jour", - "album_leave": "Quitter l'album ?", - "album_leave_confirmation": "Êtes-vous sûr de vouloir quitter l'album {album} ?", - "album_name": "Nom de l'album", - "album_options": "Options de l'album", - "album_remove_user": "Supprimer l'utilisateur ?", - "album_remove_user_confirmation": "Êtes-vous sûr de vouloir supprimer {user} ?", - "album_search_not_found": "Aucun album trouvé ne correspond à votre recherche", - "album_selected": "Album sélectionné", - "album_share_no_users": "Il semble que vous ayez partagé cet album avec tous les utilisateurs ou que vous n'ayez aucun utilisateur avec lequel le partager.", - "album_summary": "Résumé de l'album", - "album_updated": "Album mis à jour", - "album_updated_setting_description": "Recevoir une notification par courriel lorsqu'un album partagé a de nouveaux médias", - "album_upload_assets": "Téléchargez des fichiers depuis votre ordinateur et ajoutez-les à l'album", - "album_user_left": "{album} quitté", - "album_user_removed": "{user} supprimé", - "album_viewer_appbar_delete_confirm": "Êtes-vous sur de vouloir supprimer cet album de votre compte ?", - "album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album", - "album_viewer_appbar_share_err_leave": "Impossible de quitter l'album", - "album_viewer_appbar_share_err_remove": "Il y a des problèmes lors de la suppression des éléments de l'album", - "album_viewer_appbar_share_err_title": "Échec de la modification du titre de l'album", - "album_viewer_appbar_share_leave": "Quitter l'album", - "album_viewer_appbar_share_to": "Partager à", - "album_viewer_page_share_add_users": "Ajouter des utilisateurs", - "album_with_link_access": "Permettre à n'importe qui possédant le lien de voir les photos et les personnes de cet album.", - "albums": "Albums", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albums}}", - "albums_default_sort_order": "Ordre de tri par défaut des albums", - "albums_default_sort_order_description": "Ordre de tri des médias pour les nouveaux albums créés.", - "albums_feature_description": "Bibliothèques de médias pouvant être partagés avec d'autres utilisateurs.", - "albums_on_device_count": "Album sur l'appareil ({count})", - "albums_selected": "{count, plural, one {# album sélectionné} other {# albums sélectionnés}}", - "all": "Tout", - "all_albums": "Tous les albums", - "all_people": "Toutes les personnes", - "all_photos": "Toutes les photos", - "all_videos": "Toutes les vidéos", - "allow_dark_mode": "Autoriser le mode sombre", - "allow_edits": "Autoriser les modifications", - "allow_public_user_to_download": "Permettre le téléchargement par des utilisateurs non connectés", - "allow_public_user_to_upload": "Permettre l'envoi par des utilisateurs non connectés", - "allowed": "Autorisé", - "alt_text_qr_code": "Image du code QR", - "always_keep": "Toujours conserver", - "always_keep_photos_hint": "Libérer de l'espace va conserver toutes les photos sur cet appareil.", - "always_keep_videos_hint": "Libérer de l'espace va conserver toutes les vidéos sur cet appareil.", - "anti_clockwise": "Sens anti-horaire", - "api_key": "Clé API", - "api_key_description": "Cette valeur ne sera affichée qu'une seule fois. Assurez-vous de la copier avant de fermer la fenêtre.", - "api_key_empty": "Le nom de votre clé API ne doit pas être vide", - "api_keys": "Clés d'API", - "app_architecture_variant": "Variante (Architecture)", - "app_bar_signout_dialog_content": "Êtes-vous sûr(e) de vouloir vous déconnecter ?", - "app_bar_signout_dialog_ok": "Oui", - "app_bar_signout_dialog_title": "Se déconnecter", - "app_download_links": "Liens de téléchargement de l'appli", - "app_settings": "Paramètres de l'application", - "app_stores": "Magasins d'applications", - "app_update_available": "Une mise à jour est disponible", - "appears_in": "Apparaît dans", - "apply_count": "Appliquer ({count, number})", - "archive": "Archive", - "archive_action_prompt": "{count} ajouté(s) à l'archive", - "archive_or_unarchive_photo": "Archiver ou désarchiver une photo", - "archive_page_no_archived_assets": "Aucun élément archivé n'a été trouvé", - "archive_page_title": "Archiver ({count})", - "archive_size": "Taille de l'archive", - "archive_size_description": "Configurer la taille de l'archive maximale pour les téléchargements (en Go)", - "archived": "Archives", - "archived_count": "{count, plural, one {# archivé} other {# archivés}}", - "are_these_the_same_person": "Est-ce la même personne ?", - "are_you_sure_to_do_this": "Êtes-vous sûr de vouloir faire ceci ?", - "array_field_not_fully_supported": "Les champs du tableau nécessitent la modification manuelle du JSON", - "asset_action_delete_err_read_only": "Impossible de supprimer le(s) média(s) en lecture seule, ils sont ignorés", - "asset_action_share_err_offline": "Impossible de récupérer le(s) média(s) hors ligne, ils sont ignorés", - "asset_added_to_album": "Ajouté à l'album", - "asset_adding_to_album": "Ajout à l'album…", - "asset_created": "Média créé", - "asset_description_updated": "La description du média a été mise à jour", - "asset_filename_is_offline": "Le média {filename} est hors ligne", - "asset_has_unassigned_faces": "Le média a des visages non attribués", - "asset_hashing": "Hachage…", - "asset_list_group_by_sub_title": "Regrouper par", - "asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique", - "asset_list_layout_settings_group_automatically": "Automatique", - "asset_list_layout_settings_group_by": "Grouper les éléments par", - "asset_list_layout_settings_group_by_month_day": "Mois + jour", - "asset_list_layout_sub_title": "Disposition", - "asset_list_settings_subtitle": "Paramètres de disposition de la grille de photos", - "asset_list_settings_title": "Grille de photos", - "asset_not_found_on_device_android": "Média introuvable sur l'appareil", - "asset_not_found_on_device_ios": "Média introuvable sur l'appareil. Si vous utilisez iCloud, le média peut être inaccessible en raison d'un fichier corrompu stocké sur iCloud", - "asset_not_found_on_icloud": "Média introuvable sur iCloud. Le média est peut-être inaccessible en raison d'un fichier corrompu stocké sur iCloud", - "asset_offline": "Média hors ligne", - "asset_offline_description": "Ce média externe n'est plus accessible sur le disque. Veuillez contacter votre administrateur Immich pour obtenir de l'aide.", - "asset_restored_successfully": "Élément restauré avec succès", - "asset_skipped": "Sauté", - "asset_skipped_in_trash": "À la corbeille", - "asset_trashed": "Média mis à la corbeille", - "asset_troubleshoot": "Dépannage de média", - "asset_uploaded": "Envoyé", - "asset_uploading": "Envoi…", - "asset_viewer_settings_subtitle": "Modifier les paramètres du visualiseur photos", - "asset_viewer_settings_title": "Visualiseur d'éléments", - "assets": "Médias", - "assets_added_count": "{count, plural, one {# média ajouté} other {# médias ajoutés}}", - "assets_added_to_album_count": "{count, plural, one {# média ajouté} other {# médias ajoutés}} à l'album", - "assets_added_to_albums_count": "{assetTotal, plural, one {# média ajouté} other {# médias ajoutés}} à {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Le média ne peut pas être ajouté} other {Les médias ne peuvent pas être ajoutés}} à l'album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Le média ne peut être ajouté} other {Les médias ne peuvent être ajoutés}} à aucun des albums", - "assets_count": "{count, plural, one {# média} other {# médias}}", - "assets_deleted_permanently": "{count} média(s) supprimé(s) définitivement", - "assets_deleted_permanently_from_server": "{count} média(s) supprimé(s) définitivement du serveur Immich", - "assets_downloaded_failed": "{count, plural, one {# fichier téléchargé - échec du fichier {error}} other {# fichiers téléchargés - échec des fichiers {error}}}", - "assets_downloaded_successfully": "{count, plural, one {# fichier téléchargé avec succès} other {# fichiers téléchargés avec succès}}", - "assets_moved_to_trash_count": "{count, plural, one {# média déplacé} other {# médias déplacés}} dans la corbeille", - "assets_permanently_deleted_count": "{count, plural, one {# média supprimé} other {# médias supprimés}} définitivement", - "assets_removed_count": "{count, plural, one {# média supprimé} other {# médias supprimés}}", - "assets_removed_permanently_from_device": "{count} média(s) supprimé(s) définitivement de votre appareil", - "assets_restore_confirmation": "Êtes-vous sûr de vouloir restaurer tous vos médias de la corbeille ? Vous ne pouvez pas annuler cette action ! Notez que les médias hors ligne ne peuvent être restaurés de cette façon.", - "assets_restored_count": "{count, plural, one {# média restauré} other {# médias restaurés}}", - "assets_restored_successfully": "{count} élément(s) restauré(s) avec succès", - "assets_trashed": "{count} média(s) déplacé(s) vers la corbeille", - "assets_trashed_count": "{count, plural, one {# média} other {# médias}} mis à la corbeille", - "assets_trashed_from_server": "{count} média(s) déplacé(s) vers la corbeille du serveur Immich", - "assets_were_part_of_album_count": "{count, plural, one {Un média est} other {Des médias sont}} déjà dans l'album", - "assets_were_part_of_albums_count": "{count, plural, one {Le média était déjà présent} other {Les médias étaient déjà présents}} dans les albums", - "authorized_devices": "Appareils autorisés", - "automatic_endpoint_switching_subtitle": "Se connecter localement via le réseau Wi-Fi désigné lorsqu'il est disponible et utiliser d'autres connexions ailleurs", - "automatic_endpoint_switching_title": "Changement automatique d'adresse", - "autoplay_slideshow": "Lecture automatique d'un diaporama", - "back": "Retour", - "back_close_deselect": "Retournez en arrière, fermez ou désélectionnez", - "background_backup_running_error": "La sauvegarde en tâche de fond est actuellement en cours, impossible de démarrer une sauvegarde manuelle", - "background_location_permission": "Permission de localisation en arrière plan", - "background_location_permission_content": "Afin de pouvoir changer d'adresse en arrière plan, Immich doit avoir *en permanence* accès à la localisation précise, afin d'accéder au le nom du réseau Wi-Fi utilisé", - "background_options": "Options d'arrière-plan", - "backup": "Sauvegarde", - "backup_album_selection_page_albums_device": "Albums sur l'appareil ({count})", - "backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure", - "backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.", - "backup_album_selection_page_select_albums": "Sélectionner les albums", - "backup_album_selection_page_selection_info": "Informations sur la sélection", - "backup_album_selection_page_total_assets": "Total des éléments uniques", - "backup_albums_sync": "Sauvegarde de la Synchronisation des Albums", - "backup_all": "Tout", - "backup_background_service_backup_failed_message": "Échec de la sauvegarde des médias. Nouvelle tentative…", - "backup_background_service_complete_notification": "Sauvegarde du média terminée", - "backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentative…", - "backup_background_service_current_upload_notification": "Envoi de {filename}", - "backup_background_service_default_notification": "Recherche de nouveaux médias…", - "backup_background_service_error_title": "Erreur de sauvegarde", - "backup_background_service_in_progress_notification": "Sauvegarde de vos médias…", - "backup_background_service_upload_failure_notification": "Échec lors de l'envoi de {filename}", - "backup_controller_page_albums": "Sauvegarder les albums", - "backup_controller_page_background_app_refresh_disabled_content": "Activez le rafraîchissement de l'application en arrière-plan dans Paramètres > Général > Rafraîchissement de l'application en arrière-plan afin d'utiliser la sauvegarde en arrière-plan.", - "backup_controller_page_background_app_refresh_disabled_title": "Rafraîchissement de l'application en arrière-plan désactivé", - "backup_controller_page_background_app_refresh_enable_button_text": "Aller aux paramètres", - "backup_controller_page_background_battery_info_link": "Montrez-moi comment", - "backup_controller_page_background_battery_info_message": "Pour une expérience optimale de la sauvegarde en arrière-plan, veuillez désactiver toute optimisation de la batterie limitant l'activité en arrière-plan pour Immich.\n\nÉtant donné que cela est spécifique à chaque appareil, veuillez consulter les informations requises pour le fabricant de votre appareil.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Optimisation de la batterie", - "backup_controller_page_background_charging": "Seulement pendant la charge", - "backup_controller_page_background_configure_error": "Échec de la configuration du service d'arrière-plan", - "backup_controller_page_background_delay": "Retarder la sauvegarde des nouveaux médias : {duration}", - "backup_controller_page_background_description": "Activez le service d'arrière-plan pour sauvegarder automatiquement tous les nouveaux médias sans avoir à ouvrir l'application", - "backup_controller_page_background_is_off": "La sauvegarde automatique en arrière-plan est désactivée", - "backup_controller_page_background_is_on": "La sauvegarde automatique en arrière-plan est activée", - "backup_controller_page_background_turn_off": "Désactiver le service d'arrière-plan", - "backup_controller_page_background_turn_on": "Activer le service d'arrière-plan", - "backup_controller_page_background_wifi": "Uniquement en wifi", - "backup_controller_page_backup": "Sauvegardé", - "backup_controller_page_backup_selected": "Sélectionné : ", - "backup_controller_page_backup_sub": "Photos et vidéos sauvegardées", - "backup_controller_page_created": "Créé le : {date}", - "backup_controller_page_desc_backup": "Activez la sauvegarde au premier plan pour envoyer automatiquement les nouveaux médias sur le serveur lors de l'ouverture de l'application.", - "backup_controller_page_excluded": "Exclus : ", - "backup_controller_page_failed": "Échec de l'opération ({count})", - "backup_controller_page_filename": "Nom du fichier : {filename} [{size}]", - "backup_controller_page_id": "ID : {id}", - "backup_controller_page_info": "Informations de sauvegarde", - "backup_controller_page_none_selected": "Aucune sélection", - "backup_controller_page_remainder": "Restant", - "backup_controller_page_remainder_sub": "Photos et albums restants à sauvegarder à partir de la sélection", - "backup_controller_page_server_storage": "Stockage du serveur", - "backup_controller_page_start_backup": "Démarrer la sauvegarde", - "backup_controller_page_status_off": "La sauvegarde est désactivée", - "backup_controller_page_status_on": "La sauvegarde est activée", - "backup_controller_page_storage_format": "{used} sur {total} utilisés", - "backup_controller_page_to_backup": "Albums à sauvegarder", - "backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés", - "backup_controller_page_turn_off": "Désactiver la sauvegarde", - "backup_controller_page_turn_on": "Activer la sauvegarde au premier plan", - "backup_controller_page_uploading_file_info": "Envoi des informations du fichier", - "backup_err_only_album": "Impossible de retirer le seul album", - "backup_error_sync_failed": "Échec de synchronisation.", - "backup_info_card_assets": "médias", - "backup_manual_cancelled": "Annulé", - "backup_manual_in_progress": "Envoi déjà en cours. Réessayez plus tard", - "backup_manual_success": "Succès", - "backup_manual_title": "Statut de l'envoi", - "backup_options": "Options de sauvegarde", - "backup_options_page_title": "Options de sauvegarde", - "backup_setting_subtitle": "Ajuster les paramètres d'envoi au premier et en arrière-plan", - "backup_settings_subtitle": "Gérer les paramètres de téléversement", - "backup_upload_details_page_more_details": "Tapoter pour plus de détails", - "backward": "Arrière", - "biometric_auth_enabled": "Authentification biométrique activée", - "biometric_locked_out": "L'authentification biométrique est verrouillé", - "biometric_no_options": "Aucune option biométrique disponible", - "biometric_not_available": "L'authentification biométrique n'est pas disponible sur cet appareil", - "birthdate_saved": "Date de naissance enregistrée avec succès", - "birthdate_set_description": "La date de naissance est utilisée pour calculer l'âge de cette personne au moment où la photo a été prise.", - "blurred_background": "Arrière-plan flouté", - "bugs_and_feature_requests": "Bugs & demandes d'évolutions", - "build": "Version", - "build_image": "Image de la version", - "bulk_delete_duplicates_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# doublon} other {# doublons}} ? Cette opération conservera le plus grand média de chaque groupe et supprimera définitivement tous les autres doublons. Vous ne pouvez pas annuler cette action !", - "bulk_keep_duplicates_confirmation": "Êtes-vous sûr de vouloir conserver {count, plural, one {# doublon} other {# doublons}} ? Cela résoudra tous les groupes de doublons sans rien supprimer.", - "bulk_trash_duplicates_confirmation": "Êtes-vous sûr de vouloir mettre à la corbeille {count, plural, one {# doublon} other {# doublons}} ? Cette opération permet de conserver le plus grand média de chaque groupe et de mettre à la corbeille tous les autres doublons.", - "buy": "Acheter Immich", - "cache_settings_clear_cache_button": "Effacer le cache", - "cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.", - "cache_settings_duplicated_assets_clear_button": "EFFACER", - "cache_settings_duplicated_assets_subtitle": "Photos et vidéos qui sont ignorées par l'application", - "cache_settings_duplicated_assets_title": "Médias dupliqués ({count})", - "cache_settings_statistics_album": "Miniatures de la bibliothèque", - "cache_settings_statistics_full": "Images complètes", - "cache_settings_statistics_shared": "Miniatures de l'album partagé", - "cache_settings_statistics_thumbnail": "Miniatures", - "cache_settings_statistics_title": "Utilisation du cache", - "cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich", - "cache_settings_tile_subtitle": "Contrôler le comportement du stockage local", - "cache_settings_tile_title": "Stockage local", - "cache_settings_title": "Paramètres de mise en cache", - "camera": "Appareil photo", - "camera_brand": "Marque d'appareil", - "camera_model": "Modèle d'appareil", - "cancel": "Annuler", - "cancel_search": "Annuler la recherche", - "canceled": "Annulé", - "canceling": "Annulation", - "cannot_merge_people": "Impossible de fusionner les personnes", - "cannot_undo_this_action": "Vous ne pouvez pas annuler cette action !", - "cannot_update_the_description": "Impossible de mettre à jour la description", - "cast": "Diffusion", - "cast_description": "Configurer les destinations disponibles de diffusion", - "change_date": "Changer la date", - "change_description": "Changer la description", - "change_display_order": "Modifier l'ordre d'affichage", - "change_expiration_time": "Modifier le délai d'expiration", - "change_location": "Changer la localisation", - "change_name": "Changer le nom", - "change_name_successfully": "Nouveau nom enregistré", - "change_password": "Modifier le mot de passe", - "change_password_description": "C'est la première fois que vous vous connectez ou une demande a été faite pour changer votre mot de passe. Veuillez entrer le nouveau mot de passe ci-dessous.", - "change_password_form_confirm_password": "Confirmez le mot de passe", - "change_password_form_description": "Bonjour {name},\n\nC'est la première fois que vous vous connectez au système ou vous avez demandé de changer votre mot de passe. Veuillez saisir le nouveau mot de passe ci-dessous.", - "change_password_form_log_out": "Déconnecter tous les autres appareils", - "change_password_form_log_out_description": "Il est recommandé de déconnecter tous les autres appareils", - "change_password_form_new_password": "Nouveau mot de passe", - "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", - "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", - "change_pin_code": "Changer le code PIN", - "change_trigger": "Changer le déclencheur", - "change_trigger_prompt": "Êtes-vous sûr de vouloir changer le déclencheur ? Cela va supprimer toutes les actions et filtres existants.", - "change_your_password": "Changer votre mot de passe", - "changed_visibility_successfully": "Visibilité modifiée avec succès", - "charging": "En charge", - "charging_requirement_mobile_backup": "La sauvegarde en tâche de fond nécessite que l'appareil soit en charge", - "check_corrupt_asset_backup": "Vérifier la corruption des éléments enregistrés", - "check_corrupt_asset_backup_button": "Vérifier", - "check_corrupt_asset_backup_description": "Lancer cette vérification uniquement lorsque connecté à un réseau Wi-Fi et que tout le contenu a été enregistré. Cette procédure peut durer plusieurs minutes.", - "check_logs": "Vérifier les journaux", - "checksum": "Somme de contrôle", - "choose_matching_people_to_merge": "Choisir les personnes à fusionner", - "city": "Ville", - "cleanup_confirm_description": "Immich a trouvé {count} éléments (créés avant {date}) sauvegardés en toute sécurité sur le serveur. Supprimer les copies locales de cet appareil ?", - "cleanup_confirm_prompt_title": "Supprimer de cet appareil ?", - "cleanup_deleted_assets": "{count} éléments ont été déplacés vers la corbeille de l'appareil", - "cleanup_deleting": "Déplacement vers la corbeille...", - "cleanup_found_assets": "{count} éléments trouvés et sauvegardés", - "cleanup_found_assets_with_size": "{count} médias sauvegardés trouvés ({size})", - "cleanup_icloud_shared_albums_excluded": "Les albums partagés iCloud sont exclus de l'analyse", - "cleanup_no_assets_found": "Aucun élément correspondant aux critères ci-dessus n'a été trouvé. Libérer de l'espace peut seulement supprimer les médias qui ont été sauvegardés sur le serveur", - "cleanup_preview_title": "Éléments à supprimer ({count})", - "cleanup_step3_description": "Rechercher des médias sauvegardés qui correspondent à vos dates et aux paramètres de conservation.", - "cleanup_step4_summary": "{count} éléments créés avant le {date} à supprimer localement sur votre appareil. Les photos resteront accessibles depuis l'appli Immich.", - "cleanup_trash_hint": "Pour libérer complètement l’espace de stockage, ouvrez l’application Galerie du système et videz la corbeille", - "clear": "Effacer", - "clear_all": "Effacer tout", - "clear_all_recent_searches": "Supprimer les recherches récentes", - "clear_file_cache": "Vider le fichier de cache", - "clear_message": "Effacer le message", - "clear_value": "Effacer la valeur", - "client_cert_dialog_msg_confirm": "D'accord", - "client_cert_enter_password": "Entrer mot de passe", - "client_cert_import": "Importer", - "client_cert_import_success_msg": "Certificat importé", - "client_cert_invalid_msg": "Fichier de certificat invalide ou mot de passe incorrect", - "client_cert_remove_msg": "Certificat supprimé", - "client_cert_subtitle": "Prend en charge uniquement le format PKCS12 (.p12, .pfx). L'importation/suppression de certificats n'est possible qu'avant la connexion", - "client_cert_title": "Certificat SSL [EXPÉRIMENTAL]", - "clockwise": "Sens horaire", - "close": "Fermer", - "collapse": "Réduire", - "collapse_all": "Tout réduire", - "color": "Couleur", - "color_theme": "Thème de couleur", - "command": "Commande", - "comment_deleted": "Commentaire supprimé", - "comment_options": "Options des commentaires", - "comments_and_likes": "Commentaires et \"J'aime\"", - "comments_are_disabled": "Les commentaires sont désactivés", - "common_create_new_album": "Créer un nouvel album", - "completed": "Complété", - "confirm": "Confirmer", - "confirm_admin_password": "Confirmez le mot de passe Admin", - "confirm_delete_face": "Êtes-vous sûr de vouloir supprimer le visage de {name} du média ?", - "confirm_delete_shared_link": "Voulez-vous vraiment supprimer ce lien partagé ?", - "confirm_keep_this_delete_others": "Tous les autres médias dans la pile seront supprimés sauf celui-ci. Êtes-vous sûr de vouloir continuer ?", - "confirm_new_pin_code": "Confirmez le nouveau code PIN", - "confirm_password": "Confirmez le mot de passe", - "confirm_tag_face": "Voulez-vous identifier ce visage en tant que {name} ?", - "confirm_tag_face_unnamed": "Voulez-vous identifier ce visage ?", - "connected_device": "Appareil connecté", - "connected_to": "Connecté à", - "contain": "Contenu", - "context": "Contexte", - "continue": "Continuer", - "control_bottom_app_bar_create_new_album": "Créer un nouvel album", - "control_bottom_app_bar_delete_from_immich": "Supprimer de Immich", - "control_bottom_app_bar_delete_from_local": "Supprimer de l'appareil", - "control_bottom_app_bar_edit_location": "Modifier la localisation", - "control_bottom_app_bar_edit_time": "Modifier la date et l'heure", - "control_bottom_app_bar_share_link": "Lien partagé", - "control_bottom_app_bar_share_to": "Partager à", - "control_bottom_app_bar_trash_from_immich": "Déplacer vers la corbeille", - "copied_image_to_clipboard": "Image copiée dans le presse-papiers.", - "copied_to_clipboard": "Copié dans le presse-papiers !", - "copy_error": "Copier l'erreur", - "copy_file_path": "Copier le chemin du fichier", - "copy_image": "Copier l'image", - "copy_link": "Copier le lien", - "copy_link_to_clipboard": "Copier le lien dans le presse-papiers", - "copy_password": "Copier le mot de passe", - "copy_to_clipboard": "Copier dans le presse-papiers", - "country": "Pays", - "cover": "Couverture", - "covers": "Couvertures", - "create": "Créer", - "create_album": "Créer un album", - "create_album_page_untitled": "Sans titre", - "create_api_key": "Créer une clé d'API", - "create_first_workflow": "Créer le premier flux de traitement", - "create_library": "Créer une bibliothèque", - "create_link": "Créer le lien", - "create_link_to_share": "Créer un lien pour partager", - "create_link_to_share_description": "Permettre à n'importe qui ayant le lien de voir la(es) photo(s) sélectionnée(s)", - "create_new": "NOUVEAU", - "create_new_person": "Créer une nouvelle personne", - "create_new_person_hint": "Attribuer les médias sélectionnés à une nouvelle personne", - "create_new_user": "Créer un nouvel utilisateur", - "create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS", - "create_shared_album_page_share_select_photos": "Sélectionner les photos", - "create_shared_link": "Créer un lien partagé", - "create_tag": "Créer une étiquette", - "create_tag_description": "Créer une nouvelle étiquette. Pour les étiquettes imbriquées, veuillez entrer le chemin complet de l'étiquette, y compris les caractères \"/\".", - "create_user": "Créer un utilisateur", - "create_workflow": "Créer un flux de traitement", - "created": "Créé", - "created_at": "Créé à", - "creating_linked_albums": "Création des albums liés...", - "crop": "Recadrer", - "crop_aspect_ratio_fixed": "Figé", - "crop_aspect_ratio_free": "Libre", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Objets", - "current_device": "Appareil actuel", - "current_pin_code": "Code PIN actuel", - "current_server_address": "Adresse actuelle du serveur", - "custom_date": "Date personnalisée", - "custom_locale": "Paramètres régionaux personnalisés", - "custom_locale_description": "Afficher les dates et nombres en fonction des paramètres régionaux", - "custom_url": "URL personnalisée", - "cutoff_date_description": "Conservez les photos depuis les derniers…", - "cutoff_day": "{count, plural, one {jour} other {jours}}", - "cutoff_year": "{count, plural, one {année} other {années}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Sombre", - "dark_theme": "Activer le thème sombre", - "date": "Date", - "date_after": "Date après", - "date_and_time": "Date et heure", - "date_before": "Date avant", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "Date de naissance enregistrée avec succès", - "date_range": "Plage de dates", - "day": "Jour", - "days": "Jours", - "deduplicate_all": "Dédupliquer tout", - "deduplication_criteria_1": "Taille de l'image en octets", - "deduplication_criteria_2": "Nombre de données EXIF", - "deduplication_info": "Info de déduplication", - "deduplication_info_description": "Pour présélectionner automatiquement les médias et supprimer les doublons en masse, nous examinons :", - "default_locale": "Région par défaut", - "default_locale_description": "Afficher les dates et nombres en fonction des paramètres de votre navigateur", - "delete": "Supprimer", - "delete_action_confirmation_message": "Êtes-vous sûr de vouloir supprimer ce média ? Cela déplacera le média dans la poubelle du serveur et vous demandera si vous voulez le supprimer localement", - "delete_action_prompt": "{count} supprimé(s)", - "delete_album": "Supprimer l'album", - "delete_api_key_prompt": "Voulez-vous vraiment supprimer cette clé API ?", - "delete_dialog_alert": "Ces médias seront définitivement supprimés de Immich et de votre appareil", - "delete_dialog_alert_local": "Ces médias seront définitivement supprimés de votre appareil mais resteront disponibles sur le serveur d'Immich", - "delete_dialog_alert_local_non_backed_up": "Certains médias ne sont pas sauvegardés sur Immich et seront définitivement supprimés de votre appareil", - "delete_dialog_alert_remote": "Ces médias seront définitivement supprimés du serveur Immich", - "delete_dialog_ok_force": "Supprimer tout de même", - "delete_dialog_title": "Supprimer définitivement", - "delete_duplicates_confirmation": "Êtes-vous certain de vouloir supprimer définitivement ces doublons ?", - "delete_face": "Supprimer le visage", - "delete_key": "Supprimer la clé", - "delete_library": "Supprimer la bibliothèque", - "delete_link": "Supprimer le lien", - "delete_local_action_prompt": "{count} supprimé(s) localement", - "delete_local_dialog_ok_backed_up_only": "Suppression des données sauvegardées uniquement", - "delete_local_dialog_ok_force": "Supprimer tout de même", - "delete_others": "Supprimer les autres", - "delete_permanently": "Supprimer définitivement", - "delete_permanently_action_prompt": "{count} supprimé(s) définitivement", - "delete_shared_link": "Supprimer le lien partagé", - "delete_shared_link_dialog_title": "Supprimer le lien partagé", - "delete_tag": "Supprimer l'étiquette", - "delete_tag_confirmation_prompt": "Êtes-vous sûr de vouloir supprimer l'étiquette {tagName} ?", - "delete_user": "Supprimer l'utilisateur", - "deleted_shared_link": "Lien partagé supprimé", - "deletes_missing_assets": "Supprimer les médias manquants du disque", - "description": "Description", - "description_input_hint_text": "Ajouter une description...", - "description_input_submit_error": "Erreur de mise à jour de la description, vérifier le journal pour plus de détails", - "deselect_all": "Tout désélectionner", - "details": "Détails", - "direction": "Ordre", - "disable": "Désactiver", - "disabled": "Désactivé", - "disallow_edits": "Ne pas autoriser les modifications", - "discord": "Discord", - "discover": "Découvrir", - "discovered_devices": "Appareils identifiés", - "dismiss_all_errors": "Ignorer toutes les erreurs", - "dismiss_error": "Ignorer l'erreur", - "display_options": "Afficher les options", - "display_order": "Ordre d'affichage", - "display_original_photos": "Afficher les photos originales", - "display_original_photos_setting_description": "Afficher de préférence la photo originale lors de la visualisation d'un média plutôt que sa miniature lorsque cela est possible. Cela peut entraîner des vitesses d'affichage plus lentes.", - "do_not_show_again": "Ne plus afficher ce message", - "documentation": "Documentation", - "done": "Terminé", - "download": "Télécharger", - "download_action_prompt": "Téléchargement de {count} éléments", - "download_canceled": "Téléchargement annulé", - "download_complete": "Téléchargement terminé", - "download_enqueue": "Téléchargement en attente", - "download_error": "Erreur de téléchargement", - "download_failed": "Téléchargement échoué", - "download_finished": "Téléchargement terminé", - "download_include_embedded_motion_videos": "Vidéos intégrées", - "download_include_embedded_motion_videos_description": "Inclure des vidéos intégrées dans les photos de mouvement comme un fichier séparé", - "download_notfound": "Téléchargement non trouvé", - "download_original": "Télécharger l'original", - "download_paused": "Téléchargement en pause", - "download_settings": "Télécharger", - "download_settings_description": "Gérer les paramètres de téléchargement des médias", - "download_started": "Téléchargement commencé", - "download_sucess": "Téléchargement réussi", - "download_sucess_android": "Le média a été téléchargé dans DCIM/Immich", - "download_waiting_to_retry": "Téléchargement en attente du prochain essai", - "downloading": "Téléchargement", - "downloading_asset_filename": "Téléchargement du média {filename}", - "downloading_from_icloud": "Téléchargement depuis iCloud", - "downloading_media": "Téléchargement du média", - "drop_files_to_upload": "Déposez les fichiers n'importe où pour envoyer", - "duplicates": "Doublons", - "duplicates_description": "Examiner chaque groupe et indiquer s'il y a des doublons", - "duration": "Durée", - "edit": "Modifier", - "edit_album": "Modifier l'album", - "edit_avatar": "Modifier l'avatar", - "edit_birthday": "Modifier l'anniversaire", - "edit_date": "Modifier la date", - "edit_date_and_time": "Modifier la date et l'heure", - "edit_date_and_time_action_prompt": "{count} modifié(s) sur leur date et heure", - "edit_date_and_time_by_offset": "Ajouter un décalage à la date", - "edit_date_and_time_by_offset_interval": "Nouvelle plage de date : {from} - {to}", - "edit_description": "Modifier la description", - "edit_description_prompt": "Choisir une nouvelle description :", - "edit_exclusion_pattern": "Modifier le schéma d'exclusion", - "edit_faces": "Modifier les visages", - "edit_key": "Modifier la clé", - "edit_link": "Modifier le lien", - "edit_location": "Modifier la localisation", - "edit_location_action_prompt": "{count} localisation(s) mise(s) à jour", - "edit_location_dialog_title": "Localisation", - "edit_name": "Modifier le nom", - "edit_people": "Modifier les personnes", - "edit_tag": "Modifier l'étiquette", - "edit_title": "Modifier le titre", - "edit_user": "Modifier l'utilisateur", - "edit_workflow": "Modifier le flux de traitement", - "editor": "Editeur", - "editor_close_without_save_prompt": "Les changements ne seront pas enregistrés", - "editor_close_without_save_title": "Fermer l'éditeur ?", - "editor_confirm_reset_all_changes": "Êtes-vous sûr de vouloir réinitialiser toutes les modifications ?", - "editor_flip_horizontal": "Retourner horizontalement", - "editor_flip_vertical": "Retourner verticalement", - "editor_orientation": "Orientation", - "editor_reset_all_changes": "Réinitialiser les modifications", - "editor_rotate_left": "Rotation de 90° dans le sens inverse des aiguilles d'une montre", - "editor_rotate_right": "Rotation de 90° dans le sens des aiguilles d'une montre", - "email": "Courriel", - "email_notifications": "Notifications email", - "empty_folder": "Ce dossier est vide", - "empty_trash": "Vider la corbeille", - "empty_trash_confirmation": "Êtes-vous sûr de vouloir vider la corbeille ? Cela supprimera définitivement de Immich tous les médias qu'elle contient.\nVous ne pouvez pas annuler cette action !", - "enable": "Active", - "enable_backup": "Sauvegarde", - "enable_biometric_auth_description": "Entrez votre code PIN pour activer l'authentification biométrique", - "enabled": "Activé", - "end_date": "Date de fin", - "enqueued": "Mis en file d'attente", - "enter_wifi_name": "Entrez le nom du réseau wifi", - "enter_your_pin_code": "Entrez votre code PIN", - "enter_your_pin_code_subtitle": "Entrez votre code PIN pour accéder au dossier verrouillé", - "error": "Erreur", - "error_change_sort_album": "Impossible de modifier l'ordre de tri des albums", - "error_delete_face": "Erreur lors de la suppression du visage pour le média", - "error_getting_places": "Erreur à la récupération des lieux", - "error_loading_albums": "Erreur au chargement des albums", - "error_loading_image": "Erreur de chargement de l'image", - "error_loading_partners": "Erreur de récupération des partenaires : {error}", - "error_retrieving_asset_information": "Erreur à la récupération des informations du média", - "error_saving_image": "Erreur : {error}", - "error_tag_face_bounding_box": "Erreur lors de l'identification de visage - impossible de récupérer les coordonnées du cadre entourant le visage", - "error_title": "Erreur - Quelque chose s'est mal passé", - "error_while_navigating": "Erreur lors de la navigation vers le média", - "errors": { - "cannot_navigate_next_asset": "Impossible de naviguer jusqu'au prochain média", - "cannot_navigate_previous_asset": "Impossible de naviguer jusqu'au précédent média", - "cant_apply_changes": "Impossible d'appliquer les changements", - "cant_change_activity": "Impossible {enabled, select, true {d'interdire} other {d'autoriser}} l'activité", - "cant_change_asset_favorite": "Impossible de changer le favori du média", - "cant_change_metadata_assets_count": "Impossible de modifier les métadonnées {count, plural, one {d'un média} other {de # médias}}", - "cant_get_faces": "Impossible d'obtenir des visages", - "cant_get_number_of_comments": "Impossible d'obtenir le nombre de commentaires", - "cant_search_people": "Impossible de rechercher des personnes", - "cant_search_places": "Impossible de rechercher des lieux", - "error_adding_assets_to_album": "Erreur lors de l'ajout des médias à l'album", - "error_adding_users_to_album": "Erreur lors de l'ajout d'utilisateurs à l'album", - "error_deleting_shared_user": "Erreur lors de la suppression de l'utilisateur partagé", - "error_downloading": "Erreur lors du téléchargement de {filename}", - "error_hiding_buy_button": "Impossible de masquer le bouton d'achat", - "error_removing_assets_from_album": "Erreur lors de la suppression des médias de l'album, vérifier la console pour plus de détails", - "error_selecting_all_assets": "Erreur lors de la sélection de tous les médias", - "exclusion_pattern_already_exists": "Ce modèle d'exclusion existe déjà.", - "failed_to_create_album": "Impossible de créer l'album", - "failed_to_create_shared_link": "Impossible de créer le lien partagé", - "failed_to_edit_shared_link": "Impossible de modifier le lien partagé", - "failed_to_get_people": "Impossible d'obtenir les personnes", - "failed_to_keep_this_delete_others": "Impossible de conserver ce média et de supprimer les autres médias", - "failed_to_load_asset": "Impossible de charger le média", - "failed_to_load_assets": "Impossible de charger les médias", - "failed_to_load_notifications": "Erreur de récupération des notifications", - "failed_to_load_people": "Impossible de charger les personnes", - "failed_to_remove_product_key": "Échec de suppression de la clé du produit", - "failed_to_reset_pin_code": "Échec de la réinitialisation du code PIN", - "failed_to_stack_assets": "Impossible d'empiler les médias", - "failed_to_unstack_assets": "Impossible de dépiler les médias", - "failed_to_update_notification_status": "Erreur de mise à jour du statut des notifications", - "incorrect_email_or_password": "Courriel ou mot de passe incorrect", - "library_folder_already_exists": "Ce chemin d'import existe déjà.", - "paths_validation_failed": "Validation échouée pour {paths, plural, one {# un chemin} other {# plusieurs chemins}}", - "profile_picture_transparent_pixels": "Les images de profil ne peuvent pas avoir de pixels transparents. Veuillez agrandir et/ou déplacer l'image.", - "quota_higher_than_disk_size": "Le quota saisi est supérieur à l'espace disponible", - "something_went_wrong": "Une erreur est survenue", - "unable_to_add_album_users": "Impossible d'ajouter des utilisateurs à l'album", - "unable_to_add_assets_to_shared_link": "Impossible d'ajouter des médias au lien partagé", - "unable_to_add_comment": "Impossible d'ajouter un commentaire", - "unable_to_add_exclusion_pattern": "Impossible d'ajouter un schéma d'exclusion", - "unable_to_add_partners": "Impossible d'ajouter des partenaires", - "unable_to_add_remove_archive": "Impossible {archived, select, true {de supprimer des médias de} other {d'ajouter des médias à}} l'archive", - "unable_to_add_remove_favorites": "Impossible {favorite, select, true {d'ajouter des médias aux} other {de supprimer des médias des}} favoris", - "unable_to_archive_unarchive": "Impossible {archived, select, true {d'archiver} other {de supprimer de l'archive}}", - "unable_to_change_album_user_role": "Impossible de changer le rôle de l'utilisateur de l'album", - "unable_to_change_date": "Impossible de modifier la date", - "unable_to_change_description": "Échec de la modification de la description", - "unable_to_change_favorite": "Impossible de changer de favori pour le média", - "unable_to_change_location": "Impossible de changer la localisation", - "unable_to_change_password": "Impossible de changer le mot de passe", - "unable_to_change_visibility": "Impossible de changer la visibilité pour {count, plural, one {# personne} other {# personnes}}", - "unable_to_complete_oauth_login": "Impossible de terminer la connexion OAuth", - "unable_to_connect": "Impossible de se connecter", - "unable_to_copy_to_clipboard": "Impossible de copier dans le presse-papiers, assurez-vous que vous accédez à la page via https", - "unable_to_create": "Impossible de créer le flux de traitement", - "unable_to_create_admin_account": "Impossible de créer le compte administrateur", - "unable_to_create_api_key": "Impossible de créer une nouvelle clé API", - "unable_to_create_library": "Impossible de créer la bibliothèque", - "unable_to_create_user": "Impossible de créer l'utilisateur", - "unable_to_delete_album": "Impossible de supprimer l'album", - "unable_to_delete_asset": "Impossible de supprimer le média", - "unable_to_delete_assets": "Erreur lors de la suppression des médias", - "unable_to_delete_exclusion_pattern": "Impossible de supprimer le modèle d'exclusion", - "unable_to_delete_shared_link": "Impossible de supprimer le lien de partage", - "unable_to_delete_user": "Impossible de supprimer l'utilisateur", - "unable_to_delete_workflow": "Impossible de supprimer le flux de traitement", - "unable_to_download_files": "Impossible de télécharger les fichiers", - "unable_to_edit_exclusion_pattern": "Impossible de modifier le modèle d'exclusion", - "unable_to_empty_trash": "Impossible de vider la corbeille", - "unable_to_enter_fullscreen": "Mode plein écran indisponible", - "unable_to_exit_fullscreen": "Impossible de sortir du mode plein écran", - "unable_to_get_comments_number": "Impossible d'obtenir le nombre de commentaires", - "unable_to_get_shared_link": "Échec de la récupération du lien partagé", - "unable_to_hide_person": "Impossible de cacher la personne", - "unable_to_link_motion_video": "Impossible de lier la photo animée", - "unable_to_link_oauth_account": "Impossible de lier le compte OAuth", - "unable_to_log_out_all_devices": "Incapable de déconnecter tous les appareils", - "unable_to_log_out_device": "Impossible de déconnecter l'appareil", - "unable_to_login_with_oauth": "Impossible de se connecter avec OAuth", - "unable_to_play_video": "Impossible de lancer la vidéo", - "unable_to_reassign_assets_existing_person": "Impossible de réattribuer les médias à {name, select, null {une personne existante} other {{name}}}", - "unable_to_reassign_assets_new_person": "Impossible de réattribuer les médias à une nouvelle personne", - "unable_to_refresh_user": "Impossible d'actualiser l'utilisateur", - "unable_to_remove_album_users": "Impossible de supprimer les utilisateurs de l'album", - "unable_to_remove_api_key": "Impossible de supprimer la clé API", - "unable_to_remove_assets_from_shared_link": "Impossible de supprimer des médias du lien partagé", - "unable_to_remove_library": "Impossible de supprimer la bibliothèque", - "unable_to_remove_partner": "Impossible de supprimer le partenaire", - "unable_to_remove_reaction": "Impossible de supprimer la réaction", - "unable_to_reset_password": "Impossible de réinitialiser le mot de passe", - "unable_to_reset_pin_code": "Impossible de réinitialiser le code PIN", - "unable_to_resolve_duplicate": "Impossible de résoudre le doublon", - "unable_to_restore_assets": "Impossible de restaurer les médias", - "unable_to_restore_trash": "Impossible de restaurer la corbeille", - "unable_to_restore_user": "Impossible de restaurer l'utilisateur", - "unable_to_save_album": "Impossible de sauvegarder l'album", - "unable_to_save_api_key": "Impossible de sauvegarder la clé API", - "unable_to_save_date_of_birth": "Impossible de sauvegarder la date de naissance", - "unable_to_save_name": "Impossible de sauvegarder le nom", - "unable_to_save_profile": "Impossible de sauvegarder le profil", - "unable_to_save_settings": "Impossible d'enregistrer les préférences", - "unable_to_scan_libraries": "Impossible de scanner les bibliothèques", - "unable_to_scan_library": "Impossible de scanner la bibliothèque", - "unable_to_set_feature_photo": "Impossible de définir la photo de la personne", - "unable_to_set_profile_picture": "Impossible d'enregistrer la photo de profil", - "unable_to_set_rating": "Impossible de définir une note", - "unable_to_submit_job": "Impossible d'exécuter la tâche", - "unable_to_trash_asset": "Impossible de mettre le média à la corbeille", - "unable_to_unlink_account": "Impossible de détacher le compte", - "unable_to_unlink_motion_video": "Impossible de détacher la photo animée", - "unable_to_update_album_cover": "Impossible de mettre à jour la couverture de l'album", - "unable_to_update_album_info": "Impossible de mettre à jour les informations de l'album", - "unable_to_update_library": "Impossible de mettre à jour la bibliothèque", - "unable_to_update_location": "Impossible de mettre à jour la localisation", - "unable_to_update_settings": "Impossible de mettre à jour les paramètres", - "unable_to_update_timeline_display_status": "Impossible de mettre à jour le statut d'affichage de la vue chronologique", - "unable_to_update_user": "Impossible de mettre à jour l'utilisateur", - "unable_to_update_workflow": "Impossible de mettre à jour le flux de traitement", - "unable_to_upload_file": "Impossible d'envoyer le fichier" - }, - "errors_text": "Erreurs", - "exclusion_pattern": "Schéma d'exclusion", - "exif": "Exif", - "exif_bottom_sheet_description": "Ajouter une description...", - "exif_bottom_sheet_description_error": "Erreur de mise à jour de la description", - "exif_bottom_sheet_details": "DÉTAILS", - "exif_bottom_sheet_location": "LOCALISATION", - "exif_bottom_sheet_no_description": "Aucune description", - "exif_bottom_sheet_people": "PERSONNES", - "exif_bottom_sheet_person_add_person": "Ajouter un nom", - "exit_slideshow": "Quitter le diaporama", - "expand_all": "Tout développer", - "experimental_settings_new_asset_list_subtitle": "En cours de développement", - "experimental_settings_new_asset_list_title": "Activer la grille de photos expérimentale", - "experimental_settings_subtitle": "Utilisez à vos dépends !", - "experimental_settings_title": "Expérimental", - "expire_after": "Expire", - "expired": "Expiré", - "expires_date": "Expire le {date}", - "explore": "Explorer", - "explorer": "Explorateur", - "export": "Exporter", - "export_as_json": "Exporter en JSON", - "export_database": "Exporter la base de données", - "export_database_description": "Exporter la base de données SQLite", - "extension": "Extension", - "external": "Externe", - "external_libraries": "Bibliothèques externes", - "external_network": "Réseau externe", - "external_network_sheet_info": "Quand vous n'êtes pas connecté(e) à votre réseau wifi préféré, l'application va tenter de se connecter aux adresses ci-dessous, en commençant par la première", - "face_unassigned": "Non attribué", - "failed": "Échec", - "failed_count": "En erreur : {count}", - "failed_to_authenticate": "Échec de l'authentification", - "failed_to_load_assets": "Échec du chargement des ressources", - "failed_to_load_folder": "Échec de chargement du dossier", - "favorite": "Favori", - "favorite_action_prompt": "{count} ajouté(s) aux Favoris", - "favorite_or_unfavorite_photo": "Ajouter ou supprimer des favoris", - "favorites": "Favoris", - "favorites_page_no_favorites": "Aucun élément favori n'a été trouvé", - "feature_photo_updated": "Photo de la personne mise à jour", - "features": "Fonctionnalités", - "features_in_development": "Fonctionnalités en développement", - "features_setting_description": "Gérer les fonctionnalités de l'application", - "file_name_or_extension": "Nom du fichier ou extension", - "file_size": "Taille du fichier", - "filename": "Nom du fichier", - "filetype": "Type de fichier", - "filter": "Filtrer", - "filter_description": "Conditions pour filtrer les médias ciblés", - "filter_people": "Filtrer les personnes", - "filter_places": "Filtrer par lieu", - "filters": "Filtres", - "find_them_fast": "Pour les retrouver rapidement par leur nom", - "first": "Premier", - "fix_incorrect_match": "Corriger une association incorrecte", - "folder": "Dossier", - "folder_not_found": "Dossier introuvable", - "folders": "Dossiers", - "folders_feature_description": "Parcourir l'affichage par dossiers pour les photos et les vidéos sur le système de fichiers", - "forgot_pin_code_question": "Code PIN oublié ?", - "forward": "Avant", - "free_up_space": "Libérer de l'espace", - "free_up_space_description": "Déplacer les photos et vidéos sauvegardées vers la corbeille de votre appareil pour libérer de l'espace. Vos copies sur le serveur restent en sécurité.", - "free_up_space_settings_subtitle": "Libérer l'espace de votre appareil", - "full_path": "Chemin complet : {path}", - "gcast_enabled": "Diffusion Google Cast", - "gcast_enabled_description": "Cette fonctionnalité charge des ressources externes depuis Google pour fonctionner.", - "general": "Général", - "geolocation_instruction_location": "Cliquez sur un média avec des coordonnées GPS pour utiliser sa localisation, ou bien sélectionnez une localisation directement sur la carte", - "get_help": "Obtenir de l'aide", - "get_people_error": "Erreur de récupération des personnes", - "get_wifiname_error": "Impossible d'obtenir le nom du réseau wifi. Assurez-vous d'avoir donné les permissions nécessaires à l'application et que vous êtes connecté à un réseau wifi", - "getting_started": "Commencer", - "go_back": "Retour", - "go_to_folder": "Dossier", - "go_to_search": "Faire une recherche", - "gps": "GPS", - "gps_missing": "Pas de GPS", - "grant_permission": "Accorder les permissions", - "group_albums_by": "Grouper les albums par...", - "group_country": "Grouper par pays", - "group_no": "Pas de groupe", - "group_owner": "Grouper par propriétaire", - "group_places_by": "Grouper les lieux par...", - "group_year": "Grouper par année", - "haptic_feedback_switch": "Activer le retour haptique", - "haptic_feedback_title": "Retour haptique", - "has_quota": "Quota", - "hash_asset": "Hasher le média", - "hashed_assets": "Média hashés", - "hashing": "Hash", - "header_settings_add_header_tip": "Ajouter un en-tête", - "header_settings_field_validator_msg": "Cette valeur ne peut pas être vide", - "header_settings_header_name_input": "Nom de l'en-tête", - "header_settings_header_value_input": "Valeur de l'en-tête", - "headers_settings_tile_title": "En-têtes de proxy personnalisés", - "height": "Hauteur", - "hi_user": "Bonjour {name} ({email})", - "hide_all_people": "Cacher toutes les personnes", - "hide_gallery": "Masquer la galerie", - "hide_named_person": "Masquer {name}", - "hide_password": "Masquer le mot de passe", - "hide_person": "Masquer la personne", - "hide_schema": "Masquer le schéma", - "hide_text_recognition": "Cacher la reconnaissance de texte", - "hide_unnamed_people": "Cacher les personnes non nommées", - "home_page_add_to_album_conflicts": "{added} éléments ajoutés à l'album {album}. {failed} éléments sont déjà dans l'album.", - "home_page_add_to_album_err_local": "Impossible d'ajouter des médias locaux aux albums, ils sont ignorés", - "home_page_add_to_album_success": "{added} éléments ajoutés à l'album {album}.", - "home_page_album_err_partner": "Impossible d'ajouter des médias d'un partenaire à un album, ils sont ignorés", - "home_page_archive_err_local": "Impossible d'archiver les médias locaux, ils sont ignorés", - "home_page_archive_err_partner": "Impossible d'archiver les médias d'un partenaire, ils sont ignorés", - "home_page_building_timeline": "Construction de la chronologie", - "home_page_delete_err_partner": "Impossible de supprimer les médias d'un partenaire, ils sont ignorés", - "home_page_delete_remote_err_local": "Des médias locaux sont dans la sélection de suppression à distance, ils sont ignorés", - "home_page_favorite_err_local": "Impossible d'ajouter des médias locaux aux favoris, ils sont ignorés", - "home_page_favorite_err_partner": "Impossible de mettre en favori les médias d'un partenaire, ils sont ignorés", - "home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidéos de cet ou ces albums", - "home_page_locked_error_local": "Impossible de déplacer l'objet vers le dossier verrouillé, passer", - "home_page_locked_error_partner": "Impossible de déplacer l'objet du collaborateur vers le dossier verrouillé, opération ignorée", - "home_page_share_err_local": "Impossible de partager par lien les médias locaux, ils sont ignorés", - "home_page_upload_err_limit": "Impossible d'envoyer plus de 30 médias en même temps, demande ignorée", - "host": "Hôte", - "hour": "Heure", - "hours": "Heures", - "id": "ID", - "idle": "Inactif", - "ignore_icloud_photos": "Ignorer les photos iCloud", - "ignore_icloud_photos_description": "Les photos stockées sur iCloud ne seront pas envoyées sur le serveur Immich", - "image": "Image", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} prise le {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} prise avec {person1} le {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} prise avec {person1} et {person2} le {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} prise avec {person1}, {person2}, et {person3} le {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} prise avec {person1}, {person2} et {additionalCount, number} autres personnes le {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} le {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1} le {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1} et {person2} le {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1}, {person2}, et {person3} le {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1}, {person2} et {additionalCount, number} autres personnes le {date}", - "image_saved_successfully": "Image enregistré", - "image_viewer_page_state_provider_download_started": "Téléchargement démarré", - "image_viewer_page_state_provider_download_success": "Téléchargement réussi", - "image_viewer_page_state_provider_share_error": "Erreur de partage", - "immich_logo": "Logo Immich", - "immich_web_interface": "Interface Web Immich", - "import_from_json": "Importer depuis un fichier JSON", - "import_path": "Chemin d'importation", - "in_albums": "Dans {count, plural, one {# album} other {# albums}}", - "in_archive": "Dans les archives", - "in_year": "Dans {year}", - "in_year_selector": "Dans", - "include_archived": "Inclure les archives", - "include_shared_albums": "Inclure les albums partagés", - "include_shared_partner_assets": "Inclure les médias partagés du partenaire", - "individual_share": "Partage d'un média unique", - "individual_shares": "Partages d'un média unique", - "info": "Information", - "interval": { - "day_at_onepm": "Tous les jours à 13h", - "hours": "Toutes les {hours, plural, one {heures} other {{hours, number} heures}}", - "night_at_midnight": "Tous les jours à minuit", - "night_at_twoam": "Tous les jours à 2h" - }, - "invalid_date": "Date invalide", - "invalid_date_format": "Format de date invalide", - "invite_people": "Inviter une personne", - "invite_to_album": "Inviter à l'album", - "ios_debug_info_fetch_ran_at": "Récupération lancée {dateTime}", - "ios_debug_info_last_sync_at": "Dernière synchronisation {dateTime}", - "ios_debug_info_no_processes_queued": "Aucun processus en arrière plan en attente de traitement", - "ios_debug_info_no_sync_yet": "Le traitement de synchronisation en arrière plan n'a jamais été lancé", - "ios_debug_info_processes_queued": "{count, plural, one {{count} processus en arrière plan en attente de traitement} other {{count} processus en arrière plan en attente de traitement}}", - "ios_debug_info_processing_ran_at": "Le traitement a été lancé {dateTime}", - "items_count": "{count, plural, one {# élément} other {# éléments}}", - "jobs": "Tâches", - "json_editor": "Éditeur JSON", - "json_error": "Erreur JSON", - "keep": "Conserver", - "keep_albums": "Conserver les albums", - "keep_albums_count": "Conserver {count} {count, plural, one {album} other {albums}}", - "keep_all": "Les conserver tous", - "keep_description": "Choisissez ce qui reste sur votre appareil quand vous libérez de l'espace.", - "keep_favorites": "Garder les favoris", - "keep_on_device": "Conserver sur l'appareil", - "keep_on_device_hint": "Sélectionnez les éléments à conserver sur cet appareil", - "keep_this_delete_others": "Conserver celui-ci, supprimer les autres", - "keeping": "Conservé : {items}", - "kept_this_deleted_others": "Ce média a été conservé, et {count, plural, one {un autre a été supprimé} other {# autres ont été supprimés}}", - "keyboard_shortcuts": "Raccourcis clavier", - "language": "Langue", - "language_no_results_subtitle": "Essayer d'affiner vos critères de recherche", - "language_no_results_title": "Aucune langue trouvée", - "language_search_hint": "Recherche de langues...", - "language_setting_description": "Sélectionnez votre langue préférée", - "large_files": "Fichiers volumineux", - "last": "Dernier", - "last_months": "{count, plural, one {Dernier mois} other {Derniers # mois}}", - "last_seen": "Dernièrement utilisé", - "latest_version": "Dernière version", - "latitude": "Latitude", - "leave": "Quitter", - "leave_album": "Quitter l'album", - "lens_model": "Modèle d'objectif", - "let_others_respond": "Laisser les autres réagir", - "level": "Niveau", - "library": "Bibliothèque", - "library_add_folder": "Ajouter un dossier", - "library_edit_folder": "Modifier un dossier", - "library_options": "Options de bibliothèque", - "library_page_device_albums": "Albums sur l'appareil", - "library_page_new_album": "Nouvel album", - "library_page_sort_asset_count": "Nombre d'éléments", - "library_page_sort_created": "Créations les plus récentes", - "library_page_sort_last_modified": "Dernière modification", - "library_page_sort_title": "Titre de l'album", - "licenses": "Licences", - "light": "Clair", - "like": "J'aime", - "like_deleted": "Réaction « J'aime » supprimée", - "link_motion_video": "Lier la photo animée", - "link_to_oauth": "Lien au service OAuth", - "linked_oauth_account": "Compte OAuth rattaché", - "list": "Liste", - "loading": "Chargement", - "loading_search_results_failed": "Chargement des résultats échoué", - "local": "Local", - "local_asset_cast_failed": "Impossible de caster un média qui n'a pas envoyé vers le serveur", - "local_assets": "Média locaux", - "local_id": "ID locale", - "local_media_summary": "Résumé du média local", - "local_network": "Réseau local", - "local_network_sheet_info": "L'application va se connecter au serveur via cette URL quand l'appareil est connecté à ce réseau Wi-Fi", - "location": "Localisation", - "location_permission": "Autorisation de localisation", - "location_permission_content": "Afin de pouvoir changer d'adresse automatiquement, Immich doit avoir accès à la localisation précise, afin d'accéder au nom du réseau wifi utilisé", - "location_picker_choose_on_map": "Sélectionner sur la carte", - "location_picker_latitude_error": "Saisir une latitude correcte", - "location_picker_latitude_hint": "Saisir la latitude ici", - "location_picker_longitude_error": "Saisir une longitude correcte", - "location_picker_longitude_hint": "Saisir la longitude ici", - "lock": "Verrouiller", - "locked_folder": "Dossier verrouillé", - "log_detail_title": "Niveau de journalisation", - "log_out": "Se déconnecter", - "log_out_all_devices": "Déconnecter tous les appareils", - "logged_in_as": "Connecté en tant que {user}", - "logged_out_all_devices": "Déconnecté de tous les appareils", - "logged_out_device": "Déconnecté de l'appareil", - "login": "Connexion", - "login_disabled": "La connexion a été désactivée", - "login_form_api_exception": "Erreur de l'API. Veuillez vérifier l'URL du serveur et réessayer.", - "login_form_back_button_text": "Retour", - "login_form_email_hint": "votrecourriel@email.com", - "login_form_endpoint_hint": "http://adresse-ip-serveur:port", - "login_form_endpoint_url": "URL du point d'accès au serveur", - "login_form_err_http": "Veuillez préciser http:// ou https://", - "login_form_err_invalid_email": "Courriel invalide", - "login_form_err_invalid_url": "URL invalide", - "login_form_err_leading_whitespace": "Espace en début de ligne", - "login_form_err_trailing_whitespace": "Espace de fin de ligne", - "login_form_failed_get_oauth_server_config": "Erreur de connexion par OAuth, vérifiez l\"URL du serveur", - "login_form_failed_get_oauth_server_disable": "La fonctionnalité OAuth n'est pas disponible sur ce serveur", - "login_form_failed_login": "Erreur de connexion, vérifiez l'url du serveur, le courriel et le mot de passe", - "login_form_handshake_exception": "Il y a eu une exception de liaison avec le serveur. Activez la prise en charge des certificats auto-signés dans les paramètres si vous utilisez un certificat auto-signé.", - "login_form_password_hint": "mot de passe", - "login_form_save_login": "Rester connecté", - "login_form_server_empty": "Saisissez l'URL du serveur.", - "login_form_server_error": "Impossible de se connecter au serveur.", - "login_has_been_disabled": "La connexion a été désactivée.", - "login_password_changed_error": "Une erreur s'est produite lors de la mise à jour de votre mot de passe", - "login_password_changed_success": "Mot de passe mis à jour avec succès", - "logout_all_device_confirmation": "Êtes-vous sûr de vouloir déconnecter tous les appareils ?", - "logout_this_device_confirmation": "Êtes-vous sûr de vouloir déconnecter cet appareil ?", - "logs": "Journaux", - "longitude": "Longitude", - "look": "Regarder", - "loop_videos": "Vidéos en boucle", - "loop_videos_description": "Activer pour voir la vidéo en boucle dans le lecteur détaillé.", - "main_branch_warning": "Vous utilisez une version de développement. Nous vous recommandons fortement d'utiliser une version stable !", - "main_menu": "Menu principal", - "maintenance_action_restore": "Restauration de la base de données", - "maintenance_description": "Immich a été mis en mode maintenance.", - "maintenance_end": "Arrêter le mode maintenance", - "maintenance_end_error": "Échec de l'arrêt du mode maintenance.", - "maintenance_logged_in_as": "Actuellement connecté en tant que {user}", - "maintenance_restore_from_backup": "Restaurer à partir d'une sauvegarde", - "maintenance_restore_library": "Restaurer votre bibliothèque", - "maintenance_restore_library_confirm": "Si cela vous semble correct, continuez à restaurer une sauvegarde !", - "maintenance_restore_library_description": "Restauration de la base de données", - "maintenance_restore_library_folder_has_files": "Le dossier {folder} contient {count} dossier(s)", - "maintenance_restore_library_folder_no_files": "Il manque des fichiers dans {folder}  !", - "maintenance_restore_library_folder_pass": "lecture et écriture", - "maintenance_restore_library_folder_read_fail": "lecture impossible", - "maintenance_restore_library_folder_write_fail": "écriture impossible", - "maintenance_restore_library_hint_missing_files": "Vous risquez de perdre des fichiers importants", - "maintenance_restore_library_hint_regenerate_later": "Vous pouvez les régénérer ultérieurement dans les paramètres", - "maintenance_restore_library_hint_storage_template_missing_files": "Vous utilisez un modèle de stockage ? Il se peut que certains fichiers soient manquants", - "maintenance_restore_library_loading": "Chargement des contrôles d'intégrité et des heuristiques…", - "maintenance_task_backup": "Création d'une sauvegarde de la base de données existante…", - "maintenance_task_migrations": "Exécution des migrations de base de données…", - "maintenance_task_restore": "Restauration de la sauvegarde sélectionnée…", - "maintenance_task_rollback": "La restauration a échoué, retour au point de restauration…", - "maintenance_title": "Temporairement non disponible", - "make": "Marque", - "manage_geolocation": "Gérer la localisation", - "manage_media_access_rationale": "Cette autorisation est nécessaire pour gérer correctement le déplacement de médias vers la corbeille et la restauration depuis celle-ci.", - "manage_media_access_settings": "Ouvrir les paramètres", - "manage_media_access_subtitle": "Autoriser l'application Immich à gérer et déplacer des fichiers de média.", - "manage_media_access_title": "Accès à la gestion de médias", - "manage_shared_links": "Gérer les liens partagés", - "manage_sharing_with_partners": "Gérer le partage avec les partenaires", - "manage_the_app_settings": "Gérer les paramètres de l'application", - "manage_your_account": "Gérer votre compte", - "manage_your_api_keys": "Gérer vos clés API", - "manage_your_devices": "Gérer vos appareils", - "manage_your_oauth_connection": "Gérer votre connexion OAuth", - "map": "Carte", - "map_assets_in_bounds": "{count, plural, =0 {Aucune photo dans cette zone} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "Impossible d'obtenir la localisation de l'utilisateur", - "map_location_dialog_yes": "Oui", - "map_location_picker_page_use_location": "Utiliser ma position", - "map_location_service_disabled_content": "Le service de localisation doit être activé pour afficher les médias de votre emplacement actuel. Souhaitez-vous l'activer maintenant ?", - "map_location_service_disabled_title": "Service de localisation désactivé", - "map_marker_for_images": "Marqueur de carte pour les images prises à {city}, {country}", - "map_marker_with_image": "Marqueur de carte avec image", - "map_no_location_permission_content": "L'autorisation de localisation est nécessaire pour afficher les médias de votre emplacement actuel. Souhaitez-vous l'autoriser maintenant ?", - "map_no_location_permission_title": "Permission de localisation refusée", - "map_settings": "Paramètres de la carte", - "map_settings_dark_mode": "Mode sombre", - "map_settings_date_range_option_day": "Dernières 24 heures", - "map_settings_date_range_option_days": "{days} derniers jours", - "map_settings_date_range_option_year": "Année passée", - "map_settings_date_range_option_years": "{years} dernières années", - "map_settings_dialog_title": "Paramètres de la carte", - "map_settings_include_show_archived": "Inclure les archives", - "map_settings_include_show_partners": "Inclure les partenaires", - "map_settings_only_show_favorites": "Afficher uniquement les favoris", - "map_settings_theme_settings": "Thème de la carte", - "map_zoom_to_see_photos": "Dézoomer pour voir les photos", - "mark_all_as_read": "Tout marquer comme lu", - "mark_as_read": "Marquer comme lu", - "marked_all_as_read": "Tout a été marqué comme lu", - "matches": "Correspondances", - "matching_assets": "Médias correspondants", - "media_type": "Type de média", - "memories": "Souvenirs", - "memories_all_caught_up": "Vous avez tout vu", - "memories_check_back_tomorrow": "Revenez demain pour d'autres souvenirs", - "memories_setting_description": "Gérer ce que vous voyez dans vos souvenirs", - "memories_start_over": "Recommencer", - "memories_swipe_to_close": "Balayez vers le haut pour fermer", - "memory": "Souvenir", - "memory_lane_title": "Fil de souvenirs {title}", - "menu": "Menu", - "merge": "Fusionner", - "merge_people": "Fusionner les personnes", - "merge_people_limit": "Vous pouvez seulement fusionner 5 visages à la fois", - "merge_people_prompt": "Voulez-vous fusionner ces personnes ? Cette action est irréversible.", - "merge_people_successfully": "Fusion des personnes réussie", - "merged_people_count": "{count, plural, one {# personne fusionnée} other {# personnes fusionnées}}", - "minimize": "Réduire", - "minute": "Minute", - "minutes": "Minutes", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertical", - "missing": "Manquant", - "mobile_app": "Appli mobile", - "mobile_app_download_onboarding_note": "Téléchargez l'application mobile compagnon via les options suivantes", - "model": "Modèle", - "month": "Mois", - "monthly_title_text_date_format": "MMMM y", - "more": "Plus", - "move": "Déplacer", - "move_down": "Descendre", - "move_off_locked_folder": "Déplacer en dehors du dossier verrouillé", - "move_to": "Déplacer vers", - "move_to_device_trash": "Déplacer vers la corbeille de l'appareil", - "move_to_lock_folder_action_prompt": "{count} ajouté(s) au dossier verrouillé", - "move_to_locked_folder": "Déplacer dans le dossier verrouillé", - "move_to_locked_folder_confirmation": "Ces photos et vidéos seront retirées de tous les albums et ne seront visibles que dans le dossier verrouillé", - "move_up": "Monter", - "moved_to_archive": "{count, plural, one {# élément déplacé} other {# éléments déplacés}} vers les archives", - "moved_to_library": "{count, plural, one {# élément déplacé} other {# éléments déplacés}} vers la bibliothèque", - "moved_to_trash": "Déplacé dans la corbeille", - "multiselect_grid_edit_date_time_err_read_only": "Impossible de modifier la date de médias en lecture seule, ils sont ignorés", - "multiselect_grid_edit_gps_err_read_only": "Impossible de modifier l'emplacement de médias en lecture seule, ils sont ignorés", - "mute_memories": "Mettre en sourdine les souvenirs", - "my_albums": "Mes albums", - "name": "Nom", - "name_or_nickname": "Nom ou surnom", - "name_required": "Le nom est nécessaire", - "navigate": "Naviguer vers", - "navigate_to_time": "Naviguer vers Date/Heure", - "network_requirement_photos_upload": "Utiliser les données mobile pour sauvegarder les photos", - "network_requirement_videos_upload": "Utiliser les données mobile pour sauvegarder les vidéos", - "network_requirements": "Prérequis réseau", - "network_requirements_updated": "Contraintes réseau modifiées, file d'attente de sauvegarde réinitialisée", - "networking_settings": "Réseau", - "networking_subtitle": "Gérer les adresses du serveur", - "never": "Jamais", - "new_album": "Nouvel Album", - "new_api_key": "Nouvelle clé API", - "new_date_range": "Nouvelle plage de date", - "new_password": "Nouveau mot de passe", - "new_person": "Nouvelle personne", - "new_pin_code": "Nouveau code PIN", - "new_pin_code_subtitle": "C'est votre premier accès au dossier verrouillé. Créez un code PIN pour sécuriser l'accès à cette page", - "new_timeline": "Nouvelle vue chronologique", - "new_update": "Nouvelle mise à jour", - "new_user_created": "Nouvel utilisateur créé", - "new_version_available": "NOUVELLE VERSION DISPONIBLE", - "newest_first": "Récents en premier", - "next": "Suivant", - "next_memory": "Souvenir suivant", - "no": "Non", - "no_actions_added": "Aucune action ajoutée pour le moment", - "no_albums_found": "Aucun album trouvé", - "no_albums_message": "Créer un album pour organiser vos photos et vidéos", - "no_albums_with_name_yet": "Il semble que vous n'ayez pas encore d'albums avec ce nom.", - "no_albums_yet": "Il semble que vous n'ayez pas encore d'album.", - "no_archived_assets_message": "Archiver des photos et vidéos pour les masquer dans votre bibliothèque", - "no_assets_message": "Cliquez pour envoyer votre première photo", - "no_assets_to_show": "Aucun élément à afficher", - "no_cast_devices_found": "Aucun appareil de diffusion trouvé", - "no_checksum_local": "Aucune empreinte numerique disponible - impossible de récupérer les médias locaux", - "no_checksum_remote": "Aucune empreinte numérique disponible - impossible de récupérer les médias distants", - "no_configuration_needed": "Aucune configuration nécessaire", - "no_devices": "Aucun appareil autorisé", - "no_duplicates_found": "Aucun doublon n'a été trouvé.", - "no_exif_info_available": "Aucune information exif disponible", - "no_explore_results_message": "Envoyez plus de photos pour explorer votre bibliothèque.", - "no_favorites_message": "Ajouter des photos et vidéos à vos favoris pour les retrouver plus rapidement", - "no_filters_added": "Aucun filtre ajouté pour le moment", - "no_libraries_message": "Créer une bibliothèque externe pour voir vos photos et vidéos dans un autre espace de stockage", - "no_local_assets_found": "Aucun média local trouvé avec cette empreinte numerique", - "no_location_set": "Aucune localisation definie", - "no_locked_photos_message": "Les photos et vidéos du dossier verrouillé sont masqués et ne s'afficheront pas dans votre galerie ou la recherche.", - "no_name": "Pas de nom", - "no_notifications": "Pas de notification", - "no_people_found": "Aucune personne correspondante trouvée", - "no_places": "Pas de lieu", - "no_remote_assets_found": "Aucun média distant trouvé avec cette empreinte numerique", - "no_results": "Aucun résultat", - "no_results_description": "Essayez un synonyme ou un mot-clé plus général", - "no_shared_albums_message": "Créer un album pour partager vos photos et vidéos avec les personnes de votre réseau", - "no_uploads_in_progress": "Pas d'envoi en cours", - "none": "Aucun", - "not_allowed": "Non autorisé", - "not_available": "N/A", - "not_in_any_album": "Dans aucun album", - "not_selected": "Non sélectionné", - "note_apply_storage_label_to_previously_uploaded assets": "Note : Pour appliquer l'étiquette de stockage aux médias précédemment envoyés, exécutez", - "notes": "Notes", - "nothing_here_yet": "Rien pour le moment", - "notification_permission_dialog_content": "Pour activer les notifications, allez dans Paramètres et sélectionnez Autoriser.", - "notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.", - "notification_permission_list_tile_enable_button": "Activer les notifications", - "notification_permission_list_tile_title": "Permission de notification", - "notification_toggle_setting_description": "Activer les notifications par courriel", - "notifications": "Notifications", - "notifications_setting_description": "Gérer les notifications", - "oauth": "OAuth", - "obtainium_configurator": "Configuration pour Obtainium", - "obtainium_configurator_instructions": "Utilisez Obtainium pour installer et mettre à jour l'application Android directement depuis la version d'Immich sur Github. Créer une clé d'API et sélectionner une variante pour créer votre lien de configuration pour Obtainium", - "ocr": "Reconnaissance Optique de Caractères", - "official_immich_resources": "Ressources Immich officielles", - "offline": "Hors ligne", - "offset": "Décalage", - "ok": "OK", - "oldest_first": "Anciens en premier", - "on_this_device": "Sur cet appareil", - "onboarding": "Accueil", - "onboarding_locale_description": "Choisissez vote langue préférée. Vous pourrez en changer par la suite dans les paramètres.", - "onboarding_privacy_description": "Les fonctions suivantes (optionnelles) dépendent de services externes et peuvent être désactivées à tout moment dans les paramètres.", - "onboarding_server_welcome_description": "Démarrons votre instance avec quelques paramètres courants.", - "onboarding_theme_description": "Choisissez un thème de couleur pour votre instance. Vous pouvez le changer plus tard dans vos paramètres.", - "onboarding_user_welcome_description": "Commençons !", - "onboarding_welcome_user": "Bienvenue {user}", - "online": "En ligne", - "only_favorites": "Uniquement les favoris", - "open": "Ouvrir", - "open_in_map_view": "Montrer sur la carte", - "open_in_openstreetmap": "Ouvrir dans OpenStreetMap", - "open_the_search_filters": "Ouvrir les filtres de recherche", - "options": "Options", - "or": "ou", - "organize_into_albums": "Organiser dans des albums", - "organize_into_albums_description": "Mettre les photos existantes dans des albums en utilisant les paramètres de synchronisation actuels", - "organize_your_library": "Organiser votre bibliothèque", - "original": "original", - "other": "Autre", - "other_devices": "Autres appareils", - "other_entities": "Autres entités", - "other_variables": "Autres variables", - "owned": "Possédé", - "owner": "Propriétaire", - "page": "Page", - "partner": "Partenaire", - "partner_can_access": "{partner} a accès", - "partner_can_access_assets": "Toutes vos photos et vidéos, exceptées celles archivées ou supprimées", - "partner_can_access_location": "La localisation de vos photos et vidéos", - "partner_list_user_photos": "Photos de {user}", - "partner_list_view_all": "Voir tous", - "partner_page_empty_message": "Vos photos ne sont pas encore partagées avec un partenaire.", - "partner_page_no_more_users": "Plus d'utilisateurs à ajouter", - "partner_page_partner_add_failed": "Échec de l'ajout d'un partenaire", - "partner_page_select_partner": "Sélectionner un partenaire", - "partner_page_shared_to_title": "Partagé avec", - "partner_page_stop_sharing_content": "{partner} ne pourra plus accéder à vos photos.", - "partner_sharing": "Partage avec les partenaires", - "partners": "Partenaires", - "password": "Mot de passe", - "password_does_not_match": "Les mots de passe ne correspondent pas", - "password_required": "Mot de passe obligatoire", - "password_reset_success": "Mot de passe réinitialisé avec succès", - "past_durations": { - "days": "{days, plural, one {Dernier jour} other {# derniers jours}}", - "hours": "{hours, plural, one {Dernière heure} other {# dernières heures}}", - "years": "{years, plural, one {Dernière année} other {# dernières années}}" - }, - "path": "Chemin", - "pattern": "Schéma", - "pause": "Pause", - "pause_memories": "Mettre en pause les souvenirs", - "paused": "En pause", - "pending": "En attente", - "people": "Personnes", - "people_edits_count": "{count, plural, one {# personne éditée} other {# personnes éditées}}", - "people_feature_description": "Parcourir les photos et vidéos groupées par personnes", - "people_selected": "{count, plural, one {# personne sélectionnée} other {# personnes sélectionnées}}", - "people_sidebar_description": "Afficher le menu Personnes dans la barre latérale", - "permanent_deletion_warning": "Avertissement avant suppression définitive", - "permanent_deletion_warning_setting_description": "Afficher un avertissement avant la suppression définitive d'un média", - "permanently_delete": "Supprimer définitivement", - "permanently_delete_assets_count": "Suppression définitive {count, plural, one {du média} other {des médias}}", - "permanently_delete_assets_prompt": "Êtes-vous sûr de vouloir supprimer définitivement {count, plural, one {ce média ?} other {ces # médias ?}} Cela {count, plural, one {le} other {les}} supprimera aussi de {count, plural, one {son (ses)} other {leur(s)}} album(s).", - "permanently_deleted_asset": "Média supprimé définitivement", - "permanently_deleted_assets_count": "{count, plural, one {# média définitivement supprimé} other {# médias définitivement supprimés}}", - "permission": "Autorisation", - "permission_empty": "Votre autorisation ne doit pas être vide", - "permission_onboarding_back": "Retour", - "permission_onboarding_continue_anyway": "Continuer quand même", - "permission_onboarding_get_started": "Commencer", - "permission_onboarding_go_to_settings": "Accéder aux paramètres", - "permission_onboarding_permission_denied": "Permission refusée. Pour utiliser Immich, accordez lautorisation pour les photos et vidéos dans les Paramètres.", - "permission_onboarding_permission_granted": "Permission accordée ! Vous êtes prêts.", - "permission_onboarding_permission_limited": "Permission limitée. Pour permettre à Immich de sauvegarder et de gérer l'ensemble de votre bibliothèque, accordez l'autorisation pour les photos et vidéos dans les Paramètres.", - "permission_onboarding_request": "Immich nécessite l'autorisation d'accéder à vos photos et vidéos.", - "person": "Personne", - "person_age_months": "{months, plural, one {# mois} other {# mois}}", - "person_age_year_months": "1 an, {months, plural, one {# mois} other {# mois}}", - "person_age_years": "{years, plural, other {# ans}}", - "person_birthdate": "Né(e) le {date}", - "person_hidden": "{name}{hidden, select, true { (caché)} other {}}", - "person_recognized": "Personne reconnue", - "person_selected": "Personne sélectionnée", - "photo_shared_all_users": "Il semble que vous ayez partagé vos photos avec tous les utilisateurs ou que vous n'ayez aucun utilisateur avec qui les partager.", - "photos": "Photos", - "photos_and_videos": "Photos et vidéos", - "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", - "photos_from_previous_years": "Photos des années précédentes", - "photos_only": "Photos uniquement", - "pick_a_location": "Choisissez une localisation", - "pick_custom_range": "Période personnalisée", - "pick_date_range": "Sélectionner une période de dates", - "pin_code_changed_successfully": "Code PIN changé avec succès", - "pin_code_reset_successfully": "Réinitialisation du code PIN réussie", - "pin_code_setup_successfully": "Définition du code PIN réussie", - "pin_verification": "Vérification du code PIN", - "place": "Lieu", - "places": "Lieux", - "places_count": "{count, plural, one {{count, number} Lieu} other {{count, number} Lieux}}", - "play": "Lancer", - "play_memories": "Lancer les souvenirs", - "play_motion_photo": "Jouer la photo animée", - "play_or_pause_video": "Lancer ou mettre en pause la vidéo", - "play_original_video": "Lire la vidéo originale", - "play_original_video_setting_description": "Préférer la lecture des vidéos originales plutôt que les vidéos transcodées. Si le média original n'est pas compatible, il pourrait ne pas être lu correctement.", - "play_transcoded_video": "Lire la vidéo transcodée", - "please_auth_to_access": "Merci de vous authentifier pour accéder", - "port": "Port", - "preferences_settings_subtitle": "Gérer les préférences de l'application", - "preferences_settings_title": "Préférences", - "preparing": "Préparation", - "preset": "Préréglage", - "preview": "Aperçu", - "previous": "Précédent", - "previous_memory": "Souvenir précédent", - "previous_or_next_day": "Jour précédent / suivant", - "previous_or_next_month": "Mois précédent / suivant", - "previous_or_next_photo": "Photo précédente / suivante", - "previous_or_next_year": "Année précédente / suivante", - "primary": "Primaire", - "privacy": "Vie privée", - "profile": "Profil", - "profile_drawer_app_logs": "Journaux", - "profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mode lecture seule activé. Faites un appui long sur l'image de l'utilisateur pour quitter.", - "profile_image_of_user": "Image de profil de {user}", - "profile_picture_set": "Photo de profil définie.", - "public_album": "Album public", - "public_share": "Partage public", - "purchase_account_info": "Contributeur", - "purchase_activated_subtitle": "Merci d'avoir apporté votre soutien à Immich et aux logiciels open source", - "purchase_activated_time": "Activé le {date}", - "purchase_activated_title": "Votre clé a été activée avec succès", - "purchase_button_activate": "Activer", - "purchase_button_buy": "Acheter", - "purchase_button_buy_immich": "Acheter Immich", - "purchase_button_never_show_again": "Ne plus l'afficher", - "purchase_button_reminder": "Me le rappeler dans 30 jours", - "purchase_button_remove_key": "Supprimer la clé", - "purchase_button_select": "Sélectionner", - "purchase_failed_activation": "Échec de l'activation. Veuillez vérifier votre courriel pour obtenir la clé correcte du produit !", - "purchase_individual_description_1": "Pour un utilisateur", - "purchase_individual_description_2": "Statut de contributeur", - "purchase_individual_title": "Utilisateur", - "purchase_input_suggestion": "Si vous avez déjà une clé de produit, renseignez-la ci-dessous", - "purchase_license_subtitle": "Acheter Immich pour soutenir le développement de ce service", - "purchase_lifetime_description": "Achat à vie", - "purchase_option_title": "OPTIONS D'ACHAT", - "purchase_panel_info_1": "Développer Immich nécessite du temps et de l'énergie, et nous avons des ingénieurs qui travaillent à plein temps pour en faire le meilleur produit possible. Notre mission est de générer, pour les logiciels open source et les pratiques de travail éthique, une source de revenus suffisante pour les développeurs et de créer un écosystème respectueux de la vie privée grâce a des alternatives crédibles aux services cloud peu scrupuleux.", - "purchase_panel_info_2": "Étant donné que nous nous engageons à ne pas ajouter de fonctionnalités payantes, cet achat ne vous donnera pas de fonctionnalités supplémentaires dans Immich. Nous comptons sur des utilisateurs comme vous pour soutenir le développement continu d'Immich.", - "purchase_panel_title": "Soutenir le projet", - "purchase_per_server": "Par serveur", - "purchase_per_user": "Par utilisateur", - "purchase_remove_product_key": "Supprimer la clé du produit", - "purchase_remove_product_key_prompt": "Êtes-vous sûr de vouloir supprimer la clé du produit ?", - "purchase_remove_server_product_key": "Supprimer la clé du produit pour le Serveur", - "purchase_remove_server_product_key_prompt": "Êtes-vous sûr de vouloir supprimer la clé du produit pour le Serveur ?", - "purchase_server_description_1": "Pour l'ensemble du serveur", - "purchase_server_description_2": "Statut de contributeur", - "purchase_server_title": "Serveur", - "purchase_settings_server_activated": "La clé du produit pour le Serveur est gérée par l'administrateur", - "query_asset_id": "Obtenir l'ID du média", - "queue_status": "{count}/{total} en file d'attente", - "rate_asset": "Évaluer un média", - "rating": "Étoile d'évaluation", - "rating_clear": "Effacer l'évaluation", - "rating_count": "{count, plural, one {# étoile} other {# étoiles}}", - "rating_description": "Afficher l'évaluation EXIF dans le panneau d'information", - "rating_set": "Note définie sur {rating, plural, one {# étoile} other {# étoiles}}", - "reaction_options": "Options de réaction", - "read_changelog": "Lire les changements", - "readonly_mode_disabled": "Mode lecture seule désactivé", - "readonly_mode_enabled": "Mode lecture seule activé", - "ready_for_upload": "Téléchargement prêt", - "reassign": "Réattribuer", - "reassigned_assets_to_existing_person": "{count, plural, one {# média réattribué} other {# médias réattribués}} à {name, select, null {une personne existante} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {# média réattribué} other {# médias réattribués}} à une nouvelle personne", - "reassing_hint": "Attribuer ces médias à une personne existante", - "recent": "Récent", - "recent-albums": "Albums récents", - "recent_searches": "Recherches récentes", - "recently_added": "Récemment ajouté", - "recently_added_page_title": "Récemment ajouté", - "recently_taken": "Récemment photographié", - "recently_taken_page_title": "Récemment photographié", - "refresh": "Actualiser", - "refresh_encoded_videos": "Actualiser les vidéos encodées", - "refresh_faces": "Actualiser les visages", - "refresh_metadata": "Actualiser les métadonnées", - "refresh_thumbnails": "Actualiser les miniatures", - "refreshed": "Actualisé", - "refreshes_every_file": "Actualise tous les fichiers (existants et nouveaux)", - "refreshing_encoded_video": "Actualisation de la vidéo encodée", - "refreshing_faces": "Actualisation des visages", - "refreshing_metadata": "Actualisation des métadonnées", - "regenerating_thumbnails": "Regénération des miniatures", - "remote": "À distance", - "remote_assets": "Média à distance", - "remote_media_summary": "Résumé du média distant", - "remove": "Supprimer", - "remove_assets_album_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# média} other {# médias}} de l'album ?", - "remove_assets_shared_link_confirmation": "Êtes-vous sûr de vouloir supprimer {count, plural, one {# média} other {# médias}} de ce lien partagé ?", - "remove_assets_title": "Supprimer les médias ?", - "remove_custom_date_range": "Supprimer la plage de date personnalisée", - "remove_deleted_assets": "Supprimer les fichiers hors ligne", - "remove_from_album": "Supprimer de l'album", - "remove_from_album_action_prompt": "{count} supprimé(s) de l'album", - "remove_from_favorites": "Supprimer des favoris", - "remove_from_lock_folder_action_prompt": "{count} supprimé(s) du dossier verrouillé", - "remove_from_locked_folder": "Supprimer du dossier verrouillé", - "remove_from_locked_folder_confirmation": "Êtes vous sûr de vouloir déplacer ces photos et vidéos en dehors du dossier verrouillé ? Elles seront visibles dans votre galerie.", - "remove_from_shared_link": "Supprimer des liens partagés", - "remove_memory": "Supprimer le souvenir", - "remove_photo_from_memory": "Supprimer la photo de ce souvenir", - "remove_tag": "Supprimer l'étiquette", - "remove_url": "Supprimer l'URL", - "remove_user": "Supprimer l'utilisateur", - "removed_api_key": "Clé API supprimée : {name}", - "removed_from_archive": "Supprimé de l'archive", - "removed_from_favorites": "Supprimé des favoris", - "removed_from_favorites_count": "{count, plural, one {# supprimé} other {# supprimés}} des favoris", - "removed_memory": "Souvenir supprimé", - "removed_photo_from_memory": "Photo supprimée du souvenir", - "removed_tagged_assets": "Étiquette supprimée de {count, plural, one {# média} other {# médias}}", - "rename": "Renommer", - "repair": "Réparer", - "repair_no_results_message": "Les fichiers non importés ou absents s'afficheront ici", - "replace_with_upload": "Remplacer avec l'envoi", - "repository": "Dépôt", - "require_password": "Demander le mot de passe", - "require_user_to_change_password_on_first_login": "Demander à l'utilisateur de changer son mot de passe lors de sa première connexion", - "rescan": "Rescanner", - "reset": "Réinitialiser", - "reset_password": "Réinitialiser le mot de passe", - "reset_people_visibility": "Réinitialiser la visibilité des personnes", - "reset_pin_code": "Réinitialiser le code PIN", - "reset_pin_code_description": "Si vous avez oublié votre code PIN, vous devez contacter l'administrateur du serveur pour le réinitialiser", - "reset_pin_code_success": "Code PIN réinitialisé avec succès", - "reset_pin_code_with_password": "Vous pouvez toujours réinitialiser le code PIN avec votre mot de passe", - "reset_sqlite": "Réinitialiser la base de données SQLite", - "reset_sqlite_confirmation": "Êtes-vous certain de vouloir réinitialiser la base de données SQLite ? Vous devrez vous déconnecter puis vous reconnecter à nouveau pour resynchroniser les données", - "reset_sqlite_success": "La base de données SQLite à été réinitialisé avec succès", - "reset_to_default": "Rétablir les valeurs par défaut", - "resolution": "Résolution", - "resolve_duplicates": "Résoudre les doublons", - "resolved_all_duplicates": "Résolution de tous les doublons", - "restore": "Restaurer", - "restore_all": "Tout restaurer", - "restore_trash_action_prompt": "{count} restauré de la corbeille", - "restore_user": "Restaurer l'utilisateur", - "restored_asset": "Média restauré", - "resume": "Reprendre", - "resume_paused_jobs": "Reprendre {count, plural, one {la tâche en cours} other {les # tâches en cours}}", - "retry_upload": "Réessayer l'envoi", - "review_duplicates": "Consulter les doublons", - "review_large_files": "Consulter les fichiers volumineux", - "role": "Rôle", - "role_editor": "Éditeur", - "role_viewer": "Visionneur", - "running": "En cours", - "save": "Sauvegarder", - "save_to_gallery": "Enregistrer", - "saved": "Sauvegardé", - "saved_api_key": "Clé API sauvegardée", - "saved_profile": "Profil enregistré", - "saved_settings": "Paramètres enregistrés", - "say_something": "Réagir", - "scaffold_body_error_occurred": "Une erreur s'est produite", - "scan": "Analyse", - "scan_all_libraries": "Analyser toutes les bibliothèques", - "scan_library": "Analyser", - "scan_settings": "Paramètres d'analyse", - "scanning": "Analyse en cours", - "scanning_for_album": "Recherche d'albums en cours...", - "search": "Recherche", - "search_albums": "Rechercher des albums", - "search_by_context": "Rechercher par contexte", - "search_by_description": "Recherche par description", - "search_by_description_example": "Randonnée à Sapa", - "search_by_filename": "Rechercher par nom du fichier ou extension", - "search_by_filename_example": "Exemple : IMG_1234.JPG ou PNG", - "search_by_ocr": "Recherche par OCR", - "search_by_ocr_example": "café latte", - "search_camera_lens_model": "Chercher par modèle d'objectif...", - "search_camera_make": "Rechercher par marque d'appareil photo...", - "search_camera_model": "Rechercher par modèle d'appareil photo...", - "search_city": "Rechercher par ville...", - "search_country": "Rechercher par pays...", - "search_filter_apply": "Appliquer le filtre", - "search_filter_camera_title": "Sélectionner le type d'appareil", - "search_filter_date": "Date", - "search_filter_date_interval": "{start} à {end}", - "search_filter_date_title": "Sélectionner une période", - "search_filter_display_option_not_in_album": "Pas dans un album", - "search_filter_display_options": "Options d'affichage", - "search_filter_filename": "Recherche par nom de fichier", - "search_filter_location": "Localisation", - "search_filter_location_title": "Sélectionner une localisation", - "search_filter_media_type": "Type de média", - "search_filter_media_type_title": "Sélectionner type de média", - "search_filter_ocr": "Recherche par OCR", - "search_filter_people_title": "Sélectionner une personne", - "search_filter_star_rating": "Note par étoiles", - "search_for": "Chercher", - "search_for_existing_person": "Rechercher une personne existante", - "search_no_more_result": "Plus de résultats", - "search_no_people": "Aucune personne", - "search_no_people_named": "Aucune personne nommée « {name} »", - "search_no_result": "Aucun résultat trouvé, essayez un autre terme de recherche ou une autre combinaison", - "search_options": "Rechercher une option", - "search_page_categories": "Catégories", - "search_page_motion_photos": "Photos avec mouvement", - "search_page_no_objects": "Aucune information disponible sur les objets", - "search_page_no_places": "Aucune information disponible sur la localisation", - "search_page_screenshots": "Captures d'écran", - "search_page_search_photos_videos": "Rechercher dans vos photos et vidéos", - "search_page_selfies": "Autoportraits (Selfies)", - "search_page_things": "Objets", - "search_page_view_all_button": "Voir tout", - "search_page_your_activity": "Votre activité", - "search_page_your_map": "Votre carte", - "search_people": "Rechercher une personne", - "search_places": "Rechercher un lieu", - "search_rating": "Chercher par évaluation...", - "search_result_page_new_search_hint": "Nouvelle recherche", - "search_settings": "Paramètres de recherche", - "search_state": "Rechercher par état/région...", - "search_suggestion_list_smart_search_hint_1": "La recherche intelligente est activée par défaut. Pour rechercher des métadonnées, utilisez la syntaxe suivante ", - "search_suggestion_list_smart_search_hint_2": "m:votre-terme-de-recherche", - "search_tags": "Recherche d'étiquettes...", - "search_timezone": "Rechercher par fuseau horaire...", - "search_type": "Rechercher par type", - "search_your_photos": "Rechercher vos photos", - "searching_locales": "Recherche des paramètres régionaux...", - "second": "Seconde", - "see_all_people": "Voir toutes les personnes", - "select": "Sélectionner", - "select_album": "Sélectionnez un album", - "select_album_cover": "Sélectionner la couverture d'album", - "select_albums": "Sélectionnez des albums", - "select_all": "Tout sélectionner", - "select_all_duplicates": "Sélectionner tous les doublons", - "select_all_in": "Tout sélectionner dans {group}", - "select_avatar_color": "Sélectionner la couleur de l'avatar", - "select_count": "{count, plural, one {Sélectionner #} other {Sélectionner #}}", - "select_cutoff_date": "Sélectionnez la date limite", - "select_face": "Sélectionner le visage", - "select_featured_photo": "Sélectionner la photo de profil de cette personne", - "select_from_computer": "Sélectionner à partir de l'ordinateur", - "select_keep_all": "Choisir de tout garder", - "select_library_owner": "Sélectionner le propriétaire de la bibliothèque", - "select_new_face": "Sélectionner un nouveau visage", - "select_people": "Sélectionnez des personnes", - "select_person": "Sélectionnez une personne", - "select_person_to_tag": "Sélectionner une personne à identifier", - "select_photos": "Sélectionner les photos", - "select_trash_all": "Choisir de tout supprimer", - "select_user_for_sharing_page_err_album": "Échec de la création de l'album", - "selected": "Sélectionné", - "selected_count": "{count, plural, one {# sélectionné} other {# sélectionnés}}", - "selected_gps_coordinates": "Coordonnées GPS sélectionnées", - "send_message": "Envoyer un message", - "send_welcome_email": "Envoyer un courriel de bienvenue", - "server_endpoint": "Adresse du serveur", - "server_info_box_app_version": "Version de l'application", - "server_info_box_server_url": "Server URL", - "server_offline": "Serveur hors ligne", - "server_online": "Serveur en ligne", - "server_privacy": "Vie privée pour le serveur", - "server_restarting_description": "Cette page va se rafraîchir dans quelques instants.", - "server_restarting_title": "Le serveur redémarre", - "server_stats": "Statistiques du serveur", - "server_update_available": "Une mise à jour du serveur est disponible", - "server_version": "Version du serveur", - "set": "Définir", - "set_as_album_cover": "Définir comme couverture d'album", - "set_as_featured_photo": "Définir comme photo mise en avant", - "set_as_profile_picture": "Définir comme photo de profil", - "set_date_of_birth": "Changer la date de naissance", - "set_profile_picture": "Définir la photo de profil", - "set_slideshow_to_fullscreen": "Afficher le diaporama en plein écran", - "set_stack_primary_asset": "Marquer comme média principal", - "setting_image_viewer_help": "Le visualiseur de détails charge d'abord la petite miniature, puis l'aperçu de taille moyenne (s'il est activé), enfin l'original (s'il est activé).", - "setting_image_viewer_original_subtitle": "Activez cette option pour charger l'image en résolution originale (fichier volumineux !). Désactiver pour réduire l'utilisation des données (réseau et cache de l'appareil).", - "setting_image_viewer_original_title": "Charger l'image originale", - "setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la miniature.", - "setting_image_viewer_preview_title": "Charger l'image d'aperçu", - "setting_image_viewer_title": "Images", - "setting_languages_apply": "Appliquer", - "setting_languages_subtitle": "Changer la langue de l'application", - "setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan : {duration}", - "setting_notifications_notify_hours": "{count} heures", - "setting_notifications_notify_immediately": "immédiatement", - "setting_notifications_notify_minutes": "{count} minutes", - "setting_notifications_notify_never": "jamais", - "setting_notifications_notify_seconds": "{count} secondes", - "setting_notifications_single_progress_subtitle": "Informations détaillées sur la progression de l'envoi par média", - "setting_notifications_single_progress_title": "Afficher la progression du détail de la sauvegarde en arrière-plan", - "setting_notifications_subtitle": "Ajustez vos préférences de notification", - "setting_notifications_total_progress_subtitle": "Progression globale de l'envoi (effectué/total des médias)", - "setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan", - "setting_video_viewer_auto_play_subtitle": "Lancer automatiquement la lecture des vidéos lorsqu’elles sont ouvertes", - "setting_video_viewer_auto_play_title": "Lecture automatique des vidéos", - "setting_video_viewer_looping_title": "Boucle", - "setting_video_viewer_original_video_subtitle": "Lors de la diffusion d'une vidéo depuis le serveur, lisez l'original même si un transcodage est disponible. Cela peut entraîner de la mise en mémoire tampon. Les vidéos disponibles localement sont lues en qualité d'origine, quel que soit ce paramètre.", - "setting_video_viewer_original_video_title": "Forcer la vidéo originale", - "settings": "Paramètres", - "settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre", - "settings_saved": "Paramètres sauvegardés", - "setup_pin_code": "Définir un code PIN", - "share": "Partager", - "share_action_prompt": "{count} éléments partagés", - "share_add_photos": "Ajouter des photos", - "share_assets_selected": "{count} sélectionné(s)", - "share_dialog_preparing": "Préparation...", - "share_link": "Partager le lien", - "shared": "Partagé", - "shared_album_activities_input_disable": "Les commentaires sont désactivés", - "shared_album_activity_remove_content": "Souhaitez-vous supprimer cette activité ?", - "shared_album_activity_remove_title": "Supprimer l'activité", - "shared_album_section_people_action_error": "Erreur lors de la suppression", - "shared_album_section_people_action_leave": "Supprimer l'utilisateur de l'album", - "shared_album_section_people_action_remove_user": "Supprimer l'utilisateur de l'album", - "shared_album_section_people_title": "PERSONNES", - "shared_by": "Partagé par", - "shared_by_user": "Partagé par {user}", - "shared_by_you": "Partagé par vous", - "shared_from_partner": "Photos de {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Envoyé(s)", - "shared_link_app_bar_title": "Liens partagés", - "shared_link_clipboard_copied_massage": "Copié dans le presse-papier", - "shared_link_clipboard_text": "Lien : {link}\nMot de passe : {password}", - "shared_link_create_error": "Erreur pendant la création du lien partagé", - "shared_link_custom_url_description": "Accéder à ce lien partagé avec une URL personnalisée", - "shared_link_edit_description_hint": "Saisir la description du partage", - "shared_link_edit_expire_after_option_day": "1 jour", - "shared_link_edit_expire_after_option_days": "{count} jours", - "shared_link_edit_expire_after_option_hour": "1 heure", - "shared_link_edit_expire_after_option_hours": "{count} heures", - "shared_link_edit_expire_after_option_minute": "1 minute", - "shared_link_edit_expire_after_option_minutes": "{count} minutes", - "shared_link_edit_expire_after_option_months": "{count} mois", - "shared_link_edit_expire_after_option_year": "{count} an", - "shared_link_edit_password_hint": "Saisir le mot de passe de partage", - "shared_link_edit_submit_button": "Mettre à jour le lien", - "shared_link_error_server_url_fetch": "Impossible de récupérer l'url du serveur", - "shared_link_expires_day": "Expire dans {count} jour", - "shared_link_expires_days": "Expire dans {count} jours", - "shared_link_expires_hour": "Expire dans {count} heure", - "shared_link_expires_hours": "Expire dans {count} heures", - "shared_link_expires_minute": "Expire dans {count} minute", - "shared_link_expires_minutes": "Expire dans {count} minutes", - "shared_link_expires_never": "Expire ∞", - "shared_link_expires_second": "Expire dans {count} seconde", - "shared_link_expires_seconds": "Expire dans {count} secondes", - "shared_link_individual_shared": "Partagé individuellement", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Gérer les liens partagés", - "shared_link_options": "Options de lien partagé", - "shared_link_password_description": "Demander un mot de passe pour accéder à ce lien partagé", - "shared_links": "Liens partagés", - "shared_links_description": "Partager les photos et vidéos via un lien", - "shared_photos_and_videos_count": "{assetCount, plural, other {# photos et vidéos partagées.}}", - "shared_with_me": "Partagé avec moi", - "shared_with_partner": "Partagé avec {partner}", - "sharing": "Partage", - "sharing_enter_password": "Veuillez saisir le mot de passe pour visualiser cette page.", - "sharing_page_album": "Albums partagés", - "sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.", - "sharing_page_empty_list": "LISTE VIDE", - "sharing_sidebar_description": "Afficher un lien vers Partager dans la barre latérale", - "sharing_silver_appbar_create_shared_album": "Créer un album partagé", - "sharing_silver_appbar_share_partner": "Partager avec un partenaire", - "shift_to_permanent_delete": "appuyez sur ⇧ pour supprimer définitivement le média", - "show_album_options": "Afficher les options de l'album", - "show_albums": "Montrer les albums", - "show_all_people": "Montrer toutes les personnes", - "show_and_hide_people": "Afficher / Masquer les personnes", - "show_file_location": "Afficher l'emplacement du fichier", - "show_gallery": "Afficher la galerie", - "show_hidden_people": "Afficher les personnes masquées", - "show_in_timeline": "Afficher dans la vue chronologique", - "show_in_timeline_setting_description": "Afficher les photos et vidéos de cet utilisateur dans votre vue chronologique", - "show_keyboard_shortcuts": "Afficher les raccourcis clavier", - "show_metadata": "Afficher les métadonnées", - "show_or_hide_info": "Afficher ou masquer les informations", - "show_password": "Afficher le mot de passe", - "show_person_options": "Afficher les options de personnes", - "show_progress_bar": "Afficher la barre de progression", - "show_schema": "Afficher le schéma", - "show_search_options": "Afficher les options de recherche", - "show_shared_links": "Afficher les liens partagés", - "show_slideshow_transition": "Afficher la transition du diaporama", - "show_supporter_badge": "Badge de contributeur", - "show_supporter_badge_description": "Afficher le badge de contributeur", - "show_text_recognition": "Afficher la reconnaissance de texte", - "show_text_search_menu": "Afficher le menu de recherche de texte", - "shuffle": "Aléatoire", - "sidebar": "Barre latérale", - "sidebar_display_description": "Afficher un lien vers la vue dans la barre latérale", - "sign_out": "Déconnexion", - "sign_up": "S'enregistrer", - "size": "Taille", - "skip_to_content": "Passer", - "skip_to_folders": "Passer vers les dossiers", - "skip_to_tags": "Passer vers les étiquettes", - "slideshow": "Diaporama", - "slideshow_repeat": "Répéter le diaporama", - "slideshow_repeat_description": "Reboucler au début lorsque le diaporama se termine", - "slideshow_settings": "Paramètres du diaporama", - "sort_albums_by": "Trier les albums par...", - "sort_created": "Date de création", - "sort_items": "Nombre d'éléments", - "sort_modified": "Date de modification", - "sort_newest": "Photo la plus récente", - "sort_oldest": "Photo la plus ancienne", - "sort_people_by_similarity": "Trier les personnes par similitude", - "sort_recent": "Photo la plus récente", - "sort_title": "Titre", - "source": "Source", - "stack": "Empiler", - "stack_action_prompt": "{count} empilé(s)", - "stack_duplicates": "Empiler les doublons", - "stack_select_one_photo": "Sélectionnez une photo principale pour la pile", - "stack_selected_photos": "Empiler les photos sélectionnées", - "stacked_assets_count": "{count, plural, one {# média empilé} other {# médias empilés}}", - "stacktrace": "Trace de la pile", - "start": "Commencer", - "start_date": "Date de début", - "start_date_before_end_date": "La date de début doit être avant la date de fin", - "state": "Région", - "status": "Statut", - "stop_casting": "Arrêter la diffusion", - "stop_motion_photo": "Photo Stop Motion", - "stop_photo_sharing": "Arrêter de partager vos photos ?", - "stop_photo_sharing_description": "{partner} ne pourra plus accéder à vos photos.", - "stop_sharing_photos_with_user": "Arrêter de partager vos photos avec cet utilisateur", - "storage": "Stockage", - "storage_label": "Étiquette de stockage", - "storage_quota": "Quota de stockage", - "storage_usage": "{used} sur {available} utilisé", - "submit": "Soumettre", - "success": "Réussi", - "suggestions": "Suggestions", - "sunrise_on_the_beach": "Lever de soleil sur la plage", - "support": "Soutenir", - "support_and_feedback": "Support & Retours", - "support_third_party_description": "Votre installation d'Immich est packagée via une application tierce. Si vous rencontrez des anomalies, elles peuvent venir de ce packaging tiers, merci de créer les anomalies avec ces tiers en premier lieu en utilisant les liens ci-dessous.", - "swap_merge_direction": "Inverser la direction de fusion", - "sync": "Synchroniser", - "sync_albums": "Synchroniser dans des albums", - "sync_albums_manual_subtitle": "Synchroniser toutes les vidéos et photos envoyées dans les albums sélectionnés", - "sync_local": "Synchronisation locale", - "sync_remote": "Synchronisation à distance", - "sync_status": "Statut de synchronisation", - "sync_status_subtitle": "Consulter et gérer le système de synchronisation", - "sync_upload_album_setting_subtitle": "Créez et envoyez vos photos et vidéos dans les albums sélectionnés sur Immich", - "tag": "Étiquette", - "tag_assets": "Étiqueter les médias", - "tag_created": "Étiquette créée : {tag}", - "tag_feature_description": "Parcourir les photos et vidéos groupées par thèmes logiques", - "tag_not_found_question": "Vous ne trouvez pas une étiquette ? Créer une nouvelle étiquette.", - "tag_people": "Étiqueter les personnes", - "tag_updated": "Étiquette mise à jour : {tag}", - "tagged_assets": "Étiquette ajoutée à {count, plural, one {# média} other {# médias}}", - "tags": "Étiquettes", - "tap_to_run_job": "Appuyez pour démarrer la tâche", - "template": "Modèle", - "text_recognition": "Reconnaissance de texte", - "theme": "Thème", - "theme_selection": "Sélection du thème", - "theme_selection_description": "Ajuster automatiquement le thème clair ou sombre via les préférences système", - "theme_setting_asset_list_storage_indicator_title": "Afficher l'indicateur de stockage sur les tuiles des éléments", - "theme_setting_asset_list_tiles_per_row_title": "Nombre de médias par ligne ({count})", - "theme_setting_colorful_interface_subtitle": "Appliquer la couleur principale sur les surfaces d'arrière-plan.", - "theme_setting_colorful_interface_title": "Interface colorée", - "theme_setting_image_viewer_quality_subtitle": "Ajustez la qualité de la visionneuse d'images détaillées", - "theme_setting_image_viewer_quality_title": "Qualité de la visualisation des images", - "theme_setting_primary_color_subtitle": "Choisissez une couleur pour les actions principales et les accentuations.", - "theme_setting_primary_color_title": "Couleur principale", - "theme_setting_system_primary_color_title": "Utiliser la couleur du système", - "theme_setting_system_theme_switch": "Automatique (suivre les paramètres du système)", - "theme_setting_theme_subtitle": "Choisissez le thème de l'application", - "theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau", - "theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes", - "then": "Ensuite", - "they_will_be_merged_together": "Elles seront fusionnées ensemble", - "third_party_resources": "Ressources tierces", - "time": "Horaire", - "time_based_memories": "Souvenirs basés sur la date", - "time_based_memories_duration": "Durée en secondes d'affichage de chaque image.", - "timeline": "Vue chronologique", - "timezone": "Fuseau horaire", - "to_archive": "Archiver", - "to_change_password": "Modifier le mot de passe", - "to_favorite": "Ajouter aux favoris", - "to_login": "Se connecter", - "to_multi_select": "pour faire une sélection multiple", - "to_parent": "Aller au dossier parent", - "to_select": "pour faire une sélection", - "to_trash": "Corbeille", - "toggle_settings": "Afficher/masquer les paramètres", - "toggle_theme_description": "Changer le thème", - "total": "Total", - "total_usage": "Utilisation globale", - "trash": "Corbeille", - "trash_action_prompt": "{count} média(s) mis à la corbeille", - "trash_all": "Tout supprimer", - "trash_count": "Corbeille {count, number}", - "trash_delete_asset": "Mettre à la corbeille/Supprimer un média", - "trash_emptied": "Corbeille vidée", - "trash_no_results_message": "Les photos et vidéos supprimées s'afficheront ici.", - "trash_page_delete_all": "Tout supprimer", - "trash_page_empty_trash_dialog_content": "Voulez-vous vider les médias de la corbeille ? Ces objets seront définitivement retirés d'Immich", - "trash_page_info": "Les médias mis à la corbeille seront définitivement supprimés au bout de {days} jours", - "trash_page_no_assets": "Aucun élément dans la corbeille", - "trash_page_restore_all": "Tout restaurer", - "trash_page_select_assets_btn": "Sélectionner les éléments", - "trash_page_title": "Corbeille ({count})", - "trashed_items_will_be_permanently_deleted_after": "Les éléments dans la corbeille seront supprimés définitivement après {days, plural, one {# jour} other {# jours}}.", - "trigger": "Déclencheur", - "trigger_asset_uploaded": "Média téléversé", - "trigger_asset_uploaded_description": "Déclenché lorsqu'un nouveau média est téléversé", - "trigger_description": "Un événement qui active le flux de traitement", - "trigger_person_recognized": "Personne reconnue", - "trigger_person_recognized_description": "Déclenché lorsqu'une personne est détectée", - "trigger_type": "Type de déclencheur", - "troubleshoot": "Dépannage", - "type": "Type", - "unable_to_change_pin_code": "Impossible de changer le code PIN", - "unable_to_check_version": "Impossible de vérifier la version", - "unable_to_setup_pin_code": "Impossible de définir le code PIN", - "unarchive": "Désarchiver", - "unarchive_action_prompt": "{count} supprimé(s) de l'archive", - "unarchived_count": "{count, plural, one {# supprimé} other {# supprimés}} de l'archive", - "undo": "Annuler", - "unfavorite": "Enlever des favoris", - "unfavorite_action_prompt": "{count} supprimé(s) des favoris", - "unhide_person": "Afficher la personne", - "unknown": "Inconnu", - "unknown_country": "Pays non connu", - "unknown_date": "Date inconnue", - "unknown_year": "Année inconnue", - "unlimited": "Illimité", - "unlink_motion_video": "Détacher la photo animée", - "unlink_oauth": "Déconnecter OAuth", - "unlinked_oauth_account": "Compte OAuth non connecté", - "unmute_memories": "Réactiver les souvenirs", - "unnamed_album": "Album sans nom", - "unnamed_album_delete_confirmation": "Êtes-vous sûr de vouloir supprimer cet album ?", - "unnamed_share": "Partage sans nom", - "unsaved_change": "Modification non enregistrée", - "unselect_all": "Annuler la sélection", - "unselect_all_duplicates": "Désélectionner tous les doublons", - "unselect_all_in": "Tout désélectionner dans {group}", - "unstack": "Dépiler", - "unstack_action_prompt": "{count} dépilé(s)", - "unstacked_assets_count": "{count, plural, one {# média dépilé} other {# médias dépilés}}", - "unsupported_field_type": "Type de champ non supporté", - "untagged": "Sans étiquette", - "untitled_workflow": "Flux de traitement sans titre", - "up_next": "Suite", - "update_location_action_prompt": "Mettre à jour la localisation des {count} médias sélectionnés avec :", - "updated_at": "Mis à jour à", - "updated_password": "Mot de passe mis à jour", - "upload": "Envoyer", - "upload_concurrency": "Envois simultanés", - "upload_details": "Détails des envois", - "upload_dialog_info": "Voulez-vous sauvegarder la sélection vers le serveur ?", - "upload_dialog_title": "Envoyer le média", - "upload_error_with_count": "Erreur de chargement pour {count, plural, one {# média} other {# médias}}", - "upload_errors": "L'envoi s'est complété avec {count, plural, one {# erreur} other {# erreurs}}. Rafraîchissez la page pour voir les nouveaux médias envoyés.", - "upload_finished": "Envoi fini", - "upload_progress": "{remaining, number} restant(s) - {processed, number} traité(s)/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {# doublon ignoré} other {# doublons ignorés}}", - "upload_status_duplicates": "Doublons", - "upload_status_errors": "Erreurs", - "upload_status_uploaded": "Envoyé", - "upload_success": "Envoi réussi. Rafraîchissez la page pour voir les nouveaux médias envoyés.", - "upload_to_immich": "Envoyer vers Immich ({count})", - "uploading": "Envoi", - "uploading_media": "Envoi du média", - "url": "URL", - "usage": "Utilisation", - "use_biometric": "Utiliser l'authentification biométrique", - "use_current_connection": "Utiliser le réseau actuel", - "use_custom_date_range": "Utilisez une plage de date personnalisée à la place", - "user": "Utilisateur", - "user_has_been_deleted": "Cet utilisateur à été supprimé.", - "user_id": "ID Utilisateur", - "user_liked": "{user} a aimé {type, select, photo {cette photo} video {cette vidéo} asset {ce média} other {ceci}}", - "user_pin_code_settings": "Code PIN", - "user_pin_code_settings_description": "Gérer votre code PIN", - "user_privacy": "Vie privée pour l'utilisateur", - "user_purchase_settings": "Achat", - "user_purchase_settings_description": "Gérer votre achat", - "user_role_set": "Définir {user} comme {role}", - "user_usage_detail": "Détail de l'utilisation des utilisateurs", - "user_usage_stats": "Statistiques d'utilisation du compte", - "user_usage_stats_description": "Voir les statistiques d'utilisation du compte", - "username": "Nom d'utilisateur", - "users": "Utilisateurs", - "users_added_to_album_count": "{count, plural, one {# utilisateur ajouté} other {# utilisateurs ajoutés}} à l'album", - "utilities": "Utilitaires", - "validate": "Valider", - "validate_endpoint_error": "Merci d'entrer un lien valide", - "validation_error": "Erreur de validation", - "variables": "Variables", - "version": "Version", - "version_announcement_closing": "Ton ami, Alex", - "version_announcement_message": "Bonjour, il y a une nouvelle version de l'application. Prenez le temps de consulter les notes de version et assurez vous que votre installation est à jour pour éviter toute erreur de configuration, surtout si vous utilisez WatchTower ou tout autre mécanisme qui gère automatiquement la mise à jour de votre application.", - "version_history": "Historique de version", - "version_history_item": "Version {version} installée le {date}", - "video": "Vidéo", - "video_hover_setting": "Lire la miniature des vidéos au survol", - "video_hover_setting_description": "Lancer la prévisualisation vidéo au survol. Si désactivé, la lecture peut quand même être démarrée en survolant le bouton Play.", - "videos": "Vidéos", - "videos_count": "{count, plural, one {# Vidéo} other {# Vidéos}}", - "videos_only": "Vidéos uniquement", - "view": "Voir", - "view_album": "Afficher l'album", - "view_all": "Voir tout", - "view_all_users": "Voir tous les utilisateurs", - "view_asset_owners": "Voir les propriétaires des médias", - "view_details": "Voir les détails", - "view_in_timeline": "Voir dans la vue chronologique", - "view_link": "Voir le lien", - "view_links": "Voir les liens", - "view_name": "Vue", - "view_next_asset": "Voir le média suivant", - "view_previous_asset": "Voir le média précédent", - "view_qr_code": "Voir le QR code", - "view_similar_photos": "Afficher les photos similaires", - "view_stack": "Afficher la pile", - "view_user": "Voir l'utilisateur", - "viewer_remove_from_stack": "Retirer de la pile", - "viewer_stack_use_as_main_asset": "Utiliser comme élément principal", - "viewer_unstack": "Dépiler", - "visibility_changed": "Visibilité changée pour {count, plural, one {# personne} other {# personnes}}", - "visual": "Visuel", - "visual_builder": "Constructeur visuel", - "waiting": "En attente", - "waiting_count": "En attente : {count}", - "warning": "Attention", - "week": "Semaine", - "welcome": "Bienvenue", - "welcome_to_immich": "Bienvenue sur Immich", - "width": "Largeur", - "wifi_name": "Nom du réseau wifi", - "workflow_delete_prompt": "Êtes-vous sûr de vouloir supprimer ce flux de traitement ?", - "workflow_deleted": "Flux de traitement supprimé", - "workflow_description": "Description du flux de traitement", - "workflow_info": "Informations du flux de traitement", - "workflow_json": "JSON du flux de traitement", - "workflow_json_help": "Modifier la configuration du flux de traitement dans un format JSON. Les changements se synchroniseront avec le constructeur visuel.", - "workflow_name": "Nom du flux de traitement", - "workflow_navigation_prompt": "Êtes-vous sûr de vouloir quitter sans enregistrer vos changements ?", - "workflow_summary": "Résumé du flux de traitement", - "workflow_update_success": "Flux de traitement mis à jour avec succès", - "workflow_updated": "Flux de traitement mis à jour", - "workflows": "Flux de traitement", - "workflows_help_text": "Les flux de traitement automatisent des actions sur vos médias, en se basant sur des déclencheurs et des filtres", - "wrong_pin_code": "Code PIN erroné", - "year": "Année", - "years_ago": "Il y a {years, plural, one {# an} other {# ans}}", - "yes": "Oui", - "you_dont_have_any_shared_links": "Vous n'avez aucun lien partagé", - "your_wifi_name": "Nom du réseau wifi", - "zero_to_clear_rating": "Appuyez sur 0 pour effacer la notation du média", - "zoom_image": "Zoomer", - "zoom_to_bounds": "Zoom sur la zone" -} +{} diff --git a/i18n/ga.json b/i18n/ga.json index 464e8d9763..0967ef424b 100644 --- a/i18n/ga.json +++ b/i18n/ga.json @@ -1,2401 +1 @@ -{ - "about": "Maidir", - "account": "Cuntas", - "account_settings": "Socruithe Cuntais", - "acknowledge": "Admháil", - "action": "Gníomh", - "action_common_update": "Nuashonrú", - "action_description": "Sraith gníomhartha le déanamh ar na sócmhainní scagtha", - "actions": "Gníomhartha", - "active": "Gníomhach", - "active_count": "Gníomhach: {count}", - "activity": "Gníomhaíocht", - "activity_changed": "Tá an ghníomhaíocht {enabled, select, true {enabled} other {disabled}}", - "add": "Cuir leis", - "add_a_description": "Cuir cur síos leis", - "add_a_location": "Cuir suíomh leis", - "add_a_name": "Cuir ainm leis", - "add_a_title": "Cuir teideal leis", - "add_action": "Cuir gníomh leis", - "add_action_description": "Cliceáil chun gníomh a chur leis le déanamh", - "add_assets": "Cuir sócmhainní leis", - "add_birthday": "Cuir breithlá leis", - "add_endpoint": "Cuir críochphointe leis", - "add_exclusion_pattern": "Cuir patrún eisiaimh leis", - "add_filter": "Cuir scagaire leis", - "add_filter_description": "Cliceáil chun coinníoll scagaire a chur leis", - "add_location": "Cuir suíomh leis", - "add_more_users": "Cuir níos mó úsáideoirí leis", - "add_partner": "Cuir comhpháirtí leis", - "add_path": "Cuir cosán leis", - "add_photos": "Cuir grianghraif leis", - "add_tag": "Cuir clib leis", - "add_to": "Cuir le…", - "add_to_album": "Cuir leis an albam", - "add_to_album_bottom_sheet_added": "Curtha le {album}", - "add_to_album_bottom_sheet_already_exists": "Cheana féin i {album}", - "add_to_album_bottom_sheet_some_local_assets": "Níorbh fhéidir roinnt sócmhainní áitiúla a chur leis an albam", - "add_to_album_toggle": "Athraigh rogha do {album}", - "add_to_albums": "Cuir le halbaim", - "add_to_albums_count": "Cuir le halbaim ({count})", - "add_to_bottom_bar": "Cuir le", - "add_to_shared_album": "Cuir le halbam comhroinnte", - "add_upload_to_stack": "Cuir uaslódáil leis an gcruach", - "add_url": "Cuir URL leis", - "add_workflow_step": "Cuir céim sreabha oibre leis", - "added_to_archive": "Curtha leis an gcartlann", - "added_to_favorites": "Curtha le rogha pearsanta", - "added_to_favorites_count": "Cuireadh {count, number} le mo rogha pearsanta", - "admin": { - "add_exclusion_pattern_description": "Cuir patrúin eisiaimh leis. Tacaítear le globáil ag baint úsáide as *, **, agus ?. Chun neamhaird a dhéanamh ar gach comhad in aon eolaire darb ainm \"Raw\", bain úsáid as \"**/Raw/**\". Chun neamhaird a dhéanamh ar gach comhad a chríochnaíonn le \".tif\", bain úsáid as \"**/*.tif\". Chun neamhaird a dhéanamh ar chonair absalóideach, bain úsáid as \"/path/to/ignore/**\".", - "admin_user": "Úsáideoir Riaracháin", - "asset_offline_description": "Níl an tsócmhainn sheachtrach leabharlainne seo le fáil ar an diosca a thuilleadh agus tá sí bogtha chuig an mbruscar. Má aistríodh an comhad laistigh den leabharlann, seiceáil d'amlíne don tsócmhainn chomhfhreagrach nua. Chun an tsócmhainn seo a athchóiriú, cinntigh gur féidir le Immich rochtain a fháil ar an gcosán comhaid thíos agus scanadh an leabharlann.", - "authentication_settings": "Socruithe Fíordheimhnithe", - "authentication_settings_description": "Bainistigh pasfhocal, OAuth, agus socruithe fíordheimhnithe eile", - "authentication_settings_disable_all": "An bhfuil tú cinnte gur mian leat gach modh logála isteach a dhíchumasú? Díchumasófar logáil isteach go hiomlán.", - "authentication_settings_reenable": "Chun é a athchumasú, bain úsáid as Ordú Freastalaí.", - "background_task_job": "Tascanna Cúlra", - "backup_database": "Cruthaigh Dumpáil Bunachar Sonraí", - "backup_database_enable_description": "Cumasaigh dumpálacha bunachar sonraí", - "backup_keep_last_amount": "Méid na ndumpálacha roimhe seo le coinneáil", - "backup_onboarding_1_description": "cóip lasmuigh den láthair sa scamall nó in áit fhisiciúil eile.", - "backup_onboarding_2_description": "cóipeanna áitiúla ar ghléasanna éagsúla. Áirítear leis seo na príomhchomhaid agus cúltaca de na comhaid sin go háitiúil.", - "backup_onboarding_3_description": "cóipeanna iomlána de do shonraí, lena n-áirítear na comhaid bhunaidh. Áirítear leis seo cóip amháin lasmuigh den láthair agus dhá chóip áitiúla.", - "backup_onboarding_description": "Moltar straitéis chúltaca 3-2-1 chun do shonraí a chosaint. Ba chóir duit cóipeanna de do ghrianghraif/fhíseáin uaslódáilte a choinneáil chomh maith le bunachar sonraí Immich le haghaidh réiteach cúltaca cuimsitheach.", - "backup_onboarding_footer": "Le haghaidh tuilleadh eolais faoi chúltaca d'Immich, féach ar an doiciméadú.", - "backup_onboarding_parts_title": "Áirítear le cúltaca 3-2-1:", - "backup_onboarding_title": "Cúltacaí", - "backup_settings": "Socruithe Dumpála Bunachar Sonraí", - "backup_settings_description": "Bainistigh socruithe dumpála bunachar sonraí.", - "cleared_jobs": "Poist glanta do: {job}", - "config_set_by_file": "Tá an chumraíocht socraithe ag comhad cumraíochta faoi láthair", - "confirm_delete_library": "An bhfuil tú cinnte gur mian leat leabharlann {library} a scriosadh?", - "confirm_delete_library_assets": "An bhfuil tú cinnte gur mian leat an leabharlann seo a scriosadh? Scriosfaidh sé seo {count, plural, one {# contained asset} other {all # contained assets}} ó Immich agus ní féidir é a chealú. Fanfaidh na comhaid ar an diosca.", - "confirm_email_below": "Chun deimhniú, clóscríobh \"{email}\" thíos", - "confirm_reprocess_all_faces": "An bhfuil tú cinnte gur mian leat gach aghaidh a athphróiseáil? Glanfaidh sé seo daoine ainmnithe freisin.", - "confirm_user_password_reset": "An bhfuil tú cinnte gur mian leat pasfhocal {user} a athshocrú?", - "confirm_user_pin_code_reset": "An bhfuil tú cinnte gur mian leat cód PIN {user} a athshocrú?", - "copy_config_to_clipboard_description": "Cóipeáil cumraíocht reatha an chórais mar réad JSON chuig an ngearrthaisce", - "create_job": "Cruthaigh post", - "cron_expression": "Léiriú Cron", - "cron_expression_description": "Socraigh an t-eatramh scanadh ag baint úsáide as an bhformáid cron. Le haghaidh tuilleadh eolais féach ar m.sh. Crontab Guru", - "cron_expression_presets": "Réamhshocruithe léirithe Cron", - "disable_login": "Díchumasaigh logáil isteach", - "duplicate_detection_job_description": "Rith foghlaim meaisín ar shócmhainní chun íomhánna comhchosúla a bhrath. Braitheann sé ar Chuardach Cliste", - "exclusion_pattern_description": "Le patrúin eisiaimh, is féidir leat neamhaird a dhéanamh de chomhaid agus fillteáin agus tú ag scanadh do leabharlann. Tá sé seo úsáideach má tá fillteáin agat ina bhfuil comhaid nach mian leat a allmhairiú, amhail comhaid RAW.", - "export_config_as_json_description": "Íoslódáil cumraíocht reatha an chórais mar chomhad JSON", - "external_libraries_page_description": "Leathanach leabharlainne seachtrach riarthóra", - "face_detection": "Brath aghaidhe", - "face_detection_description": "Braith aghaidheanna i sócmhainní ag baint úsáide as foghlaim meaisín. I gcás físeáin, ní chuirtear san áireamh ach an mionsamhail. Déanann \"Athnuachan\" na sócmhainní go léir a phróiseáil (athphróiseáil). Glanann \"Athshocraigh\" na sonraí aghaidhe reatha go léir freisin. Cuireann \"Ar Iarraidh\" sócmhainní nach bhfuil próiseáilte fós i scuaine. Cuirfear aghaidheanna braite i scuaine le haghaidh Aitheantas Aghaidhe tar éis don Bhrath Aghaidhe a bheith críochnaithe, agus grúpálfar iad i ndaoine atá ann cheana féin nó i ndaoine nua.", - "facial_recognition_job_description": "Grúpáil aghaidheanna braite i ndaoine. Ritheann an chéim seo tar éis don Bhrath Aghaidhe a bheith críochnaithe. Déanann \"Athshocraigh\" (ath-)ghlasáil ar na haghaidheanna go léir. Cuireann \"Ar Iarraidh\" aghaidheanna nach bhfuil duine sannta dóibh i scuaine.", - "failed_job_command": "Theip ar ordú {command} don phost: {job}", - "force_delete_user_warning": "RABHADH: Bainfear an t-úsáideoir agus na sócmhainní go léir láithreach leis seo. Ní féidir é seo a chealú agus ní féidir na comhaid a aisghabháil.", - "image_format": "Formáid", - "image_format_description": "Táirgeann FormatWebP comhaid níos lú ná JPEG, ach tá sé níos moille le hionchódú.", - "image_fullsize_description": "Íomhá lánmhéide le meiteashonraí stróicthe, a úsáidtear nuair a dhéantar zúmáil isteach", - "image_fullsize_enabled": "Cumasaigh giniúint íomhá lánmhéide", - "image_fullsize_enabled_description": "Íomhá lánmhéide a ghiniúint le haghaidh formáidí nach bhfuil cairdiúil don ghréasán. Nuair a bhíonn \"Is fearr réamhamharc leabaithe\" cumasaithe, úsáidtear réamhamhairc leabaithe go díreach gan tiontú. Ní dhéanann sé difear do fhormáidí atá cairdiúil don ghréasán ar nós JPEG.", - "image_fullsize_quality_description": "Cáilíocht íomhá lánmhéide ó 1-100. Is fearr níos airde, ach cruthaíonn sé comhaid níos mó.", - "image_fullsize_title": "Socruithe Íomhá Lánmhéide", - "image_prefer_embedded_preview": "Réamhamharc leabaithe is fearr leat", - "image_prefer_embedded_preview_setting_description": "Leathanach poist riarthóraÚsáid réamhamhairc leabaithe i ngrianghraif RAW mar ionchur le haghaidh próiseála íomhá agus nuair is féidir. Is féidir leis seo dathanna níos cruinne a tháirgeadh do roinnt íomhánna, ach braitheann cáilíocht an réamhamhairc ar an gceamara agus d'fhéadfadh go mbeadh níos mó déantáin chomhbhrúite san íomhá.", - "image_prefer_wide_gamut": "Is fearr gamut leathan", - "image_prefer_wide_gamut_setting_description": "Bain úsáid as Taispeántas P3 le haghaidh mionsamhlacha. Coinníonn sé seo beocht na n-íomhánna le spásanna dathanna leathana níos fearr, ach d'fhéadfadh cuma dhifriúil a bheith ar íomhánna ar sheanghléasanna le seanleagan brabhsálaí. Coinnítear íomhánna sRGB mar sRGB chun athruithe datha a sheachaint.", - "image_preview_description": "Íomhá meánmhéide le meiteashonraí stróicthe, a úsáidtear agus sócmhainn aonair á breathnú agus le haghaidh foghlama meaisín", - "image_preview_quality_description": "Cáilíocht réamhamhairc ó 1-100. Is airde is fearr, ach cruthaíonn sé comhaid níos mó agus d'fhéadfadh sé freagrúlacht aipeanna a laghdú. D'fhéadfadh tionchar a bheith ag luach íseal ar cháilíocht na foghlama meaisín.", - "image_preview_title": "Socruithe Réamhamhairc", - "image_progressive": "Forásach", - "image_progressive_description": "Íomhánna JPEG á n-ionchódú de réir a chéile le haghaidh taispeántais luchtaithe de réir a chéile. Níl aon éifeacht aige seo ar íomhánna WebP.", - "image_quality": "Cáilíocht", - "image_resolution": "Taifeach", - "image_resolution_description": "Is féidir le taifeach níos airde níos mó sonraí a chaomhnú ach tógann sé níos faide iad a ionchódú, bíonn méideanna comhaid níos mó acu agus féadann siad freagrúlacht aipeanna a laghdú.", - "image_settings": "Socruithe Íomhá", - "image_settings_description": "Bainistigh cáilíocht agus réiteach na n-íomhánna a ghintear", - "image_thumbnail_description": "Mionsamhail bheag le meiteashonraí stróicthe, a úsáidtear agus grúpaí grianghraf á bhféachaint cosúil leis an bpríomh-amlíne", - "image_thumbnail_quality_description": "Cáilíocht mionsamhlacha ó 1-100. Is airde an caighdeán is fearr, ach cruthaíonn sé comhaid níos mó agus d'fhéadfadh sé freagrúlacht an aip a laghdú.", - "image_thumbnail_title": "Socruithe Mionsamhail", - "import_config_from_json_description": "Cumraíocht chórais a allmhairiú trí chomhad cumraíochta JSON a uaslódáil", - "job_concurrency": "comhthráthacht {job}", - "job_created": "Post cruthaithe", - "job_not_concurrency_safe": "Níl an post seo sábháilte le haghaidh comhuaineachta.", - "job_settings": "Socruithe Poist", - "job_settings_description": "Bainistigh comhthráthacht poist", - "jobs_delayed": "{jobCount, plural, other {# moillithe}}", - "jobs_failed": "{jobCount, plural, other {# theip}}", - "jobs_over_time": "Poist le himeacht ama", - "library_created": "Leabharlann cruthaithe: {library}", - "library_deleted": "Scriosadh an leabharlann", - "library_details": "Sonraí na leabharlainne", - "library_folder_description": "Sonraigh fillteán le hallmhairiú. Déanfar scanadh ar an bhfillteán seo, lena n-áirítear fo-fhillteáin, le haghaidh íomhánna agus físeáin.", - "library_remove_exclusion_pattern_prompt": "An bhfuil tú cinnte gur mian leat an patrún eisiaimh seo a bhaint?", - "library_remove_folder_prompt": "An bhfuil tú cinnte gur mian leat an fillteán allmhairithe seo a bhaint?", - "library_scanning": "Scanadh Tréimhsiúil", - "library_scanning_description": "Cumraigh scanadh tréimhsiúil leabharlainne", - "library_scanning_enable_description": "Cumasaigh scanadh tréimhsiúil leabharlainne", - "library_settings": "Leabharlann Sheachtrach", - "library_settings_description": "Bainistigh socruithe leabharlainne seachtraí", - "library_tasks_description": "Scan leabharlanna seachtracha le haghaidh sócmhainní nua agus/nó athraithe", - "library_updated": "Leabharlann nuashonraithe", - "library_watching_enable_description": "Faire ar leabharlanna seachtracha le haghaidh athruithe ar chomhaid", - "library_watching_settings": "Ag breathnú ar an leabharlann [TURGNAMHACH]", - "library_watching_settings_description": "Faire go huathoibríoch ar chomhaid athraithe", - "logging_enable_description": "Cumasaigh logáil", - "logging_level_description": "Nuair a bheidh sé cumasaithe, cén leibhéal loga atá le húsáid.", - "logging_settings": "Logáil", - "machine_learning_availability_checks": "Seiceálacha infhaighteachta", - "machine_learning_availability_checks_description": "Braith go huathoibríoch agus cuir tús le freastalaithe foghlama meaisín atá ar fáil", - "machine_learning_availability_checks_enabled": "Cumasaigh seiceálacha infhaighteachta", - "machine_learning_availability_checks_interval": "Seiceáil an t-eatramh", - "machine_learning_availability_checks_interval_description": "Eatramh i milleasoicindí idir seiceálacha infhaighteachta", - "machine_learning_availability_checks_timeout": "Iarratas ar theorainn ama", - "machine_learning_availability_checks_timeout_description": "Am críochnaithe i milleasoicindí le haghaidh seiceálacha infhaighteachta", - "machine_learning_clip_model": "Samhail CLIP", - "machine_learning_clip_model_description": "Ainm mhúnla CLIP atá liostaithe anseo. Tabhair faoi deara go gcaithfidh tú an post 'Cuardach Cliste' a athrith do gach íomhá nuair a athraíonn tú samhail.", - "machine_learning_duplicate_detection": "Brath Dúblach", - "machine_learning_duplicate_detection_enabled": "Cumasaigh braiteadh dúblach", - "machine_learning_duplicate_detection_enabled_description": "Má tá sé díchumasaithe, déanfar sócmhainní comhionanna a dhídhúbláil fós.", - "machine_learning_duplicate_detection_setting_description": "Bain úsáid as leabaithe CLIP chun dúblaigh dóchúla a aimsiú", - "machine_learning_enabled": "Cumasaigh foghlaim meaisín", - "machine_learning_enabled_description": "Má tá sé díchumasaithe, díchumasófar gach gné ML beag beann ar na socruithe thíos.", - "machine_learning_facial_recognition": "Aitheantas Aghaidhe", - "machine_learning_facial_recognition_description": "Aghaidheanna a bhrath, a aithint agus a ghrúpáil in íomhánna", - "machine_learning_facial_recognition_model": "Múnla aitheantais aghaidhe", - "machine_learning_facial_recognition_model_description": "Liostaítear samhlacha in ord íslitheach méide. Bíonn samhlacha níos mó níos moille agus úsáideann siad níos mó cuimhne, ach tugann siad torthaí níos fearr. Tabhair faoi deara go gcaithfidh tú an post Braite Aghaidhe a athrith do gach íomhá nuair a athraíonn tú samhail.", - "machine_learning_facial_recognition_setting": "Cumasaigh aitheantas aghaidhe", - "machine_learning_facial_recognition_setting_description": "Mura bhfuil sé sin ar fáil, ní dhéanfar íomhánna a ionchódú le haghaidh aitheantais aghaidhe agus ní líonfar iad sa rannán Daoine ar an leathanach Iniúchta.", - "machine_learning_max_detection_distance": "Fad braite uasta", - "machine_learning_max_detection_distance_description": "An fad uasta idir dhá íomhá chun iad a mheas mar dhúblaigh, idir 0.001-0.1. Aimseoidh luachanna níos airde níos mó dúblach, ach d’fhéadfadh torthaí dearfacha bréagacha a bheith mar thoradh orthu.", - "machine_learning_max_recognition_distance": "Fad aitheantais uasta", - "machine_learning_max_recognition_distance_description": "An fad uasta idir dhá aghaidh le go measfaí gurb é an duine céanna é, idir 0-2. Má íslítear é seo, is féidir cosc a chur ar dhá dhuine a lipéadú mar an duine céanna, agus má ardaítear é, is féidir cosc a chur ar an duine céanna a lipéadú mar dhá dhuine éagsúla. Tabhair faoi deara go bhfuil sé níos éasca dhá dhuine a chumasc ná duine amháin a roinnt ina dhá leath, mar sin bí ag iarraidh tairseach níos ísle a shocrú nuair is féidir.", - "machine_learning_min_detection_score": "Scór braite íosta", - "machine_learning_min_detection_score_description": "An scór muiníne íosta le haghaidh aghaidh a bhrath ó 0-1. Braithfidh luachanna níos ísle níos mó aghaidheanna ach d’fhéadfadh torthaí dearfacha bréagacha a bheith mar thoradh orthu.", - "machine_learning_min_recognized_faces": "Íosmhéid aghaidheanna aitheanta", - "machine_learning_min_recognized_faces_description": "An líon íosta aghaidheanna aitheanta le go gcruthófar duine. Má mhéadaítear an líon seo, méadaítear an Aithint Aghaidhe agus méadaítear an seans nach sanntar aghaidh do dhuine.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Úsáid foghlaim meaisín chun téacs in íomhánna a aithint", - "machine_learning_ocr_enabled": "Cumasaigh OCR", - "machine_learning_ocr_enabled_description": "Mura bhfuil sé sin ar fáil, ní dhéanfar aitheantas téacs ar íomhánna.", - "machine_learning_ocr_max_resolution": "Uasmhéid réitigh", - "machine_learning_ocr_max_resolution_description": "Déanfar athrú méide ar réamhamhairc os cionn an taifigh seo agus an cóimheas gné á chaomhnú. Bíonn luachanna níos airde níos cruinne, ach tógann siad níos faide le próiseáil agus úsáidfidh siad níos mó cuimhne.", - "machine_learning_ocr_min_detection_score": "Scór braite íosta", - "machine_learning_ocr_min_detection_score_description": "Scór muiníne íosta le haghaidh téacs a bhrath ó 0-1. Braithfidh luachanna níos ísle níos mó téacs ach d’fhéadfadh torthaí dearfacha bréagacha a bheith mar thoradh orthu.", - "machine_learning_ocr_min_recognition_score": "Scór aitheantais íosta", - "machine_learning_ocr_min_score_recognition_description": "Scór muiníne íosta le haghaidh téacs braite a aithint ó 0-1. Aithneoidh luachanna níos ísle níos mó téacs ach d'fhéadfadh torthaí dearfacha bréagacha a bheith mar thoradh orthu.", - "machine_learning_ocr_model": "Samhail OCR", - "machine_learning_ocr_model_description": "Tá samhlacha freastalaí níos cruinne ná samhlacha soghluaiste, ach tógann siad níos faide le próiseáil agus níos mó cuimhne a úsáid.", - "machine_learning_settings": "Socruithe Foghlama Meaisín", - "machine_learning_settings_description": "Bainistigh gnéithe agus socruithe foghlama meaisín", - "machine_learning_smart_search": "Cuardach Cliste", - "machine_learning_smart_search_description": "Cuardaigh íomhánna go séimeantach ag baint úsáide as leabaithe CLIP", - "machine_learning_smart_search_enabled": "Cumasaigh cuardach cliste", - "machine_learning_smart_search_enabled_description": "Mura bhfuil sé sin ar fáil, ní dhéanfar íomhánna a ionchódú le haghaidh cuardaigh chliste.", - "machine_learning_url_description": "URL an fhreastalaí foghlama meaisín. Má chuirtear níos mó ná URL amháin ar fáil, déanfar iarracht ar gach freastalaí ceann ag an am go dtí go bhfreagróidh ceann acu go rathúil, in ord ón gcéad cheann go dtí an ceann deireanach. Déanfar neamhaird shealadach ar fhreastalaithe nach bhfreagróidh go dtí go mbeidh siad ar líne arís.", - "maintenance_delete_backup": "Scrios Cúltaca", - "maintenance_delete_backup_description": "Scriosfar an comhad seo go neamh-inchúlghairthe.", - "maintenance_delete_error": "Theip ar an gcúltaca a scriosadh.", - "maintenance_restore_backup": "Athchóirigh Cúltaca", - "maintenance_restore_backup_description": "Scriosfar agus athchóireofar Immich ón gcúltaca roghnaithe. Cruthófar cúltaca sula leanfar ar aghaidh.", - "maintenance_restore_backup_different_version": "Cruthaíodh an cúltaca seo le leagan difriúil de Immich!", - "maintenance_restore_backup_unknown_version": "Níorbh fhéidir an leagan cúltaca a chinneadh.", - "maintenance_restore_database_backup": "Athchóirigh cúltaca bunachar sonraí", - "maintenance_restore_database_backup_description": "Rolladh ar ais go staid bhunachar sonraí níos luaithe ag baint úsáide as comhad cúltaca", - "maintenance_settings": "Cothabháil", - "maintenance_settings_description": "Cuir Immich i mód cothabhála.", - "maintenance_start": "Athraigh go mód cothabhála", - "maintenance_start_error": "Theip ar an modh cothabhála a thosú.", - "maintenance_upload_backup": "Uaslódáil comhad cúltaca bunachar sonraí", - "maintenance_upload_backup_error": "Níorbh fhéidir an cúltaca a uaslódáil, an comhad .sql/.sql.gz é?", - "manage_concurrency": "Bainistigh Comhthráthacht", - "manage_concurrency_description": "Téigh chuig leathanach na bpost chun comhthráthacht poist a bhainistiú", - "manage_log_settings": "Bainistigh socruithe loga", - "map_dark_style": "Stíl dhorcha", - "map_enable_description": "Cumasaigh gnéithe léarscáile", - "map_gps_settings": "Socruithe Léarscáile & GPS", - "map_gps_settings_description": "Bainistigh Socruithe Léarscáile & GPS (Geochódú Droim ar Ais)", - "map_implications": "Braitheann an ghné léarscáile ar sheirbhís tíleanna seachtrach (tiles.immich.cloud)", - "map_light_style": "Stíl éadrom", - "map_manage_reverse_geocoding_settings": "Bainistigh socruithe Geochódú Droim ar Ais", - "map_reverse_geocoding": "Geochódú Droim ar Ais", - "map_reverse_geocoding_enable_description": "Cumasaigh geo-chódú droim ar ais", - "map_reverse_geocoding_settings": "Socruithe Geochódála Droim ar Ais", - "map_settings": "Léarscáil", - "map_settings_description": "Bainistigh socruithe léarscáile", - "map_style_description": "URL chuig téama léarscáile style.json", - "memory_cleanup_job": "Glanadh cuimhne", - "memory_generate_job": "Giniúint cuimhne", - "metadata_extraction_job": "Meiteashonraí a bhaint amach", - "metadata_extraction_job_description": "Bain faisnéis meiteashonraí as gach sócmhainn, amhail GPS, aghaidheanna agus réiteach", - "metadata_faces_import_setting": "Cumasaigh allmhairiú aghaidheanna", - "metadata_faces_import_setting_description": "Aghaidheanna a allmhairiú ó shonraí EXIF íomhá agus comhaid taobhlíne", - "metadata_settings": "Socruithe Meiteashonraí", - "metadata_settings_description": "Bainistigh socruithe meiteashonraí", - "migration_job": "Imirce", - "migration_job_description": "Aistrigh mionsamhlacha le haghaidh sócmhainní agus aghaidheanna chuig an struchtúr fillteán is déanaí", - "nightly_tasks_cluster_faces_setting_description": "Rith aitheantas aghaidhe ar aghaidheanna nua-bhraite", - "nightly_tasks_cluster_new_faces_setting": "Braisle aghaidheanna nua", - "nightly_tasks_database_cleanup_setting": "Tascanna glantacháin bunachar sonraí", - "nightly_tasks_database_cleanup_setting_description": "Glan suas sonraí seanchaite, imithe in éag ón mbunachar sonraí", - "nightly_tasks_generate_memories_setting": "Cuimhní cinn a ghiniúint", - "nightly_tasks_generate_memories_setting_description": "Cruthaigh cuimhní cinn nua ó shócmhainní", - "nightly_tasks_missing_thumbnails_setting": "Gin mionsamhlacha atá ar iarraidh", - "nightly_tasks_missing_thumbnails_setting_description": "Sócmhainní gan mionsamhlacha a chur i scuaine le haghaidh giniúint mionsamhlacha", - "nightly_tasks_settings": "Socruithe Tascanna Oíche", - "nightly_tasks_settings_description": "Bainistigh tascanna oíche", - "nightly_tasks_start_time_setting": "Am tosaithe", - "nightly_tasks_start_time_setting_description": "An t-am a thosaíonn an freastalaí ag rith na dtascanna oíche", - "nightly_tasks_sync_quota_usage_setting": "Úsáid cuóta sioncrónaithe", - "nightly_tasks_sync_quota_usage_setting_description": "Nuashonraigh cuóta stórála úsáideora, bunaithe ar an úsáid reatha", - "no_paths_added": "Níor cuireadh aon chosáin leis", - "no_pattern_added": "Níor cuireadh patrún leis", - "note_apply_storage_label_previous_assets": "Nóta: Chun an Lipéad Stórála a chur i bhfeidhm ar shócmhainní a uaslódáileadh roimhe seo, rith an", - "note_cannot_be_changed_later": "NÓTA: Ní féidir é seo a athrú níos déanaí!", - "notification_email_from_address": "Ó sheoladh", - "notification_email_from_address_description": "Seoladh ríomhphoist an tseoltóra, mar shampla: \"Immich Photo Server \". Déan cinnte seoladh a úsáid a bhfuil cead agat ríomhphoist a sheoladh uaidh.", - "notification_email_host_description": "Óstach an fhreastalaí ríomhphoist (m.sh. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Déan neamhaird ar earráidí teastais", - "notification_email_ignore_certificate_errors_description": "Déan neamhaird ar earráidí bailíochtaithe teastais TLS (ní mholtar)", - "notification_email_password_description": "Pasfhocal le húsáid agus fíordheimhniú á dhéanamh leis an bhfreastalaí ríomhphoist", - "notification_email_port_description": "Port an fhreastalaí ríomhphoist (m.sh. 25, 465, nó 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Úsáid SMTPS (SMTP thar TLS)", - "notification_email_sent_test_email_button": "Seol ríomhphost tástála agus sábháil", - "notification_email_setting_description": "Socruithe chun fógraí ríomhphoist a sheoladh", - "notification_email_test_email": "Seol ríomhphost tástála", - "notification_email_test_email_failed": "Theip ar ríomhphost tástála a sheoladh, seiceáil do luachanna", - "notification_email_test_email_sent": "Tá ríomhphost tástála seolta chuig {email}. Seiceáil do bhosca isteach le do thoil.", - "notification_email_username_description": "Ainm úsáideora le húsáid agus fíordheimhniú á dhéanamh leis an bhfreastalaí ríomhphoist", - "notification_enable_email_notifications": "Cumasaigh fógraí ríomhphoist", - "notification_settings": "Socruithe Fógra", - "notification_settings_description": "Bainistigh socruithe fógraí, lena n-áirítear ríomhphost", - "oauth_auto_launch": "Seoladh uathoibríoch", - "oauth_auto_launch_description": "Tosaigh sreabhadh logála isteach OAuth go huathoibríoch nuair a dhéantar nascleanúint chuig an leathanach logála isteach", - "oauth_auto_register": "Clárú uathoibríoch", - "oauth_auto_register_description": "Cláraigh úsáideoirí nua go huathoibríoch tar éis síniú isteach le OAuth", - "oauth_button_text": "Téacs cnaipe", - "oauth_client_secret_description": "Riachtanach do chliant rúnda, nó mura dtacaítear le PKCE (Eochair Chruthúnais le haghaidh Malartú Cód) do chliant poiblí.", - "oauth_enable_description": "Logáil isteach le OAuth", - "oauth_mobile_redirect_uri": "URI atreoraithe soghluaiste", - "oauth_mobile_redirect_uri_override": "Sárú URI atreoraithe soghluaiste", - "oauth_mobile_redirect_uri_override_description": "Cumasaigh nuair nach gceadaíonn soláthraí OAuth URI soghluaiste, mar shampla ''{callback}''", - "oauth_role_claim": "Éileamh Róil", - "oauth_role_claim_description": "Deonaigh rochtain riarthóra go huathoibríoch bunaithe ar láithreacht an éilimh seo. Féadfaidh 'úsáideoir' nó 'riarthóir' a bheith san éileamh.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Bainistigh socruithe logála isteach OAuth", - "oauth_settings_more_details": "Le haghaidh tuilleadh sonraí faoin ngné seo, féach ar na doiciméid.", - "oauth_storage_label_claim": "Éileamh lipéad stórála", - "oauth_storage_label_claim_description": "Socraigh lipéad stórála an úsáideora go huathoibríoch go luach an éilimh seo.", - "oauth_storage_quota_claim": "Éileamh ar chuóta stórála", - "oauth_storage_quota_claim_description": "Socraigh cuóta stórála an úsáideora go huathoibríoch go luach an éilimh seo.", - "oauth_storage_quota_default": "Cuóta stórála réamhshocraithe (GiB)", - "oauth_storage_quota_default_description": "Cuóta i GiB le húsáid nuair nach gcuirtear aon éileamh ar fáil.", - "oauth_timeout": "Iarratas Ama", - "oauth_timeout_description": "Am críochnaithe d'iarratais i milleasoicindí", - "ocr_job_description": "Úsáid foghlaim meaisín chun téacs in íomhánna a aithint", - "password_enable_description": "Logáil isteach le ríomhphost agus pasfhocal", - "password_settings": "Pasfhocal Logáil Isteach", - "password_settings_description": "Bainistigh socruithe logála isteach le pasfhocal", - "paths_validated_successfully": "Gach cosán bailíochtaithe go rathúil", - "person_cleanup_job": "Glanadh duine", - "queue_details": "Sonraí na Scuaine", - "queues": "Scuainí Poist", - "queues_page_description": "Leathanach scuainí poist riarthóra", - "quota_size_gib": "Méid an Chuóta (GiB)", - "refreshing_all_libraries": "Ag athnuachan na leabharlanna go léir", - "registration": "Clárú Riaracháin", - "registration_description": "Ós rud é gur tusa an chéad úsáideoir ar an gcóras, sannfar an Riarthóir duit agus beidh tú freagrach as tascanna riaracháin, agus cruthóidh tú féin úsáideoirí breise.", - "remove_failed_jobs": "Bain poist theip orthu", - "require_password_change_on_login": "Éiligh ar an úsáideoir an focal faire a athrú ar an gcéad logáil isteach", - "reset_settings_to_default": "Athshocraigh socruithe go dtí na socruithe réamhshocraithe", - "reset_settings_to_recent_saved": "Athshocraigh socruithe chuig na socruithe sábháilte le déanaí", - "scanning_library": "Leabharlann scanadh", - "search_jobs": "Cuardaigh poist…", - "send_welcome_email": "Seol ríomhphost fáilte", - "server_external_domain_settings": "Fearann seachtrach", - "server_external_domain_settings_description": "Fearann le haghaidh naisc chomhroinnte poiblí, lena n-áirítear http(s)://", - "server_public_users": "Úsáideoirí Poiblí", - "server_public_users_description": "Liostaítear gach úsáideoir (ainm agus ríomhphost) nuair a chuirtear úsáideoir le halbaim chomhroinnte. Nuair a bhíonn sé díchumasaithe, ní bheidh an liosta úsáideoirí ar fáil ach d’úsáideoirí riarthóra.", - "server_settings": "Socruithe Freastalaí", - "server_settings_description": "Bainistigh socruithe an fhreastalaí", - "server_stats_page_description": "Leathanach staitisticí freastalaí riarthóra", - "server_welcome_message": "Teachtaireacht fáilte", - "server_welcome_message_description": "Teachtaireacht a thaispeántar ar an leathanach logála isteach.", - "settings_page_description": "Leathanach socruithe riarthóra", - "sidecar_job": "Meiteashonraí taobhcharr", - "sidecar_job_description": "Meiteashonraí taobhlíne ón gcóras comhad a aimsiú nó a shioncrónú", - "slideshow_duration_description": "Líon na soicindí chun gach íomhá a thaispeáint", - "smart_search_job_description": "Rith foghlaim meaisín ar shócmhainní chun tacú le cuardach cliste", - "storage_template_date_time_description": "Úsáidtear stampa ama cruthaithe na sócmhainne don fhaisnéis dáta-ama", - "storage_template_date_time_sample": "Am samplach {date}", - "storage_template_enable_description": "Cumasaigh inneall teimpléid stórála", - "storage_template_hash_verification_enabled": "Fíorú haise cumasaithe", - "storage_template_hash_verification_enabled_description": "Cumasaíonn sé fíorú haise, ná díchumasaigh é seo mura bhfuil tú cinnte faoi na himpleachtaí", - "storage_template_migration": "Imirce teimpléid stórála", - "storage_template_migration_description": "Cuir an {template} reatha i bhfeidhm ar shócmhainní a uaslódáileadh roimhe seo", - "storage_template_migration_info": "Déanfaidh an teimpléad stórála na síntí go léir a thiontú go litreacha beaga. Ní bheidh feidhm ag athruithe ar an teimpléad ach amháin maidir le sócmhainní nua. Chun an teimpléad a chur i bhfeidhm go siarghabhálach ar shócmhainní a uaslódáladh roimhe seo, rith {job}.", - "storage_template_migration_job": "Post Imirce Teimpléid Stórála", - "storage_template_more_details": "Le haghaidh tuilleadh sonraí faoin ngné seo, féach ar an Teimpléad Stórála agus a implications", - "storage_template_onboarding_description_v2": "Nuair a bheidh sé cumasaithe, eagróidh an ghné seo comhaid go huathoibríoch bunaithe ar theimpléad atá sainithe ag an úsáideoir. Le haghaidh tuilleadh eolais, féach ar an doiciméadú.", - "storage_template_path_length": "Teorainn fhaid chosáin thart: {length, number}/{limit, number}", - "storage_template_settings": "Teimpléad Stórála", - "storage_template_settings_description": "Bainistigh struchtúr an fhillteáin agus ainm comhaid na sócmhainne uaslódála", - "storage_template_user_label": "Is é {label} Lipéad Stórála an úsáideora", - "system_settings": "Socruithe Córais", - "tag_cleanup_job": "Glanadh clibeanna", - "template_email_available_tags": "Is féidir leat na hathróga seo a leanas a úsáid i do theimpléad: {tags}", - "template_email_if_empty": "Mura bhfuil an teimpléad ann, úsáidfear an ríomhphost réamhshocraithe.", - "template_email_invite_album": "Teimpléad Albam Cuireadh", - "template_email_preview": "Réamhamharc", - "template_email_settings": "Teimpléid Ríomhphoist", - "template_email_update_album": "Nuashonraigh Teimpléad Albam", - "template_email_welcome": "Teimpléad ríomhphoist fáilte", - "template_settings": "Teimpléid Fógra", - "template_settings_description": "Bainistigh teimpléid saincheaptha le haghaidh fógraí", - "theme_custom_css_settings": "CSS saincheaptha", - "theme_custom_css_settings_description": "Le Bileoga Stíl Eascáideacha is féidir dearadh Immich a shaincheapadh.", - "theme_settings": "Socruithe Téama", - "theme_settings_description": "Bainistigh saincheapadh chomhéadan gréasáin Immich", - "thumbnail_generation_job": "Gin Mionsamhlacha", - "thumbnail_generation_job_description": "Gin mionsamhlacha móra, beaga agus doiléire do gach sócmhainn, chomh maith le mionsamhlacha do gach duine", - "transcoding_acceleration_api": "API Luasghéaraithe", - "transcoding_acceleration_api_description": "An API a idirghníomhóidh le do ghléas chun traschódú a bhrostú. Is é an socrú seo an 'iarracht is fearr': úsáidfear traschódú bogearraí mar rogha eile má theipeann air. D’fhéadfadh VP9 oibriú nó gan oibriú ag brath ar do chrua-earraí.", - "transcoding_acceleration_nvenc": "NVENC (éilíonn GPU NVIDIA)", - "transcoding_acceleration_qsv": "Sioncrónú Tapa (éilíonn LAP Intel den 7ú glúin nó níos déanaí)", - "transcoding_acceleration_rkmpp": "RKMPP (ar SOCanna Rockchip amháin)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Códaic fuaime glactha", - "transcoding_accepted_audio_codecs_description": "Roghnaigh cé na códaic fuaime nach gá a thraschódú. Ní úsáidtear é ach le haghaidh polasaithe traschódála áirithe.", - "transcoding_accepted_containers": "Coimeádáin glactha", - "transcoding_accepted_containers_description": "Roghnaigh cé na formáidí coimeádáin nach gá a ath-chomhshó go MP4. Ní úsáidtear é ach le haghaidh polasaithe traschódaithe áirithe.", - "transcoding_accepted_video_codecs": "Códaic físe glactha", - "transcoding_accepted_video_codecs_description": "Roghnaigh cé na códaic físe nach gá a thraschódú. Ní úsáidtear é ach le haghaidh polasaithe traschódála áirithe.", - "transcoding_advanced_options_description": "Roghanna nach mbeadh ar fhormhór na n-úsáideoirí a athrú", - "transcoding_audio_codec": "Códac fuaime", - "transcoding_audio_codec_description": "Is é Opus an rogha is airde cáilíochta, ach tá comhoiriúnacht níos ísle aige le seanghléasanna nó bogearraí.", - "transcoding_bitrate_description": "Físeáin níos airde ná an uasráta giotán nó nach bhfuil i bhformáid inghlactha", - "transcoding_codecs_learn_more": "Chun tuilleadh eolais a fháil faoin téarmaíocht a úsáidtear anseo, féach ar dhoiciméid FFmpeg le haghaidh códéc H.264, códéc HEVC agus códéc VP9.", - "transcoding_constant_quality_mode": "Mód cáilíochta tairiseach", - "transcoding_constant_quality_mode_description": "Tá ICQ níos fearr ná CQP, ach ní thacaíonn roinnt gléasanna luasghéaraithe crua-earraí leis an modh seo. Trí an rogha seo a shocrú, tabharfar tús áite don mhodh sonraithe agus ionchódú bunaithe ar cháilíocht á úsáid. Ní thacaíonn NVENC leis mar nach dtacaíonn sé le ICQ.", - "transcoding_constant_rate_factor": "Fachtóir ráta tairiseach (-crf)", - "transcoding_constant_rate_factor_description": "Leibhéal cáilíochta físe. Is iad na luachanna tipiciúla ná 23 do H.264, 28 do HEVC, 31 do VP9, agus 35 do AV1. Is fearr dá ísle, ach cruthaíonn sé comhaid níos mó.", - "transcoding_disabled_description": "Ná traschódaigh aon fhíseáin, d'fhéadfadh sé go gcuirfí isteach ar an athsheinm ar roinnt cliant", - "transcoding_encoding_options": "Roghanna Ionchódaithe", - "transcoding_encoding_options_description": "Socraigh códaigh, taifeach, cáilíocht agus roghanna eile do na físeáin ionchódaithe", - "transcoding_hardware_acceleration": "Luasghéarú Crua-earraí", - "transcoding_hardware_acceleration_description": "Turgnamhach: traschódú níos tapúla ach d'fhéadfadh sé go laghdófaí an caighdeán ag an ráta giotán céanna", - "transcoding_hardware_decoding": "Díchódú crua-earraí", - "transcoding_hardware_decoding_setting_description": "Cumasaíonn sé luasghéarú ó cheann ceann go ceann seachas ionchódú amháin a luasghéarú. Seans nach n-oibreoidh sé ar gach físeán.", - "transcoding_max_b_frames": "Uasmhéid frámaí B", - "transcoding_max_b_frames_description": "Feabhsaíonn luachanna níos airde éifeachtúlacht an chomhbhrúite, ach moillíonn siad ionchódú. B’fhéidir nach mbeidh siad comhoiriúnach le luasghéarú crua-earraí ar fheistí níos sine. Díchumasaíonn 0 frámaí-B, agus socraíonn -1 an luach seo go huathoibríoch.", - "transcoding_max_bitrate": "Uasráta giotán", - "transcoding_max_bitrate_description": "Is féidir le huasráta giotán a shocrú méideanna comhad a dhéanamh níos intuartha ar chostas beag don cháilíocht. Ag 720p, is iad na luachanna tipiciúla ná 2600 kbit/s do VP9 nó HEVC, nó 4500 kbit/s do H.264. Díchumasaithe má shocraítear go 0. Nuair nach sonraítear aon aonad, glactar leis go bhfuil k (do kbit/s); dá bhrí sin, tá 5000, 5000k, agus 5M (do Mbit/s) coibhéiseach.", - "transcoding_max_keyframe_interval": "Uasmhéid eatramh fráma eochair", - "transcoding_max_keyframe_interval_description": "Socraíonn sé an fad fráma uasta idir eochairfhrámaí. Laghdaíonn luachanna níos ísle éifeachtúlacht an chomhbhrúite, ach feabhsaíonn siad amanna cuardaigh agus féadfaidh siad feabhas a chur ar cháilíocht i radhairc le gluaiseacht thapa. Socraíonn 0 an luach seo go huathoibríoch.", - "transcoding_optimal_description": "Físeáin níos airde ná an rún sprice nó nach bhfuil i bhformáid inghlactha", - "transcoding_policy": "Polasaí Traschódála", - "transcoding_policy_description": "Socraigh cathain a thraschódófar físeán", - "transcoding_preferred_hardware_device": "Gléas crua-earraí is fearr leat", - "transcoding_preferred_hardware_device_description": "Ní bhaineann sé ach le VAAPI agus QSV. Socraíonn sé an nód dri a úsáidtear le haghaidh traschódú crua-earraí.", - "transcoding_preset_preset": "Réamhshocrú (-réamhshocrú)", - "transcoding_preset_preset_description": "Luas comhbhrúite. Gineann réamhshocruithe níos moille comhaid níos lú, agus méadaíonn siad cáilíocht nuair a bhíonn ráta giotán áirithe á spriocdhíriú. Ní thugann VP9 aird ar luasanna os cionn 'níos tapúla'.", - "transcoding_reference_frames": "Frámaí tagartha", - "transcoding_reference_frames_description": "Líon na bhfrámaí le tagairt dóibh agus fráma áirithe á chomhbhrú. Feabhsaíonn luachanna níos airde éifeachtúlacht an chomhbhrúite, ach moillíonn siad an ionchódú. Socraíonn 0 an luach seo go huathoibríoch.", - "transcoding_required_description": "Físeáin amháin nach bhfuil i bhformáid inghlactha", - "transcoding_settings": "Socruithe Traschódaithe Físeáin", - "transcoding_settings_description": "Bainistigh cé na físeáin atá le traschódú agus conas iad a phróiseáil", - "transcoding_target_resolution": "Réiteach sprice", - "transcoding_target_resolution_description": "Is féidir le taifeach níos airde níos mó sonraí a chaomhnú ach tógann sé níos faide iad a ionchódú, bíonn méideanna comhaid níos mó acu, agus féadann siad freagrúlacht aipeanna a laghdú.Is féidir le taifeach níos airde níos mó sonraí a chaomhnú ach tógann sé níos faide iad a ionchódú, bíonn méideanna comhaid níos mó acu, agus féadann siad freagrúlacht aipeanna a laghdú.", - "transcoding_temporal_aq": "AQ Ama", - "transcoding_temporal_aq_description": "Baineann sé seo le NVENC amháin. Méadaíonn Cainníochtú Oiriúnaitheach Ama cáilíocht radharcanna ard-mhionsonraí, ísealghluaiseachta. Seans nach mbeidh sé comhoiriúnach le gléasanna níos sine.", - "transcoding_threads": "Snáitheanna", - "transcoding_threads_description": "Bíonn ionchódú níos tapúla mar thoradh ar luachanna níos airde, ach fágann siad níos lú spáis don fhreastalaí chun tascanna eile a phróiseáil agus é gníomhach. Níor cheart go mbeadh an luach seo níos mó ná líon chroíthe an LAP. Uasmhéadaíonn sé an úsáid má shocraítear é go 0.", - "transcoding_tone_mapping": "Mapáil toin", - "transcoding_tone_mapping_description": "Déanann iarracht cuma físeáin HDR a chaomhnú nuair a dhéantar iad a thiontú go SDR. Déanann gach algartam comhbhabhtálacha difriúla maidir le dath, mionsonraí agus gile. Coinníonn Hable mionsonraí, coinníonn Mobius dath, agus coinníonn Reinhard gile.", - "transcoding_transcode_policy": "Polasaí traschódála", - "transcoding_transcode_policy_description": "Polasaí maidir le cathain ba chóir físeán a thraschódú. Déanfar físeáin HDR a thraschódú i gcónaí (ach amháin má tá traschódú díchumasaithe).", - "transcoding_two_pass_encoding": "Ionchódú dhá phas", - "transcoding_two_pass_encoding_setting_description": "Traschódaigh i ndá phas chun físeáin ionchódaithe níos fearr a tháirgeadh. Nuair a bhíonn an ráta giotán uasta cumasaithe (riachtanach chun go n-oibreoidh sé le H.264 agus HEVC), úsáideann an mód seo raon ráta giotán bunaithe ar an ráta giotán uasta agus ní thugann sé aird ar CRF. I gcás VP9, is féidir CRF a úsáid má tá an ráta giotán uasta díchumasaithe.", - "transcoding_video_codec": "Códac físe", - "transcoding_video_codec_description": "Tá ardéifeachtúlacht agus comhoiriúnacht gréasáin ag VP9, ach tógann sé níos faide é a thraschódú. Feidhmíonn HEVC ar an gcaoi chéanna, ach tá comhoiriúnacht gréasáin níos ísle aige. Tá H.264 comhoiriúnach go forleathan agus is furasta é a thraschódú, ach táirgeann sé comhaid i bhfad níos mó. Is é AV1 an códac is éifeachtaí ach níl tacaíocht aige ar fheistí níos sine.", - "trash_enabled_description": "Cumasaigh gnéithe Bruscair", - "trash_number_of_days": "Líon na laethanta", - "trash_number_of_days_description": "Líon na laethanta chun na sócmhainní a choinneáil sa bhruscar sula mbaintear iad go buan", - "trash_settings": "Socruithe Bruscair", - "trash_settings_description": "Bainistigh socruithe bruscair", - "unlink_all_oauth_accounts": "Dínasc gach cuntas OAuth", - "unlink_all_oauth_accounts_description": "Cuimhnigh gach cuntas OAuth a dhínascadh sula ndéanann tú aistriú chuig soláthraí nua.", - "unlink_all_oauth_accounts_prompt": "An bhfuil tú cinnte gur mian leat gach cuntas OAuth a dhínascadh? Athshocróidh sé seo an ID OAuth do gach úsáideoir agus ní féidir é a chealú.", - "user_cleanup_job": "Glanadh úsáideora", - "user_delete_delay": "Sceidealófar cuntas agus sócmhainní {user} le haghaidh scriosadh buan i gceann {delay, plural, one {# day} other {# days}}.", - "user_delete_delay_settings": "Moill scriosta", - "user_delete_delay_settings_description": "Líon na laethanta tar éis bainte chun cuntas agus sócmhainní úsáideora a scriosadh go buan. Ritheann an post scriosta úsáideora ag meán oíche chun a sheiceáil an bhfuil úsáideoirí réidh le scriosadh. Déanfar athruithe ar an socrú seo a mheas ag an gcéad fhorghníomhú eile.", - "user_delete_immediately": "Cuirfear cuntas agus sócmhainní {user} i scuaine le haghaidh scriosadh buan láithreach.", - "user_delete_immediately_checkbox": "Cuir úsáideoir agus sócmhainní i scuaine le haghaidh scriosadh láithreach", - "user_details": "Sonraí Úsáideora", - "user_management": "Bainistíocht Úsáideoirí", - "user_password_has_been_reset": "Tá pasfhocal an úsáideora athshocraithe:", - "user_password_reset_description": "Tabhair an focal faire sealadach don úsáideoir agus cuir in iúl dóibh go mbeidh orthu an focal faire a athrú an chéad uair eile a logálann siad isteach.", - "user_restore_description": "Athchóireofar cuntas {user}.", - "user_restore_scheduled_removal": "Athchóirigh úsáideoir - baint sceidealaithe ar {date, date, long}", - "user_settings": "Socruithe Úsáideora", - "user_settings_description": "Bainistigh socruithe úsáideora", - "user_successfully_removed": "Baineadh an t-úsáideoir {email} go rathúil.", - "users_page_description": "Leathanach úsáideoirí riarthóra", - "version_check_enabled_description": "Cumasaigh seiceáil leagan", - "version_check_implications": "Braitheann an ghné seiceála leagan ar chumarsáid thréimhsiúil le github.com", - "version_check_settings": "Seiceáil Leagan", - "version_check_settings_description": "Cumasaigh/díchumasaigh an fógra faoin leagan nua", - "video_conversion_job": "Físeáin Traschódaithe", - "video_conversion_job_description": "Traschódaigh físeáin le haghaidh comhoiriúnachta níos leithne le brabhsálaithe agus gléasanna" - }, - "admin_email": "Ríomhphost an Riarthóra", - "admin_password": "Pasfhocal Riarthóra", - "administration": "Riarachán", - "advanced": "Ardleibhéil", - "advanced_settings_clear_image_cache": "Glan an Taisce Íomhá", - "advanced_settings_clear_image_cache_error": "Theip ar an taisce íomhá a ghlanadh", - "advanced_settings_clear_image_cache_success": "Glanadh {size} go rathúil", - "advanced_settings_enable_alternate_media_filter_subtitle": "Úsáid an rogha seo chun meáin a scagadh le linn sioncrónaithe bunaithe ar chritéir mhalartacha. Ná déan iarracht air seo ach amháin má bhíonn fadhbanna agat leis an aip ag braith gach albam.", - "advanced_settings_enable_alternate_media_filter_title": "[TURGNAMHACH] Úsáid scagaire sioncrónaithe albam gléas malartach", - "advanced_settings_log_level_title": "Leibhéal loga: {level}", - "advanced_settings_prefer_remote_subtitle": "Bíonn roinnt gléasanna thar a bheith mall ag luchtú mionsamhlacha ó shócmhainní áitiúla. Gníomhachtaigh an socrú seo chun íomhánna iargúlta a luchtú ina ionad.", - "advanced_settings_prefer_remote_title": "Is fearr leat íomhánna iargúlta", - "advanced_settings_proxy_headers_subtitle": "Sainmhínigh ceanntásca seachfhreastalaí ba chóir do Immich a sheoladh le gach iarratas líonra", - "advanced_settings_proxy_headers_title": "Ceanntásca seachfhreastalaí saincheaptha [TURGNAÍOCH]", - "advanced_settings_readonly_mode_subtitle": "Cumasaíonn sé seo an modh léite amháin ina bhféadfar na grianghraif a fheiceáil amháin, agus bíonn rudaí cosúil le híomhánna iolracha a roghnú, a roinnt, a chraoladh, a scriosadh díchumasaithe. Cumasaigh/Díchumasaigh an modh léite amháin trí abhatár an úsáideora ón bpríomhscáileán", - "advanced_settings_readonly_mode_title": "Mód léite amháin", - "advanced_settings_self_signed_ssl_subtitle": "Scipeann sé fíorú teastais SSL don chríochphointe freastalaí. Riachtanach le haghaidh teastais féinshínithe.", - "advanced_settings_self_signed_ssl_title": "Ceadaigh teastais SSL féinshínithe [TURGHAINNEACH]", - "advanced_settings_sync_remote_deletions_subtitle": "Scrios nó athchóirigh sócmhainn go huathoibríoch ar an ngléas seo nuair a dhéantar an gníomh sin ar an ngréasán", - "advanced_settings_sync_remote_deletions_title": "Sioncrónaigh scriostaí iargúlta [TURGNAMHACH]", - "advanced_settings_tile_subtitle": "Socruithe úsáideora ardleibhéil", - "advanced_settings_troubleshooting_subtitle": "Cumasaigh gnéithe breise le haghaidh fabhtcheartaithe", - "advanced_settings_troubleshooting_title": "Fabhtcheartú", - "age_months": "Aois {months, plural, one {# mí} other {# míonna}}", - "age_year_months": "Aois 1 bhliain, {months, plural, one {# mí} other {# míonna}}", - "age_years": "{years, plural, other {Aois #}}", - "album": "Albam", - "album_added": "Albam curtha leis", - "album_added_notification_setting_description": "Faigh fógra ríomhphoist nuair a chuirtear le halbam comhroinnte thú", - "album_cover_updated": "Clúdach an albaim nuashonraithe", - "album_delete_confirmation": "An bhfuil tú cinnte gur mian leat an t-albam {album} a scriosadh?", - "album_delete_confirmation_description": "Mura roinnfear an t-albam seo, ní bheidh úsáideoirí eile in ann rochtain a fháil air a thuilleadh.", - "album_deleted": "Scriosadh an t-albam", - "album_info_card_backup_album_excluded": "EISIATA", - "album_info_card_backup_album_included": "SAN ÁIREAMH", - "album_info_updated": "Eolas albam nuashonraithe", - "album_leave": "Fág an t-albam?", - "album_leave_confirmation": "An bhfuil tú cinnte gur mian leat {album} a fhágáil?", - "album_name": "Ainm an Albaim", - "album_options": "Roghanna albaim", - "album_remove_user": "Bain an t-úsáideoir?", - "album_remove_user_confirmation": "An bhfuil tú cinnte gur mian leat {user} a bhaint?", - "album_search_not_found": "Ní bhfuarthas aon albaim a mheaitseálann do chuardach", - "album_selected": "Albam roghnaithe", - "album_share_no_users": "Is cosúil gur roinn tú an t-albam seo le gach úsáideoir nó nach bhfuil aon úsáideoir agat le roinnt leis.", - "album_summary": "Achoimre ar an albam", - "album_updated": "Albam nuashonraithe", - "album_updated_setting_description": "Faigh fógra ríomhphoist nuair a bhíonn sócmhainní nua i albam comhroinnte", - "album_upload_assets": "Uaslódáil sócmhainní ó do ríomhaire agus cuir le halbam iad", - "album_user_left": "D'fhág {album}", - "album_user_removed": "Baineadh {user}", - "album_viewer_appbar_delete_confirm": "An bhfuil tú cinnte gur mian leat an t-albam seo a scriosadh ó do chuntas?", - "album_viewer_appbar_share_err_delete": "Theip ar an albam a scriosadh", - "album_viewer_appbar_share_err_leave": "Theip ar an albam a fhágáil", - "album_viewer_appbar_share_err_remove": "Tá fadhbanna ann maidir le sócmhainní a bhaint as albam", - "album_viewer_appbar_share_err_title": "Theip ar theideal an albaim a athrú", - "album_viewer_appbar_share_leave": "Fág an t-albam", - "album_viewer_appbar_share_to": "Comhroinn Le", - "album_viewer_page_share_add_users": "Cuir úsáideoirí leis", - "album_with_link_access": "Lig d’aon duine a bhfuil an nasc aige grianghraif agus daoine san albam seo a fheiceáil.", - "albums": "Albaim", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albaim}}", - "albums_default_sort_order": "Ord sórtála réamhshocraithe albam", - "albums_default_sort_order_description": "Ord sórtála sócmhainní tosaigh agus albaim nua á gcruthú.", - "albums_feature_description": "Bailiúcháin sócmhainní is féidir a roinnt le húsáideoirí eile.", - "albums_on_device_count": "Albaim ar an ngléas ({count})", - "albums_selected": "{count, plural, one {# albam roghnaithe} other {# albam roghnaithe}}", - "all": "Gach", - "all_albums": "Gach albam", - "all_people": "Gach duine", - "all_photos": "Gach grianghraf", - "all_videos": "Gach físeán", - "allow_dark_mode": "Ceadaigh mód dorcha", - "allow_edits": "Ceadaigh eagarthóireachtaí", - "allow_public_user_to_download": "Ceadaigh d'úsáideoirí poiblí íoslódáil a dhéanamh", - "allow_public_user_to_upload": "Ceadaigh d'úsáideoirí poiblí uaslódáil", - "allowed": "Ceadaithe", - "alt_text_qr_code": "Íomhá cód QR", - "always_keep": "Coinnigh i gcónaí", - "always_keep_photos_hint": "Coinneoidh Saoradh Spáis na grianghraif go léir ar an ngléas seo.", - "always_keep_videos_hint": "Coinneoidh Saoradh Spáis na físeáin go léir ar an ngléas seo.", - "anti_clockwise": "Tuathalach", - "api_key": "Eochair API", - "api_key_description": "Ní thaispeánfar an luach seo ach uair amháin. Bí cinnte é a chóipeáil sula ndúnann tú an fhuinneog.", - "api_key_empty": "Níor cheart go mbeadh ainm d’Eochair API folamh", - "api_keys": "Eochracha API", - "app_architecture_variant": "Malartach (Ailtireacht)", - "app_bar_signout_dialog_content": "An bhfuil tú cinnte gur mhaith leat síniú amach?", - "app_bar_signout_dialog_ok": "Tá", - "app_bar_signout_dialog_title": "Sínigh amach", - "app_download_links": "Naisc Íoslódála Aipeanna", - "app_settings": "Socruithe Aipe", - "app_stores": "Siopaí Aipeanna", - "app_update_available": "Tá nuashonrú aip ar fáil", - "appears_in": "Feictear i", - "apply_count": "Cuir i bhfeidhm ({count, number})", - "archive": "Cartlann", - "archive_action_prompt": "{count} curtha leis an gCartlann", - "archive_or_unarchive_photo": "Cartlannaigh nó díchartlannaigh grianghraf", - "archive_page_no_archived_assets": "Ní bhfuarthas aon sócmhainní cartlannaithe", - "archive_page_title": "Cartlann ({count})", - "archive_size": "Méid na cartlainne", - "archive_size_description": "Cumraigh méid na cartlainne le haghaidh íoslódálacha (i GiB)", - "archived": "Cartlannaithe", - "archived_count": "{count, plural, other {Cartlannaithe #}}", - "are_these_the_same_person": "An iad seo an duine céanna?", - "are_you_sure_to_do_this": "An bhfuil tú cinnte gur mian leat é seo a dhéanamh?", - "array_field_not_fully_supported": "Éilíonn réimsí eagar eagarthóireacht JSON de láimh", - "asset_action_delete_err_read_only": "Ní féidir sócmhainn(í) léite amháin a scriosadh, ag scipeáil", - "asset_action_share_err_offline": "Ní féidir sócmhainn(í) as líne a fháil, ag scipeáil", - "asset_added_to_album": "Curtha leis an albam", - "asset_adding_to_album": "Ag cur leis an albam…", - "asset_created": "Sócmhainn cruthaithe", - "asset_description_updated": "Tá cur síos na sócmhainne nuashonraithe", - "asset_filename_is_offline": "Tá an tsócmhainn {filename} as líne", - "asset_has_unassigned_faces": "Tá aghaidheanna neamhshannta ag an tsócmhainn", - "asset_hashing": "Ag hasáil…", - "asset_list_group_by_sub_title": "Grúpáil de réir", - "asset_list_layout_settings_dynamic_layout_title": "Leagan amach dinimiciúil", - "asset_list_layout_settings_group_automatically": "Uathoibríoch", - "asset_list_layout_settings_group_by": "Sócmhainní grúpa de réir", - "asset_list_layout_settings_group_by_month_day": "Mí + lá", - "asset_list_layout_sub_title": "Leagan Amach", - "asset_list_settings_subtitle": "Socruithe leagan amach eangach grianghraf", - "asset_list_settings_title": "Eangach Grianghraf", - "asset_not_found_on_device_android": "Níor aimsíodh an tsócmhainn ar an ngléas", - "asset_not_found_on_device_ios": "Sócmhainn gan teacht ar an ngléas. Má tá iCloud in úsáid agat, b'fhéidir nach bhfuil an tsócmhainn inrochtana mar gheall ar chomhad lochtach atá stóráilte ar iCloud", - "asset_not_found_on_icloud": "Sócmhainn gan teacht ar iCloud. B’fhéidir nach bhfuil an tsócmhainn inrochtana mar gheall ar chomhad lochtach atá stóráilte ar iCloud", - "asset_offline": "Sócmhainn As Líne", - "asset_offline_description": "Níl an tsócmhainn sheachtrach seo le fáil ar dhiosca a thuilleadh. Téigh i dteagmháil le riarthóir do Immich le haghaidh cabhrach.", - "asset_restored_successfully": "Athchóiríodh an tsócmhainn go rathúil", - "asset_skipped": "Scipeáilte", - "asset_skipped_in_trash": "Sa bhruscar", - "asset_trashed": "Sócmhainn curtha sa bhruscar", - "asset_troubleshoot": "Fabhtcheartú Sócmhainní", - "asset_uploaded": "Uaslódáilte", - "asset_uploading": "Ag uaslódáil…", - "asset_viewer_settings_subtitle": "Bainistigh do shocruithe breathnóra gailearaí", - "asset_viewer_settings_title": "Amharcóir Sócmhainní", - "assets": "Sócmhainní", - "assets_added_count": "Cuireadh {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "assets_added_to_album_count": "Cuireadh {count, plural, one {# asset} other {# assets}} leis an albam", - "assets_added_to_albums_count": "Cuireadh {assetTotal, plural, one {# sócmhainn} other {# sócmhainní}} go {albumTotal, plural, one {# albam} other {# albaim}}", - "assets_cannot_be_added_to_album_count": "Ní féidir {count, plural, one {Sócmhainn} other {Sócmhainní}} a chur leis an albam", - "assets_cannot_be_added_to_albums": "Ní féidir {count, plural, one {Asset} other {Assets}} a chur le haon cheann de na halbaim", - "assets_count": "{count, plural, one {# sócmhainn} other {# sócmhainní}}", - "assets_deleted_permanently": "Scriosadh {count} sócmhainn(í) go buan", - "assets_deleted_permanently_from_server": "Scriosadh {count} sócmhainn(í) go buan ón bhfreastalaí Immich", - "assets_downloaded_failed": "{count, plural, one {Íoslódáil # comhad - {error} theip ar chomhad} other {Íoslódáil # comhaid - {error} theip ar chomhad}}", - "assets_downloaded_successfully": "{count, plural, one {Íoslódáileadh # comhad go rathúil} other {Íoslódáileadh # comhaid go rathúil}}", - "assets_moved_to_trash_count": "Bogadh {count, plural, one {# sócmhainn} other {# sócmhainní}} go dtí an bruscar", - "assets_permanently_deleted_count": "Scriosta go buan {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "assets_removed_count": "Baineadh {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "assets_removed_permanently_from_device": "Baineadh {count} sócmhainn(í) go buan ó do ghléas", - "assets_restore_confirmation": "An bhfuil tú cinnte gur mian leat do shócmhainní uile atá curtha sa bhruscar a athchóiriú? Ní féidir leat an gníomh seo a chealú! Tabhair faoi deara nach féidir aon sócmhainní as líne a athchóiriú ar an mbealach seo.", - "assets_restored_count": "Athchóirithe {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "assets_restored_successfully": "{count} sócmhainn(í) athchóirithe go rathúil", - "assets_trashed": "{count} sócmhainn(í) curtha sa bhruscar", - "assets_trashed_count": "Bruscar {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "assets_trashed_from_server": "{count} sócmhainn(í) curtha sa bhruscar ón bhfreastalaí Immich", - "assets_were_part_of_album_count": "{count, plural, one {Sócmhainn a bhí} other {Sócmhainní a bhí}} mar chuid den albam cheana féin", - "assets_were_part_of_albums_count": "Bhí {count, plural, one {an tsócmhainn} other {na tsócmhainní}} mar chuid de na halbaim cheana féin", - "authorized_devices": "Gléasanna Údaraithe", - "automatic_endpoint_switching_subtitle": "Ceangail go háitiúil thar Wi-Fi ainmnithe nuair atá sé ar fáil agus bain úsáid as naisc mhalartacha in áiteanna eile", - "automatic_endpoint_switching_title": "Athrú uathoibríoch URL", - "autoplay_slideshow": "Taispeántas sleamhnán uathsheinm", - "back": "Ar ais", - "back_close_deselect": "Siar, dún, nó díroghnaigh", - "background_backup_running_error": "Tá cúltaca cúlra ar siúl faoi láthair, ní féidir cúltaca láimhe a thosú", - "background_location_permission": "Cead suímh sa chúlra", - "background_location_permission_content": "Chun líonraí a athrú agus é ag rith sa chúlra, ní mór rochtain chruinn suímh a bheith ag Immich *i gcónaí* ionas gur féidir leis an aip ainm an líonra Wi-Fi a léamh", - "background_options": "Roghanna Cúlra", - "backup": "Cúltaca", - "backup_album_selection_page_albums_device": "Albaim ar an ngléas ({count})", - "backup_album_selection_page_albums_tap": "Tapáil le cur san áireamh, tapáil faoi dhó le heisiamh", - "backup_album_selection_page_assets_scatter": "Is féidir sócmhainní a scaipeadh ar fud il-albaim. Dá bhrí sin, is féidir albaim a chur san áireamh nó a eisiamh le linn an phróisis chúltaca.", - "backup_album_selection_page_select_albums": "Roghnaigh albaim", - "backup_album_selection_page_selection_info": "Eolas Roghnúcháin", - "backup_album_selection_page_total_assets": "Iomlán na sócmhainní uathúla", - "backup_albums_sync": "Sioncrónú Albam Cúltaca", - "backup_all": "Gach", - "backup_background_service_backup_failed_message": "Theip ar chúltaca sócmhainní. Ag iarraidh arís…", - "backup_background_service_complete_notification": "Cúltaca sócmhainní críochnaithe", - "backup_background_service_connection_failed_message": "Theip ar cheangal leis an bhfreastalaí. Ag iarraidh arís…", - "backup_background_service_current_upload_notification": "Ag uaslódáil {filename}", - "backup_background_service_default_notification": "Ag seiceáil le haghaidh sócmhainní nua…", - "backup_background_service_error_title": "Earráid chúltaca", - "backup_background_service_in_progress_notification": "Ag déanamh cúltaca de do shócmhainní…", - "backup_background_service_upload_failure_notification": "Theip ar {filename} a uaslódáil", - "backup_controller_page_albums": "Albaim Chúltaca", - "backup_controller_page_background_app_refresh_disabled_content": "Cumasaigh athnuachan aipeanna cúlra i Socruithe > Ginearálta > Athnuachan Aipeanna Cúlra chun cúltaca cúlra a úsáid.", - "backup_controller_page_background_app_refresh_disabled_title": "Athnuachan aip sa chúlra díchumasaithe", - "backup_controller_page_background_app_refresh_enable_button_text": "Téigh go dtí na socruithe", - "backup_controller_page_background_battery_info_link": "Taispeáin dom conas", - "backup_controller_page_background_battery_info_message": "Chun an taithí cúltaca cúlra is fearr a fháil, díchumasaigh aon uasmhéaduithe ceallraí a chuireann srian ar ghníomhaíocht chúlra do Immich.\n\nÓs rud é go bhfuil sé seo sainiúil don ghléas, féach ar an bhfaisnéis riachtanach le haghaidh monaróir do ghléis.", - "backup_controller_page_background_battery_info_ok": "Ceart go leor", - "backup_controller_page_background_battery_info_title": "Optamúcháin ceallraí", - "backup_controller_page_background_charging": "Agus é á mhuirearú amháin", - "backup_controller_page_background_configure_error": "Theip ar chumrú na seirbhíse cúlra", - "backup_controller_page_background_delay": "Moill ar chúltaca sócmhainní nua: {duration}", - "backup_controller_page_background_description": "Cas ar an tseirbhís chúlra chun cúltaca uathoibríoch d’aon sócmhainní nua a dhéanamh gan an aip a oscailt", - "backup_controller_page_background_is_off": "Tá cúltaca uathoibríoch sa chúlra múchta", - "backup_controller_page_background_is_on": "Tá cúltaca uathoibríoch sa chúlra ar siúl", - "backup_controller_page_background_turn_off": "Múch an tseirbhís chúlra", - "backup_controller_page_background_turn_on": "Cuir seirbhís chúlra ar siúl", - "backup_controller_page_background_wifi": "Ar Wi-Fi amháin", - "backup_controller_page_backup": "Cúltaca", - "backup_controller_page_backup_selected": "Roghnaithe: ", - "backup_controller_page_backup_sub": "Grianghraif agus físeáin cúltaca", - "backup_controller_page_created": "Cruthaithe ar: {date}", - "backup_controller_page_desc_backup": "Cas ar chúltaca sa tulra chun sócmhainní nua a uaslódáil go huathoibríoch chuig an bhfreastalaí nuair a osclaítear an aip.", - "backup_controller_page_excluded": "Eisiata: ", - "backup_controller_page_failed": "Theip air ({count})", - "backup_controller_page_filename": "Ainm comhaid: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Faisnéis Chúltaca", - "backup_controller_page_none_selected": "Níor roghnaíodh aon cheann", - "backup_controller_page_remainder": "Fuílleach", - "backup_controller_page_remainder_sub": "Grianghraif agus físeáin atá fágtha le cúltaca ón rogha", - "backup_controller_page_server_storage": "Stóráil Freastalaí", - "backup_controller_page_start_backup": "Tosaigh Cúltaca", - "backup_controller_page_status_off": "Tá cúltaca uathoibríoch sa tulra múchta", - "backup_controller_page_status_on": "Tá cúltaca uathoibríoch sa tulra ar siúl", - "backup_controller_page_storage_format": "{used} de {total} úsáidte", - "backup_controller_page_to_backup": "Albaim le cúltaca", - "backup_controller_page_total_sub": "Gach grianghraf agus físeán uathúil ó albaim roghnaithe", - "backup_controller_page_turn_off": "Múch cúltaca sa tulra", - "backup_controller_page_turn_on": "Cuir cúltaca sa tulra ar siúl", - "backup_controller_page_uploading_file_info": "Eolas comhaid á uaslódáil", - "backup_err_only_album": "Ní féidir an t-aon albam a bhaint", - "backup_error_sync_failed": "Theip ar an sioncrónú. Ní féidir an cúltaca a phróiseáil.", - "backup_info_card_assets": "sócmhainní", - "backup_manual_cancelled": "Cealaithe", - "backup_manual_in_progress": "Uaslódáil ar siúl cheana féin. Bain triail as ar ball", - "backup_manual_success": "Rath", - "backup_manual_title": "Stádas uaslódála", - "backup_options": "Roghanna Cúltaca", - "backup_options_page_title": "Roghanna cúltaca", - "backup_setting_subtitle": "Bainistigh socruithe uaslódála cúlra agus tulra", - "backup_settings_subtitle": "Bainistigh socruithe uaslódála", - "backup_upload_details_page_more_details": "Tapáil le haghaidh tuilleadh sonraí", - "backward": "Ar gcúl", - "biometric_auth_enabled": "Cumasaíodh fíordheimhniú bithmhéadrach", - "biometric_locked_out": "Albaim an chulta", - "biometric_no_options": "Níl aon roghanna bithmhéadracha ar fáil", - "biometric_not_available": "Níl fíordheimhniú bithmhéadrach ar fáil ar an ngléas seo", - "birthdate_saved": "Dáta breithe sábháilte go rathúil", - "birthdate_set_description": "Úsáidtear dáta breithe chun aois an duine seo tráth a ngrianghraf a ríomh.", - "blurred_background": "Cúlra doiléir", - "bugs_and_feature_requests": "Fabhtanna & Iarratais ar Ghnéithe", - "build": "Tógáil", - "build_image": "Tóg Íomhá", - "bulk_delete_duplicates_confirmation": "An bhfuil tú cinnte gur mian leat {count, plural, one {# sócmhainn dhúblach} other {# sócmhainní dúblacha}} a scriosadh go mórchóir? Coinneoidh sé seo an tsócmhainn is mó de gach grúpa agus scriosfaidh sé go buan na dúblaigh eile go léir. Ní féidir leat an gníomh seo a chealú!", - "bulk_keep_duplicates_confirmation": "An bhfuil tú cinnte gur mian leat {count, plural, one {# duplicate asset} other {# duplicate assets}} a choinneáil? Réiteoidh sé seo gach grúpa dúblach gan aon rud a scriosadh.", - "bulk_trash_duplicates_confirmation": "An bhfuil tú cinnte gur mian leat {count, plural, one {# duplicate asset} other {# duplicate assets}} a chur sa bhruscar? Coinneoidh sé seo an tsócmhainn is mó de gach grúpa agus cuirfidh sé na dúblaigh eile go léir sa bhruscar.", - "buy": "Ceannachán Immich", - "cache_settings_clear_cache_button": "Glan an taisce", - "cache_settings_clear_cache_button_title": "Glanann sé seo taisce an aip. Beidh tionchar suntasach aige seo ar fheidhmíocht an aip go dtí go mbeidh an taisce atógtha.", - "cache_settings_duplicated_assets_clear_button": "GLAN", - "cache_settings_duplicated_assets_subtitle": "Grianghraif agus físeáin a ndéantar neamhaird orthu san aip", - "cache_settings_duplicated_assets_title": "Sócmhainní Dúblaithe ({count})", - "cache_settings_statistics_album": "Mionsamhlacha leabharlainne", - "cache_settings_statistics_full": "Íomhánna iomlána", - "cache_settings_statistics_shared": "Mionsamhlacha albam comhroinnte", - "cache_settings_statistics_thumbnail": "Mionsamhlacha", - "cache_settings_statistics_title": "Úsáid taisce", - "cache_settings_subtitle": "Rialú iompar taisceála an fheidhmchláir shoghluaiste Immich", - "cache_settings_tile_subtitle": "Rialú an iompair stórála áitiúil", - "cache_settings_tile_title": "Stóráil Áitiúil", - "cache_settings_title": "Socruithe Taisceála", - "camera": "Ceamara", - "camera_brand": "Branda ceamara", - "camera_model": "Múnla ceamara", - "cancel": "Cealaigh", - "cancel_search": "Cealaigh an cuardach", - "canceled": "Cealaithe", - "canceling": "Ag cealú", - "cannot_merge_people": "Ní féidir daoine a chumasc", - "cannot_undo_this_action": "Ní féidir leat an gníomh seo a chealú!", - "cannot_update_the_description": "Ní féidir an cur síos a nuashonrú", - "cast": "Caith", - "cast_description": "Cumraigh na cinn scríbe teilgthe atá ar fáil", - "change_date": "Athraigh dáta", - "change_description": "Athraigh cur síos", - "change_display_order": "Athraigh ord taispeána", - "change_expiration_time": "Athraigh an t-am éaga", - "change_location": "Athraigh suíomh", - "change_name": "Athraigh ainm", - "change_name_successfully": "Athraíodh an t-ainm go rathúil", - "change_password": "Athraigh Pasfhocal", - "change_password_description": "Seo an chéad uair duit síniú isteach sa chóras nó rinneadh iarratas chun do phasfhocal a athrú. Cuir isteach an pasfhocal nua thíos le do thoil.", - "change_password_form_confirm_password": "Deimhnigh Pasfhocal", - "change_password_form_description": "Haigh, {name},\n\nIs í seo an chéad uair duit síniú isteach sa chóras nó rinneadh iarratas chun do phasfhocal a athrú. Cuir isteach an pasfhocal nua thíos le do thoil.", - "change_password_form_log_out": "Logáil amach as gach gléas eile", - "change_password_form_log_out_description": "Moltar logáil amach as gach gléas eile", - "change_password_form_new_password": "Pasfhocal Nua", - "change_password_form_password_mismatch": "Ní hionann na pasfhocail", - "change_password_form_reenter_new_password": "Ath-iontráil Pasfhocal Nua", - "change_pin_code": "Athraigh an cód PIN", - "change_trigger": "Athraigh an spreagadh", - "change_trigger_prompt": "An bhfuil tú cinnte gur mian leat an spreagthóir a athrú? Bainfear gach gníomh agus scagaire atá ann cheana leis seo.", - "change_your_password": "Athraigh do phasfhocal", - "changed_visibility_successfully": "Athraíodh an infheictheacht go rathúil", - "charging": "Muirearú", - "charging_requirement_mobile_backup": "Éilíonn cúltaca cúlra go bhfuil an gléas á mhuirearú", - "check_corrupt_asset_backup": "Seiceáil le haghaidh cúltacaí sócmhainní truaillithe", - "check_corrupt_asset_backup_button": "Déan seiceáil", - "check_corrupt_asset_backup_description": "Ná déan an seiceáil seo ach amháin thar Wi-Fi agus nuair a bheidh cúltaca déanta de na sócmhainní go léir. D’fhéadfadh sé go dtógfadh an nós imeachta cúpla nóiméad.", - "check_logs": "Seiceáil Logaí", - "checksum": "Suim sheiceála", - "choose_matching_people_to_merge": "Roghnaigh daoine comhoiriúnacha le cumasc", - "city": "Cathair", - "cleanup_confirm_description": "Fuair Immich {count} sócmhainní (cruthaithe roimh {date}) cúltaca sábháilte chuig an bhfreastalaí. Bain na cóipeanna áitiúla den ghléas seo?", - "cleanup_confirm_prompt_title": "Bain den ghléas seo?", - "cleanup_deleted_assets": "Bogadh {count} sócmhainní chuig bruscar an ghléis", - "cleanup_deleting": "Ag bogadh go dtí an bruscar...", - "cleanup_found_assets": "Fuarthas {count} sócmhainní cúltaca", - "cleanup_found_assets_with_size": "Fuarthas {count} sócmhainní cúltaca ({size})", - "cleanup_icloud_shared_albums_excluded": "Níl Albaim Chomhroinnte iCloud san áireamh sa scanadh", - "cleanup_no_assets_found": "Ní bhfuarthas aon sócmhainní a chomhlíonann na critéir thuas. Ní féidir le Spás Saor a Bhaint ach sócmhainní a bhaint atá cúltaca déanta díobh chuig an bhfreastalaí", - "cleanup_preview_title": "Sócmhainní le baint ({count})", - "cleanup_step3_description": "Scanáil le haghaidh sócmhainní cúltaca a mheaitseálann do dháta agus coinnigh socruithe.", - "cleanup_step4_summary": "{count} sócmhainní (cruthaithe roimh {date}) le baint de do ghléas áitiúil. Beidh rochtain ar ghrianghraif ón aip Immich i gcónaí.", - "cleanup_trash_hint": "Chun spás stórála a athghabháil go hiomlán, oscail aip gailearaí an chórais agus folmhaigh an bruscar", - "clear": "Glan", - "clear_all": "Glan gach rud", - "clear_all_recent_searches": "Glan gach cuardach le déanaí", - "clear_file_cache": "Glan an Taisce Comhad", - "clear_message": "Teachtaireacht shoiléir", - "clear_value": "Glan luach", - "client_cert_dialog_msg_confirm": "Ceart go leor", - "client_cert_enter_password": "Cuir isteach Pasfhocal", - "client_cert_import": "Iompórtáil", - "client_cert_import_success_msg": "Tá deimhniú cliant allmhairithe", - "client_cert_invalid_msg": "Comhad teastais neamhbhailí nó pasfhocal mícheart", - "client_cert_remove_msg": "Baineadh teastas an chliaint", - "client_cert_subtitle": "Tacaíonn sé le formáid PKCS12 (.p12, .pfx) amháin. Ní féidir teastais a allmhairiú/a bhaint ach amháin roimh logáil isteach", - "client_cert_title": "Teastas cliant SSL [TURGHAINNEACH]", - "clockwise": "Deiseal", - "close": "Dún", - "collapse": "Laghdaigh", - "collapse_all": "Laghdaigh gach rud", - "color": "Dath", - "color_theme": "Téama datha", - "command": "Ordú", - "comment_deleted": "Trácht scriosta", - "comment_options": "Roghanna tráchta", - "comments_and_likes": "Tráchtanna & Is maith liom", - "comments_are_disabled": "Tá tuairimí díchumasaithe", - "common_create_new_album": "Cruthaigh albam nua", - "completed": "Críochnaithe", - "confirm": "Deimhnigh", - "confirm_admin_password": "Deimhnigh Pasfhocal an Riarthóra", - "confirm_delete_face": "An bhfuil tú cinnte gur mian leat aghaidh {name} a scriosadh ón tsócmhainn?", - "confirm_delete_shared_link": "An bhfuil tú cinnte gur mian leat an nasc comhroinnte seo a scriosadh?", - "confirm_keep_this_delete_others": "Scriosfar gach sócmhainn eile sa chairn seachas an tsócmhainn seo. An bhfuil tú cinnte gur mian leat leanúint ar aghaidh?", - "confirm_new_pin_code": "Deimhnigh an cód PIN nua", - "confirm_password": "Deimhnigh an focal faire", - "confirm_tag_face": "Ar mhaith leat an aghaidh seo a chlibeáil mar {name}?", - "confirm_tag_face_unnamed": "Ar mhaith leat clib a chur ar an aghaidh seo?", - "connected_device": "Gléas ceangailte", - "connected_to": "Ceangailte le", - "contain": "Coinnigh", - "context": "Comhthéacs", - "continue": "Lean ar aghaidh", - "control_bottom_app_bar_create_new_album": "Cruthaigh albam nua", - "control_bottom_app_bar_delete_from_immich": "Scrios ó Immich", - "control_bottom_app_bar_delete_from_local": "Scrios ón ngléas", - "control_bottom_app_bar_edit_location": "Cuir Suíomh in Eagar", - "control_bottom_app_bar_edit_time": "Cuir Dáta & Am in Eagar", - "control_bottom_app_bar_share_link": "Comhroinn an Nasc", - "control_bottom_app_bar_share_to": "Comhroinn Le", - "control_bottom_app_bar_trash_from_immich": "Bog go dtí an Bruscar", - "copied_image_to_clipboard": "Íomhá cóipeáilte chuig an ghearrthaisce.", - "copied_to_clipboard": "Cóipeáilte chuig an ghearrthaisce!", - "copy_error": "Earráid chóipeála", - "copy_file_path": "Cóipeáil cosán comhaid", - "copy_image": "Cóipeáil Íomhá", - "copy_link": "Cóipeáil nasc", - "copy_link_to_clipboard": "Cóipeáil nasc chuig an ghearrthaisce", - "copy_password": "Cóipeáil an focal faire", - "copy_to_clipboard": "Cóipeáil chuig an nGearrthaisce", - "country": "Tír", - "cover": "Clúdach", - "covers": "Clúdaigh", - "create": "Cruthaigh", - "create_album": "Cruthaigh albam", - "create_album_page_untitled": "Gan Teideal", - "create_api_key": "Cruthaigh eochair API", - "create_first_workflow": "Cruthaigh an chéad sreabhadh oibre", - "create_library": "Cruthaigh Leabharlann", - "create_link": "Cruthaigh nasc", - "create_link_to_share": "Cruthaigh nasc le roinnt", - "create_link_to_share_description": "Lig do dhuine ar bith a bhfuil an nasc aige/aici an/na grianghraf/na grianghraif roghnaithe a fheiceáil", - "create_new": "CRUTHAIGH NUA", - "create_new_person": "Cruthaigh duine nua", - "create_new_person_hint": "Sannadh sócmhainní roghnaithe do dhuine nua", - "create_new_user": "Cruthaigh úsáideoir nua", - "create_shared_album_page_share_add_assets": "CUIR SÓCMHAINNÍ LEIS", - "create_shared_album_page_share_select_photos": "Roghnaigh Grianghraif", - "create_shared_link": "Cruthaigh nasc comhroinnte", - "create_tag": "Cruthaigh clib", - "create_tag_description": "Cruthaigh clib nua. I gcás clibeanna neadaithe, cuir isteach cosán iomlán an chlib, lena n-áirítear slaiseanna ar aghaidh.", - "create_user": "Cruthaigh úsáideoir", - "create_workflow": "Cruthaigh sreabhadh oibre", - "created": "Cruthaithe", - "created_at": "Cruthaithe", - "creating_linked_albums": "Ag cruthú albaim nasctha...", - "crop": "Barr", - "crop_aspect_ratio_fixed": "Seasta", - "crop_aspect_ratio_free": "Saor in aisce", - "crop_aspect_ratio_original": "Bunaidh", - "curated_object_page_title": "Rudaí", - "current_device": "Gléas reatha", - "current_pin_code": "Cód PIN reatha", - "current_server_address": "Seoladh reatha an fhreastalaí", - "custom_date": "Dáta saincheaptha", - "custom_locale": "Logán Saincheaptha", - "custom_locale_description": "Formáidigh dátaí agus uimhreacha bunaithe ar an teanga agus ar an réigiún", - "custom_url": "URL Saincheaptha", - "cutoff_date_description": "Coinnigh grianghraif ón uair dheireanach…", - "cutoff_day": "{count, plural, one {lá} other {laethanta}}", - "cutoff_year": "{count, plural, one {bliain} other {blianta}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Dorcha", - "dark_theme": "Scoránaigh an téama dorcha", - "date": "Dáta", - "date_after": "Dáta i ndiaidh", - "date_and_time": "Dáta agus Am", - "date_before": "Dáta roimh", - "date_format": "E, d LLL, y • h:mm a", - "date_of_birth_saved": "Dáta breithe sábháilte go rathúil", - "date_range": "Raon dáta", - "day": "Lá", - "days": "Laethanta", - "deduplicate_all": "Dídhúblaigh Gach Rud", - "deduplication_criteria_1": "Méid na híomhá i mbéiteanna", - "deduplication_criteria_2": "Líon sonraí EXIF", - "deduplication_info": "Eolas Dídhúblála", - "deduplication_info_description": "Chun sócmhainní a réamhroghnú go huathoibríoch agus dúblaigh a bhaint i mórchóir, féachaimid ar:", - "default_locale": "Logán Réamhshocraithe", - "default_locale_description": "Formáidigh dátaí agus uimhreacha bunaithe ar shuíomh do bhrabhsálaí", - "delete": "Scrios", - "delete_action_confirmation_message": "An bhfuil tú cinnte gur mian leat an tsócmhainn seo a scriosadh? Bogfaidh an gníomh seo an tsócmhainn go dtí bruscar an fhreastalaí agus fiafróidh sé díot an mian leat í a scriosadh go háitiúil", - "delete_action_prompt": "{count} scriosta", - "delete_album": "Scrios albam", - "delete_api_key_prompt": "An bhfuil tú cinnte gur mian leat an eochair API seo a scriosadh?", - "delete_dialog_alert": "Scriosfar na míreanna seo go buan ó Immich agus ó do ghléas", - "delete_dialog_alert_local": "Bainfear na míreanna seo go buan de do ghléas ach beidh siad fós ar fáil ar fhreastalaí Immich", - "delete_dialog_alert_local_non_backed_up": "Níl cuid de na míreanna cúltaca déanta chuig Immich agus bainfear iad go buan de do ghléas", - "delete_dialog_alert_remote": "Scriosfar na míreanna seo go buan ón bhfreastalaí Immich", - "delete_dialog_ok_force": "Scrios Ar Aon Slí", - "delete_dialog_title": "Scrios go Buan", - "delete_duplicates_confirmation": "An bhfuil tú cinnte gur mian leat na dúblaigh seo a scriosadh go buan?", - "delete_face": "Scrios aghaidh", - "delete_key": "Eochair scriosta", - "delete_library": "Scrios Leabharlann", - "delete_link": "Scrios nasc", - "delete_local_action_prompt": "{count} scriosta go háitiúil", - "delete_local_dialog_ok_backed_up_only": "Scrios Cúltaca Amháin", - "delete_local_dialog_ok_force": "Scrios Ar Aon Slí", - "delete_others": "Scrios cinn eile", - "delete_permanently": "Scrios go buan", - "delete_permanently_action_prompt": "{count} scriosta go buan", - "delete_shared_link": "Scrios nasc comhroinnte", - "delete_shared_link_dialog_title": "Scrios Nasc Comhroinnte", - "delete_tag": "Scrios an chlib", - "delete_tag_confirmation_prompt": "An bhfuil tú cinnte gur mian leat an clib {tagName} a scriosadh?", - "delete_user": "Scrios úsáideoir", - "deleted_shared_link": "Nasc comhroinnte scriosta", - "deletes_missing_assets": "Scriosann sé sócmhainní atá ar iarraidh ón diosca", - "description": "Cur síos", - "description_input_hint_text": "Cuir cur síos leis...", - "description_input_submit_error": "Earráid ag nuashonrú an tuairisce, féach ar an log le haghaidh tuilleadh sonraí", - "deselect_all": "Díroghnaigh Gach Rud", - "details": "Sonraí", - "direction": "Treo", - "disable": "Díchumasaigh", - "disabled": "Míchumasaithe", - "disallow_edits": "Dícheadaigh eagarthóireachtaí", - "discord": "Discord", - "discover": "Faigh amach", - "discovered_devices": "Gléasanna a aimsíodh", - "dismiss_all_errors": "Ruaig gach earráid", - "dismiss_error": "Díbhe earráid", - "display_options": "Roghanna taispeána", - "display_order": "Ord taispeána", - "display_original_photos": "Taispeáin grianghraif bhunaidh", - "display_original_photos_setting_description": "Is fearr an grianghraf bunaidh a thaispeáint agus sócmhainn á breathnú seachas mionsamhlacha nuair atá an tsócmhainn bhunaidh comhoiriúnach leis an ngréasán. D’fhéadfadh sé seo luasanna taispeána grianghraf a mhoilliú.", - "do_not_show_again": "Ná taispeáin an teachtaireacht seo arís", - "documentation": "Doiciméadú", - "done": "Déanta", - "download": "Íoslódáil", - "download_action_prompt": "Ag íoslódáil {count} sócmhainní", - "download_canceled": "Íoslódáil curtha ar ceal", - "download_complete": "Íoslódáil críochnaithe", - "download_enqueue": "Íoslódáil curtha i scuaine", - "download_error": "Earráid Íoslódála", - "download_failed": "Theip ar an íoslódáil", - "download_finished": "Íoslódáil críochnaithe", - "download_include_embedded_motion_videos": "Físeáin leabaithe", - "download_include_embedded_motion_videos_description": "Cuir físeáin atá leabaithe i ngrianghraif ghluaiste san áireamh mar chomhad ar leithligh", - "download_notfound": "Íoslódáil gan aimsiú", - "download_original": "Íoslódáil an bunleagan", - "download_paused": "Íoslódáil curtha ar sos", - "download_settings": "Íoslódáil", - "download_settings_description": "Bainistigh socruithe a bhaineann le híoslódáil sócmhainní", - "download_started": "Íoslódáil tosaithe", - "download_sucess": "Rath ar an íoslódáil", - "download_sucess_android": "Tá na meáin íoslódáilte chuig DCIM/Immich", - "download_waiting_to_retry": "Ag fanacht le hathiarracht", - "downloading": "Ag íoslódáil", - "downloading_asset_filename": "Ag íoslódáil sócmhainn {filename}", - "downloading_from_icloud": "Ag íoslódáil ó iCloud", - "downloading_media": "Ag íoslódáil na meán", - "drop_files_to_upload": "Scaoil comhaid áit ar bith le huaslódáil", - "duplicates": "Dúblaigh", - "duplicates_description": "Réitigh gach grúpa trína léiriú cé acu de na dúblaigh, más ann dóibh", - "duration": "Fad", - "edit": "Cuir in Eagar", - "edit_album": "Cuir albam in eagar", - "edit_avatar": "Cuir an t-avatár in eagar", - "edit_birthday": "Cuir breithlá in eagar", - "edit_date": "Cuir an dáta in eagar", - "edit_date_and_time": "Cuir an dáta agus an t-am in eagar", - "edit_date_and_time_action_prompt": "dáta agus am {count} curtha in eagar", - "edit_date_and_time_by_offset": "Athraigh an dáta de réir fritháireamh", - "edit_date_and_time_by_offset_interval": "Raon dáta nua: {from} - {to}", - "edit_description": "Cuir cur síos in eagar", - "edit_description_prompt": "Roghnaigh cur síos nua le do thoil:", - "edit_exclusion_pattern": "Cuir patrún eisiaimh in eagar", - "edit_faces": "Cuir aghaidheanna in eagar", - "edit_key": "Eochair eagarthóireachta", - "edit_link": "Cuir nasc in eagar", - "edit_location": "Cuir suíomh in eagar", - "edit_location_action_prompt": "suíomh {count} curtha in eagar", - "edit_location_dialog_title": "Suíomh", - "edit_name": "Cuir ainm in eagar", - "edit_people": "Cuir daoine in eagar", - "edit_tag": "Cuir an clib in eagar", - "edit_title": "Cuir Teideal in Eagar", - "edit_user": "Cuir úsáideoir in eagar", - "edit_workflow": "Sreabhadh oibre a chur in eagar", - "editor": "Eagarthóir", - "editor_close_without_save_prompt": "Ní shábhálfar na hathruithe", - "editor_close_without_save_title": "Dún an t-eagarthóir?", - "editor_confirm_reset_all_changes": "An bhfuil tú cinnte gur mian leat na hathruithe go léir a athshocrú?", - "editor_flip_horizontal": "Fillte go cothrománach", - "editor_flip_vertical": "Smeach ingearach", - "editor_orientation": "Treoshuíomh", - "editor_reset_all_changes": "Athshocraigh athruithe", - "editor_rotate_left": "Rothlaigh 90° tuathalach", - "editor_rotate_right": "Rothlaigh 90° deiseal", - "email": "Ríomhphost", - "email_notifications": "Fógraí ríomhphoist", - "empty_folder": "Tá an fillteán seo folamh", - "empty_trash": "Folmhaigh an bruscar", - "empty_trash_confirmation": "An bhfuil tú cinnte gur mian leat an bruscar a fholmhú? Bainfidh sé seo na sócmhainní go léir sa bhruscar go buan as Immich.\nNí féidir leat an gníomh seo a chealú!", - "enable": "Cumasaigh", - "enable_backup": "Cumasaigh Cúltaca", - "enable_biometric_auth_description": "Cuir isteach do chód PIN chun fíordheimhniú bithmhéadrach a chumasú", - "enabled": "Cumasaithe", - "end_date": "Dáta deiridh", - "enqueued": "Sa scuaine", - "enter_wifi_name": "Cuir isteach ainm an Wi-Fi", - "enter_your_pin_code": "Cuir isteach do chód PIN", - "enter_your_pin_code_subtitle": "Cuir isteach do chód PIN chun rochtain a fháil ar an bhfillteán faoi ghlas", - "error": "Earráid", - "error_change_sort_album": "Theip ar ord sórtála an albaim a athrú", - "error_delete_face": "Earráid ag scriosadh aghaidhe ón tsócmhainn", - "error_getting_places": "Earráid ag fáil áiteanna", - "error_loading_albums": "Earráid ag luchtú albaim", - "error_loading_image": "Earráid ag luchtú íomhá", - "error_loading_partners": "Earráid ag luchtú comhpháirtithe: {error}", - "error_retrieving_asset_information": "Earráid ag aisghabháil faisnéise sócmhainne", - "error_saving_image": "Earráid: {error}", - "error_tag_face_bounding_box": "Earráid ag clibeáil aghaidhe - ní féidir comhordanáidí bosca teorann a fháil", - "error_title": "Earráid - Chuaigh rud éigin mícheart", - "error_while_navigating": "Earráid agus nascleanúint á déanamh chuig an tsócmhainn", - "errors": { - "cannot_navigate_next_asset": "Ní féidir nascleanúint a dhéanamh chuig an gcéad tsócmhainn eile", - "cannot_navigate_previous_asset": "Ní féidir nascleanúint a dhéanamh chuig an tsócmhainn roimhe seo", - "cant_apply_changes": "Ní féidir athruithe a chur i bhfeidhm", - "cant_change_activity": "Ní féidir {enabled, select, true {díchumasaigh} other {cumasaigh}} gníomhaíocht", - "cant_change_asset_favorite": "Ní féidir an rogha is fearr leat a athrú don tsócmhainn", - "cant_change_metadata_assets_count": "Ní féidir meiteashonraí {count, plural, one {# sócmhainn} other {# sócmhainní}} a athrú", - "cant_get_faces": "Ní féidir aghaidheanna a fháil", - "cant_get_number_of_comments": "Ní féidir líon na dtuairimí a fháil", - "cant_search_people": "Ní féidir daoine a chuardach", - "cant_search_places": "Ní féidir áiteanna a chuardach", - "error_adding_assets_to_album": "Earráid ag cur sócmhainní leis an albam", - "error_adding_users_to_album": "Earráid ag cur úsáideoirí leis an albam", - "error_deleting_shared_user": "Earráid ag scriosadh úsáideora comhroinnte", - "error_downloading": "Earráid ag íoslódáil {filename}", - "error_hiding_buy_button": "Earráid i bhfolach an cnaipe ceannaigh", - "error_removing_assets_from_album": "Earráid ag baint sócmhainní ón albam, seiceáil an consól le haghaidh tuilleadh sonraí", - "error_selecting_all_assets": "Earráid ag roghnú na sócmhainní go léir", - "exclusion_pattern_already_exists": "Tá an patrún eisiaimh seo ann cheana féin.", - "failed_to_create_album": "Theip ar albam a chruthú", - "failed_to_create_shared_link": "Theip ar nasc comhroinnte a chruthú", - "failed_to_edit_shared_link": "Theip ar an nasc comhroinnte a chur in eagar", - "failed_to_get_people": "Theip ar dhaoine a fháil", - "failed_to_keep_this_delete_others": "Theip ar an tsócmhainn seo a choinneáil agus na sócmhainní eile a scriosadh", - "failed_to_load_asset": "Theip ar an tsócmhainn a lódáil", - "failed_to_load_assets": "Theip ar shócmhainní a lódáil", - "failed_to_load_notifications": "Theip ar fhógraí a lódáil", - "failed_to_load_people": "Theip ar dhaoine a lódáil", - "failed_to_remove_product_key": "Theip ar eochair an táirge a bhaint", - "failed_to_reset_pin_code": "Theip ar an gcód PIN a athshocrú", - "failed_to_stack_assets": "Theip ar shócmhainní a chruachadh", - "failed_to_unstack_assets": "Theip ar shócmhainní a dhíchruachadh", - "failed_to_update_notification_status": "Theip ar stádas an fhógra a nuashonrú", - "incorrect_email_or_password": "Ríomhphost nó pasfhocal mícheart", - "library_folder_already_exists": "Tá an cosán allmhairithe seo ann cheana féin.", - "paths_validation_failed": "Theip ar bhailíochtú {paths, plural, one {# cosán} other {# cosáin}}", - "profile_picture_transparent_pixels": "Ní féidir picteilíní trédhearcacha a bheith i bpictiúir phróifíle. Zúmáil isteach agus/nó bog an íomhá le do thoil.", - "quota_higher_than_disk_size": "Shocraigh tú cuóta níos airde ná méid an diosca", - "something_went_wrong": "Chuaigh rud éigin mícheart", - "unable_to_add_album_users": "Ní féidir úsáideoirí a chur leis an albam", - "unable_to_add_assets_to_shared_link": "Ní féidir sócmhainní a chur leis an nasc comhroinnte", - "unable_to_add_comment": "Ní féidir trácht a chur leis", - "unable_to_add_exclusion_pattern": "Ní féidir patrún eisiaimh a chur leis", - "unable_to_add_partners": "Ní féidir comhpháirtithe a chur leis", - "unable_to_add_remove_archive": "Ní féidir {archived, select, true {bain sócmhainn as} other {add asset to}} cartlannú a dhéanamh", - "unable_to_add_remove_favorites": "Ní féidir {favorite, select, true {add sócmhainn le} other {remove sócmhainn ó}} rogha eile", - "unable_to_archive_unarchive": "Ní féidir {archived, select, true {archive} other {unarchive}} a chur ar an gcartlann", - "unable_to_change_album_user_role": "Ní féidir ról úsáideora an albaim a athrú", - "unable_to_change_date": "Ní féidir an dáta a athrú", - "unable_to_change_description": "Ní féidir an cur síos a athrú", - "unable_to_change_favorite": "Ní féidir an rogha is fearr leat a athrú don tsócmhainn", - "unable_to_change_location": "Ní féidir an suíomh a athrú", - "unable_to_change_password": "Ní féidir an focal faire a athrú", - "unable_to_change_visibility": "Ní féidir an infheictheacht a athrú do {count, plural, one {# duine} other {# duine}}", - "unable_to_complete_oauth_login": "Ní féidir logáil isteach OAuth a chríochnú", - "unable_to_connect": "Ní féidir ceangal", - "unable_to_copy_to_clipboard": "Ní féidir cóip a dhéanamh chuig an ghearrthaisce, déan cinnte go bhfuil tú ag rochtain an leathanaigh trí https", - "unable_to_create": "Ní féidir sreabhadh oibre a chruthú", - "unable_to_create_admin_account": "Ní féidir cuntas riarthóra a chruthú", - "unable_to_create_api_key": "Ní féidir eochair API nua a chruthú", - "unable_to_create_library": "Ní féidir leabharlann a chruthú", - "unable_to_create_user": "Ní féidir úsáideoir a chruthú", - "unable_to_delete_album": "Ní féidir albam a scriosadh", - "unable_to_delete_asset": "Ní féidir an tsócmhainn a scriosadh", - "unable_to_delete_assets": "Earráid ag scriosadh sócmhainní", - "unable_to_delete_exclusion_pattern": "Ní féidir patrún eisiaimh a scriosadh", - "unable_to_delete_shared_link": "Ní féidir nasc comhroinnte a scriosadh", - "unable_to_delete_user": "Ní féidir an t-úsáideoir a scriosadh", - "unable_to_delete_workflow": "Ní féidir an sreabhadh oibre a scriosadh", - "unable_to_download_files": "Ní féidir comhaid a íoslódáil", - "unable_to_edit_exclusion_pattern": "Ní féidir patrún eisiaimh a chur in eagar", - "unable_to_empty_trash": "Ní féidir an bruscar a fholmhú", - "unable_to_enter_fullscreen": "Ní féidir dul isteach sa lánscáileán", - "unable_to_exit_fullscreen": "Ní féidir an scáileán iomlán a fhágáil", - "unable_to_get_comments_number": "Ní féidir líon na dtuairimí a fháil", - "unable_to_get_shared_link": "Theip ar an nasc comhroinnte a fháil", - "unable_to_hide_person": "Ní féidir an duine a cheilt", - "unable_to_link_motion_video": "Ní féidir físeán gluaisne a nascadh", - "unable_to_link_oauth_account": "Ní féidir cuntas OAuth a nascadh", - "unable_to_log_out_all_devices": "Ní féidir logáil amach as gach gléas", - "unable_to_log_out_device": "Ní féidir logáil amach as an ngléas", - "unable_to_login_with_oauth": "Ní féidir logáil isteach le OAuth", - "unable_to_play_video": "Ní féidir físeán a sheinm", - "unable_to_reassign_assets_existing_person": "Ní féidir sócmhainní a athshannadh chuig {name, select, null {an existing person} other {{name}}}", - "unable_to_reassign_assets_new_person": "Ní féidir sócmhainní a athshannadh do dhuine nua", - "unable_to_refresh_user": "Ní féidir an t-úsáideoir a athnuachan", - "unable_to_remove_album_users": "Ní féidir úsáideoirí a bhaint den albam", - "unable_to_remove_api_key": "Ní féidir an eochair API a bhaint", - "unable_to_remove_assets_from_shared_link": "Ní féidir sócmhainní a bhaint as nasc comhroinnte", - "unable_to_remove_library": "Ní féidir an leabharlann a bhaint", - "unable_to_remove_partner": "Ní féidir an comhpháirtí a bhaint", - "unable_to_remove_reaction": "Ní féidir an imoibriú a bhaint", - "unable_to_reset_password": "Ní féidir an focal faire a athshocrú", - "unable_to_reset_pin_code": "Ní féidir an cód PIN a athshocrú", - "unable_to_resolve_duplicate": "Ní féidir dúblach a réiteach", - "unable_to_restore_assets": "Ní féidir sócmhainní a athchóiriú", - "unable_to_restore_trash": "Ní féidir an bruscar a athchóiriú", - "unable_to_restore_user": "Ní féidir an t-úsáideoir a athchóiriú", - "unable_to_save_album": "Ní féidir an t-albam a shábháil", - "unable_to_save_api_key": "Ní féidir an eochair API a shábháil", - "unable_to_save_date_of_birth": "Ní féidir an dáta breithe a shábháil", - "unable_to_save_name": "Ní féidir an t-ainm a shábháil", - "unable_to_save_profile": "Ní féidir an phróifíl a shábháil", - "unable_to_save_settings": "Ní féidir socruithe a shábháil", - "unable_to_scan_libraries": "Ní féidir leabharlanna a scanadh", - "unable_to_scan_library": "Ní féidir an leabharlann a scanadh", - "unable_to_set_feature_photo": "Ní féidir grianghraf gné a shocrú", - "unable_to_set_profile_picture": "Ní féidir pictiúr próifíle a shocrú", - "unable_to_set_rating": "Ní féidir rátáil a shocrú", - "unable_to_submit_job": "Ní féidir an post a chur isteach", - "unable_to_trash_asset": "Ní féidir an tsócmhainn a chur sa bhruscar", - "unable_to_unlink_account": "Ní féidir an cuntas a dhícheangal", - "unable_to_unlink_motion_video": "Ní féidir físeán gluaisne a dhícheangal", - "unable_to_update_album_cover": "Ní féidir clúdach an albaim a nuashonrú", - "unable_to_update_album_info": "Ní féidir faisnéis an albaim a nuashonrú", - "unable_to_update_library": "Ní féidir an leabharlann a nuashonrú", - "unable_to_update_location": "Ní féidir an suíomh a nuashonrú", - "unable_to_update_settings": "Ní féidir socruithe a nuashonrú", - "unable_to_update_timeline_display_status": "Ní féidir stádas taispeána an amlíne a nuashonrú", - "unable_to_update_user": "Ní féidir an t-úsáideoir a nuashonrú", - "unable_to_update_workflow": "Ní féidir an sreabhadh oibre a nuashonrú", - "unable_to_upload_file": "Ní féidir an comhad a uaslódáil" - }, - "errors_text": "Earráidí", - "exclusion_pattern": "Patrún eisiaimh", - "exif": "Exif", - "exif_bottom_sheet_description": "Cuir Cur Síos leis...", - "exif_bottom_sheet_description_error": "Earráid ag nuashonrú an tuairisce", - "exif_bottom_sheet_details": "SONRAÍ", - "exif_bottom_sheet_location": "SUÍOMH", - "exif_bottom_sheet_no_description": "Gan cur síos", - "exif_bottom_sheet_people": "DAOINE", - "exif_bottom_sheet_person_add_person": "Cuir ainm leis", - "exit_slideshow": "Scoir an Taispeántais Sleamhnán", - "expand_all": "Leathnaigh gach rud", - "experimental_settings_new_asset_list_subtitle": "Obair ar siúl", - "experimental_settings_new_asset_list_title": "Cumasaigh eangach grianghraf turgnamhach", - "experimental_settings_subtitle": "Bain úsáid as ar do phriacal féin!", - "experimental_settings_title": "Turgnamhach", - "expire_after": "Rachaidh in éag tar éis", - "expired": "Imithe in éag", - "expires_date": "Éagaíonn {date}", - "explore": "Taiscéal", - "explorer": "Taiscéalaí", - "export": "Easpórtáil", - "export_as_json": "Easpórtáil mar JSON", - "export_database": "Easpórtáil Bunachar Sonraí", - "export_database_description": "Easpórtáil an bunachar sonraí SQLite", - "extension": "Síneadh", - "external": "Seachtrach", - "external_libraries": "Leabharlanna Seachtracha", - "external_network": "Líonra seachtrach", - "external_network_sheet_info": "Nuair nach bhfuil sé ar an líonra Wi-Fi is fearr leis, ceanglóidh an aip leis an bhfreastalaí tríd an gcéad cheann de na URLanna thíos ar féidir leis teacht orthu, ag tosú ó bharr go bun", - "face_unassigned": "Gan sannadh", - "failed": "Theip air", - "failed_count": "Theip ar: {count}", - "failed_to_authenticate": "Theip ar fhíordheimhniú", - "failed_to_load_assets": "Theip ar shócmhainní a lódáil", - "failed_to_load_folder": "Theip ar an bhfillteán a luchtú", - "favorite": "Ceanán", - "favorite_action_prompt": "{count} curtha le Ceanáin", - "favorite_or_unfavorite_photo": "Grianghraf is fearr leat nó nach fearr leat", - "favorites": "Ceanáin", - "favorites_page_no_favorites": "Níor aimsíodh aon sócmhainní is fearr leat", - "feature_photo_updated": "Grianghraf gné nuashonraithe", - "features": "Gnéithe", - "features_in_development": "Gnéithe i bhForbairt", - "features_setting_description": "Bainistigh gnéithe an aip", - "file_name_or_extension": "Ainm comhaid nó síneadh", - "file_size": "Méid comhaid", - "filename": "Ainm comhaid", - "filetype": "Cineál comhaid", - "filter": "Scagaire", - "filter_description": "Coinníollacha chun na sócmhainní sprice a scagadh", - "filter_people": "Scag daoine", - "filter_places": "Scag áiteanna", - "filters": "Scagairí", - "find_them_fast": "Aimsigh iad go tapa de réir ainm le cuardach", - "first": "Ar dtús", - "fix_incorrect_match": "Deisigh cluiche mícheart", - "folder": "Fillteán", - "folder_not_found": "Níor aimsíodh fillteán", - "folders": "Fillteáin", - "folders_feature_description": "Ag brabhsáil an amharc fillteáin le haghaidh na ngrianghraf agus na bhfíseán ar an gcóras comhad", - "forgot_pin_code_question": "An ndearna tú dearmad ar do PIN?", - "forward": "Chun tosaigh", - "free_up_space": "Spás a Shaoradh", - "free_up_space_description": "Bog grianghraif agus físeáin chúltaca chuig bruscar do ghléis chun spás a shaoradh. Fanann do chóipeanna ar an bhfreastalaí slán.", - "free_up_space_settings_subtitle": "Saor stóráil gléis", - "full_path": "Cosán iomlán: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Lódálann an ghné seo acmhainní seachtracha ó Google chun go n-oibreoidh sí.", - "general": "Ginearálta", - "geolocation_instruction_location": "Cliceáil ar shócmhainn le comhordanáidí GPS chun a suíomh a úsáid, nó roghnaigh suíomh go díreach ón léarscáil", - "get_help": "Faigh Cabhair", - "get_people_error": "Earráid ag fáil daoine", - "get_wifiname_error": "Níorbh fhéidir ainm Wi-Fi a fháil. Cinntigh gur dheonaigh tú na ceadanna riachtanacha agus go bhfuil tú ceangailte le líonra Wi-Fi", - "getting_started": "Ag Tosú", - "go_back": "Téigh ar ais", - "go_to_folder": "Téigh go dtí an fillteán", - "go_to_search": "Téigh go dtí an cuardach", - "gps": "GPS", - "gps_missing": "Gan GPS", - "grant_permission": "Deonaigh cead", - "group_albums_by": "Albaim ghrúpa le...", - "group_country": "Grúpáil de réir tíre", - "group_no": "Gan grúpáil", - "group_owner": "Grúpáil de réir úinéara", - "group_places_by": "Grúpáil áiteanna de réir...", - "group_year": "Grúpa de réir bliana", - "haptic_feedback_switch": "Cumasaigh aiseolas haptic", - "haptic_feedback_title": "Aiseolas Haptic", - "has_quota": "Tá cuóta aige", - "hash_asset": "Sócmhainn hash", - "hashed_assets": "Sócmhainní hasháilte", - "hashing": "Hasháil", - "header_settings_add_header_tip": "Cuir ceanntásc leis", - "header_settings_field_validator_msg": "Ní féidir luach a fhágáil folamh", - "header_settings_header_name_input": "Ainm an cheanntásca", - "header_settings_header_value_input": "Luach ceanntásca", - "headers_settings_tile_title": "Ceanntásca seachfhreastalaí saincheaptha", - "height": "Airde", - "hi_user": "Haigh {name} ({email})", - "hide_all_people": "Folaigh gach duine", - "hide_gallery": "Folaigh gailearaí", - "hide_named_person": "Folaigh duine {name}", - "hide_password": "Folaigh an focal faire", - "hide_person": "Folaigh duine", - "hide_schema": "Folaigh an scéim", - "hide_text_recognition": "Folaigh aitheantas téacs", - "hide_unnamed_people": "Folaigh daoine gan ainm", - "home_page_add_to_album_conflicts": "Cuireadh sócmhainní {added} leis an albam {album}. Tá sócmhainní {failed} san albam cheana féin.", - "home_page_add_to_album_err_local": "Ní féidir sócmhainní áitiúla a chur le halbaim go fóill, ag scipeáil", - "home_page_add_to_album_success": "Cuireadh sócmhainní {added} leis an albam {album}.", - "home_page_album_err_partner": "Ní féidir sócmhainní comhpháirtíochta a chur le halbam go fóill, ag scipeáil", - "home_page_archive_err_local": "Ní féidir sócmhainní áitiúla a chartlannú go fóill, ag scipeáil", - "home_page_archive_err_partner": "Ní féidir sócmhainní comhpháirtíochta a chartlannú, ag scipeáil", - "home_page_building_timeline": "Ag tógáil an amlíne", - "home_page_delete_err_partner": "Ní féidir sócmhainní comhpháirtíochta a scriosadh, ag scipeáil", - "home_page_delete_remote_err_local": "Sócmhainní áitiúla i scriosadh roghnúcháin iargúlta, ag scipeáil", - "home_page_favorite_err_local": "Ní féidir sócmhainní áitiúla a chur i bhfabhar go fóill, ag scipeáil", - "home_page_favorite_err_partner": "Ní féidir sócmhainní comhpháirtíochta a chur i bhfabhar go fóill, ag scipeáil", - "home_page_first_time_notice": "Más é seo an chéad uair duit an aip a úsáid, déan cinnte albam cúltaca a roghnú ionas gur féidir leis an amlíne grianghraif agus físeáin a líonadh ann", - "home_page_locked_error_local": "Ní féidir sócmhainní áitiúla a bhogadh chuig fillteán faoi ghlas, ag scipeáil", - "home_page_locked_error_partner": "Ní féidir sócmhainní comhpháirtíochta a bhogadh chuig fillteán faoi ghlas, ag scipeáil", - "home_page_share_err_local": "Ní féidir sócmhainní áitiúla a roinnt tríd an nasc, ag scipeáil", - "home_page_upload_err_limit": "Ní féidir ach 30 sócmhainn ar a mhéad a uaslódáil ag an am céanna, ag scipeáil", - "host": "Óstach", - "hour": "Uair an chloig", - "hours": "Uaireanta", - "id": "ID", - "idle": "Díomhaoin", - "ignore_icloud_photos": "Déan neamhaird de ghrianghraif iCloud", - "ignore_icloud_photos_description": "Ní uaslódálfar grianghraif atá stóráilte ar iCloud chuig freastalaí Immich", - "image": "Íomhá", - "image_alt_text_date": "{isVideo, select, true {Físeán} other {Íomhá}} tógtha ar {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Íomhá}} tógtha le {person1} ar {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tógtha le {person1} agus {person2} ar {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Íomhá}} tógtha le {person1}, {person2}, agus {person3} ar {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tógtha le {person1}, {person2}, agus {additionalCount, number} daoine eile ar {date}", - "image_alt_text_date_place": "{isVideo, select, true {Físeán} other {Íomhá}} tógtha i {city}, {country} ar {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Físeán} other {Íomhá}} tógtha i {city}, {country} le {person1} ar {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Físeán} other {Íomhá}} tógtha i {city}, {country} le {person1} agus {person2} ar {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Íomhá}} tógtha i {city}, {country} le {person1}, {person2}, agus {person3} ar {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tógtha i {city}, {country} le {person1}, {person2}, agus {additionalCount, number} daoine eile ar {date}", - "image_saved_successfully": "Íomhá sábháilte", - "image_viewer_page_state_provider_download_started": "Íoslódáil Tosaithe", - "image_viewer_page_state_provider_download_success": "Íoslódáil Rathúil", - "image_viewer_page_state_provider_share_error": "Earráid Chomhroinnte", - "immich_logo": "Lógó Immich", - "immich_web_interface": "Comhéadan Gréasáin Immich", - "import_from_json": "Iompórtáil ó JSON", - "import_path": "Cosán allmhairithe", - "in_albums": "I {count, plural, one {# albam} other {# albaim}}", - "in_archive": "Sa chartlann", - "in_year": "I {year}", - "in_year_selector": "Isteach", - "include_archived": "Cuir cartlannaithe san áireamh", - "include_shared_albums": "Cuir albaim chomhroinnte san áireamh", - "include_shared_partner_assets": "Cuir sócmhainní comhpháirtíochta san áireamh", - "individual_share": "Sciar aonair", - "individual_shares": "Scaireanna aonair", - "info": "Eolas", - "interval": { - "day_at_onepm": "Gach lá ag 1pm", - "hours": "Gach {hours, plural, one {uair an chloig} other {{hours, number} uair an chloig}}", - "night_at_midnight": "Gach oíche ag meán oíche", - "night_at_twoam": "Gach oíche ag 2am" - }, - "invalid_date": "Dáta neamhbhailí", - "invalid_date_format": "Formáid dáta neamhbhailí", - "invite_people": "Tabhair cuireadh do dhaoine", - "invite_to_album": "Cuireadh chuig albam", - "ios_debug_info_fetch_ran_at": "Rith Fetch {dateTime}", - "ios_debug_info_last_sync_at": "Sioncrónú deireanach {dateTime}", - "ios_debug_info_no_processes_queued": "Níl aon phróisis chúlra i scuaine", - "ios_debug_info_no_sync_yet": "Níl aon phost sioncrónaithe cúlra rite fós", - "ios_debug_info_processes_queued": "{count, plural, one {{count} próiseas cúlra queued} other {{count} próisis chúlra queued}}", - "ios_debug_info_processing_ran_at": "Rith an phróiseáil {dateTime}", - "items_count": "{count, plural, one {# mír} other {# míreanna}}", - "jobs": "Poist", - "json_editor": "Eagarthóir JSON", - "json_error": "Earráid JSON", - "keep": "Coimeád", - "keep_albums": "Coinnigh albaim", - "keep_albums_count": "Ag coinneáil {count} {count, plural, one {album} other {albums}}", - "keep_all": "Coinnigh Gach Rud", - "keep_description": "Roghnaigh cad a fhanann ar do ghléas agus spás á shaoradh.", - "keep_favorites": "Coinnigh na cinn is fearr leat", - "keep_on_device": "Coinnigh ar an ngléas", - "keep_on_device_hint": "Roghnaigh míreanna le coinneáil ar an ngléas seo", - "keep_this_delete_others": "Coinnigh seo, scrios cinn eile", - "keeping": "Ag coinneáil: {items}", - "kept_this_deleted_others": "Choinnigh an tsócmhainn seo agus scriosadh {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "keyboard_shortcuts": "Aicearraí méarchláir", - "language": "Teanga", - "language_no_results_subtitle": "Bain triail as do théarma cuardaigh a choigeartú", - "language_no_results_title": "Níor aimsíodh aon teangacha", - "language_search_hint": "Cuardaigh teangacha...", - "language_setting_description": "Roghnaigh do theanga is fearr leat", - "large_files": "Comhaid Mhóra", - "last": "Deireanach", - "last_months": "{count, plural, one {An mhí seo caite} other {An # mhí seo caite}}", - "last_seen": "Chonaic mé an uair dheireanach", - "latest_version": "Leagan is Déanaí", - "latitude": "Domhanleithead", - "leave": "Fág", - "leave_album": "Fág an t-albam", - "lens_model": "Múnla lionsa", - "let_others_respond": "Lig do dhaoine eile freagairt", - "level": "Leibhéal", - "library": "Leabharlann", - "library_add_folder": "Cuir fillteán leis", - "library_edit_folder": "Cuir fillteán in eagar", - "library_options": "Roghanna leabharlainne", - "library_page_device_albums": "Albaim ar an nGléas", - "library_page_new_album": "Albam nua", - "library_page_sort_asset_count": "Líon na sócmhainní", - "library_page_sort_created": "Dáta cruthaithe", - "library_page_sort_last_modified": "Athraithe go deireanach", - "library_page_sort_title": "Teideal an albaim", - "licenses": "Ceadúnais", - "light": "Solas", - "like": "Is maith liom", - "like_deleted": "Scriosadh an rud is maith liom", - "link_motion_video": "Físeán gluaiseachta nasctha", - "link_to_oauth": "Nasc le OAuth", - "linked_oauth_account": "Cuntas OAuth nasctha", - "list": "Liosta", - "loading": "Ag luchtú", - "loading_search_results_failed": "Theip ar lódáil na dtorthaí cuardaigh", - "local": "Áitiúil", - "local_asset_cast_failed": "Ní féidir sócmhainn nach bhfuil uaslódáilte chuig an bhfreastalaí a chasadh", - "local_assets": "Sócmhainní Áitiúla", - "local_id": "Aitheantas Áitiúil", - "local_media_summary": "Achoimre ar na Meáin Áitiúla", - "local_network": "Líonra áitiúil", - "local_network_sheet_info": "Ceanglóidh an aip leis an bhfreastalaí tríd an URL seo agus an líonra Wi-Fi sonraithe á úsáid", - "location": "Suíomh", - "location_permission": "Cead suímh", - "location_permission_content": "Chun an ghné uath-athraithe a úsáid, ní mór cead suímh bheacht a bheith ag Immich ionas gur féidir leis ainm an líonra Wi-Fi reatha a léamh", - "location_picker_choose_on_map": "Roghnaigh ar an léarscáil", - "location_picker_latitude_error": "Cuir isteach domhanleithead bailí", - "location_picker_latitude_hint": "Cuir isteach do dhomhanleithead anseo", - "location_picker_longitude_error": "Cuir isteach domhanfhad bailí", - "location_picker_longitude_hint": "Cuir isteach do dhomhanfhad anseo", - "lock": "Glasáil", - "locked_folder": "Fillteán faoi Ghlas", - "log_detail_title": "Sonraí Logála", - "log_out": "Logáil amach", - "log_out_all_devices": "Logáil Amach Gach Gléas", - "logged_in_as": "Logáilte isteach mar {user}", - "logged_out_all_devices": "Logáladh amach gach gléas", - "logged_out_device": "Gléas logáilte amach", - "login": "Logáil Isteach", - "login_disabled": "Tá logáil isteach díchumasaithe", - "login_form_api_exception": "Eisceacht API. Seiceáil URL an fhreastalaí agus déan iarracht arís.", - "login_form_back_button_text": "Ar ais", - "login_form_email_hint": "doríomhphost@ríomhphost.com", - "login_form_endpoint_hint": "http://ip-do-fhreastalaí:port", - "login_form_endpoint_url": "URL Deireadhphointe an Fhreastalaí", - "login_form_err_http": "Sonraigh http:// nó https:// le do thoil", - "login_form_err_invalid_email": "Ríomhphost Neamhbhailí", - "login_form_err_invalid_url": "URL neamhbhailí", - "login_form_err_leading_whitespace": "Spás bán tosaigh", - "login_form_err_trailing_whitespace": "Spás bán ag leanúint", - "login_form_failed_get_oauth_server_config": "Earráid logála ag baint úsáide as OAuth, seiceáil URL an fhreastalaí", - "login_form_failed_get_oauth_server_disable": "Níl gné OAuth ar fáil ar an bhfreastalaí seo", - "login_form_failed_login": "Earráid ag logáil isteach, seiceáil URL an fhreastalaí, an ríomhphost agus an focal faire", - "login_form_handshake_exception": "Bhí Eisceacht Lámh-Chroith leis an bhfreastalaí. Cumasaigh tacaíocht do theastas féinshínithe sna socruithe má tá teastas féinshínithe in úsáid agat.", - "login_form_password_hint": "pasfhocal", - "login_form_save_login": "Fan logáilte isteach", - "login_form_server_empty": "Cuir isteach URL freastalaí.", - "login_form_server_error": "Níorbh fhéidir ceangal leis an bhfreastalaí.", - "login_has_been_disabled": "Tá logáil isteach díchumasaithe.", - "login_password_changed_error": "Tharla earráid agus do phasfhocal á nuashonrú", - "login_password_changed_success": "Nuashonraíodh an focal faire go rathúil", - "logout_all_device_confirmation": "An bhfuil tú cinnte gur mian leat logáil amach as gach gléas?", - "logout_this_device_confirmation": "An bhfuil tú cinnte gur mian leat logáil amach as an ngléas seo?", - "logs": "Logaí", - "longitude": "Domhanfhad", - "look": "Féach", - "loop_videos": "Físeáin lúbtha", - "loop_videos_description": "Cumasaigh físeán a lúbadh go huathoibríoch san amharcóir sonraí.", - "main_branch_warning": "Tá leagan forbartha in úsáid agat; molaimid go láidir leagan scaoilte a úsáid!", - "main_menu": "Príomh-roghchlár", - "maintenance_action_restore": "Bunachar Sonraí á Athchóiriú", - "maintenance_description": "Tá Immich curtha i mód cothabhála.", - "maintenance_end": "Deireadh a chur leis an modh cothabhála", - "maintenance_end_error": "Theip ar an modh cothabhála a chríochnú.", - "maintenance_logged_in_as": "Logáilte isteach faoi láthair mar {user}", - "maintenance_restore_from_backup": "Athchóirigh ó Chúltaca", - "maintenance_restore_library": "Athchóirigh Do Leabharlann", - "maintenance_restore_library_confirm": "Más cosúil go bhfuil sé seo ceart, lean ar aghaidh le cúltaca a athchóiriú!", - "maintenance_restore_library_description": "Bunachar Sonraí á Athchóiriú", - "maintenance_restore_library_folder_has_files": "Tá {count} fillteán(anna) i {folder}", - "maintenance_restore_library_folder_no_files": "Tá comhaid ar iarraidh i {folder}!", - "maintenance_restore_library_folder_pass": "inléite agus inscríofa", - "maintenance_restore_library_folder_read_fail": "ní féidir a léamh", - "maintenance_restore_library_folder_write_fail": "ní féidir a scríobh", - "maintenance_restore_library_hint_missing_files": "B’fhéidir go bhfuil comhaid thábhachtacha ar iarraidh ort", - "maintenance_restore_library_hint_regenerate_later": "Is féidir leat iad seo a athghiniúint níos déanaí sna socruithe", - "maintenance_restore_library_hint_storage_template_missing_files": "Ag baint úsáide as teimpléad stórála? B’fhéidir go bhfuil comhaid ar iarraidh ort", - "maintenance_restore_library_loading": "Ag lódáil seiceálacha sláine agus heorasticí…", - "maintenance_task_backup": "Ag cruthú cúltaca den bhunachar sonraí atá ann cheana féin…", - "maintenance_task_migrations": "Imircí bunachar sonraí á reáchtáil…", - "maintenance_task_restore": "Ag athchóiriú an chúltaca roghnaithe…", - "maintenance_task_rollback": "Theip ar an athchóiriú, ag rolladh ar ais go dtí an pointe athchóirithe…", - "maintenance_title": "Gan Fáil go Sealadach", - "make": "Déan", - "manage_geolocation": "Bainistigh suíomh", - "manage_media_access_rationale": "Tá an cead seo ag teastáil chun déileáil i gceart le sócmhainní a bhogadh chuig an mbruscar agus iad a athbhunú uaidh.", - "manage_media_access_settings": "Oscail socruithe", - "manage_media_access_subtitle": "Lig don aip Immich comhaid meán a bhainistiú agus a bhogadh.", - "manage_media_access_title": "Rochtain Bainistíochta Meán", - "manage_shared_links": "Bainistigh naisc chomhroinnte", - "manage_sharing_with_partners": "Bainistigh comhroinnt le comhpháirtithe", - "manage_the_app_settings": "Bainistigh socruithe an aip", - "manage_your_account": "Bainistigh do chuntas", - "manage_your_api_keys": "Bainistigh d’eochracha API", - "manage_your_devices": "Bainistigh do ghléasanna logáilte isteach", - "manage_your_oauth_connection": "Bainistigh do nasc OAuth", - "map": "Léarscáil", - "map_assets_in_bounds": "{count, plural, =0 {Gan aon ghrianghraif sa cheantar seo} one {# grianghraf} other {# grianghraif}}", - "map_cannot_get_user_location": "Ní féidir suíomh an úsáideora a fháil", - "map_location_dialog_yes": "Tá", - "map_location_picker_page_use_location": "Úsáid an suíomh seo", - "map_location_service_disabled_content": "Ní mór seirbhís suímh a chumasú chun sócmhainní ó do shuíomh reatha a thaispeáint. Ar mhaith leat é a chumasú anois?", - "map_location_service_disabled_title": "Seirbhís Suímh díchumasaithe", - "map_marker_for_images": "Marcóir léarscáile le haghaidh íomhánna a tógadh i {city}, {country}", - "map_marker_with_image": "Marcóir léarscáile le híomhá", - "map_no_location_permission_content": "Tá cead suímh ag teastáil chun sócmhainní a thaispeáint ó do shuíomh reatha. Ar mhaith leat é a cheadú anois?", - "map_no_location_permission_title": "Cead Suímh diúltaithe", - "map_settings": "Socruithe léarscáile", - "map_settings_dark_mode": "Mód dorcha", - "map_settings_date_range_option_day": "Le 24 uair an chloig anuas", - "map_settings_date_range_option_days": "Laethanta anuas ({days})", - "map_settings_date_range_option_year": "An bhliain seo caite", - "map_settings_date_range_option_years": "Blianta {years} anuas", - "map_settings_dialog_title": "Socruithe Léarscáile", - "map_settings_include_show_archived": "Cuir Cartlannaithe san áireamh", - "map_settings_include_show_partners": "Cuir Comhpháirtithe san áireamh", - "map_settings_only_show_favorites": "Taispeáin Is Fearr Leat Amháin", - "map_settings_theme_settings": "Téama Léarscáile", - "map_zoom_to_see_photos": "Zúmáil amach chun grianghraif a fheiceáil", - "mark_all_as_read": "Marcáil gach rud mar léite", - "mark_as_read": "Marcáil mar léite", - "marked_all_as_read": "Marcáladh gach rud mar léite", - "matches": "Cluichí", - "matching_assets": "Sócmhainní Meaitseála", - "media_type": "Cineál meán", - "memories": "Cuimhní cinn", - "memories_all_caught_up": "Gach rud gafa suas", - "memories_check_back_tomorrow": "Seiceáil ar ais amárach le haghaidh tuilleadh cuimhní cinn", - "memories_setting_description": "Bainistigh a bhfuil le feiceáil agat i do chuimhní cinn", - "memories_start_over": "Tosaigh Arís", - "memories_swipe_to_close": "Svaidhpeáil suas le dúnadh", - "memory": "Cuimhne", - "memory_lane_title": "Lána na Cuimhne {title}", - "menu": "Roghchlár", - "merge": "Cumaisc", - "merge_people": "Daoine a chumasc", - "merge_people_limit": "Ní féidir leat ach suas le 5 aghaidh a chumasc ag an am céanna", - "merge_people_prompt": "Ar mhaith leat na daoine seo a chumasc? Ní féidir an gníomh seo a aisiompú.", - "merge_people_successfully": "Daoine a chumasc go rathúil", - "merged_people_count": "Cumaiscthe {count, plural, one {# duine} other {# daoine}}", - "minimize": "Íoslaghdaigh", - "minute": "Nóiméad", - "minutes": "Nóiméid", - "mirror_horizontal": "Cothrománach", - "mirror_vertical": "Ingearach", - "missing": "Ar iarraidh", - "mobile_app": "Aip Shoghluaiste", - "mobile_app_download_onboarding_note": "Íoslódáil an aip shoghluaiste tionlacain ag baint úsáide as na roghanna seo a leanas", - "model": "Samhail", - "month": "Mí", - "monthly_title_text_date_format": "MMMM y", - "more": "Tuilleadh", - "move": "Bog", - "move_down": "Bog síos", - "move_off_locked_folder": "Bog amach as fillteán faoi ghlas", - "move_to": "Bog go", - "move_to_device_trash": "Bog go dtí bruscar an ghléis", - "move_to_lock_folder_action_prompt": "{count} curtha leis an bhfillteán faoi ghlas", - "move_to_locked_folder": "Bog go fillteán faoi ghlas", - "move_to_locked_folder_confirmation": "Bainfear na grianghraif agus na físeáin seo as na halbaim uile, agus ní bheidh siad le feiceáil ach amháin ón bhfillteán faoi ghlas", - "move_up": "Bog suas", - "moved_to_archive": "Bogadh {count, plural, one {# sócmhainn} other {# sócmhainní}} chuig an gcartlann", - "moved_to_library": "Bogadh {count, plural, one {# sócmhainn} other {# sócmhainní}} chuig an leabharlann", - "moved_to_trash": "Bogtha chuig an mbruscar", - "multiselect_grid_edit_date_time_err_read_only": "Ní féidir dáta sócmhainn(í) inléite amháin a chur in eagar, ag scipeáil", - "multiselect_grid_edit_gps_err_read_only": "Ní féidir suíomh sócmhainn(í) inléite amháin a chur in eagar, ag scipeáil", - "mute_memories": "Cuimhní Balbhaigh", - "my_albums": "Mo chuid albaim", - "name": "Ainm", - "name_or_nickname": "Ainm nó leasainm", - "name_required": "Tá ainm ag teastáil", - "navigate": "Loingseoireacht", - "navigate_to_time": "Nascleanúint chuig Am", - "network_requirement_photos_upload": "Úsáid sonraí ceallacha chun grianghraif a chúltaca", - "network_requirement_videos_upload": "Úsáid sonraí ceallacha chun físeáin a chúltaca", - "network_requirements": "Riachtanais Líonra", - "network_requirements_updated": "Athraíodh riachtanais líonra, athshocraíodh an scuaine cúltaca", - "networking_settings": "Líonrú", - "networking_subtitle": "Bainistigh socruithe críochphointe an fhreastalaí", - "never": "Choíche", - "new_album": "Albam Nua", - "new_api_key": "Eochair API Nua", - "new_date_range": "Raon dáta nua", - "new_password": "Pasfhocal nua", - "new_person": "Duine nua", - "new_pin_code": "Cód PIN nua", - "new_pin_code_subtitle": "Seo é an chéad uair duit rochtain a fháil ar an bhfillteán faoi ghlas. Cruthaigh cód PIN chun rochtain shlán a fháil ar an leathanach seo", - "new_timeline": "Amlíne Nua", - "new_update": "Nuashonrú nua", - "new_user_created": "Úsáideoir nua cruthaithe", - "new_version_available": "LEAGAN NUA AR FÁIL", - "newest_first": "Is nuaí ar dtús", - "next": "Ar Aghaidh", - "next_memory": "An chéad chuimhne eile", - "no": "Níl", - "no_actions_added": "Níl aon ghníomhartha curtha leis fós", - "no_albums_found": "Níor aimsíodh aon albaim", - "no_albums_message": "Cruthaigh albam chun do ghrianghraif agus do fhíseáin a eagrú", - "no_albums_with_name_yet": "Is cosúil nach bhfuil aon albaim agat leis an ainm seo go fóill.", - "no_albums_yet": "Is cosúil nach bhfuil aon albaim agat fós.", - "no_archived_assets_message": "Cartlannaigh grianghraif agus físeáin chun iad a cheilt ó d’amharc Grianghraf", - "no_assets_message": "Cliceáil chun do chéad ghrianghraf a uaslódáil", - "no_assets_to_show": "Gan aon sócmhainní le taispeáint", - "no_cast_devices_found": "Ní bhfuarthas aon ghléasanna teilgthe", - "no_checksum_local": "Níl aon suim seiceála ar fáil - ní féidir sócmhainní áitiúla a aisghabháil", - "no_checksum_remote": "Níl aon suim seiceála ar fáil - ní féidir sócmhainn iargúlta a aisghabháil", - "no_configuration_needed": "Níl aon chumraíocht ag teastáil", - "no_devices": "Gan aon fheistí údaraithe", - "no_duplicates_found": "Ní bhfuarthas aon dúblaigh.", - "no_exif_info_available": "Níl aon fhaisnéis exif ar fáil", - "no_explore_results_message": "Uaslódáil tuilleadh grianghraf chun do bhailiúchán a iniúchadh.", - "no_favorites_message": "Cuir na cinn is fearr leat leis chun do phictiúir agus do fhíseáin is fearr a aimsiú go tapa", - "no_filters_added": "Níl aon scagairí curtha leis fós", - "no_libraries_message": "Cruthaigh leabharlann sheachtrach chun do ghrianghraif agus físeáin a fheiceáil", - "no_local_assets_found": "Ní bhfuarthas aon sócmhainní áitiúla leis an tsuim sheiceála seo", - "no_location_set": "Níl aon suíomh socraithe", - "no_locked_photos_message": "Tá grianghraif agus físeáin sa bhfillteán faoi ghlas i bhfolach agus ní thaispeánfar iad agus tú ag brabhsáil nó ag cuardach do leabharlann.", - "no_name": "Gan Ainm", - "no_notifications": "Gan aon fhógraí", - "no_people_found": "Ní bhfuarthas aon daoine comhoiriúnacha", - "no_places": "Gan aon áiteanna", - "no_remote_assets_found": "Ní bhfuarthas aon sócmhainní iargúlta leis an tsuim sheiceála seo", - "no_results": "Gan aon torthaí", - "no_results_description": "Bain triail as comhchiallach nó eochairfhocal níos ginearálta", - "no_shared_albums_message": "Cruthaigh albam chun grianghraif agus físeáin a roinnt le daoine i do líonra", - "no_uploads_in_progress": "Níl aon uaslódálacha ar siúl", - "none": "Dada", - "not_allowed": "Ní cheadaítear", - "not_available": "N/B", - "not_in_any_album": "Ní in aon albam", - "not_selected": "Níor roghnaíodh", - "note_apply_storage_label_to_previously_uploaded assets": "Nóta: Chun an Lipéad Stórála a chur i bhfeidhm ar shócmhainní a uaslódáileadh roimhe seo, rith an", - "notes": "Nótaí", - "nothing_here_yet": "Níl aon rud anseo fós", - "notification_permission_dialog_content": "Chun fógraí a chumasú, téigh go Socruithe agus roghnaigh ceadaigh.", - "notification_permission_list_tile_content": "Tabhair cead fógraí a chumasú.", - "notification_permission_list_tile_enable_button": "Cumasaigh Fógraí", - "notification_permission_list_tile_title": "Cead Fógra", - "notification_toggle_setting_description": "Cumasaigh fógraí ríomhphoist", - "notifications": "Fógraí", - "notifications_setting_description": "Bainistigh fógraí", - "oauth": "OAuth", - "obtainium_configurator": "Cumraitheoir Obtainium", - "obtainium_configurator_instructions": "Bain úsáid as Obtainium chun an aip Android a shuiteáil agus a nuashonrú go díreach ó eisiúint Immich ar GitHub. Cruthaigh eochair API agus roghnaigh malairt chun do nasc cumraíochta Obtainium a chruthú", - "ocr": "OCR", - "official_immich_resources": "Acmhainní Oifigiúla Immich", - "offline": "As líne", - "offset": "Fritháireamh", - "ok": "Ceart go leor", - "oldest_first": "Is sine ar dtús", - "on_this_device": "Ar an ngléas seo", - "onboarding": "Ionduchtú", - "onboarding_locale_description": "Roghnaigh do theanga is fearr leat. Is féidir leat é seo a athrú níos déanaí i do shocruithe.", - "onboarding_privacy_description": "Braitheann na gnéithe seo a leanas (roghnacha) ar sheirbhísí seachtracha, agus is féidir iad a dhíchumasú tráth ar bith sna socruithe.", - "onboarding_server_welcome_description": "Lig dúinn do chás a shocrú le roinnt socruithe coitianta.", - "onboarding_theme_description": "Roghnaigh téama datha do do chás. Is féidir leat é seo a athrú níos déanaí i do shocruithe.", - "onboarding_user_welcome_description": "Cuirfimid tús leis!", - "onboarding_welcome_user": "Fáilte, {user}", - "online": "Ar líne", - "only_favorites": "Is fearr leat amháin", - "open": "Oscail", - "open_in_map_view": "Oscail i radharc léarscáile", - "open_in_openstreetmap": "Oscail in OpenStreetMap", - "open_the_search_filters": "Oscail na scagairí cuardaigh", - "options": "Roghanna", - "or": "nó", - "organize_into_albums": "Eagraigh in albaim", - "organize_into_albums_description": "Cuir grianghraif atá ann cheana féin in albaim ag baint úsáide as na socruithe sioncrónaithe reatha", - "organize_your_library": "Eagraigh do leabharlann", - "original": "bunaidh", - "other": "Eile", - "other_devices": "Gléasanna eile", - "other_entities": "Eintitis eile", - "other_variables": "Athróga eile", - "owned": "Faoi úinéireacht", - "owner": "Úinéir", - "page": "Leathanach", - "partner": "Comhpháirtí", - "partner_can_access": "Is féidir le {partner} rochtain a fháil", - "partner_can_access_assets": "Do ghrianghraif agus do fhíseáin go léir seachas iad siúd atá i gCartlann agus Scriosta", - "partner_can_access_location": "An áit inar tógadh do ghrianghraif", - "partner_list_user_photos": "Grianghraif {user}", - "partner_list_view_all": "Féach ar gach rud", - "partner_page_empty_message": "Níl do ghrianghraif roinnte le haon pháirtí go fóill.", - "partner_page_no_more_users": "Níl aon úsáideoirí eile le cur leis", - "partner_page_partner_add_failed": "Theip ar chomhpháirtí a chur leis", - "partner_page_select_partner": "Roghnaigh comhpháirtí", - "partner_page_shared_to_title": "Roinnte le", - "partner_page_stop_sharing_content": "Ní bheidh {partner} in ann rochtain a fháil ar do ghrianghraif a thuilleadh.", - "partner_sharing": "Comhroinnt Chomhpháirtíochta", - "partners": "Comhpháirtithe", - "password": "Pasfhocal", - "password_does_not_match": "Ní hionann an focal faire", - "password_required": "Pasfhocal Riachtanach", - "password_reset_success": "Athshocrú pasfhocail rathúil", - "past_durations": { - "days": "An t-am atá thart {days, plural, one {lá} other {# laethanta}}", - "hours": "Am atá thart {hours, plural, one {uair} other {# uair an chloig}}", - "years": "Am atá thart {years, plural, one {bliain} other {# bliana}}" - }, - "path": "Cosán", - "pattern": "Patrún", - "pause": "Sos", - "pause_memories": "Cuir cuimhní cinn ar sos", - "paused": "Sosaithe", - "pending": "Ar feitheamh", - "people": "Daoine", - "people_edits_count": "Eagarthóireacht déanta {count, plural, one {# duine} other {# daoine}}", - "people_feature_description": "Ag brabhsáil grianghraif agus físeáin grúpáilte de réir daoine", - "people_selected": "{count, plural, one {# duine roghnaithe} other {# duine roghnaithe}}", - "people_sidebar_description": "Taispeáin nasc chuig Daoine sa bharra taoibh", - "permanent_deletion_warning": "Rabhadh scriosadh buan", - "permanent_deletion_warning_setting_description": "Taispeáin rabhadh agus sócmhainní á scriosadh go buan", - "permanently_delete": "Scrios go buan", - "permanently_delete_assets_count": "Scrios go buan {count, plural, one {sócmhainn} other {sócmhainní}}", - "permanently_delete_assets_prompt": "An bhfuil tú cinnte gur mian leat {count, plural, one {an tsócmhainn seo a scriosadh go buan?} other {na # sócmhainní seo a scriosadh go buan?}} Bainfear {count, plural, one {í dá halbam chomh maith.} other {iad dá n-albamanna chomh maith}}.", - "permanently_deleted_asset": "Sócmhainn scriosta go buan", - "permanently_deleted_assets_count": "Scriosta go buan {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "permission": "Cead", - "permission_empty": "Níor cheart go mbeadh do chead folamh", - "permission_onboarding_back": "Ar ais", - "permission_onboarding_continue_anyway": "Lean ar aghaidh ar aon nós", - "permission_onboarding_get_started": "Tosaigh anois", - "permission_onboarding_go_to_settings": "Téigh go dtí na socruithe", - "permission_onboarding_permission_denied": "Cead diúltaithe. Chun Immich a úsáid, deonaigh ceadanna grianghraf agus físeáin sna Socruithe.", - "permission_onboarding_permission_granted": "Cead tugtha! Tá gach rud réidh.", - "permission_onboarding_permission_limited": "Cead teoranta. Chun ligean d’Immich cúltaca a dhéanamh de do bhailiúchán gailearaí iomlán agus é a bhainistiú, deonaigh ceadanna grianghraf agus físeáin sna Socruithe.", - "permission_onboarding_request": "Éilíonn Immich cead chun do ghrianghraif agus do fhíseáin a fheiceáil.", - "person": "Duine", - "person_age_months": "{months, plural, one {# mí} other {# míonna}} d'aois", - "person_age_year_months": "1 bhliain, {months, plural, one {# mí} other {# míonna}} d'aois", - "person_age_years": "{years, plural, other {# blianta}} d'aois", - "person_birthdate": "Rugadh ar {date}", - "person_hidden": "{name}{hidden, select, true { (i bhfolach)} other {}}", - "person_recognized": "Duine aitheanta", - "person_selected": "Duine roghnaithe", - "photo_shared_all_users": "Is cosúil gur roinn tú do ghrianghraif le gach úsáideoir nó nach bhfuil aon úsáideoir agat le roinnt leis.", - "photos": "Grianghraif", - "photos_and_videos": "Grianghraif & Físeáin", - "photos_count": "{count, plural, one {{count, number} Grianghraf} other {{count, number} Grianghraif}}", - "photos_from_previous_years": "Grianghraif ó bhlianta roimhe seo", - "photos_only": "Grianghraif amháin", - "pick_a_location": "Roghnaigh suíomh", - "pick_custom_range": "Raon saincheaptha", - "pick_date_range": "Roghnaigh raon dáta", - "pin_code_changed_successfully": "Athraíodh an cód PIN go rathúil", - "pin_code_reset_successfully": "Athshocraíodh an cód PIN go rathúil", - "pin_code_setup_successfully": "Socraíodh cód PIN go rathúil", - "pin_verification": "Fíorú cód PIN", - "place": "Áit", - "places": "Áiteanna", - "places_count": "{count, plural, one {{count, number} Áit} other {{count, number} Áiteanna}}", - "play": "Seinn", - "play_memories": "Seinnchuimhní", - "play_motion_photo": "Seinn Grianghraf Gluaiseachta", - "play_or_pause_video": "Seinn nó sos físeán", - "play_original_video": "Seinn físeán bunaidh", - "play_original_video_setting_description": "Is fearr físeáin bhunaidh a athsheinm seachas físeáin thraschódaithe. Mura bhfuil an tsócmhainn bhunaidh comhoiriúnach, b’fhéidir nach n-athsheinmfear i gceart í.", - "play_transcoded_video": "Seinn físeán traschódaithe", - "please_auth_to_access": "Fíordheimhnigh le do thoil chun rochtain a fháil", - "port": "Port", - "preferences_settings_subtitle": "Bainistigh roghanna an aip", - "preferences_settings_title": "Roghanna", - "preparing": "Ag ullmhú", - "preset": "Réamhshocrú", - "preview": "Réamhamharc", - "previous": "Roimhe Seo", - "previous_memory": "Cuimhne roimhe seo", - "previous_or_next_day": "Lá ar aghaidh/ar gcúl", - "previous_or_next_month": "Mí ar aghaidh/ar gcúl", - "previous_or_next_photo": "Grianghraf ar aghaidh/ar gcúl", - "previous_or_next_year": "Bliain ar aghaidh/ar gcúl", - "primary": "Príomhúil", - "privacy": "Príobháideacht", - "profile": "Próifíl", - "profile_drawer_app_logs": "Logaí", - "profile_drawer_client_server_up_to_date": "Tá an cliant agus an freastalaí cothrom le dáta", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mód léite amháin cumasaithe. Brúigh ar dheilbhín an avatáir úsáideora le himeacht.", - "profile_image_of_user": "Íomhá phróifíle de {user}", - "profile_picture_set": "Sraith pictiúr próifíle.", - "public_album": "Albam poiblí", - "public_share": "Comhroinn Phoiblí", - "purchase_account_info": "Tacaíochtaí", - "purchase_activated_subtitle": "Go raibh maith agat as tacú le Immich agus bogearraí foinse oscailte", - "purchase_activated_time": "Gníomhachtaithe ar {date}", - "purchase_activated_title": "Tá d’eochair gníomhachtaithe go rathúil", - "purchase_button_activate": "Gníomhachtaigh", - "purchase_button_buy": "Ceannach", - "purchase_button_buy_immich": "Ceannaigh Immich", - "purchase_button_never_show_again": "Ná taispeáin arís choíche", - "purchase_button_reminder": "Cuir i gcuimhne dom i gceann 30 lá", - "purchase_button_remove_key": "Bain an eochair", - "purchase_button_select": "Roghnaigh", - "purchase_failed_activation": "Theip ar an ngníomhachtú! Seiceáil do r-phost le haghaidh an eochair tháirge cheart!", - "purchase_individual_description_1": "Do dhuine aonair", - "purchase_individual_description_2": "Stádas an tacaí", - "purchase_individual_title": "Aonair", - "purchase_input_suggestion": "An bhfuil eochair táirge agat? Cuir isteach an eochair thíos", - "purchase_license_subtitle": "Ceannaigh Immich chun tacú le forbairt leanúnach na seirbhíse", - "purchase_lifetime_description": "Ceannach saoil", - "purchase_option_title": "ROGHANNA CEANNAIGH", - "purchase_panel_info_1": "Tógann sé go leor ama agus iarrachta Immich a thógáil, agus tá innealtóirí lánaimseartha againn ag obair air chun é a dhéanamh chomh maith agus is féidir linn. Is é ár misean go mbeidh bogearraí foinse oscailte agus cleachtais ghnó eiticiúla ina bhfoinse ioncaim inbhuanaithe d’fhorbróirí agus go gcruthófar éiceachóras a urramaíonn príobháideacht le roghanna fíor seachas seirbhísí scamall saothraithe.", - "purchase_panel_info_2": "Ós rud é go bhfuilimid tiomanta gan ballaí íocaíochta a chur leis, ní thabharfaidh an ceannachán seo aon ghnéithe breise duit in Immich. Táimid ag brath ar úsáideoirí cosúil leatsa chun tacú le forbairt leanúnach Immich.", - "purchase_panel_title": "Tacaigh leis an tionscadal", - "purchase_per_server": "In aghaidh an fhreastalaí", - "purchase_per_user": "In aghaidh an úsáideora", - "purchase_remove_product_key": "Bain Eochair an Táirge", - "purchase_remove_product_key_prompt": "An bhfuil tú cinnte gur mian leat an eochair táirge a bhaint?", - "purchase_remove_server_product_key": "Bain eochair táirge an fhreastalaí", - "purchase_remove_server_product_key_prompt": "An bhfuil tú cinnte gur mian leat eochair táirge an Fhreastalaí a bhaint?", - "purchase_server_description_1": "Don fhreastalaí ar fad", - "purchase_server_description_2": "Stádas an tacaí", - "purchase_server_title": "Freastalaí", - "purchase_settings_server_activated": "Déanann an riarthóir bainistíocht ar eochair táirge an fhreastalaí", - "query_asset_id": "ID Sócmhainne Iarratais", - "queue_status": "Scuaineáil {count}/{total}", - "rate_asset": "Rátáil Sócmhainn", - "rating": "Rátáil réalta", - "rating_clear": "Glan rátáil", - "rating_count": "{count, plural, one {# réalta} other {# réaltaí}}", - "rating_description": "Taispeáin an rátáil EXIF sa phainéal eolais", - "rating_set": "Socraithe go {rating, plural, one {# réalta} other {# réalta}}", - "reaction_options": "Roghanna imoibrithe", - "read_changelog": "Léigh an Log Athraithe", - "readonly_mode_disabled": "Mód léite amháin díchumasaithe", - "readonly_mode_enabled": "Mód léite amháin cumasaithe", - "ready_for_upload": "Réidh le huaslódáil", - "reassign": "Athshannadh", - "reassigned_assets_to_existing_person": "Athshannadh {count, plural, one {# sócmhainn} other {# sócmhainní}} chuig {name, select, null {duine atá ann cheana féin} other {{name}}}", - "reassigned_assets_to_new_person": "Athshannadh {count, plural, one {# sócmhainn} other {# sócmhainní}} do dhuine nua", - "reassing_hint": "Sannadh sócmhainní roghnaithe do dhuine atá ann cheana féin", - "recent": "Le déanaí", - "recent-albums": "Albaim le déanaí", - "recent_searches": "Cuardaigh le déanaí", - "recently_added": "Cuireadh leis le déanaí", - "recently_added_page_title": "Curtha leis le Déanaí", - "recently_taken": "Tógtha le déanaí", - "recently_taken_page_title": "Tógtha le Déanaí", - "refresh": "Athnuachan", - "refresh_encoded_videos": "Athnuaigh físeáin ionchódaithe", - "refresh_faces": "Athnuachan aghaidheanna", - "refresh_metadata": "Athnuaigh meiteashonraí", - "refresh_thumbnails": "Athnuachan mionsamhlacha", - "refreshed": "Athnuachan", - "refreshes_every_file": "Athléann sé gach comhad atá ann cheana féin agus comhad nua", - "refreshing_encoded_video": "Físeán ionchódaithe á athnuachan", - "refreshing_faces": "Aghaidheanna athnuachana", - "refreshing_metadata": "Meiteashonraí á n-athnuachan", - "regenerating_thumbnails": "Mionsamhlacha á n-athghiniúint", - "remote": "Cianda", - "remote_assets": "Sócmhainní Cianda", - "remote_media_summary": "Achoimre ar na Meáin Iargúlta", - "remove": "Bain", - "remove_assets_album_confirmation": "An bhfuil tú cinnte gur mian leat {count, plural, one {# sócmhainn} other {# sócmhainní}} a bhaint den albam?", - "remove_assets_shared_link_confirmation": "An bhfuil tú cinnte gur mian leat {count, plural, one {# sócmhainn} other {# sócmhainní}} a bhaint den nasc comhroinnte seo?", - "remove_assets_title": "Bain sócmhainní?", - "remove_custom_date_range": "Bain raon dáta saincheaptha", - "remove_deleted_assets": "Bain Sócmhainní Scriosta", - "remove_from_album": "Bain den albam", - "remove_from_album_action_prompt": "Baineadh {count} den albam", - "remove_from_favorites": "Bain as ceanáin", - "remove_from_lock_folder_action_prompt": "Baineadh {count} as an bhfillteán faoi ghlas", - "remove_from_locked_folder": "Bain as fillteán faoi ghlas", - "remove_from_locked_folder_confirmation": "An bhfuil tú cinnte gur mian leat na grianghraif agus na físeáin seo a bhogadh as an bhfillteán faoi ghlas? Beidh siad le feiceáil i do leabharlann.", - "remove_from_shared_link": "Bain den nasc comhroinnte", - "remove_memory": "Bain cuimhne", - "remove_photo_from_memory": "Bain grianghraf as an gcuimhne seo", - "remove_tag": "Bain an chlib", - "remove_url": "Bain URL", - "remove_user": "Bain úsáideoir", - "removed_api_key": "Eochair API bainte: {name}", - "removed_from_archive": "Bainte as an gcartlann", - "removed_from_favorites": "Bainte as na cinn is ansa leat", - "removed_from_favorites_count": "{count, plural, other {Baineadh #}} ó ceanáin", - "removed_memory": "Cuimhne bainte", - "removed_photo_from_memory": "Baineadh grianghraf as an gcuimhne", - "removed_tagged_assets": "Baineadh an clib as {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "rename": "Athainmnigh", - "repair": "Deisiú", - "repair_no_results_message": "Beidh comhaid neamhrianaithe agus ar iarraidh le feiceáil anseo", - "replace_with_upload": "Cuir uaslódáil ina áit", - "repository": "Stór", - "require_password": "Éiligh pasfhocal", - "require_user_to_change_password_on_first_login": "Éiligh ar an úsáideoir an focal faire a athrú ar an gcéad logáil isteach", - "rescan": "Athscanadh", - "reset": "Athshocraigh", - "reset_password": "Athshocraigh an focal faire", - "reset_people_visibility": "Athshocraigh infheictheacht daoine", - "reset_pin_code": "Athshocraigh an cód PIN", - "reset_pin_code_description": "Má rinne tú dearmad ar do chód PIN, is féidir leat teagmháil a dhéanamh le riarthóir an fhreastalaí chun é a athshocrú", - "reset_pin_code_success": "Athshocraíodh an cód PIN go rathúil", - "reset_pin_code_with_password": "Is féidir leat do chód PIN a athshocrú i gcónaí le do phasfhocal", - "reset_sqlite": "Athshocraigh Bunachar Sonraí SQLite", - "reset_sqlite_confirmation": "An bhfuil tú cinnte gur mian leat bunachar sonraí SQLite a athshocrú? Beidh ort logáil amach agus logáil isteach arís chun na sonraí a athshioncronú", - "reset_sqlite_success": "Athshocraíodh bunachar sonraí SQLite go rathúil", - "reset_to_default": "Athshocraigh go réamhshocraithe", - "resolution": "Taifeach", - "resolve_duplicates": "Réitigh dúblaigh", - "resolved_all_duplicates": "Réitíodh na dúblaigh go léir", - "restore": "Athchóirigh", - "restore_all": "Athchóirigh gach rud", - "restore_trash_action_prompt": "{count} athchóirithe ón mbruscar", - "restore_user": "Athchóirigh úsáideoir", - "restored_asset": "Sócmhainn athchóirithe", - "resume": "Atosú", - "resume_paused_jobs": "Atosú {count, plural, one {# post ar sos} other {# poist ar sos}}", - "retry_upload": "Déan iarracht arís uaslódáil", - "review_duplicates": "Athbhreithnigh dúblaigh", - "review_large_files": "Athbhreithnigh comhaid mhóra", - "role": "Ról", - "role_editor": "Eagarthóir", - "role_viewer": "Amharcóir", - "running": "Ag rith", - "save": "Sábháil", - "save_to_gallery": "Sábháil chuig an ngailearaí", - "saved": "Sábháilte", - "saved_api_key": "Eochair API Sábháilte", - "saved_profile": "Próifíl shábháilte", - "saved_settings": "Socruithe sábháilte", - "say_something": "Abair rud éigin", - "scaffold_body_error_occurred": "Tharla earráid", - "scan": "Scanadh", - "scan_all_libraries": "Scanáil Gach Leabharlann", - "scan_library": "Scanadh", - "scan_settings": "Socruithe Scanadh", - "scanning": "Ag scanadh", - "scanning_for_album": "Ag scanadh le haghaidh albam...", - "search": "Cuardaigh", - "search_albums": "Cuardaigh albaim", - "search_by_context": "Cuardaigh de réir comhthéacs", - "search_by_description": "Cuardaigh de réir cur síos", - "search_by_description_example": "Lá siúlóide i Sapa", - "search_by_filename": "Cuardaigh de réir ainm comhaid nó síneadh", - "search_by_filename_example": "i.e. IMG_1234.JPG nó PNG", - "search_by_ocr": "Cuardaigh de réir OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Cuardaigh samhail lionsa...", - "search_camera_make": "Cuardaigh déantúsóir ceamara...", - "search_camera_model": "Cuardaigh samhail ceamara...", - "search_city": "Cuardaigh cathair...", - "search_country": "Cuardaigh tír...", - "search_filter_apply": "Cuir scagaire i bhfeidhm", - "search_filter_camera_title": "Roghnaigh cineál ceamara", - "search_filter_date": "Dáta", - "search_filter_date_interval": "{start} go {end}", - "search_filter_date_title": "Roghnaigh raon dáta", - "search_filter_display_option_not_in_album": "Ní san albam", - "search_filter_display_options": "Roghanna Taispeána", - "search_filter_filename": "Cuardaigh de réir ainm comhaid", - "search_filter_location": "Suíomh", - "search_filter_location_title": "Roghnaigh suíomh", - "search_filter_media_type": "Cineál Meán", - "search_filter_media_type_title": "Roghnaigh cineál meán", - "search_filter_ocr": "Cuardaigh de réir OCR", - "search_filter_people_title": "Roghnaigh daoine", - "search_filter_star_rating": "Rátáil Réalta", - "search_for": "Cuardaigh le haghaidh", - "search_for_existing_person": "Cuardaigh duine atá ann cheana féin", - "search_no_more_result": "Gan aon torthaí eile", - "search_no_people": "Gan aon duine", - "search_no_people_named": "Gan aon duine darb ainm \"{name}\"", - "search_no_result": "Ní bhfuarthas aon torthaí, bain triail as téarma cuardaigh nó teaglaim eile", - "search_options": "Roghanna cuardaigh", - "search_page_categories": "Catagóirí", - "search_page_motion_photos": "Grianghraif Ghluaiseachta", - "search_page_no_objects": "Níl aon eolas faoi réada ar fáil", - "search_page_no_places": "Níl aon eolas faoi áiteanna ar fáil", - "search_page_screenshots": "Seat scáileáin", - "search_page_search_photos_videos": "Cuardaigh do ghrianghraif agus do fhíseáin", - "search_page_selfies": "Féinín", - "search_page_things": "Rudaí", - "search_page_view_all_button": "Féach ar gach rud", - "search_page_your_activity": "Do ghníomhaíocht", - "search_page_your_map": "Do Léarscáil", - "search_people": "Cuardaigh daoine", - "search_places": "Cuardaigh áiteanna", - "search_rating": "Cuardaigh de réir rátála...", - "search_result_page_new_search_hint": "Cuardach Nua", - "search_settings": "Socruithe cuardaigh", - "search_state": "Cuardaigh stát...", - "search_suggestion_list_smart_search_hint_1": "Tá cuardach cliste cumasaithe de réir réamhshocraithe, chun meiteashonraí a chuardach bain úsáid as an comhréir ", - "search_suggestion_list_smart_search_hint_2": "m:do-théarma-cuardaigh", - "search_tags": "Cuardaigh clibeanna...", - "search_timezone": "Cuardaigh crios ama...", - "search_type": "Cineál cuardaigh", - "search_your_photos": "Cuardaigh do ghrianghraif", - "searching_locales": "Ag cuardach láithreacha...", - "second": "Dara", - "see_all_people": "Féach ar gach duine", - "select": "Roghnaigh", - "select_album": "Roghnaigh albam", - "select_album_cover": "Roghnaigh clúdach albaim", - "select_albums": "Roghnaigh albaim", - "select_all": "Roghnaigh gach rud", - "select_all_duplicates": "Roghnaigh na dúblaigh go léir", - "select_all_in": "Roghnaigh gach rud i {group}", - "select_avatar_color": "Roghnaigh dath an abhatár", - "select_count": "{count, plural, one {Roghnaigh #} other {Roghnaigh #}}", - "select_cutoff_date": "Roghnaigh dáta scoir", - "select_face": "Roghnaigh aghaidh", - "select_featured_photo": "Roghnaigh grianghraf le feiceáil", - "select_from_computer": "Roghnaigh ón ríomhaire", - "select_keep_all": "Roghnaigh coinnigh gach rud", - "select_library_owner": "Roghnaigh úinéir leabharlainne", - "select_new_face": "Roghnaigh aghaidh nua", - "select_people": "Roghnaigh daoine", - "select_person": "Roghnaigh duine", - "select_person_to_tag": "Roghnaigh duine le clibeáil", - "select_photos": "Roghnaigh grianghraif", - "select_trash_all": "Roghnaigh gach rud sa bhruscar", - "select_user_for_sharing_page_err_album": "Theip ar albam a chruthú", - "selected": "Roghnaithe", - "selected_count": "{count, plural, other {# roghnaithe}}", - "selected_gps_coordinates": "Comhordanáidí GPS Roghnaithe", - "send_message": "Seol teachtaireacht", - "send_welcome_email": "Seol ríomhphost fáilte", - "server_endpoint": "Críochphointe Freastalaí", - "server_info_box_app_version": "Leagan na hAipe", - "server_info_box_server_url": "URL an fhreastalaí", - "server_offline": "Freastalaí As Líne", - "server_online": "Freastalaí Ar Líne", - "server_privacy": "Príobháideacht an Fhreastalaí", - "server_restarting_description": "Athnuachanófar an leathanach seo ar feadh tamaill.", - "server_restarting_title": "Tá an freastalaí ag atosú", - "server_stats": "Staitisticí Freastalaí", - "server_update_available": "Tá nuashonrú freastalaí ar fáil", - "server_version": "Leagan an Fhreastalaí", - "set": "Socraigh", - "set_as_album_cover": "Socraigh mar chlúdach albaim", - "set_as_featured_photo": "Socraigh mar ghrianghraf le feiceáil", - "set_as_profile_picture": "Socraigh mar phictiúr próifíle", - "set_date_of_birth": "Socraigh dáta breithe", - "set_profile_picture": "Socraigh pictiúr próifíle", - "set_slideshow_to_fullscreen": "Socraigh Sleamhnán go lánscáileán", - "set_stack_primary_asset": "Socraigh mar phríomhshócmhainn", - "setting_image_viewer_help": "Lódálann an breathnóir sonraí an mionsamhail bheag ar dtús, ansin luchtaíonn sé an réamhamharc meánmhéide (más cumasaithe), agus ar deireadh luchtaíonn sé an bunleagan (más cumasaithe).", - "setting_image_viewer_original_subtitle": "Cumasaigh chun an íomhá lántaifigh bhunaidh (mór!) a luchtú. Díchumasaigh chun úsáid sonraí a laghdú (ar an líonra agus ar an taisce ar an ngléas araon).", - "setting_image_viewer_original_title": "Luchtaigh an íomhá bhunaidh", - "setting_image_viewer_preview_subtitle": "Cumasaigh chun íomhá meántaifigh a luchtú. Díchumasaigh chun an bunleagan a luchtú go díreach nó chun an mionsamhail amháin a úsáid.", - "setting_image_viewer_preview_title": "Luchtaigh íomhá réamhamhairc", - "setting_image_viewer_title": "Íomhánna", - "setting_languages_apply": "Cuir isteach", - "setting_languages_subtitle": "Athraigh teanga an aip", - "setting_notifications_notify_failures_grace_period": "Fógra a thabhairt faoi theipeanna cúltaca cúlra: {duration}", - "setting_notifications_notify_hours": "{count} uair an chloig", - "setting_notifications_notify_immediately": "láithreach", - "setting_notifications_notify_minutes": "{count} nóiméad", - "setting_notifications_notify_never": "riamh", - "setting_notifications_notify_seconds": "{count} soicind", - "setting_notifications_single_progress_subtitle": "Faisnéis mhionsonraithe faoi dhul chun cinn uaslódála in aghaidh an tsócmhainne", - "setting_notifications_single_progress_title": "Taispeáin dul chun cinn sonraí cúltaca sa chúlra", - "setting_notifications_subtitle": "Coigeartaigh do chuid roghanna fógra", - "setting_notifications_total_progress_subtitle": "Dul chun cinn foriomlán uaslódála (críochnaithe/sócmhainní iomlána)", - "setting_notifications_total_progress_title": "Taispeáin dul chun cinn iomlán an chúltaca sa chúlra", - "setting_video_viewer_auto_play_subtitle": "Tosaíonn siad ag seinm físeáin go huathoibríoch nuair a osclaítear iad", - "setting_video_viewer_auto_play_title": "Físeáin uath-sheinm", - "setting_video_viewer_looping_title": "Lúbáil", - "setting_video_viewer_original_video_subtitle": "Agus físeán á shruthú ón bhfreastalaí, seinn an bunleagan fiú nuair a bhíonn traschód ar fáil. D’fhéadfadh sé seo maolánú a chur faoi deara. Seinntear físeáin atá ar fáil go háitiúil i gcáilíocht bhunaidh beag beann ar an socrú seo.", - "setting_video_viewer_original_video_title": "Fórsaigh físeán bunaidh", - "settings": "Socruithe", - "settings_require_restart": "Atosaigh Immich le do thoil chun an socrú seo a chur i bhfeidhm", - "settings_saved": "Socruithe sábháilte", - "setup_pin_code": "Socraigh cód PIN", - "share": "Comhroinn", - "share_action_prompt": "Sócmhainní comhroinnte {count}", - "share_add_photos": "Cuir grianghraif leis", - "share_assets_selected": "{count} roghnaithe", - "share_dialog_preparing": "Ag ullmhú...", - "share_link": "Comhroinn an Nasc", - "shared": "Roinnte", - "shared_album_activities_input_disable": "Tá trácht díchumasaithe", - "shared_album_activity_remove_content": "Ar mhaith leat an ghníomhaíocht seo a scriosadh?", - "shared_album_activity_remove_title": "Scrios Gníomhaíocht", - "shared_album_section_people_action_error": "Earráid ag fágáil/ag baint den albam", - "shared_album_section_people_action_leave": "Bain úsáideoir den albam", - "shared_album_section_people_action_remove_user": "Bain úsáideoir den albam", - "shared_album_section_people_title": "DAOINE", - "shared_by": "Roinnte ag", - "shared_by_user": "Roinnte ag {user}", - "shared_by_you": "Roinnte agatsa", - "shared_from_partner": "Grianghraif ó {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Uaslódáilte", - "shared_link_app_bar_title": "Naisc Chomhroinnte", - "shared_link_clipboard_copied_massage": "Cóipeáilte chuig an ghearrthaisce", - "shared_link_clipboard_text": "Nasc: {link}\nPasfhocal: {password}", - "shared_link_create_error": "Earráid agus nasc comhroinnte á chruthú", - "shared_link_custom_url_description": "Rochtain a fháil ar an nasc comhroinnte seo le URL saincheaptha", - "shared_link_edit_description_hint": "Cuir isteach cur síos an chomhroinnte", - "shared_link_edit_expire_after_option_day": "1 lá", - "shared_link_edit_expire_after_option_days": "{count} lá", - "shared_link_edit_expire_after_option_hour": "1 uair an chloig", - "shared_link_edit_expire_after_option_hours": "{count} uair an chloig", - "shared_link_edit_expire_after_option_minute": "1 nóiméad", - "shared_link_edit_expire_after_option_minutes": "{count} nóiméad", - "shared_link_edit_expire_after_option_months": "{count} míonna", - "shared_link_edit_expire_after_option_year": "{count} bliain", - "shared_link_edit_password_hint": "Cuir isteach an focal faire comhroinnte", - "shared_link_edit_submit_button": "Nuashonraigh an nasc", - "shared_link_error_server_url_fetch": "Ní féidir url an fhreastalaí a fháil", - "shared_link_expires_day": "Éagaíonn i gceann {count} lá", - "shared_link_expires_days": "Éagaíonn i gceann {count} laethanta", - "shared_link_expires_hour": "Éagaíonn i gceann {count} uair", - "shared_link_expires_hours": "Éagaíonn i gceann {count} uair an chloig", - "shared_link_expires_minute": "Éagaíonn i gceann {count} nóiméad", - "shared_link_expires_minutes": "Éagaíonn i gceann {count} nóiméid", - "shared_link_expires_never": "Éagaíonn ∞", - "shared_link_expires_second": "Éagaíonn i gceann {count} soicind", - "shared_link_expires_seconds": "Éagaíonn i gceann {count} soicindí", - "shared_link_individual_shared": "Aonair roinnte", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Bainistigh naisc chomhroinnte", - "shared_link_options": "Roghanna nasc comhroinnte", - "shared_link_password_description": "Éiligh pasfhocal chun rochtain a fháil ar an nasc comhroinnte seo", - "shared_links": "Naisc chomhroinnte", - "shared_links_description": "Roinn grianghraif agus físeáin le nasc", - "shared_photos_and_videos_count": "{assetCount, plural, other {# grianghraif & físeáin chomhroinnte.}}", - "shared_with_me": "Roinnte liom", - "shared_with_partner": "Roinnte le {partner}", - "sharing": "Roinnt", - "sharing_enter_password": "Cuir isteach an focal faire le do thoil chun an leathanach seo a fheiceáil.", - "sharing_page_album": "Albaim chomhroinnte", - "sharing_page_description": "Cruthaigh albaim chomhroinnte chun grianghraif agus físeáin a roinnt le daoine i do líonra.", - "sharing_page_empty_list": "LIOSTA FOLAMH", - "sharing_sidebar_description": "Taispeáin nasc chuig Comhroinnt sa bharra taoibh", - "sharing_silver_appbar_create_shared_album": "Albam comhroinnte nua", - "sharing_silver_appbar_share_partner": "Comhroinn le comhpháirtí", - "shift_to_permanent_delete": "brúigh ⇧ chun sócmhainn a scriosadh go buan", - "show_album_options": "Taispeáin roghanna albaim", - "show_albums": "Taispeáin albaim", - "show_all_people": "Taispeáin gach duine", - "show_and_hide_people": "Taispeáin & folaigh daoine", - "show_file_location": "Taispeáin suíomh an chomhaid", - "show_gallery": "Taispeáin gailearaí", - "show_hidden_people": "Taispeáin daoine i bhfolach", - "show_in_timeline": "Taispeáin san amlíne", - "show_in_timeline_setting_description": "Taispeáin grianghraif agus físeáin ón úsáideoir seo i do líne ama", - "show_keyboard_shortcuts": "Taispeáin aicearraí méarchláir", - "show_metadata": "Taispeáin meiteashonraí", - "show_or_hide_info": "Taispeáin nó folaigh faisnéis", - "show_password": "Taispeáin an focal faire", - "show_person_options": "Taispeáin roghanna duine", - "show_progress_bar": "Taispeáin an Barra Dul Chun Cinn", - "show_schema": "Taispeáin scéim", - "show_search_options": "Taispeáin roghanna cuardaigh", - "show_shared_links": "Taispeáin naisc chomhroinnte", - "show_slideshow_transition": "Taispeáin an t-aistriú sleamhnán", - "show_supporter_badge": "Suaitheantas tacaíochta", - "show_supporter_badge_description": "Taispeáin suaitheantas tacaíochta", - "show_text_recognition": "Taispeáin aitheantas téacs", - "show_text_search_menu": "Taispeáin roghchlár cuardaigh téacs", - "shuffle": "Meascán", - "sidebar": "Barra taobh", - "sidebar_display_description": "Taispeáin nasc chuig an radharc sa bharra taoibh", - "sign_out": "Sínigh Amach", - "sign_up": "Cláraigh", - "size": "Méid", - "skip_to_content": "Léim go dtí an t-ábhar", - "skip_to_folders": "Léim go dtí na fillteáin", - "skip_to_tags": "Léim go dtí na clibeanna", - "slideshow": "Sleamhnán", - "slideshow_repeat": "Athdhéan an sleamhnán", - "slideshow_repeat_description": "Lúb ar ais go dtí an tús nuair a chríochnaíonn an sleamhnán", - "slideshow_settings": "Socruithe sleamhnán", - "sort_albums_by": "Sórtáil albaim de réir...", - "sort_created": "Dáta cruthaithe", - "sort_items": "Líon na míreanna", - "sort_modified": "Dáta athraithe", - "sort_newest": "An grianghraf is nuaí", - "sort_oldest": "An grianghraf is sine", - "sort_people_by_similarity": "Sórtáil daoine de réir cosúlachta", - "sort_recent": "Grianghraf is déanaí", - "sort_title": "Teideal", - "source": "Foinse", - "stack": "Cruach", - "stack_action_prompt": "{count} cruachta", - "stack_duplicates": "Cruach dúblach", - "stack_select_one_photo": "Roghnaigh príomhghrianghraf amháin don chairn", - "stack_selected_photos": "Cruach na grianghraif roghnaithe", - "stacked_assets_count": "Cruachta {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "stacktrace": "Rian cruachta", - "start": "Tosaigh", - "start_date": "Dáta tosaithe", - "start_date_before_end_date": "Ní mór don dáta tosaigh a bheith roimh an dáta deiridh", - "state": "Stát", - "status": "Stádas", - "stop_casting": "Stop a chur leis an gcraoladh", - "stop_motion_photo": "Grianghraf Stop-Ghluaiseachta", - "stop_photo_sharing": "Stop a chur le do ghrianghraif a roinnt?", - "stop_photo_sharing_description": "Ní bheidh {partner} in ann rochtain a fháil ar do ghrianghraif a thuilleadh.", - "stop_sharing_photos_with_user": "Stop a roinnt do ghrianghraif leis an úsáideoir seo", - "storage": "Spás stórála", - "storage_label": "Lipéad stórála", - "storage_quota": "Cuóta Stórála", - "storage_usage": "{used} de {available} úsáidte", - "submit": "Cuir isteach", - "success": "Rath", - "suggestions": "Moltaí", - "sunrise_on_the_beach": "Éirí gréine ar an trá", - "support": "Tacaíocht", - "support_and_feedback": "Tacaíocht & Aiseolas", - "support_third_party_description": "Rinne tríú páirtí pacáiste de do shuiteáil Immich. D’fhéadfadh sé gur an pacáiste sin ba chúis le fadhbanna a bhíonn agat, mar sin tabhair ceisteanna dóibh ar dtús trí na naisc thíos a úsáid.", - "swap_merge_direction": "Malartaigh treo an chumaisc", - "sync": "Sioncrónaigh", - "sync_albums": "Sioncrónaigh albaim", - "sync_albums_manual_subtitle": "Sioncrónaigh na físeáin agus na grianghraif uile a uaslódáiltear leis na halbaim chúltaca roghnaithe", - "sync_local": "Sioncrónaigh Áitiúil", - "sync_remote": "Sioncrónaigh Cianda", - "sync_status": "Stádas Sioncrónaithe", - "sync_status_subtitle": "Féach ar an gcóras sioncrónaithe agus bainistigh é", - "sync_upload_album_setting_subtitle": "Cruthaigh agus uaslódáil do ghrianghraif agus físeáin chuig na halbaim roghnaithe ar Immich", - "tag": "Clib", - "tag_assets": "Sócmhainní clibe", - "tag_created": "Clib cruthaithe: {tag}", - "tag_feature_description": "Ag brabhsáil grianghraif agus físeáin grúpáilte de réir topaicí clibeanna loighciúla", - "tag_not_found_question": "Ní féidir clib a aimsiú? Cruthaigh clib nua.", - "tag_people": "Daoine a Chlibeáil", - "tag_updated": "Clib nuashonraithe: {tag}", - "tagged_assets": "Clibeáilte {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "tags": "Clibeanna", - "tap_to_run_job": "Tapáil chun an post a rith", - "template": "Teimpléad", - "text_recognition": "Aitheantas téacs", - "theme": "Téama", - "theme_selection": "Rogha téama", - "theme_selection_description": "Socraigh an téama go huathoibríoch go geal nó dorcha bunaithe ar rogha chórais do bhrabhsálaí", - "theme_setting_asset_list_storage_indicator_title": "Taispeáin táscaire stórála ar thíleanna sócmhainní", - "theme_setting_asset_list_tiles_per_row_title": "Líon na sócmhainní in aghaidh an ró ({count})", - "theme_setting_colorful_interface_subtitle": "Cuir an dath príomhúil i bhfeidhm ar dhromchlaí cúlra.", - "theme_setting_colorful_interface_title": "Comhéadan ildaite", - "theme_setting_image_viewer_quality_subtitle": "Coigeartaigh cáilíocht an bhreathnóra íomhá mionsonraithe", - "theme_setting_image_viewer_quality_title": "Cáilíocht breathnóra íomhá", - "theme_setting_primary_color_subtitle": "Roghnaigh dath do phríomhghníomhartha agus do bhéimnithe.", - "theme_setting_primary_color_title": "Dath príomhúil", - "theme_setting_system_primary_color_title": "Úsáid dath an chórais", - "theme_setting_system_theme_switch": "Uathoibríoch (Lean socruithe an chórais)", - "theme_setting_theme_subtitle": "Roghnaigh socrú téama an aip", - "theme_setting_three_stage_loading_subtitle": "D’fhéadfadh luchtú trí chéim feidhmíocht an luchtaithe a mhéadú ach bíonn ualach líonra i bhfad níos airde mar thoradh air", - "theme_setting_three_stage_loading_title": "Cumasaigh luchtú trí chéim", - "then": "Ansin", - "they_will_be_merged_together": "Cuirfear le chéile iad", - "third_party_resources": "Acmhainní Tríú Páirtí", - "time": "Am", - "time_based_memories": "Cuimhní cinn atá bunaithe ar am", - "time_based_memories_duration": "Líon na soicindí chun gach íomhá a thaispeáint.", - "timeline": "Amlíne", - "timezone": "Crios ama", - "to_archive": "Cartlann", - "to_change_password": "Athraigh an focal faire", - "to_favorite": "Is fearr leat", - "to_login": "Logáil Isteach", - "to_multi_select": "chun ilroghnú", - "to_parent": "Téigh chuig tuismitheoir", - "to_select": "a roghnú", - "to_trash": "Bruscar", - "toggle_settings": "Socruithe a scoránaigh", - "toggle_theme_description": "Téama a scoránaigh", - "total": "Iomlán", - "total_usage": "Úsáid iomlán", - "trash": "Bruscar", - "trash_action_prompt": "{count} bogtha chuig an mbruscar", - "trash_all": "Bruscar Uile", - "trash_count": "Bruscar {count, number}", - "trash_delete_asset": "Bruscar/Scrios Sócmhainn", - "trash_emptied": "Bruscar folamh", - "trash_no_results_message": "Beidh grianghraif agus físeáin atá scriosta le feiceáil anseo.", - "trash_page_delete_all": "Scrios Gach Rud", - "trash_page_empty_trash_dialog_content": "Ar mhaith leat do shócmhainní bruscair a fholmhú? Bainfear na míreanna seo go buan as Immich", - "trash_page_info": "Scriosfar míreanna atá curtha sa bhruscar go buan tar éis {days} lá", - "trash_page_no_assets": "Gan aon sócmhainní bruscar", - "trash_page_restore_all": "Athchóirigh Gach Rud", - "trash_page_select_assets_btn": "Roghnaigh sócmhainní", - "trash_page_title": "Bruscar ({count})", - "trashed_items_will_be_permanently_deleted_after": "Scriosfar míreanna atá curtha sa bhruscar go buan i ndiaidh {days, plural, one {# lá} other {# laethanta}}.", - "trigger": "Spriocdhíriú", - "trigger_asset_uploaded": "Sócmhainn Uaslódáilte", - "trigger_asset_uploaded_description": "Spreagtha nuair a uaslódálfar sócmhainn nua", - "trigger_description": "Imeacht a chuireann tús leis an sreabhadh oibre", - "trigger_person_recognized": "Duine Aitheanta", - "trigger_person_recognized_description": "Spreagtar nuair a bhraitear duine", - "trigger_type": "Cineál spreagthóra", - "troubleshoot": "Fabhtcheartaigh", - "type": "Cineál", - "unable_to_change_pin_code": "Ní féidir an cód PIN a athrú", - "unable_to_check_version": "Ní féidir leagan an aip ná an fhreastalaí a sheiceáil", - "unable_to_setup_pin_code": "Ní féidir cód PIN a shocrú", - "unarchive": "Díchartlannaigh", - "unarchive_action_prompt": "Baineadh {count} as an gCartlann", - "unarchived_count": "{count, plural, other {Díchartlannaithe #}}", - "undo": "Cuir ar ceal", - "unfavorite": "Bain de na cinn is fearr leat", - "unfavorite_action_prompt": "Baineadh {count} as na Ceanáin", - "unhide_person": "Nocht an duine", - "unknown": "Anaithnid", - "unknown_country": "Tír Anaithnid", - "unknown_date": "Dáta anaithnid", - "unknown_year": "Bliain Anaithnid", - "unlimited": "Gan teorainn", - "unlink_motion_video": "Dínasc físeán gluaisne", - "unlink_oauth": "Dínasc OAuth", - "unlinked_oauth_account": "Cuntas OAuth neamhnasctha", - "unmute_memories": "Díbholg Cuimhní", - "unnamed_album": "Albam Gan Ainm", - "unnamed_album_delete_confirmation": "An bhfuil tú cinnte gur mian leat an t-albam seo a scriosadh?", - "unnamed_share": "Comhroinn Gan Ainm", - "unsaved_change": "Athrú neamhshábháilte", - "unselect_all": "Díroghnaigh gach rud", - "unselect_all_duplicates": "Díroghnaigh gach dúblach", - "unselect_all_in": "Díroghnaigh gach rud i {group}", - "unstack": "Dí-chruachadh", - "unstack_action_prompt": "{count} gan chruachadh", - "unstacked_assets_count": "Gan chruachadh {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "unsupported_field_type": "Cineál réimse nach dtacaítear leis", - "untagged": "Gan Chlib", - "untitled_workflow": "Sreabhadh oibre gan teideal", - "up_next": "Ar aghaidh", - "update_location_action_prompt": "Nuashonraigh suíomh na sócmhainní roghnaithe {count} le:", - "updated_at": "Nuashonraithe", - "updated_password": "Pasfhocal nuashonraithe", - "upload": "Uaslódáil", - "upload_concurrency": "Uaslódáil comhthráthacht", - "upload_details": "Sonraí Uaslódála", - "upload_dialog_info": "Ar mhaith leat cúltaca den Shócmhainn/na Sócmhainní roghnaithe a dhéanamh chuig an bhfreastalaí?", - "upload_dialog_title": "Uaslódáil Sócmhainn", - "upload_error_with_count": "Earráid uaslódála le haghaidh {count, plural, one {# sócmhainn} other {# sócmhainní}}", - "upload_errors": "Uaslódáil críochnaithe le {count, plural, one {# earráid} other {# earráidí}}, athnuachan an leathanach chun sócmhainní uaslódála nua a fheiceáil.", - "upload_finished": "Uaslódáil críochnaithe", - "upload_progress": "Fágtha {remaining, number} - Próiseáilte {processed, number}/{total, number}", - "upload_skipped_duplicates": "Scipeáilte {count, plural, one {# sócmhainn dhúblach} other {# sócmhainní dúblacha}}", - "upload_status_duplicates": "Dúblaigh", - "upload_status_errors": "Earráidí", - "upload_status_uploaded": "Uaslódáilte", - "upload_success": "Uaslódáil rathúil, athnuachan an leathanach chun sócmhainní uaslódála nua a fheiceáil.", - "upload_to_immich": "Uaslódáil chuig Immich ({count})", - "uploading": "Ag uaslódáil", - "uploading_media": "Ag uaslódáil meán", - "url": "URL", - "usage": "Úsáid", - "use_biometric": "Úsáid bithmhéadrach", - "use_current_connection": "Úsáid an nasc reatha", - "use_custom_date_range": "Úsáid raon dáta saincheaptha ina ionad", - "user": "Úsáideoir", - "user_has_been_deleted": "Scriosadh an t-úsáideoir seo.", - "user_id": "Aitheantas Úsáideora", - "user_liked": "Thaitin {user} leis {type, select, photo {an grianghraf seo} video {an físeán seo} asset {an tsócmhainn seo} other {é}}", - "user_pin_code_settings": "Cód PIN", - "user_pin_code_settings_description": "Bainistigh do chód PIN", - "user_privacy": "Príobháideacht Úsáideora", - "user_purchase_settings": "Ceannaigh", - "user_purchase_settings_description": "Bainistigh do cheannachán", - "user_role_set": "Socraigh {user} mar {role}", - "user_usage_detail": "Sonraí úsáide úsáideora", - "user_usage_stats": "Staitisticí úsáide cuntais", - "user_usage_stats_description": "Féach ar staitisticí úsáide cuntais", - "username": "Ainm úsáideora", - "users": "Úsáideoirí", - "users_added_to_album_count": "Cuireadh {count, plural, one {# úsáideoir} other {# úsáideoirí}} leis an albam", - "utilities": "Fóntais", - "validate": "Bailíochtú", - "validate_endpoint_error": "Cuir isteach URL bailí le do thoil", - "validation_error": "Earráid bailíochtaithe", - "variables": "Athróga", - "version": "Leagan", - "version_announcement_closing": "Do chara, Alex", - "version_announcement_message": "Haigh a chairde! Tá leagan nua de Immich ar fáil. Glac roinnt ama le do thoil chun na nótaí scaoilte a léamh lena chinntiú go bhfuil do shocrú cothrom le dáta chun aon mhíchumraíochtaí a chosc, go háirithe má úsáideann tú WatchTower nó aon mheicníocht a láimhseálann nuashonrú uathoibríoch ar d'eispéireas Immich.", - "version_history": "Stair Leagan", - "version_history_item": "Suiteáladh {version} ar {date}", - "video": "Físeán", - "video_hover_setting": "Seinn mionsamhail físe ar an luchóg", - "video_hover_setting_description": "Seinn mionsamhail físe nuair a bhíonn an luch ag luascadh thar an mír. Fiú nuair atá sé díchumasaithe, is féidir athsheinm a thosú tríd an luch a luascadh thar an deilbhín seinnte.", - "videos": "Físeáin", - "videos_count": "{count, plural, one {# Físeán} other {# Físeáin}}", - "videos_only": "Físeáin amháin", - "view": "Amharc", - "view_album": "Féach ar an Albam", - "view_all": "Féach ar Gach Rud", - "view_all_users": "Féach ar gach úsáideoir", - "view_asset_owners": "Féach ar úinéirí sócmhainní", - "view_details": "Féach Sonraí", - "view_in_timeline": "Féach san amlíne", - "view_link": "Féach ar an nasc", - "view_links": "Féach naisc", - "view_name": "Amharc", - "view_next_asset": "Féach ar an gcéad tsócmhainn eile", - "view_previous_asset": "Féach ar an tsócmhainn roimhe seo", - "view_qr_code": "Féach ar an gcód QR", - "view_similar_photos": "Féach ar ghrianghraif chomhchosúla", - "view_stack": "Féach ar an gCruach", - "view_user": "Féach ar an Úsáideoir", - "viewer_remove_from_stack": "Bain den Chruach", - "viewer_stack_use_as_main_asset": "Úsáid mar Phríomhshócmhainn", - "viewer_unstack": "Dí-Chruach", - "visibility_changed": "Athraíodh infheictheacht do {count, plural, one {# duine} other {# daoine}}", - "visual": "Amhairc", - "visual_builder": "Tógálaí amhairc", - "waiting": "Ag fanacht", - "waiting_count": "Ag fanacht: {count}", - "warning": "Rabhadh", - "week": "Seachtain", - "welcome": "Fáilte", - "welcome_to_immich": "Fáilte go hImmich", - "width": "Leithead", - "wifi_name": "Ainm Wi-Fi", - "workflow_delete_prompt": "An bhfuil tú cinnte gur mian leat an sreabhadh oibre seo a scriosadh?", - "workflow_deleted": "Sreabhadh oibre scriosta", - "workflow_description": "Cur síos ar an sreabhadh oibre", - "workflow_info": "Eolas faoin sreabhadh oibre", - "workflow_json": "Sreabhadh Oibre JSON", - "workflow_json_help": "Cuir cumraíocht an tsreabha oibre in eagar i bhformáid JSON. Déanfar athruithe a shioncronú leis an tógálaí amhairc.", - "workflow_name": "Ainm an tsreafa oibre", - "workflow_navigation_prompt": "An bhfuil tú cinnte gur mian leat imeacht gan do chuid athruithe a shábháil?", - "workflow_summary": "Achoimre ar an sreabhadh oibre", - "workflow_update_success": "Nuashonraíodh an sreabhadh oibre go rathúil", - "workflow_updated": "Sreabhadh oibre nuashonraithe", - "workflows": "Sreafaí oibre", - "workflows_help_text": "Uathoibríonn sreafaí oibre gníomhartha ar do shócmhainní bunaithe ar spreagthóirí agus scagairí", - "wrong_pin_code": "Cód PIN mícheart", - "year": "Bliain", - "years_ago": "{years, plural, one {# bliain} other {# blianta}} ó shin", - "yes": "Tá", - "you_dont_have_any_shared_links": "Níl aon naisc chomhroinnte agat", - "your_wifi_name": "Ainm do Wi-Fi", - "zero_to_clear_rating": "brúigh 0 chun rátáil sócmhainne a ghlanadh", - "zoom_image": "Íomhá Zúmáil", - "zoom_to_bounds": "Zúmáil go dtí na teorainneacha" -} +{} diff --git a/i18n/gl.json b/i18n/gl.json index f3717259ee..0967ef424b 100644 --- a/i18n/gl.json +++ b/i18n/gl.json @@ -1,2298 +1 @@ -{ - "about": "Acerca de", - "account": "Conta", - "account_settings": "Configuración da conta", - "acknowledge": "De acordo", - "action": "Acción", - "action_common_update": "Actualizar", - "action_description": "Un conxunto de accións a levar a cabo nos recursos filtrados", - "actions": "Accións", - "active": "Activo", - "active_count": "Activo:{count}", - "activity": "Actividade", - "activity_changed": "A actividade está {enabled, select, true {activada} other {desactivada}}", - "add": "Engadir", - "add_a_description": "Engadir unha descrición", - "add_a_location": "Engadir unha localización", - "add_a_name": "Engadir un nome", - "add_a_title": "Engadir un título", - "add_action": "Engadir acción", - "add_action_description": "Faga click para engadir unha acción a realizar", - "add_birthday": "Engadir aniversario", - "add_endpoint": "Engadir punto final", - "add_exclusion_pattern": "Engadir patrón de exclusión", - "add_filter": "Engadir filtro", - "add_filter_description": "Faga click para engadir unha condición de filtrado", - "add_location": "Engadir localización", - "add_more_users": "Engadir máis usuarios", - "add_partner": "Engadir compañeiro/a", - "add_path": "Engadir ruta", - "add_photos": "Engadir fotos", - "add_tag": "Engadir etiqueta", - "add_to": "Engadir a…", - "add_to_album": "Engadir ao álbum", - "add_to_album_bottom_sheet_added": "Engadido a {album}", - "add_to_album_bottom_sheet_already_exists": "Xa está en {album}", - "add_to_album_bottom_sheet_some_local_assets": "Algunhas imaxes ou ficheiros locais non se puideron engadir ao álbum", - "add_to_album_toggle": "Alternar selección para {album}", - "add_to_albums": "Engadir a álbums", - "add_to_albums_count": "Engadir a {count} álbums", - "add_to_bottom_bar": "Engadir a", - "add_to_shared_album": "Engadir ao álbum compartido", - "add_upload_to_stack": "Engade cargar á pila", - "add_url": "Engadir URL", - "add_workflow_step": "Engadir paso de fluxo de traballo", - "added_to_archive": "Engadido ao arquivo", - "added_to_favorites": "Engadido a favoritos", - "added_to_favorites_count": "Engadíronse {count, number} a favoritos", - "admin": { - "add_exclusion_pattern_description": "Engadir patróns de exclusión. Admítense caracteres comodín usando *, ** e ?. Para ignorar todos os ficheiros en calquera directorio chamado \"Raw\", empregue \"**/Raw/**\". Para ignorar todos os ficheiros que rematen en \".tif\", use \"**/*.tif\". Para ignorar unha ruta absoluta, empregue \"/ruta/a/ignorar/**\".", - "admin_user": "Usuario administrador", - "asset_offline_description": "Este activo da biblioteca externa xa non se atopa no disco e moveuse ao lixo. Se o ficheiro se moveu dentro da biblioteca, comprobe a súa liña de tempo para o novo activo correspondente. Para restaurar este activo, asegúrese de que Immich poida acceder á ruta do ficheiro a continuación e escanee a biblioteca.", - "authentication_settings": "Configuración de autenticación", - "authentication_settings_description": "Xestionar contrasinal, OAuth e outras configuracións de autenticación", - "authentication_settings_disable_all": "Está seguro de que quere desactivar todos os métodos de inicio de sesión? O inicio de sesión desactivarase completamente.", - "authentication_settings_reenable": "Para reactivalo, use un Comando de servidor.", - "background_task_job": "Tarefas en segundo plano", - "backup_database": "Crear unha copia da base de datos", - "backup_database_enable_description": "Activar a copia de seguridade da base de datos", - "backup_keep_last_amount": "Número de copias anteriores a conservar", - "backup_onboarding_1_description": "Copia externa na nube ou noutra localización física.", - "backup_onboarding_2_description": "Copias locais en diferentes dispositivos. Isto inclúe os arquivos principais e as copias deses arquivos localmente.", - "backup_onboarding_3_description": "Copias totais da súa información, incluíndo os arquivos orixinais. Isto inclúe 1 copia externa e 2 copias locais.", - "backup_onboarding_description": "Unha estratexia de copia de seguridade 3-2-1 é recomendada para protexer os seus datos. Debería gardar copias das súas fotos/vídeos subidos así como da base de datos de Immich como unha solución de seguridade.", - "backup_onboarding_footer": "Para máis información sobre copias de seguridade de Immich, por favor use a seguinte ligazón á documentación.", - "backup_onboarding_parts_title": "Unha copia de seguridade 3-2-1 inclúe:", - "backup_onboarding_title": "Copia de seguridade", - "backup_settings": "Configuración da copia da base de datos", - "backup_settings_description": "Xestionar a configuración da copia da base de datos.", - "cleared_jobs": "Traballos borrados para: {job}", - "config_set_by_file": "A configuración establécese actualmente mediante un ficheiro de configuración", - "confirm_delete_library": "Está seguro de que quere eliminar a biblioteca {library}?", - "confirm_delete_library_assets": "Está seguro de que quere eliminar esta biblioteca? Isto eliminará {count, plural, one {# activo contido} other {todos os # activos contidos}} de Immich e non se pode desfacer. Os ficheiros permanecerán no disco.", - "confirm_email_below": "Para confirmar, escriba \"{email}\" a continuación", - "confirm_reprocess_all_faces": "Está seguro de que quere reprocesar todas as caras? Isto tamén borrará as persoas nomeadas.", - "confirm_user_password_reset": "Está seguro de que quere restablecer o contrasinal de {user}?", - "confirm_user_pin_code_reset": "Está seguro de que quere restablecer o PIN de {user}?", - "copy_config_to_clipboard_description": "Copiar a configuración actual do sistema coma un obxecto JSON ao portapapeis", - "create_job": "Crear traballo", - "cron_expression": "Expresión Cron", - "cron_expression_description": "Estableza o intervalo de escaneo usando o formato cron. Para obter máis información, consulte por exemplo Crontab Guru", - "cron_expression_presets": "Preaxustes de expresión Cron", - "disable_login": "Desactivar inicio de sesión", - "duplicate_detection_job_description": "Executar aprendizaxe automática nos activos para detectar imaxes similares. Depende da Busca Intelixente", - "exclusion_pattern_description": "Os patróns de exclusión permítenlle ignorar ficheiros e cartafoles ao escanear a súa biblioteca. Isto é útil se ten cartafoles que conteñen ficheiros que non quere importar, como ficheiros RAW.", - "export_config_as_json_description": "Descarga a configuración actual coma un arquivo JSON", - "external_libraries_page_description": "Páxina da librería externa do administrador", - "face_detection": "Detección de caras", - "face_detection_description": "Detectar as caras nos activos usando aprendizaxe automática. Para vídeos, só se considera a miniatura. \"Actualizar\" (re)procesa todos os activos. \"Restablecer\" ademais borra todos os datos de caras actuais. \"Faltantes\" pon en cola os activos que aínda non foron procesados. As caras detectadas poranse en cola para o Recoñecemento Facial despois de completar a Detección de Caras, agrupándoas en persoas existentes ou novas.", - "facial_recognition_job_description": "Agrupar caras detectadas en persoas. Este paso execútase despois de completar a Detección de Caras. \"Restablecer\" (re)agrupa todas as caras. \"Faltantes\" pon en cola as caras que non teñen unha persoa asignada.", - "failed_job_command": "O comando {command} fallou para o traballo: {job}", - "force_delete_user_warning": "AVISO: Isto eliminará inmediatamente o usuario e todos os seus activos. Esta acción non se pode desfacer e os ficheiros non se poderán recuperar.", - "image_format": "Formato", - "image_format_description": "WebP produce ficheiros máis pequenos que JPEG, pero é máis lento de codificar.", - "image_fullsize_description": "Imaxe a tamaño completo con metadatos eliminados, usada ao facer zoom", - "image_fullsize_enabled": "Activar a xeración de imaxes a tamaño completo", - "image_fullsize_enabled_description": "Xerar imaxe a tamaño completo para formatos non compatibles coa web. Cando \"Preferir vista previa incrustada\" está activado, as vistas previas incrustadas utilízanse directamente sen conversión. Non afecta a formatos compatibles coa web como JPEG.", - "image_fullsize_quality_description": "Calidade da imaxe a tamaño completo de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes.", - "image_fullsize_title": "Configuración da imaxe a tamaño completo", - "image_prefer_embedded_preview": "Preferir vista previa incrustada", - "image_prefer_embedded_preview_setting_description": "Usar vistas previas incrustadas en fotos RAW como entrada para o procesamento de imaxes cando estean dispoñibles. Isto pode producir cores máis precisas para algunhas imaxes, pero a calidade da vista previa depende da cámara e a imaxe pode ter máis artefactos de compresión.", - "image_prefer_wide_gamut": "Preferir gama de cores ampla", - "image_prefer_wide_gamut_setting_description": "Usar Display P3 para as miniaturas. Isto preserva mellor a viveza das imaxes con espazos de cor amplos, pero as imaxes poden aparecer de forma diferente en dispositivos antigos cunha versión de navegador antiga. As imaxes sRGB mantéñense como sRGB para evitar cambios de cor.", - "image_preview_description": "Imaxe de tamaño medio con metadatos eliminados, usada ao ver un único activo e para aprendizaxe automática", - "image_preview_quality_description": "Calidade da vista previa de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación. Establecer un valor baixo pode afectar á calidade da aprendizaxe automática.", - "image_preview_title": "Configuración da vista previa", - "image_quality": "Calidade", - "image_resolution": "Resolución", - "image_resolution_description": "Resolucións máis altas poden preservar máis detalles pero tardan máis en codificarse, teñen tamaños de ficheiro máis grandes e poden reducir a capacidade de resposta da aplicación.", - "image_settings": "Configuración da imaxe", - "image_settings_description": "Xestionar a calidade e resolución das imaxes xeradas", - "image_thumbnail_description": "Miniatura pequena con metadatos eliminados, usada ao ver grupos de fotos como a liña de tempo principal", - "image_thumbnail_quality_description": "Calidade da miniatura de 1 a 100. Canto máis alto, mellor, pero produce ficheiros máis grandes e pode reducir a capacidade de resposta da aplicación.", - "image_thumbnail_title": "Configuración da miniatura", - "import_config_from_json_description": "Importar a configuración do sistema subindo un arquivo de configuración JSON", - "job_concurrency": "concorrencia de {job}", - "job_created": "Traballo creado", - "job_not_concurrency_safe": "Este traballo non é seguro para execución concorrente.", - "job_settings": "Configuración de traballos", - "job_settings_description": "Xestionar a concorrencia de traballos", - "jobs_delayed": "{jobCount, plural, other {# atrasados}}", - "jobs_failed": "{jobCount, plural, other {# fallados}}", - "jobs_over_time": "Traballos ao longo do tempo", - "library_created": "Biblioteca creada: {library}", - "library_deleted": "Biblioteca eliminada", - "library_details": "Detalles da biblioteca", - "library_folder_description": "Especifique un cartafol para importar. Este cartafol, incluídos os subcartafoles, analizaranse para atopar imaxes e vídeos.", - "library_remove_exclusion_pattern_prompt": "Está seguro de que quere eliminar este patrón de exclusión?", - "library_remove_folder_prompt": "Seguro que queres eliminar este cartafol importante?", - "library_scanning": "Escaneo periódico", - "library_scanning_description": "Configurar o escaneo periódico da biblioteca", - "library_scanning_enable_description": "Activar o escaneo periódico da biblioteca", - "library_settings": "Biblioteca externa", - "library_settings_description": "Xestionar a configuración da biblioteca externa", - "library_tasks_description": "Escanear bibliotecas externas en busca de activos novos e/ou modificados", - "library_updated": "Biblioteca actualizada", - "library_watching_enable_description": "Vixiar bibliotecas externas para detectar cambios nos ficheiros", - "library_watching_settings": "Vixilancia da biblioteca [EXPERIMENTAL]", - "library_watching_settings_description": "Vixiar automaticamente os ficheiros modificados", - "logging_enable_description": "Activar rexistro", - "logging_level_description": "Cando estea activado, que nivel de rexistro usar.", - "logging_settings": "Rexistro", - "machine_learning_availability_checks": "Comprobacións de dispoñibilidade", - "machine_learning_availability_checks_description": "Detectar automaticamente e preferir servidores de aprendizaxe automática dispoñibles", - "machine_learning_availability_checks_enabled": "Activar comprobacións de dispoñibilidade", - "machine_learning_availability_checks_interval": "Intervalo de comprobación", - "machine_learning_availability_checks_interval_description": "Intervalo en milisegundos entre comprobacións de dispoñibilidade", - "machine_learning_availability_checks_timeout": "Tempo de espera da solicitude", - "machine_learning_availability_checks_timeout_description": "Tempo de espera en milisegundos para as comprobacións de dispoñibilidade", - "machine_learning_clip_model": "Modelo CLIP", - "machine_learning_clip_model_description": "O nome dun modelo CLIP listado aquí. Teña en conta que debe volver executar o traballo 'Busca Intelixente' para todas as imaxes ao cambiar un modelo.", - "machine_learning_duplicate_detection": "Detección de duplicados", - "machine_learning_duplicate_detection_enabled": "Activar detección de duplicados", - "machine_learning_duplicate_detection_enabled_description": "Se está desactivado, os activos exactamente idénticos aínda se eliminarán.", - "machine_learning_duplicate_detection_setting_description": "Usar incrustacións CLIP para atopar posibles duplicados", - "machine_learning_enabled": "Activar aprendizaxe automática", - "machine_learning_enabled_description": "Se está desactivado, todas as funcións de aprendizaxe automática desactivaranse independentemente da configuración a continuación.", - "machine_learning_facial_recognition": "Recoñecemento facial", - "machine_learning_facial_recognition_description": "Detectar, recoñecer e agrupar caras en imaxes", - "machine_learning_facial_recognition_model": "Modelo de recoñecemento facial", - "machine_learning_facial_recognition_model_description": "Os modelos están listados en orde descendente de tamaño. Os modelos máis grandes son máis lentos e usan máis memoria, pero producen mellores resultados. Teña en conta que debe volver executar o traballo de Detección de Caras para todas as imaxes ao cambiar un modelo.", - "machine_learning_facial_recognition_setting": "Activar recoñecemento facial", - "machine_learning_facial_recognition_setting_description": "Se está desactivado, as imaxes non se codificarán para o recoñecemento facial e non encherán a sección Persoas na páxina Explorar.", - "machine_learning_max_detection_distance": "Distancia máxima de detección", - "machine_learning_max_detection_distance_description": "Distancia máxima entre dúas imaxes para consideralas duplicadas, variando de 0.001 a 0.1. Valores máis altos detectarán máis duplicados, pero poden resultar en falsos positivos.", - "machine_learning_max_recognition_distance": "Distancia máxima de recoñecemento", - "machine_learning_max_recognition_distance_description": "Distancia máxima entre dúas caras para ser consideradas a mesma persoa, variando de 0 a 2. Baixar isto pode evitar etiquetar dúas persoas como a mesma persoa, mentres que subilo pode evitar etiquetar a mesma persoa como dúas persoas diferentes. Teña en conta que é máis fácil fusionar dúas persoas que dividir unha persoa en dúas, así que erre polo lado dun limiar máis baixo cando sexa posible.", - "machine_learning_min_detection_score": "Puntuación mínima de detección", - "machine_learning_min_detection_score_description": "Puntuación mínima de confianza para que unha cara sexa detectada, de 0 a 1. Valores máis baixos detectarán máis caras pero poden resultar en falsos positivos.", - "machine_learning_min_recognized_faces": "Mínimo de caras recoñecidas", - "machine_learning_min_recognized_faces_description": "O número mínimo de caras recoñecidas para que se cree unha persoa. Aumentar isto fai que o Recoñecemento Facial sexa máis preciso a costa de aumentar a posibilidade de que unha cara non se asigne a unha persoa.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Emprega aprendizaxe automática para recoñecer texto en imaxes", - "machine_learning_ocr_enabled": "Activa o OCR", - "machine_learning_ocr_enabled_description": "Se está desactivado, as imaxes non se someterán a recoñecemento de texto.", - "machine_learning_ocr_max_resolution": "Resolución máxima", - "machine_learning_ocr_max_resolution_description": "As previsualizacións por enriba desta resolución redimensionaranse mantendo a proporción. Os valores máis altos son máis precisos, pero tardan máis en procesarse e usan máis memoria.", - "machine_learning_ocr_min_detection_score": "Puntuación mínima de detección", - "machine_learning_ocr_min_detection_score_description": "Puntuación mínima de confianza para que o texto sexa detectado, de 0 a 1. Os valores máis baixos detectarán máis texto, pero poden producir falsos positivos.", - "machine_learning_ocr_min_recognition_score": "Puntuación mínima de recoñecemento", - "machine_learning_ocr_min_score_recognition_description": "Puntuación mínima de confianza para que o texto detectado sexa recoñecido, de 0 a 1. Os valores máis baixos recoñecerán máis texto, pero poden producir falsos positivos.", - "machine_learning_ocr_model": "Modelo OCR", - "machine_learning_ocr_model_description": "Os modelos de servidor son máis precisos que os modelos móbiles, pero tardan máis en procesarse e usan máis memoria.", - "machine_learning_settings": "Configuración da Aprendizaxe Automática", - "machine_learning_settings_description": "Xestionar funcións e configuracións da aprendizaxe automática", - "machine_learning_smart_search": "Busca Intelixente", - "machine_learning_smart_search_description": "Buscar imaxes semanticamente usando incrustacións CLIP", - "machine_learning_smart_search_enabled": "Activar busca intelixente", - "machine_learning_smart_search_enabled_description": "Se está desactivado, as imaxes non se codificarán para a busca intelixente.", - "machine_learning_url_description": "A URL do servidor de aprendizaxe automática. Se se proporciona máis dunha URL, intentarase con cada servidor un por un ata que un responda correctamente, en orde do primeiro ao último. Os servidores que non respondan ignoraranse temporalmente ata que volvan estar en liña.", - "maintenance_settings": "Mantemento", - "maintenance_settings_description": "Poñer Immich en modo mantemento.", - "maintenance_start": "Comezar modo de mantemento", - "maintenance_start_error": "Erro ao iniciar o modo de mantemento.", - "manage_concurrency": "Xestionar Concorrencia", - "manage_concurrency_description": "Navegar á páxina de traballos para xestionar a concorrencia de trabalhos", - "manage_log_settings": "Xestionar configuración de rexistro", - "map_dark_style": "Estilo escuro", - "map_enable_description": "Activar funcións do mapa", - "map_gps_settings": "Configuración do Mapa e GPS", - "map_gps_settings_description": "Xestionar a configuración do Mapa e GPS (Xeocodificación Inversa)", - "map_implications": "A función do mapa depende dun servizo de teselas externo (tiles.immich.cloud)", - "map_light_style": "Estilo claro", - "map_manage_reverse_geocoding_settings": "Xestionar configuración de Xeocodificación Inversa", - "map_reverse_geocoding": "Xeocodificación Inversa", - "map_reverse_geocoding_enable_description": "Activar xeocodificación inversa", - "map_reverse_geocoding_settings": "Configuración da Xeocodificación Inversa", - "map_settings": "Mapa", - "map_settings_description": "Xestionar a configuración do mapa", - "map_style_description": "URL a un tema de mapa style.json", - "memory_cleanup_job": "Limpeza de recordos", - "memory_generate_job": "Xeración de recordos", - "metadata_extraction_job": "Extraer metadatos", - "metadata_extraction_job_description": "Extraer información de metadatos de cada activo, como GPS, caras e resolución", - "metadata_faces_import_setting": "Activar importación de caras", - "metadata_faces_import_setting_description": "Importar caras dos datos EXIF da imaxe e ficheiros sidecar", - "metadata_settings": "Configuración de Metadatos", - "metadata_settings_description": "Xestionar a configuración de metadatos", - "migration_job": "Migración", - "migration_job_description": "Migrar miniaturas de activos e caras á última estrutura de cartafoles", - "nightly_tasks_cluster_faces_setting_description": "Executar recoñecemento facial nas novas caras detectadas", - "nightly_tasks_cluster_new_faces_setting": "Agrupar novas caras", - "nightly_tasks_database_cleanup_setting": "Tarefas de limpeza da base de datos", - "nightly_tasks_database_cleanup_setting_description": "Limpar información vella e obsoleta da base de datos", - "nightly_tasks_generate_memories_setting": "Xerar recordos", - "nightly_tasks_generate_memories_setting_description": "Crear novos recordos a partir dos activos", - "nightly_tasks_missing_thumbnails_setting": "Xerar as miniaturas que faltan", - "nightly_tasks_missing_thumbnails_setting_description": "Poñer en cola os arquivos sen miniaturas para a súa xeración", - "nightly_tasks_settings": "Configuración das tarefas nocturnas", - "nightly_tasks_settings_description": "Administrar as tarefas nocturnas", - "nightly_tasks_start_time_setting": "Hora de inicio", - "nightly_tasks_start_time_setting_description": "A hora na que o servidor comeza a executar as tarefas nocturnas", - "nightly_tasks_sync_quota_usage_setting": "Sincronizar uso de cota", - "nightly_tasks_sync_quota_usage_setting_description": "Actualizar a cota de almacenamento do usuario, en base ao uso actual", - "no_paths_added": "Non se engadiron rutas", - "no_pattern_added": "Non se engadiu ningún patrón", - "note_apply_storage_label_previous_assets": "Nota: Para aplicar a Etiqueta de Almacenamento a activos cargados previamente, execute o", - "note_cannot_be_changed_later": "NOTA: Isto non se pode cambiar máis tarde!", - "notification_email_from_address": "Enderezo do remitente", - "notification_email_from_address_description": "Enderezo de correo do remitente, por exemplo: \"Servidor de Fotos Immich noreply@exemplo.com\". Asegúrese de empregar un enderezo dende o que teña permiso para enviar correos.", - "notification_email_host_description": "Host do servidor de correo electrónico (p. ex. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorar erros de certificado", - "notification_email_ignore_certificate_errors_description": "Ignorar erros de validación do certificado TLS (non recomendado)", - "notification_email_password_description": "Contrasinal a usar ao autenticarse co servidor de correo electrónico", - "notification_email_port_description": "Porto do servidor de correo electrónico (p. ex. 25, 465 ou 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Usar SMTPS (SMTP sobre TLS)", - "notification_email_sent_test_email_button": "Enviar correo de proba e gardar", - "notification_email_setting_description": "Configuración para enviar notificacións por correo electrónico", - "notification_email_test_email": "Enviar correo de proba", - "notification_email_test_email_failed": "Erro ao enviar correo de proba, comprobe os seus valores", - "notification_email_test_email_sent": "Enviouse un correo electrónico de proba a {email}. Por favor, comprobe a súa caixa de entrada.", - "notification_email_username_description": "Nome de usuario a usar ao autenticarse co servidor de correo electrónico", - "notification_enable_email_notifications": "Activar notificacións por correo electrónico", - "notification_settings": "Configuración de Notificacións", - "notification_settings_description": "Xestionar a configuración de notificacións, incluído o correo electrónico", - "oauth_auto_launch": "Lanzamento automático", - "oauth_auto_launch_description": "Iniciar o fluxo de inicio de sesión OAuth automaticamente ao navegar á páxina de inicio de sesión", - "oauth_auto_register": "Rexistro automático", - "oauth_auto_register_description": "Rexistrar automaticamente novos usuarios despois de iniciar sesión con OAuth", - "oauth_button_text": "Texto do botón", - "oauth_client_secret_description": "Requirido se o provedor OAuth non admite PKCE (Proof Key for Code Exchange)", - "oauth_enable_description": "Iniciar sesión con OAuth", - "oauth_mobile_redirect_uri": "URI de redirección móbil", - "oauth_mobile_redirect_uri_override": "Substitución de URI de redirección móbil", - "oauth_mobile_redirect_uri_override_description": "Activar cando o provedor OAuth non permite un URI móbil, como ''{callback}''", - "oauth_role_claim": "Declaración de rol", - "oauth_role_claim_description": "Conceder acceso de administrador automaticamente segundo a presenza desta declaración. A declaración pode ter os valores 'user' ou 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Xestionar a configuración de inicio de sesión OAuth", - "oauth_settings_more_details": "Para máis detalles sobre esta función, consulte a documentación.", - "oauth_storage_label_claim": "Declaración de etiqueta de almacenamento", - "oauth_storage_label_claim_description": "Establecer automaticamente a etiqueta de almacenamento do usuario ao valor desta declaración.", - "oauth_storage_quota_claim": "Declaración de cota de almacenamento", - "oauth_storage_quota_claim_description": "Establecer automaticamente a cota de almacenamento do usuario ao valor desta declaración.", - "oauth_storage_quota_default": "Cota de almacenamento predeterminada (GiB)", - "oauth_storage_quota_default_description": "Cota en GiB a empregar cando non se proporciona ningunha declaración.", - "oauth_timeout": "Tempo máximo de espera da solicitude", - "oauth_timeout_description": "Tempo máximo de espera para as solicitudes en milisegundos", - "ocr_job_description": "Emprega aprendizaxe automática para recoñecer texto en imaxes", - "password_enable_description": "Iniciar sesión con correo electrónico e contrasinal", - "password_settings": "Inicio de sesión con contrasinal", - "password_settings_description": "Xestionar a configuración de inicio de sesión con contrasinal", - "paths_validated_successfully": "Todas as rutas validadas correctamente", - "person_cleanup_job": "Limpeza de persoas", - "queue_details": "Detalles da Cola", - "queues": "Colas de traballos", - "queues_page_description": "Páxina de colas de traballo (admin)", - "quota_size_gib": "Tamaño da cota (GiB)", - "refreshing_all_libraries": "Actualizando todas as bibliotecas", - "registration": "Rexistro do administrador", - "registration_description": "Dado que vostede é o primeiro usuario no sistema, asignaráselle como Administrador e será responsable das tarefas administrativas. Os usuarios adicionais serán creados por vostede.", - "remove_failed_jobs": "Eliminar os traballos con erros", - "require_password_change_on_login": "Requirir que o usuario cambie o contrasinal no primeiro inicio de sesión", - "reset_settings_to_default": "Restablecer a configuración aos valores predeterminados", - "reset_settings_to_recent_saved": "Restablecer á configuración gardada recentemente", - "scanning_library": "Escaneando biblioteca", - "search_jobs": "Buscar traballos…", - "send_welcome_email": "Enviar correo electrónico de benvida", - "server_external_domain_settings": "Dominio externo", - "server_external_domain_settings_description": "Dominio para ligazóns públicas compartidas, incluíndo http(s)://", - "server_public_users": "Usuarios públicos", - "server_public_users_description": "Todos os usuarios (nome e correo electrónico) lístanse ao engadir un usuario a álbums compartidos. Cando está desactivado, a lista de usuarios só estará dispoñible para os usuarios administradores.", - "server_settings": "Configuración do servidor", - "server_settings_description": "Xestionar a configuración do servidor", - "server_stats_page_description": "Páxina de estatísticas do servidor (admin)", - "server_welcome_message": "Mensaxe de benvida", - "server_welcome_message_description": "Unha mensaxe que se mostra na páxina de inicio de sesión.", - "settings_page_description": "Páxina de axustes (admin)", - "sidecar_job": "Metadatos Sidecar", - "sidecar_job_description": "Descubrir ou sincronizar metadatos sidecar desde o sistema de ficheiros", - "slideshow_duration_description": "Número de segundos para mostrar cada imaxe", - "smart_search_job_description": "Executar aprendizaxe automática nos activos para soportar a busca intelixente", - "storage_template_date_time_description": "A marca de tempo de creación do activo úsase para a información de data e hora", - "storage_template_date_time_sample": "Tempo de mostra {date}", - "storage_template_enable_description": "Activar o motor de modelos de almacenamento", - "storage_template_hash_verification_enabled": "Verificación de hash activada", - "storage_template_hash_verification_enabled_description": "Activa a verificación de hash. Non desactive isto a menos que estea seguro das implicacións", - "storage_template_migration": "Migración do modelo de almacenamento", - "storage_template_migration_description": "Aplicar o {template} actual aos activos cargados previamente", - "storage_template_migration_info": "O modelo de almacenamento converterá todas as extensións a minúsculas. Os cambios no modelo só se aplicarán aos activos novos. Para aplicar retroactivamente o modelo aos activos cargados previamente, execute o {job}.", - "storage_template_migration_job": "Traballo de Migración do Modelo de Almacenamento", - "storage_template_more_details": "Para máis detalles sobre esta función, consulte o Modelo de Almacenamento e as súas implicacións", - "storage_template_onboarding_description_v2": "Cando está activada, esta función organizará automaticamente os ficheiros segundo un modelo definido polo usuario. Para máis información, consulte a documentación.", - "storage_template_path_length": "Límite aproximado da lonxitude da ruta: {length, number}/{limit, number}", - "storage_template_settings": "Modelo de Almacenamento", - "storage_template_settings_description": "Xestionar a estrutura de cartafoles e o nome de ficheiro do activo cargado", - "storage_template_user_label": "{label} é a Etiqueta de Almacenamento do usuario", - "system_settings": "Configuración do Sistema", - "tag_cleanup_job": "Limpeza de etiquetas", - "template_email_available_tags": "Pode usar as seguintes variables no seu modelo: {tags}", - "template_email_if_empty": "Se o modelo está baleiro, usarase o correo electrónico predeterminado.", - "template_email_invite_album": "Modelo de Invitación a Álbum", - "template_email_preview": "Vista previa", - "template_email_settings": "Modelos de Correo Electrónico", - "template_email_update_album": "Modelo de Actualización de Álbum", - "template_email_welcome": "Modelo de correo electrónico de benvida", - "template_settings": "Modelos de Notificación", - "template_settings_description": "Xestionar modelos personalizados para notificacións", - "theme_custom_css_settings": "CSS Personalizado", - "theme_custom_css_settings_description": "As Follas de Estilo en Cascada (CSS) permiten personalizar o deseño de Immich.", - "theme_settings": "Configuración do Tema", - "theme_settings_description": "Xestionar a personalización da interface web de Immich", - "thumbnail_generation_job": "Xerar Miniaturas", - "thumbnail_generation_job_description": "Xerar miniaturas grandes, pequenas e borrosas para cada activo, así como miniaturas para cada persoa", - "transcoding_acceleration_api": "API de aceleración", - "transcoding_acceleration_api_description": "A API que interactuará co seu dispositivo para acelerar a transcodificación. Esta configuración é de 'mellor esforzo': recurrirá á transcodificación por software en caso de fallo. VP9 pode funcionar ou non dependendo do seu hardware.", - "transcoding_acceleration_nvenc": "NVENC (require GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (require CPU Intel de 7ª xeración ou posterior)", - "transcoding_acceleration_rkmpp": "RKMPP (só en SOCs Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Códecs de audio aceptados", - "transcoding_accepted_audio_codecs_description": "Seleccione que códecs de audio non necesitan ser transcodificados. Só se usa para certas políticas de transcodificación.", - "transcoding_accepted_containers": "Contedores aceptados", - "transcoding_accepted_containers_description": "Seleccione que formatos de contedor non necesitan ser remuxados a MP4. Só se usa para certas políticas de transcodificación.", - "transcoding_accepted_video_codecs": "Códecs de vídeo aceptados", - "transcoding_accepted_video_codecs_description": "Seleccione que códecs de vídeo non necesitan ser transcodificados. Só se usa para certas políticas de transcodificación.", - "transcoding_advanced_options_description": "Opcións que a maioría dos usuarios non deberían necesitar cambiar", - "transcoding_audio_codec": "Códec de audio", - "transcoding_audio_codec_description": "Opus é a opción de maior calidade, pero ten menor compatibilidade con dispositivos ou software antigos.", - "transcoding_bitrate_description": "Vídeos cun bitrate superior ao máximo ou que non estean nun formato aceptado", - "transcoding_codecs_learn_more": "Para saber máis sobre a terminoloxía usada aquí, consulte a documentación de FFmpeg para o códec H.264, o códec HEVC e o códec VP9.", - "transcoding_constant_quality_mode": "Modo de calidade constante", - "transcoding_constant_quality_mode_description": "ICQ é mellor que CQP, pero algúns dispositivos de aceleración por hardware non admiten este modo. Establecer esta opción preferirá o modo especificado ao usar codificación baseada na calidade. Ignorado por NVENC xa que non admite ICQ.", - "transcoding_constant_rate_factor": "Factor de taxa constante (-crf)", - "transcoding_constant_rate_factor_description": "Nivel de calidade do vídeo. Valores típicos son 23 para H.264, 28 para HEVC, 31 para VP9 e 35 para AV1. Canto máis baixo, mellor, pero produce ficheiros máis grandes.", - "transcoding_disabled_description": "Non transcodificar ningún vídeo; pode romper a reprodución nalgúns clientes", - "transcoding_encoding_options": "Opcións de Codificación", - "transcoding_encoding_options_description": "Establecer códecs, resolución, calidade e outras opcións para os vídeos codificados", - "transcoding_hardware_acceleration": "Aceleración por Hardware", - "transcoding_hardware_acceleration_description": "Experimental: transcodificación máis rápida, pero pode reducir a calidade ao mesmo bitrate", - "transcoding_hardware_decoding": "Decodificación por hardware", - "transcoding_hardware_decoding_setting_description": "Activa a aceleración de extremo a extremo en lugar de só acelerar a codificación. Pode non funcionar en todos os vídeos.", - "transcoding_max_b_frames": "Máximo de B-frames", - "transcoding_max_b_frames_description": "Valores máis altos melloran a eficiencia da compresión, pero ralentizan a codificación. Pode non ser compatible coa aceleración por hardware en dispositivos máis antigos. 0 desactiva os B-frames, mentres que -1 establece este valor automaticamente.", - "transcoding_max_bitrate": "Bitrate máximo", - "transcoding_max_bitrate_description": "Establecer unha taxa de bits máxima pode facer que os tamaños dos ficheiros sexan máis predicibles a un custo menor na calidade. En 720p, os valores típicos son 2600 kbit/s para VP9 ou HEVC, ou 4500 kbit/s para H.264. Desactivado se se establece en 0. Cando non se especifica unha unidade, asúmese k (para kbit/s); polo tanto, 5000, 5000k e 5M (para Mbit/s) son equivalentes.", - "transcoding_max_keyframe_interval": "Intervalo máximo de fotogramas clave", - "transcoding_max_keyframe_interval_description": "Establece a distancia máxima de fotogramas entre fotogramas clave. Valores máis baixos empeoran a eficiencia da compresión, pero melloran os tempos de busca e poden mellorar a calidade en escenas con movemento rápido. 0 establece este valor automaticamente.", - "transcoding_optimal_description": "Vídeos cunha resolución superior á obxectivo ou que non estean nun formato aceptado", - "transcoding_policy": "Política de Transcodificación", - "transcoding_policy_description": "Establecer cando se transcodificará un vídeo", - "transcoding_preferred_hardware_device": "Dispositivo de hardware preferido", - "transcoding_preferred_hardware_device_description": "Aplícase só a VAAPI e QSV. Establece o nodo dri usado para a transcodificación por hardware.", - "transcoding_preset_preset": "Preaxuste (-preset)", - "transcoding_preset_preset_description": "Velocidade de compresión. Preaxustes máis lentos producen ficheiros máis pequenos e aumentan a calidade ao apuntar a un certo bitrate. VP9 ignora velocidades superiores a 'faster'.", - "transcoding_reference_frames": "Fotogramas de referencia", - "transcoding_reference_frames_description": "O número de fotogramas aos que facer referencia ao comprimir un fotograma dado. Valores máis altos melloran a eficiencia da compresión, pero ralentizan a codificación. 0 establece este valor automaticamente.", - "transcoding_required_description": "Só vídeos que non estean nun formato aceptado", - "transcoding_settings": "Configuración da Transcodificación de Vídeo", - "transcoding_settings_description": "Xestionar que vídeos transcodificar e como procesalos", - "transcoding_target_resolution": "Resolución obxectivo", - "transcoding_target_resolution_description": "Resolucións máis altas poden preservar máis detalles pero tardan máis en codificarse, teñen tamaños de ficheiro máis grandes e poden reducir a capacidade de resposta da aplicación.", - "transcoding_temporal_aq": "AQ Temporal", - "transcoding_temporal_aq_description": "Aplícase só a NVENC. A Cuantización Adaptativa Temporal aumenta a calidade das escenas con moito detalle e pouco movemento. Pode que non sexa compatible con dispositivos máis antigos.", - "transcoding_threads": "Fíos", - "transcoding_threads_description": "Valores máis altos levan a unha codificación máis rápida, pero deixan menos marxe para que o servidor procese outras tarefas mentres está activo. Este valor non debería ser maior que o número de núcleos da CPU. Maximiza a utilización se se establece en 0.", - "transcoding_tone_mapping": "Mapeo de tons", - "transcoding_tone_mapping_description": "Intenta preservar a aparencia dos vídeos HDR cando se converten a SDR. Cada algoritmo fai diferentes compromisos para cor, detalle e brillo. Hable preserva o detalle, Mobius preserva a cor e Reinhard preserva o brillo.", - "transcoding_transcode_policy": "Política de transcodificación", - "transcoding_transcode_policy_description": "Política para cando un vídeo debe ser transcodificado. Os vídeos HDR sempre serán transcodificados (excepto se a transcodificación está desactivada).", - "transcoding_two_pass_encoding": "Codificación en dous pasos", - "transcoding_two_pass_encoding_setting_description": "Transcodificar en dous pasos para producir vídeos codificados de mellor calidade. Cando o bitrate máximo está activado (requirido para que funcione con H.264 e HEVC), este modo usa un rango de bitrate baseado no bitrate máximo e ignora CRF. Para VP9, pódese usar CRF se o bitrate máximo está desactivado.", - "transcoding_video_codec": "Códec de vídeo", - "transcoding_video_codec_description": "VP9 ten alta eficiencia e compatibilidade web, pero tarda máis en transcodificarse. HEVC ten un rendemento similar, pero ten menor compatibilidade web. H.264 é amplamente compatible e rápido de transcodificar, pero produce ficheiros moito máis grandes. AV1 é o códec máis eficiente pero carece de soporte en dispositivos máis antigos.", - "trash_enabled_description": "Activar funcións do Lixo", - "trash_number_of_days": "Número de días", - "trash_number_of_days_description": "Número de días para manter os activos no lixo antes de eliminalos permanentemente", - "trash_settings": "Configuración do Lixo", - "trash_settings_description": "Xestionar a configuración do lixo", - "unlink_all_oauth_accounts": "Desvincular todas as contas OAuth", - "unlink_all_oauth_accounts_description": "Lembre desvincular todas as contas OAuth antes de migrar a un novo provedor.", - "unlink_all_oauth_accounts_prompt": "Está seguro de que quere desvincular todas as contas OAuth? Isto reiniciará o ID de OAuth de cada usuario e non se poderá desfacer.", - "user_cleanup_job": "Limpeza de usuarios", - "user_delete_delay": "A conta e os activos de {user} programaranse para a súa eliminación permanente en {delay, plural, one {# día} other {# días}}.", - "user_delete_delay_settings": "Atraso na eliminación", - "user_delete_delay_settings_description": "Número de días despois da eliminación para eliminar permanentemente a conta e os activos dun usuario. O traballo de eliminación de usuarios execútase á medianoite para comprobar os usuarios que están listos para a eliminación. Os cambios nesta configuración avaliaranse na próxima execución.", - "user_delete_immediately": "A conta e os activos de {user} poranse en cola para a súa eliminación permanente inmediatamente.", - "user_delete_immediately_checkbox": "Poñer en cola o usuario e os activos para a súa eliminación inmediata", - "user_details": "Detalles do usuario", - "user_management": "Xestión de Usuarios", - "user_password_has_been_reset": "Restableceuse o contrasinal do usuario:", - "user_password_reset_description": "Proporcione o contrasinal temporal ao usuario e infórmelle de que necesitará cambiar o contrasinal no seu próximo inicio de sesión.", - "user_restore_description": "Restaurarase a conta de {user}.", - "user_restore_scheduled_removal": "Restaurar usuario - eliminación programada o {date, date, long}", - "user_settings": "Configuración do Usuario", - "user_settings_description": "Xestionar a configuración do usuario", - "user_successfully_removed": "O usuario {email} foi eliminado satisfactoriamente.", - "users_page_description": "Páxina de usuarios administradores", - "version_check_enabled_description": "Activar comprobación de versión", - "version_check_implications": "A función de comprobación de versión depende da comunicación periódica con github.com", - "version_check_settings": "Comprobación de Versión", - "version_check_settings_description": "Activar/desactivar a notificación de nova versión", - "video_conversion_job": "Transcodificar vídeos", - "video_conversion_job_description": "Transcodificar vídeos para unha maior compatibilidade con navegadores e dispositivos" - }, - "admin_email": "Correo electrónico do administrador", - "admin_password": "Contrasinal do administrador", - "administration": "Administración", - "advanced": "Avanzado", - "advanced_settings_enable_alternate_media_filter_subtitle": "Use esta opción para filtrar medios durante a sincronización baseándose en criterios alternativos. Só probe isto se ten problemas coa aplicación para detectar todos os álbums.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Usar filtro alternativo de sincronización de álbums do dispositivo", - "advanced_settings_log_level_title": "Nivel de rexistro: {level}", - "advanced_settings_prefer_remote_subtitle": "Algúns dispositivos tardan moito en cargar as miniaturas de ficheiros locais. Active esta opción para cargar imaxes remotas no seu lugar.", - "advanced_settings_prefer_remote_title": "Preferir imaxes remotas", - "advanced_settings_proxy_headers_subtitle": "Definir cabeceiras de proxy que Immich debería enviar con cada solicitude de rede", - "advanced_settings_proxy_headers_title": "Cabeceiras de proxy personalizadas [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Activa o modo de só lectura, no que as fotos só se poden visualizar; opcións como seleccionar varias imaxes, compartir, enviar a outros dispositivos ou eliminar están deshabilitadas. Active/desactive o modo de só lectura a través do avatar do usuario na pantalla principal", - "advanced_settings_readonly_mode_title": "Modo de só lectura", - "advanced_settings_self_signed_ssl_subtitle": "Omite a verificación do certificado SSL para o punto final do servidor. Requirido para certificados autofirmados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados [EXPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Eliminar ou restaurar automaticamente un activo neste dispositivo cando esa acción se realiza na web", - "advanced_settings_sync_remote_deletions_title": "Sincronizar eliminacións remotas [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Configuración de usuario avanzado", - "advanced_settings_troubleshooting_subtitle": "Activar funcións adicionais para a resolución de problemas", - "advanced_settings_troubleshooting_title": "Resolución de problemas", - "age_months": "Idade: {months, plural, one {# mes} other {# meses}}", - "age_year_months": "Idade: 1 ano e {months, plural, one {# mes} other {# meses}}", - "age_years": "Idade: {years, plural, one {# ano} other {# anos}}", - "album": "Álbum", - "album_added": "Álbum engadido", - "album_added_notification_setting_description": "Recibir unha notificación por correo electrónico cando sexa engadido a un álbum compartido", - "album_cover_updated": "Portada do álbum actualizada", - "album_delete_confirmation": "Está seguro de que quere eliminar o álbum {album}?", - "album_delete_confirmation_description": "Se este álbum está compartido, outros usuarios non poderán acceder a el.", - "album_deleted": "Álbum eliminado", - "album_info_card_backup_album_excluded": "EXCLUÍDO", - "album_info_card_backup_album_included": "INCLUÍDO", - "album_info_updated": "Información do álbum actualizada", - "album_leave": "Saír do álbum?", - "album_leave_confirmation": "Está seguro de que quere saír de {album}?", - "album_name": "Nome do Álbum", - "album_options": "Opcións do álbum", - "album_remove_user": "Eliminar usuario?", - "album_remove_user_confirmation": "Está seguro de que quere eliminar a {user}?", - "album_search_not_found": "Non se atoparon álbums que coincidan coa súa busca", - "album_selected": "Álbum seleccionado", - "album_share_no_users": "Parece que compartiu este álbum con todos os usuarios ou non ten ningún usuario co que compartir.", - "album_summary": "Resumo do álbum", - "album_updated": "Álbum actualizado", - "album_updated_setting_description": "Recibir unha notificación por correo electrónico cando un álbum compartido teña novos activos", - "album_user_left": "Saíu de {album}", - "album_user_removed": "Eliminouse a {user}", - "album_viewer_appbar_delete_confirm": "Está seguro de que quere eliminar este álbum da súa conta?", - "album_viewer_appbar_share_err_delete": "Erro ao eliminar o álbum", - "album_viewer_appbar_share_err_leave": "Erro ao saír do álbum", - "album_viewer_appbar_share_err_remove": "Houbo problemas ao eliminar activos do álbum", - "album_viewer_appbar_share_err_title": "Erro ao cambiar o título do álbum", - "album_viewer_appbar_share_leave": "Saír do álbum", - "album_viewer_appbar_share_to": "Compartir con", - "album_viewer_page_share_add_users": "Engadir usuarios", - "album_with_link_access": "Permitir que calquera persoa coa ligazón vexa fotos e persoas neste álbum.", - "albums": "Álbums", - "albums_count": "{count, plural, one {{count, number} Álbum} other {{count, number} Álbums}}", - "albums_default_sort_order": "Orde de clasificación predeterminada do álbum", - "albums_default_sort_order_description": "Orde inicial dos ficheiros ao crear novos álbums.", - "albums_feature_description": "Coleccións de ficheiros que se poden compartir con outros usuarios.", - "albums_on_device_count": "Álbums no dispositivo ({count})", - "albums_selected": "{count, plural, one {# álbum selected} other {# álbums selected}}", - "all": "Todo", - "all_albums": "Todos os álbums", - "all_people": "Todas as persoas", - "all_videos": "Todos os vídeos", - "allow_dark_mode": "Permitir modo escuro", - "allow_edits": "Permitir edicións", - "allow_public_user_to_download": "Permitir que o usuario público descargue", - "allow_public_user_to_upload": "Permitir que o usuario público cargue", - "allowed": "Permitido", - "alt_text_qr_code": "Imaxe de código QR", - "anti_clockwise": "Sentido antihorario", - "api_key": "Chave API", - "api_key_description": "Este valor só se mostrará unha vez. Asegúrese de copialo antes de pechar a xanela.", - "api_key_empty": "O nome da súa chave API non pode estar baleiro", - "api_keys": "Chaves API", - "app_architecture_variant": "Variante (Arquitectura)", - "app_bar_signout_dialog_content": "Está seguro de que quere pechar sesión?", - "app_bar_signout_dialog_ok": "Si", - "app_bar_signout_dialog_title": "Pechar sesión", - "app_download_links": "Ligazóns de Descarga da Aplicación", - "app_settings": "Configuración da Aplicación", - "app_stores": "Tendas de aplicacións", - "app_update_available": "Hai unha actualización da aplicación dispoñible", - "appears_in": "Aparece en", - "apply_count": "Aplicar ({count, number})", - "archive": "Arquivo", - "archive_action_prompt": "{count} engadido(s) ao Arquivo", - "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", - "archive_page_no_archived_assets": "Non se atoparon activos arquivados", - "archive_page_title": "Arquivo ({count})", - "archive_size": "Tamaño do arquivo", - "archive_size_description": "Configurar o tamaño do arquivo para descargas (en GiB)", - "archived": "Arquivado", - "archived_count": "{count, plural, other {Arquivados #}}", - "are_these_the_same_person": "Son estas a mesma persoa?", - "are_you_sure_to_do_this": "Está seguro de que quere facer isto?", - "array_field_not_fully_supported": "Os campos tipo array precisan edición manual no JSON", - "asset_action_delete_err_read_only": "Non se poden eliminar activo(s) de só lectura, omitindo", - "asset_action_share_err_offline": "Non se poden obter activo(s) fóra de liña, omitindo", - "asset_added_to_album": "Engadido ao álbum", - "asset_adding_to_album": "Engadindo ao álbum…", - "asset_created": "Recurso creado", - "asset_description_updated": "A descrición do activo actualizouse", - "asset_filename_is_offline": "O activo {filename} está fóra de liña", - "asset_has_unassigned_faces": "O activo ten caras sen asignar", - "asset_hashing": "Calculando hash…", - "asset_list_group_by_sub_title": "Agrupar por", - "asset_list_layout_settings_dynamic_layout_title": "Deseño dinámico", - "asset_list_layout_settings_group_automatically": "Automático", - "asset_list_layout_settings_group_by": "Agrupar activos por", - "asset_list_layout_settings_group_by_month_day": "Mes + día", - "asset_list_layout_sub_title": "Deseño", - "asset_list_settings_subtitle": "Configuración do deseño da grella de fotos", - "asset_list_settings_title": "Grella de Fotos", - "asset_offline": "Activo Fóra de Liña", - "asset_offline_description": "Este activo externo xa non se atopa no disco. Por favor, contacte co seu administrador de Immich para obter axuda.", - "asset_restored_successfully": "Activo restaurado correctamente", - "asset_skipped": "Omitido", - "asset_skipped_in_trash": "No lixo", - "asset_trashed": "Ficheiro enviado ao lixo", - "asset_troubleshoot": "Solución de problemas do ficheiro", - "asset_uploaded": "Subido", - "asset_uploading": "Subindo…", - "asset_viewer_settings_subtitle": "Xestionar a súa configuración do visor da galería", - "asset_viewer_settings_title": "Visor de Activos", - "assets": "Activos", - "assets_added_count": "Engadido {count, plural, one {# activo} other {# activos}}", - "assets_added_to_album_count": "Engadido {count, plural, one {# activo} other {# activos}} ao álbum", - "assets_added_to_albums_count": "Engadido {assetTotal, plural, one {# ficheiro} other {# ficheiros}} a {albumTotal, plural, one {# álbum} other {# álbums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {O ficheiro non se pode engadir} other {Os ficheiros non se poden engadir}} ao álbum", - "assets_cannot_be_added_to_albums": "{count, plural, one {O ficheiro non se pode engadir} other {Os ficheiros non se poden engadir}} a ningún dos álbums", - "assets_count": "{count, plural, one {# activo} other {# activos}}", - "assets_deleted_permanently": "{count} activo(s) eliminado(s) permanentemente", - "assets_deleted_permanently_from_server": "{count} activo(s) eliminado(s) permanentemente do servidor Immich", - "assets_downloaded_failed": "{count, plural, one {Descargouse # ficheiro - fallou 1 ficheiro} other {Descargáronse # ficheiros - fallaron {error} ficheiros}}", - "assets_downloaded_successfully": "{count, plural, one {Descargouse # ficheiro correctamente} other {Descargáronse # ficheiros correctamente}}", - "assets_moved_to_trash_count": "Movido {count, plural, one {# activo} other {# activos}} ao lixo", - "assets_permanently_deleted_count": "Eliminados permanentemente {count, plural, one {# activo} other {# activos}}", - "assets_removed_count": "Eliminados {count, plural, one {# activo} other {# activos}}", - "assets_removed_permanently_from_device": "{count} activo(s) eliminado(s) permanentemente do seu dispositivo", - "assets_restore_confirmation": "Está seguro de que quere restaurar todos os activos no lixo? Non pode desfacer esta acción! Teña en conta que calquera activo fóra de liña non pode ser restaurado desta maneira.", - "assets_restored_count": "Restaurados {count, plural, one {# activo} other {# activos}}", - "assets_restored_successfully": "{count} activo(s) restaurado(s) correctamente", - "assets_trashed": "{count} activo(s) movido(s) ao lixo", - "assets_trashed_count": "Movido {count, plural, one {# activo} other {# activos}} ao lixo", - "assets_trashed_from_server": "{count} activo(s) movido(s) ao lixo desde o servidor Immich", - "assets_were_part_of_album_count": "{count, plural, one {O activo xa era} other {Os activos xa eran}} parte do álbum", - "assets_were_part_of_albums_count": "{count, plural, one {O ficheiro xa estaba} other {Os ficheiros xa estaban}} neses álbums", - "authorized_devices": "Dispositivos Autorizados", - "automatic_endpoint_switching_subtitle": "Conectar localmente a través da wifi designada cando estea dispoñible e usar conexións alternativas noutros lugares", - "automatic_endpoint_switching_title": "Cambio automático de URL", - "autoplay_slideshow": "Reprodución automática da presentación", - "back": "Atrás", - "back_close_deselect": "Atrás, pechar ou deseleccionar", - "background_backup_running_error": "A copia de seguridade en segundo plano está en curso, non se pode iniciar unha copia manual", - "background_location_permission": "Permiso de localización en segundo plano", - "background_location_permission_content": "Para cambiar de rede cando se executa en segundo plano, Immich debe ter *sempre* acceso á localización precisa para que a aplicación poida ler o nome da rede wifi", - "background_options": "Opcións en segundo plano", - "backup": "Copia de Seguridade", - "backup_album_selection_page_albums_device": "Álbums no dispositivo ({count})", - "backup_album_selection_page_albums_tap": "Tocar para incluír, dobre toque para excluír", - "backup_album_selection_page_assets_scatter": "Os activos poden estar dispersos por varios álbums. Polo tanto, os álbums poden incluírse ou excluírse durante o proceso de copia de seguridade.", - "backup_album_selection_page_select_albums": "Seleccionar álbums", - "backup_album_selection_page_selection_info": "Información da selección", - "backup_album_selection_page_total_assets": "Total de activos únicos", - "backup_albums_sync": "Sincronización de álbums da copia de seguridade", - "backup_all": "Todo", - "backup_background_service_backup_failed_message": "Erro ao facer copia de seguridade dos activos. Reintentando…", - "backup_background_service_complete_notification": "Copia de seguridade dos recursos completada", - "backup_background_service_connection_failed_message": "Erro ao conectar co servidor. Reintentando…", - "backup_background_service_current_upload_notification": "Subindo {filename}", - "backup_background_service_default_notification": "Comprobando novos activos…", - "backup_background_service_error_title": "Erro na copia de seguridade", - "backup_background_service_in_progress_notification": "Facendo copia de seguridade dos seus activos…", - "backup_background_service_upload_failure_notification": "Erro ao subir {filename}", - "backup_controller_page_albums": "Álbums da Copia de Seguridade", - "backup_controller_page_background_app_refresh_disabled_content": "Active a actualización de aplicacións en segundo plano en Axustes > Xeral > Actualización en segundo plano para usar a copia de seguridade en segundo plano.", - "backup_controller_page_background_app_refresh_disabled_title": "Actualización de aplicacións en segundo plano desactivada", - "backup_controller_page_background_app_refresh_enable_button_text": "Ir a axustes", - "backup_controller_page_background_battery_info_link": "Móstreme como", - "backup_controller_page_background_battery_info_message": "Para a mellor experiencia de copia de seguridade en segundo plano, desactive calquera optimización de batería que restrinxa a actividade en segundo plano para Immich.\n\nDado que isto é específico do dispositivo, busque a información requirida para o fabricante do seu dispositivo.", - "backup_controller_page_background_battery_info_ok": "Aceptar", - "backup_controller_page_background_battery_info_title": "Optimizacións da batería", - "backup_controller_page_background_charging": "Só mentres se carga", - "backup_controller_page_background_configure_error": "Erro ao configurar o servizo en segundo plano", - "backup_controller_page_background_delay": "Atrasar copia de seguridade de novos activos: {duration}", - "backup_controller_page_background_description": "Active o servizo en segundo plano para facer copia de seguridade automaticamente de calquera activo novo sen necesidade de abrir a aplicación", - "backup_controller_page_background_is_off": "A copia de seguridade automática en segundo plano está desactivada", - "backup_controller_page_background_is_on": "A copia de seguridade automática en segundo plano está activada", - "backup_controller_page_background_turn_off": "Desactivar servizo en segundo plano", - "backup_controller_page_background_turn_on": "Activar servizo en segundo plano", - "backup_controller_page_background_wifi": "Só con wifi", - "backup_controller_page_backup": "Copia de Seguridade", - "backup_controller_page_backup_selected": "Seleccionado: ", - "backup_controller_page_backup_sub": "Fotos e vídeos con copia de seguridade", - "backup_controller_page_created": "Creado o: {date}", - "backup_controller_page_desc_backup": "Active a copia de seguridade en primeiro plano para cargar automaticamente novos activos ao servidor ao abrir a aplicación.", - "backup_controller_page_excluded": "Excluído: ", - "backup_controller_page_failed": "Fallado ({count})", - "backup_controller_page_filename": "Nome do ficheiro: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Información da Copia de Seguridade", - "backup_controller_page_none_selected": "Ningún seleccionado", - "backup_controller_page_remainder": "Restante", - "backup_controller_page_remainder_sub": "Fotos e vídeos restantes para facer copia de seguridade da selección", - "backup_controller_page_server_storage": "Almacenamento do Servidor", - "backup_controller_page_start_backup": "Iniciar Copia de Seguridade", - "backup_controller_page_status_off": "A copia de seguridade automática en primeiro plano está desactivada", - "backup_controller_page_status_on": "A copia de seguridade automática en primeiro plano está activada", - "backup_controller_page_storage_format": "{used} de {total} usado", - "backup_controller_page_to_backup": "Álbums para facer copia de seguridade", - "backup_controller_page_total_sub": "Todas as fotos e vídeos únicos dos álbums seleccionados", - "backup_controller_page_turn_off": "Desactivar copia de seguridade en primeiro plano", - "backup_controller_page_turn_on": "Activar copia de seguridade en primeiro plano", - "backup_controller_page_uploading_file_info": "Subindo información do ficheiro", - "backup_err_only_album": "Non se pode eliminar o único álbum", - "backup_error_sync_failed": "A sincronización fallou. Non se pode procesar a copia de seguridade.", - "backup_info_card_assets": "activos", - "backup_manual_cancelled": "Cancelado", - "backup_manual_in_progress": "Subida xa en progreso. Inténteo despois dun tempo", - "backup_manual_success": "Éxito", - "backup_manual_title": "Estado da subida", - "backup_options": "Opcións de copia de seguridade", - "backup_options_page_title": "Opcións da copia de seguridade", - "backup_setting_subtitle": "Xestionar a configuración de carga en segundo plano e primeiro plano", - "backup_settings_subtitle": "Xestionar configuración de subidas", - "backup_upload_details_page_more_details": "Toca para mais detalles", - "backward": "Atrás", - "biometric_auth_enabled": "Autenticación biométrica activada", - "biometric_locked_out": "Está bloqueado da autenticación biométrica", - "biometric_no_options": "Non hai opcións biométricas dispoñibles", - "biometric_not_available": "A autenticación biométrica non está dispoñible neste dispositivo", - "birthdate_saved": "Data de nacemento gardada correctamente", - "birthdate_set_description": "A data de nacemento úsase para calcular a idade desta persoa no momento dunha foto.", - "blurred_background": "Fondo borroso", - "bugs_and_feature_requests": "Erros e Solicitudes de Funcións", - "build": "Compilación", - "build_image": "Construír Imaxe", - "bulk_delete_duplicates_confirmation": "Está seguro de que quere eliminar masivamente {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservará o activo máis grande de cada grupo e eliminará permanentemente todos os demais duplicados. Non pode desfacer esta acción!", - "bulk_keep_duplicates_confirmation": "Está seguro de que quere conservar {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto resolverá todos os grupos duplicados sen eliminar nada.", - "bulk_trash_duplicates_confirmation": "Está seguro de que quere mover masivamente ao lixo {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservará o activo máis grande de cada grupo e moverá ao lixo todos os demais duplicados.", - "buy": "Apoiar Immich", - "cache_settings_clear_cache_button": "Borrar caché", - "cache_settings_clear_cache_button_title": "Borra a caché da aplicación. Isto afectará significativamente o rendemento da aplicación ata que a caché se reconstruíu.", - "cache_settings_duplicated_assets_clear_button": "BORRAR", - "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que a aplicación ten na lista de ignorados", - "cache_settings_duplicated_assets_title": "Activos Duplicados ({count})", - "cache_settings_statistics_album": "Miniaturas da biblioteca", - "cache_settings_statistics_full": "Imaxes completas", - "cache_settings_statistics_shared": "Miniaturas de álbums compartidos", - "cache_settings_statistics_thumbnail": "Miniaturas", - "cache_settings_statistics_title": "Uso da caché", - "cache_settings_subtitle": "Controlar o comportamento da caché da aplicación móbil Immich", - "cache_settings_tile_subtitle": "Controlar o comportamento do almacenamento local", - "cache_settings_tile_title": "Almacenamento Local", - "cache_settings_title": "Configuración da Caché", - "camera": "Cámara", - "camera_brand": "Marca da cámara", - "camera_model": "Modelo da cámara", - "cancel": "Cancelar", - "cancel_search": "Cancelar busca", - "canceled": "Cancelado", - "canceling": "Cancelando", - "cannot_merge_people": "Non se poden fusionar persoas", - "cannot_undo_this_action": "Non pode desfacer esta acción!", - "cannot_update_the_description": "Non se pode actualizar a descrición", - "cast": "Enviar a dispositivo", - "cast_description": "Configurar destinos dispoñibles para enviar a dispositivo", - "change_date": "Cambiar data", - "change_description": "Cambiar descrición", - "change_display_order": "Cambiar orde de visualización", - "change_expiration_time": "Cambiar hora de caducidade", - "change_location": "Cambiar localización", - "change_name": "Cambiar nome", - "change_name_successfully": "Nome cambiado correctamente", - "change_password": "Cambiar Contrasinal", - "change_password_description": "Esta é a primeira vez que inicia sesión no sistema ou solicitouse un cambio do seu contrasinal. Introduza o novo contrasinal a continuación.", - "change_password_form_confirm_password": "Confirmar Contrasinal", - "change_password_form_description": "Ola {name},\n\nEsta é a primeira vez que inicia sesión no sistema ou solicitouse un cambio do seu contrasinal. Introduza o novo contrasinal a continuación.", - "change_password_form_log_out": "Pechar sesión en todos os outros dispositivos", - "change_password_form_log_out_description": "Recoméndase pechar sesión en todos os outros dispositivos", - "change_password_form_new_password": "Novo Contrasinal", - "change_password_form_password_mismatch": "Os contrasinais non coinciden", - "change_password_form_reenter_new_password": "Reintroducir Novo Contrasinal", - "change_pin_code": "Cambiar código PIN", - "change_trigger": "Cambiar o disparador", - "change_trigger_prompt": "Seguro que queres cambiar o disparador? Eliminará todas as accións e filtros existentes.", - "change_your_password": "Cambiar o seu contrasinal", - "changed_visibility_successfully": "Visibilidade cambiada correctamente", - "charging": "Cargando", - "charging_requirement_mobile_backup": "A copia de seguridade en segundo plano require que o dispositivo estea cargando", - "check_corrupt_asset_backup": "Comprobar copias de seguridade de activos corruptos", - "check_corrupt_asset_backup_button": "Realizar comprobación", - "check_corrupt_asset_backup_description": "Execute esta comprobación só a través da wifi e unha vez que todos os activos teñan copia de seguridade. O procedemento pode tardar uns minutos.", - "check_logs": "Comprobar Rexistros", - "checksum": "Suma de comprobación", - "choose_matching_people_to_merge": "Elixir persoas coincidentes para fusionar", - "city": "Cidade", - "clear": "Limpar", - "clear_all": "Limpar todo", - "clear_all_recent_searches": "Limpar todas as buscas recentes", - "clear_file_cache": "Limpar caché de ficheiros", - "clear_message": "Limpar mensaxe", - "clear_value": "Limpar valor", - "client_cert_dialog_msg_confirm": "Aceptar", - "client_cert_enter_password": "Introducir Contrasinal", - "client_cert_import": "Importar", - "client_cert_import_success_msg": "Certificado de cliente importado", - "client_cert_invalid_msg": "Ficheiro de certificado inválido ou contrasinal incorrecto", - "client_cert_remove_msg": "Certificado de cliente eliminado", - "client_cert_subtitle": "Soporta só o formato PKCS12 (.p12, .pfx). A importación ou eliminación de certificados está dispoñible só antes de iniciar sesión", - "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", - "clockwise": "Sentido horario", - "close": "Pechar", - "collapse": "Contraer", - "collapse_all": "Contraer todo", - "color": "Cor", - "color_theme": "Tema de cor", - "command": "Comando", - "comment_deleted": "Comentario eliminado", - "comment_options": "Opcións de comentario", - "comments_and_likes": "Comentarios e Gústames", - "comments_are_disabled": "Os comentarios están desactivados", - "common_create_new_album": "Crear novo álbum", - "completed": "Completado", - "confirm": "Confirmar", - "confirm_admin_password": "Confirmar Contrasinal do Administrador", - "confirm_delete_face": "Está seguro de que quere eliminar a cara de {name} do activo?", - "confirm_delete_shared_link": "Está seguro de que quere eliminar esta ligazón compartida?", - "confirm_keep_this_delete_others": "Todos os demais activos na pila eliminaranse excepto este activo. Está seguro de que quere continuar?", - "confirm_new_pin_code": "Confirmar novo código PIN", - "confirm_password": "Confirmar contrasinal", - "confirm_tag_face": "Quere etiquetar esta cara como {name}?", - "confirm_tag_face_unnamed": "Quere etiquetar esta cara?", - "connected_device": "Dispositivo conectado", - "connected_to": "Conectado a", - "contain": "Conter", - "context": "Contexto", - "continue": "Continuar", - "control_bottom_app_bar_create_new_album": "Crear novo álbum", - "control_bottom_app_bar_delete_from_immich": "Eliminar de Immich", - "control_bottom_app_bar_delete_from_local": "Eliminar do dispositivo", - "control_bottom_app_bar_edit_location": "Editar localización", - "control_bottom_app_bar_edit_time": "Editar Data e Hora", - "control_bottom_app_bar_share_link": "Compartir Ligazón", - "control_bottom_app_bar_share_to": "Compartir Con", - "control_bottom_app_bar_trash_from_immich": "Mover ao Lixo", - "copied_image_to_clipboard": "Imaxe copiada ao portapapeis.", - "copied_to_clipboard": "Copiado ao portapapeis!", - "copy_error": "Erro ao copiar", - "copy_file_path": "Copiar ruta do ficheiro", - "copy_image": "Copiar Imaxe", - "copy_link": "Copiar ligazón", - "copy_link_to_clipboard": "Copiar ligazón ao portapapeis", - "copy_password": "Copiar contrasinal", - "copy_to_clipboard": "Copiar ao Portapapeis", - "country": "País", - "cover": "Portada", - "covers": "Portadas", - "create": "Crear", - "create_album": "Crear álbum", - "create_album_page_untitled": "Sen título", - "create_api_key": "Crear chave API", - "create_first_workflow": "Crear o primeiro fluxo de traballo", - "create_library": "Crear Biblioteca", - "create_link": "Crear ligazón", - "create_link_to_share": "Crear ligazón para compartir", - "create_link_to_share_description": "Permitir que calquera persoa coa ligazón vexa a(s) foto(s) seleccionada(s)", - "create_new": "CREAR NOVO", - "create_new_person": "Crear nova persoa", - "create_new_person_hint": "Asignar activos seleccionados a unha nova persoa", - "create_new_user": "Crear novo usuario", - "create_shared_album_page_share_add_assets": "ENGADIR ACTIVOS", - "create_shared_album_page_share_select_photos": "Seleccionar Fotos", - "create_shared_link": "Crear ligazón compartida", - "create_tag": "Crear etiqueta", - "create_tag_description": "Crear unha nova etiqueta. Para etiquetas aniñadas, introduza a ruta completa da etiqueta incluíndo barras inclinadas.", - "create_user": "Crear usuario", - "create_workflow": "Crear fluxo de traballo", - "created": "Creado", - "created_at": "Creado", - "creating_linked_albums": "Creando álbums vinculados...", - "crop": "Recortar", - "curated_object_page_title": "Cousas", - "current_device": "Dispositivo actual", - "current_pin_code": "Código PIN actual", - "current_server_address": "Enderezo do servidor actual", - "custom_locale": "Configuración Rexional Personalizada", - "custom_locale_description": "Formatar datas e números baseándose na lingua e a rexión", - "custom_url": "URL personalizada", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Escuro", - "dark_theme": "Alternar tema escuro", - "date": "Data", - "date_after": "Data posterior a", - "date_and_time": "Data e Hora", - "date_before": "Data anterior a", - "date_format": "E, d LLL, y • H:mm", - "date_of_birth_saved": "Data de nacemento gardada correctamente", - "date_range": "Rango de datas", - "day": "Día", - "days": "Días", - "deduplicate_all": "Eliminar todos os duplicados", - "deduplication_criteria_1": "Tamaño da imaxe en bytes", - "deduplication_criteria_2": "Reconto de datos EXIF", - "deduplication_info": "Información de Deduplicación", - "deduplication_info_description": "Para preseleccionar automaticamente activos e eliminar duplicados masivamente, miramos:", - "default_locale": "Configuración Rexional Predeterminada", - "default_locale_description": "Formatar datas e números baseándose na configuración rexional do seu navegador", - "delete": "Eliminar", - "delete_action_confirmation_message": "Está seguro de que quere eliminar este ficheiro? Esta acción moverá o ficheiro ao lixo do servidor e preguntaralle se tamén quere eliminalo localmente", - "delete_action_prompt": "{count} eliminado(s)", - "delete_album": "Eliminar álbum", - "delete_api_key_prompt": "Está seguro de que quere eliminar esta chave API?", - "delete_dialog_alert": "Estes elementos eliminaranse permanentemente de Immich e do seu dispositivo", - "delete_dialog_alert_local": "Estes elementos eliminaranse permanentemente do seu dispositivo pero aínda estarán dispoñibles no servidor Immich", - "delete_dialog_alert_local_non_backed_up": "Algúns dos elementos non teñen copia de seguridade en Immich e eliminaranse permanentemente do seu dispositivo", - "delete_dialog_alert_remote": "Estes elementos eliminaranse permanentemente do servidor Immich", - "delete_dialog_ok_force": "Eliminar Igualmente", - "delete_dialog_title": "Eliminar Permanentemente", - "delete_duplicates_confirmation": "Está seguro de que quere eliminar permanentemente estes duplicados?", - "delete_face": "Eliminar cara", - "delete_key": "Eliminar chave", - "delete_library": "Eliminar Biblioteca", - "delete_link": "Eliminar ligazón", - "delete_local_action_prompt": "{count} eliminado(s) localmente", - "delete_local_dialog_ok_backed_up_only": "Eliminar Só con Copia de Seguridade", - "delete_local_dialog_ok_force": "Eliminar Igualmente", - "delete_others": "Eliminar outros", - "delete_permanently": "Eliminar permanentemente", - "delete_permanently_action_prompt": "{count} eliminado(s) permanentemente", - "delete_shared_link": "Eliminar ligazón compartida", - "delete_shared_link_dialog_title": "Eliminar Ligazón Compartida", - "delete_tag": "Eliminar etiqueta", - "delete_tag_confirmation_prompt": "Está seguro de que quere eliminar a etiqueta {tagName}?", - "delete_user": "Eliminar usuario", - "deleted_shared_link": "Ligazón compartida eliminada", - "deletes_missing_assets": "Elimina activos que faltan no disco", - "description": "Descrición", - "description_input_hint_text": "Engadir descrición...", - "description_input_submit_error": "Erro ao actualizar a descrición, comprobe o rexistro para máis detalles", - "deselect_all": "Deseleccionar todo", - "details": "Detalles", - "direction": "Dirección", - "disable": "Desactivar", - "disabled": "Desactivado", - "disallow_edits": "Non permitir edicións", - "discord": "Discord", - "discover": "Descubrir", - "discovered_devices": "Dispositivos descubertos", - "dismiss_all_errors": "Descartar todos os erros", - "dismiss_error": "Descartar erro", - "display_options": "Opcións de visualización", - "display_order": "Orde de visualización", - "display_original_photos": "Mostrar fotos orixinais", - "display_original_photos_setting_description": "Preferir mostrar a foto orixinal ao ver un activo en lugar de miniaturas cando o activo orixinal é compatible coa web. Isto pode resultar en velocidades de visualización de fotos máis lentas.", - "do_not_show_again": "Non mostrar esta mensaxe de novo", - "documentation": "Documentación", - "done": "Feito", - "download": "Descargar", - "download_action_prompt": "Descargando {count} activo(s)", - "download_canceled": "Descarga cancelada", - "download_complete": "Descarga completada", - "download_enqueue": "Descarga en cola", - "download_error": "Erro na Descarga", - "download_failed": "Descarga fallada", - "download_finished": "Descarga finalizada", - "download_include_embedded_motion_videos": "Vídeos incrustados", - "download_include_embedded_motion_videos_description": "Incluír vídeos incrustados en fotos en movemento como un ficheiro separado", - "download_notfound": "Descarga non atopada", - "download_original": "Descargar ­orixinal", - "download_paused": "Descarga pausada", - "download_settings": "Descarga", - "download_settings_description": "Xestionar configuracións relacionadas coa descarga de activos", - "download_started": "Descarga iniciada", - "download_sucess": "Descarga exitosa", - "download_sucess_android": "Os medios descargáronse en DCIM/Immich", - "download_waiting_to_retry": "Agardando para reintentar", - "downloading": "Descargando", - "downloading_asset_filename": "Descargando activo {filename}", - "downloading_media": "Descargando medios", - "drop_files_to_upload": "Solte ficheiros en calquera lugar para cargar", - "duplicates": "Duplicados", - "duplicates_description": "Resolve cada grupo indicando cales, se os houber, son duplicados", - "duration": "Duración", - "edit": "Editar", - "edit_album": "Editar álbum", - "edit_avatar": "Editar avatar", - "edit_birthday": "Editar aniversario", - "edit_date": "Editar data", - "edit_date_and_time": "Editar data e hora", - "edit_date_and_time_action_prompt": "{count} data(s) e hora(s) editada(s)", - "edit_date_and_time_by_offset": "Cambiar data por desfase", - "edit_date_and_time_by_offset_interval": "Nova franxa de datas: {from} - {to}", - "edit_description": "Editar descrición", - "edit_description_prompt": "Por favor, seleccione unha nova descrición:", - "edit_exclusion_pattern": "Editar patrón de exclusión", - "edit_faces": "Editar caras", - "edit_key": "Editar chave", - "edit_link": "Editar ligazón", - "edit_location": "Editar localización", - "edit_location_action_prompt": "{count} localización(s) editada(s)", - "edit_location_dialog_title": "Localización", - "edit_name": "Editar nome", - "edit_people": "Editar persoas", - "edit_tag": "Editar etiqueta", - "edit_title": "Editar Título", - "edit_user": "Editar usuario", - "edit_workflow": "Editar fluxo de traballo", - "editor": "Editor", - "editor_close_without_save_prompt": "Os cambios non se gardarán", - "editor_close_without_save_title": "Pechar editor?", - "email": "Correo electrónico", - "email_notifications": "Notificacións por correo electrónico", - "empty_folder": "Este cartafol está baleiro", - "empty_trash": "Baleirar lixo", - "empty_trash_confirmation": "Está seguro de que quere baleirar o lixo? Isto eliminará permanentemente todos os activos no lixo de Immich. Non pode desfacer esta acción!", - "enable": "Activar", - "enable_backup": "Activar copia de seguridade", - "enable_biometric_auth_description": "Introduza o seu código PIN para activar a autenticación biométrica", - "enabled": "Activado", - "end_date": "Data de fin", - "enqueued": "En cola", - "enter_wifi_name": "Introducir nome da wifi", - "enter_your_pin_code": "Introduza o seu código PIN", - "enter_your_pin_code_subtitle": "Introduza o seu código PIN para acceder ao cartafol bloqueado", - "error": "Erro", - "error_change_sort_album": "Erro ao cambiar a orde de clasificación do álbum", - "error_delete_face": "Erro ao eliminar a cara do activo", - "error_getting_places": "Erro ao obter lugares", - "error_loading_image": "Erro ao cargar a imaxe", - "error_loading_partners": "Erro cargando compañeiros/as: {error}", - "error_saving_image": "Erro: {error}", - "error_tag_face_bounding_box": "Erro ao etiquetar cara - non se poden obter as coordenadas da caixa delimitadora", - "error_title": "Erro - Algo saíu mal", - "errors": { - "cannot_navigate_next_asset": "Non se pode navegar ao seguinte activo", - "cannot_navigate_previous_asset": "Non se pode navegar ao activo anterior", - "cant_apply_changes": "Non se poden aplicar os cambios", - "cant_change_activity": "Non se pode {enabled, select, true {desactivar} other {activar}} a actividade", - "cant_change_asset_favorite": "Non se pode cambiar o favorito do activo", - "cant_change_metadata_assets_count": "Non se poden cambiar os metadatos de {count, plural, one {# activo} other {# activos}}", - "cant_get_faces": "Non se poden obter caras", - "cant_get_number_of_comments": "Non se pode obter o número de comentarios", - "cant_search_people": "Non se poden buscar persoas", - "cant_search_places": "Non se poden buscar lugares", - "error_adding_assets_to_album": "Erro ao engadir activos ao álbum", - "error_adding_users_to_album": "Erro ao engadir usuarios ao álbum", - "error_deleting_shared_user": "Erro ao eliminar o usuario compartido", - "error_downloading": "Erro ao descargar {filename}", - "error_hiding_buy_button": "Erro ao ocultar o botón de apoio", - "error_removing_assets_from_album": "Erro ao eliminar activos do álbum, comprobe a consola para máis detalles", - "error_selecting_all_assets": "Erro ao seleccionar todos os activos", - "exclusion_pattern_already_exists": "Este patrón de exclusión xa existe.", - "failed_to_create_album": "Erro ao crear o álbum", - "failed_to_create_shared_link": "Erro ao crear a ligazón compartida", - "failed_to_edit_shared_link": "Erro ao editar a ligazón compartida", - "failed_to_get_people": "Erro ao obter persoas", - "failed_to_keep_this_delete_others": "Erro ao conservar este activo e eliminar os outros activos", - "failed_to_load_asset": "Erro ao cargar o activo", - "failed_to_load_assets": "Erro ao cargar activos", - "failed_to_load_notifications": "Erro ao cargar as notificacións", - "failed_to_load_people": "Erro ao cargar persoas", - "failed_to_remove_product_key": "Erro ao eliminar a chave do produto", - "failed_to_reset_pin_code": "Erro ao restablecer o código PIN", - "failed_to_stack_assets": "Erro ao apilar activos", - "failed_to_unstack_assets": "Erro ao desapilar activos", - "failed_to_update_notification_status": "Erro ao actualizar o estado das notificacións", - "incorrect_email_or_password": "Correo electrónico ou contrasinal incorrectos", - "library_folder_already_exists": "Esta ruta de importación xa existe.", - "paths_validation_failed": "{paths, plural, one {# ruta fallou} other {# rutas fallaron}} na validación", - "profile_picture_transparent_pixels": "As imaxes de perfil non poden ter píxeles transparentes. Por favor, faga zoom e/ou mova a imaxe.", - "quota_higher_than_disk_size": "Estableceu unha cota superior ao tamaño do disco", - "something_went_wrong": "Algo fallou", - "unable_to_add_album_users": "Non se puideron engadir usuarios ao álbum", - "unable_to_add_assets_to_shared_link": "Non se puideron engadir activos á ligazón compartida", - "unable_to_add_comment": "Non se puido engadir o comentario", - "unable_to_add_exclusion_pattern": "Non se puido engadir o patrón de exclusión", - "unable_to_add_partners": "Non se puideron engadir compañeiros/as", - "unable_to_add_remove_archive": "Non se puido {archived, select, true {eliminar activo do} other {engadir activo ao}} arquivo", - "unable_to_add_remove_favorites": "Non se puido {favorite, select, true {engadir activo a} other {eliminar activo de}} favoritos", - "unable_to_archive_unarchive": "Non se puido {archived, select, true {arquivar} other {desarquivar}}", - "unable_to_change_album_user_role": "Non se puido cambiar o rol do usuario do álbum", - "unable_to_change_date": "Non se puido cambiar a data", - "unable_to_change_description": "Non se puido cambiar a descrición", - "unable_to_change_favorite": "Non se puido cambiar o favorito do activo", - "unable_to_change_location": "Non se puido cambiar a localización", - "unable_to_change_password": "Non se puido cambiar o contrasinal", - "unable_to_change_visibility": "Non se puido cambiar a visibilidade para {count, plural, one {# persoa} other {# persoas}}", - "unable_to_complete_oauth_login": "Non se puido completar o inicio de sesión OAuth", - "unable_to_connect": "Non se puido conectar", - "unable_to_copy_to_clipboard": "Non se puido copiar ao portapapeis, asegúrese de acceder á páxina a través de https", - "unable_to_create": "Non se pode crear o fluxo de traballo", - "unable_to_create_admin_account": "Non se puido crear a conta de administrador", - "unable_to_create_api_key": "Non se puido crear unha nova Chave API", - "unable_to_create_library": "Non se puido crear a biblioteca", - "unable_to_create_user": "Non se puido crear o usuario", - "unable_to_delete_album": "Non se puido eliminar o álbum", - "unable_to_delete_asset": "Non se puido eliminar o activo", - "unable_to_delete_assets": "Erro ao eliminar activos", - "unable_to_delete_exclusion_pattern": "Non se puido eliminar o patrón de exclusión", - "unable_to_delete_shared_link": "Non se puido eliminar a ligazón compartida", - "unable_to_delete_user": "Non se puido eliminar o usuario", - "unable_to_delete_workflow": "Non se pode eliminar o fluxo de traballo", - "unable_to_download_files": "Non se puideron descargar os ficheiros", - "unable_to_edit_exclusion_pattern": "Non se puido editar o patrón de exclusión", - "unable_to_empty_trash": "Non se puido baleirar o lixo", - "unable_to_enter_fullscreen": "Non se puido entrar en pantalla completa", - "unable_to_exit_fullscreen": "Non se puido saír da pantalla completa", - "unable_to_get_comments_number": "Non se puido obter o número de comentarios", - "unable_to_get_shared_link": "Erro ao obter a ligazón compartida", - "unable_to_hide_person": "Non se puido ocultar a persoa", - "unable_to_link_motion_video": "Non se puido ligar o vídeo en movemento", - "unable_to_link_oauth_account": "Non se puido ligar a conta OAuth", - "unable_to_log_out_all_devices": "Non se puido pechar sesión en todos os dispositivos", - "unable_to_log_out_device": "Non se puido pechar sesión no dispositivo", - "unable_to_login_with_oauth": "Non se puido iniciar sesión con OAuth", - "unable_to_play_video": "Non se puido reproducir o vídeo", - "unable_to_reassign_assets_existing_person": "Non se puideron reasignar activos a {name, select, null {unha persoa existente} other {{name}}}", - "unable_to_reassign_assets_new_person": "Non se puideron reasignar activos a unha nova persoa", - "unable_to_refresh_user": "Non se puido actualizar o usuario", - "unable_to_remove_album_users": "Non se puideron eliminar usuarios do álbum", - "unable_to_remove_api_key": "Non se puido eliminar a Chave API", - "unable_to_remove_assets_from_shared_link": "Non se puideron eliminar activos da ligazón compartida", - "unable_to_remove_library": "Non se puido eliminar a biblioteca", - "unable_to_remove_partner": "Non se puido eliminar o/a compañeiro/a", - "unable_to_remove_reaction": "Non se puido eliminar a reacción", - "unable_to_reset_password": "Non se puido restablecer o contrasinal", - "unable_to_reset_pin_code": "Non é posible reiniciar o código PIN", - "unable_to_resolve_duplicate": "Non se puido resolver o duplicado", - "unable_to_restore_assets": "Non se puideron restaurar os activos", - "unable_to_restore_trash": "Non se puido restaurar o lixo", - "unable_to_restore_user": "Non se puido restaurar o usuario", - "unable_to_save_album": "Non se puido gardar o álbum", - "unable_to_save_api_key": "Non se puido gardar a Chave API", - "unable_to_save_date_of_birth": "Non se puido gardar a data de nacemento", - "unable_to_save_name": "Non se puido gardar o nome", - "unable_to_save_profile": "Non se puido gardar o perfil", - "unable_to_save_settings": "Non se puido gardar a configuración", - "unable_to_scan_libraries": "Non se puideron escanear as bibliotecas", - "unable_to_scan_library": "Non se puido escanear a biblioteca", - "unable_to_set_feature_photo": "Non se puido establecer a foto destacada", - "unable_to_set_profile_picture": "Non se puido establecer a imaxe de perfil", - "unable_to_submit_job": "Non se puido enviar o traballo", - "unable_to_trash_asset": "Non se puido mover o activo ao lixo", - "unable_to_unlink_account": "Non se puido desvincular a conta", - "unable_to_unlink_motion_video": "Non se puido desvincular o vídeo en movemento", - "unable_to_update_album_cover": "Non se puido actualizar a portada do álbum", - "unable_to_update_album_info": "Non se puido actualizar a información do álbum", - "unable_to_update_library": "Non se puido actualizar a biblioteca", - "unable_to_update_location": "Non se puido actualizar a localización", - "unable_to_update_settings": "Non se puido actualizar a configuración", - "unable_to_update_timeline_display_status": "Non se puido actualizar o estado de visualización da liña de tempo", - "unable_to_update_user": "Non se puido actualizar o usuario", - "unable_to_update_workflow": "Non se pode actualizar o fluxo de traballo", - "unable_to_upload_file": "Non se puido cargar o ficheiro" - }, - "errors_text": "Erros", - "exclusion_pattern": "Patrón de exclusión", - "exif": "Exif", - "exif_bottom_sheet_description": "Engadir Descrición...", - "exif_bottom_sheet_description_error": "Erro ao actualizar a descrición", - "exif_bottom_sheet_details": "DETALLES", - "exif_bottom_sheet_location": "LOCALIZACIÓN", - "exif_bottom_sheet_no_description": "Sen descrición", - "exif_bottom_sheet_people": "PERSOAS", - "exif_bottom_sheet_person_add_person": "Engadir nome", - "exit_slideshow": "Saír da Presentación", - "expand_all": "Expandir todo", - "experimental_settings_new_asset_list_subtitle": "Traballo en progreso", - "experimental_settings_new_asset_list_title": "Activar grella de fotos experimental", - "experimental_settings_subtitle": "Use baixo o seu propio risco!", - "experimental_settings_title": "Experimental", - "expire_after": "Caduca despois de", - "expired": "Caducado", - "expires_date": "Caduca o {date}", - "explore": "Explorar", - "explorer": "Explorador", - "export": "Exportar", - "export_as_json": "Exportar como JSON", - "export_database": "Exportar a Base de Datos", - "export_database_description": "Exportar a base de datos SQLite", - "extension": "Extensión", - "external": "Externo", - "external_libraries": "Bibliotecas Externas", - "external_network": "Rede externa", - "external_network_sheet_info": "Cando non estea na rede wifi preferida, a aplicación conectarase ao servidor a través da primeira das seguintes URLs que poida alcanzar, comezando de arriba a abaixo", - "face_unassigned": "Sen asignar", - "failed": "Fallado", - "failed_count": "Fallou: {count}", - "failed_to_authenticate": "Fallou a autenticación", - "failed_to_load_assets": "Erro ao cargar activos", - "failed_to_load_folder": "Erro ao cargar o cartafol", - "favorite": "Favorito", - "favorite_action_prompt": "{count} engadido/a a Favoritos", - "favorite_or_unfavorite_photo": "Marcar ou desmarcar como favorito", - "favorites": "Favoritos", - "favorites_page_no_favorites": "Non se atoparon activos favoritos", - "feature_photo_updated": "Foto destacada actualizada", - "features": "Funcións", - "features_in_development": "Funcionalidades en Desenvolvemento", - "features_setting_description": "Xestionar as funcións da aplicación", - "file_name_or_extension": "Nome do ficheiro ou extensión", - "file_size": "Tamaño do arquivo", - "filename": "Nome do ficheiro", - "filetype": "Tipo de ficheiro", - "filter": "Filtro", - "filter_description": "Condicións para filtrar os activos obxectivo", - "filter_people": "Filtrar persoas", - "filter_places": "Filtrar lugares", - "filters": "Filtros", - "find_them_fast": "Atópeos rápido por nome coa busca", - "first": "Primeiro/a", - "fix_incorrect_match": "Corrixir coincidencia incorrecta", - "folder": "Cartafol", - "folder_not_found": "Cartafol non atopado", - "folders": "Cartafoles", - "folders_feature_description": "Navegar pola vista de cartafoles para as fotos e vídeos no sistema de ficheiros", - "forgot_pin_code_question": "Esqueceu o seu PIN?", - "forward": "Adiante", - "full_path": "Ruta completa: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Esta funcionalidade carga recursos externos de Google para poder funcionar.", - "general": "Xeral", - "geolocation_instruction_location": "Prema nun recurso con coordenadas GPS para usar a súa localización, ou seleccione unha localización directamente no mapa", - "get_help": "Obter Axuda", - "get_people_error": "Erro ao obter xente", - "get_wifiname_error": "Non se puido obter o nome da wifi. Asegúrese de que concedeu os permisos necesarios e está conectado a unha rede wifi", - "getting_started": "Primeiros Pasos", - "go_back": "Volver", - "go_to_folder": "Ir ao cartafol", - "go_to_search": "Ir á busca", - "gps": "GPS", - "gps_missing": "Sen GPS", - "grant_permission": "Conceder permiso", - "group_albums_by": "Agrupar álbums por...", - "group_country": "Agrupar por país", - "group_no": "Sen agrupación", - "group_owner": "Agrupar por propietario", - "group_places_by": "Agrupar lugares por...", - "group_year": "Agrupar por ano", - "haptic_feedback_switch": "Activar resposta háptica", - "haptic_feedback_title": "Resposta Háptica", - "has_quota": "Ten cota", - "hash_asset": "Facer hash do recurso", - "hashed_assets": "Recursos cun hash", - "hashing": "Aplicando hash", - "header_settings_add_header_tip": "Engadir cabeceira", - "header_settings_field_validator_msg": "O valor non pode estar baleiro", - "header_settings_header_name_input": "Nome da cabeceira", - "header_settings_header_value_input": "Valor da cabeceira", - "headers_settings_tile_title": "Cabeceiras de proxy personalizadas", - "height": "Altura", - "hi_user": "Ola {name} ({email})", - "hide_all_people": "Ocultar todas as persoas", - "hide_gallery": "Ocultar galería", - "hide_named_person": "Ocultar a persoa {name}", - "hide_password": "Ocultar contrasinal", - "hide_person": "Ocultar persoa", - "hide_schema": "Ocultar esquema", - "hide_text_recognition": "Ocultar recoñecemento de texto", - "hide_unnamed_people": "Ocultar persoas sen nome", - "home_page_add_to_album_conflicts": "Engadidos {added} activos ao álbum {album}. {failed} activos xa están no álbum.", - "home_page_add_to_album_err_local": "Non se poden engadir activos locais a álbums aínda, omitindo", - "home_page_add_to_album_success": "Engadidos {added} activos ao álbum {album}.", - "home_page_album_err_partner": "Non se poden engadir activos de compañeiro/a a un álbum aínda, omitindo", - "home_page_archive_err_local": "Non se poden arquivar activos locais aínda, omitindo", - "home_page_archive_err_partner": "Non se poden arquivar activos de compañeiro/a, omitindo", - "home_page_building_timeline": "Construíndo a liña de tempo", - "home_page_delete_err_partner": "Non se poden eliminar activos de compañeiro/a, omitindo", - "home_page_delete_remote_err_local": "Activos locais na selección de eliminación remota, omitindo", - "home_page_favorite_err_local": "Non se poden marcar como favoritos activos locais aínda, omitindo", - "home_page_favorite_err_partner": "Non se poden marcar como favoritos activos de compañeiro/a aínda, omitindo", - "home_page_first_time_notice": "Se esta é a primeira vez que usa a aplicación, asegúrese de elixir un álbum de copia de seguridade para que a liña de tempo poida encherse con fotos e vídeos nel", - "home_page_locked_error_local": "Non é posíbel mover os recursos locais ao cartafol bloqueado, saltando", - "home_page_locked_error_partner": "Non é posíbel mover os recursos do/a colaborador/a ao cartafol bloqueado, saltando", - "home_page_share_err_local": "Non se poden compartir activos locais mediante ligazón, omitindo", - "home_page_upload_err_limit": "Só se pode cargar un máximo de 30 activos á vez, omitindo", - "host": "Servidor", - "hour": "Hora", - "hours": "Horas", - "id": "ID", - "idle": "Inactivo/a", - "ignore_icloud_photos": "Ignorar fotos de iCloud", - "ignore_icloud_photos_description": "As fotos que están almacenadas en iCloud non se cargarán ao servidor Immich", - "image": "Imaxe", - "image_alt_text_date": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a o {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a con {person1} o {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a con {person1} e {person2} o {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a con {person1}, {person2} e {person3} o {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a con {person1}, {person2} e {additionalCount, number} outros/as o {date}", - "image_alt_text_date_place": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} o {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1} o {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1} e {person2} o {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1}, {person2} e {person3} o {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1}, {person2} e {additionalCount, number} outros/as o {date}", - "image_saved_successfully": "Imaxe gardada", - "image_viewer_page_state_provider_download_started": "Descarga Iniciada", - "image_viewer_page_state_provider_download_success": "Descarga Exitosa", - "image_viewer_page_state_provider_share_error": "Erro ao Compartir", - "immich_logo": "Logo de Immich", - "immich_web_interface": "Interface Web de Immich", - "import_from_json": "Importar desde JSON", - "import_path": "Ruta de importación", - "in_albums": "En {count, plural, one {# álbum} other {# álbums}}", - "in_archive": "No arquivo", - "in_year": "No {year}", - "in_year_selector": "No", - "include_archived": "Incluír arquivados", - "include_shared_albums": "Incluír álbums compartidos", - "include_shared_partner_assets": "Incluír activos de compañeiro/a compartidos", - "individual_share": "Compartir individual", - "individual_shares": "Comparticións individuais", - "info": "Información", - "interval": { - "day_at_onepm": "Todos os días ás 13:00", - "hours": "Cada {hours, plural, one {hora} other {{hours, number} horas}}", - "night_at_midnight": "Todas as noites á medianoite", - "night_at_twoam": "Todas as noites ás 2:00" - }, - "invalid_date": "Data inválida", - "invalid_date_format": "Formato de data inválido", - "invite_people": "Invitar Persoas", - "invite_to_album": "Invitar ao álbum", - "ios_debug_info_fetch_ran_at": "Obtívose ás {dateTime}", - "ios_debug_info_last_sync_at": "Última sincronización: {dateTime}", - "ios_debug_info_no_processes_queued": "Sen procesos en segundo plano en cola", - "ios_debug_info_no_sync_yet": "Aínda non se executou ningunha tarefa de sincronización en segundo plano", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proceso en segundo plano en cola} other {{count} procesos en segundo plano en cola}}", - "ios_debug_info_processing_ran_at": "O procesamento executouse ás {dateTime}", - "items_count": "{count, plural, one {# elemento} other {# elementos}}", - "jobs": "Traballos", - "json_editor": "Editor JSON", - "json_error": "Erro JSON", - "keep": "Conservar", - "keep_all": "Conservar Todo", - "keep_this_delete_others": "Conservar este, eliminar outros", - "kept_this_deleted_others": "Conservouse este activo e elimináronse {count, plural, one {# activo} other {# activos}}", - "keyboard_shortcuts": "Atallos de teclado", - "language": "Lingua", - "language_no_results_subtitle": "Probe axustar o seu termo de busca", - "language_no_results_title": "Non se atopou ningunha lingua", - "language_search_hint": "Buscar linguas...", - "language_setting_description": "Seleccione a súa lingua preferida", - "large_files": "Ficheiros Grandes", - "last": "Último/a", - "last_months": "{count, plural, one {O mes pasado} other {Os últimos # meses}}", - "last_seen": "Visto por última vez", - "latest_version": "Última Versión", - "latitude": "Latitude", - "leave": "Saír", - "leave_album": "Deixar o álbum", - "lens_model": "Modelo da lente", - "let_others_respond": "Permitir que outros respondan", - "level": "Nivel", - "library": "Biblioteca", - "library_add_folder": "Engadir carpeta", - "library_edit_folder": "Editar carpeta", - "library_options": "Opcións da biblioteca", - "library_page_device_albums": "Álbums no Dispositivo", - "library_page_new_album": "Novo álbum", - "library_page_sort_asset_count": "Número de activos", - "library_page_sort_created": "Data de creación", - "library_page_sort_last_modified": "Última modificación", - "library_page_sort_title": "Título do álbum", - "licenses": "Licenzas", - "light": "Claro", - "like": "Gústame", - "like_deleted": "Gústame eliminado", - "link_motion_video": "Ligar vídeo en movemento", - "link_to_oauth": "Ligar a OAuth", - "linked_oauth_account": "Conta OAuth ligada", - "list": "Lista", - "loading": "Cargando", - "loading_search_results_failed": "Erro ao cargar os resultados da busca", - "local": "Local", - "local_asset_cast_failed": "Non é posíbel proxectar un recurso que non está cargado no servidor", - "local_assets": "Recursos Locais", - "local_id": "ID local", - "local_media_summary": "Resumo de Contido Local", - "local_network": "Rede local", - "local_network_sheet_info": "A aplicación conectarase ao servidor a través desta URL cando use a rede wifi especificada", - "location": "Localización", - "location_permission": "Permiso de localización", - "location_permission_content": "Para usar a función de cambio automático, Immich necesita permiso de localización precisa para poder ler o nome da rede wifi actual", - "location_picker_choose_on_map": "Elixir no mapa", - "location_picker_latitude_error": "Introducir unha latitude válida", - "location_picker_latitude_hint": "Introduza a súa latitude aquí", - "location_picker_longitude_error": "Introducir unha lonxitude válida", - "location_picker_longitude_hint": "Introduza a súa lonxitude aquí", - "lock": "Bloquear", - "locked_folder": "Cartafol Bloqueado", - "log_detail_title": "Detalle do Rexistro", - "log_out": "Pechar sesión", - "log_out_all_devices": "Pechar Sesión en Todos os Dispositivos", - "logged_in_as": "Sesión iniciada como {user}", - "logged_out_all_devices": "Pechouse sesión en todos os dispositivos", - "logged_out_device": "Pechouse sesión no dispositivo", - "login": "Iniciar sesión", - "login_disabled": "O inicio de sesión foi desactivado", - "login_form_api_exception": "Excepción da API. Por favor, comprobe a URL do servidor e inténteo de novo.", - "login_form_back_button_text": "Atrás", - "login_form_email_hint": "oseuemail@dominio.com", - "login_form_endpoint_hint": "http://ip-do-servidor:porto", - "login_form_endpoint_url": "URL do Punto Final do Servidor", - "login_form_err_http": "Por favor, especifique http:// ou https://", - "login_form_err_invalid_email": "Correo electrónico inválido", - "login_form_err_invalid_url": "URL inválida", - "login_form_err_leading_whitespace": "Espazo en branco inicial", - "login_form_err_trailing_whitespace": "Espazo en branco final", - "login_form_failed_get_oauth_server_config": "Erro ao iniciar sesión usando OAuth, comprobe a URL do servidor", - "login_form_failed_get_oauth_server_disable": "A función OAuth non está dispoñible neste servidor", - "login_form_failed_login": "Erro ao iniciar sesión, comprobe a URL do servidor, correo electrónico e contrasinal", - "login_form_handshake_exception": "Houbo unha Excepción de Handshake co servidor. Active o soporte para certificados autofirmados nas configuracións se está a usar un certificado autofirmado.", - "login_form_password_hint": "contrasinal", - "login_form_save_login": "Manter sesión iniciada", - "login_form_server_empty": "Introduza unha URL do servidor.", - "login_form_server_error": "Non se puido conectar co servidor.", - "login_has_been_disabled": "O inicio de sesión foi desactivado.", - "login_password_changed_error": "Houbo un erro ao actualizar o seu contrasinal", - "login_password_changed_success": "Contrasinal actualizado correctamente", - "logout_all_device_confirmation": "Está seguro de que quere pechar sesión en todos os dispositivos?", - "logout_this_device_confirmation": "Está seguro de que quere pechar sesión neste dispositivo?", - "logs": "Rexistros", - "longitude": "Lonxitude", - "look": "Aspecto", - "loop_videos": "Reproducir vídeos en bucle", - "loop_videos_description": "Activar para reproducir automaticamente un vídeo en bucle no visor de detalles.", - "main_branch_warning": "Está a usar unha versión de desenvolvemento; recomendamos encarecidamente usar unha versión de lanzamento!", - "main_menu": "Menú principal", - "maintenance_description": "Immich foi posto en modo de mantemento.", - "maintenance_end": "Finalizar o modo de mantemento", - "maintenance_end_error": "Erro ao finalizar o modo de mantemento.", - "maintenance_logged_in_as": "Sesión iniciada actualmente como {user}", - "maintenance_title": "Non dispoñible temporalmente", - "make": "Marca", - "manage_geolocation": "Xestionar a localización", - "manage_media_access_rationale": "Requírese este permiso para xestionar correctamente o traslado dos recursos ao lixo e a súa restauración desde el.", - "manage_media_access_settings": "Abrir axustes", - "manage_media_access_subtitle": "Permitir que a aplicación Immich xestione e mova ficheiros multimedia.", - "manage_media_access_title": "Acceso á xestión de medios", - "manage_shared_links": "Xestionar ligazóns compartidas", - "manage_sharing_with_partners": "Xestionar compartición con compañeiros/as", - "manage_the_app_settings": "Xestionar a configuración da aplicación", - "manage_your_account": "Xestionar a súa conta", - "manage_your_api_keys": "Xestionar as súas claves API", - "manage_your_devices": "Xestionar os seus dispositivos con sesión iniciada", - "manage_your_oauth_connection": "Xestionar a súa conexión OAuth", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {Sen fotos nesta área} one {# foto} other {# fotos}}", - "map_cannot_get_user_location": "Non se pode obter a localización do usuario", - "map_location_dialog_yes": "Si", - "map_location_picker_page_use_location": "Usar esta localización", - "map_location_service_disabled_content": "O servizo de localización debe estar activado para mostrar activos da súa localización actual. Quere activalo agora?", - "map_location_service_disabled_title": "Servizo de localización deshabilitado", - "map_marker_for_images": "Marcador de mapa para imaxes tomadas en {city}, {country}", - "map_marker_with_image": "Marcador de mapa con imaxe", - "map_no_location_permission_content": "Necesítase permiso de localización para mostrar activos da súa localización actual. Quere permitilo agora?", - "map_no_location_permission_title": "Permiso de localización denegado", - "map_settings": "Configuración do mapa", - "map_settings_dark_mode": "Modo escuro", - "map_settings_date_range_option_day": "Últimas 24 horas", - "map_settings_date_range_option_days": "Últimos {days} días", - "map_settings_date_range_option_year": "Último ano", - "map_settings_date_range_option_years": "Últimos {years} anos", - "map_settings_dialog_title": "Configuración do Mapa", - "map_settings_include_show_archived": "Incluír Arquivados", - "map_settings_include_show_partners": "Incluír Compañeiros/as", - "map_settings_only_show_favorites": "Mostrar Só Favoritos", - "map_settings_theme_settings": "Tema do Mapa", - "map_zoom_to_see_photos": "Afaste o zoom para ver fotos", - "mark_all_as_read": "Marcar todo como lido", - "mark_as_read": "Marcar como lido", - "marked_all_as_read": "Marcado todo como lido", - "matches": "Coincidencias", - "matching_assets": "Recursos Correspondentes", - "media_type": "Tipo de medio", - "memories": "Recordos", - "memories_all_caught_up": "Todo ao día", - "memories_check_back_tomorrow": "Volva mañá para máis recordos", - "memories_setting_description": "Xestionar o que ve nos seus recordos", - "memories_start_over": "Comezar de novo", - "memories_swipe_to_close": "Deslice cara arriba para pechar", - "memory": "Recordo", - "memory_lane_title": "Camiño dos Recordos: {title}", - "menu": "Menú", - "merge": "Fusionar", - "merge_people": "Fusionar persoas", - "merge_people_limit": "Só pode fusionar ata 5 caras á vez", - "merge_people_prompt": "Quere fusionar estas persoas? Esta acción é irreversible.", - "merge_people_successfully": "Persoas fusionadas correctamente", - "merged_people_count": "Fusionadas {count, plural, one {# persoa} other {# persoas}}", - "minimize": "Minimizar", - "minute": "Minuto", - "minutes": "Minutos", - "missing": "Faltantes", - "mobile_app": "Aplicación Móbil", - "mobile_app_download_onboarding_note": "Descarga a aplicación móbil complementaria usando as seguintes opcións", - "model": "Modelo", - "month": "Mes", - "monthly_title_text_date_format": "MMMM a", - "more": "Máis", - "move": "Mover", - "move_down": "Baixar", - "move_off_locked_folder": "Mover fóra do cartafol bloqueado", - "move_to": "Mover a", - "move_to_lock_folder_action_prompt": "{count} engadido/a ao cartafol bloqueado", - "move_to_locked_folder": "Mover ao cartafol bloqueado", - "move_to_locked_folder_confirmation": "Estas fotos e vídeo eliminaranse de todos os álbums e só serán visíbeis dende o cartafol bloqueado", - "move_up": "Subir", - "moved_to_archive": "Moveuse {count, plural, one {# recurso} other {# recursos}} ao arquivo", - "moved_to_library": "Moveuse {count, plural, one {# recurso} other {# recursos}} á biblioteca", - "moved_to_trash": "Movido ao lixo", - "multiselect_grid_edit_date_time_err_read_only": "Non se pode editar a data de activo(s) de só lectura, omitindo", - "multiselect_grid_edit_gps_err_read_only": "Non se pode editar a localización de activo(s) de só lectura, omitindo", - "mute_memories": "Silenciar Recordos", - "my_albums": "Os meus álbums", - "name": "Nome", - "name_or_nickname": "Nome ou alcume", - "name_required": "O nome é obligatorio", - "navigate": "Navegar", - "navigate_to_time": "Navegar ata Hora", - "network_requirement_photos_upload": "Usar datos móbiles para facer copia de seguridade das fotos", - "network_requirement_videos_upload": "Usar datos móbiles para facer copia de seguridade dos vídeos", - "network_requirements": "Requisitos de rede", - "network_requirements_updated": "Os requisitos de rede cambiaron, reiniciando cola de copia de seguridade", - "networking_settings": "Rede", - "networking_subtitle": "Xestionar a configuración do punto final do servidor", - "never": "Nunca", - "new_album": "Novo Álbum", - "new_api_key": "Nova Chave API", - "new_date_range": "Novo rango de datas", - "new_password": "Novo contrasinal", - "new_person": "Nova persoa", - "new_pin_code": "Novo código PIN", - "new_pin_code_subtitle": "Esta é a túa primeira vez accedendo á carpeta segura. Crea un código PIN para acceder de maneira segura a esta páxina", - "new_timeline": "Nova liña de tempo", - "new_update": "Nova actualización", - "new_user_created": "Novo usuario creado", - "new_version_available": "NOVA VERSIÓN DISPOÑIBLE", - "newest_first": "Máis recentes primeiro", - "next": "Seguinte", - "next_memory": "Seguinte recordo", - "no": "Non", - "no_actions_added": "Non hai accións engadidas polo momento", - "no_albums_message": "Cree un álbum para organizar as súas fotos e vídeos", - "no_albums_with_name_yet": "Parece que aínda non ten ningún álbum con este nome.", - "no_albums_yet": "Parece que aínda non ten ningún álbum.", - "no_archived_assets_message": "Arquive fotos e vídeos para ocultalos da súa vista de Fotos", - "no_assets_message": "PREMA PARA CARGAR A SÚA PRIMEIRA FOTO", - "no_assets_to_show": "Non hai activos para mostrar", - "no_cast_devices_found": "Non se atoparon dispositivos de transmisión", - "no_checksum_local": "Non hai suma de verificación dispoñible - non se poden obter os activos locais", - "no_checksum_remote": "Non hai suma de verificación dispoñible - non se pode obter o activo remoto", - "no_configuration_needed": "Non se precisa configuración", - "no_devices": "Dispositivos non autorizados", - "no_duplicates_found": "Non se atoparon duplicados.", - "no_exif_info_available": "Non hai información EXIF dispoñible", - "no_explore_results_message": "Suba máis fotos para explorar a súa colección.", - "no_favorites_message": "Engada favoritos para atopar rapidamente as súas mellores fotos e vídeos", - "no_filters_added": "Aínda non se engadiron filtros", - "no_libraries_message": "Cree unha biblioteca externa para ver as súas fotos e vídeos", - "no_local_assets_found": "Non se atoparon elementos locais con esta suma de comprobación", - "no_location_set": "Non se estableceu a localización", - "no_locked_photos_message": "As fotos e vídeos no cartafol con chave están ocultos e non aparecerán mentres navegas ou buscas na túa biblioteca.", - "no_name": "Sen Nome", - "no_notifications": "Sen notificacións", - "no_people_found": "Non se atoparon persoas coincidentes", - "no_places": "Sen lugares", - "no_remote_assets_found": "Non se atoparon activos remotos con esta suma de verificación", - "no_results": "Sen resultados", - "no_results_description": "Probe cun sinónimo ou palabra chave máis xeral", - "no_shared_albums_message": "Cree un álbum para compartir fotos e vídeos con persoas na súa rede", - "no_uploads_in_progress": "Non hai cargas en curso", - "not_allowed": "Non permitido", - "not_available": "Non dispoñible", - "not_in_any_album": "Non está en ningún álbum", - "not_selected": "Non seleccionado", - "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar a Etiqueta de Almacenamento a activos cargados previamente, execute o", - "notes": "Notas", - "nothing_here_yet": "Aínda nada por aquí", - "notification_permission_dialog_content": "Para activar as notificacións, vaia a Axustes e seleccione permitir.", - "notification_permission_list_tile_content": "Conceda permiso para activar as notificacións.", - "notification_permission_list_tile_enable_button": "Activar Notificacións", - "notification_permission_list_tile_title": "Permiso de Notificación", - "notification_toggle_setting_description": "Activar notificacións por correo electrónico", - "notifications": "Notificacións", - "notifications_setting_description": "Xestionar notificacións", - "oauth": "OAuth", - "obtainium_configurator": "Configurador de Obtainium", - "obtainium_configurator_instructions": "Emprega Obtainium para instalar e actualizar a aplicación de Android directamente desde o lanzamento do GitHub de Immich. Crea unha chave API e selecciona unha variante para xerar o teu enlace de configuración de Obtainium", - "ocr": "OCR", - "official_immich_resources": "Recursos Oficiais de Immich", - "offline": "Fóra de liña", - "offset": "Desprazamento", - "ok": "Aceptar", - "oldest_first": "Máis antigos primeiro", - "on_this_device": "Neste dispositivo", - "onboarding": "Primeiros pasos", - "onboarding_locale_description": "Selecciona o teu idioma preferido. Podes cambialo máis tarde na túa configuración.", - "onboarding_privacy_description": "As seguintes funcións (opcionais) dependen de servizos externos e poden desactivarse en calquera momento na configuración da administración.", - "onboarding_server_welcome_description": "Imos configurar a túa instancia con algunhas opcións comúns.", - "onboarding_theme_description": "Elixa un tema de cor para a súa instancia. Pode cambialo máis tarde na súa configuración.", - "onboarding_user_welcome_description": "Imos comezar!", - "onboarding_welcome_user": "Benvido/a, {user}", - "online": "En liña", - "only_favorites": "Só favoritos", - "open": "Abrir", - "open_in_map_view": "Abrir na vista de mapa", - "open_in_openstreetmap": "Abrir en OpenStreetMap", - "open_the_search_filters": "Abrir os filtros de busca", - "options": "Opcións", - "or": "ou", - "organize_into_albums": "Organizar en álbums", - "organize_into_albums_description": "Poñer as fotos existentes en álbums usando as opcións de sincronización actuais", - "organize_your_library": "Organizar a súa biblioteca", - "original": "orixinal", - "other": "Outro", - "other_devices": "Outros dispositivos", - "other_entities": "Outras entidades", - "other_variables": "Outras variables", - "owned": "Propio", - "owner": "Propietario", - "page": "Páxina", - "partner": "Compañeiro/a", - "partner_can_access": "{partner} pode acceder a", - "partner_can_access_assets": "Todas as súas fotos e vídeos excepto os de Arquivo e Eliminados", - "partner_can_access_location": "A localización onde se tomaron as súas fotos", - "partner_list_user_photos": "Fotos de {user}", - "partner_list_view_all": "Ver todo", - "partner_page_empty_message": "As súas fotos aínda non están compartidas con ningún compañeiro/a.", - "partner_page_no_more_users": "Non hai máis usuarios para engadir", - "partner_page_partner_add_failed": "Erro ao engadir compañeiro/a", - "partner_page_select_partner": "Seleccionar compañeiro/a", - "partner_page_shared_to_title": "Compartido con", - "partner_page_stop_sharing_content": "{partner} xa non poderá acceder ás súas fotos.", - "partner_sharing": "Compartición con Compañeiro/a", - "partners": "Compañeiros/as", - "password": "Contrasinal", - "password_does_not_match": "O contrasinal non coincide", - "password_required": "Requírese Contrasinal", - "password_reset_success": "Contrasinal restablecido correctamente", - "past_durations": { - "days": "Últimos {days, plural, one {día} other {# días}}", - "hours": "Últimas {hours, plural, one {hora} other {# horas}}", - "years": "Últimos {years, plural, one {ano} other {# anos}}" - }, - "path": "Ruta", - "pattern": "Patrón", - "pause": "Pausa", - "pause_memories": "Pausar recordos", - "paused": "Pausado", - "pending": "Pendente", - "people": "Persoas", - "people_edits_count": "Editadas {count, plural, one {# persoa} other {# persoas}}", - "people_feature_description": "Navegar por fotos e vídeos agrupados por persoas", - "people_selected": "{count, plural, one {# persoa seleccionada} other {# persoas seleccionadas}}", - "people_sidebar_description": "Mostrar unha ligazón a Persoas na barra lateral", - "permanent_deletion_warning": "Aviso de eliminación permanente", - "permanent_deletion_warning_setting_description": "Mostrar un aviso ao eliminar permanentemente activos", - "permanently_delete": "Eliminar permanentemente", - "permanently_delete_assets_count": "Eliminar permanentemente {count, plural, one {activo} other {activos}}", - "permanently_delete_assets_prompt": "Está seguro de que quere eliminar permanentemente {count, plural, one {este activo?} other {estes # activos?}} Isto tamén {count, plural, one {o eliminará do seu} other {os eliminará dos seus}} álbum(s).", - "permanently_deleted_asset": "Activo eliminado permanentemente", - "permanently_deleted_assets_count": "Eliminados permanentemente {count, plural, one {# activo} other {# activos}}", - "permission": "Permiso", - "permission_empty": "O teu permiso non debe estar baleiro", - "permission_onboarding_back": "Atrás", - "permission_onboarding_continue_anyway": "Continuar de todos os xeitos", - "permission_onboarding_get_started": "Comezar", - "permission_onboarding_go_to_settings": "Ir a axustes", - "permission_onboarding_permission_denied": "Permiso denegado. Para usar Immich, conceda permisos de fotos e vídeos en Axustes.", - "permission_onboarding_permission_granted": "Permiso concedido! Xa está todo listo.", - "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich faga copia de seguridade e xestione toda a súa colección da galería, conceda permisos de fotos e vídeos en Configuración.", - "permission_onboarding_request": "Immich require permiso para ver as súas fotos e vídeos.", - "person": "Persoa", - "person_age_months": "{months, plural, one {# mes} other {# meses}} de idade", - "person_age_year_months": "1 ano, {months, plural, one {# mes} other {# meses}} de idade", - "person_age_years": "{years, plural, one {# ano} other {# anos}} de idade", - "person_birthdate": "Nacido/a o {date}", - "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", - "person_recognized": "Persoa recoñecida", - "person_selected": "Persoa seleccionada", - "photo_shared_all_users": "Parece que compartiu as súas fotos con todos os usuarios ou non ten ningún usuario co que compartir.", - "photos": "Fotos", - "photos_and_videos": "Fotos e Vídeos", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", - "photos_from_previous_years": "Fotos de anos anteriores", - "pick_a_location": "Elixir unha localización", - "pick_custom_range": "Rango personalizado", - "pick_date_range": "Seleccionar un rango de datas", - "pin_code_changed_successfully": "Código PIN cambiado correctamente", - "pin_code_reset_successfully": "Código PIN restablecido correctamente", - "pin_code_setup_successfully": "Código PIN configurado correctamente", - "pin_verification": "Verificación do código PIN", - "place": "Lugar", - "places": "Lugares", - "places_count": "{count, plural, one {{count, number} Lugar} other {{count, number} Lugares}}", - "play": "Reproducir", - "play_memories": "Reproducir recordos", - "play_motion_photo": "Reproducir Foto en Movemento", - "play_or_pause_video": "Reproducir ou pausar vídeo", - "play_original_video": "Reproducir o vídeo orixinal", - "play_original_video_setting_description": "Preferir a reprodución dos vídeos orixinais en vez dos vídeos transcodificados. Se o recurso orixinal non é compatible, pode que non se reproduza correctamente.", - "play_transcoded_video": "Reproducir vídeo transcodificado", - "please_auth_to_access": "Por favor, autentícate para acceder", - "port": "Porto", - "preferences_settings_subtitle": "Xestionar as preferencias da aplicación", - "preferences_settings_title": "Preferencias", - "preparing": "Preparando", - "preset": "Preaxuste", - "preview": "Vista previa", - "previous": "Anterior", - "previous_memory": "Recordo anterior", - "previous_or_next_day": "Día seguinte / Día anterior", - "previous_or_next_month": "Mes seguinte / Mes anterior", - "previous_or_next_photo": "Foto anterior ou seguinte", - "previous_or_next_year": "Ano seguinte / Ano anterior", - "primary": "Principal", - "privacy": "Privacidade", - "profile": "Perfil", - "profile_drawer_app_logs": "Rexistros", - "profile_drawer_client_server_up_to_date": "Cliente e Servidor están actualizados", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Modo só lectura activado. Mantén premido o ícone do avatar do usuario para saír.", - "profile_image_of_user": "Imaxe de perfil de {user}", - "profile_picture_set": "Imaxe de perfil establecida.", - "public_album": "Álbum público", - "public_share": "Compartir Público", - "purchase_account_info": "Seguidor/a", - "purchase_activated_subtitle": "Grazas por apoiar Immich e o software de código aberto", - "purchase_activated_time": "Activado o {date}", - "purchase_activated_title": "A súa chave activouse correctamente", - "purchase_button_activate": "Activar", - "purchase_button_buy": "Apoiar", - "purchase_button_buy_immich": "Apoiar Immich", - "purchase_button_never_show_again": "Non mostrar nunca máis", - "purchase_button_reminder": "Lembrarme en 30 días", - "purchase_button_remove_key": "Eliminar chave", - "purchase_button_select": "Seleccionar", - "purchase_failed_activation": "Erro ao activar! Por favor, comprobe o seu correo electrónico para a chave do produto correcta!", - "purchase_individual_description_1": "Para un individuo", - "purchase_individual_description_2": "Estado de seguidor/a", - "purchase_individual_title": "Individual", - "purchase_input_suggestion": "Ten unha chave de produto? Introduza a chave a continuación", - "purchase_license_subtitle": "Apoie Immich para contribuír ao desenvolvemento continuado do servizo", - "purchase_lifetime_description": "Contribución vitalicia", - "purchase_option_title": "OPCIÓNS DE APOIO", - "purchase_panel_info_1": "Construír Immich leva moito tempo e esforzo, e temos enxeñeiros a tempo completo traballando nel para facelo o mellor posible. A nosa misión é que o software de código aberto e as prácticas comerciais éticas se convertan nunha fonte de ingresos sostible para os desenvolvedores e crear un ecosistema respectuoso coa privacidade con alternativas reais aos servizos na nube explotadores.", - "purchase_panel_info_2": "Como estamos comprometidos a non engadir muros de pago, esta contribución non lle outorgará ningunha función adicional en Immich. Dependemos de usuarios coma vostede para apoiar o desenvolvemento continuo de Immich.", - "purchase_panel_title": "Apoiar o proxecto", - "purchase_per_server": "Por servidor", - "purchase_per_user": "Por usuario", - "purchase_remove_product_key": "Eliminar Chave do Produto", - "purchase_remove_product_key_prompt": "Está seguro de que quere eliminar a chave do produto?", - "purchase_remove_server_product_key": "Eliminar chave do produto do Servidor", - "purchase_remove_server_product_key_prompt": "Está seguro de que quere eliminar a chave do produto do Servidor?", - "purchase_server_description_1": "Para todo o servidor", - "purchase_server_description_2": "Estado de seguidor/a", - "purchase_server_title": "Servidor", - "purchase_settings_server_activated": "A chave do produto do servidor é xestionada polo administrador", - "query_asset_id": "Consultar o ID do activo", - "queue_status": "Pondo en cola {count}/{total}", - "rating": "Clasificación por estrelas", - "rating_clear": "Borrar clasificación", - "rating_count": "{count, plural, one {# estrela} other {# estrelas}}", - "rating_description": "Mostrar a clasificación EXIF no panel de información", - "reaction_options": "Opcións de reacción", - "read_changelog": "Ler Rexistro de Cambios", - "readonly_mode_disabled": "Modo só lectura desactivado", - "readonly_mode_enabled": "Modo só lectura activado", - "ready_for_upload": "Listo para a carga", - "reassign": "Reasignar", - "reassigned_assets_to_existing_person": "Reasignados {count, plural, one {# activo} other {# activos}} a {name, select, null {unha persoa existente} other {{name}}}", - "reassigned_assets_to_new_person": "Reasignados {count, plural, one {# activo} other {# activos}} a unha nova persoa", - "reassing_hint": "Asignar activos seleccionados a unha persoa existente", - "recent": "Recente", - "recent-albums": "Álbums recentes", - "recent_searches": "Buscas recentes", - "recently_added": "Engadido recentemente", - "recently_added_page_title": "Engadido Recentemente", - "recently_taken": "Tomado recentemente", - "recently_taken_page_title": "Tomado Recentemente", - "refresh": "Actualizar", - "refresh_encoded_videos": "Actualizar vídeos codificados", - "refresh_faces": "Actualizar caras", - "refresh_metadata": "Actualizar metadatos", - "refresh_thumbnails": "Actualizar miniaturas", - "refreshed": "Actualizado", - "refreshes_every_file": "Volve ler todos os ficheiros existentes e novos", - "refreshing_encoded_video": "Actualizando vídeo codificado", - "refreshing_faces": "Actualizando caras", - "refreshing_metadata": "Actualizando metadatos", - "regenerating_thumbnails": "Rexenerando miniaturas", - "remote": "Remoto", - "remote_assets": "Activos Remotos", - "remote_media_summary": "Resumo de Medios Remotos", - "remove": "Eliminar", - "remove_assets_album_confirmation": "Está seguro de que quere eliminar {count, plural, one {# activo} other {# activos}} do álbum?", - "remove_assets_shared_link_confirmation": "Está seguro de que quere eliminar {count, plural, one {# activo} other {# activos}} desta ligazón compartida?", - "remove_assets_title": "Eliminar activos?", - "remove_custom_date_range": "Eliminar rango de datas personalizado", - "remove_deleted_assets": "Eliminar Activos Eliminados", - "remove_from_album": "Eliminar do álbum", - "remove_from_album_action_prompt": "{count} eliminado(s) do álbum", - "remove_from_favorites": "Eliminar de favoritos", - "remove_from_lock_folder_action_prompt": "{count} eliminado(s) do cartafol con chave", - "remove_from_locked_folder": "Eliminar do cartafol con chave", - "remove_from_locked_folder_confirmation": "Estás seguro de que queres mover estas fotos e vídeos fóra do cartafol con chave? Serán visibles na túa biblioteca.", - "remove_from_shared_link": "Eliminar da ligazón compartida", - "remove_memory": "Eliminar recordo", - "remove_photo_from_memory": "Eliminar foto deste recordo", - "remove_tag": "Eliminar a etiqueta", - "remove_url": "Eliminar URL", - "remove_user": "Eliminar usuario", - "removed_api_key": "Chave API eliminada: {name}", - "removed_from_archive": "Eliminado do arquivo", - "removed_from_favorites": "Eliminado de favoritos", - "removed_from_favorites_count": "{count, plural, other {Eliminados #}} de favoritos", - "removed_memory": "Recordo eliminado", - "removed_photo_from_memory": "Foto eliminada do recordo", - "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# activo} other {# activos}}", - "rename": "Renomear", - "repair": "Reparar", - "repair_no_results_message": "Os ficheiros non rastrexados e faltantes aparecerán aquí", - "replace_with_upload": "Substituír con carga", - "repository": "Repositorio", - "require_password": "Requirir contrasinal", - "require_user_to_change_password_on_first_login": "Requirir que o usuario cambie o contrasinal no primeiro inicio de sesión", - "rescan": "Volver escanear", - "reset": "Restablecer", - "reset_password": "Restablecer contrasinal", - "reset_people_visibility": "Restablecer visibilidade das persoas", - "reset_pin_code": "Restablecer o código PIN", - "reset_pin_code_description": "Se esqueciches o teu código PIN, podes contactar co administrador do servidor para restablecelo", - "reset_pin_code_success": "Código PIN restablecido correctamente", - "reset_pin_code_with_password": "Sempre podes restablecer o teu código PIN coa túa contrasinal", - "reset_sqlite": "Restablecer a Base de Datos SQLite", - "reset_sqlite_confirmation": "Estás seguro de que queres restablecer a base de datos SQLite? Terás que pechar a sesión e iniciar sesión de novo para sincronizar os datos outra vez", - "reset_sqlite_success": "Base de datos SQLite restablecida correctamente", - "reset_to_default": "Restablecer ao predeterminado", - "resolution": "Resolución", - "resolve_duplicates": "Resolver duplicados", - "resolved_all_duplicates": "Resolvéronse todos os duplicados", - "restore": "Restaurar", - "restore_all": "Restaurar todo", - "restore_trash_action_prompt": "{count} restaurado(s) do lixo", - "restore_user": "Restaurar usuario", - "restored_asset": "Activo restaurado", - "resume": "Reanudar", - "resume_paused_jobs": "Retomar {count, plural, one {# traballo pausado} other {# traballos pausados}}", - "retry_upload": "Reintentar carga", - "review_duplicates": "Revisar duplicados", - "review_large_files": "Revisar ficheiros grandes", - "role": "Rol", - "role_editor": "Editor", - "role_viewer": "Visor", - "running": "Executándose", - "save": "Gardar", - "save_to_gallery": "Gardar na galería", - "saved": "Gardo", - "saved_api_key": "Chave API gardada", - "saved_profile": "Perfil gardado", - "saved_settings": "Configuración gardada", - "say_something": "Dicir algo", - "scaffold_body_error_occurred": "Ocorreu un erro", - "scan_all_libraries": "Escanear Todas as Bibliotecas", - "scan_library": "Escanear", - "scan_settings": "Configuración de Escaneo", - "scanning_for_album": "Escaneando álbum...", - "search": "Buscar", - "search_albums": "Buscar álbums", - "search_by_context": "Buscar por contexto", - "search_by_description": "Buscar por descrición", - "search_by_description_example": "Día de sendeirismo en Sapa", - "search_by_filename": "Buscar por nome de ficheiro ou extensión", - "search_by_filename_example": "p. ex. IMG_1234.JPG ou PNG", - "search_by_ocr": "Buscar mediante OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Buscar modelo de lente...", - "search_camera_make": "Buscar marca de cámara...", - "search_camera_model": "Buscar modelo de cámara...", - "search_city": "Buscar cidade...", - "search_country": "Buscar país...", - "search_filter_apply": "Aplicar filtro", - "search_filter_camera_title": "Seleccionar tipo de cámara", - "search_filter_date": "Data", - "search_filter_date_interval": "{start} a {end}", - "search_filter_date_title": "Seleccionar un rango de datas", - "search_filter_display_option_not_in_album": "Non nun álbum", - "search_filter_display_options": "Opcións de Visualización", - "search_filter_filename": "Buscar por nome de ficheiro", - "search_filter_location": "Localización", - "search_filter_location_title": "Seleccionar localización", - "search_filter_media_type": "Tipo de Medio", - "search_filter_media_type_title": "Seleccionar tipo de medio", - "search_filter_ocr": "Buscar por OCR", - "search_filter_people_title": "Seleccionar persoas", - "search_for": "Buscar por", - "search_for_existing_person": "Buscar persoa existente", - "search_no_more_result": "Non hai máis resultados", - "search_no_people": "Sen persoas", - "search_no_people_named": "Sen persoas chamadas \"{name}\"", - "search_no_result": "Non se atoparon resultados, probe cun termo de busca ou combinación diferente", - "search_options": "Opcións de busca", - "search_page_categories": "Categorías", - "search_page_motion_photos": "Fotos en Movemento", - "search_page_no_objects": "Non hai Información de Obxectos Dispoñible", - "search_page_no_places": "Non hai Información de Lugares Dispoñible", - "search_page_screenshots": "Capturas de pantalla", - "search_page_search_photos_videos": "Busca as súas fotos e vídeos", - "search_page_selfies": "Selfies", - "search_page_things": "Cousas", - "search_page_view_all_button": "Ver todo", - "search_page_your_activity": "A súa actividade", - "search_page_your_map": "O seu Mapa", - "search_people": "Buscar persoas", - "search_places": "Buscar lugares", - "search_rating": "Buscar por clasificación...", - "search_result_page_new_search_hint": "Nova Busca", - "search_settings": "Configuración da busca", - "search_state": "Buscar estado...", - "search_suggestion_list_smart_search_hint_1": "A busca intelixente está activada por defecto, para buscar metadatos use a sintaxe ", - "search_suggestion_list_smart_search_hint_2": "m:o-seu-termo-de-busca", - "search_tags": "Buscar etiquetas...", - "search_timezone": "Buscar fuso horario...", - "search_type": "Tipo de busca", - "search_your_photos": "Buscar as súas fotos", - "searching_locales": "Buscando configuracións rexionais...", - "second": "Segundo", - "see_all_people": "Ver todas as persoas", - "select": "Seleccionar", - "select_album": "Seleccionar álbume", - "select_album_cover": "Seleccionar portada do álbum", - "select_albums": "Seleccionar álbumes", - "select_all": "Seleccionar todo", - "select_all_duplicates": "Seleccionar todos os duplicados", - "select_all_in": "Seleccionar todo en {group}", - "select_avatar_color": "Seleccionar cor do avatar", - "select_count": "{count, plural, one {Seleccionar #} other {Seleccionar #}}", - "select_face": "Seleccionar cara", - "select_featured_photo": "Seleccionar foto destacada", - "select_from_computer": "Seleccionar do ordenador", - "select_keep_all": "Seleccionar conservar todo", - "select_library_owner": "Seleccionar propietario da biblioteca", - "select_new_face": "Seleccionar nova cara", - "select_people": "Seleccionar xente", - "select_person": "Seleccionar persoa", - "select_person_to_tag": "Seleccionar unha persoa para etiquetar", - "select_photos": "Seleccionar fotos", - "select_trash_all": "Seleccionar mover todo ao lixo", - "select_user_for_sharing_page_err_album": "Erro ao crear o álbum", - "selected": "Seleccionado", - "selected_count": "{count, plural, other {# seleccionados}}", - "selected_gps_coordinates": "Coordenadas GPS seleccionadas", - "send_message": "Enviar mensaxe", - "send_welcome_email": "Enviar correo electrónico de benvida", - "server_endpoint": "Punto Final do Servidor", - "server_info_box_app_version": "Versión da Aplicación", - "server_info_box_server_url": "URL do Servidor", - "server_offline": "Servidor Fóra de Liña", - "server_online": "Servidor En Liña", - "server_privacy": "Privacidade do Servidor", - "server_restarting_description": "Esta páxina actualizarase en breve.", - "server_restarting_title": "O servidor estase reiniciando", - "server_stats": "Estatísticas do Servidor", - "server_update_available": "Hai unha actualización do servidor dispoñible", - "server_version": "Versión do Servidor", - "set": "Establecer", - "set_as_album_cover": "Establecer como portada do álbum", - "set_as_featured_photo": "Establecer como foto destacada", - "set_as_profile_picture": "Establecer como imaxe de perfil", - "set_date_of_birth": "Establecer data de nacemento", - "set_profile_picture": "Establecer imaxe de perfil", - "set_slideshow_to_fullscreen": "Poñer Presentación a pantalla completa", - "set_stack_primary_asset": "Establecer como activo principal", - "setting_image_viewer_help": "O visor de detalles carga primeiro a miniatura pequena, despois carga a vista previa de tamaño medio (se está activada), e finalmente carga o orixinal (se está activado).", - "setting_image_viewer_original_subtitle": "Activar para cargar a imaxe orixinal a resolución completa (grande!). Desactivar para reducir o uso de datos (tanto na rede como na caché do dispositivo).", - "setting_image_viewer_original_title": "Cargar imaxe orixinal", - "setting_image_viewer_preview_subtitle": "Activar para cargar unha imaxe de resolución media. Desactivar para cargar directamente o orixinal ou usar só a miniatura.", - "setting_image_viewer_preview_title": "Cargar imaxe de vista previa", - "setting_image_viewer_title": "Imaxes", - "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Cambiar a lingua da aplicación", - "setting_notifications_notify_failures_grace_period": "Notificar fallos da copia de seguridade en segundo plano: {duration}", - "setting_notifications_notify_hours": "{count} horas", - "setting_notifications_notify_immediately": "inmediatamente", - "setting_notifications_notify_minutes": "{count} minutos", - "setting_notifications_notify_never": "nunca", - "setting_notifications_notify_seconds": "{count} segundos", - "setting_notifications_single_progress_subtitle": "Información detallada do progreso da carga por activo", - "setting_notifications_single_progress_title": "Mostrar progreso detallado da copia de seguridade en segundo plano", - "setting_notifications_subtitle": "Axustar as súas preferencias de notificación", - "setting_notifications_total_progress_subtitle": "Progreso xeral da carga (feitos/total activos)", - "setting_notifications_total_progress_title": "Mostrar progreso total da copia de seguridade en segundo plano", - "setting_video_viewer_auto_play_subtitle": "Reproducir vídeos automaticamente cando se abren", - "setting_video_viewer_auto_play_title": "Reproducir vídeos automaticamente", - "setting_video_viewer_looping_title": "Bucle", - "setting_video_viewer_original_video_subtitle": "Ao transmitir un vídeo desde o servidor, reproducir o orixinal aínda que haxa unha transcodificación dispoñible. Pode provocar buffering. Os vídeos dispoñibles localmente reprodúcense en calidade orixinal independentemente desta configuración.", - "setting_video_viewer_original_video_title": "Forzar vídeo orixinal", - "settings": "Configuración", - "settings_require_restart": "Por favor, reinicie Immich para aplicar esta configuración", - "settings_saved": "Configuración gardada", - "setup_pin_code": "Configurar un código PIN", - "share": "Compartir", - "share_action_prompt": "Compartidos {count} activos", - "share_add_photos": "Engadir fotos", - "share_assets_selected": "{count} seleccionados", - "share_dialog_preparing": "Preparando...", - "share_link": "Ligazón para Compartir", - "shared": "Compartido", - "shared_album_activities_input_disable": "O comentario está desactivado", - "shared_album_activity_remove_content": "Quere eliminar esta actividade?", - "shared_album_activity_remove_title": "Eliminar Actividade", - "shared_album_section_people_action_error": "Erro ao saír/eliminar do álbum", - "shared_album_section_people_action_leave": "Eliminar usuario do álbum", - "shared_album_section_people_action_remove_user": "Eliminar usuario do álbum", - "shared_album_section_people_title": "PERSOAS", - "shared_by": "Compartido por", - "shared_by_user": "Compartido por {user}", - "shared_by_you": "Compartido por vostede", - "shared_from_partner": "Fotos de {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Subidos", - "shared_link_app_bar_title": "Ligazóns Compartidas", - "shared_link_clipboard_copied_massage": "Copiado ao portapapeis", - "shared_link_clipboard_text": "Ligazón: {link}\nContrasinal: {password}", - "shared_link_create_error": "Erro ao crear ligazón compartida", - "shared_link_custom_url_description": "Acceder a esta ligazón compartida cun URL personalizado", - "shared_link_edit_description_hint": "Introduza a descrición da compartición", - "shared_link_edit_expire_after_option_day": "1 día", - "shared_link_edit_expire_after_option_days": "{count} días", - "shared_link_edit_expire_after_option_hour": "1 hora", - "shared_link_edit_expire_after_option_hours": "{count} horas", - "shared_link_edit_expire_after_option_minute": "1 minuto", - "shared_link_edit_expire_after_option_minutes": "{count} minutos", - "shared_link_edit_expire_after_option_months": "{count} meses", - "shared_link_edit_expire_after_option_year": "{count} ano", - "shared_link_edit_password_hint": "Introduza o contrasinal da compartición", - "shared_link_edit_submit_button": "Actualizar ligazón", - "shared_link_error_server_url_fetch": "Non se pode obter a URL do servidor", - "shared_link_expires_day": "Caduca en {count} día", - "shared_link_expires_days": "Caduca en {count} días", - "shared_link_expires_hour": "Caduca en {count} hora", - "shared_link_expires_hours": "Caduca en {count} horas", - "shared_link_expires_minute": "Caduca en {count} minuto", - "shared_link_expires_minutes": "Caduca en {count} minutos", - "shared_link_expires_never": "Non caduca", - "shared_link_expires_second": "Caduca en {count} segundo", - "shared_link_expires_seconds": "Caduca en {count} segundos", - "shared_link_individual_shared": "Compartido individualmente", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Xestionar ligazóns Compartidas", - "shared_link_options": "Opcións da ligazón compartida", - "shared_link_password_description": "Requirir un contrasinal para acceder a esta ligazón compartida", - "shared_links": "Ligazóns compartidas", - "shared_links_description": "Compartir fotos e vídeos cunha ligazón", - "shared_photos_and_videos_count": "{assetCount, plural, other {# fotos e vídeos compartidos.}}", - "shared_with_me": "Compartido comigo", - "shared_with_partner": "Compartido con {partner}", - "sharing": "Compartir", - "sharing_enter_password": "Por favor, introduza o contrasinal para ver esta páxina.", - "sharing_page_album": "Álbums compartidos", - "sharing_page_description": "Cree álbums compartidos para compartir fotos e vídeos con persoas na súa rede.", - "sharing_page_empty_list": "LISTA BALEIRA", - "sharing_sidebar_description": "Mostrar unha ligazón a Compartir na barra lateral", - "sharing_silver_appbar_create_shared_album": "Novo álbum compartido", - "sharing_silver_appbar_share_partner": "Compartir con compañeiro/a", - "shift_to_permanent_delete": "prema ⇧ para eliminar permanentemente o activo", - "show_album_options": "Mostrar opcións do álbum", - "show_albums": "Mostrar álbums", - "show_all_people": "Mostrar todas as persoas", - "show_and_hide_people": "Mostrar e ocultar persoas", - "show_file_location": "Mostrar localización do ficheiro", - "show_gallery": "Mostrar galería", - "show_hidden_people": "Mostrar persoas ocultas", - "show_in_timeline": "Mostrar na liña de tempo", - "show_in_timeline_setting_description": "Mostrar fotos e vídeos deste usuario na súa liña de tempo", - "show_keyboard_shortcuts": "Mostrar atallos de teclado", - "show_metadata": "Mostrar metadatos", - "show_or_hide_info": "Mostrar ou ocultar información", - "show_password": "Mostrar contrasinal", - "show_person_options": "Mostrar opcións da persoa", - "show_progress_bar": "Mostrar Barra de Progreso", - "show_schema": "Mostrar esquema", - "show_search_options": "Mostrar opcións de busca", - "show_shared_links": "Mostrar ligazóns compartidas", - "show_slideshow_transition": "Mostrar transición da presentación", - "show_supporter_badge": "Insignia de seguidor/a", - "show_supporter_badge_description": "Mostrar unha insignia de seguidor/a", - "show_text_recognition": "Mostrar recoñecemento de texto", - "show_text_search_menu": "Mostrar o menú de busca de texto", - "shuffle": "Aleatorio", - "sidebar": "Barra lateral", - "sidebar_display_description": "Mostrar unha ligazón á vista na barra lateral", - "sign_out": "Pechar Sesión", - "sign_up": "Rexistrarse", - "size": "Tamaño", - "skip_to_content": "Saltar ao contido", - "skip_to_folders": "Saltar a cartafoles", - "skip_to_tags": "Saltar a etiquetas", - "slideshow": "Presentación", - "slideshow_settings": "Configuración da presentación", - "sort_albums_by": "Ordenar álbums por...", - "sort_created": "Data de creación", - "sort_items": "Número de elementos", - "sort_modified": "Data de modificación", - "sort_newest": "Foto máis recente", - "sort_oldest": "Foto máis antiga", - "sort_people_by_similarity": "Ordenar persoas por similitude", - "sort_recent": "Foto máis recente", - "sort_title": "Título", - "source": "Fonte", - "stack": "Apilar", - "stack_action_prompt": "{count} apilados", - "stack_duplicates": "Apilar duplicados", - "stack_select_one_photo": "Seleccionar unha foto principal para a pila", - "stack_selected_photos": "Apilar fotos seleccionadas", - "stacked_assets_count": "Apilados {count, plural, one {# activo} other {# activos}}", - "stacktrace": "Rastro da Pila", - "start": "Iniciar", - "start_date": "Data de inicio", - "start_date_before_end_date": "A data de inicio debe ser anterior á data de fin", - "state": "Estado", - "status": "Estado", - "stop_casting": "Deixade de emitir", - "stop_motion_photo": "Deter Foto en Movemento", - "stop_photo_sharing": "Deixar de compartir as súas fotos?", - "stop_photo_sharing_description": "{partner} xa non poderá acceder ás súas fotos.", - "stop_sharing_photos_with_user": "Deixar de compartir as súas fotos con este usuario", - "storage": "Almacenamento", - "storage_label": "Etiqueta de almacenamento", - "storage_quota": "Cota de Almacenamento", - "storage_usage": "{used} de {available} usado", - "submit": "Enviar", - "success": "Éxito", - "suggestions": "Suxestións", - "sunrise_on_the_beach": "Amencer na praia", - "support": "Soporte", - "support_and_feedback": "Soporte e Comentarios", - "support_third_party_description": "A súa instalación de Immich foi empaquetada por un terceiro. Os problemas que experimente poden ser causados por ese paquete, así que por favor, comunique os problemas con eles en primeira instancia usando as ligazóns a continuación.", - "swap_merge_direction": "Intercambiar dirección de fusión", - "sync": "Sincronizar", - "sync_albums": "Sincronizar álbums", - "sync_albums_manual_subtitle": "Sincronizar todos os vídeos e fotos cargados aos álbums de copia de seguridade seleccionados", - "sync_local": "Sincronizar Local", - "sync_remote": "Sincronizar Remoto", - "sync_status": "Estado de Sincronización", - "sync_status_subtitle": "Ver e xestionar o sistema de sincronización", - "sync_upload_album_setting_subtitle": "Crear e subir as súas fotos e vídeos aos álbums seleccionados en Immich", - "tag": "Etiqueta", - "tag_assets": "Etiquetar activos", - "tag_created": "Etiqueta creada: {tag}", - "tag_feature_description": "Navegar por fotos e vídeos agrupados por temas de etiquetas lóxicas", - "tag_not_found_question": "Non atopa unha etiqueta? Crear unha nova etiqueta.", - "tag_people": "Etiquetar Persoas", - "tag_updated": "Etiqueta actualizada: {tag}", - "tagged_assets": "Etiquetados {count, plural, one {# activo} other {# activos}}", - "tags": "Etiquetas", - "tap_to_run_job": "Tocar para executar tarefa", - "template": "Modelo", - "text_recognition": "Recoñecemento de texto", - "theme": "Tema", - "theme_selection": "Selección de tema", - "theme_selection_description": "Establecer automaticamente o tema a claro ou escuro baseándose na preferencia do sistema do seu navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de almacenamento nas celas de activos", - "theme_setting_asset_list_tiles_per_row_title": "Número de activos por fila ({count})", - "theme_setting_colorful_interface_subtitle": "Aplicar cor primaria ás superficies de fondo.", - "theme_setting_colorful_interface_title": "Interface colorida", - "theme_setting_image_viewer_quality_subtitle": "Axustar a calidade do visor de imaxes de detalle", - "theme_setting_image_viewer_quality_title": "Calidade do visor de imaxes", - "theme_setting_primary_color_subtitle": "Elixa unha cor para accións primarias e acentos.", - "theme_setting_primary_color_title": "Cor primaria", - "theme_setting_system_primary_color_title": "Usar cor do sistema", - "theme_setting_system_theme_switch": "Automático (Seguir configuración do sistema)", - "theme_setting_theme_subtitle": "Elixir a configuración do tema da aplicación", - "theme_setting_three_stage_loading_subtitle": "A carga en tres etapas pode aumentar o rendemento da carga pero causa unha carga de rede significativamente maior", - "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", - "they_will_be_merged_together": "Fusionaranse xuntos", - "third_party_resources": "Recursos de Terceiros", - "time": "Hora", - "time_based_memories": "Recordos baseados no tempo", - "time_based_memories_duration": "Número de segundos para mostrar cada imaxe.", - "timeline": "Liña de tempo", - "timezone": "Fuso horario", - "to_archive": "Arquivar", - "to_change_password": "Cambiar contrasinal", - "to_favorite": "Favorito", - "to_login": "Iniciar sesión", - "to_multi_select": "Para a selección múltiple", - "to_parent": "Ir ao pai", - "to_select": "Para seleccionar", - "to_trash": "Lixo", - "toggle_settings": "Alternar configuración", - "toggle_theme_description": "Cambiar tema", - "total": "Total", - "total_usage": "Uso total", - "trash": "Lixo", - "trash_action_prompt": "{count} movidos ao lixo", - "trash_all": "Mover Todo ao Lixo", - "trash_count": "Lixo ({count, number})", - "trash_delete_asset": "Mover ao Lixo/Eliminar Activo", - "trash_emptied": "Lixo baleirado", - "trash_no_results_message": "As fotos e vídeos movidos ao lixo aparecerán aquí.", - "trash_page_delete_all": "Eliminar Todo", - "trash_page_empty_trash_dialog_content": "Quere baleirar os seus activos no lixo? Estes elementos eliminaranse permanentemente de Immich", - "trash_page_info": "Os elementos no lixo eliminaranse permanentemente despois de {days} días", - "trash_page_no_assets": "Non hai activos no lixo", - "trash_page_restore_all": "Restaurar Todo", - "trash_page_select_assets_btn": "Seleccionar activos", - "trash_page_title": "Lixo ({count})", - "trashed_items_will_be_permanently_deleted_after": "Os elementos no lixo eliminaranse permanentemente despois de {days, plural, one {# día} other {# días}}.", - "trigger": "Disparador", - "trigger_asset_uploaded": "Activo subido", - "trigger_asset_uploaded_description": "Actívase cando se carga un activo novo", - "trigger_description": "Un evento que inicia o fluxo de traballo", - "trigger_person_recognized": "Persoa recoñecida", - "trigger_person_recognized_description": "Actívase cando se detecta a unha persoa", - "trigger_type": "TIpo de disparador", - "troubleshoot": "Solucionar problemas", - "type": "Tipo", - "unable_to_change_pin_code": "Non é posible cambiar o código PIN", - "unable_to_check_version": "Non se puido verificar a versión da aplicación ou do servidor", - "unable_to_setup_pin_code": "Non é posible configurar o código PIN", - "unarchive": "Desarquivar", - "unarchive_action_prompt": "{count} eliminados do Arquivo", - "unarchived_count": "{count, plural, other {Desarquivados #}}", - "undo": "Desfacer", - "unfavorite": "Desmarcar como favorito", - "unfavorite_action_prompt": "{count} eliminados de Favoritos", - "unhide_person": "Mostrar persoa", - "unknown": "Descoñecido", - "unknown_country": "País Descoñecido", - "unknown_year": "Ano Descoñecido", - "unlimited": "Ilimitado", - "unlink_motion_video": "Desvincular vídeo en movemento", - "unlink_oauth": "Desvincular OAuth", - "unlinked_oauth_account": "Conta OAuth desvinculada", - "unmute_memories": "Desilenciar Recordos", - "unnamed_album": "Álbum Sen Nome", - "unnamed_album_delete_confirmation": "Está seguro de que quere eliminar este álbum?", - "unnamed_share": "Compartición Sen Nome", - "unsaved_change": "Cambio sen gardar", - "unselect_all": "Deseleccionar todo", - "unselect_all_duplicates": "Deseleccionar todos os duplicados", - "unselect_all_in": "Desmarcar todo en {group}", - "unstack": "Desapilar", - "unstack_action_prompt": "{count} desapilados", - "unstacked_assets_count": "Desapilados {count, plural, one {# activo} other {# activos}}", - "unsupported_field_type": "Tipo de campo non soportado", - "untagged": "Sen etiquetar", - "untitled_workflow": "Fluxo de traballo sen título", - "up_next": "A continuación", - "update_location_action_prompt": "Actualizar a localización de {count} elementos seleccionados con:", - "updated_at": "Actualizado", - "updated_password": "Contrasinal actualizado", - "upload": "Subir", - "upload_concurrency": "Concorrencia de subida", - "upload_details": "Detalles da Carga", - "upload_dialog_info": "Quere facer copia de seguridade do(s) Activo(s) seleccionado(s) no servidor?", - "upload_dialog_title": "Subir Activo", - "upload_errors": "Subida completada con {count, plural, one {# erro} other {# erros}}. Actualice a páxina para ver os novos activos subidos.", - "upload_finished": "Carga finalizada", - "upload_progress": "Restantes {remaining, number} - Procesados {processed, number}/{total, number}", - "upload_skipped_duplicates": "Omitidos {count, plural, one {# activo duplicado} other {# activos duplicados}}", - "upload_status_duplicates": "Duplicados", - "upload_status_errors": "Erros", - "upload_status_uploaded": "Subido", - "upload_success": "Subida exitosa. Actualice a páxina para ver os novos activos subidos.", - "upload_to_immich": "Subir a Immich ({count})", - "uploading": "Subindo", - "uploading_media": "Cargando multimedia", - "url": "URL", - "usage": "Uso", - "use_biometric": "Usar biometría", - "use_current_connection": "usar conexión actual", - "use_custom_date_range": "Usar rango de datas personalizado no seu lugar", - "user": "Usuario", - "user_has_been_deleted": "Este usuario foi eliminado.", - "user_id": "ID de Usuario", - "user_liked": "A {user} gustoulle {type, select, photo {esta foto} video {este vídeo} asset {este activo} other {isto}}", - "user_pin_code_settings": "Código PIN", - "user_pin_code_settings_description": "Xestionar seu código PIN", - "user_privacy": "Privacidade do usuario", - "user_purchase_settings": "Contribución", - "user_purchase_settings_description": "Xestionar a súa contribución", - "user_role_set": "Establecer {user} como {role}", - "user_usage_detail": "Detalle de uso do usuario", - "user_usage_stats": "Estatísticas de uso da conta", - "user_usage_stats_description": "Ver estatísticas de uso da conta", - "username": "Nome de usuario", - "users": "Usuarios", - "users_added_to_album_count": "Engadido/s {count, plural, one {# usuario} other {# usuarios}} ao álbum", - "utilities": "Utilidades", - "validate": "Validar", - "validate_endpoint_error": "Por favor, introduza unha URL válida", - "validation_error": "Erro de validación", - "variables": "Variables", - "version": "Versión", - "version_announcement_closing": "O seu amigo, Alex", - "version_announcement_message": "Ola! Unha nova versión de Immich está dispoñible. Por favor, tome un tempo para ler as notas de lanzamento para asegurarse de que a súa configuración está actualizada para evitar calquera configuración incorrecta, especialmente se usa WatchTower ou calquera mecanismo que xestione a actualización automática da súa instancia de Immich.", - "version_history": "Historial de Versións", - "version_history_item": "Instalado {version} o {date}", - "video": "Vídeo", - "video_hover_setting": "Reproducir miniatura do vídeo ao pasar o rato por riba", - "video_hover_setting_description": "Reproducir miniatura do vídeo cando o rato está sobre o elemento. Mesmo cando está desactivado, a reprodución pode iniciarse pasando o rato sobre a icona de reprodución.", - "videos": "Vídeos", - "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", - "view": "Ver", - "view_album": "Ver Álbum", - "view_all": "Ver Todo", - "view_all_users": "Ver todos os usuarios", - "view_asset_owners": "Ver os propietarios", - "view_details": "Ver detalles", - "view_in_timeline": "Ver na liña de tempo", - "view_link": "Ver ligazón", - "view_links": "Ver ligazóns", - "view_name": "Vista", - "view_next_asset": "Ver seguinte activo", - "view_previous_asset": "Ver activo anterior", - "view_qr_code": "Ver código QR", - "view_similar_photos": "Ver fotos semellantes", - "view_stack": "Ver Pila", - "view_user": "Ver Usuario", - "viewer_remove_from_stack": "Eliminar da Pila", - "viewer_stack_use_as_main_asset": "Usar como Activo Principal", - "viewer_unstack": "Desapilar", - "visibility_changed": "Visibilidade cambiada para {count, plural, one {# persoa} other {# persoas}}", - "visual": "Visual", - "visual_builder": "Construtor visual", - "waiting": "Agardando", - "waiting_count": "Esperando: {count}", - "warning": "Aviso", - "week": "Semana", - "welcome": "Benvido/a", - "welcome_to_immich": "Benvido/a a Immich", - "width": "Ancho", - "wifi_name": "Nome da wifi", - "workflow_delete_prompt": "Estás seguro que queres eliminar este fluxo de traballo?", - "workflow_deleted": "Fluxo de traballo eliminado", - "workflow_description": "Descrición do fluxo de traballo", - "workflow_info": "Información do fluxo de traballo", - "workflow_json": "JSON do fluxo de traballo", - "workflow_json_help": "Edita a configuración do fluxo de traballo en formato JSON. Os cambios sincronizaranse co creador visual.", - "workflow_name": "Nome do fluxo de traballo", - "workflow_navigation_prompt": "Estás seguro que desexar saír sen gardar os cambios?", - "workflow_summary": "Resumo do fluxo de traballo", - "workflow_update_success": "Fluxo de traballo actualizado con éxito", - "workflow_updated": "Fluxo de traballo actualizado", - "workflows": "Fluxos de traballo", - "workflows_help_text": "Os fluxos de traballo automatizan accións nos teus recursos en función de disparadores e filtros", - "wrong_pin_code": "Código PIN incorrecto", - "year": "Ano", - "years_ago": "Hai {years, plural, one {# ano} other {# anos}}", - "yes": "Si", - "you_dont_have_any_shared_links": "Non ten ningunha ligazón compartida", - "your_wifi_name": "O nome da súa wifi", - "zoom_image": "Ampliar Imaxe", - "zoom_to_bounds": "Axustar ao perímetro" -} +{} diff --git a/i18n/gsw.json b/i18n/gsw.json index 0d8b7abf3a..0967ef424b 100644 --- a/i18n/gsw.json +++ b/i18n/gsw.json @@ -1,1517 +1 @@ -{ - "about": "Über Immich", - "account": "Konto", - "account_settings": "Iistelligä", - "acknowledge": "Bestätige", - "action": "Aktion", - "action_common_update": "Aktualisiere", - "actions": "Aktione", - "active": "Aktiv", - "active_count": "Aktive:{count}", - "activity": "Aktivität", - "activity_changed": "Aktivität isch {enabled, select, true {aktiviert} other {deaktiviert}}", - "add": "Aafüege", - "add_a_description": "Beschriebig hinzuäzfüägä", - "add_a_location": "Standort hiifüege", - "add_a_name": "Name hiifüege", - "add_a_title": "Titel hiifüege", - "add_birthday": "Geburtsdatum hiifüege", - "add_endpoint": "Endpunkt hiifüege", - "add_exclusion_pattern": "Ausschlussmuster hiifüege", - "add_location": "Standort hiifüege", - "add_more_users": "Wiiteri Nutzer hiifüege", - "add_partner": "Partner hiifüege", - "add_path": "Pfad hiifüege", - "add_photos": "Fotos hiifüege", - "add_tag": "Tag hiifüege", - "add_to": "Hiifüege zu …", - "add_to_album": "Zum Album hiifüege", - "add_to_album_bottom_sheet_added": "Zu {album} hiigfüegt", - "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", - "add_to_album_bottom_sheet_some_local_assets": "E paar lokali Dateie händ nöd chönne zum Album hiigfüegt werde", - "add_to_album_toggle": "Uswahl umschalte für {album}", - "add_to_albums": "Zu Albe hiifüege", - "add_to_albums_count": "Zu Albe hiifüege ({count})", - "add_to_bottom_bar": "Hiifüege zu", - "add_to_shared_album": "Zum gteilte Album hiifüege", - "add_upload_to_stack": "Upload zum Stapel hiifüege", - "add_url": "URL hiifüege", - "added_to_archive": "Zum Archiv hiigfüegt", - "added_to_favorites": "Zu de Favoritä hiigfüegt", - "added_to_favorites_count": "{count, number} zu de Favoritä hiigfüegt", - "admin": { - "add_exclusion_pattern_description": "Ausschlussmuster hiifüege. Platzhalter wie *, ** und ? werde unterstützt. Um alli Datei in eme Verzeichnis mit em Name „Raw“ z ignoriere, „/Raw/“ verwände.\nUm alli Datei z ignoriere, wo uf „.tif“ änded, „/*.tif“ verwände. Um en absolute Pfad z ignoriere, „/pfad/zum/ignorieren/“ verwände.", - "admin_user": "Administrator", - "asset_offline_description": "Die Datei vo ere externe Bibliothek isch nümme uf de Festplatte und isch i de Papirkorb verschobe worde. Falls d’Datei i dä Bibliothek verschobe worde isch, lueg i diner Ziitliiste noch de neue entsprechende Datei. Um die Datei wiederherzstelle, sorg bitte derfür, dass Immich uf de unten aagegehene Dateipfad cha zgriffe und lass d’Bibliothek neu scanne.", - "authentication_settings": "Authentifizierigsiistellige", - "authentication_settings_description": "Passwort-, OAuth- und anderi Authentifizierigsiistellige verwalte", - "authentication_settings_disable_all": "Bisch du sicher, dass du alli Ahmäldemethode wotsch deaktiviiere? D’Aahmäldig wird komplett deaktiviert.", - "authentication_settings_reenable": "Bruuch en Server-Befehl zum Wiederaktiviiere.", - "background_task_job": "Hintergrundufgabe", - "backup_database": "Datebank-Sicherig erstelle", - "backup_database_enable_description": "Datebank regelmässig sichere", - "backup_keep_last_amount": "Aazahl vo de ältere Sicherige, wo sötted ufbewahrt blibe", - "backup_onboarding_1_description": "Offsite-Kopie i de Cloud oder an eme andere physische Ort.", - "backup_onboarding_2_description": "Lokali Kopie uf verschidene Gerät. Däzu ghöred d’Hauptdateie und e lokali Sicherig vo däne Dateie.", - "backup_onboarding_3_description": "Kopie vo dine Date, inklusiv Originaldateie. Das umfasst 1 Kopie an eme andere Ort und 2 lokali Kopie.", - "backup_onboarding_description": "E 3-2-1 Sicherigsstrategie wird empfohlen, um dini Date z schütze. Du söttisch sowohl Kopie vo dine ufegladne Fotos/Videos wie au vo de Immich-Datebank ufbewahre, um e umfangrichi Sicherigslösung z ha.", - "backup_onboarding_footer": "Wiiteri Informatione zum Sichere vo Immich findes du i de Dokumentation.", - "backup_onboarding_parts_title": "E 3-2-1-Sicherig umfasst:", - "backup_onboarding_title": "Sicherige", - "backup_settings": "Iistellige für d’Datebank-Sicherig", - "backup_settings_description": "Iistellige zur regelmässige Sicherig vo de Datebank. Hinweis: Die Jobs werde nöd überwacht und du wirsch nöd über Fähler informiert.", - "cleared_jobs": "Folgendi Ufgabe zruggsetzt: {job}", - "config_set_by_file": "Isch aktuell i ere Konfigurationsdatei feschtlegt", - "confirm_delete_library": "Bisch du sicher, dass du d’Bibliothek {library} wotsch lösche?", - "confirm_delete_library_assets": "Bisch du sicher, dass du die Bibliothek wotsch löösche? Das löscht {count, plural, one {# enthaltigs Objekt} other {alli # enthaltige Objekt}} us Immich und chan nöd rückgängig gmacht werde. D’Dateie bliibe uf de Festplatt erhalte.", - "confirm_email_below": "Bestätig, indem du unne „{email}“ igisch", - "confirm_reprocess_all_faces": "Bisch du sicher, dass du alli Gsichter nomol wotsch verarbeite? Das löscht au alli Persone, wo scho benennt sind.", - "confirm_user_password_reset": "Bisch du sicher, dass du s’Passwort für {user} wotsch zruggsetze?", - "confirm_user_pin_code_reset": "Bisch du sicher, dass du de PIN-Code vo {user} wotsch zruggsetze?", - "copy_config_to_clipboard_description": "Kopier d’aktuel Systemkonfiguration als JSON-Objekt i d’Zwüscheablage", - "create_job": "Ufgob erstelle", - "cron_expression": "Cron-Ziitahgob", - "cron_expression_description": "Setz s’Scan-Intervall im Cron-Format. Hilf zum Format findes du zum Bispiel bi de Setz s’Scan-Intervall im Cron-Format. Hilf zum Format findes du zum Bispiel bi de Crontab Guru.Crontab Guru.", - "cron_expression_presets": "Vorlag für Cron-Ziitangab", - "disable_login": "Login deaktiviiere", - "duplicate_detection_job_description": "Die Ufgab führt s’maschinelli Lärene für jede Datei us, um Duplikat z finde. Die Ufgab basiert uf de intelligente Suechi", - "exclusion_pattern_description": "Mit Usschlussmuster chönnd Datei und Ordner bim Scanne vo diner Bibliothek ignoriert werde. Das isch nöützlich, wenn du Ordner hesch, wo Datei enthalte, wo du nöd importiere wotsch – zum Bispiel RAW-Dateie.", - "export_config_as_json_description": "Lad d’aktuel Systemkonfiguration als JSON-Datei ab", - "external_libraries_page_description": "Externi Bibliotheksite für Administratore", - "face_detection": "Gsichtererfassig", - "face_detection_description": "Die Ufgab erfasst Gsichter i Dateie mit maschinellem Lärne. Bi Videos wird nume d’Miniaturahsicht bruucht. „Aktualisiere“ verarbeitet alli Dateie nomol. „Zruggsetze“ setzt zusätzlich alli Gsichter zrugg. „Fählendi“ stellt nume nöd verarbeiteti Dateie i d’Warteschlange. Erfassti Gsichter werde derfür i d’Warteschlange gstellt, um si bi dr Gsichtsidentifizierig z’gruppiere – in bestaandi oder neu Persone.", - "facial_recognition_job_description": "Die Ufgab gruppiert nach dr Gsichtererfassig d’erfasste Gsichter zu Persone. „Zruggsetze“ gruppiert alli Gsichter nomol, während „Fählendi“ Gsichter ohni Zuordnig i d’Warteschlange stellt.", - "failed_job_command": "Befähl {command} isch für d’Ufgab {job} gscheitert", - "force_delete_user_warning": "WARNIG: Diä Aktion löscht de Benutzer grad sofort und alli sini Dateie. Das cha nöd rückgängig gmacht werde und d’Dateie chöi nöd widerherstellt werde.", - "image_format": "Format", - "image_format_description": "WebP produziert chliinere Dateie als JPEG, isch aber es bitz langsamer bim Erstelle.", - "image_fullsize_description": "Hochuflösends Bild mit entfernte Metadate, wo bim Zoome bruucht wird", - "image_fullsize_enabled": "Hochuflösendi Vorschaubilder aktiviere", - "image_fullsize_enabled_description": "Generier hochuflösendi Vorschaubilder i Originaluflösig für nöd web-kompatibli Formate. Wenn „Iigbetteti Vorschau bevorzugt“ aktiviert isch, werde iigbetteti Vorschaubilder direkt bruucht. Het kei Einfluss uf web-kompatibli Formate wie JPEG.", - "image_fullsize_quality_description": "Qualität vo de hochuflösende Vorschaubilder vo 1–100. Höcher isch besser, erzeugt aber grösseri Dateie.", - "image_fullsize_title": "Iistellige für hochuflösendi Vorschau", - "image_prefer_embedded_preview": "Iigbetteti Vorschau bevorzugä", - "image_prefer_embedded_preview_setting_description": "Verwänd iigbetteti Vorschaubilder i RAW-Fotos als Grundlag für d’Bildverarbeitig, wenn die verfügbar sind. Das cha bi gewisse Bilder genaueri Farä ergäh, aber d’Qualität vo dr Vorschau isch kameraabhängig und s’Bild cha meh Kompressionsartefakt ufwiise.", - "image_prefer_wide_gamut": "Breits Spektrum bevorzugä", - "image_prefer_wide_gamut_setting_description": "Verwendig vo Display P3 (DCI-P3) für Miniaturahsichte. Dadurch bliibt d’Lebendigkeit vo Bilder mit breite Farbröim besser erhalte, aber d’Bilder chöi uf ältere Gerät mit ere ältere Browserversion es bitz anders uusgseh. sRGB-Bilder werde im sRGB-Format belaah, um Farbschüebe z vermeide.", - "image_preview_description": "Mittelgrosses Bild mit entfernte Metadate, wo bim Aaluegä vo ere einzelne Datei und fürs maschinelli Lärne bruucht wird", - "image_preview_quality_description": "Vorschauqualität vo 1–100. En höcherer Wert isch besser, erzeugt aber grösseri Dateie und cha d’Reaktionsfähigkait vo dr App beeinträchtige. En tieferer Wert cha hingegen d’Qualität vom maschinelle Lärne beeinträchtige.", - "image_preview_title": "Vorschauiistellige", - "image_quality": "Qualität", - "image_resolution": "Uuflösig", - "image_resolution_description": "Höcheri Uflösige chöi meh Detail erhalte, bruuched aber meh Ziit für d’Kodierig, hei grösseri Dateigrössi und chöi d’Reaktionsfähigkait vo Applikatione beeinträchtige.", - "image_settings": "Bildiistellige", - "image_settings_description": "Qualität und Uflösig vo generierte Bilder verwalte", - "image_thumbnail_description": "Chliini Miniaturahsicht mit entfernte Metadate, wo bi dr Aazeig vo Fotosammlige wie dr Ziitliiste bruucht wird", - "image_thumbnail_quality_description": "Qualität vo dr Miniaturahsicht vo 1–100. Höcher isch besser, erzeugt aber grösseri Dateie und cha d’Reaktionsfähigkait vo dr App beeinträchtige.", - "image_thumbnail_title": "Miniaturahsicht-Iistellige", - "import_config_from_json_description": "Importier d’Systemkonfiguration, indem du e JSON-Konfigurationsdatei ufladsch", - "job_concurrency": "{job} (Aazahl gliichziitig laufendi Prozäss)", - "job_created": "Ufgab erstellt", - "job_not_concurrency_safe": "Die Ufgab isch nöd parallelisierigssicher.", - "job_settings": "Ufgabiistellige", - "job_settings_description": "D’gliichziitigi Uusfüerig vo Ufgabe verwalte", - "jobs_delayed": "{jobCount, plural, other {# verzögeret}}", - "jobs_failed": "{jobCount, plural, other {# gschiiteret}}", - "jobs_over_time": "Jobs im Lauf vo dr Ziit", - "library_created": "Bibliothek erstellt: {library}", - "library_deleted": "Bibliothek glöscht", - "library_details": "Bibliotheksdetails", - "library_folder_description": "Gib en Ordner aa zum Importiere. Däre Ordner wird inklusiv alli Underordner nach Bilder und Videos dürgsuecht.", - "library_remove_exclusion_pattern_prompt": "Bisch du sicher, dass du dies Ausschlussmuster wotsch entferne?", - "library_remove_folder_prompt": "Bisch du sicher, dass du dä Import-Ordner wotsch entferne?", - "library_scanning": "Periodisches Scanne", - "library_scanning_description": "Regelmässigs Dürgsueche vo dr Bibliothek iistelle", - "library_scanning_enable_description": "Regelmässigs Scanne vo dr Bibliothek aktiviere", - "library_settings": "Externi Bibliothek", - "library_settings_description": "Iistellige vo externe Bibliotheke verwalte", - "library_tasks_description": "Prüef externe Bibliotheke uf neui und/oder veränderti Medie", - "library_updated": "Aktualisierti Bibliothek", - "library_watching_enable_description": "Überwach externi Bibliotheke uf Dateiänderige", - "library_watching_settings": "Überwach Bibliothek [EXPERIMENTELL]", - "library_watching_settings_description": "Automatisch uf gändereti Dateie prüefe", - "logging_enable_description": "Aktivierä Logging", - "logging_level_description": "Wenn aktiviert, weles Log-Level bruucht wird.", - "logging_settings": "Protokollierig", - "machine_learning_availability_checks": "Verfügbarkeitschecks", - "machine_learning_availability_checks_description": "Verfüegbari Machine-Learning-Server erkenne und bevorzuuge", - "machine_learning_availability_checks_enabled": "Verfügbarkeits-Checks iischalte", - "machine_learning_availability_checks_interval": "Überprüefigsintervall", - "machine_learning_availability_checks_interval_description": "Interval i Millisekunde zwüsche Verfügbarkeits-Checks", - "machine_learning_availability_checks_timeout": "Aafrags-Ziitüberschrittig", - "machine_learning_availability_checks_timeout_description": "Ziitüberschrittig i Millisekunde für Verfügbarkeits-Checks", - "machine_learning_clip_model": "CLIP-Modell", - "machine_learning_clip_model_description": "Der Name eines CLIP-Modells, welches hier aufgeführt ist. Beachte, dass du die Aufgabe \"Intelligente Suche\" für alle Bilder erneut ausführen musst, wenn du das Modell wechselst.", - "machine_learning_duplicate_detection": "Duplikaterkennig", - "machine_learning_duplicate_detection_enabled": "Duplikaterkennung aktivierä", - "machine_learning_duplicate_detection_enabled_description": "Wenn die Option deaktiviert isch, werde exakt identischi Dateie denoch de-dupliziert.", - "machine_learning_duplicate_detection_setting_description": "Verwendig vo CLIP-Embeddings zum Erkenne vo möglicke Duplikate", - "machine_learning_enabled": "Maschinells Lärne aktivierä", - "machine_learning_enabled_description": "Wenn die Option deaktiviert isch, werde alli ML-Funktione unabhäng vo de unten ufgführte Iistellige deaktiviert.", - "machine_learning_facial_recognition": "Gsichtererkennig", - "machine_learning_facial_recognition_description": "Erfass, identifizier und gruppier Gsichter i Bilder", - "machine_learning_facial_recognition_model": "Gsichtererkenigs-Modell", - "machine_learning_facial_recognition_model_description": "D’Modell sind i absteigender Räihefolg vo ihrer Grössi ufgführt. Grösseri Modell sind langsamer und bruuched meh Speicher, liefere aber besseri Resultat. Bitte beachte, dass du d’Gsichtererfassigs-Ufgab für alli Bilder nomol starte muesch, wenn du s’Modell wächselsch.", - "machine_learning_facial_recognition_setting": "Gsichtererkennig aktiviere", - "machine_learning_facial_recognition_setting_description": "Wenn die Option deaktiviert isch, werde d’Bilder nöd für d’Gsichtererkenig kodiert und dr Abschnitt „Persone“ uf dr „Erchunde“-Site wird nöd azeigt.", - "machine_learning_max_detection_distance": "Maximaler Erfassigsabstand", - "machine_learning_max_detection_distance_description": "Maximaler Underschid zwüsche zwei Bilder, um si als Duplikat z betrachtä – i enem Bereich vo 0.001 bis 0.1. Bi höcherne Wert werde meh Duplikate erkennt, aber es cha zu falsch positive Resultat cho.", - "machine_learning_max_recognition_distance": "Maximaler Erkennigsabstand", - "machine_learning_max_recognition_distance_description": "Maximaler Abstand zwüsche zwei Gsichter, wo als di gliichi Person aagluegt werde — vo 0 bis 2. En tüüfere Wert cha verhindere, dass zwei Persone als di gliichi Person igstuft werde, während en höcherer Wert cha verhindere, dass di gliichi Person als zwei verschideni Persone igstuft wird. Bitte beachte: Es isch einfacher, zwei Persone zämmezfüehre als eini Person wieder z trenne — drum nimm, wenn möglech, en tieferer Schwellenwert.", - "machine_learning_min_detection_score": "Mindest-Erfassigswert", - "machine_learning_min_detection_score_description": "Minimali Konfidenzrate für d’Erfassig vo enem Gsicht vo 0–1. Bi tieferne Wert werde meh Gsichter erfasst, aber es cha zu falsch positive Resultat cho.", - "machine_learning_min_recognized_faces": "Mindescht erkannnti Gsichter", - "machine_learning_min_recognized_faces_description": "D’Mindestazahl vo erkannnte Gsichter, die nötig isch, damit e Person erstellt cha werde. En höcherer Wert macht d’Gsichtererkenig präziser, aber erhöht d’Wahrschindlechkeit, dass es Gsicht keiner Person zueordnet wird.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Maschinells Lärne bruuchen, um Text i Bilder z erkenne", - "machine_learning_ocr_enabled": "OCR aktivierä", - "machine_learning_ocr_enabled_description": "Wenn deaktiviert, werde d’Bilder nöd vo dr Texterkenig bearbeitet.", - "machine_learning_ocr_max_resolution": "Maximali Uflösig", - "machine_learning_ocr_max_resolution_description": "Vorschau, wo über däre Uflösig liit, wird unter Bibehalte vom Seitenverhältnis chliiner gmacht. Höcheri Wert sind genauer, bruuched aber meh Ziit für d’Verarbeitig und meh Speicher.", - "machine_learning_ocr_min_detection_score": "Mindescht-Erfassigswert", - "machine_learning_ocr_min_detection_score_description": "Minimali Konfidenzrate für d’Texterkenig vo 0–1. Tieferi Wert führt derzue, dass meh Text erkannt wird, cha aber zu falsch positive Resultat führe.", - "machine_learning_ocr_min_recognition_score": "Mindescht-Erkennigswert", - "machine_learning_ocr_min_score_recognition_description": "Minimali Konfidenzrate für d’Erkenig vo erkanntem Text vo 0–1. Tieferi Wert führt derzue, dass meh Text erkannt wird, cha aber zu falsch positive Resultat führe.", - "machine_learning_ocr_model": "OCR Modäll", - "machine_learning_ocr_model_description": "Server-Modelle sind genauer als mobil Modell, bruuched aber länger zur Verarbeitig und meh Speicher.", - "machine_learning_settings": "Iistelligä fürs maschinelli Lärne", - "machine_learning_settings_description": "Funktione und Iistellige vom maschinelle Lärne verwalte", - "machine_learning_smart_search": "Intelligenti Suechi", - "machine_learning_smart_search_description": "Semantischi Bildsuächi mit CLIP-Iibettige", - "machine_learning_smart_search_enabled": "Intelligenti Suechi aktiviere", - "machine_learning_smart_search_enabled_description": "Wenn die Option deaktiviert isch, werde d’Bilder nöd für d’intelligenti Suechi bruucht.", - "machine_learning_url_description": "D’URL vom Server fürs maschinelli Lärne. Wenn meh als eini URL aagee wird, wird jeder Server einzeln usprobiert, bis einer erfolgrich antwortet – und zwar i dr Räihäfolg vom erschte bis zum letschte. Server, wo nöd antworted, werde vorübergehend ignoriert, bis si wieder verfügbar sind.", - "maintenance_settings": "Wartig", - "maintenance_settings_description": "Immich i de Wartigsmodus versetze.", - "maintenance_start": "Wartigsmodus startä", - "maintenance_start_error": "Wartigsmodus het nöd chönne gstartet werde.", - "manage_concurrency": "Gliichziitigi Uusfüerig verwalte", - "manage_concurrency_description": "Gang zur Job-Site, um d’Job-Parallelität z verwalte", - "manage_log_settings": "Log-Iistellige verwalte", - "map_dark_style": "Dunkle Stil", - "map_enable_description": "Chartefunktion aktiviere", - "map_gps_settings": "Charte- & GPS-Iistellige", - "map_gps_settings_description": "Charte- & GPS-Iistellige verwalte", - "map_implications": "D’Chartefunktion bruucht en externe Tile-Service (tiles.immich.cloud)", - "map_light_style": "Helle Stil", - "map_manage_reverse_geocoding_settings": "Iistellige für d’umgekehrti Geokodierig verwalte", - "map_reverse_geocoding": "Umkherti Geokodierig", - "map_reverse_geocoding_enable_description": "Umkherti Geokodierig aktivere", - "map_reverse_geocoding_settings": "Iistelligä für umkherti Geokodierig", - "map_settings": "Charte", - "map_settings_description": "Charte- und GPS-Iistellige verwalte", - "map_style_description": "URL zu enem style.json-Karte-Theme", - "memory_cleanup_job": "Erinnerige ufrumä", - "memory_generate_job": "Erinnerige-Generierig", - "metadata_extraction_job": "Metadate extrahiere", - "metadata_extraction_job_description": "Metadate extrahiere, wie zum Bispiel GPS, Gsichter und Uflösig us jede Datei", - "metadata_faces_import_setting": "Import vo Gsichter aktiviere", - "metadata_faces_import_setting_description": "Gsichter us EXIF-Dateie vom Bild und Sidecar-Dateie importiere", - "metadata_settings": "Metadate-Iistellige", - "metadata_settings_description": "Metadate-Iistellige verwalte", - "migration_job": "Migration", - "migration_job_description": "Die Ufgab migriert Miniaturahsicht für Dateie und Gsichter i d’neuschte Ordnerstruktur", - "nightly_tasks_cluster_faces_setting_description": "Gsichteridentifizierig uf neu erfasste Gsichter usfüehre", - "nightly_tasks_cluster_new_faces_setting": "Neu Gsichter gruppiere", - "nightly_tasks_database_cleanup_setting": "Datebank-Bereinigigs-Ufgob", - "nightly_tasks_database_cleanup_setting_description": "Alti, abglaufeni Date us dr Datebank bereinige", - "nightly_tasks_generate_memories_setting": "Erinnerige generiere", - "nightly_tasks_generate_memories_setting_description": "Neui Erinnerige us Dateie erstelle", - "nightly_tasks_missing_thumbnails_setting": "Fählendi Miniaturahsicht generiere", - "nightly_tasks_missing_thumbnails_setting_description": "Dateie ohni Miniaturahsicht i d’Warteschlange zur Miniaturahsicht-Generierig hiifüege", - "nightly_tasks_settings": "Iistellige für nächtligi Ufgobe", - "nightly_tasks_settings_description": "Nächtlichi Ufgabe verwalte", - "nightly_tasks_start_time_setting": "Startziit", - "nightly_tasks_start_time_setting_description": "D’Ziit, zu der dr Server mit dr Uusfüerig vo de nächtlige Ufgabe beginnt", - "nightly_tasks_sync_quota_usage_setting": "Kontingentnutzig synchronisiere", - "nightly_tasks_sync_quota_usage_setting_description": "Benutzer-Speicherkontingent basierend uf dr aktuälle Nutzung aktualisiere", - "no_paths_added": "Kei Pfad hiigfüegt", - "no_pattern_added": "Keis Usschlussmuster hiigfüegt", - "note_apply_storage_label_previous_assets": "Hiwiis: Um dr Speicherpfad uf d’vorhär ufgeladene Dateie aazwände, starte dr", - "note_cannot_be_changed_later": "HINWEIS: Das cha spöter nümme gänderet werde!", - "notification_email_from_address": "Absenderadresse", - "notification_email_from_address_description": "E-Mail-Adrässe vom Sender, zum Bispiel: \"Immich Photo Server noreply@example.com\". Stell sicher, dass du e Adrässe bruuchsch, wo du berechtigt bisch z bruuchä.", - "notification_email_host_description": "Host vom E-Mail-Server (z.B. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Zertifikats-Fähler ignoriere", - "notification_email_ignore_certificate_errors_description": "TLS-Zertifikatsvalidierig-Fähler ignoriere (nöd empfehlenswert)", - "notification_email_password_description": "Passwort für d’Aahmäldig am E-Mail-Server", - "notification_email_port_description": "Port vom E-Mail-Server (z.B. 25, 465 oder 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Benutze SMTPS (SMTP über TLS)", - "notification_email_sent_test_email_button": "Test-E-Mail verschicke und speichere", - "notification_email_setting_description": "Iistellige für E-Mail-Benachrichtigige", - "notification_email_test_email": "Test-E-Mail sende", - "notification_email_test_email_failed": "D’Test-E-Mail het nöd chenne verschickt werde, bitte prüef dini Ahgobe", - "notification_email_test_email_sent": "Es isch e Test-E-Mail an {email} verschickt worde. Bitte prüef din Posteingang.", - "notification_email_username_description": "Benutzername, wo bi dr Aahmäldig am E-Mail-Server bruucht wird", - "notification_enable_email_notifications": "E-Mail-Benochrichtigige aktiviere", - "notification_settings": "Benochrichtigigs-Iistellige", - "notification_settings_description": "Benochrichtigigs-Iistellige (inkl. E-Mail) verwalte", - "oauth_auto_launch": "Auto-Start", - "oauth_auto_launch_description": "Automatischer Start vom OAuth-Aahmäldvorgang bim Ufruäfä vo dr Aahmäldigs-Site", - "oauth_auto_register": "Automatischi Registrierig", - "oauth_auto_register_description": "Automatischi Registrierig vo neue Benutzer nach dr OAuth-Aahmäldig", - "oauth_button_text": "Button-Text", - "oauth_client_secret_description": "Erforderlich, wenn PKCE (Proof Key for Code Exchange) nöd vom OAuth-Anbieter unterstützt wird", - "oauth_enable_description": "Ahmeldig mit OAuth", - "oauth_mobile_redirect_uri": "Mobile Umleitigs-URI", - "oauth_mobile_redirect_uri_override": "Mobile Umleitigs-URI überschriebe", - "oauth_mobile_redirect_uri_override_description": "Iischalte, wenn dr OAuth-Anbieter kei mobile URI wie „{callback}“ erlaubt", - "oauth_role_claim": "Rolle-Claim", - "oauth_role_claim_description": "Gib automatisch Admin-Zugriff basierend uf s’Vorhande si vo däm Claim. Dr Claim cha entweder „user“ oder „admin“ si.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth-Aahmäldigs-Iistellige verwalte", - "oauth_settings_more_details": "Wiiteri Informatione zu däre Funktion findsch du i de Dokumentation.", - "oauth_storage_label_claim": "Speicherpfad-Bezeichnig", - "oauth_storage_label_claim_description": "D’Speicherpfad-Bezeichnig vom Benutzer automatisch uf dr Wert vo däre Ahgob setze.", - "oauth_storage_quota_claim": "Speicherkontingentahgob", - "oauth_storage_quota_claim_description": "Setz s’Speicherkontingent vom Benutzer automatisch uf dr ahgebeni Wert.", - "oauth_storage_quota_default": "Standard-Speicherplatz-Kontingent (GiB)", - "oauth_storage_quota_default_description": "Kontingent i GiB, wo bruucht wird, wenn keins übermittelt wird.", - "oauth_timeout": "Ziitüberschrittig bi Aafrog", - "oauth_timeout_description": "Ziitüberschrittig für Aafroge i Millisekunde", - "ocr_job_description": "Bruuch Machine Learning zur Erkenig vo Text i Bilder", - "password_enable_description": "Mit E-Mail und Passwort aahmälde", - "password_settings": "Passwort-Aahmeldig", - "password_settings_description": "Passwort-Aahmäldigs-Iistellige verwalte", - "paths_validated_successfully": "Alli Pfad erfolgrich prüeft", - "person_cleanup_job": "Persone ufrumä", - "queue_details": "Warteschlangedetails", - "queues": "Uuftrags-Warteschlange", - "queues_page_description": "Uuftragswarteschlange-Adminsiite", - "quota_size_gib": "Kontingent (GiB)", - "refreshing_all_libraries": "Alli Bibliotheke aktualisierä", - "registration": "Admin-Regischtrierig", - "registration_description": "Da du dr erschte Benutzer im System bisch, wird dir d’Rolle vom Administrator zuegwise, womit du für d’Verwaltigs-Ufgabe verantwortlich bisch. Wiiteri Benutzer werde vo dir erstellt.", - "remove_failed_jobs": "Gscheitereti Ufgabe entferne", - "require_password_change_on_login": "Benutzer mues s’Passwort bim erschte Login ändere", - "reset_settings_to_default": "Iistellige uf Standard zruggsetze", - "reset_settings_to_recent_saved": "Iistellige uf d’letzt gespeicherte Iistellige zruggsetze", - "scanning_library": "Bibliothek scanne", - "search_jobs": "Suechufgobe…", - "send_welcome_email": "Begrüssigsmail sende", - "server_external_domain_settings": "Externi Domain", - "server_external_domain_settings_description": "Domäne für öffentlich freigäbeni Links, inkl. http(s)://", - "server_public_users": "Öffentlichi Benutzer", - "server_public_users_description": "Beim Hiifüege vo enem Benutzer zu fregäbeni Albene werde alli Benutzer (Name und E-Mail) ufgführt. Wenn die Option deaktiviert isch, isch d’Benutzerlist nume für Administratore verfügbar.", - "server_settings": "Server-Iistellige", - "server_settings_description": "Server-Iistellige verwalte", - "server_stats_page_description": "Server-Statistik-Site für Administratore", - "server_welcome_message": "Willkommensnochricht", - "server_welcome_message_description": "Gscheitereti Ufgobe entferne.", - "settings_page_description": "Site mit dr Admin-Iistellige", - "sidecar_job": "Sidecar Metadate", - "sidecar_job_description": "Mit däre Ufgab werde Filialdatei-Metadate im Dateisystem entdeckt oder synchronisiert", - "slideshow_duration_description": "Duur vo dr Aazeig vo jedem Bild i Sekunde", - "smart_search_job_description": "Die Ufgob wendet s’maschinelli Lärne uf Dateie a, um d’intelligenti Suechi z ermögliche", - "storage_template_date_time_description": "Dr Erstelligs-Ziitstempel vo dr Datei wird für d’Datum- und Ziit-Information bruucht", - "storage_template_date_time_sample": "Bispiel-Ziitpunkt {date}", - "storage_template_enable_description": "Speicher-Vorlage-Engine aktiviere", - "storage_template_hash_verification_enabled": "Hash-Prüefig aktiviert", - "storage_template_hash_verification_enabled_description": "Aktiviert d’Hash-Verifizierig. Deaktivier die Option nur, wenn du dir über d’damit verbunde uuswirkige im chlare bisch", - "storage_template_migration": "Migration vo Speicher-Vorlage", - "storage_template_migration_description": "Die Ufgab wendet d’aktuel {template} uf zuvor ufgeladene Dateie a", - "storage_template_migration_info": "D’Speicher-Vorlage wird alli Dateierweiterige i Chliibuchstabe umwandle. Vorlageänderige gältet nume für neui Dateie. Um d’Vorlage rückwirkend uf scho ufgeladeni Assets aazwände, führ dr {job} us.", - "storage_template_migration_job": "Speicher-Vorlage-Migrations-Ufgob", - "storage_template_more_details": "Wiiteri Details zu däre Funktion findsch du unter Speicher-Vorlage und dere Implikatione", - "storage_template_onboarding_description_v2": "Wenn aktiviert, werdend Dateie automatisch nach enere benutzerdefinierte Vorlag organisiert. Für meh Informatione siehe d’Dokumentation.", - "storage_template_path_length": "Ungefähres Pfadlänge-Limit: {length, number}/{limit, number}", - "storage_template_settings": "Speichervorlag", - "storage_template_settings_description": "D’Ordnerstruktur und dr Dateiname vo dr ufgeladene Datei verwalte", - "storage_template_user_label": "{label} isch d’Speicherpfad-Bezeichnig vom Benutzer", - "system_settings": "Syschtem-Iistellige", - "tag_cleanup_job": "Tags ufrumä", - "template_email_available_tags": "Du chasch die folgende Variable i diner Vorlage bruuche: {tags}", - "template_email_if_empty": "Wenn d’Vorlage leer isch, wird d’Standard-E-Mail-Vorlag bruucht.", - "template_email_invite_album": "Iihladig zum Album", - "template_email_preview": "Vorschau", - "template_email_settings": "E-Mail-Vorlage", - "template_email_update_album": "Aktualisierts Album", - "template_email_welcome": "Willkommens-E-Mail", - "template_settings": "Benochhrichtigungsvorlage", - "template_settings_description": "Benutzerdefinierti Vorlag für Benachrichtigige verwalte", - "theme_custom_css_settings": "Benutzerdefinierts CSS", - "theme_custom_css_settings_description": "Mit Cascading Style Sheets (CSS) cha s’Design vo Immich aapasst werde.", - "theme_settings": "Theme-Iistellige", - "theme_settings_description": "Aapassig vo dr Immich-Web-Oberflächi", - "thumbnail_generation_job": "Miniaturahsichte generiere", - "thumbnail_generation_job_description": "Die Ufgob erzüügt grossi, chliini und unscharfi Miniaturahsicht für jede einzelne Datei, sowie Miniaturahsicht für jedi Person", - "transcoding_acceleration_api": "Beschleunigungs-API", - "transcoding_acceleration_api_description": "D’Schnittstell, wo mit em Gerät interagiert, um d’Transkodierig z beschleunige. Bi däre Iistellig handelt es sich um d’„bestmöglichi Lösig“: Bi enem Fähler wird uf d’Software-Transkodierig zrugggriffe. Abhängig vo dr bruuchte Hardware cha VP9 funktioniere oder au nöd.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA-GPU erforderlich)", - "transcoding_acceleration_qsv": "Quick Sync (fordert e Intel-CPU vo dr 7. Generation oder höcher)", - "transcoding_acceleration_rkmpp": "RKMPP (nur bi Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Zueglasseni Audio-Codecs", - "transcoding_accepted_audio_codecs_description": "Uswahl vo de Audio-Codecs, wo nöd transkodiert werde müend. Wird nume für bestimmti Transkodierigspolicies bruucht.", - "transcoding_accepted_containers": "Akzeptierte Container", - "transcoding_accepted_containers_description": "Wähl us, welche Container nöd zu MP4 geremuxt werde sölled. Wird nume für bestimmti Transkodierigspolicies bruucht.", - "transcoding_accepted_video_codecs": "Akzeptierte Video-Codecs", - "transcoding_accepted_video_codecs_description": "Uswahl vo de Video-Codecs, wo nöd transkodiert werde müend. Wird nume für bestimmti Transkodierigspolicies bruucht.", - "transcoding_advanced_options_description": "Iistellige, wo vo de meiste Benutzer nöd gänderet werde müend", - "transcoding_audio_codec": "Audio-Codec", - "transcoding_audio_codec_description": "Opus isch d’hochwertigsti Option, het aber e geringeri Kompatibilität mit alte Gerät oder alte Software.", - "transcoding_bitrate_description": "Videos, wo d’maximali Bitrate überschritte oder i enem nöd akzeptierte Format vorlieged", - "transcoding_codecs_learn_more": "Um meh über d’hier bruuchte Terminologie z erfahre, lueg di FFmpeg-Dokumentation für dr H.264-Codec, dr HEVC-Codec und dr VP9-Codec a.", - "transcoding_constant_quality_mode": "Modus für konstanti Qualität", - "transcoding_constant_quality_mode_description": "ICQ isch besser als CQP, aber eini Hardware-Beschleunigungsgeräte unterstützed dä Modus nöd. Wenn die Option gsetzt wird, wird dr aagee Modus bevorzugt, sobald qualitätsbasierti Kodierig bruucht wird. Wird vo NVENC ignoriert, da s’ICQ nöd unterstützt.", - "transcoding_constant_rate_factor": "Faktor vo dr konstante Rate (-crf)", - "transcoding_constant_rate_factor_description": "Videoqualitätsstufä. Typischi Wärt sind 23 für H.264, 28 für HEVC, 31 für VP9 und 35 für AV1. Ein niedrigere Wert isch besser, erzüügt aber grösseri Dateie.", - "transcoding_disabled_description": "Videos nöd transkodieren, das cha d’Wiedergob uf einige Gerät beeihträchtige", - "transcoding_encoding_options": "Kodierigsoptione", - "transcoding_encoding_options_description": "Setz Codec, Uflösig, Qualität und anderi Option für d’kodierte Videos", - "transcoding_hardware_acceleration": "Hardware-Beschlünigung", - "transcoding_hardware_acceleration_description": "Experimentell: schnälleri Transkodierig, cha aber d’Qualität bi gliicher Bitrate verringerä", - "transcoding_hardware_decoding": "Hardware-Dekodierig", - "transcoding_hardware_decoding_setting_description": "Ermöglicht e Ende-zu-Ende-Beschleunigung, statt nume d’Codierig z beschleunige. Das funktionier vielleicht nöd bi allne Videos.", - "transcoding_max_b_frames": "Maximali B-Frames", - "transcoding_max_b_frames_description": "Höcheri Wert verbessered d’Komprimierungseffizienz, verlangsamed aber d’Kodierig. Isch vielleicht nöd kompatibel mit dr Hardware-Beschlünigung vo ältere Grät. 0 deaktiviert d’B-Frames, während -1 dä Wert automatisch setzt.", - "transcoding_max_bitrate": "Maximali Bitrate", - "transcoding_max_bitrate_description": "S’Feschtlege vo ener maximalen Bitrate cha d’Dateigrössi vorhersagbarer mache, ohni dass d’Qualität drunter liidet. Bi 720p sind typischi Wert 2600 kbit/s für VP9 oder HEVC oder 4500 kbit/s für H.264. Deaktiviert, wenn dr Wert uf 0 gsetzt isch. Wenn kei Einheit aagee wird, wird vo k (für kbit/s) usgange; drum sind 5000, 5000k und 5M (für Mbit/s) gliich.", - "transcoding_max_keyframe_interval": "Maximals Keyframe-Intervall", - "transcoding_max_keyframe_interval_description": "Legt dr maximale Frame-Abstand zwüsche Keyframes fescht. Tieferi Wert verschlechtered d’Komprimierungseffizienz, verbessere aber d’Suechziite und chöi d’Qualität i Szene mit schnälle Bewegige verbessere. Bi 0 wird dä Wert automatisch gstellt.", - "transcoding_optimal_description": "Videos mit ere höcheri Uflösig als dr Zieluflösig oder i enem nöd akzeptierte Format", - "transcoding_policy": "Transkodierigsrichtlinie", - "transcoding_policy_description": "Bestimm, wenn es Video transkodiert wird", - "transcoding_preferred_hardware_device": "Bevorzugts Hardwaregrät", - "transcoding_preferred_hardware_device_description": "Gilt nume für VAAPI und QSV. Legt dr für d’Hardware-Transkodierig bruuchte dri-Node fescht.", - "transcoding_preset_preset": "Voriistellig (-preset)", - "transcoding_preset_preset_description": "Komprimierigsgschwindigkeit. E langsamere Voriistellig erzeugt chliinere Dateie und erhöht d’Qualität, wenn mer e gewisse Bitrate aastrebt. VP9 ignoriert Gschwindigkeite über „Schnäller“.", - "transcoding_reference_frames": "Referenz-Frames", - "transcoding_reference_frames_description": "D’Aazahl vo de Bilder, uf die bi dr Komprimierig vo enem bestimmte Bild verwiese wird. Höcheri Wert verbessere d’Komprimierungseffizienz, verlangsamed aber d’Kodierig. 0 setzt dä Wert automatisch.", - "transcoding_required_description": "Nume Videos i enem nöd akzeptierte Format", - "transcoding_settings": "Iistellige für d’Video-Transkodierig", - "transcoding_settings_description": "Verwalte, welche Videos transkodiert werde und wie die verarbeitet werdend", - "transcoding_target_resolution": "Ziel-Uuflösig", - "transcoding_target_resolution_description": "Höcheri Uflösige chöi meh Detail erhalte, bruuched aber meh Ziit für d’Codierig, hei grösseri Dateigrössi und chöi d’Reaktionsziit vo dr Applikation beeinträchtige.", - "transcoding_temporal_aq": "Temporäre AQ", - "transcoding_temporal_aq_description": "Gilt nume für NVENC. Ziitlich adaptiv Quantisierung verbessert d’Qualität vo Szene mit hohe Detailfülle und geringe Bewegige. Das isch vielleicht nöd kompatibel mit ältere Gerät.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Höcheri Wert führe zu ener schnällere Kodierig, lah aber em Server weniiger Spielraum für d’Verarbeitig vo andere Ufgabe im aktive Zustand. Dä Wert sött nöd höcher si als d’Aazahl vo de CPU-Kerne. Maximiert d’Uuslaschtig, wenn dr Wert uf 0 gsetzt isch.", - "transcoding_tone_mapping": "Farbton-Mapping", - "transcoding_tone_mapping_description": "Versuecht, s’Ussseh vo HDR-Videos bi dr Konvertierig i SDR z’bibehalte. Jeder Algorithmus git unterschidlichi Kompromiss bi Farbe, Detail und Helligkeit. Hable bibhält Detail, Mobius bihält d’Farbe und Reinhard bibhält d’Helligkeit.", - "transcoding_transcode_policy": "Transcodierigsrichtlinie", - "transcoding_transcode_policy_description": "Richtlinie, wenn es Video transkodiert werde söll. HDR-Videos werde immer transkodiert (usser wenn d’Transkodierig deaktiviert isch).", - "transcoding_two_pass_encoding": "Two-Pass Kodierig", - "transcoding_two_pass_encoding_setting_description": "Führt e Transkodierig i zwei Durchgäng dur, um besseri kodierti Videos z erzeugen. Wenn d’maximale Bitrate aktiviert isch (erforderlich für d’Verwendig mit H.264 und HEVC), bruucht dä Modus e Bitratebereich, wo uf dr maximale Bitrate basiert, und ignoriert CRF. Für VP9 cha CRF bruucht werde, wenn d’maximale Bitrate deaktiviert isch.", - "transcoding_video_codec": "Video-Codec", - "transcoding_video_codec_description": "VP9 het e hohe Effizienz und Web-Kompatibilität, bruucht aber länger für d’Transkodierig. HEVC bietet e ähnlichi Leistig, isch aber weniger web-kompatibel. H.264 isch wiitgehend kompatibel und lässt sich schnäll transkodiere, erzüügt aber viel grösseri Dateie. AV1 isch dr effizientischte Codec, wird aber vo ältere Gerät nöd unterstützt.", - "trash_enabled_description": "Papierkorbfunktionen aktiviere", - "trash_number_of_days": "Aahzahl vo de Täg", - "trash_number_of_days_description": "Aazahl vo de Täg, wo d’Objekt im Papirkorb bliibe, bevor sie endgültig entfernet werded", - "trash_settings": "Papierkorbihstellige", - "trash_settings_description": "Papierkorbihstellige verwalte", - "unlink_all_oauth_accounts": "Us allne OAuth Kontene uslogge", - "unlink_all_oauth_accounts_description": "Denk dra, alli OAuth-Konto z deaktiviere, bevor du zu enem neue Anbieter migrierisch.", - "unlink_all_oauth_accounts_prompt": "Bisch du sicher, dass du alli OAuth-Konto deaktivieren wotsch? Die Aktion cha nöd rückgängig gmacht werde und setzt zusätzlich d’OAuth-ID vo alli Benutzer zrugg.", - "user_cleanup_job": "Benutzer uufrumä", - "user_delete_delay": "Das Konto und d’Dateie vo {user} werde in {delay, plural, one {eim Tag} other {# Täg}} für e permanenti Löschig plant.", - "user_delete_delay_settings": "Verzögerig für s’Lösche vo Benutzer", - "user_delete_delay_settings_description": "Gibt d’Aazahl vo de Täg a bis zur endgültige Löschung vo enem Konto und sini Dateie. Dr Benutzer-Löschauftrag wird täglich um Mitternacht usgführt, um z prüefe, ob d’Benutzer zur Löschung bereit sind. Änderige an däre Iistellige werde erscht bi dr nächschte Uusfüerig berücksichtigt.", - "user_delete_immediately": "S’Konto und d’Dateie vo {user} werde sofort für e permanente Löschung i d’Warteschlange gstellt.", - "user_delete_immediately_checkbox": "Benutzer und Dateie zur sofortige Löschig i d’Warteschlange stelle", - "user_details": "Benutzerdetails", - "user_management": "Benutzerverwaltig", - "user_password_has_been_reset": "S’Passwort vom Benutzer isch zruggsetzt worde:", - "user_password_reset_description": "Bitte gib em Benutzer s’temporäre Passwort und informier ihn, dass s’Passwort bim nächschte Login ändert werde muen.", - "user_restore_description": "Das Konto vo {user} wird wiederhergstellt.", - "user_restore_scheduled_removal": "Wiederherstellig vom Benutzer – planti Entfernig am {date, date, long}", - "user_settings": "Benutzerihstellige", - "user_settings_description": "Benutzerihstellige verwalte", - "user_successfully_removed": "Dr Benutzer {email} isch erfolgrich entfernt worde.", - "users_page_description": "Administrator-Benutzersiite", - "version_check_enabled_description": "Versionsprüefig akivierä", - "version_check_implications": "D’Funktion zur Versionsprüefig basiert uf regelmässiger Kommunikazion mit GitHub.com", - "version_check_settings": "Versionsprüefig", - "version_check_settings_description": "Aktiviere/Deaktivier d’Benochrichtigung über neui Versione", - "video_conversion_job": "Videos transkodiere", - "video_conversion_job_description": "Diä Ufgob transkodiert Videos, um d’Kompatibilität mit Browser und Gerät z verbessere" - }, - "admin_email": "Administrator E-Mail", - "admin_password": "Administrator Passwort", - "administration": "Verwaltig", - "advanced": "Erwiitert", - "advanced_settings_enable_alternate_media_filter_subtitle": "Bruuch die Option, um Medie während dr Synchronisierung nach andere Kriterie z filtere. Versuech das nume, wenn Problem mit dr Erkenig vo allne Albe dur d’App auftrete.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTELL] Benutz alternative Filter für d’Synchronisierig vo de Geräte-Albe", - "advanced_settings_log_level_title": "Log-Level: {level}", - "advanced_settings_prefer_remote_subtitle": "Einigi Gerät sind sehr langsam bim Lade vo lokale Vorschaubilder. Aktivier die Iistellige, um stattdesse d’Server-Bilder z lade.", - "advanced_settings_prefer_remote_title": "Server-Bilder bevorzuge", - "advanced_settings_proxy_headers_subtitle": "Definier en Proxy-Header, wo Immich bi jede Netzwerk-Aafrog mitschicke söll", - "advanced_settings_proxy_headers_title": "Benutzerdefinierti Proxy-Header [Experimentell]", - "advanced_settings_readonly_mode_subtitle": "Aktiviert dr schriibgschützte Modus, i dem d’Fotos nume aazeigt werde chöi. Funktione wie mehri Bilder uswähle, teile, übertrage und lösche sind deaktiviert. Aktivier/Deaktivier dr schriibgschützte Modus über dr Benutzer-Avatar uf em Hauptbildschirm", - "advanced_settings_readonly_mode_title": "Schriibgschützte Modus", - "advanced_settings_self_signed_ssl_subtitle": "Verifizierig vo SSL-Zertifikate vom Server überspringe. Nötig bi selbstsignierte Zertifikate.", - "advanced_settings_self_signed_ssl_title": "Selbstsignierti SSL-Zertifikate erlaubä [Experimentell]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatisches Lösche oder Wiederherstellig vo ener Datei uf däm Gerät, wenn die Aktion im Web durgfüert wird", - "advanced_settings_sync_remote_deletions_title": "Mit Server-Löschige synchronisiere [Experimentell]", - "advanced_settings_tile_subtitle": "Erwiiterti Benutzer-Iistellige", - "advanced_settings_troubleshooting_subtitle": "Erwiterti Funktione zur Fehlersuechi aktiviere", - "advanced_settings_troubleshooting_title": "Fählersuechi", - "age_months": "Alter {months, plural, one {# Monat} other {# Monate}}", - "age_year_months": "Alter 1 Johr, {months, plural, one {# Monat} other {# Monate}}", - "age_years": "Alter {years, plural, one {# Johr} other {# Johre}}", - "album": "Album", - "album_added": "Album hinzuegfüegt", - "album_added_notification_setting_description": "Erhalt e E-Mail-Benochrichtigung, wenn du zu enem freigäbene Album hinzugfüegt wirsch", - "album_cover_updated": "Album-Cover aktualisiert", - "album_delete_confirmation": "Bisch du sicher, dass du s’Album {album} wotsch lösche?", - "album_delete_confirmation_description": "Wenn das Album gteiltt worde isch, chönnt anderi Benutzer nümme druf zuegriife.", - "album_deleted": "Album glöscht", - "album_info_card_backup_album_excluded": "USGSCHLOSSÄ", - "album_info_card_backup_album_included": "IHGSCHLOSSÄ", - "album_info_updated": "Album-Infos aktualisiert", - "album_leave": "Album verloh?", - "album_leave_confirmation": "Bisch du sicher, dass du s’Album {album} verla lasse wotsch?", - "album_name": "Albumname", - "album_options": "Albumoptione", - "album_remove_user": "Nutzer entfernä?", - "album_remove_user_confirmation": "Bisch du sicher, dass du {user} entferne wotsch?", - "album_search_not_found": "Kei Albe gfunde, wo zur Suechi passe", - "album_share_no_users": "Es gseht so us, als hetsch du das Album mit allne Benutzer gteiltt oder du hesch kei Benutzer, mit dene du chasch teile.", - "album_summary": "Album Zämmefassig", - "album_updated": "Album aktualisiert", - "album_updated_setting_description": "Erhalt e E-Mail-Benachrichtigig, wenn e freigähs Album neui Dateie enthält", - "album_user_left": "{album} verlasse", - "album_user_removed": "{user} entfernt", - "album_viewer_appbar_delete_confirm": "Bisch du sicher, dass du das Album us dim Konto wotsch lösche?", - "album_viewer_appbar_share_err_delete": "Album het nöd chenne glöscht werde", - "album_viewer_appbar_share_err_leave": "Album het nöd chenne verloh werde", - "album_viewer_appbar_share_err_remove": "Bi dr Löschung vo Element us em Album isch es Problem uufgetrete", - "album_viewer_appbar_share_err_title": "Dr Titel het nöd chönne gänderet werde", - "album_viewer_appbar_share_leave": "Album verloh", - "album_viewer_appbar_share_to": "Teile über", - "album_viewer_page_share_add_users": "Nutzer hinzufüegä", - "album_with_link_access": "Lah jede mit em Link d’Fotos und d’Persone i däm Album aaluege.", - "albums": "Albene", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albene}}", - "albums_default_sort_order": "Standard Album Sortierig", - "albums_default_sort_order_description": "Sortierräihefolg vo de Dateie bi dr Erstellig vo neue Albene.", - "albums_feature_description": "Sammlig vo Albe, wo mit andere Benutzer gteiltt werde chönnd.", - "albums_on_device_count": "Albene uf dem Grät ({count})", - "all": "Alli", - "all_albums": "Alli Albene", - "all_people": "Alli Persone", - "all_videos": "Alli Videos", - "allow_dark_mode": "Dunkle-Modus erlaube", - "allow_edits": "Bearbeitig erlaube", - "allow_public_user_to_download": "Erlaub öffentliche Benutzer z’downloade", - "allow_public_user_to_upload": "Erlaub öffentliche Benutzer ufezlade", - "allowed": "Erlaubt", - "alt_text_qr_code": "QR-Code Bild", - "anti_clockwise": "Gäge de Uhrzeigersinn", - "api_key": "API-Schlüssel", - "api_key_description": "Dä Wert wird nume einisch aazeigt. Bitte kopier ihn, bevor du s’Fänschter schliessisch.", - "api_key_empty": "Din API-Schlüssel-Name dörf nöd leer si", - "api_keys": "API-Schlüssel", - "app_architecture_variant": "Variante (Architektur)", - "app_bar_signout_dialog_content": "Bisch du sicher, dass du dich abmälde wotsch?", - "app_bar_signout_dialog_ok": "Jo", - "app_bar_signout_dialog_title": "Abmälde", - "app_download_links": "App Download Links", - "app_settings": "App-Ihstellige", - "app_stores": "App Stores", - "app_update_available": "App Update verfüegbar", - "appears_in": "Erschiint in", - "apply_count": "Aahwendä ({count, number})", - "archive": "Archiv", - "archive_action_prompt": "{count} zum Archiv hinzuegfüegt", - "archive_or_unarchive_photo": "Foti archivierä bzw. Archvierig ufhebä", - "archive_page_no_archived_assets": "Kei archivierti Inhält gfundä", - "archive_page_title": "Archiv ({count})", - "archive_size": "Archivgrössi", - "archive_size_description": "Archivgrössi für Downloads konfiguriere (in GiB)", - "archived": "Archiviert", - "archived_count": "{count, plural, other {# archiviert}}", - "are_these_the_same_person": "Isch das di gliichi Person?", - "are_you_sure_to_do_this": "Bisch du sicher, dass du das wotsch mache?", - "asset_action_delete_err_read_only": "Schriibgschützti Inhält chönd nöd glöscht werde, überspringe", - "asset_action_share_err_offline": "Die Offline-Date chönnd nöd glesä werdä, überspringä", - "asset_added_to_album": "Zum Album hinzugfüegt", - "asset_adding_to_album": "Hinzufüegä zum Album…", - "asset_description_updated": "Die Beschribiig vo de Datei isch aktualisiert worde", - "asset_filename_is_offline": "Datei {filename} isch offline", - "asset_has_unassigned_faces": "Datei het nöd zuegwiseni Gsichter", - "asset_hashing": "Berechni Prüfsummi…", - "asset_list_group_by_sub_title": "Gruppiere noch", - "asset_list_layout_settings_dynamic_layout_title": "Dynamischs Layout", - "asset_list_layout_settings_group_automatically": "Automatisch", - "asset_list_layout_settings_group_by": "Gruppierä Elemente noch", - "asset_list_layout_settings_group_by_month_day": "Monet + Tag", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Iistellige fürs Foto-Gitter-Layout", - "asset_list_settings_title": "Fotigitter", - "asset_offline": "Datei offline", - "asset_offline_description": "Die externi Datei isch nümme uf em Datenträger vorhande. Bitte wend dich ah din Immich-Administrator, um Hilf z becho.", - "asset_restored_successfully": "Datei erfolgriich wiederhergstellt", - "asset_skipped": "Übersprungä", - "asset_skipped_in_trash": "Im Papierkorb", - "asset_trashed": "Datei glöscht", - "asset_troubleshoot": "Datei Fehlerbehebig", - "asset_uploaded": "Ufeglade", - "asset_uploading": "Ufelade…", - "asset_viewer_settings_subtitle": "Verwaltig vo de Iistellige für d’Foto-Aazeig", - "asset_viewer_settings_title": "Foto-Aazeig", - "assets": "Dateie", - "assets_added_count": "{count, plural, one {# Datei} other {# Dateie}} hinzugefügt", - "assets_added_to_album_count": "{count, plural, one {# Datei} other {# Dateie}} zum Album hinzugefügt", - "assets_added_to_albums_count": "{assetTotal, plural, one {# Datei} other {# Dateie}} zu {albumTotal, plural, one {# Album} other {# Albene}} hinzugefügt", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Datei chan}other {Dateie chönnd}} nöd zum Album hinzuegfüegt werde", - "assets_cannot_be_added_to_albums": "{count, plural, one {Datei chan} other {Dateie chönnd}} nöd zu dene Albe hinzuegfüegt werde", - "assets_count": "{count, plural, one {# Datei} other {# Dateie}}", - "assets_deleted_permanently": "{count} Elemänt(e) permanent glöscht", - "assets_deleted_permanently_from_server": "{count} Elemänt(e) permanent vom Immich-Server glöscht", - "assets_downloaded_failed": "{count, plural, one {# Datei abeglade – {error} gschiiteret} other {# Dateie abeglade – {error} gschiiteret}}", - "assets_downloaded_successfully": "{count, plural, one {# Datei erfolgreich abeglade} other {# Dateie erfolgreich abeglade}}", - "assets_moved_to_trash_count": "{count, plural, one {# Datei} other {# Dateie}} i de Papirkorb verschobe", - "assets_permanently_deleted_count": "{count, plural, one {# Datei} other {# Dateie}} endgültig glöscht", - "assets_removed_count": "{count, plural, one {# Datei} other {# Dateie}} entfernt", - "assets_removed_permanently_from_device": "{count} Element(er) permanent vo dim Gerät glöscht", - "assets_restore_confirmation": "Bisch du sicher, dass du alli Date us em Papirkorb wiederherställe wotsch? Die Aktion cha nöd rückgängig gmacht werde! Beachte, dass Offline-Dateie uf die Wiis nöd wiederherställt werde chönnd.", - "assets_restored_count": "{count, plural, one {# Datei} other {# Dateie}} wiederhergstellt", - "assets_restored_successfully": "{count} Elemänt(e) erfolgriich wiederhergstellt", - "assets_trashed": "{count} Element(e) glöscht", - "assets_trashed_count": "{count, plural, one {# Datei} other {# Dateie}} ih de Papierkorb verschobe", - "assets_trashed_from_server": "{count} Elemänt(e) vom Immich-Server glöscht", - "assets_were_part_of_album_count": "{count, plural, one {# Datei isch} other {# Dateie sind}} bereits im Album vorhanden", - "assets_were_part_of_albums_count": "{count, plural, one {Datei isch} other {Dateie sind}} bereits i de Albene", - "authorized_devices": "Verwendeti Grät", - "automatic_endpoint_switching_subtitle": "Verbinde dich lokal über es bstimmts WLAN, wenn verfügbar, und bruuch anderi Verbindungsmöglichkeite anderswo", - "automatic_endpoint_switching_title": "Automatische URL-Umschaltig", - "autoplay_slideshow": "Automatische Diashow", - "back": "Zrugg", - "back_close_deselect": "Zrugg, Schliesse oder Abwählä", - "background_backup_running_error": "Sicherig läuft im Hintergrund. Manuelli Sicherig cha nöd gstartet werde", - "background_location_permission": "Hintergrund Standortfreigob", - "background_location_permission_content": "Um im Hintergrund zwüsche de Netzwerke wächsle z’chönne, mues Immich *immer* Zuegriff uf dr genaue Standort ha, damit d’App dr Name vom WLAN-Netzwerk ermittle cha", - "background_options": "Hintergrund Optione", - "backup": "Sicherig", - "backup_album_selection_page_albums_device": "Albene uf dem Grät ({count})", - "backup_album_selection_page_albums_tap": "Aatippe zum sichere, nomol aatippe zum usschliesse", - "backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) chönnd sich über mehreri Albe verteile. Drum chönnd die vor dr Sicherig iigschlosse oder usgschlosse werde.", - "backup_album_selection_page_select_albums": "Albene uuswählä", - "backup_album_selection_page_selection_info": "Uuswahlinformatione", - "backup_album_selection_page_total_assets": "Element gsamt", - "backup_albums_sync": "Synchronisation vo de Sicherigsalbene", - "backup_all": "Alli", - "backup_background_service_backup_failed_message": "Es isch e Fähler bi dr Sicherig uufträtä. Versueche erneut…", - "backup_background_service_complete_notification": "Datei Backup abgschlossä", - "backup_background_service_connection_failed_message": "Es het kei Verbindung zum Server chönne hergstellt werde. Nomol versueche…", - "backup_background_service_current_upload_notification": "Lädt {filename} hoch", - "backup_background_service_default_notification": "Suech nach neue Element…", - "backup_background_service_error_title": "Fähler bi de Sicherig", - "backup_background_service_in_progress_notification": "Elemänt werdet gsicheret…", - "backup_background_service_upload_failure_notification": "{filename} het nöd chönne ufeglade werde", - "backup_controller_page_albums": "Gsicherti Albene", - "backup_controller_page_background_app_refresh_disabled_content": "Aktiviere Hintergrundaktualisierige i de Iihstellige -> Allgemein -> Hintergrundaktualisierige, um Sicherige im Hintergrund z ermöglichä.", - "backup_controller_page_background_app_refresh_disabled_title": "Hintergrundaktualisierige sind deaktiviert", - "backup_controller_page_background_app_refresh_enable_button_text": "Gang zu de Iistellige", - "backup_controller_page_background_battery_info_link": "Zeig mir wie", - "backup_controller_page_background_battery_info_message": "Für di beschte Resultat bi Hintergrund‑Sicherige: deaktiviere alli Batterieoptimierig und all Beschränkige für Hintergrund‑Aktivitäte vo Immich.\n\nWäge dem das gerätespezifisch isch, lueg bitte i de Infoe vom Hersteller vo dim Gerät nach.", - "backup_controller_page_background_battery_info_ok": "Ok", - "backup_controller_page_background_battery_info_title": "Batterieoptimierige", - "backup_controller_page_background_charging": "Nume während des Ladens", - "backup_controller_page_background_configure_error": "Hand nöd chönne de Hintergrundservice konfiguriere", - "backup_controller_page_background_delay": "Sicherig vo neue Elemente verzögeret um: {duration}", - "backup_controller_page_background_description": "Schalt de Hintergrundservice iih, um neui Elemente automatisch im Hintergrund z sichere, ohni d’App z öffne", - "backup_controller_page_background_is_off": "Automatischi Sicherig im Hintergrund isch deaktiviert", - "backup_controller_page_background_is_on": "Automatischi Sicherig im Hintergrund isch aktiviert", - "backup_controller_page_background_turn_off": "Hintergrundservice ussschaltä", - "backup_controller_page_background_turn_on": "Hintergrundservice iihschaltä", - "backup_controller_page_background_wifi": "Nume im WLAN", - "backup_controller_page_backup": "Sicherig", - "backup_controller_page_backup_selected": "Usgwählt: ", - "backup_controller_page_backup_sub": "Gsicherti Fotis und Videos", - "backup_controller_page_created": "Erstellt am: {date}", - "backup_controller_page_desc_backup": "Aktivier d’Sicherig, um Elemänt immer automatisch uf de Server ufezlade, während du d’App bruuchsch.", - "backup_controller_page_excluded": "Usgschlossä: ", - "backup_controller_page_failed": "Fählgschagä ({count})", - "backup_controller_page_filename": "Dateiname: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informatione zur Sicherig", - "backup_controller_page_none_selected": "Keini usgwählt", - "backup_controller_page_remainder": "Verbliibend", - "backup_controller_page_remainder_sub": "No z sichere Fotis und Videos", - "backup_controller_page_server_storage": "Server-Speicher", - "backup_controller_page_start_backup": "Sicherig starte", - "backup_controller_page_status_off": "Sicherig im Vordergrund isch inaktiv", - "backup_controller_page_status_on": "Sicherig im Vordergrund isch aktiv", - "backup_controller_page_storage_format": "{used} von {total} gnutzt", - "backup_controller_page_to_backup": "No z sichere Albene", - "backup_controller_page_total_sub": "Alli Fotis und Videos", - "backup_controller_page_turn_off": "Sicherig im Vordergrund ussschalte", - "backup_controller_page_turn_on": "Sicherig im Vordergrund iihschalte", - "backup_controller_page_uploading_file_info": "Informatione", - "backup_err_only_album": "Das einzigi Album cha nöd entfernet werde", - "backup_error_sync_failed": "Synchronisierung gschiiteret. Sicherig cha nöd verarbeitet werde.", - "backup_info_card_assets": "Elemänt", - "backup_manual_cancelled": "Abbroche", - "backup_manual_in_progress": "Sicherig lauft scho. Bitte versuech es spöter nomol", - "backup_manual_success": "Erfolgriich", - "backup_manual_title": "Sicherigsstatus", - "backup_options": "Backup Optione", - "backup_options_page_title": "Sicherigsoptione", - "backup_setting_subtitle": "Verwaltig vo de Upload-iihstellige im Hintergrund und Vordergrund", - "backup_settings_subtitle": "Upload-Iihstellige verwalte", - "backward": "Rückwärts", - "biometric_auth_enabled": "Biometrische Authentifizierig aktiviert", - "biometric_locked_out": "Du bisch vo de biometrische Authenfizierig usgschlossä", - "biometric_no_options": "Kei biometrische Optione verfüegbar", - "biometric_not_available": "D’biometrischi Authentifizierig isch uf däm Gerät nöd verfügbar", - "birthdate_saved": "Geburtsdatum erfolgrich gspeichert", - "birthdate_set_description": "Das Geburtsdatum wird bruucht, um s’Alter vo däre Person zum Ziitpunkt vo enem Foto z berechne.", - "blurred_background": "Unscharfe Hintergrund", - "bugs_and_feature_requests": "Fähler & Verbesserigsvorschläg", - "build": "Build", - "build_image": "Build Abbild", - "bulk_delete_duplicates_confirmation": "Bisch du sicher, dass du {count, plural, one {# duplizierti Datei} other {# duplizierti Dateie gemeinsam}} lösche wotsch? Dabei wird d’grössti Datei vo jeder Gruppe bibhalte und alli andere Duplikate endgültig gelöscht. Die Aktion cha nöd rückgängig gmacht werde!", - "bulk_keep_duplicates_confirmation": "Bisch du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateie}} behalte wotsch? Das wird alli Duplikat-Gruppä uuflöse, ohni öppis z lösche.", - "bulk_trash_duplicates_confirmation": "Bisch du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateie gemeinsam}} i de Papirkorb verschiebe wotsch? Das wird d’grössti Datei vo jeder Gruppe behalte und alli andere Duplikate i de Papirkorb verschiebe.", - "buy": "Immich erwerbä", - "cache_settings_clear_cache_button": "Zwüschespeicher lösche", - "cache_settings_clear_cache_button_title": "Löscht dr Zwüschecache vo dr App. Das wird d’Leistungsfähigkeit vo dr App deutlich einschränke, bis dr Zwüschecache wieder ufbaut worde isch.", - "cache_settings_duplicated_assets_clear_button": "LÄÄRE", - "cache_settings_duplicated_assets_subtitle": "Fotos und Videos, wo vo dr App blockiert werdend", - "cache_settings_duplicated_assets_title": "Duplikate ({count})", - "cache_settings_statistics_album": "Vorschaubilder i dä Bibliothek", - "cache_settings_statistics_full": "Originalbilder", - "cache_settings_statistics_shared": "Vorschaubilder vo geteilte Albene", - "cache_settings_statistics_thumbnail": "Vorschaubilder", - "cache_settings_statistics_title": "Zwüschespiicher-Nutzig", - "cache_settings_subtitle": "Kontrolliere, wie Immich dr Zwüschecache bruucht", - "cache_settings_tile_subtitle": "Lokale Spiicher verwalte", - "cache_settings_tile_title": "Lokale Spiicher", - "cache_settings_title": "Zwüschespiicher Ihstellige", - "camera": "Kamera", - "camera_brand": "Kamera-Marke", - "camera_model": "Kamera-Modell", - "cancel": "Abbräche", - "cancel_search": "Suechi abbräche", - "canceled": "Abbroche", - "canceling": "Abbroche", - "cannot_merge_people": "Persone chönnd nöd zämmegfüehrt werde", - "cannot_undo_this_action": "Die Aktion chan nöd rückgängig gmacht werde!", - "cannot_update_the_description": "Beschriibrig cha nöd aktualisiert werde", - "cast": "Überträge", - "cast_description": "Konfiguration verfügbarer Ziel", - "change_date": "Datum ändere", - "change_description": "Beschriibig aahpasse", - "change_display_order": "Aahzeigereihefolg ändere", - "change_expiration_time": "Verfallsziitpunkt ändere", - "change_location": "Standort ändere", - "change_name": "Name ändere", - "change_name_successfully": "Name isch erfolgrich gänderet worde", - "change_password": "Passwort ändere", - "change_password_description": "Das isch entweder das erscht Mal, dass du dich im System aagmäldet hesch, oder es isch e Aafrog zur Änderig vo dim Passwort gstellt worde. Bitte gib unte dis neue Passwort i.", - "change_password_form_confirm_password": "Passwort bestätige", - "change_password_form_description": "Hallo {name}\n\nDas isch entweder das erscht Mal, dass du dich iiglogt hesch, oder es isch e Aafrog zur Änderig vo dim Passwort gstellt worde. Bitte gib s’neus Passwort i.", - "change_password_form_log_out": "Vo allne Grät abmeldä", - "change_password_form_log_out_description": "Es isch empfohlä, alli andereä Grät abzmäldä", - "change_password_form_new_password": "Neus Passwort", - "change_password_form_password_mismatch": "Passwörter stimmed nöd überein", - "change_password_form_reenter_new_password": "Passwort erneut iihgäh", - "change_pin_code": "PIN-Code ändere", - "change_your_password": "Ändere dis Passwort", - "changed_visibility_successfully": "D’Sichtbarkeit isch erfolgrich gänderet worde", - "charging": "Uufladä", - "charging_requirement_mobile_backup": "Sicherig im Hintergrund fordert, dass s’Gerät ufglade wird", - "check_corrupt_asset_backup": "Uf bschädigti Asset-Sicherige prüefe", - "check_corrupt_asset_backup_button": "Überprüefig durrefüehrä", - "check_corrupt_asset_backup_description": "Führ die Prüefig nume mit aktiviertem WLAN dur, nachdem alli Dateie gsiichert worde sind. Dä Vorgang cha e paar Minute duurä.", - "check_logs": "Logs prüafä", - "checksum": "Prüefsumme", - "choose_matching_people_to_merge": "Wähl passendi Persone zum Zämmezfüehre", - "city": "Stadt", - "cleanup_confirm_description": "Immich hed {count} Dateie (vorem {date} erstellt) sicher ufem Server gfunde. Sölled die lokale Kopie vo dem Grät glöscht werde?", - "cleanup_confirm_prompt_title": "Vo dem Grät entferne?", - "cleanup_deleted_assets": "{count} Dateie i de lokali Papierchorb verschobe", - "cleanup_icloud_shared_albums_excluded": "Teilti iCloud Albe sind vom Scan usgschlosse", - "clear": "Lääre", - "clear_all": "Alles lääre", - "clear_all_recent_searches": "Alli letschte Suechvorgäng lösche", - "clear_file_cache": "Dateie-Cache lääre", - "clear_message": "Nochrichte lääre", - "clear_value": "Wert lääre", - "client_cert_dialog_msg_confirm": "Ok", - "client_cert_enter_password": "Passwort iigäh", - "client_cert_import": "Importiere", - "client_cert_import_success_msg": "Client Zertifikat isch importiert wordä", - "client_cert_invalid_msg": "Ungültigi Zertifikatsdatei oder falsches Passwort", - "client_cert_remove_msg": "Client Zertifikat isch entfernt wordä", - "client_cert_subtitle": "Unterstützt nume s’PKCS12 (.p12, .pfx) Format. Zertifikat-Import oder -Entferne sind nume vor em Login möglech", - "client_cert_title": "SSL-Client-Zertifikat [Experimentell]", - "clockwise": "Im Uhrzeigersinn", - "close": "Schlüssä", - "collapse": "Zämmeklappe", - "collapse_all": "Alles zämmeklappe", - "color": "Farb", - "color_theme": "Farb-Theme", - "command": "Befähl", - "comment_deleted": "Kommentar glöscht", - "comment_options": "Kommentaroptione", - "comments_and_likes": "Kommentär & Likes", - "comments_are_disabled": "Kommentär sind deaktiviert", - "common_create_new_album": "Neus Album erstellä", - "completed": "Abgschlossä", - "confirm": "Bestätige", - "confirm_admin_password": "Administrator Passwort bestätige", - "confirm_delete_face": "Bisch du sicher, dass du s’Gsicht vo {name} us dr Datei entferne wotsch?", - "confirm_delete_shared_link": "Bisch du sicher, dass du dä gteilte Link lösche wotsch?", - "confirm_keep_this_delete_others": "Alli andere Dateie im Stapel bis uf die werde gelöscht. Bisch du sicher, dass du witerfahre wotsch?", - "confirm_new_pin_code": "Neue PIN-Code Iigäh", - "confirm_password": "Passwort bestätige", - "confirm_tag_face": "Wotsch du das Gsicht mit {name} markiere?", - "confirm_tag_face_unnamed": "Wotsch du das Gsicht markiere?", - "connected_device": "Verbundenes Grät", - "connected_to": "Verbunde mit", - "contain": "Vollständig", - "context": "Kontext", - "continue": "Fortsetze", - "control_bottom_app_bar_create_new_album": "Neus Album erstellä", - "control_bottom_app_bar_delete_from_immich": "Vo Immich löschä", - "control_bottom_app_bar_delete_from_local": "Vom Grät lösche", - "control_bottom_app_bar_edit_location": "Standort bearbeite", - "control_bottom_app_bar_edit_time": "Datum und Uhrziit bearbeite", - "control_bottom_app_bar_share_link": "Link teile", - "control_bottom_app_bar_share_to": "Teile mit", - "control_bottom_app_bar_trash_from_immich": "I de Papierkorb verschiebä", - "copied_image_to_clipboard": "S’Bild isch i d’Zwüscheablag kopiert worde.", - "copied_to_clipboard": "I d’Zwüscheablage kopiert!", - "copy_error": "Kopier-Fähler", - "copy_file_path": "Dateipfad kopiere", - "copy_image": "Bild kopiere", - "copy_link": "Link kopiere", - "copy_link_to_clipboard": "Link I d’Zwüscheablage kopiere", - "copy_password": "Passwort kopiere", - "copy_to_clipboard": "I d’Zwüscheablage kopiere", - "country": "Land", - "cover": "Randlos", - "covers": "Albumcover", - "create": "Erstellä", - "create_album": "Album erstellä", - "create_album_page_untitled": "Unbenennt", - "create_api_key": "API Key erstellä", - "create_first_workflow": "Erste Workflow erstelle", - "create_library": "Bibliothek erstellä", - "create_link": "Link erstellä", - "create_link_to_share": "Link zum Teile erstellä", - "create_link_to_share_description": "Lah jede mit em Link d’usgwählte Fotos aaluege", - "create_new": "NEUS ERSTELLÄ", - "create_new_person": "Neui Person erstelle", - "create_new_person_hint": "Usgwählti Dateie ener neue Person zueweise", - "create_new_user": "Neue Nutzer erstellä", - "create_shared_album_page_share_add_assets": "INHALTE HINZUEFÜEGÄ", - "create_shared_album_page_share_select_photos": "Fotis uswählä", - "create_shared_link": "Teilte Link erstellä", - "create_tag": "Tag erstellä", - "create_tag_description": "Erstell en neue Tag. Für verschachtleti Tags gib dr ganze Pfad inklusiv Schrägstrich aa.", - "create_user": "Nutzer erstellä", - "create_workflow": "Workflow erstelle", - "created": "Erstellt", - "created_at": "Erstellt", - "creating_linked_albums": "Erstelle verknüpfti Albene...", - "crop": "Zueschniidä", - "crop_aspect_ratio_fixed": "Fixiert", - "crop_aspect_ratio_free": "Frei", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Sachä", - "current_device": "Aktuells Grät", - "current_pin_code": "Aktuelle PIN Code", - "current_server_address": "Aktuelli Serveradresse", - "custom_locale": "Benutzerdefinierti Sproch", - "custom_locale_description": "Datumsahgoob und Zahle je nach Sproch und Land formatiere", - "custom_url": "Benutzerdefinierti URL", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Dunkäl", - "dark_theme": "Dunkli Aahsicht umschalte", - "date": "Datum", - "date_after": "Datum noch", - "date_and_time": "Datum und Ziit", - "date_before": "Datum vor", - "date_format": "E d. LLL y • hh:mm", - "date_of_birth_saved": "S’Geburtsdatum isch erfolgrich gspiicheret worde", - "date_range": "Datumsberiich", - "day": "Tag", - "days": "Täg", - "deduplicate_all": "Alli Duplikate entfernä", - "deduplication_criteria_1": "Bildgrössi in Bytes", - "deduplication_criteria_2": "Anzahl vo de EXIF Date", - "deduplication_info": "Deduplizierungsinformatione", - "deduplication_info_description": "Für d’automatischi Datei-Voruswahl und s’Dedupliziere vo allne Dateie berücksichtige mir:", - "default_locale": "Standard Sproch", - "default_locale_description": "Datumsangabe und Zahle basierend uf em Gebietsschema vom Browser formatiere", - "delete": "Lösche", - "delete_action_confirmation_message": "Bisch du sicher, dass du dies Objekt lösche wotsch? Die Aktion verschiebt s’Objekt i de Papirkorb vom Server und fragt dich, ob du’s lokal löösche wotsch", - "delete_action_prompt": "{count} glöscht", - "delete_album": "Album lösche", - "delete_api_key_prompt": "Bisch du sicher, dass du dä API-Schlüssel lösche wotsch?", - "delete_dialog_alert": "Die Elemente werde unwiderruflich vo Immich und vom Grät entfernt", - "delete_dialog_alert_local": "Die Inhält werdend vom Grät glöscht, bliibend aber uf dem Immich-Server", - "delete_dialog_alert_local_non_backed_up": "Einigi Inhalt sind nöd i Immich gsiichert und werde dauerhaft vom Gerät gelöscht", - "delete_dialog_alert_remote": "Die Inhält werdend dauerhaft vom Immich-Server glöscht", - "delete_dialog_ok_force": "Trotzdem lösche", - "delete_dialog_title": "Endgültig lösche", - "delete_duplicates_confirmation": "Bisch du sicher, dass du diä Duplikat endgültig lösche wotsch?", - "delete_face": "Gsicht löschä", - "delete_key": "Schlüssel löschä", - "delete_library": "Bibliothek löschä", - "delete_link": "Link löschä", - "delete_local_action_prompt": "{count} lokal glöscht", - "delete_local_dialog_ok_backed_up_only": "Nur gsicherti Inhält lösche", - "delete_local_dialog_ok_force": "Trotzdem lösche", - "delete_others": "Anderi lösche", - "delete_permanently": "Endgültig lösche", - "delete_permanently_action_prompt": "{count} endgültig glöscht", - "delete_shared_link": "teilte link lösche", - "delete_shared_link_dialog_title": "Teilte Link lösche", - "delete_tag": "tag lösche", - "delete_tag_confirmation_prompt": "Bisch du sicher, dass de {tagName} glöscht werde sött?", - "delete_user": "Nutzer lösche", - "deleted_shared_link": "teilte link lösche", - "deletes_missing_assets": "Löscht Dateie, welli uf de Feschtplatte fählend", - "description": "Beschriibig", - "description_input_hint_text": "Beschriibig hinzufüegä...", - "description_input_submit_error": "D’Beschribig het nöd chenne gänderet werde, bitte lueg im Log für meh Details", - "deselect_all": "Alli abwähle", - "details": "Details", - "direction": "Richtig", - "disable": "Deaktiviere", - "disabled": "Deaktiviert", - "disallow_edits": "Bearbeitig verbüütä", - "discord": "Discord", - "discover": "Entdeckä", - "discovered_devices": "Gfundeni Grät", - "dismiss_all_errors": "Alli Fähler ignorierä", - "dismiss_error": "Fähler ignorierä", - "display_options": "Aahzeigeoptione", - "display_order": "Aahzeigereihefolg", - "display_original_photos": "Originali Fotis Aahzeige", - "display_original_photos_setting_description": "Bi dr Aazeig vo enem Bild wird bevorzugt s’Originalfoto statt dr Miniaturahsicht azeigt, sofern s’Original web-kompatibel isch. Das cha z längere Ladeziite vo de Fotos führe.", - "do_not_show_again": "Die Nochricht nöd erneut Aahzeige", - "documentation": "Dokumentation", - "done": "Fertig", - "download": "Abelade", - "download_action_prompt": "Abelade vo {count} Dateie", - "download_canceled": "Download abbroche", - "download_complete": "Download vollständig", - "download_enqueue": "Download i d’Warteschlange gstellt", - "download_error": "Download fählerhaft", - "download_failed": "Download fählgschlage", - "download_finished": "Download abgschlosse", - "download_include_embedded_motion_videos": "Iigbetteti Videos", - "download_include_embedded_motion_videos_description": "Videos, wo i Bewegigsfotos iigbettet sind, als separate Datei iifüege", - "download_notfound": "Download nöd gfundä", - "download_original": "Original abelade", - "download_paused": "Download pausiert", - "download_settings": "Download", - "download_settings_description": "Iihstelligä fürs Abeladä vo Dateie verwalte", - "download_started": "Download gstartet", - "download_sucess": "Download erfolgriich", - "download_sucess_android": "Die Datei wurde nach DCIM/Immich abeglade", - "download_waiting_to_retry": "Warte uf neue Versuech", - "downloading": "Abelade", - "downloading_asset_filename": "Datei {filename} wird abeglade", - "downloading_media": "Medie werdend abegladä", - "drop_files_to_upload": "Lad Dateie ufä, indem du sie do ane züchsch", - "duplicates": "Duplikate", - "duplicates_description": "Lös jedi Gruppe uf, indem du aagisch, welche — wenn überhaupt — Duplikat sind", - "duration": "Duur", - "edit": "Bearbeite", - "edit_album": "Album bearbeite", - "edit_avatar": "Avatar bearbeite", - "edit_birthday": "Geburtsdatum bearbeite", - "edit_date": "Datum bearbeite", - "edit_date_and_time": "Datum und Uhrziit bearbeite", - "edit_date_and_time_action_prompt": "{count} Date und Ziite gänderet", - "edit_date_and_time_by_offset": "Datum ändere um Versatz", - "edit_date_and_time_by_offset_interval": "Neue Datumsbereich: {from} - {to}", - "edit_description": "Beschriibig bearbeite", - "edit_description_prompt": "Bitte wähl e neui Beschriibig:", - "edit_exclusion_pattern": "Usschlussmuster bearbeite", - "edit_faces": "Gsichter bearbeite", - "edit_key": "Schlüssel bearbeite", - "edit_link": "Link bearbeite", - "edit_location": "Standort bearbeite", - "edit_location_action_prompt": "{count} Geolokatione ahpasst", - "edit_location_dialog_title": "Standort bearbeite", - "edit_name": "Name bearbeite", - "edit_people": "Person bearbeite", - "edit_tag": "Tag bearbeite", - "edit_title": "Titel bearbeite", - "edit_user": "Nutzer bearbeite", - "edit_workflow": "Workflow bearbeite", - "editor": "Bearbeiter", - "editor_close_without_save_prompt": "D’Änderige werden nöd gspeichert", - "editor_close_without_save_title": "Editor schlüssä?", - "email": "E-Mail", - "email_notifications": "E-Mail Benochrichtigunge", - "empty_folder": "Dä Ordner isch leer", - "empty_trash": "Papierkorb lääre", - "empty_trash_confirmation": "Bisch du sicher, dass du dr Papirkorb leere wotsch?\nDas entfernt alli Dateie im Papirkorb endgültig us Immich und cha nöd rückgängig gmacht werde!", - "enable": "Aktiviere", - "enable_backup": "Sicherig aktiviere", - "enable_biometric_auth_description": "Gib din PIN-Code ii, um d’biometrischi Authentifizierig z aktiviere", - "enabled": "Aktiviert", - "end_date": "Enddatum", - "enqueued": "iihgreiht", - "enter_wifi_name": "WLAN-Name Iigäh", - "enter_your_pin_code": "PIN-Code Iigäh", - "enter_your_pin_code_subtitle": "Gib din PIN-Code ii, um uf dr gschperrte Ordner zuezgriffä", - "error": "Fähler", - "error_change_sort_album": "Änderig vo dr Aazeigeräihefolg isch gscheitert", - "error_delete_face": "Fähler bim Löschä vom Gsicht", - "error_getting_places": "Fähler bim Abruefä vo dä Ört", - "error_loading_image": "Fähler bim Lade vom Bild", - "error_loading_partners": "Fähler bim Lade vo de Partner: {error}", - "error_saving_image": "Fähler: {error}", - "error_tag_face_bounding_box": "Fähler bim Markiere vom Gsicht – Begrenzige chönd nöd abgruefä wärde", - "error_title": "Fähler - Öppis isch schief gloffe", - "errors": { - "cannot_navigate_next_asset": "Chan nöd zu de nögst Datei navigierä", - "cannot_navigate_previous_asset": "Chan nöd zu de vorherigä Datei navigierä", - "cant_apply_changes": "Änderige chönnt nöd übernoh werden", - "cant_change_activity": "Aktivität cha nöd {enabled, select, true {deaktiviert} other {aktiviert}} werde", - "cant_change_asset_favorite": "Favorit-Status für d’Datei cha nöd gänderet werde", - "cant_change_metadata_assets_count": "Metadate für {count, plural, one {# Datei} other {# Dateie}} chönnd nöd gänderet werde", - "cant_get_faces": "Gsichter chönnd nöd abgruefä werde", - "cant_get_number_of_comments": "Aazahl vo de Kommentär het nöd chönne abgrüefä wärde", - "cant_search_people": "Persone händ nöd chenne gsuecht werde", - "cant_search_places": "Orte händ nöd chönne gsuecht werde", - "error_adding_assets_to_album": "Fähler bim Dateie-Zuefüege is Album", - "error_adding_users_to_album": "Fähler bim Benutzer is Album zuefüege", - "error_deleting_shared_user": "Fähler bim lösche vom teilte Benutzer", - "error_downloading": "Fähler bim abelade vo {filename}", - "error_hiding_buy_button": "Fähler bim de Chauf-Chnopf usblände", - "error_removing_assets_from_album": "Fähler bim Dateie us em Album wägmache, lueg i d Konsole für meh Details", - "error_selecting_all_assets": "Fähler bim alli Dateie uuswähle", - "exclusion_pattern_already_exists": "Das Uschlussmuuster gits scho.", - "failed_to_create_album": "Album het nöd chöne erstellt werde", - "failed_to_create_shared_link": "Teilte Link het nöd chöne erstellt werde", - "failed_to_edit_shared_link": "Teilte Link het nöd chöne bearbeited werde", - "failed_to_get_people": "Persone händ nöd chöne abgruefe werde", - "failed_to_keep_this_delete_others": "Fähler bim di andere Dateie lösche", - "failed_to_load_asset": "Fähler bim Datei lade", - "failed_to_load_assets": "Fähler bim Lade vo de Dateie", - "failed_to_load_notifications": "Fähler bim lade vo de Benochrichtigunge", - "failed_to_load_people": "Fähler bim Lade vo de Persone", - "failed_to_remove_product_key": "Fähler bim entfernä vom Produktschlüssel", - "failed_to_reset_pin_code": "Zruggsetze vom PIN-Code isch nöd gange", - "failed_to_stack_assets": "Dateie händ nöd chöne gstaplet werde", - "failed_to_unstack_assets": "Dateie händ nöd chöne entstaplet werde", - "failed_to_update_notification_status": "Benachrichtigigs-Status aktualisiere isch fählgschlage", - "incorrect_email_or_password": "Ungültigi E-Mail oder Passwort", - "library_folder_already_exists": "Dä Importpfad exischtiert bereits.", - "paths_validation_failed": "{paths, plural, one {# Pfad isch} other {# Pfäd sind}} nöd chöne validiert werde", - "profile_picture_transparent_pixels": "Profilbilder dörfed kei transparente Pixel haa. Zoome bitte ine und/oder verschiebe s Bild.", - "quota_higher_than_disk_size": "Dii feschtgleiti Kontingänt isch grösser als de verfüegbari Spicher", - "something_went_wrong": "En Fähler isch ufträtä", - "unable_to_add_album_users": "Benutzer händ nöd chöne zum Album zuegfüegt werde", - "unable_to_add_assets_to_shared_link": "Datei het nöd chöne zum gteilte Link zuegfüegt werde", - "unable_to_add_comment": "Es cha kei Kommentar zuegfüegt werde", - "unable_to_add_exclusion_pattern": "Usschlussmuuster het nöd chöne zuegfüegt werde", - "unable_to_add_partners": "Es chönd kei Partner zuegfüegt werde", - "unable_to_add_remove_archive": "Datei het nöd chöne {archived, select, true {uss em Archiv gnoh} other {zum Archiv zuegfüegt}} werde", - "unable_to_add_remove_favorites": "Datei het nöd chöne {favorite, select, true {vo de Favorite gnoh} other {zu de Favorite zuegfüegt}} werde", - "unable_to_archive_unarchive": "Het nöd chöne {archived, select, true {archiviere} other {entarchiviere}}", - "unable_to_change_album_user_role": "D Roll vom Album-Benutzer cha nöd gänderet werde", - "unable_to_change_date": "Datum cha nöd veränderet werde", - "unable_to_change_description": "Änderig vo de Beschriibig isch nöd möglich", - "unable_to_change_favorite": "De Favorite-Status für die Datei het nöd chöne gänderet werde", - "unable_to_change_location": "Standort cha nöd veränderet werde", - "unable_to_change_password": "Passwort het nöd chöne gänderet werde", - "unable_to_change_visibility": "Sichtbarkeit vo {count, plural, one {ere Person} other {# Persone}} het nöd chöne gänderet werde", - "unable_to_complete_oauth_login": "OAuth-Aamäldig het nöd chöne abgschlosse werde", - "unable_to_connect": "Verbindig het nöd chöne gmacht werde", - "unable_to_copy_to_clipboard": "Het nöd chöne i d Zwüschespeichere kopiere, lueg, ass du per https uf d Siite zuegriefsch", - "unable_to_create_admin_account": "Administrator-Konto het nöd chöne gmacht werde", - "unable_to_create_api_key": "Es het kei API-Schlüssel chöne erstellt werde", - "unable_to_create_library": "Bibliothek het nöd chöne erstellt werde", - "unable_to_create_user": "Nutzer/Benutzer het nöd chöne erstellt werde", - "unable_to_delete_album": "Album het nöd chöne glöscht werde", - "unable_to_delete_asset": "Datei het nöd chöne glöscht werde", - "unable_to_delete_assets": "Fähler bim löschä vo de Dateie", - "unable_to_delete_exclusion_pattern": "Usschlussmuuster het nöd chöne glöscht werde", - "unable_to_delete_shared_link": "Gteilte Link cha nöd glöscht werde", - "unable_to_delete_user": "Nutzer het nöd chöne glöscht werde", - "unable_to_download_files": "Dateie händ nöd chöne abglade werde", - "unable_to_edit_exclusion_pattern": "Usschlussmuuster het nöd chöne bearbeited werde", - "unable_to_empty_trash": "Papiirkorb het nöd chöne gleeret werde", - "unable_to_enter_fullscreen": "Vollbild-Modus cha nöd aktiviert werde", - "unable_to_exit_fullscreen": "Vollbild-Modus cha nöd deaktiviert werde", - "unable_to_get_comments_number": "Aazahl vo de Kommentär het nöd chöne abgruefe werde", - "unable_to_get_shared_link": "Fähler bim de Freigab-Link abruefe", - "unable_to_hide_person": "Person cha nöd versteckt werde", - "unable_to_link_motion_video": "Bewegigsvideo cha nöd verchnüpft werde", - "unable_to_link_oauth_account": "OAuth-Konto cha nöd verchnüpft werde", - "unable_to_log_out_all_devices": "Het nöd chöne vo allne Gerät abgmäldet werde", - "unable_to_log_out_device": "Het nöd chöne vom Gerät abgmäldet werde", - "unable_to_login_with_oauth": "Aamäldig mit OAuth isch nöd möglich", - "unable_to_play_video": "S Video cha nöd wiederggäh/gspielt werde", - "unable_to_reassign_assets_existing_person": "Cha Dateie nöd {name, select, null {ere existierendi Person} other {{name}}} zuwiise", - "unable_to_reassign_assets_new_person": "Dateie händ nöd chöne ere neue Person zuewiise werde", - "unable_to_refresh_user": "De Benutzer/Nutzer cha nöd aktualisiert werde", - "unable_to_remove_album_users": "Mitglieder vo de Albene chönd nöd entfernt werde", - "unable_to_remove_api_key": "API-Schlüssel het nöd chöne entfernt werde", - "unable_to_remove_assets_from_shared_link": "Dateie händ nöd chöne vom gteilte Link gnoh/entfernt werde", - "unable_to_remove_library": "Bibliothek cha nöd entfernt werde", - "unable_to_remove_partner": "Partner cha nöd entfernt werde", - "unable_to_remove_reaction": "Reaktion cha nöd entfernt wärde", - "unable_to_reset_password": "Passwort cha nöd zruggsetzt werde", - "unable_to_reset_pin_code": "Zruggsetze vom PIN-Code isch nöd möglich", - "unable_to_resolve_duplicate": "Duplikat chönd nöd ufglööst werde", - "unable_to_restore_assets": "Dateie händ nöd chöne widerhergstellt werde", - "unable_to_restore_trash": "Papiirkorb cha nöd widerhergstellt werde", - "unable_to_restore_user": "Benutzer cha nöd widerhergstellt werde", - "unable_to_save_album": "Album cha nöd gspiicheret werde", - "unable_to_save_api_key": "API-Schlüssel het nöd chöne gspiicheret werde", - "unable_to_save_date_of_birth": "S Geburtsdatum het nöd chöne gspiicheret werde", - "unable_to_save_name": "Name het nöd chöne gspiicheret werde", - "unable_to_save_profile": "Profil het nöd chöne gspiicheret werde", - "unable_to_save_settings": "Iistellige händ nöd chöne gspiicheret werde", - "unable_to_scan_libraries": "Bibliotheke händ nöd chöne gscannt werde", - "unable_to_scan_library": "Bibliothek het nöd chöne gscannt werde", - "unable_to_set_feature_photo": "Hauptfoto het nöd chöne feschtgsetzt werde", - "unable_to_set_profile_picture": "Profilbild het nöd chöne gsetzt werde", - "unable_to_submit_job": "Uufgab het nöd chöne iigreicht werde", - "unable_to_trash_asset": "Objekt händ nöd chöne glöscht werde", - "unable_to_unlink_account": "D Verchnüpfig vom Konto cha nöd ufghobe werde", - "unable_to_unlink_motion_video": "Verchnüpfig zum Bewegigsvideo cha nöd ufghobe werde", - "unable_to_update_album_cover": "Album-Cover het nöd chöne aktualisiert werde", - "unable_to_update_album_info": "Album-Info het nöd chöne aktualisiert werde", - "unable_to_update_library": "D Bibliothek het nöd chöne aktualisiert werde", - "unable_to_update_location": "De Standoort het nöd chöne aktualisiert werde", - "unable_to_update_settings": "D Iistellige händ nöd chöne aktualisiert werde", - "unable_to_update_timeline_display_status": "Status vo de Ziitlinie-Aazeig het nöd chöne aktualisiert werde", - "unable_to_update_user": "De Nutzer/Benutzer het nöd chöne aktualisiert werde", - "unable_to_upload_file": "Datei het nöd chöne ufeglade werde" - }, - "exclusion_pattern": "Usschlussmuster", - "exif": "EXIF", - "exif_bottom_sheet_description": "Beschriibig dezuefüege...", - "exif_bottom_sheet_description_error": "Fähler bim d Aktualisierig vo de Beschriibig", - "exif_bottom_sheet_details": "DETAILS", - "exif_bottom_sheet_location": "STANDORT", - "exif_bottom_sheet_no_description": "Kei Beschriibig", - "exif_bottom_sheet_people": "PERSONE", - "exif_bottom_sheet_person_add_person": "Name dezuefüege", - "exit_slideshow": "Diashow beende", - "expand_all": "Alli ufklappe", - "experimental_settings_new_asset_list_subtitle": "In Arbet", - "experimental_settings_new_asset_list_title": "Experimentells Föteli-Gitter aktiviere", - "experimental_settings_subtitle": "Benutzig uf eigni Gfohr!", - "experimental_settings_title": "Experimentell", - "expire_after": "Verfällt noch", - "expired": "Verfalle", - "expires_date": "Lauft ab: {date}", - "explore": "Erkundä", - "explorer": "Datei-Explorer", - "export": "Exportierä", - "export_as_json": "Als JSON exportierä", - "export_database": "Datebank exportierä", - "export_database_description": "Exportiert d SQLite Datebank", - "extension": "Erwiiterig", - "external": "Extern", - "external_libraries": "Externi Bibliotheke", - "external_network": "Externs Netzwerk", - "external_network_sheet_info": "Wenn d App nöd im bevorzugte WLAN-Netzwärch isch, verbindet si sich mit em Server über die erschti vo de folgende URLs, wo si cha erreiche (vo obe nah unde)", - "face_unassigned": "Nöd zuegwisää", - "failed": "Nöd glunge", - "failed_count": "Nöd glungä: {count}", - "failed_to_authenticate": "Authenfizierig nöd glungä", - "failed_to_load_assets": "Lade vo de Assets nöd glungä", - "failed_to_load_folder": "Fähler bim Ladä vom Ordner", - "favorite": "Favorit", - "favorite_action_prompt": "{count} zu de Favorite hinzuegfüegt", - "favorite_or_unfavorite_photo": "Favorisierts oder nöd favorisierts Foti", - "favorites": "Favorite", - "favorites_page_no_favorites": "Kei favorisierte Inhält gfundä", - "feature_photo_updated": "Profilbild aktualisiert", - "features": "Funktione", - "features_in_development": "Feature isch in Entwicklig", - "features_setting_description": "Funkione i de App verwalte", - "file_name_or_extension": "Dateiname oder -erwiiterig", - "file_size": "Dateigrössi", - "filename": "Dateiname", - "filetype": "Dateityp", - "filter": "Filter", - "filter_people": "Persone filterä", - "filter_places": "Ort filterä", - "find_them_fast": "Finded sie schneller mit de Suechi noch Näme", - "first": "Erste", - "fix_incorrect_match": "Fählerhafti Überiistimmig behebe", - "folder": "Ordner", - "folder_not_found": "Ordner nöd gfundä", - "folders": "Ordner", - "folders_feature_description": "D' Ordneraazeig (Ordneransicht) für Föteli und Videos im Dateiesyschtem dursueche", - "forgot_pin_code_question": "PIN-Code vergessä?", - "forward": "Vorwärts", - "full_path": "Vollständige Pfad: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Die Funktion ladet externi Quelle vo Google, zum funktioniere.", - "general": "Allgemein", - "geolocation_instruction_location": "Klick uf eh Datei mit GPS Koordinate, zum de Standoort z'bruuche, oder wähl en Standoort direkt uf de Charte", - "get_help": "Hilf erhalte", - "get_wifiname_error": "WLAN-Naame het nöd chöne gfunde werde. Vergewissere dich, dass die erforderliche Berächtigunge erteilt worde sind, und du mit eme WLAN-Netzwärch verbunde bisch", - "getting_started": "Erschti Schritt", - "go_back": "Zrugg", - "go_to_folder": "Gang zum Ordner", - "go_to_search": "Zur Suechi go", - "gps": "GPS", - "gps_missing": "Kein GPS", - "grant_permission": "Erlaubnis gwähre", - "group_albums_by": "Albe gruppiere noch...", - "group_country": "Noch Land gruppiere", - "group_no": "Kei Gruppierig", - "group_owner": "Gruppiere noch Bsitzer", - "group_places_by": "Ort gruppiere noch...", - "group_year": "Gruppierig noch Johr", - "haptic_feedback_switch": "Haptisches Feedback aktiviere", - "haptic_feedback_title": "Haptisches Feedback", - "has_quota": "Kontingent", - "hash_asset": "Dateihash", - "hashed_assets": "Ghäshte Dateie", - "hashing": "Häshe", - "header_settings_add_header_tip": "Header hinzufüegä", - "header_settings_field_validator_msg": "Dä Wert dörf nöd leer si", - "header_settings_header_name_input": "Header-Name", - "header_settings_header_value_input": "Header-Wert", - "headers_settings_tile_title": "Benutzerdefinierte Proxy-Header", - "hi_user": "Hoi {name} ({email})", - "hide_all_people": "Alli persone verberge", - "hide_gallery": "Galerie verbergä", - "hide_named_person": "Person {name} verbergä", - "hide_password": "Passwort verbergä", - "hide_person": "Person verbergä", - "hide_text_recognition": "Texterkennung verbergä", - "hide_unnamed_people": "Unbenannti Person verbergä", - "home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugfüegt. {failed} Elemente sind bereits vorhande.", - "home_page_add_to_album_err_local": "Es chönd no nöd lokali Element zu Albe dezuegfuegt werde, überspringe", - "home_page_add_to_album_success": "{added} Element zu {album} dezuegfuegt.", - "home_page_album_err_partner": "Inhält vo Partner chönd im Momänt nöd zu Albe dezuegfuegt werde", - "home_page_archive_err_local": "Cha lokali Element nöd archiviere, überspringe", - "home_page_archive_err_partner": "Inhält vo Partner chönd nöd archiviert werde", - "home_page_building_timeline": "Ziitachse wird erstellt", - "home_page_delete_err_partner": "Inhält vo Partner chönd nöd glöscht werde, überspringe", - "home_page_delete_remote_err_local": "Lokali Element in de Uswahl zum Entferne vo Remote-Element, Überspringe", - "home_page_favorite_err_local": "Cha lokali Element no nöd favorisiere, überspringe", - "home_page_favorite_err_partner": "Inhält vo Partner chönd nöd favorisiert werde, überspringe", - "home_page_first_time_notice": "Wenn Du d'App zum erschte Mol bruuchsch, wähl bitte es Album zum Siichere uus, demit d'Ziitachs mit Föteli und Videos cha gfüllt werde", - "home_page_locked_error_local": "Lokali Dateie chönd nöd i de gsperrti Ordner verschobe werde, überspringe", - "home_page_locked_error_partner": "Dateie vo Partner chönd nöd i de gsperrti Ordner verschobe werde, überspringe", - "home_page_share_err_local": "Lokali Inhält chönd nöd per Link teilt werde, überspringe", - "home_page_upload_err_limit": "Es chönd max. 30 Element gliichzitig ufelglade werde, überspringe", - "host": "Host", - "hour": "Stund", - "hours": "Stunde", - "id": "ID", - "idle": "Untätig", - "ignore_icloud_photos": "iCloud Fotos ignorierä", - "ignore_icloud_photos_description": "Föteli, wo i de iCloud gspiicheret sind, wärded nöd uf de immich Server ufeglade", - "image": "Föteli", - "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} ufgnoo am {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Bild}} ufgnoo mit {person1} am {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo mit {person1} und {person2} am {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo mit {person1}, {person2} und {person3} am {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo mit {person1}, {person2} und {additionalCount, number} andere am {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} am {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1} am {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1} und {person2} am {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1}, {person2} und {person3} am {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1}, {person2} und {additionalCount, number} andere am {date}", - "image_saved_successfully": "Bild gspiicheret", - "image_viewer_page_state_provider_download_started": "Download gstartet", - "image_viewer_page_state_provider_download_success": "Erfolgriich abeglade", - "image_viewer_page_state_provider_share_error": "Fähler bim Teile", - "immich_logo": "Immich-Logo", - "immich_web_interface": "Immich-Web-Oberflächi", - "import_from_json": "Us JSON importierä", - "import_path": "Importpfad", - "in_albums": "In {count, plural, one {# Album} other {# Albe}}", - "in_archive": "Im Archiv", - "in_year": "Im Johr {year}", - "in_year_selector": "Im Johr", - "include_archived": "Archivierti Dateie iibezieh", - "include_shared_albums": "Teilti Albe iibezieh", - "include_shared_partner_assets": "Teilti Partner-Dateie mit iibezieh", - "individual_share": "Individuelli Freigob", - "individual_shares": "Individuells Teile", - "info": "Info", - "interval": { - "day_at_onepm": "Täglich um 13:00 Uhr", - "hours": "{hours, plural, one {Jedi Stund} other {Alli {hours, number} Stunde}}", - "night_at_midnight": "Täglich um Mitternacht", - "night_at_twoam": "Täglich nachts um 2:00 Uhr" - }, - "invalid_date": "Ungültigs Datum", - "invalid_date_format": "Ungültigs Datumsformat", - "invite_people": "Persone ilade", - "invite_to_album": "Zum Album ilade", - "ios_debug_info_fetch_ran_at": "Abruäf lauft {dateTime}", - "ios_debug_info_last_sync_at": "Zletscht aktualisiert {dateTime}", - "ios_debug_info_no_processes_queued": "Kei Hintergründsprozäss i de Waarteschlange", - "ios_debug_info_no_sync_yet": "No kei Hindergrundsynchronisierigs-Uftrag usgfüehrt", - "ios_debug_info_processes_queued": "{count, plural, one {{count} Hindergrundsprozäss i de Waarteschlange} other {{count} Hindergrundsprozäss i de Waarteschlange}}", - "ios_debug_info_processing_ran_at": "Prozess lauft {dateTime}", - "items_count": "{count, plural, one {# Iitrag} other {# Iiträg}}", - "jobs": "Ufgobe", - "keep": "Bhalte", - "keep_all": "Alli bhalte", - "keep_this_delete_others": "Das bhalte, anderi lösche", - "kept_this_deleted_others": "Die Datei bhalte und {count, plural, one {# Datei} other {# Dateie}} glöscht", - "keyboard_shortcuts": "Taschtechürzel", - "language": "Sproch", - "language_no_results_subtitle": "Versuäch's mit eme andere Suechbegriff", - "language_no_results_title": "Kei Sproch gfundä", - "language_search_hint": "Sproch durchsuechä...", - "language_setting_description": "Wähl dini bevorzugti Sproch", - "large_files": "Grossi Dateie", - "last": "Letschte", - "last_months": "{count, plural, one {Letscht Monet} other {Letschti # Mönet}}", - "last_seen": "Zletscht gseh", - "latest_version": "Aktuelli Version", - "latitude": "Breitegraad", - "leave": "Verlaa", - "leave_album": "Album verlaa", - "lens_model": "Objektivmodäll", - "let_others_respond": "Antworte zuelaa", - "level": "Level", - "library": "Bibliothek", - "library_add_folder": "Ordner dezuefüege", - "library_edit_folder": "Ordner bearbeite", - "library_options": "Bibliotheksoptione", - "library_page_device_albums": "Albene uf dem Grät", - "library_page_new_album": "Neus Album", - "library_page_sort_asset_count": "Aazahl vo de Inhält", - "library_page_sort_created": "Zletscht erstellt", - "library_page_sort_last_modified": "Zletscht bearbeitet", - "library_page_sort_title": "Titel vom Album", - "licenses": "Lizenze", - "light": "Hell", - "like": "Gfallt mir", - "like_deleted": "Like glöscht", - "link_motion_video": "Bewegigsvideo verchnüpfe", - "link_to_oauth": "Mit OAuth verchnüpfe", - "linked_oauth_account": "Verchnüpftes OAuth-Chonto", - "list": "Lischte", - "loading": "Lade", - "loading_search_results_failed": "Lade vo Suechergäbnis isch fählgschlage", - "local": "Lokal", - "local_asset_cast_failed": "E Datei, wo nöd uf de Server ufeglade worde isch, chan nöd gcastet werde", - "local_assets": "Lokali Dateie", - "local_media_summary": "Zämmefassig vo de lokale Medie", - "local_network": "Lokals Nätzwerk", - "local_network_sheet_info": "D'App stellt über die URL e Verbindig zum Server her, wenn siäs aagegänne WLAN-Näzwerk verwändet", - "location": "Standort", - "location_permission": "Standort Gnähmigung", - "location_permission_content": "Zum d'automatischi Umschaltfunktioon nutze z'chönne, bruucht Immich en gnaui Standoortberechtigig, damit's de Name vom aktuelle WLAN-Näzwerk chan usefinde", - "location_picker_choose_on_map": "Uf de Charte uswähle", - "location_picker_latitude_error": "Gülltige Breitegraad iigä", - "location_picker_latitude_hint": "Breitegraad iigä", - "location_picker_longitude_error": "Gülltige Längegraad iigä", - "location_picker_longitude_hint": "Längegraad iigä", - "lock": "Spärre", - "locked_folder": "Gspärrter Ordner", - "log_detail_title": "Protokoll Details", - "log_out": "Abmälde", - "log_out_all_devices": "Alli Grät abmälde", - "logged_in_as": "Aagmäldet als {user}", - "logged_out_all_devices": "Alli Grät abgmäldet", - "logged_out_device": "Grät abgmäldet", - "login": "Aamälde", - "login_disabled": "Login isch deaktiviert", - "login_form_api_exception": "API Fähler. Bitte d'Serveradressa überprüäfe und noochemaa probiere.", - "login_form_back_button_text": "Zrugg", - "login_form_email_hint": "dini@email.ch", - "login_form_endpoint_hint": "http://din-server-ip:port", - "login_form_endpoint_url": "Server-URL", - "login_form_err_http": "Bitte gib http:// oder https:// aa", - "login_form_err_invalid_email": "Uungültigi E-Mail", - "login_form_err_invalid_url": "Uungültigi URL", - "login_form_err_leading_whitespace": "Läärzeiche am Aafang", - "login_form_err_trailing_whitespace": "Läärzeiche am Ändi", - "login_form_failed_get_oauth_server_config": "Fähler bim Login per OAuth, bitte Server-URL überprüäfe", - "login_form_failed_get_oauth_server_disable": "D'OAuth-Funktion isch uf däm Server nöd verfüegbar", - "login_form_failed_login": "Fähler bim Login, bitte überprüäf d'Server-URL, dini E-Mail oder's Passwoort", - "login_form_handshake_exception": "Fähler bim Verbindigsufbau mit em Server. Falls du en sälbscht-underschriibigs Zärtifikaat verwändisch, aktivier d'Uunderstützig defür i de Iistellige.", - "login_form_password_hint": "Passwort", - "login_form_save_login": "Aagmäldet bliibe", - "login_form_server_empty": "Serveradressa iigä.", - "login_form_server_error": "Es het nöd chönne Verbindig zum Server ufgnoh werde.", - "login_has_been_disabled": "D'Aamäldig isch deaktiviert worde.", - "login_password_changed_error": "Fähler bim Ändere vo dim Passwort", - "login_password_changed_success": "Passwort erfolgriich gänderet", - "logout_all_device_confirmation": "Bisch sicher, dass du alli Grät abmälde wetsch?", - "logout_this_device_confirmation": "Bisch sicher, dass du das Grät abmälde wetsch?", - "logs": "Protokoll", - "longitude": "Längegrad", - "look": "Erscheinigsbild", - "loop_videos": "Loop-Videos", - "loop_videos_description": "Aktiviere die Option, um i dr Detailahsicht e automatische Videoschlaufe z erstelle.", - "main_branch_warning": "Du bruuchsch e Entwickler-Version. Mir empfehled dringend, e Release-Version z verwände!", - "main_menu": "Hauptmenü", - "maintenance_description": "Immich isch i de Wartigmodus versetzt worde.", - "maintenance_end": "Wartigsmodus beendä", - "maintenance_end_error": "Wartigmodus het nöd chenne beendet werde.", - "maintenance_logged_in_as": "Aktuell aagmäldet als {user}", - "maintenance_title": "Vorübergehend nöd verfügbar", - "make": "Markä", - "manage_geolocation": "Standort verwaltä", - "manage_media_access_rationale": "Die Berechtigung wird bruuch, um Dateie ordnigsgemäss i de Papirkorb z verschiebe und wieder herzstelle.", - "manage_media_access_settings": "Iistellige öffne", - "manage_media_access_subtitle": "Erlaub Immich, Mediedateie z verwalte und z verschiebe.", - "manage_media_access_title": "Verwaltig vo Mediedateie", - "manage_shared_links": "Freigäbeni Links verwalte", - "manage_sharing_with_partners": "Gmeinsami Nutzig mit Partner verwalte", - "manage_the_app_settings": "App-Iistellige verwalte", - "manage_your_account": "Dis Chonto verwalte", - "manage_your_api_keys": "Dini API-Schlüssel verwalte", - "manage_your_devices": "Dini iigloggte Gerät verwalte", - "manage_your_oauth_connection": "Dini OAuth-Verchnüpfig verwalte", - "map": "Charte", - "map_assets_in_bounds": "{count, plural, =0 {Kei Fotos i däm Gebiet} one {# Foto} other {# Fotos}}", - "map_cannot_get_user_location": "Standort het nöd chenne ermittelt werde", - "map_location_dialog_yes": "Jo", - "map_location_picker_page_use_location": "Ufnahmeort verwände", - "map_location_service_disabled_content": "D’Ortigsdienscht müend aktiviert si, um Inhält am aktuelle Standort aazeige z chönne. Wotsch d’Ortigsdienscht jetzt aktiviere?", - "map_location_service_disabled_title": "Ortigsdienscht deaktiviert", - "map_marker_for_images": "Charte-Markierige für Bilder, wo i {city}, {country} ufgnoh worde sind", - "map_marker_with_image": "Charte-Markierig mit Bild", - "map_no_location_permission_content": "D’Ortigsdienscht müend aktiviert si, um Inhält am aktuelle Standort aazeige z chönne. Wotsch d’Ortigsdienscht jetzt aktiviere?", - "map_no_location_permission_title": "Kei Zuegriff uf dä Standort", - "map_settings": "Charte-Iistellige", - "map_settings_dark_mode": "Dunklä Modus", - "map_settings_date_range_option_day": "Letschti 24 Stund", - "map_settings_date_range_option_days": "Letschte {days} Täg", - "map_settings_date_range_option_year": "Letschtes Johr", - "map_settings_date_range_option_years": "Letschte {years} Johre", - "map_settings_dialog_title": "Charte-Iistellige", - "map_settings_include_show_archived": "Archivierti azeige", - "map_settings_include_show_partners": "Partner iibeziehä", - "map_settings_only_show_favorites": "Nur Favoritä azeige", - "map_settings_theme_settings": "Chartedesign", - "map_zoom_to_see_photos": "Ansicht chliiner mache, um Fotos z gseh", - "mark_all_as_read": "Alli als gläse markiere", - "mark_as_read": "Als gläse markiere", - "marked_all_as_read": "Alli als gläse markiert", - "matches": "Treffer", - "matching_assets": "Passendi Dateie", - "media_type": "Medietyp", - "memories": "Erinerrige", - "memories_all_caught_up": "Alles ufgholt", - "memories_check_back_tomorrow": "Chum morn wieder verbi für wiiteri Erinnerige", - "memories_setting_description": "Verwalt, was du i dine Erinnerige gsehsch", - "memories_start_over": "Erneut beginne", - "memories_swipe_to_close": "Nach obe wüschä zum Schliesse", - "memory": "Erinnerig", - "memory_lane_title": "Foto-Erinnerige {title}", - "menu": "Menü", - "merge": "Zämmefüehre", - "merge_people": "Persone Zämmefüehre", - "merge_people_limit": "Du chasch maximal 5 Gsichter uf einisch zämmefüehre", - "merge_people_prompt": "Wotsch die Persone zämmefüehre? Die Aktion cha nöd rückgängig gmacht werde.", - "merge_people_successfully": "Persone erfolgrich zämmegführt", - "merged_people_count": "{count, plural, one {# Person} other {# Persone}} zämmegfüegt", - "minimize": "Minimierä", - "minute": "Minute", - "minutes": "Minuten", - "missing": "Fählendi", - "mobile_app": "Mobile App", - "mobile_app_download_onboarding_note": "Herunterlade vo dr mobile App über eini vo de folgende Möglichkeitä", - "model": "Modäll", - "month": "Monet", - "monthly_title_text_date_format": "MMMM y", - "more": "Meh", - "move": "Verschiebä", - "move_off_locked_folder": "Us em gschperrte Ordner verschiebe", - "move_to": "Verschiebe noch", - "move_to_lock_folder_action_prompt": "{count} zum gschperrte Ordner hinzuegfüegt", - "move_to_locked_folder": "I dä gschperrte Ordner verschiebe", - "move_to_locked_folder_confirmation": "Die Fotos und Videos werde us allne Albe entfernet und chönnd nume no im gschperrte Ordner azeigt werde", - "moved_to_archive": "{count, plural, one {# Datei} other {# Dateie}} archiviert", - "moved_to_library": "{count, plural, one {# Datei} other {# Dateie}} i d'Bibliotheek verschobe", - "moved_to_trash": "I de Papiirschorb verschobe", - "multiselect_grid_edit_date_time_err_read_only": "S Daatum und d'Uhrziit vo schriibgschütztem Inehalt chan nöd veränderet werde, überspringe", - "multiselect_grid_edit_gps_err_read_only": "De Uufnaameort vo schriibgschütztem Inhält chan nöd veränderet werde, überspringe", - "mute_memories": "Erinnerige stumm schalte", - "my_albums": "Mini Albä", - "name": "Name", - "name_or_nickname": "Name oder Spitzname", - "navigate": "Navigation", - "navigate_to_time": "Navigier zu Zyt", - "network_requirement_photos_upload": "Mobili Date verwände, zum Föteli sichere", - "network_requirement_videos_upload": "Mobili Date verwände, zum Videos sichere", - "network_requirements": "Aaforderige ans Nätzwerk", - "network_requirements_updated": "Nätzwerk-Abhängigkeite händ sich gänderet, d'Backup-Warteschlange wird zrugggsetzt", - "networking_settings": "Nätzwerk", - "networking_subtitle": "Verwaltig vo Server-Ändpunkt-Iistellige", - "never": "Niemols", - "new_album": "Neus Album", - "new_api_key": "Neue API-Schlüssel", - "new_date_range": "Neue Datumsberiich", - "new_password": "Neus Passwort", - "new_person": "Neui Person", - "new_pin_code": "Neue PIN-Code", - "new_pin_code_subtitle": "Das isch din erschte Zuegriff uf de gspärrti Ordner. Erstell en PIN-Code, um sicher uf die Siite z'cho", - "new_timeline": "Neui Zytschine", - "new_update": "Neus Update", - "new_user_created": "Neue Benutzer isch erstellt worde", - "new_version_available": "NEUI VERSION VERFÜEGBAR", - "newest_first": "Neuschti z'erscht", - "next": "Wiiter", - "next_memory": "Nächschti Erinnerig", - "no": "Nei", - "no_albums_message": "Erstell es Album, zum dini Föteli und Videos organisiere", - "no_albums_with_name_yet": "Es gseht eso uus, as hättisch no kei Albe mit däm Name.", - "no_albums_yet": "Es gseht eso uus, as hättisch no kei Albene", - "no_archived_assets_message": "Archiviär Föteli und Videos, zum si us dinere Foto-Aasicht z'entfärne", - "no_assets_message": "KLICK, UM DIS ERSHCHTS FÖTELI UFEZLADE", - "no_assets_to_show": "Kei Vorschau vorhande", - "no_cast_devices_found": "Kei Grät zum Überträge gfunde", - "no_checksum_local": "Prüäfsumme nöd verfüegbar - chan lokali Datei/e nöd lade", - "no_checksum_remote": "Prüäfsumme nöd verfüegbar - chan entfernti Datei/e nöd lade", - "no_devices": "Kei verwändeti Grät", - "no_duplicates_found": "Es sind kei Duplikäät gfunde worde.", - "no_exif_info_available": "Kei EXIF-Infos vorhande", - "no_explore_results_message": "Lad wiiteri Föteli uufe, zum dini Sammlig z'entdäcke.", - "no_favorites_message": "Füeg Favoriite dezue, zum dini beschte Bild und Videos schnäll finde", - "no_libraries_message": "Erstell e externi Bibliotheek, zum dini Föteli und Videos aluäge", - "no_local_assets_found": "Kei lokali Datei mit dä Prüäfsumme gfunde", - "no_location_set": "Kei Standoort feschtgleit", - "no_locked_photos_message": "Föteli und Videos im gspärrte Ordner sind versteckt und wärded nöd aazeigt, wänn du dini Bibliotheek dursuächsch.", - "no_name": "Kei Name", - "no_notifications": "Kei Benochrichtigunge", - "no_people_found": "Kei passendi Persone gfunde", - "no_places": "Kei Ört", - "no_remote_assets_found": "Kei entfernti Dateie mit dä Prüäfsumme gfunde", - "no_results": "Kei Ergebniss", - "no_results_description": "Versuech's mit eme Synonüm oder eme allgemäinere Stichwort", - "no_shared_albums_message": "Erstell es Album, zum Föteli und Videos mit Persone i dim Näzwerk teile", - "no_uploads_in_progress": "Kei Upload am Laufä", - "not_allowed": "Nöd erlaubt", - "not_available": "N/A", - "not_in_any_album": "I keinem Album", - "not_selected": "Nöd usgwählt", - "note_apply_storage_label_to_previously_uploaded assets": "Hiwiis: Zum e Spycherpfad-Bezeichnig aawehde, start de", - "notes": "Notize", - "nothing_here_yet": "No nüt do", - "notification_permission_dialog_content": "Zum Benachrichtige aktiviere, navigier zu Iistellige und drück \"Erlaube\".", - "notification_permission_list_tile_content": "Erlaub Berechtigunge für Benochrichtigunge.", - "notification_permission_list_tile_enable_button": "Aktivier Benochrichtigunge", - "notification_permission_list_tile_title": "Benochrichtigungs-Berechtigig", - "notification_toggle_setting_description": "E-Mail-Benochrichtigunge aktiviere", - "notifications": "Benochrichtigunge", - "notifications_setting_description": "Benochrichtigunge verwalte", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium Konfiguratior", - "obtainium_configurator_instructions": "Du chasch Obtainium bruuche, zum d'App direkt us de Github Releases z'instaliere oder z'aktualisiere. Bitte mach dezuene en API-Schlüssel und wähl en Varianti us, zum en Obtainium-Konfigurationslink z'mache", - "ocr": "OCR", - "official_immich_resources": "Offizielli Immich Quelle", - "offline": "Offline", - "offset": "Verschiebig", - "ok": "Ok", - "oldest_first": "Älteschti zerscht", - "on_this_device": "Uf däm Grät", - "onboarding": "Iistig", - "onboarding_locale_description": "Wähl dini bevorzugti Sprooch. Du chasch die au spöter i dine Iistellige ändere.", - "onboarding_privacy_description": "Diä folgende (optionali) Funktione hänged vo externä Diänscht ab und chönd jederziit i de Iistellige deaktiviärt wärde." -} +{} diff --git a/i18n/gu.json b/i18n/gu.json index e942301430..0967ef424b 100644 --- a/i18n/gu.json +++ b/i18n/gu.json @@ -1,17 +1 @@ -{ - "about": "વિશે", - "account": "ખાતું", - "account_settings": "ખાતાનાં સેટિંગ્સ", - "active": "સક્રિય", - "active_count": "સક્રિય: {count}", - "activity": "પ્રવૃત્તિ", - "add": "ઉમેરો", - "add_a_description": "એક વર્ણન ઉમેરો", - "add_a_location": "એક સ્થાન ઉમેરો", - "add_a_name": "એક નામ ઉમેરો", - "add_a_title": "એક શીર્ષક ઉમેરો", - "add_birthday": "એક જન્મદિવસ ઉમેરો", - "add_endpoint": "એન્ડપોઇન્ટ ઉમેરો", - "add_location": "સ્થાન ઉમેરો", - "add_partner": "સાથી ઉમેરો" -} +{} diff --git a/i18n/he.json b/i18n/he.json index 7884cea268..0967ef424b 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -1,2240 +1 @@ -{ - "about": "אודות", - "account": "חשבון", - "account_settings": "הגדרות חשבון", - "acknowledge": "הבנתי", - "action": "פעולה", - "action_common_update": "עדכון", - "action_description": "סט פעולות לביצוע על נכסים מסוננים", - "actions": "פעולות", - "active": "פעיל", - "active_count": "פעיל: {count}", - "activity": "פעילות", - "activity_changed": "הפעילות {enabled, select, true {מופעלת} other {מושבתת}}", - "add": "הוספה", - "add_a_description": "הוספת תיאור", - "add_a_location": "הוספת מיקום", - "add_a_name": "הוספת שם", - "add_a_title": "הוספת כותרת", - "add_action": "הוסף פעולה", - "add_action_description": "לחץ כדי להוסיף פעולה לביצוע", - "add_birthday": "הוספת יום הולדת", - "add_endpoint": "הוסף כתובת URL", - "add_exclusion_pattern": "הוספת דפוס החרגה", - "add_filter": "הוסף סינון", - "add_filter_description": "לחץ כדי להוסיף תנאי לסינון", - "add_location": "הוספת מיקום", - "add_more_users": "הוספת עוד משתמשים", - "add_partner": "הוספת שותף", - "add_path": "הוספת נתיב", - "add_photos": "הוספת תמונות", - "add_tag": "הוספת תג", - "add_to": "הוספה ל…", - "add_to_album": "הוספה לאלבום", - "add_to_album_bottom_sheet_added": "נוסף ל {album}", - "add_to_album_bottom_sheet_already_exists": "כבר ב {album}", - "add_to_album_bottom_sheet_some_local_assets": "לא ניתן היה להוסיף חלק מהקבצים המקומיים לאלבום", - "add_to_album_toggle": "החלפת מצב בחירה עבור {album}", - "add_to_albums": "הוספה לאלבומים", - "add_to_albums_count": "הוסף ({count}) לאלבום", - "add_to_bottom_bar": "הוספה אל", - "add_to_shared_album": "הוספה לאלבום משותף", - "add_upload_to_stack": "הוסף את ההעלאה לערימה", - "add_url": "הוספת קישור", - "add_workflow_step": "הוסף שלב בסדר פעולות", - "added_to_archive": "נוסף לארכיון", - "added_to_favorites": "נוסף למועדפים", - "added_to_favorites_count": "{count, number} נוספו למועדפים", - "admin": { - "add_exclusion_pattern_description": "הוספת דפוסי החרגה. נתמכת התאמת דפוסים באמצעות *, ** ו-?. כדי להתעלם מכל הקבצים בתיקיה כלשהי בשם \"Raw\", יש להשתמש ב \"**/Raw/**\". כדי להתעלם מכל הקבצים המסתיימים ב \"tif.\", יש להשתמש ב \"tif.*/**\". כדי להתעלם מנתיב מוחלט, יש להשתמש ב \"**/נתיב/להתעלמות\".", - "admin_user": "מנהל מערכת", - "asset_offline_description": "תמונה מספרייה חיצונית זו לא נמצאת יותר בדיסק והועברה לאשפה. אם הקובץ הועבר מתוך הספרייה, נא לבדוק את ציר הזמן שלך עבור התמונה המקבילה החדש. כדי לשחזר תמונה זו, נא לוודא ש-Immich יכול לגשת אל נתיב הקובץ למטה ולסרוק מחדש את הספרייה.", - "authentication_settings": "הגדרות התחברות", - "authentication_settings_description": "ניהול סיסמה, OAuth, והגדרות התחברות אחרות", - "authentication_settings_disable_all": "האם ברצונך להשבית את כל שיטות ההתחברות? כניסה למערכת תהיה מושבתת לחלוטין.", - "authentication_settings_reenable": "כדי לאפשר מחדש, נא להשתמש בפקודת שרת.", - "background_task_job": "משימות רקע", - "backup_database": "גיבוי מסד נתונים", - "backup_database_enable_description": "אפשר גיבויי מסד נתונים", - "backup_keep_last_amount": "כמות של גיבויים קודמים שיש לשמור", - "backup_onboarding_1_description": "העתק בענן או במיקום פיזי אחר מחוץ למקום השרת.", - "backup_onboarding_2_description": "העתקים מקומיים במכשירים שונים. זה כולל את הקבצים הראשיים וגיבוי של הקבצים האלה באופן מקומי.", - "backup_onboarding_3_description": "סך כל ההעתקים של הנתונים שלך, כולל הקבצים המקוריים. זה כולל העתק אחד מחוץ למקום השרת ושני העתקים מקומיים.", - "backup_onboarding_description": "אסטרטגיית גיבוי 3-2-1 הינה מומלצת על מנת להגן על הנתונים שלך. עליך להשאיר העתקים של תמונות/סרטונים שהועלו כמו גם את מסד הנתונים של Immich עבור פתרון גיבוי מקיף.", - "backup_onboarding_footer": "עבור מידע נוסף על גיבוי Immich, נא לפנות אל התיעוד.", - "backup_onboarding_parts_title": "גיבוי 3-2-1 כולל:", - "backup_onboarding_title": "גיבויים", - "backup_settings": "הגדרות גיבוי", - "backup_settings_description": "ניהול הגדרות גיבוי מסד נתונים.", - "cleared_jobs": "נוקו משימות עבור: {job}", - "config_set_by_file": "התצורה מוגדרת כעת על ידי קובץ תצורה", - "confirm_delete_library": "האם באמת ברצונך למחוק את הספרייה {library}?", - "confirm_delete_library_assets": "האם באמת ברצונך למחוק את הספרייה הזו? זה ימחק את {count, plural, one {תמונה # המוכלת} other {כל # תמונות המוכלים}} בה מ-Immich ואינו ניתן לביטול. קבצים יישארו בדיסק.", - "confirm_email_below": "כדי לאשר, יש להקליד \"{email}\" למטה", - "confirm_reprocess_all_faces": "האם באמת ברצונך לעבד מחדש את כל הפנים? זה גם ינקה אנשים בעלי שם.", - "confirm_user_password_reset": "האם באמת ברצונך לאפס את הסיסמה של המשתמש {user}?", - "confirm_user_pin_code_reset": "האם אתה בטוח שברצונך לאפס את קוד ה PIN של {user}?", - "copy_config_to_clipboard_description": "העתק את תצורת המערכת הנוכחית כאובייקט JSON ללוח", - "create_job": "צור עבודה", - "cron_expression": "ביטוי cron", - "cron_expression_description": "הגדר את מרווח הסריקה באמצעות תבנית ה- cron. למידע נוסף נא לפנות למשל אל Crontab Guru", - "cron_expression_presets": "הגדרות קבועות מראש של ביטוי cron", - "disable_login": "השבת כניסה", - "duplicate_detection_job_description": "הפעל למידת מכונה על תמונות כדי לזהות תמונות דומות. נשען על חיפוש חכם", - "exclusion_pattern_description": "דפוסי החרגה מאפשרים לך להתעלם מקבצים ומתיקיות בעת סריקת הספרייה שלך. זה שימושי אם יש לך תיקיות המכילות קבצים שאינך רוצה לייבא, כגון קובצי RAW.", - "export_config_as_json_description": "הורדת הגדרות המערכת הנוכחיות כקובץ JSON", - "external_libraries_page_description": "דף ספרייה חיצוני של מנהל מערכת", - "face_detection": "איתור פנים", - "face_detection_description": "אתר את הפנים בתמונות באמצעות למידת מכונה. עבור סרטונים, רק התמונה הממוזערת נלקחת בחשבון. \"רענון\" מעבד (מחדש) את כל התמונות. \"איפוס\" מנקה בנוסף את כל נתוני הפנים הנוכחיים. \"חסרים\" מוסיף לתור תמונות שלא עובדו עדיין. לאחר שאיתור הפנים הושלם, פנים שאותרו יעמדו בתור לזיהוי פנים המשייך אותן לאנשים קיימים או חדשים.", - "facial_recognition_job_description": "קבץ פנים שאותרו לתוך אנשים. שלב זה מורץ לאחר השלמת איתור פנים. \"איפוס\" מקבץ (מחדש) את כל הפרצופים. \"חסרים\" מוסיף לתור פנים שלא הוקצה להם אדם.", - "failed_job_command": "הפקודה {command} נכשלה עבור המשימה: {job}", - "force_delete_user_warning": "אזהרה: פעולה זו תסיר מיד את המשתמש ואת כל התמונות. לא ניתן לבטל פעולה זו והקבצים לא ניתנים לשחזור.", - "image_format": "פורמט", - "image_format_description": "WebP מפיק קבצים קטנים יותר מ JPEG, אך הוא איטי יותר לקידוד.", - "image_fullsize_description": "תמונה בגודל מלא עם מטא-נתונים שהוסרו, משמשת כאשר התמונה מוצגת מקרוב", - "image_fullsize_enabled": "אפשר יצירה של תמונות בגודל מלא", - "image_fullsize_enabled_description": "צור תמונה בגודל מלא עבור פורמטים לא ידידותיים לרשת. כאשר האפשרות \"העדף תצוגה מקדימה מוטמעת\" מופעלת, תצוגות מקדימות מוטמעות משמשות ישירות ללא המרה. אין השפעה על פורמטים ידידותיים לרשת כמו JPEG.", - "image_fullsize_quality_description": "איכות תמונה בגודל מלא מ-1 עד 100. איכות גבוהה יותר היא טובה יותר, אבל מייצרת קבצים גדולים יותר.", - "image_fullsize_title": "הגדרות תמונה בגודל מלא", - "image_prefer_embedded_preview": "העדף תצוגה מקדימה מוטמעת", - "image_prefer_embedded_preview_setting_description": "השתמש בתצוגות מקדימות מוטמעות בתמונות RAW כקלט לעיבוד תמונה וכאשר זמינות. זה יכול להפיק צבעים מדויקים יותר עבור תמונות מסוימות, אבל האיכות של התצוגה המקדימה היא תלוית מצלמה ולתמונה עשויים להיות יותר פגמי דחיסה.", - "image_prefer_wide_gamut": "העדף סולם צבעים רחב", - "image_prefer_wide_gamut_setting_description": "השתמש ב-Display P3 לתמונות ממוזערות. זה משמר טוב יותר את החיוניות של תמונות עם מרחבי צבע רחבים, אבל תמונות עשויות להופיע אחרת במכשירים ישנים עם גרסת דפדפן ישנה. תמונות sRGB נשמרות כ-sRGB כדי למנוע שינויי צבע.", - "image_preview_description": "תמונה בגודל בינוני עם מטא-נתונים שהוסרו, משמשת בעת צפייה בתמונה בודדת ועבור למידת מכונה", - "image_preview_quality_description": "איכות תצוגה מקדימה מ-1 עד 100. איכות גבוהה יותר היא טובה יותר, אבל מייצרת קבצים גדולים יותר ויכולה להפחית את תגובתיות היישום. הגדרת ערך נמוך עשויה להשפיע על איכות תוצאות של למידת מכונה.", - "image_preview_title": "הגדרות תצוגה מקדימה", - "image_quality": "איכות", - "image_resolution": "רזולוציה", - "image_resolution_description": "רזולוציות גבוהות יותר יכולות לשמר פרטים רבים יותר אך לוקחות זמן רב יותר לקידוד, יש להן גדלי קבצים גדולים יותר ויכולות להפחית את תגובתיות היישום.", - "image_settings": "הגדרות תמונה", - "image_settings_description": "ניהול האיכות והרזולוציה של תמונות שנוצרו", - "image_thumbnail_description": "תמונה ממוזערת קטנה עם מטא-נתונים שהוסרו, משמשת בעת צפייה בקבוצות של תמונות כמו ציר הזמן הראשי", - "image_thumbnail_quality_description": "איכות תמונה ממוזערת מ-1 עד 100. איכות גבוהה יותר היא טובה יותר, אבל מייצרת קבצים גדולים יותר ויכולה להפחית את תגובתיות היישום.", - "image_thumbnail_title": "הגדרות תמונה ממוזערת", - "import_config_from_json_description": "ייבוא תצורת מערכת באמצעות קובץ תצורה JSON", - "job_concurrency": "בו-זמניות של {job}", - "job_created": "עבודה נוצרה", - "job_not_concurrency_safe": "עבודה זו אינה בטוחה להרצה במקביל.", - "job_settings": "הגדרות משימה", - "job_settings_description": "נהל את מקביליות העבודות", - "jobs_delayed": "{jobCount, plural, other {# עוכבו}}", - "jobs_failed": "{jobCount, plural, other {# נכשלו}}", - "jobs_over_time": "משימות לאורך זמן", - "library_created": "נוצרה ספרייה: {library}", - "library_deleted": "ספרייה נמחקה", - "library_details": "פרטי ספריה", - "library_folder_description": "ציין תיקייה לייבוא. תיקייה זו, כולל תיקיות משנה, תיסרק לאיתור תמונות וסרטונים.", - "library_remove_exclusion_pattern_prompt": "האם אתה בטוח שברצונך להסיר את דפוס ההחרגה הזה?", - "library_remove_folder_prompt": "האם אתה בטוח שברצונך להסיר את תיקיית הייבוא הזו?", - "library_scanning": "סריקה תקופתית", - "library_scanning_description": "הגדר סריקת ספרייה תקופתית", - "library_scanning_enable_description": "אפשר סריקת ספרייה תקופתית", - "library_settings": "ספרייה חיצונית", - "library_settings_description": "ניהול הגדרות ספרייה חיצונית", - "library_tasks_description": "סרוק ספריות חיצוניות עבור תמונות חדשות ו/או שהשתנו", - "library_updated": "ספריה מעודכנת", - "library_watching_enable_description": "עקוב אחר שינויי קבצים בספריות חיצוניות", - "library_watching_settings": "צפייה בספרייה [ניסיוני]", - "library_watching_settings_description": "עקוב אוטומטית אחר שינויי קבצים", - "logging_enable_description": "אפשר רישום ביומן", - "logging_level_description": "כאשר פועל, באיזה רמת יומן לתעד.", - "logging_settings": "רישום ביומן", - "machine_learning_availability_checks": "בדיקת זמינות", - "machine_learning_availability_checks_description": "זהה ותעדף אוטומטית שרתי למידת מכונה זמינים", - "machine_learning_availability_checks_enabled": "הפעלת בדיקות זמינות", - "machine_learning_availability_checks_interval": "תזמון בדיקה", - "machine_learning_availability_checks_interval_description": "מרווח זמן במילישניות בין בדיקות זמינות", - "machine_learning_availability_checks_timeout": "פסק זמן לבקשה", - "machine_learning_availability_checks_timeout_description": "פסק זמן במילישניות עבור בדיקות זמינות", - "machine_learning_clip_model": "מודל CLIP", - "machine_learning_clip_model_description": "שמו של מודל CLIP רשום כאן. שים לב שעליך להפעיל מחדש את המשימה 'חיפוש חכם' עבור כל התמונות בעת שינוי מודל.", - "machine_learning_duplicate_detection": "איתור כפילויות", - "machine_learning_duplicate_detection_enabled": "אפשר איתור כפילויות", - "machine_learning_duplicate_detection_enabled_description": "אם מושבת, תמונות זהות בדיוק עדיין יעברו ביטול כפילויות.", - "machine_learning_duplicate_detection_setting_description": "השתמש בהטמעות של CLIP כדי למצוא כפילויות אפשריות", - "machine_learning_enabled": "אפשר למידת מכונה", - "machine_learning_enabled_description": "אם מושבת, כל תכונות למידת מכונה יהיו מושבתות ללא קשר להגדרות שלהלן.", - "machine_learning_facial_recognition": "זיהוי פנים", - "machine_learning_facial_recognition_description": "אתר, זהה וקבץ פנים בתמונות", - "machine_learning_facial_recognition_model": "מודל זיהוי פנים", - "machine_learning_facial_recognition_model_description": "הדגמים מופיעים בסדר גודל יורד. דגמים גדולים יותר הם איטיים יותר ומשתמשים ביותר זיכרון, אך מניבים תוצאות טובות יותר. שים לב שעליך להפעיל מחדש את משימת זיהוי הפנים עבור כל התמונות בעת שינוי מודל.", - "machine_learning_facial_recognition_setting": "אפשר זיהוי פנים", - "machine_learning_facial_recognition_setting_description": "אם מושבת, תמונות לא יקודדו לזיהוי פנים ולא יאכלסו את קטע האנשים בעמוד ה\"חקור\".", - "machine_learning_max_detection_distance": "מרחק איתור מרבי", - "machine_learning_max_detection_distance_description": "מרחק מרבי בין שתי תמונות כדי להחשיב אותן ככפולות, נע בין 0.001 ל-0.1. ערכים גבוהים יותר יאתרו כפילויות נוספות, אך עלולים לגרום לתוצאות חיוביות שגויות.", - "machine_learning_max_recognition_distance": "מרחק זיהוי מרבי", - "machine_learning_max_recognition_distance_description": "מרחק מרבי בין שני פנים שייחשב לאותו אדם, נע בין 0 עד 2. הורדת ערך זה יכולה למנוע תיוג של שני אנשים כאותו אדם, בעוד שהעלאתו יכולה למנוע תיוג של אותו אדם כשני אנשים שונים. שים לב שקל יותר למזג שני אנשים מאשר לפצל אדם אחד לשניים, אז עדיף לטעות בצד של סף נמוך יותר כשאפשר.", - "machine_learning_min_detection_score": "ציון איתור מינימלי", - "machine_learning_min_detection_score_description": "ציון ביטחון מינימלי לאיתור פנים מ-0 עד 1. ערכים נמוכים יותר יאתרו יותר פנים אך עלולים לגרום לתוצאות חיוביות שגויות.", - "machine_learning_min_recognized_faces": "מינימום פנים מזוהים", - "machine_learning_min_recognized_faces_description": "המספר המינימלי של פנים מזוהים ליצירת אדם. הגדלת ערך זה הופכת את זיהוי הפנים למדויק יותר בעלות של הגברת הסיכוי שלא יוקצו פנים לאדם.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "השתמש בלמידת מכונה לזיהוי טקסט בתמונות", - "machine_learning_ocr_enabled": "הפעלת OCR", - "machine_learning_ocr_enabled_description": "אם מבוטל, תמונות לא יעברו זיהוי טקסט.", - "machine_learning_ocr_max_resolution": "רזולוציה מירבית", - "machine_learning_ocr_max_resolution_description": "תצוגות מקדימות מעל רזולוציה זו ישונו תוך שמירה על יחס גובה לרוחב. ערכים גבוהים הם מדויקים יותר, אך דורשים יותר זמן עיבוד וזיכרון.", - "machine_learning_ocr_min_detection_score": "ציון איתור מזערי", - "machine_learning_ocr_min_detection_score_description": "ציון ביטחון מזערי לאיתור טקסט בטווח 0-1. ערכים נמוכים יאתרו יותר טקסט אך עלולים לגרום לאיתורים שגויים.", - "machine_learning_ocr_min_recognition_score": "ציון זיהוי מזערי", - "machine_learning_ocr_min_score_recognition_description": "ציון ביטחון מזערי לזיהוי טקסט שאותר בטווח 0-1. ערכים נמוכים יזהו יותר טקסט אך עלולים לגרום לזיהויים שגויים.", - "machine_learning_ocr_model": "מודל OCR", - "machine_learning_ocr_model_description": "מודלי שרת הינם מדויקים יותר ממודלי טלפון, אך לוקחים יותר זמן עיבוד וצורכים יותר זיכרון.", - "machine_learning_settings": "הגדרות למידת מכונה", - "machine_learning_settings_description": "ניהול התכונות וההגדרות של למידת המכונה", - "machine_learning_smart_search": "חיפוש חכם", - "machine_learning_smart_search_description": "חיפוש תמונות באופן סמנטי באמצעות הטמעות של CLIP", - "machine_learning_smart_search_enabled": "אפשר חיפוש חכם", - "machine_learning_smart_search_enabled_description": "אם מושבת, תמונות לא יקודדו לחיפוש חכם.", - "machine_learning_url_description": "כתובת ה-URL של שרת למידת המכונה. אם ניתנת יותר מכתובת URL אחת, כל שרת ינוסה ניסיון אחד בכל פעם עד שאחד מהם יגיב בהצלחה, לפי הסדר מהראשון עד האחרון. שרתים שלא מגיבים יוזנחו זמנית עד שיחזרו להיות מקוונים.", - "maintenance_settings": "תחזוקה", - "maintenance_settings_description": "העבר את Immich למצב תחזוקה.", - "maintenance_start": "התחלת מצב תחזוקה", - "maintenance_start_error": "התחלת מצב תחזוקה נכשלה.", - "manage_concurrency": "ניהול מקביליות", - "manage_concurrency_description": "עבור לדף העבודות כדי לנהל הרצת עבודות במקביל", - "manage_log_settings": "ניהול הגדרות רישום ביומן", - "map_dark_style": "עיצוב כהה", - "map_enable_description": "אפשר תכונות מפה", - "map_gps_settings": "הגדרות מפה & GPS", - "map_gps_settings_description": "ניהול הגדרות מפה & GPS (קידוד גאוגרפי הפוך)", - "map_implications": "תכונת המפה מסתמכת על שירות אריח חיצוני (tiles.immich.cloud)", - "map_light_style": "עיצוב בהיר", - "map_manage_reverse_geocoding_settings": "ניהול הגדרות קידוד גאוגרפי הפוך", - "map_reverse_geocoding": "קידוד גיאוגרפי הפוך", - "map_reverse_geocoding_enable_description": "אפשר קידוד גיאוגרפי הפוך", - "map_reverse_geocoding_settings": "הגדרות קידוד גיאוגרפי הפוך", - "map_settings": "מפה", - "map_settings_description": "ניהול הגדרות מפה", - "map_style_description": "כתובת אתר לערכת נושא של מפה style.json", - "memory_cleanup_job": "ניקוי זיכרון (היום לפני..)", - "memory_generate_job": "יצירת זיכרון (היום לפני..)", - "metadata_extraction_job": "חלץ מטא-נתונים", - "metadata_extraction_job_description": "חלץ מטא-נתונים מכל תמונה, כגון GPS, פנים ורזולוציה", - "metadata_faces_import_setting": "אפשר יבוא פנים", - "metadata_faces_import_setting_description": "יבא פנים מנתוני EXIF של תמונה ומקבצים נלווים", - "metadata_settings": "הגדרות מטא-נתונים", - "metadata_settings_description": "ניהול הגדרות metadata", - "migration_job": "נדידה", - "migration_job_description": "העבר תמונות ממוזערות של תמונות ופנים למבנה התיקיות העדכני ביותר", - "nightly_tasks_cluster_faces_setting_description": "בצע זיהוי פנים עבור פרצופים שזוהו לאחרונה", - "nightly_tasks_cluster_new_faces_setting": "קבץ פנים חדשות", - "nightly_tasks_database_cleanup_setting": "משימות תחזוקה וניקוי של מסד הנתונים", - "nightly_tasks_database_cleanup_setting_description": "נקה נתונים ישנים שפג תוקפם ממסד הנתונים", - "nightly_tasks_generate_memories_setting": "יצירת זכרונות", - "nightly_tasks_generate_memories_setting_description": "צור זכרונות חדשים מהתמונות שלך", - "nightly_tasks_missing_thumbnails_setting": "צור תמונות ממוזערות חסרות", - "nightly_tasks_missing_thumbnails_setting_description": "הוסף לתור קבצים ללא תמונות ממוזערות ליצירה של תמונות ממוזערות", - "nightly_tasks_settings": "הגדרות של משימות ליליות", - "nightly_tasks_settings_description": "נהל משימות ליליות", - "nightly_tasks_start_time_setting": "זמן התחלה", - "nightly_tasks_start_time_setting_description": "השעה שבה השרת מתחיל להריץ את המשימות הליליות", - "nightly_tasks_sync_quota_usage_setting": "סנכרון מכסת שימוש", - "nightly_tasks_sync_quota_usage_setting_description": "עדכן את מכסת האחסון של המשתמש בהתאם לשימוש הנוכחי", - "no_paths_added": "לא נוספו נתיבים", - "no_pattern_added": "לא נוספה תבנית", - "note_apply_storage_label_previous_assets": "הערה: כדי להחיל את תווית האחסון על תמונות שהועלו בעבר, הפעל את", - "note_cannot_be_changed_later": "הערה: אי אפשר לשנות זאת מאוחר יותר!", - "notification_email_from_address": "מכתובת", - "notification_email_from_address_description": "כתובת דוא\"ל של השולח, לדוגמה: \"Immich שרת תמונות \". יש לוודא שנעשה שימוש בכתובת ממנה הנך מורשה לשלוח דוא\"ל.", - "notification_email_host_description": "מארח שרת הדוא\"ל (למשל smtp.immich.app)", - "notification_email_ignore_certificate_errors": "התעלם משגיאות תעודה", - "notification_email_ignore_certificate_errors_description": "התעלם משגיאות אימות תעודת TLS (לא מומלץ)", - "notification_email_password_description": "סיסמה לשימוש בעת אימות עם שרת הדוא\"ל", - "notification_email_port_description": "יציאה של שרת הדוא\"ל (למשל 25, 465, או 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "השתמש ב-SMTPS (פרוטוקול SMTP מעל TLS)", - "notification_email_sent_test_email_button": "שלח דוא\"ל בדיקה ושמור", - "notification_email_setting_description": "הגדרות לשליחת התראות דוא\"ל", - "notification_email_test_email": "שלח דוא\"ל בדיקה", - "notification_email_test_email_failed": "שליחת דוא\"ל בדיקה נכשלה, בדוק את הערכים שלך", - "notification_email_test_email_sent": "דוא\"ל בדיקה נשלח אל {email}. נא לבדוק את תיבת הדואר הנכנס שלך.", - "notification_email_username_description": "שם משתמש לשימוש בעת אימות עם שרת הדוא\"ל", - "notification_enable_email_notifications": "אפשר התראות דוא\"ל", - "notification_settings": "הגדרות התראות", - "notification_settings_description": "ניהול הגדרות התראות, כולל דוא\"ל", - "oauth_auto_launch": "הפעלה אוטומטית", - "oauth_auto_launch_description": "התחל את זרימת ההתחברות של OAuth באופן אוטומטי עם הניווט לדף ההתחברות", - "oauth_auto_register": "רישום אוטומטי", - "oauth_auto_register_description": "רשום אוטומטית משתמשים חדשים לאחר כניסה עם OAuth", - "oauth_button_text": "טקסט לחצן", - "oauth_client_secret_description": "נדרש כאשר ספק ה־OAuth אינו תומך ב־PKCE (מפתח הוכחה להחלפת קוד)", - "oauth_enable_description": "התחבר עם OAuth", - "oauth_mobile_redirect_uri": "URI להפניה מחדש בנייד", - "oauth_mobile_redirect_uri_override": "עקיפת URI להפניה מחדש בנייד", - "oauth_mobile_redirect_uri_override_description": "אפשר כאשר ספק OAuth לא מאפשר כתובת URI לנייד, כמו ''{callback}''", - "oauth_role_claim": "דרישת תפקיד", - "oauth_role_claim_description": "הענק גישת מנהל באופן אוטומטי אם תביעה זו קיימת. ערך התביעה יכול להיות 'user' או 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "ניהול הגדרות התחברות עם OAuth", - "oauth_settings_more_details": "למידע נוסף אודות תכונה זו, בדוק את התיעוד.", - "oauth_storage_label_claim": "דרישת תווית אחסון", - "oauth_storage_label_claim_description": "הגדר אוטומטית את תווית האחסון של המשתמש לערך של דרישה זו.", - "oauth_storage_quota_claim": "דרישת מכסת אחסון", - "oauth_storage_quota_claim_description": "הגדר אוטומטית את מכסת האחסון של המשתמש לערך של דרישה זו.", - "oauth_storage_quota_default": "מכסת אחסון ברירת מחדל (GiB)", - "oauth_storage_quota_default_description": "מכסה ב-GiB לשימוש כאשר לא מסופקת דרישה.", - "oauth_timeout": "הבקשה נכשלה – הזמן הקצוב הסתיים", - "oauth_timeout_description": "זמן קצוב לבקשות (במילישניות)", - "ocr_job_description": "השתמש בלמידת מכונה לזיהוי טקסט בתמונות", - "password_enable_description": "התחבר עם דוא\"ל וסיסמה", - "password_settings": "סיסמת התחברות", - "password_settings_description": "ניהול הגדרות סיסמת התחברות", - "paths_validated_successfully": "כל הנתיבים אומתו בהצלחה", - "person_cleanup_job": "ניקוי אדם", - "queue_details": "פרטי התור", - "queues": "תורי משימות", - "queues_page_description": "עמוד ניהול תורי משימות", - "quota_size_gib": "גודל מכסה (GiB)", - "refreshing_all_libraries": "מרענן את כל הספריות", - "registration": "רישום מנהל מערכת", - "registration_description": "מכיוון שאתה המשתמש הראשון במערכת, אתה תוקצה כמנהל ואתה אחראי על משימות ניהול, ומשתמשים נוספים ייווצרו על ידך.", - "remove_failed_jobs": "הסרת משימות שנכשלו", - "require_password_change_on_login": "דרוש מהמשתמש לשנות סיסמה בכניסה הראשונה", - "reset_settings_to_default": "אפס הגדרות לברירת המחדל", - "reset_settings_to_recent_saved": "אפס הגדרות להגדרות שנשמרו לאחרונה", - "scanning_library": "סורק ספרייה", - "search_jobs": "חיפוש עבודות…", - "send_welcome_email": "שלח דוא\"ל ברוכים הבאים", - "server_external_domain_settings": "דומיין חיצוני", - "server_external_domain_settings_description": "דומיין עבור קישורים משותפים ציבוריים, כולל //:http(s)", - "server_public_users": "משתמשים ציבוריים", - "server_public_users_description": "כל המשתמשים (שם ודוא\"ל) מופיעים בעת הוספת משתמש לאלבומים משותפים. כאשר התכונה מושבתת, רשימת המשתמשים תהיה זמינה רק למשתמשים בעלי הרשאות ניהול.", - "server_settings": "הגדרות שרת", - "server_settings_description": "ניהול הגדרות שרת", - "server_stats_page_description": "עמוד ניהול סטטיסטיקות שרת", - "server_welcome_message": "הודעת פתיחה", - "server_welcome_message_description": "הודעה שמוצגת במסך ההתחברות.", - "settings_page_description": "עמוד ניהול הגדרות", - "sidecar_job": "מטא-נתונים נלווים", - "sidecar_job_description": "גלה או סנכרן מטא-נתונים נלווים ממערכת הקבצים", - "slideshow_duration_description": "מספר שניות להצגת כל תמונה", - "smart_search_job_description": "הפעל למידת מכונה על תמונות כדי לתמוך בחיפוש חכם", - "storage_template_date_time_description": "חותמת זמן יצירת התמונה משמשת למידע על התאריך והשעה", - "storage_template_date_time_sample": "זמן לדוגמא {date}", - "storage_template_enable_description": "הפעל מנוע תבנית אחסון", - "storage_template_hash_verification_enabled": "אימות גיבוב מופעל", - "storage_template_hash_verification_enabled_description": "מאפשר אימות גיבוב, אין להשבית זאת אלא אם יש לך ודאות לגבי ההשלכות", - "storage_template_migration": "העברת תבנית אחסון", - "storage_template_migration_description": "החל את ה{template} הנוכחית על תמונות שהועלו בעבר", - "storage_template_migration_info": "תבנית האחסון תמיר את כל ההרחבות לאותיות קטנות. שינויים בתבנית יחולו רק על תמונות חדשות. כדי להחיל באופן רטרואקטיבי את התבנית על תמונות שהועלו בעבר, הפעל את {job}.", - "storage_template_migration_job": "משימת העברת תבנית אחסון", - "storage_template_more_details": "לפרטים נוספים אודות תכונה זו, עיין בתבנית האחסון ובהשלכותיה", - "storage_template_onboarding_description_v2": "כאשר פיצ’ר זה מופעל, הקבצים יאורגנו אוטומטית לפי תבנית שהוגדרה על ידי המשתמש. למידע נוסף, עיין ב־תיעוד.", - "storage_template_path_length": "מגבלת אורך נתיב משוערת: {length, number}/{limit, number}", - "storage_template_settings": "תבנית אחסון", - "storage_template_settings_description": "ניהול מבנה התיקיות ואת שם הקובץ של התמונה שהועלתה", - "storage_template_user_label": "{label} היא תווית האחסון של המשתמש", - "system_settings": "הגדרות מערכת", - "tag_cleanup_job": "ניקוי תגים", - "template_email_available_tags": "ניתן להשתמש במשתנים הבאים בתבנית שלך: {tags}", - "template_email_if_empty": "אם התבנית ריקה, ייעשה שימוש בדוא\"ל ברירת המחדל.", - "template_email_invite_album": "תבנית הזמנת אלבום", - "template_email_preview": "תצוגה מקדימה", - "template_email_settings": "תבניות דוא\"ל", - "template_email_update_album": "עדכון תבנית אלבום", - "template_email_welcome": "תבנית דוא\"ל ברוכים הבאים", - "template_settings": "תבניות התראה", - "template_settings_description": "ניהול תבניות מותאמות אישית עבור התראות", - "theme_custom_css_settings": "CSS בהתאמה אישית", - "theme_custom_css_settings_description": "גיליונות סגנון מדורגים (CSS) מאפשרים התאמה אישית של העיצוב של Immich.", - "theme_settings": "הגדרות ערכת נושא", - "theme_settings_description": "ניהול התאמה אישית של ממשק האינטרנט של Immich", - "thumbnail_generation_job": "צור תמונות ממוזערות", - "thumbnail_generation_job_description": "יוצר תמונות ממוזערות גדולות, קטנות ומטושטשות עבור כל תמונה, כמו גם תמונות ממוזערות עבור כל אדם", - "transcoding_acceleration_api": "API האצה", - "transcoding_acceleration_api_description": "ה-API שייצור אינטראקציה עם המכשיר שלך כדי להאיץ את המרת הקידוד. הגדרה זו היא 'המאמץ הטוב ביותר': היא תחזור לקידוד תוכנה במקרה של כשל. VP9 עשוי לעבוד או לא, תלוי בחומרה שלך.", - "transcoding_acceleration_nvenc": "NVENC (דורש כרטיס מסך של NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (דורש מעבד אינטל מדור 7 ואילך)", - "transcoding_acceleration_rkmpp": "RKMPP (רק במערכות על שבב של Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "קודקים מקובלים של שמע", - "transcoding_accepted_audio_codecs_description": "בחר אילו קודקים של שמע אינם צריכים לעבור המרת קידוד. משמש רק עבור פוליסות המרת קידוד מסוימות.", - "transcoding_accepted_containers": "מכולות מקובלות", - "transcoding_accepted_containers_description": "בחר אילו פורמטי מכולה אין צורך לשנות ל-MP4. משמש רק עבור מדיניות קידוד מסוימות.", - "transcoding_accepted_video_codecs": "קודקים מקובלים של סרטונים", - "transcoding_accepted_video_codecs_description": "בחר אילו קודקים של סרטונים אינם צריכים לעבור המרת קידוד. משמש רק עבור פוליסות המרת קידוד מסוימות.", - "transcoding_advanced_options_description": "אפשרויות שרוב המשתמשים לא צריכים לשנות", - "transcoding_audio_codec": "קודק שמע", - "transcoding_audio_codec_description": "Opus היא האפשרות האיכותית ביותר, אך בעלת תאימות נמוכה יותר למכשירים או תוכנות ישנות.", - "transcoding_bitrate_description": "סרטונים גבוהים מקצב סיביות מרבי או לא בפורמט מקובל", - "transcoding_codecs_learn_more": "כדי ללמוד עוד על המינוח בשימוש כאן, פנה אל תיעוד FFmpeg עבור קודק H.264, קודק HEVC וקודק VP9.", - "transcoding_constant_quality_mode": "מצב איכות קבועה", - "transcoding_constant_quality_mode_description": "ICQ טוב יותר מ-CQP, אך חלק מהתקני האצת החומרה אינם תומכים במצב זה. הגדרת אפשרות זו תעדיף את המצב שצוין בעת שימוש בקידוד מבוסס איכות. בהתעלמות מצד NVENC מכיוון שהוא אינו תומך ב-ICQ.", - "transcoding_constant_rate_factor": "גורם קצב קבוע (-crf)", - "transcoding_constant_rate_factor_description": "רמת איכות וידאו. ערכים אופייניים הם הערך 23 עבור H.264, הערך 28 עבור HEVC, הערך 31 עבור VP9, והערך 35 עבור AV1. נמוך יותר הוא טוב יותר, אבל מייצר קבצים גדולים יותר.", - "transcoding_disabled_description": "אין להמיר את הקידוד של שום סרטון, עלול לגרום לכך שהניגון לא יפעל במכשירים מסוימים", - "transcoding_encoding_options": "אפשרויות קידוד", - "transcoding_encoding_options_description": "הגדר מקודדים, רזולוציה, איכות ואפשרויות אחרות עבור הסרטונים המקודדים", - "transcoding_hardware_acceleration": "האצת חומרה", - "transcoding_hardware_acceleration_description": "ניסיוני; המרה יותר מהירה, אבל תהיה באיכות נמוכה יותר באותו קצב סיביות", - "transcoding_hardware_decoding": "פענוח חומרה", - "transcoding_hardware_decoding_setting_description": "מאפשר האצה מקצה לקצה במקום רק האצת קידוד. ייתכן שלא יפעל על כל הסרטונים.", - "transcoding_max_b_frames": "B-פריימים מרביים", - "transcoding_max_b_frames_description": "ערכים גבוהים יותר משפרים את יעילות הדחיסה, אך מאטים את הקידוד. ייתכן שלא יהיה תואם עם האצת חומרה במכשירים ישנים יותר. 0 משבית את B-פריימים, בעוד ש1- מגדיר את הערך זה באופן אוטומטי.", - "transcoding_max_bitrate": "קצב סיביות מרבי", - "transcoding_max_bitrate_description": "קביעת קצב סיביות מרבי יכולה להפוך את גדלי הקבצים לצפויים יותר בעלות קלה לאיכות. ב-720p, ערכים טיפוסיים הם 2600 kbit/s עבור VP9 או HEVC, או 4500 kbit/s עבור H.264. מושבת אם מוגדר ל-0. אם לא הוגדרו יחידות, ייעשה שימוש ב-k (עבור kbit/s)ף כלומר 5000, 5000k ו-5M (עבור Mbit/s) שקולים.", - "transcoding_max_keyframe_interval": "מרווח תמונת מפתח מרבי", - "transcoding_max_keyframe_interval_description": "מגדיר את מרחק הפריימים המרבי בין תמונות מפתח. ערכים נמוכים גורעים את יעילות הדחיסה, אך משפרים את זמני החיפוש ועשויים לשפר את האיכות בסצנות עם תנועה מהירה. 0 מגדיר ערך זה באופן אוטומטי.", - "transcoding_optimal_description": "סרטונים גבוהים מרזולוציית היעד או לא בפורמט מקובל", - "transcoding_policy": "מדיניות המרה", - "transcoding_policy_description": "הגדר מתי סרטון יעבור המרת קידוד", - "transcoding_preferred_hardware_device": "מכשיר חומרה מועדף", - "transcoding_preferred_hardware_device_description": "חל רק על VAAPI ו-QSV. מגדיר את צומת ה-dri המשמש להמרת קידוד של חומרה.", - "transcoding_preset_preset": "הגדרות קבועות מראש (-preset)", - "transcoding_preset_preset_description": "מהירות דחיסה. הגדרות קבועות מראש איטיות יותר מייצרות קבצים קטנים יותר, ומגבירות את האיכות כאשר מכוונים לקצב סיביות מסוים. VP9 מתעלם ממהירויות מעל 'מהיר יותר'.", - "transcoding_reference_frames": "פריימים לייחוס", - "transcoding_reference_frames_description": "מספר הפריימים לייחוס בעת דחיסה של פריים נתון. ערכים גבוהים יותר משפרים את יעילות הדחיסה, אך מאטים את הקידוד. 0 מגדיר את הערך זה באופן אוטומטי.", - "transcoding_required_description": "רק סרטונים שאינם בפורמט מקובל", - "transcoding_settings": "הגדרות המרת קידוד סרטונים", - "transcoding_settings_description": "ניהול אילו סרטונים להמיר וכיצד לעבד אותם", - "transcoding_target_resolution": "רזולוציה יעד", - "transcoding_target_resolution_description": "רזולוציות גבוהות יותר יכולות לשמר פרטים רבים יותר אך לוקחות זמן רב יותר לקידוד, יש להן גדלי קבצים גדולים יותר, ויכולות להפחית את תגובתיות היישום.", - "transcoding_temporal_aq": "AQ מבוסס זמן", - "transcoding_temporal_aq_description": "חל רק על NVENC. כימות מסתגל לפי זמן מגביר את האיכות של סצנות עם רמת פירוט גבוהה בהילוך איטי. ייתכן שלא יהיה תואם למכשירים ישנים יותר.", - "transcoding_threads": "תהליכונים", - "transcoding_threads_description": "ערכים גבוהים יותר מובילים לקידוד מהיר יותר, אך משאירים פחות מקום לשרת לעבד משימות אחרות בעודו פעיל. ערך זה לא אמור להיות יותר ממספר ליבות המעבד. ממקסם את הניצול אם מוגדר ל-0.", - "transcoding_tone_mapping": "מיפוי גוונים", - "transcoding_tone_mapping_description": "מנסה לשמר את המראה של סרטוני HDR כשהם מומרים ל-SDR. כל אלגוריתם עושה פשרות שונות עבור צבע, פירוט ובהירות. Hable משמר פרטים, Mobius משמר צבע, ו-Reinhard משמר בהירות.", - "transcoding_transcode_policy": "מדיניות המרת קידוד", - "transcoding_transcode_policy_description": "מדיניות לגבי מתי יש להמיר קידוד של סרטון. תמיד יומר הקידוד של סרטוני HDR (למעט אם המרת קידוד מושבתת).", - "transcoding_two_pass_encoding": "קידוד בשני מעברים", - "transcoding_two_pass_encoding_setting_description": "המר קידוד בשני מעברים כדי לייצר סרטונים מקודדים טוב יותר. כאשר קצב סיביות מרבי מופעל (נדרש כדי שזה יעבוד עם H.264 ו-HEVC), מצב זה משתמש בטווח קצב סיביות המבוסס על קצב הסיביות המרבי ומתעלם מ-CRF. עבור VP9, ניתן להשתמש ב-CRF אם קצב סיביות מרבי מושבת.", - "transcoding_video_codec": "מקודד סרטון", - "transcoding_video_codec_description": "ל-VP9 יש יעילות גבוהה ותאימות רשת, אבל לוקח יותר זמן להמיר את הקידוד עבורו. HEVC מתפקד באופן דומה, אך בעל תאימות רשת נמוכה יותר. H.264 תואם באופן נרחב ומהיר להמיר את קידודו, אבל הוא מייצר קבצים גדולים בהרבה. AV1 הוא הקידוד היעיל ביותר אך לוקה בתמיכה במכשירים ישנים יותר.", - "trash_enabled_description": "הפעל את תכונות האשפה", - "trash_number_of_days": "מספר הימים", - "trash_number_of_days_description": "מספר הימים לשמירה של תמונות באשפה לפני הסרתם לצמיתות", - "trash_settings": "הגדרות האשפה", - "trash_settings_description": "ניהול הגדרות האשפה", - "unlink_all_oauth_accounts": "ביטול קישור כל חשבונות OAuth", - "unlink_all_oauth_accounts_description": "זכור לבטל את הקישור של כל חשבונות ה-OAuth לפני ההגירה לספק חדש.", - "unlink_all_oauth_accounts_prompt": "האם באמת ברצונך לבטל את הקישור של כל חשבונות ה-OAuth? פעולה זו תאפס את מזהה ה-OAuth עבור כל משתמש ואינה ניתנת לביטול.", - "user_cleanup_job": "ניקוי משתמשים", - "user_delete_delay": "החשבון והתמונות של {user} יתוזמנו למחיקה לצמיתות בעוד {delay, plural, one {יום #} other {# ימים}}.", - "user_delete_delay_settings": "עיכוב מחיקה", - "user_delete_delay_settings_description": "מספר הימים לאחר ההסרה עד מחיקה לצמיתות של החשבון והתמונות של המשתמש. משימת מחיקת המשתמש פועלת בחצות כדי לבדוק אם יש משתמשים שמוכנים למחיקה. שינויים בהגדרה זו יוערכו בביצוע הבא.", - "user_delete_immediately": "החשבון והתמונות של {user} יעמדו בתור למחיקה לצמיתות באופן מיידי.", - "user_delete_immediately_checkbox": "הצב משתמש ותמונות בתור למחיקה מיידית", - "user_details": "פרטי משתמש", - "user_management": "ניהול משתמשים", - "user_password_has_been_reset": "סיסמת המשתמש אופסה:", - "user_password_reset_description": "אנא ספק את הסיסמה הזמנית למשתמש והודע לו שיש צורך לשנות את הסיסמה בכניסה הבאה שלו.", - "user_restore_description": "החשבון של {user} ישוחזר.", - "user_restore_scheduled_removal": "שחזר משתמש - מחיקה מתוזמנת ב-{date, date, long}", - "user_settings": "הגדרות משתמש", - "user_settings_description": "ניהול הגדרות משתמש", - "user_successfully_removed": "המשתמש {email} הוסר בהצלחה.", - "users_page_description": "עמוד ניהול משתמשים", - "version_check_enabled_description": "אפשר בדיקת גרסה", - "version_check_implications": "תכונת בדיקת הגרסה מסתמכת על תקשורת תקופתית עם github.com", - "version_check_settings": "בדיקת גרסה", - "version_check_settings_description": "הפעל/השבת את ההתראה על גרסה חדשה", - "video_conversion_job": "המרת קידוד סרטונים", - "video_conversion_job_description": "המרת קידוד סרטונים לתאימות רחבה יותר עם דפדפנים ומכשירים" - }, - "admin_email": "דוא\"ל מנהל", - "admin_password": "סיסמת מנהל", - "administration": "ניהול", - "advanced": "מתקדם", - "advanced_settings_enable_alternate_media_filter_subtitle": "השתמש באפשרות זו כדי לסנן מדיה במהלך הסנכרון לפי קריטריונים חלופיים. מומלץ להשתמש בזה רק אם יש בעיה בזיהוי כל האלבומים באפליקציה.", - "advanced_settings_enable_alternate_media_filter_title": "[ניסיוני] השתמש במסנן סנכרון אלבום חלופי שמבכשיר", - "advanced_settings_log_level_title": "רמת רישום ביומן: {level}", - "advanced_settings_prefer_remote_subtitle": "במכשירים מסוימים טעינת תמונות ממוזערות מקבצים מקומיים עלולה להיות איטית במיוחד. הפעל הגדרה זו כדי לטעון תמונות מרוחקות במקום זאת.", - "advanced_settings_prefer_remote_title": "העדף תמונות מרוחקות", - "advanced_settings_proxy_headers_subtitle": "הגדר proxy headers שהיישום צריך לשלוח עם כל בקשת רשת", - "advanced_settings_proxy_headers_title": "כותרות פרוקסי [ניסיוני]", - "advanced_settings_readonly_mode_subtitle": "מאפשר את מצב לקריאה בלבד בו התמונות ניתנות לצפייה בלבד, דברים כמו בחירת תמונות מרובות, שיתוף, שידור, מחיקה הם כולם מושבתים. אפשר/השבת מצב לקריאה בלבד באמצעות יצגן המשתמש מהמסך הראשי", - "advanced_settings_readonly_mode_title": "מצב קריאה בלבד", - "advanced_settings_self_signed_ssl_subtitle": "מדלג על אימות תעודת SSL עבור כתובת URL של השרת. דרוש עבור תעודות בחתימה עצמית.", - "advanced_settings_self_signed_ssl_title": "התר תעודות SSL בחתימה עצמית [ניסיוני]", - "advanced_settings_sync_remote_deletions_subtitle": "מחק או שחזר תמונה במכשיר זה באופן אוטומטי כאשר פעולה זו נעשית בדפדפן", - "advanced_settings_sync_remote_deletions_title": "סנכרן מחיקות שבוצעו במכשירים אחרים [נסיוני]", - "advanced_settings_tile_subtitle": "הגדרות משתמש מתקדם", - "advanced_settings_troubleshooting_subtitle": "אפשר תכונות נוספות לפתרון בעיות", - "advanced_settings_troubleshooting_title": "פתרון בעיות", - "age_months": "גיל {months, plural, one {חודש #} other {# חודשים}}", - "age_year_months": "גיל שנה 1, {months, plural, one {חודש #} other {# חודשים}}", - "age_years": "{years, plural, other {גיל #}}", - "album": "אלבום", - "album_added": "אלבום נוסף", - "album_added_notification_setting_description": "קבלת הודעת דוא\"ל כאשר מוסיפים אותך לאלבום משותף", - "album_cover_updated": "עטיפת האלבום עודכנה", - "album_delete_confirmation": "האם באמת ברצונך למחוק את האלבום {album}?", - "album_delete_confirmation_description": "אם האלבום הזה משותף, משתמשים אחרים לא יוכלו לגשת אליו יותר.", - "album_deleted": "אלבום נמחק", - "album_info_card_backup_album_excluded": "הוחרגו", - "album_info_card_backup_album_included": "נכללו", - "album_info_updated": "מידע האלבום עודכן", - "album_leave": "לעזוב אלבום?", - "album_leave_confirmation": "האם באמת ברצונך לעזוב את {album}?", - "album_name": "שם האלבום", - "album_options": "אפשרויות האלבום", - "album_remove_user": "להסיר משתמש?", - "album_remove_user_confirmation": "האם באמת ברצונך להסיר את {user}?", - "album_search_not_found": "לא נמצאו אלבומים התואמים לחיפוש שלך", - "album_selected": "אלבום נבחר", - "album_share_no_users": "נראה ששיתפת את האלבום הזה עם כל המשתמשים או שאין לך אף משתמש לשתף איתו.", - "album_summary": "תקציר אלבום", - "album_updated": "אלבום עודכן", - "album_updated_setting_description": "קבל הודעת דוא\"ל כאשר לאלבום משותף יש תמונות חדשות", - "album_user_left": "עזב את {album}", - "album_user_removed": "{user} הוסר", - "album_viewer_appbar_delete_confirm": "האם את/ה בטוח/ה שברצונך למחוק את האלבום הזה מהחשבון שלך?", - "album_viewer_appbar_share_err_delete": "מחיקת אלבום נכשלה", - "album_viewer_appbar_share_err_leave": "עזיבת האלבום נכשלה", - "album_viewer_appbar_share_err_remove": "יש בעיות בהסרת התמונות מהאלבום", - "album_viewer_appbar_share_err_title": "נכשל בשינוי כותרת האלבום", - "album_viewer_appbar_share_leave": "עזוב אלבום", - "album_viewer_appbar_share_to": "שתף עם", - "album_viewer_page_share_add_users": "הוסף משתמשים", - "album_with_link_access": "אפשר לכל אחד עם הקישור לראות תמונות ואנשים באלבום הזה.", - "albums": "אלבומים", - "albums_count": "{count, plural, one {אלבום {count, number}} other {{count, number} אלבומים}}", - "albums_default_sort_order": "סדר מיון אלבומים ברירת מחדל", - "albums_default_sort_order_description": "סדר מיון תמונות ראשוני בעת יצירת אלבומים חדשים.", - "albums_feature_description": "אוספים של תמונות אשר ניתנים לשיתוף עם משתמשים אחרים.", - "albums_on_device_count": "אלבומים במכשיר ({count})", - "all": "הכל", - "all_albums": "כל האלבומים", - "all_people": "כל האנשים", - "all_videos": "כל הסרטונים", - "allow_dark_mode": "אפשר מצב כהה", - "allow_edits": "אפשר עריכות", - "allow_public_user_to_download": "אפשר למשתמש ציבורי להוריד", - "allow_public_user_to_upload": "אפשר למשתמש ציבורי להעלות", - "allowed": "מורשה", - "alt_text_qr_code": "תמונת קוד QR", - "anti_clockwise": "נגד כיוון השעון", - "api_key": "מפתח API", - "api_key_description": "הערך הזה יוצג רק פעם אחת. נא לוודא שהעתקת אותו לפני סגירת החלון.", - "api_key_empty": "מפתח ה-API שלך לא אמור להיות ריק", - "api_keys": "מפתחות API", - "app_architecture_variant": "וריאנט (ארכיטקטורה)", - "app_bar_signout_dialog_content": "האם את/ה בטוח/ה שברצונך להתנתק?", - "app_bar_signout_dialog_ok": "כן", - "app_bar_signout_dialog_title": "התנתק", - "app_download_links": "קישורים להורדת האפליקציה", - "app_settings": "הגדרות יישום", - "app_stores": "חנויות אפליקציה", - "app_update_available": "יש עדכון לאפליקציה", - "appears_in": "מופיע ב", - "apply_count": "החל ({count, number})", - "archive": "ארכיון", - "archive_action_prompt": "{count} נוספו לארכיון", - "archive_or_unarchive_photo": "העבר תמונה לארכיון או הוצא אותה משם", - "archive_page_no_archived_assets": "לא נמצאו תמונות בארכיון", - "archive_page_title": "בארכיון ({count})", - "archive_size": "גודל הארכיון", - "archive_size_description": "הגדר את גודל הארכיון להורדות (ב-GiB)", - "archived": "בארכיון", - "archived_count": "{count, plural, other {# הועברו לארכיון}}", - "are_these_the_same_person": "האם אלה אותו האדם?", - "are_you_sure_to_do_this": "האם באמת ברצונך לעשות את זה?", - "array_field_not_fully_supported": "שדות המערך דורשים עריכה ידנית של ה-JSON", - "asset_action_delete_err_read_only": "לא ניתן למחוק תמונות לקריאה בלבד, מדלג", - "asset_action_share_err_offline": "לא ניתן להשיג תמונות לא מקוונות, מדלג", - "asset_added_to_album": "נוסף לאלבום", - "asset_adding_to_album": "מוסיף לאלבום…", - "asset_created": "תמונה נוצרה", - "asset_description_updated": "תיאור התמונה עודכן", - "asset_filename_is_offline": "התמונה {filename} אינה מקוונת", - "asset_has_unassigned_faces": "לתמונה יש פנים שלא הוקצו", - "asset_hashing": "מגבב…", - "asset_list_group_by_sub_title": "קבץ לפי", - "asset_list_layout_settings_dynamic_layout_title": "פריסה דינמית", - "asset_list_layout_settings_group_automatically": "אוטומטי", - "asset_list_layout_settings_group_by": "קבץ תמונות לפי", - "asset_list_layout_settings_group_by_month_day": "חודש + יום", - "asset_list_layout_sub_title": "פריסה", - "asset_list_settings_subtitle": "הגדרות תבנית רשת תמונות", - "asset_list_settings_title": "רשת תמונות", - "asset_offline": "תמונה לא מקוונת", - "asset_offline_description": "התמונה החיצונית הזאת כבר לא נמצאת בדיסק. נא ליצור קשר עם מנהל Immich שלך לקבלת עזרה.", - "asset_restored_successfully": "תמונה שוחזרה בהצלחה", - "asset_skipped": "דילג", - "asset_skipped_in_trash": "באשפה", - "asset_trashed": "התמונה הועברה לאשפה", - "asset_troubleshoot": "פתרון בעיות בתמונות", - "asset_uploaded": "הועלה", - "asset_uploading": "מעלה…", - "asset_viewer_settings_subtitle": "ניהול הגדרות מציג הגלריה שלך", - "asset_viewer_settings_title": "מציג התמונות", - "assets": "תמונות וסרטונים", - "assets_added_count": "{count, plural, one {נוספה תומנה #} other {נוספו # תמונות}}", - "assets_added_to_album_count": "{count, plural, one {נוספה תמונה #} other {נוספו # תמונות}} לאלבום", - "assets_added_to_albums_count": "{assetTotal, plural, one {נוסף פריט #} other {נוספו # פריטים}} אל {albumTotal, plural, one {אלבום #} other {# אלבומים}}", - "assets_cannot_be_added_to_album_count": "לא ניתן להוסיף את ה{count, plural, one {תמונה} other {תמונות}} לאלבום", - "assets_cannot_be_added_to_albums": "לא ניתן להוסיף {count, plural, one {פריט} other {פריטים}} לאף אחד מהאלבומים", - "assets_count": "{count, plural, one {תמונה #} other {# תמונות}}", - "assets_deleted_permanently": "{count} תמונות נמחקו לצמיתות", - "assets_deleted_permanently_from_server": "{count} תמונות נמחקו לצמיתות משרת ה-Immich", - "assets_downloaded_failed": "{count, plural, one {הורד קובץ # - קובץ {error} נכשל} other {ירדו # קבצים - {error} קבצים נכשלו}}", - "assets_downloaded_successfully": "{count, plural, one {קובץ # ירד בהצלחה} other {# קבצים ירדו בהצלחה}}", - "assets_moved_to_trash_count": "{count, plural, one {תמונה # הועברה} other {# תמונות הועברו}} לאשפה", - "assets_permanently_deleted_count": "{count, plural, one {תמונה # נמחקה} other {# תמונות נמחקו}} לצמיתות", - "assets_removed_count": "{count, plural, one {תמונה # הוסרה} other {# תמונות הוסרו}}", - "assets_removed_permanently_from_device": "{count} תמונות נמחקו לצמיתות מהמכשיר שלך", - "assets_restore_confirmation": "האם באמת ברצונך לשחזר את כל התמונות שבאשפה? אין באפשרותך לבטל את הפעולה הזו! יש לשים לב שלא ניתן לשחזר תמונות לא מקוונות בדרך זו.", - "assets_restored_count": "{count, plural, one {תמונה # שוחזרה} other {# תמונות שוחזרו}}", - "assets_restored_successfully": "{count} תמונות שוחזרו בהצלחה", - "assets_trashed": "{count} תמונות הועברו לאשפה", - "assets_trashed_count": "{count, plural, one {תמונה # הושלכה} other {# תמונות הושלכו}} לאשפה", - "assets_trashed_from_server": "{count} תמונות הועברו לאשפה מהשרת", - "assets_were_part_of_album_count": "{count, plural, one {תמונה הייתה} other {תמונות היו}} כבר חלק מהאלבום", - "assets_were_part_of_albums_count": "{count, plural, one {הפריט כבר היה} other {הפריטים כבר היו}} חלק מהאלבומים", - "authorized_devices": "מכשירים מורשים", - "automatic_endpoint_switching_subtitle": "התחבר מקומית דרך אינטרנט אלחוטי ייעודי כאשר זמין והשתמש בחיבורים חלופיים במקומות אחרים", - "automatic_endpoint_switching_title": "החלפת כתובת אוטומטית", - "autoplay_slideshow": "מצגת תמונות אוטומטית", - "back": "חזרה", - "back_close_deselect": "חזור, סגור, או בטל בחירה", - "background_backup_running_error": "גיבוי ברקע פועל כעת, לא ניתן להפעיל גיבוי ידני", - "background_location_permission": "הרשאת מיקום ברקע", - "background_location_permission_content": "כדי להחליף רשתות בעת ריצה ברקע, היישום צריך *תמיד* גישה למיקום מדויק על מנת לקרוא את השם של רשת האינטרנט האלחוטי", - "background_options": "אפשרויות רקע", - "backup": "גיבוי", - "backup_album_selection_page_albums_device": "({count}) אלבומים במכשיר", - "backup_album_selection_page_albums_tap": "הקש כדי לכלול, הקש פעמיים כדי להחריג", - "backup_album_selection_page_assets_scatter": "תמונות יכולות להתפזר על פני אלבומים מרובים. לפיכך, ניתן לכלול או להחריג אלבומים במהלך תהליך הגיבוי.", - "backup_album_selection_page_select_albums": "בחירת אלבומים", - "backup_album_selection_page_selection_info": "פרטי בחירה", - "backup_album_selection_page_total_assets": "סה״כ תמונות ייחודיות", - "backup_albums_sync": "סנכרון אלבומי גיבוי", - "backup_all": "הכל", - "backup_background_service_backup_failed_message": "נכשל בגיבוי תמונות. מנסה שוב…", - "backup_background_service_complete_notification": "גיבוי הנכסים הושלם", - "backup_background_service_connection_failed_message": "נכשל בהתחברות לשרת. מנסה שוב…", - "backup_background_service_current_upload_notification": "מעלה {filename}", - "backup_background_service_default_notification": "מחפש תמונות חדשות…", - "backup_background_service_error_title": "שגיאת גיבוי", - "backup_background_service_in_progress_notification": "מגבה את התמונות שלך…", - "backup_background_service_upload_failure_notification": "{filename} נכשל בהעלאה", - "backup_controller_page_albums": "אלבומים לגיבוי", - "backup_controller_page_background_app_refresh_disabled_content": "אפשר רענון אפליקציה ברקע בהגדרות > כללי > רענון אפליקציה ברקע כדי להשתמש בגיבוי ברקע.", - "backup_controller_page_background_app_refresh_disabled_title": "רענון אפליקציה ברקע מושבת", - "backup_controller_page_background_app_refresh_enable_button_text": "לך להגדרות", - "backup_controller_page_background_battery_info_link": "הראה לי איך", - "backup_controller_page_background_battery_info_message": "עבור חווית גיבוי ברקע הטובה ביותר, נא להשבית את כל מיטובי הסוללה המגבילים פעילות ברקע עבור היישום.\n\nמכיוון שזה תלוי מכשיר, בבקשה חפש/י את המידע הנדרש עבור יצרן המכשיר שלך.", - "backup_controller_page_background_battery_info_ok": "בסדר", - "backup_controller_page_background_battery_info_title": "מיטובי סוללה", - "backup_controller_page_background_charging": "רק בטעינה", - "backup_controller_page_background_configure_error": "נכשל בהגדרת תצורת שירות הרקע", - "backup_controller_page_background_delay": "השהה גיבוי של תמונות חדשות: {duration}", - "backup_controller_page_background_description": "הפעל את השירות רקע כדי לגבות באופן אוטומטי כל תמונה חדשה מבלי להצטרך לפתוח את היישום", - "backup_controller_page_background_is_off": "גיבוי אוטומטי ברקע כבוי", - "backup_controller_page_background_is_on": "גיבוי אוטומטי ברקע מופעל", - "backup_controller_page_background_turn_off": "כבה שירות גיבוי ברקע", - "backup_controller_page_background_turn_on": "הפעל שירות גיבוי ברקע", - "backup_controller_page_background_wifi": "רק ברשת אלחוטית", - "backup_controller_page_backup": "גיבוי", - "backup_controller_page_backup_selected": "נבחרו: ", - "backup_controller_page_backup_sub": "תמונות וסרטונים מגובים", - "backup_controller_page_created": "נוצר ב: {date}", - "backup_controller_page_desc_backup": "הפעל גיבוי חזית כדי להעלות באופן אוטומטי תמונות חדשות לשרת כשפותחים את היישום.", - "backup_controller_page_excluded": "הוחרגו: ", - "backup_controller_page_failed": "({count}) נכשלו", - "backup_controller_page_filename": "שם הקובץ: {size} [{filename}]", - "backup_controller_page_id": "מזהה: {id}", - "backup_controller_page_info": "פרטי גיבוי", - "backup_controller_page_none_selected": "אין בחירה", - "backup_controller_page_remainder": "בהמתנה לגיבוי", - "backup_controller_page_remainder_sub": "תמונות וסרטונים הנותרים לגיבוי מתוך בחירה", - "backup_controller_page_server_storage": "אחסון בשרת", - "backup_controller_page_start_backup": "התחל גיבוי", - "backup_controller_page_status_off": "גיבוי חזית אוטומטי כבוי", - "backup_controller_page_status_on": "גיבוי חזית אוטומטי מופעל", - "backup_controller_page_storage_format": "{used} מתוך {total} בשימוש", - "backup_controller_page_to_backup": "אלבומים לגבות", - "backup_controller_page_total_sub": "כל התמונות והסרטונים הייחודיים מאלבומים שנבחרו", - "backup_controller_page_turn_off": "כיבוי גיבוי חזית", - "backup_controller_page_turn_on": "הפעל גיבוי חזית", - "backup_controller_page_uploading_file_info": "מעלה מידע על הקובץ", - "backup_err_only_album": "לא ניתן להסיר את האלבום היחיד", - "backup_error_sync_failed": "הסינכרון נכשל. לא ניתן להשלים את הגיבוי.", - "backup_info_card_assets": "תמונות", - "backup_manual_cancelled": "בוטל", - "backup_manual_in_progress": "העלאה כבר בתהליך. נסה אחרי זמן מה", - "backup_manual_success": "הצלחה", - "backup_manual_title": "מצב העלאה", - "backup_options": "אפשרויות גיבוי", - "backup_options_page_title": "אפשרויות גיבוי", - "backup_setting_subtitle": "ניהול הגדרות העלאת רקע וחזית", - "backup_settings_subtitle": "נהל הגדרות העלאה", - "backup_upload_details_page_more_details": "הקש לפרטים נוספים", - "backward": "אחורה", - "biometric_auth_enabled": "אימות ביומטרי הופעל", - "biometric_locked_out": "גישה לאימות הביומטרי נחסמה", - "biometric_no_options": "אין אפשרויות זמינות עבור אימות ביומטרי", - "biometric_not_available": "אימות ביומטרי אינו זמין במכשיר זה", - "birthdate_saved": "תאריך לידה נשמר בהצלחה", - "birthdate_set_description": "תאריך לידה משמש לחישוב הגיל של האדם הזה בזמן תצלום.", - "blurred_background": "רקע מטושטש", - "bugs_and_feature_requests": "באגים & בקשות לתכונות", - "build": "גרסת בנייה", - "build_image": "גרסת תוכנה", - "bulk_delete_duplicates_confirmation": "האם באמת ברצונך למחוק בכמות גדולה {count, plural, one {תמונה # כפולה} other {# תמונות כפולות}}? זה ישמור על התמונה הכי גדולה של כל קבוצה וימחק לצמיתות את כל שאר הכפילויות. אין באפשרותך לבטל את הפעולה הזו!", - "bulk_keep_duplicates_confirmation": "האם באמת ברצונך להשאיר {count, plural, one {תמונה # כפולה} other {# תמונות כפולות}}? זה יסגור את כל הקבוצות הכפולות מבלי למחוק דבר.", - "bulk_trash_duplicates_confirmation": "האם באמת ברצונך להעביר לאשפה בכמות גדולה {count, plural, one {תמונה # כפולה} other {# תמונות כפולות}}? זה ישמור על התמונה הגדולה ביותר של כל קבוצה ויעביר לאשפה את כל שאר הכפילויות.", - "buy": "רכוש את Immich", - "cache_settings_clear_cache_button": "ניקוי מטמון", - "cache_settings_clear_cache_button_title": "מנקה את המטמון של היישום. זה ישפיע באופן משמעותי על הביצועים של היישום עד שהמטמון מתמלא מחדש.", - "cache_settings_duplicated_assets_clear_button": "נקה", - "cache_settings_duplicated_assets_subtitle": "תמונות וסרטונים שנמצאים ברשימת ההתעלמות של האפליקציה", - "cache_settings_duplicated_assets_title": "({count}) תמונות משוכפלות", - "cache_settings_statistics_album": "תמונות ממוזערות של ספרייה", - "cache_settings_statistics_full": "תמונות מלאות", - "cache_settings_statistics_shared": "תמונות ממוזערות של אלבום משותף", - "cache_settings_statistics_thumbnail": "תמונות ממוזערות", - "cache_settings_statistics_title": "שימוש במטמון", - "cache_settings_subtitle": "הגדר כיצד אפליקציית Immich שומרת נתונים באופן זמני", - "cache_settings_tile_subtitle": "שלוט בהתנהגות האחסון המקומי", - "cache_settings_tile_title": "אחסון מקומי", - "cache_settings_title": "הגדרות שמירת מטמון", - "camera": "מצלמה", - "camera_brand": "מותג המצלמה", - "camera_model": "דגם המצלמה", - "cancel": "ביטול", - "cancel_search": "ביטול חיפוש", - "canceled": "בוטל", - "canceling": "מבטל", - "cannot_merge_people": "לא ניתן למזג אנשים", - "cannot_undo_this_action": "אין באפשרותך לבטל את הפעולה הזו!", - "cannot_update_the_description": "לא ניתן לעדכן את התיאור", - "cast": "שידור למסך", - "cast_description": "הגדרת התקנים זמינים לשידור", - "change_date": "שנה תאריך", - "change_description": "שנה תיאור", - "change_display_order": "שנה סדר תצוגה", - "change_expiration_time": "שנה את זמן התפוגה", - "change_location": "שנה מיקום", - "change_name": "שנה שם", - "change_name_successfully": "השם שונה בהצלחה", - "change_password": "שינוי סיסמה", - "change_password_description": "זאת או הפעם הראשונה שהתחברת למערכת או שנעשתה בקשה לשינוי הסיסמה שלך. נא להזין את הסיסמה החדשה למטה.", - "change_password_form_confirm_password": "אשר סיסמה", - "change_password_form_description": "הי {name},\n\nזאת או הפעם הראשונה שאת/ה מתחבר/ת למערכת או שנעשתה בקשה לשינוי הסיסמה שלך. נא להזין את הסיסמה החדשה למטה.", - "change_password_form_log_out": "התנתק מכל שאר המכשירים", - "change_password_form_log_out_description": "מומלץ להתנתק מכל שאר הרכיבים", - "change_password_form_new_password": "סיסמה חדשה", - "change_password_form_password_mismatch": "סיסמאות לא תואמות", - "change_password_form_reenter_new_password": "הכנס שוב סיסמה חדשה", - "change_pin_code": "שנה קוד PIN", - "change_your_password": "החלף את הסיסמה שלך", - "changed_visibility_successfully": "הנראות שונתה בהצלחה", - "charging": "טוען", - "charging_requirement_mobile_backup": "גיבוי ברקע דורש טעינה של המכשיר", - "check_corrupt_asset_backup": "בדוק גיבויים פגומים של תמונות", - "check_corrupt_asset_backup_button": "בצע בדיקה", - "check_corrupt_asset_backup_description": "הרץ בדיקה זו רק על Wi-Fi ולאחר שכל התמונות גובו. ההליך עשוי לקחת כמה דקות.", - "check_logs": "בדוק יומני רישום", - "checksum": "Checksum", - "choose_matching_people_to_merge": "בחר אנשים תואמים למיזוג", - "city": "עיר", - "clear": "נקה", - "clear_all": "נקה הכל", - "clear_all_recent_searches": "נקה את כל החיפושים האחרונים", - "clear_file_cache": "נקה קבצי מטמון", - "clear_message": "נקה הודעה", - "clear_value": "נקה ערך", - "client_cert_dialog_msg_confirm": "בסדר", - "client_cert_enter_password": "הזן סיסמה", - "client_cert_import": "ייבוא", - "client_cert_import_success_msg": "תעודת לקוח מיובאת", - "client_cert_invalid_msg": "קובץ תעודה לא תקין או סיסמה שגויה", - "client_cert_remove_msg": "תעודת לקוח הוסרה", - "client_cert_subtitle": "תומך בפורמט PKCS12 (.p12, .pfx) בלבד. ייבוא/הסרה של תעודה זמינה רק לפני התחברות", - "client_cert_title": "תעודת לקוח SSL [ניסיוני]", - "clockwise": "עם כיוון השעון", - "close": "סגור", - "collapse": "כווץ", - "collapse_all": "כווץ הכל", - "color": "צבע", - "color_theme": "צבע ערכת נושא", - "command": "פקודה", - "comment_deleted": "תגובה נמחקה", - "comment_options": "אפשרויות תגובה", - "comments_and_likes": "תגובות & לייקים", - "comments_are_disabled": "תגובות מושבתות", - "common_create_new_album": "צור אלבום חדש", - "completed": "הושלמו", - "confirm": "אישור", - "confirm_admin_password": "אישור סיסמת מנהל", - "confirm_delete_face": "האם באמת ברצונך למחוק את הפנים של {name} מהתמונה?", - "confirm_delete_shared_link": "האם באמת ברצונך למחוק את הקישור המשותף הזה?", - "confirm_keep_this_delete_others": "כל שאר תמונות שבערימה יימחקו למעט תמונה זאת. האם באמת ברצונך להמשיך?", - "confirm_new_pin_code": "אשר קוד PIN חדש", - "confirm_password": "אשר סיסמה", - "confirm_tag_face": "האם אתה רוצה לתייג את הפנים האלה כ־{name}?", - "confirm_tag_face_unnamed": "האם ברצונך לתייג את הפנים האלה?", - "connected_device": "מכשיר מחובר", - "connected_to": "מחובר אל", - "contain": "מכיל", - "context": "הקשר", - "continue": "המשך", - "control_bottom_app_bar_create_new_album": "צור אלבום חדש", - "control_bottom_app_bar_delete_from_immich": "מחק מהשרת", - "control_bottom_app_bar_delete_from_local": "מחק מהמכשיר", - "control_bottom_app_bar_edit_location": "ערוך מיקום", - "control_bottom_app_bar_edit_time": "ערוך תאריך & זמן", - "control_bottom_app_bar_share_link": "שתף קישור", - "control_bottom_app_bar_share_to": "שתף עם", - "control_bottom_app_bar_trash_from_immich": "העבר לאשפה", - "copied_image_to_clipboard": "תמונה הועתקה ללוח.", - "copied_to_clipboard": "הועתק ללוח!", - "copy_error": "שגיאת העתקה", - "copy_file_path": "העתק את נתיב הקובץ", - "copy_image": "העתק תמונה", - "copy_link": "העתק קישור", - "copy_link_to_clipboard": "העתק קישור ללוח", - "copy_password": "העתק סיסמה", - "copy_to_clipboard": "העתק ללוח", - "country": "ארץ", - "cover": "מכסה", - "covers": "עטיפות", - "create": "צור", - "create_album": "צור אלבום", - "create_album_page_untitled": "ללא כותרת", - "create_api_key": "יצירת מפתח API", - "create_library": "צור ספרייה", - "create_link": "צור קישור", - "create_link_to_share": "צור קישור לשיתוף", - "create_link_to_share_description": "אפשר לכל אחד עם הקישור לראות את התמונות שנבחרו", - "create_new": "צור חדש", - "create_new_person": "צור אדם חדש", - "create_new_person_hint": "הקצה את התמונות שנבחרו לאדם חדש", - "create_new_user": "צור משתמש חדש", - "create_shared_album_page_share_add_assets": "הוסף תמונות", - "create_shared_album_page_share_select_photos": "בחירת תמונות", - "create_shared_link": "צור קישור משותף", - "create_tag": "צור תג", - "create_tag_description": "צור תג חדש. עבור תגים מקוננים, נא להזין את הנתיב המלא של התג כולל קווים נטויים.", - "create_user": "צור משתמש", - "created": "נוצר", - "created_at": "נוצר", - "creating_linked_albums": "יוצר אלבומים מקושרים...", - "crop": "חתוך", - "curated_object_page_title": "דברים", - "current_device": "מכשיר נוכחי", - "current_pin_code": "קוד PIN הנוכחי", - "current_server_address": "כתובת שרת נוכחית", - "custom_locale": "אזור שפה מותאם אישית", - "custom_locale_description": "עצב תאריכים ומספרים על סמך השפה והאזור", - "custom_url": "קישור מותאם אישית", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "כהה", - "dark_theme": "הפעל/כבה מצב כהה", - "date": "תאריך", - "date_after": "תאריך אחרי", - "date_and_time": "תאריך ושעה", - "date_before": "תאריך לפני", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "תאריך לידה נשמר בהצלחה", - "date_range": "טווח תאריכים", - "day": "יום", - "days": "ימים", - "deduplicate_all": "ביטול כל הכפילויות", - "deduplication_criteria_1": "גודל תמונה בבתים", - "deduplication_criteria_2": "כמות נתוני EXIF", - "deduplication_info": "מידע על ביטול כפילויות", - "deduplication_info_description": "כדי לבחור מראש תמונות באופן אוטומטי ולהסיר כפילויות בכמות גדולה, אנו מסתכלים על:", - "default_locale": "שפת ברירת מחדל", - "default_locale_description": "פורמט תאריכים ומספרים מבוסס שפת הדפדפן שלך", - "delete": "מחק", - "delete_action_confirmation_message": "האם אתה בטוח שברצונך למחוק את התמונה הזאת? פעולה זו תעביר אותו לאשפה של השרת, ותשאל אם ברצונך למחוק אותו גם מהמכשיר המקומי", - "delete_action_prompt": "{count} נמחקו", - "delete_album": "מחק אלבום", - "delete_api_key_prompt": "האם אתה בטוח שברצונך למחוק מפתח ה-API הזה?", - "delete_dialog_alert": "הפריטים האלה ימחקו לצמיתות מהשרת ומהמכשיר שלך", - "delete_dialog_alert_local": "הפריטים האלה יוסרו לצמיתות מהמכשיר שלך אבל עדיין יהיו זמינים בשרת", - "delete_dialog_alert_local_non_backed_up": "חלק מהפריטים לא מגובים לשרת ויוסרו לצמיתות מהמכשיר שלך", - "delete_dialog_alert_remote": "הפריטים האלה ימחקו לצמיתות מהשרת", - "delete_dialog_ok_force": "מחק בכל זאת", - "delete_dialog_title": "מחק לצמיתות", - "delete_duplicates_confirmation": "האם באמת ברצונך למחוק לצמיתות את הכפילויות האלה?", - "delete_face": "מחק פנים", - "delete_key": "מחק מפתח", - "delete_library": "מחק ספרייה", - "delete_link": "מחק קישור", - "delete_local_action_prompt": "{count} נמחקו באופן מקומי", - "delete_local_dialog_ok_backed_up_only": "מחק את מה שמגובה בלבד", - "delete_local_dialog_ok_force": "מחק בכל זאת", - "delete_others": "מחק אחרים", - "delete_permanently": "מחק לצמיתות", - "delete_permanently_action_prompt": "{count} נמחקו לצמיתות", - "delete_shared_link": "מחק קישור משותף", - "delete_shared_link_dialog_title": "מחק קישור משותף", - "delete_tag": "מחק תג", - "delete_tag_confirmation_prompt": "האם באמת ברצונך למחוק תג {tagName}?", - "delete_user": "מחק משתמש", - "deleted_shared_link": "קישור משותף נמחק", - "deletes_missing_assets": "מוחק תמונות שחסרות בדיסק", - "description": "תיאור", - "description_input_hint_text": "הוסף תיאור...", - "description_input_submit_error": "שגיאה בעדכון תיאור, בדוק את היומן לפרטים נוספים", - "deselect_all": "בטל הכל", - "details": "פרטים", - "direction": "כיוון", - "disabled": "מושבת", - "disallow_edits": "אל תאפשר עריכות", - "discord": "דיסקורד", - "discover": "גילוי", - "discovered_devices": "מכשירים שהתגלו", - "dismiss_all_errors": "התעלמות מכל השגיאות", - "dismiss_error": "התעלמות מהשגיאה", - "display_options": "הצגת אפשרויות", - "display_order": "סדר תצוגה", - "display_original_photos": "הצגת תמונות מקוריות", - "display_original_photos_setting_description": "הצג תמונה מקורית בעת צפייה בתמונה במקום תמונות ממוזערות כאשר התמונה המקורית תומכת בתצוגה בדפדפן. זה עלול לגרום לתמונות להיות מוצגות באיטיות.", - "do_not_show_again": "אל תציג את ההודעה הזאת שוב", - "documentation": "תיעוד", - "done": "סיום", - "download": "הורדה", - "download_action_prompt": "מוריד {count} תמונות", - "download_canceled": "הורדה בוטלה", - "download_complete": "הורדה הושלמה", - "download_enqueue": "הורדה נוספה לתור", - "download_error": "שגיאת הורדה", - "download_failed": "הורדה נכשלה", - "download_finished": "הורדה הסתיימה", - "download_include_embedded_motion_videos": "סרטונים מוטמעים", - "download_include_embedded_motion_videos_description": "כלול סרטונים מוטעמים בתמונות עם תנועה כקובץ נפרד", - "download_notfound": "הורדה לא נמצא", - "download_paused": "הורדה הופסקה", - "download_settings": "הורדה", - "download_settings_description": "ניהול הגדרות הקשורות להורדת תמונות", - "download_started": "הורדה החלה", - "download_sucess": "הצלחת הורדה", - "download_sucess_android": "המדיה הורדה אל DCIM/Immich", - "download_waiting_to_retry": "מחכה כדי לנסות שוב", - "downloading": "מוריד", - "downloading_asset_filename": "מוריד תמונה {filename}", - "downloading_media": "מוריד מדיה", - "drop_files_to_upload": "שחרר קבצים בכל מקום כדי להעלות", - "duplicates": "כפילויות", - "duplicates_description": "הפרד כל קבוצה על ידי ציון אילו, אם בכלל, הן כפילויות", - "duration": "משך זמן", - "edit": "ערוך", - "edit_album": "ערוך אלבום", - "edit_avatar": "ערוך יצגן", - "edit_birthday": "עריכת יום הולדת", - "edit_date": "ערוך תאריך", - "edit_date_and_time": "ערוך תאריך ושעה", - "edit_date_and_time_action_prompt": "{count} תאריך ושעה נערכו", - "edit_date_and_time_by_offset": "שינוי תאריך לפי קיזוז", - "edit_date_and_time_by_offset_interval": "טווח תאריכים חדש: {from} עד {to}", - "edit_description": "ערוך תיאור", - "edit_description_prompt": "אנא בחר תיאור חדש:", - "edit_exclusion_pattern": "ערוך דפוס החרגה", - "edit_faces": "ערוך פנים", - "edit_key": "ערוך מפתח", - "edit_link": "ערוך קישור", - "edit_location": "ערוך מיקום", - "edit_location_action_prompt": "{count} מיקומים נערכו", - "edit_location_dialog_title": "מיקום", - "edit_name": "ערוך שם", - "edit_people": "ערוך אנשים", - "edit_tag": "ערוך תג", - "edit_title": "ערוך כותרת", - "edit_user": "ערוך משתמש", - "editor": "עורך", - "editor_close_without_save_prompt": "השינויים לא יישמרו", - "editor_close_without_save_title": "לסגור את העורך?", - "email": "דוא\"ל", - "email_notifications": "התראות באימייל", - "empty_folder": "תיקיה זו ריקה", - "empty_trash": "רוקן אשפה", - "empty_trash_confirmation": "האם באמת ברצונך לרוקן את האשפה? זה יסיר לצמיתות את כל התמונות מהאשפה של השרת.\nאין באפשרותך לבטל פעולה זו!", - "enable": "אפשר", - "enable_backup": "הפעל גיבוי", - "enable_biometric_auth_description": "הזן את קוד ה־PIN שלך כדי להפעיל אימות ביומטרי", - "enabled": "מופעל", - "end_date": "תאריך סיום", - "enqueued": "הוצבו בתור", - "enter_wifi_name": "הזן שם אינטרנט אלחוטי", - "enter_your_pin_code": "הזן את קוד ה־PIN שלך", - "enter_your_pin_code_subtitle": "הזן את קוד ה־PIN שלך כדי לגשת לספרייה הנעולה", - "error": "שגיאה", - "error_change_sort_album": "שינוי סדר מיון אלבום נכשל", - "error_delete_face": "שגיאה במחיקת פנים מתמונה", - "error_getting_places": "שגיאה בקבלת מקומות", - "error_loading_image": "שגיאה בטעינת התמונה", - "error_loading_partners": "שגיאה בטעינת שותפים: {error}", - "error_saving_image": "שגיאה: {error}", - "error_tag_face_bounding_box": "שגיאה בתיוג הפנים – לא ניתן לקבל את קואורדינטות המסגרת", - "error_title": "שגיאה - משהו השתבש", - "errors": { - "cannot_navigate_next_asset": "לא ניתן לנווט לתמונה הבאה", - "cannot_navigate_previous_asset": "לא ניתן לנווט לתמונה הקודמת", - "cant_apply_changes": "לא ניתן להחיל שינויים", - "cant_change_activity": "לא ניתן {enabled, select, true {להשבית} other {לאפשר}} פעילות", - "cant_change_asset_favorite": "לא ניתן לשנות סימון מועדף עבור התמונה", - "cant_change_metadata_assets_count": "לא ניתן לשנות את המטא-נתונים של {count, plural, one {תמונה #} other {# תמונות}}", - "cant_get_faces": "לא ניתן לקבל פנים", - "cant_get_number_of_comments": "לא ניתן לקבל את מספר התגובות", - "cant_search_people": "לא ניתן לחפש אנשים", - "cant_search_places": "לא ניתן לחפש מקומות", - "error_adding_assets_to_album": "שגיאה בהוספת תמונות לאלבום", - "error_adding_users_to_album": "שגיאה בהוספת משתמשים לאלבום", - "error_deleting_shared_user": "שגיאה במחיקת משתמש משותף", - "error_downloading": "שגיאה בהורדת {filename}", - "error_hiding_buy_button": "שגיאה בהסתרת לחצן 'קנה'", - "error_removing_assets_from_album": "שגיאה בהסרת תמונות מהאלבום, בדוק את היומנים לפרטים נוספים", - "error_selecting_all_assets": "שגיאה בבחירת כל התמונות", - "exclusion_pattern_already_exists": "דפוס החרגה זה כבר קיים.", - "failed_to_create_album": "יצירת אלבום נכשלה", - "failed_to_create_shared_link": "יצירת קישור משותף נכשלה", - "failed_to_edit_shared_link": "עריכת קישור משותף נכשלה", - "failed_to_get_people": "קבלת אנשים נכשלה", - "failed_to_keep_this_delete_others": "הפעולה נכשלה לא ניתן היה לשמור את התמונה הזו ולמחוק את שאר התמונות", - "failed_to_load_asset": "טעינת התמונה נכשלה", - "failed_to_load_assets": "טעינת התמונות נכשלה", - "failed_to_load_notifications": "אירעה שגיאה בעת טעינת ההתראות", - "failed_to_load_people": "נכשל באחזור אנשים", - "failed_to_remove_product_key": "הסרת מפתח מוצר נכשלה", - "failed_to_reset_pin_code": "איפוס קוד PIN נכשל", - "failed_to_stack_assets": "יצירת ערימת תמונות נכשלה", - "failed_to_unstack_assets": "ביטול ערימת תמונות נכשלה", - "failed_to_update_notification_status": "שגיאה בעדכון ההתראה", - "incorrect_email_or_password": "דוא\"ל או סיסמה שגויים", - "library_folder_already_exists": "נתיב הייבוא כבר מוגדר.", - "paths_validation_failed": "{paths, plural, one {נתיב # נכשל} other {# נתיבים נכשלו}} אימות", - "profile_picture_transparent_pixels": "תמונות פרופיל אינן יכולות לכלול פיקסלים שקופים. נא להגדיל ו/או להזיז את התמונה.", - "quota_higher_than_disk_size": "הגדרת מכסה גבוהה יותר מגודל הדיסק", - "something_went_wrong": "משהו השתבש", - "unable_to_add_album_users": "לא ניתן להוסיף משתמשים לאלבום", - "unable_to_add_assets_to_shared_link": "לא ניתן להוסיף תמונות לקישור משותף", - "unable_to_add_comment": "לא ניתן להוסיף תגובה", - "unable_to_add_exclusion_pattern": "לא ניתן להוסיף דפוס החרגה", - "unable_to_add_partners": "לא ניתן להוסיף שותפים", - "unable_to_add_remove_archive": "לא ניתן {archived, select, true {להסיר תמונה מ} other {להוסיף תמונה ל}}ארכיון", - "unable_to_add_remove_favorites": "לא ניתן {favorite, select, true {להוסיף תמונה ל} other {להסיר תמונה מ}}מועדפים", - "unable_to_archive_unarchive": "לא ניתן {archived, select, true {להעביר לארכיון} other {להוציא מארכיון}}", - "unable_to_change_album_user_role": "לא ניתן לשנות את התפקיד של משתמש האלבום", - "unable_to_change_date": "לא ניתן לשנות תאריך", - "unable_to_change_description": "לא ניתן לשנות תיאור", - "unable_to_change_favorite": "לא ניתן לשנות מצב מועדף עבור התמונה", - "unable_to_change_location": "לא ניתן לשנות מיקום", - "unable_to_change_password": "לא ניתן לשנות סיסמה", - "unable_to_change_visibility": "לא ניתן לשנות את הנראות עבור {count, plural, one {אדם #} other {# אנשים}}", - "unable_to_complete_oauth_login": "לא ניתן להשלים התחברות OAuth", - "unable_to_connect": "לא ניתן להתחבר", - "unable_to_copy_to_clipboard": "לא ניתן להעתיק ללוח, יש לודא שניגשת לדף דרך https", - "unable_to_create_admin_account": "לא ניתן ליצור חשבון מנהל", - "unable_to_create_api_key": "לא ניתן ליצור מפתח API חדש", - "unable_to_create_library": "לא ניתן ליצור ספרייה", - "unable_to_create_user": "לא ניתן ליצור משתמש", - "unable_to_delete_album": "לא ניתן למחוק אלבום", - "unable_to_delete_asset": "לא ניתן למחוק את התמונה", - "unable_to_delete_assets": "שגיאה במחיקת התמונות", - "unable_to_delete_exclusion_pattern": "לא ניתן למחוק דפוס החרגה", - "unable_to_delete_shared_link": "לא ניתן למחוק קישור משותף", - "unable_to_delete_user": "לא ניתן למחוק משתמש", - "unable_to_download_files": "לא ניתן להוריד קבצים", - "unable_to_edit_exclusion_pattern": "לא ניתן לערוך דפוס החרגה", - "unable_to_empty_trash": "לא ניתן לרוקן אשפה", - "unable_to_enter_fullscreen": "לא ניתן להיכנס למסך מלא", - "unable_to_exit_fullscreen": "לא ניתן לצאת ממסך מלא", - "unable_to_get_comments_number": "לא ניתן להשיג את מספר התגובות", - "unable_to_get_shared_link": "קבלת קישור משותף נכשלה", - "unable_to_hide_person": "לא ניתן להסתיר אדם", - "unable_to_link_motion_video": "לא ניתן לקשר סרטון תנועה", - "unable_to_link_oauth_account": "לא ניתן לקשר חשבון OAuth", - "unable_to_log_out_all_devices": "לא ניתן לנתק את כל המכשירים", - "unable_to_log_out_device": "לא ניתן לנתק מכשיר", - "unable_to_login_with_oauth": "לא ניתן להתחבר באמצעות OAuth", - "unable_to_play_video": "לא ניתן לנגן סרטון", - "unable_to_reassign_assets_existing_person": "לא ניתן להקצות מחדש תמונות ל{name, select, null {אדם קיים} other {{name}}}", - "unable_to_reassign_assets_new_person": "לא ניתן להקצות מחדש תמונות לאדם חדש", - "unable_to_refresh_user": "לא ניתן לרענן את המשתמש", - "unable_to_remove_album_users": "לא ניתן להסיר משתמשים מהאלבום", - "unable_to_remove_api_key": "לא ניתן להסיר מפתח API", - "unable_to_remove_assets_from_shared_link": "לא ניתן להסיר תמונות מקישור משותף", - "unable_to_remove_library": "לא ניתן להסיר ספרייה", - "unable_to_remove_partner": "לא ניתן להסיר שותף", - "unable_to_remove_reaction": "לא ניתן להסיר תגובה", - "unable_to_reset_password": "לא ניתן לאפס סיסמה", - "unable_to_reset_pin_code": "לא ניתן לאפס קוד PIN", - "unable_to_resolve_duplicate": "לא ניתן לפתור כפילות", - "unable_to_restore_assets": "לא ניתן לשחזר תמונות", - "unable_to_restore_trash": "לא ניתן לשחזר אשפה", - "unable_to_restore_user": "לא ניתן לשחזר משתמש", - "unable_to_save_album": "לא ניתן לשמור אלבום", - "unable_to_save_api_key": "לא ניתן לשמור מפתח API", - "unable_to_save_date_of_birth": "לא ניתן לשמור תאריך לידה", - "unable_to_save_name": "לא ניתן לשמור שם", - "unable_to_save_profile": "לא ניתן לשמור פרופיל", - "unable_to_save_settings": "לא ניתן לשמור הגדרות", - "unable_to_scan_libraries": "לא ניתן לסרוק ספריות", - "unable_to_scan_library": "לא ניתן לסרוק ספרייה", - "unable_to_set_feature_photo": "לא ניתן להגדיר תמונה מייצגת", - "unable_to_set_profile_picture": "לא ניתן להגדיר תמונת פרופיל", - "unable_to_submit_job": "לא ניתן לשלוח משימה", - "unable_to_trash_asset": "לא ניתן להעביר תמונה לאשפה", - "unable_to_unlink_account": "לא ניתן לבטל קישור חשבון", - "unable_to_unlink_motion_video": "לא ניתן לבטל קישור סרטון תנועה", - "unable_to_update_album_cover": "לא ניתן לעדכן עטיפת אלבום", - "unable_to_update_album_info": "לא ניתן לעדכן פרטי אלבום", - "unable_to_update_library": "לא ניתן לעדכן ספרייה", - "unable_to_update_location": "לא ניתן לעדכן מיקום", - "unable_to_update_settings": "לא ניתן לעדכן הגדרות", - "unable_to_update_timeline_display_status": "לא ניתן לעדכן את מצב תצוגת ציר הזמן", - "unable_to_update_user": "לא ניתן לעדכן משתמש", - "unable_to_upload_file": "לא ניתן להעלות קובץ" - }, - "exclusion_pattern": "דפוס אי הכללה", - "exif": "Exif", - "exif_bottom_sheet_description": "הוסף תיאור...", - "exif_bottom_sheet_description_error": "שגיאה בעדכון התיאור", - "exif_bottom_sheet_details": "פרטים", - "exif_bottom_sheet_location": "מיקום", - "exif_bottom_sheet_no_description": "ללא תיאור", - "exif_bottom_sheet_people": "אנשים", - "exif_bottom_sheet_person_add_person": "הוסף שם", - "exit_slideshow": "צא ממצגת שקופיות", - "expand_all": "הרחב הכל", - "experimental_settings_new_asset_list_subtitle": "עבודה בתהליך", - "experimental_settings_new_asset_list_title": "אפשר רשת תמונות ניסיונית", - "experimental_settings_subtitle": "השימוש הוא על אחריותך בלבד!", - "experimental_settings_title": "נסיוני", - "expire_after": "פג לאחר", - "expired": "פג", - "expires_date": "יפוג {date}", - "explore": "חקור", - "explorer": "סייר", - "export": "ייצוא", - "export_as_json": "ייצוא כ-JSON", - "export_database": "ייצא מסד נתונים", - "export_database_description": "ייצא מסד נתונים SQL", - "extension": "סיומת", - "external": "חיצוני", - "external_libraries": "ספריות חיצוניות", - "external_network": "רשת חיצונית", - "external_network_sheet_info": "אם אינך מחובר ל־Wi‑Fi מועדף, האפליקציה תנסה להתחבר לשרת לפי הסדר מהרשימה שלמטה (מלמעלה למטה)", - "face_unassigned": "לא מוקצה", - "failed": "נכשלו", - "failed_count": "נכשלו: {count}", - "failed_to_authenticate": "ההזדהות נכשלה", - "failed_to_load_assets": "טעינת תמונות נכשלה", - "failed_to_load_folder": "טעינת תיקיה נכשלה", - "favorite": "מועדף", - "favorite_action_prompt": "{count} נוספו למועדפים", - "favorite_or_unfavorite_photo": "הוסף או הסר תמונה מהמועדפים", - "favorites": "מועדפים", - "favorites_page_no_favorites": "לא נמצאו תמונות מועדפים", - "feature_photo_updated": "תמונה מייצגת עודכנה", - "features": "תכונות", - "features_in_development": "תכונות בפיתוח", - "features_setting_description": "ניהול תכונות היישום", - "file_name_or_extension": "שם קובץ או סיומת", - "file_size": "גודל קובץ", - "filename": "שם קובץ", - "filetype": "סוג קובץ", - "filter": "סנן", - "filter_people": "סנן אנשים", - "filter_places": "סינון מקומות", - "find_them_fast": "מצא אותם מהר לפי שם עם חיפוש", - "first": "ראשון", - "fix_incorrect_match": "תקן התאמה שגויה", - "folder": "תיקיה", - "folder_not_found": "תיקיה לא נמצאה", - "folders": "תיקיות", - "folders_feature_description": "עיון בתצוגת התיקייה עבור התמונות והסרטונים שבמערכת הקבצים", - "forgot_pin_code_question": "שחכת את ה-PIN שלך?", - "forward": "קדימה", - "full_path": "נתיב מלא: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "תכונה זאת טוענת משאבים חיצוניים מגוגל בכדי לפעול.", - "general": "כללי", - "geolocation_instruction_location": "לחץ על פריט עם קואורדינטות GPS כדי להשתמש במיקומו, או בחר מיקום ישירות מהמפה", - "get_help": "קבל עזרה", - "get_wifiname_error": "לא היה ניתן לקבל את שם האינטרנט האלחוטי שלך. יש לודא שהענקת את ההרשאות הדרושות ושאת/ה מחובר/ת לרשת אינטרנט אלחוטי", - "getting_started": "תחילת העבודה", - "go_back": "חזור", - "go_to_folder": "עבור לתיקיה", - "go_to_search": "עבור לחיפוש", - "gps": "GPS", - "gps_missing": "אין GPS", - "grant_permission": "להעניק הרשאה", - "group_albums_by": "קבץ אלבומים לפי..", - "group_country": "קבץ לפי מדינה", - "group_no": "אין קיבוץ", - "group_owner": "קבץ לפי בעלים", - "group_places_by": "קבץ מקומות לפי...", - "group_year": "קבץ לפי שנה", - "haptic_feedback_switch": "אפשר משוב ברטט", - "haptic_feedback_title": "משוב ברטט", - "has_quota": "יש מכסה", - "hash_asset": "גבב פריט", - "hashed_assets": "תמונות מגובבות", - "hashing": "מגבב", - "header_settings_add_header_tip": "הוסף כותרת", - "header_settings_field_validator_msg": "ערך אינו יכול להיות ריק", - "header_settings_header_name_input": "שם כותרת", - "header_settings_header_value_input": "ערך כותרת", - "headers_settings_tile_title": "כותרות פרוקסי מותאמות", - "height": "גובה", - "hi_user": "היי {name}, ({email})", - "hide_all_people": "הסתר את כל האנשים", - "hide_gallery": "הסתר גלרייה", - "hide_named_person": "הסתר אדם {name}", - "hide_password": "הסתר סיסמה", - "hide_person": "הסתר אדם", - "hide_text_recognition": "הסתרת זיהוי טקסט", - "hide_unnamed_people": "הסתר אנשים ללא שם", - "home_page_add_to_album_conflicts": "{added} תמונות נוספו לאלבום {album}. {failed} תמונות כבר נמצאות באלבום.", - "home_page_add_to_album_err_local": "לא ניתן להוסיף תמונות מקומיות לאלבום עדיין, מדלג", - "home_page_add_to_album_success": "{added} תמונות נוספו לאלבום {album}.", - "home_page_album_err_partner": "לא ניתן להוסיף תמונת שותף לאלבום עדיין, מדלג", - "home_page_archive_err_local": "לא ניתן להעביר לארכיון תמונות מקומיות עדיין, מדלג", - "home_page_archive_err_partner": "לא ניתן להעביר לארכיון תמונות של השותף, מדלג", - "home_page_building_timeline": "בונה את ציר הזמן", - "home_page_delete_err_partner": "לא ניתן למחוק תמונות של השותף, מדלג", - "home_page_delete_remote_err_local": "תמונות מקומיות נבחרו מרחוק למחיקה, מדלג", - "home_page_favorite_err_local": "לא ניתן להוסיף למועדפים תמונות מקומיות עדיין, מדלג", - "home_page_favorite_err_partner": "לא ניתן להוסיף למועדפים תמונות של השותף עדיין, מדלג", - "home_page_first_time_notice": "אם זאת הפעם הראשונה שאת/ה משתמש/ת ביישום, נא להקפיד לבחור אלבומ(ים) לגיבוי כך שציר הזמן יוכל לאכלס תמונות וסרטונים באלבומ(ים)", - "home_page_locked_error_local": "לא ניתן להעביר תמונות מקומיות לתיקייה נעולה, מדלג", - "home_page_locked_error_partner": "לא ניתן להעביר תמונות של שותף לתיקיה נעולה, מדלג", - "home_page_share_err_local": "לא ניתן לשתף תמונות מקומיות על ידי קישור, מדלג", - "home_page_upload_err_limit": "ניתן להעלות רק מקסימום של 30 תמונות בכל פעם, מדלג", - "host": "מארח", - "hour": "שעה", - "hours": "שעות", - "id": "מזהה", - "idle": "במצב סרק", - "ignore_icloud_photos": "התעלם מתמונות iCloud", - "ignore_icloud_photos_description": "תמונות שמאוחסנות ב-iCloud לא יועלו לשרת", - "image": "תמונה", - "image_alt_text_date": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{date}", - "image_alt_text_date_1_person": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1} ב-{date}", - "image_alt_text_date_2_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1} ו-{person2} ב-{date}", - "image_alt_text_date_3_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1}, {person2}, ו-{person3} ב-{date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} עם {person1}, {person2}, ו-{additionalCount, number} אחרים ב-{date}", - "image_alt_text_date_place": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} ב-{date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1} ב-{date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1} ו-{person2} ב-{date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1}, {person2}, ו-{person3} ב-{date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {סרטון שצולם} other {תמונה שצולמה}} ב-{city}, {country} עם {person1}, {person2}, ו-{additionalCount, number} אחרים ב-{date}", - "image_saved_successfully": "תמונה נשמרה", - "image_viewer_page_state_provider_download_started": "ההורדה החלה", - "image_viewer_page_state_provider_download_success": "הצלחת הורדה", - "image_viewer_page_state_provider_share_error": "שיתוף שגיאה", - "immich_logo": "הלוגו של Immich", - "immich_web_interface": "ממשק האינטרנט של Immich", - "import_from_json": "ייבוא מ-JSON", - "import_path": "נתיב ייבוא", - "in_albums": "ב{count, plural, one {אלבום #} other {# אלבומים}}", - "in_archive": "בארכיון", - "in_year": "בעוד {year}", - "in_year_selector": "ב", - "include_archived": "כלול ארכיון", - "include_shared_albums": "כלול אלבומים משותפים", - "include_shared_partner_assets": "כלול תמונות ששותפו ע\"י השותף", - "individual_share": "שיתוף יחיד", - "individual_shares": "שיתופים בודדים", - "info": "מידע", - "interval": { - "day_at_onepm": "כל יום בשעה 13:00", - "hours": "כל {hours, plural, one {שעה} other {{hours, number} שעות}}", - "night_at_midnight": "כל יום בחצות", - "night_at_twoam": "כל יום בשעה 02:00" - }, - "invalid_date": "תאריך לא תקין", - "invalid_date_format": "פורמט תאריך לא תקין", - "invite_people": "הזמן אנשים", - "invite_to_album": "הזמן לאלבום", - "ios_debug_info_fetch_ran_at": "השליפה בוצעה ב־{dateTime}", - "ios_debug_info_last_sync_at": "סנכרון אחרון ב- {dateTime}", - "ios_debug_info_no_processes_queued": "אין תהליכי רקע הממתינים בתור", - "ios_debug_info_no_sync_yet": "טרם התבצע תהליך סנכרון ברקע", - "ios_debug_info_processes_queued": "{count, plural, one {תהליך רקע {count} בתור} other {{count} תהליכי רקע בתור}}", - "ios_debug_info_processing_ran_at": "העיבוד התבצע ב־{dateTime}", - "items_count": "{count, plural, one {פריט #} other {# פריטים}}", - "jobs": "משימות", - "keep": "שמור", - "keep_all": "שמור הכל", - "keep_this_delete_others": "שמור על זה, מחק אחרים", - "kept_this_deleted_others": "תמונה זו נשמרה ונמחקו {count, plural, one {תמונה #} other {# תמונות}}", - "keyboard_shortcuts": "קיצורי מקלדת", - "language": "שפה", - "language_no_results_subtitle": "נסה לשנות את ‪מונח החיפוש שלך", - "language_no_results_title": "לא נמצאה שפה", - "language_search_hint": "חפש שפות...", - "language_setting_description": "בחר את השפה המועדפת עליך", - "large_files": "קבצים גדולים", - "last": "אחרון", - "last_months": "{count, plural, one {החודש האחרון} other {# החודשים האחרונים}}", - "last_seen": "נראה לאחרונה", - "latest_version": "גרסה עדכנית ביותר", - "latitude": "קו רוחב", - "leave": "לעזוב", - "leave_album": "עזיבת אלבום", - "lens_model": "דגם עדשה", - "let_others_respond": "אפשר לאחרים להגיב", - "level": "רמה", - "library": "ספרייה", - "library_add_folder": "הוספת ספריה", - "library_edit_folder": "עריכת ספריה", - "library_options": "אפשרויות ספרייה", - "library_page_device_albums": "אלבומים במכשיר", - "library_page_new_album": "אלבום חדש", - "library_page_sort_asset_count": "מספר תמונות", - "library_page_sort_created": "תאריך יצירה", - "library_page_sort_last_modified": "שונה לאחרונה", - "library_page_sort_title": "כותרת אלבום", - "licenses": "רישיונות", - "light": "בהיר", - "like": "אהבתי", - "like_deleted": "לייק נמחק", - "link_motion_video": "קשר סרטון תנועה", - "link_to_oauth": "קישור ל-OAuth", - "linked_oauth_account": "חשבון OAuth מקושר", - "list": "רשימה", - "loading": "טוען", - "loading_search_results_failed": "טעינת תוצאות החיפוש נכשלה", - "local": "מקומי", - "local_asset_cast_failed": "לא ניתן לשדר תמונה שלא הועלתה לשרת", - "local_assets": "תמונות מקומיות", - "local_id": "ID מקומי", - "local_media_summary": "סיכום של מדיה מקומית", - "local_network": "רשת מקומית", - "local_network_sheet_info": "היישום יתחבר לשרת דרך הכתובת הזאת כאשר משתמשים ברשת האינטרנט האלחוטי שמצוינת", - "location": "מיקום", - "location_permission": "הרשאת מיקום", - "location_permission_content": "כדי להשתמש בתכונת ההחלפה האוטומטית, היישום צריך הרשאה למיקום מדויק על מנת לקרוא את השם של רשת האינטרנט האלחוטי", - "location_picker_choose_on_map": "בחר על מפה", - "location_picker_latitude_error": "הזן קו רוחב חוקי", - "location_picker_latitude_hint": "הזן את קו הרוחב שלך כאן", - "location_picker_longitude_error": "הזן קו אורך חוקי", - "location_picker_longitude_hint": "הזן את קו האורך שלך כאן", - "lock": "נעל", - "locked_folder": "תיקיה נעולה", - "log_detail_title": "פרטי יומן", - "log_out": "התנתק", - "log_out_all_devices": "התנתק מכל המכשירים", - "logged_in_as": "מחובר כ {user}", - "logged_out_all_devices": "מנותק מכל המכשירים", - "logged_out_device": "מכשיר מנותק", - "login": "כניסה", - "login_disabled": "כניסה למערכת הושבתה", - "login_form_api_exception": "חריגת API. נא לבדוק את כתובת השרת ולנסות שוב.", - "login_form_back_button_text": "חזרה", - "login_form_email_hint": "yourmail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "כתובת URL של השרת", - "login_form_err_http": "נא לציין //:http או //:https", - "login_form_err_invalid_email": "דוא\"ל שגוי", - "login_form_err_invalid_url": "כתובת לא חוקית", - "login_form_err_leading_whitespace": "רווח לבן מוביל", - "login_form_err_trailing_whitespace": "רווח לבן נגרר", - "login_form_failed_get_oauth_server_config": "שגיאה בהתחברות באמצעות OAuth, בדוק את כתובת השרת", - "login_form_failed_get_oauth_server_disable": "תכונת OAuth לא זמינה בשרת זה", - "login_form_failed_login": "שגיאה בכניסה למערכת, בדוק את כתובת השרת, דוא\"ל וסיסמה", - "login_form_handshake_exception": "אירעה חריגה בעת ביצוע Handshake עם השרת. אפשר תמיכה בתעודה בחתימה עצמית בהגדרות אם את/ה משתמש/ת בתעודה בחתימה עצמית.", - "login_form_password_hint": "סיסמה", - "login_form_save_login": "הישאר/י מחובר/ת", - "login_form_server_empty": "הכנס כתובת שרת.", - "login_form_server_error": "לא היה ניתן להתחבר לשרת.", - "login_has_been_disabled": "הכניסה הושבתה.", - "login_password_changed_error": "הייתה שגיאה בעדכון הסיסמה שלך", - "login_password_changed_success": "סיסמה עודכנה בהצלחה", - "logout_all_device_confirmation": "האם באמת ברצונך להתנתק מכל המכשירים?", - "logout_this_device_confirmation": "האם באמת ברצונך להתנתק מהמכשיר הזה?", - "logs": "יומנים", - "longitude": "קו אורך", - "look": "מראה", - "loop_videos": "הפעלה חוזרת של סרטונים", - "loop_videos_description": "אפשר הפעלה חוזרת אוטומטית של סרטון במציג הפרטים.", - "main_branch_warning": "הגרסה המותקנת היא גרסת פיתוח; אנחנו ממליצים בחום להשתמש בגרסה יציבה!", - "main_menu": "תפריט ראשי", - "maintenance_description": "Immich הועבר למצב תחזוקה.", - "maintenance_end": "סיום מצב תחזוקה", - "maintenance_end_error": "כשל בסיום מצב תחזוקה.", - "maintenance_logged_in_as": "מחובר כרגע בתור {user}", - "maintenance_title": "לא זמין באופן זמני", - "make": "תוצרת", - "manage_geolocation": "נהל מיקום", - "manage_media_access_rationale": "הרשאה זאת נדרשת לטיפול תקין בהעברת נכסים לאשפה ושחזורם ממנה.", - "manage_media_access_settings": "פתח הגדרות", - "manage_media_access_subtitle": "אפשר לאפליקציית Immich לנהל ולהזיז קבצי מדיה.", - "manage_media_access_title": "גישה לניהול מדיה", - "manage_shared_links": "ניהול קישורים משותפים", - "manage_sharing_with_partners": "ניהול שיתוף עם שותפים", - "manage_the_app_settings": "ניהול הגדרות האפליקציה", - "manage_your_account": "ניהול החשבון שלך", - "manage_your_api_keys": "ניהול מפתחות ה API שלך", - "manage_your_devices": "ניהול המכשירים המחוברים שלך", - "manage_your_oauth_connection": "ניהול חיבור ה-OAuth שלך", - "map": "מפה", - "map_assets_in_bounds": "{count, plural, =0 {אין תמונות באזור זה} one {תמונה #} other {# תמונות}}", - "map_cannot_get_user_location": "לא ניתן לקבוע את מיקום המשתמש", - "map_location_dialog_yes": "כן", - "map_location_picker_page_use_location": "השתמש במיקום הזה", - "map_location_service_disabled_content": "שירות המיקום צריך להיות מופעל כדי להציג תמונות מהמיקום הנוכחי שלך. האם ברצונך להפעיל אותו עכשיו?", - "map_location_service_disabled_title": "שירות מיקום מבוטל", - "map_marker_for_images": "סמן מפה לתמונות שצולמו ב{city}, {country}", - "map_marker_with_image": "סמן מפה עם תמונה", - "map_no_location_permission_content": "יש צורך בהרשאה למיקום כדי להציג תמונות מהמיקום הנוכחי שלך. האם ברצונך לאפשר זאת עכשיו?", - "map_no_location_permission_title": "הרשאה למיקום נדחתה", - "map_settings": "הגדרות מפה", - "map_settings_dark_mode": "מצב כהה", - "map_settings_date_range_option_day": "24 שעות אחרונות", - "map_settings_date_range_option_days": "ב-{days} ימים אחרונים", - "map_settings_date_range_option_year": "שנה אחרונה", - "map_settings_date_range_option_years": "ב-{years} שנים אחרונות", - "map_settings_dialog_title": "הגדרות מפה", - "map_settings_include_show_archived": "כלול ארכיון", - "map_settings_include_show_partners": "כלול שותפים", - "map_settings_only_show_favorites": "הצג מועדפים בלבד", - "map_settings_theme_settings": "ערכת נושא למפה", - "map_zoom_to_see_photos": "הקטן את התצוגה כדי לראות תמונות", - "mark_all_as_read": "סמן הכל כנקרא", - "mark_as_read": "סמן כנקרא", - "marked_all_as_read": "כל ההתראות סומנו כנקראו", - "matches": "התאמות", - "matching_assets": "תמונות תואמות", - "media_type": "סוג מדיה", - "memories": "זכרונות", - "memories_all_caught_up": "ראית הכל", - "memories_check_back_tomorrow": "חזור מחר לעוד זכרונות", - "memories_setting_description": "נהל את מה שרואים בזכרונות שלך", - "memories_start_over": "התחל מחדש", - "memories_swipe_to_close": "החלק למעלה כדי לסגור", - "memory": "זיכרון (היום לפני..)", - "memory_lane_title": "משעול הזיכרונות {title}", - "menu": "תפריט", - "merge": "מזג", - "merge_people": "מזג אנשים", - "merge_people_limit": "באפשרותך למזג רק עד 5 פנים בכל פעם", - "merge_people_prompt": "האם ברצונך למזג את האנשים האלה? פעולה זו היא בלתי הפיכה.", - "merge_people_successfully": "מיזוג אנשים בוצע בהצלחה", - "merged_people_count": "{count, plural, one {אדם # מוזג} other {# אנשים מוזגו}}", - "minimize": "מזער", - "minute": "דקה", - "minutes": "דקות", - "missing": "חסרים", - "mobile_app": "אפליקציה לטלפון", - "mobile_app_download_onboarding_note": "הורד את האפליקציה המלווה באחת מהאפשרויות הבאות", - "model": "דגם", - "month": "חודש", - "monthly_title_text_date_format": "MMMM y", - "more": "עוד", - "move": "העבר", - "move_off_locked_folder": "הוצאה מהתיקייה הנעולה", - "move_to": "העבר ל", - "move_to_lock_folder_action_prompt": "{count} נוספו לתיקייה הנעולה", - "move_to_locked_folder": "העבר לתיקיה הנעולה", - "move_to_locked_folder_confirmation": "התמונות והסרטונים האלו יוסרו מכל האלבומים, ויהיו מוצגים רק בתיקיה הנעולה", - "moved_to_archive": "{count, plural, one {הועברה תמונה # } other {# תמונות הועברו}} לארכיון", - "moved_to_library": "{count, plural, one {הועברה תמונה # } other {# תמונות הועברו}} לספריה", - "moved_to_trash": "הועבר לאשפה", - "multiselect_grid_edit_date_time_err_read_only": "לא ניתן לערוך תאריך של תמונות לקריאה בלבד, מדלג", - "multiselect_grid_edit_gps_err_read_only": "לא ניתן לערוך מיקום של תמונות לקריאה בלבד, מדלג", - "mute_memories": "השתקת זיכרונות", - "my_albums": "האלבומים שלי", - "name": "שם", - "name_or_nickname": "שם או כינוי", - "navigate": "נווט", - "navigate_to_time": "נווט אל זמן", - "network_requirement_photos_upload": "השתמש בנתונים ניידים לגיבוי תמונות", - "network_requirement_videos_upload": "השתמש בנתונים ניידים לגיבוי סרטונים", - "network_requirements": "דרישות רשת", - "network_requirements_updated": "דרישות הרשת השתנו, תור הגיבוי אופס", - "networking_settings": "רשת", - "networking_subtitle": "ניהול הגדרות כתובת URL של השרת", - "never": "אף פעם", - "new_album": "אלבום חדש", - "new_api_key": "מפתח API חדש", - "new_date_range": "טווח תאריך חדש", - "new_password": "סיסמה חדשה", - "new_person": "אדם חדש", - "new_pin_code": "קוד PIN חדש", - "new_pin_code_subtitle": "זאת הפעם הראשונה שנכנסת לתיקיה הנעולה. צור קוד PIN כדי לאבטח את הגישה לדף זה", - "new_timeline": "ציר הזמן החדש", - "new_update": "עדכון חדש", - "new_user_created": "משתמש חדש נוצר", - "new_version_available": "גרסה חדשה זמינה", - "newest_first": "החדש ביותר ראשון", - "next": "הבא", - "next_memory": "הזיכרון הבא", - "no": "לא", - "no_albums_message": "צור אלבום כדי לארגן את התמונות והסרטונים שלך", - "no_albums_with_name_yet": "זה נראה שאין לך עדיין אף אלבום עם השם הזה.", - "no_albums_yet": "זה נראה שאין לך עדיין אלבומים.", - "no_archived_assets_message": "העבר תמונות וסרטונים לארכיון כדי להסתיר אותם מתצוגת התמונות שלך", - "no_assets_message": "לחץ כדי להעלות את התמונה הראשונה שלך", - "no_assets_to_show": "אין תמונות להצגה", - "no_cast_devices_found": "לא נמצאו מכשירי שידור", - "no_checksum_local": "אין Checksum זמין - לא ניתן לאחזר תמונות מקומיות", - "no_checksum_remote": "אין Checksum זמין - לא ניתן לאחזר תמונות מהשרת", - "no_devices": "אין מכשירים מורשים", - "no_duplicates_found": "לא נמצאו כפילויות.", - "no_exif_info_available": "אין מידע זמין על מטא-נתונים (exif)", - "no_explore_results_message": "העלה תמונות נוספות כדי לחקור את האוסף שלך.", - "no_favorites_message": "הוסף מועדפים כדי למצוא במהירות את התמונות והסרטונים הכי טובים שלך", - "no_libraries_message": "צור ספרייה חיצונית כדי לראות את התמונות והסרטונים שלך", - "no_local_assets_found": "לא נמצאו תמונות עם Checksum זהה", - "no_location_set": "לא הוגדר מיקום", - "no_locked_photos_message": "תמונות וסרטונים בתיקייה הנעולה מוסתרים ולא יופיעו בזמן הגלישה או החיפוש בספרייה שלך.", - "no_name": "אין שם", - "no_notifications": "אין התראות", - "no_people_found": "לא נמצאו אנשים תואמים", - "no_places": "אין מקומות", - "no_remote_assets_found": "לא נמצאו תמונות בשרת עם Checksum זהה", - "no_results": "אין תוצאות", - "no_results_description": "נסה להשתמש במילה נרדפת או במילת מפתח יותר כללית", - "no_shared_albums_message": "צור אלבום כדי לשתף תמונות וסרטונים עם אנשים ברשת שלך", - "no_uploads_in_progress": "אין העלאות בתהליך", - "not_allowed": "לא מורשה", - "not_available": "לא רלוונטי", - "not_in_any_album": "לא בשום אלבום", - "not_selected": "לא נבחרו", - "note_apply_storage_label_to_previously_uploaded assets": "הערה: כדי להחיל את תווית האחסון על תמונות שהועלו בעבר, הפעל את", - "notes": "הערות", - "nothing_here_yet": "אין כאן כלום עדיין", - "notification_permission_dialog_content": "כדי לאפשר התראות, לך להגדרות המכשיר ובחר אפשר.", - "notification_permission_list_tile_content": "הענק הרשאה כדי לאפשר התראות.", - "notification_permission_list_tile_enable_button": "אפשר התראות", - "notification_permission_list_tile_title": "הרשאת התראה", - "notification_toggle_setting_description": "אפשר התראות דוא\"ל", - "notifications": "התראות", - "notifications_setting_description": "ניהול התראות", - "oauth": "OAuth", - "obtainium_configurator": "הגדרות Obtainium", - "obtainium_configurator_instructions": "השתמש ב-Obtainium כגדי להתקין ולעדכן את אפליקציית האנדרואיד ישירות לגרסאות הגיטהאב של Immich. צור מפתח API ובחר וריאנט כדי ליצור קישור קונפיגורציה עבור Obtainium", - "ocr": "OCR", - "official_immich_resources": "מקורות רשמיים של Immich", - "offline": "לא מקוון", - "offset": "קיזוז", - "ok": "בסדר", - "oldest_first": "הישן ביותר ראשון", - "on_this_device": "במכשיר הזה", - "onboarding": "היכרות", - "onboarding_locale_description": "יש לבחור שפה מועדפת, ניתן לשנות זאת מאוחר יותר בהגדרות.", - "onboarding_privacy_description": "התכונות (האופציונליות) הבאות מסתמכות על שירותים חיצוניים, וניתנות לביטול בכל עת בהגדרות.", - "onboarding_server_welcome_description": "בואו נאתחל את הספריה שלך עם כמה הגדרות נפוצות.", - "onboarding_theme_description": "יש לבחור את צבע ערכת הנושא עבור ההתקנה שלך. באפשרותך לשנות את זה מאוחר יותר בהגדרות שלך.", - "onboarding_user_welcome_description": "בואו נתחיל!", - "onboarding_welcome_user": "ברוך בואך, {user}", - "online": "מקוון", - "only_favorites": "רק מועדפים", - "open": "פתח", - "open_in_map_view": "פתח בתצוגת מפה", - "open_in_openstreetmap": "פתח ב-OpenStreetMap", - "open_the_search_filters": "פתח את מסנני החיפוש", - "options": "אפשרויות", - "or": "או", - "organize_into_albums": "ארגן בתוך אלבומים", - "organize_into_albums_description": "שים תמונות קיימות בתוך אלבומים באמצעות הגדרות הסנכרון הנוכחיות", - "organize_your_library": "ארגן את הספרייה שלך", - "original": "מקורי", - "other": "אחר", - "other_devices": "מכשירים אחרים", - "other_entities": "ישויות אחרות", - "other_variables": "משתנים אחרים", - "owned": "בבעלות", - "owner": "בעלים", - "page": "דף", - "partner": "שותף", - "partner_can_access": "{partner} יכול/ה לגשת", - "partner_can_access_assets": "כל התמונות והסרטונים שלך פרט לאלו שבארכיון ושנמחקו", - "partner_can_access_location": "המיקום שבו צולמו התמונות שלך", - "partner_list_user_photos": "תמונות של {user}", - "partner_list_view_all": "הצג הכל", - "partner_page_empty_message": "התמונות שלך עדיין לא משותפות עם אף שותף.", - "partner_page_no_more_users": "אין עוד משתמשים להוסיף", - "partner_page_partner_add_failed": "הוספת שותף נכשלה", - "partner_page_select_partner": "בחירת שותף", - "partner_page_shared_to_title": "משותף עם", - "partner_page_stop_sharing_content": "{partner} לא יוכל יותר לגשת לתמונות שלך.", - "partner_sharing": "שיתוף שותפים", - "partners": "שותפים", - "password": "סיסמה", - "password_does_not_match": "הסיסמה אינה תואמת", - "password_required": "נדרשת סיסמה", - "password_reset_success": "איפוס הסיסמה הצליח", - "past_durations": { - "days": "{days, plural, one {ביממה האחרונה} other {ב# הימים האחרונים}}", - "hours": "{hours, plural, one {בשעה האחרונה} other {ב# השעות האחרונות}}", - "years": "{years, plural, one {בשנה האחרונה} other {ב# השנים האחרונות}}" - }, - "path": "נתיב", - "pattern": "תבנית", - "pause": "השהיה", - "pause_memories": "השהה זיכרונות", - "paused": "מושהה", - "pending": "ממתין", - "people": "אנשים", - "people_edits_count": "{count, plural, one {אדם # נערך} other {# אנשים נערכו}}", - "people_feature_description": "עיון בתמונות וסרטונים שקובצו על ידי אנשים", - "people_sidebar_description": "הצג קישור אל אנשים בסרגל הצד", - "permanent_deletion_warning": "אזהרת מחיקה לצמיתות", - "permanent_deletion_warning_setting_description": "הצג אזהרה בעת מחיקת תמונות לצמיתות", - "permanently_delete": "מחק לצמיתות", - "permanently_delete_assets_count": "מחק לצמיתות {count, plural, one {תמונה} other {תמונות}}", - "permanently_delete_assets_prompt": "האם באמת ברצונך למחוק לצמיתות {count, plural, one {תמונה זאת?} other {# תמונות אלו?}}זה גם יסיר {count, plural, one {אותו מאלבומו} other {אותם מאלבומים}}.", - "permanently_deleted_asset": "התמונה נמחקה לצמיתות", - "permanently_deleted_assets_count": "{count, plural, one {תמונה # נמחקה} other {# תמונות נמחקו}} לצמיתות", - "permission": "הרשאות", - "permission_empty": "ההרשאות שלך לא יכולות להיות ריקות", - "permission_onboarding_back": "חזרה", - "permission_onboarding_continue_anyway": "המשך בכל זאת", - "permission_onboarding_get_started": "להתחיל", - "permission_onboarding_go_to_settings": "לך להגדרות", - "permission_onboarding_permission_denied": "הרשאה נדחתה. כדי להשתמש ביישום, הענק הרשאה לתמונות וסרטונים בהגדרות.", - "permission_onboarding_permission_granted": "ההרשאה ניתנה! הכל מוכן.", - "permission_onboarding_permission_limited": "הרשאה מוגבלת. כדי לתת ליישום לגבות ולנהל את כל אוסף הגלריה שלך, הענק הרשאה לתמונות וסרטונים בהגדרות.", - "permission_onboarding_request": "היישום דורש הרשאה כדי לראות את התמונות והסרטונים שלך.", - "person": "אדם", - "person_age_months": "{months, plural, one {חודש #} other {# חודשים}}", - "person_age_year_months": "בגיל שנה ו{months, plural, one {חודש #} other {# חודשים}}", - "person_age_years": "בגיל {years, plural, other {# שנים}}", - "person_birthdate": "נולד בתאריך {date}", - "person_hidden": "{name}{hidden, select, true { (מוסתר)} other {}}", - "photo_shared_all_users": "נראה ששיתפת את התמונות שלך עם כל המשתמשים או שאין לך אף משתמש לשתף איתו.", - "photos": "תמונות", - "photos_and_videos": "תמונות & סרטונים", - "photos_count": "{count, plural, one {תמונה {count, number}} other {{count, number} תמונות}}", - "photos_from_previous_years": "תמונות משנים קודמות", - "pick_a_location": "בחר מיקום", - "pick_custom_range": "טווח מותאם אישית", - "pick_date_range": "בחר טווח תאריכים", - "pin_code_changed_successfully": "קוד ה PIN שונה בהצלחה", - "pin_code_reset_successfully": "קוד PIN אופס בהצלחה", - "pin_code_setup_successfully": "קוד PIN הוגדר בהצלחה", - "pin_verification": "אימות קוד PIN", - "place": "מקום", - "places": "מקומות", - "places_count": "{count, plural, one {מקום {count, number}} other {{count, number} מקומות}}", - "play": "נגן", - "play_memories": "נגן זכרונות", - "play_motion_photo": "הפעל תמונה עם תנועה", - "play_or_pause_video": "הפעל או השהה סרטון", - "play_original_video": "נגן את הווידיאו המקורי", - "play_original_video_setting_description": "העדף לנגן סרטונים המקוריים על פני סרטונים משועתקים. אם הסרטון המקורי אינו תואם הוא עלול להתנגן לא נכון.", - "play_transcoded_video": "נגן סרטון משועתק", - "please_auth_to_access": "אנא אמת את זהותך כדי לגשת", - "port": "יציאה", - "preferences_settings_subtitle": "ניהול העדפות יישום", - "preferences_settings_title": "העדפות", - "preparing": "בהכנה", - "preset": "הגדרות קבועות מראש", - "preview": "תצוגה מקדימה", - "previous": "הקודם", - "previous_memory": "זיכרון קודם", - "previous_or_next_day": "התקדם יום / חזור יום", - "previous_or_next_month": "חודש קדימה / חודש אחורה", - "previous_or_next_photo": "תמונה הבאה / התמונה הקודמת", - "previous_or_next_year": "שנה קדימה / שנה אחורה", - "primary": "ראשי", - "privacy": "פרטיות", - "profile": "פרופיל", - "profile_drawer_app_logs": "יומן", - "profile_drawer_client_server_up_to_date": "היישום והשרת מעודכנים", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "מצב לקריאה בלבד מופעל. לחץ לחיצה ארוכה על סמל היצגן של המשתמש כדי לצאת.", - "profile_image_of_user": "תמונת פרופיל של {user}", - "profile_picture_set": "תמונת פרופיל נבחרה.", - "public_album": "אלבום ציבורי", - "public_share": "שיתוף ציבורי", - "purchase_account_info": "תומך", - "purchase_activated_subtitle": "תודה לך על התמיכה ב-Immich ובתוכנות קוד-פתוח", - "purchase_activated_time": "הופעל ב-{date}", - "purchase_activated_title": "המפתח שלך הופעל בהצלחה", - "purchase_button_activate": "הפעל", - "purchase_button_buy": "קנה", - "purchase_button_buy_immich": "קנה את Immich", - "purchase_button_never_show_again": "לעולם אל תראה שוב", - "purchase_button_reminder": "הזכר לי בעוד 30 יום", - "purchase_button_remove_key": "הסר מפתח", - "purchase_button_select": "בחר", - "purchase_failed_activation": "ההפעלה נכשלה! נא לבדוק את הדוא\"ל שלך עבור מפתח המוצר הנכון!", - "purchase_individual_description_1": "ליחיד", - "purchase_individual_description_2": "מעמד תומך", - "purchase_individual_title": "יחיד", - "purchase_input_suggestion": "יש לך מפתח מוצר? הכנס את המפתח למטה", - "purchase_license_subtitle": "קנה את Immich כדי לתמוך בפיתוח המתמשך של השירות", - "purchase_lifetime_description": "רכישה לכל החיים", - "purchase_option_title": "אפשרויות רכישה", - "purchase_panel_info_1": "בניית Immich לוקחת הרבה זמן ומאמץ, ויש לנו מהנדסים במשרה מלאה שעובדים על זה כדי לעשות את זה הכי טוב שאנחנו יכולים. המשימה שלנו היא שתוכנות קוד-פתוח ושיטות עסקיות אתיות יהיו מקור הכנסה בר-קיימא למפתחים וליצור אקוסיסטם המכבדת פרטיות עם חלופות אמיתיות לשירותי ענן נצלנים.", - "purchase_panel_info_2": "מכיוון שאנחנו מחויבים לא להוסיף חומות תשלום, הרכישה הזאת לא תקנה לך תכונות נוספות כלשהן ב-Immich. אנחנו סומכים על משתמשים כמוך שיתמכו בפיתוח המתמשך של Immich.", - "purchase_panel_title": "תמוך בפרויקט", - "purchase_per_server": "עבור שרת", - "purchase_per_user": "עבור משתמש", - "purchase_remove_product_key": "הסר מפתח מוצר", - "purchase_remove_product_key_prompt": "האם באמת ברצונך להסיר את מפתח המוצר?", - "purchase_remove_server_product_key": "הסר מפתח מוצר של שרת", - "purchase_remove_server_product_key_prompt": "האם באמת ברצונך להסיר את מפתח המוצר של השרת?", - "purchase_server_description_1": "עבור כל השרת", - "purchase_server_description_2": "מעמד תומך", - "purchase_server_title": "שרת", - "purchase_settings_server_activated": "מפתח המוצר של השרת מנוהל על ידי מנהל המערכת", - "query_asset_id": "שאילתה על מזהה הפריט", - "queue_status": "{count} מתוך {total} עומדים בתור", - "rating": "דירוג כוכב", - "rating_clear": "נקה דירוג", - "rating_count": "{count, plural, one {כוכב #} other {# כוכבים}}", - "rating_description": "הצג את דירוג ה-EXIF בלוח המידע", - "reaction_options": "אפשרויות הגבה", - "read_changelog": "קרא את יומן השינויים", - "readonly_mode_disabled": "מצב לקריאה בלבד מושבת", - "readonly_mode_enabled": "מצב לקריאה בלבד מופעל", - "ready_for_upload": "מוכן להעלאה", - "reassign": "הקצאה מחדש", - "reassigned_assets_to_existing_person": "{count, plural, one {תמונה # הוקצתה} other {# תמונות הוקצו}} מחדש אל {name, select, null {אדם קיים} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {תמונה # הוקצתה} other {# תמונות הוקצו}} מחדש לאדם חדש", - "reassing_hint": "הקצאת תמונות שנבחרו לאדם קיים", - "recent": "חדש", - "recent-albums": "אלבומים אחרונים", - "recent_searches": "חיפושים אחרונים", - "recently_added": "נוסף לאחרונה", - "recently_added_page_title": "נוסף לאחרונה", - "recently_taken": "צולם לאחרונה", - "recently_taken_page_title": "צולם לאחרונה", - "refresh": "רענון", - "refresh_encoded_videos": "רענון סרטונים מקודדים", - "refresh_faces": "רענון פנים", - "refresh_metadata": "רענון מטא-נתונים", - "refresh_thumbnails": "רענון תמונות ממוזערות", - "refreshed": "רוענן", - "refreshes_every_file": "קורא מחדש את כל הקבצים הקיימים והחדשים", - "refreshing_encoded_video": "מרענן סרטון מקודד", - "refreshing_faces": "מרענן פרצופים", - "refreshing_metadata": "מרענן מטא-נתונים", - "regenerating_thumbnails": "מחדש תמונות ממוזערות", - "remote": "מרוחק", - "remote_assets": "תמונות מרוחקות", - "remote_media_summary": "תקציר תמונות מרוחקות", - "remove": "הסרה", - "remove_assets_album_confirmation": "האם באמת ברצונך להסיר {count, plural, one {תמונה #} other {# תמונות}} מהאלבום?", - "remove_assets_shared_link_confirmation": "האם ברצונך להסיר {count, plural, one {תמונה #} other {# תמונות}} מהקישור המשותף הזה?", - "remove_assets_title": "להסיר תמונות?", - "remove_custom_date_range": "הסרת טווח תאריכים מותאם", - "remove_deleted_assets": "הסרת קבצים לא מקוונים", - "remove_from_album": "הסרה מאלבום", - "remove_from_album_action_prompt": "{count} הוסרו מהאלבום", - "remove_from_favorites": "הסרה מהמועדפים", - "remove_from_lock_folder_action_prompt": "{count} הוסרו מהתיקייה הנעולה", - "remove_from_locked_folder": "הסר מהתיקייה הנעולה", - "remove_from_locked_folder_confirmation": "האם אתה בטוח שברצונך להעביר את התמונות והסרטונים האלה מחוץ לתיקייה הנעולה? הם יהיו מוצגים בספרייה שלך.", - "remove_from_shared_link": "הסר מקישור משותף", - "remove_memory": "הסר זיכרון", - "remove_photo_from_memory": "הסר תמונה מזיכרון זה", - "remove_tag": "הסרת תג", - "remove_url": "הסר URL", - "remove_user": "הסר משתמש", - "removed_api_key": "מפתח API הוסר: {name}", - "removed_from_archive": "הוסר מארכיון", - "removed_from_favorites": "הוסר ממועדפים", - "removed_from_favorites_count": "{count, plural, other {הוסרו #}} מהמועדפים", - "removed_memory": "זיכרון הוסר", - "removed_photo_from_memory": "התמונה הוסרה מהזיכרון", - "removed_tagged_assets": "תג הוסר מ{count, plural, one {תמונה #} other {# תמונות}}", - "rename": "שנה שם", - "repair": "תיקון", - "repair_no_results_message": "קבצים חסרי מעקב וחסרים יופיעו כאן", - "replace_with_upload": "החלף בהעלאה", - "repository": "מאגר", - "require_password": "דרוש סיסמה", - "require_user_to_change_password_on_first_login": "דרוש מהמשתמש לשנות סיסמה בכניסה הראשונה", - "rescan": "סרוק מחדש", - "reset": "איפוס", - "reset_password": "איפוס סיסמה", - "reset_people_visibility": "אפס את נראות האנשים", - "reset_pin_code": "אפס קוד PIN", - "reset_pin_code_description": "אם שחכת את קוד ה-PIN שלך, באפשרותך ליצור קשר עם מנהל השרת כדי לאפס אותו", - "reset_pin_code_success": "קוד ה-PIN אופס בהצלחה", - "reset_pin_code_with_password": "באפשרותך תמיד לאפס את קוד ה-PIN שלך עם הסיסמה שלך", - "reset_sqlite": "אפס את מסד הנתונים SQLite", - "reset_sqlite_confirmation": "האם אתה בטוח שברצונך לאפס את מסד הנתונים SQLite? יהיה עליך להתנתק ולהתחבר מחדש כדי לסנכרן את הנתונים מחדש", - "reset_sqlite_success": "איפוס מסד הנתונים SQLite בוצע בהצלחה", - "reset_to_default": "אפס לברירת מחדל", - "resolution": "רזולוציה", - "resolve_duplicates": "פתור כפילויות", - "resolved_all_duplicates": "כל הכפילויות נפתרו", - "restore": "שחזר", - "restore_all": "שחזר הכל", - "restore_trash_action_prompt": "{count} שוחזרו מהשאפה", - "restore_user": "שחזר משתמש", - "restored_asset": "התמונה שוחזרה", - "resume": "המשך", - "resume_paused_jobs": "המשך {count, plural, one {עבודה # שהופסקה} other {# עבודות שהופסקו}}", - "retry_upload": "נסה שוב להעלות", - "review_duplicates": "בדוק כפילויות", - "review_large_files": "צפייה בקבצים גדולים", - "role": "תפקיד", - "role_editor": "עורך", - "role_viewer": "צופה", - "running": "פועל", - "save": "שמור", - "save_to_gallery": "שמור לגלריה", - "saved": "נשמר", - "saved_api_key": "מפתח API שמור", - "saved_profile": "פרופיל שמור", - "saved_settings": "הגדרות שמורות", - "say_something": "להגיד משהו", - "scaffold_body_error_occurred": "אירעה שגיאה", - "scan_all_libraries": "סרוק את כל הספריות", - "scan_library": "סרוק", - "scan_settings": "הגדרות סריקה", - "scanning_for_album": "סורק אחר אלבום...", - "search": "חיפוש", - "search_albums": "חיפוש אלבומים", - "search_by_context": "חיפוש לפי הקשר", - "search_by_description": "חיפוש לפי תיאור", - "search_by_description_example": "יום טיול בסאפה", - "search_by_filename": "חיפוש לפי שם קובץ או סיומת", - "search_by_filename_example": "לדוגמא IMG_1234.JPG או PNG", - "search_by_ocr": "חיפוש לפי OCR", - "search_by_ocr_example": "לאטה", - "search_camera_lens_model": "חיפוש סוג עדשה...", - "search_camera_make": "חיפוש תוצרת המצלמה...", - "search_camera_model": "חפש דגם המצלמה...", - "search_city": "חיפוש עיר...", - "search_country": "חיפוש ארץ...", - "search_filter_apply": "החל סינון", - "search_filter_camera_title": "בחר סוג מצלמה", - "search_filter_date": "תאריך", - "search_filter_date_interval": "{start} עד {end}", - "search_filter_date_title": "בחר טווח תאריכים", - "search_filter_display_option_not_in_album": "לא באלבום", - "search_filter_display_options": "אפשרויות תצוגה", - "search_filter_filename": "חיפוש לפי שם קובץ", - "search_filter_location": "מיקום", - "search_filter_location_title": "בחר מיקום", - "search_filter_media_type": "סוג מדיה", - "search_filter_media_type_title": "בחר סוג מדיה", - "search_filter_ocr": "חיפוש לפי OCR", - "search_filter_people_title": "בחר אנשים", - "search_for": "חיפוש", - "search_for_existing_person": "חיפוש אדם קיים", - "search_no_more_result": "אין עוד תוצאות", - "search_no_people": "אין אנשים", - "search_no_people_named": "אין אנשים בשם \"{name}\"", - "search_no_result": "לא נמצאו תוצאות, יש לנסות ביטוי או צירוף חיפוש שונה", - "search_options": "אפשרויות חיפוש", - "search_page_categories": "קטגוריות", - "search_page_motion_photos": "תמונות עם תנועה", - "search_page_no_objects": "אין מידע זמין על אובייקטים", - "search_page_no_places": "אין מידע זמין על מקומות", - "search_page_screenshots": "צילומי מסך", - "search_page_search_photos_videos": "חפש את התמונות והסרטונים שלך", - "search_page_selfies": "צילומי סלפי", - "search_page_things": "דברים", - "search_page_view_all_button": "הצג הכל", - "search_page_your_activity": "הפעילות שלך", - "search_page_your_map": "המפה שלך", - "search_people": "חיפוש אנשים", - "search_places": "חיפוש מקומות", - "search_rating": "חיפוש לפי דירוג...", - "search_result_page_new_search_hint": "חיפוש חדש", - "search_settings": "הגדרות חיפוש", - "search_state": "חיפוש מדינה...", - "search_suggestion_list_smart_search_hint_1": "חיפוש חכם מופעל כברירת מחדל, כדי לחפש מטא-נתונים השתמש בתחביר ", - "search_suggestion_list_smart_search_hint_2": "תנאי-החיפוש-שלך:m", - "search_tags": "חיפוש תגים...", - "search_timezone": "חיפוש אזור זמן...", - "search_type": "סוג חיפוש", - "search_your_photos": "חיפוש בתמונות שלך", - "searching_locales": "מחפש אזורי שפה...", - "second": "שנייה", - "see_all_people": "ראה את כל האנשים", - "select": "בחר", - "select_album_cover": "בחר עטיפת אלבום", - "select_all": "בחר הכל", - "select_all_duplicates": "בחר את כל הכפילויות", - "select_all_in": "בחר הכול בתוך {group}", - "select_avatar_color": "בחר צבע יצגן", - "select_face": "בחר פנים", - "select_featured_photo": "בחר תמונה מייצגת", - "select_from_computer": "בחר מהמחשב", - "select_keep_all": "בחר שמור הכל", - "select_library_owner": "בחר את הבעלים של הספרייה", - "select_new_face": "בחר פנים חדשות", - "select_person_to_tag": "בחר אדם לתיוג", - "select_photos": "בחר תמונות", - "select_trash_all": "בחר העבר הכל לאשפה", - "select_user_for_sharing_page_err_album": "יצירת אלבום נכשלה", - "selected": "נבחרו", - "selected_count": "{count, plural, other {# נבחרו}}", - "selected_gps_coordinates": "קואורדינטות GPS שנבחרו", - "send_message": "שלח הודעה", - "send_welcome_email": "שלח דוא\"ל קבלת פנים", - "server_endpoint": "כתובת URL של השרת", - "server_info_box_app_version": "גרסת יישום", - "server_info_box_server_url": "כתובת שרת", - "server_offline": "השרת מנותק", - "server_online": "החיבור לשרת פעיל", - "server_privacy": "פרטיות השרת", - "server_restarting_description": "הדף יתרענן בעוד רגע.", - "server_restarting_title": "השרת מופעל מחדש", - "server_stats": "סטטיסטיקות שרת", - "server_update_available": "עדכון שרת זמין", - "server_version": "גרסת שרת", - "set": "הגדר", - "set_as_album_cover": "הגדר כעטיפת האלבום", - "set_as_featured_photo": "הגדר כתמונה מוצגת", - "set_as_profile_picture": "הגדר כתמונת פרופיל", - "set_date_of_birth": "הגדר תאריך לידה", - "set_profile_picture": "הגדר תמונת פרופיל", - "set_slideshow_to_fullscreen": "הגדר מצגת שקופיות למסך מלא", - "set_stack_primary_asset": "הגדרה כתמונה ראשית", - "setting_image_viewer_help": "מציג הפרטים טוען את התמונה הממוזערת הקטנה קודם, לאחר מכן טוען את התצוגה המקדימה בגודל בינוני (אם מופעל), לבסוף טוען את המקורית (אם מופעל).", - "setting_image_viewer_original_subtitle": "אפשר לטעון את התמונה המקורית ברזלוציה מלאה (גדולה!). השבת כדי להקטין שימוש בנתונים (גם בשרת וגם בזיכרון המטמון שבמכשיר).", - "setting_image_viewer_original_title": "טען תמונה מקורית", - "setting_image_viewer_preview_subtitle": "אפשר לטעון תמונה ברזלוציה בינונית. השבת כדי או לטעון את המקורית או רק להשתמש בתמונה הממוזערת.", - "setting_image_viewer_preview_title": "טען תמונת תצוגה מקדימה", - "setting_image_viewer_title": "תמונות", - "setting_languages_apply": "החל", - "setting_languages_subtitle": "שינוי שפת היישום", - "setting_notifications_notify_failures_grace_period": "הודע על כשלים בגיבוי ברקע: {duration}", - "setting_notifications_notify_hours": "{count} שעות", - "setting_notifications_notify_immediately": "באופן מיידי", - "setting_notifications_notify_minutes": "{count} דקות", - "setting_notifications_notify_never": "אף פעם", - "setting_notifications_notify_seconds": "{count} שניות", - "setting_notifications_single_progress_subtitle": "מידע מפורט על התקדמות העלאה לכל תמונה", - "setting_notifications_single_progress_title": "הראה פרטי התקדמות גיבוי ברקע", - "setting_notifications_subtitle": "התאם את העדפות ההתראה שלך", - "setting_notifications_total_progress_subtitle": "התקדמות העלאה כללית (בוצע/סה״כ תמונות)", - "setting_notifications_total_progress_title": "הראה סה״כ התקדמות גיבוי ברקע", - "setting_video_viewer_auto_play_subtitle": "נגן סרטונים אוטומטית כשהם נפתחים", - "setting_video_viewer_auto_play_title": "נגן סרטונים אוטומטית", - "setting_video_viewer_looping_title": "הפעלה חוזרת", - "setting_video_viewer_original_video_subtitle": "כאשר מזרימים סרטון מהשרת, נגן את המקורי אפילו כשהמרת קידוד זמינה. עלול להוביל לתקיעות. סרטונים זמינים מקומית מנוגנים באיכות מקורית ללא קשר להגדרה זו.", - "setting_video_viewer_original_video_title": "כפה סרטון מקורי", - "settings": "הגדרות", - "settings_require_restart": "אנא הפעל מחדש את היישום כדי להחיל הגדרה זו", - "settings_saved": "ההגדרות נשמרו", - "setup_pin_code": "הגדר קוד PIN", - "share": "שתף", - "share_action_prompt": "שותפו {count} תמונות", - "share_add_photos": "הוסף תמונות", - "share_assets_selected": "{count} נבחרו", - "share_dialog_preparing": "מכין...", - "share_link": "שתף קישור", - "shared": "משותף", - "shared_album_activities_input_disable": "התגובה מושבתת", - "shared_album_activity_remove_content": "האם ברצונך למחוק את הפעילות הזאת?", - "shared_album_activity_remove_title": "מחיקת פעילות", - "shared_album_section_people_action_error": "שגיאה בעזיבה/הסרה מאלבום", - "shared_album_section_people_action_leave": "הסר משתמש מאלבום", - "shared_album_section_people_action_remove_user": "הסר משתמש מאלבום", - "shared_album_section_people_title": "אנשים", - "shared_by": "משותף על ידי", - "shared_by_user": "משותף על ידי {user}", - "shared_by_you": "משותף על ידך", - "shared_from_partner": "תמונות מאת {partner}", - "shared_intent_upload_button_progress_text": "{total} / {current} הועלו", - "shared_link_app_bar_title": "קישורים משותפים", - "shared_link_clipboard_copied_massage": "הועתק ללוח", - "shared_link_clipboard_text": "קישור: {password}\nסיסמה: {link}", - "shared_link_create_error": "שגיאה ביצירת קישור משותף", - "shared_link_custom_url_description": "גש לקישור ששותף באמצעות כתובת URL מותאמת אישית", - "shared_link_edit_description_hint": "הכנס את תיאור השיתוף", - "shared_link_edit_expire_after_option_day": "1 יום", - "shared_link_edit_expire_after_option_days": "{count} ימים", - "shared_link_edit_expire_after_option_hour": "1 שעה", - "shared_link_edit_expire_after_option_hours": "{count} שעות", - "shared_link_edit_expire_after_option_minute": "1 דקה", - "shared_link_edit_expire_after_option_minutes": "{count} דקות", - "shared_link_edit_expire_after_option_months": "{count} חודשים", - "shared_link_edit_expire_after_option_year": "{count} שנה", - "shared_link_edit_password_hint": "הכנס את סיסמת השיתוף", - "shared_link_edit_submit_button": "עדכן קישור", - "shared_link_error_server_url_fetch": "לא ניתן להשיג את כתובת האינטרנט של השרת", - "shared_link_expires_day": "יפוג בעוד יום {count}", - "shared_link_expires_days": "יפוג בעוד {count} ימים", - "shared_link_expires_hour": "יפוג בעוד שעה {count}", - "shared_link_expires_hours": "יפוג בעוד {count} שעות", - "shared_link_expires_minute": "יפוג בעוד דקה {count}", - "shared_link_expires_minutes": "יפוג בעוד {count} דקות", - "shared_link_expires_never": "יפוג ∞", - "shared_link_expires_second": "יפוג בעוד שנייה {count}", - "shared_link_expires_seconds": "יפוג בעוד {count} שניות", - "shared_link_individual_shared": "משותף ליחיד", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "ניהול קישורים משותפים", - "shared_link_options": "אפשרויות קישור משותף", - "shared_link_password_description": "דרוש סיסמה כדי לגשת לקישור המשותף הזה", - "shared_links": "קישורים משותפים", - "shared_links_description": "שתף תמונות וסרטונים עם קישור", - "shared_photos_and_videos_count": "{assetCount, plural, other {# תמונות וסרטונים משותפים.}}", - "shared_with_me": "משותף איתי", - "shared_with_partner": "משותף עם {partner}", - "sharing": "שיתוף", - "sharing_enter_password": "נא להזין את הסיסמה כדי לצפות בדף זה.", - "sharing_page_album": "אלבומים משותפים", - "sharing_page_description": "צור אלבומים משותפים כדי לשתף תמונות וסרטונים עם אנשים ברשת שלך.", - "sharing_page_empty_list": "רשימה ריקה", - "sharing_sidebar_description": "הצג קישור אל שיתוף בסרגל הצד", - "sharing_silver_appbar_create_shared_album": "אלבום משותף חדש", - "sharing_silver_appbar_share_partner": "שיתוף עם שותף", - "shift_to_permanent_delete": "לחץ ⇧ כדי למחוק תמונה לצמיתות", - "show_album_options": "הצג אפשרויות אלבום", - "show_albums": "הצג אלבומים", - "show_all_people": "הצג את כל האנשים", - "show_and_hide_people": "הצג & הסתר אנשים", - "show_file_location": "הצג את מיקום הקובץ", - "show_gallery": "הצג גלריה", - "show_hidden_people": "הצג אנשים מוסתרים", - "show_in_timeline": "הצג בציר הזמן", - "show_in_timeline_setting_description": "הצג תמונות וסרטונים ממשתמש זה בציר הזמן שלך", - "show_keyboard_shortcuts": "הצג קיצורי מקלדת", - "show_metadata": "הצג מטא-נתונים", - "show_or_hide_info": "הצג או הסתר מידע", - "show_password": "הראה סיסמה", - "show_person_options": "הצג אפשרויות אדם", - "show_progress_bar": "הצג סרגל התקדמות", - "show_search_options": "הצג אפשרויות חיפוש", - "show_shared_links": "הצג קישורים משותפים", - "show_slideshow_transition": "הצג מעבר מצגת", - "show_supporter_badge": "תג תומך", - "show_supporter_badge_description": "הצג תג תומך", - "show_text_recognition": "הצג זיהוי טקסט", - "show_text_search_menu": "הצג תפריט חיפוש טקסט", - "shuffle": "ערבוב", - "sidebar": "סרגל צד", - "sidebar_display_description": "הצג קישור לתצוגה בסרגל הצד", - "sign_out": "יציאה מהמערכת", - "sign_up": "הרשמה", - "size": "גודל", - "skip_to_content": "דלג לתוכן", - "skip_to_folders": "דלג לתיקיות", - "skip_to_tags": "דלג לתגים", - "slideshow": "מצגת שקופיות", - "slideshow_settings": "הגדרות מצגת שקופיות", - "sort_albums_by": "מיין אלבומים לפי...", - "sort_created": "תאריך יצירה", - "sort_items": "מספר פריטים", - "sort_modified": "תאריך שינוי", - "sort_newest": "תמונה הכי חדשה", - "sort_oldest": "תמונה הכי ישנה", - "sort_people_by_similarity": "מיין אנשים לפי דמיון", - "sort_recent": "תמונה אחרונה ביותר", - "sort_title": "כותרת", - "source": "קוד מקור", - "stack": "ערימה", - "stack_action_prompt": "{count} קובצו", - "stack_duplicates": "צור ערימת כפילויות", - "stack_select_one_photo": "בחר תמונה ראשית אחת עבור הערימה", - "stack_selected_photos": "צור ערימת תמונות נבחרות", - "stacked_assets_count": "{count, plural, one {תמונה # נערמה} other {# תמונות נערמו}}", - "stacktrace": "Stack trace", - "start": "התחל", - "start_date": "תאריך התחלה", - "start_date_before_end_date": "תאריך ההתחלה חייב להיות לפני תאריך הסיום", - "state": "מדינה", - "status": "מצב", - "stop_casting": "הפסקת שידור", - "stop_motion_photo": "עצור תמונה עם תנועה", - "stop_photo_sharing": "להפסיק לשתף את התמונות שלך?", - "stop_photo_sharing_description": "{partner} לא יוכל לגשת לתמונות שלך יותר.", - "stop_sharing_photos_with_user": "הפסק לשתף את התמונות שלך עם משתמש זה", - "storage": "שטח אחסון", - "storage_label": "תווית אחסון", - "storage_quota": "מכסת האחסון", - "storage_usage": "{used} בשימוש מתוך {available}", - "submit": "שלח", - "success": "בוצע בהצלחה", - "suggestions": "הצעות", - "sunrise_on_the_beach": "שקיעה בחוף", - "support": "תמיכה", - "support_and_feedback": "תמיכה & משוב", - "support_third_party_description": "התקנת ה-Immich שלך נארזה על ידי צד שלישי. בעיות שאתה חווה עשויות להיגרם על ידי חבילה זו, אז בבקשה תעלה בעיות איתם ראשית כל באמצעות הקישורים למטה.", - "swap_merge_direction": "החלף כיוון מיזוג", - "sync": "סנכרן", - "sync_albums": "סנכרן אלבומים", - "sync_albums_manual_subtitle": "סנכרן את כל הסרטונים והתמונות שהועלו לאלבומי הגיבוי שנבחרו", - "sync_local": "סנכרן מקומי", - "sync_remote": "סנכרן מהשרת", - "sync_status": "סטטוס סנכרון", - "sync_status_subtitle": "הצג ונהל את מערכת הסנכרון", - "sync_upload_album_setting_subtitle": "צור והעלה תמונות וסרטונים שלך לאלבומים שנבחרו ביישום", - "tag": "תג", - "tag_assets": "תיוג תמונות", - "tag_created": "נוצר תג: {tag}", - "tag_feature_description": "עיון בתמונות וסרטונים שקובצו על ידי נושאי תג לוגיים", - "tag_not_found_question": "לא מצליח למצוא תג? צור תג חדש", - "tag_people": "תייג אנשים", - "tag_updated": "תג מעודכן: {tag}", - "tagged_assets": "תויגו {count, plural, one {תמונה #} other {# תמונות}}", - "tags": "תגים", - "tap_to_run_job": "לחץ על מנת להפעיל משימה", - "template": "תבנית", - "text_recognition": "זיהוי טקסט", - "theme": "ערכת נושא", - "theme_selection": "בחירת ערכת נושא", - "theme_selection_description": "הגדר אוטומטית את ערכת הנושא לבהיר או כהה בהתבסס על העדפת המערכת של הדפדפן שלך", - "theme_setting_asset_list_storage_indicator_title": "הצג סטטוס גיבוי על גבי התמונות", - "theme_setting_asset_list_tiles_per_row_title": "מספר תמונות בכל שורה ({count})", - "theme_setting_colorful_interface_subtitle": "החל את הצבע העיקרי למשטחי רקע.", - "theme_setting_colorful_interface_title": "ממשק צבעוני", - "theme_setting_image_viewer_quality_subtitle": "התאם את האיכות של מציג פרטי התמונות", - "theme_setting_image_viewer_quality_title": "איכות מציג תמונות", - "theme_setting_primary_color_subtitle": "בחר צבע לפעולות עיקריות והדגשות.", - "theme_setting_primary_color_title": "צבע עיקרי", - "theme_setting_system_primary_color_title": "השתמש בצבע המערכת", - "theme_setting_system_theme_switch": "אוטומטי (עקוב אחרי הגדרת מערכת)", - "theme_setting_theme_subtitle": "בחר את הגדרת ערכת הנושא של היישום", - "theme_setting_three_stage_loading_subtitle": "טעינה בשלושה שלבים עשויה לשפר את ביצועי הטעינה אבל גורמת באופן משמעותי לעומס רשת גבוה יותר", - "theme_setting_three_stage_loading_title": "אפשר טעינה בשלושה שלבים", - "they_will_be_merged_together": "הם יתמזגו יחד", - "third_party_resources": "משאבי צד שלישי", - "time": "זמן", - "time_based_memories": "זכרונות מבוססי זמן", - "time_based_memories_duration": "מספר השניות להצגת כל תמונה.", - "timeline": "ציר זמן", - "timezone": "אזור זמן", - "to_archive": "העבר לארכיון", - "to_change_password": "שנה סיסמה", - "to_favorite": "מועדף", - "to_login": "כניסה", - "to_multi_select": "לבחור מרובות", - "to_parent": "לך להורה", - "to_select": "לבחור", - "to_trash": "אשפה", - "toggle_settings": "החלף מצב הגדרות", - "toggle_theme_description": "הפעלה/כיבוי של ערכת נושא", - "total": "סה\"כ", - "total_usage": "שימוש כולל", - "trash": "אשפה", - "trash_action_prompt": "{count} הועברו לאשפה", - "trash_all": "העבר הכל לאשפה", - "trash_count": "העבר לאשפה {count, number}", - "trash_delete_asset": "העבר לאשפה/מחק תמונה", - "trash_emptied": "האשפה רוקנה", - "trash_no_results_message": "תמונות וסרטונים שהועברו לאשפה יופיעו כאן.", - "trash_page_delete_all": "מחק הכל", - "trash_page_empty_trash_dialog_content": "האם ברצונך לרוקן את התמונות שבאשפה? הפריטים האלה ימחקו לצמיתות מהשרת", - "trash_page_info": "פריטים באשפה ימחקו לצמיתות לאחר {days} ימים", - "trash_page_no_assets": "אין תמונות באשפה", - "trash_page_restore_all": "שחזר הכל", - "trash_page_select_assets_btn": "בחר תמונות", - "trash_page_title": "אשפה ({count})", - "trashed_items_will_be_permanently_deleted_after": "פריטים באשפה ימחקו לצמיתות לאחר {days, plural, one {יום #} other {# ימים}}.", - "troubleshoot": "פתור בעיות", - "type": "סוג", - "unable_to_change_pin_code": "לא ניתן לשנות את קוד ה PIN", - "unable_to_check_version": "לא ניתן לבדוק את גרסאת האפליקציה או השרת", - "unable_to_setup_pin_code": "לא ניתן להגדיר קוד PIN", - "unarchive": "הוצא מארכיון", - "unarchive_action_prompt": "{count} הוסרו מהארכיון", - "unarchived_count": "{count, plural, other {# הוצאו מהארכיון}}", - "undo": "לבטל", - "unfavorite": "לא מועדף", - "unfavorite_action_prompt": "{count} הוסרו מהמועדפים", - "unhide_person": "בטל הסתרת אדם", - "unknown": "לא ידוע", - "unknown_country": "מדינה לא ידועה", - "unknown_year": "שנה לא ידועה", - "unlimited": "בלתי מוגבל", - "unlink_motion_video": "בטל קישור סרטון תנועה", - "unlink_oauth": "בטל קישור OAuth", - "unlinked_oauth_account": "בוטל קישור חשבון OAuth", - "unmute_memories": "בטל השתקת זיכרונות", - "unnamed_album": "אלבום ללא שם", - "unnamed_album_delete_confirmation": "האם באמת ברצונך למחוק את האלבום הזה?", - "unnamed_share": "שיתוף ללא שם", - "unsaved_change": "שינוי לא נשמר", - "unselect_all": "בטל בחירה בהכל", - "unselect_all_duplicates": "בטל בחירת כל הכפילויות", - "unselect_all_in": "בטל את הבחירה של הכל ב {group}", - "unstack": "בטל ערימה", - "unstack_action_prompt": "{count} הופרדו", - "unstacked_assets_count": "{count, plural, one {תמונה # הוסרה} other {# תמונות הוסרו}} מהערימה", - "untagged": "לא מתיוגים", - "up_next": "הבא בתור", - "update_location_action_prompt": "עדכן את המיקום של {count} פריטים שנבחרו עם:", - "updated_at": "עודכן", - "updated_password": "סיסמה עודכנה", - "upload": "העלאה", - "upload_concurrency": "מספר העלאות במקביל", - "upload_details": "פרטי העלאה", - "upload_dialog_info": "האם ברצונך לגבות את התמונות שנבחרו לשרת?", - "upload_dialog_title": "העלאת תמונה", - "upload_errors": "העלאה הושלמה עם {count, plural, one {שגיאה #} other {# שגיאות}}, רענן את הדף כדי לראות תמונות שהועלו.", - "upload_finished": "העלאה הסתיימה", - "upload_progress": "נותרו {remaining, number} - טופלו {processed, number}/{total, number}", - "upload_skipped_duplicates": "דילג על {count, plural, one {תמונה כפולה #} other {# תמונות כפולות}}", - "upload_status_duplicates": "כפילויות", - "upload_status_errors": "שגיאות", - "upload_status_uploaded": "הועלה", - "upload_success": "ההעלאה בוצעה בהצלחה. רענן את הדף כדי לצפות בתמונות שהועלו.", - "upload_to_immich": "העלה לשרת ({count})", - "uploading": "מעלה", - "uploading_media": "מעלה מדיה", - "url": "URL", - "usage": "שימוש", - "use_biometric": "השתמש באימות ביומטרי", - "use_current_connection": "השתמש בחיבור נוכחי", - "use_custom_date_range": "השתמש בטווח תאריכים מותאם במקום", - "user": "משתמש", - "user_has_been_deleted": "משתמש זה נמחק.", - "user_id": "מזהה משתמש", - "user_liked": "{user} אהב את {type, select, photo {התמונה הזאת} video {הסרטון הזה} asset {התמונה הזאת} other {זה}}", - "user_pin_code_settings": "קוד PIN", - "user_pin_code_settings_description": "נהל את קוד ה PIN שלך", - "user_privacy": "פרטיות המשתמש", - "user_purchase_settings": "רכישה", - "user_purchase_settings_description": "ניהול הרכישה שלך", - "user_role_set": "הגדר את {user} בתור {role}", - "user_usage_detail": "פרטי השימוש של המשתמש", - "user_usage_stats": "סטטיסטיקות שימוש בחשבון", - "user_usage_stats_description": "הצג סטטיסטיקות שימוש בחשבון", - "username": "שם משתמש", - "users": "משתמשים", - "users_added_to_album_count": "נוספו {count, plural, one {משתמש #} other {# משתמשים}} לאלבום", - "utilities": "כלים", - "validate": "לאמת", - "validate_endpoint_error": "נא להזין כתובת URL תקנית", - "variables": "משתנים", - "version": "גרסה", - "version_announcement_closing": "החבר שלך, אלכס", - "version_announcement_message": "שלום לך! זמינה גרסה חדשה של Immich. נא לקחת זמן מה לקרוא את הערות הפרסום כדי לוודא שההתקנה שלך עדכנית על מנת למנוע תצורות שגויות, במיוחד אם נעשה שימוש ב-WatchTower או בכל מנגנון שמטפל בעדכון מופע ה-Immich שלך באופן אוטומטי.", - "version_history": "היסטוריית גרסאות", - "version_history_item": "{version} הותקנה ב-{date}", - "video": "סרטון", - "video_hover_setting": "הפעל תצוגת סרטון מקדימה בעת ריחוף", - "video_hover_setting_description": "הפעל תצוגת סרטון מקדימה כאשר העכבר מרחף מעל פריט. אפילו כשהגדרה זו מושבתת, ניתן להתחיל את הניגון על ידי ריחוף מעל סמל ההפעלה.", - "videos": "סרטונים", - "videos_count": "{count, plural, one {סרטון #} other {# סרטונים}}", - "view": "הצג", - "view_album": "הצג אלבום", - "view_all": "הצג הכל", - "view_all_users": "הצג את כל המשתמשים", - "view_asset_owners": "הצג את בעלי התמונות", - "view_details": "הצג פרטים", - "view_in_timeline": "ראה בציר הזמן", - "view_link": "הצג קישור", - "view_links": "הצג קישורים", - "view_name": "הצג", - "view_next_asset": "הצג את התמונה הבאה", - "view_previous_asset": "הצג את התמונה הקודמת", - "view_qr_code": "הצג ברקוד", - "view_similar_photos": "הצג תמונות דומות", - "view_stack": "הצג ערימה", - "view_user": "הצג משתמש", - "viewer_remove_from_stack": "הסר מערימה", - "viewer_stack_use_as_main_asset": "השתמש כתמונה ראשית", - "viewer_unstack": "ביטול ערימה", - "visibility_changed": "הנראות השתנתה עבור {count, plural, one {אדם #} other {# אנשים}}", - "waiting": "ממתין", - "waiting_count": "ממתין: {count}", - "warning": "אזהרה", - "week": "שבוע", - "welcome": "ברוכים הבאים", - "welcome_to_immich": "ברוכים הבאים אל immich", - "width": "רוחב", - "wifi_name": "שם הרשת האלחוטית", - "wrong_pin_code": "קוד PIN שגוי", - "year": "שנה", - "years_ago": "לפני {years, plural, one {שנה #} other {# שנים}}", - "yes": "כן", - "you_dont_have_any_shared_links": "אין לך קישורים משותפים", - "your_wifi_name": "שם אינטרנט אלחוטי שלך", - "zoom_image": "זום לתמונה", - "zoom_to_bounds": "התמקד באזור" -} +{} diff --git a/i18n/hi.json b/i18n/hi.json index 959a3aaf73..0967ef424b 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -1,2218 +1 @@ -{ - "about": "बारे में", - "account": "खाता", - "account_settings": "खाता सेटिंग्स", - "acknowledge": "स्वीकार करें", - "action": "कार्रवाई", - "action_common_update": "अद्यतन", - "action_description": "फ़िल्टर किए गए एसेट्स पर किए जाने वाले एक्शन का सेट", - "actions": "कार्यवाहियां", - "active": "सक्रिय", - "active_count": "सक्रिय: {count}", - "activity": "गतिविधि", - "activity_changed": "गतिविधि {enabled, select, true {enabled} other {disabled}}", - "add": "डालें", - "add_a_description": "एक विवरण डालें", - "add_a_location": "एक स्थान डालें", - "add_a_name": "नाम डालें", - "add_a_title": "एक शीर्षक डालें", - "add_action": "कार्रवाई डालें", - "add_action_description": "कोई एक्शन जोड़ने के लिए क्लिक करें", - "add_assets": "एसेट्स जोड़ें", - "add_birthday": "अपने जन्मदिन का उल्लेख करें", - "add_endpoint": "endpoint डालें", - "add_exclusion_pattern": "अपवाद उदाहरण डालें", - "add_filter": "फ़िल्टर डालें", - "add_filter_description": "फ़िल्टर कंडीशन जोड़ने के लिए क्लिक करें", - "add_location": "स्थान डालें", - "add_more_users": "अधिक उपयोगकर्ता डालें", - "add_partner": "जोड़ीदार डालें", - "add_path": "पथ डालें", - "add_photos": "फ़ोटो डालें", - "add_tag": "चिह्नित करें", - "add_to": "इसमें डालें…", - "add_to_album": "एल्बम में डालें", - "add_to_album_bottom_sheet_added": "{album} में डालें", - "add_to_album_bottom_sheet_already_exists": "{album} में पहले से है", - "add_to_album_bottom_sheet_some_local_assets": "कुछ स्थानीय एसेट एल्बम में नहीं जोड़े जा सके", - "add_to_album_toggle": "{album} के लिए चयन टॉगल करें", - "add_to_albums": "एकाधिक एल्बम में डाले", - "add_to_albums_count": "एल्बमों में डालें ({count})", - "add_to_bottom_bar": "इसमें जोड़ें", - "add_to_shared_album": "शेयर किए गए एल्बम में डालें", - "add_upload_to_stack": "स्टैक में अपलोड करें", - "add_url": "URL डालें", - "add_workflow_step": "वर्कफ़्लो स्टेप जोड़ें", - "added_to_archive": "संग्रहीत कर दिया गया है", - "added_to_favorites": "पसंदीदा में डाला गया", - "added_to_favorites_count": "पसंदीदा में {count, number} डाला गया", - "admin": { - "add_exclusion_pattern_description": "बहिष्करण पैटर्न जोड़ें. *, **, और ? का उपयोग करके ग्लोबिंग करना समर्थित है। \"Raw\" नामक किसी भी निर्देशिका की सभी फ़ाइलों को अनदेखा करने के लिए, \"**/Raw/**\" का उपयोग करें। \".tif\" से समाप्त होने वाली सभी फ़ाइलों को अनदेखा करने के लिए, \"**/*.tif\" का उपयोग करें। किसी पूर्ण पथ को अनदेखा करने के लिए, \"/path/to/ignore/**\" का उपयोग करें।", - "admin_user": "व्यवस्थापक उपयोगकर्ता", - "asset_offline_description": "यह बाहरी लाइब्रेरी एसेट अब डिस्क पर मौजूद नहीं है और इसे ट्रैश में डाल दिया गया है। यदि फ़ाइल को लाइब्रेरी के भीतर कहीं ले जाया गया था, तो नई संबंधित एसेट के लिए अपनी टाइमलाइन देखें। इस एसेट को वापस पाने के लिए, कृपया सुनिश्चित करें कि नीचे दिए गए फ़ाइल पथ को इम्मिच द्वारा एक्सेस किया जा सकता है और फिर लाइब्रेरी को स्कैन करें।", - "authentication_settings": "प्रमाणीकरण सेटिंग्स", - "authentication_settings_description": "पासवर्ड, OAuth और अन्य प्रमाणीकरण सेटिंग्स प्रबंधित करें", - "authentication_settings_disable_all": "क्या आप वाकई सभी लॉगिन विधियों को अक्षम करना चाहते हैं? लॉगिन पूरी तरह से अक्षम कर दिया जाएगा।", - "authentication_settings_reenable": "पुनः सक्षम करने के लिए, Server Command का प्रयोग करे।", - "background_task_job": "पृष्ठभूमि कार्य", - "backup_database": "डेटाबेस डंप बनाएं", - "backup_database_enable_description": "Enable database dumps", - "backup_keep_last_amount": "रखने के लिए पिछले डंप की मात्रा", - "backup_onboarding_1_description": "क्लाउड में या किसी अन्य भौतिक स्थान पर ऑफसाइट प्रतिलिपि।", - "backup_onboarding_2_description": "विभिन्न उपकरणों पर स्थानीय प्रतियाँ। इसमें मुख्य फ़ाइलें और उन फ़ाइलों का स्थानीय बैकअप शामिल है।", - "backup_onboarding_3_description": "आपके डेटा की कुल प्रतियाँ, जिनमें मूल फ़ाइलें भी शामिल हैं। इसमें 1 ऑफ़साइट प्रति और 2 स्थानीय प्रतियाँ शामिल हैं।", - "backup_onboarding_description": "अपने डेटा की सुरक्षा के लिए 3-2-1 बैकअप रणनीति की सिफारिश की जाती है। एक पूर्ण बैकअप समाधान के लिए, आपको अपलोड की गई फ़ोटो/वीडियो की प्रतियाँ और Immich डेटाबेस दोनों को सुरक्षित रखना चाहिए।", - "backup_onboarding_footer": "Immich का बैकअप लेने से संबंधित अधिक जानकारी के लिए कृपया प्रलेखन देखें।", - "backup_onboarding_parts_title": "3-2-1 बैकअप में शामिल हैं:", - "backup_onboarding_title": "बैकअप्स", - "backup_settings": "डेटाबेस डंप सेटिंग्स", - "backup_settings_description": "डेटाबेस डंप सेटिंग्स प्रबंधित करें।", - "cleared_jobs": "{job}: के लिए कार्य साफ़ कर दिए गए", - "config_set_by_file": "Config वर्तमान में एक config फ़ाइल द्वारा सेट किया गया है", - "confirm_delete_library": "क्या आप वाकई {library} लाइब्रेरी को हटाना चाहते हैं?", - "confirm_delete_library_assets": "क्या आप वाकई इस लाइब्रेरी को हटाना चाहते हैं? यह इम्मीच से {count, plural, one {# contained asset} other {all # contained assets}} हटा दिया जाएगा और इसे पूर्ववत नहीं किया जा सकेगा। फ़ाइलें डिस्क पर रहेंगी।", - "confirm_email_below": "पुष्टि करने के लिए नीचे \"{email}\" टाइप करें", - "confirm_reprocess_all_faces": "क्या आप वाकई सभी चेहरों को दोबारा संसाधित करना चाहते हैं? इससे नामित लोग भी साफ हो जायेंगे।", - "confirm_user_password_reset": "क्या आप वाकई {user} का पासवर्ड रीसेट करना चाहते हैं?", - "confirm_user_pin_code_reset": "क्या आप वाकई {user} का पिन कोड रीसेट करना चाहते हैं?", - "copy_config_to_clipboard_description": "मौजूदा सिस्टम कॉन्फ़िगरेशन को JSON ऑब्जेक्ट के रूप में क्लिपबोर्ड पर कॉपी करें", - "create_job": "जॉब बनाएँ", - "cron_expression": "क्रॉन अभिव्यक्ति", - "cron_expression_description": "क्रॉन प्रारूप का उपयोग करके स्कैनिंग अंतराल सेट करें। अधिक जानकारी के लिए कृपया क्रोनटैब गुरु देखें", - "cron_expression_presets": "क्रॉन अभिव्यक्ति प्रीसेट", - "disable_login": "लॉगिन अक्षम करें", - "duplicate_detection_job_description": "समान छवियों का पता लगाने के लिए संपत्तियों पर मशीन लर्निंग चलाएं। यह कार्यक्षमता स्मार्ट खोज पर निर्भर करती है", - "exclusion_pattern_description": "Exclusion पैटर्न आपको अपनी लाइब्रेरी को स्कैन करते समय फ़ाइलों और फ़ोल्डरों को अनदेखा करने देता है। यह उपयोगी है यदि आपके पास ऐसे फ़ोल्डर हैं जिनमें ऐसी फ़ाइलें हैं जिन्हें आप आयात नहीं करना चाहते हैं, जैसे RAW फ़ाइलें।", - "export_config_as_json_description": "वर्तमान सिस्टम कॉन्फ़िगरेशन को JSON फ़ाइल के रूप में डाउनलोड करें", - "external_libraries_page_description": "एडमिन बाहरी लाइब्रेरी पेज", - "face_detection": "मुख संशोधन", - "face_detection_description": "मशीन लर्निंग का उपयोग करके संपत्तियों में चेहरों का पता लगाएं। वीडियो के लिए, केवल थंबनेल पर विचार किया जाता है। \"सभी\" परिसंपत्तियों को (पुनः) संसाधित करता है। \"लापता\" उन परिसंपत्तियों को कतारबद्ध करता है जिन्हें अभी तक संसाधित नहीं किया गया है। फेस डिटेक्शन पूरा होने के बाद पहचाने गए चेहरों को चेहरे की पहचान के लिए कतारबद्ध किया जाएगा, उन्हें मौजूदा या नए लोगों में समूहित किया जाएगा।", - "facial_recognition_job_description": "समूह ने लोगों में चेहरों का पता लगाया। यह चरण फेस डिटेक्शन पूरा होने के बाद चलता है। \"सभी\" चेहरों को (पुनः) समूहित करता है। \"लापता\" कतार में वे चेहरे हैं जिनके लिए कोई व्यक्ति नियुक्त नहीं है।", - "failed_job_command": "कार्य {job} के लिए आदेश {command} विफल", - "force_delete_user_warning": "चेतावनी: इससे उपयोगकर्ता और सारा डेटा तुरंत हट जाएगा। इसे पूर्ववत नहीं किया जा सकता और फ़ाइलें पुनर्प्राप्त नहीं की जा सकतीं।", - "image_format": "प्रारूप", - "image_format_description": "वेबपी, जेपीईजी की तुलना में छोटी फ़ाइलें बनाता है, लेकिन एनकोड करने में धीमा है।", - "image_fullsize_description": "पूर्ण आकार की छवि, जिसमें मेटाडेटा हटाया गया है, ज़ूम करने पर उपयोग की जाती है", - "image_fullsize_enabled": "पूर्ण आकार की छवि निर्माण सक्षम करें", - "image_fullsize_enabled_description": "वेब-अनुकूल न होने वाले फ़ॉर्मैट्स के लिए पूर्ण आकार की छवि जनरेट करें। यदि 'एम्बेडेड प्रीव्यू प्राथमिकता' चालू है, तो रूपांतरण के बिना वही प्रीव्यू दिखाया जाएगा। यह JPEG जैसे फॉर्मैट्स पर लागू नहीं होता।", - "image_fullsize_quality_description": "पूर्ण आकार की छवि की गुणवत्ता (1 से 100 तक)। अधिक मान बेहतर गुणवत्ता देता है, लेकिन फ़ाइल का आकार भी बड़ा होता है।", - "image_fullsize_title": "पूर्ण आकार छवि सेटिंग्स", - "image_prefer_embedded_preview": "एम्बेडेड पूर्वावलोकन को प्राथमिकता दें", - "image_prefer_embedded_preview_setting_description": "RAW फ़ोटो में एम्बेडेड प्रीव्यू का उपयोग इमेज प्रोसेसिंग के लिए इनपुट के रूप में और उपलब्ध होने पर करें। यह कुछ छवियों के लिए अधिक सटीक रंग प्रदान कर सकता है, लेकिन प्रीव्यू की गुणवत्ता कैमरे पर निर्भर करती है और छवि में अधिक कंप्रेशन आर्टिफैक्ट हो सकते हैं।", - "image_prefer_wide_gamut": "विस्तृत सरगम को प्राथमिकता दें", - "image_prefer_wide_gamut_setting_description": "थंबनेल के लिए डिस्प्ले P3 का उपयोग करें। यह विस्तृत कलरस्पेस वाली छवियों की जीवंतता को बेहतर ढंग से संरक्षित करता है, लेकिन पुराने ब्राउज़र संस्करण वाले पुराने डिवाइस पर छवियां अलग-अलग दिखाई दे सकती हैं। रंग परिवर्तन से बचने के लिए sRGB छवियों को sRGB के रूप में रखा जाता है।", - "image_preview_description": "मेटाडेटा रहित मध्यम आकार की छवि, जिसका उपयोग एकल संपत्ति देखने और मशीन लर्निंग के लिए होता है", - "image_preview_quality_description": "पूर्वावलोकन की गुणवत्ता (1 से 100 तक)। अधिक मान बेहतर गुणवत्ता देता है, लेकिन इससे फ़ाइल का आकार बढ़ता है और ऐप की प्रतिक्रिया क्षमता कम हो सकती है। बहुत कम मान मशीन लर्निंग की गुणवत्ता को प्रभावित कर सकता है।", - "image_preview_title": "पूर्वदर्शन सेटिंग्स", - "image_quality": "गुणवत्ता", - "image_resolution": "रिज़ॉल्यूशन", - "image_resolution_description": "उच्चतर रिज़ॉल्यूशन अधिक विवरण सुरक्षित रख सकता है, लेकिन एन्कोड करने में अधिक समय लेता है, फ़ाइल आकार बड़ा होता है और ऐप की प्रतिक्रियाशीलता कम हो सकती है।", - "image_settings": "छवि सेटिंग्स", - "image_settings_description": "उत्पन्न छवियों की गुणवत्ता और रिज़ॉल्यूशन प्रबंधित करें", - "image_thumbnail_description": "मेटाडेटा हटाई गई छोटी थंबनेल, जिसका उपयोग फोटो समूहों को देखने के लिए जैसे मुख्य टाइमलाइन में किया जाता है", - "image_thumbnail_quality_description": "थंबनेल की गुणवत्ता 1-100 तक। उच्चतर बेहतर है, लेकिन बड़ी फ़ाइलें बनाता है और ऐप की प्रतिक्रियाशीलता को कम कर सकता है।", - "image_thumbnail_title": "थंबनेल सेटिंग्स", - "import_config_from_json_description": "JSON कॉन्फ़िगरेशन फ़ाइल अपलोड करके सिस्टम कॉन्फ़िगरेशन इंपोर्ट करें", - "job_concurrency": "{job} समरूपता", - "job_created": "नौकरी बनाई गई", - "job_not_concurrency_safe": "यह कार्य (जॉब) समवर्ती-सुरक्षित नहीं है।", - "job_settings": "कार्य (जॉब) सेटिंग्स", - "job_settings_description": "कार्य (जॉब) समवर्तीता प्रबंधित करें", - "jobs_delayed": "{jobCount, plural, other {# विलंबित}}", - "jobs_failed": "{jobCount, plural, other {# असफल}}", - "jobs_over_time": "समय के साथ नौकरियां", - "library_created": "निर्मित संग्रह: {library}", - "library_deleted": "संग्रह हटा दिया गया", - "library_details": "संग्रह विवरण", - "library_folder_description": "इंपोर्ट करने के लिए एक फ़ोल्डर बताएं। इस फ़ोल्डर को, सबफ़ोल्डर्स के साथ, इमेज और वीडियो के लिए स्कैन किया जाएगा।", - "library_remove_exclusion_pattern_prompt": "क्या आप वाकई इस एक्सक्लूजन पैटर्न को हटाना चाहते हैं?", - "library_remove_folder_prompt": "क्या आप वाकई इस इंपोर्ट फ़ोल्डर को हटाना चाहते हैं?", - "library_scanning": "सामयिक स्कैनिंग", - "library_scanning_description": "सामयिक लाइब्रेरी स्कैनिंग कॉन्फ़िगर करें", - "library_scanning_enable_description": "सामयिक लाइब्रेरी स्कैनिंग सक्षम करें", - "library_settings": "बाहरी संग्रह", - "library_settings_description": "बाहरी संग्रह सेटिंग प्रबंधित करें", - "library_tasks_description": "नई और/या परिवर्तित संपत्तियों के लिए बाहरी लाइब्रेरीज़ को स्कैन करें", - "library_updated": "अद्यतित पुस्तकालय", - "library_watching_enable_description": "एक्सटर्नल लाइब्रेरीज में बदलावों के लिए निगरानी रखें", - "library_watching_settings": "पुस्तकालय निगरानी [प्रायोगिक]", - "library_watching_settings_description": "परिवर्तित फ़ाइलों पर स्वचालित रूप से नज़र रखें", - "logging_enable_description": "लॉगिंग करने देना", - "logging_level_description": "सक्षम होने पर, किस लॉग स्तर का उपयोग करना है।", - "logging_settings": "लॉगिंग", - "machine_learning_availability_checks": "उपलब्धता जांच", - "machine_learning_availability_checks_description": "उपलब्ध मशीन लर्निंग सर्वर का स्वचालित रूप से पता लगाएं और प्राथमिकता दें", - "machine_learning_availability_checks_enabled": "उपलब्धता जांच सक्षम करें", - "machine_learning_availability_checks_interval": "अंतराल की जाँच करें", - "machine_learning_availability_checks_interval_description": "उपलब्धता जांच के बीच मिलीसेकेंड में अंतराल", - "machine_learning_availability_checks_timeout": "अनुरोध समयबाह्य हुआ", - "machine_learning_availability_checks_timeout_description": "उपलब्धता जांच के लिए मिलीसेकंड में समयबाह्य अंतराल", - "machine_learning_clip_model": "क्लिप मॉडल", - "machine_learning_clip_model_description": "CLIP मॉडल का नाम यहां सूचीबद्ध है। ध्यान दें कि मॉडल बदलने पर आपको सभी छवियों के लिए 'स्मार्ट सर्च' जोब फिर से चलाना होगा।", - "machine_learning_duplicate_detection": "डुप्लिकेट का पता लगाना", - "machine_learning_duplicate_detection_enabled": "डुप्लिकेट पहचान सक्षम करें", - "machine_learning_duplicate_detection_enabled_description": "यदि अक्षम किया गया है, तो बिल्कुल समान चित्र अभी भी डी-डुप्लिकेट किया जाएगा।", - "machine_learning_duplicate_detection_setting_description": "संभावित डुप्लिकेट खोजने के लिए CLIP एम्बेडिंग का उपयोग करें", - "machine_learning_enabled": "मशीन लर्निंग सक्षम करें", - "machine_learning_enabled_description": "यदि अक्षम किया गया है, तो नीचे दी गई सेटिंग्स पर ध्यान दिए बिना सभी एमएल सुविधाएं अक्षम कर दी जाएंगी।", - "machine_learning_facial_recognition": "चेहरे की पहचान", - "machine_learning_facial_recognition_description": "छवियों में चेहरे का पता लगाना, पहचानना और समूह बनाना", - "machine_learning_facial_recognition_model": "चेहरे की पहचान मॉडल", - "machine_learning_facial_recognition_model_description": "मॉडल आकार के अवरोही क्रम में सूचीबद्ध हैं। बड़े मॉडल धीमी हैं और अधिक स्मृति का उपयोग करते हैं, लेकिन बेहतर परिणाम देते हैं। ध्यान दें कि आपको एक मॉडल बदलने पर सभी छवियों के लिए फेस डिटेक्शन जॉब को फिर से शुरू करना होगा।।", - "machine_learning_facial_recognition_setting": "चेहरे की पहचान सक्षम करें", - "machine_learning_facial_recognition_setting_description": "यदि अक्षम किया गया है, तो छवियों को चेहरे की पहचान के लिए एन्कोड नहीं किया जाएगा और एक्सप्लोर पेज में लोग अनुभाग को पॉप्युलेट नहीं किया जाएगा।", - "machine_learning_max_detection_distance": "अधिकतम पता लगाने की दूरी", - "machine_learning_max_detection_distance_description": "दो छवियों को डुप्लिकेट मानने के लिए उनके बीच की अधिकतम दूरी 0.001-0.1 के बीच है।", - "machine_learning_max_recognition_distance": "अधिकतम पहचान दूरी", - "machine_learning_max_recognition_distance_description": "एक ही व्यक्ति माने जाने वाले दो चेहरों के बीच अधिकतम दूरी 0-2 के बीच है।", - "machine_learning_min_detection_score": "न्यूनतम पहचान स्कोर", - "machine_learning_min_detection_score_description": "किसी चेहरे का पता लगाने के लिए न्यूनतम आत्मविश्वास स्कोर 0-1 होना चाहिए।", - "machine_learning_min_recognized_faces": "निम्नतम पहचाने चेहरे", - "machine_learning_min_recognized_faces_description": "किसी व्यक्ति के लिए पहचाने जाने वाले चेहरों की न्यूनतम संख्या।", - "machine_learning_ocr": "ओ.सी.आर", - "machine_learning_ocr_description": "चित्रों में पाठ को पहचानने के लिए मशीन लर्निंग का उपयोग करें", - "machine_learning_ocr_enabled": "ओ.सी.आर. सक्षम करें", - "machine_learning_ocr_enabled_description": "यदि अक्षम किया गया है, तो चित्रों पर पाठ-पहचान नहीं होगा।", - "machine_learning_ocr_max_resolution": "अधिकतम रिज़ॉल्यूशन", - "machine_learning_ocr_max_resolution_description": "इस रिज़ॉल्यूशन से ऊपर के प्रदर्शन का आकार मूल अनुपात को संरक्षित करते हुए बदल दिया जाएगा। उच्च मान अधिक सटीक होते हैं, लेकिन संसाधित होने में अधिक मेमोरी और समय लगाते हैं।", - "machine_learning_ocr_min_detection_score": "न्यूनतम खोज अंक", - "machine_learning_ocr_min_detection_score_description": "पाठ का पता लगाने के लिए 0-1 के बीच न्यूनतम आत्मविश्वास अंक। कम अंक अधिक पाठ का पता लगाएंगे लेकिन परिणाम गलत हो सकते हैं।", - "machine_learning_ocr_min_recognition_score": "न्यूनतम पहचान अंक", - "machine_learning_ocr_min_score_recognition_description": "पाठ को पहचानने के लिए 0-1 के बीच न्यूनतम आत्मविश्वास अंक। कम अंक अधिक पाठ को पहचानेंगे लेकिन परिणाम गलत हो सकते हैं।", - "machine_learning_ocr_model": "ओसीआर प्रतिमान", - "machine_learning_ocr_model_description": "सर्वर प्रतिमान मोबाइल प्रतिमान की तुलना में अधिक सटीक होते हैं, लेकिन संसाधित होने में अधिक मेमोरी और समय लेते हैं।", - "machine_learning_settings": "मशीन लर्निंग सेटिंग्स", - "machine_learning_settings_description": "मशीन लर्निंग सुविधाओं और सेटिंग्स को प्रबंधित करें", - "machine_learning_smart_search": "स्मार्ट खोज", - "machine_learning_smart_search_description": "CLIP एम्बेडिंग का उपयोग करके शब्दार्थ रूप से छवियां खोजें", - "machine_learning_smart_search_enabled": "स्मार्ट खोज सक्षम करें", - "machine_learning_smart_search_enabled_description": "यदि अक्षम किया गया है, तो स्मार्ट खोज के लिए छवियों को एन्कोड नहीं किया जाएगा।", - "machine_learning_url_description": "मशीन लर्निंग सर्वर का URL। यदि एक से अधिक URL दिए गए हैं, तो प्रत्येक सर्वर को एक-एक करके कोशिश किया जाएगा, पहले से आखिरी तक, जब तक कोई सफलतापूर्वक प्रतिक्रिया न दे। जो सर्वर प्रतिक्रिया नहीं देते, उन्हें अस्थायी रूप से नजरअंदाज किया जाएगा जब तक वे फिर से ऑनलाइन न हों।", - "maintenance_settings": "रखरखाव", - "maintenance_settings_description": "Immich को मेंटेनेंस मोड में रखें।", - "maintenance_start": "रखरखाव मोड शुरू करें", - "maintenance_start_error": "मेंटेनेंस मोड शुरू नहीं हो सका।", - "manage_concurrency": "समवर्तीता प्रबंधित करें", - "manage_log_settings": "लॉग सेटिंग प्रबंधित करें", - "map_dark_style": "डार्क शैली", - "map_enable_description": "मानचित्र सुविधाएँ सक्षम करें", - "map_gps_settings": "मानचित्र एवं जीपीएस सेटिंग्स", - "map_gps_settings_description": "मानचित्र और जीपीएस (रिवर्स जियोकोडिंग) सेटिंग्स प्रबंधित करें", - "map_implications": "मानचित्र सुविधा एक बाहरी टाइल सेवा (tiles.immich.cloud) पर निर्भर करती है", - "map_light_style": "हल्की शैली", - "map_manage_reverse_geocoding_settings": "प्रबंधित करना रिवर्स जियोकोडिंग समायोजन", - "map_reverse_geocoding": "रिवर्स जियोकोडिंग", - "map_reverse_geocoding_enable_description": "रिवर्स जियोकोडिंग सक्षम करें", - "map_reverse_geocoding_settings": "जियोकोडिंग सेटिंग्स को उल्टा करें", - "map_settings": "मानचित्र सेटिंग", - "map_settings_description": "मानचित्र सेटिंग प्रबंधित करें", - "map_style_description": "style.json मैप थीम का URL", - "memory_cleanup_job": "स्मृति सफ़ाई", - "memory_generate_job": "स्मृति पीढ़ी", - "metadata_extraction_job": "मेटाडेटा निकालें", - "metadata_extraction_job_description": "प्रत्येक परिसंपत्ति से जीपीएस और रिज़ॉल्यूशन जैसी मेटाडेटा जानकारी निकालें", - "metadata_faces_import_setting": "चेहरा आयात सक्षम करें", - "metadata_faces_import_setting_description": "छवि EXIF डेटा और साइडकार फ़ाइलों से चेहरे आयात करें", - "metadata_settings": "मेटाडेटा सेटिंग्स", - "metadata_settings_description": "मेटाडेटा सेटिंग प्रबंधित करें", - "migration_job": "प्रवास", - "migration_job_description": "संपत्तियों और चेहरों के थंबनेल को नवीनतम फ़ोल्डर संरचना में माइग्रेट करें", - "nightly_tasks_cluster_faces_setting_description": "नए पहचाने गए चेहरों पर चेहरे की पहचान चलाएँ", - "nightly_tasks_cluster_new_faces_setting": "नए चेहरों को समूह में शामिल करें", - "nightly_tasks_database_cleanup_setting": "डेटाबेस क्लीनअप कार्य", - "nightly_tasks_database_cleanup_setting_description": "डेटाबेस से पुराना, समाप्त हो चुका डेटा साफ़ करें", - "nightly_tasks_generate_memories_setting": "यादें उत्पन्न करें", - "nightly_tasks_generate_memories_setting_description": "संपत्तियों से नई यादें बनाएँ", - "nightly_tasks_missing_thumbnails_setting": "गायब थंबनेल उत्पन्न करें", - "nightly_tasks_missing_thumbnails_setting_description": "थंबनेल निर्माण के लिए थंबनेल के बिना कतारबद्ध परिसंपत्तियाँ", - "nightly_tasks_settings": "रात्रिकालीन कार्य सेटिंग्स", - "nightly_tasks_settings_description": "रात्रिकालीन कार्यों का प्रबंधन करें", - "nightly_tasks_start_time_setting": "समय शुरू", - "nightly_tasks_start_time_setting_description": "वह समय जब सर्वर रात्रिकालीन कार्य चलाना शुरू करता है", - "nightly_tasks_sync_quota_usage_setting": "सिंक कोटा उपयोग", - "nightly_tasks_sync_quota_usage_setting_description": "वर्तमान उपयोग के आधार पर उपयोगकर्ता संग्रहण कोटा अपडेट करें", - "no_paths_added": "कोई पथ नहीं डाला गया", - "no_pattern_added": "कोई पैटर्न नहीं डाला गया", - "note_apply_storage_label_previous_assets": "नोट: पहले अपलोड की गई संपत्तियों पर स्टोरेज लेबल लागू करने के लिए, चलाएँ", - "note_cannot_be_changed_later": "नोट: इसे बाद में बदला नहीं जा सकता!", - "notification_email_from_address": "इस पते से", - "notification_email_from_address_description": "प्रेषक का ईमेल पता, उदाहरण के लिए: \"इमिच फोटो सर्वर \"। यह सुनिश्चित करें कि आप उसी पते का उपयोग करें जिससे आपको ईमेल भेजने की अनुमति है।", - "notification_email_host_description": "ईमेल सर्वर का होस्ट (उदा. smtp.immitch.app)", - "notification_email_ignore_certificate_errors": "प्रमाणपत्र त्रुटियों पर ध्यान न दें", - "notification_email_ignore_certificate_errors_description": "टीएलएस प्रमाणपत्र सत्यापन त्रुटियों पर ध्यान न दें (अनुशंसित नहीं)", - "notification_email_password_description": "ईमेल सर्वर से प्रमाणीकरण करते समय उपयोग किया जाने वाला पासवर्ड", - "notification_email_port_description": "ईमेल सर्वर का पोर्ट (जैसे 25, 465, या 587)", - "notification_email_secure": "एस एम टी पी एस", - "notification_email_secure_description": "एस.एम.टी.पी.एस. प्रयोग करें (टी.एल.एस पर एस.एम.टी.पी)", - "notification_email_sent_test_email_button": "परीक्षण ईमेल भेजें और सहेजें", - "notification_email_setting_description": "ईमेल सूचनाएं भेजने के लिए सेटिंग्स", - "notification_email_test_email": "परीक्षण ईमेल भेजें", - "notification_email_test_email_failed": "परीक्षण ईमेल भेजने में विफल, अपने मूल्यों की जाँच करें", - "notification_email_test_email_sent": "{email} पर एक परीक्षण ईमेल भेजा गया है। कृपया अपना इनबॉक्स देखें।", - "notification_email_username_description": "ईमेल सर्वर से प्रमाणीकरण करते समय उपयोग किया जाने वाला उपयोगकर्ता नाम", - "notification_enable_email_notifications": "ईमेल सूचनाएं सक्षम करें", - "notification_settings": "अधिसूचना सेटिंग्स", - "notification_settings_description": "ईमेल सहित अधिसूचना सेटिंग्स प्रबंधित करें", - "oauth_auto_launch": "ऑटो लांच", - "oauth_auto_launch_description": "लॉगिन पृष्ठ पर नेविगेट करने पर OAuth लॉगिन प्रवाह स्वचालित रूप से प्रारंभ करें", - "oauth_auto_register": "ऑटो रजिस्टर", - "oauth_auto_register_description": "OAuth के साथ साइन इन करने के बाद स्वचालित रूप से नए उपयोगकर्ताओं को पंजीकृत करें", - "oauth_button_text": "टेक्स्ट बटन", - "oauth_client_secret_description": "यदि PKCE (कोड एक्सचेंज के लिए प्रूफ़ कुंजी) OAuth प्रदाता द्वारा समर्थित नहीं है तो यह आवश्यक है", - "oauth_enable_description": "OAuth से लॉगिन करें", - "oauth_mobile_redirect_uri": "मोबाइल रीडायरेक्ट यूआरआई", - "oauth_mobile_redirect_uri_override": "मोबाइल रीडायरेक्ट यूआरआई ओवरराइड", - "oauth_mobile_redirect_uri_override_description": "जब OAuth प्रदाता किसी मोबाइल URI, जैसे ''{callback}'' की अनुमति नहीं देता, तब सक्षम करें", - "oauth_role_claim": "भूमिका का दावा", - "oauth_role_claim_description": "इस दावे की उपस्थिति के आधार पर स्वचालित रूप से व्यवस्थापक पहुँच प्रदान करें। दावे में 'उपयोगकर्ता' या 'व्यवस्थापक' हो सकता है।", - "oauth_settings": "ओऑथ", - "oauth_settings_description": "OAuth लॉगिन सेटिंग प्रबंधित करें", - "oauth_settings_more_details": "इस सुविधा के बारे में अधिक जानकारी के लिए, देखें डॉक्स।", - "oauth_storage_label_claim": "भंडारण लेबल का दावा", - "oauth_storage_label_claim_description": "इस दावे के मूल्य पर उपयोगकर्ता के भंडारण लेबल को स्वचालित रूप से सेट करें।", - "oauth_storage_quota_claim": "भंडारण कोटा का दावा", - "oauth_storage_quota_claim_description": "उपयोगकर्ता के संग्रहण कोटा को इस दावे के मूल्य पर स्वचालित रूप से सेट करें।", - "oauth_storage_quota_default": "डिफ़ॉल्ट संग्रहण कोटा (GiB)", - "oauth_storage_quota_default_description": "GiB में कोटा का उपयोग तब किया जाएगा जब कोई दावा प्रदान नहीं किया गया हो ।", - "oauth_timeout": "ब्रेक का अनुरोध", - "oauth_timeout_description": "अनुरोधों के लिए समय-सीमा मिलीसेकंड में", - "ocr_job_description": "चित्रों में पाठ को पहचानने के लिए मशीन लर्निंग का उपयोग करें", - "password_enable_description": "ईमेल और पासवर्ड से लॉगिन करें", - "password_settings": "पासवर्ड लॉग इन", - "password_settings_description": "पासवर्ड लॉगिन सेटिंग प्रबंधित करें", - "paths_validated_successfully": "सभी पथ सफलतापूर्वक मान्य किए गए", - "person_cleanup_job": "व्यक्ति सफ़ाई", - "quota_size_gib": "कोटा आकार (GiB)", - "refreshing_all_libraries": "सभी पुस्तकालयों को ताज़ा किया जा रहा है", - "registration": "व्यवस्थापक पंजीकरण", - "registration_description": "चूंकि आप सिस्टम पर पहले उपयोगकर्ता हैं, इसलिए आपको व्यवस्थापक के रूप में नियुक्त किया जाएगा और आप प्रशासनिक कार्यों के लिए जिम्मेदार होंगे, और अतिरिक्त उपयोगकर्ता आपके द्वारा बनाए जाएंगे।", - "require_password_change_on_login": "उपयोगकर्ता को पहले लॉगिन पर पासवर्ड बदलने की आवश्यकता है", - "reset_settings_to_default": "सेटिंग्स को डिफ़ॉल्ट पर रीसेट करें", - "reset_settings_to_recent_saved": "सेटिंग्स को हाल ही में सहेजी गई सेटिंग्स पर रीसेट करें", - "scanning_library": "स्कैनिंग लाइब्रेरी", - "search_jobs": "नौकरी खोजें…", - "send_welcome_email": "स्वागत ईमेल भेजें", - "server_external_domain_settings": "बाहरी डोमेन", - "server_external_domain_settings_description": "सार्वजनिक साझा लिंक के लिए डोमेन, जिसमें http(s):// शामिल है", - "server_public_users": "सार्वजनिक उपयोगकर्ता", - "server_public_users_description": "साझा एल्बम में उपयोगकर्ता जोड़ते समय सभी उपयोगकर्ताओं (नाम और ईमेल) की सूची दिखाई जाती है। यदि यह विकल्प अक्षम किया गया है, तो उपयोगकर्ता सूची केवल व्यवस्थापक (एडमिन) उपयोगकर्ताओं के लिए उपलब्ध होगी।", - "server_settings": "सर्वर सेटिंग्स", - "server_settings_description": "सर्वर सेटिंग्स प्रबंधित करें", - "server_welcome_message": "स्वागत संदेश", - "server_welcome_message_description": "एक संदेश जो लॉगिन पृष्ठ पर प्रदर्शित होता है।", - "sidecar_job": "साइडकार मेटाडेटा", - "sidecar_job_description": "फ़ाइल सिस्टम से साइडकार मेटाडेटा खोजें या सिंक्रनाइज़ करें", - "slideshow_duration_description": "प्रत्येक छवि को प्रदर्शित करने के लिए सेकंड की संख्या", - "smart_search_job_description": "स्मार्ट खोज का समर्थन करने के लिए संपत्तियों पर मशीन लर्निंग चलाएं", - "storage_template_date_time_description": "एसेट के निर्माण टाइमस्टैम्प का उपयोग दिनांक समय की जानकारी के लिए किया जाता है", - "storage_template_date_time_sample": "नमूना समय {date}", - "storage_template_enable_description": "भंडारण टेम्पलेट इंजन सक्षम करें", - "storage_template_hash_verification_enabled": "हैश सत्यापन सक्षम किया गया", - "storage_template_hash_verification_enabled_description": "हैश सत्यापन सक्षम करता है, जब तक आप इसके निहितार्थों के बारे में निश्चित न हों, इसे अक्षम न करें", - "storage_template_migration": "भंडारण टेम्पलेट माइग्रेशन", - "storage_template_migration_description": "वर्तमान {template} को पहले अपलोड की गई संपत्तियों पर लागू करें", - "storage_template_migration_info": "स्टोरेज टेम्प्लेट सभी एक्सटेंशन को लोअरकेस में बदल देगा। टेम्प्लेट में किए गए बदलाव सिर्फ़ नई संपत्तियों पर लागू होंगे। टेम्प्लेट को पहले अपलोड की गई संपत्तियों पर पूर्वव्यापी रूप से लागू करने के लिए, {job} चलाएँ।", - "storage_template_migration_job": "संग्रहण टेम्पलेट माइग्रेशन कार्य", - "storage_template_more_details": "इस सुविधा के बारे में अधिक जानकारी के लिए, देखें भंडारण टेम्पलेट और इसके आशय", - "storage_template_onboarding_description_v2": "सक्षम होने पर, यह सुविधा उपयोगकर्ता-निर्धारित टेम्पलेट के आधार पर फ़ाइलों को स्वतः व्यवस्थित करेगी। अधिक जानकारी के लिए, कृपया दस्तावेज़ीकरण देखें।", - "storage_template_path_length": "अनुमानित पथ लंबाई सीमा: {length, number}/{limit, number}", - "storage_template_settings": "भंडारण टेम्पलेट", - "storage_template_settings_description": "अपलोड संपत्ति की फ़ोल्डर संरचना और फ़ाइल नाम प्रबंधित करें", - "storage_template_user_label": "{label} उपयोगकर्ता का स्टोरेज लेबल है", - "system_settings": "प्रणाली व्यवस्था", - "tag_cleanup_job": "टैग सफ़ाई", - "template_email_available_tags": "आप अपने टेम्प्लेट में निम्नलिखित वेरिएबल का उपयोग कर सकते हैं: {tags}", - "template_email_if_empty": "यदि टेम्पलेट रिक्त है, तो डिफ़ॉल्ट ईमेल का उपयोग किया जाएगा।", - "template_email_invite_album": "आमंत्रण एल्बम टेम्पलेट", - "template_email_preview": "पूर्व दर्शन", - "template_email_settings": "ईमेल टेम्प्लेट", - "template_email_update_album": "एल्बम टेम्पलेट अपडेट करें", - "template_email_welcome": "स्वागत ईमेल टेम्पलेट", - "template_settings": "अधिसूचना टेम्पलेट्स", - "template_settings_description": "सूचनाओं के लिए कस्टम टेम्प्लेट प्रबंधित करें", - "theme_custom_css_settings": "कस्टम सीएसएस", - "theme_custom_css_settings_description": "कैस्केडिंग स्टाइल शीट्स इमिच के डिज़ाइन को अनुकूलित करने की अनुमति देती हैं।", - "theme_settings": "थीम सेटिंग", - "theme_settings_description": "इम्मीच वेब इंटरफ़ेस का अनुकूलन प्रबंधित करें", - "thumbnail_generation_job": "थंबनेल उत्पन्न करें", - "thumbnail_generation_job_description": "प्रत्येक संपत्ति के लिए बड़े, छोटे और धुंधले थंबनेल, साथ ही प्रत्येक व्यक्ति के लिए थंबनेल बनाएं", - "transcoding_acceleration_api": "त्वरण एपीआई", - "transcoding_acceleration_api_description": "एपीआई जो ट्रांसकोडिंग को तेज करने के लिए आपके डिवाइस के साथ इंटरैक्ट करेगा।", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU की आवश्यकता है)", - "transcoding_acceleration_qsv": "त्वरित सिंक (सातवीं पीढ़ी के इंटेल सीपीयू या बाद के संस्करण की आवश्यकता है)", - "transcoding_acceleration_rkmpp": "आरकेएमपीपी (केवल रॉकचिप एसओसी पर)", - "transcoding_acceleration_vaapi": "वीएएपीआई", - "transcoding_accepted_audio_codecs": "स्वीकृत ऑडियो कोडेक्स", - "transcoding_accepted_audio_codecs_description": "चुनें कि किन ऑडियो कोडेक्स को ट्रांसकोड करने की आवश्यकता नहीं है।", - "transcoding_accepted_containers": "स्वीकृत कंटेनर", - "transcoding_accepted_containers_description": "चुनें कि किन कंटेनर प्रारूपों को MP4 में रीमक्स करने की आवश्यकता नहीं है।", - "transcoding_accepted_video_codecs": "स्वीकृत वीडियो कोडेक्स", - "transcoding_accepted_video_codecs_description": "चुनें कि किन वीडियो कोडेक्स को ट्रांसकोड करने की आवश्यकता नहीं है।", - "transcoding_advanced_options_description": "अधिकांश उपयोगकर्ताओं को विकल्प बदलने की आवश्यकता नहीं होनी चाहिए", - "transcoding_audio_codec": "ऑडियो कोडेक", - "transcoding_audio_codec_description": "ओपस उच्चतम गुणवत्ता वाला विकल्प है, लेकिन पुराने उपकरणों या सॉफ़्टवेयर के साथ इसकी अनुकूलता कम है।", - "transcoding_bitrate_description": "अधिकतम बिटरेट से अधिक या स्वीकृत प्रारूप में नहीं होने वाले वीडियो", - "transcoding_codecs_learn_more": "यहां प्रयुक्त शब्दावली के बारे में अधिक जानने के लिए, FFmpeg दस्तावेज़ देखें H.264 कोडेक, एचईवीसी कोडेक और VP9 कोडेक।", - "transcoding_constant_quality_mode": "लगातार गुणवत्ता मोड", - "transcoding_constant_quality_mode_description": "ICQ CQP से बेहतर है, लेकिन कुछ हार्डवेयर एक्सेलेरेशन डिवाइस इस मोड का समर्थन नहीं करते हैं।", - "transcoding_constant_rate_factor": "स्थिर दर कारक (-सीआरएफ)", - "transcoding_constant_rate_factor_description": "वीडियो गुणवत्ता स्तर।", - "transcoding_disabled_description": "किसी भी वीडियो को ट्रांसकोड न करें, इससे कुछ क्लाइंट पर प्लेबैक बाधित हो सकता है", - "transcoding_encoding_options": "एन्कोडिंग विकल्प", - "transcoding_encoding_options_description": "एनकोडेड वीडियो के लिए कोडेक्स, रिज़ॉल्यूशन, गुणवत्ता और अन्य विकल्प सेट करें", - "transcoding_hardware_acceleration": "हार्डवेयर एक्सिलरेशन", - "transcoding_hardware_acceleration_description": "प्रायोगिक; बहुत अधिक: तेज़, लेकिन इसमें कम ट्रांसकोडिंग होगी लेकिन समान बिटरेट पर गुणवत्ता कम हो सकती है", - "transcoding_hardware_decoding": "हार्डवेयर डिकोडिंग", - "transcoding_hardware_decoding_setting_description": "केवल एनवीईएनसी, क्यूएसवी और आरकेएमपीपी पर लागू होता है।", - "transcoding_max_b_frames": "अधिकतम बी-फ्रेम", - "transcoding_max_b_frames_description": "उच्च मान संपीड़न दक्षता में सुधार करते हैं, लेकिन एन्कोडिंग को धीमा कर देते हैं।", - "transcoding_max_bitrate": "अधिकतम बिटरेट", - "transcoding_max_bitrate_description": "अधिकतम बिटरेट सेट करने से फ़ाइल आकार को गुणवत्ता पर मामूली लागत के साथ अधिक पूर्वानुमानित किया जा सकता है। 720p पर, सामान्य मान VP9 या HEVC के लिए 2600k kbit/s या H.264 के लिए 4500k kbit/s हैं। 0 पर सेट होने पर अक्षम। जब कोई इकाई निर्दिष्ट नहीं की जाती है, तो k (kbit/s के लिए) मान लिया जाता है; इसलिए 5000, 5000k, और 5M (Mbit/s के लिए) समतुल्य हैं।", - "transcoding_max_keyframe_interval": "अधिकतम मुख्यफ़्रेम अंतराल", - "transcoding_max_keyframe_interval_description": "मुख्यफ़्रेम के बीच अधिकतम फ़्रेम दूरी निर्धारित करता है।", - "transcoding_optimal_description": "लक्ष्य रिज़ॉल्यूशन से अधिक ऊंचे वीडियो या स्वीकृत प्रारूप में नहीं", - "transcoding_policy": "ट्रांसकोड नीति", - "transcoding_policy_description": "सेट करें कि वीडियो कब ट्रांसकोड किया जाएगा", - "transcoding_preferred_hardware_device": "पसंदीदा हार्डवेयर डिवाइस", - "transcoding_preferred_hardware_device_description": "केवल VAAPI और QSV पर लागू होता है।", - "transcoding_preset_preset": "प्रीसेट (-preset)", - "transcoding_preset_preset_description": "संपीड़न गति।", - "transcoding_reference_frames": "संदर्भ फ्रेम", - "transcoding_reference_frames_description": "किसी दिए गए फ़्रेम को संपीड़ित करते समय संदर्भित किए जाने वाले फ़्रेमों की संख्या।", - "transcoding_required_description": "केवल वे वीडियो जो स्वीकृत प्रारूप में नहीं हैं", - "transcoding_settings": "वीडियो ट्रांसकोडिंग सेटिंग्स", - "transcoding_settings_description": "वीडियो फ़ाइलों के रिज़ॉल्यूशन और एन्कोडिंग जानकारी को प्रबंधित करें, किन वीडियो को ट्रांसकोड करना है और उन्हें कैसे प्रोसेस करना है", - "transcoding_target_resolution": "लक्ष्य संकल्प", - "transcoding_target_resolution_description": "उच्च रिज़ॉल्यूशन अधिक विवरण संरक्षित कर सकते हैं लेकिन एन्कोड करने में अधिक समय लेते हैं, फ़ाइल आकार बड़े होते हैं, और ऐप प्रतिक्रियाशीलता को कम कर सकते हैं।", - "transcoding_temporal_aq": "अस्थायी AQ", - "transcoding_temporal_aq_description": "केवल NVENC पर लागू होता है। टेम्पोरल अडैप्टिव क्वांटाइज़ेशन उच्च-विस्तार, कम-गति वाले दृश्यों की गुणवत्ता बढ़ाता है। हो सकता है कि यह पुराने उपकरणों के साथ संगत न हो।", - "transcoding_threads": "थ्रेड्स", - "transcoding_threads_description": "उच्च मान तेज़ एन्कोडिंग की ओर ले जाते हैं, लेकिन सक्रिय रहते हुए सर्वर के लिए अन्य कार्यों को संसाधित करने के लिए कम जगह छोड़ते हैं।", - "transcoding_tone_mapping": "टोन-मैपिंग", - "transcoding_tone_mapping_description": "एसडीआर में परिवर्तित होने पर एचडीआर वीडियो की उपस्थिति को संरक्षित करने का प्रयास।", - "transcoding_transcode_policy": "ट्रांसकोड नीति", - "transcoding_transcode_policy_description": "किसी वीडियो को कब ट्रांसकोड किया जाना चाहिए, इसके लिए नीति।", - "transcoding_two_pass_encoding": "दो-पास एन्कोडिंग", - "transcoding_two_pass_encoding_setting_description": "बेहतर एन्कोडेड वीडियो बनाने के लिए दो पासों में ट्रांसकोड करें।", - "transcoding_video_codec": "वीडियो कोडेक", - "transcoding_video_codec_description": "VP9 में उच्च दक्षता और वेब अनुकूलता है, लेकिन ट्रांसकोड करने में अधिक समय लगता है।", - "trash_enabled_description": "ट्रैश सुविधाएँ सक्षम करें", - "trash_number_of_days": "दिनों की संख्या", - "trash_number_of_days_description": "संपत्तियों को स्थायी रूप से हटाने से पहले उन्हें कूड़ेदान में रखने के लिए दिनों की संख्या", - "trash_settings": "ट्रैश सेटिंग", - "trash_settings_description": "ट्रैश सेटिंग प्रबंधित करें", - "unlink_all_oauth_accounts": "सभी ओ.औथ खातों से संपर्क तोड़ दें", - "unlink_all_oauth_accounts_description": "नए प्रदाता पर सतानांतरण करने से पहले सभी ओ.औथ खातों से संपर्क तोड़ना याद रखें।", - "unlink_all_oauth_accounts_prompt": "क्या आप वाकई सभी ओ.औथ खातों से संपर्क तोड़ना चाहते हैं? इससे प्रत्येक उपयोगकर्ता के लिए ओ.औथ आई.डी रद्द हो जाएगी और इसे पूर्ववत नहीं किया जा सकेगा।", - "user_cleanup_job": "उपयोगकर्ता सफ़ाई", - "user_delete_delay": "{user} के खाते और परिसंपत्तियों को {delay, plural, one {# day} other {# days}} में स्थायी रूप से हटाने के लिए शेड्यूल किया जाएगा।", - "user_delete_delay_settings": "हटाने में देरी", - "user_delete_delay_settings_description": "किसी उपयोगकर्ता के खाते और संपत्तियों को स्थायी रूप से हटाने के लिए हटाने के बाद दिनों की संख्या।", - "user_delete_immediately": "{user} के खाते और परिसंपत्तियों को तुरंत स्थायी रूप से हटाने के लिए कतार में रखा जाएगा।", - "user_delete_immediately_checkbox": "तत्काल विलोपन के लिए उपयोगकर्ता और परिसंपत्तियों को कतारबद्ध करें", - "user_details": "उपयोगकर्ता विवरण", - "user_management": "प्रयोक्ता प्रबंधन", - "user_password_has_been_reset": "उपयोगकर्ता का पासवर्ड रीसेट कर दिया गया है:", - "user_password_reset_description": "कृपया उपयोगकर्ता को अस्थायी पासवर्ड प्रदान करें और उन्हें सूचित करें कि उन्हें अपने अगले लॉगिन पर पासवर्ड बदलने की आवश्यकता होगी।", - "user_restore_description": "{user} का खाता बहाल कर दिया जाएगा।", - "user_restore_scheduled_removal": "उपयोगकर्ता को पुनर्स्थापित करें - {date, date, long} पर हटाया जाना निर्धारित है", - "user_settings": "उपयोगकर्ता सेटिंग", - "user_settings_description": "उपयोगकर्ता सेटिंग प्रबंधित करें", - "version_check_enabled_description": "नई रिलीज़ की जाँच के लिए GitHub पर आवधिक अनुरोध सक्षम करें", - "version_check_implications": "संस्करण जाँच सुविधा github.com के साथ आवधिक संचार पर निर्भर करती है", - "version_check_settings": "संस्करण चेक", - "version_check_settings_description": "नए संस्करण अधिसूचना को सक्षम/अक्षम करें", - "video_conversion_job": "ट्रांसकोड वीडियो", - "video_conversion_job_description": "ब्राउज़रों और उपकरणों के साथ व्यापक अनुकूलता के लिए वीडियो ट्रांसकोड करें" - }, - "admin_email": "व्यवस्थापक ईमेल", - "admin_password": "व्यवस्थापक पासवर्ड", - "administration": "प्रशासन", - "advanced": "विकसित", - "advanced_settings_enable_alternate_media_filter_subtitle": "सिंक के दौरान वैकल्पिक मानदंडों के आधार पर मीडिया को फ़िल्टर करने के लिए इस विकल्प का उपयोग करें। इसे केवल तभी आज़माएँ जब आपको ऐप द्वारा सभी एल्बमों का पता लगाने में समस्या हो।", - "advanced_settings_enable_alternate_media_filter_title": "[प्रयोगात्मक] वैकल्पिक डिवाइस एल्बम सिंक फ़िल्टर का उपयोग करें", - "advanced_settings_log_level_title": "लॉग स्तर:{level}", - "advanced_settings_prefer_remote_subtitle": "कुछ डिवाइस स्थानीय एसेट से थंबनेल लोड करने में बहुत धीमे होते हैं। इसके बजाय, दूरस्थ इमेज लोड करने के लिए इस सेटिंग को सक्रिय करें।", - "advanced_settings_prefer_remote_title": "दूरस्थ छवियों को प्राथमिकता दें", - "advanced_settings_proxy_headers_subtitle": "प्रत्येक नेटवर्क अनुरोध के साथ इम्मिच द्वारा भेजे जाने वाले प्रॉक्सी हेडर को परिभाषित करें", - "advanced_settings_proxy_headers_title": "कस्टम प्रॉक्सी हेडर [प्रायोगिक]", - "advanced_settings_readonly_mode_subtitle": "रीड-ओनली प्रणाली को सक्षम करता है जहां चित्र को केवल देखा जा सकता है, एकाधिक चित्रों का चयन करना, साझा करना, कास्टिंग करना, हटाना जैसी सभी चीज़ें अक्षम हैं। मुख्य स्क्रीन में उपयोगकर्ता- अवतार के माध्यम से रीड-ओनली प्रणाली को सक्षम/अक्षम करें", - "advanced_settings_readonly_mode_title": "रीड-ओनली प्रणाली", - "advanced_settings_self_signed_ssl_subtitle": "सर्वर एंडपॉइंट के लिए SSL प्रमाणपत्र सत्यापन को छोड़ देता है। स्व-हस्ताक्षरित प्रमाणपत्रों के लिए आवश्यक है।", - "advanced_settings_self_signed_ssl_title": "स्व-हस्ताक्षरित SSL प्रमाणपत्रों की अनुमति दें [प्रायोगिक]", - "advanced_settings_sync_remote_deletions_subtitle": "वेब पर कार्रवाई किए जाने पर इस डिवाइस पर किसी संपत्ति को स्वचालित रूप से हटाएँ या पुनर्स्थापित करें", - "advanced_settings_sync_remote_deletions_title": "दूरस्थ विलोपन सिंक करें [प्रायोगिक]", - "advanced_settings_tile_subtitle": "उन्नत उपयोगकर्ता सेटिंग्स", - "advanced_settings_troubleshooting_subtitle": "समस्या निवारण के लिए अतिरिक्त सुविधाएँ सक्षम करें", - "advanced_settings_troubleshooting_title": "समस्या निवारण", - "age_months": "आयु {months, plural, one {# month} other {# months}}", - "age_year_months": "आयु 1 वर्ष, {months, plural, one {# month} other {# months}}", - "age_years": "{years, plural, other {Age #}}", - "album": "एल्बम", - "album_added": "एल्बम डाला गया", - "album_added_notification_setting_description": "जब आपको किसी साझा एल्बम में जोड़ा जाए तो एक ईमेल सूचना प्राप्त करें", - "album_cover_updated": "एल्बम कवर अपडेट किया गया", - "album_delete_confirmation": "क्या आप वाकई एल्बम {album} हटाना चाहते हैं?", - "album_delete_confirmation_description": "यदि यह एल्बम साझा किया गया है, तो अन्य उपयोगकर्ता इसे एक्सेस नहीं कर पाएंगे।", - "album_deleted": "एल्बम हटा दिया गया", - "album_info_card_backup_album_excluded": "छोड़ा गया", - "album_info_card_backup_album_included": "शामिल", - "album_info_updated": "एल्बम की जानकारी अपडेट की गई", - "album_leave": "एल्बम छोड़ें?", - "album_leave_confirmation": "क्या आप वाकई {album} छोड़ना चाहते हैं?", - "album_name": "एल्बम का नाम", - "album_options": "एल्बम विकल्प", - "album_remove_user": "उपयोगकर्ता हटाएं?", - "album_remove_user_confirmation": "क्या आप वाकई {user} को हटाना चाहते हैं?", - "album_search_not_found": "आपकी खोज से मेल खाता कोई एल्बम नहीं मिला", - "album_share_no_users": "ऐसा लगता है कि आपने यह एल्बम सभी उपयोगकर्ताओं के साथ साझा कर दिया है या आपके पास साझा करने के लिए कोई उपयोगकर्ता नहीं है।", - "album_summary": "एल्बम सारांश", - "album_updated": "एल्बम अपडेट किया गया", - "album_updated_setting_description": "जब किसी साझा एल्बम में नई संपत्तियाँ हों तो एक ईमेल सूचना प्राप्त करें", - "album_user_left": "बायाँ {album}", - "album_user_removed": "{user} को हटाया गया", - "album_viewer_appbar_delete_confirm": "क्या आप वाकई इस एल्बम को अपने खाते से हटाना चाहते हैं?", - "album_viewer_appbar_share_err_delete": "एल्बम हटाने में विफल", - "album_viewer_appbar_share_err_leave": "एल्बम छोड़ने में विफल", - "album_viewer_appbar_share_err_remove": "एल्बम से संपत्ति हटाने में समस्याएँ हैं", - "album_viewer_appbar_share_err_title": "एल्बम का शीर्षक बदलने में विफल", - "album_viewer_appbar_share_leave": "एल्बम छोड़ें", - "album_viewer_appbar_share_to": "साझा करें", - "album_viewer_page_share_add_users": "उपयोगकर्ता जोड़ें", - "album_with_link_access": "लिंक वाले किसी भी व्यक्ति को इस एल्बम में फ़ोटो और लोगों को देखने दें।", - "albums": "एलबम", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albums}}", - "albums_default_sort_order": "डिफ़ॉल्ट एल्बम सॉर्ट क्रम", - "albums_default_sort_order_description": "नये एल्बम बनाते समय आरंभिक परिसंपत्ति सॉर्ट क्रम।", - "albums_feature_description": "परिसंपत्तियों का संग्रह जिसे अन्य उपयोगकर्ताओं के साथ साझा किया जा सकता है।", - "albums_on_device_count": "डिवाइस पर एल्बम ({count})", - "all": "सभी", - "all_albums": "सभी एलबम", - "all_people": "सभी लोग", - "all_videos": "सभी वीडियो", - "allow_dark_mode": "डार्क मोड की अनुमति दें", - "allow_edits": "संपादन की अनुमति दें", - "allow_public_user_to_download": "सार्वजनिक उपयोगकर्ता को डाउनलोड करने की अनुमति दें", - "allow_public_user_to_upload": "सार्वजनिक उपयोगकर्ता को अपलोड करने की अनुमति दें", - "allowed": "अनुमत", - "alt_text_qr_code": "क्यूआर कोड छवि", - "anti_clockwise": "वामावर्त", - "api_key": "एपीआई की", - "api_key_description": "यह की केवल एक बार दिखाई जाएगी। विंडो बंद करने से पहले कृपया इसे कॉपी करना सुनिश्चित करें।।", - "api_key_empty": "आपका एपीआई कुंजी नाम खाली नहीं होना चाहिए", - "api_keys": "एपीआई कीज", - "app_architecture_variant": "रूपान्तर (स्थापत्य/आर्किटेक्चर)", - "app_bar_signout_dialog_content": "क्या आप सुनिश्चित हैं कि आप लॉग आउट करना चाहते हैं?", - "app_bar_signout_dialog_ok": "हाँ", - "app_bar_signout_dialog_title": "लॉग आउट", - "app_download_links": "ऐप डाउनलोड लिंक", - "app_settings": "एप्लिकेशन सेटिंग", - "app_stores": "ऐप स्टोर/गोदाम", - "app_update_available": "आधुनिक ऐप उपलब्ध है", - "appears_in": "प्रकट होता है", - "apply_count": "लागू करें ({count, number})", - "archive": "संग्रहालय", - "archive_action_prompt": "{count} को संग्रह में जोड़ा गया", - "archive_or_unarchive_photo": "फ़ोटो को संग्रहीत या असंग्रहीत करें", - "archive_page_no_archived_assets": "कोई संग्रहीत संपत्ति नहीं मिली", - "archive_page_title": "पुरालेख ({count})", - "archive_size": "पुरालेख आकार", - "archive_size_description": "डाउनलोड के लिए संग्रह आकार कॉन्फ़िगर करें (GiB में)", - "archived": "संग्रहित", - "archived_count": "{count, plural, other {# संग्रहीत किए गए}}", - "are_these_the_same_person": "क्या ये वही व्यक्ति हैं?", - "are_you_sure_to_do_this": "क्या आप वास्तव में इसे करना चाहते हैं?", - "asset_action_delete_err_read_only": "केवल पढ़ने योग्य परिसंपत्ति(ओं) को हटाया नहीं जा सकता, छोड़ा जा सकता है", - "asset_action_share_err_offline": "ऑफ़लाइन परिसंपत्ति(एँ) प्राप्त नहीं की जा सकती, छोड़ी जा रही है", - "asset_added_to_album": "एल्बम में डाला गया", - "asset_adding_to_album": "एल्बम में डाला जा रहा है…", - "asset_description_updated": "संपत्ति विवरण अद्यतन कर दिया गया है", - "asset_filename_is_offline": "एसेट {filename} ऑफ़लाइन है", - "asset_has_unassigned_faces": "एसेट में अनिर्धारित चेहरे हैं", - "asset_hashing": "हैशिंग…", - "asset_list_group_by_sub_title": "द्वारा समूह बनाएं", - "asset_list_layout_settings_dynamic_layout_title": "गतिशील लेआउट", - "asset_list_layout_settings_group_automatically": "स्वचालित", - "asset_list_layout_settings_group_by": "समूह परिसंपत्तियों द्वारा", - "asset_list_layout_settings_group_by_month_day": "महीना + दिन", - "asset_list_layout_sub_title": "लेआउट", - "asset_list_settings_subtitle": "फ़ोटो ग्रिड लेआउट सेटिंग्स", - "asset_list_settings_title": "चित्र की जाली", - "asset_offline": "संपत्ति ऑफ़लाइन", - "asset_offline_description": "यह संपत्ति ऑफ़लाइन है।", - "asset_restored_successfully": "संपत्ति(याँ) सफलतापूर्वक पुनर्स्थापित की गईं", - "asset_skipped": "छोड़ा गया", - "asset_skipped_in_trash": "कचरे में", - "asset_trashed": "एसेट नष्ट किया गया", - "asset_troubleshoot": "एसेट समस्या निवारण", - "asset_uploaded": "अपलोड किए गए", - "asset_uploading": "अपलोड हो रहा है…", - "asset_viewer_settings_subtitle": "अपनी गैलरी व्यूअर सेटिंग प्रबंधित करें", - "asset_viewer_settings_title": "एसेट व्यूअर", - "assets": "संपत्तियां", - "assets_added_count": "{count, plural, one {# asset} other {# assets}} जोड़ा गया", - "assets_added_to_album_count": "एल्बम में {count, plural, one {# asset} other {# assets}} जोड़ा गया", - "assets_added_to_albums_count": "{assetTotal, plural, one {# asset} other {# assets}} को {albumTotal, plural, one {# album} other {# albums}} से जोड़ा गया", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} को एल्बम में नहीं जोड़ा जा सकता", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} किसी एल्बम से नहीं जोड़े जा सकते", - "assets_count": "{count, plural, one {# आइटम} other {# आइटम्स}}", - "assets_deleted_permanently": "{count} संपत्ति(याँ) स्थायी रूप से हटा दी गईं", - "assets_deleted_permanently_from_server": "{count} संपत्ति(याँ) इमिच सर्वर से स्थायी रूप से हटा दी गईं", - "assets_downloaded_failed": "{count, plural, one {Downloaded # file - {error} file failed} other {Downloaded # files - {error} files failed}}", - "assets_downloaded_successfully": "{count, plural, one {# फ़ाइल सफलतापूर्वक डाउनलोड की गई} other {# फ़ाइलें सफलतापूर्वक डाउनलोड की गईं}}", - "assets_moved_to_trash_count": "{count, plural, one {# asset} other {# assets}} को ट्रैश में ले जाया गया", - "assets_permanently_deleted_count": "स्थायी रूप से हटा दिया गया {count, plural, one {# asset} other {# assets}}", - "assets_removed_count": "{count, plural, one {# asset} other {# assets}} हटा दिया गया", - "assets_removed_permanently_from_device": "{count} संपत्ति(याँ) आपके डिवाइस से स्थायी रूप से हटा दी गईं", - "assets_restore_confirmation": "क्या आप वाकई अपनी सभी नष्ट की गई संपत्तियों को पुनर्स्थापित करना चाहते हैं? आप इस क्रिया को पूर्ववत नहीं कर सकते।", - "assets_restored_count": "पुनर्स्थापित {count, plural, one {# asset} other {# assets}}", - "assets_restored_successfully": "{count} संपत्ति(याँ) सफलतापूर्वक पुनर्स्थापित की गईं", - "assets_trashed": "{count} संपत्ति(याँ) कचरे में डाली गईं", - "assets_trashed_count": "ट्रैश की गई {count, plural, one {# asset} other {# assets}}", - "assets_trashed_from_server": "{count} संपत्ति(याँ) इमिच सर्वर से कचरे में डाली गईं", - "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}}एल्बम का पहले से ही हिस्सा थे", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} पहले ही एल्बम में संयोजित हैं", - "authorized_devices": "अधिकृत उपकरण", - "automatic_endpoint_switching_subtitle": "उपलब्ध होने पर निर्दिष्ट वाई-फाई से स्थानीय रूप से कनेक्ट करें और अन्यत्र वैकल्पिक कनेक्शन का उपयोग करें", - "automatic_endpoint_switching_title": "स्वचालित URL स्विचिंग", - "autoplay_slideshow": "ऑटोप्ले स्लाइड शो", - "back": "वापस", - "back_close_deselect": "वापस जाएँ, बंद करें, या अचयनित करें", - "background_backup_running_error": "परिप्रेक्ष्य बैकअप अभी जारी है, नियमावली बैकअप प्रारंभ नहीं किया जा सकता", - "background_location_permission": "पृष्ठभूमि स्थान अनुमति", - "background_location_permission_content": "पृष्ठभूमि में चलते समय नेटवर्क बदलने के लिए, Immich के पास *हमेशा* सटीक स्थान तक पहुंच होनी चाहिए ताकि ऐप वाई-फाई नेटवर्क का नाम पढ़ सके", - "background_options": "परिप्रेक्ष्य विकल्प", - "backup": "बैकअप", - "backup_album_selection_page_albums_device": "डिवाइस पर एल्बम ({count})", - "backup_album_selection_page_albums_tap": "शामिल करने के लिए टैप करें, बाहर करने के लिए डबल टैप करें", - "backup_album_selection_page_assets_scatter": "एसेट कई एल्बमों में बिखरे हो सकते हैं। इसलिए, बैकअप प्रक्रिया के दौरान एल्बमों को शामिल या बाहर किया जा सकता है।", - "backup_album_selection_page_select_albums": "एल्बम चुनें", - "backup_album_selection_page_selection_info": "चयन जानकारी", - "backup_album_selection_page_total_assets": "कुल अद्वितीय संपत्तियाँ", - "backup_albums_sync": "बैकअप एल्बम का तुल्यकालन", - "backup_all": "सभी", - "backup_background_service_backup_failed_message": "संपत्तियों का बैकअप लेने में विफल. पुनः प्रयास किया जा रहा है…", - "backup_background_service_complete_notification": "एसेट का बैकअप पूरा हुआ", - "backup_background_service_connection_failed_message": "सर्वर से कनेक्ट करने में विफल. पुनः प्रयास किया जा रहा है…", - "backup_background_service_current_upload_notification": "{filename} अपलोड हो रहा है", - "backup_background_service_default_notification": "नई परिसंपत्तियों की जांच की जा रही है…", - "backup_background_service_error_title": "बैकअप त्रुटि", - "backup_background_service_in_progress_notification": "अपनी परिसंपत्तियों का बैकअप लेना…", - "backup_background_service_upload_failure_notification": "{filename} अपलोड करने में विफल", - "backup_controller_page_albums": "बैकअप एल्बम", - "backup_controller_page_background_app_refresh_disabled_content": "बैकग्राउंड बैकअप का उपयोग करने के लिए सेटिंग्स > सामान्य > बैकग्राउंड ऐप रिफ्रेश में बैकग्राउंड ऐप रिफ्रेश सक्षम करें।", - "backup_controller_page_background_app_refresh_disabled_title": "पृष्ठभूमि ऐप रीफ़्रेश अक्षम", - "backup_controller_page_background_app_refresh_enable_button_text": "सेटिंग्स पर जाएँ", - "backup_controller_page_background_battery_info_link": "कैसे मुझे दिखाओ", - "backup_controller_page_background_battery_info_message": "सर्वोत्तम बैकग्राउंड बैकअप अनुभव के लिए, कृपया Immich के लिए बैकग्राउंड गतिविधि को प्रतिबंधित करने वाले किसी भी बैटरी ऑप्टिमाइज़ेशन को अक्षम करें।\n\nचूँकि यह डिवाइस-विशिष्ट है, इसलिए कृपया अपने डिवाइस निर्माता से आवश्यक जानकारी देखें।", - "backup_controller_page_background_battery_info_ok": "ठीक", - "backup_controller_page_background_battery_info_title": "बैटरी अनुकूलन", - "backup_controller_page_background_charging": "केवल चार्ज करते समय", - "backup_controller_page_background_configure_error": "पृष्ठभूमि सेवा कॉन्फ़िगर करने में विफल", - "backup_controller_page_background_delay": "नई संपत्ति का बैकअप विलंबित करें: {duration}", - "backup_controller_page_background_description": "ऐप खोले बिना किसी भी नई संपत्ति का स्वचालित रूप से बैकअप लेने के लिए पृष्ठभूमि सेवा चालू करें", - "backup_controller_page_background_is_off": "स्वचालित पृष्ठभूमि बैकअप बंद है", - "backup_controller_page_background_is_on": "स्वचालित पृष्ठभूमि बैकअप चालू है", - "backup_controller_page_background_turn_off": "पृष्ठभूमि सेवा बंद करें", - "backup_controller_page_background_turn_on": "पृष्ठभूमि सेवा चालू करें", - "backup_controller_page_background_wifi": "केवल वाई-फ़ाई पर", - "backup_controller_page_backup": "बैकअप", - "backup_controller_page_backup_selected": "चयनित: ", - "backup_controller_page_backup_sub": "बैकअप किए गए फ़ोटो और वीडियो", - "backup_controller_page_created": "निर्मित तिथि: {date}", - "backup_controller_page_desc_backup": "ऐप खोलते समय सर्वर पर नई संपत्तियों को स्वचालित रूप से अपलोड करने के लिए अग्रभूमि बैकअप चालू करें।", - "backup_controller_page_excluded": "छोड़ा गया: ", - "backup_controller_page_failed": "विफल ({count})", - "backup_controller_page_filename": "फ़ाइल नाम: {filename} [{size}]", - "backup_controller_page_id": "आईडी: {id}", - "backup_controller_page_info": "बैकअप जानकारी", - "backup_controller_page_none_selected": "कोई भी चयनित नहीं", - "backup_controller_page_remainder": "शेष", - "backup_controller_page_remainder_sub": "चयन से बैकअप लेने के लिए शेष फ़ोटो और वीडियो", - "backup_controller_page_server_storage": "सर्वर संग्रहण", - "backup_controller_page_start_backup": "बैकअप प्रारंभ करें", - "backup_controller_page_status_off": "स्वचालित अग्रभूमि बैकअप बंद है", - "backup_controller_page_status_on": "स्वचालित अग्रभूमि बैकअप चालू है", - "backup_controller_page_storage_format": "{total} में से {used} उपयोग किया गया", - "backup_controller_page_to_backup": "बैकअप किए जाने वाले एल्बम", - "backup_controller_page_total_sub": "चयनित एल्बमों से सभी अद्वितीय फ़ोटो और वीडियो", - "backup_controller_page_turn_off": "अग्रभूमि बैकअप बंद करें", - "backup_controller_page_turn_on": "अग्रभूमि बैकअप चालू करें", - "backup_controller_page_uploading_file_info": "फ़ाइल जानकारी अपलोड करना", - "backup_err_only_album": "एकमात्र एल्बम नहीं हटाया जा सकता", - "backup_error_sync_failed": "तुल्यकालन विफल. बैकअप संसाधित नहीं किया जा सकता।", - "backup_info_card_assets": "संपत्ति", - "backup_manual_cancelled": "रद्द", - "backup_manual_in_progress": "अपलोड पहले से ही प्रगति पर है। कुछ देर बाद प्रयास करें", - "backup_manual_success": "सफलता", - "backup_manual_title": "अपलोड स्थिति", - "backup_options": "बैकअप विकल्प", - "backup_options_page_title": "बैकअप विकल्प", - "backup_setting_subtitle": "पृष्ठभूमि और अग्रभूमि अपलोड सेटिंग प्रबंधित करें", - "backup_settings_subtitle": "अपलोड सेटिंग्स संभालें", - "backward": "पिछला", - "biometric_auth_enabled": "बायोमेट्रिक प्रमाणीकरण सक्षम", - "biometric_locked_out": "आप बायोमेट्रिक प्रमाणीकरण से बाहर हैं", - "biometric_no_options": "कोई बायोमेट्रिक विकल्प उपलब्ध नहीं है", - "biometric_not_available": "इस डिवाइस पर बायोमेट्रिक प्रमाणीकरण उपलब्ध नहीं है", - "birthdate_saved": "जन्मतिथि सफलतापूर्वक सहेजी गई", - "birthdate_set_description": "जन्मतिथि का उपयोग फोटो के समय इस व्यक्ति की आयु की गणना करने के लिए किया जाता है।", - "blurred_background": "धुंधली पृष्ठभूमि", - "bugs_and_feature_requests": "बग और सुविधा अनुरोध", - "build": "निर्माण", - "build_image": "छवि बनाएँ", - "bulk_delete_duplicates_confirmation": "क्या आप वाकई {count, plural, one {# duplicate asset} other {# duplicate assets}} को बल्क में हटाना चाहते हैं? इससे हर ग्रुप की सबसे बड़ी संपत्ति बनी रहेगी और बाकी सभी डुप्लिकेट हमेशा के लिए हट जाएँगे। आप इस क्रिया को पूर्ववत नहीं कर सकते!", - "bulk_keep_duplicates_confirmation": "क्या आप वाकई {count, plural, one {# duplicate asset} other {# duplicate assets}} रखना चाहते हैं? इससे बिना कुछ हटाए सभी डुप्लिकेट ग्रुप हल हो जाएँगे।", - "bulk_trash_duplicates_confirmation": "क्या आप वाकई {count, plural, one {# duplicate asset} other {# duplicate assets}} को बल्क ट्रैश करना चाहते हैं? इससे हर ग्रुप की सबसे बड़ी एसेट रहेगी और बाकी सभी डुप्लिकेट ट्रैश हो जाएँगे।", - "buy": "इम्मीच खरीदो", - "cache_settings_clear_cache_button": "कैश को साफ़ करें", - "cache_settings_clear_cache_button_title": "ऐप का कैश साफ़ करता है। कैश के दोबारा बनने तक, यह ऐप के प्रदर्शन पर काफ़ी असर डालेगा।", - "cache_settings_duplicated_assets_clear_button": "स्पष्ट", - "cache_settings_duplicated_assets_subtitle": "ऐप द्वारा अनदेखा की गई तस्वीरें और वीडियो", - "cache_settings_duplicated_assets_title": "डुप्लिकेट संपत्तियां ({count})", - "cache_settings_statistics_album": "लाइब्रेरी थंबनेल", - "cache_settings_statistics_full": "पूर्ण चित्र", - "cache_settings_statistics_shared": "साझा किए गए एल्बम थंबनेल", - "cache_settings_statistics_thumbnail": "थंबनेल", - "cache_settings_statistics_title": "कैश उपयोग", - "cache_settings_subtitle": "Immich मोबाइल एप्लिकेशन के कैशिंग व्यवहार को नियंत्रित करें", - "cache_settings_tile_subtitle": "स्थानीय संग्रहण के व्यवहार को नियंत्रित करें", - "cache_settings_tile_title": "स्थानीय संग्रहण", - "cache_settings_title": "कैशिंग सेटिंग्स", - "camera": "कैमरा", - "camera_brand": "कैमरा ब्रांड", - "camera_model": "कैमरा मॉडल", - "cancel": "रद्द करना", - "cancel_search": "खोज रद्द करें", - "canceled": "रद्द करना", - "canceling": "रद्द कर रहा है", - "cannot_merge_people": "लोगों का विलय नहीं हो सकता", - "cannot_undo_this_action": "आप इस क्रिया को पूर्ववत नहीं कर सकते!", - "cannot_update_the_description": "विवरण अद्यतन नहीं किया जा सकता", - "cast": "ढालना", - "cast_description": "उपलब्ध कास्ट गंतव्यों को कॉन्फ़िगर करें", - "change_date": "बदलाव दिनांक", - "change_description": "विवरण बदलें", - "change_display_order": "प्रदर्शन क्रम बदलें", - "change_expiration_time": "समाप्ति समय बदलें", - "change_location": "स्थान बदलें", - "change_name": "नाम परिवर्तन करें", - "change_name_successfully": "नाम सफलतापूर्वक बदला गया", - "change_password": "पासवर्ड बदलें", - "change_password_description": "यह या तो पहली बार है जब आप सिस्टम में साइन इन कर रहे हैं या आपका पासवर्ड बदलने का अनुरोध किया गया है।", - "change_password_form_confirm_password": "पासवर्ड की पुष्टि कीजिये", - "change_password_form_description": "नमस्ते {name},\n\nया तो आप पहली बार सिस्टम में साइन इन कर रहे हैं या फिर आपका पासवर्ड बदलने का अनुरोध किया गया है। कृपया नीचे नया पासवर्ड डालें।", - "change_password_form_log_out": "अन्य सभी डिवाइस को लॉग आउट करें", - "change_password_form_log_out_description": "अन्य सभी डिवाइस से लॉग आउट करना अनुशंसित है", - "change_password_form_new_password": "नया पासवर्ड", - "change_password_form_password_mismatch": "सांकेतिक शब्द मेल नहीं खाते", - "change_password_form_reenter_new_password": "नया पासवर्ड पुनः दर्ज करें", - "change_pin_code": "पिन कोड बदलें", - "change_your_password": "अपना पासवर्ड बदलें", - "changed_visibility_successfully": "दृश्यता सफलतापूर्वक परिवर्तित", - "charging": "चार्जिंग", - "charging_requirement_mobile_backup": "परिप्रेक्ष्य बैकअप के लिए डिवाइस का चार्जिंग पे लगे होना आवश्यक है", - "check_corrupt_asset_backup": "दूषित परिसंपत्ति बैकअप की जाँच करें", - "check_corrupt_asset_backup_button": "जाँच करें", - "check_corrupt_asset_backup_description": "यह जाँच केवल वाई-फ़ाई पर ही करें और सभी संपत्तियों का बैकअप लेने के बाद ही करें। इस प्रक्रिया में कुछ मिनट लग सकते हैं।", - "check_logs": "लॉग जांचें", - "choose_matching_people_to_merge": "मर्ज करने के लिए मिलते-जुलते लोगों को चुनें", - "city": "शहर", - "clear": "स्पष्ट", - "clear_all": "सभी साफ करें", - "clear_all_recent_searches": "सभी हालिया खोजें साफ़ करें", - "clear_file_cache": "फ़ाइल कैश साफ़ करें", - "clear_message": "स्पष्ट संदेश", - "clear_value": "स्पष्ट मूल्य", - "client_cert_dialog_msg_confirm": "ठीक", - "client_cert_enter_password": "पास वर्ड दर्ज करें", - "client_cert_import": "आयात", - "client_cert_import_success_msg": "क्लाइंट प्रमाणपत्र आयात किया गया है", - "client_cert_invalid_msg": "अमान्य प्रमाणपत्र फ़ाइल या गलत पासवर्ड", - "client_cert_remove_msg": "क्लाइंट प्रमाणपत्र हटा दिया गया है", - "client_cert_subtitle": "केवल PKCS12 (.p12, .pfx) प्रारूप का समर्थन करता है। प्रमाणपत्र आयात/निकालना केवल लॉगिन से पहले ही उपलब्ध है।", - "client_cert_title": "SSL क्लाइंट प्रमाणपत्र [प्रायोगिक]", - "clockwise": "दक्षिणावर्त", - "close": "बंद करें", - "collapse": "गिर जाना", - "collapse_all": "सभी को संकुचित करें", - "color": "रंग", - "color_theme": "रंग थीम", - "comment_deleted": "टिप्पणी हटा दी गई", - "comment_options": "टिप्पणी विकल्प", - "comments_and_likes": "टिप्पणियाँ और पसंद", - "comments_are_disabled": "टिप्पणियाँ अक्षम हैं", - "common_create_new_album": "नया एल्बम बनाएँ", - "completed": "पूरित", - "confirm": "पुष्टि करें", - "confirm_admin_password": "एडमिन पासवर्ड की पुष्टि करें", - "confirm_delete_face": "क्या आप वाकई एसेट से {name} चेहरा हटाना चाहते हैं?", - "confirm_delete_shared_link": "क्या आप वाकई इस साझा लिंक को हटाना चाहते हैं?", - "confirm_keep_this_delete_others": "इस एसेट को छोड़कर, स्टैक की सभी अन्य एसेट हटा दी जाएँगी। क्या आप वाकई जारी रखना चाहते हैं?", - "confirm_new_pin_code": "नए पिन कोड की पुष्टि करें", - "confirm_password": "पासवर्ड की पुष्टि कीजिये", - "confirm_tag_face": "क्या आप इस चेहरे को {name} के रूप में टैग करना चाहते हैं?", - "confirm_tag_face_unnamed": "क्या आप इस चेहरे को टैग करना चाहते हैं?", - "connected_device": "योजित यंत्र", - "connected_to": "से जुड़ा", - "contain": "समाहित", - "context": "संदर्भ", - "continue": "जारी", - "control_bottom_app_bar_create_new_album": "नया एल्बम बनाएँ", - "control_bottom_app_bar_delete_from_immich": "इम्मिच से हटाएं", - "control_bottom_app_bar_delete_from_local": "डिवाइस से हटाएं", - "control_bottom_app_bar_edit_location": "स्थान संपादित करें", - "control_bottom_app_bar_edit_time": "तारीख और समय संपादित करें", - "control_bottom_app_bar_share_link": "लिंक शेयर करें", - "control_bottom_app_bar_share_to": "साझा करें", - "control_bottom_app_bar_trash_from_immich": "ट्रैश में ले जाएं", - "copied_image_to_clipboard": "छवि को क्लिपबोर्ड पर कॉपी किया गया।", - "copied_to_clipboard": "क्लिपबोर्ड पर नकल!", - "copy_error": "प्रतिलिपि त्रुटि", - "copy_file_path": "फ़ाइल पथ कॉपी करें", - "copy_image": "नकल छवि", - "copy_link": "लिंक की प्रतिलिपि करें", - "copy_link_to_clipboard": "लिंक को क्लिपबोर्ड पर कॉपी करें", - "copy_password": "पासवर्ड कॉपी करें", - "copy_to_clipboard": "क्लिपबोर्ड पर कॉपी करें", - "country": "देश", - "cover": "पूर्ण आवरण", - "covers": "आवरण", - "create": "तैयार करें", - "create_album": "एल्बम बनाओ", - "create_album_page_untitled": "शीर्षकहीन", - "create_api_key": "ऐ.पी.आई. चाभी बनाएं", - "create_library": "लाइब्रेरी बनाएं", - "create_link": "लिंक बनाएं", - "create_link_to_share": "शेयर करने के लिए लिंक बनाएं", - "create_link_to_share_description": "लिंक वाले किसी भी व्यक्ति को चयनित फ़ोटो देखने दें", - "create_new": "नया बनाएं", - "create_new_person": "नया व्यक्ति बनाएं", - "create_new_person_hint": "चयनित संपत्तियों को एक नए व्यक्ति को सौंपें", - "create_new_user": "नया उपयोगकर्ता बनाएं", - "create_shared_album_page_share_add_assets": "संपत्ति जोड़ें", - "create_shared_album_page_share_select_photos": "फ़ोटो चुनें", - "create_shared_link": "शेयर लिंक बनाएँ", - "create_tag": "टैग बनाएँ", - "create_tag_description": "एक नया टैग बनाएँ। नेस्टेड टैग के लिए, कृपया फ़ॉरवर्ड स्लैश सहित टैग का पूरा पथ दर्ज करें।", - "create_user": "उपयोगकर्ता बनाइये", - "created": "बनाया", - "created_at": "बनाया था", - "creating_linked_albums": "जुड़े हुए एल्बम बनाए जा रहे हैं..।", - "crop": "छाँटें", - "curated_object_page_title": "चीज़ें", - "current_device": "वर्तमान उपकरण", - "current_pin_code": "वर्तमान पिन कोड", - "current_server_address": "वर्तमान सर्वर पता", - "custom_locale": "कस्टम लोकेल", - "custom_locale_description": "भाषा और क्षेत्र के आधार पर दिनांक और संख्याएँ प्रारूपित करें", - "custom_url": "कस्टम URL", - "daily_title_text_date": "ई, एमएमएम डीडी", - "daily_title_text_date_year": "ई, एमएमएम दिन, वर्ष", - "dark": "डार्क", - "dark_theme": "डार्क थीम टॉगल करें", - "date": "दिनांक", - "date_after": "इसके बाद की तारीख", - "date_and_time": "तिथि और समय", - "date_before": "पहले की तारीख", - "date_format": "ई, एलएलएल डी, वाई • एच:एमएम ए", - "date_of_birth_saved": "जन्मतिथि सफलतापूर्वक सहेजी गई", - "date_range": "तिथि सीमा", - "day": "दिन", - "days": "दिन", - "deduplicate_all": "सभी को डुप्लिकेट करें", - "deduplication_criteria_1": "छवि का आकार बाइट्स में", - "deduplication_criteria_2": "EXIF डेटा की संख्या", - "deduplication_info": "डुप्लीकेशन हटाने की जानकारी", - "deduplication_info_description": "परिसंपत्तियों का स्वचालित रूप से पूर्व-चयन करने और डुप्लिकेट को थोक में हटाने के लिए, हम निम्न पर ध्यान देते हैं:", - "default_locale": "डिफ़ॉल्ट स्थान", - "default_locale_description": "अपने ब्राउज़र स्थान के आधार पर दिनांक और संख्याएँ प्रारूपित करें", - "delete": "हटाएँ", - "delete_action_confirmation_message": "क्या आप वाकई इस आइटम को हटाना चाहते हैं? यह कार्रवाई आइटम को सर्वर की ट्रैश में ले जाएगी और स्थानीय रूप से हटाने के लिए पुष्टि मांगेगी", - "delete_action_prompt": "{count} हटाए गए", - "delete_album": "एल्बम हटाएँ", - "delete_api_key_prompt": "क्या आप वाकई इस एपीआई कुंजी को हटाना चाहते हैं?", - "delete_dialog_alert": "ये आइटम Immich और आपके डिवाइस से स्थायी रूप से हटा दिए जाएंगे", - "delete_dialog_alert_local": "ये आइटम आपके डिवाइस से स्थायी रूप से हटा दिए जाएंगे, लेकिन फिर भी Immich सर्वर पर उपलब्ध रहेंगे", - "delete_dialog_alert_local_non_backed_up": "कुछ आइटम का Immich में बैकअप नहीं लिया गया है और उन्हें आपके डिवाइस से स्थायी रूप से हटा दिया जाएगा", - "delete_dialog_alert_remote": "ये आइटम Immich सर्वर से स्थायी रूप से हटा दिए जाएंगे", - "delete_dialog_ok_force": "फिर भी हटाएं", - "delete_dialog_title": "स्थायी रूप से हटाएँ", - "delete_duplicates_confirmation": "क्या आप वाकई इन डुप्लिकेट को स्थायी रूप से हटाना चाहते हैं?", - "delete_face": "चेहरा हटाएं", - "delete_key": "कुंजी हटाएँ", - "delete_library": "लाइब्रेरी हटाएँ", - "delete_link": "लिंक हटाएँ", - "delete_local_action_prompt": "{count} स्थानीय रूप से हटा दिया गया", - "delete_local_dialog_ok_backed_up_only": "केवल बैकअप हटाएं", - "delete_local_dialog_ok_force": "फिर भी हटाएं", - "delete_others": "अन्य को हटाएँ", - "delete_permanently": "स्थायी रूप से हटाएँ", - "delete_permanently_action_prompt": "{count} स्थायी रूप से हटाए गए", - "delete_shared_link": "साझा किए गए लिंक को हटाएं", - "delete_shared_link_dialog_title": "साझा किए गए लिंक को हटाएं", - "delete_tag": "टैग हटाएं", - "delete_tag_confirmation_prompt": "क्या आप वाकई {tagName} टैग हटाना चाहते हैं?", - "delete_user": "उपभोक्ता मिटायें", - "deleted_shared_link": "साझा किया गया लिंक हटा दिया गया", - "deletes_missing_assets": "डिस्क से गायब संपत्तियों को हटाता है", - "description": "वर्णन", - "description_input_hint_text": "विवरण जोड़ें..।", - "description_input_submit_error": "विवरण अपडेट करते समय त्रुटि हुई, अधिक जानकारी के लिए लॉग देखें", - "deselect_all": "सबको अचयनित करो", - "details": "विवरण", - "direction": "दिशा", - "disabled": "अक्षम", - "disallow_edits": "संपादनों की अनुमति न दें", - "discord": "डिसकॉर्ड", - "discover": "खोजें", - "discovered_devices": "खोजे गए उपकरण", - "dismiss_all_errors": "सभी त्रुटियाँ ख़ारिज करें", - "dismiss_error": "त्रुटि ख़ारिज करें", - "display_options": "प्रदर्शन चुनाव", - "display_order": "आदेश को प्रदर्शित करें", - "display_original_photos": "मूल फ़ोटो प्रदर्शित करें", - "display_original_photos_setting_description": "किसी संपत्ति को देखते समय थंबनेल के बजाय मूल तस्वीर प्रदर्शित करना पसंद करें जब मूल संपत्ति वेब-संगत हो।", - "do_not_show_again": "इस संदेश को दुबारा मत दिखाना", - "documentation": "प्रलेखन", - "done": "ठीक है", - "download": "डाउनलोड करें", - "download_action_prompt": "{count} संपत्तियां डाउनलोड हो रही हैं", - "download_canceled": "डाउनलोड रद्द कर दिया गया", - "download_complete": "डाउनलोड पूरा", - "download_enqueue": "डाउनलोड कतार में है", - "download_error": "डाउनलोड त्रुटि", - "download_failed": "डाउनलोड विफल", - "download_finished": "डाउनलोड समाप्त", - "download_include_embedded_motion_videos": "एम्बेडेड वीडियो", - "download_include_embedded_motion_videos_description": "मोशन फ़ोटो में एम्बेड किए गए वीडियो को एक अलग फ़ाइल के रूप में शामिल करें", - "download_notfound": "डाउनलोड नहीं मिला", - "download_paused": "डाउनलोड स्थगित", - "download_settings": "डाउनलोड करना", - "download_settings_description": "संपत्ति डाउनलोड से संबंधित सेटिंग्स प्रबंधित करें", - "download_started": "डाउनलोड प्रारंभ हुआ", - "download_sucess": "डाउनलोड सफल", - "download_sucess_android": "मीडिया DCIM/Immich में डाउनलोड हो गया है", - "download_waiting_to_retry": "पुनः प्रयास करने का इंतजार कर रहा है", - "downloading": "डाउनलोड", - "downloading_asset_filename": "संपत्ति {filename} डाउनलोड हो रही है", - "downloading_media": "मीडिया डाउनलोड हो रहा है", - "drop_files_to_upload": "अपलोड करने के लिए फ़ाइलें कहीं भी छोड़ें", - "duplicates": "डुप्लिकेट", - "duplicates_description": "प्रत्येक समूह को यह इंगित करके हल करें कि कौन सा, यदि कोई है, डुप्लिकेट है", - "duration": "अवधि", - "edit": "संपादन करना", - "edit_album": "एल्बम संपादित करें", - "edit_avatar": "अवतार को एडिट करें", - "edit_birthday": "जन्मदिन बदलें", - "edit_date": "संपादन की तारीख", - "edit_date_and_time": "दिनांक और समय संपादित करें", - "edit_date_and_time_action_prompt": "{count} तारीख और समय संपादित किए गए", - "edit_date_and_time_by_offset": "अंकुर से दिनांक बदलें", - "edit_date_and_time_by_offset_interval": "नयी दिनांक सीमा: {from} - {to}", - "edit_description": "संपादित करें वर्णन", - "edit_description_prompt": "कृपया एक नया विवरण चुनें:", - "edit_exclusion_pattern": "बहिष्करण पैटर्न संपादित करें", - "edit_faces": "चेहरे संपादित करें", - "edit_key": "कुंजी संपादित करें", - "edit_link": "लिंक संपादित करें", - "edit_location": "स्थान संपादित करें", - "edit_location_action_prompt": "{count} स्थान संपादित किए गए", - "edit_location_dialog_title": "स्थान", - "edit_name": "नाम संपादित करें", - "edit_people": "लोगों को संपादित करें", - "edit_tag": "टैग बदलें", - "edit_title": "शीर्षक संपादित करें", - "edit_user": "यूजर को संपादित करो", - "editor": "संपादक", - "editor_close_without_save_prompt": "परिवर्तन सहेजे नहीं जाएँगे", - "editor_close_without_save_title": "संपादक बंद करें?", - "email": "ईमेल", - "email_notifications": "ईमेल सूचनाएँ", - "empty_folder": "यह फ़ोल्डर खाली है", - "empty_trash": "कूड़ेदान खाली करें", - "empty_trash_confirmation": "क्या आपको यकीन है कि आप कचरा खाली करना चाहते हैं? यह इमिच से स्थायी रूप से कचरा में सभी संपत्तियों को हटा देगा।\nआप इस कार्रवाई को नहीं रोक सकते!", - "enable": "सक्षम", - "enable_backup": "बैकअप चालू करें", - "enable_biometric_auth_description": "बायोमेट्रिक प्रमाणीकरण सक्षम करने के लिए अपना पिन कोड दर्ज करें", - "enabled": "सक्रिय", - "end_date": "अंतिम तिथि", - "enqueued": "कतार में जोड़ा गया", - "enter_wifi_name": "Wi-Fi का नाम लिखें", - "enter_your_pin_code": "अपना पिन कोड डालें", - "enter_your_pin_code_subtitle": "लॉक फ़ोल्डर खोलने के लिए पिन कोड डालें", - "error": "गलती", - "error_change_sort_album": "एल्बम का क्रम बदलने में असफल रहा", - "error_delete_face": "एसेट से चेहरे को हटाने में त्रुटि हुई", - "error_getting_places": "स्थानों को प्राप्त करने में त्रुटि हुई", - "error_loading_image": "छवि लोड करने में त्रुटि", - "error_loading_partners": "जोड़ीदार लोड करने में त्रुटि हुई: {error}", - "error_saving_image": "त्रुटि: {error}", - "error_tag_face_bounding_box": "चेहरे को टैग करने में त्रुटि – बाउंडिंग बॉक्स निर्देशांक प्राप्त नहीं कर सके", - "error_title": "त्रुटि - कुछ गलत हो गया", - "errors": { - "cannot_navigate_next_asset": "अगली संपत्ति पर नेविगेट नहीं किया जा सकता", - "cannot_navigate_previous_asset": "पिछली संपत्ति पर नेविगेट नहीं किया जा सकता", - "cant_apply_changes": "परिवर्तन लागू नहीं कर सकते", - "cant_change_activity": "गतिविधि को {enabled, select, true {अक्षम} other {सक्षम}} नहीं कर सकते", - "cant_change_asset_favorite": "संपत्ति के लिए पसंदीदा नहीं बदला जा सकता", - "cant_change_metadata_assets_count": "{count, plural, one {# आइटम} other {# आइटम्स}} के मेटाडेटा को बदल नहीं सकते", - "cant_get_faces": "चेहरे नहीं मिल सके", - "cant_get_number_of_comments": "टिप्पणियों की संख्या नहीं मिल सकी", - "cant_search_people": "लोगों को खोजा नहीं जा सकता", - "cant_search_places": "स्थान खोज नहीं सकते", - "error_adding_assets_to_album": "एल्बम में संपत्ति डालने में त्रुटि", - "error_adding_users_to_album": "एल्बम में उपयोगकर्ताओं को डालने में त्रुटि", - "error_deleting_shared_user": "साझा उपयोगकर्ता को हटाने में त्रुटि", - "error_downloading": "{filename} डाउनलोड नहीं हो पाया", - "error_hiding_buy_button": "खरीदें बटन छिपाने में त्रुटि", - "error_removing_assets_from_album": "एल्बम से संपत्तियों को हटाने में त्रुटि, अधिक विवरण के लिए कंसोल की जाँच करें", - "error_selecting_all_assets": "सभी परिसंपत्तियों का चयन करने में त्रुटि", - "exclusion_pattern_already_exists": "यह बहिष्करण पैटर्न पहले से मौजूद है।", - "failed_to_create_album": "एल्बम बनाने में विफल", - "failed_to_create_shared_link": "साझा लिंक बनाने में विफल", - "failed_to_edit_shared_link": "साझा लिंक संपादित करने में विफल", - "failed_to_get_people": "लोगों को पाने में विफल", - "failed_to_keep_this_delete_others": "इस आइटम को रखने और बाकी को हटाने में असफल रहा", - "failed_to_load_asset": "परिसंपत्ति लोड करने में विफल", - "failed_to_load_assets": "परिसंपत्तियाँ लोड करने में विफल", - "failed_to_load_notifications": "सूचनाएँ लोड करने में विफल", - "failed_to_load_people": "लोगों को लोड करने में विफल", - "failed_to_remove_product_key": "उत्पाद कुंजी निकालने में विफल", - "failed_to_reset_pin_code": "पिन कोड रीसेट करना विफल हुआ", - "failed_to_stack_assets": "परिसंपत्तियों का ढेर लगाने में विफल", - "failed_to_unstack_assets": "परिसंपत्तियों का ढेर खोलने में विफल", - "failed_to_update_notification_status": "सूचना की स्थिति अपडेट करने में विफल", - "incorrect_email_or_password": "गलत ईमेल या पासवर्ड", - "library_folder_already_exists": "यह इंपोर्ट पाथ पहले से मौजूद है।", - "paths_validation_failed": "{paths, plural, one {# पथ} other {# पथ}} सत्यापन में विफल रहे", - "profile_picture_transparent_pixels": "प्रोफ़ाइल चित्रों में पारदर्शी पिक्सेल नहीं हो सकते।", - "quota_higher_than_disk_size": "आपने डिस्क आकार से अधिक कोटा निर्धारित किया है", - "something_went_wrong": "कुछ त्रुटि हुई", - "unable_to_add_album_users": "उपयोगकर्ताओं को एल्बम में डालने में असमर्थ", - "unable_to_add_assets_to_shared_link": "साझा लिंक में संपत्ति डालने में असमर्थ", - "unable_to_add_comment": "टिप्पणी डालने में असमर्थ", - "unable_to_add_exclusion_pattern": "बहिष्करण पैटर्न डालने में असमर्थ", - "unable_to_add_partners": "साझेदार डालने में असमर्थ", - "unable_to_add_remove_archive": "{archived, select, true {एसेट को संग्रह से हटाने में असमर्थ} other {एसेट को संग्रह में जोड़ने में असमर्थ}}", - "unable_to_add_remove_favorites": "{favorite, select, true {एसेट को पसंदीदा में जोड़ने में असमर्थ} other {एसेट को पसंदीदा से हटाने में असमर्थ}}", - "unable_to_archive_unarchive": "{archived, select, true {संग्रहित करने में असमर्थ} other {संग्रह से हटाने में असमर्थ}}", - "unable_to_change_album_user_role": "एल्बम उपयोगकर्ता की भूमिका बदलने में असमर्थ", - "unable_to_change_date": "दिनांक बदलने में असमर्थ", - "unable_to_change_description": "विवरण बदलने में असमर्थ", - "unable_to_change_favorite": "संपत्ति के लिए पसंदीदा बदलने में असमर्थ", - "unable_to_change_location": "स्थान बदलने में असमर्थ", - "unable_to_change_password": "पासवर्ड बदलने में असमर्थ", - "unable_to_change_visibility": "{count, plural, one {# व्यक्ति} other {# लोग}} की दृश्यता बदलने में असमर्थ", - "unable_to_complete_oauth_login": "OAuth लॉगिन पूर्ण करने में असमर्थ", - "unable_to_connect": "कनेक्ट करने में असमर्थ", - "unable_to_copy_to_clipboard": "क्लिपबोर्ड पर कॉपी नहीं किया जा सकता, सुनिश्चित करें कि आप https के माध्यम से पेज तक पहुंच रहे हैं", - "unable_to_create_admin_account": "व्यवस्थापक खाता बनाने में असमर्थ", - "unable_to_create_api_key": "नई API कुंजी बनाने में असमर्थ", - "unable_to_create_library": "लाइब्रेरी बनाने में असमर्थ", - "unable_to_create_user": "उपयोगकर्ता बनाने में असमर्थ", - "unable_to_delete_album": "एल्बम हटाने में असमर्थ", - "unable_to_delete_asset": "संपत्ति हटाने में असमर्थ", - "unable_to_delete_assets": "संपत्तियों को हटाने में त्रुटि", - "unable_to_delete_exclusion_pattern": "बहिष्करण पैटर्न को हटाने में असमर्थ", - "unable_to_delete_shared_link": "साझा लिंक हटाने में असमर्थ", - "unable_to_delete_user": "उपयोगकर्ता को हटाने में असमर्थ", - "unable_to_download_files": "फ़ाइलें डाउनलोड करने में असमर्थ", - "unable_to_edit_exclusion_pattern": "बहिष्करण पैटर्न संपादित करने में असमर्थ", - "unable_to_empty_trash": "कचरा खाली करने में असमर्थ", - "unable_to_enter_fullscreen": "फ़ुलस्क्रीन दर्ज करने में असमर्थ", - "unable_to_exit_fullscreen": "फ़ुलस्क्रीन से बाहर निकलने में असमर्थ", - "unable_to_get_comments_number": "टिप्पणियों की संख्या प्राप्त करने में असमर्थ", - "unable_to_get_shared_link": "साझा लिंक प्राप्त करने में विफल", - "unable_to_hide_person": "व्यक्ति को छुपाने में असमर्थ", - "unable_to_link_motion_video": "मोशन वीडियो लिंक करने में असमर्थ", - "unable_to_link_oauth_account": "OAuth खाता लिंक करने में असमर्थ", - "unable_to_log_out_all_devices": "सभी डिवाइसों को लॉग आउट करने में असमर्थ", - "unable_to_log_out_device": "डिवाइस लॉग आउट करने में असमर्थ", - "unable_to_login_with_oauth": "OAuth से लॉगिन करने में असमर्थ", - "unable_to_play_video": "वीडियो चलाने में असमर्थ", - "unable_to_reassign_assets_existing_person": "{ name, select, null {एसेट्स को एक मौजूदा व्यक्ति को पुनः असाइन करने में असमर्थ} other {एसेट्स को {name} को पुनः असाइन करने में असमर्थ} }", - "unable_to_reassign_assets_new_person": "किसी नये व्यक्ति को संपत्ति पुनः सौंपने में असमर्थ", - "unable_to_refresh_user": "उपयोगकर्ता को ताज़ा करने में असमर्थ", - "unable_to_remove_album_users": "उपयोगकर्ताओं को एल्बम से निकालने में असमर्थ", - "unable_to_remove_api_key": "API कुंजी निकालने में असमर्थ", - "unable_to_remove_assets_from_shared_link": "साझा लिंक से संपत्तियों को निकालने में असमर्थ", - "unable_to_remove_library": "लाइब्रेरी हटाने में असमर्थ", - "unable_to_remove_partner": "पार्टनर को हटाने में असमर्थ", - "unable_to_remove_reaction": "प्रतिक्रिया निकालने में असमर्थ", - "unable_to_reset_password": "पासवर्ड रीसेट करने में असमर्थ", - "unable_to_reset_pin_code": "पिन कोड रीसेट करने में असमर्थ", - "unable_to_resolve_duplicate": "डुप्लिकेट का समाधान करने में असमर्थ", - "unable_to_restore_assets": "संपत्तियों को पुनर्स्थापित करने में असमर्थ", - "unable_to_restore_trash": "कचरा पुनर्स्थापित करने में असमर्थ", - "unable_to_restore_user": "उपयोगकर्ता को पुनर्स्थापित करने में असमर्थ", - "unable_to_save_album": "एल्बम सहेजने में असमर्थ", - "unable_to_save_api_key": "एपीआई कुंजी सहेजने में असमर्थ", - "unable_to_save_date_of_birth": "जन्मतिथि सहेजने में असमर्थ", - "unable_to_save_name": "नाम सहेजने में असमर्थ", - "unable_to_save_profile": "प्रोफ़ाइल सहेजने में असमर्थ", - "unable_to_save_settings": "सेटिंग्स सहेजने में असमर्थ", - "unable_to_scan_libraries": "पुस्तकालयों को स्कैन करने में असमर्थ", - "unable_to_scan_library": "लाइब्रेरी स्कैन करने में असमर्थ", - "unable_to_set_feature_photo": "फ़ीचर फ़ोटो सेट करने में असमर्थ", - "unable_to_set_profile_picture": "प्रोफ़ाइल चित्र सेट करने में असमर्थ", - "unable_to_submit_job": "कार्य प्रस्तुत करने में असमर्थ", - "unable_to_trash_asset": "संपत्ति को ट्रैश करने में असमर्थ", - "unable_to_unlink_account": "खाता अनलिंक करने में असमर्थ", - "unable_to_unlink_motion_video": "मोशन वीडियो अनलिंक करने में असमर्थ", - "unable_to_update_album_cover": "एल्बम कवर अपडेट करने में असमर्थ", - "unable_to_update_album_info": "एल्बम जानकारी अद्यतन करने में असमर्थ", - "unable_to_update_library": "लाइब्रेरी अद्यतन करने में असमर्थ", - "unable_to_update_location": "स्थान अद्यतन करने में असमर्थ", - "unable_to_update_settings": "सेटिंग्स अपडेट करने में असमर्थ", - "unable_to_update_timeline_display_status": "समयरेखा प्रदर्शन स्थिति अद्यतन करने में असमर्थ", - "unable_to_update_user": "उपयोगकर्ता को अद्यतन करने में असमर्थ", - "unable_to_upload_file": "फाइल अपलोड करने में असमर्थ" - }, - "exclusion_pattern": "बहिष्करण पैटर्न", - "exif": "एक्सिफ", - "exif_bottom_sheet_description": "विवरण जोड़ें..।", - "exif_bottom_sheet_description_error": "विवरण के आधुनीकरण करने में त्रुटि हुई", - "exif_bottom_sheet_details": "विवरण", - "exif_bottom_sheet_location": "स्थान", - "exif_bottom_sheet_no_description": "कोई विवरण नहीं", - "exif_bottom_sheet_people": "लोग", - "exif_bottom_sheet_person_add_person": "नाम डालें", - "exit_slideshow": "स्लाइड शो से बाहर निकलें", - "expand_all": "सभी का विस्तार", - "experimental_settings_new_asset_list_subtitle": "कार्य प्रगति पर है", - "experimental_settings_new_asset_list_title": "प्रयोगात्मक फोटो ग्रिड सक्षम करें", - "experimental_settings_subtitle": "अपने जोखिम पर उपयोग करें!", - "experimental_settings_title": "प्रयोगात्मक", - "expire_after": "एक्सपायर आफ्टर", - "expired": "खत्म हो चुका", - "expires_date": "{date} को समाप्त हो रहा है", - "explore": "अन्वेषण करना", - "explorer": "समन्वेषक", - "export": "निर्यात", - "export_as_json": "JSON के रूप में निर्यात करें", - "export_database": "डेटाबेस निर्यात करें", - "export_database_description": "इस.क्यू.लाइट डेटाबेस निर्यात करें", - "extension": "विस्तार", - "external": "बाहरी", - "external_libraries": "बाहरी पुस्तकालय", - "external_network": "बाहरी नेटवर्क", - "external_network_sheet_info": "जब पसंदीदा वाई-फाई नेटवर्क पर नहीं होगा, तो ऐप नीचे दिए गए यूआरएल में से पहले के माध्यम से सर्वर से कनेक्ट होगा, ऊपर से नीचे तक शुरू करते हुए", - "face_unassigned": "सौंपे नहीं गए", - "failed": "विफल हुआ", - "failed_to_authenticate": "प्रमाणित करने में विफल", - "failed_to_load_assets": "एसेट लोड करने में विफल", - "failed_to_load_folder": "फोल्डर लोड करने में विफल", - "favorite": "पसंदीदा", - "favorite_action_prompt": "{count} पसंदीदा संकलन में जोड़े गए", - "favorite_or_unfavorite_photo": "पसंदीदा या नापसंद फोटो", - "favorites": "पसंदीदा", - "favorites_page_no_favorites": "कोई पसंदीदा एसेट नहीं मिले", - "feature_photo_updated": "फ़ीचर फ़ोटो अपडेट किया गया", - "features": "विशेषताएँ", - "features_in_development": "विकास में सुविधाएँ", - "features_setting_description": "ऐप सुविधाओं का प्रबंधन करें", - "file_name_or_extension": "फ़ाइल का नाम या एक्सटेंशन", - "file_size": "फ़ाइल का साइज़", - "filename": "फ़ाइल का नाम", - "filetype": "फाइल का प्रकार", - "filter": "फ़िल्टर", - "filter_people": "लोगों को फ़िल्टर करें", - "filter_places": "स्थानों को फ़िल्टर करें", - "find_them_fast": "खोज के साथ नाम से उन्हें तेजी से ढूंढें", - "first": "पहला", - "fix_incorrect_match": "ग़लत मिलान ठीक करें", - "folder": "फ़ोल्डर", - "folder_not_found": "फ़ोल्डर नहीं मिला", - "folders": "फ़ोल्डर", - "folders_feature_description": "फ़ाइल सिस्टम पर फ़ोटो और वीडियो के लिए फ़ोल्डर दृश्य ब्राउज़ करना", - "forgot_pin_code_question": "अपना पिन भूल गए?", - "forward": "आगे", - "full_path": "पूरा पथ: {path}", - "gcast_enabled": "गूगल कास्ट", - "gcast_enabled_description": "यह सुविधा काम करने के लिए गूगल से बाह्य संसाधन लोड करती है।", - "general": "सामान्य", - "geolocation_instruction_location": "किसी परिसंपत्ति के स्थान का उपयोग करने के लिए GPS निर्देशांक वाली परिसंपत्ति पर क्लिक करें, या सीधे मानचित्र से कोई स्थान चुनें", - "get_help": "मदद लें", - "get_wifiname_error": "वाई-फ़ाई नाम नहीं मिल सका। सुनिश्चित करें कि आपने आवश्यक अनुमतियाँ दे दी हैं और वाई-फ़ाई नेटवर्क से कनेक्ट हैं", - "getting_started": "शुरू करना", - "go_back": "वापस जाओ", - "go_to_folder": "फ़ोल्डर पर जाएँ", - "go_to_search": "खोज पर जाएँ", - "gps": "GPS", - "gps_missing": "कोई जीपीएस नहीं", - "grant_permission": "अनुमति प्रदान करें", - "group_albums_by": "इनके द्वारा समूह एल्बम..।", - "group_country": "देश के अनुसार समूह", - "group_no": "कोई समूहीकरण नहीं", - "group_owner": "स्वामी द्वारा समूह", - "group_places_by": "स्थानों को समूहबद्ध करें..।", - "group_year": "वर्ष के अनुसार समूह", - "haptic_feedback_switch": "स्पर्श प्रतिक्रिया सक्षम करें", - "haptic_feedback_title": "हैप्टिक राय", - "has_quota": "कोटा है", - "hash_asset": "हैश परिसंपत्ति", - "hashed_assets": "हैश की गई संपत्तियां", - "hashing": "हैशिंग", - "header_settings_add_header_tip": "हेडर जोड़ें", - "header_settings_field_validator_msg": "मान रिक्त नहीं हो सकता", - "header_settings_header_name_input": "हेडर का नाम", - "header_settings_header_value_input": "हेडर मान", - "headers_settings_tile_title": "कस्टम प्रॉक्सी हेडर", - "hi_user": "नमस्ते {name} ({email})", - "hide_all_people": "सभी लोगों को छुपाएं", - "hide_gallery": "गैलरी छिपाएँ", - "hide_named_person": "व्यक्ति को छिपाएँ {name}", - "hide_password": "पासवर्ड छिपाएं", - "hide_person": "व्यक्ति छिपाएँ", - "hide_text_recognition": "टेक्स्ट पहचान छिपाएँ", - "hide_unnamed_people": "अनाम लोगों को छुपाएं", - "home_page_add_to_album_conflicts": "{added} संपत्तियां एल्बम {album} में जोड़ी गईं. {failed} संपत्तियां पहले से ही एल्बम में हैं।", - "home_page_add_to_album_err_local": "अभी तक एल्बम में स्थानीय संपत्तियां नहीं जोड़ी जा सकीं, छोड़ दिया गया", - "home_page_add_to_album_success": "एल्बम {album} में {added} संपत्तियां जोड़ी गईं।", - "home_page_album_err_partner": "अभी पार्टनर एसेट्स को एल्बम में डाल नहीं सकते, स्किप कर रहे हैं", - "home_page_archive_err_local": "स्थानीय संपत्तियों को अभी संग्रहीत नहीं किया जा सकता, छोड़ा जा रहा है", - "home_page_archive_err_partner": "पार्टनर एसेट्स को आर्काइव नहीं कर सकते, स्किप कर रहे हैं", - "home_page_building_timeline": "समयरेखा का निर्माण", - "home_page_delete_err_partner": "पार्टनर एसेट्स को डिलीट नहीं कर सकते, स्किप कर रहे हैं", - "home_page_delete_remote_err_local": "दूरस्थ चयन को हटाने, छोड़ने में स्थानीय संपत्तियाँ", - "home_page_favorite_err_local": "स्थानीय संपत्तियों को अभी तक पसंदीदा नहीं बनाया जा सका, छोड़ा जा रहा है", - "home_page_favorite_err_partner": "अब तक पार्टनर एसेट्स को फेवरेट नहीं कर सकते, स्किप कर रहे हैं", - "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).", - "home_page_locked_error_local": "स्थानीय संपत्तियों को लॉक किए गए फ़ोल्डर में नहीं ले जाया जा सकता, छोड़ा जा सकता है", - "home_page_locked_error_partner": "साझेदार संपत्तियों को लॉक किए गए फ़ोल्डर में नहीं ले जाया जा सकता, छोड़ें", - "home_page_share_err_local": "लोकल एसेट्स को लिंक के जरिए शेयर नहीं कर सकते, स्किप कर रहे हैं", - "home_page_upload_err_limit": "एक समय में अधिकतम 30 संपत्तियां ही अपलोड की जा सकती हैं, छोड़कर", - "host": "मेज़बान", - "hour": "घंटा", - "hours": "घंटे", - "id": "पहचान", - "idle": "निष्क्रिय", - "ignore_icloud_photos": "आइक्लाउड फ़ोटो को अनदेखा करें", - "ignore_icloud_photos_description": "आइक्लाउड पर स्टोर की गई फ़ोटोज़ इमिच सर्वर पर अपलोड नहीं की जाएंगी", - "image": "छवि", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} {date} को लिया गया", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} {person1} के साथ {date} को लिया गया", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} {person1} और {person2} के साथ {date} को लिया गया", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} {person1}, {person2}, और {person3} के साथ {date} को लिया गया", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} {person1}, {person2}, other {additionalCount, number} अन्य लोगों के साथ {date} को लिया गया", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} {city}, {country} में {date} को लिया गया", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} {city}, {country} में {person1} के साथ {date} को लिया गया", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} में {person1} और {person2} के साथ {date} को लिया गया", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} में {person1}, {person2}, और {person3} के साथ {date} को लिया गया", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} में {person1}, {person2}, और {additionalCount, number} अन्य लोगों के साथ {date} को लिया गया", - "image_saved_successfully": "इमेज सहेज दी गई", - "image_viewer_page_state_provider_download_started": "डाउनलोड शुरू", - "image_viewer_page_state_provider_download_success": "डाउनलोड सफल", - "image_viewer_page_state_provider_share_error": "साझा करने में त्रुटि", - "immich_logo": "Immich लोगो", - "immich_web_interface": "इमिच वेब इंटरफ़ेस", - "import_from_json": "JSON से आयात करें", - "import_path": "आयात पथ", - "in_albums": "{count, plural, one {# album} other {# albums}} में", - "in_archive": "पुरालेख में", - "in_year": "{year} में", - "in_year_selector": "में", - "include_archived": "संग्रहीत शामिल करें", - "include_shared_albums": "साझा किए गए एल्बम शामिल करें", - "include_shared_partner_assets": "साझा भागीदार संपत्तियां शामिल करें", - "individual_share": "व्यक्तिगत हिस्सेदारी", - "individual_shares": "व्यक्तिगत शेयर", - "info": "जानकारी", - "interval": { - "day_at_onepm": "हर दिन दोपहर 1 बजे", - "hours": "हर {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "हर रात आधी रात को", - "night_at_twoam": "हर रात 2 बजे" - }, - "invalid_date": "अमान्य तारीख़", - "invalid_date_format": "अमान्य तारीख़ प्रारूप", - "invite_people": "लोगो को निमंत्रण भेजो", - "invite_to_album": "एल्बम के लिए आमंत्रित करें", - "ios_debug_info_fetch_ran_at": "फ़ेच रन {dateTime}", - "ios_debug_info_last_sync_at": "अंतिम सिंक {dateTime}", - "ios_debug_info_no_processes_queued": "कोई पृष्ठभूमि प्रक्रिया कतारबद्ध नहीं है", - "ios_debug_info_no_sync_yet": "अभी तक कोई पृष्ठभूमि समन्वयन कार्य नहीं चलाया गया है", - "ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}", - "ios_debug_info_processing_ran_at": "प्रसंस्करण {dateTime} पर चला", - "items_count": "{count, plural, one {# item} other {# items}}", - "jobs": "नौकरियां", - "keep": "रखना", - "keep_all": "सभी रखना", - "keep_this_delete_others": "इसे रखें, अन्य को हटाएँ", - "kept_this_deleted_others": "इस संपत्ति को रखा गया और {count, plural, one {# asset} other {# assets}} को हटा दिया गया", - "keyboard_shortcuts": "कुंजीपटल अल्प मार्ग", - "language": "भाषा", - "language_no_results_subtitle": "अपने खोज शब्द को समायोजित करने का प्रयास करें", - "language_no_results_title": "कोई भाषा नहीं मिली", - "language_search_hint": "भाषाएं खोजें..।", - "language_setting_description": "अपनी पसंदीदा भाषा चुनें", - "large_files": "बड़ी फ़ाइलें", - "last": "अंतिम", - "last_months": "{count, plural, one {Last month} other {Last # months}}", - "last_seen": "अंतिम बार देखा गया", - "latest_version": "नवीनतम संस्करण", - "latitude": "अक्षांश", - "leave": "छुट्टी", - "leave_album": "एल्बम छोड़ें", - "lens_model": "लेंस मॉडल", - "let_others_respond": "दूसरों को जवाब देने दें", - "level": "स्तर", - "library": "पुस्तकालय", - "library_add_folder": "फ़ोल्डर जोड़ें", - "library_edit_folder": "फ़ोल्डर संपादित करें", - "library_options": "पुस्तकालय विकल्प", - "library_page_device_albums": "डिवाइस पर एल्बम", - "library_page_new_album": "नया एल्बम", - "library_page_sort_asset_count": "परिसंपत्तियों की संख्या", - "library_page_sort_created": "सृजित दिनांक", - "library_page_sort_last_modified": "अंतिम संशोधन", - "library_page_sort_title": "एल्बम का शीर्षक", - "licenses": "लाइसेंस", - "light": "रोशनी", - "like": "पसंद", - "like_deleted": "जैसे हटा दिया गया", - "link_motion_video": "लिंक मोशन वीडियो", - "link_to_oauth": "OAuth से लिंक करें", - "linked_oauth_account": "लिंक किया गया OAuth खाता", - "list": "सूची", - "loading": "लोड हो रहा है", - "loading_search_results_failed": "खोज परिणाम लोड करना विफल रहा", - "local": "स्थानीय", - "local_asset_cast_failed": "सर्वर पर अपलोड न की गई संपत्ति को कास्ट करने में असमर्थ", - "local_assets": "स्थानीय संपत्तियाँ", - "local_media_summary": "स्थानीय मीडिया सारांश", - "local_network": "स्थानीय नेटवर्क", - "local_network_sheet_info": "निर्दिष्ट वाई-फाई नेटवर्क का उपयोग करते समय ऐप इस URL के माध्यम से सर्वर से कनेक्ट होगा", - "location": "स्थान", - "location_permission": "स्थान की अनुमति", - "location_permission_content": "ऑटो-स्विचिंग सुविधा का उपयोग करने के लिए,Immich को सटीक स्थान अनुमति की आवश्यकता होती है ताकि वह वर्तमान वाई-फाई नेटवर्क का नाम पढ़ सके", - "location_picker_choose_on_map": "मानचित्र पर चुनें", - "location_picker_latitude_error": "मान्य अक्षांश दर्ज करें", - "location_picker_latitude_hint": "अपना अक्षांश यहां दर्ज करें", - "location_picker_longitude_error": "एक मान्य देशांतर दर्ज करें", - "location_picker_longitude_hint": "अपना देशांतर यहाँ दर्ज करें", - "lock": "ताला", - "locked_folder": "लॉक किया गया फ़ोल्डर", - "log_detail_title": "लॉग विवरण", - "log_out": "लॉग आउट", - "log_out_all_devices": "सभी डिवाइस लॉग आउट करें", - "logged_in_as": "{user} के रूप में लॉग इन किया गया", - "logged_out_all_devices": "सभी डिवाइस लॉग आउट कर दिए गए", - "logged_out_device": "लॉग आउट डिवाइस", - "login": "लॉग इन करें", - "login_disabled": "लॉगिन अक्षम कर दिया गया है", - "login_form_api_exception": "API अपवाद. कृपया सर्वर URL जाँचें और पुनः प्रयास करें।", - "login_form_back_button_text": "पीछे", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://आपका-सर्वर-आईपी:पोर्ट", - "login_form_endpoint_url": "सर्वर एंडपॉइंट URL", - "login_form_err_http": "कृपया http:// या https:// निर्दिष्ट करें", - "login_form_err_invalid_email": "अमान्य ईमेल", - "login_form_err_invalid_url": "असामान्य यूआरएल", - "login_form_err_leading_whitespace": "अग्रणी रिक्त स्थान", - "login_form_err_trailing_whitespace": "अनुगामी रिक्त स्थान", - "login_form_failed_get_oauth_server_config": "OAuth का उपयोग करते समय त्रुटि लॉगिंग, सर्वर URL की जाँच करें", - "login_form_failed_get_oauth_server_disable": "इस सर्वर पर OAuth सुविधा उपलब्ध नहीं है", - "login_form_failed_login": "लॉग इन करते समय त्रुटि हुई, सर्वर URL, ईमेल और पासवर्ड जांचें", - "login_form_handshake_exception": "सर्वर में एक हैंडशेक अपवाद था। यदि आप स्व-हस्ताक्षरित प्रमाणपत्र का उपयोग कर रहे हैं, तो सेटिंग्स में स्व-हस्ताक्षरित प्रमाणपत्र समर्थन सक्षम करें।", - "login_form_password_hint": "पासवर्ड", - "login_form_save_login": "लॉग इन रहें", - "login_form_server_empty": "सर्वर URL दर्ज करें।", - "login_form_server_error": "सर्वर से कनेक्ट नहीं कर सका।", - "login_has_been_disabled": "लॉगिन अक्षम कर दिया गया है।", - "login_password_changed_error": "आपका पासवर्ड अपडेट करते समय एक त्रुटि हुई", - "login_password_changed_success": "पासवर्ड सफलतापूर्वक अद्यतन", - "logout_all_device_confirmation": "क्या आप वाकई सभी डिवाइस से लॉग आउट करना चाहते हैं?", - "logout_this_device_confirmation": "क्या आप वाकई इस डिवाइस को लॉग आउट करना चाहते हैं?", - "logs": "लॉग्स", - "longitude": "देशान्तर", - "look": "देखना", - "loop_videos": "लूप वीडियो", - "loop_videos_description": "विवरण व्यूअर में किसी वीडियो को स्वचालित रूप से लूप करने में सक्षम करें।", - "main_branch_warning": "आप विकास संस्करण का उपयोग कर रहे हैं; हम दृढ़ता से रिलीज़ संस्करण का उपयोग करने की अनुशंसा करते हैं!", - "main_menu": "मेनू चलाएँ", - "maintenance_description": "Immich को मेंटेनेंस मोड में डाल दिया गया है।", - "maintenance_end": "रखरखाव मोड समाप्त करें", - "maintenance_end_error": "मेंटेनेंस मोड खत्म नहीं हो सका।", - "maintenance_logged_in_as": "अभी {user} के तौर पर लॉग इन हैं", - "maintenance_title": "अस्थाई रूप से अनुपलब्ध", - "make": "बनाना", - "manage_geolocation": "स्थान प्रबंधित करें", - "manage_media_access_rationale": "यह अनुमति परिसंपत्तियों को कूड़ेदान में ले जाने तथा वहां से उन्हें वापस लाने के उचित प्रबंधन के लिए आवश्यक है।", - "manage_media_access_settings": "खुली सेटिंग", - "manage_media_access_subtitle": "Immich ऐप को मीडिया फ़ाइलों को प्रबंधित करने और स्थानांतरित करने की अनुमति दें।", - "manage_media_access_title": "मीडिया प्रबंधन पहुँच", - "manage_shared_links": "साझा किए गए लिंक का प्रबंधन करें", - "manage_sharing_with_partners": "साझेदारों के साथ साझाकरण प्रबंधित करें", - "manage_the_app_settings": "ऐप सेटिंग प्रबंधित करें", - "manage_your_account": "अपना खाता प्रबंधित करें", - "manage_your_api_keys": "अपनी एपीआई कुंजियाँ प्रबंधित करें", - "manage_your_devices": "अपने लॉग-इन डिवाइस प्रबंधित करें", - "manage_your_oauth_connection": "अपना OAuth कनेक्शन प्रबंधित करें", - "map": "नक्शा", - "map_assets_in_bounds": "{count, plural, =0 {No photos in this area} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "उपयोगकर्ता का स्थान प्राप्त नहीं किया जा सका", - "map_location_dialog_yes": "हाँ", - "map_location_picker_page_use_location": "इस स्थान का उपयोग करें", - "map_location_service_disabled_content": "आपके वर्तमान स्थान की संपत्तियाँ प्रदर्शित करने के लिए स्थान सेवा सक्षम होनी चाहिए। क्या आप इसे अभी सक्षम करना चाहते हैं?", - "map_location_service_disabled_title": "स्थान सेवा अक्षम", - "map_marker_for_images": "{city}, {country} में ली गई छवियों के लिए मानचित्र मार्कर", - "map_marker_with_image": "छवि के साथ मानचित्र मार्कर", - "map_no_location_permission_content": "आपके वर्तमान स्थान से संपत्तियाँ प्रदर्शित करने के लिए स्थान अनुमति आवश्यक है। क्या आप इसे अभी अनुमति देना चाहते हैं?", - "map_no_location_permission_title": "स्थान की अनुमति अस्वीकृत", - "map_settings": "मानचित्र सेटिंग", - "map_settings_dark_mode": "डार्क मोड", - "map_settings_date_range_option_day": "पिछले 24 घंटे", - "map_settings_date_range_option_days": "पिछले {days} दिन", - "map_settings_date_range_option_year": "पिछले एक साल", - "map_settings_date_range_option_years": "पिछले {years} वर्ष", - "map_settings_dialog_title": "मानचित्र सेटिंग्स", - "map_settings_include_show_archived": "संग्रहीत शामिल करें", - "map_settings_include_show_partners": "भागीदारों को शामिल करें", - "map_settings_only_show_favorites": "केवल पसंदीदा दिखाएँ", - "map_settings_theme_settings": "मानचित्र थीम", - "map_zoom_to_see_photos": "फ़ोटो देखने के लिए ज़ूम आउट करें", - "mark_all_as_read": "सभी को पढ़ा हुआ मार्क करें", - "mark_as_read": "पढ़े हुए का चिह्न", - "marked_all_as_read": "सभी को पढ़ा हुआ चिह्नित किया गया", - "matches": "माचिस", - "matching_assets": "मिलान संपत्तियां", - "media_type": "मीडिया प्रकार", - "memories": "यादें", - "memories_all_caught_up": "सारी जानकारी प्राप्त", - "memories_check_back_tomorrow": "अधिक यादों के लिए कल पुनः आइए", - "memories_setting_description": "आप अपनी यादों में जो देखते हैं उसे प्रबंधित करें", - "memories_start_over": "प्रारंभ करें", - "memories_swipe_to_close": "बंद करने के लिए ऊपर स्वाइप करें", - "memory": "याद", - "memory_lane_title": "स्मृति लेन {title}", - "menu": "मेन्यू", - "merge": "मर्ज", - "merge_people": "लोगों को मिलाओ", - "merge_people_limit": "आप एक समय में अधिकतम 5 चेहरों को ही मर्ज कर सकते हैं", - "merge_people_prompt": "क्या आप इन लोगों का विलय करना चाहते हैं? यह कार्रवाई अपरिवर्तनीय है।", - "merge_people_successfully": "लोगों को सफलतापूर्वक मर्ज करें", - "merged_people_count": "विलयित {count, plural, one {# person} other {# people}}", - "minimize": "छोटा करना", - "minute": "मिनट", - "minutes": "मिनट", - "missing": "गुम", - "mobile_app": "मोबाइल एप्लिकेशन", - "mobile_app_download_onboarding_note": "निम्नलिखित विकल्पों का उपयोग करके साथी मोबाइल ऐप डाउनलोड करें", - "model": "मॉडल", - "month": "महीना", - "monthly_title_text_date_format": "एमएमएमएम वाई", - "more": "अधिक", - "move": "स्थान परिवर्तन", - "move_off_locked_folder": "लॉक किए गए फ़ोल्डर से बाहर ले जाएं", - "move_to": "करने के लिए कदम", - "move_to_lock_folder_action_prompt": "{count} लॉक किए गए फ़ोल्डर में जोड़ा गया", - "move_to_locked_folder": "लॉक किए गए फ़ोल्डर में ले जाएं", - "move_to_locked_folder_confirmation": "ये फ़ोटो और वीडियो सभी एल्बमों से हटा दिए जाएँगे और केवल लॉक किए गए फ़ोल्डर से ही देखे जा सकेंगे", - "moved_to_archive": "{count, plural, one {# asset} other {# assets}} को संग्रह में ले जाया गया", - "moved_to_library": "{count, plural, one {# asset} other {# assets}} को लाइब्रेरी में ले जाया गया", - "moved_to_trash": "कूड़ेदान में ले जाया गया", - "multiselect_grid_edit_date_time_err_read_only": "केवल पढ़ने योग्य परिसंपत्ति(यों) की तिथि संपादित नहीं की जा सकती, छोड़ी जा रही है", - "multiselect_grid_edit_gps_err_read_only": "केवल पढ़ने योग्य परिसंपत्ति(यों) का स्थान संपादित नहीं किया जा सकता, छोड़ा जा रहा है", - "mute_memories": "मूक यादें", - "my_albums": "मेरे एल्बम", - "name": "नाम", - "name_or_nickname": "नाम या उपनाम", - "navigate": "नेविगेट", - "navigate_to_time": "समय पर नेविगेट करें", - "network_requirement_photos_upload": "फ़ोटो का बैकअप लेने के लिए सेलुलर डेटा का उपयोग करें", - "network_requirement_videos_upload": "वीडियो का बैकअप लेने के लिए सेलुलर डेटा का उपयोग करें", - "network_requirements": "नेटवर्क आवश्यकताएँ", - "network_requirements_updated": "नेटवर्क आवश्यकताएँ बदली गईं, बैकअप कतार रीसेट की जा रही है", - "networking_settings": "नेटवर्किंग", - "networking_subtitle": "सर्वर एंडपॉइंट सेटिंग्स प्रबंधित करें", - "never": "कभी नहीं", - "new_album": "नयी एल्बम", - "new_api_key": "नई एपीआई कुंजी", - "new_date_range": "नई तिथि सीमा", - "new_password": "नया पासवर्ड", - "new_person": "नया व्यक्ति", - "new_pin_code": "नया पिन कोड", - "new_pin_code_subtitle": "आप पहली बार लॉक किए गए फ़ोल्डर तक पहुँच रहे हैं। इस पृष्ठ तक सुरक्षित रूप से पहुँचने के लिए एक पिन कोड बनाएँ", - "new_timeline": "नई समयरेखा", - "new_update": "नई अपडेट", - "new_user_created": "नया उपयोगकर्ता बनाया गया", - "new_version_available": "नया संस्करण उपलब्ध है", - "newest_first": "नवीनतम पहले", - "next": "अगला", - "next_memory": "अगली स्मृति", - "no": "नहीं", - "no_albums_message": "अपनी फ़ोटो और वीडियो को व्यवस्थित करने के लिए एक एल्बम बनाएं", - "no_albums_with_name_yet": "ऐसा लगता है कि आपके पास अभी तक इस नाम का कोई एल्बम नहीं है।", - "no_albums_yet": "ऐसा लगता है कि आपके पास अभी तक कोई एल्बम नहीं है।", - "no_archived_assets_message": "फ़ोटो और वीडियो को अपने फ़ोटो दृश्य से छिपाने के लिए उन्हें संग्रहीत करें", - "no_assets_message": "अपना पहला फोटो अपलोड करने के लिए क्लिक करें", - "no_assets_to_show": "दिखाने के लिए कोई संपत्ति नहीं", - "no_cast_devices_found": "कोई कास्ट डिवाइस नहीं मिला", - "no_checksum_local": "कोई चेकसम उपलब्ध नहीं है - स्थानीय संपत्तियां प्राप्त नहीं की जा सकतीं", - "no_checksum_remote": "कोई चेकसम उपलब्ध नहीं है - दूरस्थ संपत्ति प्राप्त नहीं की जा सकती", - "no_devices": "कोई अधिकृत उपकरण नहीं", - "no_duplicates_found": "कोई नकलची नहीं मिला।", - "no_exif_info_available": "कोई एक्सिफ़ जानकारी उपलब्ध नहीं है", - "no_explore_results_message": "अपने संग्रह का पता लगाने के लिए और फ़ोटो अपलोड करें।", - "no_favorites_message": "अपनी सर्वश्रेष्ठ तस्वीरें और वीडियो तुरंत ढूंढने के लिए पसंदीदा जोड़ें", - "no_libraries_message": "अपनी फ़ोटो और वीडियो देखने के लिए एक बाहरी लाइब्रेरी बनाएं", - "no_local_assets_found": "इस चेकसम के साथ कोई स्थानीय संपत्ति नहीं मिली", - "no_location_set": "कोई स्थान निर्धारित नहीं", - "no_locked_photos_message": "लॉक किए गए फ़ोल्डर में मौजूद फ़ोटो और वीडियो छिपे हुए हैं और जब आप अपनी लाइब्रेरी ब्राउज़ या खोजेंगे तो वे दिखाई नहीं देंगे।", - "no_name": "कोई नाम नहीं", - "no_notifications": "कोई सूचना नहीं", - "no_people_found": "कोई मेल खाता व्यक्ति नहीं मिला", - "no_places": "कोई जगह नहीं", - "no_remote_assets_found": "इस चेकसम के साथ कोई दूरस्थ संपत्ति नहीं मिली", - "no_results": "कोई परिणाम नहीं", - "no_results_description": "कोई पर्यायवाची या अधिक सामान्य कीवर्ड आज़माएँ", - "no_shared_albums_message": "अपने नेटवर्क में लोगों के साथ फ़ोटो और वीडियो साझा करने के लिए एक एल्बम बनाएं", - "no_uploads_in_progress": "कोई अपलोड प्रगति पर नहीं है", - "not_allowed": "अनुमति नहीं", - "not_available": "लागू नहीं", - "not_in_any_album": "किसी एलबम में नहीं", - "not_selected": "चयनित नहीं", - "note_apply_storage_label_to_previously_uploaded assets": "नोट: पहले अपलोड की गई संपत्तियों पर स्टोरेज लेबल लागू करने के लिए, चलाएँ", - "notes": "टिप्पणियाँ", - "nothing_here_yet": "यहाँ अभी तक कुछ नहीं", - "notification_permission_dialog_content": "सूचनाएं सक्षम करने के लिए सेटिंग्स में जाएं और अनुमति दें चुनें।", - "notification_permission_list_tile_content": "अधिसूचनाएं सक्षम करने की अनुमति प्रदान करें।", - "notification_permission_list_tile_enable_button": "सूचनाएं सक्षम करें", - "notification_permission_list_tile_title": "अधिसूचना अनुमति", - "notification_toggle_setting_description": "ईमेल सूचनाएं सक्षम करें", - "notifications": "सूचनाएं", - "notifications_setting_description": "सूचनाएं प्रबंधित करें", - "oauth": "OAuth", - "obtainium_configurator": "ओब्टेनियम विन्यासक", - "obtainium_configurator_instructions": "Immich GitHub रिलीज़ से सीधे Android ऐप इंस्टॉल और अपडेट करने के लिए Obtainium का उपयोग करें। अपना Obtainium कॉन्फ़िगरेशन लिंक बनाने के लिए एक API कुंजी बनाएँ और एक वैरिएंट चुनें", - "ocr": "ओसीआर", - "official_immich_resources": "आधिकारिक Immich संसाधन", - "offline": "ऑफलाइन", - "offset": "ओफ़्सेट", - "ok": "ठीक है", - "oldest_first": "सबसे पुराना पहले", - "on_this_device": "इस डिवाइस पर", - "onboarding": "ज्ञानप्राप्ति", - "onboarding_locale_description": "अपनी पसंदीदा भाषा चुनें। आप इसे बाद में अपनी सेटिंग में बदल सकते हैं।", - "onboarding_privacy_description": "निम्नलिखित (वैकल्पिक) सुविधाएं बाहरी सेवाओं पर निर्भर करती हैं, और इन्हें सेटिंग्स में किसी भी समय अक्षम किया जा सकता है।", - "onboarding_server_welcome_description": "आइए, कुछ सामान्य सेटिंग्स के साथ आपका इंस्टेंस सेट अप करें।", - "onboarding_theme_description": "अपने उदाहरण के लिए एक रंग थीम चुनें।", - "onboarding_user_welcome_description": "चलिए शुरू करते हैं!", - "onboarding_welcome_user": "स्वागत है, {user}", - "online": "ऑनलाइन", - "only_favorites": "केवल पसंदीदा", - "open": "खुला", - "open_in_map_view": "मानचित्र दृश्य में खोलें", - "open_in_openstreetmap": "OpenStreetMap में खोलें", - "open_the_search_filters": "खोज फ़िल्टर खोलें", - "options": "विकल्प", - "or": "या", - "organize_into_albums": "एल्बम में व्यवस्थित करें", - "organize_into_albums_description": "वर्तमान सिंक सेटिंग्स का उपयोग करके मौजूदा फ़ोटो को एल्बम में डालें", - "organize_your_library": "अपनी लाइब्रेरी व्यवस्थित करें", - "original": "मूल", - "other": "अन्य", - "other_devices": "अन्य उपकरण", - "other_entities": "अन्य संस्थाएँ", - "other_variables": "अन्य चर", - "owned": "स्वामित्व", - "owner": "मालिक", - "partner": "साथी", - "partner_can_access": "{partner} एक्सेस कर सकते हैं", - "partner_can_access_assets": "संग्रहीत और हटाए गए को छोड़कर आपके सभी फ़ोटो और वीडियो", - "partner_can_access_location": "वह स्थान जहां आपकी तस्वीरें ली गईं थीं", - "partner_list_user_photos": "{user} की तस्वीरें", - "partner_list_view_all": "सभी को देखें", - "partner_page_empty_message": "आपकी तस्वीरें अभी तक किसी भी भागीदार के साथ साझा नहीं की गई हैं।", - "partner_page_no_more_users": "अब और कोई उपयोगकर्ता जोड़ने की आवश्यकता नहीं है", - "partner_page_partner_add_failed": "भागीदार जोड़ने में विफल", - "partner_page_select_partner": "भागीदार चुनें", - "partner_page_shared_to_title": "साझा किया गया", - "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos।", - "partner_sharing": "पार्टनर शेयरिंग", - "partners": "भागीदारों", - "password": "पासवर्ड", - "password_does_not_match": "पासवर्ड मैच नहीं कर रहा है", - "password_required": "पासवर्ड आवश्यक", - "password_reset_success": "पासवर्ड रीसेट सफल", - "past_durations": { - "days": "पिछले {days, plural, one {day} other {# days}}", - "hours": "पिछले {hours, plural, one {hour} other {# hours}}", - "years": "पिछले {years, plural, one {year} other {# years}}" - }, - "path": "पथ", - "pattern": "नमूना", - "pause": "विराम", - "pause_memories": "यादें रोकें", - "paused": "रोके गए", - "pending": "लंबित", - "people": "लोग", - "people_edits_count": "संपादित {count, plural, one {# person} other {# people}}", - "people_feature_description": "लोगों द्वारा समूहीकृत फ़ोटो और वीडियो ब्राउज़ करना", - "people_sidebar_description": "साइडबार में लोगों के लिए एक लिंक प्रदर्शित करें", - "permanent_deletion_warning": "स्थायी विलोपन चेतावनी", - "permanent_deletion_warning_setting_description": "संपत्तियों को स्थायी रूप से हटाते समय एक चेतावनी दिखाएं", - "permanently_delete": "स्थायी रूप से हटाना", - "permanently_delete_assets_count": "{count, plural, one {asset} other {assets}} को स्थायी रूप से हटाएं", - "permanently_delete_assets_prompt": "क्या आप वाकई {count, plural, one {this asset?} other {these # assets?}} को स्थायी रूप से हटाना चाहते हैं? इससे {count, plural, one {it from its} other {them from their}} एल्बम भी हट जाएंगे।", - "permanently_deleted_asset": "स्थायी रूप से हटाई गई संपत्ति", - "permanently_deleted_assets_count": "स्थायी रूप से हटा दिया गया {count, plural, one {# asset} other {# assets}}", - "permission": "अनुमति", - "permission_empty": "आपकी अनुमति खाली नहीं होनी चाहिए", - "permission_onboarding_back": "वापस", - "permission_onboarding_continue_anyway": "फिर भी जारी रखें", - "permission_onboarding_get_started": "शुरू हो जाओ", - "permission_onboarding_go_to_settings": "सेटिंग्स पर जाएँ", - "permission_onboarding_permission_denied": "अनुमति अस्वीकृत। Immich का उपयोग करने के लिए, सेटिंग में फ़ोटो और वीडियो की अनुमति दें।", - "permission_onboarding_permission_granted": "अनुमति मिल गई! आप तैयार हैं।", - "permission_onboarding_permission_limited": "अनुमति सीमित है। Immich को आपके संपूर्ण गैलरी संग्रह का बैकअप लेने और उसे प्रबंधित करने की अनुमति देने के लिए, सेटिंग में फ़ोटो और वीडियो की अनुमति दें।", - "permission_onboarding_request": "Immich को आपकी तस्वीरें और वीडियो देखने के लिए अनुमति की आवश्यकता है।", - "person": "व्यक्ति", - "person_age_months": "{months, plural, one {# month} other {# months}} पुराना", - "person_age_year_months": "1 वर्ष, {months, plural, one {# month} other {# months}} पुराना", - "person_age_years": "{years, plural, other {# years}} पुराना", - "person_birthdate": "{date} को जन्मे", - "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", - "photo_shared_all_users": "ऐसा लगता है कि आपने अपनी तस्वीरें सभी उपयोगकर्ताओं के साथ साझा कीं या आपके पास साझा करने के लिए कोई उपयोगकर्ता नहीं है।", - "photos": "तस्वीरें", - "photos_and_videos": "तस्वीरें और वीडियो", - "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", - "photos_from_previous_years": "पिछले वर्षों की तस्वीरें", - "pick_a_location": "एक स्थान चुनें", - "pick_custom_range": "कस्टम रेंज", - "pick_date_range": "दिनांक सीमा चुनें", - "pin_code_changed_successfully": "पिन कोड सफलतापूर्वक बदला गया", - "pin_code_reset_successfully": "पिन कोड सफलतापूर्वक रीसेट किया गया", - "pin_code_setup_successfully": "पिन कोड सफलतापूर्वक सेट किया गया", - "pin_verification": "पिन कोड सत्यापन", - "place": "जगह", - "places": "स्थानों", - "places_count": "{count, plural, one {{count, number} Place} other {{count, number} Places}}", - "play": "खेल", - "play_memories": "यादें खेलें", - "play_motion_photo": "मोशन फ़ोटो चलाएं", - "play_or_pause_video": "वीडियो चलाएं या रोकें", - "play_original_video": "मूल वीडियो चलाएँ", - "play_original_video_setting_description": "ट्रांसकोड किए गए वीडियो के बजाय मूल वीडियो के प्लेबैक को प्राथमिकता दें। यदि मूल एसेट संगत नहीं है, तो हो सकता है कि वह ठीक से प्लेबैक न हो।", - "play_transcoded_video": "ट्रांसकोडेड वीडियो चलाएं", - "please_auth_to_access": "कृपया पहुँच के लिए प्रमाणित करें", - "port": "पत्तन", - "preferences_settings_subtitle": "ऐप की प्राथमिकताएँ प्रबंधित करें", - "preferences_settings_title": "प्राथमिकताएँ", - "preparing": "तैयारी", - "preset": "प्रीसेट", - "preview": "पूर्व दर्शन", - "previous": "पहले का", - "previous_memory": "पिछली स्मृति", - "previous_or_next_day": "दिन आगे/पीछे", - "previous_or_next_month": "महीना आगे/पीछे", - "previous_or_next_photo": "फ़ोटो आगे/पीछे", - "previous_or_next_year": "वर्ष आगे/पीछे", - "primary": "प्राथमिक", - "privacy": "गोपनीयता", - "profile": "प्रोफ़ाइल", - "profile_drawer_app_logs": "लॉग्स", - "profile_drawer_client_server_up_to_date": "क्लाइंट और सर्वर अद्यतित हैं", - "profile_drawer_github": "गिटहब", - "profile_drawer_readonly_mode": "केवल-पठन मोड सक्षम है। बाहर निकलने के लिए उपयोगकर्ता अवतार आइकन को देर तक दबाएँ।", - "profile_image_of_user": "{user} की प्रोफ़ाइल छवि", - "profile_picture_set": "प्रोफ़ाइल चित्र सेट।", - "public_album": "सार्वजनिक एल्बम", - "public_share": "सार्वजनिक शेयर", - "purchase_account_info": "समर्थक", - "purchase_activated_subtitle": "इमिच और ओपन-सोर्स सॉफ़्टवेयर का समर्थन करने के लिए धन्यवाद", - "purchase_activated_time": "{date} को सक्रिय किया गया", - "purchase_activated_title": "आपकी कुंजी सफलतापूर्वक सक्रिय कर दी गई है", - "purchase_button_activate": "सक्रिय", - "purchase_button_buy": "खरीदना", - "purchase_button_buy_immich": "इमिच खरीदें", - "purchase_button_never_show_again": "फिर कभी दिखाई मत देना", - "purchase_button_reminder": "मुझे 30 दिन में याद दिलाएं", - "purchase_button_remove_key": "कुंजी निकालें", - "purchase_button_select": "चुनना", - "purchase_failed_activation": "सक्रिय करने में विफल!", - "purchase_individual_description_1": "एक व्यक्ति के लिए", - "purchase_individual_description_2": "समर्थक स्थिति", - "purchase_individual_title": "व्यक्ति", - "purchase_input_suggestion": "क्या आपके पास उत्पाद कुंजी है? नीचे कुंजी दर्ज करें", - "purchase_license_subtitle": "सेवा के निरंतर विकास का समर्थन करने के लिए इमिच खरीदें", - "purchase_lifetime_description": "जीवन भर की खरीदारी", - "purchase_option_title": "खरीद विकल्प", - "purchase_panel_info_1": "इमिच को बनाने में बहुत समय और प्रयास लगता है, और हमारे पास इसे जितना संभव हो सके उतना अच्छा बनाने के लिए पूर्णकालिक इंजीनियर इस पर काम कर रहे हैं।", - "purchase_panel_info_2": "चूँकि हम पेवॉल नहीं जोड़ने के लिए प्रतिबद्ध हैं, इसलिए इस खरीदारी से आपको Immich में कोई अतिरिक्त सुविधाएँ नहीं मिलेंगी। Immich के निरंतर विकास में सहयोग के लिए हम आप जैसे उपयोगकर्ताओं पर निर्भर हैं।", - "purchase_panel_title": "परियोजना का समर्थन करें", - "purchase_per_server": "प्रति सर्वर", - "purchase_per_user": "प्रति उपयोगकर्ता", - "purchase_remove_product_key": "उत्पाद कुंजी निकालें", - "purchase_remove_product_key_prompt": "क्या आप वाकई उत्पाद कुंजी हटाना चाहते हैं?", - "purchase_remove_server_product_key": "सर्वर उत्पाद कुंजी निकालें", - "purchase_remove_server_product_key_prompt": "क्या आप वाकई सर्वर उत्पाद कुंजी को हटाना चाहते हैं?", - "purchase_server_description_1": "पूरे सर्वर के लिए", - "purchase_server_description_2": "समर्थक स्थिति", - "purchase_server_title": "सर्वर", - "purchase_settings_server_activated": "सर्वर उत्पाद कुंजी व्यवस्थापक द्वारा प्रबंधित की जाती है", - "query_asset_id": "क्वेरी एसेट आईडी", - "queue_status": "कतारबद्ध {count}/{total}", - "rating": "स्टार रेटिंग", - "rating_clear": "स्पष्ट रेटिंग", - "rating_count": "{count, plural, one {# star} other {# stars}}", - "rating_description": "जानकारी पैनल में EXIF रेटिंग प्रदर्शित करें", - "reaction_options": "प्रतिक्रिया विकल्प", - "read_changelog": "चेंजलॉग पढ़ें", - "readonly_mode_disabled": "केवल-पढ़ने के लिए मोड अक्षम", - "readonly_mode_enabled": "केवल-पढ़ने के लिए मोड सक्षम", - "ready_for_upload": "अपलोड के लिए तैयार", - "reassign": "पुनः असाइन", - "reassigned_assets_to_existing_person": "{count, plural, one {# asset} other {# assets}} को {name, select, null {an existing person} other {{name}}}को फिर से असाइन किया गया", - "reassigned_assets_to_new_person": "{count, plural, one {# asset} other {# assets}} को एक नए व्यक्ति को फिर से असाइन किया गया", - "reassing_hint": "चयनित संपत्तियों को किसी मौजूदा व्यक्ति को सौंपें", - "recent": "हाल ही का", - "recent-albums": "हाल के एल्बम", - "recent_searches": "हाल की खोजें", - "recently_added": "हाल ही में डाला गया", - "recently_added_page_title": "हाल ही में डाला गया", - "recently_taken": "हाल ही में लिया गया", - "recently_taken_page_title": "हाल ही में लिया गया", - "refresh": "ताज़ा करना", - "refresh_encoded_videos": "एन्कोडेड वीडियो ताज़ा करें", - "refresh_faces": "चेहरे ताज़ा करें", - "refresh_metadata": "मेटाडेटा ताज़ा करें", - "refresh_thumbnails": "थंबनेल ताज़ा करें", - "refreshed": "ताज़ा किया", - "refreshes_every_file": "प्रत्येक फ़ाइल को ताज़ा करता है", - "refreshing_encoded_video": "ताज़ा किया जा रहा एन्कोडेड वीडियो", - "refreshing_faces": "ताज़ा चेहरे", - "refreshing_metadata": "ताज़ा मेटाडेटा", - "regenerating_thumbnails": "पुनर्जीवित थंबनेल", - "remote": "रिमोट", - "remote_assets": "दूरस्थ संपत्तियाँ", - "remote_media_summary": "रिमोट मीडिया सारांश", - "remove": "निकालना", - "remove_assets_album_confirmation": "क्या आप वाकई एल्बम से {count, plural, one {# asset} other {# assets}} हटाना चाहते हैं?", - "remove_assets_shared_link_confirmation": "क्या आप वाकई इस शेयर्ड लिंक से {count, plural, one {# asset} other {# assets}} हटाना चाहते हैं?", - "remove_assets_title": "संपत्तियाँ हटाएँ?", - "remove_custom_date_range": "कस्टम दिनांक सीमा हटाएँ", - "remove_deleted_assets": "ऑफ़लाइन फ़ाइलें हटाएँ", - "remove_from_album": "एल्बम से हटाएँ", - "remove_from_album_action_prompt": "एल्बम से {count} हटा दिया गया", - "remove_from_favorites": "पसंदीदा से निकालें", - "remove_from_lock_folder_action_prompt": "लॉक किए गए फ़ोल्डर से {count} हटा दिया गया", - "remove_from_locked_folder": "लॉक किए गए फ़ोल्डर से निकालें", - "remove_from_locked_folder_confirmation": "क्या आप वाकई इन फ़ोटो और वीडियो को लॉक किए गए फ़ोल्डर से बाहर ले जाना चाहते हैं? वे आपकी लाइब्रेरी में दिखाई देंगे।", - "remove_from_shared_link": "साझा लिंक से हटाएँ", - "remove_memory": "मेमोरी हटाएँ", - "remove_photo_from_memory": "इस मेमोरी से फ़ोटो हटाएँ", - "remove_tag": "टैग हटाएँ", - "remove_url": "URL हटाएँ", - "remove_user": "उपयोगकर्ता को हटाएँ", - "removed_api_key": "हटाई गई API कुंजी: {name}", - "removed_from_archive": "संग्रह से हटा दिया गया", - "removed_from_favorites": "पसंदीदा से हटाया गया", - "removed_from_favorites_count": "पसंदीदा से {count, plural, other {Removed #}}", - "removed_memory": "हटाई गई मेमोरी", - "removed_photo_from_memory": "मेमोरी से फ़ोटो हटा दी गई", - "removed_tagged_assets": "{count, plural, one {# asset} other {# assets}} से टैग हटाया गया", - "rename": "नाम बदलें", - "repair": "मरम्मत", - "repair_no_results_message": "ट्रैक न की गई और गुम फ़ाइलें यहां दिखाई देंगी", - "replace_with_upload": "अपलोड के साथ बदलें", - "repository": "कोष", - "require_password": "पासवर्ड की आवश्यकता है", - "require_user_to_change_password_on_first_login": "उपयोगकर्ता को पहले लॉगिन पर पासवर्ड बदलने की आवश्यकता है", - "rescan": "पुन: स्कैन", - "reset": "रीसेट", - "reset_password": "पासवर्ड रीसेट", - "reset_people_visibility": "लोगों की दृश्यता रीसेट करें", - "reset_pin_code": "पिन कोड रीसेट करें", - "reset_pin_code_description": "अगर आप अपना PIN कोड भूल गए हैं, तो आप इसे रीसेट करने के लिए सर्वर एडमिनिस्ट्रेटर से संपर्क कर सकते हैं", - "reset_pin_code_success": "पिन कोड सफलतापूर्वक रीसेट किया गया", - "reset_pin_code_with_password": "आप हमेशा अपने पासवर्ड से अपना पिन कोड रीसेट कर सकते हैं", - "reset_sqlite": "SQLite डेटाबेस रीसेट करें", - "reset_sqlite_confirmation": "क्या आप वाकई SQLite डेटाबेस को रीसेट करना चाहते हैं? डेटा को फिर से सिंक करने के लिए आपको लॉग आउट करके फिर से लॉग इन करना होगा", - "reset_sqlite_success": "SQLite डेटाबेस को सफलतापूर्वक रीसेट करें", - "reset_to_default": "वितथ पर ले जाएं", - "resolution": "संकल्प", - "resolve_duplicates": "डुप्लिकेट का समाधान करें", - "resolved_all_duplicates": "सभी डुप्लिकेट का समाधान किया गया", - "restore": "पुनर्स्थापित करना", - "restore_all": "सभी बहाल करो", - "restore_trash_action_prompt": "{count} ट्रैश से पुनर्स्थापित किया गया", - "restore_user": "उपयोगकर्ता को पुनर्स्थापित करें", - "restored_asset": "पुनर्स्थापित संपत्ति", - "resume": "फिर शुरू करना", - "resume_paused_jobs": "रिज्यूमे {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "पुनः अपलोड करने का प्रयास करें", - "review_duplicates": "डुप्लिकेट की समीक्षा करें", - "review_large_files": "बड़ी फ़ाइलों की समीक्षा करें", - "role": "भूमिका", - "role_editor": "संपादक", - "role_viewer": "दर्शक", - "running": "चल रहा है", - "save": "बचाना", - "save_to_gallery": "गैलरी में सहेजें", - "saved": "सहेजा गया", - "saved_api_key": "सहेजी गई एपीआई कुंजी", - "saved_profile": "प्रोफ़ाइल सहेजी गई", - "saved_settings": "सहेजी गई सेटिंग्स", - "say_something": "कुछ कहें", - "scaffold_body_error_occurred": "त्रुटि हुई", - "scan_all_libraries": "सभी पुस्तकालयों को स्कैन करें", - "scan_library": "स्कैन", - "scan_settings": "सेटिंग्स स्कैन करें", - "scanning_for_album": "एल्बम के लिए स्कैन किया जा रहा है..।", - "search": "खोज", - "search_albums": "एल्बम खोजें", - "search_by_context": "संदर्भ के आधार पर खोजें", - "search_by_description": "विवरण के अनुसार खोजें", - "search_by_description_example": "सापा में हाइकिंग का दिन", - "search_by_filename": "फ़ाइल नाम या एक्सटेंशन के आधार पर खोजें", - "search_by_filename_example": "यानी IMG_1234.JPG या PNG", - "search_by_ocr": "OCR द्वारा खोजें", - "search_by_ocr_example": "लट्टे", - "search_camera_lens_model": "लेंस मॉडल खोजें..।", - "search_camera_make": "कैमरा निर्माण खोजें..।", - "search_camera_model": "कैमरा मॉडल खोजें..।", - "search_city": "शहर खोजें..।", - "search_country": "देश खोजें..।", - "search_filter_apply": "फ़िल्टर लागू करें", - "search_filter_camera_title": "कैमरा प्रकार चुनें", - "search_filter_date": "तारीख़", - "search_filter_date_interval": "{start} से {end} तक", - "search_filter_date_title": "तारीख़ की सीमा चुनें", - "search_filter_display_option_not_in_album": "एल्बम में नहीं", - "search_filter_display_options": "प्रदर्शन विकल्प", - "search_filter_filename": "फ़ाइल नाम से खोजें", - "search_filter_location": "स्थान", - "search_filter_location_title": "स्थान चुनें", - "search_filter_media_type": "मीडिया प्रकार", - "search_filter_media_type_title": "मीडिया प्रकार चुनें", - "search_filter_ocr": "OCR द्वारा खोजें", - "search_filter_people_title": "लोगों का चयन करें", - "search_for": "निम्न को खोजें", - "search_for_existing_person": "मौजूदा व्यक्ति को खोजें", - "search_no_more_result": "कोई और परिणाम नहीं", - "search_no_people": "कोई लोग नहीं", - "search_no_people_named": "\"{name}\" नाम के कोई लोग नहीं हैं", - "search_no_result": "कोई रिज़ल्ट नहीं मिला, कोई दूसरा सर्च टर्म या कॉम्बिनेशन ट्राई करें", - "search_options": "खोज विकल्प", - "search_page_categories": "श्रेणियाँ", - "search_page_motion_photos": "मोशन फ़ोटो", - "search_page_no_objects": "कोई ऑब्जेक्ट जानकारी उपलब्ध नहीं है", - "search_page_no_places": "कोई जगह की जानकारी उपलब्ध नहीं है", - "search_page_screenshots": "स्क्रीनशॉट", - "search_page_search_photos_videos": "अपनी फ़ोटो और वीडियो खोजें", - "search_page_selfies": "सेल्फ़ीज़", - "search_page_things": "चीज़ें", - "search_page_view_all_button": "सभी को देखें", - "search_page_your_activity": "आपकी गतिविधि", - "search_page_your_map": "आपका नक्शा", - "search_people": "लोगों को खोजें", - "search_places": "स्थान खोजें", - "search_rating": "रेटिंग के हिसाब से खोजें..।", - "search_result_page_new_search_hint": "नई खोज", - "search_settings": "खोज सेटिंग्स", - "search_state": "स्थिति खोजें..।", - "search_suggestion_list_smart_search_hint_1": "स्मार्ट सर्च डिफ़ॉल्ट रूप से चालू रहता है, मेटाडेटा खोजने के लिए सिंटैक्स का इस्तेमाल करें ", - "search_suggestion_list_smart_search_hint_2": "m:आपका-खोज-शब्द", - "search_tags": "टैग खोजें..।", - "search_timezone": "समयक्षेत्र खोजें..।", - "search_type": "तलाश की विधि", - "search_your_photos": "अपनी फ़ोटो खोजें", - "searching_locales": "स्थान खोजे जा रहे हैं..।", - "second": "दूसरा", - "see_all_people": "सभी लोगों को देखें", - "select": "चुनना", - "select_album_cover": "एल्बम कवर चुनें", - "select_all": "सबका चयन करें", - "select_all_duplicates": "सभी डुप्लिकेट का चयन करें", - "select_all_in": "{group} में सभी का चयन करें", - "select_avatar_color": "अवतार रंग चुनें", - "select_face": "चेहरा चुनें", - "select_featured_photo": "चुनिंदा फ़ोटो चुनें", - "select_from_computer": "कंप्यूटर से चयन करें", - "select_keep_all": "सभी रखें का चयन करें", - "select_library_owner": "लाइब्रेरी स्वामी का चयन करें", - "select_new_face": "नया चेहरा चुनें", - "select_person_to_tag": "टैग करने के लिए किसी व्यक्ति का चयन करें", - "select_photos": "फ़ोटो चुनें", - "select_trash_all": "ट्रैश ऑल का चयन करें", - "select_user_for_sharing_page_err_album": "एल्बम बनाने में विफल", - "selected": "चयनित", - "selected_count": "{count, plural, other {# selected}}", - "selected_gps_coordinates": "चयनित GPS निर्देशांक", - "send_message": "मेसेज भेजें", - "send_welcome_email": "स्वागत ईमेल भेजें", - "server_endpoint": "सर्वर एंडपॉइंट", - "server_info_box_app_version": "एप्लिकेशन वेरीज़न", - "server_info_box_server_url": "सर्वर URL", - "server_offline": "सर्वर ऑफ़लाइन", - "server_online": "सर्वर ऑनलाइन", - "server_privacy": "सर्वर गोपनीयता", - "server_restarting_description": "यह पेज कुछ देर में रिफ्रेश हो जाएगा।", - "server_restarting_title": "सर्वर पुनः प्रारंभ हो रहा है", - "server_stats": "सर्वर आँकड़े", - "server_update_available": "सर्वर अपडेट उपलब्ध है", - "server_version": "सर्वर संस्करण", - "set": "तय करना", - "set_as_album_cover": "एल्बम कवर के रूप में सेट करें", - "set_as_featured_photo": "फ़ीचर्ड फ़ोटो के तौर पर सेट करें", - "set_as_profile_picture": "प्रोफाइल चित्र के रूप में सेट", - "set_date_of_birth": "जन्मतिथि निर्धारित करें", - "set_profile_picture": "प्रोफ़ाइल चित्र सेट करें", - "set_slideshow_to_fullscreen": "स्लाइड शो को फ़ुलस्क्रीन पर सेट करें", - "set_stack_primary_asset": "प्राथमिक संपत्ति के रूप में सेट करें", - "setting_image_viewer_help": "डिटेल व्यूअर पहले छोटा थंबनेल लोड करता है, फिर मीडियम-साइज़ प्रीव्यू लोड करता है (अगर इनेबल है), और आखिर में ओरिजिनल लोड करता है (अगर इनेबल है)।", - "setting_image_viewer_original_subtitle": "ओरिजिनल फुल-रिज़ॉल्यूशन इमेज (बड़ी!) लोड करने के लिए इनेबल करें। डेटा का इस्तेमाल कम करने के लिए डिसेबल करें (नेटवर्क और डिवाइस कैश दोनों)।", - "setting_image_viewer_original_title": "मूल छवि लोड करें", - "setting_image_viewer_preview_subtitle": "मीडियम-रिज़ॉल्यूशन इमेज लोड करने के लिए चालू करें। ओरिजिनल इमेज को सीधे लोड करने या सिर्फ़ थंबनेल इस्तेमाल करने के लिए बंद करें।", - "setting_image_viewer_preview_title": "पूर्वावलोकन छवि लोड करें", - "setting_image_viewer_title": "इमेजिस", - "setting_languages_apply": "आवेदन करना", - "setting_languages_subtitle": "ऐप की भाषा बदलें", - "setting_notifications_notify_failures_grace_period": "बैकग्राउंड बैकअप फेलियर की सूचना दें: {duration}", - "setting_notifications_notify_hours": "{count} घंटे", - "setting_notifications_notify_immediately": "तुरंत", - "setting_notifications_notify_minutes": "{count} मिनट", - "setting_notifications_notify_never": "कभी नहीं", - "setting_notifications_notify_seconds": "{count} सेकंड", - "setting_notifications_single_progress_subtitle": "हर एसेट के लिए अपलोड प्रोग्रेस की पूरी जानकारी", - "setting_notifications_single_progress_title": "बैकग्राउंड बैकअप डिटेल प्रोग्रेस दिखाएँ", - "setting_notifications_subtitle": "अपनी नोटिफ़िकेशन प्राथमिकताएँ समायोजित करें", - "setting_notifications_total_progress_subtitle": "ओवरऑल अपलोड प्रोग्रेस (हो गया/टोटल एसेट्स)", - "setting_notifications_total_progress_title": "बैकग्राउंड बैकअप की कुल प्रोग्रेस दिखाएँ", - "setting_video_viewer_auto_play_subtitle": "वीडियो खुलने पर अपने आप चलना शुरू हो जाता है", - "setting_video_viewer_auto_play_title": "ऑटो प्ले वीडियो", - "setting_video_viewer_looping_title": "पाशन", - "setting_video_viewer_original_video_subtitle": "सर्वर से वीडियो स्ट्रीम करते समय, ट्रांसकोड उपलब्ध होने पर भी ओरिजिनल वीडियो चलाएं। इससे बफरिंग हो सकती है। इस सेटिंग के बावजूद, स्थानीय रूप से उपलब्ध वीडियो ओरिजिनल क्वालिटी में चलाए जाते हैं।", - "setting_video_viewer_original_video_title": "मूल वीडियो को बलपूर्वक", - "settings": "समायोजन", - "settings_require_restart": "इस सेटिंग को लागू करने के लिए कृपया Immich को रीस्टार्ट करें", - "settings_saved": "सेटिंग्स को सहेजा गया", - "setup_pin_code": "पिन कोड सेट करें", - "share": "शेयर करना", - "share_action_prompt": "साझा {count} संपत्तियाँ", - "share_add_photos": "फ़ोटो डालें", - "share_assets_selected": "{count} चयनित", - "share_dialog_preparing": "तैयारी..।", - "share_link": "लिंक शेयर करें", - "shared": "साझा", - "shared_album_activities_input_disable": "कॉमेंट डिजेबल्ड है", - "shared_album_activity_remove_content": "क्या आप इस गतिविधि को हटाना चाहते हैं?", - "shared_album_activity_remove_title": "गतिविधि हटाएं", - "shared_album_section_people_action_error": "एल्बम से हटाने/छोड़ने में त्रुटि", - "shared_album_section_people_action_leave": "उपयोगकर्ता को एल्बम से हटाएँ", - "shared_album_section_people_action_remove_user": "उपयोगकर्ता को एल्बम से हटाएँ", - "shared_album_section_people_title": "लोग", - "shared_by": "द्वारा साझा", - "shared_by_user": "{user} द्वारा साझा किया गया", - "shared_by_you": "आपके द्वारा साझा किया गया", - "shared_from_partner": "{partner} से फ़ोटो", - "shared_intent_upload_button_progress_text": "{current} / {total} अपलोड किया गया", - "shared_link_app_bar_title": "साझा किए गए लिंक", - "shared_link_clipboard_copied_massage": "क्लिपबोर्ड पर कॉपी किया गया", - "shared_link_clipboard_text": "लिंक: {link}\nपासवर्ड: {password}", - "shared_link_create_error": "शेयर्ड लिंक बनाते समय एरर आया", - "shared_link_custom_url_description": "कस्टम URL से इस शेयर्ड लिंक को एक्सेस करें", - "shared_link_edit_description_hint": "शेयर विवरण दर्ज करें", - "shared_link_edit_expire_after_option_day": "1 दिन", - "shared_link_edit_expire_after_option_days": "{count} दिन", - "shared_link_edit_expire_after_option_hour": "1 घंटा", - "shared_link_edit_expire_after_option_hours": "{count} घंटे", - "shared_link_edit_expire_after_option_minute": "1 मिनट", - "shared_link_edit_expire_after_option_minutes": "{count} मिनट", - "shared_link_edit_expire_after_option_months": "{count} महीने", - "shared_link_edit_expire_after_option_year": "{count} वर्ष", - "shared_link_edit_password_hint": "शेयर पासवर्ड दर्ज करें", - "shared_link_edit_submit_button": "अपडेट लिंक", - "shared_link_error_server_url_fetch": "सर्वर URL नहीं मिल रहा है", - "shared_link_expires_day": "{count} दिन में समाप्त हो रहा है", - "shared_link_expires_days": "{count} दिनों में समाप्त हो जाएगा", - "shared_link_expires_hour": "{count} घंटे में समाप्त हो जाएगा", - "shared_link_expires_hours": "{count} घंटे में समाप्त हो जाएगा", - "shared_link_expires_minute": "{count} मिनट में समाप्त हो रहा है", - "shared_link_expires_minutes": "{count} मिनट में समाप्त हो जाएगा", - "shared_link_expires_never": "समाप्त ∞", - "shared_link_expires_second": "{count} सेकंड में समाप्त हो रहा है", - "shared_link_expires_seconds": "{count} सेकंड में समाप्त हो जाएगा", - "shared_link_individual_shared": "व्यक्तिगत साझा", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "साझा किए गए लिंक का प्रबंधन करें", - "shared_link_options": "साझा लिंक विकल्प", - "shared_link_password_description": "इस शेयर किए गए लिंक को एक्सेस करने के लिए पासवर्ड ज़रूरी है", - "shared_links": "साझा किए गए लिंक", - "shared_links_description": "लिंक के साथ फ़ोटो और वीडियो शेयर करें", - "shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}", - "shared_with_me": "मेरे साथ साझा किया गया", - "shared_with_partner": "{partner} के साथ शेयर किया गया", - "sharing": "शेयरिंग", - "sharing_enter_password": "कृपया इस पृष्ठ को देखने के लिए पासवर्ड दर्ज करें।", - "sharing_page_album": "साझा एल्बम", - "sharing_page_description": "अपने नेटवर्क में लोगों के साथ फ़ोटो और वीडियो शेयर करने के लिए शेयर्ड एल्बम बनाएं।", - "sharing_page_empty_list": "खाली सूची", - "sharing_sidebar_description": "साइडबार में शेयरिंग के लिए एक लिंक प्रदर्शित करें", - "sharing_silver_appbar_create_shared_album": "नया साझा एल्बम", - "sharing_silver_appbar_share_partner": "पार्टनर के साथ शेयर करें", - "shift_to_permanent_delete": "संपत्ति को स्थायी रूप से हटाने के लिए ⇧ दबाएँ", - "show_album_options": "एल्बम विकल्प दिखाएँ", - "show_albums": "एल्बम दिखाएँ", - "show_all_people": "सभी लोगों को दिखाओ", - "show_and_hide_people": "लोगों को दिखाएँ और छिपाएँ", - "show_file_location": "फ़ाइल स्थान दिखाएँ", - "show_gallery": "गैलरी दिखाएँ", - "show_hidden_people": "छुपे हुए लोगों को दिखाएं", - "show_in_timeline": "टाइमलाइन में दिखाएँ", - "show_in_timeline_setting_description": "अपनी टाइमलाइन में इस उपयोगकर्ता के फ़ोटो और वीडियो दिखाएं", - "show_keyboard_shortcuts": "कुंजीपटल शॉर्टकट दिखाएँ", - "show_metadata": "मेटाडेटा दिखाएं", - "show_or_hide_info": "जानकारी दिखाएँ या छिपाएँ", - "show_password": "पासवर्ड दिखाए", - "show_person_options": "व्यक्ति विकल्प दिखाएँ", - "show_progress_bar": "प्रगति पट्टी दिखाएँ", - "show_search_options": "खोज विकल्प दिखाएँ", - "show_shared_links": "साझा लिंक दिखाएँ", - "show_slideshow_transition": "स्लाइड शो ट्रांज़िशन दिखाएँ", - "show_supporter_badge": "समर्थक बिल्ला", - "show_supporter_badge_description": "समर्थक बैज दिखाएँ", - "show_text_recognition": "पाठ पहचान दिखाएँ", - "show_text_search_menu": "टेक्स्ट खोज मेनू दिखाएँ", - "shuffle": "मिश्रण", - "sidebar": "साइड बार", - "sidebar_display_description": "साइडबार में व्यू का लिंक दिखाएं", - "sign_out": "साइन आउट", - "sign_up": "साइन अप करें", - "size": "आकार", - "skip_to_content": "इसे छोड़कर सामग्री पर बढ़ने के लिए", - "skip_to_folders": "फ़ोल्डरों पर जाएं", - "skip_to_tags": "टैग पर जाएं", - "slideshow": "स्लाइड शो", - "slideshow_settings": "स्लाइड शो सेटिंग्स", - "sort_albums_by": "एल्बम को क्रमबद्ध करें..।", - "sort_created": "बनाया गया दिनांक", - "sort_items": "मदों की संख्या", - "sort_modified": "डेटा संशोधित", - "sort_newest": "नवीनतम फोटो", - "sort_oldest": "सबसे पुरानी तस्वीर", - "sort_people_by_similarity": "लोगों को समानता के आधार पर छाँटें", - "sort_recent": "सबसे ताज़ा फ़ोटो", - "sort_title": "शीर्षक", - "source": "स्रोत", - "stack": "ढेर", - "stack_action_prompt": "{count} स्टैक्ड", - "stack_duplicates": "डुप्लिकेट स्टैक करें", - "stack_select_one_photo": "स्टैक के लिए एक मुख्य फ़ोटो चुनें", - "stack_selected_photos": "चयनित फ़ोटो को ढेर करें", - "stacked_assets_count": "स्टैक्ड {count, plural, one {# asset} other {# assets}}", - "stacktrace": "स्टैक ट्रेस", - "start": "शुरू", - "start_date": "आरंभ करने की तिथि", - "start_date_before_end_date": "आरंभ तिथि समाप्ति तिथि से पहले होनी चाहिए", - "state": "राज्य", - "status": "स्थिति", - "stop_casting": "कास्टिंग बंद करो", - "stop_motion_photo": "स्टॉप मोशन फोटो", - "stop_photo_sharing": "अपनी तस्वीरें साझा करना बंद करें?", - "stop_photo_sharing_description": "{partner} अब आपकी फ़ोटो एक्सेस नहीं कर पाएगा।", - "stop_sharing_photos_with_user": "इस उपयोगकर्ता के साथ अपनी तस्वीरें साझा करना बंद करें", - "storage": "स्टोरेज की जगह", - "storage_label": "भंडारण लेबल", - "storage_quota": "भंडारण कोटा", - "storage_usage": "{used} में से {available} इस्तेमाल किया हुआ", - "submit": "जमा करना", - "success": "सफलता", - "suggestions": "सुझाव", - "sunrise_on_the_beach": "समुद्र तट पर सूर्योदय", - "support": "सहायता", - "support_and_feedback": "समर्थन और प्रतिक्रिया", - "support_third_party_description": "आपका Immich इंस्टॉलेशन किसी थर्ड-पार्टी ने पैकेज किया था। आपको जो दिक्कतें आ रही हैं, वे उस पैकेज की वजह से हो सकती हैं, इसलिए कृपया नीचे दिए गए लिंक का इस्तेमाल करके सबसे पहले उनके साथ अपनी दिक्कतें बताएं।", - "swap_merge_direction": "मर्ज दिशा स्वैप करें", - "sync": "साथ-साथ करना", - "sync_albums": "एल्बम्स सिंक करें", - "sync_albums_manual_subtitle": "चुने हुए बैकअप एल्बम्स में सभी अपलोड की गई वीडियो और फ़ोटो सिंक करें", - "sync_local": "स्थानीय सिंक करें", - "sync_remote": "सिंक रिमोट", - "sync_status": "सिंक स्थिति", - "sync_status_subtitle": "सिंक सिस्टम देखें और मैनेज करें", - "sync_upload_album_setting_subtitle": "अपनी फ़ोटो और वीडियो बनाएँ और उन्हें इमिच पर चुने हुए एल्बम्स में अपलोड करें", - "tag": "टैग", - "tag_assets": "टैग संपत्तियाँ", - "tag_created": "बनाया गया टैग: {tag}", - "tag_feature_description": "लॉजिकल टैग टॉपिक के हिसाब से ग्रुप किए गए फ़ोटो और वीडियो ब्राउज़ करना", - "tag_not_found_question": "टैग नहीं मिल रहा है? नया टैग बनाएं.", - "tag_people": "लोगों को टैग करें", - "tag_updated": "अपडेट किया गया टैग: {tag}", - "tagged_assets": "टैग किया गया {count, plural, one {# asset} other {# assets}}", - "tags": "टैग्स", - "tap_to_run_job": "जॉब चलाने के लिए टैप करें", - "template": "खाका", - "text_recognition": "पाठ पहचान", - "theme": "विषय", - "theme_selection": "थीम चयन", - "theme_selection_description": "आपके ब्राउज़र की सिस्टम प्राथमिकता के आधार पर थीम को स्वचालित रूप से प्रकाश या अंधेरे पर सेट करें", - "theme_setting_asset_list_storage_indicator_title": "एसेट टाइल्स पर स्टोरेज इंडिकेटर दिखाएं", - "theme_setting_asset_list_tiles_per_row_title": "प्रति पंक्ति एसेट की संख्या ({count})", - "theme_setting_colorful_interface_subtitle": "प्राथमिक रंग को पृष्ठभूमि सतहों पर लागू करें", - "theme_setting_colorful_interface_title": "रंगीन इंटरफ़ेस", - "theme_setting_image_viewer_quality_subtitle": "डिटेल इमेज व्यूअर की क्वालिटी एडजस्ट करें", - "theme_setting_image_viewer_quality_title": "छवि दर्शक गुणवत्ता", - "theme_setting_primary_color_subtitle": "प्राथमिक क्रियाओं और उच्चारणों के लिए एक रंग चुनें", - "theme_setting_primary_color_title": "प्राथमिक रंग", - "theme_setting_system_primary_color_title": "सिस्टम रंग का उपयोग करें", - "theme_setting_system_theme_switch": "ऑटोमैटिक (सिस्टम सेटिंग फ़ॉलो करें)", - "theme_setting_theme_subtitle": "ऐप की थीम सेटिंग चुनें", - "theme_setting_three_stage_loading_subtitle": "थ्री-स्टेज लोडिंग से लोडिंग परफॉर्मेंस बढ़ सकती है लेकिन इससे नेटवर्क लोड काफी बढ़ जाता है", - "theme_setting_three_stage_loading_title": "तीन-चरण लोडिंग सक्षम करें", - "they_will_be_merged_together": "इन्हें एक साथ मिला दिया जाएगा", - "third_party_resources": "तृतीय-पक्ष संसाधन", - "time": "समय", - "time_based_memories": "समय आधारित यादें", - "time_based_memories_duration": "हर इमेज दिखाने में लगने वाला सेकंड।", - "timeline": "समयरेखा", - "timezone": "समय क्षेत्र", - "to_archive": "पुरालेख", - "to_change_password": "पासवर्ड बदलें", - "to_favorite": "पसंदीदा", - "to_login": "लॉग इन करें", - "to_multi_select": "बहु-चयन करने के लिए", - "to_parent": "माता-पिता के पास जाएँ", - "to_select": "चयन करने के लिए", - "to_trash": "कचरा", - "toggle_settings": "सेटिंग्स टॉगल करें", - "total": "कुल", - "total_usage": "कुल उपयोग", - "trash": "कचरा", - "trash_action_prompt": "{count} को ट्रैश में ले जाया गया", - "trash_all": "सब कचरा", - "trash_count": "कचरा {count, number}", - "trash_delete_asset": "संपत्ति को ट्रैश/डिलीट करें", - "trash_emptied": "कचरा खाली कर दिया", - "trash_no_results_message": "ट्रैश की गई फ़ोटो और वीडियो यहां दिखाई देंगे।", - "trash_page_delete_all": "सभी हटा दो", - "trash_page_empty_trash_dialog_content": "क्या आप अपनी कूड़ेदान संपत्तियों को खाली करना चाहते हैं? इन आइटमों को Immich से स्थायी रूप से हटा दिया जाएगा", - "trash_page_info": "ट्रैश किए गए आइटम {days} दिनों के बाद हमेशा के लिए डिलीट कर दिए जाएंगे", - "trash_page_no_assets": "कोई ट्रैश की गई संपत्ति नहीं", - "trash_page_restore_all": "सभी को पुनः स्थानांतरित करें", - "trash_page_select_assets_btn": "संपत्तियों को चयन करें", - "trash_page_title": "कचरा ({count})", - "trashed_items_will_be_permanently_deleted_after": "ट्रैश किए गए आइटम {days, plural, one {# day} other {# days}} के बाद स्थायी रूप से हटा दिए जाएंगे।", - "troubleshoot": "समस्याओं का निवारण", - "type": "प्रकार", - "unable_to_change_pin_code": "पिन कोड बदलने में असमर्थ", - "unable_to_check_version": "ऐप या सर्वर वर्शन चेक नहीं कर पा रहे हैं", - "unable_to_setup_pin_code": "पिन कोड सेट करने में असमर्थ", - "unarchive": "संग्रह से निकालें", - "unarchive_action_prompt": "{count} आर्काइव से हटा दिया गया", - "unarchived_count": "{count, plural, other {Unarchived #}}", - "undo": "पूर्ववत", - "unfavorite": "नापसंद करें", - "unfavorite_action_prompt": "{count} को पसंदीदा से हटा दिया गया", - "unhide_person": "व्यक्ति को उजागर करें", - "unknown": "अज्ञात", - "unknown_country": "अज्ञात देश", - "unknown_year": "अज्ञात वर्ष", - "unlimited": "असीमित", - "unlink_motion_video": "मोशन वीडियो को अनलिंक करें", - "unlink_oauth": "OAuth को अनलिंक करें", - "unlinked_oauth_account": "OAuth खाता अनलिंक किया गया", - "unmute_memories": "यादें अनम्यूट करें", - "unnamed_album": "अनाम एल्बम", - "unnamed_album_delete_confirmation": "क्या आप वाकई इस एल्बम को डिलीट करना चाहते हैं?", - "unnamed_share": "अनाम साझा करें", - "unsaved_change": "सहेजा न गया परिवर्तन", - "unselect_all": "सभी को अचयनित करें", - "unselect_all_duplicates": "सभी डुप्लिकेट को अचयनित करें", - "unselect_all_in": "{group} में सभी का चयन रद्द करें", - "unstack": "स्टैक रद्द करें", - "unstack_action_prompt": "{count} अनस्टैक्ड", - "unstacked_assets_count": "अन-स्टैक्ड {count, plural, one {# asset} other {# assets}}", - "untagged": "टैग नहीं किए गए", - "up_next": "अब अगला", - "update_location_action_prompt": "{count} चुने गए एसेट की लोकेशन अपडेट करें:", - "updated_at": "अपडेट किया गया", - "updated_password": "अद्यतन पासवर्ड", - "upload": "डालना", - "upload_concurrency": "समवर्ती अपलोड करें", - "upload_details": "विवरण अपलोड करें", - "upload_dialog_info": "क्या आप चुने हुए एसेट का सर्वर पर बैकअप लेना चाहते हैं?", - "upload_dialog_title": "संपत्ति अपलोड करें", - "upload_errors": "अपलोड {count, plural, one {# error} other {# errors}} के साथ पूरा हुआ, नए अपलोड एसेट देखने के लिए पेज को रिफ्रेश करें।", - "upload_finished": "अपलोड समाप्त", - "upload_progress": "शेष {remaining, number} - संसाधित {processed, number}/{total, number}", - "upload_skipped_duplicates": "छोड़ा गया {count, plural, one {# duplicate asset} other {# duplicate assets}}", - "upload_status_duplicates": "डुप्लिकेट", - "upload_status_errors": "त्रुटियाँ", - "upload_status_uploaded": "अपलोड किए गए", - "upload_success": "अपलोड सफल रहा, नई अपलोड संपत्तियां देखने के लिए पेज को रीफ्रेश करें।", - "upload_to_immich": "Immich पर अपलोड करें ({count})", - "uploading": "अपलोड हो रहा है", - "uploading_media": "मीडिया अपलोड करना", - "url": "यूआरएल", - "usage": "प्रयोग", - "use_biometric": "बायोमेट्रिक का उपयोग करें", - "use_current_connection": "वर्तमान कनेक्शन का उपयोग करें", - "use_custom_date_range": "इसके बजाय कस्टम दिनांक सीमा का उपयोग करें", - "user": "उपयोगकर्ता", - "user_has_been_deleted": "इस यूज़र को हटा दिया गया है।", - "user_id": "उपयोगकर्ता पहचान", - "user_liked": "{user} ने पसंद किया {type, select, photo {this photo} video {this video} asset {this asset} other {it}}", - "user_pin_code_settings": "पिन कोड", - "user_pin_code_settings_description": "अपना पिन कोड प्रबंधित करें", - "user_privacy": "उपयोगकर्ता गोपनीयता", - "user_purchase_settings": "खरीदना", - "user_purchase_settings_description": "अपनी खरीदारी प्रबंधित करें", - "user_role_set": "{user} को {role} के तौर पर सेट करें", - "user_usage_detail": "उपयोगकर्ता उपयोग विवरण", - "user_usage_stats": "खाता उपयोग के आँकड़े", - "user_usage_stats_description": "खाता उपयोग सांख्यिकी देखें", - "username": "उपयोगकर्ता नाम", - "users": "उपयोगकर्ताओं", - "users_added_to_album_count": "एल्बम में {count, plural, one {# user} other {# users}} जोड़े गए", - "utilities": "उपयोगिताओं", - "validate": "मान्य", - "validate_endpoint_error": "क्रुपया मान्य यूआरएल दर्ज करें", - "variables": "चर", - "version": "संस्करण", - "version_announcement_closing": "आपका मित्र, एलेक्स", - "version_announcement_message": "नमस्ते! Immich का नया वर्शन उपलब्ध है। कृपया रिलीज़ नोट्स पढ़ने के लिए कुछ समय निकालें ताकि यह पक्का हो सके कि आपका सेटअप अप-टू-डेट है ताकि कोई भी गलत कॉन्फ़िगरेशन न हो, खासकर अगर आप WatchTower या कोई ऐसा मैकेनिज़्म इस्तेमाल करते हैं जो आपके Immich इंस्टेंस को ऑटोमैटिकली अपडेट करता है।", - "version_history": "संस्करण इतिहास", - "version_history_item": "{version} को {date} पर इंस्टॉल किया गया", - "video": "वीडियो", - "video_hover_setting": "होवर पर वीडियो थंबनेल चलाएं", - "video_hover_setting_description": "जब माउस आइटम पर घूम रहा हो तो वीडियो थंबनेल चलाएं।", - "videos": "वीडियो", - "videos_count": "{count, plural, one {# Video} other {# Videos}}", - "view": "देखना", - "view_album": "एल्बम देखें", - "view_all": "सभी को देखें", - "view_all_users": "सभी उपयोगकर्ताओं को देखें", - "view_details": "विवरण देखें", - "view_in_timeline": "टाइमलाइन में देखें", - "view_link": "लिंक देखें", - "view_links": "लिंक देखें", - "view_name": "देखना", - "view_next_asset": "अगली संपत्ति देखें", - "view_previous_asset": "पिछली संपत्ति देखें", - "view_qr_code": "QR कोड देखें", - "view_similar_photos": "समान फ़ोटो देखें", - "view_stack": "ढेर देखें", - "view_user": "उपयोगकर्ता देखें", - "viewer_remove_from_stack": "स्टैक से हटाएं", - "viewer_stack_use_as_main_asset": "मुख्य संपत्ति के रूप में उपयोग करें", - "viewer_unstack": "स्टैक रद्द करें", - "visibility_changed": "{count, plural, one {# person} other {# people}} के लिए विज़िबिलिटी बदली गई", - "waiting": "इंतज़ार में", - "warning": "चेतावनी", - "week": "सप्ताह", - "welcome": "स्वागत", - "welcome_to_immich": "Immich में आपका स्वागत है", - "wifi_name": "वाई-फाई का नाम", - "wrong_pin_code": "गलत पिन कोड", - "year": "वर्ष", - "years_ago": "{years, plural, one {# year} other {# years}} पहले", - "yes": "हाँ", - "you_dont_have_any_shared_links": "आपके पास कोई साझा लिंक नहीं है", - "your_wifi_name": "आपके वाईफाई का नाम", - "zoom_image": "छवि ज़ूम करें", - "zoom_to_bounds": "सीमा तक ज़ूम करें" -} +{} diff --git a/i18n/hr.json b/i18n/hr.json index 83cf002c37..0967ef424b 100644 --- a/i18n/hr.json +++ b/i18n/hr.json @@ -1,2165 +1 @@ -{ - "about": "Pojedinosti", - "account": "Račun", - "account_settings": "Postavke računa", - "acknowledge": "Potvrdi", - "action": "Akcija", - "action_common_update": "Ažuriranje", - "actions": "Akcije", - "active": "Aktivno", - "active_count": "Aktivno:{count}", - "activity": "Aktivnost", - "activity_changed": "Aktivnost je {enabled, select, true {omogućena} other {onemogućena}}", - "add": "Dodaj", - "add_a_description": "Dodaj opis", - "add_a_location": "Dodaj lokaciju", - "add_a_name": "Dodaj ime", - "add_a_title": "Dodaj naslov", - "add_birthday": "Dodaj rođendan", - "add_endpoint": "Dodaj krajnju točku", - "add_exclusion_pattern": "Dodaj uzorak izuzimanja", - "add_location": "Dodaj lokaciju", - "add_more_users": "Dodaj još korisnika", - "add_partner": "Dodaj partnera", - "add_path": "Dodaj putanju", - "add_photos": "Dodaj slike", - "add_tag": "Dodaj oznaku", - "add_to": "Dodaj u…", - "add_to_album": "Dodaj u album", - "add_to_album_bottom_sheet_added": "Dodano u {album}", - "add_to_album_bottom_sheet_already_exists": "Već u {album}", - "add_to_album_bottom_sheet_some_local_assets": "Neke lokalne stavke nije moguće dodati u album", - "add_to_album_toggle": "Uključi/isključi odabir za {album}", - "add_to_albums": "Dodaj u albume", - "add_to_albums_count": "Dodaj u albume ({count})", - "add_to_bottom_bar": "Dodaj u", - "add_to_shared_album": "Dodaj u dijeljeni album", - "add_upload_to_stack": "Dodaj preneseno u skup", - "add_url": "Dodaj URL", - "added_to_archive": "Dodano u arhivu", - "added_to_favorites": "Dodano u omiljeno", - "added_to_favorites_count": "Dodano {count, number} u omiljeno", - "admin": { - "add_exclusion_pattern_description": "Dodajte uzorke izuzimanja. Globiranje pomoću *, ** i ? je podržano. Za ignoriranje svih datoteka u bilo kojem direktoriju pod nazivom \"Raw\", koristite \"**/Raw/**\". Kako biste ignorirali sve datoteke koje završavaju na \".tif\", koristite \"**/*.tif\". Kako biste ignorirali apsolutnu putanju, koristite \"/putanja/za/ignoriranje/**\".", - "admin_user": "Administrator", - "asset_offline_description": "Ova stavka vanjske biblioteke nije pronađena na disku i premještena je u smeće. Ako je datoteka premještena unutar biblioteke, provjerite svoju vremensku traku za novu odgovarajuću stavku. Da biste vratili ovu stavku, provjerite može li Immich pristupiti donjoj putanji datoteke i skenirajte biblioteku.", - "authentication_settings": "Postavke autentifikacije", - "authentication_settings_description": "Upravljajte lozinkom, OAuthom i drugim postavkama autentifikacije", - "authentication_settings_disable_all": "Jeste li sigurni da želite onemogućiti sve načine prijave? Prijava će biti potpuno onemogućena.", - "authentication_settings_reenable": "Za ponovno uključivanje upotrijebite naredbu servera.", - "background_task_job": "Pozadinski zadaci", - "backup_database": "Kreiraj sigurnosnu kopiju baze podataka", - "backup_database_enable_description": "Omogućite sigurnosne kopije baze podataka", - "backup_keep_last_amount": "Količina prethodnih sigurnosnih kopija za čuvanje", - "backup_onboarding_1_description": "kopija izvan lokacije u oblaku ili na drugoj fizičkoj lokaciji.", - "backup_onboarding_2_description": "lokalne kopije na različitim uređajima. To uključuje glavne datoteke i lokalnu sigurnosnu kopiju tih datoteka.", - "backup_onboarding_3_description": "ukupne kopije vaših podataka, uključujući izvorne datoteke. To uključuje 1 kopiju izvan lokacije i 2 lokalne kopije.", - "backup_onboarding_description": "Preporučuje se 3-2-1 strategija sigurnosnog kopiranja za zaštitu vaših podataka. Trebali biste čuvati kopije svojih prenesenih fotografija/videozapisa kao i Immich bazu podataka za sveobuhvatno rješenje sigurnosne kopije.", - "backup_onboarding_footer": "Za više informacija o sigurnosnom kopiranju Immicha, molimo pogledajte dokumentaciju.", - "backup_onboarding_parts_title": "3-2-1 sigurnosna kopija uključuje:", - "backup_onboarding_title": "Sigurnosne kopije", - "backup_settings": "Postavke sigurnosne kopije baze podataka", - "backup_settings_description": "Upravljajte postavkama sigurnosne kopije baze podataka.", - "cleared_jobs": "Izbrisani poslovi za: {job}", - "config_set_by_file": "Konfiguracija je trenutno postavljena konfiguracijskom datotekom", - "confirm_delete_library": "Jeste li sigurni da želite izbrisati biblioteku {library}?", - "confirm_delete_library_assets": "Jeste li sigurni da želite izbrisati ovu biblioteku? Time će {count, plural, one {biti izbrisana # sadržana stavka} few {biti izbrisane sve # sadržane stavke} other {biti izbrisano svih # sadržanih stavki}} iz Immicha i to se ne može poništiti. Datoteke će ostati na disku.", - "confirm_email_below": "Za potvrdu upišite \"{email}\" ispod", - "confirm_reprocess_all_faces": "Jeste li sigurni da želite ponovno obraditi sva lica? Ovo će također obrisati imenovane osobe.", - "confirm_user_password_reset": "Jeste li sigurni da želite poništiti lozinku korisnika {user}?", - "confirm_user_pin_code_reset": "Jeste li sigurni da želite resetirati PIN korisnika {user}?", - "copy_config_to_clipboard_description": "Kopiraj trenutne postavke sustava kao JSON objekt u međuspremnik", - "create_job": "Stvori posao", - "cron_expression": "Cron izraz", - "cron_expression_description": "Postavite interval skeniranja koristeći cron format. Za više informacija pogledajte npr. Crontab Guru", - "cron_expression_presets": "Unaprijed postavljene postavke cron izraza", - "disable_login": "Onemogući prijavu", - "duplicate_detection_job_description": "Pokrenite strojno učenje na stavkama kako biste otkrili slične slike. Oslanja se na Pametno pretraživanje", - "exclusion_pattern_description": "Uzorci izuzimanja omogućuju vam da ignorirate datoteke i mape prilikom skeniranja svoje biblioteke. Ovo je korisno ako imate mape koje sadrže datoteke koje ne želite uvesti, kao što su RAW datoteke.", - "export_config_as_json_description": "Preuzmi trenutnu konfiguraciju sustava kao JSON datoteku", - "external_libraries_page_description": "Administracijska stranica vanjskih programskih biblioteka", - "face_detection": "Detekcija lica", - "face_detection_description": "Detektirajte lica u stavkama pomoću strojnog učenja. Za videozapise se uzima u obzir samo sličica. \"Osvježi\" (ponovno) obrađuje sve stavke. \"Poništi\" dodatno briše sve trenutne podatke o licu. \"Nedostaje\" stavlja u red čekanja stavke koje još nisu obrađene. Detektirana lica bit će stavljena u red čekanja za prepoznavanje lica nakon što se dovrši detekcija lica, grupirajući ih u postojeće ili nove osobe.", - "facial_recognition_job_description": "Grupirajte otkrivena lica u osobe. Ovaj korak se izvršava nakon što je Detekcija lica dovršena. \"Resetiraj\" (ponovno) grupira sva lica. \"Nedostaje\" stavlja u red lica kojima nije dodijeljena osoba.", - "failed_job_command": "Naredba {command} nije uspjela za posao: {job}", - "force_delete_user_warning": "UPOZORENJE: Ovo će odmah ukloniti korisnika i sve pripadajuće stavke. Ovo se ne može poništiti i datoteke se ne mogu vratiti.", - "image_format": "Format", - "image_format_description": "WebP proizvodi manje datoteke od JPEG-a, ali se sporije kodira.", - "image_fullsize_description": "Slika pune veličine bez metapodataka, koristi se prilikom zumiranja", - "image_fullsize_enabled": "Omogući generiranje slika pune veličine", - "image_fullsize_enabled_description": "Generiraj sliku pune veličine za formate koji nisu prilagođeni webu. Kada je opcija \"Preferiraj ugrađeni pregled\" omogućena, ugrađeni pregledi koriste se izravno bez konverzije. Ne utječe na formate prilagođene webu kao što je JPEG.", - "image_fullsize_quality_description": "Kvaliteta slike pune veličine od 1 do 100. Veća vrijednost znači bolja kvaliteta, ali stvara veće datoteke.", - "image_fullsize_title": "Postavke slike pune veličine", - "image_prefer_embedded_preview": "Preferiraj ugrađeni pregled", - "image_prefer_embedded_preview_setting_description": "Koristite ugrađene preglede u RAW fotografije kao ulaz za obradu slike kada su dostupni. To može proizvesti preciznije boje za neke slike, ali kvaliteta pregleda ovisi o kameri i slika može imati više artifakta kompresije.", - "image_prefer_wide_gamut": "Preferirajte široku gamu", - "image_prefer_wide_gamut_setting_description": "Koristite Display P3 za sličice. Ovo bolje čuva živost slika sa širokim prostorima boja, ali slike mogu izgledati drugačije na starim uređajima sa starom verzijom preglednika. sRGB slike čuvaju se kao sRGB kako bi se izbjegle promjene boja.", - "image_preview_description": "Slika srednje veličine s uklonjenim metapodacima, koristi se prilikom pregledavanja jedne stavke i za strojno učenje", - "image_preview_quality_description": "Kvaliteta pregleda od 1-100. Više je bolje, ali proizvodi veće datoteke i može smanjiti odziv aplikacije. Postavljanje niske vrijednosti može utjecati na kvalitetu strojnog učenja.", - "image_preview_title": "Postavke pregleda", - "image_quality": "Kvaliteta", - "image_resolution": "Rezolucija", - "image_resolution_description": "Veće razlučivosti mogu sačuvati više detalja, ali trebaju dulje za kodiranje, imaju veće veličine datoteka i mogu smanjiti odziv aplikacije.", - "image_settings": "Postavke slike", - "image_settings_description": "Upravljajte kvalitetom i rezolucijom generiranih slika", - "image_thumbnail_description": "Mala minijatura s ogoljenim metapodacima, koristi se pri gledanju grupa fotografija poput glavne vremenske trake", - "image_thumbnail_quality_description": "Kvaliteta sličica od 1-100. Više je bolje, ali proizvodi veće datoteke i može smanjiti odziv aplikacije.", - "image_thumbnail_title": "Postavke sličica", - "import_config_from_json_description": "Učitaj konfiguraciju sustava učitavanjem JSON datoteke konfiguracije", - "job_concurrency": "{job} istovremenost", - "job_created": "Zadatak je kreiran", - "job_not_concurrency_safe": "Ovaj posao nije siguran za istovremenost.", - "job_settings": "Postavke posla", - "job_settings_description": "Upravljajte istovremenošću poslova", - "jobs_delayed": "{jobCount, plural, other {# odgođenih}}", - "jobs_failed": "{jobCount, plural, one {# neuspješan} few {# neuspješna} other {# neuspješnih}}", - "jobs_over_time": "Zadaci kroz vrijeme", - "library_created": "Stvorena biblioteka: {library}", - "library_deleted": "Biblioteka izbrisana", - "library_details": "Detalji biblioteke", - "library_folder_description": "Odredi mapu za učitavanje. U ovoj mapi, uključujući podmape, će se pretražiti slike i videi.", - "library_remove_exclusion_pattern_prompt": "Jeste li sigurni da želite ukloniti ovaj uzorak isključivanja?", - "library_remove_folder_prompt": "Jeste li sigurni da želite ukloniti ovu mapu označenu za učitavanje?", - "library_scanning": "Periodično skeniranje", - "library_scanning_description": "Konfigurirajte periodično skeniranje biblioteke", - "library_scanning_enable_description": "Omogući periodično skeniranje biblioteke", - "library_settings": "Vanjska biblioteka", - "library_settings_description": "Upravljajte postavkama vanjske biblioteke", - "library_tasks_description": "Skeniraj vanjske biblioteke za nove i/ili promijenjene stavke", - "library_updated": "Ažurirana programska biblioteka", - "library_watching_enable_description": "Pratite vanjske biblioteke za promjena datoteke", - "library_watching_settings": "Gledanje biblioteke [EKSPERIMENTALNO]", - "library_watching_settings_description": "Automatsko praćenje promijenjenih datoteke", - "logging_enable_description": "Omogući zapisivanje", - "logging_level_description": "Kada je omogućeno, koju razinu zapisivanja koristiti.", - "logging_settings": "Zapisivanje", - "machine_learning_availability_checks": "Provjere dostupnosti", - "machine_learning_availability_checks_description": "Automatski detektiraj i preferiraj dostupne servere mašinskog učenja", - "machine_learning_availability_checks_enabled": "Omogući provjere dostupnosti", - "machine_learning_availability_checks_interval": "Provjeri interval", - "machine_learning_availability_checks_interval_description": "Interval u milisekundama između provjera dostupnosti", - "machine_learning_availability_checks_timeout": "Zahtjev istekao", - "machine_learning_availability_checks_timeout_description": "Vrijeme čekanja u milisekundama za provjeru dostupnosti", - "machine_learning_clip_model": "CLIP model", - "machine_learning_clip_model_description": "Naziv CLIP modela navedenog ovdje. Imajte na umu da morate ponovno pokrenuti posao 'Pametno Pretraživanje' za sve slike nakon promjene modela.", - "machine_learning_duplicate_detection": "Detekcija duplikata", - "machine_learning_duplicate_detection_enabled": "Omogući detekciju duplikata", - "machine_learning_duplicate_detection_enabled_description": "Ako je onemogućeno, potpuno identične stavke i dalje će biti deduplicirane.", - "machine_learning_duplicate_detection_setting_description": "Upotrijebite CLIP ugradnje da biste pronašli vjerojatne duplikate", - "machine_learning_enabled": "Uključi strojsko učenje", - "machine_learning_enabled_description": "Ukoliko je ovo isključeno, sve funkcije strojnoga učenja biti će isključene bez obzira na postavke ispod.", - "machine_learning_facial_recognition": "Prepoznavanje lica", - "machine_learning_facial_recognition_description": "Detektiraj, prepoznaj i grupiraj lica u fotografijama", - "machine_learning_facial_recognition_model": "Model prepoznavanja lica", - "machine_learning_facial_recognition_model_description": "Modeli su navedeni silaznim redoslijedom veličine. Veći modeli su sporiji i koriste više memorije, ali daju bolje rezultate. Imajte na umu da morate ponovno pokrenuti posao detekcije lica za sve slike nakon promjene modela.", - "machine_learning_facial_recognition_setting": "Omogući prepoznavanje lica", - "machine_learning_facial_recognition_setting_description": "Ako je onemogućeno, slike neće biti kodirane za prepoznavanje lica i neće popuniti odjeljak Osobe na stranici Istraži.", - "machine_learning_max_detection_distance": "Maksimalna udaljenost za detektiranje", - "machine_learning_max_detection_distance_description": "Maksimalna udaljenost između dvije slike da bi se smatrale duplikatima, u rasponu od 0,001-0,1. Više vrijednosti otkrit će više duplikata, ali mogu rezultirati netočnim rezultatima.", - "machine_learning_max_recognition_distance": "Maksimalna udaljenost za detekciju", - "machine_learning_max_recognition_distance_description": "Maksimalna udaljenost između dva lica koja se smatraju istom osobom, u rasponu od 0-2. Snižavanje može spriječiti označavanje dvije osobe kao iste osobe, dok podizanje može spriječiti označavanje iste osobe kao dvije različite osobe. Imajte na umu da je lakše spojiti dvije osobe nego jednu osobu podijeliti na dvije, stoga koristite niži prag kada je to moguće.", - "machine_learning_min_detection_score": "Minimalni rezultat otkrivanja", - "machine_learning_min_detection_score_description": "Minimalni rezultat pouzdanosti za detektirano lice od 0-1. Niže vrijednosti otkrit će više lica, ali mogu dovesti do lažno pozitivnih rezultata.", - "machine_learning_min_recognized_faces": "Minimum prepoznatih lica", - "machine_learning_min_recognized_faces_description": "Najmanji broj prepoznatih lica za osobu koja se stvara. Povećanje toga čini Prepoznavanje lica preciznijim po cijenu povećanja šanse da lice nije dodijeljeno osobi.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Koristi strojno učenje za prepoznavanje teksta u slikama", - "machine_learning_ocr_enabled": "Omogući OCR", - "machine_learning_ocr_enabled_description": "Ukoliko se onemogući, slike neće prolaziti kroz postupak prepoznavanja teksta.", - "machine_learning_ocr_max_resolution": "Maksimalna razlučivost", - "machine_learning_ocr_max_resolution_description": "Pregledi preko ove razlučivosti će promijeniti veličinu poštujući omjer slike. Veće vrijednosti su točnije, ali trebaju više vremena za obradu i koriste više memorije.", - "machine_learning_ocr_min_detection_score": "Minimalna ocjena prepoznavanja", - "machine_learning_settings": "Postavke strojnog učenja", - "machine_learning_settings_description": "Upravljajte značajkama i postavkama strojnog učenja", - "machine_learning_smart_search": "Pametna pretraga", - "machine_learning_smart_search_description": "Pretražujte slike semantički koristeći CLIP ugradnje", - "machine_learning_smart_search_enabled": "Omogući pametno pretraživanje", - "machine_learning_smart_search_enabled_description": "Ako je onemogućeno, slike neće biti kodirane za pametno pretraživanje.", - "machine_learning_url_description": "URL poslužitelja strojnog učenja. Ako ste dodali više od jednog URLa, svaki server će biti kontaktiraj jedanput dok jedan ne odgovori uspješno, u redu od prvog do zadnjeg. Serveri koji ne odgovore će privremeno biti ignorirani dok ponovo ne postanu dostupni.", - "manage_concurrency": "Upravljanje Istovremenošću", - "manage_log_settings": "Upravljanje postavkama zapisivanje", - "map_dark_style": "Tamni stil", - "map_enable_description": "Omogući značajke karte", - "map_gps_settings": "Postavke karte i GPS-a", - "map_gps_settings_description": "Upravljajte postavkama karte i GPS-a (obrnutog geokodiranja)", - "map_implications": "Značajka karte se oslanja na vanjsku uslugu pločica (tiles.immich.cloud)", - "map_light_style": "Svijetli stil", - "map_manage_reverse_geocoding_settings": "Upravljajte postavkama Obrnutog geokodiranja", - "map_reverse_geocoding": "Obrnuto Geokodiranje", - "map_reverse_geocoding_enable_description": "Omogući obrnuto geokodiranje", - "map_reverse_geocoding_settings": "Postavke Obrnutog geokodiranja", - "map_settings": "Karta", - "map_settings_description": "Upravljajte postavkama karte", - "map_style_description": "URL na style.json temu karte", - "memory_cleanup_job": "Čišćenje memorije", - "memory_generate_job": "Generiranje memorije", - "metadata_extraction_job": "Izdvoj metapodatke", - "metadata_extraction_job_description": "Izdvojite metapodatke iz svake stavke, kao što su GPS, lica i rezolucija", - "metadata_faces_import_setting": "Omogući uvoz lica", - "metadata_faces_import_setting_description": "Uvezite lica iz EXIF podataka slike i sidecar datoteka", - "metadata_settings": "Postavke metapodataka", - "metadata_settings_description": "Upravljanje postavkama metapodataka", - "migration_job": "Migracija", - "migration_job_description": "Premjestite sličice za stavke i lica u najnoviju strukturu mapa", - "nightly_tasks_cluster_faces_setting_description": "Pokreni prepoznavanje lica na novootkrivenim licima", - "nightly_tasks_cluster_new_faces_setting": "Grupiraj nova lica", - "nightly_tasks_database_cleanup_setting": "Zadaci čišćenja baze podataka", - "nightly_tasks_database_cleanup_setting_description": "Očisti stare, istekle podatke iz baze podataka", - "nightly_tasks_generate_memories_setting": "Generiraj uspomene", - "nightly_tasks_generate_memories_setting_description": "Stvori nove uspomene iz stavki", - "nightly_tasks_missing_thumbnails_setting": "Generiraj nedostajuće sličice", - "nightly_tasks_missing_thumbnails_setting_description": "Stavke bez sličica stavi u red čekanja za generiranje sličica", - "nightly_tasks_settings": "Postavke noćnih zadataka", - "nightly_tasks_settings_description": "Upravljanje noćnim zadacima", - "nightly_tasks_start_time_setting": "Vrijeme početka", - "nightly_tasks_start_time_setting_description": "Vrijeme pokretanja noćnih zadataka na poslužitelju", - "nightly_tasks_sync_quota_usage_setting": "Sinkroniziraj iskorištenost kvote", - "nightly_tasks_sync_quota_usage_setting_description": "Ažuriraj korisničku kvotu za pohranu na temelju trenutne potrošnje", - "no_paths_added": "Nema dodanih putanja", - "no_pattern_added": "Nije dodan uzorak", - "note_apply_storage_label_previous_assets": "Napomena: Da biste primijenili oznaku pohrane na prethodno prenesene stavke, pokrenite", - "note_cannot_be_changed_later": "NAPOMENA: Ovo se ne može promijeniti kasnije!", - "notification_email_from_address": "Od adrese", - "notification_email_from_address_description": "E-mail adresa pošiljatelja, na primjer: \"Immich Photo Server \". Obavezno koristite adresu s koje vam je dopušteno slanje e-pošte.", - "notification_email_host_description": "Poslužitelja e-pošte (npr. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignoriraj pogreške certifikata", - "notification_email_ignore_certificate_errors_description": "Ignoriraj pogreške provjere valjanosti TLS certifikata (nije preporučeno)", - "notification_email_password_description": "Lozinka za korištenje pri autentifikaciji s poslužiteljem e-pošte", - "notification_email_port_description": "Port poslužitelja e-pošte (npr. 25, 465, ili 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Koristi SMTPS (SMTP umjesto TLS)", - "notification_email_sent_test_email_button": "Pošaljite probni e-mail i spremi", - "notification_email_setting_description": "Postavke za slanje e-mail obavijeste", - "notification_email_test_email": "Pošalji probni e-mail", - "notification_email_test_email_failed": "Slanje testne e-pošte nije uspjelo, provjerite svoje postavke", - "notification_email_test_email_sent": "Testna e-poruka poslana je na {email}. Provjerite svoju pristiglu poštu.", - "notification_email_username_description": "Korisničko ime koje se koristi pri autentifikaciji s poslužiteljem e-pošte", - "notification_enable_email_notifications": "Omogući obavijesti putem e-pošte", - "notification_settings": "Postavke obavijesti", - "notification_settings_description": "Upravljanje postavkama obavijesti, uključujući e-poštu", - "oauth_auto_launch": "Automatsko pokretanje", - "oauth_auto_launch_description": "Automatski pokrenite OAuth prijavu nakon navigacije na stranicu za prijavu", - "oauth_auto_register": "Automatska registracija", - "oauth_auto_register_description": "Automatski registrirajte nove korisnike nakon prijave s OAuth", - "oauth_button_text": "Tekst gumba", - "oauth_client_secret_description": "Obavezno ukoliko PKCE (Proof Key for Code Exchange) nije podržan od strane OAuth pružatelja", - "oauth_enable_description": "Prijavite se putem OAutha", - "oauth_mobile_redirect_uri": "Mobilnog Preusmjeravanja URI", - "oauth_mobile_redirect_uri_override": "Nadjačavanje URI-preusmjeravanja za mobilne uređaje", - "oauth_mobile_redirect_uri_override_description": "Omogući kada pružatelj OAuth ne dopušta mobilni URI, poput ''{callback}''", - "oauth_role_claim": "Dodjela uloge", - "oauth_role_claim_description": "Automatski dodijeli administratorski pristup na temelju prisutnosti ove tvrdnje. Tvrdnja može sadržavati ili 'user' ili 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Upravljanje postavkama za prijavu kroz OAuth", - "oauth_settings_more_details": "Za više pojedinosti o ovoj značajci pogledajte uputstva.", - "oauth_storage_label_claim": "Potraživanje oznake za pohranu", - "oauth_storage_label_claim_description": "Automatski postavite korisničku oznaku za pohranu na vrijednost ovog zahtjeva.", - "oauth_storage_quota_claim": "Zahtjev za kvotom pohrane", - "oauth_storage_quota_claim_description": "Automatski postavite korisničku kvotu pohrane na vrijednost ovog zahtjeva.", - "oauth_storage_quota_default": "Zadana kvota pohrane (GiB)", - "oauth_storage_quota_default_description": "Kvota u GiB koja će se koristiti kada nema zahtjeva.", - "oauth_timeout": "Istek vremena zahtjeva", - "oauth_timeout_description": "Istek vremena zahtjeva je u milisekundama", - "password_enable_description": "Prijava s email adresom i zaporkom", - "password_settings": "Prijava zaporkom", - "password_settings_description": "Upravljanje postavkama za prijavu zaporkom", - "paths_validated_successfully": "Sve su putanje uspješno potvrđene", - "person_cleanup_job": "Čišćenje lica", - "quota_size_gib": "Veličina kvote (GiB)", - "refreshing_all_libraries": "Osvježavanje svih biblioteka", - "registration": "Registracija administratora", - "registration_description": "Budući da ste prvi korisnik na sustavu, bit ćete dodijeljeni administratorsku ulogu i odgovorni ste za administrativne poslove, a dodatne korisnike kreirat ćete sami.", - "require_password_change_on_login": "Zahtijevajte od korisnika promjenu lozinke pri prvoj prijavi", - "reset_settings_to_default": "Vrati postavke na zadane", - "reset_settings_to_recent_saved": "Resetirajte postavke na nedavno spremljene postavke", - "scanning_library": "Skeniranje biblioteke", - "search_jobs": "Traži zadatke…", - "send_welcome_email": "Pošaljite email dobrodošlice", - "server_external_domain_settings": "Vanjska domena", - "server_external_domain_settings_description": "Domena za javno dijeljene linkove, uključujući http(s)://", - "server_public_users": "Javni korisnici", - "server_public_users_description": "Svi korisnici (ime i e-pošta) navedeni su prilikom dodavanja korisnika u dijeljene albume. Kada je onemogućeno, popis korisnika bit će dostupan samo korisnicima administratora.", - "server_settings": "Postavke servera", - "server_settings_description": "Upravljanje postavkama servera", - "server_welcome_message": "Poruka dobrodošlice", - "server_welcome_message_description": "Poruka koja je prikazana na prijavi.", - "sidecar_job": "Sidecar metapodaci", - "sidecar_job_description": "Otkrijte ili sinkronizirajte sidecar metapodatke iz datotečnog sustava", - "slideshow_duration_description": "Broj sekundi za prikaz svake slike", - "smart_search_job_description": "Pokrenite strojno učenje na stavkama za korištenje pametnog pretraživanja", - "storage_template_date_time_description": "Vremenska oznaka stvaranja stavke koristi se za informacije o datumu i vremenu", - "storage_template_date_time_sample": "Vrijeme uzorka {date}", - "storage_template_enable_description": "Omogući mehanizam predloška za pohranu", - "storage_template_hash_verification_enabled": "Omogućena hash provjera", - "storage_template_hash_verification_enabled_description": "Omogućuje hash provjeru, nemojte je onemogućiti osim ako niste sigurni u implikacije", - "storage_template_migration": "Migracija predloška za pohranu", - "storage_template_migration_description": "Primijenite trenutni {template} na prethodno prenesene stavke", - "storage_template_migration_info": "Predložak za pohranu pretvorit će sve datotečne nastavke u mala slova. Promjene predloška primijenit će se samo na nove stavke. Da biste retroaktivno primijenili predložak na prethodno prenesene stavke, pokrenite {job}.", - "storage_template_migration_job": "Posao Migracije Predloška Pohrane", - "storage_template_more_details": "Za više pojedinosti o ovoj značajci pogledajte Predložak pohrane i njegove implikacije", - "storage_template_onboarding_description_v2": "Kada je omogućena, ova će značajka automatski organizira datoteke prema predlošku koji je definirao korisnik. Za više informacija pogledajte dokumentaciju.", - "storage_template_path_length": "Približno ograničenje duljine putanje: {length, number}/{limit, number}", - "storage_template_settings": "Predložak pohrane", - "storage_template_settings_description": "Upravljajte strukturom mape i nazivom datoteke učitane stavke", - "storage_template_user_label": "{label} je korisnička oznaka za pohranu", - "system_settings": "Postavke sustava", - "tag_cleanup_job": "Čišćenje oznaka", - "template_email_available_tags": "Možete koristiti sljedeće varijable u vašem predlošku:{tags}", - "template_email_if_empty": "Ukoliko je predložak prazan, koristit će se zadana e-mail adresa.", - "template_email_invite_album": "Predložak za pozivnicu u album", - "template_email_preview": "Pregled", - "template_email_settings": "E-mail predlošci", - "template_email_update_album": "Ažuriraj Album Predložak", - "template_email_welcome": "Predložak e-maila dobrodošlice", - "template_settings": "Predložak Obavijesti", - "template_settings_description": "Upravljaj prilagođenim predlošcima za obavijesti", - "theme_custom_css_settings": "Prilagođeni CSS", - "theme_custom_css_settings_description": "Kaskadni listovi stilova (CSS) omogućuju prilagođavanje dizajna Immicha.", - "theme_settings": "Postavke tema", - "theme_settings_description": "Upravljajte prilagodbom Immich web sučelja", - "thumbnail_generation_job": "Generirajte sličice", - "thumbnail_generation_job_description": "Generirajte velike, male i zamućene sličice za svaku stavku, kao i sličice za svaku osobu", - "transcoding_acceleration_api": "API ubrzanja", - "transcoding_acceleration_api_description": "API koji će komunicirati s vašim uređajem radi ubrzanja transkodiranja. Ova postavka je 'najveći trud': vratit će se na softversko transkodiranje u slučaju kvara. VP9 može ili ne mora raditi ovisno o vašem hardveru.", - "transcoding_acceleration_nvenc": "NVENC (zahtjeva NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (zahtjeva Intel CPU sedme ili veće generacije)", - "transcoding_acceleration_rkmpp": "RKMPP (samo na Rockchip SOCima)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Prihvačeni audio kodeci", - "transcoding_accepted_audio_codecs_description": "Odaberite koji audio kodeci ne trebaju biti transkodirani. Samo korišteno za neka pravila za transkodiranje.", - "transcoding_accepted_containers": "Prihvaćeni kontenjeri", - "transcoding_accepted_containers_description": "Odaberite koji formati spremnika ne moraju biti remulksirani u MP4. Koristi se samo za određena pravila transkodiranja.", - "transcoding_accepted_video_codecs": "Prihvaćeni video kodeci", - "transcoding_accepted_video_codecs_description": "Odaberite koje video kodeke nije potrebno transkodirati. Koristi se samo za određena pravila transkodiranja.", - "transcoding_advanced_options_description": "Postavke većina korisnika ne treba mjenjati", - "transcoding_audio_codec": "Audio kodek", - "transcoding_audio_codec_description": "Opus je opcija s najvećom kvalitetom, no ima manju podršku s starim uređajima i softverima.", - "transcoding_bitrate_description": "Videozapisi veći od maksimalne brzine prijenosa ili nisu u prihvatljivom formatu", - "transcoding_codecs_learn_more": "Da biste saznali više o terminologiji koja se ovdje koristi, pogledajte FFmpeg dokumentaciju za H.264 kodek, HEVC kodek i VP9 kodek.", - "transcoding_constant_quality_mode": "Način stalne kvalitete", - "transcoding_constant_quality_mode_description": "ICQ je bolji od CQP-a, ali neki uređaji za hardversko ubrzanje ne podržavaju ovaj način rada. Postavljanje ove opcije daje prednost navedenom načinu rada kada se koristi kodiranje temeljeno na kvaliteti. NVENC je zanemaren jer ne podržava ICQ.", - "transcoding_constant_rate_factor": "Faktor konstantne stope (-crf)", - "transcoding_constant_rate_factor_description": "Razina kvalitete videa. Uobičajene vrijednosti su 23 za H.264, 28 za HEVC, 31 za VP9 i 35 za AV1. Niže je bolje, ali stvara veće datoteke.", - "transcoding_disabled_description": "Nemojte transkodirati nijedan videozapis, može prekinuti reprodukciju na nekim klijentima", - "transcoding_encoding_options": "Opcije kodiranja", - "transcoding_encoding_options_description": "Postavi kodeke, rezoluciju, kvalitetu i druge opcije za kodirane videje", - "transcoding_hardware_acceleration": "Hardversko ubrzanje", - "transcoding_hardware_acceleration_description": "Eksperimentalno: brže transkodiranje, ali može smanjiti kvalitetu pri istoj brzini prijenosa", - "transcoding_hardware_decoding": "Hardversko dekodiranje", - "transcoding_hardware_decoding_setting_description": "Odnosi se samo na NVENC, QSV i RKMPP. Omogućuje ubrzanje s kraja na kraj umjesto samo ubrzavanja kodiranja. Možda neće raditi na svim videozapisima.", - "transcoding_max_b_frames": "Maksimalni B-frameovi", - "transcoding_max_b_frames_description": "Više vrijednosti poboljšavaju učinkovitost kompresije, ali usporavaju kodiranje. Možda nije kompatibilan s hardverskim ubrzanjem na starijim uređajima. 0 onemogućuje B-frameove, dok -1 automatski postavlja ovu vrijednost.", - "transcoding_max_bitrate": "Maksimalne brzina prijenosa (bitrate)", - "transcoding_max_bitrate_description": "Postavljanje maksimalne brzine prijenosa može učiniti veličine datoteka predvidljivijima uz manji gubitak kvalitete. Pri 720p, tipične vrijednosti su 2600 kbit/s za VP9 ili HEVC, te 4500 kbit/s za H.264. Onemogućeno ako je postavljeno na 0. Kada nije navedena mjerna jedinica, pretpostavlja se k (za kbit/s); stoga su 5000, 5000k i 5M (za Mbit/s) ekvivalentni.", - "transcoding_max_keyframe_interval": "Maksimalni interval ključnih sličica", - "transcoding_max_keyframe_interval_description": "Postavlja maksimalnu udaljenost slika između ključnih kadrova. Niže vrijednosti pogoršavaju učinkovitost kompresije, ali poboljšavaju vrijeme traženja i mogu poboljšati kvalitetu u scenama s brzim kretanjem. 0 automatski postavlja ovu vrijednost.", - "transcoding_optimal_description": "Videozapisi koji su veći od ciljne rezolucije ili nisu u prihvatljivom formatu", - "transcoding_policy": "Pravila transkodiranja", - "transcoding_policy_description": "Postavi kada će video biti transkodiran", - "transcoding_preferred_hardware_device": "Preferirani hardverski uređaj", - "transcoding_preferred_hardware_device_description": "Odnosi se samo na VAAPI i QSV. Postavlja dri node koji se koristi za hardversko transkodiranje.", - "transcoding_preset_preset": "Preset (-preset)", - "transcoding_preset_preset_description": "Brzina kompresije. Sporije postavke proizvode manje datoteke i povećavaju kvalitetu pri ciljanju određene postavke bitratea. VP9 zanemaruje brzine iznad 'brže'.", - "transcoding_reference_frames": "Referentne slike", - "transcoding_reference_frames_description": "Broj slika za referencu prilikom komprimiranja određene slike. Više vrijednosti poboljšavaju učinkovitost kompresije, ali usporavaju kodiranje. 0 automatski postavlja ovu vrijednost.", - "transcoding_required_description": "Samo videozapisi koji nisu u prihvaćenom formatu", - "transcoding_settings": "Postavke video transkodiranja", - "transcoding_settings_description": "Upravljaj koji videozapisi će se transkodirati i kako ih obraditi", - "transcoding_target_resolution": "Ciljana rezolucija", - "transcoding_target_resolution_description": "Veće razlučivosti mogu sačuvati više detalja, ali trebaju dulje za kodiranje, imaju veće veličine datoteka i mogu smanjiti odziv aplikacije.", - "transcoding_temporal_aq": "Vremenski AQ", - "transcoding_temporal_aq_description": "Odnosi se samo na NVENC. Vremenska adaptivna kvantizacija povećava kvalitetu scena s mnogim detaljima i malo kretanja. Možda nije kompatibilno sa starijim uređajima.", - "transcoding_threads": "Sljedovi (Threads)", - "transcoding_threads_description": "Više vrijednosti dovode do bržeg kodiranja, ali ostavljaju manje prostora poslužitelju za obradu drugih zadataka dok je aktivan. Ova vrijednost ne smije biti veća od broja CPU jezgri. Maksimalno povećava iskorištenje ako je postavljeno na 0.", - "transcoding_tone_mapping": "Tonsko preslikavanje", - "transcoding_tone_mapping_description": "Pokušava sačuvati izgled HDR videozapisa kada se pretvori u SDR. Svaki algoritam čini različite kompromise za boju, detalje i svjetlinu. Hable čuva detalje, Mobius čuva boju, a Reinhard svjetlinu.", - "transcoding_transcode_policy": "Pravila transkodiranja", - "transcoding_transcode_policy_description": "Pravila o tome kada se video treba transkodirati. HDR videozapisi uvijek će biti transkodirani (osim ako je transkodiranje onemogućeno).", - "transcoding_two_pass_encoding": "Kodiranje u dva prolaza", - "transcoding_two_pass_encoding_setting_description": "Transkodiranje u dva prolaza za proizvodnju bolje kodiranih videozapisa. Kada je omogućena maksimalna brzina prijenosa (potrebna za rad s H.264 i HEVC), ovaj način rada koristi raspon brzine prijenosa na temelju maksimalne brzine prijenosa i zanemaruje CRF. Za VP9, CRF se može koristiti ako je maksimalna brzina prijenosa onemogućena.", - "transcoding_video_codec": "Video kodek", - "transcoding_video_codec_description": "VP9 ima visoku učinkovitost i web-kompatibilnost, ali treba dulje za transkodiranje. HEVC ima sličnu izvedbu, ali ima slabiju web kompatibilnost. H.264 široko je kompatibilan i brzo se transkodira, ali proizvodi mnogo veće datoteke. AV1 je najučinkovitiji kodek, ali nema podršku na starijim uređajima.", - "trash_enabled_description": "Omogući značajke smeća", - "trash_number_of_days": "Broj dana", - "trash_number_of_days_description": "Broj dana za čuvanje stavki u smeću prije njihovog trajnog uklanjanja", - "trash_settings": "Postavke smeća", - "trash_settings_description": "Upravljanje postavkama smeća", - "unlink_all_oauth_accounts": "Odspoji sve OAuth račune", - "unlink_all_oauth_accounts_description": "Zapamtite da odspojite sve OAuth račune prije prelaska na novog pružatelja usluge.", - "unlink_all_oauth_accounts_prompt": "Jeste li sigurni da želite odspojiti sve OAuth račune? Ovo će resetirati OAuth ID za svakog korisnika i ne može se poništiti.", - "user_cleanup_job": "Čišćenje korisnika", - "user_delete_delay": "Račun i stavke korisnika {user} bit će stavljeni u red čekanja trajnog brisanja za {delay, plural, one {# dan} other {# dana}}.", - "user_delete_delay_settings": "Brisanje odgode", - "user_delete_delay_settings_description": "Broj dana nakon uklanjanja za trajno brisanje korisničkog računa i stavki. Posao brisanja korisnika pokreće se u ponoć kako bi se provjerili korisnici koji su spremni za brisanje. Promjene ove postavke bit će procijenjene pri sljedećem izvršavanju.", - "user_delete_immediately": "Račun i stavke korisnika {user} bit će stavljeni u red čekanja za trajno brisanje odmah.", - "user_delete_immediately_checkbox": "Stavite korisnika i stavke u red čekanja za trenutno brisanje", - "user_details": "Detalji korisnika", - "user_management": "Upravljanje korisnicima", - "user_password_has_been_reset": "Korisnička lozinka je poništena:", - "user_password_reset_description": "Molimo dostavite privremenu lozinku korisniku i obavijestite ga da će morati promijeniti lozinku pri sljedećoj prijavi.", - "user_restore_description": "Račun korisnika {user} bit će vraćen.", - "user_restore_scheduled_removal": "Vrati korisnika - zakazano uklanjanje {date, date, long}", - "user_settings": "Korisničke postavke", - "user_settings_description": "Upravljanje korisničkim postavkama", - "version_check_enabled_description": "Omogući provjeru verzije", - "version_check_implications": "Značajka provjere verzije oslanja se na periodičnu komunikaciju s github.com", - "version_check_settings": "Provjera verzije", - "version_check_settings_description": "Omogućite/onemogućite obavijest o novoj verziji", - "video_conversion_job": "Transkodiranje videozapisa", - "video_conversion_job_description": "Transkodiranje videozapisa za veću kompatibilnost s preglednicima i uređajima" - }, - "admin_email": "E-pošta administratora", - "admin_password": "Admin lozinka", - "administration": "Administracija", - "advanced": "Napredno", - "advanced_settings_enable_alternate_media_filter_subtitle": "Koristite ovu opciju za filtriranje medija tijekom sinkronizacije na temelju alternativnih kriterija. Pokušajte ovo samo ako imate problema s aplikacijom koja ne prepoznaje sve albume.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Koristite alternativni filter za sinkronizaciju albuma na uređaju", - "advanced_settings_log_level_title": "Razina zapisivanja: {level}", - "advanced_settings_prefer_remote_subtitle": "Neki uređaji sporo učitavaju sličice s lokalnih stavki. Aktivirajte ovu postavku kako biste umjesto toga učitali slike s udaljenih izvora.", - "advanced_settings_prefer_remote_title": "Preferiraj udaljene slike", - "advanced_settings_proxy_headers_subtitle": "Definirajte proxy zaglavlja koja Immich treba poslati sa svakim mrežnim zahtjevom", - "advanced_settings_proxy_headers_title": "Prilagođeni proxy zaglavlja [EKSPERIMENTALNO]", - "advanced_settings_readonly_mode_subtitle": "Omogućuje način rada za čitanje u kojem se fotografije mogu samo pregledavati, a stvari poput odabira više slika, dijeljenja, emitiranja i brisanja su onemogućene. Omogući/onemogući način rada za čitanje putem korisničkog avatara s glavnog zaslona", - "advanced_settings_readonly_mode_title": "Read-only mod", - "advanced_settings_self_signed_ssl_subtitle": "Preskoči provjeru SSL certifikata za krajnju točku poslužitelja. Potrebno za samo-potpisane certifikate.", - "advanced_settings_self_signed_ssl_title": "Dopusti samopotpisane SSL certifikate [EKSPERIMENTALNO]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatski izbriši ili obnovi stavku na ovom uređaju kada se ta radnja izvrši na webu", - "advanced_settings_sync_remote_deletions_title": "Sinkroniziraj udaljena brisanja [EKSPERIMENTALNO]", - "advanced_settings_tile_subtitle": "Postavke za napredne korisnike", - "advanced_settings_troubleshooting_subtitle": "Omogući dodatne značajke za rješavanje problema", - "advanced_settings_troubleshooting_title": "Rješavanje problema", - "age_months": "Dob {months, plural, one {# mjesec} other {# mjeseca}}", - "age_year_months": "Dob 1 godina, {months, plural, one {# mjesec} other {# mjeseca}}", - "age_years": "{years, plural, other {Dob #}}", - "album_added": "Album dodan", - "album_added_notification_setting_description": "Primite obavijest e-poštom kada ste dodani u dijeljeni album", - "album_cover_updated": "Naslovnica albuma ažurirana", - "album_delete_confirmation": "Jeste li sigurni da želite izbrisati album {album}?", - "album_delete_confirmation_description": "Ako se ovaj album dijeli, drugi korisnici mu više neće moći pristupiti.", - "album_deleted": "Album izbrisan", - "album_info_card_backup_album_excluded": "IZUZETO", - "album_info_card_backup_album_included": "UKLJUČENO", - "album_info_updated": "Podaci o albumu ažurirani", - "album_leave": "Napustiti album?", - "album_leave_confirmation": "Jeste li sigurni da želite napustiti {album}?", - "album_name": "Naziv albuma", - "album_options": "Opcije albuma", - "album_remove_user": "Ukloni korisnika?", - "album_remove_user_confirmation": "Jeste li sigurni da želite ukloniti {user}?", - "album_search_not_found": "Nema albuma koji odgovaraju vašem pretraživanju", - "album_share_no_users": "Čini se da ste podijelili ovaj album sa svim korisnicima ili nemate nijednog korisnika s kojim biste ga dijelili.", - "album_summary": "Sažetak albuma", - "album_updated": "Album ažuriran", - "album_updated_setting_description": "Primite obavijest e-poštom kada dijeljeni album ima nove stavke", - "album_user_left": "Napušten {album}", - "album_user_removed": "Uklonjen {user}", - "album_viewer_appbar_delete_confirm": "Jeste li sigurni da želite izbrisati ovaj album s vašeg računa?", - "album_viewer_appbar_share_err_delete": "Neuspješno brisanje albuma", - "album_viewer_appbar_share_err_leave": "Neuspješno napuštanje albuma", - "album_viewer_appbar_share_err_remove": "Postoje problemi s uklanjanjem stavki iz albuma", - "album_viewer_appbar_share_err_title": "Neuspješno mijenjanje naslova albuma", - "album_viewer_appbar_share_leave": "Napusti album", - "album_viewer_appbar_share_to": "Podijeli s", - "album_viewer_page_share_add_users": "Dodaj korisnike", - "album_with_link_access": "Dopusti svima s poveznicom pristup fotografijama i osobama u ovom albumu.", - "albums": "Albumi", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albumi}}", - "albums_default_sort_order": "Zadani redoslijed sortiranja albuma", - "albums_default_sort_order_description": "Početni redoslijed sortiranja stavki prilikom izrade novih albuma.", - "albums_feature_description": "Zbirke stavki koje se mogu dijeliti s drugim korisnicima.", - "albums_on_device_count": "Albumi na uređaju ({count})", - "all": "Sve", - "all_albums": "Svi albumi", - "all_people": "Sve osobe", - "all_videos": "Svi videi", - "allow_dark_mode": "Dozvoli tamni način", - "allow_edits": "Dozvoli izmjene", - "allow_public_user_to_download": "Dopusti javnom korisniku preuzimanje", - "allow_public_user_to_upload": "Dopusti javnom korisniku učitavanje", - "alt_text_qr_code": "Slika QR koda", - "anti_clockwise": "Suprotno smjeru kazaljke na satu", - "api_key": "API Ključ", - "api_key_description": "Ova će vrijednost biti prikazana samo jednom. Obavezno ju kopirajte prije zatvaranja prozora.", - "api_key_empty": "Naziv vašeg API ključa ne smije biti prazan", - "api_keys": "API ključevi", - "app_architecture_variant": "Varijanta(Arhitektura)", - "app_bar_signout_dialog_content": "Jeste li sigurni da se želite odjaviti?", - "app_bar_signout_dialog_ok": "Da", - "app_bar_signout_dialog_title": "Odjavi se", - "app_download_links": "Poveznica za preuzimanje aplikacije", - "app_settings": "Postavke aplikacije", - "app_update_available": "Ažuriranje aplikacije je dostupno", - "appears_in": "Pojavljuje se u", - "apply_count": "Primijeni ({count, number})", - "archive": "Arhiva", - "archive_action_prompt": "{count} dodano u arhivu", - "archive_or_unarchive_photo": "Arhivirajte ili dearhivirajte fotografiju", - "archive_page_no_archived_assets": "Nema arhiviranih stavki", - "archive_page_title": "Arhiviraj ({count})", - "archive_size": "Veličina arhive", - "archive_size_description": "Konfigurirajte veličinu arhive za preuzimanja (u GiB)", - "archived": "Arhivirano", - "archived_count": "{count, plural, one {Arhivirana #} few {Arhivirane #} other {Arhivirano #}}", - "are_these_the_same_person": "Je li ovo ista osoba?", - "are_you_sure_to_do_this": "Jeste li sigurni da to želite učiniti?", - "asset_action_delete_err_read_only": "Nije moguće izbrisati stavke samo za čitanje, preskakanje", - "asset_action_share_err_offline": "Nije moguće dohvatiti izvanmrežne stavke, preskakanje", - "asset_added_to_album": "Dodano u album", - "asset_adding_to_album": "Dodavanje u album…", - "asset_description_updated": "Opis stavke je ažuriran", - "asset_filename_is_offline": "Stavka {filename} je izvan mreže", - "asset_has_unassigned_faces": "Stavka ima nedodijeljena lica", - "asset_hashing": "Hashiranje…", - "asset_list_group_by_sub_title": "Grupiraj po", - "asset_list_layout_settings_dynamic_layout_title": "Dinamički raspored", - "asset_list_layout_settings_group_automatically": "Automatski", - "asset_list_layout_settings_group_by": "Grupiraj stavke po", - "asset_list_layout_settings_group_by_month_day": "Mjesec + dan", - "asset_list_layout_sub_title": "Raspored", - "asset_list_settings_subtitle": "Postavke izgleda Mreže fotografija", - "asset_list_settings_title": "Mreža fotografija", - "asset_offline": "Stavka izvan mreže", - "asset_offline_description": "Ova vanjska stavka nije pronađena na disku. Za pomoć se obratite Immich administratoru.", - "asset_restored_successfully": "Stavka uspješno obnovljena", - "asset_skipped": "Preskočeno", - "asset_skipped_in_trash": "U smeću", - "asset_trashed": "Stavka premještena u smeće", - "asset_troubleshoot": "Rješavanje problema sa stavkom", - "asset_uploaded": "Preneseno", - "asset_uploading": "Prenošenje…", - "asset_viewer_settings_subtitle": "Upravljajte postavkama vašeg preglednika galerije", - "asset_viewer_settings_title": "Preglednik stavki", - "assets": "Stavke", - "assets_added_count": "{count, plural, one {Dodana # stavka} few {Dodane # stavke} other {Dodano # stavki}}", - "assets_added_to_album_count": "{count, plural, one {Dodana # stavka} few {Dodane # stavke} other {Dodano # stavki}} u album", - "assets_added_to_albums_count": "{assetTotal, plural, one {Dodana # stavka} other {Dodano # stavki}} u {albumTotal, plural, one {# album} other {# albuma}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Stavka se ne može} other {Stavke se ne mogu}} dodati u album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Stavka se ne može} few {Stavke se ne mogu} other {Stavki se ne može}} dodati ni u jedan album", - "assets_count": "{count, plural, one {# stavka} few {# stavke} other {# stavki}}", - "assets_deleted_permanently": "{count, plural, one {# stavka trajno izbrisana} few {# stavke trajno izbrisane} other {# stavki trajno izbrisano}}", - "assets_deleted_permanently_from_server": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}} s Immich servera", - "assets_downloaded_failed": "{count, plural, one {Preuzeta # datoteka – {error} datoteka nije uspjela} few {Preuzete # datoteke - {error} datoteke nisu uspjele} other {Preuzeto # datoteka – {error} datoteke nisu uspjele}}", - "assets_downloaded_successfully": "{count, plural, one {Uspješno preuzeta # datoteka} few {Uspješno preuzete # datoteke} other {Uspješno preueto # datoteka}}", - "assets_moved_to_trash_count": "{count, plural, one {# stavka premještena} few {# stavke premještene} other {# stavk premještenoi}} u smeće", - "assets_permanently_deleted_count": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}}", - "assets_removed_count": "{count, plural, one {Uklonjena # stavka} few {Uklonjene # stavke} other {Uklonjeno # stavki}}", - "assets_removed_permanently_from_device": "{count, plural, one {# stavka trajno uklonjena} few {# stavke trajno uklonjene} other {# stavki trajno uklonjeno}} s vašeg uređaja", - "assets_restore_confirmation": "Jeste li sigurni da želite vratiti sve svoje izbrisane stavke? Ovu radnju ne možete poništiti! Imajte na umu da se na ovaj način ne mogu vratiti izvanmrežne stavke.", - "assets_restored_count": "{count, plural, one {Vraćena # stavka} few {Vraćene # stavke} other {Vraćeno # stavki}}", - "assets_restored_successfully": "{count, plural, one {# stavka uspješno obnovljena} few {# stavke uspješno obnovljene} other {# stavki uspješno obnovljeno}}", - "assets_trashed": "{count, plural, one {# stavka premještena} few {# stavke premještene} other {# stavki premješteno}} u smeće", - "assets_trashed_count": "{count, plural, one {# stavka premještena} few {# stavke premještene} other {# stavki premješteno}} u smeće", - "assets_trashed_from_server": "{count, plural, one {# stavka premještena} few {# stavke premještene} other {# stavki premješteno}} u smeće s Immich poslužitelja", - "assets_were_part_of_album_count": "{count, plural, one {Stavka je već bila} other {Stavke su već bile}} dio albuma", - "assets_were_part_of_albums_count": "{count, plural, one {Stavka je već bila} other {Stavke su već bile}} dio albuma", - "authorized_devices": "Ovlašteni uređaji", - "automatic_endpoint_switching_subtitle": "Povežite se lokalno preko naznačene Wi-Fi mreže kada je dostupna i koristite alternativne veze na drugim lokacijama", - "automatic_endpoint_switching_title": "Automatsko prebacivanje URL-a", - "autoplay_slideshow": "Automatsko prikazivanje slajdova", - "back": "Nazad", - "back_close_deselect": "Natrag, zatvorite ili poništite odabir", - "background_backup_running_error": "Sigurnosno kopiranje u pozadini je trenutno aktivno, ručno sigurnosno kopiranje nije moguće pokrenuti", - "background_location_permission": "Dozvola za lokaciju u pozadini", - "background_location_permission_content": "Kako bi prebacivao mreže dok radi u pozadini, Immich mora *uvijek* imati pristup preciznoj lokaciji kako bi aplikacija mogla pročitati naziv Wi-Fi mreže", - "background_options": "Pozadinske opcije", - "backup": "Sigurnosna kopija", - "backup_album_selection_page_albums_device": "Albumi na uređaju ({count})", - "backup_album_selection_page_albums_tap": "Dodirnite za uključivanje, dvostruki dodir za isključivanje", - "backup_album_selection_page_assets_scatter": "Stavke se mogu raspršiti po više albuma. Stoga se albumi mogu uključiti ili isključiti tijekom postupka sigurnosnog kopiranja.", - "backup_album_selection_page_select_albums": "Odabrani albumi", - "backup_album_selection_page_selection_info": "Informacije o odabiru", - "backup_album_selection_page_total_assets": "Ukupan broj jedinstvenih stavki", - "backup_albums_sync": "Sinkronizacija sigurnosnih kopija albuma", - "backup_all": "Sve", - "backup_background_service_backup_failed_message": "Neuspješno sigurnosno kopiranje stavki. Ponovno pokušavanje…", - "backup_background_service_complete_notification": "Sigurnosno kopiranje stavki dovršeno", - "backup_background_service_connection_failed_message": "Neuspješno povezivanje s poslužiteljem. Pokušavam ponovo…", - "backup_background_service_current_upload_notification": "Šaljem {filename}", - "backup_background_service_default_notification": "Provjera novih stavki…", - "backup_background_service_error_title": "Pogreška pri sigurnosnom kopiranju", - "backup_background_service_in_progress_notification": "Sigurnosno kopiranje vaših stavki…", - "backup_background_service_upload_failure_notification": "Neuspješno slanje {filename}", - "backup_controller_page_albums": "Sigurnosno kopiranje albuma", - "backup_controller_page_background_app_refresh_disabled_content": "Omogućite osvježavanje aplikacije u pozadini u Postavke > Opće Postavke > Osvježavanje Aplikacija u Pozadini kako biste koristili sigurnosno kopiranje u pozadini.", - "backup_controller_page_background_app_refresh_disabled_title": "Osvježavanje aplikacija u pozadini je onemogućeno", - "backup_controller_page_background_app_refresh_enable_button_text": "Idite u postavke", - "backup_controller_page_background_battery_info_link": "Pokaži mi kako", - "backup_controller_page_background_battery_info_message": "Za najbolje iskustvo sigurnosnog kopiranja u pozadini, molimo onemogućite sve optimizacije baterije koje ograničavaju pozadinsku aktivnost Immicha.\n\nBudući da je ovo specifično za uređaj, molimo potražite potrebne informacije za proizvođača vašeg uređaja.", - "backup_controller_page_background_battery_info_ok": "U redu", - "backup_controller_page_background_battery_info_title": "Optimizacije baterije", - "backup_controller_page_background_charging": "Samo tijekom punjenja", - "backup_controller_page_background_configure_error": "Neuspješno konfiguriranje pozadinske usluge", - "backup_controller_page_background_delay": "Odgoda sigurnosnog kopiranja novih stavki: {duration}", - "backup_controller_page_background_description": "Uključite pozadinsku uslugu za automatsko sigurnosno kopiranje svih novih stavki bez potrebe za otvaranjem aplikacije", - "backup_controller_page_background_is_off": "Automatsko sigurnosno kopiranje u pozadini je isključeno", - "backup_controller_page_background_is_on": "Automatsko sigurnosno kopiranje u pozadini je uključeno", - "backup_controller_page_background_turn_off": "Isključite pozadinsku uslugu", - "backup_controller_page_background_turn_on": "Uključite pozadinsku uslugu", - "backup_controller_page_background_wifi": "Samo na Wi-Fi mreži", - "backup_controller_page_backup": "Sigurnosna kopija", - "backup_controller_page_backup_selected": "Odabrani: ", - "backup_controller_page_backup_sub": "Sigurnosno kopirane fotografije i videozapisi", - "backup_controller_page_created": "Kreirano: {date}", - "backup_controller_page_desc_backup": "Uključite sigurnosno kopiranje u prvom planu kako biste automatski prenijeli nove stavke na poslužitelj prilikom otvaranja aplikacije.", - "backup_controller_page_excluded": "Izuzeto: ", - "backup_controller_page_failed": "Neuspješno ({count})", - "backup_controller_page_filename": "Naziv datoteke: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informacije o sigurnosnom kopiranju", - "backup_controller_page_none_selected": "Nema odabranih", - "backup_controller_page_remainder": "Preostalo", - "backup_controller_page_remainder_sub": "Preostale fotografije i videozapisi za sigurnosno kopiranje iz odabira", - "backup_controller_page_server_storage": "Pohrana na poslužitelju", - "backup_controller_page_start_backup": "Pokreni Sigurnosno Kopiranje", - "backup_controller_page_status_off": "Automatsko sigurnosno kopiranje u prvom planu je isključeno", - "backup_controller_page_status_on": "Automatsko sigurnosno kopiranje u prvom planu je uključeno", - "backup_controller_page_storage_format": "{used} od {total} iskorišteno", - "backup_controller_page_to_backup": "Albumi za sigurnosno kopiranje", - "backup_controller_page_total_sub": "Sve jedinstvene fotografije i videozapisi iz odabranih albuma", - "backup_controller_page_turn_off": "Isključite sigurnosno kopiranje u prvom planu", - "backup_controller_page_turn_on": "Uključite sigurnosno kopiranje u prvom planu", - "backup_controller_page_uploading_file_info": "Slanje informacija o datoteci", - "backup_err_only_album": "Nije moguće ukloniti jedini album", - "backup_error_sync_failed": "Sinkronizacija nije uspjela. Sigurnosna kopija se ne može obraditi.", - "backup_info_card_assets": "stavke", - "backup_manual_cancelled": "Otkazano", - "backup_manual_in_progress": "Slanje već u tijeku. Pokšuajte nakon nekog vremena", - "backup_manual_success": "Uspijeh", - "backup_manual_title": "Status slanja", - "backup_options": "Opcije sigurnosne kopije", - "backup_options_page_title": "Opcije sigurnosnog kopiranja", - "backup_setting_subtitle": "Upravljajte postavkama učitavanja u pozadini i prvom planu", - "backup_settings_subtitle": "Upravljaj postavkama slanja", - "backward": "Unazad", - "biometric_auth_enabled": "Biometrijska autentikacija omogućena", - "biometric_locked_out": "Zaključani ste iz biometrijske autentikacije", - "biometric_no_options": "Nema dostupnih biometrijskih opcija", - "biometric_not_available": "Biometrijska autentikacija nije dostupna na ovom uređaju", - "birthdate_saved": "Datum rođenja uspješno spremljen", - "birthdate_set_description": "Datum rođenja se koristi za izračunavanje godina ove osobe u trenutku fotografije.", - "blurred_background": "Zamućena pozadina", - "bugs_and_feature_requests": "Bugovi i zahtjevi za značajke", - "build": "Sagradi (Build)", - "build_image": "Sagradi (Build) Image", - "bulk_delete_duplicates_confirmation": "Jeste li sigurni da želite skupno izbrisati {count, plural, one {# dupliciranu stavku} few {# duplicirane stavke} other {# dupliciranih stavki}}? Ovo će zadržati najveću stavku svake grupe i trajno izbrisati sve druge duplikate. Ne možete poništiti ovu radnju!", - "bulk_keep_duplicates_confirmation": "Jeste li sigurni da želite zadržati {count, plural, one {# duplicate asset} other {# duplicate asset}}? Ovo će riješiti sve duplicirane grupe bez brisanja ičega.", - "bulk_trash_duplicates_confirmation": "Jeste li sigurni da želite skupno u smeće premjestiti {count, plural, one {# dupliciranu stavku} few {# duplicirane stavke} other {# dupliciranih stavki}}? Ovo će zadržati najveću stavku svake grupe i premjestiti sve ostale duplikate u smeće.", - "buy": "Kupi Immich", - "cache_settings_clear_cache_button": "Očisti predmemoriju", - "cache_settings_clear_cache_button_title": "Briše predmemoriju aplikacije. Ovo će značajno utjecati na performanse aplikacije dok se predmemorija ponovno ne izgradi.", - "cache_settings_duplicated_assets_clear_button": "OČISTI", - "cache_settings_duplicated_assets_subtitle": "Fotografije i videozapisi koje aplikacija ignorira", - "cache_settings_duplicated_assets_title": "Duplicirane stavke ({count})", - "cache_settings_statistics_album": "Sličice biblioteke", - "cache_settings_statistics_full": "Pune slike", - "cache_settings_statistics_shared": "Sličice dijeljenih albuma", - "cache_settings_statistics_thumbnail": "Sličice", - "cache_settings_statistics_title": "Korištenje predmemorije", - "cache_settings_subtitle": "Upravljajte ponašanjem predmemorije mobilne aplikacije Immich", - "cache_settings_tile_subtitle": "Upravljajte ponašanjem lokalne pohrane", - "cache_settings_tile_title": "Lokalna pohrana", - "cache_settings_title": "Postavke predmemorije", - "camera": "Kamera", - "camera_brand": "Marka kamere", - "camera_model": "Model kamere", - "cancel": "Otkaži", - "cancel_search": "Otkaži pretragu", - "canceled": "Otkazano", - "canceling": "Otkazivanje", - "cannot_merge_people": "Nije moguće spojiti osobe", - "cannot_undo_this_action": "Ne možete poništiti ovu radnju!", - "cannot_update_the_description": "Nije moguće ažurirati opis", - "cast": "Prijenos na uređaj (Cast)", - "cast_description": "Konfigurirajte dostupna odredišta za prijenos (cast)", - "change_date": "Promjena datuma", - "change_description": "Promijeni opis", - "change_display_order": "Promijeni redoslijed prikaza", - "change_expiration_time": "Promjena vremena isteka", - "change_location": "Promjena lokacije", - "change_name": "Promjena imena", - "change_name_successfully": "Promijena imena uspješna", - "change_password": "Promjena Lozinke", - "change_password_description": "Ovo je ili prvi put da se prijavljujete u sustav ili je poslan zahtjev za promjenom lozinke. Unesite novu lozinku ispod.", - "change_password_form_confirm_password": "Potvrdi lozinku", - "change_password_form_description": "Pozdrav {name},\n\nOvo je ili prvi put da se prijavljujete u sustav ili je zatražena promjena vaše lozinke. Molimo unesite novu lozinku dolje.", - "change_password_form_log_out": "Odjavi se iz svih uređaja", - "change_password_form_log_out_description": "Preporučeno je odjaviti se sa svih uređaja", - "change_password_form_new_password": "Nova lozinka", - "change_password_form_password_mismatch": "Lozinke se ne podudaraju", - "change_password_form_reenter_new_password": "Ponovno unesite novu lozinku", - "change_pin_code": "Promijeni PIN kod", - "change_your_password": "Promijenite lozinku", - "changed_visibility_successfully": "Vidljivost je uspješno promijenjena", - "charging": "Punjenje", - "charging_requirement_mobile_backup": "Za izradu sigurnosne kopije u pozadini potrebno je punjenje uređaja", - "check_corrupt_asset_backup": "Provjeri oštećene sigurnosne kopije stavki", - "check_corrupt_asset_backup_button": "Izvrši provjeru", - "check_corrupt_asset_backup_description": "Pokrenite ovu provjeru samo putem Wi-Fi mreže i nakon što su sve stavke sigurnosno kopirane. Postupak može potrajati nekoliko minuta.", - "check_logs": "Provjera Zapisa", - "choose_matching_people_to_merge": "Odaberite odgovarajuće osobe za spajanje", - "city": "Grad", - "clear": "Očisti", - "clear_all": "Očisti sve", - "clear_all_recent_searches": "Izbriši sva nedavna pretraživanja", - "clear_file_cache": "Očisti predmemoriju datoteka", - "clear_message": "Jasna poruka", - "clear_value": "Očisti vrijednost", - "client_cert_dialog_msg_confirm": "U redu", - "client_cert_enter_password": "Unesite lozinku", - "client_cert_import": "Uvezi", - "client_cert_import_success_msg": "Klijentski certifikat je uvezen", - "client_cert_invalid_msg": "Neispravna datoteka certifikata ili pogrešna lozinka", - "client_cert_remove_msg": "Klijentski certifikat je uklonjen", - "client_cert_subtitle": "Podržava samo PKCS12 (.p12, .pfx) format. Uvoz/uklanjanje certifikata dostupno je samo prije prijave", - "client_cert_title": "SSL klijentski certifikat [EKSPERIMENTALNO]", - "clockwise": "U smjeru kazaljke na satu", - "close": "Zatvori", - "collapse": "Sažmi", - "collapse_all": "Sažmi sve", - "color": "Boja", - "color_theme": "Tema boja", - "command": "Naredba", - "comment_deleted": "Komentar izbrisan", - "comment_options": "Opcije komentara", - "comments_and_likes": "Komentari i lajkovi", - "comments_are_disabled": "Komentari onemogućeni", - "common_create_new_album": "Kreiraj novi album", - "completed": "Dovršeno", - "confirm": "Potvrdi", - "confirm_admin_password": "Potvrdite lozinku administratora", - "confirm_delete_face": "Jeste li sigurni da želite izbrisati lice {name} iz stavke?", - "confirm_delete_shared_link": "Jeste li sigurni da želite izbrisati ovu zajedničku vezu?", - "confirm_keep_this_delete_others": "Sve druge stavke u stogu bit će izbrisane osim ove stavke. Jeste li sigurni da želite nastaviti?", - "confirm_new_pin_code": "Potvrdi novi PIN kod", - "confirm_password": "Potvrdite lozinku", - "confirm_tag_face": "Želite li označiti ovo lice kao {name}?", - "confirm_tag_face_unnamed": "Želite li označiti ovo lice?", - "connected_device": "Uređaj povezan", - "connected_to": "Povezano s", - "contain": "Sadrži", - "context": "Kontekst", - "continue": "Nastavi", - "control_bottom_app_bar_create_new_album": "Kreiraj novi album", - "control_bottom_app_bar_delete_from_immich": "Izbriši iz Immicha", - "control_bottom_app_bar_delete_from_local": "Izbriši s uređaja", - "control_bottom_app_bar_edit_location": "Uredi lokaciju", - "control_bottom_app_bar_edit_time": "Uredi datum i vrijeme", - "control_bottom_app_bar_share_link": "Podijeli poveznicu", - "control_bottom_app_bar_share_to": "Dijeli s", - "control_bottom_app_bar_trash_from_immich": "Premjesti u smeće", - "copied_image_to_clipboard": "Slika je kopirana u međuspremnik.", - "copied_to_clipboard": "Kopirano u međuspremnik!", - "copy_error": "Greška kopiranja", - "copy_file_path": "Kopiraj put datoteke", - "copy_image": "Kopiraj Sliku", - "copy_link": "Kopiraj poveznicu", - "copy_link_to_clipboard": "Kopiraj poveznicu u međuspremnik", - "copy_password": "Kopiraj lozinku", - "copy_to_clipboard": "Kopiraj u međuspremnik", - "country": "Država", - "cover": "Naslovnica", - "covers": "Naslovnice", - "create": "Kreiraj", - "create_album": "Kreiraj album", - "create_album_page_untitled": "Bez naslova", - "create_api_key": "Izradi API ključ", - "create_library": "Kreiraj Biblioteku", - "create_link": "Kreiraj poveznicu", - "create_link_to_share": "Izradite vezu za dijeljenje", - "create_link_to_share_description": "Dopusti svakome s vezom da vidi odabrane fotografije", - "create_new": "KREIRAJ NOVO", - "create_new_person": "Stvorite novu osobu", - "create_new_person_hint": "Dodijelite odabrane stavke novoj osobi", - "create_new_user": "Kreiraj novog korisnika", - "create_shared_album_page_share_add_assets": "DODAJ STAVKE", - "create_shared_album_page_share_select_photos": "Odaberi fotografije", - "create_shared_link": "Kreiraj dijeljeni link", - "create_tag": "Stvori oznaku", - "create_tag_description": "Napravite novu oznaku. Za ugniježđene oznake unesite punu putanju oznake uključujući kose crte.", - "create_user": "Stvori korisnika", - "created": "Stvoreno", - "created_at": "Kreirano", - "creating_linked_albums": "Izradi povezane albume...", - "crop": "Obreži", - "curated_object_page_title": "Stvari", - "current_device": "Trenutačni uređaj", - "current_pin_code": "Trenutni PIN kod", - "current_server_address": "Trenutna adresa poslužitelja", - "custom_locale": "Prilagođena Lokalizacija", - "custom_locale_description": "Formatiranje datuma i brojeva na temelju jezika i regije", - "custom_url": "Prilagođena URL adresa", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "Tamno", - "dark_theme": "Prebaci tamnu temu", - "date": "Datum", - "date_after": "Datum nakon", - "date_and_time": "Datum i Vrijeme", - "date_before": "Datum prije", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "Datum rođenja uspješno spremljen", - "date_range": "Razdoblje", - "day": "Dan", - "days": "Dani", - "deduplicate_all": "Dedupliciraj Sve", - "deduplication_criteria_1": "Veličina slike u bajtovima", - "deduplication_criteria_2": "Broj EXIF podataka", - "deduplication_info": "Informacije o uklanjanju duplikata", - "deduplication_info_description": "Za automatski odabir stavki i masovno uklanjanje duplikata, uzimamo u obzir:", - "default_locale": "Zadana lokalizacija", - "default_locale_description": "Oblikujte datume i brojeve na temelju jezika preglednika", - "delete": "Izbriši", - "delete_action_confirmation_message": "Jeste li sigurni da želite izbrisati ovu stavku? Ova radnja će premjestiti stavku u smeće poslužitelja i pitati vas želite li ju izbrisati lokalno", - "delete_action_prompt": "{count} izbrisano", - "delete_album": "Izbriši album", - "delete_api_key_prompt": "Jeste li sigurni da želite izbrisati ovaj API ključ?", - "delete_dialog_alert": "Ove stavke bit će trajno izbrisane iz Immicha i s vašeg uređaja", - "delete_dialog_alert_local": "Ove stavke bit će trajno uklonjene s vašeg uređaja, ali će i dalje biti dostupne na Immich poslužitelju", - "delete_dialog_alert_local_non_backed_up": "Neke od stavki nisu sigurnosno kopirane na Immich i bit će trajno uklonjene s vašeg uređaja", - "delete_dialog_alert_remote": "Ove stavke bit će trajno izbrisane s Immich poslužitelja", - "delete_dialog_ok_force": "Izbriši svejedno", - "delete_dialog_title": "Trajno izbriši", - "delete_duplicates_confirmation": "Jeste li sigurni da želite trajno izbrisati ove duplikate?", - "delete_face": "Izbriši lice", - "delete_key": "Ključ za brisanje", - "delete_library": "Izbriši knjižnicu", - "delete_link": "Izbriši poveznicu", - "delete_local_action_prompt": "{count} izbrisano lokalno", - "delete_local_dialog_ok_backed_up_only": "Izbriši samo sigurnosno kopirane", - "delete_local_dialog_ok_force": "Izbriši svejedno", - "delete_others": "Izbriši druge", - "delete_permanently": "Izbriši trajno", - "delete_permanently_action_prompt": "{count} trajno izbrisano", - "delete_shared_link": "Izbriši dijeljenu poveznicu", - "delete_shared_link_dialog_title": "Izbriši dijeljenu poveznicu", - "delete_tag": "Izbriši oznaku", - "delete_tag_confirmation_prompt": "Jeste li sigurni da želite izbrisati oznaku {tagName}?", - "delete_user": "Izbriši korisnika", - "deleted_shared_link": "Izbrisana dijeljena poveznica", - "deletes_missing_assets": "Briše stavke koje nedostaju na disku", - "description": "Opis", - "description_input_hint_text": "Dodaj opis...", - "description_input_submit_error": "Pogreška pri ažuriranju opisa, provjerite zapisnik za više detalja", - "deselect_all": "Poništi odabir svih", - "details": "Detalji", - "direction": "Smjer", - "disabled": "Onemogućeno", - "disallow_edits": "Zabrani izmjene", - "discord": "Discord", - "discover": "Otkrij", - "discovered_devices": "Otkriveni uređaji", - "dismiss_all_errors": "Odbaci sve pogreške", - "dismiss_error": "Odbaci pogrešku", - "display_options": "Mogućnosti prikaza", - "display_order": "Redoslijed prikaza", - "display_original_photos": "Prikaz originalnih fotografija", - "display_original_photos_setting_description": "Radije prikažite izvornu fotografiju kada gledate stavku umjesto sličica kada je izvorna stavka kompatibilna s webom. To može rezultirati sporijim brzinama prikaza fotografija.", - "do_not_show_again": "Ne prikazuj više ovu poruku", - "documentation": "Dokumentacija", - "done": "Gotovo", - "download": "Preuzmi", - "download_action_prompt": "Preuzimanje {count, plural, one {# stavke} few {# stavke} other {# stavki}}", - "download_canceled": "Preuzimanje otkazano", - "download_complete": "Preuzimanje završeno", - "download_enqueue": "Preuzimanje dodano u red", - "download_error": "Pogreška pri preuzimanju", - "download_failed": "Preuzimanje nije uspjelo", - "download_finished": "Preuzimanje završeno", - "download_include_embedded_motion_videos": "Ugrađeni videozapisi", - "download_include_embedded_motion_videos_description": "Uključite videozapise ugrađene u fotografije s pokretom kao zasebnu datoteku", - "download_notfound": "Preuzimanje nije pronađeno", - "download_paused": "Preuzimanje pauzirano", - "download_settings": "Preuzmi", - "download_settings_description": "Upravljajte postavkama vezanim uz preuzimanje stavki", - "download_started": "Preuzimanje započeto", - "download_sucess": "Preuzimanje uspješno", - "download_sucess_android": "Medij je preuzet u DCIM/Immich", - "download_waiting_to_retry": "Čeka se ponovni pokušaj", - "downloading": "Preuzimanje", - "downloading_asset_filename": "Preuzimanje stavke {filename}", - "downloading_media": "Preuzimanje medija", - "drop_files_to_upload": "Ispustite datoteke bilo gdje za prijenos", - "duplicates": "Duplikati", - "duplicates_description": "Razriješite svaku grupu tako da naznačite koji su duplikati, ako ih ima", - "duration": "Trajanje", - "edit": "Izmjena", - "edit_album": "Uredi album", - "edit_avatar": "Uredi avatar", - "edit_birthday": "Izmijeni rođendan", - "edit_date": "Uredi datum", - "edit_date_and_time": "Uredite datum i vrijeme", - "edit_date_and_time_action_prompt": "{count} datuma i vremena uređeno", - "edit_date_and_time_by_offset": "Promijeni datum prema pomaku", - "edit_date_and_time_by_offset_interval": "Novi raspon datuma: {from} - {to}", - "edit_description": "Uredi opis", - "edit_description_prompt": "Molimo odaberite novi opis:", - "edit_exclusion_pattern": "Uredi uzorak izuzimanja", - "edit_faces": "Uređivanje lica", - "edit_key": "Ključ za uređivanje", - "edit_link": "Uredi poveznicu", - "edit_location": "Uredi lokaciju", - "edit_location_action_prompt": "{count} uređenih lokacija", - "edit_location_dialog_title": "Lokacija", - "edit_name": "Uredi ime", - "edit_people": "Uredi ljude", - "edit_tag": "Uredi oznaku", - "edit_title": "Uredi Naslov", - "edit_user": "Uredi korisnika", - "editor": "Urednik", - "editor_close_without_save_prompt": "Promjene neće biti spremljene", - "editor_close_without_save_title": "Zatvoriti uređivač?", - "email": "E-pošta", - "email_notifications": "Obavijesti putem e-maila", - "empty_folder": "Ova mapa je prazna", - "empty_trash": "Isprazni smeće", - "empty_trash_confirmation": "Jeste li sigurni da želite isprazniti smeće? Time će se iz Immicha trajno ukloniti sve stavke u smeću.\nNe možete poništiti ovu radnju!", - "enable": "Omogući", - "enable_backup": "Omogući sigurnosnu kopiju", - "enable_biometric_auth_description": "Unesite svoj PIN kod za omogućavanje biometrijske autentikacije", - "enabled": "Omogućeno", - "end_date": "Datum završetka", - "enqueued": "Dodano u red", - "enter_wifi_name": "Unesite naziv Wi-Fi mreže", - "enter_your_pin_code": "Unesite svoj PIN kod", - "enter_your_pin_code_subtitle": "Unesite svoj PIN kod za pristup zaključanoj mapi", - "error": "Greška", - "error_change_sort_album": "Nije moguće promijeniti redoslijed albuma", - "error_delete_face": "Pogreška pri brisanju lica sa stavke", - "error_getting_places": "Greška prilikom dohvaćanja lokacija", - "error_loading_image": "Pogreška pri učitavanju slike", - "error_loading_partners": "Greška prilikom učitavanja partnera:{error}", - "error_saving_image": "Pogreška: {error}", - "error_tag_face_bounding_box": "Pogreška pri označavanju lica – nije moguće dohvatiti koordinate granica (bounding box)", - "error_title": "Greška - Nešto je pošlo krivo", - "errors": { - "cannot_navigate_next_asset": "Nije moguće prijeći na sljedeću stavku", - "cannot_navigate_previous_asset": "Nije moguće prijeći na prethodnu stavku", - "cant_apply_changes": "Nije moguće primijeniti promjene", - "cant_change_activity": "Nije moguće {enabled, select, true {onemogućiti} other {omogućiti}} aktivnost", - "cant_change_asset_favorite": "Nije moguće promijeniti favorita za stavku", - "cant_change_metadata_assets_count": "Nije moguće promijeniti metapodatke {count, plural, one {# stavke} few {# stavke} other {# stavki}}", - "cant_get_faces": "Ne mogu dobiti lica", - "cant_get_number_of_comments": "Ne mogu dobiti broj komentara", - "cant_search_people": "Ne mogu pretraživati ljude", - "cant_search_places": "Ne mogu pretraživati mjesta", - "error_adding_assets_to_album": "Pogreška pri dodavanju stavki u album", - "error_adding_users_to_album": "Pogreška pri dodavanju korisnika u album", - "error_deleting_shared_user": "Pogreška pri brisanju dijeljenog korisnika", - "error_downloading": "Pogreška pri preuzimanju {filename}", - "error_hiding_buy_button": "Pogreška pri skrivanju gumba za kupnju", - "error_removing_assets_from_album": "Pogreška prilikom uklanjanja stavki iz albuma, provjerite konzolu za više detalja", - "error_selecting_all_assets": "Pogreška pri odabiru svih stavki", - "exclusion_pattern_already_exists": "Ovaj uzorak izuzimanja već postoji.", - "failed_to_create_album": "Izrada albuma nije uspjela", - "failed_to_create_shared_link": "Stvaranje dijeljene veze nije uspjelo", - "failed_to_edit_shared_link": "Nije uspjelo uređivanje dijeljene poveznice", - "failed_to_get_people": "Dohvaćanje osoba nije uspjelo", - "failed_to_keep_this_delete_others": "Zadržavanje ove stavke i brisanje ostalih stavki nije uspjelo", - "failed_to_load_asset": "Učitavanje stavke nije uspjelo", - "failed_to_load_assets": "Učitavanje stavki nije uspjelo", - "failed_to_load_notifications": "Neuspješno učitavanje obavijesti", - "failed_to_load_people": "Učitavanje osoba nije uspjelo", - "failed_to_remove_product_key": "Uklanjanje ključa proizvoda nije uspjelo", - "failed_to_reset_pin_code": "Neuspješno resetiranje PIN koda", - "failed_to_stack_assets": "Slaganje stavki nije uspjelo", - "failed_to_unstack_assets": "Razdvajanje stavki nije uspjelo", - "failed_to_update_notification_status": "Neuspješno ažuriranje statusa obavijesti", - "incorrect_email_or_password": "Netočna adresa e-pošte ili lozinka", - "library_folder_already_exists": "Ova putanja unosa već postoji.", - "paths_validation_failed": "{paths, plural, one {# putanja nije prošla} other {# putanje nisu prošle}} provjeru valjanosti", - "profile_picture_transparent_pixels": "Profilne slike ne smiju imati prozirne piksele. Povećajte i/ili pomaknite sliku.", - "quota_higher_than_disk_size": "Postavili ste kvotu veću od veličine diska", - "something_went_wrong": "Nešto je pošlo po zlu", - "unable_to_add_album_users": "Nije moguće dodati korisnike u album", - "unable_to_add_assets_to_shared_link": "Nije moguće dodati stavke u dijeljenu vezu", - "unable_to_add_comment": "Nije moguće dodati komentar", - "unable_to_add_exclusion_pattern": "Nije moguće dodati uzorak izuzimanja", - "unable_to_add_partners": "Nije moguće dodati partnere", - "unable_to_add_remove_archive": "Nije moguće {archived, select, true {ukloniti stavku iz} other {dodati stavku u}} arhivu", - "unable_to_add_remove_favorites": "Nije moguće {favorite, select, true {add asset to} other {remove asset from}} favorite", - "unable_to_archive_unarchive": "Nije moguće {archived, select, true {arhivirati} other {dearhivirati}}", - "unable_to_change_album_user_role": "Nije moguće promijeniti ulogu korisnika albuma", - "unable_to_change_date": "Nije moguće promijeniti datum", - "unable_to_change_description": "Nije moguće promijeniti opis", - "unable_to_change_favorite": "Nije moguće promijeniti favorita za stavku", - "unable_to_change_location": "Nije moguće promijeniti lokaciju", - "unable_to_change_password": "Nije moguće promijeniti lozinku", - "unable_to_change_visibility": "Nije moguće promijeniti vidljivost za {count, plural, one {# osobu} other {# osobe}}", - "unable_to_complete_oauth_login": "Nije moguće dovršiti OAuth prijavu", - "unable_to_connect": "Povezivanje nije moguće", - "unable_to_copy_to_clipboard": "Nije moguće kopirati u međuspremnik, provjerite pristupate li stranici putem https-a", - "unable_to_create_admin_account": "Nije moguće stvoriti administratorski račun", - "unable_to_create_api_key": "Nije moguće izraditi novi API ključ", - "unable_to_create_library": "Nije moguće stvoriti biblioteku", - "unable_to_create_user": "Nije moguće stvoriti korisnika", - "unable_to_delete_album": "Nije moguće izbrisati album", - "unable_to_delete_asset": "Nije moguće izbrisati stavku", - "unable_to_delete_assets": "Pogreška pri brisanju stavki", - "unable_to_delete_exclusion_pattern": "Nije moguće izbrisati uzorak izuzimanja", - "unable_to_delete_shared_link": "Nije moguće izbrisati dijeljenu poveznicu", - "unable_to_delete_user": "Nije moguće izbrisati korisnika", - "unable_to_download_files": "Nije moguće preuzeti datoteke", - "unable_to_edit_exclusion_pattern": "Nije moguće urediti uzorak izuzimanja", - "unable_to_empty_trash": "Nije moguće isprazniti otpad", - "unable_to_enter_fullscreen": "Nije moguće otvoriti cijeli zaslon", - "unable_to_exit_fullscreen": "Nije moguće izaći iz cijelog zaslona", - "unable_to_get_comments_number": "Nije moguće dobiti broj komentara", - "unable_to_get_shared_link": "Dohvaćanje dijeljene veze nije uspjelo", - "unable_to_hide_person": "Nije moguće sakriti osobu", - "unable_to_link_motion_video": "Nije moguće povezati videozapis pokreta", - "unable_to_link_oauth_account": "Nije moguće povezati OAuth račun", - "unable_to_log_out_all_devices": "Nije moguće odjaviti sve uređaje", - "unable_to_log_out_device": "Nije moguće odjaviti uređaj", - "unable_to_login_with_oauth": "Nije moguće prijaviti se pomoću OAutha", - "unable_to_play_video": "Nije moguće reproducirati video", - "unable_to_reassign_assets_existing_person": "Nije moguće ponovno dodijeliti stavke {name, select, null {postojećoj osobi} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nije moguće ponovno dodijeliti stavke novoj osobi", - "unable_to_refresh_user": "Nije moguće osvježiti korisnika", - "unable_to_remove_album_users": "Nije moguće ukloniti korisnike iz albuma", - "unable_to_remove_api_key": "Nije moguće ukloniti API ključ", - "unable_to_remove_assets_from_shared_link": "Nije moguće ukloniti stavke iz dijeljene veze", - "unable_to_remove_library": "Nije moguće ukloniti biblioteku", - "unable_to_remove_partner": "Nije moguće ukloniti partnera", - "unable_to_remove_reaction": "Nije moguće ukloniti reakciju", - "unable_to_reset_password": "Nije moguće ponovno postaviti lozinku", - "unable_to_reset_pin_code": "Nije moguće poništiti PIN kod", - "unable_to_resolve_duplicate": "Nije moguće razriješiti duplikat", - "unable_to_restore_assets": "Nije moguće vratiti stavke", - "unable_to_restore_trash": "Nije moguće vratiti otpad", - "unable_to_restore_user": "Nije moguće vratiti korisnika", - "unable_to_save_album": "Nije moguće spremiti album", - "unable_to_save_api_key": "Nije moguće spremiti API ključ", - "unable_to_save_date_of_birth": "Nije moguće spremiti datum rođenja", - "unable_to_save_name": "Nije moguće spremiti ime", - "unable_to_save_profile": "Nije moguće spremiti profil", - "unable_to_save_settings": "Nije moguće spremiti postavke", - "unable_to_scan_libraries": "Nije moguće skenirati knjižnice", - "unable_to_scan_library": "Nije moguće skenirati knjižnicu", - "unable_to_set_feature_photo": "Nije moguće postaviti istaknutu fotografiju", - "unable_to_set_profile_picture": "Nije moguće postaviti profilnu sliku", - "unable_to_submit_job": "Nije moguće poslati posao", - "unable_to_trash_asset": "Nije moguće premjestiti stavku u smeće", - "unable_to_unlink_account": "Nije moguće prekinuti vezu računa", - "unable_to_unlink_motion_video": "Nije moguće prekinuti vezu videozapisa pokreta", - "unable_to_update_album_cover": "Nije moguće ažurirati omot albuma", - "unable_to_update_album_info": "Nije moguće ažurirati informacije o albumu", - "unable_to_update_library": "Nije moguće ažurirati biblioteku", - "unable_to_update_location": "Nije moguće ažurirati lokaciju", - "unable_to_update_settings": "Nije moguće ažurirati postavke", - "unable_to_update_timeline_display_status": "Nije moguće ažurirati status prikaza vremenske trake", - "unable_to_update_user": "Nije moguće ažurirati korisnika", - "unable_to_upload_file": "Nije moguće učitati datoteku" - }, - "exclusion_pattern": "Uzorak isključenja", - "exif": "Exif", - "exif_bottom_sheet_description": "Dodaj opis...", - "exif_bottom_sheet_description_error": "Pogreška pri ažuriranju opisa", - "exif_bottom_sheet_details": "DETALJI", - "exif_bottom_sheet_location": "LOKACIJA", - "exif_bottom_sheet_no_description": "Bez opisa", - "exif_bottom_sheet_people": "OSOBE", - "exif_bottom_sheet_person_add_person": "Dodaj ime", - "exit_slideshow": "Izađi iz projekcije slideova", - "expand_all": "Proširi sve", - "experimental_settings_new_asset_list_subtitle": "Rad u tijeku", - "experimental_settings_new_asset_list_title": "Omogući eksperimentalnu mrežu fotografija", - "experimental_settings_subtitle": "Koristite na vlastitu odgovornost!", - "experimental_settings_title": "Eksperimentalno", - "expire_after": "Istječe nakon", - "expired": "Isteklo", - "expires_date": "Ističe {date}", - "explore": "Istraži", - "explorer": "Pretraživač (Explorer)", - "export": "Izvoz", - "export_as_json": "Izvezi kao JSON", - "export_database": "Izvezi bazu podataka", - "export_database_description": "Izvezi SQLite bazu podataka", - "extension": "Proširenje (Extension)", - "external": "Vanjski", - "external_libraries": "Vanjske biblioteke", - "external_network": "Vanjska mreža", - "external_network_sheet_info": "Kada niste na željenoj Wi-Fi mreži, aplikacija će se povezati s poslužiteljem putem prve dostupne URL adrese s popisa ispod, redom od vrha prema dnu", - "face_unassigned": "Nedodijeljeno", - "failed": "Neuspješno", - "failed_count": "Neuspjelo:{count}", - "failed_to_authenticate": "Neuspješna autentikacija", - "failed_to_load_assets": "Učitavanje stavki nije uspjelo", - "failed_to_load_folder": "Neuspjelo učitavanje mape", - "favorite": "Omiljeno", - "favorite_action_prompt": "{count} dodano u Omiljeno", - "favorite_or_unfavorite_photo": "Omiljena ili neomiljena fotografija", - "favorites": "Omiljene", - "favorites_page_no_favorites": "Nema pronađenih favorita", - "feature_photo_updated": "Istaknuta fotografija ažurirana", - "features": "Značajke", - "features_in_development": "Značajke u razvoju", - "features_setting_description": "Upravljajte značajkama aplikacije", - "file_name_or_extension": "Naziv ili ekstenzija datoteke", - "file_size": "Veličina datoteke", - "filename": "Naziv datoteke", - "filetype": "Vrsta datoteke", - "filter": "Filtar", - "filter_people": "Filtrirajte ljude", - "filter_places": "Filtriraj mjesta", - "find_them_fast": "Pronađite ih brzo po imenu pomoću pretraživanja", - "first": "Prvi", - "fix_incorrect_match": "Ispravite netočno podudaranje", - "folder": "Mapa", - "folder_not_found": "Mapa nije pronađena", - "folders": "Mape", - "folders_feature_description": "Pregledavanje prikaza mape za fotografije i videozapise u sustavu datoteka", - "forgot_pin_code_question": "Zaboravili ste svoj PIN?", - "forward": "Naprijed", - "full_path": "Puna putanja:{path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ova značajka učitava vanjske stavke s Googlea kako bi radila.", - "general": "Općenito", - "geolocation_instruction_location": "Kliknite na stavku s GPS koordinatama da biste koristili njezinu lokaciju ili odaberite lokaciju izravno s karte", - "get_help": "Potražite pomoć", - "get_wifiname_error": "Nije moguće dohvatiti naziv Wi-Fi mreže. Provjerite imate li potrebna dopuštenja i jeste li povezani na Wi-Fi mrežu", - "getting_started": "Početak Rada", - "go_back": "Idi natrag", - "go_to_folder": "Idi u mapu", - "go_to_search": "Idi na pretragu", - "gps": "GPS", - "gps_missing": "Bez GPSa", - "grant_permission": "Dodijeli dopuštenje", - "group_albums_by": "Grupiraj albume po...", - "group_country": "Grupiraj po zemlji", - "group_no": "Nema grupiranja", - "group_owner": "Grupiraj po vlasniku", - "group_places_by": "Grupiraj mjesta po...", - "group_year": "Grupiraj po godini", - "haptic_feedback_switch": "Omogući haptičku povratnu informaciju", - "haptic_feedback_title": "Haptička povratna informacija", - "has_quota": "Ima kvotu", - "hash_asset": "Hashiraj stavku", - "hashed_assets": "Hashirane stavke", - "hashing": "Hashiranje", - "header_settings_add_header_tip": "Dodaj zaglavlje", - "header_settings_field_validator_msg": "Vrijednost ne može biti prazna", - "header_settings_header_name_input": "Naziv zaglavlja", - "header_settings_header_value_input": "Vrijednost zaglavlja", - "headers_settings_tile_title": "Prilagođena proxy zaglavlja", - "hi_user": "Bok {name} ({email})", - "hide_all_people": "Sakrij sve ljude", - "hide_gallery": "Sakrij galeriju", - "hide_named_person": "Sakrij osobu {name}", - "hide_password": "Sakrij lozinku", - "hide_person": "Sakrij osobu", - "hide_text_recognition": "Sakrij prepoznavanje teksta", - "hide_unnamed_people": "Sakrij neimenovane osobe", - "home_page_add_to_album_conflicts": "Dodano {added} stavki u album {album}. {failed} stavki je već u albumu.", - "home_page_add_to_album_err_local": "Lokalne stavke još nije moguće dodati u albume, preskakanje", - "home_page_add_to_album_success": "Dodano {added} stavki u album {album}.", - "home_page_album_err_partner": "Još nije moguće dodati partnerove stavke u album, preskakanje", - "home_page_archive_err_local": "Lokalne stavke još nije moguće arhivirati, preskakanje", - "home_page_archive_err_partner": "Partnerove stavke nije moguće arhivirati, preskakanje", - "home_page_building_timeline": "Izrada vremenske crte", - "home_page_delete_err_partner": "Nije moguće izbrisati partnerove stavke, preskakanje", - "home_page_delete_remote_err_local": "Lokalne stavke su u odabiru za udaljeno brisanje, preskakanje", - "home_page_favorite_err_local": "Lokalne stavke još nije moguće označiti kao favorite, preskakanje", - "home_page_favorite_err_partner": "Partnerove stavke još nije moguće označiti kao favorite, preskakanje", - "home_page_first_time_notice": "Ako prvi put koristite aplikaciju, svakako odaberite album za sigurnosnu kopiju kako bi vremenska crta mogla prikazati fotografije i videozapise", - "home_page_locked_error_local": "Nije moguće premjestiti lokalne stavke u zaključanu mapu, preskakanje", - "home_page_locked_error_partner": "Nije moguće premjestiti partnerove stavke u zaključanu mapu, preskakanje", - "home_page_share_err_local": "Lokalne stavke nije moguće dijeliti putem poveznice, preskakanje", - "home_page_upload_err_limit": "Moguće je prenijeti najviše 30 stavki odjednom, preskakanje", - "host": "Domaćin", - "hour": "Sat", - "hours": "Sati", - "id": "ID", - "idle": "Neaktivan", - "ignore_icloud_photos": "Ignoriraj iCloud fotografije", - "ignore_icloud_photos_description": "Fotografije pohranjene na iCloudu neće biti učitane na Immich poslužitelj", - "image": "Slika", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} snimljeno {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} snimljeno s {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} snimljeno s {person1} i {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} snimljeno s {person1}, {person2} i {person3} {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} snimljeno s {person1}, {person2} i {additionalCount, number} drugih {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1} i {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1}, {person2} i {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1}, {person2} i {additionalCount, number} drugih {date}", - "image_saved_successfully": "Slika je spremljena", - "image_viewer_page_state_provider_download_started": "Preuzimanje započelo", - "image_viewer_page_state_provider_download_success": "Uspješno Preuzimanje", - "image_viewer_page_state_provider_share_error": "Greška pri dijeljenju", - "immich_logo": "Immich Logo", - "immich_web_interface": "Immich Web Sučelje", - "import_from_json": "Uvoz iz JSON-a", - "import_path": "Putanja uvoza", - "in_albums": "U {count, plural, one {# album} other {# albuma}}", - "in_archive": "U arhivi", - "include_archived": "Uključi arhivirano", - "include_shared_albums": "Uključi dijeljene albume", - "include_shared_partner_assets": "Uključi zajedničke stavke partnera", - "individual_share": "Pojedinačni udio", - "individual_shares": "Pojedinačna dijeljenja", - "info": "Informacije", - "interval": { - "day_at_onepm": "Svaki dan u 13 sati", - "hours": "{hours, plural, one {Svaki sat} few {Svakih {hours, number} sata} other {Svakih {hours, number} sati}}", - "night_at_midnight": "Svaku večer u ponoć", - "night_at_twoam": "Svake noći u 2 ujutro" - }, - "invalid_date": "Neispravan datum", - "invalid_date_format": "Neispravan format datuma", - "invite_people": "Pozovite ljude", - "invite_to_album": "Pozovi u album", - "ios_debug_info_fetch_ran_at": "Dohvaćanje pokrenuto {dateTime}", - "ios_debug_info_last_sync_at": "Zadnja sinkronizacija {dateTime}", - "ios_debug_info_no_processes_queued": "Nema pozadinskih procesa u redu čekanja", - "ios_debug_info_no_sync_yet": "Još nije pokrenut nijedan zadatak pozadinske sinkronizacije", - "ios_debug_info_processes_queued": "{count, plural, one {{count} pozadinski proces u redu čekanja} other {{count} pozadinskih procesa u redu čekanja}}", - "ios_debug_info_processing_ran_at": "Obrada pokrenuta {dateTime}", - "items_count": "{count, plural, one {# datoteka} other {# datoteke}}", - "jobs": "Poslovi", - "keep": "Zadrži", - "keep_all": "Zadrži Sve", - "keep_this_delete_others": "Zadrži ovo, izbriši ostale", - "kept_this_deleted_others": "Zadržana je ova stavka i {count, plural, one {izbrisana # datoteka} few {izbrisane # datoteke} other {izbrisano # datoteka}}", - "keyboard_shortcuts": "Prečaci tipkovnice", - "language": "Jezik", - "language_no_results_subtitle": "Pokušajte prilagoditi pojam za pretraživanje", - "language_no_results_title": "Nisu pronađeni jezici", - "language_search_hint": "Pretraži jezike...", - "language_setting_description": "Odaberite željeni jezik", - "large_files": "Velike datoteke", - "last": "Zadnji", - "last_seen": "Zadnji put viđen", - "latest_version": "Najnovija verzija", - "latitude": "Zemljopisna širina", - "leave": "Izađi", - "leave_album": "Napusti album", - "lens_model": "Model objektiva", - "let_others_respond": "Dozvoli da drugi odgovore", - "level": "Razina", - "library": "Biblioteka", - "library_options": "Mogućnosti biblioteke", - "library_page_device_albums": "Albumi na uređaju", - "library_page_new_album": "Novi album", - "library_page_sort_asset_count": "Broj stavki", - "library_page_sort_created": "Datum kreiranja", - "library_page_sort_last_modified": "Zadnja izmjena", - "library_page_sort_title": "Naslov albuma", - "licenses": "Licence", - "light": "Svjetlo", - "like": "Sviđa mi se", - "like_deleted": "Like izbrisan", - "link_motion_video": "Povežite videozapis pokreta", - "link_to_oauth": "Veza na OAuth", - "linked_oauth_account": "Povezani OAuth račun", - "list": "Popis", - "loading": "Učitavanje", - "loading_search_results_failed": "Učitavanje rezultata pretraživanja nije uspjelo", - "local": "Lokalno", - "local_asset_cast_failed": "Nije moguće emitirati stavku koja nije prenesena na poslužitelj", - "local_assets": "Lokalne stavke", - "local_network": "Lokalna mreža", - "local_network_sheet_info": "Aplikacija će se povezati s poslužiteljem putem ovog URL-a kada koristi određenu Wi-Fi mrežu", - "location_permission": "Dozvola za lokaciju", - "location_permission_content": "Kako bi koristio značajku automatskog prebacivanja, Immich treba dozvolu za preciznu lokaciju kako bi mogao očitati naziv trenutne Wi-Fi mreže", - "location_picker_choose_on_map": "Odaberi na karti", - "location_picker_latitude_error": "Unesite valjanu geografsku širinu", - "location_picker_latitude_hint": "Unesite ovdje svoju geografsku širinu", - "location_picker_longitude_error": "Unesite valjanu geografsku dužinu", - "location_picker_longitude_hint": "Unesite ovdje svoju geografsku dužinu", - "lock": "Zaključaj", - "locked_folder": "Zaključana mapa", - "log_out": "Odjavi se", - "log_out_all_devices": "Odjava sa svih uređaja", - "logged_in_as": "Prijavljeni kao {user}", - "logged_out_all_devices": "Odjavljeni su svi uređaji", - "logged_out_device": "Odjavljen uređaj", - "login": "Prijava", - "login_disabled": "Prijava je onemogućena", - "login_form_api_exception": "API iznimka. Provjerite URL poslužitelja i pokušajte ponovno.", - "login_form_back_button_text": "Nazad", - "login_form_email_hint": "vasaemailadresa@email.com", - "login_form_endpoint_hint": "http://vaš-server-ip:port", - "login_form_endpoint_url": "URL krajnje točke poslužitelja", - "login_form_err_http": "Molimo navedite http:// ili https://", - "login_form_err_invalid_email": "Nevažeća e-mail adresa", - "login_form_err_invalid_url": "Nevažeći URL", - "login_form_err_leading_whitespace": "Početni razmak", - "login_form_err_trailing_whitespace": "Završni razmak", - "login_form_failed_get_oauth_server_config": "Pogreška pri prijavi putem OAuth-a, provjerite URL poslužitelja", - "login_form_failed_get_oauth_server_disable": "OAuth mogućnost nije dostupna na ovom poslužitelju", - "login_form_failed_login": "Pogreška pri prijavi, provjerite URL poslužitelja, e-mail i lozinku", - "login_form_handshake_exception": "Došlo je do greške u uspostavi veze s poslužiteljem. Omogućite podršku za samopotpisane certifikate u postavkama ako koristite takav certifikat.", - "login_form_password_hint": "lozinka", - "login_form_save_login": "Ostani prijavljen", - "login_form_server_empty": "Unesite URL poslužitelja.", - "login_form_server_error": "Nije moguće povezivanje s poslužiteljem.", - "login_has_been_disabled": "Prijava je onemogućena.", - "login_password_changed_error": "Došlo je do pogreške pri ažuriranju lozinke", - "login_password_changed_success": "Lozinka je uspješno ažurirana", - "logout_all_device_confirmation": "Jeste li sigurni da želite odjaviti sve uređaje?", - "logout_this_device_confirmation": "Jeste li sigurni da se želite odjaviti s ovog uređaja?", - "longitude": "Zemljopisna dužina", - "look": "Izgled", - "loop_videos": "Ponavljajte videozapise", - "loop_videos_description": "Omogućite automatsko ponavljanje videozapisa u pregledniku detalja.", - "main_branch_warning": "Koristite razvojnu verziju; strogo preporučamo korištenje izdane verzije!", - "main_menu": "Glavni izbornik", - "make": "Proizvođač", - "manage_shared_links": "Upravljanje dijeljenim vezama", - "manage_sharing_with_partners": "Upravljajte dijeljenjem s partnerima", - "manage_the_app_settings": "Upravljajte postavkama aplikacije", - "manage_your_account": "Upravljajte svojim računom", - "manage_your_api_keys": "Upravljajte svojim API ključevima", - "manage_your_devices": "Upravljajte uređajima na kojima ste prijavljeni", - "manage_your_oauth_connection": "Upravljajte svojom OAuth vezom", - "map": "Karta", - "map_assets_in_bounds": "{count, plural, =0 {Nema fotografija na ovom području} one {# fotografija} few {# fotografije} other {# fotografija}}", - "map_cannot_get_user_location": "Nije moguće dohvatiti lokaciju korisnika", - "map_location_dialog_yes": "Da", - "map_location_picker_page_use_location": "Koristi ovu lokaciju", - "map_location_service_disabled_content": "Usluga lokacije mora biti omogućena za prikaz stavki s vaše trenutne lokacije. Želite li je sada omogućiti?", - "map_location_service_disabled_title": "Usluga lokacije onemogućena", - "map_marker_for_images": "Oznaka karte za slike snimljene u {city}, {country}", - "map_marker_with_image": "Oznaka karte sa slikom", - "map_no_location_permission_content": "Potrebno je dopuštenje za lokaciju kako bi se prikazale stavke s vaše trenutne lokacije. Želite li ga sada omogućiti?", - "map_no_location_permission_title": "Dopuštenje za lokaciju odbijeno", - "map_settings": "Postavke karte", - "map_settings_dark_mode": "Tamni način rada", - "map_settings_date_range_option_day": "Posljednja 24 sata", - "map_settings_date_range_option_days": "Posljednjih {days} dana", - "map_settings_date_range_option_year": "Prošla godina", - "map_settings_date_range_option_years": "Posljednjih {years} godina", - "map_settings_dialog_title": "Postavke karte", - "map_settings_include_show_archived": "Uključi arhivirane", - "map_settings_include_show_partners": "Uključi partnere", - "map_settings_only_show_favorites": "Prikaži samo omiljene", - "map_settings_theme_settings": "Tema karte", - "map_zoom_to_see_photos": "Umanjite prikaz za pregled fotografija", - "mark_all_as_read": "Označi sve kao pročitano", - "mark_as_read": "Označi kao pročitano", - "marked_all_as_read": "Označeno sve kao pročitano", - "matches": "Podudaranja", - "matching_assets": "Odgovarajuće stavke", - "media_type": "Vrsta medija", - "memories": "Sjećanja", - "memories_all_caught_up": "Sve ste pregledali", - "memories_check_back_tomorrow": "Provjerite ponovno sutra za više uspomena", - "memories_setting_description": "Upravljajte onim što vidite u svojim sjećanjima", - "memories_start_over": "Započni iznova", - "memories_swipe_to_close": "Prijeđite prstom prema gore za zatvaranje", - "memory": "Memorija", - "memory_lane_title": "Traka sjećanja {title}", - "menu": "Izbornik", - "merge": "Spoji", - "merge_people": "Spoji osobe", - "merge_people_limit": "Možete spojiti najviše 5 lica odjednom", - "merge_people_prompt": "Želite li spojiti ove ljude? Ova radnja je nepovratna.", - "merge_people_successfully": "Uspješno spajanje osoba", - "merged_people_count": "{count, plural, one {# Spojena osoba} other {# Spojene osobe}}", - "minimize": "Minimiziraj", - "minute": "Minuta", - "minutes": "Minute", - "missing": "Nedostaje", - "model": "Model", - "month": "Mjesec", - "monthly_title_text_date_format": "MMMM y", - "more": "Više", - "move": "Pomakni", - "move_off_locked_folder": "Premjesti iz zaključane mape", - "move_to_lock_folder_action_prompt": "{count} dodano u zaključanu mapu", - "move_to_locked_folder": "Premjesti u zaključanu mapu", - "move_to_locked_folder_confirmation": "Ove fotografije i videozapis bit će uklonjeni iz svih albuma i bit će vidljivi samo iz zaključane mape", - "moved_to_archive": "{count, plural, one {Premještena # stavka} few {Premještene # stavke} other {Premješteno # stavki}} u arhivu", - "moved_to_library": "{count, plural, one {Premještena # stavka} few {Premještene # stavke} other {Premješteno # stavki}} u biblioteku", - "moved_to_trash": "Premješteno u smeće", - "multiselect_grid_edit_date_time_err_read_only": "Nije moguće urediti datum stavki označenih kao samo za čitanje, preskakanje", - "multiselect_grid_edit_gps_err_read_only": "Nije moguće urediti lokaciju stavki označenih kao samo za čitanje, preskakanje", - "mute_memories": "Isključi uspomene", - "my_albums": "Moji albumi", - "name": "Ime", - "name_or_nickname": "Ime ili nadimak", - "network_requirement_photos_upload": "Koristi mobilne podatke za sigurnosno kopiranje fotografija", - "network_requirement_videos_upload": "Koristi mobilne podatke za sigurnosno kopiranje videozapisa", - "network_requirements": "Mrežni zahtjevi", - "network_requirements_updated": "Zahtjevi za mrežu su se promijenili, red za sigurnosno kopiranje se resetira", - "networking_settings": "Umrežavanje", - "networking_subtitle": "Upravljajte postavkama krajnje točke poslužitelja", - "never": "Nikada", - "new_album": "Novi Album", - "new_api_key": "Novi API ključ", - "new_date_range": "Novi raspon datuma", - "new_password": "Nova lozinka", - "new_person": "Nova osoba", - "new_pin_code": "Novi PIN kod", - "new_pin_code_subtitle": "Ovo je vaš prvi pristup zaključanoj mapi. Kreirajte PIN kod za siguran pristup ovoj stranici", - "new_timeline": "Nova vremenska lenta", - "new_update": "Novo ažuriranje", - "new_user_created": "Stvoren novi korisnik", - "new_version_available": "DOSTUPNA NOVA VERZIJA", - "newest_first": "Prvo najnovije", - "next": "Sljedeće", - "next_memory": "Sljedeće sjećanje", - "no": "Ne", - "no_albums_message": "Izradite album za organiziranje svojih fotografija i videozapisa", - "no_albums_with_name_yet": "Čini se da još nemate nijedan album s ovim imenom.", - "no_albums_yet": "Čini se da još nemate nijedan album.", - "no_archived_assets_message": "Arhivirajte fotografije i videozapise kako biste ih sakrili iz prikaza fotografija", - "no_assets_message": "KLIKNITE DA PRENESETE SVOJU PRVU FOTOGRAFIJU", - "no_assets_to_show": "Nema stavki za prikaz", - "no_cast_devices_found": "Nisu pronađeni uređaji za reprodukciju", - "no_checksum_local": "Nema dostupnog kontrolnog zbroja - nije moguće dohvatiti lokalne stavke", - "no_checksum_remote": "Nema dostupnog kontrolnog zbroja - nije moguće dohvatiti udaljenu stavku", - "no_devices": "Nema autoriziranih uređaja", - "no_duplicates_found": "Nisu pronađeni duplikati.", - "no_exif_info_available": "Nema dostupnih exif podataka", - "no_explore_results_message": "Prenesite više fotografija da istražite svoju zbirku.", - "no_favorites_message": "Dodajte favorite kako biste brzo pronašli svoje najbolje slike i videozapise", - "no_libraries_message": "Stvorite vanjsku biblioteku za pregled svojih fotografija i videozapisa", - "no_local_assets_found": "Nisu pronađene lokalne stavke s ovim kontrolnim zbrojem", - "no_location_set": "Lokacija nije postavljena", - "no_locked_photos_message": "Fotografije i videozapisi u zaključanoj mapi su skriveni i neće se prikazivati dok pregledavate ili pretražujete svoju biblioteku.", - "no_name": "Bez imena", - "no_notifications": "Nema notifikacija", - "no_people_found": "Nema pronađenih odgovarajućih osoba", - "no_places": "Nema mjesta", - "no_remote_assets_found": "Nisu pronađene udaljene stavke s ovim kontrolnim zbrojem", - "no_results": "Nema rezultata", - "no_results_description": "Pokušajte sa sinonimom ili općenitijom ključnom riječi", - "no_shared_albums_message": "Stvorite album za dijeljenje fotografija i videozapisa s osobama u svojoj mreži", - "no_uploads_in_progress": "Nema aktivnih prijenosa", - "not_allowed": "Zabranjeno", - "not_available": "N/A", - "not_in_any_album": "Ni u jednom albumu", - "not_selected": "Nije odabrano", - "note_apply_storage_label_to_previously_uploaded assets": "Napomena: Da biste primijenili oznaku pohrane na prethodno prenesene stavke, pokrenite", - "notes": "Bilješke", - "nothing_here_yet": "Ovdje još nema ničega", - "notification_permission_dialog_content": "Da biste omogućili obavijesti, idite u Postavke i odaberite dopusti.", - "notification_permission_list_tile_content": "Dodijelite dopuštenje za omogućavanje obavijesti.", - "notification_permission_list_tile_enable_button": "Omogući obavijesti", - "notification_permission_list_tile_title": "Dopuštenje za obavijesti", - "notification_toggle_setting_description": "Omogući obavijesti putem e-pošte", - "notifications": "Obavijesti", - "notifications_setting_description": "Upravljanje obavijestima", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium konfigurator", - "official_immich_resources": "Službeni Immich resursi", - "offline": "Izvan mreže", - "offset": "Pomak", - "ok": "Ok", - "oldest_first": "Prvo najstarije", - "on_this_device": "Na ovom uređaju", - "onboarding": "Uključivanje (Onboarding)", - "onboarding_locale_description": "Odaberite željeni jezik. Kasnije ga možete promijeniti u postavkama.", - "onboarding_privacy_description": "Sljedeće (neobavezne) značajke oslanjaju se na vanjske usluge i mogu se onemogućiti u bilo kojem trenutku u postavkama.", - "onboarding_server_welcome_description": "Postavimo vašu instancu s nekim uobičajenim postavkama.", - "onboarding_theme_description": "Odaberite temu boja za svoj primjer. To možete kasnije promijeniti u postavkama.", - "onboarding_user_welcome_description": "Počnimo!", - "onboarding_welcome_user": "Dobro došli, {user}", - "online": "Dostupan (Online)", - "only_favorites": "Samo omiljeno", - "open": "Otvori", - "open_in_map_view": "Otvori u prikazu karte", - "open_in_openstreetmap": "Otvori u OpenStreetMap", - "open_the_search_filters": "Otvorite filtre pretraživanja", - "options": "Opcije", - "or": "ili", - "organize_your_library": "Organizirajte svoju knjižnicu", - "original": "originalno", - "other": "Ostalo", - "other_devices": "Ostali uređaji", - "other_entities": "Ostali entiteti", - "other_variables": "Ostale varijable", - "owned": "Vlasništvo", - "owner": "Vlasnik", - "partner": "Partner", - "partner_can_access": "{partner} može pristupiti", - "partner_can_access_assets": "Sve vaše fotografije i videozapisi osim onih u arhivi i smeću", - "partner_can_access_location": "Mjesto otkuda je slika otkinuta", - "partner_list_user_photos": "{user} fotografije", - "partner_list_view_all": "Prikaži sve", - "partner_page_empty_message": "Vaše fotografije još nisu podijeljene ni s jednim partnerom.", - "partner_page_no_more_users": "Nema više korisnika za dodavanje", - "partner_page_partner_add_failed": "Nije uspjelo dodavanje partnera", - "partner_page_select_partner": "Odaberi partnera", - "partner_page_shared_to_title": "Podijeljeno s", - "partner_page_stop_sharing_content": "{partner} više neće moći pristupiti vašim fotografijama.", - "partner_sharing": "Dijeljenje s partnerom", - "partners": "Partneri", - "password": "Zaporka", - "password_does_not_match": "Zaporka se ne podudara", - "password_required": "Zaporka je obavezna", - "password_reset_success": "Reset zaporke je uspješan", - "past_durations": { - "days": "{days, plural, one {Prošli dan} few {Prošlih # dana} other {Prošlih # dana}}", - "hours": "{hours, plural, one {Prošli sat} few {Prošla # sata} other {Prošlih # sati}}", - "years": "{years, plural, one {Prošle godine} few {Prošle # godine} other {Prošlih # godina}}" - }, - "path": "Putanja", - "pattern": "Uzorak", - "pause": "Pauza", - "pause_memories": "Pauziraj sjećanja", - "paused": "Pauzirano", - "pending": "Na čekanju", - "people": "Osobe", - "people_edits_count": "Izmjenjeno {count, plural, one {# osoba} other {# osobe}}", - "people_feature_description": "Pregledavanje fotografija i videozapisa grupiranih po osobama", - "people_sidebar_description": "Prikažite poveznicu na Osobe na bočnoj traci", - "permanent_deletion_warning": "Upozorenje za nepovratno brisanje", - "permanent_deletion_warning_setting_description": "Prikaži upozorenje prilikom trajnog brisanja stavki", - "permanently_delete": "Nepovratno obriši", - "permanently_delete_assets_count": "Trajno izbriši {count, plural, one {datoteku} other {datoteke}}", - "permanently_delete_assets_prompt": "Jeste li sigurni da želite trajno izbrisati {count, plural, one {ovu stavku?} other {ove # stavke?}} Ovo će {count, plural, one {ju također ukloniti iz njezinog} other {ih također ukloniti iz njihovih}} albuma.", - "permanently_deleted_asset": "Trajno izbrisana stavka", - "permanently_deleted_assets_count": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbisano # stavki}}", - "permission": "Dozvola", - "permission_empty": "Vaša dozvola ne smije biti prazna", - "permission_onboarding_back": "Natrag", - "permission_onboarding_continue_anyway": "Nastavi svejedno", - "permission_onboarding_get_started": "Započni", - "permission_onboarding_go_to_settings": "Idi u postavke", - "permission_onboarding_permission_denied": "Dopuštenje odbijeno. Za korištenje Immicha, dodijelite dopuštenja za fotografije i videozapise u Postavkama.", - "permission_onboarding_permission_granted": "Dopuštenje dodijeljeno! Sve je spremno.", - "permission_onboarding_permission_limited": "Dopuštenje ograničeno. Da biste Immichu dopustili sigurnosno kopiranje i upravljanje cijelom galerijom, dodijelite dopuštenja za fotografije i videozapise u Postavkama.", - "permission_onboarding_request": "Immich zahtijeva dopuštenje za pregled vaših fotografija i videozapisa.", - "person": "Osoba", - "person_age_months": "{months, plural, one {# mjesec} few {# mjeseca} other {# mjeseci}} staro", - "person_age_year_months": "1 godina, {months, plural, one {# mjesec} few {# mjeseca} other {# mjeseci}} staro", - "person_age_years": "{years, plural, few {# godine} other {# godina}} staro", - "person_birthdate": "Rođen/a {date}", - "person_hidden": "{name}{hidden, select, true { (skriveno)} other {}}", - "photo_shared_all_users": "Čini se da ste svoje fotografije podijelili sa svim korisnicima ili nemate nijednog korisnika s kojim biste ih podijelili.", - "photos": "Fotografije", - "photos_and_videos": "Fotografije i videozapisi", - "photos_count": "{count, plural, one {{count, number} fotografija} few {{count, number} fotografije} other {{count, number} fotografija}}", - "photos_from_previous_years": "Fotografije iz prethodnih godina", - "pick_a_location": "Odaberite lokaciju", - "pin_code_changed_successfully": "PIN kod je uspješno promijenjen", - "pin_code_reset_successfully": "PIN kod je uspješno poništen", - "pin_code_setup_successfully": "PIN kod je uspješno postavljen", - "pin_verification": "Provjera PIN koda", - "place": "Mjesto", - "places": "Mjesta", - "places_count": "{count, plural, =1 {{count, number} Mjesto} few {{count, number} Mjesta} other {{count, number} Mjesta}}", - "play": "Pokreni", - "play_memories": "Pokreni sjećanja", - "play_motion_photo": "Reproduciraj Pokretnu fotografiju", - "play_or_pause_video": "Reproducirajte ili pauzirajte video", - "play_original_video_setting_description": "Preferirajte reprodukciju originalnih videozapisa umjesto transkodiranih videozapisa. Ako originalna stavka nije kompatibilna, možda se neće ispravno reproducirati.", - "please_auth_to_access": "Molimo autentificirajte se za pristup", - "port": "Port", - "preferences_settings_subtitle": "Upravljajte postavkama aplikacije", - "preferences_settings_title": "Postavke", - "preparing": "Pripremanje", - "preset": "Unaprijed postavljeno", - "preview": "Pregled", - "previous": "Prethodno", - "previous_memory": "Prethodno sjećanje", - "previous_or_next_day": "Dan naprijed/natrag", - "previous_or_next_month": "Mjesec naprijed/natrag", - "previous_or_next_photo": "Fotografija naprijed/natrag", - "previous_or_next_year": "Godina naprijed/natrag", - "primary": "Primarna (Primary)", - "privacy": "Privatnost", - "profile": "Profil", - "profile_drawer_app_logs": "Zapisnici", - "profile_drawer_client_server_up_to_date": "Klijent i poslužitelj su ažurirani", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Omogućen je način rada samo za čitanje. Dugo pritisnite ikonu korisničkog avatara za izlaz.", - "profile_image_of_user": "Profilna slika korisnika {user}", - "profile_picture_set": "Profilna slika postavljena.", - "public_album": "Javni album", - "public_share": "Javno dijeljenje", - "purchase_account_info": "Podržava softver", - "purchase_activated_subtitle": "Hvala što podržavate Immich i softver otvorenog koda", - "purchase_activated_time": "Aktivirano {date}", - "purchase_activated_title": "Vaš ključ je uspješno aktiviran", - "purchase_button_activate": "Aktiviraj", - "purchase_button_buy": "Kupi", - "purchase_button_buy_immich": "Kupi Immich", - "purchase_button_never_show_again": "Nikad više ne prikazuj", - "purchase_button_reminder": "Podsjeti me za 30 dana", - "purchase_button_remove_key": "Ukloni ključ", - "purchase_button_select": "Odaberite", - "purchase_failed_activation": "Aktivacija nije uspjela! Provjerite svoju e-poštu za točan ključ proizvoda!", - "purchase_individual_description_1": "Za pojedinca", - "purchase_individual_description_2": "Status podržavanja", - "purchase_individual_title": "Pojedinačna licenca", - "purchase_input_suggestion": "Imate ključ proizvoda? Unesite ključ ispod", - "purchase_license_subtitle": "Kupite Immich kako biste podržali kontinuirani razvoj usluge", - "purchase_lifetime_description": "Doživotna kupnja", - "purchase_option_title": "MOGUĆNOSTI KUPNJE", - "purchase_panel_info_1": "Za izgradnju Immicha potrebno je puno vremena i truda, a mi imamo inženjere koji rade na tome s punim radnim vremenom kako bismo ga učinili što boljim. Naša je misija da softver otvorenog koda i etička poslovna praksa postanu održivi izvor prihoda za programere i da se stvori ekosustav koji poštuje privatnost sa stvarnim alternativama eksploatacijskim uslugama u oblaku.", - "purchase_panel_info_2": "Budući da se obvezujemo da nećemo dodavati dodatne pretplate, ova vam kupnja neće dodijeliti nikakve dodatne značajke u Immichu. Oslanjamo se na korisnike poput vas da podržimo stalni razvoj Immicha.", - "purchase_panel_title": "Podrži projekt", - "purchase_per_server": "Po serveru", - "purchase_per_user": "Po korisniku", - "purchase_remove_product_key": "Ukloni ključ proizvoda", - "purchase_remove_product_key_prompt": "Jeste li sigurni da želite ukloniti ključ proizvoda?", - "purchase_remove_server_product_key": "Uklonite ključ proizvoda poslužitelja (Server)", - "purchase_remove_server_product_key_prompt": "Jeste li sigurni da želite ukloniti ključ proizvoda poslužitelja (Server)?", - "purchase_server_description_1": "Za cijeli server", - "purchase_server_description_2": "Status podupiratelja", - "purchase_server_title": "Poslužitelj (Server)", - "purchase_settings_server_activated": "Ključem proizvoda poslužitelja upravlja administrator", - "query_asset_id": "Ispitaj ID stavke", - "queue_status": "Stavljanje u red {count}/{total}", - "rating": "Broj zvjezdica", - "rating_clear": "Obriši ocjenu", - "rating_count": "{count, plural, one {# zvijezda} other {# zvijezde}}", - "rating_description": "Prikaži EXIF ocjenu na info ploči", - "reaction_options": "Mogućnosti reakcije", - "read_changelog": "Pročitajte Dnevnik promjena", - "readonly_mode_disabled": "Onemogućen način rada samo za čitanje", - "readonly_mode_enabled": "Omogućen način rada samo za čitanje", - "ready_for_upload": "Spremno za prijenos", - "reassign": "Ponovno dodijeli", - "reassigned_assets_to_existing_person": "Ponovno dodijeljeno {count, plural, one {# stavka} few {# stavke} other {# stavki}} {name, select, null {postojećoj osobi} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {# stavka ponovno dodijeljena} few {# stavke ponovno dodijeljene} other {# stavki ponovno dodijeljeno}} novoj osobi", - "reassing_hint": "Dodijelite odabrane stavke postojećoj osobi", - "recent": "Nedavno", - "recent-albums": "Nedavni albumi", - "recent_searches": "Nedavne pretrage", - "recently_added": "Nedavno dodano", - "recently_added_page_title": "Nedavno dodano", - "recently_taken": "Nedavno snimljeno", - "recently_taken_page_title": "Nedavno snimljeno", - "refresh": "Osvježi", - "refresh_encoded_videos": "Osvježite kodirane videozapise", - "refresh_faces": "Osvježite lica", - "refresh_metadata": "Osvježi metapodatke", - "refresh_thumbnails": "Osvježi sličice", - "refreshed": "Osvježeno", - "refreshes_every_file": "Osvježava svaku datoteku", - "refreshing_encoded_video": "Osvježavanje kodiranog videa", - "refreshing_faces": "Osvježavanje lica", - "refreshing_metadata": "Osvježavanje metapodataka", - "regenerating_thumbnails": "Obnavljanje sličica", - "remote": "Udaljeno", - "remote_assets": "Udaljene stavke", - "remove": "Ukloni", - "remove_assets_album_confirmation": "Jeste li sigurni da želite ukloniti {count, plural, one {# stavku} few {# stavke} other {# stavki}} iz albuma?", - "remove_assets_shared_link_confirmation": "Jeste li sigurni da želite ukloniti {count, plural, one {# stavku} few {# stavke} other {# stavki}} iz ove dijeljene veze?", - "remove_assets_title": "Ukloniti stavke?", - "remove_custom_date_range": "Ukloni prilagođeni datumski raspon", - "remove_deleted_assets": "Ukloni izbrisane stavke", - "remove_from_album": "Ukloni iz albuma", - "remove_from_album_action_prompt": "{count} uklonjeno iz albuma", - "remove_from_favorites": "Ukloni iz favorita", - "remove_from_lock_folder_action_prompt": "{count} uklonjeno iz zaključane mape", - "remove_from_locked_folder": "Ukloni iz zaključane mape", - "remove_from_locked_folder_confirmation": "Jeste li sigurni da želite premjestiti ove fotografije i videozapise iz zaključane mape? Bit će vidljivi u vašoj biblioteci.", - "remove_from_shared_link": "Ukloni iz dijeljene poveznice", - "remove_memory": "Ukloni uspomenu", - "remove_photo_from_memory": "Ukloni fotografiju iz ove uspomene", - "remove_tag": "Ukloni oznaku", - "remove_url": "Ukloni URL", - "remove_user": "Ukloni korisnika", - "removed_api_key": "Uklonjen API ključ: {name}", - "removed_from_archive": "Uklonjeno iz arhive", - "removed_from_favorites": "Uklonjeno iz favorita", - "removed_from_favorites_count": "{count, plural, other {Uklonjeno #}} iz omiljenih", - "removed_memory": "Uklonjena uspomena", - "removed_photo_from_memory": "Uklonjena fotografija iz uspomene", - "removed_tagged_assets": "Uklonjena oznaka iz {count, plural, one {# stavke} few {# stavke} other {# stavki}}", - "rename": "Preimenuj", - "repair": "Popravi", - "repair_no_results_message": "Nepraćene datoteke i datoteke koje nedostaju pojavit će se ovdje", - "replace_with_upload": "Zamijeni s prijenosom", - "repository": "Spremište (Repository)", - "require_password": "Zahtijevaj lozinku", - "require_user_to_change_password_on_first_login": "Zahtijevajte od korisnika promjenu lozinke pri prvoj prijavi", - "rescan": "Ponovno skeniraj", - "reset": "Resetiraj", - "reset_password": "Resetiraj lozinku", - "reset_people_visibility": "Poništi vidljivost osoba", - "reset_pin_code": "Resetiraj PIN kod", - "reset_pin_code_description": "Ako ste zaboravili svoj PIN kod, možete kontaktirati administratora poslužitelja da ga resetira", - "reset_pin_code_success": "PIN kod je uspješno resetiran", - "reset_pin_code_with_password": "Uvijek možete resetirati svoj PIN kod pomoću svoje lozinke", - "reset_sqlite": "Resetiraj SQLite bazu podataka", - "reset_sqlite_confirmation": "Jeste li sigurni da želite resetirati SQLite bazu podataka? Morat ćete se odjaviti i ponovno prijaviti kako biste ponovno sinkronizirali podatke", - "reset_sqlite_success": "SQLite baza podataka je uspješno resetirana", - "reset_to_default": "Vrati na zadano", - "resolve_duplicates": "Riješite duplikate", - "resolved_all_duplicates": "Razriješi sve duplikate", - "restore": "Oporavi", - "restore_all": "Oporavi sve", - "restore_trash_action_prompt": "{count} vraćeno iz smeća", - "restore_user": "Vrati korisnika", - "restored_asset": "Obnovljena stavka", - "resume": "Nastavi", - "retry_upload": "Ponovi prijenos", - "review_duplicates": "Pregledajte duplikate", - "review_large_files": "Pregledaj velike datoteke", - "role": "Uloga", - "role_editor": "Urednik", - "role_viewer": "Gledatelj", - "running": "U tijeku", - "save": "Spremi", - "save_to_gallery": "Spremi u galeriju", - "saved_api_key": "Spremljen API ključ", - "saved_profile": "Spremljen profil", - "saved_settings": "Spremljene postavke", - "say_something": "Reci nešto", - "scaffold_body_error_occurred": "Došlo je do pogreške", - "scan_all_libraries": "Skeniraj sve Knjižnice", - "scan_library": "Skeniraj", - "scan_settings": "Postavke skeniranja", - "scanning_for_album": "Skeniranje albuma...", - "search": "Pretraživanje", - "search_albums": "Traži albume", - "search_by_context": "Pretraživanje po kontekstu", - "search_by_description": "Pretraži po opisu", - "search_by_description_example": "Planinarenje u Sapi", - "search_by_filename": "Pretražujte prema nazivu datoteke ili ekstenziji", - "search_by_filename_example": "npr. IMG_1234.JPG ili PNG", - "search_camera_lens_model": "Pretraži model objektiva...", - "search_camera_make": "Pretražite marku kamere...", - "search_camera_model": "Pretražite model kamere...", - "search_city": "Pretražite grad...", - "search_country": "Pretražite državu...", - "search_filter_apply": "Primijeni filtar", - "search_filter_camera_title": "Odaberi vrstu kamere", - "search_filter_date": "Datum", - "search_filter_date_interval": "{start} do {end}", - "search_filter_date_title": "Odaberi raspon datuma", - "search_filter_display_option_not_in_album": "Nije u albumu", - "search_filter_display_options": "Opcije prikaza", - "search_filter_filename": "Pretraži po nazivu datoteke", - "search_filter_location": "Lokacija", - "search_filter_location_title": "Odaberi lokaciju", - "search_filter_media_type": "Vrsta medija", - "search_filter_media_type_title": "Odaberi vrstu medija", - "search_filter_ocr": "Pretraži OCRom", - "search_filter_people_title": "Odaberi osobe", - "search_for": "Traži", - "search_for_existing_person": "Potražite postojeću osobu", - "search_no_more_result": "Nema više rezultata", - "search_no_people": "Nema osoba", - "search_no_people_named": "Nema osoba s imenom \"{name}\"", - "search_no_result": "Nema rezultata, pokušajte s drugim pojmom za pretraživanje ili kombinacijom", - "search_options": "Opcije pretraživanja", - "search_page_categories": "Kategorije", - "search_page_motion_photos": "Pokretne fotografije", - "search_page_no_objects": "Nema dostupnih informacija o objektima", - "search_page_no_places": "Nema dostupnih informacija o mjestima", - "search_page_screenshots": "Snimke zaslona", - "search_page_search_photos_videos": "Pretražite svoje fotografije i videozapise", - "search_page_selfies": "Selfiji", - "search_page_things": "Stvari", - "search_page_view_all_button": "Prikaži sve", - "search_page_your_activity": "Vaša aktivnost", - "search_page_your_map": "Vaša karta", - "search_people": "Traži ljude", - "search_places": "Traži mjesta", - "search_rating": "Pretraži po ocjeni...", - "search_result_page_new_search_hint": "Nova pretraga", - "search_settings": "Postavke pretraživanja", - "search_state": "Država pretraživanja...", - "search_suggestion_list_smart_search_hint_1": "Pametna pretraga je omogućena prema zadanim postavkama, za pretraživanje metapodataka koristite sintaksu ", - "search_suggestion_list_smart_search_hint_2": "m:vaš-pojam-pretrage", - "search_tags": "Traži oznake...", - "search_timezone": "Pretraživanje vremenske zone...", - "search_type": "Vrsta pretraživanja", - "search_your_photos": "Pretražite svoje fotografije", - "searching_locales": "Traženje lokaliteta...", - "second": "Drugi", - "see_all_people": "Vidi sve ljude", - "select": "Odaberi", - "select_album_cover": "Odaberite omot albuma", - "select_all": "Odaberi sve", - "select_all_duplicates": "Odaberi sve duplikate", - "select_all_in": "Odaberi sve u {group}", - "select_avatar_color": "Odaberi boju avatara", - "select_face": "Odaberi lice", - "select_featured_photo": "Odaberi istaknutu fotografiju", - "select_from_computer": "Odaberi s računala", - "select_keep_all": "Odaberi zadrži sve", - "select_library_owner": "Odaberi vlasnika knjižnice", - "select_new_face": "Odaberi novo lice", - "select_person_to_tag": "Odaberite osobu za označavanje", - "select_photos": "Odaberi fotografije", - "select_trash_all": "Odaberi izbriši sve", - "select_user_for_sharing_page_err_album": "Nije uspjelo kreiranje albuma", - "selected": "Odabrano", - "selected_count": "{count, plural, =1 {# odabran} few {# odabrana} other {# odabranih}}", - "selected_gps_coordinates": "Odaberi GPS koordinate", - "send_message": "Pošalji poruku", - "send_welcome_email": "Pošalji email dobrodošlice", - "server_endpoint": "Krajnja točka poslužitelja", - "server_info_box_app_version": "Verzija aplikacije", - "server_info_box_server_url": "URL poslužitelja", - "server_offline": "Server izvan mreže", - "server_online": "Server na mreži", - "server_privacy": "Privatnost poslužitelja", - "server_restarting_description": "Ova stranica će se odmah osvježiti.", - "server_restarting_title": "Poslužitelj se ponovno pokreće", - "server_stats": "Statistike servera", - "server_update_available": "Ažuriranje poslužitelja dostupno", - "server_version": "Verzija servera", - "set": "Postavi", - "set_as_album_cover": "Postavi kao naslovnicu albuma", - "set_as_featured_photo": "Postavi kao istaknutu fotografiju", - "set_as_profile_picture": "Postavi kao profilnu sliku", - "set_date_of_birth": "Postavi datum rođenja", - "set_profile_picture": "Postavi profilnu sliku", - "set_slideshow_to_fullscreen": "Postavi prezentaciju na cijeli zaslon", - "set_stack_primary_asset": "Postavi kao glavnu stavku", - "setting_image_viewer_help": "Preglednik detalja prvo učitava malu sličicu, zatim učitava pregled srednje veličine (ako je omogućen), te na kraju učitava original (ako je omogućen).", - "setting_image_viewer_original_subtitle": "Omogućite za učitavanje originalne slike pune rezolucije (velika!). Onemogućite za smanjenje potrošnje podataka (i mrežne i na predmemoriji uređaja).", - "setting_image_viewer_original_title": "Učitaj originalnu sliku", - "setting_image_viewer_preview_subtitle": "Omogućite za učitavanje slike srednje rezolucije. Onemogućite za izravno učitavanje originala ili korištenje samo sličice.", - "setting_image_viewer_preview_title": "Učitaj sliku za pregled", - "setting_image_viewer_title": "Slike", - "setting_languages_apply": "Primijeni", - "setting_languages_subtitle": "Promijeni jezik aplikacije", - "setting_notifications_notify_failures_grace_period": "Obavijesti o neuspjehu sigurnosnog kopiranja u pozadini: {duration}", - "setting_notifications_notify_hours": "{count} sati", - "setting_notifications_notify_immediately": "odmah", - "setting_notifications_notify_minutes": "{count} minuta", - "setting_notifications_notify_never": "nikad", - "setting_notifications_notify_seconds": "{count} sekundi", - "setting_notifications_single_progress_subtitle": "Detaljne informacije o napretku prijenosa po stavki", - "setting_notifications_single_progress_title": "Prikaži detaljni napredak sigurnosnog kopiranja u pozadini", - "setting_notifications_subtitle": "Prilagodite postavke obavijesti", - "setting_notifications_total_progress_subtitle": "Ukupni napredak prijenosa (završeno/ukupan broj stavki)", - "setting_notifications_total_progress_title": "Prikaži ukupni napredak sigurnosnog kopiranja u pozadini", - "setting_video_viewer_auto_play_subtitle": "Automatski započni reprodukciju videa kad se otvori", - "setting_video_viewer_auto_play_title": "Automatski pokreni videe", - "setting_video_viewer_looping_title": "Ponavljanje", - "setting_video_viewer_original_video_subtitle": "Prilikom strujanja videozapisa s poslužitelja, reproducirajte original čak i kada je dostupna transkodirana verzija. Može doći do međuspremanja. Videozapisi dostupni lokalno reproduciraju se u originalnoj kvaliteti bez obzira na ovu postavku.", - "setting_video_viewer_original_video_title": "Forsiraj originalni videozapis", - "settings": "Postavke", - "settings_require_restart": "Ponovno pokrenite Immich da biste primijenili ovu postavku", - "settings_saved": "Postavke su spremljene", - "setup_pin_code": "Postavi PIN kod", - "share": "Podijeli", - "share_action_prompt": "{count, plural, one {Podijeljena # stavka} few {Podijeljene # stavke} other {Podijeljeno # stavki}}", - "share_add_photos": "Dodaj fotografije", - "share_assets_selected": "{count, plural, one {# odabran} few {# odabrana} other {# odabrano}}", - "share_dialog_preparing": "Priprema...", - "share_link": "Podijeli Link", - "shared": "Podijeljeno", - "shared_album_activities_input_disable": "Komentiranje je onemogućeno", - "shared_album_activity_remove_content": "Želite li izbrisati ovu aktivnost?", - "shared_album_activity_remove_title": "Izbriši aktivnost", - "shared_album_section_people_action_error": "Pogreška pri napuštanju/uklanjanju iz albuma", - "shared_album_section_people_action_leave": "Ukloni korisnika iz albuma", - "shared_album_section_people_action_remove_user": "Ukloni korisnika iz albuma", - "shared_album_section_people_title": "OSOBE", - "shared_by": "Podijelio", - "shared_by_user": "Podijelio {user}", - "shared_by_you": "Podijelili vi", - "shared_from_partner": "Fotografije od {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Preneseno", - "shared_link_app_bar_title": "Dijeljene poveznice", - "shared_link_clipboard_copied_massage": "Kopirano u međuspremnik", - "shared_link_clipboard_text": "Poveznica: {link}\nLozinka: {password}", - "shared_link_create_error": "Pogreška pri kreiranju dijeljene poveznice", - "shared_link_custom_url_description": "Pristupite ovom dijeljenom linku pomoću prilagođene URL adrese", - "shared_link_edit_description_hint": "Unesite opis dijeljenja", - "shared_link_edit_expire_after_option_day": "1 dan", - "shared_link_edit_expire_after_option_days": "{count} dana", - "shared_link_edit_expire_after_option_hour": "1 sat", - "shared_link_edit_expire_after_option_hours": "{count} sati", - "shared_link_edit_expire_after_option_minute": "1 minuta", - "shared_link_edit_expire_after_option_minutes": "{count} minuta", - "shared_link_edit_expire_after_option_months": "{count} mjeseci", - "shared_link_edit_expire_after_option_year": "{count} godina", - "shared_link_edit_password_hint": "Unesite lozinku za dijeljenje", - "shared_link_edit_submit_button": "Ažuriraj poveznicu", - "shared_link_error_server_url_fetch": "Nije moguće dohvatiti URL poslužitelja", - "shared_link_expires_day": "Istječe za {count} dan", - "shared_link_expires_days": "Istječe za {count} dana", - "shared_link_expires_hour": "Istječe za {count} sat", - "shared_link_expires_hours": "Istječe za {count} sati", - "shared_link_expires_minute": "Istječe za {count} minutu", - "shared_link_expires_minutes": "Istječe za {count} minuta", - "shared_link_expires_never": "Istječe ∞", - "shared_link_expires_second": "Istječe za {count} sekundu", - "shared_link_expires_seconds": "Istječe za {count} sekundi", - "shared_link_individual_shared": "Pojedinačno podijeljeno", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Upravljanje dijeljenim poveznicama", - "shared_link_options": "Opcije dijeljene poveznice", - "shared_link_password_description": "Zahtjevaj loziku za pristup ovom dijeljenom linku", - "shared_links": "Dijeljene poveznice", - "shared_links_description": "Podijelite fotografije i videozapise putem poveznice", - "shared_photos_and_videos_count": "{assetCount, plural, one {# podijeljena fotografija ili videozapis.} few {# podijeljene fotografije i videozapisa.} other {# podijeljenih fotografija i videozapisa.}}", - "shared_with_me": "Podijeljeno sa mnom", - "shared_with_partner": "Podijeljeno s {partner}", - "sharing": "Dijeljenje", - "sharing_enter_password": "Molimo unesite lozinku za pregled ove stranice.", - "sharing_page_album": "Dijeljeni albumi", - "sharing_page_description": "Kreirajte dijeljene albume za dijeljenje fotografija i videozapisa s ljudima u vašoj mreži.", - "sharing_page_empty_list": "PRAZAN POPIS", - "sharing_sidebar_description": "Prikaži poveznicu na Dijeljenje u bočnoj traci", - "sharing_silver_appbar_create_shared_album": "Novi dijeljeni album", - "sharing_silver_appbar_share_partner": "Podijeli s partnerom", - "shift_to_permanent_delete": "pritisnite ⇧ za trajno brisanje stavke", - "show_album_options": "Prikaži opcije albuma", - "show_albums": "Prikaži albume", - "show_all_people": "Prikaži sve osobe", - "show_and_hide_people": "Prikaži i sakrij osobe", - "show_file_location": "Pokaži mjesto datoteke", - "show_gallery": "Prikaži galeriju", - "show_hidden_people": "Prikaži skrivene osobe", - "show_in_timeline": "Prikaži na vremenskoj crti", - "show_in_timeline_setting_description": "Prikaži fotografije i videozapise ovog korisnika na vašoj vremenskoj crti", - "show_keyboard_shortcuts": "Prikaži tipkovničke prečace", - "show_metadata": "Prikaži metapodatke", - "show_or_hide_info": "Prikaži ili sakrij informacije", - "show_password": "Prikaži lozinku", - "show_person_options": "Prikaži opcije osobe", - "show_progress_bar": "Prikaži traku napretka", - "show_search_options": "Prikaži opcije pretraživanja", - "show_shared_links": "Prikaži dijeljene poveznice", - "show_slideshow_transition": "Prikaži prijelaz prezentacije", - "show_supporter_badge": "Značka podržavatelja", - "show_supporter_badge_description": "Prikaži značku podržavatelja", - "show_text_recognition": "Pokaži prepoznavanje teksta", - "show_text_search_menu": "Pokaži meni pretrage teksta", - "shuffle": "Nasumični redoslijed", - "sidebar": "Bočna traka", - "sidebar_display_description": "Prikaži poveznicu na prikaz u bočnoj traci", - "sign_out": "Odjava", - "sign_up": "Registriraj se", - "size": "Veličina", - "skip_to_content": "Preskoči na sadržaj", - "skip_to_folders": "Preskoči na mape", - "skip_to_tags": "Preskoči na oznake", - "slideshow": "Prezentacija", - "slideshow_settings": "Postavke prezentacije", - "sort_albums_by": "Sortiraj albume po...", - "sort_created": "Datum kreiranja", - "sort_items": "Broj stavki", - "sort_modified": "Datum izmjene", - "sort_newest": "Najnovija fotografija", - "sort_oldest": "Najstarija fotografija", - "sort_people_by_similarity": "Sortiraj osobe po sličnosti", - "sort_recent": "Najnovija fotografija", - "sort_title": "Naslov", - "source": "Izvor", - "stack": "Složi", - "stack_action_prompt": "{count} složeno", - "stack_duplicates": "Složi duplikate", - "stack_select_one_photo": "Odaberi jednu glavnu fotografiju za slaganje", - "stack_selected_photos": "Složi odabrane fotografije", - "stacked_assets_count": "{count, plural, one {Složena # stavka} few {Složene # stavke} other {Složeno # stavki}}", - "stacktrace": "Pracenje stoga", - "start": "Početak", - "start_date": "Datum početka", - "start_date_before_end_date": "Početni datum mora biti prije krajnjeg datuma", - "state": "Stanje", - "status": "Status", - "stop_casting": "Zaustavi reprodukciju", - "stop_motion_photo": "Zaustavi pokretnu fotografiju", - "stop_photo_sharing": "Prestati dijeliti svoje fotografije?", - "stop_photo_sharing_description": "{partner} više neće moći pristupiti vašim fotografijama.", - "stop_sharing_photos_with_user": "Prestani dijeliti svoje fotografije s ovim korisnikom", - "storage": "Prostor za pohranu", - "storage_label": "Oznaka pohrane", - "storage_quota": "Kvota Pohrane", - "storage_usage": "{used} od {available} iskorišteno", - "submit": "Pošalji", - "success": "Uspijeh", - "suggestions": "Prijedlozi", - "sunrise_on_the_beach": "Izlazak sunca na plaži", - "support": "Podrška", - "support_and_feedback": "Podrška i povratne informacije", - "support_third_party_description": "Vaša Immich instalacija je pakirana od strane treće strane. Problemi koje doživljavate mogu biti uzrokovani tim paketom, stoga vas molimo da probleme prvo prijavite njima putem poveznica u nastavku.", - "swap_merge_direction": "Zamijeni smjer spajanja", - "sync": "Sink.", - "sync_albums": "Sinkroniziraj albume", - "sync_albums_manual_subtitle": "Sinkroniziraj sve prenesene videozapise i fotografije u odabrane albume za sigurnosnu kopiju", - "sync_local": "Sinkroniziraj lokalno", - "sync_remote": "Sinkroniziraj udaljeno", - "sync_status": "Status sinkronizacije", - "sync_status_subtitle": "Pregledajte i upravljajte sistemom sinkronizacije", - "sync_upload_album_setting_subtitle": "Kreiraj i prenesi svoje fotografije i videozapise u odabrane albume na Immichu", - "tag": "Oznaka", - "tag_assets": "Označi stavke", - "tag_created": "Kreirana oznaka: {tag}", - "tag_feature_description": "Pregledavanje fotografija i videozapisa grupiranih po logičkim temama oznaka", - "tag_not_found_question": "Nije moguće pronaći oznaku? Napravite novu oznaku.", - "tag_people": "Označi osobe", - "tag_updated": "Ažurirana oznaka: {tag}", - "tagged_assets": "{count, plural, =1 {Označena # stavka} few {Označene # stavke} other {Označeno # stavki}}", - "tags": "Oznake", - "tap_to_run_job": "Dodirnite za pokretanje zadatka", - "template": "Predložak", - "text_recognition": "Prepoznavanje teksta", - "theme": "Tema", - "theme_selection": "Izbor teme", - "theme_selection_description": "Automatski postavite temu na svijetlu ili tamnu ovisno o postavkama sustava vašeg preglednika", - "theme_setting_asset_list_storage_indicator_title": "Prikaži indikator pohrane na pločicama stavki", - "theme_setting_asset_list_tiles_per_row_title": "Broj stavki po retku ({count})", - "theme_setting_colorful_interface_subtitle": "Primijeni primarnu boju na pozadinske površine.", - "theme_setting_colorful_interface_title": "Šareno sučelje", - "theme_setting_image_viewer_quality_subtitle": "Prilagodite kvalitetu preglednika detalja slike", - "theme_setting_image_viewer_quality_title": "Kvaliteta preglednika slika", - "theme_setting_primary_color_subtitle": "Odaberite boju za primarne radnje i naglaske.", - "theme_setting_primary_color_title": "Primarna boja", - "theme_setting_system_primary_color_title": "Koristi boju sustava", - "theme_setting_system_theme_switch": "Automatski (Prati postavke sustava)", - "theme_setting_theme_subtitle": "Odaberite postavku teme aplikacije", - "theme_setting_three_stage_loading_subtitle": "Trostupanjsko učitavanje može poboljšati performanse učitavanja, ali uzrokuje znatno veće opterećenje mreže", - "theme_setting_three_stage_loading_title": "Omogući trostupanjsko učitavanje", - "they_will_be_merged_together": "Oni ću biti spojeni zajedno", - "third_party_resources": "Resursi trećih strana", - "time": "Vrijeme", - "time_based_memories": "Uspomene temeljene na vremenu", - "time_based_memories_duration": "Broj sekundi za prikazivanje svake slike.", - "timeline": "Vremenska crta", - "timezone": "Vremenska zona", - "to_archive": "Arhivaj", - "to_change_password": "Promjeni lozinku", - "to_favorite": "Omiljeni", - "to_login": "Prijava", - "to_multi_select": "prema višestrukom odabiru", - "to_parent": "Idi na roditelja", - "to_select": "prema odabiranju", - "to_trash": "Smeće", - "toggle_settings": "Uključi/isključi postavke", - "toggle_theme_description": "Odaberi temu", - "total": "Ukupno", - "total_usage": "Ukupna upotreba", - "trash": "Smeće", - "trash_action_prompt": "{count} premješteno u smeće", - "trash_all": "Stavi sve u smeće", - "trash_count": "Smeće {count, number}", - "trash_delete_asset": "Premjesti u smeće / Izbriši stavku", - "trash_emptied": "Ispražnjeno smeće", - "trash_no_results_message": "Ovdje će se prikazati bačene fotografije i videozapisi.", - "trash_page_delete_all": "Izbriši sve", - "trash_page_empty_trash_dialog_content": "Želite li isprazniti svoje stavke u smeću? Ove stavke će biti trajno uklonjene iz Immicha", - "trash_page_info": "Stavke u smeću bit će trajno izbrisane nakon {days} dana", - "trash_page_no_assets": "Nema stavki u smeću", - "trash_page_restore_all": "Vrati sve", - "trash_page_select_assets_btn": "Odaberi stavke", - "trash_page_title": "Smeće ({count})", - "trashed_items_will_be_permanently_deleted_after": "Stavke bačene u smeće trajno će se izbrisati nakon {days, plural, one {# day} other {# days}}.", - "troubleshoot": "Rješavanje problema", - "type": "Vrsta", - "unable_to_change_pin_code": "Nije moguće promijeniti PIN kod", - "unable_to_check_version": "Nije moguće provjeriti verziju aplikacije ili poslužitelja", - "unable_to_setup_pin_code": "Nije moguće postaviti PIN kod", - "unarchive": "Poništi arhiviranje", - "unarchive_action_prompt": "{count} uklonjeno iz arhive", - "unarchived_count": "{count, plural, =1 {Poništeno arhiviranje #} few {Poništeno arhiviranje #} other {Poništeno arhiviranje #}}", - "undo": "Poništi", - "unfavorite": "Ukloni iz omiljenih", - "unfavorite_action_prompt": "{count} uklonjeno iz favorita", - "unhide_person": "Prikaži osobu", - "unknown": "Nepoznato", - "unknown_country": "Nepoznata država", - "unknown_year": "Nepoznata godina", - "unlimited": "Neograničeno", - "unlink_motion_video": "Odpoveži pokretni videozapis", - "unlink_oauth": "Odpoveži OAuth", - "unlinked_oauth_account": "Odpovezan OAuth račun", - "unmute_memories": "Uključi uspomene", - "unnamed_album": "Album bez imena", - "unnamed_album_delete_confirmation": "Jeste li sigurni da želite izbrisati ovaj album?", - "unnamed_share": "Dijeljenje bez imena", - "unsaved_change": "Nespremljena promjena", - "unselect_all": "Poništi odabir svih", - "unselect_all_duplicates": "Poništi odabir svih duplikata", - "unselect_all_in": "Poništi odabir svih u {group}", - "unstack": "Razdvoji", - "unstack_action_prompt": "{count} razloženo", - "unstacked_assets_count": "{count, plural, one {Razdvojena # stavka} few {Razvdvojene # stavke} other {Razdvojeno # stavki}}", - "untagged": "Bez oznaka", - "up_next": "Sljedeće", - "update_location_action_prompt": "Ažuriraj lokaciju ({count, plural, one {# odabrane stavke} few {# odabrane stavke} other {# odabranih stavki}}) s:", - "updated_at": "Ažurirano", - "updated_password": "Lozinka ažurirana", - "upload": "Prijenos", - "upload_concurrency": "Istovremeni prijenosi", - "upload_details": "Detalji prijenosa", - "upload_dialog_info": "Želite li sigurnosno kopirati odabrane stavke na poslužitelj?", - "upload_dialog_title": "Prenesi stavku", - "upload_errors": "Prijenos završen s {count, plural, one {# greškom} few {# greške} other {# grešaka}}, osvježite stranicu da biste vidjeli nove prenesene stavke.", - "upload_finished": "Prijenos završen", - "upload_progress": "Preostalo {remaining, number} - Obrađeno {processed, number}/{total, number}", - "upload_skipped_duplicates": "Preskočena {count, plural, one {# duplicirana stavka} few {# duplicirane stavke} other {# dupliciranih stavki}}", - "upload_status_duplicates": "Duplikati", - "upload_status_errors": "Greške", - "upload_status_uploaded": "Preneseno", - "upload_success": "Prijenos uspješan, osvježite stranicu da biste vidjeli nove prenesene stavke.", - "upload_to_immich": "Prenesi na Immich ({count})", - "uploading": "Prijenos u tijeku", - "uploading_media": "Prijenos medija", - "url": "URL", - "usage": "Korištenje", - "use_biometric": "Koristi biometriju", - "use_current_connection": "koristi trenutnu vezu", - "use_custom_date_range": "Koristi prilagođeni raspon datuma", - "user": "Korisnik", - "user_has_been_deleted": "Ovaj korisnik je izbrisan.", - "user_id": "ID korisnika", - "user_liked": "{user} je lajkao/la {type, select, photo {ovu fotografiju} video {ovaj videozapis} asset {ovu stavku} other {to}}", - "user_pin_code_settings": "PIN kod", - "user_pin_code_settings_description": "Upravljajte svojim PIN kodom", - "user_privacy": "Privatnost korisnika", - "user_purchase_settings": "Kupnja", - "user_purchase_settings_description": "Upravljajte svojom kupnjom", - "user_role_set": "Postavi {user} kao {role}", - "user_usage_detail": "Detalji korištenja korisnika", - "user_usage_stats": "Statistika korištenja računa", - "user_usage_stats_description": "Pregledajte statistiku korištenja računa", - "username": "Korisničko ime", - "users": "Korisnici", - "users_added_to_album_count": "{count, plural, one {Dodan # korisnik} few {Dodana # korisnika} other {Dodano # korisnika}} u album", - "utilities": "Alati", - "validate": "Provjeri valjanost", - "validate_endpoint_error": "Molimo unesite valjanu URL adresu", - "variables": "Varijable", - "version": "Verzija", - "version_announcement_closing": "Vaš prijatelj, Alex", - "version_announcement_message": "Bok! Dostupna je nova verzija Immicha. Odvojite malo vremena da pročitate bilješke o izdanju kako biste bili sigurni da je vaše postavljanje ažurno kako biste spriječili bilo kakve pogrešne konfiguracije, pogotovo ako koristite WatchTower ili bilo koji mehanizam koji automatski upravlja ažuriranjem vaše instance Immicha.", - "version_history": "Povijest verzija", - "version_history_item": "Instalirana verzija {version} dana {date}", - "video": "Videozapis", - "video_hover_setting": "Reproduciraj sličicu videozapisa pri prelasku mišem", - "video_hover_setting_description": "Reproduciraj sličicu videozapisa kada miš prelazi preko stavke. Čak i kada je onemogućeno, reprodukcija se može pokrenuti prelaskom miša preko ikone za reprodukciju.", - "videos": "Videozapisi", - "videos_count": "{count, plural, =1 {# Videozapis} few {# Videozapisa} other {# Videozapisa}}", - "view": "Prikaz", - "view_album": "Prikaži album", - "view_all": "Prikaži sve", - "view_all_users": "Prikaži sve korisnike", - "view_asset_owners": "Pogledaj vlasnike resursa", - "view_details": "Prikaži pojedinosti", - "view_in_timeline": "Prikaži na vremenskoj crti", - "view_link": "Prikaži poveznicu", - "view_links": "Prikaži poveznice", - "view_name": "Prikaz", - "view_next_asset": "Prikaži sljedeću stavku", - "view_previous_asset": "Prikaži prethodnu stavku", - "view_qr_code": "Prikaži QR kod", - "view_similar_photos": "Pregledaj slične slike", - "view_stack": "Prikaži složene", - "view_user": "Prikaži korisnika", - "viewer_remove_from_stack": "Ukloni iz složenih", - "viewer_stack_use_as_main_asset": "Koristi kao glavnu stavku", - "viewer_unstack": "Razdvoji", - "visibility_changed": "Vidljivost promijenjena za {count, plural, =1 {# osobu} few {# osobe} other {# osoba}}", - "waiting": "Čekanje", - "waiting_count": "Čekanje:{count}", - "warning": "Upozorenje", - "week": "Tjedan", - "welcome": "Dobrodošli", - "welcome_to_immich": "Dobrodošli u Immich", - "wifi_name": "Naziv Wi-Fi mreže", - "wrong_pin_code": "Krivi PIN kod", - "year": "Godina", - "years_ago": "prije {years, plural, =1 {# godinu} few {# godine} other {# godina}}", - "yes": "Da", - "you_dont_have_any_shared_links": "Nemate nijednu dijeljenu poveznicu", - "your_wifi_name": "Naziv vaše Wi-Fi mreže", - "zoom_image": "Povećaj sliku", - "zoom_to_bounds": "Zumiraj do granica" -} +{} diff --git a/i18n/hu.json b/i18n/hu.json index 232d492dd7..0967ef424b 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -1,2401 +1 @@ -{ - "about": "Az Immich-ről", - "account": "Fiók", - "account_settings": "Fiókbeállítások", - "acknowledge": "Megértettem", - "action": "Művelet", - "action_common_update": "Frissítés", - "action_description": "A szűrt elemeken végrehajtandó műveletek", - "actions": "Műveletek", - "active": "Aktív", - "active_count": "Aktív: {count}", - "activity": "Tevékenység", - "activity_changed": "A tevékenység {enabled, select, true {bekapcsolva} other {kikapcsolva}}", - "add": "Hozzáadás", - "add_a_description": "Leírás hozzáadása", - "add_a_location": "Helyszín hozzáadása", - "add_a_name": "Név megadása", - "add_a_title": "Cím megadása", - "add_action": "Művelet hozzáadása", - "add_action_description": "Kattints ide egy végrehajtandó művelet hozzáadásához", - "add_assets": "Elemek hozzáadása", - "add_birthday": "Születésnap hozzáadása", - "add_endpoint": "Végpont megadása", - "add_exclusion_pattern": "Kihagyási minta (pattern) hozzáadása", - "add_filter": "Szűrő hozzáadása", - "add_filter_description": "Kattints ide egy szűrési feltétel hozzáadásához", - "add_location": "Helyszín megadása", - "add_more_users": "További felhasználók hozzáadása", - "add_partner": "Partner hozzáadása", - "add_path": "Elérési útvonal megadása", - "add_photos": "Fotók hozzáadása", - "add_tag": "Címke hozzáadása", - "add_to": "Hozzáadás ide…", - "add_to_album": "Felvétel albumba", - "add_to_album_bottom_sheet_added": "Hozzáadva a(z) \"{album}\" albumhoz", - "add_to_album_bottom_sheet_already_exists": "Már benne van a(z) \"{album}\" albumban", - "add_to_album_bottom_sheet_some_local_assets": "Néhány helyi elem nem adható hozzá az albumhoz", - "add_to_album_toggle": "{album} kijelölésének váltása", - "add_to_albums": "Hozzáadás albumokhoz", - "add_to_albums_count": "Hozzáadás albumokhoz ({count})", - "add_to_bottom_bar": "Hozzáadás ehhez", - "add_to_shared_album": "Felvétel megosztott albumba", - "add_upload_to_stack": "Feltöltés hozzáadása csoporthoz", - "add_url": "URL hozzáadása", - "add_workflow_step": "Folyamat lépés hozzáadása", - "added_to_archive": "Hozzáadva az archívumhoz", - "added_to_favorites": "Hozzáadva a kedvencekhez", - "added_to_favorites_count": "{count, number} hozzáadva a kedvencekhez", - "admin": { - "add_exclusion_pattern_description": "Kihagyási minták (pattern) megadása. A *, ** és ? helyettesítő karakterek engedélyezettek. Pl. a \"Raw\" könyvtárban tárolt összes fájl kihagyásához használható a \"**/Raw/**\". Minden \".tif\" fájl kihagyása az összes mappában: \"**/*.tif\". Abszolút elérési útvonal kihagyása: \"/kihagyni/kivant/mappa/**\".", - "admin_user": "Admin felhasználó", - "asset_offline_description": "Ez a külső képtárban lévő elem már nem található, ezért a lomtárba került. Ha a fájl a képtáron belül lett áthelyezve, akkor ellenőrizd, hogy továbbra is látható az idővonaladon. Az elem visszaállításához győződj meg róla, hogy az alábbi mappa az Immich számára elérhető, majd újra fésüld át a képtárat.", - "authentication_settings": "Hitelesítési beállítások", - "authentication_settings_description": "Jelszó, OAuth és egyéb hitelesítési beállítások kezelése", - "authentication_settings_disable_all": "Biztosan letiltod az összes bejelentkezési módot? A bejelentkezés teljesen le lesz tiltva.", - "authentication_settings_reenable": "Az újbóli engedélyezéshez használj egy szerver parancsot.", - "background_task_job": "Háttérfeladatok", - "backup_database": "Adatbázis lementése", - "backup_database_enable_description": "Adatbázis mentések engedélyezése", - "backup_keep_last_amount": "Megőrizendő korábbi mentések száma", - "backup_onboarding_1_description": "külső tárolón, felhőben vagy másik fizikai helyen tárolt másolat.", - "backup_onboarding_2_description": "helyi másolatok különböző eszközökön. Beleértve a fő fájlokat és azok helyi biztonsági másolatát.", - "backup_onboarding_3_description": "az adatok összes másolata, beleértve az eredetit is. Ez 1 külső és 2 helyi másolatot tartalmaz.", - "backup_onboarding_description": "Az adatok védelme érdekében a 3-2-1 biztonsági mentési stratégia alkalmazása ajánlott. A teljes körű biztonsági mentés érdekében érdemes másolatot készíteni a feltöltött fényképekről/videókról, valamint az Immich adatbázisról.", - "backup_onboarding_footer": "Az Immich biztonsági mentéséről további információkat a dokumentációban találsz.", - "backup_onboarding_parts_title": "A 3-2-1 biztonsági mentés ezt tartalmazza:", - "backup_onboarding_title": "Biztonsági mentések", - "backup_settings": "Adatbázis mentés beállításai", - "backup_settings_description": "Adatbázis mentés beállításainak kezelése.", - "cleared_jobs": "{job} feladatai törölve", - "config_set_by_file": "A konfigurációt jelenleg egy konfigurációs fájl állítja be", - "confirm_delete_library": "Biztosan törölni szeretnéd a(z) {library} képtárat?", - "confirm_delete_library_assets": "Biztosan törlöd ezt a képtárat? Ez nem visszavonható módon törli az Immich-ből a benne lévő {count, plural, one {#} other {#}} elemet is. A fájlok fizikailag a lemezen maradnak.", - "confirm_email_below": "A megerősítéshez írd be, hogy \"{email}\"", - "confirm_reprocess_all_faces": "Biztos vagy benne, hogy újra fel szeretnéd dolgozni az összes arcot? Ez a már elnevezett személyeket is törli.", - "confirm_user_password_reset": "Biztosan vissza szeretnéd állítani {user} jelszavát?", - "confirm_user_pin_code_reset": "Biztosan vissza akarod állítani {user} PIN-kódját?", - "copy_config_to_clipboard_description": "Jelenlegi rendszer konfiguráció másolása a vágólapra JSON objektumként", - "create_job": "Feladat létrehozása", - "cron_expression": "Cron kifejezés", - "cron_expression_description": "A beolvasási időköz beállítása a cron formátummal. További információért lásd pl. Crontab Guru", - "cron_expression_presets": "Cron kifejezés előbeállítás", - "disable_login": "Bejelentkezés letiltása", - "duplicate_detection_job_description": "Gépi tanulás futtatása a hasonló elemek megtalálása céljából. Ez az Okos keresés funkciót használja", - "exclusion_pattern_description": "A kihagyási minták (pattern) használatakor a mintának megfelelő fájlok vagy mappák át lesznek ugorva a képtár átfésülésekor. Akkor hasznos, ha a mappákban vannak olyan fájlok is, amelyeket nem szeretnél importálni, pl. nyers (RAW) fájlok.", - "export_config_as_json_description": "Jelenlegi rendszer konfiguráció letöltése JSON fájlként", - "external_libraries_page_description": "Admin külső könyvtár oldala", - "face_detection": "Arckeresés", - "face_detection_description": "Gépi tanulás segítségével megkeresi, hogy hol találhatóak arcok az elemeken. Videók esetében csak a bélyegképeken keres. \"Frissítés\" (újra) feldolgozza az összes elemet. \"Visszaállítás\" ezen felül törli az összes aktuális arcadatot. \"Hiányzók\" sorba állítja azokat az elemeket, amelyek eddig még nem lettek feldolgozva. A megtalált arcok ezután sorba lesznek állítva az arcfelismeréshez, ami ezután az arcokat csoportosítja és meglevő vagy új személyekhez rendeli.", - "facial_recognition_job_description": "A megtalált arcokat személyekhez csoportosítja. Ez a lépés azután következik, amikor az arckeresés lefutott. \"Visszaállítás\" (újra)csoportosítja az összes arcot. \"Hiányzók\" csak azokkal az arcokkal foglalkozik, amelyekhez még nincsen személy rendelve.", - "failed_job_command": "A(z) {command} parancs nem sikerült a következő feladathoz: {job}", - "force_delete_user_warning": "FIGYELEM: Ez azonnal eltávolítja a felhasználót és az összes hozzá tartozó elemet. A művelet nem visszavonható és a fájlokat sem lehet később visszanyerni.", - "image_format": "Formátum", - "image_format_description": "WebP a JPEG-nél kisebb fájlokat készít, de lassabban.", - "image_fullsize_description": "Teljes méretű kép eltávolított metaadatokkal, nagyításkor használva", - "image_fullsize_enabled": "Teljes méretű képgenerálás engedélyezése", - "image_fullsize_enabled_description": "Teljes méretű kép generálása nem webbarát formátumokhoz. Ha a „Beágyazott előnézet preferálása” engedélyezve van, a beágyazott előnézetek közvetlenül, átalakítás nélkül kerülnek felhasználásra. Nem érinti a webbarát formátumokat, például a JPEG-et.", - "image_fullsize_quality_description": "Teljes méretű képminőség 1-100 között. A magasabb érték jobb minőséget eredményez, de nagyobb fájlméretet is.", - "image_fullsize_title": "Teljes méretű képbeállítások", - "image_prefer_embedded_preview": "Beágyazott előnézeti kép előnyben részesítése", - "image_prefer_embedded_preview_setting_description": "Nyers (RAW) fotók esetén használja a beépített előnézeti képet (ha van) a képek feldogozásához. Ez néhány kép esetében pontosabb színeket eredményezhet, de az előnézeti kép minősége erősen fényképezőgép függő, és a képen előfordulhatnak tömörítési hibák.", - "image_prefer_wide_gamut": "Széles színtér preferálása", - "image_prefer_wide_gamut_setting_description": "A bélyegképekhez DCI-P3 színtér használata. Ez a széles színteret használó képek esetén (pl: Adobe RGB, P3) jobban megőrzi az élénkebb színeket, de régebbi eszközökön vagy böngészőkben a kép színei másképpen jelenhetnek meg. Az sRGB képek a színeltolódások megelőzése érdekében nem változnak.", - "image_preview_description": "Közepes méretű kép eltávolított metaadatokkal, egy képes nézethez és a gépi tanuláshoz", - "image_preview_quality_description": "Előnézet minősége 1-100 között. A magasabb szám jobb minőséget, de nagyobb fájlokat eredményez és belassíthatja az alkalmazást. Túl alacsony érték befolyásolhatja a gépi tanulás pontosságát.", - "image_preview_title": "Előnézet beállítások", - "image_progressive": "Progresszív", - "image_progressive_description": "JPEG képek progresszív kódolása a fokozatos megjelenítéshez betöltéskor. Nincs hatással a WebP képekre.", - "image_quality": "Minőség", - "image_resolution": "Felbontás", - "image_resolution_description": "A nagyobb felbontás több részletet őriz meg, de lassabb létrehozni, nagyobb fájlt eredményez és belassíthatja az alkalmazást.", - "image_settings": "Képbeállítások", - "image_settings_description": "A létrehozott képek minőségi és felbontási beállításainak kezelése", - "image_thumbnail_description": "Kicsi bélyegkép eltávolított metaadatokkal, sok kis kép (pl idővonal) megjelenítéséhez", - "image_thumbnail_quality_description": "Bélyegkép minősége 1-100 között. A magasabb szám jobb minőséget, de nagyobb fájlméretet eredményez és belassíthatja az alkalmazást.", - "image_thumbnail_title": "Bélyegkép beállítások", - "import_config_from_json_description": "Rendszer konfiguráció importálása JSON fájlból", - "job_concurrency": "{job} párhuzamosan", - "job_created": "Feladat létrehozva", - "job_not_concurrency_safe": "Ez a feladat nem párhuzamosság-biztos.", - "job_settings": "Feladat beállítások", - "job_settings_description": "Párhuzamosan futó feladatok kezelése", - "jobs_delayed": "{jobCount, plural, other {# késik}}", - "jobs_failed": "{jobCount, plural, other {# sikertelen}}", - "jobs_over_time": "Feladat aktivitás", - "library_created": "Képtár létrehozva: {library}", - "library_deleted": "Képtár törölve", - "library_details": "Könyvtár részletei", - "library_folder_description": "Adja meg az importálandó mappát. Ez a mappa, az almappákkal együtt, át lesz vizsgálva képek és videók után.", - "library_remove_exclusion_pattern_prompt": "Biztosan törölni szeretné ezt a kizárási mintát?", - "library_remove_folder_prompt": "Biztosan törölni szeretné ezt az import mappát?", - "library_scanning": "Időszakos Átfésülés", - "library_scanning_description": "A képtár időszakos átfésülésének beállítása", - "library_scanning_enable_description": "Képtár időszakos átfésülésének engedélyezése", - "library_settings": "Külső képtár", - "library_settings_description": "Külső képtár beállításainak kezelése", - "library_tasks_description": "Külső könyvtárak szkennelése új és/vagy módosított elemek után", - "library_updated": "Könyvtár frissítve", - "library_watching_enable_description": "Külső képtár változásainak figyelése", - "library_watching_settings": "Könyvtár figyelése [KÍSÉRLETI]", - "library_watching_settings_description": "Megváltozott fájlok automatikus észlelése", - "logging_enable_description": "Naplózás engedélyezése", - "logging_level_description": "Ha be van kapcsolva, milyen részletességű legyen a naplózás.", - "logging_settings": "Naplózás", - "machine_learning_availability_checks": "Elérhetőség ellenőrzése", - "machine_learning_availability_checks_description": "Automatikusan keressen és válasszon elérhető gépi tanulás szervereket", - "machine_learning_availability_checks_enabled": "Elérhetőség ellenőrzésének bekapcsolása", - "machine_learning_availability_checks_interval": "Ellenőrzési intervallum", - "machine_learning_availability_checks_interval_description": "Elérhetőség-ellenőrzések közötti késleltetés milliszekundumban", - "machine_learning_availability_checks_timeout": "Kérések időkorlátja", - "machine_learning_availability_checks_timeout_description": "Elérhetőség-ellenőrzések időkorlátja milliszekundumban", - "machine_learning_clip_model": "CLIP modell", - "machine_learning_clip_model_description": "Egy CLIP modell neve az itt felsoroltak közül. A modell megváltoztatása után újra kell futtatni az 'Okos keresés' feladatot minden képre.", - "machine_learning_duplicate_detection": "Duplikációk keresése", - "machine_learning_duplicate_detection_enabled": "Duplikációk keresésének engedélyezése", - "machine_learning_duplicate_detection_enabled_description": "Ha ki van kapcsolva, a pontosan azonos elemek akkor sem lesznek duplikálva.", - "machine_learning_duplicate_detection_setting_description": "CLIP beágyazások használata a valószínű másolatok kereséséhez", - "machine_learning_enabled": "Gépi tanulás engedélyezése", - "machine_learning_enabled_description": "Ha ki van kapcsolva, a gépi tanulási képességek az alábbi beállításoktól függetlenül ki lesznek kapcsolva.", - "machine_learning_facial_recognition": "Arcfelismerés", - "machine_learning_facial_recognition_description": "A képekben szereplő arcok megkeresése, felismerése és csoportosítása", - "machine_learning_facial_recognition_model": "Arcfelismerési modell", - "machine_learning_facial_recognition_model_description": "A modellek méret szerint csökkenő sorrendben vannak felsorolva. A nagyobb modellek lassabbak és több memóriát használnak, de jobb eredményt produkálnak. Modellváltás után az összes képen futtasd újra az Arckeresés feladatot.", - "machine_learning_facial_recognition_setting": "Arckeresés engedélyezése", - "machine_learning_facial_recognition_setting_description": "Ha ki van kapcsolva, a képek nem lesznek az arcfelismerésen lefuttatva és a Böngészés oldalon az Személyek szekcióban nem fog szerepelni senki.", - "machine_learning_max_detection_distance": "Maximum keresési távolság", - "machine_learning_max_detection_distance_description": "Két kép közötti maximális távolság, amely esetében még duplikációnak tekintendők (0.001 és 0.1 közötti érték). Minél magasabb az érték, annál több lesz a megtalált duplikáció, de a hamis találatok esélye is egyre nagyobb.", - "machine_learning_max_recognition_distance": "Maximum felismerési távolság", - "machine_learning_max_recognition_distance_description": "Két arc közötti maximális távolság, amely alapján ugyanazon személynek tekinthetők, 0 és 2 között. Ennek csökkentése megakadályozhatja, hogy két különböző személyt ugyanannak a személynek jelöljünk, míg a növelése megakadályozhatja, hogy ugyanazt a személyt két különböző személyként jelöljük. Vedd figyelembe, hogy könnyebb két személyt összevonni, mint egy személyt kettéválasztani, ezért lehetőség szerint inkább alacsonyabb küszöbértéket válassz.", - "machine_learning_min_detection_score": "Minimális észlelési érték", - "machine_learning_min_detection_score_description": "Az arcok észleléséhez szükséges minimális megbízhatósági pontszám 0 és 1 között. Minél alacsonyabb az érték, annál több lesz a megtalált arc, de a hamis találatok esélye is egyre nagyobb.", - "machine_learning_min_recognized_faces": "Minimum felismert arc", - "machine_learning_min_recognized_faces_description": "Egy személy létrehozásához szükséges minimálisan felismert arcok száma. Ennek növelésével a arcfelismerés pontosabbá válik, azonban növeli annak az esélyét, hogy egy arc nem rendelődik hozzá egy személyhez.", - "machine_learning_ocr": "OCR (Optikai karakterfelismerés)", - "machine_learning_ocr_description": "Gépi tanulás használata a képeken megjelenő szövegek felismerésére", - "machine_learning_ocr_enabled": "OCR engedélyezése", - "machine_learning_ocr_enabled_description": "Kikapcsolt állapotban a képeken nem történik szövegfelismerés.", - "machine_learning_ocr_max_resolution": "Maximális felbontás", - "machine_learning_ocr_max_resolution_description": "Az ennél nagyobb felbontású előnézetek átméretezésre kerülnek a képarány megtartásával. A magasabb értékeknél az előnézetek pontosabbak, de ez hosszabb feldolgozási időt és több memóriát igényel.", - "machine_learning_ocr_min_detection_score": "Minimális észlelési érték", - "machine_learning_ocr_min_detection_score_description": "A szövegfelismerés minimális bizalmi szintje 0 és 1 között. Az alacsonyabb érték több szöveget észlelhet, de növeli a téves találatok esélyét.", - "machine_learning_ocr_min_recognition_score": "Minimális felismerési érték", - "machine_learning_ocr_min_score_recognition_description": "A szövegfelismerés minimális bizalmi szintje 0 és 1 között. Az alacsonyabb értékek több szöveget ismerhetnek fel, de növelhetik a téves találatok számát.", - "machine_learning_ocr_model": "Szövegfelismerő modell (OCR)", - "machine_learning_ocr_model_description": "A szervermodellek pontosabbak, mint a mobilmodellek, de hosszabb feldolgozási időt és több memóriát igényelnek.", - "machine_learning_settings": "Gépi tanulás beállítások", - "machine_learning_settings_description": "Gépi tanulási funkciók és beállítások kezelése", - "machine_learning_smart_search": "Okos keresés", - "machine_learning_smart_search_description": "Képek szemantikai keresése CLIP beágyazások segítségével", - "machine_learning_smart_search_enabled": "Okos keresés engedélyezése", - "machine_learning_smart_search_enabled_description": "Ha ki van kapcsolva, a képek nem lesznek átalakítva Okos kereséshez.", - "machine_learning_url_description": "Gépi tanulás szerver URL címe. Ha többi, mint egy URL van megadva, mindegyik szervert egyenként próbálja meg, amíg az egyik sikeresen nem válaszol, sorrendben az elsőtől az utólsóig. A nem elérhető szervereket átmenetileg figyelmen kívül lesznek hagyva, amíg újra online nem lesznek.", - "maintenance_delete_backup": "Biztonsági mentés törlése", - "maintenance_delete_backup_description": "A fájl törlése nem visszafordítható.", - "maintenance_delete_error": "A biztonsági mentés törlése sikertelen volt.", - "maintenance_restore_backup": "Biztonsági mentés visszaállítása", - "maintenance_restore_backup_description": "Az Immich adatai törölve lesznek és a kiválasztott biztonsági mentés kerül visszaállításra. Egy biztonsági mentés készül, mielőtt folytatnád.", - "maintenance_restore_backup_different_version": "Ez a biztonsági mentés az Immich egy másik verziójával készült!", - "maintenance_restore_backup_unknown_version": "A biztonsági mentés verziójának meghatározása sikertelen.", - "maintenance_restore_database_backup": "Adatbázis visszaállítása biztonsági mentésből", - "maintenance_restore_database_backup_description": "Visszaállítás egy korábbi adatbázis állapotba egy biztonsági mentés fájl segítségével", - "maintenance_settings": "Karbantartás", - "maintenance_settings_description": "Az Immich karbantartási módjának beállítása.", - "maintenance_start": "Karbantartási mód bekapcsolása", - "maintenance_start_error": "Hiba történt a karbantartási mód bekapcsolás közben.", - "maintenance_upload_backup": "Adatbázis biztonsági mentés fájl feltöltése", - "maintenance_upload_backup_error": "A biztonsági mentés nem tölthető fel. Biztos, hogy .sql/.sql.gz a fájlkiterjesztés?", - "manage_concurrency": "Feladatok párhuzamosságának kezelése", - "manage_concurrency_description": "Navigálás a feladatok oldalra az egyidejű munkavégzés kezeléséhez", - "manage_log_settings": "Naplózás beállítások kezelése", - "map_dark_style": "Sötét stílus", - "map_enable_description": "Térkép funkciók engedélyezése", - "map_gps_settings": "Térkép és GPS beállítások", - "map_gps_settings_description": "A térkép és GPS (fordított geokódolás) beállításainak kezelése", - "map_implications": "A térkép szolgáltatás egy külső csempeszolgáltatót használ (tiles.immich.cloud)", - "map_light_style": "Világos stílus", - "map_manage_reverse_geocoding_settings": "A fordított geokódolás beállításainak kezelése", - "map_reverse_geocoding": "Fordított geokódolás", - "map_reverse_geocoding_enable_description": "Fordított geokódolás engedélyezése", - "map_reverse_geocoding_settings": "Fordított geokódolás beállítások", - "map_settings": "Térkép", - "map_settings_description": "Térkép beállítások kezelése", - "map_style_description": "Egy style.json térképtémára mutató URL cím", - "memory_cleanup_job": "Memória takarítás", - "memory_generate_job": "Emlékek generálása", - "metadata_extraction_job": "Metaadatok kinyerése", - "metadata_extraction_job_description": "Metaadat információk (pl. GPS, arcok és felbontás) kinyerése minden elemből", - "metadata_faces_import_setting": "Arc importálás engedélyezése", - "metadata_faces_import_setting_description": "Arcok importálása a kép EXIF adataiból és segédfájlokból", - "metadata_settings": "Metaadat beállítások", - "metadata_settings_description": "Metaadat beállítások kezelése", - "migration_job": "Migrálás", - "migration_job_description": "Az elemek és arcok bélyegképeinek migrálása a legújabb mappastruktúrába", - "nightly_tasks_cluster_faces_setting_description": "Arcfelismerés futtatása az újonnan érzékelt arcokon", - "nightly_tasks_cluster_new_faces_setting": "Új arcok csoportosítása", - "nightly_tasks_database_cleanup_setting": "Adatbázis-tisztítási feladatok", - "nightly_tasks_database_cleanup_setting_description": "A régi, lejárt adatok törlése az adatbázisból", - "nightly_tasks_generate_memories_setting": "Emlékek generálása", - "nightly_tasks_generate_memories_setting_description": "Új emlékek létrehozása elemekből", - "nightly_tasks_missing_thumbnails_setting": "Hiányzó indexképek generálása", - "nightly_tasks_missing_thumbnails_setting_description": "A bélyegkép nélküli elemek bélyegképgeneráló várólistára helyezése", - "nightly_tasks_settings": "Éjjeli feladat beállítások", - "nightly_tasks_settings_description": "Éjjeli feladatok kezelése", - "nightly_tasks_start_time_setting": "Kezdőidő", - "nightly_tasks_start_time_setting_description": "Az az időpont, amikor a szerver elkezdi futtatni az éjszakai feladatokat", - "nightly_tasks_sync_quota_usage_setting": "Kvóta használat szinkronizálása", - "nightly_tasks_sync_quota_usage_setting_description": "A felhasználó kvótájának frissítése az aktuális tárhelyhasználat alapján", - "no_paths_added": "Nincs megadva elérési útvonal", - "no_pattern_added": "Nincs megadva minta (pattern)", - "note_apply_storage_label_previous_assets": "Megjegyzés: Ha a korábban feltöltött elemekhez is szeretne tárhely címkéket társítani, akkor futtassa ezt", - "note_cannot_be_changed_later": "FIGYELEM: ezt később nem lehet megváltoztatni!", - "notification_email_from_address": "Feladó cím", - "notification_email_from_address_description": "Küldő email címe, például: \"Immich Fotószerver \". Figyelj hogy olyan címet adj meg ahonnan az email küldés engedélyezett.", - "notification_email_host_description": "Email szerver kiszolgálója (pl. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Tanúsítvány hibák figyelmen kívül hagyása", - "notification_email_ignore_certificate_errors_description": "TLS tanúsítvány érvényességi hibák figyelmen kívül hagyása (nem ajánlott)", - "notification_email_password_description": "Az email szerverrel való hitelesítéshez használt jelszó", - "notification_email_port_description": "Email szerver portja (pl. 25, 465 vagy 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "SMTPS használata (SMTP TLS-en keresztül)", - "notification_email_sent_test_email_button": "Teszt email küldése és mentés", - "notification_email_setting_description": "Email értesítés küldés beállításai", - "notification_email_test_email": "Teszt email küldése", - "notification_email_test_email_failed": "Nem sikerült elküldeni a teszt emailt, ellenőrizd a beállításokat", - "notification_email_test_email_sent": "Egy teszt emailt küldtünk a(z) {email} címre. Figyeld a beérkező üzeneteidet.", - "notification_email_username_description": "Az email szerverrel való hitelesítéshez használt felhasználónév", - "notification_enable_email_notifications": "Email értesítések engedélyezése", - "notification_settings": "Értesítés beállítások", - "notification_settings_description": "Értesítési és email beállítások kezelése", - "oauth_auto_launch": "Automatikus indítás", - "oauth_auto_launch_description": "Az OAuth bejelentkezési folyamat automatikus indítása a bejelentkezési oldal megnyitásakor", - "oauth_auto_register": "Automatikus regisztráció", - "oauth_auto_register_description": "Új felhasználók automatikus regisztrálása az OAuth használatával történő bejelentkezés után", - "oauth_button_text": "Gomb szövege", - "oauth_client_secret_description": "Bizalmas kliens esetén kötelező, vagy ha az OAuth szolgáltató nem támogatja a PKCE-t (Proof Key for Code Exchange) nyilvános kliensnél.", - "oauth_enable_description": "Bejelentkezés OAuth használatával", - "oauth_mobile_redirect_uri": "Mobil átirányítási URI", - "oauth_mobile_redirect_uri_override": "Mobil átirányítási URI felülírás", - "oauth_mobile_redirect_uri_override_description": "Engedélyezd, ha az OAuth szolgáltató tiltja a mobil URI-t, mint például ''{callback}''", - "oauth_role_claim": "Szerepkör kérelem", - "oauth_role_claim_description": "Automatikusan adjon rendszergazdai hozzáférést ennek az igénylésnek a jelenléte alapján. A kérelem lehet „felhasználó” vagy „rendszergazda”.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth bejelentkezési beállítások kezelése", - "oauth_settings_more_details": "Erről a funkcióról további információt a dokumentációban találsz.", - "oauth_storage_label_claim": "Tárhely címke igénylés", - "oauth_storage_label_claim_description": "A felhasználó tárhely címkéjének automatikus beállítása az igényeltre.", - "oauth_storage_quota_claim": "Tárhelykvóta igénylése", - "oauth_storage_quota_claim_description": "A felhasználó tárhelykvótájának automatikus beállítása ennek az igényeltre.", - "oauth_storage_quota_default": "Alapértelmezett tárhelykvóta (GiB)", - "oauth_storage_quota_default_description": "Alapértelmezett tárhely kvóta GiB-ban, amennyiben a felhasználó nem jelezte az igényét.", - "oauth_timeout": "Kérés időkorlátja", - "oauth_timeout_description": "Kérések időkorlátja milliszekundumban", - "ocr_job_description": "Gépi tanulás használata a képeken lévő szövegek felismerésére", - "password_enable_description": "Bejelentkezés emaillel és jelszóval", - "password_settings": "Jelszavas bejelentkezés", - "password_settings_description": "Jelszavas bejelentkezés beállítások kezelése", - "paths_validated_successfully": "Összes útvonal sikeresen érvényesítve", - "person_cleanup_job": "Személyek kipucolása", - "queue_details": "Sor részletei", - "queues": "Feladatsor", - "queues_page_description": "Admin feladatsor oldala", - "quota_size_gib": "Kvóta mérete (GiB)", - "refreshing_all_libraries": "Összes képtár frissítése", - "registration": "Admin regisztráció", - "registration_description": "Mivel ez az első felhasználó a rendszerben, ezért te leszel az Admin, aki az adminisztratív teendőkért felelős és további felhasználókat tud létrehozni.", - "remove_failed_jobs": "Sikertelen feladatok eltávolítása", - "require_password_change_on_login": "Kötelező jelszómódosítás az első bejelentkezéskor", - "reset_settings_to_default": "Beállítások visszaállítása az alapértelmezettre", - "reset_settings_to_recent_saved": "Beállítások visszaállítása a legutóbb mentettre", - "scanning_library": "Képtár átfésülése", - "search_jobs": "Feladatok keresése…", - "send_welcome_email": "Üdvözlő email küldése", - "server_external_domain_settings": "Külső domain", - "server_external_domain_settings_description": "Nyilvánosan megosztott linkek domainje (http(s)://-sel)", - "server_public_users": "Nyilvános felhasználók", - "server_public_users_description": "Az összes felhasználó (név és email) ki van írva, amikor egy felhasználót adsz hozzá egy megosztott albumhoz. Amikor le van tiltva, a felhasználólista csak adminok számára lesz elérhető.", - "server_settings": "Szerver beállítások", - "server_settings_description": "Szerver beállítások kezelése", - "server_stats_page_description": "Admin szerver statisztikai oldala", - "server_welcome_message": "Üdvözlő üzenet", - "server_welcome_message_description": "A bejelentkezőoldalon megjelenő üzenet.", - "settings_page_description": "Admin beállítások oldala", - "sidecar_job": "Segédfájl metaadatok", - "sidecar_job_description": "Metaadatok keresése vagy szinkronizálása a fájlrendszeren lévő segédfájlokból", - "slideshow_duration_description": "Az egyes képek megjelenítésének időtartama másodpercben", - "smart_search_job_description": "Gépi tanulás futtatása az elemeken, ami az Okos kereséshez szükséges", - "storage_template_date_time_description": "Az elem készítési időpontja lesz felhasználva az időpont információhoz", - "storage_template_date_time_sample": "Példa időpont {date}", - "storage_template_enable_description": "Tárhely sablon motor engedélyezése", - "storage_template_hash_verification_enabled": "Hash ellenőrzés engedélyezve", - "storage_template_hash_verification_enabled_description": "Engedélyezi a hash-érték ellenőrzést - csak akkor kapcsold ki, ha tisztában vagy a következményekkel", - "storage_template_migration": "Tárhely sablon migrálása", - "storage_template_migration_description": "A jelenlegi {template} alkalmazása a már feltöltött elemekre", - "storage_template_migration_info": "A sablon az összes kiterjesztést kisbetűssé alakítja át. A megváltozott sablon csak az újonnan feltöltött elemekre vonatkozik. A korábbi elemek visszamenőleges áthelyezéséhez ezt futtasd: {job}.", - "storage_template_migration_job": "Tárhely sablon migrálása", - "storage_template_more_details": "További részletekért erről a funkcióról lásd a tárhely sablon és annak következményeit a dokumentációban", - "storage_template_onboarding_description_v2": "A funkció engedélyezésével automatikusan, a felhasználó által definiált sablon alapján lesznek rendezve a fájlok. Több információért lásd a dokumentációt.", - "storage_template_path_length": "Útvonal hozzávetőleges maximális hossza: {length, number}{limit, number}", - "storage_template_settings": "Tárhely sablon", - "storage_template_settings_description": "A feltöltött elemek mappaszerkezetének és fájl elnevezésének kezelése", - "storage_template_user_label": "A felhasználó tárhely címkéje {label}", - "system_settings": "Rendszerbeállítások", - "tag_cleanup_job": "Címkék kipucolása", - "template_email_available_tags": "Használthatod a következő változókat a sablonodban: {tags}", - "template_email_if_empty": "Ha a sablon üres, akkor az alapértelmezett email lesz használva.", - "template_email_invite_album": "Album meghívás sablon", - "template_email_preview": "Előnézet", - "template_email_settings": "Email sablonok", - "template_email_update_album": "Album frissítve sablon", - "template_email_welcome": "Üdvözlő email sablon", - "template_settings": "Értesítés sablonok", - "template_settings_description": "Egyéni sablonok kezelése az értesítésekhez", - "theme_custom_css_settings": "Egyéni CSS", - "theme_custom_css_settings_description": "Cascading Style Sheet stíluslapokkal az Immich stílusa megváltoztatható.", - "theme_settings": "Téma beállítások", - "theme_settings_description": "Az Immich webes felületének testreszabása", - "thumbnail_generation_job": "Bélyegképek generálása", - "thumbnail_generation_job_description": "Nagy, kicsi és elmosódott bélyegképek létrehozása minden elemhez, valamint bélyegképek generálása minden személyhez", - "transcoding_acceleration_api": "Gyorsító API", - "transcoding_acceleration_api_description": "Az átkódolás felgyorsításához használt eszközödhöz tartozó API. Ez a beállítás „legtöbb, amit megtehetünk” alapon működik: probléma esetén visszaáll szoftveres átkódolásra. A VP9 a hardvertől függően vagy működik, vagy nem.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU-t igényel)", - "transcoding_acceleration_qsv": "Quick Sync (7. generációs vagy újabb Intel CPU-t igényel)", - "transcoding_acceleration_rkmpp": "RKMPP (csak Rockchip SOC-on)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Elfogadott audio kodekek", - "transcoding_accepted_audio_codecs_description": "Válaszd ki, hogy melyik audio kodekeket nem kell átkódolni. Csak bizonyos átkódolási szabályzatokhoz használt.", - "transcoding_accepted_containers": "Elfogadott tárolók", - "transcoding_accepted_containers_description": "Válaszd ki, hogy melyik tároló formátumokat nem szükséges átkódolni MP4 formátumba. Csak bizonyos átkódolási szabályzatokhoz használt.", - "transcoding_accepted_video_codecs": "Elfogadott videó kodekek", - "transcoding_accepted_video_codecs_description": "Válaszd ki, hogy mely videó kodekeket nem kell átkódolni. Csak bizonyos átkódolási szabályzatokhoz használt.", - "transcoding_advanced_options_description": "Ezeket az opciókat a legtöbb felhasználónak nem kell módosítania", - "transcoding_audio_codec": "Audio kodek", - "transcoding_audio_codec_description": "Az Opus a legjobb minőségű opció (jobb hangminőség ugyanakkora tárhelyen), de kevésbé kompatibilis a régi eszközökkel vagy szoftverekkel.", - "transcoding_bitrate_description": "A maximum bitrátát meghaladó vagy nem megfelelő formátumú videókat", - "transcoding_codecs_learn_more": "Hogy többet tudj meg az itt felhasznált kifejezésekről, nézd meg az FFmpeg dokumentációt a H.264 kodekről, a HEVC kodekről és a VP9 kodekről.", - "transcoding_constant_quality_mode": "Állandó minőségű mód", - "transcoding_constant_quality_mode_description": "Az ICQ jobb, mint a CQP, viszont az előbbit nem minden hardver támogatja. A rendszer az itt beállított módot preferálja a minőség orientált kódoláshoz. Az NVENC nem használja ezt a beállítást, mivel nem támogatja az ICQ-t.", - "transcoding_constant_rate_factor": "Állandó ráta tényező (-crf)", - "transcoding_constant_rate_factor_description": "Videó minőségi szint. Tipikus értékek kodekenként: H.264: 23, HEVC: 28, VP9: 31, AV1: 35. Minél alacsonyabb, annál jobb minőséget eredményez, viszont nagyobb fájlmérettel is jár.", - "transcoding_disabled_description": "Ne kódolja át a videókat. Néhány kliensnél nem lejátszható videókhoz vezethet", - "transcoding_encoding_options": "Enkódolás beállítások", - "transcoding_encoding_options_description": "Beállíthatod az enkódolt videók kódolási algoritmusát, felbontását, minőségét és egyéb beállításait", - "transcoding_hardware_acceleration": "Hardveres gyorsítás", - "transcoding_hardware_acceleration_description": "Kísérleti funkció: gyorsabb transzkódolás, viszont azonos bitrátán alacsonyabb minőséghez vezethet", - "transcoding_hardware_decoding": "Hardveres dekódolás", - "transcoding_hardware_decoding_setting_description": "Lehetővé teszi az egész folyamat gyorsítását a pusztán kódolás gyorsítása helyett. Nem biztos, hogy minden videó esetén működik.", - "transcoding_max_b_frames": "B-képkockák maximum száma", - "transcoding_max_b_frames_description": "Nagyobb értékek megnövelik a tömörítés hatékonyságát, de lelassítják a kódolást. Nem minden hardvereszköz támogatja. A 0 érték kikapcsolja a B-képkockákat, míg -1 esetén a szoftver magának választ értéket.", - "transcoding_max_bitrate": "Maximum bitráta", - "transcoding_max_bitrate_description": "Maximum bitráta beállítása konzisztensebb fájlméretet eredményez egy kevés minőségi romlás árán. 720p esetén jellemző érték lehet 2600 kbit/s a VP9 vagy HEVC kódoláshoz, 4500 kbit/s a H.264 kódoláshoz. A 0 érték esetén nincs maximum bitráta. Ha nincs megadva mértékegység, alapértelmezetten „k” (kbit/s) értendő - tehát az 5000, 5000k és az 5M (Mbit/s) azonos beállítások.", - "transcoding_max_keyframe_interval": "Maximum kulcskocka intervallum", - "transcoding_max_keyframe_interval_description": "Beállítja a kulcskockák közötti legnagyobb lehetséges távolságot. Alacsony érték csökkenti a tömörítési hatékonyságot, de lejátszás közben az előre- és hátratekerés gyorsabb, valamint javíthatja a gyorsan mozgó jelenetek képminőségét. 0 esetén a szoftver magának állítja be az értéket.", - "transcoding_optimal_description": "A célfelbontásnál nagyobb vagy a nem elfogadott formátumú videókat", - "transcoding_policy": "Átkódolási irányelvek", - "transcoding_policy_description": "Beállíthatod, hogy egy videó mikor legyen átkódolva", - "transcoding_preferred_hardware_device": "Átkódoláshoz preferált hardver eszköz", - "transcoding_preferred_hardware_device_description": "Csak VAAPI vagy QSV esetén. Beállítja a hardveres átkódoláshoz használt DRI node-ot.", - "transcoding_preset_preset": "Előre beállított (-preset)", - "transcoding_preset_preset_description": "Tömörítési sebesség. A lassabb beállítások kisebb fájlokat hoznak létre és növelik a minőséget az adott bitráta mellett. A VP9 kódolás figyelmen kívül hagyja a 'gyorsabb (faster)'-nél nagyobb sebességeket.", - "transcoding_reference_frames": "Referencia képkockák", - "transcoding_reference_frames_description": "A hivatkozott képkockák száma egy képkocka tömörítéséhez. Magasabb értékek növelik a tömörítési hatékonyságot, de lelassítják a kódolási folyamatot. 0 esetén a szoftver magának állítja be az értéket.", - "transcoding_required_description": "Csak az el nem fogadott formátumú videókat", - "transcoding_settings": "Videó átkódolás beállítások", - "transcoding_settings_description": "Beállíthatod, hogy mely videókat kell átkódolni és hogyan kell feldolgozni őket", - "transcoding_target_resolution": "Célfelbontás", - "transcoding_target_resolution_description": "A magasabb felbontás jobb minőségben őrzi meg a részleteket, de tovább tart létrehozni, nagyobb fájlmérethez vezet és belassíthatja az alkalmazást.", - "transcoding_temporal_aq": "Időbeli (Temporal) AQ", - "transcoding_temporal_aq_description": "Csak NVENC esetén. Az Időbeli Adaptív Kvantálás növeli a nagyon részletes, keveset mozgó videóanyag minőségét. Nem minden régi eszköz támogatja.", - "transcoding_threads": "Folyamatok száma", - "transcoding_threads_description": "Magas értékek esetén gyorsabban kódol, viszont kevesebb erőforrást hagy a szerver többi folyamatának. Nem ajánlott a CPU magjainak számánál nagyobb érték beállítása. A 0 érték maximalizálja a processzor kihasználását.", - "transcoding_tone_mapping": "Tónusleképezés (tone-mapping)", - "transcoding_tone_mapping_description": "Megpróbálja megőrizni a HDR videók kinézetét SDR-re való konvertálás során. Minden algoritmus különböző módon tesz kompromisszumot a színek, részletek, és a fényerő megőrzésében. A Hable inkább a részleteket őrzi meg, a Mobius a színeket, a Reinhard pedig a fényerőt.", - "transcoding_transcode_policy": "Átkódolási szabályzat", - "transcoding_transcode_policy_description": "Videó átkódolási szabályzat . HDR videók mindig átkódolásra kerülnek (kivéve, ha az átkódolás ki van kapcsolva).", - "transcoding_two_pass_encoding": "Átkódolás két menetben", - "transcoding_two_pass_encoding_setting_description": "A két menetben átkódolt videók jobb minőségűek lesznek. Ha engedélyezve van a bitráta maximalizálása (amely szükséges a H.264 és a HEVC használatakor), ez a funkció figyelmen kívül hagyja a CRF-et. VP9 használata esetén a CRF használható, ha a bitráta nincs maximalizálva (azaz ki van kapcsolva).", - "transcoding_video_codec": "Videó kodek", - "transcoding_video_codec_description": "VP9 hatékonyabb és kompatibilisebb webre, de tovább tart az átkódolás. HEVC hasonló teljesítményű, de több web kompatibilitási problémát okozhat. H.264 széles körben kompatibilis és gyors az átkódolása, de sokkal nagyobb fájlokat készít. AV1 a leghatékonyabb kodek, de régebbi eszközök nem támogatják.", - "trash_enabled_description": "Lomtár engedélyezése", - "trash_number_of_days": "Napok száma", - "trash_number_of_days_description": "Hány napig legyenek a lomtárban az elemek a végleges törlés előtt", - "trash_settings": "Lomtár beállítások", - "trash_settings_description": "Lomtár beállítások kezelése", - "unlink_all_oauth_accounts": "Összes OAuth-fiók szétkapcsolása", - "unlink_all_oauth_accounts_description": "Ne felejtsd el, hogy az új szolgáltatóra való áttérés előtt minden OAuth-fiók kapcsolatot meg kell szüntetned.", - "unlink_all_oauth_accounts_prompt": "Biztosan meg szeretnéd szüntetni az összes OAuth-fiók összekapcsolását? Ez minden felhasználó OAuth-azonosítóját visszaállítja, és nem vonható vissza.", - "user_cleanup_job": "Felhasználók kipucolása", - "user_delete_delay": "{user} felhasználói fiókja és elemei véglegesen törölve lesznek {delay, plural, one {# nap} other {# nap}} múlva.", - "user_delete_delay_settings": "Törlési késleltetés", - "user_delete_delay_settings_description": "Hány nappal az eltávolítás után legyen véglegesen törölve a felhasználó fiókja és tárolt elemei. A végleges törlés feladat minden éjfélkor fut le, hogy ellenőrizze, hogy van-e törlendő felhasználó. Ez a beállítás a következő futtatás során lép életbe.", - "user_delete_immediately": "{user} felhasználója és összes eleme azonnal sorba állításra kerül a végleges törléshez .", - "user_delete_immediately_checkbox": "Felhasználó és tárolt elemeinek sorba állítása azonnali törlésre", - "user_details": "Felhasználói adatok", - "user_management": "Felhasználók", - "user_password_has_been_reset": "A felhasználó jelszava megváltoztatásra került:", - "user_password_reset_description": "Juttasd el az átmeneti jelszót a felhasználóhoz és tájékoztasd, hogy a következő belépésnél azt majd meg kell változtatnia.", - "user_restore_description": "{user} felhasználója vissza lesz állítva.", - "user_restore_scheduled_removal": "Felhasználó visszaállítása - törlésre jelölve: {date, date, long}", - "user_settings": "Felhasználó beállítások", - "user_settings_description": "Felhasználó beállítások kezelése", - "user_successfully_removed": "{email} felhasználó sikeresen eltávolítva.", - "users_page_description": "Admin felhasználók oldala", - "version_check_enabled_description": "Új verziók elérhetőségének ellenőrzése", - "version_check_implications": "Az új verziók ellenőrzése időszakos kommunikációt igényel a github.com oldallal", - "version_check_settings": "Verzió ellenőrzés", - "version_check_settings_description": "Az új verzióról való értesítés be- és kikapcsolása", - "video_conversion_job": "Videók Átkódolása", - "video_conversion_job_description": "Videók átkódolása böngészőkkel és eszközökkel való széleskörű kompatibilitás érdekében" - }, - "admin_email": "Admin e-mail", - "admin_password": "Admin jelszó", - "administration": "Adminisztráció", - "advanced": "Haladó", - "advanced_settings_clear_image_cache": "Fényképek gyorsítótárának kiürítése", - "advanced_settings_clear_image_cache_error": "Fényképek gyorsítótárának kiürítése sikertelen", - "advanced_settings_clear_image_cache_success": "{size} sikeresen felszabadítva", - "advanced_settings_enable_alternate_media_filter_subtitle": "Ezzel a beállítással a szinkronizálás során alternatív kritériumok alapján szűrheted a fájlokat. Csak akkor próbáld ki, ha problémáid vannak azzal, hogy az alkalmazás nem ismeri fel az összes albumot.", - "advanced_settings_enable_alternate_media_filter_title": "[KÍSÉRLETI] Alternatív eszköz album szinkronizálási szűrő használata", - "advanced_settings_log_level_title": "Naplózás szintje: {level}", - "advanced_settings_prefer_remote_subtitle": "Néhány eszköz fájdalmasan lassan tölti be az eszközön lévő indexképeket. Ez a beállítás inkább a távoli képeket (a szerverről) tölti be helyettük.", - "advanced_settings_prefer_remote_title": "Távoli képek előnyben részesítése", - "advanced_settings_proxy_headers_subtitle": "Add meg azokat a proxy fejléceket, amiket az app elküldjön minden hálózati kérésnél", - "advanced_settings_proxy_headers_title": "Egyedi proxy fejlécek [KÍSÉRLETI]", - "advanced_settings_readonly_mode_subtitle": "Bekapcsol egy írásvédett módot ahol csak fotókat nézni lehetséges, egyebek, mint több kép kiválasztása, megosztás, kivetítés és törlés ki vannak kapcsolva. Ki/bekapcsolható a felhasználó ikonjáról a fő képernyőn", - "advanced_settings_readonly_mode_title": "Írásvédett mód", - "advanced_settings_self_signed_ssl_subtitle": "Nem ellenőrzi a szerver SSL tanúsítványát. Önaláírt tanúsítvány esetén szükséges beállítás.", - "advanced_settings_self_signed_ssl_title": "Önaláírt SSL tanúsítványok engedélyezése [KÍSÉRLETI]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatikusan törölni vagy visszaállítani egy elemet ezen az eszközön, ha az adott műveletet a weben hajtották végre", - "advanced_settings_sync_remote_deletions_title": "Távoli törlések szinkronizálása [KÍSÉRLETI]", - "advanced_settings_tile_subtitle": "Haladó felhasználói beállítások", - "advanced_settings_troubleshooting_subtitle": "További funkciók engedélyezése hibaelhárítás céljából", - "advanced_settings_troubleshooting_title": "Hibaelhárítás", - "age_months": "Kor {months, plural, one {# hónap} other {# hónap}}", - "age_year_months": "Kor 1 év, {months, plural, one {# hónap} other {# hónap}}", - "age_years": "{years, plural, other {# év}}", - "album": "Album", - "album_added": "Album hozzáadva", - "album_added_notification_setting_description": "Email értesítőt kapsz, amikor hozzáadnak egy megosztott albumhoz", - "album_cover_updated": "Album borító frissítve", - "album_delete_confirmation": "Biztos, hogy ki szeretnéd törölni a(z) {album} albumot?", - "album_delete_confirmation_description": "Amennyiben ez egy megosztott album, a többi felhasználó sem fog tudni többé hozzáférni.", - "album_deleted": "Album törölve", - "album_info_card_backup_album_excluded": "KIHAGYVA", - "album_info_card_backup_album_included": "BELEÉRTVE", - "album_info_updated": "Album infó frissítve", - "album_leave": "Kilépsz az albumból?", - "album_leave_confirmation": "Biztos, hogy ki szeretnél lépni a(z) {album} albumból?", - "album_name": "Album név", - "album_options": "Album beállítások", - "album_remove_user": "Felhasználó törlése?", - "album_remove_user_confirmation": "Biztos, hogy el szeretnéd távolítani {user} felhasználót?", - "album_search_not_found": "Nem található a keresésnek megfelelő album", - "album_selected": "Album kiválasztva", - "album_share_no_users": "Úgy tűnik, hogy már minden felhasználóval megosztottad ezt az albumot, vagy nincs senki, akivel meg tudnád osztani.", - "album_summary": "Album összefogalaló", - "album_updated": "Album frissült", - "album_updated_setting_description": "Küldjön email értesítőt, amikor egy megosztott albumhoz új elemeket adnak hozzá", - "album_upload_assets": "Elemek feltöltése és albumhoz adása", - "album_user_left": "Kiléptél a(z) {album} albumból", - "album_user_removed": "{user} eltávolítva", - "album_viewer_appbar_delete_confirm": "Biztos, hogy törölni szeretnéd ezt az albumot?", - "album_viewer_appbar_share_err_delete": "Az album törlése sikertelen", - "album_viewer_appbar_share_err_leave": "Nem sikerült kilépni az albumból", - "album_viewer_appbar_share_err_remove": "Néhány elemet nem sikerült törölni az albumból", - "album_viewer_appbar_share_err_title": "Az album átnevezése sikertelen", - "album_viewer_appbar_share_leave": "Kilépés az albumból", - "album_viewer_appbar_share_to": "Megosztás ide", - "album_viewer_page_share_add_users": "Felhasználók hozzáadása", - "album_with_link_access": "A link birtokában bárki láthatja a fotókat és a személyeket ebben az albumban.", - "albums": "Albumok", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", - "albums_default_sort_order": "Alapértelmezett album rendezés", - "albums_default_sort_order_description": "Alapértelmezett sorrendezés új albumok létrehozásánál.", - "albums_feature_description": "Másokkal megosztható elemek gyűjteménye.", - "albums_on_device_count": "Albumok az eszközön ({count})", - "albums_selected": "{count, plural, one {# album kiválasztva} other {# album kiválasztva}}", - "all": "Mind", - "all_albums": "Minden album", - "all_people": "Minden személy", - "all_photos": "Minden fénykép", - "all_videos": "Minden videó", - "allow_dark_mode": "Sötét téma engedélyezése", - "allow_edits": "Módosítások engedélyezése", - "allow_public_user_to_download": "Engedélyezi a letöltést publikus felhasználó számára", - "allow_public_user_to_upload": "Engedélyezi a feltöltést publikus felhasználó számára", - "allowed": "Engedélyezett", - "alt_text_qr_code": "QR kód kép", - "always_keep": "Tartsa meg mindig", - "always_keep_photos_hint": "A tárhely-felszabadítás nem törli az eszközön található fényképeket.", - "always_keep_videos_hint": "A tárhely-felszabadítás nem törli az eszközön található videókat.", - "anti_clockwise": "Óramutató járásával ellentétes irány", - "api_key": "API kulcs", - "api_key_description": "Ez csak most az egyszer jelenik meg. Az ablak bezárása előtt feltétlenül másold.", - "api_key_empty": "Az API kulcs név nem lehet üres", - "api_keys": "API kulcsok", - "app_architecture_variant": "Variant (Architektúra)", - "app_bar_signout_dialog_content": "Biztos, hogy ki szeretnél jelentkezni?", - "app_bar_signout_dialog_ok": "Igen", - "app_bar_signout_dialog_title": "Kijelentkezés", - "app_download_links": "App letöltési linkek", - "app_settings": "Alkalmazás beállítások", - "app_stores": "App Store-ok", - "app_update_available": "Egy új frissítés érhető el", - "appears_in": "Itt szerepel", - "apply_count": "Alkalmaz ({count, number})", - "archive": "Archívum", - "archive_action_prompt": "{count} elem hozzáadva az Archívumhoz", - "archive_or_unarchive_photo": "Fotó archiválása vagy archiválásának visszavonása", - "archive_page_no_archived_assets": "Nem található archivált elem", - "archive_page_title": "Archívum ({count})", - "archive_size": "Archívum mérete", - "archive_size_description": "Beállítja letöltésnél az archívum méretét (GiB)", - "archived": "Archivált", - "archived_count": "{count, plural, other {Archiválva #}}", - "are_these_the_same_person": "Ugyanaz a személy?", - "are_you_sure_to_do_this": "Biztosan ezt szeretnéd csinálni?", - "array_field_not_fully_supported": "Lista mezőkhöz a JSON manuális szerkesztése szükséges", - "asset_action_delete_err_read_only": "Csak-olvasható elem(ek)et nem lehet törölni, így ezeket átugorjuk", - "asset_action_share_err_offline": "Nem lehet betölteni a kapcsolat nélküli elem(ek)et, így ezeket kihagyjuk", - "asset_added_to_album": "Hozzáadva az albumhoz", - "asset_adding_to_album": "Hozzáadás az albumhoz…", - "asset_created": "Elem létrehozva", - "asset_description_updated": "Az elem leírása frissült", - "asset_filename_is_offline": "A(z) {filename} elem nem elérhető, mert offline", - "asset_has_unassigned_faces": "Az elemnek hozzá nem rendelt arcai vannak", - "asset_hashing": "Hash számítása…", - "asset_list_group_by_sub_title": "Csoportosítás", - "asset_list_layout_settings_dynamic_layout_title": "Dinamikus elrendezés", - "asset_list_layout_settings_group_automatically": "Automatikusan", - "asset_list_layout_settings_group_by": "Elemek csoportosítása", - "asset_list_layout_settings_group_by_month_day": "Hónapok és napok szerint", - "asset_list_layout_sub_title": "Elrendezés", - "asset_list_settings_subtitle": "Fotórács elrendezése", - "asset_list_settings_title": "Fotórács", - "asset_not_found_on_device_android": "Az elem nem található az eszközön", - "asset_not_found_on_device_ios": "Az elem nem található az eszközön. Ha az iCloud-ot használod, az elem lehet hogy azért nem elérhető, mert rossz fájl van tárolva az iCloud-on", - "asset_not_found_on_icloud": "Az elem nem található az iCloud-on. Lehet, hogy azért nem elérhető, mert rossz fájl van az iCloud-on tárolva", - "asset_offline": "Elem offline", - "asset_offline_description": "Ez a külső elem már nem elérhető a lemezen. Kérlek, lépj kapcsolatba az Immich adminisztrátorával.", - "asset_restored_successfully": "Elem sikeresen helyreállítva", - "asset_skipped": "Kihagyva", - "asset_skipped_in_trash": "Lomtárban", - "asset_trashed": "Elem lomtárba helyezve", - "asset_troubleshoot": "Hibajavítás", - "asset_uploaded": "Feltöltve", - "asset_uploading": "Feltöltés…", - "asset_viewer_settings_subtitle": "A képnézegető beállításainak kezelése", - "asset_viewer_settings_title": "Elem megjelenítő", - "assets": "Elemek", - "assets_added_count": "{count, plural, other {# elem}} hozzáadva", - "assets_added_to_album_count": "{count, plural, other {# elem}} hozzáadva az albumhoz", - "assets_added_to_albums_count": "{assetTotal, plural, one {# elem} other {# elemek}} hozzáadva {albumTotal, plural, one {# albumhoz} other {# albumokhoz}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Az elem} other {Az elemek}} nem adhatóak hozzá az albumhoz", - "assets_cannot_be_added_to_albums": "Az {count, plural, one {elemet} other {elemeket}} nem lehet hozzáadni egy albumhoz sem", - "assets_count": "{count, plural, other {# elem}}", - "assets_deleted_permanently": "{count} elem véglegesen törölve", - "assets_deleted_permanently_from_server": "{count} elem véglegesen törölve az Immich szerverről", - "assets_downloaded_failed": "{count, plural, one {# fájl letöltve - {error} fájl sikertelen} other {# fájl letöltve - {error} fájl sikertelen}}", - "assets_downloaded_successfully": "{count, plural, one {# fájl sikeresen letöltve} other {# fájl sikeresen letöltve}}", - "assets_moved_to_trash_count": "{count, plural, other {# elem}} áthelyezve a lomtárba", - "assets_permanently_deleted_count": "{count, plural, other {# elem}} véglegesen törölve", - "assets_removed_count": "{count, plural, other {# elem}} eltávolítva", - "assets_removed_permanently_from_device": "{count} elem véglegesen törölve az eszközödről", - "assets_restore_confirmation": "Biztos, hogy visszaállítod a lomtárban lévő összes elemet? Ez a művelet nem visszavonható! Megjegyzés: az offline elemeket nem lehet így visszaállítani.", - "assets_restored_count": "{count, plural, other {# elem}} visszaállítva", - "assets_restored_successfully": "{count} elem sikeresen helyreállítva", - "assets_trashed": "{count} elem lomtárba helyezve", - "assets_trashed_count": "{count, plural, other {# elem}} a lomtárba helyezve", - "assets_trashed_from_server": "{count} elem lomtárba helyezve az Immich szerveren", - "assets_were_part_of_album_count": "{count, plural, other {# elem}} már eleve szerepelt az albumban", - "assets_were_part_of_albums_count": "Az {count, plural, one {elem} other {elemek}} már az hozzá lettek adva az albumhoz", - "authorized_devices": "Engedélyezett eszközök", - "automatic_endpoint_switching_subtitle": "A megadott Wi-Fi-n keresztül helyi hálózaton keresztül kapcsolódolik, egyébként az alternatív címeket használja", - "automatic_endpoint_switching_title": "Automatikus URL váltás", - "autoplay_slideshow": "Automatikus diavetítés", - "back": "Vissza", - "back_close_deselect": "Vissza, bezárás, vagy kijelölés törlése", - "background_backup_running_error": "Háttérben futó mentés folyamatban, a kézi mentés nem indítható", - "background_location_permission": "Háttérben történő helymeghatározási engedély", - "background_location_permission_content": "Hálózatok automatikus váltásához az Immich-nek *mindenképpen* hozzá kell férnie a pontos helyzethez, hogy az alkalmazás le tudja kérni a Wi-Fi hálózat nevét", - "background_options": "Háttérbeli futás beállításai", - "backup": "Biztonsági Mentés", - "backup_album_selection_page_albums_device": "Ezen az eszközön lévő albumok ({count})", - "backup_album_selection_page_albums_tap": "Koppints a hozzáadáshoz, duplán koppints az eltávolításhoz", - "backup_album_selection_page_assets_scatter": "Egy elem több albumban is lehet. Ezért a mentéshez albumokat lehet hozzáadni vagy azokat a mentésből kihagyni.", - "backup_album_selection_page_select_albums": "Válassz albumokat", - "backup_album_selection_page_selection_info": "Összegzés", - "backup_album_selection_page_total_assets": "Összes egyedi elem", - "backup_albums_sync": "Biztonsági mentés albumok szinkronizálása", - "backup_all": "Összes", - "backup_background_service_backup_failed_message": "Az elemek mentése sikertelen. Újrapróbálkozás…", - "backup_background_service_complete_notification": "Az adatok mentése befejeződött", - "backup_background_service_connection_failed_message": "A szerverhez csatlakozás sikertelen. Újrapróbálkozás…", - "backup_background_service_current_upload_notification": "Feltöltés {filename}", - "backup_background_service_default_notification": "Új elemek ellenőrzése…", - "backup_background_service_error_title": "Hiba a mentés közben", - "backup_background_service_in_progress_notification": "Elemek mentése folyamatban…", - "backup_background_service_upload_failure_notification": "A feltöltés sikertelen {filename}", - "backup_controller_page_albums": "Albumok biztonsági mentése", - "backup_controller_page_background_app_refresh_disabled_content": "Engedélyezd a háttérben történő frissítést a Beállítások > Általános > Háttérben Frissítés menüpontban.", - "backup_controller_page_background_app_refresh_disabled_title": "Háttérben frissítés kikapcsolva", - "backup_controller_page_background_app_refresh_enable_button_text": "Ugrás a beállításokhoz", - "backup_controller_page_background_battery_info_link": "Mutasd meg hogyan", - "backup_controller_page_background_battery_info_message": "A sikeres háttérben történő mentéshez kérjük, tiltsd le az Immich akkumulátor optimalizálását.\n\nMivel ezt a különféle eszközökön máshogy kell, ezért kérjük, az eszközöd gyártójától tudd meg, hogyan kell.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Akkumulátor optimalizálás", - "backup_controller_page_background_charging": "Csak töltés közben", - "backup_controller_page_background_configure_error": "A háttérszolgáltatás beállítása sikertelen", - "backup_controller_page_background_delay": "Új elemek mentésének késleltetése: {duration}", - "backup_controller_page_background_description": "Kapcsold be a háttérfolyamatot, hogy automatikusan mentsen elemeket az applikáció megnyitása nélkül", - "backup_controller_page_background_is_off": "Automatikus mentés a háttérben ki van kapcsolva", - "backup_controller_page_background_is_on": "Automatikus mentés a háttérben be van kapcsolva", - "backup_controller_page_background_turn_off": "Háttérszolgáltatás kikapcsolása", - "backup_controller_page_background_turn_on": "Háttérszolgáltatás bekapcsolása", - "backup_controller_page_background_wifi": "Csak Wi-Fi-n", - "backup_controller_page_backup": "Mentés", - "backup_controller_page_backup_selected": "Kiválasztva: ", - "backup_controller_page_backup_sub": "Mentett fotók és videók", - "backup_controller_page_created": "Létrehozva: {date}", - "backup_controller_page_desc_backup": "Ha bekapcsolod az előtérben mentést, akkor az új elemek automatikusan feltöltődnek a szerverre, amikor megyitod az alkalmazást.", - "backup_controller_page_excluded": "Kivéve: ", - "backup_controller_page_failed": "Sikertelen ({count})", - "backup_controller_page_filename": "Fájlnév: {filename}[{size}]", - "backup_controller_page_id": "Azonosító: {id}", - "backup_controller_page_info": "Mentési információk", - "backup_controller_page_none_selected": "Egy sincs kiválasztva", - "backup_controller_page_remainder": "Hátralévő", - "backup_controller_page_remainder_sub": "Hátralévő fotók és videók a kijelöltek közül", - "backup_controller_page_server_storage": "Szerver tárhely", - "backup_controller_page_start_backup": "Mentés indítása", - "backup_controller_page_status_off": "Automatikus mentés az előtérben ki van kapcsolva", - "backup_controller_page_status_on": "Automatikus mentés az előtérben be van kapcsolva", - "backup_controller_page_storage_format": "{used} / {total} felhasználva", - "backup_controller_page_to_backup": "Mentésre kijelölt albumok", - "backup_controller_page_total_sub": "Minden egyedi fotó és videó a kijelölt albumokból", - "backup_controller_page_turn_off": "Előtérben mentés kikapcsolása", - "backup_controller_page_turn_on": "Előtérben mentés bekapcsolása", - "backup_controller_page_uploading_file_info": "Fájl információk feltöltése", - "backup_err_only_album": "Az utolsó albumot nem tudod törölni", - "backup_error_sync_failed": "A szinkronizálás nem sikerült. A biztonsági mentés nem elkészíthető.", - "backup_info_card_assets": "elemek", - "backup_manual_cancelled": "Megszakítva", - "backup_manual_in_progress": "Feltöltés már folyamatban. Próbáld meg később", - "backup_manual_success": "Sikeres", - "backup_manual_title": "Feltöltés állapota", - "backup_options": "Biztonsági mentés beállításai", - "backup_options_page_title": "Biztonági mentés beállításai", - "backup_setting_subtitle": "A háttérben és előtérben mentés beállításainak kezelése", - "backup_settings_subtitle": "Feltöltés beállításai", - "backup_upload_details_page_more_details": "Érintse meg további részletekhez", - "backward": "Visszafele", - "biometric_auth_enabled": "Biometrikus azonosítás engedélyezve", - "biometric_locked_out": "Ki vagy zárva a biometrikus azonosításból", - "biometric_no_options": "Nincsen elérhető biometrikus azonosítás", - "biometric_not_available": "Biometrikus azonosítás ezen az eszközön nem elérhető", - "birthdate_saved": "Születésnap elmentve", - "birthdate_set_description": "A születés napját a rendszer arra használja, hogy kiírja, hogy a fénykép készítésekor a személy hány éves volt.", - "blurred_background": "Homályos háttér", - "bugs_and_feature_requests": "Hibabejelentés és új funkció kérése", - "build": "Felépítés", - "build_image": "Kép elkészítése", - "bulk_delete_duplicates_confirmation": "Biztosan kitörölsz {count, plural, one {# duplikált elemet} other {# duplikált elemet}}? A művelet a legnagyobb méretű elemet tartja meg minden hasonló csoportból és minden másik duplikált elemet kitöröl. Ez a művelet nem visszavonható!", - "bulk_keep_duplicates_confirmation": "Biztosan meg szeretnél tartani {count, plural, other {# egyező elemet}}? Ez a művelet az elemek törlése nélkül megszünteti az összes duplikált csoportosítást.", - "bulk_trash_duplicates_confirmation": "Biztosan kitörölsz {count, plural, one {# duplikált fájlt} other {# duplikált fájlt}}? Ez a művelet megtartja minden csoportból a legnagyobb méretű elemet, és kitöröl minden másik duplikáltat.", - "buy": "Immich megvásárlása", - "cache_settings_clear_cache_button": "Gyorsítótár kiürítése", - "cache_settings_clear_cache_button_title": "Kiüríti az alkalmazás gyorsítótárát. Ez jelentősen kihat az alkalmazás teljesítményére, amíg a gyorsítótár újra nem épül.", - "cache_settings_duplicated_assets_clear_button": "KIÜRÍT", - "cache_settings_duplicated_assets_subtitle": "Fotók és videók, amiket az alkalmazás figyelmen kívül hagyott", - "cache_settings_duplicated_assets_title": "Duplikált elemek ({count})", - "cache_settings_statistics_album": "Képtár bélyegképei", - "cache_settings_statistics_full": "Teljes méretű képek", - "cache_settings_statistics_shared": "Megosztott album bélyegképei", - "cache_settings_statistics_thumbnail": "Bélyegképek", - "cache_settings_statistics_title": "Gyorsítótár használata", - "cache_settings_subtitle": "Az Immich mobilalkalmazás gyorsítótár viselkedésének beállítása", - "cache_settings_tile_subtitle": "Helyi tárhely viselkedésének beállítása", - "cache_settings_tile_title": "Helyi tárhely", - "cache_settings_title": "Gyorsítótár beállítások", - "camera": "Fényképezőgép", - "camera_brand": "Fényképezőgép márka", - "camera_model": "Fényképezőgép modell", - "cancel": "Mégsem", - "cancel_search": "Keresés megszakítása", - "canceled": "Megszakítva", - "canceling": "Lemondás", - "cannot_merge_people": "Személyek összevonása nem sikerült", - "cannot_undo_this_action": "Ez a művelet nem visszavonható!", - "cannot_update_the_description": "A leírás megváltoztatása nem sikerült", - "cast": "Közvetítés", - "cast_description": "Közvetítési célok beállítása", - "change_date": "Dátum változtatása", - "change_description": "Leírás megváltoztatása", - "change_display_order": "Megjelenítési sorrend megváltoztatása", - "change_expiration_time": "Lejárati idő megváltoztatása", - "change_location": "Helyszín változtatása", - "change_name": "Név változtatása", - "change_name_successfully": "A név megváltoztatása sikeres", - "change_password": "Jelszócsere", - "change_password_description": "Most jelentkezel be a rendszerbe első alkalommal, vagy valaki jelszó-változtatást kezdeményezett. Kérjük, add meg az új jelszót.", - "change_password_form_confirm_password": "Jelszó megerősítése", - "change_password_form_description": "Szia {name}!\n\nMost jelentkezel be először a rendszerbe vagy más okból szükséges a jelszavad megváltoztatása. Kérjük, add meg az új jelszavad.", - "change_password_form_log_out": "Kijelentkezés az összes többi eszközről", - "change_password_form_log_out_description": "Javasolt kijelentkezni az összes többi eszközről", - "change_password_form_new_password": "Új jelszó", - "change_password_form_password_mismatch": "A beírt jelszavak nem egyeznek", - "change_password_form_reenter_new_password": "Jelszó (még egyszer)", - "change_pin_code": "PIN kód megváltoztatása", - "change_trigger": "Feltétel módosítása", - "change_trigger_prompt": "Biztosan módosítani szeretnéd az indítási feltételt? Ezzel törlöd az összes műveletet és szűrőt.", - "change_your_password": "Jelszavad megváltoztatása", - "changed_visibility_successfully": "Láthatóság sikeresen megváltoztatva", - "charging": "Töltés", - "charging_requirement_mobile_backup": "Háttérben mentéshez szükséges, hogy az eszköz töltőn legyen", - "check_corrupt_asset_backup": "Sérült elemek keresése a mentésben", - "check_corrupt_asset_backup_button": "Ellenőrzés", - "check_corrupt_asset_backup_description": "Ezt az ellenőtzést csak Wi-Fi hálózaton futtasd és csak akkot, ha már az összes elem feltöltésre került. A folyamat néhány percig is eltarthat.", - "check_logs": "Hibanapló megnyitása", - "checksum": "Ellenőrző összeg", - "choose_matching_people_to_merge": "Válaszd ki a megegyező személyeket összevonásra", - "city": "Város", - "cleanup_confirm_description": "Az Immich {count} elemet talált ({date}-ig), amelyek biztonságosan mentésre kerültek a szerveren. Törlésre kerüljenek a lokális példányok erről az eszközről?", - "cleanup_confirm_prompt_title": "Törlés erről az eszközről?", - "cleanup_deleted_assets": "{count} elem áthelyezve az eszköz lomtárába", - "cleanup_deleting": "Lomtárba helyezés...", - "cleanup_found_assets": "{count} feltöltött elem találva", - "cleanup_found_assets_with_size": "{count} feltöltött elem találva ({size})", - "cleanup_icloud_shared_albums_excluded": "Megosztott iCloud albumok nem kerülnek átnézésre", - "cleanup_no_assets_found": "Nincs elem ezekkel a kritériumokkal. Tárhely felszabadításakor csak olyan elemeket törölhet, amelyek már fel lettek töltve a szerverre", - "cleanup_preview_title": "Törlendő elemek ({count})", - "cleanup_step3_description": "Szerverre feltöltött elemek keresése dátum és egyéb megadott szűrési kritériumok szerint.", - "cleanup_step4_summary": "{count} {date} előtti elem eltávolításra fog kerülni erről az eszközről. Az elemek továbbra is elérhetők lesznek az Immich alkalmazásban.", - "cleanup_trash_hint": "A tárhely visszanyeréséhez nyisd meg a beépített galéria alkalmazást és töröld a lomtárat", - "clear": "Törlés", - "clear_all": "Alaphelyzet", - "clear_all_recent_searches": "Legutóbbi keresések törlése", - "clear_file_cache": "Gyorsítótár törlése", - "clear_message": "Üzenet törlése", - "clear_value": "Érték törlése", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Jelszó megadása", - "client_cert_import": "Importálás", - "client_cert_import_success_msg": "Kliens tanúsítvány importálva", - "client_cert_invalid_msg": "Érvénytelen tanúsítvány fájl vagy hibás jelszó", - "client_cert_remove_msg": "Kliens tanúsítvány eltávolítva", - "client_cert_subtitle": "Csak a PKCS12 (.p12, .pfx) formátum támogatott. Tanúsítvány importálása/eltávolítása csak a bejelentkezés előtt lehetséges", - "client_cert_title": "SSL kliens tanúsítvány [KÍSÉRLETI]", - "clockwise": "Óramutató járásával megegyező irány", - "close": "Bezárás", - "collapse": "Összecsuk", - "collapse_all": "Mindet összecsuk", - "color": "Szín", - "color_theme": "Színtéma", - "command": "Parancs", - "comment_deleted": "Megjegyzés törölve", - "comment_options": "Megjegyzés beállítások", - "comments_and_likes": "Megjegyzések és reakciók", - "comments_are_disabled": "A megjegyzések le vannak tiltva", - "common_create_new_album": "Új album létrehozása", - "completed": "Kész", - "confirm": "Jóváhagyás", - "confirm_admin_password": "Admin jelszó megerősítése", - "confirm_delete_face": "Biztos, hogy törölni szeretnéd a(z) {name} arcát az elemről?", - "confirm_delete_shared_link": "Biztosan törölni szeretnéd ezt a megosztott linket?", - "confirm_keep_this_delete_others": "Minden más elem a készletben törlésre kerül, kivéve ezt az elemet. Biztosan folytatni szeretnéd?", - "confirm_new_pin_code": "Új PIN kód megerősítése", - "confirm_password": "Jelszó megerősítése", - "confirm_tag_face": "Szeretnéd ezt az arcot {name}-nak/nek megjelölni?", - "confirm_tag_face_unnamed": "Szeretnéd ezt az arcot megjelölni?", - "connected_device": "Kapcsolt eszköz", - "connected_to": "Kapcsolódva", - "contain": "Belül", - "context": "Kontextus", - "continue": "Folytatás", - "control_bottom_app_bar_create_new_album": "Új album létrehozása", - "control_bottom_app_bar_delete_from_immich": "Törlés az Immich-ből", - "control_bottom_app_bar_delete_from_local": "Törlés az eszközről", - "control_bottom_app_bar_edit_location": "Hely módosítása", - "control_bottom_app_bar_edit_time": "Dátum és idő módosítása", - "control_bottom_app_bar_share_link": "Link megosztása", - "control_bottom_app_bar_share_to": "Megosztás ide", - "control_bottom_app_bar_trash_from_immich": "Lomtárba helyezés", - "copied_image_to_clipboard": "Kép a vágólapra másolva.", - "copied_to_clipboard": "Vágólapra másolva!", - "copy_error": "Másolási hiba", - "copy_file_path": "Fájlútvonal másolása", - "copy_image": "Kép másolása", - "copy_link": "Link másolása", - "copy_link_to_clipboard": "Link másolása a vágólapra", - "copy_password": "Jelszó másolása", - "copy_to_clipboard": "Másolás a vágólapra", - "country": "Ország", - "cover": "Kitöltés", - "covers": "Borítók", - "create": "Létrehozás", - "create_album": "Album létrehozása", - "create_album_page_untitled": "Névtelen", - "create_api_key": "API kulcs létrehozása", - "create_first_workflow": "Az első folyamat létrehozása", - "create_library": "Képtár létrehozása", - "create_link": "Link létrehozása", - "create_link_to_share": "Megosztási link létrehozása", - "create_link_to_share_description": "A kiválasztott fotókat mindenki láthassa, aki a linket használja", - "create_new": "ÚJ LÉTREHOZÁSA", - "create_new_person": "Új személy létrehozása", - "create_new_person_hint": "Kiválasztott elemek új személyhez rendelése", - "create_new_user": "Új felhasználó létrehozása", - "create_shared_album_page_share_add_assets": "ELEMEK HOZZÁADÁSA", - "create_shared_album_page_share_select_photos": "Fotók választása", - "create_shared_link": "Megosztott link létrehozása", - "create_tag": "Címke létrehozása", - "create_tag_description": "Új címke létrehozása. Beágyazott címkék esetén add meg a címke teljes elérési útvonalát, beleértve a perjeleket is.", - "create_user": "Felhasználó létrehozása", - "create_workflow": "Folyamat létrehozása", - "created": "Készült", - "created_at": "Létrehozva", - "creating_linked_albums": "Kapcsolt albumok létrehozása...", - "crop": "Kivágás", - "crop_aspect_ratio_fixed": "Rögzített", - "crop_aspect_ratio_free": "Tetszőleges", - "crop_aspect_ratio_original": "Eredeti", - "curated_object_page_title": "Dolgok", - "current_device": "Ez az eszköz", - "current_pin_code": "Aktuális PIN kód", - "current_server_address": "Jelenlegi szerver cím", - "custom_date": "Egyéni dátum", - "custom_locale": "Egyéni területi beállítás", - "custom_locale_description": "Dátumok és számok formázása a nyelv és terület szerint", - "custom_url": "Egyéni URL", - "cutoff_date_description": "Fotók megtartása az elmúlt…", - "cutoff_day": "{count, plural, one {nap} other {nap}}", - "cutoff_year": "{count, plural, one {év} other {év}}", - "daily_title_text_date": "MMM dd (E)", - "daily_title_text_date_year": "yyyy MMM dd (E)", - "dark": "Sötét", - "dark_theme": "Sötét téma kapcsolása", - "date": "Dátum", - "date_after": "Dátumtól", - "date_and_time": "Dátum és idő", - "date_before": "Dátumig", - "date_format": "y LLL d (E) • HH:mm", - "date_of_birth_saved": "Születésnap sikeresen elmentve", - "date_range": "Dátum intervallum", - "day": "Nap", - "days": "Napok", - "deduplicate_all": "Összes deduplikálása", - "deduplication_criteria_1": "Kép mérete bájtokban", - "deduplication_criteria_2": "EXIF adatok mennyisége", - "deduplication_info": "Deduplikációs infó", - "deduplication_info_description": "Az automatikus előválogatáshoz és a duplikátumok tömeges eltávolításához a következőket vizsgáljuk:", - "default_locale": "Alapértelmezett területi beállítás", - "default_locale_description": "Dátumok és számok formázása a böngésződ területi beállítása alapján", - "delete": "Törlés", - "delete_action_confirmation_message": "Biztosan törölni szeretnéd ezt az elemet? Így az elem a szerver lomtárába kerül, és megkérdezi, hogy törölni szeretnéd-e a az eszközön is", - "delete_action_prompt": "{count} törölve", - "delete_album": "Album törlése", - "delete_api_key_prompt": "Biztosan törölni szeretnéd ezt az API kulcsot?", - "delete_dialog_alert": "Ezek az elemek véglegesen törölve lesznek Immich-ről és az eszközödről is", - "delete_dialog_alert_local": "Ezek az elemek véglegesen törölve lesznek az eszközödről, de továbbra is elérhetőek maradnak az Immich szerveren", - "delete_dialog_alert_local_non_backed_up": "Néhány elem nem lett elmentve az Immich szerverre és most véglegesen törölve lesznek az eszközödről is", - "delete_dialog_alert_remote": "Ezek az elemek véglegesen törlésre kerülnek az Immich szerverről", - "delete_dialog_ok_force": "Törlés mindenképp", - "delete_dialog_title": "Végleges törlés", - "delete_duplicates_confirmation": "Biztosan véglegesen törölni szeretnéd ezeket a duplikátumokat?", - "delete_face": "Arc törlése", - "delete_key": "Kulcs törlése", - "delete_library": "Képtár törlése", - "delete_link": "Link törlése", - "delete_local_action_prompt": "{count} törölve az eszközről", - "delete_local_dialog_ok_backed_up_only": "Csak a biztonsági mentés törlése", - "delete_local_dialog_ok_force": "Törlés mindenképp", - "delete_others": "Többi törlése", - "delete_permanently": "Törlés véglegesen", - "delete_permanently_action_prompt": "{count} törölve véglegesen", - "delete_shared_link": "Megosztott link törlése", - "delete_shared_link_dialog_title": "Megosztott link törlése", - "delete_tag": "Címke törlése", - "delete_tag_confirmation_prompt": "Biztosan törölni szeretnéd a(z) {tagName} címkét?", - "delete_user": "Felhasználó törlése", - "deleted_shared_link": "Törölt megosztott link", - "deletes_missing_assets": "Törli a fizikailag hiányzó elemeket", - "description": "Leírás", - "description_input_hint_text": "Leírás hozzáadása...", - "description_input_submit_error": "Nem sikerült frissíteni a leírást. További információért kérjük, nézd meg az eseménynaplót", - "deselect_all": "Kijelölés megszüntetés", - "details": "Részletek", - "direction": "Irány", - "disable": "Letiltás", - "disabled": "Letiltott", - "disallow_edits": "Módosítások letiltása", - "discord": "Discord", - "discover": "Felfedez", - "discovered_devices": "Felfedezett eszközök", - "dismiss_all_errors": "Minden hiba elvetése", - "dismiss_error": "Hiba elvetése", - "display_options": "Megjelenítési beállítások", - "display_order": "Megjelenítési sorrend", - "display_original_photos": "Eredeti fotók megjelenítése", - "display_original_photos_setting_description": "Egy elem nézegetése közben jelenítse meg inkább az eredeti elemet a bélyegkép helyett, ha az is web-kompatibilis. Ez lelassíthatja a fotók megjelenítését.", - "do_not_show_again": "Ne mutassa többé ezt az üzenetet", - "documentation": "Dokumentáció", - "done": "Kész", - "download": "Letöltés", - "download_action_prompt": "{count} elem letöltése", - "download_canceled": "Letöltés megszakítva", - "download_complete": "Letöltés kész", - "download_enqueue": "Letöltés sorba állítva", - "download_error": "Letöltési hiba", - "download_failed": "Sikertelen letöltés", - "download_finished": "Letöltés kész", - "download_include_embedded_motion_videos": "Beágyazott videók", - "download_include_embedded_motion_videos_description": "Mozgó képekbe ágyazott videók megjelenítése külön fájlként", - "download_notfound": "Letöltés nem található", - "download_original": "Eredeti letöltése", - "download_paused": "Letöltés szüneteltetve", - "download_settings": "Letöltés", - "download_settings_description": "Elemek letöltésével kapcsolatos beállítások kezelése", - "download_started": "Letöltés megkezdve", - "download_sucess": "Sikeres letöltés", - "download_sucess_android": "Média letöltve a DCIM/Immich mappába", - "download_waiting_to_retry": "Várás az újrapróbálkozásra", - "downloading": "Letöltés", - "downloading_asset_filename": "{filename} elem letöltése", - "downloading_from_icloud": "Letöltés az iCloudról", - "downloading_media": "Média letöltése", - "drop_files_to_upload": "A feltöltéshez húzd bárhova a fájlokat", - "duplicates": "Duplikátumok", - "duplicates_description": "Jelöld meg a duplikátumokat (ha léteznek) a csoportokban", - "duration": "Időtartam", - "edit": "Szerkesztés", - "edit_album": "Album módosítása", - "edit_avatar": "Profilkép módosítása", - "edit_birthday": "Születésnap szerkesztése", - "edit_date": "Dátum módosítása", - "edit_date_and_time": "Dátum és idő módosítása", - "edit_date_and_time_action_prompt": "{count} dátum és idő módosítva", - "edit_date_and_time_by_offset": "Dátum módosítása időeltolással", - "edit_date_and_time_by_offset_interval": "Új dátumtartomány: {from} - {to}", - "edit_description": "Leírás szerkesztése", - "edit_description_prompt": "Kérlek válassz egy új leírást:", - "edit_exclusion_pattern": "Kizárási minta (pattern) módosítása", - "edit_faces": "Arcok módosítása", - "edit_key": "Kulcs módosítása", - "edit_link": "Link módosítása", - "edit_location": "Hely módosítása", - "edit_location_action_prompt": "{count} hely változtatva", - "edit_location_dialog_title": "Hely", - "edit_name": "Név módosítása", - "edit_people": "Személyek módosítása", - "edit_tag": "Címke módosítása", - "edit_title": "Cím módosítása", - "edit_user": "Felhasználó módosítása", - "edit_workflow": "Folyamat módosítása", - "editor": "Szerkesztő", - "editor_close_without_save_prompt": "A változtatások nem lesznek elmentve", - "editor_close_without_save_title": "Szerkesztő bezárása?", - "editor_confirm_reset_all_changes": "Biztosan vissza szeretnéd állítani az összes módosítást?", - "editor_flip_horizontal": "Vízszintes tükrözés", - "editor_flip_vertical": "Függőleges tükrözés", - "editor_orientation": "Orientáció", - "editor_reset_all_changes": "Módosítások visszaállítása", - "editor_rotate_left": "Forgatás balra 90°-kal", - "editor_rotate_right": "Forgatás jobbra 90°-kal", - "email": "E-mail", - "email_notifications": "E-mail értesítések", - "empty_folder": "Ez a mappa üres", - "empty_trash": "Lomtár ürítése", - "empty_trash_confirmation": "Biztosan kiüríted a lomtárat? Ez az Immich lomtárában lévő összes elemet véglegesen törli.\nEz a művelet nem visszavonható!", - "enable": "Engedélyezés", - "enable_backup": "Biztonsági mentés bekapcsolása", - "enable_biometric_auth_description": "Add meg a jelszavad a biometrikus azonosítás engedélyezéséhez", - "enabled": "Engedélyezve", - "end_date": "Vég dátum", - "enqueued": "Sorba állítva", - "enter_wifi_name": "Add meg a Wi-Fi hálózat nevét", - "enter_your_pin_code": "Add meg a jelszavad", - "enter_your_pin_code_subtitle": "Add meg a PIN kódodat a zárolt mappa megnyitásához", - "error": "Hiba", - "error_change_sort_album": "Album sorbarendezésének megváltoztatása sikertelen", - "error_delete_face": "Hiba az arc törlése során", - "error_getting_places": "Hiba a helyek betöltésekor", - "error_loading_albums": "Hiba az albumok betöltésekor", - "error_loading_image": "Hiba a kép betöltése közben", - "error_loading_partners": "Hiba a partnerek betöltésénél: {error}", - "error_retrieving_asset_information": "Hiba az elem adatainak lekérése közben", - "error_saving_image": "Hiba: {error}", - "error_tag_face_bounding_box": "Hiba az arc megjelölése közben - nem elérhetőek a határoló koordináták", - "error_title": "Hiba - valami félresikerült", - "error_while_navigating": "Hiba az elemhez navigálás közben", - "errors": { - "cannot_navigate_next_asset": "Nem lehet a következő elemhez navigálni", - "cannot_navigate_previous_asset": "Nem lehet az előző elemhez navigálni", - "cant_apply_changes": "Nem lehet alkalmazni a változtatásokat", - "cant_change_activity": "Nem lehet {enabled, select, true {engedélyezni} other {kikapcsolni}} a tevékenységet", - "cant_change_asset_favorite": "Nem lehet a kedvenc állapotot megváltoztatni ehhez az elemhez", - "cant_change_metadata_assets_count": "Nem sikerült {count, plural, other {# elem}} metaadatát megváltoztatni", - "cant_get_faces": "Nem sikerült az arcok lekérdezése", - "cant_get_number_of_comments": "Hozzászólások számának lekérdezése sikertelen", - "cant_search_people": "Személyek keresése sikertelen", - "cant_search_places": "Helyek keresése sikertelen", - "error_adding_assets_to_album": "Elemek albumhoz adása sikertelen", - "error_adding_users_to_album": "Felhasználók albumhoz adása sikertelen", - "error_deleting_shared_user": "Megosztott felhasználó törlése sikertelen", - "error_downloading": "{filename} letöltése sikertelen", - "error_hiding_buy_button": "A megvásárlás gomb elrejtése sikertelen", - "error_removing_assets_from_album": "Az elemek albumból való eltávolítása sikertelen - további információért ellenőrizd a konzol kimenetet", - "error_selecting_all_assets": "Az összes elem kijelölése sikertelen", - "exclusion_pattern_already_exists": "Ez a kizárási minta (pattern) már létezik.", - "failed_to_create_album": "Album készítése sikertelen", - "failed_to_create_shared_link": "Megosztott link készítése sikertelen", - "failed_to_edit_shared_link": "Megosztott link módosítása sikertelen", - "failed_to_get_people": "Személyek lekérdezése sikertelen", - "failed_to_keep_this_delete_others": "Nem sikerült megtartani ezt az elemet, és a többi elemet törölni", - "failed_to_load_asset": "Elem betöltése sikertelen", - "failed_to_load_assets": "Elemek betöltése sikertelen", - "failed_to_load_notifications": "Értesítések betöltése sikertelen", - "failed_to_load_people": "Személyek betöltése sikertelen", - "failed_to_remove_product_key": "Termékkulcs eltávolítása sikertelen", - "failed_to_reset_pin_code": "PIN kód visszaállítása sikertelen", - "failed_to_stack_assets": "Elemek csoportosítása sikertelen", - "failed_to_unstack_assets": "Csoportosított elemek szétszedése sikertelen", - "failed_to_update_notification_status": "Értesítés státusz frissítése sikertelen", - "incorrect_email_or_password": "Helytelen email vagy jelszó", - "library_folder_already_exists": "Az import mappa elérési útja már létezik.", - "paths_validation_failed": "A(z) {paths, plural, one {# elérési útvonal} other {# elérési útvonal}} érvényesítése sikertelen", - "profile_picture_transparent_pixels": "Profilképek nem tartalmazhatnak átlátszó pixeleket. Közelíts rá és/vagy mozgasd a képet.", - "quota_higher_than_disk_size": "Az elérhető lemezméretnél nagyobb kvótát állítottál be", - "something_went_wrong": "Valami baj történt", - "unable_to_add_album_users": "Felhasználók albumhoz adása sikertelen", - "unable_to_add_assets_to_shared_link": "Elemeket megosztott linkhez adása sikertelen", - "unable_to_add_comment": "Hozzászólás sikertelen", - "unable_to_add_exclusion_pattern": "Kivétel minta (pattern) hozzáadása sikertelen", - "unable_to_add_partners": "Partnerek hozzáadása sikertelen", - "unable_to_add_remove_archive": "Az elem {archived, select, true {eltávolítása at Archívumból} other {hozzáadása Archívumhoz}} sikertelen", - "unable_to_add_remove_favorites": "Az elem {favorite, select, true {eltávolítása a Kedvencekből} other {hozzáadása a Kedvencekhez}} sikertelen", - "unable_to_archive_unarchive": "Az elem {archived, select, true {archiválása} other {kivétele az archívumból}} sikertelen", - "unable_to_change_album_user_role": "Az album felhasználói jogkörének megváltoztatása sikertelen", - "unable_to_change_date": "Dátum megváltoztatása sikertelen", - "unable_to_change_description": "Leírás módosítása sikertelen", - "unable_to_change_favorite": "Az elem kedvenc állapotának megváltoztatása sikertelen", - "unable_to_change_location": "Hely megváltoztatása sikertelen", - "unable_to_change_password": "Jelszó megváltoztatása sikertelen", - "unable_to_change_visibility": "{count, plural, other {# személy}} láthatóságának megváltoztatása sikertelen", - "unable_to_complete_oauth_login": "OAuth bejelentkezés befejezése sikertelen", - "unable_to_connect": "Csatlakozás sikertelen", - "unable_to_copy_to_clipboard": "Nem lehet a vágólapra másolni. Ellenőrizd, hogy az oldalt https-en keresztül használod-e", - "unable_to_create": "Folyamat létrehozása sikertelen", - "unable_to_create_admin_account": "Admin felhasználó létrehozása sikertelen", - "unable_to_create_api_key": "Új API kulcs létrehozása sikertelen", - "unable_to_create_library": "Képtár létrehozása sikertelen", - "unable_to_create_user": "Felhasználó létrehozása sikertelen", - "unable_to_delete_album": "Album törlése sikertelen", - "unable_to_delete_asset": "Elem törlése sikertelen", - "unable_to_delete_assets": "Hiba az elemek törlésekor", - "unable_to_delete_exclusion_pattern": "Kizárási minta (pattern) törlése sikertelen", - "unable_to_delete_shared_link": "Megosztott link törlése sikertelen", - "unable_to_delete_user": "Felhasználó törlése sikertelen", - "unable_to_delete_workflow": "Folyamat törlése sikertelen", - "unable_to_download_files": "Fájlok letöltése sikertelen", - "unable_to_edit_exclusion_pattern": "Kizárási minta (pattern) módosítása sikertelen", - "unable_to_empty_trash": "Lomtár ürítése sikertelen", - "unable_to_enter_fullscreen": "Teljes képernyőre váltás sikertelen", - "unable_to_exit_fullscreen": "Kilépés a teljes képernyős módból sikertelen", - "unable_to_get_comments_number": "Hozzászólások számának lekérdezése sikertelen", - "unable_to_get_shared_link": "Megosztott link lekérdezése sikertelen", - "unable_to_hide_person": "Személy elrejtése sikertelen", - "unable_to_link_motion_video": "Motion videó összekapcsolása sikertelen", - "unable_to_link_oauth_account": "OAuth felhasználó hozzárendelése sikertelen", - "unable_to_log_out_all_devices": "Kijelentkezés az összes eszközből sikertelen", - "unable_to_log_out_device": "Kijelentkezés az eszközről sikertelen", - "unable_to_login_with_oauth": "OAuth bejelentkezés sikertelen", - "unable_to_play_video": "Videó lejátszása sikertelen", - "unable_to_reassign_assets_existing_person": "Nem sikerült az elemeket hozzárendelni{name, select, null { egy meglévő személyhez} other {: {name}}}", - "unable_to_reassign_assets_new_person": "Elemek új személyhez rendelése sikertelen", - "unable_to_refresh_user": "Felhasználó frissítése sikertelen", - "unable_to_remove_album_users": "Felhasználó eltávolítása az albumból sikertelen", - "unable_to_remove_api_key": "API kulcs eltávolítása sikertelen", - "unable_to_remove_assets_from_shared_link": "Elemek eltávolítása a megosztott linkből sikertelen", - "unable_to_remove_library": "Képtár eltávolítása sikertelen", - "unable_to_remove_partner": "Partner eltávolítása sikertelen", - "unable_to_remove_reaction": "Reakció eltávolítása sikertelen", - "unable_to_reset_password": "Jelszó visszaállítása sikertelen", - "unable_to_reset_pin_code": "A PIN-kód visszaállítása nem sikerült", - "unable_to_resolve_duplicate": "Duplikátum feloldása sikertelen", - "unable_to_restore_assets": "Elemek visszaállítása sikertelen", - "unable_to_restore_trash": "Az összes elem visszaállítása sikertelen", - "unable_to_restore_user": "Felhasználó visszaállítása sikertelen", - "unable_to_save_album": "Album mentése sikertelen", - "unable_to_save_api_key": "API kulcs mentése sikertelen", - "unable_to_save_date_of_birth": "Születési időpont mentése sikertelen", - "unable_to_save_name": "Név mentése sikertelen", - "unable_to_save_profile": "Profil mentése sikertelen", - "unable_to_save_settings": "Beállítások mentése sikertelen", - "unable_to_scan_libraries": "A képtárak átfésülése sikertelen", - "unable_to_scan_library": "A képtár átfésülése sikertelen", - "unable_to_set_feature_photo": "Kijelölt fénykép beállítása sikertelen", - "unable_to_set_profile_picture": "Profilkép beállítása sikertelen", - "unable_to_set_rating": "Nem sikerült módosítani az értékelést", - "unable_to_submit_job": "A feladat elindítása sikertelen", - "unable_to_trash_asset": "Elem lomtárba helyezése sikertelen", - "unable_to_unlink_account": "A fiók szétkapcsolása sikertelen", - "unable_to_unlink_motion_video": "A motion videó szétkapcsolása sikertelen", - "unable_to_update_album_cover": "Albumborító beállítása sikertelen", - "unable_to_update_album_info": "Album információ frissítése sikertelen", - "unable_to_update_library": "Képtár frissítése sikertelen", - "unable_to_update_location": "Hely módosítása sikertelen", - "unable_to_update_settings": "Beállítások módosítása sikertelen", - "unable_to_update_timeline_display_status": "Az idővonal megjelenítési státuszának frissítése sikertelen", - "unable_to_update_user": "Felhasználó módosítása sikertelen", - "unable_to_update_workflow": "Folyamat módosítása sikertelen", - "unable_to_upload_file": "Fájlfeltöltés sikertelen" - }, - "errors_text": "Hibák", - "exclusion_pattern": "Kizárási minta", - "exif": "Exif", - "exif_bottom_sheet_description": "Leírás Hozzáadása...", - "exif_bottom_sheet_description_error": "Hiba a leírás frissítésekor", - "exif_bottom_sheet_details": "RÉSZLETEK", - "exif_bottom_sheet_location": "HELY", - "exif_bottom_sheet_no_description": "Nincs leírás", - "exif_bottom_sheet_people": "EMBEREK", - "exif_bottom_sheet_person_add_person": "Elnevez", - "exit_slideshow": "Kilépés a diavetítésből", - "expand_all": "Összes kinyitása", - "experimental_settings_new_asset_list_subtitle": "Fejlesztés alatt", - "experimental_settings_new_asset_list_title": "Kisérleti képrács engedélyezése", - "experimental_settings_subtitle": "Csak saját felelősségre használd!", - "experimental_settings_title": "Kísérleti", - "expire_after": "Lejárati idő", - "expired": "Lejárt", - "expires_date": "Lejár: {date}", - "explore": "Böngészés", - "explorer": "Böngésző", - "export": "Exportálás", - "export_as_json": "Exportálás JSON-ként", - "export_database": "Adatbázis exportálása", - "export_database_description": "Az SQLite adatbázis exportálása", - "extension": "Kiterjesztés", - "external": "Külső képtár", - "external_libraries": "Külső képtárak", - "external_network": "Külső hálózat", - "external_network_sheet_info": "Ha nem vagy a megadott Wi-Fi hálózathoz csatlakozva, akkor az alkalmazás az alábbi URL címeken fogja elérni a szervert, fentről lefelé haladva", - "face_unassigned": "Nincs hozzárendelve", - "failed": "Sikertelen", - "failed_count": "Sikertelen: {count}", - "failed_to_authenticate": "Autentikáció sikertelen", - "failed_to_load_assets": "Nem sikerült betölteni az elemeket", - "failed_to_load_folder": "Mappa betöltése sikertelen", - "favorite": "Kedvenc", - "favorite_action_prompt": "{count} hozzáadva a Kedvencekhez", - "favorite_or_unfavorite_photo": "Fotó kedvencnek jelölése vagy annak visszavonása", - "favorites": "Kedvencek", - "favorites_page_no_favorites": "Nem található kedvencnek jelölt elem", - "feature_photo_updated": "Címlapkép frissítve", - "features": "Beállítások", - "features_in_development": "Folyamatban lévő fejlesztések", - "features_setting_description": "Az alkalmazás jellemzőinek kezelése", - "file_name_or_extension": "Fájlnév vagy kiterjesztés", - "file_size": "Fájlméret", - "filename": "Fájlnév", - "filetype": "Fájltípus", - "filter": "Szűrő", - "filter_description": "Az elemek szűrési feltételei", - "filter_people": "Személyek szűrése", - "filter_places": "Helyszínek szűrése", - "filters": "Szűrők", - "find_them_fast": "Név alapján kereséssel gyorsan megtalálhatóak", - "first": "Első", - "fix_incorrect_match": "Hibás találat javítása", - "folder": "Mappa", - "folder_not_found": "Mappa nem található", - "folders": "Mappák", - "folders_feature_description": "A fájlrendszerben lévő fényképek és videók mappanézetben való böngészése", - "forgot_pin_code_question": "Elfelejtetted a PIN kódod?", - "forward": "Előre", - "free_up_space": "Tárhely felszabadítása", - "free_up_space_description": "Hely felszabadítása érdekében helyezze át a mentett fotókat és videókat az eszköz kukájába. A szerveren lévő másolatok biztonságban maradnak.", - "free_up_space_settings_subtitle": "Eszköz tárhely felszabadítása", - "full_path": "Teljes eléréi útvonal: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ez a funkció a Google-től tölti be a működéséhez szükséges külső adatokat.", - "general": "Általános", - "geolocation_instruction_location": "Kattints egy elemre, amelynek ismert a helyszíne a pozíció kiválasztásához, vagy válassz a térképen", - "get_help": "Segítségkérés", - "get_people_error": "Hiba a személyek beszerzése közben", - "get_wifiname_error": "Nem sikerült lekérni a Wi-Fi nevét. Győződj meg róla, hogy megadtad a szükséges engedélyeket és csatlakoztál egy Wi-Fi hálózathoz", - "getting_started": "Kezdő lépések", - "go_back": "Visszalépés", - "go_to_folder": "Ugrás a mappához", - "go_to_search": "Ugrás a kereséshez", - "gps": "GPS", - "gps_missing": "Nincs GPS", - "grant_permission": "Engedély megadása", - "group_albums_by": "Albumok csoportosítása...", - "group_country": "Csoportosítás ország szerint", - "group_no": "Nincs csoportosítás", - "group_owner": "Csoportosítás tulajdonos szerint", - "group_places_by": "Helyszínek csoportosítása...", - "group_year": "Csoportosítás év szerint", - "haptic_feedback_switch": "Rezgéses visszajelzés engedélyezése", - "haptic_feedback_title": "Rezgéses visszajelzés", - "has_quota": "Kvóta", - "hash_asset": "Elem hash-elése", - "hashed_assets": "Hash-elt elemek", - "hashing": "Hash-elés folyamatban", - "header_settings_add_header_tip": "Fejléc hozzáadása", - "header_settings_field_validator_msg": "Az érték nem lehet üres", - "header_settings_header_name_input": "Fejléc neve", - "header_settings_header_value_input": "Fejléc értéke", - "headers_settings_tile_title": "Egyéni proxy fejlécek", - "height": "Magasság", - "hi_user": "Szia {name} ({email})", - "hide_all_people": "Minden személy elrejtése", - "hide_gallery": "Galéria elrejtése", - "hide_named_person": "{name} elrejtése", - "hide_password": "Jelszó elrejtése", - "hide_person": "Személy elrejtése", - "hide_schema": "Séma elrejtése", - "hide_text_recognition": "Szövegfelismerés elrejtése", - "hide_unnamed_people": "Név nélküli személyek elrejtése", - "home_page_add_to_album_conflicts": "{added} elem hozzáadva a(z) \"{album}\" albumhoz. {failed} elem már eleve az albumban volt.", - "home_page_add_to_album_err_local": "Helyi elemeket még nem lehet albumba tenni, ki lesznek hagyva", - "home_page_add_to_album_success": "{added} elem hozzáadva a(z) \"{album}\" albumhoz.", - "home_page_album_err_partner": "Még nem lehet a partner elemeit albumokhoz adni, ki lesznek hagyva", - "home_page_archive_err_local": "Helyi elemek archiválása még nem támogatott, úgyhogy kihagyjuk", - "home_page_archive_err_partner": "Partner elemeit nem lehet archiválni, úgyhogy kihagyjuk", - "home_page_building_timeline": "Idővonal összeállítása", - "home_page_delete_err_partner": "Partner elemeit nem lehet törölni, úgyhogy kihagyjuk", - "home_page_delete_remote_err_local": "Helyi elemek vannak távoli törlésre kiválasztva, úgyhogy ezeket kihagyjuk", - "home_page_favorite_err_local": "Helyi elemeket még nem lehet a kedvencek közé tenni, úgyhogy ezeket kihagyjuk", - "home_page_favorite_err_partner": "Partner elemeit még nem lehet a kedvencek közé tenni, úgyhogy ezeket kihagyjuk", - "home_page_first_time_notice": "Ha most használod először az alkalmazást, a fotók és videók megjelenítéséhez az idővonaladon, állítsd be, hogy melyik albumaidról készüljön biztonsági mentés", - "home_page_locked_error_local": "A helyi elemek nem mozgathatóak a zárolt mappába, ki lesznek hagyva", - "home_page_locked_error_partner": "Partner elemek nem mozgathatóak a zárolt mappába, átugorva", - "home_page_share_err_local": "Helyi elemekről nem lehet megosztott linket készíteni, úgyhogy kihagyjuk", - "home_page_upload_err_limit": "Csak 30 elemet tudsz egyszerre feltölteni, úgyhogy kihagyjuk", - "host": "Kiszolgáló", - "hour": "Óra", - "hours": "Órák", - "id": "Azonosító", - "idle": "Üresjárat", - "ignore_icloud_photos": "iCloud fotók figyelmen kívül hagyása", - "ignore_icloud_photos_description": "Az iCloud-ban tárolt fotók nem lesznek feltöltve az Immich szerverre", - "image": "Kép", - "image_alt_text_date": "{isVideo, select, true {Videó} other {Kép}} készítési dátuma: {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Videó} other {Kép}} vele: {person1} (készült {date})", - "image_alt_text_date_2_people": "{isVideo, select, true {Videó} other {Kép}} velük: {person1} és {person2} (készült: {date})", - "image_alt_text_date_3_people": "{isVideo, select, true {Videó} other {Kép}} velük: {person1}, {person2} és {person3} (készült: {date})", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Videó} other {Kép}} velük: {person1}, {person2} és további {additionalCount, number} személy (készült: {date})", - "image_alt_text_date_place": "{isVideo, select, true {Videó} other {Kép}} itt: {country}, {city} (készült: {date})", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Videó} other {Kép}} itt: {country}, {city}, vele: {person1} (készült: {date})", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Videó} other {Kép}} itt: {country}, {city}, velük: {person1} és {person2} (készült: {date})", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Videó} other {Kép}} itt: {country}, {city}, velük: {person1}, {person2} és {person3} (készült: {date})", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Videó} other {Kép}} itt: {country}, {city}, velük: {person1}, {person2} és további {additionalCount, number} személy (készült: {date})", - "image_saved_successfully": "Kép elmentve", - "image_viewer_page_state_provider_download_started": "A letöltés elkezdődött", - "image_viewer_page_state_provider_download_success": "Letöltés sikeres", - "image_viewer_page_state_provider_share_error": "Megosztási hiba", - "immich_logo": "Immich logó", - "immich_web_interface": "Immich webes felület", - "import_from_json": "Importálás JSON-ből", - "import_path": "Importálási útvonal", - "in_albums": "{count, plural, one {# albumban} other {# albumban}}", - "in_archive": "Archívumban", - "in_year": "Ebben az évben: {year}", - "in_year_selector": "Ebben az évben", - "include_archived": "Archiváltakkal együtt", - "include_shared_albums": "Megosztott albumokkal együtt", - "include_shared_partner_assets": "Partner által megosztott elemekkel együtt", - "individual_share": "Egymagában megosztott elem", - "individual_shares": "Egyéni megosztások", - "info": "Infó", - "interval": { - "day_at_onepm": "Minden nap 13 órakor", - "hours": "{hours, plural, one {óránként} other {{hours, number} óránként}}", - "night_at_midnight": "Minden éjjel éjfélkor", - "night_at_twoam": "Minden éjjel 2 órakor" - }, - "invalid_date": "Érvénytelen dátum", - "invalid_date_format": "Érvénytelen dátumformátum", - "invite_people": "Személyek meghívása", - "invite_to_album": "Meghívás az albumba", - "ios_debug_info_fetch_ran_at": "Letöltés futtatva {dateTime}", - "ios_debug_info_last_sync_at": "Utoljára szinkronizálva {dateTime}", - "ios_debug_info_no_processes_queued": "Nincs a sorban háttérfolyamat jelenleg", - "ios_debug_info_no_sync_yet": "Még nem futott szinkronizáló háttérfolyamat", - "ios_debug_info_processes_queued": "{count, plural, one {{count} háttérfolyamat előkészítve} other {{count} háttérfolyamat előkészítve}}", - "ios_debug_info_processing_ran_at": "A feldolgozás ekkor futott: {dateTime}", - "items_count": "{count, plural, other {# elem}}", - "jobs": "Feladatok", - "json_editor": "JSON szerkesztő", - "json_error": "JSON hiba", - "keep": "Megtart", - "keep_albums": "Albumok megtartása", - "keep_albums_count": "{count} album megtartása", - "keep_all": "Összes megtartása", - "keep_description": "Válaszd ki, mi maradjon az eszközödön tárhely felszabadításakor.", - "keep_favorites": "Kedvencek megtartása", - "keep_on_device": "Maradjon az eszközön", - "keep_on_device_hint": "Válaszd ki az eszközön tartandó elemeket", - "keep_this_delete_others": "Ennek a meghagyása, a többi törlése", - "keeping": "Meg lesz tartva: {items}", - "kept_this_deleted_others": "Ez az elem és a töröltek meg lettek hagyva {count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "Billentyűparancsok", - "language": "Nyelv", - "language_no_results_subtitle": "Próbáld módosítani a szavaidat a keresésnél", - "language_no_results_title": "Nem található nyelv", - "language_search_hint": "Nyelvek keresése...", - "language_setting_description": "Válaszd ki preferált nyelvet", - "large_files": "Nagy fájlok", - "last": "Utolsó", - "last_months": "{count, plural, one {Utolsó hónap} other {Utolsó # hónap}}", - "last_seen": "Utoljára bejelentkezve", - "latest_version": "Legfrissebb verzió", - "latitude": "Szélesség", - "leave": "Elhagyás", - "leave_album": "Album elhagyása", - "lens_model": "Objektív modell", - "let_others_respond": "Mások is reagálhatnak", - "level": "Szint", - "library": "Képtár", - "library_add_folder": "Könyvtár hozzáadása", - "library_edit_folder": "Könyvtár szerkesztése", - "library_options": "Képtár beállítások", - "library_page_device_albums": "Albumok az eszközön", - "library_page_new_album": "Új album", - "library_page_sort_asset_count": "Elemek száma", - "library_page_sort_created": "Létrehozás ideje", - "library_page_sort_last_modified": "Utolsó módosítás ideje", - "library_page_sort_title": "Album címe", - "licenses": "Licencek", - "light": "Világos", - "like": "Tetszik", - "like_deleted": "Reakció törölve", - "link_motion_video": "Motion videó hozzárendelése", - "link_to_oauth": "Csatlakoztatás OAuth-hoz", - "linked_oauth_account": "Csatlakoztatott OAuth fiók", - "list": "Lista", - "loading": "Betöltés", - "loading_search_results_failed": "Keresési eredmények betöltése sikertelen", - "local": "Helyi", - "local_asset_cast_failed": "Nem lehet olyan elemet vetíteni, ami nincs a szerverre feltöltve", - "local_assets": "Helyi elemek", - "local_id": "Helyi azonosító", - "local_media_summary": "Helyi média összegzés", - "local_network": "Helyi hálózat", - "local_network_sheet_info": "Az alkalmazés ezen az URL címen fogja elérni a szervert, ha a megadott Wi-Fi hálózathoz van csatlankozva", - "location": "Lokáció", - "location_permission": "Helymeghatározási engedély", - "location_permission_content": "A hálózatok automatikus váltásához az Immich-nek szüksége van a pontos helymeghatározásra, hogy az alkalmazás le tudja kérni a Wi-Fi hálózat nevét", - "location_picker_choose_on_map": "Válassz a térképen", - "location_picker_latitude_error": "Érvényes szélességi kört írj be", - "location_picker_latitude_hint": "Ide írd a szélességi kört", - "location_picker_longitude_error": "Érvényes hosszúsági kört írj be", - "location_picker_longitude_hint": "Ide írd a hosszúsági kört", - "lock": "Zárolás", - "locked_folder": "Zárolt mappa", - "log_detail_title": "Naplók részletei", - "log_out": "Kijelentkezés", - "log_out_all_devices": "Kijelentkezés minden eszközön", - "logged_in_as": "Belépve: {user} néven", - "logged_out_all_devices": "Minden eszköz kijelentkeztetve", - "logged_out_device": "Eszköz kijelentkeztetve", - "login": "Bejelentkezés", - "login_disabled": "A bejelentkezés letiltva", - "login_form_api_exception": "API hiba. Kérljük, ellenőrid a szerver címét, majd próbáld újra.", - "login_form_back_button_text": "Vissza", - "login_form_email_hint": "email@cimed.hu", - "login_form_endpoint_hint": "http://szerver-címe:port", - "login_form_endpoint_url": "Szerver címe", - "login_form_err_http": "Kérjük, hogy egy http:// vagy https:// címet adj meg", - "login_form_err_invalid_email": "Érvénytelen email cím", - "login_form_err_invalid_url": "Érvénytelen cím", - "login_form_err_leading_whitespace": "Az első karakter szóköz", - "login_form_err_trailing_whitespace": "Az utolsó karakter szóköz", - "login_form_failed_get_oauth_server_config": "Nem sikerült az OAuth bejelentkezés. Ellenőrizd a szerver URL-t", - "login_form_failed_get_oauth_server_disable": "OAuth bejelentkezés nem elérhető ezen a szerveren", - "login_form_failed_login": "Hiba a bejelentkezés közben, ellenőrizd a szerver címét, az emailt és a jelszót", - "login_form_handshake_exception": "Handshake hiba történt a szerverrel. Engedélyezd a saját aláírású tanúsítványok használatát a beállításokban, ha ilyen tanúsítványt használsz.", - "login_form_password_hint": "jelszó", - "login_form_save_login": "Maradjon bejelentkezve", - "login_form_server_empty": "Add meg a szerver címét.", - "login_form_server_error": "Nem sikerült kapcsolódni a szerverhez.", - "login_has_been_disabled": "Bejelentkezés le van tiltva.", - "login_password_changed_error": "Nem sikerült módosítani a jelszót", - "login_password_changed_success": "Jelszó sikeresen módosítva", - "logout_all_device_confirmation": "Biztos, hogy minden eszközön ki szeretnél jelentkezni?", - "logout_this_device_confirmation": "Biztos, hogy ki szeretnél jelentkezni ezen az eszközön?", - "logs": "Naplók", - "longitude": "Hosszúság", - "look": "Megjelenítés", - "loop_videos": "Videók ismétlése", - "loop_videos_description": "Engedélyezi a videók folyamatosan ismételt lejátszását.", - "main_branch_warning": "Fejlesztői verziót használsz. Javasoljuk a stabil verzió használatát!", - "main_menu": "Főmenü", - "maintenance_action_restore": "Adatbázis helyreállítása", - "maintenance_description": "Az Immich maintenance mode-ba lett állítva.", - "maintenance_end": "Karbantartási mód kikapcsolása", - "maintenance_end_error": "Karbantartási mód kikapcsolása sikertelen.", - "maintenance_logged_in_as": "Bejelentkezve mint: {user}", - "maintenance_restore_from_backup": "Helyreállítás biztonsági mentésből", - "maintenance_restore_library": "Könyvtár helyreállítása", - "maintenance_restore_library_confirm": "Ha ez jónak tűnik, tovább a biztonsági mentés visszaállítására!", - "maintenance_restore_library_description": "Adatbázis helyreállítása", - "maintenance_restore_library_folder_has_files": "{folder} {count} mappával rendelkezik", - "maintenance_restore_library_folder_no_files": "{folder}-ból/-ből fájlok hiányoznak!", - "maintenance_restore_library_folder_pass": "olvasható és írható", - "maintenance_restore_library_folder_read_fail": "nem olvasható", - "maintenance_restore_library_folder_write_fail": "nem írható", - "maintenance_restore_library_hint_missing_files": "Fontos fájlok hiányozhatnak", - "maintenance_restore_library_hint_regenerate_later": "Regenerálhatja ezeket később a beállításokban", - "maintenance_restore_library_hint_storage_template_missing_files": "A tárolási sablon-t használaja? Lehet, hogy hiányoznak fájlok", - "maintenance_restore_library_loading": "Integritásellenőrzés és heurisztikák betöltése…", - "maintenance_task_backup": "Adatbázis biztonsági mentése folyamatban…", - "maintenance_task_migrations": "Adatbázis migrálása folyamatban…", - "maintenance_task_restore": "A választott biztonsági mentés visszaállítása…", - "maintenance_task_rollback": "A visszaállítás sikertelen, kezdeti állapot visszatöltése folyamatban…", - "maintenance_title": "Átmenetileg nem elérhető", - "make": "Gyártó", - "manage_geolocation": "Helyadatok kezelése", - "manage_media_access_rationale": "Ez az engedély szükséges a mozgatható eszközök kukába való áthelyezéshez és onnan való visszaállításhoz.", - "manage_media_access_settings": "Beállítások megnyitása", - "manage_media_access_subtitle": "Engedélyezze az Immichnek a médiafájlok kezelését és áthelyezését.", - "manage_media_access_title": "Médiakezelés hozzáférés", - "manage_shared_links": "Megosztási linkek kezelése", - "manage_sharing_with_partners": "Partnerekkel való megosztás kezelése", - "manage_the_app_settings": "Alkalmazás beállításainak kezelése", - "manage_your_account": "Saját fiókod kezelése", - "manage_your_api_keys": "Saját API kulcsok kezelése", - "manage_your_devices": "Bejelentkezett eszközök kezelése", - "manage_your_oauth_connection": "OAuth kapcsolódás kezelése", - "map": "Térkép", - "map_assets_in_bounds": "{count, plural, =0 {Nincs fotó ezen a területen} one {# fotó} other {# fotó}}", - "map_cannot_get_user_location": "A helymeghatározás nem sikerült", - "map_location_dialog_yes": "Igen", - "map_location_picker_page_use_location": "Kiválasztott hely használata", - "map_location_service_disabled_content": "A helymeghatározás szolgáltatást engedélyezni kell a jelenlegi helyednél lévő elemek megjelenítéséhez. Szeretnéd most engedélyezni?", - "map_location_service_disabled_title": "Helymeghatározás szolgáltatás letiltva", - "map_marker_for_images": "{country}, {city} helyen készült képek térképjelölője", - "map_marker_with_image": "Térképjelölő képpel", - "map_no_location_permission_content": "A helymeghatározást engedélyezni kell a jelenlegi helyednél lévő elemek megjelenítéséhez. Szeretnéd most engedélyezni?", - "map_no_location_permission_title": "Helymeghatározás letiltva", - "map_settings": "Térkép beállítások", - "map_settings_dark_mode": "Sötét téma", - "map_settings_date_range_option_day": "Elmúlt 24 óra", - "map_settings_date_range_option_days": "Elmúlt {days} nap", - "map_settings_date_range_option_year": "Elmúlt év", - "map_settings_date_range_option_years": "Elmúlt {years} év", - "map_settings_dialog_title": "Térkép beállítások", - "map_settings_include_show_archived": "Archiváltakkal együtt", - "map_settings_include_show_partners": "Partnerekkel együtt", - "map_settings_only_show_favorites": "Csak kedvencek megjelenítése", - "map_settings_theme_settings": "Térkép téma", - "map_zoom_to_see_photos": "Kicsinyítsd, hogy láss fényképeket", - "mark_all_as_read": "Összes megjelölése olvasottként", - "mark_as_read": "Megjelölés olvasottként", - "marked_all_as_read": "Összes megjelölve olvasottként", - "matches": "Azonosak", - "matching_assets": "Kapcsolódó elemek", - "media_type": "Médiatípus", - "memories": "Emlékek", - "memories_all_caught_up": "Naprakész vagy", - "memories_check_back_tomorrow": "Nézz vissza holnap újabb emlékekért", - "memories_setting_description": "Állítsd be, hogy mik jelenjenek meg az emlékeid közt", - "memories_start_over": "Újrakezdés", - "memories_swipe_to_close": "Bezáráshoz söpörd ki felfelé", - "memory": "Emlék", - "memory_lane_title": "Emlékek {title}", - "menu": "Menü", - "merge": "Összevonás", - "merge_people": "Személyek összevonása", - "merge_people_limit": "Egyszerre legfeljebb 5 arcot vonhatsz össze", - "merge_people_prompt": "Biztosan összevonod ezeket a személyeket? Ez a művelet nem visszavonható.", - "merge_people_successfully": "Személyek sikeresen összevonva", - "merged_people_count": "Összevonva {count, plural, other {# személy}}", - "minimize": "Kicsinyítés", - "minute": "Perc", - "minutes": "Percek", - "mirror_horizontal": "Vízszintesen", - "mirror_vertical": "Függőlegesen", - "missing": "Hiányzók", - "mobile_app": "Mobilapplikáció", - "mobile_app_download_onboarding_note": "Töltse le a kiegészítő mobilalkalmazást az alábbi opciók segítségével", - "model": "Modell", - "month": "Hónap", - "monthly_title_text_date_format": "y MMMM", - "more": "Továbbiak", - "move": "Áthelyezés", - "move_down": "Lejjebb", - "move_off_locked_folder": "Átmozgatás a zárolt mappából", - "move_to": "Mozgatás", - "move_to_device_trash": "Áthelyezés az eszköz szemetesébe", - "move_to_lock_folder_action_prompt": "{count} hozzáadva a zárolt mappához", - "move_to_locked_folder": "Áthelyezés a zárolt mappába", - "move_to_locked_folder_confirmation": "Ezek a képek és videók az összes albumból kikerülnek, és csak a zárolt mappában lesznek elérhetőek", - "move_up": "Feljebb", - "moved_to_archive": "{count, plural, one {# elem} other {# elem}} archiválva", - "moved_to_library": "{count, plural, one {# elem} other {# elem}} másik könyvtárba helyezve", - "moved_to_trash": "Áthelyezve a lomtárba", - "multiselect_grid_edit_date_time_err_read_only": "Csak-olvasható elem(ek) dátuma nem módosítható, ezért kihagyjuk", - "multiselect_grid_edit_gps_err_read_only": "Csak-olvasható elem(ek) helye nem módosítható, ezért kihagyjuk", - "mute_memories": "Emlékek elnémítása", - "my_albums": "Saját albumaim", - "name": "Név", - "name_or_nickname": "Név vagy becenév", - "name_required": "Kötelező megadni egy nevet", - "navigate": "Navigáció", - "navigate_to_time": "Navigálás adott időponthoz", - "network_requirement_photos_upload": "Mobil adatforgalmat használjon a fényképek biztonsági mentéséhez", - "network_requirement_videos_upload": "Mobil adatforgalmat használjon a videók biztonsági mentéséhez", - "network_requirements": "Hálózati követelmények", - "network_requirements_updated": "A hálózat megváltozott, a biztonsági mentési sor visszaállítása", - "networking_settings": "Hálózat", - "networking_subtitle": "Szerver végpont beállítások kezelése", - "never": "Soha", - "new_album": "Új album", - "new_api_key": "Új API kulcs", - "new_date_range": "Új dátumtartomány", - "new_password": "Új jelszó", - "new_person": "Új személy", - "new_pin_code": "Új PIN kód", - "new_pin_code_subtitle": "Ez az első alkalom hogy megnyitod a zárolt mappát. Hozz létre egy jelszót a mappa biztonságos eléréséhez", - "new_timeline": "Új idővonal", - "new_update": "Új frissítés", - "new_user_created": "Új felhasználó létrehozva", - "new_version_available": "ÚJ VERZIÓ ÉRHETŐ EL", - "newest_first": "Legújabb először", - "next": "Következő", - "next_memory": "Következő emlék", - "no": "Nem", - "no_actions_added": "Még nincsenek műveletek", - "no_albums_found": "Nem találhatók albumok", - "no_albums_message": "Fotóid és videóid rendszerezéséhez hozz létre egy új albumot", - "no_albums_with_name_yet": "Úgy tűnik, hogy ilyen névvel még nincs albumod.", - "no_albums_yet": "Úgy tűnik, hogy még egy albumod sincs.", - "no_archived_assets_message": "Archiváld a fényképeket és videókat, hogy elrejtsd azokat a Képek nézetből", - "no_assets_message": "Kattints ide az első fotód feltöltéséhez", - "no_assets_to_show": "Nincs megjeleníthető elem", - "no_cast_devices_found": "Nem található eszköz vetítéshez", - "no_checksum_local": "Nincs elérhető ellenőrző összeg - a helyi elemek nem kérhetők le", - "no_checksum_remote": "Nincs elérhető ellenőrző összeg - a távoli elem nem kérhető le", - "no_configuration_needed": "Nincs szükség konfigurációra", - "no_devices": "Nincs engedélyezett eszköz", - "no_duplicates_found": "Nem találhatók duplikátumok.", - "no_exif_info_available": "Nincs elérhető Exif információ", - "no_explore_results_message": "Tölts fel több képet, hogy böngészhesd a gyűjteményed.", - "no_favorites_message": "Add hozzá a kedvencekhez, hogy gyorsan megtaláld a legjobb képeidet és videóidat", - "no_filters_added": "Még nincsenek szűrők", - "no_libraries_message": "Hozz létre külső képtárat a fényképeid és videóid megtekintéséhez", - "no_local_assets_found": "Nem találhatók helyi eszközök ezzel az ellenőrzőösszeggel", - "no_location_set": "Nincs hely megadva", - "no_locked_photos_message": "A zárolt mappában elhelyezett fotók és videók rejtettek, és nem jelennek meg a könyvtárad böngészése vagy keresése közben sem.", - "no_name": "Nincs név", - "no_notifications": "Nincsenek értesítések", - "no_people_found": "Nem található személy", - "no_places": "Nincsenek helyek", - "no_remote_assets_found": "Nem találhatók távoli eszközök ezzel az ellenőrzőösszeggel", - "no_results": "Nincs találat", - "no_results_description": "Próbálkozz szinonimákkal vagy általánosabb kulcsszavakkal", - "no_shared_albums_message": "Hozz létre egy új albumot, hogy megoszthasd fényképeid és videóid másokkal", - "no_uploads_in_progress": "Nincs folyamatban lévő feltöltés", - "none": "Semelyik", - "not_allowed": "Nem engedélyezett", - "not_available": "N/A", - "not_in_any_album": "Nincs albumban", - "not_selected": "Nincs kiválasztva", - "note_apply_storage_label_to_previously_uploaded assets": "Megjegyzés: a korábban feltöltött elemek tárhely címkézéséhez futtasd a(z)", - "notes": "Megjegyzések", - "nothing_here_yet": "Még semmi sincs itt", - "notification_permission_dialog_content": "Az értesítések bekapcsolásához a Beállítások menüben válaszd ki az Engedélyezés-t.", - "notification_permission_list_tile_content": "Értesítések engedélyezése.", - "notification_permission_list_tile_enable_button": "Értesítések engedélyezése", - "notification_permission_list_tile_title": "Engedély az Értesítésekhez", - "notification_toggle_setting_description": "Email értesítések engedélyezése", - "notifications": "Értesítések", - "notifications_setting_description": "Értesítések kezelése", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium konfigurátor", - "obtainium_configurator_instructions": "Az Obtainium segítségével közvetlenül az Immich GitHub-os kiadásából telepítheted és frissítheted az Android-alkalmazást. Hozz létre egy API-kulcsot és válassz egy változatot az Obtainium konfigurációs hivatkozás elkészítéséhez", - "ocr": "OCR", - "official_immich_resources": "Hivatalos Immich források", - "offline": "Nem elérhető (offline)", - "offset": "Eltolás", - "ok": "Rendben", - "oldest_first": "Legrégebbi először", - "on_this_device": "Ezen az eszközön", - "onboarding": "Első lépések", - "onboarding_locale_description": "Válaszd ki a preferált nyelved. Ezt később a beállításokban bármikor módosíthatod.", - "onboarding_privacy_description": "Az alábbi (nem kötelező) funkciók külsős szolgáltatásokon alapulnak és bármikor kikapcsolhatóak a beállításokban.", - "onboarding_server_welcome_description": "Állítsuk be a példányodat pár alap beállítással.", - "onboarding_theme_description": "Válassz egy színtémát. Ezt bármikor megváltoztathatod a beállításokban.", - "onboarding_user_welcome_description": "Kezdjünk bele!", - "onboarding_welcome_user": "Üdvözöllek {user}", - "online": "Online (elérhető)", - "only_favorites": "Csak kedvencek", - "open": "Nyitva", - "open_in_map_view": "Megnyitás térkép nézetben", - "open_in_openstreetmap": "Megnyitás OpenStreetMap-ben", - "open_the_search_filters": "Keresési szűrők megnyitása", - "options": "Beállítások", - "or": "vagy", - "organize_into_albums": "Albumokba rendezés", - "organize_into_albums_description": "Meglévő fotók albumokba helyezése, a jelenlegi szinkronizációs beállítások alapján", - "organize_your_library": "Rendszerezd a képtáradat", - "original": "eredeti", - "other": "Egyéb", - "other_devices": "Egyéb eszközök", - "other_entities": "Egyéb entitások", - "other_variables": "Egyéb változók", - "owned": "Tulajdonos", - "owner": "Tulajdonos", - "page": "Oldal", - "partner": "Partner", - "partner_can_access": "{partner} hozzáférhet", - "partner_can_access_assets": "Minden fényképed és videód, kivéve az archiváltak és a töröltek", - "partner_can_access_location": "A helyszín, ahol a fotókat készítették", - "partner_list_user_photos": "{user} fényképei", - "partner_list_view_all": "Összes megjelenítése", - "partner_page_empty_message": "Még senkivel nem osztottad meg a fényképeidet.", - "partner_page_no_more_users": "Nincs több hozzáadható felhasználó", - "partner_page_partner_add_failed": "Partner hozzáadása sikertelen", - "partner_page_select_partner": "Partner kiválasztása", - "partner_page_shared_to_title": "Megosztva", - "partner_page_stop_sharing_content": "{partner} nem fog többé hozzáférni a fotóidhoz.", - "partner_sharing": "Partnerrel megosztás", - "partners": "Partnerek", - "password": "Jelszó", - "password_does_not_match": "A jelszavak nem egyeznek", - "password_required": "Jelszó szükséges", - "password_reset_success": "A jelszó visszaállítása sikeres", - "past_durations": { - "days": "{days, plural, one {Tegnap} other {Elmúlt # nap}}", - "hours": "{hours, plural, one {Előző óra} other {Elmúlt # óra}}", - "years": "{years, plural, one {Tavaly} other {Elmúlt # év}}" - }, - "path": "Útvonal", - "pattern": "Minta (Pattern)", - "pause": "Szüneteltetés", - "pause_memories": "Emlékek szüneteltetése", - "paused": "Szüneteltetve", - "pending": "Folyamatban lévő", - "people": "Személyek", - "people_edits_count": "{count, plural, other {# személy}} módosítva", - "people_feature_description": "Személyek szerint csoportosított fényképek és videók böngészése", - "people_selected": "{count, plural, other {# személy}} kiválasztva", - "people_sidebar_description": "Személyek link megjelenítése az oldalsávban", - "permanent_deletion_warning": "Figyelmeztetés végleges törlésről", - "permanent_deletion_warning_setting_description": "Figyelmeztessen elemek végleges törlése előtt", - "permanently_delete": "Végleges törlés", - "permanently_delete_assets_count": "{count, plural, one {Elem} other {Elemek}} végleges törlése", - "permanently_delete_assets_prompt": "Biztos, hogy véglegesen törölni {count, plural, one {szeretnéd ezt az elemet} other {szeretnél # elemet}}? Ez el fogja távolítani az {count, plural, one {elemet az albumokból, amikben szerepel} other {elemeket az albumokból, amikben szerepelnek}}.", - "permanently_deleted_asset": "Elem véglegesen törölve", - "permanently_deleted_assets_count": "{count, plural, other {# elem}} véglegesen törölve", - "permission": "Jogosultság", - "permission_empty": "A jogosultság nem hagyható üresen", - "permission_onboarding_back": "Vissza", - "permission_onboarding_continue_anyway": "Folytatás mindenképp", - "permission_onboarding_get_started": "Vágjunk bele", - "permission_onboarding_go_to_settings": "Ugrás a beállításokhoz", - "permission_onboarding_permission_denied": "Hozzáférés megtagadva. Az Immich használatához engedélyezni kell a fotó és videó hozzáférést a Beállításokban.", - "permission_onboarding_permission_granted": "Hozzáférés engedélyezve! Minden készen áll.", - "permission_onboarding_permission_limited": "Korlátozott hozzáférés. Ha szeretnéd, hogy az Immich a teljes galéria gyűjteményedet mentse és kezelje, akkor a Beállításokban engedélyezd a fotó és videó jogosultságokat.", - "permission_onboarding_request": "Engedélyezni kell, hogy az Immich hozzáférjen a képeidhez és videóidhoz.", - "person": "Személy", - "person_age_months": "{months, plural, one {# hónapja} other {# hónapja}}", - "person_age_year_months": "Egy év és {months, plural, one {# hónapja} other {# hónapja}}", - "person_age_years": "{years, plural, other {# éve}}", - "person_birthdate": "Született: {date}", - "person_hidden": "{name}{hidden, select, true { (rejtett)} other {}}", - "person_recognized": "Személy felismerve", - "person_selected": "Személy kiválasztva", - "photo_shared_all_users": "Úgy tűnik, hogy már mindenkivel megosztottad a fényképeidet, vagy nincs senki, akivel meg tudnád osztani.", - "photos": "Fényképek", - "photos_and_videos": "Fényképek és videók", - "photos_count": "{count, plural, one {{count, number} fotó} other {{count, number} fotó}}", - "photos_from_previous_years": "Fényképek az előző évekből", - "photos_only": "Csak képek", - "pick_a_location": "Hely választása", - "pick_custom_range": "Egyedi tartomány", - "pick_date_range": "Válasszon egy dátumtartományt", - "pin_code_changed_successfully": "Sikeres PIN kód változtatás", - "pin_code_reset_successfully": "Sikeres PIN kód visszaállítás", - "pin_code_setup_successfully": "Sikeres PIN kód beállítás", - "pin_verification": "PIN kód megerősítése", - "place": "Hely", - "places": "Helyek", - "places_count": "{count, plural, one {{count, number} helyszín} other {{count, number} helyszín}}", - "play": "Lejátszás", - "play_memories": "Emlékek lejátszása", - "play_motion_photo": "Mozgókép lejátszása", - "play_or_pause_video": "Videó elindítása vagy megállítása", - "play_original_video": "Eredeti videó lejátszása", - "play_original_video_setting_description": "A rendszer az eredeti videók lejátszását részesíti előnyben a transzkódolt verziókkal szemben. Ha az eredeti fájl nem kompatibilis, előfordulhat, hogy nem játszható le megfelelően.", - "play_transcoded_video": "Transzkódolt videó lejátszása", - "please_auth_to_access": "Kérlek jelentkezz be a hozzáféréshez", - "port": "Port", - "preferences_settings_subtitle": "Alkalmazásbeállítások kezelése", - "preferences_settings_title": "Beállítások", - "preparing": "Előkészítés", - "preset": "Előre definiált", - "preview": "Előnézet", - "previous": "Előző", - "previous_memory": "Előző emlék", - "previous_or_next_day": "Nap előre/hátra", - "previous_or_next_month": "Hónap előre/hátra", - "previous_or_next_photo": "Fotó előre/hátra", - "previous_or_next_year": "Év előre/hátra", - "primary": "Elsődleges", - "privacy": "Magánszféra", - "profile": "Profil", - "profile_drawer_app_logs": "Naplók", - "profile_drawer_client_server_up_to_date": "A kliens és a szerver is naprakész", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Csak olvasható mód engedélyezve. A kilépéshez hosszan nyomja meg a felhasználói avatar ikont.", - "profile_image_of_user": "{user} profilképe", - "profile_picture_set": "Profilkép beállítva.", - "public_album": "Nyilvános album", - "public_share": "Nyilvános megosztás", - "purchase_account_info": "Támogató", - "purchase_activated_subtitle": "Köszönjük, hogy támogattad az Immich-et és a nyílt forráskódú szoftvereket", - "purchase_activated_time": "Aktiválva ekkor: {date}", - "purchase_activated_title": "Kulcs sikeresen aktiválva", - "purchase_button_activate": "Aktiválás", - "purchase_button_buy": "Vásárlás", - "purchase_button_buy_immich": "Vásárold meg az Immich-et", - "purchase_button_never_show_again": "Soha többé ne mutassa", - "purchase_button_reminder": "Emlékeztessen 30 nap múlva", - "purchase_button_remove_key": "Kulcs eltávolítása", - "purchase_button_select": "Kiválaszt", - "purchase_failed_activation": "Sikertelen aktiválás! A helyes termékkulcsot az email fiókodban találhatod meg!", - "purchase_individual_description_1": "Egy magánszemélynek", - "purchase_individual_description_2": "Támogató állapot", - "purchase_individual_title": "Magánszemély", - "purchase_input_suggestion": "Van egy termékkulcsod? Add meg a kulcsot alább", - "purchase_license_subtitle": "Az Immich megvásárlásával támogasd a szolgáltatás folyamatos fejlesztését", - "purchase_lifetime_description": "Élettartamra szóló vásárlás", - "purchase_option_title": "VÁSÁRLÁSI LEHETŐSÉGEK", - "purchase_panel_info_1": "Az Immich készítése sok időt és erőfeszítést igényel, ezért főállásban foglalkoztatunk szoftvermérnököket, hogy annyira jó programmá tegyük, amennyire csak lehet. Küldetésünk, hogy a nyílt forráskódú szoftver és etikus üzleti gyakorlat fenntartható bevételi forrás legyen a fejlesztőknek, és hogy létrehozzunk egy magánszférát tiszteletben tartó ökoszisztémát, ami valódi alternatíváját jelenti a felhasználókat kizsákmányoló felhőalapú szolgáltatásoknak.", - "purchase_panel_info_2": "Mivel elkötelezettek vagyunk amellett, hogy ne vezessünk be csak pénzért elérhető extrákat, ezért ez a vásárlás nem biztosít új funkciókat az Immich-ben. Az Immich folyamatos fejlesztését az olyan felhasználók támogatására építjük mint Te.", - "purchase_panel_title": "Támogasd a projektet", - "purchase_per_server": "Szerverenként", - "purchase_per_user": "Felhasználónként", - "purchase_remove_product_key": "Termékkulcs eltávolítása", - "purchase_remove_product_key_prompt": "Biztosan el szeretnéd távolítani a termékkulcsot?", - "purchase_remove_server_product_key": "Szerver termékkulcs eltávolítása", - "purchase_remove_server_product_key_prompt": "Biztosan el szeretnéd távolítani a szerver termékkulcsot?", - "purchase_server_description_1": "Az egész szerverre", - "purchase_server_description_2": "Támogató státusz", - "purchase_server_title": "Szerver", - "purchase_settings_server_activated": "A szerver termékkulcsot az admin kezeli", - "query_asset_id": "Lekérdezési eszköz azonosítója", - "queue_status": "Feldolgozva {count}/{total}", - "rate_asset": "Elem értékelése", - "rating": "Értékelés csillagokkal", - "rating_clear": "Értékelés törlése", - "rating_count": "{count, plural, one {# csillag} other {# csillag}}", - "rating_description": "Exif értékelés megjelenítése az infópanelen", - "rating_set": "Értékelés beállítva: {rating, plural, one {# csillag} other {# csillag}}", - "reaction_options": "Reakció lehetőségek", - "read_changelog": "Változásnapló elolvasása", - "readonly_mode_disabled": "Csak olvasható mód kikapcsolva", - "readonly_mode_enabled": "Csak olvasható mód bekapcsolva", - "ready_for_upload": "Készen áll a feltöltésre", - "reassign": "Hozzárendel", - "reassigned_assets_to_existing_person": "{count, plural, other {# elem}} hozzárendelve{name, select, null { egy létező személyhez} other {: {name}}}", - "reassigned_assets_to_new_person": "{count, plural, other {# elem}} hozzárendelve egy új személyhez", - "reassing_hint": "Kijelölt elemek létező személyhez rendelése", - "recent": "Friss", - "recent-albums": "Legutóbbi albumok", - "recent_searches": "Legutóbbi keresések", - "recently_added": "Nemrég hozzáadott", - "recently_added_page_title": "Nemrég hozzáadott", - "recently_taken": "Nemrég készített", - "recently_taken_page_title": "Nemrég készített", - "refresh": "Frissítés", - "refresh_encoded_videos": "Átkódolt videók frissítése", - "refresh_faces": "Arcok frissítése", - "refresh_metadata": "Metaadatok frissítése", - "refresh_thumbnails": "Bélyegképek frissítése", - "refreshed": "Frissítve", - "refreshes_every_file": "Minden létező és új fájl újraolvasása", - "refreshing_encoded_video": "Átkódolt videók frissítése folyamatban", - "refreshing_faces": "Arcok frissítése folyamatban", - "refreshing_metadata": "Metaadatok frissítése folyamatban", - "regenerating_thumbnails": "Bélyegképek újragenerálása folyamatban", - "remote": "Távoli", - "remote_assets": "Távoli elemek", - "remote_media_summary": "Távoli médiaösszefoglaló", - "remove": "Eltávolítás", - "remove_assets_album_confirmation": "Biztosan el szeretnél távolítani {count, plural, one {# elemet} other {# elemet}} az albumból?", - "remove_assets_shared_link_confirmation": "Biztosan el szeretnél távolítani {count, plural, one {# elemet} other {# elemet}} ebből a megosztott linkből?", - "remove_assets_title": "Elemek eltávolítása?", - "remove_custom_date_range": "Egyéni időintervallum eltávolítása", - "remove_deleted_assets": "Törölt elemek eltávolítása", - "remove_from_album": "Eltávolítás az albumból", - "remove_from_album_action_prompt": "{count} eltávolítva az albumból", - "remove_from_favorites": "Eltávolítás a kedvencekből", - "remove_from_lock_folder_action_prompt": "{count} eltávolítva a zárolt mappából", - "remove_from_locked_folder": "Eltávolítás a zárolt mappából", - "remove_from_locked_folder_confirmation": "Biztosan ki szeretnéd venni ezeket a fotókat és videókat a zárolt mappából? Láthatóak lesznek a könyvtáradban.", - "remove_from_shared_link": "Eltávolítás a megosztott linkből", - "remove_memory": "Emlék eltávolítása", - "remove_photo_from_memory": "Kép eltávolítása az emlékből", - "remove_tag": "Címke eltávolítása", - "remove_url": "URL eltávolítása", - "remove_user": "Felhasználó eltávolítása", - "removed_api_key": "API kulcs eltávolítva: {name}", - "removed_from_archive": "Archívumból eltávolítva", - "removed_from_favorites": "Kedvencekből eltávolítva", - "removed_from_favorites_count": "A kedvencekből {count, plural, other {# elem}} eltávolítva", - "removed_memory": "Eltávolított emlék", - "removed_photo_from_memory": "Fotó törölve az emlékből", - "removed_tagged_assets": "Címke eltávolítva {count, plural, one {# elemről} other {# elemről}}", - "rename": "Átnevezés", - "repair": "Javítás", - "repair_no_results_message": "A nem nyilvántartott és a hiányzó fájlok itt jelennek meg", - "replace_with_upload": "Csere feltöltéssel", - "repository": "Tároló", - "require_password": "Jelszó megadása szükséges", - "require_user_to_change_password_on_first_login": "A felhasználónak kötelező megváltoztatnia a jelszavát az első bejelentkezéskor", - "rescan": "Újraszkennelés", - "reset": "Visszaállítás", - "reset_password": "Jelszó visszaállítása", - "reset_people_visibility": "Személyek láthatóságának visszaállítása", - "reset_pin_code": "PIN kód visszaállítása", - "reset_pin_code_description": "Ha elfelejtetted a PIN-kódod, vedd fel a kapcsolatot a szerver rendszergazdájával, hogy visszaállíthassa azt", - "reset_pin_code_success": "PIN kód sikeresen visszaállítva", - "reset_pin_code_with_password": "A PIN kódod mindig visszaállíthatod a jelszavaddal", - "reset_sqlite": "SQLite adatbázis visszaállítása", - "reset_sqlite_confirmation": "Biztosan vissza szeretnéd állítani az SQLite adatbázist? Az adatok újraszinkronizálásához ki kell jelentkezed, majd újra be kell lépned", - "reset_sqlite_success": "SQLite adatbázis sikeresen visszaállítva", - "reset_to_default": "Visszaállítás alapállapotba", - "resolution": "Felbontás", - "resolve_duplicates": "Duplikátumok feloldása", - "resolved_all_duplicates": "Minden duplikátum feloldása", - "restore": "Visszaállít", - "restore_all": "Minden visszaállítása", - "restore_trash_action_prompt": "{count} visszaállítva a lomtárból", - "restore_user": "Felhasználó visszaállítása", - "restored_asset": "Visszaállított elem", - "resume": "Folytatás", - "resume_paused_jobs": "Folytatás {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Feltöltés újrapróbálása", - "review_duplicates": "Duplikátumok áttekintése", - "review_large_files": "Nagy fájlok áttekintése", - "role": "Jogkör", - "role_editor": "Szerkesztő", - "role_viewer": "Megjelenítő", - "running": "Futó", - "save": "Mentés", - "save_to_gallery": "Mentés a galériába", - "saved": "Mentve", - "saved_api_key": "API kulcs elmentve", - "saved_profile": "Profil elmentve", - "saved_settings": "Elmentett beállítások", - "say_something": "Szólj hozzá", - "scaffold_body_error_occurred": "Hiba történt", - "scan": "Átfésül", - "scan_all_libraries": "Minden képtár átfésülése", - "scan_library": "Beolvasás", - "scan_settings": "Átfésülési beállítások", - "scanning": "Átfésülés folyamatban", - "scanning_for_album": "Albumok átfésülése...", - "search": "Keresés", - "search_albums": "Albumok keresése", - "search_by_context": "Keresés tartalom alapján", - "search_by_description": "Keresés leírás alapján", - "search_by_description_example": "Túrázós nap Szapában", - "search_by_filename": "Keresés fájlnév vagy kiterjesztés alapján", - "search_by_filename_example": "például IMG_1234.JPG vagy PNG", - "search_by_ocr": "Keresés szövegfelismeréssel (OCR)", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Keresés objektívmodell alapján...", - "search_camera_make": "Kameragyártó keresése...", - "search_camera_model": "Kameramodell keresése...", - "search_city": "Város keresése...", - "search_country": "Ország keresése...", - "search_filter_apply": "Szűrő alkalmazása", - "search_filter_camera_title": "Válaszd ki a kamera típusát", - "search_filter_date": "Dátum", - "search_filter_date_interval": "{start} - {end}", - "search_filter_date_title": "Válassz dátum intervallumot", - "search_filter_display_option_not_in_album": "Nincs albumban", - "search_filter_display_options": "Megjelenítési beállítások", - "search_filter_filename": "Keresés fájlnév alapján", - "search_filter_location": "Hely", - "search_filter_location_title": "Válassz helyet", - "search_filter_media_type": "Média típus", - "search_filter_media_type_title": "Válassz média típust", - "search_filter_ocr": "Keresés szövegfelismeréssel (OCR)", - "search_filter_people_title": "Válassz embereket", - "search_filter_star_rating": "Értékelés", - "search_for": "Keresés", - "search_for_existing_person": "Már meglévő személy keresése", - "search_no_more_result": "Nincs több találat", - "search_no_people": "Nincs személy", - "search_no_people_named": "Nincs \"{name}\" nevű személy", - "search_no_result": "Nincs találat, próbálj más kulcsszavakkal keresni", - "search_options": "Keresési lehetőségek", - "search_page_categories": "Kategóriák", - "search_page_motion_photos": "Mozgóképek", - "search_page_no_objects": "Nincs információ a tárgyakról", - "search_page_no_places": "Nincs információ a helyekről", - "search_page_screenshots": "Képernyőképek", - "search_page_search_photos_videos": "Keresés a fotóid és videóid közt", - "search_page_selfies": "Szelfik", - "search_page_things": "Dolgok", - "search_page_view_all_button": "Összes megjelenítése", - "search_page_your_activity": "Tevékenységeid", - "search_page_your_map": "Térképed", - "search_people": "Személyek keresése", - "search_places": "Helyek keresése", - "search_rating": "Keresés értékelés szerint...", - "search_result_page_new_search_hint": "Új keresés", - "search_settings": "Beállítások keresése", - "search_state": "Megye/Állam keresése...", - "search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz ", - "search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés", - "search_tags": "Címkék keresése...", - "search_timezone": "Időzóna keresése...", - "search_type": "Típus keresése", - "search_your_photos": "Keresés", - "searching_locales": "Helyszín keresése...", - "second": "Másodperc", - "see_all_people": "Minden személy megtekintése", - "select": "Kiválasztás", - "select_album": "Album kiválasztása", - "select_album_cover": "Albumborító kiválasztása", - "select_albums": "Albumok kiválasztása", - "select_all": "Összes kijelölése", - "select_all_duplicates": "Minden duplikátum kijelölése", - "select_all_in": "Összes kijelölése itt: {group}", - "select_avatar_color": "Avatár színének választása", - "select_count": "{count, plural, one {# kiválasztása} other {# kiválasztása}}", - "select_cutoff_date": "Határdátum választása", - "select_face": "Arc kiválasztása", - "select_featured_photo": "Alapértelmezett fénykép kiválasztása", - "select_from_computer": "Kiválasztás a számítógépről", - "select_keep_all": "'Megtart' kijelölése", - "select_library_owner": "Válaszd ki a képtár tulajdonosát", - "select_new_face": "Új arc választása", - "select_people": "Személyek kiválasztása", - "select_person": "Személy kiválasztása", - "select_person_to_tag": "Válassz ki egy személyt a megjelöléshez", - "select_photos": "Fotók választása", - "select_trash_all": "'Lomtár' kijelölése", - "select_user_for_sharing_page_err_album": "Az album létrehozása sikertelen", - "selected": "Kiválasztott", - "selected_count": "{count, plural, other {# kiválasztva}}", - "selected_gps_coordinates": "Kiválasztott GPS kordináták", - "send_message": "Üzenet küldése", - "send_welcome_email": "Üdvözlő email küldése", - "server_endpoint": "Szerver végpont", - "server_info_box_app_version": "Alkalmazás verzió", - "server_info_box_server_url": "Szerver URL", - "server_offline": "A szerver nem elérhető", - "server_online": "A szerver elérhető", - "server_privacy": "Szerver biztonság", - "server_restarting_description": "Az oldal pillanatokon belül frissül.", - "server_restarting_title": "A szerver újraindul", - "server_stats": "Szerver statisztikák", - "server_update_available": "Szerverfrissítés érhető el", - "server_version": "Szerver verzió", - "set": "Beállít", - "set_as_album_cover": "Beállítás albumborítóként", - "set_as_featured_photo": "Beállítás kiemelt fotónak", - "set_as_profile_picture": "Beállítás profilképként", - "set_date_of_birth": "Születési dátum beállítása", - "set_profile_picture": "Profilkép beállítása", - "set_slideshow_to_fullscreen": "Diavetítés teljes képernyőre állítása", - "set_stack_primary_asset": "Beállítás elsődleges elemként", - "setting_image_viewer_help": "Az Elem Megjelenítő először a kis bélyegképet tölti be, aztán a közepes méretű előnézetet (ha elérhető), végül az eredetit (ha elérhető).", - "setting_image_viewer_original_subtitle": "Engedélyezi az eredeti teljes felbontású kép betöltését (nagy!). Kikapcsolva csökkenti az adathasználatot (a neten és az eszköz gyorsítótárán is).", - "setting_image_viewer_original_title": "Eredeti kép betöltése", - "setting_image_viewer_preview_subtitle": "Engedélyezi a közepes felbontású kép betöltését. Kikapcsolva vagy az eredeti kép töltődik be, vagy csak a bélyegkép.", - "setting_image_viewer_preview_title": "Előnézet betöltése", - "setting_image_viewer_title": "Képek", - "setting_languages_apply": "Alkalmaz", - "setting_languages_subtitle": "Az alkalmazás nyelvének megváltoztatása", - "setting_notifications_notify_failures_grace_period": "Értesítés a háttérben történő mentés hibáiról: {duration}", - "setting_notifications_notify_hours": "{count} óra", - "setting_notifications_notify_immediately": "azonnal", - "setting_notifications_notify_minutes": "{count} perc", - "setting_notifications_notify_never": "soha", - "setting_notifications_notify_seconds": "{count} másodperc", - "setting_notifications_single_progress_subtitle": "Részletes feltöltési folyamat információ minden elemről", - "setting_notifications_single_progress_title": "Mutassa a háttérben történő mentés részletes folyamatát", - "setting_notifications_subtitle": "Értesítési beállítások módosítása", - "setting_notifications_total_progress_subtitle": "Átfogó feltöltési folyamat (kész/összes elem)", - "setting_notifications_total_progress_title": "Mutassa a háttérben történő mentés teljes folyamatát", - "setting_video_viewer_auto_play_subtitle": "A videók automatikus lejátszása megnyitáskor", - "setting_video_viewer_auto_play_title": "Videók automatikus lejátszása", - "setting_video_viewer_looping_title": "Ismétlés", - "setting_video_viewer_original_video_subtitle": "A szerverről történő videólejátszás során az eredeti videó lejátszása még akkor is, ha van optimalizált, átkódolt verzió. Akadozó lejátszást eredményezhet. A helyi eszközön eleve elérhető videókat mindenképpen eredeti minőségben játszuk le.", - "setting_video_viewer_original_video_title": "Mindig az eredeti videó lejátszása", - "settings": "Beállítások", - "settings_require_restart": "Ennek a beállításnak az érvénybe lépéséhez indítsd újra az Immich-et", - "settings_saved": "Beállítások elmentve", - "setup_pin_code": "PIN kód beállítása", - "share": "Megosztás", - "share_action_prompt": "{count} elem megosztva", - "share_add_photos": "Fotók hozzáadása", - "share_assets_selected": "{count} kiválasztva", - "share_dialog_preparing": "Előkészítés...", - "share_link": "Link megosztása", - "shared": "Megosztva", - "shared_album_activities_input_disable": "Hozzászólások kikapcsolva", - "shared_album_activity_remove_content": "Törölni szeretnéd ezt a tevékenységet?", - "shared_album_activity_remove_title": "Tevékenység törlése", - "shared_album_section_people_action_error": "Hiba az albummal kapcsolatos kilépés/eltávolítás közben", - "shared_album_section_people_action_leave": "Felhasználó eltávolítása az albumból", - "shared_album_section_people_action_remove_user": "Felhasználó eltávolítása az albumból", - "shared_album_section_people_title": "EMBEREK", - "shared_by": "Megosztotta", - "shared_by_user": "{user} osztotta meg", - "shared_by_you": "Te osztottad meg", - "shared_from_partner": "{partner} fényképei", - "shared_intent_upload_button_progress_text": "{current} / {total} feltöltve", - "shared_link_app_bar_title": "Megosztott linkek", - "shared_link_clipboard_copied_massage": "Vágólapra másolva", - "shared_link_clipboard_text": "Link: {link}\nJelszó: {password}", - "shared_link_create_error": "Hiba a megosztott link létrehozásakor", - "shared_link_custom_url_description": "Hozzáférés ehhez a megosztott linkhez egyedi URL címen keresztül", - "shared_link_edit_description_hint": "Add meg a megosztás leírását", - "shared_link_edit_expire_after_option_day": "1 nap", - "shared_link_edit_expire_after_option_days": "{count} nap", - "shared_link_edit_expire_after_option_hour": "1 óra", - "shared_link_edit_expire_after_option_hours": "{count} óra", - "shared_link_edit_expire_after_option_minute": "1 perc", - "shared_link_edit_expire_after_option_minutes": "{count} perc", - "shared_link_edit_expire_after_option_months": "{count} hónap", - "shared_link_edit_expire_after_option_year": "{count} év", - "shared_link_edit_password_hint": "Add meg a megosztáshoz tartozó jelszót", - "shared_link_edit_submit_button": "Link frissítése", - "shared_link_error_server_url_fetch": "A szerver címét nem lehet betölteni", - "shared_link_expires_day": "{count} nap múlva lejár", - "shared_link_expires_days": "{count} nap múlva lejár", - "shared_link_expires_hour": "{count} óra múlva lejár", - "shared_link_expires_hours": "{count} óra múlva lejár", - "shared_link_expires_minute": "{count} perc múlva lejár", - "shared_link_expires_minutes": "{count} perc múlva lejár", - "shared_link_expires_never": "Nem jár le", - "shared_link_expires_second": "{count} másodperc múlva lejár", - "shared_link_expires_seconds": "{count} másodperc múlva lejár", - "shared_link_individual_shared": "Egyénileg megosztva", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Megosztott linkek kezelése", - "shared_link_options": "Megosztott link beállításai", - "shared_link_password_description": "Jelszó megadása szükséges ehhez a megosztott linkhez", - "shared_links": "Megosztott linkek", - "shared_links_description": "Fényképek és videók megosztása linkkel", - "shared_photos_and_videos_count": "{assetCount, plural, other {# megosztott kép és videó.}}", - "shared_with_me": "Velem megosztva", - "shared_with_partner": "Megosztva {partner} partnereddel", - "sharing": "Megosztás", - "sharing_enter_password": "Add meg a jelszót az oldal megtekintéséhez.", - "sharing_page_album": "Megosztott albumok", - "sharing_page_description": "Megosztott albumok létrehozásával fényképeket és videókat oszthatsz meg az ismerőseiddel.", - "sharing_page_empty_list": "ÜRES LISTA", - "sharing_sidebar_description": "Megosztás link megjelenítése az oldalsávban", - "sharing_silver_appbar_create_shared_album": "Új megosztott album", - "sharing_silver_appbar_share_partner": "Megosztás partnerrel", - "shift_to_permanent_delete": "nyomd meg a ⇧ nyilat az elem végleges törléséhez", - "show_album_options": "Album beállítások megjelenítése", - "show_albums": "Albumok megjelenítése", - "show_all_people": "Minden személy megjelenítése", - "show_and_hide_people": "Személyek megjelenítése és elrejtése", - "show_file_location": "Fájl helyének megjelenítése", - "show_gallery": "Galéria megjelenítése", - "show_hidden_people": "Rejtett személyek megjelenítése", - "show_in_timeline": "Mutatás az idővonalon", - "show_in_timeline_setting_description": "Ennek a felhasználónak a képei és videói jelenjenek meg az idővonaladon", - "show_keyboard_shortcuts": "Billentyűparancsok megjelenítése", - "show_metadata": "Metaadatok megjelenítése", - "show_or_hide_info": "Információk megjelenítése vagy elrejtése", - "show_password": "Jelszó megjelenítése", - "show_person_options": "Személy beállítások megjelenítése", - "show_progress_bar": "Folyamatjelző megjelenítése", - "show_schema": "Séma megjelenítése", - "show_search_options": "Keresési beállítások megjelenítése", - "show_shared_links": "Megosztott linkek megjelenítése", - "show_slideshow_transition": "Vetítés áttűnési effektus megjelenítése", - "show_supporter_badge": "Támogató jelvény", - "show_supporter_badge_description": "Támogató jelvény megjelenítése", - "show_text_recognition": "Mutasd a szövegfelismerést", - "show_text_search_menu": "Mutasd a szövegkeresési menüt", - "shuffle": "Véletlenszerű", - "sidebar": "Oldalsáv", - "sidebar_display_description": "Nézet link megjelenítése az oldalsávban", - "sign_out": "Kijelentkezés", - "sign_up": "Regisztráció", - "size": "Méret", - "skip_to_content": "Ugrás a tartalomhoz", - "skip_to_folders": "Ugrás a mappákhoz", - "skip_to_tags": "Ugrás a címkékhez", - "slideshow": "Diavetítés", - "slideshow_repeat": "Diavetítés ismétlése", - "slideshow_repeat_description": "Ha a diavetítés véget ér, újraindul az elejétől", - "slideshow_settings": "Diavetítés beállításai", - "sort_albums_by": "Albumok rendezése...", - "sort_created": "Létrehozás dátuma", - "sort_items": "Elemek száma", - "sort_modified": "Módosítás dátuma", - "sort_newest": "Legújabb fotó", - "sort_oldest": "Legrégebbi fénykép", - "sort_people_by_similarity": "Emberek hasonlóság szerinti rendezése", - "sort_recent": "Legújabb fénykép", - "sort_title": "Cím", - "source": "Forrás", - "stack": "Kollázs", - "stack_action_prompt": "{count} egymásra helyezve", - "stack_duplicates": "Duplikátumok csoportosítása", - "stack_select_one_photo": "Válassz egy fő képet a csoportból", - "stack_selected_photos": "Kiválasztott fényképek csoportosítása", - "stacked_assets_count": "{count, plural, other {# elem}} csoportosítva", - "stacktrace": "Hiba leírása", - "start": "Elindít", - "start_date": "Kezdő dátum", - "start_date_before_end_date": "A kezdeti dátumnak a befejezési dátum előtt kell lennie", - "state": "Megye/Állam", - "status": "Állapot", - "stop_casting": "Vetítés megszüntetése", - "stop_motion_photo": "Stop motion kép", - "stop_photo_sharing": "Megszünteted fotóid megosztását?", - "stop_photo_sharing_description": "{partner} mostantól nem fog tudni hozzáférni a fényképeidhez.", - "stop_sharing_photos_with_user": "Fényképeid megosztásának megszüntetése ezzel a felhasználóval", - "storage": "Tárhely", - "storage_label": "Tárhely címke", - "storage_quota": "Tárhely kvóta", - "storage_usage": "{used}/{available} használatban", - "submit": "Beküldés", - "success": "Siker", - "suggestions": "Javaslatok", - "sunrise_on_the_beach": "Napkelte a tengerparton", - "support": "Támogatás", - "support_and_feedback": "Támogatás és visszajelzés", - "support_third_party_description": "Az Immich telepítésedet egy harmadik fél csomagolta. Mivel elképzelhető, hogy az esetlegesen felmerülő problémákat ez a csomag okozza, ezért kérjük, először velük közöld a problémákat az alábbi linkek segítségével.", - "swap_merge_direction": "Egyesítés irányának megfordítása", - "sync": "Szinkronizálás", - "sync_albums": "Albumok szinkronizálása", - "sync_albums_manual_subtitle": "Összes feltöltött fotó és videó szinkronizálása a kiválasztott albumokba", - "sync_local": "Helyi szinkronizálása", - "sync_remote": "Távoli szinkronizálása", - "sync_status": "Szinkronizálás állapota", - "sync_status_subtitle": "Szinkronizálás megtekintése és kezelése", - "sync_upload_album_setting_subtitle": "Fotók és videók létrehozása és szinkronizálása a kiválasztott Immich albumokba", - "tag": "Címke", - "tag_assets": "Elemek címkézése", - "tag_created": "Létrehozott címke: {tag}", - "tag_feature_description": "Fényképek és videók böngészése a címkék témája szerint csoportosítva", - "tag_not_found_question": "Nem találod a címkét? Hozz létre egy új címkét", - "tag_people": "Emberek címkézése", - "tag_updated": "Frissített címke: {tag}", - "tagged_assets": "{count, plural, one {# elem} other {# elem}} felcímkézve", - "tags": "Címkék", - "tap_to_run_job": "Érintsd meg a feladat futtatásához", - "template": "Sablon", - "text_recognition": "Szövegfelismerés", - "theme": "Téma", - "theme_selection": "Témaválasztás", - "theme_selection_description": "A böngésző beállításának megfelelően automatikusan használjon világos vagy sötét témát", - "theme_setting_asset_list_storage_indicator_title": "Tárhely ikon megjelenítése elemeken", - "theme_setting_asset_list_tiles_per_row_title": "Elemek száma soronként ({count})", - "theme_setting_colorful_interface_subtitle": "Alapértelmezett szín használata a háttérben lévő felületekhez.", - "theme_setting_colorful_interface_title": "Színes felhasználói felület", - "theme_setting_image_viewer_quality_subtitle": "Részletes képmegjelenítő minőségének beállítása", - "theme_setting_image_viewer_quality_title": "Képmegjelenítő minősége", - "theme_setting_primary_color_subtitle": "Válassz egy színt az alapértelmezett műveletekhez és kiemelésekhez.", - "theme_setting_primary_color_title": "Alapértelmezett szín", - "theme_setting_system_primary_color_title": "Rendszerszínek használata", - "theme_setting_system_theme_switch": "Automatikus (követi a rendszer témáját)", - "theme_setting_theme_subtitle": "Alkalmazás témájának választása", - "theme_setting_three_stage_loading_subtitle": "A háromlépcsős betöltés javíthatja a betöltési teljesítményt, de jelentősen növeli a hálózati forgalmat", - "theme_setting_three_stage_loading_title": "Háromlépcsős betöltés engedélyezése", - "then": "Akkor", - "they_will_be_merged_together": "Egyesítve lesznek", - "third_party_resources": "Harmadik féltől származó források", - "time": "Idő", - "time_based_memories": "Emlékek idő alapján", - "time_based_memories_duration": "Másodpercek száma, egyes képek mutatására.", - "timeline": "Idővonal", - "timezone": "Időzóna", - "to_archive": "Archiválás", - "to_change_password": "Jelszó megváltoztatása", - "to_favorite": "Kedvenc", - "to_login": "Bejelentkezés", - "to_multi_select": "több elem kiválasztásához", - "to_parent": "Egy szinttel feljebb", - "to_select": "a kiválasztáshoz", - "to_trash": "Lomtárba helyezés", - "toggle_settings": "Beállítások átállítása", - "toggle_theme_description": "Téma váltása", - "total": "Összesen", - "total_usage": "Összesen használatban", - "trash": "Lomtár", - "trash_action_prompt": "{count} lomtárba helyezve", - "trash_all": "Mindet lomtárba", - "trash_count": "{count, number} elem lomtárba helyezése", - "trash_delete_asset": "Elem törlése / lomtárba helyezése", - "trash_emptied": "Lomtár kiürítve", - "trash_no_results_message": "Itt lesznek láthatóak a lomtárba tett képek és videók.", - "trash_page_delete_all": "Összes törlése", - "trash_page_empty_trash_dialog_content": "Ki szeretnéd üríteni a lomtárban lévő elemeket? Ezeket véglegesen eltávolítjuk az Immich-ből", - "trash_page_info": "A Lomátrba helyezett elemek {days} nap után véglegesen törlődnek", - "trash_page_no_assets": "A Lomtár üres", - "trash_page_restore_all": "Összes visszaállítása", - "trash_page_select_assets_btn": "Elemek kiválasztása", - "trash_page_title": "Lomtár ({count})", - "trashed_items_will_be_permanently_deleted_after": "A lomtárban lévő elemek véglegesen törlésre kerülnek {days, plural, other {# nap}} múlva.", - "trigger": "Feltétel", - "trigger_asset_uploaded": "Elem feltöltve", - "trigger_asset_uploaded_description": "Új elem feltöltésekor indul el", - "trigger_description": "Egy esemény, ami elindítja a folyamatot", - "trigger_person_recognized": "Személy felismerve", - "trigger_person_recognized_description": "Személy felismerésekor indul el", - "trigger_type": "Feltétel típusa", - "troubleshoot": "Hibaelhárítás", - "type": "Típus", - "unable_to_change_pin_code": "Sikertelen PIN kód változtatás", - "unable_to_check_version": "Az alkalmazás vagy a szerver verziója nem ellenőrizhető", - "unable_to_setup_pin_code": "Sikertelen PIN kód beállítás", - "unarchive": "Archívumból kivesz", - "unarchive_action_prompt": "{count} eltávolítva az Archívumból", - "unarchived_count": "{count, plural, other {# elem kivéve az archívumból}}", - "undo": "Visszavonás", - "unfavorite": "Kedvenc közül kivesz", - "unfavorite_action_prompt": "{count} eltávolítva a Kedvencekből", - "unhide_person": "Nem rejtett személy", - "unknown": "Ismeretlen", - "unknown_country": "Ismeretlen ország", - "unknown_date": "Ismeretlen dátum", - "unknown_year": "Ismeretlen Év", - "unlimited": "Korlátlan", - "unlink_motion_video": "Mozgókép leválasztása", - "unlink_oauth": "OAuth leválasztása", - "unlinked_oauth_account": "Leválasztott OAuth fiók", - "unmute_memories": "Emlékek némításának feloldása", - "unnamed_album": "Névtelen album", - "unnamed_album_delete_confirmation": "Biztosan törölni szeretnéd ezt az albumot?", - "unnamed_share": "Névtelen megosztás", - "unsaved_change": "Nem mentett változtatás", - "unselect_all": "Kijelölések megszüntetése", - "unselect_all_duplicates": "Duplikátumok kijelölésének megszüntetése", - "unselect_all_in": "Kijelölés megszüntetése itt: {group}", - "unstack": "Csoport szétbontása", - "unstack_action_prompt": "{count} egymásra helyezés megszüntetése", - "unstacked_assets_count": "{count, plural, other {# elemből}} álló csoport szétszedve", - "unsupported_field_type": "Nem támogatott mezőtípus", - "untagged": "Címke eltávolítva", - "untitled_workflow": "Névtelen folyamat", - "up_next": "Következik", - "update_location_action_prompt": "{count} elem pozíciójának frissítése a következővel:", - "updated_at": "Frissítve", - "updated_password": "Jelszó megváltoztatva", - "upload": "Feltöltés", - "upload_concurrency": "Párhuzamos feltöltés", - "upload_details": "Feltöltés állapota", - "upload_dialog_info": "Szeretnél mentést készíteni a kiválasztott elem(ek)ről a szerverre?", - "upload_dialog_title": "Elem feltöltése", - "upload_error_with_count": "Feltöltési hiba {count} elemnél", - "upload_errors": "Feltöltés befejezve {count, plural, other {# hibával}}, frissítsd az oldalt az újonnan feltöltött elemek megtekintéséhez.", - "upload_finished": "Feltöltés befejezve", - "upload_progress": "{remaining, number} hátra van - {processed, number}/{total, number} feldolgozva", - "upload_skipped_duplicates": "{count, plural, other {# duplikátum}} kihagyva", - "upload_status_duplicates": "Duplikátumok", - "upload_status_errors": "Hibák", - "upload_status_uploaded": "Feltöltve", - "upload_success": "Feltöltés sikeres, frissítsd az oldalt az újonnan feltöltött elemek megtekintéséhez.", - "upload_to_immich": "Feltöltés Immich-be ({count})", - "uploading": "Feltöltés folyamatban", - "uploading_media": "Média feltöltés folyamatban", - "url": "URL", - "usage": "Használat", - "use_biometric": "Biometrikus azonosítás használata", - "use_current_connection": "Jelenlegi kapcsolat használata", - "use_custom_date_range": "Szabadon megadott időintervallum használata", - "user": "Felhasználó", - "user_has_been_deleted": "Ez a felhasználó törlésre került.", - "user_id": "Felhasználó azonosítója", - "user_liked": "{user} felhasználónak {type, select, photo {ez a fénykép} video {ez a videó} asset {ez az elem} other {ez}} tetszik", - "user_pin_code_settings": "PIN kód", - "user_pin_code_settings_description": "PIN kód kezelése", - "user_privacy": "Felhasználói adatvédelem", - "user_purchase_settings": "Megvásárlás", - "user_purchase_settings_description": "Vásárlás kezelése", - "user_role_set": "{user} felhasználónak {role} jogkör biztosítása", - "user_usage_detail": "Felhasználó használati adatai", - "user_usage_stats": "Fiók használati statisztikái", - "user_usage_stats_description": "Fiók használati statisztikáinak megtekintése", - "username": "Felhasználónév", - "users": "Felhasználók", - "users_added_to_album_count": "{count, plural, one {# felhasználó} other {# felhasználó}} hozzáadva az albumhoz", - "utilities": "Segédeszközök", - "validate": "Ellenőrzés", - "validate_endpoint_error": "Kérlek, érvényes URL-t adj meg", - "validation_error": "Validációs hiba", - "variables": "Változók", - "version": "Verzió", - "version_announcement_closing": "Barátsággal, Alex", - "version_announcement_message": "Szia! Az Immich-nek elérhető egy új verziója. Kérjük, szánj időt a verzióinformáció elolvasására, hogy meggyőződj róla, hogy a beállításaid naprakészek, így elkerülj egy esetleges félrekonfigurálást. Különösen, ha WatchTower-t vagy más automatikus frissítési megoldást használsz.", - "version_history": "Verziótörténet", - "version_history_item": "{version} telepítve: {date}", - "video": "Videó", - "video_hover_setting": "Kisméretű videó elindítása, ha az egér az elem felé megy", - "video_hover_setting_description": "Ha az egér az elem felé megy, akkor induljon el a kisméretű videó lejátszása. Még ha ez az opció ki is van kapcsolva, a lejátszás akkor is elindítható a lejátszás gombbal.", - "videos": "Videók", - "videos_count": "{count, plural, one {# videó} other {# videó}}", - "videos_only": "Csak videók", - "view": "Megtekintés", - "view_album": "Album megtekintése", - "view_all": "Összes megtekintése", - "view_all_users": "Minden felhasználó megtekintése", - "view_asset_owners": "Elemtulajdonosok megtekintése", - "view_details": "Részletek megtekintése", - "view_in_timeline": "Megtekintés az idővonalon", - "view_link": "Link megtekintése", - "view_links": "Linkek megtekintése", - "view_name": "Megtekintés", - "view_next_asset": "Következő elem megtekintése", - "view_previous_asset": "Előző elem megtekintése", - "view_qr_code": "QR kód megtekintése", - "view_similar_photos": "Hasonló képek keresése", - "view_stack": "Csoport megtekintése", - "view_user": "Felhasználó megtekintése", - "viewer_remove_from_stack": "Eltávolítás a csoportból", - "viewer_stack_use_as_main_asset": "Fő elemnek beállítás", - "viewer_unstack": "Csoport megszüntetése", - "visibility_changed": "{count, plural, other {# személy}} láthatósága megváltozott", - "visual": "Vizuális", - "visual_builder": "Vizuális összerakó", - "waiting": "Várakozik", - "waiting_count": "Várakozik: {count}", - "warning": "Figyelmeztetés", - "week": "Hét", - "welcome": "Üdvözlünk", - "welcome_to_immich": "Üdvözöl az Immich", - "width": "Szélesség", - "wifi_name": "Wi-Fi neve", - "workflow_delete_prompt": "Biztosan törölni szeretnéd ezt a folyamatot?", - "workflow_deleted": "Folyamat törölve", - "workflow_description": "Folyamat leírása", - "workflow_info": "Folyamat részletei", - "workflow_json": "Folyamat JSON", - "workflow_json_help": "Itt módosíthatod a folyamatot JSON formátumban. A változásokat szinkronban tartjuk a grafikus felülettel.", - "workflow_name": "Folyamat neve", - "workflow_navigation_prompt": "Biztosan tovább szeretnél lépni a változások mentése nélkül?", - "workflow_summary": "Folyamat összefoglaló", - "workflow_update_success": "Folyamat sikeresen frissítve", - "workflow_updated": "Folyamat frissítve", - "workflows": "Folyamatok", - "workflows_help_text": "A folyamatok automatizált műveleteket hajtanak végre elemeken, indítási feltételek és szűrők alapján", - "wrong_pin_code": "Hibás PIN kód", - "year": "Év", - "years_ago": "{years, plural, one {# évvel} other {# évvel}} ezelőtt", - "yes": "Igen", - "you_dont_have_any_shared_links": "Nincsenek megosztott linkjeid", - "your_wifi_name": "A Wi-Fi hálózatod neve", - "zero_to_clear_rating": "0: értékelés eltávolítása", - "zoom_image": "Kép nagyítása", - "zoom_to_bounds": "Nagyítás a határokhoz" -} +{} diff --git a/i18n/hy.json b/i18n/hy.json index 1b12eb1cf0..0967ef424b 100644 --- a/i18n/hy.json +++ b/i18n/hy.json @@ -1,152 +1 @@ -{ - "about": "Մասին", - "account": "Հաշիվ", - "account_settings": "Հաշվի կարգավորումներ", - "acknowledge": "Ընդունել", - "action": "Գործողություն", - "action_common_update": "Թարմացնել", - "actions": "Գործողություններ", - "active": "Ակտիվ", - "add": "Ավելացնել", - "add_a_description": "Ավելացնել մեկնաբանություն", - "add_a_location": "Ավելացնել տեղ", - "add_a_name": "Ավելացնել անուն", - "add_a_title": "Ավելացնել վերագիր", - "add_birthday": "Ավելացնել ծննդյան ամսաթիվ", - "add_endpoint": "Ավելացնել վերջնակետ", - "add_location": "Ավելացնել տեղ", - "add_more_users": "Ավելացնել հավելյալ օգտատերեր", - "add_partner": "Ավելացնել գործընկեր", - "add_photos": "Ավելացնել նկարներ", - "added_to_archive": "Ավելացվել է արխիվում", - "added_to_favorites": "Ավելացվել է հավածներում", - "admin": { - "authentication_settings": "Նույնականացման կարգավորումներ", - "authentication_settings_disable_all": "Վստա՞հ եք, որ ցանկանում եք անջատել մուտքային մեթոդները։ Մուտքն ամբողջությամբ կանջատվի։" - }, - "back": "Հետ", - "backup_all": "Բոլոր", - "backup_controller_page_background_battery_info_link": "Ցույց տուր ինչպես", - "backup_controller_page_background_battery_info_ok": "Լավ", - "backup_controller_page_background_wifi": "Միայն Wi-Fi միացումում", - "backup_controller_page_created": "Ստեղծվել է {date}֊ին", - "backup_controller_page_uploading_file_info": "Վերբեռնվում է ֆայլի տեղեկությունները", - "biometric_auth_enabled": "Կենսաչափական նույնականցումը միացված է", - "change_display_order": "Փոփոխել ցուցադրման հաջորդականությունը", - "change_location": "Փոխել տեղը", - "change_name": "Փոխել անուն", - "change_password_form_reenter_new_password": "Կրկին մուտքագրել նոր գաղտնաբառը", - "change_pin_code": "Փոփոխել ՊԻՆ կոդը", - "city": "Քաղաք", - "client_cert_dialog_msg_confirm": "Լավ", - "client_cert_title": "SSL հաճախորդի հավաստագիր", - "color": "Գույն", - "common_create_new_album": "Ստեղծել նոր ալբոմ", - "control_bottom_app_bar_create_new_album": "Ստեղծել նոր ալբոմ", - "control_bottom_app_bar_delete_from_immich": "Ջնջել Immich֊ից", - "control_bottom_app_bar_delete_from_local": "Ջնջել սարքավորումից", - "control_bottom_app_bar_edit_location": "Փոխել Տեղը", - "control_bottom_app_bar_trash_from_immich": "Տեղափոխել աղբաման", - "country": "Երկիր", - "create_new": "ՍՏԵՂԾԵԼ ՆՈՐ", - "create_new_person": "Ստեղծել նոր անձ", - "create_shared_album_page_share_select_photos": "Ընտրե Նկարներ", - "curated_object_page_title": "Բաներ", - "current_pin_code": "Տվյալ ՊԻՆ կոդը", - "current_server_address": "Ընթացիկ սերվերի հասցե", - "daily_title_text_date": "E, MMM dd", - "dark": "Մութ", - "dark_theme": "Միացնել/անջատել մուգ տեսքը", - "day": "Օր", - "delete": "Ջնջել", - "delete_shared_link_dialog_title": "Ջնջել կիսված հղումը", - "download_notfound": "Ներբեռնվածը չի հայտնաբերվել", - "edit_location": "Փոխել տեղը", - "enter_wifi_name": "Մուտքագրել Wi-Fi անունը", - "exif_bottom_sheet_person_add_person": "Ավելացնել անուն", - "experimental_settings_new_asset_list_subtitle": "Ընթացքում է", - "failed_to_authenticate": "Նույնականացումը ձախողվել է", - "folder_not_found": "Թղթապանակը չի հայտնաբերվել", - "hi_user": "Բարեւ {name} ({email})", - "ignore_icloud_photos": "Անտեսել iCloud֊ի նկարները", - "invalid_date_format": "Անվավեր ամսաթվի ձևաչափ", - "ios_debug_info_last_sync_at": "Վերջին թարմացումը {dateTime}֊ին", - "language_no_results_title": "Լեզուներ չեն գտնվել", - "library_page_device_albums": "Սարքավորման ալբոմները", - "location_picker_choose_on_map": "Ընտրել քարտեզի վրա", - "login_form_endpoint_url": "Սերվերի վերջնակետի URL", - "login_form_save_login": "Մնալ մուտքագրված", - "login_password_changed_success": "Գաղտնաբառը հաջողությամբ փոփոխվել է", - "map_assets_in_bounds": "{count} նկարներ", - "map_location_picker_page_use_location": "Օգտագործել այս տեղը", - "map_location_service_disabled_title": "Տեղորոշման ծառայություններն անջատված են", - "map_no_location_permission_title": "Տեղորոշման թույլտվությունը մերժված է", - "map_settings_date_range_option_day": "Վերջին 24 ժամում", - "map_settings_date_range_option_days": "Վերջին {days} օրում", - "map_settings_date_range_option_years": "Վերջին {years} տարում", - "mark_as_read": "Նշել կարդացած", - "new_pin_code": "Նոր ՊԻՆ կոդ", - "on_this_device": "Այս սարքավորումում", - "partner_list_user_photos": "{}-ին նկարները", - "permission_onboarding_go_to_settings": "Գնալ կարգավորումներ", - "photos": "Նկարներ", - "pin_verification": "ՊԻՆ կոդի ստուգում", - "reset_pin_code": "Վերականգնել ՊԻՆ կոդը", - "save": "Պահե", - "save_to_gallery": "Պահպանել պատկերասրահում", - "scan_library": "Նայե", - "search": "Փնտրե", - "search_city": "Որոնե քաղաք…", - "search_filter_camera_title": "Ընտրել տեսախցիկի տեսակը", - "search_filter_date": "Ամսաթիվ", - "search_filter_date_interval": "{start} մինչեւ {end}", - "search_filter_display_option_not_in_album": "Ալբոմում չկա", - "search_filter_location": "Տեղ", - "search_filter_location_title": "Ընտրե տեղ", - "search_filter_media_type_title": "Ընտրել մեդիայի տեսակը", - "search_no_more_result": "Այլևս արդյունքներ չկան", - "search_no_people": "Ոչ մի անձ", - "search_page_motion_photos": "Շարժվող Նկարներ", - "select_photos": "Ընտրե նկարներ", - "setting_image_viewer_original_title": "Բեռնել բնագիր նկարը", - "setting_image_viewer_preview_title": "Բեռնել նախադիտման նկարը", - "setting_notifications_notify_never": "երբեք", - "setting_notifications_notify_seconds": "{count} վայրկյան", - "setting_video_viewer_original_video_title": "Ստիպել բնագիր տեսահոլովակը", - "share_add_photos": "Ավելացնել նկարներ", - "shared_album_activities_input_disable": "Մեկնաբանություններն անջատված են", - "shared_link_clipboard_copied_massage": "Պատճենահանված է", - "shared_link_edit_expire_after_option_day": "1 օր", - "shared_link_edit_expire_after_option_days": "{count} օր", - "shared_link_edit_expire_after_option_hour": "1 ժամ", - "shared_link_edit_expire_after_option_hours": "{count} ժամ", - "shared_link_edit_expire_after_option_minute": "1 րոպե", - "shared_link_edit_expire_after_option_minutes": "{count} րոպե", - "shared_link_edit_expire_after_option_months": "{count} ամիս", - "shared_link_edit_expire_after_option_year": "{count} տարի", - "shared_link_manage_links": "Կառավարել կիսված հղումները", - "shared_with_me": "Ինձ հետ կիսված", - "sharing_silver_appbar_create_shared_album": "Նոր կիսված ալբոմ", - "sharing_silver_appbar_share_partner": "Կիսել գործընկերոջ հետ", - "sort_oldest": "Ամենահին նկարը", - "sort_recent": "Ամենանոր նկարը", - "theme_setting_image_viewer_quality_title": "Նկարի դիտման որակ", - "theme_setting_system_primary_color_title": "Օգտագործել համակարգի գույնը", - "timezone": "Ժամային գոտի", - "to_trash": "Աղբ", - "trash": "Աղբ", - "trash_page_title": "Աղբ ({count})", - "type": "Տեսակ", - "unknown": "Անհայտ", - "unknown_country": "Անհայտ Երկիր", - "unknown_year": "Անհայտ Տարի", - "upload_status_errors": "Սխալներ", - "use_current_connection": "օգտագործել տվյալ կապը", - "version_announcement_closing": "Քո ընկերը՝ Ալեքսը", - "week": "Շաբաթ", - "welcome": "Բարի գալուստ", - "wrong_pin_code": "Սխալ ՊԻՆ կոդ", - "year": "Տարի", - "yes": "Այո", - "your_wifi_name": "Ձեր Wi-Fi անունը" -} +{} diff --git a/i18n/id.json b/i18n/id.json index 6f00e98867..0967ef424b 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -1,2356 +1 @@ -{ - "about": "Tentang", - "account": "Akun", - "account_settings": "Pengaturan Akun", - "acknowledge": "Mengerti", - "action": "Tindakan", - "action_common_update": "Perbarui", - "action_description": "Tindakan yang perlu dijalankan pada aset yang terfilter", - "actions": "Tindakan", - "active": "Aktif", - "active_count": "Aktif: {count}", - "activity": "Aktivitas", - "activity_changed": "Aktivitas {enabled, select, true {aktif} other {nonaktif}}", - "add": "Tambahkan", - "add_a_description": "Tambah keterangan", - "add_a_location": "Tambahkan lokasi", - "add_a_name": "Tambahkan nama", - "add_a_title": "Tambahkan judul", - "add_action": "Tambah tindakan", - "add_action_description": "Klik untuk menambahkan tindakan yang perlu dijalankan", - "add_assets": "Tambahkan aset", - "add_birthday": "Tambahkan tanggal lahir", - "add_endpoint": "Tambahkan titik akhir", - "add_exclusion_pattern": "Tambahkan pola pengecualian", - "add_filter": "Tambahkan filter", - "add_filter_description": "Klik untuk menambahkan kondisi filter", - "add_location": "Tambahkan lokasi", - "add_more_users": "Tambahkan lebih banyak pengguna", - "add_partner": "Tambahkan partner", - "add_path": "Tambahkan jalur", - "add_photos": "Tambahkan foto", - "add_tag": "Tambahkan tag", - "add_to": "Tambahkan ke…", - "add_to_album": "Tambahkan ke album", - "add_to_album_bottom_sheet_added": "Ditambahkan ke {album}", - "add_to_album_bottom_sheet_already_exists": "Sudah ada di {album}", - "add_to_album_bottom_sheet_some_local_assets": "Beberapa aset lokal tidak dapat ditambahkan ke album", - "add_to_album_toggle": "Masukkan ke {album} / Batalkan dari {album}", - "add_to_albums": "Tambahkan ke album", - "add_to_albums_count": "Tambahkan ke album ({count})", - "add_to_bottom_bar": "Tambahkan ke", - "add_to_shared_album": "Tambahkan ke album terbagi", - "add_upload_to_stack": "Tambahkan unggahan ke tumpukan", - "add_url": "Tambahkan URL", - "add_workflow_step": "Tambahkan langkah alur kerja", - "added_to_archive": "Ditambahkan ke arsip", - "added_to_favorites": "Ditambahkan ke favorit", - "added_to_favorites_count": "Ditambahkan {count, number} ke favorit", - "admin": { - "add_exclusion_pattern_description": "Tambahkan pola pengecualian. Glob menggunakan *, **, dan ? didukung. Untuk mengabaikan semua berkas dalam direktori apa pun bernama \"Raw\", gunakan \"**/Raw/**\". Untuk mengabaikan semua berkas berakhiran dengan \".tif\", gunakan \"**/*.tif\". Untuk mengabaikan jalur absolut, gunakan \"/jalur/untuk/diabaikan/**\".", - "admin_user": "Pengguna Admin", - "asset_offline_description": "Aset pustaka eksternal ini tidak ada di diska dan telah dipindahkan ke tempat sampah. Jika berkasnya dipindah dalam pustaka, periksa lini masa Anda untuk aset baru yang cocok. Untuk memulihkan aset ini, pastikan jalur berkas di bawah dapat diakses oleh Immich dan pindai pustaka.", - "authentication_settings": "Pengaturan Autentikasi", - "authentication_settings_description": "Kelola kata sandi, OAuth, dan pengaturan autentikasi lainnya", - "authentication_settings_disable_all": "Anda yakin untuk menonaktifkan semua cara login? Login akan dinonaktikan secara menyeluruh.", - "authentication_settings_reenable": "Untuk mengaktifkan ulang, gunakan Perintah Server.", - "background_task_job": "Tugas Latar Belakang", - "backup_database": "Buat Cadangan Basis Data", - "backup_database_enable_description": "Aktifkan pencadangan basis data", - "backup_keep_last_amount": "Jumlah cadangan untuk disimpan", - "backup_onboarding_1_description": "Simpan salinan cadangan di awan atau di lokasi fisik terpisah.", - "backup_onboarding_2_description": "Salinan lokal diperangkat yang berbeda. Termasuk berkas utama dan cadangannya disimpan secara lokal.", - "backup_onboarding_3_description": "jumlah salinan data, termasuk berkas asli yang termasuk 1 salinan diluar dan 2 salinan lokal.", - "backup_onboarding_description": "Disarankan menggunakan metode pencadangan 3-2-1untuk melindungi data anda. Anda disarankan untuk menyimpan salinan foto/video serta basis data Immich untuk memastikan solusi pencadangan secara menyeluruh.", - "backup_onboarding_footer": "Untuk informasi lebih lanjut terkait pencadangan Immich, silahkan mengunjungi dokumentasi.", - "backup_onboarding_parts_title": "Metode pencadangan 3-2-1 meliputi:", - "backup_onboarding_title": "Cadangkan", - "backup_settings": "Pengaturan Pencadangan Basis Data", - "backup_settings_description": "Kelola pengaturan pencadangan basis data.", - "cleared_jobs": "Tugas terselesaikan untuk: {job}", - "config_set_by_file": "Konfigurasi saat ini ditetapkan oleh berkas konfigurasi", - "confirm_delete_library": "Apakah Anda yakin ingin menghapus pustaka {library}?", - "confirm_delete_library_assets": "Apakah Anda yakin ingin menghapus pustaka ini? Ini akan menghapus {count, plural, one {# aset berisi} other {semua # aset berisi}} dari Immich dan tidak dapat diurungkan. Berkas akan tetap tersedia di diska.", - "confirm_email_below": "Untuk mengonfirmasi, ketik \"{email}\" di bawah", - "confirm_reprocess_all_faces": "Apakah Anda yakin ingin memproses semua wajah? Ini juga akan menghapus nama orang.", - "confirm_user_password_reset": "Apakah Anda yakin ingin mengatur ulang kata sandi {user}?", - "confirm_user_pin_code_reset": "Apakah Anda yakin ingin mereset kode PIN milik {user}?", - "copy_config_to_clipboard_description": "Salin konfigurasi sistem saat ini sebagai objek JSON ke papan klip", - "create_job": "Buat tugas", - "cron_expression": "Ekspresi cron", - "cron_expression_description": "Tetapkan interval pemindaian menggunakan format cron. Untuk informasi lebih lanjut, silakan merujuk misalnya ke Crontab Guru", - "cron_expression_presets": "Prasetel ekspresi cron", - "disable_login": "Nonaktifkan log masuk", - "duplicate_detection_job_description": "Jalankan pembelajaran mesin pada aset untuk mendeteksi gambar yang serupa. Bergantung pada Pencarian Pintar", - "exclusion_pattern_description": "Pola pengecualian memungkinkan Anda mengabaikan berkas dan folder ketika memindai pustaka Anda. Ini berguna jika Anda memiliki folder yang berisi berkas yang tidak ingin diimpor, seperti berkas RAW.", - "export_config_as_json_description": "Unduh konfigurasi sistem saat ini sebagai berkas JSON", - "external_libraries_page_description": "Halaman pustaka eksternal admin", - "face_detection": "Deteksi wajah", - "face_detection_description": "Deteksikan wajah dalam aset menggunakan pembelajaran mesin. Untuk video, hanya gambar kecilnya yang disertakan. \"Segarkan\" memproses (ulang) semua aset. \"Segarkan\" juga menghapus data wajah terkini. \"Hilang\" mengantrekan aset yang belum diproses. Wajah yang dideteksi akan diantrekan untuk Pengenalan Wajah setelah Pendeteksian Wajah selesai, mengelompokkan mereka dalam orang yang sudah ada atau yang baru.", - "facial_recognition_job_description": "Kelompokkan wajah yang telah dideteksi menjadi orang. Langkah ini berjalan setelah Deteksi Wajah selesai. \"Segarkan\" mengelompokkan (ulang) semua wajah. \"Hilang\" mengantrekan wajah yang belum ditetapkan dengan seseorang.", - "failed_job_command": "Perintah {command} gagal untuk tugas: {job}", - "force_delete_user_warning": "PERINGATAN: Ini akan segera menghapus pengguna dan semua asetnya. Ini tidak dapat diurungkan dan semua berkasnya tidak dapat dipulihkan.", - "image_format": "Format", - "image_format_description": "WebP menghasilkan ukuran berkas yang lebih kecil daripada JPEG, tetapi lebih lambat untuk dienkode.", - "image_fullsize_description": "Gambar berukuran penuh tanpa metadata, digunakan ketika diperbesar", - "image_fullsize_enabled": "Aktifkan pembuatan gambar berukuran penuh", - "image_fullsize_enabled_description": "Buat gambar berukuran penuh untuk format yang tidak ramah web. Ketika \"Utamakan pratinjau tersemat\" diaktifkan, pratinjau tersema digunakan secara langsung tanpa konversi. Tidak memengaruhi format ramah web seperti JPEG.", - "image_fullsize_quality_description": "Kualitas gambar berukuran penuh dari 1-100. Lebih tinggi lebih baik, tetapi menghasilkan berkas lebih besar.", - "image_fullsize_title": "Pengaturan Gambar Berukuran Penuh", - "image_prefer_embedded_preview": "Utamakan pratinjau tersemat", - "image_prefer_embedded_preview_setting_description": "Gunakan pratinjau tersemat dalam foto RAW sebagai masukan dalam pemrosesan gambar dan ketika tersedia. Ini dapat menghasilkan warna yang lebih akurat untuk beberapa gambar, tetapi kualitas pratinjau bergantung pada kamera dan gambarnya dapat memiliki lebih banyak artefak kompresi.", - "image_prefer_wide_gamut": "Utamakan gamut luas", - "image_prefer_wide_gamut_setting_description": "Gunakan Display P3 untuk gambar kecil. Ini menjaga kecerahan gambar dengan ruang warna yang luas, tetapi gambar dapat terlihat beda pada perangkat lawas dengan versi peramban yang lawas. Gambar sRGB tetap dalam sRGB untuk menghindari perubahan warna.", - "image_preview_description": "Gambar berukuran sedang tanpa metadata, digunakan ketika melihat aset satuan dan untuk pembelajaran mesin", - "image_preview_quality_description": "Kualitas pratinjau dari 1-100. Lebih tinggi lebih baik, tetapi menghasilkan berkas lebih besar dan respons aplikasi. Menetapkan nilai rendah dapat memengaruhi kualitas pembelajaran mesin.", - "image_preview_title": "Pengaturan Pratinjau", - "image_progressive": "Progresif", - "image_progressive_description": "Enkode gambar-gambar JPEG secara progresif untuk memuat tampilan secara bertahap. Ini tidak berpengaruh pada gambar-gambar WebP.", - "image_quality": "Kualitas", - "image_resolution": "Resolusi", - "image_resolution_description": "Resolusi yang lebih tinggi dapat menyimpan lebih banyak detail tetapi memerlukan waktu yang lebih lama untuk di-enkode, memiliki ukuran berkas yang lebih besar, dan dapat mengurangi respons aplikasi.", - "image_settings": "Pengaturan Gambar", - "image_settings_description": "Kelola kualitas dan resolusi gambar yang dibuat", - "image_thumbnail_description": "Gambar kecil tanpa metadata, digunakan ketika melihat kelompok foto seperti lini masa utama", - "image_thumbnail_quality_description": "Kualitas gambar kecil dari 1-100. Lebih tinggi lebih baik, tetapi menghasilkan berkas lebih besar dan dapat mengurangi respons aplikasi.", - "image_thumbnail_title": "Pengaturan Gambar Kecil", - "import_config_from_json_description": "Impor konfigurasi sistem dengan mengunggah berkas konfigurasi JSON", - "job_concurrency": "Konkurensi {job}", - "job_created": "Tugas telah dibuat", - "job_not_concurrency_safe": "Tugas ini tidak aman untuk konkurensi.", - "job_settings": "Pengaturan Tugas", - "job_settings_description": "Kelola konkurensi tugas", - "jobs_delayed": "{jobCount, plural, other {# tertunda}}", - "jobs_failed": "{jobCount, plural, other {# gagal}}", - "jobs_over_time": "Tugas dari waktu ke waktu", - "library_created": "Pustaka dibuat: {library}", - "library_deleted": "Pustaka dihapus", - "library_details": "Detail pustaka", - "library_folder_description": "Tentukan folder untuk diimpor. Folder ini beserta subfoldernya akan dipindai untuk gambar dan video.", - "library_remove_exclusion_pattern_prompt": "Yakin ingin menghapus pola pengecualian ini?", - "library_remove_folder_prompt": "Yakin ingin menghapus folder impor ini?", - "library_scanning": "Pemindaian Berkala", - "library_scanning_description": "Atur pemindaian pustaka berkala", - "library_scanning_enable_description": "Aktifkan pemindaian pustaka berkala", - "library_settings": "Pustaka Eksternal", - "library_settings_description": "Kelola pengaturan pustaka eksternal", - "library_tasks_description": "Pindai pustaka eksternal untuk aset baru dan/atau berubah", - "library_updated": "Pustaka yang diperbarui", - "library_watching_enable_description": "Pantau perubahan berkas dalam pustaka eksternal", - "library_watching_settings": "Pemantauan pustaka [UJI COBA]", - "library_watching_settings_description": "Pantau berkas yang telah diubah secara otomatis", - "logging_enable_description": "Aktifkan log", - "logging_level_description": "Ketika diaktifkan, tingkat log apa yang digunakan.", - "logging_settings": "Penulisan log", - "machine_learning_availability_checks": "Pemeriksaan ketersediaan", - "machine_learning_availability_checks_description": "Secara otomatis mendeteksi dan memprioritaskan server machine learning yang tersedia", - "machine_learning_availability_checks_enabled": "Aktifkan pemeriksaan ketersediaan", - "machine_learning_availability_checks_interval": "Interval pemeriksaan", - "machine_learning_availability_checks_interval_description": "Interval dalam milidetik antar pemeriksaan ketersediaan", - "machine_learning_availability_checks_timeout": "Batas waktu permintaan", - "machine_learning_availability_checks_timeout_description": "Batas waktu dalam milidetik untuk pemeriksaan ketersediaan", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Nama model CLIP yang didaftarkan di sini. Anda harus menjalankan ulang tugas 'Pencarian Otomatis' untuk semua gambar ketika mengganti model.", - "machine_learning_duplicate_detection": "Deteksi Duplikat", - "machine_learning_duplicate_detection_enabled": "Aktifkan deteksi duplikat", - "machine_learning_duplicate_detection_enabled_description": "Jika diaktifkan, aset yang mirip akan tetap dideduplikasikan.", - "machine_learning_duplicate_detection_setting_description": "Gunakan penyematan CLIP untuk mencari kemungkinan duplikat", - "machine_learning_enabled": "Aktifkan pembelajaran mesin", - "machine_learning_enabled_description": "Jika dinonaktifkan, semua fitur pembelajaran mesin akan dinonaktifkan terlepas dari pengaturan di bawah.", - "machine_learning_facial_recognition": "Pengenalan Wajah", - "machine_learning_facial_recognition_description": "Deteksi, kenali, dan kelompokkan wajah dalam gambar", - "machine_learning_facial_recognition_model": "Model pengenalan wajah", - "machine_learning_facial_recognition_model_description": "Model didaftarkan berdasarkan urutan ukuran menurun. Model yang lebih besar lebih lambat dan menggunakan lebih banyak memori, tetapi akan menghasilkan dengan baik. Anda harus menjalankan ulang tugas Pengenalan Wajah untuk semua gambar setelah mengganti model.", - "machine_learning_facial_recognition_setting": "Aktifkan pengenalan wajah", - "machine_learning_facial_recognition_setting_description": "Jika dinonaktifkan, gambar tidak akan dienkode untuk pengenalan wajah dan tidak akan mengisi bagian Orang dalam laman Jelajahi.", - "machine_learning_max_detection_distance": "Jarak deteksi maksimum", - "machine_learning_max_detection_distance_description": "Jarak maksimum antara dua gambar untuk dianggap sebagai duplikat, dari 0,001 sampai 0,1. Nilai lebih tinggi akan mendeteksi lebih banyak duplikat, tetapi dapat mengakibatkan positif palsu.", - "machine_learning_max_recognition_distance": "Jarak pengenalan maksimum", - "machine_learning_max_recognition_distance_description": "Jarak maksimum antara dua wajah untuk dianggap sebagai orang yang sama, dari 0 sampai 2. Menurunkan ini dapat mencegah pelabelan dua orang sebagai orang yang sama, sedangkan meninggikan ini dapat mencegah pelabelan orang yang sama sebagai dua orang berbeda. Menggabungkan dua orang lebih mudah daripada memisahkan satu orang menjadi dua, jadi usahakan menetapkan ambang yang lebih rendah ketika memungkinkan.", - "machine_learning_min_detection_score": "Nilai deteksi minimum", - "machine_learning_min_detection_score_description": "Nilai keyakinan minimum untuk sebuah wajah untuk dideteksi dari 0 sampai 1. Nilai yang lebih rendah akan mendeteksi lebih banyak wajah tetapi dapat mengakibatkan positif palsu.", - "machine_learning_min_recognized_faces": "Wajah terkenal minimum", - "machine_learning_min_recognized_faces_description": "Jumlah minimum wajah yang dikenal untuk seseorang untuk dibuat. Meningkatkan ini membuat Pengenalan Wajah lebih tepat dengan kemungkinan bahwa sebuah wajah tidak dikaitkan dengan seseorang.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Gunakan pembelajaran mesin untuk mengenali teks di dalam gambar", - "machine_learning_ocr_enabled": "Aktfikan OCR", - "machine_learning_ocr_enabled_description": "Jika dinonaktifkan, gambar-gambar tidak akan mengalami pengenalan teks.", - "machine_learning_ocr_max_resolution": "Resolusi maksimum", - "machine_learning_ocr_max_resolution_description": "Pratinjau di atas resolusi ini akan disesuaikan ukurannya sambil mempertahankan aspek rasio. Nilai yang lebih tinggi lebih akurat, tetapi membutuhkan waktu yang lama untuk memproses dan membutuhkan memori lebih banyak.", - "machine_learning_ocr_min_detection_score": "Skor deteksi minimum", - "machine_learning_ocr_min_detection_score_description": "Skor kepercayaan minimum untuk teks yang akan dideteksi berkisar antara 0-1. Nilai yang lebih rendah akan mendeteksi teks yang lebih banyak, tetapi dapat menyebabkan hasil yang positif palsu.", - "machine_learning_ocr_min_recognition_score": "Skor pengenalan minimum", - "machine_learning_ocr_min_score_recognition_description": "Skor kepercayaan minimum untuk teks yang akan dideteksi berkisar antara 0-1. Nilai yang lebih rendah akan mendeteksi teks yang lebih banyak, tetapi dapat menyebabkan hasil yang positif palsu.", - "machine_learning_ocr_model": "Model OCR", - "machine_learning_ocr_model_description": "Model server lebih akurat daripada model mobile, tetapi membutuhkan waktu yang lebih lama untuk memproses dan menggunakan memori yang lebih banyak.", - "machine_learning_settings": "Pengaturan Mesin Pembelajaran", - "machine_learning_settings_description": "Kelola fitur dan pengaturan mesin pembelajaran", - "machine_learning_smart_search": "Pencarian Pintar", - "machine_learning_smart_search_description": "Cari gambar secara semantik menggunakan penyematan CLIP", - "machine_learning_smart_search_enabled": "Aktifkan pencarian pintar", - "machine_learning_smart_search_enabled_description": "Jika dinonaktifkan, gambar tidak akan dienkode untuk pencarian pintar.", - "machine_learning_url_description": "URL server pembelajaran mesin. Jika lebih dari satu URL disediakan, setiap server akan dicoba satu per satu sampai salah satu berhasil merespons, dari urutan pertama sampai terakhir. Server yang tidak merespons akan diabaikan sementara sampai kembali daring.", - "maintenance_delete_backup": "Hapus Cadangan", - "maintenance_delete_backup_description": "File ini akan dihapus secara permanen.", - "maintenance_delete_error": "Gagal menghapus cadangan.", - "maintenance_restore_backup": "Mengembalikan Cadangan", - "maintenance_restore_backup_description": "Immich akan dihapus dan dikembalikan dari candangan yang dipilih. Sebuah candangan akan dibuat sebelum dilanjutkan.", - "maintenance_restore_backup_different_version": "Cadangan ini dibuat dengan versi Immich yang berbeda!", - "maintenance_restore_backup_unknown_version": "Tidak dapat menentukan versi candangan.", - "maintenance_restore_database_backup": "Mengembalikan cadangan database", - "maintenance_restore_database_backup_description": "Kembalikan ke keadaan database sebelumnya menggunakan sebuah file cadangan", - "maintenance_settings": "Pemeliharaan", - "maintenance_settings_description": "Setel mode pemeliharaan Immich.", - "maintenance_start": "Pindah ke mode pemeliharaan", - "maintenance_start_error": "Gagal memulai mode pemeliharaan.", - "maintenance_upload_backup": "Unggah file candangan database", - "maintenance_upload_backup_error": "Tidak dapat mengunggah cadangan, apakah ini sebuah file .sql/.sql.gz?", - "manage_concurrency": "Kelola Konkurensi", - "manage_concurrency_description": "Pindah ke halaman tugas untuk mengelola konkurensi tugas", - "manage_log_settings": "Kelola pengaturan log", - "map_dark_style": "Gaya gelap", - "map_enable_description": "Aktifkan fitur peta", - "map_gps_settings": "Pengaturan Peta & GPS", - "map_gps_settings_description": "Kelola Pengaturan Peta & GPS (Pengodean Geografis Terbalik)", - "map_implications": "Fitur peta mengandalkan layanan tile eksternal", - "map_light_style": "Gaya terang", - "map_manage_reverse_geocoding_settings": "Kelola settingan Pengodean Geografis Terbalik", - "map_reverse_geocoding": "Pengodean Geografis Terbalik", - "map_reverse_geocoding_enable_description": "Aktifkan pengodean geografis terbalik", - "map_reverse_geocoding_settings": "Pengaturan Pengodean Geografis Terbalik", - "map_settings": "Peta", - "map_settings_description": "Kelola pengaturan peta", - "map_style_description": "URL ke tema peta style.json", - "memory_cleanup_job": "Pembersihan memori", - "memory_generate_job": "Pembuatan memori", - "metadata_extraction_job": "Ekstrak metadata", - "metadata_extraction_job_description": "Ekstrak informasi metadata dari setiap aset, seperti GPS, wajah dan resolusi", - "metadata_faces_import_setting": "Aktifkan impor wajah", - "metadata_faces_import_setting_description": "Impor wajah dari data gambar EXIF dan berkas sidecar", - "metadata_settings": "Pengaturan Metadata", - "metadata_settings_description": "Kelola pengaturan metadata", - "migration_job": "Migrasi", - "migration_job_description": "Migrasikan gambar kecil untuk aset dan wajah ke struktur folder terkini", - "nightly_tasks_cluster_faces_setting_description": "Mulai pengenalan wajah pada semua wajah yang baru saja terdeteksi", - "nightly_tasks_cluster_new_faces_setting": "Kelompokkan semua wajah baru", - "nightly_tasks_database_cleanup_setting": "Tugas pembersihan basis data", - "nightly_tasks_database_cleanup_setting_description": "Membersihkan data lama, kadaluarsa dari database", - "nightly_tasks_generate_memories_setting": "Buat kenang-kenangan", - "nightly_tasks_generate_memories_setting_description": "Buat kenang-kenangan baru dari berbagai aset", - "nightly_tasks_missing_thumbnails_setting": "Membuat thumbnail yang hilang", - "nightly_tasks_missing_thumbnails_setting_description": "Mengantrikan aset tanpa thumbnail untuk pembuatan thumbnail", - "nightly_tasks_settings": "Pengaturan Tugas Malam", - "nightly_tasks_settings_description": "Atur tugas malam", - "nightly_tasks_start_time_setting": "Waktu mulai", - "nightly_tasks_start_time_setting_description": "Waktu saat server mulai menjalankan tugas malam", - "nightly_tasks_sync_quota_usage_setting": "Sinkronisasi penggunaan kuota", - "nightly_tasks_sync_quota_usage_setting_description": "Pembaruan kuota penyimpanan pengguna, berdasarkan penggunaan sekarang", - "no_paths_added": "Tidak ada jalur yang ditambahkan", - "no_pattern_added": "Tidak ada pola yang ditambahkan", - "note_apply_storage_label_previous_assets": "Catatan: Untuk menerapkan Label Penyimpanan untuk aset yang telah diunggah sebelumnya, jalankan", - "note_cannot_be_changed_later": "CATATAN: Ini tidak akan dapat diubah lagi!", - "notification_email_from_address": "Dari alamat", - "notification_email_from_address_description": "Alamat surel pengirim, misalnya: \"Server Foto Immich \". Pastikan untuk menggunakan alamat yang diizinkan untuk mengirim email.", - "notification_email_host_description": "Hos server surel (mis. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Abaikan eror sertifikat", - "notification_email_ignore_certificate_errors_description": "Abaikan eror validasi sertifikat TLS (tidak disarankan)", - "notification_email_password_description": "Kata sandi yang digunakan ketika mengautentikasi dengan server surel", - "notification_email_port_description": "Porta server surel (mis. 25, 465, atau 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Gunakan SMTPS (SMTP melalui TLS)", - "notification_email_sent_test_email_button": "Kirim surel uji coba dan simpan", - "notification_email_setting_description": "Pengaturan pengiriman notifikasi surel", - "notification_email_test_email": "Kirim surel uji coba", - "notification_email_test_email_failed": "Gagal mengirim surel uji coba, periksa kembali nilai Anda", - "notification_email_test_email_sent": "Surel uji coba telah dikirim ke {email}. Silakan periksa kotak masuk Anda.", - "notification_email_username_description": "Nama pengguna yang digunakan ketika mengautentikasi ke server surel", - "notification_enable_email_notifications": "Aktifkan notifikasi surel", - "notification_settings": "Pengaturan Notifikasi", - "notification_settings_description": "Kelola pengaturan notifikasi, termasuk surel", - "oauth_auto_launch": "Peluncuran otomatis", - "oauth_auto_launch_description": "Mulai alur log masuk OAuth secara otomatis setelah menuju ke laman log masuk", - "oauth_auto_register": "Pendaftaran otomatis", - "oauth_auto_register_description": "Daftar pengguna baru secara otomatis setelah log masuk dengan OAuth", - "oauth_button_text": "Teks tombol", - "oauth_client_secret_description": "Diperlukan untuk klien yang konfidensial, atau jika PKCE (Proof Key for Code Exchange) tidak didukung untuk klien umum.", - "oauth_enable_description": "Log masuk dengan OAuth", - "oauth_mobile_redirect_uri": "URI pengalihan ponsel", - "oauth_mobile_redirect_uri_override": "Penimpaan URI penerusan ponsel", - "oauth_mobile_redirect_uri_override_description": "Aktifkan ketika provider OAuth tidak mengizinkan tautan mobile, seperti ''{callback}''", - "oauth_role_claim": "Klaim Peran", - "oauth_role_claim_description": "Secara otomatis memberikan akses admin berdasarkan keberadaan klaim ini. Klaim dapat berupa \"user\" atau \"admin\".", - "oauth_settings": "OAuth", - "oauth_settings_description": "Kelola pengaturan log masuk OAuth", - "oauth_settings_more_details": "Untuk detail lanjut tentang fitur ini, lihat docs.", - "oauth_storage_label_claim": "Klaim label penyimpanan", - "oauth_storage_label_claim_description": "Atur label penyimpanan pengguna menjadi nilai klaim ini secara otomatis.", - "oauth_storage_quota_claim": "Klaim kuota penyimpanan", - "oauth_storage_quota_claim_description": "Atur kuota penyimpanan pengguna menjadi nilai klaim ini secara otomatis.", - "oauth_storage_quota_default": "Kuota penyimpanan bawaan (GiB)", - "oauth_storage_quota_default_description": "Kuota dalam GiB akan digunakan jika tidak ada klaim yang diberikan.", - "oauth_timeout": "Waktu Permintaan Habis", - "oauth_timeout_description": "Waktu habis untuk permintaan dalam milidetik", - "ocr_job_description": "Gunakan mesin pembelajaran untuk mengenali teks di dalam gambar", - "password_enable_description": "Masuk dengan surel dan kata sandi", - "password_settings": "Log Masuk Kata Sandi", - "password_settings_description": "Kelola pengaturan log masuk kata sandi", - "paths_validated_successfully": "Semua jalur berhasil divalidasi", - "person_cleanup_job": "Pembersihan data pribadi", - "queue_details": "Detail Antrian", - "queues": "Antrian Tugas", - "queues_page_description": "Halaman antrian tugas Admin", - "quota_size_gib": "Ukuran Kuota (GiB)", - "refreshing_all_libraries": "Menyegarkan semua pustaka", - "registration": "Pendaftaran Admin", - "registration_description": "Karena Anda merupakan pengguna pertama dalam sistem, Anda akan ditetapkan sebagai Admin dan bertanggung jawab atas tugas administratif dan pengguna tambahan akan dibuat oleh Anda.", - "remove_failed_jobs": "Hapus tugas-tugas gagal", - "require_password_change_on_login": "Memerlukan pengguna untuk mengubah kata sandi pada log masuk pertama", - "reset_settings_to_default": "Atur ulang pengaturan ke bawaan", - "reset_settings_to_recent_saved": "Atur ulang pengaturan ke pengaturan tersimpan terkini", - "scanning_library": "Memindai pustaka", - "search_jobs": "Mencari tugas…", - "send_welcome_email": "Kirim surel selamat datang", - "server_external_domain_settings": "Domain eksternal", - "server_external_domain_settings_description": "Domain untuk tautan terbagi publik, termasuk http(s)://", - "server_public_users": "Pengguna Publik", - "server_public_users_description": "Semua pengguna (nama dan email) didaftarkan ketika menambahkan pengguna ke album terbagi. Ketika dinonaktifkan, daftar pengguna hanya akan tersedia kepada pengguna admin.", - "server_settings": "Pengaturan Server", - "server_settings_description": "Kelola pengaturan server", - "server_stats_page_description": "Halaman statistik server Admin", - "server_welcome_message": "Pesan selamat datang", - "server_welcome_message_description": "Pesan yang ditampilkan di laman log masuk.", - "settings_page_description": "Laman pengaturan admin", - "sidecar_job": "Metadata sespan", - "sidecar_job_description": "Jelajahi atau sinkronisasikan metadata sespan dari sistem berkas", - "slideshow_duration_description": "Jumlah detik untuk menampilkan setiap gambar", - "smart_search_job_description": "Jalankan pembelajaran mesin pada aset untuk mendukung pencarian pintar", - "storage_template_date_time_description": "Waktu pembuatan aset digunakan sebagai informasi waktu dan tanggal", - "storage_template_date_time_sample": "Waktu sampel {date}", - "storage_template_enable_description": "Aktifkan mesin templat penyimpanan", - "storage_template_hash_verification_enabled": "Verifikasi hash diaktifkan", - "storage_template_hash_verification_enabled_description": "Mengaktifkan verifikasi hash, jangan mengaktifkan ini kecuali Anda sudah tahu kekurangannya", - "storage_template_migration": "Migrasi templat penyimpanan", - "storage_template_migration_description": "Tetapkan {template} saat ini pada aset yang sebelumnya diunggah", - "storage_template_migration_info": "Templat penyimpanan akan mengubah semua ekstensi ke huruf kecil. Perubahan templat hanya akan diterapkan pada aset baru. Untuk menerapkan templat pada setiap aset yang sebelumnya telah diunggah, jalankan {job}.", - "storage_template_migration_job": "Tugas Migrasi Templat Ruang Penyimpanan", - "storage_template_more_details": "Untuk detail lebih lanjut tentang fitur ini, pergi ke Templat Penyimpanan dan kekurangannya", - "storage_template_onboarding_description_v2": "Saat diaktifkan, fitur ini akan mengatur file secara otomatis berdasarkan templat yang ditentukan pengguna. Untuk informasi selengkapnya, silakan lihat dokumentasi.", - "storage_template_path_length": "Batas panjang jalur: {length, number}{limit, number}", - "storage_template_settings": "Templat Penyimpanan", - "storage_template_settings_description": "Kelola struktur folder dan nama berkas dari aset yang diunggah", - "storage_template_user_label": "{label} adalah Label Penyimpanan pengguna", - "system_settings": "Pengaturan Sistem", - "tag_cleanup_job": "Pembersihan tag", - "template_email_available_tags": "Anda dapat menggunakan variabel berikut dalam templat Anda: {tags}", - "template_email_if_empty": "Jika templat kosong, surel bawaan akan digunakan.", - "template_email_invite_album": "Templat Undangan Album", - "template_email_preview": "Pratinjau", - "template_email_settings": "Templat Surel", - "template_email_update_album": "Perbarui Templat Album", - "template_email_welcome": "Templat surel selamat datang", - "template_settings": "Templat Notifikasi", - "template_settings_description": "Kelola templat kustom untuk notifikasi", - "theme_custom_css_settings": "CSS Kustom", - "theme_custom_css_settings_description": "CSS memungkinkan desain Immich untuk diubah.", - "theme_settings": "Pengaturan Tema", - "theme_settings_description": "Kelola kustomisasi antarmuka web Immich", - "thumbnail_generation_job": "Buat Gambar Kecil", - "thumbnail_generation_job_description": "Buat aset besar, kecil, dan buram untuk setiap aset, termasuk gambar kecil untuk setiap orang", - "transcoding_acceleration_api": "API Akselerasi", - "transcoding_acceleration_api_description": "API yang akan berinteraksi dengan perangkat Anda untuk mengakselerasi transkode. Pengaturan ini merupakan 'upaya terbaik': ini akan menggunakan transkode perangkat lunak jika gagal.", - "transcoding_acceleration_nvenc": "NVENC (memerlukan GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (memerlukan CPU Intel generasi ke-7 atau lebih baru)", - "transcoding_acceleration_rkmpp": "RKMPP (hanya pada SOC Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Kodek audio yang diterima", - "transcoding_accepted_audio_codecs_description": "Pilih kodek audio apa saja yang tidak perlu ditranskode. Hanya digunakan untuk beberapa kebijakan transkode.", - "transcoding_accepted_containers": "Kontainer yang diterima", - "transcoding_accepted_containers_description": "Pilih format kontainer apa saja yang tidak perlu dimultipleks ulang ke MP4. Hanya digunakan untuk beberapa kebijakan transkode.", - "transcoding_accepted_video_codecs": "Kodek video yang diterima", - "transcoding_accepted_video_codecs_description": "Pilih kodek video apa yang tidak perlu ditranskode. Hanya digunakan untuk beberapa kebijakan transkode.", - "transcoding_advanced_options_description": "Opsi yang tidak perlu diubah oleh sebagian besar pengguna", - "transcoding_audio_codec": "Kodek audio", - "transcoding_audio_codec_description": "Opus merupakan opsi kualitas tertinggi, tetapi memiliki kompatibilitas yang lebih sedikit dengan peranti atau perangkat lunak lawas.", - "transcoding_bitrate_description": "Video yang lebih tinggi dari kecepatan bit maksimum atau tidak dalam format yang diterima", - "transcoding_codecs_learn_more": "Untuk mempelajari lebih lanjut tentang istilah yang digunakan, baca dokumentasi FFmpeg untuk kodek H.264, kodek HEVC, dan kodek VP9.", - "transcoding_constant_quality_mode": "Mode kualitas konstan", - "transcoding_constant_quality_mode_description": "ICQ lebih baik daripada CQP, tetapi beberapa perangkat akselerasi tidak mendukung mode ini. Mengatur opsi ini akan lebih menyukai mode yang ditentukan ketika menggunakan pengodean berbasis kualitas. Diabaikan oleh NVENC karena tidak mendukung ICQ.", - "transcoding_constant_rate_factor": "Faktor kecepatan konstan (-crf)", - "transcoding_constant_rate_factor_description": "Tingkat kualitas video. Nilai umum adalah 23 untuk H.264, 28 untuk HEVC, 31 untuk VP9, dan 35 untuk AV1. Lebih rendah lebih baik, tetapi menghasilkan berkas yang lebih besar.", - "transcoding_disabled_description": "Jangan transkode video apa pun, dapat merusak pemutaran pada beberapa klien", - "transcoding_encoding_options": "Opsi Pengodean", - "transcoding_encoding_options_description": "Atur kodek, resolusi, kualitas dan opsi lainnya untuk video terenkode", - "transcoding_hardware_acceleration": "Akselerasi Perangkat Keras", - "transcoding_hardware_acceleration_description": "Eksperimental: transcoding lebih cepat tetapi dapat menurunkan kualitas pada bitrate yang sama", - "transcoding_hardware_decoding": "Dekode perangkat keras", - "transcoding_hardware_decoding_setting_description": "Mengaktifkan akselerasi ujung ke ujung daripada hanya mengakselerasi pengodean. Mungkin tidak berfungsi pada semua video.", - "transcoding_max_b_frames": "Bingkai B maksimum", - "transcoding_max_b_frames_description": "Nilai yang lebih tinggi meningkatkan efisiensi kompresi, tetapi membuat pengodean lebih lambat. Mungkin tidak kompatibel dengan akselerasi perangkat keras pada perangkat lawas. 0 menonaktifkan bingkai B, sedangkan -1 mengatur nilai ini secara otomatis.", - "transcoding_max_bitrate": "Kecepatan bit maksimum", - "transcoding_max_bitrate_description": "Menetapkan kecepatan bit maksimum dapat membuat ukuran berkas lebih dapat diprediksi dengan kekurangan minor pada kualitas. Pada 720p, nilai umum adalah 2600 kbit/s untuk VP9 atau HEVC, atau 4500 kbit/s untuk H.264. Dinonaktifkan jika ditetapkan ke 0. Ketika tidak ada unit yang dipilih, k (untuk kbit/s) akan diasumsikan; oleh karena itu 5000, 5000k, dan 5M (untuk Mbit/s) terhitung setara.", - "transcoding_max_keyframe_interval": "Interval bingkai kunci maksimum", - "transcoding_max_keyframe_interval_description": "Menetapkan jarak bingkai maksimum antara bingkai kunci. Nilai yang lebih rendah membuat efisiensi kompresi lebih buruk, tetapi meningkatkan waktu pencarian dan dapat meningkatkan kualitas dalam adegan dengan gerakan cepat. 0 menetapkan nilai ini secara otomatis.", - "transcoding_optimal_description": "Video lebih tinggi dari resolusi sasaran atau tidak dalam format yang diterima", - "transcoding_policy": "Kebijakan Transkode", - "transcoding_policy_description": "Atur kapan video ditranskode", - "transcoding_preferred_hardware_device": "Perangkat keras yang lebih disukai", - "transcoding_preferred_hardware_device_description": "Hanya diterapkan pada VAAPI dan QSV. Menetapkan node dri yang digunakan untuk transkode perangkat keras.", - "transcoding_preset_preset": "Prasetel (-preset)", - "transcoding_preset_preset_description": "Kecepatan kompresi. Pra setel yang lebih lambat membuat berkas lebih kecil dan meningkatkan kualitas ketika menargetkan kecepatan bit tertentu. VP9 mengabaikan kecepatan di atas `faster`.", - "transcoding_reference_frames": "Bingkai referensi", - "transcoding_reference_frames_description": "Jumlah bingkai untuk direferensikan ketika mengompres bingkai tertentu. Nilai lebih tinggi meningkatkan efisiensi kompresi, tetapi membuat pengodean lambat. 0 menetapkan nilai ini secara otomatis.", - "transcoding_required_description": "Hanya video dalam format yang tidak diterima", - "transcoding_settings": "Pengaturan Transkode Video", - "transcoding_settings_description": "Kelola video mana saja untuk dienkode dan cara memprosesnya", - "transcoding_target_resolution": "Resolusi sasaran", - "transcoding_target_resolution_description": "Resolusi yang lebih tinggi dapat menjaga lebih banyak detail tetapi memerlukan waktu lebih lama untuk dienkode, memiliki ukuran berkas yang lebih besar, dan dapat mengurangi respons aplikasi.", - "transcoding_temporal_aq": "AQ Temporal", - "transcoding_temporal_aq_description": "Hanya diterapkan pada NVENC. Kuantisasi Adaptif Temporal meningkatkan kualitas adegan berdetail tinggi dan rendah gerakan. Mungkin tidak kompatibel dengan perangkat lawas.", - "transcoding_threads": "Utas", - "transcoding_threads_description": "Nilai yang lebih tinggi dapat mengode dengan cepat, tetapi mengurangi ruang bagi server untuk memproses tugas lain selagi aktif. Nilai ini seharusnya tidak lebih dari jumlah inti CPU. Memaksimalkan pemakaian jika ditetapkan ke 0.", - "transcoding_tone_mapping": "Pemetaan nada", - "transcoding_tone_mapping_description": "Mencoba menjaga tampilan video HDR ketika dikonversikan ke SDR. Setiap algoritma memiliki kekurangan pada warna, detail, dan kecerahan. Hable menjaga detail, Mobius menjaga warna, dan Reinhard menjada kecerahan.", - "transcoding_transcode_policy": "Kebijakan transkode", - "transcoding_transcode_policy_description": "Kebijakan untuk kapan sebuah video harus ditranskode. Video HDR akan selalu ditranskode (kecuali jika transkode dinonaktifkan).", - "transcoding_two_pass_encoding": "Pengodean dua arah", - "transcoding_two_pass_encoding_setting_description": "Transkode dalam dua arah untuk menghasilkan video yang ditranskode dengan lebih baik. Ketika kecepatan bit maksimum diaktifkan (diperlukan supaya bekerja dengan H.264 dan HEVC), mode ini menggunakan jangka kecepatan bit berdasarkan kecepatan bit maksimum dan mengabaikan CRF. Untuk VP9, CRF dapat digunakan jika kecepatan bit maksimum dinonaktifkan.", - "transcoding_video_codec": "Kodek video", - "transcoding_video_codec_description": "VP9 memiliki efisiensi dan kompatibilitas web tinggi, tetapi memerlukan waktu yang lebih lama untuk ditranskode. HEVC berkinerja serupa, tetapi memiliki kompatibilitas web yang lebih rendah. H.264 sangat kompatibel dan cepat untuk ditranskode, tetapi menghasilkan berkas yang lebih besar. AV1 adalah kodek yang paling efisien tetapi tidak didukung pada perangkat lawas.", - "trash_enabled_description": "Aktifkan fitur Sampah", - "trash_number_of_days": "Jumlah hari", - "trash_number_of_days_description": "Jumlah hari untuk menyimpan aset dalam sampah sebelum dihapus secara permanen", - "trash_settings": "Pengaturan Sampah", - "trash_settings_description": "Kelola pengaturan sampah", - "unlink_all_oauth_accounts": "Putuskan tautan semua akun OAuth", - "unlink_all_oauth_accounts_description": "Pastikan untuk memutus tautan semua akun OAuth sebelum melakukan migrasi ke penyedia baru.", - "unlink_all_oauth_accounts_prompt": "Apakah Anda yakin ingin memutus tautan semua akun OAuth? Tindakan ini akan mengatur ulang ID OAuth untuk setiap pengguna dan tidak dapat dibatalkan.", - "user_cleanup_job": "Pembersihan data pengguna", - "user_delete_delay": "Akun dan aset {user} akan dijadwalkan untuk penghapusan permanen dalam {delay, plural, one {# hari} other {# hari}}.", - "user_delete_delay_settings": "Jeda penghapusan", - "user_delete_delay_settings_description": "Jumlah hari setelah penghapusan untuk menghapus akun dan aset pengguna secara permanen. Tugas penghapusan pengguna berjalan pada tengah malam untuk memeriksa pengguna yang siap untuk dihapus. Perubahan pengaturan ini akan dievaluasi pada eksekusi berikutnya.", - "user_delete_immediately": "Akun dan aset {user} akan diantrekan untuk penghapusan permanen segera.", - "user_delete_immediately_checkbox": "Masukkan pengguna dan aset dalam antrean untuk penghapusan segera", - "user_details": "Detail Pengguna", - "user_management": "Pengelolaan Pengguna", - "user_password_has_been_reset": "Kata sandi pengguna telah diatur ulang:", - "user_password_reset_description": "Silakan sediakan kata sandi sementara untuk pengguna dan beri tahu bahwa pengguna tersebut harus mengubah kata sandinya pada log masuk berikutnya.", - "user_restore_description": "Akun {user} akan dipulihkan.", - "user_restore_scheduled_removal": "Pulihkan pengguna - jadwalkan pelepasan pada {date, date, long}", - "user_settings": "Pengaturan Pengguna", - "user_settings_description": "Kelola pengaturan pengguna", - "user_successfully_removed": "Pengguna {email} berhasil dihapus.", - "users_page_description": "Laman pengguna admin", - "version_check_enabled_description": "Aktifkan pemeriksaan versi", - "version_check_implications": "Fitur pemeriksaan versi tergantung komunikasi berkala dengan github.com", - "version_check_settings": "Pemeriksaan Versi", - "version_check_settings_description": "Aktifkan/nonaktifkan notifikasi versi baru", - "video_conversion_job": "Transkode video", - "video_conversion_job_description": "Transkode video untuk kompatibilitas lebih luas dengan peramban dan perangkat" - }, - "admin_email": "Surel Admin", - "admin_password": "Kata Sandi Admin", - "administration": "Administrasi", - "advanced": "Tingkat lanjut", - "advanced_settings_clear_image_cache": "Bersihkan Cache Gambar", - "advanced_settings_clear_image_cache_error": "Gagal untuk membersihkan cache gambar", - "advanced_settings_clear_image_cache_success": "Sukses menghapus {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Gunakan opsi ini untuk menyaring media saat sinkronisasi berdasarkan kriteria alternatif. Hanya coba ini dengan aplikasi mendeteksi semua album.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAL] Gunakan saringan sinkronisasi album perangkat alternatif", - "advanced_settings_log_level_title": "Tingkat log: {level}", - "advanced_settings_prefer_remote_subtitle": "Beberapa perangkat akan lambat memuat gambar kecil dari lokal. Menyalakan ini akan memuat gambar kecil dari peladen.", - "advanced_settings_prefer_remote_title": "Prioritaskan gambar dari server", - "advanced_settings_proxy_headers_subtitle": "Tentukan header proxy yang harus dikirim Immich dengan setiap permintaan jaringan", - "advanced_settings_proxy_headers_title": "Header proxy kustom [EKSPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Mengaktifkan mode baca-saja, di mana foto hanya bisa dilihat. Fitur seperti memilih banyak foto, berbagi, cast, dan hapus akan dinonaktifkan. Mode baca-saja bisa diaktifkan/nonaktifkan lewat avatar pengguna di layar utama", - "advanced_settings_readonly_mode_title": "Mode Hanya-Baca", - "advanced_settings_self_signed_ssl_subtitle": "Melewati verifikasi sertifikat SSL untuk titik akhir server. Diperlukan untuk sertifikat yang ditandatangani sendiri.", - "advanced_settings_self_signed_ssl_title": "Izinkan sertifikat SSL yang ditandatangani sendiri [EKSPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Hapus atau pulihkan aset pada perangkat ini secara otomatis ketika tindakan dilakukan di web", - "advanced_settings_sync_remote_deletions_title": "Sinkronisasi penghapusan jarak jauh [EKSPERIMENTAL]", - "advanced_settings_tile_subtitle": "Pengaturan pengguna tingkat lanjut", - "advanced_settings_troubleshooting_subtitle": "Aktifkan fitur tambahan untuk pemecahan masalah", - "advanced_settings_troubleshooting_title": "Penelusuran masalah", - "age_months": "Umur {months, plural, one {# bulan} other {# bulan}}", - "age_year_months": "Umur 1 tahun, {months, plural, one {# bulan} other {# bulan}}", - "age_years": "{years, plural, other {Umur #}}", - "album": "Album", - "album_added": "Album ditambahkan", - "album_added_notification_setting_description": "Terima notifikasi surel ketika Anda ditambahkan ke album terbagi", - "album_cover_updated": "Kover album diperbarui", - "album_delete_confirmation": "Apakah Anda yakin ingin menghapus album {album}?", - "album_delete_confirmation_description": "Jika album ini dibagikan, pengguna lain tidak akan dapat mengaksesnya lagi.", - "album_deleted": "Album dihapus", - "album_info_card_backup_album_excluded": "Dikecualikan", - "album_info_card_backup_album_included": "Terpilih", - "album_info_updated": "Info album diperbarui", - "album_leave": "Tinggalkan album?", - "album_leave_confirmation": "Apakah Anda yakin ingin keluar dari {album}?", - "album_name": "Nama Album", - "album_options": "Opsi Album", - "album_remove_user": "Keluarkan pengguna?", - "album_remove_user_confirmation": "Apakah Anda yakin ingin mengeluarkan {user}?", - "album_search_not_found": "Tidak ada album yang ditemukan sesuai pencarian Anda", - "album_selected": "Album yang dipilih", - "album_share_no_users": "Sepertinya Anda telah membagikan album ini dengan semua pengguna atau tidak memiliki pengguna siapa pun untuk dibagikan.", - "album_summary": "Ringkasan album", - "album_updated": "Album diperbarui", - "album_updated_setting_description": "Terima notifikasi surel ketika album terbagi memiliki aset baru", - "album_upload_assets": "Unggah aset dari komputer mu dan tambahkan ke album", - "album_user_left": "Keluar dari {album}", - "album_user_removed": "{user} dikeluarkan", - "album_viewer_appbar_delete_confirm": "Hapus album ini dari akun anda?", - "album_viewer_appbar_share_err_delete": "Gagal menghapus album", - "album_viewer_appbar_share_err_leave": "Gagal keluar album", - "album_viewer_appbar_share_err_remove": "Gagal menghapus aset dari album", - "album_viewer_appbar_share_err_title": "Gagal mengubah judul album", - "album_viewer_appbar_share_leave": "Keluar dari album", - "album_viewer_appbar_share_to": "Bagikan Ke", - "album_viewer_page_share_add_users": "Tambah pengguna", - "album_with_link_access": "Perbolehkan siapa pun dengan tautan melihat foto dan orang dalam album ini.", - "albums": "Album", - "albums_count": "{count, plural, one {{count, number} Album}other {{count, number} Album}}", - "albums_default_sort_order": "Urutan album bawaan", - "albums_default_sort_order_description": "Urutan awal aset saat membuat album baru.", - "albums_feature_description": "Koleksi foto atau video yang dapat dibagikan kepada pengguna lain.", - "albums_on_device_count": "Album di perangkat ({count})", - "albums_selected": "{count, plural, one {# album yang dipilih} other {# album yang dipilih}}", - "all": "Semua", - "all_albums": "Semua album", - "all_people": "Semua orang", - "all_photos": "Semua foto", - "all_videos": "Semua video", - "allow_dark_mode": "Perbolehkan mode gelap", - "allow_edits": "Perbolehkan penyuntingan", - "allow_public_user_to_download": "Perbolehkan pengguna publik untuk mengunduh", - "allow_public_user_to_upload": "Perbolehkan pengguna publik untuk mengunggah", - "allowed": "Diijinkan", - "alt_text_qr_code": "Gambar kode QR", - "always_keep": "Selalu simpan", - "always_keep_photos_hint": "Fitur Bebaskan Ruang ruang akan menyimpan semua foto di perangkat ini.", - "always_keep_videos_hint": "Fitur Bebaskan Ruang ruang akan menyimpan semua video di perangkat ini.", - "anti_clockwise": "Berlawanan arah jarum jam", - "api_key": "Kunci API", - "api_key_description": "Nilai ini hanya akan ditampilkan sekali. Pastikan untuk menyalin sebelum menutup jendela ini.", - "api_key_empty": "Nama Kunci API Anda seharusnya jangan kosong", - "api_keys": "Kunci API", - "app_architecture_variant": "Varian (Arsitektur)", - "app_bar_signout_dialog_content": "Apakah kamu yakin ingin keluar akun?", - "app_bar_signout_dialog_ok": "Ya", - "app_bar_signout_dialog_title": "Keluar akun", - "app_download_links": "Link Download Aplikasi", - "app_settings": "Pengaturan Aplikasi", - "app_stores": "App Stores", - "app_update_available": "Pembaruan aplikasi tersedia", - "appears_in": "Muncul dalam", - "apply_count": "Terapkan ({count, number})", - "archive": "Arsip", - "archive_action_prompt": "{count} telah ditambahkan ke Arsip", - "archive_or_unarchive_photo": "Arsipkan atau batalkan pengarsipan foto", - "archive_page_no_archived_assets": "Tidak ada aset diarsipkan", - "archive_page_title": "Arsip ({count})", - "archive_size": "Ukuran arsip", - "archive_size_description": "Atur ukuran arsip untuk unduhan (dalam GiB)", - "archived": "Diarsipkan", - "archived_count": "{count, plural, other {# terarsip}}", - "are_these_the_same_person": "Apakah ini adalah orang yang sama?", - "are_you_sure_to_do_this": "Apakah Anda yakin ingin melakukan ini?", - "array_field_not_fully_supported": "Bidang-bidang pada array membutuhkan suntingan JSON secara manual", - "asset_action_delete_err_read_only": "Tidak dapat menghapus aset yang bersifat hanya-baca, proses dilewati", - "asset_action_share_err_offline": "Tidak dapat mengambil aset luring, dilewati", - "asset_added_to_album": "Telah ditambahkan ke album", - "asset_adding_to_album": "Menambahkan ke album…", - "asset_created": "Aset berhasil dibuat", - "asset_description_updated": "Deskripsi aset telah diperbarui", - "asset_filename_is_offline": "Aset {filename} sedang luring", - "asset_has_unassigned_faces": "Aset memiliki wajah yang belum ditetapkan", - "asset_hashing": "Memilah…", - "asset_list_group_by_sub_title": "Kelompokkan berdasarkan", - "asset_list_layout_settings_dynamic_layout_title": "Tata dinamis", - "asset_list_layout_settings_group_automatically": "Otomatis", - "asset_list_layout_settings_group_by": "Kelompokkan aset berdasarkan", - "asset_list_layout_settings_group_by_month_day": "Bulan + tanggal", - "asset_list_layout_sub_title": "Penataan", - "asset_list_settings_subtitle": "Setelan grid foto", - "asset_list_settings_title": "Grid Foto", - "asset_not_found_on_device_android": "Aset tidak ditemukan di perangkat", - "asset_not_found_on_device_ios": "Aset tidak ditemukan di perangkat. Jika kamu menggunakan iCloud, aset mungkin tidak dapat diakses karena berkas rusak di iCloud", - "asset_not_found_on_icloud": "Aset tidak ditemukan di iCloud. Aset mungkin tidak dapat diakses karena berkas rusak di iCloud", - "asset_offline": "Aset Luring", - "asset_offline_description": "Aset eksternal ini tidak ada lagi di diska. Silakan hubungi administrator Immich Anda untuk bantuan.", - "asset_restored_successfully": "Aset telah berhasil dipulihkan", - "asset_skipped": "Dilewati", - "asset_skipped_in_trash": "Dalam sampah", - "asset_trashed": "Aset dibuang", - "asset_troubleshoot": "Troubleshoot Aset", - "asset_uploaded": "Sudah diunggah", - "asset_uploading": "Mengunggah…", - "asset_viewer_settings_subtitle": "Kelola pengaturan penampil galeri Anda", - "asset_viewer_settings_title": "Penampil Aset", - "assets": "Aset", - "assets_added_count": "{count, plural, one {# aset} other {# aset}} ditambahkan", - "assets_added_to_album_count": "Ditambahkan {count, plural, one {# aset} other {# aset}} ke album", - "assets_added_to_albums_count": "Ditambahkan {assetTotal, plural, one {# aset} other {# aset}} ke {albumTotal, plural, one {# album} other {# album}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} tidak dapat ditambahkan ke album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Aset} other {Aset}} tidak dapat ditambahkan ke album mana pun", - "assets_count": "{count, plural, one {# aset} other {# aset}}", - "assets_deleted_permanently": "{count} aset dihapus secara permanen", - "assets_deleted_permanently_from_server": "{count} aset dihapus secara permanen dari server Immich", - "assets_downloaded_failed": "{count, plural, one {# berkas diunduh - {error} berkas gagal} other {# berkas diunduh - {error} berkas gagal}}", - "assets_downloaded_successfully": "{count, plural, one {# berkas berhasil diunduh} other {# berkas berhasil diunduh}}", - "assets_moved_to_trash_count": "Dipindahkan {count, plural, one {# aset} other {# aset}} ke sampah", - "assets_permanently_deleted_count": "{count, plural, one {# aset} other {# aset}} dihapus secara permanen", - "assets_removed_count": "{count, plural, one {# aset} other {# aset}} dihapus", - "assets_removed_permanently_from_device": "{count} aset dihapus secara permanen dari perangkat Anda", - "assets_restore_confirmation": "Apakah Anda yakin ingin memulihkan semua aset yang dibuang? Anda tidak dapat mengurungkan tindakan ini! Perlu diingat bahwa aset luring tidak dapat dipulihkan.", - "assets_restored_count": "{count, plural, one {# aset} other {# aset}} dipulihkan", - "assets_restored_successfully": "{count} aset berhasil dipulihkan", - "assets_trashed": "{count} aset dipindahkan ke sampah", - "assets_trashed_count": "{count, plural, one {# aset} other {# aset}} dibuang ke sampah", - "assets_trashed_from_server": "{count} aset dipindahkan ke sampah dari server Immich", - "assets_were_part_of_album_count": "{count, plural, one {Aset telah} other {Aset telah}} menjadi bagian dari album", - "assets_were_part_of_albums_count": "{count, plural, one {Aset sudah} other {Aset sudah}} ada di album", - "authorized_devices": "Perangkat Terautentikasi", - "automatic_endpoint_switching_subtitle": "Sambungkan secara lokal melalui Wi-Fi yang telah ditetapkan saat tersedia, dan gunakan koneksi alternatif lain", - "automatic_endpoint_switching_title": "Peralihan URL otomatis", - "autoplay_slideshow": "Putar otomatis tayangan slide", - "back": "Kembali", - "back_close_deselect": "Kembali, tutup, atau batalkan pemilihan", - "background_backup_running_error": "Cadangan latar belakang sedang berjalan, tidak dapat memulai cadangan manual", - "background_location_permission": "Izin lokasi latar belakang", - "background_location_permission_content": "Untuk beralih jaringan saat berjalan di latar belakang, Immich harus selalu memiliki akses lokasi akurat agar aplikasi dapat membaca nama jaringan Wi-Fi", - "background_options": "Opsi Latar Belakang", - "backup": "Cadangkan", - "backup_album_selection_page_albums_device": "Album di perangkat ({count})", - "backup_album_selection_page_albums_tap": "Sentuh untuk memilih, sentuh 2x untuk mengecualikan", - "backup_album_selection_page_assets_scatter": "Aset dapat tersebar dalam banyak album. Sehingga album dapat dipilih atau dikecualikan saat proses pencadangan.", - "backup_album_selection_page_select_albums": "Pilih album", - "backup_album_selection_page_selection_info": "Info Pilihan", - "backup_album_selection_page_total_assets": "Total aset unik", - "backup_albums_sync": "Sinkronisasi Cadangan Album", - "backup_all": "Semua", - "backup_background_service_backup_failed_message": "Gagal mencadangkan aset. Mencoba lagi…", - "backup_background_service_complete_notification": "Pencadangan aset selesai", - "backup_background_service_connection_failed_message": "Koneksi ke server gagal. Mencoba ulang…", - "backup_background_service_current_upload_notification": "Mengunggah {filename}", - "backup_background_service_default_notification": "Memeriksa aset baru…", - "backup_background_service_error_title": "Galat cadangan", - "backup_background_service_in_progress_notification": "Mencadangkan asetmu…", - "backup_background_service_upload_failure_notification": "Gagal mengunggah {filename}", - "backup_controller_page_albums": "Cadangkan album", - "backup_controller_page_background_app_refresh_disabled_content": "Aktifkan penyegaran aplikasi di latar belakang di Pengaturan > Umum > Penyegaran Aplikasi Latar Belakang untuk menggunakan pencadangan latar belakang.", - "backup_controller_page_background_app_refresh_disabled_title": "Penyegaran aplikasi latar belakang dinonaktifkan", - "backup_controller_page_background_app_refresh_enable_button_text": "Ke setelan", - "backup_controller_page_background_battery_info_link": "Tunjukkan caranya", - "backup_controller_page_background_battery_info_message": "Untuk mendapatkan pengalaman pencadangan latar belakang yang optimal, nonaktifkan setiap pengaturan pengoptimalan baterai yang membatasi aktivitas latar belakang pada aplikasi Immich.\n\nKarena pengaturan ini bergantung pada jenis perangkat, silakan cari informasi yang relevan sesuai dengan produsen perangkat Anda.", - "backup_controller_page_background_battery_info_ok": "Ok", - "backup_controller_page_background_battery_info_title": "Optimisasi baterai", - "backup_controller_page_background_charging": "Hanya saat mengisi daya", - "backup_controller_page_background_configure_error": "Gagal mengatur layanan latar belakang", - "backup_controller_page_background_delay": "Tunda pencadangan aset baru: {duration}", - "backup_controller_page_background_description": "Aktifkan layanan latar belakang untuk mencadangkan aset baru secara otomatis tanpa perlu membuka app", - "backup_controller_page_background_is_off": "Pencadangan otomatis di latar belakang nonaktif", - "backup_controller_page_background_is_on": "Pencadangan otomatis di latar belakang aktif", - "backup_controller_page_background_turn_off": "Matikan layanan latar belakang", - "backup_controller_page_background_turn_on": "Nyalakan layanan latar belakang", - "backup_controller_page_background_wifi": "Hanya melalui Wi-Fi", - "backup_controller_page_backup": "Cadangkan", - "backup_controller_page_backup_selected": "Terpilih: ", - "backup_controller_page_backup_sub": "Foto dan video yang dicadangkan", - "backup_controller_page_created": "Dibuat pada: {date}", - "backup_controller_page_desc_backup": "Aktifkan pencadangan di latar depan untuk mengunggah otomatis aset baru ke server secara otomatis saat aplikasi terbuka.", - "backup_controller_page_excluded": "Dikecualikan: ", - "backup_controller_page_failed": "Gagal ({count})", - "backup_controller_page_filename": "Nama file: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informasi Cadangan", - "backup_controller_page_none_selected": "Tidak ada dipilih", - "backup_controller_page_remainder": "Sisa", - "backup_controller_page_remainder_sub": "Sisa foto dan video untuk dicadangkan", - "backup_controller_page_server_storage": "Penyimpanan Server", - "backup_controller_page_start_backup": "Mulai Cadangkan", - "backup_controller_page_status_off": "Pencadangan otomatis di latar depan nonaktif", - "backup_controller_page_status_on": "Pencadangan otomatis di latar depan aktif", - "backup_controller_page_storage_format": "{used} dari {total} digunakan", - "backup_controller_page_to_backup": "Album untuk dicadangkan", - "backup_controller_page_total_sub": "Semua foto dan video unik dari album terpilih", - "backup_controller_page_turn_off": "Nonaktifkan pencadangan latar depan", - "backup_controller_page_turn_on": "Aktifkan pencadangan latar depan", - "backup_controller_page_uploading_file_info": "Mengunggah info file", - "backup_err_only_album": "Tidak dapat menghapus album", - "backup_error_sync_failed": "Sinkronisasi gagal. Tidak dapat memproses cadangan.", - "backup_info_card_assets": "aset", - "backup_manual_cancelled": "Dibatalkan", - "backup_manual_in_progress": "Dalam proses unggah. Coba lagi nanti", - "backup_manual_success": "Sukses", - "backup_manual_title": "Status unggah", - "backup_options": "Pilihan pengaturan pencadangan", - "backup_options_page_title": "Setelan cadangan", - "backup_setting_subtitle": "Kelola pengaturan unggahan latar belakang dan latar depan", - "backup_settings_subtitle": "Kelola pengaturan unggahan", - "backup_upload_details_page_more_details": "Ketuk untuk detail lebih", - "backward": "Maju", - "biometric_auth_enabled": "Autentikasi biometrik diaktifkan", - "biometric_locked_out": "Anda terkunci oleh autentikasi biometrik", - "biometric_no_options": "Opsi biometrik tidak tersedia", - "biometric_not_available": "Autentikasi biometrik tidak tersedia di perangkat ini", - "birthdate_saved": "Tanggal lahir berhasil disimpan", - "birthdate_set_description": "Tanggal lahir digunakan untuk menghitung umur orang ini pada saat foto diambil.", - "blurred_background": "Latar belakang buram", - "bugs_and_feature_requests": "Permintaan Kutu dan Fitur", - "build": "Versi", - "build_image": "Versi Citra", - "bulk_delete_duplicates_confirmation": "Apakah Anda yakin ingin menghapus {count, plural, one {# aset duplikat} other {# aset duplikat}} secara bersamaan? Ini akan menjaga aset terbesar dari setiap kelompok dan menghapus semua duplikat lain secara permanen. Anda tidak dapat mengurungkan tindakan ini!", - "bulk_keep_duplicates_confirmation": "Apakah Anda yakin ingin menyimpan {count, plural, one {# aset duplikat} other {# aset duplikat}}? Ini akan menyelesaikan semua kelompok duplikat tanpa menghapus apa pun.", - "bulk_trash_duplicates_confirmation": "Apakah Anda yakin ingin membuang {count, plural, one {# aset duplikat} other {# aset duplikat}} secara bersamaan? Ini akan menyimpan aset terbesar dari setiap kelompok dan membuang semua duplikat lainnya.", - "buy": "Beli Immich", - "cache_settings_clear_cache_button": "Hapus cache", - "cache_settings_clear_cache_button_title": "Membersihkan cache aplikasi. Performa aplikasi akan terpengaruh hingga cache dibuat kembali.", - "cache_settings_duplicated_assets_clear_button": "BERSIHKAN", - "cache_settings_duplicated_assets_subtitle": "Foto dan video yang masuk dalam daftar abaikan oleh aplikasi", - "cache_settings_duplicated_assets_title": "Aset Duplikat ({count})", - "cache_settings_statistics_album": "Pustaka thumbnail", - "cache_settings_statistics_full": "Gambar penuh", - "cache_settings_statistics_shared": "Thumbnail album berbagi", - "cache_settings_statistics_thumbnail": "Thumbnail", - "cache_settings_statistics_title": "Penggunaan cache", - "cache_settings_subtitle": "Menyetel proses cache aplikasi Immich", - "cache_settings_tile_subtitle": "Mengatur perilaku penyimpanan lokal", - "cache_settings_tile_title": "Penyimpanan Lokal", - "cache_settings_title": "Setelan Cache", - "camera": "Kamera", - "camera_brand": "Merek kamera", - "camera_model": "Model kamera", - "cancel": "Batal", - "cancel_search": "Batalkan pencarian", - "canceled": "Dibatalkan", - "canceling": "Membatalkan", - "cannot_merge_people": "Tidak dapat menggabungkan orang", - "cannot_undo_this_action": "Anda tidak dapat mengurungkan tindakan ini!", - "cannot_update_the_description": "Tidak dapat memperbarui deskripsi", - "cast": "Pencerminan", - "cast_description": "Mengonfigurasi tujuan cast yang tersedia", - "change_date": "Ubah tanggal", - "change_description": "Ganti deskripsi", - "change_display_order": "Mengubah urutan tampilan", - "change_expiration_time": "Ubah waktu kedaluwarsa", - "change_location": "Ubah lokasi", - "change_name": "Ubah nama", - "change_name_successfully": "Nama berhasil diubah", - "change_password": "Ubah Kata Sandi", - "change_password_description": "Ini merupakan pertama kali Anda masuk ke sistem atau ada permintaan untuk mengubah kata sandi Anda. Silakan masukkan kata sandi baru di bawah.", - "change_password_form_confirm_password": "Konfirmasi Sandi", - "change_password_form_description": "Halo {name},\n\nIni pertama kali anda masuk ke dalam sistem atau terdapat permintaan penggantian kata sandi. Harap masukkan password baru.", - "change_password_form_log_out": "Keluar dari semua perangkat lain", - "change_password_form_log_out_description": "Disarankan untuk keluar dari semua perangkat lain", - "change_password_form_new_password": "Sandi Baru", - "change_password_form_password_mismatch": "Sandi tidak cocok", - "change_password_form_reenter_new_password": "Masukkan Ulang Sandi Baru", - "change_pin_code": "Ubah kode PIN", - "change_trigger": "Ubah pemicu", - "change_trigger_prompt": "Apakah anda yakin ingin mengubah pemicunya? Tindakan ini akan menghapus seluruh aksi dan filter yang sudah ada.", - "change_your_password": "Ubah kata sandi Anda", - "changed_visibility_successfully": "Keterlihatan berhasil diubah", - "charging": "Mengisi daya", - "charging_requirement_mobile_backup": "Cadangan latar belakang memerlukan perangkat dalam keadaan mengisi daya", - "check_corrupt_asset_backup": "Periksa cadangan aset yang rusak", - "check_corrupt_asset_backup_button": "Lakukan pemeriksaan", - "check_corrupt_asset_backup_description": "Jalankan pemeriksaan ini hanya melalui Wi-Fi dan setelah semua aset dicadangkan. Prosedur ini mungkin memerlukan waktu beberapa menit.", - "check_logs": "Periksa Log", - "checksum": "Jumlah kontrol", - "choose_matching_people_to_merge": "Pilih orang yang cocok untuk digabungkan", - "city": "Kota", - "cleanup_confirm_description": "Immich menemukan {count} aset (dibuat sebelum {date}) telah aman dicadangkan di server. Hapus salinan lokal dari perangkat ini?", - "cleanup_confirm_prompt_title": "Hapus dari perangkat ini?", - "cleanup_deleted_assets": "Pindahkan {count} aset ke tempat sampah di perangkat", - "cleanup_deleting": "Memindahkan ke tempat sampah...", - "cleanup_found_assets": "Menemukan {count} aset cadangan", - "cleanup_found_assets_with_size": "Menemukan {count} aset cadangan ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud Shared Albums dikecualikan dari pemindaian", - "cleanup_no_assets_found": "Tidak ada aset yang ditemukan dengan kriteria diatas. Fitur membebaskan ruang hanya dapat menghapus aset yang dicadangkan ke server", - "cleanup_preview_title": "Aset yang akan dihapus ({count})", - "cleanup_step3_description": "Pindah untuk cadangan aset yang sesuai dengan tanggal mu dan simpan pengaturan.", - "cleanup_step4_summary": "{count} aset (dibuat sebelum {date}) untuk dihapus dari perangkat lokal. Foto akan tetap dapat diakses dari aplikasi Immich.", - "cleanup_trash_hint": "Untuk dapat mengambil semua ruang penyimpanan, buka aplikasi galeri pada sistem dan kosongkan tempat sampah", - "clear": "Hapus", - "clear_all": "Hapus semua", - "clear_all_recent_searches": "Hapus semua pencarian terakhir", - "clear_file_cache": "Hapus tembolok berkas", - "clear_message": "Hapus pesan", - "clear_value": "Hapus nilai", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Masukkan kata Sandi", - "client_cert_import": "Impor", - "client_cert_import_success_msg": "Sertifikat klien telah diimpor", - "client_cert_invalid_msg": "File sertifikat tidak valid atau kata sandi salah", - "client_cert_remove_msg": "Sertifikat klien dihapus", - "client_cert_subtitle": "Hanya mendukung format PKCS12 (.p12, .pfx). Impor/hapus sertifikat hanya tersedia sebelum login", - "client_cert_title": "Sertifikat SSL klien [EKSPERIMENTAL]", - "clockwise": "Searah jarum jam", - "close": "Tutup", - "collapse": "Tutup", - "collapse_all": "Tutup Semua", - "color": "Warna", - "color_theme": "Tema warna", - "command": "Perintah", - "comment_deleted": "Komentar dihapus", - "comment_options": "Opsi komentar", - "comments_and_likes": "Komentar & suka", - "comments_are_disabled": "Komentar dinonaktifkan", - "common_create_new_album": "Buat album baru", - "completed": "Selesai", - "confirm": "Konfirmasi", - "confirm_admin_password": "Konfirmasi Kata Sandi Admin", - "confirm_delete_face": "Apakah Anda yakin ingin menghapus wajah {name} dari aset?", - "confirm_delete_shared_link": "Apakah Anda yakin ingin menghapus tautan terbagi ini?", - "confirm_keep_this_delete_others": "Semua aset lain di dalam stack akan dihapus kecuali aset ini. Anda yakin untuk melanjutkan?", - "confirm_new_pin_code": "Konfirmasi kode PIN baru", - "confirm_password": "Konfirmasi kata sandi", - "confirm_tag_face": "Apakah Anda ingin menandai wajah ini sebagai {name}?", - "confirm_tag_face_unnamed": "Apakah Anda ingin menandai wajah ini?", - "connected_device": "Perangkat terhubung", - "connected_to": "Tersambung ke", - "contain": "Berisi", - "context": "Konteks", - "continue": "Lanjutkan", - "control_bottom_app_bar_create_new_album": "Buat album baru", - "control_bottom_app_bar_delete_from_immich": "Hapus dari Immich", - "control_bottom_app_bar_delete_from_local": "Hapus dari perangkat", - "control_bottom_app_bar_edit_location": "Edit Lokasi", - "control_bottom_app_bar_edit_time": "Edit Tanggal & Waktu", - "control_bottom_app_bar_share_link": "Bagikan tautan", - "control_bottom_app_bar_share_to": "Bagikan Ke", - "control_bottom_app_bar_trash_from_immich": "Pindah ke Sampah", - "copied_image_to_clipboard": "Gambar disalin ke papan klip.", - "copied_to_clipboard": "Disalin ke papan klip!", - "copy_error": "Salin eror", - "copy_file_path": "Salin jalur berkas", - "copy_image": "Salin Gambar", - "copy_link": "Salin tautan", - "copy_link_to_clipboard": "Salin tautan ke papan klip", - "copy_password": "Salin kata sandi", - "copy_to_clipboard": "Salin ke Papan Klip", - "country": "Negara", - "cover": "Penutup", - "covers": "Penutup", - "create": "Buat", - "create_album": "Buat album", - "create_album_page_untitled": "Tak berjudul", - "create_api_key": "Buat kunci API", - "create_first_workflow": "Buat alur kerja pertama", - "create_library": "Buat Pustaka", - "create_link": "Buat tautan", - "create_link_to_share": "Buat tautan untuk dibagikan", - "create_link_to_share_description": "Biarkan siapa pun dengan tautan melihat foto yang dipilih", - "create_new": "BUAT BARU", - "create_new_person": "Buat orang baru", - "create_new_person_hint": "Tetapkan aset yang dipilih ke orang yang baru", - "create_new_user": "Buat pengguna baru", - "create_shared_album_page_share_add_assets": "TAMBAHKAN ASET", - "create_shared_album_page_share_select_photos": "Pilih Foto", - "create_shared_link": "Membuat tautan yang dapat dibagikan", - "create_tag": "Buat tag", - "create_tag_description": "Buat tag baru. Untuk tag bersarang, harap input jalur tag secara lengkap termasuk tanda garis miring ke depan.", - "create_user": "Buat pengguna", - "create_workflow": "Buat alur kerja", - "created": "Dibuat", - "created_at": "Dibuat", - "creating_linked_albums": "Membuat album tertaut...", - "crop": "Pangkas", - "crop_aspect_ratio_fixed": "Diperbaiki", - "crop_aspect_ratio_free": "Bebas", - "crop_aspect_ratio_original": "Asli", - "curated_object_page_title": "Benda", - "current_device": "Perangkat saat ini", - "current_pin_code": "Kode PIN saat ini", - "current_server_address": "Alamat server saat ini", - "custom_date": "Tanggal kustom", - "custom_locale": "Lokal Khusus", - "custom_locale_description": "Format tanggal dan angka berdasarkan bahasa dan wilayah", - "custom_url": "URL Kustom", - "cutoff_date_description": "Simpan foto dari…", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM yyyy", - "dark": "Gelap", - "dark_theme": "Nyalakan mode gelap", - "date": "Tanggal", - "date_after": "Tanggal setelah", - "date_and_time": "Tanggal dan Waktu", - "date_before": "Tanggal sebelum", - "date_format": "E, d LLL y • HH:mm", - "date_of_birth_saved": "Tanggal lahir berhasil disimpan", - "date_range": "Jangka tanggal", - "day": "Hari", - "days": "Hari", - "deduplicate_all": "Hapus semua duplikat", - "deduplication_criteria_1": "Ukuran gambar dalam bita", - "deduplication_criteria_2": "Hitungan data EXIF", - "deduplication_info": "Info deduplikasi", - "deduplication_info_description": "Untuk memilih aset secara otomatis dan menghapus duplikat secara massal, kami melihat:", - "default_locale": "Lokal Bawaan", - "default_locale_description": "Format tanggal dan angka berdasarkan peramban lokal Anda", - "delete": "Hapus", - "delete_action_confirmation_message": "Apakah Anda yakin ingin menghapus aset ini? Tindakan ini akan memindahkan aset ke tempat sampah pada server dan akan mengkonfirmasi apakah Anda ingin menghapusnya juga secara lokal", - "delete_action_prompt": "Sebanyak {count} item telah dihapus", - "delete_album": "Hapus album", - "delete_api_key_prompt": "Apakah Anda yakin ingin menghapus kunci API ini?", - "delete_dialog_alert": "Item ini akan dihapus permanen dari Immich dan perangkat", - "delete_dialog_alert_local": "Item ini akan dihapus secara permanen dari perangkat Anda, tapi masih akan tetap tersedia di server Immich", - "delete_dialog_alert_local_non_backed_up": "Beberapa item belum dicadangkan ke Immich dan akan dihapus secara permanen dari perangkat Anda", - "delete_dialog_alert_remote": "Item ini akan dihapus secara permanen dari server Immich", - "delete_dialog_ok_force": "Lanjutkan Hapus", - "delete_dialog_title": "Hapus Permanen", - "delete_duplicates_confirmation": "Apakah Anda yakin ingin menghapus duplikat ini secara permanen?", - "delete_face": "Hapus wajah", - "delete_key": "Hapus kunci", - "delete_library": "Hapus Pustaka", - "delete_link": "Hapus tautan", - "delete_local_action_prompt": "Sebanyak {count} item telah dihapus secara lokal", - "delete_local_dialog_ok_backed_up_only": "Hapus hanya yang telah dicadangkan", - "delete_local_dialog_ok_force": "Lanjutkan Hapus", - "delete_others": "Hapus lainnya", - "delete_permanently": "Hapus permanen", - "delete_permanently_action_prompt": "Sebanyak {count} item telah dihapus secara permanen", - "delete_shared_link": "Hapus tautan terbagi", - "delete_shared_link_dialog_title": "Hapus Link Berbagi", - "delete_tag": "Hapus tag", - "delete_tag_confirmation_prompt": "Apakah Anda yakin ingin menghapus label tag {tagName}?", - "delete_user": "Hapus pengguna", - "deleted_shared_link": "Tautan terbagi dihapus", - "deletes_missing_assets": "Menghapus aset yang hilang dari disk", - "description": "Deskripsi", - "description_input_hint_text": "Tambah deskripsi...", - "description_input_submit_error": "Terjadi kesalahan saat memperbarui deskripsi, periksa log untuk detail selengkapnya", - "deselect_all": "Batalkan semua pilihan", - "details": "Detail", - "direction": "Arah", - "disable": "Nonaktifkan", - "disabled": "Dinonaktifkan", - "disallow_edits": "Jangan izinkan penyuntingan", - "discord": "Discord", - "discover": "Jelajahi", - "discovered_devices": "Perangkat yang ditemukan", - "dismiss_all_errors": "Abaikan semua kesalahan", - "dismiss_error": "Abaikan kesalahan", - "display_options": "Opsi penampilan", - "display_order": "Urutan penampilan", - "display_original_photos": "Tampilkan foto asli", - "display_original_photos_setting_description": "Lebih suka menampilkan foto ketika menampilkan aset daripada gambar kecil ketika aset asli kompatibel dengan web. Ini dapat mengakibatkan kecepatan pemuatan foto yang lebih lambat.", - "do_not_show_again": "Jangan tampilkan pesan ini lagi", - "documentation": "Dokumentasi", - "done": "Selesai", - "download": "Unduh", - "download_action_prompt": "Sedang mengunduh {count} aset", - "download_canceled": "Unduhan dibatalkan", - "download_complete": "Unduhan selesai", - "download_enqueue": "Unduhan diantrikan", - "download_error": "Kesalahan unduh", - "download_failed": "Unduhan gagal", - "download_finished": "Unduhan selesai", - "download_include_embedded_motion_videos": "Video tertanam", - "download_include_embedded_motion_videos_description": "Sertakan video yang di sematkan dalam foto bergerak sebagai file terpisah", - "download_notfound": "Unduhan tidak ditemukan", - "download_original": "Unduh berkas asli", - "download_paused": "Unduhan dijeda", - "download_settings": "Unduhan", - "download_settings_description": "Kelola pengaturan berkaitan dengan pengunduhan aset", - "download_started": "Unduhan dimulai", - "download_sucess": "Unduhan sukses", - "download_sucess_android": "Media ini telah diunduh ke DCIM/Immich", - "download_waiting_to_retry": "Menunggu untuk mencoba lagi", - "downloading": "Mengunduh", - "downloading_asset_filename": "Mengunduh aset {filename}", - "downloading_from_icloud": "Mengunduh dari iCloud", - "downloading_media": "Mengunduh media", - "drop_files_to_upload": "Lepaskan berkas di mana saja untuk mengunggah", - "duplicates": "Duplikat", - "duplicates_description": "Selesaikan setiap kelompok dengan menunjukkan mana, jika ada, yang merupakan duplikat", - "duration": "Durasi", - "edit": "Sunting", - "edit_album": "Sunting album", - "edit_avatar": "Sunting avatar", - "edit_birthday": "Sunting tanggal lahir", - "edit_date": "Tanggal penyuntingan", - "edit_date_and_time": "Sunting tanggal dan waktu", - "edit_date_and_time_action_prompt": "Sebanyak {count} tanggal dan waktu disunting", - "edit_date_and_time_by_offset": "Ubah tanggal berdasarkan selisih", - "edit_date_and_time_by_offset_interval": "Rentang tanggal baru: {from} - {to}", - "edit_description": "Sunting deskripsi", - "edit_description_prompt": "Silakan pilih deskripsi baru:", - "edit_exclusion_pattern": "Sunting pola pengecualian", - "edit_faces": "Sunting wajah", - "edit_key": "Sunting kunci", - "edit_link": "Sunting tautan", - "edit_location": "Sunting lokasi", - "edit_location_action_prompt": "Sebanyak {count} lokasi telah disunting", - "edit_location_dialog_title": "Lokasi", - "edit_name": "Sunting nama", - "edit_people": "Sunting orang", - "edit_tag": "Ubah tag", - "edit_title": "Sunting Judul", - "edit_user": "Sunting pengguna", - "edit_workflow": "Sunting alur kerja", - "editor": "Penyunting", - "editor_close_without_save_prompt": "Perubahan tidak akan di simpan", - "editor_close_without_save_title": "Tutup editor?", - "editor_confirm_reset_all_changes": "Apakah anda yakin mau mengatur ulang semua perubahan?", - "editor_flip_horizontal": "Balik horizontal", - "editor_flip_vertical": "Balik vertikal", - "editor_orientation": "Orientasi", - "editor_reset_all_changes": "Mengatur ulang perubahan", - "editor_rotate_left": "Putar 90° berlawanan arah jarum jam", - "editor_rotate_right": "Putar 90° searah jarum jam", - "email": "Surel", - "email_notifications": "Notifikasi surel", - "empty_folder": "Folder ini kosong", - "empty_trash": "Kosongkan sampah", - "empty_trash_confirmation": "Apakah Anda yakin ingin mengosongkan sampah? Ini akan menghapus semua aset dalam sampah secara permanen dari Immich.\nAnda tidak dapat mengurungkan tindakan ini!", - "enable": "Aktifkan", - "enable_backup": "Aktifkan Pencadangan", - "enable_biometric_auth_description": "Masukkan kode PIN Anda untuk mengaktifkan autentikasi biometrik", - "enabled": "Diaktifkan", - "end_date": "Tanggal akhir", - "enqueued": "Diantrikan", - "enter_wifi_name": "Masukkan nama Wi-Fi", - "enter_your_pin_code": "Masukkan kode PIN Anda", - "enter_your_pin_code_subtitle": "Masukkan kode PIN Anda untuk mengakses folder terkunci", - "error": "Eror", - "error_change_sort_album": "Gagal mengubah urutan album", - "error_delete_face": "Terjadi kesalahan menghapus wajah dari aset", - "error_getting_places": "Kesalahan saat mengambil lokasi", - "error_loading_albums": "Gagal memuat album", - "error_loading_image": "Terjadi eror memuat gambar", - "error_loading_partners": "Kesalahan saat memuat partner: {error}", - "error_retrieving_asset_information": "Gagal mendapatkan informasi aset", - "error_saving_image": "Kesalahan: {error}", - "error_tag_face_bounding_box": "Galat saat memberi tag wajah – tidak dapat memperoleh koordinat kotak pembatas", - "error_title": "Eror - Ada yang salah", - "error_while_navigating": "Gagal saat berpindah ke aset", - "errors": { - "cannot_navigate_next_asset": "Tidak dapat menuju ke aset berikutnya", - "cannot_navigate_previous_asset": "Tidak dapat menuju ke aset sebelumnya", - "cant_apply_changes": "Tidak dapat menerapkan perubahan", - "cant_change_activity": "Tidak dapat {enabled, select, true {menonaktifkan} other {mengaktifkan}} aktivitas", - "cant_change_asset_favorite": "Tidak dapat mengubah favorit untuk aset", - "cant_change_metadata_assets_count": "Tidak dapat mengubah metadata {count, plural, one {# aset} other {# aset}}", - "cant_get_faces": "Tidak bisa mendapatkan wajah", - "cant_get_number_of_comments": "Tidak bisa mendapatkan jumlah komentar", - "cant_search_people": "Tidak dapat mencari orang", - "cant_search_places": "Tidak dapat mencari tempat", - "error_adding_assets_to_album": "Terjadi eror menambahkan aset ke album", - "error_adding_users_to_album": "Terjadi kesalahan menambahkan pengguna ke album", - "error_deleting_shared_user": "Terjadi eror menghapus pengguna terbagi", - "error_downloading": "Terjadi eror mengunduh {filename}", - "error_hiding_buy_button": "Kesalahan menyembunyikan tombol beli", - "error_removing_assets_from_album": "Terjadi eror menghapus aset dari album, lihat konsol untuk detail lebih lanjut", - "error_selecting_all_assets": "Terjadi eror memilih semua aset", - "exclusion_pattern_already_exists": "Pola pengecualian ini sudah ada.", - "failed_to_create_album": "Gagal membuat album", - "failed_to_create_shared_link": "Gagal membuat tautan terbagi", - "failed_to_edit_shared_link": "Gagal menyunting tautan terbagi", - "failed_to_get_people": "Gagal menangkap orang", - "failed_to_keep_this_delete_others": "Gagal menyimpan aset ini dan menghapus aset-aset lainnya", - "failed_to_load_asset": "Gagal membuka aset", - "failed_to_load_assets": "Gagal membuka aset-aset", - "failed_to_load_notifications": "Gagal memuat notifikasi", - "failed_to_load_people": "Gagal mengunggah orang", - "failed_to_remove_product_key": "Gagal menghapus kunci produk", - "failed_to_reset_pin_code": "Gagal atur ulang kode PIN", - "failed_to_stack_assets": "Gagal menumpuk aset", - "failed_to_unstack_assets": "Gagal membatalkan penumpukan aset", - "failed_to_update_notification_status": "Gagal membarui status notifikasi", - "incorrect_email_or_password": "Surel atau kata sandi tidak benar", - "library_folder_already_exists": "Jalur impor ini sudah ada.", - "paths_validation_failed": "{paths, plural, one {# jalur} other {# jalur}} gagal validasi", - "profile_picture_transparent_pixels": "Foto profil tidak dapat memiliki piksel transparan. Silakan perbesar dan/atau pindah posisi gambar.", - "quota_higher_than_disk_size": "Anda menetapkan kuota lebih tinggi dari ukuran disk", - "something_went_wrong": "Terjadi kesalahan", - "unable_to_add_album_users": "Tidak dapat menambahkan pengguna ke album", - "unable_to_add_assets_to_shared_link": "Tidak dapat menambahkan aset ke tautan terbagi", - "unable_to_add_comment": "Tidak dapat menambahkan komentar", - "unable_to_add_exclusion_pattern": "Tidak dapat menambahkan pola pengecualian", - "unable_to_add_partners": "Tidak dapat menambahkan rekan", - "unable_to_add_remove_archive": "Tidak dapat {archived, select, true {menghapus aset dari} other {menambahkan aset ke}} arsip", - "unable_to_add_remove_favorites": "Tidak dapat {favorite, select, true {menambahkan aset ke} other {menghapus aset dari}} favorit", - "unable_to_archive_unarchive": "Tidak dapat {archived, select, true {mengarsipkan} other {membatalkan pengarsipan}}", - "unable_to_change_album_user_role": "Tidak dapat mengubah peran pengguna album", - "unable_to_change_date": "Tidak dapat mengubah tanggal", - "unable_to_change_description": "Tidak dapat mengubah deskripsi", - "unable_to_change_favorite": "Tidak dapat mengubah favorit untuk aset", - "unable_to_change_location": "Tidak dapat mengubah lokasi", - "unable_to_change_password": "Tidak dapat mengubah kata sandi", - "unable_to_change_visibility": "Tidak dapat mengubah keterlihatan untuk {count, plural, one {# orang} other {# orang}}", - "unable_to_complete_oauth_login": "Tidak dapat menyelesaikan log masuk OAuth", - "unable_to_connect": "Tidak dapat menghubungkan", - "unable_to_copy_to_clipboard": "Tidak dapat menyalin ke papan klip, pastikan Anda mengakses laman ini melalui HTTPS", - "unable_to_create": "Tidak dapat membuat alur kerja", - "unable_to_create_admin_account": "Tidak dapat membuat akun admin", - "unable_to_create_api_key": "Tidak dapat membuat Kunci API baru", - "unable_to_create_library": "Tidak dapat membuat pustaka", - "unable_to_create_user": "Tidak dapat membuat pengguna", - "unable_to_delete_album": "Tidak dapat menghapus album", - "unable_to_delete_asset": "Tidak dapat menghapus aset", - "unable_to_delete_assets": "Terjadi eror menghapus aset", - "unable_to_delete_exclusion_pattern": "Tidak dapat menghapus pola pengecualian", - "unable_to_delete_shared_link": "Tidak dapat menghapus tautan terbagi", - "unable_to_delete_user": "Tidak dapat menghapus pengguna", - "unable_to_delete_workflow": "Tidak dapat menghapus alur kerja", - "unable_to_download_files": "Tidak dapat mengunduh berkas", - "unable_to_edit_exclusion_pattern": "Tidak dapat menyunting pola pengecualian", - "unable_to_empty_trash": "Tidak dapat menghapus sampah", - "unable_to_enter_fullscreen": "Tidak dapat memasuki layar penuh", - "unable_to_exit_fullscreen": "Tidak dapat keluar dari layar penuh", - "unable_to_get_comments_number": "Tidak bisa mendapatkan jumlah komentar", - "unable_to_get_shared_link": "Gagal mendapatkan tautan berbagi", - "unable_to_hide_person": "Tidak dapat menyembunyikan orang", - "unable_to_link_motion_video": "Tidak dapat menautkan video gerak", - "unable_to_link_oauth_account": "Tidak dapat menautkan akun OAuth", - "unable_to_log_out_all_devices": "Tidak dapat mengeluarkan dari semua perangkat", - "unable_to_log_out_device": "Tidak dapat mengeluarkan perangkat", - "unable_to_login_with_oauth": "Tidak dapat log masuk dengan OAuth", - "unable_to_play_video": "Tidak dapat memutar video", - "unable_to_reassign_assets_existing_person": "Tidak dapat menetapkan aset kepada {name, select, null {orang yang sudah ada} other {{name}}}", - "unable_to_reassign_assets_new_person": "Tidak dapat menetapkan ulang aset ke orang baru", - "unable_to_refresh_user": "Tidak dapat menyegarkan pengguna", - "unable_to_remove_album_users": "Tidak dapat mengeluarkan pengguna dari album", - "unable_to_remove_api_key": "Tidak dapat menghapus Kunci API", - "unable_to_remove_assets_from_shared_link": "Tidak dapat menghapus aset dari tautan terbagi", - "unable_to_remove_library": "Tidak dapat menghapus pustaka", - "unable_to_remove_partner": "Tidak dapat menghapus partner", - "unable_to_remove_reaction": "Tidak dapat menghapus reaksi", - "unable_to_reset_password": "Tidak dapat mengatur ulang kata sandi", - "unable_to_reset_pin_code": "Tidak dapat mereset kode PIN", - "unable_to_resolve_duplicate": "Tidak dapat menyelesaikan duplikat", - "unable_to_restore_assets": "Tidak dapat memulihkan aset", - "unable_to_restore_trash": "Tidak dapat memulihkan sampah", - "unable_to_restore_user": "Tidak dapat memulihkan pengguna", - "unable_to_save_album": "Tidak dapat menyimpan album", - "unable_to_save_api_key": "Tidak dapat menyimpan Kunci API", - "unable_to_save_date_of_birth": "Tidak dapat menyimpan tanggal lahir", - "unable_to_save_name": "Tidak dapat menyimpan nama", - "unable_to_save_profile": "Tidak dapat menyimpan profil", - "unable_to_save_settings": "Tidak dapat menyimpan pengaturan", - "unable_to_scan_libraries": "Tidak dapat memindai pustaka", - "unable_to_scan_library": "Tidak dapat memindai pustaka", - "unable_to_set_feature_photo": "Tidak dapat menyeting foto unggulan", - "unable_to_set_profile_picture": "Tidak dapat mengatur foto profil", - "unable_to_set_rating": "Tidak dapat mengatur penilaian", - "unable_to_submit_job": "Tidak dapat mengirim tugas", - "unable_to_trash_asset": "Tidak dapat membuang aset", - "unable_to_unlink_account": "Tidak dapat memutuskan akun", - "unable_to_unlink_motion_video": "Tidak dapat membatalkan tautan video gerak", - "unable_to_update_album_cover": "Tidak dapat memperbarui kover album", - "unable_to_update_album_info": "Tidak dapat memperbarui info album", - "unable_to_update_library": "Tidak dapat memperbarui pustaka", - "unable_to_update_location": "Tidak dapat memperbarui lokasi", - "unable_to_update_settings": "Tidak dapat memperbarui pengaturan", - "unable_to_update_timeline_display_status": "Tidak dapat memperbarui status penampilan lini masa", - "unable_to_update_user": "Tidak dapat memperbarui pengguna", - "unable_to_update_workflow": "Tidak dapat memperbarui alur kerja", - "unable_to_upload_file": "Tidak dapat mengunggah berkas" - }, - "errors_text": "Gagal", - "exclusion_pattern": "Pola pengecualian", - "exif": "EXIF", - "exif_bottom_sheet_description": "Tambahkan Deskripsi...", - "exif_bottom_sheet_description_error": "Galat saat memperbaharui deskripsi", - "exif_bottom_sheet_details": "RINCIAN", - "exif_bottom_sheet_location": "LOKASI", - "exif_bottom_sheet_no_description": "Tidak ada deskripsi", - "exif_bottom_sheet_people": "ORANG", - "exif_bottom_sheet_person_add_person": "Tambah nama", - "exit_slideshow": "Keluar dari Salindia", - "expand_all": "Buka semua", - "experimental_settings_new_asset_list_subtitle": "Memproses", - "experimental_settings_new_asset_list_title": "Nyalakan grid foto eksperimental", - "experimental_settings_subtitle": "Gunakan atas risiko anda sendiri!", - "experimental_settings_title": "Eksperimental", - "expire_after": "Kedaluwarsa setelah", - "expired": "Kedaluwarsa", - "expires_date": "Kedaluwarsa pada {date}", - "explore": "Jelajahi", - "explorer": "Jelajah", - "export": "Ekspor", - "export_as_json": "Ekspor sebagai JSON", - "export_database": "Ekspor Database", - "export_database_description": "Ekspor Database SQLite", - "extension": "Ekstensi", - "external": "Eksternal", - "external_libraries": "Pustaka Eksternal", - "external_network": "Jaringan Eksternal", - "external_network_sheet_info": "Ketika tidak berada di jaringan Wi-Fi yang disukai, aplikasi akan terhubung ke server melalui URL pertama di bawah ini yang dapat dijangkaunya, mulai dari atas ke bawah", - "face_unassigned": "Tidak ada nama", - "failed": "Gagal", - "failed_count": "Gagal: {count}", - "failed_to_authenticate": "Autentikasi gagal", - "failed_to_load_assets": "Gagal memuat aset", - "failed_to_load_folder": "Gagal memuat berkas", - "favorite": "Favorit", - "favorite_action_prompt": "{count} sudah ditambahkan kedalam Favorites", - "favorite_or_unfavorite_photo": "Favorit atau batalkan pemfavoritan foto", - "favorites": "Favorit", - "favorites_page_no_favorites": "Tidak ada aset favorit", - "feature_photo_updated": "Foto terfitur diperbarui", - "features": "Fitur", - "features_in_development": "Fitur dalam Pengembangan", - "features_setting_description": "Kelola fitur aplikasi", - "file_name_or_extension": "Nama berkas atau ekstensi", - "file_size": "Ukuran berkas", - "filename": "Nama berkas", - "filetype": "Jenis berkas", - "filter": "Filter", - "filter_description": "Kondisi untuk memfilter aset-aset target", - "filter_people": "Saring orang", - "filter_places": "Saring tempat", - "filters": "Filter-filter", - "find_them_fast": "Temukan dengan cepat berdasarkan nama dengan pencarian", - "first": "Pertama", - "fix_incorrect_match": "Perbaiki pencocokan salah", - "folder": "Berkas", - "folder_not_found": "Berkas tidak ditemukan", - "folders": "Berkas", - "folders_feature_description": "Menjelajahi tampilan folder untuk foto dan video pada sistem file", - "forgot_pin_code_question": "Lupa PIN?", - "forward": "Maju", - "free_up_space": "Bebaskan ruang", - "full_path": "Jalur lengkap: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Fitur ini memuat sumber daya eksternal dari Google agar dapat berfungsi.", - "general": "Umum", - "geolocation_instruction_location": "Klik aset yang memiliki koordinat GPS untuk menggunakan lokasinya, atau pilih lokasi langsung dari peta", - "get_help": "Dapatkan Bantuan", - "get_people_error": "Kesalahan dalam mendapatkan orang-orang", - "get_wifiname_error": "Tidak dapat mendapatkan nama Wi-Fi. Pastikan Anda telah memberikan izin yang diperlukan dan terhubung ke jaringan Wi-Fi", - "getting_started": "Memulai", - "go_back": "Kembali", - "go_to_folder": "Pergi ke folder", - "go_to_search": "Pergi ke pencarian", - "gps": "GPS", - "gps_missing": "Tidak ada GPS", - "grant_permission": "Izinkan", - "group_albums_by": "Kelompokkan album berdasarkan...", - "group_country": "Kelompokkan berdasarkan negara", - "group_no": "Tidak ada pengelompokan", - "group_owner": "Kelompokkan berdasarkan pemilik", - "group_places_by": "Kelompokkan tempat berdasarkan…", - "group_year": "Kelompokkan berdasarkan tahun", - "haptic_feedback_switch": "Nyalakan getar", - "haptic_feedback_title": "Getar", - "has_quota": "Memiliki kuota", - "hash_asset": "Aset Hash", - "hashed_assets": "Aset yang di-hash", - "hashing": "Proses Hash", - "header_settings_add_header_tip": "Tambahkan header", - "header_settings_field_validator_msg": "Nilai tidak boleh kosong", - "header_settings_header_name_input": "Nama header", - "header_settings_header_value_input": "Nilai header", - "headers_settings_tile_title": "Header proksi kustom", - "height": "Tinggi", - "hi_user": "Hai {name} ({email})", - "hide_all_people": "Sembunyikan semua orang", - "hide_gallery": "Sembunyikan galeri", - "hide_named_person": "Sembunyikan orang {name}", - "hide_password": "Sembunyikan kata sandi", - "hide_person": "Sembunyikan orang", - "hide_schema": "Sembunyikan skema", - "hide_text_recognition": "Sembunyikan teks rekognisi", - "hide_unnamed_people": "Sembunyikan orang tanpa nama", - "home_page_add_to_album_conflicts": "Aset {added} telah ditambahkan ke album {album}. Aset {failed} sudah ada dalam album.", - "home_page_add_to_album_err_local": "Belum dapat menambahkan aset lokal ke album, dilewati", - "home_page_add_to_album_success": "Aset {added} telah ditambahkan ke {album}.", - "home_page_album_err_partner": "Belum dapat menambahkan aset pasangan ke album, dilewati", - "home_page_archive_err_local": "Belum dapat mengarsipkan aset lokal, dilewati", - "home_page_archive_err_partner": "Tidak dapat mengarsipkan aset pasangan, dilewati", - "home_page_building_timeline": "Memuat linimasa", - "home_page_delete_err_partner": "Tidak dapat menghapus aset pasangan, dilewati", - "home_page_delete_remote_err_local": "Aset lokal ada dalam pilihan hapus jarak jauh, dilewat", - "home_page_favorite_err_local": "Belum dapat menandai aset lokal sebagai favorit, dilewati", - "home_page_favorite_err_partner": "Belum dapat menandai aset pasangan sebagai favorit, dilewati", - "home_page_first_time_notice": "Jika ini pertama kali Anda menggunakan aplikasi, pastikan untuk memiliki album untuk dicadangkan agar lini masa anda terisi oleh foto dan video dalam album", - "home_page_locked_error_local": "Tidak dapat memindahkan aset lokal ke folder terkunci, lewati", - "home_page_locked_error_partner": "Tidak dapat memindahkan aset partner ke folder terkunci, lewati", - "home_page_share_err_local": "Tidak bisa membagikan lokal aset melalui tautan, dilewati", - "home_page_upload_err_limit": "Hanya dapat mengunggah maksimal 30 aset dalam satu waktu, melewatkan", - "host": "Hos", - "hour": "Jam", - "hours": "Jam", - "id": "ID", - "idle": "Siaga", - "ignore_icloud_photos": "Abaikan foto iCloud", - "ignore_icloud_photos_description": "Foto yang disimpan di iCloud tidak akan diunggah ke server Immich", - "image": "Gambar", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} pada tanggal {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} diambil oleh {person1} pada {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} diambil oleh {person1} dan {person2} pada {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} diambil oleh {person1}, {person2}, dan {person3} pada {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} diambil oleh {person1}, {person2}, dan {additionalCount, number} lainnya pada {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} diambil di {city}, {country} pada {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} diambil di {city}, {country} oleh {person1} pada {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} diambil di {city}, {country} oleh {person1} dan {person2} pada {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} diambil di {city}, {country} oleh {person1}, {person2}, dan {person3} pada {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} diambil di {city}, {country} oleh {person1}, {person2}, dan {additionalCount, number} lainnya pada {date}", - "image_saved_successfully": "Gambar disimpan", - "image_viewer_page_state_provider_download_started": "Unduh dimulai", - "image_viewer_page_state_provider_download_success": "Unduh Sukses", - "image_viewer_page_state_provider_share_error": "Galat membagikan", - "immich_logo": "Logo Immich", - "immich_web_interface": "Antarmuka Web Immich", - "import_from_json": "Impor dari JSON", - "import_path": "Jalur pengimporan", - "in_albums": "Dalam {count, plural, one {# album} other {# album}}", - "in_archive": "Dalam arsip", - "in_year": "Dalam {year}", - "in_year_selector": "Dalam", - "include_archived": "Termasuk terarsip", - "include_shared_albums": "Termasuk album terbagi", - "include_shared_partner_assets": "Termasuk aset terbagi dengan partner", - "individual_share": "Bagikan individu", - "individual_shares": "Pembagian individu", - "info": "Info", - "interval": { - "day_at_onepm": "Setiap hari pada 13.00", - "hours": "Setiap {hours, plural, one {jam} other {{hours, number} jam}}", - "night_at_midnight": "Setiap malam pada 00.00", - "night_at_twoam": "Setiap malam pada 02.00" - }, - "invalid_date": "Tanggal tidak valid", - "invalid_date_format": "Format Tanggal tidak valid", - "invite_people": "Undang Orang", - "invite_to_album": "Undang ke album", - "ios_debug_info_fetch_ran_at": "Pengambilan dijalankan {dateTime}", - "ios_debug_info_last_sync_at": "Sinkronisasi terakhir {dateTime}", - "ios_debug_info_no_processes_queued": "Tidak ada proses latar belakang dalam antrean", - "ios_debug_info_no_sync_yet": "Belum ada pekerjaan sinkronisasi latar belakang yang dijalankan", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proses latar belakang dalam antrean} other {{count} proses latar belakang dalam antrean}}", - "ios_debug_info_processing_ran_at": "Pemrosesan dijalankan {dateTime}", - "items_count": "{count, plural, one {# item} other {# item}}", - "jobs": "Tugas", - "json_editor": "Editor JSON", - "json_error": "Kesalahan JSON", - "keep": "Simpan", - "keep_all": "Simpan Semua", - "keep_this_delete_others": "Pertahankan ini, hapus lainnya", - "kept_this_deleted_others": "Aset ini dipertahankan dan {count, plural, one {# asset} other {# assets}} dihapus", - "keyboard_shortcuts": "Pintasan papan ketik", - "language": "Bahasa", - "language_no_results_subtitle": "Coba sesuaikan kata kunci pencarian Anda", - "language_no_results_title": "Bahasa tidak ditemukan", - "language_search_hint": "Mencari Bahasa...", - "language_setting_description": "Pilih bahasa Anda yang disukai", - "large_files": "File Besar", - "last": "Terakhir", - "last_months": "{count, plural, one {Bulan lalu} other {# Bulan lalu}}", - "last_seen": "Terakhir dilihat", - "latest_version": "Versi Terkini", - "latitude": "Lintang", - "leave": "Tinggalkan", - "leave_album": "Keluar dari album", - "lens_model": "Model lensa", - "let_others_respond": "Biarkan orang lain merespons", - "level": "Tingkat", - "library": "Pustaka", - "library_add_folder": "Tambahkan folder", - "library_edit_folder": "Sunting folder", - "library_options": "Opsi pustaka", - "library_page_device_albums": "Album pada Perangkat", - "library_page_new_album": "Album baru", - "library_page_sort_asset_count": "Jumlah aset", - "library_page_sort_created": "Tanggal dibuat", - "library_page_sort_last_modified": "Terakhir diubah", - "library_page_sort_title": "Judul album", - "licenses": "Lisensi", - "light": "Terang", - "like": "Suka", - "like_deleted": "Suka dihapus", - "link_motion_video": "Tautan video gerak", - "link_to_oauth": "Tautkan ke OAuth", - "linked_oauth_account": "Akun OAuth tertaut", - "list": "Daftar", - "loading": "Memuat", - "loading_search_results_failed": "Pemuatan hasil pencarian gagal", - "local": "Lokal", - "local_asset_cast_failed": "Tidak dapat melakukan cast aset yang belum diunggah ke server", - "local_assets": "Aset Lokal", - "local_id": "ID Lokal", - "local_media_summary": "Ringkasan Media Lokal", - "local_network": "Jaringan Lokal", - "local_network_sheet_info": "Aplikasi akan terhubung ke server melalui URL ini saat menggunakan jaringan Wi-Fi yang ditentukan", - "location": "Lokasi", - "location_permission": "Izin lokasi", - "location_permission_content": "Untuk menggunakan fitur pengalihan otomatis, Immich memerlukan izin lokasi yang akurat agar dapat membaca nama jaringan Wi-Fi saat ini", - "location_picker_choose_on_map": "Pilih di peta", - "location_picker_latitude_error": "Masukkan lintang yang sah", - "location_picker_latitude_hint": "Masukkan lintang di sini", - "location_picker_longitude_error": "Masukkan bujur yang sah", - "location_picker_longitude_hint": "Masukkan bujur di sini", - "lock": "Kunci", - "locked_folder": "Folder Terkunci", - "log_detail_title": "Detail Log", - "log_out": "Log keluar", - "log_out_all_devices": "Keluar dari Semua Perangkat", - "logged_in_as": "Masuk sebagai {user}", - "logged_out_all_devices": "Semua perangkat telah dikeluarkan", - "logged_out_device": "Perangkat telah keluar", - "login": "Log masuk", - "login_disabled": "Login telah dimatikan", - "login_form_api_exception": "Kesalahan API. Harap periksa URL server dan coba lagi.", - "login_form_back_button_text": "Kembali", - "login_form_email_hint": "emailmu@email.com", - "login_form_endpoint_hint": "http://ip-server-anda:port", - "login_form_endpoint_url": "URL Endpoint Server", - "login_form_err_http": "Harap tentukan http:// atau https://", - "login_form_err_invalid_email": "Email Tidak Valid", - "login_form_err_invalid_url": "URL Tidak Valid", - "login_form_err_leading_whitespace": "Spasi awal", - "login_form_err_trailing_whitespace": "Spasi akhir", - "login_form_failed_get_oauth_server_config": "Gagal logging menggunakan OAuth, periksa URL server", - "login_form_failed_get_oauth_server_disable": "Fitur OAuth tidak tersedia di server ini", - "login_form_failed_login": "Login gagal, periksa URL server, email dan kata sandi", - "login_form_handshake_exception": "Terjadi Handshake Exception dengan server. Aktifkan dukungan sertifikat swatanda di pengaturan jika Anda menggunakan sertifikat swatanda.", - "login_form_password_hint": "sandi", - "login_form_save_login": "Ingat saya", - "login_form_server_empty": "Masukkan URL server.", - "login_form_server_error": "Tidak dapat menghubungi server.", - "login_has_been_disabled": "Log masuk telah dinonaktifkan.", - "login_password_changed_error": "Terdapat kesalahan mengganti password", - "login_password_changed_success": "Sandi berhasil diperbarui", - "logout_all_device_confirmation": "Apakah Anda yakin ingin keluar dari semua perangkat?", - "logout_this_device_confirmation": "Apakah Anda yakin ingin mengeluarkan perangkat ini?", - "logs": "Log", - "longitude": "Bujur", - "look": "Tampilan", - "loop_videos": "Ulangi video", - "loop_videos_description": "Aktifkan untuk mengulangi video secara otomatis dalam penampil detail.", - "main_branch_warning": "Anda menggunakan versi pengembangan; kami sangat menyarankan menggunakan versi rilis!", - "main_menu": "Menu utama", - "maintenance_description": "Immich telah ditempatkan di mode pemeliharaan.", - "maintenance_end": "Akhiri mode pemeliharaan", - "maintenance_end_error": "Gagal mengakhiri mode pemeliharaan.", - "maintenance_logged_in_as": "Saat ini masuk sebagai {user}", - "maintenance_title": "Tidak Tersedia untuk Sementara", - "make": "Merek", - "manage_geolocation": "Atur lokasi", - "manage_media_access_rationale": "Izin ini diperlukan untuk menangani perpindahan aset-aset secara tepat ke tempat sampah dan mengembalikannya dari sana.", - "manage_media_access_settings": "Buka pengaturan", - "manage_media_access_subtitle": "Izinkan aplikasi Immich untuk mengelola dan memindahkan berkas media.", - "manage_media_access_title": "Akses Manajemen Media", - "manage_shared_links": "Kelola tautan terbagi", - "manage_sharing_with_partners": "Kelola pembagian dengan partner", - "manage_the_app_settings": "Kelola pengaturan aplikasi", - "manage_your_account": "Kelola akun Anda", - "manage_your_api_keys": "Kelola kunci API Anda", - "manage_your_devices": "Kelola perangkat Anda yang masuk", - "manage_your_oauth_connection": "Kelola koneksi OAuth Anda", - "map": "Peta", - "map_assets_in_bounds": "{count, plural, =0 {Tidak ada foto di area ini} one {# foto} other {# foto}}", - "map_cannot_get_user_location": "Tidak dapat memeroleh lokasi pengguna", - "map_location_dialog_yes": "Ya", - "map_location_picker_page_use_location": "Gunakan lokasi ini", - "map_location_service_disabled_content": "Layanan lokasi perlu diaktifkan untuk menampilkan aset yang terletak di lokasi kamu saat ini. Apakah kamu ingin mengaktifkan layanan tersebut sekarang?", - "map_location_service_disabled_title": "Layanan Lokasi nonaktif", - "map_marker_for_images": "Penanda peta untuk gambar yang diambil di {city}, {country}", - "map_marker_with_image": "Penanda peta dengan gambar", - "map_no_location_permission_content": "Izin lokasi diperlukan untuk menampilkan aset yang terletak di lokasi kamu. Apakah kamu ingin mengizinkan sekarang?", - "map_no_location_permission_title": "Izin Lokasi ditolak", - "map_settings": "Pengaturan peta", - "map_settings_dark_mode": "Mode gelap", - "map_settings_date_range_option_day": "24 jam terakhir", - "map_settings_date_range_option_days": "{days} hari terakhir", - "map_settings_date_range_option_year": "1 tahun terakhir", - "map_settings_date_range_option_years": "{years} tahun terakhir", - "map_settings_dialog_title": "Pengaturan Peta", - "map_settings_include_show_archived": "Sertakan yang diarsipkan", - "map_settings_include_show_partners": "Sertakan Pasangan", - "map_settings_only_show_favorites": "Tampilkan Hanya Favorit", - "map_settings_theme_settings": "Tema Peta", - "map_zoom_to_see_photos": "Perkecil untuk lihat foto", - "mark_all_as_read": "Tandai semua sebagai telah dibaca", - "mark_as_read": "Tandai sebagai telah dibaca", - "marked_all_as_read": "Semua telah ditandai sebagai telah dibaca", - "matches": "Cocokan", - "matching_assets": "Aset yang Cocok", - "media_type": "Jenis media", - "memories": "Kenangan", - "memories_all_caught_up": "Semua telah dilihat", - "memories_check_back_tomorrow": "Lihat lagi besok untuk kenangan lainnya", - "memories_setting_description": "Kelola apa yang Anda lihat dalam kenangan Anda", - "memories_start_over": "Ulang Dari Awal", - "memories_swipe_to_close": "Geser ke atas untuk menutup", - "memory": "Kenangan", - "memory_lane_title": "Jalur Kenangan {title}", - "menu": "Menu", - "merge": "Gabungkan", - "merge_people": "Gabungkan orang", - "merge_people_limit": "Anda hanya dapat menggabungkan sampai 5 wajah sekaligus", - "merge_people_prompt": "Apakah Anda ingin menggabungkan orang ini? Tindakan ini tidak dapat diurungkan.", - "merge_people_successfully": "Orang berhasil digabungkan", - "merged_people_count": "{count, plural, one {# orang} other {# orang}} digabung", - "minimize": "Kecilkan", - "minute": "Menit", - "minutes": "Menit", - "missing": "Hilang", - "mobile_app": "Aplikasi Seluler", - "mobile_app_download_onboarding_note": "Unduh aplikasi seluler pendamping dengan menggunakan opsi berikut", - "model": "Model", - "month": "Bulan", - "monthly_title_text_date_format": "BBBB t", - "more": "Lainnya", - "move": "Pindah", - "move_down": "Pindah ke bawah", - "move_off_locked_folder": "Pindahkan dari folder terkunci", - "move_to": "Pindah ke", - "move_to_lock_folder_action_prompt": "{count} ditambahkan ke folder terkunci", - "move_to_locked_folder": "Pindahkan dari folder terkunci", - "move_to_locked_folder_confirmation": "Foto dan video ini akan dihapus dari semua album, dan hanya dapat dilihat dari folder terkunci", - "move_up": "Pindah ke atas", - "moved_to_archive": "Dipindahkan {count, plural, one {# asset} other {# assets}} ke arsip", - "moved_to_library": "Dipindahkan {count, plural, one {# asset} other {# assets}} ke pustaka", - "moved_to_trash": "Dipindahkan ke sampah", - "multiselect_grid_edit_date_time_err_read_only": "Tidak dapat mengedit tanggal aset hanya-baca, dilewati", - "multiselect_grid_edit_gps_err_read_only": "Tidak dapat mengedit lokasi aset hanya-baca, dilewati", - "mute_memories": "Nonaktifkan Kenangan", - "my_albums": "Album saya", - "name": "Nama", - "name_or_nickname": "Nama atau nama panggilan", - "name_required": "Nama diperlukan", - "navigate": "Navigasi", - "navigate_to_time": "Navigasi ke Waktu", - "network_requirement_photos_upload": "Gunakan data seluler untuk cadangkan foto", - "network_requirement_videos_upload": "Gunakan data seluler untuk cadangkan video", - "network_requirements": "Persyaratan Jaringan", - "network_requirements_updated": "Persyaratan jaringan telah berubah, antrean pencadangan diatur ulang", - "networking_settings": "Jaringan", - "networking_subtitle": "Kelola pengaturan Endpoint server", - "never": "Tidak pernah", - "new_album": "Album baru", - "new_api_key": "Kunci API Baru", - "new_date_range": "Rentang tanggal baru", - "new_password": "Kata sandi baru", - "new_person": "Orang baru", - "new_pin_code": "Kode PIN baru", - "new_pin_code_subtitle": "Ini adalah akses pertama Anda ke folder terkunci. Buat kode PIN untuk mengamankan akses ke halaman ini", - "new_timeline": "Linimasa Baru", - "new_update": "Pembaruan baru", - "new_user_created": "Pengguna baru dibuat", - "new_version_available": "VERSI BARU TERSEDIA", - "newest_first": "Terkini dahulu", - "next": "Berikutnya", - "next_memory": "Kenangan berikutnya", - "no": "Tidak", - "no_actions_added": "Belum ada aksi yang ditambahkan", - "no_albums_message": "Buat album untuk mengelola foto dan video Anda", - "no_albums_with_name_yet": "Sepertinya Anda belum memiliki album apa pun dengan nama ini.", - "no_albums_yet": "Sepertinya Anda belum memiliki album apa pun.", - "no_archived_assets_message": "Arsipkan foto dan video untuk menyembunyikannya dari tampilan Foto", - "no_assets_message": "KLIK UNTUK MENGUNGGAH FOTO PERTAMA ANDA", - "no_assets_to_show": "Tidak ada aset", - "no_cast_devices_found": "Tidak ada perangkat cast yang ditemukan", - "no_checksum_local": "Tidak ada checksum yang tersedia - tidak dapat mengambil aset lokal", - "no_checksum_remote": "Tidak ada checksum yang tersedia - tidak dapat mengambil aset jarak jauh", - "no_configuration_needed": "Tidak ada konfigurasi yang diperlukan", - "no_devices": "Tidak ada perangkat terotorisasi", - "no_duplicates_found": "Tidak ada duplikat yang ditemukan.", - "no_exif_info_available": "Tidak ada info EXIF yang tersedia", - "no_explore_results_message": "Unggah lebih banyak foto untuk menjelajahi koleksi Anda.", - "no_favorites_message": "Tambahkan favorit untuk mencari foto dan video terbaik Anda dengan cepat", - "no_filters_added": "Belum ada filter yang ditambahkan", - "no_libraries_message": "Buat pustaka eksternal untuk menampilkan foto dan video Anda", - "no_local_assets_found": "Tidak ada aset lokal yang ditemukan dengan checksum ini", - "no_location_set": "Tidak ada lokasi yang ditetapkan", - "no_locked_photos_message": "Foto dan video di folder terkunci disembunyikan dan tidak akan muncul saat Anda menelusuri atau mencari di pustaka.", - "no_name": "Tidak Ada Nama", - "no_notifications": "Tidak ada notifikasi", - "no_people_found": "Orang tidak ditemukan", - "no_places": "Tidak ada tempat", - "no_remote_assets_found": "Tidak ada aset jarak jauh yang ditemukan dengan checksum ini", - "no_results": "Tidak ada hasil", - "no_results_description": "Coba sinonim atau kata kunci yang lebih umum", - "no_shared_albums_message": "Buat sebuah album untuk membagikan foto dan video dengan orang-orang dalam jaringan Anda", - "no_uploads_in_progress": "Tidak ada unggahan yang sedang berlangsung", - "not_allowed": "Tidak diperbolehkan", - "not_available": "T/T", - "not_in_any_album": "Tidak ada dalam album apa pun", - "not_selected": "Belum dipilih", - "note_apply_storage_label_to_previously_uploaded assets": "Catatan: Untuk menerapkan Label Penyimpanan pada aset yang sebelumnya telah diunggah, jalankan", - "notes": "Catatan", - "nothing_here_yet": "Masih kosong", - "notification_permission_dialog_content": "Untuk mengaktifkan notifikasi, buka Pengaturan lalu berikan izin.", - "notification_permission_list_tile_content": "Berikan izin untuk mengaktifkan notifikasi.", - "notification_permission_list_tile_enable_button": "Aktifkan Notifikasi", - "notification_permission_list_tile_title": "Izin Notifikasi", - "notification_toggle_setting_description": "Aktifkan notifikasi surel", - "notifications": "Notifikasi", - "notifications_setting_description": "Kelola notifikasi", - "oauth": "OAuth", - "obtainium_configurator": "Konfigurator Obtainium", - "obtainium_configurator_instructions": "Gunakan Obtainium untuk menginstal dan memperbarui aplikasi Android secara langsung dari rilis GitHub Immich. Buat kunci API dan pilih varian untuk membuat tautan konfigurasi Obtainium anda", - "ocr": "OCR", - "official_immich_resources": "Sumber Daya Immich Resmi", - "offline": "Luring", - "offset": "Ofset", - "ok": "Oke", - "oldest_first": "Terlawas dahulu", - "on_this_device": "Di perangkat ini", - "onboarding": "Memulai", - "onboarding_locale_description": "Pilih bahasa pilihan Anda. Anda dapat mengubahnya nanti di pengaturan.", - "onboarding_privacy_description": "Fitur-fitur berikut ini (bersifat opsional) bergantung pada layanan eksternal dan dapat Anda nonaktifkan kapan saja melalui menu pengaturan.", - "onboarding_server_welcome_description": "Mari kita konfigurasikan instans Anda dengan sejumlah pengaturan umum.", - "onboarding_theme_description": "Pilih tema warna untuk server Anda. Ini dapat diubah lagi dalam pengaturan Anda.", - "onboarding_user_welcome_description": "Ayo kita mulai!", - "onboarding_welcome_user": "Selamat datang, {user}", - "online": "Daring", - "only_favorites": "Hanya favorit", - "open": "Buka", - "open_in_map_view": "Buka dalam tampilan peta", - "open_in_openstreetmap": "Buka di OpenStreetMap", - "open_the_search_filters": "Buka saringan pencarian", - "options": "Opsi", - "or": "atau", - "organize_into_albums": "Atur ke dalam album", - "organize_into_albums_description": "Masukkan foto lama ke album sesuai pengaturan sinkronisasi", - "organize_your_library": "Kelola pustaka Anda", - "original": "asli", - "other": "Lainnya", - "other_devices": "Perangkat lain", - "other_entities": "Entitas lain", - "other_variables": "Variabel lain", - "owned": "Dimiliki", - "owner": "Pemilik", - "page": "Laman", - "partner": "Rekan", - "partner_can_access": "{partner} dapat mengakses", - "partner_can_access_assets": "Semua foto dan video Anda kecuali yang ada di Arsip dan Terhapus", - "partner_can_access_location": "Lokasi di mana foto Anda diambil", - "partner_list_user_photos": "Foto {user}", - "partner_list_view_all": "Lihat semua", - "partner_page_empty_message": "Foto anda tidak dibagikan dengan partner manapun.", - "partner_page_no_more_users": "Tidak ada pengguna lain yang bisa ditambahkan", - "partner_page_partner_add_failed": "Gagal menambahkan partner", - "partner_page_select_partner": "Pilih partner", - "partner_page_shared_to_title": "Dibagikan dengan", - "partner_page_stop_sharing_content": "{partner} tidak akan bisa mengakses foto Anda lagi.", - "partner_sharing": "Pembagian Partner", - "partners": "Partner", - "password": "Kata sandi", - "password_does_not_match": "Kata sandi tidak cocok", - "password_required": "Kata Sandi Diperlukan", - "password_reset_success": "Pengaturan ulang kata sandi berhasil", - "past_durations": { - "days": "{days, plural, one {hari} other {# hari}} lalu", - "hours": "{hours, plural, one {jam} other {# jam}} lalu", - "years": "{years, plural, one {tahun} other {# tahun}} lalu" - }, - "path": "Jalur", - "pattern": "Pola", - "pause": "Jeda", - "pause_memories": "Jeda kenangan", - "paused": "Dijeda", - "pending": "Tertunda", - "people": "Orang", - "people_edits_count": "{count, plural, one {# orang} other {# orang}} disunting", - "people_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan orang", - "people_selected": "{count, plural, one {# orang dipilih} other {# orang dipilih}}", - "people_sidebar_description": "Tampilkan tautan ke Orang dalam bilah samping", - "permanent_deletion_warning": "Peringatan penghapusan permanen", - "permanent_deletion_warning_setting_description": "Tampilkan peringatan ketika menghapus aset secara permanen", - "permanently_delete": "Hapus secara permanen", - "permanently_delete_assets_count": "Hapus {count, plural, one {aset} other {aset}} secara permanen", - "permanently_delete_assets_prompt": "Apakah Anda yakin untuk menghapus {count, plural, one {aset ini secara permanen?} other {sebanyak # aset-aset berikut secara permanen?}} Ini juga akan menghapus {count, plural, one {ini dari} other {semua dari}} album-albumnya.", - "permanently_deleted_asset": "Aset dihapus secara permanen", - "permanently_deleted_assets_count": "{count, plural, one {# aset} other {# aset}} dihapus secara permanen", - "permission": "Perizinan", - "permission_empty": "Bagian izin tidak boleh dibiarkan kosong", - "permission_onboarding_back": "Kembali", - "permission_onboarding_continue_anyway": "Lanjutkan saja", - "permission_onboarding_get_started": "Memulai", - "permission_onboarding_go_to_settings": "Buka setelan", - "permission_onboarding_permission_denied": "Izin ditolak. Untuk menggunakan Immich, berikan izin akses foto dan video di Setelan.", - "permission_onboarding_permission_granted": "Izin diberikan! Semua sudah siap.", - "permission_onboarding_permission_limited": "Izin dibatasi. Agai Immich dapat mencadangkan dan mengatur seluruh koleksi galeri, izinkan akses foto dan video pada Setelan.", - "permission_onboarding_request": "Immich memerlukan izin untuk melihat foto dan video kamu.", - "person": "Orang", - "person_age_months": "{months, plural, one {# bulan} other {# bulan}} old", - "person_age_year_months": "1 year, {months, plural, one {# bulan} other {# bulan}} old", - "person_age_years": "{years, plural, other {# tahun}} old", - "person_birthdate": "Lahir pada {date}", - "person_hidden": "{name}{hidden, select, true { (tersembunyi)} other {}}", - "person_recognized": "Orang yang dikenali", - "person_selected": "Orang yang dipilih", - "photo_shared_all_users": "Sepertinya Anda membagikan foto Anda dengan semua pengguna atau Anda tidak memiliki pengguna siapa pun untuk dibagikan.", - "photos": "Foto", - "photos_and_videos": "Foto & Video", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Foto}}", - "photos_from_previous_years": "Foto dari tahun lalu", - "pick_a_location": "Pilih lokasi", - "pick_custom_range": "Rentang kustom", - "pick_date_range": "Pilih rentang tanggal", - "pin_code_changed_successfully": "Berhasil mengubah kode PIN", - "pin_code_reset_successfully": "Berhasil mereset kode PIN", - "pin_code_setup_successfully": "Berhasil memasang kode PIN", - "pin_verification": "Verifikasi kode PIN", - "place": "Tempat", - "places": "Tempat", - "places_count": "{count, plural, one {{count, number} Tempat} other {{count, number} Tempat}}", - "play": "Putar", - "play_memories": "Putar kenangan", - "play_motion_photo": "Putar Foto Gerak", - "play_or_pause_video": "Putar atau jeda video", - "play_original_video": "Putar video asli", - "play_original_video_setting_description": "Lebih menyukai memutar video asli daripada video yang telah dikonversi. Jika aset asli tidak kompatibel, video mungkin tidak dapat diputar dengan benar.", - "play_transcoded_video": "Putar video yang telah dikonversi", - "please_auth_to_access": "Silakan autentikasi untuk mengakses", - "port": "Porta", - "preferences_settings_subtitle": "Kelola preferensi aplikasi", - "preferences_settings_title": "Preferensi", - "preparing": "Mempersiapkan", - "preset": "Prasetel", - "preview": "Pratinjau", - "previous": "Sebelumnya", - "previous_memory": "Kenangan sebelumnya", - "previous_or_next_day": "Hari berikutnya/sebelumnya", - "previous_or_next_month": "Bulan berikutnya/sebelumnya", - "previous_or_next_photo": "Foto berikutnya/sebelumnya", - "previous_or_next_year": "Tahun berikutnya/sebelumnya", - "primary": "Utama", - "privacy": "Privasi", - "profile": "Profil", - "profile_drawer_app_logs": "Log", - "profile_drawer_client_server_up_to_date": "Klien dan server menjalankan versi terbaru", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mode baca-saja aktif. Tekan lama ikon avatar pengguna untuk keluar.", - "profile_image_of_user": "Foto profil dari {user}", - "profile_picture_set": "Foto profil ditetapkan.", - "public_album": "Album publik", - "public_share": "Pembagian Publik", - "purchase_account_info": "Pendukung", - "purchase_activated_subtitle": "Terima kasih telah mendukung Immich dan perangkat lunak sumber terbuka", - "purchase_activated_time": "Di aktivasi pada {date}", - "purchase_activated_title": "Kunci kamu telah sukses di aktivasi", - "purchase_button_activate": "Aktifkan", - "purchase_button_buy": "Beli", - "purchase_button_buy_immich": "Beli Immich", - "purchase_button_never_show_again": "Jangan tampilkan lagi", - "purchase_button_reminder": "Ingatkan saya pada 30 hari lagi", - "purchase_button_remove_key": "Hapus kunci", - "purchase_button_select": "Pilih", - "purchase_failed_activation": "Gagal mengaktifkan! Silakan periksa email kamu untuk kunci produk yang benar!", - "purchase_individual_description_1": "Untuk perorangan", - "purchase_individual_description_2": "Status pendukung", - "purchase_individual_title": "Perorangan", - "purchase_input_suggestion": "Punya kunci produk? Masukkan kunci di bawah ini", - "purchase_license_subtitle": "Beli Immich untuk keberlangsungan pengembangan layanan", - "purchase_lifetime_description": "Pembayaran seumur hidup", - "purchase_option_title": "PILIHAN PEMBAYARAN", - "purchase_panel_info_1": "Membangun Immich membutuhkan banyak waktu dan upaya, dan kami memiliki insinyur penuh waktu yang bekerja untuk membuatnya sebaik mungkin. Misi kami adalah agar perangkat lunak sumber terbuka dan praktik bisnis yang beretika menjadi sumber pendapatan yang berkelanjutan bagi para pengembang dan menciptakan ekosistem yang menghargai privasi dengan alternatif nyata untuk layanan cloud yang eksploitatif.", - "purchase_panel_info_2": "Karena kami berkomitmen untuk tidak menambahkan batasan akses berbayar (paywall), pembelian ini tidak akan memberikan fitur tambahan apa pun di Immich. Kami mengandalkan dukungan dari pengguna seperti Anda untuk memastikan pengembangan Immich dapat terus berjalan.", - "purchase_panel_title": "Dukung proyek ini", - "purchase_per_server": "Per server", - "purchase_per_user": "Per pengguna", - "purchase_remove_product_key": "Hapus Kunci Produk", - "purchase_remove_product_key_prompt": "Apakah kamu yakin ingin menghapus kunci produk?", - "purchase_remove_server_product_key": "Hapus kunci produk Server", - "purchase_remove_server_product_key_prompt": "Apakah kamu yakin ingin menghapus kunci produk Server?", - "purchase_server_description_1": "Untuk keseluruhan server", - "purchase_server_description_2": "Status pendukung", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Kunci produk server dikelola oleh admin", - "query_asset_id": "ID Aset Kueri", - "queue_status": "Antrian {count}/{total}", - "rate_asset": "Menilai Aset", - "rating": "Peringkat bintang", - "rating_clear": "Hapus peringkat", - "rating_count": "{count, plural, one {# peringkat} other {# peringkat}}", - "rating_description": "Tampilkan peringkat EXIF pada panel info", - "rating_set": "Mengatur nilai menjadi {rating, plural, one {# bintang} other {# bintang}}", - "reaction_options": "Opsi reaksi", - "read_changelog": "Baca Log Perubahan", - "readonly_mode_disabled": "Mode baca-saja dimatikan", - "readonly_mode_enabled": "Mode baca-saja diaktifkan", - "ready_for_upload": "Siap untuk mengunggah", - "reassign": "Tetapkan ulang", - "reassigned_assets_to_existing_person": "Menetapkan ulang {count, plural, one {# aset} other {# aset}} kepada {name, select, null {orang yang sudah ada} other {{name}}}", - "reassigned_assets_to_new_person": "Menetapkan ulang {count, plural, one {# aset} other {# aset}} kepada orang baru", - "reassing_hint": "Tetapkan aset yang dipilih ke orang yang sudah ada", - "recent": "Terkini", - "recent-albums": "Album terkini", - "recent_searches": "Pencarian terkini", - "recently_added": "Barusaja ditambahkan", - "recently_added_page_title": "Baru Ditambahkan", - "recently_taken": "Diambil terkini", - "recently_taken_page_title": "Diambil Terkini", - "refresh": "Segarkan", - "refresh_encoded_videos": "Segarkan video terenkode", - "refresh_faces": "Segarkan wajah", - "refresh_metadata": "Segarkan metadata", - "refresh_thumbnails": "Segarkan gambar kecil", - "refreshed": "Disegarkan", - "refreshes_every_file": "Membaca ulang semua berkas yang sudah ada dan yang baru", - "refreshing_encoded_video": "Menyegarkan video terenkode", - "refreshing_faces": "Menyegarkan wajah", - "refreshing_metadata": "Menyegarkan metadata", - "regenerating_thumbnails": "Membuat ulang gambar kecil", - "remote": "Jarak Jauh", - "remote_assets": "Aset Jarak Jauh", - "remote_media_summary": "Ringkasan Media Jarak Jauh", - "remove": "Hapus", - "remove_assets_album_confirmation": "Apakah Anda yakin ingin menghapus {count, plural, one {# aset} other {# aset}} dari album?", - "remove_assets_shared_link_confirmation": "Apakah Anda yakin ingin menghapus {count, plural, one {# aset} other {# aset}} dari tautan terbagi ini?", - "remove_assets_title": "Hapus aset?", - "remove_custom_date_range": "Hapus jangka tanggal khusus", - "remove_deleted_assets": "Hapus Berkas Luring", - "remove_from_album": "Hapus dari album", - "remove_from_album_action_prompt": "{count} telah dihapus dari album", - "remove_from_favorites": "Hapus dari favorit", - "remove_from_lock_folder_action_prompt": "{count} telah dihapus dari folder terkunci", - "remove_from_locked_folder": "Hapus dari folder terkunci", - "remove_from_locked_folder_confirmation": "Apakah Anda yakin ingin memindahkan foto dan video ini keluar dari folder terkunci? Setelah dipindahkan, item tersebut akan terlihat di pustaka Anda.", - "remove_from_shared_link": "Hapus dari tautan terbagi", - "remove_memory": "Hapus kenangan", - "remove_photo_from_memory": "Hapus foto dari kenangan ini", - "remove_tag": "Hapus Tanda", - "remove_url": "Hapus URL", - "remove_user": "Keluarkan pengguna", - "removed_api_key": "Kunci API Dihapus: {name}", - "removed_from_archive": "Dihapus dari arsip", - "removed_from_favorites": "Dihapus dari favorit", - "removed_from_favorites_count": "{count, plural, other {Menghapus #}} dari favorit", - "removed_memory": "Memori dihapus", - "removed_photo_from_memory": "Foto dihapus dari memori", - "removed_tagged_assets": "Hapus tag dari {count, plural, one {# aset} other {# aset}}", - "rename": "Ubah nama", - "repair": "Perbaiki", - "repair_no_results_message": "Berkas yang tidak dilacak dan hilang akan muncul di sini", - "replace_with_upload": "Ganti dengan unggahan", - "repository": "Repositori", - "require_password": "Memerlukan kata sandi", - "require_user_to_change_password_on_first_login": "Memerlukan pengguna untuk mengubah kata sandi pada log masuk pertama", - "rescan": "Pindai ulang", - "reset": "Atur ulang", - "reset_password": "Atur ulang kata sandi", - "reset_people_visibility": "Atur ulang keterlihatan orang", - "reset_pin_code": "Reset kode PIN", - "reset_pin_code_description": "Jika Anda lupa kode PIN, Anda bisa menghubungi admin untuk atur ulang", - "reset_pin_code_success": "Berhasil atur ulang kode PIN", - "reset_pin_code_with_password": "Anda selalu dapat mengatur ulang kode PIN dengan kata sandi Anda", - "reset_sqlite": "Atur ulang basis data SQLite", - "reset_sqlite_confirmation": "Apakah Anda yakin ingin mengatur ulang basis data SQLite? Setelah tindakan ini, Anda harus keluar lalu masuk kembali untuk melakukan sinkronisasi ulang data", - "reset_sqlite_success": "Berhasil mengatur ulang basis data SQLite", - "reset_to_default": "Atur ulang ke bawaan", - "resolution": "Resolusi", - "resolve_duplicates": "Mengatasi duplikat", - "resolved_all_duplicates": "Semua duplikat terselesaikan", - "restore": "Pulihkan", - "restore_all": "Pulihkan semua", - "restore_trash_action_prompt": "Sebanyak {count} item telah dipulihkan dari tempat sampah", - "restore_user": "Pulihkan pengguna", - "restored_asset": "Aset dipulihkan", - "resume": "Lanjutkan", - "resume_paused_jobs": "Lanjutkan {count, plural, one {# pekerjaan yang dijeda} other {# pekerjaan yang dijeda}}", - "retry_upload": "Ulangi pengunggahan", - "review_duplicates": "Pratinjau duplikat", - "review_large_files": "Meninjau berkas berukuran besar", - "role": "Peran", - "role_editor": "Penyunting", - "role_viewer": "Penampil", - "running": "Berjalan", - "save": "Simpan", - "save_to_gallery": "Simpan ke galeri", - "saved": "Disimpan", - "saved_api_key": "Kunci API Tersimpan", - "saved_profile": "Profil disimpan", - "saved_settings": "Pengaturan disimpan", - "say_something": "Ucapkan sesuatu", - "scaffold_body_error_occurred": "Terjadi kesalahan", - "scan_all_libraries": "Pindai Semua Pustaka", - "scan_library": "Pindai", - "scan_settings": "Pengaturan Pemindaian", - "scanning_for_album": "Memindai album...", - "search": "Cari", - "search_albums": "Cari album", - "search_by_context": "Cari berdasarkan konteks", - "search_by_description": "Cari berdasarkan deskripsi", - "search_by_description_example": "Hari mendaki di Sapa", - "search_by_filename": "Cari berdasarkan nama berkas atau ekstensi", - "search_by_filename_example": "mis. IMG_1234.JPG atau PNG", - "search_by_ocr": "Cari dengan OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Pencarian model lensa...", - "search_camera_make": "Cari merek kamera...", - "search_camera_model": "Cari model kamera...", - "search_city": "Cari kota...", - "search_country": "Cari negara...", - "search_filter_apply": "Terapkan filter", - "search_filter_camera_title": "Pilih tipe kamera", - "search_filter_date": "Tanggal", - "search_filter_date_interval": "{start} sampai dengan {end}", - "search_filter_date_title": "Pilih rentang tanggal", - "search_filter_display_option_not_in_album": "Tidak dalam album", - "search_filter_display_options": "Opsi tampilan", - "search_filter_filename": "Cari berdasarkan nama file", - "search_filter_location": "Lokasi", - "search_filter_location_title": "Pilih Lokasi", - "search_filter_media_type": "Tipe Media", - "search_filter_media_type_title": "Pilih jenis media", - "search_filter_ocr": "Cari dengan OCR", - "search_filter_people_title": "Pilih orang", - "search_for": "Cari", - "search_for_existing_person": "Cari orang yang sudah ada", - "search_no_more_result": "Tidak ada hasil lagi", - "search_no_people": "Tidak ada orang", - "search_no_people_named": "Tidak ada orang bernama \"{name}\"", - "search_no_result": "Tidak ada hasil yang ditemukan, coba kata kunci atau kombinasi lain", - "search_options": "Pilihan pencarian", - "search_page_categories": "Kategori", - "search_page_motion_photos": "Foto Bergerak", - "search_page_no_objects": "Tidak Ada Info Objek", - "search_page_no_places": "Tidak Ada Info Lokasi", - "search_page_screenshots": "Tangkapan Layar", - "search_page_search_photos_videos": "Cari foto dan video Anda", - "search_page_selfies": "Swafoto", - "search_page_things": "Objek", - "search_page_view_all_button": "Lihat semua", - "search_page_your_activity": "Aktivitasmu", - "search_page_your_map": "Peta Anda", - "search_people": "Cari orang", - "search_places": "Cari tempat", - "search_rating": "Cari berdasarkan penilaian...", - "search_result_page_new_search_hint": "Pencarian Baru", - "search_settings": "Pengaturan pencarian", - "search_state": "Cari negara bagian...", - "search_suggestion_list_smart_search_hint_1": "Penelusuran cerdas aktif secara bawaan. Untuk menelusuri metadata, gunakan sintaks ", - "search_suggestion_list_smart_search_hint_2": "m:penelusuran-kamu", - "search_tags": "Cari tag...", - "search_timezone": "Cari zona waktu...", - "search_type": "Jenis pencarian", - "search_your_photos": "Cari foto Anda", - "searching_locales": "Mencari lokal...", - "second": "Detik", - "see_all_people": "Lihat semua orang", - "select": "Pilih", - "select_album": "Pilih album", - "select_album_cover": "Pilih kover album", - "select_albums": "Pilih album-album", - "select_all": "Pilih semua", - "select_all_duplicates": "Pilih semua duplikat", - "select_all_in": "Pilih semua di {group}", - "select_avatar_color": "Pilih warna avatar", - "select_count": "{count, plural, one {Pilih #} other {Pilih #}}", - "select_face": "Pilih wajah", - "select_featured_photo": "Pilih foto terfitur", - "select_from_computer": "Pilih dari komputer", - "select_keep_all": "Pilih simpan semua", - "select_library_owner": "Pilih pemilik pustaka", - "select_new_face": "Pilih wajah baru", - "select_people": "Pilih orang", - "select_person": "Pilih orang", - "select_person_to_tag": "Pilih orang untuk ditandai", - "select_photos": "Pilih foto", - "select_trash_all": "Pilih buang semua", - "select_user_for_sharing_page_err_album": "Gagal membuat album", - "selected": "Dipilih", - "selected_count": "{count, plural, other {# dipilih}}", - "selected_gps_coordinates": "Koordinat GPS yang dipilih", - "send_message": "Kirim pesan", - "send_welcome_email": "Kirim surel selamat datang", - "server_endpoint": "Endpoint server", - "server_info_box_app_version": "Versi App", - "server_info_box_server_url": "URL Server", - "server_offline": "Server Luring", - "server_online": "Server Daring", - "server_privacy": "Privasi server", - "server_restarting_description": "Laman ini akan dimuat ulang sesaat lagi.", - "server_restarting_title": "Server sedang dimulai ulang", - "server_stats": "Statistik Server", - "server_update_available": "Pembaruan server tersedia", - "server_version": "Versi Server", - "set": "Atur", - "set_as_album_cover": "Atur sebagai kover album", - "set_as_featured_photo": "Tetapkan sebagai foto unggulan", - "set_as_profile_picture": "Atur sebagai foto profil", - "set_date_of_birth": "Atur tanggal lahir", - "set_profile_picture": "Tetapkan foto profil", - "set_slideshow_to_fullscreen": "Atur Salindia ke layar penuh", - "set_stack_primary_asset": "Atur sebagai aset utama", - "setting_image_viewer_help": "Penampil detail akan terlebih dahulu memuat gambar mini berukuran kecil, kemudian memuat pratinjau berukuran sedang (apabila diaktifkan), dan akhirnya memuat berkas asli (apabila diaktifkan).", - "setting_image_viewer_original_subtitle": "Aktifkan untuk memuat gambar asli dengan resolusi penuh (berukuran besar!). Nonaktifkan untuk mengurangi penggunaan data (baik jaringan maupun cache perangkat).", - "setting_image_viewer_original_title": "Muat gambar kualitas asli", - "setting_image_viewer_preview_subtitle": "Aktifkan untuk memuat gambar dengan resolusi sedang. Nonaktifkan jika ingin langsung memuat gambar asli atau hanya ingin memuat thumbnail.", - "setting_image_viewer_preview_title": "Muat gambar preview", - "setting_image_viewer_title": "Foto", - "setting_languages_apply": "Terapkan", - "setting_languages_subtitle": "Mengubah bahasa pada aplikasi", - "setting_notifications_notify_failures_grace_period": "Beritahu kegagalan cadangan latar belakang: {duration}", - "setting_notifications_notify_hours": "{count} jam", - "setting_notifications_notify_immediately": "segera", - "setting_notifications_notify_minutes": "{count} menit", - "setting_notifications_notify_never": "Jangan pernah", - "setting_notifications_notify_seconds": "{count} detik", - "setting_notifications_single_progress_subtitle": "Rincian info proses unggah setiap asset", - "setting_notifications_single_progress_title": "Tampilkan rincian proses cadangkan latar belakang", - "setting_notifications_subtitle": "Atur setelan notifikasi", - "setting_notifications_total_progress_subtitle": "Progres keseluruhan unggahan (selesai/total aset)", - "setting_notifications_total_progress_title": "Tampilkan progres total pencadangan latar belakang", - "setting_video_viewer_auto_play_subtitle": "Otomatis memutar video saat dibuka", - "setting_video_viewer_auto_play_title": "Putar video secara otomatis", - "setting_video_viewer_looping_title": "Ulangi", - "setting_video_viewer_original_video_subtitle": "Ketika melakukan streaming video dari server, sistem akan memutar versi asli meskipun tersedia hasil transkode. Pengaturan ini dapat menyebabkan terjadinya buffering. Video yang tersedia secara lokal akan selalu diputar dalam kualitas asli tanpa terpengaruh oleh pengaturan ini.", - "setting_video_viewer_original_video_title": "Paksa video asli", - "settings": "Pengaturan", - "settings_require_restart": "Harap mulai ulang Immich untuk menerapkan pengaturan ini", - "settings_saved": "Pengaturan disimpan", - "setup_pin_code": "Pasang kode PIN", - "share": "Bagikan", - "share_action_prompt": "Sebanyak {count} aset telah dibagikan", - "share_add_photos": "Tambah foto", - "share_assets_selected": "{count} dipilih", - "share_dialog_preparing": "Menyiapkan...", - "share_link": "Bagikan Link", - "shared": "Dibagikan", - "shared_album_activities_input_disable": "Komentar dinonaktifkan", - "shared_album_activity_remove_content": "Apakah Anda ingin menghapus aktivitas ini?", - "shared_album_activity_remove_title": "Hapus Aktivitas", - "shared_album_section_people_action_error": "Gagal menghapus dari album", - "shared_album_section_people_action_leave": "Hapus pengguna dari album", - "shared_album_section_people_action_remove_user": "Hapus pengguna dari album", - "shared_album_section_people_title": "ORANG", - "shared_by": "Dibagikan oleh", - "shared_by_user": "Dibagikan oleh {user}", - "shared_by_you": "Dibagikan oleh Anda", - "shared_from_partner": "Foto dari {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Diunggah", - "shared_link_app_bar_title": "Link Berbagi", - "shared_link_clipboard_copied_massage": "Tersalin ke papan klip", - "shared_link_clipboard_text": "Tautan: {link}\nKata Sandi: {password}", - "shared_link_create_error": "Terjadi kesalahan saat membuat link berbagi", - "shared_link_custom_url_description": "Akses tautan bersama ini dengan URL khusus", - "shared_link_edit_description_hint": "Masukkan deskripsi link", - "shared_link_edit_expire_after_option_day": "1 hari", - "shared_link_edit_expire_after_option_days": "{count} hari", - "shared_link_edit_expire_after_option_hour": "1 jam", - "shared_link_edit_expire_after_option_hours": "{count} jam", - "shared_link_edit_expire_after_option_minute": "1 menit", - "shared_link_edit_expire_after_option_minutes": "{count} menit", - "shared_link_edit_expire_after_option_months": "{count} bulan", - "shared_link_edit_expire_after_option_year": "{count} tahun", - "shared_link_edit_password_hint": "Masukkan sandi link", - "shared_link_edit_submit_button": "Perbarui link", - "shared_link_error_server_url_fetch": "Tidak dapat memuat url server", - "shared_link_expires_day": "Kedaluwarsa dalam {count} hari", - "shared_link_expires_days": "Kedaluwarsa dalam {count} hari", - "shared_link_expires_hour": "Kedaluwarsa dalam {count} jam", - "shared_link_expires_hours": "Kedaluwarsa dalam {count} jam", - "shared_link_expires_minute": "Kedaluwarsa dalam {count} menit", - "shared_link_expires_minutes": "Kedaluwarsa dalam {count} menit", - "shared_link_expires_never": "Tidak akan kedaluwarsa", - "shared_link_expires_second": "Kedaluwarsa dalam {count} detik", - "shared_link_expires_seconds": "Kedaluwarsa dalam {count} detik", - "shared_link_individual_shared": "Dibagikan secara individual", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Atur link berbagi", - "shared_link_options": "Pilihan tautan bersama", - "shared_link_password_description": "Wajibkan kata sandi untuk mengakses tautan bersama ini", - "shared_links": "Tautan terbagi", - "shared_links_description": "Bagikan foto dan video dengan tautan", - "shared_photos_and_videos_count": "{assetCount, plural, other {# foto & video terbagi.}}", - "shared_with_me": "Dibagikan kepada saya", - "shared_with_partner": "Dibagikan dengan {partner}", - "sharing": "Pembagian", - "sharing_enter_password": "Masukkan kata sandi untuk membuka tautan halaman ini.", - "sharing_page_album": "Album berbagi", - "sharing_page_description": "Buat album berbagi untuk berbagi foto dan video dengan pengguna di dalam jaringan.", - "sharing_page_empty_list": "DAFTAR KOSONG", - "sharing_sidebar_description": "Tampilkan tautan ke Pembagian dalam bilah samping", - "sharing_silver_appbar_create_shared_album": "Buat album berbagi", - "sharing_silver_appbar_share_partner": "Berbagi dengan partner", - "shift_to_permanent_delete": "tekan ⇧ untuk menghapus aset secara permanen", - "show_album_options": "Tampilkan opsi album", - "show_albums": "Tampilkan album", - "show_all_people": "Tampilkan semua orang", - "show_and_hide_people": "Tampilkan & sembunyikan orang", - "show_file_location": "Tampilkan lokasi berkas", - "show_gallery": "Tampilkan galeri", - "show_hidden_people": "Tampilkan orang tersembunyi", - "show_in_timeline": "Tampilkan dalam lini masa", - "show_in_timeline_setting_description": "Tampilkan foto dan video dari pengguna ini di lini masa Anda", - "show_keyboard_shortcuts": "Tampilkan pintasan papan ketik", - "show_metadata": "Tampilkan metadata", - "show_or_hide_info": "Tampilkan atau sembunyikan info", - "show_password": "Tampilkan kata sandi", - "show_person_options": "Tampilkan opsi orang", - "show_progress_bar": "Tampilkan Bilah Progres", - "show_schema": "Tampilkan skema", - "show_search_options": "Tampilkan opsi pencarian", - "show_shared_links": "Tampilkan tautan terbagi", - "show_slideshow_transition": "Tampilkan transisi salindia", - "show_supporter_badge": "Lencana suporter", - "show_supporter_badge_description": "Tampilkan lencana suporter", - "show_text_recognition": "Tampilkan teks rekognisi", - "show_text_search_menu": "Tampilkan menu pencarian teks", - "shuffle": "Acak", - "sidebar": "Bilah sisi", - "sidebar_display_description": "Menampilkan tautan ke tampilan di bilah sisi", - "sign_out": "Keluar", - "sign_up": "Daftar", - "size": "Ukuran", - "skip_to_content": "Lewati ke konten", - "skip_to_folders": "Lewati ke berkas", - "skip_to_tags": "Lewati ke tag", - "slideshow": "Salindia", - "slideshow_settings": "Pengaturan salindia", - "sort_albums_by": "Urutkan album berdasarkan...", - "sort_created": "Tanggal dibuat", - "sort_items": "Jumlah item", - "sort_modified": "Tanggal diubah", - "sort_newest": "Foto terbaru", - "sort_oldest": "Foto terlawas", - "sort_people_by_similarity": "Urutkan orang berdasarkan kemiripan", - "sort_recent": "Foto paling terkini", - "sort_title": "Judul", - "source": "Sumber", - "stack": "Tumpukan", - "stack_action_prompt": "{count} tumpukan", - "stack_duplicates": "Stack duplikat", - "stack_select_one_photo": "Pilih satu foto utama untuk stack", - "stack_selected_photos": "Tumpuk foto terpilih", - "stacked_assets_count": "{count, plural, one {# aset} other {# aset}} ditumpuk", - "stacktrace": "Jejak tumpukan", - "start": "Mulai", - "start_date": "Tanggal mulai", - "start_date_before_end_date": "Tanggal mulai harus sebelum tanggal akhir", - "state": "Keadaan", - "status": "Status", - "stop_casting": "Hentikan cast", - "stop_motion_photo": "Hentikan Foto Gerak", - "stop_photo_sharing": "Berhenti membagikan foto Anda?", - "stop_photo_sharing_description": "{partner} tidak akan dapat mengakses foto Anda lagi.", - "stop_sharing_photos_with_user": "Berhenti membagikan foto Anda dengan pengguna ini", - "storage": "Ruang penyimpanan", - "storage_label": "Label penyimpanan", - "storage_quota": "Kuota Penyimpanan", - "storage_usage": "{used} dari {available} digunakan", - "submit": "Kirim", - "success": "Sukses", - "suggestions": "Saran", - "sunrise_on_the_beach": "Matahari terbit di pantai", - "support": "Dukungan", - "support_and_feedback": "Dukungan & Masukan", - "support_third_party_description": "Pemasangan Immich Anda telah dipaketkan oleh pihak ketiga. Masalah yang Anda alami dapat disebabkan oleh paket tersebut, jadi silakan ajukan isu dengan masalah tersebut menggunakan tautan di bawah.", - "swap_merge_direction": "Ganti arah penggabungan", - "sync": "Sinkronisasikan", - "sync_albums": "Sinkronkan album", - "sync_albums_manual_subtitle": "Melakukan sinkronisasi semua video dan foto yang telah diunggah ke album cadangan yang dipilih", - "sync_local": "Sinkronkan lokal", - "sync_remote": "Sinkronkan jarak jauh", - "sync_status": "Status Sinkronisasi", - "sync_status_subtitle": "Lihat dan atur sistem sinkronisasi", - "sync_upload_album_setting_subtitle": "Membuat dan mengunggah foto serta video Anda ke album yang telah dipilih pada Immich", - "tag": "Label", - "tag_assets": "Tag aset", - "tag_created": "Tag yang di buat: {tag}", - "tag_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan topik tag logis", - "tag_not_found_question": "Tidak dapat menemukan tag? Buat tag baru.", - "tag_people": "Tandai Orang", - "tag_updated": "Tag yang diperbarui: {tag}", - "tagged_assets": "Ditandai {count, plural, one {# aset} other {# aset}}", - "tags": "Tag", - "tap_to_run_job": "Ketuk untuk menjalankan pekerjaan", - "template": "Templat", - "text_recognition": "Teks rekognisi", - "theme": "Tema", - "theme_selection": "Pemilihan tema", - "theme_selection_description": "Tetapkan tema ke terang atau gelap secara otomatis berdasarkan preferensi sistem peramban Anda", - "theme_setting_asset_list_storage_indicator_title": "Tampilkan sisa penyimpanan", - "theme_setting_asset_list_tiles_per_row_title": "Jumlah aset per baris ({count})", - "theme_setting_colorful_interface_subtitle": "Menerapkan warna utama ke permukaan latar belakang.", - "theme_setting_colorful_interface_title": "Antarmuka berwarna-warni", - "theme_setting_image_viewer_quality_subtitle": "Atur kualitas dari penampil gambar", - "theme_setting_image_viewer_quality_title": "Kualitas penampil gambar", - "theme_setting_primary_color_subtitle": "Pilihlah warna yang akan digunakan untuk tindakan utama dan elemen aksen.", - "theme_setting_primary_color_title": "Warna utama", - "theme_setting_system_primary_color_title": "Gunakan warna sistem", - "theme_setting_system_theme_switch": "Otomatis (Ikuti pengaturan sistem)", - "theme_setting_theme_subtitle": "Pilih setelan tema aplikasi", - "theme_setting_three_stage_loading_subtitle": "Pemuatan tiga tahap dapat meningkatkan performa pemuatan, namun akan menyebabkan beban jaringan meningkat secara signifikan", - "theme_setting_three_stage_loading_title": "Aktifkan pemuatan tiga tahap", - "they_will_be_merged_together": "Mereka akan digabungkan bersama", - "third_party_resources": "Sumber Daya Pihak Ketiga", - "time": "Waktu", - "time_based_memories": "Kenangan berbasis waktu", - "time_based_memories_duration": "Jumlah detik untuk menampilkan tiap gambar.", - "timeline": "Lini masa", - "timezone": "Zona waktu", - "to_archive": "Arsipkan", - "to_change_password": "Ubah kata sandi", - "to_favorite": "Favorit", - "to_login": "Log masuk", - "to_multi_select": "untuk memilih beberapa", - "to_parent": "Ke induk", - "to_select": "untuk memilih", - "to_trash": "Sampah", - "toggle_settings": "Saklar pengaturan", - "toggle_theme_description": "Sakelar tema", - "total": "Jumlah", - "total_usage": "Jumlah penggunaan", - "trash": "Sampah", - "trash_action_prompt": "Sebanyak {count} item telah dipindahkan ke tempat sampah", - "trash_all": "Buang Semua", - "trash_count": "Sampah {count, number}", - "trash_delete_asset": "Hapus Aset", - "trash_emptied": "Tempat sampah telah dikosongkan", - "trash_no_results_message": "Foto dan video di sampah akan muncul di sini.", - "trash_page_delete_all": "Hapus Semua", - "trash_page_empty_trash_dialog_content": "Apakah kamu ingin menghapus semua aset di sampah? Item tersebut akan dihapus secara permanen dari Immich", - "trash_page_info": "Item yang dipindahkan ke sampah akan terhapus secara permanen setelah {days} hari", - "trash_page_no_assets": "Tidak ada aset di sampah", - "trash_page_restore_all": "Pulihkan Semua", - "trash_page_select_assets_btn": "Pilih aset", - "trash_page_title": "Sampah ({count})", - "trashed_items_will_be_permanently_deleted_after": "Item yang dibuang akan dihapus secara permanen setelah {days, plural, one {# hari} other {# hari}}.", - "trigger": "Pemicu", - "trigger_asset_uploaded": "Asset telah terunggah", - "trigger_asset_uploaded_description": "Terpicu saat aset baru telah terunggah", - "trigger_description": "Sebuah peristiwa yang memicu alur kerja", - "trigger_person_recognized": "Orang telah dikenali", - "trigger_person_recognized_description": "Terpicu saat seseorang terdeteksi", - "trigger_type": "Tipe pemicu", - "troubleshoot": "Pemecahan Masalah", - "type": "Jenis", - "unable_to_change_pin_code": "Tidak dapat mengubah kode PIN", - "unable_to_check_version": "Tidak dapat memeriksa versi aplikasi atau server", - "unable_to_setup_pin_code": "Tidak dapat memasang kode PIN", - "unarchive": "Keluarkan dari arsip", - "unarchive_action_prompt": "Sebanyak {count} item telah dihapus dari Arsip", - "unarchived_count": "{count, plural, other {# dipindahkan dari arsip}}", - "undo": "Urungkan", - "unfavorite": "Hapus favorit", - "unfavorite_action_prompt": "Sebanyak {count} item telah dihapus dari Favorit", - "unhide_person": "Munculkan orang", - "unknown": "Tidak diketahui", - "unknown_country": "Negara Tidak Diketahui", - "unknown_year": "Tahun Tidak Diketahui", - "unlimited": "Tidak terbatas", - "unlink_motion_video": "Membatalkan tautan video gerak", - "unlink_oauth": "Putuskan OAuth", - "unlinked_oauth_account": "Akun OAuth terputus", - "unmute_memories": "Aktifkan Kenangan", - "unnamed_album": "Album Tanpa Nama", - "unnamed_album_delete_confirmation": "Apakah kamu yakin akan menghapus album ini?", - "unnamed_share": "Pembagian Tanpa Nama", - "unsaved_change": "Perubahan belum disimpan", - "unselect_all": "Batalkan semua pilihan", - "unselect_all_duplicates": "Batal pilih semua duplikat", - "unselect_all_in": "Membatalkan semua pilihan dalam {group}", - "unstack": "Batalkan penumpukan", - "unstack_action_prompt": "{count} Tidak dalam tumpukan", - "unstacked_assets_count": "Penumpukan {count, plural, one {# aset} other {# aset}} dibatalkan", - "unsupported_field_type": "Tipe bidang tidak didukung", - "untagged": "Tidak ditandai", - "untitled_workflow": "Alur kerja tak berjudul", - "up_next": "Berikutnya", - "update_location_action_prompt": "Perbarui lokasi {count} aset yang dipilih dengan:", - "updated_at": "Diperbarui", - "updated_password": "Kata sandi diperbarui", - "upload": "Unggah", - "upload_concurrency": "Konkurensi pengunggahan", - "upload_details": "Detil unggahan", - "upload_dialog_info": "Apakah akan mencadangkan aset terpilih ke server?", - "upload_dialog_title": "Unggah Aset", - "upload_errors": "Unggahan selesai dengan {count, plural, one {# eror} other {# eror}}, muat ulang laman untuk melihat aset terunggah baru.", - "upload_finished": "Unggahan berhasil", - "upload_progress": "Tersisa {remaining, number} - Di proses {processed, number}/{total, number}", - "upload_skipped_duplicates": "Melewati {count, plural, one {# aset duplikat} other {# aset duplikat}}", - "upload_status_duplicates": "Duplikat", - "upload_status_errors": "Eror", - "upload_status_uploaded": "Diunggah", - "upload_success": "Pengunggahan berhasil, muat ulang laman untuk melihat aset terunggah yang baru.", - "upload_to_immich": "Unggah ke Immich ({count})", - "uploading": "Mengunggah", - "uploading_media": "Mengunggah media", - "url": "URL", - "usage": "Penggunaan", - "use_biometric": "Gunakan biometrik", - "use_current_connection": "Gunakan koneksi saat ini", - "use_custom_date_range": "Gunakan jangka tanggal khusus saja", - "user": "Pengguna", - "user_has_been_deleted": "Pengguna ini telah dihapus.", - "user_id": "ID Pengguna", - "user_liked": "{user} menyukai {type, select, photo {foto ini} video {tayangan ini} asset {aset ini} other {ini}}", - "user_pin_code_settings": "Kode PIN", - "user_pin_code_settings_description": "Atur kode PIN Anda", - "user_privacy": "Kerahasiaan pengguna", - "user_purchase_settings": "Pembelian", - "user_purchase_settings_description": "Atur pembelian kamu", - "user_role_set": "Tetapkan {user} sebagai {role}", - "user_usage_detail": "Detail penggunaan pengguna", - "user_usage_stats": "Statistik penggunaan akun", - "user_usage_stats_description": "Tampilkan statistik penggunaan akun", - "username": "Nama pengguna", - "users": "Pengguna", - "users_added_to_album_count": "{count, plural, one {Menambahkan # pengguna ke album} other {Menambahkan # pengguna ke album}}", - "utilities": "Peralatan", - "validate": "Validasi", - "validate_endpoint_error": "Masukkan URL yang valid", - "validation_error": "Kesalahan validasi", - "variables": "Variabel", - "version": "Versi", - "version_announcement_closing": "Temanmu, Alex", - "version_announcement_message": "Hai! Versi baru Immich telah tersedia. Harap luangkan waktu untuk membaca catatan rilis untuk memastikan pengaturan Anda terkini untuk mencegah kesalahan konfigurasi, terutama jika Anda menggunakan WatchTower atau mekanisme apa pun yang menangani pembaruan server Immich secara otomatis.", - "version_history": "Riwayat Versi", - "version_history_item": "Terpasang {version} pada {date}", - "video": "Video", - "video_hover_setting": "Putar gambar kecil video saat kursor di atas", - "video_hover_setting_description": "Putar gambar kecil video ketika tetikus berada di atas item. Bahkan saat dinonaktifkan, pemutaran dapat dimulai dengan mengambang di atas ikon putar.", - "videos": "Video", - "videos_count": "{count, plural, one {# Video} other {# Video}}", - "view": "Tampilkan", - "view_album": "Tampilkan Album", - "view_all": "Tampilkan Semua", - "view_all_users": "Tampilkan semua pengguna", - "view_asset_owners": "Lihat pemilik asset", - "view_details": "Tampilkan detil", - "view_in_timeline": "Lihat di timeline", - "view_link": "Tampilkan tautan", - "view_links": "Tampilkan tautan", - "view_name": "Tampilkan", - "view_next_asset": "Tampilkan aset berikutnya", - "view_previous_asset": "Tampilkan aset sebelumnya", - "view_qr_code": "Tampilkan kode QR", - "view_similar_photos": "Lihat foto yang mirip", - "view_stack": "Tampilkan Tumpukan", - "view_user": "Lihat Pengguna", - "viewer_remove_from_stack": "Keluarkan dari Tumpukan", - "viewer_stack_use_as_main_asset": "Gunakan sebagai aset utama", - "viewer_unstack": "Lepas tumpukan", - "visibility_changed": "Keterlihatan diubah untuk {count, plural, one {# orang} other {# orang}}", - "visual": "Visual", - "visual_builder": "Pembuat visual", - "waiting": "Menunggu", - "waiting_count": "Menunggu: {count}", - "warning": "Peringatan", - "week": "Pekan", - "welcome": "Selamat datang", - "welcome_to_immich": "Selamat datang di Immich", - "width": "Lebar", - "wifi_name": "Nama Wi-Fi", - "workflow_delete_prompt": "Apakah anda yakin ingin menghapus alur kerja ini?", - "workflow_deleted": "Alur kerja telah dihapus", - "workflow_description": "Deskripsi alur kerja", - "workflow_info": "Informasi alur kerja", - "workflow_json": "JSON alur kerja", - "workflow_json_help": "Ubah konfigurasi alur kerja dengan format JSON. Perubahan akan disinkronisasikan ke pembuat visual.", - "workflow_name": "Nama alur kerja", - "workflow_navigation_prompt": "Apakah anda yakin ingin keluar tanpa menyimpan perubahan anda?", - "workflow_summary": "Ringkasan alur kerja", - "workflow_update_success": "Alur kerja berhasil diubah", - "workflow_updated": "Alur kerja diubah", - "workflows": "Alur kerja", - "workflows_help_text": "Alur kerja untuk otomasi kegiatan pada aset anda sesuai dengan pemicu dan filter", - "wrong_pin_code": "Kode PIN salah", - "year": "Tahun", - "years_ago": "{years, plural, one {# tahun} other {# tahun}} yang lalu", - "yes": "Ya", - "you_dont_have_any_shared_links": "Anda tidak memiliki tautan terbagi", - "your_wifi_name": "Nama Wi-Fi Anda", - "zero_to_clear_rating": "tekan 0 untuk menghapus penilaian pada aset", - "zoom_image": "Perbesar Gambar", - "zoom_to_bounds": "Perbesar ke batas" -} +{} diff --git a/i18n/is.json b/i18n/is.json index a355b71661..0967ef424b 100644 --- a/i18n/is.json +++ b/i18n/is.json @@ -1,1136 +1 @@ -{ - "about": "Um", - "account": "Aðgangur", - "account_settings": "Aðgangs stillingar", - "acknowledge": "Staðfesta", - "action": "Aðgerð", - "action_common_update": "Uppfæra", - "actions": "Aðgerðir", - "active": "Virkt", - "active_count": "Virk: {count}", - "activity": "Virkni", - "activity_changed": "Virkni er {enabled, select, true {enabled} other {disabled}}", - "add": "Bæta við", - "add_a_description": "Bæta við lýsingu", - "add_a_location": "Setja inn staðsetningu", - "add_a_name": "Bæta við nafni", - "add_a_title": "Bæta við titli", - "add_birthday": "Bæta við afmælisdegi", - "add_endpoint": "Bæta við endapunkti", - "add_exclusion_pattern": "Bæta við útilokunarmynstri", - "add_location": "Bæta við staðsetningu", - "add_more_users": "Bæta við fleiri notendum", - "add_partner": "Bæta við maka", - "add_path": "Bæta við slóð", - "add_photos": "Bæta við myndum", - "add_tag": "Bæta við merki", - "add_to": "Bæta í…", - "add_to_album": "Bæta í albúm", - "add_to_album_bottom_sheet_added": "Bætt við {album}", - "add_to_album_bottom_sheet_already_exists": "Þegar í {album}", - "add_to_album_bottom_sheet_some_local_assets": "Ekki gekk að bæta sumum staðværum eignum í myndasafn", - "add_to_album_toggle": "Breyta vali fyrir {album}", - "add_to_albums": "Bæta í albúm", - "add_to_albums_count": "Bætt í albúm ({count})", - "add_to_bottom_bar": "Bæta í", - "add_to_shared_album": "Bæta í deilt albúm", - "add_upload_to_stack": "Bæta upphleðslu í hrúgu", - "add_url": "Setja inn URL", - "added_to_archive": "Bætt í geymslu", - "added_to_favorites": "Bætt í uppáhalds", - "added_to_favorites_count": "Bætti {count, number} í uppáhalds", - "admin": { - "add_exclusion_pattern_description": "Bæta við útolokunarmynstri. Algildisstöfun með *, ** og ? er stutt. Til að hundsa allar skrár í öllum möppum sem heita \"Raw\", notið \"**/Raw/**\". Til að hundsa allar skrár sem enda á \".tif\", notið \"**/*.tif\". Til að hundsa beinar slóðir notið \"/sleppt/slóð/**\".", - "admin_user": "Stjórnandi", - "asset_offline_description": "Þessi utanaðkomandi skrá í safninu finnst ekki lengur á disknum og hefur verið færð í ruslið. Ef skráin var færð til innan safnsins skaltu athuga tímalínuna fyrir nýja, samsvarandi skrá. Til að endurheimta þessa skrá skaltu tryggja að Immich hafi aðgang að skráarslóðinni hér að neðan og skanna síðan safnið aftur.", - "authentication_settings": "Auðkenningarstillingar", - "authentication_settings_description": "Sýsla með lykilorð, OAuth og aðrar auðkennisstillingar", - "authentication_settings_disable_all": "Ertu viss um að þú viljir slökkva á öllum innskráningar aðferðum? Innskráning mun vera algjörlega slökkt.", - "authentication_settings_reenable": "Til að kveikja aftur, notaðu Þjóns skipun.", - "background_task_job": "Bakgrunns verk", - "backup_database": "Búa til gagnagrunnsafrit", - "backup_database_enable_description": "Virkja gagnagrunnsafrit", - "backup_keep_last_amount": "Fjöldi afrita sem á að geyma", - "backup_onboarding_1_description": "fjar afrit í skýinu eða á öðrum stað.", - "backup_onboarding_2_description": "afrit sem geymd eru á staðnum á fleiri en einu tæki. Þetta nær bæði yfir upprunalegu gögnin og staðbundið öryggisafrit þeirra.", - "backup_onboarding_3_description": "heildar afrit af gögnunum þínum, þar á meðal upprunanlegu skrárnar. Þá er um að ræða 1 fjar afrit og 2 afrit á staðnum.", - "backup_onboarding_description": "Mælt er með 3-2-1 afritun til að vedna gögnin þín. Þú ættir að eiga afrit af upphlöðnum myndum/myndböndum sem og Immich gagnagrunninum sem alhliða afritunar lausn.", - "backup_onboarding_footer": "Fyrir frekari upplýsingar um afritun fyrir Immich þá bendum við á handbókina.", - "backup_onboarding_parts_title": "3-2-1 afrit er:", - "backup_onboarding_title": "Afrit", - "backup_settings": "Stillingar fyrir gagnagrunnsafritun", - "backup_settings_description": "Stjórna stillingum fyrir gagnagrunnsafrit.", - "cleared_jobs": "Hreinsaði ferla fyrir: {job}", - "config_set_by_file": "Stillingar eru skilgreindar í stilliskrá eins og er", - "confirm_delete_library": "Ertu viss um að þú viljir eyða safni {library}?", - "confirm_delete_library_assets": "Ertu viss um að þú viljir eyða þessu safni? Þetta mun eyða {count, plural, one {# contained asset} other {all # contained assets}} úr Immich og er ekki hægt að afturkalla. Skrár munu ekki eyðast af diski.", - "confirm_email_below": "Skrifaðu \"{email}\" hér að neðan til staðfestingar", - "confirm_reprocess_all_faces": "Ertu viss um að þú viljir endurskanna öll andlit? Þetta mun einnig hreinsa nefnt fólk.", - "confirm_user_password_reset": "Ertu viss um að þú viljir endursetja lykilorð fyrir {user}?", - "confirm_user_pin_code_reset": "Ertu viss um að þú viljir endursetja PIN kóða fyrir {user}?", - "copy_config_to_clipboard_description": "Afrita núverandi stillingar sem JSON", - "create_job": "Búa til feril", - "cron_expression": "Cron segð", - "cron_expression_description": "Setja tímabil milli skoðana á cron formi. Sjá Crontab Guru fyrir frekari upplýsingar", - "cron_expression_presets": "Forstilltar cron segðir", - "disable_login": "Afvirkja innskráningu", - "duplicate_detection_job_description": "Keyra vélnám á eignir til að greina svipaðar myndir. Reiðir sig á Snjallleit", - "exclusion_pattern_description": "Útilokunarmynstur má nota til að hundsa skrár og möppur þegar myndasöfn eru skönnuð. Þetta nýtist þegar sleppa á vissum skrám eins og t.d. RAW skrám.", - "export_config_as_json_description": "Niðurhala núverandi kerfisstillingum sem JSON skrá", - "external_libraries_page_description": "Stjórnunarsíða utanáliggjandi myndasafna", - "face_detection": "Andlitsgreining", - "face_detection_description": "Greina andlit á eignum með vélnámi. Aðeins gaummynd er unnin í tilfelli myndbanda. \"Endurnýja\" (endur-)skoðar allar eignir. \"Endursetja\" hreinsar einnig núverandi andlitsgögn. \"Óunnið\" setur eignir í biðröð sem hafa ekki enn verið unnar. Greind andlit fara í biðröð fyrir andlitakennsl eftir að andlitsgreiningu er lokið og safnar þeim saman í núverandi eða nýtt fólk.", - "facial_recognition_job_description": "Safna greindum andlitum saman í fólk. Þetta skref keyrir að Andlitsgreiningu lokinni. \"Endursetja\" (endur-)hópar öllum andlitum saman. \"Óunnið\" setur andlit í biðröð sem hafa ekki verið tengd við manneskju.", - "failed_job_command": "Skipun {command} klúðraðist fyrir ferli: {job}", - "force_delete_user_warning": "AÐVÖRUN: Þetta mun fjarlægja notanda og allar eignir á stundinni. Þetta er ekki hægt að afturkalla og skrár geta ekki verið endurheimtar.", - "image_format": "Snið", - "image_format_description": "WebP framkallar smærri skrár en JPEG, en er hægvirkara í kóðun.", - "image_fullsize_description": "Mynd í fullri stærð án lýsigagna, notað þegar þysjað er að", - "image_fullsize_enabled": "Virkja framleiðslu mynda í fullri stærð", - "image_fullsize_enabled_description": "Framleiða myndir í fullri stærð fyrir snið ótilfallin vefnum. Þegar \"Kjósa innbyggða gægjumynd\" er valið þá er innbyggð gægjumynd notuð án umskráningar. Hefur ekki áhrif á snið tilfallin vefnum eins og JPEG.", - "image_fullsize_quality_description": "Myndgæði myndar í fullri stærð frá 1-100. Hærra er betra en skráastærðin verður meiri.", - "image_fullsize_title": "Stillingar Mynda í Fullri Stærð", - "image_prefer_embedded_preview": "Kjósa innbyggða gaummynd", - "image_prefer_embedded_preview_setting_description": "Nota innbyggða gaummynd í RAW myndum sem inntakið í myndvinnslu þegar hægt er. Þetta gæti framleitt nákvæmari liti fyrir sumar myndir, en gæði gaummyndarinnar er háð myndavélinni og myndin gæti haft lýti vegna þjöppunar.", - "image_prefer_wide_gamut": "Kjósa vítt litróf", - "image_prefer_wide_gamut_setting_description": "Nota Display P3 fyrir gaummyndir. Þetta viðheldur litríki mynda með víða litrýmd, en myndir gætu birst mismunandi á gömlum tækjum með gamla vafra. Myndir með sRGB litrýmd er haldið í sRGB til að komast hjá hliðrun lita.", - "image_preview_description": "Mynd í miðstærð án lýsigagna, notað við skoðun stakrar myndar og fyrir vélnám", - "image_preview_quality_description": "Myndgæði gaummyndar frá 1-100. Hærra er betra, en stærð skráar verður meiri sem gæti haft áhrif á snerpu forritsins. Lágt gildi gæti haft áhrif á gæði vélnáms.", - "image_preview_title": "Stillingar Forskoðunar", - "image_quality": "Gæði", - "image_resolution": "Upplausn", - "image_resolution_description": "Hærri upplausn getur varðveitt smáatriði í myndum en tekur lengri tíma í kóðun, býr til stærri skrár og getur haft áhrif á snerpu forritsins.", - "image_settings": "Stillingar Mynda", - "image_settings_description": "Sýsla með gæði og upplausn framleiddra mynda", - "image_thumbnail_description": "Lítil gaummynd án lýsigagna, notað þegar margar myndir eru skoðaðar eins og á tímalínunni", - "image_thumbnail_quality_description": "Gæði gaummynda frá 1-100. Hærra er betra, en býr til stærri skrár sem geta haft áhrif á snerpu forritsins.", - "image_thumbnail_title": "Stillingar Gaummynda", - "import_config_from_json_description": "Færa inn kerfisstillingar með því að hlaða upp JSON skrá", - "job_concurrency": "samvinnsla {job}", - "job_created": "Ferill búinn til", - "job_not_concurrency_safe": "Þessi ferill er ekki öruggur í samvinnslu.", - "job_settings": "Stillingar Ferla", - "job_settings_description": "Sýsla með samvinnslu ferla", - "jobs_delayed": "{jobCount, plural, other {# seinkað}}", - "jobs_failed": "{jobCount, plural, other {# klúður}}", - "jobs_over_time": "Ferlar yfir tíma", - "library_created": "Bjó til myndasafn: {library}", - "library_deleted": "Myndasafni eytt", - "library_details": "Smáatriði myndasafns", - "library_folder_description": "Tilgreindu möppu til að hlaða inn. Þessi mappa og undirmöppur hennar verða skannaðar m.t.t. mynda og myndbanda.", - "library_remove_exclusion_pattern_prompt": "Ertu viss um að þú viljir fjarlægja þetta útilokunarmynstur?", - "library_remove_folder_prompt": "Ertu viss um að þú viljir fjarlægja þessa innfluttu möppu?", - "library_scanning": "Lotubundin Skönnun", - "library_scanning_description": "Sýsla með lotubundna skönnun myndasafna", - "library_scanning_enable_description": "Virkja lotubundna skönnun myndasafna", - "library_settings": "Utanaðkomandi Myndasafn", - "library_settings_description": "Sýsla með stillingar utanaðkomandi myndasafna", - "library_tasks_description": "Skanna utanaðkomandi myndasöfn m.t.t. nýrra og/eða breyttra eigna", - "library_updated": "Uppfærði myndasafn", - "library_watching_enable_description": "Fylgjast með breytingum í utanaðkomandi myndasöfnum", - "library_watching_settings": "Vöktun myndasafna [Á TILRAUNASTIGI]", - "library_watching_settings_description": "Fylgjast sjálfkrafa með breytingum á skrám", - "logging_enable_description": "Virkja atburðaskráningu", - "logging_level_description": "Þegar virkjað, hvaða stig atburðaskráninga skal nota.", - "logging_settings": "Atburðaskráning", - "machine_learning_availability_checks": "Athuganir á tiltækileika", - "machine_learning_availability_checks_description": "Sjálfkrafa nema og nota frekar tiltækilega vélnámsþjóna", - "machine_learning_availability_checks_enabled": "Virkja athuganir á tiltækileika", - "machine_learning_availability_checks_interval": "Lota athugana", - "machine_learning_availability_checks_interval_description": "Bil í millisekúndum á milli athugana á tiltækileika", - "machine_learning_availability_checks_timeout": "Tímamörk beiðnar", - "machine_learning_availability_checks_timeout_description": "Tímamörk í millisekúndum fyrir athuganir á tiltækileika", - "machine_learning_clip_model": "CLIP módel", - "machine_learning_clip_model_description": "Nafn CLIP módels á lista. Athugaðu að keyra þarf feril fyrir 'Snjalleit' á allar myndir ef módeli er breytt.", - "machine_learning_duplicate_detection": "Greina Fjölföldun", - "machine_learning_duplicate_detection_enabled": "Virkja greiningu fjölföldunar", - "machine_learning_duplicate_detection_enabled_description": "Ef óvirkjað verða samt nákvæmlega eins eignir einfaldaðar.", - "machine_learning_duplicate_detection_setting_description": "Nota CLIP ívaf til að finna líklegar fjölfaldanir", - "machine_learning_enabled": "Virkja vélnám", - "machine_learning_enabled_description": "Ef óvirkjað verður öll vélnámsvirkni óvirkjuð, burtséð frá stillingum að neðan.", - "machine_learning_facial_recognition": "Andlitskennsl", - "machine_learning_facial_recognition_description": "Nema, greina og hópa andlit á myndum", - "machine_learning_facial_recognition_model": "Módel andlitskennsla", - "machine_learning_facial_recognition_model_description": "Módel eru listuð í minnkandi stærðarröð. Stærri módel eru hægvirkari og minnisfrekari, en skila betri niðurstöðum. Athugaðu að keyra þarf feril fyrir Andlitsgreiningu á allar myndir ef módeli er breytt.", - "machine_learning_facial_recognition_setting": "Virkja andlitsgreiningu", - "machine_learning_facial_recognition_setting_description": "Ef óvirkjað verða myndir ekki kóðaðar fyrir andlitsgreiningu og munu ekki fylla Fólk hlutann undir Skoða síðunni.", - "machine_learning_max_detection_distance": "Hámarks greiningarfjarlægð", - "machine_learning_max_detection_distance_description": "Hámark fjarlægðar á milli tveggja mynda þannig að um tvítekningu sé að ræða á bilinu 0.001-0.1 Hærri gildi greina fleiri fjölfaldanir á kostnað réttleika.", - "machine_learning_max_recognition_distance": "Hámarks greiningarfjarðlægð", - "machine_learning_max_recognition_distance_description": "Hámarks fjarlægð á milli tveggja andlita, á bilinu 0-2, svo þau séu talin tilheyra sömu manneskjunni. Sé gildið lækkað gæti það komið í veg fyrir að tvær aðskildar manneskjur séu merktar sem sú sama, en hækkun á gildinu kæmi í veg fyrir að sama manneskjan sé talin vera margar. Athugið að það er auðveldara að sameina tvær manneskjur en það er að skipta einni í tvennt, svo betra er að vera í lægri kantinum.", - "machine_learning_min_detection_score": "Lægsta greiningargildi", - "machine_learning_min_detection_score_description": "Lágmarks traustgildi þess að andlit sé greint, frá 0-1. Lægri gildi greina fleiri andlit en gera fleiri mistök.", - "machine_learning_min_recognized_faces": "Lágmark greindra andlita", - "machine_learning_min_recognized_faces_description": "Lágmarks fjöldi greindra andlita sem þarf til að persóna sé sköpuð. Hærri gildi gerir Andlitskennsl nákvæmari á kostnað þess að líklegra sé að andlit sé ekki kennt við persónu.", - "machine_learning_ocr": "Bókstafagreining", - "machine_learning_ocr_description": "Nota vélanám til að bera kenns á texta í myndum", - "machine_learning_ocr_enabled": "Virkja Bókstafagreiningu", - "machine_learning_ocr_enabled_description": "Ef óvirkt, verður ekki skimað eftir texta í myndum.", - "machine_learning_ocr_max_resolution": "Hámarks upplausn", - "machine_learning_ocr_max_resolution_description": "Stærð gaummynda með hærri upplausn en tilgreint er hér verður breytt án þess að breyta hlutföllum myndarinnar. Hærri gildi lýsa myndinni betur, en nota meira minni og afl.", - "machine_learning_ocr_min_detection_score": "Lágmarks greinigargildi", - "machine_learning_ocr_min_detection_score_description": "Lágmark traustgildi þess að texti sé greindur, frá 0-1. Lægri gildi greina meiri texta á kostnað réttleika.", - "machine_learning_ocr_min_recognition_score": "Lágmarks greiningargildi", - "machine_learning_ocr_min_score_recognition_description": "Lámarks greiningargildi krafist til að texti sé greindur frá 0-1. Lægri gildi munu greina meiri texta en geta valdið villum.", - "machine_learning_ocr_model": "Bókstafagreiningar módel", - "machine_learning_ocr_model_description": "Vélþjónalíkön eru nákvæmari en farandslíkön á kostnað tíma og minnisnotkunar.", - "machine_learning_settings": "Stillingar Vélnáms", - "machine_learning_settings_description": "Sýsla með eiginleika og stillingar vélnáms", - "machine_learning_smart_search": "Snjöll Leit", - "machine_learning_smart_search_description": "Leita merkingarlega að myndum með CLIP ívafi", - "machine_learning_smart_search_enabled": "Virkja snjalla leit", - "machine_learning_smart_search_enabled_description": "Myndir verða ekki kóðaðar fyrir snjallleit sé slökkt á þessu.", - "machine_learning_url_description": "Veffang vélnámsþjónsins. Ef fleira en eitt veffang er tilgreint verður haft samband við eitt þeirra í einu þar til eitthvað svarar, í þeirri röð sem þau eru tilgreind.", - "maintenance_settings": "Viðhald", - "maintenance_settings_description": "Setja Immich í viðhaldsham.", - "maintenance_start": "Virkja viðhaldsham", - "maintenance_start_error": "Ekki gekk að virkja viðhaldsham.", - "manage_concurrency": "Sýsla með samvinnslu", - "manage_concurrency_description": "Farðu á stillingasíðuna til að sýsla með samvinnslu", - "manage_log_settings": "Sýsla með atburðaskrá", - "map_dark_style": "Dökk ásjá", - "map_enable_description": "Virkja kortamöguleika", - "map_gps_settings": "Kort & GPS stillingar", - "map_gps_settings_description": "Sýsla með Kort og GPS (afturvirk landkóðun)", - "map_implications": "Kortaeiginleikinn krefst utanaðkomandi þjónustu (tiles.immich.cloud)", - "map_light_style": "Ljós ásjá", - "map_manage_reverse_geocoding_settings": "Sýsla með Afturvirka Landkóðun", - "map_reverse_geocoding": "Afturvirk Landkóðun", - "map_reverse_geocoding_enable_description": "Virkja afturvirka landkóðun", - "map_reverse_geocoding_settings": "Stillingar Afturvirkrar Landkóðunar", - "map_settings": "Kort", - "map_settings_description": "Breyta kortastillingum", - "map_style_description": "Veffang sem vísar á style.json kortaþema", - "memory_cleanup_job": "Hreinsa geymslupláss", - "memory_generate_job": "Framleiðsla minnis", - "metadata_extraction_job": "Sækja lýsigögn", - "metadata_extraction_job_description": "Sækja lýsigögn frá sérhverri eign, t.a.m. GPS, andlit og upplausn", - "metadata_faces_import_setting": "Virkja innflutning andlita", - "metadata_faces_import_setting_description": "Flytja inn andlit úr EXIF gögnum og fylgiskrám", - "metadata_settings": "Stillingar Lýsigagna", - "metadata_settings_description": "Sýsla með lýsigögn", - "migration_job": "Flutningar", - "migration_job_description": "Flytja gaummyndir eigna og andlita í nýjasta skráaskipulag", - "nightly_tasks_cluster_faces_setting_description": "Keyra andlitakennsl á nýlega greind andlit", - "nightly_tasks_cluster_new_faces_setting": "Hópa ný andlit", - "nightly_tasks_database_cleanup_setting": "Ferlar hreinsunar gagnagrunns", - "nightly_tasks_database_cleanup_setting_description": "Hreinsa upp gömul útrunnin gögn úr gagnagrunni", - "nightly_tasks_generate_memories_setting": "Framleiða minningar", - "nightly_tasks_generate_memories_setting_description": "Búa til nýjar minningar úr eign", - "nightly_tasks_missing_thumbnails_setting": "Framleiða gaummyndir þar sem vantar", - "nightly_tasks_missing_thumbnails_setting_description": "Setja eignir án gaummynda í biðröð fyrir framleiðslu gaummynda", - "nightly_tasks_settings": "Stillingar næturferla", - "nightly_tasks_settings_description": "Sýsla með næturferla", - "nightly_tasks_start_time_setting": "Upphafstími", - "nightly_tasks_start_time_setting_description": "Sá tími þegar vélþjónninn hefur keyrslu næturferilsins", - "nightly_tasks_sync_quota_usage_setting": "Notkun úthlutaðs pláss", - "nightly_tasks_sync_quota_usage_setting_description": "Uppfæra úthlutað pláss notanda, byggt á núverandi notkun", - "no_paths_added": "Engum slóðum bætt við", - "no_pattern_added": "Engu mynstri bætt við", - "note_apply_storage_label_previous_assets": "Athugið: Til að beita Geymslumerkingu á fyrirliggjandi eignir, keyrðu", - "note_cannot_be_changed_later": "ATHUGIÐ: Þessu getur ekki verið breytt seinna!", - "notification_email_from_address": "Frá tölvupóstfang", - "notification_email_from_address_description": "Netfang sendanda, til dæmis \"Immich myndaþjónn \". Notið netfang sem þið hafið leyfi til að senda frá.", - "notification_email_host_description": "Hýsill tölvupóstþjónsins (t.d. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Hundsa villur í TLS skírteini", - "notification_email_ignore_certificate_errors_description": "Hundsa villur í sannreynslu TLS skírteinis (slæm hugmynd)", - "notification_email_password_description": "Lykilorð til auðkenningar á póstþjóninum", - "notification_email_port_description": "Tengi póstþjónustunnar (t.d. 25, 465 eða 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Nota SMTPS (SMTP yfir TLS)", - "notification_email_sent_test_email_button": "Senda prufupóst og vista", - "notification_email_setting_description": "Stillingar fyrir tölvupóstmeldingar", - "notification_email_test_email": "Senda prufupóst", - "notification_email_test_email_failed": "Ekki gekk að senda prufupóst, athugið stillingar", - "notification_email_test_email_sent": "Prufupóstur hefur verið sendur á {email}. Vinsamlega kíktu í pósthólfið þitt.", - "notification_email_username_description": "Notandanafn til auðkenningar á póstþjóni", - "notification_enable_email_notifications": "Virkja meldingar í tölvupósti", - "notification_settings": "Stillingar fyrir meldingar", - "notification_settings_description": "Sýsla með meldingar, þ.á.m. tölvupóst", - "oauth_auto_launch": "Hefja sjálfkrafa", - "oauth_auto_launch_description": "Hefja OAuth innskráninguna sjálfkrafa þegar farið er á innskráningarsíðuna", - "oauth_auto_register": "Sjálfvirk skráning", - "oauth_auto_register_description": "Skrá nýja notendur sjálfkrafa eftir að þeir hafa skráð sig inn með OAuth", - "oauth_button_text": "Texti á takka", - "oauth_client_secret_description": "Krafist ef PKCE (Proof Key for Code Exchange) er ekki stutt af OAuth aðila", - "oauth_enable_description": "Skrá inn með OAuth", - "oauth_mobile_redirect_uri": "Veffang endurbeiningar", - "oauth_mobile_redirect_uri_override": "Yfirtaka veffangs endurbeiningar", - "oauth_mobile_redirect_uri_override_description": "Virkja þegar OAuth aðili leyfir ekki endurbeiningar á borð við \"{callback}\"", - "oauth_role_claim": "Hlutverkakrafa", - "oauth_role_claim_description": "Veita stjórunaraðgang sjálfkrafa byggt á verund þessarar kröfu. Krafan getur annað hvort verið 'user' eða 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Sýsla með OAuth innskráningu", - "oauth_settings_more_details": "Fyrir frekari upplýsingar um þennan eiginleika, kíktu í leiðbeiningarnar.", - "oauth_storage_label_claim": "Krafa geymslumerkingar", - "oauth_storage_label_claim_description": "Stilla geymslumerkingar notanda sjálfkrafa samkvæmt gildi þessarar kröfu.", - "oauth_storage_quota_claim": "Krafa úthlutaðs pláss", - "oauth_storage_quota_claim_description": "Stilla úthlutað pláss notanda sjálfkrafa samkvæmt gildi þessarar kröfu.", - "oauth_storage_quota_default": "Sjálfgefin stærð úthlutaðs pláss í GiB", - "oauth_storage_quota_default_description": "Úthlutað pláss í GiB þegar engin krafa er gefin.", - "oauth_timeout": "Tímamörk beiðna", - "oauth_timeout_description": "Tímamörk fyrir beiðnir í millisekúndum", - "ocr_job_description": "Notkun vélnáms til að greina texta í myndum", - "password_enable_description": "Skrá inn með netfangi og lykilorði", - "password_settings": "Innskráning með lykilorði", - "password_settings_description": "Sýsla með innskráningar með lykilorðum", - "paths_validated_successfully": "Allar slóðir hafa verið sannreyndar", - "person_cleanup_job": "Hreinsun manneskja", - "queue_details": "Upplýsingar um biðröð", - "queues": "Biðraðir ferla", - "queues_page_description": "Síða biðraða stjórnunarferla", - "quota_size_gib": "Stærð úthlutaðs pláss (GiB)", - "refreshing_all_libraries": "Fríska upp á öll myndasöfn", - "registration": "Skráning stjórnenda", - "registration_description": "Þar sem þú ert fyrsti notandi kerfisins verður þú gerður að Stjórnanda og ert sem slíkur ábyrgur fyrir stjórnunarverkum og fleiri notendur verða búnir til af þér.", - "remove_failed_jobs": "Fjarlægja brostna ferla", - "require_password_change_on_login": "Notandi þarf að breyta lykilorði við innskráningu", - "reset_settings_to_default": "Endurstilla í sjálfgefin gildi", - "reset_settings_to_recent_saved": "Endurstilla í nýlega vistaðar stillingar", - "scanning_library": "Skanna myndasafn", - "search_jobs": "Leitarferlar…", - "send_welcome_email": "Senda kveðju í tölvupósti", - "server_external_domain_settings": "Utanaðkomandi lén", - "server_external_domain_settings_description": "Lén fyrir deilda hlekki, með http(s)://", - "server_public_users": "Almennir notendur", - "server_public_users_description": "Allir notendur (nafn og netfang) eru listaðir upp þegar notanda er bætt við deilt myndasafn. Listinn verður aðeins aðgengilegur stjórnendum sé slökkt á þessu.", - "server_settings": "Stillingar vélþjóns", - "server_settings_description": "Sýsla með vélþjón", - "server_stats_page_description": "Stjórnunarsíða fyrir tölfræði vélþjóns", - "server_welcome_message": "Kveðja", - "server_welcome_message_description": "Skilaboð sem eru birt á innskráningarsíðunni.", - "settings_page_description": "Stjórnstillingar", - "sidecar_job": "Lýsigögn í fylgiskrám", - "sidecar_job_description": "Uppgötva eða samræma lýsigögn í fylgiskrám á skráakerfi", - "slideshow_duration_description": "Fjöldi sekúndna sem hver mynd er sýnd", - "smart_search_job_description": "Keyra vélnám á eignir til að styða snjalla leit", - "storage_template_date_time_description": "Tímistimpill uppruna eignarinnar er notaður í tímaupplýsingar", - "storage_template_date_time_sample": "Sýnishorn tíma {date}", - "storage_template_enable_description": "Virkja sniðmát fyrir gagnageymslu", - "storage_template_hash_verification_enabled": "Hakkagildi sannreynd", - "storage_template_hash_verification_enabled_description": "Sannreynir hakkagildi, ekki slökkva á þessu án þess að gera þér grein fyrir afleiðingunum", - "storage_template_migration": "Yfirfærsla á geymslusniðmáti", - "storage_template_migration_description": "Beita núverandi {template} á eignir sem hafa nú þegar verið færðar inn", - "storage_template_migration_info": "Geymslusniðmátið mun breyta öllum nafnaukum í lágstafi. Breytingar á sniðmáti eiga bara við nýjar eignir. Til að beita breytingum á fyrirliggjandi eignir má keyra ferilinn {job}.", - "storage_template_migration_job": "Ferill yfirfærslu geymslusniðmáts", - "storage_template_more_details": "Frekari upplýsingar um þennan eiginleika, sjá Geymslusniðmátið og afleiðingar þess", - "storage_template_onboarding_description_v2": "Þetta virkjar sjálfvirkt skipulag á skrám samkvæmt fyrirfram skilgreindum sniðmátum. Sjá leiðbeiningar fyrir frekari upplýsingar.", - "storage_template_path_length": "Gróft takmark á lengd slóðar: {length,number}/{limit,number}", - "storage_template_settings": "Geymslusniðmát", - "storage_template_settings_description": "Sýsla með uppbyggingu skráakerfis og heiti eigna sem færðar eru inn", - "storage_template_user_label": "{label} er Geymslumerki notandans", - "system_settings": "Kerfisstillingar", - "tag_cleanup_job": "Hreinsun á merkjum", - "template_email_available_tags": "Þú getur notað eftirfarandi breytur í sniðmátinu: {tags}", - "template_email_if_empty": "Ef sniðmátið er tómt mun sjálfgefna netfangið vera notað.", - "template_email_invite_album": "Sniðmát Boðs í Myndasafn", - "template_email_preview": "Forskoðun", - "template_email_settings": "Sniðmát Tölvupósts", - "template_email_update_album": "Uppfæra Sniðmát Myndasafna", - "template_email_welcome": "Sniðmát kveðjupósts", - "template_settings": "Sniðmát meldinga", - "template_settings_description": "Sýsla með sérsniðin sniðmát fyrir meldingar", - "theme_custom_css_settings": "Sérsniðið CSS", - "theme_custom_css_settings_description": "Stölluð Stílblöð leyfa þér að breyta hönnun Immich.", - "theme_settings": "Þemastillingar", - "theme_settings_description": "Sýsla með sérstillingar vefviðmóts Immich", - "thumbnail_generation_job": "Framleiða Gaummyndir", - "thumbnail_generation_job_description": "Framleiða stórar, smáar og skýjaðar gaummyndir fyrir sérhverja eign, sem og gaummynd fyrir hverja manneskju", - "transcoding_acceleration_api": "Forritaskil Hröðunar", - "transcoding_acceleration_api_description": "Forritaskilin sem tala við tækið þitt til að hraða kóðun. Þessi stilling er eftir 'bestu getu: Það fellur til baka á kóðun í hugbúnaði við villu. VP9 virkar kannski, fer eftir vélbúnaði.", - "transcoding_acceleration_nvenc": "NVENC (krefst NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (krefst Intel örgjafa af 7. kynslóð eða nýrri)", - "transcoding_acceleration_rkmpp": "RKMPP (einungis á Rockchip SOC)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Leyfðir víxlbreytar hljóðs", - "transcoding_accepted_audio_codecs_description": "Velja hvaða víxlbreytar hljóðs þarf ekki að umkóða. Aðeins notað í sumum umkóðunarreglum.", - "transcoding_accepted_containers": "Leyfðir kassar", - "transcoding_accepted_containers_description": "Velja hvaða kassasnið þarf ekki að endurblanda í MP4. Aðeins notað í sumum umkóðunarreglum.", - "transcoding_accepted_video_codecs": "Leyfðir víxlbreytar myndbands", - "transcoding_accepted_video_codecs_description": "Velja hvaða víxlbreytar myndbands þarf ekki að umkóða. Aðeins notað í sumum umkóðunarreglum.", - "transcoding_advanced_options_description": "Valmöguleikar sem flestir notendur ættu ekki að þurfa að breyta", - "transcoding_audio_codec": "Víxlbreytir hljóðs", - "transcoding_audio_codec_description": "Opus gefur mestu gæðin, en hefur verra samhæfi með eldri vél- og hugbúnaði.", - "transcoding_bitrate_description": "Myndbönd með hærri en hámarks bitahraða eru ekki í leyfðu sniði", - "transcoding_codecs_learn_more": "Sjá leiðbeiningar fyrir H.264 víxlbreyti, HEVC víxlbreyti og VP9 víxlbreyti til að læra meira um íðorðastaðalinn í notkun hér.", - "transcoding_constant_quality_mode": "Hamur stöðugra gæða", - "transcoding_constant_quality_mode_description": "ICQ er betra en CQP, en vélbúnaðarhröðun sumra tækja styður það ekki. Þessi valmöguleiki mun kjósa tilgreinda haminn þegar gæða-byggð kóðun er notuð. Hundsað af NVENC þar sem það styður ekki ICQ.", - "transcoding_constant_rate_factor": "Stuðull stöðugs hraða (-crf)", - "transcoding_constant_rate_factor_description": "Gæðastig myndbanda. Algeng gildi eru 23 fyrir H.264, 28 fyrir HEVC, 31 fyrir VP9 og 35 fyrir AV1. Lægra er betra, en býr til stærri skrár.", - "transcoding_disabled_description": "Ekki umkóða nein myndbönd, gæti brotið afspilun hjá sumum", - "transcoding_encoding_options": "Valmöguleikar kóðunar", - "transcoding_encoding_options_description": "Stilla víxlbreyta, upplausn, gæði og fleira fyrir kóðun myndbanda", - "transcoding_hardware_acceleration": "Vélhröðun", - "transcoding_hardware_acceleration_description": "Á tilraunastigi: hraðari kóðabreyting á kostnað gæða á stöðugum bitahraða", - "transcoding_hardware_decoding": "Afkóðun með vélbúnaði", - "transcoding_hardware_decoding_setting_description": "Virkjar hröðun milli enda í stað hröðunar á kóðun einni og sér. Virkar kannski ekki á öll myndbönd.", - "transcoding_max_b_frames": "Hámarks B-rammar", - "transcoding_max_b_frames_description": "Hærri gildi bæta afköst í þjöppun á kostnað kóðunarhraða. Gæti verið ósamhæft vélhröðun á eldri tækjum. 0 slekkur á B-römmum á meðan -1 velur gildi sjálfkrafa.", - "transcoding_max_bitrate": "Hámarks bitahraði", - "transcoding_max_bitrate_description": "Hámarks bitahraði gefur fyrirsjáanlegri skráastærðir á lítinn kostnað gæða. Týpísk gildi fyrir 720p gæða skrá eru 2600kbit/s fyrir VP9 eða HEVC, eða 4500kbit/s fyrir H.264. Óvirkt ef gildi er 0. Þegar eining er óskilgreind er gert ráð fyrir k fyrir kbit/s; 5000, 5000k og 5M (fyrir Mbit/s) eru því jafngild.", - "transcoding_max_keyframe_interval": "Hámarks millibil lyklaramma", - "transcoding_max_keyframe_interval_description": "Setur hámarksfjarlægð ramma á milli lyklaramma. Lægri gildi bæta leitartíma og gæði í senum með snöggum hreyfingum á kostnað afkasta þjöppunar. 0 velur gildið sjálfkrafa.", - "transcoding_optimal_description": "Myndbönd með hærri upplausn en miðað er á eða ekki í leyfðu sniði", - "transcoding_policy": "Reglur umkóðunar", - "transcoding_policy_description": "Stillt þegar myndband mun vera umkóðað", - "transcoding_preferred_hardware_device": "Ákjósanlegur vélbúnaður", - "transcoding_preferred_hardware_device_description": "Á aðeins við um VAAPI og QSV. Stillir dri nóðuna sem er notuð í vélhröðun.", - "transcoding_preset_preset": "Forstilling (-preset)", - "transcoding_preset_preset_description": "Hraði þjöppunar. Lægri forstillingar framleiða smærri skrár og bæta gæði þegar átt er við ákveðna bitahraða. VP9 hundsar hærri gildi en 'hraðara'.", - "transcoding_reference_frames": "Viðmiðunarrammar", - "transcoding_reference_frames_description": "Fjöldi ramma til að miða við þegar gefinn rammi er þjappaður. Hærri vildi bæta afköst þjöppunar á kostnað hraða kóðunar. 0 stillir gildið sjálfkrafa.", - "transcoding_required_description": "Aðeins myndbönd í óleyfðum sniðum", - "transcoding_settings": "Stillingar fyrir umkóðun myndbanda", - "transcoding_settings_description": "Sýsla með hvaða myndbönd eru umkóðuð og hvernig", - "transcoding_target_resolution": "Markupplausn", - "transcoding_target_resolution_description": "Hærri upplausn gæti varðveitt smáatriði á kostnað hraða kóðunar, skráastærð og snerpu forritsins.", - "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Á aðeins við um NVENC. Temporal Adaptive Quantization eykur gæði í senum með miklum smáatriðum og lítilli hreyfingu. Mögulega ósamhæft eldri tækjum.", - "transcoding_threads": "Þræðir", - "transcoding_threads_description": "Hærri gildi samsvara hraðari kóðun, en heldur vélþjóni uppteknum á meðan það er í gangi. Þetta gildi ætti ekki að vera hærra en fjöldi kjarna í örgjafa. Hámarkar afköst ef stillt í 0.", - "transcoding_tone_mapping": "Tónavörpun", - "transcoding_tone_mapping_description": "Tilraunir til að varðveita útlit HDR myndbanda þegar þeim er breytt í SDR. Hvert algrím fórnar ýmist lit, smáatriðum og birtu. Hable varðveitir smáatriði, Mobius viðheldur lit og Reinhard viðheldur birtu.", - "transcoding_transcode_policy": "Reglur umkóðunar", - "transcoding_transcode_policy_description": "Reglur varðandi hvenær ætti að umkóða myndbönd. HRD myndbönd verða alltaf umkóðuð (nema þegar umkóðun er óvirk).", - "transcoding_two_pass_encoding": "Tvítæk kóðun", - "transcoding_two_pass_encoding_setting_description": "Umkóða í tveimur atrennum til að skila betur kóðuðum myndböndum. Þegar hámarks bitahraði er stilltur (skilyrði svo það virki með H.264 og HEVC) notar þessi hamur bitahraða á bili sem er byggt á hámarks bitahraðanum og hundsar CRF. CRF má nota með VP9 ef hámarks bitahraði er óvirkur.", - "transcoding_video_codec": "Víxlbreytir myndbanda", - "transcoding_video_codec_description": "VP9 er afkastamikið og hentar vel á vefnum en er lengri í umkóðun. HEVC er svipað en samhæfist vef ekki jafn vel. H.264 er almennt samæft flestu og er snöggt í umkóðun en því fylgja stórar skrár. AV1 er afkastamesti víxlbreytirinn en er ósamhæft eldri tækjum.", - "trash_enabled_description": "Virkja ruslatunnu", - "trash_number_of_days": "Fjöldi daga", - "trash_number_of_days_description": "Fjöldi daga sem eignum er haldið í rusli áður en þeim er eytt fyrir fullt og allt", - "trash_settings": "Stillingar ruslatunnu", - "trash_settings_description": "Sýsla með rusl", - "unlink_all_oauth_accounts": "Afhlekkja alla OAuth aðganga", - "unlink_all_oauth_accounts_description": "Munið að afhlekkja alla OAuth aðganga áður en flutt er á milli þjónustuaðila.", - "unlink_all_oauth_accounts_prompt": "Ertu viss um að þú viljir afhlekkja alla OAuth aðganga? Það endurstillir öll OAuth auðkenni fyrir alla notendur og er óafturkræft.", - "user_cleanup_job": "Hreinsun notenda", - "user_delete_delay": "Aðgangurinn {user} og eignir hans verður eytt fyrir fullt og allt eftir {delay, plural, one {# dag} other {# daga}}.", - "user_delete_delay_settings": "Seinkun eyðingar", - "user_delete_delay_settings_description": "Fjöldi daga eftir að aðgangur er fjarlægður sem eyða á öllum gögnum tengdum honum. Ferill eyðingar notanda keyrir á miðnætti og athuga hvara notendur eru tilbúnir fyrir eyðingu. Breytingar á þessum stillingum verka við næstu keyrslu ferilsins.", - "user_delete_immediately": "Aðganginum {user} og eignum hans verður eytt undir eins.", - "user_delete_immediately_checkbox": "Setja notanda og eignir í biðröð fyrir eyðingu undir eins", - "user_details": "Upplýsingar um notanda", - "user_management": "Sýsla með notanda", - "user_password_has_been_reset": "Lykilorð eftirfarandi notanda hefur verið endursett:", - "user_password_reset_description": "Látið notandann fá tímabundna lykilorðið og gerið honum grein fyrir því að breyta þurfi lykilorðinu við næstu innskráningu.", - "user_restore_description": "Aðgangurinn {user} verður endurheimtaður.", - "user_restore_scheduled_removal": "Endurheimta notanda - tímasett eyðing á {date, date, long}", - "user_settings": "Stillingar notanda", - "user_settings_description": "Sýsla með stillingar notanda", - "user_successfully_removed": "Notandi {email} hefur verið fjarlægður.", - "users_page_description": "Síða stjórnunarnotanda", - "version_check_enabled_description": "Virkja athugun á útgáfu", - "version_check_implications": "Þessi athugun hefur lotubundin samskipti við github.com", - "version_check_settings": "Athugun útgáfu", - "version_check_settings_description": "Af-/virkja meldingu um nýja útgáfu", - "video_conversion_job": "Umkóða myndbönd", - "video_conversion_job_description": "Umkóða myndbönd fyrir betri samhæfingu með vöfrum og tækjum" - }, - "admin_email": "Netfang stjórnanda", - "admin_password": "Lykilorð stjórnanda", - "administration": "Stjórnun", - "advanced": "Fyrir lengra komna", - "advanced_settings_enable_alternate_media_filter_subtitle": "Notið þessa stillingu til að sía efni þegar því er hlaðið inn byggt á öðrum eiginleikum. Reynið þetta bara ef Immich á í erfiðleikum með að greina öll myndasöfn.", - "advanced_settings_enable_alternate_media_filter_title": "[Á TILRAUNASTIGI] Nota aðrar síur fyrir samstillingu myndasafna á tæki", - "advanced_settings_log_level_title": "Stig atburða: {level}", - "advanced_settings_prefer_remote_subtitle": "Sum tæki eru agalega lengi að hlaða gaummyndum úr staðværum eignum. Virkið þetta til að hlaða inn gaummyndum úr fjarska í staðinn.", - "advanced_settings_prefer_remote_title": "Kjósa myndir úr fjarska", - "advanced_settings_proxy_headers_subtitle": "Skilgreinið vefselshausa sem Immich ætti að senda með hverri beiðni", - "advanced_settings_proxy_headers_title": "Sérsniðnir vefselshausar [Á TILRAUNASTIGI]", - "advanced_settings_readonly_mode_subtitle": "Virkja skoðunarham þar sem aðeins má skoða myndir, aðgerðir á borð við að velja margar myndir, deila, streyma og eyða eru allar bannaðar. Af-/virkið skoðunarham í gegnum notandateiknið á aðalsíðunni", - "advanced_settings_readonly_mode_title": "Skoðunarhamur", - "advanced_settings_self_signed_ssl_subtitle": "Sleppir því að sannreyna TLS skírteini frá vefþjóninum. Nauðsyn ef notast er við sjálfskráð skírteini.", - "advanced_settings_self_signed_ssl_title": "Leyfa sjálfskráð TLS skírteini [Á TILRAUNASTIGI]", - "advanced_settings_sync_remote_deletions_subtitle": "Eyða eða endurheimta eign sjálfkrafa á þessu tæki þegar þetta er framkvæmt á vefnum", - "advanced_settings_sync_remote_deletions_title": "Samræma eyðingu úr fjarska [Á TILRAUNASTIGI]", - "advanced_settings_tile_subtitle": "Notandastillingar fyrir lengra komna", - "advanced_settings_troubleshooting_subtitle": "Virkja frekari eiginleika fyrir bilanagreiningu", - "advanced_settings_troubleshooting_title": "Bilanagreining", - "age_months": "Aldur {months, plural, one {# mánuður} other {# mánuðir}}", - "age_year_months": "Aldur 1 árs, {months, plural, one {# mánaða} other {# mánaða}}", - "age_years": "{years, plural, other {Aldur #}}", - "album": "Myndasafn", - "album_added": "Myndasafni bætt við", - "album_added_notification_setting_description": "Fá meldingu í tölvupósti þegar mér er bætt við deilt myndasafn", - "album_cover_updated": "Kápa myndasafns uppfærð", - "album_delete_confirmation": "Ertu viss um að þú viljir eyða myndasafni {album}?", - "album_delete_confirmation_description": "Ef þessu myndasafni er deilt með öðrum missa þeir notendur aðgang að því.", - "album_deleted": "Myndasafni eytt", - "album_info_card_backup_album_excluded": "SLEPPT", - "album_info_card_backup_album_included": "MEÐ TALIÐ", - "album_info_updated": "Upplýsingar um myndasafn uppfærðar", - "album_leave": "Fara úr myndasafni?", - "album_leave_confirmation": "Ertu viss um að þú viljir segja þig úr {album}?", - "album_name": "Nafn Myndasafns", - "album_options": "Valmöguleikar myndasafns", - "album_remove_user": "Fjarlægja notanda?", - "album_remove_user_confirmation": "Ertu viss um að þú viljir fjarlægja {user}?", - "album_search_not_found": "Engin myndasöfn fundust sem uppfylla leitarskilyrðin", - "album_share_no_users": "Það lítur út fyrir að þú hafir deilt þessu myndasafni með öllum eða þá að þú hafir enga til að deila því með.", - "album_summary": "Samantekt myndasafns", - "album_updated": "Myndasafn uppfært", - "album_updated_setting_description": "Fá meldingu í tölvupósti þegar eignum er bætt í deilt myndasafn", - "album_user_left": "Fór úr {album}", - "album_user_removed": "Fjarlægði {user}", - "album_viewer_appbar_delete_confirm": "Ertu viss um að þú viljir eyða þessu myndasafni úr aðgangnum þínum?", - "album_viewer_appbar_share_err_delete": "Ekki gekk að eyða myndasafni", - "album_viewer_appbar_share_err_leave": "Ekki gekk að fara úr myndasafni", - "album_viewer_appbar_share_err_remove": "Það eru vandamál við að fjarlægja eignir úr þessu myndasafni", - "album_viewer_appbar_share_err_title": "Ekki gekk að breyta titli á myndasafni", - "album_viewer_appbar_share_leave": "Fara úr myndasafni", - "album_viewer_appbar_share_to": "Deila til", - "album_viewer_page_share_add_users": "Bæta við notendum", - "album_with_link_access": "Leyfa hverjum sem hefur hlekkinn að sjá myndir og fólk í myndasafninu.", - "albums": "Myndasöfn", - "albums_count": "{count, plural, one {{count, number} Myndasafn} other {{count, number} Myndasöfn}}", - "albums_default_sort_order": "Sjálfgefin röðun myndasafna", - "albums_default_sort_order_description": "Upphafleg röðun eigna þegar nýtt myndasafn er búið til.", - "albums_feature_description": "Safn af eignum sem hægt er að deila með öðrum notendum.", - "albums_on_device_count": "Myndasöfn á tæki ({count})", - "all": "Allt", - "all_albums": "Öll myndasöfn", - "all_people": "Allt fólk", - "all_videos": "Öll myndbönd", - "allow_dark_mode": "Leyfa skuggaham", - "allow_edits": "Leyfa breytingar", - "allow_public_user_to_download": "Leyfa almennum notendum að hala niður", - "allow_public_user_to_upload": "Leyfa almennum notendum að hlaða upp", - "allowed": "Leyft", - "alt_text_qr_code": "Mynd af snarkóða", - "anti_clockwise": "Öfugur sólarhringur", - "api_key": "API lykill", - "api_key_description": "Þetta gildi er aðeins sýnt einu sinni. Afritaðu það áður en glugganum er lokað.", - "api_key_empty": "Heiti API lyklisins ætti ekki að vera tómt", - "api_keys": "API lyklar", - "app_architecture_variant": "Margbreytni (Högun)", - "app_bar_signout_dialog_content": "Ertu viss um að þú viljir skrá þig út?", - "app_bar_signout_dialog_ok": "Já", - "app_bar_signout_dialog_title": "Skrá út", - "app_download_links": "Hlekkir til að hala niður forriti", - "app_settings": "Stillingar forrits", - "app_stores": "Forritabúðir", - "app_update_available": "Uppærsla er tiltækileg", - "appears_in": "Kemur fyrir í", - "apply_count": "Beita ({count, number})", - "archive": "Geymsla", - "archive_action_prompt": "{count} bætt í Geymslu", - "archive_or_unarchive_photo": "Setja í eða taka úr geymslu", - "archive_page_no_archived_assets": "Engar eignir í geymslu fundust", - "archive_page_title": "Geymsla ({count})", - "archive_size": "Stærð geymslu", - "archive_size_description": "Stillið stærð geymslu fyrir niðurhöl (í GiB)", - "archived": "Geymt", - "archived_count": "{count, plural, other {Geymt #}}", - "are_these_the_same_person": "Eru þessi sama manneskjan?", - "are_you_sure_to_do_this": "Ertu viss um að þú viljir gera þetta?", - "asset_action_delete_err_read_only": "Get ekki eytt eignum í skoðunarham, sleppi", - "asset_action_share_err_offline": "Get ekki sótt ótengdar eignir, sleppi", - "asset_added_to_album": "Bætt í myndasafn", - "asset_adding_to_album": "Bæti í myndasafn…", - "asset_description_updated": "Lýsing eignarinnar hefur verið uppfærð", - "asset_filename_is_offline": "Eign {filename} er ótengd", - "asset_has_unassigned_faces": "Eign er með óskráð andlit", - "asset_hashing": "Hakka…", - "asset_list_group_by_sub_title": "Hópa eftir", - "asset_list_layout_settings_dynamic_layout_title": "Kviklegt viðmót", - "asset_list_layout_settings_group_automatically": "Sjálfvirkt", - "asset_list_layout_settings_group_by": "Hópa eignir eftir", - "asset_list_layout_settings_group_by_month_day": "Mánuður + dagur", - "asset_list_layout_sub_title": "Viðmót", - "asset_list_settings_subtitle": "Stillingar viðmóts myndanets", - "asset_list_settings_title": "Myndanet", - "asset_offline": "Eign Ótengd", - "asset_offline_description": "Þessi utanaðkomandi eign finnst ekki lengur í skráakerfinu. Kallið á hjálp frá ykkar Immich stjórnanda.", - "asset_restored_successfully": "Eign endurheimtuð", - "asset_skipped": "Sleppt", - "asset_skipped_in_trash": "Í rusli", - "asset_trashed": "Eign hent í ruslið", - "asset_troubleshoot": "Bilanagreina eign", - "asset_uploaded": "Hlaðið upp", - "asset_uploading": "Hleð upp…", - "asset_viewer_settings_subtitle": "Sýsla með skoðun myndasafns", - "asset_viewer_settings_title": "Skoðun Eigna", - "assets": "Eignir", - "assets_added_count": "Bætti við {count, plural, one {# eign} other {# eignum}}", - "assets_added_to_album_count": "Bætti við {count, plural, one {# eign} other {# eigum}} í myndasafnið", - "assets_added_to_albums_count": "Bætti við {assetTotal, plural, one {# eign} other {# eignum}} í {albumTotal, plural, one {# myndasafn} other {# myndasöfn}}", - "assets_cannot_be_added_to_album_count": "Ekki hægt að bæta {count, plural, one {eign} other {eignum}} í myndasafnið", - "assets_cannot_be_added_to_albums": "Ekki hægt að bæta {count, plural, one {eign} other {eignum}} í neitt af þessum myndasöfnum", - "assets_count": "{count, plural, one {# eign} other {# eignir}}", - "assets_deleted_permanently": "{count} eign/-um eytt fyrir fullt og allt", - "assets_deleted_permanently_from_server": "{count} eign/-um eytt fyrir fullt og allt af Immich þjóninum", - "assets_downloaded_failed": "{count, plural, one {Sótt # skrá - {error} skrá brast} other {Sóttar # skrár - {error} skrár brugðust}}", - "assets_downloaded_successfully": "{count, plural, one {Sótti # skrá} other {Sótti # skrár}}", - "assets_moved_to_trash_count": "Færði {count, plural, one {# eign} other {# eignir}} í ruslið", - "assets_permanently_deleted_count": "Eyddi {count, plural, one {# eign} other {# eignum}} fyrir fullt og allt", - "assets_removed_count": "Fjarlægði {count, plural, one {# eign} other {# eignir}}", - "assets_removed_permanently_from_device": "{count} eign/-ir fjarlægðar af tækinu þínu fyrir fullt og allt", - "assets_restore_confirmation": "Ertu viss um að þú viljir endurheimta allar eignir úr ruslinu? Þú getur ekki tekið þetta til baka! Athugið að ótengdar eignir geta ekki verið endurheimtar á þennan hátt.", - "assets_restored_count": "Endurheimti {count, plural, one {# eign} other {# eignir}}", - "assets_restored_successfully": "{count} eign/-ir endurheimtar", - "assets_trashed": "{count} eign/-um hent í ruslið", - "assets_trashed_count": "Henti {count, plural, one {# eign} other {# eignum}} í ruslið", - "assets_trashed_from_server": "{count} eign/-um hent í ruslið á Immich þjóninum", - "assets_were_part_of_album_count": "{count, plural, one {Eign var} other {Eignir voru}} nú þegar í myndasafninu", - "assets_were_part_of_albums_count": "{count, plural, one {Eign var} other {Eignir voru}} nú þegar í myndasöfnunum", - "authorized_devices": "Leyfð Tæki", - "automatic_endpoint_switching_subtitle": "Tengjast staðvært yfir tilgreint Wi-Fi þegar það er í boði og nota annarskonar tengingar annarsstaðar", - "automatic_endpoint_switching_title": "Skipta sjálfkrafa um vefslóð", - "autoplay_slideshow": "Spila glærusýningu sjálfkrafa", - "back": "Til baka", - "back_close_deselect": "Til baka, loka eða hætta við val", - "background_backup_running_error": "Afritun er nú þegar í gangi í bakgrunni, get ekki hafið handvirka afritun", - "background_location_permission": "Leyfi á staðsetningu afritunar", - "background_location_permission_content": "Til að skipta um net í bakgrunni þarf Immich *alltaf* að hafa nákvæman aðgang að nákvæmum staðsetningargögnum svo að forritið geti lesið heitið á Wi-Fi netinu", - "background_options": "Bakgrunnsvalmöguleikar", - "backup": "Afrit", - "backup_album_selection_page_albums_device": "Myndasöfn á tæki ({count})", - "backup_album_selection_page_albums_tap": "Smelltu til að taka með, tvísmelltu til að sleppa", - "backup_album_selection_page_assets_scatter": "Eignum getur verið dreift á milli margra myndasafna. Þannig er hægt að velja hvaða myndasöfn eru afrituð.", - "backup_album_selection_page_select_albums": "Velja myndasöfn", - "backup_album_selection_page_selection_info": "Upplýsingar um val", - "backup_album_selection_page_total_assets": "Heildarfjöldi einkvæmra eigna", - "backup_albums_sync": "Samstilling afritaðra myndasafna", - "backup_all": "Allt", - "backup_background_service_backup_failed_message": "Ekki gekk að afrita eignir. Reyni aftur…", - "backup_background_service_complete_notification": "Afritun eigna lokið", - "backup_background_service_connection_failed_message": "Ekki gekk að tengjast þjóni. Reyni aftur…", - "backup_background_service_current_upload_notification": "Hleð upp {filename}", - "backup_background_service_default_notification": "Athuga með nýjar eignir…", - "backup_background_service_error_title": "Afritunarvilla", - "backup_background_service_in_progress_notification": "Afrita eignir…", - "backup_background_service_upload_failure_notification": "Ekki gekk að hlaða upp {filename}", - "backup_controller_page_albums": "Afrituð Myndasöfn", - "backup_controller_page_background_app_refresh_disabled_content": "Virkið bakgrunnsvinnslu í Settings > General > Background App Refresh til að afrita gögn í bakgrunni.", - "backup_controller_page_background_app_refresh_disabled_title": "Bakgrunnsvinnsla óvirk", - "backup_controller_page_background_app_refresh_enable_button_text": "Fara í stillingar", - "backup_controller_page_background_battery_info_link": "Sýndu mér hvernig", - "backup_controller_page_background_battery_info_message": "Slökktu á allri bestun á rafhlöðunotkun fyrir Immich til að fá sem áreiðanlegasta bakgrunnsafritun.\n\nÞetta er mismunandi eftir framleiðanda, flettu upp leiðbeiningum frá þínum framleiðanda.", - "backup_controller_page_background_battery_info_ok": "Í lagi", - "backup_controller_page_background_battery_info_title": "Bestun rafhlöðu", - "backup_controller_page_background_charging": "Aðeins þegar í hleðslu", - "backup_controller_page_background_configure_error": "Ekki gekk að stilla bakgrunnsþjónustuna", - "backup_controller_page_background_delay": "Seinka afritun nýrrar eignar: {duration}", - "backup_controller_page_background_description": "Kveiktu á bakgrunnsþjónustum til að afrita nýjar eignir sjálfkrafa án þess að þurfa að opna forritið", - "backup_controller_page_background_is_off": "Sjálfvirk afritun í bakgrunni er óvirk", - "backup_controller_page_background_is_on": "Sjálfvirk afritun í bakgrunni er virk", - "backup_controller_page_background_turn_off": "Slökkva á bakgrunnsþjónustu", - "backup_controller_page_background_turn_on": "Kveikja á bakgrunnsþjónustu", - "backup_controller_page_background_wifi": "Aðeins á Wi-Fi", - "backup_controller_page_backup": "Afrit", - "backup_controller_page_backup_selected": "Valið: ", - "backup_controller_page_backup_sub": "Afritaðar myndir og myndbönd", - "backup_controller_page_created": "Búið til á: {date}", - "backup_controller_page_desc_backup": "Kveikið á afritun í forgrunni til að afrita nýjar eignir sjálfkrafa þegar forritið er opnað.", - "backup_controller_page_excluded": "Sleppt: ", - "backup_controller_page_failed": "Brostið ({count})", - "backup_controller_page_filename": "Skráarheiti: {filename} [{size}]", - "backup_controller_page_id": "Auðkenni: {id}", - "backup_controller_page_info": "Upplýsingar um Afrit", - "backup_controller_page_none_selected": "Ekkert valið", - "backup_controller_page_remainder": "Afgangur", - "backup_controller_page_remainder_sub": "Myndir og myndbönd úr vali sem á eftir að afrita", - "backup_controller_page_server_storage": "Pláss á Þjóni", - "backup_controller_page_start_backup": "Hefja Afritun", - "backup_controller_page_status_off": "Sjálfvirk afritun í forgrunni er óvirk", - "backup_controller_page_status_on": "Sjálfvirk afritun í forgrunni er virk", - "backup_controller_page_storage_format": "{used} af {total} notað", - "backup_controller_page_to_backup": "Myndasöfn sem á að afrita", - "backup_controller_page_total_sub": "Allar einkvæmar myndir og myndbönd úr völdum myndasöfnum", - "backup_controller_page_turn_off": "Slökkva á afritun í forgrunni", - "backup_controller_page_turn_on": "Kveikja á afritun í forgrunni", - "backup_controller_page_uploading_file_info": "Hleð upp skráaupplýsingum", - "backup_err_only_album": "Get ekki fjarlægt eina myndasafnið", - "backup_error_sync_failed": "Samræming gekk ekki. Get ekki unnið afrit.", - "backup_info_card_assets": "eignir", - "backup_manual_cancelled": "Hætt við", - "backup_manual_in_progress": "Upphleðsla nú þegar í gangi. Reyndu aftur seinna", - "backup_manual_success": "Glæstur árangur", - "backup_manual_title": "Staða upphleðslu", - "backup_options": "Valmöguleikar Afritunar", - "backup_options_page_title": "Valmöguleikar afritunar", - "backup_setting_subtitle": "Sýsla með upphleðslu í for- og bakgrunni", - "backup_settings_subtitle": "Sýsla með upphleðslu", - "backup_upload_details_page_more_details": "Smelltu fyrir frekari upplýsingar", - "backward": "Afturábak", - "biometric_auth_enabled": "Auðkenning með lífkennum virk", - "biometric_locked_out": "Þú ert læst út úr auðkenningu með lífkennum", - "biometric_no_options": "Ekkert val á lífkennum í boði", - "biometric_not_available": "Auðkenning með lífkennum er ekki í boði á þessu tæki", - "birthdate_saved": "Fæðingardagur vistaður", - "birthdate_set_description": "Fæðingardagur er notaður til að reikna út aldur manneskju á myndinni.", - "blurred_background": "Skýjaður bakgrunnur", - "bugs_and_feature_requests": "Pöddur og Beiðnir um Nýja Eiginleika", - "build": "Smíð", - "build_image": "Byggja Mynd", - "bulk_delete_duplicates_confirmation": "Ertu viss um að þú viljir eyða í magni {count, plural, one {# margfaldri eign} other {# margfölduðum eignum}}? Það mun halda stærstu eigninni í hverjum hóp og eyða öllu öðru fyrir fullt og allt. Þetta er ekki hægt að taka til baka!", - "bulk_keep_duplicates_confirmation": "Ertu viss um að þú viljir halda {count, plural, one {# margfaldri eign} other {# margföldum eignum}}? Þetta leysir alla margfeldnihópa án þess að eyða neinu.", - "bulk_trash_duplicates_confirmation": "Ertu viss um að þú viljir eyða {count, plural, one {# margfaldri eign} other {# margföldum eignum}}? Stærsta skráin í hverjum hóp verður geymd og öðrum hent í ruslið.", - "buy": "Kaupa Immich", - "cache_settings_clear_cache_button": "Hreinsa skyndiminni", - "cache_settings_duplicated_assets_clear_button": "HREINSA", - "cache_settings_duplicated_assets_title": "Margfaldar Eignir ({count})", - "cache_settings_statistics_album": "Gaummyndir fyrir myndasöfn", - "cache_settings_statistics_full": "Myndir í fullum gæðum", - "cache_settings_statistics_shared": "Gaummyndir fyrir sameiginleg myndasöfn", - "cache_settings_statistics_thumbnail": "Gaummynd", - "cache_settings_statistics_title": "Skyndiminni í notkun", - "cache_settings_subtitle": "Sýsla með högun skyndiminnis í smáforriti Immich", - "cache_settings_tile_subtitle": "Sýsla með högun staðbundinnar geymslu", - "cache_settings_tile_title": "Staðbundin geymsla", - "cache_settings_title": "Stillingar Skyndiminnis", - "camera": "Myndavél", - "camera_brand": "Framleiðandi myndvélar", - "camera_model": "Gerð myndavélar", - "cancel": "Hætta við", - "cancel_search": "Hætta við leit", - "canceled": "Hætt við", - "canceling": "Hætti við", - "cannot_merge_people": "Get ekki sameinað fólk", - "cannot_undo_this_action": "Þú getur ekki tekið þetta til baka!", - "cannot_update_the_description": "Get ekki uppfært lýsingu", - "cast": "Kasta", - "cast_description": "Stilla tiltækilega staði köstunar", - "change_date": "Breyta dagsetningu", - "change_description": "Breyta lýsingu", - "change_display_order": "Breyta röðun", - "change_expiration_time": "Breyta fyrningardagsetningu", - "change_location": "Breyta staðsetningu", - "change_name": "Breyta nafni", - "change_name_successfully": "Breytti heiti", - "change_password": "Breyta Lykilorði", - "change_password_description": "Þetta er annað hvort fyrsta innskráningin þín, eða þá að beiðni hefur verið gerð til að breyta lykilorðinu þínu. Vinsamlega sláðu inn nýtt lykilorð hér að neðan.", - "change_password_form_confirm_password": "Staðfestu Lykilorð", - "change_password_form_description": "Hæ {name}\n\nÞetta er annað hvort fyrsta innskráningin þín, eða þá að beiðni hefur verið gerð til að breyta lykilorðinu þínu. Vinsamlega sláðu inn nýtt lykilorð hér að neðan.", - "change_password_form_log_out": "Skrá út á öllum öðrum tækjum", - "change_password_form_log_out_description": "Mælt er með því að skrá sig út af öllum öðrum tækjum", - "change_password_form_new_password": "Nýtt lykilorð", - "change_password_form_password_mismatch": "Lykilorð stemma ekki", - "change_password_form_reenter_new_password": "Sláðu lykilorðið inn aftur", - "change_pin_code": "Breyta PIN kóða", - "change_your_password": "Breyta lykilorði", - "changed_visibility_successfully": "Breytti sýn", - "charging": "Hleður", - "charging_requirement_mobile_backup": "Aftirun í bakgrunni krefst þess að tækið sé í hleðslu", - "check_corrupt_asset_backup": "Athuga með spillt afritunargögn", - "check_corrupt_asset_backup_button": "Framkvæma athugun", - "check_corrupt_asset_backup_description": "Vertu á Wi-Fi tengingu og með öll afrit klár þegar þetta er keyrt. Þetta gæti tekið nokkrar mínútur.", - "check_logs": "Athuga Atburðaskrá", - "checksum": "Prófsumma", - "choose_matching_people_to_merge": "Veldur samskonar fólk til að sameina", - "city": "Borg", - "clear": "Hreinsa", - "clear_all": "Hreinsa allt", - "clear_all_recent_searches": "Hreinsa allar nýlegar leitir", - "clear_file_cache": "Hreinsa Flýtiminni Skráa", - "clear_message": "Hreinsa skilboð", - "clear_value": "Hreinsa gildi", - "client_cert_dialog_msg_confirm": "Í lagi", - "client_cert_enter_password": "Sláðu inn lykilorð", - "client_cert_import": "Flytja inn", - "client_cert_import_success_msg": "Skírteini biðlara flutt inn", - "client_cert_invalid_msg": "Ógilt skírteini eða rangt lykilorð", - "client_cert_remove_msg": "Skírteini biðlara fjarlægt", - "client_cert_subtitle": "Styður einungis PKC12 (.p12, .ptx) snið. Einungis hægt að flytja inn/fjarlægja skírteini fyrir innskráningu", - "client_cert_title": "TLS skírteini biðlara [Á TILRAUNASTIGI]", - "clockwise": "Sólarhringur", - "close": "Loka", - "collapse": "Fella upp", - "collapse_all": "Fella allt upp", - "color": "Litur", - "color_theme": "Litaþema", - "command": "Skipun", - "comment_deleted": "Athugasemd eytt", - "comment_options": "Valmöguleikar athugasemda", - "comments_and_likes": "Athugasemdir og viðbrögð", - "comments_are_disabled": "Slökkt er á athugasemdum", - "common_create_new_album": "Búa til nýtt myndasafn", - "completed": "Lokið", - "confirm": "Staðfesta", - "confirm_admin_password": "Staðfesta Lykilorð Stjóra", - "confirm_delete_face": "Ertu viss um að þú viljir eyða andliti {name} úr eigninni?", - "confirm_delete_shared_link": "Ertu viss um að þú viljir eyða deildum hlekki?", - "confirm_keep_this_delete_others": "Öllum öðrum eignum í staflanum verður eytt nema þessari. Ertu viss um að þú viljir halda áfram?", - "confirm_new_pin_code": "Staðfestu nýja PIN kóðann", - "confirm_password": "Staðfestu lykilorð", - "confirm_tag_face": "Viltu merkja þetta andlit sem {name}?", - "confirm_tag_face_unnamed": "Viltu merkja þetta andlit?", - "connected_device": "Tengt tæki", - "connected_to": "Tengt við", - "contain": "Inniheldur", - "context": "Samhengi", - "continue": "Halda áfram", - "control_bottom_app_bar_create_new_album": "Búa til nýtt myndasafn", - "control_bottom_app_bar_delete_from_immich": "Eyða úr Immich", - "control_bottom_app_bar_delete_from_local": "Eyða úr tæki", - "control_bottom_app_bar_edit_location": "Breyta Staðsetningu", - "control_bottom_app_bar_edit_time": "Breyta Tíma og Dagsetningu", - "control_bottom_app_bar_share_link": "Deila Hlekk", - "control_bottom_app_bar_share_to": "Deila Til", - "control_bottom_app_bar_trash_from_immich": "Færa í Ruslið", - "copied_image_to_clipboard": "Afritaði mynd á klemmuspjald.", - "copied_to_clipboard": "Afritað á klemmuspjald!", - "copy_error": "Villa við að afrita", - "copy_file_path": "Afrita slóð", - "copy_image": "Afrita Mynd", - "copy_link": "Afrita hlekk", - "copy_link_to_clipboard": "Afrita hlekk á klemmuspjald", - "copy_password": "Afrita lykilorð", - "copy_to_clipboard": "Afrita á Klemmuspjald", - "country": "Land", - "cover": "Kápumynd", - "covers": "Kápumyndir", - "create": "Búa til", - "create_album": "Búa til myndasafn", - "create_album_page_untitled": "Ótitlað", - "create_api_key": "Búa til API lykil", - "create_library": "Búa til Myndasafn", - "create_link": "Búa til hlekk", - "create_link_to_share": "Búa til hlekk til að deila", - "create_link_to_share_description": "Leyfðu hverjum sem býr yfir hlekknum að sjá valdar myndir", - "create_new": "BÚA TIL NÝTT", - "create_new_person": "Stofna nýja manneskju", - "created_at": "Búið til", - "download_finished": "Niðurhali lokið", - "download_include_embedded_motion_videos": "Innfelld myndbönd", - "download_include_embedded_motion_videos_description": "Haga innfelldum myndböndum í ljósmyndum sem sjálfstæðum skrám", - "download_notfound": "Niðurhal finnst ekki", - "download_paused": "Niðurhal í pásu", - "download_settings": "Niðurhal", - "download_settings_description": "Sýsla með stillingar er varða niðurhal á eignum", - "download_started": "Niðurhal byrjað", - "download_sucess": "Niðurhal tókst", - "download_sucess_android": "Efninu hefur verið halað niður í DCIM/Immich", - "download_waiting_to_retry": "Bíð eftir annarri tilraun", - "downloading": "Hleð niður", - "downloading_asset_filename": "Hleð niður {filename}", - "downloading_media": "Hleð niður efni", - "drop_files_to_upload": "Slepptu skrá hvar sem er til að hlaða upp", - "duplicates": "Margfeldi", - "duplicates_description": "Leystu hvern hóp með því að tilgreina hvað er aukatekning, ef eitthvað", - "duration": "Tími", - "edit": "Breyta", - "edit_album": "Breyta myndasafni", - "edit_avatar": "Breyta teikn", - "edit_birthday": "Breyta afmæli", - "edit_date": "Breyta dagsetningu", - "edit_date_and_time": "Breyta tíma og dagsetningu", - "edit_date_and_time_action_prompt": "{count} tímasetningum breytt", - "edit_date_and_time_by_offset": "Breyta dagsetningu með hliðrun", - "edit_date_and_time_by_offset_interval": "Nýtt tímabil: {from} - {to}", - "edit_description": "Breyta lýsingu", - "edit_description_prompt": "Vinsamlega veldu nýja lýsingu:", - "edit_exclusion_pattern": "Breyta útilokunarmynstri", - "edit_faces": "Haga andlitum", - "edit_key": "Breyta lykli", - "edit_link": "Breyta hlekk", - "edit_location": "Breyta staðsetningu", - "edit_location_action_prompt": "{count} staðsetningum breytt", - "edit_location_dialog_title": "Staðsetning", - "edit_name": "Breyta nafni", - "edit_people": "Haga manneskjum", - "edit_tag": "Breyta merki", - "edit_title": "Breyta Titli", - "edit_user": "Breyta notanda", - "editor": "Myndvinnsla", - "editor_close_without_save_prompt": "Breytingarnar verða ekki vistaðar", - "editor_close_without_save_title": "Loka myndvinnslu?", - "email": "Netfang", - "email_notifications": "Meldingar í tölvupósti", - "empty_folder": "Þessi mappa er tóm", - "empty_trash": "Tæma rusl", - "empty_trash_confirmation": "Ertu viss um að þú viljir tæma ruslið? Öllum eignum í ruslinu verður eytt úr Immich fyrir fullt og allt.\nÞetta er ekki afturkræft!", - "enable": "Virkja", - "enable_backup": "Virkja Afritun", - "enable_biometric_auth_description": "Sláðu inn PIN kóðann þinn til að virkja auðkenningu með lífkennum", - "enabled": "Virkt", - "end_date": "Lokadagsetning", - "enqueued": "Sett í biðröð", - "enter_wifi_name": "Sláðu inn Wi-Fi heitið", - "enter_your_pin_code": "Sláðu inn PIN númerið þitt", - "enter_your_pin_code_subtitle": "Sláðu inn PIN númer til að fá aðgang að læstu möppunni", - "error": "Villa", - "error_change_sort_album": "Ekki gekk að breyta flokkunarröð myndasafna", - "error_delete_face": "Villa við að eyða andliti úr eign", - "error_getting_places": "Villa við að sækja staði", - "error_loading_image": "Villa við að hlaða mynd", - "error_loading_partners": "Villa við að hlaða félögum: {error}", - "error_saving_image": "Villa: {error}", - "error_tag_face_bounding_box": "Villa við að merkja andlit - get ekki sót hnit hneppiramma", - "error_title": "Villa - Eitthvað fór úrskeiðis", - "errors": { - "cannot_navigate_next_asset": "Get ekki stiklað á næstu eign", - "cannot_navigate_previous_asset": "Get ekki stiklað á fyrri eign", - "cant_apply_changes": "Get ekki beitt breytingum", - "cant_change_activity": "Get ekki {enabled, select, true {afvirkjað} other {virkjað}} virkni", - "cant_change_asset_favorite": "Get ekki breytt uppáhalds fyrir eign", - "cant_change_metadata_assets_count": "Get ekki breytt lýsigögnum {count, plural, one {# eignar} other {# eigna}}", - "cant_get_faces": "Get ekki sótt andlit", - "cant_get_number_of_comments": "Get ekki sótt fjölda athugasemda", - "cant_search_people": "Get ekki leitað að fólki", - "cant_search_places": "Get ekki leitað að stöðum", - "error_adding_assets_to_album": "Villa við að bæta eignum í safn", - "error_adding_users_to_album": "Villa við að bæta notendum í safn", - "error_deleting_shared_user": "Villa við að eyða deildum notanda", - "error_downloading": "Villa við að hlaða niður {filename}", - "error_hiding_buy_button": "Villa við að fela kauptakkann", - "error_removing_assets_from_album": "Villa við að fjarlægja eignir úr safni, athugið stjórnborð fyrir frekari upplýsingar", - "error_selecting_all_assets": "Villa við að velja allar eignir", - "exclusion_pattern_already_exists": "Útilokunarmynstur er nú þegar til.", - "failed_to_create_album": "Ekki gekk að búa til myndasafn", - "failed_to_create_shared_link": "Ekki gekk að búa til deilihlekk", - "failed_to_edit_shared_link": "Ekki gekk að breyta deilihlekk", - "failed_to_get_people": "Gat ekki sótt fólk", - "failed_to_keep_this_delete_others": "Ekki gekk að halda þessari eign og eyða öðrum", - "failed_to_load_asset": "Ekki gekk að hlaða eign", - "failed_to_load_assets": "Ekki gekk að hlaða eignum", - "failed_to_load_notifications": "Ekki gekk að hlaða meldingum", - "failed_to_load_people": "Ekki gekk að hlaða fólki", - "failed_to_remove_product_key": "Ekki gekk að fjarlægja vörulykil", - "failed_to_reset_pin_code": "Ekki gekk að endursetja PIN númer", - "failed_to_stack_assets": "Ekki gekk að stafla eignum", - "failed_to_unstack_assets": "Ekki gekk að af-stafla eignum", - "failed_to_update_notification_status": "Ekki gekk að uppfæra stöðu meldingar", - "incorrect_email_or_password": "Rangt netfang eða lykilorð", - "library_folder_already_exists": "Slóð innflutnings er nú þegar til.", - "paths_validation_failed": "Ekki gekk að sannreyna {paths, plural, one {# slóð} other {# slóðir}}", - "profile_picture_transparent_pixels": "Prófílmynd má ekki vera með gagnsæja pixla. Vinsamlega þysjaðu inn og/eða færðu myndina.", - "quota_higher_than_disk_size": "Þú stilltir stærð sem er stærri en stærð disksins", - "something_went_wrong": "Eitthvað fór úrskeiðis", - "unable_to_add_album_users": "Get ekki bætt notendum við safn", - "unable_to_add_assets_to_shared_link": "Get ekki bætt eignum við deilihlekk", - "unable_to_add_comment": "Get ekki bætt við athugasemd", - "unable_to_add_exclusion_pattern": "Get ekki bætt við útilokunarmynstri", - "unable_to_add_partners": "Get ekki bætt við félögum", - "unable_to_add_remove_archive": "Get ekki {archived, select, true {fjarlægt eignir úr} other {bætt eignum í}} geymslu", - "unable_to_add_remove_favorites": "Get ekki {favorite, select, true {bætt eignum í} other {fjarlægt eignir úr}} uppáhalds", - "unable_to_archive_unarchive": "Get ekki {archived, select, true {sett í geymslu} other {tekið úr geymslu}}", - "unable_to_change_album_user_role": "Get ekki breytt hlutverki notanda í safni", - "unable_to_change_date": "Get ekki breytt dagsetningu", - "unable_to_change_description": "Get ekki breytt lýsingu", - "unable_to_change_favorite": "Get ekki breytt hvort mynd sé uppáhalds", - "unable_to_change_location": "Get ekki breytt staðsetningu", - "unable_to_change_password": "Get ekki breytt lykilorði", - "unable_to_change_visibility": "Get ekki breytt sýn fyrir {count, plural, one {# fólk} other {# fólk}}", - "unable_to_complete_oauth_login": "Get ekki lokið OAuth innskráningu", - "unable_to_connect": "Get ekki tengst", - "unable_to_copy_to_clipboard": "Get ekki afritað á klemmuspjald, gakktu úr skugga um að þú tengist þessari síðu með TLS", - "unable_to_create_admin_account": "Get ekki búð til stjórnunaraðgang", - "unable_to_create_api_key": "Get ekki búð til nýjan API lykil", - "unable_to_create_library": "Get ekki búið til myndasafn", - "unable_to_create_user": "Get ekki búið til notanda", - "unable_to_delete_album": "Get ekki eytt myndasafni", - "unable_to_delete_asset": "Get ekki eytt eign", - "unable_to_delete_assets": "Villa við að eyða eignum", - "unable_to_delete_exclusion_pattern": "Get ekki eytt útilokunarmynstri", - "unable_to_delete_shared_link": "Get ekki eytt deilihlekk", - "unable_to_delete_user": "Get ekki eytt notanda", - "unable_to_download_files": "Get ekki hlaðið niður skrám", - "unable_to_edit_exclusion_pattern": "Get ekki breytt útilokunarmynstri", - "unable_to_empty_trash": "Get ekki tæmt rusl", - "unable_to_enter_fullscreen": "Get ekki fyllt skjáinn", - "unable_to_exit_fullscreen": "Get ekki minnkað skjáinn", - "unable_to_get_comments_number": "Get ekki sótt fjölda athugasemda", - "unable_to_get_shared_link": "Get ekki sótt deilihlekk", - "unable_to_hide_person": "Get ekki falið manneskju", - "unable_to_link_motion_video": "Get ekki hlekkjað myndband", - "unable_to_link_oauth_account": "Get ekki hlekkjað OAuth aðgang", - "unable_to_log_out_all_devices": "Get ekki skráð út af öllum tækjum", - "unable_to_log_out_device": "Get ekki skráð út af tæki", - "unable_to_login_with_oauth": "Get ekki skráð inn með OAuth", - "unable_to_play_video": "Get ekki spilað myndband", - "unable_to_reassign_assets_existing_person": "Get ekki endurúthlutað eign á {name, select, null {fyrirliggjandi manneskju} other {{name}}}", - "unable_to_reassign_assets_new_person": "Get ekki endurúthlutað eign á nýja manneskju", - "unable_to_refresh_user": "Get ekki frískað upp á notanda", - "unable_to_remove_album_users": "Get ekki fjarlægt notendur úr myndasafni", - "unable_to_remove_api_key": "Get ekki fjarlægt API lykil", - "unable_to_remove_assets_from_shared_link": "Get ekki fjarlægt eignir úr deilihlekk", - "unable_to_remove_library": "Get ekki fjarlægt myndasafn", - "unable_to_remove_partner": "Get ekki fjarlægt félaga", - "unable_to_remove_reaction": "Get ekki fjarlægt viðbragð", - "unable_to_reset_password": "Get ekki endurstillt lykilorð", - "unable_to_reset_pin_code": "Get ekki endurstillt PIN númer", - "unable_to_resolve_duplicate": "Get ekki fundið út úr margfeldi", - "unable_to_restore_assets": "Get ekki endurheimt eignir", - "unable_to_restore_trash": "Get ekki endurheimt rusl", - "unable_to_restore_user": "Get ekki endurheimt notanda", - "unable_to_save_album": "Get ekki vistað myndasafn", - "unable_to_save_api_key": "Get ekki vistað API lykil", - "unable_to_save_date_of_birth": "Get ekki vistað afmælisdag", - "unable_to_save_name": "Get ekki vistað nafn", - "unable_to_save_profile": "Get ekki vistað prófíl", - "unable_to_save_settings": "Get ekki vistað stillingar", - "unable_to_scan_libraries": "Get ekki skannað söfn", - "unable_to_scan_library": "Get ekki skannað safn", - "unable_to_set_feature_photo": "Get ekki sett birtingarmynd", - "unable_to_set_profile_picture": "Get ekki stillt prófílmynd", - "unable_to_submit_job": "Get ekki sett inn beiðni", - "unable_to_trash_asset": "Get ekki hent eign í ruslið", - "unable_to_unlink_account": "Get ekki afhlekkjað aðgang", - "unable_to_unlink_motion_video": "Get ekki afhlekkjað myndband", - "unable_to_update_album_cover": "Get ekki uppfært kápumynd safns", - "unable_to_update_album_info": "Get ekki uppfært upplýsingar um safn", - "unable_to_update_library": "Get ekki uppfært safn", - "unable_to_update_location": "Get ekki uppfært staðsetningu", - "unable_to_update_settings": "Get ekki uppfært stillingar", - "unable_to_update_timeline_display_status": "Get ekki uppfært skoðunarstillingar tímalínu", - "unable_to_update_user": "Get ekki uppfært notanda", - "unable_to_upload_file": "Get ekki hlaðið upp skrá" - }, - "exclusion_pattern": "Útilokunarmynstur", - "exif": "Exif", - "exif_bottom_sheet_description": "Bæta við lýsingu...", - "exif_bottom_sheet_description_error": "Villa við að uppfæra lýsingu", - "exif_bottom_sheet_details": "SMÁATRIÐI", - "exif_bottom_sheet_location": "STAÐSETNING", - "exif_bottom_sheet_no_description": "Engin lýsing", - "exif_bottom_sheet_people": "FÓLK", - "exif_bottom_sheet_person_add_person": "Bæta við nafni", - "exit_slideshow": "Hætta glærusýningu", - "expand_all": "Fella allt niður", - "experimental_settings_new_asset_list_subtitle": "Í vinnslu", - "experimental_settings_new_asset_list_title": "Virkja myndanet á tilraunastigi", - "experimental_settings_subtitle": "Notkun á eigin áhættu!", - "experimental_settings_title": "Á tilraunastigi", - "expire_after": "Fyrnist eftir", - "expired": "Útrunnið", - "expires_date": "Fyrnist {date}", - "explore": "Skoða", - "explorer": "Vafri", - "export": "Flytja út", - "export_as_json": "Flytja út sem JSON", - "export_database": "Flytja gagnagrunn út", - "export_database_description": "Flytja út SQLite gagnagrunninn", - "extension": "Nafnauki", - "external": "Utanáliggjandi", - "external_libraries": "Utanáliggjandi Söfn", - "external_network": "Ytra net", - "external_network_sheet_info": "Þegar við erum ekki á Wi-Fi netinu sem er í forgangi, þá mun smáforritið tengjast þjónunum í gegnum fyrstu vefslóðina sem svarar í listanum hér að neðan", - "face_unassigned": "Óúthlutað", - "failed": "Mistókst", - "failed_count": "Mistókst: {count}", - "failed_to_authenticate": "Auðkenning mistókst", - "failed_to_load_assets": "Mistókst að hlaða eignum", - "failed_to_load_folder": "Mistókst að hlaða möppu", - "favorite": "Uppáhalds", - "favorite_action_prompt": "{count} bætt í Uppáhalds", - "favorite_or_unfavorite_photo": "Taka úr eða setja í uppáhalds", - "favorites": "Uppáhalds", - "favorites_page_no_favorites": "Engar uppáhalds eignir fundust", - "feature_photo_updated": "Birtingarmynd uppfærð", - "features": "Eiginleikar", - "features_in_development": "Eiginleikar í þróun", - "features_setting_description": "Sýsla með eiginleika smáforrits", - "file_name_or_extension": "Skráarheiti eða nafnauki", - "file_size": "Skráarstærð", - "filename": "Skráarheiti", - "filetype": "Skráargerð", - "filter": "Sía", - "filter_people": "Sía fólk", - "filter_places": "Sía staði", - "find_them_fast": "Finndu þau í snatri með leit að nafni", - "first": "Fyrst", - "fix_incorrect_match": "Laga ranga pörun", - "folder": "Mappa", - "folder_not_found": "Mappa finnst ekki", - "folders": "Möppur", - "folders_feature_description": "Vafra í möppum fyrir myndir og myndbönd á skráakerfinu", - "forgot_pin_code_question": "Gleymt PIN númer?", - "forward": "Áfram", - "full_path": "Full slóð: {path}", - "gcast_enabled": "Google Kast", - "gcast_enabled_description": "Þessi eiginleiki hleður inn efni frá Google til að virka.", - "general": "Almennt", - "geolocation_instruction_location": "Smelltu á eign með GPS hnitum til að nota þau, eða veldu staðsetningu á korti", - "get_help": "Fá Hjálp", - "get_wifiname_error": "Gat ekki sótt Wi-Fi heiti. Gakktu úr skugga um að rétt leyfi séu veitt og Wi-Fi tenging sé virk", - "getting_started": "Byrjunarskref", - "go_back": "Til baka", - "go_to_folder": "Fara í möppu", - "go_to_search": "Fara í leit", - "gps": "GPS", - "gps_missing": "Ekkert GPS", - "grant_permission": "Veita leyfi", - "group_albums_by": "Hópa söfn eftir...", - "group_country": "Hópa eftir löndum", - "group_no": "Engin hópun", - "group_owner": "Hópa eftir eigendum", - "group_places_by": "Hópa staði eftir...", - "group_year": "Hópa eftir ári", - "haptic_feedback_switch": "Virkja snertisvörun", - "haptic_feedback_title": "Snertisvörun", - "has_quota": "Er með úthutaða notkun", - "hash_asset": "Hakka eign", - "hashed_assets": "Hakkaðar eignir", - "hashing": "Hökkun", - "header_settings_add_header_tip": "Bæta við haus", - "header_settings_field_validator_msg": "Gildi má ekki vera tómt", - "header_settings_header_name_input": "Heiti hauss", - "header_settings_header_value_input": "Gildi hauss", - "headers_settings_tile_title": "Sérsniðnir vefselshausar", - "height": "Hæð", - "hi_user": "Hæ {name} ({email})", - "hide_all_people": "Fela allt fólk", - "hide_gallery": "Fela safn", - "hide_named_person": "Fela {name}", - "hide_password": "Fela lykilorð", - "hide_person": "Fela manneskju", - "hide_text_recognition": "Fela textagreiningu", - "hide_unnamed_people": "Fela ónefnt fólk", - "home_page_add_to_album_conflicts": "Bætti {added} eignum við safnið {album}. {failed} eignir eru nú þegar í safninu.", - "home_page_add_to_album_err_local": "Get ekki bætt staðværum eignum í safn, sleppi", - "home_page_add_to_album_success": "Bætti {added} eignum í safnið {album}.", - "home_page_album_err_partner": "Get ekki bætt eignum félaga í safn, sleppi", - "home_page_archive_err_local": "Get ekki sett staðværar eignir í geymslu, sleppi", - "home_page_archive_err_partner": "Get ekki sett eignir félaga í geymslu, sleppi", - "home_page_building_timeline": "Smíða tímalínu", - "home_page_delete_err_partner": "Get ekki eytt eignum félaga, sleppi", - "home_page_delete_remote_err_local": "Staðværar eignir í vali eigna í fjarska, sleppi", - "home_page_favorite_err_local": "Get ekki sett staðværar eignir í uppáhalds, sleppi", - "home_page_favorite_err_partner": "Get ekki sett eignir félaga í uppáhalds, sleppi", - "home_page_first_time_notice": "Ef þú ert að nota þetta smáforrit í fyrsta sinn, gakktu úr skugga um að þú veljir myndasafn til að afrita svo að tímalínan fyllist af myndum og myndböndum", - "home_page_locked_error_local": "Get ekki fært staðværar eignir í læsta möppu, sleppi", - "home_page_locked_error_partner": "Get ekki fært eignir félaga í læsta möppu, sleppi", - "home_page_share_err_local": "Get ekki deilt staðværum eignum með hlekk, sleppi", - "home_page_upload_err_limit": "Get bara hlaðið upp 30 myndum að hámarki í einu, sleppi", - "host": "Hýsill", - "hour": "Klukkustund", - "hours": "Klukkustundir", - "id": "Auðkenni", - "idle": "Hvílandi", - "ignore_icloud_photos": "Hundsa myndir í iCloud", - "ignore_icloud_photos_description": "Myndum sem eru geymdar í iCloud verður ekki hlaðið upp á Immich þjóninn", - "image": "Mynd", - "image_alt_text_date": "{isVideo, select, true {Myndband} other {Mynd}} tekin á {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Myndband} other {Mynd}} með {person1} á {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Myndband} other {Mynd}} með {person1} og {person2} á {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Myndband} other {Mynd}} með {person1}, {person2}, og {person3} á {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Myndband} other {Mynd}} með {person1}, {person2}, og {additionalCount, number} öðrum á {date}", - "image_alt_text_date_place": "{isVideo, select, true {Myndband} other {Mynd}} í {city}, {country} á {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Myndband} other {Mynd}} í {city}, {country} með {person1} á {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Myndband} other {Mynd}} í {city}, {country} með {person1} og {person2} á {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Myndband} other {Mynd}} í {city}, {country} með {person1}, {person2}, og {person3} á {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} í {city}, {country} með {person1}, {person2}, og {additionalCount, number} öðrum á {date}", - "image_saved_successfully": "Mynd vistuð", - "image_viewer_page_state_provider_download_started": "Niðurhal hafið", - "image_viewer_page_state_provider_download_success": "Niðurhal tókst", - "image_viewer_page_state_provider_share_error": "Villa við deilingu", - "immich_logo": "Immich merkið", - "immich_web_interface": "Vefviðmót Immich", - "import_from_json": "Flytja inn úr JSON", - "import_path": "Slóð innflutnings", - "in_albums": "Í {count, plural, one {# safni} other {# söfnum}}", - "in_archive": "Í geymslu", - "in_year": "Á {year}", - "in_year_selector": "Í", - "include_archived": "Taka með úr geymslu", - "include_shared_albums": "Taka með úr deildum söfnum", - "include_shared_partner_assets": "Taka með eignir félaga", - "individual_share": "Einstök deild", - "individual_shares": "Einstakar deildir", - "info": "Upplýsingar", - "interval": { - "day_at_onepm": "Alla daga klukkan 13" - } -} +{} diff --git a/i18n/it.json b/i18n/it.json index 50d0632f3d..0967ef424b 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -1,2401 +1 @@ -{ - "about": "Informazioni", - "account": "Profilo", - "account_settings": "Impostazioni Profilo", - "acknowledge": "Ho capito", - "action": "Azione", - "action_common_update": "Aggiorna", - "action_description": "Un insieme di azioni da eseguire sulle risorse filtrate", - "actions": "Azioni", - "active": "Attivo", - "active_count": "Attivi: {count}", - "activity": "Attività", - "activity_changed": "L'attività è {enabled, select, true {abilitata} other {disabilitata}}", - "add": "Aggiungi", - "add_a_description": "Aggiungi una descrizione", - "add_a_location": "Aggiungi una posizione", - "add_a_name": "Aggiungi un nome", - "add_a_title": "Aggiungi un titolo", - "add_action": "Aggiungi azione", - "add_action_description": "Fare clic per aggiungere un'azione da eseguire", - "add_assets": "Aggiungi risorse", - "add_birthday": "Aggiungi compleanno", - "add_endpoint": "Aggiungi un endpoint", - "add_exclusion_pattern": "Aggiungi un pattern di esclusione", - "add_filter": "Aggiungi filtro", - "add_filter_description": "Fare clic per aggiungere una condizione di filtro", - "add_location": "Aggiungi posizione", - "add_more_users": "Aggiungi altri utenti", - "add_partner": "Aggiungi partner", - "add_path": "Aggiungi un percorso", - "add_photos": "Aggiungi foto", - "add_tag": "Aggiungi tag", - "add_to": "Aggiungi a…", - "add_to_album": "Aggiungi all'album", - "add_to_album_bottom_sheet_added": "Aggiunto in {album}", - "add_to_album_bottom_sheet_already_exists": "Già presente in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Alcune risorse locali non possono essere aggiunte all'album", - "add_to_album_toggle": "Attiva/disattiva selezione per {album}", - "add_to_albums": "Aggiungi ad album", - "add_to_albums_count": "Aggiungi ad album ({count})", - "add_to_bottom_bar": "Aggiungi a", - "add_to_shared_album": "Aggiungi ad album condiviso", - "add_upload_to_stack": "Aggiungi caricamento allo stack", - "add_url": "Aggiungi URL", - "add_workflow_step": "Aggiungi passaggio del flusso di lavoro", - "added_to_archive": "Aggiunto all'archivio", - "added_to_favorites": "Aggiunto ai preferiti", - "added_to_favorites_count": "Aggiunto {count, number} ai preferiti", - "admin": { - "add_exclusion_pattern_description": "Aggiungi modelli di esclusione. È supportato il globbing utilizzando *, ** e ?. Per ignorare tutti i file in qualsiasi directory denominata \"Raw\", usa \"**/Raw/**\". Per ignorare tutti i file con estensione \".tif\", usa \"**/*.tif\". Per ignorare un percorso assoluto, usa \"/percorso/da/ignorare/**\".", - "admin_user": "Utente amministratore", - "asset_offline_description": "Questa risorsa della libreria esterna non si trova più sul disco ed è stata spostata nel cestino. Se il file è stato spostato all'interno della libreria, controlla la timeline per la nuova risorsa corrispondente. Per ripristinare questa risorsa, assicurati che Immich possa accedere al percorso del file ed esegui la scansione della libreria.", - "authentication_settings": "Impostazioni di Autenticazione", - "authentication_settings_description": "Gestisci password, OAuth e altre impostazioni di autenticazione", - "authentication_settings_disable_all": "Sei sicuro di voler disabilitare tutte le modalità di accesso? Il login verrà disabilitato completamente.", - "authentication_settings_reenable": "Per ri-abilitare, utilizza un Comando Server.", - "background_task_job": "Attività in Background", - "backup_database": "Crea Dump Database", - "backup_database_enable_description": "Abilita i backup del database", - "backup_keep_last_amount": "Numero di backup da mantenere", - "backup_onboarding_1_description": "copia offsite nel cloud o in un'altra sede fisica.", - "backup_onboarding_2_description": "copie locali su diversi dispositivi. Ciò include i file principali e un backup di tali file a livello locale.", - "backup_onboarding_3_description": "copie totali dei tuoi dati, compresi i file originali. Ciò include 1 copia offsite e 2 copie locali.", - "backup_onboarding_description": "Per proteggere i tuoi dati, è consigliato adottare una strategia di backup 3-2-1. Per una soluzione di backup completa, è consigliato conservare copie delle foto/video caricati e del database Immich.", - "backup_onboarding_footer": "Per ulteriori informazioni sul backup di Immich, consulta la documentazione.", - "backup_onboarding_parts_title": "Un backup 3-2-1 include:", - "backup_onboarding_title": "Backup", - "backup_settings": "Impostazioni Dump database", - "backup_settings_description": "Gestisci le impostazioni dei backup.", - "cleared_jobs": "Cancellati i processi per: {job}", - "config_set_by_file": "La configurazione è attualmente impostata da un file di configurazione", - "confirm_delete_library": "Sei sicuro di voler cancellare la libreria {library}?", - "confirm_delete_library_assets": "Sei sicuro di voler cancellare questa libreria? Ciò rimuoverà {count, plural, one {# risorsa} other {tutte le # risorse}} da Immich senza possibilità di tornare indietro. I file rimarranno comunque sul disco.", - "confirm_email_below": "Per confermare, scrivi \"{email}\" qui sotto", - "confirm_reprocess_all_faces": "Sei sicuro di voler riprocessare tutti i volti? Questo cancellerà anche tutte le persone associate.", - "confirm_user_password_reset": "Sei sicuro di voler resettare la password di {user}?", - "confirm_user_pin_code_reset": "Sicuro di voler resettare il codice PIN di {user}?", - "copy_config_to_clipboard_description": "Copia la configurazione attuale del sistema come oggetto JSON negli appunti", - "create_job": "Crea Processo", - "cron_expression": "Espressione Cron", - "cron_expression_description": "Imposta il tempo di scansione utilizzando il formato Cron. Per ulteriori informazioni fare riferimento a Crontab Guru", - "cron_expression_presets": "Espressione Cron preimpostata", - "disable_login": "Disabilita login", - "duplicate_detection_job_description": "Esegui il machine learning sulle risorse per rilevare immagini simili. Basato su Ricerca Intelligente", - "exclusion_pattern_description": "I modelli di esclusione ti permettono di ignorare file e cartelle durante la scansione della tua libreria. Questo è utile se hai cartelle che contengono file che non vuoi importare, come ad esempio, i file RAW.", - "export_config_as_json_description": "Scarica la configurazione attuale del sistema come file JSON", - "external_libraries_page_description": "Pagina librerie esterne (admin)", - "face_detection": "Rilevamento Volti", - "face_detection_description": "Rileva i volti presenti nelle risorse utilizzando il machine-learning. Per i video, viene presa in considerazione solo la miniatura. Utilizzare \"Ripristina\" per cancellare tutti i volti presenti, \"Ricarica\" per processare di nuovo tutti le risorse, \"Mancanti\" processa solo le risorse che non sono ancora stati processati. I volti rilevati verranno selezionati per il riconoscimento facciale dopo che il rilevamento dei volti sarà stato completato, raggruppandoli in persone esistenti e/o nuove.", - "facial_recognition_job_description": "Raggruppa i volti rilevati in persone. Questo processo viene eseguito dopo che il rilevamento volti è stato completato. \"Reset\" (ri-)unisce tutti i volti. \"Mancanti\" processa i volti che non hanno una persona assegnata.", - "failed_job_command": "Il comando {command} è fallito per il processo: {job}", - "force_delete_user_warning": "ATTENZIONE: Questo rimuoverà immediatamente l'utente e tutti le sue risorse. Non è possibile tornare indietro e i file non potranno essere recuperati.", - "image_format": "Formato", - "image_format_description": "WebP produce file più piccoli rispetto a JPEG, ma è più lento da codificare.", - "image_fullsize_description": "Immagini a dimensioni reali senza metadati, sono utilizzate durante lo zoom", - "image_fullsize_enabled": "Abilita la generazione delle immagini a dimensioni reali", - "image_fullsize_enabled_description": "Genera immagini a dimensioni reali per i formati non web-friendly. Quando è abilitata l'opzione \"Preferisci l'anteprima integrata\", le anteprime integrate saranno utilizzate direttamente senza conversione. Non influisce sui formati web-friendly come JPEG.", - "image_fullsize_quality_description": "Qualità delle immagini a dimensioni reali da 1 a 100. Un valore più alto è migliore ma produce file più grandi.", - "image_fullsize_title": "Impostazioni delle immagini a dimensioni reali", - "image_prefer_embedded_preview": "Preferisci l'anteprima integrata", - "image_prefer_embedded_preview_setting_description": "Usa l'anteprima integrata nelle foto RAW come input per l'elaborazione delle immagini, se disponibile. Questo permette un miglioramento dei colori per alcune immagini, ma la qualità delle anteprime dipende dalla macchina fotografica. Inoltre le immagini potrebbero presentare artefatti di compressione.", - "image_prefer_wide_gamut": "Preferisci gamut più ampio", - "image_prefer_wide_gamut_setting_description": "Usa lo spazio colore Display P3 per le anteprime. Questo aiuta a mantenere la vivacità delle immagini con spazi colore più ampi, tuttavia potrebbe non mostrare correttamente le immagini con dispositivi e browser obsoleti. Le immagini sRGB vengono preservate per evitare alterazioni del colore.", - "image_preview_description": "Immagine a media dimensione senza metadati, utilizzata durante la visualizzazione di una singola risorsa e per il machine learning", - "image_preview_quality_description": "Qualità dell'anteprima da 1 a 100. Più alto è meglio ma produce file più pesanti e può ridurre la reattività dell'app. Impostare un valore basso può influenzare negativamente la qualità del machine learning.", - "image_preview_title": "Impostazioni dell'anteprima", - "image_progressive": "Progressiva", - "image_progressive_description": "Codifica progressivamente le immagini JPEG per mostrarle con un caricamento graduale. Questo non ha effetto sulle immagini WebP.", - "image_quality": "Qualità", - "image_resolution": "Risoluzione", - "image_resolution_description": "Risoluzioni più elevate possono preservare più dettagli ma richiedere più tempo per la codifica, avere dimensioni di file più grandi e ridurre la reattività dell'app.", - "image_settings": "Impostazioni delle immagini", - "image_settings_description": "Gestisci qualità e risoluzione delle immagini generate", - "image_thumbnail_description": "Miniatura piccola senza metadati, utilizzata durante la visualizzazione di gruppi di foto come nella galleria principale", - "image_thumbnail_quality_description": "Qualità delle miniature da 1 a 100. Un valore più alto è migliore ma produce file più grandi e può ridurre la reattività dell'app.", - "image_thumbnail_title": "Impostazioni delle miniature", - "import_config_from_json_description": "Importa la configurazione del sistema caricando un file JSON", - "job_concurrency": "Concorrenza {job}", - "job_created": "Processo creato", - "job_not_concurrency_safe": "Questo processo non è eseguibile in maniera concorrente.", - "job_settings": "Impostazioni dei processi", - "job_settings_description": "Gestisci la concorrenza dei processi", - "jobs_delayed": "{jobCount, plural, one {# posticipato} other {# posticipati}}", - "jobs_failed": "{jobCount, plural, one {# fallito} other {# falliti}}", - "jobs_over_time": "Processi nel tempo", - "library_created": "Creata libreria: {library}", - "library_deleted": "Libreria eliminata", - "library_details": "Dettagli libreria", - "library_folder_description": "Specifica una cartella da importare. Questa cartella, incluse le sottocartelle, verrà analizzata alla ricerca di immagini e video.", - "library_remove_exclusion_pattern_prompt": "Vuoi davvero rimuovere questo pattern di esclusione?", - "library_remove_folder_prompt": "Vuoi davvero rimuovere questa cartella di importazione?", - "library_scanning": "Scansione periodica", - "library_scanning_description": "Configura la scansione periodica della libreria", - "library_scanning_enable_description": "Attiva la scansione periodica della libreria", - "library_settings": "Libreria Esterna", - "library_settings_description": "Gestisci le impostazioni della libreria esterna", - "library_tasks_description": "Scansiona le librerie esterne per risorse nuove o modificate", - "library_updated": "Libreria aggiornata", - "library_watching_enable_description": "Osserva le librerie esterne per cambiamenti", - "library_watching_settings": "Osserva librerie [SPERIMENTALE]", - "library_watching_settings_description": "Osserva automaticamente i cambiamenti dei file", - "logging_enable_description": "Attiva il logging", - "logging_level_description": "Quando attivato, che livello di log utilizzare.", - "logging_settings": "Registro dei Log", - "machine_learning_availability_checks": "Verifiche di disponibilità", - "machine_learning_availability_checks_description": "Rileva automaticamente e usa i server di machine learning disponibili", - "machine_learning_availability_checks_enabled": "Attiva verifiche di disponibilità", - "machine_learning_availability_checks_interval": "Intervallo di verifica", - "machine_learning_availability_checks_interval_description": "Intervallo (ms) tra le verifiche di disponibilità", - "machine_learning_availability_checks_timeout": "Timeout richiesta", - "machine_learning_availability_checks_timeout_description": "Timeout (ms) per le verifiche di disponibilità", - "machine_learning_clip_model": "Modello CLIP", - "machine_learning_clip_model_description": "Il nome del modello CLIP mostrato qui. Nota che devi rieseguire il processo 'Ricerca Intelligente' per tutte le immagini al cambio del modello.", - "machine_learning_duplicate_detection": "Rilevamento Duplicati", - "machine_learning_duplicate_detection_enabled": "Attiva rilevazione duplicati", - "machine_learning_duplicate_detection_enabled_description": "Se disattivo, risorse perfettamente identiche saranno comunque deduplicate.", - "machine_learning_duplicate_detection_setting_description": "Utilizza i CLIP embeddings per trovare possibili duplicati", - "machine_learning_enabled": "Attiva machine learning", - "machine_learning_enabled_description": "Se disabilitato, tutte le funzioni di ML saranno disabilitate ignorando le impostazioni sottostanti.", - "machine_learning_facial_recognition": "Riconoscimento Facciale", - "machine_learning_facial_recognition_description": "Rileva, riconosci e raggruppa volti nelle immagini", - "machine_learning_facial_recognition_model": "Modello di riconoscimento facciale", - "machine_learning_facial_recognition_model_description": "I modelli sono mostrati in ordine decrescente in base alla dimensione. I modelli più grandi sono più lenti e utilizzano più memoria, però producono risultati migliori. Nota che devi ri-eseguire il processo di rilevamento facciale per tutte le immagini quando cambi il modello.", - "machine_learning_facial_recognition_setting": "Attiva riconoscimento facciale", - "machine_learning_facial_recognition_setting_description": "Se disabilitato, le immagini non saranno codificate per il riconoscimento facciale e non verranno mostrate nella sezione Persone della pagina Esplora.", - "machine_learning_max_detection_distance": "Distanza massima di rilevazione", - "machine_learning_max_detection_distance_description": "Massima distanza fra due immagini per considerarle duplicate, variando da 0.001-0.1. Valori più alti rileveranno più duplicati, ma potrebbero causare falsi positivi.", - "machine_learning_max_recognition_distance": "Distanza massima di riconoscimento", - "machine_learning_max_recognition_distance_description": "La distanza massima tra due volti per essere considerati la stessa persona, che varia da 0 a 2. Abbassare questo valore può prevenire l'etichettatura di due persone come se fossero la stessa persona, mentre aumentarlo può prevenire l'etichettatura della stessa persona come se fossero due persone diverse. Nota che è più facile unire due persone che separare una persona in due, quindi è preferibile mantenere una soglia più bassa quando possibile.", - "machine_learning_min_detection_score": "Punteggio minimo di rilevazione", - "machine_learning_min_detection_score_description": "Punteggio di confidenza minimo per rilevare un volto, da 0 a 1. Valori più bassi rileveranno più volti, ma potrebbero generare risultati fasulli.", - "machine_learning_min_recognized_faces": "Minimo numero di volti rilevati", - "machine_learning_min_recognized_faces_description": "Il numero minimo di volti riconosciuti per creare una persona. Aumentando questo valore si rende il riconoscimento facciale più preciso, ma aumenta la possibilità che un volto non venga assegnato a una persona.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Utilizza il machine learning per riconoscere il testo nelle immagini", - "machine_learning_ocr_enabled": "Attiva OCR", - "machine_learning_ocr_enabled_description": "Se disattivato, le immagini non saranno sottoposte al riconoscimento del testo.", - "machine_learning_ocr_max_resolution": "Massima risoluzione", - "machine_learning_ocr_max_resolution_description": "L'anteprima maggiore di questa risoluzione verrà ridimensionata preservando le proporzioni. Valori maggiori sono più accurati, ma impiegano più tempo per essere processati e usano più memoria.", - "machine_learning_ocr_min_detection_score": "Punteggio minimo di rilevamento", - "machine_learning_ocr_min_detection_score_description": "Punteggio minimo di affidabilità per il rilevamento del testo da 0 a 1. Valori più bassi rileveranno più testo, ma potrebbero generare falsi positivi.", - "machine_learning_ocr_min_recognition_score": "Punteggio minimo di riconoscimento", - "machine_learning_ocr_min_score_recognition_description": "Punteggio minimo di affidabilità per il riconoscimento del testo da 0 a 1. Valori più bassi rileveranno più testo, ma potrebbero generare falsi positivi.", - "machine_learning_ocr_model": "Modello OCR", - "machine_learning_ocr_model_description": "I modelli server sono più accurati dei modelli mobile, ma impiegano più tempo nel processo e utilizzano più memoria.", - "machine_learning_settings": "Impostazioni Machine Learning", - "machine_learning_settings_description": "Gestisci le impostazioni e le funzionalità del machine learning", - "machine_learning_smart_search": "Ricerca Intelligente", - "machine_learning_smart_search_description": "Cerca immagini semanticamente utilizzato gli embedding CLIP", - "machine_learning_smart_search_enabled": "Attiva ricerca intelligente", - "machine_learning_smart_search_enabled_description": "Se disabilitato le immagini non saranno codificate per la ricerca intelligente.", - "machine_learning_url_description": "URL del server machine learning. Se sono stati forniti più di un URL, verrà testato un server alla volta finché uno non risponderà, in ordine dal primo all'ultimo. I server che non rispondono saranno temporaneamente ignorati finché non torneranno online.", - "maintenance_delete_backup": "Elimina Backup", - "maintenance_delete_backup_description": "Questo file verrà eliminato irreversibilmente.", - "maintenance_delete_error": "Eliminazione del backup fallita.", - "maintenance_restore_backup": "Ripristina Backup", - "maintenance_restore_backup_description": "Immich verrà cancellato e ripristinato dal backup scelto. Prima di procedere, verrà creato un backup.", - "maintenance_restore_backup_different_version": "Questo backup è stato creato con un'altra versione di Immich!", - "maintenance_restore_backup_unknown_version": "Impossibile determinare la versione del backup.", - "maintenance_restore_database_backup": "Ripristina il backup del database", - "maintenance_restore_database_backup_description": "Torna a uno stato precedente del database usando un file di backup", - "maintenance_settings": "Manutenzione", - "maintenance_settings_description": "Metti Immich in modalità manutenzione.", - "maintenance_start": "Passa a modalità manutenzione", - "maintenance_start_error": "Errore nell'avvio della modalità manutenzione.", - "maintenance_upload_backup": "Carica file di backup del database", - "maintenance_upload_backup_error": "Impossibile caricare il backup, è un file .sql/.sql.gz?", - "manage_concurrency": "Gestisci Concorrenza", - "manage_concurrency_description": "Vai alla pagina dei processi per gestire la concorrenza dei job", - "manage_log_settings": "Gestisci le impostazioni dei log", - "map_dark_style": "Tema scuro", - "map_enable_description": "Abilita funzionalità della mappa", - "map_gps_settings": "Impostazioni Mappe & GPS", - "map_gps_settings_description": "Gestisci le impostazioni di Mappe & GPS (Geocoding Inverso)", - "map_implications": "La funzionalità mappa si basa su un servizio tile esterno (tiles.immich.cloud)", - "map_light_style": "Tema chiaro", - "map_manage_reverse_geocoding_settings": "Gestisci impostazioni Geocodifica inversa", - "map_reverse_geocoding": "Geocodifica inversa", - "map_reverse_geocoding_enable_description": "Abilita geocodifica inversa", - "map_reverse_geocoding_settings": "Impostazioni Geocodifica Inversa", - "map_settings": "Mappa", - "map_settings_description": "Gestisci impostazioni mappa", - "map_style_description": "URL per un tema della mappa style.json", - "memory_cleanup_job": "Pulizia dei vecchi Ricordi", - "memory_generate_job": "Generazione dei Ricordi", - "metadata_extraction_job": "Estrazione Metadati", - "metadata_extraction_job_description": "Estrai informazioni dai metadati di ciascuna risorsa, come coordinate GPS, volti e risoluzione", - "metadata_faces_import_setting": "Abilita l'importazione dei volti", - "metadata_faces_import_setting_description": "Importa i volti dai dati EXIF dell'immagine e dai file sidecar", - "metadata_settings": "Impostazioni Metadati", - "metadata_settings_description": "Gestisci le impostazioni dei metadati", - "migration_job": "Migrazione", - "migration_job_description": "Migra le anteprime per le risorse e i volti alla struttura di cartelle più recente", - "nightly_tasks_cluster_faces_setting_description": "Avvia riconoscimento facciale sui volti appena rilevati", - "nightly_tasks_cluster_new_faces_setting": "Raggruppa nuovi volti", - "nightly_tasks_database_cleanup_setting": "Processi di pulizia del database", - "nightly_tasks_database_cleanup_setting_description": "Ripulisci il database da file vecchi e scaduti", - "nightly_tasks_generate_memories_setting": "Genera Ricordi", - "nightly_tasks_generate_memories_setting_description": "Genera nuovi Ricordi a partire dalle risorse", - "nightly_tasks_missing_thumbnails_setting": "Genera anteprime mancanti", - "nightly_tasks_missing_thumbnails_setting_description": "Metti in coda le risorse senza miniatura per la generazione delle anteprime", - "nightly_tasks_settings": "Impostazioni delle attività notturne", - "nightly_tasks_settings_description": "Gestisci attività notturne", - "nightly_tasks_start_time_setting": "Orario di avvio", - "nightly_tasks_start_time_setting_description": "L'orario in cui il server fa partire le attività notturne", - "nightly_tasks_sync_quota_usage_setting": "Sincronizza la quota di utilizzo", - "nightly_tasks_sync_quota_usage_setting_description": "Aggiorna la quota di spazio dell'utente in base all'utilizzo corrente", - "no_paths_added": "Nessun percorso aggiunto", - "no_pattern_added": "Nessun pattern aggiunto", - "note_apply_storage_label_previous_assets": "Nota: Per assegnare l'etichetta storage a risorse precedentemente caricate, esegui", - "note_cannot_be_changed_later": "NOTA: Non potrà essere modificato in futuro!", - "notification_email_from_address": "Indirizzo mittente", - "notification_email_from_address_description": "Indirizzo email del mittente, ad esempio: \"Immich Photo Server \". Assicurati di utilizzare un indirizzo da cui sei autorizzato a inviare email.", - "notification_email_host_description": "Host del server email (es. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignora errori di certificato", - "notification_email_ignore_certificate_errors_description": "Ignora errori TLS di validazione del certificato (sconsigliato)", - "notification_email_password_description": "Password da usare per l'autenticazione con il server email", - "notification_email_port_description": "Porta del server email (es. 25, 465, 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Usa SMTPS (SMTP over TLS)", - "notification_email_sent_test_email_button": "Invia email di prova e salva", - "notification_email_setting_description": "Impostazioni per le notifiche via email", - "notification_email_test_email": "Invia email di prova", - "notification_email_test_email_failed": "Impossibile inviare email di prova, controlla i valori inseriti", - "notification_email_test_email_sent": "Un'email di prova è stata inviata a {email}. Per favore controlla la tua casella di posta.", - "notification_email_username_description": "Nome utente utilizzato per l'autenticazione con il server email", - "notification_enable_email_notifications": "Attiva notifiche via email", - "notification_settings": "Impostazioni Notifiche", - "notification_settings_description": "Gestisci le impostazioni di notifica, incluse le email", - "oauth_auto_launch": "Avvio automatico", - "oauth_auto_launch_description": "Esegui il flusso di autenticazione OAuth automaticamente navigando sulla pagina di login", - "oauth_auto_register": "Registrazione automatica", - "oauth_auto_register_description": "Automaticamente registra nuovi utenti dopo il login OAuth", - "oauth_button_text": "Testo pulsante", - "oauth_client_secret_description": "Richiesto per client confidenziali o se PKCE (Proof Key for Code Exchange) non è supportato dal client pubblico.", - "oauth_enable_description": "Login con OAuth", - "oauth_mobile_redirect_uri": "URI di reindirizzamento per app mobile", - "oauth_mobile_redirect_uri_override": "Sovrascrivi URI di reindirizzamento per app mobile", - "oauth_mobile_redirect_uri_override_description": "Abilita quando il gestore OAuth non consente un URL come ''{callback}''", - "oauth_role_claim": "Claim del ruolo", - "oauth_role_claim_description": "Concedi automaticamente l'accesso come amministratore in base alla presenza di questo claim. Il claim può essere 'user' o 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Gestisci impostazioni di login OAuth", - "oauth_settings_more_details": "Per maggiori informazioni su questa funzionalità, consulta la documentazione.", - "oauth_storage_label_claim": "Dichiarazione di ambito(claim) etichetta archiviazione", - "oauth_storage_label_claim_description": "Imposta automaticamente l'etichetta dell'archiviazione dell'utente al valore di questa dichiarazione di ambito(claim).", - "oauth_storage_quota_claim": "Dichiarazione di ambito(claim) limite archiviazione", - "oauth_storage_quota_claim_description": "Imposta automaticamente il limite di archiviazione dell'utente in base al valore di questa dichiarazione di ambito(claim).", - "oauth_storage_quota_default": "Limite predefinito di archiviazione (GiB)", - "oauth_storage_quota_default_description": "Limite in GiB da usare quanto nessuna dichiarazione di ambito(claim) è stata fornita.", - "oauth_timeout": "Timeout Richiesta", - "oauth_timeout_description": "Timeout per le richieste, espresso in millisecondi", - "ocr_job_description": "Utilizza il machine learning per riconoscere il testo nelle immagini", - "password_enable_description": "Login con email e password", - "password_settings": "Login con password", - "password_settings_description": "Gestisci impostazioni del login con password", - "paths_validated_successfully": "Percorsi validati con successo", - "person_cleanup_job": "Pulizia Persona", - "queue_details": "Dettagli della Coda", - "queues": "Code dei processi", - "queues_page_description": "Pagina delle code dei job (admin)", - "quota_size_gib": "Dimensione Archiviazione (GiB)", - "refreshing_all_libraries": "Aggiornando tutte le librerie", - "registration": "Registrazione amministratore", - "registration_description": "Poiché sei il primo utente del sistema, sarai assegnato come Amministratore e sarai responsabile dei task amministrativi, e utenti aggiuntivi saranno creati da te.", - "remove_failed_jobs": "Rimuovi i job falliti", - "require_password_change_on_login": "Richiedi all'utente di cambiare password al primo accesso", - "reset_settings_to_default": "Ripristina impostazioni predefinite", - "reset_settings_to_recent_saved": "Ripristina impostazioni alle impostazioni salvate di recente", - "scanning_library": "Scansione della libreria", - "search_jobs": "Cerca Attività…", - "send_welcome_email": "Invia email di benvenuto", - "server_external_domain_settings": "Dominio esterno", - "server_external_domain_settings_description": "Dominio per link condivisi pubblicamente, incluso http(s)://", - "server_public_users": "Utenti Pubblici", - "server_public_users_description": "Tutti gli utenti (nome ed e-mail) sono elencati quando si aggiunge un utente agli album condivisi. Quando disabilitato, l'elenco degli utenti sarà disponibile solo per gli utenti amministratori.", - "server_settings": "Impostazioni Server", - "server_settings_description": "Gestisci le impostazioni del server", - "server_stats_page_description": "Pagina delle statistiche del server (admin)", - "server_welcome_message": "Messaggio di benvenuto", - "server_welcome_message_description": "Un messaggio mostrato sulla pagina di login.", - "settings_page_description": "Pagina delle impostazioni (admin)", - "sidecar_job": "Metadati sidecar", - "sidecar_job_description": "Scopri o sincronizza metadati sidecar dal filesystem", - "slideshow_duration_description": "Numero di secondi per cui mostrare ciascuna immagine", - "smart_search_job_description": "Esegui il machine learning sulle risorse per permettere la ricerca intelligente", - "storage_template_date_time_description": "Data e ora di creazione del media vengono usate come data e ora dello stesso", - "storage_template_date_time_sample": "Esempio di data {date}", - "storage_template_enable_description": "Attiva il motore del modello di archiviazione", - "storage_template_hash_verification_enabled": "Verifica hash abilitata", - "storage_template_hash_verification_enabled_description": "Attiva verifica hash, non disabilitare questo se non sei certo delle implicazioni", - "storage_template_migration": "Migrazione modello archiviazione", - "storage_template_migration_description": "Applica il {template} attuale alle risorse caricate in precedenza", - "storage_template_migration_info": "Le modifiche al modello di archiviazione verranno applicate solo alle nuove risorse. Per applicare le modifiche retroattivamente esegui {job}.", - "storage_template_migration_job": "Processo di migrazione del Modello di Archiviazione", - "storage_template_more_details": "Per maggiori informazioni riguardo a questa funzionalità, consulta il Modello di Archiviazione e le sue conseguenze", - "storage_template_onboarding_description_v2": "Se attiva, questa funzionalità organizzerà automaticamente i file utilizzando un modello definito dall'utente. Per maggiori informazioni, consultare la documentazione.", - "storage_template_path_length": "Limite approssimativo lunghezza percorso: {length, number}/{limit, number}", - "storage_template_settings": "Modello di Archiviazione", - "storage_template_settings_description": "Gestisci la struttura delle cartelle e il nome degli asset caricati", - "storage_template_user_label": "{label} è l'etichetta di archiviazione dell'utente", - "system_settings": "Impostazioni di sistema", - "tag_cleanup_job": "Pulisci Tag", - "template_email_available_tags": "Puoi usare le seguenti variabili nel tuo modello: {tags}", - "template_email_if_empty": "Se il modello è vuoto, verrà usata l'email di default.", - "template_email_invite_album": "Modello di invito all'album", - "template_email_preview": "Anteprima", - "template_email_settings": "Template Email", - "template_email_update_album": "Aggiorna template dell'album", - "template_email_welcome": "Modello di email di benvenuto", - "template_settings": "Templates Notifiche", - "template_settings_description": "Gestisci i modelli personalizzati per le notifiche", - "theme_custom_css_settings": "CSS Personalizzato", - "theme_custom_css_settings_description": "I Cascading Style Sheets (CSS) permettono di personalizzare l'interfaccia di Immich.", - "theme_settings": "Impostazioni Tema", - "theme_settings_description": "Gestisci la personalizzazione dell'interfaccia web di Immich", - "thumbnail_generation_job": "Genera Anteprime", - "thumbnail_generation_job_description": "Genera anteprime grandi, piccole e sfocate per ogni asset, oltre a miniature per ogni persona", - "transcoding_acceleration_api": "API di accelerazione", - "transcoding_acceleration_api_description": "L'API che interagirà con il tuo dispositivo per accelerare la transcodifica. Questa impostazione è \"best effort\": ripiegherà sulla transcodifica software in caso di fallimento. VP9 potrebbe funzionare o meno a seconda del tuo hardware.", - "transcoding_acceleration_nvenc": "NVENC (richiede GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (richiede CPU Intel di 7a gen o più recente)", - "transcoding_acceleration_rkmpp": "RKMPP (Solo per SOC Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codifiche audio accettate", - "transcoding_accepted_audio_codecs_description": "Seleziona quali codifiche audio non devono essere transcodificate. Solo usato per alcune politiche di transcodifica.", - "transcoding_accepted_containers": "Contenitori accettati", - "transcoding_accepted_containers_description": "Seleziona quali formati non hanno bisogno di essere remuxati in MP4. Usato solo per certe politiche di transcodifica.", - "transcoding_accepted_video_codecs": "Codifiche video accettate", - "transcoding_accepted_video_codecs_description": "Seleziona quali codifiche video non devono essere transcodificate. Usato solo per alcune politiche di transcodifica.", - "transcoding_advanced_options_description": "Impostazioni che la maggior parte degli utenti non dovrebbero cambiare", - "transcoding_audio_codec": "Codifica Audio", - "transcoding_audio_codec_description": "Opus è l'opzione con la qualità più alta, ma è meno compatibile con dispositivi o software vecchi.", - "transcoding_bitrate_description": "Video con bitrate superiore al massimo o in formato non accettato", - "transcoding_codecs_learn_more": "Per saperne di più sulla terminologia utilizzata, fai riferimento alla documentazione di FFmpeg su codec H.264, codec HEVC e codec VP9.", - "transcoding_constant_quality_mode": "Modalità qualità costante", - "transcoding_constant_quality_mode_description": "iCQ è migliore di CQP, però alcuni dispositivi di accelerazione hardware non supportano questa modalità. Impostando questa opzione l'applicazione preferirà il modo specificato quando è in uso la codifica quality-based. Ignorato da NVENC perché non supporta ICQ.", - "transcoding_constant_rate_factor": "Fattore di rateo costante (-crf)", - "transcoding_constant_rate_factor_description": "Livello di qualità video. I valori tipici sono 23 per H.264, 28 per HEVC, 31 per VP9 e 35 per AV1. Un valore inferiore indica una qualità migliore, ma produce file di dimensioni maggiori.", - "transcoding_disabled_description": "Non transcodificare alcun video, potrebbe rompere la riproduzione su alcuni client", - "transcoding_encoding_options": "Opzioni di codifica", - "transcoding_encoding_options_description": "Imposta codecs, risoluzione, qualità ed altre opzioni per i video codificati", - "transcoding_hardware_acceleration": "Accelerazione Hardware", - "transcoding_hardware_acceleration_description": "Sperimentale: codifica più veloce ma con qualità inferiore a parità bitrate", - "transcoding_hardware_decoding": "Decodifica hardware", - "transcoding_hardware_decoding_setting_description": "Abilita l'accelerazione end-to-end anziché accelerare solo la codifica. Potrebbe non funzionare su tutti i video.", - "transcoding_max_b_frames": "B-frames Massimi", - "transcoding_max_b_frames_description": "Valori più alti migliorano l'efficienza di compressione, ma rallentano l'encoding. Potrebbero non essere compatibili con l'accelerazione hardware su dispositivi più vecchi. 0 disabilita i B-frames, mentre -1 imposta questo valore automaticamente.", - "transcoding_max_bitrate": "Bitrate massimo", - "transcoding_max_bitrate_description": "Impostare un bitrate massimo può rendere le dimensioni dei file più prevedibili a un costo minore per la qualità. A 720p, i valori tipici sono 2600 kbit/s per VP9 o HEVC, o 4500 kbit/s per H.264. Disabilitato se impostato su 0. Quando non viene specificata alcuna unità, si presume k (per kbit/s); pertanto 5000, 5000k e 5M (per Mbit/s) sono equivalenti.", - "transcoding_max_keyframe_interval": "Intervallo massimo dei keyframe", - "transcoding_max_keyframe_interval_description": "Imposta la distanza massima tra i keyframe. Valori più bassi peggiorano l'efficienza di compressione, però migliorano i tempi di ricerca e possono migliorare la qualità nelle scene con movimenti rapidi. 0 imposta questo valore automaticamente.", - "transcoding_optimal_description": "Video con risoluzione più alta rispetto alla risoluzione desiderata o in formato non accettato", - "transcoding_policy": "Politiche di transcodifica", - "transcoding_policy_description": "Imposta quando un video sarà transcodificato", - "transcoding_preferred_hardware_device": "Dispositivo hardware preferito", - "transcoding_preferred_hardware_device_description": "Si applica solo a VAAPI e QSV. Imposta il nodo DRI utilizzato per la transcodifica hardware.", - "transcoding_preset_preset": "Preset (-preset)", - "transcoding_preset_preset_description": "Velocità di compressione. Preset più lenti producono file più piccoli e aumentano la qualità quando viene impostato un certo bitrate. VP9 ignora velocità superiori a `faster`.", - "transcoding_reference_frames": "Frame di riferimento", - "transcoding_reference_frames_description": "Il numero di frame da prendere in considerazione nel comprimere un determinato frame. Valori più alti migliorano l'efficienza di compressione, ma rallentano la codifica. 0 imposta questo valore automaticamente.", - "transcoding_required_description": "Solo video che non sono in un formato accettato", - "transcoding_settings": "Impostazioni Transcodifica Video", - "transcoding_settings_description": "Gestisci quali video transcodificare e come processarli", - "transcoding_target_resolution": "Risoluzione desiderata", - "transcoding_target_resolution_description": "Risoluzioni più elevate possono preservare più dettagli ma richiedono più tempo per la codifica, producono file di dimensioni maggiori e possono ridurre la reattività dell'applicazione.", - "transcoding_temporal_aq": "AQ temporale", - "transcoding_temporal_aq_description": "Si applica solo a NVENC. La Quantizzazione Adattiva Temporale aumenta la qualità delle scene con molto dettaglio e poco movimento. Potrebbe non essere compatibile con dispositivi più vecchi.", - "transcoding_threads": "Thread", - "transcoding_threads_description": "Valori più alti portano a una codifica più veloce, ma lasciano meno spazio al server per elaborare altre attività durante l'attività. Questo valore non dovrebbe essere superiore al numero di core CPU. Massimizza l'utilizzo se impostato su 0.", - "transcoding_tone_mapping": "Mappatura della tonalità", - "transcoding_tone_mapping_description": "Tenta di preservare l'aspetto dei video HDR quando convertiti in SDR. Ciascun algoritmo fa diversi compromessi per colore, dettaglio e luminosità. Hable conserva il dettaglio, Mobius conserva il colore e Reinhard conserva la luminosità.", - "transcoding_transcode_policy": "Politica di transcodifica", - "transcoding_transcode_policy_description": "Politica che determina quando un video deve essere transcodificato. I video HDR verranno sempre transcodificati (eccetto quando la transcodifica è disabilitata).", - "transcoding_two_pass_encoding": "Codifica a due passaggi", - "transcoding_two_pass_encoding_setting_description": "Transcodifica in due passaggi per produrre video codificati migliori. Quando il bitrate massimo è abilitato (necessario affinché funzioni con H.264 e HEVC), questa modalità utilizza un intervallo di bitrate basato sul bitrate massimo e ignora CRF. Per VP9, CRF può essere utilizzato se il bitrate massimo è disabilitato.", - "transcoding_video_codec": "Codec video", - "transcoding_video_codec_description": "VP9 ha alta efficienza e compatibilità web, ma richiede più tempo per la transcodifica. HEVC ha prestazioni simili, ma una minore compatibilità web. H.264 è ampiamente compatibile e veloce da transcodificare, ma produce file molto più grandi. AV1 è il codec più efficiente, ma non è supportato sui dispositivi più vecchi.", - "trash_enabled_description": "Abilita Funzionalità Cestino", - "trash_number_of_days": "Numero di giorni", - "trash_number_of_days_description": "Numero di giorni per cui mantenere gli asset nel cestino prima di rimuoverli definitivamente", - "trash_settings": "Impostazioni cestino", - "trash_settings_description": "Gestisci impostazioni cestino", - "unlink_all_oauth_accounts": "Disconnetti tutti gli account OAuth", - "unlink_all_oauth_accounts_description": "Ricorda di scollegare tutti gli account OAuth prima di passare a un nuovo provider.", - "unlink_all_oauth_accounts_prompt": "Sei sicuro di voler scollegare tutti gli account OAuth? Questa operazione reimposterà l’ID OAuth per ogni utente e non potrà essere annullata.", - "user_cleanup_job": "Pulizia Utente", - "user_delete_delay": "L'account e gli asset dell'utente {user} verranno programmati per la cancellazione definitiva tra {delay, plural, one {# giorno} other {# giorni}}.", - "user_delete_delay_settings": "Ritardo eliminazione", - "user_delete_delay_settings_description": "Numero di giorni dopo l'eliminazione per cancellare in modo definitivo l'account e gli asset di un utente. Il processo di cancellazione dell'utente viene eseguito a mezzanotte per verificare se esistono utenti pronti a essere eliminati. Le modifiche a questa impostazioni saranno prese in considerazione dalla prossima esecuzione.", - "user_delete_immediately": "L'account e tutti gli asset dell'utente {user} verranno messi in coda per la cancellazione permanente immediata.", - "user_delete_immediately_checkbox": "utente", - "user_details": "Dettagli Utente", - "user_management": "Gestione Utenti", - "user_password_has_been_reset": "La password dell'utente è stata reimpostata:", - "user_password_reset_description": "Per favore inserisci una password temporanea per l'utente e informalo che dovrà cambiare la password al prossimo login.", - "user_restore_description": "L'account di {user} verrà ripristinato.", - "user_restore_scheduled_removal": "Ripristina utente - rimozione programmata per il {date, date, long}", - "user_settings": "Impostazione Utente", - "user_settings_description": "Gestisci impostazioni utente", - "user_successfully_removed": "L'utente {email} è stato rimosso con successo.", - "users_page_description": "Pagina utenti (admin)", - "version_check_enabled_description": "Abilita controllo della versione", - "version_check_implications": "La funzione di controllo della versione fa uso di una comunicazione periodica con github.com", - "version_check_settings": "Controllo Versione", - "version_check_settings_description": "Abilita/disabilita la notifica per nuove versioni", - "video_conversion_job": "Transcodifica video", - "video_conversion_job_description": "Transcodifica video per maggiore compatibilità con browser e dispositivi" - }, - "admin_email": "Email Amministratore", - "admin_password": "Password Amministratore", - "administration": "Amministrazione", - "advanced": "Avanzate", - "advanced_settings_clear_image_cache": "Cancella la cache dell' immagine", - "advanced_settings_clear_image_cache_error": "Impossibile cancellare la cache dell'immagine", - "advanced_settings_clear_image_cache_success": "Cancellato/i con successo {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Usa questa opzione per filtrare i contenuti multimediali durante la sincronizzazione in base a criteri alternativi. Prova questa opzione solo se riscontri problemi con il rilevamento di tutti gli album da parte dell'app.", - "advanced_settings_enable_alternate_media_filter_title": "[SPERIMENTALE] Usa un filtro alternativo per la sincronizzazione degli album del dispositivo", - "advanced_settings_log_level_title": "Livello log: {level}", - "advanced_settings_prefer_remote_subtitle": "Alcuni dispositivi sono estremamente lenti a caricare le miniature da risorse locali. Attiva questa impostazione per caricare invece le immagini remote.", - "advanced_settings_prefer_remote_title": "Preferisci immagini remote", - "advanced_settings_proxy_headers_subtitle": "Definisci gli header per i proxy che Immich dovrebbe inviare con ogni richiesta di rete", - "advanced_settings_proxy_headers_title": "Header Proxy Personalizzato [SPERIMENTALE]", - "advanced_settings_readonly_mode_subtitle": "Abilita la modalità di sola lettura in cui le foto possono essere solo visualizzate, mentre funzioni come la selezione di più immagini, la condivisione, la trasmissione e l'eliminazione sono tutte disabilitate. Abilita/Disabilita la sola lettura tramite l'avatar dell'utente dalla schermata principale", - "advanced_settings_readonly_mode_title": "Modalità di sola lettura", - "advanced_settings_self_signed_ssl_subtitle": "Salta la verifica dei certificati SSL del server. Richiesto con l'uso di certificati self-signed.", - "advanced_settings_self_signed_ssl_title": "Consenti certificati SSL self-signed [SPERIMENTALE]", - "advanced_settings_sync_remote_deletions_subtitle": "Rimuovi o ripristina automaticamente un elemento su questo dispositivo quando l'azione è stata fatta via web", - "advanced_settings_sync_remote_deletions_title": "Sincronizza le cancellazioni remote [SPERIMENTALE]", - "advanced_settings_tile_subtitle": "Impostazioni avanzate dell'utente", - "advanced_settings_troubleshooting_subtitle": "Attiva funzioni addizionali per la risoluzione dei problemi", - "advanced_settings_troubleshooting_title": "Risoluzione problemi", - "age_months": "Età {months, plural, one {# mese} other {# mesi}}", - "age_year_months": "Età 1 anno, {months, plural, one {# mese} other {# mesi}}", - "age_years": "{years, plural, other {Età #}}", - "album": "Album", - "album_added": "Album aggiunto", - "album_added_notification_setting_description": "Ricevi una notifica email quando sei aggiunto ad un album condiviso", - "album_cover_updated": "Copertina dell'album aggiornata", - "album_delete_confirmation": "Sei sicuro di voler cancellare l'album {album}?", - "album_delete_confirmation_description": "Se l'album è condiviso gli altri utenti perderanno l'accesso.", - "album_deleted": "Album eliminato", - "album_info_card_backup_album_excluded": "ESCLUSI", - "album_info_card_backup_album_included": "INCLUSI", - "album_info_updated": "Informazioni dell'album aggiornate", - "album_leave": "Abbandona l'album?", - "album_leave_confirmation": "Sei sicuro di voler abbandonare {album}?", - "album_name": "Nome Album", - "album_options": "Impostazioni Album", - "album_remove_user": "Rimuovi l'utente?", - "album_remove_user_confirmation": "Sicuro di voler rimuovere l'utente {user}?", - "album_search_not_found": "Nessun album trovato corrispondente alla tua ricerca", - "album_selected": "Album selezionato", - "album_share_no_users": "Sembra che tu abbia condiviso questo album con tutti gli utenti oppure non hai nessun utente con cui condividere.", - "album_summary": "Sommario Album", - "album_updated": "Album aggiornato", - "album_updated_setting_description": "Ricevi una notifica email quando un album condiviso ha nuovi media", - "album_upload_assets": "Carica risorse dal tuo computer e aggiungile all'album", - "album_user_left": "{album} abbandonato", - "album_user_removed": "Utente {user} rimosso", - "album_viewer_appbar_delete_confirm": "Sei sicuro di voler rimuovere questo album dal tuo account?", - "album_viewer_appbar_share_err_delete": "Non è stato possibile eliminare l'album", - "album_viewer_appbar_share_err_leave": "Non è stato possibile lasciare l'album", - "album_viewer_appbar_share_err_remove": "Ci sono problemi nella rimozione di risorse dall'album", - "album_viewer_appbar_share_err_title": "Non è stato possibile cambiare il titolo dell'album", - "album_viewer_appbar_share_leave": "Lascia album", - "album_viewer_appbar_share_to": "Condividi a", - "album_viewer_page_share_add_users": "Aggiungi utenti", - "album_with_link_access": "Permetti a chiunque possieda il link di visualizzare le foto e le persone dell'album.", - "albums": "Album", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", - "albums_default_sort_order": "Ordinamento predefinito degli album", - "albums_default_sort_order_description": "Ordine iniziale delle risorse nei nuovi album.", - "albums_feature_description": "Raggruppamento delle risorse che possono essere condivise con altri utenti.", - "albums_on_device_count": "Album sul dispositivo ({count})", - "albums_selected": "{count,plural, one{# album selezionato} other {# album selezionati}}", - "all": "Tutti", - "all_albums": "Tutti gli album", - "all_people": "Tutte le persone", - "all_photos": "Tutte le foto", - "all_videos": "Tutti i video", - "allow_dark_mode": "Permetti Tema Scuro", - "allow_edits": "Permetti modifiche", - "allow_public_user_to_download": "Permetti agli utenti pubblici di scaricare", - "allow_public_user_to_upload": "Permetti agli utenti pubblici di caricare", - "allowed": "Consentito", - "alt_text_qr_code": "Immagine QR", - "always_keep": "Mantieni sempre", - "always_keep_photos_hint": "Libera Spazio mantiene tutte le foto su questo dispositivo.", - "always_keep_videos_hint": "Libera Spazio mantiene tutti i video su questo dispositivo.", - "anti_clockwise": "Senso anti-orario", - "api_key": "Chiave API", - "api_key_description": "Questo valore verrà mostrato una sola volta. Assicurati di copiarlo prima di chiudere la finestra.", - "api_key_empty": "Il nome della chiave API non dovrebbe essere vuoto", - "api_keys": "Chiavi API", - "app_architecture_variant": "Variante (Architettura)", - "app_bar_signout_dialog_content": "Sei sicuro di volerti disconnettere?", - "app_bar_signout_dialog_ok": "Si", - "app_bar_signout_dialog_title": "Disconnetti", - "app_download_links": "Link per il download dell'app", - "app_settings": "Impostazioni Applicazione", - "app_stores": "App Stores", - "app_update_available": "Aggiornamento App disponibile", - "appears_in": "Compare in", - "apply_count": "Applica ({count, number})", - "archive": "Archivio", - "archive_action_prompt": "Aggiunti {count} elementi all'Archivio", - "archive_or_unarchive_photo": "Archivia o ripristina foto", - "archive_page_no_archived_assets": "Non è stato trovato nessuna risorsa archiviata", - "archive_page_title": "Archivio ({count})", - "archive_size": "Dimensioni Archivio", - "archive_size_description": "Imposta le dimensioni dell'archivio per i download (in GiB)", - "archived": "Archiviati", - "archived_count": "{count, plural, other {Archiviati #}}", - "are_these_the_same_person": "Sono la stessa persona?", - "are_you_sure_to_do_this": "Sei sicuro di voler procedere?", - "array_field_not_fully_supported": "Insieme di campi richiedono una modifica manuale del JSON", - "asset_action_delete_err_read_only": "Non puoi eliminare risorse in sola lettura, azione ignorata", - "asset_action_share_err_offline": "Non è possibile recuperare le risorse offline, azione ignorata", - "asset_added_to_album": "Aggiunto all'album", - "asset_adding_to_album": "Inserimento nell'album…", - "asset_created": "Risorsa creata", - "asset_description_updated": "La descrizione della risorsa è stata aggiornata", - "asset_filename_is_offline": "La risorsa {filename} è offline", - "asset_has_unassigned_faces": "La risoesa ha dei volti non categorizzati", - "asset_hashing": "Hashing in corso …", - "asset_list_group_by_sub_title": "Raggruppa per", - "asset_list_layout_settings_dynamic_layout_title": "Layout dinamico", - "asset_list_layout_settings_group_automatically": "Automatico", - "asset_list_layout_settings_group_by": "Raggruppa le risorse per", - "asset_list_layout_settings_group_by_month_day": "Mese + giorno", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Impostazioni del layout della griglia delle foto", - "asset_list_settings_title": "Griglia foto", - "asset_not_found_on_device_android": "Risorsa non trovata sul dispositivo", - "asset_not_found_on_device_ios": "Risorsa non trovata sul dispositivo. Se stai usando iCloud, la risorsa potrebbe essere inaccessibile a causa di un file errato salvato su iCloud", - "asset_not_found_on_icloud": "Risorsa non trovata su iCloud. La risorsa potrebbe essere inaccessibile a causa di un file errato salvato su iCloud", - "asset_offline": "Elemento Offline", - "asset_offline_description": "Questa risorsa esterna non esiste più sul disco. Contatta il tuo amministratore di Immich per assistenza.", - "asset_restored_successfully": "Risorsa ripristinata con successo", - "asset_skipped": "Saltato", - "asset_skipped_in_trash": "Nel cestino", - "asset_trashed": "Risorsa cestinata", - "asset_troubleshoot": "Risoluzione dei problemi della risorsa", - "asset_uploaded": "Caricata", - "asset_uploading": "Caricamento…", - "asset_viewer_settings_subtitle": "Gestisci le impostazioni del visualizzatore della galleria", - "asset_viewer_settings_title": "Visualizzazione Risorse", - "assets": "Risorse", - "assets_added_count": "{count, plural, one {# risorsa aggiunta} other {# risorse aggiunte}}", - "assets_added_to_album_count": "{count, plural, one {# risorsa aggiunta} other {# risorse aggiunte}} all'album", - "assets_added_to_albums_count": "Aggiunto {assetTotal, plural, one {# risorsa} other {# risorse}} a {albumTotal, plural, one {# album} other {# album}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {La risorsa} other {Le risorse}} non possono essere aggiunte all'album", - "assets_cannot_be_added_to_albums": "Non é stato possibile aggiungere {count, plural, one {la risorsa} other {le risorse}} a nessun album", - "assets_count": "{count, plural, one {# risorsa} other {# risorse}}", - "assets_deleted_permanently": "{count} risorsa/e cancellate definitivamente", - "assets_deleted_permanently_from_server": "{count} risorsa/e cancellate definitivamente sul server Immich", - "assets_downloaded_failed": "{count, plural, one {Scaricato # file - {error} file non riuscito} other {Scaricati # file - {error} file non riusciti}}", - "assets_downloaded_successfully": "{count, plural, one {Scaricato # file con successo} other {Scaricati # file con successo}}", - "assets_moved_to_trash_count": "{count, plural, one {# risorsa spostata} other {# risorse spostate}} nel cestino", - "assets_permanently_deleted_count": "{count, plural, one {# risorsa cancellata} other {# risorse cancellate}} definitivamente", - "assets_removed_count": "{count, plural, one {# risorsa rimossa} other {# risorse rimosse}}", - "assets_removed_permanently_from_device": "{count} risorsa/e cancellate definitivamente sul tuo dispositivo", - "assets_restore_confirmation": "Sei sicuro di voler ripristinare tutti le risorse cancellate? Non puoi annullare questa azione! Tieni presente che eventuali risorse offline non potranno essere ripristinate in questo modo.", - "assets_restored_count": "{count, plural, one {# risorsa ripristinata} other {# risorse ripristinate}}", - "assets_restored_successfully": "{count} risorsa/e ripristinati", - "assets_trashed": "{count} risorsa/e cestinati", - "assets_trashed_count": "{count, plural, one {Spostato # risorsa} other {Spostate # risorse}} nel cestino", - "assets_trashed_from_server": "{count} risorsa/e cestinate sul server Immich", - "assets_were_part_of_album_count": "{count, plural, one {La risorsa fa} other {Le risorse facevano}} già parte dell'album", - "assets_were_part_of_albums_count": "{count, plural, one {La risorsa fa} other {Le risorse facevano}} già parte degli album", - "authorized_devices": "Dispositivi autorizzati", - "automatic_endpoint_switching_subtitle": "Connetti localmente alla rete Wi-Fi specificata, se disponibile; altrimenti utilizza connessioni alternative", - "automatic_endpoint_switching_title": "Cambio automatico di URL", - "autoplay_slideshow": "Avvio automatico presentazione", - "back": "Indietro", - "back_close_deselect": "Indietro, chiudi o deseleziona", - "background_backup_running_error": "Il backup in background è attualmente in esecuzione, impossibile avviare il backup manuale", - "background_location_permission": "Permesso di localizzazione in background", - "background_location_permission_content": "Per fare in modo che sia possibile cambiare rete quando è in esecuzione in background, Immich deve *sempre* avere accesso alla tua posizione precisa in modo da poter leggere il nome della rete Wi-Fi", - "background_options": "Opzioni sfondo", - "backup": "Backup", - "backup_album_selection_page_albums_device": "Album sul dispositivo ({count})", - "backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere", - "backup_album_selection_page_assets_scatter": "Visto che le risorse possono trovarsi in più album, questi possono essere inclusi o esclusi dal backup.", - "backup_album_selection_page_select_albums": "Seleziona gli album", - "backup_album_selection_page_selection_info": "Informazioni sulla selezione", - "backup_album_selection_page_total_assets": "Numero totale delle risorse", - "backup_albums_sync": "Sincronizzazione Album di Backup", - "backup_all": "Tutti", - "backup_background_service_backup_failed_message": "Impossibile effettuare il backup delle risorse. Riprovo…", - "backup_background_service_complete_notification": "Backup completato", - "backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…", - "backup_background_service_current_upload_notification": "Caricamento di {filename} in corso", - "backup_background_service_default_notification": "Ricerca di nuove risorse…", - "backup_background_service_error_title": "Errore di backup", - "backup_background_service_in_progress_notification": "Backup delle tue risorse…", - "backup_background_service_upload_failure_notification": "Impossibile caricare {filename}", - "backup_controller_page_albums": "Backup Album", - "backup_controller_page_background_app_refresh_disabled_content": "Attiva l'aggiornamento dell'app in background in Impostazioni > Generale > Aggiorna app in background per utilizzare backup in background.", - "backup_controller_page_background_app_refresh_disabled_title": "Aggiornamento dell'app in background disattivo", - "backup_controller_page_background_app_refresh_enable_button_text": "Vai alle impostazioni", - "backup_controller_page_background_battery_info_link": "Mostrami come", - "backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimizzazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Ottimizzazioni batteria", - "backup_controller_page_background_charging": "Solo durante la ricarica", - "backup_controller_page_background_configure_error": "Impossibile configurare i servizi in background", - "backup_controller_page_background_delay": "Ritarda il backup delle nuove risorse: {duration}", - "backup_controller_page_background_description": "Abilita il servizio in background per effettuare il backup delle nuove risorse senza la necessità di aprire l'app", - "backup_controller_page_background_is_off": "Backup automatico in background disattivato", - "backup_controller_page_background_is_on": "Backup automatico in background attivo", - "backup_controller_page_background_turn_off": "Disabilita servizi in background", - "backup_controller_page_background_turn_on": "Abilita servizi in background", - "backup_controller_page_background_wifi": "Solo con Wi-Fi", - "backup_controller_page_backup": "Backup", - "backup_controller_page_backup_selected": "Selezionati: ", - "backup_controller_page_backup_sub": "Foto e video caricati", - "backup_controller_page_created": "Creato il: {date}", - "backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server all'apertura dell'applicazione.", - "backup_controller_page_excluded": "Esclusi: ", - "backup_controller_page_failed": "Falliti: ({count})", - "backup_controller_page_filename": "Nome file: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informazioni sul backup", - "backup_controller_page_none_selected": "Nessuna selezione", - "backup_controller_page_remainder": "Rimanenti", - "backup_controller_page_remainder_sub": "Foto e video che devono essere ancora caricati", - "backup_controller_page_server_storage": "Spazio sul server", - "backup_controller_page_start_backup": "Avvia backup", - "backup_controller_page_status_off": "Backup è disattivato", - "backup_controller_page_status_on": "Backup è attivato", - "backup_controller_page_storage_format": "{used} di {total} usati", - "backup_controller_page_to_backup": "Album da caricare", - "backup_controller_page_total_sub": "Tutte le foto e i video unici caricati dagli album selezionati", - "backup_controller_page_turn_off": "Disattiva backup", - "backup_controller_page_turn_on": "Attiva backup", - "backup_controller_page_uploading_file_info": "Caricamento informazioni file", - "backup_err_only_album": "Non è possibile rimuovere l'unico album", - "backup_error_sync_failed": "Sincronizzazione non riuscita. Impossibile elaborare il backup.", - "backup_info_card_assets": "risorse", - "backup_manual_cancelled": "Annullato", - "backup_manual_in_progress": "Caricamento già in corso. Riprova più tardi", - "backup_manual_success": "Successo", - "backup_manual_title": "Stato del caricamento", - "backup_options": "Opzioni di Backup", - "backup_options_page_title": "Opzioni di Backup", - "backup_setting_subtitle": "Gestisci le impostazioni di upload in primo piano e in background", - "backup_settings_subtitle": "Gestisci le impostazioni di caricamento", - "backup_upload_details_page_more_details": "Tocca per maggiori informazioni", - "backward": "Indietro", - "biometric_auth_enabled": "Autenticazione biometrica attivata", - "biometric_locked_out": "Sei stato bloccato dall'autenticazione biometrica", - "biometric_no_options": "Nessuna opzione biometrica disponibile", - "biometric_not_available": "L'autenticazione biometrica non è disponibile su questo dispositivo", - "birthdate_saved": "Data di nascita salvata con successo", - "birthdate_set_description": "La data di nascita è usata per calcolare l'età di questa persona al momento dello scatto della foto.", - "blurred_background": "Sfondo sfocato", - "bugs_and_feature_requests": "Bug & Richieste di nuove funzionalità", - "build": "Compilazione", - "build_image": "Immagine Compilata", - "bulk_delete_duplicates_confirmation": "Sei sicuro di voler cancellare {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questa operazione manterrà la risorsa più grande di ogni gruppo e cancellerà permanentemente tutti gli altri duplicati. Non puoi annullare questa operazione!", - "bulk_keep_duplicates_confirmation": "Sei sicuro di voler tenere {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questa operazione risolverà tutti i gruppi duplicati senza cancellare nulla.", - "bulk_trash_duplicates_confirmation": "Sei davvero sicuro di voler cancellare {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questa operazione manterrà la risorsa più grande di ogni gruppo e cancellerà permanentemente tutti gli altri duplicati.", - "buy": "Acquista Immich", - "cache_settings_clear_cache_button": "Pulisci cache", - "cache_settings_clear_cache_button_title": "Pulisce la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.", - "cache_settings_duplicated_assets_clear_button": "PULISCI", - "cache_settings_duplicated_assets_subtitle": "Foto e video che sono nella black list dell'applicazione", - "cache_settings_duplicated_assets_title": "Risorse duplicate ({count})", - "cache_settings_statistics_album": "Anteprime librerie", - "cache_settings_statistics_full": "Immagini complete", - "cache_settings_statistics_shared": "Anteprime album condivisi", - "cache_settings_statistics_thumbnail": "Anteprime", - "cache_settings_statistics_title": "Uso della cache", - "cache_settings_subtitle": "Controlla il comportamento della cache dell'applicazione mobile immich", - "cache_settings_tile_subtitle": "Controlla il comportamento dello storage locale", - "cache_settings_tile_title": "Archiviazione locale", - "cache_settings_title": "Impostazioni della Cache", - "camera": "Fotocamera", - "camera_brand": "Marca fotocamera", - "camera_model": "Modello fotocamera", - "cancel": "Annulla", - "cancel_search": "Annulla ricerca", - "canceled": "Annullato", - "canceling": "Annullamento", - "cannot_merge_people": "Impossibile unire le persone", - "cannot_undo_this_action": "Non puoi annullare questa azione!", - "cannot_update_the_description": "Impossibile aggiornare la descrizione", - "cast": "Trasmetti", - "cast_description": "Configura le destinazioni di trasmissione disponibili", - "change_date": "Modifica data", - "change_description": "Modifica descrizione", - "change_display_order": "Cambia l'ordine di visualizzazione", - "change_expiration_time": "Modifica tempo di scadenza", - "change_location": "Modifica posizione", - "change_name": "Modifica nome", - "change_name_successfully": "Nome modificato con successo", - "change_password": "Modifica Password", - "change_password_description": "È stato richiesto di cambiare la password (oppure è la prima volta che accedi). Inserisci la tua nuova password qui sotto.", - "change_password_form_confirm_password": "Conferma Password", - "change_password_form_description": "Ciao {name},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto.", - "change_password_form_log_out": "Log out da tutti gli altri dispositivi", - "change_password_form_log_out_description": "È consigliato il log out da tutti gli altri dispositivi", - "change_password_form_new_password": "Nuova Password", - "change_password_form_password_mismatch": "Le password non coincidono", - "change_password_form_reenter_new_password": "Inserisci ancora la nuova password", - "change_pin_code": "Cambia il codice PIN", - "change_trigger": "Cambia il trigger", - "change_trigger_prompt": "Sei sicuro di voler cambiare il trigger? Questo rimuoverà tutte le esistenti azioni e filtri.", - "change_your_password": "Modifica la tua password", - "changed_visibility_successfully": "Visibilità modificata con successo", - "charging": "In carica", - "charging_requirement_mobile_backup": "Il backup in background richiede che il dispositivo sia in carica", - "check_corrupt_asset_backup": "Verifica la presenza di backup di risorse corrotte", - "check_corrupt_asset_backup_button": "Effettua controllo", - "check_corrupt_asset_backup_description": "Effettua questo controllo solo su rete Wi-Fi e solo quando tutte le risorse saranno state sottoposte a backup. La procedura potrebbe impiegare qualche minuto.", - "check_logs": "Controlla i log", - "checksum": "Checksum", - "choose_matching_people_to_merge": "Scegli persone combacianti da unire", - "city": "Città", - "cleanup_confirm_description": "Immich ha trovato {count} risorse (create prima del {date}) e già salvate sul server. Rimuovo le copie locali da questo dispositivo?", - "cleanup_confirm_prompt_title": "Rimuovo da questo dispositivo?", - "cleanup_deleted_assets": "Spostate {count} risorse nel cestino", - "cleanup_deleting": "Spostamento nel cestino...", - "cleanup_found_assets": "Trovate {count} risorse già salvate", - "cleanup_found_assets_with_size": "Trovate {count} risorse salvate ({size})", - "cleanup_icloud_shared_albums_excluded": "Gli Album Condivisi di iCloud sono esclusi dalla ricerca", - "cleanup_no_assets_found": "Nessuna risorsa trovata con i criteri specificati. Libera Spazio può solo rimuovere le risorse che sono state salvate sul server", - "cleanup_preview_title": "Risorse da rimuovere ({count})", - "cleanup_step3_description": "Ricerca risorse già salvate sul server corrispondenti alle opzioni di ricerca.", - "cleanup_step4_summary": "{count} risorse (create prima del {date}) da rimuovere sul tuo dispositivo. Rimarrano comunque accessibili dall'app Immich.", - "cleanup_trash_hint": "Per recuperare completamente lo spazio devi aprire l'app della galleria e svuotarne il cestino", - "clear": "Pulisci", - "clear_all": "Pulisci tutto", - "clear_all_recent_searches": "Rimuovi tutte le ricerche recenti", - "clear_file_cache": "Cancella la cache dei file", - "clear_message": "Pulisci messaggio", - "clear_value": "Pulisci valore", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Inserire la password", - "client_cert_import": "Importa", - "client_cert_import_success_msg": "Certificato client importato", - "client_cert_invalid_msg": "File certificato invalido o password errata", - "client_cert_remove_msg": "Certificato client rimosso", - "client_cert_subtitle": "Supporta solo il formato PKCS12 (.p12, .pfx). L'importazione/rimozione del certificato è disponibile solo prima del login", - "client_cert_title": "Certificato Client SSL [SPERIMENTALE]", - "clockwise": "Senso orario", - "close": "Chiudi", - "collapse": "Restringi", - "collapse_all": "Comprimi tutto", - "color": "Colore", - "color_theme": "Colore Tema", - "command": "Comando", - "comment_deleted": "Commento eliminato", - "comment_options": "Opzioni per i commenti", - "comments_and_likes": "Commenti & mi piace", - "comments_are_disabled": "I commenti sono disabilitati", - "common_create_new_album": "Crea nuovo Album", - "completed": "Completato", - "confirm": "Conferma", - "confirm_admin_password": "Conferma password dell'amministratore", - "confirm_delete_face": "Sei sicuro di voler cancellare il volto di {name} dalla risorsa?", - "confirm_delete_shared_link": "Sei sicuro di voler eliminare questo link condiviso?", - "confirm_keep_this_delete_others": "Tutti le altre risorse nello stack saranno eliminate, eccetto questa. Sei sicuro di voler continuare?", - "confirm_new_pin_code": "Conferma il nuovo codice PIN", - "confirm_password": "Conferma password", - "confirm_tag_face": "Vuoi taggare questo volto come {name}?", - "confirm_tag_face_unnamed": "Vuoi taggare questo volto?", - "connected_device": "Dispositivo connesso", - "connected_to": "Connesso", - "contain": "Adatta alla finestra", - "context": "Contesto", - "continue": "Continua", - "control_bottom_app_bar_create_new_album": "Crea nuovo album", - "control_bottom_app_bar_delete_from_immich": "Elimina da Immich", - "control_bottom_app_bar_delete_from_local": "Elimina dal dispositivo", - "control_bottom_app_bar_edit_location": "Modifica posizione", - "control_bottom_app_bar_edit_time": "Modifica data e ora", - "control_bottom_app_bar_share_link": "Condividi Link", - "control_bottom_app_bar_share_to": "Condividi a", - "control_bottom_app_bar_trash_from_immich": "Sposta nel cestino", - "copied_image_to_clipboard": "Immagine copiata negli appunti.", - "copied_to_clipboard": "Copiato negli appunti!", - "copy_error": "Errore nella copia", - "copy_file_path": "Copia percorso file", - "copy_image": "Copia immagine", - "copy_link": "Copia link", - "copy_link_to_clipboard": "Copia link negli appunti", - "copy_password": "Copia password", - "copy_to_clipboard": "Copia negli appunti", - "country": "Nazione", - "cover": "Riempi la finestra", - "covers": "Copertine", - "create": "Crea", - "create_album": "Crea album", - "create_album_page_untitled": "Senza titolo", - "create_api_key": "Crea chiave API", - "create_first_workflow": "Crea il primo workflow", - "create_library": "Crea libreria", - "create_link": "Crea link", - "create_link_to_share": "Crea link da condividere", - "create_link_to_share_description": "Permetti a chiunque con il link di vedere le foto selezionate", - "create_new": "CREA NUOVO", - "create_new_person": "Crea nuova persona", - "create_new_person_hint": "Assegna le risorse selezionate a una nuova persona", - "create_new_user": "Crea nuovo utente", - "create_shared_album_page_share_add_assets": "AGGIUNGI RISORSE", - "create_shared_album_page_share_select_photos": "Seleziona foto", - "create_shared_link": "Crea link condiviso", - "create_tag": "Crea tag", - "create_tag_description": "Crea un nuovo tag. Per i tag nidificati, inserisci il percorso completo del tag includendo le barre oblique (/).", - "create_user": "Crea utente", - "create_workflow": "Crea il workflow", - "created": "Creato", - "created_at": "Creato il", - "creating_linked_albums": "Creazione di album collegati...", - "crop": "Ritaglia", - "crop_aspect_ratio_fixed": "Fisso", - "crop_aspect_ratio_free": "Libero", - "crop_aspect_ratio_original": "Originale", - "curated_object_page_title": "Oggetti", - "current_device": "Dispositivo attuale", - "current_pin_code": "Attuale codice PIN", - "current_server_address": "Indirizzo del server in uso", - "custom_date": "Data specifica", - "custom_locale": "Localizzazione personalizzata", - "custom_locale_description": "Formatta data e numeri in base alla lingua e al paese", - "custom_url": "URL personalizzato", - "cutoff_date_description": "Mantieni le foto fino al…", - "cutoff_day": "{count, plural, one {giorno} other {giorni}}", - "cutoff_year": "{count, plural, one {anno} other {anni}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Scuro", - "dark_theme": "Imposta tema scuro", - "date": "Data", - "date_after": "Dopo la data", - "date_and_time": "Data e ora", - "date_before": "Prima della data", - "date_format": "E, d LLL, y • hh:mm", - "date_of_birth_saved": "Data di nascita salvata con successo", - "date_range": "Intervallo di date", - "day": "Giorno", - "days": "Giorni", - "deduplicate_all": "Elimina tutti i doppioni", - "deduplication_criteria_1": "Dimensione immagine in bytes", - "deduplication_criteria_2": "Numero di dati EXIF", - "deduplication_info": "Informazioni di deduplicazione", - "deduplication_info_description": "Per preselezionare automaticamente le risorse e rimuovere i duplicati in massa, verifichiamo:", - "default_locale": "Localizzazione preimpostata", - "default_locale_description": "Formatta la data e i numeri in base alle impostazioni del tuo browser", - "delete": "Elimina", - "delete_action_confirmation_message": "Vuoi davvero eliminare questa risorsa? Questa azione sposterà la risorsa nel cestino del server e ti chiederà se desideri eliminarla dal dispositivo", - "delete_action_prompt": "{count} elementi eliminati", - "delete_album": "Elimina album", - "delete_api_key_prompt": "Sei sicuro di voler eliminare questa chiave API?", - "delete_dialog_alert": "Questi oggetti saranno eliminati definitivamente da Immich e dal tuo device", - "delete_dialog_alert_local": "Questi elementi verranno eliminati definitivamente dal dispositivo, ma saranno ancora disponibili sul server Immich", - "delete_dialog_alert_local_non_backed_up": "Alcuni degli elementi non sono stati caricati su Immich e saranno rimossi definitivamente dal tuo dispositivo", - "delete_dialog_alert_remote": "Questi elementi verranno eliminati permanentemente dal server Immich", - "delete_dialog_ok_force": "Elimina comunque", - "delete_dialog_title": "Elimina definitivamente", - "delete_duplicates_confirmation": "Sei sicuro di voler eliminare questi duplicati per sempre?", - "delete_face": "cancella faccia", - "delete_key": "Elimina chiave", - "delete_library": "Elimina libreria", - "delete_link": "Elimina link", - "delete_local_action_prompt": "{count} elementi rimossi in locale", - "delete_local_dialog_ok_backed_up_only": "Elimina solo con backup", - "delete_local_dialog_ok_force": "Elimina comunque", - "delete_others": "Elimina gli altri", - "delete_permanently": "Elimina definitivamente", - "delete_permanently_action_prompt": "{count} eliminati definitivamente", - "delete_shared_link": "Elimina link condiviso", - "delete_shared_link_dialog_title": "Elimina link condiviso", - "delete_tag": "Elimina tag", - "delete_tag_confirmation_prompt": "Sei sicuro di voler cancellare il tag {tagName}?", - "delete_user": "Elimina utente", - "deleted_shared_link": "Elimina link condiviso", - "deletes_missing_assets": "Cancella le risorse mancanti dal disco", - "description": "Descrizione", - "description_input_hint_text": "Aggiungi descrizione...", - "description_input_submit_error": "Errore modificare descrizione, controlli I log per maggiori dettagli", - "deselect_all": "Deseleziona Tutto", - "details": "Dettagli", - "direction": "Direzione", - "disable": "Disabilita", - "disabled": "Disabilitato", - "disallow_edits": "Blocca modifiche", - "discord": "Discord", - "discover": "Scopri", - "discovered_devices": "Dispositivi trovati", - "dismiss_all_errors": "Ignora tutti gli errori", - "dismiss_error": "Ignora errore", - "display_options": "Impostazioni visualizzazione", - "display_order": "Ordine di visualizzazione", - "display_original_photos": "Visualizza foto originali", - "display_original_photos_setting_description": "Visualizza la foto originale anziché le miniature quando la risorsa originale è compatibile con il web. Questo potrebbe causare un ritardo nella visualizzazione delle foto.", - "do_not_show_again": "Non mostrare più questo messaggio", - "documentation": "Documentazione", - "done": "Fatto", - "download": "Scarica", - "download_action_prompt": "Sto scaricando {count} risorse", - "download_canceled": "Download annullato", - "download_complete": "Download completato", - "download_enqueue": "Download in coda", - "download_error": "Errore durante il download", - "download_failed": "Download fallito", - "download_finished": "Download terminato", - "download_include_embedded_motion_videos": "Video incorporati", - "download_include_embedded_motion_videos_description": "Includere i video incorporati nelle foto in movimento come file separato", - "download_notfound": "Download non trovato", - "download_original": "Scarica l'originale", - "download_paused": "Download in pausa", - "download_settings": "Scarica", - "download_settings_description": "Gestisci le impostazioni relative al download delle risorse", - "download_started": "Download avviato", - "download_sucess": "Download completato", - "download_sucess_android": "I contenuti multimediali sono stati scaricati in DCIM/Immich", - "download_waiting_to_retry": "In attesa di riprovare", - "downloading": "Scaricamento", - "downloading_asset_filename": "Sto scaricando la risorsa {filename}", - "downloading_from_icloud": "Scaricamento da iCloud", - "downloading_media": "Scaricamento file multimediali", - "drop_files_to_upload": "Rilascia i file ovunque per caricarli", - "duplicates": "Duplicati", - "duplicates_description": "Risolvi ciascun gruppo indicando quali sono, se esistono, i duplicati", - "duration": "Durata", - "edit": "Modifica", - "edit_album": "Modifica album", - "edit_avatar": "Modifica avatar", - "edit_birthday": "Modifica compleanno", - "edit_date": "Modifica data", - "edit_date_and_time": "Modifica data e ora", - "edit_date_and_time_action_prompt": "{count} data e ora modificata", - "edit_date_and_time_by_offset": "Modifica data per offset", - "edit_date_and_time_by_offset_interval": "Nuovo intervallo di date: {from} - {to}", - "edit_description": "Modifica la descrizione", - "edit_description_prompt": "Selezionare una nuova descrizione:", - "edit_exclusion_pattern": "Modifica pattern di esclusione", - "edit_faces": "Modifica volti", - "edit_key": "Modifica chiave", - "edit_link": "Modifica link", - "edit_location": "Modifica posizione", - "edit_location_action_prompt": "{count} luoghi modificati", - "edit_location_dialog_title": "Posizione", - "edit_name": "Modifica nome", - "edit_people": "Modifica persone", - "edit_tag": "Modifica tag", - "edit_title": "Modifica Titolo", - "edit_user": "Modifica utente", - "edit_workflow": "Edita il workflow", - "editor": "Editor", - "editor_close_without_save_prompt": "Le modifiche non verranno salvate", - "editor_close_without_save_title": "Vuoi chiudere l'editor?", - "editor_confirm_reset_all_changes": "Sicuro di voler resettare tutte le modifiche?", - "editor_flip_horizontal": "Capovolgi in orizzontale", - "editor_flip_vertical": "Capovolgi in verticale", - "editor_orientation": "Orientamento", - "editor_reset_all_changes": "Annulla modifiche", - "editor_rotate_left": "Ruota di 90° antiorario", - "editor_rotate_right": "Ruota di 90° orario", - "email": "Email", - "email_notifications": "Notifiche email", - "empty_folder": "La cartella è vuota", - "empty_trash": "Svuota cestino", - "empty_trash_confirmation": "Sei sicuro di volere svuotare il cestino? Questo rimuoverà tutte le risorse nel cestino in modo permanente da Immich.\nNon puoi annullare questa azione!", - "enable": "Abilita", - "enable_backup": "Abilita Backup", - "enable_biometric_auth_description": "Inserire il codice PIN per abilitare l'autenticazione biometrica", - "enabled": "Abilitato", - "end_date": "Data Fine", - "enqueued": "Accodato", - "enter_wifi_name": "Inserisci il nome della rete Wi-Fi", - "enter_your_pin_code": "Inserisci il tuo codice PIN", - "enter_your_pin_code_subtitle": "Inserire il codice PIN per accedere alla cartella protetta", - "error": "Errore", - "error_change_sort_album": "Errore nel cambiare l'ordine di degli album", - "error_delete_face": "Errore nella rimozione del volto dalla risorsa", - "error_getting_places": "Errore durante il recupero dei luoghi", - "error_loading_albums": "Errore nel caricamento degli album", - "error_loading_image": "Errore nel caricamento dell'immagine", - "error_loading_partners": "Errore durante il caricamento dei partner: {error}", - "error_retrieving_asset_information": "Errore nel recuperare informazioni sull'elemento", - "error_saving_image": "Errore: {error}", - "error_tag_face_bounding_box": "Errore durante il tag del volto - impossibile ricavare le coordinate del riquadro", - "error_title": "Errore - Qualcosa è andato storto", - "error_while_navigating": "Errore durante la navigazione verso l'elemento", - "errors": { - "cannot_navigate_next_asset": "Impossibile passare alla risorsa successiva", - "cannot_navigate_previous_asset": "Impossibile passare alla risorsa precedente", - "cant_apply_changes": "Impossibile applicare le modifiche", - "cant_change_activity": "Impossibile {enabled, select, true {disabilitare} other {abilitare}} l'attività", - "cant_change_asset_favorite": "Impossibile cambiare il preferito per la risorsa", - "cant_change_metadata_assets_count": "Impossibile cambiare i metadati di {count, plural, one {# risorsa} other {# risorse}}", - "cant_get_faces": "Impossibile ottenere i volti", - "cant_get_number_of_comments": "Impossibile ottenere il numero di commenti", - "cant_search_people": "Impossibile cercare persone", - "cant_search_places": "Impossibile cercare luoghi", - "error_adding_assets_to_album": "Errore nell'aggiunta di risorse all'album", - "error_adding_users_to_album": "Errore aggiungendo gli utenti all'album", - "error_deleting_shared_user": "Errore durante la cancellazione dell'utente condiviso", - "error_downloading": "Errore scaricando {filename}", - "error_hiding_buy_button": "Errore nel nascondere il pulsante di acquisto", - "error_removing_assets_from_album": "Errore nella rimozione di risorse dall'album, controlla la console per ulteriori dettagli", - "error_selecting_all_assets": "Errore nella selezione di tutte le risorse", - "exclusion_pattern_already_exists": "Questo pattern di esclusione è già presente.", - "failed_to_create_album": "Creazione dell'album non riuscita", - "failed_to_create_shared_link": "Creazione del link condivisibile non riuscita", - "failed_to_edit_shared_link": "Errore durante la modifica del link condivisibile", - "failed_to_get_people": "Impossibile ottenere le persone", - "failed_to_keep_this_delete_others": "Impossibile conservare questa risorsa ed eliminare le altre", - "failed_to_load_asset": "Errore durante il caricamento della risorsa", - "failed_to_load_assets": "Errore durante il caricamento delle risorse", - "failed_to_load_notifications": "Errore nel caricamento delle notifiche", - "failed_to_load_people": "Caricamento delle persone non riuscito", - "failed_to_remove_product_key": "Rimozione del codice del prodotto fallita", - "failed_to_reset_pin_code": "Impossibile reimpostare il codice PIN", - "failed_to_stack_assets": "Errore durante il raggruppamento delle risorse", - "failed_to_unstack_assets": "Errore durante la separazione delle risorse", - "failed_to_update_notification_status": "Aggiornamento stato notifiche fallito", - "incorrect_email_or_password": "Email o password non corretta", - "library_folder_already_exists": "Questo path di importazione esiste già.", - "paths_validation_failed": "{paths, plural, one {# percorso} other {# percorsi}} hanno fallito la validazione", - "profile_picture_transparent_pixels": "Le foto profilo non possono avere pixel trasparenti. Riprova ingrandendo e/o muovendo l'immagine.", - "quota_higher_than_disk_size": "Hai impostato un limite più alto della dimensione del disco", - "something_went_wrong": "Qualcosa è andato storto", - "unable_to_add_album_users": "Impossibile aggiungere utenti all'album", - "unable_to_add_assets_to_shared_link": "Impossibile aggiungere le risorse al link condiviso", - "unable_to_add_comment": "Impossibile aggiungere commento", - "unable_to_add_exclusion_pattern": "Impossibile aggiungere pattern di esclusione", - "unable_to_add_partners": "Impossibile aggiungere compagni", - "unable_to_add_remove_archive": "Impossibile {archived, select, true {rimuovere la risorsa dall'archivio} other {aggiungere la risorsa all'archivio}}", - "unable_to_add_remove_favorites": "Impossibile {favorite, select, true {aggiungere la risorsa ai} other {rimuovere la risorsa dai}} preferiti", - "unable_to_archive_unarchive": "Impossible {archived, select, true {archiviare} other {rimuovere dall'archivio}}", - "unable_to_change_album_user_role": "Impossibile modificare il ruolo dell'utente nell'album", - "unable_to_change_date": "Impossibile modificare la data", - "unable_to_change_description": "Impossibile modificare la descrizione", - "unable_to_change_favorite": "Errore durante il cambio di stato preferito della risorsa", - "unable_to_change_location": "Impossibile modificare posizione", - "unable_to_change_password": "Impossibile modificare password", - "unable_to_change_visibility": "Errore durante la modifica della visibilità per {count, plural, one {# persona} other {# persone}}", - "unable_to_complete_oauth_login": "Errore durante l'accesso tramite OAuth", - "unable_to_connect": "Impossibile connettersi", - "unable_to_copy_to_clipboard": "Impossibile copiare negli appunti, assicurati di aver aperto la pagina in https", - "unable_to_create": "Impossibile create il workflow", - "unable_to_create_admin_account": "Impossibile creare un account admin", - "unable_to_create_api_key": "Impossibile creare una nuova chiave API", - "unable_to_create_library": "Impossibile creare la libreria", - "unable_to_create_user": "Impossibile creare utente", - "unable_to_delete_album": "Impossibile cancellare album", - "unable_to_delete_asset": "Impossibile cancellare la risorsa", - "unable_to_delete_assets": "Errore durante l'eliminazione delle risorse", - "unable_to_delete_exclusion_pattern": "Impossibile cancellare pattern di esclusione", - "unable_to_delete_shared_link": "Impossibile cancellare link condiviso", - "unable_to_delete_user": "Impossibile cancellare utente", - "unable_to_delete_workflow": "Impossibile eleminare il workflow", - "unable_to_download_files": "Impossibile scaricare i file", - "unable_to_edit_exclusion_pattern": "Impossibile modificare pattern di esclusione", - "unable_to_empty_trash": "Impossibile svuotare il cestino", - "unable_to_enter_fullscreen": "Impossibile aprire l'applicazione a schermo intero", - "unable_to_exit_fullscreen": "Impossibile uscire dallo schermo intero", - "unable_to_get_comments_number": "Impossibile ottenere il numero di commenti", - "unable_to_get_shared_link": "Impossibile ottenere il link condiviso", - "unable_to_hide_person": "Impossibile nascondere persona", - "unable_to_link_motion_video": "Impossibile collegare video in movimento", - "unable_to_link_oauth_account": "Impossibile collegare l'account OAuth", - "unable_to_log_out_all_devices": "Impossibile eseguire il logout da tutti i dispositivi", - "unable_to_log_out_device": "Impossibile eseguire il logout dal dispositivo", - "unable_to_login_with_oauth": "Impossibile effettuare l'accesso tramite OAuth", - "unable_to_play_video": "Impossibile riprodurre il video", - "unable_to_reassign_assets_existing_person": "Errore durante la riassegnazione delle risorse a {name, select, null {una persona esistente} other {{name}}}", - "unable_to_reassign_assets_new_person": "Errore durante la riassegnazione delle risorse ad una nuova persona", - "unable_to_refresh_user": "Impossibile aggiornare l'utente", - "unable_to_remove_album_users": "Impossibile rimuovere gli utenti dall'album", - "unable_to_remove_api_key": "Impossibile rimuovere la chiave API", - "unable_to_remove_assets_from_shared_link": "Errore durante la rimozione delle risorse dal link condiviso", - "unable_to_remove_library": "Impossibile rimuovere libreria", - "unable_to_remove_partner": "Impossibile rimuovere compagno", - "unable_to_remove_reaction": "Impossibile rimuovere reazione", - "unable_to_reset_password": "Impossibile reimpostare la password", - "unable_to_reset_pin_code": "Impossibile resettare il codice PIN", - "unable_to_resolve_duplicate": "Impossibile risolvere duplicato", - "unable_to_restore_assets": "Impossibile ripristinare le risorse", - "unable_to_restore_trash": "Impossibile ripristinare cestino", - "unable_to_restore_user": "Impossibile ripristinare utente", - "unable_to_save_album": "Impossibile salvare album", - "unable_to_save_api_key": "Impossibile salvare chiave API", - "unable_to_save_date_of_birth": "Impossible salvare la data di nascita", - "unable_to_save_name": "Impossibile salvare il nome", - "unable_to_save_profile": "Impossibile salvare il profilo", - "unable_to_save_settings": "Impossibile salvare le impostazioni", - "unable_to_scan_libraries": "Impossibile analizzare le librerie", - "unable_to_scan_library": "Impossibile analizzare la libreria", - "unable_to_set_feature_photo": "Impossibile impostare la foto in evidenza", - "unable_to_set_profile_picture": "Impossibile impostare la foto profilo", - "unable_to_set_rating": "Impossibile impostare il rating", - "unable_to_submit_job": "Impossibile eseguire l'attività", - "unable_to_trash_asset": "Impossibile cestinare la risorsa", - "unable_to_unlink_account": "Impossibile scollegare l'account", - "unable_to_unlink_motion_video": "Impossibile scollegare video in movimento", - "unable_to_update_album_cover": "Errore durante l'aggiornamento della copertina dell'album", - "unable_to_update_album_info": "Impossibile aggiornare le informazioni sull'album", - "unable_to_update_library": "Impossibile aggiornare la libreria", - "unable_to_update_location": "Impossibile aggiornare la posizione", - "unable_to_update_settings": "Impossibile aggiornare le impostazioni", - "unable_to_update_timeline_display_status": "Impossibile aggiornare lo stato di visualizzazione della sequenza temporale", - "unable_to_update_user": "Impossibile aggiornare l'utente", - "unable_to_update_workflow": "Impossibile aggiornare il workflow", - "unable_to_upload_file": "Impossibile caricare il file" - }, - "errors_text": "Errori", - "exclusion_pattern": "Pattern di esclusione", - "exif": "Exif", - "exif_bottom_sheet_description": "Aggiungi una descrizione...", - "exif_bottom_sheet_description_error": "Errore durante l'aggiornamento della descrizione", - "exif_bottom_sheet_details": "DETTAGLI", - "exif_bottom_sheet_location": "POSIZIONE", - "exif_bottom_sheet_no_description": "Nessuna descrizione", - "exif_bottom_sheet_people": "PERSONE", - "exif_bottom_sheet_person_add_person": "Aggiungi nome", - "exit_slideshow": "Esci dalla presentazione", - "expand_all": "Espandi tutto", - "experimental_settings_new_asset_list_subtitle": "Lavori in corso", - "experimental_settings_new_asset_list_title": "Attiva griglia foto sperimentale", - "experimental_settings_subtitle": "Usalo a tuo rischio!", - "experimental_settings_title": "Sperimentale", - "expire_after": "Scade dopo", - "expired": "Scaduto", - "expires_date": "Scade il {date}", - "explore": "Esplora", - "explorer": "Esplora", - "export": "Esporta", - "export_as_json": "Esporta come JSON", - "export_database": "Esporta database", - "export_database_description": "Esporta il database SQLite", - "extension": "Estensione", - "external": "Esterno", - "external_libraries": "Librerie esterne", - "external_network": "Rete esterna", - "external_network_sheet_info": "Quando non si è connessi alla rete Wi-Fi preferita, l'app si collegherà al server tramite il primo degli indirizzi della lista che riuscirà a raggiungere, dall'alto verso il basso", - "face_unassigned": "Non assegnata", - "failed": "Fallito", - "failed_count": "Falliti: {count}", - "failed_to_authenticate": "Autenticazione non riuscita", - "failed_to_load_assets": "Impossibile caricare le risorse", - "failed_to_load_folder": "Impossibile caricare la cartella", - "favorite": "Preferito", - "favorite_action_prompt": "{count} elementi aggiunti ai preferiti", - "favorite_or_unfavorite_photo": "Aggiungi o rimuovi foto da preferiti", - "favorites": "Preferiti", - "favorites_page_no_favorites": "Nessun preferito", - "feature_photo_updated": "Foto in evidenza aggiornata", - "features": "Funzionalità", - "features_in_development": "Funzionalità in fase di sviluppo", - "features_setting_description": "Gestisci le funzionalità dell'app", - "file_name_or_extension": "Nome file o estensione", - "file_size": "Dimensione del file", - "filename": "Nome file", - "filetype": "Tipo file", - "filter": "Filtro", - "filter_description": "Condizioni per filtrare le risorse obiettivo", - "filter_people": "Filtra persone", - "filter_places": "Filtra luoghi", - "filters": "Filtri", - "find_them_fast": "Trovale velocemente con la ricerca", - "first": "Primo", - "fix_incorrect_match": "Correggi corrispondenza errata", - "folder": "Cartella", - "folder_not_found": "Cartella non trovata", - "folders": "Cartelle", - "folders_feature_description": "Navigare la visualizzazione a cartelle per le foto e i video sul file system", - "forgot_pin_code_question": "Hai dimenticato il tuo PIN?", - "forward": "Avanti", - "free_up_space": "Libera Spazio", - "free_up_space_description": "Sposta le foto e i video del tuo dispositivo nel cestino per liberare spazio. Le copie sul server rimarranno al sicuro.", - "free_up_space_settings_subtitle": "Libera spazio sul dispositivo", - "full_path": "Percorso completo: {path}", - "gcast_enabled": "Google Cast Abilitato", - "gcast_enabled_description": "Questa funzione carica risorse esterne da Google per poter funzionare.", - "general": "Generale", - "geolocation_instruction_location": "Fai clic su una risorsa con coordinate GPS per utilizzare la sua posizione oppure seleziona una posizione direttamente dalla mappa", - "get_help": "Chiedi Aiuto", - "get_people_error": "Errore nel ritrovare le persone", - "get_wifiname_error": "Non sono riuscito a recuperare il nome della rete Wi-Fi. Accertati di aver concesso i permessi necessari e di essere connesso ad una rete Wi-Fi", - "getting_started": "Iniziamo", - "go_back": "Torna indietro", - "go_to_folder": "Vai alla cartella", - "go_to_search": "Vai alla ricerca", - "gps": "GPS", - "gps_missing": "No GPS", - "grant_permission": "Concedi permesso", - "group_albums_by": "Raggruppa album in base a...", - "group_country": "Raggruppa per paese", - "group_no": "Nessun raggruppamento", - "group_owner": "Raggruppa in base al proprietario", - "group_places_by": "Raggruppa posti per...", - "group_year": "Raggruppa per anno", - "haptic_feedback_switch": "Abilita feedback aptico", - "haptic_feedback_title": "Feedback aptico", - "has_quota": "Ha limite", - "hash_asset": "Hash risorsa", - "hashed_assets": "Hash risorse", - "hashing": "Hashing", - "header_settings_add_header_tip": "Aggiungi header", - "header_settings_field_validator_msg": "Il valore non può essere vuoto", - "header_settings_header_name_input": "Nome header", - "header_settings_header_value_input": "Valore header", - "headers_settings_tile_title": "Header proxy personalizzati", - "height": "Altezza", - "hi_user": "Ciao {name} ({email})", - "hide_all_people": "Nascondi tutte le persone", - "hide_gallery": "Nascondi galleria", - "hide_named_person": "Nascondi {name}", - "hide_password": "Nascondi password", - "hide_person": "Nascondi persona", - "hide_schema": "Nascondi schema", - "hide_text_recognition": "Nascondi riconoscimento del testo", - "hide_unnamed_people": "Nascondi persone senza nome", - "home_page_add_to_album_conflicts": "Aggiunte {added} risorse all'album {album}. {failed} risorse erano già presenti nell'album.", - "home_page_add_to_album_err_local": "Non puoi aggiungere all'album risorse non ancora caricate, azione ignorata", - "home_page_add_to_album_success": "Aggiunte {added} risorse all'album {album}.", - "home_page_album_err_partner": "Non puoi ancora aggiungere risorse del partner a un album, azione ignorata", - "home_page_archive_err_local": "Non puoi archiviare risorse non ancora caricate, azione ignorata", - "home_page_archive_err_partner": "Non puoi archiviare risorse del partner, azione ignorata", - "home_page_building_timeline": "Caricamento della timeline", - "home_page_delete_err_partner": "Non puoi eliminare risorse del partner, azione ignorata", - "home_page_delete_remote_err_local": "Risorse locali presenti nella selezione della eliminazione remota, azione ignorata", - "home_page_favorite_err_local": "Non puoi aggiungere ai preferiti le risorse non ancora caricate, azione ignorata", - "home_page_favorite_err_partner": "Non puoi aggiungere le risorse del partner ai preferiti, azione ignorata", - "home_page_first_time_notice": "Se è la prima volta che utilizzi l'app, assicurati di scegliere uno o più album di backup, in modo che la timeline possa popolare le foto e i video presenti negli album", - "home_page_locked_error_local": "Non puoi spostare le risorse locali nella cartella privata, azione ignorata", - "home_page_locked_error_partner": "Non puoi spostare le risorse del partner nella cartella privata, azione ignorata", - "home_page_share_err_local": "Non puoi condividere una risorsa locale tramite link, azione ignorata", - "home_page_upload_err_limit": "Puoi caricare al massimo 30 risorse per volta, azione ignorata", - "host": "Host", - "hour": "Ora", - "hours": "Ore", - "id": "ID", - "idle": "Inattivo", - "ignore_icloud_photos": "Ignora foto iCloud", - "ignore_icloud_photos_description": "Le foto che sono memorizzate su iCloud non verranno caricate sul server Immich", - "image": "Immagine", - "image_alt_text_date": "{isVideo, select, true {Video girato} other {Foto scattata}} il {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video girato} other {Foto scattata}} con {person1} il giorno {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video girato} other {Foto scattata}} con {person1} e {person2} il giorno {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video girato} other {Foto scattata}} con {person1}, {person2}, e {person3} il giorno {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video girato} other {Foto scattata}} con {person1}, {person2}, e altre {additionalCount, number} persone il giorno {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} il giorno {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1} il giorno {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1} e {person2} il giorno {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1}, {person2}, e {person3} il giorno {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1}, {person2} e {additionalCount, number} altre persone il {date}", - "image_saved_successfully": "Immagine salvata", - "image_viewer_page_state_provider_download_started": "Download iniziato", - "image_viewer_page_state_provider_download_success": "Download con successo", - "image_viewer_page_state_provider_share_error": "Errore di condivisione", - "immich_logo": "Logo Immich", - "immich_web_interface": "Interfaccia Web Immich", - "import_from_json": "Importa da JSON", - "import_path": "Importa percorso", - "in_albums": "In {count, plural, one {# album} other {# album}}", - "in_archive": "In archivio", - "in_year": "Nel {year}", - "in_year_selector": "Nel", - "include_archived": "Includi Archiviati", - "include_shared_albums": "Includi album condivisi", - "include_shared_partner_assets": "Includi risorse condivise dai compagni", - "individual_share": "Condivisione individuale", - "individual_shares": "Condivisioni individuali", - "info": "Info", - "interval": { - "day_at_onepm": "Ogni giorno alle 13", - "hours": "Ogni {hours, plural, one {ora} other {{hours, number} ore}}", - "night_at_midnight": "Ogni notte a mezzanotte", - "night_at_twoam": "Ogni notte alle 2" - }, - "invalid_date": "Data invalida", - "invalid_date_format": "Formato data invalido", - "invite_people": "Invita Persone", - "invite_to_album": "Invita nell'album", - "ios_debug_info_fetch_ran_at": "Recupero dati eseguito {dateTime}", - "ios_debug_info_last_sync_at": "Ultima sincronizzazione {dateTime}", - "ios_debug_info_no_processes_queued": "Nessun processo in background in coda", - "ios_debug_info_no_sync_yet": "Nessun processo di sincronizzazione in background è stato ancora avviato", - "ios_debug_info_processes_queued": "{count, plural, one {{count} processo in background messo in coda} other {{count} processi in background messi in coda}}", - "ios_debug_info_processing_ran_at": "Processo eseguito {dateTime}", - "items_count": "{count, plural, one {# elemento} other {# elementi}}", - "jobs": "Processi", - "json_editor": "Modificatore JSON", - "json_error": "JSON errore", - "keep": "Mantieni", - "keep_albums": "Mantieni gli album", - "keep_albums_count": "{count} {count, plural, one {Album} other {Album}} mantenuti", - "keep_all": "Tieni tutto", - "keep_description": "Scegli cosa rimane sul tuo dispositivo quando liberi spazio.", - "keep_favorites": "Mantieni i favoriti", - "keep_on_device": "Mantieni sul dispositivo", - "keep_on_device_hint": "Seleziona le risorse da mantenere sul dispositivo", - "keep_this_delete_others": "Tieni questo, elimina gli altri", - "keeping": "Mantieni: {items}", - "kept_this_deleted_others": "Mantenuto questa risorsa ed {count, plural, one {eliminata # risorsa} other {eliminate # risorse}}", - "keyboard_shortcuts": "Scorciatoie da tastiera", - "language": "Lingua", - "language_no_results_subtitle": "Prova a cambiare i tuoi termini di ricerca", - "language_no_results_title": "Linguaggi non trovati", - "language_search_hint": "Cerca una lingua...", - "language_setting_description": "Seleziona la tua lingua predefinita", - "large_files": "File pesanti", - "last": "Ultimo", - "last_months": "{count, plural, one {Ultimo mese} other {Ultimi # mesi}}", - "last_seen": "Ultimo accesso", - "latest_version": "Ultima Versione", - "latitude": "Latitudine", - "leave": "Esci", - "leave_album": "Esci dall’album", - "lens_model": "Modello lenti", - "let_others_respond": "Permetti agli altri di rispondere", - "level": "Livello", - "library": "Libreria", - "library_add_folder": "Aggiungi cartella", - "library_edit_folder": "Modifica cartella", - "library_options": "Impostazioni Libreria", - "library_page_device_albums": "Album sul dispositivo", - "library_page_new_album": "Nuovo Album", - "library_page_sort_asset_count": "Numero di risorse", - "library_page_sort_created": "Data di creazione", - "library_page_sort_last_modified": "Ultima modifica", - "library_page_sort_title": "Titolo album", - "licenses": "Licenze", - "light": "Chiaro", - "like": "Mi piace", - "like_deleted": "Mi piace rimosso", - "link_motion_video": "Collega video in movimento", - "link_to_oauth": "Collegamento a OAuth", - "linked_oauth_account": "Account OAuth collegato", - "list": "Lista", - "loading": "Caricamento", - "loading_search_results_failed": "Impossibile caricare i risultati della ricerca", - "local": "Locale", - "local_asset_cast_failed": "Impossibile trasmettere una risorsa che non è caricata sul server", - "local_assets": "Risorsa locale", - "local_id": "ID Locale", - "local_media_summary": "Riepilogo dei Media Locali", - "local_network": "Rete locale", - "local_network_sheet_info": "L'app si collegherà al server tramite questo URL quando è in uso la rete Wi-Fi specificata", - "location": "Posizione", - "location_permission": "Permesso di localizzazione", - "location_permission_content": "Per usare la funzione di cambio automatico, Immich necessita del permesso di localizzazione così da poter leggere il nome della rete Wi-Fi in uso", - "location_picker_choose_on_map": "Scegli una mappa", - "location_picker_latitude_error": "Inserisci una latitudine valida", - "location_picker_latitude_hint": "Inserisci la tua latitudine qui", - "location_picker_longitude_error": "Inserisci una longitudine valida", - "location_picker_longitude_hint": "Inserisci la longitudine qui", - "lock": "Rendi privato", - "locked_folder": "Cartella Privata", - "log_detail_title": "Dettaglio dei Log", - "log_out": "Esci", - "log_out_all_devices": "Disconnetti tutti i dispositivi", - "logged_in_as": "Effettuato l'accesso come {user}", - "logged_out_all_devices": "Disconnesso da tutti i dispositivi", - "logged_out_device": "Disconnesso dal dispositivo", - "login": "Accesso", - "login_disabled": "L'accesso è stato disattivato", - "login_form_api_exception": "API error, per favore ricontrolli URL del server e riprovi.", - "login_form_back_button_text": "Indietro", - "login_form_email_hint": "tuaemail@email.com", - "login_form_endpoint_hint": "http://ip-del-tuo-server:port", - "login_form_endpoint_url": "URL dell'Endpoint del Server", - "login_form_err_http": "Per favore specificare http:// o https://", - "login_form_err_invalid_email": "Email non valida", - "login_form_err_invalid_url": "URL non valido", - "login_form_err_leading_whitespace": "Whitespace all'inizio", - "login_form_err_trailing_whitespace": "Whitespace alla fine", - "login_form_failed_get_oauth_server_config": "Errore di login usando OAuth, controlla l'URL del server", - "login_form_failed_get_oauth_server_disable": "OAuth non è disponibile su questo server", - "login_form_failed_login": "Errore nel login, controlla URL del server e le credenziali (email e password)", - "login_form_handshake_exception": "Si è verificata un'eccezione di handshake con il server. Abilita il supporto del certificato self-signed nelle impostazioni se si utilizza questo tipo di certificato.", - "login_form_password_hint": "password", - "login_form_save_login": "Rimani connesso", - "login_form_server_empty": "Inserisci URL del server.", - "login_form_server_error": "Non è possibile connettersi al server.", - "login_has_been_disabled": "Il login è stato disabilitato.", - "login_password_changed_error": "C'è stato un errore durante l'aggiornamento della password", - "login_password_changed_success": "Password aggiornata con successo", - "logout_all_device_confirmation": "Sei sicuro di volerti disconnettere da tutti i dispositivi?", - "logout_this_device_confirmation": "Sei sicuro di volerti disconnettere da questo dispositivo?", - "logs": "Logs", - "longitude": "Longitudine", - "look": "Guarda", - "loop_videos": "Riproduci video in loop", - "loop_videos_description": "Abilita per riprodurre automaticamente un video in loop nel visualizzatore dei dettagli.", - "main_branch_warning": "Stai utilizzando una versione di sviluppo. Ti consigliamo vivamente di utilizzare una versione di rilascio!", - "main_menu": "Menu Principale", - "maintenance_action_restore": "Ripristinando Database", - "maintenance_description": "Immich è stato posto in modalità manutenzione.", - "maintenance_end": "Termina modalità manutenzione", - "maintenance_end_error": "Errore nel terminare la modalità manutenzione.", - "maintenance_logged_in_as": "Accesso effettuato come {user}", - "maintenance_restore_from_backup": "Ripristina da Backup", - "maintenance_restore_library": "Ripristina la tua Libreria", - "maintenance_restore_library_confirm": "Se questo sembra corretto, procedi al ripristino del backup!", - "maintenance_restore_library_description": "Ripristinando Database", - "maintenance_restore_library_folder_has_files": "{folder} contiene {count} cartelle", - "maintenance_restore_library_folder_no_files": "File mancanti in {folder}!", - "maintenance_restore_library_folder_pass": "leggibile e scrivibile", - "maintenance_restore_library_folder_read_fail": "illeggibile", - "maintenance_restore_library_folder_write_fail": "non scrivibile", - "maintenance_restore_library_hint_missing_files": "Potrebbero mancarti file importanti", - "maintenance_restore_library_hint_regenerate_later": "Puoi rigenerarli più tardi dalle impostazioni", - "maintenance_restore_library_hint_storage_template_missing_files": "Stai usando un modello di archiviazione? Potrebbero mancarti dei file", - "maintenance_restore_library_loading": "Caricamento controlli di integrità ed euristiche…", - "maintenance_task_backup": "Creando un backup del database esistente…", - "maintenance_task_migrations": "Esecuzione delle migrazioni del database…", - "maintenance_task_restore": "Ripristinando il backup scelto…", - "maintenance_task_rollback": "Ripristino fallito, tornando al punto di ripristino…", - "maintenance_title": "Temporaneamente non disponibile", - "make": "Produttore", - "manage_geolocation": "Gestisci posizione", - "manage_media_access_rationale": "Questo permesso è richiesto per gestire correttamente lo spostamento del materiale nel cestino e per recuperarlo da esso.", - "manage_media_access_settings": "Apri impostazioni", - "manage_media_access_subtitle": "Permetti all'app di Immich di gestire e spostare file multimediali.", - "manage_media_access_title": "Accesso alla gestione di contenuti multimediali", - "manage_shared_links": "Gestisci link condivisi", - "manage_sharing_with_partners": "Gestisci la condivisione con i compagni", - "manage_the_app_settings": "Gestisci le impostazioni dell'applicazione", - "manage_your_account": "Gestisci il tuo account", - "manage_your_api_keys": "Gestisci le tue chiavi API", - "manage_your_devices": "Gestisci i tuoi dispositivi collegati", - "manage_your_oauth_connection": "Gestisci la tua connessione OAuth", - "map": "Mappa", - "map_assets_in_bounds": "{count, plural, =0 {Nessuna risorsa in quest’area} one {# risorsa} other {# risorse}}", - "map_cannot_get_user_location": "Non è possibile ottenere la posizione dell'utente", - "map_location_dialog_yes": "Si", - "map_location_picker_page_use_location": "Usa questa posizione", - "map_location_service_disabled_content": "I servizi di geolocalizzazione devono essere attivati per poter visualizzare le risorse dalla tua posizione attuale. Vuoi attivarli adesso?", - "map_location_service_disabled_title": "Servizio Localizzazione disattivato", - "map_marker_for_images": "Indicatore mappa per le immagini scattate in {city}, {country}", - "map_marker_with_image": "Segnaposto con immagine", - "map_no_location_permission_content": "L'accesso alla posizione è necessario per visualizzare le risorse dalla tua posizione attuale. Vuoi consentirlo adesso?", - "map_no_location_permission_title": "Autorizzazione Posizione negata", - "map_settings": "Impostazioni Mappa", - "map_settings_dark_mode": "Modalità scura", - "map_settings_date_range_option_day": "Ultime 24 ore", - "map_settings_date_range_option_days": "Ultimi {days} giorni", - "map_settings_date_range_option_year": "Ultimo anno", - "map_settings_date_range_option_years": "Ultimi {years} anni", - "map_settings_dialog_title": "Impostazioni Mappa", - "map_settings_include_show_archived": "Includi Archiviati", - "map_settings_include_show_partners": "Includi Partner", - "map_settings_only_show_favorites": "Mostra solo preferiti", - "map_settings_theme_settings": "Tema della mappa", - "map_zoom_to_see_photos": "Riduci lo zoom per vedere le foto", - "mark_all_as_read": "Segna tutto come letto", - "mark_as_read": "Segna come letto", - "marked_all_as_read": "Segnato tutto come letto", - "matches": "Corrispondenze", - "matching_assets": "Risorse Corrispondenti", - "media_type": "Tipo Media", - "memories": "Ricordi", - "memories_all_caught_up": "Tutto a posto", - "memories_check_back_tomorrow": "Torna domani per altri ricordi", - "memories_setting_description": "Gestisci cosa vedi nei tuoi ricordi", - "memories_start_over": "Ricomincia", - "memories_swipe_to_close": "Scorri sopra per chiudere", - "memory": "Memoria", - "memory_lane_title": "Sentiero dei Ricordi {title}", - "menu": "Menù", - "merge": "Unisci", - "merge_people": "Unisci persone", - "merge_people_limit": "Puoi unire al massimo 5 volti alla volta", - "merge_people_prompt": "Vuoi unire queste persone? Questa azione è irreversibile.", - "merge_people_successfully": "Unione persone completata con successo", - "merged_people_count": "{count, plural, one {Unita # persona} other {Unite # persone}}", - "minimize": "Minimizza", - "minute": "Minuto", - "minutes": "Minuti", - "mirror_horizontal": "Orizzontale", - "mirror_vertical": "Verticale", - "missing": "Mancanti", - "mobile_app": "App Cellulare", - "mobile_app_download_onboarding_note": "Scarica l’app mobile dedicata utilizzando una delle seguenti opzioni", - "model": "Modello", - "month": "Mese", - "monthly_title_text_date_format": "MMMM y", - "more": "Di più", - "move": "Sposta", - "move_down": "Muovi in basso", - "move_off_locked_folder": "Sposta al di fuori della cartella privata", - "move_to": "Sposta in", - "move_to_device_trash": "Sposta nel cestino del dispositivo", - "move_to_lock_folder_action_prompt": "{count} elementi aggiunti alla cartella sicura", - "move_to_locked_folder": "Sposta nella cartella privata", - "move_to_locked_folder_confirmation": "Queste foto e video verranno rimossi da tutti gli album, e saranno visibili solo dalla cartella privata", - "move_up": "Muovi in alto", - "moved_to_archive": "{count, plural, one {Spostata # risorsa} other {Spostate # risorse}} nell'archivio", - "moved_to_library": "{count, plural, one {Spostata # risorsa} other {Spostate # risorse}} nella libreria", - "moved_to_trash": "Spostato nel cestino", - "multiselect_grid_edit_date_time_err_read_only": "Non puoi modificare la data di risorse in sola lettura, azione ignorata", - "multiselect_grid_edit_gps_err_read_only": "Non puoi modificare la posizione di risorse in sola lettura, azione ignorata", - "mute_memories": "Silenzia ricordi", - "my_albums": "I miei album", - "name": "Nome", - "name_or_nickname": "Nome o soprannome", - "name_required": "Nome è richiesto", - "navigate": "Naviga", - "navigate_to_time": "Navigazione alla data", - "network_requirement_photos_upload": "Utilizza la connessione dati per il backup delle foto", - "network_requirement_videos_upload": "Utilizza la connessione dati per il backup dei video", - "network_requirements": "Requisiti di rete", - "network_requirements_updated": "Requisiti di rete modificati, coda di backup reimpostata", - "networking_settings": "Rete", - "networking_subtitle": "Gestisci le impostazioni riguardanti gli endpoint del server", - "never": "Mai", - "new_album": "Nuovo Album", - "new_api_key": "Nuova Chiave di API", - "new_date_range": "Nuovo intervallo di date", - "new_password": "Nuova password", - "new_person": "Nuova persona", - "new_pin_code": "Nuovo codice PIN", - "new_pin_code_subtitle": "Questa è la prima volta che accedi alla cartella privata. Crea un codice PIN per accedere in modo sicuro a questa pagina", - "new_timeline": "Nuova Timeline", - "new_update": "Nuovo aggiornamento", - "new_user_created": "Nuovo utente creato", - "new_version_available": "NUOVA VERSIONE DISPONIBILE", - "newest_first": "Prima recenti", - "next": "Prossimo", - "next_memory": "Prossima memoria", - "no": "No", - "no_actions_added": "Nessuna azione è stata ancora aggiunta", - "no_albums_found": "Nessun album trovato", - "no_albums_message": "Crea un album per organizzare le tue foto ed i tuoi video", - "no_albums_with_name_yet": "Sembra che tu non abbia ancora nessun album con questo nome.", - "no_albums_yet": "Sembra che tu non abbia ancora nessun album.", - "no_archived_assets_message": "Archivia foto e video per nasconderli dalla visualizzazione galleria", - "no_assets_message": "Clicca per caricare la tua prima foto", - "no_assets_to_show": "Nessuna risorsa da mostrare", - "no_cast_devices_found": "Nessun dispositivo di trasmissione trovato", - "no_checksum_local": "Nessun checksum disponibile: impossibile recuperare le risorse locali", - "no_checksum_remote": "Nessun checksum disponibile: impossibile recuperare la risorsa remota", - "no_configuration_needed": "Nessuna configurazione è necessaria", - "no_devices": "Nessun device autorizzato", - "no_duplicates_found": "Nessun duplicato trovato.", - "no_exif_info_available": "Nessuna informazione exif disponibile", - "no_explore_results_message": "Carica più foto per esplorare la tua collezione.", - "no_favorites_message": "Aggiungi preferiti per trovare facilmente le tue migliori foto e video", - "no_filters_added": "Nessun filtro ancora aggiunto", - "no_libraries_message": "Crea una libreria esterna per vedere le tue foto e i tuoi video", - "no_local_assets_found": "Nessuna risorsa locale trovata con questo checksum", - "no_location_set": "Nessuna posizione impostata", - "no_locked_photos_message": "Le foto e i video nella cartella privata sono nascosti e non vengono visualizzati mentre navighi o cerchi nella tua libreria.", - "no_name": "Nessun nome", - "no_notifications": "Nessuna notifica", - "no_people_found": "Nessuna persona trovata", - "no_places": "Nessun posto", - "no_remote_assets_found": "Nessuna risorsa remota trovata con questo checksum", - "no_results": "Nessun risultato", - "no_results_description": "Prova ad usare un sinonimo oppure una parola chiave più generica", - "no_shared_albums_message": "Crea un album per condividere foto e video con le persone nella tua rete", - "no_uploads_in_progress": "Nessun upload in corso", - "none": "Nulla", - "not_allowed": "Non permesso", - "not_available": "N/A", - "not_in_any_album": "In nessun album", - "not_selected": "Non selezionato", - "note_apply_storage_label_to_previously_uploaded assets": "Nota: Per aggiungere l'etichetta dell'archiviazione alle risorse caricate in precedenza, esegui", - "notes": "Note", - "nothing_here_yet": "Ancora nulla qui", - "notification_permission_dialog_content": "Per attivare le notifiche, vai alle Impostazioni e seleziona concedi.", - "notification_permission_list_tile_content": "Concedi i permessi per attivare le notifiche.", - "notification_permission_list_tile_enable_button": "Attiva notifiche", - "notification_permission_list_tile_title": "Permessi delle Notifiche", - "notification_toggle_setting_description": "Attiva le notifiche via email", - "notifications": "Notifiche", - "notifications_setting_description": "Gestisci notifiche", - "oauth": "OAuth", - "obtainium_configurator": "Configuratore Obtainium", - "obtainium_configurator_instructions": "Utilizza Obtainium per installare e aggiornare l'app Android direttamente dalla versione rilasciata su GitHub da Immich. Crea una chiave API e seleziona una variante per creare il tuo link di configurazione Obtainium", - "ocr": "OCR", - "official_immich_resources": "Risorse Ufficiali Immich", - "offline": "Offline", - "offset": "Offset", - "ok": "Ok", - "oldest_first": "Prima vecchi", - "on_this_device": "Su questo dispositivo", - "onboarding": "Inserimento", - "onboarding_locale_description": "Seleziona il tuo linguaggio di preferenza. Puoi cambiarlo successivamente nelle impostazioni.", - "onboarding_privacy_description": "Le seguenti funzioni (opzionali) fanno uso di servizi esterni, e possono essere disabilitate in qualsiasi momento nelle impostazioni.", - "onboarding_server_welcome_description": "Configuriamo la tua istanza con alcune impostazioni standard.", - "onboarding_theme_description": "Scegli un tema colore per la tua istanza. Potrai cambiarlo nelle impostazioni.", - "onboarding_user_welcome_description": "Iniziamo!", - "onboarding_welcome_user": "Benvenuto, {user}", - "online": "Online", - "only_favorites": "Solo preferiti", - "open": "Apri", - "open_in_map_view": "Apri nella visualizzazione mappa", - "open_in_openstreetmap": "Apri su OpenStreetMap", - "open_the_search_filters": "Apri filtri di ricerca", - "options": "Opzioni", - "or": "o", - "organize_into_albums": "Organizza all'interno degli albums", - "organize_into_albums_description": "Inserisci le foto esistenti all'interno degli albums utilizzando le attuale impostazioni di sincronizzazione", - "organize_your_library": "Organizza la tua libreria", - "original": "originale", - "other": "Altro", - "other_devices": "Altri dispositivi", - "other_entities": "Altre entità", - "other_variables": "Altre variabili", - "owned": "Posseduti", - "owner": "Proprietario", - "page": "Pagina", - "partner": "Partner", - "partner_can_access": "{partner} può accedere", - "partner_can_access_assets": "Tutte le tue foto e i tuoi video eccetto quelli Archiviati e Cancellati", - "partner_can_access_location": "La posizione dove è stata scattata la foto", - "partner_list_user_photos": "Foto di {user}", - "partner_list_view_all": "Mostra tutto", - "partner_page_empty_message": "Le tue foto non sono ancora condivise con alcun partner.", - "partner_page_no_more_users": "Nessun altro utente da aggiungere", - "partner_page_partner_add_failed": "Aggiunta del partner non riuscita", - "partner_page_select_partner": "Seleziona partner", - "partner_page_shared_to_title": "Condividi con", - "partner_page_stop_sharing_content": "{partner} non sarà più in grado di accedere alle tue foto.", - "partner_sharing": "Condivisione Compagno", - "partners": "Compagni", - "password": "Password", - "password_does_not_match": "Le password non coincidono", - "password_required": "Password Richiesta", - "password_reset_success": "Ripristino password avvenuto con successo", - "past_durations": { - "days": "{days, plural, one {Ultimo giorno} other {Ultimi # giorni}}", - "hours": "{hours, plural, one {Ultima ora} other {Ultime # ore}}", - "years": "{years, plural, one {Ultimo anno} other {Ultimi # anni}}" - }, - "path": "Percorso", - "pattern": "Modello", - "pause": "Pausa", - "pause_memories": "Pausa ricordi", - "paused": "In pausa", - "pending": "In attesa", - "people": "Persone", - "people_edits_count": "{count, plural, one {Modificata # persona} other {Modificate # persone}}", - "people_feature_description": "Navigare foto e video raggruppati da persone", - "people_selected": "{count, plural, one {# persona selezionata} other {# persone selezionate}}", - "people_sidebar_description": "Mostra un link alle persone nella barra laterale", - "permanent_deletion_warning": "Avviso eliminazione permanente", - "permanent_deletion_warning_setting_description": "Mostra un avviso all'eliminazione definitiva di una risorsa", - "permanently_delete": "Elimina definitivamente", - "permanently_delete_assets_count": "Cancella definitivamente {count, plural, one {la risorsa} other {le risorse}}", - "permanently_delete_assets_prompt": "Sei sicuro di voler cancellare definitivamente {count, plural, one {questa risorsa?} other {# risorse?}} Questa operazione {count, plural, one {la cancellerà dal suo} other {le cancellerà dai loro}} album.", - "permanently_deleted_asset": "Risorsa eliminata definitivamente", - "permanently_deleted_assets_count": "{count, plural, one {Cancellata # risorsa} other {Cancellate # risorse}} definitivamente", - "permission": "Autorizzazione", - "permission_empty": "La tua autorizzazione non può essere vuota", - "permission_onboarding_back": "Indietro", - "permission_onboarding_continue_anyway": "Continua lo stesso", - "permission_onboarding_get_started": "Inizia", - "permission_onboarding_go_to_settings": "Vai a Impostazioni", - "permission_onboarding_permission_denied": "Permessi negati. Per usare Immich concedi i permessi ai video e foto dalle impostazioni.", - "permission_onboarding_permission_granted": "Concessi i permessi! Ora sei tutto apposto.", - "permission_onboarding_permission_limited": "Permessi limitati. Per consentire a Immich di gestire e fare i backup di tutta la galleria, concedi i permessi Foto e Video dalle Impostazioni.", - "permission_onboarding_request": "Immich richiede i permessi per vedere le tue foto e video.", - "person": "Persona", - "person_age_months": "{months, plural, one {# mese} other {# mesi}}", - "person_age_year_months": "1 anno e {months, plural, one {# mese} other {# mesi}}", - "person_age_years": "{years, plural, one {# anno} other {# anni}}", - "person_birthdate": "Nato il {date}", - "person_hidden": "{name}{hidden, select, true { (nascosto)} other {}}", - "person_recognized": "Persona riconosciuta", - "person_selected": "Persona selezionata", - "photo_shared_all_users": "Sembra che tu abbia condiviso le tue foto con tutti gli utenti, oppure che tu non abbia alcun utente con cui condividerle.", - "photos": "Foto", - "photos_and_videos": "Foto & Video", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Foto}}", - "photos_from_previous_years": "Foto dagli anni scorsi", - "photos_only": "Solo foto", - "pick_a_location": "Scegli una posizione", - "pick_custom_range": "Intervallo personalizzato", - "pick_date_range": "Seleziona un periodo temporale", - "pin_code_changed_successfully": "Codice PIN cambiato correttamente", - "pin_code_reset_successfully": "Codice PIN resettato con successo", - "pin_code_setup_successfully": "Codice PIN impostato correttamente", - "pin_verification": "Verifica del codice PIN", - "place": "Luogo", - "places": "Luoghi", - "places_count": "{count, plural, one {{count, number} Posizione} other {{count, number} Posizioni}}", - "play": "Riproduci", - "play_memories": "Riproduci ricordi", - "play_motion_photo": "Riproduci foto in movimento", - "play_or_pause_video": "Avvia o metti in pausa il video", - "play_original_video": "Riproduci il video originale", - "play_original_video_setting_description": "Preferisci la riproduzione dei video originali anzichè ricodificarli. Se l'originale non è compatibile non sarà riprodotto correttamente.", - "play_transcoded_video": "Riproduci video ricodificato", - "please_auth_to_access": "Autenticati per accedere", - "port": "Porta", - "preferences_settings_subtitle": "Gestisci le preferenze dell'app", - "preferences_settings_title": "Preferenze", - "preparing": "Preparando", - "preset": "Preimpostazione", - "preview": "Anteprima", - "previous": "Precedente", - "previous_memory": "Ricordo precedente", - "previous_or_next_day": "Giorno avanti/indietro", - "previous_or_next_month": "Mese successivo/precedente", - "previous_or_next_photo": "Foto successiva/precedente", - "previous_or_next_year": "Anno successivo/precedente", - "primary": "Primario", - "privacy": "Privacy", - "profile": "Profilo", - "profile_drawer_app_logs": "Registri", - "profile_drawer_client_server_up_to_date": "Client e server sono aggiornati", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Modalità di sola lettura abilitata. Tieni premuto sull'avatar dell'utente per disabilitarla.", - "profile_image_of_user": "Immagine profilo di {user}", - "profile_picture_set": "Foto profilo impostata.", - "public_album": "Album pubblico", - "public_share": "Condivisione Pubblica", - "purchase_account_info": "Contributore", - "purchase_activated_subtitle": "Grazie per supportare Immich e il software open source", - "purchase_activated_time": "Attivato il {date}", - "purchase_activated_title": "La tua chiave è stata attivata con successo", - "purchase_button_activate": "Attiva", - "purchase_button_buy": "Acquista", - "purchase_button_buy_immich": "Acquista Immich", - "purchase_button_never_show_again": "Non mostrare più", - "purchase_button_reminder": "Ricordamelo tra 30 giorni", - "purchase_button_remove_key": "Rimuovi chiave", - "purchase_button_select": "Seleziona", - "purchase_failed_activation": "Attivazione fallita! Controlla la tua email per la chiave prodotto corretta!", - "purchase_individual_description_1": "Per un individuo", - "purchase_individual_description_2": "Stato di Sostenitore", - "purchase_individual_title": "Individuale", - "purchase_input_suggestion": "Hai una chiave prodotto? Inseriscila qui sotto", - "purchase_license_subtitle": "Acquista Immich per supportare lo sviluppo continuo del servizio", - "purchase_lifetime_description": "Acquisto a vita", - "purchase_option_title": "OPZIONI DI ACQUISTO", - "purchase_panel_info_1": "Sviluppare Immich richiede molto tempo e impegno, e abbiamo ingegneri a tempo pieno che lavorano per renderlo il migliore possibile. La nostra missione è fare in modo che il software open source e pratiche commerciali etiche diventino una fonte di reddito sostenibile per gli sviluppatori, creando al contempo un ecosistema che rispetti la privacy, offrendo vere alternative ai servizi cloud sfruttatori.", - "purchase_panel_info_2": "Poiché ci impegniamo a non aggiungere paywall, questo acquisto non ti garantirà funzionalità aggiuntive in Immich. Contiamo su utenti come te per supportare lo sviluppo continuo di Immich.", - "purchase_panel_title": "Contribuisci al progetto", - "purchase_per_server": "Per server", - "purchase_per_user": "Per utente", - "purchase_remove_product_key": "Rimuovi la Chiave Prodotto", - "purchase_remove_product_key_prompt": "Sei sicuro di voler rimuovere la chiave prodotto?", - "purchase_remove_server_product_key": "Rimuovi la chiave del prodotto per Server", - "purchase_remove_server_product_key_prompt": "Sei sicuro di voler rimuovere la chiave del prodotto per Server?", - "purchase_server_description_1": "Per l'intero server", - "purchase_server_description_2": "Stato di Contributore", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "La chiave del prodotto del server è gestita dall'amministratore", - "query_asset_id": "Esegui una query sull'ID della risorsa", - "queue_status": "Messi in coda {count}/{total}", - "rate_asset": "Valuta la risorsa", - "rating": "Valutazione a stelle", - "rating_clear": "Azzera valutazione", - "rating_count": "{count, plural, one {# stella} other {# stelle}}", - "rating_description": "Visualizza la valutazione EXIF nel pannello informazioni", - "rating_set": "Valutazione impostata a {rating, plural, one {# stella} other {# stelle}}", - "reaction_options": "Impostazioni Reazioni", - "read_changelog": "Leggi Riepilogo Modifiche", - "readonly_mode_disabled": "Modalità di sola lettura disabilitata", - "readonly_mode_enabled": "Modalità di sola lettura abilitata", - "ready_for_upload": "Pronto per il caricamento", - "reassign": "Riassegna", - "reassigned_assets_to_existing_person": "{count, plural, one {Riassegnata # risorsa} other {Riassegnate # risorse}} {name, select, null {ad una persona esistente} other {a {name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {Riassegnata # risorsa} other {Riassegnate # risorse}} ad una nuova persona", - "reassing_hint": "Assegna le risorse selezionate ad una persona esistente", - "recent": "Recenti", - "recent-albums": "Album recenti", - "recent_searches": "Ricerche recenti", - "recently_added": "Aggiunti recentemente", - "recently_added_page_title": "Aggiunti di recente", - "recently_taken": "Scattate di recente", - "recently_taken_page_title": "Scattate di Recente", - "refresh": "Ricarica", - "refresh_encoded_videos": "Ricarica video codificati", - "refresh_faces": "Aggiorna volti", - "refresh_metadata": "Ricarica metadati", - "refresh_thumbnails": "Ricarica anteprime", - "refreshed": "Aggiornato", - "refreshes_every_file": "Rilegge tutti i file esistenti e nuovi", - "refreshing_encoded_video": "Ricaricando il video codificato", - "refreshing_faces": "Aggiornando volti", - "refreshing_metadata": "Ricaricando i metadati", - "regenerating_thumbnails": "Rigenerando le anteprime", - "remote": "Remoto", - "remote_assets": "Risorse remote", - "remote_media_summary": "Riepilogo dei Media Remoti", - "remove": "Rimuovi", - "remove_assets_album_confirmation": "Sei sicuro di voler rimuovere {count, plural, one {# risorsa} other {# risorse}} dall'album?", - "remove_assets_shared_link_confirmation": "Sei sicuro di voler rimuovere {count, plural, one {# risorsa} other {# risorse}} da questo link condiviso?", - "remove_assets_title": "Rimuovo le risorse?", - "remove_custom_date_range": "Rimuovi intervallo data personalizzato", - "remove_deleted_assets": "Rimuovi le Risorse cancellate", - "remove_from_album": "Rimuovere dall'album", - "remove_from_album_action_prompt": "{count} elementi rimossi dall'album", - "remove_from_favorites": "Rimuovi dai preferiti", - "remove_from_lock_folder_action_prompt": "{count} elementi rimossi dalla cartella sicura", - "remove_from_locked_folder": "Rimuovi dalla cartella privata", - "remove_from_locked_folder_confirmation": "Sei sicuro di voler spostare queste foto e questi video dalla cartella privata? Diventeranno visibili nella vostra libreria.", - "remove_from_shared_link": "Rimuovi dal link condiviso", - "remove_memory": "Rimuovi ricordo", - "remove_photo_from_memory": "Rimuovi foto da questo ricordo", - "remove_tag": "Rimuovi tag", - "remove_url": "Rimuovi URL", - "remove_user": "Rimuovi utente", - "removed_api_key": "Rimossa chiave API: {name}", - "removed_from_archive": "Rimosso dall'archivio", - "removed_from_favorites": "Rimosso dai preferiti", - "removed_from_favorites_count": "{count, plural, one {Rimosso } other {Rimossi #}} dai preferiti", - "removed_memory": "Memoria rimossa", - "removed_photo_from_memory": "Foto rimossa dalla memoria", - "removed_tagged_assets": "Rimossa etichetta {count, plural, one {# dalla risorsa} other {# dalle risorse}}", - "rename": "Rinomina", - "repair": "Ripara", - "repair_no_results_message": "I file mancanti e non tracciati saranno mostrati qui", - "replace_with_upload": "Rimpiazza con upload", - "repository": "Repository", - "require_password": "Richiedi password", - "require_user_to_change_password_on_first_login": "Richiedi all'utente di cambiare password al primo accesso", - "rescan": "Scansiona nuovamente", - "reset": "Ripristina", - "reset_password": "Ripristina password", - "reset_people_visibility": "Ripristina visibilità persone", - "reset_pin_code": "Resetta il codice PIN", - "reset_pin_code_description": "Se hai dimenticato il codice PIN, puoi contattare l’amministratore del server per reimpostarlo", - "reset_pin_code_success": "Codice PIN reimpostato con successo", - "reset_pin_code_with_password": "Puoi sempre reimpostare il codice PIN usando la tua password", - "reset_sqlite": "Resetta Database SQLite", - "reset_sqlite_confirmation": "Vuoi davvero reimpostare il database SQLite? Dovrai disconnetterti e riconnetterti per risincronizzare i dati", - "reset_sqlite_success": "Database SQLite reimpostato correttamente", - "reset_to_default": "Ripristina i valori predefiniti", - "resolution": "Risoluzione", - "resolve_duplicates": "Risolvi duplicati", - "resolved_all_duplicates": "Tutti i duplicati sono stati risolti", - "restore": "Ripristina", - "restore_all": "Ripristina tutto", - "restore_trash_action_prompt": "{count} ripristinati dal cestino", - "restore_user": "Ripristina utente", - "restored_asset": "Risorsa ripristinata", - "resume": "Riprendi", - "resume_paused_jobs": "Riprendi {count, plural, one {# processo in pausa} other {# i processi in pausa}}", - "retry_upload": "Riprova caricamento", - "review_duplicates": "Esamina duplicati", - "review_large_files": "Revisiona file pesanti", - "role": "Ruolo", - "role_editor": "Editor", - "role_viewer": "Visualizzatore", - "running": "In esecuzione", - "save": "Salva", - "save_to_gallery": "Salva in galleria", - "saved": "Salvato", - "saved_api_key": "Chiave API salvata", - "saved_profile": "Profilo salvato", - "saved_settings": "Impostazioni salvate", - "say_something": "Dici qualcosa", - "scaffold_body_error_occurred": "Si è verificato un errore", - "scan": "Scansione", - "scan_all_libraries": "Analizza tutte le librerie", - "scan_library": "Scansione", - "scan_settings": "Impostazioni Analisi", - "scanning": "Scansione in corso", - "scanning_for_album": "Sto cercando l'album...", - "search": "Cerca", - "search_albums": "Cerca album", - "search_by_context": "Cerca con contesto", - "search_by_description": "Ricerca per descrizione", - "search_by_description_example": "Giornata di escursioni a Sapa", - "search_by_filename": "Cerca per nome del file o estensione", - "search_by_filename_example": "es. IMG_1234.JPG o PNG", - "search_by_ocr": "Ricerca tramite OCR", - "search_by_ocr_example": "Caffè Latte", - "search_camera_lens_model": "Cerca il modello del'obiettivo...", - "search_camera_make": "Cerca produttore fotocamera...", - "search_camera_model": "Cerca modello fotocamera...", - "search_city": "Cerca città...", - "search_country": "Cerca paese...", - "search_filter_apply": "Applica filtro", - "search_filter_camera_title": "Seleziona il tipo di camera", - "search_filter_date": "Data", - "search_filter_date_interval": "{start} a {end}", - "search_filter_date_title": "Scegli un range di date", - "search_filter_display_option_not_in_album": "Non nell'album", - "search_filter_display_options": "Opzioni di Visualizzazione", - "search_filter_filename": "Cerca per nome file", - "search_filter_location": "Posizione", - "search_filter_location_title": "Seleziona posizione", - "search_filter_media_type": "Tipo di media", - "search_filter_media_type_title": "Seleziona il tipo di media", - "search_filter_ocr": "Cerca tramite OCR", - "search_filter_people_title": "Seleziona persone", - "search_filter_star_rating": "Voto in Stelle", - "search_for": "Cerca per", - "search_for_existing_person": "Cerca per persona esistente", - "search_no_more_result": "Non ci sono altri risultati", - "search_no_people": "Nessuna persona", - "search_no_people_named": "Nessuna persona chiamate \"{name}\"", - "search_no_result": "Nessun risultato trovato, prova con un termine o combinazione diversi", - "search_options": "Opzioni di ricerca", - "search_page_categories": "Categoria", - "search_page_motion_photos": "Foto in movimento", - "search_page_no_objects": "Nessuna informazione sugli oggetti disponibile", - "search_page_no_places": "Nessuna informazione sui luoghi disponibile", - "search_page_screenshots": "Screenshot", - "search_page_search_photos_videos": "Ricerca le tue foto e i tuoi video", - "search_page_selfies": "Selfie", - "search_page_things": "Oggetti", - "search_page_view_all_button": "Guarda tutto", - "search_page_your_activity": "Le tua attività", - "search_page_your_map": "La tua mappa", - "search_people": "Cerca persone", - "search_places": "Cerca luoghi", - "search_rating": "Cerca per valutazione...", - "search_result_page_new_search_hint": "Nuova ricerca", - "search_settings": "Cerca Impostazioni", - "search_state": "Cerca stato...", - "search_suggestion_list_smart_search_hint_1": "Ricerca Smart è attiva di default, per usare la ricerca con i metadata usare la seguente sintassi ", - "search_suggestion_list_smart_search_hint_2": "m:termine-di-ricerca", - "search_tags": "Cerca tag...", - "search_timezone": "Cerca fuso orario...", - "search_type": "Cerca tipo", - "search_your_photos": "Cerca le tue foto", - "searching_locales": "Cerca localizzazioni...", - "second": "Secondo", - "see_all_people": "Vedi tutte le persone", - "select": "Seleziona", - "select_album": "Seleziona album", - "select_album_cover": "Seleziona copertina album", - "select_albums": "Seleziona gli album", - "select_all": "Seleziona tutto", - "select_all_duplicates": "Seleziona tutti i duplicati", - "select_all_in": "Seleziona tutto in {group}", - "select_avatar_color": "Seleziona colore avatar", - "select_count": "{count, plural, one {Seleziona #} other {Seleziona #}}", - "select_cutoff_date": "Seleziona la data limite", - "select_face": "Seleziona volto", - "select_featured_photo": "Seleziona foto in evidenza", - "select_from_computer": "Seleziona dal computer", - "select_keep_all": "Seleziona mantieni tutto", - "select_library_owner": "Seleziona proprietario libreria", - "select_new_face": "Seleziona nuovo volto", - "select_people": "Seleziona persone", - "select_person": "Seleziona una persona", - "select_person_to_tag": "Seleziona una persona da taggare", - "select_photos": "Seleziona foto", - "select_trash_all": "Seleziona cestina tutto", - "select_user_for_sharing_page_err_album": "Impossibile nel creare l'album", - "selected": "Selezionato", - "selected_count": "{count, plural, one {# selezionato} other {# selezionati}}", - "selected_gps_coordinates": "Coordinate GPS selezionate", - "send_message": "Manda messaggio", - "send_welcome_email": "Invia email di benvenuto", - "server_endpoint": "Server endpoint", - "server_info_box_app_version": "Versione App", - "server_info_box_server_url": "URL del Server", - "server_offline": "Server Offline", - "server_online": "Server Online", - "server_privacy": "Privacy del Server", - "server_restarting_description": "Questa pagina si aggiornerà per un momento.", - "server_restarting_title": "Il server si sta riavviando", - "server_stats": "Statistiche Server", - "server_update_available": "Aggiornamento Server disponibile", - "server_version": "Versione Server", - "set": "Imposta", - "set_as_album_cover": "Imposta come copertina album", - "set_as_featured_photo": "Imposta come immagine in primo piano", - "set_as_profile_picture": "Imposta come foto profilo", - "set_date_of_birth": "Imposta data di nascita", - "set_profile_picture": "Imposta foto profilo", - "set_slideshow_to_fullscreen": "Imposta presentazione a schermo intero", - "set_stack_primary_asset": "Imposta come risorsa primaria", - "setting_image_viewer_help": "Il visualizzatore dettagliato carica una piccola thumbnail per prima, per poi caricare un immagine di media grandezza (se abilitato). Ed infine carica l'originale (se abilitato).", - "setting_image_viewer_original_subtitle": "Abilita il caricamento dell’immagine originale in alta risoluzione (dimensioni elevate). Disattiva per ridurre il consumo di dati, sia di rete che in cache locale.", - "setting_image_viewer_original_title": "Carica l'immagine originale", - "setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media. Disabilita per caricare direttamente l'immagine originale o usare la thumbnail.", - "setting_image_viewer_preview_title": "Carica immagine di anteprima", - "setting_image_viewer_title": "Immagini", - "setting_languages_apply": "Applica", - "setting_languages_subtitle": "Cambia la lingua dell'app", - "setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {duration}", - "setting_notifications_notify_hours": "{count} ore", - "setting_notifications_notify_immediately": "immediatamente", - "setting_notifications_notify_minutes": "{count} minuti", - "setting_notifications_notify_never": "mai", - "setting_notifications_notify_seconds": "{count} secondi", - "setting_notifications_single_progress_subtitle": "Informazioni dettagliate sul caricamento della risorsa", - "setting_notifications_single_progress_title": "Mostra avanzamento dettagliato del backup in background", - "setting_notifications_subtitle": "Cambia le impostazioni di notifica", - "setting_notifications_total_progress_subtitle": "Avanzamento complessivo del caricamento (completati/risorse totali)", - "setting_notifications_total_progress_title": "Mostra avanzamento del backup in background", - "setting_video_viewer_auto_play_subtitle": "Avvia automaticamente la riproduzione dei video quando vengono aperti", - "setting_video_viewer_auto_play_title": "Riproduci video automaticamente", - "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_original_video_subtitle": "Quando riproduci un video dal server, riproduci l'originale anche se è disponibile una versione transcodificata. Questo potrebbe portare a buffering. I video disponibili localmente sono sempre riprodotti a qualità originale indipendentemente da questa impostazione.", - "setting_video_viewer_original_video_title": "Forza video originale", - "settings": "Impostazioni", - "settings_require_restart": "Si prega di riavviare Immich perché vengano applicate le impostazioni", - "settings_saved": "Impostazioni salvate", - "setup_pin_code": "Configura un codice PIN", - "share": "Condividi", - "share_action_prompt": "Condivisi {count} risorse", - "share_add_photos": "Aggiungi foto", - "share_assets_selected": "{count} selezionati", - "share_dialog_preparing": "Preparo…", - "share_link": "Link di condivisione", - "shared": "Condivisi", - "shared_album_activities_input_disable": "I commenti sono disabilitati", - "shared_album_activity_remove_content": "Vuoi eliminare questa attività?", - "shared_album_activity_remove_title": "Elimina attività", - "shared_album_section_people_action_error": "Errore durante la rimozione/uscita dell'album", - "shared_album_section_people_action_leave": "Rimuovi utente dall'album", - "shared_album_section_people_action_remove_user": "Rimuovi utente dall'album", - "shared_album_section_people_title": "PERSONE", - "shared_by": "Condiviso da", - "shared_by_user": "Condiviso da {user}", - "shared_by_you": "Condiviso da te", - "shared_from_partner": "Foto da {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Caricati", - "shared_link_app_bar_title": "Link condivisi", - "shared_link_clipboard_copied_massage": "Copiato negli appunti", - "shared_link_clipboard_text": "Link: {link}\nPassword: {password}", - "shared_link_create_error": "Si è verificato un errore durante la creazione del link condiviso", - "shared_link_custom_url_description": "Accedi a questo link condiviso con un URL personalizzato", - "shared_link_edit_description_hint": "Inserisci la descrizione della condivisione", - "shared_link_edit_expire_after_option_day": "1 giorno", - "shared_link_edit_expire_after_option_days": "{count} giorni", - "shared_link_edit_expire_after_option_hour": "1 ora", - "shared_link_edit_expire_after_option_hours": "{count} ore", - "shared_link_edit_expire_after_option_minute": "1 minuto", - "shared_link_edit_expire_after_option_minutes": "{count} minuti", - "shared_link_edit_expire_after_option_months": "{count} mesi", - "shared_link_edit_expire_after_option_year": "{count} anno", - "shared_link_edit_password_hint": "Inserire la password di condivisione", - "shared_link_edit_submit_button": "Aggiorna link", - "shared_link_error_server_url_fetch": "Non è possibile trovare l'indirizzo del server", - "shared_link_expires_day": "Scade tra {count} giorno", - "shared_link_expires_days": "Scade tra {count} giorni", - "shared_link_expires_hour": "Scade tra {count} ora", - "shared_link_expires_hours": "Scade tra {count} ore", - "shared_link_expires_minute": "Scade tra {count} minuto", - "shared_link_expires_minutes": "Scade tra {count} minuti", - "shared_link_expires_never": "Scadenza ∞", - "shared_link_expires_second": "Scade tra {count} secondo", - "shared_link_expires_seconds": "Scade tra {count} secondi", - "shared_link_individual_shared": "Condiviso individualmente", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Gestisci link condivisi", - "shared_link_options": "Opzioni link condiviso", - "shared_link_password_description": "Imposta una password per questo link condiviso", - "shared_links": "Link condivisi", - "shared_links_description": "Condividi foto e video con un link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# foto e video condivisi.}}", - "shared_with_me": "Condivisi con me", - "shared_with_partner": "Condiviso con {partner}", - "sharing": "Condivisione", - "sharing_enter_password": "Inserisci la password per accedere a questa pagina.", - "sharing_page_album": "Album condivisi", - "sharing_page_description": "Crea un album condiviso per condividere foto e video con gli utenti della tua rete Immich.", - "sharing_page_empty_list": "LISTA VUOTA", - "sharing_sidebar_description": "Mostra un link a Condivisione nella barra laterale", - "sharing_silver_appbar_create_shared_album": "Crea album condiviso", - "sharing_silver_appbar_share_partner": "Condividi con partner", - "shift_to_permanent_delete": "premi ⇧ per cancellare definitivamente la risorsa", - "show_album_options": "Mostra opzioni album", - "show_albums": "Mostra gli album", - "show_all_people": "Mostra tutte le persone", - "show_and_hide_people": "Mostra & nascondi persone", - "show_file_location": "Mostra percorso file", - "show_gallery": "Mostra galleria", - "show_hidden_people": "Mostra persone nascoste", - "show_in_timeline": "Mostra nella linea temporale", - "show_in_timeline_setting_description": "Mostra foto e video dalla linea temporale di questo utente", - "show_keyboard_shortcuts": "Mostra comandi rapidi", - "show_metadata": "Visualizza metadati", - "show_or_hide_info": "Mostra o nascondi informazioni", - "show_password": "Mostra password", - "show_person_options": "Mostra opzioni persona", - "show_progress_bar": "Mostra Barra Avanzamento", - "show_schema": "Mostra lo schema", - "show_search_options": "Mostra impostazioni di ricerca", - "show_shared_links": "Mostra link condivisi", - "show_slideshow_transition": "Mostra la transizione della presentazione", - "show_supporter_badge": "Medaglia di Contributore", - "show_supporter_badge_description": "Mostra la medaglia di contributore", - "show_text_recognition": "Mostra riconoscimento del testo", - "show_text_search_menu": "Mostra il menu di ricerca del testo", - "shuffle": "Casuale", - "sidebar": "Barra laterale", - "sidebar_display_description": "Visualizzare un link alla vista nella barra laterale", - "sign_out": "Esci", - "sign_up": "Registrati", - "size": "Dimensione", - "skip_to_content": "Salta al contenuto", - "skip_to_folders": "Salta alle cartelle", - "skip_to_tags": "Salta alle etichette", - "slideshow": "Presentazione", - "slideshow_repeat": "Ripeti presentazione", - "slideshow_repeat_description": "Ricomincia da capo quando la presentazione termina", - "slideshow_settings": "Impostazioni presentazione", - "sort_albums_by": "Ordina album per...", - "sort_created": "Data creazione", - "sort_items": "Numero di elementi", - "sort_modified": "Data modifica", - "sort_newest": "Foto più recente", - "sort_oldest": "Foto più vecchia", - "sort_people_by_similarity": "Ordina persone per somiglianza", - "sort_recent": "Foto più recente", - "sort_title": "Titolo", - "source": "Sorgente", - "stack": "Raggruppa", - "stack_action_prompt": "{count} elementi raggruppati", - "stack_duplicates": "Raggruppa i duplicati", - "stack_select_one_photo": "Seleziona una foto principale per il gruppo", - "stack_selected_photos": "Raggruppa foto selezionate", - "stacked_assets_count": "{count, plural, one {Raggruppata # risorsa} other {Raggruppate # risorse}}", - "stacktrace": "Traccia dell'errore", - "start": "Avvia", - "start_date": "Data di inizio", - "start_date_before_end_date": "La data di inizio deve essere precedente alla data di fine", - "state": "Provincia", - "status": "Stato", - "stop_casting": "Interrompi trasmissione", - "stop_motion_photo": "Ferma Foto in Movimento", - "stop_photo_sharing": "Interrompere la condivisione delle tue foto?", - "stop_photo_sharing_description": "{partner} non potrà più accedere alle tue foto.", - "stop_sharing_photos_with_user": "Interrompi la condivisione delle tue foto con questo utente", - "storage": "Spazio di archiviazione", - "storage_label": "Etichetta archiviazione", - "storage_quota": "Limite Archiviazione", - "storage_usage": "{used} di {available} utilizzati", - "submit": "Invia", - "success": "Successo", - "suggestions": "Suggerimenti", - "sunrise_on_the_beach": "Tramonto sulla spiaggia", - "support": "Supporto", - "support_and_feedback": "Supporto & Feedback", - "support_third_party_description": "La tua installazione di Immich è stata costruita da terze parti. I problemi che riscontri potrebbero essere causati da altri pacchetti, quindi ti preghiamo di sollevare il problema in prima istanza utilizzando i link sottostanti.", - "swap_merge_direction": "Scambia direzione di unione", - "sync": "Sincronizza", - "sync_albums": "Sincronizza album", - "sync_albums_manual_subtitle": "Sincronizza tutti i video e le foto caricati con gli album di backup selezionati", - "sync_local": "Sincronizza gli elementi locali", - "sync_remote": "Sincronizza gli elementi remoti", - "sync_status": "Stato di Sincronizzazione", - "sync_status_subtitle": "Visualizza e gestisci il sistema di sincronizzazione", - "sync_upload_album_setting_subtitle": "Crea e carica le tue foto e video sull'album selezionato in Immich", - "tag": "Tag", - "tag_assets": "Tagga risorse", - "tag_created": "Tag creata: {tag}", - "tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici", - "tag_not_found_question": "Non riesci a trovare un tag? Creane uno nuovo.", - "tag_people": "Tagga persone", - "tag_updated": "Tag {tag} aggiornata", - "tagged_assets": "{count, plural, one {# risorsa etichettata} other {# risorse etichettate}}", - "tags": "Tag", - "tap_to_run_job": "Tocca per eseguire l'attività", - "template": "Modello", - "text_recognition": "Riconoscimento del testo", - "theme": "Tema", - "theme_selection": "Selezione tema", - "theme_selection_description": "Imposta automaticamente il tema chiaro o scuro in base all'impostazione del tuo browser", - "theme_setting_asset_list_storage_indicator_title": "Mostra indicatore dello storage nei titoli delle risorse", - "theme_setting_asset_list_tiles_per_row_title": "Numero di risorse per riga ({count})", - "theme_setting_colorful_interface_subtitle": "Applica il colore primario alle superfici di sfondo.", - "theme_setting_colorful_interface_title": "Interfaccia colorata", - "theme_setting_image_viewer_quality_subtitle": "Cambia la qualità del dettaglio dell'immagine", - "theme_setting_image_viewer_quality_title": "Qualità immagine", - "theme_setting_primary_color_subtitle": "Scegli un colore per le azioni primarie e accentate.", - "theme_setting_primary_color_title": "Colore primario", - "theme_setting_system_primary_color_title": "Usa colori di sistema", - "theme_setting_system_theme_switch": "Automatico (Segue le impostazioni di sistema)", - "theme_setting_theme_subtitle": "Scegli un'impostazione per il tema dell'app", - "theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda", - "theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage", - "then": "Allora", - "they_will_be_merged_together": "Verranno uniti insieme", - "third_party_resources": "Risorse di Terze Parti", - "time": "Orario", - "time_based_memories": "Ricordi basati sul tempo", - "time_based_memories_duration": "Numero di secondi per visualizzare ciascuna immagine.", - "timeline": "Linea temporale", - "timezone": "Fuso orario", - "to_archive": "Archivio", - "to_change_password": "Modifica password", - "to_favorite": "Preferito", - "to_login": "Accedi", - "to_multi_select": "per selezione multipla", - "to_parent": "Sali di un livello", - "to_select": "per selezionare", - "to_trash": "Cancella", - "toggle_settings": "Attiva/disattiva impostazioni", - "toggle_theme_description": "Cambia tema", - "total": "Totale", - "total_usage": "Utilizzo totale", - "trash": "Cestino", - "trash_action_prompt": "{count} elementi spostati nel cestino", - "trash_all": "Cestina Tutto", - "trash_count": "Cancella {count, number}", - "trash_delete_asset": "Cestina/Cancella Risorsa", - "trash_emptied": "Cestino svuotato", - "trash_no_results_message": "Le foto cestinate saranno mostrate qui.", - "trash_page_delete_all": "Elimina tutti", - "trash_page_empty_trash_dialog_content": "Vuoi eliminare le risorse dal cestino? Saranno eliminate definitivamente da Immich", - "trash_page_info": "Gli elementi cestinati saranno eliminati definitivamente dopo {days} giorni", - "trash_page_no_assets": "Nessuna risorsa cestinata", - "trash_page_restore_all": "Ripristina tutto", - "trash_page_select_assets_btn": "Seleziona risorse", - "trash_page_title": "Cestino ({count})", - "trashed_items_will_be_permanently_deleted_after": "Gli elementi cestinati saranno eliminati definitivamente dopo {days, plural, one {# giorno} other {# giorni}}.", - "trigger": "Evento di attivazione", - "trigger_asset_uploaded": "Risorsa Caricata", - "trigger_asset_uploaded_description": "Attivato quando una nuova risorsa viene caricata", - "trigger_description": "Un evento che attiva il flusso di lavoro", - "trigger_person_recognized": "Persona Riconosciuta", - "trigger_person_recognized_description": "Attivato quando è rilevata una persona", - "trigger_type": "Tipo di trigger", - "troubleshoot": "Risoluzione dei problemi", - "type": "Tipo", - "unable_to_change_pin_code": "Impossibile cambiare il codice PIN", - "unable_to_check_version": "Impossibile controllare la versione del server o dell'app", - "unable_to_setup_pin_code": "Impossibile configurare il codice PIN", - "unarchive": "Annulla l'archiviazione", - "unarchive_action_prompt": "{count} elementi rimossi dall'Archivio", - "unarchived_count": "{count, plural, other {Non archiviati #}}", - "undo": "Annulla", - "unfavorite": "Rimuovi preferito", - "unfavorite_action_prompt": "{count} rimossi dai Favoriti", - "unhide_person": "Mostra persona", - "unknown": "Sconosciuto", - "unknown_country": "Paese sconosciuto", - "unknown_date": "Data sconosciuta", - "unknown_year": "Anno sconosciuto", - "unlimited": "Illimitato", - "unlink_motion_video": "Scollega video in movimento", - "unlink_oauth": "Scollega OAuth", - "unlinked_oauth_account": "Scollega account OAuth", - "unmute_memories": "Disattiva l'audio dei ricordi", - "unnamed_album": "Album senza nome", - "unnamed_album_delete_confirmation": "Sei sicuro di voler eliminare questo album?", - "unnamed_share": "Condivisione senza nome", - "unsaved_change": "Modifica non salvata", - "unselect_all": "Deseleziona tutto", - "unselect_all_duplicates": "Deseleziona tutti i duplicati", - "unselect_all_in": "Deseleziona tutto in {group}", - "unstack": "Separa dal gruppo", - "unstack_action_prompt": "{count} separati", - "unstacked_assets_count": "{count, plural, one {Separata # risorsa} other {Separate # risorse}}", - "unsupported_field_type": "Tipo di campo non supportato", - "untagged": "Senza tag", - "untitled_workflow": "Flusso di lavoro senza titolo", - "up_next": "Prossimo", - "update_location_action_prompt": "Aggiorna la posizione di {count} risorse selezionate con:", - "updated_at": "Aggiornato il", - "updated_password": "Password aggiornata", - "upload": "Carica", - "upload_concurrency": "Caricamenti contemporanei", - "upload_details": "Dettagli di caricamento", - "upload_dialog_info": "Vuoi fare il backup sul server delle risorse selezionate?", - "upload_dialog_title": "Carica Risorsa", - "upload_error_with_count": "Invio in errore per {count, plural, one {# risorsa} other {# risorse}}", - "upload_errors": "Caricamento completato con {count, plural, one {# errore} other {# errori}}, ricarica la pagina per vedere le risorse caricate.", - "upload_finished": "Upload terminato", - "upload_progress": "Rimanenti {remaining, number} - Processati {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {Ignorata # risorsa duplicata} other {Ignorate # risorse duplicate}}", - "upload_status_duplicates": "Duplicati", - "upload_status_errors": "Errori", - "upload_status_uploaded": "Caricato", - "upload_success": "Caricamento completato, aggiorna la pagina per vedere le nuove risorse caricate.", - "upload_to_immich": "Carica su Immich ({count})", - "uploading": "Caricamento", - "uploading_media": "Caricando i media", - "url": "URL", - "usage": "Utilizzo", - "use_biometric": "Usa biometrica", - "use_current_connection": "Usa la connessione attuale", - "use_custom_date_range": "Altrimenti utilizza un intervallo date personalizzato", - "user": "Utente", - "user_has_been_deleted": "L'utente è stato rimosso.", - "user_id": "ID utente", - "user_liked": "A {user} piace {type, select, photo {questa foto} video {questo video} asset {questa risorsa} other {questo elemento}}", - "user_pin_code_settings": "Codice PIN", - "user_pin_code_settings_description": "Gestisci il tuo codice PIN", - "user_privacy": "Privacy dell'utente", - "user_purchase_settings": "Acquisto", - "user_purchase_settings_description": "Gestisci il tuo acquisto", - "user_role_set": "Imposta {user} come {role}", - "user_usage_detail": "Dettagli utilizzo utente", - "user_usage_stats": "Statistiche d'uso", - "user_usage_stats_description": "Consulta le statistiche d'uso dell'account", - "username": "Nome utente", - "users": "Utenti", - "users_added_to_album_count": "Aggiunti {count, plural, one {# utente} other {# utenti}} all'album", - "utilities": "Utilità", - "validate": "Validazione", - "validate_endpoint_error": "Inserisci un URL valido", - "validation_error": "Erroe di validazione", - "variables": "Variabili", - "version": "Versione", - "version_announcement_closing": "Il tuo amico, Alex", - "version_announcement_message": "Ehilà! È stata rilasciata una nuova versione di Immich. Leggi le release notes e assicurati che i tuoi file di configurazione siano aggiornati per evitare problemi e incongruenze, soprattutto se utilizzi WatchTower o altri strumenti per aggiornare Immich in automatico.", - "version_history": "Storico delle Versioni", - "version_history_item": "Versione installata {version} il {date}", - "video": "Video", - "video_hover_setting": "Riproduci l'anteprima del video al passaggio del mouse", - "video_hover_setting_description": "Riproduci miniatura video quando il mouse passa sopra l'elemento. Anche se disabilitato, la riproduzione può essere avviata passando con il mouse sopra l'icona riproduci.", - "videos": "Video", - "videos_count": "{count, plural, one {# Video} other {# Video}}", - "videos_only": "Solo video", - "view": "Visualizza", - "view_album": "Visualizza Album", - "view_all": "Vedi tutto", - "view_all_users": "Visualizza tutti gli utenti", - "view_asset_owners": "Visualizza proprietari della risorsa", - "view_details": "Visualizza Dettagli", - "view_in_timeline": "Visualizza in timeline", - "view_link": "Visualizza link", - "view_links": "Visualizza i link", - "view_name": "Vista", - "view_next_asset": "Visualizza risorsa successiva", - "view_previous_asset": "Visualizza risorsa precedente", - "view_qr_code": "Visualizza Codice QR", - "view_similar_photos": "Visualizza foto simili", - "view_stack": "Visualizza Raggruppamento", - "view_user": "Visualizza Utente", - "viewer_remove_from_stack": "Rimuovi dal gruppo", - "viewer_stack_use_as_main_asset": "Usa come risorsa principale", - "viewer_unstack": "Separa dal gruppo", - "visibility_changed": "Visibilità modificata per {count, plural, one {# persona} other {# persone}}", - "visual": "Visuale", - "visual_builder": "Costruttore di visuale", - "waiting": "In Attesa", - "waiting_count": "In attesa: {count}", - "warning": "Attenzione", - "week": "Settimana", - "welcome": "Benvenuto", - "welcome_to_immich": "Benvenuto in Immich", - "width": "Larghezza", - "wifi_name": "Nome rete Wi-Fi", - "workflow_delete_prompt": "Sei sicuro di voler cancellare questo flusso di lavoro?", - "workflow_deleted": "Flusso di lavoro cancellato", - "workflow_description": "Descrizione del flusso di lavoro", - "workflow_info": "Informazioni sul flusso di lavoro", - "workflow_json": "Flusso di lavoro JSON", - "workflow_json_help": "Edita la configurazione del flusso di lavoro in formato JSON. I cambiamenti verranno sincronizzati con il costruttore visuale.", - "workflow_name": "Nome del flusso di lavoro", - "workflow_navigation_prompt": "Sei sicuro di voler uscire senza salvare i cambiamenti?", - "workflow_summary": "Sommario del flusso di lavoro", - "workflow_update_success": "Flusso di lavoro aggiornato con successo", - "workflow_updated": "Flusso di lavoro aggiornato", - "workflows": "Flussi di lavoro", - "workflows_help_text": "I flussi di lavoro automatizzano azioni sulle tue risorse a seconda di eventi e filtri", - "wrong_pin_code": "Codice PIN errato", - "year": "Anno", - "years_ago": "{years, plural, one {# anno} other {# anni}} fa", - "yes": "Sì", - "you_dont_have_any_shared_links": "Non hai nessun link condiviso", - "your_wifi_name": "Nome della tua rete Wi-Fi", - "zero_to_clear_rating": "Premi 0 per eliminare la valutazione", - "zoom_image": "Ingrandisci immagine", - "zoom_to_bounds": "Ingrandisci fino ai bordi" -} +{} diff --git a/i18n/ja.json b/i18n/ja.json index b89e335004..0967ef424b 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -1,2401 +1 @@ -{ - "about": "Immich について", - "account": "アカウント", - "account_settings": "アカウント設定", - "acknowledge": "了解", - "action": "アクション", - "action_common_update": "更新", - "action_description": "抽出された写真/動画に対して行う手順", - "actions": "アクション", - "active": "アクティブ", - "active_count": "アクティブ: {count}", - "activity": "アクティビティ", - "activity_changed": "アクティビティは{enabled, select, true {有効} other {無効}}になりました", - "add": "追加", - "add_a_description": "説明を追加", - "add_a_location": "場所を追加", - "add_a_name": "名前を追加", - "add_a_title": "タイトルを追加", - "add_action": "アクションを追加", - "add_action_description": "クリックしアクションを追加", - "add_assets": "項目を追加", - "add_birthday": "誕生日を設定", - "add_endpoint": "エンドポイントを追加", - "add_exclusion_pattern": "除外パターンを追加", - "add_filter": "フィルターを追加", - "add_filter_description": "フィルターする条件を追加", - "add_location": "場所を追加", - "add_more_users": "ユーザーを追加", - "add_partner": "パートナーを追加", - "add_path": "パスを追加", - "add_photos": "写真を追加", - "add_tag": "タグを追加", - "add_to": "追加先…", - "add_to_album": "アルバムに追加", - "add_to_album_bottom_sheet_added": "{album}に追加", - "add_to_album_bottom_sheet_already_exists": "{album}に追加済み", - "add_to_album_bottom_sheet_some_local_assets": "いくつかの項目はまだサーバーへアップロードされていないためアルバムに追加できませんでした", - "add_to_album_toggle": "{album}の選択を切り替え", - "add_to_albums": "アルバムに追加", - "add_to_albums_count": "{count}つのアルバムへ追加", - "add_to_bottom_bar": "追加する", - "add_to_shared_album": "共有アルバムに追加", - "add_upload_to_stack": "スタックにアップロードを追加", - "add_url": "URLを追加", - "add_workflow_step": "ワークフローのステップを追加", - "added_to_archive": "アーカイブにしました", - "added_to_favorites": "お気に入りに追加済", - "added_to_favorites_count": "{count, number} 枚の画像をお気に入りに追加しました", - "admin": { - "add_exclusion_pattern_description": "除外パターンを追加します。ワイルドカード「*」「**」「?」を使用できます。すべてのディレクトリで「Raw」と名前が付いたファイルを無視するには、「**/Raw/**」を使用します。また、「.tif」で終わるファイルをすべて無視するには、「**/*.tif」を使用します。さらに、絶対パスを無視するには「/path/to/ignore/**」を使用します。", - "admin_user": "管理ユーザー", - "asset_offline_description": "この外部ライブラリのアセットはディスク上に見つからなくなってゴミ箱に移動されました。ファイルがライブラリの中で移動された場合はタイムラインで新しい対応するアセットを確認してください。このアセットを復元するには以下のファイルパスがImmichからアクセスできるか確認してライブラリをスキャンしてください。", - "authentication_settings": "認証設定", - "authentication_settings_description": "認証設定の管理(パスワード、OAuth、その他)", - "authentication_settings_disable_all": "本当に全てのログイン方法を無効にしますか? ログインは完全に無効になります。", - "authentication_settings_reenable": "再び有効にするには、サーバーコマンドを使用してください。", - "background_task_job": "バックグラウンドタスク", - "backup_database": "データベースのバックアップを作成", - "backup_database_enable_description": "データベースバックアップの作成を有効にする", - "backup_keep_last_amount": "過去のバックアップの保持数", - "backup_onboarding_1_description": "クラウドを利用したオフサイトのコピーか別の物理的な場所。", - "backup_onboarding_2_description": "別々のデバイス上のローカルコピー。これはメインファイルやそのローカルバックアップファイルを含みます。", - "backup_onboarding_3_description": "あなたのすべてのデータ(1つのオフサイトコピーと2つのローカルコピーを含む)のコピー。", - "backup_onboarding_description": "データ保護には3-2-1バックアップ戦略が推奨されます。アップロードした写真/動画のコピーに加え、Immichデータベースのコピーも保持することで包括的なバックアップソリューションを実現できます。", - "backup_onboarding_footer": "Immichのバックアップに関する情報は、ドキュメンテーションを確認してください。", - "backup_onboarding_parts_title": "3-2-1バックアップ:", - "backup_onboarding_title": "バックアップ", - "backup_settings": "データベースバックアップ作成の設定", - "backup_settings_description": "データベースのバックアップ作成設定の管理 (このジョブはモニタリングされませんし、失敗が発生してもあなたに通知が行くことはありません)", - "cleared_jobs": "{job}のジョブをクリアしました", - "config_set_by_file": "設定は現在 Config File で設定されている", - "confirm_delete_library": "本当に {library} を削除しますか?", - "confirm_delete_library_assets": "本当にこのライブラリを削除しますか? {count, plural, one {#個の項目} other {#個の項目全て}} がImmichから削除され、元に戻すことはできません。ファイルはディスク上に残ります。", - "confirm_email_below": "確認のため、以下に \"{email}\" と入力してください", - "confirm_reprocess_all_faces": "本当にすべての顔を再処理しますか? これにより名前が付けられた人物も消去されます。", - "confirm_user_password_reset": "本当に {user} のパスワードをリセットしますか?", - "confirm_user_pin_code_reset": "{user}のPINコードをリセットしてよいですか?", - "copy_config_to_clipboard_description": "JSONオブジェクトとして現在のシステムコンフィグをクリップボードにコピーする", - "create_job": "ジョブの作成", - "cron_expression": "Cron式", - "cron_expression_description": "cronのフォーマットを使ってスキャン間隔を設定します。詳しくはCrontab Guruなどを参照してください", - "cron_expression_presets": "Cron式のプリセット", - "disable_login": "ログインを無効にする", - "duplicate_detection_job_description": "機械学習を用いて類似画像の検出を行います。(スマートサーチに依存)", - "exclusion_pattern_description": "除外パターンを使用すると、ライブラリをスキャンする際にファイルやフォルダを無視することができます。RAWファイルなど、インポートしたくないファイルを含むフォルダがある場合に便利です。", - "export_config_as_json_description": "現在のシステムコンフィグをJSONファイルとしてダウンロード", - "external_libraries_page_description": "管理者用 外部ライブラリ ページ", - "face_detection": "顔検出", - "face_detection_description": "機械学習を使用してアセット内の顔を検出します。動画の場合は、サムネイルのみが対象となります。\"すべて\" はすべてのアセットを(再)処理します。 \"欠落\" はまだ処理されていないアセットをキューに入れます。顔検出の完了後、検出された顔は顔認識のキューへ入れられ、既存または新規の人物にグループ化されます。", - "facial_recognition_job_description": "検出された顔を人物にグループ化します。このステップは顔検出が完了した後に実行されます。 \"すべて\" はすべての顔を(再)クラスタリングし、 \"欠落\" は人物が割り当てられていない顔をキューに入れます。", - "failed_job_command": "ジョブ {job}のコマンド {command}が失敗しました", - "force_delete_user_warning": "警告:この操作を行うと、ユーザーとすべてのアセットが直ちに削除されます。これは元に戻せず、ファイルも復元できません。", - "image_format": "フォーマット", - "image_format_description": "WebPはJPEGよりもファイルサイズが小さいですが、エンコードに時間がかかります。", - "image_fullsize_description": "画像を拡大する時に使われるメタデータを取り除いた原寸大画像", - "image_fullsize_enabled": "原寸大画像生成を有効にする", - "image_fullsize_enabled_description": "Webで表示が難しいとされる画像フォーマットに対して原寸大画像を生成する。", - "image_fullsize_quality_description": "1から100まで原寸大画像の質です。高いほうがいいがファイルが大きくなります。", - "image_fullsize_title": "原寸大画像設定", - "image_prefer_embedded_preview": "埋め込みプレビューを優先", - "image_prefer_embedded_preview_setting_description": "RAW写真の埋め込みプレビューが利用可能な場合に画像処理の入力として使用します。これにより、いくつかの画像でより正確な色を得ることができますが、プレビューの品質はカメラによって異なり、画像により多くの圧縮アーティファクトが含まれる場合があります。", - "image_prefer_wide_gamut": "広色域に対応させる", - "image_prefer_wide_gamut_setting_description": "サムネイルにはDisplay P3を使用します。これにより、広色域の画像の鮮やかさをよりよく保つことができますが、古いデバイスや古いブラウザバージョンでは画像が異なって見える場合があります。sRGBの画像は、色の変化を避けるためにsRGBのままにします。", - "image_preview_description": "単一のアセットを表示する時や機械学習に使われるメタデータを取り除いた中サイズの画像", - "image_preview_quality_description": "プレビューの画質は1〜100で設定できます。値が高いほど品質は良くなりますがファイルサイズが大きくなってアプリの応答性が低下するおそれがあります。低い値を設定すると機械学習の品質に影響を与えるおそれがあります。", - "image_preview_title": "プレビュー設定", - "image_progressive": "漸進的読み込み", - "image_progressive_description": "JPEG画像を段階的にエンコードし、画像を徐々に表示します。この設定はWebP画像に影響を及ぼしません。", - "image_quality": "品質", - "image_resolution": "解像度", - "image_resolution_description": "解像度を上げるとより精細に保存できますが、エンコードに時間がかかりファイルサイズが大きくなってアプリの応答性が低下するおそれがあります。", - "image_settings": "画像設定", - "image_settings_description": "生成される画像の品質と解像度の設定", - "image_thumbnail_description": "メインのタイムラインのような写真グループで表示する際に使われるメタデータを取り除いた小さなサムネイル", - "image_thumbnail_quality_description": "サムネイルの画質を1〜100の間で設定できます。値が大きいほど良い品質ですがファイルサイズが大きくなりアプリの応答性が低下します。", - "image_thumbnail_title": "サムネイル設定", - "import_config_from_json_description": "システムコンフィグのJSONファイルをアップロードしインポート", - "job_concurrency": "{job} の同時実行数", - "job_created": "ジョブを作成しました", - "job_not_concurrency_safe": "このジョブは安全に同時実行できません。", - "job_settings": "ジョブ設定", - "job_settings_description": "ジョブの同時実行を管理します", - "jobs_delayed": "{jobCount, plural, other {#件}}の遅延", - "jobs_failed": "{jobCount, plural, other {#件}}の失敗", - "jobs_over_time": "終わらなかったジョブ", - "library_created": "作成されたライブラリ:{library}", - "library_deleted": "ライブラリは削除されました", - "library_details": "ライブラリの詳細", - "library_folder_description": "インポートするフォルダを指定してください、サブフォルダー内を含む画像と動画がスキャンされます", - "library_remove_exclusion_pattern_prompt": "この除外パターンを削除してよいですか?", - "library_remove_folder_prompt": "このインポートフォルダを解除しますか?", - "library_scanning": "定期スキャン", - "library_scanning_description": "ライブラリの定期スキャン設定", - "library_scanning_enable_description": "ライブラリ定期スキャンの有効化", - "library_settings": "外部ライブラリ", - "library_settings_description": "外部ライブラリ設定を管理します", - "library_tasks_description": "アセットが追加または変更された外部ライブラリをスキャンする", - "library_updated": "更新されたライブラリ", - "library_watching_enable_description": "外部ライブラリのファイル変更を監視", - "library_watching_settings": "ライブラリ監視(実験的機能)", - "library_watching_settings_description": "変更されたファイルを自動的に監視", - "logging_enable_description": "ログの有効化", - "logging_level_description": "有効な場合に使用されるログ レベル。", - "logging_settings": "ログ", - "machine_learning_availability_checks": "可用性の確認", - "machine_learning_availability_checks_description": "利用可能な機械学習のサーバーを自動で検知し優先的に使用します", - "machine_learning_availability_checks_enabled": "可用性チェックを有効にする", - "machine_learning_availability_checks_interval": "チェックの間隔", - "machine_learning_availability_checks_interval_description": "可用性チェックの間隔(ミリ秒単位)", - "machine_learning_availability_checks_timeout": "リクエストタイムアウト", - "machine_learning_availability_checks_timeout_description": "可用性チェックのタイムアウト時間(ミリ秒単位)", - "machine_learning_clip_model": "Clipモデル", - "machine_learning_clip_model_description": "CLIP モデルの名前はここにリストされています。モデルを変更した場合は、すべてのイメージに対して「スマート検索」ジョブを再実行する必要があります。", - "machine_learning_duplicate_detection": "重複検出", - "machine_learning_duplicate_detection_enabled": "重複検出の有効化", - "machine_learning_duplicate_detection_enabled_description": "無効にした場合でも、完全に同一アセットの重複は排除されます。", - "machine_learning_duplicate_detection_setting_description": "CLIP埋め込みを使用して、重複の可能性が高いものを検出する", - "machine_learning_enabled": "機械学習の有効化", - "machine_learning_enabled_description": "無効にした場合、以下の設定に関係なく、すべての機械学習機能が無効になります。", - "machine_learning_facial_recognition": "顔認識", - "machine_learning_facial_recognition_description": "画像から顔を検出・認識・グループ化", - "machine_learning_facial_recognition_model": "顔認識モデル", - "machine_learning_facial_recognition_model_description": "モデルはサイズの大きい順に並んでいます。サイズの大きいモデルは遅く、メモリを多く使用しますが、より良い結果を作り出します。モデルを変更した場合、顔検出ジョブをすべての画像で再実行する必要があります。", - "machine_learning_facial_recognition_setting": "顔認識を有効にする", - "machine_learning_facial_recognition_setting_description": "無効にした場合、顔認識のために画像をエンコードしなくなり、検索ページの人物に表示されなくなります。", - "machine_learning_max_detection_distance": "検出距離の最大値", - "machine_learning_max_detection_distance_description": "重複と見なすための2つの画像間の最大距離を0.001から0.1の範囲で設定します。値が高いほど多くの重複が検出されますが、誤検出が増える可能性があります。", - "machine_learning_max_recognition_distance": "認識距離の最大値", - "machine_learning_max_recognition_distance_description": "同一人物と見なすための2つの顔間の最大距離を0から2の範囲で設定します。この値を下げると、異なる人物を同一人物と誤認識することを防げ、この値を上げると、同一人物を異なる人物と誤認識することを防ぎます。なお、2人を1人に統合するよりも、1人を2人に分ける方が難しいため、可能な限り低いしきい値に設定することをお勧めします。", - "machine_learning_min_detection_score": "信頼スコアの最低値", - "machine_learning_min_detection_score_description": "顔を検出するための最低信頼スコアを0から1の範囲で設定します。値を低くするとより多くの顔を検出できますが、誤検出の可能性が高くなります。", - "machine_learning_min_recognized_faces": "顔認識の最低値", - "machine_learning_min_recognized_faces_description": "人物として作成されるために必要な最低認識顔数を設定します。この値を増やすと顔認識の精度が向上しますが、その代わりに顔が人物として認識されない可能性も高くなります。", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "画像内の文字を認識するために機械学習を使用する", - "machine_learning_ocr_enabled": "OCRを有効にする", - "machine_learning_ocr_enabled_description": "無効にすると、画像は文字認識されません.", - "machine_learning_ocr_max_resolution": "最大解像度", - "machine_learning_ocr_max_resolution_description": "この解像度を超えるプレビューは、アスペクト比を維持しながらサイズが変更されます。値を大きくすると精度は向上しますが、処理に時間がかかり、メモリ使用量も増加します。", - "machine_learning_ocr_min_detection_score": "検出スコアの最低値", - "machine_learning_ocr_min_detection_score_description": "検出するテキストの最小信頼度スコアを0から1の範囲で設定します。値が低いほど多くのテキストが検出されますが、誤検出が発生する可能性があります。", - "machine_learning_ocr_min_recognition_score": "認識スコアの最小値", - "machine_learning_ocr_min_score_recognition_description": "検出されたテキストを認識するための最低信頼スコアを0から1の範囲で設定します。。値が低いほど多くのテキストが認識されますが、誤検出が発生する可能性があります。", - "machine_learning_ocr_model": "OCRモデル", - "machine_learning_ocr_model_description": "サーバーモデルはモバイルモデルよりも正確ですが、処理に時間がかかり、メモリも多く使用します。", - "machine_learning_settings": "機械学習設定", - "machine_learning_settings_description": "機械学習の機能と設定を管理します", - "machine_learning_smart_search": "スマートサーチ", - "machine_learning_smart_search_description": "CLIP埋め込みを使用して画像を意味的に検索します", - "machine_learning_smart_search_enabled": "スマートサーチを有効にします", - "machine_learning_smart_search_enabled_description": "無効にすると、画像はスマートサーチ用にエンコードされません。", - "machine_learning_url_description": "機械学習サーバーのURL。複数のURLが設定された場合は1つずつサーバーが正常に応答するまで接続を試みます。応答のないサーバーはオンラインになるまで一時的に無視されます。", - "maintenance_delete_backup": "バックアップを削除", - "maintenance_delete_backup_description": "このファイルは不可逆的に削除されます。", - "maintenance_delete_error": "バックアップの削除に失敗しました。", - "maintenance_restore_backup": "バックアップを復元", - "maintenance_restore_backup_description": "現在のImmichは削除され、選択したバックアップから復元されます。続行前にバックアップが作成されます。", - "maintenance_restore_backup_different_version": "このバックアップは異なるバージョンのImmichにより作成されたものです!", - "maintenance_restore_backup_unknown_version": "バックアップのバージョンを特定できません。", - "maintenance_restore_database_backup": "データベースのバックアップを復元", - "maintenance_restore_database_backup_description": "バックアップファイルを用いて、以前のデートペースの状態にロールバックします", - "maintenance_settings": "メンテナンス", - "maintenance_settings_description": "Immichをメンテナンスモードにする。", - "maintenance_start": "メンテナンスモードへ切り替える", - "maintenance_start_error": "メンテナンスモードの開始に失敗しました。", - "maintenance_upload_backup": "データベースのバックアップファイルをアップロード", - "maintenance_upload_backup_error": "バックアップをアップロードできません。そのファイルは.sql/.sql.gzファイルですか?", - "manage_concurrency": "同時実行数の管理", - "manage_concurrency_description": "ジョブ ページで、同時並行で稼働するジョブ数を管理できます", - "manage_log_settings": "ログ設定を管理します", - "map_dark_style": "ダークモード", - "map_enable_description": "地図表示機能を有効にします", - "map_gps_settings": "地図・GPS設定", - "map_gps_settings_description": "地図とGPS(逆ジオコーディング)の設定を管理します", - "map_implications": "地図表示機能は外部のタイルサービス(tiles.immich.cloud)に依存します", - "map_light_style": "ライトモード", - "map_manage_reverse_geocoding_settings": "逆ジオコーディングの設定を管理します", - "map_reverse_geocoding": "逆ジオコーディング", - "map_reverse_geocoding_enable_description": "逆ジオコーディング(緯度経度から住所を生成)を有効にする", - "map_reverse_geocoding_settings": "逆ジオコーディング設定", - "map_settings": "地図", - "map_settings_description": "地図設定", - "map_style_description": "マップテーマ(style.json)の参照先URL", - "memory_cleanup_job": "メモリーのクリーンアップ", - "memory_generate_job": "メモリーの生成", - "metadata_extraction_job": "メタデータの展開", - "metadata_extraction_job_description": "GPSや解像度などのメタデータを各アセットから抽出", - "metadata_faces_import_setting": "顔のインポートを有効にする", - "metadata_faces_import_setting_description": "画像のEXIFデータとサイドカーファイルから顔をインポート", - "metadata_settings": "メタデータ設定", - "metadata_settings_description": "メタデータの設定を管理します", - "migration_job": "マイグレーション", - "migration_job_description": "アセットおよび顔のサムネイルを最新のフォルダ構造に移行します", - "nightly_tasks_cluster_faces_setting_description": "新しく検出された顔に対して顔認識を実行", - "nightly_tasks_cluster_new_faces_setting": "新たな顔のクラスター", - "nightly_tasks_database_cleanup_setting": "データベースの掃除タスク", - "nightly_tasks_database_cleanup_setting_description": "期限切れのデータをデータベースから削除", - "nightly_tasks_generate_memories_setting": "メモリーを生成", - "nightly_tasks_generate_memories_setting_description": "項目から新しいメモリーを作成", - "nightly_tasks_missing_thumbnails_setting": "欠けているサムネイルを生成", - "nightly_tasks_missing_thumbnails_setting_description": "サムネイルの欠いている項目をサムネイル生成ジョブの順番待ちにする", - "nightly_tasks_settings": "毎晩行うタスクの設定", - "nightly_tasks_settings_description": "毎晩行うタスクの管理", - "nightly_tasks_start_time_setting": "開始時間", - "nightly_tasks_start_time_setting_description": "このサーバーが毎晩行うタスクを行う時間", - "nightly_tasks_sync_quota_usage_setting": "割当容量の同期", - "nightly_tasks_sync_quota_usage_setting_description": "ユーザーの現在のストレージ使用状況に応じて割当容量を更新", - "no_paths_added": "パスが追加されていません", - "no_pattern_added": "パターンが追加されていません", - "note_apply_storage_label_previous_assets": "注意: 以前にアップロードされたアセットにストレージラベルを適用するには、以下を実行してください", - "note_cannot_be_changed_later": "注意: 後から変更できません!", - "notification_email_from_address": "送信メールアドレス", - "notification_email_from_address_description": "送信メールアドレスを設定します(例: \"Immich Photo Server \" ). 必ずメール送信が許可されているアドレスを使用してください.", - "notification_email_host_description": "送信メールサーバーを設定します(例:smtp.immich.app)", - "notification_email_ignore_certificate_errors": "証明書エラーを無視", - "notification_email_ignore_certificate_errors_description": "TLS証明書の検証エラーを無視します(非推奨)", - "notification_email_password_description": "メールサーバーでの認証時に使用するパスワードを設定します", - "notification_email_port_description": "メールサーバーのポート番号を指定します(例:25, 465, 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "SMTPSを使用 (SMTP over TLS)", - "notification_email_sent_test_email_button": "テストメールを送信して設定を保存", - "notification_email_setting_description": "メール通知の送信設定", - "notification_email_test_email": "テストメールを送信", - "notification_email_test_email_failed": "テストメールの送信に失敗しました。設定を確認してください", - "notification_email_test_email_sent": "{email} へテストメールが送信されました。受信トレイを確認してください。", - "notification_email_username_description": "メールサーバーでの認証時に使用するユーザーネームを設定します", - "notification_enable_email_notifications": "メール通知を有効にします", - "notification_settings": "通知設定", - "notification_settings_description": "メールを含む通知設定を管理します", - "oauth_auto_launch": "自動起動", - "oauth_auto_launch_description": "ログインページに移動すると、OAuthログインフローが自動的に開始されます", - "oauth_auto_register": "自動登録", - "oauth_auto_register_description": "OAuthでサインインしたあと、自動的に新規ユーザーを登録する", - "oauth_button_text": "ボタンテキスト", - "oauth_client_secret_description": "OAuthプロバイダーがPKCEをサポートしていない場合は必要", - "oauth_enable_description": "OAuthでログイン", - "oauth_mobile_redirect_uri": "モバイル用リダイレクトURI", - "oauth_mobile_redirect_uri_override": "モバイル用リダイレクトURI(上書き)", - "oauth_mobile_redirect_uri_override_description": "''{callback}''など、モバイルURIがOAuthプロバイダーによって許可されていない場合に有効にしてください", - "oauth_role_claim": "役職を主張する", - "oauth_role_claim_description": "管理者になると申し立てが行われた場合、自動的に管理者アクセスがその人に付与されるようにします。", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuthログイン設定を管理します", - "oauth_settings_more_details": "この機能の詳細については、ドキュメントを参照してください。", - "oauth_storage_label_claim": "ストレージラベル クレーム", - "oauth_storage_label_claim_description": "ユーザーのストレージラベルを、このクレームの値に自動的に設定します。", - "oauth_storage_quota_claim": "ストレージクォータ クレーム", - "oauth_storage_quota_claim_description": "ユーザーのストレージクォータをこのクレームの値に自動的に設定します。", - "oauth_storage_quota_default": "デフォルトのストレージ割り当て(GiB)", - "oauth_storage_quota_default_description": "クレームが提供されていない場合に使用されるクォータをGiB単位で設定します。", - "oauth_timeout": "リクエストタイムアウト", - "oauth_timeout_description": "リクエストのタイムアウトまでの時間(ms)", - "ocr_job_description": "機械学習を使用して画像内のテキストを認識する", - "password_enable_description": "メールアドレスとパスワードでログイン", - "password_settings": "パスワード ログイン", - "password_settings_description": "パスワード ログイン設定を管理します", - "paths_validated_successfully": "すべてのパスが正常に検証されました", - "person_cleanup_job": "人物のクリーンアップ", - "queue_details": "待機中タスクの詳細", - "queues": "待機中のジョブ", - "queues_page_description": "管理者用 ジョブ待ち列 ページ", - "quota_size_gib": "割り当て容量 (GiB)", - "refreshing_all_libraries": "すべてのライブラリを更新", - "registration": "管理者登録", - "registration_description": "あなたはシステムの最初のユーザーであるため、管理者として割り当てられ、管理タスクを担当し、追加のユーザーはあなたによって作成されます。", - "remove_failed_jobs": "失敗したジョブを削除", - "require_password_change_on_login": "初回ログイン時にパスワード変更を要求する", - "reset_settings_to_default": "設定をデフォルトにリセットします", - "reset_settings_to_recent_saved": "前回の設定値に戻す", - "scanning_library": "ライブラリのスキャン", - "search_jobs": "ジョブを検索…", - "send_welcome_email": "ウェルカム メール を送信します", - "server_external_domain_settings": "外部ドメイン", - "server_external_domain_settings_description": "公開共有リンク用のドメイン( http(s):// を含める)", - "server_public_users": "公開ユーザー", - "server_public_users_description": "共有アルバムにユーザーを追加するとすべてのユーザー (名前とメールアドレス) がリスト化されます。無効にするとユーザーリストは管理者のみ利用可能になります。", - "server_settings": "サーバー設定", - "server_settings_description": "サーバー設定を管理します", - "server_stats_page_description": "管理者用 サーバー統計情報 ページ", - "server_welcome_message": "ウェルカム メッセージ", - "server_welcome_message_description": "ログインページにメッセージを表示します。", - "settings_page_description": "管理者用 設定 ページ", - "sidecar_job": "XMPメタデータ", - "sidecar_job_description": "ファイルシステムからXMPメタデータを検出または同期する", - "slideshow_duration_description": "各画像を表示する秒数", - "smart_search_job_description": "スマート検索をサポートするため、アセットに対して機械学習を実行する", - "storage_template_date_time_description": "アセットの作成タイムスタンプは日時の情報に使用されます", - "storage_template_date_time_sample": "日時の例 {date}", - "storage_template_enable_description": "ストレージ テンプレート エンジンの有効化", - "storage_template_hash_verification_enabled": "ハッシュ検証を有効化", - "storage_template_hash_verification_enabled_description": "ハッシュ検証の有効化(よくわからなければ、有効にしてください)", - "storage_template_migration": "ストレージ テンプレート の移行", - "storage_template_migration_description": "現在の{template}を以前にアップロードされたアセットに適用", - "storage_template_migration_info": "ストレージテンプレートは全ての拡張子を小文字に変換します。テンプレートの変更は新しいアセットにのみ適用されます。 以前にアップロードしたアセットにテンプレートを遡って適用するには、{job} を実行してください。", - "storage_template_migration_job": "ストレージテンプレート移行ジョブ", - "storage_template_more_details": "この機能の詳細については、ストレージテンプレートとその影響を参照してください", - "storage_template_onboarding_description_v2": "この設定をオンにすると、ユーザーの定義したテンプレートに従って自動でファイルが整理されます。詳しい情報はドキュメンテーションで確認してください。", - "storage_template_path_length": "おおよそのパス長の制限: {length, number}/{limit, number}", - "storage_template_settings": "ストレージ テンプレート", - "storage_template_settings_description": "アップロードしたアセットのフォルダ構造とファイル名を管理します", - "storage_template_user_label": "{label}はユーザーのストレージラベルです", - "system_settings": "システム設定", - "tag_cleanup_job": "タグのクリーンアップ", - "template_email_available_tags": "テンプレートで次の変数を使えます: {tags}", - "template_email_if_empty": "テンプレートが空の場合はデフォルトのメールが使われます。", - "template_email_invite_album": "アルバム招待のテンプレート", - "template_email_preview": "プレビュー", - "template_email_settings": "メールテンプレート", - "template_email_update_album": "アルバム更新のテンプレート", - "template_email_welcome": "ウェルカムメールのテンプレート", - "template_settings": "通知テンプレート", - "template_settings_description": "通知のためのカスタムテンプレートを管理します", - "theme_custom_css_settings": "カスタムCSS", - "theme_custom_css_settings_description": "CSS を使って Immich のデザインをカスタマイズできます。", - "theme_settings": "テーマ設定", - "theme_settings_description": "Web インターフェースのカスタマイズを管理します", - "thumbnail_generation_job": "サムネイル生成", - "thumbnail_generation_job_description": "各アセットのサムネイル(大、小、ぼかし)と、各人物のサムネイルを生成します", - "transcoding_acceleration_api": "アクセラレーション API", - "transcoding_acceleration_api_description": "デバイスでハードウェアトランスコードを行うためのAPIです。この設定は『ベストエフォート』であり、失敗した場合はソフトウェアトランスコードになります。VP9はハードウェアによって機能する場合としない場合があります。", - "transcoding_acceleration_nvenc": "NVEnc(NVIDIA GPUが必要)", - "transcoding_acceleration_qsv": "Quick Sync(7世代以降のIntel CPUが必要)", - "transcoding_acceleration_rkmpp": "RKMPP(Rockchip SoC のみ対応)", - "transcoding_acceleration_vaapi": "VA-API", - "transcoding_accepted_audio_codecs": "容認する音声コーデック", - "transcoding_accepted_audio_codecs_description": "トランスコードする必要のない音声コーデックを選択します。特定のトランスコードポリシーにのみ使用されます。", - "transcoding_accepted_containers": "容認するコンテナ", - "transcoding_accepted_containers_description": "MP4に再多重化する必要がないコンテナを選択します。特定のトランスコードポリシーにのみ使用されます。", - "transcoding_accepted_video_codecs": "容認する動画コーデック", - "transcoding_accepted_video_codecs_description": "トランスコードする必要のない動画コーデックを選択します。特定のトランスコードポリシーにのみ使用されます。", - "transcoding_advanced_options_description": "ほとんどのユーザーは変更する必要のないオプション", - "transcoding_audio_codec": "音声コーデック", - "transcoding_audio_codec_description": "Opusが最も品質の高い選択ですが、古いデバイスやソフトウェアとの互換性が低下します。", - "transcoding_bitrate_description": "最大ビットレートを超える動画、または容認されていない形式の動画", - "transcoding_codecs_learn_more": "ここで使用される用語について知るには、H.264 コーデックHEVC コーデックVP9 コーデックについてのFFmpegのドキュメントを参照してください。", - "transcoding_constant_quality_mode": "品質固定モード", - "transcoding_constant_quality_mode_description": "ICQはCQPより優れていますが、一部のハードウェアアクセラレーションデバイスはこのモードをサポートしていません。このオプションを設定すると、品質ベースのエンコードを使用する際に指定されたモードが優先されます。ただし、NVEncではICQをサポートしていないため、この設定は無視されます。", - "transcoding_constant_rate_factor": "CRF値 (-crf)", - "transcoding_constant_rate_factor_description": "出力動画の品質レベル。H.264の場合は23、HEVCの場合は28、VP9の場合は31、AV1の場合は35が一般的な値です。値が低いほど品質が良くなりますが、ファイルサイズが大きくなります。", - "transcoding_disabled_description": "動画をトランスコードしない設定にしますが、これにより一部のクライアントで再生ができなくなる可能性があります", - "transcoding_encoding_options": "エンコードオプション", - "transcoding_encoding_options_description": "エンコードされた動画のコーデック、解像度、画質、その他オプションの設定します", - "transcoding_hardware_acceleration": "ハードウェアアクセラレーション", - "transcoding_hardware_acceleration_description": "より高速ですが、同じビットレートではより低品質になります(実験的)", - "transcoding_hardware_decoding": "ハードウェアデコード", - "transcoding_hardware_decoding_setting_description": "NVEnc、QSV、RKMPP にのみ適用されます。エンコード アクセラレーションだけでなく、エンドツーエンド アクセラレーションを可能にします。すべての動画で動作するとは限りません。", - "transcoding_max_b_frames": "最大Bフレーム", - "transcoding_max_b_frames_description": "値を高くすると圧縮効率が向上しますが、エンコード速度が遅くなります。古いデバイスのハードウェアアクセラレーションでは対応していない場合があります。\"0\" はBフレームを無効にし、\"-1\" はこの値を自動的に設定します。", - "transcoding_max_bitrate": "最大ビットレート", - "transcoding_max_bitrate_description": "最大ビットレートを設定すると、品質にわずかな影響を与えながらも、ファイルサイズを予測しやすくなります。720pの場合、一般的な値は VP9 や HEVC で \"2600 kbit/s\"、H.264 で \"4500 kbit/s\" です。\"0\" に設定すると無効になります。単位が指定されていない場合、k(kbit/s)が適用されます。したがって5000、5000k、5M(5Mbit/s)は等しい設定値です。", - "transcoding_max_keyframe_interval": "最大キーフレーム間隔", - "transcoding_max_keyframe_interval_description": "キーフレーム間の最大フレーム間隔を設定します。値を低くすると圧縮効率が悪化しますが、シーク時間が改善され、動きの速いシーンの品質が向上する場合があります。\"0\" に設定すると、この値が自動的に設定されます。", - "transcoding_optimal_description": "設定解像度を超える動画、または容認されていない形式の動画", - "transcoding_policy": "トランスコードポリシー", - "transcoding_policy_description": "動画がいつトランスコードされるかを設定します", - "transcoding_preferred_hardware_device": "推奨ハードウェアデバイス", - "transcoding_preferred_hardware_device_description": "VAAPI と QSV のみに適用されます。 ハードウェアトランスコードに使用されるdriノードを設定します。", - "transcoding_preset_preset": "プリセット (-preset)", - "transcoding_preset_preset_description": "圧縮速度。遅いプリセットはファイルサイズを小さくし、特定のビットレートを目標とする場合に品質を向上させます。VP9は 'faster'以上の速度を無視します。", - "transcoding_reference_frames": "参照フレーム", - "transcoding_reference_frames_description": "特定のフレームを圧縮するときに参照するフレームの数。より高い値は圧縮効率を改善しますが、エンコードが遅くなります。\"0\" に設定すると、この値が自動的に設定されます。", - "transcoding_required_description": "許容されていない動画形式のみ", - "transcoding_settings": "動画トランスコード設定", - "transcoding_settings_description": "トランスコードする動画とその処理方法を管理します", - "transcoding_target_resolution": "解像度", - "transcoding_target_resolution_description": "解像度を高くすると細かなディテールを保持できますが、エンコードに時間がかかり、ファイルサイズが大きくなり、アプリの応答性が低下する可能性があります。", - "transcoding_temporal_aq": "適応的量子化(Temporal AQ)", - "transcoding_temporal_aq_description": "NVEncにのみ適用されます。Temporal AQは高精細で動きの少ないシーンの画質を向上させます。古いデバイスとの互換性はありません。", - "transcoding_threads": "スレッド数", - "transcoding_threads_description": "値を高くするとエンコード速度が速くなりますが、アクティブな間はサーバーが他のタスクを処理する余裕が少なくなります。この値はCPUのコア数を超えないようにする必要があります。\"0\" に設定すると、最大限利用されます。", - "transcoding_tone_mapping": "トーンマッピング", - "transcoding_tone_mapping_description": "HDR動画をSDRに変換する際に見た目を維持しようと試みます。各アルゴリズムは、色、詳細、明るさに対して異なるトレードオフを行います。Hableは詳細を維持し、Mobiusは色を維持し、Reinhardは明るさを維持します。", - "transcoding_transcode_policy": "トランスコードポリシー", - "transcoding_transcode_policy_description": "動画がトランスコードされるべきかを決めるポリシー。HDR動画は常にトランスコードされます(トランスコードが無効化されている場合を除く)。", - "transcoding_two_pass_encoding": "Two-passエンコード", - "transcoding_two_pass_encoding_setting_description": "二つのパスでトランスコードし、よりよくエンコードされた動画を生成します。最大ビットレートが有効になっている場合(H.264とHEVCが動作するために必要)、このモードは最大ビットレートを基にしたビットレートの範囲を使用し、CRFを無視します。VP9については最大ビットレートの無効時にCRFを使うことができます。", - "transcoding_video_codec": "動画コーデック", - "transcoding_video_codec_description": "VP9 は高い効率とWeb 互換性を持ちますが、トランスコードに時間がかかります。 HEVC も同様ですが、Web 互換性が低くなります。 H.264 は幅広い互換性がありトランスコードが高速ですが、生成されるファイルははるかに大きくなります。 AV1 は最も効率的なコーデックですが、古いデバイスではサポートされていません。", - "trash_enabled_description": "ごみ箱機能を有効化", - "trash_number_of_days": "日数", - "trash_number_of_days_description": "項目を完全に削除する前にごみ箱に保管しておく日数", - "trash_settings": "ごみ箱の設定", - "trash_settings_description": "ごみ箱の設定を管理します", - "unlink_all_oauth_accounts": "全てのOAuthアカウントをリンク解除", - "unlink_all_oauth_accounts_description": "新しいプロバイダーに移行する前に、すべてのOAuthアカウントのリンクを解除してください。", - "unlink_all_oauth_accounts_prompt": "すべてのOAuthアカウントを解除しますか?これにより、各ユーザーのOAuth IDがリセットされ、元に戻すことはできません。", - "user_cleanup_job": "ユーザーのクリーンアップ", - "user_delete_delay": "{user}のアカウントとアセットは{delay, plural, one {#日} other {#日}}後に完全に削除されるように予定されます。", - "user_delete_delay_settings": "遅延削除", - "user_delete_delay_settings_description": "削除実行後、ユーザーのアカウントとアセットが完全に削除されるまでの日数。 ユーザー削除ジョブは深夜に実行され、削除の準備ができているユーザーを確認します。 この設定への変更は、次回の実行時に反映されます。", - "user_delete_immediately": "{user} のアカウントとアセットは、直ちに完全に削除するためにキューに追加されます。", - "user_delete_immediately_checkbox": "ユーザーとアセットを即時削除するキューに入れる", - "user_details": "ユーザー詳細", - "user_management": "ユーザー管理", - "user_password_has_been_reset": "ユーザーのパスワードがリセットされました:", - "user_password_reset_description": "ユーザーにこの一時パスワードを提供し、次回ログイン時にパスワードを変更しなければならないことを伝えてください。", - "user_restore_description": "{user}のアカウントは復元されました。", - "user_restore_scheduled_removal": "ユーザーを復元 - {date, date, long}に削除予定", - "user_settings": "ユーザー設定", - "user_settings_description": "ユーザー設定を管理します", - "user_successfully_removed": "ユーザー {email} は正常に削除されました。", - "users_page_description": "管理者用 ユーザー ページ", - "version_check_enabled_description": "バージョンの確認を有効にする", - "version_check_implications": "このバージョン確認機能は定期的なgithub.comとの通信によります", - "version_check_settings": "バージョンチェック", - "version_check_settings_description": "新しいバージョンの通知を有効/無効にします", - "video_conversion_job": "動画をトランスコード", - "video_conversion_job_description": "幅広いブラウザやデバイスとの互換性を高めるために動画をトランスコードします" - }, - "admin_email": "管理者Email", - "admin_password": "管理者パスワード", - "administration": "管理", - "advanced": "詳細設定", - "advanced_settings_clear_image_cache": "画像のキャッシュを削除", - "advanced_settings_clear_image_cache_error": "画像のキャッシュの削除に失敗しました", - "advanced_settings_clear_image_cache_success": "{size}の削除に成功しました", - "advanced_settings_enable_alternate_media_filter_subtitle": "別の基準に従ってメディアファイルにフィルターをかけて、同期を行います。アプリがすべてのアルバムを読み込んでくれない場合にのみ、この機能を試してください。", - "advanced_settings_enable_alternate_media_filter_title": "[試験運用] 別のデバイスのアルバム同期フィルターを使用する", - "advanced_settings_log_level_title": "ログレベル: {level}", - "advanced_settings_prefer_remote_subtitle": "デバイスによっては、デバイス上にあるサムネイルのロードに非常に時間がかかることがあります。このオプションを有効にする事により、サーバーから直接画像をロードすることが可能です。", - "advanced_settings_prefer_remote_title": "リモートを優先する", - "advanced_settings_proxy_headers_subtitle": "プロキシヘッダを設定する", - "advanced_settings_proxy_headers_title": "カスタムプロキシヘッダ [実験的]", - "advanced_settings_readonly_mode_subtitle": "読み取り専用モードを有効にすると、写真の複数選択や、共有、削除、キャスト機能が無効になります。メインスクリーンのユーザーアバターから有効/無効を切り替えられます", - "advanced_settings_readonly_mode_title": "読み取り専用モード", - "advanced_settings_self_signed_ssl_subtitle": "SSLのチェックをスキップする。自己署名証明書が必要です。", - "advanced_settings_self_signed_ssl_title": "自己署名証明書を許可する [実験的]", - "advanced_settings_sync_remote_deletions_subtitle": "Webでこの操作を行った際に、自動的にこのデバイス上から項目を削除または復元する", - "advanced_settings_sync_remote_deletions_title": "リモート削除の同期 [試験運用]", - "advanced_settings_tile_subtitle": "追加ユーザー設定", - "advanced_settings_troubleshooting_subtitle": "トラブルシューティング用の詳細設定をオンにする", - "advanced_settings_troubleshooting_title": "トラブルシューティング", - "age_months": "生後 {months,plural, one {#か月} other {#か月}}", - "age_year_months": "1歳{months,plural, one {#か月} other {#か月}}", - "age_years": "{years,plural, one {#歳} other {#歳}}", - "album": "アルバム", - "album_added": "アルバム追加", - "album_added_notification_setting_description": "共有アルバムに追加されたときEメール通知を受信する", - "album_cover_updated": "アルバムカバー更新", - "album_delete_confirmation": "アルバム{album}を本当に削除しますか?", - "album_delete_confirmation_description": "このアルバムが共有されているなら、他のユーザーもアルバムにアクセスできなくなります。", - "album_deleted": "アルバムが削除されました", - "album_info_card_backup_album_excluded": "除外中", - "album_info_card_backup_album_included": "選択中", - "album_info_updated": "アルバム情報更新", - "album_leave": "アルバムから去りますか?", - "album_leave_confirmation": "本当に {album} から去りますか?", - "album_name": "アルバム名", - "album_options": "アルバム設定", - "album_remove_user": "ユーザーを削除しますか?", - "album_remove_user_confirmation": "本当に{user}を削除しますか?", - "album_search_not_found": "検索に一致するアルバムがありません", - "album_selected": "アルバム選択中", - "album_share_no_users": "このアルバムを全てのユーザーと共有したか、共有するユーザーがいないようです。", - "album_summary": "アルバムのまとめ", - "album_updated": "アルバム更新", - "album_updated_setting_description": "共有アルバムに新しい項目が追加されたとき通知を受け取る", - "album_upload_assets": "コンピュータから項目をアップロードし、アルバムに追加する", - "album_user_left": "{album} を去りました", - "album_user_removed": "{user} を削除しました", - "album_viewer_appbar_delete_confirm": "本当にこのアルバムを削除しますか?", - "album_viewer_appbar_share_err_delete": "アルバムの削除に失敗しました", - "album_viewer_appbar_share_err_leave": "退出に失敗しました", - "album_viewer_appbar_share_err_remove": "アルバムから写真を削除する際にエラー発生", - "album_viewer_appbar_share_err_title": "タイトル変更の失敗", - "album_viewer_appbar_share_leave": "アルバムから退出", - "album_viewer_appbar_share_to": "次の方々と共有します", - "album_viewer_page_share_add_users": "ユーザーを追加", - "album_with_link_access": "リンクを知っている人は誰でも、このアルバム中の写真と人物を閲覧できるようになります。", - "albums": "アルバム", - "albums_count": "{count, plural, one {{count, number} 件} other {{count, number} 件}}のアルバム", - "albums_default_sort_order": "デフォルトのアルバム表示順", - "albums_default_sort_order_description": "新規アルバム作成時の初期表示順.", - "albums_feature_description": "他のユーザーと共有できるアセットのコレクション.", - "albums_on_device_count": "デバイス上のアルバム ({count})", - "albums_selected": "{count, plural, one {# アルバム選択中} other {# アルバム選択中}}", - "all": "すべて", - "all_albums": "全てのアルバム", - "all_people": "全ての人物", - "all_photos": "全ての写真", - "all_videos": "全ての動画", - "allow_dark_mode": "ダークモードを許可", - "allow_edits": "編集を許可", - "allow_public_user_to_download": "一般ユーザーによるダウンロードを許可", - "allow_public_user_to_upload": "一般ユーザーによるアップロードを許可", - "allowed": "許可されている", - "alt_text_qr_code": "QRコード画像", - "always_keep": "常に保持", - "always_keep_photos_hint": "「ストレージを解放」で、全ての写真がこのデバイスに保持されます。", - "always_keep_videos_hint": "「ストレージを解放」で、全ての動画がこのデバイスに保持されます。", - "anti_clockwise": "反時計回り", - "api_key": "APIキー", - "api_key_description": "この値は一回のみ表示されます。 ウィンドウを閉じる前に必ずコピーしてください。", - "api_key_empty": "APIキー名は空白にできません", - "api_keys": "APIキー", - "app_architecture_variant": "CPUアーキテクチャ (ABI)", - "app_bar_signout_dialog_content": "サインアウトしますか?", - "app_bar_signout_dialog_ok": "はい", - "app_bar_signout_dialog_title": "サインアウト", - "app_download_links": "アプリのダウンロードリンク", - "app_settings": "アプリ設定", - "app_stores": "アプリストア", - "app_update_available": "アプリのアップデートが利用可能です", - "appears_in": "これらに含まれます", - "apply_count": "適用 ({count, number})", - "archive": "アーカイブ", - "archive_action_prompt": "アーカイブに{count}項目追加しました", - "archive_or_unarchive_photo": "写真をアーカイブまたはアーカイブ解除", - "archive_page_no_archived_assets": "アーカイブした写真またはビデオがありません", - "archive_page_title": "アーカイブ ({count})", - "archive_size": "アーカイブサイズ", - "archive_size_description": "ダウンロードのアーカイブ サイズを設定(GiB 単位)", - "archived": "アーカイブ", - "archived_count": "アーカイブされた{count, plural, other {#個の項目}}", - "are_these_the_same_person": "これらは同じ人物ですか?", - "are_you_sure_to_do_this": "本当にこれを行いますか?", - "array_field_not_fully_supported": "配列フィールドは手動でJSON編集する必要があります", - "asset_action_delete_err_read_only": "読み取り専用の項目は削除できません。スキップします", - "asset_action_share_err_offline": "オフラインの項目をゲットできません。スキップします", - "asset_added_to_album": "アルバムに追加", - "asset_adding_to_album": "アルバムに追加しています…", - "asset_created": "項目が作成されました", - "asset_description_updated": "項目の説明文が更新されました", - "asset_filename_is_offline": "項目 {filename} がオフラインです", - "asset_has_unassigned_faces": "項目に名前のついていない人物の顔があります", - "asset_hashing": "ハッシュ計算中…", - "asset_list_group_by_sub_title": "グループ分け", - "asset_list_layout_settings_dynamic_layout_title": "ダイナミックレイアウト", - "asset_list_layout_settings_group_automatically": "自動", - "asset_list_layout_settings_group_by": "写真のグループ分け", - "asset_list_layout_settings_group_by_month_day": "月 + 日", - "asset_list_layout_sub_title": "レイアウト", - "asset_list_settings_subtitle": "グリッドに関する設定", - "asset_list_settings_title": "グリッド", - "asset_not_found_on_device_android": "デバイス上に写真/動画が見つかりません", - "asset_not_found_on_device_ios": "デバイス上に写真/動画が見つかりませんでした。iCloudを併せてご利用の場合は、iCloudのファイル保管方法に問題があり、アクセスできない可能性があります。", - "asset_not_found_on_icloud": "iCloud上の写真/動画が見つかりませんでした。", - "asset_offline": "項目がオフラインです", - "asset_offline_description": "この外部項目はディスク上にもうありません。Immichサーバーの管理者に連絡をしてください。", - "asset_restored_successfully": "復元できました", - "asset_skipped": "スキップ済", - "asset_skipped_in_trash": "ゴミ箱の中", - "asset_trashed": "項目が削除されました", - "asset_troubleshoot": "項目をトラブルシューㇳ", - "asset_uploaded": "アップロード済", - "asset_uploading": "アップロード中…", - "asset_viewer_settings_subtitle": "ギャラリービューアーに関する設定", - "asset_viewer_settings_title": "アセットビューアー", - "assets": "アセット", - "assets_added_count": "{count, plural, one {#個} other {#個}}のアセットを追加しました", - "assets_added_to_album_count": "{count, plural, one {#個} other {#個}}のアセットをアルバムに追加しました", - "assets_added_to_albums_count": "{assetTotal, plural, one {# 項目} other {# 項目}}を{albumTotal, plural, one {# つのアルバム} other {# つのアルバム}}に追加しました", - "assets_cannot_be_added_to_album_count": "{count, plural, one {アセット} other {アセット}} はアルバムに追加できません", - "assets_cannot_be_added_to_albums": "{count, plural, one {項目} other {項目}} をどのアルバムにも追加できませんでした", - "assets_count": "{count, plural, one {#個} other {#個}}のアセット", - "assets_deleted_permanently": "{count}項目を完全に削除しました", - "assets_deleted_permanently_from_server": "サーバー上の{count}項目を完全に削除しました", - "assets_downloaded_failed": "{count, plural, other {# ファイル中 {error} ファイルのダウンロードに失敗しました}}", - "assets_downloaded_successfully": "{count, plural, other {# ファイルのダウンロードに成功しました}}", - "assets_moved_to_trash_count": "{count, plural, one {#項目} other {#項目}}をゴミ箱に移動しました", - "assets_permanently_deleted_count": "{count, plural, one {#項目} other {#項目}}を完全に削除しました", - "assets_removed_count": "{count, plural, one {#項目} other {#項目}}を削除しました", - "assets_removed_permanently_from_device": "デバイスから{count}項目を完全に削除しました", - "assets_restore_confirmation": "ごみ箱のアセットをすべて復元してもよろしいですか? この操作を元に戻すことはできません! オフラインのアセットはこの方法では復元できません。", - "assets_restored_count": "{count, plural, one {#} other {#}}項目を復元しました", - "assets_restored_successfully": "{count}項目を復元しました", - "assets_trashed": "{count}項目をゴミ箱に移動しました", - "assets_trashed_count": "{count, plural, one {#個} other {#個}}のアセットをごみ箱に移動しました", - "assets_trashed_from_server": "サーバー上の{count}項目をゴミ箱に移動しました", - "assets_were_part_of_album_count": "{count, plural, one {個} other {個}}のアセットは既にアルバムの一部です", - "assets_were_part_of_albums_count": "{count, plural, one {項目は} other {項目は}}すでにアルバムに追加されているものでした", - "authorized_devices": "認可済みデバイス", - "automatic_endpoint_switching_subtitle": "指定されたWi-Fiに接続時のみローカル接続を行い、他のネットワーク下では通常通りの接続を行います", - "automatic_endpoint_switching_title": "自動URL切り替え", - "autoplay_slideshow": "スライドショーを自動再生", - "back": "戻る", - "back_close_deselect": "戻る、閉じる、選択解除", - "background_backup_running_error": "バックグラウンドのバックアップがすでに行われている最中です。そのため、マニュアルでのバックアップを開始することはできません", - "background_location_permission": "バックグラウンド位置情報アクセス", - "background_location_permission_content": "正常にWi-Fiの名前(SSID)を獲得するにはアプリが常に詳細な位置情報にアクセスできる必要があります", - "background_options": "バックグラウンドの動作オプション", - "backup": "バックアップ", - "backup_album_selection_page_albums_device": "デバイス上のアルバム({count})", - "backup_album_selection_page_albums_tap": "タップで選択、ダブルタップで除外", - "backup_album_selection_page_assets_scatter": "アルバムを選択・除外してバックアップする写真を選ぶ。 (同じ写真が複数のアルバムに登録されていることがあるため)", - "backup_album_selection_page_select_albums": "アルバムを選択", - "backup_album_selection_page_selection_info": "選択・除外中のアルバム", - "backup_album_selection_page_total_assets": "選択されたアルバムの写真と動画の数", - "backup_albums_sync": "アルバム同期状態をバックアップ", - "backup_all": "すべて", - "backup_background_service_backup_failed_message": "アップロードに失敗しました。リトライ中…", - "backup_background_service_complete_notification": "写真/動画のバックアップが完了しました", - "backup_background_service_connection_failed_message": "サーバーに接続できません。リトライ中…", - "backup_background_service_current_upload_notification": "{filename}をアップロード中", - "backup_background_service_default_notification": "新しい写真を確認中…", - "backup_background_service_error_title": "バックアップエラー", - "backup_background_service_in_progress_notification": "バックアップ中…", - "backup_background_service_upload_failure_notification": "{filename}のアップロードに失敗", - "backup_controller_page_albums": "アルバム", - "backup_controller_page_background_app_refresh_disabled_content": "バックグラウンドで写真のバックアップを行いたい場合は、バックグラウンド更新を\n設定 > 一般 > Appのバックグラウンド更新\nからオンにしてください。", - "backup_controller_page_background_app_refresh_disabled_title": "バックグラウンド更新はオフになっています", - "backup_controller_page_background_app_refresh_enable_button_text": "設定を開く", - "backup_controller_page_background_battery_info_link": "詳細", - "backup_controller_page_background_battery_info_message": "バックグラウンド処理を正常に動作させるためには、Immichアプリに適用されているバッテリーの最適化をオフにしてください。\n\nデバイスによって設定方法が異なりますので各々調べてください。", - "backup_controller_page_background_battery_info_ok": "了解", - "backup_controller_page_background_battery_info_title": "バッテリーの最適化", - "backup_controller_page_background_charging": "充電中のみ", - "backup_controller_page_background_configure_error": "バックグラウンドサービスの設定に失敗", - "backup_controller_page_background_delay": "新しい項目のバックアップ開始まで待つ時間: {duration}", - "backup_controller_page_background_description": "アプリを開いていないときもバックアップを行います", - "backup_controller_page_background_is_off": "バックグランドサービスがオフになっています", - "backup_controller_page_background_is_on": "バックグランドサービスがオンになっています", - "backup_controller_page_background_turn_off": "バックグラウンドサービスをオフにする", - "backup_controller_page_background_turn_on": "バックグラウンドサービスをオンにする", - "backup_controller_page_background_wifi": "WiFi接続中のみ", - "backup_controller_page_backup": "バックアップ", - "backup_controller_page_backup_selected": "選択中: ", - "backup_controller_page_backup_sub": "バックアップされた写真と動画の数", - "backup_controller_page_created": "作成日: {date}", - "backup_controller_page_desc_backup": "アプリを開いているときに写真と動画をバックアップします。", - "backup_controller_page_excluded": "除外中のアルバム: ", - "backup_controller_page_failed": "失敗: ({count})", - "backup_controller_page_filename": "ファイル名: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "バックアップ情報", - "backup_controller_page_none_selected": "なし", - "backup_controller_page_remainder": "残り", - "backup_controller_page_remainder_sub": "残りの写真と動画の数", - "backup_controller_page_server_storage": "サーバー容量", - "backup_controller_page_start_backup": "バックアップ開始", - "backup_controller_page_status_off": "バックアップがオフになっています", - "backup_controller_page_status_on": "バックアップがオンになっています", - "backup_controller_page_storage_format": "使用中: {used} / {total}", - "backup_controller_page_to_backup": "バックアップされるアルバム", - "backup_controller_page_total_sub": "選択されたアルバムの写真と動画の数", - "backup_controller_page_turn_off": "バックアップをオフにする", - "backup_controller_page_turn_on": "バックアップをオンにする", - "backup_controller_page_uploading_file_info": "アップロード中のファイル", - "backup_err_only_album": "最低1つのアルバムを選択してください", - "backup_error_sync_failed": "同期に失敗しました。バックアップができません。", - "backup_info_card_assets": "写真と動画", - "backup_manual_cancelled": "キャンセルされました", - "backup_manual_in_progress": "アップロードが進行中です。後でもう一度試してください", - "backup_manual_success": "成功", - "backup_manual_title": "アップロード状況", - "backup_options": "バックアップオプション", - "backup_options_page_title": "バックアップオプション", - "backup_setting_subtitle": "アップロードに関する設定", - "backup_settings_subtitle": "アップロード設定を管理", - "backup_upload_details_page_more_details": "タップで詳細閲覧", - "backward": "新しい方へ", - "biometric_auth_enabled": "生体認証を有効化しました", - "biometric_locked_out": "生体認証により、アクセスできません", - "biometric_no_options": "生体認証を利用できません", - "biometric_not_available": "このデバイスでは生体認証をご利用いただけません", - "birthdate_saved": "生年月日が正常に保存されました", - "birthdate_set_description": "生年月日は、写真撮影時のこの人物の年齢を計算するために使用されます。", - "blurred_background": "ぼやけた背景", - "bugs_and_feature_requests": "バグと機能のリクエスト", - "build": "ビルド", - "build_image": "ビルドイメージ", - "bulk_delete_duplicates_confirmation": "本当に {count, plural, one {#} other {#}}の重複した項目を一括削除しますか?これにより、重複した画像それぞれの中で最もサイズの大きいものを残し、他の全ての重複が削除されます。この操作を元に戻すことはできません!", - "bulk_keep_duplicates_confirmation": "本当に{count, plural, one {#個} other {#個}}の重複した項目を保持しますか?これにより何も削除されずに重複グループが解決されます。", - "bulk_trash_duplicates_confirmation": "本当に{count, plural, one {#個} other {#個}}の重複した項目を一括でごみ箱に移動しますか?これにより各重複中の最大サイズの項目が保持され、他の全ての重複はごみ箱に移動されます。", - "buy": "Immichを購入", - "cache_settings_clear_cache_button": "キャッシュをクリア", - "cache_settings_clear_cache_button_title": "キャッシュを削除 (キャッシュが再生成されるまで、アプリのパフォーマンスが著しく低下します)", - "cache_settings_duplicated_assets_clear_button": "クリア", - "cache_settings_duplicated_assets_subtitle": "サーバーにアップロード済みと認識された写真や動画の数", - "cache_settings_duplicated_assets_title": "重複した項目数: ({count})", - "cache_settings_statistics_album": "ライブラリのサムネイル", - "cache_settings_statistics_full": "フル画像", - "cache_settings_statistics_shared": "共有アルバムのサムネイル", - "cache_settings_statistics_thumbnail": "サムネイル", - "cache_settings_statistics_title": "キャッシュ", - "cache_settings_subtitle": "キャッシュの動作を変更する", - "cache_settings_tile_subtitle": "ローカルストレージの挙動を確認する", - "cache_settings_tile_title": "ローカルストレージ", - "cache_settings_title": "キャッシュの設定", - "camera": "カメラブランド", - "camera_brand": "カメラブランド", - "camera_model": "カメラモデル", - "cancel": "キャンセル", - "cancel_search": "検索をキャンセル", - "canceled": "キャンセルされました", - "canceling": "キャンセル中", - "cannot_merge_people": "人物を統合できません", - "cannot_undo_this_action": "この操作は元に戻せません!", - "cannot_update_the_description": "説明を更新できません", - "cast": "キャスト", - "cast_description": "キャスト先の候補を設定", - "change_date": "日時を変更", - "change_description": "説明文を変更", - "change_display_order": "表示順を変更", - "change_expiration_time": "有効期限を変更", - "change_location": "場所を変更", - "change_name": "名前を変更", - "change_name_successfully": "名前を正常に変更しました", - "change_password": "パスワードを変更", - "change_password_description": "これは、初めてのサインインであるか、パスワードの変更要求が行われたかのいずれかです。 新しいパスワードを下に入力してください。", - "change_password_form_confirm_password": "確定", - "change_password_form_description": "{name}さん こんにちは\n\nサーバーにアクセスするのが初めてか、パスワードリセットのリクエストがされました。新しいパスワードを入力してください。", - "change_password_form_log_out": "他の全てのデバイスからログアウトさせる", - "change_password_form_log_out_description": "他のすべてのデバイスからログアウトすることをお勧めします", - "change_password_form_new_password": "新しいパスワード", - "change_password_form_password_mismatch": "パスワードが一致しません", - "change_password_form_reenter_new_password": "再度パスワードを入力してください", - "change_pin_code": "PINコードを変更", - "change_trigger": "トリガーを変更", - "change_trigger_prompt": "トリガーを変えてもよいですか?アクション・フィルターが全て削除されます", - "change_your_password": "パスワードを変更します", - "changed_visibility_successfully": "非表示設定を正常に変更しました", - "charging": "充電中", - "charging_requirement_mobile_backup": "バックグラウンドでのバックアップを行うためには、デバイスが充電中である必要があります", - "check_corrupt_asset_backup": "破損されている項目を探す", - "check_corrupt_asset_backup_button": "チェックを行う", - "check_corrupt_asset_backup_description": "写真や動画などが全てアップロードし終えてからWi-Fiに接続時のみチェックを行なってください。作業が完了するには数分かかる場合があります。", - "check_logs": "ログを確認", - "checksum": "チェックサム", - "choose_matching_people_to_merge": "統合先の人物を選んでください", - "city": "市町村", - "cleanup_confirm_description": "サーバーにバックアップ済みの写真/動画({date}以前に作成)を{count}件発見しました。このデバイスからローカルコピーを削除しますか?", - "cleanup_confirm_prompt_title": "このデバイスから削除しますか?", - "cleanup_deleted_assets": "{count}件の写真/動画をデバイスのゴミ箱に移動しました", - "cleanup_deleting": "ゴミ箱に移動中…", - "cleanup_found_assets": "{count}件のバックアップ済み写真/動画を検出", - "cleanup_found_assets_with_size": "{count}個の写真/動画のバックアップが見つかりました({size})", - "cleanup_icloud_shared_albums_excluded": "iCloudの共有アルバムはスキャンの対象外になります", - "cleanup_no_assets_found": "上記の条件に当てはまる写真/動画が見つかりませんでした。「ストレージを解放」はサーバにバックアップされている写真/動画のみ削除できます", - "cleanup_preview_title": "削除される写真/動画 ({count})", - "cleanup_step3_description": "あなたの設定した期間に合致するバックアップ済み写真/動画を探し出し、設定を維持します。", - "cleanup_step4_summary": "あなたのローカルデバイスから{count}枚の写真/動画({date}以前に作成されたもの)が削除されます。操作後も写真はImmichアプリからアクセスできます。", - "cleanup_trash_hint": "ストレージの容量を取り戻すには、システムのギャラリーアプリを開き、ゴミ箱を空にしてください", - "clear": "クリア", - "clear_all": "全てクリア", - "clear_all_recent_searches": "全ての最近の検索をクリア", - "clear_file_cache": "ファイルキャッシュを削除", - "clear_message": "メッセージをクリア", - "clear_value": "値をクリア", - "client_cert_dialog_msg_confirm": "了解", - "client_cert_enter_password": "パスワードを入力", - "client_cert_import": "インポート", - "client_cert_import_success_msg": "クライアント証明書が導入されました", - "client_cert_invalid_msg": "パスワードが間違っているか証明書が無効です", - "client_cert_remove_msg": "クライアント証明書が削除されました", - "client_cert_subtitle": "PKCS12 (.p12 .pfx) フォーマットのみ対応しています。証明書の導入や削除はログイン前のみ行えます", - "client_cert_title": "SSLクライアント証明書 [実験的]", - "clockwise": "時計回り", - "close": "閉じる", - "collapse": "展開", - "collapse_all": "全て展開", - "color": "カラー", - "color_theme": "カラーテーマ", - "command": "コマンド", - "comment_deleted": "コメントが削除されました", - "comment_options": "コメント設定", - "comments_and_likes": "コメントといいね", - "comments_are_disabled": "コメントは無効化されています", - "common_create_new_album": "アルバムを作成", - "completed": "完了", - "confirm": "確認", - "confirm_admin_password": "管理者パスワードを確認", - "confirm_delete_face": "本当に『{name}』の顔を項目から削除しますか?", - "confirm_delete_shared_link": "本当にこの共有リンクを削除しますか?", - "confirm_keep_this_delete_others": "この項目以外の項目がスタックから削除されます。本当に削除しますか?", - "confirm_new_pin_code": "このPINコードを使う", - "confirm_password": "確認", - "confirm_tag_face": "この顔を{name}として登録しますか?", - "confirm_tag_face_unnamed": "この顔を登録しますか?", - "connected_device": "接続されたデバイス", - "connected_to": "接続:", - "contain": "収める", - "context": "状況", - "continue": "続ける", - "control_bottom_app_bar_create_new_album": "アルバムを作成", - "control_bottom_app_bar_delete_from_immich": "サーバーから削除", - "control_bottom_app_bar_delete_from_local": "デバイス上から削除", - "control_bottom_app_bar_edit_location": "位置情報を編集", - "control_bottom_app_bar_edit_time": "撮影日時を編集", - "control_bottom_app_bar_share_link": "共有リンク", - "control_bottom_app_bar_share_to": "次のユーザーに共有:", - "control_bottom_app_bar_trash_from_immich": "ゴミ箱に入れる", - "copied_image_to_clipboard": "画像をクリップボードにコピーしました。", - "copied_to_clipboard": "クリップボードにコピーしました!", - "copy_error": "コピーのエラー", - "copy_file_path": "ファイルパスをコピー", - "copy_image": "画像をコピー", - "copy_link": "リンクをコピー", - "copy_link_to_clipboard": "リンクをクリップボードにコピー", - "copy_password": "パスワードをコピー", - "copy_to_clipboard": "クリップボードにコピー", - "country": "国", - "cover": "カバー", - "covers": "カバー", - "create": "作成", - "create_album": "アルバムを作成", - "create_album_page_untitled": "無題のタイトル", - "create_api_key": "APIキーを作成", - "create_first_workflow": "初めてのワークフローを作成", - "create_library": "ライブラリを作成", - "create_link": "リンクを作る", - "create_link_to_share": "共有リンクを作る", - "create_link_to_share_description": "リンクを知っている人全員が選択した写真を閲覧できるようになります", - "create_new": "新規作成", - "create_new_person": "新しい人物を作成", - "create_new_person_hint": "選択した写真/動画を新しい人物として割り当て", - "create_new_user": "新規ユーザーの作成", - "create_shared_album_page_share_add_assets": "写真を追加", - "create_shared_album_page_share_select_photos": "写真を選択", - "create_shared_link": "共有リンクを作成", - "create_tag": "タグを作成する", - "create_tag_description": "タグを作成します。入れ子構造のタグは、はじめのスラッシュを含めた、タグの完全なパスを入力してください。", - "create_user": "ユーザーを作成", - "create_workflow": "ワークフローを作成", - "created": "作成", - "created_at": "作成:", - "creating_linked_albums": "リンクされたアルバムを作成中・・・", - "crop": "クロップ", - "crop_aspect_ratio_fixed": "固定", - "crop_aspect_ratio_free": "自由", - "crop_aspect_ratio_original": "オリジナル", - "curated_object_page_title": "被写体", - "current_device": "現在のデバイス", - "current_pin_code": "現在のPINコード", - "current_server_address": "現在のサーバーURL", - "custom_date": "カスタム日付", - "custom_locale": "カスタムロケール", - "custom_locale_description": "言語と地域に基づいて日付と数値をフォーマットします", - "custom_url": "カスタムURL", - "cutoff_date_description": "写真を保持する期間:", - "cutoff_day": "{count, plural, one {(日)} other {(日)}}", - "cutoff_year": "{count, plural, one {年} other {年}}", - "daily_title_text_date": "MM DD, EE", - "daily_title_text_date_year": "yyyy MM DD, EE", - "dark": "ダークモード", - "dark_theme": "ダークモード切り替え", - "date": "日付", - "date_after": "この日以降", - "date_and_time": "日付と時間", - "date_before": "この日以前", - "date_format": "MM DD, EE • hh:mm", - "date_of_birth_saved": "生年月日は正常に保存されました", - "date_range": "日付", - "day": "ライトモード", - "days": "日", - "deduplicate_all": "全て重複排除", - "deduplication_criteria_1": "バイト単位の画像サイズ", - "deduplication_criteria_2": "EXIFデータ数", - "deduplication_info": "重複排除情報", - "deduplication_info_description": "写真/動画を自動的に選択して重複を一括で削除するには次のようにします:", - "default_locale": "デフォルトのロケール", - "default_locale_description": "ブラウザのロケールに基づいて日付と数値をフォーマットします", - "delete": "削除", - "delete_action_confirmation_message": "この項目を削除しますか?まず、この項目はサーバー上のゴミ箱へ移動されます。その後、あなたのデバイス上から削除するかを決めていただきます", - "delete_action_prompt": "{count}項目を削除しました", - "delete_album": "アルバムを削除", - "delete_api_key_prompt": "本当にこのAPI キーを削除しますか?", - "delete_dialog_alert": "選択された項目はサーバーとデバイスの両方から完全に削除されます", - "delete_dialog_alert_local": "選択された項目はデバイスから完全に削除されますが、サーバーには残ります", - "delete_dialog_alert_local_non_backed_up": "選択された項目の一部はサーバーにバックアップされておらず、デバイスから完全に削除されます", - "delete_dialog_alert_remote": "選択された項目はサーバーから完全に削除されます", - "delete_dialog_ok_force": "削除します", - "delete_dialog_title": "完全に削除", - "delete_duplicates_confirmation": "本当にこれらの重複を完全に削除しますか?", - "delete_face": "顔の削除", - "delete_key": "キーを削除", - "delete_library": "ライブラリを削除", - "delete_link": "リンクを削除", - "delete_local_action_prompt": "{count}項目をデバイスから削除しました", - "delete_local_dialog_ok_backed_up_only": "バックアップ済みのみを削除", - "delete_local_dialog_ok_force": "削除します", - "delete_others": "ほかを削除", - "delete_permanently": "完全に削除", - "delete_permanently_action_prompt": "{count}項目が完全に削除されました", - "delete_shared_link": "共有リンクを消す", - "delete_shared_link_dialog_title": "共有リンクを消す", - "delete_tag": "タグを削除する", - "delete_tag_confirmation_prompt": "本当に{tagName}タグを削除しますか?", - "delete_user": "ユーザーを削除", - "deleted_shared_link": "共有リンクを削除", - "deletes_missing_assets": "ディスクからなくなったアセットを削除する", - "description": "概要欄", - "description_input_hint_text": "説明を追加", - "description_input_submit_error": "説明の編集に失敗しました。詳細はログを確認してください。", - "deselect_all": "すべての選択を解除", - "details": "詳細", - "direction": "方向", - "disable": "無効化", - "disabled": "無効", - "disallow_edits": "編集を許可しない", - "discord": "Discord", - "discover": "探索", - "discovered_devices": "発見されたデバイス", - "dismiss_all_errors": "全てのエラーを無視", - "dismiss_error": "エラーを無視", - "display_options": "表示オプション", - "display_order": "表示順", - "display_original_photos": "オリジナルの写真を表示", - "display_original_photos_setting_description": "オリジナルのアセットが Web 互換である場合は、アセットを表示するときにサムネイルではなく元の写真を優先して表示します。これにより写真の表示速度が遅くなる可能性があります。", - "do_not_show_again": "このメッセージを再び表示しない", - "documentation": "ドキュメント", - "done": "完了", - "download": "ダウンロード", - "download_action_prompt": "{count}項目をダウンロード中", - "download_canceled": "ダウンロードがキャンセルされました", - "download_complete": "ダウンロード完了", - "download_enqueue": "ダウンロード待機中", - "download_error": "ダウンロードエラー", - "download_failed": "ダウンロード失敗", - "download_finished": "ダウンロード終了", - "download_include_embedded_motion_videos": "埋め込まれた動画", - "download_include_embedded_motion_videos_description": "別ファイルとして、モーションフォトに埋め込まれた動画を含める", - "download_notfound": "ダウンロードが見つかりません", - "download_original": "オリジナルをダウンロード", - "download_paused": "ダウンロード一時停止中", - "download_settings": "ダウンロード", - "download_settings_description": "写真/動画のダウンロードに関連する設定を管理します", - "download_started": "ダウンロード開始", - "download_sucess": "ダウンロード成功", - "download_sucess_android": "DCIM/Immichに保存されました", - "download_waiting_to_retry": "リトライ中", - "downloading": "ダウンロード中", - "downloading_asset_filename": "写真/動画 {filename} をダウンロード中", - "downloading_from_icloud": "iCloudからダウンロード", - "downloading_media": "ダウンロード中", - "drop_files_to_upload": "ファイルをドロップしてアップロード", - "duplicates": "重複", - "duplicates_description": "もしあれば、重複しているグループを示すことで解決します", - "duration": "間隔", - "edit": "編集", - "edit_album": "アルバムを編集", - "edit_avatar": "アバターを編集", - "edit_birthday": "誕生日を編集する", - "edit_date": "日付を編集", - "edit_date_and_time": "撮影日時を編集", - "edit_date_and_time_action_prompt": "{count} 個のアイテムの日時が編集されました", - "edit_date_and_time_by_offset": "オフセットを用いて日付を変更", - "edit_date_and_time_by_offset_interval": "新しい日付範囲: {from} - {to}", - "edit_description": "説明文を編集", - "edit_description_prompt": "新しい説明文を選んでください:", - "edit_exclusion_pattern": "除外パターンを編集", - "edit_faces": "顔を編集", - "edit_key": "キーを編集", - "edit_link": "リンクを編集する", - "edit_location": "位置情報を編集", - "edit_location_action_prompt": "{count}項目の位置情報を変更しました", - "edit_location_dialog_title": "位置情報", - "edit_name": "名前を変更", - "edit_people": "人物を編集", - "edit_tag": "タグを編集する", - "edit_title": "タイトルを編集", - "edit_user": "ユーザーを編集", - "edit_workflow": "ワークフローを編集", - "editor": "編集画面", - "editor_close_without_save_prompt": "変更は破棄されます", - "editor_close_without_save_title": "編集画面を閉じますか?", - "editor_confirm_reset_all_changes": "本当に全ての変更をリセットしますか?", - "editor_flip_horizontal": "水平方向に反転", - "editor_flip_vertical": "垂直に反転", - "editor_orientation": "向き", - "editor_reset_all_changes": "変更をリセット", - "editor_rotate_left": "反時計回りに90°回転", - "editor_rotate_right": "時計回りに90°回転", - "email": "メールアドレス", - "email_notifications": "Eメール通知", - "empty_folder": "このフォルダーは空です", - "empty_trash": "ゴミ箱を空にする", - "empty_trash_confirmation": "本当にゴミ箱を空にしますか? ゴミ箱内のすべての写真/動画がImmichから永続的に削除されます。\nこの操作を元に戻すことはできません!", - "enable": "有効化", - "enable_backup": "バックアップを有効化", - "enable_biometric_auth_description": "生体認証を有効化するために、PINコードを入力してください", - "enabled": "有効", - "end_date": "終了日", - "enqueued": "順番待ち中", - "enter_wifi_name": "Wi-Fiの名前(SSID)を入力", - "enter_your_pin_code": "PINコードを入力してください", - "enter_your_pin_code_subtitle": "鍵付きフォルダー用のPINコードを入力してください", - "error": "エラー", - "error_change_sort_album": "アルバムの表示順の変更に失敗しました", - "error_delete_face": "写真/動画から顔の削除ができませんでした", - "error_getting_places": "場所の取得に失敗しました", - "error_loading_albums": "アルバムの読み込みエラー", - "error_loading_image": "画像の読み込みエラー", - "error_loading_partners": "パートナーの読み込みに失敗しました: {error}", - "error_retrieving_asset_information": "項目情報の取得エラー", - "error_saving_image": "エラー: {error}", - "error_tag_face_bounding_box": "顔の登録に失敗しました - 顔を囲む四角形の座標取得に失敗", - "error_title": "エラー - 問題が発生しました", - "error_while_navigating": "項目のナビゲーション中のエラー", - "errors": { - "cannot_navigate_next_asset": "次の写真/動画に移動できません", - "cannot_navigate_previous_asset": "前の写真/動画に移動できません", - "cant_apply_changes": "変更を適用できません", - "cant_change_activity": "アクティビティを{enabled, select, true {無効化} other {有効化}}できません", - "cant_change_asset_favorite": "項目のお気に入りを変更できません", - "cant_change_metadata_assets_count": "{count, plural, one {#個} other {#個}}の写真/動画のメタデータを変更できません", - "cant_get_faces": "顔を取得できません", - "cant_get_number_of_comments": "コメント数を取得できません", - "cant_search_people": "人物を検索できません", - "cant_search_places": "場所を検索できません", - "error_adding_assets_to_album": "エラーが発生し、写真/動画をアルバムに追加できませんでした", - "error_adding_users_to_album": "ユーザーをアルバムに追加中のエラー", - "error_deleting_shared_user": "共有ユーザを削除中のエラー", - "error_downloading": "{filename}をダウンロード中にエラー", - "error_hiding_buy_button": "購入ボタン非表示のエラー", - "error_removing_assets_from_album": "アルバムからアセットを削除中のエラー、詳細についてはコンソールを確認してください", - "error_selecting_all_assets": "エラーが発生し、全写真/動画を選択に失敗しました", - "exclusion_pattern_already_exists": "この除外パターンは既に存在します。", - "failed_to_create_album": "アルバムを作成できませんでした", - "failed_to_create_shared_link": "共有リンクを作成できませんでした", - "failed_to_edit_shared_link": "共有リンクを編集できませんでした", - "failed_to_get_people": "人物を取得できませんでした", - "failed_to_keep_this_delete_others": "この項目以外の項目の削除に失敗しました", - "failed_to_load_asset": "写真/動画を読み込めませんでした", - "failed_to_load_assets": "写真/動画を読み込めませんでした", - "failed_to_load_notifications": "通知の読み込みに失敗しました", - "failed_to_load_people": "人物を読み込めませんでした", - "failed_to_remove_product_key": "プロダクトキーを削除できませんでした", - "failed_to_reset_pin_code": "PINコードのリセットに失敗しました", - "failed_to_stack_assets": "写真/動画をスタックできませんでした", - "failed_to_unstack_assets": "写真/動画をスタック解除ができませんでした", - "failed_to_update_notification_status": "通知ステータスの更新に失敗しました", - "incorrect_email_or_password": "メールアドレスまたはパスワードが間違っています", - "library_folder_already_exists": "このインポートパスは既に存在します。", - "paths_validation_failed": "{paths, plural, one {#個} other {#個}}のパスの検証に失敗しました", - "profile_picture_transparent_pixels": "プロフィール写真には透明ピクセルを含めることはできません。画像を拡大/縮小したり移動してください。", - "quota_higher_than_disk_size": "ディスク容量より大きい容量が指定されました", - "something_went_wrong": "問題が発生しました", - "unable_to_add_album_users": "ユーザーをアルバムに追加できません", - "unable_to_add_assets_to_shared_link": "写真/動画を共有リンクに追加できません", - "unable_to_add_comment": "コメントを追加できません", - "unable_to_add_exclusion_pattern": "除外パターンを追加できません", - "unable_to_add_partners": "パートナーを追加できません", - "unable_to_add_remove_archive": "アーカイブ{archived, select, true {から写真/動画を削除} other {に写真/動画を追加}}できません", - "unable_to_add_remove_favorites": "写真/動画をお気に入り{favorite, select, true {に追加} other {の解除}}できませんでした", - "unable_to_archive_unarchive": "{archived, select, true {アーカイブ} other {アーカイブ解除}}できません", - "unable_to_change_album_user_role": "アルバムのユーザーロールを変更できません", - "unable_to_change_date": "日付を変更できません", - "unable_to_change_description": "説明文の変更に失敗しました", - "unable_to_change_favorite": "お気に入りを変更できませんでした", - "unable_to_change_location": "場所を変更できません", - "unable_to_change_password": "パスワードを変更できません", - "unable_to_change_visibility": "{count, plural, one {#人} other {#人}}の人物の非表示設定を変更できません", - "unable_to_complete_oauth_login": "OAuth ログインを完了できません", - "unable_to_connect": "接続できません", - "unable_to_copy_to_clipboard": "クリップボードにコピーできません。https 経由でページにアクセスしていることを確認してください", - "unable_to_create": "ワークフローを作成できません", - "unable_to_create_admin_account": "管理者アカウントを作成できません", - "unable_to_create_api_key": "新しいAPI キーを作成できません", - "unable_to_create_library": "ライブラリを作成できません", - "unable_to_create_user": "ユーザーを作成できません", - "unable_to_delete_album": "アルバムを削除できません", - "unable_to_delete_asset": "項目を削除できません", - "unable_to_delete_assets": "項目を削除中のエラー", - "unable_to_delete_exclusion_pattern": "除外パターンを削除できません", - "unable_to_delete_shared_link": "共有リンクを削除できません", - "unable_to_delete_user": "ユーザーを削除できません", - "unable_to_delete_workflow": "ワークフローを削除できません", - "unable_to_download_files": "ファイルをダウンロードできません", - "unable_to_edit_exclusion_pattern": "除外パターンを編集できません", - "unable_to_empty_trash": "ゴミ箱を空にできません", - "unable_to_enter_fullscreen": "フルスクリーンにできません", - "unable_to_exit_fullscreen": "フルスクリーンを解除できません", - "unable_to_get_comments_number": "コメント数を取得できません", - "unable_to_get_shared_link": "共有リンクの取得に失敗しました", - "unable_to_hide_person": "人物を非表示にできません", - "unable_to_link_motion_video": "モーションビデオをリンクできません", - "unable_to_link_oauth_account": "OAuth アカウントをリンクできません", - "unable_to_log_out_all_devices": "全てのデバイスからログアウトできません", - "unable_to_log_out_device": "デバイスからログアウトできません", - "unable_to_login_with_oauth": "OAuth でログインできません", - "unable_to_play_video": "動画を再生できません", - "unable_to_reassign_assets_existing_person": "写真/動画を{name, select, null {既存の人物} other {{name}}}に再割り当てできません", - "unable_to_reassign_assets_new_person": "写真/動画を新しい人物に再割り当てできません", - "unable_to_refresh_user": "ユーザーを更新できません", - "unable_to_remove_album_users": "アルバムからユーザーを削除できません", - "unable_to_remove_api_key": "API キーを削除できません", - "unable_to_remove_assets_from_shared_link": "共有リンクから写真/動画を削除できません", - "unable_to_remove_library": "ライブラリを削除できません", - "unable_to_remove_partner": "パートナーを削除できません", - "unable_to_remove_reaction": "リアクションを削除できません", - "unable_to_reset_password": "パスワードをリセットできません", - "unable_to_reset_pin_code": "PINコードをリセットできませんでした", - "unable_to_resolve_duplicate": "重複を解決できません", - "unable_to_restore_assets": "写真/動画を復元できません", - "unable_to_restore_trash": "ゴミ箱を復元できません", - "unable_to_restore_user": "ユーザーを復元できません", - "unable_to_save_album": "アルバムを保存できません", - "unable_to_save_api_key": "API キーを保存できません", - "unable_to_save_date_of_birth": "生年月日を保存できません", - "unable_to_save_name": "名前を保存できません", - "unable_to_save_profile": "プロフィールを保存できません", - "unable_to_save_settings": "設定を保存できません", - "unable_to_scan_libraries": "ライブラリをスキャンできません", - "unable_to_scan_library": "ライブラリをスキャンできません", - "unable_to_set_feature_photo": "アイキャッチ写真を設定できません", - "unable_to_set_profile_picture": "プロフィール画像を設定できません", - "unable_to_set_rating": "評価を設定できません", - "unable_to_submit_job": "ジョブを送信できません", - "unable_to_trash_asset": "写真/動画をゴミ箱に移動できません", - "unable_to_unlink_account": "アカウントのリンクを解除できません", - "unable_to_unlink_motion_video": "モーションビデオのリンクを解除できません", - "unable_to_update_album_cover": "アルバムカバーを更新できません", - "unable_to_update_album_info": "アルバム情報を更新できません", - "unable_to_update_library": "ライブラリを更新できません", - "unable_to_update_location": "場所を更新できません", - "unable_to_update_settings": "設定を更新できません", - "unable_to_update_timeline_display_status": "タイムラインでの表示の設定状態を更新できません", - "unable_to_update_user": "ユーザーを更新できません", - "unable_to_update_workflow": "ワークフローを更新できません", - "unable_to_upload_file": "ファイルをアップロードできません" - }, - "errors_text": "エラー", - "exclusion_pattern": "除外パターン", - "exif": "Exif", - "exif_bottom_sheet_description": "説明を追加", - "exif_bottom_sheet_description_error": "説明文をアップデートできませんでした", - "exif_bottom_sheet_details": "詳細", - "exif_bottom_sheet_location": "撮影場所", - "exif_bottom_sheet_no_description": "説明なし", - "exif_bottom_sheet_people": "人物", - "exif_bottom_sheet_person_add_person": "名前を追加", - "exit_slideshow": "スライドショーを終わる", - "expand_all": "全て展開", - "experimental_settings_new_asset_list_subtitle": "製作途中 (WIP)", - "experimental_settings_new_asset_list_title": "試験的なグリッドを有効化", - "experimental_settings_subtitle": "試験的機能につき自己責任で!", - "experimental_settings_title": "試験的機能", - "expire_after": "有効期限", - "expired": "有効期限が切れました", - "expires_date": "{date} に失効", - "explore": "探索", - "explorer": "探索", - "export": "エクスポート", - "export_as_json": "JSONとしてエクスポート", - "export_database": "データベースを出力", - "export_database_description": "SQLiteデータベースを出力", - "extension": "拡張子", - "external": "外部", - "external_libraries": "外部ライブラリ", - "external_network": "外部のネットワーク", - "external_network_sheet_info": "指定されたWi-Fiに繋がっていない時アプリはサーバーへの接続を指定されたURLで行います。優先順位は上から下です", - "face_unassigned": "未割り当て", - "failed": "失敗", - "failed_count": "失敗: {count}", - "failed_to_authenticate": "認証に失敗しました", - "failed_to_load_assets": "写真/動画のロードに失敗しました", - "failed_to_load_folder": "フォルダーの読み込みに失敗", - "favorite": "お気に入り", - "favorite_action_prompt": "{count}項目をお気に入りに追加しました", - "favorite_or_unfavorite_photo": "写真をお気にいりに登録または解除", - "favorites": "お気に入り", - "favorites_page_no_favorites": "お気に入り登録された項目がありません", - "feature_photo_updated": "人物画像が更新されました", - "features": "機能", - "features_in_development": "開発中の機能", - "features_setting_description": "アプリの機能を管理する", - "file_name_or_extension": "ファイル名または拡張子", - "file_size": "ファイルサイズ", - "filename": "ファイル名", - "filetype": "ファイルタイプ", - "filter": "フィルター", - "filter_description": "対象とするアセットの抽出条件", - "filter_people": "人物を絞り込み", - "filter_places": "場所をフィルター", - "filters": "フィルター", - "find_them_fast": "名前で検索して素早く発見", - "first": "はじめ", - "fix_incorrect_match": "間違った一致を修正", - "folder": "フォルダー", - "folder_not_found": "フォルダーが見つかりませんでした", - "folders": "フォルダ", - "folders_feature_description": "ファイルシステム上の写真と動画のフォルダビューを閲覧する", - "forgot_pin_code_question": "PINを忘れましたか?", - "forward": "前へ", - "free_up_space": "ストレージを解放", - "free_up_space_description": "バックアップされた写真と動画をあなたのデバイスのゴミ箱へ移動し、ストレージを解放します。コピーはサーバ上に安全に保管されています。", - "free_up_space_settings_subtitle": "デバイスのストレージを解放する", - "full_path": "フルパス: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "この機能は動作のためにGoogleのリソースを読み込みます。", - "general": "一般", - "geolocation_instruction_location": "位置情報付きの項目をクリックして、その位置情報を利用します。あるいは、地図上の地点を直接選ぶことも可能です", - "get_help": "助けを求める", - "get_people_error": "人物の取得時にエラー", - "get_wifiname_error": "Wi-Fiの名前(SSID)が入手できませんでした。Wi-Fiに繋がってるのと必要な権限を許可したか確認してください", - "getting_started": "はじめる", - "go_back": "戻る", - "go_to_folder": "フォルダへ", - "go_to_search": "検索へ", - "gps": "GPS", - "gps_missing": "GPS無", - "grant_permission": "許可する", - "group_albums_by": "これでアルバムをグループ化…", - "group_country": "国でグループ化", - "group_no": "グループ化なし", - "group_owner": "所有者でグループ化", - "group_places_by": "グループ分け...", - "group_year": "年でグループ化", - "haptic_feedback_switch": "ハプティックフィードバック", - "haptic_feedback_title": "ハプティックフィードバックを有効にする", - "has_quota": "クォータ有り", - "hash_asset": "項目をハッシュ化する", - "hashed_assets": "ハッシュ化された項目", - "hashing": "ハッシュ化", - "header_settings_add_header_tip": "ヘッダを追加", - "header_settings_field_validator_msg": "ヘッダを空白にはできません", - "header_settings_header_name_input": "ヘッダの名前", - "header_settings_header_value_input": "ヘッダのバリュー", - "headers_settings_tile_title": "カスタムプロキシヘッダ", - "height": "高さ", - "hi_user": "こんにちは、{name}( {email})さん", - "hide_all_people": "全ての人物を非表示", - "hide_gallery": "ギャラリーを非表示", - "hide_named_person": "人物 {name} を非表示", - "hide_password": "パスワードを隠す", - "hide_person": "人物を非表示", - "hide_schema": "スキーマを非表示", - "hide_text_recognition": "文字認識を非表示", - "hide_unnamed_people": "名前がない人物を非表示", - "home_page_add_to_album_conflicts": "{album}に{added}個の写真/動画を追加しました。追加済みの{failed}個はスキップしました。", - "home_page_add_to_album_err_local": "まだアップロードされてない写真/動画は、アルバムに追加できません", - "home_page_add_to_album_success": "{album}に{added}個の写真/動画を追加しました", - "home_page_album_err_partner": "まだパートナーの写真はアルバムに追加できません。スキップします (アップデートをお待ちください)", - "home_page_archive_err_local": "まだアップロードされてない項目はアーカイブできません", - "home_page_archive_err_partner": "パートナーの写真はアーカイブできません。スキップします", - "home_page_building_timeline": "タイムライン構築中", - "home_page_delete_err_partner": "パートナーの写真は削除できません。スキップします", - "home_page_delete_remote_err_local": "サーバー上のアイテムの削除の選択にデバイス上のアイテムが含まれているのでスキップします", - "home_page_favorite_err_local": "まだアップロードされてない項目はお気に入り登録できません", - "home_page_favorite_err_partner": "パートナーの写真はお気に入り登録できません。スキップします", - "home_page_first_time_notice": "はじめてアプリを使う場合、タイムラインに写真を表示するためにアルバムを選択してください", - "home_page_locked_error_local": "デバイス上にしかない項目を鍵付きフォルダーに移動することはできません。スキップします", - "home_page_locked_error_partner": "パートナーの項目を鍵付きフォルダーに移動することはできません。スキップします", - "home_page_share_err_local": "ローカルのみの項目をリンクで共有はできません。スキップします", - "home_page_upload_err_limit": "1回でアップロードできる写真の数は30枚です。スキップします", - "host": "ホスト", - "hour": "時間", - "hours": "時間", - "id": "ID", - "idle": "アイドリング", - "ignore_icloud_photos": "iCloud上の写真をスキップ", - "ignore_icloud_photos_description": "iCloudに保存されている写真/動画はImmichサーバーにアップロードされません", - "image": "写真", - "image_alt_text_date": "{isVideo, select, true {動画} other {写真}}は{date} に撮影", - "image_alt_text_date_1_person": "{date}の、{person1}との{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_2_people": "{date}の、{person1}と{person2}の{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_3_people": "{date}の、{person1}と{person2}、そして{person3}の{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_4_or_more_people": "{date}の、{person1}と{person2}、そしてその他{additionalCount,number}人の{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_place": "{date}の、{country}、{city}での{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_place_1_person": "{date}の、{country}、{city}での{person1}の{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_place_2_people": "{date}の、{country}、{city}での{person1}と{person2}の{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_place_3_people": "{date}の、{country}、{city}での{person1}と{person2}、そして{person3}の{isVideo, select, true {動画} other {画像}}", - "image_alt_text_date_place_4_or_more_people": "{date}の、{country}、{city}での{person1}と{person2}、そしてその他{additionalCount, number}人の{isVideo, select, true {動画} other {画像}}", - "image_saved_successfully": "画像が保存されました", - "image_viewer_page_state_provider_download_started": "ダウンロードが始まります", - "image_viewer_page_state_provider_download_success": "ダウンロード成功", - "image_viewer_page_state_provider_share_error": "共有エラー", - "immich_logo": "Immichのロゴ", - "immich_web_interface": "ImmichのWebインターフェース", - "import_from_json": "JSONからインポート", - "import_path": "インポートパス", - "in_albums": "{count, plural, one {#件のアルバム} other {#件のアルバム}}の中", - "in_archive": "アーカイブ済み", - "in_year": "{year}年", - "in_year_selector": "撮影年:", - "include_archived": "アーカイブ済みを含める", - "include_shared_albums": "共有アルバムを含める", - "include_shared_partner_assets": "パートナーがシェアした写真/動画を含める", - "individual_share": "1枚の共有", - "individual_shares": "1枚ずつの共有", - "info": "情報", - "interval": { - "day_at_onepm": "毎日午後1時", - "hours": "{hours, plural, one {1時間} other {{hours, number}時間}}ごと", - "night_at_midnight": "毎晩真夜中に", - "night_at_twoam": "毎晩午前2時" - }, - "invalid_date": "日付が無効です", - "invalid_date_format": "日付のフォーマットが無効です", - "invite_people": "人々を招待", - "invite_to_album": "アルバムに招待", - "ios_debug_info_fetch_ran_at": "{dateTime}に取得を試みました", - "ios_debug_info_last_sync_at": "前回の同期時刻: {dateTime}", - "ios_debug_info_no_processes_queued": "バックグラウンドで処理待ちの項目はありません", - "ios_debug_info_no_sync_yet": "バックグラウンド同期は実行されていません", - "ios_debug_info_processes_queued": "バックグラウンド処理待ち: {count, plural, one {{count}} other {{count}}}", - "ios_debug_info_processing_ran_at": "処理実行済み: {dateTime}", - "items_count": "{count, plural, one {#個} other {#個}}の項目", - "jobs": "ジョブ", - "json_editor": "JSONエディター", - "json_error": "JSONエラー", - "keep": "保持", - "keep_albums": "アルバムを保持", - "keep_albums_count": "{count}個のアルバムを残す", - "keep_all": "全て保持", - "keep_description": "ストレージを解放する際に、デバイスに残すものを選択できます。", - "keep_favorites": "お気に入りを保持", - "keep_on_device": "デバイスに保持", - "keep_on_device_hint": "このデバイスに保持したい項目を選択します", - "keep_this_delete_others": "これを残してほかを削除する", - "keeping": "保持する項目数: {items}", - "kept_this_deleted_others": "この写真/動画を残して{count, plural, other {#件}}を削除する", - "keyboard_shortcuts": "キーボードショートカット", - "language": "言語", - "language_no_results_subtitle": "検索ワードを変えてみてください", - "language_no_results_title": "言語が見つかりません", - "language_search_hint": "言語を検索", - "language_setting_description": "優先言語を選択してください", - "large_files": "大きいサイズのファイル", - "last": "最後", - "last_months": "{count, plural, one {過去1ヶ月} other {過去#ヶ月}}", - "last_seen": "最新の活動", - "latest_version": "最新バージョン", - "latitude": "緯度", - "leave": "退出", - "leave_album": "アルバムから抜ける", - "lens_model": "レンズモデル", - "let_others_respond": "他のユーザーの返信を許可する", - "level": "レベル", - "library": "ライブラリ", - "library_add_folder": "フォルダーを追加", - "library_edit_folder": "フォルダーを編集", - "library_options": "ライブラリ設定", - "library_page_device_albums": "デバイス上のアルバム", - "library_page_new_album": "新しいアルバム", - "library_page_sort_asset_count": "項目の数", - "library_page_sort_created": "作成日時", - "library_page_sort_last_modified": "最終変更", - "library_page_sort_title": "アルバム名", - "licenses": "ライセンス", - "light": "ライトモード", - "like": "いいね", - "like_deleted": "いいねが削除されました", - "link_motion_video": "モーションビデオのリンク", - "link_to_oauth": "OAuthへリンクする", - "linked_oauth_account": "リンクされたOAuthアカウント", - "list": "リスト", - "loading": "読み込み中", - "loading_search_results_failed": "検索結果を読み込めませんでした", - "local": "ローカル", - "local_asset_cast_failed": "サーバーにアップロードされていない項目はキャストできません", - "local_assets": "ローカルの項目", - "local_id": "ローカルID", - "local_media_summary": "ローカルメディアのまとめ", - "local_network": "ローカルネットワーク", - "local_network_sheet_info": "アプリは指定されたWi-Fiに繋がっている時サーバーへの接続を下記のURLで行います", - "location": "位置情報", - "location_permission": "位置情報権限", - "location_permission_content": "自動URL切り替えを使用するには現在のWi-Fi名を取得する必要があり、アプリが常に詳細な位置情報にアクセスできる必要があります", - "location_picker_choose_on_map": "マップを選択", - "location_picker_latitude_error": "有効な緯度を入力してください", - "location_picker_latitude_hint": "緯度を入力", - "location_picker_longitude_error": "有効な経度を入力してください", - "location_picker_longitude_hint": "経度を入力", - "lock": "ロック", - "locked_folder": "鍵付きフォルダー", - "log_detail_title": "ログの詳細", - "log_out": "ログアウト", - "log_out_all_devices": "全てのデバイスからログアウト", - "logged_in_as": "{user}としてログイン中", - "logged_out_all_devices": "全てのデバイスからログアウトしました", - "logged_out_device": "デバイスからログアウトしました", - "login": "ログイン", - "login_disabled": "ログインは無効化されました", - "login_form_api_exception": "APIエラーが発生しました。URLをチェックしてもう一度お試しください。", - "login_form_back_button_text": "戻る", - "login_form_email_hint": "hoge@email.com", - "login_form_endpoint_hint": "http://サーバーのアドレス:ポート番号", - "login_form_endpoint_url": "サーバーのエンドポイントURL", - "login_form_err_http": "http://かhttps://かを指定してください", - "login_form_err_invalid_email": "メールアドレスが無効です", - "login_form_err_invalid_url": "無効なURL", - "login_form_err_leading_whitespace": "最初にスペースが含まれています", - "login_form_err_trailing_whitespace": "最後にスペースが含まれています", - "login_form_failed_get_oauth_server_config": "OAuthログインに失敗しました。サーバーのURLを確認してください。", - "login_form_failed_get_oauth_server_disable": "このサーバーではOAuthが使えません", - "login_form_failed_login": "ログインエラーが発生しました。サーバーのURL・メールアドレス・パスワードを再確認してください。", - "login_form_handshake_exception": "Handshake Exceptionエラーが発生しました。自己署名証明書を設定から有効にしてください。", - "login_form_password_hint": "パスワード", - "login_form_save_login": "ログインを保持", - "login_form_server_empty": "URLを入力", - "login_form_server_error": "サーバーに接続できません", - "login_has_been_disabled": "ログインは無効化されています。", - "login_password_changed_error": "パスワードの変更でエラーが発生しました", - "login_password_changed_success": "パスワードの変更に成功", - "logout_all_device_confirmation": "本当に全てのデバイスからログアウトしますか?", - "logout_this_device_confirmation": "本当にこのデバイスからログアウトしますか?", - "logs": "ログ", - "longitude": "経度", - "look": "見た目", - "loop_videos": "動画をループ", - "loop_videos_description": "有効にすると詳細表示で自動的に動画がループします。", - "main_branch_warning": "開発版を使っているようです。リリース版の使用を強く推奨します!", - "main_menu": "メインメニュー", - "maintenance_action_restore": "データベースを復元", - "maintenance_description": "Immich は メンテナンスモード中です。", - "maintenance_end": "メンテナンスモードを終了する", - "maintenance_end_error": "メンテナンスモードの終了に失敗しました。", - "maintenance_logged_in_as": "現在 {user}としてログインしています", - "maintenance_restore_from_backup": "バックアップから復元", - "maintenance_restore_library": "あなたのライブラリを復元", - "maintenance_restore_library_confirm": "こちらが正しいことを確認した上で、バックアップの復元を進めてください!", - "maintenance_restore_library_description": "データベースを復元", - "maintenance_restore_library_folder_has_files": "{folder}は{count}個のフォルダを含みます", - "maintenance_restore_library_folder_no_files": "{folder}にファイルがありません!", - "maintenance_restore_library_folder_pass": "読み込み可能かつ書き込み可能", - "maintenance_restore_library_folder_read_fail": "読み込み不能", - "maintenance_restore_library_folder_write_fail": "書き込み不能", - "maintenance_restore_library_hint_missing_files": "重要なファイルが失われる可能性があります", - "maintenance_restore_library_hint_regenerate_later": "この設定はあとから再生成できます", - "maintenance_restore_library_hint_storage_template_missing_files": "ストレージテンプレートを使いますか?重要なファイルが失われる可能性があります", - "maintenance_restore_library_loading": "整合性のチェックとヒューリスティックを読み込んでいます…", - "maintenance_task_backup": "既存のデータベースのバックアップを作成しています…", - "maintenance_task_migrations": "データベースのマイグレーションを実行しています…", - "maintenance_task_restore": "選択したバックアップを復元しています…", - "maintenance_task_rollback": "復元に失敗したため、復元ポイントへロールバックします…", - "maintenance_title": "一時的に利用不可能", - "make": "メーカー", - "manage_geolocation": "位置情報を編集", - "manage_media_access_rationale": "項目をゴミ箱に移動したり、復元をしたりするためにこの権限が必要です。", - "manage_media_access_settings": "設定を開く", - "manage_media_access_subtitle": "Immichアプリにメディアファイルの管理と移動を許可する。", - "manage_media_access_title": "メディア管理アクセス", - "manage_shared_links": "共有済みのリンクを管理", - "manage_sharing_with_partners": "パートナーとの共有を管理します", - "manage_the_app_settings": "アプリの設定を管理します", - "manage_your_account": "あなたのアカウントを管理します", - "manage_your_api_keys": "あなたのAPIキーを管理します", - "manage_your_devices": "ログインデバイスを管理します", - "manage_your_oauth_connection": "OAuth接続を管理します", - "map": "地図", - "map_assets_in_bounds": "{count, plural, =0 {ここに写真はありません} one {# 枚} other {# 枚}}", - "map_cannot_get_user_location": "位置情報がゲットできません", - "map_location_dialog_yes": "はい", - "map_location_picker_page_use_location": "この位置情報を使う", - "map_location_service_disabled_content": "現在地の項目を表示するには位置情報がオンである必要があります。有効化しますか?", - "map_location_service_disabled_title": "位置情報がオフです", - "map_marker_for_images": "{country} {city}で撮影された写真の地図マーカー", - "map_marker_with_image": "画像の地図マーカー", - "map_no_location_permission_content": "現在地の項目を表示するには位置情報へのアクセスが必要です。許可しますか?", - "map_no_location_permission_title": "位置情報へのアクセスが拒否されました", - "map_settings": "マップの設定", - "map_settings_dark_mode": "ダークモード", - "map_settings_date_range_option_day": "過去24時間", - "map_settings_date_range_option_days": "過去{days}日間", - "map_settings_date_range_option_year": "過去1年間", - "map_settings_date_range_option_years": "過去{years}年間", - "map_settings_dialog_title": "マップの設定", - "map_settings_include_show_archived": "アーカイブ済みを含める", - "map_settings_include_show_partners": "パートナーを含める", - "map_settings_only_show_favorites": "お気に入りのみを表示", - "map_settings_theme_settings": "地図の見た目", - "map_zoom_to_see_photos": "写真を見るにはズームアウト", - "mark_all_as_read": "すべて既読にする", - "mark_as_read": "既読にする", - "marked_all_as_read": "すべて既読にしました", - "matches": "マッチ", - "matching_assets": "一致する項目", - "media_type": "メディアタイプ", - "memories": "メモリー", - "memories_all_caught_up": "これで全部です", - "memories_check_back_tomorrow": "また明日、見に来てくださいね", - "memories_setting_description": "メモリーの内容を管理します", - "memories_start_over": "もう一度見る", - "memories_swipe_to_close": "上にスワイプして閉じる", - "memory": "メモリー", - "memory_lane_title": "思い出 {title}", - "menu": "メニュー", - "merge": "統合", - "merge_people": "人物を統合", - "merge_people_limit": "一度に結合できる顔は最大5つまでです", - "merge_people_prompt": "これらの人物を統合しますか? この操作は元に戻せません。", - "merge_people_successfully": "人物の統合に成功しました", - "merged_people_count": "{count, plural, one {#人} other {#人}}の人物を統合しました", - "minimize": "最小化", - "minute": "分", - "minutes": "分", - "mirror_horizontal": "水平", - "mirror_vertical": "垂直", - "missing": "欠落", - "mobile_app": "モバイルアプリ", - "mobile_app_download_onboarding_note": "以下のオプションを使用してコンパニオンモバイルアプリをダウンロードしてください", - "model": "モデル", - "month": "月", - "monthly_title_text_date_format": "yyyy MM", - "more": "もっと表示", - "move": "移動", - "move_down": "下へ", - "move_off_locked_folder": "鍵付きフォルダーから出す", - "move_to": "移動する", - "move_to_device_trash": "デバイスのゴミ箱へ移動", - "move_to_lock_folder_action_prompt": "{count}項目を鍵付きフォルダーに追加しました", - "move_to_locked_folder": "鍵付きフォルダーへ移動", - "move_to_locked_folder_confirmation": "これらの写真や動画はすべてのアルバムから外され、鍵付きフォルダー内でのみ閲覧可能になります", - "move_up": "上へ", - "moved_to_archive": "{count, plural, one {#} other {#}}項目をアーカイブしました", - "moved_to_library": "{count, plural, one {#} other {#}}項目をライブラリに移動しました", - "moved_to_trash": "ゴミ箱に移動しました", - "multiselect_grid_edit_date_time_err_read_only": "読み取り専用の項目の日付を変更できません", - "multiselect_grid_edit_gps_err_read_only": "読み取り専用の項目の位置情報を変更できません", - "mute_memories": "メモリーのミュート", - "my_albums": "私のアルバム", - "name": "名前", - "name_or_nickname": "名前またはニックネーム", - "name_required": "名前は必須項目です", - "navigate": "ナビゲート", - "navigate_to_time": "特定の時間に移動", - "network_requirement_photos_upload": "モバイル通信を使用して写真のバックアップを行う", - "network_requirement_videos_upload": "モバイル通信を使用して動画のバックアップを行う", - "network_requirements": "ネットワークの要件", - "network_requirements_updated": "ネットワークの条件が変更されたため、バックアップの順番待ちをリセットします", - "networking_settings": "ネットワーク", - "networking_subtitle": "サーバーエンドポイントに関する設定", - "never": "行わない", - "new_album": "新たなアルバム", - "new_api_key": "新しいAPI キー", - "new_date_range": "新しい日付範囲", - "new_password": "新しいパスワード", - "new_person": "新しい人物", - "new_pin_code": "新しいPINコード", - "new_pin_code_subtitle": "鍵付きフォルダーを利用するのが初めてのようです。PINコードを作成してください", - "new_timeline": "新たなタイムライン", - "new_update": "新たな更新", - "new_user_created": "新しいユーザーが作成されました", - "new_version_available": "新しいバージョンが利用可能", - "newest_first": "最新順", - "next": "次", - "next_memory": "次のメモリー", - "no": "いいえ", - "no_actions_added": "アクションがありません", - "no_albums_found": "アルバムが見つかりません", - "no_albums_message": "アルバムを作成して写真や動画を整理しましょう", - "no_albums_with_name_yet": "この名前のアルバムはまだないようです。", - "no_albums_yet": "まだアルバムがないようです。", - "no_archived_assets_message": "写真や動画をアーカイブして、写真一覧から非表示にします", - "no_assets_message": "クリックして最初の写真をアップロード", - "no_assets_to_show": "表示する項目がありません", - "no_cast_devices_found": "キャスト先のデバイスが見つかりません", - "no_checksum_local": "チェックサムが見つかりません - デバイス上の項目を取得できないようです", - "no_checksum_remote": "チェックサムが見つかりません - サーバー上の項目を取得できないようです", - "no_configuration_needed": "設定は不要です", - "no_devices": "許可されたデバイスがありません", - "no_duplicates_found": "重複は見つかりませんでした。", - "no_exif_info_available": "exif情報が利用できません", - "no_explore_results_message": "コレクションを探索するにはさらに写真をアップロードしてください。", - "no_favorites_message": "お気に入り登録すると好きな写真や動画をすぐに見つけられます", - "no_filters_added": "まだフィルターが追加されていません", - "no_libraries_message": "あなたの写真や動画を表示するための外部ライブラリを作成しましょう", - "no_local_assets_found": "このチェックサムの項目はデバイス上に存在しません", - "no_location_set": "位置情報が指定されていません", - "no_locked_photos_message": "鍵付きフォルダー内の写真や動画は通常のライブラリに表示されなくなります。", - "no_name": "名前なし", - "no_notifications": "通知なし", - "no_people_found": "一致する人物が見つかりません", - "no_places": "場所なし", - "no_remote_assets_found": "このチェックサムの項目はサーバー上に存在しません", - "no_results": "結果がありません", - "no_results_description": "同義語やより一般的なキーワードを試してください", - "no_shared_albums_message": "アルバムを作成して写真や動画を共有しましょう", - "no_uploads_in_progress": "アップロードは行われていません", - "none": "なし", - "not_allowed": "許可されていません", - "not_available": "適用なし", - "not_in_any_album": "どのアルバムにも入っていない", - "not_selected": "選択なし", - "note_apply_storage_label_to_previously_uploaded assets": "注意: 以前にアップロードしたアセットにストレージラベルを適用するには以下を実行してください", - "notes": "注意", - "nothing_here_yet": "まだ何も無いようです", - "notification_permission_dialog_content": "通知を許可するには設定を開いてオンにしてください", - "notification_permission_list_tile_content": "通知の許可 をオンにしてください", - "notification_permission_list_tile_enable_button": "通知をオンにする", - "notification_permission_list_tile_title": "通知の許可", - "notification_toggle_setting_description": "メール通知を有効化", - "notifications": "通知", - "notifications_setting_description": "通知を管理します", - "oauth": "OAuth", - "obtainium_configurator": "Obtainiumの設定", - "obtainium_configurator_instructions": "Obtainiumを使用すると、Immich GitHubのリリースから直接Androidアプリをインストールおよびアップデートできます。APIキーを作成し、バリアントを選択してObtainiumの設定リンクを作成してください", - "ocr": "OCR", - "official_immich_resources": "公式Immichリソース", - "offline": "オフライン", - "offset": "オフセット", - "ok": "了解", - "oldest_first": "古い順", - "on_this_device": "デバイス上の項目", - "onboarding": "はじめに", - "onboarding_locale_description": "お好みの言語を選択してください、あとから変更もできます。", - "onboarding_privacy_description": "次の(任意の)機能は外部サービスに依存し、管理者用設定でいつでも無効にできます。", - "onboarding_server_welcome_description": "あなたのインスタンスをよくある設定とともに始めてみましょう。", - "onboarding_theme_description": "インスタンスのカラーテーマを選択してください。これは後から設定で変更できます。", - "onboarding_user_welcome_description": "はじめましょう!", - "onboarding_welcome_user": "ようこそ、{user} さん", - "online": "オンライン", - "only_favorites": "お気に入りのみ", - "open": "開く", - "open_in_map_view": "地図表示で見る", - "open_in_openstreetmap": "OpenStreetMapで開く", - "open_the_search_filters": "検索フィルタを開く", - "options": "オプション", - "or": "または", - "organize_into_albums": "アルバムに追加して整理する", - "organize_into_albums_description": "既存の写真を、現在の同期設定に基づきアルバムに追加する", - "organize_your_library": "ライブラリを整理", - "original": "オリジナル", - "other": "その他", - "other_devices": "その他のデバイス", - "other_entities": "他のもの", - "other_variables": "その他の変数", - "owned": "所有中", - "owner": "オーナー", - "page": "ページ", - "partner": "パートナー", - "partner_can_access": "{partner} がアクセスできます", - "partner_can_access_assets": "アーカイブ済みのものと削除済みのものを除いた全ての写真と動画", - "partner_can_access_location": "写真が撮影された場所", - "partner_list_user_photos": "{user}さんの写真", - "partner_list_view_all": "すべて見る", - "partner_page_empty_message": "まだどのパートナーとも写真を共有してません", - "partner_page_no_more_users": "追加できるユーザーがもういません", - "partner_page_partner_add_failed": "パートナーの追加に失敗", - "partner_page_select_partner": "パートナーを選択", - "partner_page_shared_to_title": "次のユーザーと共有します:", - "partner_page_stop_sharing_content": "{partner}は今後あなたの写真へアクセスできなくなります", - "partner_sharing": "パートナとの共有", - "partners": "パートナー", - "password": "パスワード", - "password_does_not_match": "パスワードが一致しません", - "password_required": "パスワードが必要", - "password_reset_success": "パスワードのリセットに成功", - "past_durations": { - "days": "{days, plural, one {#日} other {#日}}前", - "hours": "{hours, plural, one {#時間} other {#時間}}前", - "years": "{years, plural, one {#年} other {#年}}前" - }, - "path": "パス", - "pattern": "パターン", - "pause": "一時停止", - "pause_memories": "メモリーを一時停止", - "paused": "停止", - "pending": "待機中", - "people": "人物", - "people_edits_count": "{count, plural, one {#人} other {#人}}が編集済", - "people_feature_description": "人物でグループ化された写真と動画を閲覧する", - "people_selected": "{count, plural, one {# 人物を選択中} other {# 人物を選択中}}", - "people_sidebar_description": "人物へのリンクをサイドバーに表示", - "permanent_deletion_warning": "永久削除の警告", - "permanent_deletion_warning_setting_description": "アセットを完全に削除するときに警告を表示する", - "permanently_delete": "完全に削除", - "permanently_delete_assets_count": "{count, plural, one {#} other {#}}項目を完全に削除", - "permanently_delete_assets_prompt": "本当に{count, plural, one {このアセット} other {これらの#個のアセット}}を完全に削除しますか? これにより {count, plural, one {このアセット} other {これらのアセット}}はアルバムからも削除されます。", - "permanently_deleted_asset": "項目を完全に削除しました", - "permanently_deleted_assets_count": "{count, plural, one {#個} other {#個}}の項目を完全に削除しました", - "permission": "権限", - "permission_empty": "権限を空白には出来ません", - "permission_onboarding_back": "戻る", - "permission_onboarding_continue_anyway": "無視して続行", - "permission_onboarding_get_started": "はじめる", - "permission_onboarding_go_to_settings": "システム設定", - "permission_onboarding_permission_denied": "写真へのアクセスが許可されていません。このアプリを使うには設定から写真と動画へのアクセスを許可してください", - "permission_onboarding_permission_granted": "写真へのアクセスが許可されました", - "permission_onboarding_permission_limited": "写真へのアクセスが制限されています。Immichが写真のバックアップと管理を行うには、システム設定から写真と動画のアクセス権限を変更してください。", - "permission_onboarding_request": "Immichは写真へのアクセス許可が必要です", - "person": "人物", - "person_age_months": "生後 {months, plural, one {# ヶ月} other {# ヶ月}}", - "person_age_year_months": "1 歳と, {months, plural, one {# ヶ月} other {# ヶ月}}", - "person_age_years": "{years, plural, other {# 歳}}", - "person_birthdate": "{date}生まれ", - "person_hidden": "{name}{hidden, select, true { (非表示)} other {}}", - "person_recognized": "人物が認識されています", - "person_selected": "人物が選択されています", - "photo_shared_all_users": "写真をすべてのユーザーと共有したか、共有するユーザーがいないようです。", - "photos": "写真", - "photos_and_videos": "写真と動画", - "photos_count": "{count, plural, one {{count, number}枚の写真} other {{count, number}枚の写真}}", - "photos_from_previous_years": "以前の年の写真", - "photos_only": "写真のみ", - "pick_a_location": "場所を選択", - "pick_custom_range": "期間を指定", - "pick_date_range": "日付範囲の選択", - "pin_code_changed_successfully": "PINコードを変更しました", - "pin_code_reset_successfully": "PINコードをリセットしました", - "pin_code_setup_successfully": "PINコードをセットアップしました", - "pin_verification": "PINコード認証", - "place": "場所", - "places": "撮影場所", - "places_count": "{count, plural, other {{count, number}箇所}}", - "play": "再生", - "play_memories": "メモリーを再生", - "play_motion_photo": "モーションビデオを再生", - "play_or_pause_video": "動画を再生または一時停止", - "play_original_video": "オリジナルの動画を再生", - "play_original_video_setting_description": "トランスコードされた動画よりも、オリジナルの動画を優先して再生します。オリジナルのアセットが互換性がない場合、正しく再生されない可能性があります。", - "play_transcoded_video": "トランスコード済みの動画を再生する", - "please_auth_to_access": "アクセスするには認証が必要です", - "port": "ポートレート", - "preferences_settings_subtitle": "アプリに関する設定", - "preferences_settings_title": "設定", - "preparing": "準備中", - "preset": "プリセット", - "preview": "プレビュー", - "previous": "前", - "previous_memory": "前のメモリー", - "previous_or_next_day": "前または次の日", - "previous_or_next_month": "前または次の月", - "previous_or_next_photo": "前または次の写真", - "previous_or_next_year": "前または次の年", - "primary": "最優先", - "privacy": "プライバシー", - "profile": "プロフィール", - "profile_drawer_app_logs": "ログ", - "profile_drawer_client_server_up_to_date": "すべて最新版です", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "読み取り専用モードが有効です。ユーザーのアイコンを長押しして読み取り専用モードを解除してください。", - "profile_image_of_user": "{user} のプロフィール画像", - "profile_picture_set": "プロフィール画像が設定されました。", - "public_album": "公開アルバム", - "public_share": "公開共有", - "purchase_account_info": "サポーター", - "purchase_activated_subtitle": "Immich とオープンソース ソフトウェアを支援していただきありがとうございます", - "purchase_activated_time": "{date}にアクティベート", - "purchase_activated_title": "キーは正常にアクティベートされました", - "purchase_button_activate": "アクティベート", - "purchase_button_buy": "購入", - "purchase_button_buy_immich": "Immichを購入", - "purchase_button_never_show_again": "二度と表示しない", - "purchase_button_reminder": "30日後に通知する", - "purchase_button_remove_key": "キーを削除", - "purchase_button_select": "選択", - "purchase_failed_activation": "アクティベートに失敗しました! メールで正しいプロダクトキーを確認してください!", - "purchase_individual_description_1": "個人向け", - "purchase_individual_description_2": "サポーターの状態", - "purchase_individual_title": "個人", - "purchase_input_suggestion": "プロダクトキーをお持ちですか? 下に入力してください", - "purchase_license_subtitle": "Immich を購入してサービスの継続的な開発を支援してください", - "purchase_lifetime_description": "生涯の購入", - "purchase_option_title": "購入オプション", - "purchase_panel_info_1": "Immichの製作には多くの時間と労力を要しており、また、可能な限りImmichを良いものにするために取り組んでいる専任の技術者がいます。私たちの使命は、オープンソースソフトウェアであり倫理観に則したビジネスの実践のために、開発者の持続可能な収入源となること、そして搾取的なクラウドサービスの本当の代替サービスで、プライバシーを尊重したエコシステムをつくることです。", - "purchase_panel_info_2": "私たちは有料化しないことを約束していますので、この購入によってImmichに追加の機能が付与されることはありません。私たちは、皆様のようなImmichの継続的な開発を支援するユーザーに支えられています。", - "purchase_panel_title": "プロジェクトを支援", - "purchase_per_server": "サーバーごと", - "purchase_per_user": "ユーザーごと", - "purchase_remove_product_key": "プロダクトキーを削除", - "purchase_remove_product_key_prompt": "本当にプロダクトキーを削除しますか?", - "purchase_remove_server_product_key": "サーバープロダクトキーを削除", - "purchase_remove_server_product_key_prompt": "本当にサーバープロダクトキーを削除しますか?", - "purchase_server_description_1": "サーバ全体", - "purchase_server_description_2": "サポーターの状態", - "purchase_server_title": "サーバー", - "purchase_settings_server_activated": "サーバーのプロダクトキーは管理者に管理されています", - "query_asset_id": "順番待ちの項目ID", - "queue_status": "順番待ち中 {count}/{total}", - "rate_asset": "項目を評価する", - "rating": "星での評価", - "rating_clear": "評価を取り消す", - "rating_count": "星{count, plural, one {#つ} other {#つ}}", - "rating_description": "情報欄にEXIFの評価を表示", - "rating_set": "お気に入り度 {rating, plural, one {# ツ星} other {# ツ星}}", - "reaction_options": "リアクションの選択", - "read_changelog": "変更履歴を読む", - "readonly_mode_disabled": "読み取り専用モード無効", - "readonly_mode_enabled": "読み取り専用モード有効", - "ready_for_upload": "アップロード準備完了", - "reassign": "再割り当て", - "reassigned_assets_to_existing_person": "{count, plural, one {#個} other {#個}}の写真/動画を{name, select, null {既存の人物} other {{name}}}に再割り当てしました", - "reassigned_assets_to_new_person": "{count, plural, one {#個} other {#個}}の写真/動画を新しい人物に割り当てました", - "reassing_hint": "選択された写真/動画を既存の人物に割り当て", - "recent": "最近", - "recent-albums": "最近のアルバム", - "recent_searches": "最近の検索", - "recently_added": "最近追加された項目", - "recently_added_page_title": "最近", - "recently_taken": "最近撮られたもの", - "recently_taken_page_title": "最近の撮影", - "refresh": "更新", - "refresh_encoded_videos": "エンコードされた動画を更新", - "refresh_faces": "顔認識を更新", - "refresh_metadata": "メタデータを更新", - "refresh_thumbnails": "サムネイルを更新", - "refreshed": "更新済", - "refreshes_every_file": "すべてのファイルを更新", - "refreshing_encoded_video": "エンコードされた動画を更新中", - "refreshing_faces": "顔認識を更新中", - "refreshing_metadata": "メタデータを更新中", - "regenerating_thumbnails": "サムネイルを再生成中", - "remote": "リモート", - "remote_assets": "リモートの項目", - "remote_media_summary": "サーバー上のメディアまとめ", - "remove": "削除", - "remove_assets_album_confirmation": "本当に{count, plural, one {#個} other {#個}}の写真/動画をアルバムから削除しますか?", - "remove_assets_shared_link_confirmation": "本当にこの共有リンクから{count, plural, one {#個} other {#個}}の写真/動画を削除しますか?", - "remove_assets_title": "写真/動画を削除しますか?", - "remove_custom_date_range": "カスタム日付範囲を削除", - "remove_deleted_assets": "オフラインの写真/動画を削除", - "remove_from_album": "アルバムから削除", - "remove_from_album_action_prompt": "{count}項目がアルバムから除かれました", - "remove_from_favorites": "お気に入り解除", - "remove_from_lock_folder_action_prompt": "{count}項目を鍵付きフォルダーから出しました", - "remove_from_locked_folder": "鍵付きフォルダーから取り除く", - "remove_from_locked_folder_confirmation": "選択した写真/動画を鍵付きフォルダーの外に出してよろしいですか?ライブラリに再び表示されるようになります", - "remove_from_shared_link": "共有リンクから削除", - "remove_memory": "メモリーの削除", - "remove_photo_from_memory": "メモリーから写真を削除", - "remove_tag": "タグを削除", - "remove_url": "URLの削除", - "remove_user": "ユーザーを削除", - "removed_api_key": "削除されたAPI キー: {name}", - "removed_from_archive": "アーカイブから外しました", - "removed_from_favorites": "お気に入りを解除しました", - "removed_from_favorites_count": "{count, plural, other {#項目}}をお気に入りから外しました", - "removed_memory": "削除されたメモリー", - "removed_photo_from_memory": "メモリーから削除された写真", - "removed_tagged_assets": "{count, plural, one {#項目} other {#項目}}からタグを外しました", - "rename": "リネーム", - "repair": "修復", - "repair_no_results_message": "追跡されていないファイルや存在しないファイルがここに表示されます", - "replace_with_upload": "アップロードして置き換え", - "repository": "リポジトリ", - "require_password": "パスワードを要求", - "require_user_to_change_password_on_first_login": "ユーザーに初回ログイン時にパスワードの変更を要求する", - "rescan": "再スキャン", - "reset": "リセット", - "reset_password": "パスワードをリセット", - "reset_people_visibility": "人物の非表示設定をリセット", - "reset_pin_code": "PINコードをリセット", - "reset_pin_code_description": "PINコードを忘れた場合は、サーバー管理者に連絡してリセットできます", - "reset_pin_code_success": "正常にPINコードをリセットしました", - "reset_pin_code_with_password": "PINコードはいつでもパスワードを使ってリセットできます", - "reset_sqlite": "SQLiteデータベースをリセット", - "reset_sqlite_confirmation": "SQLiteを本当にリセットしますか?データを再び同期するためにログアウトし再ログインをする必要があります", - "reset_sqlite_success": "SQLiteデータベースのリセットに成功しました", - "reset_to_default": "デフォルトにリセット", - "resolution": "解像度", - "resolve_duplicates": "重複を解決する", - "resolved_all_duplicates": "全ての重複を解決しました", - "restore": "復元", - "restore_all": "全て復元", - "restore_trash_action_prompt": "{count}項目がゴミ箱から復元されました", - "restore_user": "ユーザーを復元", - "restored_asset": "項目を復元しました", - "resume": "再開", - "resume_paused_jobs": "再開: {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "アップロードを再試行", - "review_duplicates": "重複を調査", - "review_large_files": "サイズの大きなファイルを見る", - "role": "ロール", - "role_editor": "編集者", - "role_viewer": "閲覧者", - "running": "実行中", - "save": "保存", - "save_to_gallery": "ギャラリーに保存", - "saved": "保存しました", - "saved_api_key": "APIキーを保存しました", - "saved_profile": "プロフィールを保存しました", - "saved_settings": "設定を保存しました", - "say_something": "何か書き込みましょう", - "scaffold_body_error_occurred": "エラーが発生しました", - "scan": "スキャン", - "scan_all_libraries": "全てのライブラリをスキャン", - "scan_library": "スキャン", - "scan_settings": "スキャン設定", - "scanning": "スキャン中", - "scanning_for_album": "アルバムをスキャン中…", - "search": "検索", - "search_albums": "アルバムを検索", - "search_by_context": "状況で検索", - "search_by_description": "概要で検索", - "search_by_description_example": "サパでハイキングした日", - "search_by_filename": "ファイル名もしくは拡張子で検索", - "search_by_filename_example": "例: IMG_1234.JPG もしくは PNG", - "search_by_ocr": "OCR検索", - "search_by_ocr_example": "お茶", - "search_camera_lens_model": "レンズモデルで検索…", - "search_camera_make": "カメラメーカーを検索…", - "search_camera_model": "カメラのモデルを検索…", - "search_city": "市町村を検索…", - "search_country": "国を検索…", - "search_filter_apply": "フィルターを適用する", - "search_filter_camera_title": "カメラの種類を選択", - "search_filter_date": "撮影日", - "search_filter_date_interval": "{start}から{end}まで", - "search_filter_date_title": "撮影期間を選択", - "search_filter_display_option_not_in_album": "アルバムにありません", - "search_filter_display_options": "表示オプション", - "search_filter_filename": "ファイル名で検索", - "search_filter_location": "場所", - "search_filter_location_title": "場所を選択", - "search_filter_media_type": "メディアの種類", - "search_filter_media_type_title": "メディアの種類を選択", - "search_filter_ocr": "OCRで検索", - "search_filter_people_title": "人物を選択", - "search_filter_star_rating": "星評価", - "search_for": "検索", - "search_for_existing_person": "既存の人物を検索", - "search_no_more_result": "検索結果以上", - "search_no_people": "人物がいません", - "search_no_people_named": "「{name}」という名前の人物がいません", - "search_no_result": "検索結果なし", - "search_options": "検索オプション", - "search_page_categories": "カテゴリ", - "search_page_motion_photos": "モーションフォト", - "search_page_no_objects": "被写体に関するデータがなし", - "search_page_no_places": "場所に関するデータなし", - "search_page_screenshots": "スクリーンショット", - "search_page_search_photos_videos": "写真や動画を検索", - "search_page_selfies": "自撮り", - "search_page_things": "被写体", - "search_page_view_all_button": "すべて表示", - "search_page_your_activity": "アクティビティ", - "search_page_your_map": "あなたのマップ", - "search_people": "人物を検索", - "search_places": "場所を検索", - "search_rating": "レートで検索...", - "search_result_page_new_search_hint": "検索", - "search_settings": "検索設定", - "search_state": "都道府県を検索…", - "search_suggestion_list_smart_search_hint_1": "スマート検索はデフォルトでオンになっています。メタデータで検索を行う場合:", - "search_suggestion_list_smart_search_hint_2": "m:単語", - "search_tags": "タグを検索...", - "search_timezone": "タイムゾーンを検索…", - "search_type": "検索タイプ", - "search_your_photos": "写真を検索", - "searching_locales": "ロケールを検索…", - "second": "秒", - "see_all_people": "全ての人物を見る", - "select": "選択", - "select_album": "アルバム選択", - "select_album_cover": "アルバムカバーを選択", - "select_albums": "アルバム選択", - "select_all": "全て選択", - "select_all_duplicates": "全ての重複を選択", - "select_all_in": "{group}のすべてを選択", - "select_avatar_color": "アバターの色を選択", - "select_count": "{count, plural, one {# 選択中} other {# 選択中}}", - "select_cutoff_date": "打ち切り期間を選択", - "select_face": "顔を選択", - "select_featured_photo": "人物写真を選択", - "select_from_computer": "PCから選択", - "select_keep_all": "全て保持", - "select_library_owner": "ライブラリ所有者を選択", - "select_new_face": "新しい顔を選択", - "select_people": "人物を選択", - "select_person": "人物を選択", - "select_person_to_tag": "タグを付ける人物を選んでください", - "select_photos": "写真を選択", - "select_trash_all": "全て削除", - "select_user_for_sharing_page_err_album": "アルバム作成に失敗", - "selected": "選択済み", - "selected_count": "{count, plural, other {#個選択済み}}", - "selected_gps_coordinates": "選択された位置情報", - "send_message": "メッセージを送信", - "send_welcome_email": "ウェルカムメールを送信", - "server_endpoint": "サーバーエンドポイント", - "server_info_box_app_version": "アプリのバージョン", - "server_info_box_server_url": "サーバーのURL", - "server_offline": "サーバーがオフラインです", - "server_online": "サーバーがオンラインです", - "server_privacy": "サーバープライバシー", - "server_restarting_description": "このページはすぐに更新されます。", - "server_restarting_title": "サーバーは再起動しています", - "server_stats": "サーバー統計", - "server_update_available": "サーバーのアップデートが利用可能です", - "server_version": "サーバーバージョン", - "set": "設定", - "set_as_album_cover": "アルバムカバーとして設定", - "set_as_featured_photo": "人物写真に設定", - "set_as_profile_picture": "プロフィール画像として設定", - "set_date_of_birth": "生年月日を設定", - "set_profile_picture": "プロフィール画像を設定", - "set_slideshow_to_fullscreen": "スライドショーをフルスクリーンにする", - "set_stack_primary_asset": "メインの写真として設定", - "setting_image_viewer_help": "写真をタップするとサムネイル・中画質・オリジナルの順に読み込みます", - "setting_image_viewer_original_subtitle": "オリジナルの画像を表示したいときにオンにしてください。(最大画質で表示されるので、データと端末のストレージの消費量が増えます)", - "setting_image_viewer_original_title": "オリジナルを読み込む", - "setting_image_viewer_preview_subtitle": "中画質の写真をロードしたいときにオンにしてください。このステップをスキップして直接最大画質の写真を表示したい場合はオフにしてください。(ロード中はサムネイルが代わりに表示されます)", - "setting_image_viewer_preview_title": "プレビューを読み込む", - "setting_image_viewer_title": "画像", - "setting_languages_apply": "適用する", - "setting_languages_subtitle": "アプリの言語を変更する", - "setting_notifications_notify_failures_grace_period": "バックグラウンドバックアップ失敗の通知: {duration}", - "setting_notifications_notify_hours": "{count}時間後", - "setting_notifications_notify_immediately": "すぐに行う", - "setting_notifications_notify_minutes": "{count}分後", - "setting_notifications_notify_never": "行わない", - "setting_notifications_notify_seconds": "{count}秒後", - "setting_notifications_single_progress_subtitle": "アップロード中の写真の詳細", - "setting_notifications_single_progress_title": "バックアップの詳細な進行状況を表示", - "setting_notifications_subtitle": "通知設定を変更する", - "setting_notifications_total_progress_subtitle": "アップロードの進行状況 (完了済み/全体枚数)", - "setting_notifications_total_progress_title": "全体のバックアップの進行状況を表示", - "setting_video_viewer_auto_play_subtitle": "動画を開くと自動で再生されます", - "setting_video_viewer_auto_play_title": "動画の自動再生", - "setting_video_viewer_looping_title": "動画をループする", - "setting_video_viewer_original_video_subtitle": "動画をストリーミングする際に、トランスコードされた動画が存在していても、あえてオリジナル画質の動画を再生します。ストリーミングに待ち時間が生じるかもしれません。なお、デバイス上に保存されている動画はこの設定の有無に関わらず、オリジナル画質の動画を再生します。", - "setting_video_viewer_original_video_title": "常にオリジナル画質の動画を再生する", - "settings": "設定", - "settings_require_restart": "Immichを再起動して設定を適用してください", - "settings_saved": "設定が保存されました", - "setup_pin_code": "PINコードをセットアップ", - "share": "共有", - "share_action_prompt": "{count}項目を共有しました", - "share_add_photos": "写真を追加", - "share_assets_selected": "{count}選択中", - "share_dialog_preparing": "準備中", - "share_link": "共有リンク", - "shared": "共有済み", - "shared_album_activities_input_disable": "コメントはオフになってます", - "shared_album_activity_remove_content": "このアクティビティを削除しますか?", - "shared_album_activity_remove_title": "アクティビティを削除します", - "shared_album_section_people_action_error": "退出に失敗", - "shared_album_section_people_action_leave": "ユーザーをアルバムから退出させる", - "shared_album_section_people_action_remove_user": "ユーザーをアルバムから退出させる", - "shared_album_section_people_title": "人物", - "shared_by": "により共有", - "shared_by_user": "{user} により共有", - "shared_by_you": "あなたにより共有", - "shared_from_partner": "{partner} による写真", - "shared_intent_upload_button_progress_text": "{current} / {total} アップロード完了", - "shared_link_app_bar_title": "共有リンク", - "shared_link_clipboard_copied_massage": "クリップボードにコピーしました", - "shared_link_clipboard_text": "リンク: {link}\nパスワード: {password}", - "shared_link_create_error": "共有用のリンク作成時にエラーが発生しました", - "shared_link_custom_url_description": "この共有リンクにカスタムURLでアクセス", - "shared_link_edit_description_hint": "概要を追加", - "shared_link_edit_expire_after_option_day": "1日", - "shared_link_edit_expire_after_option_days": "{count}日", - "shared_link_edit_expire_after_option_hour": "1時間", - "shared_link_edit_expire_after_option_hours": "{count}時間", - "shared_link_edit_expire_after_option_minute": "1分", - "shared_link_edit_expire_after_option_minutes": "{count}分", - "shared_link_edit_expire_after_option_months": "{count}ヶ月", - "shared_link_edit_expire_after_option_year": "{count}年", - "shared_link_edit_password_hint": "共有パスワードを入力する", - "shared_link_edit_submit_button": "リンクをアップデートする", - "shared_link_error_server_url_fetch": "サーバーのURLを取得できません", - "shared_link_expires_day": "{count}日後に有効期限切れ", - "shared_link_expires_days": "{count}日後に有効期限切れ", - "shared_link_expires_hour": "{count}時間後に有効期限切れ", - "shared_link_expires_hours": "{count}時間後に有効期限切れ", - "shared_link_expires_minute": "{count}分後に有効期限切れ", - "shared_link_expires_minutes": "{count}分後に有効期限切れ", - "shared_link_expires_never": "有効期限はありません", - "shared_link_expires_second": "{count}秒後に有効期限切れ", - "shared_link_expires_seconds": "{count}秒後に有効期限切れ", - "shared_link_individual_shared": "1枚ずつ共有されています", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "共有済みのリンクを管理", - "shared_link_options": "共有リンクのオプション", - "shared_link_password_description": "この共有リンクにアクセスする際にパスワードを要求する", - "shared_links": "共有リンク", - "shared_links_description": "写真や動画をリンクで共有", - "shared_photos_and_videos_count": "{assetCount, plural, other {#個の共有された写真と動画}}", - "shared_with_me": "自分と共有中", - "shared_with_partner": "{partner} と共有しました", - "sharing": "共有", - "sharing_enter_password": "このページを閲覧するにはパスワードを入力してください。", - "sharing_page_album": "共有アルバム", - "sharing_page_description": "共有アルバムを作成して同じネットワークにいる人たちに写真を共有", - "sharing_page_empty_list": "共有アルバムなし", - "sharing_sidebar_description": "共有へのリンクをサイドバーに表示", - "sharing_silver_appbar_create_shared_album": "共有アルバムを作成", - "sharing_silver_appbar_share_partner": "パートナーと共有", - "shift_to_permanent_delete": "⇧を押してアセットを完全に削除", - "show_album_options": "アルバム設定を表示", - "show_albums": "アルバムを表示", - "show_all_people": "全ての人物を表示", - "show_and_hide_people": "人物を表示/非表示", - "show_file_location": "ファイルの場所を表示", - "show_gallery": "ギャラリーを表示", - "show_hidden_people": "非表示の人物を表示", - "show_in_timeline": "タイムラインに表示", - "show_in_timeline_setting_description": "このユーザーの写真と動画をタイムラインに表示", - "show_keyboard_shortcuts": "キーボードショートカットを表示", - "show_metadata": "メタデータを見る", - "show_or_hide_info": "情報を表示/非表示", - "show_password": "パスワードを表示", - "show_person_options": "人物設定を表示", - "show_progress_bar": "プログレスバーを表示", - "show_schema": "スキーマを表示", - "show_search_options": "検索オプションを表示", - "show_shared_links": "共有リンクを表示", - "show_slideshow_transition": "スライドショーのトランジションを表示", - "show_supporter_badge": "サポーターバッジ", - "show_supporter_badge_description": "サポーターバッジを表示", - "show_text_recognition": "文字認識を表示", - "show_text_search_menu": "テキスト検索メニューを表示", - "shuffle": "ランダム", - "sidebar": "サイドバー", - "sidebar_display_description": "サイドバーにビューへのリンクを表示", - "sign_out": "サインアウト", - "sign_up": "登録", - "size": "サイズ", - "skip_to_content": "コンテンツへスキップ", - "skip_to_folders": "フォルダへスキップ", - "skip_to_tags": "タグへスキップ", - "slideshow": "スライドショー", - "slideshow_repeat": "スライドショーを繰り返す", - "slideshow_repeat_description": "スライドショーが終わったら始めに戻ります", - "slideshow_settings": "スライドショー設定", - "sort_albums_by": "この順序でアルバムをソート…", - "sort_created": "作成日", - "sort_items": "項目の数", - "sort_modified": "変更日", - "sort_newest": "最新の写真", - "sort_oldest": "古い写真", - "sort_people_by_similarity": "似ている順に人物を並び替える", - "sort_recent": "最新の写真", - "sort_title": "タイトル", - "source": "ソース", - "stack": "スタック", - "stack_action_prompt": "{count}が重ねられました", - "stack_duplicates": "スタックの重複", - "stack_select_one_photo": "スタックのメインの写真を選択", - "stack_selected_photos": "選択した写真をスタックする", - "stacked_assets_count": "{count, plural, one {#枚} other {#枚}}スタックしました", - "stacktrace": "スタックトレース", - "start": "開始", - "start_date": "開始日", - "start_date_before_end_date": "開始日は終了日より前でなければなりません", - "state": "都道府県", - "status": "ステータス", - "stop_casting": "キャストを停止", - "stop_motion_photo": "ストップモーション写真", - "stop_photo_sharing": "写真の共有を無効化しますか?", - "stop_photo_sharing_description": "{partner} はあなたの写真にアクセスできなくなります。", - "stop_sharing_photos_with_user": "このユーザーとの写真の共有をやめる", - "storage": "ストレージ使用量", - "storage_label": "ストレージラベル", - "storage_quota": "ストレージ容量", - "storage_usage": "{available} 中 {used} 使用中", - "submit": "送信", - "success": "成功", - "suggestions": "ユーザーリスト", - "sunrise_on_the_beach": "海岸の日の出", - "support": "サポート", - "support_and_feedback": "サポートとフィードバック", - "support_third_party_description": "Immichのインストールはサードパーティーによってパッケージ化されています。遭遇した問題はそのパッケージに起因している可能性があるので以下のリンクを使って最初にそのパッケージに問題を提起してください。", - "swap_merge_direction": "統合する方向を入れ替え", - "sync": "同期", - "sync_albums": "アルバムを同期", - "sync_albums_manual_subtitle": "アップロード済みの全ての写真や動画を選択されたバックアップアルバムに同期する", - "sync_local": "ローカルを同期", - "sync_remote": "リモートを同期", - "sync_status": "同期の状態", - "sync_status_subtitle": "同期システムを確認・管理", - "sync_upload_album_setting_subtitle": "サーバー上のアルバムの内容を端末上のアルバムと同期します (サーバーにアルバムが無い場合自動で作成されます。また、アップロードされていない写真や動画は同期されません)", - "tag": "タグ付けする", - "tag_assets": "写真/動画にタグ付けする", - "tag_created": "タグ: {tag} を作成しました", - "tag_feature_description": "意味を持たせたタグトでグループ化して写真と動画を閲覧する", - "tag_not_found_question": "タグが見つかりませんか? こちらからタグを作成できます", - "tag_people": "人物タグ", - "tag_updated": "タグ: {tag} を更新しました", - "tagged_assets": "{count, plural, one {#個} other {#個}}の写真/動画にタグ付けしました", - "tags": "タグ", - "tap_to_run_job": "タップでジョブを開始", - "template": "テンプレート", - "text_recognition": "文字認識", - "theme": "テーマ", - "theme_selection": "テーマ選択", - "theme_selection_description": "ブラウザのシステム設定に基づいてテーマを明色または暗色に自動的に設定します", - "theme_setting_asset_list_storage_indicator_title": "ストレージに関する情報を表示", - "theme_setting_asset_list_tiles_per_row_title": "一行ごとの表示枚数: {count}", - "theme_setting_colorful_interface_subtitle": "アクセントカラーを背景にも使用する", - "theme_setting_colorful_interface_title": "カラフルなUI", - "theme_setting_image_viewer_quality_subtitle": "画像ビューの画質の設定", - "theme_setting_image_viewer_quality_title": "画像ビュー", - "theme_setting_primary_color_subtitle": "アクセント用の色を選択", - "theme_setting_primary_color_title": "アクセントカラー", - "theme_setting_system_primary_color_title": "端末で設定されている色を使う", - "theme_setting_system_theme_switch": "自動 (デバイスの設定を反映)", - "theme_setting_theme_subtitle": "テーマ設定", - "theme_setting_three_stage_loading_subtitle": "三段階読み込みを有効にすると、パフォーマンスが改善する可能性がありますが、ネットワーク負荷が著しく増加します。", - "theme_setting_three_stage_loading_title": "三段階読み込みをオンにする", - "then": "そのとき", - "they_will_be_merged_together": "これらは一緒に統合されます", - "third_party_resources": "サードパーティーリソース", - "time": "時刻", - "time_based_memories": "過去の思い出", - "time_based_memories_duration": "各写真を表示する秒数。", - "timeline": "タイムライン", - "timezone": "タイムゾーン", - "to_archive": "アーカイブ", - "to_change_password": "パスワードを変更", - "to_favorite": "お気に入り", - "to_login": "ログイン", - "to_multi_select": "複数選択", - "to_parent": "上位の階層へ", - "to_select": "選択", - "to_trash": "ゴミ箱", - "toggle_settings": "設定をトグル", - "toggle_theme_description": "テーマを切り替え", - "total": "合計", - "total_usage": "総使用量", - "trash": "ゴミ箱", - "trash_action_prompt": "{count}項目をゴミ箱に移動しました", - "trash_all": "全て削除", - "trash_count": "{count, number}枚ゴミ箱へ移動", - "trash_delete_asset": "写真/動画をゴミ箱へ移動/削除", - "trash_emptied": "ゴミ箱を空にしました", - "trash_no_results_message": "ゴミ箱に移動した写真や動画がここに表示されます。", - "trash_page_delete_all": "すべて削除", - "trash_page_empty_trash_dialog_content": "ゴミ箱を空にしますか?選択された項目は完全に削除されます。この操作は取り消せません。", - "trash_page_info": "ゴミ箱に移動したアイテムは{days}日後に削除されます", - "trash_page_no_assets": "ゴミ箱は空です", - "trash_page_restore_all": "すべて復元", - "trash_page_select_assets_btn": "項目を選択", - "trash_page_title": "ゴミ箱 ({count})", - "trashed_items_will_be_permanently_deleted_after": "ゴミ箱に入れられたアイテムは{days, plural, one {#日} other {#日}}後に完全に削除されます。", - "trigger": "トリガー", - "trigger_asset_uploaded": "アセットがアップロード", - "trigger_asset_uploaded_description": "新しい項目がアップロードされたときにトリガーされます", - "trigger_description": "ワークフローを開始するイベント", - "trigger_person_recognized": "認識された人物", - "trigger_person_recognized_description": "人物が検知された際のトリガー", - "trigger_type": "トリガータイプ", - "troubleshoot": "トラブルシューティング", - "type": "タイプ", - "unable_to_change_pin_code": "PINコードを変更できませんでした", - "unable_to_check_version": "アプリまたはサーバーのバージョンをチェックできませんでした", - "unable_to_setup_pin_code": "PINコードをセットアップできませんでした", - "unarchive": "アーカイブを解除", - "unarchive_action_prompt": "{count}項目をアーカイブから除きました", - "unarchived_count": "{count, plural, other {#枚アーカイブを解除しました}}", - "undo": "元に戻す", - "unfavorite": "お気に入り解除", - "unfavorite_action_prompt": "{count}項目をお気に入りから解除", - "unhide_person": "人物の非表示を解除", - "unknown": "不明", - "unknown_country": "不明な国", - "unknown_date": "不明な日付", - "unknown_year": "不明な年", - "unlimited": "無制限", - "unlink_motion_video": "モーションビデオのリンクを解除", - "unlink_oauth": "OAuthのリンクを解除", - "unlinked_oauth_account": "リンクが解除されたOAuthアカウント", - "unmute_memories": "メモリーのミュートを解除", - "unnamed_album": "無名のアルバム", - "unnamed_album_delete_confirmation": "本当にこのアルバムを削除しますか?", - "unnamed_share": "無名の共有", - "unsaved_change": "未保存の変更", - "unselect_all": "全て選択解除", - "unselect_all_duplicates": "全ての重複の選択を解除", - "unselect_all_in": "{group}のすべての選択を解除", - "unstack": "スタックを解除", - "unstack_action_prompt": "{count}項目の重ね合わせを解除", - "unstacked_assets_count": "{count, plural, one {#個} other {#個}}の写真/動画をスタックから解除しました", - "unsupported_field_type": "サポートされていないフィールドタイプ", - "untagged": "タグを解除", - "untitled_workflow": "無題のワークフロー", - "up_next": "次へ", - "update_location_action_prompt": "{count}項目を右記の位置情報にアップデートします:", - "updated_at": "更新", - "updated_password": "パスワードを更新しました", - "upload": "アップロード", - "upload_concurrency": "アップロードの同時実行数", - "upload_details": "アップロードの詳細", - "upload_dialog_info": "選択した項目のバックアップをしますか?", - "upload_dialog_title": "アップロード", - "upload_error_with_count": "{count, plural, one {#個の写真/動画} other {#個の写真/動画}}についてアップロードエラーが発生しました", - "upload_errors": "アップロードは{count, plural, one {#個} other {#個}}のエラーで完了しました、新しくアップロードされたアセットを見るにはページを更新してください。", - "upload_finished": "アップロード完了", - "upload_progress": "残り {remaining, number} - {processed, number}/{total, number} 処理済み", - "upload_skipped_duplicates": "{count, plural, one {#個} other {#個}}の重複した写真/動画をスキップしました", - "upload_status_duplicates": "重複", - "upload_status_errors": "エラー", - "upload_status_uploaded": "アップロード済", - "upload_success": "アップロード成功、新しくアップロードされた写真/動画を見るにはページを更新してください。", - "upload_to_immich": "Immichにアップロード ({count})", - "uploading": "アップロード中", - "uploading_media": "メディアをアップロード中", - "url": "URL", - "usage": "使用容量", - "use_biometric": "生体認証をご利用ください", - "use_current_connection": "現在の接続情報を使用", - "use_custom_date_range": "代わりにカスタム日付範囲を使用", - "user": "ユーザー", - "user_has_been_deleted": "このユーザーは削除されました", - "user_id": "ユーザーID", - "user_liked": "{user} が{type, select, photo {この写真を} video {この動画を} asset {このアセットを} other {}}いいねしました", - "user_pin_code_settings": "PINコード", - "user_pin_code_settings_description": "PINコードを管理", - "user_privacy": "ユーザープライバシー", - "user_purchase_settings": "購入", - "user_purchase_settings_description": "購入を管理", - "user_role_set": "{user} を{role}に設定しました", - "user_usage_detail": "ユーザー使用状況の詳細", - "user_usage_stats": "アカウント利用状況統計", - "user_usage_stats_description": "アカウント利用状況統計を表示", - "username": "ユーザー名", - "users": "ユーザー", - "users_added_to_album_count": "{count, plural, one {#人} other {#人}}をアルバムに追加しました", - "utilities": "ユーティリティ", - "validate": "認証", - "validate_endpoint_error": "有効なURLを入力してください", - "validation_error": "バリデーションエラー", - "variables": "変数", - "version": "バージョン", - "version_announcement_closing": "あなたの友人、Alex", - "version_announcement_message": "こんにちは! 新しいバージョンのImmichがリリースされました。特にWatchTowerやImmichインスタンスを自動的に更新する仕組みを設けている場合はリリースノートをよく読んで設定が最新のものになっているか確認してください。", - "version_history": "バージョン履歴", - "version_history_item": "{date}に{version}をインストール", - "video": "動画", - "video_hover_setting": "ホバー時にサムネイルで動画を再生", - "video_hover_setting_description": "マウスが項目の上にあるときに動画のサムネイルを再生します。無効時でも再生アイコンにカーソルを合わせると再生を開始できます。", - "videos": "ビデオ", - "videos_count": "{count, plural, one {#個} other {#個}}の動画", - "videos_only": "動画のみ", - "view": "見る", - "view_album": "アルバムを見る", - "view_all": "すべて見る", - "view_all_users": "全てのユーザーを確認する", - "view_asset_owners": "アセットの所有者を閲覧", - "view_details": "詳細を表示", - "view_in_timeline": "タイムラインで見る", - "view_link": "リンクを見る", - "view_links": "リンクを確認する", - "view_name": "分類", - "view_next_asset": "次の写真/動画を見る", - "view_previous_asset": "前の写真/動画を見る", - "view_qr_code": "QRコードを見る", - "view_similar_photos": "類似する写真を見る", - "view_stack": "ビュースタック", - "view_user": "ユーザーを見る", - "viewer_remove_from_stack": "スタックから外す", - "viewer_stack_use_as_main_asset": "メインの画像として使用する", - "viewer_unstack": "スタックを解除", - "visibility_changed": "{count, plural, one {#人} other {#人}}の人物の非表示設定が変更されました", - "visual": "ビジュアル", - "visual_builder": "ビジュアルビルダー", - "waiting": "待機中", - "waiting_count": "待機中: {count}", - "warning": "警告", - "week": "週", - "welcome": "ようこそ", - "welcome_to_immich": "Immichにようこそ", - "width": "幅", - "wifi_name": "Wi-Fiの名前(SSID)", - "workflow_delete_prompt": "このワークフローをほんとうに削除しますか?", - "workflow_deleted": "ワークフロー削除完了", - "workflow_description": "ワークフローの説明文", - "workflow_info": "ワークフローの情報", - "workflow_json": "ワークフローJSON", - "workflow_json_help": "JSONフォーマットでワークフローを編集 (編集内容はビジュアルビルダーにも反映されます)", - "workflow_name": "ワークフロー名称", - "workflow_navigation_prompt": "変更内容を保存せずに終了しますか?", - "workflow_summary": "ワークフローのサマリ", - "workflow_update_success": "ワークフローの更新に成功しました", - "workflow_updated": "ワークフローが更新されました", - "workflows": "ワークフロー", - "workflows_help_text": "ワークフローはあなたのアセットに対し、トリガーやフィルターを設定することでアクションを自動化します", - "wrong_pin_code": "PINコードが間違っています", - "year": "年", - "years_ago": "{years, plural, one {#年} other {#年}}前", - "yes": "はい", - "you_dont_have_any_shared_links": "共有リンクはありません", - "your_wifi_name": "Wi-Fiの名前(SSID)", - "zero_to_clear_rating": "0を押すと項目の評価を削除できます", - "zoom_image": "画像を拡大", - "zoom_to_bounds": "画面端までズーム" -} +{} diff --git a/i18n/ka.json b/i18n/ka.json index f8d98ed25c..0967ef424b 100644 --- a/i18n/ka.json +++ b/i18n/ka.json @@ -1,424 +1 @@ -{ - "about": "შესახებ", - "account": "ანგარიში", - "account_settings": "ანგარიშის პარამეტრები", - "acknowledge": "მიღება", - "action": "ქმედება", - "action_common_update": "განაახლე", - "actions": "ქმედებები", - "active": "აქტიური", - "active_count": "aქტიური: {count}", - "activity": "აქტივობა", - "activity_changed": "აქტივობა {enabled, select, true {ჩართული} other {გამორთული}}", - "add": "დაამატე", - "add_a_description": "დაამატე აღწერა", - "add_a_location": "დაამატე ადგილი", - "add_a_name": "დაამატე სახელი", - "add_a_title": "დაასათაურე", - "add_action": "დაამატე მოქმედება", - "add_assets": "რესურსის ატვირთვა", - "add_birthday": "დაბადების დღის დამატება", - "add_endpoint": "ბოლოწერტილის დამატება", - "add_exclusion_pattern": "დაამატე გამონაკლისი ნიმუში", - "add_filter": "დაამატე ფილტრი", - "add_location": "დაამატე ადგილი", - "add_more_users": "დაამატე მომხმარებლები", - "add_partner": "დაამატე პარტნიორი", - "add_path": "დაამატე მისამართი", - "add_photos": "დაამატე ფოტოები", - "add_tag": "დაამატე თეგი", - "add_to": "დაამატე ...ში", - "add_to_album": "დაამატე ალბომში", - "add_to_album_bottom_sheet_added": "დამატებულია {album}-ში", - "add_to_album_bottom_sheet_already_exists": "{album}-ში უკვე არსებობს", - "add_to_albums": "დაამატე ალბომებში", - "add_to_albums_count": "დაამატე ალბომში ({count})", - "add_to_bottom_bar": "დამატება სად", - "add_to_shared_album": "დაამატე საზიარო ალბომში", - "add_url": "დაამატე URL", - "added_to_archive": "დაარქივდა", - "added_to_favorites": "დაამატე რჩეულებში", - "added_to_favorites_count": "{count, number} დაემატა რჩეულებში", - "admin": { - "admin_user": "ადმინ მომხმარებელი", - "asset_offline_description": "ეს გარე ბიბლიოთეკის აქტივი დისკზე ვერ მოიძებნა და გადატანილი იქნა ნაგვის ყუთში. თუ ფაილი ბიბლიოთეკის შიგნით იქნა გადატანილი, შეამოწმეთ შესაბამისი აქტივი დროის ხაზზე. ამ აქტივის აღსადგენად, დარწმუნდით, რომ ქვემოთ მოცემული ფაილის მისამართი Immich-ის მიერ წვდომადია და დაასკანერეთ ბიბლიოთეკა.", - "authentication_settings": "ავთენტიკაციის პარამეტრები", - "authentication_settings_description": "პაროლის, OAuth-ის და სხვა ავტენთიფიკაციის პარამეტრების მართვა", - "authentication_settings_disable_all": "ნამდვილად გინდა ავტორიზაციის ყველა მეთოდის გამორთვა? ავტორიზაციას ვეღარანაირად შეძლებ.", - "authentication_settings_reenable": "რეაქტივაციისთვის, გამოიყენე სერვერის ბრძანება.", - "background_task_job": "ფონური დავალებები", - "backup_database": "ბაზის დამპის შექმნა", - "backup_database_enable_description": "ბაზის დამპების ჩართვა", - "backup_keep_last_amount": "წინა დამპების შესანარჩუნებელი რაოდენობა", - "backup_onboarding_title": "მარქაფები", - "backup_settings": "მონაცემთა ბაზის დამპის მორგება", - "backup_settings_description": "მონაცემთა ბაზის დამპის პარამეტრების მართვა.", - "cleared_jobs": "დავალებები {job}-ისათვის გაწმენდილია", - "config_set_by_file": "მიმდინარე კონფიგურაცია ფაილის მიერ არის დაყენებული", - "confirm_delete_library": "ნამდვილად გინდა {library} ბიბლიოთეკის წაშლა?", - "confirm_delete_library_assets": "მართლა გსურთ ამ ბიბლიოთეკის წაშლა? ეს ქმედება Immich-იდან წაშლის{count, plural, one {# არსებულ აქტივს} other {ყველა # არებულ აქტივს}} და ეს ქმედება შეუქცევადია. ფაილები დისკზე შენარჩუნებული იქნება.", - "confirm_email_below": "დასადასტურებლად, ქვემოთ აკრიფე \"{email}\"", - "confirm_reprocess_all_faces": "მართლა გსურთ ყველა სახის თავიდან დამუშავება? ეს ქმედება ხალხისათვის მინიჭებულ სახელებს გაწმენდს.", - "confirm_user_password_reset": "ნამდვილად გინდა {user}-(ი)ს პაროლის დარესეტება?", - "create_job": "შექმენი დავალება", - "cron_expression": "Cron გამოსახულება", - "disable_login": "გამორთე ავტორიზაცია", - "face_detection": "სახის ამოცნობა", - "image_format": "ფორმატი", - "image_format_description": "WebP ფორმატი JPEG-ზე პატარა ფაილებს აწარმოებს, მაგრამ მის დამზადებას უფრო მეტი დრო სჭირდება.", - "image_fullsize_title": "სრული ზომის გამოსახულების პარამეტრები", - "image_prefer_wide_gamut": "უპირატესობა მიენიჭოს ფერის ფართე დიაპაზონს", - "image_preview_title": "გამოსახულების გადახედვის პარამეტრები", - "image_quality": "ხარისხი", - "image_resolution": "გაფართოება", - "image_settings": "გამოსახულების პარამეტრები", - "image_settings_description": "გენერირებული ფოტოების ხარისხისა და რეზოლუციის მართვა", - "image_thumbnail_description": "მინიატურა მეტაინფორმაციის გარეშე, რომელიც ფოტოები ჯგუფურად თვალიერებისას გამოიყენება(მაგ. მთავარ თაიმლაინზე)", - "image_thumbnail_quality_description": "მინიატურის ხარისხი 1-დან 100-მდე. დიდი რიცხვი შეესაბამება უკეთეს ხარისხს, თუმცა, უფრო დიდ ფაილებს და აპლიკაციის შესაძლო შენელებას.", - "image_thumbnail_title": "მინიატურის პარამეტრები", - "library_created": "შეიქმნა ბიბლიოთეკა: {library}", - "library_deleted": "ბიბლიოთეკა წაიშალა", - "library_settings": "გარე ბიბლიოთეკა", - "library_settings_description": "გარე ბიბლიოთეკების პარამეტრების მართვა", - "logging_settings": "ჟურნალი", - "machine_learning_ocr": "OCR", - "map_settings": "რუკა", - "migration_job": "მიგრაცია", - "notification_email_secure": "SMTPS", - "oauth_settings": "OAuth", - "template_email_preview": "მინიატურა", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_hardware_acceleration": "ჰარდვეარული ამაჩქარებელი", - "transcoding_policy": "ტრანსკოდირების პოლიტიკა", - "transcoding_threads": "ნაკადები", - "transcoding_tone_mapping": "ტონების ასახვა" - }, - "administration": "ადმინისტრაცია", - "advanced": "დამატებით", - "advanced_settings_troubleshooting_title": "პრობლემების გადაწყვეტა", - "album_info_card_backup_album_excluded": "ამოღებულია", - "album_info_card_backup_album_included": "ჩასმულია", - "albums": "ალბომები", - "all": "ყველა", - "allowed": "დაშვებულია", - "anti_clockwise": "საათის ისრის საწინააღმდეგო", - "app_bar_signout_dialog_ok": "დიახ", - "archive": "არქივი", - "archived": "დაარქივებულია", - "asset_hashing": "დაჰეშვა.…", - "asset_list_layout_settings_group_automatically": "ავტომატური", - "asset_list_layout_sub_title": "განლაგება", - "asset_skipped": "გამოტოვებულია", - "asset_uploaded": "ატვირთულია", - "asset_uploading": "მიმდინარეობს ატვირთვა…", - "assets": "ობიექტები", - "back": "უკან", - "backup": "მარქაფი", - "backup_all": "ყველა", - "backup_controller_page_background_battery_info_ok": "დიახ", - "backup_controller_page_backup": "მარქაფი", - "backup_controller_page_backup_selected": "არჩეულია: ", - "backup_controller_page_excluded": "ამოღებულია: ", - "backup_controller_page_remainder": "დარჩენილია", - "backup_info_card_assets": "აქტივები", - "backup_manual_cancelled": "გაუქმებულია", - "backup_manual_success": "წარმატება", - "backward": "უკან გადასვლა", - "build": "აგება", - "cache_settings_duplicated_assets_clear_button": "გასუფთავება", - "cache_settings_statistics_thumbnail": "მინიატურები", - "camera": "კამერა", - "cancel": "გაუქმება", - "canceled": "გაუქმებულია", - "canceling": "უქმდება", - "cast": "ტრანსლაცია", - "charging": "იტენება", - "city": "ქალაქი", - "clear": "გასუფთავება", - "client_cert_dialog_msg_confirm": "დიახ", - "client_cert_import": "შემოტანა", - "clockwise": "საათის ისრის მიმართულებით", - "close": "დახურვა", - "collapse": "აკეცვა", - "color": "ფერი", - "completed": "დასრულდა", - "confirm": "დასტური", - "contain": "შეიცავს", - "context": "კონტექსტი", - "continue": "გაგრძელება", - "country": "ქვეყანა", - "cover": "ყდა", - "covers": "ყდები", - "create": "შექმნა", - "create_album_page_untitled": "უსახელო", - "created": "შექმნილია", - "created_at": "შეიქმნა", - "crop": "ამოჭრა", - "curated_object_page_title": "ნივთები", - "dark": "მუქი", - "date": "თარიღი", - "day": "დღე", - "days": "დღე", - "delete": "წაშლა", - "description": "აღწერა", - "details": "დეტალები", - "direction": "მიმართულება", - "disabled": "გათიშულია", - "discord": "Discord", - "discover": "აღმოჩენა", - "documentation": "დოკუმენტაცია", - "done": "მზადაა", - "download": "გადმოწერა", - "download_settings": "გადმოწერა", - "downloading": "მიმდინარეობს გადმოწერა", - "duplicates": "დუბლიკატები", - "duration": "ხანგრძლივობა", - "edit": "ჩასწორება", - "edit_location_dialog_title": "მდებარეობა", - "editor": "რედაქტორი", - "email": "ელფოსტა", - "enable": "ჩართვა", - "enabled": "ჩართულია", - "enqueued": "რიგში ჩასმულია", - "error": "შეცდომა", - "exif": "Exif", - "exif_bottom_sheet_details": "დეტალები", - "exif_bottom_sheet_location": "მდებარეობა", - "exif_bottom_sheet_people": "ხალხი", - "experimental_settings_title": "საცდელი", - "expired": "ვადაამოწურულია", - "explore": "დათვალიერება", - "explorer": "გამცილებელი", - "export": "გატანა", - "extension": "გაფართოება", - "external": "გარე", - "face_unassigned": "მიუნიჭებელი", - "failed": "ჩავარდა", - "favorite": "რჩეული", - "favorites": "რჩეულები", - "features": "თვისებები", - "filename": "ფაილის სახელი", - "filetype": "ფაილის ტიპი", - "filter": "ფილტრი", - "first": "პირველი", - "folder": "საქაღალდე", - "folders": "საქაღალდეები", - "forward": "წინ", - "general": "ზოგადი", - "gps": "GPS", - "hashing": "დაჰეშვა", - "host": "ჰოსტი", - "hour": "საათი", - "hours": "საათი", - "id": "ID", - "idle": "უქმე", - "image": "გამოსახულება", - "info": "ინფორმაცია", - "jobs": "დავალებები", - "keep": "შენარჩუნება", - "language": "ენა", - "last": "ბოლო", - "latitude": "განედი", - "leave": "გასვლა", - "level": "დონე", - "library": "ბიბლიოთეკა", - "licenses": "ლიცენზიები", - "light": "ღია", - "like": "მოწონება", - "list": "სია", - "loading": "ჩატვირთვა", - "local": "ლოკალური", - "location": "მდებარეობა", - "lock": "დაბლოკვა", - "login": "შესვლა", - "login_form_back_button_text": "უკან", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:პორტი", - "login_form_password_hint": "პაროლი", - "logs": "ჟურნალი", - "longitude": "გრძედი", - "look": "შეხედვა", - "make": "მწარმოებელი", - "map": "რუკა", - "map_location_dialog_yes": "დიახ", - "matches": "დამთხვევები", - "memories": "მოგონებები", - "memory": "მეხსიერება", - "menu": "მენიუ", - "merge": "შერწყმა", - "minimize": "დაპატარავება", - "minute": "წუთი", - "minutes": "წუთი", - "missing": "აკლია", - "model": "მოდელი", - "month": "თვე", - "more": "მეტი", - "move": "გადატანა", - "name": "სახელი", - "navigate": "ნავიგაცია", - "networking_settings": "ქსელი", - "never": "არასდროს", - "next": "შემდეგი", - "no": "არა", - "not_available": "N/A", - "notes": "შენშვნები", - "notifications": "გაფრთხილებები", - "oauth": "OAuth", - "ocr": "OCR", - "offline": "ინტერნეტის გარეშე", - "offset": "წანაცვლება", - "ok": "დიახ", - "onboarding": "სამუშაოს დაწყება", - "online": "ხაზზეა", - "open": "გახსნა", - "options": "მორგება", - "or": "ან", - "original": "ორიგინალი", - "other": "სხვა", - "owned": "საკუთარი", - "owner": "მფლობელი", - "partner": "პარტნიორი", - "partners": "პარტნიორები", - "password": "პაროლი", - "path": "ბილიკი", - "pattern": "შაბლონი", - "pause": "პაუზა", - "paused": "დაპაუზებული", - "pending": "რიგშია", - "people": "ხალხი", - "permission": "წვდომა", - "permission_onboarding_back": "უკან", - "person": "პიროვნება", - "photos": "ფოტოები", - "place": "ადგილი", - "places": "ადგილები", - "play": "დაკვრა", - "port": "პორტი", - "preferences_settings_title": "მორგება", - "preparing": "მომზადება", - "preset": "პრესეტი", - "preview": "მინიატურა", - "previous": "წინა", - "primary": "ძირითადი", - "privacy": "კონფიდენციალობა", - "profile": "პროფილი", - "profile_drawer_app_logs": "ჟურნალი", - "profile_drawer_github": "GitHub", - "purchase_account_info": "მხარდამჭერი", - "purchase_button_activate": "გააქტიურება", - "purchase_button_buy": "ყიდვა", - "purchase_button_select": "არჩევა", - "purchase_individual_title": "ინდივიდუალური", - "purchase_server_title": "სერვერი", - "reassign": "თავიდან მინიჭება", - "recent": "უახლესი", - "refresh": "განახლება", - "refreshed": "განახლებულია", - "remote": "დაშორებული", - "remove": "წაშლა", - "rename": "სახელის გადარქმევა", - "repair": "შეკეთება", - "repository": "რეპოზიტორია", - "rescan": "თავიდან სკანირება", - "reset": "ჩამოყრა", - "resolution": "გაფართოება", - "restore": "აღდგენა", - "resume": "გაგრძელება", - "role": "როლი", - "role_editor": "რედაქტორი", - "role_viewer": "დამთვალიერებელი", - "running": "გაშვებულია", - "save": "შენახვა", - "saved": "შენახულია", - "scan_library": "სკანირება", - "search": "ძებნა", - "search_by_ocr_example": "ლატე", - "search_filter_date": "თარიღი", - "search_filter_location": "მდებარეობა", - "search_page_categories": "კატეგორიები", - "search_page_screenshots": "ეკრანის ანაბეჭდები", - "search_page_selfies": "სელფიები", - "search_page_things": "ნივთები", - "search_suggestion_list_smart_search_hint_2": "m:თქვენი-საძებნი-სტრიქონი", - "second": "წამი", - "select": "აირჩიეთ", - "selected": "არჩეულია", - "set": "დაყენება", - "setting_image_viewer_title": "გამოსახულებები", - "setting_languages_apply": "გადატარება", - "setting_notifications_notify_immediately": "დაუყოვნებლივ", - "setting_notifications_notify_never": "არასდროს", - "setting_video_viewer_looping_title": "წრიულად", - "settings": "მორგება", - "share": "გაზიარება", - "share_dialog_preparing": "მომზადება...", - "shared": "გაზიარებულია", - "shared_album_section_people_title": "ხალხი", - "shared_link_info_chip_metadata": "EXIF", - "sharing": "გაზიარებები", - "shuffle": "შემთხვევით", - "sidebar": "გვერდითი პანელი", - "size": "ზომა", - "slideshow": "სლაიდშოუ", - "sort_title": "სათაური", - "source": "წყარო", - "stack": "დაჯგუფება", - "stacktrace": "ჯგუფის ტრეისი", - "start": "გაშვება", - "state": "მდგომარეობა", - "status": "სტატუსი", - "submit": "გადაცემა", - "success": "წარმატება", - "suggestions": "რჩევები", - "support": "მხარდაჭერა", - "sync": "სინქრონიზაცია", - "tag": "ჭდე", - "tags": "ჭდეები", - "template": "ნიმუში", - "theme": "თემა", - "time": "დრო", - "timeline": "ქრონოლოგია", - "timezone": "დროის სარტყელი", - "to_archive": "არქივი", - "to_favorite": "რჩეული", - "to_login": "შესვლა", - "to_trash": "ნაგვის ყუთი", - "total": "ჯამი", - "trash": "ნაგვის ყუთი", - "troubleshoot": "პრობლემების გადაჭრა", - "type": "ტიპი", - "unarchive": "არქივიდან ამოღება", - "undo": "გაუქმება", - "unfavorite": "რჩეულებიდან წაშლა", - "unknown": "უცნობი", - "unlimited": "შეუზღუდავი", - "unstack": "განჯგუფება", - "untagged": "ჭდის გარეშე", - "updated_at": "განახლდა", - "upload": "ატვირთვა", - "upload_status_duplicates": "დუბლიკატები", - "upload_status_errors": "შეცდომები", - "upload_status_uploaded": "ატვირთულია", - "uploading": "მიმდინარეობს ატვირთვა", - "url": "URL", - "usage": "გამოყენება", - "user": "მომხმარებელი", - "user_purchase_settings": "შეძენა", - "username": "მომხმარებლის სახელი", - "users": "მომხმარებლები", - "utilities": "ხელსაწყოები", - "validate": "გადამოწმება", - "variables": "ცვლადები", - "version": "ვერსია", - "video": "ვიდეო", - "videos": "ვიდეოები", - "view": "დათვალიერება", - "view_name": "ხედი", - "viewer_unstack": "განჯგუფება", - "waiting": "მოლოდინი", - "warning": "გაფრთხილება", - "week": "კვირა", - "welcome": "მოგესალმებით", - "year": "წელი", - "yes": "დიახ" -} +{} diff --git a/i18n/kk.json b/i18n/kk.json index 7025ae0d22..0967ef424b 100644 --- a/i18n/kk.json +++ b/i18n/kk.json @@ -1,27 +1 @@ -{ - "about": "Туралы", - "account": "Тіркелгі", - "add": "қосу", - "add_a_description": "сипаттаманы қосу", - "add_a_location": "суретті түсірген жерді қосы", - "add_a_name": "Атын қосу", - "add_birthday": "Туған күнін қосу", - "add_location": "жерді қосу", - "add_more_users": "қосымша адамдарды тіркеу", - "add_partner": "жолдасты қосу", - "add_photos": "суреттерді қосу", - "add_tag": "тегті қосу", - "add_to": "қосу…", - "add_to_album": "альбомға қосу", - "add_to_album_bottom_sheet_added": "{album}'ға қосылған", - "add_to_album_bottom_sheet_already_exists": "Онсыз да {album} болған", - "add_to_albums": "альбомдарға қосу", - "add_to_shared_album": "бөліскен альбомға қосу", - "add_url": "URL таңдау", - "added_to_archive": "Архивке жіберілген", - "added_to_favorites": "таңдаулыларға қосылған", - "admin": { - "create_job": "Жұмысты бастау" - }, - "zoom_image": "суретті үлкейту" -} +{} diff --git a/i18n/km.json b/i18n/km.json index 4a90cf5e38..0967ef424b 100644 --- a/i18n/km.json +++ b/i18n/km.json @@ -1,56 +1 @@ -{ - "about": "អំពី", - "account": "គណនី", - "account_settings": "ការកំណត់គណនី", - "acknowledge": "បានដឹងនិងទទួលស្គាល់", - "action": "សកម្មភាព", - "action_common_update": "ធ្វើបច្ចុប្បន្នភាព", - "actions": "សកម្មភាពផ្សេងៗ", - "active": "កំពុងសកម្ម", - "active_count": "ដំណើរការ :{count}", - "activity": "សកម្មភាពផ្សេងៗ", - "add": "បន្ថែម", - "add_a_description": "បន្ថែមការពិពណ៌នា", - "add_a_location": "បន្ថែមទីតាំង", - "add_a_name": "បន្ងែមឈ្មោះ", - "add_a_title": "បន្ងែមចំណងជើង", - "add_birthday": "បន្ថែមថ្ងៃខែឆ្មាំកំណើត", - "add_endpoint": "បន្ថែម Endpoint", - "add_exclusion_pattern": "បន្ថែមលំនាំលើកលែង", - "add_location": "បន្ថែមទីតាំង", - "add_more_users": "បន្ថែមអ្នកប្រើប្រាស់", - "add_partner": "បន្ថែមដៃគូ", - "add_path": "បន្លែម Path", - "add_photos": "បន្ថែមរូបថត", - "add_tag": "បន្ថែមស្លាក", - "add_to": "បន្ថែមទៅក្នុង…", - "add_to_album": "បន្ថែមទៅក្នុងអាល់ប៊ុម", - "add_to_album_bottom_sheet_added": "បានបន្ថែមទៅក្នុង {album}", - "add_to_album_bottom_sheet_already_exists": "បន្ថែមក្នុង {album} រួចទៅហើយ", - "add_to_album_bottom_sheet_some_local_assets": "Local assets មួយចំនួនមិនអាចបញ្ចូលទៅក្នុងអាល់ប៊ុមបានទេ", - "add_to_album_toggle": "បិទបើកការជ្រើសរើសសម្រាប់ {album}", - "add_to_albums": "បន្ថែមទៅក្នុងអាល់ប៊ុមច្រើន", - "add_to_albums_count": "បន្ថែមទៅក្នុងអាល់ប៊ុមចំនួន ({count})", - "add_to_bottom_bar": "បន្ថែមទៅក្នុង", - "add_to_shared_album": "បន្ថែមទៅក្នុងអាល់ប៊ុមដែលបានចែករំលែក", - "add_upload_to_stack": "បន្ថែមការបង្ហោះទៅជង់", - "add_url": "បន្ថែម URL", - "added_to_archive": "បានបន្ថែមទៅក្នុងបណ្ណសារ", - "added_to_favorites": "បានបន្ថែមទៅក្នុងចំណង់ចំណូលចិត្ត", - "added_to_favorites_count": "បានបន្ថែម {count, number} ទៅក្នុងចំណង់ចំណូលចិត្ត", - "admin": { - "admin_user": "អ្នកប្រើប្រាស់អេតមីន", - "asset_offline_description": "Asset ​បណ្ណាល័យ​ខាង​ក្រៅ​នេះ​លែង​ត្រូវ​បាន​រក​ឃើញ​នៅ​លើ​ថាស​ហើយ​ត្រូវ​បាន​ផ្លាស់ទី​តាំងទៅ​ធុង​សំរាម។ ប្រសិនបើឯកសារត្រូវបានផ្លាស់ទីក្នុងបណ្ណាល័យ, សូមពិនិត្យមើលការកំណត់ពេលវេលារបស់អ្នកសម្រាប់ Asset ដែលត្រូវគ្នាថ្មី។ ដើម្បីស្ដារ Asset នេះឡើងវិញ សូមប្រាកដថា Path ឯកសារខាងក្រោមអាចចូលប្រើបានដោយ Immich និងស្កេនបណ្ណាល័យបាន។", - "authentication_settings": "ការកំណត់ការផ្ទៀងផ្ទាត់", - "authentication_settings_description": "គ្រប់គ្រងពាក្យសំងាត់, OAuth, និងការកំណត់ការផ្ទៀងផ្ទាត់ផ្សេងៗទៀត", - "authentication_settings_disable_all": "តើអ្នកប្រាកដថាចង់បិទវិធីសាស្ត្រចូលទាំងអស់មែនទេ? ការចូលនឹងត្រូវបានបិទទាំងស្រុង។", - "authentication_settings_reenable": "ដើម្បីបើកដំណើរការឡើងវិញ, សូមប្រើServer Command។", - "background_task_job": "កិច្ចការផ្ទៃខាងក្រោយ", - "backup_database": "បង្កើតមូលដ្ឋានទិន្នន័យ​ Dump", - "backup_database_enable_description": "បើកមូលដ្ឋានទិន្នន័យ Dumps", - "backup_keep_last_amount": "ចំនួននៃ Dumps ពីមុនដែលត្រូវរក្សាទុក", - "backup_onboarding_1_description": "ច្បាប់ចម្លងក្រៅបណ្តាញនៅក្នុងពពក ឬនៅកន្លែងផ្សេងទៀត។", - "backup_onboarding_2_description": "ឯកសារចម្លងនៅលើឧបករណ៍ផ្សេងៗ។ នេះរួមបញ្ចូលទាំងឯកសារសំខាន់ៗ និងការបម្រុងទុកនៃឯកសារទាំងនោះ។", - "backup_onboarding_3_description": "ចំនួនឯកសារចម្លងនៃទិន្នន័យរបស់អ្នក, រួមបញ្ចូលទាំងឯកសារដើម។ នេះរួមបញ្ចូលទាំងច្បាប់ចម្លងក្រៅបណ្តាញ 1 និងច្បាប់ចម្លងខាងក្រៅ 2 ។" - } -} +{} diff --git a/i18n/kmr.json b/i18n/kmr.json index cf634e00da..0967ef424b 100644 --- a/i18n/kmr.json +++ b/i18n/kmr.json @@ -1,6 +1 @@ -{ - "about": "دەربارە", - "account": "هەژمار", - "account_settings": "ڕێکخستنی هەژمار", - "acknowledge": "دانپێدانان" -} +{} diff --git a/i18n/kn.json b/i18n/kn.json index ec7c174e69..0967ef424b 100644 --- a/i18n/kn.json +++ b/i18n/kn.json @@ -1,273 +1 @@ -{ - "about": "ಕುರಿತು", - "account": "ಖಾತೆ", - "account_settings": "ಖಾತೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "acknowledge": "ಅಂಗೀಕರಿಸಿ", - "action": "ಕಾರ್ಯ", - "action_common_update": "ನವೀಕರಿಸಿ", - "action_description": "ಫಿಲ್ಟರ್ ಮಾಡಿದ ಸ್ವತ್ತುಗಳ ಮೇಲೆ ನಿರ್ವಹಿಸಬೇಕಾದ ಕ್ರಿಯೆಗಳ ಸೆಟ್", - "actions": "ಕ್ರಿಯೆಗಳು", - "active": "ಸಕ್ರಿಯ", - "active_count": "ಸಕ್ರಿಯ: {count}", - "activity": "ಚಟುವಟಿಕೆ", - "activity_changed": "ಚಟುವಟಿಕೆ {enabled, select, true{ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ} other {ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ}}", - "add": "ಸೇರಿಸಿ", - "add_a_description": "ವಿವರಣೆಯನ್ನು ಸೇರಿಸಿ", - "add_a_location": "ಸ್ಥಳವನ್ನು ಸೇರಿಸಿ", - "add_a_name": "ಹೆಸರನ್ನು ಸೇರಿಸಿ", - "add_a_title": "ಶೀರ್ಷಿಕೆಯನ್ನು ಸೇರಿಸಿ", - "add_action": "ಕ್ರಿಯೆಯನ್ನು ಸೇರಿಸಿ", - "add_action_description": "ನಿರ್ವಹಿಸಲು ಕ್ರಿಯೆಯನ್ನು ಸೇರಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ", - "add_birthday": "ಜನ್ಮದಿನ ಸೇರಿಸಿ", - "add_endpoint": "ಎಂಡ್‌ಪಾಯಿಂಟ್ ಸೇರಿಸಿ", - "add_exclusion_pattern": "ಹೊರಗಿಡುವಿಕೆ ಮಾದರಿಯನ್ನು ಸೇರಿಸಿ", - "add_filter": "ಫಿಲ್ಟರ್ ಸೇರಿಸಿ", - "add_filter_description": "ಫಿಲ್ಟರ್ ಸ್ಥಿತಿಯನ್ನು ಸೇರಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ", - "add_location": "ಸ್ಥಳ ಸೇರಿಸಿ", - "add_more_users": "ಹೆಚ್ಚಿನ ಬಳಕೆದಾರರನ್ನು ಸೇರಿಸಿ", - "add_partner": "ಪಾಲುದಾರರನ್ನು ಸೇರಿಸಿ", - "add_path": "ಹಾದಿಯನ್ನು ಸೇರಿಸಿ", - "add_photos": "ಫೋಟೋಗಳನ್ನು ಸೇರಿಸಿ", - "add_tag": "ಟ್ಯಾಗ್ ಸೇರಿಸಿ", - "add_to": "ಸೇರಿಸಿ…", - "add_to_album": "ಆಲ್ಬಮ್‌ಗೆ ಸೇರಿಸಿ", - "add_to_album_bottom_sheet_added": "{album}ಗೆ ಸೇರಿಸಿದೆ", - "add_to_album_bottom_sheet_already_exists": "ಈಗಾಗಲೇ {album} ನಲ್ಲಿದೆ", - "add_to_album_bottom_sheet_some_local_assets": "ಕೆಲವು ಸ್ಥಳೀಯ ಸ್ವತ್ತುಗಳನ್ನು ಆಲ್ಬಮ್‌ಗೆ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", - "add_to_album_toggle": "{album}ಗಾಗಿ ಆಯ್ಕೆಯನ್ನು ಟಾಗಲ್ ಮಾಡಿ", - "add_to_albums": "ಆಲ್ಬಮ್‌ಗಳಿಗೆ ಸೇರಿಸಿ", - "add_to_albums_count": "({count}) ಆಲ್ಬಮ್‌ಗಳಿಗೆ ಸೇರಿಸಿ", - "add_to_bottom_bar": "ಗೆ ಸೇರಿಸಿ", - "add_to_shared_album": "ಹಂಚಿದ ಆಲ್ಬಮ್‌ಗೆ ಸೇರಿಸಿ", - "add_upload_to_stack": "ಸ್ಟ್ಯಾಕ್‌ಗೆ ಅಪ್‌ಲೋಡ್ ಸೇರಿಸಿ", - "add_url": "URL ಸೇರಿಸಿ", - "add_workflow_step": "ಕೆಲಸದ ಹರಿವಿನ ಹಂತವನ್ನು ಸೇರಿಸಿ", - "added_to_archive": "ಆರ್ಕೈವ್‌ಗೆ ಸೇರಿಸಲಾಗಿದೆ", - "added_to_favorites": "ಮೆಚ್ಚಿನವುಗಳಿಗೆ ಸೇರಿಸಲಾಗಿದೆ", - "added_to_favorites_count": "{count, number} ಮೆಚ್ಚಿನವುಗಳಿಗೆ ಸೇರಿಸಲಾಗಿದೆ", - "admin": { - "admin_user": "ನಿರ್ವಾಹಕ ಬಳಕೆದಾರ", - "authentication_settings": "ದೃಢೀಕರಣ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "authentication_settings_description": "ಪಾಸ್‌ವರ್ಡ್, ಒಔತ್ ಮತ್ತು ಇತರ ದೃಢೀಕರಣ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "authentication_settings_disable_all": "ನೀವು ಎಲ್ಲಾ ಲಾಗಿನ್ ವಿಧಾನಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ? ಲಾಗಿನ್ ಅನ್ನು ಸಂಪೂರ್ಣವಾಗಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತದೆ.", - "background_task_job": "ಹಿನ್ನೆಲೆ ಕಾರ್ಯಗಳು", - "backup_database": "ಡೇಟಾಬೇಸ್ ಡಂಪ್ ರಚಿಸಿ", - "backup_database_enable_description": "ಡೇಟಾಬೇಸ್ ಡಂಪ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "backup_keep_last_amount": "ಹಿಂದೆ ಇಡಬೇಕಾದ ಡಂಪ್ ಗಳ ಪ್ರಮಾಣ", - "backup_onboarding_1_description": "ಕ್ಲೌಡ್‌ನಲ್ಲಿ ಅಥವಾ ಇನ್ನೊಂದು ಭೌತಿಕ ಸ್ಥಳದಲ್ಲಿ ಆಫ್‌ಸೈಟ್ ನಕಲು.", - "backup_onboarding_2_description": "ವಿವಿಧ ಸಾಧನಗಳಲ್ಲಿ ಸ್ಥಳೀಯ ಪ್ರತಿಗಳು. ಇದು ಮುಖ್ಯ ಫೈಲ್‌ಗಳು ಮತ್ತು ಆ ಫೈಲ್‌ಗಳ ಸ್ಥಳೀಯ ಬ್ಯಾಕಪ್ ಅನ್ನು ಒಳಗೊಂಡಿರುತ್ತದೆ.", - "backup_onboarding_3_description": "ಮೂಲ ಫೈಲ್‌ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ಡೇಟಾದ ಒಟ್ಟು ಪ್ರತಿಗಳು. ಇದರಲ್ಲಿ 1 ಆಫ್‌ಸೈಟ್ ಪ್ರತಿ ಮತ್ತು 2 ಸ್ಥಳೀಯ ಪ್ರತಿಗಳು ಸೇರಿವೆ.", - "backup_onboarding_footer": "ಇಮ್ಮಿಚ್ ಅನ್ನು ಬ್ಯಾಕಪ್ ಮಾಡುವ ಬಗ್ಗೆ ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ, ದಯವಿಟ್ಟು ಡಾಕ್ಯುಮೆಂಟೇಶನ್ ಅನ್ನು ನೋಡಿ.", - "backup_onboarding_parts_title": "3-2-1 ಬ್ಯಾಕಪ್ ಇವುಗಳನ್ನು ಒಳಗೊಂಡಿದೆ:", - "backup_onboarding_title": "ಬ್ಯಾಕಪ್‌ಗಳು", - "backup_settings": "ಡೇಟಾಬೇಸ್ ಡಂಪ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "backup_settings_description": "ಡೇಟಾಬೇಸ್ ಡಂಪ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ.", - "cleared_jobs": "{job} ಗಾಗಿ ಉದ್ಯೋಗಗಳನ್ನು ತೆರವುಗೊಳಿಸಲಾಗಿದೆ", - "config_set_by_file": "ಪ್ರಸ್ತುತ ಕಾನ್ಫಿಗರೇಶನ್ ಫೈಲ್‌ನಿಂದ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ಹೊಂದಿಸಲಾಗಿದೆ", - "confirm_delete_library": "ನೀವು {library} ಲೈಬ್ರರಿಯನ್ನು ಅಳಿಸಲು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?", - "confirm_email_below": "ದೃಢೀಕರಿಸಲು, ಕೆಳಗೆ \"{email}\" ಎಂದು ಟೈಪ್ ಮಾಡಿ", - "confirm_reprocess_all_faces": "ನೀವು ಎಲ್ಲಾ ಮುಖಗಳನ್ನು ಮರುಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ? ಇದು ಹೆಸರಿಸಲಾದ ಜನರನ್ನು ಸಹ ತೆರವುಗೊಳಿಸುತ್ತದೆ.", - "confirm_user_password_reset": "ನೀವು {user} ಅವರ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮರುಹೊಂದಿಸಲು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?", - "confirm_user_pin_code_reset": "ನೀವು {user} ಅವರ ಪಿನ್ ಕೋಡ್ ಅನ್ನು ಮರುಹೊಂದಿಸಲು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?", - "copy_config_to_clipboard_description": "ಪ್ರಸ್ತುತ ಸಿಸ್ಟಮ್ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು JSON ಆಬ್ಜೆಕ್ಟ್ ಆಗಿ ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಿ", - "create_job": "ಉದ್ಯೋಗ ರಚಿಸಿ", - "cron_expression_presets": "ಕ್ರಾನ್ ಅಭಿವ್ಯಕ್ತಿ ಪೂರ್ವನಿಗದಿಗಳು", - "disable_login": "ಲಾಗಿನ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ", - "export_config_as_json_description": "ಪ್ರಸ್ತುತ ಸಿಸ್ಟಮ್ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು JSON ಫೈಲ್ ಆಗಿ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ", - "external_libraries_page_description": "ನಿರ್ವಾಹಕ ಬಾಹ್ಯ ಗ್ರಂಥಾಲಯ ಪುಟ", - "face_detection": "ಮುಖ ಪತ್ತೆ", - "failed_job_command": "{job} ಎಂಬ ಕೆಲಸಕ್ಕೆ {command} ಆಜ್ಞೆ ವಿಫಲವಾಗಿದೆ", - "force_delete_user_warning": "ಎಚ್ಚರಿಕೆ: ಇದು ಬಳಕೆದಾರರನ್ನು ಮತ್ತು ಎಲ್ಲಾ ಸ್ವತ್ತುಗಳನ್ನು ತಕ್ಷಣವೇ ತೆಗೆದುಹಾಕುತ್ತದೆ. ಇದನ್ನು ರದ್ದುಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ ಮತ್ತು ಫೈಲ್‌ಗಳನ್ನು ಮರುಪಡೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ.", - "image_format": "ಸ್ವರೂಪ", - "image_format_description": "WebP, JPEG ಗಿಂತ ಚಿಕ್ಕ ಫೈಲ್‌ಗಳನ್ನು ಉತ್ಪಾದಿಸುತ್ತದೆ, ಆದರೆ ಎನ್‌ಕೋಡ್ ಮಾಡಲು ನಿಧಾನವಾಗಿರುತ್ತದೆ.", - "image_fullsize_description": "ಝೂಮ್ ಇನ್ ಮಾಡಿದಾಗ ಬಳಸಲಾದ, ಸ್ಟ್ರಿಪ್ಡ್ ಮೆಟಾಡೇಟಾ ಹೊಂದಿರುವ ಪೂರ್ಣ-ಗಾತ್ರದ ಚಿತ್ರ", - "image_fullsize_enabled": "ಪೂರ್ಣ-ಗಾತ್ರದ ಚಿತ್ರ ರಚನೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "image_fullsize_quality_description": "1-100 ರವರೆಗಿನ ಪೂರ್ಣ-ಗಾತ್ರದ ಚಿತ್ರದ ಗುಣಮಟ್ಟ. ಹೆಚ್ಚಿನದು ಉತ್ತಮ, ಆದರೆ ದೊಡ್ಡ ಫೈಲ್‌ಗಳನ್ನು ಉತ್ಪಾದಿಸುತ್ತದೆ.", - "image_fullsize_title": "ಪೂರ್ಣ-ಗಾತ್ರದ ಚಿತ್ರ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "image_prefer_embedded_preview": "ಎಂಬೆಡ್ ಮಾಡಿದ ಪೂರ್ವವೀಕ್ಷಣೆಗೆ ಆದ್ಯತೆ ನೀಡಿ", - "image_prefer_wide_gamut": "ವಿಶಾಲ ವ್ಯಾಪ್ತಿಗೆ ಆದ್ಯತೆ ನೀಡಿ", - "image_preview_quality_description": "1-100 ವರೆಗಿನ ಪೂರ್ವವೀಕ್ಷಣೆ ಗುಣಮಟ್ಟ. ಹೆಚ್ಚಿನದು ಉತ್ತಮ, ಆದರೆ ದೊಡ್ಡ ಫೈಲ್‌ಗಳನ್ನು ಉತ್ಪಾದಿಸುತ್ತದೆ ಮತ್ತು ಅಪ್ಲಿಕೇಶನ್ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಕಡಿಮೆ ಮಾಡುತ್ತದೆ. ಕಡಿಮೆ ಮೌಲ್ಯವನ್ನು ಹೊಂದಿಸುವುದು ಯಂತ್ರ ಕಲಿಕೆಯ ಗುಣಮಟ್ಟದ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರಬಹುದು.", - "image_preview_title": "ಪೂರ್ವವೀಕ್ಷಣೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "image_quality": "ಗುಣಮಟ್ಟ", - "image_resolution": "ರೆಸಲ್ಯೂಶನ್", - "image_settings": "ಚಿತ್ರ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "image_settings_description": "ರಚಿಸಲಾದ ಚಿತ್ರಗಳ ಗುಣಮಟ್ಟ ಮತ್ತು ರೆಸಲ್ಯೂಶನ್ ಅನ್ನು ನಿರ್ವಹಿಸಿ", - "image_thumbnail_title": "ಥಂಬ್‌ನೇಲ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "import_config_from_json_description": "JSON ಕಾನ್ಫಿಗರೇಶನ್ ಫೈಲ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡುವ ಮೂಲಕ ಸಿಸ್ಟಮ್ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ಆಮದು ಮಾಡಿ", - "job_concurrency": "{job} ಸಹವರ್ತಿತ್ವ", - "job_created": "ಕೆಲಸವನ್ನು ರಚಿಸಲಾಗಿದೆ", - "job_not_concurrency_safe": "ಈ ಕೆಲಸವು ಸಹವರ್ತಿತ್ವಕ್ಕೆ ಸುರಕ್ಷಿತವಲ್ಲ.", - "job_settings": "ಕೆಲಸದ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "job_settings_description": "ಕೆಲಸದ ಸಮಕಾಲೀನತೆಯನ್ನು ನಿರ್ವಹಿಸಿ", - "jobs_over_time": "ಕಾಲಾನಂತರದ ಉದ್ಯೋಗಗಳು", - "library_deleted": "ಲೈಬ್ರರಿಯನ್ನು ಅಳಿಸಲಾಗಿದೆ", - "library_details": "ಲೈಬ್ರರಿಯ ವಿವರಗಳು", - "library_folder_description": "ಆಮದು ಮಾಡಿಕೊಳ್ಳಲು ಒಂದು ಫೋಲ್ಡರ್ ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸಿ. ಉಪ ಫೋಲ್ಡರ್‌ಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ಈ ಫೋಲ್ಡರ್ ಅನ್ನು ಚಿತ್ರಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳಿಗಾಗಿ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತದೆ.", - "library_remove_exclusion_pattern_prompt": "ಈ ಹೊರಗಿಡುವ ಮಾದರಿಯನ್ನು ತೆಗೆದುಹಾಕಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?", - "library_remove_folder_prompt": "ಈ ಆಮದು ಫೋಲ್ಡರ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?", - "library_scanning": "ಆವರ್ತಕ ಸ್ಕ್ಯಾನಿಂಗ್", - "library_scanning_description": "ಆವರ್ತಕ ಗ್ರಂಥಾಲಯ ಸ್ಕ್ಯಾನಿಂಗ್ ಅನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಿ", - "library_scanning_enable_description": "ಆವರ್ತಕ ಗ್ರಂಥಾಲಯ ಸ್ಕ್ಯಾನಿಂಗ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "library_settings": "ಬಾಹ್ಯ ಗ್ರಂಥಾಲಯ", - "library_settings_description": "ಬಾಹ್ಯ ಗ್ರಂಥಾಲಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "library_tasks_description": "ಹೊಸ ಮತ್ತು/ಅಥವಾ ಬದಲಾದ ಸ್ವತ್ತುಗಳಿಗಾಗಿ ಬಾಹ್ಯ ಗ್ರಂಥಾಲಯಗಳನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಿ", - "library_updated": "ನವೀಕರಿಸಿದ ಗ್ರಂಥಾಲಯ", - "library_watching_enable_description": "ಫೈಲ್ ಬದಲಾವಣೆಗಳಿಗಾಗಿ ಬಾಹ್ಯ ಗ್ರಂಥಾಲಯಗಳನ್ನು ವೀಕ್ಷಿಸಿ", - "library_watching_settings": "ಗ್ರಂಥಾಲಯ ವೀಕ್ಷಣೆ [ಪ್ರಾಯೋಗಿಕ]", - "library_watching_settings_description": "ಬದಲಾದ ಫೈಲ್‌ಗಳಿಗಾಗಿ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ವೀಕ್ಷಿಸಿ", - "logging_enable_description": "ಲಾಗಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸಿ", - "logging_level_description": "ಸಕ್ರಿಯಗೊಳಿಸಿದಾಗ, ಯಾವ ಲಾಗ್ ಮಟ್ಟವನ್ನು ಬಳಸಬೇಕು.", - "logging_settings": "ಲಾಗಿಂಗ್", - "machine_learning_availability_checks": "ಲಭ್ಯತೆ ಪರಿಶೀಲನೆಗಳು", - "machine_learning_availability_checks_description": "ಲಭ್ಯವಿರುವ ಯಂತ್ರ ಕಲಿಕೆ ಸರ್ವರ್‌ಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಪತ್ತೆಹಚ್ಚಿ ಮತ್ತು ಆದ್ಯತೆ ನೀಡಿ", - "machine_learning_availability_checks_enabled": "ಲಭ್ಯತೆ ಪರಿಶೀಲನೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "machine_learning_availability_checks_interval": "ಮಧ್ಯಂತರವನ್ನು ಪರಿಶೀಲಿಸಿ", - "machine_learning_availability_checks_timeout": "ವಿನಂತಿ ಅವಧಿ ಮೀರಿದೆ", - "machine_learning_clip_model": "CLIP ಮಾದರಿ", - "machine_learning_duplicate_detection": "ನಕಲು ಪತ್ತೆ", - "machine_learning_duplicate_detection_enabled": "ನಕಲು ಪತ್ತೆಹಚ್ಚುವಿಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "machine_learning_duplicate_detection_setting_description": "ಸಂಭಾವ್ಯ ನಕಲುಗಳನ್ನು ಕಂಡುಹಿಡಿಯಲು CLIP ಎಂಬೆಡಿಂಗ್‌ಗಳನ್ನು ಬಳಸಿ", - "machine_learning_enabled": "ಯಂತ್ರ ಕಲಿಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "machine_learning_enabled_description": "ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದರೆ, ಕೆಳಗಿನ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಲೆಕ್ಕಿಸದೆ ಎಲ್ಲಾ ML ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತದೆ.", - "machine_learning_facial_recognition": "ಮುಖ ಗುರುತಿಸುವಿಕೆ", - "machine_learning_facial_recognition_description": "ಚಿತ್ರಗಳಲ್ಲಿ ಮುಖಗಳನ್ನು ಪತ್ತೆ ಮಾಡಿ, ಗುರುತಿಸಿ ಮತ್ತು ಗುಂಪು ಮಾಡಿ", - "machine_learning_facial_recognition_model": "ಮುಖ ಗುರುತಿಸುವಿಕೆ ಮಾದರಿ", - "machine_learning_facial_recognition_setting": "ಮುಖ ಗುರುತಿಸುವಿಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "machine_learning_ocr": "ಓಸಿಆರ್", - "machine_learning_ocr_enabled": "OCR ಸಕ್ರಿಯಗೊಳಿಸಿ", - "machine_learning_ocr_enabled_description": "ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದರೆ, ಚಿತ್ರಗಳು ಪಠ್ಯ ಗುರುತಿಸುವಿಕೆಗೆ ಒಳಗಾಗುವುದಿಲ್ಲ.", - "machine_learning_ocr_max_resolution": "ಗರಿಷ್ಠ ರೆಸಲ್ಯೂಷನ್", - "machine_learning_ocr_max_resolution_description": "ಈ ರೆಸಲ್ಯೂಷನ್ ಮೇಲಿನ ಪೂರ್ವವೀಕ್ಷಣೆಗಳನ್ನು ಆಕಾರ ಅನುಪಾತವನ್ನು ಸಂರಕ್ಷಿಸುವಾಗ ಮರುಗಾತ್ರಗೊಳಿಸಲಾಗುತ್ತದೆ. ಹೆಚ್ಚಿನ ಮೌಲ್ಯಗಳು ಹೆಚ್ಚು ನಿಖರವಾಗಿರುತ್ತವೆ, ಆದರೆ ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಮತ್ತು ಹೆಚ್ಚಿನ ಮೆಮೊರಿಯನ್ನು ಬಳಸಲು ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳುತ್ತದೆ.", - "machine_learning_ocr_min_recognition_score": "ಕನಿಷ್ಠ ಡಿಟೆಕ್ಷನ್ ಅಂಕ", - "machine_learning_ocr_model": "OCR ಮಾಡೆಲ್", - "machine_learning_settings": "ಯಂತ್ರ ಕಲಿಕೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "machine_learning_settings_description": "ಯಂತ್ರ ಕಲಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳು ಮತ್ತು ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "machine_learning_smart_search": "ಸ್ಮಾರ್ಟ್ ಹುಡುಕಾಟ", - "machine_learning_smart_search_description": "CLIP ಎಂಬೆಡಿಂಗ್‌ಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಚಿತ್ರಗಳನ್ನು ಅರ್ಥಪೂರ್ಣವಾಗಿ ಹುಡುಕಿ", - "machine_learning_smart_search_enabled": "ಸ್ಮಾರ್ಟ್ ಹುಡುಕಾಟವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "machine_learning_smart_search_enabled_description": "ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿದರೆ, ಚಿತ್ರಗಳನ್ನು ಸ್ಮಾರ್ಟ್ ಹುಡುಕಾಟಕ್ಕಾಗಿ ಎನ್‌ಕೋಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ.", - "maintenance_settings": "ನಿರ್ವಹಣೆ", - "maintenance_settings_description": "ಇಮ್ಮಿಚ್ ಅನ್ನು ನಿರ್ವಹಣಾ ಕ್ರಮಕ್ಕೆ ಇರಿಸಿ.", - "maintenance_start": "ನಿರ್ವಹಣಾ ಮೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ", - "maintenance_start_error": "ನಿರ್ವಹಣಾ ಕ್ರಮವನ್ನು ಪ್ರಾರಂಭಿಸಲು ವಿಫಲವಾಗಿದೆ.", - "manage_log_settings": "ಲಾಗ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "map_enable_description": "ನಕ್ಷೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "map_gps_settings": "ನಕ್ಷೆ ಮತ್ತು GPS ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "map_reverse_geocoding": "ರಿವರ್ಸ್ ಜಿಯೋಕೋಡಿಂಗ್", - "map_reverse_geocoding_enable_description": "ರಿವರ್ಸ್ ಜಿಯೋಕೋಡಿಂಗ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "map_reverse_geocoding_settings": "ರಿವರ್ಸ್ ಜಿಯೋಕೋಡಿಂಗ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "map_settings": "ನಕ್ಷೆ", - "map_settings_description": "ನಕ್ಷೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "memory_generate_job": "ಸ್ಮೃತಿ ಉತ್ಪಾದನೆ", - "metadata_extraction_job": "ಮೆಟಾಡೇಟಾವನ್ನು ಹೊರತೆಗೆಯಿರಿ", - "metadata_extraction_job_description": "GPS, ಮುಖಗಳು ಮತ್ತು ರೆಸಲ್ಯೂಶನ್‌ನಂತಹ ಪ್ರತಿ ಸ್ವತ್ತಿನಿಂದ ಮೆಟಾಡೇಟಾ ಮಾಹಿತಿಯನ್ನು ಹೊರತೆಗೆಯಿರಿ", - "metadata_faces_import_setting": "ಮುಖ ಆಮದು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "metadata_settings": "ಮೆಟಾಡೇಟಾ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "metadata_settings_description": "ಮೆಟಾಡೇಟಾ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "migration_job": "ವಲಸೆ", - "migration_job_description": "ಸ್ವತ್ತುಗಳು ಮತ್ತು ಮುಖಗಳಿಗಾಗಿ ಥಂಬ್‌ನೇಲ್‌ಗಳನ್ನು ಇತ್ತೀಚಿನ ಫೋಲ್ಡರ್ ರಚನೆಗೆ ಸ್ಥಳಾಂತರಿಸಿ", - "nightly_tasks_cluster_faces_setting_description": "ಹೊಸದಾಗಿ ಪತ್ತೆಯಾದ ಮುಖಗಳಲ್ಲಿ ಮುಖ ಗುರುತಿಸುವಿಕೆಯನ್ನು ರನ್ ಮಾಡಿ", - "nightly_tasks_cluster_new_faces_setting": "ಹೊಸ ಮುಖಗಳನ್ನು ಸಮೂಹ ಮಾಡಿ", - "nightly_tasks_database_cleanup_setting": "ಡೇಟಾಬೇಸ್ ಸ್ವಚ್ಛಗೊಳಿಸುವ ಕಾರ್ಯಗಳು", - "nightly_tasks_database_cleanup_setting_description": "ಡೇಟಾಬೇಸ್‌ನಿಂದ ಹಳೆಯ, ಅವಧಿ ಮೀರಿದ ಡೇಟಾವನ್ನು ಸ್ವಚ್ಛಗೊಳಿಸಿ", - "nightly_tasks_generate_memories_setting": "ನೆನಪುಗಳನ್ನು ರಚಿಸಿ", - "nightly_tasks_generate_memories_setting_description": "ಸ್ವತ್ತುಗಳಿಂದ ಹೊಸ ನೆನಪುಗಳನ್ನು ರಚಿಸಿ", - "nightly_tasks_missing_thumbnails_setting": "ಕಾಣೆಯಾದ ಥಂಬ್‌ನೇಲ್‌ಗಳನ್ನು ರಚಿಸಿ", - "nightly_tasks_settings": "ರಾತ್ರಿಯ ಕಾರ್ಯಗಳ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "nightly_tasks_settings_description": "ರಾತ್ರಿಯ ಕಾರ್ಯಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "nightly_tasks_start_time_setting": "ಪ್ರಾರಂಭ ಸಮಯ", - "nightly_tasks_start_time_setting_description": "ಸರ್ವರ್ ರಾತ್ರಿಯ ಕಾರ್ಯಗಳನ್ನು ನಡೆಸಲು ಪ್ರಾರಂಭಿಸುವ ಸಮಯ", - "nightly_tasks_sync_quota_usage_setting": "ಸಿಂಕ್ ಕೋಟಾ ಬಳಕೆ", - "nightly_tasks_sync_quota_usage_setting_description": "ಪ್ರಸ್ತುತ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಬಳಕೆದಾರರ ಸಂಗ್ರಹಣಾ ಕೋಟಾವನ್ನು ನವೀಕರಿಸಿ", - "no_pattern_added": "ಯಾವುದೇ ಪ್ಯಾಟರ್ನ್ ಸೇರಿಸಲಾಗಿಲ್ಲ", - "note_cannot_be_changed_later": "ಗಮನಿಸಿ: ಇದನ್ನು ನಂತರ ಬದಲಾಯಿಸಲಾಗುವುದಿಲ್ಲ!", - "notification_email_from_address": "ವಿಳಾಸದಿಂದ", - "notification_email_ignore_certificate_errors": "ಪ್ರಮಾಣಪತ್ರ ದೋಷಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ", - "notification_email_ignore_certificate_errors_description": "TLS ಪ್ರಮಾಣಪತ್ರ ಮೌಲ್ಯೀಕರಣ ದೋಷಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ (ಶಿಫಾರಸು ಮಾಡಲಾಗಿಲ್ಲ)", - "notification_email_password_description": "ಇಮೇಲ್ ಸರ್ವರ್‌ನೊಂದಿಗೆ ದೃಢೀಕರಿಸುವಾಗ ಬಳಸಬೇಕಾದ ಪಾಸ್‌ವರ್ಡ್", - "notification_email_port_description": "ಇಮೇಲ್ ಸರ್ವರ್‌ನ ಪೋರ್ಟ್ (ಉದಾ. 25, 465, ಅಥವಾ 587)", - "notification_email_secure": "ಎಸ್‌ಎಂಟಿಪಿಎಸ್", - "notification_email_sent_test_email_button": "ಪರೀಕ್ಷಾ ಇಮೇಲ್ ಕಳುಹಿಸಿ ಮತ್ತು ಉಳಿಸಿ", - "notification_email_setting_description": "ಇಮೇಲ್ ಅಧಿಸೂಚನೆಗಳನ್ನು ಕಳುಹಿಸಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "notification_email_test_email": "ಪರೀಕ್ಷಾ ಇಮೇಲ್ ಕಳುಹಿಸಿ", - "notification_email_test_email_failed": "ಪರೀಕ್ಷಾ ಇಮೇಲ್ ಕಳುಹಿಸಲು ವಿಫಲವಾಗಿದೆ, ನಿಮ್ಮ ಮೌಲ್ಯಗಳನ್ನು ಪರಿಶೀಲಿಸಿ", - "notification_enable_email_notifications": "ಇಮೇಲ್ ಅಧಿಸೂಚನೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", - "notification_settings": "ಅಧಿಸೂಚನೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", - "notification_settings_description": "ಇಮೇಲ್ ಸೇರಿದಂತೆ ಅಧಿಸೂಚನೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "oauth_auto_launch": "ಸ್ವಯಂ ಉಡಾವಣೆ", - "oauth_storage_quota_claim": "ಸಂಗ್ರಹಣೆ ಕೋಟಾ ಹಕ್ಕು", - "password_settings": "ಪಾಸ್‌ವರ್ಡ್ ಲಾಗಿನ್", - "password_settings_description": "ಪಾಸ್‌ವರ್ಡ್ ಲಾಗಿನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", - "paths_validated_successfully": "ಎಲ್ಲಾ ಮಾರ್ಗಗಳನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಮೌಲ್ಯೀಕರಿಸಲಾಗಿದೆ", - "person_cleanup_job": "ವ್ಯಕ್ತಿ ಶುಚಿಗೊಳಿಸುವಿಕೆ", - "queue_details": "ಸರದಿ ವಿವರಗಳು", - "queues": "ಕೆಲಸದ ಸರತಿ ಸಾಲುಗಳು", - "queues_page_description": "ನಿರ್ವಾಹಕ ಕೆಲಸದ ಸರತಿ ಪುಟ", - "template_email_preview": "ಪೂರ್ವವೀಕ್ಷಣೆ", - "transcoding_tone_mapping": "ಟೋನ್-ಮ್ಯಾಪಿಂಗ್" - }, - "administration": "ಆಡಳಿತ", - "advanced": "ಸುಧಾರಿತ", - "albums": "ಆಲ್ಬಂಗಳು", - "all": "ಎಲ್ಲವೂ", - "anti_clockwise": "ಅಪ್ರದಕ್ಷಿಣಾಕಾರವಾಗಿ", - "archive": "ಆರ್ಕೈವ್", - "asset_uploaded": "ಅಪ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ", - "asset_uploading": "ಅಪ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ…", - "assets": "ಸ್ವತ್ತುಗಳು", - "back": "ಹಿಂದೆ", - "backward": "ಹಿಂದಕ್ಕೆ", - "build": "ನಿರ್ಮಾಣ", - "camera": "ಕ್ಯಾಮೆರಾ", - "cancel": "ರದ್ದುಮಾಡಿ", - "city": "ನಗರ", - "close": "ಮುಚ್ಚಿ", - "collapse": "ಕುಗ್ಗಿಸು", - "color": "ಬಣ್ಣ", - "confirm": "ದೃಢೀಕರಿಸಿ", - "context": "ಸಂದರ್ಭ", - "continue": "ಮುಂದುವರಿಸಿ", - "country": "ದೇಶ", - "cover": "ಕವರ್", - "covers": "ಕವರ್‌ಗಳು", - "create": "ರಚಿಸಿ", - "dark": "ಕತ್ತಲು", - "day": "ದಿನ", - "delete": "ಅಳಿಸಿ", - "description": "ವಿವರಣೆ", - "details": "ವಿವರಗಳು", - "direction": "ನಿರ್ದೇಶನ", - "documentation": "ದಸ್ತಾವೇಜೀಕರಣ", - "done": "ಮುಗಿದಿದೆ", - "download": "ಡೌನ್‌ಲೋಡ್", - "download_settings": "ಡೌನ್‌ಲೋಡ್", - "duration": "ಅವಧಿ", - "email": "ಇಮೇಲ್", - "enable": "ಸಕ್ರಿಯಗೊಳಿಸಿ", - "enabled": "ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ", - "error": "ದೋಷ", - "exif": "ಎಕ್ಸಿಫ್", - "face_unassigned": "ನಿಯೋಜಿಸಲಾಗಿಲ್ಲ", - "favorites": "ಮೆಚ್ಚಿನವುಗಳು", - "filename": "ಫೈಲ್ ಹೆಸರು", - "filetype": "ಫೈಲ್ ಪ್ರಕಾರ", - "folders": "ಫೋಲ್ಡರ್‌ಗಳು", - "forward": "ಮುಂದೆ", - "general": "ಜನರಲ್", - "host": "ಹೋಸ್ಟ್", - "hour": "ಗಂಟೆ", - "image": "ಚಿತ್ರ", - "info": "ಮಾಹಿತಿ", - "jobs": "ಉದ್ಯೋಗಗಳು", - "keep": "ಇರಿಸಿಕೊಳ್ಳಿ", - "language": "ಭಾಷೆ", - "leave": "ಬಿಡಿ", - "level": "ಮಟ್ಟ", - "light": "ಬೆಳಕು", - "list": "ಪಟ್ಟಿ", - "login": "ಲಾಗಿನ್", - "make": "ಮಾಡಿ", - "map": "ನಕ್ಷೆ", - "memories": "ನೆನಪುಗಳು", - "memory": "ನೆನಪು" -} +{} diff --git a/i18n/ko.json b/i18n/ko.json index 147ace091d..0967ef424b 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -1,2367 +1 @@ -{ - "about": "정보", - "account": "계정", - "account_settings": "계정 설정", - "acknowledge": "확인", - "action": "작업", - "action_common_update": "업데이트", - "action_description": "필터링된 자산에 대해 수행할 일련의 작업", - "actions": "작업", - "active": "활성", - "active_count": "활성: {count}", - "activity": "활동", - "activity_changed": "활동이 {enabled, select, true {활성화} other {비활성화}}되었습니다.", - "add": "추가", - "add_a_description": "설명 추가", - "add_a_location": "위치 추가", - "add_a_name": "이름 추가", - "add_a_title": "제목 추가", - "add_action": "작업 추가", - "add_action_description": "클릭하여 수행할 작업을 추가하세요", - "add_assets": "항목 추가", - "add_birthday": "생일 추가", - "add_endpoint": "엔드포인트 추가", - "add_exclusion_pattern": "제외 규칙 추가", - "add_filter": "필터 추가", - "add_filter_description": "필터 조건을 추가하려면 클릭하세요", - "add_location": "위치 추가", - "add_more_users": "다른 사용자 추가", - "add_partner": "파트너 추가", - "add_path": "경로 추가", - "add_photos": "사진 추가", - "add_tag": "태그 추가", - "add_to": "이곳에 추가…", - "add_to_album": "앨범에 추가", - "add_to_album_bottom_sheet_added": "{album}에 추가됨", - "add_to_album_bottom_sheet_already_exists": "이미 {album}에 있음", - "add_to_album_bottom_sheet_some_local_assets": "일부 로컬 항목이 앨범에 추가되지 않았습니다.", - "add_to_album_toggle": "{album} 선택/해제", - "add_to_albums": "여러 앨범에 추가", - "add_to_albums_count": "여러 앨범에 추가 ({count})", - "add_to_bottom_bar": "다음에 추가", - "add_to_shared_album": "공유 앨범에 추가", - "add_upload_to_stack": "스택에 항목 업로드", - "add_url": "URL 추가", - "add_workflow_step": "워크플로 단계 추가", - "added_to_archive": "보관함으로 이동되었습니다.", - "added_to_favorites": "즐겨찾기에 추가되었습니다.", - "added_to_favorites_count": "즐겨찾기에 항목 {count, number}개 추가됨", - "admin": { - "add_exclusion_pattern_description": "*, **, ? 등의 glob 패턴을 사용할 수 있습니다. 예를 들어 \"Raw\" 폴더 내 모든 파일을 제외하려면 \"**/Raw/**\"를, .tif 파일을 제외하려면 \"**/*.tif\", 특정한 절대 경로를 제외하려면 \"/path/to/ignore/**\" 처럼 사용합니다.", - "admin_user": "관리자", - "asset_offline_description": "이 항목은 외부 라이브러리에 등록되었으나 디스크에서 찾을 수 없어 휴지통으로 이동했습니다. 파일이 라이브러리 경로 내에서 이동된 경우 타임라인에서 새로 인식된 항목이 있는지 확인해보세요. 이 항목을 복원하려면 아래 경로에 Immich가 접근할 수 있는지 확인하고 라이브러리를 다시 스캔하세요.", - "authentication_settings": "인증 설정", - "authentication_settings_description": "비밀번호, OAuth 및 기타 인증 설정을 관리합니다.", - "authentication_settings_disable_all": "모든 로그인 수단을 비활성화하시겠습니까? 더이상 로그인할 수 없습니다.", - "authentication_settings_reenable": "다시 활성화하려면 서버 명령어를 사용하세요.", - "background_task_job": "백그라운드 작업", - "backup_database": "데이터베이스 덤프 생성", - "backup_database_enable_description": "데이터베이스 덤프 활성화", - "backup_keep_last_amount": "보관할 이전 덤프 수", - "backup_onboarding_1_description": "개는 클라우드나 다른 물리적 위치에 보관합니다.", - "backup_onboarding_2_description": "개는 서로 다른 로컬 장치에 보관하고,", - "backup_onboarding_3_description": "개의 데이터 사본을 만듭니다.", - "backup_onboarding_description": "소중한 데이터를 안전하게 보호하기 위해 3-2-1 백업 전략 사용을 권장합니다. Immich를 백업할 때 업로드한 사진 및 동영상뿐 아니라 데이터베이스도 함께 보관해야 한다는 점을 잊지 마세요.", - "backup_onboarding_footer": "Immich 백업에 대한 자세한 내용은 공식 문서를 참조하세요.", - "backup_onboarding_parts_title": "3-2-1 백업이란:", - "backup_onboarding_title": "백업", - "backup_settings": "데이터베이스 덤프 설정", - "backup_settings_description": "데이터베이스 덤프 주기와 보관 기간을 설정합니다.", - "cleared_jobs": "작업 중단: {job}", - "config_set_by_file": "설정이 구성 파일을 통해 관리되고 있습니다.", - "confirm_delete_library": "{library} 라이브러리를 삭제하시겠습니까?", - "confirm_delete_library_assets": "이 라이브러리를 삭제하시겠습니까? Immich에서 {count, plural, one {항목 #개가} other {항목 #개가}} 삭제되며 되돌릴 수 없습니다. 원본 파일은 디스크에 남아 있습니다.", - "confirm_email_below": "계속 진행하려면 아래에 \"{email}\" 입력", - "confirm_reprocess_all_faces": "모든 얼굴을 다시 처리하시겠습니까? 이름이 지정된 인물도 초기화됩니다.", - "confirm_user_password_reset": "{user}님의 비밀번호를 초기화하시겠습니까?", - "confirm_user_pin_code_reset": "{user}님의 PIN 코드를 초기화하시겠습니까?", - "copy_config_to_clipboard_description": "현재 시스템 구성을 JSON 형태로 클립보드에 복사합니다.", - "create_job": "새 작업", - "cron_expression": "Cron 표현식", - "cron_expression_description": "Cron 표현식으로 스캔 주기를 설정합니다. 자세한 내용은 다음 링크를 확인하세요. Crontab Guru", - "cron_expression_presets": "Cron 표현식 프리셋", - "disable_login": "로그인 비활성화", - "duplicate_detection_job_description": "기계 학습으로 유사한 이미지를 감지합니다. 스마트 검색이 활성화되어 있어야 합니다.", - "exclusion_pattern_description": "라이브러리 스캔에서 제외할 파일이나 폴더 규칙을 설정합니다. 폴더에 원하지 않는 파일(RAW 파일 등)이 함께 존재하는 경우 유용합니다.", - "export_config_as_json_description": "현재 시스템 구성을 JSON 파일로 다운로드합니다.", - "external_libraries_page_description": "외부 라이브러리 페이지 관리", - "face_detection": "얼굴 감지", - "face_detection_description": "기계 학습으로 항목에서 얼굴을 감지합니다. 동영상의 경우 섬네일만 분석에 사용됩니다. \"새로고침\"은 모든 항목을 (재)처리하며, \"초기화\"는 현재 모든 얼굴 데이터를 추가로 삭제합니다. \"누락\"은 아직 처리되지 않은 항목을 대기열에 추가합니다. 얼굴 감지가 완료되면 얼굴 인식 단계로 넘어가 기존 인물이나 새로운 인물로 그룹화합니다.", - "facial_recognition_job_description": "감지된 얼굴을 인물별로 그룹화합니다. 이 작업은 얼굴 감지 작업이 완료된 후 진행됩니다. \"초기화\"는 모든 얼굴을 다시 그룹화합니다. \"누락\"은 그룹화되지 않은 얼굴을 대기열에 추가합니다.", - "failed_job_command": "{job} 작업의 {command} 실패", - "force_delete_user_warning": "경고: 이 작업은 해당 사용자의 계정과 모든 항목을 즉시 삭제합니다. 이 작업은 되돌릴 수 없으며 삭제된 파일은 복구할 수 없습니다.", - "image_format": "형식", - "image_format_description": "WebP는 JPEG보다 파일 크기가 작지만 인코딩 속도가 느립니다.", - "image_fullsize_description": "메타데이터가 제거된 전체 크기 이미지. 이미지 확대 시 사용됩니다.", - "image_fullsize_enabled": "전체 크기 이미지 생성 활성화", - "image_fullsize_enabled_description": "웹 친화적이지 않은 형식에 대해 전체 크기 이미지를 생성합니다. \"파일에 포함된 미리보기 우선\"이 활성화된 경우, 변환 없이 해당 미리보기를 그대로 사용합니다. JPEG과 같은 웹 친화적인 형식에는 영향을 주지 않습니다.", - "image_fullsize_quality_description": "전체 크기 이미지의 품질을 1에서 100 사이로 설정합니다. 값을 높이면 품질이 좋아지지만 파일 크기가 커집니다.", - "image_fullsize_title": "전체 크기 이미지 설정", - "image_prefer_embedded_preview": "파일에 포함된 미리보기 우선 사용", - "image_prefer_embedded_preview_setting_description": "RAW 사진에 포함된 내장 미리보기를 가능한 경우 이미지 처리에 사용합니다. 이 방식은 일부 이미지에서 더 정확한 색상을 얻을 수 있지만, 미리보기의 품질은 카메라에 따라 다르며 압축으로 인한 품질 저하가 나타날 수 있습니다.", - "image_prefer_wide_gamut": "광색역 우선 사용", - "image_prefer_wide_gamut_setting_description": "섬네일에 Display P3 색역을 사용합니다. 광색역 이미지를 더 생동감 있게 표현할 수 있지만, 구형 브라우저나 장치에서는 다르게 보일 수 있습니다. sRGB 이미지의 경우 색상 왜곡을 방지하기 위해 그대로 유지됩니다.", - "image_preview_description": "메타데이터가 제거된 중간 크기 이미지. 기계 학습 또는 개별 항목을 표시할 때 사용됩니다.", - "image_preview_quality_description": "미리보기의 품질을 1에서 100 사이로 설정합니다. 값을 높이면 품질이 좋아지지만 파일 크기가 커지고 앱 반응 속도가 느려질 수 있습니다. 너무 낮은 값은 기계 학습에 영향을 줄 수 있습니다.", - "image_preview_title": "미리보기 설정", - "image_progressive": "점진적 로딩", - "image_progressive_description": "JPEG 이미지를 점진적으로 표시할 수 있게 단계적으로 인코딩합니다. WebP 이미지에는 영향이 없습니다.", - "image_quality": "품질", - "image_resolution": "해상도", - "image_resolution_description": "해상도가 높으면 세부 정보가 보존되지만, 인코딩에 더 많은 시간이 소요되고 파일 크기가 커져 앱 반응 속도가 느려질 수 있습니다.", - "image_settings": "이미지 설정", - "image_settings_description": "이미지의 품질 및 처리 방식을 관리합니다.", - "image_thumbnail_description": "메타데이터가 제거된 작은 섬네일. 메인 타임라인 등에서 여러 사진을 표시할 때 사용됩니다.", - "image_thumbnail_quality_description": "섬네일 품질을 1에서 100 사이로 설정합니다. 값을 높이면 품질이 좋아지지만 파일 크기가 커지고 앱 반응 속도가 느려질 수 있습니다.", - "image_thumbnail_title": "섬네일 설정", - "import_config_from_json_description": "JSON 파일을 업로드하여 시스템 구성을 가져옵니다.", - "job_concurrency": "{job} 동시성", - "job_created": "작업이 생성되었습니다.", - "job_not_concurrency_safe": "이 작업은 동시 실행에 안전하지 않습니다.", - "job_settings": "작업 설정", - "job_settings_description": "각 작업에서 동시에 처리할 항목 수를 지정합니다.", - "jobs_delayed": "{jobCount, plural, other {#개}} 지연", - "jobs_failed": "{jobCount, plural, other {#개}} 실패", - "jobs_over_time": "작업 만료 시간", - "library_created": "{library} 라이브러리를 생성했습니다.", - "library_deleted": "라이브러리가 삭제되었습니다.", - "library_details": "라이브러리 상세", - "library_folder_description": "가져올 폴더를 지정합니다. 해당 폴더를 포함한 모든 하위 폴더에서 이미지 및 동영상을 스캔합니다.", - "library_remove_exclusion_pattern_prompt": "이 제외 규칙을 삭제하시겠습니까?", - "library_remove_folder_prompt": "이 가져오기 폴더를 정말로 삭제하시겠습니까?", - "library_scanning": "주기적인 스캔", - "library_scanning_description": "주기적인 라이브러리 스캔을 구성합니다.", - "library_scanning_enable_description": "주기적인 라이브러리 스캔 활성화", - "library_settings": "외부 라이브러리", - "library_settings_description": "외부 라이브러리 스캔 및 감시 기능을 설정합니다.", - "library_tasks_description": "외부 라이브러리에서 새 항목 또는 변경된 항목을 스캔", - "library_updated": "라이브러리 업데이트됨", - "library_watching_enable_description": "외부 라이브러리 파일 변경 감시", - "library_watching_settings": "라이브러리 감시 (실험적)", - "library_watching_settings_description": "변경된 파일을 자동으로 감시합니다.", - "logging_enable_description": "로그 기록 활성화", - "logging_level_description": "활성화 시 사용할 로그 레벨을 선택합니다.", - "logging_settings": "로깅", - "machine_learning_availability_checks": "가용성 확인", - "machine_learning_availability_checks_description": "사용 가능한 기계 학습 서버를 자동으로 감지하고 우선적으로 선택합니다.", - "machine_learning_availability_checks_enabled": "가용성 확인 활성화", - "machine_learning_availability_checks_interval": "확인 주기", - "machine_learning_availability_checks_interval_description": "가용성 확인 주기 (밀리초 단위)", - "machine_learning_availability_checks_timeout": "요청 타임아웃", - "machine_learning_availability_checks_timeout_description": "가용성 확인 요청 타임아웃 (밀리초 단위)", - "machine_learning_clip_model": "CLIP 모델", - "machine_learning_clip_model_description": "CLIP 모델의 종류는 이곳을 참조하세요. 한국어 등 여러 언어로 검색하려면 Multilingual CLIP 모델을 선택하세요. 모델을 변경한 경우 모든 이미지의 '스마트 검색' 작업을 다시 실행해야 합니다.", - "machine_learning_duplicate_detection": "비슷한 항목 감지", - "machine_learning_duplicate_detection_enabled": "비슷한 항목 감지 활성화", - "machine_learning_duplicate_detection_enabled_description": "비활성화해도 완전히 동일한 항목은 중복 제거됩니다.", - "machine_learning_duplicate_detection_setting_description": "CLIP 임베딩으로 비슷해 보이는 항목을 찾습니다.", - "machine_learning_enabled": "기계 학습 활성화", - "machine_learning_enabled_description": "비활성화하면 아래 설정과 관계없이 모든 기계 학습 기능이 비활성화됩니다.", - "machine_learning_facial_recognition": "얼굴 인식", - "machine_learning_facial_recognition_description": "이미지에서 얼굴 감지, 인식 및 그룹화를 진행합니다.", - "machine_learning_facial_recognition_model": "얼굴 인식 모델", - "machine_learning_facial_recognition_model_description": "크기에 따라 내림차순으로 나열됩니다. 큰 모델은 느리고 메모리를 많이 사용하지만 더 나은 결과를 보입니다. 모델을 변경한 경우 모든 이미지의 얼굴 감지 작업을 다시 실행해야 합니다.", - "machine_learning_facial_recognition_setting": "얼굴 인식 활성화", - "machine_learning_facial_recognition_setting_description": "비활성화하면 이미지에서 얼굴 인식을 진행하지 않으며, 탐색 페이지에 인물 목록이 표시되지 않습니다.", - "machine_learning_max_detection_distance": "최대 감지 거리", - "machine_learning_max_detection_distance_description": "비슷한 이미지로 간주하는 임계값을 0.001에서 0.1 사이로 설정합니다. 값을 높이면 비슷한 항목이 더 많이 감지되지만 잘못 감지될 가능성도 높아집니다.", - "machine_learning_max_recognition_distance": "최대 인식 거리", - "machine_learning_max_recognition_distance_description": "두 얼굴을 동일인으로 간주하는 임계값을 0에서 2 사이로 설정합니다. 이 값을 낮추면 두 사람을 동일인으로 인식하는 것을 방지할 수 있고, 값을 높이면 한 사람을 다른 두 사람으로 인식하는 것을 방지할 수 있습니다. 두 사람을 합치는 것이 한 사람을 두 명으로 분리하는 것보다 쉬우므로 가능한 낮은 값을 사용하세요.", - "machine_learning_min_detection_score": "최소 신뢰도 점수", - "machine_learning_min_detection_score_description": "감지된 얼굴의 최소 신뢰도 점수를 0에서 1 사이로 설정합니다. 값을 낮추면 더 많은 얼굴을 감지하지만 잘못 감지될 가능성도 높아집니다.", - "machine_learning_min_recognized_faces": "최소 인식 얼굴", - "machine_learning_min_recognized_faces_description": "인물을 생성하기 위해 인식할 얼굴 수의 최솟값을 설정합니다. 값이 높으면 얼굴 인식이 정확해지지만 감지된 얼굴이 인물에 할당되지 않을 가능성이 증가합니다.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "기계 학습으로 이미지에서 텍스트를 인식합니다.", - "machine_learning_ocr_enabled": "OCR 활성화", - "machine_learning_ocr_enabled_description": "비활성화하면 이미지에서 텍스트 인식을 진행하지 않습니다.", - "machine_learning_ocr_max_resolution": "최대 해상도", - "machine_learning_ocr_max_resolution_description": "이 해상도보다 높은 미리보기는 종횡비를 유지하면서 크기가 조절됩니다. 값이 높을수록 정확도도 높아지지만, 처리 시간이 길어지고 메모리 사용량도 늘어납니다.", - "machine_learning_ocr_min_detection_score": "최소 신뢰도 점수", - "machine_learning_ocr_min_detection_score_description": "감지할 텍스트의 최소 신뢰도 점수를 0에서 1 사이로 설정합니다. 값을 낮추면 더 많은 텍스트를 감지하지만 잘못 감지될 가능성도 높아집니다.", - "machine_learning_ocr_min_recognition_score": "최소 인식 점수", - "machine_learning_ocr_min_score_recognition_description": "인식할 텍스트의 최소 신뢰도 점수를 0에서 1 사이로 설정합니다. 값을 낮추면 더 많은 텍스트를 인식하지만 잘못 인식될 가능성도 높아집니다.", - "machine_learning_ocr_model": "OCR 모델", - "machine_learning_ocr_model_description": "서버 모델은 모바일 모델보다 정확하지만, 처리 시간이 길어지고 메모리 사용량도 늘어납니다.", - "machine_learning_settings": "기계 학습 설정", - "machine_learning_settings_description": "기계 학습 시 사용할 모델과 세부 설정을 관리합니다.", - "machine_learning_smart_search": "스마트 검색", - "machine_learning_smart_search_description": "CLIP 임베딩으로 의미 기반 검색을 사용합니다.", - "machine_learning_smart_search_enabled": "스마트 검색 활성화", - "machine_learning_smart_search_enabled_description": "비활성화하면 스마트 검색을 위한 이미지 처리를 진행하지 않습니다.", - "machine_learning_url_description": "기계 학습 서버의 URL을 설정합니다. 여러 개가 입력되면 첫 번째부터 한 번에 하나씩 순서대로 응답하는 서버를 찾을 때까지 요청을 시도합니다. 응답하지 않는 서버는 다시 사용 가능할 때까지 일시적으로 제외됩니다.", - "maintenance_delete_backup": "백업 삭제", - "maintenance_delete_backup_description": "이 파일은 영구적으로 삭제됩니다.", - "maintenance_delete_error": "백업 삭제 실패.", - "maintenance_restore_backup": "백업 복원", - "maintenance_restore_backup_description": "Immich가 삭제되고 선택한 백업에서 복원됩니다. 계속하기 전에 백업이 생성됩니다.", - "maintenance_restore_backup_different_version": "이 백업은 다른 버전의 Immich에서 생성되었습니다!", - "maintenance_restore_backup_unknown_version": "백업 버전을 확인할 수 없습니다.", - "maintenance_restore_database_backup": "데이터베이스 백업 복원", - "maintenance_restore_database_backup_description": "백업 파일을 사용해 이전 데이터베이스 상태로 롤백", - "maintenance_settings": "유지보수", - "maintenance_settings_description": "Immich를 유지 보수 모드로 전환하기.", - "maintenance_start": "유지 보수 모드로 전환", - "maintenance_start_error": "유지 보수 모드 시작에 실패함.", - "maintenance_upload_backup": "데이터베이스 백업 파일 업로드", - "maintenance_upload_backup_error": "백업을 업로드할 수 없습니다, .sql/.sql.gz 파일이 맞습니까?", - "manage_concurrency": "동시성 관리", - "manage_concurrency_description": "작업 페이지로 이동하여 작업 동시 진행 상황을 관리하세요", - "manage_log_settings": "로그 기록 설정을 관리합니다.", - "map_dark_style": "다크 스타일", - "map_enable_description": "지도 기능 활성화", - "map_gps_settings": "지도 및 GPS 설정", - "map_gps_settings_description": "지도 사용자 정의 및 역지오코딩 설정을 관리합니다.", - "map_implications": "지도 기능은 외부 타일 서비스(tiles.immich.cloud)에 의존합니다.", - "map_light_style": "라이트 스타일", - "map_manage_reverse_geocoding_settings": "역지오코딩 설정을 관리합니다.", - "map_reverse_geocoding": "역지오코딩", - "map_reverse_geocoding_enable_description": "역지오코딩 활성화", - "map_reverse_geocoding_settings": "역지오코딩 설정", - "map_settings": "지도", - "map_settings_description": "지도 설정을 관리합니다.", - "map_style_description": "지도 테마 style.json URL", - "memory_cleanup_job": "추억 정리", - "memory_generate_job": "추억 생성", - "metadata_extraction_job": "메타데이터 추출", - "metadata_extraction_job_description": "각 항목에서 GPS, 인물 및 해상도 등의 메타데이터 정보 추출", - "metadata_faces_import_setting": "얼굴 가져오기 활성화", - "metadata_faces_import_setting_description": "사이드카 파일의 이미지 EXIF 데이터에서 얼굴 가져오기", - "metadata_settings": "메타데이터 설정", - "metadata_settings_description": "메타데이터 설정을 관리합니다.", - "migration_job": "마이그레이션", - "migration_job_description": "각 항목의 섬네일 및 인물의 얼굴을 최신 폴더 구조에 맞게 마이그레이션", - "nightly_tasks_cluster_faces_setting_description": "새로 감지된 얼굴의 인식 작업을 실행합니다.", - "nightly_tasks_cluster_new_faces_setting": "새 얼굴 그룹화", - "nightly_tasks_database_cleanup_setting": "데이터베이스 정리", - "nightly_tasks_database_cleanup_setting_description": "데이터베이스에서 오래되었거나 불필요한 데이터를 정리합니다.", - "nightly_tasks_generate_memories_setting": "추억 생성", - "nightly_tasks_generate_memories_setting_description": "사진 및 동영상에서 새로운 추억 모음을 생성합니다.", - "nightly_tasks_missing_thumbnails_setting": "누락된 섬네일 생성", - "nightly_tasks_missing_thumbnails_setting_description": "섬네일 생성 대기열에 누락된 항목을 추가합니다.", - "nightly_tasks_settings": "정기 작업 설정", - "nightly_tasks_settings_description": "특정 시간에 수행되는 작업을 관리합니다.", - "nightly_tasks_start_time_setting": "시작 시간", - "nightly_tasks_start_time_setting_description": "서버가 작업을 시작하는 시간", - "nightly_tasks_sync_quota_usage_setting": "사용량 동기화", - "nightly_tasks_sync_quota_usage_setting_description": "사용자의 저장 공간 할당량을 현재 사용량 기반으로 갱신합니다.", - "no_paths_added": "추가된 경로 없음", - "no_pattern_added": "추가된 규칙 없음", - "note_apply_storage_label_previous_assets": "참고: 이전에 업로드한 항목에도 스토리지 레이블을 적용하려면 다음을 실행합니다,", - "note_cannot_be_changed_later": "주의: 추후 변경할 수 없습니다!", - "notification_email_from_address": "보낸 사람 이메일", - "notification_email_from_address_description": "보낸 사람의 이메일 주소(예: \"Immich Photo Server \"). 이메일 발송이 허용된 주소를 사용해야 합니다.", - "notification_email_host_description": "이메일 서버의 호스트 (예: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "인증서 오류 무시", - "notification_email_ignore_certificate_errors_description": "TLS 인증서 유효성 검사 오류 무시 (권장되지 않음)", - "notification_email_password_description": "이메일 서버 인증 시 사용할 비밀번호", - "notification_email_port_description": "이메일 서버 포트 (예: 25, 465 또는 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "SMTPS 사용 (SMTP over TLS)", - "notification_email_sent_test_email_button": "테스트 이메일 전송 및 저장", - "notification_email_setting_description": "이메일 알림 전송 설정", - "notification_email_test_email": "테스트 이메일 전송", - "notification_email_test_email_failed": "테스트 이메일 전송에 실패했습니다. 입력값을 확인하세요", - "notification_email_test_email_sent": "테스트 이메일이 {email}(으)로 전송되었습니다. 받은편지함을 확인하세요.", - "notification_email_username_description": "이메일 서버 인증 시 사용할 사용자명", - "notification_enable_email_notifications": "이메일 알림 활성화", - "notification_settings": "알림 설정", - "notification_settings_description": "이메일을 포함한 알림 설정을 관리합니다.", - "oauth_auto_launch": "자동 실행", - "oauth_auto_launch_description": "로그인 페이지에 접근 시 OAuth 로그인 과정을 자동으로 진행", - "oauth_auto_register": "자동 등록", - "oauth_auto_register_description": "OAuth 로그인 후 새 사용자를 자동으로 등록합니다.", - "oauth_button_text": "버튼 텍스트", - "oauth_client_secret_description": "비공개 클라이언트 또는 공개 클라이언트가 PKCE(Proof Key for Code Exchange, 코드 교환용 검증 키)를 지원하지 않는 경우 필요합니다.", - "oauth_enable_description": "OAuth 로그인", - "oauth_mobile_redirect_uri": "모바일 리다이렉트 URI", - "oauth_mobile_redirect_uri_override": "모바일 리다이렉트 URI 오버라이드", - "oauth_mobile_redirect_uri_override_description": "OAuth 공급자가 ''{callback}'' 등의 모바일 URI를 허용하지 않는 경우 활성화하세요.", - "oauth_role_claim": "역할 클레임", - "oauth_role_claim_description": "요청한 클레임을 사용자의 역할로 자동 설정합니다. 'user' 또는 'admin'을 선택할 수 있습니다.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth 로그인 설정을 관리합니다.", - "oauth_settings_more_details": "이 기능에 대한 자세한 내용은 문서를 참조하세요.", - "oauth_storage_label_claim": "스토리지 레이블 클레임", - "oauth_storage_label_claim_description": "클레임의 값을 사용자 스토리지 레이블로 자동 설정합니다.", - "oauth_storage_quota_claim": "스토리지 용량 클레임", - "oauth_storage_quota_claim_description": "요청한 값을 사용자 스토리지 할당량으로 자동 설정합니다.", - "oauth_storage_quota_default": "기본 스토리지 할당량 (GiB)", - "oauth_storage_quota_default_description": "입력하지 않은 경우 사용할 GiB 단위의 할당량", - "oauth_timeout": "요청 타임아웃", - "oauth_timeout_description": "요청 타임아웃 (밀리초 단위)", - "ocr_job_description": "기계 학습으로 이미지에서 텍스트를 인식합니다.", - "password_enable_description": "이메일과 비밀번호로 로그인", - "password_settings": "비밀번호 로그인", - "password_settings_description": "비밀번호 로그인 설정을 관리합니다.", - "paths_validated_successfully": "모든 경로가 검증되었습니다.", - "person_cleanup_job": "인물 정리", - "queue_details": "대기열 상세", - "queues": "작업 대기열", - "queues_page_description": "관리자 작업 대기열 페이지", - "quota_size_gib": "할당량 (GiB)", - "refreshing_all_libraries": "모든 라이브러리를 새로고침합니다.", - "registration": "관리자 등록", - "registration_description": "첫 번째 사용자이므로 관리자 권한이 부여됩니다. 관리 작업 및 사용자 생성이 가능합니다.", - "remove_failed_jobs": "실패한 작업 삭제", - "require_password_change_on_login": "최초 로그인 시 비밀번호 변경 요구", - "reset_settings_to_default": "설정을 기본값으로 복원", - "reset_settings_to_recent_saved": "마지막에 저장된 설정으로 복원", - "scanning_library": "라이브러리 스캔 중", - "search_jobs": "작업 검색…", - "send_welcome_email": "환영 이메일 전송", - "server_external_domain_settings": "외부 도메인", - "server_external_domain_settings_description": "공개 공유 링크에 사용할 도메인 (http(s):// 포함)", - "server_public_users": "모든 사용자", - "server_public_users_description": "사용자를 공유 앨범에 추가할 때 모든 사용자(이름과 이메일)가 표시됩니다. 비활성화하면 관리자만 목록을 볼 수 있습니다.", - "server_settings": "서버 설정", - "server_settings_description": "서버 설정을 관리합니다.", - "server_stats_page_description": "관리자 서버 통계 페이지", - "server_welcome_message": "환영 메시지", - "server_welcome_message_description": "로그인 페이지에 표시되는 메시지입니다.", - "settings_page_description": "관리자 설정 페이지", - "sidecar_job": "사이드카 메타데이터", - "sidecar_job_description": "파일 시스템에서 사이드카 메타데이터 파일 탐색 및 동기화", - "slideshow_duration_description": "개별 사진이 표시되는 초 단위의 시간", - "smart_search_job_description": "기계 학습을 진행하여 스마트 검색 기능 지원", - "storage_template_date_time_description": "날짜 및 시간 정보는 항목 생성 날짜의 타임스탬프를 사용합니다.", - "storage_template_date_time_sample": "예시 시간: {date}", - "storage_template_enable_description": "스토리지 템플릿 엔진 활성화", - "storage_template_hash_verification_enabled": "해시 검증 활성화", - "storage_template_hash_verification_enabled_description": "해시 검증을 활성화합니다. 이 설정의 영향을 확실히 이해하지 않는 한 비활성화하지 마세요.", - "storage_template_migration": "스토리지 템플릿 마이그레이션", - "storage_template_migration_description": "이전에 업로드된 항목에 현재 {template} 적용", - "storage_template_migration_info": "스토리지 템플릿은 모든 확장자를 소문자로 변환하며, 변경 사항은 새로 업로드한 항목에만 적용됩니다. 기존에 업로드된 항목에 적용하려면 {job}을 실행하세요.", - "storage_template_migration_job": "스토리지 템플릿 마이그레이션 작업", - "storage_template_more_details": "이 기능에 대한 자세한 내용은 스토리지 템플릿설명을 참조하세요.", - "storage_template_onboarding_description_v2": "활성화하면 사용자 정의 템플릿에 따라 파일이 자동으로 분류됩니다. 자세한 내용은 문서를 참조하세요.", - "storage_template_path_length": "대략적인 경로 길이 제한: {length, number}/{limit, number}", - "storage_template_settings": "스토리지 템플릿", - "storage_template_settings_description": "업로드된 항목의 파일명 및 폴더 구조를 관리합니다.", - "storage_template_user_label": "사용자의 스토리지 레이블: {label}", - "system_settings": "시스템 설정", - "tag_cleanup_job": "태그 정리", - "template_email_available_tags": "템플릿에 다음 변수를 사용할 수 있습니다: {tags}", - "template_email_if_empty": "비어 있는 경우 기본 템플릿이 사용됩니다.", - "template_email_invite_album": "앨범 초대 템플릿", - "template_email_preview": "미리보기", - "template_email_settings": "이메일 템플릿", - "template_email_update_album": "앨범 업데이트 알림 템플릿", - "template_email_welcome": "환영 메시지 템플릿", - "template_settings": "알림 템플릿", - "template_settings_description": "알림에 사용되는 사용자 지정 템플릿을 관리합니다.", - "theme_custom_css_settings": "사용자 지정 CSS", - "theme_custom_css_settings_description": "CSS로 Immich의 디자인을 사용자 정의할 수 있습니다.", - "theme_settings": "테마 설정", - "theme_settings_description": "Immich 웹 인터페이스를 사용자 정의합니다.", - "thumbnail_generation_job": "섬네일 생성", - "thumbnail_generation_job_description": "각 항목 및 인물에 대해 크고 작은 썸네일, 흐릿한 섬네일 생성", - "transcoding_acceleration_api": "가속 API", - "transcoding_acceleration_api_description": "트랜스코딩 가속에 사용할 API를 지정합니다. 이 설정은 'best effort' 방식으로 동작하며, 실패 시 소프트웨어 트랜스코딩으로 전환됩니다. 하드웨어에 따라 VP9은 지원되지 않을 수 있습니다.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU 필요)", - "transcoding_acceleration_qsv": "Quick Sync (인텔 7세대 이상 CPU 필요)", - "transcoding_acceleration_rkmpp": "RKMPP (Rockchip SoC 필요)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "허용 오디오 코덱", - "transcoding_accepted_audio_codecs_description": "트랜스코딩에서 제외할 오디오 코덱을 선택합니다. 이 설정은 특정 트랜스코딩 기준에서만 사용됩니다.", - "transcoding_accepted_containers": "허용 컨테이너", - "transcoding_accepted_containers_description": "MP4로 재다중화(remux)하지 않을 컨테이너 포맷을 선택합니다. 이 설정은 특정 트랜스코딩 기준에서만 사용됩니다.", - "transcoding_accepted_video_codecs": "허용 동영상 코덱", - "transcoding_accepted_video_codecs_description": "트랜스코딩에서 제외할 동영상 코덱을 선택합니다. 이 설정은 특정 트랜스코딩 기준에서만 사용됩니다.", - "transcoding_advanced_options_description": "대부분의 사용자가 변경할 필요가 없는 설정", - "transcoding_audio_codec": "오디오 코덱", - "transcoding_audio_codec_description": "Opus는 품질이 가장 좋지만, 구형 기기나 소프트웨어에서 호환성이 낮아질 수 있습니다.", - "transcoding_bitrate_description": "최대 비트레이트를 초과하거나 허용되지 않은 형식의 동영상", - "transcoding_codecs_learn_more": "이곳에서 사용되는 용어에 대한 자세한 내용은 FFmpeg 문서의 H.264 코덱, HEVC 코덱VP9 코덱 항목을 참조하세요.", - "transcoding_constant_quality_mode": "Constant quality mode", - "transcoding_constant_quality_mode_description": "ICQ는 CQP보다 우수하나 일부 하드웨어 가속 장치에서는 지원되지 않습니다. 이 옵션을 설정하면 품질 기반 인코딩 지정된 모드를 우선 사용합니다. NVENC는 ICQ를 지원하지 않아 이 설정이 무시됩니다.", - "transcoding_constant_rate_factor": "Constant rate factor (-crf)", - "transcoding_constant_rate_factor_description": "품질 수준을 설정합니다. 일반적으로 H.264는 23, HEVC는 28, VP9은 31, AV1은 35를 사용하며, 값을 낮추면 품질이 향상되지만 파일 크기가 커집니다.", - "transcoding_disabled_description": "동영상을 트랜스코딩하지 않음. 일부 기기에서 재생이 불가능할 수 있습니다.", - "transcoding_encoding_options": "인코딩 옵션", - "transcoding_encoding_options_description": "코덱, 해상도, 품질 및 기타 인코딩 옵션을 설정합니다.", - "transcoding_hardware_acceleration": "하드웨어 가속", - "transcoding_hardware_acceleration_description": "(실험적) 트랜스코딩 속도가 빨라지지만 동일 비트레이트에서 품질이 상대적으로 저하될 수 있습니다.", - "transcoding_hardware_decoding": "하드웨어 디코딩", - "transcoding_hardware_decoding_setting_description": "종단간 가속으로 디코딩부터 인코딩까지 전체 과정을 가속합니다. 일부 동영상에서는 작동하지 않을 수 있습니다.", - "transcoding_max_b_frames": "최대 B-프레임", - "transcoding_max_b_frames_description": "값을 높이면 압축 효율이 향상되지만 인코딩 속도가 느려집니다. 오래된 장치의 하드웨어 가속과 호환되지 않을 수 있습니다. 0을 입력하면 B-프레임을 비활성화하고, -1을 입력하면 자동으로 설정합니다.", - "transcoding_max_bitrate": "최대 비트레이트", - "transcoding_max_bitrate_description": "최대 비트레이트를 지정하면 파일 크기가 예측 가능해지지만 품질이 다소 저하될 수 있습니다. 일반적으로 720p 해상도에서는 VP9, HEVC가 2600kbit/s, H.264는 4500kbit/s를 사용하며, 0으로 설정하면 비활성화됩니다. 단위를 생략하면 k(kbit/s)로 간주되며 5000, 5000k, 5M(Mbit/s)은 같은 값으로 처리됩니다.", - "transcoding_max_keyframe_interval": "최대 키프레임 간격", - "transcoding_max_keyframe_interval_description": "키프레임 간 최대 프레임 간격을 설정합니다. 값을 낮추면 압축 효율은 떨어지지만 탐색 속도가 빨라지고 움직임이 많은 장면에서 품질이 향상될 수 있습니다. 0을 입력하면 자동으로 설정합니다.", - "transcoding_optimal_description": "목표 해상도를 초과하거나 허용되지 않은 포맷의 동영상", - "transcoding_policy": "트랜스코드 기준", - "transcoding_policy_description": "트랜스코딩 대상 동영상 설정", - "transcoding_preferred_hardware_device": "기본 하드웨어 장치", - "transcoding_preferred_hardware_device_description": "(VAAPI 및 QSV인 경우) 하드웨어 트랜스코딩에 사용할 dri 노드를 지정합니다.", - "transcoding_preset_preset": "프리셋 (-preset)", - "transcoding_preset_preset_description": "압축 속도를 설정합니다. 동일 비트레이트에서 느린 속도를 선택하면 상대적으로 파일 크기가 감소하며 품질이 향상됩니다. VP9는 'faster' 이상의 속도를 무시합니다.", - "transcoding_reference_frames": "참조 프레임 수", - "transcoding_reference_frames_description": "특정 프레임을 압축할 때 참조할 프레임 수를 설정합니다. 값을 높이면 압축 효율이 향상되지만 인코딩 속도가 느려집니다. 0을 입력하면 자동으로 설정합니다.", - "transcoding_required_description": "허용 포맷이 아닌 동영상만 트랜스코딩", - "transcoding_settings": "트랜스코딩 설정", - "transcoding_settings_description": "트랜스코딩할 동영상 및 처리 방법을 관리합니다.", - "transcoding_target_resolution": "목표 해상도", - "transcoding_target_resolution_description": "해상도를 높이면 세부 정보가 더 많이 보존되지만, 인코딩 시간이 늘어나고 파일 크기가 커져 앱 반응 속도가 느려질 수 있습니다.", - "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "NVENC에만 적용됩니다. Temporal Adaptive Quantization은 디테일이 많고 정적인 장면의 품질이 향상됩니다. 오래된 기기에서 호환되지 않을 수 있습니다.", - "transcoding_threads": "스레드 수", - "transcoding_threads_description": "값을 높이면 인코딩 속도가 빨라지지만, 서버가 다른 작업을 처리할 여유가 줄어듭니다. 입력한 값은 CPU 코어 수를 초과하지 않아야 하며, 0으로 설정하면 CPU를 최대한 활용합니다.", - "transcoding_tone_mapping": "톤 매핑", - "transcoding_tone_mapping_description": "HDR 영상을 SDR로 변환할 때 사용할 톤 매핑 알고리즘을 설정합니다. Hable은 디테일, Mobius는 색상, Reinhard는 밝기에 중점을 두며 '비활성화'는 톤 매핑을 사용하지 않습니다.", - "transcoding_transcode_policy": "트랜스코드 기준", - "transcoding_transcode_policy_description": "동영상을 트랜스코딩할 기준을 설정합니다. HDR 동영상은 항상 트랜스코딩됩니다. (트랜스코딩이 비활성화된 경우 제외)", - "transcoding_two_pass_encoding": "2패스 인코딩", - "transcoding_two_pass_encoding_setting_description": "2패스 인코딩을 사용해 인코딩 품질을 높입니다. H.264 및 HEVC의 경우 CRF를 무시하고 최대 비트레이트 기반의 비트레이트 범위를 사용합니다. VP9의 경우 최대 비트레이트를 비활성화하면 CRF를 사용할 수 있습니다.", - "transcoding_video_codec": "동영상 코덱", - "transcoding_video_codec_description": "VP9는 효율적이고 웹 호환성이 높으나 트랜스코딩이 오래 걸립니다. HEVC는 VP9와 비슷하지만 웹 호환성이 낮습니다. H.264는 호환성이 가장 높으나 처리된 파일의 크기가 크고, AV1은 가장 효율적이지만 오래된 기기와의 호환성이 낮습니다.", - "trash_enabled_description": "휴지통 기능 활성화", - "trash_number_of_days": "휴지통 보관 기간", - "trash_number_of_days_description": "항목을 영구적으로 삭제하기 전까지 휴지통에 보관할 기간(일)", - "trash_settings": "휴지통 설정", - "trash_settings_description": "휴지통 기능을 설정합니다.", - "unlink_all_oauth_accounts": "모든 OAuth 계정 연결 해제", - "unlink_all_oauth_accounts_description": "새 공급자로 이전하려면 먼저 모든 OAuth 계정의 연결을 해제해야 합니다.", - "unlink_all_oauth_accounts_prompt": "모든 OAuth 계정 연결을 해제하시겠습니까? 각 사용자의 OAuth ID를 초기화하며 되돌릴 수 없습니다.", - "user_cleanup_job": "사용자 정리", - "user_delete_delay": "{user}님의 계정과 항목이 {delay, plural, one {#일} other {#일}} 후 영구적으로 삭제됩니다.", - "user_delete_delay_settings": "삭제 대기 기간", - "user_delete_delay_settings_description": "사용자 제거 후 계정과 항목이 영구 삭제되기까지의 기간(일). 이 설정은 자정마다 실행되는 삭제 작업 시 반영됩니다.", - "user_delete_immediately": "{user}님의 계정과 항목이 즉시 영구적으로 삭제됩니다.", - "user_delete_immediately_checkbox": "사용자 및 항목을 즉시 삭제 대기열에 추가", - "user_details": "사용자 정보", - "user_management": "사용자 관리", - "user_password_has_been_reset": "사용자의 비밀번호가 초기화되었습니다:", - "user_password_reset_description": "이 임시 비밀번호를 사용자에게 전달하고, 다음 로그인 시 반드시 변경해야 한다고 안내하세요.", - "user_restore_description": "예약된 {user}님의 삭제를 취소합니다.", - "user_restore_scheduled_removal": "{date, date, long}에 예약된 사용자 삭제 취소", - "user_settings": "사용자 설정", - "user_settings_description": "사용자 설정을 관리합니다.", - "user_successfully_removed": "사용자 {email}님이 성공적으로 삭제되었습니다.", - "users_page_description": "관리자 사용자 페이지", - "version_check_enabled_description": "버전 확인 활성화", - "version_check_implications": "주기적으로 Github에 요청을 보내 새 버전을 확인합니다.", - "version_check_settings": "버전 확인", - "version_check_settings_description": "새 버전 확인 및 알림 기능을 관리합니다.", - "video_conversion_job": "동영상 트랜스코드", - "video_conversion_job_description": "다양한 브라우저 및 기기와의 호환성을 위한 동영상 트랜스코드" - }, - "admin_email": "관리자 이메일", - "admin_password": "관리자 비밀번호", - "administration": "관리", - "advanced": "고급", - "advanced_settings_clear_image_cache": "이미지 캐시 지우기", - "advanced_settings_clear_image_cache_error": "이미지 캐시 삭제 실패", - "advanced_settings_clear_image_cache_success": "{size}가 성공적으로 정리됨", - "advanced_settings_enable_alternate_media_filter_subtitle": "이 옵션을 사용하면 동기화 중 미디어를 대체 기준으로 필터링할 수 있습니다. 앱이 모든 앨범을 제대로 감지하지 못할 때만 사용하세요.", - "advanced_settings_enable_alternate_media_filter_title": "대체 기기 앨범 동기화 필터 사용 (실험적)", - "advanced_settings_log_level_title": "로그 레벨: {level}", - "advanced_settings_prefer_remote_subtitle": "일부 기기의 경우 로컬 항목에서 섬네일을 로드하는 속도가 매우 느립니다. 서버 이미지를 대신 로드하려면 이 설정을 활성화하세요.", - "advanced_settings_prefer_remote_title": "서버 이미지 선호", - "advanced_settings_proxy_headers_subtitle": "Immich가 네트워크 요청 시 사용할 프록시 헤더를 정의합니다.", - "advanced_settings_proxy_headers_title": "커스텀 프록시 헤더 (실험적)", - "advanced_settings_readonly_mode_subtitle": "읽기 전용 모드를 활성화하면 이미지 선택, 공유, 캐스트, 삭제 등의 동작이 비활성화됩니다. 메인 화면에서 사용자 아이콘을 길게 눌러 읽기 전용 모드를 활성화/비활성화하세요.", - "advanced_settings_readonly_mode_title": "읽기 전용 모드", - "advanced_settings_self_signed_ssl_subtitle": "서버 엔드포인트의 SSL 인증서 검증을 건너뜁니다. 자체 서명 인증서를 사용하는 경우 활성화하세요.", - "advanced_settings_self_signed_ssl_title": "자체 서명된 SSL 인증서 허용 (실험적)", - "advanced_settings_sync_remote_deletions_subtitle": "웹에서 삭제하거나 복원한 항목을 이 기기에서도 자동으로 처리하도록 설정", - "advanced_settings_sync_remote_deletions_title": "원격 삭제 동기화 (실험적)", - "advanced_settings_tile_subtitle": "고급 사용자 설정", - "advanced_settings_troubleshooting_subtitle": "문제 해결을 위한 추가 기능 사용", - "advanced_settings_troubleshooting_title": "문제 해결", - "age_months": "생후 {months, plural, one {#개월} other {#개월}}", - "age_year_months": "생후 1년 {months, plural, one {#개월} other {#개월}}", - "age_years": "{years, plural, other {#세}}", - "album": "앨범", - "album_added": "공유 앨범 초대", - "album_added_notification_setting_description": "공유 앨범에 추가된 경우 이메일 알림 받기", - "album_cover_updated": "앨범 커버 업데이트됨", - "album_delete_confirmation": "{album} 앨범을 삭제하시겠습니까?", - "album_delete_confirmation_description": "이 앨범이 공유된 경우, 다른 사용자가 더이상 앨범에 접근할 수 없습니다.", - "album_deleted": "앨범 삭제됨", - "album_info_card_backup_album_excluded": "제외됨", - "album_info_card_backup_album_included": "선택됨", - "album_info_updated": "앨범 정보 업데이트됨", - "album_leave": "앨범에서 나가시겠습니까?", - "album_leave_confirmation": "{album} 앨범에서 나가시겠습니까?", - "album_name": "앨범 이름", - "album_options": "앨범 옵션", - "album_remove_user": "사용자를 제거하시겠습니까?", - "album_remove_user_confirmation": "{user}님을 앨범에서 제거하시겠습니까?", - "album_search_not_found": "검색 결과에 해당하는 앨범이 없습니다.", - "album_selected": "선택된 앨범", - "album_share_no_users": "이미 모든 사용자와 앨범을 공유했거나 공유할 사용자가 없습니다.", - "album_summary": "앨범 요약", - "album_updated": "항목 추가 알림", - "album_updated_setting_description": "공유 앨범에 항목이 추가된 경우 이메일 알림 받기", - "album_upload_assets": "컴퓨터에서 항목을 업로드하고 앨범에 추가", - "album_user_left": "{album} 앨범에서 나옴", - "album_user_removed": "{user}님을 앨범에서 제거함", - "album_viewer_appbar_delete_confirm": "이 앨범을 삭제하시겠습니까?", - "album_viewer_appbar_share_err_delete": "앨범 삭제 실패", - "album_viewer_appbar_share_err_leave": "앨범 나가기 실패", - "album_viewer_appbar_share_err_remove": "앨범에서 항목 제거 중 문제 발생", - "album_viewer_appbar_share_err_title": "앨범명 변경 실패", - "album_viewer_appbar_share_leave": "앨범 나가기", - "album_viewer_appbar_share_to": "공유 대상", - "album_viewer_page_share_add_users": "사용자 추가", - "album_with_link_access": "링크가 있는 경우 누구나 이 앨범의 사진과 인물을 볼 수 있습니다.", - "albums": "앨범", - "albums_count": "앨범 {count, plural, one {{count, number}개} other {{count, number}개}}", - "albums_default_sort_order": "기본 앨범 정렬 순서", - "albums_default_sort_order_description": "새 앨범 생성 시 적용되는 기본 정렬을 설정합니다.", - "albums_feature_description": "여러 사진과 동영상을 한곳에 모아 둘 수 있습니다.", - "albums_on_device_count": "기기의 앨범 ({count}개)", - "albums_selected": "{count, plural, one {#개} other {#개}} 앨범 선택됨", - "all": "모두", - "all_albums": "모든 앨범", - "all_people": "모든 인물", - "all_photos": "모든 사진", - "all_videos": "모든 동영상", - "allow_dark_mode": "다크 모드 사용", - "allow_edits": "편집자로 설정", - "allow_public_user_to_download": "모든 사용자의 다운로드 허용", - "allow_public_user_to_upload": "모든 사용자의 업로드 허용", - "allowed": "허용됨", - "alt_text_qr_code": "QR 코드 이미지", - "always_keep": "항상 유지", - "always_keep_photos_hint": "이 기기에 모든 사진이 보관됩니다.", - "always_keep_videos_hint": "이 기기에 모든 동영상이 보관됩니다.", - "anti_clockwise": "반시계 방향", - "api_key": "API 키", - "api_key_description": "이 값은 한 번만 표시됩니다. 창을 닫기 전 반드시 복사해주세요.", - "api_key_empty": "API 키 이름은 비워둘 수 없습니다.", - "api_keys": "API 키", - "app_architecture_variant": "변형 (아키텍처)", - "app_bar_signout_dialog_content": "정말 로그아웃하시겠습니까?", - "app_bar_signout_dialog_ok": "네", - "app_bar_signout_dialog_title": "로그아웃", - "app_download_links": "앱 다운로드 링크", - "app_settings": "앱 설정", - "app_stores": "앱 스토어", - "app_update_available": "앱 업데이트 가능", - "appears_in": "다음 앨범에 포함됨", - "apply_count": "적용 ({count, number})", - "archive": "보관함", - "archive_action_prompt": "보관함으로 항목 {count}개 이동됨", - "archive_or_unarchive_photo": "보관 처리 또는 해제", - "archive_page_no_archived_assets": "보관된 항목 없음", - "archive_page_title": "보관함 ({count})", - "archive_size": "압축 파일 크기", - "archive_size_description": "지정한 크기를 초과하면 여러 개의 파일로 분할됩니다. (GiB 단위)", - "archived": "보관함", - "archived_count": "보관함으로 항목 {count, plural, other {#개}} 이동됨", - "are_these_the_same_person": "동일한 인물인가요?", - "are_you_sure_to_do_this": "계속 진행하시겠습니까?", - "array_field_not_fully_supported": "배열 필드는 JSON을 수동으로 편집해야 합니다", - "asset_action_delete_err_read_only": "읽기 전용 항목은 삭제할 수 없어 건너뜁니다.", - "asset_action_share_err_offline": "오프라인 항목은 불러올 수 없어 건너뜁니다.", - "asset_added_to_album": "앨범에 추가되었습니다.", - "asset_adding_to_album": "앨범에 추가 중…", - "asset_created": "자산 생성됨", - "asset_description_updated": "항목 설명이 업데이트되었습니다.", - "asset_filename_is_offline": "{filename} 항목 누락됨", - "asset_has_unassigned_faces": "항목에 할당되지 않은 얼굴이 있음", - "asset_hashing": "해싱 중…", - "asset_list_group_by_sub_title": "다음으로 그룹화", - "asset_list_layout_settings_dynamic_layout_title": "동적 레이아웃", - "asset_list_layout_settings_group_automatically": "자동", - "asset_list_layout_settings_group_by": "다음으로 그룹화", - "asset_list_layout_settings_group_by_month_day": "월 + 일", - "asset_list_layout_sub_title": "레이아웃", - "asset_list_settings_subtitle": "사진 배열 레이아웃 설정", - "asset_list_settings_title": "사진 배열", - "asset_not_found_on_device_android": "기기에서 항목을 찾을 수 없음", - "asset_not_found_on_device_ios": "기기에서 항목을 찾을 수 없습니다. iCloud를 사용하는 경우 저장된 파일이 손상되었을 수 있습니다.", - "asset_not_found_on_icloud": "iCloud에서 항목을 찾을 수 없습니다. iCloud에 저장된 파일이 손상되었을 수 있습니다.", - "asset_offline": "누락된 항목", - "asset_offline_description": "디스크에서 항목을 더이상 찾을 수 없습니다. 서버 관리자에게 연락하세요.", - "asset_restored_successfully": "항목이 복원되었습니다.", - "asset_skipped": "건너뜀", - "asset_skipped_in_trash": "휴지통의 항목", - "asset_trashed": "항목 삭제됨", - "asset_troubleshoot": "항목 트러블슈팅", - "asset_uploaded": "업로드 완료", - "asset_uploading": "업로드 중…", - "asset_viewer_settings_subtitle": "갤러리 보기 설정을 관리합니다.", - "asset_viewer_settings_title": "보기 옵션", - "assets": "항목", - "assets_added_count": "항목 {count, plural, one {#개} other {#개}} 추가됨", - "assets_added_to_album_count": "앨범에 항목 {count, plural, one {#개} other {#개}} 추가됨", - "assets_added_to_albums_count": "{albumTotal, plural, one {앨범에} other {앨범 #개에}} {assetTotal, plural, one {항목이} other {항목 #개가}} 추가됨", - "assets_cannot_be_added_to_album_count": "{count, plural, one {앨범에 항목을} other {일부 항목을 앨범에}} 추가할 수 없습니다.", - "assets_cannot_be_added_to_albums": "{count, plural, one {항목을} other {항목들을}} 어느 앨범에도 추가할 수 없습니다.", - "assets_count": "{count, plural, one {#개} other {#개}} 항목", - "assets_deleted_permanently": "{count}개 항목이 영구적으로 삭제됨", - "assets_deleted_permanently_from_server": "{count}개 항목이 서버에서 영구적으로 삭제됨", - "assets_downloaded_failed": "{count, plural, one {파일 #개 다운로드 완료 - {error}개 실패} other {파일 #개 다운로드 완료 - {error}개 실패}}", - "assets_downloaded_successfully": "{count, plural, one {파일 #개 다운로드 완료} other {파일 #개 다운로드 완료}}", - "assets_moved_to_trash_count": "휴지통으로 항목 {count, plural, one {#개} other {#개}} 이동됨", - "assets_permanently_deleted_count": "{count, plural, one {#개 항목} other {#개 항목}}이 영구적으로 삭제됨", - "assets_removed_count": "{count, plural, one {항목 #개} other {항목 #개}} 제거됨", - "assets_removed_permanently_from_device": "{count}개 항목이 기기에서 영구적으로 삭제됨", - "assets_restore_confirmation": "휴지통의 모든 항목을 복원하시겠습니까? 이 작업은 되돌릴 수 없습니다! 누락된 항목은 복원되지 않습니다.", - "assets_restored_count": "{count, plural, one {항목 #개} other {항목 #개}} 복원됨", - "assets_restored_successfully": "항목 {count}개 복원됨", - "assets_trashed": "휴지통으로 항목 {count}개 이동됨", - "assets_trashed_count": "휴지통으로 항목 {count, plural, one {#개} other {#개}} 이동됨", - "assets_trashed_from_server": "서버 항목 {count}개 휴지통으로 이동됨", - "assets_were_part_of_album_count": "앨범에 이미 포함된 {count, plural, one {항목} other {항목}}입니다.", - "assets_were_part_of_albums_count": "이미 앨범에 포함된 {count, plural, one {항목} other {항목}}입니다.", - "authorized_devices": "내 기기", - "automatic_endpoint_switching_subtitle": "지정된 Wi-Fi가 사용 가능한 경우 내부망으로 연결하고, 그 외의 경우 다른 연결 방식을 사용합니다.", - "automatic_endpoint_switching_title": "자동 URL 전환", - "autoplay_slideshow": "슬라이드 쇼 자동 재생", - "back": "뒤로", - "back_close_deselect": "뒤로, 닫기 또는 선택 해제", - "background_backup_running_error": "백그라운드 백업이 진행 중입니다. 수동 백업을 시작할 수 없습니다.", - "background_location_permission": "백그라운드 위치 권한", - "background_location_permission_content": "Immich가 백그라운드에서 실행 중일 때 네트워크를 전환하려면 Wi-Fi 네트워크 이름을 확인해야 하며, 이를 위해 '정확한 위치' 권한을 항상 허용해야 합니다.", - "background_options": "백그라운드 옵션", - "backup": "백업", - "backup_album_selection_page_albums_device": "기기의 앨범 ({count})", - "backup_album_selection_page_albums_tap": "탭하여 포함, 두 번 탭하여 제외", - "backup_album_selection_page_assets_scatter": "각 항목은 여러 앨범에 포함될 수 있으며, 백업 진행 중에도 대상 앨범을 포함하거나 제외할 수 있습니다.", - "backup_album_selection_page_select_albums": "앨범 선택", - "backup_album_selection_page_selection_info": "선택한 앨범", - "backup_album_selection_page_total_assets": "전체 항목", - "backup_albums_sync": "앨범 동기화 백업", - "backup_all": "모두", - "backup_background_service_backup_failed_message": "항목 백업에 실패했습니다. 다시 시도하는 중…", - "backup_background_service_complete_notification": "항목 백업 완료", - "backup_background_service_connection_failed_message": "서버 연결에 실패했습니다. 다시 시도하는 중…", - "backup_background_service_current_upload_notification": "{filename} 업로드 중", - "backup_background_service_default_notification": "새로운 항목을 확인하는 중…", - "backup_background_service_error_title": "백업 오류", - "backup_background_service_in_progress_notification": "항목을 백업하는 중…", - "backup_background_service_upload_failure_notification": "{filename} 업로드 실패", - "backup_controller_page_albums": "백업할 앨범", - "backup_controller_page_background_app_refresh_disabled_content": "백그라운드 백업을 사용하려면 설정 > 일반 > 백그라운드 앱 새로 고침에서 백그라운드 앱 새로 고침을 활성화하세요.", - "backup_controller_page_background_app_refresh_disabled_title": "백그라운드 새로 고침 비활성화됨", - "backup_controller_page_background_app_refresh_enable_button_text": "설정으로 이동", - "backup_controller_page_background_battery_info_link": "설정 방법", - "backup_controller_page_background_battery_info_message": "최상의 백그라운드 백업 환경을 위해 Immich의 백그라운드 활동을 제한하는 배터리 최적화 기능을 비활성화하세요.\n\n기기마다 설정 방법에 차이가 있으므로 제조사에서 관련 정보를 찾아보세요.", - "backup_controller_page_background_battery_info_ok": "확인", - "backup_controller_page_background_battery_info_title": "배터리 최적화", - "backup_controller_page_background_charging": "충전 중에만", - "backup_controller_page_background_configure_error": "백그라운드 서비스 구성 실패", - "backup_controller_page_background_delay": "새 항목 백업 지연: {duration}", - "backup_controller_page_background_description": "앱을 열지 않고도 새 항목을 자동으로 백업하려면 백그라운드 서비스를 활성화하세요.", - "backup_controller_page_background_is_off": "자동 백그라운드 백업을 사용하지 않습니다.", - "backup_controller_page_background_is_on": "자동 백그라운드 백업을 사용 중입니다.", - "backup_controller_page_background_turn_off": "백그라운드 서비스 비활성화", - "backup_controller_page_background_turn_on": "백그라운드 서비스 활성화", - "backup_controller_page_background_wifi": "Wi-Fi에서만", - "backup_controller_page_backup": "백업", - "backup_controller_page_backup_selected": "선택됨: ", - "backup_controller_page_backup_sub": "백업된 사진 및 동영상", - "backup_controller_page_created": "생성일: {date}", - "backup_controller_page_desc_backup": "포그라운드 백업을 활성화하여 앱을 시작할 때 새 항목을 서버에 자동으로 업로드하세요.", - "backup_controller_page_excluded": "제외됨: ", - "backup_controller_page_failed": "실패 ({count})", - "backup_controller_page_filename": "파일명: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "백업 정보", - "backup_controller_page_none_selected": "선택된 항목 없음", - "backup_controller_page_remainder": "남은 항목", - "backup_controller_page_remainder_sub": "백업 대기 중인 사진 및 동영상", - "backup_controller_page_server_storage": "저장 공간", - "backup_controller_page_start_backup": "백업 시작", - "backup_controller_page_status_off": "자동 포그라운드 백업을 사용하지 않습니다.", - "backup_controller_page_status_on": "자동 포그라운드 백업을 사용 중입니다.", - "backup_controller_page_storage_format": "{total} 중 {used} 사용", - "backup_controller_page_to_backup": "백업할 앨범 목록", - "backup_controller_page_total_sub": "선택한 앨범의 고유한 사진 및 동영상", - "backup_controller_page_turn_off": "비활성화", - "backup_controller_page_turn_on": "활성화", - "backup_controller_page_uploading_file_info": "파일 정보 업로드 중", - "backup_err_only_album": "유일한 앨범은 삭제할 수 없습니다.", - "backup_error_sync_failed": "동기화에 실패했습니다. 백업을 진행할 수 없습니다.", - "backup_info_card_assets": "항목", - "backup_manual_cancelled": "취소됨", - "backup_manual_in_progress": "업로드가 이미 진행 중입니다. 잠시 후 다시 시도하세요", - "backup_manual_success": "성공", - "backup_manual_title": "업로드 상태", - "backup_options": "백업 옵션", - "backup_options_page_title": "백업 옵션", - "backup_setting_subtitle": "백그라운드 및 포그라운드 백업 설정을 관리합니다.", - "backup_settings_subtitle": "업로드 설정을 관리합니다.", - "backup_upload_details_page_more_details": "탭하여 상세보기", - "backward": "뒤로", - "biometric_auth_enabled": "생체 인증이 활성화되었습니다.", - "biometric_locked_out": "생체 인증이 일시적으로 비활성화되었습니다.", - "biometric_no_options": "사용 가능한 생체 인증 옵션 없음", - "biometric_not_available": "이 기기에서는 생체 인증을 사용할 수 없습니다.", - "birthdate_saved": "생년월일이 저장되었습니다.", - "birthdate_set_description": "생년월일은 사진 촬영 당시 인물의 나이를 계산하는 데 사용됩니다.", - "blurred_background": "흐린 배경", - "bugs_and_feature_requests": "버그 제보 & 기능 요청", - "build": "빌드", - "build_image": "빌드 이미지", - "bulk_delete_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개를} other {#개를}} 삭제하시겠습니까? 각 그룹에서 크기가 가장 큰 항목을 제외한 나머지를 영구적으로 삭제합니다. 이 작업은 되돌릴 수 없습니다!", - "bulk_keep_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개를} other {#개를}} 유지하시겠습니까? 이 작업은 모든 비슷한 항목의 그룹을 삭제 없이 정리합니다.", - "bulk_trash_duplicates_confirmation": "비슷한 항목 {count, plural, one {#개를} other {#개를}} 휴지통으로 이동하시겠습니까? 각 그룹에서 크기가 가장 큰 항목을 제외한 나머지를 휴지통으로 이동합니다.", - "buy": "Immich 구매", - "cache_settings_clear_cache_button": "캐시 지우기", - "cache_settings_clear_cache_button_title": "앱 캐시를 지웁니다. 이 작업은 캐시가 다시 생성될 때까지 앱 성능에 상당한 영향을 미칠 수 있습니다.", - "cache_settings_duplicated_assets_clear_button": "지우기", - "cache_settings_duplicated_assets_subtitle": "제외된 사진 및 동영상", - "cache_settings_duplicated_assets_title": "비슷한 항목 ({count})", - "cache_settings_statistics_album": "라이브러리 섬네일", - "cache_settings_statistics_full": "전체 이미지", - "cache_settings_statistics_shared": "공유 앨범 섬네일", - "cache_settings_statistics_thumbnail": "섬네일", - "cache_settings_statistics_title": "캐시 사용률", - "cache_settings_subtitle": "Immich 모바일 앱의 캐싱 동작 제어", - "cache_settings_tile_subtitle": "로컬 스토리지 동작 제어", - "cache_settings_tile_title": "로컬 스토리지", - "cache_settings_title": "캐시 설정", - "camera": "카메라", - "camera_brand": "카메라 제조사", - "camera_model": "카메라 모델", - "cancel": "닫기", - "cancel_search": "검색 닫기", - "canceled": "중단됨", - "canceling": "취소 중...", - "cannot_merge_people": "인물을 병합할 수 없습니다.", - "cannot_undo_this_action": "이 작업은 되돌릴 수 없습니다!", - "cannot_update_the_description": "설명을 변경할 수 없습니다.", - "cast": "캐스트", - "cast_description": "사용 가능한 캐스트 기기 구성", - "change_date": "날짜 변경", - "change_description": "설명 변경", - "change_display_order": "표시 순서 변경", - "change_expiration_time": "만료일 변경", - "change_location": "위치 변경", - "change_name": "이름 변경", - "change_name_successfully": "이름이 변경되었습니다.", - "change_password": "비밀번호 변경", - "change_password_description": "처음 로그인하거나 비밀번호 초기화 요청이 있습니다. 새 비밀번호를 입력하세요.", - "change_password_form_confirm_password": "현재 비밀번호 입력", - "change_password_form_description": "안녕하세요 {name}님,\n\n처음 로그인하거나 비밀번호 초기화 요청이 있습니다. 새 비밀번호를 입력하세요.", - "change_password_form_log_out": "다른 모든 기기에서 로그아웃", - "change_password_form_log_out_description": "다른 모든 기기에서 로그아웃하는 것이 좋습니다", - "change_password_form_new_password": "새 비밀번호 입력", - "change_password_form_password_mismatch": "비밀번호가 일치하지 않습니다.", - "change_password_form_reenter_new_password": "새 비밀번호 확인", - "change_pin_code": "PIN 코드 변경", - "change_trigger": "트리거 변경", - "change_trigger_prompt": "트리거를 변경하시겠습니까? 이렇게 하면 기존의 모든 액션과 필터가 제거됩니다.", - "change_your_password": "사용자 계정의 비밀번호를 변경합니다.", - "changed_visibility_successfully": "숨김 여부가 변경되었습니다.", - "charging": "충전 중", - "charging_requirement_mobile_backup": "백그라운드 백업은 기기가 충전 중일 때 진행됩니다.", - "check_corrupt_asset_backup": "백업된 항목의 손상 여부 확인", - "check_corrupt_asset_backup_button": "확인 수행", - "check_corrupt_asset_backup_description": "이 검사는 모든 항목이 백업된 후 Wi-Fi가 연결된 상태에서만 실행하세요. 이 작업은 몇 분 정도 소요될 수 있습니다.", - "check_logs": "로그 확인", - "checksum": "체크섬", - "choose_matching_people_to_merge": "병합할 인물 선택", - "city": "도시", - "cleanup_confirm_description": "Immich 서버에서 안전하게 백업된 항목 {count}개({date} 이전에 생성됨)를 찾았습니다. 이 기기에서 로컬 복사복을 삭제하시겠습니까?", - "cleanup_confirm_prompt_title": "이 기기에서 삭제하시겠습니까?", - "cleanup_deleted_assets": "{count}개 항목 휴지통으로 이동됨", - "cleanup_deleting": "휴지통으로 이동 중...", - "cleanup_found_assets": "백업된 {count}개의 항목 찾음", - "cleanup_found_assets_with_size": "백업된 {count}개의 항목 찾음 ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud의 공유 앨범은 스캔 대상에서 제외됩니다", - "cleanup_no_assets_found": "위 조건에 일치하는 항목이 없습니다. 저장 공간 확보 기능은 서버에 백업된 항목만 삭제할 수 있습니다.", - "cleanup_preview_title": "삭제 대상 항목 ({count})", - "cleanup_step3_description": "백업된 항목에서 날짜 및 보존 설정과 일치하는 항목을 스캔합니다.", - "cleanup_step4_summary": "({date} 이전에 생성된) {count} 항목이 로컬 기기에서 삭제됩니다. 사진은 Immich 앱에서 계속 액세스할 수 있습니다.", - "cleanup_trash_hint": "저장 공간을 완전히 확보하려면 시스템의 갤러리 앱을 열고 휴지통을 비우세요", - "clear": "지우기", - "clear_all": "모두 지우기", - "clear_all_recent_searches": "검색 기록 전체 삭제", - "clear_file_cache": "파일 캐시 지우기", - "clear_message": "메시지 지우기", - "clear_value": "값 지우기", - "client_cert_dialog_msg_confirm": "확인", - "client_cert_enter_password": "비밀번호 입력", - "client_cert_import": "가져오기", - "client_cert_import_success_msg": "클라이언트 인증서 가져오기 완료", - "client_cert_invalid_msg": "인증서가 유효하지 않거나 비밀번호가 올바르지 않음", - "client_cert_remove_msg": "클라이언트 인증서 제거됨", - "client_cert_subtitle": "인증서 가져오기/제거는 로그인 전에만 가능하며, PKCS12 (.p12, .pfx) 형식만 지원합니다.", - "client_cert_title": "SSL 클라이언트 인증서 (실험적)", - "clockwise": "시계 방향", - "close": "닫기", - "collapse": "접기", - "collapse_all": "모두 접기", - "color": "색상", - "color_theme": "테마 색상", - "command": "명령", - "comment_deleted": "댓글이 삭제되었습니다.", - "comment_options": "댓글 옵션", - "comments_and_likes": "댓글 및 좋아요", - "comments_are_disabled": "댓글이 비활성화되었습니다.", - "common_create_new_album": "앨범 생성", - "completed": "완료됨", - "confirm": "확인", - "confirm_admin_password": "관리자 비밀번호 확인", - "confirm_delete_face": "항목에서 {name}의 얼굴을 삭제하시겠습니까?", - "confirm_delete_shared_link": "이 공유 링크를 삭제하시겠습니까?", - "confirm_keep_this_delete_others": "이 항목을 제외한 스택의 나머지 항목이 모두 삭제됩니다. 계속하시겠습니까?", - "confirm_new_pin_code": "새 PIN 코드 확인", - "confirm_password": "비밀번호 확인", - "confirm_tag_face": "이 얼굴을 {name}로 태그하시겠습니까?", - "confirm_tag_face_unnamed": "이 얼굴에 태그하시겠습니까?", - "connected_device": "연결된 기기", - "connected_to": "연결됨:", - "contain": "맞춤", - "context": "문맥", - "continue": "계속", - "control_bottom_app_bar_create_new_album": "앨범 생성", - "control_bottom_app_bar_delete_from_immich": "Immich에서 삭제", - "control_bottom_app_bar_delete_from_local": "기기에서 삭제", - "control_bottom_app_bar_edit_location": "위치 편집", - "control_bottom_app_bar_edit_time": "날짜 변경", - "control_bottom_app_bar_share_link": "공유 링크", - "control_bottom_app_bar_share_to": "공유 대상", - "control_bottom_app_bar_trash_from_immich": "휴지통", - "copied_image_to_clipboard": "이미지가 클립보드에 복사되었습니다.", - "copied_to_clipboard": "클립보드에 복사되었습니다!", - "copy_error": "오류 복사", - "copy_file_path": "파일 경로 복사", - "copy_image": "이미지 복사", - "copy_link": "링크 복사", - "copy_link_to_clipboard": "링크를 클립보드에 복사", - "copy_password": "비밀번호 복사", - "copy_to_clipboard": "클립보드에 복사", - "country": "국가", - "cover": "확대", - "covers": "타일", - "create": "생성", - "create_album": "앨범 생성", - "create_album_page_untitled": "제목 없음", - "create_api_key": "API 키 생성", - "create_first_workflow": "첫 번째 워크플로를 생성합니다", - "create_library": "새 라이브러리", - "create_link": "링크 생성", - "create_link_to_share": "공유 링크 생성", - "create_link_to_share_description": "링크가 있는 경우 누구나 선택한 사진을 볼 수 있습니다.", - "create_new": "새로 만들기", - "create_new_person": "인물 생성", - "create_new_person_hint": "선택한 항목의 인물을 새 인물로 변경", - "create_new_user": "새 사용자 생성", - "create_shared_album_page_share_add_assets": "항목 추가", - "create_shared_album_page_share_select_photos": "사진 선택", - "create_shared_link": "공유 링크 생성", - "create_tag": "태그 생성", - "create_tag_description": "새 태그를 생성합니다. 하위 태그의 경우 /를 포함한 전체 태그명을 입력하세요.", - "create_user": "사용자 계정 생성", - "create_workflow": "워크플로 생성", - "created": "생성됨", - "created_at": "생성됨", - "creating_linked_albums": "연결된 앨범 생성 중...", - "crop": "자르기", - "crop_aspect_ratio_fixed": "고정", - "crop_aspect_ratio_free": "직접 조절", - "crop_aspect_ratio_original": "원본", - "curated_object_page_title": "사물", - "current_device": "현재 기기", - "current_pin_code": "현재 PIN 코드", - "current_server_address": "현재 서버 주소", - "custom_date": "날짜 선택", - "custom_locale": "사용자 지정 로케일", - "custom_locale_description": "언어 및 지역에 따른 날짜 및 숫자 형식 지정", - "custom_url": "사용자 지정 URL", - "cutoff_date_description": "선택한 기간의 사진을 유지합니다…", - "cutoff_day": "{count, plural, one {일} other {일}}", - "cutoff_year": "{count, plural, one {년} other {년}}", - "daily_title_text_date": "M월 d일 EEEE", - "daily_title_text_date_year": "yyyy년 M월 d일 EEEE", - "dark": "다크", - "dark_theme": "다크 테마 토글", - "date": "날짜", - "date_after": "다음 날짜 이후", - "date_and_time": "날짜 및 시간", - "date_before": "다음 날짜 전", - "date_format": "yyyy년 M월 d일 EEEE • a h:mm", - "date_of_birth_saved": "생년월일이 저장되었습니다.", - "date_range": "날짜 범위", - "day": "일", - "days": "일", - "deduplicate_all": "모두 삭제", - "deduplication_criteria_1": "이미지 크기 (바이트)", - "deduplication_criteria_2": "EXIF 정보 항목 수", - "deduplication_info": "비슷한 항목 정보", - "deduplication_info_description": "항목을 자동으로 미리 선택하고, 비슷한 항목을 구분할 때 다음 정보를 참고합니다:", - "default_locale": "기본 로케일", - "default_locale_description": "브라우저 로케일에 따른 날짜 및 숫자 형식 지정", - "delete": "삭제", - "delete_action_confirmation_message": "이 항목을 삭제하시겠습니까? 서버에서는 항목을 휴지통으로 이동시키며, 로컬에서도 삭제할 것인지 확인 메시지가 표시됩니다.", - "delete_action_prompt": "{count}개 항목 삭제됨", - "delete_album": "앨범 삭제", - "delete_api_key_prompt": "API 키를 삭제하시겠습니까?", - "delete_dialog_alert": "이 항목들이 Immich와 기기에서 영구적으로 삭제됩니다.", - "delete_dialog_alert_local": "이 항목들이 기기에서 영구적으로 삭제됩니다. 서버에서는 삭제되지 않습니다.", - "delete_dialog_alert_local_non_backed_up": "일부 항목이 서버에 백업되지 않았으며, 기기에서 영구적으로 삭제됩니다.", - "delete_dialog_alert_remote": "이 항목들이 서버에서 영구적으로 삭제됩니다.", - "delete_dialog_ok_force": "무시하고 삭제", - "delete_dialog_title": "영구적으로 삭제", - "delete_duplicates_confirmation": "비슷한 항목들을 영구적으로 삭제하시겠습니까?", - "delete_face": "얼굴 삭제", - "delete_key": "키 삭제", - "delete_library": "라이브러리 삭제", - "delete_link": "링크 삭제", - "delete_local_action_prompt": "기기에서 {count}개 항목 삭제됨", - "delete_local_dialog_ok_backed_up_only": "백업된 항목만 삭제", - "delete_local_dialog_ok_force": "무시하고 삭제", - "delete_others": "다른 인물 삭제", - "delete_permanently": "영구 삭제", - "delete_permanently_action_prompt": "{count}개 항목이 영구적으로 삭제됨", - "delete_shared_link": "공유 링크 삭제", - "delete_shared_link_dialog_title": "공유 링크 삭제", - "delete_tag": "태그 삭제", - "delete_tag_confirmation_prompt": "{tagName} 태그를 삭제하시겠습니까?", - "delete_user": "사용자 삭제", - "deleted_shared_link": "공유 링크가 삭제되었습니다.", - "deletes_missing_assets": "디스크에 존재하지 않는 항목 제거", - "description": "설명", - "description_input_hint_text": "설명 추가...", - "description_input_submit_error": "설명 변경 중 오류가 발생했습니다. 로그에서 자세한 내용을 확인하세요.", - "deselect_all": "모두 선택 해제", - "details": "상세 정보", - "direction": "방향", - "disable": "비활성화", - "disabled": "비활성화", - "disallow_edits": "뷰어로 설정", - "discord": "Discord", - "discover": "탐색", - "discovered_devices": "주변 기기", - "dismiss_all_errors": "모든 오류 무시", - "dismiss_error": "오류 무시", - "display_options": "표시 옵션", - "display_order": "표시 순서", - "display_original_photos": "원본 사진 표시", - "display_original_photos_setting_description": "항목을 표시할 때 웹과 호환되는 경우 원본을 표시합니다. 사진 표시 속도가 느려질 수 있습니다.", - "do_not_show_again": "이 메시지를 다시 표시하지 않음", - "documentation": "문서", - "done": "완료", - "download": "다운로드", - "download_action_prompt": "항목 {count}개 다운로드 중...", - "download_canceled": "다운로드가 취소되었습니다.", - "download_complete": "다은로드가 완료되었습니다.", - "download_enqueue": "대기열에 다운로드", - "download_error": "다운로드 오류", - "download_failed": "다운로드 실패", - "download_finished": "다운로드가 완료되었습니다.", - "download_include_embedded_motion_videos": "모션 포토 영상", - "download_include_embedded_motion_videos_description": "모션 포토에 포함된 동영상을 별도의 파일로 분리해 저장합니다.", - "download_notfound": "다운로드할 수 없음", - "download_original": "원본 다운로드", - "download_paused": "다운로드 일시 중지됨", - "download_settings": "다운로드", - "download_settings_description": "파일 다운로드 설정을 관리합니다.", - "download_started": "다운로드가 시작되었습니다.", - "download_sucess": "다운로드가 완료되었습니다.", - "download_sucess_android": "항목이 DCIM/Immich 폴더에 다운로드되었습니다.", - "download_waiting_to_retry": "재시도 대기 중", - "downloading": "다운로드", - "downloading_asset_filename": "{filename} 다운로드 중...", - "downloading_from_icloud": "iCloud에서 다운로드 중", - "downloading_media": "미디어 다운로드 중", - "drop_files_to_upload": "아무 곳에나 파일을 드롭하여 업로드", - "duplicates": "비슷한 항목", - "duplicates_description": "각 그룹을 확인하고, 비슷한 항목을 선택해 삭제 여부를 결정하세요.", - "duration": "기간", - "edit": "편집", - "edit_album": "앨범 수정", - "edit_avatar": "프로필 수정", - "edit_birthday": "생년월일 수정", - "edit_date": "날짜 변경", - "edit_date_and_time": "날짜 및 시간 변경", - "edit_date_and_time_action_prompt": "항목 {count}개 날짜가 변경됨", - "edit_date_and_time_by_offset": "날짜를 오프셋으로 변경", - "edit_date_and_time_by_offset_interval": "새 날짜 범위: {from} - {to}", - "edit_description": "설명 편집", - "edit_description_prompt": "새 설명을 입력하세요:", - "edit_exclusion_pattern": "제외 규칙 수정", - "edit_faces": "얼굴 수정", - "edit_key": "키 수정", - "edit_link": "링크 수정", - "edit_location": "위치 변경", - "edit_location_action_prompt": "항목 {count}개의 위치가 변경됨", - "edit_location_dialog_title": "위치", - "edit_name": "이름 변경", - "edit_people": "인물 수정", - "edit_tag": "태그 수정", - "edit_title": "제목 변경", - "edit_user": "사용자 수정", - "edit_workflow": "워크플로 편집", - "editor": "편집기", - "editor_close_without_save_prompt": "변경 사항이 저장되지 않습니다.", - "editor_close_without_save_title": "편집을 종료하시겠습니까?", - "editor_confirm_reset_all_changes": "모든 수정사항을 초기화하시겠습니까?", - "editor_flip_horizontal": "좌우반전", - "editor_flip_vertical": "상하반전", - "editor_orientation": "방향", - "editor_reset_all_changes": "편집내용 초기화", - "editor_rotate_left": "반시계 방향으로 90° 회전", - "editor_rotate_right": "시계 방향으로 90° 회전", - "email": "이메일", - "email_notifications": "이메일 알림", - "empty_folder": "폴더가 비어 있음", - "empty_trash": "휴지통 비우기", - "empty_trash_confirmation": "휴지통을 비우시겠습니까? 휴지통에 있는 모든 항목이 Immich에서 영구적으로 삭제됩니다.\n이 작업은 되돌릴 수 없습니다!", - "enable": "활성화", - "enable_backup": "백업 활성화", - "enable_biometric_auth_description": "PIN 코드를 입력해 생체 인증을 활성화하세요", - "enabled": "활성화됨", - "end_date": "종료일", - "enqueued": "대기열에 추가됨", - "enter_wifi_name": "Wi-Fi 이름 입력", - "enter_your_pin_code": "PIN 코드 입력", - "enter_your_pin_code_subtitle": "잠금 폴더에 접근하려면 PIN 코드를 입력하세요.", - "error": "오류", - "error_change_sort_album": "앨범 표시 순서 변경 실패", - "error_delete_face": "항목에서 얼굴 삭제 중 오류 발생", - "error_getting_places": "장소 로딩 오류", - "error_loading_albums": "앨범 로딩 오류", - "error_loading_image": "이미지 로딩 오류", - "error_loading_partners": "파트너 로딩 오류: {error}", - "error_saving_image": "오류: {error}", - "error_tag_face_bounding_box": "얼굴 태그 실패 - 얼굴의 위치를 가져올 수 없습니다.", - "error_title": "오류 - 문제가 발생했습니다", - "errors": { - "cannot_navigate_next_asset": "다음 항목으로 이동할 수 없습니다.", - "cannot_navigate_previous_asset": "이전 항목으로 이동할 수 없습니다.", - "cant_apply_changes": "변경 사항을 적용할 수 없습니다.", - "cant_change_activity": "활동을 {enabled, select, true {비활성화} other {활성화}}할 수 없습니다.", - "cant_change_asset_favorite": "즐겨찾기를 변경할 수 없습니다.", - "cant_change_metadata_assets_count": "항목 {count, plural, one {#개} other {#개}}의 메타데이터를 변경할 수 없습니다.", - "cant_get_faces": "얼굴을 불러올 수 없음", - "cant_get_number_of_comments": "댓글 수를 불러올 수 없음", - "cant_search_people": "인물을 검색할 수 없음", - "cant_search_places": "장소를 검색할 수 없음", - "error_adding_assets_to_album": "앨범에 항목 추가 중 오류 발생", - "error_adding_users_to_album": "앨범에 사용자 추가 중 오류 발생", - "error_deleting_shared_user": "공유된 사용자 삭제 중 오류 발생", - "error_downloading": "{filename} 다운로드 오류", - "error_hiding_buy_button": "구매 버튼 숨김 중 오류 발생", - "error_removing_assets_from_album": "앨범에서 항목 제거 중 오류가 발생했습니다. 콘솔에서 세부 정보를 확인하세요.", - "error_selecting_all_assets": "모든 항목 선택 중 오류 발생", - "exclusion_pattern_already_exists": "이 제외 규칙은 이미 존재합니다.", - "failed_to_create_album": "앨범을 생성하지 못했습니다.", - "failed_to_create_shared_link": "공유 링크를 생성하지 못했습니다.", - "failed_to_edit_shared_link": "공유 링크를 수정하지 못했습니다.", - "failed_to_get_people": "인물 로드 실패", - "failed_to_keep_this_delete_others": "이 항목을 유지하고 다른 항목 삭제에 실패했습니다.", - "failed_to_load_asset": "항목 로드 실패", - "failed_to_load_assets": "항목 로드 실패", - "failed_to_load_notifications": "알림 로드 실패", - "failed_to_load_people": "인물 로드 실패", - "failed_to_remove_product_key": "제품 키 제거에 실패했습니다.", - "failed_to_reset_pin_code": "PIN 코드 초기화 실패", - "failed_to_stack_assets": "항목 스택에 실패했습니다.", - "failed_to_unstack_assets": "항목 스택 풀기에 실패했습니다.", - "failed_to_update_notification_status": "알림 상태 업데이트 실패", - "incorrect_email_or_password": "잘못된 이메일 또는 비밀번호", - "library_folder_already_exists": "가져올 경로가 이미 존재합니다.", - "paths_validation_failed": "{paths, plural, one {경로 #개} other {경로 #개}}가 유효성 검사에 실패했습니다.", - "profile_picture_transparent_pixels": "프로필 사진에 투명 픽셀을 사용할 수 없습니다. 사진을 확대하거나 이동하세요.", - "quota_higher_than_disk_size": "할당량은 디스크 크기보다 작아야 합니다.", - "something_went_wrong": "문제가 발생했습니다.", - "unable_to_add_album_users": "앨범에 사용자를 추가할 수 없습니다.", - "unable_to_add_assets_to_shared_link": "항목을 공유 링크에 추가할 수 없습니다.", - "unable_to_add_comment": "댓글을 추가할 수 없습니다.", - "unable_to_add_exclusion_pattern": "제외 규칙을 추가할 수 없습니다.", - "unable_to_add_partners": "파트너를 추가할 수 없습니다.", - "unable_to_add_remove_archive": "{archived, select, true {보관함에서 항목을 제거할} other {보관함으로 항목을 이동할}} 수 없습니다.", - "unable_to_add_remove_favorites": "즐겨찾기에 항목을 {favorite, select, true {추가} other {제거}}할 수 없습니다", - "unable_to_archive_unarchive": "항목을 {archived, select, true {보관} other {보관 해제}}할 수 없습니다", - "unable_to_change_album_user_role": "앨범 사용자의 역할을 변경할 수 없습니다.", - "unable_to_change_date": "날짜를 변경할 수 없습니다.", - "unable_to_change_description": "설명을 변경할 수 없습니다.", - "unable_to_change_favorite": "항목의 즐겨찾기를 변경할 수 없습니다.", - "unable_to_change_location": "위치를 변경할 수 없습니다.", - "unable_to_change_password": "비밀번호를 변경할 수 없습니다.", - "unable_to_change_visibility": "인물 {count, plural, one {#명} other {#명}}의 표시 여부를 변경할 수 없음", - "unable_to_complete_oauth_login": "OAuth 로그인을 완료할 수 없습니다.", - "unable_to_connect": "연결할 수 없음", - "unable_to_copy_to_clipboard": "클립보드에 복사할 수 없습니다. HTTPS로 접속 중인지 확인하세요.", - "unable_to_create": "워크플로를 생성할 수 없습니다", - "unable_to_create_admin_account": "관리자 계정을 생성할 수 없습니다.", - "unable_to_create_api_key": "새 API 키를 생성할 수 없습니다.", - "unable_to_create_library": "라이브러리를 생성할 수 없습니다.", - "unable_to_create_user": "사용자 계정을 생성할 수 없음", - "unable_to_delete_album": "앨범을 삭제할 수 없습니다.", - "unable_to_delete_asset": "항목을 삭제할 수 없습니다.", - "unable_to_delete_assets": "항목 삭제 중 오류 발생", - "unable_to_delete_exclusion_pattern": "제외 규칙을 삭제할 수 없습니다.", - "unable_to_delete_shared_link": "공유 링크를 삭제할 수 없습니다.", - "unable_to_delete_user": "사용자를 삭제할 수 없습니다.", - "unable_to_delete_workflow": "워크플로를 삭제할 수 없습니다", - "unable_to_download_files": "파일을 다운로드할 수 없습니다.", - "unable_to_edit_exclusion_pattern": "제외 규칙을 수정할 수 없습니다.", - "unable_to_empty_trash": "휴지통을 비울 수 없습니다.", - "unable_to_enter_fullscreen": "전체 화면으로 전환할 수 없습니다.", - "unable_to_exit_fullscreen": "전체 화면을 종료할 수 없습니다.", - "unable_to_get_comments_number": "댓글 수를 불러올 수 없습니다.", - "unable_to_get_shared_link": "공유 링크를 불러오지 못했습니다.", - "unable_to_hide_person": "인물을 숨길 수 없습니다.", - "unable_to_link_motion_video": "모션 비디오를 연결할 수 없습니다", - "unable_to_link_oauth_account": "OAuth 계정을 연결할 수 없습니다.", - "unable_to_log_out_all_devices": "모든 기기에서 로그아웃할 수 없습니다.", - "unable_to_log_out_device": "기기에서 로그아웃할 수 없습니다.", - "unable_to_login_with_oauth": "OAuth로 로그인할 수 없습니다.", - "unable_to_play_video": "동영상을 재생할 수 없습니다.", - "unable_to_reassign_assets_existing_person": "{name, select, null {다른 인물} other {{name}}}에게 항목을 재할당할 수 없습니다.", - "unable_to_reassign_assets_new_person": "새 인물에게 항목을 재할당할 수 없습니다.", - "unable_to_refresh_user": "사용자를 새로고침할 수 없습니다.", - "unable_to_remove_album_users": "앨범에서 사용자를 제거할 수 없습니다.", - "unable_to_remove_api_key": "API 키를 제거할 수 없습니다.", - "unable_to_remove_assets_from_shared_link": "공유 링크에서 항목을 제거할 수 없습니다.", - "unable_to_remove_library": "라이브러리를 제거할 수 없습니다.", - "unable_to_remove_partner": "파트너를 제거할 수 없습니다.", - "unable_to_remove_reaction": "반응을 제거할 수 없습니다.", - "unable_to_reset_password": "비밀번호를 초기화할 수 없습니다.", - "unable_to_reset_pin_code": "PIN 코드를 초기화할 수 없음", - "unable_to_resolve_duplicate": "비슷한 항목을 처리할 수 없음", - "unable_to_restore_assets": "항목을 복원할 수 없습니다.", - "unable_to_restore_trash": "휴지통을 복원할 수 없습니다.", - "unable_to_restore_user": "사용자를 복원할 수 없습니다.", - "unable_to_save_album": "앨범을 저장할 수 없습니다.", - "unable_to_save_api_key": "API 키를 저장할 수 없습니다.", - "unable_to_save_date_of_birth": "생년월일을 저장할 수 없습니다.", - "unable_to_save_name": "이름을 저장할 수 없습니다.", - "unable_to_save_profile": "프로필을 저장할 수 없습니다.", - "unable_to_save_settings": "설정을 저장할 수 없습니다.", - "unable_to_scan_libraries": "라이브러리를 스캔할 수 없습니다.", - "unable_to_scan_library": "라이브러리를 스캔할 수 없습니다.", - "unable_to_set_feature_photo": "대표 사진을 설정할 수 없습니다.", - "unable_to_set_profile_picture": "프로필 사진을 설정할 수 없습니다.", - "unable_to_submit_job": "작업을 수행할 수 없습니다.", - "unable_to_trash_asset": "휴지통으로 이동할 수 없습니다.", - "unable_to_unlink_account": "계정 연결을 해제할 수 없습니다.", - "unable_to_unlink_motion_video": "모션 비디오 연결을 해제할 수 없습니다.", - "unable_to_update_album_cover": "앨범 커버를 변경할 수 없습니다.", - "unable_to_update_album_info": "앨범 정보를 변경할 수 없습니다.", - "unable_to_update_library": "라이브러리를 업데이트할 수 없습니다.", - "unable_to_update_location": "위치를 변경할 수 없습니다.", - "unable_to_update_settings": "설정을 변경할 수 없습니다.", - "unable_to_update_timeline_display_status": "타임라인 표시 상태를 변경할 수 없습니다.", - "unable_to_update_user": "사용자를 업데이트할 수 없습니다.", - "unable_to_update_workflow": "워크플로를 업데이트할 수 없습니다", - "unable_to_upload_file": "파일을 업로드할 수 없습니다." - }, - "exclusion_pattern": "제외 규칙", - "exif": "EXIF", - "exif_bottom_sheet_description": "설명 추가...", - "exif_bottom_sheet_description_error": "설명 변경 중 오류 발생", - "exif_bottom_sheet_details": "상세 정보", - "exif_bottom_sheet_location": "위치", - "exif_bottom_sheet_no_description": "설명 없음", - "exif_bottom_sheet_people": "인물", - "exif_bottom_sheet_person_add_person": "이름 추가", - "exit_slideshow": "슬라이드 쇼 종료", - "expand_all": "모두 확장", - "experimental_settings_new_asset_list_subtitle": "진행 중", - "experimental_settings_new_asset_list_title": "새 사진 배열 사용 (실험적)", - "experimental_settings_subtitle": "본인 책임 하에 사용하세요!", - "experimental_settings_title": "실험적", - "expire_after": "만료일 설정", - "expired": "만료됨", - "expires_date": "{date} 만료", - "explore": "탐색", - "explorer": "탐색기", - "export": "내보내기", - "export_as_json": "JSON으로 내보내기", - "export_database": "데이터베이스 내보내기", - "export_database_description": "SQLite 데이터베이스 내보내기", - "extension": "확장자", - "external": "외부", - "external_libraries": "외부 라이브러리", - "external_network": "외부 네트워크", - "external_network_sheet_info": "선호하는 Wi-Fi 네트워크에 연결되어 있지 않은 경우, 앱은 아래 나열된 URL 중 위에서부터 순서대로 사용 가능한 첫 번째 URL을 사용하여 연결합니다.", - "face_unassigned": "알 수 없음", - "failed": "실패함", - "failed_count": "실패: {count}", - "failed_to_authenticate": "인증에 실패했습니다.", - "failed_to_load_assets": "항목 로드 실패", - "failed_to_load_folder": "폴더 로드 실패", - "favorite": "즐겨찾기", - "favorite_action_prompt": "즐겨찾기에 {count}개 항목 추가됨", - "favorite_or_unfavorite_photo": "즐겨찾기 추가/제거", - "favorites": "즐겨찾기", - "favorites_page_no_favorites": "즐겨찾기된 항목 없음", - "feature_photo_updated": "대표 사진 업데이트됨", - "features": "기능", - "features_in_development": "개발 중인 기능", - "features_setting_description": "사진 및 동영상 관리 기능을 설정합니다.", - "file_name_or_extension": "파일명 또는 확장자", - "file_size": "파일 크기", - "filename": "파일명", - "filetype": "파일 형식", - "filter": "필터", - "filter_description": "대상 자산을 필터링하기 위한 조건", - "filter_people": "인물 필터", - "filter_places": "장소 필터", - "filters": "필터", - "find_them_fast": "이름으로 검색하여 빠르게 찾기", - "first": "첫 번째", - "fix_incorrect_match": "잘못된 분류 수정", - "folder": "폴더", - "folder_not_found": "폴더를 찾을 수 없음", - "folders": "폴더", - "folders_feature_description": "파일 시스템의 사진과 동영상을 폴더 보기로 탐색합니다.", - "forgot_pin_code_question": "PIN 번호를 잊어버렸나요?", - "forward": "앞으로", - "free_up_space": "저장 공간 확보", - "free_up_space_description": "백업된 사진과 동영상을 기기의 휴지통으로 이동하여 저장 공간을 확보하세요. 원본 파일은 서버에 안전하게 보관됩니다.", - "free_up_space_settings_subtitle": "기기의 저장 공간을 확보합니다.", - "full_path": "전체 경로: {path}", - "gcast_enabled": "구글 캐스트", - "gcast_enabled_description": "이 기능은 Google의 외부 리소스를 사용합니다.", - "general": "일반", - "geolocation_instruction_location": "GPS 좌표가 포함된 항목을 클릭해 위치를 사용하거나, 지도에서 직접 위치를 선택하세요.", - "get_help": "도움 얻기", - "get_people_error": "사람들을 불러오는 데 오류가 발생했습니다", - "get_wifiname_error": "Wi-Fi 이름을 가져올 수 없습니다. 필수 권한이 부여되었는지, Wi-Fi 네트워크에 연결되어 있는지 확인하세요.", - "getting_started": "시작하기", - "go_back": "뒤로", - "go_to_folder": "폴더로 이동", - "go_to_search": "검색으로 이동", - "gps": "GPS", - "gps_missing": "GPS 없음", - "grant_permission": "권한 부여", - "group_albums_by": "다음으로 앨범 그룹화...", - "group_country": "국가별로 그룹화", - "group_no": "그룹화 없음", - "group_owner": "소유자로 그룹화", - "group_places_by": "다음으로 장소 그룹화…", - "group_year": "연도로 그룹화", - "haptic_feedback_switch": "햅틱 피드백 활성화", - "haptic_feedback_title": "햅틱 피드백", - "has_quota": "할당량", - "hash_asset": "항목 해싱", - "hashed_assets": "해시된 항목", - "hashing": "해싱 중...", - "header_settings_add_header_tip": "헤더 추가", - "header_settings_field_validator_msg": "값은 비워둘 수 없습니다.", - "header_settings_header_name_input": "헤더 이름", - "header_settings_header_value_input": "헤더 값", - "headers_settings_tile_title": "사용자 지정 프록시 헤더", - "height": "높이", - "hi_user": "안녕하세요 {name}님, ({email})", - "hide_all_people": "모든 인물 숨기기", - "hide_gallery": "갤러리 숨기기", - "hide_named_person": "인물 {name} 숨기기", - "hide_password": "비밀번호 숨기기", - "hide_person": "인물 숨기기", - "hide_schema": "스키마 숨기기", - "hide_text_recognition": "텍스트 인식 숨기기", - "hide_unnamed_people": "이름 없는 인물 숨기기", - "home_page_add_to_album_conflicts": "{album} 앨범에 항목 {added}개가 추가되었습니다. 항목 {failed}개는 앨범에 이미 존재합니다.", - "home_page_add_to_album_err_local": "로컬 항목은 앨범에 추가할 수 없어 건너뜁니다.", - "home_page_add_to_album_success": "{album} 앨범에 항목 {added}개가 추가되었습니다.", - "home_page_album_err_partner": "파트너 항목은 앨범에 추가할 수 없어 건너뜁니다.", - "home_page_archive_err_local": "로컬 항목은 보관할 수 없어 건너뜁니다.", - "home_page_archive_err_partner": "파트너 항목은 보관할 수 없어 건너뜁니다.", - "home_page_building_timeline": "타임라인 구성 중", - "home_page_delete_err_partner": "파트너 항목은 삭제할 수 없어 건너뜁니다.", - "home_page_delete_remote_err_local": "서버에서 삭제 대상에 포함된 로컬 항목은 건너뜁니다.", - "home_page_favorite_err_local": "로컬 항목은 즐겨찾기할 수 없어 건너뜁니다.", - "home_page_favorite_err_partner": "파트너 항목은 즐겨찾기할 수 없어 건너뜁니다.", - "home_page_first_time_notice": "앱을 처음 사용하는 경우, 기기에 있는 사진과 동영상을 타임라인에 표시하고 백업하려면 백업할 앨범을 선택하세요.", - "home_page_locked_error_local": "로컬 항목은 잠금 폴더로 이동할 수 없습니다. 생략됨", - "home_page_locked_error_partner": "파트너 항목은 잠금 폴더로 이동할 수 없습니다. 생략됨", - "home_page_share_err_local": "로컬 항목은 링크로 공유할 수 없어 건너뜁니다.", - "home_page_upload_err_limit": "한 번에 최대 30개의 항목까지 업로드할 수 있습니다.", - "host": "호스트", - "hour": "시간", - "hours": "시간", - "id": "ID", - "idle": "유휴", - "ignore_icloud_photos": "iCloud 사진 제외", - "ignore_icloud_photos_description": "iCloud에 저장된 사진이 Immich에 업로드되지 않습니다.", - "image": "이미지", - "image_alt_text_date": "{date} 촬영한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_1_person": "{date} {person1}님과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_2_people": "{date} {person1}, {person2}님과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_3_people": "{date} {person1}, {person2}, {person3}님과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_4_or_more_people": "{date} {person1}, {person2}님 및 {additionalCount, number}명과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_place": "{date} {country}, {city}에서 촬영한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_place_1_person": "{date} {country}, {city}에서 {person1}님과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_place_2_people": "{date} {country}, {city}에서 {person1}, {person2}님과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_place_3_people": "{date} {country}, {city}에서 {person1}, {person2}님 및 {person3}님과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_alt_text_date_place_4_or_more_people": "{date} {country}, {city}에서 {person1}, {person2}님 및 {additionalCount, number}명과 함께한 {isVideo, select, true {동영상} other {사진}}", - "image_saved_successfully": "이미지가 저장되었습니다.", - "image_viewer_page_state_provider_download_started": "다운로드가 시작되었습니다.", - "image_viewer_page_state_provider_download_success": "다운로드 완료", - "image_viewer_page_state_provider_share_error": "공유 오류", - "immich_logo": "Immich 로고", - "immich_web_interface": "Immich 웹 인터페이스", - "import_from_json": "JSON에서 가져오기", - "import_path": "가져올 경로", - "in_albums": "포함된 앨범 {count, plural, one {#개} other {#개}}", - "in_archive": "보관된 항목", - "in_year": "{year}년도", - "in_year_selector": "안에", - "include_archived": "보관된 항목 포함", - "include_shared_albums": "공유 앨범 포함", - "include_shared_partner_assets": "파트너가 공유한 항목 포함", - "individual_share": "개인 공유", - "individual_shares": "개별 공유", - "info": "정보", - "interval": { - "day_at_onepm": "매일 오후 1시", - "hours": "매 {hours, plural, one {시간} other {{hours, number}시간}}", - "night_at_midnight": "매일 밤 자정", - "night_at_twoam": "매일 새벽 2시" - }, - "invalid_date": "유효하지 않은 날짜", - "invalid_date_format": "유효하지 않은 날짜 형식", - "invite_people": "사용자 초대", - "invite_to_album": "앨범으로 초대", - "ios_debug_info_fetch_ran_at": "데이터 불러오기 작업이 {dateTime}에 실행됨", - "ios_debug_info_last_sync_at": "마지막 동기화: {dateTime}", - "ios_debug_info_no_processes_queued": "대기 중인 백그라운드 작업 없음", - "ios_debug_info_no_sync_yet": "백그라운드 동기화 작업이 아직 실행되지 않음", - "ios_debug_info_processes_queued": "{count, plural, one {백그라운드 작업 {count}개 대기 중} other {백그라운드 작업 {count}개 대기 중}}", - "ios_debug_info_processing_ran_at": "{dateTime}에 처리됨", - "items_count": "{count, plural, one {#개} other {#개}} 항목", - "jobs": "작업", - "json_editor": "JSON 편집기", - "json_error": "JSON 오류", - "keep": "유지", - "keep_albums": "앨범 유지", - "keep_albums_count": "{count}개의 {count, plural, one {앨범} other {앨범}} 유지", - "keep_all": "모두 유지", - "keep_description": "저장 공간 확보시에 유지할 항목을 선택하세요.", - "keep_favorites": "즐겨찾기 유지", - "keep_on_device": "기기에 유지", - "keep_on_device_hint": "이 기기에 유지할 항목을 선택합니다", - "keep_this_delete_others": "이 항목은 유지하고 나머지는 삭제", - "keeping": "유지: {items}", - "kept_this_deleted_others": "이 항목을 유지하고 {count, plural, one {#개의 항목} other {#개의 항목}}을 삭제함", - "keyboard_shortcuts": "키보드 단축키", - "language": "언어", - "language_no_results_subtitle": "다른 검색어를 사용해 보세요.", - "language_no_results_title": "결과 없음", - "language_search_hint": "언어 검색...", - "language_setting_description": "사용할 언어를 선택하세요.", - "large_files": "큰 파일", - "last": "마지막", - "last_months": "{count, plural, one {지난 1개월} other {지난#개월}}", - "last_seen": "최근 활동", - "latest_version": "최신 버전", - "latitude": "위도", - "leave": "나가기", - "leave_album": "앨범에서 나가기", - "lens_model": "카메라 렌즈 모델", - "let_others_respond": "다른 사용자의 반응 허용", - "level": "레벨", - "library": "라이브러리", - "library_add_folder": "폴더 추가", - "library_edit_folder": "폴더 수정", - "library_options": "라이브러리 옵션", - "library_page_device_albums": "기기의 앨범", - "library_page_new_album": "앨범 생성", - "library_page_sort_asset_count": "항목 수", - "library_page_sort_created": "만든 날짜", - "library_page_sort_last_modified": "마지막 수정", - "library_page_sort_title": "앨범명", - "licenses": "라이선스", - "light": "라이트", - "like": "좋아요", - "like_deleted": "좋아요가 삭제되었습니다.", - "link_motion_video": "모션 비디오 링크", - "link_to_oauth": "OAuth에 연결", - "linked_oauth_account": "OAuth 계정이 연결되었습니다.", - "list": "목록", - "loading": "로드 중", - "loading_search_results_failed": "검색 결과 로드 실패", - "local": "로컬", - "local_asset_cast_failed": "서버에 업로드되지 않은 항목을 캐스팅할 수 없음", - "local_assets": "로컬 항목", - "local_id": "로컬 ID", - "local_media_summary": "로컬 미디어 요약", - "local_network": "로컬 네트워크", - "local_network_sheet_info": "지정된 Wi-Fi를 사용할 때 앱이 아래 URL로 서버에 연결합니다.", - "location": "위치", - "location_permission": "위치 권한", - "location_permission_content": "자동 전환 기능을 사용하려면 Immich가 현재 연결된 Wi-Fi 네트워크 이름을 확인해야 하며, 이를 위해 '정확한 위치' 권한이 필요합니다.", - "location_picker_choose_on_map": "지도에서 선택", - "location_picker_latitude_error": "유효한 위도를 입력하세요.", - "location_picker_latitude_hint": "여기에 위도를 입력하세요", - "location_picker_longitude_error": "유효한 경도를 입력하세요.", - "location_picker_longitude_hint": "여기에 경도를 입력하세요", - "lock": "잠금", - "locked_folder": "잠금 폴더", - "log_detail_title": "상세 로그", - "log_out": "로그아웃", - "log_out_all_devices": "모든 기기에서 로그아웃", - "logged_in_as": "{user}로 로그인됨", - "logged_out_all_devices": "모든 기기에서 로그아웃되었습니다.", - "logged_out_device": "기기에서 로그아웃되었습니다.", - "login": "로그인", - "login_disabled": "로그인이 비활성화되었습니다.", - "login_form_api_exception": "API 예외가 발생했습니다. 서버 URL을 확인한 후 다시 시도하세요.", - "login_form_back_button_text": "뒤로", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "서버 엔드포인트 URL", - "login_form_err_http": "http:// 또는 https://로 시작해야 합니다.", - "login_form_err_invalid_email": "유효하지 않은 이메일", - "login_form_err_invalid_url": "잘못된 URL입니다.", - "login_form_err_leading_whitespace": "입력값 앞에 공백이 있습니다.", - "login_form_err_trailing_whitespace": "입력값 끝에 공백이 있습니다.", - "login_form_failed_get_oauth_server_config": "OAuth 로그인 중 오류가 발생했습니다. 서버 URL을 확인하세요.", - "login_form_failed_get_oauth_server_disable": "이 서버는 OAuth 기능을 지원하지 않습니다.", - "login_form_failed_login": "로그인 중 오류가 발생했습니다. 서버 URL, 이메일 및 비밀번호를 확인하세요.", - "login_form_handshake_exception": "서버와 통신 중 인증서 예외가 발생했습니다. 자체 서명된 인증서를 사용 중이라면, 설정에서 자체 서명된 인증서 허용을 활성화하세요.", - "login_form_password_hint": "비밀번호", - "login_form_save_login": "로그인 유지", - "login_form_server_empty": "서버 URL을 입력하세요.", - "login_form_server_error": "서버에 연결할 수 없습니다.", - "login_has_been_disabled": "로그인이 비활성화되었습니다.", - "login_password_changed_error": "비밀번호 변경 중 오류가 발생했습니다.", - "login_password_changed_success": "비밀번호가 변경되었습니다.", - "logout_all_device_confirmation": "모든 기기에서 로그아웃하시겠습니까?", - "logout_this_device_confirmation": "이 기기에서 로그아웃하시겠습니까?", - "logs": "로그", - "longitude": "경도", - "look": "보기", - "loop_videos": "동영상 반복", - "loop_videos_description": "상세 보기에서 영상을 반복 재생합니다.", - "main_branch_warning": "개발 버전을 사용 중입니다. 정식 릴리스 버전 사용을 권장합니다!", - "main_menu": "메인 메뉴", - "maintenance_description": "Immich가 유지관리 모드로 전환되었습니다.", - "maintenance_end": "유지 관리 모드 종료", - "maintenance_end_error": "유지관리 모드를 종료하는 데 실패했습니다.", - "maintenance_logged_in_as": "현재 {user} 님으로 로그인되어 있습니다", - "maintenance_title": "일시적으로 이용할 수 없습니다", - "make": "제조사", - "manage_geolocation": "위치 정보 관리", - "manage_media_access_rationale": "이 권한은 자산을 휴지통으로 이동하고 휴지통에서 복원하는 작업을 올바르게 처리하는 데 필요합니다.", - "manage_media_access_settings": "설정 열기", - "manage_media_access_subtitle": "Immich 앱이 미디어 파일을 관리하고 이동할 수 있도록 허용하십시오.", - "manage_media_access_title": "미디어 관리 액세스", - "manage_shared_links": "공유 링크 관리", - "manage_sharing_with_partners": "공유할 파트너를 초대하거나 제거합니다.", - "manage_the_app_settings": "앱 동작 및 표시 환경을 사용자 정의합니다.", - "manage_your_account": "사용자의 계정 정보를 확인하고 변경합니다.", - "manage_your_api_keys": "API 키를 생성, 삭제하거나 권한을 관리합니다.", - "manage_your_devices": "현재 로그인한 기기를 확인하고 로그아웃할 수 있습니다.", - "manage_your_oauth_connection": "OAuth 연결을 관리합니다.", - "map": "지도", - "map_assets_in_bounds": "{count, plural, =0 {사진 없음} one {사진 #개} other {사진 #개}}", - "map_cannot_get_user_location": "사용자의 위치를 가져올 수 없습니다.", - "map_location_dialog_yes": "예", - "map_location_picker_page_use_location": "이 위치 사용", - "map_location_service_disabled_content": "현재 위치의 항목을 표시하려면 위치 서비스를 활성화해야 합니다. 지금 활성화하시겠습니까?", - "map_location_service_disabled_title": "위치 서비스 비활성화됨", - "map_marker_for_images": "{country}, {city}에서 촬영된 이미지의 지도 마커", - "map_marker_with_image": "이미지가 있는 지도 마커", - "map_no_location_permission_content": "현재 위치의 항목을 표시하려면 위치 권한이 필요합니다. 지금 허용하시겠습니까?", - "map_no_location_permission_title": "위치 권한 거부됨", - "map_settings": "지도 설정", - "map_settings_dark_mode": "다크 모드", - "map_settings_date_range_option_day": "지난 24시간", - "map_settings_date_range_option_days": "지난 {days}일", - "map_settings_date_range_option_year": "지난 1년", - "map_settings_date_range_option_years": "지난 {years}년", - "map_settings_dialog_title": "지도 설정", - "map_settings_include_show_archived": "보관된 항목 포함", - "map_settings_include_show_partners": "파트너가 공유한 항목 포함", - "map_settings_only_show_favorites": "즐겨찾기만 표시", - "map_settings_theme_settings": "지도 테마", - "map_zoom_to_see_photos": "축소하여 사진 보기", - "mark_all_as_read": "모두 읽음으로 표시", - "mark_as_read": "읽음으로 표시", - "marked_all_as_read": "모두 읽음으로 표시했습니다.", - "matches": "일치", - "matching_assets": "일치하는 항목", - "media_type": "미디어 종류", - "memories": "추억", - "memories_all_caught_up": "모두 확인함", - "memories_check_back_tomorrow": "내일 더 많은 추억을 확인해보세요.", - "memories_setting_description": "추억 표시 설정을 관리합니다.", - "memories_start_over": "다시 보기", - "memories_swipe_to_close": "위로 밀어서 닫기", - "memory": "추억", - "memory_lane_title": "{title} 추억", - "menu": "메뉴", - "merge": "병합", - "merge_people": "인물 병합", - "merge_people_limit": "한 번에 최대 5명까지 합칠 수 있습니다.", - "merge_people_prompt": "인물들을 병합하시겠습니까? 이 작업은 되돌릴 수 없습니다.", - "merge_people_successfully": "선택한 인물을 합쳤습니다.", - "merged_people_count": "인물 {count, plural, one {#명} other {#명}}을 합쳤습니다.", - "minimize": "최소화", - "minute": "분", - "minutes": "분", - "mirror_horizontal": "수평", - "mirror_vertical": "수직", - "missing": "누락", - "mobile_app": "모바일 앱", - "mobile_app_download_onboarding_note": "다음 옵션 중 하나를 사용해 모바일 앱을 다운로드하세요.", - "model": "모델", - "month": "월", - "monthly_title_text_date_format": "yyyy년 M월", - "more": "더보기", - "move": "이동", - "move_down": "아래로 이동", - "move_off_locked_folder": "잠금 폴더에서 해제", - "move_to": "다음으로 이동", - "move_to_device_trash": "기기의 휴지통으로 이동", - "move_to_lock_folder_action_prompt": "잠금 폴더로 항목 {count}개 이동됨", - "move_to_locked_folder": "잠금 폴더로 이동", - "move_to_locked_folder_confirmation": "선택한 사진 또는 동영상이 모든 앨범에서 제거되며, 잠금 폴더에서만 볼 수 있습니다.", - "move_up": "위로 이동", - "moved_to_archive": "보관함으로 항목 {count, plural, one {#개} other {#개}} 이동됨", - "moved_to_library": "라이브러리로 항목 {count, plural, one {#개} other {#개}} 이동됨", - "moved_to_trash": "휴지통으로 이동되었습니다.", - "multiselect_grid_edit_date_time_err_read_only": "읽기 전용인 항목의 날짜는 변경할 수 없어 건너뜁니다.", - "multiselect_grid_edit_gps_err_read_only": "읽기 전용인 항목의 위치는 변경할 수 없어 건너뜁니다.", - "mute_memories": "추억 음소거", - "my_albums": "내 앨범", - "name": "이름", - "name_or_nickname": "이름 또는 닉네임", - "name_required": "이름은 필수 입력 사항입니다", - "navigate": "탐색", - "navigate_to_time": "시간으로 탐색", - "network_requirement_photos_upload": "사진 백업에 모바일 데이터 사용", - "network_requirement_videos_upload": "동영상 백업에 모바일 데이터 사용", - "network_requirements": "네트워크 요구사항", - "network_requirements_updated": "네트워크 상태가 변경되었습니다. 백업 대기열을 초기화합니다.", - "networking_settings": "연결", - "networking_subtitle": "서버 엔드포인트 설정을 관리합니다.", - "never": "없음", - "new_album": "새 앨범", - "new_api_key": "새 API 키", - "new_date_range": "새 날짜 범위", - "new_password": "새 비밀번호", - "new_person": "새 인물 생성", - "new_pin_code": "새 PIN 코드", - "new_pin_code_subtitle": "잠금 폴더에 처음 접근하셨습니다. 이곳에 안전하게 접근하기 위한 PIN 코드를 설정하세요.", - "new_timeline": "새 타임라인", - "new_update": "새로운 업데이트", - "new_user_created": "사용자 계정이 생성되었습니다.", - "new_version_available": "새 버전 사용 가능", - "newest_first": "최신순", - "next": "다음", - "next_memory": "다음 추억", - "no": "아니요", - "no_actions_added": "아직 추가된 작업이 없습니다", - "no_albums_found": "앨범이 없습니다.", - "no_albums_message": "앨범을 생성하여 사진과 동영상을 정리하기", - "no_albums_with_name_yet": "아직 해당하는 이름의 앨범이 없는 것 같습니다.", - "no_albums_yet": "아직 앨범이 없는 것 같습니다.", - "no_archived_assets_message": "사진과 동영상을 보관함으로 옮겨 포토 뷰에서 숨기기", - "no_assets_message": "여기를 클릭해 첫 사진을 업로드하세요.", - "no_assets_to_show": "표시할 항목 없음", - "no_cast_devices_found": "캐스트 기기 없음", - "no_checksum_local": "체크섬이 없습니다. 로컬 항목을 불러올 수 없습니다.", - "no_checksum_remote": "체크섬이 없습니다. 원격 항목을 불러올 수 없습니다.", - "no_configuration_needed": "별도의 설정이 필요하지 않습니다", - "no_devices": "승인되지 않은 기기", - "no_duplicates_found": "비슷한 항목이 없습니다.", - "no_exif_info_available": "EXIF 정보 없음", - "no_explore_results_message": "더 많은 사진을 업로드하여 탐색 기능을 사용하세요.", - "no_favorites_message": "즐겨찾기에서 사진과 동영상을 빠르게 찾기", - "no_filters_added": "아직 추가된 필터 없음", - "no_libraries_message": "외부 라이브러리로 다른 경로의 사진과 동영상을 확인하세요.", - "no_local_assets_found": "체크섬과 일치하는 로컬 항목을 찾을 수 없습니다.", - "no_location_set": "위치가 설정되지 않았습니다", - "no_locked_photos_message": "잠금 폴더의 사진 및 동영상은 숨겨지며 라이브러리를 탐색할 때 표시되지 않습니다.", - "no_name": "이름 없음", - "no_notifications": "알림 없음", - "no_people_found": "일치하는 인물 없음", - "no_places": "장소 없음", - "no_remote_assets_found": "체크섬과 일치하는 원격 항목을 찾을 수 없습니다.", - "no_results": "결과 없음", - "no_results_description": "동의어 또는 더 일반적인 단어를 사용해 보세요.", - "no_shared_albums_message": "앨범을 만들어 주변 사람들과 사진 및 동영상을 공유하세요.", - "no_uploads_in_progress": "진행 중인 업로드 없음", - "not_allowed": "허용되지 않음", - "not_available": "없음", - "not_in_any_album": "앨범에 없음", - "not_selected": "선택되지 않음", - "note_apply_storage_label_to_previously_uploaded assets": "참고: 이전에 업로드한 항목에도 스토리지 레이블을 적용하려면 다음을 실행합니다,", - "notes": "참고", - "nothing_here_yet": "아직 아무것도 없음", - "notification_permission_dialog_content": "알림을 활성화하려면 설정에서 알림 권한을 허용하세요.", - "notification_permission_list_tile_content": "알림을 활성화하려면 권한을 부여하세요.", - "notification_permission_list_tile_enable_button": "알림 활성화", - "notification_permission_list_tile_title": "알림 권한", - "notification_toggle_setting_description": "이메일 알림 활성화", - "notifications": "알림", - "notifications_setting_description": "알림 전송 설정을 관리합니다.", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium 구성", - "obtainium_configurator_instructions": "Obtainium으로 Immich GitHub 릴리스에서 직접 안드로이드 앱을 설치하고 업데이트하세요. API 키를 생성하고 변형을 선택해 Obtanium 설정 링크를 생성하세요.", - "ocr": "OCR", - "official_immich_resources": "Immich 공식 리소스", - "offline": "오프라인", - "offset": "오프셋", - "ok": "확인", - "oldest_first": "오래된 순", - "on_this_device": "이 장치에서", - "onboarding": "초기 설정", - "onboarding_locale_description": "사용할 언어를 선택하세요. 설정에서 언제든 변경할 수 있습니다.", - "onboarding_privacy_description": "다음 선택적 기능은 외부 서비스를 사용하며 설정에서 언제든 비활성화할 수 있습니다.", - "onboarding_server_welcome_description": "몇 가지 일반적인 설정을 진행하겠습니다.", - "onboarding_theme_description": "사용할 테마를 선택하세요. 설정에서 언제든 변경할 수 있습니다.", - "onboarding_user_welcome_description": "기본 설정을 시작하겠습니다!", - "onboarding_welcome_user": "환영합니다, {user}님.", - "online": "온라인", - "only_favorites": "즐겨찾기만", - "open": "열기", - "open_in_map_view": "지도 보기에서 열기", - "open_in_openstreetmap": "OpenStreetMap에서 열기", - "open_the_search_filters": "검색 필터 열기", - "options": "옵션", - "or": "또는", - "organize_into_albums": "앨범으로 정리하기", - "organize_into_albums_description": "현재 동기화 설정을 사용하여 기존 사진을 앨범으로 정리합니다", - "organize_your_library": "라이브러리 정리", - "original": "원본", - "other": "기타", - "other_devices": "다른 기기", - "other_entities": "기타 엔티티", - "other_variables": "기타 변수", - "owned": "소유함", - "owner": "소유자", - "page": "페이지", - "partner": "파트너", - "partner_can_access": "{partner}님이 접근할 수 있는 항목", - "partner_can_access_assets": "보관되거나 삭제된 항목을 제외한 모든 사진 및 동영상", - "partner_can_access_location": "사진을 촬영한 위치", - "partner_list_user_photos": "{user}님의 사진", - "partner_list_view_all": "모두 보기", - "partner_page_empty_message": "사진이 아직 어떤 파트너와도 공유되지 않았습니다.", - "partner_page_no_more_users": "추가할 사용자가 없습니다.", - "partner_page_partner_add_failed": "파트너 추가 실패", - "partner_page_select_partner": "파트너 선택", - "partner_page_shared_to_title": "공유 대상", - "partner_page_stop_sharing_content": "더 이상 {partner}님이 사진에 접근할 수 없습니다.", - "partner_sharing": "파트너와 공유", - "partners": "파트너", - "password": "비밀번호", - "password_does_not_match": "비밀번호가 일치하지 않습니다.", - "password_required": "비밀번호 필요", - "password_reset_success": "비밀번호 초기화 성공", - "past_durations": { - "days": "지난 {days, plural, one {일} other {#일}}", - "hours": "지난 {hours, plural, one {시간} other {#시간}}", - "years": "지난 {years, plural, one {년} other {#년}}" - }, - "path": "경로", - "pattern": "규칙", - "pause": "일시 정지", - "pause_memories": "추억 일시 정지", - "paused": "일시 정지됨", - "pending": "진행 중", - "people": "인물", - "people_edits_count": "인물 {count, plural, one {#명} other {#명}}이 수정되었습니다.", - "people_feature_description": "사진과 동영상을 인물 그룹별로 탐색", - "people_selected": "인물 {count, plural, one {#명} other {#명}} 선택됨", - "people_sidebar_description": "사이드바에 인물 링크 표시", - "permanent_deletion_warning": "영구 삭제 경고", - "permanent_deletion_warning_setting_description": "항목을 완전히 삭제하기 전 경고 메시지를 표시합니다.", - "permanently_delete": "영구 삭제", - "permanently_delete_assets_count": "{count, plural, one {asset} other {assets}}를 영구삭제", - "permanently_delete_assets_prompt": "{count, plural, one {이 항목을} other {항목 #개를}} 영구적으로 삭제하시겠습니까? {count, plural, one {항목이} other {항목이}} 앨범에 포함된 경우 앨범에서 제거됩니다.", - "permanently_deleted_asset": "항목이 영구적으로 삭제되었습니다.", - "permanently_deleted_assets_count": "{count, plural, one {#개} other {#개}} 항목이 영구적으로 삭제됨", - "permission": "권한", - "permission_empty": "하나 이상의 권한을 선택해야 합니다.", - "permission_onboarding_back": "뒤로", - "permission_onboarding_continue_anyway": "무시하고 진행", - "permission_onboarding_get_started": "시작하기", - "permission_onboarding_go_to_settings": "설정으로 이동", - "permission_onboarding_permission_denied": "권한이 없습니다. Immich를 사용하려면 설정에서 사진 및 동영상 권한을 부여하세요.", - "permission_onboarding_permission_granted": "권한이 부여되었습니다! 준비가 완료되었습니다.", - "permission_onboarding_permission_limited": "권한이 없습니다. Immich가 전체 갤러리 컬렉션을 백업하고 관리할 수 있도록 하려면 설정에서 사진 및 동영상 권한을 부여하세요.", - "permission_onboarding_request": "Immich가 사진 및 동영상에 접근하기 위한 권한이 필요합니다.", - "person": "인물", - "person_age_months": "{months, plural, one {#개월} other {#개월}}", - "person_age_year_months": "1세, {months, plural, one {#개월} other {#개월}}", - "person_age_years": "{years, plural, other {#세}}", - "person_birthdate": "{date} 출생", - "person_hidden": "{name}{hidden, select, true { (숨김)} other {}}", - "person_recognized": "신원이 확인된 사람", - "person_selected": "선택된 사람", - "photo_shared_all_users": "이미 모든 사용자와 사진을 공유 중이거나 다른 사용자가 없는 것 같습니다.", - "photos": "사진", - "photos_and_videos": "사진 및 동영상", - "photos_count": "사진 {count, plural, one {{count, number}개} other {{count, number}개}}", - "photos_from_previous_years": "지난 몇 년간의 사진", - "pick_a_location": "위치 선택", - "pick_custom_range": "범위 지정", - "pick_date_range": "날짜 범위 선택", - "pin_code_changed_successfully": "PIN 코드가 변경되었습니다.", - "pin_code_reset_successfully": "PIN 코드를 초기화했습니다.", - "pin_code_setup_successfully": "PIN 코드를 설정했습니다.", - "pin_verification": "PIN 코드 인증", - "place": "장소", - "places": "장소", - "places_count": "{count, plural, one {{count, number} 장소} other {{count, number} 장소}}", - "play": "재생", - "play_memories": "추억 재생", - "play_motion_photo": "모션 포토 재생", - "play_or_pause_video": "동영상 재생/일시 정지", - "play_original_video": "원본 동영상 재생", - "play_original_video_setting_description": "트랜스코딩된 영상보다 원본 영상을 우선 재생합니다. 원본이 호환되지 않는 형식인 경우 정상적으로 재생되지 않을 수 있습니다.", - "play_transcoded_video": "트랜스코딩 동영상 재생", - "please_auth_to_access": "계속 진행하려면 인증하세요.", - "port": "포트", - "preferences_settings_subtitle": "앱 개인 설정을 관리합니다.", - "preferences_settings_title": "개인 설정", - "preparing": "준비 중", - "preset": "프리셋", - "preview": "미리 보기", - "previous": "이전", - "previous_memory": "이전 추억", - "previous_or_next_day": "이전/다음 날로", - "previous_or_next_month": "이전/다음 달로", - "previous_or_next_photo": "이전/다음 사진으로", - "previous_or_next_year": "이전/다음 연도로", - "primary": "기본", - "privacy": "개인정보", - "profile": "프로필", - "profile_drawer_app_logs": "로그", - "profile_drawer_client_server_up_to_date": "클라이언트와 서버가 최신 상태입니다.", - "profile_drawer_github": "Github", - "profile_drawer_readonly_mode": "읽기 전용 모드 활성화. 사용자 아이콘을 길게 눌러 해제할 수 있습니다.", - "profile_image_of_user": "{user}님의 프로필 이미지", - "profile_picture_set": "프로필 사진이 설정되었습니다.", - "public_album": "공개 앨범", - "public_share": "모든 사용자와 공유", - "purchase_account_info": "서포터", - "purchase_activated_subtitle": "Immich와 오픈 소스 소프트웨어를 지원해 주셔서 감사합니다.", - "purchase_activated_time": "{date} 등록됨", - "purchase_activated_title": "제품 키가 활성화되었습니다.", - "purchase_button_activate": "등록", - "purchase_button_buy": "구매", - "purchase_button_buy_immich": "Immich 구매", - "purchase_button_never_show_again": "다시 보지 않기", - "purchase_button_reminder": "30일 후에 다시 알림", - "purchase_button_remove_key": "키 제거", - "purchase_button_select": "선택", - "purchase_failed_activation": "등록하지 못했습니다. 이메일로 전송된 키를 정확히 입력했는지 확인하세요!", - "purchase_individual_description_1": "개인 사용자용", - "purchase_individual_description_2": "서포터 배지", - "purchase_individual_title": "개인", - "purchase_input_suggestion": "제품 키를 보유 중인가요? 아래에 키를 입력하세요.", - "purchase_license_subtitle": "Immich를 구매하여 지속적인 개발을 지원하세요.", - "purchase_lifetime_description": "일회성 구매", - "purchase_option_title": "구매 옵션", - "purchase_panel_info_1": "Immich를 개발하는 데는 많은 시간과 노력이 필요합니다. 우리는 좋은 앱을 만들기 위해 풀 타임 개발자와 함께하고 있으며, 최종적으로 오픈 소스 소프트웨어와 비즈니스 행동 윤리가 개발자에게 지속 가능한 수입원을 제공하고 착취적인 클라우드 서비스를 대체할 수 있는 개인 정보 보호 생태계를 구축하는 것을 목표로 합니다.", - "purchase_panel_info_2": "유료화 정책을 도입하지 않기로 약속했기에 이 구매는 어떠한 추가 기능도 제공하지 않습니다. Immich의 지속적인 개발은 당신과 같은 여러 사용자의 지원이 있기에 가능합니다.", - "purchase_panel_title": "프로젝트 지원", - "purchase_per_server": "서버당", - "purchase_per_user": "사용자당", - "purchase_remove_product_key": "제품 키 제거", - "purchase_remove_product_key_prompt": "제품 키를 제거하시겠습니까?", - "purchase_remove_server_product_key": "서버 제품 키 제거", - "purchase_remove_server_product_key_prompt": "서버 제품 키를 제거하시겠습니까?", - "purchase_server_description_1": "서버 전체에 적용", - "purchase_server_description_2": "서포터 배지", - "purchase_server_title": "서버", - "purchase_settings_server_activated": "서버 제품 키는 관리자가 제어합니다.", - "query_asset_id": "쿼리 항목 ID", - "queue_status": "전체 {total}, {count} 대기 중", - "rating": "등급", - "rating_clear": "등급 초기화", - "rating_count": "{count, plural, one {#점} other {#점}}", - "rating_description": "상세 정보 패널에 EXIF 등급 태그 표시", - "reaction_options": "반응 옵션", - "read_changelog": "변경 내역 보기", - "readonly_mode_disabled": "읽기 전용 모드 비활성화", - "readonly_mode_enabled": "읽기 전용 모드 활성화", - "ready_for_upload": "업로드 준비 완료", - "reassign": "다시 할당", - "reassigned_assets_to_existing_person": "{count, plural, one {항목 #개} other {항목 #개}}를 {name, select, null {기존 인물} other {기존 인물 {name}}}에게 재지정했습니다.", - "reassigned_assets_to_new_person": "{count, plural, one {항목 #개} other {항목 #개}}를 새 인물에게 재지정했습니다.", - "reassing_hint": "기존 인물에 선택한 항목 할당", - "recent": "최근", - "recent-albums": "최근 앨범", - "recent_searches": "최근 검색", - "recently_added": "최근 추가", - "recently_added_page_title": "최근 추가", - "recently_taken": "최근 항목", - "recently_taken_page_title": "최근 촬영됨", - "refresh": "새로고침", - "refresh_encoded_videos": "동영상 재인코딩", - "refresh_faces": "얼굴 새로고침", - "refresh_metadata": "메타데이터 갱신", - "refresh_thumbnails": "섬네일 다시 생성", - "refreshed": "새로고침이 완료되었습니다.", - "refreshes_every_file": "기존 파일 및 새 파일 스캔", - "refreshing_encoded_video": "동영상을 다시 인코딩합니다.", - "refreshing_faces": "얼굴을 새로고침합니다.", - "refreshing_metadata": "메타데이터를 새로고침합니다.", - "regenerating_thumbnails": "섬네일을 다시 생성하는 중...", - "remote": "원격", - "remote_assets": "원격 항목", - "remote_media_summary": "원격 미디어 요약", - "remove": "제거", - "remove_assets_album_confirmation": "앨범에서 항목 {count, plural, one {#개} other {#개}}를 제거하시겠습니까?", - "remove_assets_shared_link_confirmation": "공유 링크에서 항목 {count, plural, one {#개} other {#개}}를 제거하시겠습니까?", - "remove_assets_title": "항목을 제거하시겠습니까?", - "remove_custom_date_range": "맞춤 기간 제거", - "remove_deleted_assets": "누락된 파일 제거", - "remove_from_album": "앨범에서 제거", - "remove_from_album_action_prompt": "앨범에서 항목 {count}개 제거됨", - "remove_from_favorites": "즐겨찾기에서 제거", - "remove_from_lock_folder_action_prompt": "잠금 폴더에서 항목 {count}개 내보냄", - "remove_from_locked_folder": "잠금 폴더에서 내보내기", - "remove_from_locked_folder_confirmation": "이 사진과 영상을 잠금 폴더에서 해제하시겠습니까? 이제 라이브러리에서 보이게 됩니다.", - "remove_from_shared_link": "공유 링크에서 제거", - "remove_memory": "추억 제거", - "remove_photo_from_memory": "추억에서 사진 제거", - "remove_tag": "태그 제거", - "remove_url": "URL 제거", - "remove_user": "사용자 삭제", - "removed_api_key": "API 키 삭제: {name}", - "removed_from_archive": "보관함에서 제거되었습니다.", - "removed_from_favorites": "즐겨찾기에서 제거되었습니다.", - "removed_from_favorites_count": "즐겨찾기에서 항목 {count, plural, other {#개}} 제거됨", - "removed_memory": "추억이 제거되었습니다.", - "removed_photo_from_memory": "추억에서 사진이 제거되었습니다.", - "removed_tagged_assets": "항목 {count, plural, one {#개} other {#개}}에서 태그를 제거함", - "rename": "이름 바꾸기", - "repair": "수리", - "repair_no_results_message": "추적되지 않거나 누락된 파일이 여기에 표시됩니다.", - "replace_with_upload": "파일 바꾸기", - "repository": "리포지터리", - "require_password": "비밀번호 필요", - "require_user_to_change_password_on_first_login": "사용자가 처음 로그인할 때 비밀번호를 변경하도록 요구", - "rescan": "재검색", - "reset": "초기화", - "reset_password": "비밀번호 재설정", - "reset_people_visibility": "인물 표시 여부 초기화", - "reset_pin_code": "PIN 코드 초기화", - "reset_pin_code_description": "PIN 코드를 잊어버린 경우 초기화하려면 서버 관리자에게 연락하세요.", - "reset_pin_code_success": "PIN 코드를 초기화했습니다.", - "reset_pin_code_with_password": "패스워드로 PIN 코드를 재설정할 수 있습니다.", - "reset_sqlite": "SQLite 데이터베이스 초기화", - "reset_sqlite_confirmation": "SQLite 데이터베이스를 초기화하시겠습니까? 데이터를 재동기화하려면 로그아웃 후 다시 로그인해야 합니다.", - "reset_sqlite_success": "SQLite 데이터베이스를 초기화했습니다.", - "reset_to_default": "기본값으로 복원", - "resolution": "해상도", - "resolve_duplicates": "비슷한 항목 확인", - "resolved_all_duplicates": "비슷한 항목을 모두 처리했습니다.", - "restore": "복원", - "restore_all": "모두 복원", - "restore_trash_action_prompt": "휴지통에서 {count}개 항목 복원됨", - "restore_user": "사용자 복원", - "restored_asset": "항목이 복원되었습니다.", - "resume": "재개", - "resume_paused_jobs": "일시 중지된 작업 {count, plural, one {#개} other {#개}} 재개", - "retry_upload": "다시 시도", - "review_duplicates": "비슷한 항목 확인", - "review_large_files": "용량이 큰 파일 확인", - "role": "역할", - "role_editor": "편집자", - "role_viewer": "뷰어", - "running": "작동 중", - "save": "저장", - "save_to_gallery": "갤러리에 저장", - "saved": "저장됨", - "saved_api_key": "API 키가 수정되었습니다.", - "saved_profile": "프로필이 저장되었습니다.", - "saved_settings": "설정이 저장되었습니다.", - "say_something": "댓글을 입력하세요", - "scaffold_body_error_occurred": "오류가 발생했습니다.", - "scan": "스캔", - "scan_all_libraries": "모든 라이브러리 스캔", - "scan_library": "스캔", - "scan_settings": "스캔 설정", - "scanning_for_album": "앨범을 스캔하는 중...", - "search": "검색", - "search_albums": "앨범 검색", - "search_by_context": "문맥 기반 검색", - "search_by_description": "설명으로 검색", - "search_by_description_example": "동해안에서 맞이한 새해 일출", - "search_by_filename": "파일명 또는 확장자로 검색", - "search_by_filename_example": "예: IMG_1234.JPG 또는 PNG", - "search_by_ocr": "OCR로 검색", - "search_camera_lens_model": "렌즈 모델 검색...", - "search_camera_make": "카메라 제조사 검색...", - "search_camera_model": "카메라 모델명 검색...", - "search_city": "도시 검색...", - "search_country": "국가 검색...", - "search_filter_apply": "필터 적용", - "search_filter_camera_title": "카메라 종류 선택", - "search_filter_date": "날짜", - "search_filter_date_interval": "{start} - {end}", - "search_filter_date_title": "날짜 범위 선택", - "search_filter_display_option_not_in_album": "앨범에 없음", - "search_filter_display_options": "표시 옵션", - "search_filter_filename": "파일명으로 검색", - "search_filter_location": "위치", - "search_filter_location_title": "위치 선택", - "search_filter_media_type": "미디어 종류", - "search_filter_media_type_title": "미디어 종류 선택", - "search_filter_ocr": "OCR 검색", - "search_filter_people_title": "인물 선택", - "search_for": "검색", - "search_for_existing_person": "존재하는 인물 검색", - "search_no_more_result": "더이상 결과 없음", - "search_no_people": "인물 없음", - "search_no_people_named": "\"{name}\"을(를) 찾을 수 없음", - "search_no_result": "검색 결과가 없습니다. 다른 검색어나 조합으로 다시 시도해 보세요.", - "search_options": "검색 옵션", - "search_page_categories": "분류", - "search_page_motion_photos": "모션 포토", - "search_page_no_objects": "표시할 사물 정보 없음", - "search_page_no_places": "표시할 장소 정보 없음", - "search_page_screenshots": "스크린샷", - "search_page_search_photos_videos": "사진 및 동영상 검색", - "search_page_selfies": "셀피", - "search_page_things": "사물", - "search_page_view_all_button": "모두 보기", - "search_page_your_activity": "활동", - "search_page_your_map": "나의 지도", - "search_people": "인물 검색", - "search_places": "장소 검색", - "search_rating": "등급으로 검색...", - "search_result_page_new_search_hint": "새 검색", - "search_settings": "설정 검색", - "search_state": "지역 검색...", - "search_suggestion_list_smart_search_hint_1": "스마트 검색이 기본 활성화되어 있습니다. 메타데이터 검색은 다음과 같이 입력하세요: ", - "search_suggestion_list_smart_search_hint_2": "m:검색어", - "search_tags": "태그 검색...", - "search_timezone": "시간대 검색...", - "search_type": "검색 유형", - "search_your_photos": "사진 검색", - "searching_locales": "로케일 검색...", - "second": "초", - "see_all_people": "모든 인물 보기", - "select": "선택", - "select_album": "앨범 선택", - "select_album_cover": "앨범 커버 선택", - "select_albums": "앨범 선택", - "select_all": "모두 선택", - "select_all_duplicates": "비슷한 항목 모두 선택", - "select_all_in": "{group}의 모든 항목 선택", - "select_avatar_color": "아바타 색상 선택", - "select_face": "얼굴 선택", - "select_featured_photo": "대표 사진 선택", - "select_from_computer": "컴퓨터에서 선택", - "select_keep_all": "모두 유지", - "select_library_owner": "라이브러리 소유자 선택", - "select_new_face": "새 얼굴 선택", - "select_people": "사람 선택", - "select_person": "사람 선택", - "select_person_to_tag": "태그할 인물을 선택하세요.", - "select_photos": "사진 선택", - "select_trash_all": "모두 삭제", - "select_user_for_sharing_page_err_album": "앨범을 생성하지 못했습니다.", - "selected": "선택됨", - "selected_count": "{count, plural, other {#개 선택됨}}", - "selected_gps_coordinates": "선택한 GPS 좌표", - "send_message": "메시지 전송", - "send_welcome_email": "환영 이메일 전송", - "server_endpoint": "서버 엔드포인트", - "server_info_box_app_version": "앱 버전", - "server_info_box_server_url": "서버 URL", - "server_offline": "오프라인", - "server_online": "온라인", - "server_privacy": "개인정보", - "server_restarting_description": "이 페이지는 잠시 후 새로 고쳐집니다.", - "server_restarting_title": "서버가 재시작 중입니다", - "server_stats": "서버 통계", - "server_update_available": "서버 업데이트 가능", - "server_version": "서버 버전", - "set": "설정", - "set_as_album_cover": "앨범 커버로 설정", - "set_as_featured_photo": "대표 사진으로 설정", - "set_as_profile_picture": "프로필 사진으로 설정", - "set_date_of_birth": "생년월일 설정", - "set_profile_picture": "프로필 사진으로 설정", - "set_slideshow_to_fullscreen": "슬라이드 쇼를 전체 화면으로 설정", - "set_stack_primary_asset": "대표 항목으로 설정", - "setting_image_viewer_help": "상세 보기에서는 작은 섬네일, (활성화된 경우) 중간 섬네일, 원본 순으로 불러옵니다.", - "setting_image_viewer_original_subtitle": "원본 고해상도 이미지를 불러옵니다. 데이터 사용량 및 캐시 크기를 줄이려면 비활성화하세요.", - "setting_image_viewer_original_title": "원본 이미지 로드", - "setting_image_viewer_preview_subtitle": "중간 해상도 이미지를 불러옵니다. 비활성화하는 경우 원본 또는 섬네일만 불러옵니다.", - "setting_image_viewer_preview_title": "미리보기 이미지 로드", - "setting_image_viewer_title": "이미지", - "setting_languages_apply": "적용", - "setting_languages_subtitle": "앱 언어 변경", - "setting_notifications_notify_failures_grace_period": "백그라운드 백업 실패 알림: {duration} 후", - "setting_notifications_notify_hours": "{count}시간", - "setting_notifications_notify_immediately": "즉시", - "setting_notifications_notify_minutes": "{count}분", - "setting_notifications_notify_never": "알리지 않음", - "setting_notifications_notify_seconds": "{count}초", - "setting_notifications_single_progress_subtitle": "개별 항목의 상세 업로드 정보 표시", - "setting_notifications_single_progress_title": "백그라운드 백업 상세 진행률 표시", - "setting_notifications_subtitle": "알림 기본 설정 조정", - "setting_notifications_total_progress_subtitle": "전체 업로드 진행률 (완료/총 항목)", - "setting_notifications_total_progress_title": "백그라운드 백업 전체 진행률 표시", - "setting_video_viewer_auto_play_subtitle": "동영상을 열면 자동으로 재생", - "setting_video_viewer_auto_play_title": "동영상 자동 재생", - "setting_video_viewer_looping_title": "반복", - "setting_video_viewer_original_video_subtitle": "동영상 스트리밍 시 트랜스코딩된 파일 대신 원본을 재생합니다. 재생 시 버퍼링이 발생할 수 있습니다. 로컬에 있는 영상은 항상 원본 화질로 재생됩니다.", - "setting_video_viewer_original_video_title": "원본 동영상 강제 사용", - "settings": "설정", - "settings_require_restart": "설정을 적용하려면 Immich를 다시 시작해야 합니다.", - "settings_saved": "설정이 저장되었습니다.", - "setup_pin_code": "PIN 코드 설정", - "share": "공유", - "share_action_prompt": "항목 {count}개 공유됨", - "share_add_photos": "사진 추가", - "share_assets_selected": "{count}개 선택됨", - "share_dialog_preparing": "준비 중...", - "share_link": "공유 링크", - "shared": "공유됨", - "shared_album_activities_input_disable": "댓글이 비활성화되었습니다", - "shared_album_activity_remove_content": "이 활동을 삭제하시겠습니까?", - "shared_album_activity_remove_title": "활동 삭제", - "shared_album_section_people_action_error": "앨범에서 나가기/제거 중 문제가 발생했습니다.", - "shared_album_section_people_action_leave": "앨범에서 사용자 제거", - "shared_album_section_people_action_remove_user": "앨범에서 사용자 제거", - "shared_album_section_people_title": "사용자", - "shared_by": "다음 사용자가 공유", - "shared_by_user": "{user}님이 공유함", - "shared_by_you": "내가 공유함", - "shared_from_partner": "{partner}님의 사진", - "shared_intent_upload_button_progress_text": "{total}개 중 {current}개 업로드됨", - "shared_link_app_bar_title": "공유 링크", - "shared_link_clipboard_copied_massage": "클립보드에 복사되었습니다.", - "shared_link_clipboard_text": "링크: {link}\n비밀번호: {password}", - "shared_link_create_error": "공유 링크 생성 중 오류 발생", - "shared_link_custom_url_description": "사용자 지정 URL을 사용하여 공유 링크 접근", - "shared_link_edit_description_hint": "공유 링크 설명 입력", - "shared_link_edit_expire_after_option_day": "1일", - "shared_link_edit_expire_after_option_days": "{count}일", - "shared_link_edit_expire_after_option_hour": "1시간", - "shared_link_edit_expire_after_option_hours": "{count}시간", - "shared_link_edit_expire_after_option_minute": "1분", - "shared_link_edit_expire_after_option_minutes": "{count}분", - "shared_link_edit_expire_after_option_months": "{count}개월", - "shared_link_edit_expire_after_option_year": "{count}년", - "shared_link_edit_password_hint": "공유 비밀번호 입력", - "shared_link_edit_submit_button": "링크 편집", - "shared_link_error_server_url_fetch": "서버 URL을 가져올 수 없습니다", - "shared_link_expires_day": "{count}일 후 만료", - "shared_link_expires_days": "{count}일 후 만료", - "shared_link_expires_hour": "{count}시간 후 만료", - "shared_link_expires_hours": "{count}시간 후 만료", - "shared_link_expires_minute": "{count}분 후 만료", - "shared_link_expires_minutes": "{count}분 후 만료", - "shared_link_expires_never": "만료되지 않음", - "shared_link_expires_second": "{count}초 후 만료", - "shared_link_expires_seconds": "{count}초 후 만료", - "shared_link_individual_shared": "개인 공유", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "공유 링크를 관리합니다.", - "shared_link_options": "공유 링크 옵션", - "shared_link_password_description": "이 페이지를 보려면 비밀번호를 입력하세요.", - "shared_links": "공유 링크", - "shared_links_description": "링크를 통해 사진 및 동영상 공유", - "shared_photos_and_videos_count": "사진 및 동영상 {assetCount, plural, other {#개를 공유했습니다.}}", - "shared_with_me": "나와 공유됨", - "shared_with_partner": "{partner}님과 공유함", - "sharing": "공유", - "sharing_enter_password": "이 페이지를 보려면 비밀번호를 입력하세요.", - "sharing_page_album": "공유 앨범", - "sharing_page_description": "공유 앨범을 만들어 주변 사람들과 사진 및 동영상을 공유하세요.", - "sharing_page_empty_list": "공유 앨범 없음", - "sharing_sidebar_description": "사이드바에 공유 링크 표시", - "sharing_silver_appbar_create_shared_album": "새 공유 앨범", - "sharing_silver_appbar_share_partner": "파트너와 공유", - "shift_to_permanent_delete": "⇧를 눌러 항목 영구 삭제", - "show_album_options": "앨범 옵션 표시", - "show_albums": "앨범 표시", - "show_all_people": "모든 인물 보기", - "show_and_hide_people": "인물 숨기기", - "show_file_location": "파일 위치 표시", - "show_gallery": "갤러리 표시", - "show_hidden_people": "숨겨진 인물 표시", - "show_in_timeline": "타임라인에 표시", - "show_in_timeline_setting_description": "타임라인에 이 사용자의 사진과 동영상을 표시", - "show_keyboard_shortcuts": "키보드 단축키 표시", - "show_metadata": "메타데이터 표시", - "show_or_hide_info": "정보 표시 또는 숨기기", - "show_password": "비밀번호 표시", - "show_person_options": "인물 옵션 표시", - "show_progress_bar": "진행 표시줄 표시", - "show_schema": "스키마 표시", - "show_search_options": "검색 옵션 표시", - "show_shared_links": "공유 링크 표시", - "show_slideshow_transition": "슬라이드 전환 표시", - "show_supporter_badge": "서포터 배지", - "show_supporter_badge_description": "서포터 배지 표시", - "show_text_recognition": "텍스트 인식 표시", - "show_text_search_menu": "텍스트 검색 메뉴 표시", - "shuffle": "셔플", - "sidebar": "사이드바", - "sidebar_display_description": "보기 링크를 사이드바에 표시", - "sign_out": "로그아웃", - "sign_up": "로그인", - "size": "크기", - "skip_to_content": "항목으로 건너뛰기", - "skip_to_folders": "폴더로 건너뛰기", - "skip_to_tags": "태그로 건너뛰기", - "slideshow": "슬라이드 쇼", - "slideshow_repeat": "슬라이드 쇼 반복", - "slideshow_repeat_description": "슬라이드 쇼가 끝나면 처음으로 되돌아갑니다", - "slideshow_settings": "슬라이드 쇼 설정", - "sort_albums_by": "다음으로 앨범 정렬...", - "sort_created": "생성된 날짜", - "sort_items": "항목 수", - "sort_modified": "수정된 날짜", - "sort_newest": "최근 사진", - "sort_oldest": "오래된 사진", - "sort_people_by_similarity": "유사성을 기준으로 인물 정렬", - "sort_recent": "최근 사진", - "sort_title": "제목", - "source": "소스", - "stack": "스택", - "stack_action_prompt": "항목 {count}개 스택됨", - "stack_duplicates": "비슷한 항목 스택", - "stack_select_one_photo": "스택의 대표 사진 선택", - "stack_selected_photos": "선택한 이미지 스택", - "stacked_assets_count": "항목 {count, plural, one {#개} other {#개}} 스택됨", - "stacktrace": "스택 추적", - "start": "시작", - "start_date": "시작일", - "start_date_before_end_date": "시작일은 종료일보다 이전이어야 합니다", - "state": "지역", - "status": "상태", - "stop_casting": "캐스팅 중단", - "stop_motion_photo": "모션 포토 정지", - "stop_photo_sharing": "공유를 중단하시겠습니까?", - "stop_photo_sharing_description": "더 이상 {partner}님이 사진에 접근할 수 없습니다.", - "stop_sharing_photos_with_user": "이 사용자와 사진 공유 중단", - "storage": "저장 공간", - "storage_label": "스토리지 레이블", - "storage_quota": "스토리지 할당량", - "storage_usage": "{available} 중 {used} 사용", - "submit": "확인", - "success": "성공", - "suggestions": "추천", - "sunrise_on_the_beach": "눈 내리는 거리, 책 읽는 사람", - "support": "지원", - "support_and_feedback": "지원 & 제안", - "support_third_party_description": "서드파티 패키지를 이용하여 Immich가 설치된 것으로 보입니다. 현재 발생하는 문제는 해당 패키지가 원인일 수 있으므로, 먼저 아래 링크를 통해 패키지 개발자에게 문의해주세요.", - "swap_merge_direction": "병합 방향 변경", - "sync": "동기화", - "sync_albums": "앨범 동기화", - "sync_albums_manual_subtitle": "업로드한 모든 동영상과 사진을 선택한 백업 앨범에 동기화", - "sync_local": "로컬 동기화", - "sync_remote": "원격 동기화", - "sync_status": "동기화 상태", - "sync_status_subtitle": "동기화 시스템 확인 및 관리", - "sync_upload_album_setting_subtitle": "선택한 앨범을 Immich에 생성하고 사진 및 동영상 업로드", - "tag": "태그", - "tag_assets": "항목 태그", - "tag_created": "태그 생성됨: {tag}", - "tag_feature_description": "태그 주제별로 그룹화된 사진과 동영상 탐색", - "tag_not_found_question": "태그를 찾을 수 없나요? 새 태그를 생성하세요.", - "tag_people": "인물 태그", - "tag_updated": "태그 업데이트됨: {tag}", - "tagged_assets": "항목 {count, plural, one {#개} other {#개}}에 태그를 적용함", - "tags": "태그", - "tap_to_run_job": "탭하여 작업 실행", - "template": "템플릿", - "text_recognition": "텍스트 인식", - "theme": "테마", - "theme_selection": "테마 선택", - "theme_selection_description": "시스템의 다크 모드 설정에 따라 테마를 자동으로 적용합니다.", - "theme_setting_asset_list_storage_indicator_title": "타일에 서버 동기화 상태 표시", - "theme_setting_asset_list_tiles_per_row_title": "한 줄에 표시할 항목 수 ({count})", - "theme_setting_colorful_interface_subtitle": "기본 색상을 배경에 적용", - "theme_setting_colorful_interface_title": "미려한 인터페이스", - "theme_setting_image_viewer_quality_subtitle": "상세 보기 이미지 품질 조정", - "theme_setting_image_viewer_quality_title": "이미지 보기 품질", - "theme_setting_primary_color_subtitle": "주요 기능 및 강조에 사용할 색상 선택", - "theme_setting_primary_color_title": "기본 색상", - "theme_setting_system_primary_color_title": "시스템 색상 사용", - "theme_setting_system_theme_switch": "자동 (시스템 설정)", - "theme_setting_theme_subtitle": "앱 테마 선택", - "theme_setting_three_stage_loading_subtitle": "3단계 로딩을 사용하면 로드 성능을 향상시킬 수 있으나, 네트워크 부하가 크게 증가합니다.", - "theme_setting_three_stage_loading_title": "3단계 로드 활성화", - "they_will_be_merged_together": "선택한 인물들을 한 인물로 합칩니다.", - "third_party_resources": "서드 파티 리소스", - "time": "시간", - "time_based_memories": "시간 기준 추억", - "time_based_memories_duration": "각 이미지를 표시하는 데 걸리는 시간(초).", - "timeline": "타임라인", - "timezone": "시간대", - "to_archive": "보관함으로 이동", - "to_change_password": "비밀번호 변경", - "to_favorite": "즐겨찾기", - "to_login": "로그인", - "to_multi_select": "다중 선택", - "to_parent": "상위 항목으로", - "to_select": "선택", - "to_trash": "삭제", - "toggle_settings": "설정 변경", - "toggle_theme_description": "테마 전환", - "total": "전체", - "total_usage": "총 사용량", - "trash": "휴지통", - "trash_action_prompt": "휴지통으로 항목 {count}개 이동됨", - "trash_all": "모두 삭제", - "trash_count": "{count, number}개 삭제", - "trash_delete_asset": "휴지통 이동/삭제", - "trash_emptied": "휴지통을 비웠습니다.", - "trash_no_results_message": "삭제된 사진과 동영상이 여기에 표시됩니다.", - "trash_page_delete_all": "모두 삭제", - "trash_page_empty_trash_dialog_content": "휴지통을 비우시겠습니까? 휴지통의 모든 항목이 Immich에서 영구적으로 삭제됩니다.", - "trash_page_info": "휴지통으로 이동된 항목은 {days}일 후 영구적으로 삭제됩니다.", - "trash_page_no_assets": "휴지통이 비어 있음", - "trash_page_restore_all": "모두 복원", - "trash_page_select_assets_btn": "항목 선택", - "trash_page_title": "휴지통 ({count})", - "trashed_items_will_be_permanently_deleted_after": "휴지통으로 이동된 항목은 {days, plural, one {#일} other {#일}} 후 영구적으로 삭제됩니다.", - "trigger": "트리거", - "trigger_asset_uploaded": "자산 업로드됨", - "trigger_asset_uploaded_description": "새로운 에셋이 업로드될 때 트리거됩니다", - "trigger_description": "워크플로우를 시작하는 이벤트", - "trigger_person_recognized": "신원 확인됨", - "trigger_person_recognized_description": "사람이 감지되면 작동합니다", - "trigger_type": "트리거 유형", - "troubleshoot": "문제 해결", - "type": "형식", - "unable_to_change_pin_code": "PIN 코드를 변경할 수 없음", - "unable_to_check_version": "앱 또는 서버 버전을 확인할 수 없음", - "unable_to_setup_pin_code": "PIN 코드를 설정할 수 없음", - "unarchive": "보관함에서 제거", - "unarchive_action_prompt": "보관함에서 항목 {count}개 제거됨", - "unarchived_count": "보관함에서 항목 {count, plural, other {#개}} 제거됨", - "undo": "실행 취소", - "unfavorite": "즐겨찾기 해제", - "unfavorite_action_prompt": "즐겨찾기에서 항목 {count}개 제거됨", - "unhide_person": "인물 숨김 해제", - "unknown": "알 수 없음", - "unknown_country": "알 수 없는 지역", - "unknown_date": "알 수 없는 날짜", - "unknown_year": "알 수 없는 연도", - "unlimited": "무제한", - "unlink_motion_video": "모션 비디오 링크 해제", - "unlink_oauth": "OAuth 연결 해제", - "unlinked_oauth_account": "OAuth 계정 연결이 해제되었습니다.", - "unmute_memories": "음소거 해제", - "unnamed_album": "이름 없는 앨범", - "unnamed_album_delete_confirmation": "선텍한 앨범을 삭제하시겠습니까?", - "unnamed_share": "이름 없는 공유", - "unsaved_change": "저장되지 않은 변경 사항", - "unselect_all": "모두 선택 해제", - "unselect_all_duplicates": "모두 선택 해제", - "unselect_all_in": "모든 {group} 선택 해제", - "unstack": "스택 풀기", - "unstack_action_prompt": "항목 {count}개 스택 풀림", - "unstacked_assets_count": "항목 {count, plural, one {#개} other {#개}}의 스택을 풀었습니다.", - "unsupported_field_type": "지원되지 않는 필드 유형", - "untagged": "태그 해제됨", - "untitled_workflow": "제목 없는 워크플로", - "up_next": "다음", - "update_location_action_prompt": "선택한 {count}개 항목 위치 업데이트:", - "updated_at": "업데이트됨", - "updated_password": "비밀번호가 변경되었습니다.", - "upload": "업로드", - "upload_concurrency": "업로드 동시성", - "upload_details": "업로드 상세", - "upload_dialog_info": "선택한 항목을 서버에 백업하시겠습니까?", - "upload_dialog_title": "항목 업로드", - "upload_error_with_count": "{count, plural, one {#개} other {#개}} 항목 업로드 실패", - "upload_errors": "업로드가 완료되었습니다. 항목 {count, plural, one {#개} other {#개}}를 업로드하지 못했습니다. 업로드된 항목을 보려면 페이지를 새로고침하세요.", - "upload_finished": "업로드 완료", - "upload_progress": "전체 {total, number}개 중 {processed, number}개 완료, {remaining, number}개 대기 중", - "upload_skipped_duplicates": "중복 항목 {count, plural, one {#개 건너뜀} other {#개 건너뜀}}", - "upload_status_duplicates": "중복", - "upload_status_errors": "오류", - "upload_status_uploaded": "완료", - "upload_success": "업로드가 완료되었습니다. 업로드된 항목을 보려면 페이지를 새로고침하세요.", - "upload_to_immich": "Immich에 업로드 ({count})", - "uploading": "업로드 중", - "uploading_media": "미디어 업로드 중...", - "url": "URL", - "usage": "사용량", - "use_biometric": "생체 인증 사용", - "use_current_connection": "현재 네트워크 사용", - "use_custom_date_range": "대신 맞춤 기간 사용", - "user": "사용자", - "user_has_been_deleted": "이 사용자는 삭제되었습니다.", - "user_id": "사용자 ID", - "user_liked": "{user}님이 {type, select, photo {이 사진} video {이 동영상} asset {이 항목} other {해당 항목}}을 좋아합니다.", - "user_pin_code_settings": "PIN 코드", - "user_pin_code_settings_description": "PIN 코드를 변경하거나 잊어버린 경우 초기화합니다.", - "user_privacy": "개인정보", - "user_purchase_settings": "구매", - "user_purchase_settings_description": "구매 설정을 관리하고 키를 등록 및 제거합니다.", - "user_role_set": "{user}에게 {role} 역할 지정", - "user_usage_detail": "사용자 사용량 상세", - "user_usage_stats": "사용량 통계", - "user_usage_stats_description": "계정의 전체 및 범주별 사용량을 확인합니다.", - "username": "계정명", - "users": "사용자", - "users_added_to_album_count": "사용자 {count, plural, one {#명} other {#명}}을 앨범에 추가했습니다.", - "utilities": "도구", - "validate": "검증", - "validate_endpoint_error": "유효한 URL을 입력하세요.", - "validation_error": "유효성 검사 오류", - "variables": "변수", - "version": "버전", - "version_announcement_closing": "당신의 친구, Alex가", - "version_announcement_message": "안녕하세요! 새로운 버전의 Immich가 출시되었습니다. 설정을 최신 상태로 유지하여 잘못된 구성으로 인한 문제를 방지할 수 있도록, 특히 WatchTower 등의 자동 업데이트 기능을 사용하는 경우 잠시 시간을 내어 릴리스 노트를 확인해보세요.", - "version_history": "버전 기록", - "version_history_item": "{date} {version} 설치", - "video": "동영상", - "video_hover_setting": "섬네일 영상 미리보기", - "video_hover_setting_description": "섬네일 위에 마우스를 올리면 미리보기를 재생합니다. 비활성화해도 재생 아이콘에 마우스를 올려 미리볼 수 있습니다.", - "videos": "동영상", - "videos_count": "동영상 {count, plural, one {#개} other {#개}}", - "view": "보기", - "view_album": "앨범 보기", - "view_all": "모두 보기", - "view_all_users": "모든 사용자 보기", - "view_asset_owners": "자산 소유자 보기", - "view_details": "상세 보기", - "view_in_timeline": "타임라인에서 보기", - "view_link": "링크 보기", - "view_links": "링크 보기", - "view_name": "보기", - "view_next_asset": "다음 항목 보기", - "view_previous_asset": "이전 항목 보기", - "view_qr_code": "QR 코드 보기", - "view_similar_photos": "비슷한 사진 보기", - "view_stack": "스택 보기", - "view_user": "사용자 보기", - "viewer_remove_from_stack": "스택에서 제거", - "viewer_stack_use_as_main_asset": "대표 항목으로 설정", - "viewer_unstack": "스택 풀기", - "visibility_changed": "인물 {count, plural, one {#명} other {#명}}의 표시 여부가 변경됨", - "visual": "비주얼", - "visual_builder": "비주얼 빌더", - "waiting": "대기 중", - "waiting_count": "대기: {count}", - "warning": "경고", - "week": "주", - "welcome": "환영합니다", - "welcome_to_immich": "환영합니다", - "width": "너비", - "wifi_name": "W-Fi 이름", - "workflow_delete_prompt": "이 워크플로를 정말로 삭제하시겠습니까?", - "workflow_deleted": "워크플로가 삭제되었습니다", - "workflow_description": "워크플로 설명", - "workflow_info": "워크플로우 정보", - "workflow_json": "워크플로우 JSON", - "workflow_json_help": "워크플로 구성을 JSON 형식으로 편집하세요. 변경 사항은 비주얼 빌더에 동기화됩니다.", - "workflow_name": "워크플로 이름", - "workflow_navigation_prompt": "변경 사항을 저장하지 않고 이동하시겠습니까?", - "workflow_summary": "워크플로우 요약", - "workflow_update_success": "워크플로가 성공적으로 업데이트되었습니다", - "workflow_updated": "워크플로가 업데이트되었습니다", - "workflows": "워크플로", - "workflows_help_text": "워크플로는 트리거와 필터를 기반으로 자산에 대한 작업을 자동화합니다", - "wrong_pin_code": "잘못된 PIN 코드", - "year": "년", - "years_ago": "{years, plural, one {#년} other {#년}} 전", - "yes": "네", - "you_dont_have_any_shared_links": "공유 링크가 없습니다.", - "your_wifi_name": "Wi-Fi 네트워크 이름", - "zoom_image": "이미지 확대", - "zoom_to_bounds": "화면에 맞춰 확대" -} +{} diff --git a/i18n/lt.json b/i18n/lt.json index 2802bb58ab..0967ef424b 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -1,2128 +1 @@ -{ - "about": "Apie", - "account": "Paskyra", - "account_settings": "Paskyros nustatymai", - "acknowledge": "Patvirtinti", - "action": "Veiksmas", - "action_common_update": "Naujinti", - "action_description": "Veiksmai, kurie atliekami filtruotiems elementams", - "actions": "Veiksmai", - "active": "Vykdoma", - "active_count": "Vykdoma: {count}", - "activity": "Veikla", - "activity_changed": "Veikla yra {enabled, select, true {įjungta} other {išjungta}}", - "add": "Pridėti", - "add_a_description": "Pridėti aprašymą", - "add_a_location": "Pridėti vietovę", - "add_a_name": "Pridėti vardą", - "add_a_title": "Pridėti pavadinimą", - "add_action": "Pridėti veiksmą", - "add_action_description": "Spustelėkite, kad pridėtumėte veiksmą atlikimui", - "add_birthday": "Pridėti gimimo diena", - "add_endpoint": "Pridėti galutinį tašką", - "add_exclusion_pattern": "Pridėti išimčių šabloną", - "add_filter": "Pritaikyti filtrą", - "add_filter_description": "Spustelėkite, kad pridėtumėte filtro sąlygą", - "add_location": "Pridėti vietovę", - "add_more_users": "Pridėti daugiau naudotojų", - "add_partner": "Pridėti partnerį", - "add_path": "Pridėti kelią", - "add_photos": "Pridėti nuotraukų", - "add_tag": "Pridėti žymą", - "add_to": "Pridėti į…", - "add_to_album": "Pridėti į albumą", - "add_to_album_bottom_sheet_added": "Pridėta į {album}", - "add_to_album_bottom_sheet_already_exists": "Jau yra albume {album}", - "add_to_album_bottom_sheet_some_local_assets": "Dalis vietinių elementų negalėjo būti pridėti į albumą", - "add_to_album_toggle": "Perjungti pažymėjimus albumui {album}", - "add_to_albums": "Pridėti į albumus", - "add_to_albums_count": "Pridėti į albumus ({count})", - "add_to_bottom_bar": "Pridėti prie", - "add_to_shared_album": "Pridėti į bendrinamą albumą", - "add_upload_to_stack": "Pridėti įkėlimą į krūvą", - "add_url": "Pridėti URL", - "add_workflow_step": "Pridėti darbų eigos žingsnį", - "added_to_archive": "Pridėta į archyvą", - "added_to_favorites": "Pridėta prie mėgstamiausių", - "added_to_favorites_count": "{count, plural, one {# pridėtas} few {# pridėti} other {# pridėta}} prie mėgstamiausių", - "admin": { - "add_exclusion_pattern_description": "Pridėti išimčių taisykles. Palaikomi simboliai *,**, ir ?. Ignoruoti bet kokius failus bet kuriame aplanke pavadintame \"Raw\", naudokite \"**/RAW/**\". Ignoravimui failų su plėtiniu \".tif\", naudokite \"**/*.tiff\". Aplanko kelio nustatymams, naudokite \"/aplanko/kelias/ignoruoti/**\".", - "admin_user": "Administratorius", - "asset_offline_description": "Šis išorinės bibliotekos elementas nebepasiekiamas diske ir buvo perkeltas į šiukšliadėžę. Jei failas buvo perkeltas toje pačioje bibliotekoje, laiko skalėje rasite naują atitinkamą elementą. Jei norite šį elementą atkurti, įsitikinkite, kad Immich gali pasiekti failą žemiau nurodytu adresu, ir suvykdykite bibliotekos skenavimą.", - "authentication_settings": "Autentifikavimo nustatymai", - "authentication_settings_description": "Tvarkyti slaptažodžių, OAuth ir kitus autentifikavimo nustatymus", - "authentication_settings_disable_all": "Ar tikrai norite išjungti visus prisijungimo būdus? Prisijungimas bus visiškai išjungtas.", - "authentication_settings_reenable": "Norėdami vėl įjungti, naudokite Serverio komandą.", - "background_task_job": "Foninės užduotys", - "backup_database": "Sukurti duomenų bazės išklotinę", - "backup_database_enable_description": "Įgalinti duomenų bazės išklotinės", - "backup_keep_last_amount": "Išsaugomų ankstesnių duomenų bazės išklotinių skaičius", - "backup_onboarding_1_description": "išorinė kopija debesyje arba kitoje fizinėje lokacijoje.", - "backup_onboarding_2_description": "vietinės kopijos kituose prietaisuose. Tai apima pagrindinius failus ir jų vietines kopijas.", - "backup_onboarding_3_description": "viso jūsų duomenų kopijų, įskaitant originalius failus. Tai apima 1 išorinę ir 2 vietines kopijas.", - "backup_onboarding_description": "Jūsų duomenų apsaugojimui rekomenduojama 3-2-1 atsarginės kopijos strategija . Jūs turėtumėte saugoti įkeltų nuotraukų/video bei Immich duomenų bazės kopijas išsamiam atsarginių kopijų sprendimui.", - "backup_onboarding_footer": "Daugiau informacijos apie „Immich“ atsarginių kopijų kūrimą rasite dokumentacijoje.", - "backup_onboarding_parts_title": "3-2-1 atsarginė kopija apima:", - "backup_onboarding_title": "Atsarginės kopijos", - "backup_settings": "Duomenų bazės išklotinių nustatymai", - "backup_settings_description": "Tvarkyti duomenų bazės išklotinės nustatymus. Pastaba: šie darbai nėra stebimi ir jums nebus pranešta apie nesėkmę.", - "cleared_jobs": "Išvalytos užduotys užduočiai: {job}", - "config_set_by_file": "Konfigūracija nustatyta pagal konfigūracinį failą", - "confirm_delete_library": "Ar tikrai norite ištrinti {library} biblioteką?", - "confirm_delete_library_assets": "Ar tikrai norite ištrinti šią biblioteką? Šis veiksmas ištrins {count, plural, one {# contained asset} other {all # contained assets}} iš Immich ir negali būti grąžintas. Failai liks diske.", - "confirm_email_below": "Patvirtinimui įveskite \"{email}\" žemiau", - "confirm_reprocess_all_faces": "Ar tikrai norite iš naujo apdoroti visus veidus? Tai taip pat ištrins įvardytus asmenis.", - "confirm_user_password_reset": "Ar tikrai norite iš naujo nustatyti {user} slaptažodį?", - "confirm_user_pin_code_reset": "Ar tikrai norite iš naujo nustatyti {user} PIN kodą?", - "copy_config_to_clipboard_description": "Kopijuokite dabartinę sistemos konfigūraciją kaip JSON objektą į iškarpinę", - "create_job": "Sukurti užduotį", - "cron_expression": "Cron išraiška", - "cron_expression_description": "Nustatyti skenavimo intervalą naudojant cron formatą. Norėdami gauti daugiau informacijos žiūrėkite Crontab Guru", - "cron_expression_presets": "Išankstiniai Cron nustatymai", - "disable_login": "Išjungti prisijungimą", - "duplicate_detection_job_description": "Vykdyti mašininį mokymąsi panašių vaizdų aptikimui. Priklauso nuo išmaniosios paieškos", - "exclusion_pattern_description": "Išimčių šablonai leidžia nepaisyti failų ir aplankų skenuojant jūsų biblioteką. Tai yra naudinga, jei turite aplankų su failais, kurių nenorite importuoti, pavyzdžiui, RAW failai.", - "export_config_as_json_description": "Atsisiųskite dabartinę sistemos konfigūraciją kaip JSON failą", - "external_libraries_page_description": "Administratoriaus išorinės bibliotekos puslapis", - "face_detection": "Veidų aptikimas", - "face_detection_description": "Veidų aptikimas bibliotekos elementuose naudojant mašininį mokymąsi. Vaizdo įrašų atveju naudojama tik miniatiūra. \"Atnaujinti\" iš naujo nuskaito visus bibliotekos elementus. \"Atstatyti\" ne tik atnaujina, bet ir išvalo visus esamus veidų duomenis. \"Trūkstami\" nuskaito tik dar nenuskaitytus bibliotekos elementus. Veidų aptikimo darbui pasibaigus, aptikti veidai patenka į veidų atpažinimo darbų eilę, kur jie priskiriami jau esamiems ar naujai atpažintiems žmonėms.", - "facial_recognition_job_description": "Aptiktų veidų atpažinimas ir priskyrimas žmonėms. Šis darbas vykdomas pasibaigus \"veidų aptikimo\" darbui. \"Atstatyti\" (per)grupuoja visus aptiktus veidus. \"Trūkstami\" apdoroja jokiam žmogui dar nepriskirtus aptiktus veidus.", - "failed_job_command": "Užduoties {job} komanda {command} nepavyko", - "force_delete_user_warning": "ĮSPĖJIMAS: Šis veiksmas iš karto pašalins naudotoją ir visą jo informaciją. Šis žingsnis nesugrąžinamas ir failų nebus galima atkurti.", - "image_format": "Formatas", - "image_format_description": "WebP sukuria mažesnius failus nei JPEG, tačiau lėčiau juos apdoroja.", - "image_fullsize_description": "Pilno dydžio nuotrauka be meta duomenų naudojama priartinus", - "image_fullsize_enabled": "Įgalinti pilno dydžio nuotraukų generavimą", - "image_fullsize_enabled_description": "Generuoti viso dydžio vaizdą naršyklėms nepritaikytiems formatams. Kai įjungta parinktis „Pirmenybė įterptai peržiūrai“, įterptosios peržiūros naudojamos tiesiogiai be konvertavimo. Tai neturi įtakos internetui pritaikytiems formatams, pvz., JPEG.", - "image_fullsize_quality_description": "Pilno dydžio nuotraukų kokybė 1-100. Didesnė yra geresnė, tačiau sukuria didesniu failus.", - "image_fullsize_title": "Pilno dydžio nuotraukų Nustatymai", - "image_prefer_embedded_preview": "Pageidautinai rodyti įterptą peržiūrą", - "image_prefer_embedded_preview_setting_description": "Naudokite įterptąsias peržiūras RAW nuotraukose kaip įvestį vaizdų apdorojimui ir, jei įmanoma, tai gali suteikti tikslesnes kai kurių vaizdų spalvas, tačiau peržiūros kokybė priklauso nuo fotoaparato, todėl vaizde gali būti daugiau glaudinimo artefaktų.", - "image_prefer_wide_gamut": "Teikti pirmenybę plačiai gamai", - "image_prefer_wide_gamut_setting_description": "Miniatiūroms naudokite „Display P3“. Taip geriau išsaugomas vaizdų, turinčių plačias spalvų erdves, ryškumas, tačiau senesniuose įrenginiuose su senesne naršyklės versija vaizdai gali atrodyti kitaip. sRGB vaizdai išsaugomi kaip sRGB, kad būtų išvengta spalvų pasikeitimo.", - "image_preview_description": "Vidutinio dydžio vaizdas su išvalytais metaduomenimis, naudojamas kai žiūrimas vienas objektas arba mašininiam mokymuisi", - "image_preview_quality_description": "Peržiūros kokybė nuo 1-100. Aukštesnės reikšmės yra geriau, bet sukuriami didesni failai gali sumažinti programos reagavimo laiką. Mažos vertės nustatymas gali paveikti mašininio mokymo kokybę.", - "image_preview_title": "Peržiūros nustatymai", - "image_quality": "Kokybė", - "image_resolution": "Rezoliucija", - "image_resolution_description": "Didesnės rezoliucijos gali išsaugoti daugiau detalių, bet ilgiau užtrunka užkoduoti, failai yra didesni ir programos reagavimo laikas gali sumažėti.", - "image_settings": "Nuotraukos nustatymai", - "image_settings_description": "Keisti sugeneruotų nuotraukų kokybę ir rezoliuciją", - "image_thumbnail_description": "Maža miniatiūra su išvalytais metaduomenimis, naudojama kai žiūrimos nuotraukų grupės, kaip ir pagrindinėje laiko juostoje", - "image_thumbnail_quality_description": "Miniatiūros kokybė nuo 1-100. Aukštesnės reikšmės yra geriau, bet pagaminami didesni failai ir gali būti sulėtintas programos reagavimo greitis.", - "image_thumbnail_title": "Miniatiūros nustatymai", - "import_config_from_json_description": "Importuokite sistemos konfigūraciją, įkeliant JSON konfigūracijos failą", - "job_concurrency": "{job} lygiagretumas", - "job_created": "Užduotis sukurta", - "job_not_concurrency_safe": "Ši užduotis nėra saugi apdoroti lygiagrečiai.", - "job_settings": "Užduočių nustatymai", - "job_settings_description": "Keisti užduočių lygiagretumą", - "jobs_delayed": "{jobCount, plural, one {# atidėtas} few {# atidėti} other {# atidėtų}}", - "jobs_failed": "{jobCount, plural, other {# nepavyko}}", - "jobs_over_time": "Užduotys per laiką", - "library_created": "Sukurta biblioteka: {library}", - "library_deleted": "Biblioteka ištrinta", - "library_details": "Bibliotekos savybės", - "library_folder_description": "Nurodykite importuotiną aplanką. Šis aplankas, įskaitant poaplankius, bus nuskaitytas ieškant vaizdų ir vaizdo įrašų.", - "library_remove_exclusion_pattern_prompt": "Ar tikrai norite pašalinti šią išimtį?", - "library_remove_folder_prompt": "Ar tikrai norite pašalinti šį importo aplanką?", - "library_scanning": "Periodinis skenavimas", - "library_scanning_description": "Konfigūruoti periodinį bibliotekos skanavimą", - "library_scanning_enable_description": "Įgalinti periodinį bibliotekos skenavimą", - "library_settings": "Išorinė biblioteka", - "library_settings_description": "Tvarkyti išorinės bibliotekos parametrus", - "library_tasks_description": "Skenuoti išorines bibliotekas, ieškant naujų arba pakeistų išteklių", - "library_updated": "Atnaujinta biblioteka", - "library_watching_enable_description": "Stebėti išorines bibliotekas dėl failų pakeitimų", - "library_watching_settings": "Bibliotekų stebėjimas (EKSPERIMENTINIS", - "library_watching_settings_description": "Automatiškai stebėti dėl pakeistų failų", - "logging_enable_description": "Įjungti žurnalo vedimą", - "logging_level_description": "Įjungus, kokį žurnalo vedimo lygį naudot.", - "logging_settings": "Žurnalo vedimas", - "machine_learning_availability_checks": "Prieinamumo patikrinimai", - "machine_learning_availability_checks_description": "Automatiškai aptikti ir teikti pirmenybę prieinamiems mašininio mokymosi serveriams", - "machine_learning_availability_checks_enabled": "Įjungti prieinamumo patikrinimus", - "machine_learning_availability_checks_interval": "Patikros intervalas", - "machine_learning_availability_checks_interval_description": "Intervalas milisekundėmis tarp prieinamumo patikrinimų", - "machine_learning_availability_checks_timeout": "Užklausos laiko limitas", - "machine_learning_availability_checks_timeout_description": "Laiko limitas milisekundėmis prieinamumo patikrinimams", - "machine_learning_clip_model": "CLIP modelis", - "machine_learning_clip_model_description": "Pavadinimas CLIP modelio įvardintio here. Dėmesio, keičiant modelį jūs privalote iš naujo paleisti 'Išmaniosios Paieškos' užduotį visiems vaizdams.", - "machine_learning_duplicate_detection": "Dublikatų aptikimas", - "machine_learning_duplicate_detection_enabled": "Įjungti dublikatų aptikimą", - "machine_learning_duplicate_detection_enabled_description": "Jei išjungta, visiškai identiški elementai vis tiek bus deduplikuoti.", - "machine_learning_duplicate_detection_setting_description": "Naudoti CLIP įterpimus, norint rasti galimus duplikatus", - "machine_learning_enabled": "Įgalinti mašininį mokymąsi", - "machine_learning_enabled_description": "Jei išjungta, visos „ML“ funkcijos bus išjungtos, nepaisant toliau pateiktų nustatymų.", - "machine_learning_facial_recognition": "Veidų atpažinimas", - "machine_learning_facial_recognition_description": "Aptikti, atpažinti ir sugrupuoti veidus nuotraukose", - "machine_learning_facial_recognition_model": "Veidų atpažinimo modelis", - "machine_learning_facial_recognition_model_description": "Modeliai išvardinti apimties mažėjančia tvarka. Didieji modeliai yra lėti ir naudoja daugiau atminties, tačiau sukuria geresnius rezultatus. Pastebime kad keičiant modelį jūs turite iš naujo paleisti Veidų Atpažinimo užduotį visiems vaizdams.", - "machine_learning_facial_recognition_setting": "Įgalinti veidų atpažinimą", - "machine_learning_facial_recognition_setting_description": "Išjungus, vaizdai nebus užšifruoti veidų atpažinimui ir nebus naudojami Žmonių sekcijoje Naršymo puslapyje.", - "machine_learning_max_detection_distance": "Maksimalus aptikimo atstumas", - "machine_learning_max_detection_distance_description": "Didžiausias atstumas tarp dviejų vaizdų, kad jie būtų laikomi dublikatais, svyruoja nuo 0,001 iki 0,1. Didesnės vertės aptiks daugiau dublikatų, tačiau gali būti klaidingai teigiami.", - "machine_learning_max_recognition_distance": "Maksimalus atpažinimo atstumas", - "machine_learning_max_recognition_distance_description": "Didžiausias skirtumas tarp veidų, kad būtų užskaityti kaip vienas ir tas pats asmuo, rėžis nuo 0-2. Mažinant tai gali apsaugoti nuo dviejų žmonių žymėjimo tuo pačiu asmeniu, didinant tai gali apsaugoti nuo to pačio asmens žymėjimo kaip du skirtingus žmones. Pastebime kad yra paprasčiau apjungti kelių žmonių modelius į vieną nei vieną išdalinti į du, taigi kai įmanoma geriau naudoti mažensę ribą.", - "machine_learning_min_detection_score": "Minimalus aptikimo balas", - "machine_learning_min_detection_score_description": "Minimalus užtikrintumo balas veido aptikimui nuo 0-1. Mažesnė reikšmė aptiks daugiau veidų tačiau bus ir daugiau klaidingų teigiamų režultatų.", - "machine_learning_min_recognized_faces": "Mažiausias atpažintų veidų skaičius", - "machine_learning_min_recognized_faces_description": "Mažiausias atpažintų veidų skaičius asmeniui, kurį reikia sukurti. Tai padidinus, veido atpažinimas tampa tikslesnis, bet padidėja tikimybė, kad veidas žmogui nepriskirtas.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Naudoti mašininį mokymąsį, teksto atpažinimui nuotraukose", - "machine_learning_ocr_enabled": "Įjungti OCR", - "machine_learning_ocr_enabled_description": "Jei šis parametras išjungtas, vaizdams nebus pritaikytas teksto atpažinimas.", - "machine_learning_ocr_max_resolution": "Maksimali skiriamoji geba", - "machine_learning_ocr_max_resolution_description": "Peržiūros, kurių skiriamoji geba yra didesnė nei ši, bus pakeistos išlaikant proporcijas. Didesnės vertės yra tikslesnės, tačiau jų apdorojimas trunka ilgiau ir sunaudoja daugiau atminties.", - "machine_learning_ocr_min_detection_score": "Minimalus atpažinimo balas", - "machine_learning_ocr_min_detection_score_description": "Minimalus pasitikėjimo balas, reikalingas tekstui aptikti, yra nuo 0 iki 1. Mažesnės vertės aptiks daugiau teksto, bet gali sukelti klaidingų teigiamų rezultatų.", - "machine_learning_ocr_min_recognition_score": "Minimalus atpažinimo balas", - "machine_learning_ocr_min_score_recognition_description": "Minimalus pasitikėjimo balas, kad aptiktas tekstas būtų atpažintas nuo 0 iki 1. Mažesnės vertės atpažins daugiau teksto, bet gali sukelti klaidingus teigiamus rezultatus.", - "machine_learning_ocr_model": "OCR modelis", - "machine_learning_ocr_model_description": "Serverių modeliai yra tikslesni nei mobilieji modeliai, tačiau jų apdorojimas trunka ilgiau ir jie naudoja daugiau atminties.", - "machine_learning_settings": "Mašininio mokymosi nustatymai", - "machine_learning_settings_description": "Tvarkyti mašininio mokymosi funkcijas ir nustatymus", - "machine_learning_smart_search": "Išmanioji paieška", - "machine_learning_smart_search_description": "Semantiškai ieškoti vaizdų naudojant CLIP įtarpius", - "machine_learning_smart_search_enabled": "Įjungti išmaniąją paiešką", - "machine_learning_smart_search_enabled_description": "Jei išjungta, vaizdai nebus užkoduoti išmaniajai paieškai.", - "machine_learning_url_description": "Mašininio mokymosi serverio URL. Jei pateikta daugiau nei vienas URL, serveriai bus bandomi eilės tvarka nuo pirmo iki paskutinio tol, kol bus rastas vienas veikiantis serveris.", - "maintenance_settings": "Aptarnavimas", - "maintenance_settings_description": "Perjungti „Immich“ į aptarnavimo režimą.", - "maintenance_start": "Paleisti aptarnavimo režimą", - "maintenance_start_error": "Nepavyko paleisti aptarnavimo režimo.", - "manage_concurrency": "Tvarkyti lygiagretumą", - "manage_concurrency_description": "Eikite į darbų puslapį, kad galėtumėte valdyti darbų lygiagretumą", - "manage_log_settings": "Valdyti žurnalo nuostatas", - "map_dark_style": "Tamsioji tema", - "map_enable_description": "Įgalinti žemėlapio funkcijas", - "map_gps_settings": "Žemėlapio ir GPS nustatymai", - "map_gps_settings_description": "Tvarkyti žemėlapio ir GPS (atvirkštinio geokodavimo) nustatymus", - "map_implications": "Žemėlapio funkcija naudojasi išorine plytelių paslauga (tiles.immich.cloud)", - "map_light_style": "Šviesioji tema", - "map_manage_reverse_geocoding_settings": "Tvarkyti atvirkštinio geokodavimo nustatymus", - "map_reverse_geocoding": "Atvirkštinis geokodavimas", - "map_reverse_geocoding_enable_description": "Įjungti atvirkštinį geokodavimą", - "map_reverse_geocoding_settings": "Atvirkštinio geokodavimo nustatymai", - "map_settings": "Žemėlapis", - "map_settings_description": "Tvarkyti žemėlapio parametrus", - "map_style_description": "URL į style.json žemėlapio temą", - "memory_cleanup_job": "Atsiminimų valymas", - "memory_generate_job": "Atsiminimų generavimas", - "metadata_extraction_job": "Metaduomenų nuskaitymas", - "metadata_extraction_job_description": "Kiekvieno bibliotekos elemento metaduomenų nuskaitymas, tokių kaip GPS koordinatės, veidai ar rezoliucija", - "metadata_faces_import_setting": "Įjungti veidų importą", - "metadata_faces_import_setting_description": "Importuoti veidus iš vaizdo EXIF duomenų ir susietų failų", - "metadata_settings": "Metaduomenų nustatymai", - "metadata_settings_description": "Tvarkyti metaduomenų nustatymus", - "migration_job": "Tvarkymas", - "migration_job_description": "Pertvarkytį turinio ir veidų miniatiūras pagal naują struktūrą", - "nightly_tasks_cluster_faces_setting_description": "Paleisti veido atpažinimą naujai aptiktiems veidams", - "nightly_tasks_cluster_new_faces_setting": "Sugrupuoti naujus veidus", - "nightly_tasks_database_cleanup_setting": "Duomenų bazės valymo darbai", - "nightly_tasks_database_cleanup_setting_description": "Išvalyti senus, nebgaliojančius duomenis iš duomenų bazės", - "nightly_tasks_generate_memories_setting": "Kurti prisiminimus", - "nightly_tasks_generate_memories_setting_description": "Iš duomenų kurti naujus prisiminimus", - "nightly_tasks_missing_thumbnails_setting": "Kurti trūkstamas miniatiūras", - "nightly_tasks_missing_thumbnails_setting_description": "Sudaryti įrašų be miniatiūrų eilę miniatiūrų generavimui", - "nightly_tasks_settings": "Naktinių užduočių nustatymai", - "nightly_tasks_settings_description": "Valdyti naktines užduotis", - "nightly_tasks_start_time_setting": "Pradžios laikas", - "nightly_tasks_start_time_setting_description": "Laikas kada serveris pradės vykdyti naktines užduotis", - "nightly_tasks_sync_quota_usage_setting": "Sinchronizuoti kvotos naudojimą", - "nightly_tasks_sync_quota_usage_setting_description": "Atnaujinti vartotojo vietos kvotą remiantis dabartiniu vartojimu", - "no_paths_added": "Keliai nepridėti", - "no_pattern_added": "Šablonas nepridėtas", - "note_apply_storage_label_previous_assets": "Pastaba: norėdami pritaikyti Saugyklos Žymą seniau įkeltiems ištekliams, paleiskite", - "note_cannot_be_changed_later": "PASTABA: Vėliau to pakeisti negalima!", - "notification_email_from_address": "Iš adreso", - "notification_email_from_address_description": "Siuntėjo el. pašto adresas, pavyzdžiui: \"Immich Photo Server \". Būtinai naudokite adresą iš kurio jums galima siųsti laiškus.", - "notification_email_host_description": "Elektroninio pašto serverio adresas (pvz. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Nepaisyti sertifikatų klaidų", - "notification_email_ignore_certificate_errors_description": "Nepaisyti TLS sertifikato patvirtinimo klaidų (nerekomenduojama)", - "notification_email_password_description": "Slaptažodis, naudojant autentikacijai su elektroninio pašto serveriu", - "notification_email_port_description": "El. pašto serverio prievadas (pvz. 25, 465 arba 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Naudoti SMTPS (SMTP per TLS)", - "notification_email_sent_test_email_button": "Siųsti bandomąjį el. laišką ir išsaugoti", - "notification_email_setting_description": "El. pašto pranešimų siuntimo nustatymai", - "notification_email_test_email": "Išsiųsti bandomąjį el. laišką", - "notification_email_test_email_failed": "Nepavyko išsiųsti bandomojo el. laiško, patikrinkite savo nustatymus", - "notification_email_test_email_sent": "Bandomasis el. laiškas buvo išsiųstas į {email}. Patikrinkite savo pašto dėžutę.", - "notification_email_username_description": "Vartotojo vardas, naudojant autentikacijai su elektroninio pašto serveriu", - "notification_enable_email_notifications": "Įgalinti el. pašto pranešimus", - "notification_settings": "Pranešimų nustatymai", - "notification_settings_description": "Tvarkyti pranešimų nustatymus, įskaitant el. pašto", - "oauth_auto_launch": "Paleisti automatiškai", - "oauth_auto_launch_description": "Prisijungimo puslapyje automatiškai pradėti OAuth prisijungimo procesą", - "oauth_auto_register": "Automatinis registravimas", - "oauth_auto_register_description": "Automatiškai užregistruoti naujus naudotojus po prisijungimo per OAuth", - "oauth_button_text": "Mygtuko tekstas", - "oauth_client_secret_description": "Privalomas jei PKCE (Proof Key for Code Exchange) nepalaikomas pagal OAuth tiekėją", - "oauth_enable_description": "Prisijungti su OAuth", - "oauth_mobile_redirect_uri": "Mobiliojo peradresavimo URI", - "oauth_mobile_redirect_uri_override": "Mobiliojo peradresavimo URI pakeitimas", - "oauth_mobile_redirect_uri_override_description": "Įjunkite, kai OAuth teikėjas nepalaiko mobiliojo URI, tokio kaip ''{callback}''", - "oauth_role_claim": "Rolės Tvirtinimas", - "oauth_role_claim_description": "Suteikti admin teises automatiškai pagal šios rolės tvirtinimo buvimą. Tvirtinimas gali turėti priskirtus arba 'vartotoją' arba 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Tvarkyti OAuth prisijungimo nustatymus", - "oauth_settings_more_details": "Detaliau apie šią funkciją galite paskaityti dokumentacijoje.", - "oauth_storage_label_claim": "Saugyklos Žyma pagal tvirtinimą", - "oauth_storage_label_claim_description": "Priskirti Saugyklos Žymą automatiškai pagal reikšmę vartotojo tvirtinime.", - "oauth_storage_quota_claim": "Saugyklos apimties tvirtinimas", - "oauth_storage_quota_claim_description": "Priskirti vartotojo saugyklos apimties kvotą automatiškai pagal šio tvirtinimo reikšmę.", - "oauth_storage_quota_default": "Numatyta atminties kvota (GiB)", - "oauth_storage_quota_default_description": "Nustatoma appimties kvota GiB kai nėra nurodyta tvirtinime.", - "oauth_timeout": "Užklausa viršijo laiko limitą", - "oauth_timeout_description": "Laiko limitas užklausoms milisekundėmis", - "ocr_job_description": "Naudoti mašininį mokymąsi teksto atpažinimui nuotraukose", - "password_enable_description": "Prisijungti su el. paštu ir slaptažodžiu", - "password_settings": "Prisijungimas slaptažodžiu", - "password_settings_description": "Tvarkyti prisijungimo slaptažodžiu nustatymus", - "paths_validated_successfully": "Visi keliai patvirtinti sėkmingai", - "person_cleanup_job": "Išvalyti asmenis", - "queue_details": "Išsami informacija apie eilę", - "queues": "Darbų eilės", - "queues_page_description": "Administratoriaus darbų eilės puslapis", - "quota_size_gib": "Kvotos dydis (GiB)", - "refreshing_all_libraries": "Perkraunamos visos bibliotekos", - "registration": "Administratoriaus registracija", - "registration_description": "Kadangi esate pirmasis šio sistemos naudotojas, jums bus priskirta administratoriaus rolė, ir būsite atsakingas už administracines užduotis ir papildomų naudotojų kūrimą.", - "remove_failed_jobs": "Pašalinti nepavykusius darbus", - "require_password_change_on_login": "Reikalauti, kad naudotojas pasikeistų slaptažodį po pirmojo prisijungimo", - "reset_settings_to_default": "Atstatyti nustatymus į numatytuosius", - "reset_settings_to_recent_saved": "Nustatymų atstatymas į neseniai išsaugotus nustatymus", - "scanning_library": "Biblioteka skenuojama", - "search_jobs": "Ieškoma užduočių…", - "send_welcome_email": "Siųsti sveikinimo el. laišką", - "server_external_domain_settings": "Išorinis domenas", - "server_external_domain_settings_description": "Bendrinimo nuorodų domenas, įskaitant http(s)://", - "server_public_users": "Vieši naudotojai", - "server_public_users_description": "Pridedant naudotoją į bendrinamus albumus, rodomas visų naudotojų sąrašas (vardas ir el. paštas). Jei išjungta, naudotojų sąrašas bus prieinamas tik administratorių paskyroms.", - "server_settings": "Serverio nustatymai", - "server_settings_description": "Tvarkyti serverio nustatymus", - "server_stats_page_description": "Administratoriaus serverio statistikos puslapis", - "server_welcome_message": "Sveikinimo pranešimas", - "server_welcome_message_description": "Žinutė, rodoma prisijungimo puslapyje.", - "settings_page_description": "Administratoriaus nustatymų puslapis", - "sidecar_job": "Sidecar metaduomenys", - "sidecar_job_description": "Aptikti ar sinchronizuoti sidecar metaduomenis iš failų sistemos", - "slideshow_duration_description": "Sekundžių skaičius, kiek viena nuotrauka rodoma", - "smart_search_job_description": "Vykdykite mašininį mokymąsi bibliotekos elementų išmaniajai paieškai", - "storage_template_date_time_description": "Elemento sukūrimo laiko žymė yra naudojama laiko informacijai", - "storage_template_date_time_sample": "Pavyzdinis laikas {date}", - "storage_template_enable_description": "Aktyvuoti saugyklos šabloną", - "storage_template_hash_verification_enabled": "Aktyvuoti failo parašo tikrinimą", - "storage_template_hash_verification_enabled_description": "Aktyvuojamas failo parašo tikrinimas, neišjungti nebent gerai suprantate galimas pasekmes", - "storage_template_migration": "Saugyklos tvarkymas pagal šabloną", - "storage_template_migration_description": "Taikyti dabartinį {template} anksčiau įkeltiems duomenims", - "storage_template_migration_info": "Saugyklos tvarkyklė konvertuos visus plėtinius mažosiomis raidėmis. Šablonas bus taikomas tik naujiems duomenims. Taikyti šabloną retroaktyviai anksčiau įkeltiems duomenims, paleiskite šią {job}.", - "storage_template_migration_job": "Saugyklos Tvarkymo Pagal Šabloną Užduotis", - "storage_template_more_details": "Daugiau detalių apie šią funkciją, atsižvelkite į Storage Template ir jo galimus implications", - "storage_template_onboarding_description_v2": "Kai aktyvuota, ši funkcija automatiškai sukurs failus pagal vartotojo-nustatytą šabloną. Daugiau informacijos, prašome skaityti documentation.", - "storage_template_path_length": "Preliminarus struktūros kelio ilgis/limitas:{length, number}/{limit, number}", - "storage_template_settings": "Saugyklos Šablonas", - "storage_template_settings_description": "Tvarkyti aplankų struktūrą bei failų pavadinimus įkeliamiems duomenims", - "storage_template_user_label": "{label} yra vartotojo Saugyklos Žymą", - "system_settings": "Sistemos nustatymai", - "tag_cleanup_job": "Žymų išvalymas", - "template_email_available_tags": "Savo šablone galite naudoti nurodytas kintamas reikšmes:{tags}", - "template_email_if_empty": "Jei šablone tuščia reikšmė, bus naudojamas numatytas pagal nutylėjimą El. pašto adresas.", - "template_email_invite_album": "Kvietimų albumo šablonas", - "template_email_preview": "Peržiūra", - "template_email_settings": "El. pašto Šablonai", - "template_email_update_album": "Atnaujinti albumo šabloną", - "template_email_welcome": "Sveikinimo el. laiško šablonas", - "template_settings": "Pranešimų šablonai", - "template_settings_description": "Tvarkyti pasirinktinius pranešimų šablonus", - "theme_custom_css_settings": "Individualizuotas CSS", - "theme_custom_css_settings_description": "CSS leidžiantis keisti Immich dizainą.", - "theme_settings": "Temos nustatymai", - "theme_settings_description": "Valdyti Immich web sąsajos pritaikymus", - "thumbnail_generation_job": "Generuoti Miniatiūras", - "thumbnail_generation_job_description": "Didelių, mažų ir neryškių miniatiūrų generavimas kiekvienam bibliotekos elementui, taip pat miniatiūrų generavimas kiekvienam asmeniui", - "transcoding_acceleration_api": "Spartinimo API", - "transcoding_acceleration_api_description": "API kurį naudos paspartintam perkodavimui. Tai veiks pagal \"geriausią bandymą\": nepavykus bus naudojamas programinis perkodavimas. VP9 gali veikti arba ne priklausomai nuo jūsų techninės įrangos.", - "transcoding_acceleration_nvenc": "NVENC (reikalinga NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (reikalingas 7-os arba vėlesnės generacijos Intel procesorius)", - "transcoding_acceleration_rkmpp": "RKMPP (tik Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Priimtini garso kodekai", - "transcoding_accepted_audio_codecs_description": "Pasirinkti kurių garso kodekų nereikia perkoduoti. Naudojma tik kai kurioms perkodavimo taisyklėms.", - "transcoding_accepted_containers": "Priimami konteineriai", - "transcoding_accepted_containers_description": "Pasirinkti kurių konteinerių formatų nereikia performuoti į MP4. Naudojama tik kai kurioms perkodavimo taisyklėms.", - "transcoding_accepted_video_codecs": "Priimami vaizdo kodekai", - "transcoding_accepted_video_codecs_description": "Pasirinkti vaizdo kodekus kurių nereikia perkoduoti. Naudojama tik kai kurioms perkodavimo taisyklėms.", - "transcoding_advanced_options_description": "Parinktys, kurių daugelis naudotojų keisti neturėtų", - "transcoding_audio_codec": "Garso kodekas", - "transcoding_audio_codec_description": "Opus yra aukščiausios kokybės variantas, tačiau turi mažesnį suderinamumą su senesniais įrenginiais ar programine įranga.", - "transcoding_bitrate_description": "Vaizdo įrašai viršija maksimalią leistiną bitų spartą arba nėra priimtino formato", - "transcoding_codecs_learn_more": "Sužinoti daugiau apie naudojamą terminologiją, naudokite FFmpeg dokumentaciją H.264 codec, HEVC codec and VP9 codec.", - "transcoding_constant_quality_mode": "Pastovios kokybės režimas", - "transcoding_constant_quality_mode_description": "ICQ yra geriau nei CPQ, tačiau ne visi įrenginiai palaiko šį spartinimo būdą. Šis pasirinkimas būtų naudojamas kai nustatytas Kodavimas Pagal Kokybę. NVENC nepalaiko šio pasirinkimo todėl bus ignoruojamas.", - "transcoding_constant_rate_factor": "Pastovaus greičio faktorius (-crf)", - "transcoding_constant_rate_factor_description": "Video kokybės lygis. Tipinės reikšmės yra 23 jei H.264, 28 jei HVEC, 31 jei VP9, ir 35 jei AV1. Kuo mažesnis tuo kokybiškesnis tačiau didesni failai.", - "transcoding_disabled_description": "Nedaryti perkodavimo, įrašų peržiūra gali neveikti ant kai kūrių sąsajų", - "transcoding_encoding_options": "Užkodavimo parinktys", - "transcoding_encoding_options_description": "Nustatyti kodekus, rezoliuciją, kokybę ir kitas parinktis užkoduojamiems vaizdo įrašams", - "transcoding_hardware_acceleration": "Techninės įrangos spartinimas", - "transcoding_hardware_acceleration_description": "Eksperimentinis: greitesnis perkodavimas, bet galimai prastesne kokybe prie tos pačios bitų spartos", - "transcoding_hardware_decoding": "Aparatinis dekodavimas", - "transcoding_hardware_decoding_setting_description": "Įgalina visapusišką paspartinimą vietoje tik užkodavimo paspartinimo. Gali neveikti su kai kuriais vaizdo įrašais.", - "transcoding_max_b_frames": "Maksimaliai B-kadrų", - "transcoding_max_b_frames_description": "Didesnės reikšmės pagerina suspaudimo efektyvumą, bet sulėtina užkodavimą. Senesniuose prietaisuose gali būti nepalaikomas aparatinis spartinimas. 0 išjungia B-kadrus, o -1 nustato reikšmę automatiškai.", - "transcoding_max_bitrate": "Maksimalus bitų srautas", - "transcoding_max_bitrate_description": "Pasirenkant max bitrate galima pasiekti labiau nuspėjamą failų dydį su minimaliais kokybės praradimais. Prie 720p, tipinės reikšmės yra 2600 kbits/s jei BP9 ar HVEC, arba 4500 kbits/s jei H.264. Neveiksnus jei pasirenkamas 0. Kai vienetai nenurodyti, priimama k (kaip kbits/s); taigi 5000, 5000k, ir 5M (kaip Mbits/s) yra atitikmenys.", - "transcoding_max_keyframe_interval": "Maksimalus raktinio kadro intervalas", - "transcoding_max_keyframe_interval_description": "Nustato maksimalų kadro atstumą tarp raktinių kadrų. Žemesnės reikšmės pablogina suspaudimo efektyvumą, bet pagerina prasukimo laiką ir gali pagerinti greito veiksmo scenų kokybę. 0 - nustato šią reikšmę automatiškai.", - "transcoding_optimal_description": "Vaizdo įrašai aukštesne nei tikslinė rezoliucija arba nepalaikomu formatu", - "transcoding_policy": "Transkodavimo politika", - "transcoding_policy_description": "Nustatyti kada vaizdo įrašas bus perkoduotas", - "transcoding_preferred_hardware_device": "Pageidaujamas aparatinės įrangos įrenginys", - "transcoding_preferred_hardware_device_description": "Galioja tik VAAPI ir QSV. Nustato dri mazgą aparatiniam perkodavimui.", - "transcoding_preset_preset": "Iš anksto nustatytas (-preset)", - "transcoding_preset_preset_description": "Kompresijos greitis. Siekiant tam tikro bitrate lėtesnis apdorojimas lems mažesnius failų dydžius ir padidins kokybę. VP9 ignoruos greičius virš \"gretesnis\" lygio.", - "transcoding_reference_frames": "Nuorodiniai kadrai", - "transcoding_reference_frames_description": "Kadrų, į kuriuos reikia remtis suspaudžiant duotą kadrą, skaičius. Aukštesnė reikšmė pagerina suspaudimo efektyvumą, bet sulėtina užkodavimą. 0 - nustato reikšmę automatiškai.", - "transcoding_required_description": "Tik nepalaikomo formato vaizdo įrašai", - "transcoding_settings": "Vaizdo įrašų perkodavimo nustatymai", - "transcoding_settings_description": "Valdyti kuriuos vaizdo įrašus perkoduoti ir kaip juos apdoroti", - "transcoding_target_resolution": "Skiriamoji geba", - "transcoding_target_resolution_description": "Didesnės skiriamosios gebos gali išsaugoti daugiau detalių, tačiau jas koduoti užtrunka ilgiau, failų dydžiai yra didesni ir gali sumažėti programos jautrumas.", - "transcoding_temporal_aq": "Laikinas adaptyvus kvantavimas", - "transcoding_temporal_aq_description": "Galioja tik NVENC. Temporal Adaptive Quantization pagerina kokybę didesnės raiškos, mažo judesio scenų kokybę. Gali būti nepalaikoma senesnių įrenginių.", - "transcoding_threads": "Gijos", - "transcoding_threads_description": "Didesnės reikšmės pagreitina kodavimą, bet kol aktyvus palieka mažiau serverio resursų kitoms užduotims. Ši reikšmė negali būti didesnė už procesoriaus branduolių kiekį. Jei reikšmė 0, tai išnaudoja maksimaliai.", - "transcoding_tone_mapping": "Tonų atvaizdavimas", - "transcoding_tone_mapping_description": "Bandoma išsaugoti HDR vaizdo įrašų išvaizdą konvertuojant į SDR. Kiekvienas algoritmas taiko skirtingus kompromisus dėl spalvų, detalių ir šviesumo. Hable išsaugo detales, Mobius išsaugo spalvas, o Reinhard išsaugo šviesumą.", - "transcoding_transcode_policy": "Perkodavimo strategija", - "transcoding_transcode_policy_description": "Strategija, kada vaizdo įrašas turi būti perkoduotas. HDR vaizdo įrašai visada bus perkoduoti (išskyrus jei perkodavimas išjungtas).", - "transcoding_two_pass_encoding": "Dviejų perėjimų užkodavimas", - "transcoding_two_pass_encoding_setting_description": "Perkoduoti su dviem perėjimais, kad gauti geriau užkoduotą vaizdo įrašą. Kai maksimalus bitų srautas įjungtas (veikimui reikalaujamas H.264 ir HVEC), tada naudojamas bitų intervalas remiantis maksimaliu bitų srautu ir ignoruojamas CRF. Su VP9 gali būti naudojamas CRF, jei maksimalus bitų srautas yra išjungtas.", - "transcoding_video_codec": "Video kodekas", - "transcoding_video_codec_description": "VP9 turi didelį efektyvumą ir tinklo suderinamumą, bet užtrunka ilgiau perkoduojant. HVEC veikia panašiai, bet turi mažesnį tinklo suderinamumą. H.264 yra plačiai palaikomas ir greitai perkoduojamas, bet kuria didelius failus. AV1 yra efektyviausias kodekas, bet nepalaikomas senesnių prietaisų.", - "trash_enabled_description": "Įgalinti šiukšliadėžės funkcijas", - "trash_number_of_days": "Dienų skaičius", - "trash_number_of_days_description": "Kiek dienų bus laikomi elementai šiukšliadėžėje prieš galutinai juos ištrinant", - "trash_settings": "Šiukšliadėžės nustatymai", - "trash_settings_description": "Tvarkyti šiukšliadėžės nustatymus", - "unlink_all_oauth_accounts": "Atsieti visas OAuth paskyras", - "unlink_all_oauth_accounts_description": "Nepamirškite atsieti visas OAuth paskyras prieš migruojant pas naują tiekėją.", - "unlink_all_oauth_accounts_prompt": "Ar tikrai norite atsieti visas OAuth paskyras? Tai negrįžtama operacija kuri atstatys OAuth ID kiekvienam vartotojui.", - "user_cleanup_job": "Vartotojų išvalymas", - "user_delete_delay": "{user} paskyra ir elementai bus nustatyti galutiniam ištrynimui už {delay, plural, one {# dienos} other {# dienų}}.", - "user_delete_delay_settings": "Ištrynimo delsa", - "user_delete_delay_settings_description": "Skaičius dienų po ištrynimo kuomet naudotojo paskyra ir susiję duomenys bus negražinamai ištrinti. Naudotojo trynimo užduotis paleidžiama vidurnaktį ir tikrina kurie naudotojai gali būti trinami. Šio nustatymo pakeitimai bus naudojami sekančio užduoties paleidimo metu.", - "user_delete_immediately": "{user} paskyra ir elementai bus nedelsiant įtraukti galutiniam pašalinimui.", - "user_delete_immediately_checkbox": "Ištrinti naudotoją ir elementus nedelsiant", - "user_details": "Naudotojo duomenys", - "user_management": "Naudotojų valdymas", - "user_password_has_been_reset": "Naudotojo slaptažodis buvo iš naujo nustatytas:", - "user_password_reset_description": "Perduokite laikiną slaptažodį naudotojui ir informuokite, kad pasikeistų slaptažodį pirmo prisijungimo metu.", - "user_restore_description": "Naudotojo {user} paskyra bus atkurta.", - "user_restore_scheduled_removal": "Atkurti naudotoją - suplanuotas pašalinimas {date, date, long}", - "user_settings": "Naudotojo nustatymai", - "user_settings_description": "Valdyti naudotojo nustatymus", - "user_successfully_removed": "Naudotojas {email} sėkmingai pašalintas.", - "users_page_description": "Administratorių vartotojų puslapis", - "version_check_enabled_description": "Įgalinti versijų tikrinimą", - "version_check_implications": "Versijų tikrinimas reikalauja periodiškos komunikacijos su github.com", - "version_check_settings": "Versijos tikrinimas", - "version_check_settings_description": "Įjungti/išjungti naujos versijos pranešimus", - "video_conversion_job": "Vaizdo įrašų konvertavimas", - "video_conversion_job_description": "Vaizdo įrašų konvertavimas platesniam suderinamumui su naršyklėmis ir įrenginiais" - }, - "admin_email": "Administratoriaus el. paštas", - "admin_password": "Administratoriaus slaptažodis", - "administration": "Administravimas", - "advanced": "Sudėtingesnis", - "advanced_settings_enable_alternate_media_filter_subtitle": "Naudokite šį nustatymą medijos filtravimui sinchronizuojant remiantis alternatyviais kriterijais. Naudokite tik jei programa turi problemų su visų albumų aptikimu.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTINIS] Naudokite alternatyvų įrenginio albumų sinchronizavimo filtrą", - "advanced_settings_log_level_title": "Žurnalo įrašų lygis: {level}", - "advanced_settings_prefer_remote_subtitle": "Kai kurie įrenginiai labai lėtai įkelia miniatiūras iš vietinių elementų. Aktyvuokite šį nustatymą, kad vietoje to užkrautumėte nuotolines nuotraukas.", - "advanced_settings_prefer_remote_title": "Teikti pirmenybę nuotolinėms nuotraukoms", - "advanced_settings_proxy_headers_subtitle": "Nustatykite tarpinio serverio antraštes kurias Immich siųs su kiekvienu užklausimu", - "advanced_settings_proxy_headers_title": "Custom proxy headeriai [Experimentinis]", - "advanced_settings_readonly_mode_subtitle": "Įgalina tik skaitymo režimą kai nuotraukas galima tik žiūrėti, draudžiama pažymėti kelias, dalintis, transliuoti ar ištrinti. Įgalinkit/uždrauskit tik skaitymą per naudotojo avatar'ą iš pagrindinio lango", - "advanced_settings_readonly_mode_title": "Tik skaitymo rėžimas", - "advanced_settings_self_signed_ssl_subtitle": "Praleidžia SSL sertifikato tikrinimą serverio galutiniam taškui. Privaloma pačių pasirašytiems sertifikatams.", - "advanced_settings_self_signed_ssl_title": "Leisti self-signed SSL sertifikatus [Experimentinis]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatiškai ištrinti ar atkurti elementus įrenginyje, kai tie veiksmai atliekami naršyklėje", - "advanced_settings_sync_remote_deletions_title": "Sinchronizuoti nuotolinius ištrynimus [EKSPERIMENTINIS]", - "advanced_settings_tile_subtitle": "Pažangesni naudotojų nustatymai", - "advanced_settings_troubleshooting_subtitle": "Įgalinti papildomas galimybes trikčių šalinimui", - "advanced_settings_troubleshooting_title": "Trikčių šalinimas", - "age_months": "Amžius {months, plural, one {# mėnesis} few {# mėnesiai} other {# mėnesių}}", - "age_year_months": "Amžius 1 metai, {months, plural, one {# mėnesis} few {# mėnesiai} other {# mėnesių}}", - "age_years": "{years, plural, other {Amžius #}}", - "album": "Albumas", - "album_added": "Albumas pridėtas", - "album_added_notification_setting_description": "Gauti el. pašto pranešimą, kai būsite pridėtas prie bendrinamo albumo", - "album_cover_updated": "Albumo viršelis atnaujintas", - "album_delete_confirmation": "Ar tikrai norite ištrinti albumą {album}?", - "album_delete_confirmation_description": "Jei šiuo albumu dalijamasi, tai kiti naudotojai jo nebegalės pasiekti.", - "album_deleted": "Albumas ištrintas", - "album_info_card_backup_album_excluded": "neįtrauktas", - "album_info_card_backup_album_included": "įtrauktas", - "album_info_updated": "Albumo informacija atnaujinta", - "album_leave": "Palikti albumą?", - "album_leave_confirmation": "Ar tikrai norite palikti albumą {album}?", - "album_name": "Albumo pavadinimas", - "album_options": "Albumo parinktys", - "album_remove_user": "Pašalinti naudotoją?", - "album_remove_user_confirmation": "Ar tikrai norite pašalinti naudotoją {user}?", - "album_search_not_found": "Pagal jūsų paiešką albumų nerasta", - "album_selected": "Albumas pasirinktas", - "album_share_no_users": "Atrodo, kad bendrinate šį albumą su visais naudotojais, arba neturite naudotojų, su kuriais galėtumėte bendrinti.", - "album_summary": "Albumo santrauka", - "album_updated": "Albumas atnaujintas", - "album_updated_setting_description": "Gauti pranešimą el. paštu, kai bendrinamas albumas turi naujų elementų", - "album_user_left": "Paliko {album}", - "album_user_removed": "Pašalintas {user}", - "album_viewer_appbar_delete_confirm": "Ar tikrai norite ištrinti šį albumą iš savo paskyros?", - "album_viewer_appbar_share_err_delete": "Nepavyko ištrinti albumo", - "album_viewer_appbar_share_err_leave": "Nepavyko išeiti iš albumo", - "album_viewer_appbar_share_err_remove": "Kilo problemų pašalinant elementus iš albumo", - "album_viewer_appbar_share_err_title": "Nepavyko pakeisti albumo pavadinimą", - "album_viewer_appbar_share_leave": "Palikti albumą", - "album_viewer_appbar_share_to": "Dalintis su", - "album_viewer_page_share_add_users": "Pridėti naudotojų", - "album_with_link_access": "Tegul visi, turintys nuorodą, mato šio albumo nuotraukas ir žmones.", - "albums": "Albumai", - "albums_count": "{count, plural, one {# albumas} few {# albumai} other {# albumų}}", - "albums_default_sort_order": "Pradinė albumo rūšiavimo tvarka", - "albums_default_sort_order_description": "Pradinė elementų rūšiavimo tvarka kai kuriamas naujas albumas.", - "albums_feature_description": "Elementų rinkinys kuriuo galima dalintis su kitais naudotojais.", - "albums_on_device_count": "Albumų įrenginyje ({count})", - "albums_selected": "{count, plural, one {# pasirinktas albumas} other {# pasirinkti albumai}}", - "all": "Visi", - "all_albums": "Visi albumai", - "all_people": "Visi žmonės", - "all_videos": "Visi video", - "allow_dark_mode": "Leisti tamsųjį režimą", - "allow_edits": "Leisti redagavimus", - "allow_public_user_to_download": "Leisti viešam naudotojui atsisiųsti", - "allow_public_user_to_upload": "Leisti viešam naudotojui įkelti", - "allowed": "Leidžiama", - "alt_text_qr_code": "QR kodo paveiksliukas", - "anti_clockwise": "Prieš laikrodžio rodykles", - "api_key": "API raktas", - "api_key_description": "Ši reikšmė bus parodyta tik vieną kartą. Prašome nusikopijuoti prieš uždarant šį langą.", - "api_key_empty": "Jūsų API rakto pavadinimas netūrėtų būti tuščias", - "api_keys": "API raktai", - "app_architecture_variant": "Variantas (architektūra)", - "app_bar_signout_dialog_content": "Ar tikrai norite atsijungti?", - "app_bar_signout_dialog_ok": "Taip", - "app_bar_signout_dialog_title": "Atsijungti", - "app_download_links": "Programėlės atsisiuntimo nuorodos", - "app_settings": "Programos nustatymai", - "app_stores": "Programėlių parduotuvės", - "app_update_available": "Prieinamas programėlės atnaujinimas", - "appears_in": "Susiję", - "apply_count": "Taikyti ({count, number})", - "archive": "Archyvas", - "archive_action_prompt": "{count} pridėta į archyvą", - "archive_or_unarchive_photo": "Archyvuoti arba išarchyvuoti nuotrauką", - "archive_page_no_archived_assets": "Nerasta jokių archyvuotų elementų", - "archive_page_title": "Archyve ({count})", - "archive_size": "Archyvo dydis", - "archive_size_description": "Konfigūruoti archyvo dydį atsisiuntimams (GiB)", - "archived": "Archyvuota", - "archived_count": "{count, plural, other {# suarchyvuota}}", - "are_these_the_same_person": "Ar tai tas pats asmuo?", - "are_you_sure_to_do_this": "Ar tikrai norite tai daryti?", - "array_field_not_fully_supported": "Masyvų laukams reikia rankinio JSON redagavimo", - "asset_action_delete_err_read_only": "Negalima ištrinti tik skaitom(o, ų) element(o, ų), praleidžiama", - "asset_action_share_err_offline": "Negalima užkrauti neprisijungusių elementų, praleidžiama", - "asset_added_to_album": "Pridėta į albumą", - "asset_adding_to_album": "Pridedama į albumą…", - "asset_created": "Elementas sukurtas", - "asset_description_updated": "Elemento aprašymas buvo atnaujintas", - "asset_filename_is_offline": "Elementas {filename} nepasiekiamas", - "asset_has_unassigned_faces": "Elementas turi nepriskirtų veidų", - "asset_hashing": "Kuriami bylų parašai…", - "asset_list_group_by_sub_title": "Grupuoti pagal", - "asset_list_layout_settings_dynamic_layout_title": "Dinaminis išdėstymas", - "asset_list_layout_settings_group_automatically": "Automatiškai", - "asset_list_layout_settings_group_by": "Grupuoti elementus pagal", - "asset_list_layout_settings_group_by_month_day": "Mėnesis + diena", - "asset_list_layout_sub_title": "Išdėstymas", - "asset_list_settings_subtitle": "Nuotraukų tinklelio išdėstymo nustatymai", - "asset_list_settings_title": "Nuotraukų tinklelis", - "asset_offline": "Elementas nepasiekiamas", - "asset_offline_description": "Šis išorinis elementas neberandamas diske. Dėl pagalbos susisiekite su savo Immich administratoriumi.", - "asset_restored_successfully": "Elementas atkurtas sėkmingai", - "asset_skipped": "Praleista", - "asset_skipped_in_trash": "Šiukšliadėžėje", - "asset_trashed": "Elementai ištrinti", - "asset_troubleshoot": "Elementų trikčių šalinimas", - "asset_uploaded": "Įkelta", - "asset_uploading": "Įkeliama…", - "asset_viewer_settings_subtitle": "Tvarkykite savo galerijos peržiūros nustatymus", - "asset_viewer_settings_title": "Elementų peržiūra", - "assets": "Elementai", - "assets_added_count": "{count, plural, one {Pridėtas # elementas} few {Pridėti # elementai} other {Pridėta # elementų}}", - "assets_added_to_album_count": "Į albumą {count, plural, one {įtrauktas # elementas} few {įtraukti # elementai} other {įtraukta # elementų}}", - "assets_added_to_albums_count": "Pridėta {assetTotal, plural, one {# elementas} few {# elementai} other {# elementų}} į {albumTotal, plural, one {# albumą} few {# albumus} other {# albumų}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Elementas negali būti pridėtas} few {Elementai negali būti pridėti} other {Elementų negali būti pridėta}} į albumą", - "assets_cannot_be_added_to_albums": "{count, plural, one {Elementas negali būti pridėtas} few {Elementai negali būti pridėti} other {Elementų negali būti pridėta}} į nei vieną albumą", - "assets_count": "{count, plural, one {# elementas} few {# elementai} other {# elementų}}", - "assets_deleted_permanently": "{count} elementų ištrinta galutinai", - "assets_deleted_permanently_from_server": "{count} elementų ištrinta galutinai iš Immich serverio", - "assets_downloaded_failed": "{count, plural, one {Atsisiųstas # failas - {error} failas nepavyko} few {Atsisiųsti # failai - {error} failai nepavyko} other {Atsisiųsta # failų - {error} failų nepavyko}}", - "assets_downloaded_successfully": "{count, plural, one {Atsisiųstas # failas sėkmingai} few {Atsisiųsti # failai sėkmingai} other {Atsisiųsta # failų sėkmingai}}", - "assets_moved_to_trash_count": "{count, plural, one {# elementas perkeltas} few {# elementai perkelti} other {# elementų perkelta}} į šiukšliadėžę", - "assets_permanently_deleted_count": "{count, plural, one {# elementas ištrintas} few {# elementai ištrinti} other {# elementų ištrinta}} visam laikui", - "assets_removed_count": "{count, plural, one {Pašalintas # elementas} few {Pašalinti # elementai} other {Pašalinta # elementų}}", - "assets_removed_permanently_from_device": "{count} elementų pašalinta galutinai iš jūsų įrenginio", - "assets_restore_confirmation": "Ar tikrai norite atkurti visus šiukšliadėžėje esančius perkeltus elementus? Šio veiksmo atšaukti negalėsite! Pastaba: nepasiekiami elementai tokiu būdu atkurti nebus.", - "assets_restored_count": "{count, plural, one {Atkurtas # elementas} few {Atkurti # elementai} other {Atkurta # elementų}}", - "assets_restored_successfully": "{count} element(as, ai, ų) atkurta sėkmingai", - "assets_trashed": "{count} element(ai,ų,as) perkelta į šiukšliadėžę", - "assets_trashed_count": "Perkelta į šiukšliadėžę {count, plural, one {# elementas} few {# elementai} other {# elementų}}", - "assets_trashed_from_server": "{count} element(as, ai, ų) perkelta į šiukšliadėžę iš Immich serverio", - "assets_were_part_of_album_count": "{count, plural, one {# elementas} few {# elementai} other {# elementų}} jau prieš tai buvo albume", - "assets_were_part_of_albums_count": "{count, plural, one {Elementas } few {Elementai} other {Elementų}} jau buvo albumuose", - "authorized_devices": "Autorizuoti įrenginiai", - "automatic_endpoint_switching_subtitle": "Prisijungti vietoje per priskirtą Wi-Fi kai įmanoma ir naudoti alternatyvų prisijungimą visur kitur", - "automatic_endpoint_switching_title": "Automatinis URL perjungimas", - "autoplay_slideshow": "Automatiškai rodyti skaidrių demonstraciją", - "back": "Atgal", - "back_close_deselect": "Atgal, uždaryti arba atžymėti", - "background_backup_running_error": "Vyksta foninis atsarginis kopijavimas, negalima pradėti rankinio kopijavimo", - "background_location_permission": "Foninis vietovės leidimas", - "background_location_permission_content": "Veikiant fone tinklo perjungimui Immich privalo *visada* turėti prieigą prie tikslios vietovės, kad programa galėtų perskaityti Wi-Fi tinklo pavadinimą", - "background_options": "Fono nuostatos", - "backup": "Atsarginė kopija", - "backup_album_selection_page_albums_device": "Albumų įrenginyje ({count})", - "backup_album_selection_page_albums_tap": "Palieskite įtraukti, du kart palieskite neįtraukti", - "backup_album_selection_page_assets_scatter": "Elementai gali išsibarstyti per kelis albumus. Todėl albumai gali būti įtraukti arba neįtraukti per atsarginio kopijavimo procesą.", - "backup_album_selection_page_select_albums": "Pažymėti albumai", - "backup_album_selection_page_selection_info": "Pažymėjimo informacija", - "backup_album_selection_page_total_assets": "Viso unikalių elementų", - "backup_albums_sync": "Atsarginio kopijavimo albumų sinchronizacija", - "backup_all": "Visi", - "backup_background_service_backup_failed_message": "Nepavyko sukurti atsarginių kopijų. Bandoma dar kartą…", - "backup_background_service_complete_notification": "Elementų atsarginės kopijos kūrimas baigtas", - "backup_background_service_connection_failed_message": "Nepavyko prisijungti prie serverio. Bandoma dar kartą…", - "backup_background_service_current_upload_notification": "Įkeliamas {filename}", - "backup_background_service_default_notification": "Ieškoma naujų elementų…", - "backup_background_service_error_title": "Atsarginio kopijavimo klaida", - "backup_background_service_in_progress_notification": "Kuriama elementų atsarginė kopija…", - "backup_background_service_upload_failure_notification": "Nepavyko įkelti {filename}", - "backup_controller_page_albums": "Atsarginės kopijos albumai", - "backup_controller_page_background_app_refresh_disabled_content": "Norėdami naudoti foninį atsarginį kopijavimą, įjunkite foninį programų atnaujinimą meniu „Nustatymai“ > „Bendrieji“ > „Foninis programų atnaujinimas“.", - "backup_controller_page_background_app_refresh_disabled_title": "Foninis programos atnaujinimas išjungtas", - "backup_controller_page_background_app_refresh_enable_button_text": "Eiti į nustatymus", - "backup_controller_page_background_battery_info_link": "Parodyk man kaip", - "backup_controller_page_background_battery_info_message": "Norint geriausių foninio atsarginio kopijavimo rezultatų, prašome išjungti akumuliatoriaus optimizavimą ribojantį foninį Immich veikimą.\n\nKadangi tai priklauso nuo įrenginio, prašome susirasti reikiamą informaciją pas įrenginio gamintoją.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Akumuliatoriaus optimizavimai", - "backup_controller_page_background_charging": "Tik kol kraunasi", - "backup_controller_page_background_configure_error": "Nepavyko sukonfigūruoti foninių paslaugų", - "backup_controller_page_background_delay": "Atidėti naujų elementų atsarginį kopijavimą: {duration}", - "backup_controller_page_background_description": "Įjunkite fonines paslaugas, kad galėtumėte automatiškai kurti atsargines kopijas neatidarant programos", - "backup_controller_page_background_is_off": "Automatinis atsarginis kopijavimas yra išjungtas", - "backup_controller_page_background_is_on": "Automatinis atsarginis kopijavimas yra įjungtas", - "backup_controller_page_background_turn_off": "Išjungti fonines paslaugas", - "backup_controller_page_background_turn_on": "Įjungti fonines paslaugas", - "backup_controller_page_background_wifi": "Tik su Wi-Fi", - "backup_controller_page_backup": "Atsarginis kopijavimas", - "backup_controller_page_backup_selected": "Pasirinkta: ", - "backup_controller_page_backup_sub": "Perkeltos nuotraukos ir vaizdo įrašai", - "backup_controller_page_created": "Sukurta: {date}", - "backup_controller_page_desc_backup": "Įjunkite foninį atsarginį kopijavimą, kad būtų automatiškai perkeliami nauji elementai į serverį kai atidaroma programa.", - "backup_controller_page_excluded": "Neįtraukta: ", - "backup_controller_page_failed": "Nepavyko ({count})", - "backup_controller_page_filename": "Failo pavadinimas: {filename}[{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Atsarginio kopijavimo informacija", - "backup_controller_page_none_selected": "Niekas nepasirinkta", - "backup_controller_page_remainder": "Dar liko", - "backup_controller_page_remainder_sub": "Likusios pasirinktos atsarginio kopijavimo nuotraukos ir vaizdo įrašai", - "backup_controller_page_server_storage": "Serverio saugykla", - "backup_controller_page_start_backup": "Pradėti atsarginį kopijavimą", - "backup_controller_page_status_off": "Automatinis foninis atsarginis kopijavimas yra išjungtas", - "backup_controller_page_status_on": "Automatinis foninis atsarginis kopijavimas yra įjungtas", - "backup_controller_page_storage_format": "{used} iš {total} panaudota", - "backup_controller_page_to_backup": "Albumai kurių atsarginis kopijavimas bus atliktas", - "backup_controller_page_total_sub": "Visos unikalios nuotraukos ir video įrašai iš pažymėtų albumų", - "backup_controller_page_turn_off": "Išjungti foninį atsarginį kopijavimą", - "backup_controller_page_turn_on": "Įjungti foninį atsarginį kopijavimą", - "backup_controller_page_uploading_file_info": "Įkeliama failo info", - "backup_err_only_album": "Negalima pašalinti vienintelio albumo", - "backup_error_sync_failed": "Sinchronizavimas nepavyko. Atsarginė kopija negali būti apdorota.", - "backup_info_card_assets": "elementai", - "backup_manual_cancelled": "Atšaukta", - "backup_manual_in_progress": "Jau įkeliama, bandykite dar kartą vėliau", - "backup_manual_success": "Pavyko", - "backup_manual_title": "Įkėlimo būklė", - "backup_options": "Atsarginio kopijavimo nustatymai", - "backup_options_page_title": "Atsarginio kopijavimo nustatymai", - "backup_setting_subtitle": "Tvarkyti foninio ir priekinio plano įkėlimo nustatymus", - "backup_settings_subtitle": "Tvarkyti įkėlimo nustatymus", - "backup_upload_details_page_more_details": "Bakstelėkite detalesnei informacijai", - "backward": "Atgalinis", - "biometric_auth_enabled": "Biometrinis autentifikavimas įgalintas", - "biometric_locked_out": "Jūs esate užblokuotas biometrinio autentifikavimo funkcijai", - "biometric_no_options": "Nėra galimų biometrinių nustatymų", - "biometric_not_available": "Biometrinis autentifikavimas šiame įrenginyje negalimas", - "birthdate_saved": "Sėkmingai išsaugota gimimo data", - "birthdate_set_description": "Gimimo data naudojama apskaičiuoti asmens amžių nuotraukos darymo metu.", - "blurred_background": "Neryškus fonas", - "bugs_and_feature_requests": "Klaidų ir funkcijų užklausos", - "build": "Versija", - "bulk_delete_duplicates_confirmation": "Ar tikrai norite ištrinti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Bus paliktas didžiausias kiekvienos grupės elementas ir negrįžtamai ištrinti kiti besidubliuojantys elementai. Šio veiksmo atšaukti negalėsite!", - "bulk_keep_duplicates_confirmation": "Ar tikrai norite palikti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Tokiu būdu nieko netrinant bus sutvarkytos visos dublikatų grupės.", - "bulk_trash_duplicates_confirmation": "Ar tikrai norite perkelti į šiukšliadėžę visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančių elementų}}? Bus paliktas didžiausias kiekvienos grupės elementas ir į šiukšliadėžę perkelti kiti besidubliuojantys elementai.", - "buy": "Įsigyti Immich", - "cache_settings_clear_cache_button": "Išvalyti laikiną talpyklą", - "cache_settings_clear_cache_button_title": "Išvalo programos laikiną talpyklą. Tai gali smarkiai paveikti programos greitį, kol bus sukurta nauja laikinoji talpykla.", - "cache_settings_duplicated_assets_clear_button": "IŠVALYTI", - "cache_settings_duplicated_assets_subtitle": "Nuotraukos ir video įrašai kurie yra programos ignoruojamų sąraše", - "cache_settings_duplicated_assets_title": "Sudubliuoti elementai ({count})", - "cache_settings_statistics_album": "Bibliotekos miniatiūros", - "cache_settings_statistics_full": "Pilno dydžio nuotraukos", - "cache_settings_statistics_shared": "Bendrinamų albumų miniatiūros", - "cache_settings_statistics_thumbnail": "Miniatiūros", - "cache_settings_statistics_title": "Laikinos talpyklos naudojimas", - "cache_settings_subtitle": "Valdykite Immich mobiliosios programos laikinosios talpyklos elgesį", - "cache_settings_tile_subtitle": "Valdykite vietinės talpyklos elgesį", - "cache_settings_tile_title": "Vietinė talpykla", - "cache_settings_title": "Laikinosios talpyklos nustatymai", - "camera": "Fotoaparatas", - "camera_brand": "Fotoaparato prekės ženklas", - "camera_model": "Fotoaparato modelis", - "cancel": "Atšaukti", - "cancel_search": "Atšaukti paiešką", - "canceled": "Atšaukta", - "canceling": "Atšaukiama", - "cannot_merge_people": "Negalima sujungti asmenų", - "cannot_undo_this_action": "Jūs negalėsite atkurti po šio veiksmo!", - "cannot_update_the_description": "Negalima atnaujinti aprašymo", - "cast": "Transliuoti", - "cast_description": "Valdyti galimas transliavimo kryptis", - "change_date": "Pakeisti datą", - "change_description": "Pakeisti aprašymus", - "change_display_order": "Pakeisti atvaizdavimo tvarką", - "change_expiration_time": "Pakeisti galiojimo trukmę", - "change_location": "Pakeisti vietovę", - "change_name": "Pakeisti vardą", - "change_name_successfully": "Vardas pakeistas sėkmingai", - "change_password": "Pakeisti slaptažodį", - "change_password_description": "Tai arba pirmas kartas, kai jungiatės prie sistemos, arba buvo pateikta užklausa pakeisti jūsų slaptažodį. Prašome įvesti naują slaptažodį žemiau.", - "change_password_form_confirm_password": "Patvirtinti slaptažodį", - "change_password_form_description": "Labas {name},\n\nTai yra pirmas kartas kai tu prisijungei prie sistemos arba buvo prašymas pakeisti slaptažodį. Prašome įvesti naują slaptažodį žemiau.", - "change_password_form_log_out": "Atjungti visus kitus įrenginius", - "change_password_form_log_out_description": "Rekomenduojama atsijungti nuo visų kitų įrenginių", - "change_password_form_new_password": "Naujas slaptažodis", - "change_password_form_password_mismatch": "Slaptažodžiai nesutampa", - "change_password_form_reenter_new_password": "Pakartotinai įveskite naują slaptažodį", - "change_pin_code": "Pakeisti PIN kodą", - "change_trigger": "Pakeisti vykdymo sąlygą", - "change_trigger_prompt": "Ar tikrai norite vykdymo sąlygą? Tai pašalins visas esamas veiksmų sekas ir filtrus.", - "change_your_password": "Pakeisti slaptažodį", - "changed_visibility_successfully": "Matomumas pakeistas sėkmingai", - "charging": "Kraunasi", - "charging_requirement_mobile_backup": "Foninis kopijavimas reikalauja, kad įrenginys būtų prijungtas pakrovimui", - "check_corrupt_asset_backup": "Patikrinti sugadintų elementų atsarginę kopiją", - "check_corrupt_asset_backup_button": "Atlikti patikrinimą", - "check_corrupt_asset_backup_description": "Paleiskite šį patikrinimą tik per Wi-Fi ir tik kai visi elementai buvo perkopijuoti. Ši procedūra užtruks kelias minutes.", - "check_logs": "Tikrinti žurnalus", - "checksum": "„Checksum“", - "choose_matching_people_to_merge": "Pasirinkite atitinkančius žmones sujungimui", - "city": "Miestas", - "clear": "Išvalyti", - "clear_all": "Išvalyti viską", - "clear_all_recent_searches": "Išvalyti visas naujausias paieškas", - "clear_file_cache": "Išvalyti failų laikiną talpyklą", - "clear_message": "Išvalyti pranešimą", - "clear_value": "Išvalyti reikšmę", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Įveskite slaptažodį", - "client_cert_import": "Importuoti", - "client_cert_import_success_msg": "Kliento sertifikatas yra importuotas", - "client_cert_invalid_msg": "Netinkamas sertifikato failas arba neteisingas slaptažodis", - "client_cert_remove_msg": "Kliento sertifikatas yra pašalintas", - "client_cert_subtitle": "Palaikomi tik PKCS12 (.p12, .pfx) formatai. Sertifikato importavimas/ pašalinimas galimas tik prieš prisijungimą", - "client_cert_title": "SSL kliento sertifikatas [Experimentinis]", - "clockwise": "Pagal laikrodžio rodykles", - "close": "Uždaryti", - "collapse": "Suskleisti", - "collapse_all": "Suskleisti viską", - "color": "Spalva", - "color_theme": "Temos spalva", - "command": "Komanda", - "comment_deleted": "Komentaras ištrintas", - "comment_options": "Komentarų parinktys", - "comments_and_likes": "Komentarai ir patiktukai", - "comments_are_disabled": "Komentarai yra išjungti", - "common_create_new_album": "Sukurti naują albumą", - "completed": "Užbaigta", - "confirm": "Patvirtinti", - "confirm_admin_password": "Patvirtinti administratoriaus slaptažodį", - "confirm_delete_face": "Ar tikrai norite ištrinti {name} veidą iš elementų?", - "confirm_delete_shared_link": "Ar tikrai norite ištrinti šią bendrinimo nuorodą?", - "confirm_keep_this_delete_others": "Visi kiti elementai iš krūvos bus ištrinti išskyrus šį elementą. Ar tikrai norite tęsti?", - "confirm_new_pin_code": "Patvirtinkite naują PIN kodą", - "confirm_password": "Patvirtinti slaptažodį", - "confirm_tag_face": "Ar norite priskirti šį veidą kaip {name}?", - "confirm_tag_face_unnamed": "Ar norite priskirti šį veidą?", - "connected_device": "Prijungtas įrenginys", - "connected_to": "Prisijungta prie", - "contain": "Tilpti", - "context": "Kontekstas", - "continue": "Tęsti", - "control_bottom_app_bar_create_new_album": "Sukurti naują albumą", - "control_bottom_app_bar_delete_from_immich": "Ištrinti iš Immich", - "control_bottom_app_bar_delete_from_local": "Ištrinti iš įrenginio", - "control_bottom_app_bar_edit_location": "Redaguoti vietovę", - "control_bottom_app_bar_edit_time": "Redaguoti datą ir laiką", - "control_bottom_app_bar_share_link": "Dalintis nuoroda", - "control_bottom_app_bar_share_to": "Dalintis su", - "control_bottom_app_bar_trash_from_immich": "Perkelti į šiukšliadėžę", - "copied_image_to_clipboard": "Nuotrauka nukopijuota į iškarpinę.", - "copied_to_clipboard": "Nukopijuota į iškapinę!", - "copy_error": "Kopijavimo klaida", - "copy_file_path": "Kopijuoti failo kelią", - "copy_image": "Kopijuoti vaizdą", - "copy_link": "Kopijuoti nuorodą", - "copy_link_to_clipboard": "Kopijuoti nuorodą į iškarpinę", - "copy_password": "Kopijuoti slaptažodį", - "copy_to_clipboard": "Kopijuoti į iškarpinę", - "country": "Šalis", - "cover": "Užpildyti", - "covers": "Viršeliai", - "create": "Sukurti", - "create_album": "Sukurti albumą", - "create_album_page_untitled": "Be pavadinimo", - "create_api_key": "Sukurti API raktą", - "create_first_workflow": "Sukurti pirmą darbų eigą", - "create_library": "Sukurti biblioteką", - "create_link": "Sukurti nuorodą", - "create_link_to_share": "Sukurti bendrinimo nuorodą", - "create_link_to_share_description": "Leisti bet kam su nuoroda matyti pažymėtą(-as) nuotrauką(-as)", - "create_new": "SUKURTI NAUJĄ", - "create_new_person": "Sukurti naują žmogų", - "create_new_person_hint": "Priskirti pasirinktus elementus naujam žmogui", - "create_new_user": "Sukurti naują varotoją", - "create_shared_album_page_share_add_assets": "PRIDĖTI ELEMENTŲ", - "create_shared_album_page_share_select_photos": "Pažymėti nuotraukas", - "create_shared_link": "Sukurti dalijimosi nuorodą", - "create_tag": "Sukurti žymą", - "create_tag_description": "Sukurti naują žymą. Įdėtinėms žymoms įveskite pilną kelią, įskaitant pasviruosius brūkšnius.", - "create_user": "Sukurti naudotoją", - "create_workflow": "Sukurti darbų eigą", - "created": "Sukurta", - "created_at": "Sukurta", - "creating_linked_albums": "Kuriami susieti albumai...", - "crop": "Apkirpti", - "crop_aspect_ratio_fixed": "Užfiksuota", - "crop_aspect_ratio_free": "Nefiksuota", - "crop_aspect_ratio_original": "Originalus", - "curated_object_page_title": "Daiktai", - "current_device": "Dabartinis įrenginys", - "current_pin_code": "Dabartinis PIN kodas", - "current_server_address": "Dabartinis serverio adresas", - "custom_date": "Pasirinktinė data", - "custom_locale": "Pasirinktinė vietovė", - "custom_locale_description": "Formatuoti datas ir skaičius pagal kalbą ir regioną", - "custom_url": "Pasirinktinis URL", - "cutoff_date_description": "Pašalinkite senesnes nuotraukas ir vaizdo įrašus nei", - "cutoff_day": "{count, plural, one {diena} other {dienos}}", - "cutoff_year": "{count, plural, one {metai} other {metai}}", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "Tamsi", - "dark_theme": "Perjungti tamsią temą", - "date": "Data", - "date_after": "Data po", - "date_and_time": "Data ir laikas", - "date_before": "Data prieš", - "date_format": "E, LLL d, y • h:mm", - "date_of_birth_saved": "Gimimo data sėkmingai išsaugota", - "date_range": "Datų intervalas", - "day": "Diena", - "days": "Dienų", - "deduplicate_all": "Šalinti visus dublikatus", - "deduplication_criteria_1": "Failo dydis baitais", - "deduplication_criteria_2": "EXIF metaduomenų įrašų skaičius", - "deduplication_info": "Dublikatų šalinimo informacija", - "deduplication_info_description": "Automatinis elementų parinkimas ir masinis dublikatų šalinimas atliekamas atsižvelgiant į:", - "default_locale": "Pradinė vietovė", - "default_locale_description": "Formatuoti datas ir skaičius pagal jūsų naršyklės lokalę", - "delete": "Ištrinti", - "delete_action_confirmation_message": "Ar tikrai norite ištrinti šį elementą? Šis veiksmas perkels elementą į serverio šiukšliadėžę ir paklaus ar norite ištrinti vietiniame įrenginyje", - "delete_action_prompt": "{count} ištrinta", - "delete_album": "Ištrinti albumą", - "delete_api_key_prompt": "Ar tikrai norite ištrinti šį API raktą?", - "delete_dialog_alert": "Šie elementai bus galutinai ištrinti iš Immich ir iš jūsų įrenginio", - "delete_dialog_alert_local": "Šie elementai bus galutinai pašalinti iš jūsų įrenginio, bet bus prieinami Immich serveryje", - "delete_dialog_alert_local_non_backed_up": "Kai kurie elementai be Immich atsarginės kopijos ir bus galutinai pašalinti iš jūsų įrenginio", - "delete_dialog_alert_remote": "Šie elementai bus galutinai ištrinti iš Immich serverio", - "delete_dialog_ok_force": "Vis tiek ištrinti", - "delete_dialog_title": "Ištrinti galutinai", - "delete_duplicates_confirmation": "Ar tikrai norite visam laikui ištrinti šiuos dublikatus?", - "delete_face": "Ištrinti veidą", - "delete_key": "Ištrinti raktą", - "delete_library": "Ištrinti biblioteką", - "delete_link": "Ištrinti nuorodą", - "delete_local_action_prompt": "{count} ištrinti vietiniame įrenginyje", - "delete_local_dialog_ok_backed_up_only": "Ištrinti tik turinčius atsarginę kopiją", - "delete_local_dialog_ok_force": "Vis tiek ištrinti", - "delete_others": "Ištrinti kitus", - "delete_permanently": "Ištrinti galutinai", - "delete_permanently_action_prompt": "{count} ištrinta galutinai", - "delete_shared_link": "Ištrinti bendrinimo nuorodą", - "delete_shared_link_dialog_title": "Ištrinti dalijimosi nuorodą", - "delete_tag": "Ištrinti žymą", - "delete_tag_confirmation_prompt": "Ar tikrai norite ištrinti žymą {tagName}?", - "delete_user": "Ištrinti naudotoją", - "deleted_shared_link": "Bendrinimo nuoroda ištrinta", - "deletes_missing_assets": "Ištrinti diske trūkstamus elementus", - "description": "Aprašymas", - "description_input_hint_text": "Pridėti aprašymą...", - "description_input_submit_error": "Klaida atnaujinant aprašymą, pasitikrinkite žurnalą norint detalesnės informacijos", - "deselect_all": "Atžymėti visus", - "details": "Detalės", - "direction": "Kryptis", - "disable": "Išjungti", - "disabled": "Išjungta", - "disallow_edits": "Neleisti redaguoti", - "discord": "Discord", - "discover": "Atrasti", - "discovered_devices": "Aptikti įrenginiai", - "dismiss_all_errors": "Nepaisyti visų klaidų", - "dismiss_error": "Nepaisyti klaidos", - "display_options": "Atvaizdavimo parinktys", - "display_order": "Atvaizdavimo tvarka", - "display_original_photos": "Rodyti originalias nuotraukas", - "display_original_photos_setting_description": "Pirmenybė rodyti originalią nuotrauką vietoje miniatiūros kai originalo elementas yra palaikomas naršyklės. Tai gali lemti lėtesnį nuotraukos rodymo greitį.", - "do_not_show_again": "Daugiau nerodyti šio pranešimo", - "documentation": "Dokumentacija", - "done": "Atlikta", - "download": "Atsisiųsti", - "download_action_prompt": "Atsisiunčiami {count} elementai", - "download_canceled": "Atsisiuntimas atšauktas", - "download_complete": "Atsisiuntimas pabaigtas", - "download_enqueue": "Atsisiuntimai įtraukti į eilę", - "download_error": "Atsisiuntimo klaida", - "download_failed": "Nepavyko parsisiųsti", - "download_finished": "Atsisiuntimas pabaigtas", - "download_include_embedded_motion_videos": "Įterpti vaizdo įrašai", - "download_include_embedded_motion_videos_description": "Pridėti prie judesio nuotraukų įterptus video kaip atskirą failą", - "download_notfound": "Atsisiuntimas nerastas", - "download_original": "Atsisiųsti originalą", - "download_paused": "Atsisiuntimas pristabdytas", - "download_settings": "Atsisiųsti", - "download_settings_description": "Tvarkyti elementų atsisiuntimo nustatymus", - "download_started": "Atsisiuntimas pradėtas", - "download_sucess": "Atsisiuntimas pavyko", - "download_sucess_android": "Medija buvo atsiųsta į DCIM/Immich", - "download_waiting_to_retry": "Laukiama bandymo iš naujo", - "downloading": "Siunčiama", - "downloading_asset_filename": "Parsisiunčiamas resursas {filename}", - "downloading_from_icloud": "Atsisiųsti iš iCloud", - "downloading_media": "Atsisiunčiama medija", - "drop_files_to_upload": "Užkelkite failus bet kurioje vietoje kad įkeltumėte", - "duplicates": "Dublikatai", - "duplicates_description": "Sutvarkykite kiekvieną elementų grupę nurodydami elementus, kurie yra dublikatai (jei tokių yra)", - "duration": "Trukmė", - "edit": "Redaguoti", - "edit_album": "Redaguoti albumą", - "edit_avatar": "Redaguoti avatarą", - "edit_birthday": "Redaguoti gimtadienį", - "edit_date": "Redaguoti datą", - "edit_date_and_time": "Redaguoti datą ir laiką", - "edit_date_and_time_action_prompt": "{count} data ir laikas redaguotas", - "edit_date_and_time_by_offset": "Keisti datą pagal poslinkį", - "edit_date_and_time_by_offset_interval": "Naujas datos intervalas: {from} - {to}", - "edit_description": "Redaguoti aprašymą", - "edit_description_prompt": "Prašome pasirinkti naują aprašymą:", - "edit_exclusion_pattern": "Redaguoti išimčių šabloną", - "edit_faces": "Redaguoti veidus", - "edit_key": "Redaguoti raktą", - "edit_link": "Redaguoti nuorodą", - "edit_location": "Redaguoti vietovę", - "edit_location_action_prompt": "{count} vietovės pakeistos", - "edit_location_dialog_title": "Vietovė", - "edit_name": "Redaguoti vardą", - "edit_people": "Redaguoti žmones", - "edit_tag": "Redaguoti žymą", - "edit_title": "Redaguoti antraštę", - "edit_user": "Redaguoti naudotoją", - "edit_workflow": "Redaguoti darbų eigą", - "editor": "Redaktorius", - "editor_close_without_save_prompt": "Pakeitimai nebus išsaugoti", - "editor_close_without_save_title": "Uždaryti redaktorių?", - "editor_confirm_reset_all_changes": "Ar tikrai norite atstatyti visus pakeitimus?", - "editor_flip_horizontal": "Apversti horizontaliai", - "editor_flip_vertical": "Apversti vertikaliai", - "editor_orientation": "Orientacija", - "editor_reset_all_changes": "Atšaukti pakeitimus", - "editor_rotate_left": "Pasukti 90° prieš laikrodžio rodyklę", - "editor_rotate_right": "Pasukti 90° pagal laikrodžio rodyklę", - "email": "El. paštas", - "email_notifications": "El. pašto pranešimai", - "empty_folder": "Šis katalogas yra tuščias", - "empty_trash": "Ištuštinti šiukšliadėžę", - "empty_trash_confirmation": "Ar tikrai norite ištuštinti šiukšliadėžę? Tai galutinai pašalins elementus iš Immich.\nJūs negalėsite atkurti šio veiksmo!", - "enable": "Įgalinti", - "enable_backup": "Įgalinti atsargines kopijas", - "enable_biometric_auth_description": "Įveskite savo PIN kodą biometrinės autentifikacijos įjungimui", - "enabled": "Įgalintas", - "end_date": "Pabaigos data", - "enqueued": "Įtraukta į eilę", - "enter_wifi_name": "Įveskite Wi-Fi pavadinimą", - "enter_your_pin_code": "Įveskite savo PIN kodą", - "enter_your_pin_code_subtitle": "Įveskite savo PIN kodą, kad pasiektumėte užrakintą aplanką", - "error": "Klaida", - "error_change_sort_album": "Nepavyko pakeisti albumo rūšiavimo tvarkos", - "error_delete_face": "Klaida trinant veidą iš elementų", - "error_getting_places": "Klaida gaunant vietoves", - "error_loading_image": "Klaida įkeliant vaizdą", - "error_loading_partners": "Klaida užkraunant partnerius: {error}", - "error_saving_image": "Klaida: {error}", - "error_tag_face_bounding_box": "Klaida aprašant veidą - nepavyko gauti veido vietos koordinačių", - "error_title": "Klaida - Kažkas nutiko ne taip", - "errors": { - "cannot_navigate_next_asset": "Negalima pereiti prie sekančio elemento", - "cannot_navigate_previous_asset": "Negalima pereiti prie buvusio elemento", - "cant_apply_changes": "Negalima taikyti pakeitimų", - "cant_change_activity": "Negalima {enabled, select, true {išjungti} other {įjungti}} veiklos", - "cant_change_asset_favorite": "Elementui negalima pakeisti mėgstamiausio", - "cant_change_metadata_assets_count": "Negalima pakeisti {count, plural, one {# elemento} other {# elementų}} metadata", - "cant_get_faces": "Nepavyko gauti veidus", - "cant_get_number_of_comments": "Komentarų skaičiaus gauti negalima", - "cant_search_people": "Negalima ieškoti žmonių", - "cant_search_places": "Negalima ieškoti vietovių", - "error_adding_assets_to_album": "Klaida pridedant elementus į albumą", - "error_adding_users_to_album": "Klaida pridedant naudotojus prie albumo", - "error_deleting_shared_user": "Klaida trinant pasidalintą naudotoją", - "error_downloading": "Klaida atsisiunčiant {filename}", - "error_hiding_buy_button": "Klaida slepiant pirkimo mygtuką", - "error_removing_assets_from_album": "Klaida šalinant elementus iš albumo, patikrinkite konsolę dėl išsamesnės informacijos", - "error_selecting_all_assets": "Klaida pasirenkant visus elementus", - "exclusion_pattern_already_exists": "Šis išimčių šablonas jau egzistuoja.", - "failed_to_create_album": "Nepavyko sukurti albumo", - "failed_to_create_shared_link": "Nepavyko sukurti bendrinimo nuorodos", - "failed_to_edit_shared_link": "Nepavyko redaguoti bendrinimo nuorodos", - "failed_to_get_people": "Nepavyko gauti žmonių", - "failed_to_keep_this_delete_others": "Nepavyko palikti šį elementą ir ištrinti kitus elementus", - "failed_to_load_asset": "Nepavyko užkrauti elemento", - "failed_to_load_assets": "Nepavyko užrauti elementų", - "failed_to_load_notifications": "Nepavyko užkrauti pranešimų", - "failed_to_load_people": "Nepavyko užkrauti žmonių", - "failed_to_remove_product_key": "Nepavyko pašalinti produkto rakto", - "failed_to_reset_pin_code": "Nepavyko atkurti PIN kodo", - "failed_to_stack_assets": "Nepavyko sugrupuoti elementų", - "failed_to_unstack_assets": "Nepavyko išgrupuoti elementų", - "failed_to_update_notification_status": "Nepavyko atnaujinti pranešimo statuso", - "incorrect_email_or_password": "Neteisingas el. pašto adresas arba slaptažodis", - "library_folder_already_exists": "Šita importavimo vieta jau egzistuoja.", - "paths_validation_failed": "Nepavyko {paths, plural, one {# kelio} other {# kelių}} patvirtinimas", - "profile_picture_transparent_pixels": "Profilio nuotrauka negali turėti permatomų pikselių. Prašome priartinti ir/arba perkelkite nuotrauką.", - "quota_higher_than_disk_size": "Nustatyta kvota, viršija disko dydį", - "something_went_wrong": "Kažkas nepavyko", - "unable_to_add_album_users": "Nepavyksta pridėti naudotojų prie albumo", - "unable_to_add_assets_to_shared_link": "Nepavyko į bendrinimo nuorodą pridėti elementų", - "unable_to_add_comment": "Nepavyksta pridėti komentaro", - "unable_to_add_exclusion_pattern": "Nepavyksta pridėti išimčių šablono", - "unable_to_add_partners": "Nepavyksta pridėti partnerių", - "unable_to_add_remove_archive": "Nepavyko {archived, select, true {ištraukti iš} other {pridėti prie}} arcyhvo", - "unable_to_add_remove_favorites": "Nepavyko {favorite, select, true {įtraukti elemento į mėgstamiausius} other {pašalinti elemento iš mėgstamiausių}}", - "unable_to_archive_unarchive": "Nepavyko {archived, select, true {archyvuoti} other {išarchyvuoti}}", - "unable_to_change_album_user_role": "Nepavyksta pakeisti albumo naudotojo rolės", - "unable_to_change_date": "Negalima pakeisti datos", - "unable_to_change_description": "Nepavyko pakeisti aprašymo", - "unable_to_change_favorite": "Nepavyko pakeisti elementui mėgstamiausio", - "unable_to_change_location": "Negalima pakeisti vietos", - "unable_to_change_password": "Negalima pakeisti slaptažodžio", - "unable_to_change_visibility": "Nepavyko pakeisti matomumo {count, plural, one {# asmeniui} few {#asmenims} other {# asmenų}}", - "unable_to_complete_oauth_login": "Nepavyko prisijungti su OAuth", - "unable_to_connect": "Nepavyko prisijungti", - "unable_to_copy_to_clipboard": "Negalima kopijuoti į iškarpinę, įsitikinkite, kad prie puslapio prieinate per https", - "unable_to_create": "Nepavyko sukurti darbų eigos", - "unable_to_create_admin_account": "Nepavyko sukurti administratoriaus paskyros", - "unable_to_create_api_key": "Nepavyko sukurti naujo API rakto", - "unable_to_create_library": "Nepavyko sukurti bibliotekos", - "unable_to_create_user": "Nepavyko sukurti naudotojo", - "unable_to_delete_album": "Nepavyksta ištrinti albumo", - "unable_to_delete_asset": "Nepavyko ištrinti elemento", - "unable_to_delete_assets": "Klaida trinant elementus", - "unable_to_delete_exclusion_pattern": "Nepavyksta ištrinti išimčių šablono", - "unable_to_delete_shared_link": "Nepavyko ištrinti bendrinimo nuorodos", - "unable_to_delete_user": "Nepavyksta ištrinti naudotojo", - "unable_to_delete_workflow": "Nepavyko ištrinti darbų eigos", - "unable_to_download_files": "Nepavyksta atsisiųsti failų", - "unable_to_edit_exclusion_pattern": "Nepavyksta redaguoti išimčių šablono", - "unable_to_empty_trash": "Nepavyko ištrinti šiukšliadėžės", - "unable_to_enter_fullscreen": "Nepavyksta pereiti į viso ekrano režimą", - "unable_to_exit_fullscreen": "Nepavyksta išeiti iš viso ekrano režimo", - "unable_to_get_comments_number": "Komentarų skaičiaus gauti nepavyko", - "unable_to_get_shared_link": "Nepavyko gauti bendrinimo nuorodos", - "unable_to_hide_person": "Nepavyksta paslėpti žmogaus", - "unable_to_link_motion_video": "Nepavyko susieti judesio video", - "unable_to_link_oauth_account": "Nepavyko susieti su OAuth paskyra", - "unable_to_log_out_all_devices": "Nepavyksta atjungti visų įrenginių", - "unable_to_log_out_device": "Nepavyksta atjungti įrenginio", - "unable_to_login_with_oauth": "Nepavyko prisijungti su OAuth", - "unable_to_play_video": "Nepavyksta paleisti vaizdo įrašo", - "unable_to_reassign_assets_existing_person": "Nepavyko priskirti elementų {name, select, null {egzistuojančiam asmeniui} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nepavyko priskirti elementų naujam asmeniui", - "unable_to_refresh_user": "Nepavyksta atnaujinti naudotojo", - "unable_to_remove_album_users": "Nepavyko pašalinti naudotojų iš albumo", - "unable_to_remove_api_key": "Nepavyko pašalinti API rakto", - "unable_to_remove_assets_from_shared_link": "Nepavyko iš bendrinimo nuorodos pašalinti elementų", - "unable_to_remove_library": "Nepavyksta pašalinti bibliotekos", - "unable_to_remove_partner": "Nepavyksta pašalinti partnerio", - "unable_to_remove_reaction": "Nepavyksta pašalinti reakcijos", - "unable_to_reset_password": "Nepavyko atnaujinti slaptažodžio", - "unable_to_reset_pin_code": "Nepavyko atnaujinti PIN kodo", - "unable_to_resolve_duplicate": "Nepavyko sutvarkyti dublikatų", - "unable_to_restore_assets": "Nepavyko atstatyti elementų", - "unable_to_restore_trash": "Nepavyko atstatyti iš šiukšliadėžės", - "unable_to_restore_user": "Nepavyko atstatyti naudotojo", - "unable_to_save_album": "Nepavyko išsaugoti albumo", - "unable_to_save_api_key": "Nepavyko išsaugoti API rakto", - "unable_to_save_date_of_birth": "Nepavyko išsaugoti gimimo datos", - "unable_to_save_name": "Nepavyko išsaugoti vardo", - "unable_to_save_profile": "Nepavyko išsaugoti profilio", - "unable_to_save_settings": "Nepavyksta išsaugoti nustatymų", - "unable_to_scan_libraries": "Nepavyksta nuskaityti bibliotekų", - "unable_to_scan_library": "Nepavyksta nuskaityti bibliotekos", - "unable_to_set_feature_photo": "Nepavyksta nustatyti mėgstamiausios nuotraukos", - "unable_to_set_profile_picture": "Nepavyksta nustatyti profilio nuotraukos", - "unable_to_set_rating": "Nepavyko nustatyti įvertinimo", - "unable_to_submit_job": "Nepavyko sukurti užduoties", - "unable_to_trash_asset": "Nepavyko perkelti į šiukšliadėžę", - "unable_to_unlink_account": "Nepavyko atsieti paskyrų", - "unable_to_unlink_motion_video": "Nepavyko atsieti judesio video", - "unable_to_update_album_cover": "Nepavyko atnaujinti albumo viršelio", - "unable_to_update_album_info": "Nepavyko atnaujinti albumo informacijos", - "unable_to_update_library": "Nepavyko atnaujinti bibliotekos", - "unable_to_update_location": "Nepavyko atnaujinti vietovės", - "unable_to_update_settings": "Nepavyko atnaujinti nustatymų", - "unable_to_update_timeline_display_status": "Nepavyko atnaujinti laiko juostos rodymo statuso", - "unable_to_update_user": "Nepavyko atnaujinti naudotoją", - "unable_to_update_workflow": "Nepvyko atnaujinti darbų eigos", - "unable_to_upload_file": "Nepavyksta įkelti failo" - }, - "errors_text": "Klaidos", - "exclusion_pattern": "Atskyrimo šablonas", - "exif": "Exif", - "exif_bottom_sheet_description": "Pridėti aprašymą...", - "exif_bottom_sheet_description_error": "Klaida atnaujinant aprašymą", - "exif_bottom_sheet_details": "DETALĖS", - "exif_bottom_sheet_location": "VIETOVĖ", - "exif_bottom_sheet_no_description": "Nėra aprašymo", - "exif_bottom_sheet_people": "ŽMONĖS", - "exif_bottom_sheet_person_add_person": "Pridėti vardą", - "exit_slideshow": "Išeiti iš skaidrių peržiūros", - "expand_all": "Išskleisti viską", - "experimental_settings_new_asset_list_subtitle": "Dirbama", - "experimental_settings_new_asset_list_title": "Įgalinti eksperimentinį nuotraukų tinklelį", - "experimental_settings_subtitle": "Naudokite savo pačių rizika!", - "experimental_settings_title": "Eksperimentinis", - "expire_after": "Galiojimas baigiasi", - "expired": "Nebegalioja", - "expires_date": "Nebegalios už {date}", - "explore": "Naršyti", - "explorer": "Naršyklė", - "export": "Eksportuoti", - "export_as_json": "Eksportuoti kaip JSON", - "export_database": "Eksportuoti duomenų bazę", - "export_database_description": "Eksportuoti SQLite duomenų bazę", - "extension": "Plėtinys", - "external": "Išorinis", - "external_libraries": "Išorinės bibliotekos", - "external_network": "Išorinis tinklas", - "external_network_sheet_info": "Kai neprisijungta prie pageidaujamo Wi-Fi tinklo, programa jungsis prie serverio per pirmą URL nuorodą, kurią galės pasiekti, pradedant nuo viršaus į apačią", - "face_unassigned": "Nepriskirta", - "failed": "Įvyko klaida", - "failed_count": "Nepavykę: {count}", - "failed_to_authenticate": "Nepavyko autentifikuoti", - "failed_to_load_assets": "Nepavyko įkelti elementų", - "failed_to_load_folder": "Nepavyko įkelti katalogą", - "favorite": "Mėgstamiausias", - "favorite_action_prompt": "{count} pridėta prie mėgstamiausių", - "favorite_or_unfavorite_photo": "Įtraukti prie arba pašalinti iš mėgstamiausių", - "favorites": "Mėgstamiausi", - "favorites_page_no_favorites": "Nerasta mėgstamiausių elementų", - "feature_photo_updated": "Pageidaujama nuotrauka atnaujinta", - "features": "Funkcijos", - "features_in_development": "Kūrimo funkcijos", - "features_setting_description": "Valdyti aplikacijos funkcijas", - "file_name_or_extension": "Failo pavadinimas arba plėtinys", - "file_size": "Failo dydis", - "filename": "Failopavadinimas", - "filetype": "Failo tipas", - "filter": "Filtras", - "filter_people": "Filtruoti žmones", - "filter_places": "Filtruoti vietoves", - "filters": "Filtrai", - "find_them_fast": "Raskite greitai paieškoje pagal vardą", - "first": "Pirmas", - "fix_incorrect_match": "Pataisyti neteisingą porą", - "folder": "Katalogas", - "folder_not_found": "Katalogas nerastas", - "folders": "Aplankai", - "folders_feature_description": "Peržiūrėkite failų sistemoje esančias nuotraukas ir vaizdo įrašus aplankų rodinyje", - "forgot_pin_code_question": "Pamiršote savo PIN?", - "forward": "Pirmyn", - "free_up_space": "Atlaisvinti vietos", - "free_up_space_description": "Perkelkite atsargines nuotraukų ir vaizdo įrašų kopijas į įrenginio šiukšliadėžę, kad atlaisvintumėte vietos. Jūsų kopijos serveryje lieka saugios", - "free_up_space_settings_subtitle": "Atlaisvinkite įrenginio saugyklą", - "full_path": "Pilnas kelias: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Kad veiktų, ši funkcija įkelia išorinius „Google“ išteklius.", - "general": "Bendri", - "geolocation_instruction_location": "Paspauskite ant elemento su GPS koordinatėmis norint naudoti tą vietovę arba pasirinkite vietovę tiesiogiai žemėlapyje", - "get_help": "Gauti pagalbos", - "get_people_error": "Klaida gaunant žmones", - "get_wifiname_error": "Nepavyko gauti Wi-Fi pavadinimo. Įsitikinkite, kad suteikti būtini leidimai ir esate prisijungę prie Wi-Fi tinklo", - "getting_started": "Pradedama", - "go_back": "Eiti atgal", - "go_to_folder": "Eiti į katalogą", - "go_to_search": "Eiti į paiešką", - "gps": "GPS", - "gps_missing": "Be GPS", - "grant_permission": "Suteikti leidimą", - "group_albums_by": "Grupuoti albumus pagal...", - "group_country": "Grupuoti pagal šalis", - "group_no": "Negrupuoti", - "group_owner": "Grupuoti pagal savininką", - "group_places_by": "Grupuoti vietoves pagal...", - "group_year": "Grupuoti pagal metus", - "haptic_feedback_switch": "Įjungti haptinį grįžtamąjį ryšį", - "haptic_feedback_title": "Haptinis grįžtamasis ryšys", - "has_quota": "Turi kvotą", - "hash_asset": "Kurti bylos parašą elementui", - "hashed_assets": "Elementai su bylų parašais", - "hashing": "Bylų parašo kūrimas", - "header_settings_add_header_tip": "Pridėti headerį", - "header_settings_field_validator_msg": "Reikšmė negali būti tuščia", - "header_settings_header_name_input": "Antraštės pavadinimas", - "header_settings_header_value_input": "Antraštės reikšmė", - "headers_settings_tile_title": "Pasirinktinės tarpinio serverio antraštės", - "height": "Aukštis", - "hi_user": "Labas {name} ({email})", - "hide_all_people": "Slėpti visus asmenis", - "hide_gallery": "Slėpti galeriją", - "hide_named_person": "Slėpti asmenį {name}", - "hide_password": "Slėpti slaptažodį", - "hide_person": "Slėpti asmenį", - "hide_schema": "Slėpti schemą", - "hide_text_recognition": "Slėpti teksto atpažinimą", - "hide_unnamed_people": "Slėpti neįvardintus asmenis", - "home_page_add_to_album_conflicts": "Pridėta {added} elementų į albumą {album}. {failed} elementai jau yra albume.", - "home_page_add_to_album_err_local": "Kol kas negalima pridėti vietinių elementų į albumus, praleidžiama", - "home_page_add_to_album_success": "Pridėta {added} elementų į albumą {album}.", - "home_page_album_err_partner": "Kol kas negalima pridėti partnerio elementų į albumą, praleidžiama", - "home_page_archive_err_local": "Kol kas negalima archyvuoti vietinių elementų, praleidžiama", - "home_page_archive_err_partner": "Negalima archyvuoti partnerio elementų, praleidžiama", - "home_page_building_timeline": "Kuriama laiko juosta", - "home_page_delete_err_partner": "Negalima ištrinti partnerio elementų, praleidžiama", - "home_page_delete_remote_err_local": "Vietiniai elementai ištrinant nuotolinį pasirinkimą, praleidžiami", - "home_page_favorite_err_local": "Kol kas negalima priskirti mėgstamiausių vietinių elementų, praleidžiama", - "home_page_favorite_err_partner": "Kol kas negalima priskirti mėgstamiausių partnerio elementų, praleidžiama", - "home_page_first_time_notice": "Jei jūs naudojate programą pirmą kartą, tai prašome pasirinkti atsarginės kopijos albumą, kad laiko juosta galėtų tvarkyti albumo nuotraukas ir vaizdo įrašus", - "home_page_locked_error_local": "Nepavyko perkelti lokalių failų į užrakintą aplanką, praleidžiama", - "home_page_locked_error_partner": "Nepavyko perkelti partnerio failų į užrakintą aplanką, praleidžiama", - "home_page_share_err_local": "Negalima dalinti vietinių elementų per nuorodą, praleidžiama", - "home_page_upload_err_limit": "Galima įkelti tik iki 30 elementų vienu metu, praleidžiama", - "host": "Šeimininkas", - "hour": "Valanda", - "hours": "Valandos", - "id": "ID", - "idle": "Laisva", - "ignore_icloud_photos": "Ignoruoti iCloud nuotraukas", - "ignore_icloud_photos_description": "Nuotraukos laikomos iCloud nebus įkeltos į Immich serverį", - "image": "Nuotrauka", - "image_alt_text_date": "{isVideo, select, true {Filmuota} other {Fotografuota}} {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1} ir {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} {date} su {person1}, {person2} ir{person3}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} {date} su {person1}, {person2} ir {additionalCount, number} kitais", - "image_alt_text_date_place": "{isVideo, select, true {Filmuota} other {Fotografuota}} {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1} - {city}, {country} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1} ir {person2} - {city}, {country} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1}, {person2}, ir {person3} - {city}, {country} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1}, {person2}, ir {additionalCount, number} kitais - {city}, {country} {date}", - "image_saved_successfully": "Nuotrauka išsaugota", - "image_viewer_page_state_provider_download_started": "Atsisiuntimas pradėtas", - "image_viewer_page_state_provider_download_success": "Atsisiuntimas pavyko", - "image_viewer_page_state_provider_share_error": "Dalinimosi klaida", - "immich_logo": "Immich logotipas", - "immich_web_interface": "Immich Web sąsaja", - "import_from_json": "Importuoti iš JSON", - "import_path": "Importavimo kelias", - "in_albums": "{count, plural, one {# Albume} few {#Albumuose} other {# Albumų}}", - "in_archive": "Archyve", - "in_year": "{year} metais", - "in_year_selector": " ", - "include_archived": "Įtraukti archyvuotus", - "include_shared_albums": "Įtraukti bendrinamus albumus", - "include_shared_partner_assets": "Įtraukti partnerio pasidalintus elementus", - "individual_share": "Pavienis pasidalinimas", - "individual_shares": "Pavieniai pasidalinimai", - "info": "Informacija", - "interval": { - "day_at_onepm": "Kiekvieną dieną 13:00", - "hours": "Kas{hours, plural, one {valandą} few {#valandas} other {{hours, number} valandų}}", - "night_at_midnight": "Kiekvieną vidurnaktį", - "night_at_twoam": "Kiekvieną naktį 02:00" - }, - "invalid_date": "Netinkama data", - "invalid_date_format": "Netinkamas datos formatas", - "invite_people": "Kviesti žmones", - "invite_to_album": "Pakviesti į albumą", - "ios_debug_info_fetch_ran_at": "Užkrovimas vyko {dateTime}", - "ios_debug_info_last_sync_at": "Paskutinė sinchronizacija {dateTime}", - "ios_debug_info_no_processes_queued": "Eilėje nėra foninių procesų", - "ios_debug_info_no_sync_yet": "Jokia background sync užduotis dar nebuvo paleista", - "ios_debug_info_processes_queued": "{count, plural, one {Eilėje {count} foninis procesas} few {Eilėje {count} foniniai procesai} other {Eilėje {count} foninių procesų}}", - "ios_debug_info_processing_ran_at": "Apdorojimas vyko {dateTime}", - "items_count": "{count, plural, one {# elementas} few {# elementai} other {# elementų}}", - "jobs": "Užduotys", - "json_editor": "JSON redagavimas", - "json_error": "JSON klaida", - "keep": "Palikti", - "keep_all": "Palikti visus", - "keep_favorites": "Palikti mėgstamiausius", - "keep_this_delete_others": "Išsaugoti šį, kitus ištrinti", - "kept_this_deleted_others": "Išsaugotas šis elementas ir {count, plural, one {ištrintas # elementas} few {ištrinti # elementai} other {ištrinta # elementų}}", - "keyboard_shortcuts": "Spartieji klaviatūros klavišai", - "language": "Kalba", - "language_no_results_subtitle": "Bandykite pakoreguoti paieškos terminą", - "language_no_results_title": "Kalbos nerastos", - "language_search_hint": "Ieškoti kalbų...", - "language_setting_description": "Pasirinkti pageidaujamą kalbą", - "large_files": "Dideli failai", - "last": "Paskutinis", - "last_months": "{count, plural, one {Paskutinis mėnuo} other {Paskutiniai # mėnesiai}}", - "last_seen": "Paskutinį kartą matytas", - "latest_version": "Naujausia versija", - "latitude": "Platuma", - "leave": "Išeiti", - "leave_album": "Palikti albumą", - "lens_model": "Lęšių modelis", - "let_others_respond": "Leisti kitiems reaguoti", - "level": "Lygis", - "library": "Biblioteka", - "library_add_folder": "Pridėti aplanką", - "library_edit_folder": "Redaguoti aplanką", - "library_options": "Bibliotekos pasirinktys", - "library_page_device_albums": "Albumai įrenginyje", - "library_page_new_album": "Naujas albumas", - "library_page_sort_asset_count": "Elementų skaičius", - "library_page_sort_created": "Kūrimo data", - "library_page_sort_last_modified": "Paskutinį kartą modifikuota", - "library_page_sort_title": "Albumo pavadinimas", - "licenses": "Licencijos", - "light": "Šviesi", - "like": "Kaip", - "like_deleted": "Kaip ištrintas", - "link_motion_video": "Susieti judesio vaizdo įrašą", - "link_to_oauth": "Susieti su OAuth", - "linked_oauth_account": "Susieta OAuth paskyra", - "list": "Sąrašas", - "loading": "Kraunama", - "loading_search_results_failed": "Nepavyko užkrauti paieškos rezultatų", - "local": "Vietinis", - "local_asset_cast_failed": "Negalima transliuoti elemento kuris neįkeltas į serverį", - "local_assets": "Vietiniai elementai", - "local_id": "Vietinis ID", - "local_media_summary": "Vietinės medijos santrauka", - "local_network": "Vietinis tinklas", - "local_network_sheet_info": "Programa jungsis prie serverio per šį URL kai naudos pasirinktą Wi-Fi tinklą", - "location": "Vietovė", - "location_permission": "Vietovės leidimai", - "location_permission_content": "Norint naudoti automatinio persijungimo opciją, Immich reikia tikslios vietovės leidimo, kad galėtų nuskaityti Wi-Fi tinklo pavadinimą", - "location_picker_choose_on_map": "Pasirinkite žemėlapyje", - "location_picker_latitude_error": "Įveskite tinkamą platumą", - "location_picker_latitude_hint": "Įveskite platumą čia", - "location_picker_longitude_error": "Įveskite tinkamą ilgumą", - "location_picker_longitude_hint": "Įveskite ilgumą čia", - "lock": "Užrakinti", - "locked_folder": "Užrakintas aplankas", - "log_detail_title": "Žurnalo detalės", - "log_out": "Atsijungti", - "log_out_all_devices": "Atsijungti iš visų įrenginių", - "logged_in_as": "Prisijungta kaip {user}", - "logged_out_all_devices": "Atsijungta iš visų įrenginių", - "logged_out_device": "Atsijungta nuo įrenginio", - "login": "Prisijungti", - "login_disabled": "Prisijungimas neįgalintas", - "login_form_api_exception": "API išimtis. Patikrinkite serverio URL ir bandykite dar kartą.", - "login_form_back_button_text": "Atgal", - "login_form_email_hint": "jusupastas@email.com", - "login_form_endpoint_hint": "http://jusu-serverio-ip:port", - "login_form_endpoint_url": "Serverio galutinio taško URL", - "login_form_err_http": "Prašome nurodyti http:// arba https://", - "login_form_err_invalid_email": "Neteisingas el. paštas", - "login_form_err_invalid_url": "Neteisingas URL", - "login_form_err_leading_whitespace": "Pradinis tarpas", - "login_form_err_trailing_whitespace": "Galinis tarpas", - "login_form_failed_get_oauth_server_config": "Klaida prisijungiant su OAuth, patikrinkite serverio URL", - "login_form_failed_get_oauth_server_disable": "Serveryje OAuth funkcija negalima", - "login_form_failed_login": "Klaida prisijungiant, patikrinkite serverio URL, el. paštą ir slaptažodį", - "login_form_handshake_exception": "Įvyko serverio patvirtinimo išimtis. Jei naudojate savarankiškai pasirašytą sertifikatą, nustatymuose įjunkite savarankiškai pasirašyto sertifikato palaikymą.", - "login_form_password_hint": "slaptažodis", - "login_form_save_login": "Likti prisijungus", - "login_form_server_empty": "Įveskite serverio URL.", - "login_form_server_error": "Nepavyko prisijungti prie serverio.", - "login_has_been_disabled": "Prisijungimas išjungtas.", - "login_password_changed_error": "Įvyko klaida atnaujinant jūsų slaptažodį", - "login_password_changed_success": "Slaptažodis sėkmingai atnaujintas", - "logout_all_device_confirmation": "Ar tikrai norite atsijungti iš visų įrenginių?", - "logout_this_device_confirmation": "Ar tikrai norite atsijungti iš šio prietaiso?", - "logs": "Žurnalas", - "longitude": "Ilguma", - "look": "Išvaizda", - "loop_videos": "Kartoti vaizdo įrašus", - "loop_videos_description": "Įgalinti automatinį vaizdo įrašo rodymą iš naujo detalių peržiūroje.", - "main_branch_warning": "Jūs naudojate kūrėjo versiją, mes stipriai rekomenduojame naudoti galutinę versiją!", - "main_menu": "Pagrindinis meniu", - "maintenance_description": "Įjungtas Immich techninės priežiūros režimas.", - "maintenance_end": "Baigti techninę priežiūrą", - "maintenance_end_error": "Nepavyko išjungti techninės priežiūros režimo.", - "maintenance_logged_in_as": "Šiuo metu prisijungę kaip {user}", - "maintenance_title": "Laikinai Neprieinamas", - "make": "Gamintojas", - "manage_geolocation": "Tvarkyti vietovę", - "manage_media_access_rationale": "Šis leidimas reikalingas norint tinkamai perkelti elementus į šiukšliadėžę ir atkurti juos iš jos.", - "manage_media_access_settings": "Atidaryti nustatymus", - "manage_media_access_subtitle": "Leisti Immich tvarkyti ir perkelti medijos failus.", - "manage_media_access_title": "Medijos Valdymo Prieiga", - "manage_shared_links": "Bendrinimo nuorodų tvarkymas", - "manage_sharing_with_partners": "Valdyti dalijimąsi su partneriais", - "manage_the_app_settings": "Valdyti programos nustatymus", - "manage_your_account": "Valdyti savo paskyrą", - "manage_your_api_keys": "Valdyti savo API raktus", - "manage_your_devices": "Valdyti prijungtus įrenginius", - "manage_your_oauth_connection": "Tvarkyti OAuth prisijungimą", - "map": "Žemėlapis", - "map_assets_in_bounds": "{count, plural, =0 {Nuotraukų nėra} one {# nuotrauka} other {# nuotraukos}}", - "map_cannot_get_user_location": "Negalime gauti naudotojo vietovės", - "map_location_dialog_yes": "Taip", - "map_location_picker_page_use_location": "Naudoti šią vietovę", - "map_location_service_disabled_content": "Vietovės servisas turi būti įjungtas, kad rodytų elementus iš dabartinės vietovės. Įjungti vietovės servisą?", - "map_location_service_disabled_title": "Vietovės servisas išjungtas", - "map_marker_for_images": "Žemėlapio žymeklis nuotraukoms yra {city}, {country}", - "map_marker_with_image": "Žemėlapio žymeklis su nuotrauka", - "map_no_location_permission_content": "Reikalingas vietovės leidimas, kad rodytų elementus iš dabartinės vietovės. Ar norite suteikti leidimą?", - "map_no_location_permission_title": "Vietovės leidimas atmestas", - "map_settings": "Žemėlapio nustatymai", - "map_settings_dark_mode": "Tamsi tema", - "map_settings_date_range_option_day": "Pastarosios 24 valandos", - "map_settings_date_range_option_days": "Pastarąsias {days} dienas", - "map_settings_date_range_option_year": "Pastarieji metai", - "map_settings_date_range_option_years": "Pastaruosius {years} metus", - "map_settings_dialog_title": "Žemėlapio nustatymai", - "map_settings_include_show_archived": "Įtraukti archyvuotus", - "map_settings_include_show_partners": "Pridėti partneriai", - "map_settings_only_show_favorites": "Rodyti tik mėgstamiausius", - "map_settings_theme_settings": "Žemėlapio tema", - "map_zoom_to_see_photos": "Atitolinkite, kad matytumėte nuotraukas", - "mark_all_as_read": "Pažymėti viską kaip perskaitytą", - "mark_as_read": "Pažymėti kaip perskaitytą", - "marked_all_as_read": "Viskas pažymėta kaip perskaityta", - "matches": "Atitikmenys", - "matching_assets": "Atitinkantys elementai", - "media_type": "Laikmenos tipas", - "memories": "Atsiminimai", - "memories_all_caught_up": "Jau viskas peržiūrėta", - "memories_check_back_tomorrow": "Užsukite rytoj, kad pamatytumėte daugiau prisiminimų", - "memories_setting_description": "Valdyti tai, ką matote savo prisiminimuose", - "memories_start_over": "Pradėti iš naujo", - "memories_swipe_to_close": "Perbraukite į viršų norėdami uždaryti", - "memory": "Prisiminimai", - "memory_lane_title": "Prisiminimų juosta {title}", - "menu": "Meniu", - "merge": "Sujungti", - "merge_people": "Sujungti asmenis", - "merge_people_limit": "Vienu metu galite sujungti tik iki 5 veidų", - "merge_people_prompt": "Ar norite sujungti šiuos asmenis? Šis veiksmas yra negrįžtamas.", - "merge_people_successfully": "Asmenys sėkmingai sujungti", - "merged_people_count": "{count, plural, one {Sujungtas # asmuo} few {Sujungti # asmenys} other {Sujungta # asmenų}}", - "minimize": "Sumažinti", - "minute": "Minutė", - "minutes": "Minutės", - "mirror_horizontal": "Horizontaliai", - "mirror_vertical": "Vertikaliai", - "missing": "Trūkstami", - "mobile_app": "Mobili programa", - "mobile_app_download_onboarding_note": "Atsisiųskite mobiliąją programėlę naudodami šias parinktis", - "model": "Modelis", - "month": "Mėnesis", - "monthly_title_text_date_format": "MMMM y", - "more": "Daugiau", - "move": "Perkelti", - "move_down": "Žemyn", - "move_off_locked_folder": "Ištraukti iš užrakinto aplanko", - "move_to": "Perkelti į", - "move_to_device_trash": "Perkelti į įrenginio šiukšliadėžę", - "move_to_lock_folder_action_prompt": "{count} įkelta į užrakintą aplanką", - "move_to_locked_folder": "Įtraukti į užrakintą aplanką", - "move_to_locked_folder_confirmation": "Šios nuotraukos ir vaizdo įrašai bus pašalinti iš visų albumų ir bus matomi tik užrakintame aplanke", - "move_up": "Aukštyn", - "moved_to_archive": "{count, plural, one {# Elementas perkeltas} few {# Elementai perkelti} other {# Elementų perkelta}} į archyvą", - "moved_to_library": "{count, plural, one {# Elementas perkeltas} few {# Elementai perkelti} other {# Elementų perkelta}} į biblioteką", - "moved_to_trash": "Perkelta į šiukšliadėžę", - "multiselect_grid_edit_date_time_err_read_only": "Negalima redaguoti tik skaitomo elemento datos, praleidžiama", - "multiselect_grid_edit_gps_err_read_only": "Negalima redaguoti tik skaitomo elemento vietovės, praleidžiama", - "mute_memories": "Užtildyti prisiminimus", - "my_albums": "Mano albumai", - "name": "Vardas", - "name_or_nickname": "Vardas arba slapyvardis", - "name_required": "Vardas yra privalomas", - "navigate": "Naviguoti", - "navigate_to_time": "Naviguoti pagal Laiką", - "network_requirement_photos_upload": "Naudoti mobilų internetą atsarginėms nuotraukų kopijoms", - "network_requirement_videos_upload": "Naudoti mobilų internetą atsarginėms vaizdo įrašų kopijoms", - "network_requirements": "Tinklo reikalavimai", - "network_requirements_updated": "Tinklo reikalavimai pakeisti, atstatoma atsarginio kopijavimo eilė", - "networking_settings": "Tinklai", - "networking_subtitle": "Tvarkyti serverio galutinio taško nustatymus", - "never": "Niekada", - "new_album": "Naujas albumas", - "new_api_key": "Naujas API raktas", - "new_date_range": "Naujas datos intervalas", - "new_password": "Naujas slaptažodis", - "new_person": "Naujas asmuo", - "new_pin_code": "Naujas PIN kodas", - "new_pin_code_subtitle": "Tai pirmas kartas, kai naudojate užrakinto aplanko funkciją. Nustatykite PIN kodą savo užrakintam aplankui", - "new_timeline": "Nauja laiko juosta", - "new_update": "Nauja versija", - "new_user_created": "Naujas naudotojas sukurtas", - "new_version_available": "PRIEINAMA NAUJA VERSIJA", - "newest_first": "Pirmiausia naujausi", - "next": "Sekantis", - "next_memory": "Sekantis atsiminimas", - "no": "Ne", - "no_actions_added": "Jokių veiksmų dar nepridėta", - "no_albums_message": "Sukurkite albumą nuotraukoms ir vaizdo įrašams tvarkyti", - "no_albums_with_name_yet": "Atrodo, kad dar neturite albumų su šiuo pavadinimu.", - "no_albums_yet": "Atrodo, kad dar neturite albumų.", - "no_archived_assets_message": "Suarchyvuokite nuotraukas ir vaizdo įrašus, kad jie nebūtų rodomi nuotraukų rodinyje", - "no_assets_message": "SPUSTELĖKITE NORĖDAMI ĮKELTI SAVO PIRMĄJĄ NUOTRAUKĄ", - "no_assets_to_show": "Nėra rodomų elementų", - "no_cast_devices_found": "Nerasta transliavimo įrenginių", - "no_checksum_local": "Kontrolinė suma nepasiekiama – negalima gauti vietinių elementų", - "no_checksum_remote": "Kontrolinė suma nepasiekiama – negalima gauti nuotolinių elementų", - "no_configuration_needed": "Konfigūracija nereikalinga", - "no_devices": "Nėra autorizuotų įrenginių", - "no_duplicates_found": "Dublikatų nerasta.", - "no_exif_info_available": "Nėra Exif informacijos", - "no_explore_results_message": "Įkelkite daugiau nuotraukų ir tyrinėkite savo kolekciją.", - "no_favorites_message": "Pridėti į mėgstamiausius, kad greitai rastum geriausias nuotraukas ir vaizdo įrašus", - "no_filters_added": "Filtrų dar nepridėta", - "no_libraries_message": "Sukurkite išorinę biblioteką nuotraukoms ir vaizdo įrašams peržiūrėti", - "no_local_assets_found": "Nerasta jokių vietinių elementų su šia kontroline suma", - "no_location_set": "Nenustatyta vietovė", - "no_locked_photos_message": "Užrakintame aplanke esančios nuotraukos ir vaizdo įrašai yra paslėpti ir nematomi naršant ir ieškant.", - "no_name": "Be vardo", - "no_notifications": "Pranešimų nėra", - "no_people_found": "Ieškomų žmonių nerasta", - "no_places": "Vietovių nėra", - "no_remote_assets_found": "Nerasta jokių nuotolinių elementų su šia kontroline suma", - "no_results": "Rezultatų nerasta", - "no_results_description": "Pabandykite sinonimą arba bendresnį raktažodį", - "no_shared_albums_message": "Sukurkite nuotraukų ar vaizdo įrašų albumą dalinimuisi su žmonėmis jūsų tinkle", - "no_uploads_in_progress": "Nėra vykstančių įkėlimų", - "not_allowed": "Neleidžiama", - "not_available": "Nepasiekiamas", - "not_in_any_album": "Nė viename albume", - "not_selected": "Nepasirinkta", - "note_apply_storage_label_to_previously_uploaded assets": "Pastaba: Priskirti Saugyklos Žymą prie anksčiau įkeltų ištekliu, paleiskite šį", - "notes": "Pastabos", - "nothing_here_yet": "Kol kas tuščia", - "notification_permission_dialog_content": "Pranešimų įgalinimui eikite į Nustatymus ir pasirinkite Leisti.", - "notification_permission_list_tile_content": "Suteikti leidimą pranešimų įgalinimui.", - "notification_permission_list_tile_enable_button": "Įgalinti pranešimus", - "notification_permission_list_tile_title": "Pranešimų leidimai", - "notification_toggle_setting_description": "Įjungti el. pašto pranešimus", - "notifications": "Pranešimai", - "notifications_setting_description": "Tvarkyti pranešimus", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium Konfigūratorius", - "obtainium_configurator_instructions": "Naudokite Obtainium, jei norite įdiegti ir atnaujinti Android programėlę tiesiai iš Immich GitHub. Sukurkite API raktą ir pasirinkite variantą, Obtainium konfigūracijos nuorodos sukūrimui", - "ocr": "OCR", - "official_immich_resources": "Oficialūs Immich ištekliai", - "offline": "Neprisijungęs", - "offset": "Ofsetas", - "ok": "Ok", - "oldest_first": "Seniausias pirmas", - "on_this_device": "Šiame įrenginyje", - "onboarding": "Įdarbinimas", - "onboarding_locale_description": "Pasirinkite pageidaujamą kalbą. Vėliau ją galėsite pakeisti nustatymuose.", - "onboarding_privacy_description": "Sekančios (neprivalomos) funkcijos remiasi išorinėmis paslaugomis ir gali būti bet kada išjungtos nustatymuose.", - "onboarding_server_welcome_description": "Nustatykime jūsų programą su dažniausiai naudojamais nustatymais.", - "onboarding_theme_description": "Pasirinkite temos spalvą. Vėliau galite pasikeisti ją nustatymuose.", - "onboarding_user_welcome_description": "Pradėkime!", - "onboarding_welcome_user": "Sveiki atvykę, {user}", - "online": "Prisijungęs", - "only_favorites": "Tik mėgstamiausi", - "open": "Atverti", - "open_in_map_view": "Atverti žemėlapio peržiūroje", - "open_in_openstreetmap": "Atverti per OpenStreetMap", - "open_the_search_filters": "Atidaryti paieškos filtrus", - "options": "Pasirinktys", - "or": "arba", - "organize_into_albums": "Sutvarkyti į albumus", - "organize_into_albums_description": "Sukelti egzistuojančias nuotraukas į albumus naudojant dabartinius sinchronizavimo nustatymus", - "organize_your_library": "Tvarkykite savo biblioteką", - "original": "Originalas", - "other": "Kiti", - "other_devices": "Kiti įrenginiai", - "other_entities": "Kiti subjektai", - "other_variables": "Kiti kintamieji", - "owned": "Nuosavi", - "owner": "Savininkas", - "partner": "Partneris", - "partner_can_access": "{partner} gali naudotis", - "partner_can_access_assets": "Visos jūsų nuotraukos ir vaizdo įrašai, išskyrus archyvuotus ir ištrintus", - "partner_can_access_location": "Vieta, kurioje darytos nuotraukos", - "partner_list_user_photos": "{user} nuotraukos", - "partner_list_view_all": "Žiūrėti viską", - "partner_page_empty_message": "Jūsų nuotraukomis dar nesidalinama su jokiu partneriu.", - "partner_page_no_more_users": "Nėra daugiau naudotojų pridėjimui", - "partner_page_partner_add_failed": "Nepavyko pridėti partnerio", - "partner_page_select_partner": "Pasirinkite partnerį", - "partner_page_shared_to_title": "Dalinamasi su", - "partner_page_stop_sharing_content": "{partner} daugiau nebegalės pasiekti jūsų nuotraukų.", - "partner_sharing": "Dalinimasis su partneriu", - "partners": "Partneriai", - "password": "Slaptažodis", - "password_does_not_match": "Slaptažodis nesutampa", - "password_required": "Reikalingas slaptažodis", - "password_reset_success": "Slaptažodis sėkmingai atkurtas", - "past_durations": { - "days": "Per {days, plural, one {pastarąją dieną} few {# pastarąsias dienas} other {# pastarųjų dienų}}", - "hours": "Per {hours, plural, one {pastarąją valandą} few{# pastarąsias valandas} other {# pastarųjų valandų}}", - "years": "Per {years, plural, one {pastaruosius metus} few{# pastaruosius metus} other {# pastarųjų metų}}" - }, - "path": "Kelias", - "pattern": "Raštas", - "pause": "Sustabdyti", - "pause_memories": "Pristabdyti atsiminimus", - "paused": "Sustabdyta", - "pending": "Laukiama", - "people": "Asmenys", - "people_edits_count": "{count, plural, one {Redaguotas # asmuo} few {Redaguoti # asmenys} other {Redaguota # asmenų}}", - "people_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal asmenis", - "people_sidebar_description": "Rodyti asmenų rodinio nuorodą šoninėje juostoje", - "permanent_deletion_warning": "Ištrynimo visam laikui perspėjimas", - "permanent_deletion_warning_setting_description": "Rodyti perspėjimą kai elementas ištrinamas visam laikui", - "permanently_delete": "Ištrinti visam laikui", - "permanently_delete_assets_count": "Visam laikui ištrinti {count, plural, one {# elementą} few {# elementus} other {# elementų}}", - "permanently_delete_assets_prompt": "Ar tikrai norite visam laikui ištrinti {count, plural, one {šitą elementą?} other {šituos # elementus?} other {šitų # elementų?}} Tuo pačiu {count, plural, one {jis bus pašalintas} other {jie bus pašalinti}} iš albumo(ų).", - "permanently_deleted_asset": "Visiškai ištrinti elementai", - "permanently_deleted_assets_count": "Visam laikui {count, plural, one {ištrintas # elementas} few {ištrinti # elementai} other {ištrinta # elementų}}", - "permission": "Leidimas", - "permission_empty": "Jūsų leidimas neturėtų būti tuščias", - "permission_onboarding_back": "Atgal", - "permission_onboarding_continue_anyway": "Vis tiek tęsti", - "permission_onboarding_get_started": "Pradėkite", - "permission_onboarding_go_to_settings": "Eiti į nustatymus", - "permission_onboarding_permission_denied": "Leidimas nesuteiktas. Norėdami naudoti Immich, suteikite nuotraukų ir vaizdo įrašų leidimus nustatymuose.", - "permission_onboarding_permission_granted": "Leidimas suteiktas! jūs pasiruošę.", - "permission_onboarding_permission_limited": "Leidimai apriboti. Norėdami leisti Immich kurti atsargines kopijas ir tvarkyti visą jūsų galerijos kolekciją, suteikite nuotraukų ir vaizdo įrašų leidimus nustatymuose.", - "permission_onboarding_request": "Immich reikalingas leidimas peržiūrėti jūsų nuotraukas ir vaizdo įrašus.", - "person": "Asmuo", - "person_age_months": "{months, plural, one {# mėnesio} other {# mėnesių}} amžiaus", - "person_age_year_months": "1 metų ir {months, plural, one {# mėnesio} other {# mėnesių}} amžiaus", - "person_age_years": "{years, plural, other {# metų}} amžiaus", - "person_birthdate": "Gimė {date}", - "person_hidden": "{name}{hidden, select, true { (paslėptas)} other {}}", - "photo_shared_all_users": "Panašu, kad savo nuotraukomis pasidalijote su visais naudotojais arba neturite naudotojų, su kuriais galėtumėte jomis pasidalyti.", - "photos": "Nuotraukos", - "photos_and_videos": "Nuotraukos ir vaizdo įrašai", - "photos_count": "{count, plural, one {{count, number} nuotrauka} few {{count, number} nuotraukos} other {{count, number} nuotraukų}}", - "photos_from_previous_years": "Ankstesnių metų nuotraukos", - "pick_a_location": "Išsirinkite vietovę", - "pin_code_changed_successfully": "PIN kodas pakeistas sėkmingai", - "pin_code_reset_successfully": "PIN kodas sėkmingai atstatytas", - "pin_code_setup_successfully": "PIN kodas sėkmingai nustatytas", - "pin_verification": "PIN kodo patvirtinimas", - "place": "Vieta", - "places": "Vietos", - "places_count": "{count, plural, one {{count, number} Vieta} few{{count, number} Vietos} other {{count, number} Vietų}}", - "play": "Paleisti", - "play_memories": "Leisti atsiminimus", - "play_motion_photo": "Rodyti judančias nuotraukas", - "play_or_pause_video": "Rodyti arba sustabdyti vaizdo įrašą", - "please_auth_to_access": "Prašome patvirtinti prisijungimą", - "port": "Portas", - "preferences_settings_subtitle": "Tvarkyti programos nuostatas", - "preferences_settings_title": "Nuostatos", - "preparing": "Ruošiama", - "preset": "Šablonas", - "preview": "Peržiūra", - "previous": "Buvęs", - "previous_memory": "Buvęs prisiminimas", - "previous_or_next_day": "Dieną pirmyn/atgal", - "previous_or_next_month": "Mėnesį pirmyn/atgal", - "previous_or_next_photo": "Nuotrauką pirmyn/atgal", - "previous_or_next_year": "Metus pirmyn/atgal", - "primary": "Pirminis", - "privacy": "Privatumas", - "profile": "Profilis", - "profile_drawer_app_logs": "Logai", - "profile_drawer_client_server_up_to_date": "Klientas ir Serveris yra atnaujinti", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Tik skaitymo rėžimas įgalintas. Ilgai paspauskite vartotojo ikoną išėjimui.", - "profile_image_of_user": "{user} profilio nuotrauka", - "profile_picture_set": "Profilio nuotrauka nustatyta.", - "public_album": "Viešas albumas", - "public_share": "Viešas dilinimasis", - "purchase_account_info": "Rėmėjas", - "purchase_activated_subtitle": "Dėkojame, kad remiate Immich ir atviro kodo programinę įrangą", - "purchase_activated_time": "Suaktyvinta {date}", - "purchase_activated_title": "Jūsų raktas sėkmingai aktyvuotas", - "purchase_button_activate": "Aktyvuoti", - "purchase_button_buy": "Pirkti", - "purchase_button_buy_immich": "Pirkti Immich", - "purchase_button_never_show_again": "Niekada daugiau nerodyti", - "purchase_button_reminder": "Priminti man po 30 dienų", - "purchase_button_remove_key": "Pašalinti produkto rakta", - "purchase_button_select": "Pasirinkti", - "purchase_failed_activation": "Nepavyko suaktyvinti! Patikrinkite el. paštą, ar turite teisingo produkto koda!", - "purchase_individual_description_1": "Asmeniui", - "purchase_individual_description_2": "Rėmėjo statusas", - "purchase_individual_title": "Asmeninis", - "purchase_input_suggestion": "Turite produkto raktą? Įveskite jį žemiau", - "purchase_license_subtitle": "Įsigykite „Immich“, kad palaikytumėte tolesnį paslaugos vystymą", - "purchase_lifetime_description": "Pirkimas visam gyvenimui", - "purchase_option_title": "PIRKIMO PASIRINKIMAS", - "purchase_panel_info_1": "„Immich“ kūrimas užima daug laiko ir pastangų, o visą darbo dieną dirba inžinieriai, kad jis būtų kuo geresnis. Mūsų misija yra, kad atvirojo kodo programinė įranga ir etiška verslo praktika taptų tvariu kūrėjų pajamų šaltiniu ir sukurtų privatumą gerbiančią ekosistemą su realiomis alternatyvomis išnaudojamoms debesijos paslaugoms.", - "purchase_panel_info_2": "Kadangi esame įsipareigoję nepridėti mokamų sienų, šis pirkinys nesuteiks jums jokių papildomų Immich funkcijų. Mes tikime, kad tokie naudotojai kaip jūs palaikys nuolatinį Immich vystymąsi.", - "purchase_panel_title": "Palaikykite projektą", - "purchase_per_server": "Vienam serveriui", - "purchase_per_user": "Vienam naudotojui", - "purchase_remove_product_key": "Pašalinti produkto raktą", - "purchase_remove_product_key_prompt": "Ar tikrai norite pašalinti produkto raktą?", - "purchase_remove_server_product_key": "Pašalinti serverio produkto raktą", - "purchase_remove_server_product_key_prompt": "Ar tikrai norite pašalinti serverio produkto raktą?", - "purchase_server_description_1": "Visam serveriui", - "purchase_server_description_2": "Rėmėjo statusas", - "purchase_server_title": "Serveris", - "purchase_settings_server_activated": "Serverio produkto raktas yra tvarkomas administratoriaus", - "rating": "Įvertinimas žvaigždutėmis", - "rating_count": "{count, plural, one {# įvertinimas} few {# įvertinimai} other {# įvertinimų}}", - "rating_description": "Rodyti EXIF įvertinimus informacijos skydelyje", - "read_changelog": "Skaityti pakeitimų sąrašą", - "ready_for_upload": "Paruošta įkėlimui", - "recent-albums": "Naujausi albumai", - "recent_searches": "Naujausios paieškos", - "recently_added": "Neseniai pridėta", - "recently_added_page_title": "Neseniai pridėta", - "recently_taken": "Neseniai sukurti", - "recently_taken_page_title": "Neseniai sukurti", - "refresh": "Atnaujinti", - "refresh_encoded_videos": "Perkrauti apdorotus vaizdo įrašus", - "refresh_faces": "Perkrauti veidus", - "refresh_metadata": "Perkrauti metaduomenis", - "refresh_thumbnails": "Perkrauti miniatiūras", - "refreshed": "Atnaujinta", - "refreshes_every_file": "Iš naujo perskaito visus esamus ir naujai pridėtus failus", - "refreshing_encoded_video": "Perkraunamas apdorotas vaizdo įrašas", - "refreshing_faces": "Perkraunami veidai", - "refreshing_metadata": "Perkraunami metaduomenys", - "remove": "Pašalinti", - "remove_assets_shared_link_confirmation": "Ar tikrai norite pašalinti {count, plural, one {# elementą} few {# elementus} other {# elementų}} iš šios bendrinimo nuorodos?", - "remove_assets_title": "Pašalinti elementus?", - "remove_deleted_assets": "Pašalinti Ištrintus Elemenuts", - "remove_from_album": "Pašalinti iš albumo", - "remove_from_album_action_prompt": "{count} pašalinta iš albumo", - "remove_from_favorites": "Pašalinti iš mėgstamiausių", - "remove_from_lock_folder_action_prompt": "{count} ištraukta iš užrakinto aplanko", - "remove_from_locked_folder": "Išimti iš užrakinto aplanko", - "remove_from_locked_folder_confirmation": "Ar tikrai norite perkelti šias nuotraukas ir vaizdo įrašus iš užrakinto aplanko? Jie taps matomi jūsų galerijoje.", - "remove_from_shared_link": "Pašalinti iš bendrinimo nuorodos", - "remove_tag": "Pašalinti žymę", - "remove_url": "Pašalinti URL", - "remove_user": "Pašalinti naudotoją", - "removed_api_key": "Pašalintas API Raktas: {name}", - "removed_from_archive": "Pašalinta iš archyvo", - "removed_from_favorites": "Pašalinta iš mėgstamiausių", - "removed_from_favorites_count": "{count, plural, one {# pašalintas} few {# pašalinti} other {# pašalinta}} iš mėgstamiausių", - "removed_memory": "Atsiminimas pašalintas", - "removed_photo_from_memory": "Nuotrauka ištrinta iš atsiminimų", - "removed_tagged_assets": "Žyma pašalinta iš {count, plural, one {# elemento} other {# elementų}}", - "rename": "Pervadinti", - "repair": "Pataisyti", - "repair_no_results_message": "Nesekami ir trūkstami failai bus rodomi čia", - "replace_with_upload": "Pakeisti naujai įkeltu failu", - "repository": "Repozitoriumas", - "require_password": "Reikalauti slaptažodžio", - "rescan": "Perskenuoti", - "reset": "Atstatyti", - "reset_password": "Atstayti slaptažodį", - "reset_pin_code": "Atsatyti PIN kodą", - "reset_pin_code_description": "Jei pamiršote PIN kodą, galite susisiekti su serverio administratoriumi, kad jis jį atstatytų", - "reset_pin_code_with_password": "PIN kodą visada galite atkurti naudodami savo slaptažodį", - "reset_to_default": "Atkurti numatytuosius", - "resolution": "Rezoliucija", - "resolve_duplicates": "Sutvarkyti dublikatus", - "resolved_all_duplicates": "Sutvarkyti visi dublikatai", - "restore": "Atkurti", - "restore_all": "Atkurti visus", - "restore_user": "Atkurti naudotoją", - "restored_asset": "Atkurti elementą", - "review_duplicates": "Peržiūrėti dublikatus", - "save": "Išsaugoti", - "save_to_gallery": "Išsaugoti galerijoje", - "saved": "Išsaugota", - "saved_api_key": "Išsaugotas API raktas", - "saved_profile": "Išsaugotas profilis", - "saved_settings": "Išsaugoti nustatymai", - "say_something": "Ką nors pasakykite", - "scaffold_body_error_occurred": "Įvyko klaida", - "scan": "Skenuoti", - "scan_all_libraries": "Skenuoti visas bibliotekas", - "scan_library": "Skenuoti", - "scan_settings": "Skenavimo nustatymai", - "scanning": "Skenuojama", - "scanning_for_album": "Skenuojama albumų...", - "search": "Ieškoti", - "search_albums": "Ieškoti albumų", - "search_by_context": "Ieškoti pagal kontekstą", - "search_by_description": "Ieškoti pagal aprašymą", - "search_by_description_example": "Žygio diena Sapoje", - "search_by_filename": "Ieškoti pagal failo pavadinimą arba plėtinį", - "search_by_filename_example": "pvz. IMG_1234.JPG arba PNG", - "search_camera_make": "Ieškoti pagal kameros gamintoją...", - "search_camera_model": "Ieškoti kameros modelį...", - "search_city": "Ieškoti miesto...", - "search_country": "Ieškoti šalies...", - "search_filter_camera_title": "Pasirinkti kameros tipą", - "search_filter_date": "Data", - "search_filter_display_option_not_in_album": "Ne albume", - "search_filter_display_options": "Rodymo Nustatymai", - "search_filter_filename": "Ieškoti pagal failo pavadinimą", - "search_filter_location": "Vietovė", - "search_filter_location_title": "Pasirinkti vietovę", - "search_filter_media_type": "Medijos tipas", - "search_filter_media_type_title": "Pasirinkti medijos tipą", - "search_no_more_result": "Nėra daugiau rezultatų", - "search_no_people_named": "Nėra žmonių vardu „{name}“", - "search_page_categories": "Kategorijos", - "search_page_screenshots": "Ekrano nuotraukos", - "search_page_search_photos_videos": "Ieškokite nuotraukų ir vaizdo įrašų", - "search_page_selfies": "Asmenukės", - "search_page_things": "Dalykai", - "search_page_view_all_button": "Peržiūrėti visus", - "search_page_your_activity": "Jūsų veikla", - "search_page_your_map": "Jūsų žemėlapis", - "search_people": "Ieškoti žmonių", - "search_places": "Ieškoti vietų", - "search_rating": "Ieškoti pagal įvertinimą...", - "search_result_page_new_search_hint": "Nauja Paieška", - "search_settings": "Ieškoti nustatymų", - "search_tags": "Ieškoti žymų...", - "search_timezone": "Ieškoti laiko zonos...", - "search_type": "Paieškos tipas", - "search_your_photos": "Ieškoti nuotraukų", - "select_all_duplicates": "Pasirinkti visus dublikatus", - "select_all_in": "Pažymėti visus esančius {group}", - "select_avatar_color": "Pasirinkti avataro spalvą", - "select_face": "Pasirinkti veidą", - "select_featured_photo": "Pasirinkti rodomą nuotrauką", - "select_from_computer": "Pasirinkti iš kompiuterio", - "select_keep_all": "Visus pažymėti \"Palikti\"", - "select_library_owner": "Pasirinkti bibliotekos savininką", - "select_new_face": "Pasirinkti naują veidą", - "select_photos": "Pasirinkti nuotraukas", - "select_trash_all": "Visus pažymėti \"Išmesti\"", - "selected": "Pasirinkta", - "selected_count": "{count, plural, one {# pasirinktas} few {# pasirinkti} other {# pasirinktų}}", - "selected_gps_coordinates": "Pasirinkti GPS Koordinates", - "send_message": "Siųsti žinutę", - "send_welcome_email": "Siųsti sveikinimo el. laišką", - "server_info_box_app_version": "Programėlės versija", - "server_info_box_server_url": "Serverio URL", - "server_offline": "Serveris nepasiekiamas", - "server_online": "Serveris pasiekiamas", - "server_privacy": "Serverio Privatumas", - "server_stats": "Serverio statistika", - "server_version": "Serverio versija", - "set": "Nustatyti", - "set_as_profile_picture": "Nustatyti kaip profilio nuotrauką", - "set_date_of_birth": "Nustatyti gimimo datą", - "set_profile_picture": "Nustatyti profilio nuotrauką", - "set_slideshow_to_fullscreen": "Nustatyti skaidrių peržiūrą per visą ekraną", - "set_stack_primary_asset": "Nustatyti kaip pagrindinį elementą", - "setting_image_viewer_original_title": "Užkrauti originalią nuotrauką", - "setting_image_viewer_preview_title": "Užkrauti peržiūros nuotrauką", - "setting_image_viewer_title": "Nuotraukos", - "setting_languages_apply": "Pritaikyti", - "setting_languages_subtitle": "Pakeisti programos kalbą", - "setting_notifications_notify_failures_grace_period": "Informuoti apie foninio atsarginio kopijavimo nesėkmes: {duration}", - "setting_notifications_notify_hours": "{count} valandų", - "setting_notifications_notify_minutes": "{count} minučių", - "setting_notifications_notify_never": "niekada", - "setting_notifications_notify_seconds": "{count} sekundžių", - "setting_notifications_single_progress_subtitle": "Detali įkėlimo progreso informacija kiekvienam elementui", - "settings": "Nustatymai", - "settings_require_restart": "Prašome perkrauti Immich, siekiant pritaikyti šį nustatymą", - "settings_saved": "Nustatymai išsaugoti", - "setup_pin_code": "Nustatyti PIN kodą", - "share": "Dalintis", - "share_add_photos": "Įtraukti nuotraukų", - "share_assets_selected": "{count} pažymėta", - "share_dialog_preparing": "Ruošiama...", - "share_link": "Bendrinti nuorodą", - "shared": "Bendrinami", - "shared_by_user": "Bendrina {user}", - "shared_by_you": "Bendrinama jūsų", - "shared_from_partner": "Nuotraukos iš {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Įkelta", - "shared_link_clipboard_copied_massage": "Nukopijuota į iškarpinę", - "shared_link_clipboard_text": "Nuoroda: {link}\nSlaptažodis: {password}", - "shared_link_edit_expire_after_option_day": "1 diena", - "shared_link_edit_expire_after_option_days": "{count} dienų", - "shared_link_edit_expire_after_option_hour": "1 valanda", - "shared_link_edit_expire_after_option_hours": "{count} valandų", - "shared_link_edit_expire_after_option_minute": "1 minutė", - "shared_link_edit_expire_after_option_minutes": "{count} minučių", - "shared_link_edit_expire_after_option_months": "{count} mėnesių", - "shared_link_edit_expire_after_option_year": "{count} metų", - "shared_link_expires_day": "Galiojimas baigsis už {count} dienos", - "shared_link_expires_days": "Galiojimas baigsis už {count} dienų", - "shared_link_expires_hour": "Galiojimas baigsis už {count} valandos", - "shared_link_expires_hours": "Galiojimas baigsis už {count} valandų", - "shared_link_expires_minute": "Galiojimas baigsis už {count} minutės", - "shared_link_expires_minutes": "Galiojimas baigsis už {count} minučių", - "shared_link_expires_second": "Galiojimas baigsis už {count} sekundės", - "shared_link_expires_seconds": "Galiojimas baigsis už {count} sekundžių", - "shared_link_options": "Bendrinimo nuorodos parametrai", - "shared_links": "Bendrinimo nuorodos", - "shared_photos_and_videos_count": "{assetCount, plural, one {# bendrinama nuotrauka ir vaizdo įrašas} few {# bendrinamos nuotraukos ir vaizdo įrašai} other {# bendrinamų nuotraukų ir vaizdo įrašų}}", - "shared_with_me": "Bendrinama su manimi", - "shared_with_partner": "Pasidalinta su {partner}", - "sharing": "Dalijimasis", - "sharing_enter_password": "Norėdami peržiūrėti šį puslapį, įveskite slaptažodį.", - "sharing_page_album": "Bendrinami albumai", - "sharing_page_empty_list": "TUŠČIAS SĄRAŠAS", - "sharing_sidebar_description": "Rodyti bendrinimo rodinio nuorodą šoninėje juostoje", - "sharing_silver_appbar_create_shared_album": "Naujas bendrinamas albumas", - "sharing_silver_appbar_share_partner": "Bendrinti su partneriu", - "show_album_options": "Rodyti albumo parinktis", - "show_albums": "Rodyti albumus", - "show_all_people": "Rodyti visus asmenis", - "show_and_hide_people": "Rodyti ir paslėpti žmones", - "show_file_location": "Rodyti failo vietą", - "show_gallery": "Rodyti galeriją", - "show_hidden_people": "Rodyti paslėptus asmenis", - "show_in_timeline": "Rodyti laiko skalėje", - "show_in_timeline_setting_description": "Rodyti šio naudotojo nuotraukas ir vaizdo įrašus mano laiko skalėje", - "show_keyboard_shortcuts": "Rodyti klaviatūros trumpinius", - "show_metadata": "Rodyti metaduomenis", - "show_or_hide_info": "Rodyti arba slėpti informaciją", - "show_password": "Rodyti slaptažodį", - "show_progress_bar": "Rodyti progreso juostą", - "show_search_options": "Rodyti paieškos parinktis", - "show_slideshow_transition": "Rodyti perėjimą tarp skaidrių", - "show_supporter_badge": "Rėmėjo ženklelis", - "show_supporter_badge_description": "Rodyti rėmėjo ženklelį", - "sidebar": "Šoninė juosta", - "sidebar_display_description": "Rodyti rodinio nuorodą šoninėje juostoje", - "sign_out": "Atsijungti", - "sign_up": "Užsiregistruoti", - "size": "Dydis", - "skip_to_content": "Pereiti prie turinio", - "skip_to_folders": "Praleisti iki aplankų", - "skip_to_tags": "Praleisti iki žymių", - "slideshow": "Skaidrių peržiūra", - "slideshow_settings": "Skaidrių peržiūros nustatymai", - "sort_albums_by": "Rikiuoti albumus pagal...", - "sort_created": "Sukūrimo data", - "sort_items": "Elementų skaičių", - "sort_modified": "Keitimo data", - "sort_newest": "Naujausia nuotrauka", - "sort_oldest": "Seniausia nuotrauka", - "sort_people_by_similarity": "Rikiuoti žmonės pagal panašumą", - "sort_recent": "Naujausia nuotrauka", - "sort_title": "Pavadinimas", - "source": "Šaltinis", - "stack": "Grupuoti", - "stack_action_prompt": "{count} sugrupuota", - "stack_duplicates": "Grupuoti dublikatus", - "stack_select_one_photo": "Pasirinkti pagrindinę grupės nuotrauką", - "stack_selected_photos": "Grupuoti pasirinktas nuotraukas", - "stacked_assets_count": "{count, plural, one {Sugrupuotas # elementas} few {Sugrupuoti # elementai} other {Sugrupuota # elementų}}", - "stacktrace": "Stacktrace", - "start": "Pradėti", - "start_date": "Pradžios data", - "start_date_before_end_date": "Pradžios data turi būti ankstesnė už pabaigos datą", - "state": "Valstija", - "status": "Statusas", - "stop_casting": "Nutraukti transliavimą", - "stop_motion_photo": "Sustabdyti Judančią Foto", - "stop_photo_sharing": "Nustoti dalytis savo nuotraukomis?", - "stop_photo_sharing_description": "{partner} nebeturės prieigos prie jūsų nuotraukų.", - "stop_sharing_photos_with_user": "Nustoti dalintis savo nuotraukomis su šiuo vartotoju", - "storage": "Saugykla", - "storage_label": "Saugyklos Žyma", - "storage_quota": "Saugyklos Kvota", - "storage_usage": "Naudojama {used} iš {available}", - "submit": "Pateikti", - "success": "Sėkmė", - "suggestions": "Pasiūlymai", - "sunrise_on_the_beach": "Saulėtekis paplūdimyje", - "support": "Pagalba", - "support_and_feedback": "Palaikymas ir atsiliepimai", - "support_third_party_description": "Jūsų Immich paketas yra sukurtas trečios šalies. Problemos, su kuriomis susiduriate, gali būti susijusios su šiuo paketu, todėl pirmiausia praneškite apie problemas jiems, naudodami toliau pateiktas nuorodas.", - "swap_merge_direction": "Keisti sujungimo kryptį", - "sync": "Sinchronizuoti", - "sync_albums": "Sinchronizuoti albumus", - "sync_albums_manual_subtitle": "Sinchronizuoti visus įkeltus vaizdo įrašus ir nuotraukas su pasirinktomis atsarginėmis kopijomis", - "sync_upload_album_setting_subtitle": "Sukurti ir įkelti jūsų nuotraukas ir vaizdo įrašus į pasirinktus Immich albumus", - "tag": "Žyma", - "tag_assets": "Pažymėti", - "tag_created": "Sukurta žyma: {tag}", - "tag_feature_description": "Peržiūrėkite nuotraukas ir vaizdo įrašus sugrupuotus pagal sužymėtas temas", - "tag_not_found_question": "Nerandate žymos? Sukurti naują žymą.", - "tag_people": "Pažymėti Žmones", - "tag_updated": "Atnaujinta žyma: {tag}", - "tagged_assets": "Žyma pridėta prie {count, plural, one {# elemento} other {# elementų}}", - "tags": "Žymos", - "template": "Šablonas", - "text_recognition": "Teksto atpažinimas", - "theme": "Tema", - "theme_selection": "Temos pasirinkimas", - "theme_selection_description": "Automatiškai nustatykite šviesią arba tamsią temą pagal naršyklės sistemos nustatymus", - "theme_setting_asset_list_tiles_per_row_title": "Elementų per eilutę ({count})", - "theme_setting_primary_color_title": "Pagrindinė spalva", - "theme_setting_system_primary_color_title": "Naudoti sistemos spalvą", - "theme_setting_system_theme_switch": "Automatinė (Naudoti sistemos nustatymus)", - "theme_setting_three_stage_loading_subtitle": "Trijų etapų įkėlimas gali padidinti įkėlimo našumą, tačiau sukelia žymiai didesnę tinklo apkrovą", - "time_based_memories": "Atsiminimai pagal laiką", - "timeline": "Laiko skalė", - "timezone": "Laiko juosta", - "to_archive": "Archyvuoti", - "to_change_password": "Pakeisti slaptažodį", - "to_favorite": "Įtraukti prie mėgstamiausių", - "to_login": "Prisijungti", - "to_trash": "Išmesti", - "total": "Viso", - "trash": "Šiukšliadėžė", - "trash_all": "Perkelti visus į šiukšliadėžę", - "trash_count": "Perkelti {count, number} į šiukšliadėžę", - "trash_emptied": "Išvalytos šiukšlės", - "trash_no_results_message": "Į šiukšliadėžę perkeltos nuotraukos ir vaizdo įrašai bus rodomi čia.", - "trash_page_delete_all": "Ištrinti Visus", - "trash_page_empty_trash_dialog_content": "Ar norite ištrinti išmestus elementus? Šie elementai bus visam laikui pašalinti iš Immich", - "trash_page_info": "Šiukšliadėžės elementai bus galutinai ištrinti už {days} dienų", - "trash_page_no_assets": "Nėra išmestų elementų", - "trash_page_restore_all": "Atkurti Visus", - "trash_page_title": "Šiukšlių ({count})", - "trashed_items_will_be_permanently_deleted_after": "Į šiukšliadėžę perkelti elementai bus visam laikui ištrinti po {days, plural, one {# dienos} other {# dienų}}.", - "type": "Tipas", - "unable_to_change_pin_code": "Negalima pakeisti PIN kodo", - "unarchive": "Išarchyvuoti", - "unarchived_count": "{count, plural, other {# išarchyvuota}}", - "unfavorite": "Pašalinti iš mėgstamiausių", - "unhide_person": "Nebeslėpti žmogaus", - "unknown_country": "Nežinoma Šalis", - "unknown_year": "Nežinomi metai", - "unlimited": "Neribota", - "unlink_oauth": "Atsieti OAuth", - "unlinked_oauth_account": "Atsieta OAuth paskyra", - "unnamed_album": "Neįvardytas Albumas", - "unnamed_album_delete_confirmation": "Ar tikrai norite ištrinti šį albumą?", - "unnamed_share": "Neįvardytas Bendrinimas", - "unsaved_change": "Neišsaugoti pakeitimai", - "unselect_all": "Atšaukti visų pasirinkimą", - "unselect_all_duplicates": "Atžymėti visus dublikatus", - "unstack": "Išgrupuoti", - "unstacked_assets_count": "{count, plural, one {Išgrupuotas # elementas} few {Išgrupuoti # elementai} other {Išgrupuota # elementų}}", - "up_next": "Seknatis", - "updated_at": "Atnaujintas", - "updated_password": "Slaptažodis atnaujintas", - "upload": "Įkelti", - "upload_concurrency": "Įkėlimo lygiagretumas", - "upload_dialog_info": "Ar norite sukurti pasirinkto(-ų) turinio(-ų) atsarginę kopiją serveryje?", - "upload_dialog_title": "Įkelti turinį", - "upload_errors": "Įkėlimas įvyko su {count, plural, one {# klaida} few {# klaidomis} other {# klaidų}}, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", - "upload_progress": "Liko {remaining, number} - Apdorota {processed, number}/{total, number}", - "upload_status_duplicates": "Dublikatai", - "upload_status_errors": "Klaidos", - "upload_status_uploaded": "Įkelta", - "upload_success": "Įkėlimas pavyko, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", - "upload_to_immich": "Įkelti į Immich ({count})", - "uploading": "Įkeliama", - "url": "URL", - "usage": "Naudojimas", - "use_biometric": "Naudoti biometriją", - "use_current_connection": "naudoti dabartinį ryšį", - "user": "Naudotojas", - "user_has_been_deleted": "Šis naudotojas buvo ištrintas.", - "user_id": "Naudotojo ID", - "user_liked": "{user} patinka {type, select, photo {ši nuotrauka} video {šis vaizdo įrašas} asset {šis elementas} other {tai}}", - "user_pin_code_settings": "PIN kodas", - "user_pin_code_settings_description": "Tvarkykite savo PIN kodą", - "user_privacy": "Vartotojo Privatumas", - "user_purchase_settings": "Įsigyti", - "user_role_set": "Nustatyti {user}, kaip {role}", - "user_usage_stats": "Paskyros naudojimo statistika", - "user_usage_stats_description": "Žiūrėti paskyros naudojimo statistiką", - "username": "Naudotojo vardas", - "users": "Naudotojai", - "utilities": "Įrankiai", - "validate": "Validuoti", - "validate_endpoint_error": "Prašome įvesti galiojantį URL", - "variables": "Kintamieji", - "version": "Versija", - "version_announcement_closing": "Tavo draugas, Alex", - "version_announcement_message": "Sveiki! Nauja „Immich“ versija yra pasiekiama. Prašome skirti šiek tiek laiko perskaityti leidimo pastabas, kad įsitikintumėte, jog jūsų nustatymai yra atnaujinti. Tai padės išvengti netinkamo sukonfigūravimo, ypač jei naudojate „WatchTower“ ar kitą mechanizmą, kuris automatiškai atnaujina jūsų „Immich“ serverį.", - "version_history": "Versijų istorija", - "version_history_item": "Versija {version} įdiegta {date}", - "video": "Vaizdo įrašas", - "video_hover_setting": "Paleisti vaizdo įrašo miniatiūrą užvedus pele", - "video_hover_setting_description": "Atkurti vaizdo įrašo miniatiūrą, kai pelė užvedama ant elemento. Net ir išjungus, atkūrimą galima pradėti užvedus pelės žymeklį ant atkūrimo piktogramos.", - "videos": "Video", - "videos_count": "{count, plural, one {# vaizdo įrašas} few {# vaizdo įrašai} other {# vaizdo įrašų}}", - "view": "Žiūrėti", - "view_album": "Žiūrėti albumą", - "view_all": "Peržiūrėti viską", - "view_all_users": "Peržiūrėti visus naudotojus", - "view_in_timeline": "Žiūrėti laiko skalėje", - "view_link": "Žiūrėti nuorodą", - "view_links": "Žiūrėti nuorodas", - "view_name": "Žiūrėti", - "view_qr_code": "Žiūrėti QR kodą", - "view_similar_photos": "Žiūrėti panašias foto", - "view_stack": "Peržiūrėti grupę", - "waiting": "Laukiama", - "waiting_count": "Laukiama: {count}", - "warning": "Įspėjimas", - "week": "Savaitė", - "welcome": "Sveiki atvykę", - "welcome_to_immich": "Sveiki atvykę į Immich", - "width": "Plotis", - "wifi_name": "Wi-Fi Pavadinimas", - "workflow_delete_prompt": "Ar tikrai norite ištrinti šią darbų eigą?", - "workflow_deleted": "Darbų eiga ištrinta", - "workflow_description": "Darbų eigos aprašymas", - "workflow_info": "Darbų eigos informacija", - "workflow_json": "Darbų eigos JSON", - "workflow_json_help": "Redaguoti darbų eigos konfigūraciją JSON formatu. Pakeitimai bus sinchronizuoti su vizualiuoju kūrėju.", - "workflow_name": "Darbų eigos pavadinimas", - "workflow_navigation_prompt": "Ar norite išeiti neišsaugoję pakeitimų?", - "workflow_summary": "Darbų eigos santrauka", - "workflow_update_success": "Darbų eiga sėkmingai atnaujinta", - "workflow_updated": "Darbų eiga atnaujinta", - "workflows": "Darbų eigos", - "wrong_pin_code": "Neteisingas PIN kodas", - "year": "Metai", - "years_ago": "Prieš {years, plural, one {# metus} other {# metų}}", - "yes": "Taip", - "you_dont_have_any_shared_links": "Bendrinimo nuorodų neturite", - "your_wifi_name": "Jūsų Wi-Fi pavadinimas", - "zoom_image": "Priartinti vaizdą", - "zoom_to_bounds": "Priartinti iki kraštų" -} +{} diff --git a/i18n/lv.json b/i18n/lv.json index d9d2d52c6b..0967ef424b 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -1,1817 +1 @@ -{ - "about": "Par", - "account": "Konts", - "account_settings": "Konta iestatījumi", - "acknowledge": "Pieņemt", - "action": "Darbība", - "action_common_update": "Atjaunināt", - "action_description": "Darbību kopums, ko veikt ar filtrētajiem failiem", - "actions": "Darbības", - "active": "Aktīvs", - "active_count": "Aktīvi: {count}", - "activity": "Aktivitāte", - "activity_changed": "Aktivitāte ir {enabled, select, true {iespējota} other {atspējota}}", - "add": "Pievienot", - "add_a_description": "Pievienot aprakstu", - "add_a_location": "Pievienot atrašanās vietu", - "add_a_name": "Pievienot vārdu", - "add_a_title": "Pievienot virsrakstu", - "add_action": "Pievienot darbību", - "add_action_description": "Klikšķini, lai pievienotu veicamo darbību", - "add_assets": "Pievienot failus", - "add_birthday": "Pievienot dzimšanas dienu", - "add_endpoint": "Pievienot galapunktu", - "add_exclusion_pattern": "Pievienot izslēgšanas šablonu", - "add_filter": "Pievienot filtru", - "add_filter_description": "Klikšķini, lai pievienotu filtra nosacījumu", - "add_location": "Pievienot lokāciju", - "add_more_users": "Pievienot vēl lietotājus", - "add_partner": "Pievienot partneri", - "add_path": "Pievienot ceļu", - "add_photos": "Pievienot fotoattēlus", - "add_tag": "Pievienot atzīmi", - "add_to": "Pievienot…", - "add_to_album": "Pievienot albumam", - "add_to_album_bottom_sheet_added": "Pievienots {album}", - "add_to_album_bottom_sheet_already_exists": "Jau pievienots {album}", - "add_to_album_bottom_sheet_some_local_assets": "Dažus lokālos failus albumam nevarēja pievienot", - "add_to_album_toggle": "Pārslēgt izvēli {album}", - "add_to_albums": "Pievienot albumiem", - "add_to_albums_count": "Pievienot albumiem ({count})", - "add_to_bottom_bar": "Pievienot", - "add_to_shared_album": "Pievienot koplietotam albumam", - "add_upload_to_stack": "Pievienot augšupielādi kaudzei", - "add_url": "Pievienot URL", - "add_workflow_step": "Pievienot darba plūsmas soli", - "added_to_archive": "Pievienots arhīvam", - "added_to_favorites": "Pievienots izlasei", - "added_to_favorites_count": "{count, number} pievienoti izlasei", - "admin": { - "add_exclusion_pattern_description": "Pievieno izslēgšanas šablonus. Tiek atbalstīta aizstājējzīmju *, **, un ? izmantošana. Lai ignorētu visus failus jebkurā direktorijā ar nosaukumu “RAW”, izmanto “**/RAW/**”. Lai ignorētu visus failus, kas beidzas ar “. tif”, izmanto “**/*. tif”. Lai ignorētu absolūto ceļu, izmanto “/kāds/ignorējamais/ceļš/**”.", - "admin_user": "Administrators", - "asset_offline_description": "Šis ārējās bibliotēkas resurss vairs nav atrodams diskā un ir pārvietots uz atkritni. Ja fails tika pārvietots bibliotēkas ietvaros, pārbaudiet, vai jūsu laika skalā ir jauns atbilstošais resurss. Lai atjaunotu šo resursu, pārliecinieties, vai Immich var piekļūt tālāk norādītajam faila ceļam un uzsāc bibliotēkas skenēšanu.", - "authentication_settings": "Autentifikācijas iestatījumi", - "authentication_settings_description": "Paroļu, OAuth un citu autentifikācijas iestatījumu pārvaldība", - "authentication_settings_disable_all": "Vai tiešām vēlaties atspējot visas pieteikšanās metodes? Pieteikšanās tiks pilnībā atspējota.", - "authentication_settings_reenable": "Lai atkārtoti iespējotu, izmantojiet Servera Komandu.", - "background_task_job": "Fona uzdevumi", - "backup_database": "Izveidot datu bāzes izrakstu", - "backup_database_enable_description": "Iespējot datu bāzes izrakstus", - "backup_keep_last_amount": "Iepriekšējo izrakstu daudzums, kas jāsaglabā", - "backup_onboarding_1_description": "ārēja kopija mākonī vai citā fiziskā atrašanās vietā.", - "backup_onboarding_2_description": "lokālās kopijas citās ierīcēs. Tas ietver galvenos failus un šo failu lokālo rezerves kopiju.", - "backup_onboarding_3_description": "kopiju skaits, ieskaitot oriģinālos failus. Tas ietver 1 ārējo kopiju un 2 lokālās kopijas.", - "backup_onboarding_description": "Lai aizsargātu savus datus, ieteicams izmantot 3-2-1 rezerves kopiju stratēģiju. Lai nodrošinātu visaptverošu dublēšanas risinājumu, vajadzētu veidot kopijas saviem augšupielādētajiem fotoattēliem/videoklipiem, kā arī Immich datubāzei.", - "backup_onboarding_footer": "Lai iegūtu vairāk informācijas par Immich rezerves kopiju veidošanu, lūdzu, apskatiet dokumentāciju.", - "backup_onboarding_parts_title": "3-2-1 rezerves kopija ietver:", - "backup_onboarding_title": "Rezerves kopijas", - "backup_settings": "Datubāzes izrakstu iestatījumi", - "backup_settings_description": "Datubāzes izrakstu iestatījumu pārvaldība", - "cleared_jobs": "Notīrīti uzdevumi priekš: {job}", - "config_set_by_file": "Konfigurāciju pašlaik iestata konfigurācijas fails", - "confirm_delete_library": "Vai tiešām vēlaties dzēst {library} bibliotēku?", - "confirm_delete_library_assets": "Vai tiešām vēlaties dzēst šo bibliotēku? Tas izdzēsīs {count, plural, one {# contained asset} other {all # contained assets}} no Immich un to nevar atsaukt. Faili paliks diskā.", - "confirm_email_below": "Lai apstiprinātu, zemāk ierakstiet “{email}”", - "confirm_reprocess_all_faces": "Vai tiešām vēlies atkārtoti apstrādāt visas sejas? Tas arī atiestatīs personas ar vārdiem.", - "confirm_user_password_reset": "Vai tiešām vēlaties atiestatīt lietotāja {user} paroli?", - "confirm_user_pin_code_reset": "Vai tiešām vēlaties atiestatīt {user} PIN kodu?", - "copy_config_to_clipboard_description": "Kopēt pašreizējo sistēmas konfigurāciju kā JSON objektu starpliktuvē", - "create_job": "Izveidot uzdevumu", - "cron_expression": "Cron izteiksme", - "cron_expression_description": "Iestatiet skenēšanas intervālu, izmantojot cron formātu. Papildu informācijai skatiet, piemēram, Crontab Guru", - "cron_expression_presets": "Cron izteiksmju sagataves", - "disable_login": "Atspējot pieteikšanos", - "duplicate_detection_job_description": "Analizēt failus ar mašīnmācīšanos, lai noteiktu līdzīgus attēlus. Šī funkcija izmanto viedo meklēšanu", - "exclusion_pattern_description": "Izslēgšanas šabloni ļauj ignorēt failus un mapes, skenējot bibliotēku. Tas ir noderīgi, ja jums ir mapes, kas satur failus, kurus nevēlaties importēt, piemēram, RAW failus.", - "export_config_as_json_description": "Lejupielādēt pašreizējo sistēmas konfigurāciju kā JSON failu", - "face_detection": "Seju noteikšana", - "face_detection_description": "Atpazīt attēlos sejas, izmantojot mašīnmācīšanos. Video gadījumā tiek ņemta vērā tikai sīktēls. \"Atsvaidzināt\" atkārtoti apstrādā visus attēlus. \"Atiestatīt\" izdzēš visus pašreizējos seju datus. \"Trūkstošie\" ierindo attēlus, kas vēl nav apstrādāti. Pēc seju noteikšanas pabeigšanas atrastās sejas tiek ierindotas seju atpazīšanai, grupējot tās pēc esošas vai jauns personas.", - "facial_recognition_job_description": "Grupēt atpazītās sejas pēc cilvēkiem. Šis solis tiek veikts pēc seju noteikšanas pabeigšanas. \"Atiestatīt\" atkārtoti sagrupē visas sejas. \"Trūkstošie\" ierindo sejas, kurām nav piešķirta persona.", - "failed_job_command": "Kļūda, izpildot {job} komandu {command}", - "force_delete_user_warning": "BRĪDINĀJUMS: Tas uzreiz izdzēsīs lietotāju ar visiem failiem. Šo darbību nevar atcelt, un failus nevarēs atgūt.", - "image_format": "Formāts", - "image_format_description": "WebP veido mazākus failus nekā JPEG, taču to kodēšana ir lēnāka.", - "image_fullsize_description": "Pilnizmēra attēls ar noņemtiem metadatiem, ko izmanto, kad attēls ir tuvināts", - "image_fullsize_enabled": "Iespējot pilnizmēra attēlu ģenerēšanu", - "image_fullsize_enabled_description": "Ģenerēt pilnizmēra attēlu formātiem, kas nav piemēroti izmantošanai tīmeklī. Ja ir iespējota opcija \"Priekšroka iegultajam priekšskatījumam\", tiks izmantoti iegultie priekšskatījumi bez konvertēšanas. Neietekmē tīmeklim draudzīgus formātus, piemēram, JPEG.", - "image_fullsize_quality_description": "Pilnizmēra attēlu kvalitāte no 1-100. Augstāka vērtība dos labāku kvalitāti, taču faili būs lielāka izmēra.", - "image_fullsize_title": "Pilnizmēra attēlu iestatījumi", - "image_prefer_embedded_preview": "Priekšroka iegultajam priekšskatījumam", - "image_prefer_embedded_preview_setting_description": "Izmanto RAW fotoattēlos iestrādātos priekšskatījumus, ja tādi ir pieejami, kā ievades datus attēlu apstrādei. Tādējādi dažiem attēliem var iegūt precīzākas krāsas, taču priekšskatījuma kvalitāte ir atkarīga no fotokameras un attēlam var būt vairāk saspiešanas artefaktu.", - "image_prefer_wide_gamut": "Dot priekšroku plašai krāsu gammai", - "image_prefer_wide_gamut_setting_description": "Sīktēliem izmanto Display P3. Tas labāk saglabā attēlu dzīvīgumu ar plašu krāsu gammu, bet attēli var izskatīties atšķirīgi vecās ierīcēs ar vecu pārlūka versiju. sRGB attēli tiek saglabāti kā sRGB, lai izvairītos no krāsu izmaiņām.", - "image_preview_description": "Vidēja izmēra attēls ar noņemtiem metadatiem, ko izmanto, skatot vienu failu un mašīnmācīšanās apmācībai", - "image_preview_quality_description": "Priekšskatījuma kvalitāte no 1 līdz 100. Augstāka kvalitāte ir labāka, bet veido lielākus failus un var samazināt lietotnes reaģēšanas ātrumu. Zemas vērtības iestatīšana var ietekmēt mašīnmācīšanās kvalitāti.", - "image_preview_title": "Priekšskatījuma iestatījumi", - "image_progressive": "Progresīvi", - "image_progressive_description": "JPEG attēlus iekodēt progresīvi, lai tie ielādētos pakāpeniski. Tas neietekmē WebP attēlus.", - "image_quality": "Kvalitāte", - "image_resolution": "Izšķirtspēja", - "image_resolution_description": "Augstāka izšķirtspēja ļauj saglabāt vairāk detaļu, taču kodēšana aizņem vairāk laika, failu izmērs ir lielāks un var samazināties lietotnes reaģēšanas ātrums.", - "image_settings": "Attēlu iestatījumi", - "image_settings_description": "Ģenerēto attēlu kvalitātes un izšķirtspējas pārvaldība", - "image_thumbnail_description": "Neliels sīktēls bez metadatiem, ko izmanto, lai apskatītu vairākus fotoattēlus, piemēram, galvenajā laika skalā", - "image_thumbnail_quality_description": "Sīktēlu kvalitāte no 1 līdz 100. Augstāka kvalitāte ir labāka, bet veido lielākus failus un var samazināt lietotnes reaģēšanas ātrumu.", - "image_thumbnail_title": "Sīktēlu iestatījumi", - "import_config_from_json_description": "Importēt sistēmas konfigurāciju, augšupielādējot JSON konfigurācijas failu", - "job_concurrency": "{job} vienlaicīgi", - "job_created": "Uzdevums izveidots", - "job_not_concurrency_safe": "Šis uzdevums nav drošs vienlaicīgai izpildei.", - "job_settings": "Uzdevumu iestatījumi", - "job_settings_description": "Uzdevumu izpildes vienlaicīguma pārvaldība", - "jobs_over_time": "Uzdevumi laika gaitā", - "library_created": "Izveidoja bibliotēku: {library}", - "library_deleted": "Bibliotēka dzēsta", - "library_details": "Bibliotēkas dati", - "library_folder_description": "Norādi importējamo mapi. Lai meklētu attēlus un videoklipus, tiks pārbaudīta šī mape un tās apakšmapes.", - "library_scanning": "Periodiska skenēšana", - "library_scanning_description": "Konfigurē periodisku bibliotēku skenēšanu", - "library_scanning_enable_description": "Iespējot periodisku bibliotēku skenēšanu", - "library_settings": "Ārējās bibliotēkas", - "library_settings_description": "Ārējo bibliotēku iestatījumu pārvaldība", - "library_tasks_description": "Pārbaudīt ārējās bibliotēkas, lai atrastu jaunus un/vai mainītus failus", - "library_watching_enable_description": "Uzraudzīt ārējo bibliotēku failu izmaiņas", - "library_watching_settings": "Bibliotēku uzraudzīšana [EKSPERIMENTĀLA]", - "library_watching_settings_description": "Automātiski uzraudzīt, vai ir mainīti faili", - "logging_level_description": "Ja iespējots, kādu žurnāla līmeni izmantot.", - "logging_settings": "Žurnalēšana", - "machine_learning_availability_checks": "Pieejamības pārbaudes", - "machine_learning_availability_checks_description": "Automātiski atklāt un dod priekšroku pieejamajiem mašīnmācīšanās serveriem", - "machine_learning_availability_checks_enabled": "Iespējot pieejamības pārbaudes", - "machine_learning_availability_checks_interval": "Pārbaudes intevāls", - "machine_learning_availability_checks_interval_description": "Intervāls milisekundēs starp pieejamības pārbaudēm", - "machine_learning_availability_checks_timeout": "Pieprasījumu noildze", - "machine_learning_availability_checks_timeout_description": "Pieejamības pārbaužu noildze milisekundēs", - "machine_learning_clip_model": "CLIP modelis", - "machine_learning_clip_model_description": "Sarakstā norādītais CLIP modeļa nosaukums. Ņem vērā, ka, mainot modeli, visiem attēliem ir vēlreiz jāpalaiž \"Viedās meklēšanas\" uzdevums.", - "machine_learning_duplicate_detection": "Dublikātu noteikšana", - "machine_learning_duplicate_detection_enabled": "Iespējot dublikātu noteikšanu", - "machine_learning_duplicate_detection_enabled_description": "Ja šī funkcija ir atspējota, joprojām tiks izlaisti identiski faili.", - "machine_learning_duplicate_detection_setting_description": "Izmantot CLIP iegultos elementus, lai atrastu iespējamos dublikātus", - "machine_learning_enabled": "Iespējot mašīnmācīšanos", - "machine_learning_enabled_description": "Ja funkcija ir atspējota, tiks atspējotas visas ML funkcijas neatkarīgi no zemāk esošajiem iestatījumiem.", - "machine_learning_facial_recognition": "Seju atpazīšana", - "machine_learning_facial_recognition_description": "Noteikt, atpazīt un sagrupēt sejas attēlos", - "machine_learning_facial_recognition_model": "Seju atpazīšanas modelis", - "machine_learning_facial_recognition_model_description": "Modeļi ir uzskaitīti pēc to izmēra dilstošā secībā. Lielāki modeļi ir lēnāki un izmanto vairāk atmiņas, bet nodrošina labākus rezultātus. Ņem vērā, ka, mainot modeli, ir atkārtoti jāpalaiž sejas atpazīšanas uzdevums visiem attēliem.", - "machine_learning_facial_recognition_setting": "Iespējot seju atpazīšanu", - "machine_learning_facial_recognition_setting_description": "Ja šī funkcija ir atspējota, attēli netiks kodēti sejas atpazīšanai un netiks parādīti sadaļā “Personas” lapā “Izpētīt”.", - "machine_learning_max_detection_distance": "Maksimālā noteikšanas distance", - "machine_learning_max_detection_distance_description": "Maksimālā distance starp diviem attēliem, lai tos uzskatītu par dublikātiem, ir no 0,001 līdz 0,1. Lielākas vērtības atklās vairāk dublikātu, taču var izraisīt kļūdaini pozitīvus rezultātus.", - "machine_learning_max_recognition_distance": "Maksimālā atpazīšanas distance", - "machine_learning_max_recognition_distance_description": "Maksimālā distance starp divām sejām, lai tās tiktu uzskatītas par vienu un to pašu personu, ir no 0 līdz 2. Samazinot šo distanci, var novērst divu cilvēku apzīmēšanu kā vienu un to pašu personu, savukārt palielinot to, var novērst vienas un tās pašas personas apzīmēšanu.", - "machine_learning_min_detection_score": "Minimālais atpazīšanas rezultāts", - "machine_learning_min_detection_score_description": "Minimālais sejas noteikšanas ticamības rādītājs no 0 līdz 1. Zemākas vērtības atklās vairāk seju, taču var rasties kļūdaini pozitīvi rezultāti.", - "machine_learning_min_recognized_faces": "Minimālais atpazīto seju skaits", - "machine_learning_min_recognized_faces_description": "Minimālais atpazīto seju skaits, kas nepieciešams, lai izveidotu personu. Palielinot šo skaitu, sejas atpazīšana kļūst precīzāka, taču palielinās iespēja, ka seja netiks piešķirta personai.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Izmantot mašīnmācīšanos, lai atpazītu tekstu attēlos", - "machine_learning_ocr_enabled": "Aktivizēt OCR", - "machine_learning_ocr_enabled_description": "Ja šī opcija ir atspējota, attēli netiks pakļauti teksta atpazīšanai.", - "machine_learning_ocr_max_resolution": "Maksimālā izšķirtspēja", - "machine_learning_ocr_max_resolution_description": "Priekšskatījumi, kuru izšķirtspēja ir lielāka par šo, tiks mainīti, saglabājot malu attiecību. Augstākas vērtības ir precīzākas, taču apstrāde aizņem ilgāku laiku un izmanto vairāk atmiņas.", - "machine_learning_ocr_min_detection_score": "Minimālais atpazīšanas rezultāts", - "machine_learning_ocr_min_detection_score_description": "Minimālais teksta noteikšanas ticamības rādītājs no 0 līdz 1. Zemākas vērtības noteiks vairāk teksta, taču var izraisīt kļūdaini pozitīvus rezultātus.", - "machine_learning_ocr_min_recognition_score": "Minimālais atpazīšanas rezultāts", - "machine_learning_ocr_model": "OCR modelis", - "machine_learning_ocr_model_description": "Serveru modeļi ir precīzāki nekā mobilie modeļi, bet apstrāde aizņem vairāk laika un tie izmanto vairāk atmiņas.", - "machine_learning_settings": "Mašīnmācīšanās iestatījumi", - "machine_learning_settings_description": "Mašīnmācīšanās funkciju un iestatījumu pārvaldība", - "machine_learning_smart_search": "Viedā meklēšana", - "machine_learning_smart_search_description": "Meklēt attēlus semantiski, izmantojot CLIP iegultos elementus", - "machine_learning_smart_search_enabled": "Iespējot viedo meklēšanu", - "machine_learning_smart_search_enabled_description": "Ja funkcija ir atspējota, attēli netiks kodēti viedai meklēšanai.", - "machine_learning_url_description": "Mašīnmācīšanās servera URL. Ja ir norādīts vairāk nekā viens URL, katrs serveris, sākot no pirmā līdz pēdējam, tiks pārbaudīts pa vienam, līdz kāds no tiem atbildēs veiksmīgi. Serveri, kas neatbild, tiks īslaicīgi ignorēti, līdz tie atkal būs pieejami tiešsaistē.", - "maintenance_delete_backup": "Dzēst rezerves kopiju", - "maintenance_delete_backup_description": "Šis fails tiks neatgriezeniski dzēsts.", - "maintenance_delete_error": "Neizdevās dzēst rezerves kopiju.", - "maintenance_restore_backup_different_version": "Šī rezerves kopija tika izveidota ar citu Immich versiju!", - "maintenance_restore_backup_unknown_version": "Nevarēja noteikt rezerves kopijas versiju.", - "maintenance_restore_database_backup_description": "Atgrizties pie iepriekšējā datubāzes stāvokļa, izmantojot rezerves kopijas failu", - "maintenance_settings": "Apkope", - "maintenance_settings_description": "Pārslēgt Immich apkopes režīmā.", - "maintenance_start": "Sākt apkopes režīmu", - "maintenance_start_error": "Neizdevās uzsākt apkopes režīmu.", - "maintenance_upload_backup": "Augšupielādēt datubāzes rezerves kopijas failu", - "maintenance_upload_backup_error": "Nevarēja augšupielādēt rezerves kopiju, vai tas ir .sql/.sql.gz fails?", - "manage_concurrency": "Vienlaicīgas darbības pārvaldība", - "manage_concurrency_description": "Pāriet uz uzdevumu lapu, lai pārvaldītu uzdevumu vienlaicīgu darbību", - "manage_log_settings": "Žurnāla iestatījumu pārvaldība", - "map_dark_style": "Tumšais stils", - "map_enable_description": "Iespējot kartes funkcijas", - "map_gps_settings": "Kartes un GPS iestatījumi", - "map_gps_settings_description": "Karšu un GPS (apgrieztās ģeokodēšanas) iestatījumu pārvaldība", - "map_implications": "Kartes funkcija izmanto ārējo kartes fragmentu pakalpojumu (tiles.immich.cloud)", - "map_light_style": "Gaišais stils", - "map_manage_reverse_geocoding_settings": "Reversās ģeokodēšanas iestatījumu pārvaldība", - "map_reverse_geocoding": "Reversā ģeokodēšana", - "map_reverse_geocoding_enable_description": "Iespējot apgriezto ģeokodēšanu", - "map_reverse_geocoding_settings": "Reversās ģeokodēšanas iestatījumi", - "map_settings": "Karte", - "map_settings_description": "Kartes iestatījumu pārvaldība", - "map_style_description": "URL uz style.json kartes tēmu", - "memory_cleanup_job": "Atmiņu tīrīšana", - "memory_generate_job": "Atmiņu ģenerēšana", - "metadata_extraction_job": "Metadatu iegūšana", - "metadata_extraction_job_description": "Iegūt metadatu informāciju no katra faila, piemēram, GPS, sejas un izšķirtspēju", - "metadata_faces_import_setting": "Iespējot seju importēšanu", - "metadata_faces_import_setting_description": "Importēt sejas no attēla EXIF datiem un blakusfailiem", - "metadata_settings": "Metadatu iestatījumi", - "metadata_settings_description": "Metadatu iestatījumu pārvaldība", - "migration_job": "Migrācija", - "migration_job_description": "Pārvietot failu un seju sīktēlus uz jaunāko mapju struktūru", - "nightly_tasks_cluster_faces_setting_description": "Veikt sejas atpazīšanu jaunatklātajām sejām", - "nightly_tasks_cluster_new_faces_setting": "Sagrupēt jaunās sejas", - "nightly_tasks_database_cleanup_setting": "Datubāzes apkopes uzdevumi", - "nightly_tasks_database_cleanup_setting_description": "Dzēst vecus, neaktuālus datus no datubāzes", - "nightly_tasks_generate_memories_setting": "Veidot atmiņas", - "nightly_tasks_generate_memories_setting_description": "Veidot jaunas atmiņas no failiem", - "nightly_tasks_missing_thumbnails_setting": "Ģenerēt trūkstošos sīktēlus", - "nightly_tasks_missing_thumbnails_setting_description": "Ierindot failus bez sīktēliem sīktēlu ģenerēšanai", - "nightly_tasks_settings": "Iknakts uzdevumu iestatījumi", - "nightly_tasks_settings_description": "Pārvaldīt iknakts uzdevumus", - "nightly_tasks_start_time_setting": "Sākuma laiks", - "nightly_tasks_start_time_setting_description": "Laiks, kad serveris sāk izpildīt iknakts uzdevumus", - "nightly_tasks_sync_quota_usage_setting": "Sinhronizēt kvotas izmantošanu", - "nightly_tasks_sync_quota_usage_setting_description": "Pārrēķināt lietotāja uzglabāšanas kvotu, pamatojoties uz pašreizējo izmantošanu", - "no_paths_added": "Nav pievienots neviens ceļš", - "no_pattern_added": "Nav pievienots neviens izslēgšanas šablons", - "note_apply_storage_label_previous_assets": "Piezīme: Lai piemērotu glabātuves nosaukumu iepriekš augšupielādētiem failiem, izpildiet", - "note_cannot_be_changed_later": "PIEZĪME: Vēlāk to vairs nevar mainīt!", - "notification_email_from_address": "No adreses", - "notification_email_from_address_description": "Sūtītāja e-pasta adrese, piemēram: “Immich foto serveris ”. Pārliecinies, ka izmanto adresi, no kuras tev atļauts sūtīt e-pastus.", - "notification_email_host_description": "E-pasta servera nosaukums (piemēram, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorēt sertifikātu kļūdas", - "notification_email_ignore_certificate_errors_description": "Ignorēt TLS sertifikāta apstiprināšanas kļūdas (nav ieteicams)", - "notification_email_password_description": "Parole, kas jāizmanto, autentificējoties ar e-pasta serveri", - "notification_email_port_description": "e-pasta servera ports (piemēram, 25, 465 vai 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Izmantot SMTPS (SMTP caur TLS)", - "notification_email_sent_test_email_button": "Nosūtīt testa e-pastu un saglabāt", - "notification_email_setting_description": "E-pasta paziņojumu sūtīšanas iestatījumi", - "notification_email_test_email": "Nosūtīt testa e-pastu", - "notification_email_test_email_failed": "Neizdevās nosūtīt pārbaudes e-pastu, pārbaudi ievadītās vērtības", - "notification_email_test_email_sent": "Uz {email} ir nosūtīts testa e-pasts. Lūdzu, pārbaudi savu iesūtni.", - "notification_email_username_description": "Lietotājvārds, kas jāizmanto, autentificējoties ar e-pasta serveri", - "notification_enable_email_notifications": "Iespējot e-pasta paziņojumus", - "notification_settings": "Paziņojumu iestatījumi", - "notification_settings_description": "Paziņojumu iestatījumu, tostarp e-pasta, pārvaldība", - "oauth_auto_launch": "Palaist automātiski", - "oauth_auto_launch_description": "Pie navigācijas uz pieslēgšanās lapu automātiski uzsākt OAuth pieslēgšanās plūsmu", - "oauth_auto_register": "Automātiska reģistrācija", - "oauth_auto_register_description": "Pēc pieslēgšanās ar OAuth automātiski reģistrēt jaunus lietotājus", - "oauth_button_text": "Pogas teksts", - "oauth_client_secret_description": "Nepieciešams, ja OAuth pakalpojuma sniedzējs neatbalsta PKCE (Proof Key for Code Exchange)", - "oauth_enable_description": "Pieslēgties ar OAuth", - "oauth_mobile_redirect_uri": "Mobilās pāradresēšanas URI", - "oauth_mobile_redirect_uri_override": "Mobilās pāradresēšanas URI pārrakstīšana", - "oauth_mobile_redirect_uri_override_description": "Jāiespējo, ja OAuth pakalpojuma sniedzējs nepieļauj mobilo URI, piemēram, \"{callback}\"", - "oauth_role_claim": "Lomas pieteikums", - "oauth_role_claim_description": "Automātiski piešķirt administratora piekļuvi, pamatojoties uz šīs prasības klātbūtni. Prasība var būt vai nu \"user\", vai \"admin\".", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth pieteikšanās iestatījumu pārvaldība", - "oauth_settings_more_details": "Plašāku informāciju par šo funkcionalitāti skatīt dokumentācijā.", - "oauth_storage_label_claim": "Glabātuves nosaukuma pieteikums", - "oauth_storage_label_claim_description": "Automātiski iestatīt lietotāja glabātuves nosaukumu uz šī pieteikuma vērtību.", - "oauth_storage_quota_default": "Noklusējuma krātuves kvota (GiB)", - "oauth_timeout": "Pieprasījuma noildze", - "oauth_timeout_description": "Pieprasījumu laika limits milisekundēs", - "ocr_job_description": "Izmantot mašīnmācīšanos, lai atpazītu tekstu attēlos", - "password_enable_description": "Pieteikšanās ar e-pasta adresi un paroli", - "password_settings": "Pieteikšanās ar paroli", - "password_settings_description": "Pieteikšanās ar paroli iestatījumu pārvaldība", - "paths_validated_successfully": "Visi ceļi veiksmīgi pārbaudīti", - "person_cleanup_job": "Personu tīrīšana", - "queue_details": "Vaicājuma dati", - "queues": "Uzdevumu rindas", - "quota_size_gib": "Kvotas izmērs (GiB)", - "refreshing_all_libraries": "Atsvaidzina visas bibliotēkas", - "registration": "Administratora reģistrācija", - "registration_description": "Tā kā tu esi pirmais sistēmas lietotājs, tev tiks piešķirts administratora statuss un tu būsi atbildīgs par administrēšanas uzdevumiem, kā arī par citu lietotāju izveidi.", - "require_password_change_on_login": "Pieprasīt lietotājam mainīt paroli pēc pirmās pieteikšanās", - "reset_settings_to_default": "Atjaunot iestatījumus uz noklusējuma vērtībām", - "reset_settings_to_recent_saved": "Atjaunot iestatījumus uz pēdējiem saglabātajiem iestatījumiem", - "scanning_library": "Skenē bibliotēku", - "search_jobs": "Meklēt uzdevumus…", - "send_welcome_email": "Nosūtīt sveiciena e-pastu", - "server_external_domain_settings": "Ārējais domēns", - "server_external_domain_settings_description": "Domēns publiski kopīgotajām saitēm, iekļaujot http(s)://", - "server_public_users": "Publiski lietotāji", - "server_settings": "Servera iestatījumi", - "server_settings_description": "Servera iestatījumu pārvaldība", - "server_welcome_message": "Sveiciena ziņa", - "server_welcome_message_description": "Ziņojums, kas tiek parādīts pieslēgšanās lapā.", - "sidecar_job": "Blakusfailu metadati", - "sidecar_job_description": "Atklāt vai sinhronizēt blakusfailu metadatus no failu sistēmas", - "slideshow_duration_description": "Katra attēla rādīšanas ilgums sekundēs", - "smart_search_job_description": "Analizēt failus ar mašīnmācīšanos lai sagatavotu datus viedajai meklēšanai", - "storage_template_date_time_sample": "Laika paraugs {date}", - "storage_template_hash_verification_enabled": "Jaucējvērtību pārbaude ir iespējota", - "storage_template_hash_verification_enabled_description": "Iespējo jaucējvērtību pārbaudi, neatslēdz to, ja neapzinies sekas", - "storage_template_migration": "Krātuves veidņu migrācija", - "storage_template_migration_description": "Piemēro pašreizējo {template} iepriekš augšupielādētajiem failiem", - "storage_template_migration_info": "Krātuves veidne pārveidos visus failu paplašinājumus uz mazajiem burtiem. Veidnes izmaiņas attieksies tikai uz jauniem failiem. Lai veidni piemērotu ar atpakaļejošu efektu iepriekš augšupielādētiem failiem, palaidiet {job}.", - "storage_template_migration_job": "Krātuves veidņu migrācijas uzdevumu", - "storage_template_more_details": "Plašāku informāciju par šo funkcionalitāti skatīt sadaļā Krātuves veidne un tās sekas", - "storage_template_path_length": "Aptuvenais ceļa garuma ierobežojums: {length, number}/{limit, number}", - "storage_template_settings": "Krātuves veidne", - "storage_template_settings_description": "Pārvaldīt augšupielādēto failu mapju struktūru un faila nosaukumu", - "storage_template_user_label": "Lietotāja krātuves nosaukums ir {label}", - "system_settings": "Sistēmas iestatījumi", - "template_email_available_tags": "Sagatavē var izmantot šos mainīgos: {tags}", - "template_email_if_empty": "Ja sagatave ir tukša, tiks izmantots noklusējuma e-pasts.", - "template_email_invite_album": "Albuma ielūguma sagatave", - "template_email_preview": "Priekšskatījums", - "template_email_settings": "E-pasta sagataves", - "template_email_update_album": "Atjaunināt albuma sagatavi", - "template_settings_description": "Pielāgotu paziņojumu veidņu pārvaldība", - "theme_custom_css_settings": "Pielāgots CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets ļauj pielāgot Immich izskatu.", - "theme_settings_description": "Immich tīmekļa saskarnes pielāgojumu pārvaldība", - "thumbnail_generation_job": "Sīktēlu ģenerēšana", - "thumbnail_generation_job_description": "Izveidot lielu, mazu un izplūdušu sīktēlu katram failam, kā arī sīktēlu katrai personai", - "transcoding_acceleration_api": "Paātrināšanas API", - "transcoding_acceleration_nvenc": "NVENC (nepieciešams NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (nepieciešams 7. paaudzes vai jaunāks Intel procesors)", - "transcoding_acceleration_rkmpp": "RKMPP (tikai Rockchip SOC)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_video_codecs": "Akceptētie video kodeki", - "transcoding_accepted_video_codecs_description": "Izvēlies, kurus video kodekus nav nepieciešams transkodēt. Tiek izmantots tikai noteiktām transkodēšanas politikām.", - "transcoding_advanced_options_description": "Lielākajai daļai lietotāju nevajadzētu mainīt šīs opcijas", - "transcoding_audio_codec": "Audio kodeks", - "transcoding_audio_codec_description": "Opus ir augstākās kvalitātes izvēle, bet tā ir mazāk saderīga ar vecām ierīcēm vai programmatūru.", - "transcoding_codecs_learn_more": "Lai uzzinātu vairāk par šeit lietoto terminoloģiju, skatiet FFmpeg dokumentāciju par H.264 kodeku, HEVC kodeku un VP9 kodeku.", - "transcoding_constant_quality_mode": "Nemainīgas kvalitātes režīms", - "transcoding_constant_quality_mode_description": "ICQ ir labāks nekā CQP, bet dažas aparatūras paātrinājuma ierīces neatbalsta šo režīmu. Iestatot šo opciju, tiks izmantots norādītais režīms, ja tiek izmantota kvalitātē balstīta kodēšana. NVENC to ignorē, jo neatbalsta ICQ.", - "transcoding_constant_rate_factor_description": "Video kvalitātes līmenis. Tipiskās vērtības ir 23 priekš H.264, 28 priekš HEVC, 31 priekš VP9 un 35 priekš AV1. Zemāka vērtība ir labāka, bet rada lielākus failus.", - "transcoding_hardware_acceleration": "Aparatūras paātrinājums", - "transcoding_required_description": "Tikai video, kas nav atbalstītā formātā", - "transcoding_settings": "Video transkodēšanas iestatījumi", - "transcoding_threads": "Pavedieni", - "transcoding_threads_description": "Augstākas vērtības nodrošina ātrāku kodēšanu, bet atstāj mazāk jaudas serverim, lai apstrādātu citus aktīvos uzdevumus. Šai vērtībai nevajadzētu pārsniegt CPU kodolu skaitu. Ja iestatīta uz 0, maksimizē izmantošanu.", - "transcoding_video_codec": "Video kodeks", - "trash_number_of_days": "Dienu skaits", - "trash_settings": "Atkritnes iestatījumi", - "trash_settings_description": "Atkritnes iestatījumu pārvaldība", - "user_delete_delay_settings": "Dzēšanas aizture", - "user_delete_delay_settings_description": "Dienu skaits pēc izdzēšanas, kad neatgriezeniski tiks dzēsti lietotāja konti un faili. Lietotāju dzēšanas uzdevums tiek izpildīts pusnaktī un pārbauda, kuri lietotāji ir gatavi dzēšanai. Izmaiņas šajā iestatījumā tiks ņemtas vērā nākamajā izpildes reizē.", - "user_delete_immediately_checkbox": "Ierindot lietotāju un failus tūlītējai dzēšanai", - "user_details": "Lietotāja informācija", - "user_management": "Lietotāju pārvaldība", - "user_password_has_been_reset": "Lietotāja parole ir atiestatīta:", - "user_password_reset_description": "Lūdzu, norādi lietotājam pagaidu paroli un informē viņu, ka nākamajā pieslēgšanās reizē viņam būs jāmaina parole.", - "user_restore_description": "{user} konts tiks atjaunots.", - "user_restore_scheduled_removal": "Atjaunot lietotāju - plānotā dzēšana {date, date, long}", - "user_settings": "Lietotāja iestatījumi", - "user_settings_description": "Lietotāju iestatījumu pārvaldība", - "version_check_enabled_description": "Ieslēgt versijas pārbaudi", - "version_check_implications": "Versiju pārbaudes funkcija ir atkarīga no periodiskas saziņas ar github.com", - "version_check_settings": "Versijas pārbaude", - "version_check_settings_description": "Ieslēgt/izslēgt paziņojumus par jaunu versiju" - }, - "admin_email": "Administratora e-pasts", - "admin_password": "Administratora parole", - "administration": "Administrēšana", - "advanced": "Papildu", - "advanced_settings_clear_image_cache": "Notīrīt attēlu kešatmiņu", - "advanced_settings_clear_image_cache_error": "Neizdevās notīrīt attēlu kešatmiņu", - "advanced_settings_clear_image_cache_success": "Veiksmīgi notīrīti {size}", - "advanced_settings_log_level_title": "Žurnalēšanas līmenis: {level}", - "advanced_settings_prefer_remote_subtitle": "Dažās ierīcēs sīktēli no ierīces atmiņas ielādējas ļoti lēni. Aktivizējiet šo iestatījumu, lai tā vietā ielādētu attālus attēlus.", - "advanced_settings_prefer_remote_title": "Dot priekšroku attāliem attēliem", - "advanced_settings_proxy_headers_title": "Pielāgotas starpniekservera galvenes [EKSPERIMENTĀLAS]", - "advanced_settings_self_signed_ssl_subtitle": "Izlaiž servera galapunkta SSL sertifikātu verifikāciju. Nepieciešams pašparakstītajiem sertifikātiem.", - "advanced_settings_self_signed_ssl_title": "Atļaut pašparakstītus SSL sertifikātus [EKSPERIMENTĀLI]", - "advanced_settings_tile_subtitle": "Lietotāja papildu iestatījumi", - "advanced_settings_troubleshooting_subtitle": "Iespējot papildu aktīvus problēmu novēršanai", - "advanced_settings_troubleshooting_title": "Problēmas novēršana", - "age_months": "Vecums {months, plural, zero {# mēnešu} one {# mēnesis} other {# mēneši}}", - "age_year_months": "Vecums 1 gads, {months, plural, zero {# mēnešu} one {# mēnesis} other {# mēneši}}", - "age_years": "{years, plural, zero {# gadu} one {# gads} other {# gadi}}", - "album": "Albums", - "album_added": "Albums pievienots", - "album_added_notification_setting_description": "Saņemt e-pasta paziņojumu, kad tevi pievieno kopīgam albumam", - "album_cover_updated": "Albuma vāciņš atjaunināts", - "album_delete_confirmation_description": "Ja šis albums tiek kopīgots, citi lietotāji vairs nevarēs tam piekļūt.", - "album_deleted": "Albums dzēsts", - "album_info_card_backup_album_excluded": "NEIEKĻAUTS", - "album_info_card_backup_album_included": "IEKĻAUTS", - "album_info_updated": "Albuma informācija atjaunināta", - "album_leave": "Pamest albumu?", - "album_name": "Albuma nosaukums", - "album_remove_user": "Noņemt lietotāju?", - "album_selected": "Albums izvēlēts", - "album_summary": "Albuma kopsavilkums", - "album_updated": "Albums atjaunināts", - "album_upload_assets": "Augšupielādē failus no sava datora un pievieno tos albumam", - "album_user_left": "Pameta {album}", - "album_user_removed": "Noņēma {user}", - "album_viewer_appbar_delete_confirm": "Vai tiešām vēlaties dzēst šo albumu no sava konta?", - "album_viewer_appbar_share_err_delete": "Neizdevās izdzēst albumu", - "album_viewer_appbar_share_err_leave": "Neizdevās pamest albumu", - "album_viewer_appbar_share_err_remove": "Ir problēmas ar aktīvu noņemšanu no albuma", - "album_viewer_appbar_share_err_title": "Neizdevās mainīt albuma nosaukumu", - "album_viewer_appbar_share_leave": "Pamest albumu", - "album_viewer_appbar_share_to": "Kopīgot Uz", - "album_viewer_page_share_add_users": "Pievienot lietotājus", - "albums": "Albumi", - "albums_default_sort_order": "Albuma noklusējuma kārtošanas secība", - "albums_default_sort_order_description": "Sākotnējā failu kārtošanas secība, veidojot jaunus albumus.", - "albums_feature_description": "Failu kolekcijas, kuras var koplietot ar citiem lietotājiem.", - "albums_on_device_count": "Albumi ierīcē ({count})", - "all": "Visi", - "all_albums": "Visi albumi", - "all_people": "Visas personas", - "all_photos": "Visas fotogrāfijas", - "all_videos": "Visi video", - "allow_dark_mode": "Atļaut tumšo režīmu", - "allow_edits": "Atļaut labošanu", - "allow_public_user_to_download": "Atļaut lejupielādēt publiskiem lietotājiem", - "allow_public_user_to_upload": "Atļaut augšupielādēt publiskiem lietotājiem", - "alt_text_qr_code": "QR koda attēls", - "always_keep": "Vienmēr paturēt", - "always_keep_photos_hint": "Vietas atbrīvošanas funkcija paturēs visas fotogrāfijas šajā ierīcē.", - "always_keep_videos_hint": "Vietas atbrīvošanas funkcija paturēs visus video šajā ierīcē.", - "anti_clockwise": "Pretēji pulksteņrādītāja virzienam", - "api_key": "API atslēga", - "api_key_description": "Šī vērtība tiks parādīta tikai vienu reizi. Nokopējiet to pirms loga aizvēršanas.", - "api_keys": "API atslēgas", - "app_architecture_variant": "Variants (arhitektūra)", - "app_bar_signout_dialog_content": "Vai tiešām vēlaties izrakstīties?", - "app_bar_signout_dialog_ok": "Jā", - "app_bar_signout_dialog_title": "Izrakstīties", - "app_download_links": "Lietotņu lejupielādes saites", - "app_settings": "Lietotnes iestatījumi", - "app_stores": "Lietotņu veikali", - "app_update_available": "Pieejams lietotnes atjauninājums", - "appears_in": "Parādās iekš", - "apply_count": "Pielietot ({count, number})", - "archive": "Arhīvs", - "archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs", - "archive_page_title": "Arhīvs ({count})", - "archive_size": "Arhīva izmērs", - "archived": "Arhivēts", - "are_these_the_same_person": "Vai šī ir tā pati persona?", - "array_field_not_fully_supported": "Masīva lauki prasa manuālu JSON rediģēšanu", - "asset_action_delete_err_read_only": "Nevar dzēst read only aktīvu(-s), notiek izlaišana", - "asset_action_share_err_offline": "Nevar iegūt bezsaistes aktīvu(-s), notiek izlaišana", - "asset_added_to_album": "Pievienots albumam", - "asset_adding_to_album": "Pievieno albumam…", - "asset_description_updated": "Faila apraksts ir atjaunināts", - "asset_hashing": "Veido jaucējvērtības…", - "asset_list_group_by_sub_title": "Grupēt pēc", - "asset_list_layout_settings_dynamic_layout_title": "Dinamiskais izkārtojums", - "asset_list_layout_settings_group_automatically": "Automātiski", - "asset_list_layout_settings_group_by": "Grupēt failus pēc", - "asset_list_layout_settings_group_by_month_day": "Mēnesis + diena", - "asset_list_layout_sub_title": "Izvietojums", - "asset_list_settings_subtitle": "Fotorežģa izkārtojuma iestatījumi", - "asset_list_settings_title": "Fotorežģis", - "asset_skipped": "Izlaists", - "asset_skipped_in_trash": "Atkritnē", - "asset_uploaded": "Augšupielādēts", - "asset_uploading": "Augšupielādē…", - "asset_viewer_settings_title": "Failu skatītājs", - "assets": "Faili", - "assets_added_count": "Pievienoja {count, plural, one {# failu} other {# failus}}", - "assets_added_to_album_count": "Pievienoja albumam {count, plural, one {# failu} other {# failus}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Failu} other {Failus}} nevar pievienot albumam", - "assets_deleted_permanently_from_server": "{count} faili dzēsti no Immich servera", - "assets_moved_to_trash_count": "Pārvietoja {count, plural, one {# failu} other {# failus}} uz atkritni", - "assets_removed_count": "Noņēma {count, plural, one {# failu} other {# failus}}", - "assets_trashed": "{count} faili pārvietoti uz atkritni", - "assets_trashed_from_server": "{count} faili pārvietoti uz Immich servera atkritni", - "authorized_devices": "Autorizētās ierīces", - "automatic_endpoint_switching_title": "Automātiska URL pārslēgšana", - "autoplay_slideshow": "Automātiska slaidrādes atskaņošana", - "back": "Atpakaļ", - "background_backup_running_error": "Pašlaik darbojas dublēšana fonā, nevar uzsākt manuālu dublēšanu", - "background_options": "Fona opcijas", - "backup": "Dublēšana", - "backup_album_selection_page_albums_device": "Albumi ierīcē ({count})", - "backup_album_selection_page_albums_tap": "Pieskarieties, lai iekļautu, veiciet dubultskārienu, lai izslēgtu", - "backup_album_selection_page_assets_scatter": "Faili var būt izmētāti pa vairākiem albumiem. Tādējādi dublēšanas procesā albumus var iekļaut vai neiekļaut.", - "backup_album_selection_page_select_albums": "Atlasīt albumus", - "backup_album_selection_page_selection_info": "Atlases informācija", - "backup_album_selection_page_total_assets": "Unikālo failu kopsumma", - "backup_albums_sync": "Dublēšanas albumu sinhronizācija", - "backup_all": "Viss", - "backup_background_service_backup_failed_message": "Neizdevās dublēt līdzekļus. Notiek atkārtota mēģināšana…", - "backup_background_service_connection_failed_message": "Neizdevās izveidot savienojumu ar serveri. Notiek atkārtota mēģināšana…", - "backup_background_service_current_upload_notification": "Notiek {filename} augšupielāde", - "backup_background_service_default_notification": "Notiek jaunu aktīvu meklēšana…", - "backup_background_service_error_title": "Dublēšanas kļūda", - "backup_background_service_in_progress_notification": "Notiek aktīvu dublēšana…", - "backup_background_service_upload_failure_notification": "Neizdevās augšupielādēt {filename}", - "backup_controller_page_albums": "Dublējuma Albumi", - "backup_controller_page_background_app_refresh_disabled_content": "Iespējojiet fona aplikācijas atsvaidzināšanu sadaļā Iestatījumi > Vispārīgi > Fona Aplikācijas Atsvaidzināšana, lai izmantotu fona dublēšanu.", - "backup_controller_page_background_app_refresh_disabled_title": "Fona aplikācijas atsvaidzināšana atspējota", - "backup_controller_page_background_app_refresh_enable_button_text": "Doties uz iestatījumiem", - "backup_controller_page_background_battery_info_link": "Parādīt, kā", - "backup_controller_page_background_battery_info_message": "Lai iegūtu vislabāko fona dublēšanas pieredzi, lūdzu, atspējojiet visas akumulatora optimizācijas, kas ierobežo Immich fona aktivitāti.\n\nTā kā katrai ierīcei iestatījumi ir citādāki, lūdzu, meklējiet nepieciešamo informāciju pie ierīces ražotāja.", - "backup_controller_page_background_battery_info_ok": "Labi", - "backup_controller_page_background_battery_info_title": "Akumulatora optimizācija", - "backup_controller_page_background_charging": "Tikai uzlādes laikā", - "backup_controller_page_background_configure_error": "Neizdevās konfigurēt fona pakalpojumu", - "backup_controller_page_background_delay": "Aizkavēt jaunu failu dublēšanu: {duration}", - "backup_controller_page_background_description": "Ieslēdziet fona pakalpojumu, lai automātiski dublētu visus jaunos aktīvus, neatverot programmu", - "backup_controller_page_background_is_off": "Automātiskā fona dublēšana ir izslēgta", - "backup_controller_page_background_is_on": "Automātiskā fona dublēšana ir ieslēgta", - "backup_controller_page_background_turn_off": "Izslēgt fona pakalpojumu", - "backup_controller_page_background_turn_on": "Ieslēgt fona pakalpojumu", - "backup_controller_page_background_wifi": "Tikai Wi-Fi tīklā", - "backup_controller_page_backup": "Dublēšana", - "backup_controller_page_backup_selected": "Atlasīts: ", - "backup_controller_page_backup_sub": "Dublētie Fotoattēli un videoklipi", - "backup_controller_page_created": "Izveidots: {date}", - "backup_controller_page_desc_backup": "Ieslēdziet priekšplāna dublēšanu, lai, atverot programmu, serverī automātiski augšupielādētu jaunus aktīvus.", - "backup_controller_page_excluded": "Izņemot: ", - "backup_controller_page_failed": "Neizdevās ({count})", - "backup_controller_page_filename": "Faila nosaukums: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Dublējuma Informācija", - "backup_controller_page_none_selected": "Neviens nav atlasīts", - "backup_controller_page_remainder": "Atlikums", - "backup_controller_page_remainder_sub": "Atlikušie fotoattēli un videoklipi, kurus dublēt no atlases", - "backup_controller_page_server_storage": "Servera krātuve", - "backup_controller_page_start_backup": "Sākt dublēšanu", - "backup_controller_page_status_off": "Automātiskā priekšplāna dublēšana ir izslēgta", - "backup_controller_page_status_on": "Automātiskā priekšplāna dublēšana ir ieslēgta", - "backup_controller_page_storage_format": "{used} no {total} tiek izmantots", - "backup_controller_page_to_backup": "Dublējamie albumi", - "backup_controller_page_total_sub": "Visi unikālie fotoattēli un videoklipi no izvēlētajiem albumiem", - "backup_controller_page_turn_off": "Izslēgt priekšplāna dublēšanu", - "backup_controller_page_turn_on": "Ieslēgt priekšplāna dublēšanu", - "backup_controller_page_uploading_file_info": "Faila informācijas augšupielāde", - "backup_err_only_album": "Nevar noņemt vienīgo albumu", - "backup_error_sync_failed": "Sinhronizācija neizdevās. Nevar apstrādāt rezerves kopiju.", - "backup_info_card_assets": "faili", - "backup_manual_cancelled": "Atcelts", - "backup_manual_in_progress": "Augšupielāde jau notiek. Mēģiniet pēc kāda laika atkārtoti", - "backup_manual_success": "Veiksmīgi", - "backup_manual_title": "Augšupielādes statuss", - "backup_options_page_title": "Dublēšanas iestatījumi", - "backup_settings_subtitle": "Pārvaldīt augšupielādes iestatījumus", - "backward": "Atpakaļejoša", - "biometric_auth_enabled": "Ieslēgta biometriskā autentifikācija", - "biometric_locked_out": "Biometriskā autentifikācija tev ir bloķēta", - "biometric_no_options": "Nav pieejamas biometriskās autentifikācijas iespējas", - "biometric_not_available": "Biometriskā autentifikācija šajā ierīcē nav pieejama", - "birthdate_saved": "Dzimšanas datums veiksmīgi saglabāts", - "birthdate_set_description": "Dzimšanas datums tiek izmantots, lai aprēķinātu šīs personas vecumu fotogrāfijas uzņemšanas brīdī.", - "blurred_background": "Izpludināts fons", - "bugs_and_feature_requests": "Kļūdas un funkciju pieprasījumi", - "build": "Būvējums", - "build_image": "Būvējuma attēls", - "buy": "Iegādāties Immich", - "cache_settings_clear_cache_button": "Iztīrīt kešatmiņu", - "cache_settings_clear_cache_button_title": "Iztīra aplikācijas kešatmiņu. Tas būtiski ietekmēs lietotnes veiktspēju, līdz kešatmiņa būs pārbūvēta.", - "cache_settings_duplicated_assets_clear_button": "NOTĪRĪT", - "cache_settings_duplicated_assets_subtitle": "Fotoattēli un videoklipi, kurus lietotne ir iekļāvusi ignorējamo sarakstā", - "cache_settings_duplicated_assets_title": "Dublicētie faili ({count})", - "cache_settings_statistics_album": "Bibliotēkas sīktēli", - "cache_settings_statistics_full": "Pilni attēli", - "cache_settings_statistics_shared": "Koplietojamo albumu sīktēli", - "cache_settings_statistics_thumbnail": "Sīktēli", - "cache_settings_statistics_title": "Kešatmiņas lietojums", - "cache_settings_subtitle": "Kontrolēt Immich mobilās lietotnes kešdarbi", - "cache_settings_tile_subtitle": "Kontrolēt lokālās krātuves uzvedību", - "cache_settings_tile_title": "Lokālā krātuve", - "cache_settings_title": "Kešdarbes iestatījumi", - "camera": "Fotokamera", - "camera_brand": "Fotokameras zīmols", - "camera_model": "Fotokameras modelis", - "cancel": "Atcelt", - "cancel_search": "Atcelt meklēšanu", - "canceled": "Atcelts", - "canceling": "Atceļ", - "cannot_merge_people": "Nevar apvienot personas", - "cannot_undo_this_action": "Šo darbību nevar atcelt!", - "cast": "Pārraidīt", - "cast_description": "Konfigurēt pieejamos pārraides galamērķus", - "change_date": "Mainīt datumu", - "change_description": "Mainīt aprakstu", - "change_display_order": "Mainīt attēlošanas secību", - "change_expiration_time": "Izmainīt derīguma termiņu", - "change_location": "Mainīt atrašanās vietu", - "change_name": "Mainīt nosaukumu", - "change_name_successfully": "Vārds veiksmīgi nomainīts", - "change_password": "Nomainīt paroli", - "change_password_description": "Vai nu šī ir pirmā reize, kad pieslēdzaties sistēmai, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, ievadiet jauno paroli zemāk.", - "change_password_form_confirm_password": "Apstiprināt Paroli", - "change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.", - "change_password_form_new_password": "Jauna Parole", - "change_password_form_password_mismatch": "Paroles nesakrīt", - "change_password_form_reenter_new_password": "Atkārtoti ievadīt jaunu paroli", - "change_pin_code": "Nomainīt PIN kodu", - "charging": "Lādē", - "charging_requirement_mobile_backup": "Fona dublēšanai nepieciešams, lai ierīce tiktu lādēta", - "check_corrupt_asset_backup_button": "Veikt pārbaudi", - "checksum": "Kontrolsumma", - "choose_matching_people_to_merge": "Izvēlies atbilstošas personas apvienošanai", - "city": "Pilsēta", - "cleanup_confirm_prompt_title": "Dzēst no šīs ierīces?", - "cleanup_deleted_assets": "Pārvietoja {count} failus uz ierīces atkritni", - "cleanup_deleting": "Pārvieto uz atkritni...", - "cleanup_found_assets_with_size": "Atrada {count} dublētus failus ({size})", - "cleanup_icloud_shared_albums_excluded": "Skenēšanā netiek iekļauti iCloud kopīgotie albumi", - "cleanup_preview_title": "Dzēšamie faili ({count})", - "cleanup_trash_hint": "Lai pilnībā atbrīvotu uzglabāšanas vietu, atveriet sistēmas galerijas lietotni un iztukšojiet atkritni", - "clear": "Notīrīt", - "clear_all": "Notīrīt visu", - "clear_all_recent_searches": "Notīrīt visas pēdējās meklēšanas", - "clear_file_cache": "Notīrīt failu kešatmiņu", - "clear_message": "Notīrīt paziņojumu", - "clear_value": "Notīrīt vērtību", - "client_cert_dialog_msg_confirm": "Labi", - "client_cert_enter_password": "Ievadi paroli", - "client_cert_import": "Importēt", - "client_cert_import_success_msg": "Klienta sertifikāts ir importēts", - "client_cert_invalid_msg": "Nederīgs sertifikāta fails vai nepareiza parole", - "client_cert_remove_msg": "Klienta sertifikāts ir noņemts", - "client_cert_subtitle": "Atbalsta tikai PKCS12 (.p12, .pfx) formātu. Sertifikātu importēšana/noņemšana ir pieejama tikai pirms pieslēgšanās", - "client_cert_title": "SSL klienta sertifikāts [EKSPERIMENTĀLS]", - "clockwise": "Pulksteņrādītāja virzienā", - "close": "Aizvērt", - "collapse": "Sakļaut", - "collapse_all": "Sakļaut visu", - "color": "Krāsa", - "color_theme": "Krāsu tēma", - "command": "Komanda", - "comment_deleted": "Komentārs dzēsts", - "comment_options": "Komentāru iespējas", - "comments_and_likes": "Komentāri un tīkšķi", - "comments_are_disabled": "Komentāri ir atslēgti", - "common_create_new_album": "Izveidot jaunu albumu", - "completed": "Pabeigts", - "confirm": "Apstiprināt", - "confirm_admin_password": "Administratora paroles apstiprinājums", - "confirm_new_pin_code": "Apstiprināt jauno PIN kodu", - "confirm_password": "Apstiprināt paroli", - "confirm_tag_face": "Vai vēlies atzīmēt šo seju kā {name}?", - "confirm_tag_face_unnamed": "Vai vēlies atzīmēt šo seju?", - "context": "Konteksts", - "continue": "Turpināt", - "control_bottom_app_bar_create_new_album": "Izveidot jaunu albumu", - "control_bottom_app_bar_delete_from_immich": "Dzēst no Immich", - "control_bottom_app_bar_delete_from_local": "Dzēst no ierīces", - "control_bottom_app_bar_edit_location": "Rediģēt atrašanās vietu", - "control_bottom_app_bar_edit_time": "Rediģēt datumu un laiku", - "control_bottom_app_bar_share_to": "Kopīgot uz", - "control_bottom_app_bar_trash_from_immich": "Pārvietot uz Atkritni", - "copy_error": "Kopēšanas kļūda", - "copy_to_clipboard": "Kopēt starpliktuvē", - "country": "Valsts", - "cover": "Aizpildīts ekrāns", - "covers": "Vāciņi", - "create": "Izveidot", - "create_album": "Izveidot albumu", - "create_album_page_untitled": "Bez nosaukuma", - "create_api_key": "Izveidot API atslēgu", - "create_first_workflow": "Izveidot pirmo darba plūsmu", - "create_library": "Izveidot bibliotēku", - "create_link": "Izveidot saiti", - "create_link_to_share": "Izveidot kopīgošanas saiti", - "create_new": "IZVEIDOT JAUNU", - "create_new_person": "Izveidot jaunu personu", - "create_new_person_hint": "Piesaistīt izvēlētos failus jaunai personai", - "create_new_user": "Izveidot jaunu lietotāju", - "create_shared_album_page_share_add_assets": "PIEVIENOT AKTĪVUS", - "create_shared_album_page_share_select_photos": "Fotoattēlu Izvēle", - "create_user": "Izveidot lietotāju", - "create_workflow": "Izveidot darba plūsmu", - "created_at": "Izveidots", - "crop": "Apcirpt", - "crop_aspect_ratio_original": "Oriģināls", - "curated_object_page_title": "Lietas", - "current_pin_code": "Esošais PIN kods", - "current_server_address": "Pašreizējā servera adrese", - "custom_date": "Pielāgots datums", - "custom_locale": "Pielāgota lokalizācija", - "custom_locale_description": "Formatēt datumus un skaitļus atbilstoši valodai un reģionam", - "custom_url": "Pielāgots URL", - "cutoff_date_description": "Paturēt fotoattēlus no pēdējā…", - "cutoff_day": "{count, plural, one {dienas} other {dienām}}", - "cutoff_year": "{count, plural, one {gada} other {gadiem}}", - "daily_title_text_date_year": "E, MMM dd, gggg", - "dark_theme": "Pārslēgt tumšo tēmu", - "date_after": "Datums pēc", - "date_and_time": "Datums un Laiks", - "date_before": "Datums pirms", - "date_format": "E, LLL d, g • h:mm a", - "date_of_birth_saved": "Dzimšanas datums veiksmīgi saglabāts", - "date_range": "Datumu diapazons", - "day": "Diena", - "days": "Dienas", - "deduplicate_all": "Dedublicēt visus", - "deduplication_criteria_1": "Attēla izmēru baitos", - "deduplication_criteria_2": "EXIF datu skaitu", - "deduplication_info": "Deduplicēšanas informācija", - "deduplication_info_description": "Lai automātiski atzīmētu failus un masveidā noņemtu dublikātus, mēs skatāmies uz:", - "default_locale": "Noklusējuma lokalizācija", - "default_locale_description": "Formatēt datumus un skaitļus atbilstoši pārlūka lokalizācijai", - "delete": "Dzēst", - "delete_album": "Dzēst albumu", - "delete_dialog_alert": "Šie vienumi tiks neatgriezeniski dzēsti no Immich un jūsu ierīces", - "delete_dialog_alert_local": "Šie vienumi tiks neatgriezeniski dzēsti no jūsu ierīces, bet joprojām būs pieejami Immich serverī.", - "delete_dialog_alert_local_non_backed_up": "Daži no šiem elementiem netiek dublēti Immich un tiks neatgriezeniski dzēsti no jūsu ierīces.", - "delete_dialog_alert_remote": "Šie vienumi tiks neatgriezeniski dzēsti no Immich servera.", - "delete_dialog_ok_force": "Tā pat dzēst", - "delete_dialog_title": "Neatgriezeniski Dzēst", - "delete_face": "Dzēst seju", - "delete_key": "Dzēst atslēgu", - "delete_library": "Dzēst bibliotēku", - "delete_link": "Dzēst saiti", - "delete_local_action_prompt": "{count} dzēsti lokāli", - "delete_local_dialog_ok_backed_up_only": "Dzēst tikai dublētos", - "delete_local_dialog_ok_force": "Tā pat dzēst", - "delete_others": "Dzēst citus", - "delete_shared_link": "Dzēst Kopīgošanas saiti", - "delete_shared_link_dialog_title": "Dzēst Kopīgošanas saiti", - "delete_user": "Dzēst lietotāju", - "deleted_shared_link": "Dzēst kopīgoto saiti", - "description": "Apraksts", - "description_input_hint_text": "Pievienot aprakstu...", - "description_input_submit_error": "Atjauninot aprakstu, radās kļūda; papildinformāciju skatiet žurnālā", - "details": "INFORMĀCIJA", - "direction": "Secība", - "discord": "Discord", - "discover": "Atklāt", - "discovered_devices": "Atrastās ierīces", - "display_order": "Attēlošanas secība", - "display_original_photos": "Rādīt oriģinālās fotogrāfijas", - "do_not_show_again": "Vairs nerādīt šo ziņojumu", - "documentation": "Dokumentācija", - "done": "Gatavs", - "download": "Lejupielādēt", - "download_action_prompt": "Lejupielādē {count} failus", - "download_canceled": "Lejupielāde atcelta", - "download_complete": "Lejupielāde pabeigta", - "download_enqueue": "Lejupielāde ierindota", - "download_error": "Lejupielādes kļūda", - "download_failed": "Lejupielāde neizdevās", - "download_finished": "Lejupielāde pabeigta", - "download_include_embedded_motion_videos": "Iegultie videoklipi", - "download_include_embedded_motion_videos_description": "Iekļaut video, kas iebūvēti kustīgos fotoattēlos, kā atsevišķu failu", - "download_notfound": "Lejupielāde nav atrasta", - "download_original": "Lejupielādēt oriģinālu", - "download_paused": "Lejupielāde nopauzēta", - "download_settings": "Lejupielāde", - "download_settings_description": "Ar failu lejupielādi saistīto iestatījumu pārvaldība", - "download_started": "Lejupielāde sākta", - "download_sucess": "Lejupielāde izdevās", - "download_sucess_android": "Multivides fails ir lejupielādēts uz DCIM/Immich", - "download_waiting_to_retry": "Gaida, lai mēģinātu atkārtoti", - "downloading": "Lejupielādē", - "downloading_asset_filename": "Lejupielādē failu {filename}", - "downloading_from_icloud": "Lejupielādē no iCloud", - "downloading_media": "Lejupielādē failu", - "duplicates": "Dublikāti", - "duplicates_description": "Atrisini katru grupu, norādot, kuri no tiem ir dublikāti", - "duration": "Ilgums", - "edit": "Labot", - "edit_album": "Labot albumu", - "edit_avatar": "Labot avatāru", - "edit_birthday": "Labot dzimšanas dienu", - "edit_date": "Labot datumu", - "edit_date_and_time": "Labot datumu un laiku", - "edit_date_and_time_action_prompt": "{count} datums un laiks labots", - "edit_description": "Labot aprakstu", - "edit_description_prompt": "Lūdzu, izvēlies jaunu aprakstu:", - "edit_exclusion_pattern": "Labot izslēgšanas šablonu", - "edit_faces": "Labot sejas", - "edit_key": "Labot atslēgu", - "edit_link": "Rediģēt saiti", - "edit_location": "Rediģēt Atrašanās Vietu", - "edit_location_dialog_title": "Atrašanās vieta", - "edit_name": "Rediģēt vārdu", - "edit_people": "Labot profilu", - "edit_title": "Labot nosaukumu", - "edit_user": "Labot lietotāju", - "edit_workflow": "Labot darba plūsmu", - "editor": "Redaktors", - "editor_close_without_save_prompt": "Izmaiņas netiks saglabātas", - "editor_close_without_save_title": "Aizvērt redaktoru?", - "editor_flip_horizontal": "Apvērst horizontāli", - "editor_flip_vertical": "Apvērst vertikāli", - "editor_orientation": "Orientācija", - "editor_reset_all_changes": "Atcelt izmaiņas", - "editor_rotate_left": "Pagriezt par 90° pretēji pulksteņrādītāja virzienam", - "editor_rotate_right": "Pagriezt par 90° pulksteņrādītāja virzienā", - "email": "E-pasts", - "email_notifications": "E-pasta paziņojumi", - "empty_folder": "Šī mape ir tukša", - "empty_trash": "Iztukšot atkritni", - "enable_backup": "Ieslēgt dublēšanu", - "enable_biometric_auth_description": "Lai iespējotu biometrisko autentifikāciju, Ievadiet savu PIN kodu", - "end_date": "Beigu datums", - "enqueued": "Ierindots", - "enter_wifi_name": "Ievadi Wi-Fi nosaukumu", - "enter_your_pin_code": "Ievadi savu PIN kodu", - "enter_your_pin_code_subtitle": "Ievadi savu PIN kodu, lai piekļūtu slēgtajai mapei", - "error": "Kļūda", - "error_change_sort_album": "Neizdevās nomainīt albuma kārtošanas secību", - "error_loading_albums": "Kļūda, ielādējot albumus", - "error_loading_image": "Kļūda, ielādējot attēlu", - "error_loading_partners": "Kļūda, ielādējot partnerus: {error}", - "error_retrieving_asset_information": "Kļūda, iegūstot informāciju par resursu", - "error_saving_image": "Kļūda: {error}", - "error_title": "Kļūda - kaut kas nogāja greizi", - "error_while_navigating": "Kļūda, navigējot uz resursu", - "errors": { - "cannot_navigate_next_asset": "Nevar pāriet uz nākamo resursu", - "cannot_navigate_previous_asset": "Nevar pāriet uz iepriekšējo resursu", - "cant_apply_changes": "Nevar piemērot izmaiņas", - "cant_get_faces": "Nevar iegūt sejas", - "cant_search_people": "Neizdevās veikt peronu meklēšanu", - "exclusion_pattern_already_exists": "Šāds izslēgšanas šablons jau pastāv.", - "failed_to_create_album": "Neizdevās izveidot albumu", - "failed_to_create_shared_link": "Neizdevās izvedot kopīgošanas saiti", - "failed_to_edit_shared_link": "Neizdevās labot kopīgoto saiti", - "failed_to_get_people": "Neizdevās iegūt personas", - "failed_to_keep_this_delete_others": "Neizdevās paturēt šo failu un dzēst pārējos failus", - "failed_to_load_asset": "Neizdevās ielādēt failu", - "failed_to_load_assets": "Neizdevās ielādēt failus", - "failed_to_load_notifications": "Neizdevās ielādēt paziņojumus", - "failed_to_load_people": "Neizdevās ielādēt personas", - "failed_to_remove_product_key": "Neizdevās noņemt produkta atslēgu", - "failed_to_reset_pin_code": "Neizdevās atiestatīt PIN kodu", - "failed_to_stack_assets": "Neizdevās apvienot failus kaudzē", - "failed_to_unstack_assets": "Neizdevās atcelt failu apvienošanu kaudzē", - "failed_to_update_notification_status": "Neizdevās mainīt paziņojuma statusu", - "incorrect_email_or_password": "Nepareizs e-pasts vai parole", - "library_folder_already_exists": "Šis importa ceļš jau pastāv.", - "profile_picture_transparent_pixels": "Profila attēlos nevar būt caurspīdīgi pikseļi. Lūdzu, palielini un/vai pārvieto attēlu.", - "quota_higher_than_disk_size": "Tu esi iestatījis kvotu, kas pārsniedz diska izmēru", - "something_went_wrong": "Kaut kas nogāja greizi", - "unable_to_add_exclusion_pattern": "Neizdevās pievienot izslēgšanas šablonu", - "unable_to_change_description": "Neizdevās nomainīt aprakstu", - "unable_to_create_admin_account": "Nevar izveidot administratora kontu", - "unable_to_create_api_key": "Nevar izveidot jaunu API atslēgu", - "unable_to_create_library": "Nevar izveidot bibliotēku", - "unable_to_create_user": "Neizdevās izveidot lietotāju", - "unable_to_delete_album": "Nevar izdzēst albumu", - "unable_to_delete_asset": "Nevar izdzēst failu", - "unable_to_delete_exclusion_pattern": "Neizdevās dzēst izslēgšanas šablonu", - "unable_to_delete_user": "Neizdevās dzēst lietotāju", - "unable_to_edit_exclusion_pattern": "Neizdevās labot izslēgšanas šablonu", - "unable_to_empty_trash": "Neizdevās iztukšot atkritni", - "unable_to_hide_person": "Neizdevās paslēpt personu", - "unable_to_restore_trash": "Neizdevās atjaunot failus no atkritnes", - "unable_to_save_date_of_birth": "Neizdevās saglabāt dzimšanas datumu", - "unable_to_scan_libraries": "Bibliotēku skenēšana neizdevās", - "unable_to_scan_library": "Bibliotēkas skenēšana neizdevās", - "unable_to_trash_asset": "Neizdevās pārvietot failu uz atkritni", - "unable_to_update_album_cover": "Nevar atjaunināt albuma vāciņu" - }, - "errors_text": "Kļūdas", - "exif": "Exif", - "exif_bottom_sheet_description": "Pievienot Aprakstu...", - "exif_bottom_sheet_details": "INFORMĀCIJA", - "exif_bottom_sheet_location": "ATRAŠANĀS VIETA", - "exif_bottom_sheet_no_description": "Nav apraksta", - "exif_bottom_sheet_people": "PERSONAS", - "exif_bottom_sheet_person_add_person": "Pievienot vārdu", - "exit_slideshow": "Iziet no slīdrādes", - "experimental_settings_new_asset_list_subtitle": "Izstrādes posmā", - "experimental_settings_new_asset_list_title": "Iespējot eksperimentālo fotorežģi", - "experimental_settings_subtitle": "Izmanto uzņemoties risku!", - "experimental_settings_title": "Eksperimentāls", - "expire_after": "Derīguma termiņš beidzas pēc", - "expired": "Derīguma termiņš beidzās", - "explore": "Izpētīt", - "export": "Eksportēt", - "export_as_json": "Eksportēt kā JSON", - "export_database": "Eksportēt datubāzi", - "export_database_description": "Eksportēt SQLite datubāzi", - "extension": "Paplašinājums", - "external": "Ārējs", - "external_libraries": "Ārējas bibliotēkas", - "external_network": "Ārējs tīkls", - "external_network_sheet_info": "Kad nav pieejams izvēlētais Wi-Fi tīkls, aplikācija pieslēgsies serverim lietojot pirmo strādājošo URL no saraksta, sākot ar augšējo", - "face_unassigned": "Nepiešķirts", - "failed": "Neizdevās", - "failed_count": "Neizdevās: {count}", - "failed_to_authenticate": "Neizdevās autentificēties", - "failed_to_load_assets": "Neizdevās ielādēt failus", - "failed_to_load_folder": "Neizdevās ielādēt mapi", - "favorite": "Izlase", - "favorites": "Izlase", - "favorites_page_no_favorites": "Nav atrasti iecienītākie faili", - "features_in_development": "Izstrādes stadijā esošas funkcijas", - "features_setting_description": "Lietotnes funkciju pārvaldība", - "file_name_or_extension": "Faila nosaukums vai paplašinājums", - "filename": "Faila nosaukums", - "filetype": "Faila tips", - "filter": "Filtrēt", - "filter_people": "Filtrēt personas", - "filter_places": "Filtrēt vietas", - "filters": "Filtri", - "first": "Pirmais", - "folder": "Mape", - "folder_not_found": "Mape nav atrasta", - "folders": "Mapes", - "forgot_pin_code_question": "Aizmirsi savu PIN?", - "forward": "Uz priekšu", - "free_up_space": "Atbrīvot vietu", - "free_up_space_description": "Pārvietot dublētās fotogrāfijas un videoklipus uz ierīces atkritni, lai atbrīvotu vietu. Failu kopijas serverī paliks drošībā.", - "free_up_space_settings_subtitle": "Atbrīvot ierīces atmiņu", - "full_path": "Pilnais ceļš: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Šī funkcija darbojas, lejupielādējot ārējos resursus no Google.", - "get_help": "Saņemt palīdzību", - "get_wifiname_error": "Nevarēja iegūt Wi-Fi nosaukumu. Pārliecinies, ka esi piešķīris nepieciešamās atļaujas un esi savienots ar Wi-Fi tīklu", - "getting_started": "Pirmie soļi", - "go_back": "Doties atpakaļ", - "go_to_folder": "Doties uz mapi", - "go_to_search": "Doties uz meklēšanu", - "gps": "Ir koordinātas", - "gps_missing": "Nav koordinātu", - "grant_permission": "Piešķirt atļauju", - "group_albums_by": "Grupēt albumus pēc...", - "group_country": "Grupēt pēc valsts", - "group_no": "Negrupēt", - "group_owner": "Grupēt pēc īpašnieka", - "group_places_by": "Grupēt vietas pēc...", - "group_year": "Grupēt pēc gada", - "haptic_feedback_switch": "Iespējot haptisku reakciju", - "haptic_feedback_title": "Haptiska Reakcija", - "has_quota": "Kvota", - "hash_asset": "Veidot faila jaucējvērtību", - "hashed_assets": "Faili ar jaucējvērtībām", - "hashing": "Veido jaucējvērtības", - "header_settings_field_validator_msg": "Vērtība nevar būt tukša", - "header_settings_header_name_input": "Galvenes lauks", - "header_settings_header_value_input": "Galvenes vērtība", - "headers_settings_tile_title": "Pielāgotas starpniekservera galvenes", - "height": "Augstums", - "hide_all_people": "Paslēpt visas personas", - "hide_gallery": "Paslēpt galeriju", - "hide_named_person": "Paslēpt personu {name}", - "hide_password": "Paslēpt paroli", - "hide_person": "Paslēpt personu", - "hide_schema": "Paslēpt shēmu", - "hide_text_recognition": "Slēpt teksta atpazīšanu", - "hide_unnamed_people": "Paslēpt nenosauktas personas", - "home_page_add_to_album_conflicts": "Pievienoja {added} failus albumam {album}. {failed} faili jau ir albumā.", - "home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos failus, izlaiž", - "home_page_add_to_album_success": "Pievienoja {added} aktīvus albumam {album}.", - "home_page_album_err_partner": "Pagaidām nevar pievienot partnera aktīvus albumam, notiek izlaišana", - "home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktīvus, izlaiž", - "home_page_archive_err_partner": "Nevarēja arhivēt partnera aktīvus, notiek izlaišana", - "home_page_building_timeline": "Tiek izveidota laika skala", - "home_page_delete_err_partner": "Nevarēja dzēst partnera aktīvus, notiek izlaišana", - "home_page_delete_remote_err_local": "Lokālie faili dzēšanai attālinātajā izvēlē, izlaiž", - "home_page_favorite_err_local": "Vēl nevar pievienot izlasei lokālos failus, izlaiž", - "home_page_favorite_err_partner": "Pagaidām nevar ievietot izlasē partnera failus, izlaiž", - "home_page_first_time_notice": "Ja šī ir pirmā reize, kad izmanto lietotni, lūdzu, izvēlies dublējamo albumu, lai laika skalā varētu aizpildīt fotoattēlus un videoklipus", - "home_page_locked_error_local": "Nevar pārvietot lokālos failus uz slēgto mapi, izlaiž", - "home_page_locked_error_partner": "Nevar pārvietot partneru failus uz slēgto mapi, izlaiž", - "home_page_share_err_local": "Caur saiti nevarēja kopīgot lokālos aktīvus, izlaiž", - "home_page_upload_err_limit": "Vienlaikus var augšupielādēt ne vairāk kā 30 aktīvus, notiek izlaišana", - "hour": "Stunda", - "hours": "Stundas", - "id": "ID", - "idle": "Dīkstāvē", - "ignore_icloud_photos": "Ignorēt iCloud fotogrāfijas", - "ignore_icloud_photos_description": "iCloud uzglabātās fotogrāfijas netiks augšupielādētas Immich serverī", - "image": "Attēls", - "image_saved_successfully": "Attēls saglabāts", - "image_viewer_page_state_provider_download_started": "Lejupielāde uzsākta", - "image_viewer_page_state_provider_download_success": "Lejupielāde izdevās", - "image_viewer_page_state_provider_share_error": "Kopīgošanas Kļūda", - "immich_logo": "Immich logo", - "immich_web_interface": "Immich tīmekļa saskarne", - "import_from_json": "Importēt no JSON", - "import_path": "Importa ceļš", - "in_albums": "{count, plural, one {# albumā} other {# albumos}}", - "in_archive": "Arhīvā", - "include_archived": "Iekļaut arhivētos", - "include_shared_albums": "Iekļaut koplietotos albumus", - "include_shared_partner_assets": "Iekļaut partneru koplietotos failus", - "info": "Informācija", - "interval": { - "day_at_onepm": "Katru dienu 13.00", - "night_at_midnight": "Katru dienu pusnaktī", - "night_at_twoam": "Katru dienu 2.00 naktī" - }, - "invalid_date": "Nederīgs datums", - "invalid_date_format": "Nederīgs datuma formāts", - "invite_people": "Ielūgt cilvēkus", - "invite_to_album": "Uzaicināt albumā", - "ios_debug_info_fetch_ran_at": "Ielasīšana notika {dateTime}", - "ios_debug_info_last_sync_at": "Pēdējā sinhronizācija {dateTime}", - "ios_debug_info_no_processes_queued": "Nav ierindotu fona procesu", - "ios_debug_info_processing_ran_at": "Apstrāde notika {dateTime}", - "items_count": "{count, plural, one {# vienums} other {# vienumi}}", - "jobs": "Uzdevumi", - "json_editor": "JSON redaktors", - "json_error": "JSON kļūda", - "keep": "Paturēt", - "keep_albums": "Paturēt albumus", - "keep_albums_count": "Patur {count} {count, plural, one {albumu} other {albumus}}", - "keep_all": "Paturēt visus", - "keep_description": "Izvēlies, kas paliks tavā ierīcē, atbrīvojot vietu.", - "keep_on_device": "Paturēt ierīcē", - "keep_on_device_hint": "Izvēlies failus, kurus paturēt šajā ierīcē", - "keep_this_delete_others": "Paturēt šo, dzēst citus", - "keeping": "Patur: {items}", - "keyboard_shortcuts": "Tastatūras saīsnes", - "language": "Valoda", - "language_no_results_subtitle": "Mēģini pielāgot meklēšanas terminu", - "language_no_results_title": "Nav atrasta neviena valoda", - "language_search_hint": "Meklēt valodas...", - "language_setting_description": "Izvēlies vēlamo valodu", - "large_files": "Lielie faili", - "last": "Pēdējais", - "last_seen": "Pēdējo reizi redzēts", - "latest_version": "Jaunākā versija", - "latitude": "Ģeogrāfiskais platums", - "leave": "Paturēt", - "leave_album": "Pamest albumu", - "lens_model": "Objektīva modelis", - "let_others_respond": "Ļaut citiem atbildēt", - "level": "Līmenis", - "library": "Bibliotēka", - "library_add_folder": "Pievienot mapi", - "library_edit_folder": "Labot mapi", - "library_options": "Bibliotēkas opcijas", - "library_page_device_albums": "Albumi ierīcē", - "library_page_new_album": "Jauns albums", - "library_page_sort_asset_count": "Failu skaits", - "library_page_sort_created": "Jaunākais izveidotais", - "library_page_sort_last_modified": "Pēdējās izmaiņas", - "library_page_sort_title": "Albuma virsraksts", - "licenses": "Licences", - "like": "Patīk", - "like_deleted": "Tīkšķis dzēsts", - "link_to_oauth": "Piesaistīt OAuth", - "linked_oauth_account": "Piesaistītais OAuth konts", - "list": "Saraksts", - "loading": "Ielādē", - "local": "Lokāli", - "local_asset_cast_failed": "Nav iespējams pārraidīt resursu, kas nav augšupielādēts serverī", - "local_assets": "Lokālie faili", - "local_media_summary": "Lokālo mediju kopsavilkums", - "local_network": "Lokālais tīkls", - "local_network_sheet_info": "Izmantojot norādīto Wi-Fi tīklu, lietotne veidos savienojumu ar serveri, izmantojot šo URL", - "location_permission": "Atrašanās vietas atļauja", - "location_permission_content": "Lai izmantotu automātiskās pārslēgšanās funkciju, Immich ir nepieciešama precīzas atrašanās vietas atļauja, lai varētu nolasīt pašreizējā Wi-Fi tīkla nosaukumu", - "location_picker_choose_on_map": "Izvēlēties uz kartes", - "location_picker_latitude_error": "Ievadiet korektu ģeogrāfisko platumu", - "location_picker_latitude_hint": "Ievadiet savu ģeogrāfisko platumu šeit", - "location_picker_longitude_error": "Ievadiet korektu ģeogrāfisko garumu", - "location_picker_longitude_hint": "Ievadiet savu ģeogrāfisko garumu šeit", - "locked_folder": "Slēgtā mape", - "log_out": "Izrakstīties", - "logged_in_as": "Pieslēdzies kā {user}", - "login": "Pieslēgties", - "login_disabled": "Pieslēgšanās ir atslēgta", - "login_form_api_exception": "API izņēmums. Lūdzu, pārbaudiet servera URL un mēģiniet vēlreiz.", - "login_form_back_button_text": "Atpakaļ", - "login_form_email_hint": "jūsuepasts@email.com", - "login_form_endpoint_hint": "http://jūsu-servera-ip:ports", - "login_form_endpoint_url": "Servera Galapunkta URL", - "login_form_err_http": "Lūdzu norādiet http:// vai https://", - "login_form_err_invalid_email": "Nederīgs e-pasts", - "login_form_err_invalid_url": "Nederīgs URL", - "login_form_err_leading_whitespace": "Priekšējā baltstarpa", - "login_form_err_trailing_whitespace": "Beigu baltstarpa", - "login_form_failed_get_oauth_server_config": "Pieslēdzoties, izmantojot OAuth, radās kļūda; pārbaudiet servera URL", - "login_form_failed_get_oauth_server_disable": "OAuth līdzeklis šajā serverī nav pieejams", - "login_form_failed_login": "Radās kļūda, piesakoties, pārbaudiet servera URL, e-pastu un paroli", - "login_form_handshake_exception": "Ar serveri tika konstatēta Handshake Exception kļūda. Ja izmantojat pašparakstītu sertifikātu, tad iestatījumos iespējojiet pašparakstītu sertifikātu atbalstu.", - "login_form_password_hint": "parole", - "login_form_save_login": "Palikt pieteiktam", - "login_form_server_empty": "Ieraksties servera URL.", - "login_form_server_error": "Nevarēja izveidot savienojumu ar serveri.", - "login_password_changed_error": "Atjaunojot paroli radās kļūda", - "login_password_changed_success": "Parole veiksmīgi atjaunota", - "longitude": "Ģeogrāfiskais garums", - "look": "Izskats", - "loop_videos_description": "Iespējot, lai automātiski videoklips tiktu cikliski palaists detaļu skatītājā.", - "maintenance_action_restore": "Atjauno datubāzi", - "maintenance_description": "Immich ir pārslēgts apkopes režīmā.", - "maintenance_restore_from_backup": "Atjaunot no rezerves kopijas", - "maintenance_restore_library": "Atjaunot tavu bibliotēku", - "maintenance_restore_library_confirm": "Ja tas izskatās pareizi, turpini rezerves kopijas atjaunošanu!", - "maintenance_restore_library_description": "Atjauno datubāzi", - "maintenance_restore_library_folder_no_files": "{folder} trūkst faili!", - "maintenance_restore_library_folder_pass": "lasāms un rakstāms", - "maintenance_restore_library_folder_read_fail": "nav nolasāms", - "maintenance_restore_library_folder_write_fail": "nav rakstāms", - "maintenance_restore_library_loading": "Ielādē integritātes pārbaudes un heiristiku…", - "maintenance_task_backup": "Veido esošās datubāzes rezerves kopiju…", - "maintenance_task_migrations": "Veic datubāzes migrāciju…", - "maintenance_task_restore": "Atjauno izvēlēto rezerves kopiju…", - "maintenance_task_rollback": "Atjaunošana neizdevās, atgriežas pie atjaunošanas punkta…", - "maintenance_title": "Īslaicīgi nav pieejams", - "make": "Ražotājs", - "manage_geolocation": "Pārvaldīt atrašanās vietu", - "manage_shared_links": "Kopīgoto saišu pārvaldība", - "manage_sharing_with_partners": "Koplietošanas ar partneriem pārvaldība", - "manage_the_app_settings": "Lietotnes iestatījumu pārvaldība", - "manage_your_account": "Sava konta pārvaldība", - "manage_your_api_keys": "API atslēgu pārvaldība", - "manage_your_devices": "Pieslēgto ierīču pārvaldība", - "manage_your_oauth_connection": "OAuth savienojumu pārvaldība", - "map": "Karte", - "map_assets_in_bounds": "{count} fotoattēli", - "map_cannot_get_user_location": "Nevar iegūt lietotāja atrašanās vietu", - "map_location_dialog_yes": "Jā", - "map_location_picker_page_use_location": "Izvēlēties šo atrašanās vietu", - "map_location_service_disabled_content": "Lai tiktu rādīti jūsu pašreizējās atrašanās vietas faili, ir jāaktivizē atrašanās vietas pakalpojums. Vai vēlaties to iespējot tagad?", - "map_location_service_disabled_title": "Atrašanās vietas Pakalpojums atslēgts", - "map_marker_for_images": "Kartes marķieris attēliem, kas uzņemti {city}, {country}", - "map_marker_with_image": "Kartes marķieris ar attēlu", - "map_no_location_permission_content": "Atrašanās vietas atļauja ir nepieciešama, lai parādītu jūsu pašreizējās atrašanās vietas aktīvus. Vai vēlaties to atļaut tagad?", - "map_no_location_permission_title": "Atrašanās vietas Atļaujas liegtas", - "map_settings": "Kartes iestatījumi", - "map_settings_dark_mode": "Tumšais režīms", - "map_settings_date_range_option_day": "Pēdējās 24 stundas", - "map_settings_date_range_option_days": "Pēdējās {days} dienas", - "map_settings_date_range_option_year": "Pēdējais gads", - "map_settings_date_range_option_years": "Pēdējie {years} gadi", - "map_settings_dialog_title": "Kartes Iestatījumi", - "map_settings_include_show_archived": "Iekļaut Arhivētos", - "map_settings_include_show_partners": "Iekļaut Partnerus", - "map_settings_only_show_favorites": "Rādīt tikai izlasi", - "map_settings_theme_settings": "Kartes Dizains", - "map_zoom_to_see_photos": "Attāliniet, lai redzētu fotoattēlus", - "mark_all_as_read": "Atzīmēt visus kā lasītus", - "marked_all_as_read": "Visi atzīmēti kā lasīti", - "matches": "Atbilstības", - "media_type": "Faila veids", - "memories": "Atmiņas", - "memories_all_caught_up": "Šobrīd, tas arī viss", - "memories_check_back_tomorrow": "Atgriezies rīt, lai skatītu vairāk atmiņu", - "memories_start_over": "Sākt no jauna", - "memories_swipe_to_close": "Pavelciet uz augšu, lai aizvērtu", - "memory": "Atmiņa", - "menu": "Izvēlne", - "merge": "Apvienot", - "merge_people": "Personu apvienošana", - "merge_people_limit": "Vienlaikus var apvienot ne vairāk kā 5 sejas", - "merge_people_prompt": "Vai vēlies apvienot šīs personas? Šī darbība ir neatceļama.", - "merge_people_successfully": "Personas veiksmīgi apvienotas", - "minimize": "Minimizēt", - "minute": "Minūte", - "minutes": "Minūtes", - "mirror_horizontal": "Horizontāli", - "mirror_vertical": "Vertikāli", - "missing": "Trūkstošie", - "mobile_app": "Mobilā lietotne", - "mobile_app_download_onboarding_note": "Lejupielādē papildinošo mobilo lietotni, izmantojot šādas izvēles iespējas", - "model": "Modelis", - "month": "Mēnesis", - "monthly_title_text_date_format": "MMMM g", - "more": "Vairāk", - "move": "Pārvietot", - "move_down": "Pārvietot lejup", - "move_off_locked_folder": "Izņemt no slēgtās mapes", - "move_to": "Pārvietot uz", - "move_to_device_trash": "Pārvietot uz ierīces atkritni", - "move_to_locked_folder": "Pārvietot uz slēgto mapi", - "move_to_locked_folder_confirmation": "Šīs fotogrāfijas un video tiks izņemti no visiem albumiem un būs apskatāmi tikai no slēgtās mapes", - "move_up": "Pārvietot augšup", - "moved_to_archive": "Pārvietoja {count, plural, one {# failu} other {# failus}} uz arhīvu", - "moved_to_library": "Pārvietoja {count, plural, one {# failu} other {# failus}} uz bibliotēku", - "moved_to_trash": "Pārvietots uz atkritni", - "multiselect_grid_edit_date_time_err_read_only": "Nevar rediģēt read only aktīva(-u) datumu, notiek izlaišana", - "multiselect_grid_edit_gps_err_read_only": "Nevar rediģēt atrašanās vietu read only aktīva(-u) datumu, notiek izlaišana", - "mute_memories": "Apklusināt atmiņas", - "my_albums": "Mani albumi", - "name": "Vārds", - "name_or_nickname": "Vārds vai iesauka", - "navigate_to_time": "Pāriet uz laiku", - "network_requirement_photos_upload": "Izmantot mobilo datu pārraidi, lai dublētu fotoattēlus", - "network_requirement_videos_upload": "Izmantot mobilo datu pārraidi, lai dublētu video", - "network_requirements": "Tīkla prasības", - "network_requirements_updated": "Tīkla prasības ir mainījušās, atiestata dublēšanas rindu", - "networking_settings": "Tīkla iestatījumi", - "networking_subtitle": "Pārvaldīt servera galapunktu iestatījumus", - "never": "nekad", - "new_album": "Jauns albums", - "new_api_key": "Jauna API atslēga", - "new_password": "Jaunā parole", - "new_person": "Jauna persona", - "new_pin_code": "Jaunais PIN kods", - "new_timeline": "Jaunā laikjosla", - "new_update": "Pieejams atjauninājums", - "new_user_created": "Izveidots jauns lietotājs", - "new_version_available": "PIEEJAMA JAUNA VERSIJA", - "next": "Nākamais", - "next_memory": "Nākamā atmiņa", - "no": "Nē", - "no_albums_found": "Nav atrasts neviens albums", - "no_albums_message": "Izveido albumu, lai organizētu savas fotogrāfijas un video", - "no_albums_with_name_yet": "Izskatās, ka tev vēl nav albumu ar šādu nosaukumu.", - "no_albums_yet": "Izskatās, ka tev vēl nav neviena albuma.", - "no_archived_assets_message": "Arhivē fotoattēlus un videoklipus, lai paslēptu tos no Fotoattēli skata", - "no_assets_message": "NOKLIKŠĶINIET, LAI AUGŠUPIELĀDĒTU SAVU PIRMO FOTOATTĒLU", - "no_assets_to_show": "Nav uzrādāmo aktīvu", - "no_cast_devices_found": "Nav atrasta neviena pārraides ierīce", - "no_checksum_local": "Nav pieejama kontrolsumma - nevar iegūt lokālos failus", - "no_checksum_remote": "Nav pieejama kontrolsumma - nevar iegūt attālo failu", - "no_configuration_needed": "Konfigurācija nav nepieciešama", - "no_duplicates_found": "Dublikāti netika atrasti.", - "no_exif_info_available": "Nav pieejama exif informācija", - "no_explore_results_message": "Augšupielādē vairāk fotogrāfiju, lai iepazītu savu kolekciju.", - "no_local_assets_found": "Ar šo kontrolsummu nav atrasts neviens lokālais fails", - "no_name": "Nav nosaukuma", - "no_notifications": "Nav paziņojumu", - "no_places": "Nav atrašanās vietu", - "no_results": "Nav rezultātu", - "no_results_description": "Izmēģiniet sinonīmu vai vispārīgāku atslēgvārdu", - "not_allowed": "Nav atļauts", - "not_available": "Nav pieejams", - "not_in_any_album": "Nav nevienā albumā", - "not_selected": "Nav izvēlēts", - "note_apply_storage_label_to_previously_uploaded assets": "Piezīme: Lai piemērotu glabātuves nosaukumu iepriekš augšupielādētiem failiem, izpildiet", - "notes": "Piezīmes", - "nothing_here_yet": "Šeit vēl nekā nav", - "notification_permission_dialog_content": "Lai iespējotu paziņojumus, atveriet Iestatījumi un atlasiet Atļaut.", - "notification_permission_list_tile_content": "Piešķirt atļauju, lai iespējotu paziņojumus.", - "notification_permission_list_tile_enable_button": "Iespējot paziņojumus", - "notification_permission_list_tile_title": "Paziņojumu atļaujas", - "notification_toggle_setting_description": "Ieslēgt e-pasta paziņojumus", - "notifications": "Paziņojumi", - "notifications_setting_description": "Paziņojumu pārvaldība", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium konfigurētājs", - "obtainium_configurator_instructions": "Lūdzu, izveido API atslēgu un izvēlies variantu, lai izveidotu savu Obtainium konfigurācijas saiti.", - "official_immich_resources": "Oficiālie Immich resursi", - "offline": "Bezsaistē", - "offset": "Nobīde", - "ok": "Labi", - "onboarding": "Uzņemšana", - "onboarding_locale_description": "Izvēlies vēlamo valodu. To vēlāk var mainīt iestatījumos.", - "onboarding_server_welcome_description": "Iestatīsim šo instanci ar dažiem vispārīgiem iestatījumiem.", - "onboarding_theme_description": "Izvēlies savas instances krāsu motīvu. To vēlāk var mainīt iestatījumos.", - "onboarding_user_welcome_description": "Sāksim darbu!", - "online": "Tiešsaistē", - "only_favorites": "Tikai izlase", - "open": "Atvērt", - "open_in_map_view": "Atvērt kartes skatā", - "open_in_openstreetmap": "Atvērt OpenStreetMap", - "open_the_search_filters": "Atvērt meklēšanas filtrus", - "options": "Iestatījumi", - "or": "vai", - "organize_into_albums": "Sakārtot albumos", - "organize_into_albums_description": "Ievietot esošās fotogrāfijas albumos, izmantojot pašreizējos sinhronizācijas iestatījumus", - "organize_your_library": "Bibliotēkas organizēšana", - "original": "oriģināls", - "other": "Citi", - "other_devices": "Citas ierīces", - "other_variables": "Citi mainīgie", - "owned": "Īpašumā", - "owner": "Īpašnieks", - "page": "Lapa", - "partner": "Partneris", - "partner_can_access": "{partner} var piekļūt", - "partner_can_access_location": "Fotogrāfiju uzņemšanas vieta", - "partner_list_user_photos": "{user} fotoattēli", - "partner_list_view_all": "Apskatīt visu", - "partner_page_empty_message": "Jūsu fotogrāfijas pagaidām nav kopīgotas ar nevienu partneri.", - "partner_page_no_more_users": "Nav vairs lietotāju, kurus var pievienot", - "partner_page_partner_add_failed": "Neizdevās pievienot partneri", - "partner_page_select_partner": "Izvēlēties partneri", - "partner_page_shared_to_title": "Kopīgots uz", - "partner_page_stop_sharing_content": "{partner} vairs nevarēs piekļūt jūsu fotoattēliem.", - "partner_sharing": "Koplietošana ar partneriem", - "partners": "Partneri", - "password": "Parole", - "password_does_not_match": "Parole nesakrīt", - "path": "Ceļš", - "pattern": "Šablons", - "pause": "Pauzēt", - "pause_memories": "Pauzēt atmiņas", - "paused": "Nopauzēts", - "people": "Personas", - "people_sidebar_description": "Parādīt saiti uz personām sānu joslā", - "permission": "Atļauja", - "permission_empty": "Tava atļauja nedrīkst būt tukša", - "permission_onboarding_back": "Atpakaļ", - "permission_onboarding_continue_anyway": "Tomēr turpināt", - "permission_onboarding_get_started": "Darba sākšana", - "permission_onboarding_go_to_settings": "Doties uz iestatījumiem", - "permission_onboarding_permission_denied": "Atļauja liegta. Lai izmantotu Immich, sadaļā Iestatījumi piešķiriet fotoattēlu un video atļaujas.", - "permission_onboarding_permission_granted": "Atļauja piešķirta! Jūs esat gatavi darbam.", - "permission_onboarding_permission_limited": "Atļauja ierobežota. Lai atļautu Immich dublēšanu un varētu pārvaldīt visu galeriju kolekciju, sadaļā Iestatījumi piešķiriet fotoattēlu un video atļaujas.", - "permission_onboarding_request": "Immich nepieciešama atļauja skatīt jūsu fotoattēlus un videoklipus.", - "person": "Persona", - "person_recognized": "Persona atpazīta", - "person_selected": "Persona izvēlēta", - "photos": "Fotoattēli", - "photos_and_videos": "Fotogrāfijas un video", - "photos_from_previous_years": "Fotogrāfijas no iepriekšējiem gadiem", - "photos_only": "Tikai fotogrāfijas", - "pick_a_location": "Izvēlies atrašanās vietu", - "pick_custom_range": "Pielāgots intervāls", - "pick_date_range": "Izvēlies datumu intervālu", - "pin_verification": "PIN koda pārbaude", - "place": "Atrašanās vieta", - "places": "Vietas", - "play": "Atskaņot", - "play_memories": "Atskaņot atmiņas", - "please_auth_to_access": "Lai piekļūtu, lūdzu, autentificējieties", - "port": "Ports", - "preferences_settings_title": "Iestatījumi", - "preparing": "Sagatavo", - "preview": "Priekšskatījums", - "previous": "Iepriekšējais", - "previous_memory": "Iepriekšējā atmiņa", - "previous_or_next_day": "Dienu uz priekšu/atpakaļ", - "previous_or_next_month": "Mēnesi uz priekšu/atpakaļ", - "previous_or_next_year": "Gadu uz priekšu/atpakaļ", - "privacy": "Privātums", - "profile": "Profils", - "profile_drawer_app_logs": "Žurnāli", - "profile_drawer_client_server_up_to_date": "Klients un serveris ir atjaunināti", - "profile_drawer_github": "GitHub", - "profile_image_of_user": "{user} profila attēls", - "profile_picture_set": "Profila attēls iestatīts.", - "public_album": "Publisks albums", - "purchase_account_info": "Atbalstītājs", - "purchase_activated_subtitle": "Paldies, ka atbalstāt Immich un atvērtā koda programmatūru", - "purchase_activated_time": "Aktivizēts {date}", - "purchase_activated_title": "Tava atslēga ir sekmīgi aktivizēta", - "purchase_button_activate": "Aktivizēt", - "purchase_button_buy": "Pirkt", - "purchase_button_buy_immich": "Iegādāties Immich", - "purchase_button_never_show_again": "Nekad vairs nerādīt", - "purchase_button_reminder": "Atgādināt man pēc 30 dienām", - "purchase_button_remove_key": "Noņemt atslēgu", - "purchase_button_select": "Izvēlēties", - "purchase_failed_activation": "Neizdevās aktivizēt! Lūdzu, pārbaudi savu e-pastu, lai iegūtu pareizo produkta atslēgu!", - "purchase_individual_description_1": "Individuālam lietotājam", - "purchase_individual_description_2": "Atbalstītāja statuss", - "purchase_individual_title": "Individuāla", - "purchase_input_suggestion": "Vai tev ir produkta atslēga? Ievadi atslēgu zemāk", - "purchase_license_subtitle": "Nopērc Immich licenci, lai atbalstītu turpmāku pakalpojuma attīstību", - "purchase_lifetime_description": "Pirkums uz mūžu", - "purchase_option_title": "IEGĀDES IESPĒJAS", - "purchase_panel_info_1": "Immich veidošanai ir nepieciešams daudz laika un pūļu, un pie tā strādā pilna laika inženieri, lai padarītu to pēc iespējas labāku. Mūsu misija ir panākt, lai atvērtā koda programmatūra un ētiska uzņēmējdarbības prakse kļūtu par ilgtspējīgu ienākumu avotu izstrādātājiem un izveidotu privātumu respektējošu ekosistēmu ar reālām alternatīvām ekspluatējošiem mākoņpakalpojumiem.", - "purchase_panel_info_2": "Tā kā mēs esam apņēmušies nepievienot maksas funkcionalitāti, šis pirkums nepiešķirs jums nekādas papildu Immich funkcijas. Mēs paļaujamies uz tādiem lietotājiem kā jūs, lai atbalstītu nepārtrauktu Immich attīstību.", - "purchase_panel_title": "Atbalsti projektu", - "purchase_per_server": "Par serveri", - "purchase_per_user": "Par lietotāju", - "purchase_remove_product_key": "Noņemt produkta atslēgu", - "purchase_remove_product_key_prompt": "Vai tiešām vēlaties noņemt produkta atslēgu?", - "purchase_remove_server_product_key": "Noņemt servera produkta atslēgu", - "purchase_remove_server_product_key_prompt": "Vai tiešām vēlaties noņemt Servera produkta atslēgu?", - "purchase_server_description_1": "Visam serverim", - "purchase_server_description_2": "Atbalstītāja statuss", - "purchase_server_title": "Serveris", - "purchase_settings_server_activated": "Servera produkta atslēgu pārvalda administrators", - "queue_status": "Ierindo {count}/{total}", - "rate_asset": "Novērtēt failu", - "rating_clear": "Noņemt vērtējumu", - "rating_description": "Rādīt EXIF vērtējumu informācijas panelī", - "reaction_options": "Reakcijas iespējas", - "read_changelog": "Lasīt izmaiņu sarakstu", - "ready_for_upload": "Gatavs augšupielādei", - "recently_added_page_title": "Nesen Pievienotais", - "refresh": "Atsvaidzināt", - "refresh_faces": "Atsvaidzināt sejas", - "refresh_metadata": "Atsvaidzināt metadatus", - "refresh_thumbnails": "Atsvaidzināt sīktēlus", - "refreshed": "Atsvaidzināts", - "refreshes_every_file": "Vēlreiz nolasa esošos un jaunos failus", - "refreshing_faces": "Atsvaidzina sejas", - "refreshing_metadata": "Atsvaidzina metadatus", - "remote": "Attāli", - "remove": "Noņemt", - "remove_assets_title": "Izņemt failus?", - "remove_custom_date_range": "Novākt pielāgoto datuma intervālu", - "remove_deleted_assets": "Izņemt dzēstos failus", - "remove_from_album": "Noņemt no albuma", - "remove_from_album_action_prompt": "No albuma izņemti {count} faili", - "remove_from_favorites": "Noņemt no izlases", - "remove_from_lock_folder_action_prompt": "No slēgtās mapes izņemti {count} faili", - "remove_from_locked_folder": "Izņemt no slēgtās mapes", - "remove_memory": "Noņemt atmiņu", - "remove_photo_from_memory": "Noņemt fotogrāfiju no šīs atmiņas", - "remove_url": "Noņemt URL", - "remove_user": "Noņemt lietotāju", - "removed_api_key": "Noņēma API atslēgu: {name}", - "removed_from_archive": "Noņēma no arhīva", - "removed_from_favorites": "Noņēma no izlases", - "removed_from_favorites_count": "{count, plural, other {Izņēma #}} no izlases", - "removed_memory": "Noņēma atmiņu", - "removed_photo_from_memory": "Noņēma fotogrāfiju no atmiņas", - "rename": "Pārsaukt", - "repair": "Remonts", - "replace_with_upload": "Aizstāt ar augšupielādi", - "repository": "Repozitorijs", - "require_user_to_change_password_on_first_login": "Pieprasīt lietotājam mainīt paroli pēc pirmās pieteikšanās", - "rescan": "Pārskenēt atkārtoti", - "reset": "Atiestatīt", - "reset_password": "Atiestatīt paroli", - "reset_people_visibility": "Atiestatīt personu redzamību", - "reset_pin_code": "Atiestatīt PIN kodu", - "reset_sqlite": "Atiestatīt SQLite datubāzi", - "reset_to_default": "Atiestatīt noklusējuma iestatījumus", - "resolve_duplicates": "Atrisināt dublēšanās gadījumus", - "resolved_all_duplicates": "Visi dublikāti ir atrisināti", - "restore": "Atjaunot", - "restore_all": "Atjaunot visu", - "restore_trash_action_prompt": "{count} atjaunoti no atkritnes", - "restore_user": "Atjaunot lietotāju", - "resume": "Turpināt", - "retry_upload": "Atkārtot augšupielādi", - "review_duplicates": "Pārskatīt dublikātus", - "review_large_files": "Pārskatīt lielos failus", - "role": "Loma", - "role_editor": "Redaktors", - "role_viewer": "Skatītājs", - "save": "Saglabāt", - "save_to_gallery": "Saglabāt galerijā", - "saved": "Saglabāts", - "saved_api_key": "API atslēga saglabāta", - "saved_profile": "Profils saglabāts", - "saved_settings": "Iestatījumi saglabāti", - "say_something": "Teikt kaut ko", - "scaffold_body_error_occurred": "Radās kļūda", - "scan": "Skenēt", - "scan_all_libraries": "Skenēt visas bibliotēkas", - "scan_library": "Skenēt", - "scan_settings": "Skenēšanas iestatījumi", - "scanning": "Skenē", - "scanning_for_album": "Skenē albumu...", - "search": "Meklēt", - "search_albums": "Meklēt albumus", - "search_by_context": "Meklēt pēc konteksta", - "search_by_description": "Meklēt pēc apraksta", - "search_by_description_example": "Pārgājiens Līgatnē", - "search_by_filename": "Meklēt pēc faila nosaukuma vai paplašinājuma", - "search_by_filename_example": "piemēram, IMG_1234.JPG vai PNG", - "search_camera_make": "Meklēt pēc fotokameras ražotāja...", - "search_camera_model": "Meklēt pēc fotokameras modeļa...", - "search_city": "Meklēt pēc pilsētas...", - "search_country": "Meklēt pēc valsts...", - "search_filter_apply": "Lietot filtru", - "search_filter_camera_title": "Izvēlies fotokameras veidu", - "search_filter_date": "Datums", - "search_filter_display_option_not_in_album": "Nav albumā", - "search_filter_filename": "Meklēt pēc faila nosaukuma", - "search_filter_location": "Atrašanās vieta", - "search_filter_location_title": "Izvēlies atrašanās vietu", - "search_filter_media_type": "Multivides veids", - "search_filter_media_type_title": "Izvēlies multivides veidu", - "search_filter_star_rating": "Zvaigznīšu vērtējums", - "search_for_existing_person": "Meklēt esošu personu", - "search_no_people": "Nav personu", - "search_no_people_named": "Nav personas ar vārdu \"{name}\"", - "search_options": "Meklēšanas iespējas", - "search_page_categories": "Kategorijas", - "search_page_motion_photos": "Kustību Fotoattēli", - "search_page_no_objects": "Informācija par Objektiem nav pieejama", - "search_page_no_places": "Nav pieejama Informācija par Vietām", - "search_page_screenshots": "Ekrānuzņēmumi", - "search_page_selfies": "Selfiji", - "search_page_things": "Lietas", - "search_page_view_all_button": "Apskatīt visu", - "search_page_your_activity": "Jūsu aktivitāte", - "search_page_your_map": "Jūsu Karte", - "search_people": "Meklēt personas", - "search_rating": "Meklēt pēc vērtējuma...", - "search_result_page_new_search_hint": "Jauns Meklējums", - "search_settings": "Meklēt iestatījumos", - "search_state": "Meklēt pēc štata...", - "search_suggestion_list_smart_search_hint_1": "Viedā meklēšana pēc noklusējuma ir iespējota, lai meklētu metadatos, izmanto sintaksi ", - "search_suggestion_list_smart_search_hint_2": "m:jūsu-meklēšanas-frāze", - "search_type": "Meklēšanas veids", - "search_your_photos": "Meklēt fotoattēlos", - "searching_locales": "Meklē lokalizācijas...", - "second": "Sekunde", - "see_all_people": "Skatīt visas personas", - "select_album": "Izvēlies albumu", - "select_album_cover": "Izvēlieties albuma vāciņu", - "select_albums": "Izvēlies albumus", - "select_all_duplicates": "Atlasīt visus paturēšanai", - "select_avatar_color": "Izvēlies avatāra krāsu", - "select_face": "Izvēlies seju", - "select_from_computer": "Izvēlēties no datora", - "select_keep_all": "Atzīmēt visus paturēšanai", - "select_library_owner": "Izvēlies bibliotēkas īpašnieku", - "select_new_face": "Izvēlies jaunu seju", - "select_people": "Izvēlies personas", - "select_person": "Izvēlies personu", - "select_photos": "Fotoattēlu Izvēle", - "select_trash_all": "Atzīmēt visus dzēšanai", - "select_user_for_sharing_page_err_album": "Neizdevās izveidot albumu", - "selected": "Izvēlētie", - "selected_gps_coordinates": "Izvēlētās ģeogrāfiskās koordinātas", - "server_info_box_app_version": "Aplikācijas Versija", - "server_info_box_server_url": "Servera URL", - "server_online": "Serveris tiešsaistē", - "server_privacy": "Servera privātums", - "server_restarting_description": "Šī lapa pēc brīža tiks atjaunināta.", - "server_restarting_title": "Serveris tiek pārstartēts", - "server_stats": "Servera statistika", - "server_update_available": "Pieejams servera atjauninājums", - "server_version": "Servera versija", - "set_as_album_cover": "Iestatīt kā albuma vāciņu", - "set_as_profile_picture": "Iestatīt kā profila attēlu", - "set_date_of_birth": "Iestatīt dzimšanas datumu", - "setting_image_viewer_help": "Detaļu skatītājs vispirms ielādē mazo sīktēlu, pēc tam ielādē vidēja lieluma priekšskatījumu (ja iespējots), visbeidzot ielādē oriģinālu (ja iespējots).", - "setting_image_viewer_original_subtitle": "Iespējot sākotnējā pilnas izšķirtspējas attēla (liels!) ielādi. Atspējot, lai samazinātu datu lietojumu (gan tīklā, gan ierīces kešatmiņā).", - "setting_image_viewer_original_title": "Ielādēt oriģinālo attēlu", - "setting_image_viewer_preview_subtitle": "Iespējojiet vidējas izšķirtspējas attēla ielādēšanu. Atspējojiet vai nu tiešu oriģināla ielādi, vai izmantojiet tikai sīktēlu.", - "setting_image_viewer_preview_title": "Ielādēt priekšskatījuma attēlu", - "setting_image_viewer_title": "Attēli", - "setting_languages_apply": "Lietot", - "setting_notifications_notify_failures_grace_period": "Paziņot par fona dublēšanas kļūmēm: {duration}", - "setting_notifications_notify_hours": "{count} stundas", - "setting_notifications_notify_immediately": "nekavējoties", - "setting_notifications_notify_minutes": "{count} minūtes", - "setting_notifications_notify_never": "nekad", - "setting_notifications_notify_seconds": "{count} sekundes", - "setting_notifications_single_progress_subtitle": "Detalizēta augšupielādes progresa informācija par katru failu", - "setting_notifications_single_progress_title": "Rādīt fona dublējuma detalizēto progresu", - "setting_notifications_subtitle": "Paziņojumu preferenču pielāgošana", - "setting_notifications_total_progress_subtitle": "Kopējais augšupielādes progress (pabeigti/kopējie faili)", - "setting_notifications_total_progress_title": "Rādīt fona dublējuma kopējo progresu", - "setting_video_viewer_auto_play_subtitle": "Automātiski sākt videoklipu atskaņošanu, kad tie tiek atvērti", - "setting_video_viewer_auto_play_title": "Automātiska video atskaņošana", - "setting_video_viewer_looping_title": "Cikliski", - "setting_video_viewer_original_video_subtitle": "Straumējot video no servera, izmantot oriģinālu, pat ja ir pieejama pārkodēšana. Tas var izraisīt buferēšanu. Lokāli pieejamie video tiek atskaņoti oriģinālajā kvalitātē, neatkarīgi no šīs iestatījuma.", - "setting_video_viewer_original_video_title": "Vienmēr izmantot oriģinālo video", - "settings": "Iestatījumi", - "settings_require_restart": "Lūdzu, restartējiet Immich, lai lietotu šo iestatījumu", - "setup_pin_code": "Uzstādīt PIN kodu", - "share": "Kopīgot", - "share_add_photos": "Pievienot fotoattēlus", - "share_assets_selected": "{count} atlasīti", - "share_dialog_preparing": "Notiek sagatavošana...", - "shared": "Kopīgots", - "shared_album_activities_input_disable": "Komentāri atslēgti", - "shared_album_activity_remove_content": "Vai vēlaties šo aktivitāti dzēst?", - "shared_album_activity_remove_title": "Dzēst Aktivitāti", - "shared_album_section_people_action_error": "Kļūme pametot/noņemot no albuma", - "shared_album_section_people_action_leave": "Noņemt lietotāju no albuma", - "shared_album_section_people_action_remove_user": "Noņemt lietotāju no albuma", - "shared_album_section_people_title": "PERSONAS", - "shared_intent_upload_button_progress_text": "Augšupielādēti {current} / {total}", - "shared_link_app_bar_title": "Kopīgotas Saites", - "shared_link_clipboard_copied_massage": "Ievietots starpliktuvē", - "shared_link_clipboard_text": "Saite: {link}\nParole: {password}", - "shared_link_create_error": "Kļūda izveidojot kopīgošanas saiti", - "shared_link_edit_description_hint": "Ievadiet kopīgojuma aprakstu", - "shared_link_edit_expire_after_option_day": "1 diena", - "shared_link_edit_expire_after_option_days": "{count} dienas", - "shared_link_edit_expire_after_option_hour": "1 stunda", - "shared_link_edit_expire_after_option_hours": "{count} stundas", - "shared_link_edit_expire_after_option_minute": "1 minūte", - "shared_link_edit_expire_after_option_minutes": "{count} minūtes", - "shared_link_edit_expire_after_option_months": "{count} mēneši", - "shared_link_edit_expire_after_option_year": "{count} gads", - "shared_link_edit_password_hint": "Ierakstīt kopīgojuma paroli", - "shared_link_edit_submit_button": "Atjaunināt saiti", - "shared_link_error_server_url_fetch": "Nevarēja ienest servera URL", - "shared_link_expires_day": "Derīguma termiņš beigsies pēc {count} dienas", - "shared_link_expires_days": "Derīguma termiņš beigsies pēc {count} dienām", - "shared_link_expires_hour": "Derīguma termiņš beigsies pēc {count} stundas", - "shared_link_expires_hours": "Derīguma termiņš beigsies pēc {count} stundām", - "shared_link_expires_minute": "Derīguma termiņš beigsies pēc {count} minūtes", - "shared_link_expires_minutes": "Derīguma termiņš beidzas pēc {count} minūtēm", - "shared_link_expires_never": "Derīguma termiņš beigsies ∞", - "shared_link_expires_second": "Derīguma termiņš beigsies pēc {count} sekundes", - "shared_link_expires_seconds": "Derīguma termiņš beidzas pēc {count} sekundēm", - "shared_link_individual_shared": "Individuāli kopīgots", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Pārvaldīt Kopīgotās saites", - "shared_links": "Kopīgotās saites", - "shared_with_partner": "Kopīgots ar {partner}", - "sharing": "Kopīgošana", - "sharing_enter_password": "Lūdzu, ievadi paroli, lai apskatītu šo lapu.", - "sharing_page_album": "Kopīgotie albumi", - "sharing_page_description": "Izveidojiet koplietojamus albumus, lai kopīgotu fotoattēlus un videoklipus ar Jūsu tīkla lietotājiem.", - "sharing_page_empty_list": "TUKŠS SARAKSTS", - "sharing_sidebar_description": "Parādīt saiti uz kopīgošanu sānu joslā", - "sharing_silver_appbar_create_shared_album": "Izveidot kopīgotu albumu", - "sharing_silver_appbar_share_partner": "Dalīties ar partneri", - "show_album_options": "Rādīt albuma iespējas", - "show_albums": "Rādīt albumus", - "show_all_people": "Rādīt visas personas", - "show_and_hide_people": "Rādīt un slēpt personas", - "show_file_location": "Rādīt faila atrašanās vietu", - "show_gallery": "Rādīt galeriju", - "show_hidden_people": "Rādīt paslēptās personas", - "show_in_timeline": "Parādīt laika skalā", - "show_in_timeline_setting_description": "Rādīt šī lietotāja fotogrāfijas un video tavā laika skalā", - "show_keyboard_shortcuts": "Rādīt tastatūras saīsnes", - "show_metadata": "Rādīt metadatus", - "show_or_hide_info": "Rādīt vai slēpt informāciju", - "show_password": "Parādīt paroli", - "show_person_options": "Rādīt personas opcijas", - "show_progress_bar": "Rādīt progresa joslu", - "show_schema": "Rādīt shēmu", - "show_search_options": "Rādīt meklēšanas opcijas", - "show_shared_links": "Rādīt kopīgotās saites", - "show_slideshow_transition": "Rādīt slīdrādes pāreju", - "show_supporter_badge": "Atbalstītāja nozīmīte", - "show_supporter_badge_description": "Rādīt atbalstītāja nozīmīti", - "show_text_recognition": "Rādīt teksta atpazīšanu", - "show_text_search_menu": "Rādīt teksta meklēšanas izvēlni", - "shuffle": "Jaukta", - "sidebar": "Sānu josla", - "sidebar_display_description": "Parādīt saiti uz skatu sānu joslā", - "sign_out": "Iziet", - "sign_up": "Reģistrēties", - "size": "Izmērs", - "skip_to_content": "Pāriet uz saturu", - "skip_to_folders": "Pāriet uz mapēm", - "slideshow": "Slīdrāde", - "slideshow_repeat": "Atkārtot slīdrādi", - "slideshow_repeat_description": "Beidzoties slīdrādei, atgriezties pie tās sākuma", - "slideshow_settings": "Slīdrādes iestatījumi", - "sort_albums_by": "Kārtot albumus pēc...", - "sort_created": "Izveides datums", - "sort_items": "Vienumu skaits", - "sort_modified": "Izmaiņu datums", - "sort_newest": "Jaunākā fotogrāfija", - "sort_oldest": "Vecākā fotogrāfija", - "sort_people_by_similarity": "Sakārtot personas pēc līdzības", - "sort_recent": "Nesenākā fotogrāfija", - "sort_title": "Nosaukums", - "source": "Pirmkods", - "stack": "Apvienot kaudzē", - "stack_duplicates": "Apvienot dublikātus kaudzē", - "start": "Sākt", - "start_date": "Sākuma datums", - "start_date_before_end_date": "Sākuma datumam jābūt pirms beigu datuma", - "state": "Štats", - "status": "Statuss", - "stop_photo_sharing": "Beigt kopīgot jūsu fotogrāfijas?", - "stop_photo_sharing_description": "{partner} vairs nevarēs piekļūt tavām fotogrāfijām.", - "stop_sharing_photos_with_user": "Pārtraukt dalīties ar fotogrāfijām ar šo lietotāju", - "storage": "Vieta krātuvē", - "storage_label": "Glabātuves nosaukums", - "storage_usage": "{used} no {available} izmantoti", - "submit": "Iesniegt", - "suggestions": "Ieteikumi", - "sunrise_on_the_beach": "Saullēkts pludmalē", - "support": "Atbalsts", - "support_and_feedback": "Atbalsts un atsauksmes", - "support_third_party_description": "Tavu Immich instalāciju ir sagatavojusi trešā puse. Problēmas, ar kurām sastopies, var būt saistītas ar šo pakotni, tāpēc lūdzu vispirms ziņo par tām, izmantojot zemāk norādītās saites.", - "sync": "Sinhronizēt", - "sync_local": "Sinhronizēt lokāli", - "sync_status": "Sinhronizācijas statuss", - "sync_status_subtitle": "Skatīt un pārvaldīt sinhronizācijas sistēmu", - "text_recognition": "Teksta atpazīšana", - "theme": "Dizains", - "theme_setting_asset_list_storage_indicator_title": "Rādīt krātuves indikatoru uz attēliem režga skatā", - "theme_setting_asset_list_tiles_per_row_title": "Failu skaits rindā ({count})", - "theme_setting_colorful_interface_subtitle": "Piemērot pamatkrāsu fona virsmām.", - "theme_setting_colorful_interface_title": "Krāsaina saskarne", - "theme_setting_image_viewer_quality_subtitle": "Attēlu skatītāja detaļu kvalitātes pielāgošana", - "theme_setting_image_viewer_quality_title": "Attēlu skatītāja kvalitāte", - "theme_setting_primary_color_subtitle": "Izvēlies krāsu galvenajām darbībām un akcentiem.", - "theme_setting_primary_color_title": "Pamatkrāsa", - "theme_setting_system_primary_color_title": "Izmantot sistēmas krāsu", - "theme_setting_system_theme_switch": "Automātisks (sekot sistēmas iestatījumiem)", - "theme_setting_theme_subtitle": "Izvēlieties programmas dizaina iestatījumu", - "theme_setting_three_stage_loading_subtitle": "Trīspakāpju ielāde var palielināt ielādēšanas veiktspēju, bet izraisa ievērojami lielāku tīkla noslodzi", - "theme_setting_three_stage_loading_title": "Iespējot trīspakāpju ielādi", - "then": "Tad", - "they_will_be_merged_together": "Tās tiks apvienotas", - "third_party_resources": "Trešo pušu resursi", - "timeline": "Laika skala", - "timezone": "Laika zona", - "to_archive": "Arhivēt", - "to_change_password": "Mainīt paroli", - "to_favorite": "Pievienot izlasei", - "to_trash": "Pārvietot uz atkritni", - "toggle_settings": "Pārslēgt iestatījumus", - "toggle_theme_description": "Pārslēgt motīvu", - "total": "Kopā", - "total_usage": "Kopējais lietojums", - "trash": "Atkritne", - "trash_action_prompt": "{count} pārvietoja uz atkritni", - "trash_all": "Dzēst visu", - "trash_count": "Pārvietot uz atkritni {count, number}", - "trash_delete_asset": "Pārvietot uz atkritni/dzēst failu", - "trash_emptied": "Atkritne iztukšota", - "trash_no_results_message": "Šeit parādīsies uz atkritni pārvietotās fotogrāfijas un video.", - "trash_page_delete_all": "Dzēst Visu", - "trash_page_empty_trash_dialog_content": "Vai vēlaties iztukšot savus izmestos failus? Tie tiks neatgriezeniski izņemti no Immich", - "trash_page_info": "Atkritnes vienumi tiks neatgriezeniski dzēsti pēc {days} dienām", - "trash_page_no_assets": "Atkritnē nav aktīvu", - "trash_page_restore_all": "Atjaunot Visu", - "trash_page_select_assets_btn": "Atlasīt aktīvus", - "trash_page_title": "Atkritne ({count})", - "trashed_items_will_be_permanently_deleted_after": "Faili no atkritnes tiks neatgriezeniski dzēsti pēc {days, plural, one {# dienas} other {# dienām}}.", - "trigger_asset_uploaded": "Fails augšupielādēts", - "trigger_description": "Notikums, kas uzsāk darba plūsmu", - "trigger_person_recognized": "Persona atpazīta", - "troubleshoot": "Problēmu novēršana", - "type": "Veids", - "unable_to_change_pin_code": "Neizdevās nomainīt PIN kodu", - "unable_to_setup_pin_code": "Neizdevās uzstādīt PIN kodu", - "unarchive": "Atarhivēt", - "unfavorite": "Noņemt no izlases", - "unhide_person": "Atcelt personas slēpšanu", - "unknown": "Nezināms", - "unknown_country": "Nezināma Valsts", - "unknown_date": "Nezināms datums", - "unknown_year": "Nezināms gads", - "unlimited": "Neierobežots", - "unnamed_album": "Albums bez nosaukuma", - "unsaved_change": "Nesaglabāta izmaiņa", - "unselect_all": "Atcelt visu atlasi", - "unselect_all_duplicates": "Atlasīt visus dzēšanai", - "unstack": "At-Stekot", - "unsupported_field_type": "Nesatbalstīts lauka tips", - "untitled_workflow": "Nenosaukta darba plūsma", - "update_location_action_prompt": "Norādīt {count} izvēlēto failu atrašanās vietu kā:", - "updated_at": "Atjaunināts", - "updated_password": "Parole ir atjaunināta", - "upload": "Augšupielādēt", - "upload_dialog_info": "Vai vēlaties veikt izvēlētā(-o) aktīva(-u) dublējumu uz servera?", - "upload_dialog_title": "Augšupielādēt Aktīvu", - "upload_finished": "Augšupielāde pabeigta", - "upload_status_duplicates": "Dublikāti", - "upload_status_errors": "Kļūdas", - "upload_status_uploaded": "Augšupielādēts", - "upload_to_immich": "Augšupielādēt Immich ({count})", - "uploading": "Augšupielādē", - "uploading_media": "Augšupielādē failus", - "url": "URL", - "usage": "Lietojums", - "use_biometric": "Izmantot biometrisko autentifikāciju", - "use_current_connection": "izmantot pašreizējo savienojumu", - "use_custom_date_range": "Izmantot pielāgotu datuma intervālu", - "user": "Lietotājs", - "user_has_been_deleted": "Šis lietotājs ir dzēsts.", - "user_id": "Lietotāja ID", - "user_pin_code_settings": "PIN kods", - "user_privacy": "Lietotāju privātums", - "user_purchase_settings": "Iegādāties", - "user_purchase_settings_description": "Pirkuma pārvaldība", - "user_usage_detail": "Informācija par lietotāju lietojumu", - "user_usage_stats": "Konta izmantošanas statistika", - "user_usage_stats_description": "Skatīt konta lietojuma statistiku", - "username": "Lietotājvārds", - "users": "Lietotāji", - "utilities": "Rīki", - "validate": "Pārbaudīt", - "validation_error": "Pārbaudes kļūda", - "variables": "Mainīgie", - "version": "Versija", - "version_announcement_closing": "Tavs draugs, Alekss", - "version_announcement_message": "Sveiki! Ir pieejama jauna Immich versija. Lūdzu, veltiet laiku, lai izlasītu laidiena piezīmes un pārliecinātos, ka jūsu iestatījumi ir atjaunināti, lai novērstu jebkādu nepareizu konfigurāciju, jo īpaši, ja izmantojat WatchTower vai citu mehānismu, kas automātiski atjaunina jūsu Immich instanci.", - "version_history": "Versiju vēsture", - "version_history_item": "{version} uzstādīta {date}", - "video": "Videoklips", - "video_hover_setting_description": "Atskaņot video sīktēlu, kad peles kursors atrodas virs objekta. Pat ja funkcija ir atspējota, atskaņošanu var sākt, uzvirzot kursoru uz atskaņošanas ikonas.", - "videos": "Videoklipi", - "videos_only": "Tikai video", - "view": "Apskatīt", - "view_album": "Skatīt Albumu", - "view_all": "Apskatīt visu", - "view_all_users": "Skatīt visus lietotājus", - "view_asset_owners": "Skatīt failu īpašniekus", - "view_details": "Apskatīt informāciju", - "view_in_timeline": "Skatīt laika skalā", - "view_link": "Skatīt saiti", - "view_links": "Skatīt saites", - "view_name": "Apskatīt", - "view_next_asset": "Skatīt nākamo failu", - "view_previous_asset": "Skatīt iepriekšējo failu", - "view_qr_code": "Skatīt QR kodu", - "view_similar_photos": "Skatīt līdzīgas fotogrāfijas", - "view_stack": "Apskatīt kaudzi", - "view_user": "Apskatīt lietotāju", - "viewer_remove_from_stack": "Noņemt no Steka", - "viewer_stack_use_as_main_asset": "Izmantot kā Galveno Aktīvu", - "viewer_unstack": "At-Stekot", - "visual": "Vizuāli", - "visual_builder": "Vizuālais veidotājs", - "waiting": "Gaida", - "waiting_count": "Gaida: {count}", - "warning": "Brīdinājums", - "week": "Nedēļa", - "welcome": "Laipni lūgti", - "welcome_to_immich": "Laipni lūgti Immich", - "width": "Platums", - "wifi_name": "Wi-Fi nosaukums", - "workflow_deleted": "Darba plūsma dzēsta", - "workflow_description": "Darba plūsmas apraksts", - "workflow_info": "Darba plūsmas informācija", - "workflow_json": "Darba plūsmas JSON", - "workflow_json_help": "Labot darba plūsmas konfigurāciju JSON formātā. Izmaiņas tiks sinhronizētas ar vizuālo veidotāju.", - "workflow_name": "Darba plūsmas nosaukums", - "workflow_summary": "Darba plūsmas kopsavilkums", - "workflow_update_success": "Darba plūsma veiksmīgi izmainīta", - "workflow_updated": "Darba plūsma izmainīta", - "workflows": "Darba plūsmas", - "wrong_pin_code": "Nepareizs PIN kods", - "year": "Gads", - "years_ago": "Pirms {years, plural, one {# gada} other {# gadiem}}", - "yes": "Jā", - "your_wifi_name": "Tava Wi-Fi nosaukums", - "zero_to_clear_rating": "nospied 0, lai notīrītu faila vērtējumu", - "zoom_image": "Pietuvināt attēlu" -} +{} diff --git a/i18n/mk.json b/i18n/mk.json index b12fe7ca15..0967ef424b 100644 --- a/i18n/mk.json +++ b/i18n/mk.json @@ -1,326 +1 @@ -{ - "about": "За Immich", - "account": "Профил", - "account_settings": "Поставки за профилот", - "acknowledge": "Маркирај прочитано", - "action": "Акција", - "action_common_update": "Ажурирај", - "actions": "Акции", - "active": "Активни", - "activity": "Активност", - "activity_changed": "Активноста е {enabled, select, true {овозможена} other {неовозможена}}", - "add": "Додади", - "add_a_description": "Додади опис", - "add_a_location": "Додади локација", - "add_a_name": "Додади име", - "add_a_title": "Додади наслов", - "add_birthday": "Додади роденден", - "add_endpoint": "Додади крајна точка", - "add_exclusion_pattern": "Додади шаблон за исклучување", - "add_location": "Додади локација", - "add_more_users": "Додади уште корисници", - "add_partner": "Додади партнер", - "add_path": "Додади патека", - "add_photos": "Додади слики", - "add_tag": "Додади ознака", - "add_to": "Додади во…", - "add_to_album": "Додади во албум", - "add_to_album_bottom_sheet_added": "Додадено во {album}", - "add_to_album_bottom_sheet_already_exists": "Веќе во {album}", - "add_to_album_bottom_sheet_some_local_assets": "Некои локални ресурси не можеа да се додадат во албумот", - "add_to_album_toggle": "Промени ја селекцијата за {album}", - "add_to_albums": "Додади во албуми", - "add_to_albums_count": "Додади во албуми ({count})", - "add_to_shared_album": "Додади во споделен албум", - "add_upload_to_stack": "Додај прикаченото во куп", - "add_url": "Додади URL", - "added_to_archive": "Додадено во архива", - "added_to_favorites": "Додадено во омилени", - "added_to_favorites_count": "Додадени {count, number} во омилени", - "admin": { - "add_exclusion_pattern_description": "Додади шаблони за исклучување. Поддржано е користење на glob со *, **, и ?. За да се игнорираат сите датотеки во кој било директориум именуван \"Raw\", користи \"**/Raw/**\". За да се игнорираат сите датотеки што завршуваат со \".tif\", користи \"**/*.tif\". За да се игнорира апсолутна патека, користи \"/path/to/ignore/**\".", - "admin_user": "Административен Корисник", - "asset_offline_description": "Ова средство од екстерна библиотека веќе не е пронајдено на дискот и е преместено во ѓубре. Ако датотеката била преместена во рамките на библиотеката, проверете ја вашата временска линија за новото соодветно средство. За да го вратите ова средство, осигурајте се дека долунаведената патека може да биде пристапена од Immich и скенирајте ја библиотеката.", - "authentication_settings": "Поставки за автентикација", - "authentication_settings_description": "Управувај со лозинки, OAuth, и други поставки за автентикација", - "authentication_settings_disable_all": "Дали сте сигурни дека сакате да ги исклучите сите методи за најава? Целосно ќе биде оневозможено најавување.", - "authentication_settings_reenable": "За повторно да овозможите, искористете Сервер команда.", - "background_task_job": "Позадински задачи", - "backup_database": "Креирај резервна копија од базата на податоци", - "backup_database_enable_description": "Овозможи резервни копии од базата на податоци", - "backup_keep_last_amount": "Количина на претходни резервни копии за чување", - "backup_onboarding_1_description": "надворешна копија во облакот или на друга физичка локација.", - "backup_onboarding_2_description": "локални копии на различни уреди. Ова ги вклучува и основните фјалови и резервна копија од истите фајлови локално.", - "backup_onboarding_3_description": "сите копии од твоите податоци, вклучувајќи и оргиналните фајлови. Ова вклучува и 1 надворешна копија и 2 локални копии.", - "backup_onboarding_description": "3-2-1 стратегија за резервна копија е препорачано за да ги заштити твоите податоци. Потребно е да чуваш резервни копии од твоите прикачени фотографии/видеа како и базата за податоци на Immich за целосно решение за зачувување на резервна копија", - "backup_onboarding_footer": "Повеќе информации околу правење резервни копии за Immich, ве молам да се референцирате на документацијата", - "backup_onboarding_parts_title": "3-2-1 резервна копија вклучува:", - "backup_onboarding_title": "Резервни копии", - "backup_settings": "Поставки извезување база на податоци", - "backup_settings_description": "Управувај со поставки за извезување на базата на податоци", - "cleared_jobs": "Исчистени задачи за: {job}", - "config_set_by_file": "Конгигурацијата е моментално поставена од конфигурациска датотека", - "confirm_delete_library": "Дали сте сигурни дека сакате да ја избришете библиотеката {library}?", - "confirm_delete_library_assets": "Дали сте сигурни дека сакате да ја избришете оваа библиотека? Ова ќе {count, plural, one {избрише # содржано средство} other {ги избрише сите # содржани средства}} од Immich и нема да може да се {count, plural, one {врати} other {вратат}} назад. Датотеките ќе останат на диск.", - "confirm_email_below": "За да потврдите, внесете \"{email}\" доле", - "confirm_reprocess_all_faces": "Дали сте сигурни дека сакате да се обработат одново сите лица? Ова ќе ги избрише и сите именувани луѓе.", - "confirm_user_password_reset": "Дали сте сигурни дека сакате да се поништи лозинката на {user}?", - "confirm_user_pin_code_reset": "Дали сигурно сакаш да го смените ПИН кодот за {user}", - "create_job": "Создади задача", - "cron_expression": "Cron израз", - "cron_expression_description": "Подеси го интервалот на скенирање користејќи го cron форматот. За повеќе информации погледнете на пр. Crontab Guru", - "cron_expression_presets": "Предефинирани Cron изрази", - "disable_login": "Оневозможи најава", - "duplicate_detection_job_description": "Пушти машинско учење на средствата за да се откријат слични слики. Се потпира на Smart Search", - "face_detection": "Детекција на лице", - "force_delete_user_warning": "ПРЕДУПРЕДУВАЊЕ: Ова веднаш ќе го отстрани корисникот и сите средства. Оваа акција не може да се поништи и датотеките нема да може да се вратат назад.", - "image_format": "Формат", - "image_format_description": "WebP создава помали фајлви отколку JPEG, но е по спор при енкодирање.", - "image_fullsize_enabled": "Овозможи целосна-големина на генерирање на слика", - "image_fullsize_quality_description": "Целосна-големина на слика со квалитет од 1-100. Повисокто е подобро, но создава поголеми фајлови.", - "image_fullsize_title": "Поставки за Целосна-големина на Слика", - "image_prefer_embedded_preview": "Претпочитан вграден преглед", - "image_preview_title": "Поставки за Преглед", - "image_quality": "Квалитет", - "image_resolution": "Резолуција", - "image_settings": "Поставки за слики", - "job_concurrency": "{job} конкурентност", - "job_created": "Креирана задача", - "job_not_concurrency_safe": "Оваа задача не е конкуретно-безбедна.", - "job_settings": "Поставки за задача", - "job_settings_description": "Управувај со конкурентност на задачи", - "library_created": "Креирана библиотека: {library}", - "library_deleted": "Библиотеката е избришана", - "library_scanning": "Периодично скенирање", - "library_scanning_description": "Подеси периодично скениранје на библиотеката", - "library_scanning_enable_description": "Овозможи периодично скениранје на библиотеката", - "library_settings": "Екстерна библиотека", - "library_settings_description": "Управувај со подесувањата за надворешната библиотека", - "logging_enable_description": "Вклучи евидентирање", - "logging_settings": "Евидентирање", - "map_dark_style": "Темен стил", - "map_light_style": "Светол стил", - "map_settings": "Карта", - "metadata_extraction_job": "Извлечи метаподатоци", - "migration_job": "Миграција", - "oauth_auto_launch": "Автоматско започнување", - "oauth_auto_register": "Автоматска регистрација", - "oauth_button_text": "Текст на копче", - "oauth_settings": "OAuth", - "password_settings": "Најава со лозинка", - "sidecar_job": "Sidecar метаподатоци", - "storage_template_settings": "Шаблон за складирање", - "system_settings": "Системски поставки", - "thumbnail_generation_job": "Генерирај сликички", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_threads": "Нишки", - "transcoding_tone_mapping": "Тонско мапирање" - }, - "admin_email": "Администрациска Е-пошта", - "admin_password": "Администрациска лозинка", - "administration": "Администрација", - "advanced": "Напредно", - "albums": "Албуми", - "all": "Сите", - "all_people": "Сите луѓе", - "anti_clockwise": "Спротивно од стрелките на часовникот", - "appears_in": "Се појавува во", - "archive": "Архива", - "archive_size": "Големина на архива", - "asset_hashing": "Хеширање…", - "asset_offline": "Средството е офлајн", - "asset_skipped": "Пропуштено", - "asset_uploaded": "Прикачено", - "asset_uploading": "Прикачување…", - "assets": "Средства", - "authorized_devices": "Авторизирани уреди", - "back": "Назад", - "backup": "Резервна копија", - "backward": "Наназад", - "blurred_background": "Заматена позадина", - "build": "Верзија", - "camera": "Камера", - "camera_brand": "Марка на камера", - "camera_model": "Модел на камера", - "cancel": "Откажи", - "cancel_search": "Откажи пребарување", - "change_password": "Промени лозинка", - "city": "Град", - "clear": "Исчисти", - "clear_all": "Исчисти сѐ", - "clockwise": "Во насока на стрелките на часовникот", - "close": "Затвори", - "collapse": "Колапс", - "collapse_all": "Колапсирај сѐ", - "color": "Боја", - "comment_options": "Опции за коментар", - "confirm": "Потврди", - "confirm_password": "Потврди лозинка", - "contain": "Во рамки на прозорецот", - "context": "Контекст", - "continue": "Продолжи", - "copy_image": "Копирај слика", - "copy_link": "Копирај линк", - "country": "Држава", - "cover": "Покриј го прозорецот", - "covers": "Насловни", - "create": "Создади", - "create_album": "Создади албум", - "create_link": "Создади линк", - "created": "Создадено", - "current_device": "Тековен уред", - "dark": "Темно", - "day": "Ден", - "delete": "Избриши", - "delete_link": "Избриши линк", - "description": "Опис", - "details": "Детали", - "direction": "Насока", - "disabled": "Оневозможено", - "discord": "Дискорд", - "discover": "Откриј", - "display_options": "Опции за приказ", - "documentation": "Документација", - "done": "Готово", - "download": "Превземи", - "download_settings": "Превземање", - "downloading": "Се превземува", - "duplicates": "Дупликати", - "duration": "Времетраење", - "edit": "Уреди", - "edit_date": "Датум на уредување", - "edit_faces": "Уреди лица", - "edit_link": "Уреди линк", - "edit_location": "Уреди локација", - "edit_people": "Уреди луѓе", - "edit_user": "Уреди корисник", - "editor": "Уредувач", - "email": "Е-пошта", - "empty_trash": "Испразни го ѓубрето", - "enable": "Овозможи", - "enabled": "Овозможено", - "end_date": "Краен датум", - "error": "Грешка", - "exif": "Exif", - "expand_all": "Прошири ги сите", - "expire_after": "Да истече после", - "expired": "Истечено", - "explore": "Истражи", - "explorer": "Прегледувач", - "export": "Извези", - "extension": "Екстензија", - "external": "Екстерно", - "external_libraries": "Екстерни библиотеки", - "face_unassigned": "Недоделено", - "failed": "Неуспешно", - "favorite": "Омилено", - "favorites": "Омилени", - "features": "Функии", - "filename": "Име на датотека", - "filetype": "Тип на датотека", - "filter_people": "Филтрирај луѓе", - "folders": "Папки", - "forward": "Нанапред", - "general": "Генерално", - "get_help": "Побарај помош", - "go_back": "Врати се назад", - "hide_password": "Скриј лозинка", - "host": "Хост", - "hour": "Час", - "image": "Слика", - "in_archive": "Во архива", - "individual_share": "Индивидуално споделување", - "info": "Информации", - "jobs": "Задачи", - "keep": "Задржи", - "language": "Јазик", - "last_seen": "Последно видено", - "latitude": "Географска ширина", - "leave": "Напушти", - "level": "Ниво", - "library": "Библиотека", - "light": "Светло", - "list": "Листа", - "loading": "Вчитување", - "log_out": "Одјави се", - "login": "Најава", - "longitude": "Географска должина", - "look": "Изглед", - "make": "Марка", - "map": "Карта", - "matches": "Софпаѓања", - "media_type": "Тип на медија", - "memories": "Мемории", - "memory": "Меморија", - "menu": "Мени", - "merge": "Спој", - "minimize": "Минимизирај", - "minute": "Минута", - "missing": "Недостасувачки", - "model": "Модел", - "month": "Месец", - "more": "Уште", - "name": "Име", - "never": "Никогаш", - "new_password": "Нова лозинка", - "new_person": "Нова личност", - "next": "Следно", - "no": "Не", - "no_name": "Без име", - "no_results": "Нема резултати", - "notes": "Белешки", - "notifications": "Нотификации", - "oauth": "OAuth", - "offline": "Офлајн", - "ok": "Ок", - "onboarding": "Воведување", - "online": "Онлајн", - "options": "Опции", - "or": "или", - "original": "оригинално", - "other": "Друго", - "other_devices": "Други уреди", - "other_variables": "Други променливи", - "password": "Лозинка", - "people": "Луѓе", - "permanently_delete": "Трајни избриши", - "photos": "Слики", - "place": "Место", - "preset": "Претходно поставено", - "preview": "Преглед", - "reaction_options": "Опции за реакција", - "read_changelog": "Прочитај дневник на промени", - "refresh": "Освежи", - "refreshed": "Освежено", - "remove": "Отстрани", - "repair": "Поправи", - "require_password": "Потребно лозинка", - "reset": "Ресетирај", - "restore": "Поврати", - "role": "Улога", - "save": "Зачувај", - "search": "Пребарај", - "second": "Секунда", - "selected": "Избрано", - "settings": "Поставки", - "share": "Сподели", - "sharing": "Споделување", - "slideshow": "Слајдшоу", - "state": "Регион", - "suggestions": "Предлози", - "sync": "Синхронизација", - "template": "Шаблон", - "to_archive": "Архива", - "to_favorite": "Додади во омилени", - "trash": "Ѓубре", - "unarchive": "Извади од архива", - "unfavorite": "Извади од омилени", - "unknown": "Непознато", - "users": "Korisnici", - "utilities": "Алатки", - "variables": "Променливи", - "video": "Видео", - "waiting": "Во исчекување", - "week": "Недела", - "year": "Година" -} +{} diff --git a/i18n/ml.json b/i18n/ml.json index 7fc4475bc5..0967ef424b 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -1,2212 +1 @@ -{ - "about": "ഈ ആപ്പിനെ കുറിച്ച്", - "account": "അക്കൗണ്ട്", - "account_settings": "അക്കൗണ്ട് ക്രമീകരണങ്ങൾ", - "acknowledge": "അംഗീകരിക്കുക", - "action": "പ്രവർത്തനം", - "action_common_update": "അപ്ഡേറ്റ് ചെയ്യുക", - "action_description": "തിരഞ്ഞെടുത്ത വസ്തുക്കളിൽ നടപ്പിലാക്കേണ്ട പ്രവർത്തനങ്ങൾ", - "actions": "പ്രവർത്തികൾ", - "active": "സജീവം", - "active_count": "സജീവമായത്: {count}", - "activity": "പ്രവർത്തനം", - "activity_changed": "പ്രവർത്തനം {enabled, select, true {പ്രവർത്തനക്ഷമമാക്കി} other {നിർജ്ജീവമാക്കി}}", - "add": "ചേർക്കുക", - "add_a_description": "വിവരണം ചേർക്കുക", - "add_a_location": "സ്ഥാനം ചേർക്കുക", - "add_a_name": "പേര് ചേർക്കുക", - "add_a_title": "ശീർഷകം ചേർക്കുക", - "add_action": "പ്രവർത്തനം ചേർക്കുക", - "add_action_description": "നടപ്പിലാക്കേണ്ട പ്രവർത്തനം ചേർക്കാൻ ഇവിടെ ക്ലിക്ക് ചെയ്യുക", - "add_assets": "വസ്തുക്കൾ ചേർക്കുക", - "add_birthday": "ജന്മദിനം ചേർക്കുക", - "add_endpoint": "എൻഡ്‌പോയിന്റ് ചേർക്കുക", - "add_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ ചേർക്കുക", - "add_filter": "ഫിൽറ്റർ ചേർക്കുക", - "add_filter_description": "ഒരു ഫിൽട്ടർ ചേർക്കാൻ ക്ലിക്ക് ചെയ്യുക", - "add_location": "സ്ഥാനം ചേർക്കുക", - "add_more_users": "കൂടുതൽ ഉപയോക്താക്കളെ ചേർക്കുക", - "add_partner": "പങ്കാളിയെ ചേർക്കുക", - "add_path": "പാത്ത് ചേർക്കുക", - "add_photos": "ഫോട്ടോകൾ ചേർക്കുക", - "add_tag": "ടാഗ് ചേർക്കുക", - "add_to": "...ലേക്ക് ചേർക്കുക", - "add_to_album": "ആൽബത്തിലേക്ക് ചേർക്കുക", - "add_to_album_bottom_sheet_added": "{album} എന്നതിലേക്ക് ചേർത്തു", - "add_to_album_bottom_sheet_already_exists": "{album}-ൽ ഇതിനകം തന്നെയുണ്ട്", - "add_to_album_bottom_sheet_some_local_assets": "ചില പ്രാദേശിക അസറ്റുകൾ ആൽബത്തിലേക്ക് ചേർക്കാൻ കഴിഞ്ഞില്ല", - "add_to_album_toggle": "{album}-നുള്ള തിരഞ്ഞെടുപ്പ് ടോഗിൾ ചെയ്യുക", - "add_to_albums": "ആൽബങ്ങളിലേക്ക് ചേർക്കുക", - "add_to_albums_count": "ആൽബങ്ങളിലേക്ക് ചേർക്കുക ({count})", - "add_to_bottom_bar": "ഇതിലേക്ക് ചേർക്കുക", - "add_to_shared_album": "പങ്കിട്ട ആൽബത്തിലേക്ക് ചേർക്കുക", - "add_upload_to_stack": "അപ്‌ലോഡ് സ്റ്റാക്കിലേക്ക് ചേർക്കുക", - "add_url": "URL ചേർക്കുക", - "added_to_archive": "ആർക്കൈവിലേക്ക് ചേർത്തു", - "added_to_favorites": "പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു", - "added_to_favorites_count": "{count, number} എണ്ണം പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു", - "admin": { - "add_exclusion_pattern_description": "ഒഴിവാക്കൽ പാറ്റേണുകൾ ചേർക്കുക. *, **, ? എന്നിവ ഉപയോഗിച്ചുള്ള ഗ്ലോബിംഗ് പിന്തുണയ്ക്കുന്നു. \"Raw\" എന്ന് പേരുള്ള ഏതെങ്കിലും ഡയറക്ടറിയിലെ എല്ലാ ഫയലുകളും ഒഴിവാക്കാൻ, \"**/Raw/**\" ഉപയോഗിക്കുക. \".tif\"-ൽ അവസാനിക്കുന്ന എല്ലാ ഫയലുകളും ഒഴിവാക്കാൻ, \"**/*.tif\" ഉപയോഗിക്കുക. ഒരു കേവല പാത്ത് ഒഴിവാക്കാൻ, \"/path/to/ignore/**\" ഉപയോഗിക്കുക.", - "admin_user": "അഡ്മിൻ ഉപയോക്താവ്", - "asset_offline_description": "ഈ എക്സ്റ്റേണൽ ലൈബ്രറി അസറ്റ് ഇപ്പോൾ ഡിസ്കിൽ ലഭ്യമല്ല, അത് ട്രാഷിലേക്ക് മാറ്റിയിരിക്കുന്നു. ഫയൽ ലൈബ്രറിക്കുള്ളിൽ നീക്കിയിട്ടുണ്ടെങ്കിൽ, പുതിയ അനുബന്ധ അസറ്റിനായി നിങ്ങളുടെ ടൈംലൈൻ പരിശോധിക്കുക. ഈ അസറ്റ് പുനഃസ്ഥാപിക്കാൻ, താഴെയുള്ള ഫയൽ പാത Immich-ന് ആക്സസ് ചെയ്യാൻ കഴിയുമെന്ന് ഉറപ്പാക്കുകയും ലൈബ്രറി സ്കാൻ ചെയ്യുകയും ചെയ്യുക.", - "authentication_settings": "പ്രാമാണീകരണ ക്രമീകരണങ്ങൾ", - "authentication_settings_description": "പാസ്‌വേഡ്, OAuth, മറ്റ് പ്രാമാണീകരണ ക്രമീകരണങ്ങൾ എന്നിവ കൈകാര്യം ചെയ്യുക", - "authentication_settings_disable_all": "എല്ലാ ലോഗിൻ രീതികളും പ്രവർത്തനരഹിതമാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ലോഗിൻ പൂർണ്ണമായും പ്രവർത്തനരഹിതമാകും.", - "authentication_settings_reenable": "വീണ്ടും പ്രവർത്തനക്ഷമമാക്കാൻ, ഒരു സെർവർ കമാൻഡ് ഉപയോഗിക്കുക.", - "background_task_job": "പശ്ചാത്തല ജോലികൾ", - "backup_database": "ഡാറ്റാബേസ് ഡംപ് ഉണ്ടാക്കുക", - "backup_database_enable_description": "ഡാറ്റാബേസ് ഡംപുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "backup_keep_last_amount": "സൂക്ഷിക്കേണ്ട പഴയ ഡംപുകളുടെ എണ്ണം", - "backup_onboarding_1_description": "ക്ലൗഡിലോ മറ്റൊരു ഭൗതിക സ്ഥാനത്തോ ഉള്ള ഓഫ്‌സൈറ്റ് പകർപ്പ്.", - "backup_onboarding_2_description": "വിവിധ ഉപകരണങ്ങളിലെ പ്രാദേശിക പകർപ്പുകൾ. ഇതിൽ പ്രധാന ഫയലുകളും ആ ഫയലുകളുടെ പ്രാദേശിക ബാക്കപ്പും ഉൾപ്പെടുന്നു.", - "backup_onboarding_3_description": "യഥാർത്ഥ ഫയലുകൾ ഉൾപ്പെടെ നിങ്ങളുടെ ഡാറ്റയുടെ ആകെ പകർപ്പുകൾ. ഇതിൽ 1 ഓഫ്‌സൈറ്റ് പകർപ്പും 2 പ്രാദേശിക പകർപ്പുകളും ഉൾപ്പെടുന്നു.", - "backup_onboarding_description": "നിങ്ങളുടെ ഡാറ്റ പരിരക്ഷിക്കാൻ 3-2-1 ബാക്കപ്പ് തന്ത്രം ശുപാർശ ചെയ്യുന്നു. സമഗ്രമായ ഒരു ബാക്കപ്പ് പരിഹാരത്തിനായി, നിങ്ങൾ അപ്‌ലോഡ് ചെയ്ത ഫോട്ടോകളുടെ/വീഡിയോകളുടെ പകർപ്പുകളും Immich ഡാറ്റാബേസും സൂക്ഷിക്കേണ്ടതാണ്.", - "backup_onboarding_footer": "Immich ബാക്കപ്പ് ചെയ്യുന്നതിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്കായി, ദയവായി ഡോക്യുമെന്റേഷൻ പരിശോധിക്കുക.", - "backup_onboarding_parts_title": "ഒരു 3-2-1 ബാക്കപ്പിൽ ഉൾപ്പെടുന്നവ:", - "backup_onboarding_title": "ബാക്കപ്പുകൾ", - "backup_settings": "ഡാറ്റാബേസ് ഡംപ് ക്രമീകരണങ്ങൾ", - "backup_settings_description": "ഡാറ്റാബേസ് ഡംപ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക.", - "cleared_jobs": "{job}-നുള്ള ജോലികൾ ക്ലിയർ ചെയ്തു", - "config_set_by_file": "കോൺഫിഗറേഷൻ നിലവിൽ ഒരു കോൺഫിഗറേഷൻ ഫയൽ വഴിയാണ് സജ്ജീകരിച്ചിരിക്കുന്നത്", - "confirm_delete_library": "{library} ലൈബ്രറി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "confirm_delete_library_assets": "ഈ ലൈബ്രറി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് Immich-ൽ നിന്ന് {count, plural, one {അതിലുള്ള ഒരു അസറ്റ്} other {അതിലുള്ള എല്ലാ # അസറ്റുകളും}} ഇല്ലാതാക്കും, ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല. ഫയലുകൾ ഡിസ്കിൽ തന്നെ തുടരും.", - "confirm_email_below": "സ്ഥിരീകരിക്കുന്നതിന്, താഴെ \"{email}\" എന്ന് ടൈപ്പ് ചെയ്യുക", - "confirm_reprocess_all_faces": "എല്ലാ മുഖങ്ങളും വീണ്ടും പ്രോസസ്സ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് പേരുള്ള ആളുകളെയും നീക്കം ചെയ്യും.", - "confirm_user_password_reset": "{user}-ന്റെ പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "confirm_user_pin_code_reset": "{user}-ന്റെ പിൻ കോഡ് റീസെറ്റ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "create_job": "ജോലി ഉണ്ടാക്കുക", - "cron_expression": "ക്രോൺ എക്സ്പ്രഷൻ", - "cron_expression_description": "ക്രോൺ ഫോർമാറ്റ് ഉപയോഗിച്ച് സ്കാനിംഗ് ഇടവേള സജ്ജീകരിക്കുക. കൂടുതൽ വിവരങ്ങൾക്കായി ദയവായി ക്രോൺടാബ് ഗുരു പോലുള്ളവ പരിശോധിക്കുക", - "cron_expression_presets": "ക്രോൺ എക്സ്പ്രഷൻ പ്രീസെറ്റുകൾ", - "disable_login": "ലോഗിൻ പ്രവർത്തനരഹിതമാക്കുക", - "duplicate_detection_job_description": "സമാന ചിത്രങ്ങൾ കണ്ടെത്താൻ അസറ്റുകളിൽ മെഷീൻ ലേണിംഗ് പ്രവർത്തിപ്പിക്കുക. ഇത് സ്മാർട്ട് സെർച്ചിനെ ആശ്രയിച്ചിരിക്കുന്നു", - "exclusion_pattern_description": "നിങ്ങളുടെ ലൈബ്രറി സ്കാൻ ചെയ്യുമ്പോൾ ഫയലുകളും ഫോൾഡറുകളും ഒഴിവാക്കാൻ എക്സ്ക്ലൂഷൻ പാറ്റേണുകൾ നിങ്ങളെ സഹായിക്കുന്നു. നിങ്ങൾ ഇമ്പോർട്ട് ചെയ്യാൻ ആഗ്രഹിക്കാത്ത ഫയലുകൾ അടങ്ങിയ ഫോൾഡറുകൾ (ഉദാഹരണത്തിന് RAW ഫയലുകൾ) ഉണ്ടെങ്കിൽ ഇത് ഉപയോഗപ്രദമാണ്.", - "face_detection": "മുഖം തിരിച്ചറിയൽ", - "face_detection_description": "മെഷീൻ ലേണിംഗ് ഉപയോഗിച്ച് അസറ്റുകളിലെ മുഖങ്ങൾ കണ്ടെത്തുക. വീഡിയോകൾക്കായി, തംബ്നെയിൽ മാത്രമേ പരിഗണിക്കൂ. \"റിഫ്രഷ്\" എല്ലാ അസറ്റുകളും വീണ്ടും പ്രോസസ്സ് ചെയ്യുന്നു. \"റീസെറ്റ്\" നിലവിലുള്ള എല്ലാ മുഖ ഡാറ്റയും നീക്കംചെയ്യുന്നു. \"മിസ്സിംഗ്\" ഇതുവരെ പ്രോസസ്സ് ചെയ്യാത്ത അസറ്റുകളെ ക്യൂവിലാക്കുന്നു. മുഖം തിരിച്ചറിയൽ പൂർത്തിയായ ശേഷം, കണ്ടെത്തിയ മുഖങ്ങൾ ഫേഷ്യൽ റെക്കഗ്നിഷനായി ക്യൂ ചെയ്യപ്പെടും, അവയെ നിലവിലുള്ളതോ പുതിയതോ ആയ ആളുകളായി തരംതിരിക്കും.", - "facial_recognition_job_description": "കണ്ടെത്തിയ മുഖങ്ങളെ ആളുകളായി ഗ്രൂപ്പ് ചെയ്യുക. മുഖം കണ്ടെത്തൽ പൂർത്തിയായതിന് ശേഷമാണ് ഈ ഘട്ടം പ്രവർത്തിക്കുന്നത്. \"റീസെറ്റ്\" എല്ലാ മുഖങ്ങളെയും വീണ്ടും ക്ലസ്റ്റർ ചെയ്യുന്നു. \"മിസ്സിംഗ്\" ഒരു വ്യക്തിയെയും അസൈൻ ചെയ്യാത്ത മുഖങ്ങളെ ക്യൂവിലാക്കുന്നു.", - "failed_job_command": "{job} എന്ന ജോലിക്കുള്ള കമാൻഡ് {command} പരാജയപ്പെട്ടു", - "force_delete_user_warning": "മുന്നറിയിപ്പ്: ഇത് ഉപയോക്താവിനെയും എല്ലാ അസറ്റുകളെയും ഉടനടി നീക്കം ചെയ്യും. ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല, ഫയലുകൾ വീണ്ടെടുക്കാനും സാധിക്കില്ല.", - "image_format": "ഫോർമാറ്റ്", - "image_format_description": "WebP, JPEG-നെക്കാൾ ചെറിയ ഫയലുകൾ നിർമ്മിക്കുന്നു, പക്ഷേ എൻകോഡ് ചെയ്യാൻ വേഗത കുറവാണ്.", - "image_fullsize_description": "മെറ്റാഡാറ്റ നീക്കംചെയ്ത പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രം, സൂം ചെയ്യുമ്പോൾ ഉപയോഗിക്കുന്നു", - "image_fullsize_enabled": "പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്ര നിർമ്മാണം പ്രവർത്തനക്ഷമമാക്കുക", - "image_fullsize_enabled_description": "വെബ്-ഫ്രണ്ട്ലി അല്ലാത്ത ഫോർമാറ്റുകൾക്കായി പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രം നിർമ്മിക്കുക. \"എംബഡഡ് പ്രിവ്യൂവിന് മുൻഗണന നൽകുക\" പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, എംബഡഡ് പ്രിവ്യൂകൾ പരിവർത്തനം കൂടാതെ നേരിട്ട് ഉപയോഗിക്കുന്നു. JPEG പോലുള്ള വെബ്-ഫ്രണ്ട്ലി ഫോർമാറ്റുകളെ ഇത് ബാധിക്കില്ല.", - "image_fullsize_quality_description": "പൂർണ്ണ വലുപ്പത്തിലുള്ള ചിത്രത്തിന്റെ ഗുണമേന്മ 1-100 വരെ. ഉയർന്ന മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുന്നു.", - "image_fullsize_title": "പൂർണ്ണ വലുപ്പ ചിത്ര ക്രമീകരണങ്ങൾ", - "image_prefer_embedded_preview": "എംബഡഡ് പ്രിവ്യൂവിന് മുൻഗണന നൽകുക", - "image_prefer_embedded_preview_setting_description": "ലഭ്യമാകുമ്പോൾ, റോ (RAW) ഫോട്ടോകളിലെ എംബഡഡ് പ്രിവ്യൂകൾ ഇമേജ് പ്രോസസ്സിംഗിനുള്ള ഇൻപുട്ടായി ഉപയോഗിക്കുക. ഇത് ചില ചിത്രങ്ങൾക്ക് കൂടുതൽ കൃത്യമായ നിറങ്ങൾ നൽകിയേക്കാം, പക്ഷേ പ്രിവ്യൂവിൻ്റെ ഗുണനിലവാരം ക്യാമറയെ ആശ്രയിച്ചിരിക്കും, കൂടാതെ ചിത്രത്തിൽ കൂടുതൽ കംപ്രഷൻ ആർട്ടിഫാക്റ്റുകൾ ഉണ്ടാകാം.", - "image_prefer_wide_gamut": "വൈഡ് ഗാമറ്റിന് മുൻഗണന നൽകുക", - "image_prefer_wide_gamut_setting_description": "തംബ്നെയിലുകൾക്കായി ഡിസ്പ്ലേ P3 ഉപയോഗിക്കുക. ഇത് വൈഡ് കളർസ്പേസുകളുള്ള ചിത്രങ്ങളുടെ മിഴിവ് നന്നായി സംരക്ഷിക്കുന്നു, പക്ഷേ പഴയ ബ്രൗസർ പതിപ്പുള്ള പഴയ ഉപകരണങ്ങളിൽ ചിത്രങ്ങൾ വ്യത്യസ്തമായി കാണപ്പെട്ടേക്കാം. കളർ ഷിഫ്റ്റുകൾ ഒഴിവാക്കാൻ sRGB ചിത്രങ്ങൾ sRGB ആയി തന്നെ നിലനിർത്തുന്നു.", - "image_preview_description": "മെറ്റാഡാറ്റ നീക്കംചെയ്ത ഇടത്തരം വലുപ്പമുള്ള ചിത്രം, ഒരൊറ്റ അസറ്റ് കാണുമ്പോഴും മെഷീൻ ലേണിംഗിനും ഉപയോഗിക്കുന്നു", - "image_preview_quality_description": "പ്രിവ്യൂ ഗുണമേന്മ 1-100 വരെ. ഉയർന്ന മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുകയും ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും. കുറഞ്ഞ മൂല്യം സജ്ജീകരിക്കുന്നത് മെഷീൻ ലേണിംഗ് ഗുണമേന്മയെ ബാധിച്ചേക്കാം.", - "image_preview_title": "പ്രിവ്യൂ ക്രമീകരണങ്ങൾ", - "image_quality": "ഗുണമേന്മ", - "image_resolution": "റെസല്യൂഷൻ", - "image_resolution_description": "ഉയർന്ന റെസല്യൂഷനുകൾക്ക് കൂടുതൽ വിശദാംശങ്ങൾ സംരക്ഷിക്കാൻ കഴിയും, പക്ഷേ എൻകോഡ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കും, വലിയ ഫയൽ വലുപ്പമുണ്ടാകും, കൂടാതെ ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും.", - "image_settings": "ചിത്ര ക്രമീകരണങ്ങൾ", - "image_settings_description": "നിർമ്മിച്ച ചിത്രങ്ങളുടെ ഗുണമേന്മയും റെസല്യൂഷനും കൈകാര്യം ചെയ്യുക", - "image_thumbnail_description": "മെറ്റാഡാറ്റ നീക്കംചെയ്ത ചെറിയ തംബ്നെയിൽ, പ്രധാന ടൈംലൈൻ പോലുള്ള ഫോട്ടോകളുടെ ഗ്രൂപ്പുകൾ കാണുമ്പോൾ ഉപയോഗിക്കുന്നു", - "image_thumbnail_quality_description": "തംബ്നെയിലിന്റെ ഗുണമേന്മ 1-100 വരെ. ഉയർന്ന മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുകയും ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും.", - "image_thumbnail_title": "തംബ്നെയിൽ ക്രമീകരണങ്ങൾ", - "job_concurrency": "{job} കോൺകറൻസി", - "job_created": "ജോലി സൃഷ്ടിച്ചു", - "job_not_concurrency_safe": "ഈ ജോലി കോൺകറൻസി-സേഫ് അല്ല.", - "job_settings": "ജോലി ക്രമീകരണങ്ങൾ", - "job_settings_description": "ജോലി കോൺകറൻസി കൈകാര്യം ചെയ്യുക", - "jobs_delayed": "{jobCount, plural, one {# ജോലി വൈകി} other {# ജോലികൾ വൈകി}}", - "jobs_failed": "{jobCount, plural, one {# ജോലി പരാജയപ്പെട്ടു} other {# ജോലികൾ പരാജയപ്പെട്ടു}}", - "library_created": "{library} എന്ന ലൈബ്രറി സൃഷ്ടിച്ചു", - "library_deleted": "ലൈബ്രറി ഇല്ലാതാക്കി", - "library_details": "ലൈബ്രറി വിവരങ്ങൾ", - "library_folder_description": "ഇമ്പോർട്ടുചെയ്യാനുള്ള ഫോൾഡർ വ്യക്തമാക്കുക. ഈ ഫോൾഡറും സബ് ഫോൾഡറുകളും ചിത്രങ്ങൾക്കും വീഡിയോകൾക്കുമായി സ്കാൻ ചെയ്യും.", - "library_remove_exclusion_pattern_prompt": "ഈ ഒഴിവാക്കൽ പാറ്റേൺ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "library_remove_folder_prompt": "ഈ ഇമ്പോർട്ട് ഫോൾഡർ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "library_scanning": "ആനുകാലിക സ്കാനിംഗ്", - "library_scanning_description": "ആനുകാലിക ലൈബ്രറി സ്കാനിംഗ് കോൺഫിഗർ ചെയ്യുക", - "library_scanning_enable_description": "ആനുകാലിക ലൈബ്രറി സ്കാനിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", - "library_settings": "എക്സ്റ്റേണൽ ലൈബ്രറി", - "library_settings_description": "എക്സ്റ്റേണൽ ലൈബ്രറി ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "library_tasks_description": "പുതിയതോ മാറ്റം വരുത്തിയതോ ആയ അസറ്റുകൾക്കായി എക്സ്റ്റേണൽ ലൈബ്രറികൾ സ്കാൻ ചെയ്യുക", - "library_updated": "ലൈബ്രറി അപ്‌ഡേറ്റ് ചെയ്തു", - "library_watching_enable_description": "ഫയൽ മാറ്റങ്ങൾക്കായി എക്സ്റ്റേണൽ ലൈബ്രറികൾ നിരീക്ഷിക്കുക", - "library_watching_settings": "ലൈബ്രറി നിരീക്ഷിക്കൽ [പരീക്ഷണാടിസ്ഥാനത്തിൽ]", - "library_watching_settings_description": "മാറ്റം വന്ന ഫയലുകൾക്കായി യാന്ത്രികമായി നിരീക്ഷിക്കുക", - "logging_enable_description": "ലോഗിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", - "logging_level_description": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, ഏത് ലോഗ് ലെവൽ ഉപയോഗിക്കണം.", - "logging_settings": "ലോഗിംഗ്", - "machine_learning_availability_checks": "ലഭ്യത പരിശോധനകൾ", - "machine_learning_availability_checks_description": "ലഭ്യമായ മെഷീൻ ലേണിംഗ് സെർവറുകൾ യാന്ത്രികമായി കണ്ടെത്തുകയും മുൻഗണന നൽകുകയും ചെയ്യുക", - "machine_learning_availability_checks_enabled": "ലഭ്യത പരിശോധനകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "machine_learning_availability_checks_interval": "പരിശോധന ഇടവേള", - "machine_learning_availability_checks_interval_description": "ലഭ്യത പരിശോധനകൾക്കിടയിലുള്ള ഇടവേള (മില്ലിസെക്കൻഡിൽ)", - "machine_learning_availability_checks_timeout": "അഭ്യർത്ഥനയുടെ സമയപരിധി", - "machine_learning_availability_checks_timeout_description": "ലഭ്യത പരിശോധനകൾക്കുള്ള സമയപരിധി (മില്ലിസെക്കൻഡിൽ)", - "machine_learning_clip_model": "CLIP മോഡൽ", - "machine_learning_clip_model_description": "ഇവിടെ ലിസ്റ്റ് ചെയ്തിട്ടുള്ള ഒരു CLIP മോഡലിന്റെ പേര്. ഒരു മോഡൽ മാറ്റുമ്പോൾ എല്ലാ ചിത്രങ്ങൾക്കുമായി 'സ്മാർട്ട് സെർച്ച്' ജോലി വീണ്ടും പ്രവർത്തിപ്പിക്കേണ്ടതുണ്ടെന്ന് ഓർമ്മിക്കുക.", - "machine_learning_duplicate_detection": "ഡ്യൂപ്ലിക്കേറ്റ് കണ്ടെത്തൽ", - "machine_learning_duplicate_detection_enabled": "ഡ്യൂപ്ലിക്കേറ്റ് കണ്ടെത്തൽ പ്രവർത്തനക്ഷമമാക്കുക", - "machine_learning_duplicate_detection_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാലും, തികച്ചും സമാനമായ അസറ്റുകളിലെ ഡ്യൂപ്ലിക്കേറ്റുകൾ ഒഴിവാക്കപ്പെടും.", - "machine_learning_duplicate_detection_setting_description": "സമാനമായ ഡ്യൂപ്ലിക്കേറ്റുകൾ കണ്ടെത്താൻ CLIP എംബെഡ്ഡിംഗ്‌സ് ഉപയോഗിക്കുക", - "machine_learning_enabled": "മെഷീൻ ലേണിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", - "machine_learning_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, താഴെയുള്ള ക്രമീകരണങ്ങൾ പരിഗണിക്കാതെ എല്ലാ ML ഫീച്ചറുകളും പ്രവർത്തനരഹിതമാകും.", - "machine_learning_facial_recognition": "മുഖം തിരിച്ചറിയൽ", - "machine_learning_facial_recognition_description": "ചിത്രങ്ങളിലെ മുഖങ്ങൾ കണ്ടെത്തുക, തിരിച്ചറിയുക, ഗ്രൂപ്പ് ചെയ്യുക", - "machine_learning_facial_recognition_model": "മുഖം തിരിച്ചറിയൽ മോഡൽ", - "machine_learning_facial_recognition_model_description": "മോഡലുകൾ വലുപ്പത്തിന്റെ അവരോഹണ ക്രമത്തിലാണ് ലിസ്റ്റ് ചെയ്തിരിക്കുന്നത്. വലിയ മോഡലുകൾ വേഗത കുറഞ്ഞതും കൂടുതൽ മെമ്മറി ഉപയോഗിക്കുന്നവയുമാണ്, പക്ഷേ മികച്ച ഫലങ്ങൾ നൽകുന്നു. ഒരു മോഡൽ മാറ്റുമ്പോൾ എല്ലാ ചിത്രങ്ങൾക്കുമായി 'മുഖം കണ്ടെത്തൽ' ജോലി വീണ്ടും പ്രവർത്തിപ്പിക്കേണ്ടതുണ്ടെന്ന് ഓർമ്മിക്കുക.", - "machine_learning_facial_recognition_setting": "മുഖം തിരിച്ചറിയൽ പ്രവർത്തനക്ഷമമാക്കുക", - "machine_learning_facial_recognition_setting_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, മുഖം തിരിച്ചറിയലിനായി ചിത്രങ്ങൾ എൻകോഡ് ചെയ്യപ്പെടില്ല, കൂടാതെ എക്സ്പ്ലോർ പേജിലെ 'ആളുകൾ' വിഭാഗത്തിൽ അവ ദൃശ്യമാകുകയുമില്ല.", - "machine_learning_max_detection_distance": "പരമാവധി കണ്ടെത്തൽ ദൂരം", - "machine_learning_max_detection_distance_description": "രണ്ട് ചിത്രങ്ങളെ ഡ്യൂപ്ലിക്കേറ്റുകളായി കണക്കാക്കുന്നതിനുള്ള പരമാവധി ദൂരം, 0.001-0.1 വരെ. ഉയർന്ന മൂല്യങ്ങൾ കൂടുതൽ ഡ്യൂപ്ലിക്കേറ്റുകളെ കണ്ടെത്തും, പക്ഷേ തെറ്റായ ഫലങ്ങളിലേക്ക് നയിച്ചേക്കാം.", - "machine_learning_max_recognition_distance": "പരമാവധി തിരിച്ചറിയൽ ദൂരം", - "machine_learning_max_recognition_distance_description": "രണ്ട് മുഖങ്ങളെ ഒരേ വ്യക്തിയായി കണക്കാക്കുന്നതിനുള്ള പരമാവധി ദൂരം, 0-2 വരെ. ഇത് കുറയ്ക്കുന്നത് രണ്ട് പേരെ ഒരേ വ്യക്തിയായി ലേബൽ ചെയ്യുന്നത് തടയാൻ സഹായിക്കും, അതേസമയം ഇത് കൂട്ടുന്നത് ഒരേ വ്യക്തിയെ രണ്ട് വ്യത്യസ്ത ആളുകളായി ലേബൽ ചെയ്യുന്നത് തടയാനും സഹായിക്കും. ഒരാളെ രണ്ടായി വിഭജിക്കുന്നതിനേക്കാൾ എളുപ്പമാണ് രണ്ടുപേരെ ഒന്നാക്കുന്നത് എന്നത് ശ്രദ്ധിക്കുക, അതിനാൽ സാധ്യമാകുമ്പോൾ കുറഞ്ഞ പരിധി തിരഞ്ഞെടുക്കുന്നതാണ് ഉചിതം.", - "machine_learning_min_detection_score": "കുറഞ്ഞ കണ്ടെത്തൽ സ്കോർ", - "machine_learning_min_detection_score_description": "ഒരു മുഖം കണ്ടെത്തുന്നതിനുള്ള ഏറ്റവും കുറഞ്ഞ കോൺഫിഡൻസ് സ്കോർ 0-1 വരെ. കുറഞ്ഞ മൂല്യങ്ങൾ കൂടുതൽ മുഖങ്ങളെ കണ്ടെത്തും, പക്ഷേ തെറ്റായ ഫലങ്ങളിലേക്ക് നയിച്ചേക്കാം.", - "machine_learning_min_recognized_faces": "തിരിച്ചറിഞ്ഞ മുഖങ്ങളുടെ കുറഞ്ഞ എണ്ണം", - "machine_learning_min_recognized_faces_description": "ഒരു വ്യക്തിയെ സൃഷ്ടിക്കുന്നതിന് ആവശ്യമായ തിരിച്ചറിഞ്ഞ മുഖങ്ങളുടെ ഏറ്റവും കുറഞ്ഞ എണ്ണം. ഇത് വർദ്ധിപ്പിക്കുന്നത് മുഖം തിരിച്ചറിയൽ കൂടുതൽ കൃത്യമാക്കുന്നു, എന്നാൽ ഒരു മുഖം ഒരു വ്യക്തിക്ക് നൽകാതിരിക്കാനുള്ള സാധ്യതയും വർദ്ധിപ്പിക്കുന്നു.", - "machine_learning_ocr": "ഒ.സി.ആർ (OCR)", - "machine_learning_ocr_description": "ചിത്രങ്ങളിലെ ടെക്സ്റ്റ് തിരിച്ചറിയാൻ മെഷീൻ ലേണിംഗ് ഉപയോഗിക്കുക", - "machine_learning_ocr_enabled": "ഒ.സി.ആർ പ്രവർത്തനക്ഷമമാക്കുക", - "machine_learning_ocr_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, ചിത്രങ്ങൾ ടെക്സ്റ്റ് തിരിച്ചറിയലിന് വിധേയമാകില്ല.", - "machine_learning_ocr_max_resolution": "പരമാവധി റെസല്യൂഷൻ", - "machine_learning_ocr_max_resolution_description": "ഈ റെസല്യൂഷന് മുകളിലുള്ള പ്രിവ്യൂകൾ ആസ്പെക്റ്റ് റേഷ്യോ നിലനിർത്തിക്കൊണ്ട് റീസൈസ് ചെയ്യപ്പെടും. ഉയർന്ന മൂല്യങ്ങൾ കൂടുതൽ കൃത്യമാണെങ്കിലും പ്രോസസ്സ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കുകയും കൂടുതൽ മെമ്മറി ഉപയോഗിക്കുകയും ചെയ്യും.", - "machine_learning_ocr_min_detection_score": "കുറഞ്ഞ ഡിറ്റക്ഷൻ സ്കോർ", - "machine_learning_ocr_min_detection_score_description": "ടെക്സ്റ്റ് കണ്ടെത്തുന്നതിനുള്ള കുറഞ്ഞ കോൺഫിഡൻസ് സ്കോർ (0-1). കുറഞ്ഞ മൂല്യങ്ങൾ കൂടുതൽ ടെക്സ്റ്റ് കണ്ടെത്തിയേക്കാം, പക്ഷേ തെറ്റായ ഫലങ്ങൾക്കും സാധ്യതയുണ്ട്.", - "machine_learning_ocr_min_recognition_score": "കുറഞ്ഞ തിരിച്ചറിയൽ സ്കോർ", - "machine_learning_ocr_min_score_recognition_description": "കണ്ടെത്തിയ ടെക്സ്റ്റ് തിരിച്ചറിയുന്നതിനുള്ള കുറഞ്ഞ കോൺഫിഡൻസ് സ്കോർ (0-1). കുറഞ്ഞ മൂല്യങ്ങൾ കൂടുതൽ ടെക്സ്റ്റ് തിരിച്ചറിഞ്ഞേക്കാം, പക്ഷേ തെറ്റായ ഫലങ്ങൾക്കും സാധ്യതയുണ്ട്.", - "machine_learning_ocr_model": "ഒ.സി.ആർ മോഡൽ", - "machine_learning_ocr_model_description": "സെർവർ മോഡലുകൾ മൊബൈൽ മോഡലുകളേക്കാൾ കൃത്യതയുള്ളതാണ്, പക്ഷേ പ്രോസസ്സ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കുകയും കൂടുതൽ മെമ്മറി ഉപയോഗിക്കുകയും ചെയ്യും.", - "machine_learning_settings": "മെഷീൻ ലേണിംഗ് ക്രമീകരണങ്ങൾ", - "machine_learning_settings_description": "മെഷീൻ ലേണിംഗ് ഫീച്ചറുകളും ക്രമീകരണങ്ങളും കൈകാര്യം ചെയ്യുക", - "machine_learning_smart_search": "സ്മാർട്ട് സെർച്ച്", - "machine_learning_smart_search_description": "CLIP എംബെഡ്ഡിംഗ്‌സ് ഉപയോഗിച്ച് ചിത്രങ്ങൾക്കായി അർത്ഥപൂർവ്വം തിരയുക", - "machine_learning_smart_search_enabled": "സ്മാർട്ട് സെർച്ച് പ്രവർത്തനക്ഷമമാക്കുക", - "machine_learning_smart_search_enabled_description": "പ്രവർത്തനരഹിതമാക്കിയാൽ, സ്മാർട്ട് സെർച്ചിനായി ചിത്രങ്ങൾ എൻകോഡ് ചെയ്യപ്പെടില്ല.", - "machine_learning_url_description": "മെഷീൻ ലേണിംഗ് സെർവറിന്റെ URL. ഒന്നിൽ കൂടുതൽ URL-കൾ നൽകിയിട്ടുണ്ടെങ്കിൽ, ഓരോ സെർവറും ഒന്നിനു പുറകെ ഒന്നായി വിജയകരമായി പ്രതികരിക്കുന്നതുവരെ ശ്രമിക്കും. പ്രതികരിക്കാത്ത സെർവറുകൾ ഓൺലൈനിൽ തിരികെ വരുന്നതുവരെ താൽക്കാലികമായി അവഗണിക്കപ്പെടും.", - "maintenance_settings": "അറ്റകുറ്റപ്പണി (Maintenance)", - "maintenance_settings_description": "ഇമ്മിക്ക് മെയിന്റനൻസ് മോഡിലേക്ക് മാറ്റുക.", - "maintenance_start": "മെയിന്റനൻസ് മോഡ് ആരംഭിക്കുക", - "maintenance_start_error": "മെയിന്റനൻസ് മോഡ് ആരംഭിക്കുന്നതിൽ പരാജയപ്പെട്ടു.", - "manage_concurrency": "കോൺകറൻസി കൈകാര്യം ചെയ്യുക", - "manage_log_settings": "ലോഗ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "map_dark_style": "ഡാർക്ക് സ്റ്റൈൽ", - "map_enable_description": "മാപ്പ് ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "map_gps_settings": "മാപ്പ് & GPS ക്രമീകരണങ്ങൾ", - "map_gps_settings_description": "മാപ്പ് & GPS (റിവേഴ്സ് ജിയോകോഡിംഗ്) ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "map_implications": "മാപ്പ് ഫീച്ചർ ഒരു ബാഹ്യ ടൈൽ സേവനത്തെ (tiles.immich.cloud) ആശ്രയിച്ചിരിക്കുന്നു", - "map_light_style": "ലൈറ്റ് സ്റ്റൈൽ", - "map_manage_reverse_geocoding_settings": "റിവേഴ്സ് ജിയോകോഡിംഗ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "map_reverse_geocoding": "റിവേഴ്സ് ജിയോകോഡിംഗ്", - "map_reverse_geocoding_enable_description": "റിവേഴ്സ് ജിയോകോഡിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", - "map_reverse_geocoding_settings": "റിവേഴ്സ് ജിയോകോഡിംഗ് ക്രമീകരണങ്ങൾ", - "map_settings": "മാപ്പ്", - "map_settings_description": "മാപ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "map_style_description": "ഒരു style.json മാപ്പ് തീമിലേക്കുള്ള URL", - "memory_cleanup_job": "മെമ്മറി ക്ലീനപ്പ്", - "memory_generate_job": "മെമ്മറി ജനറേഷൻ", - "metadata_extraction_job": "മെറ്റാഡാറ്റ എക്‌സ്‌ട്രാക്റ്റുചെയ്യുക", - "metadata_extraction_job_description": "ഓരോ അസറ്റിൽ നിന്നും GPS, മുഖങ്ങൾ, റെസല്യൂഷൻ തുടങ്ങിയ മെറ്റാഡാറ്റ വിവരങ്ങൾ എക്‌സ്‌ട്രാക്റ്റുചെയ്യുക", - "metadata_faces_import_setting": "മുഖം ഇമ്പോർട്ട് പ്രവർത്തനക്ഷമമാക്കുക", - "metadata_faces_import_setting_description": "ചിത്രത്തിന്റെ EXIF ഡാറ്റയിൽ നിന്നും സൈഡ്‌കാർ ഫയലുകളിൽ നിന്നും മുഖങ്ങൾ ഇമ്പോർട്ടുചെയ്യുക", - "metadata_settings": "മെറ്റാഡാറ്റ ക്രമീകരണങ്ങൾ", - "metadata_settings_description": "മെറ്റാഡാറ്റ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "migration_job": "മൈഗ്രേഷൻ", - "migration_job_description": "അസറ്റുകളുടെയും മുഖങ്ങളുടെയും തംബ്നെയിലുകൾ ഏറ്റവും പുതിയ ഫോൾഡർ ഘടനയിലേക്ക് മൈഗ്രേറ്റ് ചെയ്യുക", - "nightly_tasks_cluster_faces_setting_description": "പുതുതായി കണ്ടെത്തിയ മുഖങ്ങളിൽ ഫേഷ്യൽ റെക്കഗ്നിഷൻ പ്രവർത്തിപ്പിക്കുക", - "nightly_tasks_cluster_new_faces_setting": "പുതിയ മുഖങ്ങളെ ക്ലസ്റ്റർ ചെയ്യുക", - "nightly_tasks_database_cleanup_setting": "ഡാറ്റാബേസ് ക്ലീനപ്പ് ടാസ്ക്കുകൾ", - "nightly_tasks_database_cleanup_setting_description": "ഡാറ്റാബേസിൽ നിന്ന് പഴയതും കാലഹരണപ്പെട്ടതുമായ ഡാറ്റ വൃത്തിയാക്കുക", - "nightly_tasks_generate_memories_setting": "മെമ്മറികൾ നിർമ്മിക്കുക", - "nightly_tasks_generate_memories_setting_description": "അസറ്റുകളിൽ നിന്ന് പുതിയ മെമ്മറികൾ സൃഷ്ടിക്കുക", - "nightly_tasks_missing_thumbnails_setting": "നഷ്ടപ്പെട്ട തംബ്നെയിലുകൾ നിർമ്മിക്കുക", - "nightly_tasks_missing_thumbnails_setting_description": "തംബ്നെയിലുകൾ ഇല്ലാത്ത അസറ്റുകളെ തംബ്നെയിൽ നിർമ്മാണത്തിനായി ക്യൂ ചെയ്യുക", - "nightly_tasks_settings": "രാത്രിയിലെ ടാസ്ക് ക്രമീകരണങ്ങൾ", - "nightly_tasks_settings_description": "രാത്രിയിലെ ടാസ്ക്കുകൾ കൈകാര്യം ചെയ്യുക", - "nightly_tasks_start_time_setting": "തുടങ്ങുന്ന സമയം", - "nightly_tasks_start_time_setting_description": "സെർവർ രാത്രിയിലെ ടാസ്ക്കുകൾ പ്രവർത്തിപ്പിക്കാൻ തുടങ്ങുന്ന സമയം", - "nightly_tasks_sync_quota_usage_setting": "ക്വാട്ട ഉപയോഗം സിങ്ക് ചെയ്യുക", - "nightly_tasks_sync_quota_usage_setting_description": "നിലവിലെ ഉപയോഗത്തെ അടിസ്ഥാനമാക്കി ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ക്വാട്ട അപ്ഡേറ്റ് ചെയ്യുക", - "no_paths_added": "പാത്തുകൾ ഒന്നും ചേർത്തിട്ടില്ല", - "no_pattern_added": "പാറ്റേൺ ഒന്നും ചേർത്തിട്ടില്ല", - "note_apply_storage_label_previous_assets": "കുറിപ്പ്: മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിൽ സ്റ്റോറേജ് ലേബൽ പ്രയോഗിക്കാൻ, ഇത് പ്രവർത്തിപ്പിക്കുക", - "note_cannot_be_changed_later": "കുറിപ്പ്: ഇത് പിന്നീട് മാറ്റാൻ കഴിയില്ല!", - "notification_email_from_address": "അയയ്ക്കുന്നയാളുടെ വിലാസം", - "notification_email_from_address_description": "അയയ്ക്കുന്നയാളുടെ ഇമെയിൽ വിലാസം, ഉദാഹരണത്തിന്: \"Immich Photo Server \". നിങ്ങൾക്ക് ഇമെയിലുകൾ അയയ്ക്കാൻ അനുവാദമുള്ള ഒരു വിലാസം ഉപയോഗിക്കുന്നുവെന്ന് ഉറപ്പാക്കുക.", - "notification_email_host_description": "ഇമെയിൽ സെർവറിന്റെ ഹോസ്റ്റ് (ഉദാ. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "സർട്ടിഫിക്കറ്റ് പിശകുകൾ അവഗണിക്കുക", - "notification_email_ignore_certificate_errors_description": "TLS സർട്ടിഫിക്കറ്റ് സാധുതാ പിശകുകൾ അവഗണിക്കുക (ശുപാർശ ചെയ്യുന്നില്ല)", - "notification_email_password_description": "ഇമെയിൽ സെർവറുമായി പ്രാമാണീകരിക്കുമ്പോൾ ഉപയോഗിക്കേണ്ട പാസ്‌വേഡ്", - "notification_email_port_description": "ഇമെയിൽ സെർവറിന്റെ പോർട്ട് (ഉദാ. 25, 465, അല്ലെങ്കിൽ 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "SMTPS ഉപയോഗിക്കുക (SMTP over TLS)", - "notification_email_sent_test_email_button": "പരിശോധനാ ഇമെയിൽ അയച്ച് സേവ് ചെയ്യുക", - "notification_email_setting_description": "ഇമെയിൽ അറിയിപ്പുകൾ അയക്കുന്നതിനുള്ള ക്രമീകരണങ്ങൾ", - "notification_email_test_email": "പരിശോധനാ ഇമെയിൽ അയക്കുക", - "notification_email_test_email_failed": "പരിശോധനാ ഇമെയിൽ അയക്കുന്നതിൽ പരാജയപ്പെട്ടു, നിങ്ങളുടെ മൂല്യങ്ങൾ പരിശോധിക്കുക", - "notification_email_test_email_sent": "{email} എന്ന വിലാസത്തിലേക്ക് ഒരു പരിശോധനാ ഇമെയിൽ അയച്ചിട്ടുണ്ട്. ദയവായി നിങ്ങളുടെ ഇൻബോക്സ് പരിശോധിക്കുക.", - "notification_email_username_description": "ഇമെയിൽ സെർവറുമായി പ്രാമാണീകരിക്കുമ്പോൾ ഉപയോഗിക്കേണ്ട ഉപയോക്തൃനാമം", - "notification_enable_email_notifications": "ഇമെയിൽ അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "notification_settings": "അറിയിപ്പ് ക്രമീകരണങ്ങൾ", - "notification_settings_description": "ഇമെയിൽ ഉൾപ്പെടെയുള്ള അറിയിപ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "oauth_auto_launch": "യാന്ത്രികമായി തുടങ്ങുക", - "oauth_auto_launch_description": "ലോഗിൻ പേജിലേക്ക് പോകുമ്പോൾ OAuth ലോഗിൻ ഫ്ലോ യാന്ത്രികമായി ആരംഭിക്കുക", - "oauth_auto_register": "യാന്ത്രികമായി രജിസ്റ്റർ ചെയ്യുക", - "oauth_auto_register_description": "OAuth ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്ത ശേഷം പുതിയ ഉപയോക്താക്കളെ യാന്ത്രികമായി രജിസ്റ്റർ ചെയ്യുക", - "oauth_button_text": "ബട്ടണിലെ വാചകം", - "oauth_client_secret_description": "OAuth ദാതാവ് PKCE (പ്രൂഫ് കീ ഫോർ കോഡ് എക്സ്ചേഞ്ച്) പിന്തുണയ്ക്കുന്നില്ലെങ്കിൽ ആവശ്യമാണ്", - "oauth_enable_description": "OAuth ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക", - "oauth_mobile_redirect_uri": "മൊബൈൽ റീഡയറക്ട് URI", - "oauth_mobile_redirect_uri_override": "മൊബൈൽ റീഡയറക്ട് URI ഓവർറൈഡ്", - "oauth_mobile_redirect_uri_override_description": "OAuth ദാതാവ് \"{callback}\" പോലുള്ള ഒരു മൊബൈൽ URI അനുവദിക്കാത്തപ്പോൾ പ്രവർത്തനക്ഷമമാക്കുക", - "oauth_role_claim": "റോൾ ക്ലെയിം", - "oauth_role_claim_description": "ഈ ക്ലെയിമിന്റെ സാന്നിധ്യത്തെ അടിസ്ഥാനമാക്കി യാന്ത്രികമായി അഡ്മിൻ ആക്സസ് നൽകുക. ക്ലെയിമിന് 'user' അല്ലെങ്കിൽ 'admin' എന്ന് ഉണ്ടാകാം.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth ലോഗിൻ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "oauth_settings_more_details": "ഈ ഫീച്ചറിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക്, ഡോക്സ് പരിശോധിക്കുക.", - "oauth_storage_label_claim": "സ്റ്റോറേജ് ലേബൽ ക്ലെയിം", - "oauth_storage_label_claim_description": "ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ലേബൽ ഈ ക്ലെയിമിന്റെ മൂല്യത്തിലേക്ക് യാന്ത്രികമായി സജ്ജമാക്കുക.", - "oauth_storage_quota_claim": "സ്റ്റോറേജ് ക്വാട്ട ക്ലെയിം", - "oauth_storage_quota_claim_description": "ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ക്വാട്ട ഈ ക്ലെയിമിന്റെ മൂല്യത്തിലേക്ക് യാന്ത്രികമായി സജ്ജമാക്കുക.", - "oauth_storage_quota_default": "ഡിഫോൾട്ട് സ്റ്റോറേജ് ക്വാട്ട (GiB)", - "oauth_storage_quota_default_description": "ക്ലെയിം ഒന്നും നൽകാത്തപ്പോൾ ഉപയോഗിക്കേണ്ട ക്വാട്ട (GiB-യിൽ).", - "oauth_timeout": "അഭ്യർത്ഥനയുടെ സമയപരിധി", - "oauth_timeout_description": "അഭ്യർത്ഥനകൾക്കുള്ള സമയപരിധി (മില്ലിസെക്കൻഡിൽ)", - "ocr_job_description": "ചിത്രങ്ങളിലെ ടെക്സ്റ്റ് തിരിച്ചറിയാൻ മെഷീൻ ലേണിംഗ് ഉപയോഗിക്കുക", - "password_enable_description": "ഇമെയിലും പാസ്‌വേഡും ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക", - "password_settings": "പാസ്‌വേഡ് ലോഗിൻ", - "password_settings_description": "പാസ്‌വേഡ് ലോഗിൻ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "paths_validated_successfully": "എല്ലാ പാത്തുകളും വിജയകരമായി സാധൂകരിച്ചു", - "person_cleanup_job": "വ്യക്തി ക്ലീനപ്പ്", - "quota_size_gib": "ക്വാട്ട വലുപ്പം (GiB)", - "refreshing_all_libraries": "എല്ലാ ലൈബ്രറികളും പുതുക്കുന്നു", - "registration": "അഡ്മിൻ രജിസ്ട്രേഷൻ", - "registration_description": "നിങ്ങളാണ് സിസ്റ്റത്തിലെ ആദ്യത്തെ ഉപയോക്താവ് എന്നതിനാൽ, നിങ്ങളെ അഡ്മിൻ ആയി നിയമിക്കും. ഭരണപരമായ ജോലികൾക്ക് നിങ്ങൾ ഉത്തരവാദിയായിരിക്കും, കൂടാതെ അധിക ഉപയോക്താക്കളെ സൃഷ്ടിക്കുന്നത് നിങ്ങളായിരിക്കും.", - "require_password_change_on_login": "ആദ്യ ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്‌വേഡ് മാറ്റാൻ ഉപയോക്താവിനോട് ആവശ്യപ്പെടുക", - "reset_settings_to_default": "ക്രമീകരണങ്ങൾ ഡിഫോൾട്ടിലേക്ക് പുനഃസജ്ജമാക്കുക", - "reset_settings_to_recent_saved": "അടുത്തിടെ സേവ് ചെയ്ത ക്രമീകരണങ്ങളിലേക്ക് പുനഃസജ്ജമാക്കുക", - "scanning_library": "ലൈബ്രറി സ്കാൻ ചെയ്യുന്നു", - "search_jobs": "ജോലികൾക്കായി തിരയുക…", - "send_welcome_email": "സ്വാഗത ഇമെയിൽ അയക്കുക", - "server_external_domain_settings": "ബാഹ്യ ഡൊമെയ്ൻ", - "server_external_domain_settings_description": "പൊതുവായി പങ്കിട്ട ലിങ്കുകൾക്കുള്ള ഡൊമെയ്ൻ, http(s):// ഉൾപ്പെടെ", - "server_public_users": "പൊതു ഉപയോക്താക്കൾ", - "server_public_users_description": "പങ്കിട്ട ആൽബങ്ങളിലേക്ക് ഒരു ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ എല്ലാ ഉപയോക്താക്കളെയും (പേരും ഇമെയിലും) ലിസ്റ്റ് ചെയ്യും. പ്രവർത്തനരഹിതമാക്കുമ്പോൾ, ഉപയോക്തൃ ലിസ്റ്റ് അഡ്മിൻ ഉപയോക്താക്കൾക്ക് മാത്രമേ ലഭ്യമാകൂ.", - "server_settings": "സെർവർ ക്രമീകരണങ്ങൾ", - "server_settings_description": "സെർവർ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "server_welcome_message": "സ്വാഗത സന്ദേശം", - "server_welcome_message_description": "ലോഗിൻ പേജിൽ പ്രദർശിപ്പിക്കുന്ന ഒരു സന്ദേശം.", - "sidecar_job": "സൈഡ്‌കാർ മെറ്റാഡാറ്റ", - "sidecar_job_description": "ഫയൽസിസ്റ്റത്തിൽ നിന്ന് സൈഡ്‌കാർ മെറ്റാഡാറ്റ കണ്ടെത്തുകയോ സിൻക്രൊണൈസ് ചെയ്യുകയോ ചെയ്യുക", - "slideshow_duration_description": "ഓരോ ചിത്രവും പ്രദർശിപ്പിക്കാനുള്ള സെക്കൻഡുകളുടെ എണ്ണം", - "smart_search_job_description": "സ്മാർട്ട് സെർച്ചിനെ പിന്തുണയ്ക്കുന്നതിനായി അസറ്റുകളിൽ മെഷീൻ ലേണിംഗ് പ്രവർത്തിപ്പിക്കുക", - "storage_template_date_time_description": "ഡേറ്റ്ടൈം വിവരങ്ങൾക്കായി അസറ്റിന്റെ ക്രിയേഷൻ ടൈംസ്റ്റാമ്പ് ഉപയോഗിക്കുന്നു", - "storage_template_date_time_sample": "സാമ്പിൾ സമയം {date}", - "storage_template_enable_description": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് എഞ്ചിൻ പ്രവർത്തനക്ഷമമാക്കുക", - "storage_template_hash_verification_enabled": "ഹാഷ് വെരിഫിക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കി", - "storage_template_hash_verification_enabled_description": "ഹാഷ് വെരിഫിക്കേഷൻ പ്രവർത്തനക്ഷമമാക്കുന്നു, പ്രത്യാഘാതങ്ങളെക്കുറിച്ച് ഉറപ്പില്ലെങ്കിൽ ഇത് പ്രവർത്തനരഹിതമാക്കരുത്", - "storage_template_migration": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് മൈഗ്രേഷൻ", - "storage_template_migration_description": "മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിലേക്ക് നിലവിലെ {template} പ്രയോഗിക്കുക", - "storage_template_migration_info": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് എല്ലാ എക്സ്റ്റൻഷനുകളെയും ചെറിയക്ഷരത്തിലേക്ക് മാറ്റും. ടെംപ്ലേറ്റിലെ മാറ്റങ്ങൾ പുതിയ അസറ്റുകൾക്ക് മാത്രമേ ബാധകമാകൂ. മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിൽ ടെംപ്ലേറ്റ് മുൻകാല പ്രാബല്യത്തോടെ പ്രയോഗിക്കാൻ, {job} പ്രവർത്തിപ്പിക്കുക.", - "storage_template_migration_job": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ് മൈഗ്രേഷൻ ജോലി", - "storage_template_more_details": "ഈ ഫീച്ചറിനെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്ക്, സ്റ്റോറേജ് ടെംപ്ലേറ്റ്-ഉം അതിൻ്റെ പ്രത്യാഘാതങ്ങളും പരിശോധിക്കുക", - "storage_template_onboarding_description_v2": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, ഉപയോക്താവ് നിർവചിച്ച ടെംപ്ലേറ്റ് അടിസ്ഥാനമാക്കി ഈ ഫീച്ചർ ഫയലുകൾ യാന്ത്രികമായി ഓർഗനൈസ് ചെയ്യും. കൂടുതൽ വിവരങ്ങൾക്ക്, ദയവായി ഡോക്യുമെന്റേഷൻ കാണുക.", - "storage_template_path_length": "ഏകദേശ പാത്ത് ദൈർഘ്യ പരിധി: {length, number}/{limit, number}", - "storage_template_settings": "സ്റ്റോറേജ് ടെംപ്ലേറ്റ്", - "storage_template_settings_description": "അപ്‌ലോഡ് ചെയ്യുന്ന അസറ്റിന്റെ ഫോൾഡർ ഘടനയും ഫയൽ നാമവും കൈകാര്യം ചെയ്യുക", - "storage_template_user_label": "{label} എന്നത് ഉപയോക്താവിന്റെ സ്റ്റോറേജ് ലേബലാണ്", - "system_settings": "സിസ്റ്റം ക്രമീകരണങ്ങൾ", - "tag_cleanup_job": "ടാഗ് ക്ലീനപ്പ്", - "template_email_available_tags": "നിങ്ങളുടെ ടെംപ്ലേറ്റിൽ ഇനിപ്പറയുന്ന വേരിയബിളുകൾ ഉപയോഗിക്കാം: {tags}", - "template_email_if_empty": "ടെംപ്ലേറ്റ് ശൂന്യമാണെങ്കിൽ, ഡിഫോൾട്ട് ഇമെയിൽ ഉപയോഗിക്കും.", - "template_email_invite_album": "ആൽബം ക്ഷണിക്കാനുള്ള ടെംപ്ലേറ്റ്", - "template_email_preview": "പ്രിവ്യൂ", - "template_email_settings": "ഇമെയിൽ ടെംപ്ലേറ്റുകൾ", - "template_email_update_album": "ആൽബം അപ്ഡേറ്റ് ടെംപ്ലേറ്റ്", - "template_email_welcome": "സ്വാഗത ഇമെയിൽ ടെംപ്ലേറ്റ്", - "template_settings": "അറിയിപ്പ് ടെംപ്ലേറ്റുകൾ", - "template_settings_description": "അറിയിപ്പുകൾക്കായി ഇഷ്ടാനുസൃത ടെംപ്ലേറ്റുകൾ കൈകാര്യം ചെയ്യുക", - "theme_custom_css_settings": "കസ്റ്റം CSS", - "theme_custom_css_settings_description": "കാസ്കേഡിംഗ് സ്റ്റൈൽ ഷീറ്റുകൾ (CSS) Immich-ന്റെ ഡിസൈൻ ഇഷ്ടാനുസൃതമാക്കാൻ അനുവദിക്കുന്നു.", - "theme_settings": "തീം ക്രമീകരണങ്ങൾ", - "theme_settings_description": "Immich വെബ് ഇന്റർഫേസിന്റെ കസ്റ്റമൈസേഷൻ കൈകാര്യം ചെയ്യുക", - "thumbnail_generation_job": "തംബ്നെയിലുകൾ നിർമ്മിക്കുക", - "thumbnail_generation_job_description": "ഓരോ അസറ്റിനും വലുതും ചെറുതും മങ്ങിയതുമായ തംബ്നെയിലുകളും ഓരോ വ്യക്തിക്കും തംബ്നെയിലുകളും നിർമ്മിക്കുക", - "transcoding_acceleration_api": "ആക്സിലറേഷൻ API", - "transcoding_acceleration_api_description": "ട്രാൻസ്‌കോഡിംഗ് ത്വരിതപ്പെടുത്തുന്നതിന് നിങ്ങളുടെ ഉപകരണവുമായി സംവദിക്കുന്ന API. ഈ ക്രമീകരണം 'ബെസ്റ്റ് എഫേർട്ട്' ആണ്: പരാജയപ്പെട്ടാൽ സോഫ്റ്റ്‌വെയർ ട്രാൻസ്‌കോഡിംഗിലേക്ക് മാറും. നിങ്ങളുടെ ഹാർഡ്‌വെയർ അനുസരിച്ച് VP9 പ്രവർത്തിക്കുകയോ പ്രവർത്തിക്കാതിരിക്കുകയോ ചെയ്യാം.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU ആവശ്യമാണ്)", - "transcoding_acceleration_qsv": "ക്വിക്ക് സിങ്ക് (7-ാം തലമുറ ഇന്റൽ സിപിയു അല്ലെങ്കിൽ അതിനുശേഷമുള്ളത് ആവശ്യമാണ്)", - "transcoding_acceleration_rkmpp": "RKMPP (Rockchip SOC-കളിൽ മാത്രം)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "അംഗീകൃത ഓഡിയോ കോഡെക്കുകൾ", - "transcoding_accepted_audio_codecs_description": "ട്രാൻസ്‌കോഡ് ചെയ്യേണ്ടാത്ത ഓഡിയോ കോഡെക്കുകൾ തിരഞ്ഞെടുക്കുക. ചില ട്രാൻസ്‌കോഡ് നയങ്ങൾക്ക് വേണ്ടി മാത്രം ഉപയോഗിക്കുന്നു.", - "transcoding_accepted_containers": "അംഗീകൃത കണ്ടെയ്‌നറുകൾ", - "transcoding_accepted_containers_description": "MP4-ലേക്ക് റീമക്സ് ചെയ്യേണ്ടാത്ത കണ്ടെയ്‌നർ ഫോർമാറ്റുകൾ തിരഞ്ഞെടുക്കുക. ചില ട്രാൻസ്‌കോഡ് നയങ്ങൾക്ക് വേണ്ടി മാത്രം ഉപയോഗിക്കുന്നു.", - "transcoding_accepted_video_codecs": "അംഗീകൃത വീഡിയോ കോഡെക്കുകൾ", - "transcoding_accepted_video_codecs_description": "ട്രാൻസ്‌കോഡ് ചെയ്യേണ്ടാത്ത വീഡിയോ കോഡെക്കുകൾ തിരഞ്ഞെടുക്കുക. ചില ട്രാൻസ്‌കോഡ് നയങ്ങൾക്ക് വേണ്ടി മാത്രം ഉപയോഗിക്കുന്നു.", - "transcoding_advanced_options_description": "മിക്ക ഉപയോക്താക്കളും മാറ്റം വരുത്തേണ്ടതില്ലാത്ത ഓപ്ഷനുകൾ", - "transcoding_audio_codec": "ഓഡിയോ കോഡെക്", - "transcoding_audio_codec_description": "Opus ഏറ്റവും ഉയർന്ന നിലവാരമുള്ള ഓപ്ഷനാണ്, പക്ഷേ പഴയ ഉപകരണങ്ങളുമായോ സോഫ്റ്റ്‌വെയറുമായോ ഇതിന് കുറഞ്ഞ അനുയോജ്യതയേ ഉള്ളൂ.", - "transcoding_bitrate_description": "പരമാവധി ബിറ്റ്റേറ്റിനേക്കാൾ ഉയർന്നതോ അംഗീകൃത ഫോർമാറ്റിൽ അല്ലാത്തതോ ആയ വീഡിയോകൾ", - "transcoding_codecs_learn_more": "ഇവിടെ ഉപയോഗിച്ചിരിക്കുന്ന പദങ്ങളെക്കുറിച്ച് കൂടുതലറിയാൻ, H.264 കോഡെക്, HEVC കോഡെക്, VP9 കോഡെക് എന്നിവയുടെ FFmpeg ഡോക്യുമെന്റേഷൻ പരിശോധിക്കുക.", - "transcoding_constant_quality_mode": "സ്ഥിരമായ ഗുണമേന്മ മോഡ്", - "transcoding_constant_quality_mode_description": "CQP-യെക്കാൾ മികച്ചതാണ് ICQ, എന്നാൽ ചില ഹാർഡ്‌വെയർ ആക്സിലറേഷൻ ഉപകരണങ്ങൾ ഈ മോഡിനെ പിന്തുണയ്ക്കുന്നില്ല. ഗുണമേന്മ അടിസ്ഥാനമാക്കിയുള്ള എൻകോഡിംഗ് ഉപയോഗിക്കുമ്പോൾ ഈ ഓപ്ഷൻ സജ്ജീകരിക്കുന്നത് നിർദ്ദിഷ്ട മോഡിന് മുൻഗണന നൽകും. ICQ പിന്തുണയ്ക്കാത്തതിനാൽ NVENC ഇത് അവഗണിക്കുന്നു.", - "transcoding_constant_rate_factor": "കോൺസ്റ്റന്റ് റേറ്റ് ഫാക്ടർ (-crf)", - "transcoding_constant_rate_factor_description": "വീഡിയോയുടെ ഗുണമേന്മ നില. സാധാരണ മൂല്യങ്ങൾ H.264-ന് 23, HEVC-ക്ക് 28, VP9-ന് 31, AV1-ന് 35 എന്നിങ്ങനെയാണ്. കുറഞ്ഞ മൂല്യം മികച്ചതാണ്, പക്ഷേ വലിയ ഫയലുകൾ ഉണ്ടാക്കുന്നു.", - "transcoding_disabled_description": "ഒരു വീഡിയോയും ട്രാൻസ്‌കോഡ് ചെയ്യരുത്, ഇത് ചില ക്ലയിന്റുകളിൽ പ്ലേബാക്ക് തടസ്സപ്പെടുത്തിയേക്കാം", - "transcoding_encoding_options": "എൻകോഡിംഗ് ഓപ്ഷനുകൾ", - "transcoding_encoding_options_description": "എൻകോഡ് ചെയ്ത വീഡിയോകൾക്കായി കോഡെക്കുകൾ, റെസല്യൂഷൻ, ഗുണമേന്മ, മറ്റ് ഓപ്ഷനുകൾ എന്നിവ സജ്ജമാക്കുക", - "transcoding_hardware_acceleration": "ഹാർഡ്‌വെയർ ആക്സിലറേഷൻ", - "transcoding_hardware_acceleration_description": "പരീക്ഷണാടിസ്ഥാനത്തിലുള്ളത്: വേഗതയേറിയ ട്രാൻസ്‌കോഡിംഗ്, എന്നാൽ ഒരേ ബിറ്റ്റേറ്റിൽ ഗുണമേന്മ കുറച്ചേക്കാം", - "transcoding_hardware_decoding": "ഹാർഡ്‌വെയർ ഡീകോഡിംഗ്", - "transcoding_hardware_decoding_setting_description": "എൻകോഡിംഗ് മാത്രം ത്വരിതപ്പെടുത്തുന്നതിന് പകരം എൻഡ്-ടു-എൻഡ് ആക്സിലറേഷൻ പ്രവർത്തനക്ഷമമാക്കുന്നു. എല്ലാ വീഡിയോകളിലും പ്രവർത്തിച്ചേക്കില്ല.", - "transcoding_max_b_frames": "പരമാവധി ബി-ഫ്രെയിമുകൾ", - "transcoding_max_b_frames_description": "ഉയർന്ന മൂല്യങ്ങൾ കംപ്രഷൻ കാര്യക്ഷമത മെച്ചപ്പെടുത്തുന്നു, പക്ഷേ എൻകോഡിംഗ് വേഗത കുറയ്ക്കുന്നു. പഴയ ഉപകരണങ്ങളിലെ ഹാർഡ്‌വെയർ ആക്സിലറേഷനുമായി പൊരുത്തപ്പെടണമെന്നില്ല. 0 ബി-ഫ്രെയിമുകൾ പ്രവർത്തനരഹിതമാക്കുന്നു, അതേസമയം -1 ഈ മൂല്യം യാന്ത്രികമായി സജ്ജീകരിക്കുന്നു.", - "transcoding_max_bitrate": "പരമാവധി ബിറ്റ്റേറ്റ്", - "transcoding_max_bitrate_description": "പരമാവധി ബിറ്റ്റേറ്റ് സജ്ജീകരിക്കുന്നത് ഗുണമേന്മയിൽ ചെറിയൊരു കുറവുണ്ടാക്കുമെങ്കിലും ഫയൽ വലുപ്പങ്ങൾ മുൻകൂട്ടി പ്രവചിക്കാൻ സഹായിക്കും. 720p-ൽ, സാധാരണ മൂല്യങ്ങൾ VP9 അല്ലെങ്കിൽ HEVC-ക്ക് 2600 kbit/s, അല്ലെങ്കിൽ H.264-ന് 4500 kbit/s ആണ്. 0 നൽകിയാൽ ഇത് പ്രവർത്തനരഹിതമാകും. യൂണിറ്റ് നൽകിയില്ലെങ്കിൽ, k (kbit/s) ആണെന്ന് കണക്കാക്കും; അതുകൊണ്ട് 5000, 5000k, 5M (Mbit/s) എന്നിവ തുല്യമാണ്.", - "transcoding_max_keyframe_interval": "പരമാവധി കീഫ്രെയിം ഇടവേള", - "transcoding_max_keyframe_interval_description": "കീഫ്രെയിമുകൾക്കിടയിലുള്ള പരമാവധി ഫ്രെയിം ദൂരം സജ്ജമാക്കുന്നു. കുറഞ്ഞ മൂല്യങ്ങൾ കംപ്രഷൻ കാര്യക്ഷമത കുറയ്ക്കുന്നു, പക്ഷേ സീക്ക് സമയം മെച്ചപ്പെടുത്തുകയും വേഗതയേറിയ ചലനമുള്ള രംഗങ്ങളിൽ ഗുണമേന്മ മെച്ചപ്പെടുത്തുകയും ചെയ്തേക്കാം. 0 ഈ മൂല്യം യാന്ത്രികമായി സജ്ജീകരിക്കുന്നു.", - "transcoding_optimal_description": "ലക്ഷ്യമിട്ട റെസല്യൂഷനേക്കാൾ ഉയർന്നതോ അംഗീകൃത ഫോർമാറ്റിൽ അല്ലാത്തതോ ആയ വീഡിയോകൾ", - "transcoding_policy": "ട്രാൻസ്‌കോഡ് നയം", - "transcoding_policy_description": "ഒരു വീഡിയോ എപ്പോൾ ട്രാൻസ്‌കോഡ് ചെയ്യണമെന്ന് സജ്ജമാക്കുക", - "transcoding_preferred_hardware_device": "തിരഞ്ഞെടുക്കുന്ന ഹാർഡ്‌വെയർ ഉപകരണം", - "transcoding_preferred_hardware_device_description": "VAAPI, QSV എന്നിവയ്ക്ക് മാത്രം ബാധകം. ഹാർഡ്‌വെയർ ട്രാൻസ്‌കോഡിംഗിനായി ഉപയോഗിക്കുന്ന dri നോഡ് സജ്ജമാക്കുന്നു.", - "transcoding_preset_preset": "പ്രീസെറ്റ് (-preset)", - "transcoding_preset_preset_description": "കംപ്രഷൻ വേഗത. വേഗത കുറഞ്ഞ പ്രീസെറ്റുകൾ ചെറിയ ഫയലുകൾ നിർമ്മിക്കുന്നു, ഒരു നിശ്ചിത ബിറ്റ്റേറ്റ് ലക്ഷ്യമിടുമ്പോൾ ഗുണമേന്മ വർദ്ധിപ്പിക്കുന്നു. 'faster'-നേക്കാൾ ഉയർന്ന വേഗത VP9 അവഗണിക്കുന്നു.", - "transcoding_reference_frames": "റഫറൻസ് ഫ്രെയിമുകൾ", - "transcoding_reference_frames_description": "നൽകിയിരിക്കുന്ന ഒരു ഫ്രെയിം കംപ്രസ് ചെയ്യുമ്പോൾ റഫറൻസ് ചെയ്യേണ്ട ഫ്രെയിമുകളുടെ എണ്ണം. ഉയർന്ന മൂല്യങ്ങൾ കംപ്രഷൻ കാര്യക്ഷമത മെച്ചപ്പെടുത്തുന്നു, പക്ഷേ എൻകോഡിംഗ് വേഗത കുറയ്ക്കുന്നു. 0 ഈ മൂല്യം യാന്ത്രികമായി സജ്ജീകരിക്കുന്നു.", - "transcoding_required_description": "അംഗീകൃത ഫോർമാറ്റിൽ അല്ലാത്ത വീഡിയോകൾ മാത്രം", - "transcoding_settings": "വീഡിയോ ട്രാൻസ്കോഡിംഗ് ക്രമീകരണങ്ങൾ", - "transcoding_settings_description": "ഏതൊക്കെ വീഡിയോകളാണ് ട്രാൻസ്‌കോഡ് ചെയ്യേണ്ടതെന്നും അവ എങ്ങനെ പ്രോസസ്സ് ചെയ്യണമെന്നും കൈകാര്യം ചെയ്യുക", - "transcoding_target_resolution": "ലക്ഷ്യമിടുന്ന റെസല്യൂഷൻ", - "transcoding_target_resolution_description": "ഉയർന്ന റെസല്യൂഷനുകൾക്ക് കൂടുതൽ വിശദാംശങ്ങൾ സംരക്ഷിക്കാൻ കഴിയും, പക്ഷേ എൻകോഡ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കും, വലിയ ഫയൽ വലുപ്പമുണ്ടാകും, കൂടാതെ ആപ്പിന്റെ പ്രതികരണശേഷി കുറയ്ക്കുകയും ചെയ്യും.", - "transcoding_temporal_aq": "ടെമ്പറൽ AQ", - "transcoding_temporal_aq_description": "NVENC-ക്ക് മാത്രം ബാധകം. ടെമ്പറൽ അഡാപ്റ്റീവ് ക്വാണ്ടൈസേഷൻ (Temporal Adaptive Quantization) ഉയർന്ന വിശദാംശങ്ങളും കുറഞ്ഞ ചലനവുമുള്ള രംഗങ്ങളുടെ ഗുണമേന്മ വർദ്ധിപ്പിക്കുന്നു. പഴയ ഉപകരണങ്ങളുമായി പൊരുത്തപ്പെടണമെന്നില്ല.", - "transcoding_threads": "ത്രെഡുകൾ", - "transcoding_threads_description": "ഉയർന്ന മൂല്യങ്ങൾ വേഗതയേറിയ എൻകോഡിംഗിലേക്ക് നയിക്കുന്നു, പക്ഷേ സെർവറിന് മറ്റ് ജോലികൾ പ്രോസസ്സ് ചെയ്യാൻ കുറച്ച് ഇടം നൽകുന്നു. ഈ മൂല്യം സിപിയു കോറുകളുടെ എണ്ണത്തേക്കാൾ കൂടുതലാകരുത്. 0 ആയി സജ്ജീകരിച്ചാൽ ഉപയോഗം പരമാവധിയാക്കുന്നു.", - "transcoding_tone_mapping": "ടോൺ-മാപ്പിംഗ്", - "transcoding_tone_mapping_description": "HDR വീഡിയോകൾ SDR-ലേക്ക് പരിവർത്തനം ചെയ്യുമ്പോൾ അവയുടെ രൂപം നിലനിർത്താൻ ശ്രമിക്കുന്നു. ഓരോ അൽഗോരിതവും നിറം, വിശദാംശങ്ങൾ, തെളിച്ചം എന്നിവയ്ക്കായി വ്യത്യസ്ത വിട്ടുവീഴ്ചകൾ ചെയ്യുന്നു. Hable വിശദാംശങ്ങൾ സംരക്ഷിക്കുന്നു, Mobius നിറം സംരക്ഷിക്കുന്നു, Reinhard തെളിച്ചം സംരക്ഷിക്കുന്നു.", - "transcoding_transcode_policy": "ട്രാൻസ്‌കോഡ് നയം", - "transcoding_transcode_policy_description": "ഒരു വീഡിയോ എപ്പോൾ ട്രാൻസ്‌കോഡ് ചെയ്യണം എന്നതിനെക്കുറിച്ചുള്ള നയം. HDR വീഡിയോകൾ എല്ലായ്പ്പോഴും ട്രാൻസ്‌കോഡ് ചെയ്യപ്പെടും (ട്രാൻസ്‌കോഡിംഗ് പ്രവർത്തനരഹിതമാക്കിയിട്ടില്ലെങ്കിൽ).", - "transcoding_two_pass_encoding": "ടു-പാസ് എൻകോഡിംഗ്", - "transcoding_two_pass_encoding_setting_description": "മെച്ചപ്പെട്ട എൻകോഡ് ചെയ്ത വീഡിയോകൾ നിർമ്മിക്കുന്നതിന് രണ്ട് പാസുകളിലായി ട്രാൻസ്‌കോഡ് ചെയ്യുക. പരമാവധി ബിറ്റ്റേറ്റ് പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ (H.264, HEVC എന്നിവയ്‌ക്കൊപ്പം പ്രവർത്തിക്കാൻ ഇത് ആവശ്യമാണ്), ഈ മോഡ് പരമാവധി ബിറ്റ്റേറ്റിനെ അടിസ്ഥാനമാക്കി ഒരു ബിറ്റ്റേറ്റ് ശ്രേണി ഉപയോഗിക്കുകയും CRF അവഗണിക്കുകയും ചെയ്യുന്നു. VP9-നായി, പരമാവധി ബിറ്റ്റേറ്റ് പ്രവർത്തനരഹിതമാക്കിയിട്ടുണ്ടെങ്കിൽ CRF ഉപയോഗിക്കാം.", - "transcoding_video_codec": "വീഡിയോ കോഡെക്", - "transcoding_video_codec_description": "VP9-ന് ഉയർന്ന കാര്യക്ഷമതയും വെബ് അനുയോജ്യതയുമുണ്ട്, പക്ഷേ ട്രാൻസ്‌കോഡ് ചെയ്യാൻ കൂടുതൽ സമയമെടുക്കും. HEVC സമാനമായി പ്രവർത്തിക്കുന്നു, പക്ഷേ വെബ് അനുയോജ്യത കുറവാണ്. H.264 വ്യാപകമായി അനുയോജ്യവും വേഗത്തിൽ ട്രാൻസ്‌കോഡ് ചെയ്യാൻ കഴിയുന്നതുമാണ്, പക്ഷേ വളരെ വലിയ ഫയലുകൾ നിർമ്മിക്കുന്നു. AV1 ഏറ്റവും കാര്യക്ഷമമായ കോഡെക്കാണ്, പക്ഷേ പഴയ ഉപകരണങ്ങളിൽ ഇതിന് പിന്തുണയില്ല.", - "trash_enabled_description": "ട്രാഷ് ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "trash_number_of_days": "ദിവസങ്ങളുടെ എണ്ണം", - "trash_number_of_days_description": "അസറ്റുകൾ ശാശ്വതമായി നീക്കം ചെയ്യുന്നതിനുമുമ്പ് ട്രാഷിൽ സൂക്ഷിക്കേണ്ട ദിവസങ്ങളുടെ എണ്ണം", - "trash_settings": "ട്രാഷ് ക്രമീകരണങ്ങൾ", - "trash_settings_description": "ട്രാഷ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "unlink_all_oauth_accounts": "എല്ലാ OAuth അക്കൗണ്ടുകളും അൺലിങ്ക് ചെയ്യുക", - "unlink_all_oauth_accounts_description": "ഒരു പുതിയ ദാതാവിലേക്ക് മാറുന്നതിന് മുമ്പ് എല്ലാ OAuth അക്കൗണ്ടുകളും അൺലിങ്ക് ചെയ്യാൻ ഓർമ്മിക്കുക.", - "unlink_all_oauth_accounts_prompt": "എല്ലാ OAuth അക്കൗണ്ടുകളും അൺലിങ്ക് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് ഓരോ ഉപയോക്താവിന്റെയും OAuth ഐഡി റീസെറ്റ് ചെയ്യും, ഇത് പഴയപടിയാക്കാൻ കഴിയില്ല.", - "user_cleanup_job": "ഉപയോക്തൃ ക്ലീനപ്പ്", - "user_delete_delay": "{user}-ന്റെ അക്കൗണ്ടും അസറ്റുകളും {delay, plural, one {# ദിവസത്തിനുള്ളിൽ} other {# ദിവസങ്ങൾക്കുള്ളിൽ}} ശാശ്വതമായി ഇല്ലാതാക്കാൻ ഷെഡ്യൂൾ ചെയ്യും.", - "user_delete_delay_settings": "ഇല്ലാതാക്കാനുള്ള കാലതാമസം", - "user_delete_delay_settings_description": "ഒരു ഉപയോക്താവിന്റെ അക്കൗണ്ടും അസറ്റുകളും ശാശ്വതമായി ഇല്ലാതാക്കാൻ നീക്കം ചെയ്തതിന് ശേഷമുള്ള ദിവസങ്ങളുടെ എണ്ണം. ഇല്ലാതാക്കാൻ തയ്യാറായ ഉപയോക്താക്കളെ പരിശോധിക്കാൻ ഉപയോക്താവിനെ ഇല്ലാതാക്കാനുള്ള ജോലി അർദ്ധരാത്രിയിൽ പ്രവർത്തിക്കുന്നു. ഈ ക്രമീകരണത്തിലെ മാറ്റങ്ങൾ അടുത്ത എക്സിക്യൂഷനിൽ വിലയിരുത്തപ്പെടും.", - "user_delete_immediately": "{user}-ന്റെ അക്കൗണ്ടും അസറ്റുകളും ഉടനടി ശാശ്വതമായി ഇല്ലാതാക്കുന്നതിനായി ക്യൂ ചെയ്യും.", - "user_delete_immediately_checkbox": "ഉപയോക്താവിനെയും അസറ്റുകളെയും ഉടനടി ഇല്ലാതാക്കാൻ ക്യൂ ചെയ്യുക", - "user_details": "ഉപയോക്തൃ വിശദാംശങ്ങൾ", - "user_management": "ഉപയോക്തൃ മാനേജ്മെന്റ്", - "user_password_has_been_reset": "ഉപയോക്താവിന്റെ പാസ്‌വേഡ് റീസെറ്റ് ചെയ്തു:", - "user_password_reset_description": "ദയവായി ഉപയോക്താവിന് താൽക്കാലിക പാസ്‌വേഡ് നൽകുക, അടുത്ത ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്‌വേഡ് മാറ്റേണ്ടിവരുമെന്ന് അവരെ അറിയിക്കുക.", - "user_restore_description": "{user}-ന്റെ അക്കൗണ്ട് പുനഃസ്ഥാപിക്കും.", - "user_restore_scheduled_removal": "ഉപയോക്താവിനെ പുനഃസ്ഥാപിക്കുക - {date, date, long}-ന് നീക്കംചെയ്യാൻ ഷെഡ്യൂൾ ചെയ്‌തിരിക്കുന്നു", - "user_settings": "ഉപയോക്താവിന്റെ ക്രമീകരണങ്ങൾ", - "user_settings_description": "ഉപയോക്തൃ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "version_check_enabled_description": "പതിപ്പ് പരിശോധന പ്രവർത്തനക്ഷമമാക്കുക", - "version_check_implications": "പതിപ്പ് പരിശോധന ഫീച്ചർ github.com-മായി ആനുകാലിക ആശയവിനിമയത്തെ ആശ്രയിച്ചിരിക്കുന്നു", - "version_check_settings": "പതിപ്പ് പരിശോധന", - "version_check_settings_description": "പുതിയ പതിപ്പിന്റെ അറിയിപ്പ് പ്രവർത്തനക്ഷമമാക്കുക/പ്രവർത്തനരഹിതമാക്കുക", - "video_conversion_job": "വീഡിയോകൾ ട്രാൻസ്‌കോഡ് ചെയ്യുക", - "video_conversion_job_description": "ബ്രൗസറുകളുമായും ഉപകരണങ്ങളുമായും കൂടുതൽ അനുയോജ്യതയ്ക്കായി വീഡിയോകൾ ട്രാൻസ്‌കോഡ് ചെയ്യുക" - }, - "admin_email": "അഡ്മിൻ ഇമെയിൽ", - "admin_password": "അഡ്മിൻ പാസ്‌വേഡ്", - "administration": "അഡ്മിനിസ്ട്രേഷൻ", - "advanced": "വിപുലമായത്", - "advanced_settings_enable_alternate_media_filter_subtitle": "ഇതര മാനദണ്ഡങ്ങളെ അടിസ്ഥാനമാക്കി സിങ്ക് സമയത്ത് മീഡിയ ഫിൽട്ടർ ചെയ്യാൻ ഈ ഓപ്ഷൻ ഉപയോഗിക്കുക. എല്ലാ ആൽബങ്ങളും കണ്ടെത്തുന്നതിൽ ആപ്പിന് പ്രശ്നങ്ങളുണ്ടെങ്കിൽ മാത്രം ഇത് പരീക്ഷിക്കുക.", - "advanced_settings_enable_alternate_media_filter_title": "[പരീക്ഷണാടിസ്ഥാനത്തിൽ] ഇതര ഉപകരണ ആൽബം സിങ്ക് ഫിൽട്ടർ ഉപയോഗിക്കുക", - "advanced_settings_log_level_title": "ലോഗ് ലെവൽ: {level}", - "advanced_settings_prefer_remote_subtitle": "ചില ഉപകരണങ്ങളിൽ പ്രാദേശിക അസറ്റുകളിൽ നിന്ന് തംബ്നെയിലുകൾ ലോഡുചെയ്യാൻ വളരെ വേഗത കുറവാണ്. പകരം റിമോട്ട് ചിത്രങ്ങൾ ലോഡുചെയ്യാൻ ഈ ക്രമീകരണം സജീവമാക്കുക.", - "advanced_settings_prefer_remote_title": "റിമോട്ട് ചിത്രങ്ങൾക്ക് മുൻഗണന നൽകുക", - "advanced_settings_proxy_headers_subtitle": "ഓരോ നെറ്റ്‌വർക്ക് അഭ്യർത്ഥനയ്‌ക്കൊപ്പവും Immich അയയ്‌ക്കേണ്ട പ്രോക്സി ഹെഡറുകൾ നിർവചിക്കുക", - "advanced_settings_proxy_headers_title": "കസ്റ്റം പ്രോക്സി ഹെഡറുകൾ [പരീക്ഷണാടിസ്ഥാനത്തിൽ]", - "advanced_settings_readonly_mode_subtitle": "ഫോട്ടോകൾ മാത്രം കാണാൻ കഴിയുന്ന റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനക്ഷമമാക്കുന്നു. ഒന്നിലധികം ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കൽ, പങ്കിടൽ, കാസ്റ്റിംഗ്, ഇല്ലാതാക്കൽ എന്നിവയെല്ലാം പ്രവർത്തനരഹിതമാകും. പ്രധാന സ്ക്രീനിലെ ഉപയോക്തൃ അവതാർ വഴി റീഡ്-ഓൺലി പ്രവർത്തനക്ഷമമാക്കുക/പ്രവർത്തനരഹിതമാക്കുക.", - "advanced_settings_readonly_mode_title": "റീഡ്-ഓൺലി മോഡ്", - "advanced_settings_self_signed_ssl_subtitle": "സെർവർ എൻഡ്‌പോയിന്റിനായുള്ള SSL സർട്ടിഫിക്കറ്റ് പരിശോധന ഒഴിവാക്കുന്നു. സ്വയം ഒപ്പിട്ട സർട്ടിഫിക്കറ്റുകൾക്ക് ആവശ്യമാണ്.", - "advanced_settings_self_signed_ssl_title": "സ്വയം ഒപ്പിട്ട SSL സർട്ടിഫിക്കറ്റുകൾ അനുവദിക്കുക [പരീക്ഷണാടിസ്ഥാനത്തിൽ]", - "advanced_settings_sync_remote_deletions_subtitle": "വെബിൽ ആ പ്രവർത്തനം നടത്തുമ്പോൾ ഈ ഉപകരണത്തിലെ ഒരു അസറ്റ് യാന്ത്രികമായി ഇല്ലാതാക്കുകയോ പുനഃസ്ഥാപിക്കുകയോ ചെയ്യുക", - "advanced_settings_sync_remote_deletions_title": "റിമോട്ട് ഇല്ലാതാക്കലുകൾ സിങ്ക് ചെയ്യുക [പരീക്ഷണാടിസ്ഥാനത്തിൽ]", - "advanced_settings_tile_subtitle": "വിപുലമായ ഉപയോക്തൃ ക്രമീകരണങ്ങൾ", - "advanced_settings_troubleshooting_subtitle": "ട്രബിൾഷൂട്ടിംഗിനായി അധിക ഫീച്ചറുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "advanced_settings_troubleshooting_title": "ട്രബിൾഷൂട്ടിംഗ്", - "age_months": "പ്രായം {months, plural, one {# മാസം} other {# മാസം}}", - "age_year_months": "പ്രായം 1 വർഷം, {months, plural, one {# മാസം} other {# മാസം}}", - "age_years": "{years, plural, other {പ്രായം #}}", - "album": "ആൽബം", - "album_added": "ആൽബം ചേർത്തു", - "album_added_notification_setting_description": "നിങ്ങളെ ഒരു പങ്കിട്ട ആൽബത്തിലേക്ക് ചേർക്കുമ്പോൾ ഒരു ഇമെയിൽ അറിയിപ്പ് സ്വീകരിക്കുക", - "album_cover_updated": "ആൽബം കവർ അപ്ഡേറ്റ് ചെയ്തു", - "album_delete_confirmation": "നിങ്ങൾക്ക് {album} ആൽബം ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ?", - "album_delete_confirmation_description": "ഈ ആൽബം പങ്കിട്ടതാണെങ്കിൽ, മറ്റ് ഉപയോക്താക്കൾക്ക് ഇനി ഇത് ആക്‌സസ് ചെയ്യാൻ കഴിയില്ല.", - "album_deleted": "ആൽബം ഇല്ലാതാക്കി", - "album_info_card_backup_album_excluded": "ഒഴിവാക്കി", - "album_info_card_backup_album_included": "ഉൾപ്പെടുത്തി", - "album_info_updated": "ആൽബം വിവരങ്ങൾ അപ്ഡേറ്റ് ചെയ്തു", - "album_leave": "ആൽബം വിടുകയാണോ?", - "album_leave_confirmation": "{album} വിട്ടുപോകണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "album_name": "ആൽബത്തിന്റെ പേര്", - "album_options": "ആൽബം ഓപ്ഷനുകൾ", - "album_remove_user": "ഉപയോക്താവിനെ നീക്കം ചെയ്യണോ?", - "album_remove_user_confirmation": "{user}-നെ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "album_search_not_found": "നിങ്ങളുടെ തിരയലുമായി പൊരുത്തപ്പെടുന്ന ആൽബങ്ങളൊന്നും കണ്ടെത്തിയില്ല", - "album_share_no_users": "നിങ്ങൾ ഈ ആൽബം എല്ലാ ഉപയോക്താക്കളുമായും പങ്കിട്ടു, അല്ലെങ്കിൽ നിങ്ങൾക്ക് പങ്കിടാൻ ഉപയോക്താക്കളാരും ഇല്ലെന്നു തോന്നുന്നു.", - "album_summary": "ആൽബത്തിന്റെ സംഗ്രഹം", - "album_updated": "ആൽബം അപ്ഡേറ്റ് ചെയ്തു", - "album_updated_setting_description": "ഒരു പങ്കിട്ട ആൽബത്തിൽ പുതിയ അസറ്റുകൾ ഉണ്ടാകുമ്പോൾ ഒരു ഇമെയിൽ അറിയിപ്പ് സ്വീകരിക്കുക", - "album_user_left": "{album} വിട്ടുപോയി", - "album_user_removed": "{user}-നെ നീക്കം ചെയ്തു", - "album_viewer_appbar_delete_confirm": "നിങ്ങളുടെ അക്കൗണ്ടിൽ നിന്ന് ഈ ആൽബം ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ?", - "album_viewer_appbar_share_err_delete": "ആൽബം ഇല്ലാതാക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "album_viewer_appbar_share_err_leave": "ആൽബം വിടുന്നതിൽ പരാജയപ്പെട്ടു", - "album_viewer_appbar_share_err_remove": "ആൽബത്തിൽ നിന്ന് അസറ്റുകൾ നീക്കം ചെയ്യുന്നതിൽ പ്രശ്നങ്ങളുണ്ട്", - "album_viewer_appbar_share_err_title": "ആൽബത്തിന്റെ ശീർഷകം മാറ്റുന്നതിൽ പരാജയപ്പെട്ടു", - "album_viewer_appbar_share_leave": "ആൽബം വിടുക", - "album_viewer_appbar_share_to": "ഇതിലേക്ക് പങ്കിടുക", - "album_viewer_page_share_add_users": "ഉപയോക്താക്കളെ ചേർക്കുക", - "album_with_link_access": "ലിങ്കുള്ള ആർക്കും ഈ ആൽബത്തിലെ ഫോട്ടോകളും ആളുകളെയും കാണാൻ അനുവദിക്കുക.", - "albums": "ആൽബങ്ങൾ", - "albums_count": "{count, plural, one {{count, number} ആൽബം} other {{count, number} ആൽബങ്ങൾ}}", - "albums_default_sort_order": "ഡിഫോൾട്ട് ആൽബം സോർട്ട് ഓർഡർ", - "albums_default_sort_order_description": "പുതിയ ആൽബങ്ങൾ സൃഷ്ടിക്കുമ്പോൾ പ്രാരംഭ അസറ്റ് സോർട്ട് ഓർഡർ.", - "albums_feature_description": "മറ്റ് ഉപയോക്താക്കളുമായി പങ്കിടാൻ കഴിയുന്ന അസറ്റുകളുടെ ശേഖരം.", - "albums_on_device_count": "ഉപകരണത്തിലെ ആൽബങ്ങൾ ({count})", - "all": "എല്ലാം", - "all_albums": "എല്ലാ ആൽബങ്ങളും", - "all_people": "എല്ലാ ആളുകളും", - "all_videos": "എല്ലാ വീഡിയോകളും", - "allow_dark_mode": "ഡാർക്ക് മോഡ് അനുവദിക്കുക", - "allow_edits": "എഡിറ്റുകൾ അനുവദിക്കുക", - "allow_public_user_to_download": "പൊതു ഉപയോക്താവിനെ ഡൗൺലോഡ് ചെയ്യാൻ അനുവദിക്കുക", - "allow_public_user_to_upload": "പൊതു ഉപയോക്താവിനെ അപ്‌ലോഡ് ചെയ്യാൻ അനുവദിക്കുക", - "allowed": "അനുവദിച്ചു", - "alt_text_qr_code": "QR കോഡ് ചിത്രം", - "anti_clockwise": "അപ്രദക്ഷിണമായി", - "api_key": "API കീ", - "api_key_description": "ഈ മൂല്യം ഒരു തവണ മാത്രമേ കാണിക്കൂ. വിൻഡോ അടയ്ക്കുന്നതിന് മുമ്പ് ഇത് പകർത്തുന്നത് ഉറപ്പാക്കുക.", - "api_key_empty": "നിങ്ങളുടെ API കീയുടെ പേര് ശൂന്യമാകരുത്", - "api_keys": "API കീകൾ", - "app_architecture_variant": "വേരിയന്റ് (ആർക്കിടെക്ചർ)", - "app_bar_signout_dialog_content": "സൈൻ ഔട്ട് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "app_bar_signout_dialog_ok": "അതെ", - "app_bar_signout_dialog_title": "സൈൻ ഔട്ട്", - "app_download_links": "ആപ്പ് ഡൗൺലോഡ് ലിങ്കുകൾ", - "app_settings": "ആപ്പ് ക്രമീകരണങ്ങൾ", - "app_stores": "ആപ്പ് സ്റ്റോറുകൾ", - "app_update_available": "ആപ്പ് അപ്‌ഡേറ്റ് ലഭ്യമാണ്", - "appears_in": "ഇതിൽ കാണപ്പെടുന്നു", - "apply_count": "പ്രയോഗിക്കുക ({count, number})", - "archive": "ആർക്കൈവ്", - "archive_action_prompt": "{count} എണ്ണം ആർക്കൈവിലേക്ക് ചേർത്തു", - "archive_or_unarchive_photo": "ഫോട്ടോ ആർക്കൈവ് ചെയ്യുക അല്ലെങ്കിൽ അൺആർക്കൈവ് ചെയ്യുക", - "archive_page_no_archived_assets": "ആർക്കൈവുചെയ്‌ത അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", - "archive_page_title": "ആർക്കൈവ് ({count})", - "archive_size": "ആർക്കൈവ് വലുപ്പം", - "archive_size_description": "ഡൗൺലോഡുകൾക്കായി ആർക്കൈവ് വലുപ്പം കോൺഫിഗർ ചെയ്യുക (GiB-യിൽ)", - "archived": "ആർക്കൈവുചെയ്‌തു", - "archived_count": "{count, plural, other {ആർക്കൈവുചെയ്‌തവ #}}", - "are_these_the_same_person": "ഇവർ ഒരേ വ്യക്തിയാണോ?", - "are_you_sure_to_do_this": "ഇത് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "asset_action_delete_err_read_only": "വായിക്കാൻ മാത്രമുള്ള അസറ്റ്(കൾ) ഇല്ലാതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "asset_action_share_err_offline": "ഓഫ്‌ലൈൻ അസറ്റ്(കൾ) ലഭ്യമാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "asset_added_to_album": "ആൽബത്തിലേക്ക് ചേർത്തു", - "asset_adding_to_album": "ആൽബത്തിലേക്ക് ചേർത്തുകൊണ്ടിരിക്കുന്നു…", - "asset_description_updated": "അസറ്റിന്റെ വിവരണം അപ്ഡേറ്റ് ചെയ്തു", - "asset_filename_is_offline": "{filename} എന്ന അസറ്റ് ഓഫ്‌ലൈനാണ്", - "asset_has_unassigned_faces": "അസറ്റിൽ അസൈൻ ചെയ്യാത്ത മുഖങ്ങളുണ്ട്", - "asset_hashing": "ഹാഷിംഗ്…", - "asset_list_group_by_sub_title": "ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", - "asset_list_layout_settings_dynamic_layout_title": "ഡൈനാമിക് ലേഔട്ട്", - "asset_list_layout_settings_group_automatically": "യാന്ത്രികം", - "asset_list_layout_settings_group_by": "അസറ്റുകളെ ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", - "asset_list_layout_settings_group_by_month_day": "മാസം + ദിവസം", - "asset_list_layout_sub_title": "ലേഔട്ട്", - "asset_list_settings_subtitle": "ഫോട്ടോ ഗ്രിഡ് ലേഔട്ട് ക്രമീകരണങ്ങൾ", - "asset_list_settings_title": "ഫോട്ടോ ഗ്രിഡ്", - "asset_offline": "അസറ്റ് ഓഫ്‌ലൈൻ", - "asset_offline_description": "ഈ എക്സ്റ്റേണൽ അസറ്റ് ഇപ്പോൾ ഡിസ്കിൽ ലഭ്യമല്ല. സഹായത്തിനായി നിങ്ങളുടെ Immich അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക.", - "asset_restored_successfully": "അസറ്റ് വിജയകരമായി പുനഃസ്ഥാപിച്ചു", - "asset_skipped": "ഒഴിവാക്കി", - "asset_skipped_in_trash": "ട്രാഷിൽ", - "asset_trashed": "അസറ്റ് ട്രാഷ് ചെയ്തു", - "asset_troubleshoot": "അസറ്റ് ട്രബിൾഷൂട്ട്", - "asset_uploaded": "അപ്‌ലോഡ് ചെയ്തു", - "asset_uploading": "അപ്‌ലോഡ് ചെയ്യുന്നു…", - "asset_viewer_settings_subtitle": "നിങ്ങളുടെ ഗാലറി വ്യൂവർ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "asset_viewer_settings_title": "അസറ്റ് വ്യൂവർ", - "assets": "അസറ്റുകൾ", - "assets_added_count": "{count, plural, one {# അസറ്റ് ചേർത്തു} other {# അസറ്റുകൾ ചേർത്തു}}", - "assets_added_to_album_count": "ആൽബത്തിലേക്ക് {count, plural, one {# അസറ്റ് ചേർത്തു} other {# അസറ്റുകൾ ചേർത്തു}}", - "assets_added_to_albums_count": "{albumTotal, plural, one {# ആൽബത്തിലേക്ക്} other {# ആൽബങ്ങളിലേക്ക്}} {assetTotal, plural, one {# അസറ്റ് ചേർത്തു} other {# അസറ്റുകൾ ചേർത്തു}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {അസറ്റ്} other {അസറ്റുകൾ}} ആൽബത്തിലേക്ക് ചേർക്കാൻ കഴിയില്ല", - "assets_cannot_be_added_to_albums": "{count, plural, one {അസറ്റ്} other {അസറ്റുകൾ}} ഒരു ആൽബത്തിലേക്കും ചേർക്കാൻ കഴിയില്ല", - "assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}}", - "assets_deleted_permanently": "{count} അസറ്റ്(കൾ) ശാശ്വതമായി ഇല്ലാതാക്കി", - "assets_deleted_permanently_from_server": "{count} അസറ്റ്(കൾ) Immich സെർവറിൽ നിന്ന് ശാശ്വതമായി ഇല്ലാതാക്കി", - "assets_downloaded_failed": "{count, plural, one {# ഫയൽ ഡൗൺലോഡ് ചെയ്തു - {error} ഫയൽ പരാജയപ്പെട്ടു} other {# ഫയലുകൾ ഡൗൺലോഡ് ചെയ്തു - {error} ഫയലുകൾ പരാജയപ്പെട്ടു}}", - "assets_downloaded_successfully": "{count, plural, one {# ഫയൽ വിജയകരമായി ഡൗൺലോഡ് ചെയ്തു} other {# ഫയലുകൾ വിജയകരമായി ഡൗൺലോഡ് ചെയ്തു}}", - "assets_moved_to_trash_count": "{count, plural, one {# അസറ്റ് ട്രാഷിലേക്ക് മാറ്റി} other {# അസറ്റുകൾ ട്രാഷിലേക്ക് മാറ്റി}}", - "assets_permanently_deleted_count": "{count, plural, one {# അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കി} other {# അസറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കി}}", - "assets_removed_count": "{count, plural, one {# അസറ്റ് നീക്കം ചെയ്തു} other {# അസറ്റുകൾ നീക്കം ചെയ്തു}}", - "assets_removed_permanently_from_device": "{count} അസറ്റ്(കൾ) നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്തു", - "assets_restore_confirmation": "ട്രാഷ് ചെയ്ത എല്ലാ അസറ്റുകളും പുനഃസ്ഥാപിക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല! ഓഫ്‌ലൈൻ അസറ്റുകളൊന്നും ഈ രീതിയിൽ പുനഃസ്ഥാപിക്കാൻ കഴിയില്ലെന്ന കാര്യം ശ്രദ്ധിക്കുക.", - "assets_restored_count": "{count, plural, one {# അസറ്റ് പുനഃസ്ഥാപിച്ചു} other {# അസറ്റുകൾ പുനഃസ്ഥാപിച്ചു}}", - "assets_restored_successfully": "{count} അസറ്റ്(കൾ) വിജയകരമായി പുനഃസ്ഥാപിച്ചു", - "assets_trashed": "{count} അസറ്റ്(കൾ) ട്രാഷ് ചെയ്തു", - "assets_trashed_count": "{count, plural, one {# അസറ്റ് ട്രാഷ് ചെയ്തു} other {# അസറ്റുകൾ ട്രാഷ് ചെയ്തു}}", - "assets_trashed_from_server": "{count} അസറ്റ്(കൾ) Immich സെർവറിൽ നിന്ന് ട്രാഷ് ചെയ്തു", - "assets_were_part_of_album_count": "{count, plural, one {അസറ്റ് ഇതിനകം} other {അസറ്റുകൾ ഇതിനകം}} ആൽബത്തിന്റെ ഭാഗമായിരുന്നു", - "assets_were_part_of_albums_count": "{count, plural, one {അസറ്റ് ഇതിനകം} other {അസറ്റുകൾ ഇതിനകം}} ആൽബങ്ങളുടെ ഭാഗമായിരുന്നു", - "authorized_devices": "അംഗീകൃത ഉപകരണങ്ങൾ", - "automatic_endpoint_switching_subtitle": "ലഭ്യമാകുമ്പോൾ നിശ്ചിത വൈ-ഫൈ വഴി പ്രാദേശികമായി കണക്റ്റുചെയ്യുക, മറ്റ് സ്ഥലങ്ങളിൽ ഇതര കണക്ഷനുകൾ ഉപയോഗിക്കുക", - "automatic_endpoint_switching_title": "യാന്ത്രിക URL സ്വിച്ചിംഗ്", - "autoplay_slideshow": "സ്ലൈഡ്‌ഷോ യാന്ത്രികമായി പ്ലേ ചെയ്യുക", - "back": "തിരികെ", - "back_close_deselect": "പുറകോട്ട്, അടയ്ക്കുക, അല്ലെങ്കിൽ തിരഞ്ഞെടുത്തത് മാറ്റുക", - "background_backup_running_error": "പശ്ചാത്തല ബാക്കപ്പ് ഇപ്പോൾ പ്രവർത്തിക്കുന്നു, മാനുവൽ ബാക്കപ്പ് ആരംഭിക്കാൻ കഴിയില്ല", - "background_location_permission": "പശ്ചാത്തല ലൊക്കേഷൻ അനുമതി", - "background_location_permission_content": "പശ്ചാത്തലത്തിൽ പ്രവർത്തിക്കുമ്പോൾ നെറ്റ്‌വർക്കുകൾ മാറുന്നതിന്, Immich-ന് എപ്പോഴും കൃത്യമായ ലൊക്കേഷൻ ആക്‌സസ് ഉണ്ടായിരിക്കണം, അതുവഴി ആപ്പിന് വൈ-ഫൈ നെറ്റ്‌വർക്കിന്റെ പേര് വായിക്കാൻ കഴിയും", - "background_options": "പശ്ചാത്തല ഓപ്ഷനുകൾ", - "backup": "ബാക്കപ്പ്", - "backup_album_selection_page_albums_device": "ഉപകരണത്തിലെ ആൽബങ്ങൾ ({count})", - "backup_album_selection_page_albums_tap": "ഉൾപ്പെടുത്താൻ ടാപ്പുചെയ്യുക, ഒഴിവാക്കാൻ ഡബിൾ ടാപ്പുചെയ്യുക", - "backup_album_selection_page_assets_scatter": "അസറ്റുകൾ ഒന്നിലധികം ആൽബങ്ങളിലായി വ്യാപിച്ചുകിടക്കാം. അതിനാൽ, ബാക്കപ്പ് പ്രക്രിയയിൽ ആൽബങ്ങൾ ഉൾപ്പെടുത്തുകയോ ഒഴിവാക്കുകയോ ചെയ്യാം.", - "backup_album_selection_page_select_albums": "ആൽബങ്ങൾ തിരഞ്ഞെടുക്കുക", - "backup_album_selection_page_selection_info": "തിരഞ്ഞെടുക്കൽ വിവരം", - "backup_album_selection_page_total_assets": "ആകെ സവിശേഷമായ അസറ്റുകൾ", - "backup_albums_sync": "ബാക്കപ്പ് ആൽബം സിൻക്രൊണൈസേഷൻ", - "backup_all": "എല്ലാം ബാക്കപ്പ് ചെയ്യുക", - "backup_background_service_backup_failed_message": "അസറ്റുകൾ ബാക്കപ്പ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു. വീണ്ടും ശ്രമിക്കുന്നു…", - "backup_background_service_complete_notification": "അസറ്റ് ബാക്കപ്പ് പൂർത്തിയായി", - "backup_background_service_connection_failed_message": "സെർവറിലേക്ക് കണക്റ്റുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു. വീണ്ടും ശ്രമിക്കുന്നു…", - "backup_background_service_current_upload_notification": "{filename} അപ്‌ലോഡ് ചെയ്യുന്നു", - "backup_background_service_default_notification": "പുതിയ അസറ്റുകൾക്കായി പരിശോധിക്കുന്നു…", - "backup_background_service_error_title": "ബാക്കപ്പ് പിശക്", - "backup_background_service_in_progress_notification": "നിങ്ങളുടെ അസറ്റുകൾ ബാക്കപ്പ് ചെയ്യുന്നു…", - "backup_background_service_upload_failure_notification": "{filename} അപ്‌ലോഡ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "backup_controller_page_albums": "ബാക്കപ്പ് ആൽബങ്ങൾ", - "backup_controller_page_background_app_refresh_disabled_content": "പശ്ചാത്തല ബാക്കപ്പ് ഉപയോഗിക്കുന്നതിന്, ക്രമീകരണങ്ങൾ > പൊതുവായത് > പശ്ചാത്തല ആപ്പ് റിഫ്രഷ് എന്നതിൽ പശ്ചാത്തല ആപ്പ് റിഫ്രഷ് പ്രവർത്തനക്ഷമമാക്കുക.", - "backup_controller_page_background_app_refresh_disabled_title": "പശ്ചാത്തല ആപ്പ് റിഫ്രഷ് പ്രവർത്തനരഹിതമാക്കി", - "backup_controller_page_background_app_refresh_enable_button_text": "ക്രമീകരണങ്ങളിലേക്ക് പോകുക", - "backup_controller_page_background_battery_info_link": "എങ്ങനെയെന്ന് കാണിക്കുക", - "backup_controller_page_background_battery_info_message": "മികച്ച പശ്ചാത്തല ബാക്കപ്പ് അനുഭവത്തിനായി, Immich-ന്റെ പശ്ചാത്തല പ്രവർത്തനം നിയന്ത്രിക്കുന്ന ഏതെങ്കിലും ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകൾ പ്രവർത്തനരഹിതമാക്കുക.\n\nഇത് ഉപകരണത്തെ ആശ്രയിച്ചിരിക്കുന്നതിനാൽ, നിങ്ങളുടെ ഉപകരണ നിർമ്മാതാവിനുള്ള ആവശ്യമായ വിവരങ്ങൾ ദയവായി കണ്ടെത്തുക.", - "backup_controller_page_background_battery_info_ok": "ശരി", - "backup_controller_page_background_battery_info_title": "ബാറ്ററി ഒപ്റ്റിമൈസേഷനുകൾ", - "backup_controller_page_background_charging": "ചാർജ് ചെയ്യുമ്പോൾ മാത്രം", - "backup_controller_page_background_configure_error": "പശ്ചാത്തല സേവനം കോൺഫിഗർ ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "backup_controller_page_background_delay": "പുതിയ അസറ്റുകളുടെ ബാക്കപ്പ് വൈകിപ്പിക്കുക: {duration}", - "backup_controller_page_background_description": "ആപ്പ് തുറക്കാതെ തന്നെ പുതിയ അസറ്റുകൾ യാന്ത്രികമായി ബാക്കപ്പ് ചെയ്യാൻ പശ്ചാത്തല സേവനം ഓണാക്കുക", - "backup_controller_page_background_is_off": "യാന്ത്രിക പശ്ചാത്തല ബാക്കപ്പ് ഓഫാണ്", - "backup_controller_page_background_is_on": "യാന്ത്രിക പശ്ചാത്തല ബാക്കപ്പ് ഓണാണ്", - "backup_controller_page_background_turn_off": "പശ്ചാത്തല സേവനം ഓഫാക്കുക", - "backup_controller_page_background_turn_on": "പശ്ചാത്തല സേവനം ഓണാക്കുക", - "backup_controller_page_background_wifi": "വൈ-ഫൈയിൽ മാത്രം", - "backup_controller_page_backup": "ബാക്കപ്പ്", - "backup_controller_page_backup_selected": "തിരഞ്ഞെടുത്തവ: ", - "backup_controller_page_backup_sub": "ബാക്കപ്പ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും", - "backup_controller_page_created": "സൃഷ്ടിച്ചത്: {date}", - "backup_controller_page_desc_backup": "ആപ്പ് തുറക്കുമ്പോൾ പുതിയ അസറ്റുകൾ യാന്ത്രികമായി സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യാൻ ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓണാക്കുക.", - "backup_controller_page_excluded": "ഒഴിവാക്കിയവ: ", - "backup_controller_page_failed": "പരാജയപ്പെട്ടു ({count})", - "backup_controller_page_filename": "ഫയലിന്റെ പേര്: {filename}[{size}]", - "backup_controller_page_id": "ഐഡി: {id}", - "backup_controller_page_info": "ബാക്കപ്പ് വിവരങ്ങൾ", - "backup_controller_page_none_selected": "ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല", - "backup_controller_page_remainder": "ശേഷിക്കുന്നത്", - "backup_controller_page_remainder_sub": "തിരഞ്ഞെടുത്തവയിൽ നിന്ന് ബാക്കപ്പ് ചെയ്യാനുള്ള ശേഷിക്കുന്ന ഫോട്ടോകളും വീഡിയോകളും", - "backup_controller_page_server_storage": "സെർവർ സ്റ്റോറേജ്", - "backup_controller_page_start_backup": "ബാക്കപ്പ് ആരംഭിക്കുക", - "backup_controller_page_status_off": "യാന്ത്രിക ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓഫാണ്", - "backup_controller_page_status_on": "യാന്ത്രിക ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓണാണ്", - "backup_controller_page_storage_format": "{total}-ൽ {used} ഉപയോഗിച്ചു", - "backup_controller_page_to_backup": "ബാക്കപ്പ് ചെയ്യേണ്ട ആൽബങ്ങൾ", - "backup_controller_page_total_sub": "തിരഞ്ഞെടുത്ത ആൽബങ്ങളിൽ നിന്നുള്ള എല്ലാ സവിശേഷമായ ഫോട്ടോകളും വീഡിയോകളും", - "backup_controller_page_turn_off": "ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓഫാക്കുക", - "backup_controller_page_turn_on": "ഫോർഗ്രൗണ്ട് ബാക്കപ്പ് ഓണാക്കുക", - "backup_controller_page_uploading_file_info": "ഫയൽ വിവരങ്ങൾ അപ്‌ലോഡ് ചെയ്യുന്നു", - "backup_err_only_album": "ഒരേയൊരു ആൽബം നീക്കം ചെയ്യാൻ കഴിയില്ല", - "backup_error_sync_failed": "സിങ്ക് പരാജയപ്പെട്ടു. ബാക്കപ്പ് പ്രോസസ്സ് ചെയ്യാൻ കഴിയില്ല.", - "backup_info_card_assets": "അസറ്റുകൾ", - "backup_manual_cancelled": "റദ്ദാക്കി", - "backup_manual_in_progress": "അപ്‌ലോഡ് ഇതിനകം പുരോഗമിക്കുന്നു. കുറച്ച് സമയത്തിന് ശേഷം ശ്രമിക്കുക", - "backup_manual_success": "വിജയം", - "backup_manual_title": "അപ്‌ലോഡ് നില", - "backup_options": "ബാക്കപ്പ് ഓപ്ഷനുകൾ", - "backup_options_page_title": "ബാക്കപ്പ് ഓപ്ഷനുകൾ", - "backup_setting_subtitle": "പശ്ചാത്തല, മുൻനിര അപ്‌ലോഡ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "backup_settings_subtitle": "അപ്‌ലോഡ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "backward": "പിന്നോട്ട്", - "biometric_auth_enabled": "ബയോമെട്രിക് പ്രാമാണീകരണം പ്രവർത്തനക്ഷമമാക്കി", - "biometric_locked_out": "ബയോമെട്രിക് പ്രാമാണീകരണത്തിൽ നിന്ന് നിങ്ങളെ ലോക്ക് ഔട്ട് ചെയ്തിരിക്കുന്നു", - "biometric_no_options": "ബയോമെട്രിക് ഓപ്ഷനുകളൊന്നും ലഭ്യമല്ല", - "biometric_not_available": "ഈ ഉപകരണത്തിൽ ബയോമെട്രിക് പ്രാമാണീകരണം ലഭ്യമല്ല", - "birthdate_saved": "ജനനത്തീയതി വിജയകരമായി സേവ് ചെയ്തു", - "birthdate_set_description": "ഒരു ഫോട്ടോ എടുക്കുമ്പോഴുള്ള ഈ വ്യക്തിയുടെ പ്രായം കണക്കാക്കാൻ ജനനത്തീയതി ഉപയോഗിക്കുന്നു.", - "blurred_background": "മങ്ങിയ പശ്ചാത്തലം", - "bugs_and_feature_requests": "ബഗുകളും ഫീച്ചർ അഭ്യർത്ഥനകളും", - "build": "ബിൽഡ്", - "build_image": "ഇമേജ് നിർമ്മിക്കുക", - "bulk_delete_duplicates_confirmation": "നിങ്ങൾക്ക് {count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} ബൾക്കായി ഇല്ലാതാക്കണമെന്ന് ഉറപ്പാണോ? ഇത് ഓരോ ഗ്രൂപ്പിലെയും ഏറ്റവും വലിയ അസറ്റ് നിലനിർത്തുകയും മറ്റ് എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും ശാശ്വതമായി ഇല്ലാതാക്കുകയും ചെയ്യും. ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല!", - "bulk_keep_duplicates_confirmation": "നിങ്ങൾക്ക് {count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} നിലനിർത്തണമെന്ന് ഉറപ്പാണോ? ഇത് ഒന്നും ഇല്ലാതാക്കാതെ എല്ലാ ഡ്യൂപ്ലിക്കേറ്റ് ഗ്രൂപ്പുകളെയും പരിഹരിക്കും.", - "bulk_trash_duplicates_confirmation": "നിങ്ങൾക്ക് {count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} ബൾക്കായി ട്രാഷ് ചെയ്യണമെന്ന് ഉറപ്പാണോ? ഇത് ഓരോ ഗ്രൂപ്പിലെയും ഏറ്റവും വലിയ അസറ്റ് നിലനിർത്തുകയും മറ്റ് എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും ട്രാഷ് ചെയ്യുകയും ചെയ്യും.", - "buy": "Immich വാങ്ങുക", - "cache_settings_clear_cache_button": "കാഷെ ക്ലിയർ ചെയ്യുക", - "cache_settings_clear_cache_button_title": "ആപ്പിന്റെ കാഷെ ക്ലിയർ ചെയ്യുന്നു. കാഷെ പുനർനിർമ്മിക്കുന്നതുവരെ ഇത് ആപ്പിന്റെ പ്രകടനത്തെ സാരമായി ബാധിക്കും.", - "cache_settings_duplicated_assets_clear_button": "ക്ലിയർ ചെയ്യുക", - "cache_settings_duplicated_assets_subtitle": "ആപ്പ് അവഗണിച്ച ഫോട്ടോകളും വീഡിയോകളും", - "cache_settings_duplicated_assets_title": "ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ ({count})", - "cache_settings_statistics_album": "ലൈബ്രറി തംബ്നെയിലുകൾ", - "cache_settings_statistics_full": "പൂർണ്ണ ചിത്രങ്ങൾ", - "cache_settings_statistics_shared": "പങ്കിട്ട ആൽബം തംബ്നെയിലുകൾ", - "cache_settings_statistics_thumbnail": "തംബ്നെയിലുകൾ", - "cache_settings_statistics_title": "കാഷെ ഉപയോഗം", - "cache_settings_subtitle": "Immich മൊബൈൽ ആപ്ലിക്കേഷന്റെ കാഷിംഗ് സ്വഭാവം നിയന്ത്രിക്കുക", - "cache_settings_tile_subtitle": "പ്രാദേശിക സ്റ്റോറേജ് സ്വഭാവം നിയന്ത്രിക്കുക", - "cache_settings_tile_title": "പ്രാദേശിക സ്റ്റോറേജ്", - "cache_settings_title": "കാഷിംഗ് ക്രമീകരണങ്ങൾ", - "camera": "ക്യാമറ", - "camera_brand": "ക്യാമറ ബ്രാൻഡ്", - "camera_model": "ക്യാമറ മോഡൽ", - "cancel": "റദ്ദാക്കുക", - "cancel_search": "തിരയൽ റദ്ദാക്കുക", - "canceled": "റദ്ദാക്കി", - "canceling": "റദ്ദാക്കുന്നു", - "cannot_merge_people": "ആളുകളെ ലയിപ്പിക്കാൻ കഴിയില്ല", - "cannot_undo_this_action": "നിങ്ങൾക്ക് ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല!", - "cannot_update_the_description": "വിവരണം അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "cast": "കാസ്റ്റ്", - "cast_description": "ലഭ്യമായ കാസ്റ്റ് ഡെസ്റ്റിനേഷനുകൾ കോൺഫിഗർ ചെയ്യുക", - "change_date": "തീയതി മാറ്റുക", - "change_description": "വിവരണം മാറ്റുക", - "change_display_order": "പ്രദർശന ക്രമം മാറ്റുക", - "change_expiration_time": "കാലഹരണപ്പെടുന്ന സമയം മാറ്റുക", - "change_location": "സ്ഥാനം മാറ്റുക", - "change_name": "പേര് മാറ്റുക", - "change_name_successfully": "പേര് വിജയകരമായി മാറ്റി", - "change_password": "പാസ്‌വേഡ് മാറ്റുക", - "change_password_description": "ഇത് നിങ്ങൾ ആദ്യമായി സിസ്റ്റത്തിൽ സൈൻ ഇൻ ചെയ്യുന്നതുകൊണ്ടോ അല്ലെങ്കിൽ നിങ്ങളുടെ പാസ്‌വേഡ് മാറ്റാൻ ഒരു അഭ്യർത്ഥന നടത്തിയതുകൊണ്ടോ ആകാം. ദയവായി താഴെ പുതിയ പാസ്‌വേഡ് നൽകുക.", - "change_password_form_confirm_password": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", - "change_password_form_description": "നമസ്കാരം {name},\n\nഇത് നിങ്ങൾ ആദ്യമായി സിസ്റ്റത്തിൽ സൈൻ ഇൻ ചെയ്യുന്നതുകൊണ്ടോ അല്ലെങ്കിൽ നിങ്ങളുടെ പാസ്‌വേഡ് മാറ്റാൻ ഒരു അഭ്യർത്ഥന നടത്തിയതുകൊണ്ടോ ആകാം. ദയവായി താഴെ പുതിയ പാസ്‌വേഡ് നൽകുക.", - "change_password_form_log_out": "മറ്റെല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യുക", - "change_password_form_log_out_description": "മറ്റെല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യാൻ ശുപാർശ ചെയ്യുന്നു", - "change_password_form_new_password": "പുതിയ പാസ്‌വേഡ്", - "change_password_form_password_mismatch": "പാസ്‌വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല", - "change_password_form_reenter_new_password": "പുതിയ പാസ്‌വേഡ് വീണ്ടും നൽകുക", - "change_pin_code": "പിൻ കോഡ് മാറ്റുക", - "change_your_password": "നിങ്ങളുടെ പാസ്‌വേഡ് മാറ്റുക", - "changed_visibility_successfully": "ദൃശ്യത വിജയകരമായി മാറ്റി", - "charging": "ചാർജ്ജ് ചെയ്യുന്നു", - "charging_requirement_mobile_backup": "പശ്ചാത്തല ബാക്കപ്പിന് ഉപകരണം ചാർജ് ചെയ്യേണ്ടതുണ്ട്", - "check_corrupt_asset_backup": "കേടായ അസറ്റ് ബാക്കപ്പുകൾ പരിശോധിക്കുക", - "check_corrupt_asset_backup_button": "പരിശോധന നടത്തുക", - "check_corrupt_asset_backup_description": "എല്ലാ അസറ്റുകളും ബാക്കപ്പ് ചെയ്ത ശേഷം, വൈ-ഫൈയിൽ മാത്രം ഈ പരിശോധന നടത്തുക. നടപടിക്രമത്തിന് കുറച്ച് മിനിറ്റുകൾ എടുത്തേക്കാം.", - "check_logs": "ലോഗുകൾ പരിശോധിക്കുക", - "choose_matching_people_to_merge": "ലയിപ്പിക്കാൻ പൊരുത്തപ്പെടുന്ന ആളുകളെ തിരഞ്ഞെടുക്കുക", - "city": "നഗരം", - "clear": "ക്ലിയർ ചെയ്യുക", - "clear_all": "എല്ലാം ക്ലിയർ ചെയ്യുക", - "clear_all_recent_searches": "അടുത്തിടെ നടത്തിയ എല്ലാ തിരയലുകളും ക്ലിയർ ചെയ്യുക", - "clear_file_cache": "ഫയൽ കാഷെ ക്ലിയർ ചെയ്യുക", - "clear_message": "സന്ദേശം ക്ലിയർ ചെയ്യുക", - "clear_value": "മൂല്യം ക്ലിയർ ചെയ്യുക", - "client_cert_dialog_msg_confirm": "ശരി", - "client_cert_enter_password": "പാസ്‌വേഡ് നൽകുക", - "client_cert_import": "ഇമ്പോർട്ട് ചെയ്യുക", - "client_cert_import_success_msg": "ക്ലയിന്റ് സർട്ടിഫിക്കറ്റ് ഇമ്പോർട്ടുചെയ്‌തു", - "client_cert_invalid_msg": "അസാധുവായ സർട്ടിഫിക്കറ്റ് ഫയൽ അല്ലെങ്കിൽ തെറ്റായ പാസ്‌വേഡ്", - "client_cert_remove_msg": "ക്ലയിന്റ് സർട്ടിഫിക്കറ്റ് നീക്കംചെയ്‌തു", - "client_cert_subtitle": "PKCS12 (.p12, .pfx) ഫോർമാറ്റ് മാത്രം പിന്തുണയ്ക്കുന്നു. ലോഗിൻ ചെയ്യുന്നതിന് മുമ്പ് മാത്രമേ സർട്ടിഫിക്കറ്റ് ഇമ്പോർട്ട്/നീക്കംചെയ്യൽ ലഭ്യമാകൂ", - "client_cert_title": "SSL ക്ലയിന്റ് സർട്ടിഫിക്കറ്റ് [പരീക്ഷണാടിസ്ഥാനത്തിൽ]", - "clockwise": "പ്രദക്ഷിണമായി", - "close": "അടയ്ക്കുക", - "collapse": "ചുരുക്കുക", - "collapse_all": "എല്ലാം ചുരുക്കുക", - "color": "നിറം", - "color_theme": "കളർ തീം", - "comment_deleted": "അഭിപ്രായം ഇല്ലാതാക്കി", - "comment_options": "അഭിപ്രായത്തിനുള്ള ഓപ്ഷനുകൾ", - "comments_and_likes": "അഭിപ്രായങ്ങളും ലൈക്കുകളും", - "comments_are_disabled": "അഭിപ്രായങ്ങൾ പ്രവർത്തനരഹിതമാക്കി", - "common_create_new_album": "പുതിയ ആൽബം ഉണ്ടാക്കുക", - "completed": "പൂർത്തിയായി", - "confirm": "സ്ഥിരീകരിക്കുക", - "confirm_admin_password": "അഡ്മിൻ പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", - "confirm_delete_face": "{name} എന്ന മുഖം അസറ്റിൽ നിന്ന് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "confirm_delete_shared_link": "ഈ പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "confirm_keep_this_delete_others": "ഈ അസറ്റ് ഒഴികെ സ്റ്റാക്കിലെ മറ്റെല്ലാ അസറ്റുകളും ഇല്ലാതാക്കപ്പെടും. തുടരണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "confirm_new_pin_code": "പുതിയ പിൻ കോഡ് സ്ഥിരീകരിക്കുക", - "confirm_password": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", - "confirm_tag_face": "ഈ മുഖം {name} എന്ന് ടാഗ് ചെയ്യണോ?", - "confirm_tag_face_unnamed": "ഈ മുഖം ടാഗ് ചെയ്യണോ?", - "connected_device": "ബന്ധിപ്പിച്ച ഉപകരണം", - "connected_to": "ഇതുമായി ബന്ധിപ്പിച്ചു", - "contain": "ഉൾക്കൊള്ളുക", - "context": "സന്ദർഭം", - "continue": "തുടരുക", - "control_bottom_app_bar_create_new_album": "പുതിയ ആൽബം ഉണ്ടാക്കുക", - "control_bottom_app_bar_delete_from_immich": "Immich-ൽ നിന്ന് ഇല്ലാതാക്കുക", - "control_bottom_app_bar_delete_from_local": "ഉപകരണത്തിൽ നിന്ന് ഇല്ലാതാക്കുക", - "control_bottom_app_bar_edit_location": "സ്ഥാനം എഡിറ്റുചെയ്യുക", - "control_bottom_app_bar_edit_time": "തീയതിയും സമയവും എഡിറ്റുചെയ്യുക", - "control_bottom_app_bar_share_link": "ലിങ്ക് പങ്കിടുക", - "control_bottom_app_bar_share_to": "ഇതിലേക്ക് പങ്കിടുക", - "control_bottom_app_bar_trash_from_immich": "ട്രാഷിലേക്ക് മാറ്റുക", - "copied_image_to_clipboard": "ചിത്രം ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി.", - "copied_to_clipboard": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി!", - "copy_error": "പകർത്തുന്നതിൽ പിശക്", - "copy_file_path": "ഫയൽ പാത്ത് പകർത്തുക", - "copy_image": "ചിത്രം പകർത്തുക", - "copy_link": "ലിങ്ക് പകർത്തുക", - "copy_link_to_clipboard": "ലിങ്ക് ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക", - "copy_password": "പാസ്‌വേഡ് പകർത്തുക", - "copy_to_clipboard": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക", - "country": "രാജ്യം", - "cover": "കവർ", - "covers": "കവറുകൾ", - "create": "സൃഷ്ടിക്കുക", - "create_album": "ആൽബം ഉണ്ടാക്കുക", - "create_album_page_untitled": "പേരില്ലാത്തത്", - "create_api_key": "എപിഐ കീ സൃഷ്ടിക്കുക", - "create_library": "ലൈബ്രറി ഉണ്ടാക്കുക", - "create_link": "ലിങ്ക് ഉണ്ടാക്കുക", - "create_link_to_share": "പങ്കിടാൻ ലിങ്ക് ഉണ്ടാക്കുക", - "create_link_to_share_description": "തിരഞ്ഞെടുത്ത ഫോട്ടോ(കൾ) കാണാൻ ലിങ്കുള്ള ആർക്കും അനുവാദം നൽകുക", - "create_new": "പുതിയത് ഉണ്ടാക്കുക", - "create_new_person": "പുതിയ വ്യക്തിയെ ഉണ്ടാക്കുക", - "create_new_person_hint": "തിരഞ്ഞെടുത്ത അസറ്റുകൾ ഒരു പുതിയ വ്യക്തിക്ക് നൽകുക", - "create_new_user": "പുതിയ ഉപയോക്താവിനെ ഉണ്ടാക്കുക", - "create_shared_album_page_share_add_assets": "അസറ്റുകൾ ചേർക്കുക", - "create_shared_album_page_share_select_photos": "ഫോട്ടോകൾ തിരഞ്ഞെടുക്കുക", - "create_shared_link": "പങ്കിട്ട ലിങ്ക് ഉണ്ടാക്കുക", - "create_tag": "ടാഗ് ഉണ്ടാക്കുക", - "create_tag_description": "ഒരു പുതിയ ടാഗ് ഉണ്ടാക്കുക. നെസ്റ്റഡ് ടാഗുകൾക്കായി, ഫോർവേഡ് സ്ലാഷുകൾ ഉൾപ്പെടെ ടാഗിന്റെ പൂർണ്ണ പാത നൽകുക.", - "create_user": "ഉപയോക്താവിനെ ഉണ്ടാക്കുക", - "created": "സൃഷ്ടിച്ചത്", - "created_at": "സൃഷ്ടിച്ചത്", - "creating_linked_albums": "ബന്ധിപ്പിച്ച ആൽബങ്ങൾ ഉണ്ടാക്കുന്നു...", - "crop": "ക്രോപ്പ് ചെയ്യുക", - "curated_object_page_title": "വസ്തുക്കൾ", - "current_device": "നിലവിലെ ഉപകരണം", - "current_pin_code": "നിലവിലെ പിൻ കോഡ്", - "current_server_address": "നിലവിലെ സെർവർ വിലാസം", - "custom_locale": "കസ്റ്റം ലൊക്കേൽ", - "custom_locale_description": "ഭാഷയെയും പ്രദേശത്തെയും അടിസ്ഥാനമാക്കി തീയതികളും അക്കങ്ങളും ഫോർമാറ്റ് ചെയ്യുക", - "custom_url": "കസ്റ്റം URL", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "ഇരുണ്ടത്", - "dark_theme": "ഡാർക്ക് തീം ടോഗിൾ ചെയ്യുക", - "date": "തീയതി", - "date_after": "ഇതിനു ശേഷമുള്ള തീയതി", - "date_and_time": "തീയതിയും സമയവും", - "date_before": "ഇതിനു മുമ്പുള്ള തീയതി", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "ജനനത്തീയതി വിജയകരമായി സേവ് ചെയ്തു", - "date_range": "തീയതി പരിധി", - "day": "ദിവസം", - "days": "ദിവസങ്ങൾ", - "deduplicate_all": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും ഒഴിവാക്കുക", - "deduplication_criteria_1": "ചിത്രത്തിന്റെ വലുപ്പം (ബൈറ്റുകളിൽ)", - "deduplication_criteria_2": "EXIF ഡാറ്റയുടെ എണ്ണം", - "deduplication_info": "ഡ്യൂപ്ലിക്കേഷൻ ഒഴിവാക്കൽ വിവരം", - "deduplication_info_description": "അസറ്റുകൾ യാന്ത്രികമായി മുൻകൂട്ടി തിരഞ്ഞെടുക്കുന്നതിനും ഡ്യൂപ്ലിക്കേറ്റുകൾ ബൾക്കായി നീക്കം ചെയ്യുന്നതിനും, ഞങ്ങൾ ഇവ പരിഗണിക്കുന്നു:", - "default_locale": "ഡിഫോൾട്ട് ലൊക്കേൽ", - "default_locale_description": "നിങ്ങളുടെ ബ്രൗസർ ലൊക്കേലിനെ അടിസ്ഥാനമാക്കി തീയതികളും അക്കങ്ങളും ഫോർമാറ്റ് ചെയ്യുക", - "delete": "ഇല്ലാതാക്കുക", - "delete_action_confirmation_message": "ഈ അസറ്റ് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഈ പ്രവർത്തനം അസറ്റിനെ സെർവറിന്റെ ട്രാഷിലേക്ക് മാറ്റും, കൂടാതെ ഇത് പ്രാദേശികമായി ഇല്ലാതാക്കണോ എന്ന് ചോദിക്കുകയും ചെയ്യും", - "delete_action_prompt": "{count} എണ്ണം ഇല്ലാതാക്കി", - "delete_album": "ആൽബം ഇല്ലാതാക്കുക", - "delete_api_key_prompt": "ഈ API കീ ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "delete_dialog_alert": "ഈ ഇനങ്ങൾ Immich-ൽ നിന്നും നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്നും ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും", - "delete_dialog_alert_local": "ഈ ഇനങ്ങൾ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്യപ്പെടും, പക്ഷേ Immich സെർവറിൽ തുടർന്നും ലഭ്യമാകും", - "delete_dialog_alert_local_non_backed_up": "ചില ഇനങ്ങൾ Immich-ലേക്ക് ബാക്കപ്പ് ചെയ്തിട്ടില്ല, അവ നിങ്ങളുടെ ഉപകരണത്തിൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്യപ്പെടും", - "delete_dialog_alert_remote": "ഈ ഇനങ്ങൾ Immich സെർവറിൽ നിന്ന് ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും", - "delete_dialog_ok_force": "എന്തായാലും ഇല്ലാതാക്കുക", - "delete_dialog_title": "ശാശ്വതമായി ഇല്ലാതാക്കുക", - "delete_duplicates_confirmation": "ഈ ഡ്യൂപ്ലിക്കേറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "delete_face": "മുഖം ഇല്ലാതാക്കുക", - "delete_key": "കീ ഇല്ലാതാക്കുക", - "delete_library": "ലൈബ്രറി ഇല്ലാതാക്കുക", - "delete_link": "ലിങ്ക് ഇല്ലാതാക്കുക", - "delete_local_action_prompt": "{count} എണ്ണം പ്രാദേശികമായി ഇല്ലാതാക്കി", - "delete_local_dialog_ok_backed_up_only": "ബാക്കപ്പ് ചെയ്തവ മാത്രം ഇല്ലാതാക്കുക", - "delete_local_dialog_ok_force": "എന്തായാലും ഇല്ലാതാക്കുക", - "delete_others": "മറ്റുള്ളവ ഇല്ലാതാക്കുക", - "delete_permanently": "ശാശ്വതമായി ഇല്ലാതാക്കുക", - "delete_permanently_action_prompt": "{count} എണ്ണം ശാശ്വതമായി ഇല്ലാതാക്കി", - "delete_shared_link": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കുക", - "delete_shared_link_dialog_title": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കുക", - "delete_tag": "ടാഗ് ഇല്ലാതാക്കുക", - "delete_tag_confirmation_prompt": "{tagName} എന്ന ടാഗ് ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "delete_user": "ഉപയോക്താവിനെ ഇല്ലാതാക്കുക", - "deleted_shared_link": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കി", - "deletes_missing_assets": "ഡിസ്കിൽ നിന്ന് കാണാതായ അസറ്റുകൾ ഇല്ലാതാക്കുന്നു", - "description": "വിവരണം", - "description_input_hint_text": "വിവരണം ചേർക്കുക...", - "description_input_submit_error": "വിവരണം അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പിശക്, കൂടുതൽ വിവരങ്ങൾക്ക് ലോഗ് പരിശോധിക്കുക", - "deselect_all": "എല്ലാം തിരഞ്ഞെടുത്തത് മാറ്റുക", - "details": "വിശദാംശങ്ങൾ", - "direction": "ദിശ", - "disabled": "പ്രവർത്തനരഹിതം", - "disallow_edits": "എഡിറ്റുകൾ അനുവദിക്കരുത്", - "discord": "ഡിസ്കോർഡ്", - "discover": "കണ്ടെത്തുക", - "discovered_devices": "കണ്ടെത്തിയ ഉപകരണങ്ങൾ", - "dismiss_all_errors": "എല്ലാ പിശകുകളും തള്ളിക്കളയുക", - "dismiss_error": "പിശക് തള്ളിക്കളയുക", - "display_options": "പ്രദർശന ഓപ്ഷനുകൾ", - "display_order": "പ്രദർശന ക്രമം", - "display_original_photos": "യഥാർത്ഥ ഫോട്ടോകൾ പ്രദർശിപ്പിക്കുക", - "display_original_photos_setting_description": "യഥാർത്ഥ അസറ്റ് വെബ്-അനുയോജ്യമാകുമ്പോൾ, തംബ്നെയിലുകൾക്ക് പകരം യഥാർത്ഥ ഫോട്ടോ പ്രദർശിപ്പിക്കാൻ മുൻഗണന നൽകുക. ഇത് ഫോട്ടോ പ്രദർശന വേഗത കുറയ്ക്കാൻ ഇടയാക്കും.", - "do_not_show_again": "ഈ സന്ദേശം വീണ്ടും കാണിക്കരുത്", - "documentation": "ഡോക്യുമെന്റേഷൻ", - "done": "പൂർത്തിയായി", - "download": "ഡൗൺലോഡ്", - "download_action_prompt": "{count} അസറ്റുകൾ ഡൗൺലോഡ് ചെയ്യുന്നു", - "download_canceled": "ഡൗൺലോഡ് റദ്ദാക്കി", - "download_complete": "ഡൗൺലോഡ് പൂർത്തിയായി", - "download_enqueue": "ഡൗൺലോഡ് ക്യൂവിൽ ചേർത്തു", - "download_error": "ഡൗൺലോഡ് പിശക്", - "download_failed": "ഡൗൺലോഡ് പരാജയപ്പെട്ടു", - "download_finished": "ഡൗൺലോഡ് കഴിഞ്ഞു", - "download_include_embedded_motion_videos": "ഉൾച്ചേർത്ത വീഡിയോകൾ", - "download_include_embedded_motion_videos_description": "ചലിക്കുന്ന ഫോട്ടോകളിൽ ഉൾച്ചേർത്ത വീഡിയോകൾ ഒരു പ്രത്യേക ഫയലായി ഉൾപ്പെടുത്തുക", - "download_notfound": "ഡൗൺലോഡ് കണ്ടെത്തിയില്ല", - "download_paused": "ഡൗൺലോഡ് താൽക്കാലികമായി നിർത്തി", - "download_settings": "ഡൗൺലോഡ് ക്രമീകരണങ്ങൾ", - "download_settings_description": "അസറ്റ് ഡൗൺലോഡുമായി ബന്ധപ്പെട്ട ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "download_started": "ഡൗൺലോഡ് ആരംഭിച്ചു", - "download_sucess": "ഡൗൺലോഡ് വിജയിച്ചു", - "download_sucess_android": "മീഡിയ DCIM/Immich എന്നതിലേക്ക് ഡൗൺലോഡ് ചെയ്തിരിക്കുന്നു", - "download_waiting_to_retry": "വീണ്ടും ശ്രമിക്കാൻ കാത്തിരിക്കുന്നു", - "downloading": "ഡൗൺലോഡ് ചെയ്യുന്നു", - "downloading_asset_filename": "{filename} എന്ന അസറ്റ് ഡൗൺലോഡ് ചെയ്യുന്നു", - "downloading_media": "മീഡിയ ഡൗൺലോഡ് ചെയ്യുന്നു", - "drop_files_to_upload": "അപ്‌ലോഡ് ചെയ്യാൻ ഫയലുകൾ എവിടെയും ഡ്രോപ്പ് ചെയ്യുക", - "duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ", - "duplicates_description": "ഏതാണ് ഡ്യൂപ്ലിക്കേറ്റ് എന്ന് സൂചിപ്പിച്ച് ഓരോ ഗ്രൂപ്പും പരിഹരിക്കുക", - "duration": "ദൈർഘ്യം", - "edit": "തിരുത്തുക", - "edit_album": "ആൽബം എഡിറ്റുചെയ്യുക", - "edit_avatar": "അവതാർ എഡിറ്റുചെയ്യുക", - "edit_birthday": "ജന്മദിനം എഡിറ്റുചെയ്യുക", - "edit_date": "തീയതി എഡിറ്റുചെയ്യുക", - "edit_date_and_time": "തീയതിയും സമയവും എഡിറ്റുചെയ്യുക", - "edit_date_and_time_action_prompt": "{count} എണ്ണത്തിന്റെ തീയതിയും സമയവും എഡിറ്റുചെയ്തു", - "edit_date_and_time_by_offset": "ഓഫ്സെറ്റ് ഉപയോഗിച്ച് തീയതി മാറ്റുക", - "edit_date_and_time_by_offset_interval": "പുതിയ തീയതി പരിധി: {from}-{to}", - "edit_description": "വിവരണം എഡിറ്റുചെയ്യുക", - "edit_description_prompt": "ദയവായി ഒരു പുതിയ വിവരണം തിരഞ്ഞെടുക്കുക:", - "edit_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ എഡിറ്റുചെയ്യുക", - "edit_faces": "മുഖങ്ങൾ എഡിറ്റുചെയ്യുക", - "edit_key": "കീ എഡിറ്റുചെയ്യുക", - "edit_link": "ലിങ്ക് എഡിറ്റുചെയ്യുക", - "edit_location": "സ്ഥാനം എഡിറ്റുചെയ്യുക", - "edit_location_action_prompt": "{count} എണ്ണത്തിന്റെ സ്ഥാനം എഡിറ്റുചെയ്തു", - "edit_location_dialog_title": "സ്ഥാനം", - "edit_name": "പേര് എഡിറ്റുചെയ്യുക", - "edit_people": "ആളുകളെ എഡിറ്റുചെയ്യുക", - "edit_tag": "ടാഗ് എഡിറ്റുചെയ്യുക", - "edit_title": "ശീർഷകം എഡിറ്റുചെയ്യുക", - "edit_user": "ഉപയോക്താവിനെ എഡിറ്റുചെയ്യുക", - "editor": "എഡിറ്റർ", - "editor_close_without_save_prompt": "മാറ്റങ്ങൾ സേവ് ചെയ്യില്ല", - "editor_close_without_save_title": "എഡിറ്റർ അടയ്ക്കണോ?", - "email": "ഇമെയിൽ", - "email_notifications": "ഇമെയിൽ അറിയിപ്പുകൾ", - "empty_folder": "ഈ ഫോൾഡർ ശൂന്യമാണ്", - "empty_trash": "ട്രാഷ് ശൂന്യമാക്കുക", - "empty_trash_confirmation": "ട്രാഷ് ശൂന്യമാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഇത് ട്രാഷിലെ എല്ലാ അസറ്റുകളും Immich-ൽ നിന്ന് ശാശ്വതമായി നീക്കംചെയ്യും.\nനിങ്ങൾക്ക് ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല!", - "enable": "പ്രവർത്തനക്ഷമമാക്കുക", - "enable_backup": "ബാക്കപ്പ് പ്രവർത്തനക്ഷമമാക്കുക", - "enable_biometric_auth_description": "ബയോമെട്രിക് പ്രാമാണീകരണം പ്രവർത്തനക്ഷമമാക്കാൻ നിങ്ങളുടെ പിൻ കോഡ് നൽകുക", - "enabled": "പ്രവർത്തനക്ഷമമാക്കി", - "end_date": "അവസാന തീയതി", - "enqueued": "ക്യൂവിൽ ചേർത്തു", - "enter_wifi_name": "വൈ-ഫൈയുടെ പേര് നൽകുക", - "enter_your_pin_code": "നിങ്ങളുടെ പിൻ കോഡ് നൽകുക", - "enter_your_pin_code_subtitle": "ലോക്ക് ചെയ്ത ഫോൾഡർ ആക്‌സസ് ചെയ്യാൻ നിങ്ങളുടെ പിൻ കോഡ് നൽകുക", - "error": "പിശക്", - "error_change_sort_album": "ആൽബത്തിന്റെ സോർട്ട് ഓർഡർ മാറ്റുന്നതിൽ പരാജയപ്പെട്ടു", - "error_delete_face": "അസറ്റിൽ നിന്ന് മുഖം ഇല്ലാതാക്കുന്നതിൽ പിശക്", - "error_getting_places": "സ്ഥലങ്ങൾ ലഭിക്കുന്നതിൽ പിശക്", - "error_loading_image": "ചിത്രം ലോഡുചെയ്യുന്നതിൽ പിശക്", - "error_loading_partners": "പങ്കാളികളെ ലോഡുചെയ്യുന്നതിൽ പിശക്: {error}", - "error_saving_image": "പിശക്: {error}", - "error_tag_face_bounding_box": "മുഖം ടാഗ് ചെയ്യുന്നതിൽ പിശക് - ബൗണ്ടിംഗ് ബോക്സ് കോർഡിനേറ്റുകൾ ലഭിക്കുന്നില്ല", - "error_title": "പിശക് - എന്തോ കുഴപ്പം സംഭവിച്ചു", - "errors": { - "cannot_navigate_next_asset": "അടുത്ത അസറ്റിലേക്ക് പോകാൻ കഴിയില്ല", - "cannot_navigate_previous_asset": "മുമ്പത്തെ അസറ്റിലേക്ക് പോകാൻ കഴിയില്ല", - "cant_apply_changes": "മാറ്റങ്ങൾ പ്രയോഗിക്കാൻ കഴിയില്ല", - "cant_change_activity": "പ്രവർത്തനം {enabled, select, true {പ്രവർത്തനരഹിതമാക്കാൻ} other {പ്രവർത്തനക്ഷമമാക്കാൻ}} കഴിയില്ല", - "cant_change_asset_favorite": "അസറ്റിന്റെ പ്രിയപ്പെട്ടവ മാറ്റാൻ കഴിയില്ല", - "cant_change_metadata_assets_count": "{count, plural, one {# അസറ്റിന്റെ} other {# അസറ്റുകളുടെ}} മെറ്റാഡാറ്റ മാറ്റാൻ കഴിയില്ല", - "cant_get_faces": "മുഖങ്ങൾ ലഭിക്കുന്നില്ല", - "cant_get_number_of_comments": "അഭിപ്രായങ്ങളുടെ എണ്ണം ലഭിക്കുന്നില്ല", - "cant_search_people": "ആളുകളെ തിരയാൻ കഴിയില്ല", - "cant_search_places": "സ്ഥലങ്ങൾ തിരയാൻ കഴിയില്ല", - "error_adding_assets_to_album": "ആൽബത്തിലേക്ക് അസറ്റുകൾ ചേർക്കുന്നതിൽ പിശക്", - "error_adding_users_to_album": "ആൽബത്തിലേക്ക് ഉപയോക്താക്കളെ ചേർക്കുന്നതിൽ പിശക്", - "error_deleting_shared_user": "പങ്കിട്ട ഉപയോക്താവിനെ ഇല്ലാതാക്കുന്നതിൽ പിശക്", - "error_downloading": "{filename} ഡൗൺലോഡ് ചെയ്യുന്നതിൽ പിശക്", - "error_hiding_buy_button": "വാങ്ങാനുള്ള ബട്ടൺ മറയ്ക്കുന്നതിൽ പിശക്", - "error_removing_assets_from_album": "ആൽബത്തിൽ നിന്ന് അസറ്റുകൾ നീക്കം ചെയ്യുന്നതിൽ പിശക്, കൂടുതൽ വിവരങ്ങൾക്ക് കൺസോൾ പരിശോധിക്കുക", - "error_selecting_all_assets": "എല്ലാ അസറ്റുകളും തിരഞ്ഞെടുക്കുന്നതിൽ പിശക്", - "exclusion_pattern_already_exists": "ഈ ഒഴിവാക്കൽ പാറ്റേൺ ഇതിനകം നിലവിലുണ്ട്.", - "failed_to_create_album": "ആൽബം ഉണ്ടാക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_create_shared_link": "പങ്കിട്ട ലിങ്ക് ഉണ്ടാക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_edit_shared_link": "പങ്കിട്ട ലിങ്ക് എഡിറ്റുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_get_people": "ആളുകളെ ലഭിക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_keep_this_delete_others": "ഈ അസറ്റ് നിലനിർത്തി മറ്റ് അസറ്റുകൾ ഇല്ലാതാക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_load_asset": "അസറ്റ് ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_load_assets": "അസറ്റുകൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_load_notifications": "അറിയിപ്പുകൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_load_people": "ആളുകളെ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_remove_product_key": "പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_reset_pin_code": "പിൻ കോഡ് റീസെറ്റ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_stack_assets": "അസറ്റുകൾ സ്റ്റാക്ക് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_unstack_assets": "അസറ്റുകൾ അൺ-സ്റ്റാക്ക് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_update_notification_status": "അറിയിപ്പിന്റെ നില അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "incorrect_email_or_password": "തെറ്റായ ഇമെയിൽ അല്ലെങ്കിൽ പാസ്‌വേഡ്", - "library_folder_already_exists": "ഈ ഇമ്പോർട്ട് പാത്ത് നിലവിലുണ്ട്.", - "paths_validation_failed": "{paths, plural, one {# പാത്ത്} other {# പാത്തുകൾ}} സാധൂകരണത്തിൽ പരാജയപ്പെട്ടു", - "profile_picture_transparent_pixels": "പ്രൊഫൈൽ ചിത്രങ്ങൾക്ക് സുതാര്യമായ പിക്സലുകൾ ഉണ്ടാകരുത്. ദയവായി സൂം ഇൻ ചെയ്യുക കൂടാതെ/അല്ലെങ്കിൽ ചിത്രം നീക്കുക.", - "quota_higher_than_disk_size": "ഡിസ്ക് വലുപ്പത്തേക്കാൾ ഉയർന്ന ക്വാട്ട നിങ്ങൾ സജ്ജമാക്കിയിരിക്കുന്നു", - "something_went_wrong": "എന്തോ കുഴപ്പം സംഭവിച്ചു", - "unable_to_add_album_users": "ആൽബത്തിലേക്ക് ഉപയോക്താക്കളെ ചേർക്കാൻ കഴിയില്ല", - "unable_to_add_assets_to_shared_link": "പങ്കിട്ട ലിങ്കിലേക്ക് അസറ്റുകൾ ചേർക്കാൻ കഴിയില്ല", - "unable_to_add_comment": "അഭിപ്രായം ചേർക്കാൻ കഴിയില്ല", - "unable_to_add_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ ചേർക്കാൻ കഴിയില്ല", - "unable_to_add_partners": "പങ്കാളികളെ ചേർക്കാൻ കഴിയില്ല", - "unable_to_add_remove_archive": "ആർക്കൈവിൽ {archived, select, true {നിന്ന് അസറ്റ് നീക്കംചെയ്യാൻ} other {ലേക്ക് അസറ്റ് ചേർക്കാൻ}} കഴിയില്ല", - "unable_to_add_remove_favorites": "പ്രിയപ്പെട്ടവയിലേക്ക് {favorite, select, true {അസറ്റ് ചേർക്കാൻ} other {നിന്ന് അസറ്റ് നീക്കംചെയ്യാൻ}} കഴിയില്ല", - "unable_to_archive_unarchive": "{archived, select, true {ആർക്കൈവ് ചെയ്യാൻ} other {അൺആർക്കൈവ് ചെയ്യാൻ}} കഴിയില്ല", - "unable_to_change_album_user_role": "ആൽബം ഉപയോക്താവിന്റെ റോൾ മാറ്റാൻ കഴിയില്ല", - "unable_to_change_date": "തീയതി മാറ്റാൻ കഴിയില്ല", - "unable_to_change_description": "വിവരണം മാറ്റാൻ കഴിയില്ല", - "unable_to_change_favorite": "അസറ്റിന്റെ പ്രിയപ്പെട്ടവ മാറ്റാൻ കഴിയില്ല", - "unable_to_change_location": "സ്ഥാനം മാറ്റാൻ കഴിയില്ല", - "unable_to_change_password": "പാസ്‌വേഡ് മാറ്റാൻ കഴിയില്ല", - "unable_to_change_visibility": "{count, plural, one {# വ്യക്തിയുടെ} other {# ആളുകളുടെ}} ദൃശ്യത മാറ്റാൻ കഴിയില്ല", - "unable_to_complete_oauth_login": "OAuth ലോഗിൻ പൂർത്തിയാക്കാൻ കഴിയില്ല", - "unable_to_connect": "ബന്ധിപ്പിക്കാൻ കഴിയില്ല", - "unable_to_copy_to_clipboard": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്താൻ കഴിയില്ല, നിങ്ങൾ https വഴിയാണ് പേജ് ആക്‌സസ് ചെയ്യുന്നതെന്ന് ഉറപ്പാക്കുക", - "unable_to_create_admin_account": "അഡ്മിൻ അക്കൗണ്ട് ഉണ്ടാക്കാൻ കഴിയില്ല", - "unable_to_create_api_key": "പുതിയ API കീ ഉണ്ടാക്കാൻ കഴിയില്ല", - "unable_to_create_library": "ലൈബ്രറി ഉണ്ടാക്കാൻ കഴിയില്ല", - "unable_to_create_user": "ഉപയോക്താവിനെ ഉണ്ടാക്കാൻ കഴിയില്ല", - "unable_to_delete_album": "ആൽബം ഇല്ലാതാക്കാൻ കഴിയില്ല", - "unable_to_delete_asset": "അസറ്റ് ഇല്ലാതാക്കാൻ കഴിയില്ല", - "unable_to_delete_assets": "അസറ്റുകൾ ഇല്ലാതാക്കുന്നതിൽ പിശക്", - "unable_to_delete_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ ഇല്ലാതാക്കാൻ കഴിയില്ല", - "unable_to_delete_shared_link": "പങ്കിട്ട ലിങ്ക് ഇല്ലാതാക്കാൻ കഴിയില്ല", - "unable_to_delete_user": "ഉപയോക്താവിനെ ഇല്ലാതാക്കാൻ കഴിയില്ല", - "unable_to_download_files": "ഫയലുകൾ ഡൗൺലോഡ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_edit_exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ എഡിറ്റുചെയ്യാൻ കഴിയില്ല", - "unable_to_empty_trash": "ട്രാഷ് ശൂന്യമാക്കാൻ കഴിയില്ല", - "unable_to_enter_fullscreen": "ഫുൾസ്ക്രീനിൽ പ്രവേശിക്കാൻ കഴിയില്ല", - "unable_to_exit_fullscreen": "ഫുൾസ്ക്രീനിൽ നിന്ന് പുറത്തുകടക്കാൻ കഴിയില്ല", - "unable_to_get_comments_number": "അഭിപ്രായങ്ങളുടെ എണ്ണം ലഭിക്കുന്നില്ല", - "unable_to_get_shared_link": "പങ്കിട്ട ലിങ്ക് ലഭിക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "unable_to_hide_person": "വ്യക്തിയെ മറയ്ക്കാൻ കഴിയില്ല", - "unable_to_link_motion_video": "ചലിക്കുന്ന വീഡിയോ ലിങ്ക് ചെയ്യാൻ കഴിയില്ല", - "unable_to_link_oauth_account": "OAuth അക്കൗണ്ട് ലിങ്ക് ചെയ്യാൻ കഴിയില്ല", - "unable_to_log_out_all_devices": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യാൻ കഴിയില്ല", - "unable_to_log_out_device": "ഉപകരണത്തിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്യാൻ കഴിയില്ല", - "unable_to_login_with_oauth": "OAuth ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യാൻ കഴിയില്ല", - "unable_to_play_video": "വീഡിയോ പ്ലേ ചെയ്യാൻ കഴിയില്ല", - "unable_to_reassign_assets_existing_person": "{name, select, null {നിലവിലുള്ള ഒരു വ്യക്തിക്ക്} other {{name}-ന്}} അസറ്റുകൾ വീണ്ടും നൽകാൻ കഴിയില്ല", - "unable_to_reassign_assets_new_person": "ഒരു പുതിയ വ്യക്തിക്ക് അസറ്റുകൾ വീണ്ടും നൽകാൻ കഴിയില്ല", - "unable_to_refresh_user": "ഉപയോക്താവിനെ പുതുക്കാൻ കഴിയില്ല", - "unable_to_remove_album_users": "ആൽബത്തിൽ നിന്ന് ഉപയോക്താക്കളെ നീക്കംചെയ്യാൻ കഴിയില്ല", - "unable_to_remove_api_key": "API കീ നീക്കംചെയ്യാൻ കഴിയില്ല", - "unable_to_remove_assets_from_shared_link": "പങ്കിട്ട ലിങ്കിൽ നിന്ന് അസറ്റുകൾ നീക്കംചെയ്യാൻ കഴിയില്ല", - "unable_to_remove_library": "ലൈബ്രറി നീക്കംചെയ്യാൻ കഴിയില്ല", - "unable_to_remove_partner": "പങ്കാളിയെ നീക്കംചെയ്യാൻ കഴിയില്ല", - "unable_to_remove_reaction": "പ്രതികരണം നീക്കംചെയ്യാൻ കഴിയില്ല", - "unable_to_reset_password": "പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_reset_pin_code": "പിൻ കോഡ് റീസെറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_resolve_duplicate": "ഡ്യൂപ്ലിക്കേറ്റ് പരിഹരിക്കാൻ കഴിയില്ല", - "unable_to_restore_assets": "അസറ്റുകൾ പുനഃസ്ഥാപിക്കാൻ കഴിയില്ല", - "unable_to_restore_trash": "ട്രാഷ് പുനഃസ്ഥാപിക്കാൻ കഴിയില്ല", - "unable_to_restore_user": "ഉപയോക്താവിനെ പുനഃസ്ഥാപിക്കാൻ കഴിയില്ല", - "unable_to_save_album": "ആൽബം സേവ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_save_api_key": "API കീ സേവ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_save_date_of_birth": "ജനനത്തീയതി സേവ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_save_name": "പേര് സേവ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_save_profile": "പ്രൊഫൈൽ സേവ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_save_settings": "ക്രമീകരണങ്ങൾ സേവ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_scan_libraries": "ലൈബ്രറികൾ സ്കാൻ ചെയ്യാൻ കഴിയില്ല", - "unable_to_scan_library": "ലൈബ്രറി സ്കാൻ ചെയ്യാൻ കഴിയില്ല", - "unable_to_set_feature_photo": "ഫീച്ചർ ഫോട്ടോ സജ്ജമാക്കാൻ കഴിയില്ല", - "unable_to_set_profile_picture": "പ്രൊഫൈൽ ചിത്രം സജ്ജമാക്കാൻ കഴിയില്ല", - "unable_to_submit_job": "ജോലി സമർപ്പിക്കാൻ കഴിയില്ല", - "unable_to_trash_asset": "അസറ്റ് ട്രാഷ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_unlink_account": "അക്കൗണ്ട് അൺലിങ്ക് ചെയ്യാൻ കഴിയില്ല", - "unable_to_unlink_motion_video": "ചലിക്കുന്ന വീഡിയോ അൺലിങ്ക് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_album_cover": "ആൽബം കവർ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_album_info": "ആൽബം വിവരങ്ങൾ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_library": "ലൈബ്രറി അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_location": "സ്ഥാനം അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_settings": "ക്രമീകരണങ്ങൾ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_timeline_display_status": "ടൈംലൈൻ പ്രദർശന നില അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_update_user": "ഉപയോക്താവിനെ അപ്ഡേറ്റ് ചെയ്യാൻ കഴിയില്ല", - "unable_to_upload_file": "ഫയൽ അപ്‌ലോഡ് ചെയ്യാൻ കഴിയില്ല" - }, - "exclusion_pattern": "ഒഴിവാക്കൽ പാറ്റേൺ", - "exif": "Exif", - "exif_bottom_sheet_description": "വിവരണം ചേർക്കുക...", - "exif_bottom_sheet_description_error": "വിവരണം അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ പിശക്", - "exif_bottom_sheet_details": "വിശദാംശങ്ങൾ", - "exif_bottom_sheet_location": "സ്ഥാനം", - "exif_bottom_sheet_no_description": "വിവരണമില്ല", - "exif_bottom_sheet_people": "ആളുകൾ", - "exif_bottom_sheet_person_add_person": "പേര് ചേർക്കുക", - "exit_slideshow": "സ്ലൈഡ്‌ഷോയിൽ നിന്ന് പുറത്തുകടക്കുക", - "expand_all": "എല്ലാം വികസിപ്പിക്കുക", - "experimental_settings_new_asset_list_subtitle": "പുരോഗതിയിലാണ്", - "experimental_settings_new_asset_list_title": "പരീക്ഷണാടിസ്ഥാനത്തിലുള്ള ഫോട്ടോ ഗ്രിഡ് പ്രവർത്തനക്ഷമമാക്കുക", - "experimental_settings_subtitle": "നിങ്ങളുടെ സ്വന്തം ഉത്തരവാദിത്വത്തിൽ ഉപയോഗിക്കുക!", - "experimental_settings_title": "പരീക്ഷണാടിസ്ഥാനത്തിലുള്ളത്", - "expire_after": "ഇതിന് ശേഷം കാലഹരണപ്പെടും", - "expired": "കാലഹരണപ്പെട്ടു", - "expires_date": "{date}-ന് കാലഹരണപ്പെടും", - "explore": "പര്യവേക്ഷണം ചെയ്യുക", - "explorer": "എക്സ്പ്ലോറർ", - "export": "കയറ്റുമതി ചെയ്യുക", - "export_as_json": "JSON ആയി കയറ്റുമതി ചെയ്യുക", - "export_database": "ഡാറ്റാബേസ് കയറ്റുമതി ചെയ്യുക", - "export_database_description": "SQLite ഡാറ്റാബേസ് കയറ്റുമതി ചെയ്യുക", - "extension": "എക്സ്റ്റൻഷൻ", - "external": "ബാഹ്യം", - "external_libraries": "ബാഹ്യ ലൈബ്രറികൾ", - "external_network": "ബാഹ്യ നെറ്റ്‌വർക്ക്", - "external_network_sheet_info": "തിരഞ്ഞെടുത്ത വൈ-ഫൈ നെറ്റ്‌വർക്കിൽ അല്ലാത്തപ്പോൾ, താഴെ നൽകിയിട്ടുള്ള URL-കളിൽ ആദ്യം ലഭ്യമാകുന്ന ഒന്നിലൂടെ, മുകളിൽ നിന്ന് താഴേക്കുള്ള ക്രമത്തിൽ, ആപ്പ് സെർവറുമായി ബന്ധിപ്പിക്കും", - "face_unassigned": "അസൈൻ ചെയ്തിട്ടില്ല", - "failed": "പരാജയപ്പെട്ടു", - "failed_to_authenticate": "പ്രാമാണീകരിക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_load_assets": "അസറ്റുകൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "failed_to_load_folder": "ഫോൾഡർ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "favorite": "പ്രിയം", - "favorite_action_prompt": "{count} എണ്ണം പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു", - "favorite_or_unfavorite_photo": "ഫോട്ടോ പ്രിയപ്പെട്ടതാക്കുക അല്ലെങ്കിൽ മാറ്റുക", - "favorites": "പ്രിയങ്ങൾ", - "favorites_page_no_favorites": "പ്രിയപ്പെട്ട അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", - "feature_photo_updated": "ഫീച്ചർ ഫോട്ടോ അപ്ഡേറ്റ് ചെയ്തു", - "features": "ഫീച്ചറുകൾ", - "features_in_development": "വികസിപ്പിച്ചുകൊണ്ടിരിക്കുന്ന ഫീച്ചറുകൾ", - "features_setting_description": "ആപ്പ് ഫീച്ചറുകൾ കൈകാര്യം ചെയ്യുക", - "file_name_or_extension": "ഫയലിന്റെ പേര് അല്ലെങ്കിൽ എക്സ്റ്റൻഷൻ", - "file_size": "ഫയൽ വലിപ്പം", - "filename": "ഫയൽനാമം", - "filetype": "ഫയൽ തരം", - "filter": "ഫിൽട്ടർ ചെയ്യുക", - "filter_people": "ആളുകളെ ഫിൽട്ടർ ചെയ്യുക", - "filter_places": "സ്ഥലങ്ങൾ ഫിൽട്ടർ ചെയ്യുക", - "find_them_fast": "തിരയലിലൂടെ പേര് ഉപയോഗിച്ച് അവരെ വേഗത്തിൽ കണ്ടെത്തുക", - "first": "ആദ്യം", - "fix_incorrect_match": "തെറ്റായ പൊരുത്തം ശരിയാക്കുക", - "folder": "ഫോൾഡർ", - "folder_not_found": "ഫോൾഡർ കണ്ടെത്തിയില്ല", - "folders": "ഫോൾഡറുകൾ", - "folders_feature_description": "ഫയൽ സിസ്റ്റത്തിലെ ഫോട്ടോകൾക്കും വീഡിയോകൾക്കുമായി ഫോൾഡർ കാഴ്‌ച ബ്രൗസുചെയ്യുന്നു", - "forgot_pin_code_question": "നിങ്ങളുടെ പിൻ മറന്നോ?", - "forward": "മുന്നോട്ട്", - "full_path": "മുഴുവൻ പാത്ത്: {path}", - "gcast_enabled": "ഗൂഗിൾ കാസ്റ്റ്", - "gcast_enabled_description": "പ്രവർത്തിക്കുന്നതിനായി ഈ ഫീച്ചർ ഗൂഗിളിൽ നിന്ന് ബാഹ്യ ഉറവിടങ്ങൾ ലോഡുചെയ്യുന്നു.", - "general": "പൊതുവായത്", - "geolocation_instruction_location": "ലൊക്കേഷൻ ഉപയോഗിക്കുന്നതിന് GPS കോർഡിനേറ്റുകളുള്ള ഒരു അസറ്റിൽ ക്ലിക്കുചെയ്യുക, അല്ലെങ്കിൽ മാപ്പിൽ നിന്ന് നേരിട്ട് ഒരു സ്ഥലം തിരഞ്ഞെടുക്കുക", - "get_help": "സഹായം നേടുക", - "get_wifiname_error": "വൈ-ഫൈയുടെ പേര് ലഭിച്ചില്ല. നിങ്ങൾ ആവശ്യമായ അനുമതികൾ നൽകിയിട്ടുണ്ടെന്നും ഒരു വൈ-ഫൈ നെറ്റ്‌വർക്കിലേക്ക് കണക്റ്റുചെയ്‌തിട്ടുണ്ടെന്നും ഉറപ്പാക്കുക", - "getting_started": "ആരംഭിക്കുന്നു", - "go_back": "പിന്നോട്ട് പോകുക", - "go_to_folder": "ഫോൾഡറിലേക്ക് പോകുക", - "go_to_search": "തിരയലിലേക്ക് പോകുക", - "gps": "ജിപിഎസ്", - "gps_missing": "GPS ഇല്ല", - "grant_permission": "അനുമതി നൽകുക", - "group_albums_by": "ആൽബങ്ങളെ ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക...", - "group_country": "രാജ്യം അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", - "group_no": "ഗ്രൂപ്പിംഗ് ഇല്ല", - "group_owner": "ഉടമ അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", - "group_places_by": "സ്ഥലങ്ങളെ ഇതനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക...", - "group_year": "വർഷം അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്യുക", - "haptic_feedback_switch": "ഹാപ്റ്റിക് ഫീഡ്‌ബാക്ക് പ്രവർത്തനക്ഷമമാക്കുക", - "haptic_feedback_title": "ഹാപ്റ്റിക് ഫീഡ്‌ബാക്ക്", - "has_quota": "ക്വാട്ടയുണ്ട്", - "hash_asset": "അസറ്റ് ഹാഷ് ചെയ്യുക", - "hashed_assets": "ഹാഷ് ചെയ്ത അസറ്റുകൾ", - "hashing": "ഹാഷിംഗ്", - "header_settings_add_header_tip": "ഹെഡർ ചേർക്കുക", - "header_settings_field_validator_msg": "മൂല്യം ശൂന്യമാകരുത്", - "header_settings_header_name_input": "ഹെഡറിന്റെ പേര്", - "header_settings_header_value_input": "ഹെഡറിന്റെ മൂല്യം", - "headers_settings_tile_title": "കസ്റ്റം പ്രോക്സി ഹെഡറുകൾ", - "hi_user": "നമസ്കാരം {name} ({email})", - "hide_all_people": "എല്ലാ ആളുകളെയും മറയ്ക്കുക", - "hide_gallery": "ഗാലറി മറയ്ക്കുക", - "hide_named_person": "{name} എന്ന വ്യക്തിയെ മറയ്ക്കുക", - "hide_password": "പാസ്‌വേഡ് മറയ്ക്കുക", - "hide_person": "വ്യക്തിയെ മറയ്ക്കുക", - "hide_text_recognition": "ടെക്സ്റ്റ് തിരിച്ചറിയൽ മറയ്ക്കുക", - "hide_unnamed_people": "പേരില്ലാത്ത ആളുകളെ മറയ്ക്കുക", - "home_page_add_to_album_conflicts": "{album} എന്ന ആൽബത്തിലേക്ക് {added} അസറ്റുകൾ ചേർത്തു. {failed} അസറ്റുകൾ ഇതിനകം ആൽബത്തിലുണ്ട്.", - "home_page_add_to_album_err_local": "പ്രാദേശിക അസറ്റുകൾ ഇപ്പോൾ ആൽബങ്ങളിലേക്ക് ചേർക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_add_to_album_success": "{album} എന്ന ആൽബത്തിലേക്ക് {added} അസറ്റുകൾ ചേർത്തു.", - "home_page_album_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ഇപ്പോൾ ഒരു ആൽബത്തിലേക്ക് ചേർക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_archive_err_local": "പ്രാദേശിക അസറ്റുകൾ ഇപ്പോൾ ആർക്കൈവ് ചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_archive_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ആർക്കൈവ് ചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_building_timeline": "ടൈംലൈൻ നിർമ്മിക്കുന്നു", - "home_page_delete_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ഇല്ലാതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_delete_remote_err_local": "റിമോട്ട് ഇല്ലാതാക്കൽ തിരഞ്ഞെടുപ്പിൽ പ്രാദേശിക അസറ്റുകൾ ഉണ്ട്, ഒഴിവാക്കുന്നു", - "home_page_favorite_err_local": "പ്രാദേശിക അസറ്റുകൾ ഇപ്പോൾ പ്രിയപ്പെട്ടതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_favorite_err_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ഇപ്പോൾ പ്രിയപ്പെട്ടതാക്കാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_first_time_notice": "നിങ്ങൾ ആദ്യമായിട്ടാണ് ഈ ആപ്പ് ഉപയോഗിക്കുന്നതെങ്കിൽ, ഒരു ബാക്കപ്പ് ആൽബം തിരഞ്ഞെടുക്കുന്നത് ഉറപ്പാക്കുക, അതുവഴി ടൈംലൈനിൽ ഫോട്ടോകളും വീഡിയോകളും ചേർക്കാൻ കഴിയും", - "home_page_locked_error_local": "പ്രാദേശിക അസറ്റുകൾ ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് മാറ്റാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_locked_error_partner": "പങ്കാളിയുടെ അസറ്റുകൾ ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് മാറ്റാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_share_err_local": "പ്രാദേശിക അസറ്റുകൾ ലിങ്ക് വഴി പങ്കിടാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "home_page_upload_err_limit": "ഒരു സമയം പരമാവധി 30 അസറ്റുകൾ മാത്രമേ അപ്‌ലോഡ് ചെയ്യാൻ കഴിയൂ, ഒഴിവാക്കുന്നു", - "host": "ഹോസ്റ്റ്", - "hour": "മണിക്കൂർ", - "hours": "മണിക്കൂറുകൾ", - "id": "ഐഡി", - "idle": "നിഷ്‌ക്രിയം", - "ignore_icloud_photos": "iCloud ഫോട്ടോകൾ അവഗണിക്കുക", - "ignore_icloud_photos_description": "iCloud-ൽ സംഭരിച്ചിരിക്കുന്ന ഫോട്ടോകൾ Immich സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യില്ല", - "image": "ചിത്രം", - "image_alt_text_date": "{date}-ന് എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_1_person": "{date}-ന് {person1}-നൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_2_people": "{date}-ന് {person1}, {person2} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_3_people": "{date}-ന് {person1}, {person2}, {person3} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_4_or_more_people": "{date}-ന് {person1}, {person2}, കൂടാതെ മറ്റ് {additionalCount, number} പേർക്കുമൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_place": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_place_1_person": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}-നൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_place_2_people": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}, {person2} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_place_3_people": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}, {person2}, {person3} എന്നിവർക്കൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_alt_text_date_place_4_or_more_people": "{date}-ന് {city}, {country} എന്നിവിടങ്ങളിൽ വെച്ച് {person1}, {person2}, കൂടാതെ മറ്റ് {additionalCount, number} പേർക്കുമൊപ്പം എടുത്ത {isVideo, select, true {വീഡിയോ} other {ചിത്രം}}", - "image_saved_successfully": "ചിത്രം സേവ് ചെയ്തു", - "image_viewer_page_state_provider_download_started": "ഡൗൺലോഡ് ആരംഭിച്ചു", - "image_viewer_page_state_provider_download_success": "ഡൗൺലോഡ് വിജയിച്ചു", - "image_viewer_page_state_provider_share_error": "പങ്കിടുന്നതിൽ പിശക്", - "immich_logo": "Immich ലോഗോ", - "immich_web_interface": "Immich വെബ് ഇന്റർഫേസ്", - "import_from_json": "JSON-ൽ നിന്ന് ഇമ്പോർട്ടുചെയ്യുക", - "import_path": "ഇമ്പോർട്ട് പാത്ത്", - "in_albums": "{count, plural, one {# ആൽബത്തിൽ} other {# ആൽബങ്ങളിൽ}}", - "in_archive": "ആർക്കൈവിൽ", - "in_year": "{year}-ൽ", - "in_year_selector": "ഇതിൽ", - "include_archived": "ആർക്കൈവുചെയ്‌തവ ഉൾപ്പെടുത്തുക", - "include_shared_albums": "പങ്കിട്ട ആൽബങ്ങൾ ഉൾപ്പെടുത്തുക", - "include_shared_partner_assets": "പങ്കിട്ട പങ്കാളിയുടെ അസറ്റുകൾ ഉൾപ്പെടുത്തുക", - "individual_share": "വ്യക്തിഗത പങ്കിടൽ", - "individual_shares": "വ്യക്തിഗത പങ്കിടലുകൾ", - "info": "വിവരങ്ങൾ", - "interval": { - "day_at_onepm": "എല്ലാ ദിവസവും ഉച്ചയ്ക്ക് 1 മണിക്ക്", - "hours": "{hours, plural, one {ഓരോ മണിക്കൂറിലും} other {ഓരോ {hours, number} മണിക്കൂറിലും}}", - "night_at_midnight": "എല്ലാ രാത്രിയും അർദ്ധരാത്രിക്ക്", - "night_at_twoam": "എല്ലാ രാത്രിയും പുലർച്ചെ 2 മണിക്ക്" - }, - "invalid_date": "അസാധുവായ തീയതി", - "invalid_date_format": "അസാധുവായ തീയതി ഫോർമാറ്റ്", - "invite_people": "ആളുകളെ ക്ഷണിക്കുക", - "invite_to_album": "ആൽബത്തിലേക്ക് ക്ഷണിക്കുക", - "ios_debug_info_fetch_ran_at": "ഫെച്ച് പ്രവർത്തിച്ചത് {dateTime}", - "ios_debug_info_last_sync_at": "അവസാന സിങ്ക് {dateTime}", - "ios_debug_info_no_processes_queued": "പശ്ചാത്തല പ്രോസസ്സുകളൊന്നും ക്യൂവിൽ ഇല്ല", - "ios_debug_info_no_sync_yet": "പശ്ചാത്തല സിങ്ക് ജോലി ഇതുവരെ പ്രവർത്തിച്ചിട്ടില്ല", - "ios_debug_info_processes_queued": "{count, plural, one {{count} പശ്ചാത്തല പ്രോസസ്സ് ക്യൂവിലുണ്ട്} other {{count} പശ്ചാത്തല പ്രോസസ്സുകൾ ക്യൂവിലുണ്ട്}}", - "ios_debug_info_processing_ran_at": "പ്രോസസ്സിംഗ് പ്രവർത്തിച്ചത് {dateTime}", - "items_count": "{count, plural, one {# ഇനം} other {# ഇനങ്ങൾ}}", - "jobs": "ജോലികൾ", - "keep": "സൂക്ഷിക്കുക", - "keep_all": "എല്ലാം സൂക്ഷിക്കുക", - "keep_this_delete_others": "ഇത് സൂക്ഷിച്ച് മറ്റുള്ളവ ഇല്ലാതാക്കുക", - "kept_this_deleted_others": "ഈ അസറ്റ് സൂക്ഷിക്കുകയും {count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ഇല്ലാതാക്കുകയും ചെയ്തു", - "keyboard_shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ", - "language": "ഭാഷ", - "language_no_results_subtitle": "നിങ്ങളുടെ തിരയൽ പദം ക്രമീകരിച്ച് ശ്രമിക്കുക", - "language_no_results_title": "ഭാഷകളൊന്നും കണ്ടെത്തിയില്ല", - "language_search_hint": "ഭാഷകൾക്കായി തിരയുക...", - "language_setting_description": "നിങ്ങൾക്കിഷ്ടപ്പെട്ട ഭാഷ തിരഞ്ഞെടുക്കുക", - "large_files": "വലിയ ഫയലുകൾ", - "last": "അവസാനത്തേത്", - "last_months": "{count, plural, one {കഴിഞ്ഞ മാസം} other {കഴിഞ്ഞ # മാസങ്ങൾ}}", - "last_seen": "അവസാനം കണ്ടത്", - "latest_version": "ഏറ്റവും പുതിയ പതിപ്പ്", - "latitude": "അക്ഷാംശം", - "leave": "വിടുക", - "leave_album": "ആൽബം വിടുക", - "lens_model": "ലെൻസ് മോഡൽ", - "let_others_respond": "മറ്റുള്ളവരെ പ്രതികരിക്കാൻ അനുവദിക്കുക", - "level": "തലം", - "library": "ലൈബ്രറി", - "library_add_folder": "ഫോൾഡർ ചേർക്കുക", - "library_edit_folder": "ഫോൾഡർ എഡിറ്റ് ചെയ്യുക", - "library_options": "ലൈബ്രറി ഓപ്ഷനുകൾ", - "library_page_device_albums": "ഉപകരണത്തിലെ ആൽബങ്ങൾ", - "library_page_new_album": "പുതിയ ആൽബം", - "library_page_sort_asset_count": "അസറ്റുകളുടെ എണ്ണം", - "library_page_sort_created": "സൃഷ്ടിച്ച തീയതി", - "library_page_sort_last_modified": "അവസാനം മാറ്റം വരുത്തിയത്", - "library_page_sort_title": "ആൽബത്തിന്റെ ശീർഷകം", - "licenses": "ലൈസൻസുകൾ", - "light": "ലൈറ്റ്", - "like": "ഇഷ്ടപ്പെട്ടു", - "like_deleted": "ഇഷ്ടം നീക്കംചെയ്തു", - "link_motion_video": "ചലിക്കുന്ന വീഡിയോ ലിങ്ക് ചെയ്യുക", - "link_to_oauth": "OAuth-ലേക്ക് ലിങ്ക് ചെയ്യുക", - "linked_oauth_account": "ലിങ്ക് ചെയ്ത OAuth അക്കൗണ്ട്", - "list": "ലിസ്റ്റ്", - "loading": "ലോഡിംഗ്", - "loading_search_results_failed": "തിരയൽ ഫലങ്ങൾ ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", - "local": "പ്രാദേശികം", - "local_asset_cast_failed": "സെർവറിലേക്ക് അപ്‌ലോഡ് ചെയ്യാത്ത ഒരു അസറ്റ് കാസ്റ്റ് ചെയ്യാൻ കഴിയില്ല", - "local_assets": "പ്രാദേശിക അസറ്റുകൾ", - "local_media_summary": "പ്രാദേശിക മീഡിയ സംഗ്രഹം", - "local_network": "പ്രാദേശിക നെറ്റ്‌വർക്ക്", - "local_network_sheet_info": "നിർദ്ദിഷ്‌ട വൈ-ഫൈ നെറ്റ്‌വർക്ക് ഉപയോഗിക്കുമ്പോൾ ഈ URL വഴി ആപ്പ് സെർവറുമായി ബന്ധിപ്പിക്കും", - "location": "സ്ഥലം", - "location_permission": "ലൊക്കേഷൻ അനുമതി", - "location_permission_content": "യാന്ത്രിക-സ്വിച്ചിംഗ് ഫീച്ചർ ഉപയോഗിക്കുന്നതിന്, Immich-ന് കൃത്യമായ ലൊക്കേഷൻ അനുമതി ആവശ്യമാണ്, അതുവഴി നിലവിലെ വൈ-ഫൈ നെറ്റ്‌വർക്കിന്റെ പേര് വായിക്കാൻ കഴിയും", - "location_picker_choose_on_map": "മാപ്പിൽ തിരഞ്ഞെടുക്കുക", - "location_picker_latitude_error": "സാധുവായ അക്ഷാംശം നൽകുക", - "location_picker_latitude_hint": "നിങ്ങളുടെ അക്ഷാംശം ഇവിടെ നൽകുക", - "location_picker_longitude_error": "സാധുവായ രേഖാംശം നൽകുക", - "location_picker_longitude_hint": "നിങ്ങളുടെ രേഖാംശം ഇവിടെ നൽകുക", - "lock": "ലോക്ക് ചെയ്യുക", - "locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡർ", - "log_detail_title": "ലോഗ് വിശദാംശം", - "log_out": "ലോഗ് ഔട്ട്", - "log_out_all_devices": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യുക", - "logged_in_as": "{user} ആയി ലോഗിൻ ചെയ്തു", - "logged_out_all_devices": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്തു", - "logged_out_device": "ഉപകരണത്തിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്തു", - "login": "ലോഗിൻ", - "login_disabled": "ലോഗിൻ പ്രവർത്തനരഹിതമാക്കി", - "login_form_api_exception": "API എക്സെപ്ഷൻ. ദയവായി സെർവർ URL പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.", - "login_form_back_button_text": "തിരികെ", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "സെർവർ എൻഡ്‌പോയിന്റ് URL", - "login_form_err_http": "ദയവായി http:// അല്ലെങ്കിൽ https:// എന്ന് വ്യക്തമാക്കുക", - "login_form_err_invalid_email": "അസാധുവായ ഇമെയിൽ", - "login_form_err_invalid_url": "അസാധുവായ URL", - "login_form_err_leading_whitespace": "തുടക്കത്തിലെ ശൂന്യസ്ഥലം", - "login_form_err_trailing_whitespace": "അവസാനത്തെ ശൂന്യസ്ഥലം", - "login_form_failed_get_oauth_server_config": "OAuth ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുന്നതിൽ പിശക്, സെർവർ URL പരിശോധിക്കുക", - "login_form_failed_get_oauth_server_disable": "ഈ സെർവറിൽ OAuth ഫീച്ചർ ലഭ്യമല്ല", - "login_form_failed_login": "നിങ്ങളെ ലോഗിൻ ചെയ്യുന്നതിൽ പിശക്, സെർവർ URL, ഇമെയിൽ, പാസ്‌വേഡ് എന്നിവ പരിശോധിക്കുക", - "login_form_handshake_exception": "സെർവറുമായി ഒരു ഹാൻഡ്‌ഷേക്ക് എക്സെപ്ഷൻ ഉണ്ടായി. നിങ്ങൾ ഒരു സ്വയം ഒപ്പിട്ട സർട്ടിഫിക്കറ്റാണ് ഉപയോഗിക്കുന്നതെങ്കിൽ ക്രമീകരണങ്ങളിൽ സ്വയം ഒപ്പിട്ട സർട്ടിഫിക്കറ്റ് പിന്തുണ പ്രവർത്തനക്ഷമമാക്കുക.", - "login_form_password_hint": "പാസ്‌വേഡ്", - "login_form_save_login": "ലോഗിൻ ആയി തുടരുക", - "login_form_server_empty": "ഒരു സെർവർ URL നൽകുക.", - "login_form_server_error": "സെർവറിലേക്ക് കണക്റ്റുചെയ്യാൻ കഴിഞ്ഞില്ല.", - "login_has_been_disabled": "ലോഗിൻ പ്രവർത്തനരഹിതമാക്കി.", - "login_password_changed_error": "നിങ്ങളുടെ പാസ്‌വേഡ് അപ്ഡേറ്റ് ചെയ്യുന്നതിൽ ഒരു പിശകുണ്ടായി", - "login_password_changed_success": "പാസ്‌വേഡ് വിജയകരമായി അപ്ഡേറ്റ് ചെയ്തു", - "logout_all_device_confirmation": "എല്ലാ ഉപകരണങ്ങളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "logout_this_device_confirmation": "ഈ ഉപകരണത്തിൽ നിന്ന് ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "logs": "ലോഗുകൾ", - "longitude": "രേഖാംശം", - "look": "കാഴ്ച", - "loop_videos": "വീഡിയോകൾ ലൂപ്പ് ചെയ്യുക", - "loop_videos_description": "വിശദാംശ വ്യൂവറിൽ ഒരു വീഡിയോ യാന്ത്രികമായി ലൂപ്പ് ചെയ്യാൻ പ്രവർത്തനക്ഷമമാക്കുക.", - "main_branch_warning": "നിങ്ങൾ ഒരു ഡെവലപ്‌മെന്റ് പതിപ്പാണ് ഉപയോഗിക്കുന്നത്; ഒരു റിലീസ് പതിപ്പ് ഉപയോഗിക്കാൻ ഞങ്ങൾ ശക്തമായി ശുപാർശ ചെയ്യുന്നു!", - "main_menu": "പ്രധാന മെനു", - "maintenance_description": "ഇമ്മിക്ക് മെയിന്റനൻസ് മോഡിലേക്ക് മാറ്റിയിരിക്കുന്നു.", - "maintenance_end": "മെയിന്റനൻസ് മോഡ് അവസാനിപ്പിക്കുക", - "maintenance_end_error": "മെയിന്റനൻസ് മോഡ് അവസാനിപ്പിക്കുന്നതിൽ പരാജയപ്പെട്ടു.", - "maintenance_logged_in_as": "നിലവിൽ {user} ആയി ലോഗിൻ ചെയ്തിരിക്കുന്നു", - "maintenance_title": "താൽക്കാലികമായി ലഭ്യമല്ല", - "make": "നിർമ്മാതാവ്", - "manage_geolocation": "സ്ഥാനം കൈകാര്യം ചെയ്യുക", - "manage_media_access_rationale": "ട്രാഷിലേക്ക് ഫയലുകൾ മാറ്റുന്നതിനും അവ വീണ്ടെടുക്കുന്നതിനും ഈ അനുമതി ആവശ്യമാണ്.", - "manage_media_access_settings": "ക്രമീകരണങ്ങൾ തുറക്കുക", - "manage_media_access_subtitle": "മീഡിയ ഫയലുകൾ കൈകാര്യം ചെയ്യുന്നതിനും മാറ്റുന്നതിനും ഇമ്മിക്ക് ആപ്പിനെ അനുവദിക്കുക.", - "manage_media_access_title": "മീഡിയ മാനേജ്മെന്റ് ആക്സസ്", - "manage_shared_links": "പങ്കിട്ട ലിങ്കുകൾ കൈകാര്യം ചെയ്യുക", - "manage_sharing_with_partners": "പങ്കാളികളുമായുള്ള പങ്കിടൽ കൈകാര്യം ചെയ്യുക", - "manage_the_app_settings": "ആപ്പ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "manage_your_account": "നിങ്ങളുടെ അക്കൗണ്ട് കൈകാര്യം ചെയ്യുക", - "manage_your_api_keys": "നിങ്ങളുടെ API കീകൾ കൈകാര്യം ചെയ്യുക", - "manage_your_devices": "നിങ്ങൾ ലോഗിൻ ചെയ്ത ഉപകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "manage_your_oauth_connection": "നിങ്ങളുടെ OAuth കണക്ഷൻ കൈകാര്യം ചെയ്യുക", - "map": "മാപ്പ്", - "map_assets_in_bounds": "{count, plural, =0 {ഈ പ്രദേശത്ത് ഫോട്ടോകളൊന്നുമില്ല} one {# ഫോട്ടോ} other {# ഫോട്ടോകൾ}}", - "map_cannot_get_user_location": "ഉപയോക്താവിന്റെ സ്ഥാനം ലഭിക്കുന്നില്ല", - "map_location_dialog_yes": "അതെ", - "map_location_picker_page_use_location": "ഈ സ്ഥലം ഉപയോഗിക്കുക", - "map_location_service_disabled_content": "നിങ്ങളുടെ നിലവിലെ സ്ഥാനത്ത് നിന്നുള്ള അസറ്റുകൾ പ്രദർശിപ്പിക്കുന്നതിന് ലൊക്കേഷൻ സേവനം പ്രവർത്തനക്ഷമമാക്കേണ്ടതുണ്ട്. ഇപ്പോൾ പ്രവർത്തനക്ഷമമാക്കണോ?", - "map_location_service_disabled_title": "ലൊക്കേഷൻ സേവനം പ്രവർത്തനരഹിതമാക്കി", - "map_marker_for_images": "{city}, {country} എന്നിവിടങ്ങളിൽ എടുത്ത ചിത്രങ്ങൾക്കുള്ള മാപ്പ് മാർക്കർ", - "map_marker_with_image": "ചിത്രത്തോടുകൂടിയ മാപ്പ് മാർക്കർ", - "map_no_location_permission_content": "നിങ്ങളുടെ നിലവിലെ സ്ഥാനത്ത് നിന്നുള്ള അസറ്റുകൾ പ്രദർശിപ്പിക്കുന്നതിന് ലൊക്കേഷൻ അനുമതി ആവശ്യമാണ്. ഇപ്പോൾ അനുവദിക്കണോ?", - "map_no_location_permission_title": "ലൊക്കേഷൻ അനുമതി നിഷേധിച്ചു", - "map_settings": "മാപ്പ് ക്രമീകരണങ്ങൾ", - "map_settings_dark_mode": "ഡാർക്ക് മോഡ്", - "map_settings_date_range_option_day": "കഴിഞ്ഞ 24 മണിക്കൂർ", - "map_settings_date_range_option_days": "കഴിഞ്ഞ {days} ദിവസങ്ങൾ", - "map_settings_date_range_option_year": "കഴിഞ്ഞ വർഷം", - "map_settings_date_range_option_years": "കഴിഞ്ഞ {years} വർഷങ്ങൾ", - "map_settings_dialog_title": "മാപ്പ് ക്രമീകരണങ്ങൾ", - "map_settings_include_show_archived": "ആർക്കൈവുചെയ്‌തവ ഉൾപ്പെടുത്തുക", - "map_settings_include_show_partners": "പങ്കാളികളെ ഉൾപ്പെടുത്തുക", - "map_settings_only_show_favorites": "പ്രിയപ്പെട്ടവ മാത്രം കാണിക്കുക", - "map_settings_theme_settings": "മാപ്പ് തീം", - "map_zoom_to_see_photos": "ഫോട്ടോകൾ കാണാൻ സൂം ഔട്ട് ചെയ്യുക", - "mark_all_as_read": "എല്ലാം വായിച്ചതായി അടയാളപ്പെടുത്തുക", - "mark_as_read": "വായിച്ചതായി അടയാളപ്പെടുത്തുക", - "marked_all_as_read": "എല്ലാം വായിച്ചതായി അടയാളപ്പെടുത്തി", - "matches": "ചേർച്ചകൾ", - "matching_assets": "ചേർച്ചയുള്ള അസറ്റുകൾ", - "media_type": "മീഡിയ തരം", - "memories": "ഓർമ്മകൾ", - "memories_all_caught_up": "എല്ലാം കണ്ടുതീർത്തു", - "memories_check_back_tomorrow": "കൂടുതൽ ഓർമ്മകൾക്കായി നാളെ വീണ്ടും പരിശോധിക്കുക", - "memories_setting_description": "നിങ്ങളുടെ ഓർമ്മകളിൽ നിങ്ങൾ കാണുന്നത് കൈകാര്യം ചെയ്യുക", - "memories_start_over": "വീണ്ടും തുടങ്ങുക", - "memories_swipe_to_close": "അടയ്ക്കാൻ മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക", - "memory": "മെമ്മറി", - "memory_lane_title": "ഓർമ്മകളുടെ പാത {title}", - "menu": "മെനു", - "merge": "ലയിപ്പിക്കുക", - "merge_people": "ആളുകളെ ലയിപ്പിക്കുക", - "merge_people_limit": "ഒരു സമയം 5 മുഖങ്ങൾ വരെ മാത്രമേ ലയിപ്പിക്കാൻ കഴിയൂ", - "merge_people_prompt": "ഈ ആളുകളെ ലയിപ്പിക്കണോ? ഈ പ്രവർത്തനം പഴയപടിയാക്കാൻ കഴിയില്ല.", - "merge_people_successfully": "ആളുകളെ വിജയകരമായി ലയിപ്പിച്ചു", - "merged_people_count": "{count, plural, one {# വ്യക്തിയെ ലയിപ്പിച്ചു} other {# ആളുകളെ ലയിപ്പിച്ചു}}", - "minimize": "ചെറുതാക്കുക", - "minute": "മിനിറ്റ്", - "minutes": "മിനിറ്റുകൾ", - "missing": "കാണാനില്ല", - "mobile_app": "മൊബൈൽ ആപ്പ്", - "mobile_app_download_onboarding_note": "താഴെ പറയുന്ന ഓപ്ഷനുകൾ ഉപയോഗിച്ച് മൊബൈൽ ആപ്പ് ഡൗൺലോഡ് ചെയ്യുക", - "model": "മോഡൽ", - "month": "മാസം", - "monthly_title_text_date_format": "MMMM y", - "more": "കൂടുതൽ", - "move": "നീക്കുക", - "move_off_locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് മാറ്റുക", - "move_to": "ഇതിലേക്ക് മാറ്റുക", - "move_to_lock_folder_action_prompt": "{count} എണ്ണം ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് ചേർത്തു", - "move_to_locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡറിലേക്ക് മാറ്റുക", - "move_to_locked_folder_confirmation": "ഈ ഫോട്ടോകളും വീഡിയോയും എല്ലാ ആൽബങ്ങളിൽ നിന്നും നീക്കം ചെയ്യപ്പെടും, ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് മാത്രമേ കാണാൻ കഴിയൂ", - "moved_to_archive": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ആർക്കൈവിലേക്ക് മാറ്റി", - "moved_to_library": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ലൈബ്രറിയിലേക്ക് മാറ്റി", - "moved_to_trash": "ട്രാഷിലേക്ക് മാറ്റി", - "multiselect_grid_edit_date_time_err_read_only": "വായിക്കാൻ മാത്രമുള്ള അസറ്റി(കളു)ടെ തീയതി എഡിറ്റുചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "multiselect_grid_edit_gps_err_read_only": "വായിക്കാൻ മാത്രമുള്ള അസറ്റി(കളു)ടെ സ്ഥാനം എഡിറ്റുചെയ്യാൻ കഴിയില്ല, ഒഴിവാക്കുന്നു", - "mute_memories": "ഓർമ്മകൾ നിശ്ശബ്ദമാക്കുക", - "my_albums": "എന്റെ ആൽബങ്ങൾ", - "name": "പേര്", - "name_or_nickname": "പേര് അല്ലെങ്കിൽ വിളിപ്പേര്", - "navigate": "നാവിഗേറ്റ് ചെയ്യുക", - "navigate_to_time": "സമയത്തിലേക്ക് നാവിഗേറ്റ് ചെയ്യുക", - "network_requirement_photos_upload": "ഫോട്ടോകൾ ബാക്കപ്പ് ചെയ്യാൻ സെല്ലുലാർ ഡാറ്റ ഉപയോഗിക്കുക", - "network_requirement_videos_upload": "വീഡിയോകൾ ബാക്കപ്പ് ചെയ്യാൻ സെല്ലുലാർ ഡാറ്റ ഉപയോഗിക്കുക", - "network_requirements": "നെറ്റ്‌വർക്ക് ആവശ്യകതകൾ", - "network_requirements_updated": "നെറ്റ്‌വർക്ക് ആവശ്യകതകൾ മാറി, ബാക്കപ്പ് ക്യൂ പുനഃസജ്ജമാക്കുന്നു", - "networking_settings": "നെറ്റ്‌വർക്കിംഗ്", - "networking_subtitle": "സെർവർ എൻഡ്‌പോയിന്റ് ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക", - "never": "ഒരിക്കലും ഇല്ല", - "new_album": "പുതിയ ആൽബം", - "new_api_key": "പുതിയ API കീ", - "new_date_range": "പുതിയ തീയതി പരിധി", - "new_password": "പുതിയ പാസ്‌വേഡ്", - "new_person": "പുതിയ വ്യക്തി", - "new_pin_code": "പുതിയ പിൻ കോഡ്", - "new_pin_code_subtitle": "നിങ്ങൾ ആദ്യമായിട്ടാണ് ലോക്ക് ചെയ്ത ഫോൾഡർ ആക്‌സസ് ചെയ്യുന്നത്. ഈ പേജ് സുരക്ഷിതമായി ആക്‌സസ് ചെയ്യാൻ ഒരു പിൻ കോഡ് ഉണ്ടാക്കുക", - "new_timeline": "പുതിയ ടൈംലൈൻ", - "new_update": "പുതിയ അപ്‌ഡേറ്റ്", - "new_user_created": "പുതിയ ഉപയോക്താവിനെ ഉണ്ടാക്കി", - "new_version_available": "പുതിയ പതിപ്പ് ലഭ്യമാണ്", - "newest_first": "ഏറ്റവും പുതിയത് ആദ്യം", - "next": "അടുത്തത്", - "next_memory": "അടുത്ത ഓർമ്മ", - "no": "ഇല്ല", - "no_albums_message": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും ക്രമീകരിക്കാൻ ഒരു ആൽബം ഉണ്ടാക്കുക", - "no_albums_with_name_yet": "ഈ പേരിൽ നിങ്ങൾക്ക് ഇതുവരെ ആൽബങ്ങളൊന്നും ഇല്ലെന്ന് തോന്നുന്നു.", - "no_albums_yet": "നിങ്ങൾക്ക് ഇതുവരെ ആൽബങ്ങളൊന്നും ഇല്ലെന്ന് തോന്നുന്നു.", - "no_archived_assets_message": "നിങ്ങളുടെ ഫോട്ടോ കാഴ്‌ചയിൽ നിന്ന് മറയ്ക്കാൻ ഫോട്ടോകളും വീഡിയോകളും ആർക്കൈവ് ചെയ്യുക", - "no_assets_message": "നിങ്ങളുടെ ആദ്യ ഫോട്ടോ അപ്‌ലോഡ് ചെയ്യാൻ ക്ലിക്കുചെയ്യുക", - "no_assets_to_show": "കാണിക്കാൻ അസറ്റുകളൊന്നുമില്ല", - "no_cast_devices_found": "കാസ്റ്റ് ഉപകരണങ്ങളൊന്നും കണ്ടെത്തിയില്ല", - "no_checksum_local": "ചെക്ക്സം ലഭ്യമല്ല - പ്രാദേശിക അസറ്റുകൾ ലഭ്യമാക്കാൻ കഴിയില്ല", - "no_checksum_remote": "ചെക്ക്സം ലഭ്യമല്ല - റിമോട്ട് അസറ്റ് ലഭ്യമാക്കാൻ കഴിയില്ല", - "no_devices": "അംഗീകൃത ഉപകരണങ്ങളൊന്നുമില്ല", - "no_duplicates_found": "ഡ്യൂപ്ലിക്കേറ്റുകളൊന്നും കണ്ടെത്തിയില്ല.", - "no_exif_info_available": "Exif വിവരങ്ങൾ ലഭ്യമല്ല", - "no_explore_results_message": "നിങ്ങളുടെ ശേഖരം പര്യവേക്ഷണം ചെയ്യാൻ കൂടുതൽ ഫോട്ടോകൾ അപ്‌ലോഡ് ചെയ്യുക.", - "no_favorites_message": "നിങ്ങളുടെ മികച്ച ചിത്രങ്ങളും വീഡിയോകളും വേഗത്തിൽ കണ്ടെത്താൻ പ്രിയപ്പെട്ടവ ചേർക്കുക", - "no_libraries_message": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും കാണുന്നതിന് ഒരു ബാഹ്യ ലൈബ്രറി ഉണ്ടാക്കുക", - "no_local_assets_found": "ഈ ചെക്ക്സം ഉപയോഗിച്ച് പ്രാദേശിക അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", - "no_location_set": "സ്ഥലം സജ്ജീകരിച്ചിട്ടില്ല", - "no_locked_photos_message": "ലോക്ക് ചെയ്‌ത ഫോൾഡറിലെ ഫോട്ടോകളും വീഡിയോകളും മറച്ചിരിക്കുന്നു, നിങ്ങൾ ലൈബ്രറി ബ്രൗസ് ചെയ്യുമ്പോഴോ തിരയുമ്പോഴോ അവ ദൃശ്യമാകില്ല.", - "no_name": "പേരില്ല", - "no_notifications": "അറിയിപ്പുകളൊന്നുമില്ല", - "no_people_found": "ചേർച്ചയുള്ള ആളുകളെ കണ്ടെത്തിയില്ല", - "no_places": "സ്ഥലങ്ങളില്ല", - "no_remote_assets_found": "ഈ ചെക്ക്സം ഉപയോഗിച്ച് റിമോട്ട് അസറ്റുകളൊന്നും കണ്ടെത്തിയില്ല", - "no_results": "ഫലങ്ങളൊന്നുമില്ല", - "no_results_description": "ഒരു പര്യായപദമോ കൂടുതൽ പൊതുവായ കീവേഡോ ഉപയോഗിച്ച് ശ്രമിക്കുക", - "no_shared_albums_message": "നിങ്ങളുടെ നെറ്റ്‌വർക്കിലെ ആളുകളുമായി ഫോട്ടോകളും വീഡിയോകളും പങ്കിടാൻ ഒരു ആൽബം ഉണ്ടാക്കുക", - "no_uploads_in_progress": "അപ്‌ലോഡുകളൊന്നും പുരോഗമിക്കുന്നില്ല", - "not_allowed": "അനുവദനീയമല്ല", - "not_available": "ലഭ്യമല്ല", - "not_in_any_album": "ഒരു ആൽബത്തിലുമില്ല", - "not_selected": "തിരഞ്ഞെടുത്തിട്ടില്ല", - "note_apply_storage_label_to_previously_uploaded assets": "കുറിപ്പ്: മുമ്പ് അപ്‌ലോഡ് ചെയ്ത അസറ്റുകളിൽ സ്റ്റോറേജ് ലേബൽ പ്രയോഗിക്കാൻ, ഇത് പ്രവർത്തിപ്പിക്കുക", - "notes": "കുറിപ്പുകൾ", - "nothing_here_yet": "ഇവിടെ ഇതുവരെ ഒന്നുമില്ല", - "notification_permission_dialog_content": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കാൻ, ക്രമീകരണങ്ങളിലേക്ക് പോയി 'അനുവദിക്കുക' തിരഞ്ഞെടുക്കുക.", - "notification_permission_list_tile_content": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കാൻ അനുമതി നൽകുക.", - "notification_permission_list_tile_enable_button": "അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "notification_permission_list_tile_title": "അറിയിപ്പ് അനുമതി", - "notification_toggle_setting_description": "ഇമെയിൽ അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക", - "notifications": "അറിയിപ്പുകൾ", - "notifications_setting_description": "അറിയിപ്പുകൾ കൈകാര്യം ചെയ്യുക", - "oauth": "OAuth", - "obtainium_configurator": "ഒബ്‌റ്റൈനിയം കോൺഫിഗറേറ്റർ", - "obtainium_configurator_instructions": "ഇമ്മിക്ക് ഗിറ്റ്ഹബ് റിലീസിൽ നിന്ന് നേരിട്ട് ആൻഡ്രോയിഡ് ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യാനും അപ്‌ഡേറ്റ് ചെയ്യാനും ഒബ്‌റ്റൈനിയം ഉപയോഗിക്കുക. ഒരു എപിഐ കീ സൃഷ്ടിച്ച് നിങ്ങളുടെ ഒബ്‌റ്റൈനിയം കോൺഫിഗറേഷൻ ലിങ്ക് സൃഷ്ടിക്കാൻ ഒരു വേരിയന്റ് തിരഞ്ഞെടുക്കുക", - "ocr": "ഒ.സി.ആർ (OCR)", - "official_immich_resources": "Immich-ന്റെ ഔദ്യോഗിക ഉറവിടങ്ങൾ", - "offline": "ഓഫ്‌ലൈൻ", - "offset": "ഓഫ്സെറ്റ്", - "ok": "ശരി", - "oldest_first": "ഏറ്റവും പഴയത് ആദ്യം", - "on_this_device": "ഈ ഉപകരണത്തിൽ", - "onboarding": "ഓൺബോർഡിംഗ്", - "onboarding_locale_description": "നിങ്ങൾക്കിഷ്ടപ്പെട്ട ഭാഷ തിരഞ്ഞെടുക്കുക. ഇത് പിന്നീട് നിങ്ങളുടെ ക്രമീകരണങ്ങളിൽ മാറ്റാവുന്നതാണ്.", - "onboarding_privacy_description": "ഇനിപ്പറയുന്ന (ഓപ്ഷണൽ) ഫീച്ചറുകൾ ബാഹ്യ സേവനങ്ങളെ ആശ്രയിച്ചിരിക്കുന്നു, അവ എപ്പോൾ വേണമെങ്കിലും ക്രമീകരണങ്ങളിൽ പ്രവർത്തനരഹിതമാക്കാം.", - "onboarding_server_welcome_description": "ചില സാധാരണ ക്രമീകരണങ്ങൾ ഉപയോഗിച്ച് നിങ്ങളുടെ ഇൻസ്റ്റൻസ് സജ്ജമാക്കാം.", - "onboarding_theme_description": "നിങ്ങളുടെ ഇൻസ്റ്റൻസിനായി ഒരു കളർ തീം തിരഞ്ഞെടുക്കുക. ഇത് പിന്നീട് നിങ്ങളുടെ ക്രമീകരണങ്ങളിൽ മാറ്റാവുന്നതാണ്.", - "onboarding_user_welcome_description": "നമുക്ക് ആരംഭിക്കാം!", - "onboarding_welcome_user": "സ്വാഗതം, {user}", - "online": "ഓൺലൈൻ", - "only_favorites": "പ്രിയപ്പെട്ടവ മാത്രം", - "open": "തുറക്കുക", - "open_in_map_view": "മാപ്പ് കാഴ്‌ചയിൽ തുറക്കുക", - "open_in_openstreetmap": "OpenStreetMap-ൽ തുറക്കുക", - "open_the_search_filters": "തിരയൽ ഫിൽട്ടറുകൾ തുറക്കുക", - "options": "ഓപ്ഷനുകൾ", - "or": "അല്ലെങ്കിൽ", - "organize_into_albums": "ആൽബങ്ങളായി ക്രമീകരിക്കുക", - "organize_into_albums_description": "നിലവിലെ സിങ്ക് ക്രമീകരണങ്ങൾ ഉപയോഗിച്ച് നിലവിലുള്ള ഫോട്ടോകൾ ആൽബങ്ങളിലേക്ക് മാറ്റുക", - "organize_your_library": "നിങ്ങളുടെ ലൈബ്രറി ക്രമീകരിക്കുക", - "original": "യഥാർത്ഥം", - "other": "മറ്റുള്ളവ", - "other_devices": "മറ്റ് ഉപകരണങ്ങൾ", - "other_entities": "മറ്റ് എന്റിറ്റികൾ", - "other_variables": "മറ്റ് വേരിയബിളുകൾ", - "owned": "സ്വന്തമായുള്ളത്", - "owner": "ഉടമ", - "partner": "പങ്കാളി", - "partner_can_access": "{partner}-ക്ക് ആക്സസ് ചെയ്യാൻ കഴിയും", - "partner_can_access_assets": "ആർക്കൈവുചെയ്‌തതും ഇല്ലാതാക്കിയതുമായവ ഒഴികെ നിങ്ങളുടെ എല്ലാ ഫോട്ടോകളും വീഡിയോകളും", - "partner_can_access_location": "നിങ്ങളുടെ ഫോട്ടോകൾ എടുത്ത സ്ഥലം", - "partner_list_user_photos": "{user}-ന്റെ ഫോട്ടോകൾ", - "partner_list_view_all": "എല്ലാം കാണുക", - "partner_page_empty_message": "നിങ്ങളുടെ ഫോട്ടോകൾ ഇതുവരെ ഒരു പങ്കാളിയുമായും പങ്കിട്ടിട്ടില്ല.", - "partner_page_no_more_users": "ചേർക്കാൻ കൂടുതൽ ഉപയോക്താക്കളില്ല", - "partner_page_partner_add_failed": "പങ്കാളിയെ ചേർക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "partner_page_select_partner": "പങ്കാളിയെ തിരഞ്ഞെടുക്കുക", - "partner_page_shared_to_title": "ഇതിലേക്ക് പങ്കിട്ടു", - "partner_page_stop_sharing_content": "{partner}-ക്ക് ഇനി നിങ്ങളുടെ ഫോട്ടോകൾ ആക്‌സസ് ചെയ്യാൻ കഴിയില്ല.", - "partner_sharing": "പങ്കാളിയുമായി പങ്കിടൽ", - "partners": "പങ്കാളികൾ", - "password": "പാസ്‌വേർഡ്", - "password_does_not_match": "പാസ്‌വേഡ് പൊരുത്തപ്പെടുന്നില്ല", - "password_required": "പാസ്‌വേഡ് ആവശ്യമാണ്", - "password_reset_success": "പാസ്‌വേഡ് റീസെറ്റ് വിജയിച്ചു", - "past_durations": { - "days": "കഴിഞ്ഞ {days, plural, one {ദിവസം} other {# ദിവസങ്ങൾ}}", - "hours": "കഴിഞ്ഞ {hours, plural, one {മണിക്കൂർ} other {# മണിക്കൂറുകൾ}}", - "years": "കഴിഞ്ഞ {years, plural, one {വർഷം} other {# വർഷങ്ങൾ}}" - }, - "path": "പാത്ത്", - "pattern": "പാറ്റേൺ", - "pause": "താൽക്കാലികമായി നിർത്തുക", - "pause_memories": "ഓർമ്മകൾ താൽക്കാലികമായി നിർത്തുക", - "paused": "താൽക്കാലികമായി നിർത്തി", - "pending": "കാത്തിരിക്കുന്നു", - "people": "ആളുകൾ", - "people_edits_count": "{count, plural, one {# വ്യക്തിയെ എഡിറ്റുചെയ്തു} other {# ആളുകളെ എഡിറ്റുചെയ്തു}}", - "people_feature_description": "ആളുകളനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും ബ്രൗസുചെയ്യുന്നു", - "people_sidebar_description": "സൈഡ്‌ബാറിൽ 'ആളുകൾ' എന്നതിലേക്ക് ഒരു ലിങ്ക് പ്രദർശിപ്പിക്കുക", - "permanent_deletion_warning": "ശാശ്വതമായ ഇല്ലാതാക്കൽ മുന്നറിയിപ്പ്", - "permanent_deletion_warning_setting_description": "അസറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കുമ്പോൾ ഒരു മുന്നറിയിപ്പ് കാണിക്കുക", - "permanently_delete": "ശാശ്വതമായി ഇല്ലാതാക്കുക", - "permanently_delete_assets_count": "{count, plural, one {അസറ്റ്} other {അസറ്റുകൾ}} ശാശ്വതമായി ഇല്ലാതാക്കുക", - "permanently_delete_assets_prompt": "{count, plural, one {ഈ അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?} other {ഈ # അസറ്റുകൾ ശാശ്വതമായി ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?}} ഇത് {count, plural, one {അതിനെ അതിന്റെ} other {അവയെ അവയുടെ}} ആൽബത്തിൽ(ങ്ങളിൽ) നിന്നും നീക്കം ചെയ്യും.", - "permanently_deleted_asset": "അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കി", - "permanently_deleted_assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ശാശ്വതമായി ഇല്ലാതാക്കി", - "permission": "അനുമതി", - "permission_empty": "നിങ്ങളുടെ അനുമതി ശൂന്യമാകരുത്", - "permission_onboarding_back": "തിരികെ", - "permission_onboarding_continue_anyway": "എന്തായാലും തുടരുക", - "permission_onboarding_get_started": "തുടങ്ങാം", - "permission_onboarding_go_to_settings": "ക്രമീകരണങ്ങളിലേക്ക് പോകുക", - "permission_onboarding_permission_denied": "അനുമതി നിഷേധിച്ചു. Immich ഉപയോഗിക്കുന്നതിന്, ക്രമീകരണങ്ങളിൽ ഫോട്ടോ, വീഡിയോ അനുമതികൾ നൽകുക.", - "permission_onboarding_permission_granted": "അനുമതി ലഭിച്ചു! നിങ്ങൾ തയ്യാറാണ്.", - "permission_onboarding_permission_limited": "അനുമതി പരിമിതമാണ്. Immich-ന് നിങ്ങളുടെ മുഴുവൻ ഗാലറി ശേഖരവും ബാക്കപ്പ് ചെയ്യാനും കൈകാര്യം ചെയ്യാനും അനുവദിക്കുന്നതിന്, ക്രമീകരണങ്ങളിൽ ഫോട്ടോ, വീഡിയോ അനുമതികൾ നൽകുക.", - "permission_onboarding_request": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും കാണുന്നതിന് Immich-ന് അനുമതി ആവശ്യമാണ്.", - "person": "വ്യക്തി", - "person_age_months": "{months, plural, one {# മാസം} other {# മാസം}} പ്രായം", - "person_age_year_months": "1 വർഷവും {months, plural, one {# മാസവും} other {# മാസവും}} പ്രായം", - "person_age_years": "{years, plural, other {# വയസ്സ്}}", - "person_birthdate": "{date}-ന് ജനിച്ചു", - "person_hidden": "{name}{hidden, select, true { (മറച്ചത്)} other {}}", - "photo_shared_all_users": "നിങ്ങൾ എല്ലാ ഉപയോക്താക്കളുമായും ഫോട്ടോകൾ പങ്കിട്ടു, അല്ലെങ്കിൽ നിങ്ങൾക്ക് പങ്കിടാൻ ഉപയോക്താക്കളാരും ഇല്ലെന്നു തോന്നുന്നു.", - "photos": "ഫോട്ടോകൾ", - "photos_and_videos": "ഫോട്ടോകളും വീഡിയോകളും", - "photos_count": "{count, plural, one {{count, number} ഫോട്ടോ} other {{count, number} ഫോട്ടോകൾ}}", - "photos_from_previous_years": "മുൻ വർഷങ്ങളിലെ ഫോട്ടോകൾ", - "pick_a_location": "ഒരു സ്ഥലം തിരഞ്ഞെടുക്കുക", - "pick_custom_range": "ഇഷ്ടാനുസൃത പരിധി", - "pick_date_range": "ഒരു തീയതി പരിധി തിരഞ്ഞെടുക്കുക", - "pin_code_changed_successfully": "പിൻ കോഡ് വിജയകരമായി മാറ്റി", - "pin_code_reset_successfully": "പിൻ കോഡ് വിജയകരമായി റീസെറ്റ് ചെയ്തു", - "pin_code_setup_successfully": "പിൻ കോഡ് വിജയകരമായി സജ്ജീകരിച്ചു", - "pin_verification": "പിൻ കോഡ് പരിശോധന", - "place": "സ്ഥലം", - "places": "സ്ഥലങ്ങൾ", - "places_count": "{count, plural, one {{count, number} സ്ഥലം} other {{count, number} സ്ഥലങ്ങൾ}}", - "play": "പ്ലേ ചെയ്യുക", - "play_memories": "ഓർമ്മകൾ പ്ലേ ചെയ്യുക", - "play_motion_photo": "ചലിക്കുന്ന ഫോട്ടോ പ്ലേ ചെയ്യുക", - "play_or_pause_video": "വീഡിയോ പ്ലേ ചെയ്യുക അല്ലെങ്കിൽ താൽക്കാലികമായി നിർത്തുക", - "play_original_video": "ഒറിജിനൽ വീഡിയോ പ്ലേ ചെയ്യുക", - "play_original_video_setting_description": "ട്രാൻസ്‌കോഡ് ചെയ്ത വീഡിയോകളേക്കാൾ ഒറിജിനൽ വീഡിയോകൾ പ്ലേ ചെയ്യാൻ മുൻഗണന നൽകുക. ഒറിജിനൽ ഫയൽ അനുയോജ്യമല്ലെങ്കിൽ അത് ശരിയായി പ്ലേ ചെയ്തേക്കില്ല.", - "play_transcoded_video": "ട്രാൻസ്‌കോഡ് ചെയ്ത വീഡിയോ പ്ലേ ചെയ്യുക", - "please_auth_to_access": "ആക്‌സസ് ചെയ്യുന്നതിന് ദയവായി പ്രാമാണീകരിക്കുക", - "port": "പോർട്ട്", - "preferences_settings_subtitle": "ആപ്പിന്റെ മുൻഗണനകൾ കൈകാര്യം ചെയ്യുക", - "preferences_settings_title": "മുൻഗണനകൾ", - "preparing": "തയ്യാറാക്കുന്നു", - "preset": "പ്രീസെറ്റ്", - "preview": "പ്രിവ്യൂ", - "previous": "മുമ്പത്തെ", - "previous_memory": "മുമ്പത്തെ ഓർമ്മ", - "previous_or_next_day": "ദിവസം മുന്നോട്ട്/പുറകോട്ട്", - "previous_or_next_month": "മാസം മുന്നോട്ട്/പുറകോട്ട്", - "previous_or_next_photo": "ഫോട്ടോ മുന്നോട്ട്/പുറകോട്ട്", - "previous_or_next_year": "വർഷം മുന്നോട്ട്/പുറകോട്ട്", - "primary": "പ്രധാനമായത്", - "privacy": "സ്വകാര്യത", - "profile": "പ്രൊഫൈൽ", - "profile_drawer_app_logs": "ലോഗുകൾ", - "profile_drawer_client_server_up_to_date": "ക്ലയിന്റും സെർവറും ഏറ്റവും പുതിയതാണ്", - "profile_drawer_github": "ഗിറ്റ്ഹബ്", - "profile_drawer_readonly_mode": "റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനക്ഷമമാക്കി. പുറത്തുകടക്കാൻ ഉപയോക്തൃ അവതാർ ഐക്കണിൽ ദീർഘനേരം അമർത്തുക.", - "profile_image_of_user": "{user}-ന്റെ പ്രൊഫൈൽ ചിത്രം", - "profile_picture_set": "പ്രൊഫൈൽ ചിത്രം സജ്ജീകരിച്ചു.", - "public_album": "പൊതു ആൽബം", - "public_share": "പൊതു പങ്കിടൽ", - "purchase_account_info": "സഹായി", - "purchase_activated_subtitle": "Immich-നെയും ഓപ്പൺ സോഴ്‌സ് സോഫ്റ്റ്‌വെയറിനെയും പിന്തുണച്ചതിന് നന്ദി", - "purchase_activated_time": "{date}-ന് സജീവമാക്കി", - "purchase_activated_title": "നിങ്ങളുടെ കീ വിജയകരമായി സജീവമാക്കി", - "purchase_button_activate": "സജീവമാക്കുക", - "purchase_button_buy": "വാങ്ങുക", - "purchase_button_buy_immich": "Immich വാങ്ങുക", - "purchase_button_never_show_again": "ഇനി കാണിക്കരുത്", - "purchase_button_reminder": "30 ദിവസത്തിനുള്ളിൽ ഓർമ്മിപ്പിക്കുക", - "purchase_button_remove_key": "കീ നീക്കം ചെയ്യുക", - "purchase_button_select": "തിരഞ്ഞെടുക്കുക", - "purchase_failed_activation": "സജീവമാക്കുന്നതിൽ പരാജയപ്പെട്ടു! ശരിയായ പ്രൊഡക്റ്റ് കീക്കായി ദയവായി നിങ്ങളുടെ ഇമെയിൽ പരിശോധിക്കുക!", - "purchase_individual_description_1": "ഒരു വ്യക്തിക്ക്", - "purchase_individual_description_2": "സഹായിയുടെ നില", - "purchase_individual_title": "വ്യക്തിഗതം", - "purchase_input_suggestion": "പ്രൊഡക്റ്റ് കീ ഉണ്ടോ? താഴെ കീ നൽകുക", - "purchase_license_subtitle": "സേവനത്തിന്റെ തുടർ വികസനത്തെ പിന്തുണയ്ക്കാൻ Immich വാങ്ങുക", - "purchase_lifetime_description": "ആജീവനാന്ത വാങ്ങൽ", - "purchase_option_title": "വാങ്ങാനുള്ള ഓപ്ഷനുകൾ", - "purchase_panel_info_1": "Immich നിർമ്മിക്കാൻ ധാരാളം സമയവും പ്രയത്നവും ആവശ്യമാണ്, അത് കഴിയുന്നത്ര മികച്ചതാക്കാൻ മുഴുവൻ സമയ എഞ്ചിനീയർമാർ പ്രവർത്തിക്കുന്നു. ഓപ്പൺ സോഴ്‌സ് സോഫ്റ്റ്‌വെയറും ധാർമ്മികമായ ബിസിനസ്സ് രീതികളും ഡെവലപ്പർമാർക്ക് സുസ്ഥിരമായ വരുമാന മാർഗ്ഗമാക്കുക, ചൂഷണപരമായ ക്ലൗഡ് സേവനങ്ങൾക്ക് യഥാർത്ഥ ബദലുകളുള്ള സ്വകാര്യതയെ മാനിക്കുന്ന ഒരു ആവാസവ്യവസ്ഥ സൃഷ്ടിക്കുക എന്നതാണ് ഞങ്ങളുടെ ദൗത്യം.", - "purchase_panel_info_2": "പേവാളുകൾ ചേർക്കില്ലെന്ന് ഞങ്ങൾ പ്രതിജ്ഞാബദ്ധരായതിനാൽ, ഈ വാങ്ങൽ നിങ്ങൾക്ക് Immich-ൽ അധിക ഫീച്ചറുകളൊന്നും നൽകില്ല. Immich-ന്റെ തുടർ വികസനത്തെ പിന്തുണയ്ക്കാൻ നിങ്ങളെപ്പോലുള്ള ഉപയോക്താക്കളെയാണ് ഞങ്ങൾ ആശ്രയിക്കുന്നത്.", - "purchase_panel_title": "പദ്ധതിയെ പിന്തുണയ്ക്കുക", - "purchase_per_server": "ഓരോ സെർവറിനും", - "purchase_per_user": "ഓരോ ഉപയോക്താവിനും", - "purchase_remove_product_key": "പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യുക", - "purchase_remove_product_key_prompt": "പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "purchase_remove_server_product_key": "സെർവർ പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യുക", - "purchase_remove_server_product_key_prompt": "സെർവർ പ്രൊഡക്റ്റ് കീ നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "purchase_server_description_1": "മുഴുവൻ സെർവറിനും", - "purchase_server_description_2": "സഹായിയുടെ നില", - "purchase_server_title": "സെർവർ", - "purchase_settings_server_activated": "സെർവർ പ്രൊഡക്റ്റ് കീ അഡ്മിൻ ആണ് കൈകാര്യം ചെയ്യുന്നത്", - "query_asset_id": "അസറ്റ് ഐഡി അന്വേഷിക്കുക", - "queue_status": "ക്യൂ ചെയ്യുന്നു {count}/{total}", - "rating": "സ്റ്റാർ റേറ്റിംഗ്", - "rating_clear": "റേറ്റിംഗ് ക്ലിയർ ചെയ്യുക", - "rating_count": "{count, plural, one {# സ്റ്റാർ} other {# സ്റ്റാറുകൾ}}", - "rating_description": "വിവര പാനലിൽ EXIF റേറ്റിംഗ് പ്രദർശിപ്പിക്കുക", - "reaction_options": "പ്രതികരണ ഓപ്ഷനുകൾ", - "read_changelog": "മാറ്റങ്ങളുടെ ലോഗ് വായിക്കുക", - "readonly_mode_disabled": "റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനരഹിതമാക്കി", - "readonly_mode_enabled": "റീഡ്-ഓൺലി മോഡ് പ്രവർത്തനക്ഷമമാക്കി", - "ready_for_upload": "അപ്‌ലോഡിനായി തയ്യാറാണ്", - "reassign": "വീണ്ടും നൽകുക", - "reassigned_assets_to_existing_person": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} {name, select, null {നിലവിലുള്ള ഒരു വ്യക്തിക്ക്} other {{name}-ന്}} വീണ്ടും നൽകി", - "reassigned_assets_to_new_person": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ഒരു പുതിയ വ്യക്തിക്ക് വീണ്ടും നൽകി", - "reassing_hint": "തിരഞ്ഞെടുത്ത അസറ്റുകൾ നിലവിലുള്ള ഒരു വ്യക്തിക്ക് നൽകുക", - "recent": "സമീപകാലം", - "recent-albums": "സമീപകാല ആൽബങ്ങൾ", - "recent_searches": "സമീപകാല തിരയലുകൾ", - "recently_added": "അടുത്തിടെ ചേർത്തത്", - "recently_added_page_title": "അടുത്തിടെ ചേർത്തത്", - "recently_taken": "അടുത്തിടെ എടുത്തത്", - "recently_taken_page_title": "അടുത്തിടെ എടുത്തത്", - "refresh": "പുതുക്കുക", - "refresh_encoded_videos": "എൻകോഡ് ചെയ്ത വീഡിയോകൾ പുതുക്കുക", - "refresh_faces": "മുഖങ്ങൾ പുതുക്കുക", - "refresh_metadata": "മെറ്റാഡാറ്റ പുതുക്കുക", - "refresh_thumbnails": "തംബ്നെയിലുകൾ പുതുക്കുക", - "refreshed": "പുതുക്കി", - "refreshes_every_file": "നിലവിലുള്ളതും പുതിയതുമായ എല്ലാ ഫയലുകളും വീണ്ടും വായിക്കുന്നു", - "refreshing_encoded_video": "എൻകോഡ് ചെയ്ത വീഡിയോ പുതുക്കുന്നു", - "refreshing_faces": "മുഖങ്ങൾ പുതുക്കുന്നു", - "refreshing_metadata": "മെറ്റാഡാറ്റ പുതുക്കുന്നു", - "regenerating_thumbnails": "തംബ്നെയിലുകൾ പുനർനിർമ്മിക്കുന്നു", - "remote": "റിമോട്ട്", - "remote_assets": "റിമോട്ട് അസറ്റുകൾ", - "remote_media_summary": "റിമോട്ട് മീഡിയ സംഗ്രഹം", - "remove": "നീക്കം ചെയ്യുക", - "remove_assets_album_confirmation": "ആൽബത്തിൽ നിന്ന് {count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "remove_assets_shared_link_confirmation": "ഈ പങ്കിട്ട ലിങ്കിൽ നിന്ന് {count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} നീക്കം ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "remove_assets_title": "അസറ്റുകൾ നീക്കം ചെയ്യണോ?", - "remove_custom_date_range": "കസ്റ്റം തീയതി പരിധി നീക്കം ചെയ്യുക", - "remove_deleted_assets": "ഇല്ലാതാക്കിയ അസറ്റുകൾ നീക്കം ചെയ്യുക", - "remove_from_album": "ആൽബത്തിൽ നിന്ന് നീക്കം ചെയ്യുക", - "remove_from_album_action_prompt": "{count} എണ്ണം ആൽബത്തിൽ നിന്ന് നീക്കം ചെയ്തു", - "remove_from_favorites": "പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്യുക", - "remove_from_lock_folder_action_prompt": "{count} എണ്ണം ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് നീക്കം ചെയ്തു", - "remove_from_locked_folder": "ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് നീക്കം ചെയ്യുക", - "remove_from_locked_folder_confirmation": "ഈ ഫോട്ടോകളും വീഡിയോകളും ലോക്ക് ചെയ്ത ഫോൾഡറിൽ നിന്ന് മാറ്റണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? അവ നിങ്ങളുടെ ലൈബ്രറിയിൽ ദൃശ്യമാകും.", - "remove_from_shared_link": "പങ്കിട്ട ലിങ്കിൽ നിന്ന് നീക്കം ചെയ്യുക", - "remove_memory": "ഓർമ്മ നീക്കം ചെയ്യുക", - "remove_photo_from_memory": "ഈ ഓർമ്മയിൽ നിന്ന് ഫോട്ടോ നീക്കം ചെയ്യുക", - "remove_tag": "ടാഗ് നീക്കം ചെയ്യുക", - "remove_url": "URL നീക്കം ചെയ്യുക", - "remove_user": "ഉപയോക്താവിനെ നീക്കം ചെയ്യുക", - "removed_api_key": "API കീ നീക്കം ചെയ്തു: {name}", - "removed_from_archive": "ആർക്കൈവിൽ നിന്ന് നീക്കം ചെയ്തു", - "removed_from_favorites": "പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു", - "removed_from_favorites_count": "{count, plural, other {# എണ്ണം}} പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു", - "removed_memory": "ഓർമ്മ നീക്കം ചെയ്തു", - "removed_photo_from_memory": "ഓർമ്മയിൽ നിന്ന് ഫോട്ടോ നീക്കം ചെയ്തു", - "removed_tagged_assets": "{count, plural, one {# അസറ്റിൽ നിന്ന്} other {# അസറ്റുകളിൽ നിന്ന്}} ടാഗ് നീക്കം ചെയ്തു", - "rename": "പുനർനാമകരണം ചെയ്യുക", - "repair": "റിപ്പയർ ചെയ്യുക", - "repair_no_results_message": "ട്രാക്ക് ചെയ്യാത്തതും കാണാതായതുമായ ഫയലുകൾ ഇവിടെ കാണിക്കും", - "replace_with_upload": "അപ്‌ലോഡ് ഉപയോഗിച്ച് മാറ്റിസ്ഥാപിക്കുക", - "repository": "റെപ്പോസിറ്ററി", - "require_password": "പാസ്‌വേഡ് ആവശ്യമാണ്", - "require_user_to_change_password_on_first_login": "ആദ്യ ലോഗിൻ ചെയ്യുമ്പോൾ പാസ്‌വേഡ് മാറ്റാൻ ഉപയോക്താവിനോട് ആവശ്യപ്പെടുക", - "rescan": "വീണ്ടും സ്കാൻ ചെയ്യുക", - "reset": "റീസെറ്റ് ചെയ്യുക", - "reset_password": "പാസ്‌വേഡ് റീസെറ്റ് ചെയ്യുക", - "reset_people_visibility": "ആളുകളുടെ ദൃശ്യത പുനഃസജ്ജമാക്കുക", - "reset_pin_code": "പിൻ കോഡ് റീസെറ്റ് ചെയ്യുക", - "reset_pin_code_description": "നിങ്ങൾ പിൻ കോഡ് മറന്നെങ്കിൽ, അത് പുനഃസജ്ജമാക്കാൻ നിങ്ങൾക്ക് സെർവർ അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടാം", - "reset_pin_code_success": "പിൻ കോഡ് വിജയകരമായി റീസെറ്റ് ചെയ്തു", - "reset_pin_code_with_password": "നിങ്ങളുടെ പാസ്‌വേഡ് ഉപയോഗിച്ച് നിങ്ങൾക്ക് എപ്പോഴും പിൻ കോഡ് റീസെറ്റ് ചെയ്യാം", - "reset_sqlite": "SQLite ഡാറ്റാബേസ് റീസെറ്റ് ചെയ്യുക", - "reset_sqlite_confirmation": "SQLite ഡാറ്റാബേസ് റീസെറ്റ് ചെയ്യണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ? ഡാറ്റ വീണ്ടും സിങ്ക് ചെയ്യുന്നതിന് നിങ്ങൾ ലോഗ് ഔട്ട് ചെയ്ത് വീണ്ടും ലോഗിൻ ചെയ്യേണ്ടിവരും", - "reset_sqlite_success": "SQLite ഡാറ്റാബേസ് വിജയകരമായി റീസെറ്റ് ചെയ്തു", - "reset_to_default": "ഡിഫോൾട്ടിലേക്ക് പുനഃസജ്ജമാക്കുക", - "resolution": "റെസല്യൂഷൻ", - "resolve_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ പരിഹരിക്കുക", - "resolved_all_duplicates": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും പരിഹരിച്ചു", - "restore": "പുനഃസ്ഥാപിക്കുക", - "restore_all": "എല്ലാം പുനഃസ്ഥാപിക്കുക", - "restore_trash_action_prompt": "{count} എണ്ണം ട്രാഷിൽ നിന്ന് പുനഃസ്ഥാപിച്ചു", - "restore_user": "ഉപയോക്താവിനെ പുനഃസ്ഥാപിക്കുക", - "restored_asset": "അസറ്റ് പുനഃസ്ഥാപിച്ചു", - "resume": "പുനരാരംഭിക്കുക", - "resume_paused_jobs": "{count, plural, one {താൽക്കാലികമായി നിർത്തിയ # ജോലി} other {താൽക്കാലികമായി നിർത്തിയ # ജോലികൾ}} പുനരാരംഭിക്കുക", - "retry_upload": "അപ്‌ലോഡ് വീണ്ടും ശ്രമിക്കുക", - "review_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ അവലോകനം ചെയ്യുക", - "review_large_files": "വലിയ ഫയലുകൾ അവലോകനം ചെയ്യുക", - "role": "റോൾ", - "role_editor": "എഡിറ്റർ", - "role_viewer": "കാണുന്നയാൾ", - "running": "പ്രവർത്തിക്കുന്നു", - "save": "സേവ് ചെയ്യുക", - "save_to_gallery": "ഗാലറിയിലേക്ക് സേവ് ചെയ്യുക", - "saved": "സേവ് ചെയ്തു", - "saved_api_key": "API കീ സേവ് ചെയ്തു", - "saved_profile": "പ്രൊഫൈൽ സേവ് ചെയ്തു", - "saved_settings": "ക്രമീകരണങ്ങൾ സേവ് ചെയ്തു", - "say_something": "എന്തെങ്കിലും പറയൂ", - "scaffold_body_error_occurred": "പിശക് സംഭവിച്ചു", - "scan_all_libraries": "എല്ലാ ലൈബ്രറികളും സ്കാൻ ചെയ്യുക", - "scan_library": "സ്കാൻ ചെയ്യുക", - "scan_settings": "സ്കാൻ ക്രമീകരണങ്ങൾ", - "scanning_for_album": "ആൽബത്തിനായി സ്കാൻ ചെയ്യുന്നു...", - "search": "തിരയുക", - "search_albums": "ആൽബങ്ങൾക്കായി തിരയുക", - "search_by_context": "സന്ദർഭം അനുസരിച്ച് തിരയുക", - "search_by_description": "വിവരണം അനുസരിച്ച് തിരയുക", - "search_by_description_example": "സപ്പയിലെ ഹൈക്കിംഗ് ദിനം", - "search_by_filename": "ഫയൽ നാമം അല്ലെങ്കിൽ എക്സ്റ്റൻഷൻ ഉപയോഗിച്ച് തിരയുക", - "search_by_filename_example": "ഉദാ. IMG_1234.JPG അല്ലെങ്കിൽ PNG", - "search_by_ocr": "ഒ.സി.ആർ ഉപയോഗിച്ച് തിരയുക", - "search_by_ocr_example": "ലാറ്റെ (Latte)", - "search_camera_lens_model": "ലെൻസ് മോഡൽ തിരയുക...", - "search_camera_make": "ക്യാമറ നിർമ്മാതാവിനായി തിരയുക...", - "search_camera_model": "ക്യാമറ മോഡലിനായി തിരയുക...", - "search_city": "നഗരത്തിനായി തിരയുക...", - "search_country": "രാജ്യത്തിനായി തിരയുക...", - "search_filter_apply": "ഫിൽട്ടർ പ്രയോഗിക്കുക", - "search_filter_camera_title": "ക്യാമറ തരം തിരഞ്ഞെടുക്കുക", - "search_filter_date": "തീയതി", - "search_filter_date_interval": "{start} മുതൽ {end} വരെ", - "search_filter_date_title": "ഒരു തീയതി പരിധി തിരഞ്ഞെടുക്കുക", - "search_filter_display_option_not_in_album": "ആൽബത്തിൽ ഇല്ല", - "search_filter_display_options": "പ്രദർശന ഓപ്ഷനുകൾ", - "search_filter_filename": "ഫയൽ നാമം ഉപയോഗിച്ച് തിരയുക", - "search_filter_location": "സ്ഥാനം", - "search_filter_location_title": "സ്ഥാനം തിരഞ്ഞെടുക്കുക", - "search_filter_media_type": "മീഡിയ തരം", - "search_filter_media_type_title": "മീഡിയ തരം തിരഞ്ഞെടുക്കുക", - "search_filter_ocr": "ഒ.സി.ആർ ഉപയോഗിച്ച് തിരയുക", - "search_filter_people_title": "ആളുകളെ തിരഞ്ഞെടുക്കുക", - "search_for": "ഇതിനായി തിരയുക", - "search_for_existing_person": "നിലവിലുള്ള വ്യക്തിക്കായി തിരയുക", - "search_no_more_result": "കൂടുതൽ ഫലങ്ങളില്ല", - "search_no_people": "ആളുകളില്ല", - "search_no_people_named": "\"{name}\" എന്ന് പേരുള്ള ആളുകളില്ല", - "search_no_result": "ഫലങ്ങളൊന്നും കണ്ടെത്തിയില്ല, മറ്റൊരു തിരയൽ പദമോ സംയോജനമോ ഉപയോഗിച്ച് ശ്രമിക്കുക", - "search_options": "തിരയൽ ഓപ്ഷനുകൾ", - "search_page_categories": "വിഭാഗങ്ങൾ", - "search_page_motion_photos": "ചലിക്കുന്ന ഫോട്ടോകൾ", - "search_page_no_objects": "വസ്തുക്കളുടെ വിവരങ്ങൾ ലഭ്യമല്ല", - "search_page_no_places": "സ്ഥലങ്ങളുടെ വിവരങ്ങൾ ലഭ്യമല്ല", - "search_page_screenshots": "സ്ക്രീൻഷോട്ടുകൾ", - "search_page_search_photos_videos": "നിങ്ങളുടെ ഫോട്ടോകൾക്കും വീഡിയോകൾക്കുമായി തിരയുക", - "search_page_selfies": "സെൽഫികൾ", - "search_page_things": "വസ്തുക്കൾ", - "search_page_view_all_button": "എല്ലാം കാണുക", - "search_page_your_activity": "നിങ്ങളുടെ പ്രവർത്തനം", - "search_page_your_map": "നിങ്ങളുടെ മാപ്പ്", - "search_people": "ആളുകളെ തിരയുക", - "search_places": "സ്ഥലങ്ങൾ തിരയുക", - "search_rating": "റേറ്റിംഗ് അനുസരിച്ച് തിരയുക...", - "search_result_page_new_search_hint": "പുതിയ തിരയൽ", - "search_settings": "തിരയൽ ക്രമീകരണങ്ങൾ", - "search_state": "സംസ്ഥാനം തിരയുക...", - "search_suggestion_list_smart_search_hint_1": "സ്മാർട്ട് സെർച്ച് ഡിഫോൾട്ടായി പ്രവർത്തനക്ഷമമാക്കിയിരിക്കുന്നു, മെറ്റാഡാറ്റയ്ക്കായി തിരയാൻ ഈ വാക്യഘടന ഉപയോഗിക്കുക ", - "search_suggestion_list_smart_search_hint_2": "m:നിങ്ങളുടെ-തിരയൽ-പദം", - "search_tags": "ടാഗുകൾക്കായി തിരയുക...", - "search_timezone": "സമയമേഖലയ്ക്കായി തിരയുക...", - "search_type": "തിരയൽ തരം", - "search_your_photos": "നിങ്ങളുടെ ഫോട്ടോകൾ തിരയുക", - "searching_locales": "ലൊക്കേലുകൾക്കായി തിരയുന്നു...", - "second": "സെക്കന്റ്", - "see_all_people": "എല്ലാ ആളുകളെയും കാണുക", - "select": "തിരഞ്ഞെടുക്കുക", - "select_album_cover": "ആൽബം കവർ തിരഞ്ഞെടുക്കുക", - "select_all": "എല്ലാം തിരഞ്ഞെടുക്കുക", - "select_all_duplicates": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും തിരഞ്ഞെടുക്കുക", - "select_all_in": "{group}-ലെ എല്ലാം തിരഞ്ഞെടുക്കുക", - "select_avatar_color": "അവതാർ നിറം തിരഞ്ഞെടുക്കുക", - "select_face": "മുഖം തിരഞ്ഞെടുക്കുക", - "select_featured_photo": "ഫീച്ചർ ചെയ്ത ഫോട്ടോ തിരഞ്ഞെടുക്കുക", - "select_from_computer": "കമ്പ്യൂട്ടറിൽ നിന്ന് തിരഞ്ഞെടുക്കുക", - "select_keep_all": "എല്ലാം സൂക്ഷിക്കാൻ തിരഞ്ഞെടുക്കുക", - "select_library_owner": "ലൈബ്രറി ഉടമയെ തിരഞ്ഞെടുക്കുക", - "select_new_face": "പുതിയ മുഖം തിരഞ്ഞെടുക്കുക", - "select_person_to_tag": "ടാഗ് ചെയ്യാൻ ഒരു വ്യക്തിയെ തിരഞ്ഞെടുക്കുക", - "select_photos": "ഫോട്ടോകൾ തിരഞ്ഞെടുക്കുക", - "select_trash_all": "എല്ലാം ട്രാഷ് ചെയ്യാൻ തിരഞ്ഞെടുക്കുക", - "select_user_for_sharing_page_err_album": "ആൽബം ഉണ്ടാക്കുന്നതിൽ പരാജയപ്പെട്ടു", - "selected": "തിരഞ്ഞെടുത്തു", - "selected_count": "{count, plural, other {# എണ്ണം തിരഞ്ഞെടുത്തു}}", - "selected_gps_coordinates": "തിരഞ്ഞെടുത്ത GPS കോർഡിനേറ്റുകൾ", - "send_message": "സന്ദേശം അയക്കുക", - "send_welcome_email": "സ്വാഗത ഇമെയിൽ അയക്കുക", - "server_endpoint": "സെർവർ എൻഡ്‌പോയിന്റ്", - "server_info_box_app_version": "ആപ്പ് പതിപ്പ്", - "server_info_box_server_url": "സെർവർ URL", - "server_offline": "സെർവർ ഓഫ്‌ലൈൻ", - "server_online": "സെർവർ ഓൺലൈൻ", - "server_privacy": "സെർവർ സ്വകാര്യത", - "server_restarting_description": "ഈ പേജ് ഉടൻ തന്നെ പുതുക്കും.", - "server_restarting_title": "സെർവർ റീസ്റ്റാർട്ട് ചെയ്യുന്നു", - "server_stats": "സെർവർ സ്ഥിതിവിവരക്കണക്കുകൾ", - "server_update_available": "സെർവർ അപ്‌ഡേറ്റ് ലഭ്യമാണ്", - "server_version": "സെർവർ പതിപ്പ്", - "set": "സജ്ജമാക്കുക", - "set_as_album_cover": "ആൽബം കവറായി സജ്ജമാക്കുക", - "set_as_featured_photo": "ഫീച്ചർ ചെയ്ത ഫോട്ടോയായി സജ്ജമാക്കുക", - "set_as_profile_picture": "പ്രൊഫൈൽ ചിത്രമായി സജ്ജമാക്കുക", - "set_date_of_birth": "ജനനത്തീയതി സജ്ജമാക്കുക", - "set_profile_picture": "പ്രൊഫൈൽ ചിത്രം സജ്ജമാക്കുക", - "set_slideshow_to_fullscreen": "സ്ലൈഡ്‌ഷോ ഫുൾസ്ക്രീനായി സജ്ജമാക്കുക", - "set_stack_primary_asset": "പ്രധാന അസറ്റായി സജ്ജമാക്കുക", - "setting_image_viewer_help": "വിശദാംശ വ്യൂവർ ആദ്യം ചെറിയ തംബ്നെയിൽ ലോഡുചെയ്യുന്നു, തുടർന്ന് ഇടത്തരം പ്രിവ്യൂ (പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ), ഒടുവിൽ യഥാർത്ഥ ചിത്രം (പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ) ലോഡുചെയ്യുന്നു.", - "setting_image_viewer_original_subtitle": "യഥാർത്ഥ പൂർണ്ണ-റെസല്യൂഷൻ ചിത്രം (വലുത്!) ലോഡുചെയ്യാൻ പ്രവർത്തനക്ഷമമാക്കുക. ഡാറ്റ ഉപയോഗം (നെറ്റ്‌വർക്കും ഉപകരണ കാഷെയും) കുറയ്ക്കാൻ പ്രവർത്തനരഹിതമാക്കുക.", - "setting_image_viewer_original_title": "യഥാർത്ഥ ചിത്രം ലോഡുചെയ്യുക", - "setting_image_viewer_preview_subtitle": "ഇടത്തരം റെസല്യൂഷനുള്ള ചിത്രം ലോഡുചെയ്യാൻ പ്രവർത്തനക്ഷമമാക്കുക. ഒന്നുകിൽ യഥാർത്ഥ ചിത്രം നേരിട്ട് ലോഡുചെയ്യുന്നതിനോ അല്ലെങ്കിൽ തംബ്നെയിൽ മാത്രം ഉപയോഗിക്കുന്നതിനോ പ്രവർത്തനരഹിതമാക്കുക.", - "setting_image_viewer_preview_title": "പ്രിവ്യൂ ചിത്രം ലോഡുചെയ്യുക", - "setting_image_viewer_title": "ചിത്രങ്ങൾ", - "setting_languages_apply": "പ്രയോഗിക്കുക", - "setting_languages_subtitle": "ആപ്പിന്റെ ഭാഷ മാറ്റുക", - "setting_notifications_notify_failures_grace_period": "പശ്ചാത്തല ബാക്കപ്പ് പരാജയങ്ങൾ അറിയിക്കുക: {duration}", - "setting_notifications_notify_hours": "{count} മണിക്കൂർ", - "setting_notifications_notify_immediately": "ഉടനടി", - "setting_notifications_notify_minutes": "{count} മിനിറ്റ്", - "setting_notifications_notify_never": "ഒരിക്കലും", - "setting_notifications_notify_seconds": "{count} സെക്കൻഡ്", - "setting_notifications_single_progress_subtitle": "ഓരോ അസറ്റിന്റെയും വിശദമായ അപ്‌ലോഡ് പുരോഗതി വിവരം", - "setting_notifications_single_progress_title": "പശ്ചാത്തല ബാക്കപ്പിന്റെ വിശദമായ പുരോഗതി കാണിക്കുക", - "setting_notifications_subtitle": "നിങ്ങളുടെ അറിയിപ്പ് മുൻഗണനകൾ ക്രമീകരിക്കുക", - "setting_notifications_total_progress_subtitle": "മൊത്തത്തിലുള്ള അപ്‌ലോഡ് പുരോഗതി (ചെയ്തത്/ആകെ അസറ്റുകൾ)", - "setting_notifications_total_progress_title": "പശ്ചാത്തല ബാക്കപ്പിന്റെ മൊത്തം പുരോഗതി കാണിക്കുക", - "setting_video_viewer_auto_play_subtitle": "വീഡിയോകൾ തുറക്കുമ്പോൾ തന്നെ പ്ലേ ചെയ്യാൻ ആരംഭിക്കുക", - "setting_video_viewer_auto_play_title": "വീഡിയോകൾ ഓട്ടോ പ്ലേ ചെയ്യുക", - "setting_video_viewer_looping_title": "ലൂപ്പിംഗ്", - "setting_video_viewer_original_video_subtitle": "സെർവറിൽ നിന്ന് ഒരു വീഡിയോ സ്ട്രീം ചെയ്യുമ്പോൾ, ഒരു ട്രാൻസ്‌കോഡ് ലഭ്യമാണെങ്കിലും യഥാർത്ഥ വീഡിയോ പ്ലേ ചെയ്യുക. ഇത് ബഫറിംഗിന് കാരണമായേക്കാം. പ്രാദേശികമായി ലഭ്യമായ വീഡിയോകൾ ഈ ക്രമീകരണം പരിഗണിക്കാതെ തന്നെ യഥാർത്ഥ ഗുണമേന്മയിൽ പ്ലേ ചെയ്യും.", - "setting_video_viewer_original_video_title": "യഥാർത്ഥ വീഡിയോ നിർബന്ധമാക്കുക", - "settings": "ക്രമീകരണങ്ങൾ", - "settings_require_restart": "ഈ ക്രമീകരണം പ്രയോഗിക്കാൻ ദയവായി Immich പുനരാരംഭിക്കുക", - "settings_saved": "ക്രമീകരണങ്ങൾ സേവ് ചെയ്തു", - "setup_pin_code": "ഒരു പിൻ കോഡ് സജ്ജീകരിക്കുക", - "share": "പങ്കിടുക", - "share_action_prompt": "{count} അസറ്റുകൾ പങ്കിട്ടു", - "share_add_photos": "ഫോട്ടോകൾ ചേർക്കുക", - "share_assets_selected": "{count} എണ്ണം തിരഞ്ഞെടുത്തു", - "share_dialog_preparing": "തയ്യാറാക്കുന്നു...", - "share_link": "ലിങ്ക് പങ്കിടുക", - "shared": "പങ്കിട്ടത്", - "shared_album_activities_input_disable": "അഭിപ്രായം പ്രവർത്തനരഹിതമാക്കി", - "shared_album_activity_remove_content": "ഈ പ്രവർത്തനം ഇല്ലാതാക്കണോ?", - "shared_album_activity_remove_title": "പ്രവർത്തനം ഇല്ലാതാക്കുക", - "shared_album_section_people_action_error": "ആൽബത്തിൽ നിന്ന് വിട്ടുപോകുന്നതിനോ/നീക്കംചെയ്യുന്നതിനോ പിശക്", - "shared_album_section_people_action_leave": "ആൽബത്തിൽ നിന്ന് ഉപയോക്താവിനെ നീക്കം ചെയ്യുക", - "shared_album_section_people_action_remove_user": "ആൽബത്തിൽ നിന്ന് ഉപയോക്താവിനെ നീക്കം ചെയ്യുക", - "shared_album_section_people_title": "ആളുകൾ", - "shared_by": "പങ്കിട്ടത്", - "shared_by_user": "{user} പങ്കിട്ടു", - "shared_by_you": "നിങ്ങൾ പങ്കിട്ടത്", - "shared_from_partner": "{partner}-ൽ നിന്നുള്ള ഫോട്ടോകൾ", - "shared_intent_upload_button_progress_text": "{current}/{total} അപ്‌ലോഡ് ചെയ്തു", - "shared_link_app_bar_title": "പങ്കിട്ട ലിങ്കുകൾ", - "shared_link_clipboard_copied_massage": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തി", - "shared_link_clipboard_text": "ലിങ്ക്: {link}\nപാസ്‌വേഡ്: {password}", - "shared_link_create_error": "പങ്കിട്ട ലിങ്ക് ഉണ്ടാക്കുന്നതിൽ പിശക്", - "shared_link_custom_url_description": "ഒരു കസ്റ്റം URL ഉപയോഗിച്ച് ഈ പങ്കിട്ട ലിങ്ക് ആക്‌സസ് ചെയ്യുക", - "shared_link_edit_description_hint": "പങ്കിടലിന്റെ വിവരണം നൽകുക", - "shared_link_edit_expire_after_option_day": "1 ദിവസം", - "shared_link_edit_expire_after_option_days": "{count} ദിവസങ്ങൾ", - "shared_link_edit_expire_after_option_hour": "1 മണിക്കൂർ", - "shared_link_edit_expire_after_option_hours": "{count} മണിക്കൂറുകൾ", - "shared_link_edit_expire_after_option_minute": "1 മിനിറ്റ്", - "shared_link_edit_expire_after_option_minutes": "{count} മിനിറ്റുകൾ", - "shared_link_edit_expire_after_option_months": "{count} മാസങ്ങൾ", - "shared_link_edit_expire_after_option_year": "{count} വർഷം", - "shared_link_edit_password_hint": "പങ്കിടലിന്റെ പാസ്‌വേഡ് നൽകുക", - "shared_link_edit_submit_button": "ലിങ്ക് അപ്ഡേറ്റ് ചെയ്യുക", - "shared_link_error_server_url_fetch": "സെർവർ url ലഭ്യമാക്കാൻ കഴിയില്ല", - "shared_link_expires_day": "{count} ദിവസത്തിനുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_days": "{count} ദിവസങ്ങൾക്കുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_hour": "{count} മണിക്കൂറിനുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_hours": "{count} മണിക്കൂറുകൾക്കുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_minute": "{count} മിനിറ്റിനുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_minutes": "{count} മിനിറ്റുകൾക്കുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_never": "കാലഹരണപ്പെടില്ല ∞", - "shared_link_expires_second": "{count} സെക്കൻഡിനുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_expires_seconds": "{count} സെക്കൻഡുകൾക്കുള്ളിൽ കാലഹരണപ്പെടും", - "shared_link_individual_shared": "വ്യക്തിഗതമായി പങ്കിട്ടത്", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "പങ്കിട്ട ലിങ്കുകൾ കൈകാര്യം ചെയ്യുക", - "shared_link_options": "പങ്കിട്ട ലിങ്ക് ഓപ്ഷനുകൾ", - "shared_link_password_description": "ഈ പങ്കിട്ട ലിങ്ക് ആക്‌സസ് ചെയ്യാൻ ഒരു പാസ്‌വേഡ് ആവശ്യമാണ്", - "shared_links": "പങ്കിട്ട ലിങ്കുകൾ", - "shared_links_description": "ഒരു ലിങ്ക് ഉപയോഗിച്ച് ഫോട്ടോകളും വീഡിയോകളും പങ്കിടുക", - "shared_photos_and_videos_count": "{assetCount, plural, other {പങ്കിട്ട # ഫോട്ടോകളും വീഡിയോകളും.}}", - "shared_with_me": "എനിക്കായി പങ്കിട്ടത്", - "shared_with_partner": "{partner}-മായി പങ്കിട്ടു", - "sharing": "പങ്കിടൽ", - "sharing_enter_password": "ഈ പേജ് കാണുന്നതിന് ദയവായി പാസ്‌വേഡ് നൽകുക.", - "sharing_page_album": "പങ്കിട്ട ആൽബങ്ങൾ", - "sharing_page_description": "നിങ്ങളുടെ നെറ്റ്‌വർക്കിലെ ആളുകളുമായി ഫോട്ടോകളും വീഡിയോകളും പങ്കിടാൻ പങ്കിട്ട ആൽബങ്ങൾ ഉണ്ടാക്കുക.", - "sharing_page_empty_list": "ശൂന്യമായ ലിസ്റ്റ്", - "sharing_sidebar_description": "സൈഡ്‌ബാറിൽ 'പങ്കിടൽ' എന്നതിലേക്ക് ഒരു ലിങ്ക് പ്രദർശിപ്പിക്കുക", - "sharing_silver_appbar_create_shared_album": "പുതിയ പങ്കിട്ട ആൽബം", - "sharing_silver_appbar_share_partner": "പങ്കാളിയുമായി പങ്കിടുക", - "shift_to_permanent_delete": "അസറ്റ് ശാശ്വതമായി ഇല്ലാതാക്കാൻ ⇧ അമർത്തുക", - "show_album_options": "ആൽബം ഓപ്ഷനുകൾ കാണിക്കുക", - "show_albums": "ആൽബങ്ങൾ കാണിക്കുക", - "show_all_people": "എല്ലാ ആളുകളെയും കാണിക്കുക", - "show_and_hide_people": "ആളുകളെ കാണിക്കുകയും മറയ്ക്കുകയും ചെയ്യുക", - "show_file_location": "ഫയലിന്റെ സ്ഥാനം കാണിക്കുക", - "show_gallery": "ഗാലറി കാണിക്കുക", - "show_hidden_people": "മറച്ച ആളുകളെ കാണിക്കുക", - "show_in_timeline": "ടൈംലൈനിൽ കാണിക്കുക", - "show_in_timeline_setting_description": "ഈ ഉപയോക്താവിൽ നിന്നുള്ള ഫോട്ടോകളും വീഡിയോകളും നിങ്ങളുടെ ടൈംലൈനിൽ കാണിക്കുക", - "show_keyboard_shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ കാണിക്കുക", - "show_metadata": "മെറ്റാഡാറ്റ കാണിക്കുക", - "show_or_hide_info": "വിവരങ്ങൾ കാണിക്കുക അല്ലെങ്കിൽ മറയ്ക്കുക", - "show_password": "പാസ്‌വേഡ് കാണിക്കുക", - "show_person_options": "വ്യക്തിയുടെ ഓപ്ഷനുകൾ കാണിക്കുക", - "show_progress_bar": "പുരോഗതി ബാർ കാണിക്കുക", - "show_search_options": "തിരയൽ ഓപ്ഷനുകൾ കാണിക്കുക", - "show_shared_links": "പങ്കിട്ട ലിങ്കുകൾ കാണിക്കുക", - "show_slideshow_transition": "സ്ലൈഡ്‌ഷോ സംക്രമണം കാണിക്കുക", - "show_supporter_badge": "സഹായിയുടെ ബാഡ്ജ്", - "show_supporter_badge_description": "ഒരു സഹായിയുടെ ബാഡ്ജ് കാണിക്കുക", - "show_text_recognition": "ടെക്സ്റ്റ് തിരിച്ചറിയൽ കാണിക്കുക", - "show_text_search_menu": "ടെക്സ്റ്റ് തിരയൽ മെനു കാണിക്കുക", - "shuffle": "ഷഫിൾ ചെയ്യുക", - "sidebar": "സൈഡ്‌ബാർ", - "sidebar_display_description": "സൈഡ്‌ബാറിലെ കാഴ്‌ചയിലേക്ക് ഒരു ലിങ്ക് പ്രദർശിപ്പിക്കുക", - "sign_out": "സൈൻ ഔട്ട്", - "sign_up": "സൈൻ അപ്പ് ചെയ്യുക", - "size": "വലിപ്പം", - "skip_to_content": "ഉള്ളടക്കത്തിലേക്ക് പോകുക", - "skip_to_folders": "ഫോൾഡറുകളിലേക്ക് പോകുക", - "skip_to_tags": "ടാഗുകളിലേക്ക് പോകുക", - "slideshow": "സ്ലൈഡ്‌ഷോ", - "slideshow_settings": "സ്ലൈഡ്‌ഷോ ക്രമീകരണങ്ങൾ", - "sort_albums_by": "ആൽബങ്ങളെ ഇതനുസരിച്ച് ക്രമീകരിക്കുക...", - "sort_created": "സൃഷ്ടിച്ച തീയതി", - "sort_items": "ഇനങ്ങളുടെ എണ്ണം", - "sort_modified": "മാറ്റം വരുത്തിയ തീയതി", - "sort_newest": "ഏറ്റവും പുതിയ ഫോട്ടോ", - "sort_oldest": "ഏറ്റവും പഴയ ഫോട്ടോ", - "sort_people_by_similarity": "സാമ്യം അനുസരിച്ച് ആളുകളെ ക്രമീകരിക്കുക", - "sort_recent": "ഏറ്റവും പുതിയ ഫോട്ടോ", - "sort_title": "ശീർഷകം", - "source": "ഉറവിടം", - "stack": "സ്റ്റാക്ക്", - "stack_action_prompt": "{count} എണ്ണം സ്റ്റാക്ക് ചെയ്തു", - "stack_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ സ്റ്റാക്ക് ചെയ്യുക", - "stack_select_one_photo": "സ്റ്റാക്കിനായി ഒരു പ്രധാന ഫോട്ടോ തിരഞ്ഞെടുക്കുക", - "stack_selected_photos": "തിരഞ്ഞെടുത്ത ഫോട്ടോകൾ സ്റ്റാക്ക് ചെയ്യുക", - "stacked_assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} സ്റ്റാക്ക് ചെയ്തു", - "stacktrace": "സ്റ്റാക്ക്ട്രേസ്", - "start": "ആരംഭിക്കുക", - "start_date": "ആരംഭ തീയതി", - "start_date_before_end_date": "ആരംഭ തീയതി അവസാന തീയതിക്ക് മുമ്പായിരിക്കണം", - "state": "സംസ്ഥാനം", - "status": "നില", - "stop_casting": "കാസ്റ്റിംഗ് നിർത്തുക", - "stop_motion_photo": "ചലിക്കുന്ന ഫോട്ടോ നിർത്തുക", - "stop_photo_sharing": "നിങ്ങളുടെ ഫോട്ടോകൾ പങ്കിടുന്നത് നിർത്തണോ?", - "stop_photo_sharing_description": "{partner}-ക്ക് ഇനി നിങ്ങളുടെ ഫോട്ടോകൾ ആക്‌സസ് ചെയ്യാൻ കഴിയില്ല.", - "stop_sharing_photos_with_user": "ഈ ഉപയോക്താവുമായി നിങ്ങളുടെ ഫോട്ടോകൾ പങ്കിടുന്നത് നിർത്തുക", - "storage": "സംഭരണ സ്ഥലം", - "storage_label": "സ്റ്റോറേജ് ലേബൽ", - "storage_quota": "സ്റ്റോറേജ് ക്വാട്ട", - "storage_usage": "{available}-ൽ {used} ഉപയോഗിച്ചു", - "submit": "സമർപ്പിക്കുക", - "success": "വിജയം", - "suggestions": "നിർദ്ദേശങ്ങൾ", - "sunrise_on_the_beach": "ബീച്ചിലെ സൂര്യോദയം", - "support": "പിന്തുണ", - "support_and_feedback": "പിന്തുണയും അഭിപ്രായവും", - "support_third_party_description": "നിങ്ങളുടെ Immich ഇൻസ്റ്റാളേഷൻ ഒരു മൂന്നാം കക്ഷി പാക്കേജ് ചെയ്തതാണ്. നിങ്ങൾ അനുഭവിക്കുന്ന പ്രശ്നങ്ങൾ ആ പാക്കേജ് കാരണമാകാം, അതിനാൽ താഴെയുള്ള ലിങ്കുകൾ ഉപയോഗിച്ച് ആദ്യം അവരുമായി പ്രശ്നങ്ങൾ ഉന്നയിക്കുക.", - "swap_merge_direction": "ലയിപ്പിക്കൽ ദിശ മാറ്റുക", - "sync": "സിങ്ക്", - "sync_albums": "ആൽബങ്ങൾ സിങ്ക് ചെയ്യുക", - "sync_albums_manual_subtitle": "അപ്‌ലോഡ് ചെയ്ത എല്ലാ വീഡിയോകളും ഫോട്ടോകളും തിരഞ്ഞെടുത്ത ബാക്കപ്പ് ആൽബങ്ങളിലേക്ക് സിങ്ക് ചെയ്യുക", - "sync_local": "പ്രാദേശികമായി സിങ്ക് ചെയ്യുക", - "sync_remote": "റിമോട്ടായി സിങ്ക് ചെയ്യുക", - "sync_status": "സിങ്ക് നില", - "sync_status_subtitle": "സിങ്ക് സിസ്റ്റം കാണുക, കൈകാര്യം ചെയ്യുക", - "sync_upload_album_setting_subtitle": "നിങ്ങളുടെ ഫോട്ടോകളും വീഡിയോകളും Immich-ലെ തിരഞ്ഞെടുത്ത ആൽബങ്ങളിലേക്ക് ഉണ്ടാക്കി അപ്‌ലോഡ് ചെയ്യുക", - "tag": "ടാഗ്", - "tag_assets": "അസറ്റുകൾ ടാഗ് ചെയ്യുക", - "tag_created": "{tag} എന്ന ടാഗ് ഉണ്ടാക്കി", - "tag_feature_description": "യുക്തിസഹമായ ടാഗ് വിഷയങ്ങൾ അനുസരിച്ച് ഗ്രൂപ്പ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും ബ്രൗസുചെയ്യുന്നു", - "tag_not_found_question": "ഒരു ടാഗ് കണ്ടെത്താൻ കഴിയുന്നില്ലേ? ഒരു പുതിയ ടാഗ് ഉണ്ടാക്കുക.", - "tag_people": "ആളുകളെ ടാഗ് ചെയ്യുക", - "tag_updated": "{tag} എന്ന ടാഗ് അപ്ഡേറ്റ് ചെയ്തു", - "tagged_assets": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} ടാഗ് ചെയ്തു", - "tags": "ടാഗുകൾ", - "tap_to_run_job": "ജോലി പ്രവർത്തിപ്പിക്കാൻ ടാപ്പുചെയ്യുക", - "template": "ടെംപ്ലേറ്റ്", - "text_recognition": "ടെക്സ്റ്റ് തിരിച്ചറിയൽ", - "theme": "തീം", - "theme_selection": "തീം തിരഞ്ഞെടുക്കൽ", - "theme_selection_description": "നിങ്ങളുടെ ബ്രൗസറിന്റെ സിസ്റ്റം മുൻഗണന അനുസരിച്ച് തീം ലൈറ്റ് അല്ലെങ്കിൽ ഡാർക്ക് ആയി യാന്ത്രികമായി സജ്ജമാക്കുക", - "theme_setting_asset_list_storage_indicator_title": "അസറ്റ് ടൈലുകളിൽ സ്റ്റോറേജ് ഇൻഡിക്കേറ്റർ കാണിക്കുക", - "theme_setting_asset_list_tiles_per_row_title": "ഓരോ വരിയിലും അസറ്റുകളുടെ എണ്ണം ({count})", - "theme_setting_colorful_interface_subtitle": "പശ്ചാത്തല പ്രതലങ്ങളിൽ പ്രധാന നിറം പ്രയോഗിക്കുക.", - "theme_setting_colorful_interface_title": "വർണ്ണാഭമായ ഇന്റർഫേസ്", - "theme_setting_image_viewer_quality_subtitle": "വിശദാംശ ഇമേജ് വ്യൂവറിന്റെ ഗുണമേന്മ ക്രമീകരിക്കുക", - "theme_setting_image_viewer_quality_title": "ഇമേജ് വ്യൂവർ ഗുണമേന്മ", - "theme_setting_primary_color_subtitle": "പ്രധാന പ്രവർത്തനങ്ങൾക്കും ആക്‌സന്റുകൾക്കുമായി ഒരു നിറം തിരഞ്ഞെടുക്കുക.", - "theme_setting_primary_color_title": "പ്രധാന നിറം", - "theme_setting_system_primary_color_title": "സിസ്റ്റം നിറം ഉപയോഗിക്കുക", - "theme_setting_system_theme_switch": "യാന്ത്രികം (സിസ്റ്റം ക്രമീകരണം പിന്തുടരുക)", - "theme_setting_theme_subtitle": "ആപ്പിന്റെ തീം ക്രമീകരണം തിരഞ്ഞെടുക്കുക", - "theme_setting_three_stage_loading_subtitle": "മൂന്ന്-ഘട്ട ലോഡിംഗ് പ്രകടനം വർദ്ധിപ്പിച്ചേക്കാം, പക്ഷേ നെറ്റ്‌വർക്ക് ലോഡ് ഗണ്യമായി വർദ്ധിപ്പിക്കുന്നു", - "theme_setting_three_stage_loading_title": "മൂന്ന്-ഘട്ട ലോഡിംഗ് പ്രവർത്തനക്ഷമമാക്കുക", - "they_will_be_merged_together": "അവയെ ഒരുമിച്ച് ലയിപ്പിക്കും", - "third_party_resources": "മൂന്നാം കക്ഷി ഉറവിടങ്ങൾ", - "time": "സമയം", - "time_based_memories": "സമയം അടിസ്ഥാനമാക്കിയുള്ള ഓർമ്മകൾ", - "time_based_memories_duration": "ഓരോ ചിത്രവും പ്രദർശിപ്പിക്കേണ്ട സെക്കൻഡുകളുടെ എണ്ണം.", - "timeline": "ടൈംലൈൻ", - "timezone": "സമയമേഖല", - "to_archive": "ആർക്കൈവ് ചെയ്യുക", - "to_change_password": "പാസ്‌വേഡ് മാറ്റുക", - "to_favorite": "പ്രിയപ്പെട്ടതാക്കുക", - "to_login": "ലോഗിൻ", - "to_multi_select": "ഒന്നിലധികം തിരഞ്ഞെടുക്കാൻ", - "to_parent": "പാരന്റിലേക്ക് പോകുക", - "to_select": "തിരഞ്ഞെടുക്കാൻ", - "to_trash": "ട്രാഷ് ചെയ്യുക", - "toggle_settings": "ക്രമീകരണങ്ങൾ ടോഗിൾ ചെയ്യുക", - "total": "ആകെ", - "total_usage": "ആകെ ഉപയോഗം", - "trash": "ട്രാഷ്", - "trash_action_prompt": "{count} എണ്ണം ട്രാഷിലേക്ക് മാറ്റി", - "trash_all": "എല്ലാം ട്രാഷ് ചെയ്യുക", - "trash_count": "ട്രാഷ് ({count, number})", - "trash_delete_asset": "അസറ്റ് ട്രാഷ് ചെയ്യുക/ഇല്ലാതാക്കുക", - "trash_emptied": "ട്രാഷ് ശൂന്യമാക്കി", - "trash_no_results_message": "ട്രാഷ് ചെയ്ത ഫോട്ടോകളും വീഡിയോകളും ഇവിടെ കാണിക്കും.", - "trash_page_delete_all": "എല്ലാം ഇല്ലാതാക്കുക", - "trash_page_empty_trash_dialog_content": "നിങ്ങളുടെ ട്രാഷ് ചെയ്ത അസറ്റുകൾ ശൂന്യമാക്കണോ? ഈ ഇനങ്ങൾ Immich-ൽ നിന്ന് ശാശ്വതമായി നീക്കം ചെയ്യപ്പെടും", - "trash_page_info": "ട്രാഷ് ചെയ്ത ഇനങ്ങൾ {days} ദിവസങ്ങൾക്ക് ശേഷം ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും", - "trash_page_no_assets": "ട്രാഷ് ചെയ്ത അസറ്റുകളൊന്നുമില്ല", - "trash_page_restore_all": "എല്ലാം പുനഃസ്ഥാപിക്കുക", - "trash_page_select_assets_btn": "അസറ്റുകൾ തിരഞ്ഞെടുക്കുക", - "trash_page_title": "ട്രാഷ് ({count})", - "trashed_items_will_be_permanently_deleted_after": "ട്രാഷ് ചെയ്ത ഇനങ്ങൾ {days, plural, one {# ദിവസത്തിന്} other {# ദിവസങ്ങൾക്ക്}} ശേഷം ശാശ്വതമായി ഇല്ലാതാക്കപ്പെടും.", - "troubleshoot": "ട്രബിൾഷൂട്ട്", - "type": "തരം", - "unable_to_change_pin_code": "പിൻ കോഡ് മാറ്റാൻ കഴിയില്ല", - "unable_to_check_version": "ആപ്പ് അല്ലെങ്കിൽ സെർവർ പതിപ്പ് പരിശോധിക്കാൻ കഴിയില്ല", - "unable_to_setup_pin_code": "പിൻ കോഡ് സജ്ജീകരിക്കാൻ കഴിയില്ല", - "unarchive": "അൺആർക്കൈവ് ചെയ്യുക", - "unarchive_action_prompt": "{count} എണ്ണം ആർക്കൈവിൽ നിന്ന് നീക്കം ചെയ്തു", - "unarchived_count": "{count, plural, other {അൺആർക്കൈവ് ചെയ്തവ #}}", - "undo": "തിരികെചെയ്യുക", - "unfavorite": "പ്രിയപ്പെട്ടതല്ലാതാക്കുക", - "unfavorite_action_prompt": "{count} എണ്ണം പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു", - "unhide_person": "വ്യക്തിയെ മറവിൽനിന്ന് മാറ്റുക", - "unknown": "അജ്ഞാതം", - "unknown_country": "അജ്ഞാത രാജ്യം", - "unknown_year": "അജ്ഞാത വർഷം", - "unlimited": "പരിധിയില്ലാത്തത്", - "unlink_motion_video": "ചലിക്കുന്ന വീഡിയോ അൺലിങ്ക് ചെയ്യുക", - "unlink_oauth": "OAuth അൺലിങ്ക് ചെയ്യുക", - "unlinked_oauth_account": "അൺലിങ്ക് ചെയ്ത OAuth അക്കൗണ്ട്", - "unmute_memories": "ഓർമ്മകൾ ശബ്ദമുള്ളതാക്കുക", - "unnamed_album": "പേരില്ലാത്ത ആൽബം", - "unnamed_album_delete_confirmation": "ഈ ആൽബം ഇല്ലാതാക്കണമെന്ന് നിങ്ങൾക്ക് ഉറപ്പാണോ?", - "unnamed_share": "പേരില്ലാത്ത പങ്കിടൽ", - "unsaved_change": "സേവ് ചെയ്യാത്ത മാറ്റം", - "unselect_all": "എല്ലാം തിരഞ്ഞെടുത്തത് മാറ്റുക", - "unselect_all_duplicates": "എല്ലാ ഡ്യൂപ്ലിക്കേറ്റുകളും തിരഞ്ഞെടുത്തത് മാറ്റുക", - "unselect_all_in": "{group}-ലെ എല്ലാം തിരഞ്ഞെടുത്തത് മാറ്റുക", - "unstack": "അൺ-സ്റ്റാക്ക് ചെയ്യുക", - "unstack_action_prompt": "{count} എണ്ണം അൺ-സ്റ്റാക്ക് ചെയ്തു", - "unstacked_assets_count": "{count, plural, one {# അസറ്റ്} other {# അസറ്റുകൾ}} അൺ-സ്റ്റാക്ക് ചെയ്തു", - "untagged": "ടാഗ് ചെയ്തിട്ടില്ല", - "up_next": "അടുത്തത്", - "update_location_action_prompt": "തിരഞ്ഞെടുത്ത {count} അസറ്റുകളുടെ സ്ഥാനം ഇതുപയോഗിച്ച് അപ്ഡേറ്റ് ചെയ്യുക:", - "updated_at": "അപ്ഡേറ്റ് ചെയ്തത്", - "updated_password": "പാസ്‌വേഡ് അപ്ഡേറ്റ് ചെയ്തു", - "upload": "അപ്‌ലോഡ്", - "upload_concurrency": "അപ്‌ലോഡ് കോൺകറൻസി", - "upload_details": "അപ്‌ലോഡ് വിശദാംശങ്ങൾ", - "upload_dialog_info": "തിരഞ്ഞെടുത്ത അസറ്റ്(കൾ) സെർവറിലേക്ക് ബാക്കപ്പ് ചെയ്യണോ?", - "upload_dialog_title": "അസറ്റ് അപ്‌ലോഡ് ചെയ്യുക", - "upload_errors": "അപ്‌ലോഡ് {count, plural, one {# പിശകോടെ} other {# പിശകുകളോടെ}} പൂർത്തിയായി, പുതിയ അപ്‌ലോഡ് അസറ്റുകൾ കാണുന്നതിന് പേജ് പുതുക്കുക.", - "upload_finished": "അപ്‌ലോഡ് കഴിഞ്ഞു", - "upload_progress": "ശേഷിക്കുന്നത് {remaining, number} - പ്രോസസ്സ് ചെയ്തത് {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റ്} other {# ഡ്യൂപ്ലിക്കേറ്റ് അസറ്റുകൾ}} ഒഴിവാക്കി", - "upload_status_duplicates": "ഡ്യൂപ്ലിക്കേറ്റുകൾ", - "upload_status_errors": "പിശകുകൾ", - "upload_status_uploaded": "അപ്‌ലോഡ് ചെയ്തു", - "upload_success": "അപ്‌ലോഡ് വിജയിച്ചു, പുതിയ അപ്‌ലോഡ് അസറ്റുകൾ കാണുന്നതിന് പേജ് പുതുക്കുക.", - "upload_to_immich": "Immich-ലേക്ക് അപ്‌ലോഡ് ചെയ്യുക ({count})", - "uploading": "അപ്‌ലോഡ് ചെയ്യുന്നു", - "uploading_media": "മീഡിയ അപ്‌ലോഡ് ചെയ്യുന്നു", - "url": "യുആർഎൽ", - "usage": "ഉപയോഗം", - "use_biometric": "ബയോമെട്രിക് ഉപയോഗിക്കുക", - "use_current_connection": "നിലവിലെ കണക്ഷൻ ഉപയോഗിക്കുക", - "use_custom_date_range": "പകരം കസ്റ്റം തീയതി പരിധി ഉപയോഗിക്കുക", - "user": "ഉപയോക്താവ്", - "user_has_been_deleted": "ഈ ഉപയോക്താവിനെ ഇല്ലാതാക്കി.", - "user_id": "ഉപയോക്തൃ ഐഡി", - "user_liked": "{user} {type, select, photo {ഈ ഫോട്ടോ} video {ഈ വീഡിയോ} asset {ഈ അസറ്റ്} other {ഇത്}} ഇഷ്ടപ്പെട്ടു", - "user_pin_code_settings": "പിൻ കോഡ്", - "user_pin_code_settings_description": "നിങ്ങളുടെ പിൻ കോഡ് കൈകാര്യം ചെയ്യുക", - "user_privacy": "ഉപയോക്തൃ സ്വകാര്യത", - "user_purchase_settings": "വാങ്ങൽ", - "user_purchase_settings_description": "നിങ്ങളുടെ വാങ്ങൽ കൈകാര്യം ചെയ്യുക", - "user_role_set": "{user}-നെ {role} ആയി സജ്ജമാക്കി", - "user_usage_detail": "ഉപയോക്തൃ ഉപയോഗ വിശദാംശം", - "user_usage_stats": "അക്കൗണ്ട് ഉപയോഗ സ്ഥിതിവിവരക്കണക്കുകൾ", - "user_usage_stats_description": "അക്കൗണ്ട് ഉപയോഗ സ്ഥിതിവിവരക്കണക്കുകൾ കാണുക", - "username": "ഉപയോക്തൃനാമം", - "users": "ഉപയോക്താക്കൾ", - "users_added_to_album_count": "{count, plural, one {# ഉപയോക്താവിനെ} other {# ഉപയോക്താക്കളെ}} ആൽബത്തിലേക്ക് ചേർത്തു", - "utilities": "യൂട്ടിലിറ്റികൾ", - "validate": "സാധൂകരിക്കുക", - "validate_endpoint_error": "ദയവായി സാധുവായ ഒരു URL നൽകുക", - "variables": "വേരിയബിളുകൾ", - "version": "പതിപ്പ്", - "version_announcement_closing": "നിങ്ങളുടെ സുഹൃത്ത്, അലക്സ്", - "version_announcement_message": "നമസ്കാരം! Immich-ന്റെ ഒരു പുതിയ പതിപ്പ് ലഭ്യമാണ്. നിങ്ങളുടെ സജ്ജീകരണം ഏറ്റവും പുതിയതാണെന്ന് ഉറപ്പാക്കാൻ ദയവായി റിലീസ് നോട്ടുകൾ വായിക്കാൻ കുറച്ച് സമയമെടുക്കുക. ഇത് തെറ്റായ കോൺഫിഗറേഷനുകൾ ഒഴിവാക്കാൻ സഹായിക്കും, പ്രത്യേകിച്ചും നിങ്ങൾ വാച്ച്ടവർ അല്ലെങ്കിൽ നിങ്ങളുടെ Immich ഇൻസ്റ്റൻസ് യാന്ത്രികമായി അപ്ഡേറ്റ് ചെയ്യുന്ന ഏതെങ്കിലും സംവിധാനം ഉപയോഗിക്കുന്നുണ്ടെങ്കിൽ.", - "version_history": "പതിപ്പ് ചരിത്രം", - "version_history_item": "{date}-ന് {version} ഇൻസ്റ്റാൾ ചെയ്തു", - "video": "വീഡിയോ", - "video_hover_setting": "ഹോവർ ചെയ്യുമ്പോൾ വീഡിയോ തംബ്നെയിൽ പ്ലേ ചെയ്യുക", - "video_hover_setting_description": "മൗസ് ഒരു ഇനത്തിന് മുകളിലൂടെ ഹോവർ ചെയ്യുമ്പോൾ വീഡിയോ തംബ്നെയിൽ പ്ലേ ചെയ്യുക. പ്രവർത്തനരഹിതമാക്കിയാലും, പ്ലേ ഐക്കണിന് മുകളിലൂടെ ഹോവർ ചെയ്ത് പ്ലേബാക്ക് ആരംഭിക്കാൻ കഴിയും.", - "videos": "വീഡിയോകൾ", - "videos_count": "{count, plural, one {# വീഡിയോ} other {# വീഡിയോകൾ}}", - "view": "കാണുക", - "view_album": "ആൽബം കാണുക", - "view_all": "എല്ലാം കാണുക", - "view_all_users": "എല്ലാ ഉപയോക്താക്കളെയും കാണുക", - "view_details": "വിശദാംശങ്ങൾ കാണുക", - "view_in_timeline": "ടൈംലൈനിൽ കാണുക", - "view_link": "ലിങ്ക് കാണുക", - "view_links": "ലിങ്കുകൾ കാണുക", - "view_name": "കാണുക", - "view_next_asset": "അടുത്ത അസറ്റ് കാണുക", - "view_previous_asset": "മുമ്പത്തെ അസറ്റ് കാണുക", - "view_qr_code": "QR കോഡ് കാണുക", - "view_similar_photos": "സമാനമായ ഫോട്ടോകൾ കാണുക", - "view_stack": "സ്റ്റാക്ക് കാണുക", - "view_user": "ഉപയോക്താവിനെ കാണുക", - "viewer_remove_from_stack": "സ്റ്റാക്കിൽ നിന്ന് നീക്കം ചെയ്യുക", - "viewer_stack_use_as_main_asset": "പ്രധാന അസറ്റായി ഉപയോഗിക്കുക", - "viewer_unstack": "അൺ-സ്റ്റാക്ക് ചെയ്യുക", - "visibility_changed": "{count, plural, one {# വ്യക്തിയുടെ} other {# ആളുകളുടെ}} ദൃശ്യത മാറ്റി", - "waiting": "കാത്തിരിക്കുന്നു", - "warning": "മുന്നറിയിപ്പ്", - "week": "ആഴ്ച", - "welcome": "സ്വാഗതം", - "welcome_to_immich": "Immich-ലേക്ക് സ്വാഗതം", - "wifi_name": "വൈ-ഫൈയുടെ പേര്", - "wrong_pin_code": "തെറ്റായ പിൻ കോഡ്", - "year": "വർഷം", - "years_ago": "{years, plural, one {# വർഷം} other {# വർഷങ്ങൾ}} മുമ്പ്", - "yes": "അതെ", - "you_dont_have_any_shared_links": "നിങ്ങൾക്ക് പങ്കിട്ട ലിങ്കുകളൊന്നുമില്ല", - "your_wifi_name": "നിങ്ങളുടെ വൈ-ഫൈയുടെ പേര്", - "zoom_image": "ചിത്രം വലുതാക്കുക", - "zoom_to_bounds": "പരിധികളിലേക്ക് സൂം ചെയ്യുക" -} +{} diff --git a/i18n/mn.json b/i18n/mn.json index fb87e18f48..0967ef424b 100644 --- a/i18n/mn.json +++ b/i18n/mn.json @@ -1,159 +1 @@ -{ - "about": "Тухай", - "account": "Бүртгэл", - "account_settings": "Бүртгэлийн тохиргоо", - "acknowledge": "Ойлголоо", - "action": "Үйлдэл", - "action_common_update": "Шинэчлэх", - "actions": "Үйлдлүүд", - "active": "Идэвхтэй", - "activity": "Үйлдлийн бүртгэл", - "activity_changed": "Үйлдлийн бүртгэл {enabled, select, true {идэвхтэй} other {идэвхгүй}}", - "add": "Нэмэх", - "add_a_description": "Тайлбар оруулах", - "add_a_location": "Байршил нэмэх", - "add_a_name": "Нэр өгөх", - "add_a_title": "Гарчиг оруулах", - "add_endpoint": "Endpoint нэмэх", - "add_location": "Байршил оруулах", - "add_more_users": "Өөр хэрэглэгчид нэмэх", - "add_partner": "Хамтрагч нэмэх", - "add_path": "Зам нэмэх", - "add_photos": "Зураг нэмэх", - "add_tag": "Шошго нэмэх", - "add_to_album": "Цомогт оруулах", - "add_to_album_bottom_sheet_added": "{album}-д нэмлээ", - "add_to_album_bottom_sheet_already_exists": "{album}-д аль хэдийн орсон байна", - "add_to_shared_album": "Нээлттэй албумд оруулах", - "add_url": "URL нэмэх", - "added_to_archive": "Архивд оруулах", - "added_to_favorites": "Дуртай зурганд нэмэх", - "added_to_favorites_count": "Дуртай зурагнуудад {count, number} нэмэгдлээ", - "admin": { - "admin_user": "Админ хэрэглэгч", - "authentication_settings": "Танин нэвтрэлт тохиргоо", - "authentication_settings_description": "Нууц үгийн удирдлага, OAuth болон бусад танин нэвтрэлтийн тохиргоо", - "authentication_settings_disable_all": "Бүх нэвтрэх аргуудыг идэвхигүй болгохдоо итгэлтэй байна уу? Нэвтрэх үйлдэл бүрэн идэвхигүй болно.", - "background_task_job": "Арын ажил", - "backup_database": "Өгөгдлийн сангийн дамп үүсгэх", - "backup_database_enable_description": "Өгөгдлийн сангийн дамп идэвхижүүлэх", - "backup_keep_last_amount": "Өмнөх хэдэн дампыг хадгалах вэ", - "backup_settings": "Датабаз дамп тохиргоо", - "backup_settings_description": "Датабазаас дамп хийх тохиргоонууд.", - "config_set_by_file": "Тохиргоог одоогоор файлаас авч байна", - "confirm_delete_library": "Та {library} гэсэн санг устгахдаа итгэлтэй байна уу?", - "confirm_delete_library_assets": "Та энэ санг устгахдаа итгэлтэй байна уу? Энэ үйлдлээр таны {count, plural, one {# contained asset} other {all # contained assets}} серверээс устах бөгөөд буцаах боломжгүй. Гэхдээ файлууд диск дээрээ үлдэнэ.", - "confirm_email_below": "Баталгаажуулахын тулд та \"{email}\" гэж бичнэ үү", - "confirm_reprocess_all_faces": "Бүх царайг дахин процесс хийх үү? Тэгвэл бүх нэрс арилах болно.", - "confirm_user_password_reset": "{user}-ийн нууц үгийг дахин тохируулах уу?", - "confirm_user_pin_code_reset": "{user} хэрэглэгчийн PIN code дахин тохируулах уу?", - "face_detection": "Нүүр илрүүлэх", - "image_quality": "Чанар", - "job_settings": "Ажлын тохиргоо", - "machine_learning_enabled": "Машин сургалт идэвхжүүлэх", - "machine_learning_enabled_description": "Идэвхгүй болгосон үед доорх тохиргооноос хамаарахгүйгээр бүх машин сургалтын боломж идэвхгүй болно.", - "machine_learning_facial_recognition": "Нүүр танилт", - "machine_learning_facial_recognition_description": "Зураг дээрх хүмүүсийн нүүрийг илрүүлж, таньж, бүлэглэнэ", - "machine_learning_facial_recognition_model": "Нүүр танилтын загвар", - "machine_learning_facial_recognition_model_description": "Загварууд хэмжээ нь буурах эрэмбээр жагссан. Том загварууд удаан, илүү их санах ой хэрэглэх боловч харьцангуй чанартай үр дүн үзүүлнэ. Загвар өөрчилсөн тохиолдолд нүүр илрүүлэлтийн ажлыг дахин эхлүүлэх шаардлагатайг санаарай.", - "machine_learning_facial_recognition_setting": "Нүүр танилт идэвхжүүлэх", - "map_settings": "Газрын зураг", - "trash_enabled_description": "Хогийн сав идэвхжүүлэх", - "trash_number_of_days": "Хоногийн тоо", - "trash_number_of_days_description": "Хогийн саванд хэд хоног хадгалаад бүр мөсөн устгах вэ", - "trash_settings": "Хогийн савны тохиргоо", - "trash_settings_description": "Хогийн савны тохиргоог өөрчлөх", - "user_management": "Хэрэглэгчийн удирдлага", - "user_password_has_been_reset": "Хэрэглэгчийн нууц үг шинээр тохируулагдлаа:", - "user_restore_description": "{user}-н бүртгэл сэргэнэ.", - "user_settings": "Хэрэглэгчийн тохиргоо" - }, - "administration": "Админ", - "album_added": "Цомог нэмэгдлээ", - "album_info_updated": "Цомгийн мэлээлэл шинэчлэгдлээ", - "album_leave": "Цомгоос гарах уу?", - "album_leave_confirmation": "Та {album} цомгоос гарахдаа итгэлтэй байна уу?", - "album_name": "Цомгийн нэр", - "album_options": "Цомгийн тохиргоо", - "album_remove_user": "Хэрэглэгч хасах уу?", - "album_remove_user_confirmation": "{user} хэрэглэгчийг хасахдаа итгэлтэй байна уу?", - "albums": "Цомгууд", - "all": "Бүгд", - "all_albums": "Бүх цомог", - "all_people": "Бүх хүн", - "all_videos": "Бүх бичлэг", - "allow_dark_mode": "Харанхуй горим зөвшөөрөх", - "allow_edits": "Засварлалт зөвшөөрөх", - "api_key": "API түлхүүр", - "api_key_description": "Энэ утга зөвхөн ганц л удаа харагдана. Цонхоо хаахаас өмнө хуулж аваарай.", - "api_key_empty": "Таны API түлхүүрийн нэр хоосон байж болохгүй", - "api_keys": "API түлхүүрүүд", - "app_settings": "Апп-н тохиргоо", - "archive": "Архив", - "archive_or_unarchive_photo": "Зургийг архивт хийх эсвэл гаргах", - "archive_size": "Архивын хэмжээ", - "archive_size_description": "Татах үеийн архивын хэмжээг тохируулах (GiB-р)", - "asset_added_to_album": "Цомогт нэмсэн", - "asset_adding_to_album": "Цомогт нэмж байна...", - "backup_controller_page_background_app_refresh_disabled_content": "Апп нээгээгүй байх үед нөөцлөлт хийх бол Settings > General > Background App Refresh хандаж идэвхижүүлнэ үү.", - "backup_controller_page_background_app_refresh_disabled_title": "Апп нээгээгүй байх үед нөөцлөлт идэвхигүй.", - "backup_controller_page_background_app_refresh_enable_button_text": "Тохиргоо хэсэгт очих", - "buy": "Immich худалдаж авах", - "camera": "Камер", - "camera_brand": "Камерын үйлдвэр", - "camera_model": "Камерын загвар", - "cancel": "Цуцлах", - "cancel_search": "Хайлт цуцлах", - "change_date": "Огноо өөрчлөх", - "change_location": "Байршил өөрчлөх", - "change_name": "Нэр өөрчлөх", - "change_name_successfully": "Нэр амжилттай өөрчлөгдлөө", - "change_password": "Нууц үг өөрчлөх", - "city": "Хот", - "clear": "Цэвэрлэх", - "clear_all": "Бүгдийг цэвэрлэх", - "empty_trash": "Хогийн сав хоослох", - "errors": { - "unable_to_empty_trash": "Хогийн савыг хоослож чадсангүй", - "unable_to_restore_trash": "Хогийн савнаас гаргаж чадсангүй" - }, - "explore": "Эрж олох", - "favorites": "Дуртай", - "invite_people": "Хүмүүс урих", - "library": "Зургийн сан", - "no_assets_message": "Энд дарж та эхний зургаа хуулж үзэх үү", - "no_explore_results_message": "Зураг хуулж оруулсаны дараа ашиглах боломжтой болно.", - "notification_permission_list_tile_content": "Мэдэгдэл нээх эрх өгнө үү.\n", - "notification_permission_list_tile_enable_button": "Мэдэгдэл нээх", - "notification_permission_list_tile_title": "Мэдэгдлийн эрх", - "only_favorites": "Зөвхөн дуртай зурагнууд", - "people": "Хүмүүс", - "places": "Байршилууд", - "remove_from_favorites": "Дуртай зурагнуудаас хасах", - "removed_from_favorites": "Дуртай зурагнуудаас хасагдсан", - "removed_from_favorites_count": "Дуртай зурагнуудаас {count, plural, other {Removed #}} хасагдлаа", - "search_places": "Байршил хайх", - "search_your_photos": "Зурагнуудаасаа хайлт хийх", - "server_online": "Сервер Онлайн", - "settings": "Тохиргоо", - "sharing": "Хуваалцах", - "sign_out": "Гарах", - "storage": "Дискний багтаамж", - "storage_usage": "Нийт {available} боломжтойгоос {used} хэрэглэсэн", - "trash": "Хогийн сав", - "upload": "Зураг хуулах", - "utilities": "Багаж хэрэгсэл", - "view_all": "Бүгдийг харах", - "view_all_users": "Бүх хэрэглэгчийг харах", - "waiting": "Хүлээж байна", - "warning": "Анхааруулга", - "week": "Долоо хоног", - "welcome": "Тавтай морил", - "welcome_to_immich": "Тавтай морилно уу", - "wifi_name": "WiFi Name", - "year": "Он", - "years_ago": "{years, plural, one {# year} other {# years}} өмнө", - "yes": "Тийм", - "you_dont_have_any_shared_links": "Танд хуваалцсан холбоос алга", - "zoom_image": "Зургийг томруулж харах" -} +{} diff --git a/i18n/mr.json b/i18n/mr.json index be0c96f9e9..0967ef424b 100644 --- a/i18n/mr.json +++ b/i18n/mr.json @@ -1,2207 +1 @@ -{ - "about": "विषयी", - "account": "खाते", - "account_settings": "खाते व्यवस्था", - "acknowledge": "मान्यता", - "action": "कृती", - "action_common_update": "अद्ययावत", - "actions": "कृत्ये", - "active": "सक्रिय", - "active_count": "कृती: {count}", - "activity": "गतिविधि", - "activity_changed": "गतिविधि {enabled, select, true {enabled} other {disabled}}", - "add": "जोडा", - "add_a_description": "वर्णन करा", - "add_a_location": "एक स्थळ टाका", - "add_a_name": "नाव टाका", - "add_a_title": "शीर्षक टाका", - "add_action": "कृती जोडा", - "add_birthday": "जन्मदिवस नोंदवा", - "add_endpoint": "एंडपॉइंट जोडा", - "add_exclusion_pattern": "अपवाद नमुना जोडा", - "add_location": "स्थळ टाका", - "add_more_users": "अधिक वापरकर्ते जोडा", - "add_partner": "भागीदार जोडा", - "add_path": "मार्ग टाका", - "add_photos": "छायाचित्रे जोडा", - "add_tag": "टॅग जोडा", - "add_to": "त्या मध्ये जोडा…", - "add_to_album": "संग्रहात टाका", - "add_to_album_bottom_sheet_added": "{album} मध्ये जोडले गेले", - "add_to_album_bottom_sheet_already_exists": "आधीच {album} मध्ये आहे", - "add_to_album_bottom_sheet_some_local_assets": "काही स्थानिक माध्यमे अल्बममध्ये जोडणे शक्य झाले नाही", - "add_to_album_toggle": "{album} साठी निवड बदला", - "add_to_albums": "अल्बममध्ये जोडा", - "add_to_albums_count": "अल्बमांमध्ये जोडा ({count})", - "add_to_bottom_bar": "मध्ये जोडा", - "add_to_shared_album": "सामायिक संग्रहात टाका", - "add_upload_to_stack": "स्टॅकमध्ये अपलोड जोडा", - "add_url": "URL प्रविष्ट करा", - "added_to_archive": "संग्रहित केले", - "added_to_favorites": "आवडत्या संग्रहात जोडले", - "added_to_favorites_count": "आवडत्यात {count, number} टाकले", - "admin": { - "add_exclusion_pattern_description": "अपवाद अनुकूलन जोडा. ** आणि ? या उपयोगात ग्लोबिंग समर्थित आहे. कोणत्याही \"Raw\" नावाच्या निर्देशिकेमधील सर्व खतावण्या दुर्लक्षीत करण्यासाठी \"/Raw/\" वापरा. \".tif\" या सामान्य पथावर समाप्त असलेल्या सर्व खतावण्या दुर्लक्षीत करण्यासाठी \"**/.tif\" वापरा. विशिष्ट पथ दुर्लक्ष करण्यासाठी \"/path/to/ignore/**\" वापरा.", - "admin_user": "प्रशासन वापरकर्ता", - "asset_offline_description": "ही बाह्य संग्रहालय संसाधने डिस्कवर नाहीत आणि ट्रॅशमध्ये विस्थापित केली गेली आहेत. जर फाइल संग्रहालयामध्ये विस्थापित केली गेली आहे, तर नवीन संगत संसाधन किंव्हा रोजीनिशी मध्ये तपासा. हा संसाधन वापर करण्यासाठी कृपया निम्नलिखित खतावणी पथाला इम्मीच द्वारा वापरू शकतो याची तपासणी करा आणि तो संग्रहालय चाळा.", - "authentication_settings": "प्रमाणीकरण साधक", - "authentication_settings_description": "परवलीचा शब्द, OAuth आणि अन्य प्रमाणीकरण प्रबंधन करा", - "authentication_settings_disable_all": "तुम्हाला खात्री आहे की तुम्ही सर्व प्रवेश पद्धती बंद करू इच्छिता? प्रवेश पूर्णपणे बंद होइल!.", - "authentication_settings_reenable": "परत चालू करण्यासाठी Server Command वापरा.", - "background_task_job": "पृष्ठभूमि कार्य", - "backup_database": "डेटाबेस डंप तयार करा", - "backup_database_enable_description": "डेटाबेस डंप सक्षम करा", - "backup_keep_last_amount": "पूर्वीच्या किती डंप्स ठेवायचे", - "backup_onboarding_1_description": "क्लाऊडमध्ये किंवा इतर कोणत्याही भौतिक ठिकाणी ठेवलेली ऑफसाइट प्रत.", - "backup_onboarding_2_description": "विविध उपकरणांवर स्थानिक प्रती ठेवली जातात. यामध्ये मुख्य फाइल्स आणि त्यांच्या स्थानिक बॅकअपचा समावेश आहे.", - "backup_onboarding_3_description": "मुळ फाइल्ससहित तुमच्या डेटाच्या एकूण प्रत्या. यामध्ये 1 ऑफसाइट प्रत आणि 2 स्थानिक प्रतांचा समावेश आहे.", - "backup_onboarding_description": "डेटा संरक्षणासाठी 3-2-1 बॅकअप धोरण शिफारस केले जाते. सर्वसमावेशक बॅकअप उपायासाठी, आपल्या अपलोड केलेल्या फोटो/व्हिडिओंच्या प्रतिला तसेच Immich डेटाबेसची प्रत जतन करा.", - "backup_onboarding_footer": "Immich चा बॅकअप कसा घ्यावा याबद्दल अधिक माहितीसाठी, कृपया दस्तऐवजीकरण पाहा.", - "backup_onboarding_parts_title": "3-2-1 बॅकअपमध्ये समाविष्ट आहे:", - "backup_onboarding_title": "बॅकअप", - "backup_settings": "डेटाबेस डंप सेटिंग्ज", - "backup_settings_description": "डेटाबेस डंप सेटिंग्ज व्यवस्थापित करा।", - "cleared_jobs": "{job}: च्या कार्यवाह्या काढल्या", - "config_set_by_file": "संरचना सध्या संरचना खतावणीद्वारे निश्चित केली आहे", - "confirm_delete_library": "तुम्हाला नक्की हे {library} संग्रहालय हटवायचे आहे का?", - "confirm_delete_library_assets": "तुम्हाला नक्की हे संग्रहालय हटवायचे आहे का? इम्मीच मधून {count, plural, one {# contained asset} other {all # contained assets}} काढले जातील, आणि पूर्ववत करता येणार नाहीत. छायाचित्रे डिस्क वर राहतील.", - "confirm_email_below": "पुष्टी करण्या साठी, खाली \"{email}\" टंकलिखित करा", - "confirm_reprocess_all_faces": "तुम्हाला खात्री आहे का की तुम्हाला सर्व चेहऱ्यांवर पुन्हा प्रक्रिया करायची आहे? यामुळे नाव दिलेले लोकही साफ होतील.", - "confirm_user_password_reset": "तुम्हाला नक्की {user} चा परवलीचा शब्द बदलायचा आहे का?", - "confirm_user_pin_code_reset": "तुम्हाला नक्की {user} चा पिन कोड रीसेट करायचा आहे का?", - "create_job": "कार्य बनवा", - "cron_expression": "वेळापत्रक सूत्र", - "cron_expression_description": "चाळन्याचे वेळापत्रक क्रॉन पद्धती ने करा. अधिक माहिती साठी पहा: क्रॉन गुरु", - "cron_expression_presets": "पूर्वनिर्धारित वेळापत्रक सूत्रे", - "disable_login": "प्रवेशाधिकर वर्ज्य करा", - "duplicate_detection_job_description": "सारख्या छायाचित्रांचा शोध घेण्यासाठी यांत्रिकी प्रशिक्षण द्या. ही कार्यक्षमता चतुर शोधप्रणालीवर अवलंबून आहे", - "exclusion_pattern_description": "आपले संग्रहालय चाळताना अपवाद नमुने आपल्याला खतावण्या आणि र्निर्देशिकेला दुर्लक्षीत करू देतात. आपल्याकडे कच्च्या खतावण्या सारख्या आयात करू इच्छित नसलेल्या असंपादित (RAW) खतावण्या असलेल्या निर्देशिका असल्यास हे उपयुक्त आहे.", - "face_detection": "मुख संशोधन", - "face_detection_description": "मशीन लर्निंग वापरून मालमत्तांमधील चेहरे शोधा. व्हिडिओंसाठी, फक्त थंबनेलचा विचार केला जातो. \"रिफ्रेश\" (पुन्हा) सर्व मालमत्तांवर प्रक्रिया करते. \"रीसेट\" याव्यतिरिक्त सर्व वर्तमान चेहरा डेटा साफ करते. \"गहाळ\" मालमत्तांवर अद्याप प्रक्रिया न केलेल्या रांगेत ठेवते. शोधलेले चेहरे फेस डिटेक्शन पूर्ण झाल्यानंतर फेशियल रेकग्निशनसाठी रांगेत ठेवले जातील, त्यांना विद्यमान किंवा नवीन लोकांमध्ये गटबद्ध केले जाईल.", - "facial_recognition_job_description": "शोधलेले चेहरे लोकांमध्ये गटबद्ध करा. हे चरण चेहरा शोधणे पूर्ण झाल्यानंतर चालते. \"रीसेट करा\" (पुन्हा) सर्व चेहरे क्लस्टर कर. \"गहाळ\" चेहरे रांगेत समाविष्ट करते ज्यांना नियुक्त केलेली व्यक्ती नाही.", - "failed_job_command": "{command} कमांड जॉबसाठी अयशस्वी झाला: {job}", - "force_delete_user_warning": "सावधान: हे वापरकर्ता आणि सर्व मालमत्ता ताबडतोब काढून टाकेल. हे पूर्ववत करता येणार नाही आणि फायली पुनर्प्राप्त करता येणार नाहीत.", - "image_format": "फॉरमॅट", - "image_format_description": "WebP JPEG पेक्षा लहान फायली तयार करते, परंतु एन्कोड करण्यास हळू असते.", - "image_fullsize_description": "झूम इन केल्यावर वापरल्या जाणाऱ्या स्ट्रिप केलेल्या मेटाडेटासह पूर्ण आकाराची प्रतिमा", - "image_fullsize_enabled": "पूर्ण-आकारातील प्रतिमा निर्मिती", - "image_fullsize_enabled_description": "वेब-फ्रेंडली नसलेल्या फॉरमॅटसाठी पूर्ण-आकाराची प्रतिमा तयार करा. जेव्हा \"embedded preview\" चालुआसेल तेव्हा, \"embedded preview\" थेट रूपांतरणाशिवाय वापरले जातात. JPEG सारख्या वेब-फ्रेंडली फॉरमॅटवर परिणाम होत नाही.", - "image_fullsize_quality_description": "१-१०० पर्यंत पूर्ण-आकारातील प्रतिमा गुणवत्ता. जास्त तेव्हडे चांगले, परंतु मोठ्या फायली तयार करते.", - "image_fullsize_title": "पूर्ण आकार प्रतिमा सेटिंग्ज", - "image_prefer_embedded_preview": "एंबेडेड पूर्वावलोकन प्राधान्य द्या", - "image_prefer_embedded_preview_setting_description": "उपलब्ध असल्यास RAW फोटोमधील एंबेडेड पूर्वावलोकने इमेज प्रोसेसिंगसाठी वापरा. यामुळे काही प्रतिमांसाठी अधिक अचूक रंग मिळू शकतात, परंतु पूर्वावलोकनाची गुणवत्ता कॅमेरावर अवलंबून असते आणि चित्रात अधिक संकुचन दोष असू शकतात.", - "image_prefer_wide_gamut": "विस्तृत रंगपरिसर प्राधान्य द्या", - "image_prefer_wide_gamut_setting_description": "थंबनेलसाठी Display P3 निवडा. हे विस्तृत रंगपरिसर असलेल्या प्रतिमांची प्रखरता जास्त चांगल्या प्रकारे टिकवते, परंतु जुन्या उपकरणे किंवा जुन्या ब्राउझर असलेल्यांवर प्रतिमा वेगळ्या दिसू शकतात. रंगबदल टाळण्यासाठी sRGB प्रतिमा sRGB मध्येच ठेवली जातात.", - "image_preview_description": "एकच मालमत्ता पाहताना आणि मशीन लर्निंगसाठी वापरली जाणारी, मेटाडेटा काढून दिलेली मध्यम आकाराची प्रतिमा", - "image_preview_quality_description": "पूर्वावलोकन गुणवत्ता 1–100: जितकी उच्च, तितकी चांगली; फाइल आकार वाढतो आणि ॲपची प्रतिसादक्षमता कमी होऊ शकते. कमी मूल्य सेट केल्यास मशीन लर्निंग गुणवत्ता प्रभावित होऊ शकते.", - "image_preview_title": "पूर्वावलोकन विन्यास", - "image_quality": "गुणवत्ता", - "image_resolution": "प्रतिमेची स्पष्टता", - "image_resolution_description": "उच्च रिझोल्यूशन अधिक तपशील जतन करतात, परंतु त्यांचे एन्कोडिंग जास्त वेळ घेतं, फाइल साईज मोठी होते आणि अ‍ॅपची प्रतिसादक्षमता कमी होऊ शकते.", - "image_settings": "प्रतिमा पर्याय", - "image_settings_description": "उत्पन्न झालेल्या प्रतिमांची गुणवत्ता आणि रिझोल्यूशन व्यवस्थापित करा", - "image_thumbnail_description": "फोटो समूह पाहताना मेटाडेटा काढून दाखवलेले लहान थंबनेल", - "image_thumbnail_quality_description": "थंबनेल गुणवत्ता (1–100): जितकी जास्त, तितकी चांगली; परंतु फाइल आकार वाढतो आणि ॲपची प्रतिसादक्षमता कमी होऊ शकते.", - "image_thumbnail_title": "लघुरूप विन्यास", - "job_concurrency": "{job} एकरूपता", - "job_created": "कार्य तयार झाले", - "job_not_concurrency_safe": "हे कार्य समांतरपणे चालवण्यासाठी सुरक्षित नाही.", - "job_settings": "कार्य सेटिंग्ज", - "job_settings_description": "कार्यांची समांतरता व्यवस्थापित करा", - "jobs_delayed": "{jobCount, plural, other {# विलंबित}}", - "jobs_failed": "{jobCount, plural, other {# अयशस्वी}}", - "library_created": "संग्रह तयार केला: {library}", - "library_deleted": "संग्रह हटवला", - "library_details": "लायब्ररी तपशील", - "library_folder_description": "इम्पोर्ट करण्यासाठी फोल्डर निवडा. हा फोल्डर आणि त्यातील सबफोल्डर्स प्रतिमा आणि व्हिडिओंसाठी स्कॅन केले जातील.", - "library_remove_exclusion_pattern_prompt": "तुम्हाला नक्की हा वगळण्याचा नमुना काढून टाकायचा आहे का?", - "library_remove_folder_prompt": "तुम्हाला नक्की हा इम्पोर्ट फोल्डर काढून टाकायचा आहे का?", - "library_scanning": "नियमित स्कॅनिंग", - "library_scanning_description": "नियमित लायब्ररी स्कॅनिंग कॉन्फिगर करा", - "library_scanning_enable_description": "नियमित लायब्ररी स्कॅनिंग चालू करा", - "library_settings": "बाह्य मीडिया संग्रह", - "library_settings_description": "बाह्य लायब्ररी सेटिंग्ज व्यवस्थापित करा", - "library_tasks_description": "बाह्य लायब्ररीतील नवीन किंवा बदललेल्या प्रतिमा आणि व्हिडिओंसाठी स्कॅन करा", - "library_updated": "लायब्ररी अपडेट केली", - "library_watching_enable_description": "फाइल बदलांसाठी बाह्य लायब्ररी तपासण्यासाठी निरीक्षण करा", - "library_watching_settings": "लायब्ररी निरीक्षण [प्रायोगिक]", - "library_watching_settings_description": "बदल झालेल्या फाइल्सवर स्वयंचलितपणे निगराणी करा", - "logging_enable_description": "लॉगिंग सक्षम करा", - "logging_level_description": "सक्षम झाल्यावर वापरण्यासाठी कोणता लॉग स्तर निवडा.", - "logging_settings": "लॉगिंग", - "machine_learning_availability_checks": "उपलब्धता तपासणी", - "machine_learning_availability_checks_description": "उपलब्ध मशीन लर्निंग सर्व्हर आपोआप शोधा आणि त्यांना प्राधान्य द्या", - "machine_learning_availability_checks_enabled": "उपलब्धता तपासणी सक्षम करा", - "machine_learning_availability_checks_interval": "तपासणी अंतर", - "machine_learning_availability_checks_interval_description": "उपलब्धता तपासण्यांमधील अंतर (मिलीसेकंदांत)", - "machine_learning_availability_checks_timeout": "विनंती वेळमर्यादा", - "machine_learning_availability_checks_timeout_description": "उपलब्धता तपासणीसाठी वेळमर्यादा (मिलीसेकंदांत)", - "machine_learning_clip_model": "CLIP मॉडेल", - "machine_learning_clip_model_description": "सूचीबद्ध CLIP मॉडेलचे नाव येथे. मॉडेल बदलल्यावर सर्व प्रतिमांसाठी ‘स्मार्ट शोध’ नोकरी पुन्हा चालवा.", - "machine_learning_duplicate_detection": "प्रतिलिपी शोध", - "machine_learning_duplicate_detection_enabled": "डुप्लिकेट ओळख सक्षम करा", - "machine_learning_duplicate_detection_enabled_description": "अक्षम असतानाही अगदी सारख्या सर्व आस्थापनांची डी-डुप्लिकेट केली जातील.", - "machine_learning_duplicate_detection_setting_description": "संभाव्य प्रतिलिपी शोधण्यासाठी CLIP एम्बेडिंग वापरा", - "machine_learning_enabled": "मशीन लर्निंग सक्षम करा", - "machine_learning_enabled_description": "अक्षम केल्यास, खालील सेटिंग्जकडे दुर्लक्ष करून सर्व एमएल वैशिष्ट्ये निष्क्रिय होतील.", - "machine_learning_facial_recognition": "चेहऱ्याची ओळख", - "machine_learning_facial_recognition_description": "प्रतिमांमधील चेहेरे शोधणे, ओळखणे आणि गटबद्ध करणे", - "machine_learning_facial_recognition_model": "चेहरा ओळख मॉडेल", - "machine_learning_facial_recognition_model_description": "मॉडेल आकाराच्या अवरोही क्रमात सूचीबद्ध आहेत. मोठे मॉडेल्स धीमे असतात आणि जास्त स्मृती वापरतात, परंतु उत्तम निकाल देतात. लक्षात ठेवा की मॉडेल बदलल्यानंतर सर्व प्रतिमांसाठी ‘फेस डिटेक्शन’ कार्य पुन्हा चालवावे लागेल.", - "machine_learning_facial_recognition_setting": "चेहरा ओळख सक्षम करा", - "machine_learning_facial_recognition_setting_description": "अक्षम केल्यास, प्रतिमा चेहरा ओळखण्यासाठी एन्कोड होणार नाहीत आणि एक्सप्लोर पेजमधील ‘लोक’ विभाग भरला जाणार नाही.", - "machine_learning_max_detection_distance": "अधिकतम शोध अंतर", - "machine_learning_max_detection_distance_description": "डुप्लिकेट म्हणून ओळखण्यासाठी दोन प्रतिमांमधील कमाल अंतर 0.001 ते 0.1 पर्यंत असावे. जास्त मूल्ये अधिक डुप्लिकेट शोधतील, परंतु खोटे सकारात्मक परिणाम देखील होऊ शकतात.", - "machine_learning_max_recognition_distance": "जास्तीत जास्त ओळख अंतर", - "machine_learning_max_recognition_distance_description": "समान व्यक्ती समजण्यासाठी दोन चेहऱ्यांमधील कमाल अंतर 0 ते 2 दरम्यान असते. हे कमी केल्यास दोन वेगळ्या लोकांना एकाच व्यक्ती म्हणून चिन्हांकित होण्यापासून प्रतिबंध होतो, तर जास्त केल्यास एकाच व्यक्तीला दोन भिन्न लोकांप्रमाणे लेबल होण्यापासून प्रतिबंध होतो. एका व्यक्तीला दोन भागांमध्ये विभागण्यापेक्षा दोन व्यक्तींना एकत्र करणे सोपे असल्याने, शक्य तेव्हा कमी थ्रेशोल्ड निवडा.", - "machine_learning_min_detection_score": "किमान शोध गुण", - "machine_learning_min_detection_score_description": "चेहरा शोधण्यासाठी किमान आत्मविश्वास गुणांक 0 ते 1 दरम्यान असावा. कमी मूल्ये अधिक चेहरे शोधतील, परंतु खोटे सकारात्मक देखील होऊ शकतात.", - "machine_learning_min_recognized_faces": "किमान ओळखलेले चेहरे", - "machine_learning_min_recognized_faces_description": "व्यक्ती तयार करण्यासाठी ओळखल्या गेलेल्या चेहर्यांची किमान संख्या. हे वाढवल्यास चेहरा एका व्यक्तीला न जोडला जाण्याची शक्यता कमी होते, परंतु चुकीच्या व्यक्ती एकत्र केल्याचे परिणाम वाढू शकतात.", - "machine_learning_ocr": "ओ.सी.आर", - "machine_learning_ocr_description": "प्रतिमांमधील मजकूर ओळखण्यासाठी मशीन लर्निंग वापरा", - "machine_learning_ocr_enabled": "ओ.सी.आर सक्षम करा", - "machine_learning_ocr_enabled_description": "हे बंद असल्यास, प्रतिमांवर मजकूर ओळख प्रक्रिया होणार नाही.", - "machine_learning_ocr_max_resolution": "कमाल रिझोल्यूशन", - "machine_learning_ocr_max_resolution_description": "या रिझोल्यूशनपेक्षा मोठ्या पूर्वदृश्यांचे आकारगुणोत्तर न बदलता आकार बदलले जातील. जास्त मूल्ये अधिक अचूक असतात, पण प्रक्रिया करण्यास जास्त वेळ घेतात आणि अधिक मेमरी वापरतात.", - "machine_learning_ocr_min_detection_score": "किमान डिटेक्शन स्कोअर", - "machine_learning_ocr_min_detection_score_description": "मजकूर ओळखण्यासाठी ०–१ मधील किमान विश्वास स्कोअर. कमी मूल्यांवर अधिक मजकूर सापडेल, पण चुकीचे सकारात्मक निकाल (false positives) येऊ शकतात.", - "machine_learning_ocr_min_recognition_score": "किमान ओळख स्कोअर", - "machine_learning_ocr_min_score_recognition_description": "ओळखलेला मजकूर अंतिम मानण्यासाठी ०–१ मधील किमान विश्वास स्कोअर. कमी मूल्यांवर अधिक मजकूर ओळखला जाईल, पण चुकीचे सकारात्मक निकाल (false positives) येऊ शकतात.", - "machine_learning_ocr_model": "ओसीआर मॉडेल", - "machine_learning_ocr_model_description": "सर्व्हर मॉडेल मोबाईल मॉडेलपेक्षा अधिक अचूक असतात, पण प्रक्रिया करण्यास जास्त वेळ घेतात आणि अधिक मेमरी वापरतात.", - "machine_learning_settings": "मशीन लर्निंग सेटिंग्ज", - "machine_learning_settings_description": "मशीन लर्निंग वैशिष्ट्ये आणि सेटिंग्ज व्यवस्थापित करा", - "machine_learning_smart_search": "स्मार्ट शोध", - "machine_learning_smart_search_description": "CLIP एम्बेडिंग्ज वापरून प्रतिमा अर्थपूर्णरित्या शोधा", - "machine_learning_smart_search_enabled": "स्मार्ट सर्च सक्षम करा", - "machine_learning_smart_search_enabled_description": "अक्षम केल्यास, प्रतिमा स्मार्ट सर्चसाठी एन्कोड केल्या जाणार नाहीत.", - "machine_learning_url_description": "मशीन लर्निंग सर्व्हरची URL. एकाहून अधिक URL दिल्यास, प्रथम ते दिलेल्या क्रमाने प्रत्येक सर्व्हरवर एक-एक करून प्रयत्न केले जातील, जोपर्यंत कोणीतरी यशस्वी प्रतिसाद देत नाही तोपर्यंत. जे सर्व्हर प्रतिसाद देत नाहीत, त्यांना ते परत ऑनलाइन येईपर्यंत तात्पुरते दुर्लक्षित केले जाईल.", - "maintenance_settings": "देखभाल", - "maintenance_settings_description": "Immich ला देखभाल मोडमध्ये ठेवा.", - "maintenance_start": "देखभाल मोड सुरू करा", - "maintenance_start_error": "देखभाल मोड सुरू करण्यात अयशस्वी.", - "manage_concurrency": "समांतरता व्यवस्थापित करा", - "manage_log_settings": "लॉग सेटिंग्ज नियंत्रण करा", - "map_dark_style": "गडद शैली", - "map_enable_description": "नकाशी सुविधा सक्षम करा", - "map_gps_settings": "नकाशा आणि जीपीएस सेटिंग्ज", - "map_gps_settings_description": "मानचित्र व GPS (रिव्हर्स ज्योकोडिंग) सेटिंग्ज व्यवस्थापित करा", - "map_implications": "मानचित्र वैशिष्ट्य बाह्य टाइल सेवेशी (tiles.immich.cloud) अवलंबून आहे", - "map_light_style": "उजळ शैली", - "map_manage_reverse_geocoding_settings": "रिव्हर्स जिओकोडिंग सेटिंग्ज व्यवस्थापित करा", - "map_reverse_geocoding": "रिव्हर्स जिओकोडिंग", - "map_reverse_geocoding_enable_description": "रिव्हर्स जिओकोडिंग सक्षम करा", - "map_reverse_geocoding_settings": "रिव्हर्स जिओकोडिंग सेटिंग्ज", - "map_settings": "नकाशा", - "map_settings_description": "नकाशा सेटिंग्ज व्यवस्थापित करा", - "map_style_description": "style.json नकाशा थीमसाठी URL", - "memory_cleanup_job": "स्मृती स्वच्छता", - "memory_generate_job": "स्मृती निर्मिती", - "metadata_extraction_job": "मेटाडेटा काढा", - "metadata_extraction_job_description": "प्रत्येक संपत्तीमधून GPS, चेहऱ्यांची व रिझोल्यूशन यांसारखी मेटाडेटा माहिती मिळवा", - "metadata_faces_import_setting": "चेहरा आयात सक्षम करा", - "metadata_faces_import_setting_description": "प्रतिमा EXIF डेटाद्वारे आणि साइडकार फाइलमधून चेहरे आयात करा", - "metadata_settings": "मेटाडेटा सेटिंग्ज", - "metadata_settings_description": "मेटाडेटा सेटिंग्ज व्यवस्थापित करा", - "migration_job": "स्थानांतरण", - "migration_job_description": "संपत्ती आणि चेहर्‍यांचे थंबनेल नवीनतम फोल्डर संरचनेत स्थलांतरित करा", - "nightly_tasks_cluster_faces_setting_description": "नवीन ओळखलेल्या चेहर्‍यांवर चेहरे ओळखण्याची प्रक्रिया चालवा", - "nightly_tasks_cluster_new_faces_setting": "नवीन चेहऱ्यांना गटबद्ध करा", - "nightly_tasks_database_cleanup_setting": "डेटाबेस स्वच्छता कार्ये", - "nightly_tasks_database_cleanup_setting_description": "डेटाबेसमधून जुनी व कालबाह्य माहिती हटवा", - "nightly_tasks_generate_memories_setting": "आठवणी निर्माण करा", - "nightly_tasks_generate_memories_setting_description": "संपत्तीमधून नवीन आठवणी तयार करा", - "nightly_tasks_missing_thumbnails_setting": "उपलब्ध नसलेल्या थंबनेल तयार करा", - "nightly_tasks_missing_thumbnails_setting_description": "थंबनेल नसलेल्या संपत्त्यांना थंबनेल निर्मितीसाठी रांगेत ठेवा", - "nightly_tasks_settings": "रात्रिकाळीन कार्यांची सेटिंग्ज", - "nightly_tasks_settings_description": "रात्रीच्या कार्यांचे व्यवस्थापन करा", - "nightly_tasks_start_time_setting": "सुरुवात वेळ", - "nightly_tasks_start_time_setting_description": "सर्वरची रात्रीची कार्ये सुरू होण्याची वेळ", - "nightly_tasks_sync_quota_usage_setting": "कोटा वापर समक्रमित करा", - "nightly_tasks_sync_quota_usage_setting_description": "वर्तमान वापरानुसार संचयन कोटा अपडेट करा", - "no_paths_added": "कोणतेही मार्ग जोडले नाहीत", - "no_pattern_added": "कोणतेही पॅटर्न जोडले नाहीत", - "note_apply_storage_label_previous_assets": "टीप: पूर्वी अपलोड केलेल्या अॅसेट्सवर स्टोरेज लेबल लागू करण्यासाठी, चालवा", - "note_cannot_be_changed_later": "नोट: हे नंतर बदलता येणार नाही!", - "notification_email_from_address": "प्रेषकाचा पत्ता", - "notification_email_from_address_description": "प्रेषक ईमेल पत्ता, उदाहरणार्थ: \"Immich Photo Server noreply@example.com\". खात्री करा की आपण ज्यापासून ईमेल पाठवण्याची परवानगी आहे तोच पत्ता वापरता.", - "notification_email_host_description": "ईमेल सर्व्हरचा होस्ट (उदा. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "प्रमाणपत्र त्रुटी दुर्लक्षित करा", - "notification_email_ignore_certificate_errors_description": "TLS प्रमाणपत्र पडताळणी त्रुटी दुर्लक्षित करा (शिफारसीय नाही)", - "notification_email_password_description": "ईमेल सर्व्हरवर प्रमाणीकरणासाठी वापरण्याचा पासवर्ड", - "notification_email_port_description": "ईमेल सर्व्हरचा पोर्ट (उदा. 25, 465 किंवा 587)", - "notification_email_secure": "एस एम टी पी एस", - "notification_email_secure_description": "एस.एम.टी.पी वापरा (टी.एल.एस वरील एस.एम.टी.पी)", - "notification_email_sent_test_email_button": "चाचणी ईमेल पाठवा आणि जतन करा", - "notification_email_setting_description": "ईमेल सूचना पाठवण्याच्या सेटिंग्ज", - "notification_email_test_email": "चाचणी ईमेल पाठवा", - "notification_email_test_email_failed": "चाचणी ईमेल पाठवण्यात अयशस्वी - कृपया आपल्या मूल्ये तपासा", - "notification_email_test_email_sent": "एक चाचणी ईमेल {email} या पत्त्यावर पाठवण्यात आले आहे. कृपया तुमचा इनबॉक्स तपासा.", - "notification_email_username_description": "ईमेल सर्व्हरवर प्रमाणीकृत करताना वापरण्याचे वापरकर्तानाव", - "notification_enable_email_notifications": "ईमेल सूचना सक्षम करा", - "notification_settings": "सूचना सेटिंग्ज", - "notification_settings_description": "ईमेलसह सूचना सेटिंग्ज व्यवस्थापित करा", - "oauth_auto_launch": "स्वयंचलित सुरू करा", - "oauth_auto_launch_description": "लॉगिन पृष्ठावर जाताच OAuth लॉगिन प्रवाह आपोआप सुरू करा", - "oauth_auto_register": "स्वयंचलित नोंदणी करा", - "oauth_auto_register_description": "OAuth सह साइन इन केल्यावर नवीन वापरकर्त्यांची आपोआप नोंदणी करा", - "oauth_button_text": "बटण मजकूर", - "oauth_client_secret_description": "PKCE (प्रूफ की फॉर कोड एक्सचेंज) OAuth प्रदात्याद्वारे समर्थित नसल्यास आवश्यक", - "oauth_enable_description": "OAuth सह लॉगिन करा", - "oauth_mobile_redirect_uri": "मोबाइल रीडायरेक्ट URI", - "oauth_mobile_redirect_uri_override": "मोबाईल रीडायरेक्ट URI अधिलेखन", - "oauth_mobile_redirect_uri_override_description": "जेव्हा OAuth प्रदाता मोबाईल URI (उदाहरणार्थ “{callback}”) ला परवानगी देत नाही, तेव्हा हे सक्षम करा", - "oauth_role_claim": "भूमिका दावा", - "oauth_role_claim_description": "या क्लेमच्या उपस्थितीवरून स्वयंचलितपणे प्रशासकीय प्रवेश द्या. या क्लेममध्ये ‘user’ किंवा ‘admin’ ही मूल्ये असू शकतात.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth लॉगिन सेटिंग्ज व्यवस्थापित करा", - "oauth_settings_more_details": "या वैशिष्ट्याबद्दल अधिक माहितीसाठी, docs पहा.", - "oauth_storage_label_claim": "स्टोरेज लेबल दावा", - "oauth_storage_label_claim_description": "वापरकर्त्याचे स्टोरेज लेबल या दाव्याच्या मूल्यावर स्वयंचलितपणे सेट करा.", - "oauth_storage_quota_claim": "संग्रहण कोटा दावा", - "oauth_storage_quota_claim_description": "या दाव्याच्या मूल्यावर आधारित वापरकर्त्याचा संचयन कोटा स्वयंचलितपणे सेट करा।", - "oauth_storage_quota_default": "डीफॉल्ट संग्रहण कोटा (GiB)", - "oauth_storage_quota_default_description": "क्लेम न दिल्यास वापरण्यात येणारा संग्रहण कोटा (GiB)।", - "oauth_timeout": "विनंती वेळ मर्यादा", - "oauth_timeout_description": "मिलिसेकंदांमध्ये विनंत्यांसाठी वेळसमाप्ती", - "ocr_job_description": "प्रतिमांमधील मजकूर ओळखण्यासाठी मशीन लर्निंग वापरा", - "password_enable_description": "ईमेल आणि पासवर्डने लॉगिन करा", - "password_settings": "पासवर्ड लॉगिन", - "password_settings_description": "पासवर्ड लॉगिन सेटिंग्ज व्यवस्थापित करा", - "paths_validated_successfully": "सर्व मार्ग यशस्वीरित्या सत्यापित केले गेली आहेत", - "person_cleanup_job": "व्यक्ती स्वच्छता काम", - "quota_size_gib": "संचय कोटा आकार (GiB)", - "refreshing_all_libraries": "सर्व लायब्ररी रीफ्रेश करीत आहे", - "registration": "प्रशासक नोंदणी", - "registration_description": "आपण प्रणालीवरील पहिले वापरकर्ता आहात, म्हणून आपल्याला प्रशासक म्हणून नियुक्त केले जाईल आणि प्रशासकीय कार्ये आपल्याद्वारे हाताळली जातील; तसेच इतर वापरकर्ते आपण तयार कराल.", - "require_password_change_on_login": "पहिल्या लॉगिनवर वापरकर्त्यास पासवर्ड बदलण्याची आवश्यकता असेल", - "reset_settings_to_default": "सेटिंग्ज डीफॉल्टवर रीसेट करा", - "reset_settings_to_recent_saved": "सेटिंग्ज अलीकडील जतन केलेल्या सेटिंग्जवर रीसेट करा", - "scanning_library": "लायब्ररी स्कॅन करीत आहे", - "search_jobs": "नोकऱ्या शोधत आहे…", - "send_welcome_email": "स्वागत ईमेल पाठवा", - "server_external_domain_settings": "बाह्य डोमेन", - "server_external_domain_settings_description": "सार्वजनिक सामायिक दुव्यांसाठी डोमेन (उदा. http(s)://)", - "server_public_users": "सार्वजनिक वापरकर्ते", - "server_public_users_description": "सार्वजनिक अल्बममध्ये वापरकर्ता जोडताना सर्व वापरकर्त्यांची (नाव व ईमेल) यादी दर्शवली जाते. अक्षम केल्यास, ही यादी फक्त प्रशासकांसाठीच उपलब्ध असेल.", - "server_settings": "सर्व्हर सेटिंग्ज", - "server_settings_description": "सर्व्हर सेटिंग्ज व्यवस्थापित करा", - "server_welcome_message": "स्वागत संदेश", - "server_welcome_message_description": "लॉगिन पृष्ठावर दर्शविण्यात येणारा संदेश।", - "sidecar_job": "साइडकार मेटाडेटा", - "sidecar_job_description": "फाईल सिस्टममधून साइडकार मेटाडेटा शोधा किंवा समक्रमित करा", - "slideshow_duration_description": "प्रत्येक प्रतिमा किती सेकंद प्रदर्शित करायची", - "smart_search_job_description": "स्मार्ट शोधासाठी मालमत्तांवर मशीन लर्निंग चालवा", - "storage_template_date_time_description": "दिनांक-वेळ माहितीसाठी मालमत्तेच्या निर्मिती वेळाचा वापर", - "storage_template_date_time_sample": "नमुना वेळ {date}", - "storage_template_enable_description": "संच टेम्पलेट इंजिन सक्षम करा", - "storage_template_hash_verification_enabled": "हॅश सत्यापन सक्षम", - "storage_template_hash_verification_enabled_description": "हॅश सत्यापन सक्षम करते; परिणामांचा पूर्ण अर्थ न कळल्यास निष्क्रिय करू नका", - "storage_template_migration": "संच टेम्पलेट स्थलांतर", - "storage_template_migration_description": "पूर्वी अपलोड केलेल्या मालमत्तांवर चालू {template} लागू करा", - "storage_template_migration_info": "संच टेम्पलेट सर्व एक्स्टेंशन्स लघू (लोअरकेस)मध्ये रूपांतरित करेल. टेम्पलेट बदल फक्त नवीन मालमत्तांवर लागू होतील. पूर्वी अपलोड केलेल्या मालमत्तांवर रेट्रो-लागू करण्यासाठी {job} चालवा।", - "storage_template_migration_job": "संग्रह टेम्प्लेट स्थलांतर जॉब", - "storage_template_more_details": "या वैशिष्ट्याबद्दल अधिक माहितीसाठी, संग्रह टेम्प्लेट आणि त्याचे परिणाम पहा", - "storage_template_onboarding_description_v2": "सक्षम केल्यास, ही वैशिष्ट्य वापरकर्ताद्वारे परिभाषित टेम्प्लेटच्या आधारे फायली स्वयंचलितपणे आयोजित करेल. अधिक माहितीसाठी, कृपया दस्तऐवजीकरण पहा.", - "storage_template_path_length": "मार्गाची अंदाजे लांबी मर्यादा: {length, number}/{limit, number}", - "storage_template_settings": "संग्रह टेम्प्लेट", - "storage_template_settings_description": "अपलोड केलेल्या फायलींच्या फोल्डर संरचना आणि नाव व्यवस्थापित करा", - "storage_template_user_label": "{label} हा वापरकर्त्याचा संग्रह लेबल आहे", - "system_settings": "प्रणाली सेटिंग्ज", - "tag_cleanup_job": "टॅग स्वच्छता", - "template_email_available_tags": "तुमच्या टेम्प्लेटमध्ये खालील चल (variables) वापरू शकता: {tags}", - "template_email_if_empty": "टेम्प्लेट रिक्त असल्यास, डीफॉल्ट ईमेल वापरला जाईल.", - "template_email_invite_album": "आमंत्रण अल्बम टेम्प्लेट", - "template_email_preview": "पूर्वावलोकन", - "template_email_settings": "ईमेल टेम्प्लेट", - "template_email_update_album": "अल्बम टेम्प्लेट अद्यतनित करा", - "template_email_welcome": "स्वागत ईमेल टेम्प्लेट", - "template_settings": "सूचना टेम्पलेट्स", - "template_settings_description": "सूचनांसाठी सानुकूल टेम्पलेट्स व्यवस्थापित करा", - "theme_custom_css_settings": "सानुकूल CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets (CSS) द्वारे Immich चे डिझाइन सानुकूल करण्याची परवानगी मिळते.", - "theme_settings": "थीम सेटिंग्ज", - "theme_settings_description": "Immich च्या वेब इंटरफेसचे सानुकूलन व्यवस्थापित करा", - "thumbnail_generation_job": "थंबनेल तयार करा", - "thumbnail_generation_job_description": "प्रत्येक मालमत्तेसाठी मोठे, लहान आणि ब्लर केलेले थंबनेल तसेच प्रत्येक व्यक्तीसाठी थंबनेल तयार करा", - "transcoding_acceleration_api": "एक्सेलेरेशन API", - "transcoding_acceleration_api_description": "ट्रान्सकोडिंग गती वाढवण्यासाठी तुमच्या उपकरणाशी संवाद साधणारी API. ही सेटिंग ‘बेस्ट इफर्ट’ आहे: अयशस्वी झाल्यास सॉफ्टवेअर ट्रान्सकोडिंगकडे पलटवते. VP9 हार्डवेअरवर अवलंबून काम करेल किंवा नाही।", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU आवश्यक)", - "transcoding_acceleration_qsv": "Quick Sync (7व्या पिढीचा Intel CPU किंवा नंतरची आवश्यकता)", - "transcoding_acceleration_rkmpp": "RKMPP (केवळ Rockchip SoC वर)", - "transcoding_acceleration_vaapi": "वीएएपीआई", - "transcoding_accepted_audio_codecs": "मान्य केलेले ऑडिओ कोडेसेस", - "transcoding_accepted_audio_codecs_description": "कोणते ऑडिओ कोडेसेस ट्रान्सकोड केले जाण्याची गरज नाही ते निवडा. केवळ ऑडिओ असलेल्या इनपुटसाठी वापरले जाते.", - "transcoding_accepted_containers": "मान्य कंटेनर प्रारूप", - "transcoding_accepted_containers_description": "कोणते कंटेनर प्रारूप MP4 मध्ये रीमक्स करण्याची गरज नाही ते निवडा. केवळ विशिष्ट ट्रान्सकोड धोरणांसाठी वापरले जाते.", - "transcoding_accepted_video_codecs": "मान्य व्हिडिओ कोडेसेस", - "transcoding_accepted_video_codecs_description": "कोणते व्हिडिओ कोडेसेस ट्रान्सकोड करण्याची गरज नाही ते निवडा. केवळ विशिष्ट ट्रान्सकोड धोरणांसाठी वापरले जाते.", - "transcoding_advanced_options_description": "अशी सेटिंग्ज ज्यात बहुतेक वापरकर्त्यांना बदल करण्याची गरज नाही", - "transcoding_audio_codec": "ऑडिओ कोडेक", - "transcoding_audio_codec_description": "Opus हा सर्वाधिक गुणवत्ता पर्याय आहे, परंतु जुन्या उपकरणे किंवा सॉफ्टवेअरशी कमी सुसंगतता असू शकते.", - "transcoding_bitrate_description": "ज्या व्हिडिओंचा बिटरेट जास्त आहे किंवा जे मान्य प्रारूपात नाहीत", - "transcoding_codecs_learn_more": "येथे वापरल्या जाणाऱ्या संज्ञेबद्दल अधिक जाणून घेण्यासाठी, H.264 कोडेक, HEVC कोडेक आणि VP9 कोडेक यांसाठी FFmpeg दस्तऐवज पहा.", - "transcoding_constant_quality_mode": "सातत्यपूर्ण गुणवत्ता मोड", - "transcoding_constant_quality_mode_description": "ICQ हे CQP पेक्षा चांगले आहे, परंतु काही हार्डवेअर त्वरक उपकरणे हे मोड समर्थन करत नाहीत. गुणवत्ता आधारित एन्कोडिंगसाठी ICQ मोड निवडेल. NVENC ICQ समर्थित नसल्याने त्याकडे दुर्लक्ष केले जाईल.", - "transcoding_constant_rate_factor": "सातत्यपूर्ण रेट फॅक्टर (-crf)", - "transcoding_constant_rate_factor_description": "व्हिडिओ गुणवत्ता स्तर. H.264 साठी सामान्यतः 23, HEVC साठी 28, VP9 साठी 31 आणि AV1 साठी 35. कमी मूल्य म्हणजे उच्च गुणवत्ता, परंतु फायली मोठ्या होतील.", - "transcoding_disabled_description": "कोणतेही व्हिडिओ ट्रान्सकोड करू नका, काही ग्राहकांच्या प्लेबॅकमध्ये अडचण येऊ शकते", - "transcoding_encoding_options": "एन्कोडिंग पर्याय", - "transcoding_encoding_options_description": "एन्कोड केलेल्या व्हिडिओंसाठी कोडेसेस, रिझोल्यूशन, गुणवत्ता आणि इतर पर्याय सेट करा", - "transcoding_hardware_acceleration": "हार्डवेअर त्वरण", - "transcoding_hardware_acceleration_description": "प्रयोगात्मक: एकाच बिटरेटवर जलद ट्रान्सकोडिंग, परंतु गुणवत्ता कमी होऊ शकते", - "transcoding_hardware_decoding": "हार्डवेअर डीकोडिंग", - "transcoding_hardware_decoding_setting_description": "फक्त एन्कोडिंग त्वरणऐवजी पूर्ण एन्ड-टू-एन्ड त्वरण सक्षम करा. सर्व व्हिडिओंवर काम नसेल.", - "transcoding_max_b_frames": "कमाल B-फ्रेम", - "transcoding_max_b_frames_description": "जास्त मूल्ये संकुचन कार्यक्षमतेत सुधारणा करतात, परंतु एन्कोडिंग मंद करतात. जुन्या उपकरणांवर हार्डवेअर त्वरणाशी सुसंगत नसायचे शक्यता. 0 असेल तर B-फ्रेम निष्क्रिय, -1 ठेवल्यास स्वयंचलितपणे सेट होईल.", - "transcoding_max_bitrate": "कमाल बिटरेट", - "transcoding_max_bitrate_description": "कमाल बिटरेट सेट केल्याने थोड्या गुणवत्तेच्या किंमतीवर फाईल साइज अधिक अंदाजयोग्य होतात. 720p साठी VP9 किंवा HEVC साठी साधारण 2600 kbit/s आणि H.264 साठी 4500 kbit/s योग्य असतात. 0 केल्यास हे सेटिंग निष्क्रिय होते. युनिट दिले नसल्यास k (kbit/s) गृहित धरले जाते; त्यामुळे 5000, 5000k आणि 5M (Mbit/s) हे समतुल्य मानले जातात.", - "transcoding_max_keyframe_interval": "कमाल कीफ्रेम अंतराल", - "transcoding_max_keyframe_interval_description": "कीफ्रेम दरम्यान कमाल फ्रेम अंतराल ठरवते. कमी मूल्ये संकुचन कार्यक्षमतेत घट करतात, परंतु शोध वेळ सुधारतात आणि वेगवान हालचालीतील दृश्यांची गुणवत्ता सुधारू शकतात. 0 ठेवल्यास स्वयंचलितपणे सेट.", - "transcoding_optimal_description": "लक्ष्य रिझोल्यूशनपेक्षा जास्त किंवा मान्य प्रारूपात नसलेले व्हिडिओ", - "transcoding_policy": "ट्रान्सकोड धोरण", - "transcoding_policy_description": "व्हिडिओ केव्हा ट्रान्सकोड केला जाईल ते सेट करा", - "transcoding_preferred_hardware_device": "प्राधान्यकृत हार्डवेअर उपकरण", - "transcoding_preferred_hardware_device_description": "केवळ VAAPI आणि QSV साठी लागू. हार्डवेअर ट्रान्सकोडिंग साठी वापरला जाणारा DRI नोड सेट करा.", - "transcoding_preset_preset": "प्रीसेट (–preset)", - "transcoding_preset_preset_description": "संकुचन गती. किंचित मंद प्रीसेट्स लहान फाइल तयार करतात आणि ठराविक बिटरेटसाठी गुणवत्ता वाढवतात. VP9 ‘faster’ पेक्षा जास्त गती लक्षात घेत नाही.", - "transcoding_reference_frames": "संदर्भ फ्रेम्स", - "transcoding_reference_frames_description": "दिलेल्या फ्रेमचे संकुचन करताना किती फ्रेम्स संदर्भित कराव्यात हे ठरवते. जास्त मूल्ये संकुचन कार्यक्षमतेत सुधारणा करतात, परंतु एन्कोडिंग मंद करतात. 0 ठेवल्यास हे स्वयंचलितपणे सेट होते.", - "transcoding_required_description": "फक्त मान्य प्रारूपात नसलेले व्हिडिओ", - "transcoding_settings": "व्हिडिओ ट्रान्सकोडिंग सेटिंग्ज", - "transcoding_settings_description": "कोणते व्हिडिओ ट्रान्सकोड करायचे आणि कसे प्रक्रिया करायची ते व्यवस्थापित करा", - "transcoding_target_resolution": "लक्ष्य रिझोल्यूशन", - "transcoding_target_resolution_description": "उच्च रिझोल्यूशन अधिक तपशील जपते, परंतु एन्कोड करण्यात जास्त वेळ लागतो, फाइल साइज मोठी होते आणि ऍप प्रतिसादक्षमता कमी होऊ शकते.", - "transcoding_temporal_aq": "अस्थायी AQ", - "transcoding_temporal_aq_description": "हे फक्त NVENC साठी लागू आहे. Temporal Adaptive Quantization उच्च तपशील असलेल्या, कमी हालचालीच्या दृश्यांची गुणवत्ता वाढवते. जुन्या डिव्हाइसशी सुसंगत नसण्याची शक्यता आहे.", - "transcoding_threads": "थ्रेड्स", - "transcoding_threads_description": "जास्त मूल्ये एन्कोडिंग जलद करतात, परंतु सक्रिय असताना सर्व्हरला इतर कार्ये प्रक्रिया करण्यासाठी कमी जागा राहते. हे मूल्य CPU कोअर्सपेक्षा जास्त नसावे. 0 ठेवल्यास सर्व्हरची पूर्ण क्षमतेने वापर होते.", - "transcoding_tone_mapping": "टोन-मॅपिंग", - "transcoding_tone_mapping_description": "HDR व्हिडिओ SDR मध्ये रूपांतरित करताना त्यांच्या रूपाची शक्य तितकी जतन करण्याचा प्रयत्न. प्रत्येक अल्गोरिथम रंग, तपशील आणि उजेड यांच्यात भिन्न समतोल साधतो. Hable तपशील जतन करते, Mobius रंग जतन करतो, Reinhard उजेड जतन करतो.", - "transcoding_transcode_policy": "ट्रान्सकोड धोरण", - "transcoding_transcode_policy_description": "व्हिडिओ कधी ट्रान्सकोड करायचा याबाबत धोरण. HDR व्हिडिओ नेहमीच ट्रान्सकोड होतील (जोपर्यंत ट्रान्सकोड सक्षम नसेल तेव्हा सोड).", - "transcoding_two_pass_encoding": "दोन टप्प्यात एन्कोडिंग", - "transcoding_two_pass_encoding_setting_description": "बेहतर एन्कोडेड व्हिडिओ मिळवण्यासाठी दोन टप्प्यात ट्रान्सकोड करा. जास्तीत जास्त बिटरेट सक्षम असल्यास (H.264 आणि HEVC साठी आवश्यक), हा मोड जास्तीत जास्त बिटरेटवर आधारित रेंज वापरतो आणि CRF दुर्लक्षित करतो. VP9 साठी, जास्तीत जास्त बिटरेट अक्षम असल्यास CRF वापरता येतो.", - "transcoding_video_codec": "व्हिडिओ कोडेक", - "transcoding_video_codec_description": "VP9 उच्च कार्यक्षमता आणि वेब सुसंगतता देतो, परंतु ट्रान्सकोडिंग जास्त वेळ घेतो. HEVC सुद्धा चांगले काम करते, परंतु सुसंगतता कमी. H.264 सर्वत्र सुसंगत आणि जलद ट्रान्सकोडिंग करतो, परंतु फाइल मोठ्या तयार करतो. AV1 सर्वाधिक कार्यक्षम परंतु जुन्या उपकरणांवर कमी समर्थन.", - "trash_enabled_description": "ट्रॅश वैशिष्ट्ये सक्षम करा", - "trash_number_of_days": "दिवसांची संख्या", - "trash_number_of_days_description": "कायमस्वरीत्या काढून टाकण्यापूर्वी ट्रॅशमध्ये सामग्री किती दिवस ठेवायची ते क्रम", - "trash_settings": "ट्रॅश सेटिंग्ज", - "trash_settings_description": "ट्रॅश सेटिंग्ज व्यवस्थापित करा", - "unlink_all_oauth_accounts": "सर्व OAuth खात्यांची जोडणी तोडा", - "unlink_all_oauth_accounts_description": "नव्या सेवा-प्रदात्याकडे स्थलांतर करण्यापूर्वी सर्व OAuth खात्यांची जोडणी तोडायला विसरू नका.", - "unlink_all_oauth_accounts_prompt": "तुम्ही खरोखर सर्व OAuth खात्यांची जोडणी तोडू इच्छिता का? यामुळे प्रत्येक वापरकर्त्याचा OAuth ID रीसेट होईल आणि ही कृती पूर्वस्थितीत आणता येणार नाही.", - "user_cleanup_job": "वापरकर्ता स्वच्छता", - "user_delete_delay": "{user} यांचे खाते आणि मालमत्ता कायमची हटविण्यासाठी {delay, plural, one {# दिवस} other {# दिवस}} नंतर शेड्यूल केली जातील.", - "user_delete_delay_settings": "हटविण्याची विलंबीत कालावधी", - "user_delete_delay_settings_description": "वापरकर्त्याचे खाते आणि मालमत्ता कायमची हटविण्यापूर्वी किती दिवस विलंब करायचा ते. वापरकर्ता हटविण्याचे जॉब मध्यरात्री चालवले जाते आणि हटविण्यास तयार असलेल्या वापरकर्त्यांची तपासणी करते. या सेटिंगमध्ये बदल पुढील चालू वेळी लागू होतील.", - "user_delete_immediately": "{user} यांचे खाते आणि मालमत्ता तात्काळ कायमची हटविण्यासाठी रांगेत टाकली जाईल.", - "user_delete_immediately_checkbox": "वापरकर्ता आणि मालमत्ता तात्काळ हटविण्यासाठी रांगेत ठेवा", - "user_details": "वापरकर्ता तपशील", - "user_management": "वापरकर्ता व्यवस्थापन", - "user_password_has_been_reset": "वापरकर्त्याचे पासवर्ड रीसेट केले गेले:", - "user_password_reset_description": "कृपया वापरकर्त्याला तात्पुरता पासवर्ड द्या आणि त्यांना कळवा की पुढील लॉगिनवर त्यांना पासवर्ड बदलावा लागेल.", - "user_restore_description": "{user} यांचे खाते पुनर्संचयित केले जाईल.", - "user_restore_scheduled_removal": "वापरकर्ता पुनर्संचयित करा – नियोजित हटविण्याची तारीख {date, date, long}", - "user_settings": "वापरकर्ता सेटिंग्ज", - "user_settings_description": "वापरकर्ता सेटिंग्ज व्यवस्थापित करा", - "version_check_enabled_description": "आवृत्ती तपासणी सक्षम करा", - "version_check_implications": "आवृत्ती तपासणी वैशिष्ट्य GitHub.com सोबत आवर्ती संवादावर अवलंबून आहे", - "version_check_settings": "आवृत्ती तपासणी", - "version_check_settings_description": "नवीन आवृत्ती सूचना सक्षम/अक्षम करा", - "video_conversion_job": "व्हिडिओ ट्रान्सकोड करा", - "video_conversion_job_description": "ब्राउझर आणि उपकरणांसह जास्त सुसंगततेसाठी व्हिडिओ ट्रान्सकोड करा" - }, - "admin_email": "प्रशासक ईमेल", - "admin_password": "प्रशासक पासवर्ड", - "administration": "प्रशासन", - "advanced": "प्रगत", - "advanced_settings_enable_alternate_media_filter_subtitle": "सिंक दरम्यान वैकल्पिक निकषांवर आधारित मीडिया फिल्टर करण्यासाठी हा पर्याय वापरा. ॲप सर्व अल्बम ओळखण्यात समस्या येत असल्यासच वापरा.", - "advanced_settings_enable_alternate_media_filter_title": "[प्रयोगात्मक] उपकरण-आधारित अल्बम सिंक फिल्टर वापरा", - "advanced_settings_log_level_title": "लॉग पातळी: {level}", - "advanced_settings_prefer_remote_subtitle": "काही उपकरणे स्थानिक अॅसेटमधून थंबनेल लोड करण्यात खूप मंद आहेत. त्याऐवजी रिमोट प्रतिमा लोड करण्यासाठी हा सेटिंग सक्षम करा.", - "advanced_settings_prefer_remote_title": "रिमोट प्रतिमा पसंत करा", - "advanced_settings_proxy_headers_subtitle": "प्रत्येक नेटवर्क विनंतीसोबत Immich पाठवावयाचे प्रॉक्सी हेडर येथे परिभाषित करा", - "advanced_settings_proxy_headers_title": "कस्टम प्रॉक्सी हेडर्स [प्रायोगिक]", - "advanced_settings_readonly_mode_subtitle": "या मोडमध्ये फोटो फक्त पाहता येतात - अनेक फोटो निवडणे, शेअर करणे, कास्ट करणे आणि हटवणे अशा क्रिया निष्क्रिय राहतात. मुख्य स्क्रीनवरील वापरकर्ता अवतारातून हा मोड चालू किंवा बंद करा", - "advanced_settings_readonly_mode_title": "फक्त पाहण्याचा मोड", - "advanced_settings_self_signed_ssl_subtitle": "सर्व्हर एंडपॉइंटसाठी SSL प्रमाणपत्र सत्यापन वगळते. स्वाक्षरीत प्रमाणपत्रांसाठी आवश्यक.", - "advanced_settings_self_signed_ssl_title": "स्वतः स्वाक्षरीत SSL प्रमाणपत्रांना परवानगी द्या [प्रायोगिक]", - "advanced_settings_sync_remote_deletions_subtitle": "वेबवर ही क्रिया केली गेल्यावर या उपकरणावर असलेले अॅसेट आपोआप हटवा किंवा पुनर्संचयित करा", - "advanced_settings_sync_remote_deletions_title": "रिमोट हटवण्या सिंक करा [प्रयोगात्मक]", - "advanced_settings_tile_subtitle": "प्रगत वापरकर्ता सेटिंग्ज", - "advanced_settings_troubleshooting_subtitle": "समस्या निवारणासाठी अतिरिक्त वैशिष्ट्ये सक्षम करा", - "advanced_settings_troubleshooting_title": "समस्या निवारण", - "age_months": "वय {months, plural, one {एक महिना} other {# महिने}}", - "age_year_months": "वय १ वर्ष, {months, plural, one {एक महिना} other {# महिने}}", - "age_years": "{years, plural, other {वय #}}", - "album": "अल्बम", - "album_added": "अल्बम जोडले", - "album_added_notification_setting_description": "शेअर केलेल्या अल्बममध्ये जोडल्यावर ईमेल सूचना मिळवा", - "album_cover_updated": "अल्बम कव्हर अद्यतनित झाले", - "album_delete_confirmation": "आपण निश्चितच अल्बम {album} हटवणार आहात का?", - "album_delete_confirmation_description": "हा अल्बम शेअर केला असेल तर इतर वापरकर्ते आता तो पाहू शकणार नाहीत.", - "album_deleted": "अल्बम हटवले", - "album_info_card_backup_album_excluded": "वगळले गेले", - "album_info_card_backup_album_included": "समाविष्ट", - "album_info_updated": "अल्बम माहिती अद्यतनित", - "album_leave": "अल्बम सोडणार का?", - "album_leave_confirmation": "आपण निश्चितच अल्बम {album} सोडणार आहात का?", - "album_name": "अल्बमचे नाव", - "album_options": "अल्बम पर्याय", - "album_remove_user": "वापरकर्ता काढून टाकायचा का?", - "album_remove_user_confirmation": "आपण निश्चितच वापरकर्ता {user} काढून टाकणार आहात का?", - "album_search_not_found": "तुमच्या शोधाशी जुळणारे कोणतेही अल्बम आढळले नाहीत", - "album_share_no_users": "असा दिसते की हा अल्बम तुम्ही सर्व वापरकर्त्यांसोबत शेअर केला आहे किंवा शेअर करण्यासाठी कुठलाही वापरकर्ता उपलब्ध नाही.", - "album_summary": "अल्बम सारांश", - "album_updated": "अल्बम अद्यतनित", - "album_updated_setting_description": "शेअर केलेल्या अल्बममध्ये नवीन फाईल्स आल्यास ईमेल सूचनार्थ प्राप्त करा", - "album_user_left": "सोडले: {album}", - "album_user_removed": "काढले: {user}", - "album_viewer_appbar_delete_confirm": "आपण निश्चितच हा अल्बम आपल्या खात्यातून हटवायचा आहे का?", - "album_viewer_appbar_share_err_delete": "अल्बम हटवण्यात अयशस्वी", - "album_viewer_appbar_share_err_leave": "अल्बम सोडण्यास अयशस्वी", - "album_viewer_appbar_share_err_remove": "अल्बममधून फाईल्स काढण्यात अडचणी", - "album_viewer_appbar_share_err_title": "अल्बमचं शीर्षक बदलण्यात अयशस्वी", - "album_viewer_appbar_share_leave": "अल्बम सोडा", - "album_viewer_appbar_share_to": "यांना शेअर करा", - "album_viewer_page_share_add_users": "वापरकर्ते जोडा", - "album_with_link_access": "लिंक असलेल्या कोणत्याही व्यक्तीस या अल्बममधील फोटो आणि लोक पाहता येतील.", - "albums": "अल्बम्स", - "albums_count": "{count, plural, one {{count, number} अल्बम} other {{count, number} अल्बम्स}}", - "albums_default_sort_order": "डीफॉल्ट अल्बम क्रमवारी", - "albums_default_sort_order_description": "नवीन अल्बम तयार करताना फाईल्सची प्रारंभिक क्रमवारी.", - "albums_feature_description": "इतर वापरकर्त्यांसोबत शेअर करता येणाऱ्या फाईल्सचा संग्रह.", - "albums_on_device_count": "डिव्हाइसवरील अल्बम्स ({count})", - "all": "सर्व", - "all_albums": "सर्व अल्बम्स", - "all_people": "सर्व लोक", - "all_videos": "सर्व व्हिडिओ", - "allow_dark_mode": "डार्क मोडला परवानगी द्या", - "allow_edits": "संपादनांना परवानगी द्या", - "allow_public_user_to_download": "सार्वजनिक वापरकर्त्यांना डाउनलोड करण्याची परवानगी द्या", - "allow_public_user_to_upload": "सार्वजनिक वापरकर्त्यांना अपलोड करण्याची परवानगी द्या", - "allowed": "परवानगी आहे", - "alt_text_qr_code": "QR कोड प्रतिमा", - "anti_clockwise": "घडीच्या उलट दिशेने", - "api_key": "एपीआई की", - "api_key_description": "हा मूल्य एकदाच दाखविला जाईल. कृपया विंडो बंद करण्यापूर्वी ते कॉपी करायला विसरू नका.", - "api_key_empty": "आपले API की नाव रिक्त असू नये", - "api_keys": "API कीज", - "app_architecture_variant": "प्रकार (आर्किटेक्चर)", - "app_bar_signout_dialog_content": "तुम्हाला नक्की साइन आउट करायचे आहे का?", - "app_bar_signout_dialog_ok": "हो", - "app_bar_signout_dialog_title": "साइन आउट", - "app_download_links": "अॅप डाउनलोड लिंकस्", - "app_settings": "अ‍ॅप सेटिंग्ज", - "app_stores": "अॅप स्टोअर्स", - "app_update_available": "अॅप अपडेट उपलब्ध आहे", - "appears_in": "दिसते (कुठे दिसते)", - "apply_count": "लागू करा ({count, number})", - "archive": "आर्काइव्ह", - "archive_action_prompt": "{count} आर्काइव्हमध्ये जोडले", - "archive_or_unarchive_photo": "फोटो आर्काइव्ह करा किंवा अनआर्काइव्ह करा", - "archive_page_no_archived_assets": "आर्काइव्ह फाईल्स सापडल्या नाहीत", - "archive_page_title": "आर्काइव्ह ({count})", - "archive_size": "आर्काइव्ह आकार", - "archive_size_description": "डाउनलोडसाठी आर्काइव्ह आकार (GiB मध्ये) सेट करा", - "archived": "आर्काइव्ह केलेले", - "archived_count": "{count, plural, other {आर्काइव्ह केलेले #}}", - "are_these_the_same_person": "हे दोन्ही एकाच व्यक्ती आहेत का?", - "are_you_sure_to_do_this": "तुम्हाला हे खरंच करायचे आहे का?", - "asset_action_delete_err_read_only": "वाचन-सुरक्षित साधन(े) हटविता येत नाहीत, वगळले जात आहे", - "asset_action_share_err_offline": "ऑफलाइन साधन(े) मिळविता येत नाहीत, वगळले जात आहे", - "asset_added_to_album": "अल्बममध्ये जोडले गेले", - "asset_adding_to_album": "अल्बममध्ये जोडत आहे…", - "asset_description_updated": "साधनाचे वर्णन अद्यावत केले गेले आहे", - "asset_filename_is_offline": "{filename} नावाचे साधन ऑफलाइन आहे", - "asset_has_unassigned_faces": "साधनात असाध्य चेहऱ्यांची माहिती आहे", - "asset_hashing": "हॅशिंग…", - "asset_list_group_by_sub_title": "गटानुसार गटबद्ध करा", - "asset_list_layout_settings_dynamic_layout_title": "डायनॅमिक लेआउट", - "asset_list_layout_settings_group_automatically": "स्वयंचलित", - "asset_list_layout_settings_group_by": "साधने गटानुसार गटबद्ध करा", - "asset_list_layout_settings_group_by_month_day": "महिना + दिवस", - "asset_list_layout_sub_title": "लेआउट", - "asset_list_settings_subtitle": "फोटो ग्रिड लेआउट सेटिंग्ज", - "asset_list_settings_title": "फोटो ग्रिड", - "asset_offline": "साधन ऑफलाइन आहे", - "asset_offline_description": "हे बाह्य साधन आता डिस्कवर सापडत नाही. मदतीसाठी आपल्या Immich प्रशासकाशी संपर्क करा.", - "asset_restored_successfully": "साधन यशस्वीपणे पुनर्संचयित केले गेले", - "asset_skipped": "वगळले", - "asset_skipped_in_trash": "ट्रॅशमध्ये", - "asset_trashed": "मीडिया घटक कचरापेटीत हलवला", - "asset_troubleshoot": "मीडिया घटक समस्यानिवारण", - "asset_uploaded": "अपलोड झाले", - "asset_uploading": "अपलोड करत आहे…", - "asset_viewer_settings_subtitle": "आपल्या गॅलरी व्ह्यूअरच्या सेटिंग्ज व्यवस्थापित करा", - "asset_viewer_settings_title": "साधन दर्शक", - "assets": "साधने", - "assets_added_count": "{count, plural, one {# साधन जोडले} other {# साधने जोडले}}", - "assets_added_to_album_count": "{count, plural, one {# साधन अल्बममध्ये जोडले} other {# साधने अल्बममध्ये जोडले}}", - "assets_added_to_albums_count": "{albumTotal, plural, one {# अल्बममध्ये} other {# अल्बममध्ये}} {assetTotal, plural, one {# मीडिया घटक} other {# मीडिया घटक}} जोडले", - "assets_cannot_be_added_to_album_count": "{count, plural, one {# साधन अल्बममध्ये जोडता येणार नाही} other {# साधने अल्बममध्ये जोडता येणार नाहीत}}", - "assets_cannot_be_added_to_albums": "{count, plural, one {# मीडिया घटक कोणत्याही अल्बममध्ये जोडता येत नाही} other {# मीडिया घटक कोणत्याही अल्बममध्ये जोडता येत नाहीत}}", - "assets_count": "{count, plural, one {# साधन} other {# साधने}}", - "assets_deleted_permanently": "{count} साधन(े) कायमचे हटविले", - "assets_deleted_permanently_from_server": "Immich सर्व्हरवरून {count} साधन(े) कायमचे हटविले", - "assets_downloaded_failed": "{count, plural, one {एक फाईल डाउनलोड अयशस्वी: {error}} other {# फाईल्स डाउनलोड अयशस्वी: {error}}}", - "assets_downloaded_successfully": "{count, plural, one {एक फाईल यशस्वीरित्या डाउनलोड झाली} other {# फाईल्स यशस्वीरित्या डाउनलोड झाल्या}}", - "assets_moved_to_trash_count": "{count, plural, one {एक फाईल ट्रॅशमध्ये हलवली} other {# फाईल्स ट्रॅशमध्ये हलवल्या}}", - "assets_permanently_deleted_count": "{count, plural, one {एक फाईल कायमस्वरूपी हटवली} other {# फाईल्स कायमस्वरूपी हटवल्या}}", - "assets_removed_count": "{count, plural, one {# मीडिया घटक काढून टाकला} other {# मीडिया घटक काढून टाकले}}", - "assets_removed_permanently_from_device": "{count} मीडिया घटक तुमच्या डिव्हाइसवरून कायमचे काढले गेले", - "assets_restore_confirmation": "कचरापेटीतले सर्व मीडिया घटक पुनर्संचयित करायचे आहेत का? ही कृती पूर्ववत करता येणार नाही. लक्षात ठेवा - ऑफलाइन मीडिया घटक अशा प्रकारे पुनर्संचयित करता येत नाहीत.", - "assets_restored_count": "{count, plural, one {# मीडिया घटक पुनर्संचयित केला} other {# मीडिया घटक पुनर्संचयित केले}}", - "assets_restored_successfully": "{count} मीडिया घटक यशस्वीरित्या पुनर्संचयित झाले", - "assets_trashed": "{count} मीडिया घटक कचरापेटीत हलवले", - "assets_trashed_count": "{count, plural, one {# मीडिया घटक कचरापेटीत हलवला} other {# मीडिया घटक कचरापेटीत हलवले}}", - "assets_trashed_from_server": "{count} मीडिया घटक Immich सर्व्हरवरून कचरापेटीत हलवले", - "assets_were_part_of_album_count": "{count, plural, one {मीडिया घटक आधीच त्या अल्बमचा भाग होता} other {मीडिया घटक आधीच त्या अल्बमचा भाग होते}}", - "assets_were_part_of_albums_count": "{count, plural, one {मीडिया घटक आधीच अल्बम्सचा भाग होता} other {मीडिया घटक आधीच अल्बम्सचा भाग होते}}", - "authorized_devices": "अधिकृत उपकरणे", - "automatic_endpoint_switching_subtitle": "उपलब्ध असल्यास निश्‍चित Wi-Fi वर स्थानिकरित्या कनेक्ट करा आणि इतर ठिकाणी पर्यायी कनेक्शन वापरा", - "automatic_endpoint_switching_title": "स्वयंचलित URL स्विचिंग", - "autoplay_slideshow": "स्वयंचलित स्लाइडशो", - "back": "मागे", - "back_close_deselect": "मागे किंवा बंद करा / निवड रद्द करा", - "background_backup_running_error": "पार्श्वभूमी बॅकअप सध्या चालू आहे, मॅन्युअल बॅकअप सुरू करू शकत नाही", - "background_location_permission": "बॅकग्राउंडमध्ये स्थान परवानगी द्या", - "background_location_permission_content": "बॅकग्राउंडमध्ये नेटवर्क स्विच करण्यासाठी Immich ला नेहमी अचूक स्थान माहिती (Wi-Fi नाव) पाहिजे", - "background_options": "पार्श्वभूमी पर्याय", - "backup": "बॅकअप", - "backup_album_selection_page_albums_device": "उपकरणावरील अल्बम ({count})", - "backup_album_selection_page_albums_tap": "समाविष्ट करण्यासाठी एकदाच टॅप करा; वगळण्यासाठी डबल टॅप करा", - "backup_album_selection_page_assets_scatter": "फाईल्स अनेक अल्बममध्ये विभागल्या जाऊ शकतात; बॅकअप दरम्यान अल्बम समाविष्ट किंवा वगळा।", - "backup_album_selection_page_select_albums": "अल्बम निवडा", - "backup_album_selection_page_selection_info": "निवड माहिती", - "backup_album_selection_page_total_assets": "एकूण स्वतंत्र फाईल्स", - "backup_albums_sync": "बॅकअप अल्बम समक्रमण", - "backup_all": "सर्व", - "backup_background_service_backup_failed_message": "बॅकअप अयशस्वी. पुन्हा प्रयत्न करत आहे…", - "backup_background_service_complete_notification": "अॅसेटचा बॅकअप पूर्ण झाला", - "backup_background_service_connection_failed_message": "सर्व्हरशी कनेक्ट करण्यात अयशस्वी. पुन्हा प्रयत्न करत आहे…", - "backup_background_service_current_upload_notification": "{filename} अपलोड करत आहे", - "backup_background_service_default_notification": "नवीन फाईल्स शोधत आहे…", - "backup_background_service_error_title": "बॅकअप त्रुटी", - "backup_background_service_in_progress_notification": "फाईल्स बॅकअप करत आहे…", - "backup_background_service_upload_failure_notification": "{filename} अपलोड करण्यात अयशस्वी", - "backup_controller_page_albums": "बॅकअप अल्बम", - "backup_controller_page_background_app_refresh_disabled_content": "बॅकग्राउंड बॅकअपसाठी सेटिंग्ज > जनरल > बॅकग्राउंड अॅप रिफ्रेश मध्ये ‘बॅकग्राउंड अॅप रिफ्रेश’ चालू करा.", - "backup_controller_page_background_app_refresh_disabled_title": "बॅकग्राउंड अॅप रिफ्रेश अक्षम", - "backup_controller_page_background_app_refresh_enable_button_text": "सेटिंग्जमध्ये जा", - "backup_controller_page_background_battery_info_link": "कसे करायचे ते दाखवा", - "backup_controller_page_background_battery_info_message": "उत्तम बॅकग्राउंड बॅकअपसाठी Immich साठी सर्व बॅटरी ऑप्टिमायझेशन अक्षम करा. \n\nहे डिव्हाइसनुसार वेगळे असू शकते, त्यामुळे तुमच्या डिव्हाइस निर्यातकर्त्यापासून मार्गदर्शन तपासा.", - "backup_controller_page_background_battery_info_ok": "ठीक आहे", - "backup_controller_page_background_battery_info_title": "बॅटरी ऑप्टिमायझेशन्स", - "backup_controller_page_background_charging": "चार्ज होतानाच", - "backup_controller_page_background_configure_error": "बॅकग्राउंड सेवा कॉन्फिगर करण्यात अयशस्वी", - "backup_controller_page_background_delay": "नवीन फाईल्स बॅकअप उशिरा: {duration}", - "backup_controller_page_background_description": "अॅप उघडल्याशिवाय नवीन फाईल्स आपोआप बॅकअप करण्यासाठी बॅकग्राउंड सेवा चालू करा", - "backup_controller_page_background_is_off": "स्वयंचलित बॅकग्राउंड बॅकअप बंद आहे", - "backup_controller_page_background_is_on": "स्वयंचलित बॅकग्राउंड बॅकअप चालू आहे", - "backup_controller_page_background_turn_off": "बॅकग्राउंड सेवा बंद करा", - "backup_controller_page_background_turn_on": "बॅकग्राउंड सेवा चालू करा", - "backup_controller_page_background_wifi": "फक्त Wi-Fi", - "backup_controller_page_backup": "बॅकअप", - "backup_controller_page_backup_selected": "निवडले: ", - "backup_controller_page_backup_sub": "फोटो आणि व्हिडिओ बॅकअप झाले", - "backup_controller_page_created": "निर्मित: {date}", - "backup_controller_page_desc_backup": "अॅप उघडल्यावर नवीन फाईल्स सर्व्हरवर आपोआप अपलोड करण्यासाठी फोरग्राउंड बॅकअप चालू करा।", - "backup_controller_page_excluded": "वगळले: ", - "backup_controller_page_failed": "{count} अयशस्वी", - "backup_controller_page_filename": "फाईल नाव: {filename} ({size})", - "backup_controller_page_id": "आयडी: {id}", - "backup_controller_page_info": "बॅकअप माहिती", - "backup_controller_page_none_selected": "काहीही निवडलेले नाही", - "backup_controller_page_remainder": "शिल्लक", - "backup_controller_page_remainder_sub": "निवडलेल्या फाईल्समधील उर्वरित फोटो व व्हिडिओ बॅकअप करायचे", - "backup_controller_page_server_storage": "सर्व्हर संग्रहण", - "backup_controller_page_start_backup": "बॅकअप सुरू करा", - "backup_controller_page_status_off": "स्वयंचलित फोरग्राउंड बॅकअप बंद आहे", - "backup_controller_page_status_on": "स्वयंचलित फोरग्राउंड बॅकअप चालू आहे", - "backup_controller_page_storage_format": "{used} पैकी {total} वापरले", - "backup_controller_page_to_backup": "बॅकअपसाठी अल्बम", - "backup_controller_page_total_sub": "निवडलेल्या अल्बममधील सर्व स्वतंत्र फोटो व व्हिडिओ", - "backup_controller_page_turn_off": "फोरग्राउंड बॅकअप बंद करा", - "backup_controller_page_turn_on": "फोरग्राउंड बॅकअप चालू करा", - "backup_controller_page_uploading_file_info": "फाईल माहिती अपलोड करत आहे", - "backup_err_only_album": "अंतिम अल्बम काढता येणार नाही", - "backup_error_sync_failed": "समक्रमण अयशस्वी. बॅकअप प्रक्रिया करता येत नाही.", - "backup_info_card_assets": "फाईल्स", - "backup_manual_cancelled": "रद्द केले", - "backup_manual_in_progress": "अपलोड आधीच चालू आहे. थोड्यावेळेनंतर पुन्हा प्रयत्न करा", - "backup_manual_success": "यशस्वी", - "backup_manual_title": "अपलोड स्थिती", - "backup_options": "बॅकअप पर्याय", - "backup_options_page_title": "बॅकअप पर्याय", - "backup_setting_subtitle": "बॅकग्राउंड आणि फोरग्राउंड अपलोड सेटिंग्ज व्यवस्थापित करा", - "backup_settings_subtitle": "अपलोड सेटिंग्ज व्यवस्थापित करा", - "backward": "मागासलेले", - "biometric_auth_enabled": "बायोमेट्रिक प्रमाणीकरण चालू आहे", - "biometric_locked_out": "आपण बायोमेट्रिक प्रमाणीकरणापासून लॉक आहात", - "biometric_no_options": "कोणतेही बायोमेट्रिक पर्याय उपलब्ध नाहीत", - "biometric_not_available": "या डिव्हाइसवर बायोमेट्रिक प्रमाणीकरण उपलब्ध नाही", - "birthdate_saved": "जन्मतारीख यशस्वीरित्या जतन झाली", - "birthdate_set_description": "फोटोच्या वेळी या व्यक्तीचे वय मोजण्यासाठी जन्मतारीख वापरली जाते.", - "blurred_background": "पार्श्वभूमी धुसळलेली", - "bugs_and_feature_requests": "बग्ज & फिचर विनंत्या", - "build": "तयार करा", - "build_image": "इमेज तयार करा", - "bulk_delete_duplicates_confirmation": "आपण {count, plural, one {# डुप्लिकेट अॅसेट सामूहिकरित्या हटवणार आहात याची खात्री आहे का} other {# डुप्लिकेट अॅसेट्स सामूहिकरित्या हटवणार आहात याची खात्री आहे का}}? प्रत्येक गटातील सर्वात मोठे अॅसेट ठेवले जाईल आणि बाकी सर्व डुप्लिकेट्स कायमस्वरूपी हटवले जातील. ही कृती पूर्ववत करता येणार नाही!", - "bulk_keep_duplicates_confirmation": "आपण {count, plural, one {# डुप्लिकेट अॅसेट ठेवणार आहात याची खात्री आहे का} other {# डुप्लिकेट अॅसेट्स ठेवणार आहात याची खात्री आहे का}}? यामुळे कोणतेही अॅसेट न हटवता सर्व डुप्लिकेट गट सोडवले जातील.", - "bulk_trash_duplicates_confirmation": "आपण {count, plural, one {# डुप्लिकेट अॅसेट ट्रॅशमध्ये टाकणार आहात याची खात्री आहे का} other {# डुप्लिकेट अॅसेट्स ट्रॅशमध्ये टाकणार आहात याची खात्री आहे का}}? प्रत्येक गटातील सर्वात मोठे अॅसेट ठेवले जाईल आणि बाकी सर्व डुप्लिकेट्स ट्रॅशमध्ये टाकले जातील.", - "buy": "Immich खरेदी करा", - "cache_settings_clear_cache_button": "कॅश मिटवा", - "cache_settings_clear_cache_button_title": "अॅपचे कॅश मिटवते. कॅश पुन्हा तयार होईपर्यंत अॅपची कामगिरी प्रभावित होऊ शकते.", - "cache_settings_duplicated_assets_clear_button": "मिटवा", - "cache_settings_duplicated_assets_subtitle": "अॅपने वगळलेले फोटो व व्हिडिओ", - "cache_settings_duplicated_assets_title": "{count} डुप्लिकेट फाईल्स", - "cache_settings_statistics_album": "लायब्ररी थंबनेल", - "cache_settings_statistics_full": "पूर्ण प्रतिमा", - "cache_settings_statistics_shared": "शेअर केलेल्या अल्बमचे थंबनेल", - "cache_settings_statistics_thumbnail": "थंबनेल", - "cache_settings_statistics_title": "कॅश वापर", - "cache_settings_subtitle": "Immich अॅपचे कॅशिंग नियंत्रण करा", - "cache_settings_tile_subtitle": "स्थानिक संग्रहण नियंत्रण करा", - "cache_settings_tile_title": "स्थानिक संग्रहण", - "cache_settings_title": "कॅश सेटिंग्ज", - "camera": "कॅमेरा", - "camera_brand": "कॅमेरा ब्रँड", - "camera_model": "कॅमेरा मॉडेल", - "cancel": "रद्द करा", - "cancel_search": "शोध रद्द करा", - "canceled": "रद्द झाले", - "canceling": "रद्द करत आहे", - "cannot_merge_people": "लोक एकत्र करता येणार नाहीत", - "cannot_undo_this_action": "ही क्रिया पूर्ववत करता येणार नाही!", - "cannot_update_the_description": "वर्णन अद्यतनित करता येणार नाही", - "cast": "कास्ट", - "cast_description": "उपलब्ध कास्ट गंतव्ये कॉन्फिगर करा", - "change_date": "तारीख बदला", - "change_description": "वर्णन बदला", - "change_display_order": "प्रदर्शन क्रम बदला", - "change_expiration_time": "समाप्ती वेळ बदला", - "change_location": "स्थान बदला", - "change_name": "नाव बदला", - "change_name_successfully": "नाव यशस्वीरित्या बदलले", - "change_password": "संकेतशब्द बदला", - "change_password_description": "प्रथमच साइन इन किंवा संकेतशब्द बदलण्याची विनंती, खाली नवीन पासवर्ड टाका।", - "change_password_form_confirm_password": "संकेतशब्द पुष्टी करा", - "change_password_form_description": "हाय {name}, \n\nप्रथमच साइन इन किंवा संकेतशब्द बदलण्याची विनंती झाली आहे. खाली नवीन संकेतशब्द प्रविष्ट करा.", - "change_password_form_log_out": "इतर सर्व डिव्हाइसवरून लॉग आऊट करा", - "change_password_form_log_out_description": "इतर सर्व डिव्हाइसेसवरून लॉग आऊट करण्याची शिफारस केली जाते", - "change_password_form_new_password": "नवीन संकेतशब्द", - "change_password_form_password_mismatch": "पासवर्ड जुळत नाहीत", - "change_password_form_reenter_new_password": "नवीन संकेतशब्द पुन्हा प्रविष्ट करा", - "change_pin_code": "PIN कोड बदला", - "change_your_password": "आपला संकेतशब्द बदला", - "changed_visibility_successfully": "दृश्यमानता यशस्वीरित्या बदलली", - "charging": "चार्जिंग", - "charging_requirement_mobile_backup": "बॅकग्राउंड बॅकअपसाठी उपकरण चार्ज होत असणे आवश्यक आहे", - "check_corrupt_asset_backup": "भ्रष्ट फाईल बॅकअप तपासा", - "check_corrupt_asset_backup_button": "तपासणी करा", - "check_corrupt_asset_backup_description": "फक्त Wi-Fi वर हा तपास चालवा आणि सर्व फाईल्स बॅकअप झाल्यावरच. प्रक्रिया काही मिनिटे लागू शकते.", - "check_logs": "लॉग्ज तपासा", - "choose_matching_people_to_merge": "विलीन करण्यासाठी जुळणारे लोक निवडा", - "city": "शहर", - "clear": "साफ करा", - "clear_all": "सर्व साफ करा", - "clear_all_recent_searches": "सर्व शोध इतिहास मिटवा", - "clear_file_cache": "फाईल कॅश मिटवा", - "clear_message": "संदेश मिटवा", - "clear_value": "मूल्य मिटवा", - "client_cert_dialog_msg_confirm": "ठीक आहे", - "client_cert_enter_password": "संकेतशब्द टाका", - "client_cert_import": "आयात करा", - "client_cert_import_success_msg": "क्लायंट प्रमाणपत्र आयात झाले", - "client_cert_invalid_msg": "अवैध प्रमाणपत्र फाईल किंवा चुकीचा संकेतशब्द", - "client_cert_remove_msg": "क्लायंट प्रमाणपत्र काढून टाकले", - "client_cert_subtitle": "फक्त PKCS12 (.p12, .pfx) फॉरमॅटला समर्थन आहे. सर्टिफिकेट आयात/काढणे फक्त लॉगिनपूर्वी उपलब्ध आहे", - "client_cert_title": "SSL क्लायंट प्रमाणपत्र [प्रायोगिक]", - "clockwise": "तासाच्या दिशेने", - "close": "बंद करा", - "collapse": "संकुचित करा", - "collapse_all": "सर्व संकुचित करा", - "color": "रंग", - "color_theme": "रंग थीम", - "comment_deleted": "टिप्पणी हटवली", - "comment_options": "टिप्पणी पर्याय", - "comments_and_likes": "टिप्पण्या & लाईक्स", - "comments_are_disabled": "टिप्पण्या अक्षम आहेत", - "common_create_new_album": "नवीन अल्बम तयार करा", - "completed": "पूर्ण झाले", - "confirm": "पुष्टी करा", - "confirm_admin_password": "ऍडमिन संकेतशब्द पुष्टी करा", - "confirm_delete_face": "तुम्हाला {name} चे चेहरा या फाईलमधून हटवायचे आहे का?", - "confirm_delete_shared_link": "तुम्हाला हा शेअर लिंक हटवायचा आहे का?", - "confirm_keep_this_delete_others": "या फाईल व्यतिरिक्त इतर सर्व फाईल्स हटवल्या जातील. पुढे सुरू ठेवायचे आहे का?", - "confirm_new_pin_code": "नवीन PIN कोड पुष्टी करा", - "confirm_password": "संकेतशब्द पुष्टी करा", - "confirm_tag_face": "तुम्हाला हा चेहरा {name} म्हणून टॅग करायचा आहे का?", - "confirm_tag_face_unnamed": "तुम्हाला हा चेहरा टॅग करायचा आहे का?", - "connected_device": "कनेक्ट झालेले उपकरण", - "connected_to": "शी कनेक्ट केले", - "contain": "समाविष्ट करा", - "context": "संदर्भ", - "continue": "पुढे", - "control_bottom_app_bar_create_new_album": "नवीन अल्बम तयार करा", - "control_bottom_app_bar_delete_from_immich": "Immich मधून हटवा", - "control_bottom_app_bar_delete_from_local": "उपकरणातून हटवा", - "control_bottom_app_bar_edit_location": "स्थान संपादित करा", - "control_bottom_app_bar_edit_time": "तारीख व वेळ संपादित करा", - "control_bottom_app_bar_share_link": "लिंक शेअर करा", - "control_bottom_app_bar_share_to": "येथे शेअर करा", - "control_bottom_app_bar_trash_from_immich": "ट्रॅशमध्ये हलवा", - "copied_image_to_clipboard": "प्रतिमा क्लिपबोर्डवर कॉपी केली।", - "copied_to_clipboard": "क्लिपबोर्डवर कॉपी झाले!", - "copy_error": "कॉपी करताना त्रुटी", - "copy_file_path": "फाईलचा मार्ग कॉपी करा", - "copy_image": "प्रतिमा कॉपी करा", - "copy_link": "लिंक कॉपी करा", - "copy_link_to_clipboard": "लिंक क्लिपबोर्डवर कॉपी करा", - "copy_password": "संकेतशब्द कॉपी करा", - "copy_to_clipboard": "क्लिपबोर्डवर कॉपी करा", - "country": "देश", - "cover": "आवरण", - "covers": "आवरणे", - "create": "तयार करा", - "create_album": "अल्बम तयार करा", - "create_album_page_untitled": "शीर्षकेतर", - "create_api_key": "API की तयार करा", - "create_library": "लायब्ररी तयार करा", - "create_link": "लिंक तयार करा", - "create_link_to_share": "शेअर करण्यासाठी लिंक तयार करा", - "create_link_to_share_description": "लिंक असलेल्या कोणालाही निवडलेल्या फोटो पाहू द्या", - "create_new": "नवीन तयार करा", - "create_new_person": "नवीन व्यक्ती तयार करा", - "create_new_person_hint": "निवडलेल्या फाईल्स नवीन व्यक्तीशी जोडा", - "create_new_user": "नवीन वापरकर्ता तयार करा", - "create_shared_album_page_share_add_assets": "फाईल्स जोडा", - "create_shared_album_page_share_select_photos": "फोटो निवडा", - "create_shared_link": "शेअर लिंक तयार करा", - "create_tag": "टॅग तयार करा", - "create_tag_description": "नवीन टॅग तयार करा. सबटॅगसाठी पूर्ण पाथसहित नाव टाका।", - "create_user": "वापरकर्ता तयार करा", - "created": "तयार केले", - "created_at": "निर्मिती तारीख", - "creating_linked_albums": "लिंक केलेले अल्बम तयार करत आहे...", - "crop": "छाटणी करा", - "curated_object_page_title": "गोष्टी", - "current_device": "वर्तमान उपकरण", - "current_pin_code": "चालू PIN कोड", - "current_server_address": "सर्व्हर पत्ता", - "custom_locale": "भाषा व क्षेत्र", - "custom_locale_description": "दिनांक व संख्या भाषेनुसार व क्षेत्रानुसार format करा", - "custom_url": "सानुकूल URL", - "daily_title_text_date": "ई, एमएमएम डीडी", - "daily_title_text_date_year": "ई, एमएमएम दिवस, वर्ष", - "dark": "डार्क", - "dark_theme": "डार्क थीम बदल", - "date": "तारीख", - "date_after": "नंतरची तारीख", - "date_and_time": "दिनांक व वेळ", - "date_before": "पूर्वची तारीख", - "date_format": "ई, एलएलएल डी, वाई • एच:एमएम ए", - "date_of_birth_saved": "जन्मतारीख जतन झाली", - "date_range": "तारीख श्रेणी", - "day": "दिवस", - "days": "अनेक दिवस", - "deduplicate_all": "सर्व डुप्लिकेट काढा", - "deduplication_criteria_1": "प्रतिमेचा आकार (बाइट्स)", - "deduplication_criteria_2": "EXIF डेटा प्रमाण", - "deduplication_info": "डुप्लिकेट निवारण माहिती", - "deduplication_info_description": "डुप्लिकेट स्वयंचलितपणे निवडून काढण्यासाठी खालील निकष वापरले जातात:", - "default_locale": "पूर्वनिर्धारित भाषा", - "default_locale_description": "तुमच्या ब्राउझरच्या भाषा-परिसरानुसार दिनांक व संख्या स्वरूपित करा", - "delete": "हटवा", - "delete_action_confirmation_message": "तुम्हाला ही फाईल हटवायची आहे का? ही क्रिया सर्व्हरच्या ट्रॅशमध्ये हलवेल आणि स्थानिकपणे हटवायचे का ते विचारेल", - "delete_action_prompt": "{count} हटवले", - "delete_album": "अल्बम हटवा", - "delete_api_key_prompt": "तुम्हाला हा API की हटवायची आहे का?", - "delete_dialog_alert": "ही फाईल्स Immich आणि तुमच्या उपकरणावरून कायमस्वरूपी हटवल्या जातील", - "delete_dialog_alert_local": "ही फाईल्स तुमच्या उपकरणावरून कायमविशिष्टपणे हटवल्या जातील, परंतु Immich सर्व्हरवर उपलब्ध राहतील", - "delete_dialog_alert_local_non_backed_up": "काही फाईल्स Immich वर बॅकअप केलेल्या नाहीत आणि तुमच्या उपकरणावरून कायमविशिष्टपणे हटवल्या जातील", - "delete_dialog_alert_remote": "ही फाईल्स Immich सर्व्हरवरून कायमस्वरूपी हटवल्या जातील", - "delete_dialog_ok_force": "तरीही हटवा", - "delete_dialog_title": "कायमस्वरूपी हटवा", - "delete_duplicates_confirmation": "तुम्हाला हे डुप्लिकेट कायमस्वरूपी हटवायचे आहेत का?", - "delete_face": "चेहरा हटवा", - "delete_key": "की हटवा", - "delete_library": "लायब्ररी हटवा", - "delete_link": "लिंक हटवा", - "delete_local_action_prompt": "{count} स्थानिकपणे हटवले", - "delete_local_dialog_ok_backed_up_only": "फक्त बॅकअप झालेले हटवा", - "delete_local_dialog_ok_force": "तरीही हटवा", - "delete_others": "इतर हटवा", - "delete_permanently": "कायमस्वरूपी हटवा", - "delete_permanently_action_prompt": "{count} कायमस्वरूपी हटवले", - "delete_shared_link": "शेअर लिंक हटवा", - "delete_shared_link_dialog_title": "शेअर लिंक हटवा", - "delete_tag": "टॅग हटवा", - "delete_tag_confirmation_prompt": "तुम्हाला {tagName} टॅग हटवायचा आहे का?", - "delete_user": "वापरकर्ता हटवा", - "deleted_shared_link": "शेअर लिंक हटवले", - "deletes_missing_assets": "डिस्कवर नसलेली फाईल्स हटवा", - "description": "वर्णन", - "description_input_hint_text": "वर्णन जोडा…", - "description_input_submit_error": "वर्णन अद्यतनित करताना त्रुटी - तपशीलांसाठी लॉग तपासा", - "deselect_all": "सर्व निवड रद्द करा", - "details": "तपशील", - "direction": "दिशा", - "disabled": "अक्षम", - "disallow_edits": "संपादन अक्षम करा", - "discord": "डिस्कॉर्ड", - "discover": "शोधा", - "discovered_devices": "शोधिलेले उपकरणे", - "dismiss_all_errors": "सर्व त्रुटी मिटवा", - "dismiss_error": "त्रुटी मिटवा", - "display_options": "प्रदर्शन पर्याय", - "display_order": "प्रदर्शन क्रम", - "display_original_photos": "मूळ फोटो दाखवा", - "display_original_photos_setting_description": "मूळ फाईल वेब-सुसंगत असल्यास थंबनेलऐवजी मूळ फोटो दाखवा. यामुळे उघडण्यास थोडा वेळ लागू शकतो.", - "do_not_show_again": "पुन्हा दाखवू नका", - "documentation": "दस्तऐवजीकरण", - "done": "पूर्ण", - "download": "डाउनलोड करा", - "download_action_prompt": "{count} फाईल्स डाउनलोड करत आहे", - "download_canceled": "डाउनलोड रद्द झाले", - "download_complete": "डाउनलोड पूर्ण झाले", - "download_enqueue": "डाउनलोड रांकेत जोडले", - "download_error": "डाउनलोड त्रुटी", - "download_failed": "डाउनलोड अयशस्वी", - "download_finished": "डाउनलोड पूर्ण झाले", - "download_include_embedded_motion_videos": "एम्बेड केलेली व्हिडिओ", - "download_include_embedded_motion_videos_description": "मोशन फोटोमधील एम्बेड केलेली व्हिडिओ स्वतंत्र फाईल म्हणून समाविष्ट करा", - "download_notfound": "डाउनलोड आढळला नाही", - "download_paused": "डाउनलोड थांबवला", - "download_settings": "डाउनलोड सेटिंग्ज", - "download_settings_description": "फाईल डाउनलोड संबंधित सेटिंग्ज व्यवस्थापित करा", - "download_started": "डाउनलोड सुरू झाला", - "download_sucess": "डाउनलोड यशस्वी", - "download_sucess_android": "मीडिया DCIM/Immich मध्ये डाउनलोड झाला आहे", - "download_waiting_to_retry": "पुन्हा प्रयत्न करण्याची प्रतीक्षा", - "downloading": "डाउनलोड करत आहे", - "downloading_asset_filename": "{filename} डाउनलोड करत आहे", - "downloading_media": "मीडिया डाउनलोड करत आहे", - "drop_files_to_upload": "अपलोडसाठी फाईल्स इथे ड्रॉप करा", - "duplicates": "डुप्लिकेट्स", - "duplicates_description": "प्रत्येक गटातले डुप्लिकेट फाईल्स निवडा", - "duration": "कालावधी", - "edit": "संपादित करा", - "edit_album": "अल्बम संपादित करा", - "edit_avatar": "अवतार संपादित करा", - "edit_birthday": "वाढदिवस संपादित करा", - "edit_date": "तारीख संपादित करा", - "edit_date_and_time": "तारीख व वेळ संपादित करा", - "edit_date_and_time_action_prompt": "{count} तारीख आणि वेळ संपादित झाले", - "edit_date_and_time_by_offset": "ऑफसेटनुसार तारीख बदला", - "edit_date_and_time_by_offset_interval": "नवीन तारीख श्रेणी: {from}–{to}", - "edit_description": "वर्णन संपादित करा", - "edit_description_prompt": "नवीन वर्णन निवडा:", - "edit_exclusion_pattern": "वगळा पॅटर्न संपादित करा", - "edit_faces": "चेहऱ्यांवर संपादन करा", - "edit_key": "की संपादित करा", - "edit_link": "लिंक संपादित करा", - "edit_location": "स्थान संपादित करा", - "edit_location_action_prompt": "{count} स्थान संपादित झाले", - "edit_location_dialog_title": "स्थान", - "edit_name": "नाव संपादित करा", - "edit_people": "लोक संपादित करा", - "edit_tag": "टॅग संपादित करा", - "edit_title": "शीर्षक संपादित करा", - "edit_user": "वापरकर्ता संपादित करा", - "editor": "एडिटर", - "editor_close_without_save_prompt": "बदल जतन होणार नाही", - "editor_close_without_save_title": "एडिटर बंद करायचा का?", - "email": "ईमेल", - "email_notifications": "ईमेल सूचना", - "empty_folder": "हा फोल्डर रिकामा आहे", - "empty_trash": "ट्रॅश रिकामी करा", - "empty_trash_confirmation": "तुम्हाला ट्रॅश रिकामी करायची आहे का? यामुळे ट्रॅशमधील सर्व फाईल्स Immich वरून कायमस्वरूपी हटवली जातील. \nही क्रिया पूर्ववत करता येणार नाही!", - "enable": "सक्षम करा", - "enable_backup": "बॅकअप सक्षम करा", - "enable_biometric_auth_description": "बायोमेट्रिक प्रमाणीकरण सक्षम करण्यासाठी PIN कोड टाका", - "enabled": "सक्षम आहे", - "end_date": "समाप्ती तारीख", - "enqueued": "रांकेत जोडले", - "enter_wifi_name": "Wi-Fi नाव टाका", - "enter_your_pin_code": "PIN कोड टाका", - "enter_your_pin_code_subtitle": "लॉक केलेला फोल्डर उघडण्यासाठी PIN कोड टाका", - "error": "त्रुटी", - "error_change_sort_album": "अल्बम क्रम बदलण्यात अयशस्वी", - "error_delete_face": "फाईलमधून चेहरा हटवताना त्रुटी", - "error_getting_places": "ठिकाणे मिळवताना त्रुटी", - "error_loading_image": "प्रतिमा लोड करताना त्रुटी", - "error_loading_partners": "पार्टनर्स लोड करताना त्रुटी: {error}", - "error_saving_image": "त्रुटी: {error}", - "error_tag_face_bounding_box": "चेहरा टॅग करताना त्रुटी – बाउंडिंग बॉक्स निर्देशांक मिळवता येत नाहीत", - "error_title": "त्रुटी – काहीतरी चुकले", - "errors": { - "cannot_navigate_next_asset": "पुढील फाईलवर जाऊ शकत नाही", - "cannot_navigate_previous_asset": "मागील फाईलवर जाऊ शकत नाही", - "cant_apply_changes": "बदल लागू करता येत नाही", - "cant_change_activity": "क्रिया {enabled, select, true {निष्क्रिय} other {सक्रिय}} करू शकत नाही", - "cant_change_asset_favorite": "फाईलसाठी आवड बदलता येत नाही", - "cant_change_metadata_assets_count": "{count, plural, one {# अॅसेटचे मेटाडेटा बदलता येत नाही} other {# अॅसेट्सचे मेटाडेटा बदलता येत नाही}}", - "cant_get_faces": "चेहऱ्यांची माहिती मिळवता येत नाही", - "cant_get_number_of_comments": "टिप्पण्यांची संख्या मिळवता येत नाही", - "cant_search_people": "लोक शोधता येत नाही", - "cant_search_places": "ठिकाणे शोधता येत नाही", - "error_adding_assets_to_album": "अल्बममध्ये फाईल्स जोडताना त्रुटी", - "error_adding_users_to_album": "अल्बममध्ये वापरकर्ते जोडताना त्रुटी", - "error_deleting_shared_user": "शेअर केलेला वापरकर्ता हटवताना त्रुटी", - "error_downloading": "{filename} डाउनलोड करताना त्रुटी", - "error_hiding_buy_button": "खरेदी बटण लपवताना त्रुटी", - "error_removing_assets_from_album": "अल्बममधून फाईल्स हटवताना त्रुटी – अधिक तपशीलांसाठी कन्सोल पहा", - "error_selecting_all_assets": "सर्व फाईल्स निवडताना त्रुटी", - "exclusion_pattern_already_exists": "हे वगळण्याचे पॅटर्न आधीच अस्तित्वात आहे।", - "failed_to_create_album": "अल्बम तयार करण्यात अयशस्वी", - "failed_to_create_shared_link": "शेअर लिंक तयार करण्यात अयशस्वी", - "failed_to_edit_shared_link": "शेअर लिंक संपादित करण्यात अयशस्वी", - "failed_to_get_people": "लोक मिळवण्यात अयशस्वी", - "failed_to_keep_this_delete_others": "ही फाईल ठेवून इतर फाईल्स हटवताना अयशस्वी", - "failed_to_load_asset": "फाईल लोड करण्यात अयशस्वी", - "failed_to_load_assets": "फाईल्स लोड करण्यात अयशस्वी", - "failed_to_load_notifications": "सूचना लोड करण्यात अयशस्वी", - "failed_to_load_people": "लोक लोड करण्यात अयशस्वी", - "failed_to_remove_product_key": "उत्पादन की काढून टाकण्यात अयशस्वी", - "failed_to_reset_pin_code": "PIN कोड रीसेट करण्यात अयशस्वी", - "failed_to_stack_assets": "फाईल्स एकत्र करता आल्या नाहीत", - "failed_to_unstack_assets": "फाईल्स विभाजित करता आल्या नाहीत", - "failed_to_update_notification_status": "सूचना स्थिती अपडेट करण्यात अयशस्वी", - "incorrect_email_or_password": "चुकीचा ईमेल किंवा संकेतशब्द", - "library_folder_already_exists": "हा इम्पोर्ट पथ आधीच अस्तित्वात आहे.", - "paths_validation_failed": "{paths, plural, one {एक मार्ग वैध नाही} other {# मार्ग वैध नाहीत}}", - "profile_picture_transparent_pixels": "प्रोफाइल चित्रात पारदर्शक पिक्सेल असू शकत नाहीत. कृपया झूम करा किंवा प्रतिमा हलवा.", - "quota_higher_than_disk_size": "तुम्ही डिस्क आकारापेक्षा मोठा कोटा सेट केला आहे", - "something_went_wrong": "काहीतरी चूक झाली", - "unable_to_add_album_users": "अल्बममध्ये वापरकर्ते जोडता आले नाहीत", - "unable_to_add_assets_to_shared_link": "शेअर लिंकमध्ये फाईल्स जोडता आले नाहीत", - "unable_to_add_comment": "टिप्पणी जोडता आले नाही", - "unable_to_add_exclusion_pattern": "वगळण्याचे पॅटर्न जोडता आले नाही", - "unable_to_add_partners": "सहयोगी जोडता आले नाहीत", - "unable_to_add_remove_archive": "{archived, select, true {आर्काइव्हमधून अॅसेट काढता आले नाही} other {आर्काइव्हमध्ये अॅसेट जोडता आले नाही}}", - "unable_to_add_remove_favorites": "{favorite, select, true {आवडत्यांमध्ये अॅसेट जोडता आले नाही} other {आवडत्यांतून अॅसेट काढता आले नाही}}", - "unable_to_archive_unarchive": "{archived, select, true{आर्काइव्ह करता आले नाही} other{अनआर्काइव्ह करता आले नाही}}", - "unable_to_change_album_user_role": "अल्बममधील वापरकर्त्याची भूमिका बदलता आले नाही", - "unable_to_change_date": "तारीख बदलता आले नाही", - "unable_to_change_description": "वर्णन बदलता आले नाही", - "unable_to_change_favorite": "फाईलसाठी आवडत्या बदलता आले नाही", - "unable_to_change_location": "स्थान बदलता आले नाही", - "unable_to_change_password": "संकेतशब्द बदलता आले नाही", - "unable_to_change_visibility": "{count, plural, one{एक व्यक्तीची दृश्यमानता बदलता आली नाही} other{# व्यक्तींची दृश्यमानता बदलता आली नाही}}", - "unable_to_complete_oauth_login": "OAuth लॉगिन पूर्ण करता आले नाही", - "unable_to_connect": "कनेक्ट करता आले नाही", - "unable_to_copy_to_clipboard": "क्लिपबोर्डवर कॉपी करता आले नाही, https द्वारे प्रवेश केल्याची खात्री करा", - "unable_to_create_admin_account": "ऍडमिन खाते तयार करता आले नाही", - "unable_to_create_api_key": "नवीन API की तयार करता आले नाही", - "unable_to_create_library": "लायब्ररी तयार करता आले नाही", - "unable_to_create_user": "वापरकर्ता तयार करता आले नाही", - "unable_to_delete_album": "अल्बम हटवता आले नाही", - "unable_to_delete_asset": "फाईल हटवता आली नाही", - "unable_to_delete_assets": "फाईल्स हटवताना त्रुटी", - "unable_to_delete_exclusion_pattern": "वगळणी पॅटर्न हटवता आला नाही", - "unable_to_delete_shared_link": "शेअर लिंक हटवता आला नाही", - "unable_to_delete_user": "वापरकर्ता हटवता आला नाही", - "unable_to_download_files": "फाईल्स डाउनलोड करता आल्या नाहीत", - "unable_to_edit_exclusion_pattern": "वगळणी पॅटर्न संपादित करता आला नाही", - "unable_to_empty_trash": "ट्रॅश रिकामा करता आला नाही", - "unable_to_enter_fullscreen": "फुलस्क्रीन मोडमध्ये जाऊ शकत नाही", - "unable_to_exit_fullscreen": "फुलस्क्रीन मोडमधून बाहेर पडता आला नाही", - "unable_to_get_comments_number": "टिप्पण्यांची संख्या मिळवता आली नाही", - "unable_to_get_shared_link": "शेअर लिंक मिळवता आली नाही", - "unable_to_hide_person": "व्यक्ती लपवता आला नाही", - "unable_to_link_motion_video": "मोशन व्हिडिओ लिंक करता आला नाही", - "unable_to_link_oauth_account": "OAuth खाते लिंक करता आले नाही", - "unable_to_log_out_all_devices": "सर्व उपकरणांमधून लॉगआउट करता आले नाही", - "unable_to_log_out_device": "उपकरणावरून लॉगआउट करता आले नाही", - "unable_to_login_with_oauth": "OAuth द्वारे लॉगिन करता आले नाही", - "unable_to_play_video": "व्हिडिओ प्ले करता आले नाही", - "unable_to_reassign_assets_existing_person": "{name} या व्यक्तीकडे फाईल्स पुनः नेमता आले नाहीत", - "unable_to_reassign_assets_new_person": "फाईल्स नवीन व्यक्तीकडे पुनः नेमता आले नाहीत", - "unable_to_refresh_user": "वापरकर्ता रिफ्रेश करता आला नाही", - "unable_to_remove_album_users": "अल्बममधून वापरकर्ते हटवता आले नाही", - "unable_to_remove_api_key": "API की काढून टाकता आले नाही", - "unable_to_remove_assets_from_shared_link": "शेअर लिंकमधील फाईल्स हटवता आले नाही", - "unable_to_remove_library": "लायब्ररी हटवता आले नाही", - "unable_to_remove_partner": "भागीदार हटवता आला नाही", - "unable_to_remove_reaction": "प्रतिक्रिया हटवता आली नाही", - "unable_to_reset_password": "संकेतशब्द रीसेट करता आला नाही", - "unable_to_reset_pin_code": "PIN कोड रीसेट करता आला नाही", - "unable_to_resolve_duplicate": "डुप्लिकेट सोडवता आले नाही", - "unable_to_restore_assets": "फाईल्स पुनर्संचयित करता आले नाहीत", - "unable_to_restore_trash": "ट्रॅश पुनर्संचयित करता आले नाही", - "unable_to_restore_user": "वापरकर्ता पुनर्संचयित करता आला नाही", - "unable_to_save_album": "अल्बम जतन करता आले नाही", - "unable_to_save_api_key": "API की जतन करता आली नाही", - "unable_to_save_date_of_birth": "जन्मतारीख जतन करता आली नाही", - "unable_to_save_name": "नाव जतन करता आले नाही", - "unable_to_save_profile": "प्रोफाइल जतन करता आला नाही", - "unable_to_save_settings": "सेटिंग्ज जतन करता आले नाहीत", - "unable_to_scan_libraries": "लायब्ररी स्कॅन करता आले नाहीत", - "unable_to_scan_library": "लायब्ररी स्कॅन करता आला नाही", - "unable_to_set_feature_photo": "वैशिष्ट्य फोटो सेट करता आला नाही", - "unable_to_set_profile_picture": "प्रोफाइल चित्र सेट करता आला नाही", - "unable_to_submit_job": "काम सबमिट करता आले नाही", - "unable_to_trash_asset": "फाईल ट्रॅश करता आले नाही", - "unable_to_unlink_account": "खाते अनलिंक करता आले नाही", - "unable_to_unlink_motion_video": "मोशन व्हिडिओ अनलिंक करता आले नाही", - "unable_to_update_album_cover": "अल्बम कव्हर अद्यतनित करता आले नाही", - "unable_to_update_album_info": "अल्बम माहिती अद्यतनित करता आले नाही", - "unable_to_update_library": "लायब्ररी अद्यतनित करता आले नाही", - "unable_to_update_location": "स्थान अद्यतनित करता आले नाही", - "unable_to_update_settings": "सेटिंग्ज अद्यतनित करता आले नाही", - "unable_to_update_timeline_display_status": "टाइमलाइन स्थिती अद्यतनित करता आले नाही", - "unable_to_update_user": "वापरकर्ता अद्यतनित करता आले नाही", - "unable_to_upload_file": "फाईल अपलोड करता आले नाही" - }, - "exclusion_pattern": "वगळण्याचा नमुना", - "exif": "एक्सिफ", - "exif_bottom_sheet_description": "वर्णन जोडा…", - "exif_bottom_sheet_description_error": "वर्णन अद्यतनित करताना त्रुटी", - "exif_bottom_sheet_details": "तपशील", - "exif_bottom_sheet_location": "स्थान", - "exif_bottom_sheet_no_description": "वर्णन नाही", - "exif_bottom_sheet_people": "लोक", - "exif_bottom_sheet_person_add_person": "नाव जोडा", - "exit_slideshow": "स्लाइडशो बंद करा", - "expand_all": "सर्व विस्तार करा", - "experimental_settings_new_asset_list_subtitle": "काम सुरू आहे", - "experimental_settings_new_asset_list_title": "प्रायोगिक फोटो ग्रिड चालू करा", - "experimental_settings_subtitle": "स्वतःच्या जबाबदारीवर वापरा!", - "experimental_settings_title": "प्रायोगिक", - "expire_after": "नंतर कालबाह्य", - "expired": "कालबाह्य", - "expires_date": "{date} ला कालबाह्य", - "explore": "अन्वेषण करा", - "explorer": "एक्सप्लोरर", - "export": "निर्यात करा", - "export_as_json": "JSON म्हणून निर्यात करा", - "export_database": "डेटाबेस निर्यात करा", - "export_database_description": "SQLite डेटाबेस निर्यात करा", - "extension": "एक्स्टेंशन", - "external": "बाह्य", - "external_libraries": "बाह्य लायब्ररी", - "external_network": "बाह्य नेटवर्क", - "external_network_sheet_info": "प्राधान्य Wi-Fi नसेल तर, अॅप खालील URL वरून वरच्या क्रमाने तपासून पहिल्या पोहोचणाऱ्या URL ने सर्व्हरशी जोडेल", - "face_unassigned": "नाव दिलेले नाही", - "failed": "अयशस्वी", - "failed_to_authenticate": "प्रमाणीकरण अयशस्वी", - "failed_to_load_assets": "फाईल्स लोड करण्यात अयशस्वी", - "failed_to_load_folder": "फोल्डर लोड करण्यात अयशस्वी", - "favorite": "आवडते", - "favorite_action_prompt": "{count} आवडींमध्ये जोडले", - "favorite_or_unfavorite_photo": "फोटो ‘आवडते’ करा किंवा काढा", - "favorites": "आवडी", - "favorites_page_no_favorites": "आवडीतील फाईल्स सापडल्या नाहीत", - "feature_photo_updated": "फीचर फोटो अद्यतनित झाला", - "features": "वैशिष्ट्ये", - "features_in_development": "विकासाधीन वैशिष्ट्ये", - "features_setting_description": "अॅपची वैशिष्ट्ये व्यवस्थापित करा", - "file_name_or_extension": "फाईल नाव किंवा एक्स्टेंशन", - "file_size": "फाइल साइज़", - "filename": "फाइलनाव", - "filetype": "फाईल प्रकार", - "filter": "फिल्टर", - "filter_people": "लोक फिल्टर करा", - "filter_places": "ठिकाणे फिल्टर करा", - "find_them_fast": "नावाने पटकन शोधा", - "first": "प्रथम", - "fix_incorrect_match": "चुकीची जुळणी दुरुस्त करा", - "folder": "फोल्डर", - "folder_not_found": "फोल्डर सापडला नाही", - "folders": "फोल्डर्स", - "folders_feature_description": "फाईल सिस्टीमवरील फोटो-व्हिडिओ फोल्डर दृश्यात ब्राउझ करा", - "forgot_pin_code_question": "PIN विसरलात?", - "forward": "पुढे", - "full_path": "पूर्ण पाथ: {path}", - "gcast_enabled": "गूगल कास्ट", - "gcast_enabled_description": "ही सुविधा चालण्यासाठी Google कडील बाह्य संसाधने लोड करते.", - "general": "सामान्य", - "geolocation_instruction_location": "GPS निर्देशांक असलेल्या मीडिया घटकावर क्लिक करून त्याचे स्थान वापरा, किंवा थेट नकाशावरून स्थान निवडा", - "get_help": "मदत घ्या", - "get_wifiname_error": "Wi-Fi चे नाव मिळाले नाही. आवश्यक परवानग्या दिल्या आहेत आणि Wi-Fi नेटवर्कशी जोडले आहात याची खात्री करा", - "getting_started": "सुरुवात करा", - "go_back": "मागे जा", - "go_to_folder": "फोल्डरकडे जा", - "go_to_search": "शोधाकडे जा", - "gps": "जीपीएस", - "gps_missing": "GPS उपलब्ध नाही", - "grant_permission": "परवानगी द्या", - "group_albums_by": "अल्बम गटबद्ध करा: …", - "group_country": "देशानुसार गट करा", - "group_no": "गटबद्ध नाही", - "group_owner": "मालकानुसार गट करा", - "group_places_by": "स्थळे गटबद्ध करा: …", - "group_year": "वर्षानुसार गटबद्ध करा", - "haptic_feedback_switch": "हॅप्टिक फीडबॅक सक्षम करा", - "haptic_feedback_title": "हॅप्टिक फीडबॅक", - "has_quota": "कोटा आहे", - "hash_asset": "मीडिया घटकाचा हॅश तयार करा", - "hashed_assets": "हॅश केलेले मीडिया घटक", - "hashing": "हॅशिंग", - "header_settings_add_header_tip": "हेडर जोडा", - "header_settings_field_validator_msg": "मूल्य रिकामे असू शकत नाही", - "header_settings_header_name_input": "हेडरचे नाव", - "header_settings_header_value_input": "हेडरचे मूल्य", - "headers_settings_tile_title": "सानुकूल प्रॉक्सी हेडर्स", - "hi_user": "नमस्कार {name} ({email})", - "hide_all_people": "सर्व व्यक्ती लपवा", - "hide_gallery": "गॅलरी लपवा", - "hide_named_person": "व्यक्ती {name} लपवा", - "hide_password": "संकेतशब्द लपवा", - "hide_person": "व्यक्ती लपवा", - "hide_text_recognition": "मजकूर ओळख लपवा", - "hide_unnamed_people": "नाव नसलेल्या व्यक्ती लपवा", - "home_page_add_to_album_conflicts": "अल्बम {album} मध्ये {added} मीडिया घटक जोडले. {failed} मीडिया घटक आधीच त्या अल्बममध्ये आहेत.", - "home_page_add_to_album_err_local": "स्थानिक मीडिया घटक अजून अल्बममध्ये जोडता येत नाहीत, वगळत आहे", - "home_page_add_to_album_success": "अल्बम {album} मध्ये {added} मीडिया घटक जोडले.", - "home_page_album_err_partner": "भागीदारचे मीडिया घटक अजून अल्बममध्ये जोडता येत नाहीत, वगळत आहे", - "home_page_archive_err_local": "स्थानिक मीडिया घटक अजून संग्रहित करता येत नाहीत, वगळत आहे", - "home_page_archive_err_partner": "भागीदारचे मीडिया घटक संग्रहित करता येत नाहीत, वगळत आहे", - "home_page_building_timeline": "टाइमलाइन तयार करत आहे", - "home_page_delete_err_partner": "भागीदारचे मीडिया घटक हटवता येत नाहीत, वगळत आहे", - "home_page_delete_remote_err_local": "दूरस्थ हटवण्याच्या निवडीत स्थानिक मीडिया घटक आहेत, वगळत आहे", - "home_page_favorite_err_local": "स्थानिक मीडिया घटकांना अजून आवडीमध्ये जोडता येत नाही, वगळत आहे", - "home_page_favorite_err_partner": "भागीदारचे मीडिया घटक अजून आवडीमध्ये जोडता येत नाहीत, वगळत आहे", - "home_page_first_time_notice": "अ‍ॅप प्रथमच वापरत असाल तर टाइमलाइनमध्ये फोटो आणि व्हिडिओ भरण्यासाठी कृपया बॅकअप अल्बम निवडा", - "home_page_locked_error_local": "स्थानिक मीडिया घटक लॉक केलेल्या फोल्डरमध्ये हलवता येत नाहीत, वगळत आहे", - "home_page_locked_error_partner": "भागीदारचे मीडिया घटक लॉक केलेल्या फोल्डरमध्ये हलवता येत नाहीत, वगळत आहे", - "home_page_share_err_local": "स्थानिक मीडिया घटक लिंकद्वारे शेअर करता येत नाहीत, वगळत आहे", - "home_page_upload_err_limit": "एकावेळी कमाल 30 मीडिया घटकच अपलोड करता येतात, वगळत आहे", - "host": "होस्ट", - "hour": "तास", - "hours": "तास", - "id": "ID", - "idle": "निष्क्रिय", - "ignore_icloud_photos": "iCloud वरील फोटो दुर्लक्षित करा", - "ignore_icloud_photos_description": "iCloud वर साठवलेले फोटो Immich सर्व्हरवर अपलोड केले जाणार नाहीत", - "image": "फोटो", - "image_alt_text_date": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {date} ला घेतले", - "image_alt_text_date_1_person": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1} सोबत {date} ला घेतले", - "image_alt_text_date_2_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1} आणि {person2} सोबत {date} ला घेतले", - "image_alt_text_date_3_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1}, {person2} आणि {person3} सोबत {date} ला घेतले", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {person1}, {person2} आणि आणखी {additionalCount, number} जणांसोबत {date} ला घेतले", - "image_alt_text_date_place": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {date} ला घेतले", - "image_alt_text_date_place_1_person": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {person1} सोबत {date} ला घेतले", - "image_alt_text_date_place_2_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {person1} आणि {person2} सोबत {date} रोजी घेतलेला", - "image_alt_text_date_place_3_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {person1}, {person2} आणि {person3} सोबत {date} रोजी घेतलेला", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {व्हिडिओ} other {फोटो}} {city}, {country} येथे {person1}, {person2} आणि इतर {additionalCount, number} जणांसोबत {date} रोजी घेतलेला", - "image_saved_successfully": "प्रतिमा जतन केली", - "image_viewer_page_state_provider_download_started": "डाउनलोड सुरू झाले", - "image_viewer_page_state_provider_download_success": "डाउनलोड यशस्वी झाले", - "image_viewer_page_state_provider_share_error": "शेअर करताना त्रुटी", - "immich_logo": "Immich लोगो", - "immich_web_interface": "Immich वेब इंटरफेस", - "import_from_json": "JSON मधून आयात करा", - "import_path": "इम्पोर्ट पाथ", - "in_albums": "{count, plural, one {# album} other {# albums}} मध्ये", - "in_archive": "आर्काइव्हमध्ये", - "in_year": "{year} मध्ये", - "in_year_selector": "मध्ये", - "include_archived": "आर्काइव्ह केलेले समाविष्ट करा", - "include_shared_albums": "शेअर्ड अल्बम समाविष्ट करा", - "include_shared_partner_assets": "भागीदाराचे शेअर्ड अॅसेट्स समाविष्ट करा", - "individual_share": "वैयक्तिक शेअर", - "individual_shares": "वैयक्तिक शेअर्स", - "info": "माहिती", - "interval": { - "day_at_onepm": "दररोज दुपारी १ वाजता", - "hours": "दर {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "दररोज मध्यरात्री", - "night_at_twoam": "दररोज रात्री २ वाजता" - }, - "invalid_date": "अवैध तारीख", - "invalid_date_format": "अवैध तारीख स्वरूप", - "invite_people": "लोकांना आमंत्रित करा", - "invite_to_album": "अल्बममध्ये आमंत्रित करा", - "ios_debug_info_fetch_ran_at": "फेच {dateTime} ला चालले", - "ios_debug_info_last_sync_at": "शेवटचे समक्रमण {dateTime} ला", - "ios_debug_info_no_processes_queued": "कोणत्याही पार्श्वभूमी प्रक्रिया प्रतीक्षेत नाहीत", - "ios_debug_info_no_sync_yet": "अजून कोणताही पार्श्वभूमी सिंक जॉब चाललेला नाही", - "ios_debug_info_processes_queued": "{count, plural, one {{count} पार्श्वभूमी प्रक्रिया प्रतीक्षेत} other {{count} पार्श्वभूमी प्रक्रिया प्रतीक्षेत}}", - "ios_debug_info_processing_ran_at": "प्रोसेसिंग {dateTime} ला चालले", - "items_count": "{count, plural, one {# आयटम} other {# आयटम} }", - "jobs": "जॉब्स", - "keep": "ठेवा", - "keep_all": "सगळे ठेवा", - "keep_this_delete_others": "हे ठेवा, इतर हटवा", - "kept_this_deleted_others": "हे अॅसेट ठेवले आणि {count, plural, one {इतर # अॅसेट हटवले} other {इतर # अॅसेट्स हटवले}}", - "keyboard_shortcuts": "कीबोर्ड शॉर्टकट्स", - "language": "भाषा", - "language_no_results_subtitle": "तुमचा शोध शब्द बदलून पाहा", - "language_no_results_title": "कोणतीही भाषा सापडली नाही", - "language_search_hint": "भाषा शोधा...", - "language_setting_description": "आपली पसंतीची भाषा निवडा", - "large_files": "मोठ्या फाईल्स", - "last": "शेवटचे", - "last_months": "{count, plural, one {मागील महिना} other {मागील # महिने}}", - "last_seen": "शेवटचे पाहिले", - "latest_version": "नवीनतम आवृत्ती", - "latitude": "अक्षांश", - "leave": "सोडा", - "leave_album": "अल्बम सोडा", - "lens_model": "लेन्स मॉडेल", - "let_others_respond": "इतरांना प्रतिसाद देऊ द्या", - "level": "पातळी", - "library": "लायब्ररी", - "library_add_folder": "फोल्डर जोडा", - "library_edit_folder": "फोल्डर संपादित करा", - "library_options": "लायब्ररी पर्याय", - "library_page_device_albums": "डिव्हाइसवरील अल्बम्स", - "library_page_new_album": "नवीन अल्बम", - "library_page_sort_asset_count": "अॅसेट्सची संख्या", - "library_page_sort_created": "तयार केलेली तारीख", - "library_page_sort_last_modified": "शेवटचा बदल", - "library_page_sort_title": "अल्बमचे शीर्षक", - "licenses": "परवाने", - "light": "लाइट", - "like": "लाईक", - "like_deleted": "लाईक काढून टाकले", - "link_motion_video": "मोशन व्हिडिओ लिंक करा", - "link_to_oauth": "OAuth शी लिंक करा", - "linked_oauth_account": "लिंक केलेले OAuth खाते", - "list": "यादी", - "loading": "लोडिंग सुरू आहे", - "loading_search_results_failed": "शोध निकाल लोड करण्यात अयशस्वी", - "local": "स्थानिक", - "local_asset_cast_failed": "सर्व्हरवर अपलोड न केलेले अॅसेट कास्ट करू शकत नाही", - "local_assets": "स्थानिक अॅसेट्स", - "local_media_summary": "स्थानिक मीडियाचा सारांश", - "local_network": "स्थानिक नेटवर्क", - "local_network_sheet_info": "दिलेल्या Wi-Fi नेटवर्कवर असताना अॅप या URL द्वारे सर्व्हरशी जोडेल", - "location": "लोकेशन", - "location_permission": "लोकेशन परवानगी", - "location_permission_content": "ऑटो-स्विचिंग फीचर वापरण्यासाठी Immich ला अचूक लोकेशन परवानगी हवी, म्हणजे ते सध्याच्या Wi-Fi नेटवर्कचे नाव वाचू शकेल", - "location_picker_choose_on_map": "नकाशावर निवडा", - "location_picker_latitude_error": "योग्य अक्षांश भरा", - "location_picker_latitude_hint": "येथे तुमचा अक्षांश भरा", - "location_picker_longitude_error": "योग्य रेखांश भरा", - "location_picker_longitude_hint": "येथे तुमचा रेखांश भरा", - "lock": "लॉक", - "locked_folder": "लॉक केलेला फोल्डर", - "log_detail_title": "लॉग तपशील", - "log_out": "लॉग आऊट", - "log_out_all_devices": "सर्व डिव्हाइसवरून लॉग आऊट करा", - "logged_in_as": "{user} म्हणून लॉग इन", - "logged_out_all_devices": "सर्व डिव्हाइसवरून लॉग आऊट झाले", - "logged_out_device": "डिव्हाइसवरून लॉग आऊट झाले", - "login": "लॉगिन", - "login_disabled": "लॉगिन बंद केले आहे", - "login_form_api_exception": "API अपवाद. कृपया सर्व्हर URL तपासा आणि पुन्हा प्रयत्न करा.", - "login_form_back_button_text": "मागे", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://तुमचा-सर्व्हर-IP:पोर्ट", - "login_form_endpoint_url": "सर्व्हर एंडपॉइंट URL", - "login_form_err_http": "कृपया http:// किंवा https:// नमूद करा", - "login_form_err_invalid_email": "अवैध ईमेल", - "login_form_err_invalid_url": "अवैध URL", - "login_form_err_leading_whitespace": "सुरुवातीला स्पेस आहे", - "login_form_err_trailing_whitespace": "शेवटी स्पेस आहे", - "login_form_failed_get_oauth_server_config": "OAuth वापरून लॉगिन करताना त्रुटी, सर्व्हर URL तपासा", - "login_form_failed_get_oauth_server_disable": "या सर्व्हरवर OAuth सुविधा उपलब्ध नाही", - "login_form_failed_login": "लॉगिन करताना त्रुटी, सर्व्हर URL, ईमेल आणि पासवर्ड तपासा", - "login_form_handshake_exception": "सर्व्हरसह handshake exception आली. तुम्ही self-signed प्रमाणपत्र वापरत असाल तर सेटिंग्जमध्ये self-signed प्रमाणपत्र समर्थन सक्षम करा.", - "login_form_password_hint": "पासवर्ड", - "login_form_save_login": "लॉगिन ठेवून द्या", - "login_form_server_empty": "सर्व्हर URL भरा.", - "login_form_server_error": "सर्व्हरशी जोडता आले नाही.", - "login_has_been_disabled": "लॉगिन बंद केले आहे.", - "login_password_changed_error": "पासवर्ड अपडेट करताना त्रुटी आली", - "login_password_changed_success": "पासवर्ड यशस्वीपणे अपडेट झाला", - "logout_all_device_confirmation": "तुम्हाला नक्की सर्व डिव्हाइसवरून लॉग आऊट करायचे आहे का?", - "logout_this_device_confirmation": "तुम्हाला नक्की या डिव्हाइसवरून लॉग आऊट करायचे आहे का?", - "logs": "लॉग्स", - "longitude": "रेखांश", - "look": "लूक", - "loop_videos": "व्हिडिओ लूप करा", - "loop_videos_description": "तपशील दृश्यात व्हिडिओ आपोआप लूप होण्यासाठी हे सक्षम करा.", - "main_branch_warning": "तुम्ही development आवृत्ती वापरत आहात; आम्ही ठामपणे release आवृत्ती वापरण्याची शिफारस करतो!", - "main_menu": "मुख्य मेनू", - "maintenance_description": "Immich ला देखभाल मोड मध्ये ठेवले आहे.", - "maintenance_end": "देखभाल मोड समाप्त करा", - "maintenance_end_error": "देखभाल मोड संपवण्यात अयशस्वी.", - "maintenance_logged_in_as": "सध्या {user} म्हणून लॉग इन आहे", - "maintenance_title": "तात्पुरते उपलब्ध नाही", - "make": "तयार करा", - "manage_geolocation": "लोकेशन व्यवस्थापित करा", - "manage_media_access_rationale": "अॅसेट्स कचऱ्यात हलवणे आणि तिथून परत आणणे योग्य प्रकारे हाताळण्यासाठी ही परवानगी आवश्यक आहे.", - "manage_media_access_settings": "सेटिंग्ज उघडा", - "manage_media_access_subtitle": "Immich अॅपला मीडिया फाइल्स व्यवस्थापित व हलवण्याची परवानगी द्या.", - "manage_media_access_title": "मीडिया मॅनेजमेंट प्रवेश", - "manage_shared_links": "शेअर्ड लिंक व्यवस्थापित करा", - "manage_sharing_with_partners": "पार्टनर्ससोबत शेअरींग व्यवस्थापित करा", - "manage_the_app_settings": "अॅप सेटिंग्ज व्यवस्थापित करा", - "manage_your_account": "तुमचे खाते व्यवस्थापित करा", - "manage_your_api_keys": "तुमच्या API कीज व्यवस्थापित करा", - "manage_your_devices": "तुमची लॉगिन असलेली डिव्हाइसेस व्यवस्थापित करा", - "manage_your_oauth_connection": "तुमचे OAuth कनेक्शन व्यवस्थापित करा", - "map": "नकाशा", - "map_assets_in_bounds": "{count, plural, =0 {या भागात कोणतेही फोटो नाहीत} one {# फोटो} other {# फोटो}}", - "map_cannot_get_user_location": "वापरकर्त्याचे लोकेशन मिळू शकले नाही", - "map_location_dialog_yes": "होय", - "map_location_picker_page_use_location": "हे लोकेशन वापरा", - "map_location_service_disabled_content": "सध्याच्या लोकेशनवरील अॅसेट्स दाखवण्यासाठी लोकेशन सेवा सक्षम असणे आवश्यक आहे. तुम्हाला ती आत्ता सक्षम करायची आहे का?", - "map_location_service_disabled_title": "लोकेशन सेवा बंद आहे", - "map_marker_for_images": "{city}, {country} येथे घेतलेल्या प्रतिमांसाठी नकाशा मार्कर", - "map_marker_with_image": "प्रतिमेसह नकाशा मार्कर", - "map_no_location_permission_content": "सध्याच्या लोकेशनवरील अॅसेट्स दाखवण्यासाठी लोकेशन परवानगी आवश्यक आहे. तुम्हाला ती परवानगी आत्ता द्यायची आहे का?", - "map_no_location_permission_title": "लोकेशन परवानगी नाकारली", - "map_settings": "नकाशा सेटिंग्ज", - "map_settings_dark_mode": "डार्क मोड", - "map_settings_date_range_option_day": "मागील २४ तास", - "map_settings_date_range_option_days": "मागील {days} दिवस", - "map_settings_date_range_option_year": "मागील वर्ष", - "map_settings_date_range_option_years": "मागील {years} वर्षे", - "map_settings_dialog_title": "नकाशा सेटिंग्ज", - "map_settings_include_show_archived": "आर्काइव्ह केलेले समाविष्ट करा", - "map_settings_include_show_partners": "पार्टनर्स समाविष्ट करा", - "map_settings_only_show_favorites": "फक्त आवडते दाखवा", - "map_settings_theme_settings": "नकाशा थीम", - "map_zoom_to_see_photos": "फोटो पाहण्यासाठी झूम आउट करा", - "mark_all_as_read": "सर्व वाचले म्हणून चिन्हांकित करा", - "mark_as_read": "वाचले म्हणून चिन्हांकित करा", - "marked_all_as_read": "सर्व वाचले म्हणून चिन्हांकित केले", - "matches": "जुळणारे", - "matching_assets": "जुळणारे अॅसेट्स", - "media_type": "मीडिया प्रकार", - "memories": "आठवणी", - "memories_all_caught_up": "सगळे पाहून झाले", - "memories_check_back_tomorrow": "आणखी आठवणींसाठी उद्या परत पहा", - "memories_setting_description": "आठवणीत काय दिसेल ते व्यवस्थापित करा", - "memories_start_over": "पुन्हा सुरू करा", - "memories_swipe_to_close": "बंद करण्यासाठी वर स्वाइप करा", - "memory": "आठवण", - "memory_lane_title": "मेमरी लेन {title}", - "menu": "मेनू", - "merge": "मर्ज करा", - "merge_people": "लोक मर्ज करा", - "merge_people_limit": "तुम्ही एकावेळी जास्तीत जास्त ५ चेहरेच मर्ज करू शकता", - "merge_people_prompt": "तुम्हाला हे लोक मर्ज करायचे आहेत का? ही क्रिया परत बदलता येणार नाही.", - "merge_people_successfully": "लोक यशस्वीपणे मर्ज केले", - "merged_people_count": "{count, plural, one {# व्यक्ती मर्ज केली} other {# व्यक्ती मर्ज केल्या}}", - "minimize": "मिनिमाईज करा", - "minute": "मिनिट", - "minutes": "मिनिटे", - "missing": "मिसिंग", - "mobile_app": "मोबाईल अॅप", - "mobile_app_download_onboarding_note": "खाली दिलेल्या पर्यायांचा वापर करून साथीदार मोबाईल अॅप डाउनलोड करा", - "model": "मॉडेल", - "month": "महिना", - "monthly_title_text_date_format": "एमएमएमएम वाई", - "more": "अधिक", - "move": "हलवा", - "move_off_locked_folder": "लॉक फोल्डरच्या बाहेर हलवा", - "move_to": "येथे हलवा", - "move_to_lock_folder_action_prompt": "{count} लॉक फोल्डरमध्ये जोडले", - "move_to_locked_folder": "लॉक फोल्डरमध्ये हलवा", - "move_to_locked_folder_confirmation": "हे फोटो आणि व्हिडिओ सर्व अल्बममधून काढले जातील आणि फक्त लॉक फोल्डरमधूनच दिसतील", - "moved_to_archive": "{count, plural, one {# अॅसेट आर्काइव्हमध्ये हलवला} other {# अॅसेट्स आर्काइव्हमध्ये हलवले}}", - "moved_to_library": "{count, plural, one {# अॅसेट लायब्ररीत हलवला} other {# अॅसेट्स लायब्ररीत हलवले}}", - "moved_to_trash": "कचरापेटीत हलवले", - "multiselect_grid_edit_date_time_err_read_only": "फक्त-वाचन (read-only) अॅसेटची तारीख बदलू शकत नाही, वगळत आहोत", - "multiselect_grid_edit_gps_err_read_only": "फक्त-वाचन (read-only) अॅसेटचे लोकेशन बदलू शकत नाही, वगळत आहोत", - "mute_memories": "आठवणी म्यूट करा", - "my_albums": "माझे अल्बम्स", - "name": "नाव", - "name_or_nickname": "नाव किंवा टोपणनाव", - "navigate": "नेव्हिगेट करा", - "navigate_to_time": "वेळेकडे नेव्हिगेट करा", - "network_requirement_photos_upload": "फोटो बॅकअपसाठी सेल्युलर डेटा वापरा", - "network_requirement_videos_upload": "व्हिडिओ बॅकअपसाठी सेल्युलर डेटा वापरा", - "network_requirements": "नेटवर्क आवश्यकता", - "network_requirements_updated": "नेटवर्क आवश्यकता बदलल्या, बॅकअप क्यू रीसेट करत आहोत", - "networking_settings": "नेटवर्किंग", - "networking_subtitle": "सर्व्हर एंडपॉइंट सेटिंग्ज व्यवस्थापित करा", - "never": "कधीच नाही", - "new_album": "नवीन अल्बम", - "new_api_key": "नवीन API की", - "new_date_range": "नवीन तारीख श्रेणी", - "new_password": "नवीन पासवर्ड", - "new_person": "नवीन व्यक्ती", - "new_pin_code": "नवीन PIN कोड", - "new_pin_code_subtitle": "तुम्ही प्रथमच लॉक फोल्डर वापरत आहात. हे पान सुरक्षितपणे उघडण्यासाठी PIN कोड तयार करा", - "new_timeline": "नवीन टाइमलाइन", - "new_update": "नवीन अपडेट", - "new_user_created": "नवीन वापरकर्ता तयार झाला", - "new_version_available": "नवीन आवृत्ती उपलब्ध", - "newest_first": "सर्वात नवी प्रथम", - "next": "पुढे", - "next_memory": "पुढील आठवण", - "no": "नाही", - "no_albums_message": "तुमचे फोटो आणि व्हिडिओ व्यवस्थित करण्यासाठी एक अल्बम तयार करा", - "no_albums_with_name_yet": "या नावाचा अजून कोणताही अल्बम नाही असे दिसते.", - "no_albums_yet": "अजून तुमच्याकडे कोणतेही अल्बम नाहीत असे दिसते.", - "no_archived_assets_message": "फोटो आणि व्हिडिओ तुमच्या फोटो व्ह्यूमधून लपवण्यासाठी त्यांना आर्काइव्ह करा", - "no_assets_message": "तुमचा पहिला फोटो जोडण्यासाठी क्लिक करा", - "no_assets_to_show": "दाखवण्यासाठी कोणतेही ॲसेट्स नाहीत", - "no_cast_devices_found": "कोणतेही कास्ट डिव्हाइस सापडले नाही", - "no_checksum_local": "चेकसम उपलब्ध नाही — स्थानिक अॅसेट्स आणू शकत नाही", - "no_checksum_remote": "चेकसम उपलब्ध नाही — रिमोट अॅसेट आणू शकत नाही", - "no_devices": "कोणतीही अधिकृत डिव्हाइसेस नाहीत", - "no_duplicates_found": "कोणतेही डुप्लिकेट सापडले नाहीत.", - "no_exif_info_available": "EXIF माहिती उपलब्ध नाही", - "no_explore_results_message": "तुमचा संग्रह एक्सप्लोर करण्यासाठी आणखी फोटो अपलोड करा.", - "no_favorites_message": "तुमचे सर्वोत्तम फोटो आणि व्हिडिओ पटकन शोधण्यासाठी त्यांना आवडत्यांत जोडा", - "no_libraries_message": "तुमचे फोटो आणि व्हिडिओ पाहण्यासाठी बाह्य लायब्ररी तयार करा", - "no_local_assets_found": "या चेकसमसह कोणतेही स्थानिक अॅसेट सापडले नाही", - "no_location_set": "लोकेशन सेट केलेले नाही", - "no_locked_photos_message": "लॉक फोल्डरमधील फोटो आणि व्हिडिओ लपवलेले आहेत आणि लायब्ररी ब्राउज किंवा शोधताना दिसणार नाहीत.", - "no_name": "कोणतेही नाव नाही", - "no_notifications": "कोणत्याही सूचना नाहीत", - "no_people_found": "जुळणारे कोणतेही लोक सापडले नाहीत", - "no_places": "कोणतीही ठिकाणे नाहीत", - "no_remote_assets_found": "या चेकसमसह कोणतेही रिमोट अॅसेट सापडले नाही", - "no_results": "कोणतेही निकाल नाहीत", - "no_results_description": "समार्थक शब्द किंवा अधिक सर्वसाधारण कीवर्ड वापरून पाहा", - "no_shared_albums_message": "तुमच्या नेटवर्कमधील लोकांसोबत फोटो आणि व्हिडिओ शेअर करण्यासाठी एक अल्बम तयार करा", - "no_uploads_in_progress": "कोणतेही अपलोड सुरू नाहीत", - "not_allowed": "परवानगी नाही", - "not_available": "उपलब्ध नाही", - "not_in_any_album": "कोणत्याही अल्बममध्ये नाही", - "not_selected": "निवडलेले नाही", - "note_apply_storage_label_to_previously_uploaded assets": "नोट: आधी अपलोड केलेल्या अॅसेट्सवर स्टोरेज लेबल लागू करण्यासाठी हा आदेश चालवा", - "notes": "नोट्स", - "nothing_here_yet": "इथे अजून काही नाही", - "notification_permission_dialog_content": "सूचना सक्षम करण्यासाठी सेटिंग्जमध्ये जा आणि अनुमती द्या.", - "notification_permission_list_tile_content": "सूचना सक्षम करण्यासाठी परवानगी द्या.", - "notification_permission_list_tile_enable_button": "सूचना सक्षम करा", - "notification_permission_list_tile_title": "सूचना परवानगी", - "notification_toggle_setting_description": "ईमेल सूचना सक्षम करा", - "notifications": "सूचना", - "notifications_setting_description": "सूचना व्यवस्थापित करा", - "oauth": "OAuth", - "obtainium_configurator": "ओब्टेनियम कॉन्फिगरेटर", - "obtainium_configurator_instructions": "Immich GitHub रिलीजमधून Android अॅप थेट इन्स्टॉल आणि अपडेट करण्यासाठी Obtainium वापरा. API की तयार करा आणि एक व्हेरियंट निवडून तुमचा Obtainium कॉन्फिगरेशन लिंक तयार करा", - "ocr": "ओसीआर", - "official_immich_resources": "अधिकृत Immich संसाधने", - "offline": "ऑफलाइन", - "offset": "ऑफसेट", - "ok": "ठीक", - "oldest_first": "सर्वात जुने आधी", - "on_this_device": "या डिव्हाइसवर", - "onboarding": "ऑनबोर्डिंग", - "onboarding_locale_description": "तुमची पसंतीची भाषा निवडा. हे नंतर सेटिंग्जमध्ये बदलू शकता.", - "onboarding_privacy_description": "खालील (पर्यायी) वैशिष्ट्ये बाह्य सेवांवर अवलंबून आहेत आणि सेटिंग्जमध्ये कधीही अक्षम करता येतात.", - "onboarding_server_welcome_description": "काही सामान्य सेटिंग्जसह तुमची इन्स्टन्स सेटअप करूया.", - "onboarding_theme_description": "तुमच्या इन्स्टन्ससाठी रंग थीम निवडा. हे नंतर सेटिंग्जमध्ये बदलू शकता.", - "onboarding_user_welcome_description": "चला, सुरुवात करूया!", - "onboarding_welcome_user": "स्वागत आहे, {user}", - "online": "ऑनलाइन", - "only_favorites": "फक्त आवडते", - "open": "उघडा", - "open_in_map_view": "नकाशा दृश्यात उघडा", - "open_in_openstreetmap": "OpenStreetMap मध्ये उघडा", - "open_the_search_filters": "शोध फिल्टर उघडा", - "options": "पर्याय", - "or": "किंवा", - "organize_into_albums": "अल्बममध्ये आयोजित करा", - "organize_into_albums_description": "सध्याच्या समक्रमण सेटिंग्ज वापरून विद्यमान फोटो अल्बममध्ये ठेवा", - "organize_your_library": "तुमची लायब्ररी व्यवस्थित करा", - "original": "मूळ", - "other": "इतर", - "other_devices": "इतर उपकरणे", - "other_entities": "इतर घटक", - "other_variables": "इतर चल", - "owned": "मालकीचे", - "owner": "मालक", - "partner": "भागीदार", - "partner_can_access": "{partner} ला प्रवेश आहे", - "partner_can_access_assets": "संग्रहित व हटविलेले वगळता तुमचे सर्व फोटो आणि व्हिडिओ", - "partner_can_access_location": "ज्या ठिकाणी तुमचे फोटो काढले गेले ते स्थान", - "partner_list_user_photos": "{user} चे फोटो", - "partner_list_view_all": "सर्व पहा", - "partner_page_empty_message": "तुमचे फोटो अजून कोणत्याही भागीदारासोबत शेअर केलेले नाहीत.", - "partner_page_no_more_users": "जोडण्यासाठी आणखी वापरकर्ते नाहीत", - "partner_page_partner_add_failed": "भागीदार जोडण्यात अयशस्वी", - "partner_page_select_partner": "भागीदार निवडा", - "partner_page_shared_to_title": "यांना शेअर केले", - "partner_page_stop_sharing_content": "{partner} आता तुमचे फोटो पाहू शकणार नाही.", - "partner_sharing": "भागीदार शेअरिंग", - "partners": "भागीदार", - "password": "पासवर्ड", - "password_does_not_match": "पासवर्ड जुळत नाही", - "password_required": "पासवर्ड आवश्यक", - "password_reset_success": "पासवर्ड रीसेट यशस्वी", - "past_durations": { - "days": "मागील {days, plural, one {# दिवस} other {# दिवस}}", - "hours": "मागील {hours, plural, one {# तास} other {# तास}}", - "years": "मागील {years, plural, one {# वर्ष} other {# वर्षे}}" - }, - "path": "मार्ग", - "pattern": "नमुना", - "pause": "थांबवा", - "pause_memories": "आठवणी थांबवा", - "paused": "थांबवले", - "pending": "प्रलंबित", - "people": "लोक", - "people_edits_count": "संपादित {count, plural, one {# व्यक्ती} other {# लोक}}", - "people_feature_description": "लोकांनुसार गटबद्ध फोटो आणि व्हिडिओ ब्राउझ करा", - "people_sidebar_description": "साइडबारमध्ये “लोक” साठी दुवा दाखवा", - "permanent_deletion_warning": "कायमस्वरूपी विलोपन सूचना", - "permanent_deletion_warning_setting_description": "अ‍ॅसेट्स कायमचे हटवताना सूचना दाखवा", - "permanently_delete": "कायमचे हटवा", - "permanently_delete_assets_count": "{count, plural, one {अ‍ॅसेट} other {अ‍ॅसेट्स}} कायमचे हटवा", - "permanently_delete_assets_prompt": "आपण {count, plural, one {हा अ‍ॅसेट कायमचा हटवू इच्छिता?} other {हे अ‍ॅसेट्स कायमचे हटवू इच्छिता?}} यामुळे {count, plural, one {तो त्याच्या} other {ते त्यांच्या}} अल्बम(मधून) देखील काढले जातील.", - "permanently_deleted_asset": "कायमचा हटवलेला अ‍ॅसेट", - "permanently_deleted_assets_count": "कायमचे हटवले {count, plural, one {# अ‍ॅसेट} other {# अ‍ॅसेट्स}}", - "permission": "परवानगी", - "permission_empty": "तुमची परवानगी रिक्त असू नये", - "permission_onboarding_back": "मागे", - "permission_onboarding_continue_anyway": "तरीही पुढे जा", - "permission_onboarding_get_started": "सुरू करा", - "permission_onboarding_go_to_settings": "सेटिंग्जमध्ये जा", - "permission_onboarding_permission_denied": "परवानगी नाकारली. Immich वापरण्यासाठी, सेटिंग्जमध्ये फोटो आणि व्हिडिओ परवानग्या द्या.", - "permission_onboarding_permission_granted": "परवानगी मंजूर! सर्व तयार.", - "permission_onboarding_permission_limited": "परवानगी मर्यादित. Immich ला संपूर्ण गॅलरी संग्रहाचा बॅकअप व व्यवस्थापन करण्यासाठी, सेटिंग्जमध्ये फोटो आणि व्हिडिओ परवानग्या द्या.", - "permission_onboarding_request": "तुमचे फोटो आणि व्हिडिओ पाहण्यासाठी Immich ला परवानगी आवश्यक आहे.", - "person": "व्यक्ती", - "person_age_months": "{months, plural, one {# महिना} other {# महिने}} वय", - "person_age_year_months": "1 वर्ष, {months, plural, one {# महिना} other {# महिने}} वय", - "person_age_years": "{years, plural, other {# वर्षांचे}}", - "person_birthdate": "जन्म {date} रोजी", - "person_hidden": "{name}{hidden, select, true { {hidden}} other {}}", - "photo_shared_all_users": "तुम्ही सर्व वापरकर्त्यांसोबत फोटो शेअर केले आहेत असे दिसते किंवा शेअर करण्यासाठी कोणताही वापरकर्ता नाही.", - "photos": "फोटो", - "photos_and_videos": "फोटो आणि व्हिडिओ", - "photos_count": "{count, plural, one {{count, number} फोटो} other {{count, number} फोटो}}", - "photos_from_previous_years": "मागील वर्षांतील फोटो", - "pick_a_location": "स्थान निवडा", - "pick_custom_range": "कस्टम श्रेणी", - "pick_date_range": "तारीख श्रेणी निवडा", - "pin_code_changed_successfully": "PIN कोड यशस्वीरित्या बदलला", - "pin_code_reset_successfully": "PIN कोड यशस्वीरित्या रीसेट केला", - "pin_code_setup_successfully": "PIN कोड यशस्वीरित्या सेट केला", - "pin_verification": "PIN कोड पडताळणी", - "place": "स्थान", - "places": "स्थाने", - "places_count": "{count, plural, one {{count, number} स्थान} other {{count, number} स्थाने}}", - "play": "प्ले करा", - "play_memories": "आठवणी प्ले करा", - "play_motion_photo": "मोशन फोटो प्ले करा", - "play_or_pause_video": "व्हिडिओ प्ले किंवा पॉज करा", - "play_original_video": "मूळ व्हिडिओ प्ले करा", - "play_original_video_setting_description": "ट्रान्सकोड केलेल्या व्हिडिओंपेक्षा मूळ व्हिडिओ प्ले करण्याला प्राधान्य द्या. मूळ अॅसेट सुसंगत नसेल तर प्लेबॅक योग्यरित्या होऊ शकत नाही.", - "play_transcoded_video": "ट्रान्सकोड केलेला व्हिडिओ प्ले करा", - "please_auth_to_access": "प्रवेशासाठी कृपया प्रमाणीकरण करा", - "port": "पोर्ट", - "preferences_settings_subtitle": "अ‍ॅपची प्राधान्ये व्यवस्थापित करा", - "preferences_settings_title": "प्राधान्ये", - "preparing": "तयार करत आहे", - "preset": "प्रिसेट", - "preview": "पूर्वावलोकन", - "previous": "मागील", - "previous_memory": "मागील आठवण", - "previous_or_next_day": "दिवस पुढे/मागे", - "previous_or_next_month": "महिना पुढे/मागे", - "previous_or_next_photo": "फोटो पुढे/मागे", - "previous_or_next_year": "वर्ष पुढे/मागे", - "primary": "प्राथमिक", - "privacy": "गोपनीयता", - "profile": "प्रोफाइल", - "profile_drawer_app_logs": "लॉग्स", - "profile_drawer_client_server_up_to_date": "क्लायंट आणि सर्व्हर अद्ययावत आहेत", - "profile_drawer_github": "गिटहब", - "profile_drawer_readonly_mode": "फक्त-वाचन मोड सक्षम. बाहेर पडण्यासाठी वापरकर्त्याच्या अवतार आयकॉनवर लांब-प्रेस करा.", - "profile_image_of_user": "{user} ची प्रोफाइल प्रतिमा", - "profile_picture_set": "प्रोफाइल चित्र सेट केले.", - "public_album": "सार्वजनिक अल्बम", - "public_share": "सार्वजनिक शेअर", - "purchase_account_info": "समर्थक", - "purchase_activated_subtitle": "Immich आणि मुक्त-स्रोत सॉफ्टवेअरला पाठिंबा दिल्याबद्दल धन्यवाद", - "purchase_activated_time": "{date} रोजी सक्रिय केले", - "purchase_activated_title": "तुमची की यशस्वीपणे सक्रिय करण्यात आली आहे", - "purchase_button_activate": "सक्रिय करा", - "purchase_button_buy": "खरेदी करा", - "purchase_button_buy_immich": "Immich खरेदी करा", - "purchase_button_never_show_again": "पुन्हा दाखवू नका", - "purchase_button_reminder": "३० दिवसांनी मला आठवण करून द्या", - "purchase_button_remove_key": "की हटवा", - "purchase_button_select": "निवडा", - "purchase_failed_activation": "सक्रिय करण्यात अयशस्वी! योग्य प्रोडक्ट कीसाठी कृपया तुमचे ईमेल तपासा!", - "purchase_individual_description_1": "वैयक्तिक वापरासाठी", - "purchase_individual_description_2": "समर्थक स्थिती", - "purchase_individual_title": "वैयक्तिक", - "purchase_input_suggestion": "प्रॉडक्ट की आहे? खाली की टाका", - "purchase_license_subtitle": "सेवेच्या पुढील विकासासाठी Immich खरेदी करून साथ द्या", - "purchase_lifetime_description": "आयुष्यभराची खरेदी", - "purchase_option_title": "खरेदी पर्याय", - "purchase_panel_info_1": "Immich तयार करणे वेळखाऊ आणि कष्टाचे आहे. आमचे ध्येय मुक्त-स्रोत सॉफ्टवेअर व नैतिक व्यावसायिक पद्धतींमधून टिकाऊ उत्पन्न मिळवणे, विकसकांना आधार देणे आणि शोषणकारी क्लाउड सेवांना पर्याय देणारे गोपनीयतेचा मान राखणारे इकोसिस्टम तयार करणे हे आहे.", - "purchase_panel_info_2": "आम्ही पेवॉल न वाढवण्यास कटिबद्ध आहोत; त्यामुळे या खरेदीमुळे Immich मध्ये कोणतीही अतिरिक्त वैशिष्ट्ये उघडणार नाहीत. चालू विकासासाठी आम्ही तुमच्यासारख्या वापरकर्त्यांच्या पाठबळावर अवलंबून आहोत.", - "purchase_panel_title": "प्रकल्पाला साथ द्या", - "purchase_per_server": "प्रति सर्व्हर", - "purchase_per_user": "प्रति वापरकर्ता", - "purchase_remove_product_key": "प्रॉडक्ट की काढा", - "purchase_remove_product_key_prompt": "तुम्हाला नक्की प्रॉडक्ट की काढायची आहे का?", - "purchase_remove_server_product_key": "सर्व्हरची प्रॉडक्ट की काढा", - "purchase_remove_server_product_key_prompt": "तुम्हाला नक्की सर्व्हरची प्रॉडक्ट की काढायची आहे का?", - "purchase_server_description_1": "संपूर्ण सर्व्हरसाठी", - "purchase_server_description_2": "समर्थक स्थिती", - "purchase_server_title": "सर्व्हर", - "purchase_settings_server_activated": "सर्व्हरची प्रॉडक्ट की प्रशासकाद्वारे व्यवस्थापित केली जाते", - "query_asset_id": "अॅसेट ID चौकशी", - "queue_status": "रांगेत {count}/{total}", - "rating": "स्टार रेटिंग", - "rating_clear": "रेटिंग साफ करा", - "rating_count": "{count, plural, one {# तारा} other {# तारे}}", - "rating_description": "माहिती पॅनेलमध्ये EXIF रेटिंग दर्शवा", - "reaction_options": "रिऍक्शन पर्याय", - "read_changelog": "चेंजलॉग वाचा", - "readonly_mode_disabled": "फक्त-वाचन मोड निष्क्रिय केला", - "readonly_mode_enabled": "फक्त-वाचन मोड सक्षम केला", - "ready_for_upload": "अपलोडसाठी तयार", - "reassign": "पुन्हा नियुक्त करा", - "reassigned_assets_to_existing_person": "{count, plural, one {# आयटम} other {# आयटम}} {name, select, null {विद्यमान व्यक्तीकडे} other {{name} कडे}} पुन्हा नियुक्त केले", - "reassigned_assets_to_new_person": "{count, plural, one {# आयटम} other {# आयटम}} नव्या व्यक्तीकडे पुन्हा नियुक्त केले", - "reassing_hint": "निवडलेले आयटम विद्यमान व्यक्तीकडे नियुक्त करा", - "recent": "अलीकडील", - "recent-albums": "अलीकडील अल्बम", - "recent_searches": "अलीकडील शोध", - "recently_added": "नुकतेच जोडलेले", - "recently_added_page_title": "नुकतेच जोडलेले", - "recently_taken": "अलीकडे घेतलेले", - "recently_taken_page_title": "अलीकडे घेतलेले", - "refresh": "रीफ्रेश करा", - "refresh_encoded_videos": "एन्कोड केलेले व्हिडिओ रीफ्रेश करा", - "refresh_faces": "चेहरे रीफ्रेश करा", - "refresh_metadata": "मेटाडेटा रीफ्रेश करा", - "refresh_thumbnails": "थंबनेल रीफ्रेश करा", - "refreshed": "रीफ्रेश झाले", - "refreshes_every_file": "विद्यमान व नवीन सर्व फाइल्स पुन्हा वाचा", - "refreshing_encoded_video": "एन्कोड केलेला व्हिडिओ रीफ्रेश करत आहे", - "refreshing_faces": "चेहरे रीफ्रेश करत आहे", - "refreshing_metadata": "मेटाडेटा रीफ्रेश करत आहे", - "regenerating_thumbnails": "थंबनेल्स पुन्हा तयार करत आहे", - "remote": "दूरस्थ", - "remote_assets": "दूरस्थ आयटम", - "remote_media_summary": "रिमोट मीडियाचा सारांश", - "remove": "काढा", - "remove_assets_album_confirmation": "अल्बममधून {count, plural, one {# आयटम} other {# आयटम}} काढायचे आहेत का?", - "remove_assets_shared_link_confirmation": "या शेअर्ड दुव्यातून {count, plural, one {# आयटम} other {# आयटम}} काढायचे आहेत का?", - "remove_assets_title": "आयटम काढायचे?", - "remove_custom_date_range": "सानुकूल दिनांक श्रेणी काढा", - "remove_deleted_assets": "हटवलेले आयटम काढा", - "remove_from_album": "अल्बममधून काढा", - "remove_from_album_action_prompt": "अल्बममधून {count} काढले", - "remove_from_favorites": "आवडीतून काढा", - "remove_from_lock_folder_action_prompt": "लॉक केलेल्या फोल्डरमधून {count} काढले", - "remove_from_locked_folder": "लॉक फोल्डरमधून काढा", - "remove_from_locked_folder_confirmation": "हे फोटो आणि व्हिडिओ लॉक फोल्डरमधून बाहेर हलवायचे आहेत का? ते तुमच्या लायब्ररीमध्ये दिसतील.", - "remove_from_shared_link": "शेअर्ड दुव्यातून काढा", - "remove_memory": "मेमरी काढा", - "remove_photo_from_memory": "या मेमरीतून फोटो काढा", - "remove_tag": "टॅग काढा", - "remove_url": "URL काढा", - "remove_user": "वापरकर्ता काढा", - "removed_api_key": "काढलेली API की: {name}", - "removed_from_archive": "आर्काइव्हमधून काढले", - "removed_from_favorites": "आवडीतून काढले", - "removed_from_favorites_count": "{count, plural, other {आवडीतून # काढले}}", - "removed_memory": "मेमरी काढली", - "removed_photo_from_memory": "मेमरीतून फोटो काढला", - "removed_tagged_assets": "{count, plural, one {# आयटमवरून टॅग काढला} other {# आयटमवरून टॅग काढले}}", - "rename": "नाव बदला", - "repair": "दुरुस्ती", - "repair_no_results_message": "अनट्रॅक्ड व हरवलेल्या फाइल्स येथे दिसतील", - "replace_with_upload": "अपलोडने बदला", - "repository": "रिपॉझिटरी", - "require_password": "पासवर्ड आवश्यक", - "require_user_to_change_password_on_first_login": "पहिल्या लॉगिनवेळी वापरकर्त्याने पासवर्ड बदलणे आवश्यक", - "rescan": "पुन्हा स्कॅन करा", - "reset": "रीसेट करा", - "reset_password": "पासवर्ड रीसेट करा", - "reset_people_visibility": "लोकांची दृश्यता रीसेट करा", - "reset_pin_code": "PIN कोड रीसेट करा", - "reset_pin_code_description": "तुमचा PIN विसरला असल्यास, तो रीसेट करण्यासाठी सर्व्हर प्रशासकाशी संपर्क साधा", - "reset_pin_code_success": "PIN कोड यशस्वीरीत्या रीसेट केला", - "reset_pin_code_with_password": "पासवर्डने तुम्ही नेहमी PIN कोड रीसेट करू शकता", - "reset_sqlite": "SQLite डेटाबेस रीसेट करा", - "reset_sqlite_confirmation": "तुम्हाला नक्की SQLite डेटाबेस रीसेट करायचा आहे का? डेटा पुन्हा समक्रमित करण्यासाठी तुम्हाला लॉगआउट करून पुन्हा लॉगइन करावे लागेल", - "reset_sqlite_success": "SQLite डेटाबेस यशस्वीरीत्या रीसेट केला", - "reset_to_default": "डीफॉल्टवर रीसेट करा", - "resolution": "रेझोल्यूशन", - "resolve_duplicates": "डुप्लिकेट्स सोडवा", - "resolved_all_duplicates": "सर्व डुप्लिकेट्स सोडवले", - "restore": "पुनर्संचयित करा", - "restore_all": "सर्व पुनर्संचयित करा", - "restore_trash_action_prompt": "कचरापेटीतून {count} पुनर्संचयित केले", - "restore_user": "वापरकर्ता पुनर्संचयित करा", - "restored_asset": "पुनर्संचयित आयटम", - "resume": "पुन्हा सुरू करा", - "resume_paused_jobs": "{count, plural, one {# थांबवलेले काम} other {# थांबवलेली कामे}} पुन्हा सुरू करा", - "retry_upload": "अपलोड पुन्हा करा", - "review_duplicates": "डुप्लिकेट्सचे पुनरावलोकन करा", - "review_large_files": "मोठ्या फाइल्सचे पुनरावलोकन करा", - "role": "भूमिका", - "role_editor": "संपादक", - "role_viewer": "दर्शक", - "running": "चालू", - "save": "जतन करा", - "save_to_gallery": "गॅलरीमध्ये जतन करा", - "saved": "जतन केले", - "saved_api_key": "जतन केलेली API की", - "saved_profile": "जतन केलेले प्रोफाइल", - "saved_settings": "जतन केलेल्या सेटिंग्ज", - "say_something": "काहीतरी बोला", - "scaffold_body_error_occurred": "त्रुटी आली", - "scan_all_libraries": "सर्व लायब्ररी स्कॅन करा", - "scan_library": "स्कॅन करा", - "scan_settings": "स्कॅन सेटिंग्ज", - "scanning_for_album": "अल्बमसाठी स्कॅन करत आहे...", - "search": "शोधा", - "search_albums": "अल्बम शोधा", - "search_by_context": "परिस्थितीनुसार शोधा", - "search_by_description": "वर्णनानुसार शोधा", - "search_by_description_example": "सापा मधील हायकिंगचा दिवस", - "search_by_filename": "फाइल नाव/एक्स्टेंशननुसार शोधा", - "search_by_filename_example": "उदा. IMG_1234.JPG किंवा PNG", - "search_by_ocr": "OCR द्वारे शोधा", - "search_by_ocr_example": "लाटे", - "search_camera_lens_model": "लेन्स मॉडेल शोधा…", - "search_camera_make": "कॅमेरा निर्माता शोधा...", - "search_camera_model": "कॅमेरा मॉडेल शोधा...", - "search_city": "शहर शोधा...", - "search_country": "देश शोधा...", - "search_filter_apply": "फिल्टर लागू करा", - "search_filter_camera_title": "कॅमेरा प्रकार निवडा", - "search_filter_date": "तारीख", - "search_filter_date_interval": "{start} ते {end}", - "search_filter_date_title": "दिनांक श्रेणी निवडा", - "search_filter_display_option_not_in_album": "अल्बममध्ये नाही", - "search_filter_display_options": "प्रदर्शन पर्याय", - "search_filter_filename": "फाइल नावाने शोधा", - "search_filter_location": "स्थान", - "search_filter_location_title": "स्थान निवडा", - "search_filter_media_type": "माध्यम प्रकार", - "search_filter_media_type_title": "माध्यम प्रकार निवडा", - "search_filter_ocr": "OCR द्वारे शोधा", - "search_filter_people_title": "लोक निवडा", - "search_for": "यासाठी शोधा", - "search_for_existing_person": "विद्यमान व्यक्ती शोधा", - "search_no_more_result": "आणखी परिणाम नाहीत", - "search_no_people": "कोणतीही व्यक्ती नाही", - "search_no_people_named": "“{name}” नावाची व्यक्ती सापडली नाही", - "search_no_result": "काहीही सापडले नाही; वेगळा शोध शब्द किंवा संयोजन वापरा", - "search_options": "शोध पर्याय", - "search_page_categories": "श्रेण्या", - "search_page_motion_photos": "मोशन फोटो", - "search_page_no_objects": "वस्तूंची माहिती उपलब्ध नाही", - "search_page_no_places": "ठिकाणांची माहिती उपलब्ध नाही", - "search_page_screenshots": "स्क्रीनशॉट्स", - "search_page_search_photos_videos": "तुमचे फोटो व व्हिडिओ शोधा", - "search_page_selfies": "सेल्फीज", - "search_page_things": "वस्तू", - "search_page_view_all_button": "सर्व पहा", - "search_page_your_activity": "तुमचे क्रियाकलाप", - "search_page_your_map": "तुमचा नकाशा", - "search_people": "लोक शोधा", - "search_places": "ठिकाणे शोधा", - "search_rating": "रेटिंगनुसार शोधा...", - "search_result_page_new_search_hint": "नवीन शोध", - "search_settings": "शोध सेटिंग्ज", - "search_state": "राज्य/स्टेट शोधा...", - "search_suggestion_list_smart_search_hint_1": "डीफॉल्टने स्मार्ट सर्च सुरू आहे; मेटाडेटा शोधण्यासाठी ही रचना वापरा. ", - "search_suggestion_list_smart_search_hint_2": "m:तुमचा-शोध-शब्द", - "search_tags": "टॅग्स शोधा...", - "search_timezone": "वेळक्षेत्र शोधा...", - "search_type": "शोध प्रकार", - "search_your_photos": "तुमचे फोटो शोधा", - "searching_locales": "लोकल्स शोधत आहे...", - "second": "सेकंद", - "see_all_people": "सर्व लोक पाहा", - "select": "निवडा", - "select_album_cover": "अल्बम कव्हर निवडा", - "select_all": "सर्व निवडा", - "select_all_duplicates": "सर्व डुप्लिकेट्स निवडा", - "select_all_in": "{group} मधील सर्व निवडा", - "select_avatar_color": "अवतारचा रंग निवडा", - "select_face": "चेहरा निवडा", - "select_featured_photo": "फिचर्ड फोटो निवडा", - "select_from_computer": "कॉम्प्युटरमधून निवडा", - "select_keep_all": "सर्व ठेवणे निवडा", - "select_library_owner": "लायब्ररी मालक निवडा", - "select_new_face": "नवा चेहरा निवडा", - "select_person_to_tag": "टॅग करण्यासाठी व्यक्ती निवडा", - "select_photos": "फोटो निवडा", - "select_trash_all": "कचरापेटीतील सर्व निवडा", - "select_user_for_sharing_page_err_album": "अल्बम तयार करण्यात अयशस्वी", - "selected": "निवडलेले", - "selected_count": "{count, plural, other {# निवडले}}", - "selected_gps_coordinates": "निवडलेल्या GPS स्थाननिर्देशांक", - "send_message": "संदेश पाठवा", - "send_welcome_email": "स्वागत ईमेल पाठवा", - "server_endpoint": "सर्व्हर एंडपॉइंट", - "server_info_box_app_version": "अॅप आवृत्ती", - "server_info_box_server_url": "सर्व्हर URL", - "server_offline": "सर्व्हर ऑफलाइन", - "server_online": "सर्व्हर ऑनलाइन", - "server_privacy": "सर्व्हर गोपनीयता", - "server_restarting_description": "हे पान थोड्याच वेळात रिफ्रेश होईल.", - "server_restarting_title": "सर्व्हर पुन्हा सुरू होत आहे", - "server_stats": "सर्व्हर आकडेवारी", - "server_update_available": "सर्व्हर अपडेट उपलब्ध आहे", - "server_version": "सर्व्हर आवृत्ती", - "set": "सेट करा", - "set_as_album_cover": "अल्बम कव्हर म्हणून सेट करा", - "set_as_featured_photo": "फिचर्ड फोटो म्हणून सेट करा", - "set_as_profile_picture": "प्रोफाइल फोटो म्हणून सेट करा", - "set_date_of_birth": "जन्मतारीख सेट करा", - "set_profile_picture": "प्रोफाइल फोटो सेट करा", - "set_slideshow_to_fullscreen": "स्लाइडशो फुलस्क्रीन करा", - "set_stack_primary_asset": "मुख्य आयटम म्हणून सेट करा", - "setting_image_viewer_help": "डीटेल व्ह्यूअर आधी लहान थंबनेल लोड करतो, नंतर (सक्षम असल्यास) मध्यम आकाराचे प्रिव्ह्यू लोड करतो, आणि शेवटी (सक्षम असल्यास) मूळ प्रतिमा लोड करतो.", - "setting_image_viewer_original_subtitle": "पूर्ण-रिझोल्यूशनची मूळ प्रतिमा लोड करण्यासाठी सक्षम करा (मोठी). डेटा वापर कमी करण्यासाठी (नेटवर्क व डिव्हाइस कॅश दोन्ही) अक्षम करा.", - "setting_image_viewer_original_title": "मूळ प्रतिमा लोड करा", - "setting_image_viewer_preview_subtitle": "मध्यम-रिझोल्यूशन प्रतिमा लोड करण्यासाठी सक्षम करा. अक्षम केल्यास थेट मूळ प्रतिमा लोड होईल किंवा फक्त थंबनेल वापरला जाईल.", - "setting_image_viewer_preview_title": "प्रिव्ह्यू प्रतिमा लोड करा", - "setting_image_viewer_title": "प्रतिमा", - "setting_languages_apply": "लागू करा", - "setting_languages_subtitle": "अॅपची भाषा बदला", - "setting_notifications_notify_failures_grace_period": "पार्श्वभूमी बॅकअप अपयशांची सूचना: {duration}", - "setting_notifications_notify_hours": "{count} तास", - "setting_notifications_notify_immediately": "तत्काळ", - "setting_notifications_notify_minutes": "{count} मिनिटे", - "setting_notifications_notify_never": "कधीच नाही", - "setting_notifications_notify_seconds": "{count} सेकंद", - "setting_notifications_single_progress_subtitle": "प्रत्येक आयटमसाठी तपशीलवार अपलोड प्रगती माहिती", - "setting_notifications_single_progress_title": "पार्श्वभूमी बॅकअपची तपशीलवार प्रगती दाखवा", - "setting_notifications_subtitle": "तुमची सूचना प्राधान्ये समायोजित करा", - "setting_notifications_total_progress_subtitle": "एकूण अपलोड प्रगती (पूर्ण/एकूण आयटम)", - "setting_notifications_total_progress_title": "पार्श्वभूमी बॅकअपची एकूण प्रगती दाखवा", - "setting_video_viewer_auto_play_subtitle": "व्हिडिओ उघडल्यावर आपोआप प्ले करा", - "setting_video_viewer_auto_play_title": "व्हिडिओ ऑटोप्ले", - "setting_video_viewer_looping_title": "लूपिंग", - "setting_video_viewer_original_video_subtitle": "सर्व्हरवरून व्हिडिओ स्ट्रिम करताना ट्रान्सकोड उपलब्ध असला तरी मूळ व्हिडिओ प्ले करा. बफरिंग होऊ शकते. स्थानिकरीत्या उपलब्ध व्हिडिओ या सेटिंगपासून स्वतंत्रपणे मूळ गुणवत्तेत प्ले होतात.", - "setting_video_viewer_original_video_title": "मूळ व्हिडिओ सक्तीने प्ले करा", - "settings": "सेटिंग्ज", - "settings_require_restart": "ही सेटिंग लागू करण्यासाठी कृपया Immich रीस्टार्ट करा", - "settings_saved": "सेटिंग्ज जतन केल्या", - "setup_pin_code": "PIN कोड सेट करा", - "share": "शेअर करा", - "share_action_prompt": "{count} आयटम शेअर केले", - "share_add_photos": "फोटो जोडा", - "share_assets_selected": "{count} निवडले", - "share_dialog_preparing": "तयार करत आहे...", - "share_link": "शेअर दुवा", - "shared": "शेअर केले", - "shared_album_activities_input_disable": "टिप्पणी निष्क्रिय आहे", - "shared_album_activity_remove_content": "ही कृती हटवायची आहे का?", - "shared_album_activity_remove_title": "कृती हटवा", - "shared_album_section_people_action_error": "अल्बममधून बाहेर पडताना/काढताना त्रुटी", - "shared_album_section_people_action_leave": "अल्बममधून वापरकर्ता काढा", - "shared_album_section_people_action_remove_user": "अल्बममधून वापरकर्ता काढा", - "shared_album_section_people_title": "लोक", - "shared_by": "यांनी शेअर केले", - "shared_by_user": "{user} यांनी शेअर केले", - "shared_by_you": "तुमच्याकडून शेअर केले", - "shared_from_partner": "{partner} कडील फोटो", - "shared_intent_upload_button_progress_text": "{current}/{total} अपलोड झाले", - "shared_link_app_bar_title": "शेअर्ड दुवे", - "shared_link_clipboard_copied_massage": "क्लिपबोर्डवर कॉपी केले", - "shared_link_clipboard_text": "दुवा: {link}\nपासवर्ड: {password}", - "shared_link_create_error": "शेअर्ड दुवा तयार करताना त्रुटी", - "shared_link_custom_url_description": "सानुकूल URL द्वारे हा शेअर्ड दुवा उघडा", - "shared_link_edit_description_hint": "शेअरचे वर्णन प्रविष्ट करा", - "shared_link_edit_expire_after_option_day": "1 दिवस", - "shared_link_edit_expire_after_option_days": "{count} दिवस", - "shared_link_edit_expire_after_option_hour": "1 तास", - "shared_link_edit_expire_after_option_hours": "{count} तास", - "shared_link_edit_expire_after_option_minute": "1 मिनिट", - "shared_link_edit_expire_after_option_minutes": "{count} मिनिटे", - "shared_link_edit_expire_after_option_months": "{count} महिने", - "shared_link_edit_expire_after_option_year": "{count} वर्ष", - "shared_link_edit_password_hint": "शेअरचा पासवर्ड प्रविष्ट करा", - "shared_link_edit_submit_button": "दुवा अद्ययावत करा", - "shared_link_error_server_url_fetch": "सर्व्हर URL मिळू शकला नाही", - "shared_link_expires_day": "{count} दिवसात संपेल", - "shared_link_expires_days": "{count} दिवसात संपेल", - "shared_link_expires_hour": "{count} तासात संपेल", - "shared_link_expires_hours": "{count} तासांत संपेल", - "shared_link_expires_minute": "{count} मिनिटात संपेल", - "shared_link_expires_minutes": "{count} मिनिटांत संपेल", - "shared_link_expires_never": "कधीच संपत नाही ∞", - "shared_link_expires_second": "{count} सेकंदात संपेल", - "shared_link_expires_seconds": "{count} सेकंदात संपेल", - "shared_link_individual_shared": "वैयक्तिक शेअर", - "shared_link_info_chip_metadata": "EXIF (एक्सिफ)", - "shared_link_manage_links": "शेअर्ड दुवे व्यवस्थापित करा", - "shared_link_options": "शेअर्ड दुवा पर्याय", - "shared_link_password_description": "हा शेअर्ड दुवा पाहण्यासाठी पासवर्ड आवश्यक आहे", - "shared_links": "शेअर्ड दुवे", - "shared_links_description": "दुव्याद्वारे फोटो आणि व्हिडिओ शेअर करा", - "shared_photos_and_videos_count": "{assetCount, plural, other {# शेअर्ड फोटो आणि व्हिडिओ}}", - "shared_with_me": "माझ्यासोबत शेअर केलेले", - "shared_with_partner": "{partner} सोबत शेअर केले", - "sharing": "शेअरिंग", - "sharing_enter_password": "हे पृष्ठ पाहण्यासाठी कृपया पासवर्ड प्रविष्ट करा.", - "sharing_page_album": "शेअर्ड अल्बम", - "sharing_page_description": "तुमच्या नेटवर्कमधील लोकांसोबत फोटो-व्हिडिओ शेअर करण्यासाठी शेअर्ड अल्बम तयार करा.", - "sharing_page_empty_list": "रिकामी यादी", - "sharing_sidebar_description": "साइडबारमध्ये शेअरिंगचा दुवा दाखवा", - "sharing_silver_appbar_create_shared_album": "नवीन शेअर्ड अल्बम", - "sharing_silver_appbar_share_partner": "भागीदारासोबत शेअर करा", - "shift_to_permanent_delete": "अॅसेट कायमचे हटवण्यासाठी ⇧ दाबा", - "show_album_options": "अल्बम पर्याय दाखवा", - "show_albums": "अल्बम दाखवा", - "show_all_people": "सर्व लोक दाखवा", - "show_and_hide_people": "लोक दाखवा आणि लपवा", - "show_file_location": "फाइलचे स्थान दाखवा", - "show_gallery": "गॅलरी दाखवा", - "show_hidden_people": "लपवलेले लोक दाखवा", - "show_in_timeline": "टाइमलाइनमध्ये दाखवा", - "show_in_timeline_setting_description": "या वापरकर्त्याचे फोटो-व्हिडिओ तुमच्या टाइमलाइनमध्ये दाखवा", - "show_keyboard_shortcuts": "कीबोर्ड शॉर्टकट दाखवा", - "show_metadata": "मेटाडेटा दाखवा", - "show_or_hide_info": "माहिती दाखवा किंवा लपवा", - "show_password": "पासवर्ड दाखवा", - "show_person_options": "व्यक्तीचे पर्याय दाखवा", - "show_progress_bar": "प्रगती पट्टी दाखवा", - "show_search_options": "शोध पर्याय दाखवा", - "show_shared_links": "शेअर केलेले दुवे दाखवा", - "show_slideshow_transition": "स्लाइडशो ट्रांझिशन दाखवा", - "show_supporter_badge": "समर्थक बॅज", - "show_supporter_badge_description": "समर्थक बॅज दाखवा", - "show_text_recognition": "टेक्स्ट ओळख दाखवा", - "show_text_search_menu": "टेक्स्ट शोध मेनू दाखवा", - "shuffle": "शफल", - "sidebar": "साइडबार", - "sidebar_display_description": "साइडबारमध्ये दृश्याचा दुवा दाखवा", - "sign_out": "साइन आउट", - "sign_up": "साइन अप", - "size": "आकार", - "skip_to_content": "सामग्रीकडे जा", - "skip_to_folders": "फोल्डर्सकडे जा", - "skip_to_tags": "टॅग्सकडे जा", - "slideshow": "स्लाइडशो", - "slideshow_settings": "स्लाइडशो सेटिंग्ज", - "sort_albums_by": "अल्बम यानुसार क्रम लावा…", - "sort_created": "तयार केलेली तारीख", - "sort_items": "आयटमांची संख्या", - "sort_modified": "बदल केलेली तारीख", - "sort_newest": "अलीकडचा फोटो", - "sort_oldest": "सर्वात जुना फोटो", - "sort_people_by_similarity": "साम्यतेनुसार व्यक्तींचा क्रम लावा", - "sort_recent": "नुकताच घेतलेला फोटो", - "sort_title": "शीर्षक", - "source": "स्त्रोत", - "stack": "स्टॅक", - "stack_action_prompt": "{count} स्टॅक केले", - "stack_duplicates": "डुप्लिकेट्स स्टॅक करा", - "stack_select_one_photo": "स्टॅकसाठी एक मुख्य फोटो निवडा", - "stack_selected_photos": "निवडलेले फोटो स्टॅक करा", - "stacked_assets_count": "स्टॅक केलेले {count, plural, one {# आयटम} other {# आयटम}}", - "stacktrace": "स्टॅकट्रेस", - "start": "सुरू करा", - "start_date": "सुरुवातीची तारीख", - "start_date_before_end_date": "सुरुवातीची तारीख शेवटच्या तारखेच्या आधी असावी", - "state": "स्थिती", - "status": "स्टेटस", - "stop_casting": "कास्टिंग थांबवा", - "stop_motion_photo": "मोशन फोटो थांबवा", - "stop_photo_sharing": "तुमचे फोटो शेअर करणे थांबवायचे?", - "stop_photo_sharing_description": "{partner} यांना आता तुमचे फोटो पाहता येणार नाहीत.", - "stop_sharing_photos_with_user": "या वापरकर्त्यासोबत तुमचे फोटो शेअर करणे थांबवा", - "storage": "संचयन जागा", - "storage_label": "संचयन लेबल", - "storage_quota": "संचयन कोटा", - "storage_usage": "{available} पैकी {used} वापरले", - "submit": "सादर करा", - "success": "यशस्वी", - "suggestions": "सूचना", - "sunrise_on_the_beach": "समुद्रकिनाऱ्यावर सूर्योदय", - "support": "सहाय्य", - "support_and_feedback": "सहाय्य आणि अभिप्राय", - "support_third_party_description": "तुमची Immich स्थापना तृतीय-पक्ष पॅकेजद्वारे दिली आहे. तुम्हाला येणाऱ्या समस्या त्या पॅकेजमुळे असू शकतात; त्यामुळे खालील दुव्यांचा वापर करून सर्वप्रथम त्यांच्याकडे समस्या नोंदवा.", - "swap_merge_direction": "मर्ज दिशेची अदलाबदल करा", - "sync": "समक्रमण", - "sync_albums": "अल्बम समक्रमित करा", - "sync_albums_manual_subtitle": "अपलोड केलेले सर्व फोटो-व्हिडिओ निवडलेल्या बॅकअप अल्बममध्ये समक्रमित करा", - "sync_local": "स्थानिक समक्रमण", - "sync_remote": "दूरस्थ समक्रमण", - "sync_status": "समक्रमण स्थिती", - "sync_status_subtitle": "समक्रमण प्रणाली पाहा आणि व्यवस्थापित करा", - "sync_upload_album_setting_subtitle": "Immich वरील निवडलेल्या अल्बममध्ये तुमचे फोटो व व्हिडिओ तयार करा आणि अपलोड करा", - "tag": "टॅग", - "tag_assets": "आयटमना टॅग लावा", - "tag_created": "तयार केलेला टॅग: {tag}", - "tag_feature_description": "तार्किक टॅग विषयांनुसार गटबद्ध फोटो व व्हिडिओ ब्राउझ करा", - "tag_not_found_question": "टॅग सापडत नाही? नवा टॅग तयार करा", - "tag_people": "व्यक्तींना टॅग करा", - "tag_updated": "अद्ययावत टॅग: {tag}", - "tagged_assets": "टॅग केलेले {count, plural, one {# आयटम} other {# आयटम}}", - "tags": "टॅग्स", - "tap_to_run_job": "जॉब चालवण्यासाठी टॅप करा", - "template": "टेम्पलेट", - "text_recognition": "मजकूर ओळख", - "theme": "थीम", - "theme_selection": "थीम निवड", - "theme_selection_description": "ब्राउझरच्या सिस्टम पसंतीनुसार थीम आपोआप लाइट/डार्क करा", - "theme_setting_asset_list_storage_indicator_title": "अॅसेट टाइल्सवर स्टोरेज निर्देशक दाखवा", - "theme_setting_asset_list_tiles_per_row_title": "प्रत्येक रांगेतील अॅसेट्सची संख्या ({count})", - "theme_setting_colorful_interface_subtitle": "बॅकग्राऊंड पृष्ठभागांवर प्राथमिक रंग लागू करा.", - "theme_setting_colorful_interface_title": "रंगीबेरंगी इंटरफेस", - "theme_setting_image_viewer_quality_subtitle": "डीटेल इमेज व्ह्यूअरची गुणवत्ता समायोजित करा", - "theme_setting_image_viewer_quality_title": "इमेज व्ह्यूअर गुणवत्ता", - "theme_setting_primary_color_subtitle": "प्राथमिक कृती व अॅक्सेंटसाठी रंग निवडा.", - "theme_setting_primary_color_title": "प्राथमिक रंग", - "theme_setting_system_primary_color_title": "सिस्टम रंग वापरा", - "theme_setting_system_theme_switch": "स्वयंचलित (सिस्टम सेटिंग्जनुसार)", - "theme_setting_theme_subtitle": "अॅपची थीम सेटिंग निवडा", - "theme_setting_three_stage_loading_subtitle": "थ्री-स्टेज लोडिंगमुळे गती वाढू शकते; परंतु नेटवर्क लोड लक्षणीय वाढतो", - "theme_setting_three_stage_loading_title": "थ्री-स्टेज लोडिंग सुरू करा", - "they_will_be_merged_together": "ते एकत्र विलीन केले जातील", - "third_party_resources": "तृतीय-पक्ष संसाधने", - "time": "वेळ", - "time_based_memories": "वेळ-आधारित मेमरीज", - "time_based_memories_duration": "प्रत्येक प्रतिमा दाखवण्यासाठी लागणाऱ्या सेकंदांची संख्या।", - "timeline": "टाइमलाइन", - "timezone": "वेळक्षेत्र", - "to_archive": "आर्काइव्ह करा", - "to_change_password": "परवलीचा शब्द बदला", - "to_favorite": "आवडीमध्ये जोडा", - "to_login": "लॉग इन करा", - "to_multi_select": "बहु-निवड करा", - "to_parent": "पालकाकडे जा", - "to_select": "निवडा", - "to_trash": "कचरापेटीत टाका", - "toggle_settings": "सेटिंग्ज टॉगल करा", - "total": "एकूण", - "total_usage": "एकूण वापर", - "trash": "कचरापेटी", - "trash_action_prompt": "{count} कचरापेटीत हलवले", - "trash_all": "सर्व कचरापेटीत टाका", - "trash_count": "कचरापेटी {count, number}", - "trash_delete_asset": "कचरापेटीत टाका/अॅसेट हटवा", - "trash_emptied": "कचरापेटी रिकामी केली", - "trash_no_results_message": "कचरापेटीत टाकलेले फोटो व व्हिडिओ येथे दिसतील.", - "trash_page_delete_all": "सर्व हटवा", - "trash_page_empty_trash_dialog_content": "कचरापेटी रिकामी करायची का? हे आयटम Immich मधून कायमचे हटवले जातील", - "trash_page_info": "कचरापेटीतील आयटम {days} दिवसांनंतर कायमचे हटवले जातील", - "trash_page_no_assets": "कचरापेटीत कोणतेही आयटम नाहीत", - "trash_page_restore_all": "सर्व परत आणा", - "trash_page_select_assets_btn": "आयटम निवडा", - "trash_page_title": "कचरापेटी ({count})", - "trashed_items_will_be_permanently_deleted_after": "कचरापेटीतील आयटम {days, plural, one {# दिवसांनंतर} other {# दिवसांनंतर}} कायमचे हटवले जातील.", - "troubleshoot": "समस्या निवारण", - "type": "प्रकार", - "unable_to_change_pin_code": "PIN कोड बदलता येत नाही", - "unable_to_check_version": "ॲप किंवा सर्व्हरची आवृत्ती तपासता येत नाही", - "unable_to_setup_pin_code": "PIN कोड सेट करू शकत नाही", - "unarchive": "अनआर्काइव्ह करा", - "unarchive_action_prompt": "{count} आर्काइव्हमधून काढले", - "unarchived_count": "{count, plural, other {अनआर्काइव्ह #}}", - "undo": "पूर्ववत करा", - "unfavorite": "आवडीतून काढा", - "unfavorite_action_prompt": "{count} आवडीतून काढले", - "unhide_person": "व्यक्ती दर्शवा", - "unknown": "अज्ञात", - "unknown_country": "अज्ञात देश", - "unknown_year": "अज्ञात वर्ष", - "unlimited": "अमर्यादित", - "unlink_motion_video": "मोशन व्हिडिओ अनलिंक करा", - "unlink_oauth": "OAuth अनलिंक करा", - "unlinked_oauth_account": "OAuth खाते अनलिंक केले", - "unmute_memories": "मेमरीज अनम्यूट करा", - "unnamed_album": "नाव नसलेला अल्बम", - "unnamed_album_delete_confirmation": "तुम्हाला हा अल्बम खरंच हटवायचा आहे का?", - "unnamed_share": "नाव नसलेले शेअर", - "unsaved_change": "न साठवलेला बदल", - "unselect_all": "सर्व निवडी रद्द करा", - "unselect_all_duplicates": "सर्व डुप्लिकेट्सची निवड रद्द करा", - "unselect_all_in": "{group} मधील सर्व निवडी रद्द करा", - "unstack": "स्टॅक वेगळा करा", - "unstack_action_prompt": "{count} अनस्टॅक केले", - "unstacked_assets_count": "अनस्टॅक केलेले {count, plural, one {# आयटम} other {# आयटम}}", - "untagged": "टॅग नसलेले", - "up_next": "पुढे", - "update_location_action_prompt": "निवडलेल्या {count} आयटमचे स्थान याने अद्ययावत करा:", - "updated_at": "अद्ययावत केले", - "updated_password": "परवलीचा शब्द अद्ययावत केला", - "upload": "अपलोड", - "upload_concurrency": "अपलोड समांतरता", - "upload_details": "अपलोड तपशील", - "upload_dialog_info": "निवडलेले आयटम सर्व्हरवर बॅकअप करायचे का?", - "upload_dialog_title": "अॅसेट अपलोड करा", - "upload_errors": "अपलोड पूर्ण झाले; {count, plural, one {# त्रुटी} other {# त्रुटी}} आढळल्या. नवीन अपलोड आयटम पाहण्यासाठी पृष्ठ रीफ्रेश करा.", - "upload_finished": "अपलोड पूर्ण", - "upload_progress": "उर्वरित {remaining, number} — प्रक्रिया झालेले {processed, number}/{total, number}", - "upload_skipped_duplicates": "वगळले {count, plural, one {# डुप्लिकेट आयटम} other {# डुप्लिकेट आयटम}}", - "upload_status_duplicates": "डुप्लिकेट", - "upload_status_errors": "त्रुटी", - "upload_status_uploaded": "अपलोड झाले", - "upload_success": "अपलोड यशस्वी. नवीन अपलोड आयटम दिसण्यासाठी पृष्ठ रीफ्रेश करा.", - "upload_to_immich": "Immich वर अपलोड करा ({count})", - "uploading": "अपलोड होत आहे", - "uploading_media": "माध्यमे अपलोड होत आहेत", - "url": "URL", - "usage": "वापर", - "use_biometric": "बायोमेट्रिक वापरा", - "use_current_connection": "सध्याचे कनेक्शन वापरा", - "use_custom_date_range": "याऐवजी सानुकूल दिनांक श्रेणी वापरा", - "user": "वापरकर्ता", - "user_has_been_deleted": "हा वापरकर्ता हटविला गेला आहे.", - "user_id": "वापरकर्ता आयडी", - "user_liked": "{user} यांना {type, select, photo {हा फोटो} video {हा व्हिडिओ} asset {हा आयटम} other {हे}} आवडले", - "user_pin_code_settings": "PIN कोड", - "user_pin_code_settings_description": "तुमचा PIN कोड व्यवस्थापित करा", - "user_privacy": "वापरकर्ता गोपनीयता", - "user_purchase_settings": "खरेदी", - "user_purchase_settings_description": "तुमची खरेदी व्यवस्थापित करा", - "user_role_set": "{user} यांना {role} म्हणून सेट करा", - "user_usage_detail": "वापरकर्त्याच्या वापराचा तपशील", - "user_usage_stats": "खात्याच्या वापराच्या सांख्यिकी", - "user_usage_stats_description": "खात्याच्या वापराच्या सांख्यिकी पहा", - "username": "वापरकर्तानाव", - "users": "वापरकर्ते", - "users_added_to_album_count": "अल्बममध्ये {count, plural, one {# वापरकर्ता जोडला} other {# वापरकर्ते जोडले}}", - "utilities": "उपयुक्तता", - "validate": "तपासा", - "validate_endpoint_error": "कृपया वैध URL प्रविष्ट करा", - "variables": "चल", - "version": "आवृत्ती", - "version_announcement_closing": "तुमचा मित्र, अ‍ॅलेक्स", - "version_announcement_message": "नमस्कार! Immich ची नवी आवृत्ती उपलब्ध आहे. तुमची संरचना अद्ययावत आणि बिनचूक राहावी यासाठी कृपया काही वेळ काढून रिलीज नोट्स वाचा, विशेषतः तुम्ही WatchTower किंवा अद्ययावत प्रक्रिया स्वयंचलितपणे हाताळणारी कोणतीही व्यवस्था वापरत असाल तर.", - "version_history": "आवृत्ती इतिहास", - "version_history_item": "{date} रोजी {version} स्थापित केली", - "video": "व्हिडिओ", - "video_hover_setting": "हावर केल्यावर व्हिडिओ थंबनेल प्ले करा", - "video_hover_setting_description": "आयटमवर माऊस नेल्यावर व्हिडिओ थंबनेल प्ले होईल. पर्याय बंद असला तरी प्ले चिन्हावर हावर केल्यास प्लेबॅक सुरू करता येईल.", - "videos": "व्हिडिओ", - "videos_count": "{count, plural, one {# व्हिडिओ} other {# व्हिडिओ}}", - "view": "पहा", - "view_album": "अल्बम पहा", - "view_all": "सर्व पहा", - "view_all_users": "सर्व वापरकर्ते पहा", - "view_details": "तपशील पहा", - "view_in_timeline": "टाइमलाइनमध्ये पहा", - "view_link": "दुवा पहा", - "view_links": "दुवे पहा", - "view_name": "पहा", - "view_next_asset": "पुढील आयटम पहा", - "view_previous_asset": "मागील आयटम पहा", - "view_qr_code": "QR कोड पहा", - "view_similar_photos": "समान फोटो पहा", - "view_stack": "स्टॅक पहा", - "view_user": "वापरकर्ता पहा", - "viewer_remove_from_stack": "स्टॅकमधून काढा", - "viewer_stack_use_as_main_asset": "मुख्य आयटम म्हणून वापरा", - "viewer_unstack": "स्टॅक वेगळा करा", - "visibility_changed": "दृश्यता {count, plural, one {# व्यक्तीसाठी बदलली} other {# व्यक्तींसाठी बदलली}}", - "waiting": "प्रतीक्षेत", - "warning": "चेतावणी", - "week": "आठवडा", - "welcome": "स्वागत आहे", - "welcome_to_immich": "Immich मध्ये आपले स्वागत आहे", - "wifi_name": "वाय-फायचे नाव", - "wrong_pin_code": "अवैध पिन कोड", - "year": "वर्ष", - "years_ago": "{years, plural, one {# वर्षापूर्वी} other {# वर्षांपूर्वी}}", - "yes": "हो", - "you_dont_have_any_shared_links": "आपल्याकडे कोणतेही सामायिक दुवे नाहीत", - "your_wifi_name": "तुमच्या Wi-Fi चे नाव", - "zoom_image": "प्रतिमा झूम करा", - "zoom_to_bounds": "सीमेपर्यंत झूम करा" -} +{} diff --git a/i18n/ms.json b/i18n/ms.json index cbec851018..0967ef424b 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -1,495 +1 @@ -{ - "about": "Tentang", - "account": "Akaun", - "account_settings": "Tetapan Akaun", - "acknowledge": "Akui", - "action": "Tindakan", - "action_common_update": "Kemaskini", - "actions": "Tindakan", - "active": "Aktif", - "active_count": "Aktif: {count}", - "activity": "Aktiviti", - "activity_changed": "Aktiviti {enabled, select, true {enabled} other {disabled}}", - "add": "Tambah", - "add_a_description": "Tambah penerangan", - "add_a_location": "Tambah lokasi", - "add_a_name": "Tambah nama", - "add_a_title": "Tambah tajuk", - "add_birthday": "Tambah hari jadi", - "add_endpoint": "Tambah titik akhir", - "add_exclusion_pattern": "Tambahkan corak pengecualian", - "add_location": "Tambah lokasi", - "add_more_users": "Tambah user lagi", - "add_partner": "Tambah rakan", - "add_path": "Tambah laluan", - "add_photos": "Tambah gambar", - "add_tag": "Tambah tag", - "add_to": "Tambah ke…", - "add_to_album": "Tambah ke album", - "add_to_album_bottom_sheet_added": "Dimasukkan ke {album}", - "add_to_album_bottom_sheet_already_exists": "Sudah ada di {album}", - "add_to_album_bottom_sheet_some_local_assets": "Sesetengah aset lokal tidak ditambahkan pada album", - "add_to_album_toggle": "Togol pilihan untuk {album}", - "add_to_albums": "Tambah pada album", - "add_to_albums_count": "Tambah pada album ({count})", - "add_to_bottom_bar": "Tambah ke", - "add_to_shared_album": "Tambah ke album yang dikongsi", - "add_upload_to_stack": "Tambah muat naik ke timbunan", - "add_url": "Tambah URL", - "added_to_archive": "Tambah ke arkib", - "added_to_favorites": "Ditambah ke kegemaran", - "added_to_favorites_count": "Menambahkan {count, number} ke kegemaran", - "admin": { - "add_exclusion_pattern_description": "Tambahkan corak pengecualian. Globbing menggunakan *, **, dan ? disokong. Untuk mengabaikan semua fail dalam mana-mana direktori bernama \"Raw\", gunakan \"**/Raw/**\". Untuk mengabaikan semua fail yang berakhir dengan \".tif\", gunakan \"**/*.tif\". Untuk mengabaikan laluan mutlak, gunakan \"/path/to/ignore/**\".", - "admin_user": "Pengguna Pentadbir", - "asset_offline_description": "Aset pustaka luaran ini tidak lagi ditemui pada cakera dan telah dialihkan ke sampah. Jika fail telah dialihkan dalam pustaka, semak garis masa anda untuk aset baharu yang sepadan. Untuk memulihkan aset ini, sila pastikan bahawa laluan fail di bawah boleh diakses oleh Immich dan mengimbas pustaka.", - "authentication_settings": "Tetapan Pengesahan", - "authentication_settings_description": "Urus kata laluan, OAuth dan tetapan pengesahan lain", - "authentication_settings_disable_all": "Adakah anda pasti mahu melumpuhkan semua kaedah log masuk? Log masuk akan dilumpuhkan sepenuhnya.", - "authentication_settings_reenable": "Untuk menghidupkan semula, guna Arahan Pelayan.", - "background_task_job": "Tugas Latar Belakang", - "backup_database": "Buat Salinan Pangkalan Data", - "backup_database_enable_description": "Dayakan salinan pangkalan data", - "backup_keep_last_amount": "Jumlah salinan pangkalan data sebelumnya untuk disimpan", - "backup_onboarding_1_description": "salinan luar tapak di awan atau di lokasi fizikal lain.", - "backup_onboarding_2_description": "salinan tempatan pada peranti yang berbeza. Ini termasuk fail utama dan sandaran fail tersebut secara setempat.", - "backup_onboarding_3_description": "jumlah salinan data anda, termasuk fail asal. Ini termasuk 1 salinan luar tapak dan 2 salinan tempatan.", - "backup_onboarding_description": "Strategi sandaran 3-2-1 disarankan untuk melindungi data anda. Anda perlu menyimpan salinan foto/video yang dimuat naik serta pangkalan data Immich bagi memastikan penyelesaian sandaran yang menyeluruh.", - "backup_onboarding_footer": "Untuk maklumat lanjut tentang membuat sandaran Immich, sila rujuk dokumentasi.", - "backup_onboarding_parts_title": "Sandaran 3-2-1 merangkumi:", - "backup_onboarding_title": "Sandaran", - "backup_settings": "Tetapan Salinan Pangkalan Data", - "backup_settings_description": "Urus tetapan salinan pangkalan data.", - "cleared_jobs": "Kerja telah dibersihkan untuk: {job}", - "config_set_by_file": "Konfigurasi kini ditetapkan oleh fail konfigurasi", - "confirm_delete_library": "Adakah anda pasti mahu memadamkan {library}?", - "confirm_delete_library_assets": "Adakah anda pasti mahu memadamkan pustaka ini? Ini akan memadam {count, plural, one {# aset yang terkandung} other {semua # aset yang terkandung}} daripada Immich dan tidak boleh dibuat asal. Fail akan kekal pada cakera.", - "confirm_email_below": "Untuk mengesahkan, sila taip \"{email}\" dibawah", - "confirm_reprocess_all_faces": "Adakah anda pasti mahu memproses semula semua wajah? Ini juga akan membersihkan orang bernama.", - "confirm_user_password_reset": "Adakah anda pasti mahu menetapkan semula kata laluan {user}?", - "confirm_user_pin_code_reset": "Adakah anda pasti untuk mengubah kod PIN {user}'s ?", - "copy_config_to_clipboard_description": "Salin konfigurasi sistem semasa sebagai objek JSON ke papan klip", - "create_job": "Cipta tugas", - "cron_expression": "Ungkapan cron", - "cron_expression_description": "Tetapkan selang imbasan menggunakan format cron. Untuk maklumat lanjut, sila rujuk ke sebagai contoh Crontab Guru", - "cron_expression_presets": "Pratetap-pratetap ungkapan Cron", - "disable_login": "Lumpuhkan fungsi log masuk", - "duplicate_detection_job_description": "Jalankan pembelajaran mesin pada aset untuk mengesan imej yang serupa. Bergantung pada Carian Pintar", - "exclusion_pattern_description": "Corak pengecualian membolehkan anda mengabaikan fail dan folder semasa mengimbas pustaka anda. Ini berguna jika anda mempunyai folder yang mengandungi fail yang anda tidak mahu import, seperti fail RAW.", - "export_config_as_json_description": "Muat turun konfigurasi sistem semasa sebagai fail JSON", - "external_libraries_page_description": "Halaman pustaka luaran admin", - "face_detection": "Pengesanan wajah", - "face_detection_description": "Kesan wajah dalam aset menggunakan pembelajaran mesin. Untuk video, hanya lakaran kecil dipertimbangkan. \"Segar Semula\" memproses semula semua aset. \"Tetapkan Semula\" juga mengosongkan semua data wajah semasa. \"Hilang\" baris gilir aset yang belum diproses lagi. Wajah yang dikesan akan beratur untuk Pengecaman Wajah selepas Pengesanan Wajah selesai, menghimpunkannya kepada orang sedia ada atau baharu.", - "facial_recognition_job_description": "Kumpulan wajah yang dikesan ke dalam orang. Langkah ini dijalankan selepas Pengesanan Wajah selesai. \"Tetapkan semula\" mengelompokkan semula semua wajah. \"Hilang\" jalankan proses pada wajah yang tidak mempunyai orang yang ditetapkan.", - "failed_job_command": "Perintah {command} gagal untuk kerja: {job}", - "force_delete_user_warning": "AMARAN: Ini akan mengalih keluar pengguna dan semua aset dengan serta-merta. Ia tidak boleh dibuat asal dan fail tidak boleh dipulihkan.", - "image_format": "Format", - "image_format_description": "WebP menghasilkan fail yang lebih kecil daripada JPEG, tetapi lebih perlahan untuk mengekod.", - "image_fullsize_description": "Imej bersaiz penuh dengan metadata yang dilucutkan, digunakan apabila dizum masuk", - "image_fullsize_enabled": "Dayakan penjanaan imej bersaiz penuh", - "image_fullsize_enabled_description": "Hasilkan imej bersaiz penuh untuk format tidak mesra web. Apabila \"Lebih suka pratonton terbenam\" didayakan, pratonton terbenam digunakan secara langsung tanpa penukaran. Tidak menjejaskan format mesra web seperti JPEG.", - "image_fullsize_quality_description": "Kualiti imej bersaiz penuh dari 1-100. Lebih tinggi adalah lebih baik, tetapi menghasilkan fail yang lebih besar.", - "image_fullsize_title": "Tetapan Imej bersaiz penuh", - "image_prefer_embedded_preview": "Cadangkan pratonton terbenam", - "image_prefer_embedded_preview_setting_description": "Gunakan pratonton terbenam dalam foto RAW sebagai input untuk pemprosesan imej apabila tersedia. Ini boleh menghasilkan warna yang lebih tepat untuk sesetengah imej, tetapi kualiti pratonton bergantung kepada kamera dan imej mungkin mengandungi lebih banyak artifak pemampatan.", - "image_prefer_wide_gamut": "Cadangkan warna gamut yang luas", - "image_prefer_wide_gamut_setting_description": "Gunakan Paparan P3 untuk lakaran kenit. Ini lebih baik mengekalkan kerancakan imej dengan ruang warna yang luas, tetapi imej mungkin kelihatan berbeza pada peranti lama dengan versi penyemak imbas lama. Imej sRGB disimpan sebagai sRGB untuk mengelakkan peralihan warna.", - "image_preview_description": "Imej bersaiz sederhana dengan metadata yang dilucutkan, digunakan semasa melihat aset tunggal dan untuk pembelajaran mesin", - "image_preview_quality_description": "Kualiti pratonton dari 1-100. Lebih tinggi adalah lebih baik, tetapi menghasilkan fail yang lebih besar dan boleh mengurangkan responsif apl. Menetapkan nilai yang rendah boleh menjejaskan kualiti pembelajaran mesin.", - "image_preview_title": "Tetapan Pratonton", - "image_quality": "Kualiti", - "image_resolution": "Resolusi", - "image_resolution_description": "Resolusi yang lebih tinggi boleh meningkatkan ketajaman imej tetapi mengambil masa yang lebih lama untuk mengekod, mempunyai saiz fail yang lebih besar dan boleh mengurangkan responsif apl.", - "image_settings": "Tetapan Imej", - "image_settings_description": "Urus kualiti dan resolusi imej yang dihasilkan", - "image_thumbnail_description": "Lakaran kecil dengan metadata yang dilucutkan, digunakan semasa melihat kumpulan foto seperti garis masa utama", - "image_thumbnail_quality_description": "Kualiti lakaran kenit daripada 1-100. Lebih tinggi adalah lebih baik, tetapi menghasilkan fail yang lebih besar dan boleh mengurangkan responsif apl.", - "image_thumbnail_title": "Tetapan Lakaran Kenit", - "import_config_from_json_description": "Import konfigurasi sistem melalui muat naik fail JSON", - "job_concurrency": "Konkurensi {job}", - "job_created": "Tugas yang dicipta", - "job_not_concurrency_safe": "Konkurensi tugas ini tidak selamat.", - "job_settings": "Tetapan Tugas", - "job_settings_description": "Urus konkurensi tugas", - "jobs_delayed": "{jobCount, plural, other {# tertangguh}}", - "jobs_failed": "{jobCount, plural, other {# gagal}}", - "jobs_over_time": "Tugas berjadual dari semasa ke semasa", - "library_created": "Pustaka dicipta: {library}", - "library_deleted": "Pustaka dipadamkan", - "library_details": "Butiran pustaka", - "library_folder_description": "Tentukan folder untuk diimport. Folder ini, termasuk subfolder, akan diimbas untuk imej dan video.", - "library_remove_exclusion_pattern_prompt": "Adakah anda pasti mahu membuang corak pengecualian ini?", - "library_remove_folder_prompt": "Adakah anda pasti mahu membuang folder import ini?", - "library_scanning": "Pengimbasan Berkala", - "library_scanning_description": "Konfigurasikan pengimbasan perpustakaan berkala", - "library_scanning_enable_description": "Dayakan pengimbasan perpustakaan berkala", - "library_settings": "Perpustakaan Luaran", - "library_settings_description": "Urus tetapan perpustakaan luaran", - "library_tasks_description": "Imbas pustaka luaran untuk aset yang baru dan/atau telah diubah", - "library_updated": "Pustaka dikemas kini", - "library_watching_enable_description": "Perhatikan perpustakaan luaran untuk perubahan fail", - "library_watching_settings": "Perhati perpustakaan [EKSPERIMEN]", - "library_watching_settings_description": "Perhati fail yang diubah secara automatik", - "logging_enable_description": "Dayakan pengelogan", - "logging_level_description": "Apabila didayakan, tahap log yang hendak digunakan.", - "logging_settings": "Log", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Nama model CLIP disenaraikan di sini. Ambil perhatian bahawa anda mesti menjalankan semula tugas 'Carian Pintar' untuk semua imej selepas menukar model.", - "machine_learning_duplicate_detection": "Pengesanan Pendua", - "machine_learning_duplicate_detection_enabled": "Dayakan pengesanan pendua", - "machine_learning_duplicate_detection_enabled_description": "Jika dilumpuhkan, aset yang betul-betul serupa masih akan dinyahduakan.", - "machine_learning_duplicate_detection_setting_description": "Gunakan pembenaman CLIP untuk mencari kemungkinan pendua", - "machine_learning_enabled": "Dayakan pembelajaran mesin", - "machine_learning_enabled_description": "Jika dilumpuhkan, semua ciri Pembelajaran Mesin akan dilumpuhkan tanpa mengira tetapan di bawah.", - "machine_learning_facial_recognition": "Pengecaman Wajah", - "machine_learning_facial_recognition_description": "Mengesan, mengecam dan mengumpulkan wajah dalam imej", - "machine_learning_facial_recognition_model": "Model pengecaman wajah", - "machine_learning_facial_recognition_model_description": "Model disenaraikan dalam susunan saiz menurun. Model yang lebih besar adalah lebih perlahan dan menggunakan lebih banyak memori, tetapi menghasilkan hasil yang lebih baik. Ambil perhatian bahawa anda mesti menjalankan semula kerja Pengesanan Wajah untuk semua imej apabila menukar model.", - "machine_learning_facial_recognition_setting": "Dayakan pengecaman wajah", - "machine_learning_facial_recognition_setting_description": "Jika dilumpuhkan, imej tidak akan dikodkan untuk pengecaman wajah dan tidak akan mengisi bahagian Orang dalam halaman Teroka.", - "machine_learning_max_detection_distance": "Jarak pengesanan maksimum", - "machine_learning_max_detection_distance_description": "Jarak maksimum antara dua imej untuk menganggapnya sebagai pendua, antara 0.001-0.1. Nilai yang lebih tinggi akan mengesan lebih banyak pendua, tetapi mungkin menghasilkan positif palsu.", - "machine_learning_max_recognition_distance": "Jarak pengecaman maksimum", - "machine_learning_max_recognition_distance_description": "Jarak maksimum antara dua muka untuk dianggap sebagai orang yang sama, antara 0-2. Menurunkan ini boleh menghalang pelabelan dua orang sebagai orang yang sama, manakala menaikkannya boleh menghalang pelabelan orang yang sama sebagai dua orang yang berbeza. Ambil perhatian bahawa adalah lebih mudah untuk menggabungkan dua orang daripada membelah satu orang kepada dua, jadi silap pada bahagian ambang yang lebih rendah apabila boleh.", - "machine_learning_min_detection_score": "Skor pengesanan minimum", - "machine_learning_min_detection_score_description": "Skor keyakinan minimum untuk wajah dikesan dari 0-1. Nilai yang lebih rendah akan mengesan lebih banyak muka tetapi mungkin menghasilkan positif palsu.", - "machine_learning_min_recognized_faces": "Minimum mengenali wajah", - "machine_learning_min_recognized_faces_description": "Bilangan minima wajah yang dikenali untuk seseorang dicipta. Peningkatan ini menjadikan Pengecaman Wajah lebih tepat atas kos meningkatkan peluang wajah tidak diberikan kepada seseorang.", - "machine_learning_ocr_enabled": "Dayakan OCR", - "machine_learning_ocr_enabled_description": "Jika dinyahdayakan, imej tidak akan melalui pengecaman teks.", - "machine_learning_ocr_max_resolution": "Resolusi Maksimum", - "machine_learning_ocr_max_resolution_description": "Pratonton yang melebihi resolusi ini akan diubah saiz sambil mengekalkan nisbah aspek. Nilai yang lebih tinggi adalah lebih tepat, tetapi mengambil masa pemprosesan yang lebih lama dan menggunakan lebih banyak memori.", - "machine_learning_ocr_model": "Model OCR", - "machine_learning_settings": "Tetapan Pembelajaran Mesin", - "machine_learning_settings_description": "Urus ciri dan tetapan pembelajaran mesin", - "machine_learning_smart_search": "Carian Pintar", - "machine_learning_smart_search_description": "Cari imej secara semantik menggunakan pembenaman CLIP", - "machine_learning_smart_search_enabled": "Dayakan carian pintar", - "machine_learning_smart_search_enabled_description": "Jika ditutup, gambar-gambar tidak akan dikodkan untuk carian pintar.", - "machine_learning_url_description": "URL pelayan pembelajaran mesin. Jika lebih daripada satu URL disediakan, setiap pelayan akan dicuba satu demi satu mengikut turutan, dari yang pertama hingga yang terakhir, sehingga salah satu memberi maklum balas yang berjaya. Pelayan yang tidak memberi maklum balas akan diabaikan sementara sehingga ia kembali dalam talian.", - "maintenance_settings": "Penyelenggaraan", - "maintenance_settings_description": "Letak Immich ke dalam mod penyelenggaraan", - "maintenance_start": "Mulakan mod penyelenggaraan", - "maintenance_start_error": "Gagal mulakan mod penyelenggaraan.", - "manage_concurrency": "Urus Concurrency", - "manage_log_settings": "Urus tetapan log", - "map_dark_style": "Tema gelap", - "map_enable_description": "Aktifkan ciri peta", - "map_gps_settings": "Tetapan Peta & GPS", - "map_gps_settings_description": "Urus Tetapan Peta & GPS (Geokod Terbalik)", - "map_implications": "Ciri peta bergantung pada perkhidmatan jubin luaran (tiles.immich.cloud)", - "map_light_style": "Tema terang", - "map_manage_reverse_geocoding_settings": "Urus tetapan Penentuan Alamat Songsang", - "map_reverse_geocoding": "Geokoding Sonsang", - "map_reverse_geocoding_enable_description": "Dayakan pengekodan geo terbalik", - "map_reverse_geocoding_settings": "Tetapan Pengekodan Geo Terbalik", - "map_settings": "Peta", - "map_settings_description": "Urus tetapan peta", - "map_style_description": "URL ke tema peta style.json", - "memory_cleanup_job": "Pembersihan memori", - "memory_generate_job": "Penjanaan memori", - "metadata_extraction_job": "Sari metadata", - "metadata_extraction_job_description": "Sari maklumat metadata dari setiap aset, seperti GPS, muka-muka, dan pelaraian", - "metadata_faces_import_setting": "Dayakan import muka", - "metadata_faces_import_setting_description": "Import muka daripada data EXIF imej dan fail sidecar", - "metadata_settings": "Tetapan Metadata", - "metadata_settings_description": "Urus tetapan metadata", - "migration_job": "Migrasi", - "migration_job_description": "Pindahkan imej kecil untuk aset-aset dan muka-muka kepada struktur folder terkini", - "nightly_tasks_cluster_faces_setting_description": "Jalankan pengecaman wajah kepada wajah baharu yang dijumpai", - "nightly_tasks_cluster_new_faces_setting": "Kumpulan wajah baharu", - "nightly_tasks_database_cleanup_setting": "Tugasan membersihkan pangkalan data", - "nightly_tasks_database_cleanup_setting_description": "Membersihkan data lama, luput dari pangkalan data", - "nightly_tasks_generate_memories_setting": "Menjana memori", - "nightly_tasks_generate_memories_setting_description": "Mencipta memori dari aset", - "nightly_tasks_missing_thumbnails_setting": "Menjana lakaran kecil yang hilang", - "nightly_tasks_missing_thumbnails_setting_description": "Aturan aset tanpa lakaran kecil untuk janaan lakaran kecil", - "nightly_tasks_settings": "Tetapan tugasan malam", - "nightly_tasks_settings_description": "Mengurus tugasan malam", - "nightly_tasks_start_time_setting": "Masa mula", - "nightly_tasks_start_time_setting_description": "Masa di mana pelayan mula bekerja pada tugasan malam", - "nightly_tasks_sync_quota_usage_setting": "Penyelarasan penggunaan kuota", - "nightly_tasks_sync_quota_usage_setting_description": "Kemaskini kuota simpanan pengguna, berdasarkan kepada penggunaan terkini", - "no_paths_added": "Tiada laluan yang ditambah", - "no_pattern_added": "Tiada corak ditambah", - "note_apply_storage_label_previous_assets": "Nota: Untuk menggunakan Label Storan pada aset yang dimuat naik sebelum ini, jalankan", - "note_cannot_be_changed_later": "NOTA: Ini tidak boleh diubah kemudian!", - "notification_email_from_address": "Dari alamat", - "notification_email_from_address_description": "Alamat e-mel penghantar, sebagai contoh: \"Pelayan Gambar Immich \". Pastikan menggunakan alamat yang dibenarkan anda untuk menghantar e-mel.", - "notification_email_host_description": "Hos e-mel pelayan (cth. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Abaikan ralat-ralat sijil", - "notification_email_ignore_certificate_errors_description": "Abaikan ralat pengesahan sijil TLS (tidak disyorkan)", - "notification_email_password_description": "Kata laluan untuk digunakan semasa membuat pengesahan dengan pelayan e-mel", - "notification_email_port_description": "Port pelayan e-mel (cth 25, 465 atau 587)", - "notification_email_sent_test_email_button": "Hantar e-mel ujian dan simpan", - "notification_email_setting_description": "Tetapan-tetapan untuk menghantar notifikasi e-mel", - "notification_email_test_email": "Hantar e-mel ujian", - "notification_email_test_email_failed": "Gagal untuk menghantar e-mel ujian, sila semak nilai-nilai anda", - "notification_email_test_email_sent": "E-mel ujian telah dihantar ke {email}. Sila semak peti masuk anda.", - "notification_email_username_description": "Nama pengguna untuk digunakan semasa mengesahkan dengan pelayan e-mel", - "notification_enable_email_notifications": "Dayakan notifikasi-notifikasi e-mel", - "notification_settings": "Tetapan Pemberitahuan", - "notification_settings_description": "Urus tetapan-tetapan notifikasi, termasuk e-mel", - "oauth_auto_launch": "Pelancaran automatik", - "oauth_auto_launch_description": "Mulakan aliran log masuk OAuth secara automatik apabila menavigasi ke halaman log masuk", - "oauth_auto_register": "Daftar secara automatik", - "oauth_auto_register_description": "Daftar secara automatik pengguna-pengguna baharu selepas mendaftar masuk dengan OAuth", - "oauth_button_text": "Teks butang", - "oauth_client_secret_description": "Diperlukan jika PKCE (Proof Key for Code Exchange) tidak disokong oleh penyedia OAuth", - "oauth_enable_description": "Log masuk dengan OAuth", - "oauth_mobile_redirect_uri": "URI ubah hala mudah alih", - "oauth_mobile_redirect_uri_override": "Penggantian URI ubah hala mudah alih", - "oauth_mobile_redirect_uri_override_description": "Aktifkan apabila pembekal OAuth tidak membenarkan URI mudah alih, seperti ''{callback}''", - "oauth_role_claim": "Tebus peranan", - "oauth_role_claim_description": "Automatik memberi kebenaran pentadbir berdasarkan tuntutan ini. Tuntutan ini mungkin mempunyai sama ada 'pengguna' atau 'pentadbir'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Urus tetapan log masuk OAuth", - "oauth_settings_more_details": "Untuk maklumat lanjut tentang ciri ini, rujuk ke dokumen.", - "oauth_storage_label_claim": "Tuntutan label storan", - "oauth_storage_label_claim_description": "Tetapkan label storan pengguna secara automatik kepada nilai tuntutan ini.", - "oauth_storage_quota_claim": "Tuntutan kuota storan", - "oauth_storage_quota_claim_description": "Tetapkan kuota storan pengguna secara automatik kepada nilai tuntutan ini.", - "oauth_storage_quota_default": "Kuota storan lalai (GiB)", - "oauth_storage_quota_default_description": "Kuota dalam GiB yang akan digunakan jika tiada tuntutan disediakan.", - "oauth_timeout": "Had Masa Permintaan", - "oauth_timeout_description": "Had masa untuk permintaan dalam milisaat", - "password_enable_description": "Log masuk dengan e-mel dan kata laluan", - "password_settings": "Kata Laluan Log Masuk", - "password_settings_description": "Urus tetapan-tetapan kata laluan log masuk", - "paths_validated_successfully": "Semua laluan berjaya disahkan", - "person_cleanup_job": "Pembersihan orang", - "quota_size_gib": "Saiz Kuota (GiB)", - "refreshing_all_libraries": "Menyegarkan semua perpustakaan", - "registration": "Pendaftaran Pentadbir", - "registration_description": "Memandangkan anda adalah pengguna pertama pada sistem, anda akan ditugaskan sebagai Pentadbir dan bertanggungjawab untuk tugas pentadbiran, serta pengguna tambahan yang akan anda tambah.", - "require_password_change_on_login": "Perlukan pengguna menukar kata laluan ketika log masuk pertama", - "reset_settings_to_default": "Tetapkan semula tetapan kepada lalai", - "reset_settings_to_recent_saved": "Tetapkan semula tetapan kepada tetapan yang disimpan baru-baru ini", - "scanning_library": "Mengimbas perpustakaan", - "search_jobs": "Cari tugasan…", - "send_welcome_email": "Hantar e-mel alu-aluan", - "server_external_domain_settings": "Domain luaran", - "server_external_domain_settings_description": "Domain untuk pautan kongsi awam, termasuk http(s)://", - "server_public_users": "Pengguna Awam", - "server_public_users_description": "Semua pengguna (nama dan e-mel) disenaraikan apabila menambahkan pengguna pada album kongsi. Apabila dilumpuhkan, senarai pengguna hanya akan tersedia kepada pengguna pentadbir.", - "server_settings": "Tetapan Pelayan", - "server_settings_description": "Urus tetapan pelayan", - "server_welcome_message": "Mesej alu-aluan", - "server_welcome_message_description": "Mesej yang dipaparkan pada halaman log masuk.", - "sidecar_job": "Metadata sampingan", - "sidecar_job_description": "Temui atau segerakkan metadata sampingan daripada sistem fail", - "slideshow_duration_description": "Bilangan saat untuk memaparkan setiap imej", - "smart_search_job_description": "Jalankan pembelajaran mesin pada aset-aset untuk menyokong carian pintar", - "storage_template_date_time_description": "Cap masa penciptaan aset digunakan untuk maklumat masa dan tarikh", - "storage_template_date_time_sample": "Contoh masa {date}", - "storage_template_enable_description": "Dayakan enjin templat storan", - "storage_template_hash_verification_enabled": "Pengesahan hac didayakan", - "storage_template_hash_verification_enabled_description": "Mendayakan pengesahan hac, jangan lumpuhkan melainkan anda pasti akan implikasinya", - "storage_template_migration": "Penghijrahan templat storan", - "storage_template_migration_description": "Gunakan {template} semasa pada aset-aset yang dimuat naik sebelum ini", - "storage_template_migration_info": "Templat storan akan menukar semua sambungan fail kepada huruf kecil. Perubahan templat hanya akan digunakan untuk aset baru. Untuk menggunakan templat ini secara retroaktif pada aset yang telah dimuat naik sebelum ini, jalankan {job}.", - "storage_template_migration_job": "Kerja Migrasi Templat Storan", - "storage_template_more_details": "Untuk butiran lanjut tentang ciri ini, rujuk kepada Templat Storan dan implikasi", - "storage_template_onboarding_description_v2": "Apabila diaktifkan, ciri ini akan mengatur fail secara automatik berdasarkan templat yang ditetapkan oleh pengguna. Untuk maklumat lanjut, sila rujuk dokumentasi.", - "storage_template_path_length": "Anggaran kepanjangan laluan: {length, number}/{limit, number}", - "storage_template_settings": "Templat Storan", - "storage_template_settings_description": "Urus struktur folder dan nama fail aset dimuat naik", - "storage_template_user_label": "{label} ialah Label Storan pengguna", - "system_settings": "Tetapan Sistem", - "tag_cleanup_job": "Pembersihan tag", - "template_email_available_tags": "Anda boleh menggunakan pembolehubah berikut dalam templat anda: {tags}", - "template_email_if_empty": "Jika templat kosong, e-mel yang terpilih sebelum ini akan digunakan.", - "template_email_invite_album": "Templat Jemputan Album", - "template_email_preview": "Previu", - "template_email_settings": "Templat E-mel", - "template_email_update_album": "Templat Kemas kini Album", - "template_email_welcome": "Templat e-mel alu-aluan", - "template_settings": "Templat Pemberitahuan", - "template_settings_description": "Urus templat tersuai untuk notifikasi", - "theme_custom_css_settings": "CSS tersuai", - "theme_custom_css_settings_description": "Lembaran Gaya Lata membolehkan reka bentuk Immich disuaikan.", - "theme_settings": "Tetapan Tema", - "theme_settings_description": "Urus penyesuaian antara muka web Immich", - "thumbnail_generation_job": "Jana Imej Kenit", - "thumbnail_generation_job_description": "Janakan imej kenit yang besar, kecil, dan kabur untuk setiap aset, serta imej kenit untuk setiap orang", - "transcoding_acceleration_api": "API Pecutan", - "transcoding_acceleration_api_description": "API yang akan berinteraksi dengan peranti anda untuk mempercepatkan transcoding. Tetapan ini adalah 'usaha terbaik': ia akan berundur kepada transkod perisian apabila gagal. VP9 mungkin berfungsi atau tidak bergantung pada perkakasan anda.", - "transcoding_acceleration_nvenc": "NVENC (memerlukan GPU NVIDIA)", - "transcoding_acceleration_qsv": "Pensegerakan Pantas (memerlukan CPU Intel generasi ke-7 atau lebih baru)", - "transcoding_acceleration_rkmpp": "RKMPP (hanya pada SOC Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codec audio yang diterima", - "transcoding_accepted_audio_codecs_description": "Pilih codec audio yang tidak perlu ditranskodkan. Hanya digunakan untuk dasar transkod tertentu.", - "transcoding_accepted_containers": "Bekas yang diterima", - "transcoding_accepted_containers_description": "Pilih format bekas yang tidak perlu ditukar semula kepada MP4. Hanya digunakan untuk dasar transkod tertentu.", - "transcoding_accepted_video_codecs": "Codec video yang diterima", - "transcoding_accepted_video_codecs_description": "Pilih codec video yang tidak perlu ditranskodkan. Hanya digunakan untuk dasar transkod tertentu.", - "transcoding_advanced_options_description": "Pilihan yang tidak perlu diubah untuk kebanyakan pengguna", - "transcoding_audio_codec": "Codec audio", - "transcoding_audio_codec_description": "Opus ialah pilihan kualiti tertinggi, tetapi mempunyai keserasian yang lebih rendah dengan peranti atau perisian lama.", - "transcoding_bitrate_description": "Video yang lebih tinggi daripada kadar bit maksimum atau tidak dalam format yang diterima", - "transcoding_codecs_learn_more": "Untuk mengetahui lebih lanjut tentang istilah yang digunakan di sini, rujuk dokumentasi FFmpeg untuk codec H.264, codec HEVC dan codec VP9.", - "transcoding_constant_quality_mode": "Mod kualiti berterusan", - "transcoding_constant_quality_mode_description": "ICQ lebih baik daripada CQP, tetapi ada beberapa peranti pecutan perkakasan tidak menyokong mod ini. Menetapkan pilihan ini akan memilih mod yang ditentukan apabila menggunakan pengekodan berasaskan kualiti. Diabaikan oleh NVENC kerana ia tidak menyokong ICQ.", - "transcoding_constant_rate_factor": "Faktor kadar malar (-crf)", - "transcoding_constant_rate_factor_description": "Tahap kualiti video. Nilai biasa ialah 23 untuk H.264, 28 untuk HEVC, 31 untuk VP9 dan 35 untuk AV1. Lebih rendah adalah lebih baik, tetapi menghasilkan fail yang lebih besar.", - "transcoding_disabled_description": "Jangan transcode mana-mana video, boleh memecahkan main balik pada sesetengah pelanggan", - "transcoding_encoding_options": "Pilihan Pengekodan", - "transcoding_encoding_options_description": "Tetapkan codec, resolusi, kualiti dan pilihan lain untuk video yang dikodkan", - "transcoding_hardware_acceleration": "Pecutan Perkakasan", - "transcoding_hardware_acceleration_description": "Eksperimen: pengekodan semula yang lebih pantas tetapi mungkin mengurangkan kualiti pada kadar bit yang sama", - "transcoding_hardware_decoding": "Penyahkodan perkakasan", - "transcoding_hardware_decoding_setting_description": "Mendayakan pecutan hujung ke hujung dan bukannya hanya mempercepatkan pengekodan. Mungkin tidak berfungsi pada semua video.", - "transcoding_max_b_frames": "Bingkai-B maksimum", - "transcoding_max_b_frames_description": "Nilai yang lebih tinggi meningkatkan kecekapan mampatan, tetapi memperlahankan pengekodan. Mungkin tidak serasi dengan pecutan perkakasan pada peranti lama. 0 melumpuhkan bingkai B, manakala -1 menetapkan nilai ini secara automatik.", - "transcoding_max_bitrate": "Kadar bit maksimum", - "transcoding_max_bitrate_description": "Menetapkan bitrate maksimum boleh menjadikan saiz fail lebih mudah diramal dengan sedikit pengorbanan kualiti. Pada 720p, nilai biasa ialah 2600 kbit/s untuk VP9 atau HEVC, atau 4500 kbit/s untuk H.264. Dimatikan jika ditetapkan kepada 0. Apabila tiada unit dinyatakan, k (untuk kbit/s) diandaikan; oleh itu 5000, 5000k dan 5M (untuk Mbit/s) adalah setara.", - "transcoding_max_keyframe_interval": "Selangan keyframe maksimum", - "transcoding_max_keyframe_interval_description": "Menetapkan jarak bingkai maksimum antara keyframes. Nilai yang lebih rendah memburukkan kecekapan mampatan, tetapi menambah baik masa carian dan mungkin meningkatkan kualiti dalam adegan dengan pergerakan pantas. 0 menetapkan nilai ini secara automatik.", - "transcoding_optimal_description": "Video yang lebih tinggi daripada resolusi sasaran atau tidak dalam format yang diterima", - "transcoding_policy": "Dasar Transkod", - "transcoding_policy_description": "Tetapkan masa bila video akan ditranskodkan", - "transcoding_preferred_hardware_device": "Pilihan peranti perkakasan", - "transcoding_preferred_hardware_device_description": "Terpakai hanya untuk VAAPI dan QSV. Menetapkan nod dri yang digunakan untuk transkod perkakasan.", - "transcoding_preset_preset": "Pratetap (-preset)", - "transcoding_preset_preset_description": "Kelajuan mampatan. Pratetap yang lebih perlahan menghasilkan fail yang lebih kecil dan meningkatkan kualiti apabila pada kadar bit tertentu. VP9 mengabaikan kelajuan di atas 'lebih cepat'.", - "transcoding_reference_frames": "Bingkai rujukan", - "transcoding_reference_frames_description": "Bilangan bingkai untuk dirujuk semasa memampatkan bingkai yang diberikan. Nilai yang lebih tinggi meningkatkan kecekapan mampatan, tetapi memperlahankan pengekodan. 0 menetapkan nilai ini secara automatik.", - "transcoding_required_description": "Hanya untuk video yang tidak dalam format yang diterima", - "transcoding_settings": "Tetapan Transkod Video", - "transcoding_settings_description": "Urus video yang hendak ditranskod dan cara memprosesnya", - "transcoding_target_resolution": "Resolusi sasaran", - "transcoding_target_resolution_description": "Peleraian yang lebih tinggi boleh mengekalkan lebih banyak butiran tetapi mengambil masa lebih lama untuk mengekod, mempunyai saiz fail yang lebih besar dan boleh mengurangkan responsif app.", - "transcoding_temporal_aq": "AQ sementara", - "transcoding_temporal_aq_description": "Terpakai hanya untuk NVEC. Temporal Adaptive Quantization meningkatkan kualiti adegan yang berperinci tinggi dan berpunya rendah gerakan. Mungkin tidak serasi dengan peranti lama.", - "transcoding_threads": "Benang", - "transcoding_threads_description": "Nilai yang lebih tinggi membawa kepada pengekodan yang lebih pantas, tetapi meninggalkan lebih sedikit ruang untuk pemproses tugas lain semasa aktif. Nilai ini tidak boleh lebih daripada bilangan teras CPU. Memaksimumkan penggunaan jika ditetapkan kepada 0.", - "transcoding_tone_mapping": "Pemetaan nada", - "transcoding_tone_mapping_description": "Percubaan untuk mengekalkan penampilan video HDR apabila ditukar kepada SDR. Setiap algoritma membuat pertukaran yang berbeza untuk warna, perincian dan kecerahan. Hable mengekalkan perincian, Mobius mengekalkan warna, dan Reinhard mengekalkan kecerahan.", - "transcoding_transcode_policy": "Dasar transkod", - "transcoding_transcode_policy_description": "Dasar untuk bila video perlu ditranskod. Video HDR akan sentiasa ditranskod (kecuali jika pengekodan semula dinyahdayakan).", - "transcoding_two_pass_encoding": "Pengekodan dua lelaran", - "transcoding_two_pass_encoding_setting_description": "Transkod dalam dua lelaran untuk menghasilkan video yang ditranskod dengan kualiti lebih baik. Apabila kadar bit maksimum diaktifkan (diperlukan untuk berfungsi dengan H.264 dan HEVC), mod ini akan menggunakan julat kadar bit berdasarkan kadar bit maksimum dan mengabaikan CRF. Untuk VP9, CRF boleh digunakan jika kadar bit maksimum dinyahdayakan.", - "transcoding_video_codec": "Kodek video", - "transcoding_video_codec_description": "VP9 mempunyai kecekapan tinggi dan keserasian web yang baik, tetapi mengambil masa lebih lama untuk ditranskod. HEVC memberikan prestasi yang serupa, tetapi kurang serasi dengan web. H.264 sangat serasi dan pantas untuk ditranskod, tetapi menghasilkan fail yang jauh lebih besar. AV1 ialah kodek paling cekap tetapi tidak disokong pada peranti lama.", - "trash_enabled_description": "Dayakan ciri Tong Sampah", - "trash_number_of_days": "Bilangan hari", - "trash_number_of_days_description": "Bilangan hari untuk menyimpan aset dalam tong sampah sebelum dipadam secara kekal", - "trash_settings": "Tetapan Tong Sampah", - "trash_settings_description": "Urus tetapan tong sampah", - "user_cleanup_job": "Pembersihan pengguna", - "user_delete_delay": "Akaun dan aset {user} akan dijadualkan untuk dipadam secara kekal dalam {delay, plural, one {# hari} other {# hari}}.", - "user_delete_delay_settings": "Kelewatan pemadaman", - "user_delete_delay_settings_description": "Bilangan hari selepas penghapusan sebelum akaun dan aset pengguna dipadam secara kekal. Tugasan pemadaman pengguna dijalankan pada tengah malam untuk menyemak pengguna yang sedia untuk dipadam. Perubahan pada tetapan ini akan dinilai semasa pelaksanaan seterusnya.", - "user_delete_immediately": "Akaun dan aset {user} akan dimasukkan ke dalam baris gilir untuk dipadam secara kekal serta-merta.", - "user_delete_immediately_checkbox": "Masukkan pengguna dan aset ke dalam baris gilir untuk dipadam serta-merta", - "user_details": "Butiran Pengguna", - "user_management": "Pengurusan Pengguna", - "user_password_has_been_reset": "Katalaluan pengguna telah ditetapkan semula:", - "user_password_reset_description": "Sila berikan katalaluan sementara kepada pengguna dan maklumkan bahawa mereka perlu menukar katalaluan semasa log masuk yang seterusnya.", - "user_restore_description": "Akaun {user} akan dipulihkan.", - "user_restore_scheduled_removal": "Pulihkan pengguna – pemadaman dijadualkan pada {date, date, long}", - "user_settings": "Tetapan Pengguna", - "user_settings_description": "Urus tetapan pengguna", - "version_check_enabled_description": "Dayakan semakan versi", - "version_check_implications": "Ciri semakan versi bergantung kepada komunikasi berkala dengan github.com", - "version_check_settings": "Semakan Versi", - "version_check_settings_description": "Dayakan/nyahdayakan notifikasi versi baharu", - "video_conversion_job": "Transkod video", - "video_conversion_job_description": "Transkod video untuk keserasian yang lebih luas dengan pelayar dan peranti" - }, - "admin_email": "Emel Pentadbir", - "admin_password": "Kata laluan Pentadbir", - "administration": "Pentadbiran", - "advanced": "Lanjutan", - "advanced_settings_enable_alternate_media_filter_subtitle": "Gunakan pilihan ini untuk menapis media semasa penyegerakan berdasarkan kriteria alternatif. Hanya cuba jika anda menghadapi masalah dengan aplikasi mengesan semua album.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAL] Gunakan penapis penyelarasan album peranti alternatif", - "advanced_settings_log_level_title": "Tahap log: {level}", - "advanced_settings_prefer_remote_subtitle": "Sesetengah peranti sangat perlahan untuk memuatkan imej kecil daripada aset lokal. Aktifkan tetapan ini untuk memuatkan imej dari jauh sebagai gantinya.", - "advanced_settings_prefer_remote_title": "Utamakan imej jauh", - "advanced_settings_proxy_headers_subtitle": "Tentukan pengepala proksi yang perlu dihantar oleh Immich dengan setiap permintaan rangkaian", - "advanced_settings_proxy_headers_title": "Pengepala Proksi khusus [EKSPERIMEN]", - "advanced_settings_self_signed_ssl_subtitle": "Langkau pengesahan sijil SSL untuk titik hujung pelayan. Diperlukan untuk sijil yang ditandatangani sendiri.", - "advanced_settings_self_signed_ssl_title": "Benarkan sijil SSL self-signed [EKSPERIMEN]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatik memadam atau memulihkan satu asset di peranti ini apabila tindakan itu diambil di dalam laman sesawang", - "advanced_settings_sync_remote_deletions_title": "Selaraskan pemadaman kawalan jauh [UJI KAJI]", - "advanced_settings_tile_subtitle": "Tetapan lanjutan pengguna", - "advanced_settings_troubleshooting_subtitle": "Dayakan ciri tambahan untuk menyelesaikan masalah", - "advanced_settings_troubleshooting_title": "Menyelesaikan masalah", - "age_months": "Umur {bulan, plural, satu {# bulan} lain {# bulan}}", - "age_year_months": "Umur 1 tahun, {bulan, plural, satu {# bulan} lain {# bulan}}", - "album_added": "Album telah ditambah", - "album_added_notification_setting_description": "Terima pemberitahuan e-mel apabila anda ditambah ke album perkongsian", - "album_cover_updated": "Album dikemas kini", - "album_leave": "Tinggalkan album?", - "album_leave_confirmation": "Adakah anda pasti mahu meninggalkan {album} ini?", - "album_name": "Nama Album", - "album_remove_user": "Buang pengguna?", - "album_remove_user_confirmation": "Adakah anda pasti mahu membuang {user}?", - "album_share_no_users": "Nampaknya anda telah berkongsi album ini dengan semua pengguna atau anda tidak mempunyai mana-mana pengguna untuk dikongsi.", - "album_updated": "Album dikemas kini", - "album_updated_setting_description": "Terima pemberitahuan e-mel apabila album perkongsian mempunyai aset baharu", - "album_user_left": "Kiri {album}", - "album_user_removed": "{user} telah dibuang", - "album_with_link_access": "Benarkan sesiapa yang mempunyai pautan melihat foto dan individu dalam album ini.", - "deduplication_criteria_1": "Saiz imej dalam bait", - "deduplication_criteria_2": "Kiraan data EXIF", - "deduplication_info": "Maklumat Pendeduplikasian", - "deduplication_info_description": "Untuk prapilih aset secara automatik dan mengalih keluar pendua secara pukal, kami melihat pada:", - "default_locale": "Tempatan Lalai", - "delete": "Padam", - "delete_album": "Padam album", - "delete_api_key_prompt": "Adakah anda pasti mahu memadam kunci API ini?", - "delete_duplicates_confirmation": "Adakah anda pasti mahu memadam pendua ini secara kekal?", - "delete_key": "Padam kunci", - "delete_library": "Padam Pustaka", - "delete_link": "Padam pautan", - "delete_others": "Padam yang lain", - "delete_shared_link": "Padam pautan yang dikongsi", - "delete_tag": "Padam tag", - "delete_tag_confirmation_prompt": "Adakah anda pasti mahu memadam tag {tagName}?", - "delete_user": "Padam pengguna", - "deleted_shared_link": "Pautan kongsi yang dipadamkan", - "deletes_missing_assets": "Memadamkan aset yang hilang daripada cakera", - "description": "Penerangan", - "details": "Butiran", - "direction": "Arah", - "disabled": "Dilumpuhkan", - "disallow_edits": "Tolak pengeditan", - "discord": "Perselisihan", - "discover": "Terokai", - "dismiss_all_errors": "Tolak semua ralat", - "dismiss_error": "Tolak ralat", - "display_options": "Pilihan paparan", - "display_order": "Tertib paparan", - "display_original_photos": "Paparkan foto asal", - "display_original_photos_setting_description": "Mengutamakan pemaparan foto asal apabila melihat aset daripada imej kecil apabila aset asal serasi web. Ini boleh menyebabkan kelajuan paparan foto yang lebih perlahan.", - "do_not_show_again": "Jangan tunjukkan mesej ini lagi", - "documentation": "Dokumentasi", - "done": "Selesai", - "download": "Muat Turun", - "download_settings": "Muat Turun", - "download_settings_description": "Urus tetapan yang berkaitan dengan muat turun aset", - "downloading": "Memuat turun", - "search_by_description": "Carian secara huraian", - "search_by_description_example": "Hari mendaki di Sapa", - "text_recognition": "Pengecaman teks", - "theme": "Tema", - "theme_selection": "Pemilihan tema", - "theme_selection_description": "Tetapkan tema terang atau gelap secara automatik berdasarkan keutamaan sistem pada pelayar anda", - "theme_setting_asset_list_tiles_per_row_title": "Bilangan aset per lajar {count}", - "timeline": "Garis masa", - "total": "Jumlah", - "user_usage_stats": "Statistik penggunaan akaun", - "user_usage_stats_description": "Papar statistik penggunaan akaun", - "width": "Lebar", - "wifi_name": "Nama Wi-Fi", - "wrong_pin_code": "Kod PIN salah", - "year": "Tahun", - "years_ago": "{years, plural, other {# tahun lalu}}", - "yes": "Ya", - "you_dont_have_any_shared_links": "Anda tidak mempunyai apa-apa pautan yang dikongsi", - "your_wifi_name": "Nama Wi-Fi anda", - "zoom_image": "Zum Gambar", - "zoom_to_bounds": "Zum ke sempadan" -} +{} diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index 03cc792718..0967ef424b 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -1,2401 +1 @@ -{ - "about": "Om", - "account": "Konto", - "account_settings": "Kontoinnstillinger", - "acknowledge": "Bekreft", - "action": "Handling", - "action_common_update": "Oppdater", - "action_description": "Ett sett med handlinger som skal utføres på de filtrerede objekter", - "actions": "Handlinger", - "active": "Aktiv", - "active_count": "Aktiv: {count}", - "activity": "Aktivitet", - "activity_changed": "Aktiviteten er {enabled, select, true {aktivert} other {deaktivert}}", - "add": "Legg til", - "add_a_description": "Legg til beskrivelse", - "add_a_location": "Legg til sted", - "add_a_name": "Legg til navn", - "add_a_title": "Legg til tittel", - "add_action": "Legg til hendelse", - "add_action_description": "Trykk for å legge til en hendelse å utføre", - "add_assets": "Legg til objekter", - "add_birthday": "Legg til bursdag", - "add_endpoint": "Legg til endepunkt", - "add_exclusion_pattern": "Legg til ekskluderingsmønster", - "add_filter": "Legg til filter", - "add_filter_description": "Trykk for å legge til filter begrensning", - "add_location": "Legg til sted", - "add_more_users": "Legg til flere brukere", - "add_partner": "Legg til partner", - "add_path": "Legg til sti", - "add_photos": "Legg til bilder", - "add_tag": "Legg til merkelapp", - "add_to": "Legg til i…", - "add_to_album": "Legg til album", - "add_to_album_bottom_sheet_added": "Lagt til i {album}", - "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", - "add_to_album_bottom_sheet_some_local_assets": "Noen lokale elementer kunne ikke legges til i albumet", - "add_to_album_toggle": "Avhuking for {album}", - "add_to_albums": "Legg til i album", - "add_to_albums_count": "Legg til i album ({count})", - "add_to_bottom_bar": "Legg til i", - "add_to_shared_album": "Legg til delt album", - "add_upload_to_stack": "Legg til opplasting i stakken", - "add_url": "Legg til URL", - "add_workflow_step": "Trykk for å legge til oppgave i arbeidsflyten", - "added_to_archive": "Lagt til i arkivet", - "added_to_favorites": "Lagt til favoritter", - "added_to_favorites_count": "Lagt til {count, number} i favoritter", - "admin": { - "add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For å ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som slutter på \".tif\", bruk \"**/*.tif\". For å ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".", - "admin_user": "Administrasjonsbruker", - "asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger på disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For å gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.", - "authentication_settings": "Godkjenninger", - "authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentisering", - "authentication_settings_disable_all": "Er du sikker på at du ønsker å deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.", - "authentication_settings_reenable": "For å aktivere på nytt, bruk en Server Command.", - "background_task_job": "Bakgrunnsjobber", - "backup_database": "Opprett database-dump", - "backup_database_enable_description": "Aktiver database-dump", - "backup_keep_last_amount": "Antall database-dumps å beholde", - "backup_onboarding_1_description": "ekstern kopi i skyen eller på et annet fysisk sted.", - "backup_onboarding_2_description": "lokale kopier på forskjellige enheter. Dette inkluderer hovedfilene og en lokal sikkerhetskopi av disse filene.", - "backup_onboarding_3_description": "totale kopier av dataene dine, inkludert originalfilene. Dette inkluderer én ekstern kopi og to lokale kopier.", - "backup_onboarding_description": "En 3-2-1 sikkerhetskopieringsstrategi anbefales for å beskytte dataene dine. Du bør beholde kopier av opplastede bilder/videoer samt Immich-databasen for en omfattende sikkerhetskopieringsløsning.", - "backup_onboarding_footer": "For mer informasjon om sikkerhetskopiering av Immich, se dokumentasjonen.", - "backup_onboarding_parts_title": "En 3-2-1 sikkerhetskopi inkluderer:", - "backup_onboarding_title": "Sikkerhetskopier", - "backup_settings": "Database-dump", - "backup_settings_description": "Håndter innstillinger for database-dump.", - "cleared_jobs": "Ryddet opp jobber for: {job}", - "config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil", - "confirm_delete_library": "Vil du virkelig slette biblioteket {library}?", - "confirm_delete_library_assets": "Vil du virkelig slette dette biblioteket? Dette vil slette alt innhold ({count, plural, one {# element} other {# elementer}}) og tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli på disken.", - "confirm_email_below": "For å bekrefte, skriv inn \"{email}\" nedenfor", - "confirm_reprocess_all_faces": "Vil du virkelig behandle alle ansikter på nytt? Dette vil også fjerne navngitte personer.", - "confirm_user_password_reset": "Vil du virkelig tilbakestille passordet til {user}?", - "confirm_user_pin_code_reset": "Vil du virkelig tilbakestille PIN-koden til {user} ?", - "copy_config_to_clipboard_description": "Kopier nåværende systemkonfigurasjon som ett JSON objekt til utklippsmenyen", - "create_job": "Lag jobb", - "cron_expression": "Cron uttrykk", - "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. Crontab Guru", - "cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk", - "disable_login": "Deaktiver innlogging", - "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Search", - "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper når du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.", - "export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil", - "external_libraries_page_description": "Administrering for eksterne bibliotek", - "face_detection": "Ansiktsgjenkjennelse", - "face_detection_description": "Finn ansikter i bilder ved hjelp av maskinlæring. For videoer brukes bare miniatyrbildet. \"Alle\" går gjennom alle bilder (igjen). \"Tilbakestill\" fjerner all gjeldende ansiktsdata. \"Mangler\" legger til filer som ikke har blitt behandlet enda i køen. Oppdagede ansikter vil blir sendt til ansiktsgjenkjenning, og koblet til eksisterende eller nye personer.", - "facial_recognition_job_description": "Kobler oppdagede ansikt til personer. Dette utføres etter at ansiktssøk er fullført. \"Tilbakestill\" (om-)grupperer alle ansikt på nytt. \"Mangler\" stiller opp ansikt som ikke har blitt tilordnet en person ennå.", - "failed_job_command": "Kommandoen {command} feilet for jobb: {job}", - "force_delete_user_warning": "ADVARSEL: Dette vil umiddelbart fjerne brukeren og alle data. Dette kan ikke angres, og filene kan ikke gjenopprettes.", - "image_format": "Format", - "image_format_description": "WebP gir mindre filer enn JPEG, men er tregere å lage.", - "image_fullsize_description": "Bilde i full størrelse med strippet metadata, brukt når du zoomer inn", - "image_fullsize_enabled": "Aktiver generering av bilder i full størrelse", - "image_fullsize_enabled_description": "Generer bilder i full størrelse for ikke-nettvennlige formater. Når \"Foretrekk innebygd forhåndsvisning\" er aktivert, brukes innebygde forhåndsvisninger direkte uten konvertering. Påvirker ikke nettvennlige formater som JPEG.", - "image_fullsize_quality_description": "Bildekvalitet i full størrelse fra 1-100. Høyere er bedre, men produserer større filer.", - "image_fullsize_title": "Bildeinnstillinger for full størrelse", - "image_prefer_embedded_preview": "Foretrekk innebygd forhåndsvisning", - "image_prefer_embedded_preview_setting_description": "Bruk innebygd forhåndsvisning i RAW-bilder som inndata til bildebehandling når tilgjengelig. Dette kan gi mer nøyaktige farger for noen bilder, men kvaliteten er avhengig av kamera og bildet kan ha komprimeringsartefakter.", - "image_prefer_wide_gamut": "Foretrekk bredt fargespekter", - "image_prefer_wide_gamut_setting_description": "Bruk Display P3 for miniatyrbilder. Dette bevarer glød bedre i bilder med bredt fargerom, men det kan hende bilder ser annerledes ut på gamle enheter med en gammel nettleserversjon. sRBG bilder beholdes som sRGB for å unngå fargeforskyvninger.", - "image_preview_description": "Mellomstort bilde med strippet metadata, brukt når du ser på en enkelt ressurs og for maskinlæring", - "image_preview_quality_description": "Kvalitet på forhåndsvisning fra 1-100. Høyere er bedre, men genererer større filer og kan redusere hastigheten på systemet. Ved for lav verdi kan det påvirke kvaliteten på maskinlæringen.", - "image_preview_title": "Forhåndsvisningsinnstillinger", - "image_progressive": "Progressiv", - "image_progressive_description": "Kod JPEG-bilder progressivt for gradvis lasting av visning. Dette har ingen effekt på WebP-bilder.", - "image_quality": "Kvalitet", - "image_resolution": "Oppløsning", - "image_resolution_description": "Høyere oppløsninger kan bevare flere detaljer, men det tar lengre tid å kode, har større filstørrelser og kan redusere appresponsen.", - "image_settings": "Bildeinnstillinger", - "image_settings_description": "Administrer kvalitet og oppløsning på genererte bilder", - "image_thumbnail_description": "Små miniatyrbilder med strippet metadata, brukt når du ser på grupper av bilder som hovedtidslinjen", - "image_thumbnail_quality_description": "Miniatyrbildekvalitet fra 1-100. Høyere er bedre, men produserer større filer og kan redusere appens respons.", - "image_thumbnail_title": "Miniatyrbilde oppsett", - "import_config_from_json_description": "Importer systemkonfigurasjon ved å laste opp en JSON konfigurasjonsfil", - "job_concurrency": "{job} samtidighet", - "job_created": "Oppgave laget", - "job_not_concurrency_safe": "Denne jobben er ikke samtidlighet sikker.", - "job_settings": "Jobbinnstillinger", - "job_settings_description": "Administrer parallellkjøring for jobber", - "jobs_delayed": "{jobCount, plural, other {# forsinket}}", - "jobs_failed": "{jobCount, plural, other {# mislyktes}}", - "jobs_over_time": "Jobber som kjører over tiden", - "library_created": "Opprettet bibliotek: {library}", - "library_deleted": "Bibliotek slettet", - "library_details": "Bibliotekdetaljer", - "library_folder_description": "Angi en mappe som skal importeres. Denne mappen, inkludert undermapper, vil bli skannet for bilder og videoer.", - "library_remove_exclusion_pattern_prompt": "Er du sikker på at du vil fjerne dette ekskluderingsmønsteret?", - "library_remove_folder_prompt": "Er du sikker på at du vil fjerne denne import-mappen?", - "library_scanning": "Periodisk skanning", - "library_scanning_description": "Konfigurer periodisk skanning av bibliotek", - "library_scanning_enable_description": "Aktiver periodisk skanning av bibliotek", - "library_settings": "Eksternt bibliotek", - "library_settings_description": "Administrer innstillinger for eksterne bibliotek", - "library_tasks_description": "Skann eksterne biblioteker for nye og/eller endrede ressurser", - "library_updated": "Oppdatert bibliotek", - "library_watching_enable_description": "Overvåk eksterne bibliotek for filendringer", - "library_watching_settings": "Overvåkning av bibliotek [EKSPERIMENTELL]", - "library_watching_settings_description": "Se automatisk etter endrede filer", - "logging_enable_description": "Aktiver logging", - "logging_level_description": "Hvis aktivert, hvilket loggnivå som skal brukes.", - "logging_settings": "Loggføring", - "machine_learning_availability_checks": "Tilgjengelighetssjekk", - "machine_learning_availability_checks_description": "Automatisk oppdag og velg tilgjengelige maskinlæring-servere", - "machine_learning_availability_checks_enabled": "Aktiver tilgjengelighetssjekk", - "machine_learning_availability_checks_interval": "Sjekkintervall", - "machine_learning_availability_checks_interval_description": "Interval i millisekunder mellom tilgjengelighetssjekk", - "machine_learning_availability_checks_timeout": "Forespørselstimeout", - "machine_learning_availability_checks_timeout_description": "Tidsavbrudd i millisekunder for tilgjengelighetssjekk", - "machine_learning_clip_model": "Clip-modell", - "machine_learning_clip_model_description": "Navnet på en CLIP-modell finnes her. Merk at du må kjøre 'Smart Søk'-jobben på nytt for alle bilder etter at du har endret modell.", - "machine_learning_duplicate_detection": "Duplikatsøk", - "machine_learning_duplicate_detection_enabled": "Aktiver duplikatdeteksjon", - "machine_learning_duplicate_detection_enabled_description": "Hvis deaktivert: helt identiske filer vil fremdeles de-duplisert.", - "machine_learning_duplicate_detection_setting_description": "Bruk CLIP-embeddings for å finne sannsynlige duplikater", - "machine_learning_enabled": "Aktiver maskinlæring", - "machine_learning_enabled_description": "Hvis deaktivert vil alle ML-funksjoner deaktiveres uavhengig av innstillingene nedenfor.", - "machine_learning_facial_recognition": "Ansiktsgjenkjenning", - "machine_learning_facial_recognition_description": "Oppdag, gjenkjenn og grupper ansikter i bilder", - "machine_learning_facial_recognition_model": "Modell for ansiktsgjenkjenning", - "machine_learning_facial_recognition_model_description": "Modeller er listet i synkende rekkefølge basert på størrelse. Større modeller er tregere og bruker mer minne, men gir bedre resultat. Merk at du må kjøre Ansiktsgjenkjenning på nytt for alle bilder etter at du endrer en modell.", - "machine_learning_facial_recognition_setting": "Aktiver ansiktsgjenkjenning", - "machine_learning_facial_recognition_setting_description": "Hvis deaktivert, vil bilder ikke bli kodet for ansiktsgjenkjenning og vil ikke vises under \"Personer\" i Utforsk-siden.", - "machine_learning_max_detection_distance": "Maksimal deteksjonsavstand", - "machine_learning_max_detection_distance_description": "Maksimal avstand mellom to bilder for å vurdere dem som duplikater, varierende fra 0,001 til 0,1. Høyere verdier vil oppdage flere duplikater, men kan føre til falske positiver.", - "machine_learning_max_recognition_distance": "Maksimal gjenkjenningsavstand", - "machine_learning_max_recognition_distance_description": "Maksimal avstand mellom to ansikter for å bli vurdert som samme person, varierende fra 0 til 2. Å senke dette kan forhindre at to personer merkes som samme person, mens å øke det kan forhindre at samme person merkes som to forskjellige personer. Vær oppmerksom på at det er lettere å slå sammen to personer enn å dele en person i to, så det er lurt å velge en lavere terskel når det er mulig.", - "machine_learning_min_detection_score": "Minimum deteksjonsresultat", - "machine_learning_min_detection_score_description": "Minimum tillitspoeng for at et ansikt skal bli oppdaget, på en skala fra 0 til 1. Lavere verdier vil oppdage flere ansikter, men kan føre til falske positiver.", - "machine_learning_min_recognized_faces": "Minimum antall gjenkjente ansikter", - "machine_learning_min_recognized_faces_description": "Det minste antallet gjenkjente ansikter som kreves for å opprette en person. Å øke dette gjør ansiktsgjenkjenning mer presis på bekostning av å øke sjansen for at et ansikt ikke tilordnes til en person.", - "machine_learning_ocr": "Tekstgjenkjenning", - "machine_learning_ocr_description": "Bruk maskinlæring til å gjenkjenne tekst i bilder", - "machine_learning_ocr_enabled": "Aktiver tekstgjenkjenning", - "machine_learning_ocr_enabled_description": "Hvis deaktivert, vil ikke bilder bli tekstgjenkjent.", - "machine_learning_ocr_max_resolution": "Maksimal oppløsning", - "machine_learning_ocr_max_resolution_description": "Forhåndsvisninger over denne oppløsningen vil bli endret i størrelse samtidig som sideforholdet bevares. Høyere verdier er mer nøyaktige, men tar lengre tid å behandle og bruker mer minne.", - "machine_learning_ocr_min_detection_score": "Minimum deteksjonspoengsum", - "machine_learning_ocr_min_detection_score_description": "Minimum konfidenspoeng for at tekst skal kunne oppdages er fra 0–1. Lavere verdier vil oppdage mer tekst, men kan føre til falske positiver.", - "machine_learning_ocr_min_recognition_score": "Minste deteksjonspoengsum", - "machine_learning_ocr_min_score_recognition_description": "Minste deteksjonspoengsum for at tekst skal bli gjenkjent fra 0-1. Lavere verdier vil gjenkjenne mer tekst, men kan gi falske positiver.", - "machine_learning_ocr_model": "Tekstgjenkjenningsmodell", - "machine_learning_ocr_model_description": "Server modeller er mer nøyaktige enn mobilmodeller, men de bruker lengre tid og mer minne.", - "machine_learning_settings": "Innstillinger for maskinlæring", - "machine_learning_settings_description": "Administrer maskinlæringsfunksjoner og innstillinger", - "machine_learning_smart_search": "Smart søk", - "machine_learning_smart_search_description": "Søk etter bilder semantisk ved å bruke CLIP-embeddings", - "machine_learning_smart_search_enabled": "Aktiver smart søk", - "machine_learning_smart_search_enabled_description": "Hvis deaktivert, vil bilder ikke bli enkodet for smart søk.", - "machine_learning_url_description": "URL til maskinlærings-serveren. Hvis mer enn en URL er lagt inn, hver server vill bli forsøkt en om gangen frem til en svarer suksessfullt, i rekkefølge fra først til sist. Servere som ikke svarer vil midlertidig bli oversett frem til dem svarer igjen.", - "maintenance_delete_backup": "Slett sikkerhetskopi", - "maintenance_delete_backup_description": "Denne filen vil bli permanent slettet.", - "maintenance_delete_error": "Feilet ved sletting av sikkerhetskopi.", - "maintenance_restore_backup": "Gjenopprett Sikkerhetskopi", - "maintenance_restore_backup_description": "Immich vil bli sletter og gjenopprettet fra en valgt sikkerhetskopi. En sikkerhetskopi vil utføres før handlingen fortsetter.", - "maintenance_restore_backup_different_version": "Denne sikkerhetskopien ble laget med en annen versjon av Immich!", - "maintenance_restore_backup_unknown_version": "Kunne ikke fastslå versjon for sikkerhetskopi.", - "maintenance_restore_database_backup": "Gjenopprett sikkerhetskopi av database", - "maintenance_restore_database_backup_description": "Rull tilbake til en tidligere database ved å bruke en sikkerhetskopi", - "maintenance_settings": "Vedlikehold", - "maintenance_settings_description": "Sett Immich i vedlikeholdsmodus.", - "maintenance_start": "Bytt til vedlikeholdsmodus", - "maintenance_start_error": "Kunne ikke starte vedlikeholdsmodus.", - "maintenance_upload_backup": "Last opp sikkerhetskopi av databasen", - "maintenance_upload_backup_error": "Klarte ikke å laste opp sikkerhetskopi, er det en .sql/.sql.gz fil?", - "manage_concurrency": "Administrer samtidighet", - "manage_concurrency_description": "Naviger til jobb-siden for å justere samtidige jobber", - "manage_log_settings": "Administrer logginnstillinger", - "map_dark_style": "Mørk stil", - "map_enable_description": "Aktiver kartfunksjoner", - "map_gps_settings": "Kart & GPS Innstillinger", - "map_gps_settings_description": "Administrer innstillinger for kart og GPS (Reversert geokoding)", - "map_implications": "Kartfunksjonen er avhengig av en ekstern bilde tjeneste (tiles.immich.cloud)", - "map_light_style": "Lys stil", - "map_manage_reverse_geocoding_settings": "Administrer instillinger for Omvendt Geokoding", - "map_reverse_geocoding": "Omvendt geokoding", - "map_reverse_geocoding_enable_description": "Aktiver omvendt geokoding", - "map_reverse_geocoding_settings": "Innstillinger for omvendt geokoding", - "map_settings": "Kart", - "map_settings_description": "Administrer kartinnstillinger", - "map_style_description": "URL til et style.json-karttema", - "memory_cleanup_job": "Minneopprydding", - "memory_generate_job": "Minnegenerering", - "metadata_extraction_job": "Hent metadata", - "metadata_extraction_job_description": "Hent metadatainformasjon fra hver fil, for eksempel GPS-posisjon og oppløsning", - "metadata_faces_import_setting": "Aktiver ansikts importering", - "metadata_faces_import_setting_description": "Importer ansikt fra bilde EXIF data og tillegsfiler", - "metadata_settings": "Metadatainnstillinger", - "metadata_settings_description": "Administrer metadatainnstillinger", - "migration_job": "Migrering", - "migration_job_description": "Migrer miniatyrbilder for filer og ansikter til den nyeste mappestrukturen", - "nightly_tasks_cluster_faces_setting_description": "Kjør ansiktsgjenkjenning på nylige oppdagede ansikter", - "nightly_tasks_cluster_new_faces_setting": "Grupper nye ansikter", - "nightly_tasks_database_cleanup_setting": "Opprydningsjobber for databasen", - "nightly_tasks_database_cleanup_setting_description": "Rydder opp i gamle, utgåtte data fra databasen", - "nightly_tasks_generate_memories_setting": "Genererer minner", - "nightly_tasks_generate_memories_setting_description": "Generer nye minner fra elementer", - "nightly_tasks_missing_thumbnails_setting": "Generer manglende miniatyrbilder", - "nightly_tasks_missing_thumbnails_setting_description": "Legg til elementer i kø som mangler miniatyrbilder for generering", - "nightly_tasks_settings": "Innstillinger for nattjobber", - "nightly_tasks_settings_description": "Endre på nattjobber", - "nightly_tasks_start_time_setting": "Starttid", - "nightly_tasks_start_time_setting_description": "Tiden som serveren starter med nattjobbene", - "nightly_tasks_sync_quota_usage_setting": "Synkroniser kvotebruk", - "nightly_tasks_sync_quota_usage_setting_description": "Oppdater brukerkvote basert på nåværende bruk", - "no_paths_added": "Ingen filstier lagt til", - "no_pattern_added": "Ingen mønster lagt til", - "note_apply_storage_label_previous_assets": "Merk: For å bruke lagringsetiketten på tidligere opplastede filer, kjør", - "note_cannot_be_changed_later": "MERK: Dette kan ikke endres senere!", - "notification_email_from_address": "Fra adresse", - "notification_email_from_address_description": "Avsenderens e-postadresse, for eksempel: \"Immich Photo Server \". Bruk en e-postadresse du har tillatelse til å sende epost fra.", - "notification_email_host_description": "Verten til e-posts serveren (f.eks. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorer sertifikatfeil", - "notification_email_ignore_certificate_errors_description": "Ignorer valideringsfeil for TLS-sertifikat (ikke anbefalt)", - "notification_email_password_description": "Passordet som skal brukes ved autentisering med e-posts serveren", - "notification_email_port_description": "Porten til e-postserveren (f.eks. 25, 465 eller 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Bruk SMTPS ( SMTP over TLS)", - "notification_email_sent_test_email_button": "Send test e-post og lagre", - "notification_email_setting_description": "Innstillinger for å sende e-postvarsler", - "notification_email_test_email": "Send test e-post", - "notification_email_test_email_failed": "Klarte ikke å sende test-e-post, sjekk instillingene", - "notification_email_test_email_sent": "En test-e-post er sendt til {email}. Vennligst sjekk innboksen din.", - "notification_email_username_description": "Brukernavn som skal brukes ved autentisering med e-posts serveren", - "notification_enable_email_notifications": "Aktiver e-postvarsler", - "notification_settings": "Varselinnstillinger", - "notification_settings_description": "Administrer varselinnstillinger, inkludert e-post", - "oauth_auto_launch": "Automatisk oppstart", - "oauth_auto_launch_description": "Start OAuth-innloggingsflyten automatisk når du navigerer til innloggingssiden", - "oauth_auto_register": "Automatisk registrering", - "oauth_auto_register_description": "Registrer automatisk nye brukere etter innlogging med OAuth", - "oauth_button_text": "Knappetekst", - "oauth_client_secret_description": "Kreves for konfidensiell klient, eller hvis PKCE (Proof Key for Code Exchange) ikke støttes for offentlig klient.", - "oauth_enable_description": "Logg inn med OAuth", - "oauth_mobile_redirect_uri": "Mobil omdirigerings-URI", - "oauth_mobile_redirect_uri_override": "Mobil omdirigerings-URI overstyring", - "oauth_mobile_redirect_uri_override_description": "Aktiver når OAuth-leverandøren ikke tillater en mobil URI, som ''{callback}''", - "oauth_role_claim": "Krev Rolle", - "oauth_role_claim_description": "Gi automatisk administratortilgang basert på tilstedeværelsen av dette kravet. Kravet kan ha enten «bruker» eller «administrator».", - "oauth_settings": "OAuth", - "oauth_settings_description": "Administrer innstillinger for OAuth-innlogging", - "oauth_settings_more_details": "For mer informasjon om denne funksjonen, se dokumentasjonen.", - "oauth_storage_label_claim": "Lagringsetikettkrav", - "oauth_storage_label_claim_description": "Sett automatisk brukerens lagringsetikett til verdien av dette kravet.", - "oauth_storage_quota_claim": "Lagringskvotekrav", - "oauth_storage_quota_claim_description": "Sett automatisk brukerens lagringskvote til verdien av dette kravet.", - "oauth_storage_quota_default": "Standard lagringskvote (GiB)", - "oauth_storage_quota_default_description": "Kvote i GiB som skal brukes når ingen krav er oppgitt.", - "oauth_timeout": "Forespørselen tok for lang tid", - "oauth_timeout_description": "Tidsavbrudd for forespørsel i millisekunder", - "ocr_job_description": "Bruk maskinlæring for å gjenkjenne tekst i bilder", - "password_enable_description": "Logg inn med e-post og passord", - "password_settings": "Passordinnlogging", - "password_settings_description": "Administrer innstillinger for passordinnlogging", - "paths_validated_successfully": "Alle filstier validert uten problemer", - "person_cleanup_job": "Person opprydding", - "queue_details": "Kø-detaljer", - "queues": "Jobbkø", - "queues_page_description": "Administrer jobbkø-siden", - "quota_size_gib": "Kvote (GiB)", - "refreshing_all_libraries": "Oppdaterer alle biblioteker", - "registration": "Administrator registrering", - "registration_description": "Siden du er den første brukeren på systemet, vil du bli utnevnt til administrator og ha ansvar for administrative oppgaver. Du vil også opprette eventuelle nye brukere.", - "remove_failed_jobs": "Fjern feilede jobber", - "require_password_change_on_login": "Krev at brukeren endrer passord ved første pålogging", - "reset_settings_to_default": "Tilbakestill innstillinger til standard", - "reset_settings_to_recent_saved": "Tilbakestill innstillingene til de nylig lagrede innstillingene", - "scanning_library": "Søk biblioteket", - "search_jobs": "Søk etter jobber…", - "send_welcome_email": "Send velkomst-e-post", - "server_external_domain_settings": "Eksternt domene", - "server_external_domain_settings_description": "Domene for offentlige delingslenker, inkludert http(s)://", - "server_public_users": "Offentlige brukere", - "server_public_users_description": "Alle brukere (navn og epost) blir vist når en bruker blir lagt til et delt album. Når deaktivert, vil brukerne bare bli synlig for administratorer.", - "server_settings": "Serverinstillinger", - "server_settings_description": "Administrer serverinnstillinger", - "server_stats_page_description": "Administrer serverstatistikk", - "server_welcome_message": "Velkomstmelding", - "server_welcome_message_description": "En melding som vises på innloggingssiden.", - "settings_page_description": "Administrer innatillinger", - "sidecar_job": "Sidecar-metadata", - "sidecar_job_description": "Oppdag eller synkroniser sidecar-metadata fra filsystemet", - "slideshow_duration_description": "Antall sekunder for å vise hvert bilde", - "smart_search_job_description": "Kjør maskinlæring på filer for å støtte smart søk", - "storage_template_date_time_description": "Elementets opprettelsestidspunkt brukes for datotid-informasjonen", - "storage_template_date_time_sample": "Eksempeltid {date}", - "storage_template_enable_description": "Aktiver lagringsmal-motoren", - "storage_template_hash_verification_enabled": "Hash verifisering aktivert", - "storage_template_hash_verification_enabled_description": "Aktiver hasjverifisering. Ikke deaktiver dette med mindre du er sikker på konsekvensene", - "storage_template_migration": "Implementer lagringsmal", - "storage_template_migration_description": "Bruk gjeldende {template} på tidligere opplastede bilder", - "storage_template_migration_info": "Lagringsmalen vil endre filtypen til små bokstaver. Malendringer vil kun gjelde nye elementer. For å anvende malen på tidligere opplastede elementer, kjør {job}.", - "storage_template_migration_job": "Migreringsjobb for lagringsmal", - "storage_template_more_details": "For mer informasjon om denne funksjonen, se lagringsmalen og dens konsekvenser", - "storage_template_onboarding_description_v2": "Når aktivert vil denne funksjonen automatisk organisere filer basert på en brukerdefinert mal. For mer informasjon, se denne linken dokumentasjon.", - "storage_template_path_length": "Omtrentlig stilengdebegrensning: {length, number}/{limit, number}", - "storage_template_settings": "Lagringsmal", - "storage_template_settings_description": "Administrer mappestrukturen og filnavnet til opplastede fil", - "storage_template_user_label": "{label} er brukerens Lagringsetikett", - "system_settings": "Systeminstillinger", - "tag_cleanup_job": "Tagg-opprydding", - "template_email_available_tags": "Du kan bruke følgende variabler i din mal: {tags}", - "template_email_if_empty": "Hvis malen er tom, vil standard epost bli brut.", - "template_email_invite_album": "Inviter Album Mal", - "template_email_preview": "Forhåndsvis", - "template_email_settings": "E-postmaler", - "template_email_update_album": "Oppdater Album Mal", - "template_email_welcome": "Mal for velkomst-e-post", - "template_settings": "Varslings Mal", - "template_settings_description": "Administrer tilpassede maler for varsling", - "theme_custom_css_settings": "Egendefinert CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets gjør det mulig å tilpasse designet av Immich.", - "theme_settings": "Tema-innstillinger", - "theme_settings_description": "Administrer tilpasning av Immich webgrensesnitt", - "thumbnail_generation_job": "Generer miniatyrbilder", - "thumbnail_generation_job_description": "Generer store, små og uskarpe miniatyrbilder for hver fil, samt miniatyrbilder for hver person", - "transcoding_acceleration_api": "Akselerasjons-API", - "transcoding_acceleration_api_description": "API-et som vil samhandle med enheten din for å akselerere transcoding. Denne innstillingen er 'best effort': den vil falle tilbake til programvaretranscoding ved feil. VP9 kan eller kan ikke fungere avhengig av maskinvaren din.", - "transcoding_acceleration_nvenc": "NVENC (krever NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (krever Intel CPU fra 7. generasjon eller nyere)", - "transcoding_acceleration_rkmpp": "RKMPP (kun på Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Godkjente lydkodeker", - "transcoding_accepted_audio_codecs_description": "Velg hvilke lydkodeker som ikke trenger å transkodes. Brukes kun for visse transkode retningslinjer.", - "transcoding_accepted_containers": "aksepterte kontainere", - "transcoding_accepted_containers_description": "Velg hvilke containerformater som ikke trenger å bli remuxet til MP4. Brukes kun for visse transkoderingspolicyer.", - "transcoding_accepted_video_codecs": "Godkjente videokodeker", - "transcoding_accepted_video_codecs_description": "Velg hvilke videokodeker som ikke trenger å transkodes. Brukes kun for visse transcoding-regler.", - "transcoding_advanced_options_description": "Valg som de fleste brukere ikke trenger å endre", - "transcoding_audio_codec": "Lyd kodek", - "transcoding_audio_codec_description": "Opus er det alternativet med høyest kvalitet, men det har lavere kompatibilitet med eldre enheter eller programvare.", - "transcoding_bitrate_description": "Videoer som har høyere bithastighet enn maksimalt tillatt, eller som ikke er i et akseptert format", - "transcoding_codecs_learn_more": "For å lære mer om terminologien brukt her, referer til FFmpeg-dokumentasjonen for H.264 codec, HEVC codec og VP) codec.", - "transcoding_constant_quality_mode": "Konstant kvalitetsmodus", - "transcoding_constant_quality_mode_description": "ICQ er bedre enn CQP, men noen maskinvareakselerasjonsenheter støtter ikke denne modusen. Å angi denne innstillingen vil foretrekke den angitte modusen ved bruk av kvalitetsbasert koding. Ignorert av NVENC da det ikke støtter ICQ.", - "transcoding_constant_rate_factor": "Konstant ratefaktor (-crf)", - "transcoding_constant_rate_factor_description": "Nivået på videokvaliteten. Typiske verdier er 23 for H.264, 28 for HEVC, 31 for VP9 og 35 for AV1. Lavere verdier gir bedre kvalitet, men større filstørrelser.", - "transcoding_disabled_description": "Ikke transkoder noen videoer; dette kan føre til avspillingsproblemer på visse klienter", - "transcoding_encoding_options": "Kodek-alternativer", - "transcoding_encoding_options_description": "Sett kodeks, oppløsning, kvalitet og andre valg for koding av videoer", - "transcoding_hardware_acceleration": "Maskinvareakselerasjon", - "transcoding_hardware_acceleration_description": "Eksperimentell: raskere transkoding, men kan ha lavere kvalitet ved samme bithastighet", - "transcoding_hardware_decoding": "Maskinvaredekoding", - "transcoding_hardware_decoding_setting_description": "Aktiverer ende-til-ende akselerasjon i stedet for bare akselerering av koding. Vil ikke fungere med alle videoer.", - "transcoding_max_b_frames": "Maksimalt antall B-frames", - "transcoding_max_b_frames_description": "Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. Kan være inkompatibelt med maskinvareakselerasjon på eldre enheter. 0 deaktiverer B-rammer, mens -1 setter verdien automatisk.", - "transcoding_max_bitrate": "Maksimal bithastighet", - "transcoding_max_bitrate_description": "Å sette en maksimal bithastighet kan gjøre filstørrelsene mer forutsigbare med en liten kostnad for kvaliteten. For 720p er typiske verdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Deaktivert hvis satt til 0. Når ingen verdi er spesifisert er k (for kbit/s) forventet slik at: 5000,5000k og 5M (for Mbit/s) er like verdier.", - "transcoding_max_keyframe_interval": "Maksimal referansebilde intervall", - "transcoding_max_keyframe_interval_description": "Setter maksimalt antall bilder mellom referansebilder. Lavere verdier reduserer kompresjonseffektiviteten, men forbedrer søketider og kan forbedre kvaliteten i scener med rask bevegelse. 0 setter verdien automatisk.", - "transcoding_optimal_description": "Videoer som har høyere oppløsning enn målopppløsningen eller som ikke er i et akseptert format", - "transcoding_policy": "Retningslinjer for omkoding", - "transcoding_policy_description": "Velg når en video vil blir omkodet", - "transcoding_preferred_hardware_device": "Foretrukken maskinvareenhet", - "transcoding_preferred_hardware_device_description": "Gjelder bare for VAAPI og QSV. Angir DRI-node brukt for maskinvaretranscoding.", - "transcoding_preset_preset": "Forhåndsinnstilling (-preset)", - "transcoding_preset_preset_description": "Komprimeringshastighet. Tregere forhåndsinnstillinger produserer mindre filer og øker kvaliteten når man sikter mot en bestemt bithastighet. VP9 ignorerer hastigheter over \"faster\".", - "transcoding_reference_frames": "Referansebilder", - "transcoding_reference_frames_description": "Antall bilder som skal refereres til når en gitt ramme komprimeres. Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. 0 setter denne verdien automatisk.", - "transcoding_required_description": "Bare videoer som ikke er i et akseptert format", - "transcoding_settings": "Innstillinger for videotranskoding", - "transcoding_settings_description": "Administrer hvilke videoer å omkode og hvordan behandle dem", - "transcoding_target_resolution": "Endelig oppløsning", - "transcoding_target_resolution_description": "Høyere oppløsninger kan bevare mer detaljer, men tar lengre tid å kode, resulterer i større filstørrelser, og kan redusere appens responsivitet.", - "transcoding_temporal_aq": "Midlertidig AQ", - "transcoding_temporal_aq_description": "Gjelder kun for NVENC. Øker kvaliteten på scener med høy detaljgrad og lav bevegelse. Kan være inkompatibelt med eldre enheter.", - "transcoding_threads": "Tråder", - "transcoding_threads_description": "Høyere verdier fører til raskere koding, men gir mindre plass for serveren til å behandle andre oppgaver mens den er aktiv. Verdien bør ikke være mer enn antall CPU-kjerner. Maksimerer utnyttelsen hvis satt til 0.", - "transcoding_tone_mapping": "Tone mapping", - "transcoding_tone_mapping_description": "Forsøker å bevare utseendet til HDR-videoer når de konverteres til SDR. Hver algoritme gjør ulike avveininger mellom farge, detaljer og lysstyrke. Hable bevarer detaljer, Mobius bevarer farge, og Reinhard bevarer lysstyrke.", - "transcoding_transcode_policy": "Transkode retningslinjer", - "transcoding_transcode_policy_description": "Retningslinjer for når en video skal transkodes. HDR-videoer vil alltid bli transkodet (unntatt hvis transkoding er deaktivert).", - "transcoding_two_pass_encoding": "To-passert koding", - "transcoding_two_pass_encoding_setting_description": "Transkoding i to pass for å produsere bedre kodede videoer. Når maksimal bithastighet er aktivert (nødvendig for å fungere med H.264 og HEVC), bruker denne modusen et bithastighetsområde basert på maksimal bithastighet og ignorerer CRF. For VP9 kan CRF brukes hvis maksimal bithastighet er deaktivert.", - "transcoding_video_codec": "Videokodek", - "transcoding_video_codec_description": "VP9 har høy effektivitet og nett-kompatibilitet, men tar lengre tid å transkode. HEVC utfører tilsvarende, men har lavere nett-kompatibilitet. H.264 er bredt kompatibelt og raskt å transkode, men produserer mye større filer. AV1 er den mest effektive kodeken, men mangler støtte på eldre enheter.", - "trash_enabled_description": "Aktiver Papirkurv-funksjoner", - "trash_number_of_days": "Antall dager", - "trash_number_of_days_description": "Antall dager å beholde filer i papirkurven før de fjernes permanent", - "trash_settings": "Innstillinger for papirkurv", - "trash_settings_description": "Administrer papirkurv-innstillinger", - "unlink_all_oauth_accounts": "Koble fra alle OAuth-kontoer", - "unlink_all_oauth_accounts_description": "Husk å koble fra alle OAuth-kontoer før du migrerer til ny leverandør.", - "unlink_all_oauth_accounts_prompt": "Vil du virkelig koble fra alle OAuth-kontoer? Dette vil nullstille OAuth ID for hver bruker, og kan ikke angres.", - "user_cleanup_job": "Bruker opprydning", - "user_delete_delay": "{user}s konto og elementer vil legges i kø for permanent sletting om {delay, plural, one {# dag} other {# dager}}.", - "user_delete_delay_settings": "Sletteforsinkelse", - "user_delete_delay_settings_description": "Antall dager etter fjerning før en brukerkonto og dens filer permanent slettes. Brukerfjerningsjobben kjører ved midnatt for å sjekke etter brukere som er klare for sletting. Endringer i denne innstillingen vil bli evaluert ved neste utførelse.", - "user_delete_immediately": "{user}s konto og elementer vil bli lagt i kø for permanent sletting umiddelbart.", - "user_delete_immediately_checkbox": "Legg bruker og elementer i kø for umiddelbar sletting", - "user_details": "Brukerdetaljer", - "user_management": "Brukeradministrasjon", - "user_password_has_been_reset": "Passordet til brukeren har blitt tilbakestilt:", - "user_password_reset_description": "Vennligst oppgi det midlertidige passordet til brukeren og informer dem om at de må endre passordet ved neste pålogging.", - "user_restore_description": "{user}s konto vil bli gjenopprettet.", - "user_restore_scheduled_removal": "Gjenopprett bruker - planlagt sletting den {date, date, long}", - "user_settings": "Brukerinnstillinger", - "user_settings_description": "Administrer brukerinnstillinger", - "user_successfully_removed": "Bruker {email} har blitt fjernet.", - "users_page_description": "Administrer brukere", - "version_check_enabled_description": "Aktiver periodiske forespørsler til GitHub for å sjekke etter nye utgivelser", - "version_check_implications": "Versjonssjekkfunksjonen baserer seg på periodisk kommunikasjon med github.com", - "version_check_settings": "Versjonssjekk", - "version_check_settings_description": "Aktiver/deaktiver varsel om ny versjon", - "video_conversion_job": "Transkod videoer", - "video_conversion_job_description": "Konverter videoer for bedre kompatibilitet med nettlesere og enheter" - }, - "admin_email": "Administrator e-post", - "admin_password": "Administratorpassord", - "administration": "Administrasjon", - "advanced": "Avansert", - "advanced_settings_clear_image_cache": "Tøm Bildecache", - "advanced_settings_clear_image_cache_error": "Feiled ved tømming av bildecache", - "advanced_settings_clear_image_cache_success": "Vellykket tømt {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Bruk denne innstillingen for å filtrere mediefiler under synkronisering basert på alternative kriterier. Bruk kun denne innstillingen dersom man opplever problemer med at applikasjonen ikke oppdager alle album.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTELT] Bruk alternativ enhet album synk filter", - "advanced_settings_log_level_title": "Loggnivå: {level}", - "advanced_settings_prefer_remote_subtitle": "Noen enheter er veldige trege til å hente miniatyrbilder fra enheten. Aktiver denne innstillingen for å hente de eksternt istedenfor.", - "advanced_settings_prefer_remote_title": "Foretrekk eksterne bilder", - "advanced_settings_proxy_headers_subtitle": "Definer proxy headere som Immich skal benytte ved enhver nettverksrequest", - "advanced_settings_proxy_headers_title": "Proxy headere[EKSPERIMENTELL]", - "advanced_settings_readonly_mode_subtitle": "Aktiverer skrivebeskyttet modus der bildene bare kan vises. Ting som å velge flere bilder, dele, caste og slette er deaktivert. Aktiver/deaktiver skrivebeskyttet modus via brukerens avatar fra hovedskjermen", - "advanced_settings_readonly_mode_title": "Skrivebeskyttet modus", - "advanced_settings_self_signed_ssl_subtitle": "Hopper over SSL sertifikatverifikasjon for server-endepunkt. Påkrevet for selvsignerte sertifikater.", - "advanced_settings_self_signed_ssl_title": "Tillat selvsignerte SSL sertifikater [EKSPERIMENTELL]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatisk slette eller gjenopprette filer på denne enheten hvis den handlingen har blitt gjort på nettsiden", - "advanced_settings_sync_remote_deletions_title": "Synk sletting fra nettsiden [EKSPERIMENTELT]", - "advanced_settings_tile_subtitle": "Avanserte brukerinnstillinger", - "advanced_settings_troubleshooting_subtitle": "Aktiver ekstra funksjoner for feilsøking", - "advanced_settings_troubleshooting_title": "Feilsøking", - "age_months": "Alder {months, plural, one {# måned} other {# måneder}}", - "age_year_months": "Alder 1 år, {months, plural, one {# måned} other {# måneder}}", - "age_years": "{years, plural, other {Alder #}}", - "album": "Album", - "album_added": "Album opprettet", - "album_added_notification_setting_description": "Motta en e-postvarsling når du legges til i et delt album", - "album_cover_updated": "Albumomslag oppdatert", - "album_delete_confirmation": "Vil du virkelig slette albumet {album}?", - "album_delete_confirmation_description": "Hvis dette albumet deles, vil andre brukere miste tilgangen til dette.", - "album_deleted": "Album slettet", - "album_info_card_backup_album_excluded": "EKSKLUDERT", - "album_info_card_backup_album_included": "INKLUDERT", - "album_info_updated": "Albuminformasjon oppdatert", - "album_leave": "Forlate album?", - "album_leave_confirmation": "Vil du virkelig forlate {album}?", - "album_name": "Albumnavn", - "album_options": "Albumalternativer", - "album_remove_user": "Fjerne bruker?", - "album_remove_user_confirmation": "Vil du virkelig fjerne {user}?", - "album_search_not_found": "Ingen album ble funnet som traff ditt søk", - "album_selected": "Album valgt", - "album_share_no_users": "Dette albumet er allerede delt med du har delt dette albumet med alle brukere, eller du ikke har noen brukere å dele det med.", - "album_summary": "Oppsummering av album", - "album_updated": "Album oppdatert", - "album_updated_setting_description": "Motta e-postvarsling når et delt album får nye filer", - "album_upload_assets": "Last opp medier fra datamaskinen og legg til i album", - "album_user_left": "Forlot {album}", - "album_user_removed": "Fjernet {user}", - "album_viewer_appbar_delete_confirm": "Vil du virkelig slette dette albumet fra kontoen din?", - "album_viewer_appbar_share_err_delete": "Kunne ikke slette albumet", - "album_viewer_appbar_share_err_leave": "Kunne ikke forlate albumet", - "album_viewer_appbar_share_err_remove": "Det oppstod et problem ved fjerning av elementer fra albumet", - "album_viewer_appbar_share_err_title": "Mislyktes ved endring av albumtittel", - "album_viewer_appbar_share_leave": "Forlat album", - "album_viewer_appbar_share_to": "Del til", - "album_viewer_page_share_add_users": "Legg til brukere", - "album_with_link_access": "La hvem som helst med lenken se bilder og personer i dette albumet.", - "albums": "Album", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", - "albums_default_sort_order": "Standard sorteringsrekkefølge for album", - "albums_default_sort_order_description": "Standard sorteringsrekkefølge for bilder når man lager et nytt album.", - "albums_feature_description": "Samlinger av bilder som kan deles med andre brukere.", - "albums_on_device_count": "Album på enheten {count}", - "albums_selected": "{count, plural, one {# valgt album} other {# albumer valgt}}", - "all": "Alle", - "all_albums": "Alle album", - "all_people": "Alle personer", - "all_photos": "Alle bilder", - "all_videos": "Alle videoer", - "allow_dark_mode": "Tillat mørk modus", - "allow_edits": "Tillat redigering", - "allow_public_user_to_download": "Tillat uautentiserte brukere å laste ned", - "allow_public_user_to_upload": "Tillat uautentiserte brukere å laste opp", - "allowed": "Tillatt", - "alt_text_qr_code": "QR-kodebilde", - "always_keep": "Alltid behold", - "always_keep_photos_hint": "Frigjør plass vil beholde alle bilder på denne enheten.", - "always_keep_videos_hint": "Frigjør plass til beholde alle videoer på denne enheten.", - "anti_clockwise": "Mot klokken", - "api_key": "API-nøkkel", - "api_key_description": "Denne verdien vil vises kun én gang. Pass på å kopiere den før du lukker vinduet.", - "api_key_empty": "API-nøkkelnavnet bør ikke være tomt", - "api_keys": "API-nøkler", - "app_architecture_variant": "Variant (Arkitektur)", - "app_bar_signout_dialog_content": "Vil du virkelig logge ut?", - "app_bar_signout_dialog_ok": "Ja", - "app_bar_signout_dialog_title": "Logg ut", - "app_download_links": "Nedlastingslinker for App", - "app_settings": "Appinstillinger", - "app_stores": "App butikker", - "app_update_available": "Appoppdatering er tilgjengelig", - "appears_in": "Vises i", - "apply_count": "Bruk ({count, number})", - "archive": "Arkiv", - "archive_action_prompt": "{count} lagt til i arkivet", - "archive_or_unarchive_photo": "Arkiver eller ta ut av arkivet", - "archive_page_no_archived_assets": "Ingen arkiverte elementer funnet", - "archive_page_title": "Arkiv ({count})", - "archive_size": "Arkivstørrelse", - "archive_size_description": "Konfigurer arkivstørrelsen for nedlastinger (i GiB)", - "archived": "Arkivert", - "archived_count": "{count, plural, other {Arkivert #}}", - "are_these_the_same_person": "Er disse samme person?", - "are_you_sure_to_do_this": "Vil du virkelig gjøre dette?", - "array_field_not_fully_supported": "Arrayfelter krever manuell JSON endring", - "asset_action_delete_err_read_only": "Kunne ikke slette element(er) med kun lese-rettighet, hopper over", - "asset_action_share_err_offline": "Kunne ikke hente offline element(er), hopper over", - "asset_added_to_album": "Lagt til i album", - "asset_adding_to_album": "Legger til i album…", - "asset_created": "Objekt opprettet", - "asset_description_updated": "Elementbeskrivelse har blitt oppdatert", - "asset_filename_is_offline": "Element {filename} er offline", - "asset_has_unassigned_faces": "Element har ikke-tilordnede ansikter", - "asset_hashing": "Hasher…", - "asset_list_group_by_sub_title": "Grupper etter", - "asset_list_layout_settings_dynamic_layout_title": "Dynamisk bildeorganisering", - "asset_list_layout_settings_group_automatically": "Automatisk", - "asset_list_layout_settings_group_by": "Grupper bilder etter", - "asset_list_layout_settings_group_by_month_day": "Måned + dag", - "asset_list_layout_sub_title": "Fordeling", - "asset_list_settings_subtitle": "Innstillinger for layout av fotorutenett", - "asset_list_settings_title": "Fotorutenett", - "asset_not_found_on_device_android": "Elementet ble ikke funnet på enheten", - "asset_not_found_on_device_ios": "Elementet ble ikke funnet på enheten. Hvis du bruker iCloud, kan elementet være utilgjengelig på grunn av en feilaktig fil er lagret i iCloud", - "asset_not_found_on_icloud": "Elementet ble ikke funnet på iCloud. Elementet kan være utilgjengelig fordi det ligger en feilaktig fil i iCloud", - "asset_offline": "Fil utilgjengelig", - "asset_offline_description": "Dette elementet er offline. Immich kan ikke aksessere dets lokasjon. Vennligst påse at elementet er tilgjengelig og skann så biblioteket på nytt.", - "asset_restored_successfully": "Objekt(er) gjenopprettet", - "asset_skipped": "Hoppet over", - "asset_skipped_in_trash": "I papirkurven", - "asset_trashed": "Objekt slettet", - "asset_troubleshoot": "Feilsøk element", - "asset_uploaded": "Lastet opp", - "asset_uploading": "Laster opp…", - "asset_viewer_settings_subtitle": "Endre dine visningsinnstillinger for galleriet", - "asset_viewer_settings_title": "Objektviser", - "assets": "Filer", - "assets_added_count": "Lagt til {count, plural, one {# element} other {# elementer}}", - "assets_added_to_album_count": "Lagt til {count, plural, one {# elementer} other {# element}} i album", - "assets_added_to_albums_count": "Lagt til {assetTotal, plural, one {# asset} other {# assets}} til {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Objektet} other {Objektene}} kan ikke legges til i albumet", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} kan ikke legges til i noen av albumene", - "assets_count": "{count, plural, one {# fil} other {# filer}}", - "assets_deleted_permanently": "{count} element(er) slettet permanent", - "assets_deleted_permanently_from_server": "{count} element(er) slettet permanent fra Immich-serveren", - "assets_downloaded_failed": "{count, plural, one {Nedlasting av # fil - {error} fil feilet} other {Nedlastede # filer - {error} filer feilet}}", - "assets_downloaded_successfully": "{count, plural, one {Nedlastet # fil vellykket} other {Nedlastede # filer vellykket}}", - "assets_moved_to_trash_count": "Flyttet {count, plural, one {# element} other {# elementer}} til søppel", - "assets_permanently_deleted_count": "Slettet {count, plural, one {# element} other {# elementer}} permanent", - "assets_removed_count": "Slettet {count, plural, one {# element} other {# elementer}}", - "assets_removed_permanently_from_device": "{count} element(er) slettet permanent fra enheten din", - "assets_restore_confirmation": "Vil du virkelig gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres! Vær oppmerksom på at frakoblede ressurser ikke kan gjenopprettes på denne måten.", - "assets_restored_count": "Gjenopprettet {count, plural, one {# element} other {# elementer}}", - "assets_restored_successfully": "{count} element(er) gjenopprettet", - "assets_trashed": "{count} element(er) slettet", - "assets_trashed_count": "Kastet {count, plural, one {# element} other {# elementer}}", - "assets_trashed_from_server": "{count} element(er) slettet fra Immich serveren", - "assets_were_part_of_album_count": "{count, plural, one {Objektet} other {Objektene}} er allerede lagt til i albumet", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} allerede inkludert i albumet", - "authorized_devices": "Autoriserte enheter", - "automatic_endpoint_switching_subtitle": "Koble til lokalt over angitt Wi-Fi når det er tilgjengelig, og bruk alternative tilkoblinger andre steder", - "automatic_endpoint_switching_title": "Automatisk URL bytte", - "autoplay_slideshow": "Autoavspilling av lysbildefremvisning", - "back": "Tilbake", - "back_close_deselect": "Tilbake, lukk eller fjern merking", - "background_backup_running_error": "Bakgrunnsbackup kjører, kan ikke starte manuell backup", - "background_location_permission": "Bakgrunnstillatelse for plassering", - "background_location_permission_content": "For å bytte nettverk når du kjører i bakgrunnen, må Immich *alltid* ha presis posisjonstilgang slik at appen kan lese Wi-Fi-nettverkets navn", - "background_options": "Bakgrunnsinnstillinger", - "backup": "Sikkerhetskopiering", - "backup_album_selection_page_albums_device": "Album på enhet ({count})", - "backup_album_selection_page_albums_tap": "Trykk for å inkludere, dobbelttrykk for å ekskludere", - "backup_album_selection_page_assets_scatter": "Objekter kan bli spredd over flere album. Album kan derfor bli inkludert eller ekskludert under sikkerhetskopieringen.", - "backup_album_selection_page_select_albums": "Velg album", - "backup_album_selection_page_selection_info": "Valginformasjon", - "backup_album_selection_page_total_assets": "Totalt antall unike elementer", - "backup_albums_sync": "Synkronisering av sikkerhetskopialbum", - "backup_all": "Alle", - "backup_background_service_backup_failed_message": "Sikkerhetskopiering av elementer feilet. Prøver på nytt…", - "backup_background_service_complete_notification": "Backup av elementer fullført", - "backup_background_service_connection_failed_message": "Tilkobling til server feilet. Prøver på nytt…", - "backup_background_service_current_upload_notification": "Laster opp {filename}", - "backup_background_service_default_notification": "Ser etter nye elementer…", - "backup_background_service_error_title": "Feil under sikkerhetskopiering", - "backup_background_service_in_progress_notification": "Sikkerhetskopierer elementer…", - "backup_background_service_upload_failure_notification": "Opplasting feilet {filename}", - "backup_controller_page_albums": "Sikkerhetskopier album", - "backup_controller_page_background_app_refresh_disabled_content": "Aktiver bakgrunnsoppdatering i Innstillinger > Generelt > Bakgrunnsoppdatering for å bruke sikkerhetskopiering i bakgrunnen.", - "backup_controller_page_background_app_refresh_disabled_title": "Bakgrunnsoppdateringer er deaktivert", - "backup_controller_page_background_app_refresh_enable_button_text": "Gå til innstillinger", - "backup_controller_page_background_battery_info_link": "Vis meg hvordan", - "backup_controller_page_background_battery_info_message": "For at sikkerhetskopiering i bakgrunnen skal fungere optimalt, deaktiver enhver batterioptimalisering som kan begrense bakgrunnsaktiviteten til Immich.\n\nSiden dette er en enhetsspesifikk justering, må du finne det i innstillingene på enheten din.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Batterioptimalisering", - "backup_controller_page_background_charging": "Kun ved lading", - "backup_controller_page_background_configure_error": "Konfigurering av bakgrunnstjenesten feilet", - "backup_controller_page_background_delay": "Forsink sikkerhetskopiering av nye elementer: {duration}", - "backup_controller_page_background_description": "Skru på bakgrunnstjenesten for å automatisk sikkerhetskopiere alle nye elementer uten å måtte åpne appen", - "backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnen er deaktivert", - "backup_controller_page_background_is_on": "Automatisk sikkerhetskopiering i bakgrunnen er aktivert", - "backup_controller_page_background_turn_off": "Skru av bakgrunnstjenesten", - "backup_controller_page_background_turn_on": "Skru på bakgrunnstjenesten", - "backup_controller_page_background_wifi": "Kun på Wi-Fi", - "backup_controller_page_backup": "Sikkerhetskopiere", - "backup_controller_page_backup_selected": "Valgte: ", - "backup_controller_page_backup_sub": "Opplastede bilder og videoer", - "backup_controller_page_created": "Opprettet: {date}", - "backup_controller_page_desc_backup": "Slå på sikkerhetskopiering i forgrunnen for automatisk å laste opp nye elementer til serveren når du åpner appen.", - "backup_controller_page_excluded": "Ekskludert: ", - "backup_controller_page_failed": "Mislyktes ({count})", - "backup_controller_page_filename": "Filnavn: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informasjon om sikkerhetskopi", - "backup_controller_page_none_selected": "Ingen valgt", - "backup_controller_page_remainder": "Gjenstår", - "backup_controller_page_remainder_sub": "Gjenstående bilder og videoer å laste opp fra utvalget", - "backup_controller_page_server_storage": "Serverlagring", - "backup_controller_page_start_backup": "Start sikkerhetskopiering", - "backup_controller_page_status_off": "Automatisk sikkerhetskopiering i forgrunnen er av", - "backup_controller_page_status_on": "Automatisk sikkerhetskopiering i forgrunnen er på", - "backup_controller_page_storage_format": "{used} av {total} brukt", - "backup_controller_page_to_backup": "Album som skal sikkerhetskopieres", - "backup_controller_page_total_sub": "Alle unike bilder og videoer fra valgte album", - "backup_controller_page_turn_off": "Slå av sikkerhetskopiering i forgrunnen", - "backup_controller_page_turn_on": "Slå på sikkerhetskopiering i forgrunnen", - "backup_controller_page_uploading_file_info": "Laster opp filinformasjon", - "backup_err_only_album": "Kunne ikke fjerne det eneste albumet", - "backup_error_sync_failed": "Synkronisering feilet. Kunne ikke fortsette sikkerhetskopiering.", - "backup_info_card_assets": "elementer", - "backup_manual_cancelled": "Avbrutt", - "backup_manual_in_progress": "Opplasting er allerede i gang. Prøv igjen om litt", - "backup_manual_success": "Vellykket", - "backup_manual_title": "Opplastingsstatus", - "backup_options": "Backup innstillinger", - "backup_options_page_title": "Backupinnstillinger", - "backup_setting_subtitle": "Administrer opplastingsinnstillinger for bakgrunn og forgrunn", - "backup_settings_subtitle": "Håndter opplastingsinnstillinger", - "backup_upload_details_page_more_details": "Trykk for flere detaljer", - "backward": "Bakover", - "biometric_auth_enabled": "Biometrisk autentisering aktivert", - "biometric_locked_out": "Du er låst ute av biometrisk verifisering", - "biometric_no_options": "Ingen biometriske valg tilgjengelige", - "biometric_not_available": "Biometrisk autentisering er ikke tilgjengelig på denne enheten", - "birthdate_saved": "Fødselsdato er vellykket lagret", - "birthdate_set_description": "Fødelsdatoen er brukt for å beregne alderen til denne personen ved tidspunktet til bildet.", - "blurred_background": "Uskarp bakgrunn", - "bugs_and_feature_requests": "Feil og funksjonsforespørsler", - "build": "Bygg", - "build_image": "Lag Bilde", - "bulk_delete_duplicates_confirmation": "Vil du virkelig slette {count, plural, one {# duplisert fil} other {# dupliserte filer}}? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", - "bulk_keep_duplicates_confirmation": "Vil du virkelig beholde {count, plural, one {# duplikat} other {# duplikater}}? Dette vil løse alle duplikatgrupper uten å slette noe.", - "bulk_trash_duplicates_confirmation": "Vil du virkelig å slette {count, plural, one {# duplisert element} other {# dupliserte elementer}}? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", - "buy": "Kjøp Immich", - "cache_settings_clear_cache_button": "Tøm buffer", - "cache_settings_clear_cache_button_title": "Tømmer app-ens buffer. Dette vil ha betydelig innvirkning på appens ytelse inntil bufferen er gjenoppbygd.", - "cache_settings_duplicated_assets_clear_button": "TØM", - "cache_settings_duplicated_assets_subtitle": "Bilder og videoer som er ignorert av app'en", - "cache_settings_duplicated_assets_title": "Dupliserte elementer ({count})", - "cache_settings_statistics_album": "Bibliotekminiatyrbilder", - "cache_settings_statistics_full": "Originalbilder", - "cache_settings_statistics_shared": "Delte albumminiatyrbilder", - "cache_settings_statistics_thumbnail": "Miniatyrbilder", - "cache_settings_statistics_title": "Bufferbruk", - "cache_settings_subtitle": "Kontroller bufringsadferden til Immich-appen", - "cache_settings_tile_subtitle": "Kontroller lokal lagring", - "cache_settings_tile_title": "Lokal lagring", - "cache_settings_title": "Bufringsinnstillinger", - "camera": "Kamera", - "camera_brand": "Kameramerke", - "camera_model": "Kameramodell", - "cancel": "Avbryt", - "cancel_search": "Avbryt søk", - "canceled": "Avbrutt", - "canceling": "Avbryter", - "cannot_merge_people": "Kunne ikke slå sammen personer", - "cannot_undo_this_action": "Du kan ikke gjøre om denne handlingen!", - "cannot_update_the_description": "Kunne ikke oppdatere beskrivelsen", - "cast": "Strøm", - "cast_description": "Konfigurer tilgjengelige cast-destinasjoner", - "change_date": "Endre dato", - "change_description": "Endre beskrivelsen", - "change_display_order": "Endre visningsrekkefølge", - "change_expiration_time": "Endre utløpstid", - "change_location": "Endre sted", - "change_name": "Endre navn", - "change_name_successfully": "Navneendring vellykket", - "change_password": "Endre passord", - "change_password_description": "Dette er enten første gang du logger inn i systemet, eller det har blitt gjort en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", - "change_password_form_confirm_password": "Bekreft passord", - "change_password_form_description": "Hei {name}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.", - "change_password_form_log_out": "Logg ut av alle andre enheter", - "change_password_form_log_out_description": "Det er anbefalt å logge ut av alle andre enheter", - "change_password_form_new_password": "Nytt passord", - "change_password_form_password_mismatch": "Passordene stemmer ikke", - "change_password_form_reenter_new_password": "Skriv nytt passord igjen", - "change_pin_code": "Endre PIN-kode", - "change_trigger": "Endre utløser", - "change_trigger_prompt": "Er du sikker på at du vil endre utløser? Dette vil fjerne alle eksisterende handlinger og filtre.", - "change_your_password": "Endre passordet ditt", - "changed_visibility_successfully": "Endret synlighet vellykket", - "charging": "Lading", - "charging_requirement_mobile_backup": "Bakgrunnsbackup krever at enheten lader", - "check_corrupt_asset_backup": "Sjekk etter korrupte backupelementer", - "check_corrupt_asset_backup_button": "Utfør sjekk", - "check_corrupt_asset_backup_description": "Kjør denne sjekken kun over Wi-Fi og når alle elementer har blitt lastet opp. Denne sjekken kan ta noen minutter.", - "check_logs": "Sjekk Logger", - "checksum": "Sjekksum", - "choose_matching_people_to_merge": "Velg personer som skal slås sammen", - "city": "By", - "cleanup_confirm_description": "Immich fant {count} mediefiler (opprettet før {date}) som er lastet opp til serveren. Vil du fjerne disse lokale kopiene fra denne enheten?", - "cleanup_confirm_prompt_title": "Fjern fra denne enheten?", - "cleanup_deleted_assets": "Flyttet {count} mediefiler til enhetens søppelkasse", - "cleanup_deleting": "Flytter til søppelkasse...", - "cleanup_found_assets": "Fant {count} mediefiler som er sikkerhetskopiert", - "cleanup_found_assets_with_size": "Fant {count} sikkerhetskopierte objekter ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud delte albumer er ekskludert fra skanningen", - "cleanup_no_assets_found": "Ingen opplastede mediefiler funnet som treffer dine søkekriterier. Frigjør plass kan kun fjerne objekter som har blitt sikkerhetskopiert", - "cleanup_preview_title": "Mediefiler å fjerne ({count})", - "cleanup_step3_description": "Skann etter bilder og videoer ved å velge sluttdato og filter i søkeinnstillinger.", - "cleanup_step4_summary": "{count} mediefiler (opprettet før {date}= er plassert i kø for fjerning fra enheten. Bildene vil være tilgjengelige fra Immich appen.", - "cleanup_trash_hint": "For å frigjøre lagringsplassen helt, åpne systemgalleri-appen og tøm papirkurven", - "clear": "Tøm", - "clear_all": "Tøm alt", - "clear_all_recent_searches": "Fjern alle nylige søk", - "clear_file_cache": "Tøm filcache", - "clear_message": "Fjern melding", - "clear_value": "Fjern verdi", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Skriv inn passord", - "client_cert_import": "Importer", - "client_cert_import_success_msg": "Klient sertifikat er importert", - "client_cert_invalid_msg": "Ugyldig sertifikat eller feil passord", - "client_cert_remove_msg": "Klient sertifikat er fjernet", - "client_cert_subtitle": "Støtter kun PKCS12 (.p12, .pfx) formater. Importering/Fjerning av sertifikater er kun mulig før innlogging", - "client_cert_title": "SSL Klient sertifikat [EKSPERIMENTELL]", - "clockwise": "Med urviseren", - "close": "Lukk", - "collapse": "Trekk sammen", - "collapse_all": "Kollaps alt", - "color": "Farge", - "color_theme": "Fargetema", - "command": "Kommando", - "comment_deleted": "Kommentar slettet", - "comment_options": "Kommentaralternativer", - "comments_and_likes": "Kommentarer & likes", - "comments_are_disabled": "Kommentarer er deaktivert", - "common_create_new_album": "Lag nytt album", - "completed": "Fullført", - "confirm": "Bekreft", - "confirm_admin_password": "Bekreft administratorpassord", - "confirm_delete_face": "Vil du virkelig slette {name} sitt ansikt fra ativia?", - "confirm_delete_shared_link": "Vil du virkelig slette denne delte lenken?", - "confirm_keep_this_delete_others": "Alle andre ressurser i denne stabelen vil bli slettet bortsett fra denne ressursen. Er du sikker på at du vil fortsette?", - "confirm_new_pin_code": "Bekreft ny PIN kode", - "confirm_password": "Bekreft passord", - "confirm_tag_face": "Vil du merke dette ansiktet som {name}?", - "confirm_tag_face_unnamed": "Vil du merke dette ansiktet?", - "connected_device": "Tilkoblet enhet", - "connected_to": "Koblet til", - "contain": "Inneholder", - "context": "Kontekst", - "continue": "Fortsett", - "control_bottom_app_bar_create_new_album": "Lag nytt album", - "control_bottom_app_bar_delete_from_immich": "Slett fra Immich", - "control_bottom_app_bar_delete_from_local": "Slett fra enhet", - "control_bottom_app_bar_edit_location": "Endre lokasjon", - "control_bottom_app_bar_edit_time": "Endre Dato og tid", - "control_bottom_app_bar_share_link": "Del Lenke", - "control_bottom_app_bar_share_to": "Del til", - "control_bottom_app_bar_trash_from_immich": "Flytt til papirkurv", - "copied_image_to_clipboard": "Bildet er kopiert til utklippstavlen.", - "copied_to_clipboard": "Kopiert til utklippstavlen!", - "copy_error": "Kopi feil", - "copy_file_path": "Kopier filplassering", - "copy_image": "Kopier bilde", - "copy_link": "Kopier lenke", - "copy_link_to_clipboard": "Kopier lenke til utklippstavlen", - "copy_password": "Kopier passord", - "copy_to_clipboard": "Kopier til utklipstavlen", - "country": "Land", - "cover": "Omslag", - "covers": "Omslag", - "create": "Opprett", - "create_album": "Opprett album", - "create_album_page_untitled": "Navnløst", - "create_api_key": "Opprett API nøkkel", - "create_first_workflow": "Opprett første arbeidsfly", - "create_library": "Opprett Bibliotek", - "create_link": "Opprett lenke", - "create_link_to_share": "Opprett delelink", - "create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene", - "create_new": "LAG NY", - "create_new_person": "Opprett ny person", - "create_new_person_hint": "Tildel valgte eiendeler til en ny person", - "create_new_user": "Opprett ny bruker", - "create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER", - "create_shared_album_page_share_select_photos": "Velg bilder", - "create_shared_link": "Opprett delt lenke", - "create_tag": "Lag merkelapp", - "create_tag_description": "Lag en ny tag. For undertag, vennligst fullfør hele stien til taggen, inkludert forovervendt skråstrek.", - "create_user": "Opprett Bruker", - "create_workflow": "Opprett arbeidsflyt", - "created": "Opprettet", - "created_at": "Laget", - "creating_linked_albums": "Oppretter sammenkoblede album...", - "crop": "Beskjær", - "crop_aspect_ratio_fixed": "Fikset", - "crop_aspect_ratio_free": "Lagret", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Ting", - "current_device": "Nåværende enhet", - "current_pin_code": "Nåværende PIN kode", - "current_server_address": "Nåværende serveradresse", - "custom_date": "Egendefinert dato", - "custom_locale": "Tilpasset lokalisering", - "custom_locale_description": "Formater datoer og tall basert på språk og region", - "custom_url": "Tilpasset URL", - "cutoff_date_description": "Fjern bilder som er eldre enn…", - "cutoff_day": "{count, plural, one {dag} other {dager}}", - "cutoff_year": "{count, plural, one {år} other {år}}", - "daily_title_text_date": "E MMM. dd", - "daily_title_text_date_year": "E MMM. dddd, yyyy", - "dark": "Mørk", - "dark_theme": "Aktiver mørk-modus", - "date": "Dato", - "date_after": "Dato etter", - "date_and_time": "Dato og tid", - "date_before": "Dato før", - "date_format": "d LLL. E y • hh:mm", - "date_of_birth_saved": "Fødselsdatoen ble lagret vellykket", - "date_range": "Datoområde", - "day": "Dag", - "days": "Dager", - "deduplicate_all": "De-dupliser alle", - "deduplication_criteria_1": "Bilde størrelse i bytes", - "deduplication_criteria_2": "Antall av EXIF data", - "deduplication_info": "Dedupliseringsinformasjon", - "deduplication_info_description": "For å automatisk forhåndsvelge eiendeler og fjerne duplikater samtidig, ser vi på:", - "default_locale": "Standard språkinnstilling", - "default_locale_description": "Formater datoer og tall basert på nettleserens språkinnstilling", - "delete": "Slett", - "delete_action_confirmation_message": "Vil du virkelig slette dette elementet? Dette vil flytte elementet til papirkurvn og vil gi deg beskjed om du vil slette det lokalt", - "delete_action_prompt": "{count} slettet", - "delete_album": "Slett album", - "delete_api_key_prompt": "Vil du virkelig slette denne API-nøkkelen?", - "delete_dialog_alert": "Disse elementene vil bli slettet permanent fra Immich og fra enheten din", - "delete_dialog_alert_local": "Disse elementene vil bli permanent slettet fra enheten din, men vil fortsatt være tilgjengelige fra Immich serveren", - "delete_dialog_alert_local_non_backed_up": "Noen av elementene er ikke sikkerhetskopiert til Immich og vil bli permanent fjernet fra enheten din", - "delete_dialog_alert_remote": "Disse elementene vil bli permanent slettet fra Immich serveren", - "delete_dialog_ok_force": "Slett uansett", - "delete_dialog_title": "Slett permanent", - "delete_duplicates_confirmation": "Vil du virkelig slette disse duplikatene permanent?", - "delete_face": "Slett ansik", - "delete_key": "Slett nøkkel", - "delete_library": "Slett bibliotek", - "delete_link": "Slett lenke", - "delete_local_action_prompt": "{count} slettet lokalt", - "delete_local_dialog_ok_backed_up_only": "Slett kun sikkerhetskopierte elementer", - "delete_local_dialog_ok_force": "Slett uansett", - "delete_others": "Slett andre", - "delete_permanently": "Slett permanent", - "delete_permanently_action_prompt": "{count} slettet permanent", - "delete_shared_link": "Slett delt lenke", - "delete_shared_link_dialog_title": "Slett delt link", - "delete_tag": "Slett tag", - "delete_tag_confirmation_prompt": "Vil du virkelig slette {tagName} tag?", - "delete_user": "Slett bruker", - "deleted_shared_link": "Slettet delt lenke", - "deletes_missing_assets": "Slett eiendeler som mangler fra disk", - "description": "Beskrivelse", - "description_input_hint_text": "Legg til beskrivelse ...", - "description_input_submit_error": "Feil ved oppdatering av beskrivelse, sjekk loggen for flere detaljer", - "deselect_all": "Avmerk alle", - "details": "Detaljer", - "direction": "Retning", - "disable": "Deaktiver", - "disabled": "Deaktivert", - "disallow_edits": "Forby redigering", - "discord": "Discord", - "discover": "Oppdag", - "discovered_devices": "Oppdagede enheter", - "dismiss_all_errors": "Avvis alle feil", - "dismiss_error": "Avvis feil", - "display_options": "Visningsalternativer", - "display_order": "Visningsrekkefølge", - "display_original_photos": "Vis opprinnelige bilder", - "display_original_photos_setting_description": "Foretrekk å vise det opprinnelige bildet når du ser på en fil i stedet for miniatyrbilder når den opprinnelige filen er kompatibel med nettet. Dette kan føre til tregere visning av bilder.", - "do_not_show_again": "Ikke vis denne meldingen igjen", - "documentation": "Dokumentasjon", - "done": "Ferdig", - "download": "Last ned", - "download_action_prompt": "Laster ned {count} elementer", - "download_canceled": "Nedlasting avbrutt", - "download_complete": "Nedlasting fullført", - "download_enqueue": "Nedlasting satt i kø", - "download_error": "Nedlasting feilet", - "download_failed": "Nedlasting feilet", - "download_finished": "Nedlasting fullført", - "download_include_embedded_motion_videos": "Innebygde videoer", - "download_include_embedded_motion_videos_description": "Inkluder innebygde videoer i levende bilder som en egen fil", - "download_notfound": "Nedlasting ikke funnet", - "download_original": "Last ned original", - "download_paused": "Nedlasting pauset", - "download_settings": "Last ned", - "download_settings_description": "Administrer innstillinger relatert til nedlasting av filer", - "download_started": "Nedlasting startet", - "download_sucess": "Nedlasting vellykket", - "download_sucess_android": "Objektet har blitt lastet ned til DCIM/Immich", - "download_waiting_to_retry": "Venter på nytt forsøk", - "downloading": "Laster ned", - "downloading_asset_filename": "Last ned {filename}", - "downloading_from_icloud": "Laster ned fra iCloud", - "downloading_media": "Laster ned media", - "drop_files_to_upload": "Slipp filer hvor som helst for å laste opp", - "duplicates": "Duplikater", - "duplicates_description": "Løs hver gruppe ved å angi hvilke, hvis noen, er duplikater", - "duration": "Varighet", - "edit": "Rediger", - "edit_album": "Rediger album", - "edit_avatar": "Rediger avatar", - "edit_birthday": "Rediger fødselsdag", - "edit_date": "Rediger dato", - "edit_date_and_time": "Rediger dato og tid", - "edit_date_and_time_action_prompt": "{count} dato og tid endret", - "edit_date_and_time_by_offset": "Endre dato med forskyvning", - "edit_date_and_time_by_offset_interval": "Nytt datointervall: {from} - {to}", - "edit_description": "Endre beskrivelse", - "edit_description_prompt": "Vennligst velg en ny beskrivelse:", - "edit_exclusion_pattern": "Rediger utelukkelsesmønster", - "edit_faces": "Rediger ansikter", - "edit_key": "Rediger nøkkel", - "edit_link": "Endre lenke", - "edit_location": "Rediger sted", - "edit_location_action_prompt": "{count} plassering endret", - "edit_location_dialog_title": "Plassering", - "edit_name": "Rediger navn", - "edit_people": "Rediger personer", - "edit_tag": "Rediger etikett", - "edit_title": "Rediger tittel", - "edit_user": "Rediger bruker", - "edit_workflow": "Endre arbeidsflyt", - "editor": "Redaktør", - "editor_close_without_save_prompt": "Endringene vil ikke bli lagret", - "editor_close_without_save_title": "Lukk redigering?", - "editor_confirm_reset_all_changes": "Er du sikker på at du vil tilbakestille alle endringer?", - "editor_flip_horizontal": "Roter horisontalt", - "editor_flip_vertical": "Roter vertikalt", - "editor_orientation": "Orientering", - "editor_reset_all_changes": "Tilbakestill endringer", - "editor_rotate_left": "Roter 90° mot klokken", - "editor_rotate_right": "Roter 90° med klokken", - "email": "E-postadresse", - "email_notifications": "Epostvarsler", - "empty_folder": "Denne mappen er tom", - "empty_trash": "Tøm papirkurv", - "empty_trash_confirmation": "Vil du virkelig Tømme søppelbøtta? Dette vil slette alle filene i søppelbøtta permanent fra Immich.\nDu kan ikke angre denne handlingen!", - "enable": "Aktivere", - "enable_backup": "Aktiver backup", - "enable_biometric_auth_description": "Skriv inn PIN-koden for å aktivere biometrisk autentisering", - "enabled": "Aktivert", - "end_date": "Sluttdato", - "enqueued": "I kø", - "enter_wifi_name": "Skriv inn Wi-Fi navn", - "enter_your_pin_code": "Skriv inn din PIN-kode", - "enter_your_pin_code_subtitle": "Skriv inn din PIN-kode for å få tilgang til låst mappe", - "error": "Feil", - "error_change_sort_album": "Mislyktes ved endring av sorteringsrekkefølge på album", - "error_delete_face": "Feil ved sletting av ansikt fra aktivia", - "error_getting_places": "Feil ved henting av steder", - "error_loading_albums": "Feil ved lasting av albumer", - "error_loading_image": "Feil ved lasting av bilde", - "error_loading_partners": "Feil ved lasting av partnere: {error}", - "error_retrieving_asset_information": "Feil ved henting av objektinformasjon", - "error_saving_image": "Feil: {error}", - "error_tag_face_bounding_box": "Feil ved merking av ansikt - klarte ikke å få koordinatene på omrisset", - "error_title": "Feil - Noe gikk galt", - "error_while_navigating": "Feil ved navigering til objekt", - "errors": { - "cannot_navigate_next_asset": "Kunne ikke navigere til neste fil", - "cannot_navigate_previous_asset": "Kunne ikke navigere til forrige fil", - "cant_apply_changes": "Kunne ikke legge til endringene", - "cant_change_activity": "Kunne ikke {enabled, select, true {disable} other {enable}} aktivitet", - "cant_change_asset_favorite": "Kunne ikke endre favoritt til filen", - "cant_change_metadata_assets_count": "Kunne ikke endre metadata for {count, plural, one {# element} other {# elementer}}", - "cant_get_faces": "Kunne ikke finne ansikter", - "cant_get_number_of_comments": "Kunne ikke hente antall kommentarer", - "cant_search_people": "Kunne ikke søke etter mennesker", - "cant_search_places": "Kunne ikke søke etter plasser", - "error_adding_assets_to_album": "Feil med å legge til bilder til album", - "error_adding_users_to_album": "Feil, kan ikke legge til brukere til album", - "error_deleting_shared_user": "Feil med å slette delt bruker", - "error_downloading": "Feil med å laste ned {filename}", - "error_hiding_buy_button": "Feil med å skjule kjøp knapp", - "error_removing_assets_from_album": "Feil med å fjerne bilder fra album, sjekk konsoll for mer detaljer", - "error_selecting_all_assets": "Feil med å velge alle bilder", - "exclusion_pattern_already_exists": "Dette eksklusjonsmønsteret eksisterer allerede.", - "failed_to_create_album": "Feil med å lage album", - "failed_to_create_shared_link": "Feil med å lage delt lenke", - "failed_to_edit_shared_link": "Mislyktes med å redigere delt lenke", - "failed_to_get_people": "Mislyktes med å finne mennesker", - "failed_to_keep_this_delete_others": "Mislyktes med å beholde dette bilde og slette de andre", - "failed_to_load_asset": "Mislyktes med å laste bilder", - "failed_to_load_assets": "Mislyktes med å laste bilde", - "failed_to_load_notifications": "Kunne ikke laste inn varsler", - "failed_to_load_people": "Feilen med å laste mennesker", - "failed_to_remove_product_key": "Mislyktes med å ta bort produkt nøkkel", - "failed_to_reset_pin_code": "Kunne ikke tilbakestille PIN-koden", - "failed_to_stack_assets": "Mislyktes med å stable bilder", - "failed_to_unstack_assets": "Mislyktes med å avstable bilder", - "failed_to_update_notification_status": "Kunne ikke oppdatere varslingsstatusen", - "incorrect_email_or_password": "Feil epost eller passord", - "library_folder_already_exists": "Importstien eksisterer allerede.", - "paths_validation_failed": "{paths, plural, one {# sti} other {# sti}} mislyktes validering", - "profile_picture_transparent_pixels": "Profil bilde kan ikke ha gjennomsiktige piksler. Vennligst zoom inn og/eller flytt bilde.", - "quota_higher_than_disk_size": "Du har satt kvoten større enn diskstørrelsen", - "something_went_wrong": "Noe gikk galt", - "unable_to_add_album_users": "Kunne ikke legge til brukere i albumet", - "unable_to_add_assets_to_shared_link": "Kunne ikke legge til bilder til delt lenke", - "unable_to_add_comment": "Kunne ikke legge til kommentar", - "unable_to_add_exclusion_pattern": "Kunne ikke legge til eksklusjonsmønster", - "unable_to_add_partners": "Kunne ikke legge til partnere", - "unable_to_add_remove_archive": "Kunne ikke {archived, select, true {fjerne element fra} other {flytte element til}} arkivet", - "unable_to_add_remove_favorites": "Kunne ikke {favorite, select, true {legge til element til} other {fjerne element fra}} favoritter", - "unable_to_archive_unarchive": "Kunne ikke {archived, select, true {archive} other {unarchive}}", - "unable_to_change_album_user_role": "Kunne ikke endre brukerens rolle i albumet", - "unable_to_change_date": "Kunne ikke endre dato", - "unable_to_change_description": "Klarte ikke å oppdatere beskrivelse", - "unable_to_change_favorite": "Kunne ikke endre favoritt for bildet", - "unable_to_change_location": "Kunne ikke endre plassering", - "unable_to_change_password": "Kunne ikke endre passord", - "unable_to_change_visibility": "Kunne ikke endre synlighet for {count, plural, one {# person} other {# people}}", - "unable_to_complete_oauth_login": "Kunne ikke fullføre OAuth innlogging", - "unable_to_connect": "Kunne ikke koble til", - "unable_to_copy_to_clipboard": "Kunne ikke kopiere til utklippstavlen, sørg for at du får tilgang til siden via https", - "unable_to_create": "Klarte ikke å opprette arbeidsflyt", - "unable_to_create_admin_account": "Kunne ikke opprette administrator bruker", - "unable_to_create_api_key": "Kunne ikke opprette en ny API-nøkkel", - "unable_to_create_library": "Kunne ikke opprette bibliotek", - "unable_to_create_user": "Kunne ikke opprette bruker", - "unable_to_delete_album": "Kunne ikke slette album", - "unable_to_delete_asset": "Kunne ikke slette filen", - "unable_to_delete_assets": "Feil med å slette bilde", - "unable_to_delete_exclusion_pattern": "Kunne ikke slette eksklusjonsmønster", - "unable_to_delete_shared_link": "Kunne ikke slette delt lenke", - "unable_to_delete_user": "Kunne ikke slette bruker", - "unable_to_delete_workflow": "Klarte ikke å slette arbeidsflyt", - "unable_to_download_files": "Kunne ikke laste ned filer", - "unable_to_edit_exclusion_pattern": "Kunne ikke redigere eksklusjonsmønster", - "unable_to_empty_trash": "Kunne ikke Tømme papirkurven", - "unable_to_enter_fullscreen": "Kunne ikke gå inn i fullskjerm", - "unable_to_exit_fullscreen": "Kunne ikke gå ut fra fullskjerm", - "unable_to_get_comments_number": "Kunne ikke hente antall kommentarer", - "unable_to_get_shared_link": "Kunne ikke hente delt lenke", - "unable_to_hide_person": "Kunne ikke skjule person", - "unable_to_link_motion_video": "Kunne ikke lenke bevegelig video", - "unable_to_link_oauth_account": "Kunne ikke lenke til OAuth-konto", - "unable_to_log_out_all_devices": "Kunne ikke logge ut fra alle enheter", - "unable_to_log_out_device": "Kunne ikke logge ut av enhet", - "unable_to_login_with_oauth": "Kunne ikke logge inn med OAuth", - "unable_to_play_video": "Kunne ikke spille av video", - "unable_to_reassign_assets_existing_person": "Kunne ikke endre bruker på bildene til {name, select, null {an existing person} other {{name}}}", - "unable_to_reassign_assets_new_person": "Kunne ikke tildele bildene til en ny person", - "unable_to_refresh_user": "Kunne ikke oppdatere bruker", - "unable_to_remove_album_users": "Kunne ikke fjerne brukere fra album", - "unable_to_remove_api_key": "Kunne ikke fjerne API-nøkkel", - "unable_to_remove_assets_from_shared_link": "Kunne ikke fjerne bilder fra delt lenke", - "unable_to_remove_library": "Kunne ikke fjerne bibliotek", - "unable_to_remove_partner": "Kunne ikke fjerne partner", - "unable_to_remove_reaction": "Kunne ikke fjerne reaksjon", - "unable_to_reset_password": "Kunne ikke tilbakestille passord", - "unable_to_reset_pin_code": "Klarte ikke å resette PIN kode", - "unable_to_resolve_duplicate": "Kunne ikke løse duplikat", - "unable_to_restore_assets": "Kunne ikke gjenopprette filer", - "unable_to_restore_trash": "Kunne ikke gjenopprette papirkurven", - "unable_to_restore_user": "Kunne ikke gjenopprette bruker", - "unable_to_save_album": "Kunne ikke lagre album", - "unable_to_save_api_key": "Kunne ikke lagre API-nøkkel", - "unable_to_save_date_of_birth": "Kunne ikke lagre bursdag", - "unable_to_save_name": "Kunne ikke lagre navn", - "unable_to_save_profile": "Kunne ikke lagre profil", - "unable_to_save_settings": "Kunne ikke lagre instillinger", - "unable_to_scan_libraries": "Kunne ikke skanne biblioteker", - "unable_to_scan_library": "Kunne ikke skanne bibliotek", - "unable_to_set_feature_photo": "Kunne ikke sette funksjonsbilde", - "unable_to_set_profile_picture": "Kunne ikke sette profilbilde", - "unable_to_set_rating": "Klarte ikke å sette rating", - "unable_to_submit_job": "Kunne ikke sende inn jobb", - "unable_to_trash_asset": "Kunne ikke flytte filen til papirkurven", - "unable_to_unlink_account": "Kunne ikke fjerne kobling til konto", - "unable_to_unlink_motion_video": "Kunne ikke ta på kobling på bevegelig video", - "unable_to_update_album_cover": "Kunne ikke oppdatere album bilde", - "unable_to_update_album_info": "Kunne ikke oppdatere informasjon i album", - "unable_to_update_library": "Kunne ikke oppdatere bibliotek", - "unable_to_update_location": "Kunne ikke oppdatere plassering", - "unable_to_update_settings": "Kunne ikke oppdatere innstillinger", - "unable_to_update_timeline_display_status": "Kunne ikke oppdatere visningsstatus for tidslinje", - "unable_to_update_user": "Kunne ikke oppdatere bruker", - "unable_to_update_workflow": "Klarte ikke å oppdatere arbeidsflyt", - "unable_to_upload_file": "Kunne ikke laste opp fil" - }, - "errors_text": "Feil", - "exclusion_pattern": "Ekskluderingsmønster", - "exif": "Exif", - "exif_bottom_sheet_description": "Legg til beskrivelse ...", - "exif_bottom_sheet_description_error": "Feil ved oppdatering av beskrivelsen", - "exif_bottom_sheet_details": "DETALJER", - "exif_bottom_sheet_location": "PLASSERING", - "exif_bottom_sheet_no_description": "Ingen beskrivelse", - "exif_bottom_sheet_people": "MENNESKER", - "exif_bottom_sheet_person_add_person": "Legg til navn", - "exit_slideshow": "Avslutt lysbildefremvisning", - "expand_all": "Utvid alle", - "experimental_settings_new_asset_list_subtitle": "Under utvikling", - "experimental_settings_new_asset_list_title": "Aktiver eksperimentell rutenettsvisning", - "experimental_settings_subtitle": "Bruk på egen risiko!", - "experimental_settings_title": "Eksperimentelt", - "expire_after": "Utgå etter", - "expired": "Utgått", - "expires_date": "Utløper {date}", - "explore": "Utforsk", - "explorer": "Utforsker", - "export": "Eksporter", - "export_as_json": "Eksporter som JSON", - "export_database": "Eksporter database", - "export_database_description": "Eksporter SQLite databasen", - "extension": "Utvidelse", - "external": "Ekstern", - "external_libraries": "Eksterne Bibliotek", - "external_network": "Eksternt nettverk", - "external_network_sheet_info": "Når du ikke er på det foretrukne Wi-Fi-nettverket, vil appen koble seg til serveren via den første av URL-ene nedenfor den kan nå, fra topp til bunn", - "face_unassigned": "Ikke tilordnet", - "failed": "Mislyktes", - "failed_count": "Feilede: {count}", - "failed_to_authenticate": "Kunne ikke autentisere", - "failed_to_load_assets": "Mislyktes med å laste fil", - "failed_to_load_folder": "Kunne ikke laste inn mappe", - "favorite": "Favoritt", - "favorite_action_prompt": "{count} lagt til i favoritter", - "favorite_or_unfavorite_photo": "Merk som favoritt eller fjern som favoritt", - "favorites": "Favoritter", - "favorites_page_no_favorites": "Ingen favorittelementer funnet", - "feature_photo_updated": "Fremhevet bilde oppdatert", - "features": "Funksjoner", - "features_in_development": "Funksjoner under utvikling", - "features_setting_description": "Administrer funksjoner for appen", - "file_name_or_extension": "Filnavn eller filtype", - "file_size": "Filstørrelse", - "filename": "Filnavn", - "filetype": "Filtype", - "filter": "Filter", - "filter_description": "Betingelser for å filtrere objekter", - "filter_people": "Filtrer personer", - "filter_places": "Filtrer steder", - "filters": "Filtre", - "find_them_fast": "Finn dem raskt ved søking av navn", - "first": "Første", - "fix_incorrect_match": "Fiks feilaktig match", - "folder": "Mappe", - "folder_not_found": "Fant ikke mappe", - "folders": "Mapper", - "folders_feature_description": "Utforsker mappe visning for bilder og videoer på fil systemet", - "forgot_pin_code_question": "Glemt PIN-koden?", - "forward": "Fremover", - "free_up_space": "Rydd opp lagringsplass", - "free_up_space_description": "Flytt sikkerhetskopierte bilder og videoer til enhetens papirkurv for å frigjøre plass. Kopiene dine på serveren forblir trygge.", - "free_up_space_settings_subtitle": "Frigjør lagringsplass på enheten", - "full_path": "Full sti: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Denne funksjonen laster eksterne ressurser fra Google for å fungere.", - "general": "Generelt", - "geolocation_instruction_location": "Klikk på et element med GPS-koordinater for å bruke posisjonen, eller velg en posisjon direkte fra kartet", - "get_help": "Få Hjelp", - "get_people_error": "Feilet ved henting av mennesker", - "get_wifiname_error": "Kunne ikke hente Wi-Fi-navnet. Sørg for at du har gitt de nødvendige tillatelsene og er koblet til et Wi-Fi-nettverk", - "getting_started": "Kom i gang", - "go_back": "Gå tilbake", - "go_to_folder": "Gå til mappe", - "go_to_search": "Gå til søk", - "gps": "GPS", - "gps_missing": "Ingen GPS", - "grant_permission": "Gi tillatelse", - "group_albums_by": "Grupper album etter...", - "group_country": "Grupper etter land", - "group_no": "Ingen gruppering", - "group_owner": "Grupper etter eiere", - "group_places_by": "Grupper plasser etter...", - "group_year": "Grupper etter år", - "haptic_feedback_switch": "Aktivert haptisk tilbakemelding", - "haptic_feedback_title": "Haptisk tilbakemelding", - "has_quota": "Kvote", - "hash_asset": "Hash elementer", - "hashed_assets": "Hashede elementer", - "hashing": "Hasher", - "header_settings_add_header_tip": "Legg til header", - "header_settings_field_validator_msg": "Verdi kan ikke være null", - "header_settings_header_name_input": "Header navn", - "header_settings_header_value_input": "Header verdi", - "headers_settings_tile_title": "Egendefinerte proxy headere", - "height": "Høyde", - "hi_user": "Hei {name} ({email})", - "hide_all_people": "Skjul alle mennesker", - "hide_gallery": "Skjul galleri", - "hide_named_person": "Skjul {name}", - "hide_password": "Skjul passord", - "hide_person": "Skjul person", - "hide_schema": "Skjul skjema", - "hide_text_recognition": "Skjul tekstgjenkjenning", - "hide_unnamed_people": "Skjul mennesker uten navn", - "home_page_add_to_album_conflicts": "Lagt til {added} elementer til album {album}. {failed} elementer er allerede i albumet.", - "home_page_add_to_album_err_local": "Kunne ikke legge til lokale elementer til album enda, hopper over", - "home_page_add_to_album_success": "Lagt til {added} elementer til album {album}.", - "home_page_album_err_partner": "Kunne ikke legge til partnerelementer i album enda, hopper over", - "home_page_archive_err_local": "Kunne ikke arkivere lokale elementer enda, hopper over", - "home_page_archive_err_partner": "Kunne ikke arkivere partnerelementer, hopper over", - "home_page_building_timeline": "Genererer tidslinjen", - "home_page_delete_err_partner": "Kunne ikke slette partnerelementer, hopper over", - "home_page_delete_remote_err_local": "Lokale elementer i fjernslettingsvalgene, hopper over", - "home_page_favorite_err_local": "Kunne ikke sette favoritt på lokale elementer enda, hopper over", - "home_page_favorite_err_partner": "Kunne ikke merke partnerelementer som favoritt enda, hopper over", - "home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, velg et album (eller flere) for sikkerhetskopiering, slik at tidslinjen kan fylles med dine bilder og videoer", - "home_page_locked_error_local": "Kunne ikke flytte lokale elementer til låst mappe, hopper over", - "home_page_locked_error_partner": "Kunne ikke flytte partner elementer til låst mappe, hopper over", - "home_page_share_err_local": "Kunne ikke dele lokale elementer via link, hopper over", - "home_page_upload_err_limit": "Maksimalt 30 elementer kan lastes opp om gangen, hopper over", - "host": "Vert", - "hour": "Time", - "hours": "Timer", - "id": "ID", - "idle": "Uvirksom", - "ignore_icloud_photos": "Ignorer iCloud bilder", - "ignore_icloud_photos_description": "Bilder som er lagret på iCloud vil ikke lastes opp til Immich", - "image": "Bilde", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tatt på {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tatt med {person1} den {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1} og {person2} den {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1}, {person2}, og {person3} den {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt med {person1}, {person2}, og {additionalCount, number} andre den {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} den {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} den {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} og {person2} den {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, og {person3} den {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, ok {additionalCount, number} andre den {date}", - "image_saved_successfully": "Bilde lagret", - "image_viewer_page_state_provider_download_started": "Nedlasting startet", - "image_viewer_page_state_provider_download_success": "Nedlasting vellykket", - "image_viewer_page_state_provider_share_error": "Delingsfeil", - "immich_logo": "Immich Logo", - "immich_web_interface": "Immich webgrensesnitt", - "import_from_json": "Importer fra JSON", - "import_path": "Import-sti", - "in_albums": "I {count, plural, one {# album} other {# albums}}", - "in_archive": "I arkiv", - "in_year": "Om {year}", - "in_year_selector": "Om", - "include_archived": "Inkluder arkiverte", - "include_shared_albums": "Inkluder delte album", - "include_shared_partner_assets": "Inkluder delte partnerfiler", - "individual_share": "Individuell deling", - "individual_shares": "Individuelle delinger", - "info": "Info", - "interval": { - "day_at_onepm": "Hver dag klokken 13:00", - "hours": "Hver {hours, plural, one {time} other {{hours, number} timer}}", - "night_at_midnight": "Hver natt ved midnatt", - "night_at_twoam": "Hver natt kl. 2" - }, - "invalid_date": "Ugyldig dato", - "invalid_date_format": "Ugyldig datoformat", - "invite_people": "Inviter Personer", - "invite_to_album": "Inviter til album", - "ios_debug_info_fetch_ran_at": "Fetch kjørte {dateTime}", - "ios_debug_info_last_sync_at": "Siste synkronisering {dateTime}", - "ios_debug_info_no_processes_queued": "Ingen bakgrunnsprosesser i kø", - "ios_debug_info_no_sync_yet": "Ingen bakgrunnssynkroniseringsjobb har kjørt ennå", - "ios_debug_info_processes_queued": "{count, plural, one {{count} bakgrunnsprosess lagt i kø} other {{count} bakgrunnsprosesser lagt i kø}}", - "ios_debug_info_processing_ran_at": "Behandlingen ble kjørt {dateTime}", - "items_count": "{count, plural, one {# gjenstand} other {# gjenstander}}", - "jobs": "Oppgaver", - "json_editor": "JSON endrer", - "json_error": "JSON feil", - "keep": "Behold", - "keep_albums": "Behold albumer", - "keep_albums_count": "Beholder {count} {count, plural, one {album} other {albumer}}", - "keep_all": "Behold alle", - "keep_description": "Velg hva som skal forbli på enheten din etter at plassen har blitt frigjort.", - "keep_favorites": "Behold favoritter", - "keep_on_device": "Behold på enheten", - "keep_on_device_hint": "Velg objekter å beholde på denne enheten", - "keep_this_delete_others": "Behold denne, slett de andre", - "keeping": "Beholder: {items}", - "kept_this_deleted_others": "Behold denne filen og slett {count, plural, one {# element} other {# elementer}}", - "keyboard_shortcuts": "Tastatursnarveier", - "language": "Språk", - "language_no_results_subtitle": "Prøv å endre søkeord", - "language_no_results_title": "Ingen språk funnet", - "language_search_hint": "Søker etter språk...", - "language_setting_description": "Velg ditt foretrukne språk", - "large_files": "Store Filer", - "last": "Siste", - "last_months": "{count, plural, one {Sist måned} other {Siste # måneder}}", - "last_seen": "Sist sett", - "latest_version": "Siste versjon", - "latitude": "Breddegrad", - "leave": "Forlat", - "leave_album": "Forlat album", - "lens_model": "Objektiv", - "let_others_respond": "La andre respondere", - "level": "Nivå", - "library": "Bibliotek", - "library_add_folder": "Legg til mappe", - "library_edit_folder": "Endre mappe", - "library_options": "Bibliotekalternativer", - "library_page_device_albums": "Album på enheten", - "library_page_new_album": "Nytt album", - "library_page_sort_asset_count": "Antall elementer", - "library_page_sort_created": "Nylig opplastet", - "library_page_sort_last_modified": "Sist endret", - "library_page_sort_title": "Albumtittel", - "licenses": "Lisenser", - "light": "Lys", - "like": "Lik", - "like_deleted": "Som slettede", - "link_motion_video": "Koble bevegelsesvideo", - "link_to_oauth": "Lenke til OAuth", - "linked_oauth_account": "Lenket til OAuth-konto", - "list": "Liste", - "loading": "Laster", - "loading_search_results_failed": "Klarte ikke å laste inn søkeresultater", - "local": "Lokal", - "local_asset_cast_failed": "Kunne ikke caste et bilde som ikke er lastet opp til serveren", - "local_assets": "Lokale elementer", - "local_id": "Lokal ID", - "local_media_summary": "Oppsummering av lokale media", - "local_network": "Lokalt nettverk", - "local_network_sheet_info": "Appen vil koble til serveren via denne URL-en når du bruker det angitte Wi-Fi-nettverket", - "location": "Lokasjon", - "location_permission": "Stedstillatelse", - "location_permission_content": "For å bruke funksjonen for automatisk veksling trenger Immich nøyaktig plasseringstillatelse slik at den kan lese navnet på det gjeldende Wi-Fi-nettverket", - "location_picker_choose_on_map": "Velg på kart", - "location_picker_latitude_error": "Skriv inn en gyldig bredddegrad", - "location_picker_latitude_hint": "Skriv inn breddegrad her", - "location_picker_longitude_error": "Skriv inn en gyldig lengdegrad", - "location_picker_longitude_hint": "Skriv inn lengdegrad her", - "lock": "Lås", - "locked_folder": "Låst mappe", - "log_detail_title": "Loggdetaljer", - "log_out": "Logg ut", - "log_out_all_devices": "Logg ut fra alle enheter", - "logged_in_as": "Logget inn som {user}", - "logged_out_all_devices": "Logg ut av alle enheter", - "logged_out_device": "Logg ut enhet", - "login": "Logg inn", - "login_disabled": "Innlogging har blitt deaktivert", - "login_form_api_exception": "API-feil. Sjekk URL-en til serveren og prøv igjen.", - "login_form_back_button_text": "Tilbake", - "login_form_email_hint": "dinepost@epost.no", - "login_form_endpoint_hint": "http://din-server-ip:port", - "login_form_endpoint_url": "Serverendepunkt-URL", - "login_form_err_http": "Vennligst spesifiser http:// eller https://", - "login_form_err_invalid_email": "Ugyldig e-postadresse", - "login_form_err_invalid_url": "Ugyldig URL", - "login_form_err_leading_whitespace": "Ledende mellomrom", - "login_form_err_trailing_whitespace": "Etterfølgende mellomrom", - "login_form_failed_get_oauth_server_config": "Feil innlogging ved bruk av OAuth, sjekk serverens URL", - "login_form_failed_get_oauth_server_disable": "OAuth-innlogging er ikke tilgjengelig på denne serveren", - "login_form_failed_login": "Feil ved innlogging, sjekk serverens URL, e-post og passord", - "login_form_handshake_exception": "Det var et unntak for handshake med serveren. Aktiver støtte for selvsignerte sertifikater i innstillingene hvis du bruker et selvsignert sertifikat.", - "login_form_password_hint": "passord", - "login_form_save_login": "Forbli innlogget", - "login_form_server_empty": "Skriv inn en server-URL.", - "login_form_server_error": "Kunne ikke koble til server.", - "login_has_been_disabled": "Innlogging har blitt deaktivert.", - "login_password_changed_error": "Det skjedde en feil ved oppdatering av passordet", - "login_password_changed_success": "Passord oppdatert", - "logout_all_device_confirmation": "Vil du virkelig logge ut av alle enheter?", - "logout_this_device_confirmation": "Vil du virkelig logge ut av denne enheten?", - "logs": "Logger", - "longitude": "Lengdegrad", - "look": "Se", - "loop_videos": "Gjenta videoer", - "loop_videos_description": "Aktiver for å automatisk loope en video i detaljeviseren.", - "main_branch_warning": "Du bruker en utviklingsversjon; vi anbefaler på det sterkeste og bruke en utgitt versjon!", - "main_menu": "Hovedmeny", - "maintenance_action_restore": "Gjenoppretter database", - "maintenance_description": "Immich er i Vedlikeholdsmodus.", - "maintenance_end": "Avslutt vedlikeholdsmodus", - "maintenance_end_error": "Kunne ikke avslutte vedlikeholdsmodus.", - "maintenance_logged_in_as": "Logged inn som {user}", - "maintenance_restore_from_backup": "Gjenopprett fra sikkerhetskopi", - "maintenance_restore_library": "Gjenopprett biblioteket", - "maintenance_restore_library_confirm": "Hvis dette ser korrekt ut, fortsett for å gjenopprette en sikkerhetskopi!", - "maintenance_restore_library_description": "Gjenoppretter database", - "maintenance_restore_library_folder_has_files": "{folder} har {count} mappe(r)", - "maintenance_restore_library_folder_no_files": "{folder} mangler filer!", - "maintenance_restore_library_folder_pass": "lesbar og skrivbar", - "maintenance_restore_library_folder_read_fail": "ikke lesbar", - "maintenance_restore_library_folder_write_fail": "ikke skrivbar", - "maintenance_restore_library_hint_missing_files": "Det kan hende du mangler viktige filer", - "maintenance_restore_library_hint_regenerate_later": "Du kan regenerere disse senere i innstillinger", - "maintenance_restore_library_hint_storage_template_missing_files": "Bruker du lagringstemplaten? Du kan mangle filer", - "maintenance_restore_library_loading": "Laster inn integritetskontroller og heuristikker …", - "maintenance_task_backup": "Oppretter en sikkerhetskopi av eksisterende database…", - "maintenance_task_migrations": "Kjører databasemigreringer…", - "maintenance_task_restore": "Gjenoppretter valgte sikkerhetskopi…", - "maintenance_task_rollback": "Gjenoppretting feilet, ruller tilbake til gjenopprettingspunkt…", - "maintenance_title": "Midlertidig utilgjengelig", - "make": "Merke", - "manage_geolocation": "Administrer plassering", - "manage_media_access_rationale": "Denne tillatelsen er nødvendig for riktig håndtering av flytting av objekter til papirkurven og gjenoppretting av dem fra den.", - "manage_media_access_settings": "Åpne innstillinger", - "manage_media_access_subtitle": "Tillatt Immich appen å håndtere og flytte mediefiler.", - "manage_media_access_title": "Media håndteringstilgang", - "manage_shared_links": "Håndter delte linker", - "manage_sharing_with_partners": "Administrer deling med partnere", - "manage_the_app_settings": "Administrer appinnstillingene", - "manage_your_account": "Administrer kontoen din", - "manage_your_api_keys": "Administrer API-nøklene dine", - "manage_your_devices": "Administrer dine innloggede enheter", - "manage_your_oauth_connection": "Administrer tilkoblingen din med OAuth", - "map": "Kart", - "map_assets_in_bounds": "{count, plural, =0 {Ingen bilder i dette området} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "Kunne ikke hente brukerlokasjon", - "map_location_dialog_yes": "Ja", - "map_location_picker_page_use_location": "Bruk denne lokasjonen", - "map_location_service_disabled_content": "Lokasjonstjeneste må være aktivert for å vise elementer fra din nåværende lokasjon. Vil du aktivere det nå?", - "map_location_service_disabled_title": "Lokasjonstjeneste deaktivert", - "map_marker_for_images": "Kart makeringer for bilder tatt i {city}, {country}", - "map_marker_with_image": "Kartmarkør med bilde", - "map_no_location_permission_content": "Lokasjonstilgang er påkrevet for å vise elementer fra din nåværende lokasjon. Vil du tillate det nå?", - "map_no_location_permission_title": "Lokasjonstilgang avvist", - "map_settings": "Kartinnstillinger", - "map_settings_dark_mode": "Mørk modus", - "map_settings_date_range_option_day": "Siste 24 timer", - "map_settings_date_range_option_days": "Siste {days} dager", - "map_settings_date_range_option_year": "Siste år", - "map_settings_date_range_option_years": "Siste {years} år", - "map_settings_dialog_title": "Kartinnstillinger", - "map_settings_include_show_archived": "Inkluder arkiverte", - "map_settings_include_show_partners": "Inkluder partner", - "map_settings_only_show_favorites": "Vis kun favoritter", - "map_settings_theme_settings": "Karttema", - "map_zoom_to_see_photos": "Zoom ut for å se bilder", - "mark_all_as_read": "Merk alle som lest", - "mark_as_read": "Merk som lest", - "marked_all_as_read": "Merket alle som lest", - "matches": "Samsvarende", - "matching_assets": "Matchende elementer", - "media_type": "Mediatype", - "memories": "Minner", - "memories_all_caught_up": "Alt utført", - "memories_check_back_tomorrow": "Sjekk igjen i morgen for flere minner", - "memories_setting_description": "Administrer hva du ser i minnene dine", - "memories_start_over": "Start på nytt", - "memories_swipe_to_close": "Sveip opp for å lukke", - "memory": "Minne", - "memory_lane_title": "Minnefelt {title}", - "menu": "Meny", - "merge": "Slå sammen", - "merge_people": "Slå sammen personer", - "merge_people_limit": "Du kan bare slå sammen opp til 5 fjes om gangen", - "merge_people_prompt": "Vil du slå sammen disse personene? Denne handlingen kan ikke reverseres.", - "merge_people_successfully": "Sammenslåing av personer var vellykket", - "merged_people_count": "Sammenslått {count, plural, one {# person} other {# people}}", - "minimize": "Minimer", - "minute": "Minutt", - "minutes": "Minutter", - "mirror_horizontal": "Horisontal", - "mirror_vertical": "Vertikal", - "missing": "Mangler", - "mobile_app": "Mobilapp", - "mobile_app_download_onboarding_note": "Last ned den tilhørende mobilappen ved å bruke følgende alternativer", - "model": "Modell", - "month": "Måned", - "monthly_title_text_date_format": "MMMM y", - "more": "Mer", - "move": "Flytt", - "move_down": "Flytt ned", - "move_off_locked_folder": "Flytt ut av låst mappe", - "move_to": "Flytt til", - "move_to_device_trash": "Flytt til enhetens søppelkasse", - "move_to_lock_folder_action_prompt": "{count} lagt til i låst mappe", - "move_to_locked_folder": "Flytt til låst mappe", - "move_to_locked_folder_confirmation": "Disse bildene og videoene vil bli fjernet fra alle album, og kun tilgjengelige via den låste mappen", - "move_up": "Flytt opp", - "moved_to_archive": "Flyttet {count, plural, one {# element} other {# elementer}} til arkivet", - "moved_to_library": "Flyttet {count, plural, one {# element} other {# elementer}} til biblioteket", - "moved_to_trash": "Flyttet til papirkurven", - "multiselect_grid_edit_date_time_err_read_only": "Kunne ikke endre dato på element(er) med kun lese-rettigheter, hopper over", - "multiselect_grid_edit_gps_err_read_only": "Kunne ikke endre lokasjon på element(er) med kun lese-rettigheter, hopper over", - "mute_memories": "Demp minner", - "my_albums": "Mine album", - "name": "Navn", - "name_or_nickname": "Navn eller kallenavn", - "name_required": "Navn er påkrevd", - "navigate": "Naviger", - "navigate_to_time": "Naviger til tid", - "network_requirement_photos_upload": "Bruk mobildata for backup av bilder", - "network_requirement_videos_upload": "Bruk mobildata for backup av videoer", - "network_requirements": "Nettverkskrav", - "network_requirements_updated": "Nettverkskrav endret, resetter backupkø", - "networking_settings": "Nettverk", - "networking_subtitle": "Administrer serverendepunkt-innstillinger", - "never": "aldri", - "new_album": "Nytt Album", - "new_api_key": "Ny API-nøkkel", - "new_date_range": "Nytt datointervall", - "new_password": "Nytt passord", - "new_person": "Ny person", - "new_pin_code": "Ny PIN-kode", - "new_pin_code_subtitle": "Dette er første gang du åpner den låste mappen. Lag en PIN-kode for å sikre tilgangen til denne siden", - "new_timeline": "Ny tidslinje", - "new_update": "Ny oppdatering", - "new_user_created": "Ny bruker opprettet", - "new_version_available": "NY VERSJON TILGJENGELIG", - "newest_first": "Nyeste først", - "next": "Neste", - "next_memory": "Neste minne", - "no": "Nei", - "no_actions_added": "Ingen hendelser lagt til enda", - "no_albums_found": "Ingen albumer funnet", - "no_albums_message": "Opprett et album for å organisere bildene og videoene dine", - "no_albums_with_name_yet": "Det ser ut som om det ikke finnes noen album med dette navnet enda.", - "no_albums_yet": "Det ser ut som om du ikke har noen album enda.", - "no_archived_assets_message": "Arkiver bilder og videoer for å skjule dem fra visningen av bildene dine", - "no_assets_message": "Trykk her for å laste opp ditt første bilde", - "no_assets_to_show": "Ingen elementer å vise", - "no_cast_devices_found": "Ingen caste-enheter oppdaget", - "no_checksum_local": "Ingen sjekksum tilgjengelig - Kunne ikke hente lokale elementer", - "no_checksum_remote": "Ingen sjekksum tilgjengelig - Kunne ikke hente eksterne elementer", - "no_configuration_needed": "Ingen konfigurasjon nødvendig", - "no_devices": "Ingen autoriserte enheter", - "no_duplicates_found": "Ingen duplikater ble funnet.", - "no_exif_info_available": "Ingen Exif-informasjon tilgjengelig", - "no_explore_results_message": "Last opp flere bilder for å utforske samlingen din.", - "no_favorites_message": "Legg til favoritter for å finne dine beste bilder og videoer raskt", - "no_filters_added": "Ingen filtre lagt til enda", - "no_libraries_message": "Opprett et eksternt bibliotek for å se bildene og videoene dine", - "no_local_assets_found": "Ingen lokale elementer funnet med denne sjekksummen", - "no_location_set": "Ingen lokasjon satt", - "no_locked_photos_message": "Bilder og videoer i den låste mappen er skjult og vil ikke vises når du blar i biblioteket.", - "no_name": "Ingen navn", - "no_notifications": "Ingen varsler", - "no_people_found": "Ingen samsvarende personer funnet", - "no_places": "Ingen steder", - "no_remote_assets_found": "Ingen eksterne elementer funnet med denne sjekksummen", - "no_results": "Ingen resultater", - "no_results_description": "Prøv et synonym eller mer generelt søkeord", - "no_shared_albums_message": "Opprett et album for å dele bilder og videoer med personer i nettverket ditt", - "no_uploads_in_progress": "Ingen opplasting pågår", - "none": "Ingen", - "not_allowed": "Ikke tillatt", - "not_available": "Ikke tilgjengelig", - "not_in_any_album": "Ikke i noe album", - "not_selected": "Ikke valgt", - "note_apply_storage_label_to_previously_uploaded assets": "Merk: For å bruke lagringsetiketten på tidligere opplastede filer, kjør", - "notes": "Notater", - "nothing_here_yet": "Ingenting her enda", - "notification_permission_dialog_content": "For å aktivere notifikasjoner, gå til Innstillinger og velg tillat.", - "notification_permission_list_tile_content": "Gi tilgang for å aktivere notifikasjoner.", - "notification_permission_list_tile_enable_button": "Aktiver notifikasjoner", - "notification_permission_list_tile_title": "Notifikasjonstilgang", - "notification_toggle_setting_description": "Aktiver e-postvarsler", - "notifications": "Notifikasjoner", - "notifications_setting_description": "Administrer varsler", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium konfigurator", - "obtainium_configurator_instructions": "Bruk Obtainium for å installere og oppdatere Android appen direkte fra Immich sin Github utgivelse. Opprett en API nøkkel og velg en variant for å lage din Obtainium konfigurasjonslink", - "ocr": "Tekstgjenkjenning", - "official_immich_resources": "Offisielle Immich-ressurser", - "offline": "Frakoblet", - "offset": "Forskyving", - "ok": "Ok", - "oldest_first": "Eldste først", - "on_this_device": "På denne enheten", - "onboarding": "Påmønstring", - "onboarding_locale_description": "Velg ditt foretrukne språk. Du kan endre dette senere under innstillinger.", - "onboarding_privacy_description": "Følgene (valgfrie) funksjoner er avhengige av eksterne tjenester, og kan bli deaktivert når som helst under innstillinger.", - "onboarding_server_welcome_description": "La oss sette opp din instans med noen standard innstillinger.", - "onboarding_theme_description": "Velg et fargetema for din bruker. Du kan endre denne senere under dine instillinger.", - "onboarding_user_welcome_description": "La oss få deg i gang!", - "onboarding_welcome_user": "Velkommen, {user}", - "online": "Tilkoblet", - "only_favorites": "Bare favoritter", - "open": "Åpne", - "open_in_map_view": "Åpne i kartvisning", - "open_in_openstreetmap": "Åpne i OpenStreetMap", - "open_the_search_filters": "Åpne søkefiltrene", - "options": "Valg", - "or": "eller", - "organize_into_albums": "Organiser til album", - "organize_into_albums_description": "Plasser eksisterende bilder i album ved å bruke synkroniseringsinnstillinger", - "organize_your_library": "Organiser biblioteket ditt", - "original": "original", - "other": "Annet", - "other_devices": "Andre enheter", - "other_entities": "Andre elementer", - "other_variables": "Andre variabler", - "owned": "Dine", - "owner": "Eier", - "page": "Side", - "partner": "Partner", - "partner_can_access": "{partner} har tilgang", - "partner_can_access_assets": "Alle bildene og videoene dine unntatt de i arkivert og slettet tilstand", - "partner_can_access_location": "Stedet der bildene dine ble tatt", - "partner_list_user_photos": "{user}'s bilder", - "partner_list_view_all": "Vis alle", - "partner_page_empty_message": "Dine bilder deles ikke med noen partner.", - "partner_page_no_more_users": "Ingen flere brukere å legge til", - "partner_page_partner_add_failed": "Klarte ikke å legge til partner", - "partner_page_select_partner": "Velg partner", - "partner_page_shared_to_title": "Delt med", - "partner_page_stop_sharing_content": "{partner} vil ikke lenger ha tilgang til dine bilder.", - "partner_sharing": "Partnerdeling", - "partners": "Partnere", - "password": "Passord", - "password_does_not_match": "Passordet stemmer ikke", - "password_required": "Passord påkrevd", - "password_reset_success": "Passordet er blitt tilbakestilt", - "past_durations": { - "days": "Siste {days, plural, one {dag} other {{days, number} dager}}", - "hours": "Siste {hours, plural, one {time} other {{hours, number} timer}}", - "years": "Siste {years, plural, one {år} other {{years, number} år}}" - }, - "path": "Sti", - "pattern": "Mønster", - "pause": "Sett på pause", - "pause_memories": "Sett minner på pause", - "paused": "Satt på pause", - "pending": "Avventer", - "people": "Personer", - "people_edits_count": "Endret {count, plural, one {# person} other {# people}}", - "people_feature_description": "Utforsk bilder og videoer gruppert etter mennesker", - "people_selected": "{count, plural, one {# person valgt} other {# personer valgt}}", - "people_sidebar_description": "Vis en lenke til Personer i sidepanelet", - "permanent_deletion_warning": "Advarsel om permanent sletting", - "permanent_deletion_warning_setting_description": "Vis en advarsel ved permanent sletting av filer", - "permanently_delete": "Slett permanent", - "permanently_delete_assets_count": "Slett {count, plural, one {element} other {elementer}} permanent", - "permanently_delete_assets_prompt": "Vil du virkelig permanent slette {count, plural, one {dette elementet?} other {disse # elementene?}} Dette vil også slette {count, plural, one {det fra dets} other {de fra deres}} album(er).", - "permanently_deleted_asset": "Filen har blitt permanent slettet", - "permanently_deleted_assets_count": "Permanent slett {count, plural, one {# element} other {# elementer}}", - "permission": "Tillatelse", - "permission_empty": "Dine tillatelser burde ikke være tomme", - "permission_onboarding_back": "Tilbake", - "permission_onboarding_continue_anyway": "Fortsett uansett", - "permission_onboarding_get_started": "Kom i gang", - "permission_onboarding_go_to_settings": "Gå til innstillinger", - "permission_onboarding_permission_denied": "Tilgang avvist. For å bruke Immich, tillat å vise bilde og videoer i Innstillinger.", - "permission_onboarding_permission_granted": "Tilgang gitt! Du er i gang.", - "permission_onboarding_permission_limited": "Begrenset tilgang. For å la Immich sikkerhetskopiere og håndtere galleriet, tillatt bilde- og video-tilgang i Innstillinger.", - "permission_onboarding_request": "Immich trenger tilgang til å se dine bilder og videoer.", - "person": "Person", - "person_age_months": "{months, plural, one {# month} other {# months}} gammel", - "person_age_year_months": "1 år, {months, plural, one {# month} other {# months}} gammel", - "person_age_years": "{years, plural, other {# years}} gammel", - "person_birthdate": "Født den {date}", - "person_hidden": "{name}{hidden, select, true { (skjult)} other {}}", - "person_recognized": "Person gjenkjent", - "person_selected": "Person valgt", - "photo_shared_all_users": "Det ser ut som om du deler bildene med alle brukere eller det er ingen brukere å dele med.", - "photos": "Bilder", - "photos_and_videos": "Bilder & Videoer", - "photos_count": "{count, plural, one {{count, number} Bilde} other {{count, number} Bilder}}", - "photos_from_previous_years": "Bilder fra tidliger år", - "photos_only": "Kun bilder", - "pick_a_location": "Velg et sted", - "pick_custom_range": "Tilpasset område", - "pick_date_range": "Velg ett datoområde", - "pin_code_changed_successfully": "Endring av PIN kode vellykket", - "pin_code_reset_successfully": "Vellykket resatt PIN kode", - "pin_code_setup_successfully": "Vellykket oppsett av PIN kode", - "pin_verification": "PINkode verifikasjon", - "place": "Sted", - "places": "Plasseringer", - "places_count": "{count, plural, one {{count, number} Sted} other {{count, number} Steder}}", - "play": "Spill av", - "play_memories": "Spill av minner", - "play_motion_photo": "Spill av bevegelsesbilde", - "play_or_pause_video": "Spill av eller pause video", - "play_original_video": "Spill av originalvideo", - "play_original_video_setting_description": "Foretrekk avspilling av originalvideoer istedenfor omkodede videoer. Hvis originalvideo ikke er kompatibel kan avspillingsproblemer oppstå.", - "play_transcoded_video": "Spill av omkodet video", - "please_auth_to_access": "Vennligst autentiser for å fortsette", - "port": "Port", - "preferences_settings_subtitle": "Administrer appens preferanser", - "preferences_settings_title": "Innstillinger", - "preparing": "Forbereder", - "preset": "Forhåndsinstilling", - "preview": "Forhåndsvis", - "previous": "Forrige", - "previous_memory": "Forrige minne", - "previous_or_next_day": "Dag forover/bakover", - "previous_or_next_month": "Måned forover/bakover", - "previous_or_next_photo": "Bilde forover/bakover", - "previous_or_next_year": "År forover/bakover", - "primary": "Primær", - "privacy": "Privat", - "profile": "Profil", - "profile_drawer_app_logs": "Logg", - "profile_drawer_client_server_up_to_date": "Klient og server er oppdatert", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Skrivebeskyttet modus er aktivert. Langttrykk på brukerens avatarikon for å avslutte.", - "profile_image_of_user": "Profil bilde av {user}", - "profile_picture_set": "Profilbildet er satt.", - "public_album": "Offentlige album", - "public_share": "Offentlig deling", - "purchase_account_info": "Støttespiller", - "purchase_activated_subtitle": "Takk for at du støtter Immich og åpen kildekode programvare", - "purchase_activated_time": "Aktiver den {date}", - "purchase_activated_title": "Du produktnøkkel har vellyket blitt aktivert", - "purchase_button_activate": "Aktiver", - "purchase_button_buy": "Kjøp", - "purchase_button_buy_immich": "Kjøp Immich", - "purchase_button_never_show_again": "Aldri vis igjen", - "purchase_button_reminder": "Påminn meg om 30 dager", - "purchase_button_remove_key": "Ta bort produktnøkkel", - "purchase_button_select": "Velg", - "purchase_failed_activation": "Mislyktes med å aktivere! Vennligst sjekk eposten for riktig produktnøkkel!", - "purchase_individual_description_1": "For en person", - "purchase_individual_description_2": "Støttespiller status", - "purchase_individual_title": "Individuell", - "purchase_input_suggestion": "Har du en produktnøkkel? Legg til denne under", - "purchase_license_subtitle": "Kjøp Immich for å støtte den videre utviklingen av systemet", - "purchase_lifetime_description": "Kjøp for livstid", - "purchase_option_title": "KJØPSVALG", - "purchase_panel_info_1": "Å lage Immich tar mye tid og energi, og nå har vi en fulltidsansatt utvikler som jobber med å gjøre produktet så godt vi kan. Vårt oppdrag er for åpen-kildekode programvare og etisk virksomhets praktisk å kunne bli bærekraftig inntekt for utviklere og for å lage privat repekterte økesystem med mulighet for å tilby skytjeneste.", - "purchase_panel_info_2": "Siden har forpliktet oss ikke å legge til betalingsmurer, vil dette kjøpet ikke gi deg noen tilleggsfunksjoner i Immich. Vi er avhengige av brukere som deg for å støtte Immichs pågående utvikling.", - "purchase_panel_title": "Hjelp prosjektet", - "purchase_per_server": "For hver server", - "purchase_per_user": "For hver bruker", - "purchase_remove_product_key": "Fjern produktnøkkel", - "purchase_remove_product_key_prompt": "Vil du virkelig ta bort produktnøkkelen?", - "purchase_remove_server_product_key": "Ta bort Server Produktnøkkel", - "purchase_remove_server_product_key_prompt": "Vil du virkelig ta bort Server Produktnøkkelen?", - "purchase_server_description_1": "For hele serveren", - "purchase_server_description_2": "Støttespiller status", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Produktnøkkel for server er administrert av administratoren", - "query_asset_id": "Forespør elementID", - "queue_status": "Kø {count}/{total}", - "rate_asset": "Vurder objekt", - "rating": "Stjernevurdering", - "rating_clear": "Slett vurdering", - "rating_count": "{count, plural, one {# sjerne} other {# stjerner}}", - "rating_description": "Vis EXIF vurdering i informasjonspanel", - "rating_set": "Vurdering satt til {rating, plural, one {# stjerne} other {# stjerner}}", - "reaction_options": "Reaksjonsalternativer", - "read_changelog": "Les endringslogg", - "readonly_mode_disabled": "Skrivebeskyttet modus deaktivert", - "readonly_mode_enabled": "Skrivebeskyttet modus aktivert", - "ready_for_upload": "Klar for opplasting", - "reassign": "Tilordne på nytt", - "reassigned_assets_to_existing_person": "Flyttet {count, plural, one {# element} other {# elementer}} to {name, select, null {en eksisterende person} other {{name}}}", - "reassigned_assets_to_new_person": "Flyttet {count, plural, one {# element} other {# elementer}} til en ny person", - "reassing_hint": "Tilordne valgte eiendeler til en eksisterende person", - "recent": "Nylig", - "recent-albums": "Nylige album", - "recent_searches": "Nylige søk", - "recently_added": "Nylig lagt til", - "recently_added_page_title": "Nylig oppført", - "recently_taken": "Nylig tatt", - "recently_taken_page_title": "Nylig Tatt", - "refresh": "Oppdater", - "refresh_encoded_videos": "Oppdater kodete videoer", - "refresh_faces": "Oppdater ansikter", - "refresh_metadata": "Oppdater metadata", - "refresh_thumbnails": "Oppdater miniatyrbilder", - "refreshed": "Oppdatert", - "refreshes_every_file": "Oppdaterer alle filer", - "refreshing_encoded_video": "Oppdaterer kodete video", - "refreshing_faces": "Oppdaterer ansikter", - "refreshing_metadata": "Oppdaterer metadata", - "regenerating_thumbnails": "Regenererer miniatyrbilder", - "remote": "Eksternt", - "remote_assets": "Eksterne elementer", - "remote_media_summary": "Oppsummering av eksterne media", - "remove": "Fjern", - "remove_assets_album_confirmation": "Er du sikker på at du fil slette {count, plural, one {# element} other {# elementer}} fra albumet?", - "remove_assets_shared_link_confirmation": "Vil du virkelig slette {count, plural, one {# element} other {# elementer}} fra den delte lenken?", - "remove_assets_title": "Vil du fjerne eiendeler?", - "remove_custom_date_range": "Fjern egendefinert datoperiode", - "remove_deleted_assets": "Fjern fra frakoblede filer", - "remove_from_album": "Fjern fra album", - "remove_from_album_action_prompt": "{count} fjernet fra albumet", - "remove_from_favorites": "Fjern fra favoritter", - "remove_from_lock_folder_action_prompt": "{count} fjernet fra låst mappe", - "remove_from_locked_folder": "Fjern fra låst mappe", - "remove_from_locked_folder_confirmation": "Vil du virkelig flytte disse bildene og videoene ut av den låste mappen? De vil bli synlige i biblioteket.", - "remove_from_shared_link": "Fjern fra delt lenke", - "remove_memory": "Slett minne", - "remove_photo_from_memory": "Slett bilde fra dette minne", - "remove_tag": "Fjern tag", - "remove_url": "Fjern URL", - "remove_user": "Fjern bruker", - "removed_api_key": "Fjernet API-nøkkel: {name}", - "removed_from_archive": "Fjernet fra arkivet", - "removed_from_favorites": "Fjernet fra favoritter", - "removed_from_favorites_count": "{count, plural, other {Removed #}} fra favoritter", - "removed_memory": "Slettet minne", - "removed_photo_from_memory": "Slettet bilde fra minne", - "removed_tagged_assets": "Fjern tag fra {count, plural, one {# element} other {# elementer}}", - "rename": "Gi nytt navn", - "repair": "Reparer", - "repair_no_results_message": "Usporrede og savnede filer vil vises her", - "replace_with_upload": "Erstatte med opplasting", - "repository": "Depot", - "require_password": "Krev passord", - "require_user_to_change_password_on_first_login": "Krev at brukeren endrer passord ved første pålogging", - "rescan": "Skann på nytt", - "reset": "Tilbakestill", - "reset_password": "Tilbakestill passord", - "reset_people_visibility": "Tilbakestill personsynlighet", - "reset_pin_code": "Resett PINkode", - "reset_pin_code_description": "Hvis du har glemt PIN-koden din, kan du kontakte serveradministratoren for å få den tilbakestilt", - "reset_pin_code_success": "PIN-koden er tilbakestilt", - "reset_pin_code_with_password": "Du kan alltid tilbakestiller PIN-koden med passordet ditt", - "reset_sqlite": "Reset SQLite Databasen", - "reset_sqlite_confirmation": "Vil du virkelig resette SQLite databasen? Du blir nødt til å logge ut og inn igjen for å resynkronisere data", - "reset_sqlite_success": "Vellykket resetting av SQLite databasen", - "reset_to_default": "Tilbakestill til standard", - "resolution": "Oppløsning", - "resolve_duplicates": "Løs duplikater", - "resolved_all_duplicates": "Løste alle duplikater", - "restore": "Gjenopprett", - "restore_all": "Gjenopprett alle", - "restore_trash_action_prompt": "{count} gjenopprettet fra papirkurven", - "restore_user": "Gjenopprett bruker", - "restored_asset": "Gjenopprettet ressurs", - "resume": "Fortsett", - "resume_paused_jobs": "Fortsett {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Prøv opplasting på nytt", - "review_duplicates": "Gjennomgå duplikater", - "review_large_files": "Se gjennom store filer", - "role": "Rolle", - "role_editor": "Redigerer", - "role_viewer": "Visning", - "running": "Kjører", - "save": "Lagre", - "save_to_gallery": "Lagre til galleriet", - "saved": "Lagret", - "saved_api_key": "Lagret API-nøkkel", - "saved_profile": "Lagret profil", - "saved_settings": "Lagret instillinger", - "say_something": "Si noe", - "scaffold_body_error_occurred": "Feil oppstått", - "scan": "Skann", - "scan_all_libraries": "Skann alle biblioteker", - "scan_library": "Skann", - "scan_settings": "Skanneinnstillinger", - "scanning": "Skanner", - "scanning_for_album": "Skanner etter album...", - "search": "Søk", - "search_albums": "Søk i album", - "search_by_context": "Søk etter kontekst", - "search_by_description": "Søk etter beskrivelse", - "search_by_description_example": "Turdag i Sapa", - "search_by_filename": "Søk etter filnavn og filtype", - "search_by_filename_example": "f.eks. IMG_1234.JPG eller PNG", - "search_by_ocr": "Søk etter tekst i bilde", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Søk etter objektivmodell...", - "search_camera_make": "Søk etter kameramerke...", - "search_camera_model": "Søk etter kameramodell...", - "search_city": "Søk etter by...", - "search_country": "Søk etter land...", - "search_filter_apply": "Aktiver filter", - "search_filter_camera_title": "Velg kameratype", - "search_filter_date": "Dato", - "search_filter_date_interval": "{start} til {end}", - "search_filter_date_title": "Velg ett datoområde", - "search_filter_display_option_not_in_album": "Ikke i album", - "search_filter_display_options": "Visningsvalg", - "search_filter_filename": "Søk etter filnavn", - "search_filter_location": "Lokasjon", - "search_filter_location_title": "Velg lokasjon", - "search_filter_media_type": "Medietype", - "search_filter_media_type_title": "Velg medietype", - "search_filter_ocr": "Søk etter tekst i bilde", - "search_filter_people_title": "Velg mennesker", - "search_filter_star_rating": "Stjernerating", - "search_for": "Søk etter", - "search_for_existing_person": "Søk etter eksisterende person", - "search_no_more_result": "Ingen flere resultater", - "search_no_people": "Ingen personer", - "search_no_people_named": "Ingen personer med navnet \"{name}\"", - "search_no_result": "Ingen resultater funnet, prøv ett annet søkeord eller kombinasjon", - "search_options": "Søke alternativer", - "search_page_categories": "Kategorier", - "search_page_motion_photos": "Bevegelige bilder", - "search_page_no_objects": "Ingen elementinfo tilgjengelig", - "search_page_no_places": "Ingen stedsinformasjon er tilgjengelig", - "search_page_screenshots": "Skjermbilder", - "search_page_search_photos_videos": "Søk etter dine bilder og videoer", - "search_page_selfies": "Selfier", - "search_page_things": "Ting", - "search_page_view_all_button": "Vis alle", - "search_page_your_activity": "Din aktivitet", - "search_page_your_map": "Ditt kart", - "search_people": "Søk personer", - "search_places": "Søk steder", - "search_rating": "Søk etter vurdering...", - "search_result_page_new_search_hint": "Nytt søk", - "search_settings": "Søke instillinger", - "search_state": "Søk etter fylke...", - "search_suggestion_list_smart_search_hint_1": "Smartsøk er aktivert som standard, for å søke etter metadata bruk syntaksen ", - "search_suggestion_list_smart_search_hint_2": "m:ditt-søkeord", - "search_tags": "Søk tags...", - "search_timezone": "Søk etter tidssone....", - "search_type": "Søk etter type", - "search_your_photos": "Søk i dine bilder", - "searching_locales": "Søker lokaler...", - "second": "Sekund", - "see_all_people": "Vis alle mennesker", - "select": "Velg", - "select_album": "Velg album", - "select_album_cover": "Velg albumomslag", - "select_albums": "Velg albumer", - "select_all": "Velg alle", - "select_all_duplicates": "Velg alle duplikater", - "select_all_in": "Velg alt i {group}", - "select_avatar_color": "Velg avatarfarge", - "select_count": "{count, plural, one {Velg #} other {Valgt #}}", - "select_cutoff_date": "Velg frist", - "select_face": "Velg ansikt", - "select_featured_photo": "Velg fremhevet bilde", - "select_from_computer": "Velg fra datamaskin", - "select_keep_all": "Velg beholde alle", - "select_library_owner": "Velg bibliotekseier", - "select_new_face": "Velg nytt ansikt", - "select_people": "Velg mennesker", - "select_person": "Valgt person", - "select_person_to_tag": "Velg en person å tagge", - "select_photos": "Velg bilder", - "select_trash_all": "Velg å flytte alt til papirkurven", - "select_user_for_sharing_page_err_album": "Mislyktes ved oppretting av album", - "selected": "Valgt", - "selected_count": "{count, plural, other {# valgt}}", - "selected_gps_coordinates": "Valgte GPS-koordinater", - "send_message": "Send melding", - "send_welcome_email": "Send velkomstmelding", - "server_endpoint": "Server endepunkt", - "server_info_box_app_version": "App-versjon", - "server_info_box_server_url": "Server-adresse", - "server_offline": "Server frakoblet", - "server_online": "Server tilkoblet", - "server_privacy": "Server personvern", - "server_restarting_description": "Siden vil oppdateres øyeblikkelig.", - "server_restarting_title": "Server restarter", - "server_stats": "Serverstatistikk", - "server_update_available": "Serveroppdatering er tilgjengelig", - "server_version": "Server Versjon", - "set": "Sett", - "set_as_album_cover": "Sett som albumomslag", - "set_as_featured_photo": "Angi som fremhevet bilde", - "set_as_profile_picture": "Sett som profilbilde", - "set_date_of_birth": "Sett fødselsdato", - "set_profile_picture": "Sett profilbilde", - "set_slideshow_to_fullscreen": "Sett lysbildefremvisning til fullskjerm", - "set_stack_primary_asset": "Velg som primærbilde", - "setting_image_viewer_help": "Detaljvisningen laster først miniatyrbildet, deretter forhåndsvisningsbildet (hvis aktivert), og til slutt originalen (hvis aktivert).", - "setting_image_viewer_original_subtitle": "Aktiver for å laste originalbildet i full oppløsning (stort!). Deaktiver for å spare databruk (både nettverksbruk og bufferdata på enheten).", - "setting_image_viewer_original_title": "Last originalbildet", - "setting_image_viewer_preview_subtitle": "Aktiver for å laste et bilde av medium oppløsning. Deaktiver for å enten direkte laste inn originalen eller kun benytte miniatyrbilde.", - "setting_image_viewer_preview_title": "Last forhåndsvisningsbilde", - "setting_image_viewer_title": "Bilder", - "setting_languages_apply": "Bekreft", - "setting_languages_subtitle": "Endre app-språk", - "setting_notifications_notify_failures_grace_period": "Varsle om sikkerhetskopieringsfeil i bakgrunnen: {duration}", - "setting_notifications_notify_hours": "{count} timer", - "setting_notifications_notify_immediately": "umiddelbart", - "setting_notifications_notify_minutes": "{count} minutter", - "setting_notifications_notify_never": "aldri", - "setting_notifications_notify_seconds": "{count} sekunder", - "setting_notifications_single_progress_subtitle": "Detaljert opplastingsinformasjon per element", - "setting_notifications_single_progress_title": "Vis detaljert status på sikkerhetskopiering i bakgrunnen", - "setting_notifications_subtitle": "Juster notifikasjonsinnstillinger", - "setting_notifications_total_progress_subtitle": "Total opplastingsstatus (fullført/totalt elementer)", - "setting_notifications_total_progress_title": "Vis status på sikkerhetskopiering i bakgrunnen", - "setting_video_viewer_auto_play_subtitle": "Automatisk avspilling av videoer når de åpnes", - "setting_video_viewer_auto_play_title": "Automatisk avspilling av videoer", - "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_original_video_subtitle": "Når det streames en video fra serveren, spill originalkvaliteten selv om en omkodet versjon finnes. Dette kan medføre buffring. Videoer som er lagret lokalt på enheten spilles i originalkvalitet uavhengig av denne innstillingen.", - "setting_video_viewer_original_video_title": "Tving original video", - "settings": "Innstillinger", - "settings_require_restart": "Vennligst restart Immich for å aktivere denne innstillingen", - "settings_saved": "Innstillinger lagret", - "setup_pin_code": "Sett opp en PINkode", - "share": "Del", - "share_action_prompt": "Delte {count} elementer", - "share_add_photos": "Legg til bilder", - "share_assets_selected": "{count} valgt", - "share_dialog_preparing": "Forbereder ...", - "share_link": "Del link", - "shared": "Delt", - "shared_album_activities_input_disable": "Kommenterer er deaktivert", - "shared_album_activity_remove_content": "Vil du slette denne aktiviteten?", - "shared_album_activity_remove_title": "Slett aktivitet", - "shared_album_section_people_action_error": "Feil ved fjerning/sletting fra album", - "shared_album_section_people_action_leave": "Fjern bruker fra album", - "shared_album_section_people_action_remove_user": "Fjern bruker fra album", - "shared_album_section_people_title": "MENNESKER", - "shared_by": "Delt av", - "shared_by_user": "Delt av {user}", - "shared_by_you": "Delt av deg", - "shared_from_partner": "Bilder fra {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Lastet opp", - "shared_link_app_bar_title": "Delte linker", - "shared_link_clipboard_copied_massage": "Kopiert til utklippslisten", - "shared_link_clipboard_text": "Link: {link}\nPassord: {password}", - "shared_link_create_error": "Feil ved oppretting av delbar link", - "shared_link_custom_url_description": "Få tilgang til denne delte lenken med en egendefinert URL", - "shared_link_edit_description_hint": "Endre delebeskrivelse", - "shared_link_edit_expire_after_option_day": "1 dag", - "shared_link_edit_expire_after_option_days": "{count} dager", - "shared_link_edit_expire_after_option_hour": "1 time", - "shared_link_edit_expire_after_option_hours": "{count} timer", - "shared_link_edit_expire_after_option_minute": "1 minutt", - "shared_link_edit_expire_after_option_minutes": "{count} minutter", - "shared_link_edit_expire_after_option_months": "{count} måneder", - "shared_link_edit_expire_after_option_year": "{count} år", - "shared_link_edit_password_hint": "Skriv inn dele-passord", - "shared_link_edit_submit_button": "Oppdater link", - "shared_link_error_server_url_fetch": "Kunne ikke hente server-url", - "shared_link_expires_day": "Utgår om {count} dag", - "shared_link_expires_days": "Utgår om {count} dager", - "shared_link_expires_hour": "Utgår om {count} time", - "shared_link_expires_hours": "Utgår om {count} timer", - "shared_link_expires_minute": "Utgår om {count} minutt", - "shared_link_expires_minutes": "Utgår om {count} minutter", - "shared_link_expires_never": "Utgår ∞", - "shared_link_expires_second": "Utgår om {count} sekund", - "shared_link_expires_seconds": "Utgår om {count} sekunder", - "shared_link_individual_shared": "Individuelt delt", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Håndter delte linker", - "shared_link_options": "Alternativer for delte lenke", - "shared_link_password_description": "Krev et passord for å få tilgang til denne delte lenken", - "shared_links": "Delte linker", - "shared_links_description": "Del bilder og videoer med lenke", - "shared_photos_and_videos_count": "{assetCount, plural, other {# delte bilder og videoer.}}", - "shared_with_me": "Delt med meg", - "shared_with_partner": "Delt med {partner}", - "sharing": "Deling", - "sharing_enter_password": "Vennligst skriv inn passordet for å se denne siden.", - "sharing_page_album": "Delte album", - "sharing_page_description": "Lag delte album for å dele bilder og videoer med personer i nettverket ditt.", - "sharing_page_empty_list": "TOM LISTE", - "sharing_sidebar_description": "Vis en lenke til Deling i sidepanelet", - "sharing_silver_appbar_create_shared_album": "Lag delt album", - "sharing_silver_appbar_share_partner": "Del med partner", - "shift_to_permanent_delete": "trykk ⇧ for å slette eiendeler permanent", - "show_album_options": "Vis albumalternativer", - "show_albums": "Vis album", - "show_all_people": "Vis alle mennesker", - "show_and_hide_people": "Vis og skjul personer", - "show_file_location": "Vis filplassering", - "show_gallery": "Vis galleri", - "show_hidden_people": "Vis skjulte personer", - "show_in_timeline": "Vis i tidslinje", - "show_in_timeline_setting_description": "Vis bilder og videoer fra denne brukeren i tidslinjen din", - "show_keyboard_shortcuts": "Vis tastatursnarveier", - "show_metadata": "Vis metadata", - "show_or_hide_info": "Vis eller skjul informasjon", - "show_password": "Vis passord", - "show_person_options": "Vis personalternativer", - "show_progress_bar": "Vis fremdriftslinje", - "show_schema": "Vis skjema", - "show_search_options": "Vis søkealternativer", - "show_shared_links": "Vis delte lenker", - "show_slideshow_transition": "Vis overgang til lysbildefremvisning", - "show_supporter_badge": "Supportermerke", - "show_supporter_badge_description": "Vis et supportermerke", - "show_text_recognition": "Vis tekstgjenkjenning", - "show_text_search_menu": "Vis tekstsøk meny", - "shuffle": "Bland", - "sidebar": "Sidefelt", - "sidebar_display_description": "Vis en lenke for visningen i sidefeltet", - "sign_out": "Logg ut", - "sign_up": "Registrer deg", - "size": "Størrelse", - "skip_to_content": "Gå til innhold", - "skip_to_folders": "Hopp til mapper", - "skip_to_tags": "Hopp til tagger", - "slideshow": "Lysbildefremvisning", - "slideshow_repeat": "Gjenta lysbildefremvisning", - "slideshow_repeat_description": "Gå tilbake til begynnelsen når lysbildeserien er slutt", - "slideshow_settings": "Lysbildefremvisning innstillinger", - "sort_albums_by": "Sorter album etter...", - "sort_created": "Dato opprettet", - "sort_items": "Antall enheter", - "sort_modified": "Dato modifisert", - "sort_newest": "Nyeste bilde", - "sort_oldest": "Eldste bilde", - "sort_people_by_similarity": "Sorter personer etter likhet", - "sort_recent": "Nyeste bilde", - "sort_title": "Tittel", - "source": "Kilde", - "stack": "Stable", - "stack_action_prompt": "{count} stakket", - "stack_duplicates": "Stable duplikater", - "stack_select_one_photo": "Velg hovedbilde for bildestabbel", - "stack_selected_photos": "Stable valgte bilder", - "stacked_assets_count": "Stable {count, plural, one {# element} other {# elementer}}", - "stacktrace": "Stakkspor", - "start": "Start", - "start_date": "Startdato", - "start_date_before_end_date": "Startdato må være før sluttdato", - "state": "Fylke", - "status": "Status", - "stop_casting": "Stopp casting", - "stop_motion_photo": "Stopmotionbilde", - "stop_photo_sharing": "Stopp deling av bildene dine?", - "stop_photo_sharing_description": "{partner} vil ikke lenger ha tilgang til bildene dine.", - "stop_sharing_photos_with_user": "Slutt å dele bildene dine med denne brukeren", - "storage": "Lagring", - "storage_label": "Lagringsetikett", - "storage_quota": "Lagringsplass", - "storage_usage": "{used} av {available} brukt", - "submit": "Send inn", - "success": "Vellykket", - "suggestions": "Forslag", - "sunrise_on_the_beach": "Soloppgang på stranden", - "support": "Støtte", - "support_and_feedback": "Støtte og tilbakemelding", - "support_third_party_description": "Immich-installasjonen din ble pakket av en tredjepart. Problemer du opplever kan være forårsaket av den pakken, så vennligst ta opp problemer med dem i første omgang ved å bruke koblingene nedenfor.", - "swap_merge_direction": "Bytt retning på sammenslåingen", - "sync": "Synkroniser", - "sync_albums": "Synkroniser album", - "sync_albums_manual_subtitle": "Synkroniser alle opplastede videoer og bilder til det valgte backupalbumet", - "sync_local": "Synkroniser lokalt", - "sync_remote": "Synkroniser eksternt", - "sync_status": "Synkroniseringsstatus", - "sync_status_subtitle": "Vis og håndter synkronisering", - "sync_upload_album_setting_subtitle": "Opprett og last opp dine bilder og videoer til det valgte albumet på Immich", - "tag": "Tagg", - "tag_assets": "Merk ressurser", - "tag_created": "Lag merke: {tag}", - "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", - "tag_not_found_question": "Finner du ikke en merke? Opprett en nytt merke.", - "tag_people": "Tag personer", - "tag_updated": "Oppdater merke: {tag}", - "tagged_assets": "Merket {count, plural, one {# element} other {# elementer}}", - "tags": "Merker", - "tap_to_run_job": "Trykk for å kjøre jobben", - "template": "Mal", - "text_recognition": "Tekstgjenkjenning", - "theme": "Tema", - "theme_selection": "Temavalg", - "theme_selection_description": "Automatisk sett tema til lys eller mørk basert på nettleserens systeminnstilling", - "theme_setting_asset_list_storage_indicator_title": "Vis lagringsindiaktor på elementer i fotorutenettet", - "theme_setting_asset_list_tiles_per_row_title": "Antall elementer per rad ({count})", - "theme_setting_colorful_interface_subtitle": "Angi primærfarge til bakgrunner.", - "theme_setting_colorful_interface_title": "Fargefullt grensesnitt", - "theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten på bilder i detaljvisning", - "theme_setting_image_viewer_quality_title": "Kvalitet på bildevisning", - "theme_setting_primary_color_subtitle": "Velg en farge for primærhendelser og etterfølgende.", - "theme_setting_primary_color_title": "Primærfarge", - "theme_setting_system_primary_color_title": "Bruk systemfarge", - "theme_setting_system_theme_switch": "Automatisk (følg systeminnstillinger)", - "theme_setting_theme_subtitle": "Velg app-ens temainnstilling", - "theme_setting_three_stage_loading_subtitle": "Tre-trinns innlasting kan øke lasteytelsen, men forårsaker betydelig høyere nettverksbelastning", - "theme_setting_three_stage_loading_title": "Aktiver tre-trinns innlasting", - "then": "Da", - "they_will_be_merged_together": "De vil bli slått sammen", - "third_party_resources": "Tredjeparts Ressurser", - "time": "Tid", - "time_based_memories": "Tidsbaserte minner", - "time_based_memories_duration": "Antall sekunder å vise hvert bilde.", - "timeline": "Tidslinje", - "timezone": "Tidssone", - "to_archive": "Arkiv", - "to_change_password": "Endre passord", - "to_favorite": "Favoritt", - "to_login": "Logg inn", - "to_multi_select": "for multivalg", - "to_parent": "Gå til overodnet", - "to_select": "for valg", - "to_trash": "Papirkurv", - "toggle_settings": "Bytt innstillinger", - "toggle_theme_description": "Aktiver tema", - "total": "Total", - "total_usage": "Totalt brukt", - "trash": "Papirkurv", - "trash_action_prompt": "{count} flyttet til søppel", - "trash_all": "Slett alt", - "trash_count": "Slett {count, number}", - "trash_delete_asset": "Slett element", - "trash_emptied": "Søppelbøtte Tømt", - "trash_no_results_message": "Her vises bilder og videoer som er flyttet til papirkurven.", - "trash_page_delete_all": "Slett alt", - "trash_page_empty_trash_dialog_content": "Vil du Tømme papirkurven? Objektene vil bli permanent fjernet fra Immich", - "trash_page_info": "Objekter i papirkurven blir permanent fjernet etter {days} dager", - "trash_page_no_assets": "Ingen forkastede elementer", - "trash_page_restore_all": "Gjenopprett alt", - "trash_page_select_assets_btn": "Velg elementer", - "trash_page_title": "Søppelbøtte ({count})", - "trashed_items_will_be_permanently_deleted_after": "Elementer i papirkurven vil bli permanent slettet etter {days, plural, one {# dag} other {# dager}}.", - "trigger": "Utløser", - "trigger_asset_uploaded": "Objekt lastet opp", - "trigger_asset_uploaded_description": "Utløser når ett nytt objekt er lastet opp", - "trigger_description": "En hendelse som utløser arbeidsflyten", - "trigger_person_recognized": "Person gjenkjent", - "trigger_person_recognized_description": "Utløses når en person blir gjenkjent", - "trigger_type": "Utløsertype", - "troubleshoot": "Feilsøk", - "type": "Type", - "unable_to_change_pin_code": "Klarte ikke å endre PIN-kode", - "unable_to_check_version": "Kunne ikke sjekke app eller serverversjon", - "unable_to_setup_pin_code": "Klarte ikke å sette opp PINkode", - "unarchive": "Fjern fra arkiv", - "unarchive_action_prompt": "{count} slettet fra Arkiv", - "unarchived_count": "{count, plural, other {uarkivert #}}", - "undo": "Angre", - "unfavorite": "Fjern favoritt", - "unfavorite_action_prompt": "{count} slettet fra Favoritter", - "unhide_person": "Vis person", - "unknown": "Ukjent", - "unknown_country": "Ukjent Land", - "unknown_date": "Ukjent dato", - "unknown_year": "Ukjent år", - "unlimited": "Ubegrenset", - "unlink_motion_video": "Koble fra bevegelsesvideo", - "unlink_oauth": "Fjern kobling til OAuth", - "unlinked_oauth_account": "Koblet fra OAuth-konto", - "unmute_memories": "Opphev demping av minner", - "unnamed_album": "Navnløst album", - "unnamed_album_delete_confirmation": "Vil du virkelig slette dette albumet?", - "unnamed_share": "Deling uten navn", - "unsaved_change": "Ulagrede endringer", - "unselect_all": "Fjern alle valg", - "unselect_all_duplicates": "Fjern markeringen av alle duplikater", - "unselect_all_in": "Fjern velging av alle i {group}", - "unstack": "avstable", - "unstack_action_prompt": "{count} ustakket", - "unstacked_assets_count": "Ikke stablet {count, plural, one {# element} other {# elementer}}", - "unsupported_field_type": "Ustøttede felttyper", - "untagged": "Umerket", - "untitled_workflow": "Arbeidsflyt uten navn", - "up_next": "Neste", - "update_location_action_prompt": "Oppdater plasseringen til {count} valgte elementer med:", - "updated_at": "Oppdatert", - "updated_password": "Passord oppdatert", - "upload": "Last opp", - "upload_concurrency": "Samtidig opplastning", - "upload_details": "Opplastingsdetaljer", - "upload_dialog_info": "Vil du utføre backup av valgte element(er) til serveren?", - "upload_dialog_title": "Last opp element", - "upload_error_with_count": "Opplastningsfeil for {count, plural, one {# element} other {# elementer}}", - "upload_errors": "Opplasting fullført med {count, plural, one {# error} other {# errors}}, oppdater siden for å se nye opplastingsressurser.", - "upload_finished": "Opplasting fullført", - "upload_progress": "Gjenstående {remaining, number} – behandlet {processed, number}/{total, number}", - "upload_skipped_duplicates": "Hoppet over {count, plural, one {# duplisert element} other {# dupliserte elementer}}", - "upload_status_duplicates": "Duplikater", - "upload_status_errors": "Feil", - "upload_status_uploaded": "Opplastet", - "upload_success": "Opplasting vellykket, oppdater siden for å se nye opplastninger.", - "upload_to_immich": "Last opp til Immich ({count})", - "uploading": "Laster opp", - "uploading_media": "Laster opp media", - "url": "URL", - "usage": "Bruk", - "use_biometric": "Bruk biometri", - "use_current_connection": "Bruk nåværende tilkobling", - "use_custom_date_range": "Bruk egendefinert datoperiode i stedet", - "user": "Bruker", - "user_has_been_deleted": "Denne brukeren har blitt slettet.", - "user_id": "Bruker ID", - "user_liked": "{user} likte {type, select, photo {dette bildet} video {denne videoen} asset {dette elementet} other {dette}}", - "user_pin_code_settings": "PINkode", - "user_pin_code_settings_description": "Håndter din PINkode", - "user_privacy": "Personverninnstillinger", - "user_purchase_settings": "Kjøpe", - "user_purchase_settings_description": "Administrer dine kjøp", - "user_role_set": "Sett {user} som {role}", - "user_usage_detail": "Detaljer av brukernes forbruk", - "user_usage_stats": "Kontobruksstatistikk", - "user_usage_stats_description": "Vis kontobruksstatistikk", - "username": "Brukernavn", - "users": "Brukere", - "users_added_to_album_count": "Lagt til {count, plural, one {# bruker} other {# brukere}} til albumet", - "utilities": "Verktøy", - "validate": "Valider", - "validate_endpoint_error": "Skriv inn en gyldig URL", - "validation_error": "valideringsfeil", - "variables": "Variabler", - "version": "Versjon", - "version_announcement_closing": "Din venn, Alex", - "version_announcement_message": "Hei! En ny versjon av Immich er tilgjengelig. Vennligst ta deg tid til å lese utgivelsesnotatene for å sikre at oppsettet ditt er oppdatert for å forhindre feilkonfigurasjoner, spesielt hvis du bruker WatchTower eller en annen mekanisme som håndterer oppdatering av Immich-forekomsten din automatisk.", - "version_history": "Versjonshistorikk", - "version_history_item": "Installert {version} den {date}", - "video": "Video", - "video_hover_setting": "Spill av forhåndsvisining mens du holder over musepekeren", - "video_hover_setting_description": "Spill av forhåndsvisning mens en musepeker er over elementet. Selv når den er deaktivert, kan avspilling startes ved å holde musepekeren over avspillingsikonet.", - "videos": "Videoer", - "videos_count": "{count, plural, one {# Video} other {# Videoer}}", - "videos_only": "Kun videoer", - "view": "Vis", - "view_album": "Vis album", - "view_all": "Vis alle", - "view_all_users": "Vis alle brukere", - "view_asset_owners": "Vis objekteiere", - "view_details": "Vis detaljer", - "view_in_timeline": "Vis i tidslinje", - "view_link": "Vis lenke", - "view_links": "Vis lenker", - "view_name": "Vis", - "view_next_asset": "Vis neste fil", - "view_previous_asset": "Vis forrige fil", - "view_qr_code": "Vis QR-kode", - "view_similar_photos": "Vis lignende bilder", - "view_stack": "Vis stabel", - "view_user": "Vis bruker", - "viewer_remove_from_stack": "Fjern fra stabling", - "viewer_stack_use_as_main_asset": "Bruk som hovedelement", - "viewer_unstack": "avstable", - "visibility_changed": "Synlighet endret for {count, plural, one {# person} other {# people}}", - "visual": "Visuell", - "visual_builder": "Visuell oppbygging", - "waiting": "Venter", - "waiting_count": "Ventende: {count}", - "warning": "Advarsel", - "week": "Uke", - "welcome": "Velkommen", - "welcome_to_immich": "Velkommen til Immich", - "width": "Bredde", - "wifi_name": "Wi-Fi-navn", - "workflow_delete_prompt": "Er du sikker på at du vil slette denne arbeidsflyten?", - "workflow_deleted": "Arbeidsflyt slettet", - "workflow_description": "Beskrivelse av arbeidsflyt", - "workflow_info": "Informasjon om arbeidsflyt", - "workflow_json": "Arbeidsflyt JSON", - "workflow_json_help": "Endre arbeidsflytskonfigurasjon i JSON format. Endringer vil synkroniseres til den visuelle konfiguratoren.", - "workflow_name": "Navn på arbeidsflyt", - "workflow_navigation_prompt": "Er du sikker på at du vil forlate uten å lagre endringene?", - "workflow_summary": "Oppsummering av arbeidsflyt", - "workflow_update_success": "Vellykket oppdatering av arbeidsflyt", - "workflow_updated": "Arbeidsflyt oppdatert", - "workflows": "Arbeidsflyter", - "workflows_help_text": "Arbeidsflyter automatiserer hendelser på dine mediefiler basert på dine utløsere og filtre", - "wrong_pin_code": "Feil PIN-kode", - "year": "År", - "years_ago": "{years, plural, one {# år} other {# år}} siden", - "yes": "Ja", - "you_dont_have_any_shared_links": "Du har ingen delte lenker", - "your_wifi_name": "Ditt Wi-Fi-navn", - "zero_to_clear_rating": "Trykk 0 for å fjerne vurdering", - "zoom_image": "Zoom Bilde", - "zoom_to_bounds": "Zoom til grensene" -} +{} diff --git a/i18n/nl.json b/i18n/nl.json index 6d4f780dd3..0967ef424b 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -1,2401 +1 @@ -{ - "about": "Over", - "account": "Account", - "account_settings": "Accountinstellingen", - "acknowledge": "Erkennen", - "action": "Actie", - "action_common_update": "Bijwerken", - "action_description": "Een groep acties om uit te voeren op de gefilterde items", - "actions": "Acties", - "active": "Actief", - "active_count": "Actief: {count}", - "activity": "Activiteit", - "activity_changed": "Activiteit is {enabled, select, true {ingeschakeld} other {uitgeschakeld}}", - "add": "Toevoegen", - "add_a_description": "Beschrijving toevoegen", - "add_a_location": "Een locatie toevoegen", - "add_a_name": "Naam toevoegen", - "add_a_title": "Titel toevoegen", - "add_action": "Actie toevoegen", - "add_action_description": "Klik om een uit te voeren actie toe te voegen", - "add_assets": "Items toevoegen", - "add_birthday": "Verjaardag toevoegen", - "add_endpoint": "Server toevoegen", - "add_exclusion_pattern": "Uitsluitingspatroon toevoegen", - "add_filter": "Filter toevoegen", - "add_filter_description": "Klik om een filter voorwaarde toe te voegen", - "add_location": "Locatie toevoegen", - "add_more_users": "Meer gebruikers toevoegen", - "add_partner": "Partner toevoegen", - "add_path": "Pad toevoegen", - "add_photos": "Foto's toevoegen", - "add_tag": "Tag toevoegen", - "add_to": "Toevoegen aan…", - "add_to_album": "Aan album toevoegen", - "add_to_album_bottom_sheet_added": "Toegevoegd aan {album}", - "add_to_album_bottom_sheet_already_exists": "Staat al in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Sommige lokale items konden niet aan album toegevoegd worden", - "add_to_album_toggle": "Selectie inschakelen voor {album}", - "add_to_albums": "Toevoegen aan albums", - "add_to_albums_count": "Toevoegen aan albums ({count})", - "add_to_bottom_bar": "Toevoegen aan", - "add_to_shared_album": "Aan gedeeld album toevoegen", - "add_upload_to_stack": "Voeg upload toe aan stack", - "add_url": "URL toevoegen", - "add_workflow_step": "Stap aan workflow toevoegen", - "added_to_archive": "Toegevoegd aan archief", - "added_to_favorites": "Toegevoegd aan favorieten", - "added_to_favorites_count": "{count, number} toegevoegd aan favorieten", - "admin": { - "add_exclusion_pattern_description": "Uitsluitingspatronen toevoegen. Globbing met *, ** en ? wordt ondersteund. Om alle bestanden in een map met de naam \"Raw\" te negeren, gebruik \"**/Raw/**\". Om alle bestanden die eindigen op \".tif\" te negeren, gebruik \"**/*.tif\". Om een absoluut pad te negeren, gebruik \"/path/to/ignore/**\".", - "admin_user": "Beheerder gebruiker", - "asset_offline_description": "Dit item uit een externe bibliotheek is niet meer beschikbaar op de schijf en is naar de prullenbak verplaatst. Als het bestand binnen de bibliotheek is verplaatst, controleer dan je tijdlijn voor het nieuwe bijbehorende item. Om dit bestand te herstellen, zorg ervoor dat het onderstaande bestandspad toegankelijk is voor Immich en scan de bibliotheek opnieuw.", - "authentication_settings": "Authenticatie-instellingen", - "authentication_settings_description": "Wachtwoord-, OAuth-, en andere authenticatie-instellingen beheren", - "authentication_settings_disable_all": "Weet je zeker dat je alle inlogmethoden wilt uitschakelen? Inloggen zal volledig worden uitgeschakeld.", - "authentication_settings_reenable": "Gebruik een servercommando om opnieuw in te schakelen.", - "background_task_job": "Achtergrondtaken", - "backup_database": "Maak database back-up", - "backup_database_enable_description": "Database back-ups activeren", - "backup_keep_last_amount": "Aantal back-ups om te bewaren", - "backup_onboarding_1_description": "externe kopie in de cloud of op een andere fysieke locatie.", - "backup_onboarding_2_description": "lokale kopieën op verschillende apparaten. Dit omvat de hoofdbestanden én een lokale back-up van deze bestanden.", - "backup_onboarding_3_description": "totaal aantal kopieën van de gegevens, inclusief originele bestanden. Dit omvat 1 externe kopie en 2 lokale kopieën.", - "backup_onboarding_description": "Een 3-2-1 back-up strategie wordt aanbevolen om de gegevens te beschermen. Bewaar kopieën van de geüploade foto's/video's en de Immich database voor een complete back-up oplossing.", - "backup_onboarding_footer": "Raadpleeg de documentatie voor meer informatie over het maken van back-ups van Immich.", - "backup_onboarding_parts_title": "Een 3-2-1 back-up omvat:", - "backup_onboarding_title": "Back-ups", - "backup_settings": "Database dump instellingen", - "backup_settings_description": "Beheer database dump instellingen.", - "cleared_jobs": "Taken gewist voor: {job}", - "config_set_by_file": "Instellingen worden momenteel beheerd door een configuratiebestand", - "confirm_delete_library": "Weet je zeker dat je de bibliotheek {library} wilt verwijderen?", - "confirm_delete_library_assets": "Weet je zeker dat je deze bibliotheek wilt verwijderen? Hiermee {count, plural, one {wordt # item} other {worden alle # items}} uit Immich verwijderd en dit kan niet ongedaan worden gemaakt. Bestanden blijven op de schijf staan.", - "confirm_email_below": "Typ hieronder \"{email}\" ter bevestiging", - "confirm_reprocess_all_faces": "Weet je zeker dat je alle gezichten opnieuw wilt verwerken? Hiermee worden ook alle mensen gewist.", - "confirm_user_password_reset": "Weet je zeker dat je het wachtwoord van {user} wilt resetten?", - "confirm_user_pin_code_reset": "Weet je zeker dat je de pincode van {user} wilt resetten?", - "copy_config_to_clipboard_description": "Kopieer de huidige systeem­configuratie als een JSON-object naar het klembord", - "create_job": "Taak maken", - "cron_expression": "Cron expressie", - "cron_expression_description": "Stel het scaninterval in met het cron-formaat. Voor meer informatie kun je bijvoorbeeld kijken naar Crontab Guru", - "cron_expression_presets": "Cron-expressie presets", - "disable_login": "Inloggen uitschakelen", - "duplicate_detection_job_description": "Machine learning uitvoeren op items om vergelijkbare items te vinden. Dit is gebaseerd op Slim Zoeken", - "exclusion_pattern_description": "Met uitsluitingspatronen kun je bestanden en mappen negeren bij het scannen van je bibliotheek. Dit is handig als je mappen hebt met bestanden die je niet wilt importeren, zoals RAW bestanden.", - "export_config_as_json_description": "Download de huidige systeem­configuratie als een JSON-bestand", - "external_libraries_page_description": "Externe-bibliotheek­pagina voor administrators", - "face_detection": "Gezichtsdetectie", - "face_detection_description": "Detecteer gezichten in items met behulp van machine learning. Voor video's wordt alleen de thumbnail gebruikt. \"Vernieuwen\" verwerkt alle items (opnieuw). \"Reset\" verwijdert daarnaast alle huidige gezichtgegevens. \"Missend\" plaatst items in de wachtrij die nog niet zijn verwerkt. Gedetecteerde gezichten worden in de wachtrij geplaatst voor gezichtsherkenning nadat gezichtsdetectie is voltooid, waarbij ze worden gegroepeerd in bestaande of nieuwe mensen.", - "facial_recognition_job_description": "Groepeer gedetecteerde gezichten tot mensen. Deze stap wordt uitgevoerd nadat gezichtsdetectie is voltooid. \"Resetten\" (her-)clustert alle gezichten. \"Missend\" plaatst gezichten in de wachtrij waaraan geen persoon is toegewezen.", - "failed_job_command": "Commando {command} mislukt voor taak: {job}", - "force_delete_user_warning": "WAARSCHUWING: Hiermee worden de gebruiker en alle items onmiddellijk verwijderd. Dit kan niet ongedaan worden gemaakt en de bestanden kunnen niet worden hersteld.", - "image_format": "Formaat", - "image_format_description": "WebP produceert kleinere bestanden dan JPEG, maar is langzamer om te verwerken.", - "image_fullsize_description": "Afbeelding op ware grootte met gestripte metadata, gebruikt bij inzoomen", - "image_fullsize_enabled": "Genereren van afbeeldingen op ware grootte inschakelen", - "image_fullsize_enabled_description": "Genereer afbeelding op volledig formaat voor niet-webvriendelijke formaten. Als “Verkies ingesloten voorbeeldafbeelding” is ingeschakeld, worden ingesloten voorvertoningen direct gebruikt zonder conversie. Heeft geen invloed op webvriendelijke formaten zoals JPEG.", - "image_fullsize_quality_description": "Beeldkwaliteit op ware grootte van 1-100. Hoger is beter, maar genereert grotere bestanden.", - "image_fullsize_title": "Instellingen afbeelding op ware grootte", - "image_prefer_embedded_preview": "Voorkeur geven aan ingesloten voorbeeldafbeelding", - "image_prefer_embedded_preview_setting_description": "Gebruik ingesloten voorbeeldafbeelding van RAW-bestanden als invoer voor beeldverwerking wanneer beschikbaar. Dit kan preciezere kleuren produceren voor sommige afbeeldingen, maar de kwaliteit van het voorbeeld is afhankelijk van de camera en de afbeelding kan mogelijk meer compressie-artefacten bevatten.", - "image_prefer_wide_gamut": "Voorkeur geven aan wide gamut", - "image_prefer_wide_gamut_setting_description": "Display P3 gebruiken voor voorbeeldafbeeldingen. Dit behoudt de levendigheid van afbeeldingen met brede kleurruimtes beter, maar afbeeldingen kunnen er anders uitzien op oude apparaten met een oude browserversie. sRGB-afbeeldingen blijven sRGB gebruiken om kleurverschuivingen te vermijden.", - "image_preview_description": "Middelgrote afbeelding met verwijderde metadata, gebruikt bij het bekijken van een enkele item en voor machine learning", - "image_preview_quality_description": "Voorbeeldafbeelding kwaliteit van 1-100. Hoger is beter, maar produceert grotere bestanden en kan de app vertragen. Een lage waarde kan de kwaliteit van machine learning beïnvloeden.", - "image_preview_title": "Voorbeeldafbeelding instellingen", - "image_progressive": "Progressief", - "image_progressive_description": "Codeer JPEG-afbeeldingen progressief voor een geleidelijke weergave. Dit heeft geen effect op WebP-afbeeldingen.", - "image_quality": "Kwaliteit", - "image_resolution": "Resolutie", - "image_resolution_description": "Hogere resoluties behouden meer details, maar verhogen de coderingstijd, bestandsgrootte en kunnen de app vertragen.", - "image_settings": "Afbeeldingsinstellingen", - "image_settings_description": "Beheer de kwaliteit en resolutie van gegenereerde afbeeldingen", - "image_thumbnail_description": "Kleine thumbnail zonder metadata, gebruikt voor het bekijken van groepen met foto's zoals de tijdlijn", - "image_thumbnail_quality_description": "Thumbnail kwaliteit van 1-100. Hoger is beter, maar produceert grotere bestanden en kan de app vertragen.", - "image_thumbnail_title": "Thumbnailinstellingen", - "import_config_from_json_description": "Importeer de systeem­configuratie door een JSON-configuratie­bestand te uploaden", - "job_concurrency": "{job} gelijktijdigheid", - "job_created": "Taak aangemaakt", - "job_not_concurrency_safe": "Deze taak kan niet parallel worden uitgevoerd.", - "job_settings": "Achtergrondtaak-instellingen", - "job_settings_description": "Beheer aantal gelijktijdige taken", - "jobs_delayed": "{jobCount, plural, other {# vertraagd}}", - "jobs_failed": "{jobCount, plural, other {# mislukt}}", - "jobs_over_time": "Taken in de loop der tijd", - "library_created": "Bibliotheek aangemaakt: {library}", - "library_deleted": "Bibliotheek verwijderd", - "library_details": "Bibliotheek details", - "library_folder_description": "Kies een map om te importeren. Deze map, waaronder subfolders, zal gescand worden voor afbeeldingen en video's.", - "library_remove_exclusion_pattern_prompt": "Weet je zeker dat je dit uitsluitingspatroon wilt verwijderen?", - "library_remove_folder_prompt": "Weet je zeker dat je deze importeer map wilt verwijderen?", - "library_scanning": "Periodiek scannen", - "library_scanning_description": "Periodieke bibliotheekscan beheren", - "library_scanning_enable_description": "Periodieke bibliotheekscan aanzetten", - "library_settings": "Externe bibliotheek", - "library_settings_description": "Externe bibliotheekinstellingen beheren", - "library_tasks_description": "Scan externe bibliotheken op nieuwe en/of gewijzigde media", - "library_updated": "Bijgewerkte bibliotheek", - "library_watching_enable_description": "Externe bibliotheken monitoren op bestandswijzigingen", - "library_watching_settings": "Bibliotheek monitoren [EXPERIMENTEEL]", - "library_watching_settings_description": "Automatisch gewijzigde bestanden bijhouden", - "logging_enable_description": "Logboek inschakelen", - "logging_level_description": "Indien ingeschakeld, welk logniveau er wordt gebruikt.", - "logging_settings": "Logboek", - "machine_learning_availability_checks": "Beschikbaarheidscontroles", - "machine_learning_availability_checks_description": "Automatisch detecteren en selecteren van beschikbare machine learning servers", - "machine_learning_availability_checks_enabled": "Activeer beschikbaarheidscontroles", - "machine_learning_availability_checks_interval": "Controleinterval", - "machine_learning_availability_checks_interval_description": "Interval in milliseconden tussen beschikbaarheidscontroles", - "machine_learning_availability_checks_timeout": "Verzoek time-out", - "machine_learning_availability_checks_timeout_description": "Time-out in milliseconden voor beschikbaarheidscontroles", - "machine_learning_clip_model": "CLIP-model", - "machine_learning_clip_model_description": "De naam van een CLIP-model dat hier is vermeld. Let op: je moet de 'Slim Zoeken'-taak voor alle afbeeldingen opnieuw uitvoeren wanneer je een model wijzigt.", - "machine_learning_duplicate_detection": "Duplicaatdetectie", - "machine_learning_duplicate_detection_enabled": "Duplicaatdetectie inschakelen", - "machine_learning_duplicate_detection_enabled_description": "Indien uitgeschakeld, worden identieke items nog steeds gededupliceerd.", - "machine_learning_duplicate_detection_setting_description": "Gebruik CLIP-embeddings om mogelijke kopieën te vinden", - "machine_learning_enabled": "Machine learning inschakelen", - "machine_learning_enabled_description": "Indien uitgeschakeld, worden alle ML-instellingen uitgezet, ongeacht onderstaande instellingen.", - "machine_learning_facial_recognition": "Gezichtsherkenning", - "machine_learning_facial_recognition_description": "Detecteer, herken en groepeer gezichten in afbeeldingen", - "machine_learning_facial_recognition_model": "Model voor gezichtsherkenning", - "machine_learning_facial_recognition_model_description": "Modellen worden weergegeven in aflopende volgorde van grootte. Grotere modellen zijn langzamer en gebruiken meer geheugen, maar leveren betere resultaten op. Houd er rekening mee dat je de taak voor gezichtsherkenning opnieuw moet uitvoeren wanneer je een model wijzigt.", - "machine_learning_facial_recognition_setting": "Gezichtsherkenning inschakelen", - "machine_learning_facial_recognition_setting_description": "Indien uitgeschakeld, worden afbeeldingen niet verwerkt voor gezichtsherkenning en worden ze niet weergegeven in het gedeelte Mensen van de Verkennen pagina.", - "machine_learning_max_detection_distance": "Maximale detectieafstand", - "machine_learning_max_detection_distance_description": "Maximale afstand tussen twee afbeeldingen om ze als duplicaten te beschouwen, tussen 0.001-0.1. Hogere waardes detecteren meer duplicaten, maar kunnen ze ook vaker ten onrechte detecteren.", - "machine_learning_max_recognition_distance": "Maximale herkenningsafstand", - "machine_learning_max_recognition_distance_description": "Maximale afstand tussen twee gezichten om ze als dezelfde persoon te beschouwen, tussen 0-2. Door dit te verlagen kan worden voorkomen dat twee personen als dezelfde persoon worden bestempeld, terwijl door dit te verhogen kan worden voorkomen dat dezelfde persoon als twee verschillende personen wordt bestempeld. Houd er rekening mee dat twee personen samenvoegen eenvoudiger is dan een persoon in tweeën splitsen, dus kies indien mogelijk voor een lagere waarde.", - "machine_learning_min_detection_score": "Minimale detectiescore", - "machine_learning_min_detection_score_description": "Minimale betrouwbaarheidsscore voor het detecteren van een gezicht tussen 0-1. Lagere waarden detecteren meer gezichten, maar kunnen ze ook vaker ten onrechte detecteren.", - "machine_learning_min_recognized_faces": "Minimaal herkende gezichten", - "machine_learning_min_recognized_faces_description": "Het minimale aantal herkende gezichten voordat een persoon wordt aangemaakt. Door dit te verhogen wordt gezichtsherkenning nauwkeuriger, maar dit vergroot de kans dat een gezicht niet aan een persoon is toegewezen.", - "machine_learning_ocr": "OCR (tekstherkenning)", - "machine_learning_ocr_description": "Gebruik machine learning om tekst in afbeeldingen te herkennen", - "machine_learning_ocr_enabled": "OCR inschakelen", - "machine_learning_ocr_enabled_description": "Indien uitgeschakeld, worden afbeeldingen niet verwerkt voor tekstherkenning.", - "machine_learning_ocr_max_resolution": "Maximale resolutie", - "machine_learning_ocr_max_resolution_description": "Voorbeeldafbeeldingen met een hogere resolutie dan deze, worden verkleind met behoud van de beeldverhouding. Hogere resoluties resulteren in een hogere nauwkeurigheid, maar kosten meer tijd werkgeheugen om te verwerken.", - "machine_learning_ocr_min_detection_score": "Minimale detectiescore", - "machine_learning_ocr_min_detection_score_description": "Minimale betrouwbaarheidsscore om tekst te detecteren, tussen 0-1. Met een lagere grenswaarde wordt meer tekst gedetecteerd, maar dit kan ook leiden tot vals-positieven.", - "machine_learning_ocr_min_recognition_score": "Minimale herkenningsafstand", - "machine_learning_ocr_min_score_recognition_description": "Minimale betrouwbaarheidsscore om tekst te herkennen, tussen 0-1. Met een lagere grenswaarde wordt meer tekst herkend, maar dit kan ook leiden tot vals-positieven.", - "machine_learning_ocr_model": "OCR-model", - "machine_learning_ocr_model_description": "De 'server' modellen zijn nauwkeuriger dan de 'mobile' modellen, maar daarmee kost het meer tijd en werkgeheugen om afbeeldingen te verwerken.", - "machine_learning_settings": "Machine learning instellingen", - "machine_learning_settings_description": "Beheer machine learning functies en instellingen", - "machine_learning_smart_search": "Slim Zoeken", - "machine_learning_smart_search_description": "Semantisch zoeken naar afbeeldingen met CLIP-embeddings", - "machine_learning_smart_search_enabled": "Slim zoeken inschakelen", - "machine_learning_smart_search_enabled_description": "Indien uitgeschakeld, worden afbeeldingen niet verwerkt voor slim zoeken.", - "machine_learning_url_description": "De URL van de machine learning server. Als er meer dan één URL is opgegeven, wordt elke server geprobeerd totdat er een succesvol reageert, op volgorde van eerste tot laatste. Servers die geen reactie geven zullen tijdelijk genegeerd worden tot zij terug online komen.", - "maintenance_delete_backup": "Backup verwijderen", - "maintenance_delete_backup_description": "Dit bestand wordt onomkeerbaar verwijderd.", - "maintenance_delete_error": "Backup verwijderen mislukt.", - "maintenance_restore_backup": "Backup herstellen", - "maintenance_restore_backup_description": "Immich wordt gereset en hersteld vanaf de gekozen backup. Er wordt een backup gemaakt voor deze actie uitgevoerd wordt.", - "maintenance_restore_backup_different_version": "Deze backup is gemaakt met een andere versie van Immich!", - "maintenance_restore_backup_unknown_version": "Kan versie van backup niet bepalen.", - "maintenance_restore_database_backup": "Database backup terugzetten", - "maintenance_restore_database_backup_description": "Een eerdere versie van de database terugzetten door middel van een backup bestand", - "maintenance_settings": "Onderhoud", - "maintenance_settings_description": "Zet Immich in onderhouds­modus.", - "maintenance_start": "Onderhouds­modus activeren", - "maintenance_start_error": "Onderhouds­modus starten mislukt.", - "maintenance_upload_backup": "Upload database backup bestand", - "maintenance_upload_backup_error": "Kon backup niet uploaden, is het een .sql/.sql.gz bestand?", - "manage_concurrency": "Beheer gelijktijdigheid", - "manage_concurrency_description": "Navigeer naar de taken­pagina om de gelijk­tijdigheid van taken te beheren", - "manage_log_settings": "Beheer logboekinstellingen", - "map_dark_style": "Donkere stijl", - "map_enable_description": "Kaartfuncties inschakelen", - "map_gps_settings": "Kaart- & gps-instellingen", - "map_gps_settings_description": "Beheer kaart- & gps-instellingen (omgekeerde geocodering)", - "map_implications": "De kaartfunctie is afhankelijk van een externe service (tiles.immich.cloud)", - "map_light_style": "Lichte stijl", - "map_manage_reverse_geocoding_settings": "Beheer omgekeerde geocodering instellingen", - "map_reverse_geocoding": "Omgekeerde geocodering", - "map_reverse_geocoding_enable_description": "Omgekeerde geocodering inschakelen", - "map_reverse_geocoding_settings": "Instellingen voor omgekeerde geocodering", - "map_settings": "Kaart", - "map_settings_description": "Beheer kaartinstellingen", - "map_style_description": "URL naar een style.json kaartthema", - "memory_cleanup_job": "Herinneringen opschonen", - "memory_generate_job": "Herinneringen genereren", - "metadata_extraction_job": "Metadata ophalen", - "metadata_extraction_job_description": "Metadata ophalen van ieder item, zoals gps, gezichten en resolutie", - "metadata_faces_import_setting": "Gezichten importeren inschakelen", - "metadata_faces_import_setting_description": "Gezichten importeren uit EXIF-gegevens van afbeeldingen en sidecar bestanden", - "metadata_settings": "Metadata-instellingen", - "metadata_settings_description": "Beheer metadata-instellingen", - "migration_job": "Migratie", - "migration_job_description": "Migreer thumbnails voor items en gezichten naar de nieuwste mapstructuur", - "nightly_tasks_cluster_faces_setting_description": "Gezichtsherkenning uitvoeren op nieuw gedetecteerde gezichten", - "nightly_tasks_cluster_new_faces_setting": "Cluster nieuwe gezichten", - "nightly_tasks_database_cleanup_setting": "Database-opruimtaken", - "nightly_tasks_database_cleanup_setting_description": "Ruim oude, niet meer geldige data op uit de database", - "nightly_tasks_generate_memories_setting": "Genereer herinneringen", - "nightly_tasks_generate_memories_setting_description": "Maak nieuwe herinneringen van items", - "nightly_tasks_missing_thumbnails_setting": "Genereer ontbrekende thumbnails", - "nightly_tasks_missing_thumbnails_setting_description": "Items zonder thumbnail in een wachtrij plaatsen voor het genereren van thumbnails", - "nightly_tasks_settings": "Instellingen voor nachtelijke taken", - "nightly_tasks_settings_description": "Beheer nachtelijke taken", - "nightly_tasks_start_time_setting": "Starttijd", - "nightly_tasks_start_time_setting_description": "De tijd waarop de server begint met het uitvoeren van de nachtelijke taken", - "nightly_tasks_sync_quota_usage_setting": "Synchroniseer opslaglimieten", - "nightly_tasks_sync_quota_usage_setting_description": "Update opslaglimieten van gebruikers, gebaseerd op huidig gebruik", - "no_paths_added": "Geen paden toegevoegd", - "no_pattern_added": "Geen patroon toegevoegd", - "note_apply_storage_label_previous_assets": "Opmerking: om het opslaglabel toe te passen op eerder geüploade items, voer de volgende taak uit", - "note_cannot_be_changed_later": "LET OP: Dit kan later niet meer worden gewijzigd!", - "notification_email_from_address": "Adres afzender", - "notification_email_from_address_description": "E-mailadres van de afzender, bijvoorbeeld: \"Immich Foto Server \". Zorg ervoor dat je een adres gebruikt waar je e-mails van mag verzenden.", - "notification_email_host_description": "Host van de e-mailserver (bijv. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Negeer certificaatfouten", - "notification_email_ignore_certificate_errors_description": "Negeer validatiefouten van TLS-certificaat (niet aanbevolen)", - "notification_email_password_description": "Wachtwoord om te gebruiken bij authenticatie met de e-mailserver", - "notification_email_port_description": "Poort van de e-mailserver (bijv. 25, 465 of 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Gebruik SMTPS (SMTP via TLS)", - "notification_email_sent_test_email_button": "Verstuur testmail en opslaan", - "notification_email_setting_description": "Instellingen voor het verzenden van e-mailmeldingen", - "notification_email_test_email": "Verstuur testmail", - "notification_email_test_email_failed": "Verzenden van testmail is mislukt, controleer je instellingen", - "notification_email_test_email_sent": "Een testmail is verzonden naar {email}. Controleer je inbox.", - "notification_email_username_description": "Gebruikersnaam voor authenticatie met de e-mailserver", - "notification_enable_email_notifications": "E-mailmeldingen inschakelen", - "notification_settings": "Meldingsinstellingen", - "notification_settings_description": "Beheer meldingsinstellingen, inclusief e-mail", - "oauth_auto_launch": "Automatisch starten", - "oauth_auto_launch_description": "Automatisch inloggen met OAuth bij het navigeren naar de inlogpagina", - "oauth_auto_register": "Automatisch registreren", - "oauth_auto_register_description": "Nieuwe gebruikers automatisch registreren na inloggen met OAuth", - "oauth_button_text": "Knoptekst", - "oauth_client_secret_description": "Vereist voor een confidentiële client, of als PKCE (Proof Key for Code Exchange) niet wordt ondersteund door de publieke client.", - "oauth_enable_description": "Inloggen met OAuth", - "oauth_mobile_redirect_uri": "Omleidings-URI voor mobiel", - "oauth_mobile_redirect_uri_override": "Omleidings-URI voor mobiele app overschrijven", - "oauth_mobile_redirect_uri_override_description": "Inschakelen wanneer de OAuth-provider geen mobiele URI toestaat, zoals ''{callback}''", - "oauth_role_claim": "Rol claim", - "oauth_role_claim_description": "Automatisch admin toegang geven als deze claim aanwezig is. De claim kan 'user' of 'admin' zijn.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Beheer OAuth inloginstellingen", - "oauth_settings_more_details": "Raadpleeg de documentatie voor meer informatie over deze functie.", - "oauth_storage_label_claim": "Claim voor opslaglabel", - "oauth_storage_label_claim_description": "Stel het opslaglabel van de gebruiker automatisch in op de waarde van deze claim.", - "oauth_storage_quota_claim": "Claim voor opslaglimiet", - "oauth_storage_quota_claim_description": "Stel de opslaglimiet van de gebruiker automatisch in op de waarde van deze claim.", - "oauth_storage_quota_default": "Standaard opslaglimiet (GiB)", - "oauth_storage_quota_default_description": "Limiet in GiB die moet worden gebruikt als er geen claim is opgegeven.", - "oauth_timeout": "Aanvraag timeout", - "oauth_timeout_description": "Time-out voor aanvragen in milliseconden", - "ocr_job_description": "Gebruik machine learning om tekst in afbeeldingen te herkennen", - "password_enable_description": "Inloggen met e-mailadres en wachtwoord", - "password_settings": "Inloggen met wachtwoord", - "password_settings_description": "Beheer instellingen voor inloggen met wachtwoord", - "paths_validated_successfully": "Alle paden succesvol gevalideerd", - "person_cleanup_job": "Persoon opschoning", - "queue_details": "Wachtrij­details", - "queues": "Taken­wachtrijen", - "queues_page_description": "Admin takenlijst pagina", - "quota_size_gib": "Opslaglimiet (GiB)", - "refreshing_all_libraries": "Alle bibliotheken aan het vernieuwen", - "registration": "Beheerdersregistratie", - "registration_description": "Omdat je de eerste gebruiker in het systeem bent, word je toegewezen als beheerder en ben je verantwoordelijk voor administratieve taken. Extra gebruikers kunnen door jou worden aangemaakt.", - "remove_failed_jobs": "Verwijder mislukte taken", - "require_password_change_on_login": "Vereisen dat de gebruiker het wachtwoord wijzigt bij de eerste keer inloggen", - "reset_settings_to_default": "Instellingen teruggezet naar standaard", - "reset_settings_to_recent_saved": "Instellingen zijn gereset naar de recent opgeslagen instellingen", - "scanning_library": "Bibliotheek scannen", - "search_jobs": "Taak zoeken…", - "send_welcome_email": "Stuur een welkomstmail", - "server_external_domain_settings": "Extern domein", - "server_external_domain_settings_description": "Domein voor openbaar gedeelde links, inclusief http(s)://", - "server_public_users": "Openbare gebruikerslijst", - "server_public_users_description": "Alle gebruikers (met naam en e-mailadres) worden weergegeven wanneer een gebruiker wordt toegevoegd aan gedeelde albums. Wanneer uitgeschakeld, is de gebruikerslijst alleen beschikbaar voor beheerders.", - "server_settings": "Serverinstellingen", - "server_settings_description": "Beheer serverinstellingen", - "server_stats_page_description": "Serverstatistieken­pagina voor administrators", - "server_welcome_message": "Welkomstbericht", - "server_welcome_message_description": "Een bericht dat op de inlogpagina wordt weergegeven.", - "settings_page_description": "Instellingen­pagina voor administrators", - "sidecar_job": "Sidecar metagegevens", - "sidecar_job_description": "Zoek of synchroniseer sidecar metadata van het bestandssysteem", - "slideshow_duration_description": "Aantal seconden dat iedere afbeelding wordt getoond", - "smart_search_job_description": "Voer machine learning uit op items om te gebruiken voor slim zoeken", - "storage_template_date_time_description": "De aanmaakdatum van een item wordt gebruikt als datum", - "storage_template_date_time_sample": "Voorbeeldtijd {date}", - "storage_template_enable_description": "Engine voor opslagtemplate inschakelen", - "storage_template_hash_verification_enabled": "Hashverificatie ingeschakeld", - "storage_template_hash_verification_enabled_description": "Zet hashverificatie aan. Schakel dit niet uit tenzij je zeker bent van de gevolgen", - "storage_template_migration": "Opslagtemplate migratie", - "storage_template_migration_description": "Pas de huidige {template} toe op eerder geüploade items", - "storage_template_migration_info": "Wijzigingen in de opslagtemplate worden alleen toegepast op nieuwe items. Om de template met terugwerkende kracht toe te passen op eerder geüploade items, voer je de {job} uit.", - "storage_template_migration_job": "Opslagtemplate migratietaak", - "storage_template_more_details": "Meer details over deze functie vind je onder Opslagtemplate, net als de gevolgen daarvan", - "storage_template_onboarding_description_v2": "Wanneer ingeschakeld, zal deze functie bestanden automatisch organiseren gebaseerd op een template gedefinieerd door de gebruiker. Voor meer informatie, bekijk de documentatie.", - "storage_template_path_length": "Geschatte padlengte: {length, number}/{limit, number}", - "storage_template_settings": "Opslagtemplate", - "storage_template_settings_description": "Beheer de mapstructuur en bestandsnaam van geüploade bestanden", - "storage_template_user_label": "{label} is het opslaglabel van de gebruiker", - "system_settings": "Systeeminstellingen", - "tag_cleanup_job": "Tag opschoning", - "template_email_available_tags": "Je kan de volgende tags gebruiken in een template: {tags}", - "template_email_if_empty": "Wanneer het sjabloon leeg is, wordt de standaard mail gebruikt.", - "template_email_invite_album": "Uitgenodigd in album sjabloon", - "template_email_preview": "Voorbeeld", - "template_email_settings": "Emailsjablonen", - "template_email_update_album": "Update in album sjabloon", - "template_email_welcome": "Welkomstmail sjabloon", - "template_settings": "Melding sjablonen", - "template_settings_description": "Beheer aangepast sjablonen voor meldingen", - "theme_custom_css_settings": "Aangepaste CSS", - "theme_custom_css_settings_description": "Met Cascading Style Sheets kan het ontwerp van Immich worden aangepast.", - "theme_settings": "Thema-instellingen", - "theme_settings_description": "Beheer het uiterlijk van de Immich webinterface", - "thumbnail_generation_job": "Thumbnail genereren", - "thumbnail_generation_job_description": "Genereer grote, kleine en vervaagde thumbnails voor ieder item, en genereer thumbnails voor iedere persoon", - "transcoding_acceleration_api": "Versnelling API", - "transcoding_acceleration_api_description": "De API die met je apparaat zal communiceren om transcodering te versnellen. Deze instelling is 'best effort': wanneer fouten optreden wordt teruggevallen op softwaretranscodering. VP9 kan wel of niet werken, afhankelijk van je hardware.", - "transcoding_acceleration_nvenc": "NVENC (vereist NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (vereist 7e generatie Intel CPU of nieuwer)", - "transcoding_acceleration_rkmpp": "RKMPP (alleen op Rockchip SOC's)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Geaccepteerde audiocodecs", - "transcoding_accepted_audio_codecs_description": "Selecteer welke audiocodecs niet getranscodeerd hoeven te worden. Wordt alleen gebruikt bij bepaald transcoderingsbeleid.", - "transcoding_accepted_containers": "Geaccepteerde containers", - "transcoding_accepted_containers_description": "Selecteer welke containerformaten niet geremuxed hoeven te worden naar MP4. Wordt alleen gebruikt voor bepaald transcoderingsbeleid.", - "transcoding_accepted_video_codecs": "Geaccepteerde videocodecs", - "transcoding_accepted_video_codecs_description": "Selecteer welke videocodecs niet getranscodeerd hoeven te worden. Wordt alleen gebruikt bij bepaald transcoderingsbeleid.", - "transcoding_advanced_options_description": "Instellingen die de meeste gebruikers niet hoeven te veranderen", - "transcoding_audio_codec": "Audiocodec", - "transcoding_audio_codec_description": "Opus is de optie met de hoogste kwaliteit, maar biedt minder goede ondersteuning voor oude apparaten of software.", - "transcoding_bitrate_description": "Video's hoger dan de maximale bitrate of niet in het juiste formaat", - "transcoding_codecs_learn_more": "Om meer te leren over de terminologie die hier wordt gebruikt, bekijk de FFmpeg documentatie voor H.264 codec, HEVC codec en VP9 codec.", - "transcoding_constant_quality_mode": "Constante kwaliteit modus", - "transcoding_constant_quality_mode_description": "ICQ is beter dan CQP, maar sommige hardware versnellingsmethodes ondersteunen deze modus niet. Als u deze optie instelt, wordt de voorkeur gegeven aan de opgegeven modus bij gebruik van op kwaliteit gebaseerde encoding. Deze optie wordt genegeerd door NVENC omdat het ICQ niet ondersteunt.", - "transcoding_constant_rate_factor": "Constant tarief factor (-ctf)", - "transcoding_constant_rate_factor_description": "Niveau voor videokwaliteit. Typische waarden zijn 23 voor H.264, 28 voor HEVC, 31 voor VP9 en 35 voor AV1. Lager is beter, maar produceert grotere bestanden.", - "transcoding_disabled_description": "Transcodeer geen video's. Het afspelen kan op sommige clients niet meer werken", - "transcoding_encoding_options": "Coderings Opties", - "transcoding_encoding_options_description": "Stel codecs, resolutie, kwaliteit en andere opties in voor de gecodeerde video's", - "transcoding_hardware_acceleration": "Hardware acceleratie", - "transcoding_hardware_acceleration_description": "Experimenteel; snellere transcodering, maar kan kwaliteit verminderen bij dezelfde bitrate", - "transcoding_hardware_decoding": "Hardware decodering", - "transcoding_hardware_decoding_setting_description": "Maakt end-to-end versnelling mogelijk in plaats van alleen de codering te versnellen. Werkt mogelijk niet op alle video's.", - "transcoding_max_b_frames": "Maximaal aantal B-frames", - "transcoding_max_b_frames_description": "Hogere waarden verbeteren de compressie efficiëntie, maar vertragen de codering. Is mogelijk niet compatibel met hardwareversnelling op oudere apparaten. 0 schakelt B-frames uit, terwijl -1 deze waarde automatisch instelt.", - "transcoding_max_bitrate": "Maximale bitrate", - "transcoding_max_bitrate_description": "Het instellen van een maximale bitrate kan de bestandsgrootte voorspelbaarder maken, tegen geringe kosten voor de kwaliteit. Bij 720p zijn de typische waarden 2600 kbit/s voor VP9 of HEVC, of 4500 kbit/s voor H.264. Uitgeschakeld indien ingesteld op 0. Zonder eenheid wordt k (kbit/s) aangenomen; bitrates 5000, 5000k, en 5M (Mbit/s) komen dus op hetzelfde neer.", - "transcoding_max_keyframe_interval": "Maximale keyframe interval", - "transcoding_max_keyframe_interval_description": "Stelt de maximale frameafstand tussen keyframes in. Lagere waarden verslechteren de compressie efficiëntie, maar verbeteren de zoektijden en kunnen de kwaliteit verbeteren in scènes met snelle bewegingen. 0 stelt deze waarde automatisch in.", - "transcoding_optimal_description": "Video's met een hogere resolutie dan de doelresolutie of niet in een geaccepteerd formaat", - "transcoding_policy": "Transcode beleid", - "transcoding_policy_description": "Stel in wanneer een video wordt getranscodeerd", - "transcoding_preferred_hardware_device": "Voorkeur hardwareapparaat", - "transcoding_preferred_hardware_device_description": "Geldt alleen voor VAAPI en QSV. Stelt de dri node in die wordt gebruikt voor hardwaretranscodering.", - "transcoding_preset_preset": "Voorkeuze (-preset)", - "transcoding_preset_preset_description": "Compressiesnelheid. Langzamere presets produceren kleinere bestanden en verhogen de kwaliteit bij het targeten van een bepaalde bitrate. VP9 negeert snelheden boven 'faster'.", - "transcoding_reference_frames": "Referentie frames", - "transcoding_reference_frames_description": "Het aantal frames om naar te verwijzen bij het comprimeren van een bepaald frame. Hogere waarden verbeteren de compressie-efficiëntie, maar vertragen de codering. Bij 0 wordt deze waarde automatisch ingesteld.", - "transcoding_required_description": "Alleen video's die geen geaccepteerd formaat hebben", - "transcoding_settings": "Instellingen voor videotranscodering", - "transcoding_settings_description": "Beheer welke videos worden getranscodeerd en hoe ze worden verwerkt", - "transcoding_target_resolution": "Target resolutie", - "transcoding_target_resolution_description": "Hogere resoluties kunnen meer details behouden, maar het coderen ervan duurt langer, de bestandsgrootte is groter en de app reageert mogelijk minder snel.", - "transcoding_temporal_aq": "Tijdelijke AQ", - "transcoding_temporal_aq_description": "Alleen van toepassing op NVENC. Temporale Adaptieve Kwantisatie verhoogt de kwaliteit van scènes met veel details en weinig beweging. Is mogelijk niet compatibel met oudere apparaten.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Hogere waarden leiden tot snellere codering, maar laten minder ruimte over voor de server om andere taken te verwerken terwijl deze actief is. Deze waarde mag niet groter zijn dan het aantal CPU cores. Maximaliseert het gebruik als deze is ingesteld op 0.", - "transcoding_tone_mapping": "Tone mapping", - "transcoding_tone_mapping_description": "Probeert het uiterlijk van HDR-video's te behouden wanneer ze worden geconverteerd naar SDR. Elk algoritme maakt verschillende afwegingen voor kleur, detail en helderheid. Hable behoudt detail, Mobius behoudt kleur en Reinhard behoudt helderheid.", - "transcoding_transcode_policy": "Transcodeerbeleid", - "transcoding_transcode_policy_description": "Beleid voor wanneer een video getranscodeerd moet worden. HDR-video's worden altijd getranscodeerd (behalve als transcodering is uitgeschakeld).", - "transcoding_two_pass_encoding": "Two-pass encodering", - "transcoding_two_pass_encoding_setting_description": "Transcodeer in twee passes om beter gecodeerde video's te produceren. Wanneer de maximale bitrate is ingeschakeld (vereist om te werken met H.264 en HEVC), gebruikt deze modus een bitraterange op basis van de maximale bitrate en negeert CRF. Voor VP9 kan CRF worden gebruikt als de maximale bitrate is uitgeschakeld.", - "transcoding_video_codec": "Video Codec", - "transcoding_video_codec_description": "VP9 heeft een hoge efficiëntie en webcompatibiliteit, maar duurt langer om te transcoderen. HEVC presteert vergelijkbaar, maar heeft een lagere webcompatibiliteit. H.264 is breed compatibel en snel om te transcoderen, maar produceert veel grotere bestanden. AV1 is de meest efficiënte codec, maar mist ondersteuning op oudere apparaten.", - "trash_enabled_description": "Prullenbakfuncties inschakelen", - "trash_number_of_days": "Aantal dagen", - "trash_number_of_days_description": "Aantal dagen dat de items in de prullenbak worden bewaard voordat ze definitief worden verwijderd", - "trash_settings": "Prullenbak instellingen", - "trash_settings_description": "Beheer prullenbak instellingen", - "unlink_all_oauth_accounts": "Ontkoppel alle OAuth accounts", - "unlink_all_oauth_accounts_description": "Vergeet niet alle OAuth accounts te ontkoppelen voor te migreren naar een nieuwe provider.", - "unlink_all_oauth_accounts_prompt": "Ben je zeker dat je alle OAuth accounts wilt ontkoppelen? Dit zal het OAuth ID voor elke gebruiker resetten en kan niet ongedaan gemaakt worden.", - "user_cleanup_job": "Gebruiker opschoning", - "user_delete_delay": "Het account en de items van {user} worden over {delay, plural, one {# dag} other {# dagen}} permanent verwijderd.", - "user_delete_delay_settings": "Verwijder vertraging", - "user_delete_delay_settings_description": "Aantal dagen na verwijdering om het account en de items van een gebruiker permanent te verwijderen. De taak voor het verwijderen van gebruikers wordt om middernacht uitgevoerd om te controleren of gebruiker te verwijderen zijn. Wijzigingen in deze instelling worden bij de volgende uitvoering meegenomen.", - "user_delete_immediately": "Het account en de items van {user} worden onmiddellijk in de wachtrij geplaatst voor permanente verwijdering.", - "user_delete_immediately_checkbox": "Gebruikers en items in de wachtrij plaatsen voor onmiddellijke verwijdering", - "user_details": "Gebruiker details", - "user_management": "Gebruikersbeheer", - "user_password_has_been_reset": "Het wachtwoord van de gebruiker is gereset:", - "user_password_reset_description": "Geef het tijdelijke wachtwoord aan de gebruiker en informeer de gebruiker dat bij de volgende keer inloggen een wachtwoordwijziging vereist is.", - "user_restore_description": "Het account van {user} zal worden hersteld.", - "user_restore_scheduled_removal": "Herstel gebruiker - geplande verwijdering op {date, date, long}", - "user_settings": "Gebruikersinstellingen", - "user_settings_description": "Gebruikersinstellingen beheren", - "user_successfully_removed": "Gebruiker {email} is succesvol verwijderd.", - "users_page_description": "Gebruikers­pagina voor administrators", - "version_check_enabled_description": "Versiecontrole inschakelen", - "version_check_implications": "De versiecontrole is afhankelijk van periodieke communicatie met github.com", - "version_check_settings": "Versiecontrole", - "version_check_settings_description": "Melding voor een nieuwe versie in-/uitschakelen", - "video_conversion_job": "Transcodeer video's", - "video_conversion_job_description": "Transcodeer video's voor bredere compatibiliteit met browsers en apparaten" - }, - "admin_email": "Beheerder e-mailadres", - "admin_password": "Beheerder wachtwoord", - "administration": "Beheer", - "advanced": "Geavanceerd", - "advanced_settings_clear_image_cache": "Wis afbeeldingscache", - "advanced_settings_clear_image_cache_error": "Het wissen van de afbeeldingscache is mislukt", - "advanced_settings_clear_image_cache_success": "{size} succesvol gewist", - "advanced_settings_enable_alternate_media_filter_subtitle": "Gebruik deze optie om media te filteren tijdens de synchronisatie op basis van alternatieve criteria. Gebruik dit enkel als de app problemen heeft met het detecteren van albums.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTEEL] Gebruik een alternatieve album synchronisatie filter", - "advanced_settings_log_level_title": "Logniveau: {level}", - "advanced_settings_prefer_remote_subtitle": "Sommige apparaten zijn traag met het laden van lokale afbeeldingen. Activeer deze instelling om in plaats daarvan externe afbeeldingen te laden.", - "advanced_settings_prefer_remote_title": "Externe afbeeldingen laden", - "advanced_settings_proxy_headers_subtitle": "Definieer proxy headers die Immich bij elk netwerkverzoek moet verzenden", - "advanced_settings_proxy_headers_title": "Proxy Headers [EXPERIMENTEEL]", - "advanced_settings_readonly_mode_subtitle": "Schakelt de alleen-lezenmodus in, waarbij de foto's alleen bekeken kunnen worden. Dingen zoals het selecteren van meerdere afbeeldingen, delen, casten en verwijderen zijn allemaal uitgeschakeld. Schakel alleen-lezen in of uit via de gebruikers avatar vanaf het hoofdscherm", - "advanced_settings_readonly_mode_title": "Alleen-lezen mode", - "advanced_settings_self_signed_ssl_subtitle": "Slaat SSL-certificaatverificatie voor de connectie met de server over. Deze optie is vereist voor zelfondertekende certificaten.", - "advanced_settings_self_signed_ssl_title": "Zelfondertekende SSL-certificaten toestaan [EXPERIMENTEEL]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatisch bestanden verwijderen of herstellen op dit apparaat als die actie op het web is ondernomen", - "advanced_settings_sync_remote_deletions_title": "Synchroniseer verwijderingen op afstand [EXPERIMENTEEL]", - "advanced_settings_tile_subtitle": "Geavanceerde gebruikersinstellingen", - "advanced_settings_troubleshooting_subtitle": "Schakel extra functies voor probleemoplossing in", - "advanced_settings_troubleshooting_title": "Probleemoplossing", - "age_months": "Leeftijd {months, plural, one {# maand} other {# maanden}}", - "age_year_months": "Leeftijd 1 jaar, {months, plural, one {# maand} other {# maanden}}", - "age_years": "{years, plural, other {Leeftijd #}}", - "album": "Album", - "album_added": "Album toegevoegd", - "album_added_notification_setting_description": "Ontvang een e-mailmelding wanneer je aan een gedeeld album wordt toegevoegd", - "album_cover_updated": "Albumomslag is bijgewerkt", - "album_delete_confirmation": "Weet je zeker dat je het album {album} wilt verwijderen?", - "album_delete_confirmation_description": "Als dit album gedeeld is, zullen andere gebruikers geen toegang meer hebben.", - "album_deleted": "Album verwijderd", - "album_info_card_backup_album_excluded": "UITGESLOTEN", - "album_info_card_backup_album_included": "INBEGREPEN", - "album_info_updated": "Albumgegevens bijgewerkt", - "album_leave": "Album verlaten?", - "album_leave_confirmation": "Weet je zeker dat je {album} wilt verlaten?", - "album_name": "Albumnaam", - "album_options": "Albumopties", - "album_remove_user": "Gebruiker verwijderen?", - "album_remove_user_confirmation": "Weet je zeker dat je {user} wilt verwijderen?", - "album_search_not_found": "Geen albums gevonden die aan je zoekopdracht voldoen", - "album_selected": "Album geselecteerd", - "album_share_no_users": "Het lijkt erop dat je dit album met alle gebruikers hebt gedeeld, of dat je geen gebruikers hebt om mee te delen.", - "album_summary": "Album samenvatting", - "album_updated": "Album bijgewerkt", - "album_updated_setting_description": "Ontvang een e-mailmelding wanneer een gedeeld album nieuwe items heeft", - "album_upload_assets": "Items uploaden van je computer en aan album toevoegen", - "album_user_left": "{album} verlaten", - "album_user_removed": "{user} verwijderd", - "album_viewer_appbar_delete_confirm": "Weet je zeker dat je dit album uit je account wilt verwijderen?", - "album_viewer_appbar_share_err_delete": "Verwijderen album mislukt", - "album_viewer_appbar_share_err_leave": "Verlaten album mislukt", - "album_viewer_appbar_share_err_remove": "Er gaat iets mis bij het verwijderen van items uit het album", - "album_viewer_appbar_share_err_title": "Albumtitel wijzigen mislukt", - "album_viewer_appbar_share_leave": "Verlaat album", - "album_viewer_appbar_share_to": "Delen via", - "album_viewer_page_share_add_users": "Gebruikers toevoegen", - "album_with_link_access": "Iedereen met de link kan de foto's en mensen in dit album bekijken.", - "albums": "Albums", - "albums_count": "{count, plural, one {{count, number} album} other {{count, number} albums}}", - "albums_default_sort_order": "Standaard sorteervolgorde album", - "albums_default_sort_order_description": "Initiële sorteervolgorde bij het maken van nieuwe albums.", - "albums_feature_description": "Collectie van items die je kan delen met andere gebruikers.", - "albums_on_device_count": "Albums op apparaat ({count})", - "albums_selected": "{count, plural, one {# album geselecteerd} other {# albums geselecteerd}}", - "all": "Alle", - "all_albums": "Alle albums", - "all_people": "Alle mensen", - "all_photos": "Alle foto's", - "all_videos": "Alle video's", - "allow_dark_mode": "Donkere modus toestaan", - "allow_edits": "Bewerkingen toestaan", - "allow_public_user_to_download": "Sta openbare gebruiker toe om te downloaden", - "allow_public_user_to_upload": "Sta openbare gebruiker toe om te uploaden", - "allowed": "Toegestaan", - "alt_text_qr_code": "QR-codeafbeelding", - "always_keep": "Altijd bewaren", - "always_keep_photos_hint": "Met Free Up Space blijven alle foto's op dit apparaat bewaard.", - "always_keep_videos_hint": "Met Free Up Space worden alle video's op dit apparaat bewaard.", - "anti_clockwise": "Linksom", - "api_key": "API-sleutel", - "api_key_description": "Deze waarde wordt slechts één keer getoond. Zorg ervoor dat je deze kopieert voordat je het venster sluit.", - "api_key_empty": "De naam van uw API-sleutel mag niet leeg zijn", - "api_keys": "API-sleutels", - "app_architecture_variant": "Variant (architectuur)", - "app_bar_signout_dialog_content": "Weet je zeker dat je wilt uitloggen?", - "app_bar_signout_dialog_ok": "Ja", - "app_bar_signout_dialog_title": "Log uit", - "app_download_links": "Download-verwijzingen van de app", - "app_settings": "App instellingen", - "app_stores": "Appstore", - "app_update_available": "Een update van de applicatie is beschikbaar", - "appears_in": "Komt voor in", - "apply_count": "Toepassen ({count, number})", - "archive": "Archief", - "archive_action_prompt": "{count} item(s) toegevoegd aan het archief", - "archive_or_unarchive_photo": "Foto archiveren of uit het archief halen", - "archive_page_no_archived_assets": "Geen gearchiveerde items gevonden", - "archive_page_title": "Archief ({count})", - "archive_size": "Archiefgrootte", - "archive_size_description": "Configureer de archiefgrootte voor downloads (in GiB)", - "archived": "Gearchiveerd", - "archived_count": "{count, plural, other {# gearchiveerd}}", - "are_these_the_same_person": "Zijn dit dezelfde personen?", - "are_you_sure_to_do_this": "Weet je zeker dat je dit wilt doen?", - "array_field_not_fully_supported": "Array velden vereisen handmatige JSON bewerking", - "asset_action_delete_err_read_only": "Kan alleen-lezen item(s) niet verwijderen, overslaan", - "asset_action_share_err_offline": "Kan offline item(s) niet ophalen, overslaan", - "asset_added_to_album": "Toegevoegd aan album", - "asset_adding_to_album": "Toevoegen aan album…", - "asset_created": "Item aangemaakt", - "asset_description_updated": "Item beschrijving is bijgewerkt", - "asset_filename_is_offline": "Item {filename} is offline", - "asset_has_unassigned_faces": "Item heeft niet-toegewezen gezichten", - "asset_hashing": "Hashen…", - "asset_list_group_by_sub_title": "Groepeer op", - "asset_list_layout_settings_dynamic_layout_title": "Dynamische layout", - "asset_list_layout_settings_group_automatically": "Automatisch", - "asset_list_layout_settings_group_by": "Groepeer items per", - "asset_list_layout_settings_group_by_month_day": "Maand + dag", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Fotoraster layout instellingen", - "asset_list_settings_title": "Fotoraster", - "asset_not_found_on_device_android": "Item niet gevonden op apparaat", - "asset_not_found_on_device_ios": "Item niet gevonden op apparaat. Wanneer je iCloud gebruikt, kan het item niet toegankelijk zijn door een slecht bestand in iCloud", - "asset_not_found_on_icloud": "Item niet gevonden in iCloud. Het item kan ontoegankelijk zijn door een slecht bestand op iCloud", - "asset_offline": "Item offline", - "asset_offline_description": "Dit externe item is niet meer op de schijf te vinden. Neem contact op met de Immich beheerder voor hulp.", - "asset_restored_successfully": "Item succesvol hersteld", - "asset_skipped": "Overgeslagen", - "asset_skipped_in_trash": "In prullenbak", - "asset_trashed": "Asset verwijderd", - "asset_troubleshoot": "Asset probleemoplossing", - "asset_uploaded": "Geüpload", - "asset_uploading": "Uploaden…", - "asset_viewer_settings_subtitle": "Beheer je instellingen voor galerijweergave", - "asset_viewer_settings_title": "Fotoweergave", - "assets": "Items", - "assets_added_count": "{count, plural, one {# item} other {# items}} toegevoegd", - "assets_added_to_album_count": "{count, plural, one {# item} other {# items}} aan het album toegevoegd", - "assets_added_to_albums_count": "{assetTotal, plural, one {# asset} other {# assets}} toegevoegd aan {albumTotal, plural, one {# album} other {#albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {# item} other {# items}} konden niet aan album toegevoegd worden", - "assets_cannot_be_added_to_albums": "{count, plural, one {Middel kan} other {Middelen kunnen}} niet toegevoegd worden aan de albums", - "assets_count": "{count, plural, one {# item} other {# items}}", - "assets_deleted_permanently": "{count} item(s) permanent verwijderd", - "assets_deleted_permanently_from_server": "{count} item(s) permanent verwijderd van de Immich server", - "assets_downloaded_failed": "{count, plural, one {# bestand gedownload - {error} bestand mislukt} other {# bestanden gedownload - {error} bestanden mislukt}}", - "assets_downloaded_successfully": "{count, plural, one {# bestand succesvol gedownload} other {# bestanden succesvol gedownload}}", - "assets_moved_to_trash_count": "{count, plural, one {# item} other {# items}} verplaatst naar prullenbak", - "assets_permanently_deleted_count": "{count, plural, one {# item} other {# items}} permanent verwijderd", - "assets_removed_count": "{count, plural, one {# item} other {# items}} verwijderd", - "assets_removed_permanently_from_device": "{count} item(s) permanent verwijderd van je apparaat", - "assets_restore_confirmation": "Weet je zeker dat je alle verwijderde items wilt herstellen? Je kunt deze actie niet ongedaan maken! Offline items kunnen op deze manier niet worden hersteld.", - "assets_restored_count": "{count, plural, one {# item} other {# items}} hersteld", - "assets_restored_successfully": "{count} item(s) succesvol hersteld", - "assets_trashed": "{count} item(s) naar de prullenbak verplaatst", - "assets_trashed_count": "{count, plural, one {# item} other {# items}} naar prullenbak verplaatst", - "assets_trashed_from_server": "{count} item(s) naar de prullenbak verplaatst op de Immich server", - "assets_were_part_of_album_count": "{count, plural, one {Item was} other {Items waren}} al onderdeel van het album", - "assets_were_part_of_albums_count": "{count, plural, one {Middel is} other {Middelen zijn}} al onderdeel van de albums", - "authorized_devices": "Geautoriseerde apparaten", - "automatic_endpoint_switching_subtitle": "Maak indien beschikbaar lokaal verbinding via het aangewezen wifi-netwerk en gebruik elders alternatieve verbindingen", - "automatic_endpoint_switching_title": "Automatische serverwissel", - "autoplay_slideshow": "Diavoorstelling automatisch afspelen", - "back": "Terug", - "back_close_deselect": "Terug, sluiten of deselecteren", - "background_backup_running_error": "Back-up draait op de achtergrond, handmatige back-up kan niet worden gestart", - "background_location_permission": "Achtergrond locatie toestemming", - "background_location_permission_content": "Om van netwerk te wisselen terwijl de app op de achtergrond draait, heeft Immich *altijd* toegang tot de exacte locatie nodig om de naam van het WiFi-netwerk te kunnen lezen", - "background_options": "Achtergrond opties", - "backup": "Back-up", - "backup_album_selection_page_albums_device": "Albums op apparaat ({count})", - "backup_album_selection_page_albums_tap": "Tik om op te nemen, dubbel tik om uit te sluiten", - "backup_album_selection_page_assets_scatter": "Items kunnen over verschillende albums verdeeld zijn, dus albums kunnen inbegrepen of uitgesloten zijn van het back-up proces.", - "backup_album_selection_page_select_albums": "Selecteer albums", - "backup_album_selection_page_selection_info": "Selectie info", - "backup_album_selection_page_total_assets": "Totaal unieke items", - "backup_albums_sync": "Backup Albums Synchronisatie", - "backup_all": "Alle", - "backup_background_service_backup_failed_message": "Fout bij het back-uppen van de items. Opnieuw proberen…", - "backup_background_service_complete_notification": "Backup voltooid", - "backup_background_service_connection_failed_message": "Fout bij het verbinden met de server. Opnieuw proberen…", - "backup_background_service_current_upload_notification": "{filename} wordt geüpload", - "backup_background_service_default_notification": "Controleren op nieuwe items…", - "backup_background_service_error_title": "Back-up fout", - "backup_background_service_in_progress_notification": "Back-up van items maken…", - "backup_background_service_upload_failure_notification": "Fout bij het uploaden van {filename}", - "backup_controller_page_albums": "Back-up albums", - "backup_controller_page_background_app_refresh_disabled_content": "Schakel verversen op de achtergrond in via 'Instellingen > Algemeen > Ververs op achtergrond', om back-ups op de achtergrond te maken.", - "backup_controller_page_background_app_refresh_disabled_title": "Verversen op achtergrond is uitgeschakeld", - "backup_controller_page_background_app_refresh_enable_button_text": "Ga naar instellingen", - "backup_controller_page_background_battery_info_link": "Laat zien hoe", - "backup_controller_page_background_battery_info_message": "Voor de beste achtergrond back-up ervaring schakelt u alle batterij optimalisaties uit die de achtergrondactiviteit voor Immich kunnen beperken.\n\nAangezien dit apparaat specifiek is, raden we aan om de vereiste informatie op te zoeken bij de fabrikant van je apparaat.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Batterij optimalisaties", - "backup_controller_page_background_charging": "Alleen tijdens opladen", - "backup_controller_page_background_configure_error": "Achtergrondservice configuratie mislukt", - "backup_controller_page_background_delay": "Back-up vertraging voor nieuwe items: {duration}", - "backup_controller_page_background_description": "Schakel de achtergrondservice in om automatisch een back-up te maken van nieuwe items zonder de app te hoeven openen", - "backup_controller_page_background_is_off": "Automatische achtergrond back-up staat uit", - "backup_controller_page_background_is_on": "Automatische achtergrond back-up staat aan", - "backup_controller_page_background_turn_off": "Achtergrondservice uitzetten", - "backup_controller_page_background_turn_on": "Achtergrondservice aanzetten", - "backup_controller_page_background_wifi": "Alleen op WiFi", - "backup_controller_page_backup": "Back-up", - "backup_controller_page_backup_selected": "Geselecteerd: ", - "backup_controller_page_backup_sub": "Geback-upte foto's en video's", - "backup_controller_page_created": "Gemaakt op: {date}", - "backup_controller_page_desc_backup": "Schakel back-up op de voorgrond in om automatisch nieuwe items naar de server te uploaden bij het openen van de app.", - "backup_controller_page_excluded": "Uitgezonderd: ", - "backup_controller_page_failed": "Mislukt ({count})", - "backup_controller_page_filename": "Bestandsnaam: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Back-up informatie", - "backup_controller_page_none_selected": "Geen geselecteerd", - "backup_controller_page_remainder": "Resterend", - "backup_controller_page_remainder_sub": "Resterende foto's en video's om een back-up van te maken uit selectie", - "backup_controller_page_server_storage": "Serveropslag", - "backup_controller_page_start_backup": "Back-up uitvoeren", - "backup_controller_page_status_off": "Automatische back-up op de voorgrond staat uit", - "backup_controller_page_status_on": "Automatische back-up op de voorgrond staat aan", - "backup_controller_page_storage_format": "{used} van {total} gebruikt", - "backup_controller_page_to_backup": "Albums om een back-up van te maken", - "backup_controller_page_total_sub": "Alle unieke foto's en video's uit geselecteerde albums", - "backup_controller_page_turn_off": "Back-up op de voorgrond uitzetten", - "backup_controller_page_turn_on": "Back-up op de voorgrond aanzetten", - "backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden", - "backup_err_only_album": "Kan het enige album niet verwijderen", - "backup_error_sync_failed": "Synchronisatie mislukt. Kan back-up niet verwerken.", - "backup_info_card_assets": "bestanden", - "backup_manual_cancelled": "Geannuleerd", - "backup_manual_in_progress": "Het uploaden is al bezig. Probeer het na een tijdje", - "backup_manual_success": "Gelukt", - "backup_manual_title": "Uploadstatus", - "backup_options": "Backup opties", - "backup_options_page_title": "Back-up instellingen", - "backup_setting_subtitle": "Beheer achtergrond en voorgrond uploadinstellingen", - "backup_settings_subtitle": "Beheer upload instellingen", - "backup_upload_details_page_more_details": "Tik voor meer details", - "backward": "Achteruit", - "biometric_auth_enabled": "Biometrische authenticatie ingeschakeld", - "biometric_locked_out": "Biometrische authenticatie is vergrendeld", - "biometric_no_options": "Geen biometrische opties beschikbaar", - "biometric_not_available": "Biometrische authenticatie is niet beschikbaar op dit apparaat", - "birthdate_saved": "Geboortedatum succesvol opgeslagen", - "birthdate_set_description": "De geboortedatum wordt gebruikt om de leeftijd van deze persoon op het moment van de foto te berekenen.", - "blurred_background": "Vervaagde achtergrond", - "bugs_and_feature_requests": "Bugs & functieverzoeken", - "build": "Build", - "build_image": "Build image", - "bulk_delete_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# dubbel item} other {# dubbele items}} in bulk wilt verwijderen? Dit zal de grootste item van elke groep behouden en alle andere duplicaten permanent verwijderen. Je kunt deze actie niet ongedaan maken!", - "bulk_keep_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# dubbel item} other {# dubbele items}} wilt behouden? Dit zal alle groepen met duplicaten oplossen zonder iets te verwijderen.", - "bulk_trash_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# dubbel item} other {# dubbele items}} in bulk naar de prullenbak wilt verplaatsen? Dit zal de grootste item van elke groep behouden en alle andere duplicaten naar de prullenbak verplaatsen.", - "buy": "Immich kopen", - "cache_settings_clear_cache_button": "Cache wissen", - "cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.", - "cache_settings_duplicated_assets_clear_button": "MAAK VRIJ", - "cache_settings_duplicated_assets_subtitle": "Foto’s en video's die de app negeert", - "cache_settings_duplicated_assets_title": "Gedupliceerde items ({count})", - "cache_settings_statistics_album": "Bibliotheekthumbnails", - "cache_settings_statistics_full": "Volledige afbeeldingen", - "cache_settings_statistics_shared": "Gedeeld-albumthumbnails", - "cache_settings_statistics_thumbnail": "Minaturen", - "cache_settings_statistics_title": "Cachegebruik", - "cache_settings_subtitle": "Beheer het cachegedrag van de Immich app", - "cache_settings_tile_subtitle": "Beheer het gedrag van lokale opslag", - "cache_settings_tile_title": "Lokale opslag", - "cache_settings_title": "Cache-instellingen", - "camera": "Camera", - "camera_brand": "Cameramerk", - "camera_model": "Cameramodel", - "cancel": "Annuleren", - "cancel_search": "Zoeken annuleren", - "canceled": "Geannuleerd", - "canceling": "Annuleren", - "cannot_merge_people": "Kan mensen niet samenvoegen", - "cannot_undo_this_action": "Je kunt deze actie niet ongedaan maken!", - "cannot_update_the_description": "Kan de beschrijving niet bijwerken", - "cast": "Cast", - "cast_description": "Configureer beschikbare cast bestemmingen", - "change_date": "Wijzig datum", - "change_description": "Wijzig beschrijving", - "change_display_order": "Weergavevolgorde wijzigen", - "change_expiration_time": "Verlooptijd wijzigen", - "change_location": "Wijzig locatie", - "change_name": "Naam wijzigen", - "change_name_successfully": "Naam succesvol gewijzigd", - "change_password": "Wijzig wachtwoord", - "change_password_description": "Dit is de eerste keer dat je inlogt op het systeem of er is een verzoek gedaan om je wachtwoord te wijzigen. Voer hieronder het nieuwe wachtwoord in.", - "change_password_form_confirm_password": "Bevestig wachtwoord", - "change_password_form_description": "Hallo {name},\n\nDit is ofwel de eerste keer dat je inlogt, of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.", - "change_password_form_log_out": "Uitloggen op alle andere apparaten", - "change_password_form_log_out_description": "Het is verstandig om op alle andere apparaten uit te loggen", - "change_password_form_new_password": "Nieuw wachtwoord", - "change_password_form_password_mismatch": "Wachtwoorden komen niet overeen", - "change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in", - "change_pin_code": "Wijzig pincode", - "change_trigger": "Wijzig trigger", - "change_trigger_prompt": "Weet u zeker dat u deze trigger wilt wijzigen? Dit verwijdert alle bestaande acties en filters.", - "change_your_password": "Wijzig je wachtwoord", - "changed_visibility_successfully": "Zichtbaarheid succesvol gewijzigd", - "charging": "Opladen", - "charging_requirement_mobile_backup": "Achtergrond backup vereist dat het apparaat wordt opgeladen", - "check_corrupt_asset_backup": "Controleer op corrupte back-ups van items", - "check_corrupt_asset_backup_button": "Controle uitvoeren", - "check_corrupt_asset_backup_description": "Voer deze controle alleen uit via WiFi en nadat alle items zijn geback-upt. De procedure kan een paar minuten duren.", - "check_logs": "Controleer logboek", - "checksum": "Controlegetal", - "choose_matching_people_to_merge": "Kies overeenkomende mensen om samen te voegen", - "city": "Stad", - "cleanup_confirm_description": "Immich heeft {count} items (gemaakt voor {date}) opgeslagen op de server. Lokale kopieën van dit apparaat verwijderen?", - "cleanup_confirm_prompt_title": "Van dit apparaat verwijderen?", - "cleanup_deleted_assets": "{count} items verplaats naar prullenbak van apparaat", - "cleanup_deleting": "Naar prullenbak verplaatsen...", - "cleanup_found_assets": "Er zijn {count} backup bestanden gevonden", - "cleanup_found_assets_with_size": "Er zijn {count} back-upbestanden gevonden ({size})", - "cleanup_icloud_shared_albums_excluded": "Gedeelde albums van iCloud zijn uitgesloten van de scan", - "cleanup_no_assets_found": "Er zijn geen bestanden gevonden die aan bovenstaande criteria voldoen. Free Up Space kan alleen bestanden verwijderen die op de server zijn geback-upt", - "cleanup_preview_title": "Bestanden te verwijderen ({count})", - "cleanup_step3_description": "Scan naar back-upbestanden die overeenkomen met uw datum en behoud uw instellingen.", - "cleanup_step4_summary": "{count} bestanden (gemaakt vóór {date}) die van uw lokale apparaat moeten worden verwijderd. Foto's blijven toegankelijk via de Immich-app.", - "cleanup_trash_hint": "Om de opslagruimte volledig vrij te maken, opent u de systeemgalerij-app en leegt u de prullenbak", - "clear": "Wissen", - "clear_all": "Alles wissen", - "clear_all_recent_searches": "Wis alle recente zoekopdrachten", - "clear_file_cache": "Bestandcache leegmaken", - "clear_message": "Bericht wissen", - "clear_value": "Waarde wissen", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Voer wachtwoord in", - "client_cert_import": "Importeren", - "client_cert_import_success_msg": "Cliëntcertificaat is geïmporteerd", - "client_cert_invalid_msg": "Ongeldig certificaatbestand of verkeerd wachtwoord", - "client_cert_remove_msg": "Clientcertificaat is verwijderd", - "client_cert_subtitle": "Ondersteunt alleen PKCS12-formaat (.p12, .pfx). Het importeren/verwijderen van certificaten is alleen beschikbaar vóór het inloggen", - "client_cert_title": "SSL clientcertificaat [EXPERIMENTEEL]", - "clockwise": "Rechtsom", - "close": "Sluiten", - "collapse": "Inklappen", - "collapse_all": "Alles inklappen", - "color": "Kleur", - "color_theme": "Kleurenthema", - "command": "Opdracht", - "comment_deleted": "Opmerking verwijderd", - "comment_options": "Opties voor opmerkingen", - "comments_and_likes": "Opmerkingen & likes", - "comments_are_disabled": "Opmerkingen zijn uitgeschakeld", - "common_create_new_album": "Nieuw album maken", - "completed": "Voltooid", - "confirm": "Bevestigen", - "confirm_admin_password": "Bevestig beheerder wachtwoord", - "confirm_delete_face": "Weet je zeker dat je het gezicht van {name} wilt verwijderen uit het item?", - "confirm_delete_shared_link": "Weet je zeker dat je deze gedeelde link wilt verwijderen?", - "confirm_keep_this_delete_others": "Alle andere items in de stack worden verwijderd, behalve deze. Weet je zeker dat je wilt doorgaan?", - "confirm_new_pin_code": "Bevestig nieuwe pincode", - "confirm_password": "Bevestig wachtwoord", - "confirm_tag_face": "Wil je dit gezicht taggen als {name}?", - "confirm_tag_face_unnamed": "Wil je dit gezicht taggen?", - "connected_device": "Verbonden apparaat", - "connected_to": "Verbonden met", - "contain": "Bevat", - "context": "Context", - "continue": "Doorgaan", - "control_bottom_app_bar_create_new_album": "Nieuw album maken", - "control_bottom_app_bar_delete_from_immich": "Verwijderen van Immich", - "control_bottom_app_bar_delete_from_local": "Verwijderen van apparaat", - "control_bottom_app_bar_edit_location": "Locatie bewerken", - "control_bottom_app_bar_edit_time": "Datum & tijd bewerken", - "control_bottom_app_bar_share_link": "Link delen", - "control_bottom_app_bar_share_to": "Delen met", - "control_bottom_app_bar_trash_from_immich": "Verwijderen van Immich", - "copied_image_to_clipboard": "Afbeelding gekopieerd naar klembord.", - "copied_to_clipboard": "Gekopieerd naar klembord!", - "copy_error": "Fout bij kopiëren", - "copy_file_path": "Kopieer bestandspad", - "copy_image": "Kopieer afbeelding", - "copy_link": "Kopieer link", - "copy_link_to_clipboard": "Kopieer link naar klembord", - "copy_password": "Kopieer wachtwoord", - "copy_to_clipboard": "Kopiëren naar klembord", - "country": "Land", - "cover": "Bedekken", - "covers": "Omslagen", - "create": "Aanmaken", - "create_album": "Album aanmaken", - "create_album_page_untitled": "Naamloos", - "create_api_key": "API-sleutel maken", - "create_first_workflow": "Maak eerste werkstroom", - "create_library": "Bibliotheek maken", - "create_link": "Link maken", - "create_link_to_share": "Gedeelde link maken", - "create_link_to_share_description": "Laat iedereen met de link de geselecteerde foto(s) zien", - "create_new": "MAAK NIEUW", - "create_new_person": "Nieuwe persoon aanmaken", - "create_new_person_hint": "Geselecteerde items toewijzen aan een nieuwe persoon", - "create_new_user": "Nieuwe gebruiker aanmaken", - "create_shared_album_page_share_add_assets": "ITEMS TOEVOEGEN", - "create_shared_album_page_share_select_photos": "Selecteer foto's", - "create_shared_link": "Gedeelde link maken", - "create_tag": "Tag aanmaken", - "create_tag_description": "Maak een nieuwe tag. Voor geneste tags, voer het volledige pad van de tag in, inclusief schuine strepen.", - "create_user": "Gebruiker aanmaken", - "create_workflow": "Maak werkstroom", - "created": "Aangemaakt", - "created_at": "Aangemaakt", - "creating_linked_albums": "Gekoppelde albums worden aangemaakt...", - "crop": "Bijsnijden", - "crop_aspect_ratio_fixed": "Vast", - "crop_aspect_ratio_free": "Vrij", - "crop_aspect_ratio_original": "Origineel", - "curated_object_page_title": "Dingen", - "current_device": "Huidig apparaat", - "current_pin_code": "Huidige pincode", - "current_server_address": "Huidig serveradres", - "custom_date": "Aangepaste datum", - "custom_locale": "Aangepaste landinstelling", - "custom_locale_description": "Formatteer datums en getallen op basis van de taal en de regio", - "custom_url": "Aangepaste URL", - "cutoff_date_description": "Bewaar foto's van de laatste…", - "cutoff_day": "{count, plural, one {dag} other {dagen}}", - "cutoff_year": "{count, plural, one {jaar} other {jaar}}", - "daily_title_text_date": "E dd MMM", - "daily_title_text_date_year": "E dd MMM yyyy", - "dark": "Donker", - "dark_theme": "Donker thema in- of uitschakelen", - "date": "Datum", - "date_after": "Datum na", - "date_and_time": "Datum en tijd", - "date_before": "Datum voor", - "date_format": "E d LLL y • H:mm", - "date_of_birth_saved": "Geboortedatum succesvol opgeslagen", - "date_range": "Datumbereik", - "day": "Dag", - "days": "Dagen", - "deduplicate_all": "Alles dedupliceren", - "deduplication_criteria_1": "Grootte van afbeelding in bytes", - "deduplication_criteria_2": "Aantal EXIF data", - "deduplication_info": "Deduplicatie-info", - "deduplication_info_description": "Om automatisch items te preselecteren en duplicaten te verwijderen in bulk, kijken we naar:", - "default_locale": "Standaard landinstelling", - "default_locale_description": "Formatteer datums en getallen op basis van de landinstellingen van je browser", - "delete": "Verwijderen", - "delete_action_confirmation_message": "Weet je zeker dat je dit item wilt verwijderen? Deze actie zorgt ervoor dat het item naar de prullenbak van de server wordt verplaatst en je wordt gevraagd of je deze ook lokaal wilt verwijderen", - "delete_action_prompt": "{count} item(s) verwijderd", - "delete_album": "Album verwijderen", - "delete_api_key_prompt": "Weet je zeker dat je deze API-sleutel wilt verwijderen?", - "delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat", - "delete_dialog_alert_local": "Deze items worden permanent verwijderd van je apparaat, maar blijven beschikbaar op de Immich server", - "delete_dialog_alert_local_non_backed_up": "Van sommige items is geen back-up gemaakt in Immich en zullen permanent van je apparaat worden verwijderd", - "delete_dialog_alert_remote": "Deze items worden permanent verwijderd van de Immich server", - "delete_dialog_ok_force": "Toch verwijderen", - "delete_dialog_title": "Permanent verwijderen", - "delete_duplicates_confirmation": "Weet je zeker dat je deze duplicaten permanent wilt verwijderen?", - "delete_face": "Gezicht verwijderen", - "delete_key": "Verwijder key", - "delete_library": "Verwijder bibliotheek", - "delete_link": "Verwijder link", - "delete_local_action_prompt": "{count} item(s) lokaal verwijderd", - "delete_local_dialog_ok_backed_up_only": "Verwijder alleen met back-up", - "delete_local_dialog_ok_force": "Toch verwijderen", - "delete_others": "Andere verwijderen", - "delete_permanently": "Permanent verwijderen", - "delete_permanently_action_prompt": "{count} item(s) permanent verwijderd", - "delete_shared_link": "Verwijder gedeelde link", - "delete_shared_link_dialog_title": "Verwijder gedeelde link", - "delete_tag": "Tag verwijderen", - "delete_tag_confirmation_prompt": "Weet je zeker dat je de tag {tagName} wilt verwijderen?", - "delete_user": "Verwijder gebruiker", - "deleted_shared_link": "Gedeelde link verwijderd", - "deletes_missing_assets": "Verwijdert items die ontbreken op de schijf", - "description": "Beschrijving", - "description_input_hint_text": "Beschrijving toevoegen...", - "description_input_submit_error": "Beschrijving bijwerken mislukt, controleer het logboek voor meer details", - "deselect_all": "Alles deselecteren", - "details": "Details", - "direction": "Richting", - "disable": "Uitschakelen", - "disabled": "Uitgeschakeld", - "disallow_edits": "Geen bewerkingen toestaan", - "discord": "Discord", - "discover": "Zoek", - "discovered_devices": "Gevonden apparaten", - "dismiss_all_errors": "Negeer alle fouten", - "dismiss_error": "Negeer fout", - "display_options": "Weergaveopties", - "display_order": "Weergavevolgorde", - "display_original_photos": "Toon originele foto's", - "display_original_photos_setting_description": "Geef de voorkeur aan het weergeven van de originele foto bij het bekijken van een item in plaats van thumbnails wanneer het originele item compatibel is. Dit kan resulteren in een lagere weergavesnelheid van foto's.", - "do_not_show_again": "Laat dit bericht niet meer zien", - "documentation": "Documentatie", - "done": "Klaar", - "download": "Downloaden", - "download_action_prompt": "{count} item(s) aan het downloaden", - "download_canceled": "Download geannuleerd", - "download_complete": "Download voltooid", - "download_enqueue": "Download in wachtrij", - "download_error": "Fout bij downloaden", - "download_failed": "Download mislukt", - "download_finished": "Download voltooid", - "download_include_embedded_motion_videos": "Ingesloten video's", - "download_include_embedded_motion_videos_description": "Voeg video's die in bewegingsfoto's zijn ingebed toe als een apart bestand", - "download_notfound": "Download niet gevonden", - "download_original": "Download origineel", - "download_paused": "Download gepauseerd", - "download_settings": "Downloaden", - "download_settings_description": "Beheer instellingen voor het downloaden van items", - "download_started": "Download gestart", - "download_sucess": "Succesvol gedownload", - "download_sucess_android": "Het bestand is gedownload naar DCIM/Immich", - "download_waiting_to_retry": "Wachten om opnieuw te proberen", - "downloading": "Downloaden", - "downloading_asset_filename": "Downloaden asset {filename}", - "downloading_from_icloud": "Media aan het downloaden van iCloud", - "downloading_media": "Media aan het downloaden", - "drop_files_to_upload": "Zet bestanden ergens neer om ze te uploaden", - "duplicates": "Duplicaten", - "duplicates_description": "Kies voor iedere groep welke, indien aanwezig, duplicaten zijn", - "duration": "Tijdsduur", - "edit": "Bewerken", - "edit_album": "Album bewerken", - "edit_avatar": "Avatar bewerken", - "edit_birthday": "Wijzig verjaardag", - "edit_date": "Datum bewerken", - "edit_date_and_time": "Datum en tijd bewerken", - "edit_date_and_time_action_prompt": "Datum en tijd bijgewerkt van {count} item(s)", - "edit_date_and_time_by_offset": "Wijzigen datum door verschuiving", - "edit_date_and_time_by_offset_interval": "Nieuw datuminterval: {from}-{to}", - "edit_description": "Beschrijving bewerken", - "edit_description_prompt": "Selecteer een nieuwe beschrijving:", - "edit_exclusion_pattern": "Uitsluitingspatroon bewerken", - "edit_faces": "Gezichten bewerken", - "edit_key": "Key bewerken", - "edit_link": "Link bewerken", - "edit_location": "Locatie bewerken", - "edit_location_action_prompt": "Locatie bijgewerkt van {count} item(s)", - "edit_location_dialog_title": "Locatie", - "edit_name": "Naam bewerken", - "edit_people": "Mensen bewerken", - "edit_tag": "Tag bewerken", - "edit_title": "Titel bewerken", - "edit_user": "Gebruiker bewerken", - "edit_workflow": "Werkstroom bewerken", - "editor": "Bewerker", - "editor_close_without_save_prompt": "De wijzigingen worden niet opgeslagen", - "editor_close_without_save_title": "Editor sluiten?", - "editor_confirm_reset_all_changes": "Weet u zeker dat u alle wijzigingen wilt resetten?", - "editor_flip_horizontal": "Horizontaal spiegelen", - "editor_flip_vertical": "Verticaal spiegelen", - "editor_orientation": "Oriëntatie", - "editor_reset_all_changes": "Reset wijzigingen", - "editor_rotate_left": "Draai 90° tegen de klok in", - "editor_rotate_right": "Draai 90° met de klok mee", - "email": "E-mailadres", - "email_notifications": "E-mailmeldingen", - "empty_folder": "Deze map is leeg", - "empty_trash": "Prullenbak leegmaken", - "empty_trash_confirmation": "Weet je zeker dat je de prullenbak wilt legen? Hiermee worden alle items in de prullenbak permanent uit Immich verwijderd.\nJe kunt deze actie niet ongedaan maken!", - "enable": "Inschakelen", - "enable_backup": "Back-up aanzetten", - "enable_biometric_auth_description": "Voer uw pincode in om biometrische authenticatie in te schakelen", - "enabled": "Ingeschakeld", - "end_date": "Einddatum", - "enqueued": "In de wachtrij", - "enter_wifi_name": "Voer de WiFi-naam in", - "enter_your_pin_code": "Voer uw pincode in", - "enter_your_pin_code_subtitle": "Voer uw pincode in om toegang te krijgen tot de vergrendelde map", - "error": "Fout", - "error_change_sort_album": "Sorteervolgorde van album wijzigen mislukt", - "error_delete_face": "Fout bij verwijderen van gezicht uit het item", - "error_getting_places": "Fout bij ophalen plaatsen", - "error_loading_albums": "Fout bij het laden van albums", - "error_loading_image": "Fout bij laden afbeelding", - "error_loading_partners": "Fout bij ophalen partners: {error}", - "error_retrieving_asset_information": "Fout bij ophalen item informatie", - "error_saving_image": "Fout: {error}", - "error_tag_face_bounding_box": "Fout bij taggen van gezicht - kan coördinaten van omvattend kader niet ophalen", - "error_title": "Fout - Er is iets misgegaan", - "error_while_navigating": "Fout bij navigeren naar item", - "errors": { - "cannot_navigate_next_asset": "Kan niet naar het volgende item navigeren", - "cannot_navigate_previous_asset": "Kan niet naar het vorige item navigeren", - "cant_apply_changes": "Kan wijzigingen niet toepassen", - "cant_change_activity": "Kan activiteit niet {enabled, select, true {uitschakelen} other {inschakelen}}", - "cant_change_asset_favorite": "Kan item niet toevoegen aan of verwijderen uit favorieten", - "cant_change_metadata_assets_count": "Kan metadata van {count, plural, one {# item} other {# items}} niet wijzigen", - "cant_get_faces": "Kan gezichten niet ophalen", - "cant_get_number_of_comments": "Kan het aantal opmerkingen niet ophalen", - "cant_search_people": "Kan mensen niet zoeken", - "cant_search_places": "Kan plaatsen niet zoeken", - "error_adding_assets_to_album": "Fout bij toevoegen van items aan album", - "error_adding_users_to_album": "Fout bij toevoegen van gebruikers aan album", - "error_deleting_shared_user": "Fout bij verwijderen van gedeelde gebruiker", - "error_downloading": "Fout bij downloaden {filename}", - "error_hiding_buy_button": "Fout bij het verbergen van de koop knop", - "error_removing_assets_from_album": "Fout bij het verwijderen van items uit het album, controleer de console voor meer details", - "error_selecting_all_assets": "Fout bij selecteren van alle items", - "exclusion_pattern_already_exists": "Dit uitsluitingspatroon bestaat al.", - "failed_to_create_album": "Fout bij maken van album", - "failed_to_create_shared_link": "Fout bij maken van gedeelde link", - "failed_to_edit_shared_link": "Fout bij bewerken van gedeelde link", - "failed_to_get_people": "Fout bij ophalen van mensen", - "failed_to_keep_this_delete_others": "Het is niet gelukt om dit item te behouden en de andere items te verwijderen", - "failed_to_load_asset": "Kan item niet laden", - "failed_to_load_assets": "Kan items niet laden", - "failed_to_load_notifications": "Kon meldingen niet laden", - "failed_to_load_people": "Kan mensen niet laden", - "failed_to_remove_product_key": "Fout bij het verwijderen van de licentiesleutel", - "failed_to_reset_pin_code": "Resetten van pincode mislukt", - "failed_to_stack_assets": "Fout bij stapelen van items", - "failed_to_unstack_assets": "Fout bij ontstapelen van items", - "failed_to_update_notification_status": "Kon notificatiestatus niet updaten", - "incorrect_email_or_password": "Onjuist e-mailadres of wachtwoord", - "library_folder_already_exists": "Dit import­pad bestaat al.", - "paths_validation_failed": "validatie van {paths, plural, one {# pad} other {# paden}} mislukt", - "profile_picture_transparent_pixels": "Profielfoto's kunnen geen transparante pixels bevatten. Zoom in en/of verplaats de afbeelding.", - "quota_higher_than_disk_size": "Je hebt een opslaglimiet ingesteld die hoger is dan de schijfgrootte", - "something_went_wrong": "Er liep iets mis", - "unable_to_add_album_users": "Kan gebruikers niet aan album toevoegen", - "unable_to_add_assets_to_shared_link": "Kan items niet aan gedeelde link toevoegen", - "unable_to_add_comment": "Kan geen opmerking toevoegen", - "unable_to_add_exclusion_pattern": "Kan geen uitsluitingspatroon toevoegen", - "unable_to_add_partners": "Kan geen partners toevoegen", - "unable_to_add_remove_archive": "Kan items niet {archived, select, true {verwijderen uit} other {toevoegen aan}} archief", - "unable_to_add_remove_favorites": "Kan items niet {favorite, select, true {toevoegen aan} other {verwijderen uit}} favorieten", - "unable_to_archive_unarchive": "Kan niet {archived, select, true {toevoegen aan} other {verwijderen uit}} archief", - "unable_to_change_album_user_role": "Kan rol van de albumgebruiker niet wijzigen", - "unable_to_change_date": "Kan datum niet wijzigen", - "unable_to_change_description": "Beschrijving kan niet worden gewijzigd", - "unable_to_change_favorite": "Kan item niet toevoegen aan of verwijderen uit favorieten", - "unable_to_change_location": "Kan locatie niet wijzigen", - "unable_to_change_password": "Kan wachtwoord niet veranderen", - "unable_to_change_visibility": "Kan de zichtbaarheid van {count, plural, one {# persoon} other {# mensen}} niet wijzigen", - "unable_to_complete_oauth_login": "Kan inloggen met OAuth niet voltooie", - "unable_to_connect": "Kan niet verbinden", - "unable_to_copy_to_clipboard": "Kan niet naar klembord kopiëren, zorg ervoor dat je de pagina via https opent", - "unable_to_create": "Kan werkstroom niet aanmaken", - "unable_to_create_admin_account": "Kan beheerdersaccount niet aanmaken", - "unable_to_create_api_key": "Kan geen nieuwe API-sleutel aanmaken", - "unable_to_create_library": "Kan bibliotheek niet aanmaken", - "unable_to_create_user": "Kan geen gebruiker aanmaken", - "unable_to_delete_album": "Kan album niet verwijderen", - "unable_to_delete_asset": "Kan item niet verwijderen", - "unable_to_delete_assets": "Fout bij verwijderen items", - "unable_to_delete_exclusion_pattern": "Kan uitsluitingspatroon niet verwijderen", - "unable_to_delete_shared_link": "Kan gedeelde link niet verwijderen", - "unable_to_delete_user": "Kan gebruiker niet verwijderen", - "unable_to_delete_workflow": "Kan werkstroom niet verwijderen", - "unable_to_download_files": "Kan bestanden niet downloaden", - "unable_to_edit_exclusion_pattern": "Kan uitsluitingspatroon niet bewerken", - "unable_to_empty_trash": "Kan prullenbak niet legen", - "unable_to_enter_fullscreen": "Kan volledig scherm niet openen", - "unable_to_exit_fullscreen": "Kan volledig scherm niet afsluiten", - "unable_to_get_comments_number": "Kan aantal opmerkingen niet ophalen", - "unable_to_get_shared_link": "Kan gedeelde link niet ophalen", - "unable_to_hide_person": "Kan persoon niet verbergen", - "unable_to_link_motion_video": "Kan bewegende video niet koppelen", - "unable_to_link_oauth_account": "Kan OAuth-account niet koppelen", - "unable_to_log_out_all_devices": "Kan niet op alle apparaten uitloggen", - "unable_to_log_out_device": "Kan apparaat niet uitloggen", - "unable_to_login_with_oauth": "Kan niet inloggen met OAuth", - "unable_to_play_video": "Kan video niet afspelen", - "unable_to_reassign_assets_existing_person": "Kan items niet opnieuw toewijzen aan {name, select, null {een bestaand persoon} other {{name}}}", - "unable_to_reassign_assets_new_person": "Kan items niet opnieuw toewijzen aan een nieuw persoon", - "unable_to_refresh_user": "Kan gebruiker niet vernieuwen", - "unable_to_remove_album_users": "Kan gebruiker niet van album verwijderen", - "unable_to_remove_api_key": "Kan API-sleutel niet verwijderen", - "unable_to_remove_assets_from_shared_link": "Kan items niet verwijderen uit gedeelde link", - "unable_to_remove_library": "Kan bibliotheek niet verwijderen", - "unable_to_remove_partner": "Kan partner niet verwijderen", - "unable_to_remove_reaction": "Kan reactie niet verwijderen", - "unable_to_reset_password": "Kan wachtwoord niet resetten", - "unable_to_reset_pin_code": "Kan pincode niet resetten", - "unable_to_resolve_duplicate": "Kan duplicaat niet oplossen", - "unable_to_restore_assets": "Kan items niet herstellen", - "unable_to_restore_trash": "Kan niet herstellen uit prullenbak", - "unable_to_restore_user": "Kan gebruiker niet herstellen", - "unable_to_save_album": "Kan album niet opslaan", - "unable_to_save_api_key": "Kan API-sleutel niet opslaan", - "unable_to_save_date_of_birth": "Kan geboortedatum niet opslaan", - "unable_to_save_name": "Kan naam niet opslaan", - "unable_to_save_profile": "Kan profiel niet opslaan", - "unable_to_save_settings": "Kan instellingen niet opslaan", - "unable_to_scan_libraries": "Kan bibliotheken niet scannen", - "unable_to_scan_library": "Kan bibliotheek niet scannen", - "unable_to_set_feature_photo": "Kan uitgelichte foto niet instellen", - "unable_to_set_profile_picture": "Kan profielfoto niet instellen", - "unable_to_set_rating": "Kan waardering niet opslaan", - "unable_to_submit_job": "Kan taak niet uitvoeren", - "unable_to_trash_asset": "Kan item niet naar prullenbak verplaatsen", - "unable_to_unlink_account": "Kan account niet ontkoppelen", - "unable_to_unlink_motion_video": "Kan bewegende video niet ontkoppelen", - "unable_to_update_album_cover": "Kan albumomslag niet bijwerken", - "unable_to_update_album_info": "Kan albumgegevens niet bijwerken", - "unable_to_update_library": "Kan bibliotheek niet bijwerken", - "unable_to_update_location": "Kan locatie niet bijwerken", - "unable_to_update_settings": "Kan instellingen niet bijwerken", - "unable_to_update_timeline_display_status": "Kan de status van de tijdlijn niet bijwerken", - "unable_to_update_user": "Kan gebruiker niet bijwerken", - "unable_to_update_workflow": "Kan werkstroom niet bijwerken", - "unable_to_upload_file": "Kan bestand niet uploaden" - }, - "errors_text": "Errors", - "exclusion_pattern": "Uitsluitingspatroon", - "exif": "Exif", - "exif_bottom_sheet_description": "Beschrijving toevoegen...", - "exif_bottom_sheet_description_error": "Fout bij het bijwerken van de beschrijving", - "exif_bottom_sheet_details": "DETAILS", - "exif_bottom_sheet_location": "LOCATIE", - "exif_bottom_sheet_no_description": "Geen beschrijving", - "exif_bottom_sheet_people": "MENSEN", - "exif_bottom_sheet_person_add_person": "Naam toevoegen", - "exit_slideshow": "Diavoorstelling sluiten", - "expand_all": "Alles uitvouwen", - "experimental_settings_new_asset_list_subtitle": "Werk in uitvoering", - "experimental_settings_new_asset_list_title": "Experimenteel fotoraster inschakelen", - "experimental_settings_subtitle": "Gebruik op eigen risico!", - "experimental_settings_title": "Experimenteel", - "expire_after": "Verloopt na", - "expired": "Verlopen", - "expires_date": "Verloopt {date}", - "explore": "Verkennen", - "explorer": "Verkenner", - "export": "Exporteren", - "export_as_json": "Exporteren als JSON", - "export_database": "Exporteer database", - "export_database_description": "Exporteer de SQLite database", - "extension": "Extensie", - "external": "Extern", - "external_libraries": "Externe bibliotheken", - "external_network": "Extern netwerk", - "external_network_sheet_info": "Als je niet verbonden bent met het opgegeven WiFi-netwerk, maakt de app verbinding met de server via de eerst bereikbare URL in de onderstaande lijst, van boven naar beneden", - "face_unassigned": "Niet toegewezen", - "failed": "Mislukt", - "failed_count": "Mislukt: {count}", - "failed_to_authenticate": "Authenticatie mislukt", - "failed_to_load_assets": "Kan items niet laden", - "failed_to_load_folder": "Laden van map mislukt", - "favorite": "Favoriet", - "favorite_action_prompt": "{count} item(s) toegevoegd aan je favorieten", - "favorite_or_unfavorite_photo": "Foto markeren als of verwijderen uit favorieten", - "favorites": "Favorieten", - "favorites_page_no_favorites": "Geen favoriete items gevonden", - "feature_photo_updated": "Uitgelichte afbeelding bijgewerkt", - "features": "Functies", - "features_in_development": "Functies in ontwikkeling", - "features_setting_description": "Beheer de app functies", - "file_name_or_extension": "Bestandsnaam of extensie", - "file_size": "Bestandsgrootte", - "filename": "Bestandsnaam", - "filetype": "Bestandstype", - "filter": "Filter", - "filter_description": "Filtervoorwaarden voor doel items", - "filter_people": "Filter op mensen", - "filter_places": "Filter locaties", - "filters": "Filters", - "find_them_fast": "Vind ze snel op naam door te zoeken", - "first": "Eerste", - "fix_incorrect_match": "Onjuiste overeenkomst corrigeren", - "folder": "Map", - "folder_not_found": "Map niet gevonden", - "folders": "Mappen", - "folders_feature_description": "Bladeren door de mapweergave van de foto's en video's op het bestandssysteem", - "forgot_pin_code_question": "Pincode vergeten?", - "forward": "Vooruit", - "free_up_space": "Maak opslag vrij", - "free_up_space_description": "Verplaats back-ups van foto's en video's naar de prullenbak van uw apparaat om ruimte vrij te maken. Uw kopieën op de server blijven veilig.", - "free_up_space_settings_subtitle": "Maak opslagruimte vrij op uw apparaat", - "full_path": "Volledig pad: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Deze functie gebruikt externe bronnen van Google om te kunnen werken.", - "general": "Algemeen", - "geolocation_instruction_location": "Klik op een item met gps-coördinaten om de locatie te gebruiken, of kies een locatie direct op de kaart", - "get_help": "Hulp vragen", - "get_people_error": "Fout bij ophalen mensen", - "get_wifiname_error": "Kon de WiFi-naam niet ophalen. Zorg ervoor dat je de benodigde machtigingen hebt verleend en verbonden bent met een WiFi-netwerk", - "getting_started": "Aan de slag", - "go_back": "Ga terug", - "go_to_folder": "Ga naar map", - "go_to_search": "Ga naar zoeken", - "gps": "GPS", - "gps_missing": "Geen GPS", - "grant_permission": "Geef toestemming", - "group_albums_by": "Groepeer albums op...", - "group_country": "Groepeer op land", - "group_no": "Niet groeperen", - "group_owner": "Groepeer op eigenaar", - "group_places_by": "Groepeer plaatsen op...", - "group_year": "Groepeer op jaar", - "haptic_feedback_switch": "Aanraaktrillingen inschakelen", - "haptic_feedback_title": "Aanraaktrillingen", - "has_quota": "Heeft limiet", - "hash_asset": "Hash item", - "hashed_assets": "Gehashte items", - "hashing": "Hashen", - "header_settings_add_header_tip": "Header toevoegen", - "header_settings_field_validator_msg": "Waarde kan niet leeg zijn", - "header_settings_header_name_input": "Header naam", - "header_settings_header_value_input": "Header waarde", - "headers_settings_tile_title": "Aangepaste proxy headers", - "height": "Hoogte", - "hi_user": "Hallo {name} ({email})", - "hide_all_people": "Verberg alle mensen", - "hide_gallery": "Galerij verbergen", - "hide_named_person": "Verberg persoon {name}", - "hide_password": "Verberg wachtwoord", - "hide_person": "Verberg persoon", - "hide_schema": "Schema verbergen", - "hide_text_recognition": "Tekst­herkenning verbergen", - "hide_unnamed_people": "Verberg mensen zonder naam", - "home_page_add_to_album_conflicts": "{added} items toegevoegd aan album {album}. {failed} items staan al in het album.", - "home_page_add_to_album_err_local": "Lokale items kunnen nog niet aan albums worden toegevoegd, overslaan", - "home_page_add_to_album_success": "{added} items toegevoegd aan album {album}.", - "home_page_album_err_partner": "Partner items kunnen nog niet toegevoegd worden aan een album, overslaan", - "home_page_archive_err_local": "Lokale items kunnen nog niet gearchiveerd worden, overslaan", - "home_page_archive_err_partner": "Partner items kunnen niet gearchiveerd worden, overslaan", - "home_page_building_timeline": "Tijdlijn opbouwen", - "home_page_delete_err_partner": "Partner items kunnen niet verwijderd worden, overslaan", - "home_page_delete_remote_err_local": "Lokale items staan in verwijder selectie externe items, overslaan", - "home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan", - "home_page_favorite_err_partner": "Partner items kunnen nog niet als favoriet gemarkeerd worden, overslaan", - "home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album", - "home_page_locked_error_local": "Kan lokale bestanden niet naar de vergrendelde map verplaatsen, sla over", - "home_page_locked_error_partner": "Kan partnerbestanden niet naar de vergrendelde map verplaatsen, sla over", - "home_page_share_err_local": "Lokale items kunnen niet via een link gedeeld worden, overslaan", - "home_page_upload_err_limit": "Kan maximaal 30 items tegelijk uploaden, overslaan", - "host": "Host", - "hour": "Uur", - "hours": "Uren", - "id": "ID", - "idle": "Inactief", - "ignore_icloud_photos": "Negeer iCloud foto's", - "ignore_icloud_photos_description": "Foto's die op iCloud zijn opgeslagen, worden niet geüpload naar de Immich server", - "image": "Afbeelding", - "image_alt_text_date": "{isVideo, select, true {Video} other {Afbeelding}} genomen op {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Afbeelding}} genomen met {person1} op {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen met {person1} en {person2} op {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen met {person1}, {person2}, en {person3} op {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen met {person1}, {person2}, en {additionalCount, number} anderen op {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} op {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1} op {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1} en {person2} op {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1}, {person2}, en {person3} op {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1}, {person2}, en {additionalCount, number} anderen op {date}", - "image_saved_successfully": "Afbeelding opgeslagen", - "image_viewer_page_state_provider_download_started": "Download gestart", - "image_viewer_page_state_provider_download_success": "Download succesvol", - "image_viewer_page_state_provider_share_error": "Error bij delen", - "immich_logo": "Immich-logo", - "immich_web_interface": "Immich Web Interface", - "import_from_json": "Importeren vanuit JSON", - "import_path": "Import-pad", - "in_albums": "In {count, plural, one {# album} other {# albums}}", - "in_archive": "In archief", - "in_year": "In {year}", - "in_year_selector": "In", - "include_archived": "Toon gearchiveerde", - "include_shared_albums": "Toon gedeelde albums", - "include_shared_partner_assets": "Toon items van gedeelde partner", - "individual_share": "Individuele deellink", - "individual_shares": "Individuele deellinks", - "info": "Info", - "interval": { - "day_at_onepm": "Iedere dag om 13 uur", - "hours": "{hours, plural, one {Ieder uur} other {Iedere {hours, number} uren}}", - "night_at_midnight": "Iedere avond om middernacht", - "night_at_twoam": "Elke nacht om 2 uur" - }, - "invalid_date": "Ongeldige datum", - "invalid_date_format": "Ongeldig datumformaat", - "invite_people": "Mensen uitnodigen", - "invite_to_album": "Uitnodigen voor album", - "ios_debug_info_fetch_ran_at": "Ophalen gelukt op {dateTime}", - "ios_debug_info_last_sync_at": "Laatst gesynchroniseerd {dateTime}", - "ios_debug_info_no_processes_queued": "Geen achtergrondprocessen in de wachtrij", - "ios_debug_info_no_sync_yet": "Er is nog geen achtergrondsynchronisatie uitgevoerd", - "ios_debug_info_processes_queued": "{count, plural, one {{count} achtergrondproces in de wachtrij} other {{count} achtergrondprocessen in de wachtrij}}", - "ios_debug_info_processing_ran_at": "Verwerking uitgevoerd op {dateTime}", - "items_count": "{count, plural, one {# item} other {# items}}", - "jobs": "Taken", - "json_editor": "JSON bewerker", - "json_error": "JSON fout", - "keep": "Behouden", - "keep_albums": "Houd albums bij", - "keep_albums_count": "Het behouden van {count} {count, plural, one {album} other {albums}}", - "keep_all": "Behoud alle", - "keep_description": "Kies zelf welke gegevens op je apparaat blijven staan wanneer je ruimte vrijmaakt.", - "keep_favorites": "Bewaar favorieten", - "keep_on_device": "Blijf op het apparaat", - "keep_on_device_hint": "Selecteer items die u op dit apparaat wilt bewaren", - "keep_this_delete_others": "Deze behouden, andere verwijderen", - "keeping": "Bewaren: {items}", - "kept_this_deleted_others": "Dit item behouden en {count, plural, one {# ander item} other {# andere items}} verwijderd", - "keyboard_shortcuts": "Sneltoetsen", - "language": "Taal", - "language_no_results_subtitle": "Probeer je zoekterm aan te passen", - "language_no_results_title": "Geen talen gevonden", - "language_search_hint": "Zoek talen...", - "language_setting_description": "Selecteer je voorkeurstaal", - "large_files": "Grote bestanden", - "last": "Laatste", - "last_months": "{count, plural, one {Vorige maand} other {Laatste # maanden}}", - "last_seen": "Laatst gezien", - "latest_version": "Nieuwste versie", - "latitude": "Breedtegraad", - "leave": "Verlaten", - "leave_album": "Verlaat album", - "lens_model": "Lensmodel", - "let_others_respond": "Laat anderen reageren", - "level": "Niveau", - "library": "Bibliotheek", - "library_add_folder": "Map toevoegen", - "library_edit_folder": "Map bewerken", - "library_options": "Bibliotheek opties", - "library_page_device_albums": "Albums op apparaat", - "library_page_new_album": "Nieuw album", - "library_page_sort_asset_count": "Aantal items", - "library_page_sort_created": "Meest recent gemaakt", - "library_page_sort_last_modified": "Laatst aangepast", - "library_page_sort_title": "Albumtitel", - "licenses": "Licenties", - "light": "Licht", - "like": "Vind ik leuk", - "like_deleted": "Like verwijderd", - "link_motion_video": "Koppel bewegende video", - "link_to_oauth": "Koppel OAuth", - "linked_oauth_account": "Gekoppeld OAuth account", - "list": "Lijst", - "loading": "Laden", - "loading_search_results_failed": "Laden van zoekresultaten mislukt", - "local": "Lokaal", - "local_asset_cast_failed": "Kan geen item casten die nog niet geüpload is naar de server", - "local_assets": "Lokale Items", - "local_id": "Lokaal ID", - "local_media_summary": "Lokale media samenvatting", - "local_network": "Lokaal netwerk", - "local_network_sheet_info": "De app maakt verbinding met de server via deze URL wanneer het opgegeven WiFi-netwerk wordt gebruikt", - "location": "Locatie", - "location_permission": "Locatietoestemming", - "location_permission_content": "Om de functie voor automatische serverwissel te gebruiken, heeft Immich toegang tot de exacte locatie nodig om de naam van het huidige WiFi-netwerk te kunnen bepalen", - "location_picker_choose_on_map": "Kies op kaart", - "location_picker_latitude_error": "Voer een geldige breedtegraad in", - "location_picker_latitude_hint": "Voer hier je breedtegraad in", - "location_picker_longitude_error": "Voer een geldige lengtegraad in", - "location_picker_longitude_hint": "Voer hier je lengtegraad in", - "lock": "Vergrendel", - "locked_folder": "Vergrendelde map", - "log_detail_title": "Log details", - "log_out": "Uitloggen", - "log_out_all_devices": "Uitloggen op alle apparaten", - "logged_in_as": "Ingelogd als {user}", - "logged_out_all_devices": "Uitgelogd op alle apparaten", - "logged_out_device": "Uitgelogd van apparaat", - "login": "Inloggen", - "login_disabled": "Aanmelding uitgeschakeld", - "login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.", - "login_form_back_button_text": "Terug", - "login_form_email_hint": "jouwemail@email.com", - "login_form_endpoint_hint": "http://jouw-server-ip:poort", - "login_form_endpoint_url": "Server-URL", - "login_form_err_http": "Voer http:// of https:// in", - "login_form_err_invalid_email": "Ongeldig e-mailadres", - "login_form_err_invalid_url": "Ongeldige URL", - "login_form_err_leading_whitespace": "Spatie aan het begin", - "login_form_err_trailing_whitespace": "Spatie aan het eind", - "login_form_failed_get_oauth_server_config": "Fout bij inloggen met OAuth, controleer server-URL", - "login_form_failed_get_oauth_server_disable": "OAuth-functie is niet beschikbaar op deze server", - "login_form_failed_login": "Fout bij inloggen; controleer server-URL, e-mailadres en wachtwoord", - "login_form_handshake_exception": "Er was een Handshake Exception met de server. Schakel ondersteuning voor zelfondertekende certificaten in bij de instellingen als u een zelfondertekend certificaat gebruikt.", - "login_form_password_hint": "wachtwoord", - "login_form_save_login": "Ingelogd blijven", - "login_form_server_empty": "Voer een server-URL in.", - "login_form_server_error": "Kan geen verbinding maken met de server.", - "login_has_been_disabled": "Inloggen is uitgeschakeld.", - "login_password_changed_error": "Er is een fout opgetreden bij het bijwerken van je wachtwoord", - "login_password_changed_success": "Wachtwoord succesvol bijgewerkt", - "logout_all_device_confirmation": "Weet je zeker dat je wilt uitloggen op alle apparaten?", - "logout_this_device_confirmation": "Weet je zeker dat je wilt uitloggen op dit apparaat?", - "logs": "Logs", - "longitude": "Lengtegraad", - "look": "Uiterlijk", - "loop_videos": "Video's herhalen", - "loop_videos_description": "Inschakelen om video's automatisch te herhalen in de detailweergave.", - "main_branch_warning": "Je gebruikt een ontwikkelingsversie. We raden je ten zeerste aan een releaseversie te gebruiken!", - "main_menu": "Hoofdmenu", - "maintenance_action_restore": "Database herstellen", - "maintenance_description": "Immich is in de onderhouds­modus gezet.", - "maintenance_end": "Onderhouds­modus beëindigen", - "maintenance_end_error": "Onderhouds­modus beëindigen mislukt.", - "maintenance_logged_in_as": "Momenteel ingelogd als {user}", - "maintenance_restore_from_backup": "Herstellen vanaf backup", - "maintenance_restore_library": "Bibliotheek herstellen", - "maintenance_restore_library_confirm": "Als dit er goed uit ziet ga dan verder om de backup terug te zetten!", - "maintenance_restore_library_description": "Database herstellen", - "maintenance_restore_library_folder_has_files": "{folder} heeft {count} map(pen)", - "maintenance_restore_library_folder_no_files": "{folder} mist bestanden!", - "maintenance_restore_library_folder_pass": "leesbaar en schrijfbaar", - "maintenance_restore_library_folder_read_fail": "niet leesbaar", - "maintenance_restore_library_folder_write_fail": "niet schrijfbaar", - "maintenance_restore_library_hint_missing_files": "Er missen mogelijk belangrijke bestanden", - "maintenance_restore_library_hint_regenerate_later": "Deze kun je later opnieuw genereren in de instellingen", - "maintenance_restore_library_hint_storage_template_missing_files": "Gebruik je een opslagtemplate? Je mist misschien bestanden", - "maintenance_restore_library_loading": "Integriteitscontrole en heuristieken laden…", - "maintenance_task_backup": "Backup van bestaande database maken…", - "maintenance_task_migrations": "Bezig met database migraties…", - "maintenance_task_restore": "De gekozen backup terugzetten…", - "maintenance_task_rollback": "Terugzetten backup mislukt, herstelpunt terugzetten…", - "maintenance_title": "Tijdelijk niet beschikbaar", - "make": "Merk", - "manage_geolocation": "Beheer locatie", - "manage_media_access_rationale": "Deze rechten zijn nodig om items op een goede manier te verwijderen en te verplaatsen.", - "manage_media_access_settings": "Open instellingen", - "manage_media_access_subtitle": "Toestaan dat de Immich app mediabestanden mag beheren en verplaatsen.", - "manage_media_access_title": "Toegang tot mediabeheer", - "manage_shared_links": "Beheer gedeelde links", - "manage_sharing_with_partners": "Beheer delen met partners", - "manage_the_app_settings": "Beheer de appinstellingen", - "manage_your_account": "Beheer je account", - "manage_your_api_keys": "Beheer je API-sleutels", - "manage_your_devices": "Beheer je ingelogde apparaten", - "manage_your_oauth_connection": "Beheer je OAuth-koppeling", - "map": "Kaart", - "map_assets_in_bounds": "{count, plural, =0 {Geen fotos in dit gebied}one {# foto} other {# foto's}}", - "map_cannot_get_user_location": "Kan locatie van de gebruiker niet ophalen", - "map_location_dialog_yes": "Ja", - "map_location_picker_page_use_location": "Gebruik deze locatie", - "map_location_service_disabled_content": "Locatie service moet ingeschakeld zijn om items van je huidige locatie weer te geven. Wil je het nu inschakelen?", - "map_location_service_disabled_title": "Locatie service uitgeschakeld", - "map_marker_for_images": "Kaartmarkering voor afbeeldingen gemaakt in {city}, {country}", - "map_marker_with_image": "Kaartmarkering met afbeelding", - "map_no_location_permission_content": "Locatietoestemming is nodig om items van je huidige locatie weer te geven. Wil je dit nu toestaan?", - "map_no_location_permission_title": "Locatietoestemming geweigerd", - "map_settings": "Kaartinstellingen", - "map_settings_dark_mode": "Donkere modus", - "map_settings_date_range_option_day": "Afgelopen 24 uur", - "map_settings_date_range_option_days": "Afgelopen {days} dagen", - "map_settings_date_range_option_year": "Afgelopen jaar", - "map_settings_date_range_option_years": "Afgelopen {years} jaar", - "map_settings_dialog_title": "Kaartinstellingen", - "map_settings_include_show_archived": "Toon gearchiveerde", - "map_settings_include_show_partners": "Toon partners", - "map_settings_only_show_favorites": "Toon enkel favorieten", - "map_settings_theme_settings": "Kaartthema", - "map_zoom_to_see_photos": "Zoom uit om foto's te zien", - "mark_all_as_read": "Alles markeren als gelezen", - "mark_as_read": "Markeren als gelezen", - "marked_all_as_read": "Allen gemarkeerd als gelezen", - "matches": "Overeenkomsten", - "matching_assets": "Overeenkomende assets", - "media_type": "Mediatype", - "memories": "Herinneringen", - "memories_all_caught_up": "Je bent helemaal bij", - "memories_check_back_tomorrow": "Kom morgen terug voor meer herinneringen", - "memories_setting_description": "Beheer wat je ziet in je herinneringen", - "memories_start_over": "Opnieuw beginnen", - "memories_swipe_to_close": "Swipe omhoog om te sluiten", - "memory": "Herinnering", - "memory_lane_title": "Herinneringen {title}", - "menu": "Menu", - "merge": "Samenvoegen", - "merge_people": "Mensen samenvoegen", - "merge_people_limit": "Je kunt maximaal 5 gezichten tegelijk samenvoegen", - "merge_people_prompt": "Wil je deze mensen samenvoegen? Deze actie is onomkeerbaar.", - "merge_people_successfully": "Mensen succesvol samengevoegd", - "merged_people_count": "{count, plural, one {# person} other {# mensen}} samengevoegd", - "minimize": "Minimaliseren", - "minute": "Minuut", - "minutes": "Minuten", - "mirror_horizontal": "Horizontaal", - "mirror_vertical": "Verticaal", - "missing": "Missend", - "mobile_app": "Mobiele app", - "mobile_app_download_onboarding_note": "Download de mobiele app via de onderstaande opties", - "model": "Model", - "month": "Maand", - "monthly_title_text_date_format": "MMMM y", - "more": "Meer", - "move": "Verplaats", - "move_down": "Naar beneden verplaatsen", - "move_off_locked_folder": "Verplaats uit vergrendelde map", - "move_to": "Verplaatsen naar", - "move_to_device_trash": "Naar prullenbak van apparaat", - "move_to_lock_folder_action_prompt": "{count} item(s) toegevoegd aan de vergrendelde map", - "move_to_locked_folder": "Verplaats naar vergrendelde map", - "move_to_locked_folder_confirmation": "Deze foto’s en video’s worden uit alle albums verwijderd en zijn alleen te bekijken in de vergrendelde map", - "move_up": "Naar boven verplaatsen", - "moved_to_archive": "{count, plural, one {# item} other {# items}} verplaatst naar archief", - "moved_to_library": "{count, plural, one {# item} other {# items}} verplaatst naar bibliotheek", - "moved_to_trash": "Naar de prullenbak verplaatst", - "multiselect_grid_edit_date_time_err_read_only": "Kan datum van alleen-lezen item(s) niet wijzigen, overslaan", - "multiselect_grid_edit_gps_err_read_only": "Kan locatie van alleen-lezen item(s) niet wijzigen, overslaan", - "mute_memories": "Herrinneringen dempen", - "my_albums": "Mijn albums", - "name": "Naam", - "name_or_nickname": "Naam of gebruikersnaam", - "name_required": "Naam is verplicht", - "navigate": "Navigeer", - "navigate_to_time": "Navigeer naar tijdstip", - "network_requirement_photos_upload": "Gebruik mobiele data voor de backup van foto's", - "network_requirement_videos_upload": "Gebruik mobiele data voor de backups van video's", - "network_requirements": "Netwerk vereisten", - "network_requirements_updated": "Netwerkeisen zijn gewijzigd, back-upwachtrij wordt opnieuw ingesteld", - "networking_settings": "Netwerk", - "networking_subtitle": "Beheer de server-eindpuntinstellingen", - "never": "Nooit", - "new_album": "Nieuw album", - "new_api_key": "Nieuwe API-sleutel", - "new_date_range": "Nieuw datumbereik", - "new_password": "Nieuw wachtwoord", - "new_person": "Nieuw persoon", - "new_pin_code": "Nieuwe pincode", - "new_pin_code_subtitle": "Dit is de eerste keer dat u de vergrendelde map opent. Stel een pincode in om deze pagina veilig te openen", - "new_timeline": "Nieuwe tijdlijn", - "new_update": "Nieuwe update", - "new_user_created": "Nieuwe gebruiker aangemaakt", - "new_version_available": "NIEUWE VERSIE BESCHIKBAAR", - "newest_first": "Nieuwste eerst", - "next": "Volgende", - "next_memory": "Volgende herinnering", - "no": "Nee", - "no_actions_added": "Geen acties toegevoegd", - "no_albums_found": "Geen albums gevonden", - "no_albums_message": "Maak een album om je foto's en video's te organiseren", - "no_albums_with_name_yet": "Het lijkt erop dat je nog geen albums met deze naam hebt.", - "no_albums_yet": "Het lijkt erop dat je nog geen albums hebt.", - "no_archived_assets_message": "Archiveer foto's en video's om ze te verbergen in je Foto's overzicht", - "no_assets_message": "Klik hier om je eerste foto te uploaden", - "no_assets_to_show": "Geen foto's om te laten zien", - "no_cast_devices_found": "Geen cast-apparaten gevonden", - "no_checksum_local": "Geen checksum beschikbaar - kan lokale assets niet ophalen", - "no_checksum_remote": "Geen checksum beschikbaar - kan online assets niet ophalen", - "no_configuration_needed": "Geen configuratie nodig", - "no_devices": "Geen geautoriseerde apparaten", - "no_duplicates_found": "Er zijn geen duplicaten gevonden.", - "no_exif_info_available": "Geen exif info beschikbaar", - "no_explore_results_message": "Upload meer foto's om je verzameling te verkennen.", - "no_favorites_message": "Voeg favorieten toe om snel je beste foto's en video's te vinden", - "no_filters_added": "Geen filters toegevoegd", - "no_libraries_message": "Maak een externe bibliotheek om je foto's en video's te bekijken", - "no_local_assets_found": "Geen lokale assets gevonden met deze checksum", - "no_location_set": "Geen locatie ingesteld", - "no_locked_photos_message": "Foto’s en video’s in de vergrendelde map zijn verborgen en worden niet weergegeven wanneer je door je bibliotheek bladert of zoekt.", - "no_name": "Geen naam", - "no_notifications": "Geen meldingen", - "no_people_found": "Geen mensen gevonden", - "no_places": "Geen plaatsen", - "no_remote_assets_found": "Geen online assets gevonden met deze checksum", - "no_results": "Geen resultaten", - "no_results_description": "Probeer een synoniem of een algemener zoekwoord", - "no_shared_albums_message": "Maak een album om foto's en video's te delen met mensen in je netwerk", - "no_uploads_in_progress": "Geen uploads bezig", - "none": "Geen", - "not_allowed": "Niet toegestaan", - "not_available": "n.v.t.", - "not_in_any_album": "Niet in een album", - "not_selected": "Niet geselecteerd", - "note_apply_storage_label_to_previously_uploaded assets": "Opmerking: om het opslaglabel toe te passen op eerder geüploade items, voer de volgende taak uit", - "notes": "Opmerkingen", - "nothing_here_yet": "Hier staan nog geen items", - "notification_permission_dialog_content": "Om meldingen in te schakelen, ga naar Instellingen en selecteer toestaan.", - "notification_permission_list_tile_content": "Geef toestemming om meldingen te versturen.", - "notification_permission_list_tile_enable_button": "Meldingen inschakelen", - "notification_permission_list_tile_title": "Meldingen toestaan", - "notification_toggle_setting_description": "E-mailmeldingen inschakelen", - "notifications": "Meldingen", - "notifications_setting_description": "Beheer meldingen", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium instellen", - "obtainium_configurator_instructions": "Gebruik Obtainium om de Android-app recht­streeks vanuit Immich's GitHub-releases te installeren en bij te werken. Maak een API-sleutel aan en selecteer een variant om je Obtainium-configuratielink te maken", - "ocr": "OCR", - "official_immich_resources": "Officiële Immich bronnen", - "offline": "Offline", - "offset": "Verrekening", - "ok": "Ok", - "oldest_first": "Oudste eerst", - "on_this_device": "Op dit apparaat", - "onboarding": "Introductie", - "onboarding_locale_description": "Selecteer je voorkeurstaal. Je dan dit later wijzigen in je instellingen.", - "onboarding_privacy_description": "De volgende (optionele) functies zijn afhankelijk van externe services en kunnen op elk moment worden uitgeschakeld in de instellingen.", - "onboarding_server_welcome_description": "Laten we je instantie instellen met een aantal veelgebruikte instellingen.", - "onboarding_theme_description": "Kies een kleurenthema voor de applicatie. Dit kun je later wijzigen in je instellingen.", - "onboarding_user_welcome_description": "Laten we beginnen!", - "onboarding_welcome_user": "Welkom, {user}", - "online": "Online", - "only_favorites": "Alleen favorieten", - "open": "Openen", - "open_in_map_view": "Openen in kaartweergave", - "open_in_openstreetmap": "Openen in OpenStreetMap", - "open_the_search_filters": "Open de zoekfilters", - "options": "Opties", - "or": "of", - "organize_into_albums": "Organiseren in albums", - "organize_into_albums_description": "Bestaande foto's in albums plaatsen met de huidige synchronisatie-instellingen", - "organize_your_library": "Organiseer je bibliotheek", - "original": "origineel", - "other": "Overige", - "other_devices": "Andere apparaten", - "other_entities": "Andere entities", - "other_variables": "Andere variabelen", - "owned": "Eigenaar", - "owner": "Eigenaar", - "page": "Pagina", - "partner": "Partner", - "partner_can_access": "{partner} heeft toegang tot", - "partner_can_access_assets": "Al je foto's en video's behalve die in het archief of de prullenbak", - "partner_can_access_location": "De locatie waar je foto's zijn genomen", - "partner_list_user_photos": "Foto's van {user}", - "partner_list_view_all": "Bekijk alle", - "partner_page_empty_message": "Je foto's zijn nog niet gedeeld met een partner.", - "partner_page_no_more_users": "Geen gebruikers meer om toe te voegen", - "partner_page_partner_add_failed": "Partner toevoegen mislukt", - "partner_page_select_partner": "Selecteer partner", - "partner_page_shared_to_title": "Gedeeld met", - "partner_page_stop_sharing_content": "{partner} zal geen toegang meer hebben tot je fotos's.", - "partner_sharing": "Delen met partner", - "partners": "Partners", - "password": "Wachtwoord", - "password_does_not_match": "Wachtwoord komt niet overeen", - "password_required": "Wachtwoord vereist", - "password_reset_success": "Wachtwoord succesvol gereset", - "past_durations": { - "days": "Afgelopen {days, plural, one {dag} other {# dagen}}", - "hours": "Afgelopen {hours, plural, one {uur} other {# uren}}", - "years": "Afgelopen {years, plural, one {jaar} other {# jaar}}" - }, - "path": "Pad", - "pattern": "Patroon", - "pause": "Pauzeren", - "pause_memories": "Herinneringen pauzeren", - "paused": "Gepauzeerd", - "pending": "In behandeling", - "people": "Mensen", - "people_edits_count": "{count, plural, one {# persoon} other {# mensen}} bijgewerkt", - "people_feature_description": "Bladeren door foto's en video's gegroepeerd op personen", - "people_selected": "{count, plural, one {# persoon geselecteerd} other {# mensen geselecteerd}}", - "people_sidebar_description": "Toon een link naar Mensen in de zijbalk", - "permanent_deletion_warning": "Waarschuwing voor permanent verwijderen", - "permanent_deletion_warning_setting_description": "Toon een waarschuwing bij het permanent verwijderen van items", - "permanently_delete": "Permanent verwijderen", - "permanently_delete_assets_count": "{count, plural, one {Item} other {Items}} permanent verwijderen", - "permanently_delete_assets_prompt": "Weet je zeker dat je {count, plural, one {dit item} other {deze # items}} permanent wilt verwijderen? Hiermee {count, plural, one {wordt} other {worden}} deze ook uit de bijbehorende album(s) verwijderd.", - "permanently_deleted_asset": "Item permanent verwijderd", - "permanently_deleted_assets_count": "{count, plural, one {# item} other {# items}} permanent verwijderd", - "permission": "Rechten", - "permission_empty": "Je rechten mogen niet leeg zijn", - "permission_onboarding_back": "Terug", - "permission_onboarding_continue_anyway": "Toch doorgaan", - "permission_onboarding_get_started": "Aan de slag", - "permission_onboarding_go_to_settings": "Ga naar instellingen", - "permission_onboarding_permission_denied": "Toestemming geweigerd. Geef toestemming tot foto's en video's in je Instellingen om Immich te kunnen gebruiken.", - "permission_onboarding_permission_granted": "Toestemming verleend. Je bent helemaal klaar.", - "permission_onboarding_permission_limited": "Beperkte toestemming. Geef toestemming tot foto's en video's in Instellingen om Immich een back-up te laten maken van je galerij en deze te beheren.", - "permission_onboarding_request": "Immich heeft toestemming nodig om je foto's en video's te bekijken.", - "person": "Persoon", - "person_age_months": "{months, plural, one {# maand} other {# maanden}} oud", - "person_age_year_months": "1 year, {months, plural, one {# maand} other {# maanden}} oud", - "person_age_years": "{years, plural, other {# jaar}} oud", - "person_birthdate": "Geboren op {date}", - "person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}", - "person_recognized": "Persoon herkend", - "person_selected": "Persoon geselecteerd", - "photo_shared_all_users": "Het lijkt erop dat je foto's met alle gebruikers zijn gedeeld, of dat je geen gebruikers hebt om mee te delen.", - "photos": "Foto's", - "photos_and_videos": "Foto's & video's", - "photos_count": "{count, plural, one {{count, number} foto} other {{count, number} foto's}}", - "photos_from_previous_years": "Foto's van voorgaande jaren", - "photos_only": "Enkel foto's", - "pick_a_location": "Kies een locatie", - "pick_custom_range": "Aangepast bereik", - "pick_date_range": "Selecteer een datumbereik", - "pin_code_changed_successfully": "Pincode succesvol gewijzigd", - "pin_code_reset_successfully": "Pincode succesvol gereset", - "pin_code_setup_successfully": "Pincode succesvol ingesteld", - "pin_verification": "Pincodeverificatie", - "place": "Plaats", - "places": "Plaatsen", - "places_count": "{count, plural, one {{count, number} Plaats} other {{count, number} Plaatsen}}", - "play": "Afspelen", - "play_memories": "Herinneringen afspelen", - "play_motion_photo": "Bewegingsfoto afspelen", - "play_or_pause_video": "Video afspelen of pauzeren", - "play_original_video": "Originele video afspelen", - "play_original_video_setting_description": "Originele video afspelen in plaats van de getranscodeerde video's. Als het origineel niet compatibel is, zou deze verkeerd weergegeven kunnen worden.", - "play_transcoded_video": "Getranscodeerde video afspelen", - "please_auth_to_access": "Verifieer om toegang te krijgen", - "port": "Poort", - "preferences_settings_subtitle": "Beheer de voorkeuren van de app", - "preferences_settings_title": "Voorkeuren", - "preparing": "Voorbereiden", - "preset": "Voorinstelling", - "preview": "Voorbeeld", - "previous": "Vorige", - "previous_memory": "Vorige herinnering", - "previous_or_next_day": "Dag vooruit/achteruit", - "previous_or_next_month": "Maand vooruit/achteruit", - "previous_or_next_photo": "Foto vooruit/achteruit", - "previous_or_next_year": "Jaar vooruit/achteruit", - "primary": "Primair", - "privacy": "Privacy", - "profile": "Profiel", - "profile_drawer_app_logs": "Logs", - "profile_drawer_client_server_up_to_date": "App en server zijn up-to-date", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Alleen-lezen-modus ingeschakeld. Druk lang op je profielfoto om te verlaten.", - "profile_image_of_user": "Profielfoto van {user}", - "profile_picture_set": "Profielfoto ingesteld.", - "public_album": "Openbaar album", - "public_share": "Openbare deellink", - "purchase_account_info": "Ondersteuner", - "purchase_activated_subtitle": "Bedankt voor het ondersteunen van Immich en open-source software", - "purchase_activated_time": "Geactiveerd op {date}", - "purchase_activated_title": "Je licentiesleutel is succesvol geactiveerd", - "purchase_button_activate": "Activeren", - "purchase_button_buy": "Kopen", - "purchase_button_buy_immich": "Koop Immich", - "purchase_button_never_show_again": "Nooit meer tonen", - "purchase_button_reminder": "Herinner mij over 30 dagen", - "purchase_button_remove_key": "Licentiesleutel verwijderen", - "purchase_button_select": "Selecteren", - "purchase_failed_activation": "Activeren mislukt! Controleer je e-mail voor de juiste licentiesleutel!", - "purchase_individual_description_1": "Voor een gebruiker", - "purchase_individual_description_2": "Supporterstatus", - "purchase_individual_title": "Gebruiker", - "purchase_input_suggestion": "Heb je een licentiesleutel? Voer deze hieronder in", - "purchase_license_subtitle": "Koop Immich om de verdere ontwikkeling van de service te ondersteunen", - "purchase_lifetime_description": "Levenslange aankoop", - "purchase_option_title": "AANKOOPMOGELIJKHEDEN", - "purchase_panel_info_1": "Het bouwen van Immich kost veel tijd en moeite, en we hebben fulltime engineers die eraan werken om het zo goed mogelijk te maken. Onze missie is om open-source software en ethische bedrijfspraktijken een duurzame inkomstenbron te laten worden voor ontwikkelaars en een ecosysteem te creëren dat de privacy respecteert met echte alternatieven voor uitbuitende cloudservices.", - "purchase_panel_info_2": "Omdat we ons inzetten om geen paywalls toe te voegen, krijg je met deze aankoop geen extra functies in Immich. We vertrouwen op gebruikers zoals jij om de verdere ontwikkeling van Immich te ondersteunen.", - "purchase_panel_title": "Steun het project", - "purchase_per_server": "Per server", - "purchase_per_user": "Per gebruiker", - "purchase_remove_product_key": "Verwijder licentiesleutel", - "purchase_remove_product_key_prompt": "Weet je zeker dat je de licentiesleutel wilt verwijderen?", - "purchase_remove_server_product_key": "Verwijder server licentiesleutel", - "purchase_remove_server_product_key_prompt": "Weet je zeker dat je de server licentiesleutel wilt verwijderen?", - "purchase_server_description_1": "Voor de volledige server", - "purchase_server_description_2": "Supporterstatus", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "De licentiesleutel van de server wordt beheerd door de beheerder", - "query_asset_id": "Item-ID opvragen", - "queue_status": "Wachtrij {count}/{total}", - "rate_asset": "Item waardering geven", - "rating": "Sterwaardering", - "rating_clear": "Waardering verwijderen", - "rating_count": "{count, plural, one {# ster} other {# sterren}}", - "rating_description": "De EXIF-waardering weergeven in het infopaneel", - "rating_set": "Item {rating, plural, one {# ster} other {# sterren}} gegeven", - "reaction_options": "Reactie-opties", - "read_changelog": "Lees wijzigingen", - "readonly_mode_disabled": "Alleen-lezen modus uitgeschakeld", - "readonly_mode_enabled": "Alleen-lezen modus ingeschakeld", - "ready_for_upload": "Klaar voor upload", - "reassign": "Opnieuw toewijzen", - "reassigned_assets_to_existing_person": "{count, plural, one {# item} other {# items}} opnieuw toegewezen aan {name, select, null {een bestaand persoon} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {# item} other {# items}} opnieuw toegewezen aan een nieuw persoon", - "reassing_hint": "Geselecteerde items toewijzen aan een bestaand persoon", - "recent": "Recent", - "recent-albums": "Recente albums", - "recent_searches": "Recente zoekopdrachten", - "recently_added": "Onlangs toegevoegd", - "recently_added_page_title": "Recent toegevoegd", - "recently_taken": "Recent genomen", - "recently_taken_page_title": "Recent Genomen", - "refresh": "Vernieuwen", - "refresh_encoded_videos": "Vernieuw gecodeerde video's", - "refresh_faces": "Vernieuw gezichten", - "refresh_metadata": "Vernieuw metadata", - "refresh_thumbnails": "Vernieuw thumbnails", - "refreshed": "Verniewd", - "refreshes_every_file": "Vernieuwt alle bestaande en nieuwe bestanden", - "refreshing_encoded_video": "Gecodeerde video aan het vernieuwen", - "refreshing_faces": "Gezichten aan het vernieuwen", - "refreshing_metadata": "Metadata aan het vernieuwen", - "regenerating_thumbnails": "Thumbnails opnieuw aan het genereren", - "remote": "Externe", - "remote_assets": "Externe Items", - "remote_media_summary": "Online media samenvatting", - "remove": "Verwijderen", - "remove_assets_album_confirmation": "Weet je zeker dat je {count, plural, one {# item} other {# items}} uit het album wilt verwijderen?", - "remove_assets_shared_link_confirmation": "Weet je zeker dat je {count, plural, one {# item} other {# items}} uit deze gedeelde link wilt verwijderen?", - "remove_assets_title": "Items verwijderen?", - "remove_custom_date_range": "Aangepast datumbereik verwijderen", - "remove_deleted_assets": "Verwijder offline bestanden", - "remove_from_album": "Verwijderen uit album", - "remove_from_album_action_prompt": "{count} item(s) verwijderd uit het album", - "remove_from_favorites": "Verwijderen uit favorieten", - "remove_from_lock_folder_action_prompt": "{count} item(s) verwijderd uit de vergrendelde map", - "remove_from_locked_folder": "Verwijder uit de vergrendelde map", - "remove_from_locked_folder_confirmation": "Weet je zeker dat je deze foto's en video's uit de vergrendelde map wilt verplaatsen? Ze zijn dan weer zichtbaar in je bibliotheek.", - "remove_from_shared_link": "Verwijderen uit gedeelde link", - "remove_memory": "Herinnering verwijderen", - "remove_photo_from_memory": "Foto uit deze herinnering verwijderen", - "remove_tag": "Verwijder tag", - "remove_url": "Verwijder URL", - "remove_user": "Verwijder gebruiker", - "removed_api_key": "API-sleutel verwijderd: {name}", - "removed_from_archive": "Verwijderd uit archief", - "removed_from_favorites": "Verwijderd uit favorieten", - "removed_from_favorites_count": "{count, plural, other {# verwijderd}} uit favorieten", - "removed_memory": "Herinnering verwijderd", - "removed_photo_from_memory": "Foto verwijderd uit herinnering", - "removed_tagged_assets": "Tag verwijderd van {count, plural, one {# item} other {# items}}", - "rename": "Hernoemen", - "repair": "Repareren", - "repair_no_results_message": "Niet bijgehouden en ontbrekende bestanden zullen hier verschijnen", - "replace_with_upload": "Vervangen door upload", - "repository": "Repository", - "require_password": "Wachtwoord vereisen", - "require_user_to_change_password_on_first_login": "Vereisen dat de gebruiker het wachtwoord wijzigt bij de eerste keer inloggen", - "rescan": "Herscannen", - "reset": "Resetten", - "reset_password": "Wachtwoord resetten", - "reset_people_visibility": "Zichtbaarheid mensen resetten", - "reset_pin_code": "Reset pincode", - "reset_pin_code_description": "Als je jouw pincode bent vergeten, neem dan contact op met de administrator van de server om deze te resetten", - "reset_pin_code_success": "Pincode succesvol gereset", - "reset_pin_code_with_password": "Je kan je pincode altijd resetten met je wachtwoord", - "reset_sqlite": "SQLite database resetten", - "reset_sqlite_confirmation": "Ben je zeker dat je de SQLite database wilt resetten? Je zal moeten uitloggen om de data opnieuw te synchroniseren", - "reset_sqlite_success": "De SQLite database is succesvol gereset", - "reset_to_default": "Resetten naar standaard", - "resolution": "Resolutie", - "resolve_duplicates": "Duplicaten oplossen", - "resolved_all_duplicates": "Alle duplicaten opgelost", - "restore": "Herstellen", - "restore_all": "Herstel alle", - "restore_trash_action_prompt": "{count} item(s) teruggehaald uit de prullenbak", - "restore_user": "Gebruiker herstellen", - "restored_asset": "Item hersteld", - "resume": "Hervatten", - "resume_paused_jobs": "Hervat {count, plural, one {# gepauseerde taak} other {# gepauseerde taken}}", - "retry_upload": "Opnieuw uploaden", - "review_duplicates": "Controleer duplicaten", - "review_large_files": "Grote bestanden beoordelen", - "role": "Rol", - "role_editor": "Bewerker", - "role_viewer": "Bekijker", - "running": "Actief", - "save": "Opslaan", - "save_to_gallery": "Opslaan in galerij", - "saved": "Opgeslagen", - "saved_api_key": "API-sleutel opgeslagen", - "saved_profile": "Profiel opgeslagen", - "saved_settings": "Instellingen opgeslagen", - "say_something": "Zeg iets", - "scaffold_body_error_occurred": "Fout opgetreden", - "scan": "Scan", - "scan_all_libraries": "Scan alle bibliotheken", - "scan_library": "Scan", - "scan_settings": "Scaninstellingen", - "scanning": "Scannen", - "scanning_for_album": "Scannen voor album...", - "search": "Zoeken", - "search_albums": "Zoek albums", - "search_by_context": "Zoeken op context", - "search_by_description": "Zoeken op beschrijving", - "search_by_description_example": "Wandelen in Sapa", - "search_by_filename": "Zoeken op bestandsnaam of -extensie", - "search_by_filename_example": "b.v. IMG_1234.JPG of PNG", - "search_by_ocr": "Zoeken op tekst herkend door OCR", - "search_by_ocr_example": "Kaneel", - "search_camera_lens_model": "Zoek cameralens…", - "search_camera_make": "Zoek cameramerk...", - "search_camera_model": "Zoek cameramodel...", - "search_city": "Zoek stad...", - "search_country": "Zoek land...", - "search_filter_apply": "Filter toepassen", - "search_filter_camera_title": "Selecteer cameratype", - "search_filter_date": "Datum", - "search_filter_date_interval": "{start} tot {end}", - "search_filter_date_title": "Selecteer datumbereik", - "search_filter_display_option_not_in_album": "Niet in album", - "search_filter_display_options": "Weergaveopties", - "search_filter_filename": "Zoeken op bestandsnaam", - "search_filter_location": "Locatie", - "search_filter_location_title": "Selecteer locatie", - "search_filter_media_type": "Mediatype", - "search_filter_media_type_title": "Selecteer mediatype", - "search_filter_ocr": "Zoeken op tekst herkend door OCR", - "search_filter_people_title": "Selecteer mensen", - "search_filter_star_rating": "Sterbeoordeling", - "search_for": "Zoeken naar", - "search_for_existing_person": "Zoek naar bestaande persoon", - "search_no_more_result": "Geen resultaten meer", - "search_no_people": "Geen mensen", - "search_no_people_named": "Geen mensen genaamd \"{name}\"", - "search_no_result": "Geen resultaten gevonden, probeer een andere zoekterm of combinatie", - "search_options": "Zoekopties", - "search_page_categories": "Categorieën", - "search_page_motion_photos": "Bewegende foto's", - "search_page_no_objects": "Geen objectgegevens beschikbaar", - "search_page_no_places": "Geen locatiegegevens beschikbaar", - "search_page_screenshots": "Schermafbeelding", - "search_page_search_photos_videos": "Zoek naar je foto's en video's", - "search_page_selfies": "Selfies", - "search_page_things": "Dingen", - "search_page_view_all_button": "Bekijk alle", - "search_page_your_activity": "Je activiteit", - "search_page_your_map": "Jouw kaart", - "search_people": "Zoek mensen", - "search_places": "Zoek plaatsen", - "search_rating": "Zoeken op beoordeling...", - "search_result_page_new_search_hint": "Nieuwe zoekopdracht", - "search_settings": "Zoek instellingen", - "search_state": "Zoek staat...", - "search_suggestion_list_smart_search_hint_1": "Slim zoeken is standaard ingeschakeld, om naar metadata te zoeken gebruik ", - "search_suggestion_list_smart_search_hint_2": "m:je-zoekterm", - "search_tags": "Tags zoeken...", - "search_timezone": "Zoek tijdzone...", - "search_type": "Type zoekopdracht", - "search_your_photos": "Foto's doorzoeken", - "searching_locales": "Zoeken naar landinstellingen...", - "second": "Seconde", - "see_all_people": "Bekijk alle mensen", - "select": "Selecteer", - "select_album": "Selecteer album", - "select_album_cover": "Selecteer albumomslag", - "select_albums": "Selecteer albums", - "select_all": "Alles selecteren", - "select_all_duplicates": "Selecteer alle duplicaten", - "select_all_in": "Selecteer alles in {group}", - "select_avatar_color": "Selecteer avatarkleur", - "select_count": "{count, plural, one {Selecteer #} other {Selecteer #}}", - "select_cutoff_date": "Selecteer einddatum", - "select_face": "Selecteer gezicht", - "select_featured_photo": "Selecteer uitgelichte foto", - "select_from_computer": "Selecteer van computer", - "select_keep_all": "Selecteer alles behouden", - "select_library_owner": "Selecteer bibliotheekeigenaar", - "select_new_face": "Selecteer nieuw gezicht", - "select_people": "Selecteer mensen", - "select_person": "Selecteer persoon", - "select_person_to_tag": "Selecteer een persoon om te taggen", - "select_photos": "Selecteer foto's", - "select_trash_all": "Selecteer alles naar prullenbak verplaatsen", - "select_user_for_sharing_page_err_album": "Album aanmaken mislukt", - "selected": "Geselecteerd", - "selected_count": "{count, plural, other {# geselecteerd}}", - "selected_gps_coordinates": "Geselecteerde gps-coördinaten", - "send_message": "Bericht versturen", - "send_welcome_email": "Stuur welkomstmail", - "server_endpoint": "Server-eindpunt", - "server_info_box_app_version": "Appversie", - "server_info_box_server_url": "Server-URL", - "server_offline": "Server offline", - "server_online": "Server online", - "server_privacy": "Serverprivacy", - "server_restarting_description": "Deze pagina wordt zometeen ververst.", - "server_restarting_title": "Server is aan het herstarten", - "server_stats": "Serverstatistieken", - "server_update_available": "Server update is beschikbaar", - "server_version": "Serverversie", - "set": "Instellen", - "set_as_album_cover": "Stel in als albumomslag", - "set_as_featured_photo": "Instellen als uitgelichte foto", - "set_as_profile_picture": "Instellen als profielfoto", - "set_date_of_birth": "Geboortedatum instellen", - "set_profile_picture": "Profielfoto instellen", - "set_slideshow_to_fullscreen": "Diavoorstelling op volledig scherm", - "set_stack_primary_asset": "Instellen als primair item", - "setting_image_viewer_help": "De gedetailleerde weergave laadt eerst de kleine thumbnail, vervolgens het middelgrote voorbeeld (indien ingeschakeld) en ten slotte het origineel (indien ingeschakeld).", - "setting_image_viewer_original_subtitle": "Schakel in om de originele afbeelding met volledige resolutie (groot!) te laden. Schakel uit om datagebruik te verminderen (zowel netwerk als apparaatcache).", - "setting_image_viewer_original_title": "Originele afbeelding laden", - "setting_image_viewer_preview_subtitle": "Schakel in om een afbeelding met middelgrote resolutie te laden. Schakel uit om alleen het origineel direct te laden of alleen de thumbnail te gebruiken.", - "setting_image_viewer_preview_title": "Voorbeeldafbeelding laden", - "setting_image_viewer_title": "Afbeeldingen", - "setting_languages_apply": "Toepassen", - "setting_languages_subtitle": "Wijzig de taal van de app", - "setting_notifications_notify_failures_grace_period": "Fouten van de achtergrond back-up melden: {duration}", - "setting_notifications_notify_hours": "{count} uur", - "setting_notifications_notify_immediately": "meteen", - "setting_notifications_notify_minutes": "{count} minuten", - "setting_notifications_notify_never": "nooit", - "setting_notifications_notify_seconds": "{count} seconden", - "setting_notifications_single_progress_subtitle": "Gedetailleerde informatie over de uploadvoortgang per item", - "setting_notifications_single_progress_title": "Gedetailleerde informatie over achtergrond back-ups tonen", - "setting_notifications_subtitle": "Voorkeuren voor meldingen beheren", - "setting_notifications_total_progress_subtitle": "Algehele uploadvoortgang (voltooid/totaal aantal items)", - "setting_notifications_total_progress_title": "Totale voortgang van achtergrond back-up tonen", - "setting_video_viewer_auto_play_subtitle": "Speel video's automatisch af zodra ze geopend worden", - "setting_video_viewer_auto_play_title": "Video's automatisch afspelen", - "setting_video_viewer_looping_title": "Herhalen", - "setting_video_viewer_original_video_subtitle": "Speel video's altijd in originele kwaliteit af, zelfs als er een getranscodeerd bestand beschikbaar is op de server. Dit kan leiden tot buffering. Video's die lokaal beschikbaar zijn, worden altijd in originele kwaliteit afgespeeld, ongeacht deze instelling.", - "setting_video_viewer_original_video_title": "Forceer originele videokwaliteit", - "settings": "Instellingen", - "settings_require_restart": "Start Immich opnieuw op om deze instelling toe te passen", - "settings_saved": "Instellingen opgeslagen", - "setup_pin_code": "Stel een pincode in", - "share": "Delen", - "share_action_prompt": "{count} item(s) gedeeld", - "share_add_photos": "Foto's toevoegen", - "share_assets_selected": "{count} item(s) geselecteerd", - "share_dialog_preparing": "Voorbereiden...", - "share_link": "Link delen", - "shared": "Gedeeld", - "shared_album_activities_input_disable": "Reactie is uitgeschakeld", - "shared_album_activity_remove_content": "Wil je deze activiteit verwijderen?", - "shared_album_activity_remove_title": "Verwijder activiteit", - "shared_album_section_people_action_error": "Fout bij verlaten/verwijderen uit album", - "shared_album_section_people_action_leave": "Verlaat album", - "shared_album_section_people_action_remove_user": "Verwijder gebruiker van album", - "shared_album_section_people_title": "MENSEN", - "shared_by": "Gedeeld door", - "shared_by_user": "Gedeeld door {user}", - "shared_by_you": "Gedeeld door jou", - "shared_from_partner": "Foto's van {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} geüpload", - "shared_link_app_bar_title": "Gedeelde links", - "shared_link_clipboard_copied_massage": "Gekopieerd naar klembord", - "shared_link_clipboard_text": "Link: {link}\nWachtwoord: {password}", - "shared_link_create_error": "Fout bij het maken van een gedeelde link", - "shared_link_custom_url_description": "Krijg toegang tot deze gedeelde link met een aangepaste URL", - "shared_link_edit_description_hint": "Voer beschrijving voor de gedeelde link in", - "shared_link_edit_expire_after_option_day": "1 dag", - "shared_link_edit_expire_after_option_days": "{count} dagen", - "shared_link_edit_expire_after_option_hour": "1 uur", - "shared_link_edit_expire_after_option_hours": "{count} uren", - "shared_link_edit_expire_after_option_minute": "1 minuut", - "shared_link_edit_expire_after_option_minutes": "{count} minuten", - "shared_link_edit_expire_after_option_months": "{count} maanden", - "shared_link_edit_expire_after_option_year": "{count} jaar", - "shared_link_edit_password_hint": "Voer wachtwoord voor de gedeelde link in", - "shared_link_edit_submit_button": "Link bijwerken", - "shared_link_error_server_url_fetch": "Kan de server-url niet ophalen", - "shared_link_expires_day": "Verloopt over {count} dag", - "shared_link_expires_days": "Verloopt over {count} dagen", - "shared_link_expires_hour": "Verloopt over {count} uur", - "shared_link_expires_hours": "Verloopt over {count} uur", - "shared_link_expires_minute": "Verloopt over {count} minuut", - "shared_link_expires_minutes": "Verloopt over {count} minuten", - "shared_link_expires_never": "Verloopt ∞", - "shared_link_expires_second": "Verloopt over {count} seconde", - "shared_link_expires_seconds": "Verloopt over {count} seconden", - "shared_link_individual_shared": "Individueel gedeeld", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Beheer gedeelde links", - "shared_link_options": "Opties voor gedeelde links", - "shared_link_password_description": "Vraag een wachtwoord om toegang te krijgen tot deze gedeelde link", - "shared_links": "Gedeelde links", - "shared_links_description": "Deel foto's en video's via een link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# gedeelde foto's & video's.}}", - "shared_with_me": "Gedeeld met mij", - "shared_with_partner": "Gedeeld met {partner}", - "sharing": "Delen", - "sharing_enter_password": "Voer het wachtwoord in om deze pagina te bekijken.", - "sharing_page_album": "Gedeelde albums", - "sharing_page_description": "Maak gedeelde albums om foto's en video's te delen met mensen in je netwerk.", - "sharing_page_empty_list": "LEGE LIJST", - "sharing_sidebar_description": "Toon een link naar Delen in de zijbalk", - "sharing_silver_appbar_create_shared_album": "Gedeeld album maken", - "sharing_silver_appbar_share_partner": "Delen met partner", - "shift_to_permanent_delete": "druk op ⇧ om items permanent te verwijderen", - "show_album_options": "Toon albumopties", - "show_albums": "Toon albums", - "show_all_people": "Toon alle mensen", - "show_and_hide_people": "Toon & verberg mensen", - "show_file_location": "Toon bestandslocatie", - "show_gallery": "Toon galerij", - "show_hidden_people": "Verbogen mensen weergeven", - "show_in_timeline": "Toon in tijdlijn", - "show_in_timeline_setting_description": "Toon foto's en video's van deze gebruiker in je tijdlijn", - "show_keyboard_shortcuts": "Sneltoetsen tonen", - "show_metadata": "Toon metadata", - "show_or_hide_info": "Toon of verberg info", - "show_password": "Toon wachtwoord", - "show_person_options": "Toon persoonopties", - "show_progress_bar": "Toon voortgangsbalk", - "show_schema": "Toon schema", - "show_search_options": "Zoekopties weergeven", - "show_shared_links": "Toon gedeelde links", - "show_slideshow_transition": "Diavoorstellingsovergang tonen", - "show_supporter_badge": "Supportersbadge", - "show_supporter_badge_description": "Toon een supportersbadge", - "show_text_recognition": "Tekst­herkenning tonen", - "show_text_search_menu": "Laat tekst zoek menu zien", - "shuffle": "Willekeurig", - "sidebar": "Zijbalk", - "sidebar_display_description": "Toon een link naar deze pagina in de zijbalk", - "sign_out": "Uitloggen", - "sign_up": "Registreren", - "size": "Grootte", - "skip_to_content": "Doorgaan naar inhoud", - "skip_to_folders": "Doorgaan naar mappen", - "skip_to_tags": "Doorgaan naar tags", - "slideshow": "Diavoorstelling", - "slideshow_repeat": "Herhaal diavoorstelling", - "slideshow_repeat_description": "Keer terug naar het begin wanneer de diavoorstelling eindigt", - "slideshow_settings": "Diavoorstelling instellingen", - "sort_albums_by": "Sorteer albums op...", - "sort_created": "Datum aangemaakt", - "sort_items": "Aantal items", - "sort_modified": "Datum aangepast", - "sort_newest": "Nieuwste foto", - "sort_oldest": "Oudste foto", - "sort_people_by_similarity": "Sorteer personen op gelijkenis", - "sort_recent": "Meest recente foto", - "sort_title": "Titel", - "source": "Bron", - "stack": "Stapel", - "stack_action_prompt": "{count} item(s) gestapeld", - "stack_duplicates": "Stapel duplicaten", - "stack_select_one_photo": "Selecteer één primaire foto voor de stapel", - "stack_selected_photos": "Geselecteerde foto's stapelen", - "stacked_assets_count": "{count, plural, one {# item} other {# items}} gestapeld", - "stacktrace": "Stacktrace", - "start": "Start", - "start_date": "Startdatum", - "start_date_before_end_date": "Startdatum moet voor einddatum liggen", - "state": "Staat", - "status": "Status", - "stop_casting": "Stop met casten", - "stop_motion_photo": "Bewegingsfoto stoppen", - "stop_photo_sharing": "Stoppen met het delen van je foto's?", - "stop_photo_sharing_description": "{partner} zal geen toegang meer hebben tot je foto's.", - "stop_sharing_photos_with_user": "Stop met het delen van je foto's met deze gebruiker", - "storage": "Opslagruimte", - "storage_label": "Opslaglabel", - "storage_quota": "Opslaglimiet", - "storage_usage": "{used} van {available} gebruikt", - "submit": "Verzenden", - "success": "Gelukt", - "suggestions": "Suggesties", - "sunrise_on_the_beach": "Zonsopkomst op het strand", - "support": "Ondersteuning", - "support_and_feedback": "Ondersteuning & feedback", - "support_third_party_description": "Je Immich installatie is door een derde partij samengesteld. Problemen die je ervaart, kunnen door dat pakket veroorzaakt zijn. Meld problemen in eerste instantie bij hen via de onderstaande links.", - "swap_merge_direction": "Wissel richting voor samenvoegen om", - "sync": "Synchroniseren", - "sync_albums": "Albums synchroniseren", - "sync_albums_manual_subtitle": "Synchroniseer alle geüploade video’s en foto’s naar de geselecteerde back-up albums", - "sync_local": "Lokaal synchroniseren", - "sync_remote": "Op afstand synchroniseren", - "sync_status": "Synchronisatiestatus", - "sync_status_subtitle": "Bekijk en beheer het synchronisatie systeem", - "sync_upload_album_setting_subtitle": "Maak en upload je foto's en video's naar de geselecteerde albums op Immich", - "tag": "Tag", - "tag_assets": "Items taggen", - "tag_created": "Tag aangemaakt: {tag}", - "tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags", - "tag_not_found_question": "Kun je een tag niet vinden? Maak een nieuwe tag.", - "tag_people": "Mensen taggen", - "tag_updated": "Tag bijgewerkt: {tag}", - "tagged_assets": "{count, plural, one {# item} other {# items}} getagd", - "tags": "Tags", - "tap_to_run_job": "Klik om job te starten", - "template": "Template", - "text_recognition": "Tekst­herkenning", - "theme": "Thema", - "theme_selection": "Thema selectie", - "theme_selection_description": "Stel het thema automatisch in op licht of donker op basis van de systeemvoorkeuren van je browser", - "theme_setting_asset_list_storage_indicator_title": "Toon opslag indicator bij de item tegels", - "theme_setting_asset_list_tiles_per_row_title": "Aantal items per rij ({count})", - "theme_setting_colorful_interface_subtitle": "Pas primaire kleuren toe op achtergronden.", - "theme_setting_colorful_interface_title": "Kleurrijke interface", - "theme_setting_image_viewer_quality_subtitle": "De kwaliteit van de gedetailleerde-fotoweergave aanpassen", - "theme_setting_image_viewer_quality_title": "Fotoweergavekwaliteit", - "theme_setting_primary_color_subtitle": "Kies een kleur voor primaire acties en accenten.", - "theme_setting_primary_color_title": "Primaire kleur", - "theme_setting_system_primary_color_title": "Gebruik systeemkleur", - "theme_setting_system_theme_switch": "Automatisch (systeeminstelling volgen)", - "theme_setting_theme_subtitle": "De thema-instelling van de app kiezen", - "theme_setting_three_stage_loading_subtitle": "Laden in drie fasen kan de laadprestaties verbeteren, maar veroorzaakt een aanzienlijk hogere netwerkbelasting", - "theme_setting_three_stage_loading_title": "Laden in drie fasen inschakelen", - "then": "Dan", - "they_will_be_merged_together": "Zij zullen worden samengevoegd", - "third_party_resources": "Bronnen van derden", - "time": "Tijd", - "time_based_memories": "Tijdgebaseerde herinneringen", - "time_based_memories_duration": "Aantal seconden dat elke afbeelding wordt weergegeven.", - "timeline": "Tijdlijn", - "timezone": "Tijdzone", - "to_archive": "Archiveren", - "to_change_password": "Wijzig wachtwoord", - "to_favorite": "Toevoegen aan favorieten", - "to_login": "Inloggen", - "to_multi_select": "naar multi-select", - "to_parent": "Ga naar hoofdmap", - "to_select": "naar selecteren", - "to_trash": "Prullenbak", - "toggle_settings": "Zichtbaarheid instellingen wisselen", - "toggle_theme_description": "Thema omschakelen", - "total": "Totaal", - "total_usage": "Totaal gebruik", - "trash": "Prullenbak", - "trash_action_prompt": "{count} item(s) verplaatst naar de prullenbak", - "trash_all": "Verplaats alle naar prullenbak", - "trash_count": "{count, number} naar prullenbak", - "trash_delete_asset": "Items naar prullenbak verplaatsen of verwijderen", - "trash_emptied": "Prullenbak geleegd", - "trash_no_results_message": "Hier verschijnen foto's en video's die in de prullenbak zijn geplaatst.", - "trash_page_delete_all": "Verwijder alle", - "trash_page_empty_trash_dialog_content": "Wil je de prullenbak leegmaken? Deze items worden permanent verwijderd van Immich", - "trash_page_info": "Verwijderde items worden permanent verwijderd na {days} dagen", - "trash_page_no_assets": "Geen verwijderde items", - "trash_page_restore_all": "Herstel alle", - "trash_page_select_assets_btn": "Selecteer items", - "trash_page_title": "Prullenbak ({count})", - "trashed_items_will_be_permanently_deleted_after": "Items in de prullenbak worden na {days, plural, one {# dag} other {# dagen}} permanent verwijderd.", - "trigger": "Trigger", - "trigger_asset_uploaded": "Item geüpload", - "trigger_asset_uploaded_description": "Getriggerd wanneer een nieuw item geüpload wordt", - "trigger_description": "Een gebeurtenis die het proces start", - "trigger_person_recognized": "Persoon herkend", - "trigger_person_recognized_description": "Getriggerd wanneer een persoon herkend is", - "trigger_type": "Trigger type", - "troubleshoot": "Problemen oplossen", - "type": "Type", - "unable_to_change_pin_code": "Pincode kan niet gewijzigd worden", - "unable_to_check_version": "Kan app-/serverversie niet checken", - "unable_to_setup_pin_code": "Pincode kan niet ingesteld worden", - "unarchive": "Herstellen uit archief", - "unarchive_action_prompt": "{count} verwijderd uit het archief", - "unarchived_count": "{count, plural, other {# verwijderd uit archief}}", - "undo": "Ongedaan maken", - "unfavorite": "Verwijderen uit favorieten", - "unfavorite_action_prompt": "{count} verwijderd uit je favorieten", - "unhide_person": "Persoon zichtbaar maken", - "unknown": "Onbekend", - "unknown_country": "Onbekend Land", - "unknown_date": "Onbekende datum", - "unknown_year": "Onbekend jaar", - "unlimited": "Onbeperkt", - "unlink_motion_video": "Ontkoppel bewegende video", - "unlink_oauth": "Ontkoppel OAuth", - "unlinked_oauth_account": "OAuth account ontkoppeld", - "unmute_memories": "Dempen van herrinneringen opheffen", - "unnamed_album": "Naamloos album", - "unnamed_album_delete_confirmation": "Weet je zeker dat je dit album wilt verwijderen?", - "unnamed_share": "Naamloze deellink", - "unsaved_change": "Niet-opgeslagen wijziging", - "unselect_all": "Alles deselecteren", - "unselect_all_duplicates": "Deselecteer alle duplicaten", - "unselect_all_in": "Deselecteer alles in {group}", - "unstack": "Ontstapelen", - "unstack_action_prompt": "{count} item(s) ontstapeld", - "unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapeld", - "unsupported_field_type": "Veldtype niet ondersteund", - "untagged": "Ongemarkeerd", - "untitled_workflow": "Naamloze werkstroom", - "up_next": "Volgende", - "update_location_action_prompt": "Werk de locatie bij van {count} geselecteerde items met:", - "updated_at": "Geüpdatet", - "updated_password": "Wachtwoord bijgewerkt", - "upload": "Uploaden", - "upload_concurrency": "Aantal gelijktijdige uploads", - "upload_details": "Uploaddetails", - "upload_dialog_info": "Wil je een backup maken van de geselecteerde item(s) op de server?", - "upload_dialog_title": "Item uploaden", - "upload_error_with_count": "Upload fout voor {count, plural, one {# item} other {# items}}", - "upload_errors": "Upload voltooid met {count, plural, one {# fout} other {# fouten}}, vernieuw de pagina om de nieuwe items te zien.", - "upload_finished": "Uploaden is voltooid", - "upload_progress": "Resterend {remaining, number} - Verwerkt {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {# duplicate item} other {# duplicate items}} overgeslagen", - "upload_status_duplicates": "Duplicaten", - "upload_status_errors": "Fouten", - "upload_status_uploaded": "Geüpload", - "upload_success": "Uploaden gelukt, vernieuw de pagina om de nieuwe items te zien.", - "upload_to_immich": "Uploaden naar Immich ({count})", - "uploading": "Aan het uploaden", - "uploading_media": "Media wordt geüpload", - "url": "URL", - "usage": "Gebruik", - "use_biometric": "Gebruik biometrische authenticatie", - "use_current_connection": "Gebruik huidige verbinding", - "use_custom_date_range": "Gebruik in plaats daarvan een aangepast datumbereik", - "user": "Gebruiker", - "user_has_been_deleted": "Deze gebruiker is verwijderd.", - "user_id": "Gebruikers ID", - "user_liked": "{user} heeft {type, select, photo {deze foto} video {deze video} asset {} other {dit item}} geliket", - "user_pin_code_settings": "Pincode", - "user_pin_code_settings_description": "Beheer je pincode", - "user_privacy": "Gebruikersprivacy", - "user_purchase_settings": "Kopen", - "user_purchase_settings_description": "Beheer je aankoop", - "user_role_set": "{user} instellen als {role}", - "user_usage_detail": "Gedetailleerd gebruik van gebruikers", - "user_usage_stats": "Statistieken van accountgebruik", - "user_usage_stats_description": "Bekijk statistieken van accountgebruik", - "username": "Gebruikersnaam", - "users": "Gebruikers", - "users_added_to_album_count": "{count, plural, one {# Gebruiker} other {# Gebruikers}} toegevoegd aan album", - "utilities": "Gereedschap", - "validate": "Valideren", - "validate_endpoint_error": "Vul een geldige URL in", - "validation_error": "Validatiefout", - "variables": "Variabelen", - "version": "Versie", - "version_announcement_closing": "Je vriend, Alex", - "version_announcement_message": "Hallo! Er is een nieuwe versie van Immich beschikbaar. Neem even de tijd om de release notes te lezen en zorg ervoor dat je setup up-to-date is om misconfiguraties te voorkomen, vooral als je WatchTower of een ander update-mechanisme gebruikt.", - "version_history": "Versiegeschiedenis", - "version_history_item": "{version} geïnstalleerd op {date}", - "video": "Video", - "video_hover_setting": "Speel videominiatuur af bij hoveren", - "video_hover_setting_description": "Speel videominiatuur af wanneer de muis over het item beweegt. Zelfs wanneer uitgeschakeld, kan het afspelen worden gestart door de muis over het afspeelpictogram te bewegen.", - "videos": "Video's", - "videos_count": "{count, plural, one {# video} other {# video's}}", - "videos_only": "Enkel video's", - "view": "Bekijken", - "view_album": "Bekijk album", - "view_all": "Bekijk alle", - "view_all_users": "Bekijk alle gebruikers", - "view_asset_owners": "Eigenaar van items bekijken", - "view_details": "Bekijk details", - "view_in_timeline": "Bekijk in tijdlijn", - "view_link": "Bekijk link", - "view_links": "Links bekijken", - "view_name": "Bekijken", - "view_next_asset": "Bekijk volgend item", - "view_previous_asset": "Bekijk vorig item", - "view_qr_code": "QR-code bekijken", - "view_similar_photos": "Bekijk vergelijkbare foto's", - "view_stack": "Bekijk stapel", - "view_user": "Bekijk gebruiker", - "viewer_remove_from_stack": "Verwijder van stapel", - "viewer_stack_use_as_main_asset": "Zet bovenaan de stapel", - "viewer_unstack": "Ontstapel", - "visibility_changed": "Zichtbaarheid gewijzigd voor {count, plural, one {# persoon} other {# mensen}}", - "visual": "Visueel", - "visual_builder": "Visuele bouwer", - "waiting": "Wachtend", - "waiting_count": "In de wacht: {count}", - "warning": "Waarschuwing", - "week": "Week", - "welcome": "Welkom", - "welcome_to_immich": "Welkom bij Immich", - "width": "Breedte", - "wifi_name": "WiFi-naam", - "workflow_delete_prompt": "Weet je zeker dat je deze werkstroom wilt verwijderen?", - "workflow_deleted": "Werkstroom verwijderd", - "workflow_description": "Werkstroom omschrijving", - "workflow_info": "Werkstroom info", - "workflow_json": "Werkstroom JSON", - "workflow_json_help": "Bewerk de werkstroom configuratie in JSON formaat. Wijzigingen worden gesynchroniseerd naar de visuele bouwer.", - "workflow_name": "Werkstroom naam", - "workflow_navigation_prompt": "Weet je zeker dat je weg wilt navigeren zonder je wijzigingen op te slaan?", - "workflow_summary": "Werkstroom samenvatting", - "workflow_update_success": "Werkstroom succesvol bijgewerkt", - "workflow_updated": "Werkstroom bijgewerkt", - "workflows": "Werkstromen", - "workflows_help_text": "Werkstromen automatiseren acties op je items gebaseerd op triggers en filters", - "wrong_pin_code": "Onjuiste pincode", - "year": "Jaar", - "years_ago": "{years, plural, one {# jaar} other {# jaar}} geleden", - "yes": "Ja", - "you_dont_have_any_shared_links": "Je hebt geen gedeelde links", - "your_wifi_name": "Je WiFi-naam", - "zero_to_clear_rating": "druk op 0 om de sterwaardering te verwijderen", - "zoom_image": "Inzoomen", - "zoom_to_bounds": "Zoom naar randen" -} +{} diff --git a/i18n/nn.json b/i18n/nn.json index 73a9d02c14..0967ef424b 100644 --- a/i18n/nn.json +++ b/i18n/nn.json @@ -1,622 +1 @@ -{ - "about": "Om", - "account": "Konto", - "account_settings": "Kontoinnstillingar", - "acknowledge": "Merk som lese", - "action": "Handling", - "action_common_update": "Oppdater", - "actions": "Handlingar", - "active": "Aktive", - "activity": "Aktivitet", - "activity_changed": "Aktivitet er {enabled, select, true {aktivert} other {deaktivert}}", - "add": "Legg til", - "add_a_description": "Legg til ei skildring", - "add_a_location": "Legg til ein stad", - "add_a_name": "Legg til eit namn", - "add_a_title": "Legg til ein tittel", - "add_birthday": "Legg til ein fødselsdag", - "add_endpoint": "Legg til endepunkt", - "add_exclusion_pattern": "Legg til unnlatingsmønster", - "add_location": "Legg til stad", - "add_more_users": "Legg til fleire brukarar", - "add_partner": "Legg til partnar", - "add_path": "Legg til sti", - "add_photos": "Legg til bilete", - "add_tag": "Legg til tagg", - "add_to": "Legg til…", - "add_to_album": "Legg til i album", - "add_to_album_bottom_sheet_added": "Lagt til i {album}", - "add_to_album_bottom_sheet_already_exists": "Allereie i {album}", - "add_to_albums": "Legg til i album", - "add_to_albums_count": "Legg til i album ({count})", - "add_to_shared_album": "Legg til i delt album", - "add_url": "Legg til URL", - "added_to_archive": "Lagt til i arkiv", - "added_to_favorites": "Lagt til i favorittar", - "added_to_favorites_count": "La til {count, number} i favorittar", - "admin": { - "add_exclusion_pattern_description": "Legg til utelatingsmønstre. Du kan bruke jokerteikna *, **, og ? for å finne filer som passar mønsteret. For å ignorere alle filer i ei mappe kalla \"Raw\", bruk \"Raw\", bruk \"**/Raw/**\". For å ignorere alle filer som sluttar på \".tif\", bruk \"**/*.tif\". For å ignorere ein absolutt sti, bruk \"/path/to/ignore/**\".", - "admin_user": "Admin-brukar", - "asset_offline_description": "Denne eksterne bibliotekressursen finst ikkje lenger på disk og har blitt flytta til papirkorga. Om fila blei flytta innad i biblioteket, sjekk tidslinja di for den tilsvarande ressursen. For å gjenopprette ressursen, vennligst sørg for at filstien under er tilgjengeleg for Immich og skann biblioteket.", - "authentication_settings": "Godkjenningsinnstillingar", - "authentication_settings_description": "Handsam passord, OAuth, og godkjenningsinnstillingar", - "authentication_settings_disable_all": "Er du sikker at du ynskjer å gjera alle innloggingsmetodar uverksame? Innlogging vil bli heilt uverksam.", - "authentication_settings_reenable": "For å aktivere på nytt, bruk ein Server Command.", - "background_task_job": "Bakgrunnsjobbar", - "backup_database": "Lag tryggingskopi av database", - "backup_database_enable_description": "Aktiver tryggingskopiering av database", - "backup_keep_last_amount": "Antal tryggingskopiar å behalde", - "backup_onboarding_1_description": "sikkerheitskopi i skya eller på eit anna fysisk sted.", - "backup_onboarding_2_description": "lokale kopiar på andre einingar. Dette inkluderer hovudfilene og backup av desse filene lokalt.", - "backup_onboarding_3_description": "fullstendige kopiar av dine data, inkludert originalfilene. Dette inkluderer 1 utomhus kopi og 2 lokale kopiar.", - "backup_onboarding_description": "Ein 3-2-1 backup-strategi tilrådast for å verne dataa dine. Du bør ha kopiar av dei opplasta bileta/videoane dine samt Immich-databasen, slik at du har ei fleirdelt backup-løysing.", - "backup_onboarding_footer": "Meir informasjon om å ta backup av Immich finn du i documentation.", - "backup_onboarding_parts_title": "3-2-1-backup består av:", - "backup_onboarding_title": "Backupar", - "backup_settings": "Tryggingskopi-innstillingar", - "backup_settings_description": "Handter innstillingar for tryggingskopiering av database. Merk: Desse jobbane vert ikkje overvaka, og du får inga varsling ved feil.", - "cleared_jobs": "Rydda jobbar for: {job}", - "config_set_by_file": "Oppsettet blir sett av ei oppsettfil", - "confirm_delete_library": "Er du sikker at du vil slette biblioteket {library}?", - "confirm_delete_library_assets": "Er du sikker at du vil slette dette biblioteket? Det kjem til å slette {count, plural, one {# contained asset} other {all # contained assets}} frå Immich og kan ikkje gjerast om. Filane blir verande på disken.", - "confirm_email_below": "For å bekrefte, skriv \"{email}\" under", - "confirm_reprocess_all_faces": "Er du sikker på at du vil behandle alle ansikt på nytt? Det vil òg fjerne namngjevne personar.", - "confirm_user_password_reset": "Er du sikker at du vil tilbakestille passordet til {user}?", - "confirm_user_pin_code_reset": "Er du sikker på at du vil tilbakestille {user} sin PIN-kode?", - "create_job": "Lag jobb", - "cron_expression": "Cron uttrykk", - "cron_expression_description": "Set inn skanningsintervall med cron-formatet. For meir informasjon sjå t.d. Crontab Guru", - "cron_expression_presets": "Førehandsinstillingar for Cron-uttrykk", - "disable_login": "Deaktiver innlogging", - "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage liknande bilete. Krev bruk av Smart Search", - "exclusion_pattern_description": "Utelatingsmønster let deg utelate filer og mapper når du skannar biblioteket ditt. Det er nyttig om du har mapper som inneheld filer du ikkje ynskjer å importere, til dømes RAW-filer.", - "face_detection": "Ansiktssøk", - "face_detection_description": "Finn ansikt i bilete ved hjelp av maskinlæring. For videoar vert berre miniatyrbilete bruka. \"Alle\" søkjer (opp att) gjennom alle bilete. \"Tilbakestill\" fjernar all gjeldande ansiktsdata. \"Manglande\" legg filer som ikkje vert behandla til i køa for ansiktssøk. Oppdaga ansikt vert lagt i køa for ansiktsattkjenning, og kopla til eksisterande eller nye personar.", - "facial_recognition_job_description": "Koplar attkjende ansikt til personar. Det skjer fyrst når anskiktssøkjet er ferdig. \"Tilbakestill\" fjernar alle koplingar til personar, og tilbakestiller ansiktsgrupper. \"Manglande\" legg ansikt som ikkje er oppkopla til i køa.", - "failed_job_command": "Kommandoen {command} feila for jobb: {job}", - "force_delete_user_warning": "ÅTVARING: Handlinga fjernar brukaren og all data. Du kan ikkje angre, og filane kan ikkje gjenopprettast.", - "image_format": "Format", - "image_format_description": "WebP gjev mindre filstorleik enn JPEG, men er treigare å lage.", - "image_fullsize_description": "Bilete i full storleik utan metadata, i bruk når zooma inn", - "image_fullsize_enabled": "Skru på generering av bilete i full storleik", - "image_fullsize_enabled_description": "Generer bilete i full storleik for ikkje web-tilpassa formatar. Når \"Foretrekk", - "image_fullsize_quality_description": "Kvalitet på bilete i full storleik frå 1-100. Høgare er betre, men gjev større filer.", - "image_fullsize_title": "Innstillingar for bilete i full storleik", - "image_prefer_embedded_preview": "Bruk helst innebygd førehandsvisning", - "image_prefer_embedded_preview_setting_description": "Når mogleg bruk innebygd førehandsvisning av RAW bilete som inndata til biletehandsaming. For noko bilete kan det gje meir nøyaktige farger, men kvaliteten kjem an på kamera og det kan oppstå komprimeringsartefakt i bilete.", - "image_prefer_wide_gamut": "Bruk helst breitt fargespektrum", - "image_prefer_wide_gamut_setting_description": "Bruk Display P3 for miniatyrbilete. For bilete med eit breitt fargerom tek det betre vare på ljosstyrke, men på einingar med gamal nettlesarversjon kan bilete sjå usamde ut. Beheld sRGB bilete som sRGB for å unnga fargeforskuvingar.", - "image_preview_description": "Mellomstore bilete utan metadata, bruka ved vising av ei enkelt fil og til maskinlæring", - "image_preview_quality_description": "Kvalitet på førehandsvising frå 1-100. Høgare tal gjev betre kvalitet, men gjev større filstorleik og kan senkje farta på systemet. Ved låge tal kan det påverkje kvaliteten på maskinlæringa.", - "image_preview_title": "Innstillingar for førehandsvisning", - "image_quality": "Kvalitet", - "image_resolution": "Oppløysing", - "image_resolution_description": "Høgare oppløysing inneheld meir detalj, men tek lengre tid å kode, gjev større filstorleik, og kan senkje appresponsen.", - "image_settings": "Innstillingar for bilete", - "image_settings_description": "Handsam kvalitet og oppløysing på framstilte bilete", - "image_thumbnail_description": "Lite miniatyrbilete med fjerna metadata, brukt når ein ser på grupper av bilete som hovudtidslinja", - "image_thumbnail_quality_description": "Kvalitet på miniatyrbilete frå 1-100. Høgare er betre, men gjev større filstorleik, og kan senkje appresposen.", - "image_thumbnail_title": "Innstillingar for miniatyrbilete", - "job_concurrency": "{job} samstundes utføring", - "job_created": "Jobb laga", - "job_not_concurrency_safe": "Kan ikke trygt utføre jobben samstundes.", - "job_settings": "Jobbinnstillingar", - "job_settings_description": "Handsam samstundes utføring av jobber", - "jobs_delayed": "{jobCount, plural, other {# forsinka}}", - "jobs_failed": "{jobCount, plural, other {# mislykkast}}", - "library_created": "Opprett bibliotek: {library}", - "library_deleted": "Bibliotek sletta", - "library_scanning": "Regelbunden skanning", - "library_scanning_description": "Sett opp regelbunden skanning av biblioteket", - "library_scanning_enable_description": "Aktiver regelbunden skanning av biblioteket", - "library_settings": "Eksternt Bibliotek", - "library_settings_description": "Handsam eksterne biblioteksinnstillingar", - "library_tasks_description": "Utfør bibliotekstoppgåver", - "library_watching_enable_description": "Sjekk eksterne bibliotek for forandringar", - "library_watching_settings": "Biblioteksovervåking (EKSPERIMENTELL)", - "library_watching_settings_description": "Sjekk automatisk for forandringar", - "logging_enable_description": "Aktiver loggføring", - "logging_level_description": "Når aktivert, kva loggnivå å bruke.", - "logging_settings": "Logging", - "machine_learning_availability_checks_description": "Automatiser oppdaging og prioritet av tilgjengelege maskinlærings-serverar", - "machine_learning_availability_checks_interval": "Sjekk intervall", - "machine_learning_availability_checks_timeout_description": "Utløpstid i millisekund for tilgjengelegheitssjekk", - "machine_learning_clip_model": "CLIP modell", - "machine_learning_clip_model_description": "Namnet på ein CLIP modell finst her. Merk at du må køyre 'Smart Søk'-jobben på nytt for alle bilete etter du har forandra modell.", - "machine_learning_duplicate_detection": "Duplikatdeteksjon", - "machine_learning_duplicate_detection_enabled": "Aktiver duplikatattkjenning", - "machine_learning_duplicate_detection_enabled_description": "Om uverksam, vil identiske filer framleis bli fjerna som duplikat.", - "machine_learning_duplicate_detection_setting_description": "Bruk CLIP-innkapslingar for å finne moglege duplikat", - "machine_learning_enabled": "Aktiver maskinlæring", - "machine_learning_enabled_description": "Om uverksam blir alle ML-funksjonar uverksame uavhengig av instillingane under.", - "machine_learning_facial_recognition": "Ansiktsgjenkjenning", - "machine_learning_facial_recognition_description": "Finn, kjenn att, og kople ansikt i bilete", - "machine_learning_facial_recognition_model": "Ansiktsattkjenningsmodell", - "machine_learning_facial_recognition_model_description": "Modellane er oppført i søkkjande rekkjefølge etter storleik. Større modellar er treigare og brukar meir minne, men gjev betre resultat. Om du forandrar modell lyt du køyre ansiktsattkjenning om att på alle bilete.", - "machine_learning_facial_recognition_setting": "Aktiver ansiktsattkjenning", - "machine_learning_facial_recognition_setting_description": "Om uverksam blir ikkje bilete koda for ansiktsattkjenning og dukkar ikkje opp i \"Personar\" i Utforsk-sida.", - "machine_learning_max_detection_distance": "Maksimal oppdagingsverdi", - "machine_learning_max_detection_distance_description": "Den største skilnaden mellom to bilete for å rekne dei som duplikat, frå 0.001-0.1. Større verdiar finn fleire duplikat, men kan gje falske treff.", - "machine_learning_max_recognition_distance": "Maksimal attkjenningsverdi", - "machine_learning_max_recognition_distance_description": "Maksimal forskjell på to ansikt for å bli rekna som same person, på ein skala frå 0-2. Eit lågare tal kan hindre to personar i å bli rekna som den same, medan eit høgare tal kan hindre at same individ vert merka som to forskjellige personar. Merk at det er lettare å slå saman to personar enn å dele éin person i to, så sikt mot den låge sida av skalaen når mogleg.", - "machine_learning_min_detection_score": "Minimum deteksjonsresultat", - "machine_learning_min_detection_score_description": "Minimum tillitspoeng for at eit ansikt skal bli oppdaga, på ein skala frå 0 til 1. Lågare verdiar vil oppdage fleire ansikt, men kan føre til feilaktige treff.", - "machine_learning_min_recognized_faces": "Minimum gjenkjende ansikt", - "machine_learning_min_recognized_faces_description": "Minste tal på gjenkjende fjes for å opprette ein person. Aukar ein dette, vert ansiktsgjenkjenninga meir presis, på bekostning av auka sjanse for at ansikt ikkje vert tileigna ein person.", - "machine_learning_settings": "Innstillingar for maskinlæring", - "machine_learning_settings_description": "Administrer maskinlæringsfunksjonar og innstillingar", - "machine_learning_smart_search": "Smart Søk", - "machine_learning_smart_search_enabled": "Aktiver smart søk", - "machine_learning_smart_search_enabled_description": "Hvis deaktivert, vil bilete ikkje bli enkoda for smart søk.", - "manage_concurrency": "Administrer samtidigheit", - "manage_log_settings": "Administrer logginnstillingar", - "map_dark_style": "Mørk modus", - "map_enable_description": "Aktiver kartfunksjonar", - "map_gps_settings": "Kart og GPS innstillingar", - "map_gps_settings_description": "Administrer innstillingar for kart og GPS (Reversert geokoding)", - "map_light_style": "Lys modus", - "map_settings": "Kart", - "map_settings_description": "Endre kartinnstillingar", - "map_style_description": "URL til eit style.json-karttema", - "memory_generate_job": "Minne-generering", - "metadata_extraction_job": "Hent ut metadata", - "metadata_extraction_job_description": "Hent ut metadata frå kvart bilete, slik som GPS, ansikt og oppløysing", - "metadata_faces_import_setting": "Skru på import av ansikt", - "metadata_faces_import_setting_description": "Importer ansikt frå bilete sine EXIF-data og sidecar-filer", - "metadata_settings": "Metadata Innstillinger", - "metadata_settings_description": "Endre metadata-innstillingar", - "migration_job": "Migrasjon", - "migration_job_description": "Overfør miniatyrbilete for bilete og ansikt til den nyaste mappestrukturen", - "nightly_tasks_cluster_faces_setting_description": "Køyr ansiktsgjenkjenning på nyleg identifiserte ansikt", - "nightly_tasks_database_cleanup_setting_description": "Fjern gamal, utgått data frå databasen", - "nightly_tasks_generate_memories_setting": "Generer minner", - "nightly_tasks_generate_memories_setting_description": "Lag nye minner frå bilete", - "nightly_tasks_missing_thumbnails_setting": "Generer manglande miniatyrbilete", - "nightly_tasks_missing_thumbnails_setting_description": "Set bilete utan miniatyrbilete i kø for generering av miniatyrbilete", - "nightly_tasks_settings": "Innstillingar for nattlege jobbar", - "nightly_tasks_settings_description": "Handsam nattlege jobbar", - "nightly_tasks_start_time_setting": "Starttid", - "nightly_tasks_start_time_setting_description": "Tidspunktet serveren køyrer nattlege jobbar", - "notification_email_from_address": "Frå adresse", - "notification_email_test_email_failed": "Mislukka sending av test-e-post, sjekk konfigurasjonen din", - "notification_email_test_email_sent": "Det vart sendt ei test-melding til {email}. Sjekk e-posten din.", - "notification_email_username_description": "Brukarnamn for autentisering på e-post-serveren", - "notification_enable_email_notifications": "Aktiver e-post-varslingar", - "notification_settings": "Varselinnstillingar", - "notification_settings_description": "Endre varslingsinnstillingar, inkludert e-post", - "oauth_auto_launch": "Autostart", - "oauth_auto_launch_description": "Start OAuth-innloggingsprosessen automatisk når innloggingssida vert opna", - "oauth_auto_register_description": "Registrer nye brukarar automatisk etter innlogging med OAuth", - "oauth_button_text": "Tekst på knapp", - "oauth_client_secret_description": "Krevjast dersom PKCE (Proof Key for Code Exchange) ikkje støttast av OAuth-tilbydaren", - "oauth_enable_description": "Logg inn med OAuth", - "oauth_settings": "OAuth", - "oauth_settings_description": "Innstillingar for innlogging med OAuth", - "oauth_storage_quota_default": "Standard lagringskvote (GiB)", - "oauth_timeout": "Tidsavbrot på førespurnad", - "oauth_timeout_description": "Tidsavbrot for førespurnadar i millisekund", - "password_enable_description": "Logg inn med e-post og passord", - "password_settings": "Passordinnlogging", - "password_settings_description": "Innstillingar for innlogging med passord", - "person_cleanup_job": "Personopprydding", - "quota_size_gib": "Lagringskvote (GiB)", - "refreshing_all_libraries": "Laster alle bibliotek opp att", - "registration": "Administrator registrering", - "registration_description": "Sidan du er den første brukaren på systemet, vil du bli utnevnt til administrator og ha ansvar for administrative oppgåver. Du vil òg opprette eventuelle nye brukarar.", - "require_password_change_on_login": "Krev at brukaren endrar passord ved første pålogging", - "reset_settings_to_default": "Tilbakestill innstillingar til standard", - "reset_settings_to_recent_saved": "Tilbakestill innstillingane til de nyleg lagra innstillingane", - "scanning_library": "Skann bibliotek", - "search_jobs": "Søk etter jobbar…", - "send_welcome_email": "Send velkomst-e-post", - "server_external_domain_settings": "Eksternt domene", - "server_external_domain_settings_description": "Domene for offentlege delingslenkjer, inkludert http(s)://", - "server_public_users": "Offentlege brukarar", - "server_public_users_description": "Alle brukarar (namn og epost) blir vist når ein brukar blir lagt til eit delt album. Når deaktivert, vil brukarane berre bli synlege for administratorar.", - "server_settings": "Serverinstillingar", - "server_settings_description": "Administrer serverinnstillingar", - "server_welcome_message": "Velkomstmelding", - "server_welcome_message_description": "Ei melding som synast på innloggingssida.", - "sidecar_job": "Sidecar-metadata", - "sidecar_job_description": "Oppdag eller synkroniser sidecar-metadata frå filsystemet", - "slideshow_duration_description": "Antal sekund å vise kvart bilete", - "storage_template_date_time_sample": "Døme på tid {date}", - "storage_template_enable_description": "Aktiver lagringsmal-motoren", - "storage_template_migration": "Overgang til ny lagringsmal", - "storage_template_migration_job": "Omorganisering etter ny lagringsmal", - "storage_template_settings": "Lagringsmal", - "system_settings": "Systeminnstillingar", - "template_email_preview": "Førehandsvisning", - "thumbnail_generation_job": "Generer miniatyrbilete", - "transcoding_acceleration_nvenc": "NVENC (Krev NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (Krev 7. generasjons Intel CPU eller nyare)", - "transcoding_acceleration_rkmpp": "RKMPP (Berre på Rockchip SOCer)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Tillatne lydkodekar", - "transcoding_accepted_audio_codecs_description": "Vel kva for lydkodekar som ikkje må omkodast. Blir berre bruka for noko omkodingsval.", - "transcoding_accepted_containers": "Tillatne behaldarar", - "transcoding_accepted_containers_description": "Vel kva for behaldarar som ikkje må omkodast til MP4. Blir berre bruka for nokon omkodingsval.", - "transcoding_accepted_video_codecs": "Tillatne videokodekar", - "transcoding_accepted_video_codecs_description": "Vel kva for videokodekar som ikkje må omkodast. Berre bruka for nokon omkodingsval.", - "transcoding_advanced_options_description": "Innstillingar dei fleste brukarar ikkje treng forandre på", - "transcoding_audio_codec": "Lydkodek", - "transcoding_audio_codec_description": "Opus er det valet med høgast lydkvalitet, men mindre kompabilitet med gamlare einingar og programvare.", - "transcoding_bitrate_description": "Videoar med bitrate over høgste tillatte verdi, eller i eit format som ikkje er tillate", - "transcoding_codecs_learn_more": "For å lære meir om nytta begrep, sjå FFmpeg dokumentasjon for H.264 codec, HEVC codec and VP9 codec.", - "transcoding_constant_rate_factor_description": "Videokvalitet. Vanlege verdiar er 23 for H.264, 28 for HEVC, 31 for VP9, og 35 for AV1. Lågare er betre, men gjev større filer.", - "transcoding_hardware_acceleration": "Maskinvare-akselerasjon", - "transcoding_max_bitrate": "Maksimal bitrate", - "transcoding_optimal_description": "Videoar med for høg oppløysing, eller ikkje i eit godkjend format" - }, - "admin_email": "Adminisrator E-post", - "admin_password": "Administratorpassord", - "administration": "Administrasjon", - "advanced": "Avansert", - "album_with_link_access": "Lat kven som helst med lenka sjå bilete og folk i dette albumet.", - "albums": "Album", - "all": "Alle", - "anti_clockwise": "Mot klokka", - "archive": "Arkiv", - "asset_skipped": "Hoppa over", - "asset_uploaded": "Opplasta", - "asset_uploading": "Lastar opp…", - "back": "Tilbake", - "backward": "Bakover", - "camera": "Kamera", - "cancel": "Avbryt", - "change_password_form_confirm_password": "Stadfest passord", - "city": "By", - "clear": "Tøm", - "clear_all": "Tøm alt", - "clear_all_recent_searches": "Tøm alle nylige søk", - "clear_message": "Tøm melding", - "clear_value": "Tøm verdi", - "client_cert_enter_password": "Oppgi passord", - "client_cert_import": "Importer", - "client_cert_import_success_msg": "Klientsertifikat vart importert", - "client_cert_invalid_msg": "Ugyldig sertifikatfil eller feil passord", - "client_cert_remove_msg": "Klientsertifikat er fjerna", - "client_cert_subtitle": "Støttar berre PKCS12-formatet (.p12, .pfx). Import og fjerning av sertifikat er berre tilgjengeleg før innlogging", - "client_cert_title": "SSL-klientsertifikat", - "clockwise": "Med klokka", - "close": "Lukk", - "collapse": "Gøym", - "collapse_all": "Gøym alle", - "color": "Farge", - "color_theme": "Fargetema", - "comment_deleted": "Kommentar vart sletta", - "comment_options": "Kommentarval", - "comments_and_likes": "Kommentarar og likerklikk", - "comments_are_disabled": "Kommentering er slått av", - "common_create_new_album": "Lag nytt album", - "completed": "Fullført", - "confirm": "Stadfest", - "confirm_admin_password": "Stadfest administratorpassord", - "confirm_delete_face": "Er du sikker på at du vil slette {name} sitt ansikt frå ressursen?", - "confirm_delete_shared_link": "Er du sikker på at du vil slette denne delte lenka?", - "confirm_keep_this_delete_others": "Alle andre ressursar i bunken vil bli sletta, bortsett frå denne. Er du sikker på at du vil halde fram?", - "confirm_password": "Stadfest passord", - "contain": "Tilpass til vindauget", - "context": "Samanheng", - "continue": "Hald fram", - "control_bottom_app_bar_create_new_album": "Lag nytt album", - "country": "Land", - "cover": "Dekk", - "covers": "Dekker", - "create": "Opprett", - "created": "Oppretta", - "dark": "Mørk", - "day": "Dag", - "delete": "Slett", - "description": "Beskrivelse", - "details": "Detaljer", - "direction": "Retning", - "discover": "Oppdag", - "display_original_photos": "Vis originale bilete", - "display_original_photos_setting_description": "Føretrekk å vise det originale biletet når ein ser på eit aktivum i staden for miniatyrbilete når det originale aktivumet er nettkompatibelt. Dette kan føre til tregare biletvisingshastigheiter.", - "documentation": "Dokumentasjon", - "done": "Ferdig", - "download": "Last ned", - "download_include_embedded_motion_videos_description": "Inkluder videoar innebygd i rørslefoto som ein eigen fil", - "download_settings": "Last ned", - "downloading": "Laster ned", - "duplicates": "Duplikat", - "duration": "Lengde", - "edit": "Rediger", - "editor": "Redigeringsverktøy", - "explore": "Utforsk", - "explorer": "Utforsker", - "folders_feature_description": "Bla gjennom mappe for bileta og videoane på filsystemet", - "hour": "Time", - "image": "Bilde", - "jobs": "Oppgåver", - "keep": "Behald", - "language": "Språk", - "latitude": "Lengdegrad", - "leave": "Forlat", - "level": "Nivå", - "library": "Bibliotek", - "light": "Lys", - "list": "Liste", - "loading": "Lastar", - "longitude": "Lengdegrad", - "look": "Utsjånad", - "make": "Produsent", - "map": "Kart", - "matches": "Treff", - "memories": "Minner", - "memory": "Minne", - "menu": "Meny", - "merge": "Slå saman", - "minimize": "Minimere", - "minute": "Minutt", - "missing": "Mangler", - "model": "Modell", - "month": "Månad", - "more": "Meir", - "name": "Namn", - "never": "Aldri", - "next": "Neste", - "no": "Nei", - "no_albums_message": "Lag eit album for å organisere bileta og videoane dine", - "no_archived_assets_message": "Arkiver bilder og videoar for å skjule dei frå bileta dine", - "no_explore_results_message": "Last opp fleire bilete for å utforske samlinga di.", - "no_libraries_message": "Lag eit eksternt bibliotek for å sjå bileta og videoane dine", - "no_shared_albums_message": "Lag eit album for å dele bilete og videoar med folk i nettverket ditt", - "notes": "Noter", - "notifications": "Varsel", - "options": "Val", - "or": "eller", - "other": "Anna", - "owner": "Eigar", - "partner_can_access_assets": "Alle bileta og videoane dine unntatt dei i Arkivert og Sletta", - "partner_can_access_location": "Staden der bileta dine vart tekne", - "password": "Passord", - "path": "Sti", - "pattern": "Mønster", - "paused": "Pausa", - "pending": "Ventar", - "people": "Folk", - "people_feature_description": "Bla gjennom foto og videoar gruppert etter folk", - "photo_shared_all_users": "Ser ut som du delte bileta dine med alle brukarar eller at du ikkje har nokon brukar å dele med.", - "photos": "Bilete", - "photos_and_videos": "Foto og Video", - "photos_from_previous_years": "Bilete frå tidlegare år", - "place": "Stad", - "places": "Stad", - "play": "Spel av", - "preview": "Førehandsvisning", - "previous": "Forrige", - "primary": "Hoved", - "privacy": "Personvern", - "purchase_button_activate": "Aktiver", - "purchase_button_buy": "Kjøp", - "purchase_button_select": "Vel", - "purchase_individual_title": "Induviduell", - "reassign": "Vel på nytt", - "recent": "Nyleg", - "refresh": "Last inn på nytt", - "refresh_encoded_videos": "Oppfrisk ferdigbehandla videoa", - "refresh_faces": "Oppfrisk ansikt", - "refresh_metadata": "Oppfrisk metadata", - "refresh_thumbnails": "Oppfrisk miniatyrbilete", - "refreshed": "Oppdatert", - "refreshes_every_file": "Les alle eksisterande og nye filer på nytt", - "refreshing_encoded_video": "Lastar inn ferdigbehandla video på nytt", - "refreshing_faces": "Oppfriskar ansiktsdata", - "refreshing_metadata": "Oppfriskar metadata", - "regenerating_thumbnails": "Regenererer miniatyrbilete", - "remove": "Fjern", - "remove_assets_album_confirmation": "Er du sikker på at du vil fjerne {count, plural, one {# asset} other {# assets}} fra albumet?", - "remove_assets_shared_link_confirmation": "Er du sikker på at du vil fjerne {count, plural, one {# asset} other {# assets}} frå denne delte lenka?", - "remove_assets_title": "Fjern ressursar?", - "remove_custom_date_range": "Fjern egendefinert datoperiode", - "remove_deleted_assets": "Fjern sletta ressursar", - "remove_from_album": "Fjern frå album", - "remove_from_favorites": "Fjern frå favorittar", - "remove_from_shared_link": "Fjern frå delt lenke", - "remove_memory": "Fjern minne", - "remove_photo_from_memory": "Fjern bilete frå dette minne", - "remove_url": "Fjern URL", - "remove_user": "Fjern brukar", - "removed_api_key": "Fjerna API-nøkkel: {name}", - "removed_from_archive": "Fjerna frå arkiv", - "removed_from_favorites": "Fjerna frå favorittar", - "removed_from_favorites_count": "{count, plural, other {Fjerna #}} frå favorittar", - "removed_memory": "Fjerna minne", - "removed_photo_from_memory": "Fjerna bilete frå minne", - "removed_tagged_assets": "Fjerna tagg frå {count, plural, one {# ressurs} other {# ressursar}}", - "rename": "Gi nytt namn", - "repair": "Reparer", - "repair_no_results_message": "Uspora og manglande filer vil visast her", - "replace_with_upload": "Erstatt med opplasting", - "repository": "Lager", - "require_password": "Krev passord", - "require_user_to_change_password_on_first_login": "Krev at brukaren endrar passord ved første innlogging", - "rescan": "Skann på nytt", - "reset": "Tilbakestill", - "reset_password": "Tilbakestill passord", - "reset_people_visibility": "Tilbakestill synlegheit for personar", - "reset_to_default": "Tilbakestill til standard", - "resolve_duplicates": "Handter duplikat", - "resolved_all_duplicates": "Alle duplikat er handterte", - "restore": "Gjenopprett", - "restore_all": "Gjenopprett alle", - "restore_user": "Gjenopprett brukar", - "restored_asset": "Ressurs gjenoppretta", - "resume": "Gjenoppta", - "retry_upload": "Prøv opplasting på nytt", - "review_duplicates": "Gå gjennom duplikat", - "role": "Rolle", - "role_editor": "Redaktør", - "role_viewer": "Observatør", - "save": "Lagre", - "save_to_gallery": "Lagre til galleri", - "saved_api_key": "API-nøkkel lagra", - "saved_profile": "Profil lagra", - "saved_settings": "Innstillingar lagra", - "say_something": "Skriv ein kommentar", - "scaffold_body_error_occurred": "Det oppstod ein feil", - "scan_all_libraries": "Skann gjennom alle bibliotek", - "scan_library": "Skann", - "scan_settings": "Skann innstillingar", - "scanning_for_album": "Skanning for album...", - "search": "Søk", - "search_albums": "Søk album", - "search_by_context": "Søk etter samanheng", - "search_by_description": "Søk etter beskrivelse", - "search_by_description_example": "Søndagstur med kvikklunsj", - "search_by_filename": "Søk etter filnamn eller filformat", - "search_by_filename_example": "t.d. IMG_1234.JPG eller PNG", - "search_camera_make": "Søk etter kamera produsent...", - "search_camera_model": "Søk etter kamera modell...", - "search_city": "Søk etter by...", - "search_country": "Søk etter land...", - "search_filter_apply": "Bruk filter", - "search_filter_camera_title": "Vel kameratype", - "search_filter_date": "Dato", - "search_filter_date_interval": "{start} til {end}", - "search_filter_date_title": "Vel eit datointervall", - "search_filter_display_option_not_in_album": "Ikkje i album", - "search_filter_display_options": "Visingsval", - "search_filter_filename": "Søk etter filnamn", - "search_filter_location": "Lokasjon", - "search_filter_location_title": "Vel lokasjon", - "search_filter_media_type": "Mediatype", - "search_filter_media_type_title": "Vel mediatype", - "search_filter_people_title": "Vel personar", - "search_for": "Søk etter", - "search_for_existing_person": "Søk etter ein eksisterande person", - "search_no_more_result": "Ingen fleire resultat", - "search_no_people": "Ingen personar", - "search_no_people_named": "Ingen personar ved namn \"{name}\"", - "search_no_result": "Fann ingen resultat – prøv eit anna søkjeord eller ei anna kombinasjon", - "search_options": "Søkjeval", - "search_page_categories": "Kategoriar", - "search_page_motion_photos": "Levande bilete", - "search_page_no_objects": "Ingen objektinformasjon tilgjengeleg", - "search_page_no_places": "Ingen stadinformasjon tilgjengeleg", - "search_page_screenshots": "Skjermbilete", - "search_page_search_photos_videos": "Søk etter bileta og videoane dine", - "search_page_selfies": "Sjølvbilete", - "search_page_things": "Ting", - "search_page_view_all_button": "Vis alle", - "search_page_your_activity": "Din aktivitet", - "search_page_your_map": "Ditt kart", - "search_people": "Søk etter personar", - "search_places": "Søk etter stad", - "search_rating": "Søk etter vurdering …", - "search_result_page_new_search_hint": "Nytt søk", - "search_settings": "Søkjeinnstillingar", - "search_state": "Søk etter fylke …", - "search_suggestion_list_smart_search_hint_1": "Smart søk er aktivert som standard. For å søkje etter metadata, bruk denne syntaksen: ", - "search_suggestion_list_smart_search_hint_2": "m:søkjeord", - "search_tags": "Søk etter taggar …", - "search_timezone": "Søk etter tidssone …", - "search_type": "Søketype", - "search_your_photos": "Søk i dine bilete", - "searching_locales": "Søkjer etter språkinnstillingar…", - "second": "Sekund", - "see_all_people": "Sjå alle personar", - "select": "Vel", - "select_album_cover": "Vel forsidebilete", - "select_all": "Vel alle", - "select_all_duplicates": "Vel alle duplikatar", - "select_avatar_color": "Vel avatarfarge", - "select_face": "Vel ansikt", - "select_featured_photo": "Vel framheva bilete", - "select_from_computer": "Vel frå datamaskin", - "select_keep_all": "Vel å behald alle", - "select_library_owner": "Vel bibliotekeigar", - "select_new_face": "Vel nytt ansikt", - "select_photos": "Vel bilete", - "select_trash_all": "Vel fjern alle", - "select_user_for_sharing_page_err_album": "Feil ved oppretting av album", - "selected": "Valgt", - "selected_count": "{count, plural, other {# valgt}}", - "send_message": "Send melding", - "send_welcome_email": "Send velkomst-e-post", - "server_endpoint": "Tenar-endepunkt", - "server_info_box_app_version": "App Versjon", - "server_info_box_server_url": "Tenar URL", - "server_offline": "Tenar Frakopla", - "server_online": "Tenar i drift", - "server_stats": "Tenarstatistikk", - "server_version": "Tenarversjon", - "set": "Sett", - "settings": "Innstillingar", - "share": "Del", - "shared": "Delt", - "shared_from_partner": "Bilete frå {partner}", - "sharing": "Deling", - "show_in_timeline_setting_description": "Vis bilete og videoar frå denne brukaren i tidslinja di", - "sidebar": "Sidefelt", - "size": "Størrelse", - "slideshow": "Lysbildeframvisning", - "sort_title": "Tittel", - "source": "Kjelde", - "stack": "Stabel", - "state": "Region", - "stop_photo_sharing": "Stopp å dele bileta dine?", - "stop_photo_sharing_description": "{partner} vil ikkje lenger kunne få tilgang til bileta dine.", - "stop_sharing_photos_with_user": "Stopp å dele bileta dine med denne brukaren", - "storage": "Lagringsplass", - "submit": "Send inn", - "suggestions": "Forslag", - "sync": "Synk", - "tag_feature_description": "Bla gjennom bilete og videoar gruppert etter logiske tag-tema", - "theme": "Tema", - "timeline": "Tidslinje", - "timezone": "Tidssone", - "to_archive": "Arkiv", - "to_favorite": "Favoritt", - "to_login": "Innlogging", - "to_trash": "Søppel", - "trash": "Søppel", - "trash_no_results_message": "Sletta foto og videoar vil dukke opp her.", - "unfavorite": "Fjern favoritt", - "unknown": "Ukjent", - "unlimited": "Ubegrensa", - "upload": "Last opp", - "upload_status_duplicates": "Duplikater", - "upload_status_errors": "Feil", - "upload_status_uploaded": "Opplasta", - "usage": "Bruk", - "user": "Brukar", - "user_purchase_settings": "Kjøp", - "user_usage_detail": "Detaljar av brukars forbruk", - "user_usage_stats": "Vis kontobruksstatistikk", - "user_usage_stats_description": "Vis kontobruksstatistikk", - "username": "Brukarnamn", - "users": "Brukarar", - "utilities": "Verktøy", - "validate": "Validere", - "variables": "Variablar", - "version": "Versjon", - "version_announcement_closing": "Din ven, Alex", - "version_history": "Versjonshistorie", - "version_history_item": "Installert {version} den {date}", - "video_hover_setting": "Spel av førehandsvisining medan du held over musepeikaren", - "videos": "Videoar", - "videos_count": "{count, plural, one {# Video} other {# Videoar}}", - "view": "Vis", - "view_album": "Sjå Album", - "view_all": "Sjå alle", - "view_all_users": "Sjå alle brukarar", - "view_in_timeline": "Sjå på tidslinja", - "view_links": "Vis lenkjer", - "view_name": "Vis", - "view_next_asset": "Vis neste fil", - "view_previous_asset": "Vis forrige fil", - "view_stack": "Syn stabel", - "visibility_changed": "Synlegheit forandra for {count, plural, one {# person} other {# personar}}", - "waiting": "Ventar", - "warning": "Advarsel", - "week": "Veke", - "welcome": "Velkomen", - "welcome_to_immich": "Velkomen til Immich", - "year": "År", - "years_ago": "{years, plural, one {# År} other {# År}} sidan", - "yes": "Ja", - "zoom_image": "Forstørr bilete" -} +{} diff --git a/i18n/pa.json b/i18n/pa.json index 52b1430134..0967ef424b 100644 --- a/i18n/pa.json +++ b/i18n/pa.json @@ -1,20 +1 @@ -{ - "about": "ਐਪ ਬਾਰੇ", - "account": "ਖ਼ਾਤਾ", - "account_settings": "ਖ਼ਾਤਾ ਸੈਟਿੰਗਾਂ", - "action": "ਕਾਰਵਾਈ", - "action_common_update": "ਅੱਪਡੇਟ", - "actions": "ਕਾਰਵਾਈਆਂ", - "active": "ਕਿਰਿਆਸ਼ੀਲ", - "activity": "ਗਤੀਵਿਧੀ", - "add": "ਸ਼ਾਮਲ ਕਰੋ", - "add_a_description": "ਵੇਰਵਾ ਸ਼ਾਮਲ ਕਰੋ", - "add_a_location": "ਇੱਕ ਸਥਾਨ ਸ਼ਾਮਲ ਕਰੋ", - "add_a_name": "ਨਾਮ ਸ਼ਾਮਲ ਕਰੋ", - "add_a_title": "ਸਿਰਲੇਖ ਸ਼ਾਮਲ ਕਰੋ", - "add_birthday": "ਜਨਮਦਿਨ ਸ਼ਾਮਲ ਕਰੋ", - "add_endpoint": "ਐਂਡਪੁਆਇੰਟ ਸ਼ਾਮਲ ਕਰੋ", - "add_exclusion_pattern": "ਅਲਹਿਦਗੀ ਪੈਟਰਨ ਸ਼ਾਮਲ ਕਰੋ", - "add_location": "ਸਥਾਨ ਸ਼ਾਮਲ ਕਰੋ", - "add_more_users": "ਹੋਰ ਉਪਭੋਗਤਾ ਸ਼ਾਮਲ ਕਰੋ" -} +{} diff --git a/i18n/pl.json b/i18n/pl.json index 37ea94d2c9..0967ef424b 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -1,2401 +1 @@ -{ - "about": "O aplikacji", - "account": "Konto", - "account_settings": "Ustawienia konta", - "acknowledge": "Zrozumiałem/łam", - "action": "Akcja", - "action_common_update": "Aktualizuj", - "action_description": "Zestaw akcji do wykonania na przefiltrowanych zasobach", - "actions": "Akcje", - "active": "Aktywne", - "active_count": "Aktywne: {count}", - "activity": "Aktywność", - "activity_changed": "Aktywność jest {enabled, select, true {włączona} other {wyłączona}}", - "add": "Dodaj", - "add_a_description": "Dodaj opis", - "add_a_location": "Dodaj lokalizację", - "add_a_name": "Dodaj nazwę", - "add_a_title": "Dodaj tytuł", - "add_action": "Dodaj akcję", - "add_action_description": "Kliknij, aby dodać akcję do wykonania", - "add_assets": "Dodaj zasoby", - "add_birthday": "Dodaj datę urodzin", - "add_endpoint": "Dodaj punkt końcowy", - "add_exclusion_pattern": "Dodaj wzór wykluczający", - "add_filter": "Dodaj filtr", - "add_filter_description": "Kliknij, aby dodać warunek filtrowania", - "add_location": "Dodaj lokalizację", - "add_more_users": "Dodaj więcej użytkowników", - "add_partner": "Dodaj partnera", - "add_path": "Dodaj ścieżkę", - "add_photos": "Dodaj zdjęcia", - "add_tag": "Dodaj tag", - "add_to": "Dodaj do…", - "add_to_album": "Dodaj do albumu", - "add_to_album_bottom_sheet_added": "Dodano do {album}", - "add_to_album_bottom_sheet_already_exists": "Już jest w {album}", - "add_to_album_bottom_sheet_some_local_assets": "Niektóre lokalne zasoby nie mogły zostać dodane do albumu", - "add_to_album_toggle": "Przełącz wybieranie dla {album}", - "add_to_albums": "Dodaj do albumów", - "add_to_albums_count": "Dodaj do albumów ({count})", - "add_to_bottom_bar": "Dodaj do", - "add_to_shared_album": "Dodaj do udostępnionego albumu", - "add_upload_to_stack": "Dodaj przesłane do stosu", - "add_url": "Dodaj URL", - "add_workflow_step": "Dodaj krok przepływu pracy", - "added_to_archive": "Dodano do archiwum", - "added_to_favorites": "Dodano do ulubionych", - "added_to_favorites_count": "Dodano {count, number} do ulubionych", - "admin": { - "add_exclusion_pattern_description": "Dodaj wzorce wykluczające. Wspierane są specjalne sekwencje (glob) *, ** oraz ?. Aby ignorować całą zawartość wszystkich folderów nazwanych \"Raw\", użyj \"**/Raw/**\". Aby ignorować wszystkie pliki kończące się na \".tif\", użyj \"**/*.tif\". Aby ignorować ścieżkę absolutną, użyj \"/ścieżka/do/ignorowania/**\".", - "admin_user": "Administrator", - "asset_offline_description": "Ten zewnętrzny zasób biblioteki nie jest już dostępny na dysku i został przeniesiony do kosza. Jeśli plik został przeniesiony w obrębie biblioteki, sprawdź swoją oś czasu pod kątem nowego odpowiadającego zasobu. Aby przywrócić ten zasób, upewnij się, że ścieżka pliku poniżej jest dostępna dla Immich i przeskanuj bibliotekę.", - "authentication_settings": "Ustawienia Uwierzytelnienia", - "authentication_settings_description": "Zarządzaj hasłem, OAuth i innymi ustawienia uwierzytelnienia", - "authentication_settings_disable_all": "Czy jesteś pewny, że chcesz wyłączyć wszystkie metody logowania? Logowanie będzie całkowicie wyłączone.", - "authentication_settings_reenable": "Aby ponownie włączyć, użyj Polecenia serwera.", - "background_task_job": "Zadania w tle", - "backup_database": "Utwórz Zrzut Bazy Danych", - "backup_database_enable_description": "Włącz zrzuty bazy danych", - "backup_keep_last_amount": "Ile poprzednich zrzutów przechowywać", - "backup_onboarding_1_description": "kopia offsite w chmurze lub w innej fizycznej lokalizacji.", - "backup_onboarding_2_description": "kopie lokalne na różnych urządzeniach. Obejmuje to główne pliki i lokalną kopię zapasową tych plików.", - "backup_onboarding_3_description": "wszystkie kopie danych, w tym oryginalne pliki. Obejmuje to 1 kopię zewnętrzną i 2 kopie lokalne.", - "backup_onboarding_description": "W celu ochrony danych zalecana jest strategia tworzenia kopii zapasowych 3-2-1. Powinieneś zachować kopie przesłanych zdjęć/filmów, a wraz z nimi bazę danych Immich, aby uzyskać kompleksowe rozwiązanie przy tworzeniu kopii zapasowych.", - "backup_onboarding_footer": "Więcej informacji na temat tworzenia kopii zapasowych Immich można znaleźć w dokumentacji.", - "backup_onboarding_parts_title": "Kopia zapasowa 3-2-1 obejmuje:", - "backup_onboarding_title": "Kopie zapasowe", - "backup_settings": "Ustawienia zrzutu bazy danych", - "backup_settings_description": "Zarządzanie ustawieniami zrzutu bazy danych.", - "cleared_jobs": "Usunięto zadania dla: {job}", - "config_set_by_file": "Konfiguracja pochodzi z pliku konfiguracyjnego", - "confirm_delete_library": "Czy na pewno chcesz usunąć bibliotekę {library}?", - "confirm_delete_library_assets": "Czy na pewno chcesz usunąć tę bibliotekę? Spowoduje to usunięcie {count, plural, one {# zawartego zasobu} other {wszystkich # zawartych zasobów}} z Immich i nie będzie możliwe cofnięcie tej operacji. Pliki pozostaną na dysku.", - "confirm_email_below": "Aby potwierdzić, wpisz \"{email}\" poniżej", - "confirm_reprocess_all_faces": "Czy na pewno chcesz ponownie przetworzyć wszystkie twarze? Spowoduje to utratę nazwanych osób.", - "confirm_user_password_reset": "Czy na pewno chcesz zresetować hasło użytkownika {user}?", - "confirm_user_pin_code_reset": "Czy jesteś pewny, że chcesz zresetować kod pin dla użytkownika {user}?", - "copy_config_to_clipboard_description": "Skopiuj do schowka bieżącą konfigurację systemu jako obiekt JSON", - "create_job": "Utwórz zadanie", - "cron_expression": "Wyrażenie Cron", - "cron_expression_description": "Ustaw interwał skanowania przy pomocy formatu Cron'a. Po więcej informacji na temat formatu Cron zobacz . Crontab Guru", - "cron_expression_presets": "Predefiniowane wyrażenia Cron'a", - "disable_login": "Wyłącz logowanie", - "duplicate_detection_job_description": "Włącz uczenie maszynowe na zasobie aby wykrywać podobne obrazy. Ta funkcja opiera się na inteligentnym wyszukiwaniu", - "exclusion_pattern_description": "Wzory wykluczające pozwalają na ignorowanie plików i folderów podczas skanowania Twojej biblioteki. Są one przydatne na przykład gdy nie chcesz importować zdjęć w formacie RAW.", - "export_config_as_json_description": "Pobierz aktualną konfigurację systemu jako plik JSON", - "external_libraries_page_description": "Strona administracyjna do zarządzania bibliotekami zewnętrznymi", - "face_detection": "Wykrywanie twarzy", - "face_detection_description": "Wykrywanie twarzy w zasobach używając uczenia maszynowego. Twarze w filmach wykryte zostaną tylko jeżeli są widoczne w miniaturze. \"Odśwież\" ponownie przetwarza wszystkie zasoby. \"Reset\" dodatkowo usuwa wszystkie bieżące dane twarzy. \"Brakujące\" dodaje do kolejki tylko zasoby, które nie zostały jeszcze przetworzone. Wykryte twarze zostaną dodane do kolejki Rozpoznawania Twarzy, aby związać je z istniejącą osobą albo stworzyć nową osobę.", - "facial_recognition_job_description": "Grupuj wykryte twarze. Ten krok uruchamiany jest po zakończeniu wykrywania twarzy. „Wszystkie” – ponownie kategoryzuje wszystkie twarze. „Brakujące” – kategoryzuje twarze, do których nie przypisano osoby.", - "failed_job_command": "Polecenie {command} nie powiodło się dla zadania: {job}", - "force_delete_user_warning": "UWAGA: Użytkownik i wszystkie zasoby użytkownika zostaną natychmiast trwale usunięte. Nie można tego cofnąć, a plików nie będzie można przywrócić.", - "image_format": "Format", - "image_format_description": "Użycie formatu WebP skutkuje utworzeniem plików o rozmiarze mniejszym niż w przypadku JPEG ale jego kodowanie trwa dłużej.", - "image_fullsize_description": "Pełnowymiarowy obraz z usuniętymi metadanymi, używany przy powiększeniu", - "image_fullsize_enabled": "Włącz generowanie obrazów o pełnym wymiarze", - "image_fullsize_enabled_description": "Generuje pełnowymiarowe obrazy dla formatów nieprzyjaznych stronom internetowym. Gdy opcja „Preferuj osadzony podgląd” jest włączona, osadzone podglądy są używane bezpośrednio bez konwersji. Nie wpływa na formaty przyjazne stronom internetowym, takie jak JPEG.", - "image_fullsize_quality_description": "Jakość pełnowymiarowego obrazu od 1 do 100. Wyższa jest lepsza, ale tworzy większe pliki.", - "image_fullsize_title": "Ustawienia pełnowymiarowego obrazu", - "image_prefer_embedded_preview": "Preferuj osadzony podgląd", - "image_prefer_embedded_preview_setting_description": "Jeśli to możliwe, używaj osadzonych podglądów w zdjęciach RAW jako danych wejściowych do przetwarzania obrazu. Może to zapewnić dokładniejsze kolory w przypadku niektórych obrazów, ale jakość podglądu zależy od aparatu, a obraz może zawierać więcej artefaktów kompresji.", - "image_prefer_wide_gamut": "Preferuj szeroką paletę barw", - "image_prefer_wide_gamut_setting_description": "Do wyświetlania miniatur użyj wyświetlacza P3. Dzięki temu lepiej zachowuje się intensywność obrazów o dużej ilości kolorów, ale obrazy mogą wyglądać inaczej na starych urządzeniach ze starą wersją przeglądarki. Obrazy sRGB są zachowywane jako sRGB, aby uniknąć przesunięć kolorów.", - "image_preview_description": "Obraz średniej wielkości z wyczyszczonymi metadanymi, używany podczas przeglądania pojedynczego zasobu i do uczenia maszynowego", - "image_preview_quality_description": "Jakość podglądu od 1 do 100. Wyższa jest lepsza, ale tworzy większe pliki i może spowolnić reakcję aplikacji. Ustawienie niskiej wartości może wpłynąć na jakość uczenia maszynowego.", - "image_preview_title": "Ustawienia podglądu", - "image_progressive": "Progresywny", - "image_progressive_description": "Koduj obrazy JPEG progresywnie, aby umożliwić stopniowe ładowanie i wyświetlanie. Nie ma to wpływu na obrazy WebP.", - "image_quality": "Jakość", - "image_resolution": "Rozdzielczość", - "image_resolution_description": "Wyższe rozdzielczości pozwalają zachować więcej szczegółów, ale wymagają dłuższego kodowania, mają większy rozmiar pliku i mogą spowalniać reakcję aplikacji.", - "image_settings": "Ustawienia Obrazu", - "image_settings_description": "Zarządzaj jakością i rozdzielczością generowanych obrazów", - "image_thumbnail_description": "Mała miniatura z wyczyszczonymi metadanymi, używana podczas przeglądania grup zdjęć, takich jak główna oś czasu", - "image_thumbnail_quality_description": "Jakość miniatur od 1 do 100. Wyższa jest lepsza, ale tworzy większe pliki i może spowolnić reakcję aplikacji.", - "image_thumbnail_title": "Ustawienia miniatur", - "import_config_from_json_description": "Zaimportuj konfigurację systemu poprzez wczytanie pliku konfiguracyjnego JSON", - "job_concurrency": "{job} współbieżność", - "job_created": "Zadanie utworzone", - "job_not_concurrency_safe": "To zadanie nie może zostać wykonane w wielu wątkach.", - "job_settings": "Ustawienia Zadań", - "job_settings_description": "Zarządzaj współbieżnością zadań", - "jobs_delayed": "{jobCount, plural, one {# oczekujący} few {# oczekujące} other {# oczekujących}}", - "jobs_failed": "{jobCount, plural, one {# nieudany} few {# nieudane} other {# nieudanych}}", - "jobs_over_time": "Zadania na przestrzeni czasu", - "library_created": "Utworzono bibliotekę: {library}", - "library_deleted": "Biblioteka usunięta", - "library_details": "Szczegóły biblioteki", - "library_folder_description": "Wskaż folder do zaimportowania. Ten folder, wraz z podfolderami, zostanie przeskanowany w poszukiwaniu obrazów i filmów.", - "library_remove_exclusion_pattern_prompt": "Czy na pewno chcesz usunąć ten szablon wykluczeń?", - "library_remove_folder_prompt": "Czy na pewno chcesz usunąć ten folder importu?", - "library_scanning": "Okresowe Skanowanie", - "library_scanning_description": "Skonfiguruj okresowe skanowania bibliotek", - "library_scanning_enable_description": "Włącz okresowe skanowanie bibliotek", - "library_settings": "Zewnętrzne Biblioteki", - "library_settings_description": "Zarządzaj ustawieniami zewnętrznych bibliotek", - "library_tasks_description": "Wyszukiwanie nowych lub zmienionych pozycji w zewnętrznych bibliotekach", - "library_updated": "Zaktualizowana biblioteka", - "library_watching_enable_description": "Przejrzyj zewnętrzne biblioteki w poszukiwaniu zmienionych plików", - "library_watching_settings": "Obserwowanie bibliotek [EKSPERYMENTALNE]", - "library_watching_settings_description": "Automatycznie obserwuj zmienione pliki", - "logging_enable_description": "Uruchom zapisywanie logów", - "logging_level_description": "Kiedy włączone, jakiego poziomu użyć.", - "logging_settings": "Rejestrowanie logów", - "machine_learning_availability_checks": "Sprawdzanie dostępności", - "machine_learning_availability_checks_description": "Automatyczne wykrywaj i preferuj dostępne serwery uczenia maszynowego", - "machine_learning_availability_checks_enabled": "Włącz sprawdzanie dostępności", - "machine_learning_availability_checks_interval": "Częstotliwość sprawdzania", - "machine_learning_availability_checks_interval_description": "Odstęp czasu w milisekundach między sprawdzeniami dostępności", - "machine_learning_availability_checks_timeout": "Upłynął czas żądania", - "machine_learning_availability_checks_timeout_description": "Limit czasu żądania w milisekundach dla sprawdzania dostępności", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Nazwa modelu CLIP jest wymieniona tutaj. Zwróć uwagę, że po zmianie modelu musisz ponownie uruchomić zadanie 'Smart Search' dla wszystkich obrazów.", - "machine_learning_duplicate_detection": "Wykrywanie Duplikatów", - "machine_learning_duplicate_detection_enabled": "Włącz wykrywanie duplikatów", - "machine_learning_duplicate_detection_enabled_description": "Jeśli ta opcja jest wyłączona, duplikaty 1:1 nadal będą usuwane z zasobów.", - "machine_learning_duplicate_detection_setting_description": "Używaj CLIP by znajdywać duplikaty", - "machine_learning_enabled": "Włącz uczenie maszynowe", - "machine_learning_enabled_description": "Jeśli wyłączone, wszystkie funkcje uczenia maszynowego zostaną wyłączone niezależnie od poniższych ustawień.", - "machine_learning_facial_recognition": "Rozpoznawanie Twarzy", - "machine_learning_facial_recognition_description": "Znajduje, rozpoznaje i grupuje twarze na zdjęciach", - "machine_learning_facial_recognition_model": "Model rozpoznawania twarzy", - "machine_learning_facial_recognition_model_description": "Modele są wymienione według rozmiaru, w kolejności malejącej. Większe modele są wolniejsze i potrzebują więcej pamięci, ale dają lepsze wyniki. Pamiętaj, że po zmianie modelu musisz ponownie uruchomić zadanie wykrywania twarzy dla wszystkich zdjęć.", - "machine_learning_facial_recognition_setting": "Włącz rozpoznawanie twarzy", - "machine_learning_facial_recognition_setting_description": "Jeśli ta opcja jest wyłączona, obrazy nie będą kodowane na potrzeby rozpoznawania twarzy i nie będą wyświetlane w sekcji Osoby na stronie Przeglądaj.", - "machine_learning_max_detection_distance": "Maksymalny dystans detekcji", - "machine_learning_max_detection_distance_description": "Maksymalna odległość między dwoma obrazami, aby uznać je za duplikaty, w zakresie od 0,001-0,1. Wyższe wartości wykryją więcej duplikatów, ale mogą skutkować błędnymi grupowaniami.", - "machine_learning_max_recognition_distance": "Maksymalny dystans rozpoznania", - "machine_learning_max_recognition_distance_description": "Maksymalna odległość między dwiema twarzami, którą należy uznać za tę samą osobę, waha się od 0-2. Obniżenie tej wartości może zapobiec grupowaniu dwóch osób jako tej samej osoby. Pamiętaj, że łatwiej jest połączyć dwie osoby niż podzielić jedną osobę na dwie, więc jeśli to możliwe, staraj się ustawić jak najmniejszą wartość, która będzie spełniała twoje wymagania.", - "machine_learning_min_detection_score": "Minimalny wskaźnik wykrywalności", - "machine_learning_min_detection_score_description": "Minimalny poziom uznania twarzy za twarz. Wartość mieści się w zakresie 0-1. Niższe wartości pozwolą wykryć więcej twarzy, ale mogą skutkować znajdywaniem twarz tam, gdzie ich nie ma.", - "machine_learning_min_recognized_faces": "Minimum rozpoznanych twarzy", - "machine_learning_min_recognized_faces_description": "Minimalna liczba rozpoznanych twarzy, zanim zostaną one powiązane jako osoba. Zwiększenie tej wartości spowoduje, że rozpoznawanie twarzy jest bardziej precyzyjne, lecz kosztem zwiększenia ryzyka, że twarz nie zostanie przypisana do jakiejkolwiek osoby.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Wykorzystaj uczenie maszynowe do rozpoznawania tekstu na zdjęciach", - "machine_learning_ocr_enabled": "Włącz OCR", - "machine_learning_ocr_enabled_description": "Jeśli opcja jest wyłączona, obrazy nie będą poddawane rozpoznawaniu tekstu.", - "machine_learning_ocr_max_resolution": "Maksymalna rozdzielczość", - "machine_learning_ocr_max_resolution_description": "Podglądy powyżej tej rozdzielczości zostaną przeskalowane z zachowaniem proporcji. Wyższe wartości są dokładniejsze, ale ich przetwarzanie trwa dłużej i zajmuje więcej pamięci.", - "machine_learning_ocr_min_detection_score": "Minimalny wskaźnik wykrywalności", - "machine_learning_ocr_min_detection_score_description": "Minimalny wskaźnik pewności, aby tekst został wykryty, w zakresie 0-1. Niższe wartości pozwolą wykryć więcej tekstu, ale mogą skutkować wynikami fałszywie dodatnimi.", - "machine_learning_ocr_min_recognition_score": "Minimalny wskaźnik rozpoznawalności", - "machine_learning_ocr_min_score_recognition_description": "Minimalny wskaźnik pewności dla wykrytego tekstu, aby został rozpoznany, w zakresie 0-1. Niższe wartości rozpoznają więcej tekstu, ale mogą skutkować wynikami fałszywie dodatnimi.", - "machine_learning_ocr_model": "Model OCR", - "machine_learning_ocr_model_description": "Modele serwerowe są dokładniejsze niż modele mobilne, ale dłużej przetwarzają dane i zużywają więcej pamięci.", - "machine_learning_settings": "Ustawienia Uczenia Maszynowego", - "machine_learning_settings_description": "Zarządzaj ustawieniami i funkcjami uczenia maszynowego", - "machine_learning_smart_search": "Inteligentne Wyszukiwanie", - "machine_learning_smart_search_description": "Szukaj obrazów semantycznie za pomocą CLIP", - "machine_learning_smart_search_enabled": "Włącz inteligentne wyszukiwanie", - "machine_learning_smart_search_enabled_description": "Jeżeli wyłączone, obrazy nie będą przygotowywane do inteligentnego wyszukiwania.", - "machine_learning_url_description": "URL serwera uczenia maszynowego. Jeżeli podano więcej niż jeden URL, do każdego serwera po kolei będzie wysłane żądanie dopóki chociaż jeden nie odpowie, w kolejności od pierwszego do ostatniego. Serwery które nie odpowiedzą, zostaną tymczasowo ignorowane aż do momentu ich przejścia w stan online.", - "maintenance_delete_backup": "Usuń kopię zapasową", - "maintenance_delete_backup_description": "Ten plik zostanie nieodwracalnie usunięty.", - "maintenance_delete_error": "Nie udało się usunąć kopii zapasowej.", - "maintenance_restore_backup": "Przywróć kopię zapasową", - "maintenance_restore_backup_description": "Immich zostanie wyczyszczony i przywrócony z wybranej kopii zapasowej. Przed rozpoczęciem operacji zostanie utworzona kopia zapasowa.", - "maintenance_restore_backup_different_version": "Ta kopia zapasowa została utworzona przy użyciu innej wersji Immich!", - "maintenance_restore_backup_unknown_version": "Nie można określić wersji kopii zapasowej.", - "maintenance_restore_database_backup": "Przywróć kopię zapasową bazy danych", - "maintenance_restore_database_backup_description": "Powrót do poprzedniego stanu bazy danych przy użyciu pliku kopii zapasowej", - "maintenance_settings": "Konserwacja", - "maintenance_settings_description": "Przełącza Immich w tryb konserwacji.", - "maintenance_start": "Przełącz na tryb konserwacji", - "maintenance_start_error": "Nie udało się uruchomić trybu konserwacji.", - "maintenance_upload_backup": "Prześlij plik kopii zapasowej bazy danych", - "maintenance_upload_backup_error": "Nie można przesłać kopii zapasowej. Czy jest to plik .sql/.sql.gz?", - "manage_concurrency": "Zarządzaj współbieżnością zadań", - "manage_concurrency_description": "Przejdź do strony zadań, aby zarządzać współbieżnością zadań", - "manage_log_settings": "Zarządzaj ustawieniami logów", - "map_dark_style": "Styl ciemny", - "map_enable_description": "Włącz funkcję mapy", - "map_gps_settings": "Mapa i ustawienia lokalizacji", - "map_gps_settings_description": "Zarządzaj mapą oraz ustawieniami odwróconego geokodowania", - "map_implications": "Funkcja mapy opiera się na zewnętrznej usłudze kafelków (tiles.immich.cloud)", - "map_light_style": "Styl jasny", - "map_manage_reverse_geocoding_settings": "Zarządzaj Ustawieniem Odwrotne Geokodowanie", - "map_reverse_geocoding": "Odwrotne Geokodowanie", - "map_reverse_geocoding_enable_description": "Włącz odwrotne geokodowanie", - "map_reverse_geocoding_settings": "Ustawienia odwrotnego geokodowania", - "map_settings": "Ustawienia Mapy", - "map_settings_description": "Zarządzaj ustawieniami mapy", - "map_style_description": "URL do pliku style.json z motywem mapy", - "memory_cleanup_job": "Czyszczenie wspomnień", - "memory_generate_job": "Generowanie wspomnień", - "metadata_extraction_job": "Wyodrębnij metadane", - "metadata_extraction_job_description": "Wyodrębnij informacje o metadanych z każdego zasobu, takie jak GPS, twarze i rozdzielczość", - "metadata_faces_import_setting": "Włącz import twarzy", - "metadata_faces_import_setting_description": "Zaimportuj twarze z danych EXIF obrazu i plików towarzyszących", - "metadata_settings": "Ustawienia Metadanych", - "metadata_settings_description": "Zarządzaj ustawieniami metadanych", - "migration_job": "Migracja", - "migration_job_description": "Przenieś miniatury zasobów i twarzy do najnowszej struktury folderów", - "nightly_tasks_cluster_faces_setting_description": "Uruchom rozpoznawanie twarzy dla nowo wykrytych twarzy", - "nightly_tasks_cluster_new_faces_setting": "Zgrupuj nowe twarze", - "nightly_tasks_database_cleanup_setting": "Zadania związane z czyszczeniem bazy danych", - "nightly_tasks_database_cleanup_setting_description": "Wyczyść stare, nieaktualne dane z bazy danych", - "nightly_tasks_generate_memories_setting": "Generuj wspomnienia", - "nightly_tasks_generate_memories_setting_description": "Stwórz nowe wspomnienia z zasobów", - "nightly_tasks_missing_thumbnails_setting": "Wygeneruj brakujące miniatury", - "nightly_tasks_missing_thumbnails_setting_description": "Dodaj zasoby bez miniatur do kolejki generowania miniatur", - "nightly_tasks_settings": "Ustawienia nocnych zadań", - "nightly_tasks_settings_description": "Zarządzaj zadaniami wykonywanymi w nocy", - "nightly_tasks_start_time_setting": "Czas rozpoczęcia", - "nightly_tasks_start_time_setting_description": "Czas, w którym serwer rozpoczyna wykonywanie nocnych zadań", - "nightly_tasks_sync_quota_usage_setting": "Zsynchronizuj wykorzystanie kontyngentu", - "nightly_tasks_sync_quota_usage_setting_description": "Zaktualizuj kontyngent przestrzeni dyskowej użytkownika na podstawie aktualnego zużycia", - "no_paths_added": "Nie dodano ścieżki", - "no_pattern_added": "Nie dodano wzoru", - "note_apply_storage_label_previous_assets": "Uwaga: aby zastosować etykietę magazynu do wcześniej przesłanych zasobów, uruchom", - "note_cannot_be_changed_later": "UWAŻAJ: Nie można tego później zmienić!", - "notification_email_from_address": "Z adresu", - "notification_email_from_address_description": "Adres e-mail nadawcy, na przykład: „Immich Photo Server ”. Upewnij się, że używasz adresu z którego masz prawo wysyłać wiadomości.", - "notification_email_host_description": "Host serwera e-mail (np. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignoruj niepoprawny certyfikat", - "notification_email_ignore_certificate_errors_description": "Ignoruj błąd walidacji certyfikatu TLS (nie zalecane)", - "notification_email_password_description": "Hasło do serwera poczty", - "notification_email_port_description": "Port serwera poczty (np. 25, 465 lub 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Użyj SMTPS (SMTP over TLS)", - "notification_email_sent_test_email_button": "Wyślij testowego maila i zapisz", - "notification_email_setting_description": "Ustawienia powiadomień e-mail", - "notification_email_test_email": "Wyślij e-mail testowy", - "notification_email_test_email_failed": "Nie udało się wysłać testowego e-maila, sprawdź poprawność wprowadzonych danych", - "notification_email_test_email_sent": "Testowy mail został wysłany na adres {email}. Sprawdź swoją skrzynkę mailową.", - "notification_email_username_description": "Nazwa użytkownika serwera e-mail", - "notification_enable_email_notifications": "Włącz powiadomienia e-mail", - "notification_settings": "Ustawienia Powiadomień", - "notification_settings_description": "Zarządzaj ustawieniami powiadomień, włączając w to powiadomienia e-mail", - "oauth_auto_launch": "Automatyczne Uruchamianie", - "oauth_auto_launch_description": "Rozpocznij proces logowania OAuth automatycznie po przejściu na stronę logowania", - "oauth_auto_register": "Automatyczna rejestracja", - "oauth_auto_register_description": "Automatycznie rejestruj nowych użytkowników po zalogowaniu się za pomocą protokołu OAuth", - "oauth_button_text": "Tekst na przycisku", - "oauth_client_secret_description": "Wymagane dla poufnego klienta lub jeśli PKCE (Proof Key for Code Exchange) nie jest obsługiwane dla klienta publicznego.", - "oauth_enable_description": "Loguj się za pomocą OAuth", - "oauth_mobile_redirect_uri": "Mobilny adres zwrotny", - "oauth_mobile_redirect_uri_override": "Zapasowy URI przekierowania mobilnego", - "oauth_mobile_redirect_uri_override_description": "Włącz, gdy dostawca OAuth nie pozwala na mobilne identyfikatory URI typu ''{callback}''", - "oauth_role_claim": "Oświadczenie roli", - "oauth_role_claim_description": "Automatycznie przyznaj dostęp administratora na podstawie obecności tego oświadczenia. Oświadczenie może mieć wartość „użytkownik” lub „administrator”.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Zarządzaj ustawieniami logowania OAuth", - "oauth_settings_more_details": "Więcej informacji o tej funkcji znajdziesz w dokumentacji.", - "oauth_storage_label_claim": "Roszczenie dotyczące etykiety przechowywania", - "oauth_storage_label_claim_description": "Automatycznie ustaw etykietę przechowywania użytkownika na podaną niżej wartość.", - "oauth_storage_quota_claim": "Ilość miejsca w magazynie", - "oauth_storage_quota_claim_description": "Automatycznie ustaw ilość miejsca w magazynie na podaną niżej wartość.", - "oauth_storage_quota_default": "Domyślna ilość miejsca w magazynie (GiB)", - "oauth_storage_quota_default_description": "Limit w GiB do wykorzystania, gdy nie podano żadnej wartości.", - "oauth_timeout": "Upłynął czas żądania", - "oauth_timeout_description": "Limit czasu żądania w milisekundach", - "ocr_job_description": "Wykorzystaj uczenie maszynowe do rozpoznawania tekstu na zdjęciach", - "password_enable_description": "Zaloguj używając e-mail i hasła", - "password_settings": "Logowanie Hasłem", - "password_settings_description": "Zarządzaj ustawieniami logowania hasłem", - "paths_validated_successfully": "Wszystkie ścieżki zostały pomyślnie zweryfikowane", - "person_cleanup_job": "Porządkowanie osób", - "queue_details": "Szczegóły kolejki", - "queues": "Kolejki zadań", - "queues_page_description": "Strona administracyjna do zarządzania kolejkami zadań", - "quota_size_gib": "Wielkość Magazynu (GiB)", - "refreshing_all_libraries": "Wszystkie biblioteki zostaną odświeżone", - "registration": "Rejestracja Administratora", - "registration_description": "Jesteś pierwszym użytkownikiem aplikacji, więc twoje konto jest administratorem. Możesz zarządzać platformą, w tym dodawać nowych użytkowników.", - "remove_failed_jobs": "Usuń nieudane zadania", - "require_password_change_on_login": "Wymagaj zmiany hasła po pierwszym zalogowaniu", - "reset_settings_to_default": "Przywróć ustawienia fabryczne", - "reset_settings_to_recent_saved": "Przywróć ustawienia do ostatnio zapisanych", - "scanning_library": "Skanowanie biblioteki", - "search_jobs": "Zadania przeszukiwania…", - "send_welcome_email": "Wyślij powitalny e-mail", - "server_external_domain_settings": "Domena zewnętrzna", - "server_external_domain_settings_description": "Domena dla publicznie udostępnionych linków, wraz z http(s)://", - "server_public_users": "Użytkownicy publiczni", - "server_public_users_description": "Wszyscy użytkownicy (nazwa i adres e-mail) są wymienieni podczas dodawania użytkownika do udostępnionych albumów. Po wyłączeniu lista użytkowników będzie dostępna tylko dla administratorów.", - "server_settings": "Ustawienia Serwera", - "server_settings_description": "Zarządzaj ustawieniami serwera", - "server_stats_page_description": "Strona administracyjna ze statystykami serwera", - "server_welcome_message": "Wiadomość powitalna", - "server_welcome_message_description": "Wiadomość, która jest wyświetlania na stronie logowania.", - "settings_page_description": "Strona administracyjna z ustawieniami", - "sidecar_job": "Poboczne metadane", - "sidecar_job_description": "Wykryj lub zsynchronizuj poboczne metadane z systemu plików", - "slideshow_duration_description": "Liczba sekund wyświetlania każdego obrazu", - "smart_search_job_description": "Włącz uczenie maszynowe na zasobie by wesprzeć inteligentne wyszukiwanie", - "storage_template_date_time_description": "Data i godzina są brane ze znacznika czasu utworzenia zasobu", - "storage_template_date_time_sample": "Przykładowy czas {date}", - "storage_template_enable_description": "Włącz silnik szablonów magazynu", - "storage_template_hash_verification_enabled": "Weryfikacja hashu włączona", - "storage_template_hash_verification_enabled_description": "Włącza weryfikację sumy kontrolnej. Nie wyłączaj tej opcji, jeśli nie jesteś pewien konsekwencji", - "storage_template_migration": "Migracja szablonu magazynu", - "storage_template_migration_description": "Zastosuj aktualny szablon {template} do wcześniej przesłanych zasobów", - "storage_template_migration_info": "Szablon Magazynu przekonwertuje wszystkie rozszerzenia na pisane małą literą. Zmiany w szablonie zostaną zastosowane tylko do nowych zasobów. Aby wstecznie zastosować szablon do wcześniej przesłanych zasobów, uruchom zadanie {job}.", - "storage_template_migration_job": "Zadanie migracji szablonu przechowywania", - "storage_template_more_details": "Aby uzyskać więcej szczegółów na temat tej funkcji, odwiedź Szablon Przechowywania oraz jego implikacje", - "storage_template_onboarding_description_v2": "Po włączeniu ta funkcja automatycznie organizuje pliki w oparciu o zdefiniowany przez użytkownika szablon. Więcej informacji można znaleźć w dokumentacji.", - "storage_template_path_length": "Przybliżony limit długości ścieżki: {length, number}/{limit, number}", - "storage_template_settings": "Szablon Magazynu", - "storage_template_settings_description": "Zarządzaj strukturą folderów i nazwą pliku przesyłanego zasobu", - "storage_template_user_label": "{label} to jest etykieta przechowywania użytkownika", - "system_settings": "Ustawienia Systemowe", - "tag_cleanup_job": "Porządkowanie etykiet", - "template_email_available_tags": "Możesz użyć tych zmiennych w swoim szablonie: {tags}", - "template_email_if_empty": "Zostaw puste, aby użyć domyślny adres e-mail.", - "template_email_invite_album": "Szablon zaproszenia do albumu", - "template_email_preview": "Podgląd", - "template_email_settings": "Szablony e-mail", - "template_email_update_album": "Szablon aktualizacji albumu", - "template_email_welcome": "Szablon powitalnego e-mail", - "template_settings": "Szablony Powiadomień", - "template_settings_description": "Zarządzaj niestandardowymi szablonami powiadomień e-mail", - "theme_custom_css_settings": "Własny CSS", - "theme_custom_css_settings_description": "Własny CSS pozwala na zmianę wyglądu aplikacji Immich.", - "theme_settings": "Ustawienia Motywu", - "theme_settings_description": "Zarządzaj wyglądem aplikacji Immich w przeglądarce", - "thumbnail_generation_job": "Stwórz Miniaturki", - "thumbnail_generation_job_description": "Generuj duże, małe i rozmyte miniatury dla każdego zasobu, a także miniatury dla każdej osoby", - "transcoding_acceleration_api": "API akceleracji", - "transcoding_acceleration_api_description": "Interfejs API, używany w celu przyspieszenia transkodowania. W przypadku niepowodzenia zostanie użyte transkodowanie programowe. Format VP9 może, ale nie musi, działać w zależności od sprzętu.", - "transcoding_acceleration_nvenc": "NVENC (wymaga NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (wymaga 7th gen Intel CPU lub nowszej)", - "transcoding_acceleration_rkmpp": "RKMPP (tylko na Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Akceptowane kodeki audio", - "transcoding_accepted_audio_codecs_description": "Wybierz, które kodeki audio nie muszą być transkodowane. Używane tylko w przypadku niektórych zasad transkodowania.", - "transcoding_accepted_containers": "Akceptowalne kontenery", - "transcoding_accepted_containers_description": "Wybierz które formaty kontenera nie muszą zostać przerobione na MP4. Użyte tylko w wybranych zasadach transkodowania.", - "transcoding_accepted_video_codecs": "Akceptowane kodeki wideo", - "transcoding_accepted_video_codecs_description": "Wybierz, które kodeki wideo nie muszą być transkodowane. Używane tylko w przypadku niektórych zasad transkodowania.", - "transcoding_advanced_options_description": "Opcje, których większość użytkowników nie powinna zmieniać", - "transcoding_audio_codec": "Kodek dźwięku", - "transcoding_audio_codec_description": "Opus to opcja najwyższej jakości, ale ma mniejszą kompatybilność ze starymi urządzeniami lub oprogramowaniem.", - "transcoding_bitrate_description": "Filmy wideo o wyższej szybkości transmisji lub w nieakceptowanym formacie", - "transcoding_codecs_learn_more": "Więcej informacji o terminologii tu użytej znajdziesz w dokumentacji FFmpeg dla kodeków H.264, HEVC oraz VP9.", - "transcoding_constant_quality_mode": "Tryb stałej jakości", - "transcoding_constant_quality_mode_description": "ICQ jest lepszy niż CQP, ale niektóre urządzenia nie obsługują tego trybu. Ustawienie tej opcji spowoduje korzystanie z określonego trybu w przypadku używania kodowania opartego na jakości. Ignorowany przez NVENC, ponieważ nie obsługuje ICQ.", - "transcoding_constant_rate_factor": "Stały współczynnik szybkości (-crf)", - "transcoding_constant_rate_factor_description": "Poziom jakości wideo. Typowe wartości to 23 dla H.264, 28 dla HEVC, 31 dla VP9 i 35 dla AV1. Niższa jakość jest lepsza, ale tworzy większe pliki.", - "transcoding_disabled_description": "Nie transkoduj żadnych filmów, może to spowodować przerwanie odtwarzania na niektórych klientach", - "transcoding_encoding_options": "Opcje kodowania", - "transcoding_encoding_options_description": "Ustawia kodeki, rozdzielczość, jakość oraz inne opcje dla kodowanych filmów", - "transcoding_hardware_acceleration": "Przyspieszenie Sprzętowe", - "transcoding_hardware_acceleration_description": "Eksperymentalny: szybsze transkodowanie, ale będzie miał niższą jakość przy tej samej szybkości transmisji", - "transcoding_hardware_decoding": "Dekodowanie sprzętowe", - "transcoding_hardware_decoding_setting_description": "Umożliwia całkowite przyspieszenie sprzętowe zamiast tylko przyspieszania kodowania. Może nie działać we wszystkich filmach.", - "transcoding_max_b_frames": "Maksymalne klatki B (B-Frames)", - "transcoding_max_b_frames_description": "Wyższe wartości poprawiają wydajność kompresji, ale spowalniają kodowanie. Może nie być kompatybilny z akceleracją sprzętową na starszych urządzeniach. 0 wyłącza klatki B (B-frames), natomiast -1 ustawia tę wartość automatycznie.", - "transcoding_max_bitrate": "Maksymalna szybkość transmisji", - "transcoding_max_bitrate_description": "Ustawienie maksymalnej szybkości transmisji może sprawić, że rozmiary plików będą bardziej przewidywalne przy niewielkim koszcie na jakość. Przy rozdzielczości 720p typowe wartości to 2600 kbit/s dla VP9 lub HEVC, lub 4500 kbit/s dla H.264. Wyłączone, jeśli ustawione na 0. Jeśli nie podano jednostki, przyjmuje się k (dla kbit/s), dlatego 5000, 5000k i 5M (dla Mbit/s) są równoznaczne.", - "transcoding_max_keyframe_interval": "Maksymalny interwał klatek kluczowych", - "transcoding_max_keyframe_interval_description": "Ustawia maksymalny dystans między klatkami kluczowymi. Niższe wartości przyspieszają przeszukiwanie filmów i mogą poprawić jakość w scenach z dużą ilością ruchu, kosztem gorszej efektywności kompresji. 0 ustawia tą wartość automatycznie.", - "transcoding_optimal_description": "Filmy w rozdzielczości wyższej niż docelowa lub w nieakceptowanym formacie", - "transcoding_policy": "Polityka transkodowania", - "transcoding_policy_description": "Ustaw kiedy film będzie transkodowany", - "transcoding_preferred_hardware_device": "Preferowane urządzenie sprzętowe", - "transcoding_preferred_hardware_device_description": "Dotyczy tylko VAAPI i QSV. Ustawia węzeł dri używany do transkodowania sprzętowego.", - "transcoding_preset_preset": "Ustawienie wstępne (-preset)", - "transcoding_preset_preset_description": "Szybkość kompresji. Wolniejsze ustawienia tworzą mniejsze pliki i zwiększają jakość, jeśli są ustawione na określoną szybkość transmisji. Kodowanie VP9 ignoruje prędkości powyżej „szybciej”.", - "transcoding_reference_frames": "Ramki referencyjne", - "transcoding_reference_frames_description": "Liczba klatek używana do odnoszenia się podczas kompresowania danej klatki. Wyższe wartości poprawiają efektywność kompresji, ale spowalniają kodowanie. 0 ustawia tą wartość automatycznie.", - "transcoding_required_description": "Tylko filmy w nieakceptowanym formacie", - "transcoding_settings": "Ustawienia Transkodowania Wideo", - "transcoding_settings_description": "Wybierz które filmy transkodować i jak je przetwarzać", - "transcoding_target_resolution": "Docelowa rozdzielczość", - "transcoding_target_resolution_description": "Wyższe rozdzielczości pozwalają zachować więcej szczegółów, ale kodowanie zajmuje więcej czasu, powoduje większe rozmiary plików i może zmniejszyć płynność aplikacji.", - "transcoding_temporal_aq": "Tymczasowe (Temporal) AQ", - "transcoding_temporal_aq_description": "Dotyczy tylko kodeka NVENC. Temporal Adaptive Quantization zwiększa jakość scen o dużej szczegółowości i małym ruchu. Może nie być kompatybilny ze starszymi urządzeniami.", - "transcoding_threads": "Wątki", - "transcoding_threads_description": "Wyższe wartości prowadzą do szybszego kodowania, ale pozostawiają mniej zasobów serwerowi na przetwarzanie innych zadań, gdy jest ono aktywne. Wartość ta nie powinna być większa niż liczba rdzeni procesora. Maksymalizuje wykorzystanie, jeśli jest ustawione na 0.", - "transcoding_tone_mapping": "Mapowanie tonów", - "transcoding_tone_mapping_description": "Próbuje zachować wygląd filmów HDR po konwersji do SDR. Każdy algorytm dokonuje różnych kompromisów w zakresie koloru, szczegółowości i jasności. Hable zachowuje szczegóły, Mobius kolor, a Reinhard jasność.", - "transcoding_transcode_policy": "Zasady transkodowania", - "transcoding_transcode_policy_description": "Zasady dotyczące transkodowania filmu. Filmy HDR będą zawsze transkodowane (z wyjątkiem sytuacji, gdy transkodowanie jest wyłączone).", - "transcoding_two_pass_encoding": "Kodowanie dwuprzebiegowe", - "transcoding_two_pass_encoding_setting_description": "Transkoduj w dwóch przebiegach, aby uzyskać lepiej zakodowane filmy. Gdy włączona jest maksymalna prędkość transmisji (wymagana do działania z H.264 i HEVC), ten tryb wykorzystuje zakres oparty na maksymalnej prędkości transmisji i ignoruje CRF. W przypadku wersji VP9 można użyć CRF, jeśli maksymalna prędkość transmisji jest wyłączona.", - "transcoding_video_codec": "Kodek Wideo", - "transcoding_video_codec_description": "VP9 jest wysoce efektywny i regularnie używany w Internecie, ale jego kodowanie zajmuje dłużej. HEVC ma podobną efektywność, ale nie wszyscy w Internecie go odtworzą. H.264 jest bardzo szeroko używany i szybko się go koduje, kosztem większego rozmiaru. AV1 jest najefektywniejszym kodekiem, ale nieobsługiwanym na starszych urządzeniach.", - "trash_enabled_description": "Włącz funkcję Kosza", - "trash_number_of_days": "Liczba dni", - "trash_number_of_days_description": "Liczba dni przechowywania zasobów w koszu przed ich trwałym usunięciem", - "trash_settings": "Ustawienia Kosza", - "trash_settings_description": "Zarządzaj ustawieniami kosza", - "unlink_all_oauth_accounts": "Odłącz wszystkie konta OAuth", - "unlink_all_oauth_accounts_description": "Pamiętaj, aby przed migracją do nowego dostawcy odłączyć wszystkie konta OAuth.", - "unlink_all_oauth_accounts_prompt": "Czy na pewno chcesz odłączyć wszystkie konta OAuth? Spowoduje to zresetowanie identyfikatora OAuth dla każdego użytkownika i nie będzie można tego cofnąć.", - "user_cleanup_job": "Porządkowanie użytkownika", - "user_delete_delay": "Konto {user} oraz jego zasoby zostaną zaplanowane do trwałego usunięcia za {delay, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}.", - "user_delete_delay_settings": "Usuń opóźnienie", - "user_delete_delay_settings_description": "Liczba dni po usunięciu, po której następuje trwałe usunięcie konta użytkownika i zasobów. Zadanie usuwania użytkowników jest uruchamiane o północy w celu sprawdzenia, czy użytkownicy są gotowi do usunięcia. Zmiany tego ustawienia zostaną sprawdzone przy następnym wykonaniu.", - "user_delete_immediately": "Konto {user} i powiązane zasoby zostaną zakolejkowane do natychmiastowego usunięcia.", - "user_delete_immediately_checkbox": "Umieść użytkownika i zasoby w kolejce do natychmiastowego usunięcia", - "user_details": "Szczegóły Użytkownika", - "user_management": "Zarządzenie Użytkownikami", - "user_password_has_been_reset": "Hasło użytkownika zostało zresetowane:", - "user_password_reset_description": "Proszę przekazać tymczasowe hasło użytkownikowi i poinformuj o konieczności jego zmiany przy najbliższym logowaniu.", - "user_restore_description": "Konto {user} zostanie przywrócone.", - "user_restore_scheduled_removal": "Przywrócenie użytkownika - zaplanowane usunięcie na {date, date, long}", - "user_settings": "Ustawienia Użytkownika", - "user_settings_description": "Zarządzaj ustawieniami użytkownika", - "user_successfully_removed": "Użytkownik {email} został pomyślnie usunięty.", - "users_page_description": "Strona administracyjna do zarządzania użytkownikami", - "version_check_enabled_description": "Włącz sprawdzanie wersji", - "version_check_implications": "Funkcja sprawdzania wersji opiera się na okresowej komunikacji z github.com", - "version_check_settings": "Sprawdzenie Wersji", - "version_check_settings_description": "Włącz/wyłącz powiadomienia o nowej wersji", - "video_conversion_job": "Transkodowanie wideo", - "video_conversion_job_description": "Transkodowanie wideo w celu zapewnienia szerokiej kompatybilności z przeglądarkami i urządzeniami" - }, - "admin_email": "E-mail Administratora", - "admin_password": "Hasło Administratora", - "administration": "Administracja", - "advanced": "Zaawansowane", - "advanced_settings_clear_image_cache": "Wyczyść pamięć podręczną obrazów", - "advanced_settings_clear_image_cache_error": "Nie udało się wyczyścić pamięci podręcznej obrazów", - "advanced_settings_clear_image_cache_success": "Pomyślnie wyczyszczono {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Użyj tej opcji do filtrowania mediów podczas synchronizacji opartej na alternatywnych kryteriach. Używaj tylko wtedy gdy aplikacja ma problemy z wykrywaniem wszystkich albumów.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERYMENTALNE] Użyj alternatywnego filtra synchronizacji albumów na urządzeniu", - "advanced_settings_log_level_title": "Poziom szczegółowości dziennika: {level}", - "advanced_settings_prefer_remote_subtitle": "Niektóre urządzenia bardzo wolno ładują miniatury z lokalnych zasobów. Aktywuj to ustawienie, aby ładować zdalne obrazy.", - "advanced_settings_prefer_remote_title": "Preferuj obrazy zdalne", - "advanced_settings_proxy_headers_subtitle": "Zdefiniuj nagłówki proxy, które Immich powinien wysyłać z każdym żądaniem sieciowym", - "advanced_settings_proxy_headers_title": "Niestandardowe nagłówki proxy [EKSPERYMENTALNE]", - "advanced_settings_readonly_mode_subtitle": "Włącza tryb tylko do odczytu, w którym zdjęcia można tylko przeglądać, a takie czynności jak wybieranie wielu obrazów, udostępnianie, przesyłanie i usuwanie są wyłączone. Włącz/wyłącz tryb tylko do odczytu za pomocą awatara użytkownika na ekranie głównym", - "advanced_settings_readonly_mode_title": "Tryb tylko do odczytu", - "advanced_settings_self_signed_ssl_subtitle": "Pomija weryfikację certyfikatu SSL dla punktu końcowego serwera. Wymagane w przypadku certyfikatów z podpisem własnym.", - "advanced_settings_self_signed_ssl_title": "Zezwól na certyfikaty SSL z podpisem własnym [EKSPERYMENTALNE]", - "advanced_settings_sync_remote_deletions_subtitle": "Automatycznie usuń lub przywróć zasób na tym urządzeniu po wykonaniu tej czynności w interfejsie webowym", - "advanced_settings_sync_remote_deletions_title": "Synchronizuj zdalne usunięcia [EKSPERYMENTALNE]", - "advanced_settings_tile_subtitle": "Zaawansowane ustawienia użytkownika", - "advanced_settings_troubleshooting_subtitle": "Włącz dodatkowe funkcje rozwiązywania problemów", - "advanced_settings_troubleshooting_title": "Rozwiązywanie problemów", - "age_months": "Wiek {months, plural, one {# miesiąc} few {# miesiące} many {# miesięcy} other {# miesięcy}}", - "age_year_months": "Wiek 1 rok, {months, plural, one {# miesiąc} few {# miesiące} many {# miesięcy} other {# miesięcy}}", - "age_years": "{years, plural, other {Wiek #}}", - "album": "Album", - "album_added": "Album udostępniony", - "album_added_notification_setting_description": "Otrzymaj powiadomienie email, gdy zostanie Ci udostępniony album", - "album_cover_updated": "Okładka albumu została zaktualizowana", - "album_delete_confirmation": "Czy na pewno chcesz usunąć album {album}?", - "album_delete_confirmation_description": "Jeżeli album jest udostępniany, inny stracą do niego dostęp.", - "album_deleted": "Album usunięty", - "album_info_card_backup_album_excluded": "WYKLUCZONE", - "album_info_card_backup_album_included": "WŁĄCZONE", - "album_info_updated": "Szczegóły albumu zostały zaktualizowane", - "album_leave": "Opuścić album?", - "album_leave_confirmation": "Na pewno chcesz opuścić {album}?", - "album_name": "Nazwa Albumu", - "album_options": "Opcje albumu", - "album_remove_user": "Usunąć użytkownika?", - "album_remove_user_confirmation": "Na pewno chcesz usunąć {user}?", - "album_search_not_found": "Nie znaleziono albumów pasujących do Twojego wyszukiwania", - "album_selected": "Wybrany album", - "album_share_no_users": "Wygląda na to, że ten album albo udostępniono wszystkim użytkownikom, albo nie ma komu go udostępnić.", - "album_summary": "Podsumowanie albumu", - "album_updated": "Album zaktualizowany", - "album_updated_setting_description": "Otrzymaj powiadomienie e-mail, gdy do udostępnionego Ci albumu zostaną dodane nowe zasoby", - "album_upload_assets": "Prześlij zasoby ze swojego komputera i dodaj je do albumu", - "album_user_left": "Opuszczono {album}", - "album_user_removed": "Usunięto {user}", - "album_viewer_appbar_delete_confirm": "Czy na pewno chcesz usunąć ten album ze swojego konta?", - "album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu", - "album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu", - "album_viewer_appbar_share_err_remove": "Wystąpiły problemy z usunięciem plików z albumu", - "album_viewer_appbar_share_err_title": "Nie udało się zmienić tytułu albumu", - "album_viewer_appbar_share_leave": "Opuść album", - "album_viewer_appbar_share_to": "Udostępnij", - "album_viewer_page_share_add_users": "Dodaj użytkowników", - "album_with_link_access": "Pozwól każdemu z dostępem do linku zobaczyć zdjęcia i osoby w tym albumie.", - "albums": "Albumy", - "albums_count": "{count, plural, one {{count, number} Album} few {{count, number} Albumy} other {{count, number} Albumów}}", - "albums_default_sort_order": "Domyślna kolejność sortowania w albumach", - "albums_default_sort_order_description": "Początkowa kolejność sortowania zasobów przy tworzeniu nowych albumów.", - "albums_feature_description": "Kolekcje zasobów, które można udostępniać innym użytkownikom.", - "albums_on_device_count": "Albumy na urządzeniu ({count})", - "albums_selected": "{count, plural, one {# wybrany album} few {# wybrane albumy} other {# wybranych albumów}}", - "all": "Wszystkie", - "all_albums": "Wszystkie albumy", - "all_people": "Wszystkie osoby", - "all_photos": "Wszystkie zdjęcia", - "all_videos": "Wszystkie filmy", - "allow_dark_mode": "Zezwalaj na tryb ciemny", - "allow_edits": "Pozwól edytować", - "allow_public_user_to_download": "Zezwól użytkownikowi publicznemu na pobieranie", - "allow_public_user_to_upload": "Zezwól użytkownikowi publicznemu na przesyłanie plików", - "allowed": "Dozwolone", - "alt_text_qr_code": "Obrazek kodu QR", - "always_keep": "Zawsze zachowuj", - "always_keep_photos_hint": "Zwolnij Miejsce zachowa wszystkie zdjęcia na tym urządzeniu.", - "always_keep_videos_hint": "Zwolnij Miejsce zachowa wszystkie filmy na tym urządzeniu.", - "anti_clockwise": "Przeciwnie do ruchu wskazówek zegara", - "api_key": "Klucz API", - "api_key_description": "Widzisz tę wartość po raz pierwszy i ostatni, więc lepiej ją skopiuj przed zamknięciem okna.", - "api_key_empty": "Twój Klucz API nie powinien być pusty", - "api_keys": "Klucze API", - "app_architecture_variant": "Wariant (Architektura)", - "app_bar_signout_dialog_content": "Czy na pewno chcesz się wylogować?", - "app_bar_signout_dialog_ok": "Tak", - "app_bar_signout_dialog_title": "Wyloguj się", - "app_download_links": "Linki do pobrania aplikacji", - "app_settings": "Ustawienia aplikacji", - "app_stores": "Sklepy z aplikacjami", - "app_update_available": "Dostępna jest aktualizacja aplikacji", - "appears_in": "W albumach", - "apply_count": "Zastosuj ({count, number})", - "archive": "Archiwum", - "archive_action_prompt": "{count} dodanych do Archiwum", - "archive_or_unarchive_photo": "Dodaj lub usuń zasób z archiwum", - "archive_page_no_archived_assets": "Nie znaleziono zarchiwizowanych zasobów", - "archive_page_title": "Archiwum {count}", - "archive_size": "Rozmiar archiwum", - "archive_size_description": "Podziel pobierane pliki na więcej niż jedno archiwum, jeżeli rozmiar archiwum przekroczy tę wartość w GiB", - "archived": "Archiwum", - "archived_count": "{count, plural, other {Zarchiwizowano #}}", - "are_these_the_same_person": "Czy to jedna i ta sama osoba?", - "are_you_sure_to_do_this": "Czy aby na pewno chcesz to zrobić?", - "array_field_not_fully_supported": "Elementy tablicy wymagają ręcznej edycji JSON", - "asset_action_delete_err_read_only": "Nie można usunąć zasobów tylko do odczytu, pomijam", - "asset_action_share_err_offline": "Nie można pobrać zasobów offline, pomijam", - "asset_added_to_album": "Dodano do albumu", - "asset_adding_to_album": "Dodawanie do albumu…", - "asset_created": "Utworzono zasób", - "asset_description_updated": "Zaktualizowano opis zasobu", - "asset_filename_is_offline": "Zasób {filename} jest offline", - "asset_has_unassigned_faces": "Zasób ma nieprzypisane twarze", - "asset_hashing": "Hashowanie…", - "asset_list_group_by_sub_title": "Grupuj według", - "asset_list_layout_settings_dynamic_layout_title": "Układ dynamiczny", - "asset_list_layout_settings_group_automatically": "Automatyczny", - "asset_list_layout_settings_group_by": "Grupuj zasoby według", - "asset_list_layout_settings_group_by_month_day": "Miesiąc + dzień", - "asset_list_layout_sub_title": "Układ", - "asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć", - "asset_list_settings_title": "Siatka Zdjęć", - "asset_not_found_on_device_android": "Nie znaleziono zasobu na urządzeniu", - "asset_not_found_on_device_ios": "Nie znaleziono zasobu na urządzeniu. Jeśli korzystasz z usługi iCloud, zasób może być niedostępny z powodu uszkodzonego pliku przechowywanego w usłudze iCloud", - "asset_not_found_on_icloud": "Nie znaleziono zasobu w usłudze iCloud. Zasób może być niedostępny z powodu uszkodzonego pliku przechowywanego w usłudze iCloud", - "asset_offline": "Zasób niedostępny", - "asset_offline_description": "Ten zewnętrzny zasób nie jest już dostępny na dysku. Aby uzyskać pomoc, skontaktuj się z administratorem Immich.", - "asset_restored_successfully": "Zasób został pomyślnie przywrócony", - "asset_skipped": "Pominięto", - "asset_skipped_in_trash": "W koszu", - "asset_trashed": "Zasób wrzucono do kosza", - "asset_troubleshoot": "Rozwiązywanie problemów z zasobami", - "asset_uploaded": "Przesłano", - "asset_uploading": "Przesyłanie…", - "asset_viewer_settings_subtitle": "Zarządzaj ustawieniami przeglądarki galerii", - "asset_viewer_settings_title": "Przeglądarka zasobów", - "assets": "Zasoby", - "assets_added_count": "Dodano {count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "assets_added_to_album_count": "Dodano {count, plural, one {# zasób} few {# zasoby} other {# zasobów}} do albumu", - "assets_added_to_albums_count": "Dodano {assetTotal, plural, one {# zasób} few {# zasoby} other {# zasobów}} do {albumTotal, plural, one {# albumu} other {# albumów}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Zasób nie może zostać dodany} other {Zasoby nie mogą zostać dodane}} do albumu", - "assets_cannot_be_added_to_albums": "{count, plural, one {Zasób nie może być dodany} other {Zasoby nie mogą być dodane}} do żadnego z albumów", - "assets_count": "{count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "assets_deleted_permanently": "{count} zostało trwale usuniętych", - "assets_deleted_permanently_from_server": "{count} zostało trwale usuniętych z serwera Immich", - "assets_downloaded_failed": "{count, plural, one {Pomyślnie pobrano # plik - {error} plik nie powiódł się} few {Pomyślnie pobrano # pliki - {error} pliki nie powiodły się} other {Pomyślnie pobrano # plików - {error} pliki nie powiodły się}}", - "assets_downloaded_successfully": "{count, plural, one {Pomyślnie pobrano # plik} few {Pomyślnie pobrano # pliki} other {Pomyślnie pobrano # plików}}", - "assets_moved_to_trash_count": "Przeniesiono {count, plural, one {# zasób} few {# zasoby} other {# zasobów}} do kosza", - "assets_permanently_deleted_count": "Trwale usunięto {count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "assets_removed_count": "Usunięto {count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "assets_removed_permanently_from_device": "{count} zostało trwale usuniętych z Twojego urządzenia", - "assets_restore_confirmation": "Na pewno chcesz przywrócić wszystkie zasoby z kosza? Nie da się tego cofnąć! Należy pamiętać, że w ten sposób nie można przywrócić zasobów offline.", - "assets_restored_count": "Przywrócono {count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "assets_restored_successfully": "{count} pomyślnie przywrócono", - "assets_trashed": "{count} szt. zostało wrzucone do kosza", - "assets_trashed_count": "Wrzucono do kosza {count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "assets_trashed_from_server": "{count} szt. usuniętych z serwera Immich", - "assets_were_part_of_album_count": "{count, plural, one {Zasób był} other {Zasoby były}} już częścią albumu", - "assets_were_part_of_albums_count": "{count, plural, one {Zasób był} other {Zasoby były}} już częścią albumów", - "authorized_devices": "Autoryzowane urządzenia", - "automatic_endpoint_switching_subtitle": "Połącz się lokalnie przez wyznaczoną sieć Wi-Fi, jeśli jest dostępna, i korzystaj z alternatywnych połączeń gdzie indziej", - "automatic_endpoint_switching_title": "Automatyczne przełączanie adresów URL", - "autoplay_slideshow": "Automatyczne odtwarzanie pokazu slajdów", - "back": "Wstecz", - "back_close_deselect": "Wróć, zamknij lub odznacz", - "background_backup_running_error": "Tworzenie kopii zapasowej w tle jest obecnie w toku, nie można rozpocząć ręcznego tworzenia kopii zapasowej", - "background_location_permission": "Uprawnienia do lokalizacji w tle", - "background_location_permission_content": "Aby móc przełączać sieć podczas pracy w tle, Immich musi *zawsze* mieć dostęp do dokładnej lokalizacji, aby aplikacja mogła odczytać nazwę sieci Wi-Fi", - "background_options": "Opcje w tle", - "backup": "Kopia zapasowa", - "backup_album_selection_page_albums_device": "Albumy na urządzeniu ({count})", - "backup_album_selection_page_albums_tap": "Stuknij, aby włączyć, stuknij dwukrotnie, aby wykluczyć", - "backup_album_selection_page_assets_scatter": "Pliki mogą być rozproszone w wielu albumach. Dzięki temu albumy mogą być włączane lub wyłączane podczas procesu tworzenia kopii zapasowej.", - "backup_album_selection_page_select_albums": "Wybierz albumy", - "backup_album_selection_page_selection_info": "Info o wyborze", - "backup_album_selection_page_total_assets": "Łącznie unikalnych plików", - "backup_albums_sync": "Synchronizacja kopii zapasowych albumów", - "backup_all": "Wszystkie", - "backup_background_service_backup_failed_message": "Nie udało się wykonać kopii zapasowej zasobów. Ponowna próba…", - "backup_background_service_complete_notification": "Kopia zapasowa zasobu zakończona", - "backup_background_service_connection_failed_message": "Nie udało się połączyć z serwerem. Ponowna próba…", - "backup_background_service_current_upload_notification": "Przesyłanie {filename}", - "backup_background_service_default_notification": "Sprawdzanie nowych zasobów…", - "backup_background_service_error_title": "Błąd kopii zapasowej", - "backup_background_service_in_progress_notification": "Tworzenie kopii zapasowej twoich zasobów…", - "backup_background_service_upload_failure_notification": "Błąd przesyłania {filename}", - "backup_controller_page_albums": "Albumy z włączoną kopią zapasową", - "backup_controller_page_background_app_refresh_disabled_content": "Włącz odświeżanie aplikacji w tle w Ustawienia > Ogólne > Odświeżanie aplikacji w tle, aby móc korzystać z kopii zapasowej w tle.", - "backup_controller_page_background_app_refresh_disabled_title": "Odświeżanie aplikacji w tle wyłączone", - "backup_controller_page_background_app_refresh_enable_button_text": "Przejdź do ustawień", - "backup_controller_page_background_battery_info_link": "Pokaż mi jak", - "backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, należy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPonieważ jest to zależne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Optymalizacja Baterii", - "backup_controller_page_background_charging": "Tylko podczas ładowania", - "backup_controller_page_background_configure_error": "Nie udało się skonfigurować usługi w tle", - "backup_controller_page_background_delay": "Opóźnienie tworzenia kopii zapasowych nowych zasobów: {duration}", - "backup_controller_page_background_description": "Włącz usługę w tle, aby automatycznie tworzyć kopie zapasowe wszelkich nowych zasobów bez konieczności otwierania aplikacji", - "backup_controller_page_background_is_off": "Automatyczna kopia zapasowa w tle jest wyłączona", - "backup_controller_page_background_is_on": "Automatyczna kopia zapasowa w tle jest włączona", - "backup_controller_page_background_turn_off": "Wyłącz usługę w tle", - "backup_controller_page_background_turn_on": "Włącz usługę w tle", - "backup_controller_page_background_wifi": "Tylko Wi-Fi", - "backup_controller_page_backup": "Kopia zapasowa", - "backup_controller_page_backup_selected": "Wybrane: ", - "backup_controller_page_backup_sub": "Zdjęcia i filmy z utworzoną kopią zapasową", - "backup_controller_page_created": "Utworzono dnia: {date}", - "backup_controller_page_desc_backup": "Włącz kopię zapasową na pierwszym planie, aby automatycznie przesyłać nowe zasoby na serwer po otworzeniu aplikacji.", - "backup_controller_page_excluded": "Wykluczone: ", - "backup_controller_page_failed": "Nieudane ({count})", - "backup_controller_page_filename": "Nazwa pliku: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informacje o kopii zapasowej", - "backup_controller_page_none_selected": "Nic nie wybrano", - "backup_controller_page_remainder": "Pozostałe", - "backup_controller_page_remainder_sub": "Pozostałe zdjęcia i filmy wybrane do wykonania kopii zapasowej", - "backup_controller_page_server_storage": "Pamięć Serwera", - "backup_controller_page_start_backup": "Rozpocznij Kopię Zapasową", - "backup_controller_page_status_off": "Automatyczne tworzenie kopii zapasowej na pierwszym planie jest wyłączone", - "backup_controller_page_status_on": "Automatyczne tworzenie kopii zapasowej na pierwszym planie jest włączone", - "backup_controller_page_storage_format": "Wykorzystano {used} z {total}", - "backup_controller_page_to_backup": "Albumy, dla których ma być tworzona kopia zapasowa", - "backup_controller_page_total_sub": "Wszystkie unikalne zdjęcia i filmy z wybranych albumów", - "backup_controller_page_turn_off": "Wyłącz kopię zapasową na pierwszym planie", - "backup_controller_page_turn_on": "Włącz kopię zapasową na pierwszym planie", - "backup_controller_page_uploading_file_info": "Informacje o przesyłanym pliku", - "backup_err_only_album": "Nie można usunąć jedynego albumu", - "backup_error_sync_failed": "Synchronizacja nie powiodła się. Nie można wykonać kopii zapasowej.", - "backup_info_card_assets": "zasoby", - "backup_manual_cancelled": "Anulowano", - "backup_manual_in_progress": "Przesyłanie już trwa. Spróbuj po pewnym czasie", - "backup_manual_success": "Sukces", - "backup_manual_title": "Stan przesyłania", - "backup_options": "Opcje kopii zapasowej", - "backup_options_page_title": "Opcje kopii zapasowej", - "backup_setting_subtitle": "Zarządzaj ustawieniami przesyłania w tle i na pierwszym planie", - "backup_settings_subtitle": "Zarządzanie ustawieniami przesyłania", - "backup_upload_details_page_more_details": "Dotknij, aby wyświetlić więcej szczegółów", - "backward": "Do tyłu", - "biometric_auth_enabled": "Włączono logowanie biometryczne", - "biometric_locked_out": "Uwierzytelnianie biometryczne jest dla Ciebie zablokowane", - "biometric_no_options": "Brak możliwości biometrii", - "biometric_not_available": "Logowanie biometryczne nie jest dostępne na tym urządzeniu", - "birthdate_saved": "Data urodzenia zapisana pomyślnie", - "birthdate_set_description": "Data urodzenia jest używana do obliczenia wieku danej osoby podczas wykonania zdjęcia.", - "blurred_background": "Rozmyte tło", - "bugs_and_feature_requests": "Błędy i prośby o funkcje", - "build": "Kompilacja", - "build_image": "Obraz Buildu", - "bulk_delete_duplicates_confirmation": "Czy na pewno chcesz trwale usunąć {count, plural, one {# zduplikowany zasób} few {# zduplikowane zasoby} other {# zduplikowanych zasobów}}? Zostanie zachowany największy zasób z każdej grupy, a wszystkie pozostałe duplikaty zostaną trwale usunięte. Nie można cofnąć tej operacji!", - "bulk_keep_duplicates_confirmation": "Czy na pewno chcesz zachować {count, plural, one {# zduplikowany zasób} few {# zduplikowane zasoby} other {# zduplikowanych zasobów}}? To spowoduje rozwiązanie wszystkich grup duplikatów bez usuwania czegokolwiek.", - "bulk_trash_duplicates_confirmation": "Czy na pewno chcesz wrzucić do kosza {count, plural, one {# zduplikowany zasób} few {# zduplikowane zasoby} other {# zduplikowanych zasobów}}? Zostanie zachowany największy zasób z każdej grupy, a wszystkie pozostałe duplikaty zostaną wrzucone do kosza.", - "buy": "Kup Immich", - "cache_settings_clear_cache_button": "Wyczyść Cache", - "cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopóki pamięć podręczna nie zostanie odbudowana.", - "cache_settings_duplicated_assets_clear_button": "WYCZYŚĆ", - "cache_settings_duplicated_assets_subtitle": "Zdjęcia i filmy umieszczone na liście ignorowanych w aplikacji", - "cache_settings_duplicated_assets_title": "Zduplikowane zasoby ({count})", - "cache_settings_statistics_album": "Biblioteka miniatur", - "cache_settings_statistics_full": "Pełne Zdjęcia", - "cache_settings_statistics_shared": "Udostępnione miniatury albumów", - "cache_settings_statistics_thumbnail": "Miniatury", - "cache_settings_statistics_title": "Użycie Cache", - "cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich", - "cache_settings_tile_subtitle": "Kontroluj zachowanie lokalnego magazynu", - "cache_settings_tile_title": "Lokalny magazyn", - "cache_settings_title": "Ustawienia Buforowania", - "camera": "Aparat", - "camera_brand": "Marka aparatu", - "camera_model": "Model aparatu", - "cancel": "Anuluj", - "cancel_search": "Anuluj wyszukiwanie", - "canceled": "Anulowano", - "canceling": "Anulowanie", - "cannot_merge_people": "Złączenie osób nie powiodło się", - "cannot_undo_this_action": "Nie da się tego cofnąć!", - "cannot_update_the_description": "Nie można zaktualizować opisu", - "cast": "Odtwórz na telewizorze", - "cast_description": "Skonfiguruj dostępne cele do przesyłania", - "change_date": "Zmień datę", - "change_description": "Zmień opis", - "change_display_order": "Zmień kolejność wyświetlania", - "change_expiration_time": "Zmień czas ważności", - "change_location": "Zmień lokalizację", - "change_name": "Zmień nazwę", - "change_name_successfully": "Pomyślnie zmieniono nazwę", - "change_password": "Zmień hasło", - "change_password_description": "Logujesz się po raz pierwszy lub wysłano prośbę o zmianę hasła. Wprowadź nowe hasło poniżej.", - "change_password_form_confirm_password": "Potwierdź Hasło", - "change_password_form_description": "Cześć {name},\n\nPierwszy raz logujesz się do systemu, albo złożono prośbę o zmianę hasła. Wpisz poniżej nowe hasło.", - "change_password_form_log_out": "Wyloguj wszystkie inne urządzenia", - "change_password_form_log_out_description": "Zaleca się wylogowanie się ze wszystkich innych urządzeń", - "change_password_form_new_password": "Nowe Hasło", - "change_password_form_password_mismatch": "Hasła nie są zgodne", - "change_password_form_reenter_new_password": "Wprowadź ponownie Nowe Hasło", - "change_pin_code": "Zmień kod PIN", - "change_trigger": "Zmień wyzwalacz", - "change_trigger_prompt": "Czy na pewno chcesz zmienić wyzwalacz? Spowoduje to usunięcie wszystkich istniejących akcji i filtrów.", - "change_your_password": "Zmień swoje hasło", - "changed_visibility_successfully": "Pomyślnie zmieniono widoczność", - "charging": "Ładowanie", - "charging_requirement_mobile_backup": "Tworzenie kopii zapasowej w tle wymaga by urządzenie było podłączone do ładowania", - "check_corrupt_asset_backup": "Sprawdź, czy kopie zapasowe zasobów nie są uszkodzone", - "check_corrupt_asset_backup_button": "Wykonaj sprawdzenie", - "check_corrupt_asset_backup_description": "Uruchom sprawdzenie tylko przez Wi-Fi i po utworzeniu kopii zapasowej wszystkich zasobów. Procedura może potrwać kilka minut.", - "check_logs": "Diagnostyka", - "checksum": "Suma kontrolna", - "choose_matching_people_to_merge": "Wybierz osoby, aby złączyć je w jedną", - "city": "Miasto", - "cleanup_confirm_description": "Immich znalazł {count} zasobów (utworzonych przed {date}) z kopią zapasową bezpiecznie przesłaną na serwer. Czy chcesz usunąć lokalne kopie z tego urządzenia?", - "cleanup_confirm_prompt_title": "Usunąć z tego urządzenia?", - "cleanup_deleted_assets": "Przeniesiono {count} zasobów do kosza urządzenia", - "cleanup_deleting": "Przenoszenie do kosza...", - "cleanup_found_assets": "Znaleziono {count} zasobów z przesłaną kopią zapasową", - "cleanup_found_assets_with_size": "Znaleziono {count} zasobów z kopią zapasową ({size})", - "cleanup_icloud_shared_albums_excluded": "Udostępniane albumy iCloud są wyłączone ze skanowania", - "cleanup_no_assets_found": "Nie znaleziono żadnych zasobów spełniających podane kryteria. Zwolnij Miejsce może usuwać jedynie zasoby, które posiadają kopię zapasową na serwerze", - "cleanup_preview_title": "Zasoby do usunięcia ({count})", - "cleanup_step3_description": "Wyszukaj zasoby z kopią zapasową, zgodne z Twoimi ustawieniami.", - "cleanup_step4_summary": "{count} zasoby (utworzone przed {date}) zostaną usunięte z tego urządzenia. Zdjęcia będą nadal dostępne w aplikacji Immich.", - "cleanup_trash_hint": "Aby całkowicie odzyskać miejsce w pamięci, otwórz aplikację galerii systemowej i opróżnij kosz", - "clear": "Wyczyść", - "clear_all": "Wyczyść wszystko", - "clear_all_recent_searches": "Usuń ostatnio wyszukiwane", - "clear_file_cache": "Wyczyść pamięć podręczną plików", - "clear_message": "Wyczyść wiadomość", - "clear_value": "Wyczyść wartość", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Wprowadź hasło", - "client_cert_import": "Importuj", - "client_cert_import_success_msg": "Certyfikat klienta został zaimportowany", - "client_cert_invalid_msg": "Nieprawidłowy plik certyfikatu lub nieprawidłowe hasło", - "client_cert_remove_msg": "Certyfikat klienta został usunięty", - "client_cert_subtitle": "Obsługuje wyłącznie format PKCS12 (.p12, .pfx). Importowanie/usuwanie certyfikatów jest dostępne wyłącznie przed zalogowaniem", - "client_cert_title": "Certyfikat klienta SSL [EKSPERYMENTALNE]", - "clockwise": "Zgodnie z ruchem wskazówek zegara", - "close": "Zamknij", - "collapse": "Zwiń", - "collapse_all": "Zwiń wszystko", - "color": "Kolor", - "color_theme": "Motyw kolorów", - "command": "Polecenie", - "comment_deleted": "Usunięto komentarz", - "comment_options": "Opcje komentarza", - "comments_and_likes": "Komentarze i polubienia", - "comments_are_disabled": "Komentarze są wyłączone", - "common_create_new_album": "Utwórz nowy album", - "completed": "Ukończono", - "confirm": "Potwierdź", - "confirm_admin_password": "Potwierdź Hasło Administratora", - "confirm_delete_face": "Czy na pewno chcesz usunąć twarz {name} z zasobów?", - "confirm_delete_shared_link": "Czy na pewno chcesz usunąć ten udostępniony link?", - "confirm_keep_this_delete_others": "Wszystkie inne zasoby w tym stosie, z wyjątkiem tego zasobu, zostaną usunięte. Czy jesteś pewien, że chcesz kontynuować?", - "confirm_new_pin_code": "Potwierdź nowy kod PIN", - "confirm_password": "Potwierdź hasło", - "confirm_tag_face": "Chcesz dodać do tej twarzy etykietę {name}?", - "confirm_tag_face_unnamed": "Chcesz dodać do tej twarzy etykietę?", - "connected_device": "Podłączone urządzenie", - "connected_to": "Połączony z", - "contain": "Zawiera", - "context": "Kontekst", - "continue": "Kontynuuj", - "control_bottom_app_bar_create_new_album": "Utwórz nowy album", - "control_bottom_app_bar_delete_from_immich": "Usuń z Immicha", - "control_bottom_app_bar_delete_from_local": "Usuń z urządzenia", - "control_bottom_app_bar_edit_location": "Edytuj lokalizację", - "control_bottom_app_bar_edit_time": "Edytuj datę i godzinę", - "control_bottom_app_bar_share_link": "Udostępnij link", - "control_bottom_app_bar_share_to": "Wyślij", - "control_bottom_app_bar_trash_from_immich": "Przenieś do kosza", - "copied_image_to_clipboard": "Skopiowano obraz do schowka.", - "copied_to_clipboard": "Skopiowano do schowka!", - "copy_error": "Błąd kopiowania", - "copy_file_path": "Skopiuj ścieżkę pliku", - "copy_image": "Skopiuj obraz", - "copy_link": "Skopiuj link", - "copy_link_to_clipboard": "Skopiuj link do schowka", - "copy_password": "Skopiuj hasło", - "copy_to_clipboard": "Skopiuj do Schowka", - "country": "Kraj", - "cover": "Pokryj", - "covers": "Okładki", - "create": "Utwórz", - "create_album": "Utwórz album", - "create_album_page_untitled": "Bez tytułu", - "create_api_key": "Utwórz klucz API", - "create_first_workflow": "Stwórz pierwszy przepływ pracy", - "create_library": "Stwórz Bibliotekę", - "create_link": "Utwórz link", - "create_link_to_share": "Utwórz link do udostępnienia", - "create_link_to_share_description": "Pozwól każdemu z dostępem do linku zobaczyć wybrane zdjęcie/zdjęcia", - "create_new": "UTWÓRZ NOWY", - "create_new_person": "Stwórz nową osobę", - "create_new_person_hint": "Przypisz wybrane zasoby do nowej osoby", - "create_new_user": "Stwórz nowego użytkownika", - "create_shared_album_page_share_add_assets": "DODAJ ZASOBY", - "create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia", - "create_shared_link": "Utwórz link udostępniający", - "create_tag": "Stwórz etykietę", - "create_tag_description": "Stwórz nową etykietę. Dla etykiet zagnieżdżonych, wprowadź pełną ścieżkę etykiety zawierającą ukośniki.", - "create_user": "Stwórz użytkownika", - "create_workflow": "Stwórz przepływ pracy", - "created": "Utworzono", - "created_at": "Utworzony", - "creating_linked_albums": "Tworzenie połączonych albumów...", - "crop": "Przytnij", - "crop_aspect_ratio_fixed": "Stałe", - "crop_aspect_ratio_free": "Dowolne", - "crop_aspect_ratio_original": "Oryginalne", - "curated_object_page_title": "Rzeczy", - "current_device": "Obecne urządzenie", - "current_pin_code": "Aktualny kod PIN", - "current_server_address": "Aktualny adres serwera", - "custom_date": "Data niestandardowa", - "custom_locale": "Niestandardowy Region", - "custom_locale_description": "Formatuj daty i liczby na podstawie języka i regionu", - "custom_url": "Niestandardowy URL", - "cutoff_date_description": "Zachowaj zdjęcia z ostatnich…", - "cutoff_day": "{count, plural, one {dzień} other {dni}}", - "cutoff_year": "{count, plural, one {rok} few {lata} other {lat}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Ciemny", - "dark_theme": "Przełącz ciemny motyw", - "date": "Data", - "date_after": "Data po", - "date_and_time": "Data i godzina", - "date_before": "Data przed", - "date_format": "E d. LLL y • hh:mm", - "date_of_birth_saved": "Data urodzenia zapisana pomyślnie", - "date_range": "Zakres dat", - "day": "Dzień", - "days": "Dni", - "deduplicate_all": "Usuń duplikaty", - "deduplication_criteria_1": "Rozmiar obrazu w bajtach", - "deduplication_criteria_2": "Ilość plików EXIF", - "deduplication_info": "Stan duplikatów", - "deduplication_info_description": "Aby zakwalifikować elementy jako duplikaty do masowego usunięcia, sprawdzane jest:", - "default_locale": "Domyślny Region", - "default_locale_description": "Formatuj daty i liczby na podstawie ustawień Twojej przeglądarki", - "delete": "Usuń", - "delete_action_confirmation_message": "Jesteś pewien, że chcesz usunąć ten zasób? Ta czynność przeniesie zasób do kosza na serwerze i wyświetli komunikat z pytaniem, czy chcesz go usunąć lokalnie", - "delete_action_prompt": "{count} usuniętych", - "delete_album": "Usuń album", - "delete_api_key_prompt": "Czy na pewno chcesz usunąć ten klucz API?", - "delete_dialog_alert": "Te elementy zostaną trwale usunięte z Immich i z Twojego urządzenia", - "delete_dialog_alert_local": "Elementy te zostaną trwale usunięte z Twojego urządzenia, ale nadal będą dostępne na serwerze Immich", - "delete_dialog_alert_local_non_backed_up": "Niektóre elementy nie mają kopii zapasowej w Immich i zostaną trwale usunięte z Twojego urządzenia", - "delete_dialog_alert_remote": "Elementy te zostaną trwale usunięte z serwera Immich", - "delete_dialog_ok_force": "Usuń mimo to", - "delete_dialog_title": "Usuń trwale", - "delete_duplicates_confirmation": "Czy na pewno chcesz trwale usunąć te duplikaty?", - "delete_face": "Usuń twarz", - "delete_key": "Usuń klucz", - "delete_library": "Usuń bibliotekę", - "delete_link": "Usuń link", - "delete_local_action_prompt": "{count} lokalnie usunięto", - "delete_local_dialog_ok_backed_up_only": "Usuń tylko kopię zapasową", - "delete_local_dialog_ok_force": "Usuń mimo to", - "delete_others": "Usuń pozostałe", - "delete_permanently": "Usuń trwale", - "delete_permanently_action_prompt": "{count} trwale usuniętych", - "delete_shared_link": "Usuń udostępniony link", - "delete_shared_link_dialog_title": "Usuń udostępniony link", - "delete_tag": "Usuń etykietę", - "delete_tag_confirmation_prompt": "Czy na pewno chcesz usunąć etykietę {tagName}?", - "delete_user": "Usuń użytkownika", - "deleted_shared_link": "Pomyślnie usunięto udostępniony link", - "deletes_missing_assets": "Usuwa brakujące zasoby z dysku", - "description": "Opis", - "description_input_hint_text": "Dodaj opis...", - "description_input_submit_error": "Błąd aktualizacji opisu, sprawdź dziennik, aby uzyskać więcej szczegółów", - "deselect_all": "Odznacz wszystkie", - "details": "Szczegóły", - "direction": "Kierunek", - "disable": "Wyłącz", - "disabled": "Wyłączone", - "disallow_edits": "Nie pozwalaj edytować", - "discord": "Discord", - "discover": "Odkryj", - "discovered_devices": "Wykryte urządzenia", - "dismiss_all_errors": "Odrzuć wszystkie błędy", - "dismiss_error": "Odrzuć błąd", - "display_options": "Opcje wyświetlania", - "display_order": "Kolejność wyświetlania", - "display_original_photos": "Wyświetlaj oryginalne zdjęcia", - "display_original_photos_setting_description": "Wyświetlając zdjęcia i filmy, prezentuj oryginalny plik zamiast miniatur jeżeli działa on w przeglądarce. Może to skutkować wolniejszym ładowaniem zdjęć i filmów.", - "do_not_show_again": "Nie pokazuj więcej tej wiadomości", - "documentation": "Dokumentacja", - "done": "Gotowe", - "download": "Pobierz", - "download_action_prompt": "Pobieranie {count} zasobów", - "download_canceled": "Pobieranie anulowane", - "download_complete": "Pobieranie zakończone", - "download_enqueue": "Pobieranie w kolejce", - "download_error": "Błąd pobierania", - "download_failed": "Pobieranie nieudane", - "download_finished": "Pobieranie zakończone", - "download_include_embedded_motion_videos": "Pobierz filmy ruchomych zdjęć", - "download_include_embedded_motion_videos_description": "Dołącz filmy osadzone w ruchomych zdjęciach jako oddzielny plik", - "download_notfound": "Nie znaleziono pliku do pobrania", - "download_original": "Pobierz oryginał", - "download_paused": "Pobieranie wstrzymane", - "download_settings": "Pobierz", - "download_settings_description": "Zarządzaj pobieraniem zasobów", - "download_started": "Pobieranie rozpoczęte", - "download_sucess": "Udane pobieranie", - "download_sucess_android": "Media zostały pobrane do DCIM/Immich", - "download_waiting_to_retry": "Oczekiwanie na ponowną próbę", - "downloading": "Pobieranie", - "downloading_asset_filename": "Pobieranie zasobu {filename}", - "downloading_from_icloud": "Pobieranie z iCloud", - "downloading_media": "Pobieranie multimediów", - "drop_files_to_upload": "Upuść pliki w dowolnym miejscu, aby je przesłać", - "duplicates": "Duplikaty", - "duplicates_description": "Rozstrzygnij każdą grupę, określając, które zasoby są duplikatami, jeżeli są duplikatami", - "duration": "Czas trwania", - "edit": "Edytuj", - "edit_album": "Edytuj album", - "edit_avatar": "Edytuj awatar", - "edit_birthday": "Edytuj datę urodzin", - "edit_date": "Edytuj datę", - "edit_date_and_time": "Edytuj datę i czas", - "edit_date_and_time_action_prompt": "{count} daty i godziny zmodyfikowane", - "edit_date_and_time_by_offset": "Zmień datę o przesunięcie", - "edit_date_and_time_by_offset_interval": "Nowy zakres dat: {from} - {to}", - "edit_description": "Edycja opisu", - "edit_description_prompt": "Wybierz nowy opis:", - "edit_exclusion_pattern": "Edytuj wzór wykluczający", - "edit_faces": "Edytuj twarze", - "edit_key": "Edytuj klucz", - "edit_link": "Edytuj link", - "edit_location": "Edytuj lokalizację", - "edit_location_action_prompt": "{count} edytowana lokalizacja", - "edit_location_dialog_title": "Lokalizacja", - "edit_name": "Edytuj nazwę", - "edit_people": "Edytuj osoby", - "edit_tag": "Edytuj etykietę", - "edit_title": "Edytuj Tytuł", - "edit_user": "Edytuj użytkownika", - "edit_workflow": "Edytuj przepływ pracy", - "editor": "Edytor", - "editor_close_without_save_prompt": "Zmiany nie zostaną zapisane", - "editor_close_without_save_title": "Zamknąć edytor?", - "editor_confirm_reset_all_changes": "Czy na pewno chcesz zresetować wszystkie zmiany?", - "editor_flip_horizontal": "Odwróć poziomo", - "editor_flip_vertical": "Odwróć pionowo", - "editor_orientation": "Orientacja", - "editor_reset_all_changes": "Zresetuj zmiany", - "editor_rotate_left": "Obróć o 90° przeciwnie do ruchu wskazówek zegara", - "editor_rotate_right": "Obróć o 90° zgodnie z ruchem wskazówek zegara", - "email": "E-mail", - "email_notifications": "Powiadomienia e-mail", - "empty_folder": "Ten folder jest pusty", - "empty_trash": "Opróżnij kosz", - "empty_trash_confirmation": "Czy na pewno chcesz opróżnić kosz? Spowoduje to trwałe usunięcie wszystkich zasobów znajdujących się w koszu z Immich.\nNie można cofnąć tej operacji!", - "enable": "Włącz", - "enable_backup": "Włącz kopię zapasową", - "enable_biometric_auth_description": "Wprowadź kod PIN aby włączyć logowanie biometryczne", - "enabled": "Włączone", - "end_date": "Do dnia", - "enqueued": "Kolejka", - "enter_wifi_name": "Wprowadź nazwę punktu dostępu Wi-Fi", - "enter_your_pin_code": "Wpisz swój kod PIN", - "enter_your_pin_code_subtitle": "Wprowadź twój kod PIN, aby uzyskać dostęp do folderu zablokowanego", - "error": "Błąd", - "error_change_sort_album": "Nie udało się zmienić kolejności sortowania albumów", - "error_delete_face": "Błąd podczas usuwania twarzy z zasobów", - "error_getting_places": "Błąd podczas pozyskiwania lokalizacji", - "error_loading_albums": "Błąd podczas ładowania albumów", - "error_loading_image": "Błąd podczas ładowania zdjęcia", - "error_loading_partners": "Błąd podczas ładowania partnerów: {error}", - "error_retrieving_asset_information": "Błąd podczas pobierania informacji o zasobie", - "error_saving_image": "Błąd: {error}", - "error_tag_face_bounding_box": "Błąd przy dodawaniu etykiety dla tej twarzy - nie może uzyskać współrzędnych granicznych", - "error_title": "Błąd - Coś poszło nie tak", - "error_while_navigating": "Błąd podczas przechodzenia do zasobu", - "errors": { - "cannot_navigate_next_asset": "Nie można przejść do następnego zasobu", - "cannot_navigate_previous_asset": "Nie można przejść do poprzedniego zasobu", - "cant_apply_changes": "Nie można zastosować zmian", - "cant_change_activity": "Nie można {enabled, select, true {wyłączyć} other {włączyć}} aktywności", - "cant_change_asset_favorite": "Nie można zmienić statusu ulubionego dla zasobu", - "cant_change_metadata_assets_count": "Nie można zmienić metadanych {count, plural, one {# zasobu} other {# zasobów}}", - "cant_get_faces": "Nie można pozyskać twarzy", - "cant_get_number_of_comments": "Nie można uzyskać liczby komentarzy", - "cant_search_people": "Nie można znaleźć osób", - "cant_search_places": "Nie można znaleźć miejsc", - "error_adding_assets_to_album": "Błąd dodania zasobów do albumu", - "error_adding_users_to_album": "Błąd dodania użytkowników do albumu", - "error_deleting_shared_user": "Błąd podczas usuwania udostępnionego użytkownika", - "error_downloading": "Błąd podczas pobierania pliku {filename}", - "error_hiding_buy_button": "Wystąpił błąd podczas ukrywania przycisku \"zakup\"", - "error_removing_assets_from_album": "Błąd usuwania zasobów z albumu, sprawdź konsolę w celu uzyskania więcej szczegółów", - "error_selecting_all_assets": "Błąd przy wybieraniu wszystkich zasobów", - "exclusion_pattern_already_exists": "Ten wzór wykluczający już istnieje.", - "failed_to_create_album": "Nie udało się utworzyć albumu", - "failed_to_create_shared_link": "Nie udało się utworzyć udostępnionego linku", - "failed_to_edit_shared_link": "Nie udało się edytować udostępnionego linku", - "failed_to_get_people": "Nie udało się pozyskać osób", - "failed_to_keep_this_delete_others": "Nie udało się zachować tego zasobu i usunąć innych zasobów", - "failed_to_load_asset": "Nie udało się załadować zasobu", - "failed_to_load_assets": "Nie udało się załadować zasobów", - "failed_to_load_notifications": "Nie udało się załadować powiadomień", - "failed_to_load_people": "Nie udało się pobrać ludzi", - "failed_to_remove_product_key": "Nie udało się usunąć klucza produktu", - "failed_to_reset_pin_code": "Nie udało się zresetować kodu PIN", - "failed_to_stack_assets": "Nie udało się utworzyć stosu z zasobów", - "failed_to_unstack_assets": "Nie udało się rozdzielić zasobów", - "failed_to_update_notification_status": "Nie udało się zaktualizować stanu powiadomienia", - "incorrect_email_or_password": "Nieprawidłowy e-mail lub hasło", - "library_folder_already_exists": "Ta ścieżka importu już istnieje.", - "paths_validation_failed": "{paths, plural, one {# ścieżka} few {# ścieżki} other {# ścieżek}}", - "profile_picture_transparent_pixels": "Zdjęcia profilowe nie mogą mieć przezroczystych pikseli. Powiększ i/lub przesuń obraz.", - "quota_higher_than_disk_size": "Ustawiony przez ciebie limit większy niż rozmiar dysku", - "something_went_wrong": "Coś poszło nie tak", - "unable_to_add_album_users": "Nie można dodać użytkowników do albumu", - "unable_to_add_assets_to_shared_link": "Nie można dodać zasobów do udostępnionego linku", - "unable_to_add_comment": "Nie można dodać komentarza", - "unable_to_add_exclusion_pattern": "Nie można dodać wzoru wykluczającego", - "unable_to_add_partners": "Nie można dodać partnerów", - "unable_to_add_remove_archive": "Nie można {archived, select, true {usunąć zasobu z} other {dodać zasobu do}} archiwum", - "unable_to_add_remove_favorites": "Nie można {favorite, select, true {dodać zasobu do} other {usunąć zasobu z }} ulubionych", - "unable_to_archive_unarchive": "Nie można {archived, select, true {zarchwizować} other {odarchiwizować}}", - "unable_to_change_album_user_role": "Nie można zmienić roli użytkownika albumu", - "unable_to_change_date": "Nie można zmienić daty", - "unable_to_change_description": "Nie udało się zmienić opisu", - "unable_to_change_favorite": "Nie można zmienić ulubionego zasobu", - "unable_to_change_location": "Nie można zmienić lokalizacji", - "unable_to_change_password": "Nie można zmienić hasła", - "unable_to_change_visibility": "Nie można zmienić widoczności dla {count, plural, one {# osoby} other {# osób}}", - "unable_to_complete_oauth_login": "Nie można ukończyć logowania przy użyciu OAuth", - "unable_to_connect": "Nie można się połączyć", - "unable_to_copy_to_clipboard": "Nie można skopiować do schowka, upewnij się, że łączysz się ze stroną przez https", - "unable_to_create": "Nie można utworzyć przepływu pracy", - "unable_to_create_admin_account": "Nie można utworzyć konta administratora", - "unable_to_create_api_key": "Nie można stworzyć Klucza API", - "unable_to_create_library": "Nie można stworzyć biblioteki", - "unable_to_create_user": "Nie można stworzyć użytkownika", - "unable_to_delete_album": "Nie można usunąć albumu", - "unable_to_delete_asset": "Nie można usunąć zasobu", - "unable_to_delete_assets": "Błąd podczas usuwania zasobów", - "unable_to_delete_exclusion_pattern": "Nie można usunąć wzoru wykluczającego", - "unable_to_delete_shared_link": "Nie można usunąć udostępnionego linku", - "unable_to_delete_user": "Nie można usunąć użytkownika", - "unable_to_delete_workflow": "Nie można usunąć przepływu pracy", - "unable_to_download_files": "Nie można pobrać plików", - "unable_to_edit_exclusion_pattern": "Nie można zmienić wzoru wykluczającego", - "unable_to_empty_trash": "Nie można opróżnić kosza", - "unable_to_enter_fullscreen": "Nie można przejść na pełny ekran", - "unable_to_exit_fullscreen": "Nie można wyjść z pełnego ekranu", - "unable_to_get_comments_number": "Nie udało się uzyskać liczby komentarzy", - "unable_to_get_shared_link": "Nie udało się uzyskać udostępnionego linku", - "unable_to_hide_person": "Ukrycie osoby nie powiodło się", - "unable_to_link_motion_video": "Nie można podłączyć ruchome wideo", - "unable_to_link_oauth_account": "Nie można powiązać konta OAuth", - "unable_to_log_out_all_devices": "Nie można wylogować wszystkich urządzeń", - "unable_to_log_out_device": "Nie można wylogować się z urządzenia", - "unable_to_login_with_oauth": "Nie można zalogować się za pomocą OAuth", - "unable_to_play_video": "Odtwarzanie filmu nie powiodło się", - "unable_to_reassign_assets_existing_person": "Nie można ponownie przypisać zasobów do {name,select, null {istniejącej osoby} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nie można ponownie przypisać zasobów nowej osobie", - "unable_to_refresh_user": "Odświeżenie użytkownika nie powiodło się", - "unable_to_remove_album_users": "Usunięcie użytkowników z albumu nie powiodło się", - "unable_to_remove_api_key": "Usunięcie Klucza API nie powiodło się", - "unable_to_remove_assets_from_shared_link": "Nie można usunąć zasobów z udostępnionego linku", - "unable_to_remove_library": "Usunięcie biblioteki nie powiodło się", - "unable_to_remove_partner": "Nie można usunąć partnerów", - "unable_to_remove_reaction": "Usunięcie reakcji nie powiodło się", - "unable_to_reset_password": "Zresetowanie hasła nie powiodło się", - "unable_to_reset_pin_code": "Zresetowanie kodu PIN nie powiodło się", - "unable_to_resolve_duplicate": "Usuwanie duplikatów nie powiodło się", - "unable_to_restore_assets": "Przywracanie zasobów nie powiodło się", - "unable_to_restore_trash": "Przywracanie zasobów z kosza nie powiodło się", - "unable_to_restore_user": "Przywracanie użytkownika nie powiodło się", - "unable_to_save_album": "Zapisywanie albumu nie powiodło się", - "unable_to_save_api_key": "Zapisywanie Klucza API nie powiodło się", - "unable_to_save_date_of_birth": "Nie można zapisać daty urodzenia", - "unable_to_save_name": "Zapisywanie nazwy nie powiodło się", - "unable_to_save_profile": "Nie można zapisać profilu", - "unable_to_save_settings": "Nie można zapisać ustawień", - "unable_to_scan_libraries": "Nie można przeskanować bibliotek", - "unable_to_scan_library": "Nie można przeskanować biblioteki", - "unable_to_set_feature_photo": "Nie można ustawić zdjęcia głównego", - "unable_to_set_profile_picture": "Nie można zmienić zdjęcia profilowego", - "unable_to_set_rating": "Nie można ustawić oceny", - "unable_to_submit_job": "Nie można przesłać zadania", - "unable_to_trash_asset": "Nie można przenieść zasobu do kosza", - "unable_to_unlink_account": "Nie można odłączyć konta", - "unable_to_unlink_motion_video": "Nie można odłączyć ruchomego wideo", - "unable_to_update_album_cover": "Nie można zaktualizować okładki albumu", - "unable_to_update_album_info": "Nie można zaktualizować informacji o albumie", - "unable_to_update_library": "Nie można zaktualizować biblioteki", - "unable_to_update_location": "Nie można zaktualizować lokalizacji", - "unable_to_update_settings": "Nie można zmienić ustawień", - "unable_to_update_timeline_display_status": "Nie można zaktualizować stanu wyświetlania na osi czasu", - "unable_to_update_user": "Nie można zmienić użytkownika", - "unable_to_update_workflow": "Nie można zaktualizować przepływu pracy", - "unable_to_upload_file": "Nie można przesłać pliku" - }, - "errors_text": "Błędy", - "exclusion_pattern": "Szablon wykluczeń", - "exif": "Exif", - "exif_bottom_sheet_description": "Dodaj Opis...", - "exif_bottom_sheet_description_error": "Wystąpił błąd podczas aktualizacji opisu", - "exif_bottom_sheet_details": "SZCZEGÓŁY", - "exif_bottom_sheet_location": "LOKALIZACJA", - "exif_bottom_sheet_no_description": "Brak opisu", - "exif_bottom_sheet_people": "LUDZIE", - "exif_bottom_sheet_person_add_person": "Dodaj nazwę", - "exit_slideshow": "Zamknij Pokaz Slajdów", - "expand_all": "Rozwiń wszystko", - "experimental_settings_new_asset_list_subtitle": "Praca w toku", - "experimental_settings_new_asset_list_title": "Włącz eksperymentalny układ zdjęć", - "experimental_settings_subtitle": "Używaj na własne ryzyko!", - "experimental_settings_title": "Eksperymentalny", - "expire_after": "Wygasa po", - "expired": "Wygasły", - "expires_date": "Wygasa {date}", - "explore": "Przeglądaj", - "explorer": "Eksplorator", - "export": "Eksportuj", - "export_as_json": "Eksportuj jako JSON", - "export_database": "Exportuj bazę danych", - "export_database_description": "Exportuj bazę danych SQLite", - "extension": "Rozszerzenie", - "external": "Zewnętrzny", - "external_libraries": "Biblioteki Zewnętrzne", - "external_network": "Sieć zewnętrzna", - "external_network_sheet_info": "Jeśli nie korzystasz z preferowanej sieci Wi-Fi aplikacja połączy się z serwerem za pośrednictwem pierwszego z dostępnych poniżej adresów URL, zaczynając od góry do dołu", - "face_unassigned": "Nieprzypisany", - "failed": "Niepowodzenie", - "failed_count": "Nie powiodło się: {count}", - "failed_to_authenticate": "Nie udało się uwierzytelnić", - "failed_to_load_assets": "Nie udało się załadować zasobów", - "failed_to_load_folder": "Nie udało się załadować folderu", - "favorite": "Ulubione", - "favorite_action_prompt": "{count} dodane do ulubionych", - "favorite_or_unfavorite_photo": "Dodaj lub usuń z ulubionych", - "favorites": "Ulubione", - "favorites_page_no_favorites": "Nie znaleziono ulubionych zasobów", - "feature_photo_updated": "Zdjęcie główne zaktualizowane pomyślnie", - "features": "Funkcje", - "features_in_development": "Funkcje w fazie rozwoju", - "features_setting_description": "Zarządzaj funkcjami aplikacji", - "file_name_or_extension": "Nazwie lub rozszerzeniu pliku", - "file_size": "Rozmiar pliku", - "filename": "Nazwa pliku", - "filetype": "Typ pliku", - "filter": "Filtr", - "filter_description": "Warunki filtrowania wybranych zasobów", - "filter_people": "Szukaj osoby", - "filter_places": "Filtruj miejsca", - "filters": "Filtry", - "find_them_fast": "Wyszukuj szybciej przypisując nazwę", - "first": "Pierwszy", - "fix_incorrect_match": "Napraw nieprawidłowe dopasowanie", - "folder": "Folder", - "folder_not_found": "Nie znaleziono folderu", - "folders": "Foldery", - "folders_feature_description": "Przeglądanie zdjęć i filmów w widoku folderów", - "forgot_pin_code_question": "Nie pamiętasz kodu PIN?", - "forward": "Do przodu", - "free_up_space": "Zwolnij miejsce w pamięci", - "free_up_space_description": "Przenieś zdjęcia i filmy z kopią zapasową do kosza urządzenia, aby zwolnić miejsce. Twoje kopie na serwerze pozostają bezpieczne.", - "free_up_space_settings_subtitle": "Zwolnij miejsce w pamięci urządzenia", - "full_path": "Pełna ścieżka: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ta funkcja , aby działać, ładuje zewnętrzne zasoby z Google.", - "general": "Ogólne", - "geolocation_instruction_location": "Kliknij na zasób z współrzędnymi GPS, aby użyć jego lokalizacji, lub wybierz lokalizację bezpośrednio z mapy", - "get_help": "Pomoc", - "get_people_error": "Błąd podczas pobierania osób", - "get_wifiname_error": "Nie można uzyskać nazwy Wi-Fi. Upewnij się, że udzieliłeś niezbędnych uprawnień i jesteś połączony z siecią Wi-Fi", - "getting_started": "Pierwsze kroki", - "go_back": "Wstecz", - "go_to_folder": "Idź do folderu", - "go_to_search": "Przejdź do wyszukiwania", - "gps": "GPS", - "gps_missing": "Brak GPS", - "grant_permission": "Udziel pozwolenia", - "group_albums_by": "Grupuj albumy...", - "group_country": "Grupuj według państwa", - "group_no": "Brak grupowania", - "group_owner": "Grupuj według właściciela", - "group_places_by": "Grupuj miejsca według...", - "group_year": "Grupuj według roku", - "haptic_feedback_switch": "Włącz technologię haptyczną", - "haptic_feedback_title": "Technologia haptyczna", - "has_quota": "Ma limit", - "hash_asset": "Hashuj zasób", - "hashed_assets": "Zahashowane zasoby", - "hashing": "Hashowanie", - "header_settings_add_header_tip": "Dodaj nagłówek", - "header_settings_field_validator_msg": "Wartość nie może być pusta", - "header_settings_header_name_input": "Nazwa nagłówka", - "header_settings_header_value_input": "Wartość nagłówka", - "headers_settings_tile_title": "Niestandardowe nagłówki proxy", - "height": "Wysokość", - "hi_user": "Cześć {name} ({email})", - "hide_all_people": "Ukryj wszystkie osoby", - "hide_gallery": "Ukryj galerię", - "hide_named_person": "Ukryj osobę {name}", - "hide_password": "Ukryj hasło", - "hide_person": "Ukryj osobę", - "hide_schema": "Ukryj schemat", - "hide_text_recognition": "Ukryj rozpoznawanie tekstu", - "hide_unnamed_people": "Ukryj nienazwaną osobę", - "home_page_add_to_album_conflicts": "Dodano {added} zasoby do albumu {album}. {failed} zasobów jest już w albumie.", - "home_page_add_to_album_err_local": "Nie można dodawać zasobów lokalnych do albumów, pomijam", - "home_page_add_to_album_success": "Dodano {added} zasoby do albumu {album}.", - "home_page_album_err_partner": "Nie można jeszcze dodać zasobów partnera do albumu, pomijam", - "home_page_archive_err_local": "Nie można jeszcze zarchiwizować zasobów lokalnych, pomijanie", - "home_page_archive_err_partner": "Nie można zarchiwizować zasobów partnera, pomijam", - "home_page_building_timeline": "Budowanie osi czasu", - "home_page_delete_err_partner": "Nie można usunąć zasobów partnera, pomijam", - "home_page_delete_remote_err_local": "Lokalne zasoby w wyborze do zdalnego usunięcia, pomijam", - "home_page_favorite_err_local": "Nie można jeszcze dodać do ulubionych lokalnych zasobów, pomijam", - "home_page_favorite_err_partner": "Nie można jeszcze dodać do ulubionych zasobów partnera, pomijam", - "home_page_first_time_notice": "Jeśli korzystasz z aplikacji po raz pierwszy, pamiętaj o wybraniu albumów do kopii zapasowej, aby oś czasu mogła wypełnić się zdjęciami i filmami", - "home_page_locked_error_local": "Nie można przenieść zasobów lokalnych do folderu zablokowanego, pomijam", - "home_page_locked_error_partner": "Nie można przenieść zasobów partnera do folderu zablokowanego, pomijam", - "home_page_share_err_local": "Nie można udostępnić zasobów lokalnych za pośrednictwem linku, pomijam", - "home_page_upload_err_limit": "Można przesłać maksymalnie 30 zasobów jednocześnie, pomijanie", - "host": "Host", - "hour": "Godzina", - "hours": "Godziny", - "id": "ID", - "idle": "Bezczynny", - "ignore_icloud_photos": "Ignoruj zdjęcia w iCloud", - "ignore_icloud_photos_description": "Zdjęcia przechowywane w usłudze iCloud nie zostaną przesłane na serwer Immich", - "image": "Zdjęcie", - "image_alt_text_date": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione dnia {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione z {person1} dnia {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione z {person1} i {person2} dnia {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione z {person1}, {person2} i {person3} dnia {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione z {person1}, {person2} i {additionalCount, number} innymi dnia {date}", - "image_alt_text_date_place": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} dnia {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1} dnia {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1} i {person2} dnia {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1}, {person2} i {person3} dnia {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1}, {person2} i {additionalCount, number} innymi dnia {date}", - "image_saved_successfully": "Obraz zapisany", - "image_viewer_page_state_provider_download_started": "Pobieranie rozpoczęte", - "image_viewer_page_state_provider_download_success": "Pobieranie zakończone", - "image_viewer_page_state_provider_share_error": "Udostępnij błąd", - "immich_logo": "Logo Immich", - "immich_web_interface": "Interfejs internetowy Immich", - "import_from_json": "Wczytaj z JSON", - "import_path": "Ścieżka importu", - "in_albums": "W {count, plural, one {# albumie} other {# albumach}}", - "in_archive": "W archiwum", - "in_year": "W {year}", - "in_year_selector": "W", - "include_archived": "Uwzględnij zarchiwizowane", - "include_shared_albums": "Uwzględnij udostępnione albumy", - "include_shared_partner_assets": "Uwzględnij udostępnione zasoby partnera", - "individual_share": "Udostępniony zasób", - "individual_shares": "Indywidualne udziały", - "info": "Informacje", - "interval": { - "day_at_onepm": "Codziennie o 13:00", - "hours": "Co {hours, plural, one {godzinę} few {{hours, number} godziny} other {{hours, number} godzin}}", - "night_at_midnight": "Codziennie o północy", - "night_at_twoam": "Codziennie o 02:00" - }, - "invalid_date": "Nieprawidłowa data", - "invalid_date_format": "Nieprawidłowy format daty", - "invite_people": "Zaproś Osoby", - "invite_to_album": "Zaproś do albumu", - "ios_debug_info_fetch_ran_at": "Pobieranie przebiegło {dateTime}", - "ios_debug_info_last_sync_at": "Ostatnia synchronizacja {dateTime}", - "ios_debug_info_no_processes_queued": "Brak procesów w tle w kolejce", - "ios_debug_info_no_sync_yet": "Nie uruchomiono jeszcze żadnego zadania synchronizacji w tle", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proces w tle w kolejce} few {{count} procesy w tle w kolejce} other {{count} procesów w tle w kolejce}}", - "ios_debug_info_processing_ran_at": "Przetwarzanie przebiegło {dateTime}", - "items_count": "{count, plural, one {# element} few {# elementy} other {# elementów}}", - "jobs": "Zadania", - "json_editor": "Edytor JSON", - "json_error": "Błąd JSON", - "keep": "Zachowaj", - "keep_albums": "Zachowaj albumy", - "keep_albums_count": "Zachowuję {count} {count, plural, one {album} few {albumy} other {albumów}}", - "keep_all": "Zachowaj wszystko", - "keep_description": "Wybierz, co zachować na Twoim urządzeniu przy zwalnianiu miejsca.", - "keep_favorites": "Zachowaj ulubione", - "keep_on_device": "Zachowaj na urządzeniu", - "keep_on_device_hint": "Wybierz elementy, które chcesz zachować na tym urządzeniu", - "keep_this_delete_others": "Zachowaj to, usuń pozostałe", - "keeping": "Zachowuję:{items}", - "kept_this_deleted_others": "Zachowano ten zasób i usunięto {count, plural, one {#zasób} other {#zasoby}}", - "keyboard_shortcuts": "Skróty klawiaturowe", - "language": "Język", - "language_no_results_subtitle": "Spróbuj dostosować wyszukiwaną frazę", - "language_no_results_title": "Nie znaleziono żadnych języków", - "language_search_hint": "Szukaj języków...", - "language_setting_description": "Wybierz swój preferowany język", - "large_files": "Duże pliki", - "last": "Ostatni", - "last_months": "{count, plural, one {Zeszły miesiąc} few {# zeszłe miesiące} other {# zeszłych miesięcy}}", - "last_seen": "Ostatnio widziane", - "latest_version": "Najnowsza wersja", - "latitude": "Szerokość geograficzna", - "leave": "Opuść", - "leave_album": "Opuść album", - "lens_model": "Model obiektywu", - "let_others_respond": "Pozwól innym reagować", - "level": "Poziom", - "library": "Biblioteka", - "library_add_folder": "Dodaj folder", - "library_edit_folder": "Edytuj folder", - "library_options": "Opcje biblioteki", - "library_page_device_albums": "Albumy na Urządzeniu", - "library_page_new_album": "Nowy album", - "library_page_sort_asset_count": "Liczba zasobów", - "library_page_sort_created": "Ostatnio utworzone", - "library_page_sort_last_modified": "Ostatnio zmodyfikowany", - "library_page_sort_title": "Tytuł albumu", - "licenses": "Licencje", - "light": "Jasny", - "like": "Polub", - "like_deleted": "Polubienie usunięte", - "link_motion_video": "Podłącz ruchome wideo", - "link_to_oauth": "Połącz z OAuth", - "linked_oauth_account": "Połączone konto OAuth", - "list": "Lista", - "loading": "Ładowanie", - "loading_search_results_failed": "Ładowanie wyników wyszukiwania nie powiodło się", - "local": "Lokalny", - "local_asset_cast_failed": "Nie można strumieniować zasobu, który nie został przesłany na serwer", - "local_assets": "Zasoby lokalne", - "local_id": "Lokalne ID", - "local_media_summary": "Podsumowanie lokalnych mediów", - "local_network": "Sieć lokalna", - "local_network_sheet_info": "Aplikacja połączy się z serwerem za pośrednictwem tego adresu URL podczas korzystania z określonej sieci Wi-Fi", - "location": "Lokalizacja", - "location_permission": "Zezwolenie na lokalizację", - "location_permission_content": "Aby móc korzystać z funkcji automatycznego przełączania, Immich potrzebuje uprawnienia do dokładnej lokalizacji, aby móc odczytać nazwę bieżącej sieci Wi-Fi", - "location_picker_choose_on_map": "Wybierz na mapie", - "location_picker_latitude_error": "Wprowadź prawidłową szerokość geograficzną", - "location_picker_latitude_hint": "Wpisz tutaj swoją szerokość geograficzną", - "location_picker_longitude_error": "Wprowadź prawidłową długość geograficzną", - "location_picker_longitude_hint": "Wpisz tutaj swoją długość geograficzną", - "lock": "Zablokuj", - "locked_folder": "Folder zablokowany", - "log_detail_title": "Szczegóły dziennika", - "log_out": "Wyloguj", - "log_out_all_devices": "Wyloguj ze Wszystkich Urządzeń", - "logged_in_as": "Zalogowano jako {user}", - "logged_out_all_devices": "Wylogowano ze wszystkich urządzeń", - "logged_out_device": "Wylogowany z urządzenia", - "login": "Logowanie", - "login_disabled": "Logowanie zostało wyłączone", - "login_form_api_exception": "Wyjątek API. Sprawdź adres URL serwera i spróbuj ponownie.", - "login_form_back_button_text": "Cofnij", - "login_form_email_hint": "twojmail@email.com", - "login_form_endpoint_hint": "http://ip-twojego-serwera:port", - "login_form_endpoint_url": "URL Serwera", - "login_form_err_http": "Proszę określić http:// lub https://", - "login_form_err_invalid_email": "Niepoprawny Email", - "login_form_err_invalid_url": "Nieprawidłowy URL", - "login_form_err_leading_whitespace": "Białe znaki", - "login_form_err_trailing_whitespace": "Białe znaki po przecinku", - "login_form_failed_get_oauth_server_config": "Błąd logowania przy użyciu OAuth. Sprawdź adres URL serwera", - "login_form_failed_get_oauth_server_disable": "Funkcja OAuth nie jest dostępna na tym serwerze", - "login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło", - "login_form_handshake_exception": "Wystąpił wyjątek uzgadniania z serwerem. Włącz obsługę certyfikatów z podpisem własnym w ustawieniach, jeśli używasz certyfikatu z podpisem własnym.", - "login_form_password_hint": "hasło", - "login_form_save_login": "Pozostań zalogowany", - "login_form_server_empty": "Wprowadź adres URL serwera.", - "login_form_server_error": "Nie można połączyć się z serwerem.", - "login_has_been_disabled": "Logowanie zostało wyłączone.", - "login_password_changed_error": "Wystąpił błąd podczas aktualizacji hasła", - "login_password_changed_success": "Hasło zostało zmienione", - "logout_all_device_confirmation": "Czy na pewno chcesz wylogować się ze wszystkich urządzeń?", - "logout_this_device_confirmation": "Czy na pewno chcesz wylogować to urządzenie?", - "logs": "Logi", - "longitude": "Długość geograficzna", - "look": "Wygląd", - "loop_videos": "Powtarzaj filmy", - "loop_videos_description": "Włącz automatyczne odtwarzanie w pętli filmu w widoku szczegółowym.", - "main_branch_warning": "Używasz wersji deweloperskiej. Zdecydowanie zalecamy korzystanie z wydanej wersji aplikacji!", - "main_menu": "Menu główne", - "maintenance_action_restore": "Przywracanie bazy danych", - "maintenance_description": "Immich został przełączony w tryb konserwacji.", - "maintenance_end": "Zakończ tryb konserwacji", - "maintenance_end_error": "Nie udało się zakończyć trybu konserwacji.", - "maintenance_logged_in_as": "Obecnie zalogowano jako {user}", - "maintenance_restore_from_backup": "Przywróć z kopii zapasowej", - "maintenance_restore_library": "Przywróć swoją bibliotekę", - "maintenance_restore_library_confirm": "Jeśli wszystko wygląda poprawnie, kontynuuj przywracanie kopii zapasowej!", - "maintenance_restore_library_description": "Przywracanie bazy danych", - "maintenance_restore_library_folder_has_files": "{folder} zawiera {count} folder(ów)", - "maintenance_restore_library_folder_no_files": "W {folder} brakuje plików!", - "maintenance_restore_library_folder_pass": "z uprawnieniami odczytu i zapisu", - "maintenance_restore_library_folder_read_fail": "brak uprawnień do odczytu", - "maintenance_restore_library_folder_write_fail": "brak uprawnień do zapisu", - "maintenance_restore_library_hint_missing_files": "Być może brakuje ważnych plików", - "maintenance_restore_library_hint_regenerate_later": "Możesz je później odtworzyć w ustawieniach", - "maintenance_restore_library_hint_storage_template_missing_files": "Korzystasz z Szablonu Magazynu? Być może brakuje Ci plików", - "maintenance_restore_library_loading": "Ładowanie kontroli integralności i heurystyki…", - "maintenance_task_backup": "Tworzenie kopii zapasowej istniejącej bazy danych…", - "maintenance_task_migrations": "Przeprowadzanie migracji bazy danych…", - "maintenance_task_restore": "Przywracanie wybranej kopii zapasowej…", - "maintenance_task_rollback": "Przywracanie nie powiodło się, powrót do punktu przywracania…", - "maintenance_title": "Tymczasowo niedostępne", - "make": "Marka", - "manage_geolocation": "Zarządzaj lokalizacją", - "manage_media_access_rationale": "To uprawnienie jest wymagane do prawidłowej obsługi przenoszenia zasobów do kosza i przywracania ich z niego.", - "manage_media_access_settings": "Otwórz ustawienia", - "manage_media_access_subtitle": "Zezwól aplikacji Immich na zarządzanie plikami multimedialnymi i przenoszenie ich.", - "manage_media_access_title": "Dostęp do zarządzania mediami", - "manage_shared_links": "Zarządzaj udostępnionymi linkami", - "manage_sharing_with_partners": "Zarządzaj dzieleniem z partnerami", - "manage_the_app_settings": "Zarządzaj ustawieniami aplikacji", - "manage_your_account": "Zarządzaj swoim kontem", - "manage_your_api_keys": "Zarządzaj swoimi kluczami API", - "manage_your_devices": "Zarządzaj swoimi zalogowanymi urządzeniami", - "manage_your_oauth_connection": "Zarządzaj swoim połączeniem OAuth", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {Brak zdjęć w tym obszarze} one {# zdjęcie} other {# zdjęć}}", - "map_cannot_get_user_location": "Nie można uzyskać lokalizacji użytkownika", - "map_location_dialog_yes": "Tak", - "map_location_picker_page_use_location": "Użyj tej lokalizacji", - "map_location_service_disabled_content": "Aby wyświetlić zasoby z Twojej bieżącej lokalizacji, należy włączyć usługę lokalizacyjną. Czy chcesz to teraz włączyć?", - "map_location_service_disabled_title": "Usługa lokalizacji wyłączona", - "map_marker_for_images": "Wskaźnik mapy dla zdjęć zrobionych w {city}, {country}", - "map_marker_with_image": "Znacznik na mapie ze zdjęciem", - "map_no_location_permission_content": "Aby wyświetlić zasoby z Twojej bieżącej lokalizacji, potrzebne jest pozwolenie na lokalizację. Czy chcesz teraz na to pozwolić?", - "map_no_location_permission_title": "Odmowa dostępu do lokalizacji", - "map_settings": "Ustawienia mapy", - "map_settings_dark_mode": "Tryb ciemny", - "map_settings_date_range_option_day": "Ostatnie 24 godziny", - "map_settings_date_range_option_days": "Minione {days} dni", - "map_settings_date_range_option_year": "Miniony rok", - "map_settings_date_range_option_years": "Ostatnie {years} lat", - "map_settings_dialog_title": "Ustawienia mapy", - "map_settings_include_show_archived": "Uwzględnij zarchiwizowane", - "map_settings_include_show_partners": "Uwzględnij partnerów", - "map_settings_only_show_favorites": "Pokaż tylko ulubione", - "map_settings_theme_settings": "Motyw mapy", - "map_zoom_to_see_photos": "Pomniejsz, aby zobaczyć zdjęcia", - "mark_all_as_read": "Zaznacz wszystkie jako odczytane", - "mark_as_read": "Zaznacz jako odczytane", - "marked_all_as_read": "Zaznaczono wszystkie jako przeczytane", - "matches": "Powiązania", - "matching_assets": "Pasujące zasoby", - "media_type": "Typ zasobu", - "memories": "Wspomnienia", - "memories_all_caught_up": "Wszystko złapane", - "memories_check_back_tomorrow": "Wróć jutro po więcej wspomnień", - "memories_setting_description": "Zarządzaj wspomnieniami", - "memories_start_over": "Zacznij od nowa", - "memories_swipe_to_close": "Przesuń w górę, aby zamknąć", - "memory": "Pamięć", - "memory_lane_title": "Aleja Wspomnień {title}", - "menu": "Menu", - "merge": "Złącz", - "merge_people": "Złącz osoby", - "merge_people_limit": "Możesz łączyć maksymalnie 5 twarzy naraz", - "merge_people_prompt": "Czy chcesz połączyć te osoby? Ta czynność jest nieodwracalna.", - "merge_people_successfully": "Pomyślnie złączono osoby", - "merged_people_count": "Połączono {count, plural, one {# osobę} few {# osoby} other {# osób}}", - "minimize": "Zminimalizuj", - "minute": "Minuta", - "minutes": "Minuty", - "mirror_horizontal": "Poziomo", - "mirror_vertical": "Pionowo", - "missing": "Brakujące", - "mobile_app": "Aplikacja mobilna", - "mobile_app_download_onboarding_note": "Pobierz towarzyszącą aplikację mobilną, korzystając z następujących opcji", - "model": "Model", - "month": "Miesiąc", - "monthly_title_text_date_format": "MMMM y", - "more": "Więcej", - "move": "Przenieś", - "move_down": "Przesuń w dół", - "move_off_locked_folder": "Przenieś z folderu zablokowanego", - "move_to": "Przenieś do", - "move_to_device_trash": "Przenieś do kosza urządzenia", - "move_to_lock_folder_action_prompt": "{count} dodanych do folderu zablokowanego", - "move_to_locked_folder": "Przenieś do folderu zablokowanego", - "move_to_locked_folder_confirmation": "Te zdjęcia i filmy zostaną usunięte ze wszystkich albumów i będą widzialne tylko w folderze zablokowanym", - "move_up": "Przesuń w górę", - "moved_to_archive": "Przeniesiono {count, plural, one {# zasób} few {# zasoby} other {# zasobów}} do archiwum", - "moved_to_library": "Przeniesiono {count, plural, one {# zasób} few {# zasoby} other {# zasobów}} do biblioteki", - "moved_to_trash": "Przeniesiono do kosza", - "multiselect_grid_edit_date_time_err_read_only": "Nie można edytować daty zasobów tylko do odczytu, pomijanie", - "multiselect_grid_edit_gps_err_read_only": "Nie można edytować lokalizacji zasobów tylko do odczytu, pomijanie", - "mute_memories": "Wycisz wspomnienia", - "my_albums": "Moje albumy", - "name": "Nazwa", - "name_or_nickname": "Nazwa lub pseudonim", - "name_required": "Imię jest wymagane", - "navigate": "Nawiguj", - "navigate_to_time": "Nawiguj do czasu", - "network_requirement_photos_upload": "Używaj danych komórkowych do tworzenia kopii zapasowych zdjęć", - "network_requirement_videos_upload": "Używaj danych komórkowych do tworzenia kopii zapasowych filmów", - "network_requirements": "Wymagania sieciowe", - "network_requirements_updated": "Zmieniono wymagania sieciowe, resetowanie kolejki kopii zapasowych", - "networking_settings": "Sieć", - "networking_subtitle": "Zarządzaj ustawieniami punktu końcowego serwera", - "never": "nigdy", - "new_album": "Nowy album", - "new_api_key": "Nowy Klucz API", - "new_date_range": "Nowy zakres dat", - "new_password": "Nowe hasło", - "new_person": "Nowa osoba", - "new_pin_code": "Nowy kod PIN", - "new_pin_code_subtitle": "Jest to pierwszy raz, kiedy wchodzisz do folderu zablokowanego. Utwórz kod PIN, aby bezpiecznie korzystać z tej strony", - "new_timeline": "Nowa oś czasu", - "new_update": "Nowa aktualizacja", - "new_user_created": "Pomyślnie stworzono nowego użytkownika", - "new_version_available": "NOWA WERSJA DOSTĘPNA", - "newest_first": "Od najnowszych", - "next": "Dalej", - "next_memory": "Następne wspomnienie", - "no": "Nie", - "no_actions_added": "Nie dodano jeszcze żadnych akcji", - "no_albums_found": "Nie znaleziono albumów", - "no_albums_message": "Stwórz album, aby organizować Twoje zdjęcia i filmy", - "no_albums_with_name_yet": "Wygląda na to, że nie masz jeszcze żadnych albumów o tej nazwie.", - "no_albums_yet": "Wygląda na to, że nie masz jeszcze żadnych albumów.", - "no_archived_assets_message": "Archiwizuj zdjęcia i filmy, aby ukryć je ze strony Zdjęcia", - "no_assets_message": "Kliknij, aby przesłać swoje pierwsze zdjęcie", - "no_assets_to_show": "Brak zasobów do pokazania", - "no_cast_devices_found": "Nie znaleziono urządzeń do przesyłania strumieniowego", - "no_checksum_local": "Brak sumy kontrolnej - nie można pobrać lokalnych zasobów", - "no_checksum_remote": "Brak sumy kontrolnej - nie można pobrać zdalnego zasobu", - "no_configuration_needed": "Nie wymaga konfiguracji", - "no_devices": "Brak autoryzowanych urządzeń", - "no_duplicates_found": "Nie znaleziono duplikatów.", - "no_exif_info_available": "Nie znaleziono informacji exif", - "no_explore_results_message": "Prześlij więcej zdjęć, aby przeglądać swój zbiór.", - "no_favorites_message": "Dodaj ulubione aby szybko znaleźć swoje najlepsze zdjęcia i filmy", - "no_filters_added": "Nie dodano jeszcze żadnych filtrów", - "no_libraries_message": "Stwórz bibliotekę zewnętrzną, aby przeglądać swoje zdjęcia i filmy", - "no_local_assets_found": "Nie znaleziono żadnych lokalnych zasobów o tej sumie kontrolnej", - "no_location_set": "Nie ustawiono lokalizacji", - "no_locked_photos_message": "Zdjęcia i filmy w folderze zablokowanym są ukryte i nie będą wyświetlane podczas przeglądania biblioteki.", - "no_name": "Brak Nazwy", - "no_notifications": "Brak powiadomień", - "no_people_found": "Brak pasujących osób", - "no_places": "Brak miejsc", - "no_remote_assets_found": "Nie znaleziono żadnych zdalnych zasobów o tej sumie kontrolnej", - "no_results": "Brak wyników", - "no_results_description": "Spróbuj użyć synonimu lub bardziej ogólnego słowa kluczowego", - "no_shared_albums_message": "Stwórz album aby udostępnić zdjęcia i filmy osobom w Twojej sieci", - "no_uploads_in_progress": "Brak przesyłań w toku", - "none": "Żadne", - "not_allowed": "Niedozwolone", - "not_available": "Nie dotyczy", - "not_in_any_album": "Bez albumu", - "not_selected": "Nie wybrano", - "note_apply_storage_label_to_previously_uploaded assets": "Uwaga: Aby przypisać etykietę magazynowania do wcześniej przesłanych zasobów, uruchom", - "notes": "Uwagi", - "nothing_here_yet": "Nic tu jeszcze nie ma", - "notification_permission_dialog_content": "Aby włączyć powiadomienia, przejdź do Ustawień i wybierz opcję Zezwalaj.", - "notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.", - "notification_permission_list_tile_enable_button": "Włącz Powiadomienia", - "notification_permission_list_tile_title": "Pozwolenie na powiadomienia", - "notification_toggle_setting_description": "Włącz powiadomienia e-mail", - "notifications": "Powiadomienia", - "notifications_setting_description": "Zarządzanie powiadomieniami", - "oauth": "OAuth", - "obtainium_configurator": "Konfigurator Obtainium", - "obtainium_configurator_instructions": "Użyj Obtainium, aby zainstalować i zaktualizować aplikację na Androida bezpośrednio z wydania GitHuba Immich. Utwórz klucz API i wybierz wariant, aby utworzyć link konfiguracyjny Obtainium", - "ocr": "OCR", - "official_immich_resources": "Oficjalne zasoby Immicha", - "offline": "Offline", - "offset": "Przesunięcie", - "ok": "Ok", - "oldest_first": "Od najstarszych", - "on_this_device": "Na tym urządzeniu", - "onboarding": "Wdrożenie", - "onboarding_locale_description": "Wybierz preferowany język. Można to później zmienić w ustawieniach.", - "onboarding_privacy_description": "Następujące (opcjonalne) funkcje opierają się na usługach zewnętrznych i można je w dowolnym momencie wyłączyć w ustawieniach.", - "onboarding_server_welcome_description": "Skonfigurujmy twoją instancję z kilkoma typowymi ustawieniami.", - "onboarding_theme_description": "Wybierz motyw kolorystyczny dla twojej instancji. Możesz go później zmienić w ustawieniach.", - "onboarding_user_welcome_description": "Zaczynamy!", - "onboarding_welcome_user": "Witaj, {user}", - "online": "Połączony", - "only_favorites": "Tylko ulubione", - "open": "Otwórz", - "open_in_map_view": "Otwórz w widoku mapy", - "open_in_openstreetmap": "Otwórz w OpenStreetMap", - "open_the_search_filters": "Otwórz filtry wyszukiwania", - "options": "Opcje", - "or": "lub", - "organize_into_albums": "Uporządkuj w albumy", - "organize_into_albums_description": "Umieść istniejące zdjęcia w albumach przy użyciu bieżących ustawień synchronizacji", - "organize_your_library": "Organizuj swoją bibliotekę", - "original": "oryginalny", - "other": "Inne", - "other_devices": "Inne urządzenia", - "other_entities": "Inne byty", - "other_variables": "Inne zmienne", - "owned": "Posiadane", - "owner": "Właściciel", - "page": "Strona", - "partner": "Partner", - "partner_can_access": "{partner} ma dostęp do", - "partner_can_access_assets": "Wszystkie Twoje zdjęcia i filmy, oprócz tych w Archiwum i Koszu", - "partner_can_access_location": "Informacji o tym, gdzie zostały zrobione Twoje zdjęcia", - "partner_list_user_photos": "Zdjęcia należące do {user}", - "partner_list_view_all": "Pokaż wszystkie", - "partner_page_empty_message": "Twoje zdjęcia nie są udostępnione żadnemu partnerowi.", - "partner_page_no_more_users": "Brak użytkowników do dodania", - "partner_page_partner_add_failed": "Nie udało się dodać partnera", - "partner_page_select_partner": "Wybierz partnera", - "partner_page_shared_to_title": "Udostępniono", - "partner_page_stop_sharing_content": "{partner} nie będzie już mieć dostępu do Twoich zdjęć.", - "partner_sharing": "Dzielenie z partnerami", - "partners": "Partnerzy", - "password": "Hasło", - "password_does_not_match": "Hasła nie są takie same", - "password_required": "Hasło Wymagane", - "password_reset_success": "Hasło zostało zresetowane", - "past_durations": { - "days": "{days, plural, one {Ostatni dzień} other {Ostatnie {days, number} dni}}", - "hours": "{hours, plural, one {Ostatnia godzina} few {Ostatnie {hours, number} godziny} other {Ostatnie {hours, number} godzin}}", - "years": "{years, plural, one {Ostatni rok} few {Ostatnie {years, number} lata} other {Ostatnie {years, number} lat}}" - }, - "path": "Ścieżka", - "pattern": "Wzór", - "pause": "Wstrzymaj", - "pause_memories": "Wstrzymaj wspomnienia", - "paused": "Wstrzymane", - "pending": "Oczekujące", - "people": "Osoby", - "people_edits_count": "Edytowano {count, plural, one {# osoba} few {# osoby} many {# osób} other {# osób}}", - "people_feature_description": "Przeglądanie zdjęć i filmów pogrupowanych według osób", - "people_selected": "{count, plural, one {# osoba wybrana} few {# osoby wybrane} other {# osób wybranych}}", - "people_sidebar_description": "Pokazuj link do Osób w panelu bocznym", - "permanent_deletion_warning": "Ostrzeżenie o trwałym usunięciu", - "permanent_deletion_warning_setting_description": "Pokaż ostrzeżenie przy trwałym usuwaniu zasobów", - "permanently_delete": "Usuń trwale", - "permanently_delete_assets_count": "Trwale usuń {count, plural, one {zasób} few {zasoby} many {zasobów} other {zasobów}}", - "permanently_delete_assets_prompt": "Czy na pewno chcesz trwale usunąć {count, plural, one {ten zasób?} few {te # zasoby?} other {te # zasobów?}} Spowoduje to również usunięcie {count, plural, one {go z jego} other {ich z ich}} album(ów).", - "permanently_deleted_asset": "Pomyślnie trwale usunięto zasób", - "permanently_deleted_assets_count": "Trwale usunięto {count, plural, one {# zasób} other {# zasobów}}", - "permission": "Pozwolenie", - "permission_empty": "Twoje pozwolenie nie powinno być puste", - "permission_onboarding_back": "Cofnij", - "permission_onboarding_continue_anyway": "Kontynuuj mimo to", - "permission_onboarding_get_started": "Rozpocznij", - "permission_onboarding_go_to_settings": "Przejdź do ustawień", - "permission_onboarding_permission_denied": "Odmowa pozwolenia. Aby korzystać z Immich, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.", - "permission_onboarding_permission_granted": "Pozwolenie udzielone! Wszystko gotowe.", - "permission_onboarding_permission_limited": "Pozwolenie ograniczone. Aby umożliwić Immichowi tworzenie kopii zapasowych całej kolekcji galerii i zarządzanie nią, przyznaj uprawnienia do zdjęć i filmów w Ustawieniach.", - "permission_onboarding_request": "Immich potrzebuje pozwolenia na przeglądanie Twoich zdjęć i filmów.", - "person": "Osoba", - "person_age_months": "{months, plural, one {# miesiąc} few {# miesiące} many {# miesięcy} other {# miesięcy}}", - "person_age_year_months": "1 rok, {months, plural, one {# miesiąc} few {# miesiące} many {# miesięcy} other {# miesięcy}}", - "person_age_years": "{years, plural, one {# rok} few {# lata} many {# lat} other {# lat}}", - "person_birthdate": "Urodzony {date}", - "person_hidden": "{name}{hidden, select, true { (ukryty)} other {}}", - "person_recognized": "Osoba rozpoznana", - "person_selected": "Osoba wybrana", - "photo_shared_all_users": "Wygląda na to, że udostępniłeś swoje zdjęcia wszystkim użytkownikom lub nie masz żadnego użytkownika, z którym można by było je udostępnić.", - "photos": "Zdjęcia", - "photos_and_videos": "Zdjęcia i Filmy", - "photos_count": "{count, plural, one {{count, number} Zdjęcie} few {{count, number} Zdjęcia} other {{count, number} Zdjęć}}", - "photos_from_previous_years": "Zdjęcia z ubiegłych lat", - "photos_only": "Tylko zdjęcia", - "pick_a_location": "Oznacz lokalizację", - "pick_custom_range": "Zakres niestandardowy", - "pick_date_range": "Wybierz zakres dat", - "pin_code_changed_successfully": "Pomyślnie zmieniono kod PIN", - "pin_code_reset_successfully": "Pomyślnie zresetowano kod PIN", - "pin_code_setup_successfully": "Pomyślnie ustawiono kod PIN", - "pin_verification": "Weryfikacja kodem PIN", - "place": "Miejsce", - "places": "Miejsca", - "places_count": "{count, plural, one {{count, number} Miejsce} few {{count, number} Miejsca}other {{count, number} Miejsc}}", - "play": "Odtwórz", - "play_memories": "Odtwórz wspomnienia", - "play_motion_photo": "Odtwórz Ruchome Zdjęcie", - "play_or_pause_video": "Odtwórz lub wstrzymaj wideo", - "play_original_video": "Odtwórz oryginalne wideo", - "play_original_video_setting_description": "Preferuj odtwarzanie oryginalnych nagrań wideo zamiast nagrań transkodowanych. Jeśli oryginalny zasób nie jest kompatybilny, może nie być odtwarzany poprawnie.", - "play_transcoded_video": "Odtwórz transkodowane wideo", - "please_auth_to_access": "Uwierzytelnij się, aby uzyskać dostęp", - "port": "Port", - "preferences_settings_subtitle": "Zarządzaj preferencjami aplikacji", - "preferences_settings_title": "Ustawienia", - "preparing": "Przygotowywanie", - "preset": "Ustawienie", - "preview": "Podgląd", - "previous": "Poprzedni", - "previous_memory": "Poprzednie wspomnienie", - "previous_or_next_day": "Dzień następny/poprzedni", - "previous_or_next_month": "Miesiąc następny/poprzedni", - "previous_or_next_photo": "Zdjęcie następne/poprzednie", - "previous_or_next_year": "Rok następny/poprzedni", - "primary": "Główny", - "privacy": "Prywatność", - "profile": "Profil", - "profile_drawer_app_logs": "Logi", - "profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Włączono tryb tylko do odczytu. Aby wyjść, naciśnij i przytrzymaj ikonę awatara użytkownika.", - "profile_image_of_user": "Zdjęcie profilowe {user}", - "profile_picture_set": "Zdjęcie profilowe ustawione.", - "public_album": "Publiczny album", - "public_share": "Udostępnienie publiczne", - "purchase_account_info": "Wspierający", - "purchase_activated_subtitle": "Dziękuję za wspieranie Immich i oprogramowania open-source", - "purchase_activated_time": "Aktywowane dnia {date}", - "purchase_activated_title": "Twój klucz został pomyślnie aktywowany", - "purchase_button_activate": "Aktywuj", - "purchase_button_buy": "Kup", - "purchase_button_buy_immich": "Kup Immich", - "purchase_button_never_show_again": "Nie pokazuj ponownie", - "purchase_button_reminder": "Przypomnij za 30 dni", - "purchase_button_remove_key": "Usuń klucz", - "purchase_button_select": "Wybierz", - "purchase_failed_activation": "Nie udało się aktywować! Sprawdź swój email, aby uzyskać prawidłowy klucz produktu!", - "purchase_individual_description_1": "Dla osoby prywatnej", - "purchase_individual_description_2": "Status wspierającego", - "purchase_individual_title": "Osoba Prywatna", - "purchase_input_suggestion": "Posiadasz klucz produktu? Wpisz go poniżej", - "purchase_license_subtitle": "Kup Immich, aby wesprzeć jego dalszy rozwój", - "purchase_lifetime_description": "Jednorazowy zakup", - "purchase_option_title": "OPCJE ZAKUPU", - "purchase_panel_info_1": "Tworzenie Immich wymaga dużo czasu i wysiłku, a nasi inżynierowie pracują nad tym na pełen etat, aby uczynić go jak najlepszym. Naszą misją jest, aby oprogramowanie open-source i etyczne praktyki biznesowe stały się zrównoważonym źródłem dochodu dla deweloperów oraz stworzyć ekosystem szanujący prywatność z prawdziwymi alternatywami dla eksploatacyjnych usług w chmurze.", - "purchase_panel_info_2": "Ponieważ zobowiązujemy się do niewprowadzania paywalli, ten zakup nie zapewni Ci dodatkowych funkcji w Immich. Polegamy na użytkownikach takich jak Ty, aby wspierać ciągły rozwój Immich.", - "purchase_panel_title": "Wsparcie projektu", - "purchase_per_server": "Per serwer", - "purchase_per_user": "Per użytkownik", - "purchase_remove_product_key": "Usuń klucz produktu", - "purchase_remove_product_key_prompt": "Czy na pewno chcesz usunąć klucz produktu?", - "purchase_remove_server_product_key": "Usuń klucz produktu serwera", - "purchase_remove_server_product_key_prompt": "Czy na pewno chcesz usunąć klucz produktu serwera?", - "purchase_server_description_1": "Dla całego serwera", - "purchase_server_description_2": "Status wspierającego", - "purchase_server_title": "Serwer", - "purchase_settings_server_activated": "Klucz produktu serwera jest zarządzany przez administratora", - "query_asset_id": "Zapytanie o ID zasobu", - "queue_status": "Kolejkowanie {count}/{total}", - "rate_asset": "Oceń zasób", - "rating": "Ocena gwiazdkowa", - "rating_clear": "Wyczyść ocenę", - "rating_count": "{count, plural, one {# gwiazdka} other {# gwiazdek}}", - "rating_description": "Wyświetl ocenę z EXIF w panelu informacji", - "rating_set": "Ocena ustawiona na {rating, plural, one {# gwiazdkę} few {# gwiazdki} other {# gwiazdek}}", - "reaction_options": "Opcje reakcji", - "read_changelog": "Zobacz Zmiany", - "readonly_mode_disabled": "Tryb tylko do odczytu wyłączony", - "readonly_mode_enabled": "Tryb tylko do odczytu włączony", - "ready_for_upload": "Gotowe do przesłania", - "reassign": "Przypisz ponownie", - "reassigned_assets_to_existing_person": "Przypisano ponownie {count, plural, one {# zasób} other {# zasobów}} do {name, select, null {istniejącej osoby} other {{name}}}", - "reassigned_assets_to_new_person": "Przypisano ponownie {count, plural, one {# zasób} other {# zasobów}} do nowej osoby", - "reassing_hint": "Przypisz wybrane zasoby do istniejącej osoby", - "recent": "Ostatnie", - "recent-albums": "Ostatnie albumy", - "recent_searches": "Ostatnie wyszukiwania", - "recently_added": "Ostatnio dodane", - "recently_added_page_title": "Ostatnio Dodane", - "recently_taken": "Ostatnio wykonane", - "recently_taken_page_title": "Ostatnio Wykonane", - "refresh": "Odśwież", - "refresh_encoded_videos": "Odśwież enkodowane wideo", - "refresh_faces": "Odśwież twarze", - "refresh_metadata": "Odśwież metadane", - "refresh_thumbnails": "Odśwież miniatury", - "refreshed": "Odświeżone", - "refreshes_every_file": "Ponownie odczytuje wszystkie istniejące i nowe pliki", - "refreshing_encoded_video": "Odświeżanie enkodowanych wideo", - "refreshing_faces": "Odświeżanie twarzy", - "refreshing_metadata": "Odświeżanie metadanych", - "regenerating_thumbnails": "Regenerowanie miniatur", - "remote": "Zdalny", - "remote_assets": "Zasoby zdalne", - "remote_media_summary": "Podsumowanie mediów zdalnych", - "remove": "Usuń", - "remove_assets_album_confirmation": "Czy na pewno chcesz usunąć {count, plural, one {# zasób} few {# zasoby} other {# zasobów}} z albumu?", - "remove_assets_shared_link_confirmation": "Czy na pewno chcesz usunąć {count, plural, one {# zasób} other {# zasoby}} z tego udostępnionego linku?", - "remove_assets_title": "Usunąć zasoby?", - "remove_custom_date_range": "Usuń niestandardowy zakres dat", - "remove_deleted_assets": "Usuń Niedostępne Pliki", - "remove_from_album": "Usuń z albumu", - "remove_from_album_action_prompt": "{count} usunięto z albumu", - "remove_from_favorites": "Usuń z ulubionych", - "remove_from_lock_folder_action_prompt": "{count} usunięte z folderu zablokowanego", - "remove_from_locked_folder": "Usuń z folderu zablokowanego", - "remove_from_locked_folder_confirmation": "Czy na pewno chcesz przenieść te zdjęcia i filmy z folderu zablokowanego? Będą one widoczne w bibliotece.", - "remove_from_shared_link": "Usuń z udostępnionego linku", - "remove_memory": "Usuń wspomnienie", - "remove_photo_from_memory": "Usuń zdjęcia z tych wspomnień", - "remove_tag": "Usuń tag", - "remove_url": "Usuń URL", - "remove_user": "Usuń użytkownika", - "removed_api_key": "Usunięto Klucz API: {name}", - "removed_from_archive": "Usunięto z archiwum", - "removed_from_favorites": "Usunięto z ulubionych", - "removed_from_favorites_count": "{count, plural, other {Usunięto #}} z ulubionych", - "removed_memory": "Wspomnienie usunięte", - "removed_photo_from_memory": "Usunięto zdjęcie ze wspomnień", - "removed_tagged_assets": "Usunięto etykietę z {count, plural, one {# zasobu} other {# zasobów}}", - "rename": "Zmień nazwę", - "repair": "Napraw", - "repair_no_results_message": "Tutaj pojawią się nieśledzone i brakujące pliki", - "replace_with_upload": "Prześlij nową wersję", - "repository": "Repozytorium", - "require_password": "Wymagaj hasła", - "require_user_to_change_password_on_first_login": "Zmuś użytkownika do zmiany hasła podczas następnego logowania", - "rescan": "Ponowne skanowanie", - "reset": "Reset", - "reset_password": "Resetuj hasło", - "reset_people_visibility": "Zresetuj widoczność osób", - "reset_pin_code": "Zresetuj kod PIN", - "reset_pin_code_description": "Jeśli zapomniałeś swojego kodu PIN, możesz skontaktować się z administratorem serwera, aby go zresetować", - "reset_pin_code_success": "Pomyślnie zresetowano kod PIN", - "reset_pin_code_with_password": "Zawsze możesz zresetować swój kod PIN za pomocą hasła", - "reset_sqlite": "Zresetuj bazę danych SQLite", - "reset_sqlite_confirmation": "Czy na pewno chcesz zresetować bazę danych SQLite? Wymagane będzie wylogowanie oraz ponowne zalogowanie, aby zsynchronizować dane", - "reset_sqlite_success": "Pomyślnie zresetowano bazę danych SQLite", - "reset_to_default": "Przywróć ustawienia domyślne", - "resolution": "Rozdzielczość", - "resolve_duplicates": "Rozwiąż problemy z duplikatami", - "resolved_all_duplicates": "Rozwiązano wszystkie duplikaty", - "restore": "Przywrócić", - "restore_all": "Przywróć wszystko", - "restore_trash_action_prompt": "{count} przywrócono z kosza", - "restore_user": "Przywróć użytkownika", - "restored_asset": "Przywrócony zasób", - "resume": "Wznów", - "resume_paused_jobs": "Wznów {count, plural, one {# wstrzymane zadanie} few {# wstrzymane zadania} other {# wstrzymanych zadań}}", - "retry_upload": "Prześlij ponownie", - "review_duplicates": "Przejrzyj duplikaty", - "review_large_files": "Przejrzyj duże pliki", - "role": "Rola", - "role_editor": "Edytor", - "role_viewer": "Widz", - "running": "W trakcie", - "save": "Zapisz", - "save_to_gallery": "Zapisz w galerii", - "saved": "Zapisano", - "saved_api_key": "Zapisany klucz API", - "saved_profile": "Zapisany profil", - "saved_settings": "Zapisane ustawienia", - "say_something": "Powiedz coś", - "scaffold_body_error_occurred": "Wystąpił błąd", - "scan": "Skanuj", - "scan_all_libraries": "Skanuj wszystkie biblioteki", - "scan_library": "Skanuj", - "scan_settings": "Ustawienia Skanowania", - "scanning": "Skanowanie", - "scanning_for_album": "Skanuję album...", - "search": "Szukaj", - "search_albums": "Przeszukaj albumy", - "search_by_context": "Wyszukaj według treści", - "search_by_description": "Wyszukaj według opisu", - "search_by_description_example": "Całodniowa wycieczka w Bieszczady", - "search_by_filename": "Szukaj według nazwy pliku lub rozszerzenia", - "search_by_filename_example": "np. IMG_1234.JPG lub PNG", - "search_by_ocr": "Wyszukaj przy użyciu OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Wyszukaj model obiektywu...", - "search_camera_make": "Wyszukaj markę aparatu...", - "search_camera_model": "Wyszukaj model aparatu...", - "search_city": "Wyszukaj miasto...", - "search_country": "Wyszukaj kraj...", - "search_filter_apply": "Zastosuj filtr", - "search_filter_camera_title": "Wybierz typ kamery", - "search_filter_date": "Data", - "search_filter_date_interval": "{start} do {end}", - "search_filter_date_title": "Wybierz zakres dat", - "search_filter_display_option_not_in_album": "Nie w albumie", - "search_filter_display_options": "Opcje wyświetlania", - "search_filter_filename": "Wyszukaj po nazwie pliku", - "search_filter_location": "Lokalizacja", - "search_filter_location_title": "Wybierz lokalizację", - "search_filter_media_type": "Typ multimediów", - "search_filter_media_type_title": "Wybierz typ multimediów", - "search_filter_ocr": "Wyszukaj przy użyciu OCR", - "search_filter_people_title": "Wybierz osoby", - "search_filter_star_rating": "Ocena gwiazdkowa", - "search_for": "Szukaj wśród", - "search_for_existing_person": "Wyszukaj istniejącą osobę", - "search_no_more_result": "Brak dalszych wyników", - "search_no_people": "Brak osób", - "search_no_people_named": "Brak osób nazwanych \"{name}\"", - "search_no_result": "Nie znaleziono wyników, spróbuj innego terminu wyszukiwania lub kombinacji", - "search_options": "Opcje wyszukiwania", - "search_page_categories": "Kategorie", - "search_page_motion_photos": "Zdjęcia ruchome", - "search_page_no_objects": "Brak informacji o obiektach", - "search_page_no_places": "Brak informacji o miejscu", - "search_page_screenshots": "Zrzuty ekranu", - "search_page_search_photos_videos": "Wyszukaj swoje zdjęcia i filmy", - "search_page_selfies": "Selfiki", - "search_page_things": "Rzeczy", - "search_page_view_all_button": "Pokaż wszystkie", - "search_page_your_activity": "Twoja aktywność", - "search_page_your_map": "Twoja mapa", - "search_people": "Wyszukaj osoby", - "search_places": "Wyszukaj miejsca", - "search_rating": "Wyszukaj według ocen...", - "search_result_page_new_search_hint": "Nowe wyszukiwanie", - "search_settings": "Ustawienia przeszukiwania", - "search_state": "Wyszukaj województwo...", - "search_suggestion_list_smart_search_hint_1": "Inteligentne wyszukiwanie jest domyślnie włączone, aby wyszukiwać metadane, użyj składni ", - "search_suggestion_list_smart_search_hint_2": "m:wyszukiwane hasło", - "search_tags": "Wyszukaj etykiety...", - "search_timezone": "Wyszukaj strefę czasową...", - "search_type": "Wyszukaj w", - "search_your_photos": "Przeszukaj swoje zdjęcia", - "searching_locales": "Wyszukaj region...", - "second": "Sekunda", - "see_all_people": "Zobacz wszystkie osoby", - "select": "Wybierz", - "select_album": "Wybierz album", - "select_album_cover": "Wybierz okładkę albumu", - "select_albums": "Wybierz albumy", - "select_all": "Zaznacz wszystko", - "select_all_duplicates": "Wybierz wszystkie duplikaty", - "select_all_in": "Wybierz wszystkie w {group}", - "select_avatar_color": "Wybierz kolor awatara", - "select_count": "{count, plural, one {Wybierz #} other {Wybierz #}}", - "select_cutoff_date": "Wybierz datę graniczną", - "select_face": "Wybierz twarz", - "select_featured_photo": "Zmień główne zdjęcie", - "select_from_computer": "Wybierz z komputera", - "select_keep_all": "Zaznacz zachowaj wszystko", - "select_library_owner": "Wybierz właściciela biblioteki", - "select_new_face": "Wybierz nową twarz", - "select_people": "Wybierz osoby", - "select_person": "Wybierz osobę", - "select_person_to_tag": "Wybierz osobę do oznaczenia", - "select_photos": "Wybierz zdjęcia", - "select_trash_all": "Zaznacz wszystko do kosza", - "select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu", - "selected": "Wybrane", - "selected_count": "{count, plural, other {# wybrane}}", - "selected_gps_coordinates": "Wybrane Współrzędne GPS", - "send_message": "Wyślij wiadomość", - "send_welcome_email": "Wyślij e-mail powitalny", - "server_endpoint": "Punkt końcowy serwera", - "server_info_box_app_version": "Wersja aplikacji", - "server_info_box_server_url": "Adres URL", - "server_offline": "Serwer Offline", - "server_online": "Serwer Online", - "server_privacy": "Ochrona prywatności serwera", - "server_restarting_description": "Ta strona zostanie za chwilę odświeżona.", - "server_restarting_title": "Trwa ponowne uruchamianie serwera", - "server_stats": "Statystyki serwera", - "server_update_available": "Dostępna jest aktualizacja serwera", - "server_version": "Wersja serwera", - "set": "Ustaw", - "set_as_album_cover": "Ustaw jako okładkę albumu", - "set_as_featured_photo": "Ustaw jako wyróżnione zdjęcie", - "set_as_profile_picture": "Ustaw jako zdjęcie profilowe", - "set_date_of_birth": "Ustaw datę urodzenia", - "set_profile_picture": "Ustaw zdjęcie profilowe", - "set_slideshow_to_fullscreen": "Ustaw Pokaz slajdów na pełny ekran", - "set_stack_primary_asset": "Ustaw jako główny zasób", - "setting_image_viewer_help": "Przeglądarka szczegółów najpierw ładuje małą miniaturę, następnie ładuje podgląd obrazu średniej wielkości (jeśli jest włączony), a na koniec ładuje oryginał (jeśli jest włączony).", - "setting_image_viewer_original_subtitle": "Włącz, aby załadować oryginalny obraz w pełnej rozdzielczości (duży!). Wyłącz, aby zmniejszyć zużycie danych (zarówno w sieci, jak i w pamięci podręcznej urządzenia).", - "setting_image_viewer_original_title": "Załaduj oryginalny obraz", - "setting_image_viewer_preview_subtitle": "Włącz, aby załadować obraz w średniej rozdzielczości. Wyłącz, aby załadować bezpośrednio oryginał lub używać tylko miniatury.", - "setting_image_viewer_preview_title": "Załaduj podgląd obrazu", - "setting_image_viewer_title": "Zdjęcia", - "setting_languages_apply": "Zastosuj", - "setting_languages_subtitle": "Zmień język aplikacji", - "setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {duration}", - "setting_notifications_notify_hours": "{count} godzin", - "setting_notifications_notify_immediately": "natychmiast", - "setting_notifications_notify_minutes": "{count} minut", - "setting_notifications_notify_never": "nigdy", - "setting_notifications_notify_seconds": "{count} sekund", - "setting_notifications_single_progress_subtitle": "Szczegółowe informacje o postępie przesyłania dla każdego zasobu", - "setting_notifications_single_progress_title": "Pokaż postęp szczegółów kopii zapasowej w tle", - "setting_notifications_subtitle": "Dostosuj preferencje powiadomień", - "setting_notifications_total_progress_subtitle": "Ogólny postęp przesyłania (gotowe/całkowite zasoby)", - "setting_notifications_total_progress_title": "Pokaż całkowity postęp tworzenia kopii zapasowej w tle", - "setting_video_viewer_auto_play_subtitle": "Automatycznie rozpoczynaj odtwarzanie filmów po ich otwarciu", - "setting_video_viewer_auto_play_title": "Automatycznie odtwarzaj filmy", - "setting_video_viewer_looping_title": "Zapętlanie", - "setting_video_viewer_original_video_subtitle": "Podczas strumieniowego przesyłania wideo z serwera odtwarzaj oryginał, nawet jeśli transkodowanie jest dostępne. Może to prowadzić do buforowania. Filmy dostępne lokalnie są odtwarzane w oryginalnej jakości niezależnie od tego ustawienia.", - "setting_video_viewer_original_video_title": "Wymuś oryginalne wideo", - "settings": "Ustawienia", - "settings_require_restart": "Aby zastosować to ustawienie, uruchom ponownie Immich", - "settings_saved": "Ustawienia zapisane", - "setup_pin_code": "Ustaw kod PIN", - "share": "Udostępnij", - "share_action_prompt": "Udostępniono {count} zasobów", - "share_add_photos": "Dodaj zdjęcia", - "share_assets_selected": "Wybrano {count}", - "share_dialog_preparing": "Przygotowywanie…", - "share_link": "Udostępnij link", - "shared": "Udostępnione", - "shared_album_activities_input_disable": "Komentarz jest wyłączony", - "shared_album_activity_remove_content": "Czy chcesz usunąć tę aktywność?", - "shared_album_activity_remove_title": "Usuń aktywność", - "shared_album_section_people_action_error": "Błąd podczas opuszczania/usuwania z albumu", - "shared_album_section_people_action_leave": "Usuń użytkownika z albumu", - "shared_album_section_people_action_remove_user": "Usuń użytkownika z albumu", - "shared_album_section_people_title": "LUDZIE", - "shared_by": "Udostępnione przez", - "shared_by_user": "Udostępnione przez {user}", - "shared_by_you": "Udostępnione przez ciebie", - "shared_from_partner": "Zdjęcia od {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Przesłano", - "shared_link_app_bar_title": "Udostępnione linki", - "shared_link_clipboard_copied_massage": "Skopiowane do schowka", - "shared_link_clipboard_text": "Link: {link}\nHasło: {password}", - "shared_link_create_error": "Błąd podczas tworzenia linka do udostępnienia", - "shared_link_custom_url_description": "Otwórz udostępniony link z niestandardowym adresem URL", - "shared_link_edit_description_hint": "Wprowadź opis udostępnienia", - "shared_link_edit_expire_after_option_day": "1 dniu", - "shared_link_edit_expire_after_option_days": "{count} dniach", - "shared_link_edit_expire_after_option_hour": "1 godzinie", - "shared_link_edit_expire_after_option_hours": "{count} godzinach", - "shared_link_edit_expire_after_option_minute": "1 minucie", - "shared_link_edit_expire_after_option_minutes": "{count} minutach", - "shared_link_edit_expire_after_option_months": "{count} miesiącach", - "shared_link_edit_expire_after_option_year": "{count} roku", - "shared_link_edit_password_hint": "Wprowadź hasło udostępniania", - "shared_link_edit_submit_button": "Aktualizuj link", - "shared_link_error_server_url_fetch": "Nie można pobrać adresu URL serwera", - "shared_link_expires_day": "Wygasa za {count} dzień", - "shared_link_expires_days": "Wygasa za {count} dni", - "shared_link_expires_hour": "Wygasa za {count} godzinę", - "shared_link_expires_hours": "Wygasa za {count} godzin(y)", - "shared_link_expires_minute": "Wygasa za {count} minutę", - "shared_link_expires_minutes": "Wygasa za {count} minut(y)", - "shared_link_expires_never": "Wygasa ∞", - "shared_link_expires_second": "Wygasa za {count} sekundę", - "shared_link_expires_seconds": "Wygasa za {count} sekund(y)", - "shared_link_individual_shared": "Indywidualnie udostępnione", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Zarządzaj udostępnionymi linkami", - "shared_link_options": "Opcje udostępniania linku", - "shared_link_password_description": "Wymagaj hasła dostępu dla udostępnionego linku", - "shared_links": "Udostępnione linki", - "shared_links_description": "Udostępnij zdjęcia oraz filmy przez link", - "shared_photos_and_videos_count": "{assetCount, plural, one {# udostępnione zdjęcie lub film.} other {# udostępnione zdjęcia i filmy.}}", - "shared_with_me": "Udostępniono mi", - "shared_with_partner": "Dzielisz się z {partner}", - "sharing": "Udostępnianie", - "sharing_enter_password": "Wprowadź hasło, aby wyświetlić tę stronę.", - "sharing_page_album": "Udostępnione albumy", - "sharing_page_description": "Twórz współdzielone albumy, aby udostępniać zdjęcia i filmy osobom w twojej sieci.", - "sharing_page_empty_list": "PUSTA LISTA", - "sharing_sidebar_description": "Wyświetl link do udostępniania na pasku bocznym", - "sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album", - "sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi", - "shift_to_permanent_delete": "naciśnij ⇧, aby trwale usunąć zasób", - "show_album_options": "Pokaż opcje albumu", - "show_albums": "Pokaż albumy", - "show_all_people": "Pokaż wszystkie osoby", - "show_and_hide_people": "Pokaż lub ukryj osoby", - "show_file_location": "Pokaż ścieżkę pliku", - "show_gallery": "Wyświetl galerię", - "show_hidden_people": "Pokaż ukryte osoby", - "show_in_timeline": "Pokaż na osi czasu", - "show_in_timeline_setting_description": "Pokaż zdjęcia i filmy tego użytkownika na swojej osi czasu", - "show_keyboard_shortcuts": "Pokaż skróty klawiaturowe", - "show_metadata": "Pokaż metadane", - "show_or_hide_info": "Pokaż lub ukryj informacje", - "show_password": "Pokaż hasło", - "show_person_options": "Pokaż opcje osoby", - "show_progress_bar": "Pokaż pasek postępu", - "show_schema": "Pokaż schemat", - "show_search_options": "Wyświetl opcje wyszukiwania", - "show_shared_links": "Pokaż udostępniane linki", - "show_slideshow_transition": "Pokaż przejście pokazu slajdów", - "show_supporter_badge": "Odznaka wspierającego", - "show_supporter_badge_description": "Pokaż odznakę wspierającego", - "show_text_recognition": "Pokaż rozpoznawanie tekstu", - "show_text_search_menu": "Pokaż menu wyszukiwania tekstowego", - "shuffle": "Losuj", - "sidebar": "Panel boczny", - "sidebar_display_description": "Wyświetl link do widoku w pasku bocznym", - "sign_out": "Wyloguj się", - "sign_up": "Zarejestruj się", - "size": "Rozmiar", - "skip_to_content": "Przejdź do treści", - "skip_to_folders": "Przejdź do folderów", - "skip_to_tags": "Przejdź do tagów", - "slideshow": "Pokaz slajdów", - "slideshow_repeat": "Powtórz pokaz slajdów", - "slideshow_repeat_description": "Zapętl pokaz slajdów", - "slideshow_settings": "Ustawienia pokazu slajdów", - "sort_albums_by": "Sortuj albumy według...", - "sort_created": "Data utworzenia", - "sort_items": "Liczba rzeczy", - "sort_modified": "Data modyfikacji", - "sort_newest": "Najnowsze zdjęcie", - "sort_oldest": "Najstarsze zdjęcie", - "sort_people_by_similarity": "Sortuj twarze według cech podobnych", - "sort_recent": "Najnowsze zdjęcie", - "sort_title": "Tytuł", - "source": "Źródło", - "stack": "Stos", - "stack_action_prompt": "{count} zgrupowano", - "stack_duplicates": "Stos duplikatów", - "stack_select_one_photo": "Wybierz jedno główne zdjęcie dla stosu", - "stack_selected_photos": "Utwórz stos z wybranych zdjęć", - "stacked_assets_count": "Utworzono stos z {count, plural, one {# zasobu} other {# zasobów}}", - "stacktrace": "Ślad stosu", - "start": "Start", - "start_date": "Od dnia", - "start_date_before_end_date": "Data początkowa musi być wcześniejsza niż data końcowa", - "state": "Województwo", - "status": "Status", - "stop_casting": "Zatrzymaj strumieniowanie", - "stop_motion_photo": "Zatrzymaj zdjęcie w ruchu", - "stop_photo_sharing": "Przestać udostępniać swoje zdjęcia?", - "stop_photo_sharing_description": "{partner} nie będzie już mieć dostępu do Twoich zdjęć.", - "stop_sharing_photos_with_user": "Przestań udostępniać zdjęcia temu użytkownikowi", - "storage": "Przestrzeń dyskowa", - "storage_label": "Etykieta magazynu", - "storage_quota": "Limit pamięci", - "storage_usage": "Wykorzystano {used} z {available}", - "submit": "Zatwierdź", - "success": "Sukces", - "suggestions": "Sugestie", - "sunrise_on_the_beach": "Wschód słońca na plaży", - "support": "Wsparcie", - "support_and_feedback": "Wsparcie i opinie", - "support_third_party_description": "Twoja instalacja immich została spakowana przez trzecią stronę. Problemy, które napotykasz, mogą być spowodowane przez ten pakiet, więc w pierwszej kolejności zgłaszaj problemy u nich, korzystając z poniższych linków.", - "swap_merge_direction": "Zmień kierunek złączenia", - "sync": "Synchronizuj", - "sync_albums": "Synchronizuj albumy", - "sync_albums_manual_subtitle": "Zsynchronizuj wszystkie przesłane filmy i zdjęcia z wybranymi albumami z włączoną kopią zapasową", - "sync_local": "Synchronizacja lokalna", - "sync_remote": "Synchronizacja zdalna", - "sync_status": "Stan synchronizacji", - "sync_status_subtitle": "Wyświetl i zarządzaj systemem synchronizacji", - "sync_upload_album_setting_subtitle": "Twórz i przesyłaj swoje zdjęcia i filmy do wybranych albumów w Immich", - "tag": "Etykieta", - "tag_assets": "Ustaw etykiety zasobów", - "tag_created": "Stworzono etykietę: {tag}", - "tag_feature_description": "Przeglądanie zdjęć i filmów pogrupowanych według logicznych etykiet wskazujących temat", - "tag_not_found_question": "Nie możesz znaleźć etykiety? Utwórz ją tutaj", - "tag_people": "Dodaj etykiety osób", - "tag_updated": "Uaktualniono etykietę: {tag}", - "tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}", - "tags": "Etykiety", - "tap_to_run_job": "Uruchom zadanie", - "template": "Szablon", - "text_recognition": "Rozpoznawanie tekstu", - "theme": "Motyw", - "theme_selection": "Wybór motywu", - "theme_selection_description": "Automatycznie zmień motyw na jasny lub ciemny zależnie od ustawień przeglądarki", - "theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów", - "theme_setting_asset_list_tiles_per_row_title": "Liczba zasobów w wierszu ({count})", - "theme_setting_colorful_interface_subtitle": "Zastosuj kolor podstawowy do powierzchni tła.", - "theme_setting_colorful_interface_title": "Kolorowy interfejs", - "theme_setting_image_viewer_quality_subtitle": "Dostosuj jakość podglądu szczegółowości", - "theme_setting_image_viewer_quality_title": "Jakość przeglądania obrazów", - "theme_setting_primary_color_subtitle": "Wybierz kolor dla głównych działań i akcentów.", - "theme_setting_primary_color_title": "Kolor podstawowy", - "theme_setting_system_primary_color_title": "Użyj koloru systemowego", - "theme_setting_system_theme_switch": "Automatyczny (Postępuj zgodnie z ustawieniami systemu)", - "theme_setting_theme_subtitle": "Wybierz ustawienia motywu aplikacji", - "theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci", - "theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania", - "then": "Wtedy", - "they_will_be_merged_together": "Zostaną one ze sobą połączone", - "third_party_resources": "Zasoby stron trzecich", - "time": "Czas", - "time_based_memories": "Wspomnienia oparte na czasie", - "time_based_memories_duration": "Ilość sekund, przez jaką wyświetlane jest poszczególne zdjęcie.", - "timeline": "Oś czasu", - "timezone": "Strefa czasowa", - "to_archive": "Zarchiwizuj", - "to_change_password": "Zmień hasło", - "to_favorite": "Dodaj do ulubionych", - "to_login": "Zaloguj się", - "to_multi_select": "aby wybrać wiele", - "to_parent": "Idź do rodzica", - "to_select": "aby wybrać", - "to_trash": "Kosz", - "toggle_settings": "Przełącz ustawienia", - "toggle_theme_description": "Przełącz motyw", - "total": "Razem", - "total_usage": "Całkowite wykorzystanie", - "trash": "Kosz", - "trash_action_prompt": "{count} przeniesione do kosza", - "trash_all": "Usuń wszystkie", - "trash_count": "Usuń {count, number}", - "trash_delete_asset": "Kosz/Usuń zasób", - "trash_emptied": "Opróżnione śmieci", - "trash_no_results_message": "Tu znajdziesz wyrzucone zdjęcia i filmy.", - "trash_page_delete_all": "Usuń wszystko", - "trash_page_empty_trash_dialog_content": "Czy chcesz opróżnić swoje usunięte zasoby? Przedmioty te zostaną trwale usunięte z Immich", - "trash_page_info": "Elementy przeniesione do kosza zostaną trwale usunięte po {days} dniach", - "trash_page_no_assets": "Brak usuniętych zasobów", - "trash_page_restore_all": "Przywrócić wszystkie", - "trash_page_select_assets_btn": "Wybierz zasoby", - "trash_page_title": "Kosz ({count})", - "trashed_items_will_be_permanently_deleted_after": "Wyrzucone zasoby zostaną trwale usunięte po {days, plural, one {jednym dniu} other {# dniach}}.", - "trigger": "Wyzwalacz", - "trigger_asset_uploaded": "Przesłano zasób", - "trigger_asset_uploaded_description": "Wyzwalane gdy przesłany zostanie nowy zasób", - "trigger_description": "Wydarzenie, które uruchamia przepływ pracy", - "trigger_person_recognized": "Osoba rozpoznana", - "trigger_person_recognized_description": "Wyzwalane gdy zostanie wykryta osoba", - "trigger_type": "Rodzaj wyzwalacza", - "troubleshoot": "Rozwiąż problemy", - "type": "Typ", - "unable_to_change_pin_code": "Nie można zmienić kodu PIN", - "unable_to_check_version": "Nie można sprawdzić wersji aplikacji lub serwera", - "unable_to_setup_pin_code": "Nie można ustawić kodu PIN", - "unarchive": "Przywróć z archiwum", - "unarchive_action_prompt": "{count} usunięto z archiwum", - "unarchived_count": "{count, plural, one {# cofnięta archiwizacja} few {# cofnięte archiwizacje} other {# cofniętych archiwizacji}}", - "undo": "Cofnij", - "unfavorite": "Usuń z ulubionych", - "unfavorite_action_prompt": "{count} usunięto z ulubionych", - "unhide_person": "Przywróć osobę", - "unknown": "Nieznany", - "unknown_country": "Nieznane państwo", - "unknown_date": "Nieznana data", - "unknown_year": "Rok nieznany", - "unlimited": "Nieograniczony", - "unlink_motion_video": "Rozłącz ruchome wideo", - "unlink_oauth": "Odłącz OAuth", - "unlinked_oauth_account": "Odłączone konto OAuth", - "unmute_memories": "Włącz dźwięk wspomnień", - "unnamed_album": "Nienazwany album", - "unnamed_album_delete_confirmation": "Czy jesteś pewna/pewien, że chcesz usunąć te album?", - "unnamed_share": "Nienazwany udział", - "unsaved_change": "Niezapisana zmiana", - "unselect_all": "Odznacz wszystko", - "unselect_all_duplicates": "Odznacz wszystkie duplikaty", - "unselect_all_in": "Odznacz wszystkie w {group}", - "unstack": "Rozdziel stos", - "unstack_action_prompt": "{count} rozdzielono", - "unstacked_assets_count": "Rozdzielono {count, plural, one {# zasób} few {# zasoby} other {# zasobów}}", - "unsupported_field_type": "Nieobsługiwany typ pola", - "untagged": "Nieoznaczone", - "untitled_workflow": "Przepływ pracy bez tytułu", - "up_next": "Do następnego", - "update_location_action_prompt": "Zaktualizuj lokalizację {count} wybranych zasobów na:", - "updated_at": "Zaktualizowany", - "updated_password": "Pomyślnie zaktualizowano hasło", - "upload": "Prześlij", - "upload_concurrency": "Współbieżność wysyłania", - "upload_details": "Szczegóły przesyłania", - "upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobów na serwerze?", - "upload_dialog_title": "Prześlij Zasób", - "upload_error_with_count": "Błąd przesyłania dla {count, plural, one {# zasobu} other {# zasobów}}", - "upload_errors": "Przesyłanie zakończone z {count, plural, one {# błędem} other {# błędami}}. Odśwież stronę, aby zobaczyć nowo przesłane zasoby.", - "upload_finished": "Przesyłanie zakończone", - "upload_progress": "Pozostałe {remaining, number} - Przetworzone {processed, number}/{total, number}", - "upload_skipped_duplicates": "Pominięto {count, plural, one {# zduplikowany zasób} few {# zduplikowane zasoby} other {# zduplikowanych zasobów}}", - "upload_status_duplicates": "Duplikaty", - "upload_status_errors": "Błędy", - "upload_status_uploaded": "Przesłano", - "upload_success": "Przesyłanie powiodło się, odśwież stronę, aby zobaczyć nowo przesłane zasoby.", - "upload_to_immich": "Prześlij do Immich ({count})", - "uploading": "Przesyłanie", - "uploading_media": "Przesyłanie multimediów", - "url": "URL", - "usage": "Użycie", - "use_biometric": "Użyj biometrii", - "use_current_connection": "Użyj bieżącego połączenia", - "use_custom_date_range": "Zamiast tego użyj niestandardowego zakresu dat", - "user": "Użytkownik", - "user_has_been_deleted": "Ten użytkownik został usunięty.", - "user_id": "ID użytkownika", - "user_liked": "{user} polubił {type, select, photo {to zdjęcie} video {to wideo} asset {ten zasób} other {to}}", - "user_pin_code_settings": "Kod PIN", - "user_pin_code_settings_description": "Zarządzaj swoim kodem PIN", - "user_privacy": "Ochrona prywatności użytkownika", - "user_purchase_settings": "Zakup", - "user_purchase_settings_description": "Zarządzaj swoim zakupem", - "user_role_set": "Ustaw {user} jako {role}", - "user_usage_detail": "Szczegóły używania przez użytkownika", - "user_usage_stats": "Statystyki użytkowania konta", - "user_usage_stats_description": "Wyświetl statystyki użytkowania konta", - "username": "Nazwa użytkownika", - "users": "Użytkownicy", - "users_added_to_album_count": "Dodano {count, plural, one {# użytkownika} other {# użytkowników}} do albumu", - "utilities": "Narzędzia", - "validate": "Walidacja", - "validate_endpoint_error": "Proszę wprowadzić prawidłowy adres URL", - "validation_error": "Błąd walidacji", - "variables": "Zmienne", - "version": "Wersja", - "version_announcement_closing": "Twój przyjaciel Aleks", - "version_announcement_message": "Witaj! Dostępna jest nowa wersja Immich. Poświęć trochę czasu na zapoznanie się z informacjami o wydaniu, aby upewnić się, że ustawienia twojej instalacji są aktualne i zapobiec błędnym konfiguracjom. Szczególnie jeśli używasz WatchTower lub jakiegokolwiek mechanizmu odpowiedzialnego za automatyczne aktualizowanie Immich.", - "version_history": "Historia wersji", - "version_history_item": "Zainstalowano {version} w {date}", - "video": "Wideo", - "video_hover_setting": "Odtwórz miniaturę wideo po najechaniu kursorem", - "video_hover_setting_description": "Odtwórz miniaturę wideo po najechaniu myszką na element. Nawet jeśli jest wyłączone, odtwarzanie można rozpocząć, najeżdżając kursorem na ikonę odtwarzania.", - "videos": "Filmy", - "videos_count": "{count, plural, one {# Film} few {# Filmy} other {# Filmów}}", - "videos_only": "Tylko filmy", - "view": "Widok", - "view_album": "Wyświetl Album", - "view_all": "Pokaż wszystkie", - "view_all_users": "Pokaż wszystkich użytkowników", - "view_asset_owners": "Wyświetl właścicieli zasobów", - "view_details": "Zobacz szczegóły", - "view_in_timeline": "Pokaż na osi czasu", - "view_link": "Zobacz link", - "view_links": "Pokaż łącza", - "view_name": "Widok", - "view_next_asset": "Wyświetl następny zasób", - "view_previous_asset": "Wyświetl poprzedni zasób", - "view_qr_code": "Pokaż kod QR", - "view_similar_photos": "Zobacz podobne zdjęcia", - "view_stack": "Zobacz stos", - "view_user": "Wyświetl użytkownika", - "viewer_remove_from_stack": "Usuń ze stosu", - "viewer_stack_use_as_main_asset": "Użyj jako głównego zasobu", - "viewer_unstack": "Rozdziel stos", - "visibility_changed": "Zmieniono widoczność dla {count, plural, one {# osoby} other {# osób}}", - "visual": "Wizualny", - "visual_builder": "Edytor wizualny", - "waiting": "Oczekujące", - "waiting_count": "W oczekiwaniu: {count}", - "warning": "Ostrzeżenie", - "week": "Tydzień", - "welcome": "Witaj", - "welcome_to_immich": "Witamy w immich", - "width": "Szerokość", - "wifi_name": "Nazwa Wi-Fi", - "workflow_delete_prompt": "Czy jesteś pewien, że chcesz usunąć ten przepływ pracy?", - "workflow_deleted": "Przepływ pracy usunięty", - "workflow_description": "Opis przepływu pracy", - "workflow_info": "Informacje o przepływie pracy", - "workflow_json": "JSON przepływu pracy", - "workflow_json_help": "Edytuj konfigurację przepływu pracy w formacie JSON. Zmiany zostaną zsynchronizowane z edytorem wizualnym.", - "workflow_name": "Nazwa przepływu pracy", - "workflow_navigation_prompt": "Czy na pewno chcesz wyjść bez zapisania zmian?", - "workflow_summary": "Podsumowanie przepływu pracy", - "workflow_update_success": "Przepływ pracy zaktualizowany pomyślnie", - "workflow_updated": "Zaktualizowano przepływ pracy", - "workflows": "Przepływy pracy", - "workflows_help_text": "Przepływy pracy automatyzują działania na twoich zasobach w oparciu o wyzwalacze i filtry", - "wrong_pin_code": "Nieprawidłowy kod PIN", - "year": "Rok", - "years_ago": "{years, plural, one {# rok} few {# lata} other {# lat}} temu", - "yes": "Tak", - "you_dont_have_any_shared_links": "Nie masz żadnych udostępnionych linków", - "your_wifi_name": "Twoja nazwa Wi-Fi", - "zero_to_clear_rating": "naciśnij 0, aby wyczyścić ocenę zasobu", - "zoom_image": "Powiększ obraz", - "zoom_to_bounds": "Powiększ do krawędzi" -} +{} diff --git a/i18n/pt.json b/i18n/pt.json index fdf3613d1e..0967ef424b 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -1,2401 +1 @@ -{ - "about": "Sobre", - "account": "Conta", - "account_settings": "Definições de Conta", - "acknowledge": "Aceitar", - "action": "Ação", - "action_common_update": "Atualizar", - "action_description": "Um conjunto de ações a executar nos ficheiros filtrados", - "actions": "Ações", - "active": "Em execução", - "active_count": "Ativas: {count}", - "activity": "Atividade", - "activity_changed": "A atividade está {enabled, select, true {ativada} other {desativada}}", - "add": "Adicionar", - "add_a_description": "Adicionar uma descrição", - "add_a_location": "Adicionar localização", - "add_a_name": "Adicionar um nome", - "add_a_title": "Adicionar um título", - "add_action": "Adicionar ação", - "add_action_description": "Faça clique para adicionar uma ação a executar", - "add_assets": "Adicionar ficheiros", - "add_birthday": "Definir aniversário", - "add_endpoint": "Adicionar URL", - "add_exclusion_pattern": "Adicionar um padrão de exclusão", - "add_filter": "Adicionar filtro", - "add_filter_description": "Faça clique para adicionar uma condição para o filtro", - "add_location": "Adicionar localização", - "add_more_users": "Adicionar mais utilizadores", - "add_partner": "Adicionar parceiro", - "add_path": "Adicionar caminho", - "add_photos": "Adicionar fotos", - "add_tag": "Adicionar Marcador", - "add_to": "Adicionar a…", - "add_to_album": "Adicionar ao álbum", - "add_to_album_bottom_sheet_added": "Adicionado a {album}", - "add_to_album_bottom_sheet_already_exists": "Já existe em {album}", - "add_to_album_bottom_sheet_some_local_assets": "Alguns conteúdos locais não puderam ser adicionados no álbum", - "add_to_album_toggle": "Alternar seleção para {album}", - "add_to_albums": "Adicionar aos álbuns", - "add_to_albums_count": "Adicionar aos álbuns ({count})", - "add_to_bottom_bar": "Adicionar a", - "add_to_shared_album": "Adicionar ao álbum partilhado", - "add_upload_to_stack": "Adicionar carregamento à fila", - "add_url": "Adicionar URL", - "add_workflow_step": "Adicionar passo de fluxo de trabalho", - "added_to_archive": "Adicionado ao arquivo", - "added_to_favorites": "Adicionado aos favoritos", - "added_to_favorites_count": "{count, plural, one {{count, number} adicionado aos favoritos} other {{count, number} adicionados aos favoritos}}", - "admin": { - "add_exclusion_pattern_description": "Adicione padrões de exclusão. Utilizar *, ** ou ? são suportados. Para ignorar todos os ficheiros em qualquer diretório chamado \"Raw\", use \"**/Raw/**'. Para ignorar todos os ficheiros que finalizam em \".tif\", use \"**/*.tif\". Para ignorar um caminho absoluto, use \"/caminho/para/ignorar/**\".", - "admin_user": "Utilizador Administrador", - "asset_offline_description": "Este ficheiro proveniente de uma biblioteca externa deixou de estar disponível no disco e foi movido para a reciclagem. Se o ficheiro foi movido no interior da biblioteca, procure na linha de tempo pelo novo ficheiro correspondente. Para restaurar este ficheiro, certifique-se que o caminho do ficheiro abaixo pode ser acedido pelo Immich e analise a biblioteca.", - "authentication_settings": "Definições de Autenticação", - "authentication_settings_description": "Gerir palavras-passe, OAuth, e outras definições de autenticação", - "authentication_settings_disable_all": "Tem a certeza que deseja desativar todos os métodos de início de sessão? O início de sessão será completamente desativado.", - "authentication_settings_reenable": "Para reativar, use um Comando de servidor.", - "background_task_job": "Tarefas em segundo plano", - "backup_database": "Criar Cópia da Base de Dados", - "backup_database_enable_description": "Ativar cópias da base de dados", - "backup_keep_last_amount": "Quantidade de cópias anteriores a manter", - "backup_onboarding_1_description": "Uma cópia remota na cloud ou outra localização física.", - "backup_onboarding_2_description": "Cópias locais em dispositivos diferentes, incluindo os ficheiros principais e uma cópia de segurança local dos mesmos.", - "backup_onboarding_3_description": "Cópias completas dos seus dados, incluindo os ficheiros originais. Inclui uma cópia remota e duas cópias locais.", - "backup_onboarding_description": "É recomendada a estratégia de cópia de segurança 3-2-1 para proteger os seus dados. Para uma solução de cópia de segurança completa, deve manter cópias das suas fotos e vídeos tal como da base de dados do Immich.", - "backup_onboarding_footer": "Para mais informações sobre como criar uma cópia de segurança do Immich, por favor leia a documentação.", - "backup_onboarding_parts_title": "A cópia de segurança 3-2-1 é definida por:", - "backup_onboarding_title": "Cópias de segurança", - "backup_settings": "Definições de Cópia da Base de Dados", - "backup_settings_description": "Gerir definições de cópia da base de dados.", - "cleared_jobs": "Eliminadas as tarefas de: {job}", - "config_set_by_file": "A configuração está atualmente definida por um ficheiro de configuração", - "confirm_delete_library": "Tem a certeza de que deseja eliminar a biblioteca {library} ?", - "confirm_delete_library_assets": "Tem a certeza de que deseja eliminar esta biblioteca? Isto eliminará {count, plural, one {# ficheiro incluído} other {todos os # ficheiros incluídos}} do Immich e esta ação não pode ser anulada. Os ficheiros permanecerão no disco.", - "confirm_email_below": "Para confirmar, escreva \"{email}\" abaixo", - "confirm_reprocess_all_faces": "Tem a certeza de que deseja reprocessar todos os rostos? Isto também limpará os nomes das pessoas.", - "confirm_user_password_reset": "Tem a certeza de que deseja redefinir a palavra-passe de {user}?", - "confirm_user_pin_code_reset": "Tem a certeza de que quer repor o código PIN de {user}?", - "copy_config_to_clipboard_description": "Copiar a configuração atual do sistema como um objeto JSON para a área de transferência", - "create_job": "Criar tarefa", - "cron_expression": "Expressão Cron", - "cron_expression_description": "Definir o intervalo de análise utilizando o formato Cron. Para mais informações, por favor consulte o Crontab Guru", - "cron_expression_presets": "Predefinições das expressões Cron", - "disable_login": "Desativar inicio de sessão", - "duplicate_detection_job_description": "Executa a aprendizagem de máquina em ficheiros para detetar imagens semelhantes. Depende da Pesquisa Inteligente", - "exclusion_pattern_description": "Os padrões de exclusão permitem ignorar ficheiros e pastas ao analisar a sua biblioteca. Isto é útil se tiver pastas que contenham ficheiros que não deseja importar, como ficheiros RAW.", - "export_config_as_json_description": "Descarregar a configuração atual do sistema como um ficheiro JSON", - "external_libraries_page_description": "Página de administrador de bibliotecas externas", - "face_detection": "Deteção de Rostos", - "face_detection_description": "Deteta rostos em ficheiros utilizando aprendizagem automática. Para vídeos, apenas a miniatura é considerada. \"Atualizar\" (re)processa todos os ficheiros, enquanto \"Redefinir\" elimina todos os dados de rostos. \"Em falta\" coloca em fila ficheiros que ainda não foram processados. Os rostos detetados serão colocados em fila para Reconhecimento Facial após a conclusão da Deteção de Rostos, agrupando-os em pessoas novas ou já existentes.", - "facial_recognition_job_description": "Agrupa rostos detetadas em pessoas. Esta etapa é executada após a conclusão da Deteção de Rostos. \"Redefinir\" (re)agrupa todos os rostos. \"Em falta\" coloca em fila rostos que ainda não têm uma pessoa atribuída.", - "failed_job_command": "Ocorreu um erro no comando {command} para a tarefa: {job}", - "force_delete_user_warning": "AVISO: Isto removerá imediatamente o utilizador e todos os ficheiros. Esta ação não pode ser anulada e os ficheiros não poderão ser recuperados.", - "image_format": "Formato", - "image_format_description": "WebP produz ficheiros mais pequenos do que JPEG, mas é mais lento para codificar.", - "image_fullsize_description": "Imagem de tamanho inteiro sem meta dados, utilizada quando esta for ampliada", - "image_fullsize_enabled": "Ativar geração de imagem em tamanho inteiro", - "image_fullsize_enabled_description": "Gerar imagens de tamanho inteiro para formatos não compatíveis com a web. Quando a opção \"Preferir visualização incorporada\" está ativada, estas serão utilizadas diretamente sem serem convertidas. Não afeta formatos compatíveis com a web tais como JPEG.", - "image_fullsize_quality_description": "Qualidade da imagem de tamanho inteiro de 1 a 100. Valores mais altos são melhores, mas produzem ficheiros maiores.", - "image_fullsize_title": "Definições de imagem de tamanho inteiro", - "image_prefer_embedded_preview": "Preferir visualização incorporada", - "image_prefer_embedded_preview_setting_description": "Utilizar visualizações incorporadas em fotos RAW como entrada para processamento de imagem e quando disponível. Isto pode produzir cores mais precisas para algumas imagens, mas a qualidade da visualização depende da câmara e a imagem pode ter mais artefatos de compressão.", - "image_prefer_wide_gamut": "Prefira ampla gama", - "image_prefer_wide_gamut_setting_description": "Utilizar Display P3 para miniaturas. Isto preserva melhor a vibrância das imagens com espaços de cores amplos, mas as imagens podem aparecer de maneira diferente em dispositivos antigos com uma versão antiga do navegador. As imagens sRGB são mantidas como sRGB para evitar mudanças de cores.", - "image_preview_description": "Imagem de tamanho médio sem metadados, utilizada ao visualizar um único ficheiro e pela aprendizagem de máquina", - "image_preview_quality_description": "Qualidade de pré-visualização de 1 a 100. Maior é melhor, mas produz ficheiros maiores e pode reduzir a capacidade de resposta da aplicação. Definir um valor demasiado baixo pode afetar a qualidade da aprendizagem de máquina.", - "image_preview_title": "Definições de Pré-visualização", - "image_progressive": "Progressivo", - "image_progressive_description": "Codificar imagens JPEG de forma progressiva para exibição com carregamento gradual. Não tem efeito em imagens WebP.", - "image_quality": "Qualidade", - "image_resolution": "Resolução", - "image_resolution_description": "Resoluções mais altas podem ajudar a preservar mais detalhes mas demoram mais a codificar, têm tamanhos de ficheiro maiores e podem reduzir a capacidade de resposta da aplicação.", - "image_settings": "Definições de imagem", - "image_settings_description": "Gerir a qualidade e resolução das imagens geradas", - "image_thumbnail_description": "Miniatura de tamanho pequena e sem metadados, utilizada ao visualizar grupos de fotos como, por exemplo, na linha de tempo principal", - "image_thumbnail_quality_description": "Qualidade das miniaturas de 1 a 100. Maior é melhor, mas produz tamanhos de ficheiro maiores e podem reduzir a capacidade de resposta da aplicação.", - "image_thumbnail_title": "Definições de Miniaturas", - "import_config_from_json_description": "Importar configuração do sistema ao carregar um ficheiro JSON", - "job_concurrency": "{job} em simultâneo", - "job_created": "Tarefa criada", - "job_not_concurrency_safe": "Esta tarefa não pode ser executada em simultâneo.", - "job_settings": "Definições de Tarefas", - "job_settings_description": "Gerir tarefas executadas em simultâneo", - "jobs_delayed": "{jobCount, plural, one {# adiado} other {# adiados}}", - "jobs_failed": "{jobCount, plural, one {# falhou} other {# falharam}}", - "jobs_over_time": "Tarefas ao logo do tempo", - "library_created": "Criada biblioteca: {library}", - "library_deleted": "Biblioteca eliminada", - "library_details": "Detalhes de Biblioteca", - "library_folder_description": "Especifique uma pasta para importar. Esta pasta, incluindo subpastas, será analisada para procurar imagens e vídeos.", - "library_remove_exclusion_pattern_prompt": "Tem a certeza que pretende remover este padrão de exclusão?", - "library_remove_folder_prompt": "Tem a certeza que quer remover esta pasta de importação?", - "library_scanning": "Análise periódica", - "library_scanning_description": "Configurar a análise periódica da biblioteca", - "library_scanning_enable_description": "Ativar análise periódica da biblioteca", - "library_settings": "Biblioteca Externa", - "library_settings_description": "Gerir definições de biblioteca externa", - "library_tasks_description": "Pesquisa bibliotecas externas em busca de itens novos e/ou alterados", - "library_updated": "Biblioteca atualizada", - "library_watching_enable_description": "Analisar bibliotecas externas por alterações de ficheiros", - "library_watching_settings": "Análise de biblioteca [EXPERIMENTAL]", - "library_watching_settings_description": "Analise automaticamente por ficheiros alterados", - "logging_enable_description": "Ativar registo", - "logging_level_description": "Quando ativado, qual o nível de log a usar.", - "logging_settings": "Registo", - "machine_learning_availability_checks": "Verificação de disponibilidade", - "machine_learning_availability_checks_description": "Detectar automaticamente e dar preferência aos servidores de aprendizagem automática disponíveis", - "machine_learning_availability_checks_enabled": "Ativar confirmações de disponibilidade", - "machine_learning_availability_checks_interval": "Confirmação de intervalo", - "machine_learning_availability_checks_interval_description": "Intervalo, em milisegundos, entre confirmações de disponibilidade", - "machine_learning_availability_checks_timeout": "Tempo limite para requisição", - "machine_learning_availability_checks_timeout_description": "Tempo limite em milissegundos para verificações de disponibilidade", - "machine_learning_clip_model": "Modelo CLIP", - "machine_learning_clip_model_description": "O nome do modelo CLIP definido aqui. Tome nota de que é necessário voltar a executar a tarefa de \"Pesquisa Inteligente\" para todas as imagens depois de alterar o modelo.", - "machine_learning_duplicate_detection": "Deteção de Itens Duplicados", - "machine_learning_duplicate_detection_enabled": "Ativar deteção de itens duplicados", - "machine_learning_duplicate_detection_enabled_description": "Se desativado, ficheiros exatamente idênticos serão desduplicados na mesma.", - "machine_learning_duplicate_detection_setting_description": "Utilizar embeddings CLIP para encontrar itens possivelmente duplicados", - "machine_learning_enabled": "Ativar a aprendizagem de máquina", - "machine_learning_enabled_description": "Se desativado, todas as funcionalidades de ML serão desativadas, independentemente das definições abaixo.", - "machine_learning_facial_recognition": "Reconhecimento Facial", - "machine_learning_facial_recognition_description": "Detetar, reconhecer e agrupar rostos em imagens", - "machine_learning_facial_recognition_model": "Modelo de reconhecimento facial", - "machine_learning_facial_recognition_model_description": "Os modelos estão ordenados por ordem decrescente de tamanho. Modelos maiores são mais lentos e utilizam mais memória, mas produzem melhores resultados. Tome conta de que ao alterar um modelo, deve executar novamente a tarefa de \"Deteção de Rostos\" para todas as imagens.", - "machine_learning_facial_recognition_setting": "Ativar reconhecimento facial", - "machine_learning_facial_recognition_setting_description": "Se desativado, as imagens não serão codificadas para reconhecimento facial e não preencherão a secção Pessoas na página Explorar.", - "machine_learning_max_detection_distance": "Distância máxima de deteção", - "machine_learning_max_detection_distance_description": "Distância máxima entre duas imagens para considerá-las duplicadas, variando entre 0,001 e 0,1. Valores mais altos detetarão mais duplicidades, mas poderão resultar em falsos positivos.", - "machine_learning_max_recognition_distance": "Distância máxima de reconhecimento", - "machine_learning_max_recognition_distance_description": "Distância máxima entre dois rostos para serem considerados a mesma pessoa, variando de 0 a 2. Valores menores evitam rotular dois rostos como a mesma pessoa, enquanto valores maiores evitam rotular o mesmo rosto como duas pessoas diferentes. Tenha em conta de que é mais fácil unir duas pessoas do que dividir uma pessoa em duas, portanto tenha preferência por valores mais baixos quando possível.", - "machine_learning_min_detection_score": "Pontuação mínima de deteção", - "machine_learning_min_detection_score_description": "Pontuação mínima de confiança para um rosto ser detetado, de 0 a 1. Valores mais baixos detetam mais rostos, mas poderão resultar em falsos positivos.", - "machine_learning_min_recognized_faces": "Mínimo de rostos reconhecidos", - "machine_learning_min_recognized_faces_description": "O número mínimo de faces reconhecidas para uma pessoa ser criada na lista. Aumentar isto torna o Reconhecimento Facial mais preciso, no entanto aumenta a probabilidade de um rosto não ser atribuído a uma pessoa.", - "machine_learning_ocr": "Reconhecimento Ótico de Caracteres (OCR)", - "machine_learning_ocr_description": "Utilizar aprendizagem de máquina para reconhecer texto dentro de imagens", - "machine_learning_ocr_enabled": "Ativar OCR", - "machine_learning_ocr_enabled_description": "Se estiver desativado, as imagens não serão utilizadas para reconhecimento de texto.", - "machine_learning_ocr_max_resolution": "Resolução máxima", - "machine_learning_ocr_max_resolution_description": "Pré-visualizações acima desta resolução serão redimensionadas mantendo a proporção. Valores mais elevados têm mais precisão, mas irão demorar mais tempo a processar e utilizam mais memória.", - "machine_learning_ocr_min_detection_score": "Pontuação mínima de deteção", - "machine_learning_ocr_min_detection_score_description": "Pontuação mínima de confiança para o texto ser detetado de 0 a 1. Valores mais baixos vão detetar mais texto mas podem resultar em falsos positivos.", - "machine_learning_ocr_min_recognition_score": "Pontuação mínima de reconhecimento", - "machine_learning_ocr_min_score_recognition_description": "Pontuação mínima de confiança para o texto ser reconhecido de 0 a 1. Valores mais baixos vão reconhecer mais texto mas podem resultar em falsos positivos.", - "machine_learning_ocr_model": "Modelo de OCR", - "machine_learning_ocr_model_description": "Modelos do servidor têm mais precisão do que modelos móveis, mas demoram mais tempo a processar e utilizam mais memória.", - "machine_learning_settings": "Definições de aprendizagem de máquina (Machine Learning)", - "machine_learning_settings_description": "Gerir funcionalidades e definições de aprendizagem de máquina", - "machine_learning_smart_search": "Pesquisa Inteligente", - "machine_learning_smart_search_description": "Pesquise imagens semanticamente utilizando embeddings CLIP", - "machine_learning_smart_search_enabled": "Ativar a Pesquisa Inteligente", - "machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para Pesquisa Inteligente.", - "machine_learning_url_description": "A URL do servidor de aprendizagem de máquina. Se for fornecido mais do que um URL, cada servidor será testado, um a um, até um deles responder com sucesso, por ordem do primeiro ao último. Servidores que não responderem serão temporariamente ignorados até voltarem a estar online.", - "maintenance_delete_backup": "Eliminar Cópia de Segurança", - "maintenance_delete_backup_description": "Este ficheiro irá ser apagado para sempre.", - "maintenance_delete_error": "Ocorreu um erro ao eliminar a cópia de segurança.", - "maintenance_restore_backup": "Restaurar Cópia de Segurança", - "maintenance_restore_backup_description": "O Immich irá ser apagado e de seguida restaurado a partir da cópia de segurança selecionada. Irá ser criada uma cópia de segurança antes de continuar.", - "maintenance_restore_backup_different_version": "Esta cópia de segurança foi criada com uma versão diferente do Immich!", - "maintenance_restore_backup_unknown_version": "Não foi possível determinar a versão da cópia de segurança.", - "maintenance_restore_database_backup": "Restaurar cópia de seguraça da base de dados", - "maintenance_restore_database_backup_description": "Reverter para um estado anterior da base de dados utilizando um ficheiro de cópia de segurança", - "maintenance_settings": "Manutenção", - "maintenance_settings_description": "Colocar o Immich no modo de manutenção.", - "maintenance_start": "Aternar para o modo de manutenção", - "maintenance_start_error": "Ocorreu um erro ao iniciar o modo de manutenção.", - "maintenance_upload_backup": "Carregar ficheiro de cópia de segurança da base de dados", - "maintenance_upload_backup_error": "Não foi possível carregar cópia de segurança. É um ficheiro .sql/.sql.gz?", - "manage_concurrency": "Gerir simultaneidade", - "manage_concurrency_description": "Navegar para a página das tarefas para gerir as tarefas em simultâneo", - "manage_log_settings": "Gerir definições de registo", - "map_dark_style": "Tema Escuro", - "map_enable_description": "Ativar funcionalidades de mapa", - "map_gps_settings": "Mapas e Definições de GPS", - "map_gps_settings_description": "Gerir Definições de Mapas e GPS (Geocodificação Reversa)", - "map_implications": "A funcionalidade do mapa necessita um serviço externo (tiles.immich.cloud)", - "map_light_style": "Tema Claro", - "map_manage_reverse_geocoding_settings": "Gerir definições de Geocodificação Reversa", - "map_reverse_geocoding": "Geocodificação Reversa", - "map_reverse_geocoding_enable_description": "Ativar Geocodificação Reversa", - "map_reverse_geocoding_settings": "Definições de Geocodificação Reversa", - "map_settings": "Mapa", - "map_settings_description": "Gerir definições do mapa", - "map_style_description": "URL para um tema de mapa style.json", - "memory_cleanup_job": "Limpeza de memórias", - "memory_generate_job": "Geração de memórias", - "metadata_extraction_job": "Extrair metadados", - "metadata_extraction_job_description": "Extrai informações de metadados de cada ficheiro, como GPS, rostos e resolução", - "metadata_faces_import_setting": "Ativar a importação facial", - "metadata_faces_import_setting_description": "Importar rostos a partir dos dados EXIF da imagem e ficheiros anexos", - "metadata_settings": "Definições de metadados", - "metadata_settings_description": "Gerir definições de metadados", - "migration_job": "Migração", - "migration_job_description": "Migra miniaturas de ficheiros e rostos para a estrutura de pastas mais recente", - "nightly_tasks_cluster_faces_setting_description": "Executar reconhecimento facial em faces detetadas recentemente", - "nightly_tasks_cluster_new_faces_setting": "Agrupar novas faces", - "nightly_tasks_database_cleanup_setting": "Tarefas de limpeza da base de dados", - "nightly_tasks_database_cleanup_setting_description": "Limpar dados antigos e expirados da base de dados", - "nightly_tasks_generate_memories_setting": "Gerar memórias", - "nightly_tasks_generate_memories_setting_description": "Criar novas memórias a partir de ficheiros", - "nightly_tasks_missing_thumbnails_setting": "Gerar miniaturas em falta", - "nightly_tasks_missing_thumbnails_setting_description": "Colocar em fila ficheiros sem miniaturas para a geração das mesmas", - "nightly_tasks_settings": "Definições de Tarefas Diárias", - "nightly_tasks_settings_description": "Gerir tarefas diárias", - "nightly_tasks_start_time_setting": "Hora de início", - "nightly_tasks_start_time_setting_description": "A hora em qual o servidor começa a executar as tarefas diárias", - "nightly_tasks_sync_quota_usage_setting": "Utilização da quota de sincronização", - "nightly_tasks_sync_quota_usage_setting_description": "Atualizar quotas de armazenamento de utilizadores, com base na utilização atual", - "no_paths_added": "Nenhum caminho adicionado", - "no_pattern_added": "Nenhum padrão adicionado", - "note_apply_storage_label_previous_assets": "Observação: Para aplicar o Rótulo de Armazenamento a ficheiros carregados anteriormente, execute a", - "note_cannot_be_changed_later": "NOTA: Isto não pode ser alterado posteriormente!", - "notification_email_from_address": "A partir do endereço", - "notification_email_from_address_description": "Endereço de e-mail do remetente, por exemplo: \"Servidor de Fotos Immich \". Certifique-se de que utiliza um endereço através do qual pode enviar e-mails.", - "notification_email_host_description": "Host do servidor de e-mail (por exemplo, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorar erros de certificado", - "notification_email_ignore_certificate_errors_description": "Ignorar erros de validação de certificado TLS (não recomendado)", - "notification_email_password_description": "Palavra-passe a ser usada ao autenticar no servidor de e-mail", - "notification_email_port_description": "Porta do servidor de e-mail (por exemplo, 25, 465 ou 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Usar SMTPS (SMTP por TLS)", - "notification_email_sent_test_email_button": "Enviar e-mail de teste e gravar", - "notification_email_setting_description": "Definições para envio de notificações por e-mail", - "notification_email_test_email": "Enviar e-mail de teste", - "notification_email_test_email_failed": "Ocorreu um erro ao enviar e-mail de teste, verifique os valores", - "notification_email_test_email_sent": "Um email de teste foi enviado para {email}. Por favor, verifique a sua caixa de entrada.", - "notification_email_username_description": "Nome de utilizador a ser usado ao autenticar com o servidor de e-mail", - "notification_enable_email_notifications": "Ativar notificações por e-mail", - "notification_settings": "Definições de notificações", - "notification_settings_description": "Gerir definições de notificações, incluindo e-mail", - "oauth_auto_launch": "Arranque automático", - "oauth_auto_launch_description": "Iniciar o fluxo de sessão por OAuth automaticamente ao navegar até a página de inicio de sessão", - "oauth_auto_register": "Registo automático", - "oauth_auto_register_description": "Registar automaticamente novos utilizadores após iniciarem sessão com o OAuth", - "oauth_button_text": "Texto do botão", - "oauth_client_secret_description": "Obrigatório para o cliente confidencial, ou se a PKCE (Proof Key for Code Exchange) não for suportada para cliente público.", - "oauth_enable_description": "Iniciar sessão com o OAuth", - "oauth_mobile_redirect_uri": "URI de redirecionamento móvel", - "oauth_mobile_redirect_uri_override": "Substituição de URI de redirecionamento móvel", - "oauth_mobile_redirect_uri_override_description": "Ative quando o provedor do OAuth não permite um URI móvel, como ''{callback}''", - "oauth_role_claim": "Reivindicação de Funções", - "oauth_role_claim_description": "Automaticamente concede acesso de administrador, com base na presença desta reivindicação. A reivindicação tanto pode ter \"user\" como \"admin\".", - "oauth_settings": "OAuth", - "oauth_settings_description": "Gerir definições de inicio de sessão do OAuth", - "oauth_settings_more_details": "Para mais informações sobre esta funcionalidade, veja a documentação.", - "oauth_storage_label_claim": "Reivindicação de Rótulo de Armazenamento", - "oauth_storage_label_claim_description": "Definir automaticamente o Rótulo de Armazenamento do utilizador para o valor desta declaração.", - "oauth_storage_quota_claim": "Reivindicação de quota de armazenamento", - "oauth_storage_quota_claim_description": "Definir automaticamente a quota de armazenamento do utilizador para o valor desta declaração.", - "oauth_storage_quota_default": "Quota de armazenamento padrão (GiB)", - "oauth_storage_quota_default_description": "Quota em GiB a ser usada quando nenhuma reivindicação for fornecida.", - "oauth_timeout": "Tempo Limite de Requisição", - "oauth_timeout_description": "Tempo limite para requisições, em milissegundos", - "ocr_job_description": "Utilizar aprendizagem de máquina para reconhecer texto em imagens", - "password_enable_description": "Iniciar sessão com e-mail e palavra-passe", - "password_settings": "Palavra-passe de acesso", - "password_settings_description": "Gerir definições de inicio de sessão e palavra-passe", - "paths_validated_successfully": "Todos os caminhos validados com sucesso", - "person_cleanup_job": "Limpeza de pessoas", - "queue_details": "Detalhes da fila", - "queues": "Filas de tarefas", - "queues_page_description": "Página de administrador de filas de tarefas", - "quota_size_gib": "Tamanho da quota (GiB)", - "refreshing_all_libraries": "A atualizar todas as bibliotecas", - "registration": "Registo de Administrador", - "registration_description": "Como é o primeiro utilizador no sistema, será marcado como administrador, e será responsável pelas tarefas administrativas, sendo que utilizadores adicionais serão criados por si.", - "remove_failed_jobs": "Remover tarefas falhadas", - "require_password_change_on_login": "Exigir que o utilizador altere a palavra-passe no primeiro início de sessão", - "reset_settings_to_default": "Redefinir as definições para o padrão", - "reset_settings_to_recent_saved": "Redefinir as definições para as guardadas mais recentemente", - "scanning_library": "A analisar biblioteca", - "search_jobs": "Pesquisar tarefas…", - "send_welcome_email": "Enviar e-mail de boas-vindas", - "server_external_domain_settings": "Domínio externo", - "server_external_domain_settings_description": "Domínio para links públicos partilhados, incluindo http(s)://", - "server_public_users": "Utilizadores Públicos", - "server_public_users_description": "Todos os utilizadores (nome e e-mail) serão listados quando adicionar um utilizador a álbuns partilhados. Quando desativado, a lista de utilizadores só será visível a administradores.", - "server_settings": "Definições do Servidor", - "server_settings_description": "Gerir definições do servidor", - "server_stats_page_description": "Página de administrador das estatísticas do servidor", - "server_welcome_message": "Mensagem de boas-vindas", - "server_welcome_message_description": "Uma mensagem que é exibida na página de inicio de sessão.", - "settings_page_description": "Página de administrador das definições", - "sidecar_job": "Metadados secundários", - "sidecar_job_description": "Descobrir ou sincronizar metadados secundários a partir do sistema de ficheiros", - "slideshow_duration_description": "Tempo em segundos para exibir cada imagem", - "smart_search_job_description": "Execute a aprendizagem automática em ficheiros para oferecer apoio à Pesquisa Inteligente", - "storage_template_date_time_description": "O registo de data e hora de criação do ficheiro é utilizado para preencher estas informações", - "storage_template_date_time_sample": "Exemplo de data/hora {date}", - "storage_template_enable_description": "Ativar mecanismo de modelo de armazenamento", - "storage_template_hash_verification_enabled": "Ativar verificação de hash", - "storage_template_hash_verification_enabled_description": "Ativa a verificação de hash, não desative esta opção a menos que tenha a certeza das implicações", - "storage_template_migration": "Migração de modelo de armazenamento", - "storage_template_migration_description": "Aplica o {template} atual para ficheiros previamente carregados", - "storage_template_migration_info": "O modelo de armazenamento irá converter todas as extensões para letra minúscula. As mudanças do modelo apenas se aplicarão a novos ficheiros. Para aplicar o modelo retroativamente para os ficheiros carregados anteriormente, execute o {job}.", - "storage_template_migration_job": "Tarefa de Migração do Modelo de Armazenamento", - "storage_template_more_details": "Para mais informações sobre esta funcionalidade, dirija-se a Modelo de Armazenamento e às suas implicações", - "storage_template_onboarding_description_v2": "Quando ativada, está função irá automaticamente organizar ficheiros com base num modelo definido pelo utilizador. Para mais informações, consulte a documentação.", - "storage_template_path_length": "Limite aproximado do tamanho do caminho: {length, number}{limit, number}", - "storage_template_settings": "Modelo de Armazenamento", - "storage_template_settings_description": "Gerir a estrutura de pastas e o nome do ficheiro carregado", - "storage_template_user_label": "{label} é o Rótulo do Armazenamento do utilizador", - "system_settings": "Definições do Sistema", - "tag_cleanup_job": "Limpeza de etiquetas", - "template_email_available_tags": "Pode usar as seguintes variáveis no modelo: {tags}", - "template_email_if_empty": "Se o modelo estiver em branco, o modelo de e-mail padrão será utilizado.", - "template_email_invite_album": "Modelo do e-mail de convite para álbum", - "template_email_preview": "Pré-visualizar", - "template_email_settings": "Modelos de e-mail", - "template_email_update_album": "Modelo do e-mail de atualização do álbum", - "template_email_welcome": "Modelos do email de boas vindas", - "template_settings": "Modelos de notificação", - "template_settings_description": "Gerir modelos personalizados para notificações", - "theme_custom_css_settings": "CSS Personalizado", - "theme_custom_css_settings_description": "Folhas de estilo em cascata (CSS) permitem que o design do Immich seja personalizado.", - "theme_settings": "Definições de Tema", - "theme_settings_description": "Gerir a personalização da interface web do Immich", - "thumbnail_generation_job": "Gerar miniaturas", - "thumbnail_generation_job_description": "Gera miniaturas grandes, pequenas e desfocadas para cada ficheiro, bem como miniaturas para cada pessoa", - "transcoding_acceleration_api": "API de aceleração", - "transcoding_acceleration_api_description": "A API que irá interagir com o seu dispositivo para acelerar a transcodificação. Esta definição é a 'melhor opção': ela voltará à transcodificação de software em caso de falha. O VP9 pode não funcionar dependendo do seu hardware.", - "transcoding_acceleration_nvenc": "NVENC (requer GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (requer CPU Intel de 7ª geração ou posterior)", - "transcoding_acceleration_rkmpp": "RKMPP (apenas em SOCs Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codecs de áudio aceites", - "transcoding_accepted_audio_codecs_description": "Selecione os codecs de áudio que não precisam de ser transcodificados. Usado apenas para determinadas políticas de transcodificação.", - "transcoding_accepted_containers": "Contentores aceites", - "transcoding_accepted_containers_description": "Selecione os formatos de contentores que não precisam de ser remisturados para MP4. Usado apenas para algumas políticas de transcodificação.", - "transcoding_accepted_video_codecs": "Codecs de vídeo aceitos", - "transcoding_accepted_video_codecs_description": "Selecione quais os codecs de vídeo que não precisam de ser transcodificados. Usado apenas para determinadas políticas de transcodificação.", - "transcoding_advanced_options_description": "Opções que a maioria dos utilizadores não deverá precisar de alterar", - "transcoding_audio_codec": "Codec de áudio", - "transcoding_audio_codec_description": "Opus é a opção de mais alta qualidade, mas tem menor compatibilidade com dispositivos ou software antigos.", - "transcoding_bitrate_description": "Vídeos com taxa de bits superior à máxima ou que não estão num formato aceite", - "transcoding_codecs_learn_more": "Para saber mais sobre as terminologias utilizadas aqui, consulte a documentação do FFmpeg para o codec H.264, codec HEVC e codec VP9.", - "transcoding_constant_quality_mode": "Modo de qualidade fixa", - "transcoding_constant_quality_mode_description": "ICQ é melhor que CQP, mas alguns dispositivos de aceleração de hardware não suportam este modo. Definir esta opção dará preferência ao modo especificado ao usar codificação baseada em qualidade. Ignorado pelo NVENC porque não suporta ICQ.", - "transcoding_constant_rate_factor": "Fator de taxa constante (-crf)", - "transcoding_constant_rate_factor_description": "Nível de qualidade do vídeo. Os valores típicos são 23 para H.264, 28 para HEVC, 31 para VP9 e 35 para AV1. Menor é melhor, mas produz ficheiros maiores.", - "transcoding_disabled_description": "Não transcodificar nenhum vídeo, no entanto pode causar erros de reprodução em alguns clientes", - "transcoding_encoding_options": "Definições de codificação de vídeo", - "transcoding_encoding_options_description": "Definir codecs, resolução, qualidade e outras opções para videos codificados", - "transcoding_hardware_acceleration": "Aceleração de hardware", - "transcoding_hardware_acceleration_description": "Experimental; transcodificação mais rápida, mas poderá ter qualidade inferior com a mesma taxa de bits", - "transcoding_hardware_decoding": "Decodificação de hardware", - "transcoding_hardware_decoding_setting_description": "Permite a aceleração ponta a ponta em vez de apenas acelerar a codificação. Pode não funcionar em todos os videos.", - "transcoding_max_b_frames": "Máximo de quadros B", - "transcoding_max_b_frames_description": "Valores mais altos melhoram a eficiência da compressão, mas tornam a codificação mais lenta. Pode não ser compatível com aceleração de hardware em dispositivos mais antigos. 0 desativa os quadros B, enquanto -1 define esse valor automaticamente.", - "transcoding_max_bitrate": "Taxa de bits máxima", - "transcoding_max_bitrate_description": "Definir uma taxa de bits máxima pode fazer os tamanhos dos ficheiros mais previsíveis com um custo menor de qualidade. Em 720p, os valores típicos são 2600 kbit/s para VP9 ou HEVC, ou 4500 kbit/s para H.264. Quando uma unidade não for especificada, k (de kbit/s) será utilizado, ou seja, 5000, 5000k e 5M (de Mbit/s) são equivalentes.", - "transcoding_max_keyframe_interval": "Intervalo máximo de quadro-chave", - "transcoding_max_keyframe_interval_description": "Define a distância máxima do quadro entre os quadros-chave. Valores mais baixos pioram a eficiência da compressão, mas melhoram os tempos de procura e podem melhorar a qualidade em cenas com movimento rápido. 0 define esse valor automaticamente.", - "transcoding_optimal_description": "Vídeos com resolução superior à desejada ou num formato não aceite", - "transcoding_policy": "Política de Transcodificação", - "transcoding_policy_description": "Definir quando um vídeo será transcodificado", - "transcoding_preferred_hardware_device": "Dispositivo de hardware preferido", - "transcoding_preferred_hardware_device_description": "Aplica-se apenas a VAAPI e QSV. Define o nó dri usado para transcodificação de hardware.", - "transcoding_preset_preset": "Predefinição (-preset)", - "transcoding_preset_preset_description": "Velocidade de compressão. Predefinições mais lentas produzem ficheiros menores e aumentam a qualidade ao atingir uma determinada taxa de bits. VP9 ignora velocidades acima de \"mais rápido\".", - "transcoding_reference_frames": "Quadros de referência", - "transcoding_reference_frames_description": "O número de quadros a serem referenciados ao comprimir um determinado quadro. Valores mais altos melhoram a eficiência da compressão, mas tornam a codificação mais lenta. 0 define esse valor automaticamente.", - "transcoding_required_description": "Apenas vídeos que não estejam num formato aceite", - "transcoding_settings": "Definições de transcodificação de vídeo", - "transcoding_settings_description": "Gerir quais os videos a transcodificar e como os processar", - "transcoding_target_resolution": "Resolução desejada", - "transcoding_target_resolution_description": "Resoluções mais altas podem preservar mais detalhes, mas demoram mais para codificar, têm tamanhos de ficheiro maiores e podem reduzir a capacidade de resposta da aplicação.", - "transcoding_temporal_aq": "QA temporal", - "transcoding_temporal_aq_description": "Aplica-se apenas ao NVENC. A Quantização com adaptação temporal aumenta a qualidade de cenas com alto detalhe e pouco movimento. Pode não ser compatível com dispositivos mais antigos.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Valores mais altos levam a uma codificação mais rápida, mas deixam menos espaço para o servidor processar outras tarefas enquanto estiver ativo. Este valor não deve ser superior ao número de núcleos do CPU. Maximiza a utilização se definido como 0.", - "transcoding_tone_mapping": "Mapeamento de tons", - "transcoding_tone_mapping_description": "Tenta preservar a aparência dos vídeos HDR quando convertidos para SDR. Cada algoritmo faz compensações diferentes em termos de cor, detalhes e brilho. Hable preserva os detalhes, Mobius preserva as cores e Reinhard preserva o brilho.", - "transcoding_transcode_policy": "Política de transcodificação", - "transcoding_transcode_policy_description": "Política para quando um vídeo deve ser transcodificado. Os vídeos HDR serão sempre transcodificados (exceto se a transcodificação estiver desativada).", - "transcoding_two_pass_encoding": "Codificação em duas passagens", - "transcoding_two_pass_encoding_setting_description": "Transcodificar em duas passagens para produzir vídeos melhor codificados. Quando a taxa de bits máxima está ativada (necessário para funcionar com H.264 e HEVC), este modo usa um intervalo de taxa de bits baseado na taxa de bits máxima e ignora o CRF. Para VP9, o CRF pode ser usado se a taxa de bits máxima estiver desativada.", - "transcoding_video_codec": "Codificador de Vídeo", - "transcoding_video_codec_description": "O VP9 tem alta eficiência e compatibilidade com a web, mas leva mais tempo para transcodificar. HEVC tem desempenho semelhante, mas tem menor compatibilidade com a web. H.264 é amplamente compatível e rápido de transcodificar, mas produz ficheiros muito maiores. AV1 é o codec mais eficiente, mas não possui suporte em dispositivos mais antigos.", - "trash_enabled_description": "Ativar funcionalidade da Reciclagem", - "trash_number_of_days": "Número de dias", - "trash_number_of_days_description": "Número de dias para manter os ficheiros na reciclagem antes de os eliminar permanentemente", - "trash_settings": "Definições da Reciclagem", - "trash_settings_description": "Gerir definições da reciclagem", - "unlink_all_oauth_accounts": "Desvincular todas as contas OAuth", - "unlink_all_oauth_accounts_description": "Lembre-se de desvincular todas as contas OAuth antes de migrar para um novo provedor.", - "unlink_all_oauth_accounts_prompt": "Tem a certeza de que deseja desvincular todas as contas OAuth? Isto irá redefinir o ID OAuth de cada utilizador e não poderá ser anulado.", - "user_cleanup_job": "Limpeza de utilizadores", - "user_delete_delay": "A conta e os ficheiros de {user} serão agendados para eliminação permanente dentro de {delay, plural, one {# dia} other {# dias}}.", - "user_delete_delay_settings": "Atraso de eliminação", - "user_delete_delay_settings_description": "Número de dias após a remoção para excluir permanentemente a conta e os ficheiros de um utilizador. A tarefa de eliminação de utilizadores é executada à meia-noite para verificar utilizadores que estão prontos para eliminação. As alterações a esta definição serão avaliadas na próxima execução.", - "user_delete_immediately": "A conta e os ficheiros de {user} serão colocados em fila para eliminação permanente de imediato.", - "user_delete_immediately_checkbox": "Adicionar utilizador e ficheiros à fila para eliminação imediata", - "user_details": "Detalhes do utilizador", - "user_management": "Gestão de utilizadores", - "user_password_has_been_reset": "A palavra-passe do utilizador foi redefinida:", - "user_password_reset_description": "Por favor forneça a palavra-passe temporária ao utilizador e informe-o(a) de que será necessário alterá-la próximo início de sessão.", - "user_restore_description": "A conta de {user} será restaurada.", - "user_restore_scheduled_removal": "Restaurar utilizador - remoção agendada em {date, date, long}", - "user_settings": "Definições do Utilizador", - "user_settings_description": "Gerir definições do utilizador", - "user_successfully_removed": "O utilizador {email} foi removido com sucesso.", - "users_page_description": "Página de administador de utilizadores", - "version_check_enabled_description": "Ativa verificação de novas versões", - "version_check_implications": "A funcionalidade de verificação da versão necessita de comunicação periódica com o github.com", - "version_check_settings": "Verificação de versão", - "version_check_settings_description": "Ativar/desativar a notificação de nova versão", - "video_conversion_job": "Transcodificar vídeos", - "video_conversion_job_description": "Transcodifica vídeos para maior compatibilidade com navegadores e dispositivos" - }, - "admin_email": "E-mail do administrador", - "admin_password": "Palavra-passe do administrador", - "administration": "Administração", - "advanced": "Avançado", - "advanced_settings_clear_image_cache": "Limpar a Cache de Imagens", - "advanced_settings_clear_image_cache_error": "Ocorreu um erro ao limpar a cache de imagens", - "advanced_settings_clear_image_cache_success": "Limpeza concluída com sucesso {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Utilize esta definição para filtrar ficheiros durante a sincronização baseada em critérios alternativos. Utilize apenas se a aplicação estiver com problemas a detetar todos os álbuns.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizar um filtro alternativo de sincronização de álbuns em dispositivos", - "advanced_settings_log_level_title": "Nível de registo: {level}", - "advanced_settings_prefer_remote_subtitle": "Alguns dispositivos são extremamente lentos a carregar miniaturas da memória interna. Ative esta opção para preferir imagens do servidor.", - "advanced_settings_prefer_remote_title": "Preferir imagens do servidor", - "advanced_settings_proxy_headers_subtitle": "Defina os cabeçalhos do proxy que o Immich deve enviar em todas comunicações com a rede", - "advanced_settings_proxy_headers_title": "Cabeçalhos do proxy personalizados [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Ativa o modo só de leitura, onde as fotos apenas podem ser visualizadas. Funções como selecionar várias imagens, partilhar, transmitir e eliminar ficam deactivadas. Pode ativar ou desativar o modo só de leitura através da imagem de perfil do utilizador na janela principal", - "advanced_settings_readonly_mode_title": "Modo só de leitura", - "advanced_settings_self_signed_ssl_subtitle": "Não validar o certificado SSL com o endereço do servidor. Isto é necessário para certificados auto-assinados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autoassinados", - "advanced_settings_sync_remote_deletions_subtitle": "Automaticamente eliminar ou restaurar um ficheiro neste dispositivo quando essa mesma ação for efetuada na web", - "advanced_settings_sync_remote_deletions_title": "Sincronizar ficheiros eliminados remotamente [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Definições avançadas do utilizador", - "advanced_settings_troubleshooting_subtitle": "Ativar funcionalidades adicionais para a resolução de problemas", - "advanced_settings_troubleshooting_title": "Resolução de problemas", - "age_months": "Idade {months, plural, one {# mês} other {# meses}}", - "age_year_months": "Idade 1 ano, {months, plural, one {# mês} other {# meses}}", - "age_years": "{years, plural, one{# ano} other {# anos}}", - "album": "Álbum", - "album_added": "Álbum adicionado", - "album_added_notification_setting_description": "Receber uma notificação por e-mail quando for adicionado a um álbum partilhado", - "album_cover_updated": "Capa do álbum atualizada", - "album_delete_confirmation": "Tem a certeza de que quer eliminar o álbum {album}?", - "album_delete_confirmation_description": "Se este álbum for partilhado, os outros utilizadores deixam de o poder aceder.", - "album_deleted": "Álbum eliminado", - "album_info_card_backup_album_excluded": "EXCLUÍDO", - "album_info_card_backup_album_included": "INCLUÍDO", - "album_info_updated": "Informações do álbum atualizadas", - "album_leave": "Sair do álbum?", - "album_leave_confirmation": "Tem a certeza de que quer sair de {album}?", - "album_name": "Nome do álbum", - "album_options": "Opções de álbum", - "album_remove_user": "Remover utilizador?", - "album_remove_user_confirmation": "Tem a certeza de que quer remover {user}?", - "album_search_not_found": "Nenhum álbum encontrado segundo a pesquisa", - "album_selected": "Álbum selecionado", - "album_share_no_users": "Parece que tem este álbum partilhado com todos os utilizadores ou que não existem utilizadores com quem o partilhar.", - "album_summary": "Resumo do álbum", - "album_updated": "Álbum atualizado", - "album_updated_setting_description": "Receber uma notificação por e-mail quando um álbum partilhado tiver novos ficheiros", - "album_upload_assets": "Carregar ficheiros a partir do seu computador e adicioná-los ao álbum", - "album_user_left": "Saíu do {album}", - "album_user_removed": "Utilizador {user} removido", - "album_viewer_appbar_delete_confirm": "Tem certeza que deseja excluir este álbum da sua conta?", - "album_viewer_appbar_share_err_delete": "Ocorreu um erro ao eliminar álbum", - "album_viewer_appbar_share_err_leave": "Ocorreu um erro ao sair do álbum", - "album_viewer_appbar_share_err_remove": "Ocorreu um erro ao remover ficheiros do álbum", - "album_viewer_appbar_share_err_title": "Ocorreu um erro ao alterar o título do álbum", - "album_viewer_appbar_share_leave": "Deixar álbum", - "album_viewer_appbar_share_to": "Compartilhar com", - "album_viewer_page_share_add_users": "Adicionar utilzadores", - "album_with_link_access": "Permite o acesso a fotos e pessoas deste álbum por qualquer pessoa com o link.", - "albums": "Álbuns", - "albums_count": "{count, plural, one {{count, number} Álbum} other {{count, number} Álbuns}}", - "albums_default_sort_order": "Ordem padrão de organização do álbum", - "albums_default_sort_order_description": "Ordem inicial dos ficheiros ao criar novos álbuns.", - "albums_feature_description": "Coleções de ficheiros que podem ser partilhados com outros utilizadores.", - "albums_on_device_count": "Álbums no dispositivo ({count})", - "albums_selected": "{count, plural, one {# álbum selecionado} other {# álbuns selecionados}}", - "all": "Todos", - "all_albums": "Todos os álbuns", - "all_people": "Todas as pessoas", - "all_photos": "Todas as fotos", - "all_videos": "Todos os vídeos", - "allow_dark_mode": "Permitir modo escuro", - "allow_edits": "Permitir edições", - "allow_public_user_to_download": "Permitir que utilizadores públicos façam transferências", - "allow_public_user_to_upload": "Permitir que utilizadores públicos façam carregamentos", - "allowed": "Permitido", - "alt_text_qr_code": "Imagem do código QR", - "always_keep": "Manter sempre", - "always_keep_photos_hint": "Libertar Espaço irá manter todas as fotos neste dispositivo.", - "always_keep_videos_hint": "Libertar Espaço irá manter todos os vídeos neste dispositivo.", - "anti_clockwise": "Sentido anti-horário", - "api_key": "Chave de API", - "api_key_description": "Este valor será apresentado apenas uma única vez. Por favor, certifique-se que o copiou antes de fechar a janela.", - "api_key_empty": "O nome da chave a API não pode estar vazio", - "api_keys": "Chaves de API", - "app_architecture_variant": "Variante (Arquitetura)", - "app_bar_signout_dialog_content": "Tem certeza que deseja sair?", - "app_bar_signout_dialog_ok": "Sim", - "app_bar_signout_dialog_title": "Sair", - "app_download_links": "Ligações de Descarga da App", - "app_settings": "Definições da Aplicação", - "app_stores": "Lojas de aplicações", - "app_update_available": "Atualização disponível", - "appears_in": "Aparece em", - "apply_count": "Aplicar ({count, number})", - "archive": "Arquivo", - "archive_action_prompt": "{count} adicionados ao Arquivo", - "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", - "archive_page_no_archived_assets": "Nenhum ficheiro arquivado encontrado", - "archive_page_title": "Arquivo ({count})", - "archive_size": "Tamanho do arquivo", - "archive_size_description": "Configure o tamanho do arquivo para transferências (em GiB)", - "archived": "Arquivado", - "archived_count": "{count, plural, one {#Arquivado # item} other {Arquivados # itens}}", - "are_these_the_same_person": "Estas pessoas são a mesma pessoa?", - "are_you_sure_to_do_this": "Tem a certeza de que quer fazer isto?", - "array_field_not_fully_supported": "Campos de listas necessitam de edição manual JSON", - "asset_action_delete_err_read_only": "Não é possível eliminar ficheiro só de leitura, a ignorar", - "asset_action_share_err_offline": "Não foi possível obter os ficheiros offline, a ignorar", - "asset_added_to_album": "Adicionado ao álbum", - "asset_adding_to_album": "A adicionar ao álbum…", - "asset_created": "Ficheiro criado", - "asset_description_updated": "A descrição do ficheiro foi atualizada", - "asset_filename_is_offline": "O ficheiro {filename} não está disponível", - "asset_has_unassigned_faces": "O ficheiro tem rostos não atribuídas", - "asset_hashing": "A criar hash…", - "asset_list_group_by_sub_title": "Agrupar por", - "asset_list_layout_settings_dynamic_layout_title": "Layout dinâmico", - "asset_list_layout_settings_group_automatically": "Automático", - "asset_list_layout_settings_group_by": "Agrupar ficheiros por", - "asset_list_layout_settings_group_by_month_day": "Mês + dia", - "asset_list_layout_sub_title": "Disposição", - "asset_list_settings_subtitle": "Configurações de disposição da grade de fotos", - "asset_list_settings_title": "Grade de fotos", - "asset_not_found_on_device_android": "Ficheiro não encontrado no dispositivo", - "asset_not_found_on_device_ios": "Ficheiro não encontrado no dispositivo. Se estiver a utilizar o iCloud, o ficheiro pode estar inacessível devido a um ficheiro corrompido armazenado no iCloud", - "asset_not_found_on_icloud": "Ficheiro não encontrado no iCloud. Este pode estar inacessível devido a um ficheiro corrompido armazenado no iCloud", - "asset_offline": "Ficheiro Indisponível", - "asset_offline_description": "Este ficheiro externo deixou de estar disponível no disco. Contacte o seu administrador do Immich para obter ajuda.", - "asset_restored_successfully": "FIcheiro restaurado com sucesso", - "asset_skipped": "Ignorado", - "asset_skipped_in_trash": "Na reciclagem", - "asset_trashed": "Ficheiro apagado", - "asset_troubleshoot": "Resolução de problemas com conteúdos", - "asset_uploaded": "Enviado", - "asset_uploading": "A enviar…", - "asset_viewer_settings_subtitle": "Gerenciar as configurações do visualizador da galeria", - "asset_viewer_settings_title": "Visualizador", - "assets": "Ficheiros", - "assets_added_count": "{count, plural, one {# ficheiro adicionado} other {# ficheiros adicionados}}", - "assets_added_to_album_count": "{count, plural, one {# ficheiro adicionado} other {# ficheiros adicionados}} ao álbum", - "assets_added_to_albums_count": "{assetTotal, plural, one {Foi adicionado # ficheiro} other {Foram adiciondos # ficheiros}} a {albumTotal, plural, one {# álbum} other {# albuns}}", - "assets_cannot_be_added_to_album_count": "Não foi possível adicionar {count, plural, one {ficheiro} other {ficheiros}} ao álbum", - "assets_cannot_be_added_to_albums": "{count, plural, one {Ficheiro não pode ser adicionado} other {Ficheiros não podem ser adiciondos}} a nenhum dos álbuns", - "assets_count": "{count, plural, one {# ficheiro} other {# ficheiros}}", - "assets_deleted_permanently": "{count} ficheiro(s) eliminado(s) permanentemente", - "assets_deleted_permanently_from_server": "{count} ficheiro(s) eliminado(s) permanentemente do servidor Immich", - "assets_downloaded_failed": "{count, plural, one {# ficheiro transferido - {error} ficheiro falhou} other {# ficheiros transferidos - {error} ficheiros falharam}}", - "assets_downloaded_successfully": "{count, plural, one {# ficheiro transferido com sucesso} other {# ficheiros transferidos com sucesso}}", - "assets_moved_to_trash_count": "{count, plural, one {# ficheiro movido} other {# ficheiros movidos}} para a reciclagem", - "assets_permanently_deleted_count": "{count, plural, one {# ficheiro} other {# ficheiros}} eliminados permanentemente", - "assets_removed_count": "{count, plural, one {# ficheiro eliminado} other {# ficheiros eliminados}}", - "assets_removed_permanently_from_device": "{count} ficheiro(s) removido(s) permanentemente do seu dispositivo", - "assets_restore_confirmation": "Tem a certeza de que quer recuperar todos os ficheiros apagados? Não é possível anular esta ação! Tenha em conta de que quaisquer ficheiros indisponíveis não podem ser restaurados desta forma.", - "assets_restored_count": "{count, plural, one {# ficheiro restaurado} other {# ficheiros restaurados}}", - "assets_restored_successfully": "{count} ficheiro(s) restaurados com sucesso", - "assets_trashed": "{count} ficheiro(s) enviado(s) para a reciclagem", - "assets_trashed_count": "{count, plural, one {# ficheiro enviado} other {# ficheiros enviados}} para a reciclagem", - "assets_trashed_from_server": "{count} ficheiro(s) do servidor Immich foi/foram enviados para a reciclagem", - "assets_were_part_of_album_count": "{count, plural, one {O ficheiro já fazia} other {Os ficheiros já faziam}} parte do álbum", - "assets_were_part_of_albums_count": "{count, plural, one {Ficheiro já fazia} other {Ficheiros já faziam}} parte dos álbuns", - "authorized_devices": "Dispositivos Autorizados", - "automatic_endpoint_switching_subtitle": "Conecte-se localmente quando estiver em uma rede uma Wi-Fi específica e use conexões alternativas em outras redes", - "automatic_endpoint_switching_title": "Troca automática de URL", - "autoplay_slideshow": "Apresentação automática de diapositivos", - "back": "Voltar", - "back_close_deselect": "Voltar, fechar ou desmarcar", - "background_backup_running_error": "Com a cópia de segurança de fundo em execução, não é possível inicar uma manual", - "background_location_permission": "Permissão de localização em segundo plano", - "background_location_permission_content": "Para que seja possível trocar de redes enquanto executa em segundo plano, o Immich deve *sempre* ter a permissão de localização precisa para que a aplicação consiga ler o nome da rede Wi-Fi", - "background_options": "Opções de fundo", - "backup": "Cópia de segurança", - "backup_album_selection_page_albums_device": "Álbuns no dispositivo ({count})", - "backup_album_selection_page_albums_tap": "Toque para incluir, duplo toque para excluir", - "backup_album_selection_page_assets_scatter": "Os ficheros podem estar espalhados por vários álbuns. Desta forma, os álbuns podem ser incluídos ou excluídos durante o processo de cópia de segurança.", - "backup_album_selection_page_select_albums": "Selecione Álbuns", - "backup_album_selection_page_selection_info": "Informações da Seleção", - "backup_album_selection_page_total_assets": "Total de ficheiros únicos", - "backup_albums_sync": "Cópia de Segurança de Sincronização de Álbuns", - "backup_all": "Tudo", - "backup_background_service_backup_failed_message": "Ocorreu um erro ao efetuar cópia de segurança dos ficheiros. A tentar de novo…", - "backup_background_service_complete_notification": "Cópia de conteúdos concluída", - "backup_background_service_connection_failed_message": "Ocorreu um erro na ligação ao servidor. A tentar de novo…", - "backup_background_service_current_upload_notification": "A enviar {filename}", - "backup_background_service_default_notification": "A verificar se há novos ficheiros…", - "backup_background_service_error_title": "Erro de backup", - "backup_background_service_in_progress_notification": "A fazer cópia de segurança dos seus ficheiros…", - "backup_background_service_upload_failure_notification": "Ocorreu um erro ao enviar {filename}", - "backup_controller_page_albums": "Backup Álbuns", - "backup_controller_page_background_app_refresh_disabled_content": "Para utilizar a cópia de segurança em segundo plano, ative a atualização da aplicação em segundo plano em Definições > Geral > Atualização da aplicação em segundo plano.", - "backup_controller_page_background_app_refresh_disabled_title": "Atualização do app em segundo plano desativada", - "backup_controller_page_background_app_refresh_enable_button_text": "Ir para as configurações", - "backup_controller_page_background_battery_info_link": "Mostre-me como", - "backup_controller_page_background_battery_info_message": "Para obter a melhor experiência de backup em segundo plano, desative todas as otimizações de bateria que restrinjam a atividade em segundo plano do Immich.\n\nComo isso é específico por dispositivo, consulte as informações de como fazer isso com o fabricante do dispositivo.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Otimizações de bateria", - "backup_controller_page_background_charging": "Apenas enquanto carrega a bateria", - "backup_controller_page_background_configure_error": "Ocorreu um erro ao configurar o serviço em segundo plano", - "backup_controller_page_background_delay": "Atraso da cópia de segurança de novos ficheiros: {duration}", - "backup_controller_page_background_description": "Ative o serviço em segundo plano para fazer cópia de segurança automática de novos ficheiros sem precisar de abrir a aplicação", - "backup_controller_page_background_is_off": "O backup automático em segundo plano está desativado", - "backup_controller_page_background_is_on": "O backup automático em segundo plano está ativado", - "backup_controller_page_background_turn_off": "Desativar o serviço em segundo plano", - "backup_controller_page_background_turn_on": "Ativar o serviço em segundo plano", - "backup_controller_page_background_wifi": "Apenas em Wi-Fi", - "backup_controller_page_backup": "Cópia de segurança", - "backup_controller_page_backup_selected": "Selecionado: ", - "backup_controller_page_backup_sub": "Fotos e vídeos salvos em backup", - "backup_controller_page_created": "Criado em: {date}", - "backup_controller_page_desc_backup": "Ative a cópia de segurança em primeiro plano para enviar novos ficheiros automaticamente ao abrir a aplicação.", - "backup_controller_page_excluded": "Eliminado: ", - "backup_controller_page_failed": "Falhou ({count})", - "backup_controller_page_filename": "Nome do ficheiro: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informações do backup", - "backup_controller_page_none_selected": "Nenhum selecionado", - "backup_controller_page_remainder": "Restante", - "backup_controller_page_remainder_sub": "Fotos e vídeos selecionados restantes para fazer backup", - "backup_controller_page_server_storage": "Armazenamento no servidor", - "backup_controller_page_start_backup": "Iniciar Cópia de Segurança", - "backup_controller_page_status_off": "Backup automático desativado", - "backup_controller_page_status_on": "Backup automático ativado", - "backup_controller_page_storage_format": "{used} de {total} utilizado", - "backup_controller_page_to_backup": "Álbuns para fazer backup", - "backup_controller_page_total_sub": "Todas as fotos e vídeos dos álbuns selecionados", - "backup_controller_page_turn_off": "Desativar backup", - "backup_controller_page_turn_on": "Ativar backup", - "backup_controller_page_uploading_file_info": "A enviar informações do ficheiro", - "backup_err_only_album": "Não é possível remover apenas o álbum", - "backup_error_sync_failed": "A sincronização falhou. Não é possível fazer a cópia de segurança.", - "backup_info_card_assets": "ficheiros", - "backup_manual_cancelled": "Cancelado", - "backup_manual_in_progress": "Envio já está em progresso. Tente novamente mais tarde", - "backup_manual_success": "Sucesso", - "backup_manual_title": "Estado do envio", - "backup_options": "Definições de cópia de segurança", - "backup_options_page_title": "Opções de cópia de segurança", - "backup_setting_subtitle": "Gerenciar as configurações de envio em primeiro e segundo plano", - "backup_settings_subtitle": "Gerir definições de carregamento", - "backup_upload_details_page_more_details": "Toque para mais detalhes", - "backward": "Para trás", - "biometric_auth_enabled": "Autenticação biométrica ativada", - "biometric_locked_out": "Está impedido de utilizar a autenticação biométrica", - "biometric_no_options": "Sem opções biométricas disponíveis", - "biometric_not_available": "A autenticação biométrica não está disponível neste dispositivo", - "birthdate_saved": "Data de nascimento guardada com sucesso", - "birthdate_set_description": "A data de nascimento é utilizada para calcular a idade desta pessoa no momento em que uma fotografia foi tirada.", - "blurred_background": "Fundo desfocado", - "bugs_and_feature_requests": "Relatar problemas ou pedir novas funcionalidades", - "build": "Versão de compilação", - "build_image": "Imagem de compilação", - "bulk_delete_duplicates_confirmation": "Tem a certeza de que deseja eliminar {count, plural, one {# ficheiro duplicado} other {# ficheiros duplicados}}? Esta ação mantém o maior ficheiro de cada grupo e elimina permanentemente todos os outros duplicados. Não é possível anular esta ação!", - "bulk_keep_duplicates_confirmation": "Tem a certeza de que deseja manter {count, plural, one {# ficheiro duplicado} other {# ficheiros duplicados}}? Isto resolverá todos os grupos duplicados sem eliminar nada.", - "bulk_trash_duplicates_confirmation": "Tem a certeza de que deseja mover para a reciclagem {count, plural, one {# ficheiro duplicado} other {# ficheiros duplicados}}? Isto manterá o maior ficheiro de cada grupo e irá mover para a reciclagem todos os outros duplicados.", - "buy": "Comprar Immich", - "cache_settings_clear_cache_button": "Limpar cache", - "cache_settings_clear_cache_button_title": "Limpa a cache da aplicação. Isto irá afetar significativamente o desempenho da mesma até que a cache seja reconstruída.", - "cache_settings_duplicated_assets_clear_button": "LIMPAR", - "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que estão na lista de bloqueio da aplicação", - "cache_settings_duplicated_assets_title": "Ficheiros duplicados ({count})", - "cache_settings_statistics_album": "Miniaturas da biblioteca", - "cache_settings_statistics_full": "Imagens completas", - "cache_settings_statistics_shared": "Miniaturas de álbuns compartilhados", - "cache_settings_statistics_thumbnail": "Miniaturas", - "cache_settings_statistics_title": "Uso de cache", - "cache_settings_subtitle": "Controlar o comportamento da cache da aplicação Immich", - "cache_settings_tile_subtitle": "Controlar o comportamento do armazenamento local", - "cache_settings_tile_title": "Armazenamento local", - "cache_settings_title": "Configurações de cache", - "camera": "Câmara", - "camera_brand": "Marca da câmara", - "camera_model": "Modelo da câmara", - "cancel": "Cancelar", - "cancel_search": "Cancelar pesquisa", - "canceled": "Cancelado", - "canceling": "A cancelar", - "cannot_merge_people": "Não foi possível unir pessoas", - "cannot_undo_this_action": "Não é possível anular esta ação!", - "cannot_update_the_description": "Não foi possível atualizar a descrição", - "cast": "Transmitir", - "cast_description": "Configurar destinos de reprodução remota disponíveis", - "change_date": "Alterar data", - "change_description": "Alterar descrição", - "change_display_order": "Mudar ordem de exibição", - "change_expiration_time": "Alterar o prazo de validade", - "change_location": "Alterar localização", - "change_name": "Alterar nome", - "change_name_successfully": "Nome alterado com sucesso", - "change_password": "Alterar a palavra-passe", - "change_password_description": "Esta é a primeira vez que está a entrar no sistema ou um pedido foi feito para alterar a sua palavra-passe. Insira a nova palavra-passe abaixo.", - "change_password_form_confirm_password": "Confirmar palavra-passe", - "change_password_form_description": "Olá, {name}\n\nEsta é a primeira vez que está a aceder ao sistema, ou então foi feito um pedido para alterar a palavra-passe. Por favor insira uma nova palavra-passe abaixo.", - "change_password_form_log_out": "Terminar sessão em todos os outros dispositivos", - "change_password_form_log_out_description": "Recomenda-se que termine a sessão em todos os outros dispositivos", - "change_password_form_new_password": "Nova palavra-passe", - "change_password_form_password_mismatch": "As palavras-passe não condizem", - "change_password_form_reenter_new_password": "Confirme a nova palavra-passe", - "change_pin_code": "Alterar código PIN", - "change_trigger": "Alterar ativador", - "change_trigger_prompt": "Tem a certeza de que quer alterar o ativador? Isto irá remover todas as ações e filtros.", - "change_your_password": "Alterar a sua palavra-passe", - "changed_visibility_successfully": "Visibilidade alterada com sucesso", - "charging": "A carregar", - "charging_requirement_mobile_backup": "Cópia de segurança de fundo necessita que o dispositivo esteja a carregar", - "check_corrupt_asset_backup": "Verificar por backups corrompidos", - "check_corrupt_asset_backup_button": "Verificar", - "check_corrupt_asset_backup_description": "Execute esta verificação apenas numa rede Wi-Fi e quando a cópia de segurança de todos os ficheiros já estiver concluída. O processo pode demorar alguns minutos.", - "check_logs": "Verificar registos", - "checksum": "Teste de soma de dados", - "choose_matching_people_to_merge": "Escolha pessoas correspondentes para unir", - "city": "Cidade/Localidade", - "cleanup_confirm_description": "O Immich encontrou {count} ficheiro(s) (criados antes de {date}) que têm cópia de segurança neste servidor. Quer remover as cópias locais deste dispositivo?", - "cleanup_confirm_prompt_title": "Remover deste dispositivo?", - "cleanup_deleted_assets": "{count} ficheiro(s) foram movidos para a reciclagem do dispositivo", - "cleanup_deleting": "A mover para a reciclagem...", - "cleanup_found_assets": "Foram encontrados {count} ficheiro(s) com cópias de segurança", - "cleanup_found_assets_with_size": "Foram encontrados {count} ficheiros com cópia de segurança ({size})", - "cleanup_icloud_shared_albums_excluded": "Álbuns Partilhados do iCloud serão excluídos da pesquisa", - "cleanup_no_assets_found": "Nenhum ficheiro encontrado que siga os critérios acima. Libertar Espaço apenas pode remover ficheiros que tenham sido copiados para o servidor", - "cleanup_preview_title": "Ficheiros a serem removidos ({count})", - "cleanup_step3_description": "Procurar por ficheiros no servidor que sigam os seus critérios de data e se serão mantidos.", - "cleanup_step4_summary": "{count} ficheiros (criados antes de {date}) para remover do seu dispositivo local. As fotos irão manter-se acessíveis através da aplicação do Immich.", - "cleanup_trash_hint": "Para recuperar por completo o espaço de armazenamento, abra a aplicação da galeria do sistema e esvazie a reciclagem", - "clear": "Limpar", - "clear_all": "Limpar tudo", - "clear_all_recent_searches": "Limpar todas as pesquisas recentes", - "clear_file_cache": "Limpar cache de ficheiros", - "clear_message": "Limpar mensagem", - "clear_value": "Limpar valor", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Escrever palavra-passe", - "client_cert_import": "Importar", - "client_cert_import_success_msg": "Certificado do cliente foi importado", - "client_cert_invalid_msg": "Certificado inválido ou palavra-passe incorreta", - "client_cert_remove_msg": "Certificado do cliente foi removido", - "client_cert_subtitle": "Apenas há suporte ao formato PKCS12 (.p12, .pfx). Importar/Remover certificados está disponível apenas antes do início de sessão", - "client_cert_title": "Certificado de Cliente SSL [EXPERIMENTAL]", - "clockwise": "Sentido horário", - "close": "Fechar", - "collapse": "Colapsar", - "collapse_all": "Colapsar tudo", - "color": "Cor", - "color_theme": "Esquema de cores", - "command": "Comando", - "comment_deleted": "Comentário eliminado", - "comment_options": "Opções de comentário", - "comments_and_likes": "Comentários e gostos", - "comments_are_disabled": "Comentários estão desativados", - "common_create_new_album": "Criar novo álbum", - "completed": "Sucesso", - "confirm": "Confirmar", - "confirm_admin_password": "Confirmar palavra-passe de administrador", - "confirm_delete_face": "Tem a certeza de que deseja remover o rosto de {name} deste ficheiro?", - "confirm_delete_shared_link": "Tem a certeza de que deseja eliminar este link partilhado?", - "confirm_keep_this_delete_others": "Todos os outros ficheiros na pilha serão eliminados, exceto este ficheiro. Tem a certeza de que deseja continuar?", - "confirm_new_pin_code": "Confirmar novo código PIN", - "confirm_password": "Confirmar a palavra-passe", - "confirm_tag_face": "Deseja marcar este rosto como {name}?", - "confirm_tag_face_unnamed": "Deseja marcar este rosto?", - "connected_device": "Dispositivo ligado", - "connected_to": "Ligado a", - "contain": "Ajustar", - "context": "Contexto", - "continue": "Continuar", - "control_bottom_app_bar_create_new_album": "Criar novo álbum", - "control_bottom_app_bar_delete_from_immich": "Excluir do Immich", - "control_bottom_app_bar_delete_from_local": "Excluir do dispositivo", - "control_bottom_app_bar_edit_location": "Editar Localização", - "control_bottom_app_bar_edit_time": "Editar Data & Hora", - "control_bottom_app_bar_share_link": "Partilhar ligação", - "control_bottom_app_bar_share_to": "Compartilhar com", - "control_bottom_app_bar_trash_from_immich": "Mover para a lixeira", - "copied_image_to_clipboard": "Imagem copiada para a área de transferência.", - "copied_to_clipboard": "Copiado para a área de transferência!", - "copy_error": "Copiar erro", - "copy_file_path": "Copiar caminho do ficheiro", - "copy_image": "Copiar Imagem", - "copy_link": "Copiar link", - "copy_link_to_clipboard": "Copiar link para a área de transferência", - "copy_password": "Copiar palavra-passe", - "copy_to_clipboard": "Copiar para a área de transferência", - "country": "País", - "cover": "Preencher", - "covers": "Capas", - "create": "Criar", - "create_album": "Criar álbum", - "create_album_page_untitled": "Sem título", - "create_api_key": "Criar chave de API", - "create_first_workflow": "Criar o primeiro fluxo de trabalho", - "create_library": "Criar biblioteca", - "create_link": "Criar link", - "create_link_to_share": "Criar link para partilhar", - "create_link_to_share_description": "Permitir a visualização desta(s) imagem(s) a qualquer pessoa com o link", - "create_new": "CRIAR NOVO", - "create_new_person": "Criar nova pessoa", - "create_new_person_hint": "Associe os ficheiros a uma nova pessoa", - "create_new_user": "Criar novo utilizador", - "create_shared_album_page_share_add_assets": "ADICIONAR FICHEIROS", - "create_shared_album_page_share_select_photos": "Selecionar Fotos", - "create_shared_link": "Criar link partilhado", - "create_tag": "Criar etiqueta", - "create_tag_description": "Criar uma nova etiqueta. Para etiquetas compostas, introduza o caminho completo, incluindo as barras.", - "create_user": "Criar utilizador", - "create_workflow": "Criar fluxo de trabalho", - "created": "Criado", - "created_at": "Criado a", - "creating_linked_albums": "A criar albuns ligados...", - "crop": "Cortar", - "crop_aspect_ratio_fixed": "Fixo", - "crop_aspect_ratio_free": "Livre", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Objetos", - "current_device": "Dispositivo atual", - "current_pin_code": "Código PIN atual", - "current_server_address": "Endereço atual do servidor", - "custom_date": "Data personalizada", - "custom_locale": "Localização Personalizada", - "custom_locale_description": "Formatar datas e números baseados na língua e na região", - "custom_url": "URL personalizado", - "cutoff_date_description": "Manter fotos dos últimos…", - "cutoff_day": "{count, plural, one {dia} other {dias}}", - "cutoff_year": "{count, plural, one {ano} other {anos}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Escuro", - "dark_theme": "Alternar tema escuro", - "date": "Data", - "date_after": "Data após", - "date_and_time": "Data e Hora", - "date_before": "Data antes", - "date_format": "E, d LLL, y • h:mm a", - "date_of_birth_saved": "Data de nascimento guardada com sucesso", - "date_range": "Intervalo de datas", - "day": "Dia", - "days": "Dias", - "deduplicate_all": "Remover todos os duplicados", - "deduplication_criteria_1": "Tamanho da imagem em bytes", - "deduplication_criteria_2": "Quantidade de dados EXIF", - "deduplication_info": "Informações sobre remoção de duplicados", - "deduplication_info_description": "Para selecionar automaticamente itens e remover duplicados em massa, iremos ver o seguinte:", - "default_locale": "Localização Padrão", - "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", - "delete": "Eliminar", - "delete_action_confirmation_message": "Tem a certeza de que quer eliminar este ficheiro? Está ação irá mover o ficheiro para a reciclagem do servidor e perguntar se quer apagá-lo localmente", - "delete_action_prompt": "{count} eliminados", - "delete_album": "Apagar álbum", - "delete_api_key_prompt": "Tem a certeza de que deseja remover esta chave de API?", - "delete_dialog_alert": "Estes ficheiros serão eliminados permanentemente do Immich e do seu dispositivo", - "delete_dialog_alert_local": "Estes ficheiros serão eliminados permanentemente do seu dispositivo, mas continuarão disponíveis no servidor Immich", - "delete_dialog_alert_local_non_backed_up": "Alguns dos ficheiros não têm cópia de segurança no Immich e serão eliminados permanentemente do seu dispositivo", - "delete_dialog_alert_remote": "Estes ficheiros serão eliminados permanentemente do servidor Immich", - "delete_dialog_ok_force": "Confirmo que quero excluir", - "delete_dialog_title": "Excluir Permanentemente", - "delete_duplicates_confirmation": "Tem a certeza de que deseja eliminar permanentemente estes itens duplicados?", - "delete_face": "Remover rosto", - "delete_key": "Apagar chave", - "delete_library": "Eliminar Biblioteca", - "delete_link": "Eliminar link", - "delete_local_action_prompt": "{count} eliminados localmente", - "delete_local_dialog_ok_backed_up_only": "Eliminar apenas ficheiros com cópia de segurança", - "delete_local_dialog_ok_force": "Excluir mesmo assim", - "delete_others": "Excluir outros", - "delete_permanently": "Eliminar permanentemente", - "delete_permanently_action_prompt": "{count} eliminados permanentemente", - "delete_shared_link": "Eliminar link de partilha", - "delete_shared_link_dialog_title": "Excluir link compartilhado", - "delete_tag": "Eliminar etiqueta", - "delete_tag_confirmation_prompt": "Tem a certeza de que pretende eliminar a etiqueta {tagName} ?", - "delete_user": "Eliminar utilizador", - "deleted_shared_link": "Link de partilha eliminado", - "deletes_missing_assets": "Elimina os ficheiros que estejam em falta no disco", - "description": "Descrição", - "description_input_hint_text": "Adicionar descrição...", - "description_input_submit_error": "Erro ao atualizar a descrição, verifique o registo para obter mais detalhes", - "deselect_all": "Remover seleção de tudo", - "details": "Detalhes", - "direction": "Direção", - "disable": "Desativar", - "disabled": "Desativado", - "disallow_edits": "Não permitir edições", - "discord": "Discord", - "discover": "Descobrir", - "discovered_devices": "Dispositivos descobertos", - "dismiss_all_errors": "Dispensar todos os erros", - "dismiss_error": "Dispensar erro", - "display_options": "Opções de exibição", - "display_order": "Ordem de exibição", - "display_original_photos": "Exibir fotos originais", - "display_original_photos_setting_description": "Preferir a exibição da foto original ao visualizar um ficheiro em vez de miniaturas quando o ficheiro original é compatível com a web. Isto pode diminuir a velocidade de exibição das fotos.", - "do_not_show_again": "Não mostrar esta mensagem novamente", - "documentation": "Documentação", - "done": "Feito", - "download": "Transferir", - "download_action_prompt": "A descarregar {count} ficheiros", - "download_canceled": "Cancelado", - "download_complete": "Sucesso", - "download_enqueue": "Na fila", - "download_error": "Erro ao baixar", - "download_failed": "Ocorreu um erro ao descarregar", - "download_finished": "Concluído", - "download_include_embedded_motion_videos": "Vídeos incorporados", - "download_include_embedded_motion_videos_description": "Incluir vídeos incorporados em fotos em movimento como um ficheiro separado", - "download_notfound": "Não encontrado", - "download_original": "Descarregar original", - "download_paused": "Pausado", - "download_settings": "Transferir", - "download_settings_description": "Gerir definições relacionadas com a transferência de ficheiros", - "download_started": "Descarregamento iniciado", - "download_sucess": "Baixado com sucesso", - "download_sucess_android": "O ficheiro foi descarregado para a pasta DCIM/Immich", - "download_waiting_to_retry": "Tentando novamente", - "downloading": "A transferir", - "downloading_asset_filename": "A transferir o ficheiro {filename}", - "downloading_from_icloud": "A descarregar do iCloud", - "downloading_media": "A descarregar ficheiro", - "drop_files_to_upload": "Solte os ficheiros em qualquer lugar para os enviar", - "duplicates": "Itens duplicados", - "duplicates_description": "Marque cada grupo indicando quais ficheiros, se algum, são duplicados", - "duration": "Duração", - "edit": "Editar", - "edit_album": "Editar álbum", - "edit_avatar": "Editar imagem de perfil", - "edit_birthday": "Editar aniversário", - "edit_date": "Editar data", - "edit_date_and_time": "Editar data e hora", - "edit_date_and_time_action_prompt": "Alterada a data e hora de {count} ficheiros", - "edit_date_and_time_by_offset": "Alterar data com diferença", - "edit_date_and_time_by_offset_interval": "Novo período: {from} - {to}", - "edit_description": "Editar descrição", - "edit_description_prompt": "Por favor selecione uma nova descrição:", - "edit_exclusion_pattern": "Editar o padrão de exclusão", - "edit_faces": "Editar rostos", - "edit_key": "Editar chave", - "edit_link": "Editar link", - "edit_location": "Editar Localização", - "edit_location_action_prompt": "{count} locais alterados", - "edit_location_dialog_title": "Localização", - "edit_name": "Editar nome", - "edit_people": "Editar pessoas", - "edit_tag": "Editar etiqueta", - "edit_title": "Editar Título", - "edit_user": "Editar utilizador", - "edit_workflow": "Editar fluxo de trabalho", - "editor": "Editar", - "editor_close_without_save_prompt": "As alterações não serão guardadas", - "editor_close_without_save_title": "Fechar editor?", - "editor_confirm_reset_all_changes": "Tem a certeza de que quer desfazer todas as alterações?", - "editor_flip_horizontal": "Espelhar na horizontal", - "editor_flip_vertical": "Espelhar na vertical", - "editor_orientation": "Orientação", - "editor_reset_all_changes": "Desfazer alterações", - "editor_rotate_left": "Rodar 90° à esquerda", - "editor_rotate_right": "Rodar 90° à direita", - "email": "E-mail", - "email_notifications": "Notificações por e-mail", - "empty_folder": "Esta pasta está vazia", - "empty_trash": "Esvaziar reciclagem", - "empty_trash_confirmation": "Tem a certeza de que deseja esvaziar a reciclagem? Isto removerá todos os ficheiros da reciclagem do Immich permanentemente.\nNão é possível anular esta ação!", - "enable": "Ativar", - "enable_backup": "Ativar Cópia de Segurança", - "enable_biometric_auth_description": "Insira o código PIN para ativar a autenticação biométrica", - "enabled": "Ativado", - "end_date": "Data de fim", - "enqueued": "Na fila", - "enter_wifi_name": "Escreva o nome da rede Wi-Fi", - "enter_your_pin_code": "Insira o código PIN", - "enter_your_pin_code_subtitle": "Insira o código PIN para aceder à pasta trancada", - "error": "Erro", - "error_change_sort_album": "Ocorreu um erro ao mudar a ordem de exibição", - "error_delete_face": "Falha ao remover rosto do ficheiro", - "error_getting_places": "Erro ao obter locais", - "error_loading_albums": "Ocorreu um erro ao carregar os álbuns", - "error_loading_image": "Erro ao carregar a imagem", - "error_loading_partners": "Erro ao carregar parceiros: {error}", - "error_retrieving_asset_information": "Ocorreu um erro ao carregar as informações do ficheiro", - "error_saving_image": "Erro: {error}", - "error_tag_face_bounding_box": "Erro ao marcar o rosto - não foi possível localizar o rosto", - "error_title": "Erro - Algo correu mal", - "error_while_navigating": "Ocorreu um erro ao navegar para o ficheiro", - "errors": { - "cannot_navigate_next_asset": "Não foi possível navegar para o próximo ficheiro", - "cannot_navigate_previous_asset": "Não foi possível navegar para o ficheiro anterior", - "cant_apply_changes": "Não foi possível aplicar as alterações", - "cant_change_activity": "Não foi possível {enabled, select, true {desativar} other {ativar}} atividade", - "cant_change_asset_favorite": "Não foi possível alterar o favorito deste ficheiro", - "cant_change_metadata_assets_count": "Não foi possível alterar os metadados de {count, plural, one {# ficheiro} other {# ficheiros}}", - "cant_get_faces": "Não foi possível obter os rostos", - "cant_get_number_of_comments": "Não foi possível obter o número de comentários", - "cant_search_people": "Não foi possível pesquisar pessoas", - "cant_search_places": "Não foi possível pesquisar locais", - "error_adding_assets_to_album": "Erro ao adicionar ficheiros ao álbum", - "error_adding_users_to_album": "Erro ao adicionar utilizador ao álbum", - "error_deleting_shared_user": "Erro ao apagar o utilizador partilhado", - "error_downloading": "Erro ao transferir {filename}", - "error_hiding_buy_button": "Erro ao esconder botão de compra", - "error_removing_assets_from_album": "Erro ao eliminar ficheiros do álbum, verifique a consola para mais detalhes", - "error_selecting_all_assets": "Erro ao selecionar todos os ficheiros", - "exclusion_pattern_already_exists": "Este padrão de exclusão já existe.", - "failed_to_create_album": "Ocorreu um erro ao criar álbum", - "failed_to_create_shared_link": "Ocorreu um erro ao criar o link partilhado", - "failed_to_edit_shared_link": "Ocorreu um erro ao editar o link partilhado", - "failed_to_get_people": "Ocorreu um erro ao obter pessoas", - "failed_to_keep_this_delete_others": "Ocorreu um erro ao manter este ficheiro e eliminar os outros", - "failed_to_load_asset": "Ocorreu um erro na leitura do ficheiro", - "failed_to_load_assets": "Ocorreu um erro na leitura dos ficheiros", - "failed_to_load_notifications": "Ocorreu um erro ao carregar notificações", - "failed_to_load_people": "Ocorreu um erro ao carregar pessoas", - "failed_to_remove_product_key": "Ocorreu um erro ao remover chave de produto", - "failed_to_reset_pin_code": "Ocorreu um erro ao repor o código PIN", - "failed_to_stack_assets": "Ocorreu um erro ao empilhar os ficheiros", - "failed_to_unstack_assets": "Ocorreu um erro ao desempilhar ficheiros", - "failed_to_update_notification_status": "Ocorreu um erro ao atualizar o estado das notificações", - "incorrect_email_or_password": "Email ou palavra-passe incorretos", - "library_folder_already_exists": "Este caminho de importação já existe.", - "paths_validation_failed": "Ocorreu um erro na validação de {paths, plural, one {# caminho} other {# caminhos}}", - "profile_picture_transparent_pixels": "Imagem de perfil não pode ter pixeis transparentes. Por favor amplie e/ou mova a imagem.", - "quota_higher_than_disk_size": "Definiu uma quota maior do que o tamanho do disco", - "something_went_wrong": "Algo correu mal", - "unable_to_add_album_users": "Não foi possível adicionar utilizadores ao álbum", - "unable_to_add_assets_to_shared_link": "Não foi possível adicionar os ficheiros ao link partilhado", - "unable_to_add_comment": "Não foi possível adicionar o comentário", - "unable_to_add_exclusion_pattern": "Não foi possível adicionar o padrão de exclusão", - "unable_to_add_partners": "Não foi possível adicionar parceiros", - "unable_to_add_remove_archive": "Não foi possível {archived, select, true {remover o ficheiro de} other {adicionar o ficheiro}}", - "unable_to_add_remove_favorites": "Não foi possível {favorite, select, true {adicionar ficheiro aos} other {remover ficheiro dos}} favoritos", - "unable_to_archive_unarchive": "Não foi possível {archived, select, true {arquivar} other {desarquivar}}", - "unable_to_change_album_user_role": "Não foi possível alterar a permissão do utilizador no álbum", - "unable_to_change_date": "Não foi possível alterar a data", - "unable_to_change_description": "Não foi possível alterar a descrição", - "unable_to_change_favorite": "Não foi possível mudar o favorito do ficheiro", - "unable_to_change_location": "Não foi possível alterar a localização", - "unable_to_change_password": "Não foi possível alterar a palavra-passe", - "unable_to_change_visibility": "Não é possível alterar a visibilidade de {count, plural, one {# pessoa} other {# pessoas}}", - "unable_to_complete_oauth_login": "Não foi possível completar o início de sessão com OAuth", - "unable_to_connect": "Não é possível ligar", - "unable_to_copy_to_clipboard": "Não foi possível copiar para a área de transferência, certifique-se de que está a aceder à pagina através de https", - "unable_to_create": "Não foi possível criar um fluxo de trabalho", - "unable_to_create_admin_account": "Não foi possível criar conta de administrador", - "unable_to_create_api_key": "Não foi possível criar uma nova Chave de API", - "unable_to_create_library": "Não foi possível criar a biblioteca", - "unable_to_create_user": "Não foi possível criar o utilizador", - "unable_to_delete_album": "Não foi possível eliminar o álbum", - "unable_to_delete_asset": "Não foi possível eliminar o ficheiro", - "unable_to_delete_assets": "Erro ao eliminar ficheiros", - "unable_to_delete_exclusion_pattern": "Não foi possível eliminar o padrão de exclusão", - "unable_to_delete_shared_link": "Não foi possível eliminar o link compartilhado", - "unable_to_delete_user": "Não foi possível eliminar o utilizador", - "unable_to_delete_workflow": "Não foi possível eliminar fluxo de trabalho", - "unable_to_download_files": "Não foi possível transferir ficheiros", - "unable_to_edit_exclusion_pattern": "Não foi possível editar o padrão de exclusão", - "unable_to_empty_trash": "Não foi possível esvaziar a reciclagem", - "unable_to_enter_fullscreen": "Não foi possível entrar em modo de ecrã inteiro", - "unable_to_exit_fullscreen": "Não foi possível sair do modo de ecrã inteiro", - "unable_to_get_comments_number": "Não foi possível obter número de comentários", - "unable_to_get_shared_link": "Ocorreu um erro ao obter link partilhado", - "unable_to_hide_person": "Não foi possível esconder a pessoa", - "unable_to_link_motion_video": "Não foi possível relacionar o video animado", - "unable_to_link_oauth_account": "Não foi possível associar a conta OAuth", - "unable_to_log_out_all_devices": "Não foi possível terminar a sessão em todos os dispositivos", - "unable_to_log_out_device": "Não foi possível terminar a sessão no dispositivo", - "unable_to_login_with_oauth": "Não foi possível iniciar sessão com OAuth", - "unable_to_play_video": "Não foi possível reproduzir o vídeo", - "unable_to_reassign_assets_existing_person": "Não foi possível reatribuir ficheiros para {name, select, null {uma pessoa existente} other {{name}}}", - "unable_to_reassign_assets_new_person": "Não foi possível reatribuir os ficheiros a uma nova pessoa", - "unable_to_refresh_user": "Não foi possível recarregar o utilizador", - "unable_to_remove_album_users": "Não foi possível remover utilizador do álbum", - "unable_to_remove_api_key": "Não foi possível remover a Chave de API", - "unable_to_remove_assets_from_shared_link": "Não foi possível remover os ficheiros do link partilhado", - "unable_to_remove_library": "Não foi possível remover a biblioteca", - "unable_to_remove_partner": "Não foi possível remover parceiro", - "unable_to_remove_reaction": "Não foi possível remover a reação", - "unable_to_reset_password": "Não foi possível redefinir a palavra-passe", - "unable_to_reset_pin_code": "Não foi possível repor o código PIN", - "unable_to_resolve_duplicate": "Não foi possível resolver as duplicidades", - "unable_to_restore_assets": "Não foi possível restaurar ficheiros", - "unable_to_restore_trash": "Não foi possível restaurar itens da reciclagem", - "unable_to_restore_user": "Não foi possível restaurar utilizador", - "unable_to_save_album": "Não foi possível guardar o álbum", - "unable_to_save_api_key": "Não foi possível guardar a Chave de API", - "unable_to_save_date_of_birth": "Não foi possível guardar a data de nascimento", - "unable_to_save_name": "Não foi possível guardar o nome", - "unable_to_save_profile": "Não foi possível guardar o perfil", - "unable_to_save_settings": "Não foi possível guardar as definições", - "unable_to_scan_libraries": "Não foi possível analisar as bibliotecas", - "unable_to_scan_library": "Não foi possível analisar a biblioteca", - "unable_to_set_feature_photo": "Não foi possível definir a foto de destaque", - "unable_to_set_profile_picture": "Não foi possível definir a foto de perfil", - "unable_to_set_rating": "Não foi possível classificar", - "unable_to_submit_job": "Não foi possível enviar a tarefa", - "unable_to_trash_asset": "Não foi possível enviar o ficheiro para a reciclagem", - "unable_to_unlink_account": "Não foi possível desvincular conta", - "unable_to_unlink_motion_video": "Não foi possível remover a relação com o video animado", - "unable_to_update_album_cover": "Não foi possível atualizar a capa do álbum", - "unable_to_update_album_info": "Não foi possível atualizar informações do álbum", - "unable_to_update_library": "Não foi possível atualizar a biblioteca", - "unable_to_update_location": "Não foi possível atualizar a localização", - "unable_to_update_settings": "Não foi possível atualizar as definições", - "unable_to_update_timeline_display_status": "Não foi possível atualizar o modo de visualização da linha do tempo", - "unable_to_update_user": "Não foi possível atualizar o utilizador", - "unable_to_update_workflow": "Não foi possível atualizar o fluxo de trabalho", - "unable_to_upload_file": "Não foi possível carregar o ficheiro" - }, - "errors_text": "Erros", - "exclusion_pattern": "Padrão de exclusão", - "exif": "Exif", - "exif_bottom_sheet_description": "Adicionar Descrição...", - "exif_bottom_sheet_description_error": "Ocorreu um erro ao alterar a descrição", - "exif_bottom_sheet_details": "DETALHES", - "exif_bottom_sheet_location": "LOCALIZAÇÃO", - "exif_bottom_sheet_no_description": "Sem descrição", - "exif_bottom_sheet_people": "PESSOAS", - "exif_bottom_sheet_person_add_person": "Adicionar nome", - "exit_slideshow": "Sair da apresentação", - "expand_all": "Expandir tudo", - "experimental_settings_new_asset_list_subtitle": "Trabalho em andamento", - "experimental_settings_new_asset_list_title": "Ativar visualização de grade experimental", - "experimental_settings_subtitle": "Use por sua conta e risco!", - "experimental_settings_title": "Experimental", - "expire_after": "Expira após", - "expired": "Expirou", - "expires_date": "Expira a {date}", - "explore": "Explorar", - "explorer": "Explorador", - "export": "Exportar", - "export_as_json": "Exportar como JSON", - "export_database": "Exportar Base de Dados", - "export_database_description": "Exportar a Base de Dados SQLite", - "extension": "Extensão", - "external": "Externo", - "external_libraries": "Bibliotecas externas", - "external_network": "Rede externa", - "external_network_sheet_info": "Quando não estiver ligado à rede Wi-Fi especificada, a aplicação irá ligar-se utilizando o primeiro URL abaixo que conseguir aceder, a começar do topo da lista para baixo", - "face_unassigned": "Sem atribuição", - "failed": "Ocorreu um erro", - "failed_count": "Falhadas: {count}", - "failed_to_authenticate": "Não foi possível autenticar", - "failed_to_load_assets": "Ocorreu um erro ao carregar ficheiros", - "failed_to_load_folder": "Ocorreu um erro ao carregar a pasta", - "favorite": "Favorito", - "favorite_action_prompt": "{count} adicionados aos favoritos", - "favorite_or_unfavorite_photo": "Marcar ou desmarcar a foto como favorita", - "favorites": "Favoritos", - "favorites_page_no_favorites": "Nenhum favorito encontrado", - "feature_photo_updated": "Foto principal atualizada", - "features": "Funcionalidades", - "features_in_development": "Funcionalidades em Desenvolvimento", - "features_setting_description": "Configurar as funcionalidades da aplicação", - "file_name_or_extension": "Nome do ficheiro ou extensão", - "file_size": "Tamanho do ficheiro", - "filename": "Nome do ficheiro", - "filetype": "Tipo de ficheiro", - "filter": "Filtro", - "filter_description": "Condições para filtrar os ficheiros alvo", - "filter_people": "Filtrar pessoas", - "filter_places": "Filtrar lugares", - "filters": "Filtros", - "find_them_fast": "Encontre-as mais rapidamente pelo nome numa pesquisa", - "first": "Primeiro", - "fix_incorrect_match": "Corrigir correspondência incorreta", - "folder": "Pasta", - "folder_not_found": "Pasta não encontrada", - "folders": "Pastas", - "folders_feature_description": "Navegar na vista de pastas por fotos e vídeos no sistema de ficheiros", - "forgot_pin_code_question": "Esqueceu-se do seu PIN?", - "forward": "Para a frente", - "free_up_space": "Libertar Espaço", - "free_up_space_description": "Mover fotos e vídeos que tenham sido copiados para o servidor para a reciclagem do seu dispositivo para libertar espaço. As cópias no servidor mantêm-se seguras.", - "free_up_space_settings_subtitle": "Libertar espaço no dispositivo", - "full_path": "Caminho completo: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Esta funcionalidade requer o carregamento de recursos externos da Google para poder funcionar.", - "general": "Geral", - "geolocation_instruction_location": "Clique num ficheiro com coordenadas GPS para usar a sua localização ou selecione um local diretamente do mapa", - "get_help": "Obter Ajuda", - "get_people_error": "Ocorreu um erro ao obter pessoas", - "get_wifiname_error": "Não foi possível obter o nome do Wi-Fi. Verifique se concedeu as permissões necessárias e se está conectado a uma rede Wi-Fi", - "getting_started": "Primeiros Passos", - "go_back": "Regressar", - "go_to_folder": "Ir para a pasta", - "go_to_search": "Ir para a pesquisa", - "gps": "GPS", - "gps_missing": "Sem GPS", - "grant_permission": "Conceder permissão", - "group_albums_by": "Agrupar álbuns por...", - "group_country": "Agrupar por país", - "group_no": "Sem agrupamento", - "group_owner": "Agrupar por dono", - "group_places_by": "Agrupar lugares por...", - "group_year": "Agrupar por ano", - "haptic_feedback_switch": "Ativar vibração", - "haptic_feedback_title": "Vibração", - "has_quota": "Tem quota", - "hash_asset": "Criptografar ficheiro", - "hashed_assets": "Ficheiros criptografados", - "hashing": "A criptografar", - "header_settings_add_header_tip": "Adicionar Cabeçalho", - "header_settings_field_validator_msg": "Campo deve ser preenchido", - "header_settings_header_name_input": "Nome do cabeçalho", - "header_settings_header_value_input": "Valor do cabeçalho", - "headers_settings_tile_title": "Cabeçalhos do Proxy customizados", - "height": "Altura", - "hi_user": "Olá {name} ({email})", - "hide_all_people": "Ocultar todas as pessoas", - "hide_gallery": "Ocultar galeria", - "hide_named_person": "Ocultar pessoa {name}", - "hide_password": "Ocultar palavra-passe", - "hide_person": "Ocultar pessoa", - "hide_schema": "Ocultar esquema", - "hide_text_recognition": "Esconder reconhecimento de texto", - "hide_unnamed_people": "Ocultar pessoas sem nome", - "home_page_add_to_album_conflicts": "Foram adicionados {added} ficheiros ao álbum {album}. {failed} ficheiros já estão no álbum.", - "home_page_add_to_album_err_local": "Ainda não é possível adicionar recursos locais aos álbuns, ignorando", - "home_page_add_to_album_success": "{added} ficheiros foram adicionados ao álbum {album}.", - "home_page_album_err_partner": "Ainda não é possível adicionar ficheiros do parceiro a um álbum, a ignorar", - "home_page_archive_err_local": "Ainda não é possível arquivar recursos locais, ignorando", - "home_page_archive_err_partner": "Não é possível arquivar Fotos e Videos do parceiro, ignorando", - "home_page_building_timeline": "Construindo a linha do tempo", - "home_page_delete_err_partner": "Não é possível eliminar ficheiros do parceiro, a ignorar", - "home_page_delete_remote_err_local": "Foram selecionados ficheiros locais para excluir remotamente, a ignorar", - "home_page_favorite_err_local": "Ainda não é possível adicionar recursos locais favoritos, ignorando", - "home_page_favorite_err_partner": "Ainda não é possível marcar ficheiros do parceiro como favoritos, a ignorar", - "home_page_first_time_notice": "Se é a primeira vez que utiliza a aplicação, certifique-se de que marca pelo menos um álbum do dispositivo para cópia de segurança, para a linha do tempo poder ser preenchida com fotos e vídeos", - "home_page_locked_error_local": "Não foi possível mover ficheiros locais para a pasta trancada, a continuar", - "home_page_locked_error_partner": "Não foi possível mover ficheiros do parceiro para a pasta trancada, a continuar", - "home_page_share_err_local": "Não é possível partilhar ficheiros locais com um link, a ignorar", - "home_page_upload_err_limit": "Só é possível enviar um máximo de 30 ficheiros de cada vez, a ignorar", - "host": "Servidor", - "hour": "Hora", - "hours": "Horas", - "id": "ID", - "idle": "Em espera", - "ignore_icloud_photos": "ignorar fotos no iCloud", - "ignore_icloud_photos_description": "Fotos que estão armazenadas no iCloud não serão carregadas para o servidor do Immich", - "image": "Imagem", - "image_alt_text_date": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1} em {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1} e {person2} em {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1}, {person2}, e {person3} em {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1}, {person2}, e outras {additionalCount, number} pessoas em {date}", - "image_alt_text_date_place": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} em {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1} em {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1} e {person2} em {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {person3} em {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e outras {additionalCount, number} pessoas em {date}", - "image_saved_successfully": "Imagem salva", - "image_viewer_page_state_provider_download_started": "Descarregamento Iniciado", - "image_viewer_page_state_provider_download_success": "Baixado com sucesso", - "image_viewer_page_state_provider_share_error": "Erro ao compartilhar", - "immich_logo": "Logotipo do Immich", - "immich_web_interface": "Interface Web do Immich", - "import_from_json": "Importar a partir de JSON", - "import_path": "Caminho de importação", - "in_albums": "Em {count, plural, one {# álbum} other {# álbuns}}", - "in_archive": "Arquivado", - "in_year": "Em {year}", - "in_year_selector": "Em", - "include_archived": "Incluir arquivados", - "include_shared_albums": "Incluir álbuns partilhados", - "include_shared_partner_assets": "Incluir ficheiros partilhados por parceiros", - "individual_share": "Partilha individual", - "individual_shares": "Partilhas individuais", - "info": "Informações", - "interval": { - "day_at_onepm": "Todos os dias, às 13:00", - "hours": "A cada {hours, plural, one {hora} other {{hours, number} horas}}", - "night_at_midnight": "Todas as noites, à meia noite", - "night_at_twoam": "Todas as noites, às 02:00" - }, - "invalid_date": "Data inválida", - "invalid_date_format": "Formato de data inválido", - "invite_people": "Convidar Pessoas", - "invite_to_album": "Convidar para o álbum", - "ios_debug_info_fetch_ran_at": "Busca ocorreu a {dateTime}", - "ios_debug_info_last_sync_at": "Última sincronização a {dateTime}", - "ios_debug_info_no_processes_queued": "Não existem processos de segundo plano em fila", - "ios_debug_info_no_sync_yet": "Ainda não foi executada nenhuma tarefa de sincronização em segundo plano", - "ios_debug_info_processes_queued": "{count, plural, one {{count} processo de segundo plano em fila} other {{count} processos de segundo plano em fila}}", - "ios_debug_info_processing_ran_at": "Processamento executado em {dateTime}", - "items_count": "{count, plural, one {item #} other {itens #}}", - "jobs": "Tarefas", - "json_editor": "Editor JSON", - "json_error": "Erro JSON", - "keep": "Manter", - "keep_albums": "Manter álbuns", - "keep_albums_count": "A manter {count} {count, plural, one {álbum} other {álbuns}}", - "keep_all": "Manter Todos", - "keep_description": "Escolha o que fica no seu dispositivo quando liberta espaço.", - "keep_favorites": "Manter favoritos", - "keep_on_device": "Manter no dispositivo", - "keep_on_device_hint": "Selecionar itens para manter neste dispositivo", - "keep_this_delete_others": "Manter este ficheiro, eliminar os outros", - "keeping": "A manter: {items}", - "kept_this_deleted_others": "Foi mantido ficheiro e {count, plural, one {eliminado # outro} other {eliminados # outros}}", - "keyboard_shortcuts": "Atalhos do teclado", - "language": "Idioma", - "language_no_results_subtitle": "Tente ajustar o seu termo de pesquisa", - "language_no_results_title": "Nenhuma língua encontrada", - "language_search_hint": "Procurar línguas...", - "language_setting_description": "Selecione o seu Idioma preferido", - "large_files": "Ficheiros Grandes", - "last": "Último", - "last_months": "{count, plural, one {No último mês} other {Nos últimos # meses}}", - "last_seen": "Visto pela ultima vez", - "latest_version": "Versão mais recente", - "latitude": "Latitude", - "leave": "Sair", - "leave_album": "Sair do álbum", - "lens_model": "Modelo de lente", - "let_others_respond": "Permitir respostas", - "level": "Nível", - "library": "Biblioteca", - "library_add_folder": "Adicionar pasta", - "library_edit_folder": "Editar pasta", - "library_options": "Opções da biblioteca", - "library_page_device_albums": "Álbuns no dispositivo", - "library_page_new_album": "Novo álbum", - "library_page_sort_asset_count": "Quantidade de ficheiros", - "library_page_sort_created": "Data de criação", - "library_page_sort_last_modified": "Última modificação", - "library_page_sort_title": "Título do álbum", - "licenses": "Licenças", - "light": "Claro", - "like": "Gosto", - "like_deleted": "Gosto removido", - "link_motion_video": "Relacionar video animado", - "link_to_oauth": "Link do OAuth", - "linked_oauth_account": "Conta OAuth Associada", - "list": "Lista", - "loading": "A Carregar", - "loading_search_results_failed": "Ocorreu um erro ao carregar os resultados da pesquisa", - "local": "Local", - "local_asset_cast_failed": "Não é possível transmitir um ficheiro que não tenha sido enviado antes para o servidor", - "local_assets": "Ficheiros Locais", - "local_id": "ID Local", - "local_media_summary": "Sumário de conteúdo local", - "local_network": "Rede local", - "local_network_sheet_info": "A aplicação irá ligar-se ao servidor através desta URL quando estiver na rede Wi-Fi especificada", - "location": "Localização", - "location_permission": "Permissão de localização", - "location_permission_content": "Para utilizar a função de troca automática de URL, o Immich necessita da permissão de localização exata, para que seja possível ler o nome da rede Wi-Fi atual", - "location_picker_choose_on_map": "Escolha no mapa", - "location_picker_latitude_error": "Digite uma latitude válida", - "location_picker_latitude_hint": "Digite a latitude", - "location_picker_longitude_error": "Digite uma longitude válida", - "location_picker_longitude_hint": "Digite a longitude", - "lock": "Trancar", - "locked_folder": "Pasta Trancada", - "log_detail_title": "Detalhes de registo", - "log_out": "Sair", - "log_out_all_devices": "Terminar a sessão de todos os dispositivos", - "logged_in_as": "Utilizador atual: {user}", - "logged_out_all_devices": "Sessão terminada em todos os dispositivos", - "logged_out_device": "Sessão terminada no dispositivo", - "login": "Iniciar Sessão", - "login_disabled": "Início de sessão desativado", - "login_form_api_exception": "Erro de API. Verifique a URL do servidor e tente novamente.", - "login_form_back_button_text": "Voltar", - "login_form_email_hint": "oseuemail@email.com", - "login_form_endpoint_hint": "http://ip-do-seu-servidor:porta", - "login_form_endpoint_url": "URL do servidor", - "login_form_err_http": "Por favor especifique http:// ou https://", - "login_form_err_invalid_email": "Email Inválido", - "login_form_err_invalid_url": "URL inválido", - "login_form_err_leading_whitespace": "Espaço em branco no início", - "login_form_err_trailing_whitespace": "Espaço em branco no fim", - "login_form_failed_get_oauth_server_config": "Ocorreu um erro ao iniciar sessão com o OAuth, verifique o URL do servidor", - "login_form_failed_get_oauth_server_disable": "A função OAuth não está disponível neste servidor", - "login_form_failed_login": "Ocorreu um erro ao iniciar sessão, verifique o URL do servidor, o e-mail e a palavra-passe", - "login_form_handshake_exception": "Erro ao conectar com o servidor. Ative o suporte para certificados auto-assinados nas configurações se estiver utilizando um certificado auto-assinado.", - "login_form_password_hint": "Palavra-passe", - "login_form_save_login": "Manter sessão iniciada", - "login_form_server_empty": "Insira a URL do servidor.", - "login_form_server_error": "Não foi possível ligar ao servidor.", - "login_has_been_disabled": "O início de sessão foi desativado.", - "login_password_changed_error": "Ocorreu um erro ao atualizar a sua palavra-passe", - "login_password_changed_success": "Palavra-passe atualizada com sucesso", - "logout_all_device_confirmation": "Tem a certeza de que deseja terminar a sessão em todos os dispositivos?", - "logout_this_device_confirmation": "Tem a certeza de que deseja terminar a sessão deste dispositivo?", - "logs": "Logs", - "longitude": "Longitude", - "look": "Estilo", - "loop_videos": "Repetir vídeos", - "loop_videos_description": "Ativar para repetir os vídeos automaticamente durante a exibição.", - "main_branch_warning": "Está a usar uma versão de desenvolvimento; recomendamos vivamente que use uma versão de lançamento!", - "main_menu": "Menu Principal", - "maintenance_action_restore": "A Restaurar Base de Dados", - "maintenance_description": "O Immich foi colocado em modo de manutenção.", - "maintenance_end": "Desativar modo de manutenção", - "maintenance_end_error": "Ocorreu um erro ao desativar o modo de manutenção.", - "maintenance_logged_in_as": "Sessão iniciada como {user}", - "maintenance_restore_from_backup": "Restaurar a partir de uma cópia de segurança", - "maintenance_restore_library": "Restaurar a Sua Biblioteca", - "maintenance_restore_library_confirm": "Se isto parecer correto, continue para restaurar uma cópia de segurança!", - "maintenance_restore_library_description": "A Restaurar Base de Dados", - "maintenance_restore_library_folder_has_files": "{folder} tem {count} pasta(s)", - "maintenance_restore_library_folder_no_files": "{folder} tem ficheiros em falta!", - "maintenance_restore_library_folder_pass": "leitura e escrita possível", - "maintenance_restore_library_folder_read_fail": "leitura impossível", - "maintenance_restore_library_folder_write_fail": "escrita impossível", - "maintenance_restore_library_hint_missing_files": "Pode ter ficheiros importantes em falta", - "maintenance_restore_library_hint_regenerate_later": "Pode regenerá-las mais tarde nas definições", - "maintenance_restore_library_hint_storage_template_missing_files": "Está a utilizar um modelo de armazenamento? Pode ter ficheiros em falta", - "maintenance_restore_library_loading": "A carregar verificações de integradade e heurísticas…", - "maintenance_task_backup": "A criar uma cópia de segurança da base de dados existente…", - "maintenance_task_migrations": "A migrar base de dados…", - "maintenance_task_restore": "A restaurar a cópia de segurança selecionada…", - "maintenance_task_rollback": "Não foi possível restaurar, a reverter para o ponto de restauro…", - "maintenance_title": "Temporariamente Indisponível", - "make": "Marca", - "manage_geolocation": "Gerir localização", - "manage_media_access_rationale": "Esta autorização é necessária para a correta gestão e envio de ficheiros para a reciclagem, e para os restaurar da mesma.", - "manage_media_access_settings": "Abrir definições", - "manage_media_access_subtitle": "Autorizar que aplicação Immich faça a gestão e movimente ficheiros.", - "manage_media_access_title": "Acesso à Gestão de Ficheiros", - "manage_shared_links": "Gerir links partilhados", - "manage_sharing_with_partners": "Gerir partilha com parceiros", - "manage_the_app_settings": "Gerir definições da aplicação", - "manage_your_account": "Gerir a sua conta", - "manage_your_api_keys": "Gerir as suas Chaves de API", - "manage_your_devices": "Gerir os seus dispositivos com sessão iniciada", - "manage_your_oauth_connection": "Gerir a sua ligação ao OAuth", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {Sem fotos nesta área} one {# foto} other {# fotos}}", - "map_cannot_get_user_location": "Impossível obter a sua localização", - "map_location_dialog_yes": "Sim", - "map_location_picker_page_use_location": "Utilizar esta localização", - "map_location_service_disabled_content": "Serviço de localização precisa de estar ativado para mostrar recursos da localização atual. Deseja ativar agora?", - "map_location_service_disabled_title": "Serviço de localização desativado", - "map_marker_for_images": "Marcador no mapa para fotos tiradas em {city}, {country}", - "map_marker_with_image": "Marcador de mapa com imagem", - "map_no_location_permission_content": "A permissão da localização é necessária para mostrar recursos da localização atual. Deseja conceder a permissão agora?", - "map_no_location_permission_title": "Permissão de localização foi negada", - "map_settings": "Definições do mapa", - "map_settings_dark_mode": "Modo escuro", - "map_settings_date_range_option_day": "Últimas 24 horas", - "map_settings_date_range_option_days": "Últimos {days} dias", - "map_settings_date_range_option_year": "Último ano", - "map_settings_date_range_option_years": "Últimos {years} anos", - "map_settings_dialog_title": "Configurações do mapa", - "map_settings_include_show_archived": "Incluir arquivados", - "map_settings_include_show_partners": "Incluir parceiros", - "map_settings_only_show_favorites": "Mostrar apenas favoritos", - "map_settings_theme_settings": "Tema do mapa", - "map_zoom_to_see_photos": "Diminua o zoom para ver mais fotos", - "mark_all_as_read": "Marcar tudo como lido", - "mark_as_read": "Marcar como lido", - "marked_all_as_read": "Tudo marcado como lido", - "matches": "Correspondências", - "matching_assets": "Conteúdos coincidentes", - "media_type": "Tipo de média", - "memories": "Memórias", - "memories_all_caught_up": "Finalizamos por hoje", - "memories_check_back_tomorrow": "Volte amanhã para ver mais memórias", - "memories_setting_description": "Gerir o que vê nas suas memórias", - "memories_start_over": "Ver de novo", - "memories_swipe_to_close": "Deslize para cima para fechar", - "memory": "Memória", - "memory_lane_title": "Memórias {title}", - "menu": "Menu", - "merge": "Unir", - "merge_people": "Unir pessoas", - "merge_people_limit": "Só é possível unir até 5 rostos de cada vez", - "merge_people_prompt": "Tem a certeza de que deseja unir estas pessoas? Esta ação é irreversível.", - "merge_people_successfully": "Pessoas unidas com sucesso", - "merged_people_count": "Unidas {count, plural, one {# pessoa} other {# pessoas}}", - "minimize": "Minimizar", - "minute": "Minuto", - "minutes": "Minutos", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertical", - "missing": "Em falta", - "mobile_app": "App móvel", - "mobile_app_download_onboarding_note": "Descarregue a aplicação para dispositivos móveis com as seguintes opções", - "model": "Modelo", - "month": "Mês", - "monthly_title_text_date_format": "MMMM y", - "more": "Mais", - "move": "Mover", - "move_down": "Mover para baixo", - "move_off_locked_folder": "Mover para fora da pasta trancada", - "move_to": "Mover para", - "move_to_device_trash": "Mover para a reciclagem do dispositivo", - "move_to_lock_folder_action_prompt": "{count} adicionados à pasta trancada", - "move_to_locked_folder": "Mover para a pasta trancada", - "move_to_locked_folder_confirmation": "Estas fotos e vídeos serão removidas de todos os álbuns, e só serão visíveis na pasta trancada", - "move_up": "Mover para cima", - "moved_to_archive": "{count, plural, one {Foi movido # ficheiro} other {Foram movidos # ficheiros}} para o arquivo", - "moved_to_library": "{count, plural, one {Foi movido # ficheiro} other {Foram movidos # ficheiros}} para a biblioteca", - "moved_to_trash": "Enviado para a reciclagem", - "multiselect_grid_edit_date_time_err_read_only": "Não é possível editar a data de um ficheiro só de leitura, a ignorar", - "multiselect_grid_edit_gps_err_read_only": "Não é possível editar a localização de um ficheiro só de leitura, a ignorar", - "mute_memories": "Silenciar Memórias", - "my_albums": "Os meus álbuns", - "name": "Nome", - "name_or_nickname": "Nome ou alcunha", - "name_required": "O nome é obrigatório", - "navigate": "Navegar", - "navigate_to_time": "Navegar para Horário", - "network_requirement_photos_upload": "Usar dados móveis para fazer cópia de segurança de fotos", - "network_requirement_videos_upload": "Usar dados móveis para fazer cópia de segurança de vídeos", - "network_requirements": "Requisitos de rede", - "network_requirements_updated": "Requisitos de rede alterados, a redefinir fila de cópia de segurança", - "networking_settings": "Ligações", - "networking_subtitle": "Gerir as ligações de rede do servidor", - "never": "Nunca", - "new_album": "Novo Álbum", - "new_api_key": "Nova Chave de API", - "new_date_range": "Nova faixa de datas", - "new_password": "Nova palavra-passe", - "new_person": "Nova Pessoa", - "new_pin_code": "Novo código PIN", - "new_pin_code_subtitle": "Esta é a primeira vez que acede à pasta trancada. Crie um código PIN para aceder a esta página de forma segura", - "new_timeline": "Nova Linha do Tempo", - "new_update": "Nova atualização", - "new_user_created": "Novo utilizador criado", - "new_version_available": "NOVA VERSÃO DISPONÍVEL", - "newest_first": "Mais recente primeiro", - "next": "Avançar", - "next_memory": "Próxima memória", - "no": "Não", - "no_actions_added": "Ainda não foram adicionadas ações", - "no_albums_found": "Nenhum álbum encontrado", - "no_albums_message": "Crie um álbum para organizar as suas fotos e vídeos", - "no_albums_with_name_yet": "Parece que ainda não tem nenhum álbum com este nome.", - "no_albums_yet": "Parece que ainda não tem nenhum álbum.", - "no_archived_assets_message": "Arquive fotos e vídeos para os ocultar da sua visualização de fotos", - "no_assets_message": "Clique para carregar a sua primeira foto", - "no_assets_to_show": "Não há ficheiros para exibir", - "no_cast_devices_found": "Nenhum dispositivo de transmissão encontrado", - "no_checksum_local": "Sem cálculo de verificação disponível - não pode capturar conteúdos locais", - "no_checksum_remote": "Soma de verificação (checksum) não disponível - não é possível obter o recurso remoto", - "no_configuration_needed": "Configuração não é necessária", - "no_devices": "Nenhum dispositivo autorizado", - "no_duplicates_found": "Nenhum item duplicado foi encontrado.", - "no_exif_info_available": "Sem informações exif disponíveis", - "no_explore_results_message": "Carregue mais fotos para explorar a sua coleção.", - "no_favorites_message": "Adicione aos favoritos para encontrar as suas melhores fotos e vídeos rapidamente", - "no_filters_added": "Ainda não foram adicionados filtros", - "no_libraries_message": "Crie uma biblioteca externa para ver as suas fotos e vídeos", - "no_local_assets_found": "Sem cálculo de verificação disponível", - "no_location_set": "Sem localização definida", - "no_locked_photos_message": "Fotos e vídeos na pasta trancada estão ocultos e não serão exibidos enquanto explora ou pesquisa na biblioteca.", - "no_name": "Sem nome", - "no_notifications": "Sem notificações", - "no_people_found": "Nenhuma pessoa encontrada", - "no_places": "Sem lugares", - "no_remote_assets_found": "Soma de verificação (checksum) não disponível - não é possível obter o recurso remoto", - "no_results": "Sem resultados", - "no_results_description": "Tente um sinónimo ou uma palavra-chave mais comum", - "no_shared_albums_message": "Crie um álbum para partilhar fotos e vídeos com pessoas na sua rede", - "no_uploads_in_progress": "Nenhum carregamento em curso", - "none": "Nenhum", - "not_allowed": "Não permitido", - "not_available": "N/A", - "not_in_any_album": "Não está em nenhum álbum", - "not_selected": "Não selecionado", - "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar o Rótulo de Armazenamento a ficheiros carregados anteriormente, execute o", - "notes": "Notas", - "nothing_here_yet": "Ainda não existe nada aqui", - "notification_permission_dialog_content": "Para ativar as notificações, vá em Configurações e selecione permitir.", - "notification_permission_list_tile_content": "Conceder permissões para ativar notificações.", - "notification_permission_list_tile_enable_button": "Ativar notificações", - "notification_permission_list_tile_title": "Permissão de notificações", - "notification_toggle_setting_description": "Ativar notificações por e-mail", - "notifications": "Notificações", - "notifications_setting_description": "Gerir notificações", - "oauth": "OAuth", - "obtainium_configurator": "Configurador Obtainium", - "obtainium_configurator_instructions": "Utilize o Obtainium para instalar e atualizar a aplicação Android diretamente a partir do lançamento do Immich no Github. Crie uma chave API e selecione uma variante para criar a sua ligação de configuração do Obtainium", - "ocr": "Reconhecimento Ótico de Caracteres (OCR)", - "official_immich_resources": "Recursos oficiais do Immich", - "offline": "Offline", - "offset": "Desvio", - "ok": "Ok", - "oldest_first": "Mais antigo primeiro", - "on_this_device": "Neste dispositivo", - "onboarding": "Integração", - "onboarding_locale_description": "Selecione a sua língua preferida. Pode alterá-la mais tarde nas definições.", - "onboarding_privacy_description": "As seguintes funcionalidades opcionais dependem de serviços externos e podem ser desativados a qualquer momento nas definições.", - "onboarding_server_welcome_description": "Vamos configurar a sua instância com algumas definições comuns.", - "onboarding_theme_description": "Escolha um tema de cor para sua instância. Pode alterar isto mais tarde nas suas definições.", - "onboarding_user_welcome_description": "Vamos começar!", - "onboarding_welcome_user": "Bem-vindo(a), {user}", - "online": "Online", - "only_favorites": "Apenas favoritos", - "open": "Abrir", - "open_in_map_view": "Abrir na visualização de mapa", - "open_in_openstreetmap": "Abrir no OpenStreetMap", - "open_the_search_filters": "Abrir os filtros de pesquisa", - "options": "Opções", - "or": "ou", - "organize_into_albums": "Organizar em álbuns", - "organize_into_albums_description": "Colocar fotos existentes em álbuns utilizando as definições atuais de sincronização", - "organize_your_library": "Organizar a sua biblioteca", - "original": "original", - "other": "Outro", - "other_devices": "Outros dispositivos", - "other_entities": "Outras entidades", - "other_variables": "Outras variáveis", - "owned": "Seus", - "owner": "Dono", - "page": "Página", - "partner": "Parceiro", - "partner_can_access": "{partner} pode aceder", - "partner_can_access_assets": "Todas as suas fotos e vídeos, exceto os Arquivados ou Eliminados", - "partner_can_access_location": "A localização onde as fotos foram tiradas", - "partner_list_user_photos": "Fotos de {user}", - "partner_list_view_all": "Ver tudo", - "partner_page_empty_message": "As suas fotos ainda não foram compartilhadas com nenhum parceiro.", - "partner_page_no_more_users": "Não existem mais utilizadores para adicionar", - "partner_page_partner_add_failed": "Ocorreu um erro ao adicionar parceiro", - "partner_page_select_partner": "Selecionar parceiro", - "partner_page_shared_to_title": "Compartilhar com", - "partner_page_stop_sharing_content": "{partner} irá deixar de ter acesso às suas fotos.", - "partner_sharing": "Partilha com Parceiro", - "partners": "Parceiros", - "password": "Palavra-passe", - "password_does_not_match": "A palavra-passe não condiz", - "password_required": "A palavra-passe é obrigatória", - "password_reset_success": "Palavra-passe redefinida com sucesso", - "past_durations": { - "days": "{days, plural, one {Último dia} other {# últimos dias}}", - "hours": "Últimas {hours, plural, one {horas} other {# horas}}", - "years": "{years, plural, one {Último ano} other {Últimos # anos}}" - }, - "path": "Caminho", - "pattern": "Padrão", - "pause": "Pausa", - "pause_memories": "Pausar memórias", - "paused": "Em Pausa", - "pending": "Pendente", - "people": "Pessoas", - "people_edits_count": "{count, plural, one {# pessoa editada} other {# pessoas editadas}}", - "people_feature_description": "Navegar por fotos e vídeos agrupados por pessoas", - "people_selected": "{count, plural, one {# pessoa selecionada} other {# pessoas selecionadas}}", - "people_sidebar_description": "Exibir o link Pessoas na barra lateral", - "permanent_deletion_warning": "Aviso de eliminação permanente", - "permanent_deletion_warning_setting_description": "Exibir um aviso ao eliminar ficheiros de forma permanente", - "permanently_delete": "Eliminar permanentemente", - "permanently_delete_assets_count": "Eliminar permanentemente {count, plural, one {ficheiro} other {ficheiros}}", - "permanently_delete_assets_prompt": "Tem a certeza de que deseja eliminar permanentemente {count, plural, one {este ficheiro?} other {estes # ficheiros?}} Esta ação também removerá {count, plural, one {isto do álbum} other {isto dos álbuns}}.", - "permanently_deleted_asset": "Ficheiro eliminado permanentemente", - "permanently_deleted_assets_count": "{count, plural, one {# Ficheiro eliminado} other {# Ficheiros eliminados}} permanentemente", - "permission": "Permissão", - "permission_empty": "A sua permissão não pode estar vazia", - "permission_onboarding_back": "Voltar", - "permission_onboarding_continue_anyway": "Continuar mesmo assim", - "permission_onboarding_get_started": "Começar", - "permission_onboarding_go_to_settings": "Ir para as configurações", - "permission_onboarding_permission_denied": "Permissão negada. Para utilizar o Immich, conceda permissões de fotos e vídeo nas configurações.", - "permission_onboarding_permission_granted": "Permissão concedida! Está tudo pronto.", - "permission_onboarding_permission_limited": "Permissão limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissões para fotos e vídeos nas configurações.", - "permission_onboarding_request": "O Immich requer autorização para ver as suas fotos e vídeos.", - "person": "Pessoa", - "person_age_months": "{months, plural, one {# month} other {# months}} de idade", - "person_age_year_months": "1 ano, {months, plural, one {# month} other {# months}} de idade", - "person_age_years": "{years, plural, other {# anos}} de idade", - "person_birthdate": "Nasceu a {date}", - "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", - "person_recognized": "Pessoa reconhecida", - "person_selected": "Pessoa selecionada", - "photo_shared_all_users": "Parece que já partilhou as suas fotos com todos os utilizadores ou não tem nenhum utilizador com quem partilhar.", - "photos": "Fotos", - "photos_and_videos": "Fotos & Vídeos", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", - "photos_from_previous_years": "Fotos de anos anteriores", - "photos_only": "Apenas fotografias", - "pick_a_location": "Selecione uma localização", - "pick_custom_range": "Intervalo personalizado", - "pick_date_range": "Selecione um intervalo de datas", - "pin_code_changed_successfully": "Código PIN alterado com sucesso", - "pin_code_reset_successfully": "Código PIN reposto com sucesso", - "pin_code_setup_successfully": "Código PIN configurado com sucesso", - "pin_verification": "Verificação do código PIN", - "place": "Lugar", - "places": "Lugares", - "places_count": "{count, plural, one {{count, number} Lugar} other {{count, number} Lugares}}", - "play": "Reproduzir", - "play_memories": "Reproduzir memórias", - "play_motion_photo": "Reproduzir foto em movimento", - "play_or_pause_video": "Reproduzir ou Pausar vídeo", - "play_original_video": "Reproduzir o vídeo original", - "play_original_video_setting_description": "Preferir a reprodução de vídeos originais em vez de vídeos transcodificados. Se o ficheiro original não for compatível, este pode não ser reproduzido corretamente.", - "play_transcoded_video": "Reproduzir vídeo transcodificado", - "please_auth_to_access": "Por favor autentique-se para aceder", - "port": "Porta", - "preferences_settings_subtitle": "Gerir as preferências da aplicação", - "preferences_settings_title": "Preferências", - "preparing": "A Preparar", - "preset": "Predefinição", - "preview": "Pré-visualizar", - "previous": "Anterior", - "previous_memory": "Memória anterior", - "previous_or_next_day": "Dia seguinte/anterior", - "previous_or_next_month": "Mês seguinte/anterior", - "previous_or_next_photo": "Foto seguinte/anterior", - "previous_or_next_year": "Ano seguinte/anterior", - "primary": "Primário", - "privacy": "Privacidade", - "profile": "Perfil", - "profile_drawer_app_logs": "Registo", - "profile_drawer_client_server_up_to_date": "Cliente e Servidor atualizados", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Modo só de leitura ativado. Faça um toque longo no ícone do perfil do utilizador para sair.", - "profile_image_of_user": "Imagem de perfil de {user}", - "profile_picture_set": "Foto de perfil definida.", - "public_album": "Álbum público", - "public_share": "Partilhar Publicamente", - "purchase_account_info": "Apoiante", - "purchase_activated_subtitle": "Agradecemos o seu apoio ao Immich e ao software de código aberto", - "purchase_activated_time": "Ativado em {date}", - "purchase_activated_title": "A sua chave foi ativada com sucesso", - "purchase_button_activate": "Ativar", - "purchase_button_buy": "Comprar", - "purchase_button_buy_immich": "Comprar Immich", - "purchase_button_never_show_again": "Não mostrar de novo", - "purchase_button_reminder": "Relembrar-me daqui a 30 dias", - "purchase_button_remove_key": "Remover chave", - "purchase_button_select": "Selecionar", - "purchase_failed_activation": "Não foi possível ativar! Verifique o seu e-mail para obter a chave de produto correta!", - "purchase_individual_description_1": "Para uma pessoa individual", - "purchase_individual_description_2": "Status de apoiante", - "purchase_individual_title": "Particular", - "purchase_input_suggestion": "Tem uma chave de produto? Insira a chave abaixo", - "purchase_license_subtitle": "Compre o Immich para apoiar o desenvolvimento contínuo do serviço", - "purchase_lifetime_description": "Compra vitalícia", - "purchase_option_title": "OPÇÕES DE COMPRA", - "purchase_panel_info_1": "O desenvolvimento do Immich requer muito tempo e esforço, e temos engenheiros a tempo inteiro a trabalhar nele para melhorá-lo quanto possível. A nossa missão é para que o software de código aberto e práticas de negócio éticas se tornem numa fonte de rendimento sustentável para os desenvolvedores e criar um ecossistema que respeite a privacidade dos utilizadores e que ofereça alternativas reais a serviços cloud explorativos.", - "purchase_panel_info_2": "Uma vez que estamos empenhados em não adicionar barreiras de pagamento, esta compra não lhe dará quaisquer funcionalidades adicionais no Immich. Contamos com utilizadores como você para apoiar o desenvolvimento contínuo do Immich.", - "purchase_panel_title": "Apoie o projeto", - "purchase_per_server": "Por servidor", - "purchase_per_user": "Por utilizador", - "purchase_remove_product_key": "Remover chave de produto", - "purchase_remove_product_key_prompt": "Tem a certeza de que deseja remover a chave do produto?", - "purchase_remove_server_product_key": "Remover chave do produto do servidor", - "purchase_remove_server_product_key_prompt": "Tem a certeza de que deseja remover a chave do produto do servidor?", - "purchase_server_description_1": "Para o servidor inteiro", - "purchase_server_description_2": "Status de apoiante", - "purchase_server_title": "Servidor", - "purchase_settings_server_activated": "A chave de produto do servidor é gerida pelo administrador", - "query_asset_id": "Consultar ID do ficheiro", - "queue_status": "Em fila {count}/{total}", - "rate_asset": "Classificar ficheiro", - "rating": "Classificação por estrelas", - "rating_clear": "Limpar classificação", - "rating_count": "{count, plural, one {# estrela} other {# estrelas}}", - "rating_description": "Mostrar a classificação EXIF no painel de informações", - "rating_set": "Classificação definida para {rating, plural, one {# estrela} other {# estrelas}}", - "reaction_options": "Opções de reação", - "read_changelog": "Ler Novidades", - "readonly_mode_disabled": "Modo só de leitura desativado", - "readonly_mode_enabled": "Modo só de leitura ativado", - "ready_for_upload": "Pronto para upload", - "reassign": "Reatribuir", - "reassigned_assets_to_existing_person": "Reatribuir {count, plural, one {# ficheiro} other {# ficheiros}} para {name, select, null {uma pessoa existente} other {{name}}}", - "reassigned_assets_to_new_person": "Reatribuído {count, plural, one {# ficheiro} other {# ficheiros}} a uma nova pessoa", - "reassing_hint": "Atribuir ficheiros selecionados a uma pessoa existente", - "recent": "Recentes", - "recent-albums": "Álbuns recentes", - "recent_searches": "Pesquisas recentes", - "recently_added": "Adicionados Recentemente", - "recently_added_page_title": "Adicionado recentemente", - "recently_taken": "Tirada recentemente", - "recently_taken_page_title": "Tiradas recentemente", - "refresh": "Atualizar", - "refresh_encoded_videos": "Atualizar vídeos codificados", - "refresh_faces": "Atualizar rostos", - "refresh_metadata": "Atualizar metadados", - "refresh_thumbnails": "Atualizar miniaturas", - "refreshed": "Atualizado", - "refreshes_every_file": "Recarrega todos os ficheiros já existentes e novos", - "refreshing_encoded_video": "A atualizar vídeo codificado", - "refreshing_faces": "A atualizar rostos", - "refreshing_metadata": "A atualizar metadados", - "regenerating_thumbnails": "A atualizar miniaturas", - "remote": "Remoto", - "remote_assets": "Ficheiros Remotos", - "remote_media_summary": "Sumário de Ficheiros Remotos", - "remove": "Remover", - "remove_assets_album_confirmation": "Tem a certeza de que deseja remover {count, plural, one {# ficheiro} other {# ficheiros}} do álbum?", - "remove_assets_shared_link_confirmation": "Tem certeza de que deseja remover {count, plural, one {# ficheiro} other {# ficheiros}} deste link partilhado?", - "remove_assets_title": "Remover ficheiros?", - "remove_custom_date_range": "Remover intervalo de datas personalizado", - "remove_deleted_assets": "Remover ficheiros indisponíveis", - "remove_from_album": "Remover do álbum", - "remove_from_album_action_prompt": "{count} removido(s) do álbum", - "remove_from_favorites": "Remover dos favoritos", - "remove_from_lock_folder_action_prompt": "{count} removidos da pasta trancada", - "remove_from_locked_folder": "Remover da pasta trancada", - "remove_from_locked_folder_confirmation": "Tem a certeza de que quer mover estas fotos e vídeos para fora da pasta trancada? Passarão a ser visíveis na biblioteca.", - "remove_from_shared_link": "Remover do link partilhado", - "remove_memory": "Remover memória", - "remove_photo_from_memory": "Remover foto desta memória", - "remove_tag": "Remover marcador", - "remove_url": "Remover URL", - "remove_user": "Remover utilizador", - "removed_api_key": "Foi removida a Chave de API: {name}", - "removed_from_archive": "Removido do arquivo", - "removed_from_favorites": "Removido dos favoritos", - "removed_from_favorites_count": "{count, plural, other {Removidos #}} dos favoritos", - "removed_memory": "Memória removida", - "removed_photo_from_memory": "Foto removida da memória", - "removed_tagged_assets": "Removida a etiqueta de {count, plural, one {# ficheiro} other {# ficheiros}}", - "rename": "Mudar o nome", - "repair": "Reparar", - "repair_no_results_message": "Ficheiros em falta ou não monitorizados irão aparecer aqui", - "replace_with_upload": "Substituir pelo ficheiro carregado", - "repository": "Repositório", - "require_password": "Proteger com palavra-passe", - "require_user_to_change_password_on_first_login": "Obrigar utilizador a alterar a palavra-passe após o primeiro início de sessão", - "rescan": "Reanalisar", - "reset": "Redefinir", - "reset_password": "Redefinir palavra-passe", - "reset_people_visibility": "Redefinir pessoas ocultas", - "reset_pin_code": "Repor código PIN", - "reset_pin_code_description": "Se esqueceu o seu código PIN, pode entrar em contacto com o administrador do servidor para o repor", - "reset_pin_code_success": "Código PIN redefinido com sucesso", - "reset_pin_code_with_password": "Pode sempre repor o seu código PIN com a sua palavra-passe", - "reset_sqlite": "Reiniciar Base de Dados SQLite", - "reset_sqlite_confirmation": "Tem a certeza de que quer reiniciar a base de dados SQLite? Vai ter de terminar a sessão e entrar outra vez para sincronizar os dados de novo", - "reset_sqlite_success": "Base de dados SQLite reiniciada com sucesso", - "reset_to_default": "Repor predefinições", - "resolution": "Resolução", - "resolve_duplicates": "Resolver itens duplicados", - "resolved_all_duplicates": "Todos os itens duplicados resolvidos", - "restore": "Restaurar", - "restore_all": "Restaurar tudo", - "restore_trash_action_prompt": "{count} restaurados da reciclagem", - "restore_user": "Restaurar utilizador", - "restored_asset": "Ficheiro restaurado", - "resume": "Continuar", - "resume_paused_jobs": "Continuar {count, plural, one {# trabalho em pausa} other {# trabalhos em pausa}}", - "retry_upload": "Tentar carregar novamente", - "review_duplicates": "Rever itens duplicados", - "review_large_files": "Rever ficheiros grandes", - "role": "Função", - "role_editor": "Editor", - "role_viewer": "Visualizador", - "running": "A executar", - "save": "Guardar", - "save_to_gallery": "Salvar na galeria", - "saved": "Guardado", - "saved_api_key": "Chave de API guardada", - "saved_profile": "Perfil guardado", - "saved_settings": "Definições guardadas", - "say_something": "Diga alguma coisa", - "scaffold_body_error_occurred": "Ocorreu um erro", - "scan": "Analisar", - "scan_all_libraries": "Analisar todas as bibliotecas", - "scan_library": "Analisar", - "scan_settings": "Opções de análise", - "scanning": "A analisar", - "scanning_for_album": "A analisar por álbum...", - "search": "Pesquisar", - "search_albums": "Pesquisar álbuns", - "search_by_context": "Pesquisar por contexto", - "search_by_description": "Pesquisar por descrição", - "search_by_description_example": "Dia de caminhada em Leiria", - "search_by_filename": "Pesquisar por nome de ficheiro ou extensão", - "search_by_filename_example": "por exemplo, IMG_1234.JPG ou PNG", - "search_by_ocr": "Pesquisar por OCR", - "search_by_ocr_example": "Galão", - "search_camera_lens_model": "Pesquisar por modelo de lente...", - "search_camera_make": "Pesquisar por marca da câmara...", - "search_camera_model": "Pesquisar por modelo da câmara...", - "search_city": "Pesquisar cidade...", - "search_country": "Pesquisar país...", - "search_filter_apply": "Aplicar filtro", - "search_filter_camera_title": "Selecione o tipo de câmara", - "search_filter_date": "Data", - "search_filter_date_interval": "{start} até {end}", - "search_filter_date_title": "Selecione a data", - "search_filter_display_option_not_in_album": "Fora de álbum", - "search_filter_display_options": "Opções de exibição", - "search_filter_filename": "Pesquisar por nome do ficheiro", - "search_filter_location": "Localização", - "search_filter_location_title": "Selecione a localização", - "search_filter_media_type": "Tipo de ficheiro", - "search_filter_media_type_title": "Selecione o tipo do ficheiro", - "search_filter_ocr": "Pesquisar por OCR", - "search_filter_people_title": "Selecionar pessoas", - "search_filter_star_rating": "Classificação", - "search_for": "Pesquisar por", - "search_for_existing_person": "Pesquisar por pessoas existentes", - "search_no_more_result": "Sem mais resultados", - "search_no_people": "Sem pessoas", - "search_no_people_named": "Nenhuma pessoa chamada \"{name}\"", - "search_no_result": "Nenhum resultado encontrado, tente pesquisar por algo diferente", - "search_options": "Opções de pesquisa", - "search_page_categories": "Categorias", - "search_page_motion_photos": "Fotos com movimento", - "search_page_no_objects": "Nenhuma informação de objeto disponível", - "search_page_no_places": "Nenhuma informação de local disponível", - "search_page_screenshots": "Capturas de tela", - "search_page_search_photos_videos": "Pesquise suas fotos e vídeos", - "search_page_selfies": "Auto-retratos (Selfies)", - "search_page_things": "Objetos", - "search_page_view_all_button": "Ver tudo", - "search_page_your_activity": "A sua atividade", - "search_page_your_map": "O seu mapa", - "search_people": "Pesquisar pessoas", - "search_places": "Pesquisar lugares", - "search_rating": "Pesquisar por classificação...", - "search_result_page_new_search_hint": "Nova Pesquisa", - "search_settings": "Pesquisar nas Definições", - "search_state": "Pesquisar estado/distrito...", - "search_suggestion_list_smart_search_hint_1": "A pesquisa inteligente está ativada por omissão. Para pesquisar por metadados, utilize a sintaxe ", - "search_suggestion_list_smart_search_hint_2": "m:a-sua-pesquisa", - "search_tags": "Pesquisar etiquetas...", - "search_timezone": "Pesquisar fuso horário...", - "search_type": "Tipo de pesquisa", - "search_your_photos": "Pesquisar fotos", - "searching_locales": "A pesquisar Lugares....", - "second": "Segundo", - "see_all_people": "Ver todas as pessoas", - "select": "Selecionar", - "select_album": "Selecionar álbum", - "select_album_cover": "Escolher capa do álbum", - "select_albums": "Selecionar álbuns", - "select_all": "Selecionar todos", - "select_all_duplicates": "Selecionar todos os itens duplicados", - "select_all_in": "Selecionar tudo em {group}", - "select_avatar_color": "Selecionar cor do avatar", - "select_count": "{count, plural, one {Selecionar #} other {Selecionar #}}", - "select_cutoff_date": "Selecionar data limite", - "select_face": "Selecionar rosto", - "select_featured_photo": "Selecionar foto principal", - "select_from_computer": "Selecionar a partir do computador", - "select_keep_all": "Selecionar manter todos", - "select_library_owner": "Selecionar o dono da biblioteca", - "select_new_face": "Selecionar novo rosto", - "select_people": "Selecionar pessoas", - "select_person": "Selecionar pessoa", - "select_person_to_tag": "Selecione uma pessoa para etiquetar", - "select_photos": "Selecionar fotos", - "select_trash_all": "Selecionar todos para reciclagem", - "select_user_for_sharing_page_err_album": "Ocorreu um erro ao criar o álbum", - "selected": "Selecionados", - "selected_count": "{count, plural, other {# selecionados}}", - "selected_gps_coordinates": "Coordenadas GPS selecionadas", - "send_message": "Enviar mensagem", - "send_welcome_email": "Enviar E-mail de boas vindas", - "server_endpoint": "URL do servidor", - "server_info_box_app_version": "Versão do app", - "server_info_box_server_url": "URL do servidor", - "server_offline": "Servidor Offline", - "server_online": "Servidor Online", - "server_privacy": "Privacidade do Servidor", - "server_restarting_description": "A página irá atualizar dentro de momentos.", - "server_restarting_title": "O servidor está a reiniciar", - "server_stats": "Estado do servidor", - "server_update_available": "Está disponível uma atualização do servidor", - "server_version": "Versão do servidor", - "set": "Definir", - "set_as_album_cover": "Definir como capa do álbum", - "set_as_featured_photo": "Definir como foto principal", - "set_as_profile_picture": "Definir como foto de perfil", - "set_date_of_birth": "Definir data de nascimento", - "set_profile_picture": "Definir foto de perfil", - "set_slideshow_to_fullscreen": "Apresentação em ecrã inteiro", - "set_stack_primary_asset": "Definir como ficheiro principal", - "setting_image_viewer_help": "O visualizador carrega primeiro a miniatura pequena, depois carrega a visualização de tamanho médio (se ativado) e, finalmente, carrega o original (se ativado).", - "setting_image_viewer_original_subtitle": "Ative para carregar a imagem original em resolução total (grande!). Desative para reduzir o uso de dados (na rede e no cache do dispositivo).", - "setting_image_viewer_original_title": "Carregar imagem original", - "setting_image_viewer_preview_subtitle": "Ative para carregar uma imagem de resolução média. Desative para carregar diretamente o original ou usar apenas a miniatura.", - "setting_image_viewer_preview_title": "Carregar imagem de pré-visualização", - "setting_image_viewer_title": "Imagens", - "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Alterar o idioma da aplicação", - "setting_notifications_notify_failures_grace_period": "Notificar erros da cópia de segurança em segundo plano: {duration}", - "setting_notifications_notify_hours": "{count} horas", - "setting_notifications_notify_immediately": "imediatamente", - "setting_notifications_notify_minutes": "{count} minutos", - "setting_notifications_notify_never": "Nunca", - "setting_notifications_notify_seconds": "{count} segundos", - "setting_notifications_single_progress_subtitle": "Informações detalhadas sobre o progresso do envio por ficheiro", - "setting_notifications_single_progress_title": "Mostrar progresso detalhado do backup em segundo plano", - "setting_notifications_subtitle": "Ajuste as preferências de notificação", - "setting_notifications_total_progress_subtitle": "Progresso do envio de ficheiro (concluídos/total)", - "setting_notifications_total_progress_title": "Mostrar progresso total do backup em segundo plano", - "setting_video_viewer_auto_play_subtitle": "Reproduzir os vídeos automaticamente quando abertos", - "setting_video_viewer_auto_play_title": "Reproduzir vídeos automaticamente", - "setting_video_viewer_looping_title": "Repetir", - "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o ficheiro original, mesmo se uma versão transcodificada estiver disponível. Pode causar interrupções. Vídeos disponíveis localmente são exibidos na qualidade original independentemente desta definição.", - "setting_video_viewer_original_video_title": "Forçar vídeo original", - "settings": "Definições", - "settings_require_restart": "Reinicie o Immich para aplicar essa configuração", - "settings_saved": "Definições guardadas", - "setup_pin_code": "Configurar um código PIN", - "share": "Partilhar", - "share_action_prompt": "Partilhados {count} ficheiros", - "share_add_photos": "Adicionar fotos", - "share_assets_selected": "{count} selecionados", - "share_dialog_preparing": "Preparando...", - "share_link": "Partilhar ligação", - "shared": "Partilhado", - "shared_album_activities_input_disable": "Comentários desativados", - "shared_album_activity_remove_content": "Deseja apagar esta atividade?", - "shared_album_activity_remove_title": "Apagar atividade", - "shared_album_section_people_action_error": "Erro ao sair/remover do álbum", - "shared_album_section_people_action_leave": "Sair do álbum", - "shared_album_section_people_action_remove_user": "Remover utilizador do álbum", - "shared_album_section_people_title": "PESSOAS", - "shared_by": "Partilhado por", - "shared_by_user": "Partilhado por {user}", - "shared_by_you": "Partilhado por si", - "shared_from_partner": "Fotos de {partner}", - "shared_intent_upload_button_progress_text": "Enviados {current} de {total}", - "shared_link_app_bar_title": "Links compartilhados", - "shared_link_clipboard_copied_massage": "Copiado para a área de transferência", - "shared_link_clipboard_text": "Ligação: {link}\nPalavra-passe: {password}", - "shared_link_create_error": "Erro ao criar o link compartilhado", - "shared_link_custom_url_description": "Aceda a este link partilhado com um URL personalizado", - "shared_link_edit_description_hint": "Digite a descrição do compartilhamento", - "shared_link_edit_expire_after_option_day": "1 dia", - "shared_link_edit_expire_after_option_days": "{count} dias", - "shared_link_edit_expire_after_option_hour": "1 hora", - "shared_link_edit_expire_after_option_hours": "{count} horas", - "shared_link_edit_expire_after_option_minute": "1 minuto", - "shared_link_edit_expire_after_option_minutes": "{count} minutos", - "shared_link_edit_expire_after_option_months": "{count} meses", - "shared_link_edit_expire_after_option_year": "{count} ano", - "shared_link_edit_password_hint": "Escreva a palavra-passe do link partilhado", - "shared_link_edit_submit_button": "Atualizar link", - "shared_link_error_server_url_fetch": "Erro ao abrir a URL do servidor", - "shared_link_expires_day": "Expira {count} dia", - "shared_link_expires_days": "Expira {count} dias", - "shared_link_expires_hour": "Expira {count} hora", - "shared_link_expires_hours": "Expira {count} horas", - "shared_link_expires_minute": "Expira {count} minuto", - "shared_link_expires_minutes": "Expira {count} minutos", - "shared_link_expires_never": "Expira ∞", - "shared_link_expires_second": "Expira {count} segundo", - "shared_link_expires_seconds": "Expira {count} segundos", - "shared_link_individual_shared": "Compartilhamento único", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Gerenciar links compartilhados", - "shared_link_options": "Opções de link partilhado", - "shared_link_password_description": "Exigir uma palavra-passe para aceder a este link partilhado", - "shared_links": "Links partilhados", - "shared_links_description": "Partilhar fotos e videos com um link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# Fotos & videos partilhados.}}", - "shared_with_me": "Partilhado comigo", - "shared_with_partner": "Partilhado com {partner}", - "sharing": "Partilha", - "sharing_enter_password": "Por favor, insira a palavra-passe para ver esta página.", - "sharing_page_album": "Álbuns partilhados", - "sharing_page_description": "Crie álbuns partilhados para partilhar fotos e vídeos com pessoas da sua rede.", - "sharing_page_empty_list": "LISTA VAZIA", - "sharing_sidebar_description": "Exibe o link para Partilhar na barra lateral", - "sharing_silver_appbar_create_shared_album": "Criar álbum partilhado", - "sharing_silver_appbar_share_partner": "Compartilhar com parceiro", - "shift_to_permanent_delete": "Pressione ⇧ para eliminar o ficheiro permanentemente", - "show_album_options": "Exibir opções do álbum", - "show_albums": "Mostrar álbuns", - "show_all_people": "Mostrar todas as pessoas", - "show_and_hide_people": "Mostrar & ocultar pessoas", - "show_file_location": "Exibir localização do ficheiro", - "show_gallery": "Exibir galeria", - "show_hidden_people": "Exibir pessoas ocultadas", - "show_in_timeline": "Exibir na linha do tempo", - "show_in_timeline_setting_description": "Exibe fotos e vídeos deste utilizador na sua linha do tempo", - "show_keyboard_shortcuts": "Exibir atalhos do teclado", - "show_metadata": "Mostrar metadados", - "show_or_hide_info": "Exibir ou ocultar informações", - "show_password": "Mostrar palavra-passe", - "show_person_options": "Exibir opções da pessoa", - "show_progress_bar": "Exibir barra de progresso", - "show_schema": "Mostrar esquema", - "show_search_options": "Mostrar opções de pesquisa", - "show_shared_links": "Mostrar links partilhados", - "show_slideshow_transition": "Mostrar transições no Modo de Apresentação", - "show_supporter_badge": "Emblema de apoiante", - "show_supporter_badge_description": "Mostrar um emblema de apoiante", - "show_text_recognition": "Mostrar reconhecimento de texto", - "show_text_search_menu": "Mostrar menu de pesquisa de texto", - "shuffle": "Aleatório", - "sidebar": "Barra lateral", - "sidebar_display_description": "Mostrar um link para a vista na barra lateral", - "sign_out": "Terminar sessão", - "sign_up": "Criar conta", - "size": "Tamanho", - "skip_to_content": "Saltar para o conteúdo", - "skip_to_folders": "Saltar para pastas", - "skip_to_tags": "Saltar para as etiquetas", - "slideshow": "Apresentação", - "slideshow_repeat": "Repetir apresentação de diapositivos", - "slideshow_repeat_description": "Repetir do inicio quando a apresentação acabar", - "slideshow_settings": "Definições de apresentação", - "sort_albums_by": "Ordenar álbuns por...", - "sort_created": "Data de criação", - "sort_items": "Número de itens", - "sort_modified": "Data de modificação", - "sort_newest": "A foto mais recente", - "sort_oldest": "Foto mais antiga", - "sort_people_by_similarity": "Ordenar pessoas por semelhança", - "sort_recent": "Foto mais recente", - "sort_title": "Título", - "source": "Fonte", - "stack": "Empilhar", - "stack_action_prompt": "{count} empilhados", - "stack_duplicates": "Empilhar itens duplicados", - "stack_select_one_photo": "Selecione uma foto principal para a pilha", - "stack_selected_photos": "Empilhar fotos selecionadas", - "stacked_assets_count": "Empilhado {count, plural, one {# ficheiro} other {# ficheiros}}", - "stacktrace": "Stacktrace", - "start": "Iniciar", - "start_date": "Data de início", - "start_date_before_end_date": "A data de início deve ser anterior à data de fim", - "state": "Estado/Distrito", - "status": "Estado", - "stop_casting": "Parar transmissão", - "stop_motion_photo": "Parar foto em movimento", - "stop_photo_sharing": "Deixar de partilhar as suas fotos?", - "stop_photo_sharing_description": "{partner} deixará de ter acesso às suas fotos.", - "stop_sharing_photos_with_user": "Deixar de partilhar as fotos com este utilizador", - "storage": "Espaço de armazenamento", - "storage_label": "Rótulo de Armazenamento", - "storage_quota": "Quota de armazenamento", - "storage_usage": "Utilizado {used} de {available}", - "submit": "Enviar", - "success": "Sucesso", - "suggestions": "Sugestões", - "sunrise_on_the_beach": "Nascer do sol na praia", - "support": "Apoio", - "support_and_feedback": "Apoio e feedback", - "support_third_party_description": "A sua instalação do Immich foi empacotada por terceiros. Quaisquer problemas que possa vir a ter poderão ser causados por esse pacote, por isso, em primeiro lugar, relate problemas aos criadores desse pacote utilizando os links abaixo.", - "swap_merge_direction": "Alternar direção da união", - "sync": "Sincronizar", - "sync_albums": "Sincronizar álbuns", - "sync_albums_manual_subtitle": "Sincronizar todas as fotos e vídeos enviados para o álbum de backup selecionado", - "sync_local": "Sincronização Local", - "sync_remote": "Sincronização Remota", - "sync_status": "Estado da sincronização", - "sync_status_subtitle": "Ver e gerir o sistema de sincronização", - "sync_upload_album_setting_subtitle": "Crie e envie suas fotos e vídeos para o álbum selecionado no Immich", - "tag": "Etiqueta", - "tag_assets": "Etiquetar ficheiros", - "tag_created": "Criada a etiqueta {tag}", - "tag_feature_description": "A mostrar fotos e videos agrupados por tópicos lógicos de etiquetas", - "tag_not_found_question": "Não consegue encontrar a etiqueta? Crie uma nova etiqueta.", - "tag_people": "Etiquetar Pessoas", - "tag_updated": "Atualizada a etiqueta: {tag}", - "tagged_assets": "Etiquetado {count, plural, one {# ficheiros} other {# ficheiros}}", - "tags": "Etiquetas", - "tap_to_run_job": "Tocar para executar tarefa", - "template": "Modelo", - "text_recognition": "Reconhecimento de texto", - "theme": "Tema", - "theme_selection": "Selecionar tema", - "theme_selection_description": "Definir automaticamente o tema como claro ou escuro com base na preferência do sistema do seu navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de armazenamento na grade de fotos", - "theme_setting_asset_list_tiles_per_row_title": "Quantidade de ficheiros por linha ({count})", - "theme_setting_colorful_interface_subtitle": "Aplica a cor primária ao fundo.", - "theme_setting_colorful_interface_title": "Interface colorida", - "theme_setting_image_viewer_quality_subtitle": "Ajuste a qualidade do visualizador de imagens detalhadas", - "theme_setting_image_viewer_quality_title": "Qualidade do visualizador de imagens", - "theme_setting_primary_color_subtitle": "Selecione a cor primária, utilizada nas ações principais e nos realces.", - "theme_setting_primary_color_title": "Cor primária", - "theme_setting_system_primary_color_title": "Use a cor do sistema", - "theme_setting_system_theme_switch": "Automático (Seguir a configuração do sistema)", - "theme_setting_theme_subtitle": "Escolha a configuração do tema da aplicação", - "theme_setting_three_stage_loading_subtitle": "O carregamento em três estágios pode aumentar o desempenho do carregamento, mas causa uma carga de rede significativamente maior", - "theme_setting_three_stage_loading_title": "Habilitar carregamento em três estágios", - "then": "Depois", - "they_will_be_merged_together": "Eles serão unidos", - "third_party_resources": "Recursos de terceiros", - "time": "Hora", - "time_based_memories": "Memórias baseadas no tempo", - "time_based_memories_duration": "Número de segundos para exibir cada imagem.", - "timeline": "Linha de tempo", - "timezone": "Fuso horário", - "to_archive": "Arquivar", - "to_change_password": "Alterar palavra-passe", - "to_favorite": "Favorito", - "to_login": "Iniciar Sessão", - "to_multi_select": "multi-selecção", - "to_parent": "Subir um nível", - "to_select": "seleccionar", - "to_trash": "Reciclagem", - "toggle_settings": "Alternar configurações", - "toggle_theme_description": "Alternar tema", - "total": "Total", - "total_usage": "Total utilizado", - "trash": "Reciclagem", - "trash_action_prompt": "{count} movidos para a reciclagem", - "trash_all": "Mover todos para a reciclagem", - "trash_count": "Reciclar {count, number}", - "trash_delete_asset": "Eliminar ficheiro", - "trash_emptied": "Lixeira esvaziada", - "trash_no_results_message": "Fotos e vídeos enviados para a reciclagem aparecem aqui.", - "trash_page_delete_all": "Excluir tudo", - "trash_page_empty_trash_dialog_content": "Deseja esvaziar a reciclagem? Estes ficheiros serão apagados permanentemente do Immich", - "trash_page_info": "Ficheiros na reciclagem irão ser eliminados permanentemente após {days} dias", - "trash_page_no_assets": "Lixeira vazia", - "trash_page_restore_all": "Restaurar tudo", - "trash_page_select_assets_btn": "Selecionar ficheiros", - "trash_page_title": "Reciclagem ({count})", - "trashed_items_will_be_permanently_deleted_after": "Os itens da reciclagem são eliminados permanentemente após {days, plural, one {# dia} other {# dias}}.", - "trigger": "Ativador", - "trigger_asset_uploaded": "Ficheiro Carregado", - "trigger_asset_uploaded_description": "Ativado quando um novo ficheiro é carregado", - "trigger_description": "Um evento que irá começar o fluxo de trabalho", - "trigger_person_recognized": "Pessoa Reconhecida", - "trigger_person_recognized_description": "Ativado quando uma pessoa for detetada", - "trigger_type": "Tipo de ativador", - "troubleshoot": "Diagnosticar problemas", - "type": "Tipo", - "unable_to_change_pin_code": "Não foi possível alterar o código PIN", - "unable_to_check_version": "Ocorreu um erro ao verificar versão da aplicação ou do servidor", - "unable_to_setup_pin_code": "Não foi possível configurar o código PIN", - "unarchive": "Desarquivar", - "unarchive_action_prompt": "{count} removidos do Arquivo", - "unarchived_count": "{count, plural, other {Não arquivado #}}", - "undo": "Anular", - "unfavorite": "Remover favorito", - "unfavorite_action_prompt": "{count} removidos dos Favoritos", - "unhide_person": "Exibir pessoa", - "unknown": "Desconhecido", - "unknown_country": "País desconhecido", - "unknown_date": "Data desconhecida", - "unknown_year": "Ano desconhecido", - "unlimited": "Ilimitado", - "unlink_motion_video": "Remover relação com video animado", - "unlink_oauth": "Desvincular OAuth", - "unlinked_oauth_account": "Conta OAuth desvinculada", - "unmute_memories": "Ativar som das memórias", - "unnamed_album": "Álbum sem nome", - "unnamed_album_delete_confirmation": "Tem a certeza de que pretende eliminar este álbum?", - "unnamed_share": "Partilha sem nome", - "unsaved_change": "Alteração não guardada", - "unselect_all": "Limpar seleção", - "unselect_all_duplicates": "Remover seleção de todos os itens duplicados", - "unselect_all_in": "Remover seleção de {group}", - "unstack": "Desempilhar", - "unstack_action_prompt": "{count} desempilhados", - "unstacked_assets_count": "Desempilhados {count, plural, one {# ficheiro} other {# ficheiros}}", - "unsupported_field_type": "Tipo de campo não suportado", - "untagged": "Sem etiqueta", - "untitled_workflow": "Fluxo de trabalho sem nome", - "up_next": "A seguir", - "update_location_action_prompt": "Atualize a localização de {count} ficheiros selecionados com:", - "updated_at": "Atualizado a", - "updated_password": "Palavra-passe atualizada", - "upload": "Carregar", - "upload_concurrency": "Carregamentos em simultâneo", - "upload_details": "Detalhes do Carregamento", - "upload_dialog_info": "Deseja realizar uma cópia de segurança dos ficheiros selecionados para o servidor?", - "upload_dialog_title": "Enviar ficheiro", - "upload_error_with_count": "Erro ao carregar {count, plural, one {# ficheiro} other {# ficheiros}}", - "upload_errors": "Envio completo com {count, plural, one {# erro} other {# erros}}, atualize a página para ver os novos ficheiros enviados.", - "upload_finished": "Carregamento acabado", - "upload_progress": "Restante(s) {remaining, number} - Processado(s) {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {# Ignorado ficheiro duplicado} other {# Ignorados ficheiros duplicados}}", - "upload_status_duplicates": "Duplicados", - "upload_status_errors": "Erros", - "upload_status_uploaded": "Enviado", - "upload_success": "Carregamento realizado com sucesso, atualize a página para ver os novos ficheiros carregados.", - "upload_to_immich": "Enviar para o Immich ({count})", - "uploading": "Enviando", - "uploading_media": "A carregar media", - "url": "URL", - "usage": "Utilização", - "use_biometric": "Utilizar dados biométricos", - "use_current_connection": "Utilizar a ligação atual", - "use_custom_date_range": "Utilizar um intervalo de datas personalizado", - "user": "Utilizador", - "user_has_been_deleted": "Este utilizador for eliminado.", - "user_id": "ID do utilizador", - "user_liked": "{user} gostou {type, select, photo {desta fotografia} video {deste video} asset {deste ficheiro} other {disto}}", - "user_pin_code_settings": "Código PIN", - "user_pin_code_settings_description": "Gerir o seu código PIN", - "user_privacy": "Privacidade de Utilizador", - "user_purchase_settings": "Comprar", - "user_purchase_settings_description": "Gerir a sua compra", - "user_role_set": "Definir {user} como {role}", - "user_usage_detail": "Detalhes de utilização por utilizador", - "user_usage_stats": "Estatísticas de utilização de conta", - "user_usage_stats_description": "Ver estatísticas de utilização de conta", - "username": "Nome de utilizador", - "users": "Utilizadores", - "users_added_to_album_count": "{count, plural, one {Foi adicionado # utilizador} other {Foram adicionados # utilizadores}} ao álbum", - "utilities": "Ferramentas", - "validate": "Validar", - "validate_endpoint_error": "Digite uma URL válida", - "validation_error": "Erro de validação", - "variables": "Variáveis", - "version": "Versão", - "version_announcement_closing": "O seu amigo, Alex", - "version_announcement_message": "Olá! Está disponível uma nova versão do Immich. Por favor leia as notas de lançamento para garantir que as suas configurações estão atualizadas e para evitar quaisquer erros, especialmente se usar o WatchTower ou qualquer mecanismo que lide com a atualização automática do Immich.", - "version_history": "Histórico de versões", - "version_history_item": "Instalado {version} em {date}", - "video": "Vídeo", - "video_hover_setting": "Reproduzir vídeo em miniatura quando passar com o cursor por cima", - "video_hover_setting_description": "Reproduzir vídeo em miniatura quando o cursor está sobre o item. Mesmo quando está desativado, a reprodução ainda pode ser iniciada passando sobre o ícone de reproduzir.", - "videos": "Vídeos", - "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", - "videos_only": "Apenas vídeos", - "view": "Ver", - "view_album": "Ver Álbum", - "view_all": "Ver tudo", - "view_all_users": "Ver todos os utilizadores", - "view_asset_owners": "Ver donos do ficheiro", - "view_details": "Ver Detalhes", - "view_in_timeline": "Ver na linha do tempo", - "view_link": "Ver link", - "view_links": "Ver links", - "view_name": "Ver", - "view_next_asset": "Ver próximo ficheiro", - "view_previous_asset": "Ver ficheiro anterior", - "view_qr_code": "Ver código QR", - "view_similar_photos": "Ver fotos similares", - "view_stack": "Ver pilha", - "view_user": "Ver utilizador", - "viewer_remove_from_stack": "Remover da pilha", - "viewer_stack_use_as_main_asset": "Usar como foto principal", - "viewer_unstack": "Desempilhar", - "visibility_changed": "Visibilidade alterada para {count, plural, one {# pessoa} other {# pessoas}}", - "visual": "Visual", - "visual_builder": "Construtor visual", - "waiting": "Em fila", - "waiting_count": "Em espera: {count}", - "warning": "Aviso", - "week": "Semana", - "welcome": "Bem-vindo(a)", - "welcome_to_immich": "Bem-vindo(a) ao Immich", - "width": "Largura", - "wifi_name": "Nome da rede Wi-Fi", - "workflow_delete_prompt": "Tem a certeza de que quer eliminar este fluxo de trabalho?", - "workflow_deleted": "Fluxo de trabalho eliminado", - "workflow_description": "Descrição do fluxo de trabalho", - "workflow_info": "Informação do fluxo de trabalho", - "workflow_json": "Fluxo de trabalho JSON", - "workflow_json_help": "Editar a configuração do fluxo de trabalho em formato JSON. Mudanças irão ser sincronizadas com o construtor visual.", - "workflow_name": "Nome do fluxo de trabalho", - "workflow_navigation_prompt": "Tem a certeza de que quer sair sem guardar as alterações?", - "workflow_summary": "Resumo do fluxo de trabalho", - "workflow_update_success": "Fluxo de trabalho atualizado com sucesso", - "workflow_updated": "Fluxo de trabalho atualizado", - "workflows": "Fluxos de trabalho", - "workflows_help_text": "Fluxos de trabalho automatizam ações nos seus ficheiros baseados em ativadores e filtros", - "wrong_pin_code": "Código PIN errado", - "year": "Ano", - "years_ago": "Há {years, plural, one {# ano} other {# anos}}", - "yes": "Sim", - "you_dont_have_any_shared_links": "Não tem links partilhados", - "your_wifi_name": "Nome da sua rede Wi-Fi", - "zero_to_clear_rating": "Carregue no 0 para retirar a classificação", - "zoom_image": "Ampliar/Reduzir imagem", - "zoom_to_bounds": "Aproximar aos limites" -} +{} diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index 6a98ddf8e1..0967ef424b 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -1,2401 +1 @@ -{ - "about": "Sobre", - "account": "Conta", - "account_settings": "Configurações da Conta", - "acknowledge": "Entendi", - "action": "Ação", - "action_common_update": "Atualizar", - "action_description": "Um conjunto de ações a serem executadas nos arquivos filtrados", - "actions": "Ações", - "active": "Em execução", - "active_count": "Ativo: {count}", - "activity": "Atividade", - "activity_changed": "Atividade foi {enabled, select, true {ativada} other {desativada}}", - "add": "Adicionar", - "add_a_description": "Adicionar uma descrição", - "add_a_location": "Adicionar uma localização", - "add_a_name": "Adicionar um nome", - "add_a_title": "Adicionar um título", - "add_action": "Adicionar ação", - "add_action_description": "Clique para adicionar uma ação", - "add_assets": "Adicionar arquivos", - "add_birthday": "Definir aniversário", - "add_endpoint": "Adicionar URL", - "add_exclusion_pattern": "Adicionar padrão de exclusão", - "add_filter": "Adicionar filtro", - "add_filter_description": "Clique para adicional uma condição no filtro", - "add_location": "Adicionar local", - "add_more_users": "Adicionar mais usuários", - "add_partner": "Adicionar parceiro", - "add_path": "Adicionar caminho", - "add_photos": "Adicionar fotos", - "add_tag": "Adicionar Marcador", - "add_to": "Adicionar a…", - "add_to_album": "Adicionar ao álbum", - "add_to_album_bottom_sheet_added": "Adicionado ao {album}", - "add_to_album_bottom_sheet_already_exists": "Já existe em {album}", - "add_to_album_bottom_sheet_some_local_assets": "Alguns arquivos locais não puderam ser adicionados ao álbum", - "add_to_album_toggle": "Alternar a seleção de {album}", - "add_to_albums": "Adicionar aos álbuns", - "add_to_albums_count": "Adicionar aos álbuns ({count})", - "add_to_bottom_bar": "Incluir em", - "add_to_shared_album": "Adicionar ao álbum compartilhado", - "add_upload_to_stack": "Adicionar ao grupo", - "add_url": "Adicionar URL", - "add_workflow_step": "Adicionar uma etapa no fluxo", - "added_to_archive": "Adicionado ao arquivo", - "added_to_favorites": "Adicionado aos favoritos", - "added_to_favorites_count": "{count, plural, one {{count, number} adicionado aos favoritos} other {{count, number} adicionados aos favoritos}}", - "admin": { - "add_exclusion_pattern_description": "Adicione padrões de exclusão. Utilizar *, ** ou ? são suportados. Para ignorar todos os arquivos em qualquer diretório chamado \"Raw\", use \"**/Raw/**'. Para ignorar todos os arquivos que terminam em \".tif\", use \"**/*.tif\". Para ignorar um caminho absoluto, use \"/caminho/para/ignorar/**\".", - "admin_user": "Usuário Administrador", - "asset_offline_description": "Este arquivo externo não foi encontrado no disco e foi movido para a lixeira. Se o arquivo foi movido para outra pasta da biblioteca externa, verifique se ele está disponível na linha do tempo. Para restaurar este arquivo, certifique-se de que o caminho abaixo é acessível pelo Immich e escaneie a biblioteca novamente.", - "authentication_settings": "Configurações de Autenticação", - "authentication_settings_description": "Gerenciar senhas, OAuth, e outras configurações de autenticação", - "authentication_settings_disable_all": "Tem certeza de que deseja desativar todos os métodos de login? O login será completamente desativado.", - "authentication_settings_reenable": "Para reabilitar, use um Comando do Servidor.", - "background_task_job": "Tarefas em segundo plano", - "backup_database": "Criar backup do banco de dados", - "backup_database_enable_description": "Ativar backup do banco de dados", - "backup_keep_last_amount": "Quantidade de backups anteriores para manter salvo", - "backup_onboarding_1_description": "Uma cópia na nuvem ou outro lugar físico.", - "backup_onboarding_2_description": "Cópias em dispositivos diferentes. Incluindo os arquivos originais e o backup deles.", - "backup_onboarding_3_description": "Cópias completas de seus dados, com os arquivos originais. Inclusive 1 cópia externa e 2 cópias locais.", - "backup_onboarding_description": "A estratégia de backup 3-2-1 é recomendada para proteger seus dados. Para uma solução completa de backup, você deve manter cópias de suas fotos, vídeos e backups do banco de dados do Immich.", - "backup_onboarding_footer": "Para mais informações sobre o backup do Immich, leia a documentação.", - "backup_onboarding_parts_title": "O backup 3-2-1 é definido por:", - "backup_onboarding_title": "Backups", - "backup_settings": "Configurações de backup", - "backup_settings_description": "Gerenciar configurações de backup do banco de dados.", - "cleared_jobs": "Tarefas removidas de: {job}", - "config_set_by_file": "A configuração está atualmente definida por um arquivo de configuração", - "confirm_delete_library": "Você tem certeza que deseja excluir a biblioteca {library} ?", - "confirm_delete_library_assets": "Você tem certeza que deseja excluir esta biblioteca? Isso excluirá {count, plural, one {# arquivo contido do Immich e não poderá ser desfeito. O arquivo permanecerá no disco} other {todos os # arquivos contidos do Immich e não poderá ser desfeito. Os arquivos permanecerão no disco}}.", - "confirm_email_below": "Para confirmar, digite \"{email}\" abaixo", - "confirm_reprocess_all_faces": "Tem certeza de que deseja reprocessar todos os rostos? Isso também limpará as pessoas nomeadas.", - "confirm_user_password_reset": "Tem certeza de que deseja redefinir a senha de {user}?", - "confirm_user_pin_code_reset": "Tem certeza de que deseja redefinir o código PIN do usuário {user}?", - "copy_config_to_clipboard_description": "Copiar as configurações do sistema como um objeto JSON para a área de transferência", - "create_job": "Criar tarefa", - "cron_expression": "Expressão cron", - "cron_expression_description": "Defina o intervalo de análise no formato Cron. Para mais informações, por favor veja o Crontab Guru", - "cron_expression_presets": "Sugestões de expressão Cron", - "disable_login": "Desabilitar login", - "duplicate_detection_job_description": "Execute o aprendizado de máquina em arquivos para detectar imagens semelhantes. Depende da Pesquisa Inteligente", - "exclusion_pattern_description": "Os padrões de exclusão permitem ignorar arquivos e pastas ao escanear sua biblioteca. Isso é útil se você tiver pastas que contenham arquivos que não deseja importar, como arquivos RAW.", - "export_config_as_json_description": "Baixar as configurações de sistema atuais para um arquivo JSON", - "external_libraries_page_description": "Página de administração de biblioteca externa", - "face_detection": "Detecção de rostos", - "face_detection_description": "Detectar rostos nos arquivos usando aprendizado de máquina. Para vídeos, apenas a miniatura é considerada. ‘Atualizar’ (re)processa todos os arquivos. ‘Resetar’ também limpa todos os dados de rosto atuais. ‘Faltando’ coloca em fila os arquivos que ainda não foram processados. Rostos detectados serão colocados em fila para o Reconhecimento Facial após a conclusão da Detecção de Rostos, agrupando-os em pessoas existentes ou novas.", - "facial_recognition_job_description": "Agrupar rostos detectados em pessoas. Esta etapa é executada após a conclusão da Detecção de Rostos. ‘Resetar’ (re)agrupará todos os rostos. ‘Faltando’ coloca em fila os rostos que não têm uma pessoa atribuída.", - "failed_job_command": "O comando {command} falhou para a tarefa: {job}", - "force_delete_user_warning": "AVISO: Isso removerá imediatamente o usuário e todos os arquivos. Isso não pode ser desfeito e os arquivos não podem ser recuperados.", - "image_format": "Formato", - "image_format_description": "WebP produz arquivos menores que JPEG, mas é mais lento para codificar.", - "image_fullsize_description": "Imagem em tamanho real sem os metadados exibida quando der zoom", - "image_fullsize_enabled": "Ativar geração de imagem no tamanho real", - "image_fullsize_enabled_description": "Gerar imagens no tamanho real para os formatos de arquivos não compatíveis com a web. Quando \"Preferir visualização incorporada\" estiver ativado, essas serão utilizadas sem conversão. Não afeta arquivos já em formatos para web, como JPEG.", - "image_fullsize_quality_description": "Qualidade da imagem em tamanho real, de 1 a 100. Valores maiores tem melhor qualidade, mas gera arquivos maiores.", - "image_fullsize_title": "Configurações de imagem em tamanho real", - "image_prefer_embedded_preview": "Preferir visualização incorporada", - "image_prefer_embedded_preview_setting_description": "Use visualizações incorporadas em fotos RAW como a entrada para processamento de imagem e quando disponível. Isso pode produzir cores mais precisas para algumas imagens, mas a qualidade da visualização depende da câmera e a imagem pode ter mais artefatos de compactação.", - "image_prefer_wide_gamut": "Prefira ampla gama", - "image_prefer_wide_gamut_setting_description": "Use o Display P3 para miniaturas. Isso preserva melhor a vibração das imagens com espaços de cores amplos, mas as imagens podem aparecer de maneira diferente em dispositivos antigos com uma versão antiga do navegador. As imagens sRGB são mantidas como sRGB para evitar mudanças de cores.", - "image_preview_description": "Imagem de tamanho médio sem os metadados, utilizado quando visualizando um único arquivo e também pelo aprendizado de máquina", - "image_preview_quality_description": "Qualidade da pré-visualização, de 1-100. Maior é melhor, mas produz arquivos maiores e pode reduzir a velocidade do aplicativo. Definir um valor muito baixo pode afetar a qualidade do aprendizado de máquina.", - "image_preview_title": "Configurações de pré-visualização", - "image_progressive": "Progressivo", - "image_progressive_description": "Codifique imagens JPEG de forma progressiva para exibição com carregamento gradual. Isso não tem efeito em imagens WebP.", - "image_quality": "Qualidade", - "image_resolution": "Resolução", - "image_resolution_description": "Resoluções mais altas preservam mais detalhes, porém demoram mais para processar, tem um tamanho de arquivo maior e pode reduzir a velocidade do aplicativo.", - "image_settings": "Configurações de imagem", - "image_settings_description": "Gerenciar a qualidade e resolução das imagens geradas", - "image_thumbnail_description": "Miniatura sem metadados, utilizado quando visualizar um grupos de fotos, como por exemplo, a linha do tempo principal", - "image_thumbnail_quality_description": "Qualidade da miniatura, de 1 a 100. Maior é melhor, mas produz arquivos maiores e pode reduzir a velocidade do aplicativo.", - "image_thumbnail_title": "Configurações de Miniaturas", - "import_config_from_json_description": "Importar configuração do sistema enviando um arquivo JSON de configuração", - "job_concurrency": "{job} simultâneo", - "job_created": "Tarefa criada", - "job_not_concurrency_safe": "Esta tarefa não é compatível com simultaneidade.", - "job_settings": "Configurações de Tarefa", - "job_settings_description": "Gerenciar simultaneidade das tarefas", - "jobs_delayed": "{jobCount, plural, one {# atrasado} other {# atrasados}}", - "jobs_failed": "{jobCount, plural, one {# falhou} other {# falharam}}", - "jobs_over_time": "Tarefas ao longo do tempo", - "library_created": "Criado biblioteca: {library}", - "library_deleted": "Biblioteca excluída", - "library_details": "Detalhes da biblioteca", - "library_folder_description": "Escolha uma pasta para importar. Esta pasta e todas suas sub pastas serão analisadas em busca de imagens e vídeos.", - "library_remove_exclusion_pattern_prompt": "Tem certeza de que deseja remover esse padrão de exclusão?", - "library_remove_folder_prompt": "Tem certeza de que deseja remover esta pasta de importação?", - "library_scanning": "Verificação Periódica", - "library_scanning_description": "Configurar verificação periódica da biblioteca", - "library_scanning_enable_description": "Habilitar verificação periódica da biblioteca", - "library_settings": "Biblioteca Externa", - "library_settings_description": "Gerenciar configurações de biblioteca externa", - "library_tasks_description": "Verificar se há arquivos novos ou modificados nas bibliotecas externas", - "library_updated": "Biblioteca atualizada", - "library_watching_enable_description": "Observe bibliotecas externas para alterações de arquivos", - "library_watching_settings": "Observação de biblioteca [EXPERIMENTAL]", - "library_watching_settings_description": "Observe automaticamente os arquivos alterados", - "logging_enable_description": "Habilitar logs", - "logging_level_description": "Quando ativado, qual nível de log usar.", - "logging_settings": "Logs", - "machine_learning_availability_checks": "Verficações de disponibilidade", - "machine_learning_availability_checks_description": "Automaticamente detectar e preferir servidores de machine learning disponíveis", - "machine_learning_availability_checks_enabled": "Habilitar verificações de disponibilidade", - "machine_learning_availability_checks_interval": "Intervalo de verificação", - "machine_learning_availability_checks_interval_description": "Intervalo em milisegundos entre verificações de disponibilidade", - "machine_learning_availability_checks_timeout": "Tempo limite da solicitação", - "machine_learning_availability_checks_timeout_description": "Tempo limite em milisegundos para verificações de disponibilidade", - "machine_learning_clip_model": "Modelo CLIP", - "machine_learning_clip_model_description": "O nome de um modelo CLIP listado aqui. Lembre-se de executar novamente a tarefa de 'Pesquisa Inteligente' para todas as imagens após alterar o modelo.", - "machine_learning_duplicate_detection": "Detecção de duplicidade", - "machine_learning_duplicate_detection_enabled": "Habilitar detecção de duplicidade", - "machine_learning_duplicate_detection_enabled_description": "Se desativado, arquivos exatamente idênticos ainda serão desduplicados.", - "machine_learning_duplicate_detection_setting_description": "Usar CLIP integrado para encontrar prováveis duplicidades", - "machine_learning_enabled": "Habilitar aprendizado de máquina", - "machine_learning_enabled_description": "Se desativado, todos os recursos de AM serão desativados, independentemente das configurações abaixo.", - "machine_learning_facial_recognition": "Reconhecimento Facial", - "machine_learning_facial_recognition_description": "Detectar, reconhecer e agrupar rostos em imagens", - "machine_learning_facial_recognition_model": "Modelo de reconhecimento facial", - "machine_learning_facial_recognition_model_description": "Os modelos estão listados em ordem decrescente de tamanho. Modelos maiores são mais lentos e utilizam mais memória, mas produzem resultados melhores. Observe que ao alterar um modelo, você deve executar novamente a tarefa de Detecção de Rostos para todas as imagens.", - "machine_learning_facial_recognition_setting": "Ativar reconhecimento facial", - "machine_learning_facial_recognition_setting_description": "Se desativado, as imagens não serão codificadas para reconhecimento facial e não preencherão a seção Pessoas na página Explorar.", - "machine_learning_max_detection_distance": "Distância máxima de detecção", - "machine_learning_max_detection_distance_description": "Distância máxima entre duas imagens para considerá-las duplicadas, variando de 0,001 a 0,1. Valores mais altos detectarão mais duplicidades, mas poderão resultar em falsos positivos.", - "machine_learning_max_recognition_distance": "Distância máxima de reconhecimento", - "machine_learning_max_recognition_distance_description": "Distância máxima entre dois rostos para ser considerada a mesma pessoa, variando de 0 a 2. Valores menores evitam rotular dois rostos como a mesma pessoa, enquanto valores maiores evitam rotular o mesmo rosto como duas pessoas diferentes. Observe que é mais fácil mesclar duas pessoas do que dividir uma pessoa em duas, portanto tenha preferência por valores mais baixos quando possível.", - "machine_learning_min_detection_score": "Pontuação mínima de detecção", - "machine_learning_min_detection_score_description": "Pontuação mínima de confiança para um rosto ser detectado, de 0 a 1. Valores mais baixos detectam mais rostos, mas poderão resultar em falsos positivos.", - "machine_learning_min_recognized_faces": "Mínimo de rostos reconhecidos", - "machine_learning_min_recognized_faces_description": "O número mínimo de rostos reconhecidos para uma pessoa ser criada. Aumentar isso torna o Reconhecimento Facial mais preciso, ao custo de aumentar a chance de um rosto não ser atribuído a uma pessoa.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Usar aprendizado de máquina para reconhecer textos em imagens", - "machine_learning_ocr_enabled": "Habilitar OCR", - "machine_learning_ocr_enabled_description": "Se desabilitado, imagens não serão processadas pelo reconhecimento de texto.", - "machine_learning_ocr_max_resolution": "Resolução máxima", - "machine_learning_ocr_max_resolution_description": "Prévias acima dessa resolução serão redimensionadas preservando a proporção da imagem. Valores maiores são mais precisos, mas levam mais tempo para serem processados e usam mais memória.", - "machine_learning_ocr_min_detection_score": "Pontuação mínima para detecção", - "machine_learning_ocr_min_detection_score_description": "Pontuação mínima de confiança para o texto ser detectado de 0 a 1. Valores mais baixos detectarão mais texto mas podem resultar em falsos positivos.", - "machine_learning_ocr_min_recognition_score": "Pontuação mínima de reconhecimento", - "machine_learning_ocr_min_score_recognition_description": "Pontuação mínima de confiança para o texto ser detectado de 0 a 1. Valores mais baixos reconhecerão mais textos mas podem resultar em falsos positivos.", - "machine_learning_ocr_model": "Modelo OCR", - "machine_learning_ocr_model_description": "Os modelos do servidor são mais precisos do que os modelos do dispositivo móvel, mas demoram mais para processar e usam mais memória.", - "machine_learning_settings": "Configurações de aprendizado de máquina", - "machine_learning_settings_description": "Gerenciar recursos e configurações do aprendizado de máquina", - "machine_learning_smart_search": "Pesquisa Inteligente", - "machine_learning_smart_search_description": "Buscar imagens semanticamente usando integrações CLIP", - "machine_learning_smart_search_enabled": "Habilitar a Pesquisa Inteligente", - "machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para pesquisa inteligente.", - "machine_learning_url_description": "A URL do servidor de aprendizado de máquina. Se mais de uma URL for fornecida, elas serão tentadas, uma de cada vez e na ordem indicada, até que uma responda com sucesso. Servidores que não responderem serão ignorados temporariamente até voltarem a estar conectados.", - "maintenance_delete_backup": "Excluir Backup", - "maintenance_delete_backup_description": "Este arquivo será excluído de forma irreversível.", - "maintenance_delete_error": "Falha ao excluir o backup.", - "maintenance_restore_backup": "Restaurar Backup", - "maintenance_restore_backup_description": "O Immich será apagado e restaurado a partir do backup escolhido. Um backup será criado antes de continuar.", - "maintenance_restore_backup_different_version": "Este backup foi criado com uma versão diferente do Immich!", - "maintenance_restore_backup_unknown_version": "Não foi possível determinar a versão do backup.", - "maintenance_restore_database_backup": "Restaurar backup do banco de dados", - "maintenance_restore_database_backup_description": "Reverter para um estado anterior do banco de dados usando um arquivo de backup", - "maintenance_settings": "Manutenção", - "maintenance_settings_description": "Coloque o Immich em modo de manutenção.", - "maintenance_start": "Alternar para o modo de manutenção", - "maintenance_start_error": "Ocorreu um erro ao iniciar o modo de manutenção.", - "maintenance_upload_backup": "Carregar arquivo de backup do banco de dados", - "maintenance_upload_backup_error": "Não foi possível carregar o backup. É um arquivo .sql/.sql.gz?", - "manage_concurrency": "Gerenciar simultaneidade", - "manage_concurrency_description": "Acesse a página de tarefas para gerenciar a simultaneidade de tarefas", - "manage_log_settings": "Gerenciar configurações de log", - "map_dark_style": "Tema Escuro", - "map_enable_description": "Ativar recursos do mapa", - "map_gps_settings": "Mapa e Configurações de GPS", - "map_gps_settings_description": "Gerenciar Mapa e Configurações de GPS (Geocodificação Reversa)", - "map_implications": "O mapa depende de um serviço externo para funcionar (tiles.immich.cloud)", - "map_light_style": "Tema Claro", - "map_manage_reverse_geocoding_settings": "Gerenciar configurações de Geocodificação reversa", - "map_reverse_geocoding": "Geocodificação reversa", - "map_reverse_geocoding_enable_description": "Ativar geocodificação reversa", - "map_reverse_geocoding_settings": "Configurações de geocodificação reversa", - "map_settings": "Mapa", - "map_settings_description": "Gerenciar configurações do mapa", - "map_style_description": "URL para um tema de mapa style.json", - "memory_cleanup_job": "Limpeza de memórias", - "memory_generate_job": "Criação de memórias", - "metadata_extraction_job": "Extrair metadados", - "metadata_extraction_job_description": "Extraia informações dos metadados de cada arquivo, como GPS, rostos e resolução", - "metadata_faces_import_setting": "Ativar a importação de rostos", - "metadata_faces_import_setting_description": "Importar rostos a partir dos metadados EXIF da imagem e arquivos auxiliares", - "metadata_settings": "Configurações de Metadados", - "metadata_settings_description": "Gerenciar configurações de metadados", - "migration_job": "Migração", - "migration_job_description": "Migrar miniaturas de arquivos e rostos para a estrutura de pastas mais recente", - "nightly_tasks_cluster_faces_setting_description": "Fazer o reconhecimento facial dos novos rostos detectados", - "nightly_tasks_cluster_new_faces_setting": "Agrupar novos rostos", - "nightly_tasks_database_cleanup_setting": "Tarefas de limpeza do banco de dados", - "nightly_tasks_database_cleanup_setting_description": "Limpe dados velhos e expirados do banco de dados", - "nightly_tasks_generate_memories_setting": "Gerar memórias", - "nightly_tasks_generate_memories_setting_description": "Criar novas memórias a partir dos arquivos", - "nightly_tasks_missing_thumbnails_setting": "Gerar miniaturas em falta", - "nightly_tasks_missing_thumbnails_setting_description": "Adiciona na fila de geração de miniaturas as fotos ainda sem miniaturas", - "nightly_tasks_settings": "Configurações de Tarefas Diárias", - "nightly_tasks_settings_description": "Gerenciar tarefas diárias", - "nightly_tasks_start_time_setting": "Hora de início", - "nightly_tasks_start_time_setting_description": "A hora que o servidor começa a executar as tarefas diárias", - "nightly_tasks_sync_quota_usage_setting": "Utilização da quota de sincronização", - "nightly_tasks_sync_quota_usage_setting_description": "Atualizar quotas de armazenamento dos usuários, com base na utilização atual", - "no_paths_added": "Nenhum caminho adicionado", - "no_pattern_added": "Nenhum padrão adicionado", - "note_apply_storage_label_previous_assets": "Observação: Para aplicar o rótulo de armazenamento a arquivos enviados anteriormente, execute o", - "note_cannot_be_changed_later": "NOTA: Isto não pode ser alterado posteriormente!", - "notification_email_from_address": "E-mail de origem", - "notification_email_from_address_description": "Endereço de e-mail do remetente, por exemplo: \"Immich Photo Server \". Tenha certeza de ter permissão para enviar e-mails a partir do endereço selecionado.", - "notification_email_host_description": "Servidor de e-mail (por exemplo, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorar erros de certificado", - "notification_email_ignore_certificate_errors_description": "Ignorar erros de validação de certificado TLS (não recomendado)", - "notification_email_password_description": "Senha a ser usada ao autenticar no servidor de e-mail", - "notification_email_port_description": "Porta do servidor de e-mail (por exemplo, 25, 465 ou 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Usar SMTPS (SMTP por TLS)", - "notification_email_sent_test_email_button": "Envie e-mail de teste e salve", - "notification_email_setting_description": "Configurações para envio de notificações por e-mail", - "notification_email_test_email": "Enviar e-mail de teste", - "notification_email_test_email_failed": "Falha ao enviar e-mail de teste. Verifique seus valores", - "notification_email_test_email_sent": "Um e-mail de teste foi enviado para {email}. Por favor, verifique sua caixa de entrada.", - "notification_email_username_description": "Nome de usuário que será usado para autenticar com o servidor de e-mail", - "notification_enable_email_notifications": "Habilitar notificações por e-mail", - "notification_settings": "Configurações de notificação", - "notification_settings_description": "Gerenciar configurações de notificação, incluindo e-mail", - "oauth_auto_launch": "Inicialização automática", - "oauth_auto_launch_description": "Inicie o fluxo de login do OAuth automaticamente ao navegar até a página de login", - "oauth_auto_register": "Registro automático", - "oauth_auto_register_description": "Registre automaticamente novos usuários após fazer login com OAuth", - "oauth_button_text": "Botão de texto", - "oauth_client_secret_description": "Obrigatório para cliente confidencial ou quando o PKCE (Proof Key for Code Exchange) não é suportado para cliente público.", - "oauth_enable_description": "Faça login com OAuth", - "oauth_mobile_redirect_uri": "URI de redirecionamento móvel", - "oauth_mobile_redirect_uri_override": "Substituição de URI de redirecionamento móvel", - "oauth_mobile_redirect_uri_override_description": "Ative quando o provedor do OAuth não suportar uma URI de aplicativo, por exemplo ''{callback}''", - "oauth_role_claim": "Declaração de função", - "oauth_role_claim_description": "Dá permissões de administrador baseado no valor desta declaração. A declaração pode conter os valores 'user' ou 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Gerenciar configurações de login do OAuth", - "oauth_settings_more_details": "Para mais detalhes sobre este recurso, consulte a documentação.", - "oauth_storage_label_claim": "Declaração do rótulo de armazenamento", - "oauth_storage_label_claim_description": "Definir automaticamente o rótulo de armazenamento do usuário com o valor desta declaração.", - "oauth_storage_quota_claim": "Declaração de cota de armazenamento", - "oauth_storage_quota_claim_description": "Definir automaticamente a cota de armazenamento do usuário com o valor desta declaração.", - "oauth_storage_quota_default": "Cota de armazenamento padrão (GiB)", - "oauth_storage_quota_default_description": "Cota em GiB que será usada caso esta declaração não seja fornecida.", - "oauth_timeout": "Tempo Limite de Requisição", - "oauth_timeout_description": "Tempo limite para requisições, em milissegundos", - "ocr_job_description": "Usa machine learning para reconhecer texto em imagens", - "password_enable_description": "Login com e-mail e senha", - "password_settings": "Senha de acesso", - "password_settings_description": "Gerenciar configurações de login e senha", - "paths_validated_successfully": "Todos os caminhos validados com sucesso", - "person_cleanup_job": "Limpeza de pessoas", - "queue_details": "Detalhes da Fila", - "queues": "Fila de Tarefas", - "queues_page_description": "Página de administração das filas de tarefas", - "quota_size_gib": "Tamanho da cota (GiB)", - "refreshing_all_libraries": "Atualizando todas as bibliotecas", - "registration": "Registro de Administrador", - "registration_description": "Como você é o primeiro usuário no sistema, será designado como o Administrador e será responsável pelas tarefas administrativas. Você também poderá criar usuários adicionais.", - "remove_failed_jobs": "Remover tarefas que falharam", - "require_password_change_on_login": "Exigir que o usuário altere a senha no primeiro login", - "reset_settings_to_default": "Redefinir as configurações para o padrão", - "reset_settings_to_recent_saved": "Redefinir as configurações para as configurações salvas recentemente", - "scanning_library": "Analisando a biblioteca", - "search_jobs": "Pesquisar tarefas…", - "send_welcome_email": "Enviar e-mail de boas-vindas", - "server_external_domain_settings": "Domínio externo", - "server_external_domain_settings_description": "Domínio para links públicos compartilhados, incluindo http(s)://", - "server_public_users": "Usuários públicos", - "server_public_users_description": "Todos os usuários (nome e e-mail) serão exibidos na lista de adicionar usuários em álbuns compartilhados. Quando desativado, essa lista de usuários só será visível aos administradores.", - "server_settings": "Configurações do servidor", - "server_settings_description": "Gerenciar configurações do servidor", - "server_stats_page_description": "Página de estatística do servidor Admin", - "server_welcome_message": "Mensagem de boas-vindas", - "server_welcome_message_description": "Uma mensagem exibida na página de login.", - "settings_page_description": "Página de configurações do Admin", - "sidecar_job": "Metadados secundários", - "sidecar_job_description": "Descubra ou sincronize metadados secundários do sistema de arquivos", - "slideshow_duration_description": "Tempo em segundos para exibir cada imagem", - "smart_search_job_description": "Execute aprendizado de máquina em arquivos para oferecer suporte à pesquisa inteligente", - "storage_template_date_time_description": "A data e hora da criação do arquivo é usado para a informações de data e hora", - "storage_template_date_time_sample": "Exemplo {date}", - "storage_template_enable_description": "Habilitar mecanismo de modelo de armazenamento", - "storage_template_hash_verification_enabled": "Verificação de hash ativada", - "storage_template_hash_verification_enabled_description": "Ativa a verificação de hash, não desative a menos que você tenha certeza das implicações", - "storage_template_migration": "Migração de modelo de armazenamento", - "storage_template_migration_description": "Aplicar o {template} atual aos arquivos enviados anteriormente", - "storage_template_migration_info": "O modelo altera todas extensões para minúsculo. As mudanças no modelo serão aplicadas apenas em novos arquivos; para aplicar o modelo aos arquivos enviados anteriormente, execute o {job}.", - "storage_template_migration_job": "Tarefa de Migração de Modelo de Armazenamento", - "storage_template_more_details": "Para mais detalhes sobre este recurso, consulte o Modelo de Armazenamento e suas implicações", - "storage_template_onboarding_description_v2": "Ao ser ativado, este recurso irá organizar automaticamente os arquivos com base em um modelo definido pelo usuário. Para mais informações, consulte a documentação.", - "storage_template_path_length": "Limite aproximado de comprimento do caminho: {length, number}/{limit, number}", - "storage_template_settings": "Modelo de Armazenamento", - "storage_template_settings_description": "Gerencie a estrutura de pasta e o nome do arquivo enviado", - "storage_template_user_label": "{label} é o Rótulo de Armazenamento do usuário", - "system_settings": "Configurações do Sistema", - "tag_cleanup_job": "Limpeza de marcadores", - "template_email_available_tags": "Você pode usar as seguintes variáveis no modelo: {tags}", - "template_email_if_empty": "Se o modelo estiver em branco, o modelo de e-mail padrão será usado.", - "template_email_invite_album": "Modelo do e-mail de convite para álbum", - "template_email_preview": "Pré visualização", - "template_email_settings": "Modelos de e-mail", - "template_email_update_album": "Modelo do e-mail de atualização do álbum", - "template_email_welcome": "Modelo do e-mail de boas vindas", - "template_settings": "Modelos de notificação", - "template_settings_description": "Gerenciar modelos personalizados para notificações", - "theme_custom_css_settings": "CSS customizado", - "theme_custom_css_settings_description": "Folhas de estilo em cascata permitem que o design do Immich seja personalizado.", - "theme_settings": "Configurações de tema", - "theme_settings_description": "Gerencie a personalização da interface web do Immich", - "thumbnail_generation_job": "Gerar Miniaturas", - "thumbnail_generation_job_description": "Gere miniaturas grandes, pequenas e desfocadas para cada arquivo, bem como miniaturas para cada pessoa", - "transcoding_acceleration_api": "API de aceleração", - "transcoding_acceleration_api_description": "A API que irá interagir com o seu dispositivo para acelerar a transcodificação. Esta configuração é a 'melhor opção': ela retornará à transcodificação de software em caso de falha. O VP9 pode não funcionar dependendo do seu hardware.", - "transcoding_acceleration_nvenc": "NVENC (requer GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (requer CPU Intel de 7ª geração ou posterior)", - "transcoding_acceleration_rkmpp": "RKMPP (apenas em SOCs Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codecs de áudio aceitos", - "transcoding_accepted_audio_codecs_description": "Selecione quais codecs de áudio não precisam ser transcodificados. Usado apenas para determinadas políticas de transcodificação.", - "transcoding_accepted_containers": "Containers aceitos", - "transcoding_accepted_containers_description": "Selecione quais formatos de contêiner não precisam ser remixados para MP4. Usado apenas para determinadas políticas de transcodificação.", - "transcoding_accepted_video_codecs": "Codecs de vídeo aceitos", - "transcoding_accepted_video_codecs_description": "Selecione quais codecs de vídeo não precisam ser transcodificados. Usado apenas para determinadas políticas de transcodificação.", - "transcoding_advanced_options_description": "Opções que a maioria dos usuários não deveria precisar alterar", - "transcoding_audio_codec": "Codec de áudio", - "transcoding_audio_codec_description": "Opus é a opção de mais alta qualidade, mas tem menor compatibilidade com dispositivos ou softwares antigos.", - "transcoding_bitrate_description": "Vídeos com taxa de bits superior à máxima ou que não estão em um formato aceito", - "transcoding_codecs_learn_more": "Para aprender mais sobre a terminologia utilizada aqui, consulte a documentação do FFmpeg para o codec H.264, o codec HEVC e o codec VP9.", - "transcoding_constant_quality_mode": "Modo de qualidade constante", - "transcoding_constant_quality_mode_description": "ICQ é melhor que CQP, mas alguns dispositivos de aceleração de hardware não suportam este modo. Definir esta opção dará preferência ao modo especificado ao usar codificação baseada em qualidade. Ignorado pelo NVENC porque não suporta ICQ.", - "transcoding_constant_rate_factor": "Fator de taxa constante (-crf)", - "transcoding_constant_rate_factor_description": "Nível de qualidade do vídeo. Os valores típicos são 23 para H.264, 28 para HEVC, 31 para VP9 e 35 para AV1. Menor é melhor, mas produz arquivos maiores.", - "transcoding_disabled_description": "Não transcodifique nenhum vídeo, pois pode interromper a reprodução em alguns clientes", - "transcoding_encoding_options": "Opções de codificação de vídeo", - "transcoding_encoding_options_description": "Defina codecs, resolução, qualidade e outras opções para vídeos codificados", - "transcoding_hardware_acceleration": "Aceleração de hardware", - "transcoding_hardware_acceleration_description": "Experimental: mais rápido, mas talvez produza uma qualidade inferior com a mesma taxa de bits", - "transcoding_hardware_decoding": "Decodificação de hardware", - "transcoding_hardware_decoding_setting_description": "Habilita a aceleração de ponta a ponta, em vez de apenas acelerar a codificação. Pode não funcionar em todos os vídeos.", - "transcoding_max_b_frames": "Máximo de quadros B", - "transcoding_max_b_frames_description": "Valores mais altos melhoram a eficiência da compactação, mas retardam a codificação. Pode não ser compatível com aceleração de hardware em dispositivos mais antigos. 0 desativa os quadros B, enquanto -1 define esse valor automaticamente.", - "transcoding_max_bitrate": "Taxa de bits máxima", - "transcoding_max_bitrate_description": "Definir uma taxa de bits máxima pode tornar os tamanhos dos arquivos mais previsíveis, ao custo de pior qualidade. Em 720p, os valores típicos são 2.600 kbit/s para VP9 ou HEVC, ou 4.500 kbit/s para H.264. Desativado se definido como 0. Quando uma unidade não for especificada, k (de kbit/s) será utilizado, ou seja, 5000, 5000k e 5M (de Mbit/s) são equivalentes.", - "transcoding_max_keyframe_interval": "Intervalo máximo de quadro-chave", - "transcoding_max_keyframe_interval_description": "Define a distância máxima do quadro entre os quadros-chave. Valores mais baixos pioram a eficiência da compressão, mas melhoram os tempos de busca e podem melhorar a qualidade em cenas com movimento rápido. 0 define esse valor automaticamente.", - "transcoding_optimal_description": "Vídeos com resolução superior à desejada ou em formato não aceito", - "transcoding_policy": "Política de Transcodificação", - "transcoding_policy_description": "Defina quando um vídeo será transcodificado", - "transcoding_preferred_hardware_device": "Dispositivo de hardware preferido", - "transcoding_preferred_hardware_device_description": "Aplica-se apenas a VAAPI e QSV. Define o nó dri usado para transcodificação de hardware.", - "transcoding_preset_preset": "Predefinido (-preset)", - "transcoding_preset_preset_description": "Velocidade de compressão. As opções mais lentas produzem arquivos menores e aumentam a qualidade. VP9 ignora as velocidades acima de 'mais rápida'.", - "transcoding_reference_frames": "Quadros de referência", - "transcoding_reference_frames_description": "O número de quadros a serem referenciados ao compactar um determinado quadro. Valores mais altos melhoram a eficiência da compactação, mas retardam a codificação. 0 define esse valor automaticamente.", - "transcoding_required_description": "Somente vídeos que não estejam em um formato aceito", - "transcoding_settings": "Configurações de transcodificação de vídeo", - "transcoding_settings_description": "Defina quais vídeos transcodificar e como serão processados", - "transcoding_target_resolution": "Resolução desejada", - "transcoding_target_resolution_description": "Resoluções mais altas podem preservar mais detalhes, mas demoram mais para codificar, têm tamanhos de arquivo maiores e podem reduzir a capacidade de resposta do aplicativo.", - "transcoding_temporal_aq": "Quantização com adaptação temporal", - "transcoding_temporal_aq_description": "Aplica-se apenas ao NVENC. A Quantização com adaptação temporal aumenta a qualidade de cenas com alto detalhe e pouco movimento. Pode não ser compatível com dispositivos mais antigos.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Valores mais altos levam a uma codificação mais rápida, mas deixam menos espaço para o servidor processar outras tarefas enquanto estiver ativo. Este valor não deve ser superior ao número de núcleos da CPU. Maximiza a utilização se definido como 0.", - "transcoding_tone_mapping": "Mapeamento de tons", - "transcoding_tone_mapping_description": "Tenta preservar a aparência dos vídeos HDR quando convertidos para SDR. Cada algoritmo faz compensações diferentes em termos de cor, detalhes e brilho. Hable preserva os detalhes, Mobius preserva as cores e Reinhard preserva o brilho.", - "transcoding_transcode_policy": "Política de transcodificação", - "transcoding_transcode_policy_description": "Política para quando um vídeo deve ser transcodificado. Os vídeos HDR sempre serão transcodificados (exceto se a transcodificação estiver desativada).", - "transcoding_two_pass_encoding": "Codificação de duas passagens", - "transcoding_two_pass_encoding_setting_description": "Transcodifique em duas passagens para produzir vídeos melhor codificados. Quando a taxa de bits máxima está habilitada (necessária para funcionar com H.264 e HEVC), este modo usa um intervalo de taxa de bits baseado na taxa de bits máxima e ignora o CRF. Para VP9, o CRF pode ser usado se a taxa de bits máxima estiver desabilitada.", - "transcoding_video_codec": "Codec de vídeo", - "transcoding_video_codec_description": "O VP9 tem alta eficiência e compatibilidade com a web, mas leva mais tempo para transcodificar. HEVC tem desempenho semelhante, mas tem menor compatibilidade com a web. H.264 é amplamente compatível e rápido de transcodificar, mas produz arquivos muito maiores. AV1 é o codec mais eficiente, mas não possui suporte em dispositivos mais antigos.", - "trash_enabled_description": "Ativar recursos da Lixeira", - "trash_number_of_days": "Número de dias", - "trash_number_of_days_description": "Número de dias para manter os arquivos na lixeira antes de deletar permanentemente", - "trash_settings": "Configurações da Lixeira", - "trash_settings_description": "Gerenciar configurações da lixeira", - "unlink_all_oauth_accounts": "Desvincular todas as contas OAuth", - "unlink_all_oauth_accounts_description": "Lembre-se de desvincular todas as contas OAuth antes de migrar para um novo provedor.", - "unlink_all_oauth_accounts_prompt": "Tem certeza que deseja desvincular todas as contas OAuth? Isto vai redefinir o ID OAuth de todos os usuário e não pode ser desfeito.", - "user_cleanup_job": "Limpeza de usuários", - "user_delete_delay": "A conta e os arquivos de {user} serão programados para exclusão permanente em {delay, plural, one {# dia} other {# dias}}.", - "user_delete_delay_settings": "Período de carência", - "user_delete_delay_settings_description": "Número de dias após a remoção para excluir permanentemente a conta e os arquivos de um usuário. A tarefa de exclusão de usuário é executada à meia-noite para verificar usuários que estão prontos para exclusão. As alterações nesta configuração serão avaliadas na próxima execução.", - "user_delete_immediately": "A conta e os arquivos de {user} serão programados para exclusão permanente imediata.", - "user_delete_immediately_checkbox": "Adicionar o usuário e seus arquivos na fila para serem deletados imediatamente", - "user_details": "Detalhes do Usuário", - "user_management": "Gerenciamento de usuários", - "user_password_has_been_reset": "A senha do usuário foi redefinida:", - "user_password_reset_description": "Forneça a senha temporária ao usuário e informe que ele precisará alterar a senha no próximo login.", - "user_restore_description": "A conta de {user} será restaurada.", - "user_restore_scheduled_removal": "Restaurar usuário - A remoção está agendada para o dia {date, date, long}", - "user_settings": "Configurações do Usuário", - "user_settings_description": "Gerenciar configurações do usuário", - "user_successfully_removed": "Usuário {email} foi removido com sucesso.", - "users_page_description": "Página de usuários Admin", - "version_check_enabled_description": "Ativa a verificação de versão", - "version_check_implications": "A verificação de versão depende de uma comunicação periódica com github.com", - "version_check_settings": "Verificação de versão", - "version_check_settings_description": "Ativar/desativar a notificação de nova versão", - "video_conversion_job": "Transcodificar vídeos", - "video_conversion_job_description": "Transcodifique vídeos para maior compatibilidade com navegadores e dispositivos" - }, - "admin_email": "E-mail do administrador", - "admin_password": "Senha do administrador", - "administration": "Administração", - "advanced": "Avançado", - "advanced_settings_clear_image_cache": "Limpar cache de imagens", - "advanced_settings_clear_image_cache_error": "Falha ao limpar o cache de imagens", - "advanced_settings_clear_image_cache_success": "Limpeza concluída com sucesso {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Use esta opção para filtrar mídias durante a sincronização com base em critérios alternativos. Tente esta opção somente se o aplicativo estiver com problemas para detectar todos os álbuns.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizar filtro alternativo de sincronização de álbum de dispositivo", - "advanced_settings_log_level_title": "Nível de log: {level}", - "advanced_settings_prefer_remote_subtitle": "Alguns dispositivos são extremamente lentos para carregar as miniaturas locais. Ative esta opção para preferir imagens do servidor.", - "advanced_settings_prefer_remote_title": "Preferir imagens do servidor", - "advanced_settings_proxy_headers_subtitle": "Defina os cabeçalhos do proxy que o Immich deve enviar em todas comunicações com a rede", - "advanced_settings_proxy_headers_title": "Cabeçalhos de proxy customizados [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Ativar o modo de apenas visualização dos arquivos. As outras ações, como: selecionar várias imagens, compartilhar, transmitir ou deletar serão desabilitadas. Ative ou Desative este modo clicando na foto do usuário na tela principal", - "advanced_settings_readonly_mode_title": "Modo de leitura apenas", - "advanced_settings_self_signed_ssl_subtitle": "Ignora a verificação do certificado SSL do servidor. Obrigatório para certificados auto assinados.", - "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL auto-assinados [EXPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Excluir ou restaurar os arquivos automaticamente neste dispositivo quando essas ações forem realizada na interface web", - "advanced_settings_sync_remote_deletions_title": "Sincronizar exclusões remotas [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Configurações avançadas do usuário", - "advanced_settings_troubleshooting_subtitle": "Ativar recursos adicionais para solução de problemas", - "advanced_settings_troubleshooting_title": "Solução de problemas", - "age_months": "Idade {months, plural, one {# mês} other {# meses}}", - "age_year_months": "Idade 1 ano e {months, plural, one {# mês} other {# meses}}", - "age_years": "{years, plural, other {Idade #}}", - "album": "Álbum", - "album_added": "Álbum adicionado", - "album_added_notification_setting_description": "Receba uma notificação por e-mail quando você for adicionado a um álbum compartilhado", - "album_cover_updated": "Capa do álbum atualizada", - "album_delete_confirmation": "Tem certeza de que deseja excluir o álbum {album}?", - "album_delete_confirmation_description": "Se este álbum é compartilhado, os outros usuários não conseguiram mais acessá-lo.", - "album_deleted": "Álbum deletado", - "album_info_card_backup_album_excluded": "EXCLUÍDO", - "album_info_card_backup_album_included": "INCLUÍDO", - "album_info_updated": "Informações do álbum atualizadas", - "album_leave": "Sair do álbum?", - "album_leave_confirmation": "Tem certeza de que deseja sair de {album}?", - "album_name": "Nome do álbum", - "album_options": "Opções de álbum", - "album_remove_user": "Remover usuário?", - "album_remove_user_confirmation": "Tem certeza de que deseja remover {user}?", - "album_search_not_found": "Não há álbum que corresponda à sua pesquisa", - "album_selected": "Álbum selecionado", - "album_share_no_users": "Parece que você já compartilhou este álbum com todos os usuários ou não há nenhum usuário para compartilhar.", - "album_summary": "Resumo do álbum", - "album_updated": "Álbum atualizado", - "album_updated_setting_description": "Receba uma notificação por e-mail quando um álbum compartilhado tiver novos recursos", - "album_upload_assets": "Enviar arquivos do seu computador e adicionar ao álbum", - "album_user_left": "Saiu do álbum {album}", - "album_user_removed": "Usuário {user} foi removido", - "album_viewer_appbar_delete_confirm": "Tem certeza de que deseja excluir este álbum da sua conta?", - "album_viewer_appbar_share_err_delete": "Falha ao excluir álbum", - "album_viewer_appbar_share_err_leave": "Falha ao sair do álbum", - "album_viewer_appbar_share_err_remove": "Há problemas ao remover recursos do álbum", - "album_viewer_appbar_share_err_title": "Falha ao alterar o título do álbum", - "album_viewer_appbar_share_leave": "Sair do álbum", - "album_viewer_appbar_share_to": "Compartilhar", - "album_viewer_page_share_add_users": "Adicionar usuários", - "album_with_link_access": "Permitir que qualquer pessoa com o link veja as fotos e as pessoas neste álbum.", - "albums": "Álbuns", - "albums_count": "{count, plural, one {{count, number} Álbum} other {{count, number} Álbuns}}", - "albums_default_sort_order": "Ordem padrão do álbum", - "albums_default_sort_order_description": "Ordem padrão dos arquivos ao criar novos álbuns.", - "albums_feature_description": "Coleções de arquivos que podem ser compartilhados com outros usuários.", - "albums_on_device_count": "Álbuns no dispositivo ({count})", - "albums_selected": "{count, plural, one {# álbum selecionado} other {# álbuns selecionados}}", - "all": "Todos", - "all_albums": "Todos os álbuns", - "all_people": "Todas as pessoas", - "all_photos": "Todas as fotos", - "all_videos": "Todos os vídeos", - "allow_dark_mode": "Permitir modo escuro", - "allow_edits": "Permitir edições", - "allow_public_user_to_download": "Permitir que usuários públicos baixem os arquivos", - "allow_public_user_to_upload": "Permitir que usuários públicos enviem novos arquivos", - "allowed": "Permitido", - "alt_text_qr_code": "Imagem do código QR", - "always_keep": "Manter sempre", - "always_keep_photos_hint": "Liberar espaço manterá todas as fotos neste dispositivo.", - "always_keep_videos_hint": "Liberar espaço manterá todos os vídeos neste dispositivo.", - "anti_clockwise": "Anti-horário", - "api_key": "Chave de API", - "api_key_description": "Este valor será mostrado apenas uma vez. Por favor, certifique-se de copiá-lo antes de fechar a janela.", - "api_key_empty": "O nome da sua chave de API não deve estar vazio", - "api_keys": "Chaves de API", - "app_architecture_variant": "Variante (Arquitetura)", - "app_bar_signout_dialog_content": "Tem certeza de que deseja sair?", - "app_bar_signout_dialog_ok": "Sim", - "app_bar_signout_dialog_title": "Sair", - "app_download_links": "Links para baixar o aplicativo", - "app_settings": "Configurações do Aplicativo", - "app_stores": "Loja de Aplicativos", - "app_update_available": "Uma atualização para o aplicativo está disponível", - "appears_in": "Aparece em", - "apply_count": "Aplicar ({count, number})", - "archive": "Arquivar", - "archive_action_prompt": "{count} mídias arquivadas", - "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", - "archive_page_no_archived_assets": "Nenhum arquivo encontrado", - "archive_page_title": "Arquivados ({count})", - "archive_size": "Tamanho do arquivamento", - "archive_size_description": "Configure o tamanho do arquivo para baixar (em GiB)", - "archived": "Arquivado", - "archived_count": "{count, plural, one {# Arquivado} other {# Arquivados}}", - "are_these_the_same_person": "Essas pessoas são a mesma pessoa?", - "are_you_sure_to_do_this": "Tem certeza de que deseja fazer isso?", - "array_field_not_fully_supported": "Campos array exigem edição manual do JSON", - "asset_action_delete_err_read_only": "Não é possível excluir arquivo só leitura, ignorando", - "asset_action_share_err_offline": "Não foi possível obter os arquivos indisponíveis, ignorando", - "asset_added_to_album": "Adicionado ao álbum", - "asset_adding_to_album": "Adicionando ao álbum…", - "asset_created": "Arquivo foi criado", - "asset_description_updated": "A descrição do arquivo foi atualizada", - "asset_filename_is_offline": "O arquivo {filename} não está disponível", - "asset_has_unassigned_faces": "O arquivo tem rostos sem nomes", - "asset_hashing": "Processando…", - "asset_list_group_by_sub_title": "Agrupar por", - "asset_list_layout_settings_dynamic_layout_title": "Layout dinâmico", - "asset_list_layout_settings_group_automatically": "Automático", - "asset_list_layout_settings_group_by": "Agrupar arquivos por", - "asset_list_layout_settings_group_by_month_day": "Mês + dia", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Configurações de layout da grade de fotos", - "asset_list_settings_title": "Grade de Fotos", - "asset_not_found_on_device_android": "Arquivo não encontrado no dispositivo", - "asset_not_found_on_device_ios": "Arquivo não encontrado no dispositivo. Se estiver usando o iCloud, o arquivo pode estar inacessível devido a um arquivo corrompido armazenado no iCloud", - "asset_not_found_on_icloud": "Arquivo não encontrado no iCloud. o arquivo pode estar inacessível devido a um arquivo corrompido armazenado no iCloud", - "asset_offline": "Arquivo indisponível", - "asset_offline_description": "Este arquivo externo não está mais disponível. Contate seu administrador do Immich para obter ajuda.", - "asset_restored_successfully": "Arquivo restaurado", - "asset_skipped": "Ignorado", - "asset_skipped_in_trash": "Na lixeira", - "asset_trashed": "Arquivo enviado para a lixeira", - "asset_troubleshoot": "Diagnóstico do arquivo", - "asset_uploaded": "Enviado", - "asset_uploading": "Enviando…", - "asset_viewer_settings_subtitle": "Gerenciar as configurações do visualizador da galeria", - "asset_viewer_settings_title": "Visualizador de Mídia", - "assets": "Arquivos", - "assets_added_count": "{count, plural, one {# arquivo adicionado} other {# arquivos adicionados}}", - "assets_added_to_album_count": "{count, plural, one {# arquivo adicionado} other {# arquivos adicionados}} ao álbum", - "assets_added_to_albums_count": "{assetTotal, plural, one {# Arquivo adicionado} other {# Arquivos adicionados}} {albumTotal, plural, one {# ao álbum} other {# aos álbuns}}", - "assets_cannot_be_added_to_album_count": "Não foi possível adicionar {count, plural, one {o arquivo} other {os arquivos}} ao álbum", - "assets_cannot_be_added_to_albums": "{count, plural, one {Arquivo não pode ser adicionado} other {Arquivos não podem ser adicionados}} a nenhum álbum", - "assets_count": "{count, plural, one {# arquivo} other {# arquivos}}", - "assets_deleted_permanently": "{count} arquivo(s) deletado(s) permanentemente", - "assets_deleted_permanently_from_server": "{count} arquivo(s) deletado(s) permanentemente do servidor Immich", - "assets_downloaded_failed": "{count, plural, one {# arquivo transferido - {error} arquivo falhou} other {# arquivos transferidos - {error} arquivos falharam}}", - "assets_downloaded_successfully": "{count, plural, one {# arquivo transferido com sucesso} other {# arquivos transferidos com sucesso}}", - "assets_moved_to_trash_count": "{count, plural, one {# arquivo movido} other {# arquivos movidos}} para a lixeira", - "assets_permanently_deleted_count": "{count, plural, one {# arquivo excluído permanentemente} other {# arquivos excluídos permanentemente}}", - "assets_removed_count": "{count, plural, one {# arquivo removido} other {# arquivos removidos}}", - "assets_removed_permanently_from_device": "{count} arquivo(s) removido(s) permanentemente do seu dispositivo", - "assets_restore_confirmation": "Tem certeza de que deseja restaurar todos os seus arquivos na lixeira? Esta ação não pode ser desfeita! Nota: Arquivos externos não podem ser restaurados desta maneira.", - "assets_restored_count": "{count, plural, one {# arquivo restaurado} other {# arquivos restaurados}}", - "assets_restored_successfully": "{count} arquivo(s) restaurado(s)", - "assets_trashed": "{count} arquivo enviado para a lixeira", - "assets_trashed_count": "{count, plural, one {# arquivo movido para a lixeira} other {# arquivos movidos para a lixeira}}", - "assets_trashed_from_server": "{count} arquivos foram enviados para a lixeira", - "assets_were_part_of_album_count": "{count, plural, one {O arquivo já faz} other {Os arquivos já fazem}} parte do álbum", - "assets_were_part_of_albums_count": "{count, plural, one {Arquivo já existe} other {Arquivos já existem}} nos álbuns", - "authorized_devices": "Dispositivos Autorizados", - "automatic_endpoint_switching_subtitle": "Conecte-se localmente quando estiver em uma rede uma Wi-Fi específica e use conexões alternativas em outras redes", - "automatic_endpoint_switching_title": "Troca automática de URL", - "autoplay_slideshow": "Apresentação de slides automática", - "back": "Voltar", - "back_close_deselect": "Voltar, fechar ou desmarcar", - "background_backup_running_error": "Não é possível iniciar o backup manual agora pois o backup em segundo plano já está sendo executado", - "background_location_permission": "Permissão de localização em segundo plano", - "background_location_permission_content": "Para que seja possível trocar o endereço quando estiver executando em segundo plano, o Immich deve *sempre* ter a permissão de localização precisa para que o aplicativo consiga ler o nome da rede Wi-Fi", - "background_options": "Opções de segundo plano", - "backup": "Backup", - "backup_album_selection_page_albums_device": "Álbuns no dispositivo ({count})", - "backup_album_selection_page_albums_tap": "Toque para incluir, toque duas vezes para excluir", - "backup_album_selection_page_assets_scatter": "Os arquivos podem se espalhar por vários álbuns. Assim, os álbuns podem ser incluídos ou excluídos durante o processo de backup.", - "backup_album_selection_page_select_albums": "Selecionar álbuns", - "backup_album_selection_page_selection_info": "Informações da Seleção", - "backup_album_selection_page_total_assets": "Total de arquivos únicos", - "backup_albums_sync": "Sincronização de álbuns", - "backup_all": "Todos", - "backup_background_service_backup_failed_message": "Falha ao fazer backup. Tentando novamente…", - "backup_background_service_complete_notification": "Backup dos arquivos concluído", - "backup_background_service_connection_failed_message": "Falha na conexão com o servidor. Tentando novamente…", - "backup_background_service_current_upload_notification": "Enviando {filename}", - "backup_background_service_default_notification": "Verificando se há novos arquivos…", - "backup_background_service_error_title": "Erro no backup", - "backup_background_service_in_progress_notification": "Fazendo backup de seus arquivos…", - "backup_background_service_upload_failure_notification": "Falha ao enviar {filename}", - "backup_controller_page_albums": "Backup de álbuns", - "backup_controller_page_background_app_refresh_disabled_content": "Para utilizar o backup em segundo plano, ative a atualização da aplicação em segundo plano em Configurações > Geral > Atualização em 2º plano.", - "backup_controller_page_background_app_refresh_disabled_title": "Atualização em 2º plano desativada", - "backup_controller_page_background_app_refresh_enable_button_text": "Ir para as configurações", - "backup_controller_page_background_battery_info_link": "Mostre-me como", - "backup_controller_page_background_battery_info_message": "Para uma melhor experiência de backup em segundo plano, desative todas as otimizações de bateria que restrinjam a atividade em segundo plano do Immich.\n\nComo isso é específico por dispositivo, consulte as informações de como fazer isso com o fabricante do seu dispositivo.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Otimizações de bateria", - "backup_controller_page_background_charging": "Apenas enquanto carrega a bateria", - "backup_controller_page_background_configure_error": "Falha ao configurar o serviço em segundo plano", - "backup_controller_page_background_delay": "Adiar backup de novos arquivos: {duration}", - "backup_controller_page_background_description": "Ative o serviço em segundo plano para fazer backup automático de novos arquivos sem precisar abrir o aplicativo", - "backup_controller_page_background_is_off": "O backup automático em segundo plano está desativado", - "backup_controller_page_background_is_on": "O backup automático em segundo plano está ativado", - "backup_controller_page_background_turn_off": "Desativar o serviço em segundo plano", - "backup_controller_page_background_turn_on": "Ativar o serviço em segundo plano", - "backup_controller_page_background_wifi": "Apenas no Wi-Fi", - "backup_controller_page_backup": "Backup", - "backup_controller_page_backup_selected": "Selecionados: ", - "backup_controller_page_backup_sub": "Total de mídias com backup", - "backup_controller_page_created": "Data: {date}", - "backup_controller_page_desc_backup": "Ative para fazer backup automático dos novos arquivos ao abrir este aplicativo.", - "backup_controller_page_excluded": "Ignorados: ", - "backup_controller_page_failed": "Falhou ({count})", - "backup_controller_page_filename": "Arquivo: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informações do backup", - "backup_controller_page_none_selected": "Nenhum álbum selecionado", - "backup_controller_page_remainder": "Restante", - "backup_controller_page_remainder_sub": "Mídias nos álbuns selecionados que ainda não tem backup", - "backup_controller_page_server_storage": "Armazenamento do servidor", - "backup_controller_page_start_backup": "Iniciar backup manual", - "backup_controller_page_status_off": "O backup automático está desativado", - "backup_controller_page_status_on": "O backup automático está ativado", - "backup_controller_page_storage_format": "{used} de {total} usados", - "backup_controller_page_to_backup": "Escolha os álbuns para fazer backup", - "backup_controller_page_total_sub": "Total de mídias nos álbuns selecionados", - "backup_controller_page_turn_off": "Desativar backup automático", - "backup_controller_page_turn_on": "Ativar backup automático", - "backup_controller_page_uploading_file_info": "Informações do arquivo", - "backup_err_only_album": "Não é possível remover o único álbum", - "backup_error_sync_failed": "A sincronização falhou. Não foi possível processar o backup.", - "backup_info_card_assets": "arquivos", - "backup_manual_cancelled": "Cancelado", - "backup_manual_in_progress": "Envio já está em progresso. Tente novamente mais tarde", - "backup_manual_success": "Sucesso", - "backup_manual_title": "Estado do envio", - "backup_options": "Opções de backup", - "backup_options_page_title": "Opções de backup", - "backup_setting_subtitle": "Gerenciar as configurações de envio em primeiro e segundo plano", - "backup_settings_subtitle": "Gerenciar configurações de envio", - "backup_upload_details_page_more_details": "Toque para mais detalhes", - "backward": "Para trás", - "biometric_auth_enabled": "Autenticação por biometria ativada", - "biometric_locked_out": "Sua autenticação por biometria está bloqueada", - "biometric_no_options": "Não há opções de biometria disponíveis", - "biometric_not_available": "A autenticação por biometria não está disponível neste dispositivo", - "birthdate_saved": "Data de nascimento salva com sucesso", - "birthdate_set_description": "A data de nascimento é usada para calcular a idade da pessoa no momento em que a foto foi tirada.", - "blurred_background": "Fundo desfocado", - "bugs_and_feature_requests": "Relatar problemas & Sugestões", - "build": "Versão de compilação", - "build_image": "Imagem de compilação", - "bulk_delete_duplicates_confirmation": "Tem a certeza de que deseja deletar {count, plural, one {# arquivo duplicado} other {em massa # arquivos duplicados}}? Esta ação mantém o maior arquivo de cada grupo e deleta permanentemente todos as outras duplicidades. Você não pode desfazer esta ação!", - "bulk_keep_duplicates_confirmation": "Tem certeza de que deseja manter {count, plural, one {# arquivo duplicado} other {# arquivos duplicados}}? Isso resolverá todos os grupos duplicados sem excluir nada.", - "bulk_trash_duplicates_confirmation": "Tem a certeza de que deseja mover para a lixeira {count, plural, one {# arquivo duplicado} other {# arquivos duplicados}}? Isso manterá o maior arquivo de cada grupo e moverá para a lixeira todas as outras duplicidades.", - "buy": "Comprar o Immich", - "cache_settings_clear_cache_button": "Limpar o cache", - "cache_settings_clear_cache_button_title": "Limpa o cache do aplicativo. Isso afetará significativamente o desempenho do aplicativo até que o cache seja reconstruído.", - "cache_settings_duplicated_assets_clear_button": "LIMPAR", - "cache_settings_duplicated_assets_subtitle": "Mídias ignoradas pelo app", - "cache_settings_duplicated_assets_title": "Arquivos duplicados ({count})", - "cache_settings_statistics_album": "Miniaturas da biblioteca", - "cache_settings_statistics_full": "Imagens completas", - "cache_settings_statistics_shared": "Miniaturas de álbuns compartilhados", - "cache_settings_statistics_thumbnail": "Miniaturas", - "cache_settings_statistics_title": "Uso do cache", - "cache_settings_subtitle": "Controle o comportamento de cache do aplicativo Immich", - "cache_settings_tile_subtitle": "Controle o comportamento do armazenamento local", - "cache_settings_tile_title": "Armazenamento Local", - "cache_settings_title": "Configurações de cache", - "camera": "Câmera", - "camera_brand": "Marca da câmera", - "camera_model": "Modelo da câmera", - "cancel": "Cancelar", - "cancel_search": "Cancelar pesquisa", - "canceled": "Cancelado", - "canceling": "Cancelando", - "cannot_merge_people": "Não é possível mesclar pessoas", - "cannot_undo_this_action": "Você não pode desfazer esta ação!", - "cannot_update_the_description": "Não é possível atualizar a descrição", - "cast": "Transmitir", - "cast_description": "Configure destinos de transmissão disponíveis", - "change_date": "Alterar data", - "change_description": "Alterar descrição", - "change_display_order": "Alterar ordem de exibição", - "change_expiration_time": "Alterar o prazo de validade", - "change_location": "Alterar localização", - "change_name": "Alterar nome", - "change_name_successfully": "Nome alterado com sucesso", - "change_password": "Mudar a senha", - "change_password_description": "Esta é a primeira vez que você está acessando o sistema ou foi feita uma solicitação para alterar sua senha. Por favor, insira a nova senha abaixo.", - "change_password_form_confirm_password": "Confirme a senha", - "change_password_form_description": "Olá {name},\n\nEsta é a primeira vez que você está acessando o sistema ou foi feita uma solicitação para alterar sua senha. Por favor, insira a nova senha abaixo.", - "change_password_form_log_out": "Desconectar todos os outros dispositivos", - "change_password_form_log_out_description": "É recomendável desconectar todos os outros dispositivos", - "change_password_form_new_password": "Nova Senha", - "change_password_form_password_mismatch": "As senhas não estão iguais", - "change_password_form_reenter_new_password": "Confirme a nova senha", - "change_pin_code": "Alterar código PIN", - "change_trigger": "Alterar gatilho", - "change_trigger_prompt": "Tem certeza de que deseja alterar o gatilho? Isso removerá todas as ações e filtros existentes.", - "change_your_password": "Alterar sua senha", - "changed_visibility_successfully": "Visibilidade alterada com sucesso", - "charging": "Carregando", - "charging_requirement_mobile_backup": "Backups em segundo plano requerem que o dispositivo esteja sendo carregado", - "check_corrupt_asset_backup": "Verifique se há backups corrompidos", - "check_corrupt_asset_backup_button": "Verificar", - "check_corrupt_asset_backup_description": "Execute esta verificação somente em uma rede Wi-Fi e quando o backup de todos os arquivos já estiver concluído. O processo demora alguns minutos.", - "check_logs": "Ver logs", - "checksum": "Checksum", - "choose_matching_people_to_merge": "Escolha pessoas correspondentes para mesclar", - "city": "Cidade", - "cleanup_confirm_description": "O Immich encontrou {count} arquivos (criados antes de {date}) salvos com segurança no servidor. Deseja remover as cópias locais deste dispositivo?", - "cleanup_confirm_prompt_title": "Remover deste dispositivo?", - "cleanup_deleted_assets": "{count} mídias movidas para a lixeira do dispositivo", - "cleanup_deleting": "Movendo para a lixeira...", - "cleanup_found_assets": "Encontrados {count} arquivos com backup", - "cleanup_found_assets_with_size": "Foram encontrados {count} arquivos com backup ({size})", - "cleanup_icloud_shared_albums_excluded": "Álbuns compartilhados do iCloud não serão incluídos", - "cleanup_no_assets_found": "Não foram encontrados arquivos que correspondam aos seus critérios. Liberar Espaço só pode remover arquivos que foram copiados para o servidor", - "cleanup_preview_title": "Remover {count} arquivos", - "cleanup_step3_description": "Procure por arquivos de backup que correspondam à sua data e manter configurações.", - "cleanup_step4_summary": "{count} arquivos criados antes de {date} foram selecionados para liberar espaço do seu dispositivo. Fotos permanecerão acessíveis através do app do Immich.", - "cleanup_trash_hint": "Para liberar espaço imediatamente, abra a galeria de fotos original do dispositivo e esvazie a lixeira", - "clear": "Limpar", - "clear_all": "Limpar tudo", - "clear_all_recent_searches": "Limpar todas as buscas recentes", - "clear_file_cache": "Limpar cache arquivos", - "clear_message": "Limpar mensagem", - "clear_value": "Limpar valor", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Digite a senha", - "client_cert_import": "Importar", - "client_cert_import_success_msg": "Certificado do cliente importado", - "client_cert_invalid_msg": "Arquivo de certificado inválido ou senha errada", - "client_cert_remove_msg": "Certificado do cliente removido", - "client_cert_subtitle": "Suporta apenas o formato PKCS12 (.p12, .pfx). A importação/remoção de certificados está disponível apenas antes do login", - "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", - "clockwise": "Horário", - "close": "Fechar", - "collapse": "Recolher", - "collapse_all": "Colapsar tudo", - "color": "Cor", - "color_theme": "Tema de cores", - "command": "Comando", - "comment_deleted": "Comentário excluído", - "comment_options": "Opções de comentário", - "comments_and_likes": "Comentários e curtidas", - "comments_are_disabled": "Comentários estão desativados", - "common_create_new_album": "Criar novo álbum", - "completed": "Completado", - "confirm": "Confirmar", - "confirm_admin_password": "Confirmar senha de administrador", - "confirm_delete_face": "Tem certeza que deseja remover a rosto de {name} deste arquivo?", - "confirm_delete_shared_link": "Tem certeza de que deseja excluir este link compartilhado?", - "confirm_keep_this_delete_others": "Todos os outros arquivos do grupo serão excluídos, exceto este arquivo. Tem certeza de que deseja continuar?", - "confirm_new_pin_code": "Confirmar novo código PIN", - "confirm_password": "Confirme a senha", - "confirm_tag_face": "Deseja marcar este rosto como {name}?", - "confirm_tag_face_unnamed": "Deseja marcar este rosto?", - "connected_device": "Dispositivo conectado", - "connected_to": "Conectado a", - "contain": "Caber", - "context": "Contexto", - "continue": "Continuar", - "control_bottom_app_bar_create_new_album": "Criar novo álbum", - "control_bottom_app_bar_delete_from_immich": "Excluir do Immich", - "control_bottom_app_bar_delete_from_local": "Excluir do dispositivo", - "control_bottom_app_bar_edit_location": "Alterar Local", - "control_bottom_app_bar_edit_time": "Editar data e hora", - "control_bottom_app_bar_share_link": "Link", - "control_bottom_app_bar_share_to": "Compartilhar", - "control_bottom_app_bar_trash_from_immich": "Mover para a Lixeira", - "copied_image_to_clipboard": "Imagem copiada para a área de transferência.", - "copied_to_clipboard": "Copiado para a área de transferência!", - "copy_error": "Copiar erro", - "copy_file_path": "Copiar caminho do arquivo", - "copy_image": "Copiar Imagem", - "copy_link": "Copiar link", - "copy_link_to_clipboard": "Copiar link para a área de transferência", - "copy_password": "Copiar senha", - "copy_to_clipboard": "Copiar para a área de transferência", - "country": "País", - "cover": "Preencher", - "covers": "Capas", - "create": "Criar", - "create_album": "Criar álbum", - "create_album_page_untitled": "Sem título", - "create_api_key": "Criar chave de API", - "create_first_workflow": "Criar primeiro fluxo", - "create_library": "Criar biblioteca", - "create_link": "Criar link", - "create_link_to_share": "Criar link e compartilhar", - "create_link_to_share_description": "Permitir que qualquer pessoa com o link veja a(s) foto(s) selecionada(s)", - "create_new": "CRIAR NOVO", - "create_new_person": "Criar nova pessoa", - "create_new_person_hint": "Atribuir arquivos selecionados a uma nova pessoa", - "create_new_user": "Criar novo usuário", - "create_shared_album_page_share_add_assets": "ADICIONAR FOTOS", - "create_shared_album_page_share_select_photos": "Selecionar fotos", - "create_shared_link": "Criar link", - "create_tag": "Criar marcador", - "create_tag_description": "Cria um novo marcador. Para marcadores multi nível, digite o caminho completo do marcador, inclusive as barras.", - "create_user": "Criar usuário", - "create_workflow": "Criar fluxo", - "created": "Criado", - "created_at": "Criado em", - "creating_linked_albums": "Criando álbuns relacionados...", - "crop": "Cortar", - "crop_aspect_ratio_fixed": "Fixo", - "crop_aspect_ratio_free": "Livre", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Objetos", - "current_device": "Dispositivo atual", - "current_pin_code": "Código PIN atual", - "current_server_address": "Endereço atual do servidor", - "custom_date": "Data específica", - "custom_locale": "Localização Customizada", - "custom_locale_description": "Formatar datas e números baseado no idioma e na região", - "custom_url": "URL personalizada", - "cutoff_date_description": "Manter fotos dos últimos…", - "cutoff_day": "{count, plural, one {dia} other {dias}}", - "cutoff_year": "{count, plural, one {ano} other {anos}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Escuro", - "dark_theme": "Usar tema escuro", - "date": "Data", - "date_after": "Data após", - "date_and_time": "Data e Hora", - "date_before": "Data antes", - "date_format": "E, d LLL, y • h:mm a", - "date_of_birth_saved": "Data de nascimento salvo com sucesso", - "date_range": "Intervalo de datas", - "day": "Dia", - "days": "Dias", - "deduplicate_all": "Limpar todas Duplicidades", - "deduplication_criteria_1": "Tamanho do arquivo em bytes", - "deduplication_criteria_2": "Quantidade de dados EXIF", - "deduplication_info": "Informações", - "deduplication_info_description": "Ao selecionar os arquivos que serão marcados para remoção por duplicidade, será considerado os parâmetros:", - "default_locale": "Localização Padrão", - "default_locale_description": "Formatar datas e números baseados na linguagem do seu navegador", - "delete": "Excluir", - "delete_action_confirmation_message": "Tem certeza? O arquivo será enviado para a lixeira do servidor, depois você poderá confirmar se deseja também deletar do seu dispositivo local", - "delete_action_prompt": "{count} deletados", - "delete_album": "Excluir álbum", - "delete_api_key_prompt": "Tem certeza de que deseja excluir esta chave de API?", - "delete_dialog_alert": "Esses itens serão excluídos permanentemente do Immich e do seu dispositivo", - "delete_dialog_alert_local": "Estes arquivos serão permanentemente excluídos do seu dispositivo, mas continuarão disponíveis no servidor Immich", - "delete_dialog_alert_local_non_backed_up": "Não há backup de alguns dos arquivos no servidor e eles serão excluídos permanentemente do seu dispositivo", - "delete_dialog_alert_remote": "Esses arquivos serão excluídos permanentemente do servidor Immich", - "delete_dialog_ok_force": "Excluir mesmo assim", - "delete_dialog_title": "Excluir permanentemente", - "delete_duplicates_confirmation": "Tem certeza de que deseja excluir permanentemente estas duplicidades?", - "delete_face": "Remover rosto", - "delete_key": "Excluir chave", - "delete_library": "Excluir biblioteca", - "delete_link": "Excluir link", - "delete_local_action_prompt": "{count} deletados do dispositivo local", - "delete_local_dialog_ok_backed_up_only": "Excluir apenas arquivos com backup feito", - "delete_local_dialog_ok_force": "Excluir mesmo assim", - "delete_others": "Excluir restante", - "delete_permanently": "Deletar permanentemente", - "delete_permanently_action_prompt": "{count} Deletado permanentemente", - "delete_shared_link": "Excluir link de compartilhamento", - "delete_shared_link_dialog_title": "Excluir link compartilhado", - "delete_tag": "Excluir marcador", - "delete_tag_confirmation_prompt": "Tem certeza que deseja excluir o marcador {tagName} ?", - "delete_user": "Excluir usuário", - "deleted_shared_link": "Link de compartilhamento excluído", - "deletes_missing_assets": "Excluir arquivos não encontrados", - "description": "Descrição", - "description_input_hint_text": "Adicionar descrição...", - "description_input_submit_error": "Erro ao atualizar a descrição, verifique o log para mais detalhes", - "deselect_all": "Desselecionar tudo", - "details": "Detalhes", - "direction": "Direção", - "disable": "Desativar", - "disabled": "Desativado", - "disallow_edits": "Não permitir edições", - "discord": "Discord", - "discover": "Descobrir", - "discovered_devices": "Dispositivos encontrados", - "dismiss_all_errors": "Dispensar todos os erros", - "dismiss_error": "Dispensar erro", - "display_options": "Opções de exibição", - "display_order": "Ordem de exibição", - "display_original_photos": "Exibir fotos originais", - "display_original_photos_setting_description": "Prefira exibir a foto original ao visualizar um arquivo em vez de miniaturas quando o arquivo original é compatível com a web. Isso pode diminuir a velocidade de exibição das fotos.", - "do_not_show_again": "Não mostrar esta mensagem novamente", - "documentation": "Documentação", - "done": "Feito", - "download": "Baixar", - "download_action_prompt": "Baixando {count} arquivos", - "download_canceled": "Cancelado", - "download_complete": "Sucesso", - "download_enqueue": "Na fila", - "download_error": "Erro ao baixar", - "download_failed": "Falha", - "download_finished": "Concluído", - "download_include_embedded_motion_videos": "Vídeos inclusos", - "download_include_embedded_motion_videos_description": "Baixar os vídeos inclusos de uma foto em movimento em um arquivo separado", - "download_notfound": "Não encontrado", - "download_original": "Baixar original", - "download_paused": "Pausado", - "download_settings": "Baixar", - "download_settings_description": "Gerenciar configurações relacionadas a transferência de arquivos", - "download_started": "Baixando", - "download_sucess": "Baixado com sucesso", - "download_sucess_android": "O arquivo foi salvo em DCIM/Immich", - "download_waiting_to_retry": "Aguardando para tentar novamente", - "downloading": "Baixando", - "downloading_asset_filename": "Baixando arquivo {filename}", - "downloading_from_icloud": "Baixando do iCloud", - "downloading_media": "Baixando mídia", - "drop_files_to_upload": "Solte os arquivos em qualquer lugar para enviar", - "duplicates": "Duplicados", - "duplicates_description": "Marque cada grupo indicando quais arquivos, se algum, são duplicados", - "duration": "Duração", - "edit": "Editar", - "edit_album": "Editar álbum", - "edit_avatar": "Editar foto de perfil", - "edit_birthday": "Alterar aniversário", - "edit_date": "Editar data", - "edit_date_and_time": "Editar data e hora", - "edit_date_and_time_action_prompt": "Alterado data e hora de {count} arquivos", - "edit_date_and_time_by_offset": "Alterar data por deslocamento", - "edit_date_and_time_by_offset_interval": "Novas datas: De {from} a {to}", - "edit_description": "Editar descrição", - "edit_description_prompt": "Por favor selecione uma nova descrição:", - "edit_exclusion_pattern": "Editar o padrão de exclusão", - "edit_faces": "Editar rostos", - "edit_key": "Editar chave", - "edit_link": "Editar link", - "edit_location": "Editar Localização", - "edit_location_action_prompt": "{count} locais alterados", - "edit_location_dialog_title": "Localização", - "edit_name": "Editar nome", - "edit_people": "Editar pessoas", - "edit_tag": "Editar marcador", - "edit_title": "Editar Título", - "edit_user": "Editar usuário", - "edit_workflow": "Editar fluxo", - "editor": "Editar", - "editor_close_without_save_prompt": "As alterações não serão salvas", - "editor_close_without_save_title": "Fechar editor?", - "editor_confirm_reset_all_changes": "Tem certeza que deseja desfazer todas alterações?", - "editor_flip_horizontal": "Virar na horizontal", - "editor_flip_vertical": "Virar na vertical", - "editor_orientation": "Orientação", - "editor_reset_all_changes": "Desfazer alterações", - "editor_rotate_left": "Girar 90° em sentido anti-horário", - "editor_rotate_right": "Girar 90° em sentido horário", - "email": "E-mail", - "email_notifications": "Notificações por e-mail", - "empty_folder": "A pasta está vazia", - "empty_trash": "Esvaziar lixeira", - "empty_trash_confirmation": "Tem certeza de que deseja esvaziar a lixeira? Isso removerá permanentemente do Immich todos os arquivos que estão na lixeira.\nVocê não pode desfazer esta ação!", - "enable": "Habilitar", - "enable_backup": "Ativar Backup", - "enable_biometric_auth_description": "Insira seu código PIN para ativar a autenticação por biometria", - "enabled": "Habilitado", - "end_date": "Data final", - "enqueued": "Na fila", - "enter_wifi_name": "Digite o nome do Wi-Fi", - "enter_your_pin_code": "Insira seu código PIN", - "enter_your_pin_code_subtitle": "Insira seu código PIN para acessar a pasta com senha", - "error": "Erro", - "error_change_sort_album": "Falha ao alterar a ordem de exibição", - "error_delete_face": "Erro ao remover face do arquivo", - "error_getting_places": "Erro ao buscar os locais", - "error_loading_albums": "Erro ao carregar álbuns", - "error_loading_image": "Erro ao carregar a página", - "error_loading_partners": "Erro ao carregar parceiros: {error}", - "error_retrieving_asset_information": "Erro ao recuperar informações do arquivo", - "error_saving_image": "Erro: {error}", - "error_tag_face_bounding_box": "Erro ao marcar o rosto - não foi possível localizar o rosto", - "error_title": "Erro - Algo deu errado", - "error_while_navigating": "Erro ao navegar para o arquivo", - "errors": { - "cannot_navigate_next_asset": "Não foi possível navegar para o próximo arquivo", - "cannot_navigate_previous_asset": "Não foi possível navegar para o arquivo anterior", - "cant_apply_changes": "Não foi possível aplicar as alterações", - "cant_change_activity": "Não foi possível {enabled, select, true {desativar} other {habilitar}} a atividade", - "cant_change_asset_favorite": "Não foi possível mudar favorito para o arquivo", - "cant_change_metadata_assets_count": "Não foi possível alterar os metadados de {count, plural, one {# arquivo} other {# arquivos}}", - "cant_get_faces": "Não foi possível obter os rostos", - "cant_get_number_of_comments": "Não foi possível obter o número de comentários", - "cant_search_people": "Não foi possível procurar pessoas", - "cant_search_places": "Não foi possível procurar locais", - "error_adding_assets_to_album": "Erro ao adicionar arquivos para o álbum", - "error_adding_users_to_album": "Erro ao adicionar usuários para o álbum", - "error_deleting_shared_user": "Erro ao deletar o usuário compartilhado", - "error_downloading": "Erro ao baixar {filename}", - "error_hiding_buy_button": "Erro ao ocultar o botão de compra", - "error_removing_assets_from_album": "Erro ao remover arquivos do álbum, verifique o console para mais detalhes", - "error_selecting_all_assets": "Erro ao selecionar todos os arquivos", - "exclusion_pattern_already_exists": "Este padrão de exclusão já existe.", - "failed_to_create_album": "Falha ao criar o álbum", - "failed_to_create_shared_link": "Falha ao criar o link compartilhado", - "failed_to_edit_shared_link": "Falha ao editar o link compartilhado", - "failed_to_get_people": "Falha na obtenção de pessoas", - "failed_to_keep_this_delete_others": "Falha ao manter este arquivo e excluir os outros", - "failed_to_load_asset": "Não foi possível carregar o arquivo", - "failed_to_load_assets": "Não foi possível carregar os arquivos", - "failed_to_load_notifications": "Falha ao carregar notificações", - "failed_to_load_people": "Falha ao carregar pessoas", - "failed_to_remove_product_key": "Falha ao remover a chave do produto", - "failed_to_reset_pin_code": "Falha ao redefinir o Código PIN", - "failed_to_stack_assets": "Falha ao agrupar arquivos", - "failed_to_unstack_assets": "Falha ao remover arquivos do grupo", - "failed_to_update_notification_status": "Falha ao atualizar o status da notificação", - "incorrect_email_or_password": "E-mail ou senha incorretos", - "library_folder_already_exists": "Este caminho de importação já existe.", - "paths_validation_failed": "A validação de {paths, plural, one {# caminho falhou} other {# caminhos falharam}}", - "profile_picture_transparent_pixels": "As imagens de perfil não podem ter pixels transparentes. Aumente o zoom e/ou mova a imagem.", - "quota_higher_than_disk_size": "Você definiu uma cota maior do que o tamanho do disco", - "something_went_wrong": "Algo deu errado", - "unable_to_add_album_users": "Não foi possível adicionar usuários ao álbum", - "unable_to_add_assets_to_shared_link": "Não é possível adicionar arquivos ao link compartilhado", - "unable_to_add_comment": "Não foi possível adicionar o comentário", - "unable_to_add_exclusion_pattern": "Não foi possível adicionar o padrão de exclusão", - "unable_to_add_partners": "Não foi possível adicionar parceiros", - "unable_to_add_remove_archive": "Não é possível {archived, select, true {remove asset from} other {add asset to}} arquivar", - "unable_to_add_remove_favorites": "Não foi possível {favorite, select, true {adicionar o arquivo aos} other {remover o arquivo dos}} favoritos", - "unable_to_archive_unarchive": "Não foi possível {archived, select, true {arquivar} other {desarquivar}}", - "unable_to_change_album_user_role": "Não foi possível alterar a permissão do usuário no álbum", - "unable_to_change_date": "Não foi possível alterar a data", - "unable_to_change_description": "Não foi possível alterar a descrição", - "unable_to_change_favorite": "Não foi possível alterar o favorito para o arquivo", - "unable_to_change_location": "Não foi possível alterar a localização", - "unable_to_change_password": "Não foi possível alterar a senha", - "unable_to_change_visibility": "Não foi possível alterar a visibilidade de {count, plural, one {# pessoa} other {# pessoas}}", - "unable_to_complete_oauth_login": "Não foi possível concluir o login OAuth", - "unable_to_connect": "Não foi possível conectar", - "unable_to_copy_to_clipboard": "Não é possível copiar para a área de transferência, certifique-se que está acessando a pagina através de https", - "unable_to_create": "Não foi possível criar fluxo", - "unable_to_create_admin_account": "Não foi possível criar uma conta de administrador", - "unable_to_create_api_key": "Não foi possível criar uma nova Chave de API", - "unable_to_create_library": "Não foi possível criar a biblioteca", - "unable_to_create_user": "Não foi possível criar o usuário", - "unable_to_delete_album": "Não foi possível deletar o álbum", - "unable_to_delete_asset": "Não foi possível deletar o arquivo", - "unable_to_delete_assets": "Erro ao excluir arquivos", - "unable_to_delete_exclusion_pattern": "Não foi possível deletar o padrão de exclusão", - "unable_to_delete_shared_link": "Não foi possível deletar o link compartilhado", - "unable_to_delete_user": "Não foi possível deletar o usuário", - "unable_to_delete_workflow": "Não foi possível excluir fluxo", - "unable_to_download_files": "Não foi possível baixar os arquivos", - "unable_to_edit_exclusion_pattern": "Não foi possível editar o padrão de exclusão", - "unable_to_empty_trash": "Não foi possível esvaziar a lixeira", - "unable_to_enter_fullscreen": "Não foi possível entrar em modo de tela cheia", - "unable_to_exit_fullscreen": "Não foi possível sair do modo de tela cheia", - "unable_to_get_comments_number": "Não foi possível obter o número de comentários", - "unable_to_get_shared_link": "Não foi possível obter link o compartilhado", - "unable_to_hide_person": "Não foi possível esconder a pessoa", - "unable_to_link_motion_video": "Não foi possível relacionar ao video animado", - "unable_to_link_oauth_account": "Não foi possível associar a conta OAuth", - "unable_to_log_out_all_devices": "Não foi possível sair de todos os dispositivos", - "unable_to_log_out_device": "Não foi possível sair do dispositivo", - "unable_to_login_with_oauth": "Não foi possível fazer login com OAuth", - "unable_to_play_video": "Não foi possível reproduzir o vídeo", - "unable_to_reassign_assets_existing_person": "Não foi possível reatribuir arquivos a {name, select, null {uma pessoa} other {{name}}}", - "unable_to_reassign_assets_new_person": "Não foi possível reatribuir arquivos a uma nova pessoa", - "unable_to_refresh_user": "Não foi possível atualizar o usuário", - "unable_to_remove_album_users": "Não foi possível remover usuários do álbum", - "unable_to_remove_api_key": "Não foi possível a Chave de API", - "unable_to_remove_assets_from_shared_link": "Não foi possível remover arquivos do link compartilhado", - "unable_to_remove_library": "Não foi possível remover a biblioteca", - "unable_to_remove_partner": "Não foi possível remover parceiro", - "unable_to_remove_reaction": "Não foi possível remover a reação", - "unable_to_reset_password": "Não foi possível resetar a senha", - "unable_to_reset_pin_code": "Não foi possível redefinir o código PIN", - "unable_to_resolve_duplicate": "Não foi possível resolver a duplicidade", - "unable_to_restore_assets": "Não foi possível restaurar", - "unable_to_restore_trash": "Não foi possível restaurar itens da lixeira", - "unable_to_restore_user": "Não foi possível restaurar usuário", - "unable_to_save_album": "Não foi possível salvar o álbum", - "unable_to_save_api_key": "Não foi possível salvar a Chave de API", - "unable_to_save_date_of_birth": "Não foi possível salvar a data de nascimento", - "unable_to_save_name": "Não foi possível salvar o nome", - "unable_to_save_profile": "Não foi possível salvar o perfil", - "unable_to_save_settings": "Não foi possível salvar as configurações", - "unable_to_scan_libraries": "Não foi possível escanear as bibliotecas", - "unable_to_scan_library": "Não foi possível escanear a biblioteca", - "unable_to_set_feature_photo": "Não foi possível definir a foto de destaque", - "unable_to_set_profile_picture": "Não foi possível definir a foto de perfil", - "unable_to_set_rating": "Não foi possível classificar", - "unable_to_submit_job": "Não foi possível enviar a tarefa", - "unable_to_trash_asset": "Não foi possível enviar o arquivo para a lixeira", - "unable_to_unlink_account": "Não foi possível desvincular conta", - "unable_to_unlink_motion_video": "Não foi possível remover a relação com o video animado", - "unable_to_update_album_cover": "Não foi possível atualizar a capa do álbum", - "unable_to_update_album_info": "Não foi possível atualizar as informações do álbum", - "unable_to_update_library": "Não foi possível atualizar a biblioteca", - "unable_to_update_location": "Não foi possível atualizar a localização", - "unable_to_update_settings": "Não foi possível atualizar as configurações", - "unable_to_update_timeline_display_status": "Não foi possível atualizar o modo de visualização da linha do tempo", - "unable_to_update_user": "Não foi possível atualizar o usuário", - "unable_to_update_workflow": "Não foi possível atualizar fluxo", - "unable_to_upload_file": "Não foi possível enviar o arquivo" - }, - "errors_text": "Erros", - "exclusion_pattern": "Padrão de exclusão", - "exif": "Exif", - "exif_bottom_sheet_description": "Adicionar descrição...", - "exif_bottom_sheet_description_error": "Erro ao alterar a descrição", - "exif_bottom_sheet_details": "DETALHES", - "exif_bottom_sheet_location": "LOCALIZAÇÃO", - "exif_bottom_sheet_no_description": "Sem descrição", - "exif_bottom_sheet_people": "PESSOAS", - "exif_bottom_sheet_person_add_person": "Adicionar nome", - "exit_slideshow": "Sair da apresentação", - "expand_all": "Expandir tudo", - "experimental_settings_new_asset_list_subtitle": "Em andamento", - "experimental_settings_new_asset_list_title": "Ativar grade de fotos experimental", - "experimental_settings_subtitle": "Use por sua conta e risco!", - "experimental_settings_title": "Experimental", - "expire_after": "Expira depois", - "expired": "Expirou", - "expires_date": "Expira em {date}", - "explore": "Explorar", - "explorer": "Explorar", - "export": "Exportar", - "export_as_json": "Exportar como JSON", - "export_database": "Exportar Banco de Dados", - "export_database_description": "Exportar o Banco de Dados SQLite", - "extension": "Extensão", - "external": "Externo", - "external_libraries": "Bibliotecas externas", - "external_network": "Rede externa", - "external_network_sheet_info": "Quando não estiver na rede Wi-Fi especificada, o aplicativo irá se conectar usando o primeiro endereço abaixo que obtiver sucesso, começando do topo da lista para baixo", - "face_unassigned": "Sem nome", - "failed": "Falhou", - "failed_count": "Falhas: {count}", - "failed_to_authenticate": "Não foi possível autenticar", - "failed_to_load_assets": "Falha ao carregar arquivos", - "failed_to_load_folder": "Falha ao carregar a pasta", - "favorite": "Favorito", - "favorite_action_prompt": "{count} marcados como favorito", - "favorite_or_unfavorite_photo": "Marque ou desmarque a foto como favorita", - "favorites": "Favoritos", - "favorites_page_no_favorites": "Nenhuma mídia favorita encontrada", - "feature_photo_updated": "Foto principal atualizada", - "features": "Funcionalidades", - "features_in_development": "Funções em desenvolvimento", - "features_setting_description": "Gerenciar as funcionalidades da aplicação", - "file_name_or_extension": "Nome do arquivo ou extensão", - "file_size": "Tamanho do arquivo", - "filename": "Nome do arquivo", - "filetype": "Tipo de arquivo", - "filter": "Filtro", - "filter_description": "Condições para filtrar os arquivos enviados", - "filter_people": "Filtrar pessoas", - "filter_places": "Filtrar lugares", - "filters": "Filtros", - "find_them_fast": "Encontre pelo nome em uma pesquisa", - "first": "Primeiro", - "fix_incorrect_match": "Corrigir correspondência incorreta", - "folder": "Pasta", - "folder_not_found": "Pasta não encontrada", - "folders": "Pastas", - "folders_feature_description": "Navegar pelas pastas das fotos e vídeos no sistema de arquivos", - "forgot_pin_code_question": "Esqueceu seu PIN?", - "forward": "Para frente", - "free_up_space": "Liberar espaço", - "free_up_space_description": "Mova as fotos e vídeos de backup para a lixeira do seu dispositivo para liberar espaço. Suas cópias no servidor permanecem seguras.", - "free_up_space_settings_subtitle": "Liberar espaço no dispositivo", - "full_path": "Caminho completo: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Esta funcionalidade carrega recursos externos do Google para funcionar.", - "general": "Geral", - "geolocation_instruction_location": "Selecione um arquivo com as coordenadas de GPS desejada, ou selecione a localização diretamente no mapa", - "get_help": "Obter Ajuda", - "get_people_error": "Erro ao obter pessoas", - "get_wifiname_error": "Não foi possível obter o nome do Wi-Fi. Verifique se concedeu as permissões necessárias e se está conectado a uma rede Wi-Fi", - "getting_started": "Primeiros passos", - "go_back": "Voltar", - "go_to_folder": "Ir para a pasta", - "go_to_search": "Ir para a pesquisa", - "gps": "GPS", - "gps_missing": "Sem GPS", - "grant_permission": "Conceder permissão", - "group_albums_by": "Agrupar álbuns por...", - "group_country": "Agrupar por país", - "group_no": "Sem agrupamento", - "group_owner": "Agrupar por dono", - "group_places_by": "Agrupar lugares por...", - "group_year": "Agrupar por ano", - "haptic_feedback_switch": "Ativar vibração", - "haptic_feedback_title": "Vibração", - "has_quota": "Cota", - "hash_asset": "Calcular hash dos arquivos", - "hashed_assets": "Com hash", - "hashing": "Calculando", - "header_settings_add_header_tip": "Adicionar cabeçalho", - "header_settings_field_validator_msg": "O valor não pode estar vazio", - "header_settings_header_name_input": "Nome do cabeçalho", - "header_settings_header_value_input": "Valor do cabeçalho", - "headers_settings_tile_title": "Cabeçalhos de proxy personalizados", - "height": "Altura", - "hi_user": "Olá {name} ({email})", - "hide_all_people": "Esconder todas as pessoas", - "hide_gallery": "Ocultar galeria", - "hide_named_person": "Esconder {name}", - "hide_password": "Ocultar senha", - "hide_person": "Ocultar pessoa", - "hide_schema": "Ocultar esquema", - "hide_text_recognition": "Esconder reconhecimento de texto", - "hide_unnamed_people": "Esconder pessoas sem nome", - "home_page_add_to_album_conflicts": "{added} arquivos adicionados ao álbum {album}. {failed} arquivos já estão no álbum.", - "home_page_add_to_album_err_local": "Ainda não é possível adicionar arquivos locais aos álbuns, ignorando", - "home_page_add_to_album_success": "{added} arquivos adicionados ao álbum {album}.", - "home_page_album_err_partner": "Ainda não é possível adicionar arquivos de parceiros a um álbum, ignorando", - "home_page_archive_err_local": "Ainda não é possível arquivar mídias locais, ignorando", - "home_page_archive_err_partner": "Não é possível arquivar as mídias do parceiro, ignorando", - "home_page_building_timeline": "Construindo a linha do tempo", - "home_page_delete_err_partner": "Não é possível excluir arquivos do parceiro, ignorando", - "home_page_delete_remote_err_local": "Foram selecionados arquivos locais para excluir remotamente, ignorando", - "home_page_favorite_err_local": "Ainda não é possível adicionar arquivos locais aos favoritos, ignorando", - "home_page_favorite_err_partner": "Ainda não é possível marcar arquivos do parceiro como favoritos, ignorando", - "home_page_first_time_notice": "Se é a primeira vez que utiliza o aplicativo, certifique-se de marcar um ou mais álbuns do dispositivo para backup, assim a linha do tempo será preenchida com as fotos e vídeos", - "home_page_locked_error_local": "Não é possível mover arquivos locais para a pasta com senha, ignorando", - "home_page_locked_error_partner": "Não é possível mover arquivos do parceiro para a pasta com senha, Ignorando", - "home_page_share_err_local": "Não é possível compartilhar arquivos locais com um link, ignorando", - "home_page_upload_err_limit": "Só é possível enviar 30 arquivos de cada vez, ignorando", - "host": "Servidor", - "hour": "Hora", - "hours": "Horas", - "id": "ID", - "idle": "Inativo", - "ignore_icloud_photos": "Ignorar fotos do iCloud", - "ignore_icloud_photos_description": "Fotos que estão armazenadas no iCloud não serão enviadas para o servidor do Immich", - "image": "Imagem", - "image_alt_text_date": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1} em {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1} e {person2} em {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1}, {person2}, e {person3} em {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1}, {person2}, e outras {additionalCount, number} em {date}", - "image_alt_text_date_place": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} em {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1} em {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1} e {person2} em {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {person3} em {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {additionalCount, number} outros em {date}", - "image_saved_successfully": "Imagem salva", - "image_viewer_page_state_provider_download_started": "Baixando arquivo", - "image_viewer_page_state_provider_download_success": "Baixado com sucesso", - "image_viewer_page_state_provider_share_error": "Erro ao compartilhar", - "immich_logo": "Logo do Immich", - "immich_web_interface": "Interface Web do Immich", - "import_from_json": "Importar do JSON", - "import_path": "Caminho de importação", - "in_albums": "Em {count, plural, one {# álbum} other {# álbuns}}", - "in_archive": "Arquivado", - "in_year": "Em {year}", - "in_year_selector": "Em", - "include_archived": "Incluir arquivados", - "include_shared_albums": "Incluir álbuns compartilhados", - "include_shared_partner_assets": "Incluir arquivos compartilhados por parceiros", - "individual_share": "Compartilhamento único", - "individual_shares": "Compartilhamentos individuais", - "info": "Informações", - "interval": { - "day_at_onepm": "Todo dia, 1pm", - "hours": "A cada {hours, plural, one {hora} other {{hours, number} horas}}", - "night_at_midnight": "Toda noite, meia noite", - "night_at_twoam": "Toda noite, 2am" - }, - "invalid_date": "Data inválida", - "invalid_date_format": "Formato de data inválido", - "invite_people": "Convidar Pessoas", - "invite_to_album": "Convidar para o álbum", - "ios_debug_info_fetch_ran_at": "Coleta ocorreu {dateTime}", - "ios_debug_info_last_sync_at": "Ultima sincronização {dateTime}", - "ios_debug_info_no_processes_queued": "Nenhum processo de segundo plano enfileirado", - "ios_debug_info_no_sync_yet": "Nenhum serviço de sicronização em segundo plano foi executado ainda", - "ios_debug_info_processes_queued": "{count, plural, one {{count} processo em segundo plano enfileirado} other {{count} processos em segundo plano enfileirados}}", - "ios_debug_info_processing_ran_at": "processamento executado em {dateTime}", - "items_count": "{count, plural, one {# item} other {# itens}}", - "jobs": "Tarefas", - "json_editor": "Editor JSON", - "json_error": "Erro no JSON", - "keep": "Manter", - "keep_albums": "Manter álbuns", - "keep_albums_count": "Mantendo {count} {count, plural, one {álbum} other {álbuns}}", - "keep_all": "Manter Todos", - "keep_description": "Escolha o que fica no seu dispositivo ao liberar espaço.", - "keep_favorites": "Manter favoritos", - "keep_on_device": "Manter no dispositivo", - "keep_on_device_hint": "Selecione os itens que deseja manter neste dispositivo", - "keep_this_delete_others": "Manter este, excluir o resto", - "keeping": "Mantendo: {items}", - "kept_this_deleted_others": "Este foi mantido e {count, plural, one {# arquivo foi excluído} other {# arquivos foram excluídos}}", - "keyboard_shortcuts": "Atalhos do teclado", - "language": "Idioma", - "language_no_results_subtitle": "tente refinar seu termo de pesquisa", - "language_no_results_title": "nenhum idioma encontrado", - "language_search_hint": "Procure idiomas...", - "language_setting_description": "Selecione seu idioma preferido", - "large_files": "Arquivos Grandes", - "last": "Último", - "last_months": "{count, plural, one {Mês passado} other {Últimos # meses}}", - "last_seen": "Visto pela última vez", - "latest_version": "Versão mais recente", - "latitude": "Latitude", - "leave": "Sair", - "leave_album": "Sair do álbum", - "lens_model": "Modelo da lente", - "let_others_respond": "Permitir respostas", - "level": "Nível", - "library": "Biblioteca", - "library_add_folder": "Adicionar pasta", - "library_edit_folder": "Editar pasta", - "library_options": "Opções da biblioteca", - "library_page_device_albums": "Álbuns no Dispositivo", - "library_page_new_album": "Novo album", - "library_page_sort_asset_count": "Quantidade de arquivos", - "library_page_sort_created": "Data de criação", - "library_page_sort_last_modified": "Última modificação", - "library_page_sort_title": "Título do álbum", - "licenses": "Licenças", - "light": "Claro", - "like": "Curtir", - "like_deleted": "Curtida excluída", - "link_motion_video": "Relacionar video animado", - "link_to_oauth": "Link do OAuth", - "linked_oauth_account": "Conta OAuth Vinculada", - "list": "Lista", - "loading": "Carregando", - "loading_search_results_failed": "Falha ao carregar os resultados da pesquisa", - "local": "Local", - "local_asset_cast_failed": "Não é possível transmitir um arquivo que não foi enviado ao servidor", - "local_assets": "Arquivos no dispositivo", - "local_id": "ID Local", - "local_media_summary": "Resumo das mídias locais", - "local_network": "Rede local", - "local_network_sheet_info": "O aplicativo irá se conectar ao servidor através deste endereço quando estiver na rede Wi-Fi especificada", - "location": "Localização", - "location_permission": "Permissão de localização", - "location_permission_content": "Para usar o recurso de alternância automática, o Immich requer permissão de localização precisa para poder ler o nome da rede Wi-Fi atual", - "location_picker_choose_on_map": "Escolha no mapa", - "location_picker_latitude_error": "Digite uma latitude válida", - "location_picker_latitude_hint": "Digite a latitude", - "location_picker_longitude_error": "Digite uma longitude válida", - "location_picker_longitude_hint": "Digite a longitude", - "lock": "Trancar", - "locked_folder": "Pasta com senha", - "log_detail_title": "Detalhes do Log", - "log_out": "Sair", - "log_out_all_devices": "Sair de todos dispositivos", - "logged_in_as": "Usuário atual: {user}", - "logged_out_all_devices": "Saiu de todos os dispositivos", - "logged_out_device": "Dispositivo desconectado", - "login": "Entrar", - "login_disabled": "Login desativado", - "login_form_api_exception": "Erro de API. Verifique a URL do servidor e tente novamente.", - "login_form_back_button_text": "Voltar", - "login_form_email_hint": "seu@email.com", - "login_form_endpoint_hint": "http://ip-do-seu-servidor:porta", - "login_form_endpoint_url": "URL do servidor", - "login_form_err_http": "Por favor especifique http:// ou https://", - "login_form_err_invalid_email": "E-mail inválido", - "login_form_err_invalid_url": "URL Inválida", - "login_form_err_leading_whitespace": "Há um espaço em branco no início", - "login_form_err_trailing_whitespace": "Há um espaço em branco no fim", - "login_form_failed_get_oauth_server_config": "Erro de login com OAuth, verifique a URL do servidor", - "login_form_failed_get_oauth_server_disable": "O recurso OAuth não está disponível neste servidor", - "login_form_failed_login": "Erro ao fazer login, verifique a URL do servidor, e-mail e senha", - "login_form_handshake_exception": "Houve um erro de autorização com o servidor. Se estiver utilizando um certificado auto assinado, ative o suporte a isso nas configurações.", - "login_form_password_hint": "senha", - "login_form_save_login": "Permaneçer conectado", - "login_form_server_empty": "Digite a URL do servidor.", - "login_form_server_error": "Não foi possível conectar ao servidor.", - "login_has_been_disabled": "Login foi desativado.", - "login_password_changed_error": "Erro ao atualizar a sua senha", - "login_password_changed_success": "Senha atualizada com sucesso", - "logout_all_device_confirmation": "Tem certeza de que deseja sair de todos os dispositivos?", - "logout_this_device_confirmation": "Tem certeza de que deseja sair deste dispositivo?", - "logs": "Registros", - "longitude": "Longitude", - "look": "Estilo", - "loop_videos": "Repetir vídeos", - "loop_videos_description": "Ative para repetir os vídeos automaticamente durante a exibição.", - "main_branch_warning": "Você está utilizando uma versão de desenvolvimento. É fortemente recomendado que utilize uma versão estável!", - "main_menu": "Menu Principal", - "maintenance_action_restore": "Restaurando Banco de Dados", - "maintenance_description": "O Immich foi colocado em modo de manutenção.", - "maintenance_end": "Desativar modo de manutenção", - "maintenance_end_error": "Ocorreu um erro ao desativar o modo de manutenção.", - "maintenance_logged_in_as": "Usuário atual: {user}", - "maintenance_restore_from_backup": "Restaurar a partir de Backup", - "maintenance_restore_library": "Restaurar Sua Biblioteca", - "maintenance_restore_library_confirm": "Se tudo parecer correto, prossiga com a restauração do backup!", - "maintenance_restore_library_description": "Restaurando o Banco de Dados", - "maintenance_restore_library_folder_has_files": "{folder} possui {count} pasta(s)", - "maintenance_restore_library_folder_no_files": "{folder} está faltando arquivos!", - "maintenance_restore_library_folder_pass": "legível e escrevível", - "maintenance_restore_library_folder_read_fail": "ilegível", - "maintenance_restore_library_folder_write_fail": "não gravável", - "maintenance_restore_library_hint_missing_files": "Talvez estejam faltando arquivos importantes", - "maintenance_restore_library_hint_regenerate_later": "Você pode regenerá-los depois nas configurações", - "maintenance_restore_library_hint_storage_template_missing_files": "Está usando um modelo de armazenamento? Podem estar faltando arquivos", - "maintenance_restore_library_loading": "Carregando verificações de integridade e heurísticas…", - "maintenance_task_backup": "Criando um backup do banco de dados existente…", - "maintenance_task_migrations": "Executando migrações do banco de dados…", - "maintenance_task_restore": "Restaurando o backup escolhido…", - "maintenance_task_rollback": "Falha na restauração, voltando para o ponto de restauração…", - "maintenance_title": "Temporariamente Indisponível", - "make": "Marca", - "manage_geolocation": "Gerenciar localização", - "manage_media_access_rationale": "Essa permissão é necessária para o correto gerenciamento da movimentação de mídias para a lixeira e para a sua restauração a partir dela.", - "manage_media_access_settings": "Abrir configurações", - "manage_media_access_subtitle": "Permita que o aplicativo Immich gerencie e mova arquivos de mídia.", - "manage_media_access_title": "Acesso para gerenciar mídias", - "manage_shared_links": "Gerir links partilhados", - "manage_sharing_with_partners": "Gerenciar compartilhamento com parceiros", - "manage_the_app_settings": "Gerenciar configurações do app", - "manage_your_account": "Gerenciar sua conta", - "manage_your_api_keys": "Gerenciar suas Chaves de API", - "manage_your_devices": "Gerenciar seus dispositivos conectados", - "manage_your_oauth_connection": "Gerenciar sua conexão OAuth", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {Sem fotos nesta área} one {# foto} other {# fotos}}", - "map_cannot_get_user_location": "Não foi possível obter a sua localização", - "map_location_dialog_yes": "Sim", - "map_location_picker_page_use_location": "Use esta localização", - "map_location_service_disabled_content": "O serviço de localização precisa estar ativado para exibir os arquivos da sua localização atual. Deseja ativar agora?", - "map_location_service_disabled_title": "Serviço de localização desativado", - "map_marker_for_images": "Marcador de mapa para imagens tiradas em {city}, {country}", - "map_marker_with_image": "Marcador de mapa com imagem", - "map_no_location_permission_content": "É necessária a permissão de localização para exibir os arquivos da sua localização atual. Deseja conceder a permissão agora?", - "map_no_location_permission_title": "Permissão de localização foi negada", - "map_settings": "Definições do mapa", - "map_settings_dark_mode": "Modo escuro", - "map_settings_date_range_option_day": "Últimas 24 horas", - "map_settings_date_range_option_days": "Últimos {days} dias", - "map_settings_date_range_option_year": "Último ano", - "map_settings_date_range_option_years": "Últimos {years} anos", - "map_settings_dialog_title": "Configurações do mapa", - "map_settings_include_show_archived": "Incluir arquivados", - "map_settings_include_show_partners": "Incluir parceiros", - "map_settings_only_show_favorites": "Mostrar apenas favoritos", - "map_settings_theme_settings": "Tema do mapa", - "map_zoom_to_see_photos": "Diminua o zoom para ver mais fotos", - "mark_all_as_read": "Marcar tudo como lido", - "mark_as_read": "Marcar como lido", - "marked_all_as_read": "Tudo marcado como lido", - "matches": "Correspondências", - "matching_assets": "Arquivos encontrados", - "media_type": "Tipo de mídia", - "memories": "Memórias", - "memories_all_caught_up": "Finalizamos por hoje", - "memories_check_back_tomorrow": "Volte amanhã para ver mais lembranças", - "memories_setting_description": "Gerencie o que vê em suas memórias", - "memories_start_over": "Ver de novo", - "memories_swipe_to_close": "Deslize para cima para fechar", - "memory": "Memória", - "memory_lane_title": "Trilha das Recordações {title}", - "menu": "Menu", - "merge": "Mesclar", - "merge_people": "Mesclar pessoas", - "merge_people_limit": "Só é possível mesclar até 5 pessoas de uma só vez", - "merge_people_prompt": "Tem certeza que deseja mesclar estas pessoas? Esta ação é irreversível.", - "merge_people_successfully": "Pessoas mescladas com sucesso", - "merged_people_count": "{count, plural, one {# pessoa foi mesclada} other {# pessoas foram mescladas}}", - "minimize": "Minimizar", - "minute": "Minuto", - "minutes": "Minutos", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertical", - "missing": "Faltando", - "mobile_app": "Aplicativo Móvel", - "mobile_app_download_onboarding_note": "Baixe o aplicativo móvel usando as opções abaixo", - "model": "Modelo", - "month": "Mês", - "monthly_title_text_date_format": "MMMM y", - "more": "Mais", - "move": "Mover", - "move_down": "Mover para baixo", - "move_off_locked_folder": "Mover para fora da pasta com senha", - "move_to": "Mover para", - "move_to_device_trash": "Mover para lixeira", - "move_to_lock_folder_action_prompt": "{count} adicionados à pasta com senha", - "move_to_locked_folder": "Mover para a pasta com senha", - "move_to_locked_folder_confirmation": "Estas fotos e vídeos serão removidos de todos os álbuns e somente poderão ser visualizados de dentro da pasta com senha", - "move_up": "Mover para cima", - "moved_to_archive": "{count, plural, one {# mídia foi arquivada} other {# mídias foram arquivadas}}", - "moved_to_library": "{count, plural, one {# arquivo foi enviado} other {# arquivos foram enviados}} à biblioteca", - "moved_to_trash": "Enviado para a lixeira", - "multiselect_grid_edit_date_time_err_read_only": "Não é possível editar a data do(s) arquivo(s) somente leitura, pulando", - "multiselect_grid_edit_gps_err_read_only": "Não é possível editar a localização dos arquivos somente leitura, ignorando", - "mute_memories": "Silenciar memórias", - "my_albums": "Meus Álbuns", - "name": "Nome", - "name_or_nickname": "Nome ou apelido", - "name_required": "Nome é obrigatório", - "navigate": "Navegar", - "navigate_to_time": "Navegar para Horário", - "network_requirement_photos_upload": "Use a rede móvel para enviar fotos", - "network_requirement_videos_upload": "Use a rede móvel para enviar vídeos", - "network_requirements": "Requerimentos de Rede", - "network_requirements_updated": "Requerimentos de rede alterados, reiniciando a fila de envio", - "networking_settings": "Conexões", - "networking_subtitle": "Gerencie as conexões ao servidor", - "never": "Nunca", - "new_album": "Novo Álbum", - "new_api_key": "Nova Chave de API", - "new_date_range": "Nova faixa de datas", - "new_password": "Nova senha", - "new_person": "Nova Pessoa", - "new_pin_code": "Novo código PIN", - "new_pin_code_subtitle": "Esta é a primeira vez que está acessando a pasta com senha. Crie um código PIN para acessar esta página de forma segura", - "new_timeline": "Nova Linha do Tempo", - "new_update": "Nova atualização", - "new_user_created": "Novo usuário criado", - "new_version_available": "NOVA VERSÃO DISPONÍVEL", - "newest_first": "Mais recente primeiro", - "next": "Avançar", - "next_memory": "Próxima memória", - "no": "Não", - "no_actions_added": "Nenhuma ação foi adicionada ainda", - "no_albums_found": "Nenhum álbum encontrado", - "no_albums_message": "Crie um álbum para organizar suas fotos e vídeos", - "no_albums_with_name_yet": "Parece que você ainda não tem nenhum álbum com esse nome.", - "no_albums_yet": "Parece que você ainda não tem nenhum álbum.", - "no_archived_assets_message": "Arquive fotos e vídeos para os ocultar da sua visualização de fotos", - "no_assets_message": "Clique aqui para enviar sua primeira foto", - "no_assets_to_show": "Não há arquivos para exibir", - "no_cast_devices_found": "Nenhum dispositivo encontrado", - "no_checksum_local": "Nenhum checksum disponível - não foi possível carregar os arquivos locais", - "no_checksum_remote": "Nenhum checksum disponível - não foi possível carregar os arquivos remotos", - "no_configuration_needed": "Nenhuma configuração necessária", - "no_devices": "Nenhum dispostivio autorizado", - "no_duplicates_found": "Nenhuma duplicidade foi encontrada.", - "no_exif_info_available": "Sem informações exif disponíveis", - "no_explore_results_message": "Envie mais fotos para explorar sua coleção.", - "no_favorites_message": "Adicione aos favoritos para encontrar suas melhores fotos e vídeos rapidamente", - "no_filters_added": "Nenhum filtro adicionado ainda", - "no_libraries_message": "Crie uma biblioteca externa para ver suas fotos e vídeos", - "no_local_assets_found": "Nenhum arquivo local foi encontrado com este checksum", - "no_location_set": "Sem localização", - "no_locked_photos_message": "Fotos e vídeos na pasta com senha são ocultos e não serão exibidos enquanto explora ou pesquisa na biblioteca.", - "no_name": "Sem Nome", - "no_notifications": "Nenhuma notificação", - "no_people_found": "Nenhuma pessoa encontrada", - "no_places": "Sem lugares", - "no_remote_assets_found": "Nenhum arquivo remoto foi encontrado com este checksum", - "no_results": "Sem resultados", - "no_results_description": "Tente um sinônimo ou uma palavra-chave mais geral", - "no_shared_albums_message": "Crie um álbum para compartilhar fotos e vídeos com pessoas em sua rede", - "no_uploads_in_progress": "Nenhum envio em progresso", - "none": "Nenhum", - "not_allowed": "Não permitido", - "not_available": "N/A", - "not_in_any_album": "Fora de álbum", - "not_selected": "Não selecionado", - "note_apply_storage_label_to_previously_uploaded assets": "Nota: Para aplicar o rótulo de armazenamento a arquivos enviados anteriormente, execute o", - "notes": "Notas", - "nothing_here_yet": "Ainda não existe nada aqui", - "notification_permission_dialog_content": "Para ativar as notificações, vá em Configurações e selecione permitir.", - "notification_permission_list_tile_content": "Conceda permissão para ativar notificações.", - "notification_permission_list_tile_enable_button": "Ativar notificações", - "notification_permission_list_tile_title": "Permissão de notificações", - "notification_toggle_setting_description": "Habilitar notificações por e-mail", - "notifications": "Notificações", - "notifications_setting_description": "Gerenciar notificações", - "oauth": "OAuth", - "obtainium_configurator": "Configurador Obtainium", - "obtainium_configurator_instructions": "Use o Obtainium para instalar e atualizar o aplicativo Android diretamente do lançamento do Immich no GitHub. Crie uma chave API e selecione a variante para criar o seu link de configuração Obtainium", - "ocr": "OCR", - "official_immich_resources": "Recursos oficiais do Immich", - "offline": "Desconectado", - "offset": "Deslocamento", - "ok": "Ok", - "oldest_first": "Mais antigo primeiro", - "on_this_device": "Neste dispositivo", - "onboarding": "Integração", - "onboarding_locale_description": "Selecione sua linguagem preferida. Você pode alterar isso mais tarde em suas configurações.", - "onboarding_privacy_description": "Os seguintes recursos opcionais dependem de serviços externos e podem ser desabilitados a qualquer momento nas configurações.", - "onboarding_server_welcome_description": "Vamos colocar sua instância no ar com algumas configurações usuais.", - "onboarding_theme_description": "Escolha um tema de cores para sua instância. Você pode alterar isso posteriormente em suas configurações.", - "onboarding_user_welcome_description": "Vamos começar!", - "onboarding_welcome_user": "Bem-vindo, {user}", - "online": "Conectado", - "only_favorites": "Somente favoritos", - "open": "Abrir", - "open_in_map_view": "Mostrar no mapa", - "open_in_openstreetmap": "Abrir no OpenStreetMap", - "open_the_search_filters": "Abre os filtros de pesquisa", - "options": "Opções", - "or": "ou", - "organize_into_albums": "Organizar em álbuns", - "organize_into_albums_description": "Colocar imagens existentes em álbuns usando as configurações de sincronização atuais", - "organize_your_library": "Organize sua biblioteca", - "original": "original", - "other": "Outro", - "other_devices": "Outros dispositivos", - "other_entities": "Outras entidades", - "other_variables": "Outras variáveis", - "owned": "Seu", - "owner": "Dono", - "page": "Página", - "partner": "Parceiro", - "partner_can_access": "{partner} pode acessar", - "partner_can_access_assets": "Todas suas fotos e vídeos, excetos os Arquivados ou Excluídos", - "partner_can_access_location": "A localização onde as fotos foram tiradas", - "partner_list_user_photos": "Fotos de {user}", - "partner_list_view_all": "Ver tudo", - "partner_page_empty_message": "As suas fotos ainda não foram compartilhadas com nenhum parceiro.", - "partner_page_no_more_users": "Não há mais usuários para adicionar", - "partner_page_partner_add_failed": "Falha ao adicionar parceiro", - "partner_page_select_partner": "Selecione o parceiro", - "partner_page_shared_to_title": "Compartilhado com", - "partner_page_stop_sharing_content": "{partner} não poderá mais acessar as suas fotos.", - "partner_sharing": "Compartilhamento com Parceiro", - "partners": "Parceiros", - "password": "Senha", - "password_does_not_match": "As senhas não são iguais", - "password_required": "A senha é obrigatório", - "password_reset_success": "Senha resetada com sucesso", - "past_durations": { - "days": "{days, plural, one {Último dia} other {Últimos {days, number} dias}}", - "hours": "{hours, plural, one {Última hora} other {Últimas {hours, number} horas}}", - "years": "{years, plural, one {Último ano} other {Últimos {years, number} anos}}" - }, - "path": "Caminho", - "pattern": "Padrão", - "pause": "Interromper", - "pause_memories": "Interromper memórias", - "paused": "Interrompido", - "pending": "Pendente", - "people": "Pessoas", - "people_edits_count": "{count, plural, one {# pessoa editada} other {# pessoas editadas}}", - "people_feature_description": "Navegar por fotos e vídeos agrupados por pessoas", - "people_selected": "{count, plural, one {# pessoa selecionada} other {# pessoas selecionadas}}", - "people_sidebar_description": "Exibe o link Pessoas na barra lateral", - "permanent_deletion_warning": "Aviso para deletar permanentemente", - "permanent_deletion_warning_setting_description": "Exibe um aviso ao deletar arquivos de forma permanente", - "permanently_delete": "Deletar permanentemente", - "permanently_delete_assets_count": "Excluir permanentemente {count, plural, one {asset} other {assets}}", - "permanently_delete_assets_prompt": "Você tem certeza de que deseja excluir permanentemente {count, plural, one {este arquivo?} other {estes # arquivos?}} Esta ação também removerá {count, plural, one {o arquivo} other {os arquivos}} de um ou mais álbuns.", - "permanently_deleted_asset": "Arquivo deletado permanentemente", - "permanently_deleted_assets_count": "{count, plural, one {# arquivo permanentemente excluído} other {# arquivos permanentemente excluídos}}", - "permission": "Permissão", - "permission_empty": "Sua permissão não deveria ser vazia", - "permission_onboarding_back": "Voltar", - "permission_onboarding_continue_anyway": "Continuar assim mesmo", - "permission_onboarding_get_started": "Começar", - "permission_onboarding_go_to_settings": "Ir para as configurações", - "permission_onboarding_permission_denied": "Permissão negada. Para utilizar o Immich, conceda permissões de fotos e vídeo nas configurações.", - "permission_onboarding_permission_granted": "Permissão concedida! Tudo pronto.", - "permission_onboarding_permission_limited": "Permissão limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissões para fotos e vídeos nas configurações.", - "permission_onboarding_request": "Immich requer permissão para visualizar suas fotos e vídeos.", - "person": "Pessoa", - "person_age_months": "{months, plural, one {# mês} other {# meses}} de idade", - "person_age_year_months": "1 ano, {months, plural, one {# mês} other {# meses}} de idade", - "person_age_years": "{years, plural, other {# anos}}", - "person_birthdate": "Nasceu em {date}", - "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", - "person_recognized": "Pessoa reconhecida", - "person_selected": "Pessoa selecionada", - "photo_shared_all_users": "Parece que você compartilhou suas fotos com todos os usuários ou não tem nenhum usuário com quem compartilhar.", - "photos": "Fotos", - "photos_and_videos": "Fotos e Vídeos", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", - "photos_from_previous_years": "Fotos de anos anteriores", - "photos_only": "Somente fotos", - "pick_a_location": "Selecione uma localização", - "pick_custom_range": "Intervalo customizado", - "pick_date_range": "Selecione o intervalo de datas", - "pin_code_changed_successfully": "Código PIN alterado com sucesso", - "pin_code_reset_successfully": "Código PIN redefinido com sucesso", - "pin_code_setup_successfully": "Código PIN criado com sucesso", - "pin_verification": "Verificação do código PIN", - "place": "Lugar", - "places": "Lugares", - "places_count": "{count, plural, one {{count, number} Lugar} other {{count, number} Lugares}}", - "play": "Reproduzir", - "play_memories": "Reproduzir memórias", - "play_motion_photo": "Reproduzir foto em movimento", - "play_or_pause_video": "Reproduzir ou Pausar vídeo", - "play_original_video": "Reproduzir o vídeo original", - "play_original_video_setting_description": "Preferir por reprodução dos vídeos originais ao invés de vídeos transcodificados. Se o arquivo original não for compatível, ele pode não ser reproduzido corretamente.", - "play_transcoded_video": "Reproduzir vídeo transcodificado", - "please_auth_to_access": "Por favor autentique-se para acessar", - "port": "Porta", - "preferences_settings_subtitle": "Gerenciar as preferências do aplicativo", - "preferences_settings_title": "Preferências", - "preparing": "Preparando", - "preset": "Predefinição", - "preview": "Pré-visualizar", - "previous": "Anterior", - "previous_memory": "Memória anterior", - "previous_or_next_day": "Dia seguinte/anterior", - "previous_or_next_month": "Mês seguinte/anterior", - "previous_or_next_photo": "Foto seguinte/anterior", - "previous_or_next_year": "Ano seguinte/anterior", - "primary": "Primário", - "privacy": "Privacidade", - "profile": "Perfil", - "profile_drawer_app_logs": "Registros", - "profile_drawer_client_server_up_to_date": "Cliente e Servidor estão atualizados", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Modo apenas leitura habilidato. Dê um toque prolongado na foto do usuário para sair deste modo.", - "profile_image_of_user": "Imagem do perfil de {user}", - "profile_picture_set": "Foto de perfil definida.", - "public_album": "Álbum público", - "public_share": "Compartilhar Publicamente", - "purchase_account_info": "Contribuidor", - "purchase_activated_subtitle": "Obrigado(a) por apoiar o Immich e programas de código aberto", - "purchase_activated_time": "Ativado em {date}", - "purchase_activated_title": "Sua chave foi ativada com sucesso", - "purchase_button_activate": "Ativar", - "purchase_button_buy": "Comprar", - "purchase_button_buy_immich": "Comprar Immich", - "purchase_button_never_show_again": "Não mostrar novamente", - "purchase_button_reminder": "Lembre-me em 30 dias", - "purchase_button_remove_key": "Remover chave", - "purchase_button_select": "Selecionar", - "purchase_failed_activation": "Falha ao ativar! Por favor, verifique seu e-mail para a chave do produto correta!", - "purchase_individual_description_1": "Para um indivíduo", - "purchase_individual_description_2": "Status de contribuidor", - "purchase_individual_title": "Indivíduo", - "purchase_input_suggestion": "Tem uma chave de produto? Insira a chave abaixo", - "purchase_license_subtitle": "Compre o Immich para apoiar o desenvolvimento contínuo do serviço", - "purchase_lifetime_description": "Compra vitalícia", - "purchase_option_title": "OPÇÕES DE COMPRA", - "purchase_panel_info_1": "Construir o Immich leva muito tempo e esforço. Temos engenheiros trabalhando em tempo integral para torná-lo o melhor possível. Nossa missão é fazer com que programas de código aberto e práticas empresariais éticas se tornem uma fonte de renda sustentável para os desenvolvedores e também criar um ecossistema que respeite a privacidade, oferecendo alternativas reais aos serviços de nuvem exploratórios.", - "purchase_panel_info_2": "Como estamos comprometidos em não adicionar funções bloqueadas por compras, esta compra não lhe concederá nenhum recurso adicional no Immich. Nós contamos com usuários como você para apoiar o desenvolvimento contínuo do Immich.", - "purchase_panel_title": "Apoiar o projeto", - "purchase_per_server": "Por servidor", - "purchase_per_user": "Por usuário", - "purchase_remove_product_key": "Remover Chave do Produto", - "purchase_remove_product_key_prompt": "Você tem certeza de que deseja remover a chave do produto?", - "purchase_remove_server_product_key": "Remover Chave do Produto para Servidor", - "purchase_remove_server_product_key_prompt": "Você tem certeza de que deseja remover a Chave do Produto para Servidor?", - "purchase_server_description_1": "Para o servidor inteiro", - "purchase_server_description_2": "Status de Contribuidor", - "purchase_server_title": "Servidor", - "purchase_settings_server_activated": "A chave do produto para servidor é gerenciada pelo administrador", - "query_asset_id": "Consultar ID do Ativo", - "queue_status": "Na fila {count} de {total}", - "rate_asset": "Classificar arquivo", - "rating": "Estrelas", - "rating_clear": "Limpar classificação", - "rating_count": "{count, plural, one {# estrela} other {# estrelas}}", - "rating_description": "Exibir o EXIF de classificação no painel de informações", - "rating_set": "Classificação alterada para {rating, plural, one {# estrela} other {# estrelas}}", - "reaction_options": "Opções de reação", - "read_changelog": "Ler Novidades", - "readonly_mode_disabled": "Modo apenas visualização desativado", - "readonly_mode_enabled": "Modo apenas visualização ativado", - "ready_for_upload": "Pronto para enviar", - "reassign": "Reatribuir", - "reassigned_assets_to_existing_person": "{count, plural, one {# arquivo reatribuído} other {# arquivos reatribuídos}} a {name, select, null {uma pessoa} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {# arquivo reatribuído} other {# arquivos reatribuídos}} a uma nova pessoa", - "reassing_hint": "Atribuir arquivos selecionados a uma pessoa existente", - "recent": "Recente", - "recent-albums": "Álbuns recentes", - "recent_searches": "Pesquisas recentes", - "recently_added": "Adicionado recentemente", - "recently_added_page_title": "Adicionados recentemente", - "recently_taken": "Tirada recentemente", - "recently_taken_page_title": "Tiradas recentemente", - "refresh": "Atualizar", - "refresh_encoded_videos": "Atualizar vídeos codificados", - "refresh_faces": "Atualizar rostos", - "refresh_metadata": "Atualizar metadados", - "refresh_thumbnails": "Atualizar miniaturas", - "refreshed": "Atualizado", - "refreshes_every_file": "Atualiza todos arquivos", - "refreshing_encoded_video": "Atualizando vídeo codificado", - "refreshing_faces": "Atualizando rostos", - "refreshing_metadata": "Atualizando metadados", - "regenerating_thumbnails": "Regenerando miniaturas", - "remote": "Remoto", - "remote_assets": "Arquivos Remotos", - "remote_media_summary": "Resumo das mídias remotas", - "remove": "Remover", - "remove_assets_album_confirmation": "Tem certeza de que deseja remover {count, plural, one {# arquivo} other {# arquivos}} do álbum?", - "remove_assets_shared_link_confirmation": "Tem certeza de que deseja remover {count, plural, one {# arquivo} other {# arquivos}} desse link compartilhado?", - "remove_assets_title": "Remover arquivos?", - "remove_custom_date_range": "Remover intervalo de datas personalizado", - "remove_deleted_assets": "Remover arquivos excluídos", - "remove_from_album": "Remover do álbum", - "remove_from_album_action_prompt": "{count} removido do álbum", - "remove_from_favorites": "Remover dos favoritos", - "remove_from_lock_folder_action_prompt": "{count} removidos da pasta com senha", - "remove_from_locked_folder": "Remover da pasta com senha", - "remove_from_locked_folder_confirmation": "Tem a certeza de que deseja mover estes arquivos para fora da pasta com senha? Eles ficarão visíveis na biblioteca principal.", - "remove_from_shared_link": "Remover do link compartilhado", - "remove_memory": "Remover memória", - "remove_photo_from_memory": "Remover foto desta memória", - "remove_tag": "Remover marcador", - "remove_url": "Remover URL", - "remove_user": "Remover usuário", - "removed_api_key": "Removido a Chave de API: {name}", - "removed_from_archive": "Removido do arquivo", - "removed_from_favorites": "Removido dos favoritos", - "removed_from_favorites_count": "{count, plural, one {# Removido} other {# Removidos}} dos favoritos", - "removed_memory": "Memória removida", - "removed_photo_from_memory": "Foto removida da memória", - "removed_tagged_assets": "Marcador removido de {count, plural, one {# arquivo} other {# arquivos}}", - "rename": "Renomear", - "repair": "Reparar", - "repair_no_results_message": "Arquivos perdidos ou não rastreados aparecem aqui", - "replace_with_upload": "Substituir", - "repository": "Repositório", - "require_password": "Proteger com senha", - "require_user_to_change_password_on_first_login": "Obrigar usuário a alterar a senha após primeiro login", - "rescan": "Reescanear", - "reset": "Resetar", - "reset_password": "Resetar senha", - "reset_people_visibility": "Resetar pessoas ocultas", - "reset_pin_code": "Redefinir código PIN", - "reset_pin_code_description": "Se esqueceu seu código PIN, entre em contato com o administrador do Immich e peça para redefinir", - "reset_pin_code_success": "código PIN alterado com sucesso", - "reset_pin_code_with_password": "Você sempre poderá redefinir seu código PIN usando a sua senha", - "reset_sqlite": "Redefinir o Banco de Dados SQLite", - "reset_sqlite_confirmation": "Realmente deseja redefinir o banco de dados SQLite? Será necessário sair e entrar em sua conta novamente para ressincronizar os dados", - "reset_sqlite_success": "Banco de dados SQLite redefinido com sucesso", - "reset_to_default": "Redefinir para a configuração padrão", - "resolution": "Resolução", - "resolve_duplicates": "Resolver duplicatas", - "resolved_all_duplicates": "Todas duplicidades resolvidas", - "restore": "Restaurar", - "restore_all": "Restaurar tudo", - "restore_trash_action_prompt": "{count} restaurados da lixeira", - "restore_user": "Restaurar usuário", - "restored_asset": "Arquivo restaurado", - "resume": "Continuar", - "resume_paused_jobs": "Retomar {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Tentar enviar novamente", - "review_duplicates": "Revisar duplicidade", - "review_large_files": "Ver arquivos grandes", - "role": "Função", - "role_editor": "Editor", - "role_viewer": "Visualizador", - "running": "Executando", - "save": "Salvar", - "save_to_gallery": "Salvar na galeria", - "saved": "Salvo", - "saved_api_key": "Chave de API salva", - "saved_profile": "Perfil Salvo", - "saved_settings": "Configurações salvas", - "say_something": "Diga algo", - "scaffold_body_error_occurred": "Ocorreu um erro", - "scan": "Escanear", - "scan_all_libraries": "Escanear Todas Bibliotecas", - "scan_library": "Escanear", - "scan_settings": "Opções de escanear", - "scanning": "Escaneando", - "scanning_for_album": "Escaneando por álbum...", - "search": "Pesquisar", - "search_albums": "Pesquisar álbuns", - "search_by_context": "Pesquisar por contexto", - "search_by_description": "Pesquisar por descrição", - "search_by_description_example": "Dia de caminhada no Ibirapuera", - "search_by_filename": "Pesquisa por nome de arquivo ou extensão", - "search_by_filename_example": "Por exemplo, IMG_1234.JPG ou PNG", - "search_by_ocr": "Buscar por OCR", - "search_by_ocr_example": "Café com leite", - "search_camera_lens_model": "Buscar por modelo de lente...", - "search_camera_make": "Pesquisar câmeras da marca...", - "search_camera_model": "Pesquisar câmera do modelo...", - "search_city": "Pesquisar cidade...", - "search_country": "Pesquisar país...", - "search_filter_apply": "Aplicar filtro", - "search_filter_camera_title": "Selecione o tipo de câmera", - "search_filter_date": "Data", - "search_filter_date_interval": "de {start} até {end}", - "search_filter_date_title": "Selecione o intervalo de datas", - "search_filter_display_option_not_in_album": "Não está em nenhum álbum", - "search_filter_display_options": "Opções de exibição", - "search_filter_filename": "Pesquisar por nome de arquivo", - "search_filter_location": "Localização", - "search_filter_location_title": "Selecione a localização", - "search_filter_media_type": "Tipo de mídia", - "search_filter_media_type_title": "Selecione o tipo de mídia", - "search_filter_ocr": "Buscar por OCR", - "search_filter_people_title": "Selecione pessoas", - "search_filter_star_rating": "Avaliação", - "search_for": "Pesquisar por", - "search_for_existing_person": "Pesquisar por pessoas", - "search_no_more_result": "Não há mais resultados", - "search_no_people": "Nenhuma pessoa", - "search_no_people_named": "Nenhuma pessoa chamada \"{name}\"", - "search_no_result": "Nenhum resultado encontrado, tente pesquisar por algo diferente", - "search_options": "Opções de pesquisa", - "search_page_categories": "Categorias", - "search_page_motion_photos": "Fotos com movimento", - "search_page_no_objects": "Nenhuma informação de objeto disponível", - "search_page_no_places": "Nenhuma informação de lugares disponível", - "search_page_screenshots": "Capturas de tela", - "search_page_search_photos_videos": "Pesquise suas fotos e vídeos", - "search_page_selfies": "Selfies", - "search_page_things": "Coisas", - "search_page_view_all_button": "Ver tudo", - "search_page_your_activity": "Sua atividade", - "search_page_your_map": "Seu mapa", - "search_people": "Pesquisar pessoas", - "search_places": "Pesquisar lugares", - "search_rating": "Pesquisar por classificação...", - "search_result_page_new_search_hint": "Nova pesquisa", - "search_settings": "Pesquisar nas Configurações", - "search_state": "Pesquisar estado...", - "search_suggestion_list_smart_search_hint_1": "A pesquisa inteligente é utilizada por padrão, para pesquisar por metadados, use a sintaxe ", - "search_suggestion_list_smart_search_hint_2": "m:seu-termo-de-pesquisa", - "search_tags": "Procurar marcadores...", - "search_timezone": "Pesquisar fuso horário...", - "search_type": "Pesquisar tipo", - "search_your_photos": "Pesquisar fotos", - "searching_locales": "Pesquisar Lugares....", - "second": "Segundo", - "see_all_people": "Ver todas as pessoas", - "select": "Selecionar", - "select_album": "Selecionar álbum", - "select_album_cover": "Escolher capa do álbum", - "select_albums": "Selecionar álbuns", - "select_all": "Selecionar todos", - "select_all_duplicates": "Selecionar todas as duplicatas", - "select_all_in": "Selecionar tudo em {group}", - "select_avatar_color": "Selecionar cor do avatar", - "select_count": "{count, plural, one {Selecionar #} other {Selecionar #}}", - "select_cutoff_date": "Selecione a data limite", - "select_face": "Selecionar rosto", - "select_featured_photo": "Selecionar foto principal", - "select_from_computer": "Selecionar do computador", - "select_keep_all": "Marcar manter em todos", - "select_library_owner": "Selecione o dono da biblioteca", - "select_new_face": "Selecionar novo rosto", - "select_people": "Selecionar pessoas", - "select_person": "Selecionar pessoa", - "select_person_to_tag": "Selecione uma pessoa para marcar", - "select_photos": "Selecionar fotos", - "select_trash_all": "Marcar lixo em todos", - "select_user_for_sharing_page_err_album": "Falha ao criar álbum", - "selected": "Selecionados", - "selected_count": "{count, plural, one {# selecionado} other {# selecionados}}", - "selected_gps_coordinates": "Coordenadas de GPS Selecionada", - "send_message": "Enviar mensagem", - "send_welcome_email": "Enviar E-mail de boas vindas", - "server_endpoint": "URL do Servidor", - "server_info_box_app_version": "Versão do aplicativo", - "server_info_box_server_url": "Endereço", - "server_offline": "Servidor Indisponível", - "server_online": "Servidor Disponível", - "server_privacy": "Privacidade do servidor", - "server_restarting_description": "Esta página será atualizada em breve.", - "server_restarting_title": "O servidor está reiniciando", - "server_stats": "Status do servidor", - "server_update_available": "Uma atualização para o servidor está disponível", - "server_version": "Versão do servidor", - "set": "Definir", - "set_as_album_cover": "Definir como capa do álbum", - "set_as_featured_photo": "Definir como foto em destaque", - "set_as_profile_picture": "Definir como foto de perfil", - "set_date_of_birth": "Definir data de nascimento", - "set_profile_picture": "Definir foto de perfil", - "set_slideshow_to_fullscreen": "Apresentação em tela cheia", - "set_stack_primary_asset": "Selecionar como arquivo principal", - "setting_image_viewer_help": "O visualizador de imagens carrega primeiro a miniatura pequena, depois carrega a imagem de tamanho médio (se ativado) e, por fim, carrega o original (se ativado).", - "setting_image_viewer_original_subtitle": "Ative para carregar a imagem original em resolução máxima (grande!). Desative para reduzir o uso de dados (tanto na rede quanto no cache do dispositivo).", - "setting_image_viewer_original_title": "Carregar imagem original", - "setting_image_viewer_preview_subtitle": "Ative para carregar uma imagem de resolução média. Desative para carregar diretamente o original ou usar apenas a miniatura.", - "setting_image_viewer_preview_title": "Carregar imagem de pré-visualização", - "setting_image_viewer_title": "Imagens", - "setting_languages_apply": "Aplicar", - "setting_languages_subtitle": "Alterar o idioma do aplicativo", - "setting_notifications_notify_failures_grace_period": "Notifique falhas de backup em segundo plano: {duration}", - "setting_notifications_notify_hours": "{count} horas", - "setting_notifications_notify_immediately": "imediatamente", - "setting_notifications_notify_minutes": "{count} minutos", - "setting_notifications_notify_never": "nunca", - "setting_notifications_notify_seconds": "{count} segundos", - "setting_notifications_single_progress_subtitle": "Informações detalhadas sobre o progresso do envio por arquivo", - "setting_notifications_single_progress_title": "Mostrar detalhes do progresso do backup em segundo plano", - "setting_notifications_subtitle": "Ajuste suas preferências de notificação", - "setting_notifications_total_progress_subtitle": "Progresso do envio de arquivos (concluídos/total)", - "setting_notifications_total_progress_title": "Mostrar o progresso total do backup em segundo plano", - "setting_video_viewer_auto_play_subtitle": "Reproduzir os vídeos automaticamente quando abertos", - "setting_video_viewer_auto_play_title": "Reproduzir vídeos automaticamente", - "setting_video_viewer_looping_title": "Repetir", - "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o arquivo original, mesmo quando uma versão transcodificada esteja disponível. Pode fazer com que o vídeo demore para carregar. Vídeos disponíveis localmente são exibidos na qualidade original independente desta configuração.", - "setting_video_viewer_original_video_title": "Forçar vídeo original", - "settings": "Configurações", - "settings_require_restart": "Reinicie o Immich para aplicar esta configuração", - "settings_saved": "Configurações salvas", - "setup_pin_code": "Criar um código PIN", - "share": "Compartilhar", - "share_action_prompt": "{count} arquivos compartilhados", - "share_add_photos": "Adicionar fotos", - "share_assets_selected": "{count} selecionado", - "share_dialog_preparing": "Preparando...", - "share_link": "Criar Link", - "shared": "Compartilhado", - "shared_album_activities_input_disable": "Comentários desativados", - "shared_album_activity_remove_content": "Deseja excluir esta atividade?", - "shared_album_activity_remove_title": "Excluir atividade", - "shared_album_section_people_action_error": "Erro ao sair/remover do álbum", - "shared_album_section_people_action_leave": "Remover usuário do álbum", - "shared_album_section_people_action_remove_user": "Remover usuário do álbum", - "shared_album_section_people_title": "PESSOAS", - "shared_by": "Compartilhado por", - "shared_by_user": "Compartilhado por {user}", - "shared_by_you": "Compartilhado por você", - "shared_from_partner": "Fotos de {partner}", - "shared_intent_upload_button_progress_text": "Enviados {current} de {total}", - "shared_link_app_bar_title": "Links compartilhados", - "shared_link_clipboard_copied_massage": "Copiado para a área de transferência", - "shared_link_clipboard_text": "Link: {link}\nSenha: {password}", - "shared_link_create_error": "Erro ao criar o link compartilhado", - "shared_link_custom_url_description": "Acessar este link com uma URL personalizada", - "shared_link_edit_description_hint": "Digite a descrição do compartilhamento", - "shared_link_edit_expire_after_option_day": "1 dia", - "shared_link_edit_expire_after_option_days": "{count} dias", - "shared_link_edit_expire_after_option_hour": "1 hora", - "shared_link_edit_expire_after_option_hours": "{count} horas", - "shared_link_edit_expire_after_option_minute": "1 minuto", - "shared_link_edit_expire_after_option_minutes": "{count} minutos", - "shared_link_edit_expire_after_option_months": "{count} meses", - "shared_link_edit_expire_after_option_year": "{count} ano", - "shared_link_edit_password_hint": "Digite uma senha para proteger este link", - "shared_link_edit_submit_button": "Atualizar link", - "shared_link_error_server_url_fetch": "Erro ao abrir a URL do servidor", - "shared_link_expires_day": "Expira em {count} dia", - "shared_link_expires_days": "Expira em {count} dias", - "shared_link_expires_hour": "Expira em {count} hora", - "shared_link_expires_hours": "Expira em {count} horas", - "shared_link_expires_minute": "Expira em {count} minuto", - "shared_link_expires_minutes": "Expira em {count} minutos", - "shared_link_expires_never": "Expira em ∞", - "shared_link_expires_second": "Expira em {count} segundo", - "shared_link_expires_seconds": "Expira em {count} segundos", - "shared_link_individual_shared": "Compartilhado Individualmente", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Gerenciar links compartilhados", - "shared_link_options": "Opções de link compartilhado", - "shared_link_password_description": "Exija uma senha para acessar este link compartilhado", - "shared_links": "Links", - "shared_links_description": "Compartilhar fotos e videos com um link", - "shared_photos_and_videos_count": "{assetCount, plural, one {# Foto & vídeo compartilhado.} other {# Fotos & vídeos compartilhados.}}", - "shared_with_me": "Compartilhado comigo", - "shared_with_partner": "Compartilhado com {partner}", - "sharing": "Compartilhamento", - "sharing_enter_password": "Digite a senha para visualizar esta página.", - "sharing_page_album": "Álbuns compartilhados", - "sharing_page_description": "Crie álbuns compartilhados para compartilhar fotos e vídeos com pessoas em sua rede.", - "sharing_page_empty_list": "LISTA VAZIA", - "sharing_sidebar_description": "Exibe um link para Compartilhamento na barra lateral", - "sharing_silver_appbar_create_shared_album": "Criar álbum compartilhado", - "sharing_silver_appbar_share_partner": "Compartilhe com o parceiro", - "shift_to_permanent_delete": "pressione ⇧ para excluir permanentemente o arquivo", - "show_album_options": "Exibir opções do álbum", - "show_albums": "Exibir álbuns", - "show_all_people": "Mostrar todas as pessoas", - "show_and_hide_people": "Mostrar & ocultar pessoas", - "show_file_location": "Exibir local do arquivo", - "show_gallery": "Exibir galeria", - "show_hidden_people": "Exibir pessoas ocultadas", - "show_in_timeline": "Exibir na linha do tempo", - "show_in_timeline_setting_description": "Exibe fotos e vídeos deste usuário na sua linha do tempo", - "show_keyboard_shortcuts": "Exibir atalhos do teclado", - "show_metadata": "Mostrar metadados", - "show_or_hide_info": "Exibir ou ocultar informações", - "show_password": "Exibir senha", - "show_person_options": "Exibir opções da pessoa", - "show_progress_bar": "Exibir barra de progresso", - "show_schema": "Exibir esquema", - "show_search_options": "Exibir opções de pesquisa", - "show_shared_links": "Mostrar links compartilhados", - "show_slideshow_transition": "Usar transições no modo de apresentação", - "show_supporter_badge": "Insígnia de apoiador", - "show_supporter_badge_description": "Mostrar uma insígnia de apoiador", - "show_text_recognition": "Exibir reconhecimento de texto", - "show_text_search_menu": "Mostrar menu de pesquisa por texto", - "shuffle": "Aleatório", - "sidebar": "Barra lateral", - "sidebar_display_description": "Exibir um link para a visualização na barra lateral", - "sign_out": "Sair", - "sign_up": "Registrar", - "size": "Tamanho", - "skip_to_content": "Ir para o conteúdo", - "skip_to_folders": "Ir para pastas", - "skip_to_tags": "Ir para os marcadores", - "slideshow": "Apresentação", - "slideshow_repeat": "Repetir apresentação de slides", - "slideshow_repeat_description": "Voltar para o início quando a apresentação terminar", - "slideshow_settings": "Opções de apresentação", - "sort_albums_by": "Ordenar álbuns por...", - "sort_created": "Data de criação", - "sort_items": "Número de itens", - "sort_modified": "Data de modificação", - "sort_newest": "Foto mais nova", - "sort_oldest": "Foto mais antiga", - "sort_people_by_similarity": "Ordenar pessoas por semelhança", - "sort_recent": "Foto mais recente", - "sort_title": "Título", - "source": "Fonte", - "stack": "Agrupar", - "stack_action_prompt": "{count} agrupados", - "stack_duplicates": "Agrupar duplicados", - "stack_select_one_photo": "Selecione uma foto principal para o grupo", - "stack_selected_photos": "Agrupar fotos selecionadas", - "stacked_assets_count": "{count, plural, one {# arquivo adicionado} other {# arquivos adicionados}} ao grupo", - "stacktrace": "Stacktrace", - "start": "Início", - "start_date": "Data inicial", - "start_date_before_end_date": "A data de início deve ser antes da data final", - "state": "Estado", - "status": "Status", - "stop_casting": "Parar transmissão", - "stop_motion_photo": "Parar foto em movimento", - "stop_photo_sharing": "Parar de compartilhar suas fotos?", - "stop_photo_sharing_description": "{partner} não terá mais acesso às suas fotos.", - "stop_sharing_photos_with_user": "Parar de compartilhar suas fotos com este usuário", - "storage": "Espaço de armazenamento", - "storage_label": "Rótulo de armazenamento", - "storage_quota": "Quota de armazenamento", - "storage_usage": "Utilizando {used} de {available}", - "submit": "Enviar", - "success": "Sucesso", - "suggestions": "Sugestões", - "sunrise_on_the_beach": "Nascer do sol na praia", - "support": "Ajuda", - "support_and_feedback": "Ajuda & Feedback", - "support_third_party_description": "Sua instalação do Immich é fornecida por terceiros. É possível que problemas sejam causados por eles, por isso, se tiver problemas, procure primeiro ajuda com eles utilizando os links abaixo.", - "swap_merge_direction": "Alternar direção da mesclagem", - "sync": "Sincronizar", - "sync_albums": "Sincronizar álbuns", - "sync_albums_manual_subtitle": "Sincronize todos as fotos e vídeos enviados para os álbuns de backup selecionados", - "sync_local": "Sincronização Local", - "sync_remote": "Sincronização Remota", - "sync_status": "Status da Sincronização", - "sync_status_subtitle": "Ver e gerenciar o sistema de sincronização", - "sync_upload_album_setting_subtitle": "Crie e envie suas fotos e vídeos para o álbum selecionado no Immich", - "tag": "Marcador", - "tag_assets": "Marcar arquivos", - "tag_created": "Marcador criado: {tag}", - "tag_feature_description": "Navegando por fotos e videos agrupados pelo tópico lógico do marcador", - "tag_not_found_question": "Não consegue encontrar o marcador? Crie uma novo aqui.", - "tag_people": "Marcar pessoas", - "tag_updated": "Marcador foi atualizado: {tag}", - "tagged_assets": "{count, plural, one {# Arquivo marcado} other {# Arquivos marcados}}", - "tags": "Marcadores", - "tap_to_run_job": "Toque para executar", - "template": "Modelo", - "text_recognition": "Reconhecimento de texto", - "theme": "Tema", - "theme_selection": "Selecionar tema", - "theme_selection_description": "Definir automaticamente o tema como claro ou escuro com base nas preferências do sistema do seu navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostrar indicador de armazenamento na grade de fotos", - "theme_setting_asset_list_tiles_per_row_title": "Quantidade de arquivos por linha ({count})", - "theme_setting_colorful_interface_subtitle": "Aplica a cor primária ao fundo.", - "theme_setting_colorful_interface_title": "Interface colorida", - "theme_setting_image_viewer_quality_subtitle": "Ajuste a qualidade de imagens detalhadas do visualizador", - "theme_setting_image_viewer_quality_title": "Qualidade das imagens do visualizador", - "theme_setting_primary_color_subtitle": "Selecione uma cor para ações principais e realces.", - "theme_setting_primary_color_title": "Cor primária", - "theme_setting_system_primary_color_title": "Usar a cor do sistema", - "theme_setting_system_theme_switch": "Automático (seguir a configuração do sistema)", - "theme_setting_theme_subtitle": "Escolha a configuração de tema do app", - "theme_setting_three_stage_loading_subtitle": "O carregamento em três estágios oferece a imagem de melhor qualidade em troca de uma velocidade de carregamento mais lenta", - "theme_setting_three_stage_loading_title": "Ative o carregamento em três estágios", - "then": "Antes", - "they_will_be_merged_together": "Eles serão mesclados", - "third_party_resources": "Recursos de terceiros", - "time": "Hora", - "time_based_memories": "Memórias baseadas no tempo", - "time_based_memories_duration": "Número de segundos para exibir cada imagem.", - "timeline": "Linha do tempo", - "timezone": "Fuso horário", - "to_archive": "Arquivar", - "to_change_password": "Alterar senha", - "to_favorite": "Favorito", - "to_login": "Iniciar sessão", - "to_multi_select": "selecionar vários", - "to_parent": "Voltar para nível acima", - "to_select": "selecionar", - "to_trash": "Mover para a lixeira", - "toggle_settings": "Alternar configurações", - "toggle_theme_description": "Alternar tema", - "total": "Total", - "total_usage": "Utilização total", - "trash": "Lixeira", - "trash_action_prompt": "{count} enviados à lixeira", - "trash_all": "Mover todos para o lixo", - "trash_count": "Lixo {count, number}", - "trash_delete_asset": "Jogar na lixeira/Excluir Arquivo", - "trash_emptied": "Lixeira esvaziada", - "trash_no_results_message": "Fotos e vídeos enviados para o lixo aparecem aqui.", - "trash_page_delete_all": "Excluir tudo", - "trash_page_empty_trash_dialog_content": "Deseja esvaziar a lixera? Estes arquivos serão apagados de forma permanente do Immich", - "trash_page_info": "Os itens da lixeira são excluídos de forma permanente após {days} dias", - "trash_page_no_assets": "Lixeira vazia", - "trash_page_restore_all": "Restaurar tudo", - "trash_page_select_assets_btn": "Selecionar arquivos", - "trash_page_title": "Lixeira ({count})", - "trashed_items_will_be_permanently_deleted_after": "Os itens da lixeira serão deletados permanentemente após {days, plural, one {# dia} other {# dias}}.", - "trigger": "Gatilho", - "trigger_asset_uploaded": "Arquivo enviado", - "trigger_asset_uploaded_description": "Acionado quando um novo arquivo é enviado", - "trigger_description": "Um evento que dá início ao fluxo", - "trigger_person_recognized": "Pessoa reconhecida", - "trigger_person_recognized_description": "Acionado quando uma pessoa é detectada", - "trigger_type": "Tipo de gatilho", - "troubleshoot": "Diagnosticar", - "type": "Tipo", - "unable_to_change_pin_code": "Não foi possível alterar o código PIN", - "unable_to_check_version": "Não foi possível verificar a versão do aplicativo ou do servidor", - "unable_to_setup_pin_code": "Não foi possível criar o código PIN", - "unarchive": "Desarquivar", - "unarchive_action_prompt": "{count} desarquivado", - "unarchived_count": "{count, plural, one {# Desarquivado} other {# Desarquivados}}", - "undo": "Desfazer", - "unfavorite": "Remover favorito", - "unfavorite_action_prompt": "{count} removido dos favoritos", - "unhide_person": "Exibir pessoa", - "unknown": "Desconhecido", - "unknown_country": "País desconhecido", - "unknown_date": "Data desconhecida", - "unknown_year": "Ano desconhecido", - "unlimited": "Ilimitado", - "unlink_motion_video": "Remover relação com video animado", - "unlink_oauth": "Desvincular OAuth", - "unlinked_oauth_account": "Conta OAuth desvinculada", - "unmute_memories": "Ativar Memórias", - "unnamed_album": "Álbum sem nome", - "unnamed_album_delete_confirmation": "Tem certeza que deseja excluir este álbum?", - "unnamed_share": "Compartilhamento sem nome", - "unsaved_change": "Alteração não salva", - "unselect_all": "Desselecionar todos", - "unselect_all_duplicates": "Desselecionar todas as duplicatas", - "unselect_all_in": "Remover seleção de {group}", - "unstack": "Desagrupar", - "unstack_action_prompt": "{count} desagrupados", - "unstacked_assets_count": "{count, plural, one {# arquivo retirado} other {# arquivos retirados}} do grupo", - "unsupported_field_type": "Tipo de campo não suportado", - "untagged": "Marcador removido", - "untitled_workflow": "Fluxo sem título", - "up_next": "A seguir", - "update_location_action_prompt": "Atualizar a localização de {count} arquivos selecionados para:", - "updated_at": "Atualizado em", - "updated_password": "Senha atualizada", - "upload": "Enviar", - "upload_concurrency": "Envios simultâneos", - "upload_details": "Detalhes do envio", - "upload_dialog_info": "Deseja fazer o backup dos arquivos selecionados no servidor?", - "upload_dialog_title": "Enviar arquivo", - "upload_error_with_count": "Erro de envio para {count, plural, one {# arquivo} other {# arquivos}}", - "upload_errors": "Envio concluído com {count, plural, one {# erro} other {# erros}}, atualize a página para ver os novos arquivos.", - "upload_finished": "Envio finalizado", - "upload_progress": "{remaining, number} restantes - {processed, number}/{total, number} já processados", - "upload_skipped_duplicates": "{count, plural, one {# Arquivo duplicado foi ignorado} other {# Arquivos duplicados foram ignorados}}", - "upload_status_duplicates": "Duplicados", - "upload_status_errors": "Erros", - "upload_status_uploaded": "Enviado", - "upload_success": "Enviado com sucesso, atualize a página para ver os novos arquivos.", - "upload_to_immich": "Enviar para o Immich ({count})", - "uploading": "Enviando", - "uploading_media": "Enviando mídia", - "url": "URL", - "usage": "Uso", - "use_biometric": "Usar biometria", - "use_current_connection": "Usar a conexão atual", - "use_custom_date_range": "Usar intervalo de datas personalizado", - "user": "Usuário", - "user_has_been_deleted": "Este usuário foi excluído.", - "user_id": "ID do usuário", - "user_liked": "{user} curtiu {type, select, photo {esta foto} video {este vídeo} asset {este arquivo} other {isto}}", - "user_pin_code_settings": "Código PIN", - "user_pin_code_settings_description": "Gerenciar o seu código PIN", - "user_privacy": "Privacidade do usuário", - "user_purchase_settings": "Comprar", - "user_purchase_settings_description": "Gerenciar sua compra", - "user_role_set": "Definir {user} como {role}", - "user_usage_detail": "Detalhes de uso do usuário", - "user_usage_stats": "Estatísticas de utilização da conta", - "user_usage_stats_description": "Ver estatísticas de utilização da conta", - "username": "Nome do usuário", - "users": "Usuários", - "users_added_to_album_count": "{count, plural, one {# usuário adicionado} other {# usuários adicionados}} ao álbum", - "utilities": "Ferramentas", - "validate": "Validar", - "validate_endpoint_error": "Digite uma URL válida", - "validation_error": "Erro de validação", - "variables": "Variáveis", - "version": "Versão", - "version_announcement_closing": "De seu amigo, Alex", - "version_announcement_message": "Olá! Uma nova versão do Immich está disponível. Para evitar configurações incorretas, leia com calma a página de notas da versão e verifique se é necessário alterar alguma configuração, principalmente se você usa o WatchTower ou qualquer outro mecanismo que faça atualizações automáticas do Immich.", - "version_history": "Histórico de versões", - "version_history_item": "Versão {version} instalada em {date}", - "video": "Vídeo", - "video_hover_setting": "Reproduzir miniatura do vídeo ao passar o mouse", - "video_hover_setting_description": "Reproduzir a miniatura do vídeo ao passar o mouse sobre o item. Mesmo quando desativado, a reprodução pode ser iniciada ao passar o mouse sobre o ícone de reprodução.", - "videos": "Vídeos", - "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", - "videos_only": "Somente videos", - "view": "Ver", - "view_album": "Ver álbum", - "view_all": "Ver tudo", - "view_all_users": "Ver todos os usuários", - "view_asset_owners": "Vizualizar donos do arquivo", - "view_details": "Ver Detalhes", - "view_in_timeline": "Ver na linha do tempo", - "view_link": "Ver link", - "view_links": "Ver links", - "view_name": "Ver", - "view_next_asset": "Ver próximo arquivo", - "view_previous_asset": "Ver arquivo anterior", - "view_qr_code": "Ver QR Code", - "view_similar_photos": "Ver fotos similares", - "view_stack": "Ver grupo", - "view_user": "Visualizar usuário", - "viewer_remove_from_stack": "Remover do grupo", - "viewer_stack_use_as_main_asset": "Usar como foto principal", - "viewer_unstack": "Desagrupar", - "visibility_changed": "A visibilidade de {count, plural, one {# pessoa foi alterada} other {# pessoas foram alteradas}}", - "visual": "Visual", - "visual_builder": "Construtor visual", - "waiting": "Na fila", - "waiting_count": "Esperando: {count}", - "warning": "Aviso", - "week": "Semana", - "welcome": "Bem-vindo(a)", - "welcome_to_immich": "Bem-vindo(a) ao Immich", - "width": "Largura", - "wifi_name": "Nome do Wi-Fi", - "workflow_delete_prompt": "Tem certeza de que deseja excluir este fluxo?", - "workflow_deleted": "Fluxo excluído", - "workflow_description": "Descrição do fluxo", - "workflow_info": "Informações sobre fluxo", - "workflow_json": "Fluxo em JSON", - "workflow_json_help": "Edite a configuração do fluxo em formato JSON. As alterações serão sincronizadas com o construtor visual.", - "workflow_name": "Nome do fluxo", - "workflow_navigation_prompt": "Tem certeza de que deseja sair sem salvar as alterações?", - "workflow_summary": "Resumo do fluxo", - "workflow_update_success": "Fluxo atualizado com sucesso", - "workflow_updated": "Fluxo atualizado", - "workflows": "Fluxos", - "workflows_help_text": "Fluxos utilizam gatilhos e filtros para automatizar ações sobre os arquivos", - "wrong_pin_code": "Código PIN incorreto", - "year": "Ano", - "years_ago": "{years, plural, one {# ano} other {# anos}} atrás", - "yes": "Sim", - "you_dont_have_any_shared_links": "Não há links compartilhados", - "your_wifi_name": "Nome do seu Wi-Fi", - "zero_to_clear_rating": "Tecle 0 para remover a classificação", - "zoom_image": "Ampliar imagem", - "zoom_to_bounds": "Ampliar para preencher" -} +{} diff --git a/i18n/ro.json b/i18n/ro.json index b9b04b7cce..0967ef424b 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -1,2401 +1 @@ -{ - "about": "Despre", - "account": "Cont", - "account_settings": "Setări cont", - "acknowledge": "Am înțeles", - "action": "Acţiune", - "action_common_update": "Actualizează", - "action_description": "Un set de acțiuni de efectuat asupra elementelor filtrate", - "actions": "Acţiuni", - "active": "Active", - "active_count": "Activ: {count}", - "activity": "Activitate", - "activity_changed": "Activitatea este {enabled, select, true {activată} other {dezactivată}}", - "add": "Adaugă", - "add_a_description": "Adaugă o descriere", - "add_a_location": "Adaugă o locație", - "add_a_name": "Adaugă un nume", - "add_a_title": "Adaugă un titlu", - "add_action": "Adaugă acţiune", - "add_action_description": "Click pentru a adăuga o acțiune de rulat", - "add_assets": "Adaugă elemente", - "add_birthday": "Adaugă zi de naștere", - "add_endpoint": "Adaugă punct final", - "add_exclusion_pattern": "Adăugă un model de excludere", - "add_filter": "Adaugă filtru", - "add_filter_description": "Click pentru a adăuga o condiție de filtrare", - "add_location": "Adaugă locație", - "add_more_users": "Adaugă mai mulți utilizatori", - "add_partner": "Adaugă partener", - "add_path": "Adaugă o cale", - "add_photos": "Adaugă fotografii", - "add_tag": "Adaugă etichetă", - "add_to": "Adaugă la…", - "add_to_album": "Adaugă în album", - "add_to_album_bottom_sheet_added": "Adăugat în {album}", - "add_to_album_bottom_sheet_already_exists": "Deja în {album}", - "add_to_album_bottom_sheet_some_local_assets": "Unele resurse locale nu au putut fi adăugate la album", - "add_to_album_toggle": "Selectează/deselectează {album}", - "add_to_albums": "Adaugă la albume", - "add_to_albums_count": "Adaugă la albume ({count})", - "add_to_bottom_bar": "Adaugă la", - "add_to_shared_album": "Adaugă la album partajat", - "add_upload_to_stack": "Încarcă și adaugă la stivă", - "add_url": "Adaugă adresa URL", - "add_workflow_step": "Adaugă un pas în workflow", - "added_to_archive": "Adăugat la arhivă", - "added_to_favorites": "Adăugat la favorite", - "added_to_favorites_count": "Adăugat {count, number} la favorite", - "admin": { - "add_exclusion_pattern_description": "Adaugă modele de excludere. Globing folosind *, ** și ? este suportat. Pentru a ignora toate fișierele din orice director numit „Raw”, utilizați „**/Raw/**”. Pentru a ignora toate fișierele care se termină în „.tif”, utilizați „**/*.tif”. Pentru a ignora o cale absolută, utilizați „/path/to/ignore/**”.", - "admin_user": "Utilizator admin", - "asset_offline_description": "Acest material din biblioteca externă nu se mai găsește pe disc și a fost mutat în coșul de gunoi. Dacă fișierul a fost mutat în bibliotecă, verificați cronologia pentru noul material corespunzător. Pentru a restabili acest material, asigurați-vă că calea fișierului de mai jos poate fi accesată de Immich și scanați biblioteca.", - "authentication_settings": "Setări de autentificare", - "authentication_settings_description": "Gestionează parola, OAuth și alte setări de autentificare", - "authentication_settings_disable_all": "Ești sigur că vrei sa dezactivezi toate metodele de autentificare? Autentificarea va fi complet dezactivată.", - "authentication_settings_reenable": "Pentru a reactiva, folosește Comandă Server.", - "background_task_job": "Activități de Fundal", - "backup_database": "Salvare bază de date", - "backup_database_enable_description": "Activare salvarea bazei de date", - "backup_keep_last_amount": "Număr de copii de rezervă anterioare de păstrat", - "backup_onboarding_1_description": "copie externă în cloud sau într-o altă locație fizică.", - "backup_onboarding_2_description": "copii locale pe diferite dispozitive. Include fișierele principale și o copie de rezervă a acestor fișiere la nivel local.", - "backup_onboarding_3_description": "numărul total de copii ale datelor dvs., inclusiv fișierele originale. Aceasta include 1 copie externă și 2 copii locale.", - "backup_onboarding_description": "Pentru a vă proteja datele, vă recomandăm să utilizați strategia de backup 3-2-1. Pentru o soluție completă de backup, ar trebui să păstrați copii ale fotografiilor/videoclipurilor încărcate, precum și ale bazei de date Immich.", - "backup_onboarding_footer": "Pentru mai multe informații despre copierea de rezervă a Immich, consultați documentația.", - "backup_onboarding_parts_title": "O copie de rezervă 3-2-1 include:", - "backup_onboarding_title": "Copii de rezervă", - "backup_settings": "Setări pentru descărcarea bazei de date", - "backup_settings_description": "Gestionați setările de descărcare a bazei de date.", - "cleared_jobs": "Sarcini șterse pentru: {job}", - "config_set_by_file": "Configurația este setată în prezent de un fișier de configurare", - "confirm_delete_library": "Sigur doriți să ștergeți biblioteca {library}?", - "confirm_delete_library_assets": "Sigur doriți să ștergeți această bibliotecă? Aceasta va șterge {count, plural, one {# contained asset} other {all # contained assets}} din Immich și nu poate fi anulată. Fișierele vor rămâne pe disc.", - "confirm_email_below": "Pentru a confirma, tastați „{email}” mai jos", - "confirm_reprocess_all_faces": "Sigur doriți să reprocesați toate fețele? Acest lucru va șterge și persoanele cu nume.", - "confirm_user_password_reset": "Sigur doriți să resetați parola utilizatorului {user}?", - "confirm_user_pin_code_reset": "Ești sigur că vrei să resetezi codul PIN al {user}?", - "copy_config_to_clipboard_description": "Copiați configurația curentă a sistemului ca obiect JSON în clipboard", - "create_job": "Creează sarcină", - "cron_expression": "Expresia cron", - "cron_expression_description": "Setați intervalul de scanare folosind formatul cron. Pentru mai multe informații, consultați de ex. Crontab Guru", - "cron_expression_presets": "Presetări de expresie cron", - "disable_login": "Dezactivați autentificarea", - "duplicate_detection_job_description": "Rulați învățarea automată pe materiale pentru a detecta imagini similare. Se bazează pe Căutare Inteligentă", - "exclusion_pattern_description": "Modelele de excludere vă permit să ignorați fișierele și folderele atunci când vă scanați biblioteca. Acest lucru este util dacă aveți foldere care conțin fișiere pe care nu doriți să le importați, cum ar fi fișierele RAW.", - "export_config_as_json_description": "Descărcați configurația actuală a sistemului ca fișier JSON", - "external_libraries_page_description": "Pagina bibliotecii externe Admin", - "face_detection": "Detecție facială", - "face_detection_description": "Detectează fețele din fișiere folosind învățare automată. Pentru videoclipuri, este luată în considerare doar miniatura. „Reîmprospătează” (re)procesează toate fișierele. „Resetează” adaugă în coadă fișierele care nu au fost încă procesate. Fețele detectate vor fi puse în coadă pentru recunoașterea facială după finalizarea detectării feței, grupându-le în persoane existente sau noi.", - "facial_recognition_job_description": "Grupați fețele detectate în persoane. Acest pas rulează după ce Detectarea Feței este finalizată. „Resetează” (re)grupează toate fețele. „Lipsă” adaugă în coadă fețe care nu au o persoană desemnată.", - "failed_job_command": "Comanda {command} a eșuat pentru jobul: {job}", - "force_delete_user_warning": "AVERTISMENT: Acest lucru va elimina imediat utilizatorul și toate activele sale. Acest lucru nu poate fi anulat și fișierele nu pot fi recuperate.", - "image_format": "Formatează", - "image_format_description": "WebP produce fișiere mai mici decât JPEG, dar este mai lent de codat.", - "image_fullsize_description": "Imaginea întreagă fără metadata, mărită", - "image_fullsize_enabled": "Activați generarea imaginilor de dimensiune înaltă", - "image_fullsize_enabled_description": "Generează imagini de dimensiune înaltă pentru formate non-web-friendly. Atunci când „Preferați previzualizarea încorporată” e activată, previzualizările încorporate sunt folosite în mod direct fără conversie. Nu afectează formate web-friendly precum JPEG.", - "image_fullsize_quality_description": "Calitatea imaginilor de dimensiune înaltă între 1-100. Valorile mai mari sunt mai bune, dar produc fișiere mai mari.", - "image_fullsize_title": "Setări Imagini de mărime mare", - "image_prefer_embedded_preview": "Preferă previzualizarea încorporată", - "image_prefer_embedded_preview_setting_description": "Folosește previzualizările încorporate în fotografiile RAW ca intrare pentru procesarea imaginii, atunci când sunt disponibile. Acest lucru poate produce culori mai precise pentru unele imagini, dar calitatea previzualizării depinde de cameră, iar imaginea poate avea mai multe artefacte de compresie.", - "image_prefer_wide_gamut": "Preferă o gamă largă", - "image_prefer_wide_gamut_setting_description": "Utilizați Display P3 pentru miniaturi. Acest lucru păstrează mai bine vibrația imaginilor cu spații de culoare largi, dar imaginile pot apărea diferit pe dispozitivele cu o versiune mai veche de browser. Imaginile sRGB sunt păstrate ca sRGB pentru a evita schimbările de culoare.", - "image_preview_description": "Imagine de dimensiune medie cu metadate eliminate, utilizată la vizualizarea unui singur element și pentru învățarea automată", - "image_preview_quality_description": "Calitatea previzualizării de la 1 la 100. O valoare mai mare oferă o calitate mai bună, dar produce fișiere mai mari și poate reduce receptivitatea aplicației. Setarea unei valori scăzute poate afecta calitatea învățării automate.", - "image_preview_title": "Previzualizați setările", - "image_progressive": "Progresiv", - "image_progressive_description": "Encodează imaginile JPEG progresiv, pentru încărcare graduală.Fără efect pentru imaginile WebP", - "image_quality": "Calitate", - "image_resolution": "Rezolutie", - "image_resolution_description": "Rezoluțiile mai mari pot păstra mai multe detalii, dar necesită mai mult timp pentru a fi codificate, au dimensiuni mai mari ale fișierelor și pot reduce răspunsul aplicației.", - "image_settings": "Setări imagine", - "image_settings_description": "Gestionează calitatea și rezoluția imaginilor generate", - "image_thumbnail_description": "Miniatură mică cu metadate eliminate, utilizată la vizualizarea grupurilor de fotografii, cum ar fi în cronologia principală", - "image_thumbnail_quality_description": "Calitatea miniaturii de la 1 la 100. O valoare mai mare oferă o calitate mai bună, dar produce fișiere mai mari și poate reduce receptivitatea aplicației.", - "image_thumbnail_title": "Setari miniaturi", - "import_config_from_json_description": "Importați configurația sistemului încărcând un fișier de configurare JSON", - "job_concurrency": "Concurență {job}", - "job_created": "Sarcină creată", - "job_not_concurrency_safe": "Această sarcină nu este sigură pentru a rula în concurență.", - "job_settings": "Setări sarcină", - "job_settings_description": "Gestionează sarcinile paralele", - "jobs_delayed": "{jobCount, plural, other {# întârziat}}", - "jobs_failed": "{jobCount, plural, other {# eșuat}}", - "jobs_over_time": "Sarcini de-a lungul timpului", - "library_created": "Librărie creată: {library}", - "library_deleted": "Bibliotecă ștearsă", - "library_details": "Detalii bibliotecă", - "library_folder_description": "Specifică un folder pentru importare. Acest folder, incluzând subfolderele, va fi scanat pentru imagini si video-uri.", - "library_remove_exclusion_pattern_prompt": "Sunteți sigur că doriți să eliminați acest model de excludere?", - "library_remove_folder_prompt": "Sunteți sigur că doriți să ștergeți acest folder de import?", - "library_scanning": "Scanare periodică", - "library_scanning_description": "Configurează scanarea periodică pentru bibliotecă", - "library_scanning_enable_description": "Activează scanarea periodică pentru bibliotecă", - "library_settings": "Bibliotecă externă", - "library_settings_description": "Administrează setările pentru biblioteci externe", - "library_tasks_description": "Scanează bibliotecile externe de active noi sau modificate", - "library_updated": "Bibliotecă actualizată", - "library_watching_enable_description": "Urmărește bibliotecile externe pentru schimbări ale fișierelor", - "library_watching_settings": "Urmărirea bibliotecii (EXPERIMENTAL)", - "library_watching_settings_description": "Urmărește automat fișierele schimbate", - "logging_enable_description": "Activează înregistrarea log-urilor", - "logging_level_description": "Dacă setarea este activată, înregistrează evenimentele cu nivelul de utilizat.", - "logging_settings": "Înregistrare", - "machine_learning_availability_checks": "Verificări disponibilitate", - "machine_learning_availability_checks_description": "Detectează automat si preferă serverele cu învațare automată", - "machine_learning_availability_checks_enabled": "Activează verificare disponibilitate", - "machine_learning_availability_checks_interval": "Interval verificare", - "machine_learning_availability_checks_interval_description": "Interval in milisecunde între verificările de disponibilitate", - "machine_learning_availability_checks_timeout": "Timp de expirare cerere", - "machine_learning_availability_checks_timeout_description": "Timp de așteptare în milisecunde pentru verificările de disponibilitate", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Numele unui model CLIP listat aici. Rețineți că trebuie să rulați din nou funcția „Smart Search” pentru toate imaginile la schimbarea unui model.", - "machine_learning_duplicate_detection": "Detectare duplicate", - "machine_learning_duplicate_detection_enabled": "Activează detectarea duplicatelor", - "machine_learning_duplicate_detection_enabled_description": "Dacă este dezactivată, elementele identice vor fi în continuare de-duplicate.", - "machine_learning_duplicate_detection_setting_description": "Utilizați încorporările CLIP pentru a găsi dubluri probabile", - "machine_learning_enabled": "Activează algoritmii de învățare automată", - "machine_learning_enabled_description": "Dacă este dezactivat, toate funcțiile ML vor fi dezactivate indiferent de setările de mai jos.", - "machine_learning_facial_recognition": "Recunoaștere Facială", - "machine_learning_facial_recognition_description": "Detectează, recunoaște și grupează fețe din imagini", - "machine_learning_facial_recognition_model": "Model de recunoaștere facială", - "machine_learning_facial_recognition_model_description": "Modelele sunt aranjate descrescător după mărime. Modelele mai mari sunt lente și folosesc multă memorie, dar produc rezultate mai bune. Rețineți că va trebui să rulați din nou recunoașterea facială pentru toate imaginile dacă schimbați modelul.", - "machine_learning_facial_recognition_setting": "Activează recunoașterea facială", - "machine_learning_facial_recognition_setting_description": "Dacă este dezactivată, imaginile nu vor fi codificate pentru recunoașterea facială și nu vor popula secțiunea persoane din pagina explorare.", - "machine_learning_max_detection_distance": "Distanța maximă pentru recunoaștere", - "machine_learning_max_detection_distance_description": "Distanța maximă dintre două imagini pentru a le considera duplicate, variind între 0,001-0,1. Valorile mai mari vor detecta mai multe duplicate, dar pot duce la rezultate fals pozitive.", - "machine_learning_max_recognition_distance": "Distanța maximă de recunoaștere", - "machine_learning_max_recognition_distance_description": "Distanța maximă dintre două fețe pentru a fi considerate aceeași persoană, variind între 0-2. Reducerea acestui prag poate împiedica etichetarea a două persoane ca fiind aceeași persoană, în timp ce creșterea lui poate împiedica etichetarea aceleiași persoane ca fiind două persoane diferite. Rețineți că este mai ușor să unificați două persoane decât să împărțiți o persoană în două, deci, dacă este posibil, alegeți un prag mai mic.", - "machine_learning_min_detection_score": "Scor minim de detecție", - "machine_learning_min_detection_score_description": "Scorul minim de încredere pentru ca o față să fie detectată de la 0 la 1. Valorile mai mici vor detecta mai multe fețe, dar pot duce la fals pozitive.", - "machine_learning_min_recognized_faces": "Fețe minim recunoscute", - "machine_learning_min_recognized_faces_description": "Numărul minim de fețe recunoscute pentru ca o persoană să fie creată. Creșterea acestui număr face ca recunoașterea facială să fie mai precisă, cu prețul creșterii șanselor ca o față să nu fie atribuită unei persoane.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Recunoașterea textului în imagini folosind învățarea automată", - "machine_learning_ocr_enabled": "Activează OCR", - "machine_learning_ocr_enabled_description": "Dacă este dezactivat, imaginile nu vor fi supuse procesului de recunoaștere a textului.", - "machine_learning_ocr_max_resolution": "Rezoluție maximă", - "machine_learning_ocr_max_resolution_description": "Previzualizările imaginilor care depășesc această rezoluție vor fi redimensionate, păstrând proporțiile. Valorile mai mari oferă o acuratețe mai bună, dar necesită mai mult timp pentru procesare și mai multă memorie.", - "machine_learning_ocr_min_detection_score": "Scor minim de detecție", - "machine_learning_ocr_min_detection_score_description": "Scor minim de încredere pentru detectarea textului, între 0 și 1. Valorile mai mici vor detecta mai mult text, dar pot genera rezultate fals pozitive.", - "machine_learning_ocr_min_recognition_score": "Scor minim de recunoaștere", - "machine_learning_ocr_min_score_recognition_description": "Scor minim de încredere pentru recunoașterea textului detectat, între 0 și 1. Valorile mai mici vor recunoaște mai mult text, dar pot produce rezultate de fals pozitiv.", - "machine_learning_ocr_model": "Model OCR", - "machine_learning_ocr_model_description": "Modelele de server sunt mai precise decât modelele mobile, dar necesită mai mult timp pentru procesare și folosesc mai multă memorie.", - "machine_learning_settings": "Setări de învățare automată", - "machine_learning_settings_description": "Gestionați caracteristicile și setările de învățare automată", - "machine_learning_smart_search": "Căutare inteligentă", - "machine_learning_smart_search_description": "Căutarea semantică a imaginilor utilizând încorporările CLIP", - "machine_learning_smart_search_enabled": "Activează căutarea inteligentă", - "machine_learning_smart_search_enabled_description": "Dacă este dezactivată, imaginile nu vor fi codificate pentru căutarea inteligentă.", - "machine_learning_url_description": "URL-ul serverului de învățare automată. Dacă sunt furnizate mai multe URL-uri, fiecare server va fi încercat pe rând, până când unul răspunde cu succes, în ordine de la primul până la ultimul. Serverele care nu răspund vor fi ignorate temporar până revin online.", - "maintenance_delete_backup": "Sterge Backup", - "maintenance_delete_backup_description": "Acest fisier va fi sters permanent.", - "maintenance_delete_error": "Stergerea backup-ului nu a reusit.", - "maintenance_restore_backup": "Restaureaza Backup", - "maintenance_restore_backup_description": "Immich va fi șters si restaurat din backup-ul ales. Va fi creat un nou backup înainte de a continua.", - "maintenance_restore_backup_different_version": "Acest backup a fost creat folosind o versiune diferita de Immich!", - "maintenance_restore_backup_unknown_version": "Versiunea de backup nu a putut fi determinată.", - "maintenance_restore_database_backup": "Restaurează baza de date din backup", - "maintenance_restore_database_backup_description": "Restaureaza la o bază de date precedentă folosind un fisier backup", - "maintenance_settings": "Întreținere", - "maintenance_settings_description": "Puneți Immich în modul de întreținere.", - "maintenance_start": "Schimbă la modul de întreținere", - "maintenance_start_error": "Nu s-a putut porni modul de întreținere.", - "maintenance_upload_backup": "Încarcă fișier backup pentru baza de date", - "maintenance_upload_backup_error": "Nu s-a putut încărca backupul, e un fișier .sql/.sql.gz?", - "manage_concurrency": "Gestionează sarcinile paralele", - "manage_concurrency_description": "Accesează pagina de joburi pentru a gestiona concurența lor", - "manage_log_settings": "Administrați setările jurnalului", - "map_dark_style": "Mod întunecat", - "map_enable_description": "Activează funcțiile hărții", - "map_gps_settings": "Setări hartă & GPS", - "map_gps_settings_description": "Gestionare setări Hartă & GPS (localizare inversă)", - "map_implications": "Caracteristica hărții se bazează pe un serviciu extern de planșe (tiles.immich.cloud)", - "map_light_style": "Mod luminos", - "map_manage_reverse_geocoding_settings": "Gestionare setări Localizare Inversă", - "map_reverse_geocoding": "Localizare inversă", - "map_reverse_geocoding_enable_description": "Activați geocodarea inversă", - "map_reverse_geocoding_settings": "Setări geocodare inversă", - "map_settings": "Hartă", - "map_settings_description": "Gestionare setări hartă", - "map_style_description": "URL-ul style.json către o temă pentru hartă", - "memory_cleanup_job": "Curăță memoria", - "memory_generate_job": "Generare memorie", - "metadata_extraction_job": "Extrageți metadatele", - "metadata_extraction_job_description": "Extragere informații metadate din fiecare fișier cum ar fi localizare GPS, fețe și rezoluție,", - "metadata_faces_import_setting": "Activare import fețe", - "metadata_faces_import_setting_description": "Importă fețe din datele EXIF ale imaginii și din fișiere tip \"sidecar\"", - "metadata_settings": "Setări metadate", - "metadata_settings_description": "Gestionează setările pentru metadate", - "migration_job": "Migrare", - "migration_job_description": "Migrați miniaturile pentru elemente și fețe la cea mai recentă structură de foldere", - "nightly_tasks_cluster_faces_setting_description": "Rulează recunoașterea facială pe fețele noi recunoscute", - "nightly_tasks_cluster_new_faces_setting": "Grupează fetele noi", - "nightly_tasks_database_cleanup_setting": "Sarcini curățare baze de date", - "nightly_tasks_database_cleanup_setting_description": "Curată date vechi, expirate din baza de date", - "nightly_tasks_generate_memories_setting": "Generare memorii", - "nightly_tasks_generate_memories_setting_description": "Creează amintiri noi din resurse", - "nightly_tasks_missing_thumbnails_setting": "Generează miniaturi lipsă", - "nightly_tasks_missing_thumbnails_setting_description": "Pune în coadă elementele fără miniaturi pentru generarea miniaturilor", - "nightly_tasks_settings": "Setări pentru sarcinile nocturne", - "nightly_tasks_settings_description": "Gestionați sarcinile nocturne", - "nightly_tasks_start_time_setting": "Ora de începere", - "nightly_tasks_start_time_setting_description": "Ora la care serverul începe să execute sarcinile nocturne", - "nightly_tasks_sync_quota_usage_setting": "Utilizarea cotei de sincronizare", - "nightly_tasks_sync_quota_usage_setting_description": "Actualizați cota de stocare a utilizatorului, în funcție de utilizarea actuală", - "no_paths_added": "Nicio cale adăugată", - "no_pattern_added": "Niciun tipar adăugat", - "note_apply_storage_label_previous_assets": "Notă: Pentru a aplica Eticheta de Stocare la elementele încărcate anterior, executați", - "note_cannot_be_changed_later": "NOTĂ: Nu se va mai putea modifica ulterior!", - "notification_email_from_address": "De la adresa", - "notification_email_from_address_description": "Adresa expeditorului, spre exemplu: „Immich Photo Server ”. Asigură-te că folosești o adresă de la care ai permisiunea de a trimite e-mailuri.", - "notification_email_host_description": "Adresa serverului de email (ex. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ingnoră erorile de certificat", - "notification_email_ignore_certificate_errors_description": "Ignoră erorile de validare a certificatului TLS (nerecomandat)", - "notification_email_password_description": "Parola utilizată pentru autentificarea în serverul de email", - "notification_email_port_description": "Portul utilizat de serverul de email (ex. 25, 465 sau 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Folosește SMTPS (SMTP prin TLS)", - "notification_email_sent_test_email_button": "Trimite un email de test și salvează configurația", - "notification_email_setting_description": "Setări pentru trimiterea de notificări pe email", - "notification_email_test_email": "Trimitere email de test", - "notification_email_test_email_failed": "Eroare la trimiterea emailului de test, verificați setările", - "notification_email_test_email_sent": "Un email de test a fost trimis la adresa {email}. Vă rugăm să vă verificați căsuța de e-mail.", - "notification_email_username_description": "Numele de utilizator pentru autentificarea pe serverul de email", - "notification_enable_email_notifications": "Activare notificări pe email", - "notification_settings": "Setări notificare", - "notification_settings_description": "Gestionează setările pentru notificări, inclusiv adresa de email", - "oauth_auto_launch": "Pornire automată", - "oauth_auto_launch_description": "Lansează automat autorizarea OAuth la accesarea paginii de login", - "oauth_auto_register": "Auto înregistrare", - "oauth_auto_register_description": "Înregistrează automat utilizatori noi după autentificarea cu OAuth", - "oauth_button_text": "Text buton", - "oauth_client_secret_description": "Necesar dacă PKCE (Proof Key for Code Exchange) nu este suportat de furnizorul OAuth", - "oauth_enable_description": "Autentifică-te cu OAuth", - "oauth_mobile_redirect_uri": "URI de redirecționare mobilă", - "oauth_mobile_redirect_uri_override": "Înlocuire URI de redirecționare mobilă", - "oauth_mobile_redirect_uri_override_description": "Activați atunci când furnizorul OAuth nu permite un URI mobil, precum ''{callback}''", - "oauth_role_claim": "Revendicare de rol", - "oauth_role_claim_description": "Acordă automat acces de administrator în funcție de prezența acestei revendicări. Revendicarea poate avea fie 'utilizator', fie 'administrator'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Gestionați setările de conectare OAuth", - "oauth_settings_more_details": "Pentru mai multe detalii despre aceastǎ funcționalitate, verificǎ documentația.", - "oauth_storage_label_claim": "Revendicare eticheta de stocare", - "oauth_storage_label_claim_description": "Setați automat eticheta de stocare a utilizatorului la valoarea acestei revendicări.", - "oauth_storage_quota_claim": "Revendicare spațiu de stocare", - "oauth_storage_quota_claim_description": "Setează automat spațiul de stocare al utilizatorului la valoarea acestei cereri.", - "oauth_storage_quota_default": "Cota implicită a spațiului de stocare (GiB)", - "oauth_storage_quota_default_description": "Spațiul în GiB ce urmează a fi utilizat atunci când nu este furnizată nicio solicitare.", - "oauth_timeout": "Solicitarea a expirat", - "oauth_timeout_description": "Timp de expirare pentru solicitări în milisecunde", - "ocr_job_description": "Folosește învățarea automată pentru recunoașterea textului din imagini", - "password_enable_description": "Autentificare cu email și parolǎ", - "password_settings": "Autentificare cu Parolǎ", - "password_settings_description": "Gestioneazǎ setǎrile de autentificare cu parola", - "paths_validated_successfully": "Toate cǎile au fost validate cu succes", - "person_cleanup_job": "Ștergere persoane", - "queue_details": "Detalii coadă", - "queues": "Cozi de joburi", - "queues_page_description": "Pagina cu cozi de sarcini administrative", - "quota_size_gib": "Spațiu de stocare alocat (GiB)", - "refreshing_all_libraries": "Bibliotecile sunt în curs de reîmprospǎtare", - "registration": "Înregistrare Administratori", - "registration_description": "Deoarece sunteți primul utilizator de pe sistem, veți fi desemnat ca administrator și sunteți responsabil pentru sarcinile administrative, iar utilizatorii suplimentari vor fi creați de dumneavoastră.", - "remove_failed_jobs": "Elimina sarcinile eșuate", - "require_password_change_on_login": "Obligǎ utilizatorul sǎ își schimbe parola la prima autentificare", - "reset_settings_to_default": "Reseteazǎ setǎrile la valorile implicite", - "reset_settings_to_recent_saved": "Reseteazǎ setǎrile la valorile salvate recent", - "scanning_library": "Se scanează biblioteca", - "search_jobs": "Caută job-uri…", - "send_welcome_email": "Trimite email de bun-venit", - "server_external_domain_settings": "Domeniu extern", - "server_external_domain_settings_description": "Domeniu pentru distribuire publicǎ a scurtǎturilor, incluzând http(s)://", - "server_public_users": "Utilizatori publici", - "server_public_users_description": "Toți utilizatorii (nume și e-mail) sunt listați atunci când adăugați un utilizator la albumele partajate. Când este dezactivată, lista de utilizatori va fi disponibilă numai pentru utilizatorii admin.", - "server_settings": "Setǎri Server", - "server_settings_description": "Gestioneazǎ setǎrile serverului", - "server_stats_page_description": "Pagina cu statistici ale serverului de administrare", - "server_welcome_message": "Mesaj de bun-venit", - "server_welcome_message_description": "Un mesaj ce este afișat pe pagina de autentificare.", - "settings_page_description": "Pagina setări administrator", - "sidecar_job": "Metadate sidecar", - "sidecar_job_description": "Descoperirea sau sincronizarea metadatelor sidecar din sistemul de fișiere", - "slideshow_duration_description": "Numǎrul de secunde pentru afișarea fiecǎrei imagini", - "smart_search_job_description": "Rulați învățarea automată pe elemente pentru a ajuta căutarea inteligentă", - "storage_template_date_time_description": "Momentul creării elementului este utilizat pentru informațiile privind data și ora", - "storage_template_date_time_sample": "Eșantion de timp {date}", - "storage_template_enable_description": "Activați motorul de șabloane de stocare", - "storage_template_hash_verification_enabled": "Verificarea hash este activată", - "storage_template_hash_verification_enabled_description": "Activează verificarea hash, nu o dezactivați decât dacă sunteți sigur de implicații", - "storage_template_migration": "Migrarea șablonului de stocare", - "storage_template_migration_description": "Aplicați {template} actual la elementele încărcate anterior", - "storage_template_migration_info": "Șablonul de stocare va converti extensiile in litere mici. Modificările șablonului se vor aplica doar materialelor noi. Pentru a aplica retroactiv șablonul la materialele încărcate anterior, rulați {job}.", - "storage_template_migration_job": "Sarcină Migrare Șablon Stocare", - "storage_template_more_details": "Pentru mai multe detalii despre aceasta caracteristică, accesați Șablon stocare si implicațiile", - "storage_template_onboarding_description_v2": "Când este activată, această funcție va organiza automat fișierele pe baza șablonului definit de către utilizator. Pentru mai multe informații, accesează documentația.", - "storage_template_path_length": "Limita de lungime pentru calea aproximativă: {length, number}/{limit, number}", - "storage_template_settings": "Șablon stocare", - "storage_template_settings_description": "Gestionează structura folderelor și numele fișierelor pentru elementele încărcate", - "storage_template_user_label": "{label} este eticheta de stocare a utilizatorului", - "system_settings": "Setǎri de Sistem", - "tag_cleanup_job": "Curățare etichete", - "template_email_available_tags": "Puteți utiliza următoarele variabile în șablonul dvs.: {tags}", - "template_email_if_empty": "Dacă șablonul este gol, va fi folosit e-mailul implicit.", - "template_email_invite_album": "Șablon de Album de Invitație", - "template_email_preview": "Previzualizare", - "template_email_settings": "Șabloane de E-mail", - "template_email_update_album": "Actualizați Șablonul de Album", - "template_email_welcome": "Șablon de e-mail de bun venit", - "template_settings": "Șabloane de Notificare", - "template_settings_description": "Gestionați șabloanele personalizate pentru notificări", - "theme_custom_css_settings": "CSS personalizat", - "theme_custom_css_settings_description": "Foile de stil în cascadă (CSS) permit personalizarea designului Immich.", - "theme_settings": "Setări temă", - "theme_settings_description": "Gestionează personalizarea interfeței web Immich", - "thumbnail_generation_job": "Generare miniaturi", - "thumbnail_generation_job_description": "Generează miniaturi mari, mici și estompate pentru fiecare resursă, precum și miniaturi pentru fiecare persoană", - "transcoding_acceleration_api": "API de accelerare", - "transcoding_acceleration_api_description": "API-ul care va interacționa cu dispozitivul tău pentru a accelera transcodarea. Această setare este 'cel mai bun efort': va reveni la transcodarea software în caz de eșec. VP9 poate funcționa sau nu, în funcție de hardware-ul tău.", - "transcoding_acceleration_nvenc": "NVENC (necesitǎ GPU NVIDIA)", - "transcoding_acceleration_qsv": "Sincronizare Rapidă (necesitǎ CPU Intel de generația a 7-a sau mai mare)", - "transcoding_acceleration_rkmpp": "RKMPP (doar pe SOC-uri Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codecuri audio acceptate", - "transcoding_accepted_audio_codecs_description": "Selectează care codecuri audio nu trebuie să fie transcodificate. Se utilizează doar pentru anumite politici de transcodare.", - "transcoding_accepted_containers": "Containere acceptate", - "transcoding_accepted_containers_description": "Selectează formatele de containere care nu trebuie să fie remixate în MP4. Se utilizează doar pentru anumite politici de transcodare.", - "transcoding_accepted_video_codecs": "Codecuri video acceptate", - "transcoding_accepted_video_codecs_description": "Selectează codecurile video care nu trebuie să fie transcodificate. Se utilizează doar pentru anumite politici de transcodare.", - "transcoding_advanced_options_description": "Opțiuni pe care majoritatea utilizatorilor nu ar trebui să fie necesar să le schimbe", - "transcoding_audio_codec": "Codec audio", - "transcoding_audio_codec_description": "Opus este opțiunea cu cea mai bună calitate, dar are o compatibilitate mai scăzută cu dispozitivele sau software-ul mai vechi.", - "transcoding_bitrate_description": "Videoclipuri cu rata de biți mai mare decât maximul acceptat sau care nu sunt într-un format acceptat", - "transcoding_codecs_learn_more": "Pentru a afla mai multe despre terminologia folosită aici, consultă documentația FFmpeg pentru codec-ul H.264, codec-ul HEVC și codec-ul VP9.", - "transcoding_constant_quality_mode": "Mod de calitate constantă", - "transcoding_constant_quality_mode_description": "ICQ este mai bun decât CQP, dar unele dispozitive de accelerare hardware nu suportă acest mod. Setarea acestei opțiuni va prefera modul specificat atunci când folosești codificarea bazată pe calitate. Ignorat de NVENC deoarece nu suportă ICQ.", - "transcoding_constant_rate_factor": "Factor de rată constantă (-crf)", - "transcoding_constant_rate_factor_description": "Nivelul de calitate al videoclipului. Valorile tipice sunt 23 pentru H.264, 28 pentru HEVC, 31 pentru VP9 și 35 pentru AV1. Cu cât valoarea este mai mică, cu atât calitatea este mai bună, dar se generează fișiere mai mari.", - "transcoding_disabled_description": "Nu transcodifică niciun videoclip; acest lucru poate afecta redarea pe anumite dispozitive", - "transcoding_encoding_options": "Opțiuni codificare", - "transcoding_encoding_options_description": "Setează codecuri , calitatea, rezoluția și alte opțiuni pentru videoclipuri codificare", - "transcoding_hardware_acceleration": "Accelerare hardware", - "transcoding_hardware_acceleration_description": "Experimental: transcodare mai rapidă, dar poate reduce calitatea la aceeași rată de biți", - "transcoding_hardware_decoding": "Decodare hardware", - "transcoding_hardware_decoding_setting_description": "Se aplică doar pentru NVENC, QSV și RKMPP. Activează accelerarea completă în loc de doar accelerarea codificării. S-ar putea să nu funcționeze pentru toate videoclipurile.", - "transcoding_max_b_frames": "Număr maxim de cadre B", - "transcoding_max_b_frames_description": "Valorile mai mari îmbunătățesc eficiența compresiei, dar încetinesc codarea. Este posibil să nu fie compatibile cu accelerarea hardware pe dispozitivele mai vechi. 0 dezactivează cadrele B, în timp ce -1 setează această valoare automat.", - "transcoding_max_bitrate": "Rata de biți maximă", - "transcoding_max_bitrate_description": "Setarea unei rate maxime de biți poate face dimensiunile fișierelor mai previzibile, cu un cost minor asupra calității. La 720p, valorile tipice sunt 2600 kbit/s pentru VP9 sau HEVC, sau 4500 kbit/s pentru H.264. Dezactivat dacă este setat la 0. Cand o unitate nu este specificata, k (pentru kbit/s) este asumat.In acest caz 5000,5000k si 5M (pentru Mbit/s) sunt echivalente.", - "transcoding_max_keyframe_interval": "Interval maxim între cadre cheie", - "transcoding_max_keyframe_interval_description": "Setează distanța maximă între cadrele cheie. Valorile mai mici reduc eficiența compresiei, dar îmbunătățesc timpii de căutare și pot îmbunătăți calitatea în scenele cu mișcare rapidă. 0 setează această valoare automat.", - "transcoding_optimal_description": "Videoclipuri cu rezoluție mai mare decât cea țintă sau care nu sunt într-un format acceptat", - "transcoding_policy": "Politică de transcodare", - "transcoding_policy_description": "Setează când un video va fi transcodat", - "transcoding_preferred_hardware_device": "Dispozitiv hardware preferat", - "transcoding_preferred_hardware_device_description": "Se aplică doar la VAAPI și QSV. Setează nodul DRI utilizat pentru transcodarea hardware.", - "transcoding_preset_preset": "Presetare (-preset)", - "transcoding_preset_preset_description": "Viteza de compresie. Presetările mai lente produc fișiere mai mici și îmbunătățesc calitatea atunci când vizezi o anumită rată de biți. VP9 ignoră vitezele de compresie mai mari decât 'mai rapid'.", - "transcoding_reference_frames": "Cadre de referință", - "transcoding_reference_frames_description": "Numărul de cadre de referință atunci când se comprimă un cadru dat. Valorile mai mari îmbunătățesc eficiența compresiei, dar încetinesc codarea. 0 setează această valoare automat.", - "transcoding_required_description": "Numai videoclipuri care nu sunt într-un format acceptat", - "transcoding_settings": "Setări de transcodare video", - "transcoding_settings_description": "Gestionează care videoclipuri să transcodam și cum să le procesam", - "transcoding_target_resolution": "Rezoluția țintă", - "transcoding_target_resolution_description": "Rezoluțiile mai mari pot păstra mai multe detalii, dar necesită mai mult timp pentru codare, au dimensiuni mai mari ale fișierelor și pot reduce răspunsul aplicației.", - "transcoding_temporal_aq": "AQ temporal", - "transcoding_temporal_aq_description": "Se aplică doar la NVENC.Cuantificarea adaptivă temporală imbunătățește calitatea scenelor cu detalii mari și mișcare redusă. Poate să nu fie compatibil cu dispozitivele mai vechi.", - "transcoding_threads": "Fire", - "transcoding_threads_description": "Valorile mai mari conduc la o codare mai rapidă, dar lasă mai puțin spațiu serverului pentru a procesa alte sarcini în timp ce este activ. Această valoare nu ar trebui să fie mai mare decât numărul de nuclee CPU. Maximizați utilizarea dacă este setat la 0.", - "transcoding_tone_mapping": "Mapare tonuri", - "transcoding_tone_mapping_description": "Încearcă să păstreze aspectul videoclipurilor HDR atunci când sunt convertite în SDR. Fiecare algoritm face compromisuri diferite pentru culoare, detalii și strălucire. Hable păstrează detaliile, Mobius păstrează culoarea, iar Reinhard păstrează strălucirea.", - "transcoding_transcode_policy": "Politica de transcodare", - "transcoding_transcode_policy_description": "Politica pentru momentul când un videoclip ar trebui să fie transcodificat. Videoclipurile HDR vor fi întotdeauna transcodificate (cu excepția cazului în care transcodarea este dezactivată).", - "transcoding_two_pass_encoding": "Codare în doi pași", - "transcoding_two_pass_encoding_setting_description": "Transcodificare în două treceri pentru a produce videoclipuri codificate mai bine. Când rata maximă de biți este activată (necesară pentru a funcționa cu H.264 și HEVC), acest mod utilizează un interval de rată de biți bazat pe rata maximă de biți și ignoră CRF. Pentru VP9, CRF poate fi utilizat dacă rata maximă de biți este dezactivată.", - "transcoding_video_codec": "Codec video", - "transcoding_video_codec_description": "VP9 are eficiențǎ mare și compatibilitate web, însǎ transcodarea este de duratǎ mai mare. HEVC se comportǎ asemǎnǎtor, însǎ are compatibilitate web mai micǎ. H.264 este foarte compatibil și rapid în transcodare, însǎ genereazǎ fișiere mult mai mari. AV1 este cel mai eficient codec dar nu este compatibil cu dispozitivele mai vechi.", - "trash_enabled_description": "Activează funcțiile Coșului de Gunoi", - "trash_number_of_days": "Numǎr de zile", - "trash_number_of_days_description": "Numǎr de zile pentru pǎstrarea fișierelor în coșul de gunoi pânǎ la ștergerea permanentǎ", - "trash_settings": "Setǎri Coș de Gunoi", - "trash_settings_description": "Gestioneazǎ setǎrile coșului de gunoi", - "unlink_all_oauth_accounts": "Deconectează toate conturile OAuth", - "unlink_all_oauth_accounts_description": "Nu uita să deconectezi toate conturile OAuth înainte de a migra la un nou furnizor.", - "unlink_all_oauth_accounts_prompt": "Ești sigur că vrei să deconectezi toate conturile OAuth? Aceasta va reseta ID-ul OAuth pentru fiecare utilizator și nu poate fi anulată.", - "user_cleanup_job": "Curățare utilizator", - "user_delete_delay": "Contul și resursele utilizatorului {user} vor fi programate pentru ștergere permanentă în {delay, plural, one {# zi} other {# zile}}.", - "user_delete_delay_settings": "Întârziere la ștergere", - "user_delete_delay_settings_description": "Numărul de zile după eliminare până la ștergerea permanentă a contului și a resurselor unui utilizator. Procesul de ștergere a utilizatorului rulează la miezul nopții pentru a verifica utilizatorii care sunt pregătiți pentru ștergere. Modificările aduse acestei setări vor fi evaluate la următoarea execuție.", - "user_delete_immediately": "Contul și resursele utilizatorului {user} vor fi puse în coadă pentru ștergere permanentă imediat.", - "user_delete_immediately_checkbox": "Pune utilizatorul și resursele în coadă pentru ștergere imediată", - "user_details": "Detalii utilizator", - "user_management": "Gestionarea utilizatorilor", - "user_password_has_been_reset": "Parola utilizatorului a fost resetată:", - "user_password_reset_description": "Vă rugăm să furnizați utilizatorului parola temporară și să îi informați că va trebui să o schimbe la următoarea autentificare.", - "user_restore_description": "Contul utilizatorului {user} va fi restaurat.", - "user_restore_scheduled_removal": "Restaurare utilizator - ștergere programată pe {date, date, long}", - "user_settings": "Setǎri utilizator", - "user_settings_description": "Gestioneazǎ setǎrile utilizatorului", - "user_successfully_removed": "Utilizatorul {email} a fost șters cu succes.", - "users_page_description": "Pagina utilizatorilor administratori", - "version_check_enabled_description": "Activează verificarea versiunii", - "version_check_implications": "Funcția de verificare a versiunii se bazează pe comunicarea periodică cu github.com", - "version_check_settings": "Verificare versiune", - "version_check_settings_description": "Activeazǎ/dezactiveazǎ notificarea unei noi versiuni", - "video_conversion_job": "Transcodați videoclipuri", - "video_conversion_job_description": "Transcodați videoclipurile pentru o compatibilitate mai mare cu browserele și dispozitivele" - }, - "admin_email": "E-mail administrator", - "admin_password": "Parolă administrator", - "administration": "Administrare", - "advanced": "Avansat", - "advanced_settings_clear_image_cache": "Șterge cache-ul", - "advanced_settings_clear_image_cache_error": "Ștergerea cache-ului de imagini a eșuat", - "advanced_settings_clear_image_cache_success": "{size} șterși cu succes", - "advanced_settings_enable_alternate_media_filter_subtitle": "Utilizați această opțiune pentru a filtra conținutul media în timpul sincronizării pe baza unor criterii alternative. Încercați numai dacă întâmpinați probleme cu aplicația la detectarea tuturor albumelor.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizați filtrul alternativ de sincronizare a albumelor de pe dispozitiv", - "advanced_settings_log_level_title": "Nivel log: {level}", - "advanced_settings_prefer_remote_subtitle": "Unele dispozitive încarcă extrem de lent miniaturile din resursele locale. Activați această setare pentru a încărca imagini la distanță.", - "advanced_settings_prefer_remote_title": "Preferă fotografii la distanță", - "advanced_settings_proxy_headers_subtitle": "Definește antetele proxy pe care Immich ar trebui să le trimită cu fiecare solicitare de rețea", - "advanced_settings_proxy_headers_title": "Headere proxy personalizate [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Activează modul doar-citire, în care fotografiile pot fi doar vizualizate, iar acțiuni precum selectarea mai multor imagini, partajarea, redarea pe alt dispozitiv sau ștergerea sunt dezactivate. Activează/Dezactivează modul doar-citire din avatarul utilizatorului de pe ecranul principal", - "advanced_settings_readonly_mode_title": "Mod doar citire", - "advanced_settings_self_signed_ssl_subtitle": "Omite verificare certificate SSL pentru distinația server-ului, necesar pentru certificate auto-semnate.", - "advanced_settings_self_signed_ssl_title": "Permite certificate SSL auto-semnate [EXPERIMENTAL]", - "advanced_settings_sync_remote_deletions_subtitle": "Ștergeți sau restaurați automat un element de pe acest dispozitiv atunci când acțiunea este efectuată pe web", - "advanced_settings_sync_remote_deletions_title": "Sincronizează stergerile efectuate la distanță [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Setări avansate pentru utilizator", - "advanced_settings_troubleshooting_subtitle": "Activează funcționalități suplimentare pentru depanare", - "advanced_settings_troubleshooting_title": "Depanare", - "age_months": "Vârstă {months, plural, one {# lună} other {# luni}}", - "age_year_months": "Vârstă de 1 an, {months, plural, one {# lună} other {# luni}}", - "age_years": "{years, plural, other {Vârstă #}}", - "album": "Album", - "album_added": "Album adăugat", - "album_added_notification_setting_description": "Primiți o notificare prin e-mail când sunteți adăugat la un album partajat", - "album_cover_updated": "Coperta albumului a fost actualizată", - "album_delete_confirmation": "Ești sigur că vrei să ștergi albumul {album}?", - "album_delete_confirmation_description": "Dacă acest album este partajat, alți utilizatori nu vor mai putea accesa.", - "album_deleted": "Album șters", - "album_info_card_backup_album_excluded": "EXCLUSE", - "album_info_card_backup_album_included": "INCLUSE", - "album_info_updated": "Informații album actualizate", - "album_leave": "Părăsiți albumul?", - "album_leave_confirmation": "Sigur doriți să părăsiți {album}?", - "album_name": "Nume Album", - "album_options": "Opțiuni album", - "album_remove_user": "Eliminare utilizator?", - "album_remove_user_confirmation": "Ești sigur că dorești eliminarea {user}?", - "album_search_not_found": "Nu s-au găsit albume care să corespundă căutării dumneavoastră", - "album_selected": "Album selectat", - "album_share_no_users": "Se pare că ai partajat acest album cu toți utilizatorii sau nu ai niciun utilizator cu care să-l partajezi.", - "album_summary": "Rezumat album", - "album_updated": "Album actualizat", - "album_updated_setting_description": "Primiți o notificare prin e-mail când un album partajat are elemente noi", - "album_upload_assets": "Încarcă elemente din calculatorul personal și adaugă in album", - "album_user_left": "A părăsit {album}", - "album_user_removed": "{user} eliminat", - "album_viewer_appbar_delete_confirm": "Ești sigur că vrei să ștergi acest album din contul tău?", - "album_viewer_appbar_share_err_delete": "Ștergere album eșuată", - "album_viewer_appbar_share_err_leave": "Părăsire album eșuată", - "album_viewer_appbar_share_err_remove": "Probleme la ștergerea resurselor din album", - "album_viewer_appbar_share_err_title": "Schimbare titlu album eșuată", - "album_viewer_appbar_share_leave": "Părăsește album", - "album_viewer_appbar_share_to": "Distribuire către", - "album_viewer_page_share_add_users": "Adaugă utilizatori", - "album_with_link_access": "Permite oricui cu link-ul să vadă fotografiile și persoanele din acest album.", - "albums": "Albume", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albume}}", - "albums_default_sort_order": "Ordinea implicită de sortare a albumelor", - "albums_default_sort_order_description": "Ordinea inițială de sortare a pozelor la crearea de albume noi.", - "albums_feature_description": "Colecții de date care pot fi partajate cu alți utilizatori.", - "albums_on_device_count": "{count} albume pe dispozitiv", - "albums_selected": "{număra, plural, unul {# album selectat} altele {# albumuri selectate}}", - "all": "Toate", - "all_albums": "Toate albumele", - "all_people": "Toți oamenii", - "all_photos": "Toate fotografiile", - "all_videos": "Toate videoclipurile", - "allow_dark_mode": "Permite mod întunecat", - "allow_edits": "Permite editări", - "allow_public_user_to_download": "Permite utilizatorului public să descarce", - "allow_public_user_to_upload": "Permite utilizatorului public să încarce", - "allowed": "Permis", - "alt_text_qr_code": "Cod QR", - "always_keep": "Păstrează întotdeauna", - "always_keep_photos_hint": "Eliberează Spațiu va păstra toate fotografiile de pe acest dispozitiv.", - "always_keep_videos_hint": "Eliberează Spațiu va păstra toate video-urile de pe acest dispozitiv.", - "anti_clockwise": "În sens invers acelor de ceasornic", - "api_key": "Cheie API", - "api_key_description": "Această valoare va fi afișată o singură dată. Vă rugăm să vă asigurați că o copiați înainte de a închide fereastra.", - "api_key_empty": "Numele cheii API nu trebuie să fie gol", - "api_keys": "Chei API", - "app_architecture_variant": "Variantă (Arhitectură)", - "app_bar_signout_dialog_content": "Ești sigur că vrei să te deconectezi?", - "app_bar_signout_dialog_ok": "Da", - "app_bar_signout_dialog_title": "Deconectare", - "app_download_links": "Linkuri de descărcare în aplicație", - "app_settings": "Setări aplicație", - "app_stores": "Magazine de aplicații", - "app_update_available": "Actualizarea aplicației disponibilă", - "appears_in": "Apare în", - "apply_count": "Aplică ({count, number})", - "archive": "Arhivă", - "archive_action_prompt": "{count} adăugate la Arhivă", - "archive_or_unarchive_photo": "Arhiveazǎ sau dezarhiveazǎ fotografia", - "archive_page_no_archived_assets": "Nu au fost găsite resurse favorite", - "archive_page_title": "Arhivă ({count})", - "archive_size": "Mărime arhivă", - "archive_size_description": "Configurează dimensiunea arhivei pentru descărcări (în GiB)", - "archived": "Arhivat", - "archived_count": "{count, plural, one {Arhivat} few {# arhivate} other {# arhivate}}", - "are_these_the_same_person": "Sunt aceștia aceeași persoană?", - "are_you_sure_to_do_this": "Sunteți sigur că doriți să faceți acest lucru?", - "array_field_not_fully_supported": "Câmpurile necesită editare manuală JSON", - "asset_action_delete_err_read_only": "Fișierele cu permisiuni doar de citire nu au putut fi șterse, omitere", - "asset_action_share_err_offline": "Fișierele offline nu au putut accesate, omitere", - "asset_added_to_album": "Adăugat la album", - "asset_adding_to_album": "Se adaugă la album…", - "asset_created": "Resurse create", - "asset_description_updated": "Descrierea resursei a fost actualizată", - "asset_filename_is_offline": "Resursa {filename} este offline", - "asset_has_unassigned_faces": "Resursa are fețe neatribuite", - "asset_hashing": "Calculare amprentă digitală…", - "asset_list_group_by_sub_title": "Grupare după", - "asset_list_layout_settings_dynamic_layout_title": "Aspect dinamic", - "asset_list_layout_settings_group_automatically": "Automat", - "asset_list_layout_settings_group_by": "Grupează resurse după", - "asset_list_layout_settings_group_by_month_day": "Lună + zi", - "asset_list_layout_sub_title": "Aspect", - "asset_list_settings_subtitle": "Setări format grilă fotografii", - "asset_list_settings_title": "Grilă fotografii", - "asset_not_found_on_device_android": "Obiect negăsit pe dispozitiv", - "asset_not_found_on_device_ios": "Obiect negăsit pe dispozitiv.Dacă folosești iCloud, obiectul poate fi inaccesibil din cauza stocării incorecte pe iCloud", - "asset_not_found_on_icloud": "Obiect negăsit pe iCloud. Obiectul poate fi inaccesibil din cauza stocării incorecte pe iCloud", - "asset_offline": "Resursă Offline", - "asset_offline_description": "Această resursă externă nu mai este găsită pe disc. Contactează te rog administratorul tău Immich pentru ajutor.", - "asset_restored_successfully": "Date restaurate cu succes", - "asset_skipped": "Sărit", - "asset_skipped_in_trash": "În coșul de gunoi", - "asset_trashed": "Resursă ștearsă", - "asset_troubleshoot": "Depanare resursă", - "asset_uploaded": "Încărcat", - "asset_uploading": "Se incarcă…", - "asset_viewer_settings_subtitle": "Gestionați setările de vizualizare a galeriei", - "asset_viewer_settings_title": "Vizualizator resurse", - "assets": "Resurse", - "assets_added_count": "Adăugat {count, plural, one {# resursă} other {# resurse}}", - "assets_added_to_album_count": "Am adăugat {count, plural, one {# resursă} other {# resurse}} în album", - "assets_added_to_albums_count": "Au fost adăugate {assetTotal, plural, one {# element} other {# elemente}} la {albumTotal, plural, one {# album} other {# albume}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} nu pot fi adăugate în album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Elementul} other {Elementele}} nu poate fi adăugat la niciunul dintre albume", - "assets_count": "{count, plural, one {# resursă} other {# resurse}}", - "assets_deleted_permanently": "{count} poză/poze ștearsă/șterse permanent", - "assets_deleted_permanently_from_server": "{count} poză/poze ștearsă/șterse permanent din serverul Immich", - "assets_downloaded_failed": "{count, plural, one {S-a descărcat # fișier – {error} fișier eșuat} other {S-au descărcat # fișiere – {error} fișiere eșuate}}", - "assets_downloaded_successfully": "{count, plural, one {S-a descărcat cu succes # fișier} other {S-au descărcat cu succes # fișiere}}", - "assets_moved_to_trash_count": "Am mutat {count, plural, one {# resursă} other {# resurse}} în coșul de gunoi", - "assets_permanently_deleted_count": "Șters permanent {count, plural, one {# resursă} other {# resurse}}", - "assets_removed_count": "Eliminat {count, plural, one {# resursă} other {# resurse}}", - "assets_removed_permanently_from_device": "{count} resurse eliminate permanent din dispozitivul dvs.", - "assets_restore_confirmation": "Ești sigur că vrei să restaurezi toate resursele tale din coșul de gunoi? Nu poți anula această acțiune! Ține minte că resursele offline nu se restaurează astfel.", - "assets_restored_count": "Restaurat {count, plural, one {# resursă} other {# resurse}}", - "assets_restored_successfully": "{count} resursă(e) restaurate cu succes", - "assets_trashed": "{count} resursă(e) eliminate", - "assets_trashed_count": "Mutat în coșul de gunoi {count, plural, one {# resursă} other {# resurse}}", - "assets_trashed_from_server": "{count} resursă(e) eliminate de pe serverul Immich", - "assets_were_part_of_album_count": "{count, plural, one {Resursa era} other {Resursele erau}} deja parte din album", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} deja parte din albume", - "authorized_devices": "Dispozitive Autorizate", - "automatic_endpoint_switching_subtitle": "Conectează-te local prin rețeaua Wi‐Fi configurată când este valabilă și prin rețele alternative în caz contrar", - "automatic_endpoint_switching_title": "Alternare URL automată", - "autoplay_slideshow": "Derulare slideshow automat", - "back": "Înapoi", - "back_close_deselect": "Înapoi, închidere sau deselectare", - "background_backup_running_error": "Procesul de backup în fundal este activ, nu se poate porni backup manual", - "background_location_permission": "Permisiune locație în fundal", - "background_location_permission_content": "Pentru a putea schimba rețeaua activă în fundal, Immich are nevoie de acces *permanent* la locația precisă pentru a citi numele rețelei Wi-Fi", - "background_options": "Opțiuni de fundal", - "backup": "Backup", - "backup_album_selection_page_albums_device": "Albume în dispozitiv ({count})", - "backup_album_selection_page_albums_tap": "Apasă odata pentru a include, de două ori pentru a exclude", - "backup_album_selection_page_assets_scatter": "Resursele pot fi împrăștiate în mai multe albume. Prin urmare, albumele pot fi incluse sau excluse în timpul procesului de backup.", - "backup_album_selection_page_select_albums": "Selectează albume", - "backup_album_selection_page_selection_info": "Informații selecție", - "backup_album_selection_page_total_assets": "Total resurse unice", - "backup_albums_sync": "Sincronizarea albumelor de backup", - "backup_all": "Toate", - "backup_background_service_backup_failed_message": "Eșuare backup resurse. Reîncercare…", - "backup_background_service_complete_notification": "Backup resurse finalizat", - "backup_background_service_connection_failed_message": "Conectare la server eșuată. Reîncercare…", - "backup_background_service_current_upload_notification": "Încărcare {filename}", - "backup_background_service_default_notification": "Verificare resurse noi…", - "backup_background_service_error_title": "Eroare backup", - "backup_background_service_in_progress_notification": "Se face backup al resurselor tale…", - "backup_background_service_upload_failure_notification": "Încărcare eșuată {filename}", - "backup_controller_page_albums": "Backup albume", - "backup_controller_page_background_app_refresh_disabled_content": "Activează reîmprospătarea aplicației în fundal în Setări > General > Reîmprospătare aplicații în fundal pentru a folosi copia de siguranță în fundal.", - "backup_controller_page_background_app_refresh_disabled_title": "Reîmprospătarea aplicației în fundal este dezactivată", - "backup_controller_page_background_app_refresh_enable_button_text": "Mergi la setări", - "backup_controller_page_background_battery_info_link": "Arată-mi cum", - "backup_controller_page_background_battery_info_message": "Pentru cea mai bună experiență a backup-ului în fundal, te rugăm să dezactivezi orice optimizare pentru baterie care restricționează activitatea în fundal pentru Immich.\n\nDeoarece aceasta este specifică fiecărui dispozitiv, te rugăm verifică informațiile necesare tipului tău de dispozitiv.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Optimizări baterie", - "backup_controller_page_background_charging": "Doar în timpul încărcării", - "backup_controller_page_background_configure_error": "Configurare serviciu în fundal eșuată", - "backup_controller_page_background_delay": "Amânare copie de siguranță a resurselor noi: {duration}", - "backup_controller_page_background_description": "Activează backup-ul în fundal pentru a încărca resursele automat pe server fără a fi nevoie să deschizi aplicația", - "backup_controller_page_background_is_off": "Backup-ul automat în fundal este dezactivat", - "backup_controller_page_background_is_on": "Backup-ul automat în fundal este activat", - "backup_controller_page_background_turn_off": "Dezactivează serviciul în fundal", - "backup_controller_page_background_turn_on": "Activează serviciul în fundal", - "backup_controller_page_background_wifi": "Numai prin Wi-Fi", - "backup_controller_page_backup": "Backup", - "backup_controller_page_backup_selected": "Selectat(e): ", - "backup_controller_page_backup_sub": "S-a făcut backup pentru fotografii și videoclipuri", - "backup_controller_page_created": "Creat la: {date}", - "backup_controller_page_desc_backup": "Activează backup-ul în prim-plan pentru a încărca resursele pe server când deschizi aplicația.", - "backup_controller_page_excluded": "Exclus(e): ", - "backup_controller_page_failed": "Eșuate ({count})", - "backup_controller_page_filename": "Nume fișier: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informații backup", - "backup_controller_page_none_selected": "Nici o selecție", - "backup_controller_page_remainder": "Rămas(e)", - "backup_controller_page_remainder_sub": "Fotografii și videoclipuri din selecție rămase pentru backup", - "backup_controller_page_server_storage": "Stocare server", - "backup_controller_page_start_backup": "Începe copia de rezervă", - "backup_controller_page_status_off": "Backup-ul automat în prim-plan este oprit", - "backup_controller_page_status_on": "Backup-ul automat în prim-plan este pornit", - "backup_controller_page_storage_format": "{used} din {total} folosit", - "backup_controller_page_to_backup": "Albume pentru backup", - "backup_controller_page_total_sub": "Toate fotografiile și videoclipurile unice din albumele selectate", - "backup_controller_page_turn_off": "Dezactivează backup-ul în prim-plan", - "backup_controller_page_turn_on": "Activează backup-ul în prim-plan", - "backup_controller_page_uploading_file_info": "Informații încărcare fișier", - "backup_err_only_album": "Nu poți șterge singurul album", - "backup_error_sync_failed": "Sincronizarea a eșuat. Nu se poate procesa copia de rezervă.", - "backup_info_card_assets": "resurse", - "backup_manual_cancelled": "Anulat", - "backup_manual_in_progress": "Încărcarea este deja în curs. Încearcă din nou mai târziu", - "backup_manual_success": "Succes", - "backup_manual_title": "Status încărcare", - "backup_options": "Opțiuni copie de rezervă", - "backup_options_page_title": "Opțiuni copie de rezervă", - "backup_setting_subtitle": "Schimbă opțiuni pentru backup în prim-plan și în fundal", - "backup_settings_subtitle": "Gestionați setările de încărcare", - "backup_upload_details_page_more_details": "Apasa pentru mai multe detalii", - "backward": "În sens invers", - "biometric_auth_enabled": "Autentificare biometrică activată", - "biometric_locked_out": "Sunteți blocați de la autentificare biometrică", - "biometric_no_options": "Nu sunt disponibile opțiuni biometrice", - "biometric_not_available": "Autentificarea biometrică nu este disponibilă pe acest dispozitiv", - "birthdate_saved": "Data nașterii salvată cu succes", - "birthdate_set_description": "Data nașterii este utilizată pentru a calcula vârsta acestei persoane la momentul realizării fotografiei.", - "blurred_background": "Fundal neclar", - "bugs_and_feature_requests": "Erori și solicitări de caracteristici", - "build": "Versiunea", - "build_image": "Versiune Imagine", - "bulk_delete_duplicates_confirmation": "Ești sigur că vrei să ștergi în masă {count, plural, one {# resursă duplicată} other {# resurse duplicate}}? Aceasta va păstra cea mai mare resursă din fiecare grup și va șterge permanent toate celelalte duplicate. Nu poți anula această acțiune!", - "bulk_keep_duplicates_confirmation": "Ești sigur că vrei să păstrezi {count, plural, one {# resursă duplicată} other {# resurse duplicate}}? Aceasta va rezolva toate grupurile duplicate fără a șterge nimic.", - "bulk_trash_duplicates_confirmation": "Ești sigur că vrei să muți în coșul de gunoi {count, plural, one {# resursă duplicată} other {# resurse duplicate}}? Aceasta va păstra cea mai mare resursă din fiecare grup și va muta în coșul de gunoi toate celelalte duplicate.", - "buy": "Achiziționați Immich", - "cache_settings_clear_cache_button": "Șterge cache", - "cache_settings_clear_cache_button_title": "Șterge memoria cache a aplicatiei. Performanța aplicației va fi semnificativ afectată până când va fi reconstruită.", - "cache_settings_duplicated_assets_clear_button": "ȘTERGE", - "cache_settings_duplicated_assets_subtitle": "Fotografii și videoclipuri ignorate în lista aplicației", - "cache_settings_duplicated_assets_title": "Resurse duplicate ({count})", - "cache_settings_statistics_album": "Miniaturi pentru librării", - "cache_settings_statistics_full": "Fotografii complete", - "cache_settings_statistics_shared": "Miniaturi pentru albumele distribuite", - "cache_settings_statistics_thumbnail": "Miniaturi", - "cache_settings_statistics_title": "Memorie cache utilizată", - "cache_settings_subtitle": "Controlează modul de cache al aplicației Immich", - "cache_settings_tile_subtitle": "Controlează modul stocării locale", - "cache_settings_tile_title": "Stocare locală", - "cache_settings_title": "Setări pentru memoria cache", - "camera": "Camerǎ", - "camera_brand": "Marcǎ cameră", - "camera_model": "Model cameră", - "cancel": "Anuleaza", - "cancel_search": "Anuleaza căutarea", - "canceled": "Anulat", - "canceling": "În curs de anulare", - "cannot_merge_people": "Nu se pot îmbina persoanele", - "cannot_undo_this_action": "Nu puteți anula această acțiune!", - "cannot_update_the_description": "Nu se poate actualiza descrierea", - "cast": "Partajare", - "cast_description": "Configurați destinațiile de difuzare disponibile", - "change_date": "Schimbați data", - "change_description": "Schimbă descrierea", - "change_display_order": "Schimbați ordinea de afișare", - "change_expiration_time": "Schimbați data expirare", - "change_location": "Schimbați locația", - "change_name": "Schimbați nume", - "change_name_successfully": "Schimbare a numelui făcută cu succes", - "change_password": "Schimbați parolă", - "change_password_description": "Aceasta este fie prima dată când te conectezi în sistem, fie s-a făcut o solicitare pentru a schimba parola ta. Te rog să introduci noua parolă mai jos.", - "change_password_form_confirm_password": "Confirmă parola", - "change_password_form_description": "Salut {name},\n\nAceasta este fie prima dată când te conectazi la sistem, fie s-a făcut o cerere pentru schimbarea parolei. Te rugăm să introduci noua parolă mai jos.", - "change_password_form_log_out": "Deconectează toate celelalte dispozitive", - "change_password_form_log_out_description": "Se recomandă deconectarea de pe toate celelalte dispozitive", - "change_password_form_new_password": "Parolă nouă", - "change_password_form_password_mismatch": "Parolele nu se potrivesc", - "change_password_form_reenter_new_password": "Reintrodu noua parolă", - "change_pin_code": "Schimbă codul PIN", - "change_trigger": "mecanism de schimbare", - "change_trigger_prompt": "Ești sigur ca vrei sa schimbi mecanismul? Aceasta va șterge toate actiunile și filtrele existente.", - "change_your_password": "Schimbă-ți parola", - "changed_visibility_successfully": "Schimbare vizibilitate cu succes", - "charging": "Încărcare", - "charging_requirement_mobile_backup": "Pentru copia de rezervă în fundal, dispozitivul trebuie să fie în curs de încărcare", - "check_corrupt_asset_backup": "Verifică copii de rezervă a resurselor corupte", - "check_corrupt_asset_backup_button": "Efectuează verificarea", - "check_corrupt_asset_backup_description": "Rulează această verificare doar prin Wi-Fi și doar după ce toate resursele au fost salvate în copia de rezerva. Procedura poate dura câteva minute.", - "check_logs": "Verificați Jurnale", - "checksum": "Suma de control", - "choose_matching_people_to_merge": "Alegeți persoanele care se potrivesc pentru a le fuziona", - "city": "Oraș", - "cleanup_confirm_description": "Immich a găsit {count} materiale (create înainte de {date}) salvate în siguranță pe server. Eliminați copiile locale de pe acest dispozitiv?", - "cleanup_confirm_prompt_title": "Elimina de pe dispozitiv?", - "cleanup_deleted_assets": "Muta {count} materiale in coșul de gunoi", - "cleanup_deleting": "Se șterge...", - "cleanup_found_assets": "Am găsit {count} materiale in copia de rezerva", - "cleanup_found_assets_with_size": "{count} obiecte găsite ({size})", - "cleanup_icloud_shared_albums_excluded": "Albumele partajate iCLoud sunt excluse de la cautare", - "cleanup_no_assets_found": "Nici un material in copia de rezerva găsit după criteriu", - "cleanup_preview_title": "Materiale sa fie șterse ({count})", - "cleanup_step3_description": "Scanați pentru fotografii și videoclipuri pentru care au fost făcute copii de rezervă pe server cu data limită selectată și opțiunile de filtrare", - "cleanup_step4_summary": "{count} elemente create înainte de {date} sunt puse în coadă pentru a fi eliminate de pe dispozitiv", - "cleanup_trash_hint": "Pentru a recupera complet spațiu de stocare, deschideți aplicația Galerie și goliți coșul de gunoi", - "clear": "Curățați", - "clear_all": "Curățați tot", - "clear_all_recent_searches": "Curățați toate căutările recente", - "clear_file_cache": "Ștergeți memoria cache a fișierelor", - "clear_message": "Ștergeți mesajul", - "clear_value": "Ștergeți valoarea", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Introdu Parola", - "client_cert_import": "Importă", - "client_cert_import_success_msg": "Certificatul de client este importat", - "client_cert_invalid_msg": "Fisier cu certificat invalid sau parola este greșită", - "client_cert_remove_msg": "Certificatul de client este șters", - "client_cert_subtitle": "Este suportat doar formatul PKCS12 (.p12, .pfx). Importul/ștergerea certificatului este disponibil(ă) doar înainte de autentificare", - "client_cert_title": "Certificat SSL pentru client [EXPERIMENTAL]", - "clockwise": "În sensul acelor de ceas", - "close": "Închideți", - "collapse": "Restrângeți", - "collapse_all": "Restrângeți toate", - "color": "Culoare", - "color_theme": "Tema de culoare", - "command": "Comandă", - "comment_deleted": "Comentariu șters", - "comment_options": "Opțiuni comentariu", - "comments_and_likes": "Comentarii & aprecieri", - "comments_are_disabled": "Comentariile sunt dezactivate", - "common_create_new_album": "Creează album nou", - "completed": "Finalizat", - "confirm": "Confirmați", - "confirm_admin_password": "Confirmați Parola de Administrator", - "confirm_delete_face": "Ești sigur ca vrei sa ștergi {name} din activ?", - "confirm_delete_shared_link": "Sunteți sigur că doriți să ștergeți acest link partajat?", - "confirm_keep_this_delete_others": "Toate celelalte active din stivă vor fi șterse, cu excepția acestui material. Sunteți sigur că doriți să continuați?", - "confirm_new_pin_code": "Confirmă noul cod PIN", - "confirm_password": "Confirmați parola", - "confirm_tag_face": "Vrei să etichetezi această față ca {name}?", - "confirm_tag_face_unnamed": "Vrei să etichetezi această față?", - "connected_device": "Dispozitiv conectat", - "connected_to": "Conectat la", - "contain": "Încadrează", - "context": "Context", - "continue": "Continuați", - "control_bottom_app_bar_create_new_album": "Creează album nou", - "control_bottom_app_bar_delete_from_immich": "Șterge din Immich", - "control_bottom_app_bar_delete_from_local": "Șterge din dispozitiv", - "control_bottom_app_bar_edit_location": "Editează locație", - "control_bottom_app_bar_edit_time": "Editează data și ora", - "control_bottom_app_bar_share_link": "Partajează linkul", - "control_bottom_app_bar_share_to": "Distribuire către", - "control_bottom_app_bar_trash_from_immich": "Mută în coș", - "copied_image_to_clipboard": "Imagine copiată în clipboard.", - "copied_to_clipboard": "Copiat în clipboard!", - "copy_error": "Eroare de copiere", - "copy_file_path": "Copiați calea fișierului", - "copy_image": "Copiere imagine", - "copy_link": "Copiere link", - "copy_link_to_clipboard": "Copiere link în clipboard", - "copy_password": "Copiere parola", - "copy_to_clipboard": "Copiere în clipboard", - "country": "Țara", - "cover": "Umple fereastra", - "covers": "Acoperă", - "create": "Creează", - "create_album": "Creează album", - "create_album_page_untitled": "Fără nume", - "create_api_key": "Creează cheie API", - "create_first_workflow": "Creați primul flux de lucru", - "create_library": "Creează Bibliotecă", - "create_link": "Creează link", - "create_link_to_share": "Creează link pentru a distribui", - "create_link_to_share_description": "Permiteți oricui are link-ul să vadă fotografia (fotografiile) selectată(e)", - "create_new": "CREARE NOUĂ", - "create_new_person": "Creați o persoană nouă", - "create_new_person_hint": "Atribuiți resursele selectate unei persoane noi", - "create_new_user": "Creează utilizator nou", - "create_shared_album_page_share_add_assets": "ADAUGĂ RESURSE", - "create_shared_album_page_share_select_photos": "Selectează fotografii", - "create_shared_link": "Creați un link partajat", - "create_tag": "Creează etichetă", - "create_tag_description": "Creează o etichetă nouă. Pentru etichete imbricate, te rog să introduci calea completă a etichetei, inclusiv bare oblice (/).", - "create_user": "Creează utilizator", - "create_workflow": "Creați un flux de lucru", - "created": "Creat", - "created_at": "Creat", - "creating_linked_albums": "Crearea albumelor cu link...", - "crop": "Decupează", - "crop_aspect_ratio_fixed": "Reparat", - "crop_aspect_ratio_free": "Liber", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Obiecte", - "current_device": "Dispozitiv curent", - "current_pin_code": "Codul PIN actual", - "current_server_address": "Adresa actuală a serverului", - "custom_date": "Data personalizată", - "custom_locale": "Setare Regională Personalizată", - "custom_locale_description": "Formatați datele și numerele în funcție de limbă și regiune", - "custom_url": "URL personalizat", - "cutoff_date_description": "Eliminați fotografiile și videoclipurile mai vechi de", - "cutoff_day": "{count, plural, o {day} mai multe {days}}", - "cutoff_year": "{count, plural, =0 {0 ani} one {# an} few {# ani} other {# de ani}}", - "daily_title_text_date": "E, LLL zz", - "daily_title_text_date_year": "E, LLL zz, aaaa", - "dark": "Întunecat", - "dark_theme": "Comută tema întunecată", - "date": "Dată", - "date_after": "După data", - "date_and_time": "Dată și oră", - "date_before": "Anterior datei", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "Data nașterii salvată cu succes", - "date_range": "Interval de date", - "day": "Zi", - "days": "Zile", - "deduplicate_all": "Deduplicați Toate", - "deduplication_criteria_1": "Marimea imagini în octeți", - "deduplication_criteria_2": "Numărul de date EXIF", - "deduplication_info": "Informați despre deduplicare", - "deduplication_info_description": "Ca să preselecționăm activele și să scoatem duplicatele în vrac , ne uităm la:", - "default_locale": "Setare Regională Implicită", - "default_locale_description": "Formatați datele și numerele în funcție de regiunea browserului dvs", - "delete": "Ștergere", - "delete_action_confirmation_message": "Sigur vrei să ștergi acest element? Această acțiune va muta elementul în coșul de gunoi al serverului și te va întreba dacă vrei să-l ștergi local", - "delete_action_prompt": "{count} șterse", - "delete_album": "Ștergere album", - "delete_api_key_prompt": "Sunteți sigur că doriți să ștergeți această cheie API?", - "delete_dialog_alert": "Aceste elemente vor fi șterse permanent de pe server-ul Immich și din dispozitivul tău", - "delete_dialog_alert_local": "Aceste fișiere vor fi șterse permanent din dispozitiv, dar vor fi disponibile pe server-ul Immich", - "delete_dialog_alert_local_non_backed_up": "Pentru unele fișere nu s-a făcut backup în Immich și vor fi șterse permanent din dispozitiv", - "delete_dialog_alert_remote": "Aceste fișiere vor fi șterse permanent de pe server-ul Immich", - "delete_dialog_ok_force": "Șterge oricum", - "delete_dialog_title": "Șterge permanent", - "delete_duplicates_confirmation": "Sunteți sigur că doriți să ștergeți permanent aceste duplicate?", - "delete_face": "Șterge fața", - "delete_key": "Ștergere cheie", - "delete_library": "Ștergere biblioteca", - "delete_link": "Ștergere link", - "delete_local_action_prompt": "{count} șterse local", - "delete_local_dialog_ok_backed_up_only": "Șterge doar fișierele pentru care s-a făcut backup", - "delete_local_dialog_ok_force": "Șterge oricum", - "delete_others": "Ștergeți celelalte", - "delete_permanently": "Șterge permanent", - "delete_permanently_action_prompt": "{count} șterse permanent", - "delete_shared_link": "Ștergere link partajat", - "delete_shared_link_dialog_title": "Șterge link distribuire", - "delete_tag": "Ștergere etichetă", - "delete_tag_confirmation_prompt": "Ești sigur că vrei să ștergi eticheta {tagName} ?", - "delete_user": "Ștergere utilizator", - "deleted_shared_link": "Link partajat șters", - "deletes_missing_assets": "Ștergere resurse lipsă de pe disc", - "description": "Descriere", - "description_input_hint_text": "Adaugă descriere...", - "description_input_submit_error": "Eroare actualizare descriere, verifică log-urile pentru mai multe detalii", - "deselect_all": "Deselectează toate", - "details": "Detalii", - "direction": "Direcție", - "disable": "Dezactivare", - "disabled": "Dezactivat", - "disallow_edits": "Interzice modificările", - "discord": "Server Discord", - "discover": "Descoperiți", - "discovered_devices": "Dispozititve descoperite", - "dismiss_all_errors": "Ignorați toate erorile", - "dismiss_error": "Ignorați eroarea", - "display_options": "Opțiuni de afișare", - "display_order": "Ordine de afișare", - "display_original_photos": "Afișați fotografiile originale", - "display_original_photos_setting_description": "Preferă să afișezi fotografia originală atunci când vizualizezi o resursă, în loc de miniaturi, atunci când resursa originală este compatibilă cu web-ul. Aceasta poate duce la viteze mai lente de afișare a fotografiilor.", - "do_not_show_again": "Nu mai afișa acest mesaj", - "documentation": "Documentație", - "done": "Gata", - "download": "Descărcați", - "download_action_prompt": "Se descarcă {count} elemente", - "download_canceled": "Descărcare anulată", - "download_complete": "Descărcare completă", - "download_enqueue": "Descărcare în coadă", - "download_error": "Eroare de descărcare", - "download_failed": "Descărcare eșuată", - "download_finished": "Descărcare finalizată", - "download_include_embedded_motion_videos": "Videoclipuri încorporate", - "download_include_embedded_motion_videos_description": "Include videoclipurile încorporate în fotografiile în mișcare ca fișier separat", - "download_notfound": "Descărcare negăsită", - "download_original": "Descarcă originalul", - "download_paused": "Descărcarea a fost întreruptă", - "download_settings": "Descărcați", - "download_settings_description": "Gestionați setările legate de descărcarea resurselor", - "download_started": "Descărcarea a început", - "download_sucess": "Descărcare reușită", - "download_sucess_android": "Fișierul media a fost descărcat în DCIM/Immich", - "download_waiting_to_retry": "Se așteaptă o nouă încercare", - "downloading": "Se descarcă", - "downloading_asset_filename": "Se descarcă resursa {filename}", - "downloading_from_icloud": "Se descarcă din iCloud", - "downloading_media": "Se descarcă fișierele media", - "drop_files_to_upload": "Trageți fișierele aici pentru a le încărca", - "duplicates": "Duplicate", - "duplicates_description": "Rezolvați fiecare grup indicând care sunt duplicate, dacă există", - "duration": "Durată", - "edit": "Editare", - "edit_album": "Editare album", - "edit_avatar": "Editare avatar", - "edit_birthday": "Modifică ziua de naștere", - "edit_date": "Editare dată", - "edit_date_and_time": "Editare dată și oră", - "edit_date_and_time_action_prompt": "{count} data și ora modificării", - "edit_date_and_time_by_offset": "Schimbă data prin decalaj", - "edit_date_and_time_by_offset_interval": "Noul interval de date: {from} - {to}", - "edit_description": "Editează descrierea", - "edit_description_prompt": "Vă rugăm să selectați o descriere nouă:", - "edit_exclusion_pattern": "Editarea modelului de excludere", - "edit_faces": "Editare fețe", - "edit_key": "Tastă de editare", - "edit_link": "Editare link", - "edit_location": "Editare locație", - "edit_location_action_prompt": "{count} locație(i) modificată(e)", - "edit_location_dialog_title": "Locație", - "edit_name": "Editare nume", - "edit_people": "Editare persoane", - "edit_tag": "Editare etichetă", - "edit_title": "Editare Titlu", - "edit_user": "Editare utilizator", - "edit_workflow": "Modifică fluxul de lucru", - "editor": "Editor", - "editor_close_without_save_prompt": "Schimbările nu vor fi salvate", - "editor_close_without_save_title": "Închideți editorul?", - "editor_confirm_reset_all_changes": "Sigur vrei să resetezi toate modificările?", - "editor_flip_horizontal": "Întoarceți orizontal", - "editor_flip_vertical": "Întoarceți vertical", - "editor_orientation": "Orientare", - "editor_reset_all_changes": "Resetați modificările", - "editor_rotate_left": "Rotiți cu 90° în sens invers acelor de ceasornic", - "editor_rotate_right": "Rotiți cu 90° în sensul acelor de ceasornic", - "email": "Adresă de mail", - "email_notifications": "Notificări e-mail", - "empty_folder": "Acest dosar este gol", - "empty_trash": "Goliți coșul de gunoi", - "empty_trash_confirmation": "Sunteți sigur că doriți să goliți coșul de gunoi? Acest lucru va elimina definitiv din Immich toate resursele din coșul de gunoi.\nNu puteți anula această acțiune!", - "enable": "Permite", - "enable_backup": "Activează backup", - "enable_biometric_auth_description": "Introduceți codul PIN pentru a activa autentificarea biometrică", - "enabled": "Activat", - "end_date": "Data de încheiere", - "enqueued": "Pus în coadă", - "enter_wifi_name": "Introduceți numele rețelei Wi-Fi", - "enter_your_pin_code": "Introduceți codul PIN", - "enter_your_pin_code_subtitle": "Introduceți codul PIN pentru a accesa folderul blocat", - "error": "Eroare", - "error_change_sort_album": "Nu s-a putut modifica ordinea de sortare a albumului", - "error_delete_face": "Eroare la ștergerea feței din activ", - "error_getting_places": "Eroare la obținerea locațiilor", - "error_loading_albums": "Eroare la încărcarea albumelor", - "error_loading_image": "Eroare la încărcarea imaginii", - "error_loading_partners": "Eroare la încărcarea partenerilor: {error}", - "error_retrieving_asset_information": "Eroare la colectarea informațiilor obiectului", - "error_saving_image": "Eroare: {error}", - "error_tag_face_bounding_box": "Eroare la etichetarea feței - nu se pot obține coordonatele casetei de delimitare", - "error_title": "Eroare - ceva nu a mers", - "error_while_navigating": "Eroare la navigarea spre obiect", - "errors": { - "cannot_navigate_next_asset": "Nu se poate naviga către următoarea resursă", - "cannot_navigate_previous_asset": "Nu se poate naviga la resursa anterioară", - "cant_apply_changes": "Nu se pot aplica schimbări", - "cant_change_activity": "Nu se poate {enabled, select, true {dezactiva} other {activa}} activitatea", - "cant_change_asset_favorite": "Nu pot schimba favoritul pentru resursa", - "cant_change_metadata_assets_count": "Nu se pot modifica metadatele pentru {count, plural, one {# resursa} other {# resurse}}", - "cant_get_faces": "Nu pot obține fețe", - "cant_get_number_of_comments": "Nu pot obține numărul de comentarii", - "cant_search_people": "Nu pot căuta oameni", - "cant_search_places": "Nu se pot căuta locații", - "error_adding_assets_to_album": "Eroare la adăugarea resurselor la album", - "error_adding_users_to_album": "Eroare la adăugarea utilizatorilor la album", - "error_deleting_shared_user": "Eroare la ștergerea utilizatorului partajat", - "error_downloading": "Eroare la descărcarea {filename}", - "error_hiding_buy_button": "Eroare la ascunderea butonului de cumpărare", - "error_removing_assets_from_album": "Eroare la eliminarea resurselor din album, verificați consola pentru mai multe detalii", - "error_selecting_all_assets": "Eroare la selectarea tuturor resurselor", - "exclusion_pattern_already_exists": "Acest model de excludere există deja.", - "failed_to_create_album": "A eșuat crearea albumului", - "failed_to_create_shared_link": "A eșuat crearea legăturii partajate", - "failed_to_edit_shared_link": "A eșuat editarea legăturii partajate", - "failed_to_get_people": "Eșec la obținerea persoanelor", - "failed_to_keep_this_delete_others": "Nu s-a putut păstra acest material respectiv nu s-au putut șterge celelalte materiale", - "failed_to_load_asset": "Eșec la încărcarea resursei", - "failed_to_load_assets": "Eșec la încărcarea resurselor", - "failed_to_load_notifications": "Nu s-au putut încărca notificările", - "failed_to_load_people": "Eșec la încărcarea persoanelor", - "failed_to_remove_product_key": "Eșec la eliminarea cheii de produs", - "failed_to_reset_pin_code": "Nu s-a reușit resetarea codului PIN", - "failed_to_stack_assets": "Eșec la combinarea resurselor", - "failed_to_unstack_assets": "Eșec la desfășurarea resurselor", - "failed_to_update_notification_status": "Nu s-a putut actualiza starea notificării", - "incorrect_email_or_password": "E-mail sau parolă incorect/ă", - "library_folder_already_exists": "Această cale de import există deja.", - "paths_validation_failed": "{paths, plural, one {# cale} other {# căi}} nu a trecut validarea", - "profile_picture_transparent_pixels": "Pozele de profil nu pot avea pixeli transparenți. Te rugăm să mărești imaginea și/sau să o muți.", - "quota_higher_than_disk_size": "Ați stabilit o valoare a spațiului de stocare mai mare decât dimensiunea discului", - "something_went_wrong": "Ceva nu a mers bine", - "unable_to_add_album_users": "Imposibil de adăugat utilizatori în album", - "unable_to_add_assets_to_shared_link": "Imposibil de adăugat resurse la link-ul partajat", - "unable_to_add_comment": "Imposibil de adăugat comentariu", - "unable_to_add_exclusion_pattern": "Nu se poate adăuga modelul de excludere", - "unable_to_add_partners": "Nu se pot adăuga parteneri", - "unable_to_add_remove_archive": "Nu se poate {archived, select, true {îndepărta resursa din} other {adăuga resursa în}} arhivă", - "unable_to_add_remove_favorites": "Nu se poate {favorite, select, true {adăuga resursa în} other {îndepărta resursa din}} favorite", - "unable_to_archive_unarchive": "Nu se poate {archived, select, true {arhiva} other {dezarhiva}}", - "unable_to_change_album_user_role": "Nu se poate schimba rolul utilizatorului de album", - "unable_to_change_date": "Imposibil de schimbat data", - "unable_to_change_description": "Nu se poate schimba descrierea", - "unable_to_change_favorite": "Nu se pot modifica favoritele pentru resursa", - "unable_to_change_location": "Imposibil de schimbat locația", - "unable_to_change_password": "Imposibil de schimbat parola", - "unable_to_change_visibility": "Nu se poate schimba vizibilitatea pentru {count, plural, one {# persoană} other {# persoane}}", - "unable_to_complete_oauth_login": "Nu s-a realizat logarea prin OAuth", - "unable_to_connect": "Nu se poate conecta", - "unable_to_copy_to_clipboard": "Nu poate fi copiat, asigură-te că accesezi pagina prin https", - "unable_to_create": "Nu se poate crea fluxul de lucru", - "unable_to_create_admin_account": "Nu se poate crea contul de administrator", - "unable_to_create_api_key": "Nu se poate crea o nouă cheie API", - "unable_to_create_library": "Nu se poate crea biblioteca", - "unable_to_create_user": "Nu se poate crea userul", - "unable_to_delete_album": "Nu se poate șterge albumul", - "unable_to_delete_asset": "Nu poate fi ștearsă resursa", - "unable_to_delete_assets": "Eroare la ștergerea resurselor", - "unable_to_delete_exclusion_pattern": "Nu se poate șterge modelul de excludere", - "unable_to_delete_shared_link": "Nu se poate șterge linkul partajat", - "unable_to_delete_user": "Nu se poate șterge userul", - "unable_to_delete_workflow": "Nu se poate șterge fluxul de lucru", - "unable_to_download_files": "Nu se pot descărca fișierele", - "unable_to_edit_exclusion_pattern": "Nu se poate edita modelul de excludere", - "unable_to_empty_trash": "Nu se poate goli coșul de gunoi", - "unable_to_enter_fullscreen": "Nu se poate accesa ecranul complet", - "unable_to_exit_fullscreen": "Imposibil de părăsit ecranul complet", - "unable_to_get_comments_number": "Nu se poate obține numărul de comentarii", - "unable_to_get_shared_link": "Nu s-a putut obține linkul partajat", - "unable_to_hide_person": "Nu se poate ascunde persoana", - "unable_to_link_motion_video": "Imposibil de conectat videoclipul în mișcare", - "unable_to_link_oauth_account": "Nu se poate conecta contul OAuth", - "unable_to_log_out_all_devices": "Nu se pot deconecta toate dispozitivele", - "unable_to_log_out_device": "Nu se poate deconecta dispozitivul", - "unable_to_login_with_oauth": "Nu se poate autentifica cu OAuth", - "unable_to_play_video": "Nu se poate reda videoul", - "unable_to_reassign_assets_existing_person": "Nu se pot reatribui elementele către {name, select, null {o persoană existentă} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nu se pot reatribui resurse unei persoane noi", - "unable_to_refresh_user": "Nu se poate reîmprospăta utilizatorul", - "unable_to_remove_album_users": "Nu se pot șterge userii din album", - "unable_to_remove_api_key": "Nu se poate șterge cheia API", - "unable_to_remove_assets_from_shared_link": "Nu se pot elimina resursele din linkul partajat", - "unable_to_remove_library": "Nu se poate șterge biblioteca", - "unable_to_remove_partner": "Imposibil de eliminat partenerul", - "unable_to_remove_reaction": "Nu se poate elimina reacția", - "unable_to_reset_password": "Imposibil de resetat parola", - "unable_to_reset_pin_code": "Nu se poate reseta codul PIN", - "unable_to_resolve_duplicate": "Nu se poate rezolva duplicatul", - "unable_to_restore_assets": "Nu se pot restaura resursele", - "unable_to_restore_trash": "Nu se poate restaura coșul de gunoi", - "unable_to_restore_user": "Nu se poate restaura utilizatorul", - "unable_to_save_album": "Imposibil de salvat albumul", - "unable_to_save_api_key": "Imposibil de salvat cheia API", - "unable_to_save_date_of_birth": "Imposibil de salvat data de naștere", - "unable_to_save_name": "Imposibil de salvat numele", - "unable_to_save_profile": "Imposibil de salvat profilul", - "unable_to_save_settings": "Nu se pot salva setările", - "unable_to_scan_libraries": "Nu se pot scana librăriile", - "unable_to_scan_library": "Nu se poate scana librăria", - "unable_to_set_feature_photo": "Nu se poate seta fotografia principală", - "unable_to_set_profile_picture": "Nu se poate seta fotografia de profil", - "unable_to_set_rating": "Nu se poate seta evaluarea", - "unable_to_submit_job": "Imposibil de trimis sarcina", - "unable_to_trash_asset": "Nu se poate elimina resursa", - "unable_to_unlink_account": "Nu se poate deconecta contul", - "unable_to_unlink_motion_video": "Imposibil de deconectat videoclipul în mișcare", - "unable_to_update_album_cover": "Nu se poate actualiza coperta de album", - "unable_to_update_album_info": "Nu se pot actualiza informațiile albumului", - "unable_to_update_library": "Nu se poate actualiza biblioteca", - "unable_to_update_location": "Nu se poate actualiza locația", - "unable_to_update_settings": "Nu se pot actualiza setările", - "unable_to_update_timeline_display_status": "Nu se poate actualiza starea de afișare a cronologiei", - "unable_to_update_user": "Nu se poate actualiza utilizatorul", - "unable_to_update_workflow": "Nu se poate actualiza fluxul de lucru", - "unable_to_upload_file": "Nu se poate încărca fișierul" - }, - "errors_text": "Erori", - "exclusion_pattern": "Model de excludere", - "exif": "Format comutabil pentru fișiere imagine", - "exif_bottom_sheet_description": "Adaugă Descriere...", - "exif_bottom_sheet_description_error": "Eroare la actualizarea descrierii", - "exif_bottom_sheet_details": "DETALII", - "exif_bottom_sheet_location": "LOCAȚIE", - "exif_bottom_sheet_no_description": "Fără descriere", - "exif_bottom_sheet_people": "PERSOANE", - "exif_bottom_sheet_person_add_person": "Adaugă nume", - "exit_slideshow": "Ieșire din Prezentare", - "expand_all": "Extindeți-le pe toate", - "experimental_settings_new_asset_list_subtitle": "Acțiune în desfășurare", - "experimental_settings_new_asset_list_title": "Activează grila experimentală de fotografii", - "experimental_settings_subtitle": "Folosește pe propria răspundere!", - "experimental_settings_title": "Experimental", - "expire_after": "Expiră după", - "expired": "Expirat", - "expires_date": "Expiră la {date}", - "explore": "Exploreazǎ", - "explorer": "Explorator", - "export": "Exportare", - "export_as_json": "Exportare ca JSON", - "export_database": "Exportați baza de date", - "export_database_description": "Exportați baza de date SQLite", - "extension": "Extensie", - "external": "Extern", - "external_libraries": "Biblioteci externe", - "external_network": "Rețea externă", - "external_network_sheet_info": "Când nu se află în rețeaua Wi-Fi preferată, aplicația se va conecta la server prin prima dintre adresele URL de mai jos pe care o poate accesa, începând de sus în jos", - "face_unassigned": "Nealocat", - "failed": "Eșuat", - "failed_count": "Eșuat: {count}", - "failed_to_authenticate": "Autentificarea nu a reușit", - "failed_to_load_assets": "Nu s-au încărcat activele", - "failed_to_load_folder": "Nu s-a putut încărca folderul", - "favorite": "Favorit", - "favorite_action_prompt": "{count} adăugate la Favorite", - "favorite_or_unfavorite_photo": "Fotografie preferată sau nepreferată", - "favorites": "Favorite", - "favorites_page_no_favorites": "Nu au fost găsite resurse favorite", - "feature_photo_updated": "Fotografie caracteristică actualizată", - "features": "Caracteristici", - "features_in_development": "Funcții în dezvoltare", - "features_setting_description": "Gestionați funcțiile aplicației", - "file_name_or_extension": "Numele sau extensia fișierului", - "file_size": "Mărime fișier", - "filename": "Numele fișierului", - "filetype": "Tipul fișierului", - "filter": "Filtre", - "filter_description": "Condiții pentru filtrarea activelor țintă", - "filter_people": "Filtrați persoanele", - "filter_places": "Filtrează locurile", - "filters": "Filtre", - "find_them_fast": "Găsiți-le rapid prin căutare după nume", - "first": "Primul", - "fix_incorrect_match": "Remediați potrivirea incorectă", - "folder": "Dosar", - "folder_not_found": "Dosar negăsit", - "folders": "Fișiere", - "folders_feature_description": "Răsfoire în conținutul folderului pentru fotografiile și videoclipurile din sistemul de fișiere", - "forgot_pin_code_question": "Ai uitat codul PIN?", - "forward": "Redirecționare", - "free_up_space": "Eliberați spațiu", - "free_up_space_description": "Mută fotografiile și videoclipurile salvate în coșul de gunoi al dispozitivului pentru a elibera spațiu. Copiile tale de pe server rămân în siguranță", - "free_up_space_settings_subtitle": "Eliberați spațiul de stocare al dispozitivului", - "full_path": "Calea completă: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Această funcție încarcă resurse externe de la Google pentru a funcționa.", - "general": "General", - "geolocation_instruction_location": "Apasă pe o resursă cu coordonate GPS pentru a folosi locația sa, sau selectează direct o locație de pe hartă", - "get_help": "Obțineți Ajutor", - "get_people_error": "Eroare la obținerea datelor despre persoane", - "get_wifiname_error": "Nu s-a putut obține numele rețelei Wi-Fi. Asigurați-vă că ați acordat permisiunile necesare și că sunteți conectat la o rețea Wi-Fi", - "getting_started": "Noțiuni de Bază", - "go_back": "Întoarcere", - "go_to_folder": "Accesează folderul", - "go_to_search": "Spre căutare", - "gps": "GPS", - "gps_missing": "Fără GPS", - "grant_permission": "Acordați permisiunea", - "group_albums_by": "Grupați albume de...", - "group_country": "Grupare după țară", - "group_no": "Fără grupare", - "group_owner": "Grupați după proprietar", - "group_places_by": "Grupare locuri după...", - "group_year": "Grupați după an", - "haptic_feedback_switch": "Activează feedback-ul haptic", - "haptic_feedback_title": "Feedback haptic", - "has_quota": "Are spațiu de stocare", - "hash_asset": "Hash-ul resursei", - "hashed_assets": "Resurse hashed", - "hashing": "Generare hash", - "header_settings_add_header_tip": "Adaugă header", - "header_settings_field_validator_msg": "Valoarea nu poate fi goală", - "header_settings_header_name_input": "Numele antetului", - "header_settings_header_value_input": "Valoarea antetului", - "headers_settings_tile_title": "Header-uri proxy personalizate", - "height": "Înălțime", - "hi_user": "Bună {name} ({email})", - "hide_all_people": "Ascundeți toate persoanele", - "hide_gallery": "Ascundeți galeria", - "hide_named_person": "Ascundeți persoana {name}", - "hide_password": "Ascundeți parola", - "hide_person": "Ascundeți persoana", - "hide_schema": "Ascunde schema", - "hide_text_recognition": "Ascunde recunoașterea textului", - "hide_unnamed_people": "Ascundeți persoanele fără nume", - "home_page_add_to_album_conflicts": "Au fost adăugate {added} de resurse în albumul {album}. {failed} de resurse sunt deja adăugate în album.", - "home_page_add_to_album_err_local": "Resursele locale nu pot fi adăugate în albume încă, omitere", - "home_page_add_to_album_success": "Au fost adăugate {added} de resurse în albumul {album}.", - "home_page_album_err_partner": "Momentan nu se pot adăuga fișierele partenerului într-un album, omitere", - "home_page_archive_err_local": "Resursele locale nu s-au putut arhiva încă, omitere", - "home_page_archive_err_partner": "Nu se pot arhiva fișierele partenerului, omitere", - "home_page_building_timeline": "Construire cronologie", - "home_page_delete_err_partner": "Nu se pot șterge fișierele partenerului, omitere", - "home_page_delete_remote_err_local": "Resursele locale sunt în selecția pentru ștergere la distanță, omitere", - "home_page_favorite_err_local": "Resursele locale nu pot fi adăugate la favorite încă, omitere", - "home_page_favorite_err_partner": "Momentan nu se pot adăuga fișierele partenerului la favorite, omitere", - "home_page_first_time_notice": "Dacă este prima dată când utilizezi aplicația, te rugăm să te asiguri că alegi unul sau mai multe albume de backup, astfel încât cronologia să poată fi populată cu fotografiile și videoclipurile din aceste albume", - "home_page_locked_error_local": "Nu se pot muta resursele locale în folderul blocat, se omit", - "home_page_locked_error_partner": "Nu se pot muta resursele partenerului în folderul blocat, omit", - "home_page_share_err_local": "Nu se pot distribui fișiere locale prin link, omitere", - "home_page_upload_err_limit": "Se pot încărca maxim 30 de resurse odată, omitere", - "host": "Gazdă", - "hour": "Oră", - "hours": "Ore", - "id": "ID", - "idle": "Inactiv", - "ignore_icloud_photos": "Ignoră fotografiile din iCloud", - "ignore_icloud_photos_description": "Fotografiile stocate pe iCloud nu vor fi încărcate pe serverul Immich", - "image": "Imagine", - "image_alt_text_date": "{isVideo, select, true {Video} other {imagine}} preluată în {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {imagine}} preluată cu {person1} în {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {imagine}} preluată cu {person1} și {person2} în {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {imagine}} preluată cu {person1}, {person2}, și {person3} în {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {imagine}} preluată cu {person1}, {person2}, și {additionalCount, number} alții în {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {imagine}} preluată în {city}, {country} în {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {imagine}} preluată în {city}, {country} cu {person1} în {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {imagine}} preluată în {city}, {country} cu {person1} și {person2} în {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {imagine}} preluată în {city}, {country} cu {person1}, {person2}, și {person3} în {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {imagine}} preluată în {city}, {country} cu {person1}, {person2}, și {additionalCount, number} alții în {date}", - "image_saved_successfully": "Imaginea a fost salvată", - "image_viewer_page_state_provider_download_started": "Descărcare începută", - "image_viewer_page_state_provider_download_success": "Descărcare cu succes", - "image_viewer_page_state_provider_share_error": "Eroare distribuire", - "immich_logo": "Logo Immich", - "immich_web_interface": "Interfața Web Immich", - "import_from_json": "Importă din JSON", - "import_path": "Calea de import", - "in_albums": "În {count, plural, one {# album} other {# albume}}", - "in_archive": "În arhivă", - "in_year": "În {year}", - "in_year_selector": "În", - "include_archived": "Include resursele arhivate", - "include_shared_albums": "Include albumele partajate", - "include_shared_partner_assets": "Include resursele partenerilor partajați", - "individual_share": "Cota individuală", - "individual_shares": "Partajări individuale", - "info": "Informație", - "interval": { - "day_at_onepm": "În fiecare zi la ora 13.00", - "hours": "La fiecare {hours, plural, one {oră} other {{hours, number} ore}}", - "night_at_midnight": "În fiecare noapte la miezul nopții", - "night_at_twoam": "În fiecare noapte la 2 dimineața" - }, - "invalid_date": "Dată invalidă", - "invalid_date_format": "Format de dată invalid", - "invite_people": "Invitați Persoane", - "invite_to_album": "Invitați în album", - "ios_debug_info_fetch_ran_at": "Fetch a funcționat la {dateTime}", - "ios_debug_info_last_sync_at": "Ultima sincronizare {dateTime}", - "ios_debug_info_no_processes_queued": "Niciun proces în fundal pus în coadă", - "ios_debug_info_no_sync_yet": "Nicio sarcină de sincronizare în fundal nu a fost încă executată", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proces în fundal pus în coadă} other {{count} procese în fundal puse în coadă}}", - "ios_debug_info_processing_ran_at": "Procesarea a rulat {dateTime}", - "items_count": "{count, plural, one {# element} other{# elemente}}", - "jobs": "Sarcini", - "json_editor": "Editor JSON", - "json_error": "Eroare JSON", - "keep": "Păstrați", - "keep_albums": "Păstreaza albume", - "keep_albums_count": "Păstrez {count} {count, plural, one {album} few {albume} other {de albume}}", - "keep_all": "Păstrați Tot", - "keep_description": "Alege ce să rămână pe dispozitiv când eliberezi spațiu.", - "keep_favorites": "Păstrați favoritele", - "keep_on_device": "Păstrează pe dispozitiv", - "keep_on_device_hint": "Selectează ce să rămână pe dispozitiv", - "keep_this_delete_others": "Păstrați asta, ștergeți celelalte", - "keeping": "Păstrez: {items}", - "kept_this_deleted_others": "S-a păstrat acest material și s-au șters {count, plural, one {# material} other {# materiale}}", - "keyboard_shortcuts": "Comenzi rapide de tastatură", - "language": "Limbă", - "language_no_results_subtitle": "Încercați să ajustați termenul de căutare", - "language_no_results_title": "Nu au fost găsite limbi", - "language_search_hint": "Căutați limbi...", - "language_setting_description": "Selectați limba preferată", - "large_files": "Fișiere mari", - "last": "Ultimul", - "last_months": "{count, plural, one {luna trecută} other {ultimele # luni}}", - "last_seen": "Văzut ultima dată", - "latest_version": "Ultima Versiune", - "latitude": "Latitudine", - "leave": "Părăsiți", - "leave_album": "Părăsește albumul", - "lens_model": "Model obiectiv", - "let_others_respond": "Permite altora să răspundă", - "level": "Nivel", - "library": "Librărie", - "library_add_folder": "Adaugă folder", - "library_edit_folder": "Editați folderul", - "library_options": "Opțiuni de bibliotecă", - "library_page_device_albums": "Albume în dispozitiv", - "library_page_new_album": "Album nou", - "library_page_sort_asset_count": "Numărul resurselor", - "library_page_sort_created": "Data creării", - "library_page_sort_last_modified": "Ultima dată modificat", - "library_page_sort_title": "Titlu album", - "licenses": "Licențe", - "light": "Lumină", - "like": "Îmi place", - "like_deleted": "Preferat șters", - "link_motion_video": "Link video în mișcare", - "link_to_oauth": "Link către OAuth", - "linked_oauth_account": "Cont OAuth conectat", - "list": "Listă", - "loading": "Încărcare", - "loading_search_results_failed": "Încărcarea rezultatelor căutării nu a reușit", - "local": "Local", - "local_asset_cast_failed": "Nu se poate converti un element care nu este încărcat pe server", - "local_assets": "Asset-uri locale", - "local_id": "ID local", - "local_media_summary": "Rezumatul fișierelor media locale", - "local_network": "Rețea locală", - "local_network_sheet_info": "Aplicația se va conecta la server prin intermediul acestei adrese URL atunci când utilizează rețeaua Wi-Fi specificată", - "location": "Locație", - "location_permission": "Permisiunea de locație", - "location_permission_content": "Pentru a utiliza funcția de comutare automată, Immich are nevoie de permisiune pentru locația precisă, astfel încât să poată citi numele rețelei Wi-Fi curente", - "location_picker_choose_on_map": "Alege pe hartă", - "location_picker_latitude_error": "Introdu o latitudine validă", - "location_picker_latitude_hint": "Introdu latitudinea aici", - "location_picker_longitude_error": "Introdu o longitudine validă", - "location_picker_longitude_hint": "Introdu longitudinea aici", - "lock": "Blocare", - "locked_folder": "Dosar blocat", - "log_detail_title": "Detalii jurnal", - "log_out": "Deconectare", - "log_out_all_devices": "Deconectați-vă de la toate dispozitivele", - "logged_in_as": "Conectat ca {user}", - "logged_out_all_devices": "S-au deconectat toate dispozitivele", - "logged_out_device": "Dispozitiv deconectat", - "login": "Conectare", - "login_disabled": "Logarea a fost dezactivată", - "login_form_api_exception": "Excepție API. Te rugăm să verifici URL-ul server-ului și să încerci din nou.", - "login_form_back_button_text": "Înapoi", - "login_form_email_hint": "email-ultau@email.com", - "login_form_endpoint_hint": "http://ip-server:port", - "login_form_endpoint_url": "URL-ul destinației sever-ului", - "login_form_err_http": "Te rugăm specifică http:// sau https://", - "login_form_err_invalid_email": "Email invalid", - "login_form_err_invalid_url": "URL invalid", - "login_form_err_leading_whitespace": "Spațiu alb în față", - "login_form_err_trailing_whitespace": "Spațiu alb la sfârșit", - "login_form_failed_get_oauth_server_config": "Eroare la autentificare folosind OAuth, verifică URL-ul server-ului", - "login_form_failed_get_oauth_server_disable": "Funcția OAuth nu este disponibilă pe acest server", - "login_form_failed_login": "Eroare la autentificare, verifică URL-ul server-ului, adresa de email și parola", - "login_form_handshake_exception": "A apărut o excepție Handshake cu server-ul. Activează suportul pentru certificate auto-semnate în setări dacă folosești un certificat auto-semnat.", - "login_form_password_hint": "parolă", - "login_form_save_login": "Rămâi conectat", - "login_form_server_empty": "Introdu URL-ul server-ului.", - "login_form_server_error": "Nu s-a putut realiza conexiunea la server.", - "login_has_been_disabled": "Conectarea a fost dezactivată.", - "login_password_changed_error": "A intervenit o eroare la actualizarea parolei", - "login_password_changed_success": "Parola a fost actualizată cu succes", - "logout_all_device_confirmation": "Sigur doriți să deconectați toate dispozitivele?", - "logout_this_device_confirmation": "Sigur doriți să deconectați acest dispozitiv?", - "logs": "Jurnale", - "longitude": "Longitudine", - "look": "Examinare", - "loop_videos": "Buclă videoclipuri", - "loop_videos_description": "Activați pentru a rula in buclă automat un videoclip în vizualizatorul de detalii.", - "main_branch_warning": "Utilizați o versiune de dezvoltare; vă recomandăm insistent să utilizați o versiune de lansare!", - "main_menu": "Meniu principal", - "maintenance_action_restore": "Restaurare bază de date", - "maintenance_description": "Immich a fost pus în modul de întreținere.", - "maintenance_end": "Ieșire din modul de întreținere", - "maintenance_end_error": "Nu s-a reușit ieșirea din modul de întreținere.", - "maintenance_logged_in_as": "Conectat în prezent ca {user}", - "maintenance_restore_from_backup": "Restaurează din backup", - "maintenance_restore_library": "Restaurează-ți biblioteca", - "maintenance_restore_library_confirm": "Dacă pare corect, continuă spre a restaura un backup!", - "maintenance_restore_library_description": "Restaurare bază de date", - "maintenance_restore_library_folder_has_files": "{folder} are {count} {count, plural, one {fișier} few {fișiere} other {de fișiere}}", - "maintenance_restore_library_folder_no_files": "Lipsesc fișiere din {folder}!", - "maintenance_restore_library_folder_pass": "permite scrierea și citirea", - "maintenance_restore_library_folder_read_fail": "nu permite citirea", - "maintenance_restore_library_folder_write_fail": "nu permite scrierea", - "maintenance_restore_library_hint_missing_files": "Posibil să lipsească fișiere importante", - "maintenance_restore_library_hint_regenerate_later": "Poți regenera mai tarziu în setări", - "maintenance_restore_library_hint_storage_template_missing_files": "Folosesti șablonul de stocare? Posibil să-ți lipsească fișiere", - "maintenance_restore_library_loading": "Încarc verificările de integritate si euristice…", - "maintenance_task_backup": "Creez backupul bazei de date existente…", - "maintenance_task_migrations": "Rulez migrările bazei de date…", - "maintenance_task_restore": "Restaurez backupul ales…", - "maintenance_task_rollback": "Restaurarea a eșuat, întorc la punctul de restaurare…", - "maintenance_title": "Temporar indisponibil", - "make": "Marcă", - "manage_geolocation": "Gestionați locația", - "manage_media_access_rationale": "Această permisiune este necesară pentru gestionarea corespunzătoare a mutării activelor în coșul de gunoi și restaurarea acestora din acesta.", - "manage_media_access_settings": "Deschideți setările", - "manage_media_access_subtitle": "Permiteți aplicației Immich să gestioneze și să mute fișierele media.", - "manage_media_access_title": "Acces la gestionarea mediilor", - "manage_shared_links": "Administrați link-urile distribuite", - "manage_sharing_with_partners": "Gestionați partajarea cu partenerii", - "manage_the_app_settings": "Gestionați setările aplicației", - "manage_your_account": "Gestionați-vă contul", - "manage_your_api_keys": "Gestionați-vă cheile API", - "manage_your_devices": "Gestionați-vă dispozitivele conectate", - "manage_your_oauth_connection": "Gestionați-vă conexiunea OAuth", - "map": "Hartă", - "map_assets_in_bounds": "{count, plural, =0 {Nu există fotografii în această zonă} one {# fotografie} other {# fotografii}}", - "map_cannot_get_user_location": "Nu se poate obține locația utilizatorului", - "map_location_dialog_yes": "Da", - "map_location_picker_page_use_location": "Folosește această locație", - "map_location_service_disabled_content": "Serviciul de localizare trebuie să fie activat pentru a afișa resursele din locația actuală. Dorești să o activezi acum?", - "map_location_service_disabled_title": "Serviciul de localizare este dezactivat", - "map_marker_for_images": "Marcator de hartă pentru imaginile realizate în {city}, {country}", - "map_marker_with_image": "Marcator de hartă cu imagine", - "map_no_location_permission_content": "Permisiunea de localizare este necesară pentru a afișa resursele din locația actuală. Dorești să o activezi acum?", - "map_no_location_permission_title": "Permisiunea de localizare este dezactivată", - "map_settings": "Setările hărții", - "map_settings_dark_mode": "Mod întunecat", - "map_settings_date_range_option_day": "Ultimele 24 de ore", - "map_settings_date_range_option_days": "Ultimele {days} zile", - "map_settings_date_range_option_year": "Ultimul an", - "map_settings_date_range_option_years": "Ultimii {years} ani", - "map_settings_dialog_title": "Setările hărții", - "map_settings_include_show_archived": "Include resursele arhivate", - "map_settings_include_show_partners": "Includeți partenerii", - "map_settings_only_show_favorites": "Arată doar favorite", - "map_settings_theme_settings": "Stilul hărții", - "map_zoom_to_see_photos": "Zoom out pentru a vedea fotografii", - "mark_all_as_read": "Marchează toate ca citite", - "mark_as_read": "Marchează ca citit", - "marked_all_as_read": "Marcate toate ca citite", - "matches": "Corespunde", - "matching_assets": "Resurse similare", - "media_type": "Tip media", - "memories": "Amintiri", - "memories_all_caught_up": "Sunteți la zi", - "memories_check_back_tomorrow": "Reveniți mâine pentru mai multe amintiri", - "memories_setting_description": "Administrați ce vedeți în amintiri", - "memories_start_over": "Începeți de la început", - "memories_swipe_to_close": "Glisează în sus pentru a închide", - "memory": "Amintire", - "memory_lane_title": "Banda Memoriei {title}", - "menu": "Meniu", - "merge": "Îmbinați", - "merge_people": "Îmbinați persoane", - "merge_people_limit": "Puteți îmbina până la 5 fețe simultan", - "merge_people_prompt": "Vreți să îmbinați aceste persoane? Această acțiune este ireversibilă.", - "merge_people_successfully": "Persoane îmbinate cu succes", - "merged_people_count": "Imbinate {count, plural, one {# persoană} other {# persoane}}", - "minimize": "Minimizare", - "minute": "Minut", - "minutes": "Minute", - "mirror_horizontal": "Orizontal", - "mirror_vertical": "Vertical", - "missing": "Lipsă", - "mobile_app": "Aplicație Mobilă", - "mobile_app_download_onboarding_note": "Descarcă aplicația mobilă folosind următoarele opțiuni", - "model": "Model", - "month": "Lună", - "monthly_title_text_date_format": "LLLL a", - "more": "Mai mult", - "move": "Mută", - "move_down": "Mută în jos", - "move_off_locked_folder": "Mutați din folderul blocat", - "move_to": "Mutare la", - "move_to_device_trash": "Mutare în coșul de gunoi al dispozitivului", - "move_to_lock_folder_action_prompt": "{count} adăugate în dosarul blocat", - "move_to_locked_folder": "Mută în dosarul blocat", - "move_to_locked_folder_confirmation": "Aceste fotografii și videoclipuri vor fi eliminate din toate albumele și vor putea fi vizualizate doar din dosarul blocat", - "move_up": "Mută sus", - "moved_to_archive": "Au fost mutate {count, plural, one {# element} other {# elemente}} în arhivă", - "moved_to_library": "Au fost mutate {count, plural, one {# element} other {# elemente}} la bibliotecă", - "moved_to_trash": "Mutat în coșul de gunoi", - "multiselect_grid_edit_date_time_err_read_only": "Nu se poate edita data fișierului(lor) cu permisiuni doar pentru citire, omitere", - "multiselect_grid_edit_gps_err_read_only": "Nu se poate edita locația fișierului(lor) cu permisiuni doar pentru citire, omitere", - "mute_memories": "Amuțește amintirile", - "my_albums": "Albumele mele", - "name": "Nume", - "name_or_nickname": "Nume sau poreclǎ", - "name_required": "Numele este obligatoriu", - "navigate": "Navighează", - "navigate_to_time": "Navigheaza la Timp", - "network_requirement_photos_upload": "Utilizați datele mobile pentru a face copii de rezervă ale fotografiilor", - "network_requirement_videos_upload": "Utilizați datele mobile pentru a face copii de rezervă ale videoclipurilor", - "network_requirements": "Cerințe privind rețeaua", - "network_requirements_updated": "Cerințele rețelei s-au modificat, resetarea cozii copiei de rezervă", - "networking_settings": "Rețele", - "networking_subtitle": "Gestionați setările endpoint-ului serverului", - "never": "Niciodată", - "new_album": "Album Nou", - "new_api_key": "Cheie API nouǎ", - "new_date_range": "Interval de dată nou", - "new_password": "Parolă nouă", - "new_person": "Persoanǎ nouǎ", - "new_pin_code": "Cod PIN nou", - "new_pin_code_subtitle": "Aceasta este prima dată când accesați folderul blocat. Creați un cod PIN pentru a accesa în siguranță această pagină", - "new_timeline": "Noua cronologie", - "new_update": "Nouă actualizare", - "new_user_created": "Utilizator nou creat", - "new_version_available": "VERSIUNE NOUĂ DISPONIBILĂ", - "newest_first": "Cel mai nou primul", - "next": "Următorul", - "next_memory": "Următoarea amintire", - "no": "Nu", - "no_actions_added": "Nu s-au adăugat încă acțiuni", - "no_albums_found": "Niciun album găsit", - "no_albums_message": "Creați un album pentru a vă organiza fotografiile și videoclipurile", - "no_albums_with_name_yet": "Se pare că nu aveți încă niciun album cu acest nume.", - "no_albums_yet": "Se pare că nu aveți încă niciun album.", - "no_archived_assets_message": "Arhivați fotografii și videoclipuri pentru a le ascunde din vizualizarea fotografii", - "no_assets_message": "CLICK PENTRU A ÎNCĂRCA PRIMA TA FOTOGRAFIE", - "no_assets_to_show": "Nicio resursă de afișat", - "no_cast_devices_found": "Nu s-au găsit dispozitive de difuzare", - "no_checksum_local": "Nu există checksum – nu se pot prelua resursele locale", - "no_checksum_remote": "Nu există checksum – nu se pot prelua resursele la distanță", - "no_configuration_needed": "Nu este necesară nicio configurare", - "no_devices": "Nu există dispozitive autorizate", - "no_duplicates_found": "Nu au fost găsite duplicate.", - "no_exif_info_available": "Nu există informații exif disponibile", - "no_explore_results_message": "Încarcați mai multe fotografii pentru a vă explora colecția.", - "no_favorites_message": "Adaugă favorite pentru a găsi rapid cele mai bune fotografii și videoclipuri", - "no_filters_added": "Nu s-au adăugat încă filtre", - "no_libraries_message": "Creați o bibliotecă externă pentru a vă vizualiza fotografiile și videoclipurile", - "no_local_assets_found": "Nicio resursă locală găsită cu acest checksum", - "no_location_set": "Locație neconfigurată", - "no_locked_photos_message": "Fotografiile și videoclipurile din folderul blocat sunt ascunse și nu vor apărea atunci când răsfoiți sau căutați în bibliotecă.", - "no_name": "Fără Nume", - "no_notifications": "Nicio notificare", - "no_people_found": "Nu au fost găsite persoane potrivite căutării", - "no_places": "Nu există locuri", - "no_remote_assets_found": "Nicio resursă de la distanță găsită cu acest checksum", - "no_results": "Fără rezultate", - "no_results_description": "Încercați un sinonim sau un cuvânt cheie mai general", - "no_shared_albums_message": "Creați un album pentru a partaja fotografii și videoclipuri cu persoanele din rețeaua dvs", - "no_uploads_in_progress": "Nicio încărcare în curs", - "none": "Niciunul", - "not_allowed": "Nu este permis", - "not_available": "N/A", - "not_in_any_album": "Nu există în niciun album", - "not_selected": "Neselectat", - "note_apply_storage_label_to_previously_uploaded assets": "Notă: Pentru a aplica eticheta de stocare la resursele încărcate anterior, rulați", - "notes": "Note", - "nothing_here_yet": "Nimic aici încă", - "notification_permission_dialog_content": "Pentru a activa notificările, mergi în Setări > Immich și selectează permite.", - "notification_permission_list_tile_content": "Acordă permisiunea pentru a activa notificările.", - "notification_permission_list_tile_enable_button": "Activează notificările", - "notification_permission_list_tile_title": "Permisiuni de notificare", - "notification_toggle_setting_description": "Activați notificările prin email", - "notifications": "Notificări", - "notifications_setting_description": "Gestionați notificările", - "oauth": "OAuth", - "obtainium_configurator": "Configurator Obtainium", - "obtainium_configurator_instructions": "Folosește Obtainium pentru a instala și actualiza aplicația Android direct din release-urile Immich de pe GitHub. Creează o cheie API și selectează o variantă pentru a genera linkul de configurare Obtainium", - "ocr": "OCR", - "official_immich_resources": "Resurse Oficiale Immich", - "offline": "Offline", - "offset": "Decalaj", - "ok": "Bine", - "oldest_first": "Cel mai vechi mai întâi", - "on_this_device": "Pe acest dispozitiv", - "onboarding": "Integrare", - "onboarding_locale_description": "Selectați limba preferată. Puteți schimba această opțiune ulterior în setări.", - "onboarding_privacy_description": "Următoarele caracteristici (opționale) se bazează pe servicii externe și pot fi dezactivate în orice moment din setări.", - "onboarding_server_welcome_description": "Hai să configurăm instanța cu câteva setări comune.", - "onboarding_theme_description": "Alegeți o temă de culoare pentru exemplul dvs. Puteți modifica acest lucru mai târziu în setări.", - "onboarding_user_welcome_description": "Hai să începem!", - "onboarding_welcome_user": "Bun venit, {user}", - "online": "Online", - "only_favorites": "Doar favorite", - "open": "Deschide", - "open_in_map_view": "Deschideți în vizualizarea hărții", - "open_in_openstreetmap": "Deschideți în OpenStreetMap", - "open_the_search_filters": "Deschideți filtrele de căutare", - "options": "Opțiuni", - "or": "sau", - "organize_into_albums": "Organizați în albume", - "organize_into_albums_description": "Pune fotografiile existente în albume folosind setările curente de sincronizare", - "organize_your_library": "Organizează-ți biblioteca", - "original": "original", - "other": "Alte", - "other_devices": "Alte dispozitive", - "other_entities": "Alte entități", - "other_variables": "Alte variabile", - "owned": "Deținut", - "owner": "Proprietar", - "page": "Pagină", - "partner": "Partener", - "partner_can_access": "{partner} poate accesa", - "partner_can_access_assets": "Toate fotografiile și videoclipurile tale, cu excepția celor din arhivate și sterse", - "partner_can_access_location": "Locația în care au fost făcute fotografiile dvs", - "partner_list_user_photos": "Fotografiile lui {user}", - "partner_list_view_all": "Vezi toate", - "partner_page_empty_message": "Fotografiile tale nu sunt încă distribuite cu nici un partener.", - "partner_page_no_more_users": "Nu mai sunt utilizatori de adăugat", - "partner_page_partner_add_failed": "Eșuare adăugare partener", - "partner_page_select_partner": "Selectează partener", - "partner_page_shared_to_title": "Distribuit cu", - "partner_page_stop_sharing_content": "{partner} nu va mai putea accesa fotografiile tale.", - "partner_sharing": "Partajarea Partenerilor", - "partners": "Parteneri", - "password": "Parolă", - "password_does_not_match": "Parola nu se potrivește", - "password_required": "Parola Obligatorie", - "password_reset_success": "Resetarea parolei efectuată cu succes", - "past_durations": { - "days": "Ultimele {days, plural, one {zi} other {# zile}}", - "hours": "Ultimele {hours, plural, one {oră} other {# ore}}", - "years": "Ultimii {years, plural, one {an} other {# ani}}" - }, - "path": "Cale", - "pattern": "Tipar", - "pause": "Pauză", - "pause_memories": "Opriți amintirile", - "paused": "Întrerupt", - "pending": "În așteptare", - "people": "Persoane", - "people_edits_count": "Editat {count, plural, one {# persoană} other {# persoane}}", - "people_feature_description": "Răsfoiți fotografii și videoclipuri grupate după persoane", - "people_selected": "{count, plural,one {# persoană selectată} few {# persoane selectate}other {# de persoane selectate}}", - "people_sidebar_description": "Afișează un link către persoane în bara laterală", - "permanent_deletion_warning": "Avertisment de ștergere permanentă", - "permanent_deletion_warning_setting_description": "Afișează un avertisment la ștergerea definitivă a resurselor", - "permanently_delete": "Ștergeți definitiv", - "permanently_delete_assets_count": "Ștergeți definitiv {count, plural, one {resursă} other {resurse}}", - "permanently_delete_assets_prompt": "Sigur doriți să ștergeți definitiv {count, plural, one {această resursă?} other {aceste # resurse?}} Acest lucru va elimina și {count, plural, one {din ea} other {din ele}} album(e).", - "permanently_deleted_asset": "Resursă ștearsă definitiv", - "permanently_deleted_assets_count": "S-au șters definitiv {count, plural, one {# resursă} other {# resurse}}", - "permission": "Permisiune", - "permission_empty": "Permisiunea dvs. nu trebuie să fie goală", - "permission_onboarding_back": "Înapoi", - "permission_onboarding_continue_anyway": "Continuă oricum", - "permission_onboarding_get_started": "Începe", - "permission_onboarding_go_to_settings": "Mergi la setări", - "permission_onboarding_permission_denied": "Permisiune refuzată. Pentru a utiliza Immich, acordă permisiuni pentru fotografii și videoclipuri în Setări.", - "permission_onboarding_permission_granted": "Permisiune acordată! Sunteți gata.", - "permission_onboarding_permission_limited": "Permisiune limitată. Pentru a permite Immich să facă copii de siguranță și să gestioneze întreaga colecție de galerii, acordă permisiuni pentru fotografii și videoclipuri în Setări.", - "permission_onboarding_request": "Immich necesită permisiunea de a vizualiza fotografiile și videoclipurile tale.", - "person": "Persoanǎ", - "person_age_months": "{months, plural, one {# lună} other {# luni}}", - "person_age_year_months": "1 an, {months, plural, one {# lună} other {# luni}}", - "person_age_years": "{years, plural, other {# ani}}", - "person_birthdate": "Născut pe {date}", - "person_hidden": "{name}{hidden, select, true { (ascuns)} other {}}", - "person_recognized": "Persoană recunoscută", - "person_selected": "Persoana selectată", - "photo_shared_all_users": "Se pare că ți-ai partajat fotografiile tuturor utilizatorilor sau că nu ai niciun utilizator căruia să le distribui.", - "photos": "Fotografii", - "photos_and_videos": "Fotografii și Videoclipuri", - "photos_count": "{count, plural, one {{count, number} imagine} other{{count, number} imagini}}", - "photos_from_previous_years": "Fotografii din anii anteriori", - "photos_only": "Numai fotografii", - "pick_a_location": "Alegeți o locație", - "pick_custom_range": "Interval personalizat", - "pick_date_range": "Selectați un interval de date", - "pin_code_changed_successfully": "Codul PIN a fost modificat cu succes", - "pin_code_reset_successfully": "Codul PIN a fost resetat cu succes", - "pin_code_setup_successfully": "Configurarea cu succes a unui cod PIN", - "pin_verification": "Verificarea codului PIN", - "place": "Loc", - "places": "Locații", - "places_count": "{count, plural, one {{count, number} Loc} other {{count, number} Locuri}}", - "play": "Redare", - "play_memories": "Redare amintiri", - "play_motion_photo": "Redare Fotografie în Mișcare", - "play_or_pause_video": "Redați sau întrerupeți videoclipul", - "play_original_video": "Redă video original", - "play_original_video_setting_description": "Se preferă redarea videoclipurilor originale în locul celor transcodate. Dacă fișierul original nu este compatibil, redarea s-ar putea să nu fie corectă.", - "play_transcoded_video": "Redă video transcodificat", - "please_auth_to_access": "Vă rugăm să vă autentificați pentru a accesa", - "port": "Port", - "preferences_settings_subtitle": "Gestionați preferințele aplicației", - "preferences_settings_title": "Preferințe", - "preparing": "Se prepară", - "preset": "Presetat", - "preview": "Previzualizare", - "previous": "Anterior", - "previous_memory": "Memoria anterioară", - "previous_or_next_day": "Zi înainte/înapoi", - "previous_or_next_month": "Lună înainte/înapoi", - "previous_or_next_photo": "Fotografie înainte/înapoi", - "previous_or_next_year": "An înainte/înapoi", - "primary": "Primar", - "privacy": "Confidențialitate", - "profile": "Profil", - "profile_drawer_app_logs": "Log-uri", - "profile_drawer_client_server_up_to_date": "Aplicația client și server-ul sunt actualizate", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mod doar citire activat. Ține apăsat pe pictograma avatarului utilizatorului pentru a ieși.", - "profile_image_of_user": "Imagine de profil a lui {user}", - "profile_picture_set": "Poză de profil setată.", - "public_album": "Album public", - "public_share": "Distribuire Publică", - "purchase_account_info": "Suporter", - "purchase_activated_subtitle": "Vă mulțumim că susțineți Immich și software-ul open-source", - "purchase_activated_time": "Activat pe data de {date}", - "purchase_activated_title": "Cheia dvs. a fost activată cu succes", - "purchase_button_activate": "Activați", - "purchase_button_buy": "Cumpărați", - "purchase_button_buy_immich": "Cumpărați Immich", - "purchase_button_never_show_again": "Nu mai arăta niciodată", - "purchase_button_reminder": "Amintește-mi în 30 de zile", - "purchase_button_remove_key": "Eliminați cheia", - "purchase_button_select": "Selectare", - "purchase_failed_activation": "Activare eșuată! Vă rugăm să vă verificați e-mailul pentru cheia de produs corectă!", - "purchase_individual_description_1": "Pentru un individ", - "purchase_individual_description_2": "Statutul de suporter", - "purchase_individual_title": "Individual", - "purchase_input_suggestion": "Aveți o cheie de produs? Introduceți cheia mai jos", - "purchase_license_subtitle": "Cumpărați Immich pentru a sprijini dezvoltarea continuă a serviciului", - "purchase_lifetime_description": "Achiziție pe viață", - "purchase_option_title": "OPȚIUNI DE CUMPĂRARE", - "purchase_panel_info_1": "Dezvoltarea programului Immich necesită mult timp și efort și avem ingineri cu normă întreagă care lucrează la el pentru a-l face cât se poate de bun. Misiunea noastră este ca software-ul open-source și practicile de afaceri etice să devină o sursă de venit durabilă pentru dezvoltatori și să se creeze un ecosistem care să respecte confidențialitatea utilizatorilor, cu alternative reale la serviciile cloud care exploatează utilizatorii.", - "purchase_panel_info_2": "Deoarece ne-am angajat să nu adăugăm planuri de plată, această achiziție nu vă va oferi nicio funcție suplimentară în Immich. Ne bazăm pe utilizatori ca dvs. pentru a sprijini dezvoltarea continuă a Immich.", - "purchase_panel_title": "Susțineți proiectul", - "purchase_per_server": "Per server", - "purchase_per_user": "Per utilizator", - "purchase_remove_product_key": "Eliminați Cheia Produsului", - "purchase_remove_product_key_prompt": "Sigur doriți să eliminați cheia de produs?", - "purchase_remove_server_product_key": "Eliminați cheia de produs a Serverului", - "purchase_remove_server_product_key_prompt": "Sigur doriți să eliminați cheia de produs a Serverului?", - "purchase_server_description_1": "Pentru tot serverul", - "purchase_server_description_2": "Statutul de suporter", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Cheia de produs a serverului este gestionată de administrator", - "query_asset_id": "Interoghează ID-ul resursei", - "queue_status": "Se pun în coadă {count}/{total}", - "rate_asset": "Dă o notă", - "rating": "Evaluare cu stele", - "rating_clear": "Anuleaza evaluarea", - "rating_count": "{count, plural, one {# stea} other {# stele}}", - "rating_description": "Afișați evaluarea EXIF în panoul de informații", - "rating_set": "Evaluare setată la {rating, plural, o {# star} alte {# stars}}", - "reaction_options": "Opțiuni de reacție", - "read_changelog": "Citiți Jurnalul de Modificări", - "readonly_mode_disabled": "Modul doar citire dezactivat", - "readonly_mode_enabled": "Modul doar citire activat", - "ready_for_upload": "Pregătit pentru încărcare", - "reassign": "Reatribuiți", - "reassigned_assets_to_existing_person": "Re-alocat {count, plural, one {# resursă} other {# resurse}} to {name, select, null {unei persoane existente} other {{name}}}", - "reassigned_assets_to_new_person": "Re-alocat {count, plural, one {# resursă} other {# resurse}} unei noi persoane", - "reassing_hint": "Atribuiți resursele selectate unei persoane existente", - "recent": "Recent", - "recent-albums": "Albume recente", - "recent_searches": "Căutări recente", - "recently_added": "Adăugate recent", - "recently_added_page_title": "Adăugate recent", - "recently_taken": "Recent realizate", - "recently_taken_page_title": "Recent realizate", - "refresh": "Reîmprospătare", - "refresh_encoded_videos": "Actualizează videoclipurile encodate", - "refresh_faces": "Reîmprospătați fețele", - "refresh_metadata": "Actualizați metadatele", - "refresh_thumbnails": "Reîmprospătați miniaturile", - "refreshed": "Reîmprospătat", - "refreshes_every_file": "Recitește toate fișierele existente și noi", - "refreshing_encoded_video": "Se reîmprospătează videoclipul encodat", - "refreshing_faces": "Se reîmprospătează fețele", - "refreshing_metadata": "Se reîmprospătează metadatele", - "regenerating_thumbnails": "Se regenerează miniaturile", - "remote": "De la distanță", - "remote_assets": "Elemente la distanță", - "remote_media_summary": "Rezumat media de la distanță", - "remove": "Eliminați", - "remove_assets_album_confirmation": "Sigur doriți să eliminați {count, plural, one {# resursă} other {# resurse}} din album?", - "remove_assets_shared_link_confirmation": "Sigur doriți să eliminați {count, plural, one {# resursă} other {# resurse}} din acest link comun?", - "remove_assets_title": "Eliminați resursele?", - "remove_custom_date_range": "Eliminați intervalul de date personalizat", - "remove_deleted_assets": "Eliminați Resursele Șterse", - "remove_from_album": "Ștergeți din album", - "remove_from_album_action_prompt": "{count} șters(e) din album", - "remove_from_favorites": "Eliminați din favorite", - "remove_from_lock_folder_action_prompt": "{count} șters(e) din dosarul blocat", - "remove_from_locked_folder": "Eliminați din folderul securizat", - "remove_from_locked_folder_confirmation": "Sunteți sigur că doriți să mutați aceste poze și videoclipuri afară din folderul securizat? Vor deveni vizibile în biblioteca dvs.", - "remove_from_shared_link": "Eliminați din linkul partajat", - "remove_memory": "Șterge amintirea", - "remove_photo_from_memory": "Șterge fotografia din această amintire", - "remove_tag": "Eliminați ticheta", - "remove_url": "Eliminați adresa URL", - "remove_user": "Eliminați utilizatorul", - "removed_api_key": "Cheie API eliminată: {name}", - "removed_from_archive": "Eliminat din arhivă", - "removed_from_favorites": "Eliminat din favorite", - "removed_from_favorites_count": "{count, plural, other {Eliminat #}} din favorite", - "removed_memory": "Amintire ștearsă", - "removed_photo_from_memory": "Fotografie ștearsă din amintire", - "removed_tagged_assets": "Eticheta a fost eliminată din {count, plural, one {# resursă} other {# resurse}}", - "rename": "Redenumiți", - "repair": "Reparați", - "repair_no_results_message": "Fișierele neurmărite și lipsă vor apărea aici", - "replace_with_upload": "Înlocuiți cu încărcare", - "repository": "Repertoriu", - "require_password": "Necesită parolă", - "require_user_to_change_password_on_first_login": "Solicitați utilizatorului să schimbe parola la prima conectare", - "rescan": "Rescanează", - "reset": "Resetare", - "reset_password": "Resetare parolă", - "reset_people_visibility": "Resetați vizibilitatea persoanelor", - "reset_pin_code": "Resetare cod PIN", - "reset_pin_code_description": "Dacă ți-ai uitat codul PIN, poți contacta administratorul serverului pentru a-l reseta", - "reset_pin_code_success": "Codul PIN a fost resetat cu succes", - "reset_pin_code_with_password": "Puteți reseta oricând codul PIN cu ajutorul parolei", - "reset_sqlite": "Resetare bază de date SQLite", - "reset_sqlite_confirmation": "Sigur doriți să resetați baza de date SQLite? Va trebui să vă deconectați și să vă conectați din nou pentru a resincroniza datele", - "reset_sqlite_success": "Resetarea cu succes a bazei de date SQLite", - "reset_to_default": "Resetați la valoarea implicită", - "resolution": "Rezoluție", - "resolve_duplicates": "Rezolvați duplicatele", - "resolved_all_duplicates": "Rezolvați toate duplicatele", - "restore": "Restaurați", - "restore_all": "Restaurați toate", - "restore_trash_action_prompt": "{count} restaurate din gunoi", - "restore_user": "Restabiliți utilizatorul", - "restored_asset": "Resursă restaurată", - "resume": "Reluare", - "resume_paused_jobs": "Reluați {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Reîncercați încărcarea", - "review_duplicates": "Examinați duplicatele", - "review_large_files": "Revizuirea fișierelor mari", - "role": "Rol", - "role_editor": "Editor", - "role_viewer": "Vizualizator", - "running": "Rulează", - "save": "Salvați", - "save_to_gallery": "Salvați în galerie", - "saved": "Salvat", - "saved_api_key": "Cheie API salvată", - "saved_profile": "Profil salvat", - "saved_settings": "Setări salvate", - "say_something": "Spuneți ceva", - "scaffold_body_error_occurred": "A apărut o eroare", - "scan": "Scanare", - "scan_all_libraries": "Scanați toate bibliotecile", - "scan_library": "Scanare", - "scan_settings": "Setări Scanare", - "scanning": "Scanare", - "scanning_for_album": "Se scanează după album...", - "search": "Căutați", - "search_albums": "Căutați albume", - "search_by_context": "Căutați după context", - "search_by_description": "Căutare după descriere", - "search_by_description_example": "Zi de drumeție în Sapa", - "search_by_filename": "Căutați după numele fișierului sau extensie", - "search_by_filename_example": "i.e. IMG_1234.JPG sau PNG", - "search_by_ocr": "Caută folosind OCR", - "search_by_ocr_example": "Factură", - "search_camera_lens_model": "Caută modelul lentilei...", - "search_camera_make": "Se caută marca camerei...", - "search_camera_model": "Se caută modelul camerei...", - "search_city": "Caută în orașul...", - "search_country": "Caută în țara...", - "search_filter_apply": "Aplicați filtrul", - "search_filter_camera_title": "Selectați tipul de cameră", - "search_filter_date": "Dată", - "search_filter_date_interval": "{start} la {end}", - "search_filter_date_title": "Selectați un interval de dată", - "search_filter_display_option_not_in_album": "Nu este în album", - "search_filter_display_options": "Opțiuni de afișare", - "search_filter_filename": "Căutare după numele fișierului", - "search_filter_location": "Locaţie", - "search_filter_location_title": "Selectați locația", - "search_filter_media_type": "Tip media", - "search_filter_media_type_title": "Selectați tipul media", - "search_filter_ocr": "Caută dupa OCR", - "search_filter_people_title": "Selectați persoane", - "search_filter_star_rating": "După rating în stele", - "search_for": "Căutare după", - "search_for_existing_person": "Caută o persoană existentă", - "search_no_more_result": "Nu mai există rezultate", - "search_no_people": "Fără persoane", - "search_no_people_named": "Nicio persoană numită \"{name}\"", - "search_no_result": "Nu s-au găsit rezultate, încercați un alt termen sau o altă combinație de termeni de căutare", - "search_options": "Opțiuni de căutare", - "search_page_categories": "Categorii", - "search_page_motion_photos": "Fotografii în mișcare", - "search_page_no_objects": "Nu sunt informații disponibile despre obiecte", - "search_page_no_places": "Nici o informație disponibilă despre locuri", - "search_page_screenshots": "Capturi de ecran", - "search_page_search_photos_videos": "Caută fotografiile și videoclipurile tale", - "search_page_selfies": "Selfie-uri", - "search_page_things": "Obiecte", - "search_page_view_all_button": "Vezi toate", - "search_page_your_activity": "Activitatea ta", - "search_page_your_map": "Harta ta", - "search_people": "Căutați oameni", - "search_places": "Căutați locuri", - "search_rating": "Caută după notă...", - "search_result_page_new_search_hint": "Căutare nouă", - "search_settings": "Setări de căutare", - "search_state": "Caută în Stat/Județ...", - "search_suggestion_list_smart_search_hint_1": "Căutarea inteligentă este activată în mod implicit, pentru a căuta metadata, utilizează sintaxa ", - "search_suggestion_list_smart_search_hint_2": "m:termen-de-căutare", - "search_tags": "Căutați etichete...", - "search_timezone": "Căutați fusul orar...", - "search_type": "Tip cǎutare", - "search_your_photos": "Căutarea fotografiilor dvs", - "searching_locales": "Se caută regionale...", - "second": "Secundǎ", - "see_all_people": "Vizualizează toate persoanele", - "select": "Selectează", - "select_album": "Selectează album", - "select_album_cover": "Selectați coperta albumului", - "select_albums": "Selectează albume", - "select_all": "Selectați tot", - "select_all_duplicates": "Selectați toate duplicatele", - "select_all_in": "Selectați tot în {group}", - "select_avatar_color": "Selectați culoarea avatarului", - "select_count": "{count, plural, one {Selectează #} few {Selectează #} other {Selectează #}}", - "select_cutoff_date": "Selectează data limită", - "select_face": "Selectați fața", - "select_featured_photo": "Selectați fotografia recomandată", - "select_from_computer": "Selectați din calculator", - "select_keep_all": "Selectați tot pentru păstrare", - "select_library_owner": "Selectați proprietarul bibliotecii", - "select_new_face": "Selectați o nouǎ fațǎ", - "select_people": "Selectează oameni", - "select_person": "Selectează persoana", - "select_person_to_tag": "Selectați o persoană pentru a o eticheta", - "select_photos": "Selectați fotografii", - "select_trash_all": "Selectați tot pentru ștergere", - "select_user_for_sharing_page_err_album": "Creare album eșuată", - "selected": "Selectat", - "selected_count": "{count, plural, other {# selectat}}", - "selected_gps_coordinates": "Coordonate GPS selectate", - "send_message": "Trimiteți mesaj", - "send_welcome_email": "Trimiteți email de bun venit", - "server_endpoint": "Endpoint server", - "server_info_box_app_version": "Versiune Aplicatie", - "server_info_box_server_url": "URL-ul server-ului", - "server_offline": "Serverul este offline", - "server_online": "Server online", - "server_privacy": "Confidențialitatea serverului", - "server_restarting_description": "Această pagină se va reîmprospăta în scurt timp.", - "server_restarting_title": "Serverul se restartează", - "server_stats": "Statistici server", - "server_update_available": "Actualizare pentru server disponibilă", - "server_version": "Versiune Server", - "set": "Setați", - "set_as_album_cover": "Setați ca și copertă a albumului", - "set_as_featured_photo": "Setează că poză prezentată", - "set_as_profile_picture": "Setați ca imagine de profil", - "set_date_of_birth": "Setați data nașterii", - "set_profile_picture": "Setați poza de profil", - "set_slideshow_to_fullscreen": "Setați Prezentare de Diapozitive la ecran complet", - "set_stack_primary_asset": "Setați ca element principal", - "setting_image_viewer_help": "Vizualizatorul detaliilor încarcă mai întâi miniatura mică, apoi încarcă previzualizarea de dimensiune medie (dacă este activată), în cele din urmă încarcă originalul (dacă este activat).", - "setting_image_viewer_original_subtitle": "Activează pentru a încărca imaginea originală în rezoluție completă (mare!). Dezactivează pentru a reduce consumul de date (atat pe rețea, cât și în memoria cache a dispozitivului).", - "setting_image_viewer_original_title": "Încarcă fotografia originală", - "setting_image_viewer_preview_subtitle": "Activează pentru a încărca o imagine în rezoluție medie. Dezactivează pentru a încărca direct imaginea originală sau doar a utiliza miniatura.", - "setting_image_viewer_preview_title": "Încarcă imaginea de previzualizare", - "setting_image_viewer_title": "Imagini", - "setting_languages_apply": "Aplică", - "setting_languages_subtitle": "Schimbați limba aplicației", - "setting_notifications_notify_failures_grace_period": "Notificare eșuări backup în fundal: {duration}", - "setting_notifications_notify_hours": "{count} ore", - "setting_notifications_notify_immediately": "imediat", - "setting_notifications_notify_minutes": "{count} minute", - "setting_notifications_notify_never": "niciodată", - "setting_notifications_notify_seconds": "{count} secunde", - "setting_notifications_single_progress_subtitle": "Informații detaliate despre progresul încărcării pentru fiecare resursă", - "setting_notifications_single_progress_title": "Afișează progresul detaliat al copiilor de siguranță în fundal", - "setting_notifications_subtitle": "Ajustează preferințele pentru notificări", - "setting_notifications_total_progress_subtitle": "Progresul general al încărcării (resurse finalizate/total)", - "setting_notifications_total_progress_title": "Afișează progresul total al copiilor de siguranță în fundal", - "setting_video_viewer_auto_play_subtitle": "Pornește automat redarea videoclipurilor când sunt deschise", - "setting_video_viewer_auto_play_title": "Redare automată a videoclipurilor", - "setting_video_viewer_looping_title": "Buclă", - "setting_video_viewer_original_video_subtitle": "Când redați în flux un videoclip de pe server, redați originalul chiar și atunci când este disponibilă o transcodare. Poate duce la încărcare temporară. Videoclipurile disponibile local sunt redate la calitatea originală indiferent de această setare.", - "setting_video_viewer_original_video_title": "Forțează videoclipul original", - "settings": "Setări", - "settings_require_restart": "Te rugăm să repornești Immich pentru a aplica această setare", - "settings_saved": "Setările au fost salvate", - "setup_pin_code": "Configurați un cod PIN", - "share": "Distribuiți", - "share_action_prompt": "{count} elemente partajate", - "share_add_photos": "Adaugă fotografii", - "share_assets_selected": "{count} selectat(e)", - "share_dialog_preparing": "Se pregătește...", - "share_link": "Partajați linkul", - "shared": "Partajat", - "shared_album_activities_input_disable": "Cometariile sunt dezactivate", - "shared_album_activity_remove_content": "Dorești să ștergi această activitate?", - "shared_album_activity_remove_title": "Șterge activitate", - "shared_album_section_people_action_error": "Eroare la părăsirea/ștergerea din album", - "shared_album_section_people_action_leave": "Șterge utilizator din album", - "shared_album_section_people_action_remove_user": "Șterge utilizator din album", - "shared_album_section_people_title": "PERSOANE", - "shared_by": "Partajat de", - "shared_by_user": "Partajat de {user}", - "shared_by_you": "Partajat de tine", - "shared_from_partner": "Fotografii de la {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} încărcate", - "shared_link_app_bar_title": "Link-uri distribuite", - "shared_link_clipboard_copied_massage": "Copiat în clipboard", - "shared_link_clipboard_text": "Link: {link}\nParolă: {password}", - "shared_link_create_error": "Eroare în timpul creării linkului de distribuire", - "shared_link_custom_url_description": "Accesează acest link partajat cu un URL personalizat", - "shared_link_edit_description_hint": "Introdu descrierea distribuirii", - "shared_link_edit_expire_after_option_day": "1 zi", - "shared_link_edit_expire_after_option_days": "{count} zile", - "shared_link_edit_expire_after_option_hour": "1 oră", - "shared_link_edit_expire_after_option_hours": "{count} ore", - "shared_link_edit_expire_after_option_minute": "1 minut", - "shared_link_edit_expire_after_option_minutes": "{count} minute", - "shared_link_edit_expire_after_option_months": "{count} luni", - "shared_link_edit_expire_after_option_year": "{count} an", - "shared_link_edit_password_hint": "Introdu parola de distribuire", - "shared_link_edit_submit_button": "Actualizează link", - "shared_link_error_server_url_fetch": "Nu se poate accesa URL-ul serverului", - "shared_link_expires_day": "Expiră în {count} zi", - "shared_link_expires_days": "Expiră în {count} zile", - "shared_link_expires_hour": "Expiră în {count} ore", - "shared_link_expires_hours": "Expiră în {count} ore", - "shared_link_expires_minute": "Expiră în {count} minute", - "shared_link_expires_minutes": "Expiră în {count} minute", - "shared_link_expires_never": "Expiră ∞", - "shared_link_expires_second": "Expiră în {count} secunde", - "shared_link_expires_seconds": "Expiră în {count} secunde", - "shared_link_individual_shared": "Partajat individual", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Administrează link-urile distribuite", - "shared_link_options": "Opțiuni de link partajat", - "shared_link_password_description": "Solicită o parolă pentru a accesa acest link partajat", - "shared_links": "Link-uri distribuite", - "shared_links_description": "Partajare imagini și clipuri printr-un link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# fotografii și videoclipuri partajate.}}", - "shared_with_me": "Distribuit cu mine", - "shared_with_partner": "Partajat cu {partner}", - "sharing": "Distribuire", - "sharing_enter_password": "Vă rugăm să introduceți parola pentru a vizualiza această pagină.", - "sharing_page_album": "Albume distribuite", - "sharing_page_description": "Creeză albume de distribuire pentru a distribui fotografii și videoclipuri cu persoanele din rețeaua ta.", - "sharing_page_empty_list": "LISTĂ GOALĂ", - "sharing_sidebar_description": "Afișați un link către Partajare în bara laterală", - "sharing_silver_appbar_create_shared_album": "Album nou distribuit", - "sharing_silver_appbar_share_partner": "Distribuie cu partenerul", - "shift_to_permanent_delete": "apăsați ⇧ pentru a șterge definitiv elementul", - "show_album_options": "Afișați opțiunile de album", - "show_albums": "Afișați albume", - "show_all_people": "Aratați toate persoanele", - "show_and_hide_people": "Afișați și ascundeți persoane", - "show_file_location": "Afișați locația fișierului", - "show_gallery": "Afișați galeria", - "show_hidden_people": "Arătați persoanele ascunse", - "show_in_timeline": "Afișați în cronologie", - "show_in_timeline_setting_description": "Afișați fotografii și videoclipuri de la acest utilizator în cronologia dvs", - "show_keyboard_shortcuts": "Afișați comenzile rapide de la tastatură", - "show_metadata": "Arătați metadatele", - "show_or_hide_info": "Afișați sau ascundeți informații", - "show_password": "Afișați parola", - "show_person_options": "Afișați opțiunile persoanelor", - "show_progress_bar": "Afișați Bara de Progres", - "show_schema": "Arată schema", - "show_search_options": "Afișați opțiunile de căutare", - "show_shared_links": "Afișare linkuri partajate", - "show_slideshow_transition": "Afișați tranziția de prezentare", - "show_supporter_badge": "Insigna suporterului", - "show_supporter_badge_description": "Arată o insignă de suporter", - "show_text_recognition": "Afișare recunoaștere text", - "show_text_search_menu": "Afișează meniul de căutare text", - "shuffle": "Amestecați", - "sidebar": "Bara laterală", - "sidebar_display_description": "Afișați un link către vizualizare în bara laterală", - "sign_out": "Deconectare", - "sign_up": "Vă înregistrați", - "size": "Dimensiune", - "skip_to_content": "Treceți la conținut", - "skip_to_folders": "Treceți la foldere", - "skip_to_tags": "Treceți la etichete", - "slideshow": "Prezentare de diapozitive", - "slideshow_repeat": "Repetă prezentarea", - "slideshow_repeat_description": "Reîntoarce-te la început cand prezentarea se încheie", - "slideshow_settings": "Setări pentru prezentarea de diapozitive", - "sort_albums_by": "Sortați albumele după...", - "sort_created": "Data creării", - "sort_items": "Numărul de articole", - "sort_modified": "Data modificării", - "sort_newest": "Cea mai nouă fotografie", - "sort_oldest": "Cea mai veche fotografie", - "sort_people_by_similarity": "Sortează oameni după asemanare", - "sort_recent": "Cea mai recentă fotografie", - "sort_title": "Titlu", - "source": "Sursă", - "stack": "Stivă", - "stack_action_prompt": "{count} suprapuse", - "stack_duplicates": "Duplicate stive", - "stack_select_one_photo": "Selectați o fotografie principală pentru stivă", - "stack_selected_photos": "Fotografie stivă selectată", - "stacked_assets_count": "Stivuite {count, plural, one {# resursă} other {# resurse}}", - "stacktrace": "Urmă stivă", - "start": "Început", - "start_date": "Data de începere", - "start_date_before_end_date": "Data de început trebuie să fie înainte de data de sfârșit", - "state": "Stat/Județ", - "status": "Stare", - "stop_casting": "Opriți difuzarea", - "stop_motion_photo": "Opriți Fotografia in Mișcare", - "stop_photo_sharing": "Încetați distribuirea fotografiilor?", - "stop_photo_sharing_description": "{partner} nu va mai putea accesa fotografiile dvs.", - "stop_sharing_photos_with_user": "Nu mai partajați fotografiile cu acest utilizator", - "storage": "Spațiu de stocare", - "storage_label": "Eticheta de depozitare", - "storage_quota": "Cotă de stocare", - "storage_usage": "{used} din {available} utilizați", - "submit": "Trimiteți", - "success": "Succes", - "suggestions": "Sugestii", - "sunrise_on_the_beach": "Rǎsǎrit pe plajǎ", - "support": "Suport tehnic", - "support_and_feedback": "Suport tehnic și feedback", - "support_third_party_description": "Instalarea dvs. Immich a fost pregătită de o terță parte. Problemele pe care le întâmpinați pot fi cauzate de acel pachet, așa că vă rugăm să ridicați probleme cu ei în primă instanță utilizând linkurile de mai jos.", - "swap_merge_direction": "Schimbați direcția de îmbinare", - "sync": "Sincronizare", - "sync_albums": "Sincronizează albumele", - "sync_albums_manual_subtitle": "Sincronizează toate videoclipurile și fotografiile încărcate cu albumele de rezervă selectate", - "sync_local": "Sincronizare locală", - "sync_remote": "Sincronizare la distanță", - "sync_status": "Status-ul sincronizării", - "sync_status_subtitle": "Vizualizează și gestionează sistemul de sincronizare", - "sync_upload_album_setting_subtitle": "Creează și încarcă fotografiile și videoclipurile tale în albumele selectate de pe Immich", - "tag": "Etichetă", - "tag_assets": "Eticheta resurselor", - "tag_created": "Etichetă creată: {tag}", - "tag_feature_description": "Răsfoirea fotografiilor și videoclipurilor grupate după subiecte de etichete logice", - "tag_not_found_question": "Nu puteți găsi o etichetă? Creați o etichetă nouă.", - "tag_people": "Etichetează Persoane", - "tag_updated": "Etichetă actualizată: {tag}", - "tagged_assets": "Etichetat {count, plural, one {# resursă} other {# resurse}}", - "tags": "Etichete", - "tap_to_run_job": "Atingeți pentru a rula job-ul", - "template": "Șablon", - "text_recognition": "Recunoașterea textului", - "theme": "Temă", - "theme_selection": "Selectarea temei", - "theme_selection_description": "Setați automat tema la mod luminos sau întunecată, în funcție de preferințele de sistem ale browserului dvs", - "theme_setting_asset_list_storage_indicator_title": "Arată indicator stocare", - "theme_setting_asset_list_tiles_per_row_title": "Număr de resurse pe rând ({count})", - "theme_setting_colorful_interface_subtitle": "Aplicați culoarea primară pe suprafețele de fundal.", - "theme_setting_colorful_interface_title": "Interfață colorată", - "theme_setting_image_viewer_quality_subtitle": "Ajustează calitatea detaliilor vizualizatorului de imagine", - "theme_setting_image_viewer_quality_title": "Calitate vizualizator de imagine", - "theme_setting_primary_color_subtitle": "Alege o culoare pentru acțiunile și accentele principale.", - "theme_setting_primary_color_title": "Culoare primară", - "theme_setting_system_primary_color_title": "Folosește culoarea sistemului", - "theme_setting_system_theme_switch": "Automat (La fel ca setarea sistemului)", - "theme_setting_theme_subtitle": "Alege tema aplicației", - "theme_setting_three_stage_loading_subtitle": "Încărcarea în trei etape are putea crește performanța încărcării dar generează un volum semnificativ mai mare de trafic pe rețea", - "theme_setting_three_stage_loading_title": "Pornește încărcarea în 3 etape", - "then": "Atunci", - "they_will_be_merged_together": "Vor fi îmbinate împreună", - "third_party_resources": "Resurse Terță Parte", - "time": "Timp", - "time_based_memories": "Amintiri bazate pe timp", - "time_based_memories_duration": "Numărul de secunde pentru afișarea fiecărei imagini.", - "timeline": "Cronologie", - "timezone": "Fus orar", - "to_archive": "Arhivă", - "to_change_password": "Schimbaţi parola", - "to_favorite": "Favorit", - "to_login": "Conectare", - "to_multi_select": "pentru selecție multiplă", - "to_parent": "Du-te la părinte", - "to_select": "a selecta", - "to_trash": "Coș de gunoi", - "toggle_settings": "Activați setările", - "toggle_theme_description": "Comută tema", - "total": "Total", - "total_usage": "Utilizare totală", - "trash": "Coș de gunoi", - "trash_action_prompt": "{count} mutat(e) la coșul de gunoi", - "trash_all": "Ștergeți Tot", - "trash_count": "Ștergeți {count, number}", - "trash_delete_asset": "Coș de gunoi/Ștergeți resursa", - "trash_emptied": "Coș de gunoi golit", - "trash_no_results_message": "Fotografiile și videoclipurile mutate în coșul de gunoi vor apărea aici.", - "trash_page_delete_all": "Șterge tot", - "trash_page_empty_trash_dialog_content": "Dorești să golești coșul? Aceste fișiere vor fi șterse permanent din Immich", - "trash_page_info": "Resursele din coș vor fi șterse permanent după {days} zile", - "trash_page_no_assets": "Nici o resursă in coș", - "trash_page_restore_all": "Restaurează toate fișierele", - "trash_page_select_assets_btn": "Selectează resurse", - "trash_page_title": "Coș ({count})", - "trashed_items_will_be_permanently_deleted_after": "Elementele din coșul de gunoi vor fi șterse definitiv după {days, plural, one {# zi} other {# zile}}.", - "trigger": "Declanșator", - "trigger_asset_uploaded": "Fișier încărcat", - "trigger_asset_uploaded_description": "Declanșează cand un fișier este încarcat", - "trigger_description": "Un eveniment care declanșează fluxul de lucru", - "trigger_person_recognized": "Persoana Recunoscută", - "trigger_person_recognized_description": "Declanșat atunci când este detectată o persoană", - "trigger_type": "Tip de declanșare", - "troubleshoot": "Depanați", - "type": "Tip", - "unable_to_change_pin_code": "Nu se poate schimba codul PIN", - "unable_to_check_version": "Verificarea versiunii aplicației sau serverului a eșuat", - "unable_to_setup_pin_code": "Nu se poate configura codul PIN", - "unarchive": "Dezarhivați", - "unarchive_action_prompt": "{count} șters(e) din Arhivă", - "unarchived_count": "{count, plural, other {dezarhivat #}}", - "undo": "Anulează", - "unfavorite": "Ștergeți din favorite", - "unfavorite_action_prompt": "{count} șters(e) de la Favorite", - "unhide_person": "Dezvăluie persoana", - "unknown": "Necunoscut", - "unknown_country": "Țară necunoscută", - "unknown_date": "Dată necunoscută", - "unknown_year": "An Necunoscut", - "unlimited": "Nelimitat", - "unlink_motion_video": "Deconectați videoclipul în mișcare", - "unlink_oauth": "Deconectați OAuth", - "unlinked_oauth_account": "Cont OAuth deconectat", - "unmute_memories": "Activează amintirile", - "unnamed_album": "Album fără Nume", - "unnamed_album_delete_confirmation": "Sigur doriți să ștergeți acest album?", - "unnamed_share": "Partajare fără Nume", - "unsaved_change": "Modificare nesalvată", - "unselect_all": "Deselectați toate", - "unselect_all_duplicates": "Deselectați toate duplicatele", - "unselect_all_in": "Deselectați toate din {group}", - "unstack": "Dezasamblați", - "unstack_action_prompt": "{count} neîmpachetate", - "unstacked_assets_count": "Nestivuit {count, plural, one {# resursă} other {# resurse}}", - "unsupported_field_type": "Tip de câmp neacceptat", - "untagged": "Neetichetat", - "untitled_workflow": "Flux fara titlu", - "up_next": "Mai departe", - "update_location_action_prompt": "Actualizează locația pentru {count} resurse selectate cu:", - "updated_at": "Actualizat", - "updated_password": "Parolă actualizată", - "upload": "Încărcați", - "upload_concurrency": "Încărcați simultan", - "upload_details": "Detalii încărcare", - "upload_dialog_info": "Vrei să backup resursele selectate pe server?", - "upload_dialog_title": "Încarcă resursă", - "upload_error_with_count": "Eroare la încărcare pentru {count, plural, one {# fișier} other {# fișiere}}", - "upload_errors": "Încărcare finalizată cu {count, plural, one {# eroare} other {# erori}}, reîmprospătați pagina pentru a reîncărca noile resurse.", - "upload_finished": "Încărcarea s-a finalizat", - "upload_progress": "Rămas {remaining, number} - Procesat {processed, number}/{total, number}", - "upload_skipped_duplicates": "Sărit {count, plural, one {# duplicat resursă} other {# duplicate resurse}}", - "upload_status_duplicates": "Duplicate", - "upload_status_errors": "Erori", - "upload_status_uploaded": "Încărcat", - "upload_success": "Încărcare reușită, reîmprospătați pagina pentru a vedea resursele noi încărcate.", - "upload_to_immich": "Încărcați pe Immich ({count})", - "uploading": "Se încarcă", - "uploading_media": "Se încarcă fișierele media", - "url": "URL", - "usage": "Utilizare", - "use_biometric": "Folosește biometrice", - "use_current_connection": "folosește conexiunea curentă", - "use_custom_date_range": "Utilizați în schimb un interval de date personalizat", - "user": "Utilizator", - "user_has_been_deleted": "Acest utilizator a fost șters.", - "user_id": "ID utilizator", - "user_liked": "{user} a apreciat {type, select, photo {această imagine} video {acest video} asset {această resursă} other {it}}", - "user_pin_code_settings": "Cod PIN", - "user_pin_code_settings_description": "Gestionați-vă codul PIN", - "user_privacy": "Confidențialitatea utilizatorilor", - "user_purchase_settings": "Cumpărare", - "user_purchase_settings_description": "Gestionați-vă achiziția", - "user_role_set": "Setați {user} ca {role}", - "user_usage_detail": "Detalii despre utilizare", - "user_usage_stats": "Statistici de utilizare a contului", - "user_usage_stats_description": "Vedeți statisticile de utilizare a contului", - "username": "Nume de utilizator", - "users": "Utilizatori", - "users_added_to_album_count": "{count, plural, one {# utilizator a fost adăugat} other {# utilizatori au fost adăugați}} în album", - "utilities": "Utilitǎți", - "validate": "Validați", - "validate_endpoint_error": "Vă rugăm să introduceți o adresă URL validă", - "validation_error": "Eroare de validare", - "variables": "Variabile", - "version": "Versiune", - "version_announcement_closing": "Prietenul tǎu, Alex", - "version_announcement_message": "Bună! Este disponibilă o nouă versiune de Immich. Vă rugăm să vă faceți timp să citiți notele de lansare pentru a vă asigura că configurația dvs. este actualizată pentru a preveni orice configurare greșită, mai ales dacă utilizați WatchTower sau orice mecanism care se ocupă de actualizarea automată a instanței dvs. Immich.", - "version_history": "Istoric Versiuni", - "version_history_item": "Instalat {version} pe data de {date}", - "video": "Videoclip", - "video_hover_setting": "Redați miniatura video la trecerea cursorului", - "video_hover_setting_description": "Redați miniatura video când mouse-ul trece peste element. Chiar și atunci când este dezactivată, redarea poate fi pornită trecând cu mouse-ul peste pictograma de redare.", - "videos": "Videoclipuri", - "videos_count": "{count, plural, one {# Videoclip} other {# Videoclipuri}}", - "videos_only": "Doar videoclipuri", - "view": "Secțiune", - "view_album": "Vizualizează Album", - "view_all": "Vizualizează Tot", - "view_all_users": "Vizulizați toți utilizatorii", - "view_asset_owners": "Vezi proprietarii resursei", - "view_details": "Vedeți detaliile", - "view_in_timeline": "Vizualizează în cronologie", - "view_link": "Vezi link", - "view_links": "Vizualizează link-urile", - "view_name": "Vizualizare", - "view_next_asset": "Vizualizează următoarea resursă", - "view_previous_asset": "Vizualizează resursa anterioară", - "view_qr_code": "Vezi cod QR", - "view_similar_photos": "Vizualizează poze similare", - "view_stack": "Vizualizare stivă", - "view_user": "Vizualizare utilizator", - "viewer_remove_from_stack": "Șterge din grup", - "viewer_stack_use_as_main_asset": "Folosește ca resursă principală", - "viewer_unstack": "Anulează grup", - "visibility_changed": "Vizibilitatea schimbată pentru {count, plural, one {# persoană} other {# persoane}}", - "visual": "Vizual", - "visual_builder": "Constructor vizual", - "waiting": "În așteptare", - "waiting_count": "În așteptare: {count}", - "warning": "Avertisment", - "week": "Sǎptǎmânǎ", - "welcome": "Bun venit", - "welcome_to_immich": "Bun venit la Immich", - "width": "Lățime", - "wifi_name": "Nume Wi-Fi", - "workflow_delete_prompt": "Ești sigur că vrei să ștergi acest flux de lucru?", - "workflow_deleted": "Flux de lucru șters", - "workflow_description": "Descrierea fluxului de lucru", - "workflow_info": "Informații despre fluxul de lucru", - "workflow_json": "Flux de lucru JSON", - "workflow_json_help": "Editează configurația fluxului de lucru în format JSON. Modificările vor fi sincronizate cu constructorul vizual.", - "workflow_name": "Numele fluxului de lucru", - "workflow_navigation_prompt": "Ești sigur că vrei să părăsești fără să salvezi modificările?", - "workflow_summary": "Rezumatul fluxului de lucru", - "workflow_update_success": "Fluxul de lucru a fost actualizat cu succes", - "workflow_updated": "Fluxul de lucru a fost actualizat", - "workflows": "Fluxuri de lucru", - "workflows_help_text": "Fluxurile de lucru automatizează acțiuni pe resurse, folosind declanșatori și filtre", - "wrong_pin_code": "Cod PIN greșit", - "year": "An", - "years_ago": "acum {years, plural, one {# an} other {# ani}} în urmă", - "yes": "Da", - "you_dont_have_any_shared_links": "Nu aveți linkuri partajate", - "your_wifi_name": "Numele rețelei tale WiFi", - "zero_to_clear_rating": "apasă 0 pentru a reseta evaluarea resursei", - "zoom_image": "Măriți Imaginea", - "zoom_to_bounds": "Mărește la margini" -} +{} diff --git a/i18n/ru.json b/i18n/ru.json index ed94f6de71..0967ef424b 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -1,2401 +1 @@ -{ - "about": "О продукте", - "account": "Учётная запись", - "account_settings": "Настройки учётной записи", - "acknowledge": "Подтвердить", - "action": "Действие", - "action_common_update": "Обновить", - "action_description": "Действия, выполняемые с отобранными объектами", - "actions": "Действия", - "active": "Выполняется", - "active_count": "Выполняются: {count}", - "activity": "Действия", - "activity_changed": "Активность {enabled, select, true {включена} other {отключена}}", - "add": "Добавить", - "add_a_description": "Добавить описание", - "add_a_location": "Добавить местоположение", - "add_a_name": "Добавить имя", - "add_a_title": "Добавить название", - "add_action": "Добавить действие", - "add_action_description": "Нажмите для добавления действия", - "add_assets": "Добавить объекты", - "add_birthday": "Указать дату рождения", - "add_endpoint": "Добавить адрес", - "add_exclusion_pattern": "Добавить шаблон исключения", - "add_filter": "Добавить фильтр", - "add_filter_description": "Нажмите для добавления условия отбора", - "add_location": "Добавить местоположение", - "add_more_users": "Добавить ещё пользователей", - "add_partner": "Добавить партнёра", - "add_path": "Добавить путь", - "add_photos": "Добавить фото", - "add_tag": "Добавить тег", - "add_to": "Добавить в…", - "add_to_album": "Добавить в альбом", - "add_to_album_bottom_sheet_added": "Добавлено в альбом {album}", - "add_to_album_bottom_sheet_already_exists": "Уже в альбоме {album}", - "add_to_album_bottom_sheet_some_local_assets": "Некоторые объекты не добавлены в альбом, поскольку еще не загружены на сервер", - "add_to_album_toggle": "Переключить выделение для альбома {album}", - "add_to_albums": "Добавить в альбомы", - "add_to_albums_count": "Добавить в альбомы ({count})", - "add_to_bottom_bar": "Добавить в", - "add_to_shared_album": "Добавить в общий альбом", - "add_upload_to_stack": "Загрузить и добавить в группу", - "add_url": "Добавить URL", - "add_workflow_step": "Добавить шаг рабочего процесса", - "added_to_archive": "Добавлено в архив", - "added_to_favorites": "Добавлено в избранное", - "added_to_favorites_count": "{count, plural, one {# объект добавлен} many {# объектов добавлено} other {# объекта добавлено}} в избранное", - "admin": { - "add_exclusion_pattern_description": "Добавьте шаблоны исключений. Поддерживаются символы подстановки *, ** и ?. Чтобы игнорировать все файлы в любом каталоге с именем \"Raw\", укажите \"**/Raw/**\". Чтобы игнорировать все файлы, заканчивающиеся на \".tif\", используйте \"**/*.tif\". Чтобы игнорировать путь целиком, укажите \"/path/to/ignore/**\".", - "admin_user": "Администратор", - "asset_offline_description": "Этот объект из внешней библиотеки не был обнаружен на диске и поэтому перемещён в корзину. Если файл объекта был перемещён внутри библиотеки, проверьте временную шкалу, чтобы найти новый соответствующий объект. Чтобы восстановить файл, убедитесь, что следующий путь доступен для Immich, и выполните сканирование библиотеки.", - "authentication_settings": "Настройки аутентификации", - "authentication_settings_description": "Управление паролями, OAuth и другими настройками аутентификации", - "authentication_settings_disable_all": "Вы уверены, что хотите отключить все методы входа? Вход будет полностью отключен.", - "authentication_settings_reenable": "Чтобы снова включить, используйте Команду сервера.", - "background_task_job": "Фоновые задачи", - "backup_database": "Создать резервную копию базы данных", - "backup_database_enable_description": "Включить создание дампов базы данных", - "backup_keep_last_amount": "Количество хранимых резервных копий базы данных", - "backup_onboarding_1_description": "хранение дополнительной внешней копии в облаке или другом физическом месте.", - "backup_onboarding_2_description": "хранение основных файлов и их локальной копии на двух разных типах носителей.", - "backup_onboarding_3_description": "создание трёх копий данных, включая исходные файлы: 2 локальных копии и 1 внешнюю.", - "backup_onboarding_description": "Для надёжной защиты рекомендуется использовать стратегию резервирования данных 3-2-1. Делайте копии как загруженных фотографий и видео, так и базы данных Immich.", - "backup_onboarding_footer": "Дополнительная информация по резервному копированию Immich доступна в документации.", - "backup_onboarding_parts_title": "Стратегия 3-2-1 подразумевает:", - "backup_onboarding_title": "Резервное копирование", - "backup_settings": "Настройки резервного копирования базы данных", - "backup_settings_description": "Настройки создания резервных копий базы данных.", - "cleared_jobs": "Очищены задачи для: {job}", - "config_set_by_file": "Настроено с помощью файла конфигурации", - "confirm_delete_library": "Вы действительно хотите удалить библиотеку {library}?", - "confirm_delete_library_assets": "Вы уверены, что хотите удалить эту библиотеку? Это безвозвратно удалит {count, plural, one {# объект} many {# объектов} other {# объекта}} из Immich. Файлы останутся на диске.", - "confirm_email_below": "Введите \"{email}\" для подтверждения", - "confirm_reprocess_all_faces": "Вы действительно хотите переопределить все лица? Также будут очищены имена всех людей.", - "confirm_user_password_reset": "Вы действительно хотите сбросить пароль пользователя {user}?", - "confirm_user_pin_code_reset": "Вы действительно хотите сбросить PIN-код пользователя {user}?", - "copy_config_to_clipboard_description": "Копировать текущую конфигурацию системы в JSON в буфер обмена", - "create_job": "Создать задачу", - "cron_expression": "Расписание (выражение планировщика cron)", - "cron_expression_description": "Частота и время выполнения задачи в формате планировщика cron. Воспользуйтесь при необходимости визуальным редактором Crontab Guru", - "cron_expression_presets": "Расписание (предустановленные варианты)", - "disable_login": "Отключить вход", - "duplicate_detection_job_description": "Запускает определение похожих изображений при помощи машинного зрения (зависит от умного поиска)", - "exclusion_pattern_description": "Шаблоны исключений позволяют игнорировать некоторые файлы и папки при сканировании библиотеки. Это полезно, если в папке есть файлы, которые не нужно импортировать. Например RAW-файлы.", - "export_config_as_json_description": "Сохранить текущую конфигурацию системы в файл JSON", - "external_libraries_page_description": "Управление внешними библиотеками", - "face_detection": "Обнаружение лиц", - "face_detection_description": "Обнаруживает лица на объектах с использованием машинного обучения. Для видео анализируется только миниатюра. Кнопка \"Обновить\" запускает повторную обработку всех объектов. \"Сброс\" — дополнительно удаляет все имеющиеся данные о лицах. \"Отсутствующие\" — ставит в очередь объекты, которые ещё не были обработаны. Обнаруженные лица помещаются в очередь для задачи Распознавание лиц и последующей их привязки к существующим или новым людям.", - "facial_recognition_job_description": "Группирует и назначает обнаруженные лица людям. Выполняется после завершения задачи Обнаружение лиц. Кнопка \"Сброс\" (пере)назначает все лица. \"Отсутствующие\" — добавляет в очередь обработки лица, не привязанные к человеку.", - "failed_job_command": "Команда {command} не выполнена для задачи: {job}", - "force_delete_user_warning": "ПРЕДУПРЕЖДЕНИЕ: Это приведет к немедленному удалению пользователя и всех его объектов. Это действие невозможно отменить, файлы не смогут быть восстановлены.", - "image_format": "Формат", - "image_format_description": "WebP создает файлы меньшего размера, чем JPEG, но кодирует медленнее.", - "image_fullsize_description": "Полноразмерное изображение без метаданных, используется при увеличении", - "image_fullsize_enabled": "Включить создание полноразмерного изображения", - "image_fullsize_enabled_description": "Создавать полноразмерное изображение для форматов, не предназначенных для веба. Когда включен параметр «Предпочитать встроенное превью», встроенные превью используются напрямую без конверсии. Не влияет на веб-совместимые форматы, такие как JPEG.", - "image_fullsize_quality_description": "Качество полноразмерного изображения от 1 до 100. Чем выше значение, тем лучше качество, но больше размер файла.", - "image_fullsize_title": "Настройки полноразмерного изображения", - "image_prefer_embedded_preview": "Предпочитать встроенное превью", - "image_prefer_embedded_preview_setting_description": "Используйте встроенные превью в фотографиях RAW в качестве входных данных для обработки изображений, если они доступны. Это может обеспечить более точную цветопередачу для некоторых изображений, но качество предварительного просмотра зависит от камеры, и изображение может иметь больше артефактов сжатия.", - "image_prefer_wide_gamut": "Предпочитаю широкую гамму", - "image_prefer_wide_gamut_setting_description": "Используйте Display P3 для миниатюр. Это лучше сохраняет яркость изображений с широким цветовым пространством, но изображения могут выглядеть по-другому на старых устройствах со старой версией браузера. Изображения sRGB сохраняются в формате sRGB, что позволяет избежать цветовых сдвигов.", - "image_preview_description": "Изображение среднего размера без метаданных, используемое при просмотре отдельных объектов и для машинного обучения", - "image_preview_quality_description": "Качество предварительного просмотра от 1 до 100. Чем выше, тем лучше, но создаются файлы большего размера, и может снизиться скорость отклика приложения. Установка низкого значения может повлиять на качество машинного обучения.", - "image_preview_title": "Настройки предварительного просмотра", - "image_progressive": "Прогрессивный JPEG", - "image_progressive_description": "Изображения с прогрессивным кодированием загружаются быстрее, постепенно улучшая качество. Настройка не влияет на изображения в формате WebP.", - "image_quality": "Качество", - "image_resolution": "Разрешение", - "image_resolution_description": "Более высокое разрешение позволяет сохранить больше деталей, но требует больше времени для кодирования, приводит к увеличению размера файлов и может снизить скорость отклика приложения.", - "image_settings": "Настройки изображений", - "image_settings_description": "Управление качеством и разрешением создаваемых изображений", - "image_thumbnail_description": "Маленькая миниатюра с удаленными метаданными, используемая при просмотре групп фотографий, таких как основная временная шкала", - "image_thumbnail_quality_description": "Качество миниатюр от 1 до 100. Чем выше качество, тем лучше, но при этом создаются файлы большего размера и может снизиться скорость отклика приложения.", - "image_thumbnail_title": "Настройки миниатюр", - "import_config_from_json_description": "Импортировать конфигурацию системы, загрузив JSON файл настроек", - "job_concurrency": "Число параллельных потоков задачи {job}", - "job_created": "Задача создана", - "job_not_concurrency_safe": "Эта задача не обеспечивает безопасность параллельности выполнения.", - "job_settings": "Настройки задач", - "job_settings_description": "Управление параллельностью выполнения задач", - "jobs_delayed": "{jobCount, plural, one {# отложена} other {# отложено}}", - "jobs_failed": "{jobCount, plural, other {# не удалось выполнить}}", - "jobs_over_time": "График обработки", - "library_created": "Создана новая библиотека: {library}", - "library_deleted": "Библиотека удалена", - "library_details": "Параметры библиотеки", - "library_folder_description": "Укажите путь к папке для импорта. Эта папка, включая подпапки, будет просканирована на наличие фото и видео.", - "library_remove_exclusion_pattern_prompt": "Удалить этот шаблон исключения?", - "library_remove_folder_prompt": "Удалить эту папку из списка?", - "library_scanning": "Периодическое сканирование", - "library_scanning_description": "Настроить периодическое сканирование библиотеки", - "library_scanning_enable_description": "Включить периодическое сканирование библиотеки", - "library_settings": "Внешняя библиотека", - "library_settings_description": "Управление внешними библиотеками", - "library_tasks_description": "Сканирование внешних библиотек на наличие новых и/или изменённых объектов", - "library_updated": "Библиотека обновлена", - "library_watching_enable_description": "Отслеживать изменения файлов во внешних библиотеках", - "library_watching_settings": "[ЭКСПЕРИМЕНТАЛЬНО] Слежение за библиотекой", - "library_watching_settings_description": "Автоматически следить за изменениями файлов", - "logging_enable_description": "Включить ведение журнала", - "logging_level_description": "Если включено, выберите желаемый уровень журналирования.", - "logging_settings": "Ведение журнала", - "machine_learning_availability_checks": "Проверка доступности", - "machine_learning_availability_checks_description": "Автоматически определять и использовать доступные серверы машинного обучения", - "machine_learning_availability_checks_enabled": "Включить проверку доступности", - "machine_learning_availability_checks_interval": "Интервал проверки", - "machine_learning_availability_checks_interval_description": "Интервал в миллисекундах между проверками", - "machine_learning_availability_checks_timeout": "Тайм-аут запроса", - "machine_learning_availability_checks_timeout_description": "Время ожидания ответа сервера в миллисекундах для определения доступности", - "machine_learning_clip_model": "CLIP модель", - "machine_learning_clip_model_description": "Названия доступных CLIP моделей размещены здесь.\nПри изменении модели необходимо заново запустить задачу «Интеллектуальный поиск» для всех объектов.", - "machine_learning_duplicate_detection": "Поиск дубликатов", - "machine_learning_duplicate_detection_enabled": "Включить обнаружение дубликатов", - "machine_learning_duplicate_detection_enabled_description": "Если этот параметр отключён, абсолютно идентичные файлы всё равно не будут загружаться.", - "machine_learning_duplicate_detection_setting_description": "Использование CLIP моделей для выявления возможных дубликатов", - "machine_learning_enabled": "Включить машинное обучение", - "machine_learning_enabled_description": "При выключении будут отключены все функции ML независимо от следующих параметров.", - "machine_learning_facial_recognition": "Распознавание лиц", - "machine_learning_facial_recognition_description": "Обнаруживать, распознавать и группировать лица на изображениях", - "machine_learning_facial_recognition_model": "Модель для распознавания лиц", - "machine_learning_facial_recognition_model_description": "Модели перечислены в порядке убывания их размера. Большие модели работают медленнее и используют больше памяти, но дают лучшие результаты. При смене модели необходимо повторно запустить задачу распознавания лиц для всех изображений.", - "machine_learning_facial_recognition_setting": "Включить функцию распознавания лиц", - "machine_learning_facial_recognition_setting_description": "При отключении этой функции изображения не будут кодироваться для распознавания лиц, и не будет заполняться раздел Люди.", - "machine_learning_max_detection_distance": "Максимальное различие изображений", - "machine_learning_max_detection_distance_description": "Максимальное различие между двумя изображениями, чтобы считать их дубликатами (в диапазоне от 0,001 до 0,1). Более высокое значение позволит найти больше дубликатов, но может привести к ложным срабатываниям.", - "machine_learning_max_recognition_distance": "Порог распознавания", - "machine_learning_max_recognition_distance_description": "Максимальное различие между двумя лицами, которые можно считать одним человеком (в диапазоне от 0 до 2). Понижение этого параметра может предотвратить распознавание двух людей как одного и того же человека, а повышение — как двух разных людей. Имейте в виду, что проще объединить двух людей, чем разделить одного человека на двоих, поэтому по возможности выбирайте меньший порог.", - "machine_learning_min_detection_score": "Минимальный порог распознавания", - "machine_learning_min_detection_score_description": "Минимальный порог для обнаружения лица (от 0 до 1). Более низкое значение позволит находить больше лиц, но может привести к ложным срабатываниям.", - "machine_learning_min_recognized_faces": "Минимум распознанных лиц", - "machine_learning_min_recognized_faces_description": "Минимальное количество распознанных лиц для создания человека. Увеличение этого параметра делает распознавание лиц более точным, но при этом увеличивается вероятность того, что лицо не будет присвоено человеку.", - "machine_learning_ocr": "Распознавание текста", - "machine_learning_ocr_description": "Использование машинного обучения для распознавания текста на изображениях", - "machine_learning_ocr_enabled": "Включить распознавание текста", - "machine_learning_ocr_enabled_description": "При отключении этой функции изображения не будут подвергаться обнаружению и распознаванию текста на них.", - "machine_learning_ocr_max_resolution": "Максимальное разрешение", - "machine_learning_ocr_max_resolution_description": "Изображения выше этого разрешения будут уменьшены с сохранением соотношения сторон. Более высокие значения повышают точность распознавания, но требуют больше времени на обработку и используют больше памяти.", - "machine_learning_ocr_min_detection_score": "Порог обнаружения текста", - "machine_learning_ocr_min_detection_score_description": "Минимальный порог обнаружения текста (от 0 до 1). Более низкое значение позволит находить больше текста, но может привести к ложным срабатываниям.", - "machine_learning_ocr_min_recognition_score": "Порог распознавания текста", - "machine_learning_ocr_min_score_recognition_description": "Минимальный порог уверенности распознавания обнаруженного текста (от 0 до 1). Более низкое значение позволит распознать больше текста, но может привести к ложным срабатываниям.", - "machine_learning_ocr_model": "Модель для распознавания текста", - "machine_learning_ocr_model_description": "Серверные модели точнее мобильных, но требуют больше времени на обработку и используют больше памяти.", - "machine_learning_settings": "Настройки машинного обучения", - "machine_learning_settings_description": "Управление функциями и настройками машинного обучения (ML)", - "machine_learning_smart_search": "Интеллектуальный поиск", - "machine_learning_smart_search_description": "Семантический (контекстный) поиск объектов с использованием CLIP моделей", - "machine_learning_smart_search_enabled": "Включить интеллектуальный поиск", - "machine_learning_smart_search_enabled_description": "При отключении этой функции изображения не будут кодироваться для интеллектуального поиска.", - "machine_learning_url_description": "URL-адрес сервера машинного обучения. Если указано несколько, запросы будут отправляться по очереди на каждый, пока от одного из них не будет получен успешный ответ. Серверы, которые не отвечают, будут временно игнорироваться до тех пор, пока не станут снова доступны.", - "maintenance_delete_backup": "Удалить резервную копию", - "maintenance_delete_backup_description": "Эта резервная копия будет безвозвратно удалена.", - "maintenance_delete_error": "Не удалось удалить резервную копию.", - "maintenance_restore_backup": "Восстановить резервную копию", - "maintenance_restore_backup_description": "База данных Immich будет очищена и затем восстановлена из выбранной резервной копии. Текущее состояние тоже будет предварительно сохранено.", - "maintenance_restore_backup_different_version": "Эта резервная копия была сделана на другой версии Immich!", - "maintenance_restore_backup_unknown_version": "Не удалось определить версию резервной копии.", - "maintenance_restore_database_backup": "Восстановить резервную копию базы данных", - "maintenance_restore_database_backup_description": "Восстановление предыдущего состояния базы данных из файла резервной копии", - "maintenance_settings": "Обслуживание", - "maintenance_settings_description": "Перевод сервера Immich в режим обслуживания.", - "maintenance_start": "Включить режим обслуживания", - "maintenance_start_error": "Не удалось перейти в режим обслуживания.", - "maintenance_upload_backup": "Загрузить файл резервной копии базы данных", - "maintenance_upload_backup_error": "Не удалось загрузить резервную копию. Это точно файл .sql/.sql.gz?", - "manage_concurrency": "Управление параллельностью", - "manage_concurrency_description": "Переход к управлению параллельностью выполнения задач", - "manage_log_settings": "Управление настройками журнала", - "map_dark_style": "Тёмный стиль", - "map_enable_description": "Включить функции карты", - "map_gps_settings": "Настройки карты и GPS", - "map_gps_settings_description": "Управление настройками карты и GPS (обратного геокодирования)", - "map_implications": "Функция отображения объектов на карте использует внешний сервис плиток (tiles.immich.cloud)", - "map_light_style": "Светлый стиль", - "map_manage_reverse_geocoding_settings": "Управление настройками обратного геокодирования", - "map_reverse_geocoding": "Обратное геокодирование", - "map_reverse_geocoding_enable_description": "Включить обратное геокодирование", - "map_reverse_geocoding_settings": "Настройки обратного геокодирования", - "map_settings": "Настройки карты", - "map_settings_description": "Управление настройками карты", - "map_style_description": "URL-адрес к JSON файлу темы карты", - "memory_cleanup_job": "Очистка воспоминаний", - "memory_generate_job": "Создание воспоминаний", - "metadata_extraction_job": "Извлечение метаданных", - "metadata_extraction_job_description": "Извлечение метаданных из файлов, таких как местоположение, лица и разрешение", - "metadata_faces_import_setting": "Включить импорт лиц", - "metadata_faces_import_setting_description": "Импорт лиц из EXIF-данных и файлов sidecar", - "metadata_settings": "Настройки метаданных", - "metadata_settings_description": "Управление настройками метаданных", - "migration_job": "Миграция", - "migration_job_description": "Перенос миниатюр объектов и лиц в последнюю структуру папок", - "nightly_tasks_cluster_faces_setting_description": "Запустить распознавание людей по новым обнаруженным лицам", - "nightly_tasks_cluster_new_faces_setting": "Распознавание новых лиц", - "nightly_tasks_database_cleanup_setting": "Задачи очистки базы данных", - "nightly_tasks_database_cleanup_setting_description": "Удаление старых и более ненужных записей из базы данных", - "nightly_tasks_generate_memories_setting": "Создание воспоминаний", - "nightly_tasks_generate_memories_setting_description": "Создание новых воспоминаний из существующих объектов", - "nightly_tasks_missing_thumbnails_setting": "Создание отсутствующих миниатюр", - "nightly_tasks_missing_thumbnails_setting_description": "Добавление объектов без миниатюр в очередь для их создания", - "nightly_tasks_settings": "Настройки ночных задач", - "nightly_tasks_settings_description": "Управление ночными регламентными задачами", - "nightly_tasks_start_time_setting": "Время начала", - "nightly_tasks_start_time_setting_description": "Время, когда сервер начинает выполнять ночные задачи", - "nightly_tasks_sync_quota_usage_setting": "Синхронизация квот хранилища", - "nightly_tasks_sync_quota_usage_setting_description": "Обновление квоты хранилища пользователя на основе актуальных данных", - "no_paths_added": "Пути не добавлены", - "no_pattern_added": "Шаблон не добавлен", - "note_apply_storage_label_previous_assets": "Примечание: Чтобы применить метку хранилища к ранее загруженным объектам, запустите", - "note_cannot_be_changed_later": "ПРИМЕЧАНИЕ: Позже нельзя будет изменить!", - "notification_email_from_address": "Адрес отправителя", - "notification_email_from_address_description": "Адрес электронной почты отправителя, например: \"Immich Photo Server \".", - "notification_email_host_description": "Доменное имя почтового сервера (например, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Игнорировать ошибки сертификата", - "notification_email_ignore_certificate_errors_description": "Игнорировать ошибки проверки сертификата TLS (не рекомендуется)", - "notification_email_password_description": "Пароль для аутентификации на сервере электронной почты", - "notification_email_port_description": "Порт почтового сервера (например, 25, 465 или 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Использовать SMTPS (SMTP через TLS)", - "notification_email_sent_test_email_button": "Отправить проверочное письмо и сохранить", - "notification_email_setting_description": "Настройки отправки уведомлений по электронной почте", - "notification_email_test_email": "Отправить проверочное письмо", - "notification_email_test_email_failed": "Не удалось отправить проверочное письмо, проверьте ваши параметры", - "notification_email_test_email_sent": "Проверочное письмо было отправлено на адрес {email}. Пожалуйста, проверьте свой почтовый ящик.", - "notification_email_username_description": "Имя пользователя для аутентификации на сервере электронной почты", - "notification_enable_email_notifications": "Включить уведомления по электронной почте", - "notification_settings": "Настройки уведомлений", - "notification_settings_description": "Управление настройками уведомлений, включая электронную почту", - "oauth_auto_launch": "Автозапуск", - "oauth_auto_launch_description": "Автоматический запуск процесса входа в систему через OAuth при переходе на страницу входа", - "oauth_auto_register": "Автоматическая регистрация", - "oauth_auto_register_description": "Автоматически регистрировать новых пользователей при входе в систему с помощью OAuth", - "oauth_button_text": "Текст кнопки", - "oauth_client_secret_description": "Требуется для конфиденциальных клиентов или если PKCE (Proof Key for Code Exchange) не поддерживается для публичных клиентов.", - "oauth_enable_description": "Вход с помощью OAuth", - "oauth_mobile_redirect_uri": "URI редиректа для мобильных", - "oauth_mobile_redirect_uri_override": "Перенаправление URI для мобильных устройств", - "oauth_mobile_redirect_uri_override_description": "Включите, если поставщик OAuth не разрешает использование мобильного URI, например, ''{callback}''", - "oauth_role_claim": "Утверждение роли", - "oauth_role_claim_description": "Автоматическое предоставление доступа администратора на основе наличия этого утверждения. Утверждение может иметь значение 'user' или 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Настройки входа через OAuth", - "oauth_settings_more_details": "Для получения дополнительной информаций об этой функции обратитесь к документации.", - "oauth_storage_label_claim": "Метка хранилища", - "oauth_storage_label_claim_description": "Автоматически установить метку хранилища пользователя на значение этого утверждения.", - "oauth_storage_quota_claim": "Квота хранилища", - "oauth_storage_quota_claim_description": "Автоматически установить квоту хранилища пользователя на значение этого утверждения.", - "oauth_storage_quota_default": "Квота хранилища по умолчанию (GiB)", - "oauth_storage_quota_default_description": "Квота в GiB, которая будет использоваться, если утверждение не задано.", - "oauth_timeout": "Таймаут для запросов", - "oauth_timeout_description": "Максимальное время, в течение которого ожидать ответа, в миллисекундах", - "ocr_job_description": "Использование машинного обучения для распознавания текста на изображениях", - "password_enable_description": "Вход по электронной почте и паролю", - "password_settings": "Настройки входа с паролем", - "password_settings_description": "Управление настройками входа по паролю", - "paths_validated_successfully": "Все пути успешно прошли проверку", - "person_cleanup_job": "Очистка персоны", - "queue_details": "Параметры очереди", - "queues": "Задачи", - "queues_page_description": "Управление регламентными задачами и просмотр статуса их выполнения", - "quota_size_gib": "Размер квоты (GiB)", - "refreshing_all_libraries": "Обновление всех библиотек", - "registration": "Регистрация администратора", - "registration_description": "Первый зарегистрированный пользователь будет назначен администратором. Он сможет управлять сервером и создавать дополнительных пользователей.", - "remove_failed_jobs": "Удалить задачи с ошибками", - "require_password_change_on_login": "Требовать смену пароля при первом входе", - "reset_settings_to_default": "Сброс настроек до значений по умолчанию", - "reset_settings_to_recent_saved": "Не сохранённые изменения сброшены к последним сохраненным значениям", - "scanning_library": "Сканирование библиотеки", - "search_jobs": "Поиск задач…", - "send_welcome_email": "Отправить приветственное письмо", - "server_external_domain_settings": "Внешний домен", - "server_external_domain_settings_description": "Домен для публичных ссылок, включая http(s)://", - "server_public_users": "Публичные пользователи", - "server_public_users_description": "Выводить список пользователей (имена и email) в общих альбомах. Когда отключено, список доступен только администраторам, пользователи смогут делиться только ссылкой.", - "server_settings": "Настройки сервера", - "server_settings_description": "Управление настройками сервера", - "server_stats_page_description": "Сводная информация по объектам и пользователям", - "server_welcome_message": "Приветственное сообщение", - "server_welcome_message_description": "Сообщение, которое будет отображаться на странице входа.", - "settings_page_description": "Управление настройками сервера", - "sidecar_job": "Метаданные из sidecar-файлов", - "sidecar_job_description": "Обнаруживает и синхронизирует метаданные из sidecar-файлов", - "slideshow_duration_description": "Длительность показа слайдов в секундах", - "smart_search_job_description": "Распознает содержимое медиафайлов для умного поиска", - "storage_template_date_time_description": "В качестве даты используется информация о времени съёмки из данных объекта", - "storage_template_date_time_sample": "Дата для примера: {date}", - "storage_template_enable_description": "Включить использование шаблона хранилища", - "storage_template_hash_verification_enabled": "Включить проверку хеша", - "storage_template_hash_verification_enabled_description": "Включает проверку хеша, не отключайте её, если не уверены в последствиях", - "storage_template_migration": "Применение шаблона хранилища", - "storage_template_migration_description": "Применяет текущий {template} к ранее загруженным объектам", - "storage_template_migration_info": "Расширения файлов всегда будут сохраняться в нижнем регистре. Изменения в шаблоне будут применяться только к новым объектам. Чтобы применить шаблон к ранее загруженным объектам, запустите {job}.", - "storage_template_migration_job": "Задача по применению шаблона хранилища", - "storage_template_more_details": "Для получения дополнительной информации об этой функции обратитесь к разделам документации Шаблон хранилища и Структура хранения файлов", - "storage_template_onboarding_description_v2": "При включении этой функции файлы будут автоматически переименовываться и распределяться по папкам на основании заданного шаблона. Дополнительная информация доступна в документации.", - "storage_template_path_length": "Примерный предел длины пути: {length, number}/{limit, number}", - "storage_template_settings": "Шаблон хранилища", - "storage_template_settings_description": "Управление структурой папок и именами загруженных файлов", - "storage_template_user_label": "{label} — метка хранилища пользователя", - "system_settings": "Системные настройки", - "tag_cleanup_job": "Очистка тега", - "template_email_available_tags": "В этом шаблоне доступны следующие переменные: {tags}", - "template_email_if_empty": "Оставьте пустым, чтобы использовать шаблон по умолчанию.", - "template_email_invite_album": "Шаблон приглашения в альбом", - "template_email_preview": "Предварительный просмотр", - "template_email_settings": "Шаблоны эл. писем", - "template_email_update_album": "Шаблон изменения альбома", - "template_email_welcome": "Шаблон приветствия", - "template_settings": "Шаблоны уведомлений", - "template_settings_description": "Настройте шаблоны уведомлений", - "theme_custom_css_settings": "Пользовательские CSS", - "theme_custom_css_settings_description": "Каскадные таблицы стилей позволяют настраивать дизайн Immich.", - "theme_settings": "Настройки темы", - "theme_settings_description": "Управление настройкой веб-интерфейса Immich", - "thumbnail_generation_job": "Создание миниатюр", - "thumbnail_generation_job_description": "Создает большие, маленькие и размытые миниатюры для каждого файла и человека", - "transcoding_acceleration_api": "API ускорителя", - "transcoding_acceleration_api_description": "Выберите подходящий используемому оборудованию API для ускорения транскодирования. Выбранное значение является «наилучшим вариантом»: в случае проблем произойдет возврат на программное транскодирование. VP9 может работать или не работать в зависимости от используемого оборудования.", - "transcoding_acceleration_nvenc": "NVENC (требуется графический процессор NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (требуется процессор Intel 7-го поколения или новее)", - "transcoding_acceleration_rkmpp": "RKMPP (только для SOC Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Поддерживаемые аудиокодеки", - "transcoding_accepted_audio_codecs_description": "Выберите аудиокодеки, которые не нужно перекодировать. Используется только для определенных политик перекодирования.", - "transcoding_accepted_containers": "Поддерживаемые контейнеры", - "transcoding_accepted_containers_description": "Выберите, какие форматы контейнеров не нужно преобразовывать в MP4. Используется только для определенных политик перекодирования.", - "transcoding_accepted_video_codecs": "Поддерживаемые видеокодеки", - "transcoding_accepted_video_codecs_description": "Выберите видеокодеки, которые не нужно перекодировать. Используется только для определенных политик перекодирования.", - "transcoding_advanced_options_description": "Параметры, которые большинству пользователей не следует менять", - "transcoding_audio_codec": "Аудиокодек", - "transcoding_audio_codec_description": "Opus — вариант самого высокого качества, но имеет меньшую совместимость со старыми устройствами или программным обеспечением.", - "transcoding_bitrate_description": "Видео с битрейтом выше максимального или в неподходящем формате может вызвать проблемы", - "transcoding_codecs_learn_more": "Для изучения терминологии, используемой здесь, обратитесь к документации FFmpeg по кодекам H.264, HEVC и VP9.", - "transcoding_constant_quality_mode": "Режим постоянного качества", - "transcoding_constant_quality_mode_description": "Режим ICQ лучше, чем CQP, но некоторые устройства аппаратного ускорения его не поддерживают. Установка этой опции будет отдавать предпочтение указанному режиму при использовании кодирования на основе качества. NVENC не поддерживает режим ICQ.", - "transcoding_constant_rate_factor": "Коэффициент постоянной скорости (-crf)", - "transcoding_constant_rate_factor_description": "Уровень качества видео. Типичные значения: 23 для H.264, 28 для HEVC, 31 для VP9 и 35 для AV1. Чем ниже, тем лучше, но при этом создаются файлы большего размера.", - "transcoding_disabled_description": "Не перекодировать видео, это может привести к сбою воспроизведения на некоторых клиентах", - "transcoding_encoding_options": "Параметры кодирования", - "transcoding_encoding_options_description": "Установите кодеки, разрешение, качество и другие параметры для кодированного видео", - "transcoding_hardware_acceleration": "Аппаратное ускорение", - "transcoding_hardware_acceleration_description": "Экспериментально: более быстрое транскодирование, но с возможным ухудшением качества при том же битрейте", - "transcoding_hardware_decoding": "Аппаратное декодирование", - "transcoding_hardware_decoding_setting_description": "Дополнительно ускоряет декодирование, а не только кодирование. Может работать не со всеми видео.", - "transcoding_max_b_frames": "Максимально промежуточных кадров", - "transcoding_max_b_frames_description": "Более высокие значения повышают эффективность сжатия, но замедляют кодирование. Может быть несовместимо с аппаратным ускорением на старых устройствах. 0 отключает B-кадры, а -1 устанавливает это значение автоматически.", - "transcoding_max_bitrate": "Максимальный битрейт", - "transcoding_max_bitrate_description": "Ограничение битрейта может сделать размер файла более предсказуемым при незначительном снижении качества. При разрешении 720p типичными значениями являются 2600 кбит/с для кодеков VP9 или HEVC и 4500 кбит/с для H.264. Ограничение отключено, если задано значение 0. Когда единица измерения не указана, предполагается k (кбит/с); поэтому значения 5000, 5000k и 5M (для Мбит/с) эквивалентны.", - "transcoding_max_keyframe_interval": "Максимальный интервал ключевых кадров", - "transcoding_max_keyframe_interval_description": "Устанавливает максимальное расстояние между ключевыми кадрами. Более низкие значения ухудшают эффективность сжатия, но сокращают время поиска и могут улучшить качество в сценах с быстрым движением. 0 устанавливает это значение автоматически.", - "transcoding_optimal_description": "Видео с разрешением выше целевого или не в принятом формате", - "transcoding_policy": "Политика перекодировки", - "transcoding_policy_description": "Установите, когда видео будет перекодировано", - "transcoding_preferred_hardware_device": "Предпочитаемое аппаратное устройство", - "transcoding_preferred_hardware_device_description": "Применяется только к VAAPI и QSV. Устанавливает dri-узел, используемый для аппаратного транскодирования.", - "transcoding_preset_preset": "Предустановка", - "transcoding_preset_preset_description": "Скорость сжатия. Медленные настройки создают более маленькие файлы и повышают качество при достижении определенного битрейта. VP9 игнорирует скорости выше “faster”.", - "transcoding_reference_frames": "Опорные кадры", - "transcoding_reference_frames_description": "Количество кадров, на которые следует ссылаться при сжатии данного кадра. Более высокие значения повышают эффективность сжатия, но замедляют кодирование. 0 устанавливает это значение автоматически.", - "transcoding_required_description": "Только видео в нестандартном формате", - "transcoding_settings": "Настройки транскодирования видео", - "transcoding_settings_description": "Управляйте тем, какие видео нужно перекодировать и как их обрабатывать", - "transcoding_target_resolution": "Целевое разрешение", - "transcoding_target_resolution_description": "Более высокие разрешения позволяют сохранить больше деталей, но требуют больше времени для кодирования, имеют больший размер файлов и могут снизить скорость отклика приложения.", - "transcoding_temporal_aq": "Temporal Adaptive Quantization (временное адаптивное квантование)", - "transcoding_temporal_aq_description": "Применимо только к NVENC (NVIDIA Encoder). Технология повышает качество высокодетализированных нединамичных сцен. Может быть несовместима со старыми устройствами.", - "transcoding_threads": "Потоки", - "transcoding_threads_description": "Более высокие значения приводят к более быстрому кодированию, но оставляют серверу меньше места для обработки других задач во время активности. Это значение не должно превышать количество ядер процессора. Максимизирует использование, если установлено значение 0.", - "transcoding_tone_mapping": "Отображение тонов", - "transcoding_tone_mapping_description": "Пытается сохранить внешний вид HDR-видео при преобразовании в SDR. Каждый алгоритм делает разные компромиссы между цветом, детализацией и яркостью. Hable сохраняет детали, Mobius сохраняет цвет, а Reinhard сохраняет яркость.", - "transcoding_transcode_policy": "Политика перекодирования", - "transcoding_transcode_policy_description": "Правила, определяющие когда видео должно быть перекодировано. HDR-видео всегда будут перекодироваться (за исключением случаев, когда перекодирование отключено).", - "transcoding_two_pass_encoding": "Двухпроходное кодирование", - "transcoding_two_pass_encoding_setting_description": "Перекодируйте за два прохода, чтобы получить более качественное кодирование видео. Когда включен максимальный битрейт (необходим для работы с H.264 и HEVC), в этом режиме используется диапазон битрейта, основанный на максимальном битрейте, и игнорируется CRF. Для VP9 можно использовать CRF, если отключен максимальный битрейт.", - "transcoding_video_codec": "Видеокодек", - "transcoding_video_codec_description": "VP9 обладает высокой эффективностью и веб-совместимостью, но перекодирование занимает больше времени. HEVC работает аналогично, но имеет меньшую веб-совместимость. H.264 широко совместим и быстро перекодируется, но создает файлы гораздо большего размера. AV1 — наиболее эффективный кодек, но он не поддерживается на старых устройствах.", - "trash_enabled_description": "Включить корзину", - "trash_number_of_days": "Срок хранения", - "trash_number_of_days_description": "Количество дней, в течение которых файлы будут храниться в корзине до окончательного удаления", - "trash_settings": "Настройки корзины", - "trash_settings_description": "Управление настройками корзины", - "unlink_all_oauth_accounts": "Отвязать все учетные записи OAuth", - "unlink_all_oauth_accounts_description": "Не забудьте отвязать все учетные записи OAuth перед миграцией к новому провайдеру.", - "unlink_all_oauth_accounts_prompt": "Вы уверены, что хотите отвязать все учетные записи OAuth? Это приведет к безвозвратному сбросу OAuth ID для каждого пользователя.", - "user_cleanup_job": "Очистка пользователя", - "user_delete_delay": "Аккаунт и файлы пользователя {user} будут отложены до окончательного удаления через {delay, plural, one {# день} few {# дня} many {# дней} other {# дня}}.", - "user_delete_delay_settings": "Отложенное удаление", - "user_delete_delay_settings_description": "Срок в днях, по истечении которого происходит окончательное удаление учётной записи пользователя и всех его объектов. Задача по удалению пользователей выполняется в полночь. Изменение этой настройки будет учтено при следующем запуске задачи.", - "user_delete_immediately": "Аккаунт и файлы пользователя {user} будут немедленно поставлены в очередь для окончательного удаления.", - "user_delete_immediately_checkbox": "Поместить пользователя и его файлы в очередь для немедленного удаления", - "user_details": "Данные пользователя", - "user_management": "Управление пользователями", - "user_password_has_been_reset": "Пароль пользователя был сброшен:", - "user_password_reset_description": "Пожалуйста, предоставьте временный пароль пользователю и сообщите ему, что при следующем входе в систему пароль нужно будет изменить.", - "user_restore_description": "Аккаунт пользователя {user} будет восстановлен.", - "user_restore_scheduled_removal": "Восстановить пользователя (окончательное удаление запланировано на {date, date, long})", - "user_settings": "Пользовательские настройки", - "user_settings_description": "Управление настройками пользователей", - "user_successfully_removed": "Пользователь {email} успешно удален.", - "users_page_description": "Управление пользователями системы", - "version_check_enabled_description": "Включить проверку наличия новых версий", - "version_check_implications": "Функция проверки версии периодически обращается к сайту github.com", - "version_check_settings": "Проверка версии", - "version_check_settings_description": "Включить/отключить уведомление о новой версии", - "video_conversion_job": "Перекодирование видео", - "video_conversion_job_description": "Перекодирует видео для более широкой совместимости с браузерами и устройствами" - }, - "admin_email": "Электронная почта администратора", - "admin_password": "Пароль администратора", - "administration": "Управление сервером", - "advanced": "Расширенные", - "advanced_settings_clear_image_cache": "Очистить кэш изображений", - "advanced_settings_clear_image_cache_error": "Не удалось очистить кэш изображений", - "advanced_settings_clear_image_cache_success": "Успешно очищено {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Подбор объектов для синхронизации на основе альтернативных критериев. Пробуйте включать только в том случае, если в приложении есть проблемы с обнаружением всех альбомов.", - "advanced_settings_enable_alternate_media_filter_title": "[ЭКСПЕРИМЕНТАЛЬНО] Использование альтернативного способа синхронизации альбомов на устройстве", - "advanced_settings_log_level_title": "Уровень логирования: {level}", - "advanced_settings_prefer_remote_subtitle": "Некоторые устройства очень медленно загружают локальные миниатюры. Активируйте эту настройку, чтобы изображения всегда загружались с сервера.", - "advanced_settings_prefer_remote_title": "Предпочитать фото на сервере", - "advanced_settings_proxy_headers_subtitle": "Определите заголовки прокси-сервера, которые Immich должен отправлять с каждым сетевым запросом", - "advanced_settings_proxy_headers_title": "[ЭКСПЕРИМЕНТАЛЬНО] Пользовательские заголовки прокси", - "advanced_settings_readonly_mode_subtitle": "Включает режим, в котором можно только просматривать объекты. Функции выбора нескольких объектов, публикации, трансляции и удаления будут недоступны. Включить/отключить этот режим можно удерживая значок аватара пользователя на главном экране.", - "advanced_settings_readonly_mode_title": "Режим «только просмотр»", - "advanced_settings_self_signed_ssl_subtitle": "Пропускать проверку SSL-сертификата сервера. Требуется для самоподписанных сертификатов.", - "advanced_settings_self_signed_ssl_title": "[ЭКСПЕРИМЕНТАЛЬНО] Разрешить самоподписанные SSL-сертификаты", - "advanced_settings_sync_remote_deletions_subtitle": "Автоматически удалять или восстанавливать объекты на этом устройстве, когда это действие выполняется через веб-интерфейс", - "advanced_settings_sync_remote_deletions_title": "[ЭКСПЕРИМЕНТАЛЬНО] Синхронизация удаления объектов", - "advanced_settings_tile_subtitle": "Расширенные настройки", - "advanced_settings_troubleshooting_subtitle": "Включить расширенные возможности для диагностики и решения проблем", - "advanced_settings_troubleshooting_title": "Режим диагностики", - "age_months": "{months, plural, one {# месяц} many {# месяцев} other {# месяца}}", - "age_year_months": "1 год {months, plural, one {# месяц} many {# месяцев} other {# месяца}}", - "age_years": "{years, plural, one {# год} many {# лет} other {# года}}", - "album": "Альбом", - "album_added": "Альбом добавлен", - "album_added_notification_setting_description": "Получать уведомление по электронной почте, когда вам предоставили доступ в общий альбом", - "album_cover_updated": "Обложка альбома обновлена", - "album_delete_confirmation": "Вы уверены, что хотите удалить альбом {album}?", - "album_delete_confirmation_description": "Если альбом был общим, другие пользователи больше не смогут получить к нему доступ.", - "album_deleted": "Альбом удалён", - "album_info_card_backup_album_excluded": "ИСКЛЮЧЕН", - "album_info_card_backup_album_included": "ВКЛЮЧЕН", - "album_info_updated": "Информация об альбоме обновлена", - "album_leave": "Покинуть альбом?", - "album_leave_confirmation": "Вы уверены, что хотите покинуть {album}?", - "album_name": "Название альбома", - "album_options": "Параметры альбома", - "album_remove_user": "Удалить пользователя?", - "album_remove_user_confirmation": "Вы уверены, что хотите удалить пользователя {user}?", - "album_search_not_found": "Не найдено альбомов по вашему запросу", - "album_selected": "Альбом выбран", - "album_share_no_users": "Нет доступных пользователей, с которыми можно поделиться альбомом.", - "album_summary": "Информация об альбоме", - "album_updated": "Альбом обновлён", - "album_updated_setting_description": "Получать уведомление по электронной почте при добавлении новых объектов в общий альбом", - "album_upload_assets": "Загрузить объекты с компьютера и добавить их в альбом", - "album_user_left": "Вы покинули {album}", - "album_user_removed": "Пользователь {user} удален", - "album_viewer_appbar_delete_confirm": "Вы уверены, что хотите удалить альбом из своей учетной записи?", - "album_viewer_appbar_share_err_delete": "Не удалось удалить альбом", - "album_viewer_appbar_share_err_leave": "Не удалось покинуть альбом", - "album_viewer_appbar_share_err_remove": "Возникли проблемы с удалением объектов из альбома", - "album_viewer_appbar_share_err_title": "Не удалось переименовать альбом", - "album_viewer_appbar_share_leave": "Покинуть альбом", - "album_viewer_appbar_share_to": "Поделиться с", - "album_viewer_page_share_add_users": "Добавить пользователей", - "album_with_link_access": "Поделитесь ссылкой на альбом, чтобы ваши друзья могли его посмотреть.", - "albums": "Альбомы", - "albums_count": "{count, plural, one {{count, number} альбом} few {{count, number} альбома} many {{count, number} альбомов} other {{count, number} альбома}}", - "albums_default_sort_order": "Порядок сортировки в альбомах по умолчанию", - "albums_default_sort_order_description": "Первоначальный порядок сортировки, устанавливаемый в новых альбомах.", - "albums_feature_description": "Коллекции фото и видео, которыми можно делиться с другими пользователями.", - "albums_on_device_count": "Альбомы на устройстве ({count})", - "albums_selected": "{count, plural, one {Выбран # альбом} many {Выбрано # альбомов} other {Выбрано # альбома}}", - "all": "Все", - "all_albums": "Все альбомы", - "all_people": "Все люди", - "all_photos": "Все фото", - "all_videos": "Все видео", - "allow_dark_mode": "Разрешить тёмный режим", - "allow_edits": "Разрешить редактирование", - "allow_public_user_to_download": "Разрешить скачивание", - "allow_public_user_to_upload": "Разрешить добавление файлов", - "allowed": "Разрешено", - "alt_text_qr_code": "QR-код", - "always_keep": "Всегда оставлять", - "always_keep_photos_hint": "Функция освобождения места оставит все фото на устройстве.", - "always_keep_videos_hint": "Функция освобождения места оставит все видео на устройстве.", - "anti_clockwise": "Против часовой", - "api_key": "API ключ", - "api_key_description": "Это значение будет показано только один раз. Пожалуйста, убедитесь, что скопировали его перед закрытием окна.", - "api_key_empty": "Имя API ключа не должно быть пустым", - "api_keys": "Ключи API", - "app_architecture_variant": "Архитектура приложения", - "app_bar_signout_dialog_content": "Вы уверены, что хотите выйти?", - "app_bar_signout_dialog_ok": "Да", - "app_bar_signout_dialog_title": "Выйти", - "app_download_links": "Ссылки для загрузки мобильного приложения", - "app_settings": "Параметры приложения", - "app_stores": "Магазины приложений", - "app_update_available": "Есть новая версия", - "appears_in": "Добавлено в", - "apply_count": "Применить ({count, number})", - "archive": "Архив", - "archive_action_prompt": "Объекты добавлены в Архив ({count} шт.)", - "archive_or_unarchive_photo": "Архивировать или разархивировать фото", - "archive_page_no_archived_assets": "В архиве сейчас пусто", - "archive_page_title": "Архив ({count})", - "archive_size": "Размер архива", - "archive_size_description": "Настройка размера архива для скачивания (в GiB)", - "archived": "Архив", - "archived_count": "{count, plural, one {# объект перенесён} many {# объектов перенесено} other {# объекта перенесено}} в архив", - "are_these_the_same_person": "Это один и тот же человек?", - "are_you_sure_to_do_this": "Вы уверены, что хотите это сделать?", - "array_field_not_fully_supported": "Поля массивов требуют ручного редактирования JSON", - "asset_action_delete_err_read_only": "Невозможно удалить объект(ы) только для чтения, пропуск", - "asset_action_share_err_offline": "Невозможно получить оффлайн-объект(ы), пропуск", - "asset_added_to_album": "Добавлено в альбом", - "asset_adding_to_album": "Добавление в альбом…", - "asset_created": "Объект создан", - "asset_description_updated": "Описание обновлено", - "asset_filename_is_offline": "Объект {filename} находится в офлайн-режиме", - "asset_has_unassigned_faces": "Есть не распознанные лица", - "asset_hashing": "Хеширование…", - "asset_list_group_by_sub_title": "Группировать по", - "asset_list_layout_settings_dynamic_layout_title": "Динамическое расположение", - "asset_list_layout_settings_group_automatically": "Автоматически", - "asset_list_layout_settings_group_by": "Группировать объекты по", - "asset_list_layout_settings_group_by_month_day": "Месяцу и дню", - "asset_list_layout_sub_title": "Разметка", - "asset_list_settings_subtitle": "Настройка сетки фотографий", - "asset_list_settings_title": "Сетка фотографий", - "asset_not_found_on_device_android": "Объект не найден на устройстве", - "asset_not_found_on_device_ios": "Объект не найден на устройстве. Если используется iCloud, доступ к объекта может быть затруднен из-за некорректного хранения файла в iCloud.", - "asset_not_found_on_icloud": "Объект не найден в iCloud. Возможно, файл недоступен из-за некорректного хранения в iCloud.", - "asset_offline": "Объект отключён", - "asset_offline_description": "Этот внешний файл не найден на диске. Пожалуйста, свяжитесь с администратором Immich для получения помощи.", - "asset_restored_successfully": "Объект успешно восстановлен", - "asset_skipped": "Пропущено", - "asset_skipped_in_trash": "В корзине", - "asset_trashed": "Объект удалён", - "asset_troubleshoot": "Данные для диагностики", - "asset_uploaded": "Загружено", - "asset_uploading": "Загрузка…", - "asset_viewer_settings_subtitle": "Параметры отображения", - "asset_viewer_settings_title": "Просмотр объектов", - "assets": "Объекты", - "assets_added_count": "{count, plural, one {Добавлен # объект} many {Добавлено # объектов} other {Добавлено # объекта}}", - "assets_added_to_album_count": "В альбом {count, plural, one {добавлен # объект} many {добавлено # объектов} other {добавлено # объекта}}", - "assets_added_to_albums_count": "{assetTotal, plural, one {# объект добавлен} many {# объектов добавлены} other {# объекта добавлены}} в {albumTotal, plural, one {# альбом} many {# альбомов} other {# альбома}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Объект не может быть добавлен} other {Объекты не могут быть добавлены}} в альбом", - "assets_cannot_be_added_to_albums": "{count, plural, one {# объект} many {# объектов} other {# объекта}} не могут быть добавлены ни в один альбом", - "assets_count": "{count, plural, one {# объект} many {# объектов} other {# объекта}}", - "assets_deleted_permanently": "Объекты безвозвратно удалены ({count} шт.)", - "assets_deleted_permanently_from_server": "Объекты безвозвратно удалены с сервера Immich ({count} шт.)", - "assets_downloaded_failed": "{count, plural, one {Скачан # файл} many {Скачано # файлов} other {Скачано # файла}}, {error} - сбой", - "assets_downloaded_successfully": "Успешно {count, plural, one {скачан # файл} many {скачано # файлов} other {скачано # файла}}", - "assets_moved_to_trash_count": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в корзину", - "assets_permanently_deleted_count": "{count, plural, one {# объект безвозвратно удалён} many {# объектов безвозвратно удалено} other {# объекта безвозвратно удалены}}", - "assets_removed_count": "{count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}}", - "assets_removed_permanently_from_device": "Объекты безвозвратно удалены с вашего устройства ({count} шт.)", - "assets_restore_confirmation": "Вы действительно хотите восстановить все объекты из корзины? Это действие нельзя отменить! Обратите внимание, объекты на устройстве не будут восстановлены таким способом.", - "assets_restored_count": "{count, plural, one {# объект восстановлен} many {# объектов восстановлены} other {# объекта восстановлены}}", - "assets_restored_successfully": "Объекты успешно восстановлены ({count} шт.)", - "assets_trashed": "Объекты перемещены в корзину ({count} шт.)", - "assets_trashed_count": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в корзину", - "assets_trashed_from_server": "Объекты перемещены в корзину на сервере Immich ({count} шт.)", - "assets_were_part_of_album_count": "{count, plural, one {# объект} many {# объектов} other {# объекта}} уже в альбоме", - "assets_were_part_of_albums_count": "{count, plural, one {# объект} many {# объектов} other {# объекта}} уже в альбомах", - "authorized_devices": "Авторизованные устройства", - "automatic_endpoint_switching_subtitle": "Подключаться локально по выбранной сети и использовать альтернативные адреса в ином случае", - "automatic_endpoint_switching_title": "Автоматическая смена URL", - "autoplay_slideshow": "Автовоспроизведение", - "back": "Назад", - "back_close_deselect": "Назад, закрыть или отменить выбор", - "background_backup_running_error": "Выполняется фоновое резервное копирование, запуск вручную пока невозможен", - "background_location_permission": "Доступ к местоположению в фоне", - "background_location_permission_content": "Чтобы считывать имя Wi-Fi сети в фоне, приложению *всегда* необходим доступ к точному местоположению устройства", - "background_options": "Выполнение фоновых задач", - "backup": "Резервное копирование", - "backup_album_selection_page_albums_device": "Альбомы на устройстве ({count})", - "backup_album_selection_page_albums_tap": "Нажмите, чтобы включить, дважды, чтобы исключить", - "backup_album_selection_page_assets_scatter": "Ваши фото и видео могут находиться в разных альбомах/папках на устройстве. Вы можете выбрать, какие альбомы включить, а какие исключить из резервного копирования.", - "backup_album_selection_page_select_albums": "Выбор альбомов", - "backup_album_selection_page_selection_info": "Выбранные альбомы", - "backup_album_selection_page_total_assets": "Всего уникальных объектов", - "backup_albums_sync": "Синхронизация альбомов", - "backup_all": "Все", - "backup_background_service_backup_failed_message": "Не удалось выполнить резервное копирование. Повторная попытка…", - "backup_background_service_complete_notification": "Резервное копирование объектов завершено", - "backup_background_service_connection_failed_message": "Не удалось подключиться к серверу. Повторная попытка…", - "backup_background_service_current_upload_notification": "Загружается {filename}", - "backup_background_service_default_notification": "Поиск новых объектов…", - "backup_background_service_error_title": "Ошибка резервного копирования", - "backup_background_service_in_progress_notification": "Резервное копирование объектов…", - "backup_background_service_upload_failure_notification": "Ошибка загрузки {filename}", - "backup_controller_page_albums": "Альбомы", - "backup_controller_page_background_app_refresh_disabled_content": "Включите фоновое обновление приложения в Настройки > Общие > Фоновое обновление приложений, чтобы использовать фоновое резервное копирование.", - "backup_controller_page_background_app_refresh_disabled_title": "Фоновое обновление отключено", - "backup_controller_page_background_app_refresh_enable_button_text": "Перейти в настройки", - "backup_controller_page_background_battery_info_link": "Подробнее", - "backup_controller_page_background_battery_info_message": "Для стабильного резервного копирования в фоновом режиме, отключите любые настройки оптимизации батареи, ограничивающие фоновую активность приложения.\n\nПоскольку настройки зависят от устройства, найдите необходимую информацию для производителя вашего устройства.", - "backup_controller_page_background_battery_info_ok": "ОК", - "backup_controller_page_background_battery_info_title": "Оптимизация батареи", - "backup_controller_page_background_charging": "Только во время зарядки", - "backup_controller_page_background_configure_error": "Не удалось настроить фоновую службу", - "backup_controller_page_background_delay": "Задержка перед загрузкой новых объектов: {duration}", - "backup_controller_page_background_description": "Включите фоновую службу для автоматического резервного копирования любых новых объектов без необходимости открывать приложение", - "backup_controller_page_background_is_off": "Автоматическое резервное копирование в фоновом режиме отключено", - "backup_controller_page_background_is_on": "Автоматическое резервное копирование в фоновом режиме включено", - "backup_controller_page_background_turn_off": "Выключить фоновую службу", - "backup_controller_page_background_turn_on": "Включить фоновую службу", - "backup_controller_page_background_wifi": "Только через Wi-Fi", - "backup_controller_page_backup": "Загружено", - "backup_controller_page_backup_selected": "Выбраны: ", - "backup_controller_page_backup_sub": "Загруженные фото и видео", - "backup_controller_page_created": "Создано: {date}", - "backup_controller_page_desc_backup": "Включите резервное копирование в активном режиме, чтобы автоматически загружать новые объекты при открытии приложения.", - "backup_controller_page_excluded": "Исключены: ", - "backup_controller_page_failed": "Неудачных ({count})", - "backup_controller_page_filename": "Имя файла: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Информация о резервном копировании", - "backup_controller_page_none_selected": "Не выбрано", - "backup_controller_page_remainder": "Осталось", - "backup_controller_page_remainder_sub": "Фото и видео для загрузки", - "backup_controller_page_server_storage": "Хранилище на сервере", - "backup_controller_page_start_backup": "Начать резервное копирование", - "backup_controller_page_status_off": "Автоматическое резервное копирование в активном режиме выключено", - "backup_controller_page_status_on": "Автоматическое резервное копирование в активном режиме включено", - "backup_controller_page_storage_format": "{used} из {total}", - "backup_controller_page_to_backup": "Альбомы для резервного копирования", - "backup_controller_page_total_sub": "Все уникальные фото и видео из выбранных альбомов", - "backup_controller_page_turn_off": "Выключить", - "backup_controller_page_turn_on": "Включить", - "backup_controller_page_uploading_file_info": "Информация о загружаемом файле", - "backup_err_only_album": "Невозможно удалить единственный альбом", - "backup_error_sync_failed": "Сбой синхронизации. Невозможно выполнить резервное копирование.", - "backup_info_card_assets": "объектов", - "backup_manual_cancelled": "Отменено", - "backup_manual_in_progress": "Загрузка в процессе. Попробуйте позже", - "backup_manual_success": "Успешно", - "backup_manual_title": "Статус загрузки", - "backup_options": "Параметры резервного копирования", - "backup_options_page_title": "Резервное копирование", - "backup_setting_subtitle": "Настройка активного и фонового резервного копирования", - "backup_settings_subtitle": "Настройка загрузки объектов", - "backup_upload_details_page_more_details": "Подробная информация", - "backward": "Назад", - "biometric_auth_enabled": "Биометрическая аутентификация включена", - "biometric_locked_out": "Вам закрыт доступ к биометрической аутентификации", - "biometric_no_options": "Биометрическая аутентификация недоступна", - "biometric_not_available": "Биометрическая аутентификация недоступна на этом устройстве", - "birthdate_saved": "Дата рождения успешно сохранена", - "birthdate_set_description": "Дата рождения используется для определения возраста человека на момент фотографии.", - "blurred_background": "Размытый фон", - "bugs_and_feature_requests": "Ошибки и запросы", - "build": "Сборка", - "build_image": "Версия сборки", - "bulk_delete_duplicates_confirmation": "Вы уверены, что хотите удалить {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Будет сохранён самый большой файл в каждой группе, а его дубликаты безвозвратно удалены. Это действие нельзя отменить!", - "bulk_keep_duplicates_confirmation": "Вы уверены, что хотите оставить {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Это сохранит все дубликаты.", - "bulk_trash_duplicates_confirmation": "Вы уверены, что хотите переместить в корзину {count, plural, one {# дублирующийся объект} many {# дублирующихся объектов} other {# дублирующихся объекта}}? Будет сохранён самый большой файл в каждой группе, а его дубликаты перемещены в корзину.", - "buy": "Приобретение лицензии Immich", - "cache_settings_clear_cache_button": "Очистить кэш", - "cache_settings_clear_cache_button_title": "Очищает кэш приложения. Это негативно повлияет на производительность, пока кэш не будет создан заново.", - "cache_settings_duplicated_assets_clear_button": "ОЧИСТИТЬ", - "cache_settings_duplicated_assets_subtitle": "Фото и видео, пропускаемые приложением", - "cache_settings_duplicated_assets_title": "Дубликаты ({count})", - "cache_settings_statistics_album": "Миниатюры библиотеки", - "cache_settings_statistics_full": "Полные изображения", - "cache_settings_statistics_shared": "Миниатюры общих альбомов", - "cache_settings_statistics_thumbnail": "Миниатюры", - "cache_settings_statistics_title": "Размер кэша", - "cache_settings_subtitle": "Управление кэшированием мобильного приложения", - "cache_settings_tile_subtitle": "Управление локальным хранилищем", - "cache_settings_tile_title": "Локальное хранилище", - "cache_settings_title": "Настройки кэширования", - "camera": "Камера", - "camera_brand": "Производитель", - "camera_model": "Модель", - "cancel": "Отменить", - "cancel_search": "Отменить поиск", - "canceled": "Отменено", - "canceling": "Отмена", - "cannot_merge_people": "Невозможно объединить людей", - "cannot_undo_this_action": "Это действие нельзя отменить!", - "cannot_update_the_description": "Невозможно обновить описание", - "cast": "Трансляция", - "cast_description": "Выбор доступных способов для трансляции", - "change_date": "Изменить дату", - "change_description": "Изменить описание", - "change_display_order": "Изменить порядок отображения", - "change_expiration_time": "Изменить срок действия", - "change_location": "Изменить местоположение", - "change_name": "Изменить имя", - "change_name_successfully": "Имя успешно изменено", - "change_password": "Изменить пароль", - "change_password_description": "Это либо первый вход в систему, либо был сделан запрос на изменение пароля. Пожалуйста, введите новый пароль ниже.", - "change_password_form_confirm_password": "Подтвердите пароль", - "change_password_form_description": "Привет, {name}!\n\nЛибо ваш первый вход в систему, либо вы запросили смену пароля. Пожалуйста, введите новый пароль ниже.", - "change_password_form_log_out": "Завершить сеансы на других устройствах", - "change_password_form_log_out_description": "Рекомендуется сбросить авторизацию на всех других ранее подключенных устройствах", - "change_password_form_new_password": "Новый пароль", - "change_password_form_password_mismatch": "Пароли не совпадают", - "change_password_form_reenter_new_password": "Повторно введите новый пароль", - "change_pin_code": "Изменить PIN-код", - "change_trigger": "Изменить триггер", - "change_trigger_prompt": "Вы действительно хотите изменить это событие? Изменение события приведёт к удалению уже созданных отборов и действий.", - "change_your_password": "Изменить свой пароль", - "changed_visibility_successfully": "Видимость успешно изменена", - "charging": "При зарядке", - "charging_requirement_mobile_backup": "Запускать резервное копирование только во время зарядки", - "check_corrupt_asset_backup": "Проверка поврежденных резервных копий", - "check_corrupt_asset_backup_button": "Проверить", - "check_corrupt_asset_backup_description": "Запускайте проверку только через Wi-Fi и после создания резервной копии всех объектов. Операция может занять несколько минут.", - "check_logs": "Проверить журналы", - "checksum": "Контрольная сумма", - "choose_matching_people_to_merge": "Выберите подходящих людей для слияния", - "city": "Город", - "cleanup_confirm_description": "Обнаружены объекты ({count} шт.), созданные до {date} и уже загруженные на сервер. Удалить их копии с устройства?", - "cleanup_confirm_prompt_title": "Удалить с устройства?", - "cleanup_deleted_assets": "Объекты перемещены в корзину устройства ({count} шт.)", - "cleanup_deleting": "Перемещение в корзину...", - "cleanup_found_assets": "Найдены уже загруженные на сервер объекты ({count} шт.)", - "cleanup_found_assets_with_size": "Найдены сохранённые на сервер объекты ({count} шт.) ({size})", - "cleanup_icloud_shared_albums_excluded": "Общие альбомы iCloud исключены из сканирования", - "cleanup_no_assets_found": "Не обнаружено объектов по указанным критериям. Освободить место можно только удалив объекты, которые загружены на сервер.", - "cleanup_preview_title": "Объекты для удаления ({count} шт.)", - "cleanup_step3_description": "Поиск объектов, которые уже сохранены на сервере, соответствующих дате и настройкам исключений.", - "cleanup_step4_summary": "Объекты ({count} шт.), созданные до {date}, в очереди на удаление с устройства. Они по-прежнему будут доступны в приложении Immich.", - "cleanup_trash_hint": "Чтобы полностью освободить место на устройстве, откройте приложение системной галереи и очистите корзину", - "clear": "Очистить", - "clear_all": "Очистить всё", - "clear_all_recent_searches": "Очистить все недавние результаты поиска", - "clear_file_cache": "Очистить файловый кэш", - "clear_message": "Очистить сообщение", - "clear_value": "Очистить значение", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Введите пароль", - "client_cert_import": "Импорт", - "client_cert_import_success_msg": "Клиентский сертификат импортирован", - "client_cert_invalid_msg": "Неверный файл сертификата или неверный пароль", - "client_cert_remove_msg": "Клиентский сертификат удален", - "client_cert_subtitle": "Поддерживается только формат PKCS12 (.p12, .pfx). Импорт/удаление сертификата доступно только перед входом в систему.", - "client_cert_title": "[ЭКСПЕРИМЕНТАЛЬНО] Клиентский SSL-сертификат", - "clockwise": "По часовой", - "close": "Закрыть", - "collapse": "Свернуть", - "collapse_all": "Свернуть всё", - "color": "Цвет", - "color_theme": "Цветовая тема", - "command": "Команда", - "comment_deleted": "Комментарий удалён", - "comment_options": "Действия с комментарием", - "comments_and_likes": "Комментарии и отметки \"нравится\"", - "comments_are_disabled": "Комментарии отключены", - "common_create_new_album": "Создать новый альбом", - "completed": "Завершено", - "confirm": "Подтвердить", - "confirm_admin_password": "Подтверждение пароля администратора", - "confirm_delete_face": "Удалить лицо человека {name} из этого объекта?", - "confirm_delete_shared_link": "Вы действительно хотите удалить эту публичную ссылку?", - "confirm_keep_this_delete_others": "Все объекты в группе кроме текущего будут удалены. Продолжить?", - "confirm_new_pin_code": "Подтвердите новый PIN-код", - "confirm_password": "Подтвердите пароль", - "confirm_tag_face": "Вы хотите отметить этого человека как {name}?", - "confirm_tag_face_unnamed": "Хотите отметить этого человека?", - "connected_device": "Подключенное устройство", - "connected_to": "Подключено к", - "contain": "Вписать", - "context": "Контекст", - "continue": "Продолжить", - "control_bottom_app_bar_create_new_album": "Создать альбом", - "control_bottom_app_bar_delete_from_immich": "Удалить из Immich", - "control_bottom_app_bar_delete_from_local": "Удалить с устройства", - "control_bottom_app_bar_edit_location": "Изменить место", - "control_bottom_app_bar_edit_time": "Изменить дату", - "control_bottom_app_bar_share_link": "Создать ссылку", - "control_bottom_app_bar_share_to": "Поделиться с", - "control_bottom_app_bar_trash_from_immich": "В корзину", - "copied_image_to_clipboard": "Изображение скопировано в буфер обмена.", - "copied_to_clipboard": "Скопировано в буфер обмена!", - "copy_error": "Скопировать ошибку", - "copy_file_path": "Копировать путь к файлу", - "copy_image": "Копировать", - "copy_link": "Копировать ссылку", - "copy_link_to_clipboard": "Скопировать ссылку в буфер обмена", - "copy_password": "Скопировать пароль", - "copy_to_clipboard": "Скопировать в буфер обмена", - "country": "Страна", - "cover": "Обрезать", - "covers": "Обложки", - "create": "Создать", - "create_album": "Создать альбом", - "create_album_page_untitled": "Без названия", - "create_api_key": "Создать API ключ", - "create_first_workflow": "Создать первый рабочий процесс", - "create_library": "Создать библиотеку", - "create_link": "Создать ссылку", - "create_link_to_share": "Создать ссылку общего доступа", - "create_link_to_share_description": "Разрешить всем, у кого есть ссылка, просматривать выбранные фотографии", - "create_new": "СОЗДАТЬ НОВЫЙ", - "create_new_person": "Добавить нового человека", - "create_new_person_hint": "Назначить выбранные объекты на нового человека", - "create_new_user": "Создать нового пользователя", - "create_shared_album_page_share_add_assets": "ДОБАВИТЬ ОБЪЕКТЫ", - "create_shared_album_page_share_select_photos": "Выбрать фотографии", - "create_shared_link": "Создать общую ссылку", - "create_tag": "Создать тег", - "create_tag_description": "Создайте новый тег. Для вложенных тегов введите полный путь к тегу, включая слэши.", - "create_user": "Создать пользователя", - "create_workflow": "Создать рабочий процесс", - "created": "Создан", - "created_at": "Создан", - "creating_linked_albums": "Создание связанных альбомов...", - "crop": "Обрезать", - "crop_aspect_ratio_fixed": "Фиксированный", - "crop_aspect_ratio_free": "Свободно", - "crop_aspect_ratio_original": "Оригинал", - "curated_object_page_title": "Предметы", - "current_device": "Текущее устройство", - "current_pin_code": "Текущий PIN-код", - "current_server_address": "Текущий адрес сервера", - "custom_date": "Произвольная дата", - "custom_locale": "Пользовательский регион", - "custom_locale_description": "Форматирование дат и чисел в зависимости от языка и региона", - "custom_url": "Свой URL", - "cutoff_date_description": "Оставить фото за последние…", - "cutoff_day": "{count, plural, one {день} many {дней} other {дня}}", - "cutoff_year": "{count, plural, one {год} many {лет} other {года}}", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "Тёмная", - "dark_theme": "Включить/выключить тёмную тему", - "date": "Дата", - "date_after": "Дата после", - "date_and_time": "Дата и время", - "date_before": "Дата до", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "Дата рождения успешно сохранена", - "date_range": "Диапазон дат", - "day": "День", - "days": "Дни", - "deduplicate_all": "Убрать все дубликаты", - "deduplication_criteria_1": "Размер изображения в байтах", - "deduplication_criteria_2": "Количество EXIF данных", - "deduplication_info": "Информация о дедупликации", - "deduplication_info_description": "Для автоматического выбора лучших объектов среди дубликатов анализируется следующая информация:", - "default_locale": "Дата и время по умолчанию", - "default_locale_description": "Использовать формат даты и времени в соответствии с языковым стандартом вашего браузера", - "delete": "Удалить", - "delete_action_confirmation_message": "Вы действительно хотите удалить этот объект? Это действие переместит объект в корзину сервера и попробует удалить его локально.", - "delete_action_prompt": "Объекты удалены ({count} шт.)", - "delete_album": "Удалить альбом", - "delete_api_key_prompt": "Вы действительно хотите удалить этот API ключ?", - "delete_dialog_alert": "Эти элементы будут безвозвратно удалены с сервера, а также с вашего устройства", - "delete_dialog_alert_local": "Эти объекты будут безвозвратно удалены с вашего устройства, но по-прежнему будут доступны на сервере Immich", - "delete_dialog_alert_local_non_backed_up": "Некоторые объекты не были загружены в Immich и будут безвозвратно удалены с вашего устройства", - "delete_dialog_alert_remote": "Эти объекты будут безвозвратно удалены с сервера Immich", - "delete_dialog_ok_force": "Все равно удалить", - "delete_dialog_title": "Удалить навсегда", - "delete_duplicates_confirmation": "Вы действительно хотите безвозвратно удалить эти дубликаты?", - "delete_face": "Удалить лицо", - "delete_key": "Удалить ключ", - "delete_library": "Удалить библиотеку", - "delete_link": "Удалить ссылку", - "delete_local_action_prompt": "Объекты удалены с устройства ({count} шт.)", - "delete_local_dialog_ok_backed_up_only": "Удалить только резервные копии", - "delete_local_dialog_ok_force": "Все равно удалить", - "delete_others": "Удалить остальные", - "delete_permanently": "Удалить навсегда", - "delete_permanently_action_prompt": "Объекты удалены безвозвратно ({count} шт.)", - "delete_shared_link": "Удалить публичную ссылку", - "delete_shared_link_dialog_title": "Удалить публичную ссылку", - "delete_tag": "Удалить тег", - "delete_tag_confirmation_prompt": "Вы уверены, что хотите удалить тег {tagName}?", - "delete_user": "Удалить пользователя", - "deleted_shared_link": "Публичная ссылка удалена", - "deletes_missing_assets": "Удаляет объекты, отсутствующие на диске", - "description": "Описание", - "description_input_hint_text": "Добавить описание...", - "description_input_submit_error": "Не удалось обновить описание, проверьте логи, чтобы узнать причину", - "deselect_all": "Снять выделение", - "details": "Подробности", - "direction": "Направление", - "disable": "Отключить", - "disabled": "Отключено", - "disallow_edits": "Запретить редактирование", - "discord": "Discord", - "discover": "Обнаружить", - "discovered_devices": "Обнаруженные устройства", - "dismiss_all_errors": "Сбросить все ошибки", - "dismiss_error": "Сбросить ошибку", - "display_options": "Дополнительно", - "display_order": "Порядок отображения", - "display_original_photos": "Отображение оригинальных фотографий", - "display_original_photos_setting_description": "Открывать при просмотре оригинал фотографии вместо миниатюры, если исходный формат поддерживается браузером. Возможно снижение скорости отображения фотографий.", - "do_not_show_again": "Больше не показывать это сообщение", - "documentation": "Документация", - "done": "Готово", - "download": "Скачать", - "download_action_prompt": "Загружаются {count} объектов", - "download_canceled": "Загрузка отменена", - "download_complete": "Загрузка окончена", - "download_enqueue": "Загрузка в очереди", - "download_error": "Ошибка загрузки", - "download_failed": "Загрузка не удалась", - "download_finished": "Загрузка окончена", - "download_include_embedded_motion_videos": "Встроенные видео", - "download_include_embedded_motion_videos_description": "Сохранять видео, встроенные в живые фото, в виде отдельных файлов", - "download_notfound": "Загрузка не найдена", - "download_original": "Скачать оригинал", - "download_paused": "Загрузка приостановлена", - "download_settings": "Скачивание", - "download_settings_description": "Управление настройками скачивания объектов", - "download_started": "Загрузка началась", - "download_sucess": "Успешная загрузка", - "download_sucess_android": "Медиафайлы загружены в DCIM/Immich", - "download_waiting_to_retry": "Ожидание повторной попытки", - "downloading": "Загрузка", - "downloading_asset_filename": "Загрузка объекта {filename}", - "downloading_from_icloud": "Загрузка из iCloud", - "downloading_media": "Загрузка медиа", - "drop_files_to_upload": "Перенесите файлы в любое место для загрузки", - "duplicates": "Дубликаты", - "duplicates_description": "Просмотрите найденные дубликаты и в каждой группе укажите, какие объекты оставить, а какие удалить", - "duration": "Продолжительность", - "edit": "Изменить", - "edit_album": "Изменить альбом", - "edit_avatar": "Изменить аватар", - "edit_birthday": "Изменить дату рождения", - "edit_date": "Изменить дату", - "edit_date_and_time": "Изменить дату и время", - "edit_date_and_time_action_prompt": "Дата и время изменены у {count} объектов", - "edit_date_and_time_by_offset": "Изменить дату по смещению", - "edit_date_and_time_by_offset_interval": "Новый диапазон дат: {from} - {to}", - "edit_description": "Изменить описание", - "edit_description_prompt": "Укажите новое описание:", - "edit_exclusion_pattern": "Редактирование шаблона исключения", - "edit_faces": "Редактирование лиц", - "edit_key": "Изменить ключ", - "edit_link": "Изменить ссылку", - "edit_location": "Изменить местоположение", - "edit_location_action_prompt": "Места изменены ({count} шт.)", - "edit_location_dialog_title": "Местоположение", - "edit_name": "Изменить имя", - "edit_people": "Изменить людей", - "edit_tag": "Изменить тег", - "edit_title": "Изменить заголовок", - "edit_user": "Изменить пользователя", - "edit_workflow": "Редактировать рабочий процесс", - "editor": "Редактор", - "editor_close_without_save_prompt": "Изменения не будут сохранены", - "editor_close_without_save_title": "Закрыть редактор?", - "editor_confirm_reset_all_changes": "Отменить все сделанные изменения?", - "editor_flip_horizontal": "Отразить горизонтально", - "editor_flip_vertical": "Отразить вертикально", - "editor_orientation": "Ориентация", - "editor_reset_all_changes": "Сбросить изменения", - "editor_rotate_left": "Повернуть на 90° против часовой стрелки", - "editor_rotate_right": "Повернуть на 90° по часовой стрелке", - "email": "Электронная почта", - "email_notifications": "Уведомления по электронной почте", - "empty_folder": "Пустая папка", - "empty_trash": "Очистить корзину", - "empty_trash_confirmation": "Вы действительно хотите очистить корзину? Все объекты в ней будут безвозвратно удалены\nВы не сможете отменить это действие!", - "enable": "Включить", - "enable_backup": "Активировать", - "enable_biometric_auth_description": "Введите свой PIN-код для включения биометрической аутентификации", - "enabled": "Включено", - "end_date": "Дата окончания", - "enqueued": "Занесено в очередь", - "enter_wifi_name": "Введите имя Wi-Fi сети", - "enter_your_pin_code": "Введите ваш PIN-код", - "enter_your_pin_code_subtitle": "Введите свой PIN-код для доступа к личной папке", - "error": "Ошибка", - "error_change_sort_album": "Не удалось изменить порядок сортировки альбома", - "error_delete_face": "Ошибка при удалении лица из объекта", - "error_getting_places": "Ошибка получения мест", - "error_loading_albums": "Ошибка при загрузке альбомов", - "error_loading_image": "Ошибка при загрузке изображения", - "error_loading_partners": "Ошибка загрузки партнёров: {error}", - "error_retrieving_asset_information": "Ошибка получения информации об объекте", - "error_saving_image": "Ошибка: {error}", - "error_tag_face_bounding_box": "Ошибка при добавлении отметки - не удалось получить координаты рамки лица", - "error_title": "Ошибка - Что-то пошло не так", - "error_while_navigating": "Ошибка при переходе к объекту", - "errors": { - "cannot_navigate_next_asset": "Не удалось перейти к следующему объекту", - "cannot_navigate_previous_asset": "Не удалось перейти к предыдущему объекту", - "cant_apply_changes": "Не удается применить изменения", - "cant_change_activity": "Не удается {enabled, select, true {отключить} other {включить}} активность", - "cant_change_asset_favorite": "Не удалось изменить статус \"Избранное\" для объекта", - "cant_change_metadata_assets_count": "Не удалось изменить метаданные у {count, plural, one {# объекта} other {# объектов}}", - "cant_get_faces": "Не удается получить лица", - "cant_get_number_of_comments": "Не удается получить количество комментариев", - "cant_search_people": "Не удается выполнить поиск людей", - "cant_search_places": "Не удается выполнить поиск мест", - "error_adding_assets_to_album": "Ошибка при добавлении объектов в альбом", - "error_adding_users_to_album": "Ошибка при добавлении пользователей в альбом", - "error_deleting_shared_user": "Ошибка при удалении пользователя с общим доступом", - "error_downloading": "Ошибка при загрузке {filename}", - "error_hiding_buy_button": "Ошибка скрытия кнопки", - "error_removing_assets_from_album": "Ошибка при удалении объектов из альбома, проверьте консоль для получения дополнительной информации", - "error_selecting_all_assets": "Ошибка при выборе всех объектов", - "exclusion_pattern_already_exists": "Такая модель исключения уже существует.", - "failed_to_create_album": "Не удалось создать альбом", - "failed_to_create_shared_link": "Не удалось создать публичную ссылку", - "failed_to_edit_shared_link": "Не удалось изменить публичную ссылку", - "failed_to_get_people": "Не удалось получить информацию о людях", - "failed_to_keep_this_delete_others": "Не удалось сохранить этот объект и удалить другие объекты", - "failed_to_load_asset": "Ошибка загрузки объекта", - "failed_to_load_assets": "Не удалось загрузить объекты", - "failed_to_load_notifications": "Сбой при загрузке уведомлений", - "failed_to_load_people": "Не удалось загрузить людей", - "failed_to_remove_product_key": "Не удалось удалить ключ продукта", - "failed_to_reset_pin_code": "Не удалось сбросить PIN-код", - "failed_to_stack_assets": "Не удалось сгруппировать объекты", - "failed_to_unstack_assets": "Не удалось разгруппировать объекты", - "failed_to_update_notification_status": "Не удалось обновить статус уведомления", - "incorrect_email_or_password": "Неверный адрес электронной почты или пароль", - "library_folder_already_exists": "Такая папка уже есть в списке.", - "paths_validation_failed": "{paths, plural, one {# путь не прошёл} many {# путей не прошли} other {# пути не прошли}} проверку", - "profile_picture_transparent_pixels": "Фотография профиля не должна содержать прозрачных пикселей. Попробуйте увеличить и/или переместить изображение.", - "quota_higher_than_disk_size": "Вы установили квоту, превышающую размер диска", - "something_went_wrong": "Что-то пошло не так", - "unable_to_add_album_users": "Не удалось добавить пользователей в альбом", - "unable_to_add_assets_to_shared_link": "Не удалось добавить объекты к публичной ссылке", - "unable_to_add_comment": "Не удалось добавить комментарий", - "unable_to_add_exclusion_pattern": "Не удалось добавить шаблон исключения", - "unable_to_add_partners": "Не удалось добавить партнёров", - "unable_to_add_remove_archive": "Не удалось {archived, select, true {удалить объект из архива} other {добавить объект в архив}}", - "unable_to_add_remove_favorites": "Не удалось {favorite, select, true {добавить объект в избранное} other {удалить объект из избранного}}", - "unable_to_archive_unarchive": "Не удалось {archived, select, true {архивировать} other {разархивировать}}", - "unable_to_change_album_user_role": "Не удалось изменить роль пользователя в альбоме", - "unable_to_change_date": "Не удалось изменить дату", - "unable_to_change_description": "Не удалось изменить описание", - "unable_to_change_favorite": "Не удалось изменить признак \"избранное\" для объекта", - "unable_to_change_location": "Не удалось изменить местоположение", - "unable_to_change_password": "Не удалось изменить пароль", - "unable_to_change_visibility": "Не удалось изменить видимость у {count, plural, one {# человека} other {# человек}}", - "unable_to_complete_oauth_login": "Не удалось выполнить вход с помощью OAuth", - "unable_to_connect": "Не удалось подключиться", - "unable_to_copy_to_clipboard": "Не удалось скопировать в буфер обмена, убедитесь, что вы получаете доступ к странице по протоколу https", - "unable_to_create": "Не удалось создать рабочий процесс", - "unable_to_create_admin_account": "Не удалось создать учетную запись администратора", - "unable_to_create_api_key": "Не удалось создать новый API ключ", - "unable_to_create_library": "Не удалось создать библиотеку", - "unable_to_create_user": "Не удалось создать пользователя", - "unable_to_delete_album": "Не удалось удалить альбом", - "unable_to_delete_asset": "Не удалось удалить объект", - "unable_to_delete_assets": "Ошибка при удалении объектов", - "unable_to_delete_exclusion_pattern": "Не удалось удалить шаблон исключения", - "unable_to_delete_shared_link": "Не удалось удалить публичную ссылку", - "unable_to_delete_user": "Не удалось удалить пользователя", - "unable_to_delete_workflow": "Не удалось удалить рабочий процесс", - "unable_to_download_files": "Не удалось скачать файлы", - "unable_to_edit_exclusion_pattern": "Не удалось отредактировать шаблон исключения", - "unable_to_empty_trash": "Не удалось очистить корзину", - "unable_to_enter_fullscreen": "Не удалось переключиться в полноэкранный режим", - "unable_to_exit_fullscreen": "Не удалось выйти из полноэкранного режима", - "unable_to_get_comments_number": "Не удалось получить количество комментариев", - "unable_to_get_shared_link": "Не удалось получить публичную ссылку", - "unable_to_hide_person": "Не удалось скрыть человека", - "unable_to_link_motion_video": "Не удалось связать движущееся видео", - "unable_to_link_oauth_account": "Не удалось привязать учетную запись OAuth", - "unable_to_log_out_all_devices": "Не удалось завершить сеанс на всех устройствах", - "unable_to_log_out_device": "Не удалось завершить сеанс на устройстве", - "unable_to_login_with_oauth": "Не удалось войти в систему с помощью OAuth", - "unable_to_play_video": "Не удалось воспроизвести видео", - "unable_to_reassign_assets_existing_person": "Не удалось переназначить объекты на {name, select, null {другого человека} other {человека с именем {name}}}", - "unable_to_reassign_assets_new_person": "Не удалось переназначить объекты на нового человека", - "unable_to_refresh_user": "Не удалось обновить пользователя", - "unable_to_remove_album_users": "Не удалось удалить пользователей из альбома", - "unable_to_remove_api_key": "Не удалось удалить API ключ", - "unable_to_remove_assets_from_shared_link": "Не удалось удалить объекты из публичной ссылки", - "unable_to_remove_library": "Не удалось удалить библиотеку", - "unable_to_remove_partner": "Не удалось удалить партнёра", - "unable_to_remove_reaction": "Не удалось удалить реакцию", - "unable_to_reset_password": "Не удается сбросить пароль", - "unable_to_reset_pin_code": "Невозможно сбросить PIN-код", - "unable_to_resolve_duplicate": "Не удалось выполнить действие над дубликатами", - "unable_to_restore_assets": "Не удалось восстановить объекты", - "unable_to_restore_trash": "Не удалось восстановить содержимое корзины", - "unable_to_restore_user": "Не удалось восстановить пользователя", - "unable_to_save_album": "Не удалось сохранить альбом", - "unable_to_save_api_key": "Не удалось сохранить API ключ", - "unable_to_save_date_of_birth": "Не удалось сохранить дату рождения", - "unable_to_save_name": "Не удалось изменить имя", - "unable_to_save_profile": "Не удалось сохранить профиль", - "unable_to_save_settings": "Не удалось сохранить настройки", - "unable_to_scan_libraries": "Не удалось просканировать библиотеки", - "unable_to_scan_library": "Не удалось просканировать библиотеку", - "unable_to_set_feature_photo": "Не удалось установить фотографию на обложку", - "unable_to_set_profile_picture": "Не удалось установить фото профиля", - "unable_to_set_rating": "Не удалось установить рейтинг", - "unable_to_submit_job": "Не удалось отправить задачу на выполнение", - "unable_to_trash_asset": "Не удалось переместить объект в корзину", - "unable_to_unlink_account": "Не удалось отсоединить учётную запись", - "unable_to_unlink_motion_video": "Не удалось отсоединить движущееся видео", - "unable_to_update_album_cover": "Не удалось обновить обложку альбома", - "unable_to_update_album_info": "Не удалось обновить информацию об альбоме", - "unable_to_update_library": "Не удалось обновить библиотеку", - "unable_to_update_location": "Не удалось обновить местоположение", - "unable_to_update_settings": "Не удалось обновить настройки", - "unable_to_update_timeline_display_status": "Не удалось изменить статус отображения на шкале времени", - "unable_to_update_user": "Не удалось обновить пользователя", - "unable_to_update_workflow": "Не удалось обновить рабочий процесс", - "unable_to_upload_file": "Не удалось загрузить файл" - }, - "errors_text": "Ошибки", - "exclusion_pattern": "Шаблоны исключений", - "exif": "Exif", - "exif_bottom_sheet_description": "Добавить описание...", - "exif_bottom_sheet_description_error": "Не удалось обновить описание", - "exif_bottom_sheet_details": "ПОДРОБНОСТИ", - "exif_bottom_sheet_location": "МЕСТО", - "exif_bottom_sheet_no_description": "Нет описания", - "exif_bottom_sheet_people": "ЛЮДИ", - "exif_bottom_sheet_person_add_person": "Добавить имя", - "exit_slideshow": "Выйти из слайд-шоу", - "expand_all": "Развернуть всё", - "experimental_settings_new_asset_list_subtitle": "В разработке", - "experimental_settings_new_asset_list_title": "Включить экспериментальную сетку фотографий", - "experimental_settings_subtitle": "Используйте на свой страх и риск!", - "experimental_settings_title": "Экспериментальные функции", - "expire_after": "Истекает через", - "expired": "Срок действия истёк", - "expires_date": "Срок действия до {date}", - "explore": "Поиск", - "explorer": "Проводник", - "export": "Экспортировать", - "export_as_json": "Экспорт в JSON", - "export_database": "Экспорт базы данных", - "export_database_description": "Экспорт базы данных SQLite", - "extension": "Расширение", - "external": "Внешний", - "external_libraries": "Внешние библиотеки", - "external_network": "Внешняя сеть", - "external_network_sheet_info": "Когда устройство не подключено к указанной Wi-Fi сети, приложение будет пытаться подключиться к серверу по адресам ниже, сверху вниз до успешного подключения", - "face_unassigned": "Не назначено", - "failed": "Ошибка", - "failed_count": "Завершены с ошибками: {count}", - "failed_to_authenticate": "Ошибка аутентификации", - "failed_to_load_assets": "Не удалось загрузить объекты", - "failed_to_load_folder": "Ошибка при загрузке папки", - "favorite": "Избранное", - "favorite_action_prompt": "Объекты добавлены в Избранное ({count} шт.)", - "favorite_or_unfavorite_photo": "Добавить или удалить фотографию из избранного", - "favorites": "Избранное", - "favorites_page_no_favorites": "В избранном сейчас пусто", - "feature_photo_updated": "Избранное фото обновлено", - "features": "Дополнительные возможности", - "features_in_development": "Функции в разработке", - "features_setting_description": "Управление дополнительными возможностями приложения", - "file_name_or_extension": "Имя файла или расширение", - "file_size": "Размер файла", - "filename": "Имя файла", - "filetype": "Тип файла", - "filter": "Фильтр", - "filter_description": "Условия отбора целевых объектов", - "filter_people": "Фильтр по людям", - "filter_places": "Фильтр по местам", - "filters": "Фильтры", - "find_them_fast": "Быстро найдите их по имени с помощью поиска", - "first": "Первый", - "fix_incorrect_match": "Исправить неправильное соответствие", - "folder": "Папка", - "folder_not_found": "Папка не найдена", - "folders": "Папки", - "folders_feature_description": "Просмотр папок с фото и видео в файловой системе", - "forgot_pin_code_question": "Забыли PIN-код?", - "forward": "Вперёд", - "free_up_space": "Освободить место", - "free_up_space_description": "Переместить скопированные на сервер фото и видео в корзину устройства для освобождения места. Копии на сервере останутся нетронутыми.", - "free_up_space_settings_subtitle": "Освободить место на устройстве", - "full_path": "Полный путь: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Для работы требуется загрузка внешних ресурсов с серверов Google.", - "general": "Общие", - "geolocation_instruction_location": "Выберите объект с имеющимися координатами, чтобы использовать их, либо вручную укажите место на карте", - "get_help": "Получить помощь", - "get_people_error": "Ошибка получения людей", - "get_wifiname_error": "Не удалось получить имя Wi-Fi сети. Убедитесь, что вы подключены к сети и предоставили приложению необходимые разрешения", - "getting_started": "Старт", - "go_back": "Назад", - "go_to_folder": "Перейти в папку", - "go_to_search": "Перейти к поиску", - "gps": "Есть координаты", - "gps_missing": "Нет координат", - "grant_permission": "Предоставить разрешение", - "group_albums_by": "Группировать альбомы по...", - "group_country": "Группировать по странам", - "group_no": "Без группировки", - "group_owner": "Группировать по владельцу", - "group_places_by": "Группировать места по...", - "group_year": "Группировать по годам", - "haptic_feedback_switch": "Включить тактильную отдачу", - "haptic_feedback_title": "Тактильная отдача", - "has_quota": "Квота", - "hash_asset": "Хеширование объектов", - "hashed_assets": "Хеши", - "hashing": "Хеширование", - "header_settings_add_header_tip": "Добавить заголовок", - "header_settings_field_validator_msg": "Значение не может быть пустым", - "header_settings_header_name_input": "Имя заголовка", - "header_settings_header_value_input": "Значение заголовка", - "headers_settings_tile_title": "Пользовательские заголовки прокси", - "height": "Высота", - "hi_user": "Привет {name} ({email})", - "hide_all_people": "Скрыть всех", - "hide_gallery": "Скрыть галерею", - "hide_named_person": "Скрыть {name}", - "hide_password": "Скрыть пароль", - "hide_person": "Скрыть человека", - "hide_schema": "Скрыть схему", - "hide_text_recognition": "Скрыть распознанный текст", - "hide_unnamed_people": "Скрыть людей без имени", - "home_page_add_to_album_conflicts": "Добавлено {added} медиа в альбом {album}. {failed} медиа уже в альбоме.", - "home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропуск", - "home_page_add_to_album_success": "Добавлено {added} медиа в альбом {album}.", - "home_page_album_err_partner": "Невозможно добавить объекты партнёра в альбом, пропуск", - "home_page_archive_err_local": "Пока нельзя добавить локальные файлы в архив, пропуск", - "home_page_archive_err_partner": "Невозможно добавить объекты партнёра в архив, пропуск", - "home_page_building_timeline": "Построение хронологии", - "home_page_delete_err_partner": "Невозможно удалить объекты партнёра, пропуск", - "home_page_delete_remote_err_local": "Невозможно удалить локальные файлы с сервера, пропуск", - "home_page_favorite_err_local": "Пока нельзя добавить в избранное локальные файлы, пропуск", - "home_page_favorite_err_partner": "Невозможно добавить объекты партнёра в избранное, пропуск", - "home_page_first_time_notice": "Перед началом использования приложения выберите альбом с объектами для резервного копирования, чтобы они отобразились на временной шкале", - "home_page_locked_error_local": "Невозможно переместить локальные объекты в личную папку, пропуск", - "home_page_locked_error_partner": "Невозможно переместить объекты партнёра в личную папку, пропуск", - "home_page_share_err_local": "Нельзя поделиться локальными файлами по ссылке, пропуск", - "home_page_upload_err_limit": "Вы можете загрузить максимум 30 файлов за раз, пропуск", - "host": "Хост", - "hour": "Час", - "hours": "Часы", - "id": "ID", - "idle": "В ожидании", - "ignore_icloud_photos": "Пропускать файлы из iCloud", - "ignore_icloud_photos_description": "Не загружать файлы в Immich, если они хранятся в iCloud", - "image": "Изображения", - "image_alt_text_date": "Совместное {isVideo, select, true {Video} other {Image}} {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} совместно с {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} совместно с {person1} и {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} совместно с {person1}, {person2}, и {person3} {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} совместно с {person1}, {person2}, и ещё с {additionalCount, number} людьми {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} снятое в {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} снятое в {city}, {country} совместно с {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} снятое в {city}, {country} с {person1} и {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} снятое в {city}, {country} с {person1}, {person2}, и {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} снятое в {city}, {country} с {person1}, {person2}, и еще с {additionalCount, number} людьми {date}", - "image_saved_successfully": "Изображение сохранено", - "image_viewer_page_state_provider_download_started": "Загрузка началась", - "image_viewer_page_state_provider_download_success": "Успешно загружено", - "image_viewer_page_state_provider_share_error": "Ошибка общего доступа", - "immich_logo": "Лого Immich", - "immich_web_interface": "Веб интерфейс Immich", - "import_from_json": "Импорт из JSON", - "import_path": "Путь импорта", - "in_albums": "В {count, plural, one {# альбоме} other {# альбомах}}", - "in_archive": "В архиве", - "in_year": "{year} г.", - "in_year_selector": "В", - "include_archived": "Отображать архив", - "include_shared_albums": "Включать объекты общих альбомов", - "include_shared_partner_assets": "Включать объекты партнёров", - "individual_share": "Индивидуальная подборка", - "individual_shares": "Подборки", - "info": "Информация", - "interval": { - "day_at_onepm": "Каждый день в 13:00", - "hours": "{hours, plural, one {Каждый час} many {Каждые # часов} other {Каждые # часа}}", - "night_at_midnight": "Каждую ночь в полночь", - "night_at_twoam": "Каждую ночь в 2:00" - }, - "invalid_date": "Неверная дата", - "invalid_date_format": "Неверный формат даты", - "invite_people": "Пригласить участника", - "invite_to_album": "Пригласить в альбом", - "ios_debug_info_fetch_ran_at": "Выборка запущена {dateTime}", - "ios_debug_info_last_sync_at": "Последняя синхронизация {dateTime}", - "ios_debug_info_no_processes_queued": "Нет фоновых процессов в очереди", - "ios_debug_info_no_sync_yet": "Фоновая синхронизация еще не выполнялась", - "ios_debug_info_processes_queued": "{count, plural, one {{count} фоновый процесс} many {{count} фоновых процессов} other {{count} фоновых процесса}} в очереди", - "ios_debug_info_processing_ran_at": "Обработка запущена {dateTime}", - "items_count": "{count, plural, one {# элемент} many {# элементов} other {# элемента}}", - "jobs": "Задачи", - "json_editor": "Редактор JSON", - "json_error": "Ошибка JSON", - "keep": "Оставить", - "keep_albums": "Оставить альбомы", - "keep_albums_count": "Оставить {count, plural, one {# альбом} many {# альбомов} other {# альбома}}", - "keep_all": "Сохранить все", - "keep_description": "Выберите, что хотите оставить на устройстве при освобождении места.", - "keep_favorites": "Оставить избранные", - "keep_on_device": "Оставить на устройстве", - "keep_on_device_hint": "Выберите объекты, которые нужно оставить на устройстве", - "keep_this_delete_others": "Оставить этот, удалить остальные", - "keeping": "Оставить: {items}", - "kept_this_deleted_others": "Сохранён этот объект и {count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}}", - "keyboard_shortcuts": "Сочетания клавиш", - "language": "Язык", - "language_no_results_subtitle": "Попробуйте скорректировать поисковый запрос", - "language_no_results_title": "Языков не найдено", - "language_search_hint": "Поиск языка...", - "language_setting_description": "Выберите предпочитаемый вами язык", - "large_files": "Файлы наибольшего размера", - "last": "Последний", - "last_months": "{count, plural, one {Последний месяц} many {# последних месяцев} other {# последних месяца}}", - "last_seen": "Последний доступ", - "latest_version": "Последняя версия", - "latitude": "Широта", - "leave": "Покинуть", - "leave_album": "Покинуть альбом", - "lens_model": "Модель объектива", - "let_others_respond": "Разрешить другим пользователям добавлять комментарии и отметки \"нравится\"", - "level": "Уровень", - "library": "Библиотека", - "library_add_folder": "Добавить папку", - "library_edit_folder": "Изменить путь к папке", - "library_options": "Действия с библиотекой", - "library_page_device_albums": "Альбомы на устройстве", - "library_page_new_album": "Новый альбом", - "library_page_sort_asset_count": "Количество объектов", - "library_page_sort_created": "Недавно созданные", - "library_page_sort_last_modified": "Последнее изменение", - "library_page_sort_title": "Название альбома", - "licenses": "Лицензии", - "light": "Светлая", - "like": "Нравится", - "like_deleted": "Лайк удален", - "link_motion_video": "Ссылка на движущееся видео", - "link_to_oauth": "Присоединение к OAuth", - "linked_oauth_account": "Присоединённый аккаунт OAuth", - "list": "Список", - "loading": "Загрузка", - "loading_search_results_failed": "Загрузка результатов поиска не удалась", - "local": "На устройстве", - "local_asset_cast_failed": "Невозможна трансляция объектов, которые ещё не загружены на сервер", - "local_assets": "Объекты на устройстве", - "local_id": "Идентификатор на устройстве", - "local_media_summary": "Информация об объекте на устройстве", - "local_network": "Локальная сеть", - "local_network_sheet_info": "Приложение будет подключаться к серверу по этому адресу, когда устройство подключено к указанной Wi-Fi сети", - "location": "Место", - "location_permission": "Доступ к местоположению", - "location_permission_content": "Чтобы использовать функцию автоматического переключения, Immich необходимо разрешение на точное определение местоположения, чтобы оно могло считывать название текущей Wi-Fi сети", - "location_picker_choose_on_map": "Выбрать на карте", - "location_picker_latitude_error": "Укажите правильную широту", - "location_picker_latitude_hint": "Введите широту", - "location_picker_longitude_error": "Укажите правильную долготу", - "location_picker_longitude_hint": "Введите долготу", - "lock": "Заблокировать", - "locked_folder": "Личная папка", - "log_detail_title": "Детали события", - "log_out": "Выйти", - "log_out_all_devices": "Завершить сеансы на всех устройствах", - "logged_in_as": "Авторизован как {user}", - "logged_out_all_devices": "Завершены все сеансы, кроме текущего", - "logged_out_device": "Сеанс на выбранном устройстве завершён", - "login": "Войти", - "login_disabled": "Вход отключен", - "login_form_api_exception": "Ошибка подключения к серверу. Проверьте URL-адрес и попробуйте еще раз.", - "login_form_back_button_text": "Назад", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "URL-aдрес сервера", - "login_form_err_http": "Пожалуйста, укажите протокол http:// или https://", - "login_form_err_invalid_email": "Некорректный адрес электронной почты", - "login_form_err_invalid_url": "Некорректный URL", - "login_form_err_leading_whitespace": "Пробел до", - "login_form_err_trailing_whitespace": "Пробел после", - "login_form_failed_get_oauth_server_config": "Ошибка авторизации с использованием OAuth, проверьте URL-адрес сервера", - "login_form_failed_get_oauth_server_disable": "Авторизация через OAuth недоступна на этом сервере", - "login_form_failed_login": "Ошибка при входе, проверьте URL-адрес сервера, адрес электронной почты и пароль", - "login_form_handshake_exception": "Ошибка при установлении защищённого соединения с сервером. Если используется самоподписанный сертификат, включите его поддержку в настройках.", - "login_form_password_hint": "пароль", - "login_form_save_login": "Оставаться в системе", - "login_form_server_empty": "Введите URL-адрес сервера.", - "login_form_server_error": "Не удалось установить соединение с сервером.", - "login_has_been_disabled": "Вход в систему был отключен.", - "login_password_changed_error": "Произошла ошибка при обновлении пароля", - "login_password_changed_success": "Пароль успешно обновлен", - "logout_all_device_confirmation": "Вы действительно хотите завершить все сеансы, кроме текущего?", - "logout_this_device_confirmation": "Вы действительно хотите завершить сеанс на этом устройстве?", - "logs": "Журнал событий", - "longitude": "Долгота", - "look": "Просмотр", - "loop_videos": "Циклическое воспроизведение видео", - "loop_videos_description": "Включить автоматический повтор видео при просмотре.", - "main_branch_warning": "Вы используете версию приложения для разработки. Настоятельно рекомендуется перейти на релизную версию приложения!", - "main_menu": "Главное меню", - "maintenance_action_restore": "Восстановление базы данных", - "maintenance_description": "Сервер Immich переведён в режим обслуживания.", - "maintenance_end": "Отключить режим обслуживания", - "maintenance_end_error": "Не удалось отключить режим обслуживания.", - "maintenance_logged_in_as": "В настоящее время вы вошли в систему как {user}", - "maintenance_restore_from_backup": "Восстановить из резервной копии", - "maintenance_restore_library": "Восстановление библиотеки", - "maintenance_restore_library_confirm": "Если всё выглядит правильно, начинайте восстановление из резервной копии!", - "maintenance_restore_library_description": "Восстановление базы данных", - "maintenance_restore_library_folder_has_files": "{folder} содержит {count} папок", - "maintenance_restore_library_folder_no_files": "В папке {folder} отсутствуют файлы!", - "maintenance_restore_library_folder_pass": "доступ для чтения и записи", - "maintenance_restore_library_folder_read_fail": "нет доступа на чтение", - "maintenance_restore_library_folder_write_fail": "нет доступа для записи", - "maintenance_restore_library_hint_missing_files": "Возможно отсутствуют важные файлы", - "maintenance_restore_library_hint_regenerate_later": "Можно будет потом заново сгенерировать в настройках", - "maintenance_restore_library_hint_storage_template_missing_files": "Используете шаблон хранилища? Возможно, отсутствуют некоторые файлы.", - "maintenance_restore_library_loading": "Загрузка проверок целостности и эвристических алгоритмов…", - "maintenance_task_backup": "Создание резервной копии существующей базы данных…", - "maintenance_task_migrations": "Миграция базы данных…", - "maintenance_task_restore": "Восстановление выбранной резервной копии…", - "maintenance_task_rollback": "Восстановление не удалось, откат к точке восстановления…", - "maintenance_title": "Временно недоступно", - "make": "Производитель", - "manage_geolocation": "Управление местами съёмки", - "manage_media_access_rationale": "Это разрешение необходимо для корректного перемещения объектов в корзину и восстановления их из неё.", - "manage_media_access_settings": "Перейти к настройкам", - "manage_media_access_subtitle": "Разрешите приложению Immich управлять медиафайлами.", - "manage_media_access_title": "Доступ к управлению медиафайлами", - "manage_shared_links": "Управление публичными ссылками", - "manage_sharing_with_partners": "Функция совместного доступа к фото и видео, позволяющая видеть объекты партнёров, а также предоставлять доступ к своим", - "manage_the_app_settings": "Управление настройками приложения", - "manage_your_account": "Управление учётной записью", - "manage_your_api_keys": "Управление API ключами для взаимодействия с другими программами", - "manage_your_devices": "Управление устройствами, на которых вы входили в свой аккаунт", - "manage_your_oauth_connection": "Настройки подключённого OAuth", - "map": "Карта", - "map_assets_in_bounds": "{count, plural, =0 {Нет фото} other {# фото}}", - "map_cannot_get_user_location": "Невозможно получить местоположение пользователя", - "map_location_dialog_yes": "Да", - "map_location_picker_page_use_location": "Это местоположение", - "map_location_service_disabled_content": "Для отображения объектов в текущем месте необходимо включить службу определения местоположения. Включить?", - "map_location_service_disabled_title": "Служба определения местоположения отключена", - "map_marker_for_images": "Маркер на карте для изображений, сделанных в {city}, {country}", - "map_marker_with_image": "Маркер на карте с изображением", - "map_no_location_permission_content": "Для отображения объектов в текущем месте необходимо разрешение на определение местоположения. Предоставить разрешение?", - "map_no_location_permission_title": "Доступ к местоположению отклонен", - "map_settings": "Настройки карты", - "map_settings_dark_mode": "Темный режим", - "map_settings_date_range_option_day": "24 часа", - "map_settings_date_range_option_days": "Последние {days} дней", - "map_settings_date_range_option_year": "Год", - "map_settings_date_range_option_years": "{years} года", - "map_settings_dialog_title": "Настройки карты", - "map_settings_include_show_archived": "Отображать архивированное", - "map_settings_include_show_partners": "Отображать медиа партнера", - "map_settings_only_show_favorites": "Показать только избранное", - "map_settings_theme_settings": "Цвет карты", - "map_zoom_to_see_photos": "Уменьшение масштаба для просмотра фотографий", - "mark_all_as_read": "Прочитано", - "mark_as_read": "Отметить как прочитанное", - "marked_all_as_read": "Все уведомления отмечены как прочитанные", - "matches": "Совпадения", - "matching_assets": "Соответствующие объекты", - "media_type": "Тип медиа", - "memories": "Воспоминания", - "memories_all_caught_up": "Это всё на сегодня", - "memories_check_back_tomorrow": "Загляните завтра, чтобы увидеть больше воспоминаний", - "memories_setting_description": "Управление тем, что вы видите в своих воспоминаниях", - "memories_start_over": "Начать заново", - "memories_swipe_to_close": "Смахните вверх, чтобы закрыть", - "memory": "Память", - "memory_lane_title": "Воспоминание {title}", - "menu": "Меню", - "merge": "Объединить", - "merge_people": "Объединить с другим", - "merge_people_limit": "Вы можете объединять до 5 лиц за один раз", - "merge_people_prompt": "Вы хотите объединить этих людей? Это действие необратимо.", - "merge_people_successfully": "Лица людей успешно объединены", - "merged_people_count": "Объединено {count, plural, one {# человек} many {# человек} other {# человека}}", - "minimize": "Минимизировать", - "minute": "Минута", - "minutes": "Минуты", - "mirror_horizontal": "Горизонтально", - "mirror_vertical": "Вертикально", - "missing": "Отсутствующие", - "mobile_app": "Мобильное приложение", - "mobile_app_download_onboarding_note": "Загрузите мобильное приложение Immich любым из следующих способов", - "model": "Модель", - "month": "Месяц", - "monthly_title_text_date_format": "MMMM y", - "more": "Дополнительные действия", - "move": "Переместить", - "move_down": "Переместить вниз", - "move_off_locked_folder": "Убрать из личной папки", - "move_to": "Переместить в", - "move_to_device_trash": "Переместить в корзину устройства", - "move_to_lock_folder_action_prompt": "Объекты добавлены в личную папку ({count} шт.)", - "move_to_locked_folder": "В личную папку", - "move_to_locked_folder_confirmation": "Эти фото и видео будут удалены из всех альбомов и будут доступны только в личной папке", - "move_up": "Переместить наверх", - "moved_to_archive": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в архив", - "moved_to_library": "{count, plural, one {# объект перемещён} many {# объектов перемещены} other {# объекта перемещены}} в библиотеку", - "moved_to_trash": "Перенесено в корзину", - "multiselect_grid_edit_date_time_err_read_only": "Невозможно изменить дату файлов только для чтения, пропуск", - "multiselect_grid_edit_gps_err_read_only": "Невозможно изменить местоположение файлов только для чтения, пропуск", - "mute_memories": "Отключить звук", - "my_albums": "Мои альбомы", - "name": "Имя", - "name_or_nickname": "Имя или ник", - "name_required": "Имя обязательно для заполнения", - "navigate": "Перейти", - "navigate_to_time": "Перейти к дате", - "network_requirement_photos_upload": "Использовать мобильный интернет для загрузки фото", - "network_requirement_videos_upload": "Использовать мобильный интернет для загрузки видео", - "network_requirements": "Требования к сети", - "network_requirements_updated": "Требования к сети изменились, сброс очереди загрузки", - "networking_settings": "Сеть", - "networking_subtitle": "Настройка подключения к серверу", - "never": "никогда", - "new_album": "Новый альбом", - "new_api_key": "Новый API ключ", - "new_date_range": "Новый диапазон дат", - "new_password": "Новый пароль", - "new_person": "Новый человек", - "new_pin_code": "Новый PIN-код", - "new_pin_code_subtitle": "Это ваш первый доступ к личной папке. Создайте PIN-код для защищенного доступа к этой странице.", - "new_timeline": "Новая лента", - "new_update": "Новое обновление", - "new_user_created": "Новый пользователь создан", - "new_version_available": "ДОСТУПНА НОВАЯ ВЕРСИЯ", - "newest_first": "Сначала новые", - "next": "Далее", - "next_memory": "Следующее воспоминание", - "no": "Нет", - "no_actions_added": "Действий пока не добавлено", - "no_albums_found": "Альбомов не найдено", - "no_albums_message": "Создавайте альбомы для систематизации ваших фотографий и видео", - "no_albums_with_name_yet": "Похоже, у вас пока нет альбомов с таким названием.", - "no_albums_yet": "Похоже, у вас пока нет альбомов.", - "no_archived_assets_message": "Архивируйте фотографии и видео, чтобы скрыть их при общем просмотре", - "no_assets_message": "Нажмите для загрузки вашего первого фото", - "no_assets_to_show": "Медиа отсутствуют", - "no_cast_devices_found": "Не найдено устройств для трансляции", - "no_checksum_local": "Контрольные суммы отсутствуют - невозможно получить объекты на устройстве", - "no_checksum_remote": "Контрольные суммы отсутствуют - невозможно получить объекты с сервера", - "no_configuration_needed": "Конфигурация не требуется", - "no_devices": "Нет авторизованных устройств", - "no_duplicates_found": "Дубликатов не обнаружено.", - "no_exif_info_available": "Нет доступной информации exif", - "no_explore_results_message": "Загружайте больше фотографий, чтобы наслаждаться вашей коллекцией.", - "no_favorites_message": "Добавляйте объекты в избранное, чтобы быстрее находить свои лучшие фото и видео", - "no_filters_added": "Фильтров пока не добавлено", - "no_libraries_message": "Создайте внешнюю библиотеку для просмотра в Immich сторонних фотографий и видео", - "no_local_assets_found": "На устройстве не найдено объектов с такой контрольной суммой", - "no_location_set": "Местоположение не установлено", - "no_locked_photos_message": "Фото и видео, перемещенные в личную папку, скрыты и не отображаются при просмотре библиотеки.", - "no_name": "Нет имени", - "no_notifications": "Нет уведомлений", - "no_people_found": "Никого не найдено", - "no_places": "Нет мест", - "no_remote_assets_found": "На сервере не найдено объектов с такой контрольной суммой", - "no_results": "Нет результатов", - "no_results_description": "Попробуйте использовать синонимы или более общие слова", - "no_shared_albums_message": "Создавайте альбомы для обмена фотографиями и видеозаписями с людьми в вашей сети", - "no_uploads_in_progress": "Нет активных загрузок", - "none": "Ничего", - "not_allowed": "Запрещено", - "not_available": "Нет данных", - "not_in_any_album": "Ни в одном альбоме", - "not_selected": "Не выбрано", - "note_apply_storage_label_to_previously_uploaded assets": "Примечание: Чтобы применить метку хранилища к ранее загруженным объектам, запустите", - "notes": "Примечание", - "nothing_here_yet": "Здесь пока ничего нет", - "notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».", - "notification_permission_list_tile_content": "Предоставьте разрешение на показ уведомлений.", - "notification_permission_list_tile_enable_button": "Включить уведомления", - "notification_permission_list_tile_title": "Разрешение на уведомление", - "notification_toggle_setting_description": "Включить уведомления по электронной почте", - "notifications": "Уведомления", - "notifications_setting_description": "Управление уведомлениями", - "oauth": "OAuth", - "obtainium_configurator": "Настройка Obtainium", - "obtainium_configurator_instructions": "Для установки и обновления Android приложения Immich напрямую из источников на GitHub (минуя магазины приложений) можно использовать Obtainium. Создайте новый API ключ и укажите архитектуру приложения для формирования ссылки для Obtainium.", - "ocr": "Текст (OCR)", - "official_immich_resources": "Официальные ресурсы Immich", - "offline": "Недоступен", - "offset": "Смещение", - "ok": "ОК", - "oldest_first": "Сначала старые", - "on_this_device": "На этом устройстве", - "onboarding": "Начало работы", - "onboarding_locale_description": "Выберите язык приложения (можно позже изменить в настройках).", - "onboarding_privacy_description": "Следующие необязательные функции зависят от внешних сервисов и в любое время могут быть отключены в настройках.", - "onboarding_server_welcome_description": "Давайте начнём с настройки нескольких параметров вашего сервера.", - "onboarding_theme_description": "Выберите тему (можно позже изменить в настройках).", - "onboarding_user_welcome_description": "Давайте начнем!", - "onboarding_welcome_user": "Добро пожаловать, {user}", - "online": "Доступен", - "only_favorites": "Только избранное", - "open": "Открыть", - "open_in_map_view": "Открыть в режиме просмотра карты", - "open_in_openstreetmap": "Открыть в OpenStreetMap", - "open_the_search_filters": "Открыть фильтры поиска", - "options": "Параметры", - "or": "или", - "organize_into_albums": "Распределить по альбомам", - "organize_into_albums_description": "Добавить уже существующие объекты в альбомы, используя текущие настройки синхронизации", - "organize_your_library": "Приведите в порядок свою библиотеку", - "original": "оригинал", - "other": "Другое", - "other_devices": "Другие устройства", - "other_entities": "Другие объекты", - "other_variables": "Другие переменные", - "owned": "Мои", - "owner": "Владелец", - "page": "Страница", - "partner": "Партнёр", - "partner_can_access": "Пользователю {partner} доступны", - "partner_can_access_assets": "Все ваши фото и видео, кроме тех, что находятся в архиве и корзине", - "partner_can_access_location": "Места, где были сделаны ваши фото и видео", - "partner_list_user_photos": "Фото и видео пользователя {user}", - "partner_list_view_all": "Посмотреть все", - "partner_page_empty_message": "Вы пока никому из партнёров не предоставили доступ к своим фото и видео.", - "partner_page_no_more_users": "Выбраны все доступные пользователи", - "partner_page_partner_add_failed": "Не удалось добавить партнёра", - "partner_page_select_partner": "Выбрать партнёра", - "partner_page_shared_to_title": "Поделиться с...", - "partner_page_stop_sharing_content": "Пользователь {partner} больше не будет иметь доступ к вашим фото и видео.", - "partner_sharing": "Совместное использование", - "partners": "Партнёры", - "password": "Пароль", - "password_does_not_match": "Пароли не совпадают", - "password_required": "Требуется пароль", - "password_reset_success": "Сброс пароля успешно выполнен", - "past_durations": { - "days": "За {days, plural, one {# день} many {# дней} other {# дня}}", - "hours": "За {hours, plural, one {# час} many {# часов} other {# часа}}", - "years": "За {years, plural, one {# год} many {# лет} other {# года}}" - }, - "path": "Путь", - "pattern": "Шаблон", - "pause": "Пауза", - "pause_memories": "Приостановить воспоминания", - "paused": "Приостановлено", - "pending": "Ожидает", - "people": "Люди", - "people_edits_count": "{count, plural, one {Изменён # человек} many {Изменено # человек} other {Изменено # человека}}", - "people_feature_description": "Просмотр фото и видео, сгруппированных по людям", - "people_selected": "{count, plural, one {Выбран # человек} many {Выбрано # человек} other {Выбрано # человека}}", - "people_sidebar_description": "Отображать пункт меню \"Люди\" в боковой панели", - "permanent_deletion_warning": "Предупреждение об удалении", - "permanent_deletion_warning_setting_description": "Предупреждать перед безвозвратным удалением объектов", - "permanently_delete": "Удалить навсегда", - "permanently_delete_assets_count": "Удалить {count, plural, one {объект} other {объекты}} навсегда", - "permanently_delete_assets_prompt": "Вы действительно хотите безвозвратно удалить {count, plural, =1 {этот объект} one {этот # объект} many {эти # объектов} other {эти # объекта}}? {count, plural, =1 {Объект также будет удален} other {Объекты также будут удалены}} из всех альбомов.", - "permanently_deleted_asset": "Удалить навсегда", - "permanently_deleted_assets_count": "{count, plural, one {# объект безвозвратно удалён} many {# объектов безвозвратно удалено} other {# объекта безвозвратно удалены}}", - "permission": "Разрешения", - "permission_empty": "Разрешения не могут быть пустыми", - "permission_onboarding_back": "Назад", - "permission_onboarding_continue_anyway": "Все равно продолжить", - "permission_onboarding_get_started": "Давайте начнём", - "permission_onboarding_go_to_settings": "Перейти в настройки", - "permission_onboarding_permission_denied": "Не удалось получить доступ. Чтобы использовать приложение, разрешите доступ к \"Фото и видео\" в настройках.", - "permission_onboarding_permission_granted": "Доступ получен! Всё готово.", - "permission_onboarding_permission_limited": "Доступ к файлам ограничен. Чтобы Immich мог создавать резервные копии и управлять вашей галереей, пожалуйста, предоставьте приложению разрешение на доступ к \"Фото и видео\" в настройках.", - "permission_onboarding_request": "Приложению необходимо разрешение на доступ к вашим фото и видео.", - "person": "Человек", - "person_age_months": "{months, plural, one {# месяц} many {# месяцев} other {# месяца}}", - "person_age_year_months": "1 год, {months, plural, one {# месяц} many {# месяцев} other {# месяца}}", - "person_age_years": "{years, plural, one {# год} many {# лет} other {# года}}", - "person_birthdate": "Дата рождения: {date}", - "person_hidden": "{name}{hidden, select, true { (скрыт)} other {}}", - "person_recognized": "Человек распознан", - "person_selected": "Человек выбран", - "photo_shared_all_users": "Похоже, что вы поделились своими фотографиями со всеми пользователями или у вас нет пользователей, с которыми можно поделиться.", - "photos": "Фото", - "photos_and_videos": "Фото и видео", - "photos_count": "{count, plural, one {{count, number} фото} other {{count, number} фото}}", - "photos_from_previous_years": "Фотографии прошлых лет в этот день", - "photos_only": "Только фото", - "pick_a_location": "Выбрать местоположение", - "pick_custom_range": "Произвольный период", - "pick_date_range": "Выберите период", - "pin_code_changed_successfully": "PIN-код успешно изменён", - "pin_code_reset_successfully": "PIN-код успешно сброшен", - "pin_code_setup_successfully": "PIN-код успешно установлен", - "pin_verification": "Проверка PIN-кода", - "place": "Места", - "places": "Места", - "places_count": "{count, plural, one {{count, number} место} many {{count, number} мест} other {{count, number} места}}", - "play": "Воспроизвести", - "play_memories": "Воспроизвести воспоминания", - "play_motion_photo": "Воспроизводить движущиеся фото", - "play_or_pause_video": "Воспроизведение или приостановка видео", - "play_original_video": "Воспроизвести оригинальное видео", - "play_original_video_setting_description": "Проигрывать оригинальные видео вместо перекодированных. Если исходный формат не поддерживается, видео может воспроизводиться неправильно.", - "play_transcoded_video": "Воспроизвести перекодированное видео", - "please_auth_to_access": "Пожалуйста, авторизуйтесь", - "port": "Порт", - "preferences_settings_subtitle": "Настройка внешнего вида", - "preferences_settings_title": "Параметры", - "preparing": "Подготовка", - "preset": "Предустановленные варианты", - "preview": "Предварительный просмотр", - "previous": "Предыдущее", - "previous_memory": "Предыдущее воспоминание", - "previous_or_next_day": "День вперёд/назад", - "previous_or_next_month": "Месяц вперёд/назад", - "previous_or_next_photo": "Предыдущее/следующее фото", - "previous_or_next_year": "Год вперёд/назад", - "primary": "Главное", - "privacy": "Конфиденциальность", - "profile": "Профиль", - "profile_drawer_app_logs": "Журнал событий", - "profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Включён режим «только просмотр». Удерживайте значок аватара пользователя для отключения.", - "profile_image_of_user": "Изображение профиля {user}", - "profile_picture_set": "Фото профиля установлено.", - "public_album": "Общий альбом", - "public_share": "Публичный доступ", - "purchase_account_info": "Поддержка", - "purchase_activated_subtitle": "Благодарим вас за поддержку Immich и программного обеспечения с открытым исходным кодом", - "purchase_activated_time": "Активировано {date}", - "purchase_activated_title": "Ваш ключ успешно активирован", - "purchase_button_activate": "Активировать", - "purchase_button_buy": "Купить", - "purchase_button_buy_immich": "Купить Immich", - "purchase_button_never_show_again": "Больше не показывать", - "purchase_button_reminder": "Напомнить через 30 дней", - "purchase_button_remove_key": "Удалить ключ", - "purchase_button_select": "Выбрать", - "purchase_failed_activation": "Ошибка активации! Пожалуйста, проверьте наличие правильного ключа продукта в письме, направленном по электронной почте!", - "purchase_individual_description_1": "Для индивидуального использования", - "purchase_individual_description_2": "Состояние поддержки", - "purchase_individual_title": "Индивидуальный", - "purchase_input_suggestion": "У вас есть ключ продукта? Введите этот ключ ниже", - "purchase_license_subtitle": "Приобретите Immich, чтобы поддержать дальнейшее развитие сервиса", - "purchase_lifetime_description": "Единовременная покупка", - "purchase_option_title": "Варианты покупки", - "purchase_panel_info_1": "Создание Immich отнимает много времени и усилий, и у нас есть штатные разработчики, которые работают над тем, чтобы сделать его настолько хорошим, насколько это возможно. Наша миссия заключается в том, чтобы программное обеспечение с открытым исходным кодом и этические методы ведения бизнеса стали устойчивым источником дохода для разработчиков и чтобы создать экосистему, уважающую конфиденциальность, с реальными альтернативами эксплуататорским облачным сервисам.", - "purchase_panel_info_2": "Поскольку мы обязались не добавлять платные функции, эта покупка не добавит дополнительных возможностей в Immich. Мы рассчитываем на таких пользователей как вы, чтобы поддерживать дальнейшую разработку Immich.", - "purchase_panel_title": "Поддержите проект", - "purchase_per_server": "На сервер", - "purchase_per_user": "На пользователя", - "purchase_remove_product_key": "Удалить ключ продукта", - "purchase_remove_product_key_prompt": "Вы уверены, что хотите удалить ключ продукта?", - "purchase_remove_server_product_key": "Удалить ключ продукта для сервера", - "purchase_remove_server_product_key_prompt": "Вы уверены, что хотите удалить ключ продукта для сервера?", - "purchase_server_description_1": "Для всего сервера", - "purchase_server_description_2": "Состояние поддержки", - "purchase_server_title": "Сервер", - "purchase_settings_server_activated": "Ключом продукта управляет администратор сервера", - "query_asset_id": "Идентификатор исходного объекта", - "queue_status": "В очереди {count}/{total}", - "rate_asset": "Установить рейтинг", - "rating": "Рейтинг", - "rating_clear": "Очистить рейтинг", - "rating_count": "{count, plural, one {# звезда} many {# звезд} other {# звезды}}", - "rating_description": "Система оценки объектов в панели информации", - "rating_set": "Установлен рейтинг {rating, plural, one {# звезда} many {# звезд} other {# звезды}}", - "reaction_options": "Действия с отметкой", - "read_changelog": "История релизов", - "readonly_mode_disabled": "Режим «только просмотр» отключён", - "readonly_mode_enabled": "Режим «только просмотр» включён", - "ready_for_upload": "Готово к загрузке", - "reassign": "Переназначить", - "reassigned_assets_to_existing_person": "Лица на {count, plural, one {# объекте} other {# объектах}} переназначены на {name, select, null {другого человека} other {человека с именем {name}}}", - "reassigned_assets_to_new_person": "Лица на {count, plural, one {# объекте} other {# объектах}} переназначены на нового человека", - "reassing_hint": "Назначить выбранные объекты указанному человеку", - "recent": "Недавние", - "recent-albums": "Недавние альбомы", - "recent_searches": "Недавние поисковые запросы", - "recently_added": "Недавно добавленные", - "recently_added_page_title": "Недавно добавленные", - "recently_taken": "Недавно снято", - "recently_taken_page_title": "Недавно Снято", - "refresh": "Обновить", - "refresh_encoded_videos": "Обновить закодированные видео", - "refresh_faces": "Обновить лица", - "refresh_metadata": "Обновить метаданные", - "refresh_thumbnails": "Обновить миниатюры", - "refreshed": "Обновлено", - "refreshes_every_file": "Обновляет все существующие и новые файлы", - "refreshing_encoded_video": "Обновление закодированного видео", - "refreshing_faces": "Обновление лиц", - "refreshing_metadata": "Обновление метаданных", - "regenerating_thumbnails": "Восстановление миниатюр", - "remote": "На сервере", - "remote_assets": "Объекты на сервере", - "remote_media_summary": "Информация об объекте на сервере", - "remove": "Удалить", - "remove_assets_album_confirmation": "Вы действительно хотите удалить {count, plural, one {# объект} many {# объектов} other {# объекта}} из альбома?", - "remove_assets_shared_link_confirmation": "Вы действительно хотите удалить {count, plural, one {# объект} many {# объектов} other {# объекта}} из публичного доступа по этой ссылке?", - "remove_assets_title": "Удалить объекты?", - "remove_custom_date_range": "Удалить пользовательский диапазон дат", - "remove_deleted_assets": "Удаление автономных файлов", - "remove_from_album": "Удалить из альбома", - "remove_from_album_action_prompt": "Объекты удалены из альбома ({count} шт.)", - "remove_from_favorites": "Удалить из избранного", - "remove_from_lock_folder_action_prompt": "Объекты удалены из личной папки ({count} шт.)", - "remove_from_locked_folder": "Удалить из личной папки", - "remove_from_locked_folder_confirmation": "Вы хотите убрать выделенные объекты из личной папки? Они снова станут доступны в вашей библиотеке.", - "remove_from_shared_link": "Удалить из публичной ссылки", - "remove_memory": "Удалить воспоминание", - "remove_photo_from_memory": "Удалить фото из воспоминания", - "remove_tag": "Удалить тег", - "remove_url": "Удалить URL", - "remove_user": "Удалить пользователя", - "removed_api_key": "API ключ \"{name}\" удалён", - "removed_from_archive": "Удален из архива", - "removed_from_favorites": "Удалено из избранного", - "removed_from_favorites_count": "{count, plural, one {# объект удалён} many {# объектов удалено} other {# объекта удалено}} из избранного", - "removed_memory": "Воспоминание удалено", - "removed_photo_from_memory": "Фото удалено из воспоминания", - "removed_tagged_assets": "Тег удалён у {count, plural, one {# объекта} other {# объектов}}", - "rename": "Переименовать", - "repair": "Ремонт", - "repair_no_results_message": "Здесь будут отображаться неотслеживаемые и отсутствующие файлы", - "replace_with_upload": "Загрузить и заменить", - "repository": "Репозиторий", - "require_password": "Требуется пароль", - "require_user_to_change_password_on_first_login": "Требовать у пользователя сменить пароль при первом входе", - "rescan": "Повторное сканирование", - "reset": "Сброс", - "reset_password": "Сбросить пароль", - "reset_people_visibility": "Восстановить видимость людей", - "reset_pin_code": "Сбросить PIN-код", - "reset_pin_code_description": "Если вы забыли свой PIN-код, вы можете обратиться к администратору сервера, чтобы его сбросить", - "reset_pin_code_success": "PIN-код успешно сброшен", - "reset_pin_code_with_password": "Вы всегда можете сбросить PIN-код с помощью пароля", - "reset_sqlite": "Очистить базу данных SQLite", - "reset_sqlite_confirmation": "Вы действительно хотите очистить базу данных SQLite? Вам потребуется выйти из приложения и снова войти для повторной синхронизации данных.", - "reset_sqlite_success": "База данных SQLite успешно очищена", - "reset_to_default": "Восстановление значений по умолчанию", - "resolution": "Разрешение", - "resolve_duplicates": "Устранить дубликаты", - "resolved_all_duplicates": "Все дубликаты устранены", - "restore": "Восстановить", - "restore_all": "Восстановить все", - "restore_trash_action_prompt": "Объекты восстановлены из корзины ({count} шт.)", - "restore_user": "Восстановить пользователя", - "restored_asset": "Восстановленный объект", - "resume": "Продолжить", - "resume_paused_jobs": "Возобновить выполнение {count, plural, one {# задачи} other {# задач}}", - "retry_upload": "Повторить загрузку", - "review_duplicates": "Разбор дубликатов", - "review_large_files": "Обзор больших файлов", - "role": "Роль", - "role_editor": "Редактор", - "role_viewer": "Зритель", - "running": "Выполняется", - "save": "Сохранить", - "save_to_gallery": "Сохранить в галерею", - "saved": "Сохранено", - "saved_api_key": "API ключ изменён", - "saved_profile": "Профиль сохранён", - "saved_settings": "Настройки сохранены", - "say_something": "Напишите что-нибудь", - "scaffold_body_error_occurred": "Возникла ошибка", - "scan": "Поиск", - "scan_all_libraries": "Сканировать все библиотеки", - "scan_library": "Сканировать", - "scan_settings": "Настройки сканирования", - "scanning": "Поиск объектов", - "scanning_for_album": "Сканирование альбома...", - "search": "Поиск", - "search_albums": "Поиск альбомов", - "search_by_context": "Поиск по контексту", - "search_by_description": "Поиск по описанию", - "search_by_description_example": "День пешего туризма в Сапе", - "search_by_filename": "Поиск по имени файла или расширению", - "search_by_filename_example": "например, IMG_1234.JPG или PNG", - "search_by_ocr": "Поиск текста", - "search_by_ocr_example": "Латте", - "search_camera_lens_model": "Поиск модели объектива...", - "search_camera_make": "Поиск производителя камеры...", - "search_camera_model": "Поиск модели камеры...", - "search_city": "Поиск города...", - "search_country": "Поиск страны...", - "search_filter_apply": "Применить фильтр", - "search_filter_camera_title": "Выберите тип камеры", - "search_filter_date": "Дата", - "search_filter_date_interval": "{start} — {end}", - "search_filter_date_title": "Выберите период", - "search_filter_display_option_not_in_album": "Не в альбоме", - "search_filter_display_options": "Настройки отображения", - "search_filter_filename": "Поиск по имени файла", - "search_filter_location": "Место", - "search_filter_location_title": "Выберите место", - "search_filter_media_type": "Тип файла", - "search_filter_media_type_title": "Выберите тип медиа", - "search_filter_ocr": "Поиск текста", - "search_filter_people_title": "Выберите людей", - "search_filter_star_rating": "Рейтинг", - "search_for": "Поиск по", - "search_for_existing_person": "Поиск существующего человека", - "search_no_more_result": "Больше результатов нет", - "search_no_people": "Нет людей", - "search_no_people_named": "Нет людей с именем \"{name}\"", - "search_no_result": "Ничего не найдено, попробуйте изменить поисковый запрос", - "search_options": "Параметры поиска", - "search_page_categories": "Категории", - "search_page_motion_photos": "Динамические фото", - "search_page_no_objects": "Нет доступной информации об объектах", - "search_page_no_places": "Информация о местах отсутствует", - "search_page_screenshots": "Снимки экрана", - "search_page_search_photos_videos": "Поиск по фото и видео", - "search_page_selfies": "Селфи", - "search_page_things": "Предметы", - "search_page_view_all_button": "Посмотреть все", - "search_page_your_activity": "Ваши действия", - "search_page_your_map": "Ваша карта", - "search_people": "Поиск людей", - "search_places": "Поиск мест", - "search_rating": "Поиск по рейтингу...", - "search_result_page_new_search_hint": "Новый поиск", - "search_settings": "Поиск настроек", - "search_state": "Поиск региона...", - "search_suggestion_list_smart_search_hint_1": "Интеллектуальный поиск включен по умолчанию, для поиска метаданных используйте специальный синтаксис ", - "search_suggestion_list_smart_search_hint_2": "m:ваш-поисковый-запрос", - "search_tags": "Поиск по тегам...", - "search_timezone": "Поиск часового пояса...", - "search_type": "Тип поиска", - "search_your_photos": "Поиск фотографий", - "searching_locales": "Идет поиск переводов...", - "second": "Секунда", - "see_all_people": "Посмотреть всех людей", - "select": "Выбрать", - "select_album": "Выберите альбом", - "select_album_cover": "Выбрать обложку альбома", - "select_albums": "Выберите альбомы", - "select_all": "Выбрать все", - "select_all_duplicates": "Выбрать все для сохранения", - "select_all_in": "Выбрать все в {group}", - "select_avatar_color": "Выберите цвет аватара", - "select_count": "Выбрано: {count, plural, other {#}}", - "select_cutoff_date": "Укажите дату отсечения", - "select_face": "Выбрать лицо", - "select_featured_photo": "Выбрать избранное фото", - "select_from_computer": "Выбрать с компьютера", - "select_keep_all": "Выбрать все для сохранения", - "select_library_owner": "Выберите владельца библиотеки", - "select_new_face": "Выбрать другого человека", - "select_people": "Выберите людей", - "select_person": "Выберите человека", - "select_person_to_tag": "Выделите лицо человека, которого хотите отметить", - "select_photos": "Выберите фотографии", - "select_trash_all": "Выбрать все для удаления", - "select_user_for_sharing_page_err_album": "Не удалось создать альбом", - "selected": "Выбрано", - "selected_count": "{count, plural, one {Выбран # объект} many {Выбрано # объектов} other {Выбрано # объекта}}", - "selected_gps_coordinates": "Выбранные координаты", - "send_message": "Отправить сообщение", - "send_welcome_email": "Отправить приветственное письмо", - "server_endpoint": "Адрес сервера", - "server_info_box_app_version": "Версия приложения", - "server_info_box_server_url": "URL сервера", - "server_offline": "Оффлайн", - "server_online": "Сервер в сети", - "server_privacy": "Конфиденциальность сервера", - "server_restarting_description": "Страница скоро обновится.", - "server_restarting_title": "Сервер перезапускается", - "server_stats": "Статистика сервера", - "server_update_available": "Доступна новая версия сервера", - "server_version": "Версия сервера", - "set": "Установить", - "set_as_album_cover": "Установить обложкой альбома", - "set_as_featured_photo": "Установить как основное фото", - "set_as_profile_picture": "Установить как фото профиля", - "set_date_of_birth": "Установить дату рождения", - "set_profile_picture": "Установка фото профиля", - "set_slideshow_to_fullscreen": "Переключить слайд-шоу в полноэкранный режим", - "set_stack_primary_asset": "Назначить основным объектом", - "setting_image_viewer_help": "При просмотре изображения сперва загружается миниатюра, затем уменьшенное изображение среднего качества (если включено), а затем оригинал (если включено).", - "setting_image_viewer_original_subtitle": "Включите для загрузки исходного изображения в полном разрешении (большое!). Отключите для уменьшения объёма данных (как сети, так и кэша устройства).", - "setting_image_viewer_original_title": "Загружать исходное изображение", - "setting_image_viewer_preview_subtitle": "Включите для загрузки изображения среднего разрешения. Отключите, чтобы загружать только оригинал или миниатюру.", - "setting_image_viewer_preview_title": "Загружать уменьшенное изображение", - "setting_image_viewer_title": "Изображения", - "setting_languages_apply": "Применить", - "setting_languages_subtitle": "Изменение языка приложения", - "setting_notifications_notify_failures_grace_period": "Уведомлять об ошибках фонового резервного копирования: {duration}", - "setting_notifications_notify_hours": "{count} ч.", - "setting_notifications_notify_immediately": "немедленно", - "setting_notifications_notify_minutes": "{count} мин.", - "setting_notifications_notify_never": "никогда", - "setting_notifications_notify_seconds": "{count} сек.", - "setting_notifications_single_progress_subtitle": "Подробная информация о ходе загрузки для каждого объекта", - "setting_notifications_single_progress_title": "Показать ход выполнения фонового резервного копирования", - "setting_notifications_subtitle": "Параметры уведомлений", - "setting_notifications_total_progress_subtitle": "Общий прогресс загрузки (выполнено/всего объектов)", - "setting_notifications_total_progress_title": "Показать общий прогресс фонового резервного копирования", - "setting_video_viewer_auto_play_subtitle": "Автоматически запускать воспроизведение видео при открытии", - "setting_video_viewer_auto_play_title": "Автовоспроизведение видео", - "setting_video_viewer_looping_title": "Циклическое воспроизведение", - "setting_video_viewer_original_video_subtitle": "При воспроизведении видео с сервера загружать оригинал, даже если доступна транскодированная версия. Может привести к буферизации. Видео, доступные локально, всегда воспроизводятся в исходном качестве.", - "setting_video_viewer_original_video_title": "Только оригинальное видео", - "settings": "Настройки", - "settings_require_restart": "Пожалуйста, перезапустите приложение, чтобы изменения вступили в силу", - "settings_saved": "Настройки сохранены", - "setup_pin_code": "Создание PIN-кода", - "share": "Поделиться", - "share_action_prompt": "Объекты в общем доступе ({count} шт.)", - "share_add_photos": "Добавить фото", - "share_assets_selected": "{count} выбрано", - "share_dialog_preparing": "Подготовка...", - "share_link": "Создать ссылку", - "shared": "Общиe", - "shared_album_activities_input_disable": "Комментарии отключены", - "shared_album_activity_remove_content": "Удалить сообщение?", - "shared_album_activity_remove_title": "Удалить", - "shared_album_section_people_action_error": "Ошибка при выходе/удалении из альбома", - "shared_album_section_people_action_leave": "Удалить пользователя из альбома", - "shared_album_section_people_action_remove_user": "Удалить пользователя из альбома", - "shared_album_section_people_title": "ЛЮДИ", - "shared_by": "Поделился", - "shared_by_user": "Владелец: {user}", - "shared_by_you": "Вы поделились", - "shared_from_partner": "Пользователь {partner} предоставил вам доступ", - "shared_intent_upload_button_progress_text": "{current} / {total} Загружено", - "shared_link_app_bar_title": "Публичные ссылки", - "shared_link_clipboard_copied_massage": "Скопировано в буфер обмена", - "shared_link_clipboard_text": "Ссылка: {link}\nПароль: {password}", - "shared_link_create_error": "Ошибка при создании публичной ссылки", - "shared_link_custom_url_description": "Пользовательский URL-адрес общего доступа", - "shared_link_edit_description_hint": "Введите описание", - "shared_link_edit_expire_after_option_day": "1 день", - "shared_link_edit_expire_after_option_days": "{count} дней", - "shared_link_edit_expire_after_option_hour": "1 час", - "shared_link_edit_expire_after_option_hours": "{count} часов", - "shared_link_edit_expire_after_option_minute": "1 минуту", - "shared_link_edit_expire_after_option_minutes": "{count} минут", - "shared_link_edit_expire_after_option_months": "{count} месяца", - "shared_link_edit_expire_after_option_year": "{count} лет", - "shared_link_edit_password_hint": "Защитите доступ паролем", - "shared_link_edit_submit_button": "Обновить ссылку", - "shared_link_error_server_url_fetch": "Не удается получить URL-адрес сервера", - "shared_link_expires_day": "Истечёт через {count} день", - "shared_link_expires_days": "Истечёт через {count} дней", - "shared_link_expires_hour": "Истечёт через {count} час", - "shared_link_expires_hours": "Истечёт через {count} часов", - "shared_link_expires_minute": "Истечёт через {count} минуту", - "shared_link_expires_minutes": "Истечёт через {count} минут", - "shared_link_expires_never": "Вечная ссылка", - "shared_link_expires_second": "Истечёт через {count} секунду", - "shared_link_expires_seconds": "Истечёт через {count} секунд", - "shared_link_individual_shared": "Индивидуальный общий доступ", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Управление публичными ссылками", - "shared_link_options": "Действия со ссылкой", - "shared_link_password_description": "Требовать пароль для доступа к объектам", - "shared_links": "Публичные ссылки", - "shared_links_description": "Делитесь фотографиями и видео по ссылке", - "shared_photos_and_videos_count": "{assetCount, plural, other {# фото и видео.}}", - "shared_with_me": "Доступные мне", - "shared_with_partner": "Вы предоставили доступ пользователю {partner}", - "sharing": "Общие", - "sharing_enter_password": "Пожалуйста, введите пароль для просмотра этой страницы.", - "sharing_page_album": "Общие альбомы", - "sharing_page_description": "Создавайте общие альбомы, чтобы делиться фотографиями и видео с людьми в вашей сети.", - "sharing_page_empty_list": "ПУСТОЙ СПИСОК", - "sharing_sidebar_description": "Отображать пункт меню \"Общие\" в боковой панели", - "sharing_silver_appbar_create_shared_album": "Создать общий альбом", - "sharing_silver_appbar_share_partner": "Поделиться с партнёром", - "shift_to_permanent_delete": "нажмите ⇧ чтобы удалить объект навсегда", - "show_album_options": "Действия с альбомом", - "show_albums": "Показать альбомы", - "show_all_people": "Показать всех людей", - "show_and_hide_people": "Показать и скрыть людей", - "show_file_location": "Показать расположение файла", - "show_gallery": "Показать галерею", - "show_hidden_people": "Показать скрытых людей", - "show_in_timeline": "Показать на временной шкале", - "show_in_timeline_setting_description": "Отображать фото и видео этого пользователя на своей временной шкале", - "show_keyboard_shortcuts": "Показать сочетания клавиш", - "show_metadata": "Показывать метаданные", - "show_or_hide_info": "Показать или скрыть информацию", - "show_password": "Показать пароль", - "show_person_options": "Действия с человеком", - "show_progress_bar": "Отображать индикатор выполнения", - "show_schema": "Показать схему", - "show_search_options": "Показать параметры поиска", - "show_shared_links": "Показать публичные ссылки", - "show_slideshow_transition": "Плавный переход", - "show_supporter_badge": "Значок поддержки", - "show_supporter_badge_description": "Показать значок поддержки", - "show_text_recognition": "Показать распознанный текст", - "show_text_search_menu": "Показать меню текстового поиска", - "shuffle": "Перемешать", - "sidebar": "Боковая панель", - "sidebar_display_description": "Отображать раздел на боковой панели", - "sign_out": "Выход", - "sign_up": "Зарегистрироваться", - "size": "Размер", - "skip_to_content": "Перейти к содержанию", - "skip_to_folders": "Перейти к папкам", - "skip_to_tags": "Перейти к тегам", - "slideshow": "Слайд-шоу", - "slideshow_repeat": "Зациклить слайд-шоу", - "slideshow_repeat_description": "Повторять слайд-шоу после его окончания", - "slideshow_settings": "Настройки слайд-шоу", - "sort_albums_by": "Сортировать альбомы по...", - "sort_created": "Дата создания", - "sort_items": "Количество элементов", - "sort_modified": "Дата изменения", - "sort_newest": "Последнее фото", - "sort_oldest": "Старейшее фото", - "sort_people_by_similarity": "Сортировать людей по сходству", - "sort_recent": "Недавние фото", - "sort_title": "Заголовок", - "source": "Исходный код", - "stack": "Сгруппировать", - "stack_action_prompt": "Объекты сгруппированы ({count} шт.)", - "stack_duplicates": "Сгруппировать дубликаты", - "stack_select_one_photo": "Выберите главную фотографию для группы", - "stack_selected_photos": "Сгруппировать выбранные объекты", - "stacked_assets_count": "{count, plural, one {# объект объединен} many {# объектов объединено} other {# объекта объединено}} в группу", - "stacktrace": "Трассировка стека", - "start": "Старт", - "start_date": "Дата начала", - "start_date_before_end_date": "Дата начала должна быть меньше даты окончания", - "state": "Регион", - "status": "Состояние", - "stop_casting": "Остановить трансляцию", - "stop_motion_photo": "Покадровая анимация", - "stop_photo_sharing": "Закрыть доступ партнёру?", - "stop_photo_sharing_description": "Пользователь {partner} больше не имеет доступа к вашим фотографиям.", - "stop_sharing_photos_with_user": "Прекратить делиться своими фото и видео с этим пользователем", - "storage": "Хранилище", - "storage_label": "Метка хранилища", - "storage_quota": "Квота хранилища", - "storage_usage": "{used} из {available}", - "submit": "Подтвердить", - "success": "Успешно", - "suggestions": "Предложения", - "sunrise_on_the_beach": "Восход солнца на пляже", - "support": "Поддержка", - "support_and_feedback": "Поддержка и обратная связь", - "support_third_party_description": "Ваша установка immich была упакована сторонним разработчиком. Проблемы, с которыми вы столкнулись, могут быть вызваны этим пакетом, поэтому, пожалуйста, в первую очередь обращайтесь к ним, используя ссылки ниже.", - "swap_merge_direction": "Изменить направление слияния", - "sync": "Синхр.", - "sync_albums": "Синхронизировать альбомы", - "sync_albums_manual_subtitle": "Синхронизировать все загруженные фото и видео в выбранные альбомы для резервного копирования", - "sync_local": "Локальная синхронизация", - "sync_remote": "Синхронизация с сервером", - "sync_status": "Статус синхронизации", - "sync_status_subtitle": "Просмотр и управление системой синхронизации", - "sync_upload_album_setting_subtitle": "Создавать на сервере такие же альбомы, как выбранные на устройстве, и загружать в них фото и видео", - "tag": "Тег", - "tag_assets": "Добавить теги", - "tag_created": "Тег {tag} создан", - "tag_feature_description": "Просмотр фотографий и видео, сгруппированных по тегам", - "tag_not_found_question": "Не удается найти тег? Создайте новый тег.", - "tag_people": "Отметить человека", - "tag_updated": "Тег {tag} изменен", - "tagged_assets": "Тег назначен для {count, plural, one {# объекта} other {# объектов}}", - "tags": "Теги", - "tap_to_run_job": "Нажмите для запуска задачи", - "template": "Шаблон", - "text_recognition": "Текст", - "theme": "Тема", - "theme_selection": "Выбор темы", - "theme_selection_description": "Автоматически устанавливать светлую или тёмную тему в зависимости от настроек вашего браузера", - "theme_setting_asset_list_storage_indicator_title": "Отображать индикатор хранилища на плитках объектов", - "theme_setting_asset_list_tiles_per_row_title": "Количество объектов в строке ({count})", - "theme_setting_colorful_interface_subtitle": "Добавить оттенок к фону.", - "theme_setting_colorful_interface_title": "Цвет фона", - "theme_setting_image_viewer_quality_subtitle": "Настройка качества просмотра изображения", - "theme_setting_image_viewer_quality_title": "Качество просмотра изображений", - "theme_setting_primary_color_subtitle": "Основной цвет приложения.", - "theme_setting_primary_color_title": "Основной цвет", - "theme_setting_system_primary_color_title": "Использовать системный цвет", - "theme_setting_system_theme_switch": "Автоматически (как в системе)", - "theme_setting_theme_subtitle": "Настройка темы приложения", - "theme_setting_three_stage_loading_subtitle": "Трехэтапная загрузка может повысить производительность, но значительно нагружает сеть", - "theme_setting_three_stage_loading_title": "Включить трехэтапную загрузку", - "then": "Затем", - "they_will_be_merged_together": "Они будут объединены вместе", - "third_party_resources": "Сторонние ресурсы", - "time": "Время", - "time_based_memories": "Воспоминания, основанные на времени", - "time_based_memories_duration": "Длительность показа слайдов в секундах", - "timeline": "Временная шкала", - "timezone": "Часовой пояс", - "to_archive": "В архив", - "to_change_password": "Изменить пароль", - "to_favorite": "Добавить в избранное", - "to_login": "Вход", - "to_multi_select": "выбрать несколько", - "to_parent": "Вернуться назад", - "to_select": "выбрать", - "to_trash": "Корзина", - "toggle_settings": "Переключение настроек", - "toggle_theme_description": "Переключить тему", - "total": "Всего", - "total_usage": "Общая статистика", - "trash": "Корзина", - "trash_action_prompt": "Объекты перемещены в корзину ({count} шт.)", - "trash_all": "Удалить все", - "trash_count": "Удалить {count, number}", - "trash_delete_asset": "Переместить в корзину", - "trash_emptied": "Корзина очищена", - "trash_no_results_message": "Здесь будут отображаться удалённые фотографии и видео.", - "trash_page_delete_all": "Удалить все", - "trash_page_empty_trash_dialog_content": "Очистить корзину? Объекты в ней будут безвозвратно удалены из Immich.", - "trash_page_info": "Объекты в корзине будут безвозвратно удалены через {days} дней", - "trash_page_no_assets": "Корзина пуста", - "trash_page_restore_all": "Восстановить все", - "trash_page_select_assets_btn": "Выбранные объекты", - "trash_page_title": "Корзина ({count})", - "trashed_items_will_be_permanently_deleted_after": "Объекты, хранящиеся в корзине более {days, plural, one {# дня} other {# дней}}, удаляются автоматически.", - "trigger": "Триггер", - "trigger_asset_uploaded": "Загрузка объекта", - "trigger_asset_uploaded_description": "Срабатывает при загрузке нового объекта", - "trigger_description": "Событие, которое запускает рабочий процесс", - "trigger_person_recognized": "Распознавание человека", - "trigger_person_recognized_description": "Срабатывает при распознавании человека", - "trigger_type": "Тип триггера", - "troubleshoot": "Диагностика", - "type": "Тип", - "unable_to_change_pin_code": "Ошибка при изменении PIN-кода", - "unable_to_check_version": "Ошибка при проверке обновлений", - "unable_to_setup_pin_code": "Ошибка при создании PIN-кода", - "unarchive": "Восстановить", - "unarchive_action_prompt": "Объекты удалены из архива ({count} шт.)", - "unarchived_count": "{count, plural, one {# объект возвращён} many {# объектов возвращено} other {# объекта возвращено}} из архива", - "undo": "Отменить", - "unfavorite": "Удалить из избранного", - "unfavorite_action_prompt": "Объекты удалены из Избранного ({count} шт.)", - "unhide_person": "Показать человека", - "unknown": "Неизвестно", - "unknown_country": "Неизвестная страна", - "unknown_date": "Дата неизвестна", - "unknown_year": "Неизвестный Год", - "unlimited": "Не ограничено", - "unlink_motion_video": "Отсоединить движущееся видео", - "unlink_oauth": "Отключить OAuth", - "unlinked_oauth_account": "Отключить аккаунт OAuth", - "unmute_memories": "Включить звук", - "unnamed_album": "Альбом без названия", - "unnamed_album_delete_confirmation": "Вы уверены, что хотите удалить этот альбом?", - "unnamed_share": "Общий доступ без названия", - "unsaved_change": "Несохранённое изменение", - "unselect_all": "Отменить выделение", - "unselect_all_duplicates": "Выбрать все для удаления", - "unselect_all_in": "Отменить выделение в {group}", - "unstack": "Разгруппировать", - "unstack_action_prompt": "Объекты разгруппированы ({count} шт.)", - "unstacked_assets_count": "{count, plural, one {Разгруппирован # объект} many {Разгруппировано # объектов} other {Разгруппировано # объекта}}", - "unsupported_field_type": "Неподдерживаемый тип поля", - "untagged": "Без тегов", - "untitled_workflow": "Рабочий процесс без названия", - "up_next": "Следующее", - "update_location_action_prompt": "Установить следующие координаты у выбранных объектов ({count} шт.):", - "updated_at": "Обновлён", - "updated_password": "Пароль изменён", - "upload": "Загрузить", - "upload_concurrency": "Параллельность загрузки", - "upload_details": "Подробности загрузки", - "upload_dialog_info": "Хотите загрузить выбранные объекты на сервер?", - "upload_dialog_title": "Загрузить объект", - "upload_error_with_count": "Ошибка при загрузке {count, plural, one {# объекта} other {# объектов}}", - "upload_errors": "Загрузка завершена с {count, plural, one {# ошибкой} other {# ошибками}}, обновите страницу, чтобы увидеть новые загруженные объекты.", - "upload_finished": "Загрузка завершена", - "upload_progress": "Осталось {remaining, number} - Обработано {processed, number}/{total, number}", - "upload_skipped_duplicates": "Пропущен{count, plural, one { # дубликат} many {о # дубликатов} other {о # дубликата}}", - "upload_status_duplicates": "Дубликаты", - "upload_status_errors": "Ошибки", - "upload_status_uploaded": "Загружено", - "upload_success": "Загрузка прошла успешно. Обновите страницу, чтобы увидеть новые объекты.", - "upload_to_immich": "Загрузка в Immich ({count})", - "uploading": "Загружается", - "uploading_media": "Выполняется загрузка", - "url": "URL", - "usage": "Использование", - "use_biometric": "Использовать биометрию", - "use_current_connection": "Использовать текущее подключение", - "use_custom_date_range": "Использовать пользовательский диапазон дат", - "user": "Пользователь", - "user_has_been_deleted": "Этот пользователь был удалён.", - "user_id": "ID пользователя", - "user_liked": "Пользователю {user} нравится {type, select, photo {это фото} video {это видео} asset {этот объект} other {этот альбом}}", - "user_pin_code_settings": "PIN-код", - "user_pin_code_settings_description": "Настройка PIN-кода для доступа к личной папке", - "user_privacy": "Конфиденциальность пользователя", - "user_purchase_settings": "Покупка", - "user_purchase_settings_description": "Управление покупкой", - "user_role_set": "Установить {user} в качестве {role}", - "user_usage_detail": "Информация по пользователям", - "user_usage_stats": "Статистика использования аккаунта", - "user_usage_stats_description": "Посмотреть статистику использования аккаунта", - "username": "Имя пользователя", - "users": "Пользователи", - "users_added_to_album_count": "{count, plural, one {# пользователь добавлен} many {# пользователей добавлено} other {# пользователя добавлено}} к альбому", - "utilities": "Утилиты", - "validate": "Проверить", - "validate_endpoint_error": "Введите корректный URL", - "validation_error": "Ошибка при проверке", - "variables": "Переменные", - "version": "Версия", - "version_announcement_closing": "Твой друг Алекс", - "version_announcement_message": "Здравствуйте! Доступна новая версия приложения. Пожалуйста, прочтите заметки к выпуску и убедитесь, что параметры конфигурации актуальны, дабы избежать ошибок, особенно если используется WatchTower или другой механизм автоматического обновления приложения.", - "version_history": "История версий", - "version_history_item": "Версия {version} установлена {date}", - "video": "Видео", - "video_hover_setting": "Воспроизведение видео при наведении курсора мыши", - "video_hover_setting_description": "Воспроизводить видео при наведении курсора мыши на миниатюру. Даже если эта функция выключена, воспроизведение можно запустить, наведя курсор на значок воспроизведения.", - "videos": "Видео", - "videos_count": "{count, plural, one {# видео} other {# видео}}", - "videos_only": "Только видео", - "view": "Просмотр", - "view_album": "Открыть альбом", - "view_all": "Посмотреть всё", - "view_all_users": "Показать всех пользователей", - "view_asset_owners": "Отображать владельцев объектов", - "view_details": "Посмотреть подробности", - "view_in_timeline": "Показать на временной шкале", - "view_link": "Показать ссылку", - "view_links": "Показать ссылки", - "view_name": "Вид", - "view_next_asset": "Показать следующий объект", - "view_previous_asset": "Показать предыдущий объект", - "view_qr_code": "Посмотреть QR код", - "view_similar_photos": "Найти похожие", - "view_stack": "Показать группу", - "view_user": "Просмотреть пользователя", - "viewer_remove_from_stack": "Убрать из группы", - "viewer_stack_use_as_main_asset": "Использовать в качестве основного объекта", - "viewer_unstack": "Разгруппировать", - "visibility_changed": "Изменена видимость у {count, plural, one {# человека} other {# человек}}", - "visual": "Визуальный", - "visual_builder": "Визуальный конструктор", - "waiting": "В очереди", - "waiting_count": "Ожидают: {count}", - "warning": "Предупреждение", - "week": "Неделя", - "welcome": "Добро пожаловать", - "welcome_to_immich": "Добро пожаловать в Immich", - "width": "Ширина", - "wifi_name": "Имя сети", - "workflow_delete_prompt": "Вы действительно хотите удалить этот рабочий процесс?", - "workflow_deleted": "Рабочий процесс удалён", - "workflow_description": "Описание рабочего процесса", - "workflow_info": "Информация о рабочем процессе", - "workflow_json": "JSON рабочего процесса", - "workflow_json_help": "Отредактируйте конфигурацию рабочего процесса в JSON формате. Изменения будут синхронизированы в визуальный конструктор.", - "workflow_name": "Имя рабочего процесса", - "workflow_navigation_prompt": "Вы действительно хотите выйти без сохранения изменений?", - "workflow_summary": "Информация о рабочем процессе", - "workflow_update_success": "Рабочий процесс успешно обновлён", - "workflow_updated": "Рабочий процесс обновлён", - "workflows": "Рабочие процессы", - "workflows_help_text": "Рабочие процессы позволяют автоматизировать операции с объектами на основании событий и фильтров", - "wrong_pin_code": "Неверный PIN-код", - "year": "Год", - "years_ago": "{years, plural, one {# год} few {# года} many {# лет} other {# года}} назад", - "yes": "Да", - "you_dont_have_any_shared_links": "У вас нет публичных ссылок", - "your_wifi_name": "Имя вашей Wi-Fi сети", - "zero_to_clear_rating": "нажмите 0 для удаления рейтинга", - "zoom_image": "Изменить масштаб", - "zoom_to_bounds": "Увеличить до границ" -} +{} diff --git a/i18n/sk.json b/i18n/sk.json index 197b9717c5..0967ef424b 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -1,2405 +1 @@ -{ - "about": "O aplikácii", - "account": "Účet", - "account_settings": "Nastavenia účtu", - "acknowledge": "Rozumiem", - "action": "Akcia", - "action_common_update": "Aktualizovať", - "action_description": "Súbor akcií, ktoré sa majú vykonať na filtrovaných položkách", - "actions": "Akcie", - "active": "Aktívne", - "active_count": "Aktívne: {count}", - "activity": "Aktivita", - "activity_changed": "Aktivita je {enabled, select, true{povolená} other {zakázaná}}", - "add": "Pridať", - "add_a_description": "Pridať popis", - "add_a_location": "Pridať polohu", - "add_a_name": "Pridať meno", - "add_a_title": "Pridať názov", - "add_action": "Pridať akciu", - "add_action_description": "Kliknutím pridáte akciu, ktorú chcete vykonať", - "add_assets": "Pridať položky", - "add_birthday": "Pridať narodeniny", - "add_endpoint": "Pridať koncový bod", - "add_exclusion_pattern": "Pridať vzor vylúčenia", - "add_filter": "Pridať filter", - "add_filter_description": "Kliknutím pridáte podmienku filtra", - "add_location": "Pridať polohu", - "add_more_users": "Pridať viac používateľov", - "add_partner": "Pridať partnera", - "add_path": "Pridať cestu", - "add_photos": "Pridať fotografie", - "add_tag": "Pridať štítok", - "add_to": "Pridať do…", - "add_to_album": "Pridať do albumu", - "add_to_album_bottom_sheet_added": "Pridané do {album}", - "add_to_album_bottom_sheet_already_exists": "Už je v {album}", - "add_to_album_bottom_sheet_some_local_assets": "Niektoré lokálne súbory nebolo možné pridať do albumu", - "add_to_album_toggle": "Prepnúť výber pre {album}", - "add_to_albums": "Pridať do albumov", - "add_to_albums_count": "Pridať do albumov ({count})", - "add_to_bottom_bar": "Pridať do", - "add_to_shared_album": "Pridať do zdieľaného albumu", - "add_upload_to_stack": "Nahrať a pridať do zoskupených", - "add_url": "Pridať URL", - "add_workflow_step": "Pridať krok pracovného postupu", - "added_to_archive": "Pridané do archívu", - "added_to_favorites": "Pridané do obľúbených", - "added_to_favorites_count": "Pridané {count, number} do obľúbených", - "admin": { - "add_exclusion_pattern_description": "Pridávanie vzorov na vylúčenie. Globovanie pomocou *, ** a ? je podporované. Ak chcete ignorovať všetky súbory v akomkoľvek adresári s názvom \"Raw\", použite \"**/Raw/**\". Ak chcete ignorovať všetky súbory končiace na \".tif\", použite \"**/*.tif\". Ak chcete ignorovať absolútnu cestu, použite príkaz \"/cesta/k/ignorovanym/**\".", - "admin_user": "Správca", - "asset_offline_description": "Táto položka externej knižnice sa už na disku nenachádza a bola presunutá do koša. Pokiaľ bol súbor presunutý v rámci knižnice, skontrolujte časovú os a vyhľadajte nové odpovedajúce položky. Ak chcete túto položku obnoviť, uistite sa, že je cesta k nižšie uvedenému súboru prístupná pre aplikáciu Immich a prehľadajte knižnicu.", - "authentication_settings": "Overovanie a prihlásenie", - "authentication_settings_description": "Spravovať heslo, protokol OAuth a ďalšie nastavenia overenia", - "authentication_settings_disable_all": "Naozaj chcete zakázať všetky spôsoby prihlásenia? Prihlásenie bude úplne zakázané.", - "authentication_settings_reenable": "Pre opätovné povolenie použite Serverový príkaz.", - "background_task_job": "Úlohy na pozadí", - "backup_database": "Vytvoriť výpis databázy", - "backup_database_enable_description": "Povoliť výpisy z databázy", - "backup_keep_last_amount": "Množstvo predchádzajúcich výpisov, ktoré sa majú zachovať", - "backup_onboarding_1_description": "externú kópiu v cloude alebo na inom fyzickom mieste.", - "backup_onboarding_2_description": "lokálne kópie na rôznych zariadeniach. To zahŕňa hlavné súbory a ich lokálnu zálohu.", - "backup_onboarding_3_description": "kompletné kópie vašich údajov vrátane pôvodných súborov. Toto zahŕňa 1 externú kópiu a 2 lokálne kópie.", - "backup_onboarding_description": "Na ochranu vašich údajov sa odporúča stratégia zálohovania 3-2-1. Pre komplexné riešenie zálohovania by ste mali uchovávať kópie nahratých fotografií/videí, ako aj databázy Immich.", - "backup_onboarding_footer": "Ďalšie informácie o vytváraní zálohy Immich nájdete v dokumentácii.", - "backup_onboarding_parts_title": "Zálohovanie 3-2-1 zahŕňa:", - "backup_onboarding_title": "Zálohy", - "backup_settings": "Nastavenia výpisu databázy", - "backup_settings_description": "Spravovať nastavenia výpisu databázy.", - "cleared_jobs": "Vyčistené úlohy pre: {job}", - "config_set_by_file": "Konfigurácia je v súčasnosti nastavená konfiguračným súborom", - "confirm_delete_library": "Naozaj chcete vymazať knižnicu {library}?", - "confirm_delete_library_assets": "Ste si istí, že chcete vymazať túto knižnicu? Tato operácia nenávratne odstráni {count, plural, one {# zahrnutú položku} few {# zahrnuté položky} other {všetkých # zahrnutých položiek}} z aplikácie Immich. Súbory budú ponechané na disku.", - "confirm_email_below": "Pre potvrdenie zadajte \"{email}\" nižšie", - "confirm_reprocess_all_faces": "Naozaj chcete spracovať všetky tváre znova? Tento proces vymaže pomenovaných ľudí.", - "confirm_user_password_reset": "Naozaj chcete obnoviť heslo pre {user}?", - "confirm_user_pin_code_reset": "Ste si istí, že chcete opätovne nastaviť PIN kód používateľa {user}?", - "copy_config_to_clipboard_description": "Skopírovať aktuálnu konfiguráciu systému ako objekt JSON do schránky", - "create_job": "Vytvoriť úlohu", - "cron_expression": "Výraz cron", - "cron_expression_description": "Nastavte interval skenovania pomocou formátu cron. Pre viac informácií navštívte Crontab Guru", - "cron_expression_presets": "Predvoľby výrazov Cron", - "disable_login": "Zakázať prihlásenie", - "duplicate_detection_job_description": "Spustite strojové učenie na položkách pre detekciu podobných obrázkov. Spolieha sa na inteligentné vyhľadávanie", - "exclusion_pattern_description": "Vylučovacie vzory Vám umožňujú ignorovať súbory a priečinky pri skenovaní Vašej knižnice. Toto je užitočné, ak máte priečinky obsahujúce súbory, ktoré nechcete importovať, napríklad RAW súbory.", - "export_config_as_json_description": "Stiahnuť aktuálnu konfiguráciu systému ako súbor JSON", - "external_libraries_page_description": "Stránka externej knižnice správcu", - "face_detection": "Detekcia tvárí", - "face_detection_description": "Rozpoznajte tváre v položkách pomocou strojového učenia. V prípade videí sa berie do úvahy len náhľad. „Aktualizovať“ (znovu) spracuje všetky položky. „Obnoviť“ dodatočne vymaže všetky aktuálne údaje o tvárach. „Chýbajúce“ zaradí do poradia médiá, ktoré ešte neboli spracované. Zistené tváre sa po dokončení rozpoznávania tvárí zaradia do poradia na rozpoznávanie tvárí, pričom sa zoskupia do existujúcich alebo nových osôb.", - "facial_recognition_job_description": "Zoskupte rozpoznané tváre do osôb. Tento krok sa vykoná po dokončení rozpoznávania tvárí. „Obnoviť“ (znovu) zoskupí všetky tváre. „Chýbajúce“ zaradí tváre, ktoré nemajú pridelenú osobu.", - "failed_job_command": "Príkaz {command} zlyhal pre úlohu: {job}", - "force_delete_user_warning": "VAROVANIE: Toto okamžite odstráni používateľa a všetky položky. Tento krok nie je možné vrátiť späť a súbory nebude možné obnoviť.", - "image_format": "Formát", - "image_format_description": "WebP vytvára menšie súbory ako JPEG, ale kódovanie je pomalšie.", - "image_fullsize_description": "Obrázok v plnom rozlíšení s odstránenými metadátami, používané pri priblížení", - "image_fullsize_enabled": "Povolenie generovania obrázkov v plnej veľkosti", - "image_fullsize_enabled_description": "Generovať obrázok v plnej veľkosti pre formáty, ktoré nie sú vhodné pre web. Ak je zapnutá možnosť „Uprednostniť vložený náhľad“, vložené náhľady sa použijú priamo bez konverzie. Nemá vplyv na formáty vhodné pre web, ako je JPEG.", - "image_fullsize_quality_description": "Kvalita obrázku v plnej veľkosti od 1 do 100. Vyššia je lepšia, ale vytvára väčšie súbory.", - "image_fullsize_title": "Nastavenia obrázku v plnej veľkosti", - "image_prefer_embedded_preview": "Uprednostňovať vstavaný náhľad", - "image_prefer_embedded_preview_setting_description": "Použiť vložené náhľady vo fotografiách RAW ako vstup pre spracovanie obrazu a ak sú k dispozícii. To môže vytvoriť presnejšie farby pre niektoré obrázky, ale kvalita náhľadu závisí od fotoaparátu a obrázok môže mať viac kompresných artefaktov.", - "image_prefer_wide_gamut": "Uprednostňovať široký farebný rozsah", - "image_prefer_wide_gamut_setting_description": "Použiť Display P3 pre miniatúry. Toto lepšie zachováva živosť obrázkov so širokým farebným rozsahom. Obrázky sa môžu zobraziť odlišne na starších zariadeniach so starou verziou prehliadača. sRGB obrázky zostávajú sRGB, aby sa zabránilo farebným posunom.", - "image_preview_description": "Stredne veľký obrázok s odstránenými metadátami, používaný pri prezeraní jednej položky a na strojové učenie", - "image_preview_quality_description": "Kvalita náhľadu v stupnici od 1 do 100. Vyššia hodnota znamená lepšiu kvalitu, ale produkuje väčšie súbory a môže znížiť odozvu aplikácie. Nastavenie nižšej hodnoty môže ovplyvniť kvalitu strojového učenia.", - "image_preview_title": "Náhľady", - "image_progressive": "Progresívne", - "image_progressive_description": "Progresívne kódovať JPEG obrázky pre postupné načítanie zobrazenia. Toto nemá žiadny vplyv na WebP obrázky.", - "image_quality": "Kvalita", - "image_resolution": "Rozlíšenie", - "image_resolution_description": "Vyššie rozlíšenie môže zachovať viac detailov, ale kódovanie trvá dlhšie, súbory sú väčšie a môže to znížiť rýchlosť odozvy aplikácie.", - "image_settings": "Obrázky", - "image_settings_description": "Spravovať kvalitu a rozlíšenie generovaných obrázkov", - "image_thumbnail_description": "Malá miniatúra s odstránenými metadátami, ktorá sa používa pri prezeraní skupín fotografií ako na hlavnej časovej osi", - "image_thumbnail_quality_description": "Kvalita miniatúry v stupnici od 1 do 100. Vyššia hodnota znamená lepšiu kvalitu, ale produkuje väčšie súbory a môže znížiť odozvu aplikácie.", - "image_thumbnail_title": "Miniatúry", - "import_config_from_json_description": "Importovať konfiguráciu systému nahraním konfiguračného súboru JSON", - "job_concurrency": "Súbežnosť úlohy - {job}", - "job_created": "Úloha bola vytvorená", - "job_not_concurrency_safe": "Táto úloha nie je bezpečná pre súbežné spracovanie.", - "job_settings": "Úlohy", - "job_settings_description": "Spravovať súbežnosť úloh", - "jobs_delayed": "{jobCount, plural, one {# oneskorený} few {# oneskorené} other {# oneskorených}}", - "jobs_failed": "{jobCount, plural, one {# neúspešný} few {# neúspešné} other {# neúspešných}}", - "jobs_over_time": "Počet úloh za čas", - "library_created": "Vytvorená knižnica: {library}", - "library_deleted": "Knižnica bola vymazaná", - "library_details": "Podrobnosti o knižnici", - "library_folder_description": "Určite priečinok, ktorý chcete importovať. Tento priečinok vrátane podpriečinkov bude prehľadaný na prítomnosť obrázkov a videí.", - "library_remove_exclusion_pattern_prompt": "Naozaj chcete odstrániť tento vzor vylúčenia?", - "library_remove_folder_prompt": "Naozaj chcete odstrániť tento importovaný priečinok?", - "library_scanning": "Pravidelné skenovanie", - "library_scanning_description": "Nastaviť pravidelné skenovanie knižnice", - "library_scanning_enable_description": "Zapnúť pravidelné skenovanie knižnice", - "library_settings": "Externá knižnica", - "library_settings_description": "Spravovať nastavenia externej knižnice", - "library_tasks_description": "Vyhľadajte nové alebo zmenené médiá v externých knižniciach", - "library_updated": "Aktualizovaná knižnica", - "library_watching_enable_description": "Sledovať externé knižnice pre zmeny v súboroch", - "library_watching_settings": "Sledovanie knižnice [EXPERIMENTÁLNE]", - "library_watching_settings_description": "Automaticky sledovať zmenené súbory", - "logging_enable_description": "Povoliť ukladanie záznamov", - "logging_level_description": "Ak je povolené, akú úroveň záznamov použiť.", - "logging_settings": "Ukladanie záznamov", - "machine_learning_availability_checks": "Kontroly dostupnosti", - "machine_learning_availability_checks_description": "Automaticky zistiť a uprednostniť dostupné servery strojového učenia", - "machine_learning_availability_checks_enabled": "Povoliť kontroly dostupnosti", - "machine_learning_availability_checks_interval": "Interval kontroly", - "machine_learning_availability_checks_interval_description": "Interval v milisekundách medzi kontrolami dostupnosti", - "machine_learning_availability_checks_timeout": "Časový limit požiadavky", - "machine_learning_availability_checks_timeout_description": "Časový limit v milisekundách pre kontroly dostupnosti", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Názov modelu CLIP je uvedený tu. Pamätajte, že pri zmene modelu je nutné znovu spustiť úlohu 'Inteligentné vyhľadávanie' pre všetky obrázky.", - "machine_learning_duplicate_detection": "Detekcia duplikátov", - "machine_learning_duplicate_detection_enabled": "Povoliť detekciu duplikátov", - "machine_learning_duplicate_detection_enabled_description": "Ak je vypnuté, úplne identické položky budú stále deduplikované.", - "machine_learning_duplicate_detection_setting_description": "Použiť CLIP embeddings na identifikáciu pravdepodobných duplikátov", - "machine_learning_enabled": "Povoliť strojové učenie", - "machine_learning_enabled_description": "Ak je vypnuté, všetky funkcie strojového učenia (ML) budú vypnuté, bez ohľadu na nastavenia nižšie.", - "machine_learning_facial_recognition": "Rozpoznávanie tvárí", - "machine_learning_facial_recognition_description": "Detekovať, rozpoznať a zoskupiť tváre na obrázkoch", - "machine_learning_facial_recognition_model": "Model pre rozpoznávanie tvárí", - "machine_learning_facial_recognition_model_description": "Modely sú zoradené od najväčšieho po najmenší. Väčšie modely sú pomalšie a vyžadujú viac pamäte, ale poskytujú lepšie výsledky. Pamätajte, že po zmene modelu je potrebné znovu spustiť úlohu detekcie tvárí pre všetky obrázky.", - "machine_learning_facial_recognition_setting": "Povoliť rozpoznávanie tvárí", - "machine_learning_facial_recognition_setting_description": "Ak je vypnuté, obrázky nebudú spracované pre rozpoznávanie tvárí a nebudú sa zobrazovať v sekcii Ľudia na stránke Preskúmať.", - "machine_learning_max_detection_distance": "Maximálna detekčná odchýlka", - "machine_learning_max_detection_distance_description": "Maximálna odchýlka medzi dvoma obrázkami, aby boli považované za duplikáty, v rozsahu od 0.001 do 0.1. Vyššie hodnoty odhalia viac duplikátov, ale môžu viesť k falošným pozitívam.", - "machine_learning_max_recognition_distance": "Maximálna rozpoznávacia odchýlka", - "machine_learning_max_recognition_distance_description": "Maximálna odchýlka medzi dvomi tvárami, aby boli považované za rovnakú osobu, v rozsahu od 0 do 2. Zníženie tejto hodnoty môže zabrániť označeniu dvoch ľudí za tú istú osobu, zatiaľ čo zvýšenie môže zabrániť označeniu jednej osoby za dve rôzne osoby. Pamätajte, že je jednoduchšie spojiť dvoch ľudí ako rozdeliť jednu osobu na dve, takže je lepšie voliť nižší prah, ak je to možné.", - "machine_learning_min_detection_score": "Minimálne detekčné skóre", - "machine_learning_min_detection_score_description": "Minimálne skóre dôveryhodnosti pre detekciu tváre v rozsahu od 0 do 1. Nižšie hodnoty odhalia viac tvárí, ale môžu viesť k falošným pozitivním výsledkom.", - "machine_learning_min_recognized_faces": "Minimum rozpoznaných tvárí", - "machine_learning_min_recognized_faces_description": "Minimálny počet rozpoznaných tvárí potrebných na vytvorenie osoby. Zvýšením tejto hodnoty sa zvyšuje presnosť rozpoznávania tvárí, ale tiež sa zvyšuje pravdepodobnosť, že tvár nebude priradená osobe.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Použiť strojové učenie na rozpoznávanie textu na obrázkoch", - "machine_learning_ocr_enabled": "Povoliť OCR", - "machine_learning_ocr_enabled_description": "Ak je táto funkcia vypnutá, obrázky nebudú podliehať rozpoznávaniu textu.", - "machine_learning_ocr_max_resolution": "Maximálne rozlíšenie", - "machine_learning_ocr_max_resolution_description": "Náhľady presahujúce toto rozlíšenie budú zmenené tak, aby sa zachoval pomer strán. Vyššie hodnoty sú presnejšie, ale ich spracovanie trvá dlhšie a využívajú viac pamäte.", - "machine_learning_ocr_min_detection_score": "Minimálne skóre detekcie", - "machine_learning_ocr_min_detection_score_description": "Minimálne skóre spoľahlivosti pre detekciu textu od 0 do 1. Nižšie hodnoty detekujú viac textu, ale môžu viesť k falošným pozitívam.", - "machine_learning_ocr_min_recognition_score": "Minimálne skóre rozpoznania", - "machine_learning_ocr_min_score_recognition_description": "Minimálne skóre spoľahlivosti pre rozpoznanie detegovaného textu v rozmedzí od 0 po 1. Nižšie hodnoty rozpoznajú viac textu, ale môžu viesť k falošným pozitívam.", - "machine_learning_ocr_model": "OCR model", - "machine_learning_ocr_model_description": "Serverové modely sú presnejšie ako mobilné modely, ale ich spracovanie trvá dlhšie a využívajú viac pamäte.", - "machine_learning_settings": "Strojové učenie", - "machine_learning_settings_description": "Spravovať funkcie a nastavenia strojového učenia", - "machine_learning_smart_search": "Inteligentné vyhľadávanie", - "machine_learning_smart_search_description": "Významové vyhľadávanie v obrázkoch pomocou CLIP vzorov", - "machine_learning_smart_search_enabled": "Povoliť inteligentné vyhľadávanie", - "machine_learning_smart_search_enabled_description": "Ak je vypnuté, obrázky nebudú spracované pre inteligentné vyhľadávanie.", - "machine_learning_url_description": "URL adresa servera strojového učenia. Ak je zadaných viacero adries URL, každý server bude testovaný postupne, kým jeden z nich neodpovie úspešne, v poradí od prvého po posledný. Servery, ktoré neodpovedajú, budú dočasne ignorované, kým nebudú opäť online.", - "maintenance_delete_backup": "Vymazať zálohu", - "maintenance_delete_backup_description": "Tento súbor bude nezvratne vymazaný.", - "maintenance_delete_error": "Nepodarilo sa vymazať zálohu.", - "maintenance_restore_backup": "Obnoviť zálohu", - "maintenance_restore_backup_description": "Immich bude vymazaný a obnovený zo zvolenej zálohy. Pred pokračovaním bude vytvorená záloha.", - "maintenance_restore_backup_different_version": "Táto záloha bola vytvorená pomocou inej verzie aplikácie Immich!", - "maintenance_restore_backup_unknown_version": "Nepodarilo sa zistiť verziu zálohy.", - "maintenance_restore_database_backup": "Obnoviť zálohu databázy", - "maintenance_restore_database_backup_description": "Vrátiť sa do predchádzajúceho stavu databázy pomocou záložného súboru", - "maintenance_settings": "Údržba", - "maintenance_settings_description": "Prepnúť Immich do režimu údržby.", - "maintenance_start": "Prepnúť do režimu údržby", - "maintenance_start_error": "Nepodarilo sa spustiť režim údržby.", - "maintenance_upload_backup": "Nahrať zálohu databázy na server", - "maintenance_upload_backup_error": "Nepodarilo sa nahrať zálohu, je to súbor .sql/.sql.gz?", - "manage_concurrency": "Spravovať súbežnosť", - "manage_concurrency_description": "Prejsť na stránku úloh, kde môžete spravovať súbežnosť úloh", - "manage_log_settings": "Spravovať nastavenia ukladania záznamov", - "map_dark_style": "Tmavý štýl", - "map_enable_description": "Povoliť funkcie mapy", - "map_gps_settings": "Mapa a nastavenia GPS", - "map_gps_settings_description": "Spravovať nastavenia mapy a GPS (reverzné geokódovanie)", - "map_implications": "Táto funkčnosť sa spolieha na externý servis spracovania mapových dlaždíc (tiles.immich.cloud)", - "map_light_style": "Svetlý štýl", - "map_manage_reverse_geocoding_settings": "Spravovať nastavenia reverzného geokódovania", - "map_reverse_geocoding": "Reverzné Geokódovanie", - "map_reverse_geocoding_enable_description": "Povoliť reverzné geokódovanie", - "map_reverse_geocoding_settings": "Reverzné geokódovanie", - "map_settings": "Mapa", - "map_settings_description": "Spravovať nastavenia mapy", - "map_style_description": "URL na motív style.json", - "memory_cleanup_job": "Vymazávanie spomienok", - "memory_generate_job": "Vytváranie spomienok", - "metadata_extraction_job": "Extrahovať metadáta", - "metadata_extraction_job_description": "Získajte informácie o metadátach z každého média, ako sú GPS, tváre a rozlíšenie", - "metadata_faces_import_setting": "Povoliť import tváre", - "metadata_faces_import_setting_description": "Importovať tváre z EXIF údajov obrázka a pridružených súborov", - "metadata_settings": "Metadáta", - "metadata_settings_description": "Spravovať nastavenia metadát", - "migration_job": "Migrácia", - "migration_job_description": "Presunúť miniatúry pre médiá a tváre do najnovšej štruktúry priečinkov", - "nightly_tasks_cluster_faces_setting_description": "Spustiť rozpoznávanie tváre na novo-zistených tvárach", - "nightly_tasks_cluster_new_faces_setting": "Zoskupiť nové tváre", - "nightly_tasks_database_cleanup_setting": "Úlohy čistenia databázy", - "nightly_tasks_database_cleanup_setting_description": "Vyčistiť databázu od starých, neplatných údajov", - "nightly_tasks_generate_memories_setting": "Vytvoriť spomienky", - "nightly_tasks_generate_memories_setting_description": "Vytvoriť nové spomienky z položiek", - "nightly_tasks_missing_thumbnails_setting": "Vytvoriť chýbajúce náhľady", - "nightly_tasks_missing_thumbnails_setting_description": "Zaradiť položky bez náhľadov do poradia na vytvorenie náhľadov", - "nightly_tasks_settings": "Nastavenia nočných úloh", - "nightly_tasks_settings_description": "Spravovať nočné úlohy", - "nightly_tasks_start_time_setting": "Čas spustenia", - "nightly_tasks_start_time_setting_description": "Čas, kedy server začne vykonávať nočné úlohy", - "nightly_tasks_sync_quota_usage_setting": "Synchronizovať využitie kvóty", - "nightly_tasks_sync_quota_usage_setting_description": "Aktualizovať kvótu úložiska používateľa na základe aktuálneho využitia", - "no_paths_added": "Neboli pridané žiadne cesty", - "no_pattern_added": "Nebol pridaný žiadny vzor", - "note_apply_storage_label_previous_assets": "Poznámka: Ak chcete použiť štítok úložiska na predtým nahrané položky, spustite príkaz", - "note_cannot_be_changed_later": "POZNÁMKA: Toto nie je možné neskôr zmeniť!", - "notification_email_from_address": "Z adresy", - "notification_email_from_address_description": "E-mailová adresa odosielateľa, napríklad: \"Immich Foto Server \". Uistite sa, že používate adresu, z ktorej máte povolené odosielať e-maily.", - "notification_email_host_description": "Adresa emailového serveru (príklad: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorovať chyby certifikátu", - "notification_email_ignore_certificate_errors_description": "Ignorovať chyby pri overení TLS certifikátu (neodporúča sa)", - "notification_email_password_description": "Heslo pre autentifikáciu s emailovým serverom", - "notification_email_port_description": "Porty e-mailového servera (napr. 25, 465, alebo 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Použiť SMTPS (SMTP cez TLS)", - "notification_email_sent_test_email_button": "Odoslať testovací e-mail a uložiť", - "notification_email_setting_description": "Nastavenie pre odosielanie e-mailových upozornení", - "notification_email_test_email": "Odoslať testovací email", - "notification_email_test_email_failed": "Odosielanie testovacieho e-mailu zlyhalo, skontrolujte hodnoty", - "notification_email_test_email_sent": "Testovací e-mail bol odoslaný na adresu {email}. Prosím skontrolujte si Doručenú poštu.", - "notification_email_username_description": "Používateľské meno, ktoré sa má použiť pri overovaní s e-mailovým serverom", - "notification_enable_email_notifications": "Povoliť e-mailové upozornenia", - "notification_settings": "Upozornenia", - "notification_settings_description": "Spravovať nastavenia upozornení, vrátane emailu", - "oauth_auto_launch": "Automatické spustenie", - "oauth_auto_launch_description": "Automatické spustenie OAuth prihlasovacieho toku pri otvorení prihlasovacej stránky", - "oauth_auto_register": "Automatická regristrácia", - "oauth_auto_register_description": "Automatické zaregistrovanie nového požívateľa pri prihlásení pomocou OAuth", - "oauth_button_text": "Text tlačítka", - "oauth_client_secret_description": "Vyžadované pre dôverného klienta alebo ak OAuth nepodporuje PKCE (Proof Key for Code Exchange).", - "oauth_enable_description": "Prihlásiť sa pomocou OAuth", - "oauth_mobile_redirect_uri": "URI mobilného presmerovania", - "oauth_mobile_redirect_uri_override": "Prepísanie URI mobilného presmerovania", - "oauth_mobile_redirect_uri_override_description": "Povoľte, keď poskytovateľ protokolu OAuth nepovoľuje identifikátor URI pre mobilné zariadenia, napríklad ''{callback}''", - "oauth_role_claim": "Požiadavka na rolu", - "oauth_role_claim_description": "Automaticky udeliť prístup správcu na základe prítomnosti tejto požiadavky. Požiadavka môže mať príznak „user“ alebo „admin“.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Spravovať nastavenia prihlásenia OAuth", - "oauth_settings_more_details": "Pre viac informácii o tejto funkcii, prejdite na dokumentáciu.", - "oauth_storage_label_claim": "Požiadavka na štítok úložiska", - "oauth_storage_label_claim_description": "Automaticky nastaviť štítok úložiska používateľa na hodnotu tejto požiadavky.", - "oauth_storage_quota_claim": "Požiadavka na kvótu úložiska", - "oauth_storage_quota_claim_description": "Automaticky nastaviť kvótu úložiska používateľa na hodnotu tejto požiadavky.", - "oauth_storage_quota_default": "Predvolený limit úložiska (GiB)", - "oauth_storage_quota_default_description": "Kvóta v GiB, ktorá sa použije, ak nie je poskytnutá žiadna požiadavka.", - "oauth_timeout": "Časový limit požiadavky", - "oauth_timeout_description": "Časový limit pre požiadavky v milisekundách", - "ocr_job_description": "Použiť strojové učenie na rozpoznávanie textu na obrázkoch", - "password_enable_description": "Prihlásiť sa pomocou emailu a hesla", - "password_settings": "Prihlásenie cez heslo", - "password_settings_description": "Spravovať nastavenia prihlásenia cez heslo", - "paths_validated_successfully": "Všetky cesty boli úspešne overené", - "person_cleanup_job": "Prečistenie osôb", - "queue_details": "Podrobnosti o poradí", - "queues": "Poradie úloh", - "queues_page_description": "Stránka poradia úloh správcu", - "quota_size_gib": "Veľkosť kvóty (GiB)", - "refreshing_all_libraries": "Obnovujú sa všetky knižnice", - "registration": "Registrácia administrátora", - "registration_description": "Keďže ste prvým používateľom v systéme, budú vám pridelené správcovské práva na vykonávanie všetkých úloh a vrátane tvorby nových používateľov.", - "remove_failed_jobs": "Odstrániť neúspešné úlohy", - "require_password_change_on_login": "Vyžadovať od používateľa zmenu hesla pri prvom prihlásení", - "reset_settings_to_default": "Obnoviť pôvodné nastavenia", - "reset_settings_to_recent_saved": "Nastavenia boli obnovené na posledné uložené nastavenia", - "scanning_library": "Knižnica sa skenuje", - "search_jobs": "Vyhľadať úlohy…", - "send_welcome_email": "Odoslať uvítací e-mail", - "server_external_domain_settings": "Externá doména", - "server_external_domain_settings_description": "Verejná doména pre zdieľané odkazy, vrátane http(s)://", - "server_public_users": "Verejní používatelia", - "server_public_users_description": "Všetci používatelia (meno a email) sú uvedení pri pridávaní používateľa do zdieľaných albumov. Ak je táto funkcia vypnutá, zoznam používateľov bude dostupný iba správcom.", - "server_settings": "Server", - "server_settings_description": "Spravovať nastavenia servera", - "server_stats_page_description": "Stránka serverových štatistík správcu", - "server_welcome_message": "Uvítacia správa", - "server_welcome_message_description": "Správa, ktorá sa zobrazí na prihlasovacej stránke.", - "settings_page_description": "Stránka nastavení správcu", - "sidecar_job": "Pridružené metadáta", - "sidecar_job_description": "Objavte alebo synchronizujte pridružené metadáta zo súborového systému", - "slideshow_duration_description": "Čas zobrazenia obrázku v sekundách", - "smart_search_job_description": "Spustite strojové učenie na médiách na podporu inteligentného vyhľadávania", - "storage_template_date_time_description": "Časová pečiatka vytvorenia položky sa používa pre informácie o dátume a čase", - "storage_template_date_time_sample": "Ukážkový čas {date}", - "storage_template_enable_description": "Povoliť nástroj šablóny úložiska", - "storage_template_hash_verification_enabled": "Overenie hash povolené", - "storage_template_hash_verification_enabled_description": "Povolí overenie hash, nezakazujte to, pokiaľ si nie ste istí dôsledkami", - "storage_template_migration": "Migrácia šablóny úložiska", - "storage_template_migration_description": "Použiť aktuálnu {template} na predtým nahrané médiá", - "storage_template_migration_info": "Šablóna úložiska skonvertuje všetky prípony na malé písmená. Zmeny šablón sa budú vzťahovať iba na nové položky. Ak chcete šablónu spätne použiť na predtým nahrané médiá, spustite {job}.", - "storage_template_migration_job": "Úloha migrácie šablóny úložiska", - "storage_template_more_details": "Podrobnejšie informácie o tejto funkcii nájdete v časti šablóna úložiska a jej následky", - "storage_template_onboarding_description_v2": "Ak je táto funkcia zapnutá, automaticky usporiada súbory na základe šablóny definovanej používateľom. Ďalšie informácie nájdete v dokumentácii.", - "storage_template_path_length": "Približný limit dĺžky cesty: {length, number}/{limit, number}", - "storage_template_settings": "Šablóna úložiska", - "storage_template_settings_description": "Spravujte štruktúru priečinkov a názov súboru odovzdaného média", - "storage_template_user_label": "{label} je štítok úložiska používateľa", - "system_settings": "Nastavenia systému", - "tag_cleanup_job": "Prečistenie štítkov", - "template_email_available_tags": "V šablóne môžete použiť nasledujúce premenné: {tags}", - "template_email_if_empty": "Ak je šablóna prázdna, použije sa predvolený e-mail.", - "template_email_invite_album": "Šablóna Pozvánky do albumu", - "template_email_preview": "Ukážka", - "template_email_settings": "Emailové šablóny", - "template_email_update_album": "Upraviť šablónu albumu", - "template_email_welcome": "Šablóna uvítacieho e-mailu", - "template_settings": "Šablóna upozornení", - "template_settings_description": "Spravovanie vlastných šablón upozornení", - "theme_custom_css_settings": "Vlastné CSS", - "theme_custom_css_settings_description": "CSS štýly umožňujú prispôsobiť dizajn Immich.", - "theme_settings": "Nastavenia témy", - "theme_settings_description": "Spravovať prispôsobenie webového rozhrania Immich", - "thumbnail_generation_job": "Generovať miniatúry", - "thumbnail_generation_job_description": "Vytvoriť veľké, malé a rozmazané náhľady pre každú položku, ako aj náhľady pre každú osobu", - "transcoding_acceleration_api": "API pre akceleráciu", - "transcoding_acceleration_api_description": "Rozhranie API, ktoré bude spolupracovať s vaším zariadením s cieľom urýchliť prekódovanie. Toto nastavenie je „najlepšie úsilie“: pri zlyhaní sa vráti k softvérovému prekódovaniu. VP9 môže alebo nemusí fungovať v závislosti od vášho hardvéru.", - "transcoding_acceleration_nvenc": "NVENC (vyžaduje NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (vyžaduje 7. generáciu Intel CPU alebo novšiu)", - "transcoding_acceleration_rkmpp": "RKMPP (iba na Rockchip SOC)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Akceptované zvukové kodeky", - "transcoding_accepted_audio_codecs_description": "Vyberte, ktoré zvukové kodeky nie je potrebné prekódovať. Používa sa len pre určité pravidlá prekódovania.", - "transcoding_accepted_containers": "Akceptované kontajnery", - "transcoding_accepted_containers_description": "Vyberte, ktoré formáty kontajnerov nie je potrebné remuxovať na MP4. Používa sa len pre určité pravidlá prekódovania.", - "transcoding_accepted_video_codecs": "Akceptované video kodeky", - "transcoding_accepted_video_codecs_description": "Vyberte, ktoré video kodeky nie je potrebné prekódovať. Používa sa len pre určité pravidlá prekódovania.", - "transcoding_advanced_options_description": "Možnosti, ktoré by väčšina používateľov nemala meniť", - "transcoding_audio_codec": "Zvukový kodek", - "transcoding_audio_codec_description": "Opus je najkvalitnejšia možnosť, ale má nižšiu kompatibilitu so starými zariadeniami alebo softvérom.", - "transcoding_bitrate_description": "Videá presahujúce maximálnu bitovú rýchlosť alebo videá, ktoré nie sú v akceptovanom formáte", - "transcoding_codecs_learn_more": "Ak sa chcete dozvedieť viac o tu použitej terminológii, pozrite si dokumentáciu FFmpeg pre kodek H.264, kodek HEVC a VP9 kodek.", - "transcoding_constant_quality_mode": "Režim konštantnej kvality", - "transcoding_constant_quality_mode_description": "ICQ je lepšie ako CQP, ale niektoré zariadenia na hardvérovú akceleráciu tento režim nepodporujú. Nastavenie tejto možnosti uprednostní špecifikovaný režim pri použití kódovania založeného na kvalite. Ignorované funkciou NVENC, pretože nepodporuje ICQ.", - "transcoding_constant_rate_factor": "Faktor konštantnej rýchlosti (-crf)", - "transcoding_constant_rate_factor_description": "Úroveň kvality videa. Typické hodnoty sú 23 pre H.264, 28 pre HEVC, 31 pre VP9 a 35 pre AV1. Nižšie je lepšie, ale vytvára väčšie súbory.", - "transcoding_disabled_description": "Neprekódovať žiadne videá, na niektorých klientoch môže narušiť prehrávanie", - "transcoding_encoding_options": "Možnosti kódovania", - "transcoding_encoding_options_description": "Nastavte kodeky, rozlíšenie, kvalitu a ďalšie možnosti pre kódované videá", - "transcoding_hardware_acceleration": "Hardvérová akcelerácia", - "transcoding_hardware_acceleration_description": "Experimentálne: rýchlejšie prekódovanie, ale môže sa znížiť kvalita pri rovnakom dátovom toku", - "transcoding_hardware_decoding": "Hardvérové dekódovanie", - "transcoding_hardware_decoding_setting_description": "Umožňuje end-to-end zrýchlenie namiesto iba zrýchlenia kódovania. Nemusí fungovať na všetkých videách.", - "transcoding_max_b_frames": "Maximálny počet B-snímkov", - "transcoding_max_b_frames_description": "Vyššie hodnoty zvyšujú účinnosť kompresie, ale spomaľujú kódovanie. Nemusí byť kompatibilný s hardvérovou akceleráciou na starších zariadeniach. Hodnota 0 zakáže B-snímky, zatiaľ čo -1 nastaví túto hodnotu automaticky.", - "transcoding_max_bitrate": "Maximálna bitová rýchlosť", - "transcoding_max_bitrate_description": "Nastavenie maximálneho dátového toku môže zvýšiť predvídateľnosť veľkosti súborov za cenu menšieho zníženia kvality. Pri rozlíšení 720p sú typické hodnoty 2600 kbit/s pre VP9 alebo HEVC alebo 4500 kbit/s pre H.264. Zakázané, ak je nastavená hodnota 0. Ak nie je špecifikovaná žiadna jednotka, predpokladá sa k (pre kbit/s); preto sú 5000, 5000k a 5M (pre Mbit/s) ekvivalentné.", - "transcoding_max_keyframe_interval": "Maximálny interval medzi kľúčovými snímkami", - "transcoding_max_keyframe_interval_description": "Nastavuje maximálnu vzdialenosť medzi kľúčovými snímkami. Nižšie hodnoty zhoršujú účinnosť kompresie, ale zlepšujú časy vyhľadávania a môžu zlepšiť kvalitu v scénach s rýchlym pohybom. Hodnota 0 nastavuje túto hodnotu automaticky.", - "transcoding_optimal_description": "Videá s vyšším ako cieľovým rozlíšením alebo videá, ktoré nie sú v prijateľnom formáte", - "transcoding_policy": "Pravidlá prekódovania", - "transcoding_policy_description": "Nastavte, kedy bude video prekódované", - "transcoding_preferred_hardware_device": "Uprednostňované hardvérové zariadenie", - "transcoding_preferred_hardware_device_description": "Platí len pre VAAPI a QSV. Nastavuje uzol dri, ktorý sa používa na hardvérové prekódovanie.", - "transcoding_preset_preset": "Predvoľba (-preset)", - "transcoding_preset_preset_description": "Rýchlosť kompresie. Pomalšie predvoľby vytvárajú menšie súbory a zvyšujú kvalitu, keď sa zameriavajú na určitý dátový tok. VP9 ignoruje rýchlosti vyššie ako „rýchlejšie“.", - "transcoding_reference_frames": "Referenčné snímky", - "transcoding_reference_frames_description": "Počet snímok, na ktoré sa má odkazovať pri kompresii daného snímku. Vyššie hodnoty zvyšujú účinnosť kompresie, ale spomaľujú kódovanie. Hodnota 0 sa nastavuje automaticky.", - "transcoding_required_description": "Iba videá, ktoré nie sú v prijateľnom formáte", - "transcoding_settings": "Nastavenia prekódovania videa", - "transcoding_settings_description": "Spravovať, ktoré videá sa majú prekódovať a ako sa majú spracovať", - "transcoding_target_resolution": "Cieľové rozlíšenie", - "transcoding_target_resolution_description": "Vyššie rozlíšenia môžu zachovať viac detailov, ale ich kódovanie trvá dlhšie, majú väčšiu veľkosť súborov a môžu znížiť odozvu aplikácie.", - "transcoding_temporal_aq": "Časové AQ", - "transcoding_temporal_aq_description": "Platí len pre NVENC. Časová adaptívna kvantizácia zvyšuje kvalitu scén s vysokým počtom detailov a nízkym počtom pohybov. Nemusí byť kompatibilné so staršími zariadeniami.", - "transcoding_threads": "Vlákna", - "transcoding_threads_description": "Vyššie hodnoty vedú k rýchlejšiemu kódovaniu, ale ponechávajú serveru menej priestoru na spracovanie iných úloh počas aktivity. Táto hodnota by nemala byť väčšia ako počet jadier CPU. Maximalizuje využitie, ak je nastavená na hodnotu 0.", - "transcoding_tone_mapping": "Tónové mapovanie", - "transcoding_tone_mapping_description": "Snaží sa zachovať vzhľad videí HDR pri konverzii na SDR. Každý algoritmus robí rôzne kompromisy v oblasti farieb, detailov a jasu. Hable zachováva detaily, Mobius zachováva farby a Reinhard zachováva jas.", - "transcoding_transcode_policy": "Pravidlá prekódovania", - "transcoding_transcode_policy_description": "Pravidlá, kedy sa má video prekódovať. HDR videá sa prekódujú vždy (okrem prípadov, keď je prekódovanie vypnuté).", - "transcoding_two_pass_encoding": "Dvojfázové kódovanie", - "transcoding_two_pass_encoding_setting_description": "Prekódovať v dvoch fázach, aby sa vytvorili lepšie kódované videá. Keď je povolený maximálny dátový tok (vyžaduje sa na prácu s formátmi H.264 a HEVC), tento režim používa rozsah dátového toku na základe maximálneho dátového toku a ignoruje CRF. V prípade VP9 sa CRF môže použiť, ak je maximálny bitrate vypnutý.", - "transcoding_video_codec": "Video kodek", - "transcoding_video_codec_description": "VP9 má vysokú účinnosť a kompatibilitu s webom, ale prekódovanie trvá dlhšie. HEVC má podobnú výkonnosť, ale nižšiu kompatibilitu s webom. H.264 je široko kompatibilný a rýchlo sa prekóduje, ale vytvára oveľa väčšie súbory. AV1 je najúčinnejší kodek, ale chýba mu podpora v starších zariadeniach.", - "trash_enabled_description": "Povoliť funkcie koša", - "trash_number_of_days": "Počet dní", - "trash_number_of_days_description": "Počet dní, počas ktorých sa majú médiá ponechať v koši pred ich trvalým odstránením", - "trash_settings": "Nastavenia koša", - "trash_settings_description": "Spravovať nastavenia koša", - "unlink_all_oauth_accounts": "Odpojiť všetky účty OAuth", - "unlink_all_oauth_accounts_description": "Nezabudnite odpojiť všetky účty OAuth pred prechodom na nového poskytovateľa.", - "unlink_all_oauth_accounts_prompt": "Naozaj chcete odpojiť všetky účty OAuth? Týmto krokom sa vynuluje identifikátor OAuth pre každého používateľa a tento krok nie je možné vrátiť späť.", - "user_cleanup_job": "Prečistenie používateľov", - "user_delete_delay": "Konto používateľa {user} a jeho médiá budú podľa plánu natrvalo vymazané za {delay, plural, one {# deň} few {# dni} other {# dní}}.", - "user_delete_delay_settings": "Oneskorenie vymazania", - "user_delete_delay_settings_description": "Počet dní, po ktorých sa po odstránení používateľa natrvalo odstráni jeho účet a položky. Úloha odstraňovania používateľov sa spúšťa o polnoci, aby sa skontrolovali používatelia, ktorí sú pripravení na odstránenie. Zmeny tohto nastavenia sa vyhodnotia pri ďalšom spustení.", - "user_delete_immediately": "Konto a médiá používateľa {user} budú zaradené do poradia na trvalé vymazanie okamžite.", - "user_delete_immediately_checkbox": "Zaradiť používateľa a položky do poradia na okamžité vymazanie", - "user_details": "Podrobnosti o používateľovi", - "user_management": "Spravovanie používateľov", - "user_password_has_been_reset": "Heslo používateľa bolo obnovené:", - "user_password_reset_description": "Poskytnite používateľovi dočasné heslo a informujte ho, že si ho bude musieť zmeniť pri ďalšom prihlásení.", - "user_restore_description": "Účet používateľa {user} bude znovu obnovený.", - "user_restore_scheduled_removal": "Obnoviť používateľa - plánované odstránenie na {date, date, long}", - "user_settings": "Nastavenia používateľa", - "user_settings_description": "Spravovať používateľské nastavenia", - "user_successfully_removed": "Používateľ {email} bol úspešne odstránený.", - "users_page_description": "Stránka používateľov pre správcu", - "version_check_enabled_description": "Povoliť kontrolu verzie", - "version_check_implications": "Funkcia kontroly verzie sa spolieha na pravidelnú komunikáciu s github.com", - "version_check_settings": "Kontrola verzie", - "version_check_settings_description": "Povoliť/zakázať upozornenia na novú verziu", - "video_conversion_job": "Prekódovať videá", - "video_conversion_job_description": "Prekódovať videá pre širšiu kompatibilitu s prehliadačmi a zariadeniami" - }, - "admin_email": "Email správcu", - "admin_password": "Administrátorské heslo", - "administration": "Administrácia", - "advanced": "Pokročilé", - "advanced_settings_clear_image_cache": "Vyčistiť vyrovnávaciu pamäť obrázkov", - "advanced_settings_clear_image_cache_error": "Nepodarilo sa vyčistiť vyrovnávaciu pamäť obrázkov", - "advanced_settings_clear_image_cache_success": "Úspešne vyčistených {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Túto možnosť použite na filtrovanie médií počas synchronizácie na základe alternatívnych kritérií. Túto možnosť vyskúšajte len vtedy, ak máte problémy s detekciou všetkých albumov v aplikácii.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTÁLNE] Použiť alternatívny filter synchronizácie albumu zariadenia", - "advanced_settings_log_level_title": "Úroveň ukladania záznamov: {level}", - "advanced_settings_prefer_remote_subtitle": "V niektorých zariadeniach sa miniatúry z miestnych položiek načítavajú veľmi pomaly. Aktivovaním tohto nastavenia sa namiesto toho načítajú vzdialené obrázky.", - "advanced_settings_prefer_remote_title": "Uprednostniť vzdialené obrázky", - "advanced_settings_proxy_headers_subtitle": "Určite hlavičky proxy servera, ktoré by mal Immich posielať s každou požiadavkou na sieť", - "advanced_settings_proxy_headers_title": "Vlastné proxy hlavičky [EXPERIMENTÁLNE]", - "advanced_settings_readonly_mode_subtitle": "Aktivuje režim iba na čítanie, v ktorom je možné fotografie iba prezerať, pričom funkcie ako výber viacerých obrázkov, zdieľanie, prenášanie a mazanie sú deaktivované. Aktivácia/deaktivácia režimu iba na čítanie prostredníctvom obrázku používateľa na hlavnej obrazovke", - "advanced_settings_readonly_mode_title": "Režim iba na čítanie", - "advanced_settings_self_signed_ssl_subtitle": "Preskakuje overovanie SSL certifikátom zo strany servera. Vyžaduje sa pre samo-podpísané certifikáty.", - "advanced_settings_self_signed_ssl_title": "Povoliť samo-podpísané SSL certifikáty [EXPERIMENTÁLNE]", - "advanced_settings_sync_remote_deletions_subtitle": "Automaticky vymazať alebo obnoviť položku na tomto zariadení, keď sa táto akcia vykoná na webe", - "advanced_settings_sync_remote_deletions_title": "Synchronizovať vzdialené vymazania [EXPERIMENTÁLNE]", - "advanced_settings_tile_subtitle": "Pokročilé nastavenia používateľa", - "advanced_settings_troubleshooting_subtitle": "Povoliť ďalšie funkcie pre opravu chýb", - "advanced_settings_troubleshooting_title": "Oprava chýb", - "age_months": "Vek {months, plural, one {# mesiac} few {# mesiace} other {# mesiacov}}", - "age_year_months": "Vek 1 rok, {months, plural, one {# mesiac} few {# mesiace} other {# mesiacov}}", - "age_years": "{years, plural, other {Vek #}}", - "album": "Album", - "album_added": "Album bol pridaný", - "album_added_notification_setting_description": "Obdržať upozornenie emailom, keď vás pridajú do zdieľaného albumu", - "album_cover_updated": "Obal albumu aktualizovaný", - "album_delete_confirmation": "Ste si istý, že chcete odstrániť album {album}?", - "album_delete_confirmation_description": "Ak je tento album zdieľaný, ostatní používatelia k nemu už nebudú mať prístup.", - "album_deleted": "Album bol vymazaný", - "album_info_card_backup_album_excluded": "VYLÚČENÉ", - "album_info_card_backup_album_included": "ZAHRNUTÉ", - "album_info_updated": "Informácie albumu aktualizované", - "album_leave": "Opustiť album?", - "album_leave_confirmation": "Ste si istý, že chcete opustiť album {album}?", - "album_name": "Názov albumu", - "album_options": "Nastavenia albumu", - "album_remove_user": "Odstrániť používateľa?", - "album_remove_user_confirmation": "Ste si istý, že chcete odstrániť používateľa {user}?", - "album_search_not_found": "Neboli nájdené žiadne albumy zodpovedajúce vášmu hľadaniu", - "album_selected": "Vybraný album", - "album_share_no_users": "Vyzerá to, že ste tento album zdieľali so všetkými používateľmi alebo nemáte žiadneho používateľa, s ktorým by ste ho mohli zdieľať.", - "album_summary": "Súhrn albumu", - "album_updated": "Album bol aktualizovaný", - "album_updated_setting_description": "Obdržať e-mailové upozornenie, keď v zdieľanom albume pribudnú nové položky", - "album_upload_assets": "Nahrajte súbory zo svojho počítača a pridajte ich do albumu", - "album_user_left": "Opustil {album}", - "album_user_removed": "Odstránený {user}", - "album_viewer_appbar_delete_confirm": "Ste si istý že chcete vymazať tento album z vášho účtu?", - "album_viewer_appbar_share_err_delete": "Nepodarilo sa odstrániť album", - "album_viewer_appbar_share_err_leave": "Nepodarilo sa ukončiť album", - "album_viewer_appbar_share_err_remove": "Pri odstraňovaní súborov z albumu sa vyskytli problémy", - "album_viewer_appbar_share_err_title": "Nepodarilo sa zmeniť názov albumu", - "album_viewer_appbar_share_leave": "Opustiť album", - "album_viewer_appbar_share_to": "Zdieľať s", - "album_viewer_page_share_add_users": "Pridať používateľov", - "album_with_link_access": "Umožnite komukoľvek s odkazom pozrieť si fotky a ľudí v tomto albume.", - "albums": "Albumy", - "albums_count": "{count, plural, one {{count, number} album} few {{count, number} albumy} other {{count, number} albumov}}", - "albums_default_sort_order": "Predvolené poradie albumov", - "albums_default_sort_order_description": "Počiatočné poradie triedenia položiek pri vytváraní nových albumov.", - "albums_feature_description": "Zbierky médií, ktoré možno zdieľať s ostatnými používateľmi.", - "albums_on_device_count": "Albumy v zariadení ({count})", - "albums_selected": "{count, plural, one {# vybraný album} few {# vybrané albumy} other {# vybraných albumov}}", - "all": "Všetko", - "all_albums": "Všetky albumy", - "all_people": "Všetci ľudia", - "all_photos": "Všetky fotky", - "all_videos": "Všetky videa", - "allow_dark_mode": "Povoliť tmavý režim", - "allow_edits": "Povoliť úpravy", - "allow_public_user_to_download": "Povoliť verejnému používateľovi stiahnutie", - "allow_public_user_to_upload": "Umožniť verejnému používateľovi nahrať", - "allowed": "Povolené", - "alt_text_qr_code": "Obrázok QR kódu", - "always_keep": "Vždy ponechať", - "always_keep_photos_hint": "Funkcia Uvoľniť miesto ponechá všetky fotografie v tomto zariadení.", - "always_keep_videos_hint": "Funkcia Uvoľniť miesto ponechá všetky videá v tomto zariadení.", - "anti_clockwise": "Proti smeru hodinových ručičiek", - "api_key": "API Klúč", - "api_key_description": "Táto hodnota sa zobrazí iba raz. Pred zatvorením okna ju určite skopírujte.", - "api_key_empty": "Názov vášho API kĺuča by nemal byť prázdny", - "api_keys": "API Kľúče", - "app_architecture_variant": "Variant (Architektúra)", - "app_bar_signout_dialog_content": "Skutočne sa chcete odhlásiť?", - "app_bar_signout_dialog_ok": "Áno", - "app_bar_signout_dialog_title": "Odhlásiť sa", - "app_download_links": "Odkazy na stiahnutie aplikácie", - "app_settings": "Nastavenia aplikácie", - "app_stores": "Obchody s aplikáciami", - "app_update_available": "Aktualizácia aplikácie je k dispozícii", - "appears_in": "Vyskytuje sa v", - "apply_count": "Použiť ({count, number})", - "archive": "Archív", - "archive_action_prompt": "{count} pridaných do archívu", - "archive_or_unarchive_photo": "Archivácia alebo odarchivovanie fotografie", - "archive_page_no_archived_assets": "Žiadne archivované médiá", - "archive_page_title": "Archív ({count})", - "archive_size": "Veľkosť archívu", - "archive_size_description": "Nastaviť veľkosť archívu na stiahnutie (v GiB)", - "archived": "Archivované", - "archived_count": "{count, plural, one {Archivovaný #} few {Archivované #} other {Archivovaných #}}", - "are_these_the_same_person": "Ide o tú istú osobu?", - "are_you_sure_to_do_this": "Ste si istý, že to chcete urobiť?", - "array_field_not_fully_supported": "Polia vyžadujú ručné úpravy JSON", - "asset_action_delete_err_read_only": "Nemožno vymazať položku len na čítanie, preskakujem", - "asset_action_share_err_offline": "Nemožno načítať offline položku, preskakujem", - "asset_added_to_album": "Pridané do albumu", - "asset_adding_to_album": "Pridáva sa do albumu…", - "asset_created": "Položka bola vytvorená", - "asset_description_updated": "Popis média bol aktualizovaný", - "asset_filename_is_offline": "Médium {filename} je offline", - "asset_has_unassigned_faces": "Položka má nepriradené tváre", - "asset_hashing": "Hašovanie…", - "asset_list_group_by_sub_title": "Zoskupiť podľa", - "asset_list_layout_settings_dynamic_layout_title": "Dynamické rozloženie", - "asset_list_layout_settings_group_automatically": "Automaticky", - "asset_list_layout_settings_group_by": "Zoskupiť položky podľa", - "asset_list_layout_settings_group_by_month_day": "Mesiac + deň", - "asset_list_layout_sub_title": "Rozvrhnutie", - "asset_list_settings_subtitle": "Nastavenia rozloženia mriežky fotografií", - "asset_list_settings_title": "Mriežka fotografií", - "asset_not_found_on_device_android": "Položka nebola nájdená v zariadení", - "asset_not_found_on_device_ios": "Položka nebola nájdená v zariadení. Ak používate iCloud, položka môže byť nedostupná kvôli poškodenému súboru uloženému v iCloude", - "asset_not_found_on_icloud": "Položka nebola nájdená v iCloude. Položka môže byť nedostupná kvôli poškodenému súboru uloženému v iCloude", - "asset_offline": "Médium je offline", - "asset_offline_description": "Tento externá položka sa už nenachádza na disku. Pre pomoc sa prosím obráťte na správcu systému Immich.", - "asset_restored_successfully": "Položky boli úspešne obnovené", - "asset_skipped": "Preskočené", - "asset_skipped_in_trash": "V koši", - "asset_trashed": "Položka bola vyhodená", - "asset_troubleshoot": "Riešenie problémov s položkami", - "asset_uploaded": "Nahrané", - "asset_uploading": "Nahráva sa…", - "asset_viewer_settings_subtitle": "Spravujte nastavenia prehliadača galérie", - "asset_viewer_settings_title": "Prehliadač médií", - "assets": "Položky", - "assets_added_count": "{count, plural, one {Pridaná # položka} few {Pridané # položky} other {Pridaných # položek}}", - "assets_added_to_album_count": "Do albumu {count, plural, one {bola pridaná # položka} few {boli pridané # položky} other {bolo pridaných # položiek}}", - "assets_added_to_albums_count": "{assetTotal, plural, one {Pridaná # položka} few {Pridané # položky} other {Pridaných # položiek}} do {albumTotal, plural, one {# albumu} other {# albumov}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {položku} other {položiek}} nie je možné pridať do albumu", - "assets_cannot_be_added_to_albums": "{count, plural, one {položka} few {položky} other {položiek}} nie je možné pridať do žiadneho albumu", - "assets_count": "{count, plural, one {# položka} few {# položky} other {# položiek}}", - "assets_deleted_permanently": "{count} položka(iek) natrvalo vymazaná(ých)", - "assets_deleted_permanently_from_server": "{count} položka(iek) natrvalo vymazaná(ých) zo servera Immich", - "assets_downloaded_failed": "{count, plural, one {Stiahnutý # súbor - {error} súbor zlyhal} few {Stiahnuté # súbory - {error} súbory zlyhali} other {Stiahnutých # súborov - {error} súborov zlyhalo}}", - "assets_downloaded_successfully": "{count, plural, one {# súbor bol úspešne stiahnutý} few {# súbory boli úspešne stiahnuté} other {# súborov bolo úspešne stiahnutých}}", - "assets_moved_to_trash_count": "Do koša {count, plural, one {bola presunutá # položka} few {boli presunuté # položky} other {bolo presunutých # položiek}}", - "assets_permanently_deleted_count": "Trvalo {count, plural, one {vymazaná # položka} few {vymazané # položky} other {vymazaných # položiek}}", - "assets_removed_count": "{count, plural, one {Odstránená # položka} few {Odstránené # položky} other {Odstránených # položiek}}", - "assets_removed_permanently_from_device": "{count} položka(iek) natrvalo vymazaná(ých) z vášho zariadenia", - "assets_restore_confirmation": "Naozaj chcete obnoviť všetky vyhodené položky? Túto akciu nie je možné vrátiť späť! Upozorňujeme, že týmto spôsobom nie je možné obnoviť žiadne offline položky.", - "assets_restored_count": "{count, plural, one {Obnovená # položka} few {Obnovené # položky} other {Obnovených # položiek}}", - "assets_restored_successfully": "{count} médií úspešne obnovených", - "assets_trashed": "{count} položka(iek) vyhodená(ých) do koša", - "assets_trashed_count": "{count, plural, one {Odstránená # položka} few {Odstránené # položky} other {Odstránených # položiek}}", - "assets_trashed_from_server": "{count} položka(iek) vyhodená(ých) do koša zo servera Immich", - "assets_were_part_of_album_count": "{count, plural, one {Položka už bola} other {Položky už boli}} súčasťou albumu", - "assets_were_part_of_albums_count": "{count, plural, one {položka} few {položky} other {položiek}} sú už súčasťou albumov", - "authorized_devices": "Autorizované zariadenia", - "automatic_endpoint_switching_subtitle": "Pripojiť sa lokálne prostredníctvom určeného pripojenia Wi-Fi, ak je k dispozícii, a používať alternatívne pripojenia inde", - "automatic_endpoint_switching_title": "Automatické prepínanie URL adresy", - "autoplay_slideshow": "Automatické prehrávanie prezentácie", - "back": "Späť", - "back_close_deselect": "Späť, zavrieť alebo zrušiť výber", - "background_backup_running_error": "V súčasnosti prebieha zálohovanie na pozadí, nie je možné spustiť ručné zálohovanie", - "background_location_permission": "Povolenie na určenie polohy na pozadí", - "background_location_permission_content": "Aby bolo možné prepínať siete pri spustení na pozadí, musí mať aplikácia Immich *vždy* presný prístup k polohe, aby mohla prečítať názov siete Wi-Fi", - "background_options": "Možnosti pozadia", - "backup": "Zálohovanie", - "backup_album_selection_page_albums_device": "Albumy v zariadení ({count})", - "backup_album_selection_page_albums_tap": "Ťuknutím na položku ju zahrniete, dvojitým ťuknutím ju vylúčite", - "backup_album_selection_page_assets_scatter": "Súbory môžu byť roztrúsené vo viacerých albumoch. To umožňuje zahrnúť alebo vylúčiť albumy počas procesu zálohovania.", - "backup_album_selection_page_select_albums": "Vybrať albumy", - "backup_album_selection_page_selection_info": "Informácie o výbere", - "backup_album_selection_page_total_assets": "Celkový počet jedinečných súborov", - "backup_albums_sync": "Synchronizácia zálohovaných albumov", - "backup_all": "Všetko", - "backup_background_service_backup_failed_message": "Zálohovanie médií zlyhalo. Skúšam to znova…", - "backup_background_service_complete_notification": "Zálohovanie položiek dokončené", - "backup_background_service_connection_failed_message": "Nepodarilo sa pripojiť k serveru. Skúšam to znova…", - "backup_background_service_current_upload_notification": "Nahrávanie {filename}", - "backup_background_service_default_notification": "Kontrola nových zdrojov…", - "backup_background_service_error_title": "Chyba zálohovania", - "backup_background_service_in_progress_notification": "Vytváram kópiu vašich médií…", - "backup_background_service_upload_failure_notification": "Nepodarilo sa nahrať {filename}", - "backup_controller_page_albums": "Zálohované albumy", - "backup_controller_page_background_app_refresh_disabled_content": "Ak chcete používať zálohovanie na pozadí, povoľte obnovovanie aplikácií na pozadí v ponuke Nastavenia > Všeobecné > Obnovovanie aplikácií na pozadí.", - "backup_controller_page_background_app_refresh_disabled_title": "Obnovovanie aplikácií na pozadí je vypnuté", - "backup_controller_page_background_app_refresh_enable_button_text": "Prejsť do nastavení", - "backup_controller_page_background_battery_info_link": "Ukáž mi ako", - "backup_controller_page_background_battery_info_message": "Ak chcete dosiahnuť najlepšie výsledky pri zálohovaní na pozadí, vypnite všetky optimalizácie batérie, ktoré obmedzujú aktivitu na pozadí pre Immich. \n\nKeďže to závisí od zariadenia, skontrolujte požadované informácie pre výrobcu vášho zariadenia.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Optimalizácia batérie", - "backup_controller_page_background_charging": "Len počas nabíjania", - "backup_controller_page_background_configure_error": "Nepodarilo sa nakonfigurovať službu na pozadí", - "backup_controller_page_background_delay": "Oneskorenie zálohovania nových médií: {duration}", - "backup_controller_page_background_description": "Povoľte službu na pozadí na automatické zálohovanie všetkých nových položiek bez nutnosti otvorenia aplikácie", - "backup_controller_page_background_is_off": "Automatické zálohovanie na pozadí je vypnuté", - "backup_controller_page_background_is_on": "Automatické zálohovanie na pozadí je zapnuté", - "backup_controller_page_background_turn_off": "Vypnúť zálohovanie na pozadí", - "backup_controller_page_background_turn_on": "Povoliť zálohovanie na pozadí", - "backup_controller_page_background_wifi": "Len cez WiFi", - "backup_controller_page_backup": "Zálohovanie", - "backup_controller_page_backup_selected": "Vybrané: ", - "backup_controller_page_backup_sub": "Zálohované fotografie a videa", - "backup_controller_page_created": "Vytvorené: {date}", - "backup_controller_page_desc_backup": "Zapnite zálohovanie na popredí, aby sa nové položky automaticky nahrávali na server pri otvorení aplikácie.", - "backup_controller_page_excluded": "Vylúčené: ", - "backup_controller_page_failed": "Nepodarilo sa ({count})", - "backup_controller_page_filename": "Názov súboru: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informácie o zálohovaní", - "backup_controller_page_none_selected": "Žiadne vybrané", - "backup_controller_page_remainder": "Zostáva", - "backup_controller_page_remainder_sub": "Zostávajúce fotografie a videá, ktoré sa majú zálohovať z výberu", - "backup_controller_page_server_storage": "Serverové úložisko", - "backup_controller_page_start_backup": "Spustiť zálohovanie", - "backup_controller_page_status_off": "Automatické zálohovanie na popredí je vypnuté", - "backup_controller_page_status_on": "Automatické zálohovanie na popredí je zapnuté", - "backup_controller_page_storage_format": "{used} z {total} použitých", - "backup_controller_page_to_backup": "Albumy ktoré budú zálohované", - "backup_controller_page_total_sub": "Všetky jedinečné fotografie a videá z vybraných albumov", - "backup_controller_page_turn_off": "Vypnúť zálohovanie na popredí", - "backup_controller_page_turn_on": "Povoliť zálohovanie na popredí", - "backup_controller_page_uploading_file_info": "Nahrávaný súbor", - "backup_err_only_album": "Nie je možné odstrániť jediný vybraný album", - "backup_error_sync_failed": "Synchronizácia sa nepodarila. Nie je možné spracovať zálohu.", - "backup_info_card_assets": "položiek", - "backup_manual_cancelled": "Zrušené", - "backup_manual_in_progress": "Nahrávanie už prebieha. Vyskúšajte neskôr", - "backup_manual_success": "Hotovo", - "backup_manual_title": "Stav nahrávania", - "backup_options": "Možnosti zálohovania", - "backup_options_page_title": "Možnosti zálohovania", - "backup_setting_subtitle": "Spravovať nastavenia odosielania na pozadí a v popredí", - "backup_settings_subtitle": "Spravovať nastavenia nahrávania", - "backup_upload_details_page_more_details": "Ťukni pre viac info", - "backward": "Dozadu", - "biometric_auth_enabled": "Biometrické overovanie je povolené", - "biometric_locked_out": "Ste vymknutí z biometrického overovania", - "biometric_no_options": "Nie sú k dispozícii žiadne biometrické možnosti", - "biometric_not_available": "Biometrické overenie nie je v tomto zariadení k dispozícii", - "birthdate_saved": "Dátum narodenia bol úspešne uložený", - "birthdate_set_description": "Dátum narodenia sa používa na výpočet veku tejto osoby v čase fotografie.", - "blurred_background": "Rozmazané pozadie", - "bugs_and_feature_requests": "Chyby a požiadavky na funkcie", - "build": "Zostava", - "build_image": "Obraz zostavy", - "bulk_delete_duplicates_confirmation": "Naozaj chcete hromadne odstrániť {count, plural, one {# duplikátnu položku} few {# duplikátne položky} other {# duplikátnych položiek}}? Týmto sa zachová najväčšia položka z každej skupiny a všetky ostatné duplikáty sa natrvalo odstránia. Túto akciu nie je možné vrátiť späť!", - "bulk_keep_duplicates_confirmation": "Naozaj chcete ponechať {count, plural, one {# duplicitnú položku} few {# duplicitné položky} other {# duplicitných položiek}}? Týmto sa vyriešia všetky duplicitné skupiny bez toho, aby sa čokoľvek odstránilo.", - "bulk_trash_duplicates_confirmation": "Naozaj chcete hromadne vymazať {count, plural, one {# duplicitnú položku} few {# duplicitné položky} other {# duplicitných položiek}}? Týmto sa zachová najväčšia položka z každej skupiny a všetky ostatné duplicitné položky sa vyhodia.", - "buy": "Kúpiť Immich", - "cache_settings_clear_cache_button": "Vymazať vyrovnávaciu pamäť", - "cache_settings_clear_cache_button_title": "Vymaže vyrovnávaciu pamäť aplikácie. To výrazne ovplyvní výkon aplikácie, kým sa vyrovnávacia pamäť neobnoví.", - "cache_settings_duplicated_assets_clear_button": "VYČISTIŤ", - "cache_settings_duplicated_assets_subtitle": "Fotografie a videá, ktoré aplikácia ignoruje podľa zoznamu", - "cache_settings_duplicated_assets_title": "Duplicitné položky ({count})", - "cache_settings_statistics_album": "Knižnica náhľadov", - "cache_settings_statistics_full": "Kompletné fotografie", - "cache_settings_statistics_shared": "Zdieľané náhľady albumov", - "cache_settings_statistics_thumbnail": "Náhľady", - "cache_settings_statistics_title": "Použitie vyrovnávacej pamäte", - "cache_settings_subtitle": "Ovládanie správania mobilnej aplikácie Immich v medzipamäti", - "cache_settings_tile_subtitle": "Ovládanie správania lokálneho úložiska", - "cache_settings_tile_title": "Lokálne úložisko", - "cache_settings_title": "Nastavenia vyrovnávacej pamäte", - "camera": "Fotoaparát", - "camera_brand": "Značka fotoaparátu", - "camera_model": "Model fotoaparátu", - "cancel": "Zrušiť", - "cancel_search": "Zrušiť vyhľadávanie", - "canceled": "Zrušené", - "canceling": "Ruší sa", - "cannot_merge_people": "Nie je možné zlúčiť ľudí", - "cannot_undo_this_action": "Túto akciu nemôžete vrátiť späť!", - "cannot_update_the_description": "Popis nie je možné aktualizovať", - "cast": "Prenos (cast)", - "cast_description": "Nastavte dostupné ciele prenosu", - "change_date": "Upraviť dátum", - "change_description": "Zmeniť popis", - "change_display_order": "Zmeniť poradie zobrazenia", - "change_expiration_time": "Zmeniť čas vypršania", - "change_location": "Zmeniť polohu", - "change_name": "Upraviť meno", - "change_name_successfully": "Meno bolo zmenené", - "change_password": "Zmeniť Heslo", - "change_password_description": "Buď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Nižšie zadajte nové heslo.", - "change_password_form_confirm_password": "Potvrďte heslo", - "change_password_form_description": "Dobrý deň, {name},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.", - "change_password_form_log_out": "Odhlásiť všetky ostatné zariadenia", - "change_password_form_log_out_description": "Odporúča sa odhlásiť sa zo všetkých ostatných zariadení", - "change_password_form_new_password": "Nové heslo", - "change_password_form_password_mismatch": "Heslá sa nezhodujú", - "change_password_form_reenter_new_password": "Znova zadajte nové heslo", - "change_pin_code": "Zmeniť PIN kód", - "change_trigger": "Zmeniť spúštač", - "change_trigger_prompt": "Naozaj chcete zmeniť spúšťač? Týmto krokom sa odstránia všetky existujúce akcie a filtre.", - "change_your_password": "Zmeniť heslo", - "changed_visibility_successfully": "Viditeľnosť bola úspešne zmenená", - "charging": "Nabíja sa", - "charging_requirement_mobile_backup": "Zálohovanie na pozadí vyžaduje, aby bolo zariadenie nabíjané", - "check_corrupt_asset_backup": "Skontrolovať, či nie sú poškodené zálohy položiek", - "check_corrupt_asset_backup_button": "Vykonať kontrolu", - "check_corrupt_asset_backup_description": "Spustiť túto kontrolu len cez Wi-Fi a po zálohovaní všetkých položiek. Tento postup môže trvať niekoľko minút.", - "check_logs": "Skontrolovať logy", - "checksum": "Kontrolný súčet", - "choose_matching_people_to_merge": "Vyberte rovnakých ľudí na zlúčenie", - "city": "Mesto", - "cleanup_confirm_description": "Immich našiel {count} položiek (vytvorených pred {date}) bezpečne zálohovaných na serveri. Odstrániť lokálne kópie z tohto zariadenia?", - "cleanup_confirm_prompt_title": "Odstrániť z tohto zariadenia?", - "cleanup_deleted_assets": "Presunutých {count} položiek do koša na zariadení", - "cleanup_deleting": "Presúvanie do koša...", - "cleanup_found_assets": "Našlo sa {count} zálohovaných položiek", - "cleanup_found_assets_with_size": "Nájdených {count} zálohovaných položiek ({size})", - "cleanup_icloud_shared_albums_excluded": "Zdieľané albumy iCloud sú vylúčené zo skenovania", - "cleanup_no_assets_found": "Nenašli sa žiadne položky zodpovedajúce vyššie uvedeným kritériám. Funkcia Uvoľniť miesto môže odstrániť len tie položky, ktoré boli zálohované na server", - "cleanup_preview_title": "Položiek na odstránenie ({count})", - "cleanup_step3_description": "Vyhľadať zálohované súbory zodpovedajúce vašim nastaveniam dátumu a ponechania.", - "cleanup_step4_summary": "{count} položiek (vytvorených pred {date}) na odstránenie z vášho lokálneho zariadenia. Fotografie zostanú dostupné v aplikácii Immich.", - "cleanup_trash_hint": "Ak chcete úplne uvoľniť úložný priestor, otvorte aplikáciu systémovej galérie a vyprázdnite koš", - "clear": "Vyčistiť", - "clear_all": "Vyčistiť všetko", - "clear_all_recent_searches": "Vyčistiť nedávne vyhľadávania", - "clear_file_cache": "Vyčistiť vyrovnávaciu pamäť súborov", - "clear_message": "Vymazať správu", - "clear_value": "Vymazať hodnotu", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Zadať heslo", - "client_cert_import": "Importovať", - "client_cert_import_success_msg": "Certifikát klienta je naimportovaný", - "client_cert_invalid_msg": "Neplatný súbor certifikátu alebo nesprávne heslo", - "client_cert_password_message": "Zadajte heslo pre tento certifikát", - "client_cert_password_title": "Heslo certifikátu", - "client_cert_remove_msg": "Certifikát klienta je odstránený", - "client_cert_subtitle": "Podporuje iba formát PKCS12 (.p12, .pfx). Importovanie/odstránenie certifikátu je k dispozícii len pred prihlásením", - "client_cert_title": "SSL certifikát klienta [EXPERIMENTÁLNE]", - "clockwise": "V smere hodinových ručičiek", - "close": "Zatvoriť", - "collapse": "Zbaliť", - "collapse_all": "Zbaliť všetko", - "color": "Farba", - "color_theme": "Farba témy", - "command": "Príkaz", - "comment_deleted": "Komentár bol odstránený", - "comment_options": "Možnosti komentára", - "comments_and_likes": "Komentáre a páči sa mi to", - "comments_are_disabled": "Komentáre sú vypnuté", - "common_create_new_album": "Vytvoriť nový album", - "completed": "Dokončené", - "confirm": "Potvrdiť", - "confirm_admin_password": "Potvrdiť heslo správcu", - "confirm_delete_face": "Naozaj chcete z položky odstrániť tvár osoby {name}?", - "confirm_delete_shared_link": "Ste si istý, že chcete odstrániť tento zdieľaný odkaz?", - "confirm_keep_this_delete_others": "Všetky ostatné položky v zoskupení budú odstránené okrem tejto položky. Naozaj chcete pokračovať?", - "confirm_new_pin_code": "Potvrdiť nový PIN kód", - "confirm_password": "Potvrdiť heslo", - "confirm_tag_face": "Chcete označiť túto tvár ako {name}?", - "confirm_tag_face_unnamed": "Chcete označiť túto tvár?", - "connected_device": "Pripojené zariadenie", - "connected_to": "Pripojené k", - "contain": "Prispôsobiť", - "context": "Kontext", - "continue": "Pokračovať", - "control_bottom_app_bar_create_new_album": "Vytvoriť nový album", - "control_bottom_app_bar_delete_from_immich": "Vymazať z aplikácie Immich", - "control_bottom_app_bar_delete_from_local": "Vymazať zo zariadenia", - "control_bottom_app_bar_edit_location": "Upraviť polohu", - "control_bottom_app_bar_edit_time": "Upraviť dátum a čas", - "control_bottom_app_bar_share_link": "Zdieľať odkaz", - "control_bottom_app_bar_share_to": "Zdieľať cez", - "control_bottom_app_bar_trash_from_immich": "Presunúť do koša", - "copied_image_to_clipboard": "Obrázok skopírovaný do schránky.", - "copied_to_clipboard": "Skopírované do schránky!", - "copy_error": "Chyba pri kopírovaní", - "copy_file_path": "Kopírovať cestu odkazu", - "copy_image": "Skopírovať obrázok", - "copy_link": "Skopírovať odkaz", - "copy_link_to_clipboard": "Skopírovať do schránky", - "copy_password": "Skopírovať heslo", - "copy_to_clipboard": "Skopírovať do schránky", - "country": "Krajina", - "cover": "Vyplniť", - "covers": "Dlaždice", - "create": "Vytvoriť", - "create_album": "Vytvoriť album", - "create_album_page_untitled": "Bez názvu", - "create_api_key": "Vytvoriť API kľúč", - "create_first_workflow": "Vytvorte prvý pracovný postup", - "create_library": "Vytvoriť knižnicu", - "create_link": "Vytvoriť odkaz", - "create_link_to_share": "Vytvoriť odkaz na zdieľanie", - "create_link_to_share_description": "Umožniť každému, kto má odkaz, zobraziť vybrané fotografie", - "create_new": "VYTVORIŤ NOVÉ", - "create_new_person": "Vytvoriť novú osobu", - "create_new_person_hint": "Priradiť vybrané položky novej osobe", - "create_new_user": "Vytvorenie nového používateľa", - "create_shared_album_page_share_add_assets": "PRIDAŤ POLOŽKY", - "create_shared_album_page_share_select_photos": "Vybrať fotografie", - "create_shared_link": "Vytvoriť zdieľaný odkaz", - "create_tag": "Vytvoriť štítok", - "create_tag_description": "Vytvorte nový štítok. V prípade vnorených štítkov zadajte celú cestu k štítku vrátane lomiek.", - "create_user": "Vytvoriť používateľa", - "create_workflow": "Vytvoriť pracovný postup", - "created": "Vytvorené", - "created_at": "Vytvorené", - "creating_linked_albums": "Vytváranie prepojených albumov...", - "crop": "Orezať", - "crop_aspect_ratio_fixed": "Pevný pomer", - "crop_aspect_ratio_free": "Voľný", - "crop_aspect_ratio_original": "Originálny", - "curated_object_page_title": "Veci", - "current_device": "Súčasné zariadenie", - "current_pin_code": "Aktuálny PIN kód", - "current_server_address": "Aktuálna adresa servera", - "custom_date": "Vlastný dátum", - "custom_locale": "Vlastné nastavenie jazyka", - "custom_locale_description": "Formátovanie dátumov a čísel podľa jazyka a regiónu", - "custom_url": "Vlastná URL adresa", - "cutoff_date_description": "Ponechať fotografie z posledného obdobia…", - "cutoff_day": "{count, plural, one {deň} few {dni} other {dní}}", - "cutoff_year": "{count, plural, one {rok} few {roky} other {rokov}}", - "daily_title_text_date": "EEEE, d. MMMM", - "daily_title_text_date_year": "EEEE, d. MMMM y", - "dark": "Tmavá", - "dark_theme": "Prepnúť tmavú tému", - "date": "Dátum", - "date_after": "Dátum po", - "date_and_time": "Dátum a Čas", - "date_before": "Dátum pred", - "date_format": "EEEE, d. MMMM y • H:mm", - "date_of_birth_saved": "Dátum narodenia úspešne uložený", - "date_range": "Rozsah dátumu", - "day": "Deň", - "days": "Dní", - "deduplicate_all": "Deduplikovať všetko", - "deduplication_criteria_1": "Veľkosť obrázku v bajtoch", - "deduplication_criteria_2": "Počet EXIF údajov", - "deduplication_info": "Info o deduplikácii", - "deduplication_info_description": "Na automatický predvýber položiek a hromadné odstránenie duplicít, sa pozeráme do:", - "default_locale": "Predvolené miestne nastavenie", - "default_locale_description": "Formátovanie dátumov a čísel na základe miestneho nastavenia prehliadača", - "delete": "Vymazať", - "delete_action_confirmation_message": "Naozaj chcete túto položku odstrániť? Táto akcia presunie položku do koša na serveri a zobrazí sa otázka, či ju chcete odstrániť aj lokálne", - "delete_action_prompt": "{count} vymazaných", - "delete_album": "Odstrániť album", - "delete_api_key_prompt": "Naozaj chcete odstrániť tento API kľúč?", - "delete_dialog_alert": "Tieto položky budú natrvalo odstránené z aplikácie Immich a z vášho zariadenia", - "delete_dialog_alert_local": "Tieto položky budú permanentne vymazané z vašeho zariadenia, ale budú stále k dispozícií na serveri Immich", - "delete_dialog_alert_local_non_backed_up": "Niektoré položky nie sú zálohované na Immich a budú permanentne odstránené z vášho zariadenia", - "delete_dialog_alert_remote": "Tieto položky budú permanentne vymazané zo serveru Immich", - "delete_dialog_ok_force": "Napriek tomu vymazať", - "delete_dialog_title": "Vymazať natrvalo", - "delete_duplicates_confirmation": "Naozaj chcete nenávratne odstrániť tieto duplikáty?", - "delete_face": "Odstrániť tvár", - "delete_key": "Odstrániť kľúč", - "delete_library": "Vymazať knižnicu", - "delete_link": "Odstrániť odkaz", - "delete_local_action_prompt": "{count} vymazané lokálne", - "delete_local_dialog_ok_backed_up_only": "Vymazať len zálohované", - "delete_local_dialog_ok_force": "Napriek tomu vymazať", - "delete_others": "Vymazať ostatné", - "delete_permanently": "Natrvalo odstrániť", - "delete_permanently_action_prompt": "{count} natrvalo odstránených", - "delete_shared_link": "Odstrániť zdieľaný odkaz", - "delete_shared_link_dialog_title": "Odstrániť zdieľaný odkaz", - "delete_tag": "Odstrániť štítok", - "delete_tag_confirmation_prompt": "Naozaj chcete odstrániť štítok menom {tagName}?", - "delete_user": "Vymazať používateľa", - "deleted_shared_link": "Vymazaný zdieľaný odkaz", - "deletes_missing_assets": "Odstráni položky chýbajúce na disku", - "description": "Popis", - "description_input_hint_text": "Pridať popis...", - "description_input_submit_error": "Chyba pri aktualizovaní popisu, zobrazte log pre viac detailov", - "deselect_all": "Zrušiť výber všetkých", - "details": "Podrobnosti", - "direction": "Smer", - "disable": "Vypnúť", - "disabled": "Vypnuté", - "disallow_edits": "Zakázať úpravy", - "discord": "Discord", - "discover": "Objaviť", - "discovered_devices": "Objavené zariadenia", - "dismiss_all_errors": "Zamietnuť všetky chyby", - "dismiss_error": "Zamietnuť chybu", - "display_options": "Možnosti zobrazenia", - "display_order": "Poradie zobrazenia", - "display_original_photos": "Zobraziť pôvodné fotky", - "display_original_photos_setting_description": "Uprednostniť zobrazenie pôvodnej fotky, pri prezeraní položky, namiesto miniatúry, ak je pôvodná položka kompatibilná s webom. To môže mať za následok nižšiu rýchlosť zobrazenia fotografií.", - "do_not_show_again": "Túto správu znova nezobrazovať", - "documentation": "Dokumentácia", - "done": "Hotovo", - "download": "Stiahnuť", - "download_action_prompt": "Sťahuje sa {count} položiek", - "download_canceled": "Stiahnutie zrušené", - "download_complete": "Stiahnutie dokončené", - "download_enqueue": "Stiahnutie v poradí", - "download_error": "Chyba sťahovania", - "download_failed": "Stiahnutie sa nepodarilo", - "download_finished": "Stiahnutie dokončené", - "download_include_embedded_motion_videos": "Vložené videá", - "download_include_embedded_motion_videos_description": "Zahrnúť videá vložené do pohyblivých fotiek ako samostatné súbory", - "download_notfound": "Stiahnutie nebolo nájdené", - "download_original": "Stiahnuť originál", - "download_paused": "Stiahnutie pozastavené", - "download_settings": "Stiahnuť", - "download_settings_description": "Spravovať nastavenia súvisiace so sťahovaním položiek", - "download_started": "Sťahovanie spustené", - "download_sucess": "Stiahnutie úspešné", - "download_sucess_android": "Médiá boli stiahnuté do DCIM/Immich", - "download_waiting_to_retry": "Čaká sa na opakovanie pokusu", - "downloading": "Sťahuje sa", - "downloading_asset_filename": "Sťahuje sa položka {filename}", - "downloading_from_icloud": "Sťahuje sa z iCloud", - "downloading_media": "Sťahovanie médií", - "drop_files_to_upload": "Umiestnite súbory kamkoľvek na nahratie", - "duplicates": "Duplikáty", - "duplicates_description": "Vysporiadať sa s každou skupinou tak, že sa duplicitné označia ako duplicitné", - "duration": "Trvanie", - "edit": "Upraviť", - "edit_album": "Upraviť album", - "edit_avatar": "Upraviť profilový obrázok", - "edit_birthday": "Upraviť narodeniny", - "edit_date": "Upraviť dátum", - "edit_date_and_time": "Upraviť dátum a čas", - "edit_date_and_time_action_prompt": "{count} dátum a čas upravený", - "edit_date_and_time_by_offset": "Zmeniť dátum podľa posunu", - "edit_date_and_time_by_offset_interval": "Nový rozsah dátumov: {from} - {to}", - "edit_description": "Upraviť popis", - "edit_description_prompt": "Vyberte prosím nový popis:", - "edit_exclusion_pattern": "Upraviť vzor vylúčenia", - "edit_faces": "Upraviť tváre", - "edit_key": "Upraviť kľúč", - "edit_link": "Upraviť odkaz", - "edit_location": "Upraviť polohu", - "edit_location_action_prompt": "{count} poloha upravená", - "edit_location_dialog_title": "Poloha", - "edit_name": "Upraviť meno", - "edit_people": "Upraviť osoby", - "edit_tag": "Upraviť štítok", - "edit_title": "Upraviť názov", - "edit_user": "Upraviť používateľa", - "edit_workflow": "Upraviť pracovný postup", - "editor": "Editor", - "editor_close_without_save_prompt": "Úpravy nebudú uložené", - "editor_close_without_save_title": "Zavrieť editor?", - "editor_confirm_reset_all_changes": "Naozaj chcete zrušiť všetky zmeny?", - "editor_flip_horizontal": "Prevrátiť horizontálne", - "editor_flip_vertical": "Prevrátiť vertikálne", - "editor_orientation": "Orientácia", - "editor_reset_all_changes": "Zrušiť zmeny", - "editor_rotate_left": "Otočiť o 90° doľava", - "editor_rotate_right": "Otočiť o 90° doprava", - "email": "E-mail", - "email_notifications": "E-mailové oznámenia", - "empty_folder": "Tento priečinok je prázdny", - "empty_trash": "Vyprázdniť kôš", - "empty_trash_confirmation": "Naozaj chcete vyprázdniť kôš? Nenávratne sa vymažú všetky položky z Immich.\nTáto akcia sa nedá vrátiť!", - "enable": "Aktivovať", - "enable_backup": "Povoliť zálohovanie", - "enable_biometric_auth_description": "Zadajte svoj PIN kód, aby ste povolili biometrické overenie", - "enabled": "Aktivovaný", - "end_date": "Koncový dátum", - "enqueued": "V poradí", - "enter_wifi_name": "Zadajte názov Wi-Fi", - "enter_your_pin_code": "Zadajte svoj PIN kód", - "enter_your_pin_code_subtitle": "Zadaním kódu PIN získate prístup k zamknutému priečinku", - "error": "Chyba", - "error_change_sort_album": "Nepodarilo sa zmeniť poradie albumu", - "error_delete_face": "Chyba pri odstraňovaní tváre z položky", - "error_getting_places": "Chyba pri získavaní polôh", - "error_loading_albums": "Chyba pri načítaní albumov", - "error_loading_image": "Nepodarilo sa načítať obrázok", - "error_loading_partners": "Chyba pri načítaní partnerov: {error}", - "error_retrieving_asset_information": "Chyba pri načítaní informácií o položke", - "error_saving_image": "Chyba: {error}", - "error_tag_face_bounding_box": "Chyba pri označovaní tváre - nemožno získať súradnice ohraničujúceho poľa", - "error_title": "Chyba - niečo sa pokazilo", - "error_while_navigating": "Chyba pri prechode na položku", - "errors": { - "cannot_navigate_next_asset": "Nie je možné prejsť na ďalšiu položku", - "cannot_navigate_previous_asset": "Nie je možné prejsť na predošlú položku", - "cant_apply_changes": "Nie je možné použiť zmeny", - "cant_change_activity": "Nie je možné {enabled, select, true {zakázať} other {povoliť}} aktivitu", - "cant_change_asset_favorite": "Nie je možné zmeniť stav obľúbenosti pre položku", - "cant_change_metadata_assets_count": "Nie je možné zmeniť metadáta pre {count, plural, one {# položku} few {# položky} other {# položiek}}", - "cant_get_faces": "Nedokážem získať tváre", - "cant_get_number_of_comments": "Nedokážem získať počet komentárov", - "cant_search_people": "Nedokážem hľadať osoby", - "cant_search_places": "Nedokážem hľadať miesta", - "error_adding_assets_to_album": "Nepodarilo sa pridať položky do albumu", - "error_adding_users_to_album": "Nepodarilo sa pridať používateľov do albumu", - "error_deleting_shared_user": "Chyba pri odstraňovaní zdieľaného používateľa", - "error_downloading": "Nepodarilo sa stiahnuť {filename}", - "error_hiding_buy_button": "Nepodarilo sa skryť tlačidlo kúpiť", - "error_removing_assets_from_album": "Nepodarilo sa odstrániť položku z albumu, podrobnejšie informácie nájdete v konzole", - "error_selecting_all_assets": "Chyba pri výbere všetkých položiek", - "exclusion_pattern_already_exists": "Tento vzor vylúčenia už existuje.", - "failed_to_create_album": "Nepodarilo sa vytvoriť album", - "failed_to_create_shared_link": "Nepodarilo sa vytvoriť zdieľaný odkaz", - "failed_to_edit_shared_link": "Nepodarilo sa editovať zdieľaný odkaz", - "failed_to_get_people": "Nepodarilo sa získať osoby", - "failed_to_keep_this_delete_others": "Nepodarilo sa ponechať túto položku a vymazať tie ostatné položky", - "failed_to_load_asset": "Nepodarilo sa načítať položku", - "failed_to_load_assets": "Nepodarilo sa načítať položky", - "failed_to_load_notifications": "Nepodarilo sa načítať oznámenia", - "failed_to_load_people": "Nepodarilo sa načítať ľudí", - "failed_to_remove_product_key": "Nepodarilo sa odstrániť produktový kľúč", - "failed_to_reset_pin_code": "PIN kód sa nepodarilo obnoviť", - "failed_to_stack_assets": "Nepodarilo sa zoskupiť položky", - "failed_to_unstack_assets": "Nepodarilo sa zrušiť zoskupenie položiek", - "failed_to_update_notification_status": "Nepodarilo sa aktualizovať stav oznámenia", - "incorrect_email_or_password": "Nesprávny e-mail alebo heslo", - "library_folder_already_exists": "Táto cesta importu už existuje.", - "paths_validation_failed": "{paths, plural, one {# cesta zlyhala} few {# cesty zlyhali} other {# ciest zlyhalo}} pri validácii", - "profile_picture_transparent_pixels": "Profilové obrázky nemôžu mať priehľadné pixely. Prosím priblížte a/alebo posuňte obrázok.", - "quota_higher_than_disk_size": "Nastavili ste kvótu vyššiu ako je veľkosť disku", - "something_went_wrong": "Niečo sa pokazilo", - "unable_to_add_album_users": "Nie je možné pridať používateľov do albumu", - "unable_to_add_assets_to_shared_link": "Nie je možné pridať položky k zdieľanému odkazu", - "unable_to_add_comment": "Nie je možné pridať komentár", - "unable_to_add_exclusion_pattern": "Nie je možné pridať vzor vylúčenia", - "unable_to_add_partners": "Nie je možné pridať partnerov", - "unable_to_add_remove_archive": "Nie je možné {archived, select, true {odstrániť položku z} other {pridať položku do}} archívu", - "unable_to_add_remove_favorites": "Nepodarilo sa {favorite, select, true {pridať položku do} other {odstrániť položku z}} obľúbených", - "unable_to_archive_unarchive": "Nepodarilo sa {archived, select, true {archivovať} other {odarchivovať}}", - "unable_to_change_album_user_role": "Nie je možné zmeniť rolu používateľa pre album", - "unable_to_change_date": "Nie je možné zmeniť dátum", - "unable_to_change_description": "Nie je možné zmeniť popis", - "unable_to_change_favorite": "Nie je možné zmeniť obľúbené pre položku", - "unable_to_change_location": "Nie je možné zmeniť polohu", - "unable_to_change_password": "Nie je možné zmeniť heslo", - "unable_to_change_visibility": "Nie je možné zmeniť viditeľnosť pre {count, plural, one {# osobu} few {# osoby} other {# osôb}}", - "unable_to_complete_oauth_login": "Nemožno dokončiť prihlásenie cez OAuth", - "unable_to_connect": "Nie je možné sa pripojiť", - "unable_to_copy_to_clipboard": "Nie je možné kopírovať do schránky, overte si, že stránku navštevujete cez https", - "unable_to_create": "Nie je možné vytvoriť pracovný postup", - "unable_to_create_admin_account": "Nie je možné vytvoriť účet správcu", - "unable_to_create_api_key": "Nie je možné vytvoriť nový API Klúč", - "unable_to_create_library": "Nie je možné vytvoriť knihovňu", - "unable_to_create_user": "Nie je možné vytvoriť používateľa", - "unable_to_delete_album": "Nie je možné vymazať album", - "unable_to_delete_asset": "Nie je možné vymazať položku", - "unable_to_delete_assets": "Chyba pri odstraňovaní položiek", - "unable_to_delete_exclusion_pattern": "Nie je možné vymazať vylučovací vzor", - "unable_to_delete_shared_link": "Nie je možné vymazať zdieľaný odkaz", - "unable_to_delete_user": "Nie je možné vymazať používateľa", - "unable_to_delete_workflow": "Nie je možné odstrániť pracovný postup", - "unable_to_download_files": "Nie je možné stiahnuť súbory", - "unable_to_edit_exclusion_pattern": "Nie je možné upraviť vzorec vylúčenia", - "unable_to_empty_trash": "Nie je možné vyprázdniť kôš", - "unable_to_enter_fullscreen": "Nie je možné prejsť do režimu celej obrazovky", - "unable_to_exit_fullscreen": "Nie je možné opustiť režim celej obrazovky", - "unable_to_get_comments_number": "Nie je možné získať počet komentárov", - "unable_to_get_shared_link": "Nepodarilo sa získať odkaz na zdieľanie", - "unable_to_hide_person": "Nie je možné skryť osobu", - "unable_to_link_motion_video": "Nie je možné prepojiť pohyblivé video", - "unable_to_link_oauth_account": "Nie je možné prepojiť účet OAuth", - "unable_to_log_out_all_devices": "Nie je možné odhlásiť všetky zariadenia", - "unable_to_log_out_device": "Nie je možné odhlásiť zariadenie", - "unable_to_login_with_oauth": "Nie je možné prihlásiť sa cez OAuth", - "unable_to_play_video": "Nie je možné prehrať video", - "unable_to_reassign_assets_existing_person": "Nepodarilo sa priradiť položku k {name, select, null {existujúcej osobe} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nie je možné priradiť položky novej osobe", - "unable_to_refresh_user": "Nie je možné aktualizovať používateľa", - "unable_to_remove_album_users": "Nie je možné odstrániť používateľov z albumu", - "unable_to_remove_api_key": "Nie je možné odstrániť kľúč API", - "unable_to_remove_assets_from_shared_link": "Nie je možné odstrániť položky zo zdieľaného odkazu", - "unable_to_remove_library": "Nie je možné odstrániť knižnicu", - "unable_to_remove_partner": "Nie je možné odstrániť partnera", - "unable_to_remove_reaction": "Nie je možné odstrániť reakciu", - "unable_to_reset_password": "Nie je možné obnoviť heslo", - "unable_to_reset_pin_code": "Nie je možné obnoviť PIN kód", - "unable_to_resolve_duplicate": "Nie je možné vyriešiť duplikát", - "unable_to_restore_assets": "Nie je možné obnoviť položky", - "unable_to_restore_trash": "Nie je možné obnoviť kôš", - "unable_to_restore_user": "Nie je možné obnoviť používateľa", - "unable_to_save_album": "Nie je možné uložiť album", - "unable_to_save_api_key": "Nie je možné uložiť API kľúč", - "unable_to_save_date_of_birth": "Nie je možné uložiť dátum narodenia", - "unable_to_save_name": "Nie je možné uložiť meno", - "unable_to_save_profile": "Nie je možné uložiť profil", - "unable_to_save_settings": "Nie je možné uložiť nastavenia", - "unable_to_scan_libraries": "Nie je možné prehľadať knižnice", - "unable_to_scan_library": "Nie je možné prehľadať knižnicu", - "unable_to_set_feature_photo": "Nie je možné nastaviť profilovú fotku", - "unable_to_set_profile_picture": "Nie je možné nastaviť profilový obrázok", - "unable_to_set_rating": "Nie je možné nastaviť hodnotenie", - "unable_to_submit_job": "Nie je možné odoslať úlohu", - "unable_to_trash_asset": "Nie je možné presunúť položku do koša", - "unable_to_unlink_account": "Nie je možné odpojiť účet", - "unable_to_unlink_motion_video": "Nie je možné zrušiť prepojenie pohyblivého videa", - "unable_to_update_album_cover": "Nie je možné aktualizovať obal albumu", - "unable_to_update_album_info": "Nie je možné aktualizovať informácie o albume", - "unable_to_update_library": "Nie je možné aktualizovať knižnicu", - "unable_to_update_location": "Nie je možné aktualizovať polohu", - "unable_to_update_settings": "Nie je možné aktualizovať nastavenia", - "unable_to_update_timeline_display_status": "Nie je možné aktualizovať stav zobrazenia časovej osi", - "unable_to_update_user": "Nie je možné aktualizovať používateľa", - "unable_to_update_workflow": "Nie je možné aktualizovať pracovný postup", - "unable_to_upload_file": "Nie je možné nahrať súbor" - }, - "errors_text": "Chyby", - "exclusion_pattern": "Vzor vylúčenia", - "exif": "Exif", - "exif_bottom_sheet_description": "Pridať popis...", - "exif_bottom_sheet_description_error": "Chyba pri aktualizácii popisu", - "exif_bottom_sheet_details": "PODROBNOSTI", - "exif_bottom_sheet_location": "POLOHA", - "exif_bottom_sheet_no_description": "Žiadny popis", - "exif_bottom_sheet_people": "ĽUDIA", - "exif_bottom_sheet_person_add_person": "Pridať meno", - "exit_slideshow": "Opustiť prezentáciu", - "expand_all": "Rozbaliť všetko", - "experimental_settings_new_asset_list_subtitle": "Prebiehajúca práca", - "experimental_settings_new_asset_list_title": "Povolenie experimentálnej mriežky fotografií", - "experimental_settings_subtitle": "Používajte na vlastné riziko!", - "experimental_settings_title": "Experimentálne", - "expire_after": "Platnosť vyprší", - "expired": "Vypršalo", - "expires_date": "Expiruje {date}", - "explore": "Preskúmať", - "explorer": "Prieskumník", - "export": "Exportovať", - "export_as_json": "Exportovať do JSON", - "export_database": "Exportovať databázu", - "export_database_description": "Exportovať databázu SQLite", - "extension": "Prípona", - "external": "Externá", - "external_libraries": "Externé knižnice", - "external_network": "Externá sieť", - "external_network_sheet_info": "Ak nie ste v preferovanej sieti Wi-Fi, aplikácia sa pripojí k serveru prostredníctvom prvej z nižšie uvedených adries URL, na ktorú sa dostane, počnúc zhora nadol", - "face_unassigned": "Nepriradená", - "failed": "Neúspešné", - "failed_count": "Zlyhalo: {count}", - "failed_to_authenticate": "Nepodarilo sa overiť", - "failed_to_load_assets": "Nepodarilo sa načítať položky", - "failed_to_load_folder": "Nepodarilo sa načítať priečinok", - "favorite": "Obľúbené", - "favorite_action_prompt": "{count} pridané do obľúbených", - "favorite_or_unfavorite_photo": "Označiť fotku ako obľúbenú alebo neobľúbenú", - "favorites": "Obľúbené", - "favorites_page_no_favorites": "Žiadne obľúbené médiá", - "feature_photo_updated": "Profilová fotka bola aktualizovaná", - "features": "Funkcie", - "features_in_development": "Funkcie vo vývoji", - "features_setting_description": "Spravovať funkcie aplikácie", - "file_name_or_extension": "Názov alebo prípona súboru", - "file_name_text": "Názov súboru", - "file_name_with_value": "Názov súboru: {file_name}", - "file_size": "Veľkosť súboru", - "filename": "Názov súboru", - "filetype": "Typ súboru", - "filter": "Filter", - "filter_description": "Podmienky na filtrovanie cieľových položiek", - "filter_people": "Filtrovať ľudí", - "filter_places": "Filtrovať miesta", - "filters": "Filtre", - "find_them_fast": "Nájdite ich rýchlejšie podľa mena", - "first": "Prvé", - "fix_incorrect_match": "Opraviť nesprávnu zhodu", - "folder": "Priečinok", - "folder_not_found": "Priečinok nebol nájdený", - "folders": "Priečinky", - "folders_feature_description": "Prezeranie zobrazenia priečinkov fotografií a videí v systéme súborov", - "forgot_pin_code_question": "Zabudli ste svoj PIN kód?", - "forward": "Dopredu", - "free_up_space": "Uvoľniť priestor", - "free_up_space_description": "Presuňte zálohované fotografie a videá do koša vášho zariadenia, aby ste uvoľnili miesto. Vaše kópie na serveri zostanú v bezpečí.", - "free_up_space_settings_subtitle": "Uvoľniť úložisko zariadenia", - "full_path": "Celá cesta: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Táto funkcia načítava externé zdroje zo spoločnosti Google, aby mohla fungovať.", - "general": "Všeobecné", - "geolocation_instruction_location": "Kliknite na položku s GPS súradnicami, aby ste použili jej polohu, alebo vyberte polohu priamo z mapy", - "get_help": "Získať pomoc", - "get_people_error": "Chyba pri načítaní ľudí", - "get_wifiname_error": "Nepodarilo sa získať názov Wi-Fi siete. Uistite sa, že ste udelili potrebné oprávnenia a ste pripojení k sieti Wi-Fi", - "getting_started": "Začíname", - "go_back": "Vrátiť sa späť", - "go_to_folder": "Prejsť do priečinka", - "go_to_search": "Prejsť na vyhľadávanie", - "gps": "GPS", - "gps_missing": "Žiadne GPS", - "grant_permission": "Udeliť povolenie", - "group_albums_by": "Zoskupiť albumy podľa...", - "group_country": "Zoskupenie podľa krajiny", - "group_no": "Nezoskupovať", - "group_owner": "Zoskupiť podľa vlastníka", - "group_places_by": "Zoskupte miesta podľa...", - "group_year": "Zoskupiť podľa roku", - "haptic_feedback_switch": "Povoliť hmatovú odozvu", - "haptic_feedback_title": "Hmatová odozva", - "has_quota": "Má kvótu", - "hash_asset": "Hashovať položku", - "hashed_assets": "Hashované položky", - "hashing": "Hashovanie", - "header_settings_add_header_tip": "Pridať hlavičku", - "header_settings_field_validator_msg": "Hodnota nemôže byť prázdna", - "header_settings_header_name_input": "Názov hlavičky", - "header_settings_header_value_input": "Hodnota hlavičky", - "headers_settings_tile_title": "Vlastné hlavičky proxy servera", - "height": "Výška", - "hi_user": "Ahoj {name} ({email})", - "hide_all_people": "Skryť všetky osoby", - "hide_gallery": "Skryť galériu", - "hide_named_person": "Skryť osobu {name}", - "hide_password": "Skryť heslo", - "hide_person": "Skryť osobu", - "hide_schema": "Skryť schému", - "hide_text_recognition": "Skryť rozpoznávanie textu", - "hide_unnamed_people": "Skryť osoby bez mena", - "home_page_add_to_album_conflicts": "Pridané {added} položiek do albumu {album}. {failed} položiek už je v albume.", - "home_page_add_to_album_err_local": "Zatiaľ nie je možné pridať lokálne média do albumov, preskakuje sa", - "home_page_add_to_album_success": "Pridané {added} položky do albumu {album}.", - "home_page_album_err_partner": "Na teraz nemôžete pridať partnerove médiá do albumov", - "home_page_archive_err_local": "Zatiaľ nemožno archivovať lokálne médiá, preskakuje sa", - "home_page_archive_err_partner": "Nemožno archivovať partnerské položky, preskakuje sa", - "home_page_building_timeline": "Vytváranie časovej osi", - "home_page_delete_err_partner": "Nie je možné vymazať položky partnera, preskakuje sa", - "home_page_delete_remote_err_local": "Miestne položky vo výbere vzdialeného odstránenia, preskakuje sa", - "home_page_favorite_err_local": "Zatiaľ nie je možné zaradiť lokálne média medzi obľúbené, preskakuje sa", - "home_page_favorite_err_partner": "Na teraz nemôžete pridať partnerove médiá medzi obľúbené", - "home_page_first_time_notice": "Ak aplikáciu používate prvýkrát, uistite sa, že ste si vybrali záložný album, aby sa na časovej osi mohli zobrazovať fotografie a videá", - "home_page_locked_error_local": "Nie je možné presunúť miestne položky do zamknutého priečinka, preskakuje sa", - "home_page_locked_error_partner": "Nie je možné presunúť partnerské položky do zamknutého priečinka, preskakuje sa", - "home_page_share_err_local": "Nemožno zdieľať lokálne médiá pomocou odkazu", - "home_page_upload_err_limit": "Naraz môžete nahrať len 30 médií, preskakuje sa", - "host": "Hostiteľ", - "hour": "Hodina", - "hours": "Hodín", - "id": "ID", - "idle": "Nečinné", - "ignore_icloud_photos": "Ignorovať fotky v službe iCloud", - "ignore_icloud_photos_description": "Fotografie uložené v službe iCloud sa nebudú odosielať na server Immich", - "image": "Obrázok", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} nasnímané {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1} dňa {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1} a {person2} dňa {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} nasnímané s {person1}, {person2} a {person3} dňa {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video nasnímané} other {Obrázok odfotený}} s osobami {person1}, {person2} a {additionalCount, number} inými dňa {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video nasnímané} other {Obrázok odfotený}} v {city}, {country} dňa {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video nasnímané} other {Obrázok odfotený}} dňa {date} v {city}, {country} s {person1}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video nasnímané} other {Obrázok odfotený}} v {city}, {country} s {person1} a {person2} dňa {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video nasnímané} other {Obrázok odfotený}} dňa {date} v {city}, {country} s {person1}, {person2} a {person3}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video nasnímamé} other {Obrázok odfotený}} v {city}, {country} s {person1}, {person2} a {additionalCount, number} inými dňa {date}", - "image_saved_successfully": "Obrázok bol uložený", - "image_viewer_page_state_provider_download_started": "Sťahovanie sa začalo", - "image_viewer_page_state_provider_download_success": "Sťahovanie bolo úspešné", - "image_viewer_page_state_provider_share_error": "Chyba zdieľania", - "immich_logo": "Logo Immich", - "immich_web_interface": "Webové rozhranie Immich", - "import_from_json": "Importovať z JSON", - "import_path": "Cesta na import", - "in_albums": "V {count, plural, one {# albume} other {# albumoch}}", - "in_archive": "V archíve", - "in_year": "V {year}", - "in_year_selector": "V", - "include_archived": "Zahrnúť archivované", - "include_shared_albums": "Zahrnúť zdieľané albumy", - "include_shared_partner_assets": "Vrátane zdieľaných položiek partnera", - "individual_share": "Zdieľanie jednotlivých položiek", - "individual_shares": "Individuálne zdieľanie", - "info": "Informácie", - "interval": { - "day_at_onepm": "Každý deň v 13:00", - "hours": "{hours, plural, one {Každú hodinu} few {Každé {hours, number} hodiny} other {Každých {hours, number} hodín}}", - "night_at_midnight": "Každý deň o polnoci", - "night_at_twoam": "Každú noc o 2:00" - }, - "invalid_date": "Neplatný dátum", - "invalid_date_format": "Neplatný formát dátumu", - "invite_people": "Pozvať ľudí", - "invite_to_album": "Pozvať do albumu", - "ios_debug_info_fetch_ran_at": "Načítanie prebehlo {dateTime}", - "ios_debug_info_last_sync_at": "Posledná synchronizácia {dateTime}", - "ios_debug_info_no_processes_queued": "Žiadne procesy nie sú v poradí na pozadí", - "ios_debug_info_no_sync_yet": "Zatiaľ nebola spustená žiadna úloha synchronizácie na pozadí", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proces na pozadí v poradí} few {{count} procesy na pozadí v poradí} other {{count} procesov na pozadí v poradí}}", - "ios_debug_info_processing_ran_at": "Spracovanie prebehlo {dateTime}", - "items_count": "{count, plural, one {# položka} few {# položky} other {# položiek}}", - "jobs": "Úlohy", - "json_editor": "Editor JSON", - "json_error": "Chyba JSON", - "keep": "Ponechať", - "keep_albums": "Ponechať albumy", - "keep_albums_count": "Ponechá sa {count} {count, plural, one {album} few {albumy} other {albumov}}", - "keep_all": "Ponechať všetko", - "keep_description": "Pri uvoľňovaní miesta vyberte, čo sa má ponechať na vašom zariadení.", - "keep_favorites": "Ponechať obľúbené", - "keep_on_device": "Ponechať na zariadení", - "keep_on_device_hint": "Vyberte položky, ktoré chcete ponechať v tomto zariadení", - "keep_this_delete_others": "Ponechať túto, odstrániť ostatné", - "keeping": "Ponechá sa: {items}", - "kept_this_deleted_others": "Táto položka bola ponechaná a {count, plural, one {odstránila sa # položka} few {odstránili sa # položky} other {odstránilo sa # položiek}}", - "keyboard_shortcuts": "Klávesové skratky", - "language": "Jazyk", - "language_no_results_subtitle": "Skúste upraviť hľadaný výraz", - "language_no_results_title": "Neboli nájdené žiadne jazyky", - "language_search_hint": "Vyhľadať jazyky...", - "language_setting_description": "Vyberte požadovaný jazyk", - "large_files": "Veľké súbory", - "last": "Posledné", - "last_months": "{count, plural, one {Minulý mesiac} few {Posledné # mesiace} other {Posledných # mesiacov}}", - "last_seen": "Naposledy videné", - "latest_version": "Najnovšia verzia", - "latitude": "Zemepisná šírka", - "leave": "Opustiť", - "leave_album": "Opustiť album", - "lens_model": "Model objektívu", - "let_others_respond": "Nechajte ostatných reagovať", - "level": "Úroveň", - "library": "Knižnica", - "library_add_folder": "Pridať priečinok", - "library_edit_folder": "Upraviť priečinok", - "library_options": "Možnosti knižnice", - "library_page_device_albums": "Albumy v zariadení", - "library_page_new_album": "Nový album", - "library_page_sort_asset_count": "Počet položiek", - "library_page_sort_created": "Najnovšie vytvorené", - "library_page_sort_last_modified": "Naposledy upravené", - "library_page_sort_title": "Podľa názvu albumu", - "licenses": "Licencie", - "light": "Svetlá", - "like": "Páči sa mi", - "like_deleted": "Like odstránený", - "link_motion_video": "Pripojiť pohyblivé video", - "link_to_oauth": "Prepojiť s OAuth", - "linked_oauth_account": "Pripojený OAuth účet", - "list": "Zoznam", - "loading": "Načítavanie", - "loading_search_results_failed": "Načítanie výsledkov hľadania sa nepodarilo", - "local": "Lokálne", - "local_asset_cast_failed": "Nie je možné preniesť médium, ktoré nie je nahrané na serveri", - "local_assets": "Lokálne položky", - "local_id": "Lokálne ID", - "local_media_summary": "Súhrn lokálnych médií", - "local_network": "Miestna sieť", - "local_network_sheet_info": "Pri použití zadanej siete Wi-Fi sa aplikácia pripojí k serveru prostredníctvom tejto URL adresy", - "location": "Poloha", - "location_permission": "Povolenie na určenie polohy", - "location_permission_content": "Na používanie funkcie automatického prepínania potrebuje aplikácia Immich presné povolenie na určenie polohy, aby mohla prečítať názov aktuálnej Wi-Fi siete", - "location_picker_choose_on_map": "Zvoľte na mape", - "location_picker_latitude_error": "Zadajte platnú zemepisnú šírku", - "location_picker_latitude_hint": "Zadajte sem vašu zemepisnú šírku", - "location_picker_longitude_error": "Zadajte platnú zemepisnú dĺžku", - "location_picker_longitude_hint": "Zadajte platnú zemepisnú dĺžku", - "lock": "Zamknúť", - "locked_folder": "Zamknutý priečinok", - "log_detail_title": "Podrobnosti o zázname", - "log_out": "Odhlásiť sa", - "log_out_all_devices": "Odhlásiť všetky zariadenia", - "logged_in_as": "Prihlásený ako {user}", - "logged_out_all_devices": "Všetky zariadenia odhlásené", - "logged_out_device": "Zariadenie odhlásené", - "login": "Prihlásenie", - "login_disabled": "Prihlasovanie bolo vypnuté", - "login_form_api_exception": "Chyba API. Skontrolujte adresu URL servera a skúste to znova.", - "login_form_back_button_text": "Späť", - "login_form_email_hint": "tvojmail@email.com", - "login_form_endpoint_hint": "http://ip-tvojho-servera:port", - "login_form_endpoint_url": "URL adresa koncového bodu servera", - "login_form_err_http": "Prosím, uveďte http:// alebo https://", - "login_form_err_invalid_email": "Neplatný e-mail", - "login_form_err_invalid_url": "Neplatná URL adresa", - "login_form_err_leading_whitespace": "Úvodná medzera", - "login_form_err_trailing_whitespace": "Koncové medzera", - "login_form_failed_get_oauth_server_config": "Chyba prihlásenia pomocou OAuth, skontrolujte adresu URL servera", - "login_form_failed_get_oauth_server_disable": "Funkcia OAuth nie je na tomto serveri dostupná", - "login_form_failed_login": "Chyba prihlásenia, skontrolujte url adresu servera, e-mail a heslo", - "login_form_handshake_exception": "Došlo k výnimke Handshake so serverom. Ak používate certifikát s vlastným podpisom, povoľte v nastaveniach podporu certifikátov s vlastným podpisom.", - "login_form_password_hint": "heslo", - "login_form_save_login": "Zostať prihlásený", - "login_form_server_empty": "Zadajte URL adresu servera.", - "login_form_server_error": "Nemožno pripojiť na server.", - "login_has_been_disabled": "Prihlásenie bolo vypnuté.", - "login_password_changed_error": "Nastala chyba pri aktualizovaní hesla", - "login_password_changed_success": "Aktualizácia hesla prebehla úspešne", - "logout_all_device_confirmation": "Ste si istý, že sa chcete odhlásiť zo všetkých zariadení?", - "logout_this_device_confirmation": "Ste si istý, že sa chcete odhlásiť z tohoto zariadenia?", - "logs": "Záznamy", - "longitude": "Zemepisná dĺžka", - "look": "Vzhľad", - "loop_videos": "Opakovať videá", - "loop_videos_description": "Povolí prehrávanie videí v slučke v detailnom zobrazení.", - "main_branch_warning": "Používate vývojársku verziu; dôrazne odporúčame používať vydané verzie!", - "main_menu": "Hlavná ponuka", - "maintenance_action_restore": "Obnovuje sa databáza", - "maintenance_description": "Immich bol prepnutý do režimu údržby.", - "maintenance_end": "Ukončiť režim údržby", - "maintenance_end_error": "Nepodarilo sa ukončiť režim údržby.", - "maintenance_logged_in_as": "Aktuálne prihlásený ako {user}", - "maintenance_restore_from_backup": "Obnoviť zo zálohy", - "maintenance_restore_library": "Obnovte svoju knižnicu", - "maintenance_restore_library_confirm": "Ak sa vám to zdá správne, pokračujte v obnovovaní zálohy!", - "maintenance_restore_library_description": "Obnovuje sa databáza", - "maintenance_restore_library_folder_has_files": "{folder} má {count, plural, one {# priečinok} few {# priečinky} other {# priečinkov}}", - "maintenance_restore_library_folder_no_files": "{folder} neobsahuje súbory!", - "maintenance_restore_library_folder_pass": "čitateľný a zapisovateľný", - "maintenance_restore_library_folder_read_fail": "nedá sa čítať", - "maintenance_restore_library_folder_write_fail": "nedá sa zapísať", - "maintenance_restore_library_hint_missing_files": "Možno vám chýbajú dôležité súbory", - "maintenance_restore_library_hint_regenerate_later": "Tieto môžete neskôr znovu vytvoriť v nastaveniach", - "maintenance_restore_library_hint_storage_template_missing_files": "Používate šablóny úložiska? Môžu vám chýbať nejaké súbory", - "maintenance_restore_library_loading": "Načítanie kontrol integrity a heuristiky…", - "maintenance_task_backup": "Vytváranie zálohy súčasnej databázy…", - "maintenance_task_migrations": "Prebieha migrácia databázy…", - "maintenance_task_restore": "Obnovuje sa zo zvolenej zálohy…", - "maintenance_task_rollback": "Obnovenie sa nepodarilo, návrat k bodu obnovenia…", - "maintenance_title": "Dočasne nedostupné", - "make": "Výrobca", - "manage_geolocation": "Spravovať polohu", - "manage_media_access_rationale": "Toto povolenie je potrebné na správne presúvanie súborov do koša a ich obnovenie z neho.", - "manage_media_access_settings": "Otvoriť nastavenia", - "manage_media_access_subtitle": "Povoliť aplikácii Immich spravovať a presúvať mediálne súbory.", - "manage_media_access_title": "Prístup k správe médií", - "manage_shared_links": "Spravovať zdieľané odkazy", - "manage_sharing_with_partners": "Spravovať zdieľanie s partnermi", - "manage_the_app_settings": "Spravovať nastavenia aplikácie", - "manage_your_account": "Spravovať váš účet", - "manage_your_api_keys": "Spravovať vaše API kľúče", - "manage_your_devices": "Spravovať vaše prihlásené zariadenia", - "manage_your_oauth_connection": "Spravovať vaše OAuth spojenia", - "map": "Mapa", - "map_assets_in_bounds": "{count, plural, =0 {Žiadne fotky v tejto sekcii} one {# fotka} few {# fotky} other {# fotiek}}", - "map_cannot_get_user_location": "Nie je možné získať polohu používateľa", - "map_location_dialog_yes": "Áno", - "map_location_picker_page_use_location": "Použiť túto polohu", - "map_location_service_disabled_content": "Služba určovania polohy musí byť povolená, aby sa zobrazovali položky z vašej aktuálnej polohy. Chcete ju teraz zapnúť?", - "map_location_service_disabled_title": "Služba určovania polohy vypnutá", - "map_marker_for_images": "Značka na mape pre obrázky odfotené v {city}, {country}", - "map_marker_with_image": "Mapová značka pre obrázok", - "map_no_location_permission_content": "Na zobrazenie položiek z vašej aktuálnej polohy je potrebné povolenie na polohu. Chcete to teraz povoliť?", - "map_no_location_permission_title": "Povolenie polohy zamietnuté", - "map_settings": "Nastavenia máp", - "map_settings_dark_mode": "Tmavý režim", - "map_settings_date_range_option_day": "Posledných 24 hodín", - "map_settings_date_range_option_days": "Po {days} dňoch", - "map_settings_date_range_option_year": "Uplynulý rok", - "map_settings_date_range_option_years": "Po {years} rokoch", - "map_settings_dialog_title": "Nastavenia máp", - "map_settings_include_show_archived": "Zahrnúť archivované", - "map_settings_include_show_partners": "Zahrnúť partnerov", - "map_settings_only_show_favorites": "Zobraziť iba obľúbené", - "map_settings_theme_settings": "Téma mapy", - "map_zoom_to_see_photos": "Oddiaľte priblíženie aby ste videli fotky", - "mark_all_as_read": "Označiť všetko ako prečítané", - "mark_as_read": "Označiť ako prečítané", - "marked_all_as_read": "Označené všetko ako prečítané", - "matches": "Zhody", - "matching_assets": "Vyhovujúce položky", - "media_type": "Typ média", - "memories": "Spomienky", - "memories_all_caught_up": "Na dnes to je všetko", - "memories_check_back_tomorrow": "Vráťte sa zajtra pre ďalšie spomienky", - "memories_setting_description": "Spravuje čo vidíte v spomienkach", - "memories_start_over": "Začať odznova", - "memories_swipe_to_close": "Zatvoríte posunom nahor", - "memory": "Pamäť", - "memory_lane_title": "Pás spomienok {title}", - "menu": "Ponuka", - "merge": "Zlúčiť", - "merge_people": "Zlúčiť ľudí", - "merge_people_limit": "Zlúčiť môžete naraz najviac 5 tvárí", - "merge_people_prompt": "Chcete zlúčiť týchto ľudí? Táto akcia sa nedá vrátiť.", - "merge_people_successfully": "Zlúčenie ľudí sa podarilo", - "merged_people_count": "{count, plural, one {Zlúčená # osoba} few {Zlúčené # osoby} other {Zlúčených # osôb}}", - "minimize": "Minimalizovať", - "minute": "Minúta", - "minutes": "Minút", - "mirror_horizontal": "Horizontálne", - "mirror_vertical": "Vertikálne", - "missing": "Chýbajúce", - "mobile_app": "Mobilná aplikácia", - "mobile_app_download_onboarding_note": "Stiahnite si sprievodnú mobilnú aplikáciu pomocou nasledujúcich možností", - "model": "Model", - "month": "Mesiac", - "monthly_title_text_date_format": "LLLL y", - "more": "Viac", - "move": "Presunúť", - "move_down": "Presunúť dole", - "move_off_locked_folder": "Presunúť zo zamknutého priečinka", - "move_to": "Presunúť do", - "move_to_device_trash": "Presunúť do koša na zariadení", - "move_to_lock_folder_action_prompt": "{count} pridaných do zamknutého priečinka", - "move_to_locked_folder": "Presunúť do zamknutého priečinka", - "move_to_locked_folder_confirmation": "Tieto fotografie a videá budú odobrané zo všetkých albumov a bude ich možné zobraziť len v zamknutom priečinku", - "move_up": "Presunúť hore", - "moved_to_archive": "{count, plural, one {Presunutá # položka} few {Presunuté # položky} other {Presunutých # položiek}} do archívu", - "moved_to_library": "{count, plural, one {Presunutá # položka} few {Presunuté # položky} other {Presunutých # položiek}} do knižnice", - "moved_to_trash": "Presunuté do koša", - "multiselect_grid_edit_date_time_err_read_only": "Nemožno upraviť dátum položky len na čítanie, preskakujem", - "multiselect_grid_edit_gps_err_read_only": "Nie je možné upraviť polohu položky (položiek), ktorá je len na čítanie, preskakuje sa", - "mute_memories": "Vyblednutie spomienok", - "my_albums": "Moje albumy", - "name": "Meno", - "name_or_nickname": "Meno alebo prezývka", - "name_required": "Meno je povinné", - "navigate": "Prejsť", - "navigate_to_time": "Prejsť na čas", - "network_requirement_photos_upload": "Použiť mobilné dáta na zálohovanie fotografií", - "network_requirement_videos_upload": "Použiť mobilné dáta na zálohovanie videí", - "network_requirements": "Požiadavky na sieť", - "network_requirements_updated": "Požiadavky na sieť sa zmenili, obnovuje sa poradie zálohovania", - "networking_settings": "Sieť", - "networking_subtitle": "Spravovať nastavenia koncového bodu servera", - "never": "nikdy", - "new_album": "Nový album", - "new_api_key": "Nový API kľúč", - "new_date_range": "Nový rozsah dátumov", - "new_password": "Nové heslo", - "new_person": "Nová osoba", - "new_pin_code": "Nový PIN kód", - "new_pin_code_subtitle": "Toto je váš prvý prístup k zamknutému priečinku. Vytvorte si PIN kód na bezpečný prístup k tejto stránke", - "new_timeline": "Nová časová os", - "new_update": "Nová aktualizácia", - "new_user_created": "Nový používateľ vytvorený", - "new_version_available": "JE DOSTUPNÁ NOVÁ VERZIA", - "newest_first": "Najprv najnovšie", - "next": "Ďalej", - "next_memory": "Ďalšia spomienka", - "no": "Nie", - "no_actions_added": "Zatiaľ neboli pridané žiadne akcie", - "no_albums_found": "Nenašli sa žiadne albumy", - "no_albums_message": "Vytvorte album na usporiadanie svojich fotiek a videí", - "no_albums_with_name_yet": "Vyzerá, že zatiaľ nemáte album s týmto názvom.", - "no_albums_yet": "Vyzerá, že zatiaľ nemáte žiadne albumy.", - "no_archived_assets_message": "Archivujte fotografie a videá a skryte ich z vášho zobrazenia fotografií", - "no_assets_message": "Kliknite a nahrajte svoju prvú fotku", - "no_assets_to_show": "Žiadne položky", - "no_cast_devices_found": "Nenašli sa žiadne zariadenia na prenos", - "no_checksum_local": "Kontrola súčtu nie je k dispozícii – nie je možné načítať lokálne položky", - "no_checksum_remote": "Kontrola súčtu nie je k dispozícii – nie je možné načítať vzdialené položky", - "no_configuration_needed": "Nie je potrebná žiadna konfigurácia", - "no_devices": "Žiadne autorizované zariadenia", - "no_duplicates_found": "Nenašli sa žiadne duplicity.", - "no_exif_info_available": "Nie sú dostupné exif údaje", - "no_explore_results_message": "Nahrajte viac fotiek na objavovanie vašej zbierky.", - "no_favorites_message": "Pridajte si obľúbené, aby ste rýchlo našli svoje najlepšie obrázky a videá", - "no_filters_added": "Zatiaľ neboli pridané žiadne filtre", - "no_libraries_message": "Vytvorte externú knižnicu na prezeranie fotiek a videí", - "no_local_assets_found": "Neboli nájdené žiadne lokálne položky s touto kontrolnou sumou", - "no_location_set": "Nie je nastavená žiadna poloha", - "no_locked_photos_message": "Fotografie a videá v zamknutom priečinku sú skryté a nezobrazujú sa pri prehľadávaní alebo vyhľadávaní v knižnici.", - "no_name": "Bez mena", - "no_notifications": "Žiadne oznámenia", - "no_people_found": "Nenašli sa žiadni vyhovujúci ľudia", - "no_places": "Bez miesta", - "no_remote_assets_found": "Neboli nájdené žiadne vzdialené položky s touto kontrolnou sumou", - "no_results": "Žiadne výsledky", - "no_results_description": "Skúste synonymum alebo všeobecnejší výraz", - "no_shared_albums_message": "Vytvorte album na zdieľanie fotiek a videí s ľuďmi vo vašej sieti", - "no_uploads_in_progress": "Žiadne prebiehajúce nahrávanie", - "none": "Žiadne", - "not_allowed": "Nepovolené", - "not_available": "Nedostupné", - "not_in_any_album": "Nie je v žiadnom albume", - "not_selected": "Nevybrané", - "note_apply_storage_label_to_previously_uploaded assets": "Poznámka: Ak chcete použiť Štítok úložiska na predtým nahrané médiá, spustite príkaz", - "notes": "Poznámky", - "nothing_here_yet": "Zatiaľ tu nič nie je", - "notification_permission_dialog_content": "Ak chcete povoliť upozornenia, prejdite do Nastavenia a vyberte možnosť Povoliť.", - "notification_permission_list_tile_content": "Udeľte povolenie na zapnutie oznámení.", - "notification_permission_list_tile_enable_button": "Povoliť oznámenia", - "notification_permission_list_tile_title": "Povolenie oznámení", - "notification_toggle_setting_description": "Povoliť e-mailové upozornenia", - "notifications": "Oznámenia", - "notifications_setting_description": "Spravovať upozornenia", - "oauth": "OAuth", - "obtainium_configurator": "Konfigurátor Obtainium", - "obtainium_configurator_instructions": "Použite Obtainium na inštaláciu a aktualizáciu aplikácie pre Android priamo z verzie Immich v službe GitHub. Vytvorte kľúč API a vyberte variantu, aby ste vytvorili konfiguračný odkaz Obtainium", - "ocr": "OCR", - "official_immich_resources": "Oficiálne Immich zdroje", - "offline": "Offline", - "offset": "Posun", - "ok": "OK", - "oldest_first": "Najprv najstaršie", - "on_this_device": "Na tomto zariadení", - "onboarding": "Na palube", - "onboarding_locale_description": "Vyberte požadovaný jazyk. Neskôr ho môžete zmeniť v nastaveniach.", - "onboarding_privacy_description": "Nasledujúce (voliteľné) funkcie závisia na externých službách a kedykoľvek ich môžete vypnúť nastaveniach.", - "onboarding_server_welcome_description": "Poďme si nastaviť vašu inštanciu s niekoľkými bežnými nastaveniami.", - "onboarding_theme_description": "Vyberte farbu témy pre váš server. Môžete to aj neskôr zmeniť vo vašich nastaveniach.", - "onboarding_user_welcome_description": "Začnime!", - "onboarding_welcome_user": "Vitaj, {user}", - "online": "Online", - "only_favorites": "Len obľúbené", - "open": "Otvoriť", - "open_in_map_view": "Otvoriť v mape", - "open_in_openstreetmap": "Otvoriť v OpenStreetMap", - "open_the_search_filters": "Otvoriť vyhľadávacie filtre", - "options": "Nastavenia", - "or": "alebo", - "organize_into_albums": "Usporiadať do albumov", - "organize_into_albums_description": "Vložiť existujúce fotky do albumov podľa aktuálnych nastavení synchronizácie", - "organize_your_library": "Usporiadajte svoju knižnicu", - "original": "originál", - "other": "Ostatné", - "other_devices": "Ďalšie zariadenia", - "other_entities": "Ostatné subjekty", - "other_variables": "Ostatné premenné", - "owned": "Vlastné", - "owner": "Vlastník", - "page": "Stránka", - "partner": "Partner", - "partner_can_access": "{partner} môže pristupovať", - "partner_can_access_assets": "Všetky vaše fotky a videá, okrem Archivovaných a Odstránených", - "partner_can_access_location": "Poloha, kde boli vaše fotografie nasnímané", - "partner_list_user_photos": "Fotky používateľa {user}", - "partner_list_view_all": "Zobraziť všetky", - "partner_page_empty_message": "Vaše fotky zatiaľ nie sú zdieľané so žiadnym partnerom.", - "partner_page_no_more_users": "Žiadni ďalší používatelia na pridanie", - "partner_page_partner_add_failed": "Pridávanie partnera zlyhalo", - "partner_page_select_partner": "Zvoliť partnera", - "partner_page_shared_to_title": "Zdieľané pre", - "partner_page_stop_sharing_content": "{partner} už nebude mať prístup ku vašim fotkám.", - "partner_sharing": "Zdieľanie s partnerom", - "partners": "Partneri", - "password": "Heslo", - "password_does_not_match": "Heslá sa nezhodujú", - "password_required": "Heslo je povinné", - "password_reset_success": "Obnovenie hesla úspešné", - "past_durations": { - "days": "{days, plural, one {Posledný deň} few {Posledné # dni} other {Posledných # dní }}", - "hours": "{hours, plural, one {Posledná hodina} few {Posledné # hodiny} other {Posledných # hodín}}", - "years": "{years, plural, one {Posledný rok} few {Posledné # roky} other {Posledných # rokov}}" - }, - "path": "Cesta", - "pattern": "Vzor", - "pause": "Pozastaviť", - "pause_memories": "Pozastaviť spomienky", - "paused": "Pozastavené", - "pending": "Čakajúce", - "people": "Ľudia", - "people_edits_count": "{count, plural, one {Upravená # osoba} few {Upravené # osoby} other {Upravených # osôb}}", - "people_feature_description": "Prehliadanie fotiek a videí zoskupených podľa ľudí", - "people_selected": "{count, plural, one {# vybraná osoba} few {# vybrané osoby} other {# vybraných osôb}}", - "people_sidebar_description": "Zobraziť odkaz na Ľudí v bočnom paneli", - "permanent_deletion_warning": "Varovanie o trvalom zmazaní", - "permanent_deletion_warning_setting_description": "Zobraziť varovanie pri trvalom zmazaní položky", - "permanently_delete": "Trvalo zmazať", - "permanently_delete_assets_count": "Natrvalo vymazať {count, plural, one {položku} few {položky} other {položiek}}", - "permanently_delete_assets_prompt": "Ste si istí, že chcete natrvalo vymazať {count, plural, one {túto položku} few {tieto # položky} other {týchto # položiek}}? Týmto sa {count, plural, one {odstráni aj z jej albumu} other {odstránia aj zo svojich albumov}}.", - "permanently_deleted_asset": "Navždy odstránená položka", - "permanently_deleted_assets_count": "Natrvalo {count, plural, one {odstránená # položka} few {odstránené # položky} other {odstránených # položiek}}", - "permission": "Povolenie", - "permission_empty": "Vaše povolenie by nemalo byť prázdne", - "permission_onboarding_back": "Späť", - "permission_onboarding_continue_anyway": "Pokračovať aj tak", - "permission_onboarding_get_started": "Začať", - "permission_onboarding_go_to_settings": "Prejsť do nastavení", - "permission_onboarding_permission_denied": "Prístup zamietnutý. Ak chcete používať Immich, udeľte v Nastaveniach povolenia na fotografie a videá.", - "permission_onboarding_permission_granted": "Povolenie udelené! Všetko je nastavené.", - "permission_onboarding_permission_limited": "Povolenie obmedzené. Ak chcete, aby Immich zálohoval a spravoval celú vašu zbierku galérie, udeľte v Nastaveniach povolenia na fotografie a videá.", - "permission_onboarding_request": "Immich vyžaduje povolenie na prezeranie vašich fotografií a videí.", - "person": "Osoba", - "person_age_months": "má {months, plural, one {# mesiac} few {# mesiace} other {# mesiacov}}", - "person_age_year_months": "má 1 rok, {months, plural, one {# mesiac} few {# mesiace} other {# mesiacov}}", - "person_age_years": "má {years, plural, one {# rok} few {# roky} other {# rokov}}", - "person_birthdate": "Narodený/á dňa {date}", - "person_hidden": "{name}{hidden, select, true { (skryté)} other {}}", - "person_recognized": "Osoba rozpoznaná", - "person_selected": "Osoba vybraná", - "photo_shared_all_users": "Vyzerá, že zdieľate svoje fotky so všetkými používateľmi alebo nemáte žiadnych používateľov.", - "photos": "Fotografie", - "photos_and_videos": "Fotografie a videá", - "photos_count": "{count, plural, one {{count, number} fotka} few {{count, number} fotky} other {{count, number} fotiek}}", - "photos_from_previous_years": "Fotky z minulých rokov", - "photos_only": "Iba fotky", - "pick_a_location": "Vyberte polohu", - "pick_custom_range": "Vlastný rozsah", - "pick_date_range": "Vybrať rozsah dátumov", - "pin_code_changed_successfully": "Úspešne ste zmenili PIN kód", - "pin_code_reset_successfully": "Úspešne ste obnovili PIN kód", - "pin_code_setup_successfully": "Úspešne ste nastavili PIN kód", - "pin_verification": "Overenie PIN kódom", - "place": "Miesto", - "places": "Miesta", - "places_count": "{count, plural, one {{count, number} miesto} few {{count, number} miesta} other {{count, number} miest}}", - "play": "Prehrať", - "play_memories": "Prehrať spomienky", - "play_motion_photo": "Prehrať pohyblivú fotku", - "play_or_pause_video": "Pustí alebo pozastaví video", - "play_original_video": "Prehrať originálne video", - "play_original_video_setting_description": "Uprednostňujte prehrávanie originálnych videí pred prekódovanými videami. Ak originálny súbor nie je kompatibilný, nemusí sa správne prehrať.", - "play_transcoded_video": "Prehrať prekódované video", - "please_auth_to_access": "Prosím, potvrďte overenie pre prístup", - "port": "Port", - "preferences_settings_subtitle": "Spravovať predvoľby aplikácie", - "preferences_settings_title": "Predvoľby", - "preparing": "Pripravuje sa", - "preset": "Predvoľba", - "preview": "Náhľad", - "previous": "Predošlé", - "previous_memory": "Predošlá spomienka", - "previous_or_next_day": "Deň dopredu/dozadu", - "previous_or_next_month": "Mesiac dopredu/dozadu", - "previous_or_next_photo": "Fotka ďalšia/predošlá", - "previous_or_next_year": "Rok dopredu/dozadu", - "primary": "Primárne", - "privacy": "Súkromie", - "profile": "Profil", - "profile_drawer_app_logs": "Záznamy", - "profile_drawer_client_server_up_to_date": "Klient a server sú aktuálne", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Režim iba na čítanie je aktivovaný. Dlhým stlačením ikony obrázku používateľa režim opustíte.", - "profile_image_of_user": "Profilový obrázok používateľa {user}", - "profile_picture_set": "Profilový obrázok nastavený.", - "public_album": "Verejný album", - "public_share": "Verejné zdieľanie", - "purchase_account_info": "Podporovateľ", - "purchase_activated_subtitle": "Ďakujeme vám za podporu aplikácie Immich a softvéru s otvoreným zdrojovým kódom", - "purchase_activated_time": "Aktivované {date}", - "purchase_activated_title": "Váš kľúč je úspešne aktivovaný", - "purchase_button_activate": "Aktivovať", - "purchase_button_buy": "Kúpiť", - "purchase_button_buy_immich": "Kúpiť Immich", - "purchase_button_never_show_again": "Už viac nezobrazovať", - "purchase_button_reminder": "Pripomenúť mi o 30 dní", - "purchase_button_remove_key": "Odobrať kľúč", - "purchase_button_select": "Vybrať", - "purchase_failed_activation": "Aktivácia sa nepodarila! Prosím skontrolujte email či je správny kľúč produktu!", - "purchase_individual_description_1": "Pre jednotlivca", - "purchase_individual_description_2": "Štatút podporovateľa", - "purchase_individual_title": "Jednotlivec", - "purchase_input_suggestion": "Máte produktový kľúč? Zadajte ho nižšie", - "purchase_license_subtitle": "Kúpte si Immich a podporte neustály vývoj tejto služby", - "purchase_lifetime_description": "Doživotná platnosť", - "purchase_option_title": "MOŽNOSTI NÁKUPU", - "purchase_panel_info_1": "Vývoj aplikácie Immich zaberá veľa času a úsilia, pričom na ňom pracujú inžinieri na plný úväzok, aby bol čo najlepší. Naším poslaním je, aby sa softvér s otvoreným zdrojovým kódom a etické obchodné postupy stali udržateľným zdrojom príjmov pre vývojárov a aby sme vytvorili ekosystém rešpektujúci súkromie so skutočnými alternatívami k zneužívajúcim cloudovým službám.", - "purchase_panel_info_2": "Keďže sme zaviazaní nezavádzať platené verzie, nezískate týmto nákupom žiadne pridané funkcie. Spoliehame sa na používateľov, ako ste vy, že podporia neustály vývoj aplikácie Immich.", - "purchase_panel_title": "Podporiť projekt", - "purchase_per_server": "Za server", - "purchase_per_user": "Za používateľa", - "purchase_remove_product_key": "Odstrániť produktový kľúč", - "purchase_remove_product_key_prompt": "Naozaj chcete odstrániť produktový kľúč?", - "purchase_remove_server_product_key": "Odstrániť produktový kľúč servera", - "purchase_remove_server_product_key_prompt": "Naozaj chcete odstrániť produktový kľúč servera?", - "purchase_server_description_1": "Pre celý server", - "purchase_server_description_2": "Štatút podporovateľa", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Produktový kľúč servera spravuje admin", - "query_asset_id": "ID požiadavky položky", - "queue_status": "V poradí {count}/{total}", - "rate_asset": "Ohodnotiť položku", - "rating": "Hodnotenie hviezdičkami", - "rating_clear": "Vyčistiť hodnotenie", - "rating_count": "{count, plural, one {# hviezdička} few {# hviezdičky} other {# hviezdičiek}}", - "rating_description": "Zobraziť EXIF hodnotenie v informačnom paneli", - "rating_set": "Hodnotenie nastavené na {rating, plural, one {# hviezdičku} few {# hviezdičky} other {# hviezdičiek}}", - "reaction_options": "Možnosti reakcie", - "read_changelog": "Prečítať zoznam zmien", - "readonly_mode_disabled": "Režim iba na čítanie je vypnutý", - "readonly_mode_enabled": "Režim iba na čítanie je zapnutý", - "ready_for_upload": "Pripravené na nahratie", - "reassign": "Preradiť", - "reassigned_assets_to_existing_person": "Opätovne {count, plural, one {priradená # položka} few {priradené # položky} other {priradených # položiek}} k {name, select, null {existujúcej osobe} other {{name}}}", - "reassigned_assets_to_new_person": "Opätovne {count, plural, one {priradená # položka} few {priradené # položky} other {priradených # položiek}} novej osobe", - "reassing_hint": "Priradí zvolenú položku k existujúcej osobe", - "recent": "Nedávne", - "recent-albums": "Posledné albumy", - "recent_searches": "Posledné vyhľadávania", - "recently_added": "Nedávno pridané", - "recently_added_page_title": "Nedávno pridané", - "recently_taken": "Nedávno nasnímané", - "recently_taken_page_title": "Nedávno zhotovené", - "refresh": "Aktualizovať", - "refresh_encoded_videos": "Obnoviť enkódované videá", - "refresh_faces": "Obnoviť tváre", - "refresh_metadata": "Obnoviť metadáta", - "refresh_thumbnails": "Obnoviť miniatúry", - "refreshed": "Obnovené", - "refreshes_every_file": "Znova prečíta všetky existujúce a nové súbory", - "refreshing_encoded_video": "Obnovovanie enkódovaných videí", - "refreshing_faces": "Obnovovanie tvárí", - "refreshing_metadata": "Obnovovanie metadát", - "regenerating_thumbnails": "Pregenerovanie náhľadov", - "remote": "Vzdialené", - "remote_assets": "Vzdialené položky", - "remote_media_summary": "Súhrn vzdialených médií", - "remove": "Odstrániť", - "remove_assets_album_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položku} few {# položky} other {# položiek}} z albumu?", - "remove_assets_shared_link_confirmation": "Naozaj chcete odstrániť {count, plural, one {# položku} few {# položky} other {# položiek}} z tohoto zdieľaného odkazu?", - "remove_assets_title": "Odstrániť položky?", - "remove_custom_date_range": "Odstrániť vlastný rozsah dátumov", - "remove_deleted_assets": "Odstrániť vymazané položky", - "remove_from_album": "Odstrániť z albumu", - "remove_from_album_action_prompt": "{count} odstránené z albumu", - "remove_from_favorites": "Odstrániť z obľúbených", - "remove_from_lock_folder_action_prompt": "{count} odobrané zo zamknutého priečinka", - "remove_from_locked_folder": "Odobrať zo zamknutého priečinka", - "remove_from_locked_folder_confirmation": "Ste si istí, že chcete tieto fotografie a videá odobrať zo zamknutého priečinka? Budú viditeľné vo vašej knižnici.", - "remove_from_shared_link": "Odstrániť zo zdieľaného odkazu", - "remove_memory": "Odstrániť spomienku", - "remove_photo_from_memory": "Odstrániť fotografiu z tejto spomienky", - "remove_tag": "Odstrániť štítok", - "remove_url": "Odstrániť URL", - "remove_user": "Odstrániť používateľa", - "removed_api_key": "Odstrániť API kľúč: {name}", - "removed_from_archive": "Odstránené z archívu", - "removed_from_favorites": "Odstránené z obľúbených", - "removed_from_favorites_count": "{count, plural, other {Odstránených #}} z obľúbených", - "removed_memory": "Odstránená pamäť", - "removed_photo_from_memory": "Fotografia odstránená z pamäte", - "removed_tagged_assets": "Odstránený štítok z {count, plural, one {# položky} other {# položiek}}", - "rename": "Premenovať", - "repair": "Opraviť", - "repair_no_results_message": "Nesledované a chýbajúce súbory sa zobrazia tu", - "replace_with_upload": "Nahradiť nahraním", - "repository": "Repozitár", - "require_password": "Vyžadovať heslo", - "require_user_to_change_password_on_first_login": "Vyžadovať zmenu hesla po prvom prihlásení", - "rescan": "Opätovné vyhľadávanie", - "reset": "Obnoviť", - "reset_password": "Obnoviť heslo", - "reset_people_visibility": "Obnoviť viditeľnosť ľudí", - "reset_pin_code": "Obnoviť PIN kód", - "reset_pin_code_description": "Ak ste zabudli svoj PIN kód, môžete kontaktovať správcu servera, aby ho obnovil", - "reset_pin_code_success": "PIN kód bol úspešne obnovený", - "reset_pin_code_with_password": "Svoj PIN kód môžete kedykoľvek obnoviť pomocou vášho hesla", - "reset_sqlite": "Obnoviť SQLite databázu", - "reset_sqlite_confirmation": "Ste si istí, že chcete obnoviť SQLite databázu? Na opätovnú synchronizáciu údajov sa budete musieť odhlásiť a znova prihlásiť", - "reset_sqlite_success": "Úspešné obnovenie databázy SQLite", - "reset_to_default": "Obnoviť na predvolené", - "resolution": "Rozlíšenie", - "resolve_duplicates": "Vyriešiť duplicity", - "resolved_all_duplicates": "Vyriešené všetky duplicity", - "restore": "Navrátiť", - "restore_all": "Navrátiť všetko", - "restore_trash_action_prompt": "{count} obnovených z koša", - "restore_user": "Navrátiť používateľa", - "restored_asset": "Navrátená položka", - "resume": "Pokračovať", - "resume_paused_jobs": "Pokračovať v {count, plural, one {# pozastavenej úlohe} other {# pozastavených úlohách}}", - "retry_upload": "Zopakovať nahrávanie", - "review_duplicates": "Preskúmať duplikáty", - "review_large_files": "Skontrolovať veľké súbory", - "role": "Rola", - "role_editor": "Editor", - "role_viewer": "Divák", - "running": "Spustené", - "save": "Uložiť", - "save_to_gallery": "Uložiť do galérie", - "saved": "Uložené", - "saved_api_key": "Uložený API Kľúč", - "saved_profile": "Uložený profil", - "saved_settings": "Nastavenia boli uložené", - "say_something": "Napíšte niečo", - "scaffold_body_error_occurred": "Vyskytla sa chyba", - "scan": "Skenovať", - "scan_all_libraries": "Preskenovať všetky knižnice", - "scan_library": "Skenovať", - "scan_settings": "Nastavenia skenovania", - "scanning": "Skenovanie", - "scanning_for_album": "Skenujem pre album...", - "search": "Hľadať", - "search_albums": "Hľadať albumy", - "search_by_context": "Hľadať s kontextom", - "search_by_description": "Vyhľadávanie podľa popisu", - "search_by_description_example": "Pešia turistika v Sape", - "search_by_filename": "Hľadať podľa názvu alebo prípony súboru", - "search_by_filename_example": "napr. IMG_1234.JPG alebo PNG", - "search_by_ocr": "Hľadať podľa OCR", - "search_by_ocr_example": "Latté", - "search_camera_lens_model": "Hľadať model objektívu...", - "search_camera_make": "Hľadať značku fotoaparátu...", - "search_camera_model": "Hľadať model fotoaparátu...", - "search_city": "Hľadať mesto...", - "search_country": "Hľadať krajinu...", - "search_filter_apply": "Použiť filter", - "search_filter_camera_title": "Vyberte typ kamery", - "search_filter_date": "Dátum", - "search_filter_date_interval": "{start} do {end}", - "search_filter_date_title": "Vyberte rozsah dátumov", - "search_filter_display_option_not_in_album": "Mimo albumu", - "search_filter_display_options": "Možnosti zobrazenia", - "search_filter_filename": "Hľadať podľa názvu súboru", - "search_filter_location": "Poloha", - "search_filter_location_title": "Vyberte polohu", - "search_filter_media_type": "Typ média", - "search_filter_media_type_title": "Vyberte typ média", - "search_filter_ocr": "Hľadať podľa OCR", - "search_filter_people_title": "Vyberte ľudí", - "search_filter_star_rating": "Hodnotenie hviezdičkami", - "search_for": "Vyhľadať", - "search_for_existing_person": "Hľadať existujúcu osobu", - "search_no_more_result": "Žiadne ďalšie výsledky", - "search_no_people": "Žiadne osoby", - "search_no_people_named": "Žiadne osoby menom \"{name}\"", - "search_no_result": "Nenašli sa žiadne výsledky, skúste použiť iný vyhľadávací výraz alebo kombináciu", - "search_options": "Možnosti hľadania", - "search_page_categories": "Kategórie", - "search_page_motion_photos": "Pohyblivé fotky", - "search_page_no_objects": "Žiadne informácie o objektoch", - "search_page_no_places": "Žiadne informácie o mieste", - "search_page_screenshots": "Snímky obrazovky", - "search_page_search_photos_videos": "Vyhľadajte svoje fotografie a videá", - "search_page_selfies": "Autoportréty", - "search_page_things": "Veci", - "search_page_view_all_button": "Zobraziť všetky", - "search_page_your_activity": "Vaša aktivita", - "search_page_your_map": "Vaša mapa", - "search_people": "Hľadať osoby", - "search_places": "Hľadať miesta", - "search_rating": "Vyhľadávanie podľa hodnotenia...", - "search_result_page_new_search_hint": "Nové vyhľadávanie", - "search_settings": "Hľadať v nastaveniach", - "search_state": "Hľadať štáty...", - "search_suggestion_list_smart_search_hint_1": "Inteligentné vyhľadávanie je predvolene povolené, na vyhľadávanie metadát použite syntax ", - "search_suggestion_list_smart_search_hint_2": "m:váš-hľadaný-výraz", - "search_tags": "Hľadať štítky...", - "search_timezone": "Hľadať časovú zónu...", - "search_type": "Typ hľadania", - "search_your_photos": "Vyhľadávanie vo vašich fotografiách", - "searching_locales": "Hľadám lokality...", - "second": "Sekundy", - "see_all_people": "Pozrieť všetky osoby", - "select": "Vybrať", - "select_album": "Vybrať album", - "select_album_cover": "Vyberte obal albumu", - "select_albums": "Vybrať albumy", - "select_all": "Vybrať všetko", - "select_all_duplicates": "Vybrať všetky duplikáty", - "select_all_in": "Označiť všetky v {group}", - "select_avatar_color": "Vyberte farbu avatara", - "select_count": "{count, plural, one {Vybrať #} other {Vybrať #}}", - "select_cutoff_date": "Vybrať cieľový dátum", - "select_face": "Vyberte tvár", - "select_featured_photo": "Vyberte náhľadovú fotku", - "select_from_computer": "Vybrať z počítača", - "select_keep_all": "Vybrať ponechať všetky", - "select_library_owner": "Vybrať vlastníka knižnice", - "select_new_face": "Vybrať novú tvár", - "select_people": "Vybrať osoby", - "select_person": "Vybrať osobu", - "select_person_to_tag": "Vyberte osobu, ktorú chcete označiť", - "select_photos": "Vybrať fotky", - "select_trash_all": "Vybrať zahodiť všetky", - "select_user_for_sharing_page_err_album": "Nepodarilo sa vytvoriť album", - "selected": "Vybrané", - "selected_count": "{count, plural, one {# vybraná} few {# vybrané} other {# vybraných}}", - "selected_gps_coordinates": "Vybrané GPS súradnice", - "send_message": "Odoslať správu", - "send_welcome_email": "Odoslať uvítací e-mail", - "server_endpoint": "Koncový bod servera", - "server_info_box_app_version": "Verzia aplikácie", - "server_info_box_server_url": "URL adresa servera", - "server_offline": "Server je Offline", - "server_online": "Server je Online", - "server_privacy": "Zásady ochrany osobných údajov servera", - "server_restarting_description": "Táto stránka sa o chvíľu obnoví.", - "server_restarting_title": "Server sa reštartuje", - "server_stats": "Štatistiky servera", - "server_update_available": "Aktualizácia servera je k dispozícii", - "server_version": "Verzia servera", - "set": "Nastaviť", - "set_as_album_cover": "Nastaviť ako obal albumu", - "set_as_featured_photo": "Nastaviť ako hlavnú fotku", - "set_as_profile_picture": "Nastaviť ako profilový obrázok", - "set_date_of_birth": "Nastaviť dátum narodenia", - "set_profile_picture": "Nastaviť profilový obrázok", - "set_slideshow_to_fullscreen": "Nastaviť prezentáciu na celú obrazovku", - "set_stack_primary_asset": "Nastaviť ako primárnu položku", - "setting_image_viewer_help": "V detailnom prehliadači sa najprv načíta malá miniatúra, potom sa načíta stredne veľký náhľad (ak je povolený) a nakoniec sa načíta originál (ak je povolený).", - "setting_image_viewer_original_subtitle": "Povolením umožníte načítať pôvodný obrázok v plnom rozlíšení (veľký!). Zakázaním znížite používania dát (v sieti, aj v dočasnej pamäte zariadenia).", - "setting_image_viewer_original_title": "Načítať pôvodný obrázok", - "setting_image_viewer_preview_subtitle": "Povolením umožníte načítať obrázok so stredným rozlíšením. Zakážte, ak chcete priamo načítať originál alebo použiť iba miniatúru.", - "setting_image_viewer_preview_title": "Načítať náhľad obrázka", - "setting_image_viewer_title": "Obrázky", - "setting_languages_apply": "Použiť", - "setting_languages_subtitle": "Zmeniť jazyk aplikácie", - "setting_notifications_notify_failures_grace_period": "Upozorniť na zlyhanie zálohovania na pozadí: {duration}", - "setting_notifications_notify_hours": "{count} hodín", - "setting_notifications_notify_immediately": "okamžite", - "setting_notifications_notify_minutes": "{count} minút", - "setting_notifications_notify_never": "nikdy", - "setting_notifications_notify_seconds": "{count} sekúnd", - "setting_notifications_single_progress_subtitle": "Podrobné informácie o priebehu nahrávania pre položku", - "setting_notifications_single_progress_title": "Zobraziť priebeh detailov zálohovania na pozadí", - "setting_notifications_subtitle": "Upravte svoje nastavenia oznámení", - "setting_notifications_total_progress_subtitle": "Celkový priebeh nahrávania (nahraných/celkovo)", - "setting_notifications_total_progress_title": "Zobraziť celkový priebeh zálohovania na pozadí", - "setting_video_viewer_auto_play_subtitle": "Automaticky spustiť prehrávanie videí po ich otvorení", - "setting_video_viewer_auto_play_title": "Automaticky prehrať videá", - "setting_video_viewer_looping_title": "Opakovanie", - "setting_video_viewer_original_video_subtitle": "Pri streamovaní videa zo servera prehrať originál, aj keď je k dispozícii prekódované video. Môže to viesť k prerušovanému prehrávaniu videa. Videá dostupné lokálne sa prehrajú v pôvodnej kvalite bez ohľadu na toto nastavenie.", - "setting_video_viewer_original_video_title": "Vynútiť pôvodné video", - "settings": "Nastavenia", - "settings_require_restart": "Na použitie tohto nastavenia reštartujte Immich", - "settings_saved": "Nastavenia boli uložené", - "setup_pin_code": "Nastavte si PIN kód", - "share": "Zdieľať", - "share_action_prompt": "{count} položiek zdieľaných", - "share_add_photos": "Pridať fotografie", - "share_assets_selected": "{count} označených", - "share_dialog_preparing": "Pripravujem...", - "share_link": "Zdieľať odkaz", - "shared": "Zdieľané", - "shared_album_activities_input_disable": "Komentár je zakázaný", - "shared_album_activity_remove_content": "Chcete vymazať túto aktivitu?", - "shared_album_activity_remove_title": "Vymazať aktivitu", - "shared_album_section_people_action_error": "Vyskytla sa chyba pri opustení/odobratí z albumu", - "shared_album_section_people_action_leave": "Odstrániť používateľa z albumu", - "shared_album_section_people_action_remove_user": "Odstrániť používateľa z albumu", - "shared_album_section_people_title": "ĽUDIA", - "shared_by": "Zdieľa", - "shared_by_user": "Zdieľa {user}", - "shared_by_you": "Zdieľané vami", - "shared_from_partner": "Fotky od {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} nahraných", - "shared_link_app_bar_title": "Zdieľané odkazy", - "shared_link_clipboard_copied_massage": "Skopírované do schránky", - "shared_link_clipboard_text": "Odkaz: {link}\nHeslo: {password}", - "shared_link_create_error": "Vyskytla sa chyba behom vytvárania zdieľaného odkazu", - "shared_link_custom_url_description": "Prístup k tomuto zdieľanému odkazu pomocou vlastnej URL adresy", - "shared_link_edit_description_hint": "Zadajte popis zdieľania", - "shared_link_edit_expire_after_option_day": "1 deň", - "shared_link_edit_expire_after_option_days": "{count} dní", - "shared_link_edit_expire_after_option_hour": "1 hodina", - "shared_link_edit_expire_after_option_hours": "{count} hodín", - "shared_link_edit_expire_after_option_minute": "1 minúta", - "shared_link_edit_expire_after_option_minutes": "{count} minút", - "shared_link_edit_expire_after_option_months": "{count} mesiacov", - "shared_link_edit_expire_after_option_year": "{count} roky", - "shared_link_edit_password_hint": "Zadajte heslo zdieľania", - "shared_link_edit_submit_button": "Aktualizovať odkaz", - "shared_link_error_server_url_fetch": "Nie je možné načítať URL adresu servera", - "shared_link_expires_day": "Vyprší o {count} deň", - "shared_link_expires_days": "Vyprší o {count} dní", - "shared_link_expires_hour": "Vyprší o {count} hodinu", - "shared_link_expires_hours": "Vyprší o {count} hodín", - "shared_link_expires_minute": "Vyprší o {count} minútu", - "shared_link_expires_minutes": "Vyprší o {count} minút", - "shared_link_expires_never": "Nevyprší", - "shared_link_expires_second": "Vyprší o {count} sekundu", - "shared_link_expires_seconds": "Vyprší o {count} sekúnd", - "shared_link_individual_shared": "Individuálne zdieľané", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Spravovať zdieľané odkazy", - "shared_link_options": "Možnosti zdieľaných odkazov", - "shared_link_password_description": "Vyžadovať heslo pre prístup k tomuto zdieľanému odkazu", - "shared_links": "Zdieľané odkazy", - "shared_links_description": "Zdieľajte fotografie a videá pomocou odkazu", - "shared_photos_and_videos_count": "{assetCount, plural, few {# zdieľané fotky a videá.} other {# zdieľaných fotiek a videí.}}", - "shared_with_me": "Zdieľané so mnou", - "shared_with_partner": "Zdieľané s {partner}", - "sharing": "Zdieľanie", - "sharing_enter_password": "Ak chcete zobraziť túto stránku, prosím, zadajte heslo.", - "sharing_page_album": "Zdieľané albumy", - "sharing_page_description": "Vytvárajte zdieľané albumy a zdieľajte fotografie a videá s ľuďmi vo vašej sieti.", - "sharing_page_empty_list": "Prázdny list", - "sharing_sidebar_description": "Zobraziť odkaz na Zdieľanie v bočnom paneli", - "sharing_silver_appbar_create_shared_album": "Vytvoriť zdieľaný album", - "sharing_silver_appbar_share_partner": "Zdieľať s partnerom", - "shift_to_permanent_delete": "stlačte ⇧ na trvalé vymazanie položky", - "show_album_options": "Zobraziť možnosti albumu", - "show_albums": "Zobraziť albumy", - "show_all_people": "Zobraziť všetkých ľudí", - "show_and_hide_people": "Zobraziť a skryť ľudí", - "show_file_location": "Zobraziť umiestnenie súboru", - "show_gallery": "Zobraziť galériu", - "show_hidden_people": "Zobraziť skrytých ľudí", - "show_in_timeline": "Zobraziť na časovej osi", - "show_in_timeline_setting_description": "Zobraziť fotky a videá tohoto používateľa na vašej časovej osi", - "show_keyboard_shortcuts": "Zobraziť klávesové skratky", - "show_metadata": "Zobraziť metadáta", - "show_or_hide_info": "Zobraziť alebo skryť informácie", - "show_password": "Zobraziť heslo", - "show_person_options": "Zobraziť možnosti osoby", - "show_progress_bar": "Zobraziť ukazovateľ priebehu", - "show_schema": "Zobraziť schému", - "show_search_options": "Zobraziť možnosti vyhľadávania", - "show_shared_links": "Zobraziť zdieľané odkazy", - "show_slideshow_transition": "Zobraziť prechody v prezentácii", - "show_supporter_badge": "Odznak podporovateľa", - "show_supporter_badge_description": "Zobraziť odznak podporovateľa", - "show_text_recognition": "Zobraziť rozpoznávanie textu", - "show_text_search_menu": "Zobraziť ponuku vyhľadávania textu", - "shuffle": "Náhodné poradie", - "sidebar": "Bočný panel", - "sidebar_display_description": "Zobraziť odkaz na zobrazenie v bočnom paneli", - "sign_out": "Odhlásiť sa", - "sign_up": "Registrovať", - "size": "Veľkosť", - "skip_to_content": "Preskočiť na obsah", - "skip_to_folders": "Preskočiť do priečinkov", - "skip_to_tags": "Preskočiť ku štítkom", - "slideshow": "Prezentácia", - "slideshow_repeat": "Opakovať prezentáciu", - "slideshow_repeat_description": "Po skončení prezentácie sa vrátiť späť na začiatok", - "slideshow_settings": "Nastavenia prezentácie", - "sort_albums_by": "Zoradiť albumy podľa...", - "sort_created": "Dátum vytvorenia", - "sort_items": "Počet položiek", - "sort_modified": "Dátum úpravy", - "sort_newest": "Najnovšia fotka", - "sort_oldest": "Najstaršia fotografia", - "sort_people_by_similarity": "Zoradiť ľudí podľa podobnosti", - "sort_recent": "Najnovšia fotografia", - "sort_title": "Názov", - "source": "Zdroj", - "stack": "Zoskupiť", - "stack_action_prompt": "{count} zoskupených", - "stack_duplicates": "Zoskupiť duplikáty", - "stack_select_one_photo": "Vyberte jednu hlavnú fotku pre zoskupenie", - "stack_selected_photos": "Zoskupiť vybraté fotky", - "stacked_assets_count": "{count, plural, one {Zoskupená # položka} few {Zoskupené # položky} other {Zoskupených # položiek}}", - "stacktrace": "Výpis zásobníku", - "start": "Spustiť", - "start_date": "Počiatočný dátum", - "start_date_before_end_date": "Dátum začiatku musí byť pred dátumom ukončenia", - "state": "Štát", - "status": "Stav", - "stop_casting": "Zastaviť prenos", - "stop_motion_photo": "Stopmotion fotka", - "stop_photo_sharing": "Zastaviť zdieľanie vašich fotografií?", - "stop_photo_sharing_description": "{partner} už nebude mať prístup k vašim fotkám.", - "stop_sharing_photos_with_user": "Zastaviť zdieľanie vašich fotiek s týmto používateľom", - "storage": "Ukladací priestor", - "storage_label": "Štítok úložiska", - "storage_quota": "Úložný limit", - "storage_usage": "Využitých {used} z {available}", - "submit": "Odoslať", - "success": "Hotovo", - "suggestions": "Návrhy", - "sunrise_on_the_beach": "Východ slnka na pláži", - "support": "Podpora", - "support_and_feedback": "Podpora a spätná väzba", - "support_third_party_description": "Vaša inštalácia Immich bola pripravená treťou stranou. Problémy, ktoré sa vyskytli, môžu byť spôsobené týmto balíčkom, preto sa na nich obráťte v prvom rade cez nasledujúce odkazy.", - "swap_merge_direction": "Vymeniť smer zlúčenia", - "sync": "Synchronizovať", - "sync_albums": "Synchronizovať albumy", - "sync_albums_manual_subtitle": "Synchronizujte všetky nahrané videá a fotografie s vybranými záložnými albumami", - "sync_local": "Synchronizovať lokálne", - "sync_remote": "Synchronizovať vzdialené", - "sync_status": "Stav synchronizácie", - "sync_status_subtitle": "Zobraziť a spravovať systém synchronizácie", - "sync_upload_album_setting_subtitle": "Vytvárajte a nahrávajte svoje fotografie a videá do vybraných albumov na Immich", - "tag": "Štítok", - "tag_assets": "Pridať štítky", - "tag_created": "Vytvorený štítok: {tag}", - "tag_feature_description": "Prehliadanie fotiek a videá zoskupených podľa tematických štítkov", - "tag_not_found_question": "Neviete nájsť štítok? Vytvorte nový štítok.", - "tag_people": "Označiť ľudí", - "tag_updated": "Upravený štítok: {tag}", - "tagged_assets": "Štítok priradený {count, plural, one {# položke} other {# položkám}}", - "tags": "Štítky", - "tap_to_run_job": "Ťuknutím na položku spustíte úlohu", - "template": "Šablóna", - "text_recognition": "Rozpoznávanie textu", - "theme": "Téma", - "theme_selection": "Výber témy", - "theme_selection_description": "Automaticky nastaví tému na svetlú alebo tmavú podľa systémových predvolieb v prehliadači", - "theme_setting_asset_list_storage_indicator_title": "Zobraziť indikátor úložiska na dlaždiciach médií", - "theme_setting_asset_list_tiles_per_row_title": "Počet položiek na riadok ({count})", - "theme_setting_colorful_interface_subtitle": "Použiť základnú farbu na plochy na pozadí.", - "theme_setting_colorful_interface_title": "Farebné rozhranie", - "theme_setting_image_viewer_quality_subtitle": "Upravte kvalitu detailného prehliadača obrázkov", - "theme_setting_image_viewer_quality_title": "Kvalita prehliadača obrázkov", - "theme_setting_primary_color_subtitle": "Vyberte si farbu pre základné akcie a dôrazy.", - "theme_setting_primary_color_title": "Základná farba", - "theme_setting_system_primary_color_title": "Použiť systémovú farbu", - "theme_setting_system_theme_switch": "Automaticky (podľa systémového nastavenia)", - "theme_setting_theme_subtitle": "Vyberte nastavenia témy aplikácie", - "theme_setting_three_stage_loading_subtitle": "Trojstupňové načítanie môže zvýšiť výkonnosť načítania, ale vedie k výrazne vyššiemu zaťaženiu siete", - "theme_setting_three_stage_loading_title": "Povolenie trojstupňového načítavania", - "then": "Potom", - "they_will_be_merged_together": "Zlúčia sa dokopy", - "third_party_resources": "Zdroje tretích strán", - "time": "Čas", - "time_based_memories": "Časové spomienky", - "time_based_memories_duration": "Počet sekúnd zobrazenia jednotlivých obrázkov.", - "timeline": "Časová os", - "timezone": "Časové pásmo", - "to_archive": "Archivovať", - "to_change_password": "Zmeniť heslo", - "to_favorite": "Obľúbiť", - "to_login": "Prihlásiť", - "to_multi_select": "na viacnásobný výber", - "to_parent": "Prejsť k nadradenému", - "to_select": "na výber", - "to_trash": "Do koša", - "toggle_settings": "Prepnúť nastavenie", - "toggle_theme_description": "Prepnúť tému", - "total": "Celkom", - "total_usage": "Celkové využitie", - "trash": "Kôš", - "trash_action_prompt": "{count} presunutých do koša", - "trash_all": "Všetko do koša", - "trash_count": "{count, number} do koša", - "trash_delete_asset": "Položky do koša/odstrániť", - "trash_emptied": "Kôš vyprázdnený", - "trash_no_results_message": "Vymazané fotografie a videá sa zobrazia tu.", - "trash_page_delete_all": "Vymazať všetky", - "trash_page_empty_trash_dialog_content": "Skutočne chcete vyprázdniť kôš? Tieto položky budú natrvalo odstránené z aplikácie Immich", - "trash_page_info": "Médiá v koši sa permanentne odstránia po {days} dňoch", - "trash_page_no_assets": "Žiadne médiá v koši", - "trash_page_restore_all": "Obnoviť všetky", - "trash_page_select_assets_btn": "Vybrať médiá", - "trash_page_title": "Kôš ({count})", - "trashed_items_will_be_permanently_deleted_after": "Položky v koši sa natrvalo vymažú po {days, plural, one {# dni} other {# dňoch}}.", - "trigger": "Spúšťač", - "trigger_asset_uploaded": "Položky boli nahrané", - "trigger_asset_uploaded_description": "Spustí sa pri nahratí novej položky", - "trigger_description": "Udalosť, ktorá spustí pracovný postup", - "trigger_person_recognized": "Osoba bola rozpoznaná", - "trigger_person_recognized_description": "Spustí sa, keď bude objavená osoba", - "trigger_type": "Typ spúšťača", - "troubleshoot": "Riešenie problémov", - "type": "Typ", - "unable_to_change_pin_code": "Nie je možné zmeniť PIN kód", - "unable_to_check_version": "Nie je možné skontrolovať verziu aplikácie alebo servera", - "unable_to_setup_pin_code": "Nie je možné nastaviť PIN kód", - "unarchive": "Odarchivovať", - "unarchive_action_prompt": "{count} odstránené z archívu", - "unarchived_count": "{count, plural, other {Odarchivovaných #}}", - "undo": "Späť", - "unfavorite": "Odznačiť ako obľúbené", - "unfavorite_action_prompt": "{count} odstránené z Obľúbených", - "unhide_person": "Znovu zobraziť osobu", - "unknown": "Neznáme", - "unknown_country": "Neznáma krajina", - "unknown_date": "Neznámy dátum", - "unknown_year": "Neznámy rok", - "unlimited": "Neobmedzené", - "unlink_motion_video": "Odpojiť pohyblivé video", - "unlink_oauth": "Odpojiť OAuth", - "unlinked_oauth_account": "Odpojený OAuth účet", - "unmute_memories": "Zrušenie stlmenia spomienok", - "unnamed_album": "Nepomenovaný album", - "unnamed_album_delete_confirmation": "Ste si istý, že chcete zmazať tento album?", - "unnamed_share": "Nepomenované zdieľanie", - "unsaved_change": "Neuložená zmena", - "unselect_all": "Zrušiť výber všetkých", - "unselect_all_duplicates": "Zrušiť výber všetkých duplicít", - "unselect_all_in": "Zrušiť výber všetkých v {group}", - "unstack": "Zrušiť zoskupenie", - "unstack_action_prompt": "{count} nezoskupených", - "unstacked_assets_count": "Zrušené zoskupenia pre {count, plural, one {# položku} few {# položky} other {# položiek}}", - "unsupported_field_type": "Nepodporovaný typ poľa", - "untagged": "Bez štítku", - "untitled_workflow": "Pracovný postup bez názvu", - "up_next": "To je všetko", - "update_location_action_prompt": "Aktualizovať polohu {count} vybraných položiek pomocou:", - "updated_at": "Aktualizované", - "updated_password": "Heslo zmenené", - "upload": "Nahrať", - "upload_concurrency": "Súbežnosť nahrávania", - "upload_details": "Podrobnosti o nahrávaní", - "upload_dialog_info": "Chcete zálohovať zvolené médiá na server?", - "upload_dialog_title": "Nahrať médiá", - "upload_error_with_count": "Chyba pri nahrávaní {count, plural, one {# položky} few {# položiek} other {# položiek}}", - "upload_errors": "Nahrávanie ukončené s {count, plural, one {# chybou} other {# chybami}}, obnovte stránku, aby sa zobrazili nové položky.", - "upload_finished": "Nahrávanie dokončené", - "upload_progress": "Ostáva {remaining, number} - Spracovaných {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {Preskočená # duplicitná položka} few {Preskočené # duplicitné položky} other {Preskočených # duplicitných položiek}}", - "upload_status_duplicates": "Duplikáty", - "upload_status_errors": "Chyby", - "upload_status_uploaded": "Nahrané", - "upload_success": "Nahrávanie úspešné, pridané súbory sa zobrazia po obnovení stránky.", - "upload_to_immich": "Nahrať na Immich ({count})", - "uploading": "Nahrávanie", - "uploading_media": "Nahrávanie médií", - "url": "Odkaz URL", - "usage": "Použitie", - "use_biometric": "Použiť biometrické údaje", - "use_current_connection": "Použiť aktuálne pripojenie", - "use_custom_date_range": "Použiť radšej vlastný rozsah dátumov", - "user": "Používateľ", - "user_has_been_deleted": "Tento používateľ bol vymazaný.", - "user_id": "ID používateľa", - "user_liked": "Používateľovi {user} sa páči {type, select, photo {táto fotka} video {toto video} asset {táto položka} other {toto}}", - "user_pin_code_settings": "PIN kód", - "user_pin_code_settings_description": "Spravujte svoj PIN kód", - "user_privacy": "Ochrana osobných údajov používateľa", - "user_purchase_settings": "Nákup", - "user_purchase_settings_description": "Spravujte svoj nákup", - "user_role_set": "Nastav {user} ako {role}", - "user_usage_detail": "Podrobnosti o využívaní používateľmi", - "user_usage_stats": "Štatistiky využitia účtu", - "user_usage_stats_description": "Zobraziť štatistiky využitia účtu", - "username": "Používateľské meno", - "users": "Používatelia", - "users_added_to_album_count": "{count, plural, one {Pridaný # používateľ} few {Pridaní # používatelia} other {Pridaných # používateľov}} do albumu", - "utilities": "Nástroje", - "validate": "Overiť", - "validate_endpoint_error": "Zadajte prosím platnú URL adresu", - "validation_error": "Chyba overenia", - "variables": "Premenné", - "version": "Verzia", - "version_announcement_closing": "Tvoj kamarát, Alex", - "version_announcement_message": "Ahoj! K dispozícii je nová verzia aplikácie Immich. Prosím, venujte trochu času prečítaniu poznámok k vydaniu, aby ste sa uistili, že vaša inštalácia je aktuálna a predišli tak akýmkoľvek chybám v konfigurácii, najmä ak používate WatchTower alebo akýkoľvek mechanizmus, ktorý sa stará o automatickú aktualizáciu vašej inštancie Immich.", - "version_history": "História verzií", - "version_history_item": "Inštalovaná {version} dňa {date}", - "video": "Video", - "video_hover_setting": "Prehrávať video náhľad pri nabehnutí myšou", - "video_hover_setting_description": "Prehrá video náhľad keď kurzor myši prejde cez položku. Aj keď je vypnuté, prehrávanie sa môže spustiť nabehnutí cez ikonu Prehrať.", - "videos": "Videá", - "videos_count": "{count, plural, one {# Video} few {# Videá} other {# Videí}}", - "videos_only": "Iba videá", - "view": "Zobrazenie", - "view_album": "Zobraziť Album", - "view_all": "Zobraziť všetky", - "view_all_users": "Zobraziť všetkých používateľov", - "view_asset_owners": "Zobraziť vlastníkov položky", - "view_details": "Zobraziť podrobnosti", - "view_in_timeline": "Zobraziť v časovej osi", - "view_link": "Zobraziť odkaz", - "view_links": "Zobraziť odkazy", - "view_name": "Zobraziť", - "view_next_asset": "Zobraziť nasledujúci súbor", - "view_previous_asset": "Zobraziť predchádzajúci súbor", - "view_qr_code": "Zobraziť QR kód", - "view_similar_photos": "Zobraziť podobné fotografie", - "view_stack": "Zobraziť zoskupenie", - "view_user": "Zobraziť používateľa", - "viewer_remove_from_stack": "Odstrániť zo zoskupenia", - "viewer_stack_use_as_main_asset": "Použiť ako hlavnú fotku", - "viewer_unstack": "Zrušiť zoskupenie", - "visibility_changed": "Viditeľnosť zmenená pre {count, plural, one {# osobu} few {# osoby} other {# osôb}}", - "visual": "Vizuálny", - "visual_builder": "Vizuálny nástroj na tvorbu", - "waiting": "Čakajúce", - "waiting_count": "V poradí: {count}", - "warning": "Varovanie", - "week": "Týždeň", - "welcome": "Vitajte", - "welcome_to_immich": "Vitajte v Immich", - "width": "Šírka", - "wifi_name": "Názov Wi-Fi", - "workflow_delete_prompt": "Naozaj chcete odstrániť tento pracovný postup?", - "workflow_deleted": "Pracovný postup bol vymazaný", - "workflow_description": "Popis pracovného postupu", - "workflow_info": "Informácie o pracovnom postupe", - "workflow_json": "Pracovný postup JSON", - "workflow_json_help": "Upravte konfiguráciu pracovného postupu vo formáte JSON. Zmeny sa synchronizujú s vizuálnym nástrojom na tvorbu.", - "workflow_name": "Názov pracovného postupu", - "workflow_navigation_prompt": "Naozaj chcete odísť bez uloženia zmien?", - "workflow_summary": "Súhrn pracovného postupu", - "workflow_update_success": "Pracovný postup bol úspešne aktualizovaný", - "workflow_updated": "Pracovný postup bol aktualizovaný", - "workflows": "Pracovné postupy", - "workflows_help_text": "Pracovné postupy automatizujú akcie týkajúce sa vašich položiek na základe spúšťačov a filtrov", - "wrong_pin_code": "Nesprávny PIN kód", - "year": "Rok", - "years_ago": "pred {years, plural, one {# rokom} other {# rokmi}}", - "yes": "Áno", - "you_dont_have_any_shared_links": "Nemáte žiadne zdielané odkazy", - "your_wifi_name": "Váš názov siete Wi-Fi", - "zero_to_clear_rating": "stlačte 0 pre vyčistenie hodnotenia položky", - "zoom_image": "Priblížiť obrázok", - "zoom_to_bounds": "Zväčšiť na okraje" -} +{} diff --git a/i18n/sl.json b/i18n/sl.json index 76e1783f71..0967ef424b 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -1,2401 +1 @@ -{ - "about": "O programu", - "account": "Račun", - "account_settings": "Nastavitve računa", - "acknowledge": "Sem seznanjen", - "action": "Dejanje", - "action_common_update": "Posodobi", - "action_description": "Nabor dejanj, ki jih je treba izvesti na filtriranih sredstvih", - "actions": "Dejanja", - "active": "Aktivno", - "active_count": "Aktivno: {count}", - "activity": "Aktivnost", - "activity_changed": "Aktivnost je {enabled, select, true {omogočena} other {onemogočena}}", - "add": "Dodaj", - "add_a_description": "Dodaj opis", - "add_a_location": "Dodaj lokacijo", - "add_a_name": "Dodaj ime", - "add_a_title": "Dodaj naslov", - "add_action": "Dodaj dejanje", - "add_action_description": "Kliknite, če želite dodati dejanje, ki ga želite izvesti", - "add_assets": "Dodaj sredstva", - "add_birthday": "Dodaj rojstni dan", - "add_endpoint": "Dodaj končno točko", - "add_exclusion_pattern": "Dodaj vzorec izključitve", - "add_filter": "Dodaj filter", - "add_filter_description": "Kliknite za dodajanje pogoja filtra", - "add_location": "Dodaj lokacijo", - "add_more_users": "Dodaj več uporabnikov", - "add_partner": "Dodaj partnerja", - "add_path": "Dodaj pot", - "add_photos": "Dodaj fotografije", - "add_tag": "Dodaj oznako", - "add_to": "Dodaj v…", - "add_to_album": "Dodaj v album", - "add_to_album_bottom_sheet_added": "Dodano v {album}", - "add_to_album_bottom_sheet_already_exists": "Že v {album}", - "add_to_album_bottom_sheet_some_local_assets": "Nekaterih lokalnih sredstev ni bilo mogoče dodati v album", - "add_to_album_toggle": "Preklopi izbiro za {album}", - "add_to_albums": "Dodaj v albume", - "add_to_albums_count": "Dodaj v albume ({count})", - "add_to_bottom_bar": "Dodaj v", - "add_to_shared_album": "Dodaj k deljenemu albumu", - "add_upload_to_stack": "Dodaj nalaganje v sklad", - "add_url": "Dodaj URL", - "add_workflow_step": "Dodaj korak poteka dela", - "added_to_archive": "Dodano v arhiv", - "added_to_favorites": "Dodano med priljubljene", - "added_to_favorites_count": "{count, number} dodanih med priljubljene", - "admin": { - "add_exclusion_pattern_description": "Dodajte vzorec izključitev. Globiranje z uporabo *, ** in ? je podprto. Če želite prezreti vse datoteke v katerem koli imeniku z imenom \"Raw\", uporabite \"**/Raw/**\". Če želite prezreti vse datoteke, ki se končajo na \".tif\", uporabite \"**/*.tif\". Če želite prezreti absolutno pot, uporabite \"/pot/za/ignoriranje/**\".", - "admin_user": "Skrbniški uporabnik", - "asset_offline_description": "Tega sredstva zunanje knjižnice ni več mogoče najti na disku in je bilo premaknjeno v koš. Če je bila datoteka premaknjena znotraj knjižnice, preverite svojo časovnico za novo ustrezno sredstvo. Če želite obnoviti to sredstvo, zagotovite, da ima Immich dostop do spodnje poti datoteke, in skenirajte knjižnico.", - "authentication_settings": "Nastavitve preverjanja pristnosti", - "authentication_settings_description": "Upravljanje gesel, OAuth in drugih nastavitev preverjanja pristnosti", - "authentication_settings_disable_all": "Ali zares želite onemogočiti vse prijavne metode? Prijava bo popolnoma onemogočena.", - "authentication_settings_reenable": "Za ponovno omogočanje uporabite strežniški ukaz.", - "background_task_job": "Opravila v ozadju", - "backup_database": "Ustvari izpis baze podatkov", - "backup_database_enable_description": "Omogoči izpise baze podatkov", - "backup_keep_last_amount": "Število prejšnjih izpisov baze podatkov, ki jih je treba obdržati", - "backup_onboarding_1_description": "kopijo zunaj lokacije v oblaku ali na drugi fizični lokaciji.", - "backup_onboarding_2_description": "lokalne kopije na različnih napravah. To vključuje glavne datoteke in lokalno varnostno kopijo teh datotek.", - "backup_onboarding_3_description": "skupno število kopij vaših podatkov, vključno z izvirnimi datotekami. To vključuje 1 kopijo zunaj lokacije in 2 lokalni kopiji.", - "backup_onboarding_description": "Za zaščito podatkov priporočamo strategijo varnostnega kopiranja 3-2-1. Za celovito rešitev varnostnega kopiranja hranite kopije naloženih fotografij/videoposnetkov in podatkovne baze Immich.", - "backup_onboarding_footer": "Za več informacij o varnostnem kopiranju Immicha glejte dokumentacijo.", - "backup_onboarding_parts_title": "Varnostna kopija 3-2-1 vključuje:", - "backup_onboarding_title": "Varnostne kopije", - "backup_settings": "Nastavitve izpisa baze podatkov", - "backup_settings_description": "Upravljanje nastavitev izpisa podatkovne baze.", - "cleared_jobs": "Razčiščena opravila za: {job}", - "config_set_by_file": "Konfiguracija je trenutno nastavljena s konfiguracijsko datoteko", - "confirm_delete_library": "Ali ste prepričani, da želite izbrisati knjižnico {library}?", - "confirm_delete_library_assets": "Ali ste prepričani, da želite izbrisati to knjižnico? To bo iz Immicha izbrisalo {count, plural, one {# vsebovani vir} two {# vsebovana vira} few {# vsebovane vire} other {vseh # vsebovanih virov}} in tega ni možno razveljaviti. Datoteke bodo ostale na disku.", - "confirm_email_below": "Za potrditev vnesite \"{email}\" spodaj", - "confirm_reprocess_all_faces": "Ali ste prepričani, da želite znova obdelati vse obraze? S tem boste počistili tudi že imenovane osebe.", - "confirm_user_password_reset": "Ali ste prepričani, da želite ponastaviti geslo uporabnika {user}?", - "confirm_user_pin_code_reset": "Ali ste prepričani, da želite ponastaviti PIN kodo uporabnika {user}?", - "copy_config_to_clipboard_description": "Kopiraj trenutno sistemsko konfiguracijo kot objekt JSON v odložišče", - "create_job": "Ustvari opravilo", - "cron_expression": "Nastavitveni izraz Cron", - "cron_expression_description": "Nastavite interval skeniranja z uporabo zapisa cron. Za več informacij poglej npr. Crontab Guru", - "cron_expression_presets": "Prednastavitve izraza Cron", - "disable_login": "Onemogoči prijavo", - "duplicate_detection_job_description": "Zaženite strojno učenje na sredstvih, da zaznate podobne slike. Zanaša se na Pametno Iskanje", - "exclusion_pattern_description": "Vzorci izključitev vam omogočajo, da prezrete datoteke in mape pri skeniranju knjižnice. To je uporabno, če imate mape z datotekami, ki jih ne želite uvoziti, na primer datoteke RAW.", - "export_config_as_json_description": "Prenesite trenutno konfiguracijo sistema kot datoteko JSON", - "external_libraries_page_description": "Skrbniška stran zunanje knjižnice", - "face_detection": "Zaznavanje obrazov", - "face_detection_description": "Zaznavanje obrazov v sredstvih z uporabo strojnega učenja. Pri videoposnetkih se upošteva samo sličica. »Osveži« (ponovno) obdela vsa sredstva. »Ponastavi« dodatno izbriše vse trenutne podatke o obrazih. »Manjkajoča« uvrsti sredstva, ki še niso bila obdelana, v čakalno vrsto. Zaznani obrazi bodo po končanem zaznavanju obrazov uvrščeni v čakalno vrsto za prepoznavanje obrazov, pri čemer bodo združeni v obstoječe ali nove osebe.", - "facial_recognition_job_description": "Združi zaznane obraze v osebe. Ta korak se izvede po končanem zaznavanju obrazov. »Ponastavi« (ponovno) združi vse obraze. »Manjkajoča« uvrsti obraze, ki jim ni dodeljena oseba, v čakalno vrsto.", - "failed_job_command": "Za opravilo {job} ukaz {command} ni uspel", - "force_delete_user_warning": "OPOZORILO: S tem boste takoj odstranili uporabnika in vsa sredstva. Tega ni mogoče razveljaviti in datotek ni mogoče obnoviti.", - "image_format": "Format", - "image_format_description": "WebP ustvari manjše datoteke kot JPEG, vendar je počasnejši za kodiranje.", - "image_fullsize_description": "Slika v polni velikosti brez metapodatkov, uporabljena pri povečavi", - "image_fullsize_enabled": "Omogoči ustvarjanje slik v polni velikosti", - "image_fullsize_enabled_description": "Ustvari sliko v polni velikosti za formate, ki niso prijazni spletu. Ko je omogočena možnost »Prednostno vdelani predogled«, se vdelani predogledi uporabljajo neposredno brez pretvorbe. Ne vpliva na spletu prijazne formate, kot je JPEG.", - "image_fullsize_quality_description": "Kakovost slike v polni velikosti od 1 do 100. Višja vrednost pomeni boljšo kakovost, vendar ustvarja večje datoteke.", - "image_fullsize_title": "Nastavitve slike v polni velikosti", - "image_prefer_embedded_preview": "Uporabi raje vdelan predogled", - "image_prefer_embedded_preview_setting_description": "Uporabi vdelane predoglede v fotografijah RAW kot vhod za obdelavo slik, kadar so na voljo. To lahko pri nekaterih slikah zagotovi natančnejše barve, vendar je kakovost predogleda odvisna od fotoaparata, slika pa lahko vsebuje več artefaktov stiskanja.", - "image_prefer_wide_gamut": "Uporabi raje širok razpon", - "image_prefer_wide_gamut_setting_description": "Uporabite P3 Display za sličice. To bolje ohranja živahnost slik s širokimi barvnimi prostori, vendar so lahko slike videti drugače na starih napravah s staro različico brskalnika. Slike sRGB se ohranijo kot sRGB, da se izognejo barvnim zamikom.", - "image_preview_description": "Slika srednje velikosti z odstranjenimi metapodatki, ki se uporablja pri ogledu posameznega sredstva in za strojno učenje", - "image_preview_quality_description": "Kakovost predogleda od 1-100. Višje je boljše, vendar ustvarja večje datoteke in lahko zmanjša odzivnost aplikacije. Nastavitev nizke vrednosti lahko vpliva na kakovost strojnega učenja.", - "image_preview_title": "Nastavitve predogleda", - "image_progressive": "Napredno", - "image_progressive_description": "Za postopno nalaganje slik JPEG kodirajte postopoma. To ne vpliva na slike WebP.", - "image_quality": "Kvaliteta", - "image_resolution": "Resolucija", - "image_resolution_description": "Višje ločljivosti lahko ohranijo več podrobnosti, vendar kodiranje traja dlje, imajo večje velikosti datotek in lahko zmanjšajo odzivnost aplikacije.", - "image_settings": "Nastavitve slike", - "image_settings_description": "Upravljajte kakovost in ločljivost ustvarjenih slik", - "image_thumbnail_description": "Majhna sličica z odstranjenimi metapodatki, ki se uporablja pri ogledovanju skupin fotografij, kot je glavna časovnica", - "image_thumbnail_quality_description": "Kakovost sličic od 1-100. Višje je boljše, vendar ustvarja večje datoteke in lahko zmanjša odzivnost aplikacije.", - "image_thumbnail_title": "Nastavitve sličic", - "import_config_from_json_description": "Uvozite sistemsko konfiguracijo z nalaganjem konfiguracijske datoteke JSON", - "job_concurrency": "{job} sočasnost", - "job_created": "Opravilo ustvarjeno", - "job_not_concurrency_safe": "To delo ni varno za sočasnost.", - "job_settings": "Nastavitve opravil", - "job_settings_description": "Upravljaj sočasnost opravil", - "jobs_delayed": "{jobCount, plural, other {# zadržani}}", - "jobs_failed": "{jobCount, plural, other {# neuspešni}}", - "jobs_over_time": "Opravila skozi čas", - "library_created": "Ustvarjena knjižnica: {library}", - "library_deleted": "Knjižnica izbrisana", - "library_details": "Podrobnosti o knjižnici", - "library_folder_description": "Določite mapo za uvoz. Ta mapa, vključno s podmapami, bo pregledana za slike in videoposnetke.", - "library_remove_exclusion_pattern_prompt": "Ali ste prepričani, da želite odstraniti ta vzorec izključitve?", - "library_remove_folder_prompt": "Ali ste prepričani, da želite odstraniti to mapo za uvoz?", - "library_scanning": "Periodični pregledi", - "library_scanning_description": "Nastavi periodični pregled knjižnic", - "library_scanning_enable_description": "Omogoči periodični pregled knjižnic", - "library_settings": "Zunanja knjižnica", - "library_settings_description": "Uredi nastavitve zunanje knjižnice", - "library_tasks_description": "Izvedi nalogo knjižnice", - "library_updated": "Posodobljena knjižnica", - "library_watching_enable_description": "Opazuj spremembe datotek v zunanji knjižnici", - "library_watching_settings": "Opazovanje knjižnice [POSKUSNO]", - "library_watching_settings_description": "Samodejno opazuj spremembo datotek", - "logging_enable_description": "Omogoči dnevnik", - "logging_level_description": "Nivo dnevnika, ko je le-ta omogočen.", - "logging_settings": "Dnevnik", - "machine_learning_availability_checks": "Preverjanja razpoložljivosti", - "machine_learning_availability_checks_description": "Samodejno zaznavanje in dajanje prednosti razpoložljivim strežnikom strojnega učenja", - "machine_learning_availability_checks_enabled": "Omogoči preverjanja razpoložljivosti", - "machine_learning_availability_checks_interval": "Interval preverjanja", - "machine_learning_availability_checks_interval_description": "Interval v milisekundah med preverjanji razpoložljivosti", - "machine_learning_availability_checks_timeout": "Zahteva za časovno omejitev", - "machine_learning_availability_checks_timeout_description": "Časovna omejitev v milisekundah za preverjanje razpoložljivosti", - "machine_learning_clip_model": "model CLIP", - "machine_learning_clip_model_description": "Ime CLIP modela iz seznama tukaj. Vedite, da boste morali po menjavi modela ponovno zagnati opravilo za 'Pametno iskanje' za vse slike.", - "machine_learning_duplicate_detection": "Zaznavanje dvojnikov", - "machine_learning_duplicate_detection_enabled": "Omogoči zaznavanje dvojnikov", - "machine_learning_duplicate_detection_enabled_description": "Če je onemogočeno, bodo popolnoma enaki posnetki še vedno obravnavani.", - "machine_learning_duplicate_detection_setting_description": "Za iskanje verjetnih dvojnikov uporabite vdelave CLIP", - "machine_learning_enabled": "Omogoči strojno učenje", - "machine_learning_enabled_description": "Če je onemogočeno, bodo vse funkcije strojnega učenja onemogočene ne glede na spodnje nastavitve.", - "machine_learning_facial_recognition": "Prepoznavanje obrazov", - "machine_learning_facial_recognition_description": "Zaznavanje, prepoznavanje in združevanje obrazov na slikah", - "machine_learning_facial_recognition_model": "Model za prepoznavanje obraza", - "machine_learning_facial_recognition_model_description": "Modeli so navedeni v padajočem vrstnem redu glede na velikost. Večji modeli so počasnejši in uporabljajo več pomnilnika, vendar dajejo boljše rezultate. Upoštevajte, da morate po spremembi modela znova zagnati opravilo zaznavanja obrazov za vse slike.", - "machine_learning_facial_recognition_setting": "Omogoči prepoznavanje obraza", - "machine_learning_facial_recognition_setting_description": "Če je onemogočeno, slike ne bodo kodirane za prepoznavanje obraza in ne bodo zapolnile razdelka Ljudje na strani Razišči.", - "machine_learning_max_detection_distance": "Največja razdalja zaznavanja", - "machine_learning_max_detection_distance_description": "Največja razdalja med dvema slikama za dvojnike, ki se giblje od 0,001 do 0,1. Višje vrednosti bodo zaznale več dvojnikov, vendar lahko povzročijo lažne pozitivne rezultate.", - "machine_learning_max_recognition_distance": "Največja razdalja za prepoznavanje", - "machine_learning_max_recognition_distance_description": "Največja razdalja med dvema obrazoma za isto osebo, ki se giblje od 0-2. Znižanje lahko prepreči označevanje dveh oseb kot iste osebe, zvišanje pa lahko prepreči označevanje iste osebe kot dve različni osebi. Upoštevajte, da je lažje združiti dve osebi kot eno osebo razdeliti na dva dela, zato se zmotite pri nižjem pragu, kadar je to mogoče.", - "machine_learning_min_detection_score": "Najmanjši rezultat zaznavanja", - "machine_learning_min_detection_score_description": "Najmanjši rezultat zaupanja za zaznavanje obraza od 0-1. Nižje vrednosti bodo zaznale več obrazov, vendar lahko povzročijo lažne pozitivne rezultate.", - "machine_learning_min_recognized_faces": "Najmanjše število prepoznanih obrazov", - "machine_learning_min_recognized_faces_description": "Najmanjše število prepoznanih obrazov za osebo, da se ustvari. Če to povečate, postane prepoznavanje obraza natančnejše na račun večje možnosti, da obraz ni dodeljen osebi.", - "machine_learning_ocr": "Optično prepoznavanje znakov (OCR)", - "machine_learning_ocr_description": "Uporaba strojnega učenja za prepoznavanje besedila na slikah", - "machine_learning_ocr_enabled": "Omogoči OCR", - "machine_learning_ocr_enabled_description": "Če je onemogočeno, slike ne bodo prepoznane po besedilu.", - "machine_learning_ocr_max_resolution": "Največja ločljivost", - "machine_learning_ocr_max_resolution_description": "Predogledi nad to ločljivostjo bodo spremenjeni, pri čemer se bo ohranilo razmerje stranic. Višje vrednosti so natančnejše, vendar obdelava traja dlje in porabi več pomnilnika.", - "machine_learning_ocr_min_detection_score": "Najnižji rezultat zaznavanja", - "machine_learning_ocr_min_detection_score_description": "Najnižja stopnja zaupanja za zaznavanje besedila je od 0 do 1. Nižje vrednosti bodo zaznale več besedila, vendar lahko povzročijo lažno pozitivne rezultate.", - "machine_learning_ocr_min_recognition_score": "Najnižji rezultat prepoznavanja", - "machine_learning_ocr_min_score_recognition_description": "Najnižja stopnja zaupanja za prepoznavanje zaznanega besedila je od 0 do 1. Nižje vrednosti bodo prepoznale več besedila, vendar lahko povzročijo lažno pozitivne rezultate.", - "machine_learning_ocr_model": "OCR model", - "machine_learning_ocr_model_description": "Strežniški modeli so natančnejši od mobilnih modelov, vendar obdelujejo podatke dlje in porabijo več pomnilnika.", - "machine_learning_settings": "Nastavitve strojnega učenja", - "machine_learning_settings_description": "Upravljajte funkcije in nastavitve strojnega učenja", - "machine_learning_smart_search": "Pametno iskanje", - "machine_learning_smart_search_description": "Semantično poiščite slike z uporabo vdelav CLIP", - "machine_learning_smart_search_enabled": "Omogoči pametno iskanje", - "machine_learning_smart_search_enabled_description": "Če je onemogočeno, slike ne bodo kodirane za pametno iskanje.", - "machine_learning_url_description": "URL strežnika za strojno učenje. Če je na voljo več kot en URL, bo vsak strežnik poskusen posamično, dokler se eden ne odzove uspešno, v vrstnem redu od prvega do zadnjega. Strežniki, ki se ne odzovejo, bodo začasno prezrti, dokler se spet ne vzpostavijo.", - "maintenance_delete_backup": "Izbriši varnostno kopijo", - "maintenance_delete_backup_description": "Ta datoteka bo nepreklicno izbrisana.", - "maintenance_delete_error": "Varnostne kopije ni bilo mogoče izbrisati.", - "maintenance_restore_backup": "Obnovi varnostno kopijo", - "maintenance_restore_backup_description": "Immich bo izbrisan in obnovljen iz izbrane varnostne kopije. Pred nadaljevanjem bo ustvarjena varnostna kopija.", - "maintenance_restore_backup_different_version": "Ta varnostna kopija je bila ustvarjena z drugačno različico programa Immich!", - "maintenance_restore_backup_unknown_version": "Varnostne različice ni bilo mogoče določiti.", - "maintenance_restore_database_backup": "Obnovi varnostno kopijo baze podatkov", - "maintenance_restore_database_backup_description": "Povrnitev na prejšnje stanje baze podatkov z uporabo varnostne kopije", - "maintenance_settings": "Vzdrževanje", - "maintenance_settings_description": "Preklopite Immich v vzdrževalni način.", - "maintenance_start": "Preklopi v način vzdrževanja", - "maintenance_start_error": "Vzdrževalnega načina ni bilo mogoče zagnati.", - "maintenance_upload_backup": "Naloži datoteko varnostne kopije baze podatkov", - "maintenance_upload_backup_error": "Varnostne kopije ni bilo mogoče naložiti. Ali gre za datoteko .sql/.sql.gz?", - "manage_concurrency": "Upravljanje sočasnosti", - "manage_concurrency_description": "Pomaknite se na stran z opravili, da upravljate sočasnost opravil", - "manage_log_settings": "Upravljanje nastavitev dnevnika", - "map_dark_style": "Temni način", - "map_enable_description": "Omogoči funkcije zemljevida", - "map_gps_settings": "Nastavitve zemljevida in GPS", - "map_gps_settings_description": "Upravljajte nastavitve zemljevida in GPS (povratno geokodiranje)", - "map_implications": "Funkcija zemljevida se opira na zunanjo storitev ploščic (tiles.immich.cloud)", - "map_light_style": "Svetli način", - "map_manage_reverse_geocoding_settings": "Upravljanje nastavitev Povratno geokodiranje", - "map_reverse_geocoding": "Povratno geokodiranje", - "map_reverse_geocoding_enable_description": "Omogoči povratno geokodiranje", - "map_reverse_geocoding_settings": "Nastavitve povratnega geokodiranja", - "map_settings": "Zemljevid", - "map_settings_description": "Upravljanje nastavitev zemljevida", - "map_style_description": "URL do teme zemljevida style.json", - "memory_cleanup_job": "Čiščenje pomnilnika", - "memory_generate_job": "Generiranje spomina", - "metadata_extraction_job": "Izvleči metapodatke", - "metadata_extraction_job_description": "Izvleči informacije iz metapodatkov iz vseh virov, kot so GPS, obrazi in resolucija", - "metadata_faces_import_setting": "Omogoči uvoz obraza", - "metadata_faces_import_setting_description": "Uvozite obraze iz slikovnih podatkov EXIF in stranskih datotek", - "metadata_settings": "Nastavitve metapodatkov", - "metadata_settings_description": "Upravljanje nastavitev metapodatkov", - "migration_job": "Migracija", - "migration_job_description": "Prenesite sličice za sredstva in obraze v najnovejšo strukturo map", - "nightly_tasks_cluster_faces_setting_description": "Zaženi prepoznavanje obrazov na novo zaznanih obrazih", - "nightly_tasks_cluster_new_faces_setting": "Združite nove obraze", - "nightly_tasks_database_cleanup_setting": "Naloge čiščenja baze podatkov", - "nightly_tasks_database_cleanup_setting_description": "Očistite stare, potekle podatke iz baze podatkov", - "nightly_tasks_generate_memories_setting": "Ustvari spomine", - "nightly_tasks_generate_memories_setting_description": "Ustvari nove spomine iz sredstev", - "nightly_tasks_missing_thumbnails_setting": "Ustvari manjkajoče sličice", - "nightly_tasks_missing_thumbnails_setting_description": "Sredstva brez sličic postavite v čakalno vrsto za ustvarjanje sličic", - "nightly_tasks_settings": "Nastavitve nočnih opravil", - "nightly_tasks_settings_description": "Upravljajte nočne naloge", - "nightly_tasks_start_time_setting": "Začetni čas", - "nightly_tasks_start_time_setting_description": "Čas, ko strežnik začne izvajati nočne naloge", - "nightly_tasks_sync_quota_usage_setting": "Posodobi kvoto porabljenega prostora", - "nightly_tasks_sync_quota_usage_setting_description": "Posodobi kvoto shrambe uporabnikov glede na trenutno uporabo", - "no_paths_added": "Ni dodanih poti", - "no_pattern_added": "Nobenega dodanega vzorca", - "note_apply_storage_label_previous_assets": "Opomba: Če želite oznako za shranjevanje uporabiti za predhodno naložena sredstva, zaženite", - "note_cannot_be_changed_later": "OPOMBA: Tega pozneje ni mogoče spremeniti!", - "notification_email_from_address": "Od naslova", - "notification_email_from_address_description": "Pošiljateljev e-poštni naslov, na primer: \"Immich Photo Server \". Uporabite naslov, s katerega lahko pošiljate e-pošto.", - "notification_email_host_description": "Gostitelj e-poštnega strežnika (npr. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Prezri napake potrdil", - "notification_email_ignore_certificate_errors_description": "Prezri napake pri preverjanju potrdila TLS (ni priporočljivo)", - "notification_email_password_description": "Geslo za uporabo pri preverjanju pristnosti z e-poštnim strežnikom", - "notification_email_port_description": "Vrata e-poštnega strežnika (npr. 25, 465 ali 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Uporabite SMTPS (SMTP prek TLS)", - "notification_email_sent_test_email_button": "Pošljite testno e-pošto in shrani", - "notification_email_setting_description": "Nastavitve za pošiljanje e-poštnih obvestil", - "notification_email_test_email": "Pošlji testno e-pošto", - "notification_email_test_email_failed": "Pošiljanje testnega e-poštnega sporočila ni uspelo, preverite svoje podatke", - "notification_email_test_email_sent": "Testno e-poštno sporočilo je bilo poslano na {email}. Prosimo, preverite svoj nabiralnik.", - "notification_email_username_description": "Uporabniško ime za uporabo pri preverjanju pristnosti z e-poštnim strežnikom", - "notification_enable_email_notifications": "Omogoči e-poštna obvestila", - "notification_settings": "Nastavitve obvestil", - "notification_settings_description": "Upravljaj z nastavitvami obvestil, vključno z e-pošto", - "oauth_auto_launch": "Samodejni zagon", - "oauth_auto_launch_description": "Samodejno zaženite tok prijave OAuth, ko obiščete stran za prijavo", - "oauth_auto_register": "Samodejna registracija", - "oauth_auto_register_description": "Samodejna registracija novih uporabnikov po prijavi z OAuth", - "oauth_button_text": "Besedilo gumba", - "oauth_client_secret_description": "Zahtevano za zaupnega odjemalca ali če PKCE (dokazni ključ za izmenjavo kode) ni podprt za javnega odjemalca.", - "oauth_enable_description": "Prijava z OAuth", - "oauth_mobile_redirect_uri": "Mobilni preusmeritveni URI", - "oauth_mobile_redirect_uri_override": "Preglasitev URI preusmeritve za mobilne naprave", - "oauth_mobile_redirect_uri_override_description": "Omogoči, ko ponudnik OAuth ne dovoli mobilnega URI-ja, kot je ''{callback}''", - "oauth_role_claim": "Zahteva za vlogo", - "oauth_role_claim_description": "Samodejno dodeli skrbniški dostop na podlagi prisotnosti tega zahtevka. Zahtevek ima lahko »uporabnik« ali »skrbnik«.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Upravljanje nastavitev prijave OAuth", - "oauth_settings_more_details": "Za več podrobnosti o tej funkciji glejte dokumentacijo.", - "oauth_storage_label_claim": "Zahtevek za nalepko za shranjevanje", - "oauth_storage_label_claim_description": "Samodejno nastavi uporabnikovo oznako za shranjevanje na vrednost tega zahtevka.", - "oauth_storage_quota_claim": "Zahtevek za kvoto prostora za shranjevanje", - "oauth_storage_quota_claim_description": "Samodejno nastavi uporabnikovo kvoto shranjevanja na vrednost tega zahtevka.", - "oauth_storage_quota_default": "Privzeta kvota za shranjevanje (GiB)", - "oauth_storage_quota_default_description": "Kvota v GiB, ki se uporabi, kadar ni podanega zahtevka.", - "oauth_timeout": "Časovna omejitev zahteve", - "oauth_timeout_description": "Časovna omejitev za zahteve v milisekundah", - "ocr_job_description": "Uporaba strojnega učenja za prepoznavanje besedila na slikah", - "password_enable_description": "Prijava z e-pošto in geslom", - "password_settings": "Prijava z geslom", - "password_settings_description": "Upravljajte nastavitve prijave z geslom", - "paths_validated_successfully": "Vse poti so bile uspešno potrjene", - "person_cleanup_job": "Čiščenje osebe", - "queue_details": "Podrobnosti čakalne vrste", - "queues": "Čakalne vrste opravil", - "queues_page_description": "Skrbniška stran s čakalnimi vrstami opravil", - "quota_size_gib": "Velikost kvote (GiB)", - "refreshing_all_libraries": "Osveževanje vseh knjižnic", - "registration": "Registracija administratorja", - "registration_description": "Ker ste prvi uporabnik v sistemu, boste dodeljeni kot skrbnik in ste odgovorni za skrbniška opravila, dodatne uporabnike pa boste ustvarili sami.", - "remove_failed_jobs": "Odstrani neuspešna opravila", - "require_password_change_on_login": "Od uporabnika zahtevajte spremembo gesla ob prvi prijavi", - "reset_settings_to_default": "Ponastavi nastavitve na privzete", - "reset_settings_to_recent_saved": "Ponastavite nastavitve na nedavno shranjene nastavitve", - "scanning_library": "Pregledovanje knjižnice", - "search_jobs": "Išči opravila…", - "send_welcome_email": "Pošlji pozdravno e-pošto", - "server_external_domain_settings": "Zunanja domena", - "server_external_domain_settings_description": "Domena za javne skupne povezave, vključno s http(s)://", - "server_public_users": "Javni uporabniki", - "server_public_users_description": "Vsi uporabniki (ime in e-pošta) so navedeni pri dodajanju uporabnika v albume v skupni rabi. Ko je onemogočen, bo seznam uporabnikov na voljo samo skrbniškim uporabnikom.", - "server_settings": "Nastavitve strežnika", - "server_settings_description": "Upravljanje nastavitev strežnika", - "server_stats_page_description": "Skrbniška stran s statistiko strežnika", - "server_welcome_message": "Pozdravno sporočilo", - "server_welcome_message_description": "Sporočilo prikazano na prijavni strani.", - "settings_page_description": "Stran z nastavitvami za skrbnike", - "sidecar_job": "Stranski metapodatki", - "sidecar_job_description": "Odkrijte ali sinhronizirajte stranske metapodatke iz datotečnega sistema", - "slideshow_duration_description": "Število sekund za prikaz posamezne slike", - "smart_search_job_description": "Izvedite strojno učenje na sredstvih za podporo pametnega iskanja", - "storage_template_date_time_description": "Časovni žig ustvarjanja sredstva se uporablja za informacije o datumu in času", - "storage_template_date_time_sample": "Vzorec časa {date}", - "storage_template_enable_description": "Omogoči mehanizem predloge za shranjevanje", - "storage_template_hash_verification_enabled": "Omogočeno preverjanje zgoščene vrednosti", - "storage_template_hash_verification_enabled_description": "Omogoči preverjanje zgoščene vrednosti, tega ne onemogočite, razen če niste prepričani o posledicah", - "storage_template_migration": "Selitev predloge za shranjevanje", - "storage_template_migration_description": "Uporabi trenutno {template} za predhodno naložena sredstva", - "storage_template_migration_info": "Spremembe predloge bodo veljale samo za nova sredstva. Če želite retroaktivno uporabiti predlogo za predhodno naložena sredstva, zaženite {job}.", - "storage_template_migration_job": "Opravilo selitve predloge za shranjevanje", - "storage_template_more_details": "Za več podrobnosti o tej funkciji si oglejte Predlogo za shranjevanje in njene posledice", - "storage_template_onboarding_description_v2": "Ko je omogočena, bo ta funkcija samodejno organizirala datoteke na podlagi uporabniško določene predloge. Za več informacij glejte dokumentacijo.", - "storage_template_path_length": "Približna omejitev dolžine poti: {length, number}/{limit, number}", - "storage_template_settings": "Predloga za shranjevanje", - "storage_template_settings_description": "Upravljajte strukturo map in ime datoteke sredstva za nalaganje", - "storage_template_user_label": "{label} je uporabniška oznaka za shranjevanje", - "system_settings": "Sistemske nastavitve", - "tag_cleanup_job": "Čiščenje oznak", - "template_email_available_tags": "V svoji predlogi lahko uporabite naslednje spremenljivke: {tags}", - "template_email_if_empty": "Če je predloga prazna, bo uporabljena privzeta e-pošta.", - "template_email_invite_album": "Predloga povabila v album", - "template_email_preview": "Predogled", - "template_email_settings": "E-poštne predloge", - "template_email_update_album": "Posodobi predlogo albuma", - "template_email_welcome": "Predloga pozdravnega e-poštnega sporočila", - "template_settings": "Predloge obvestil", - "template_settings_description": "Upravljanje predlog po meri za obvestila", - "theme_custom_css_settings": "CSS po meri", - "theme_custom_css_settings_description": "Kaskadni slogovni listi (CSS) omogočajo prilagajanje oblikovanja Immicha.", - "theme_settings": "Nastavitve teme", - "theme_settings_description": "Upravljanje prilagajanja spletnega vmesnika Immich", - "thumbnail_generation_job": "Ustvarite sličice", - "thumbnail_generation_job_description": "Ustvari velike, majhne in zamegljene sličice za vsako sredstvo ter sličice za vsako osebo", - "transcoding_acceleration_api": "API za pospeševanje", - "transcoding_acceleration_api_description": "API, ki bo sodeloval z vašo napravo za pospešitev prekodiranja. Ta nastavitev je 'po najboljših močeh': v primeru napake se bo vrnila k programskemu prekodiranju. VP9 lahko deluje ali ne deluje, odvisno od vaše strojne opreme.", - "transcoding_acceleration_nvenc": "NVENC (zahteva NVIDIA GPE)", - "transcoding_acceleration_qsv": "Hitra sinhronizacija (zahteva procesor Intel 7. generacije ali novejši)", - "transcoding_acceleration_rkmpp": "RKMPP (samo na Rockchip SOC)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Dovoljeni zvočni kodeki", - "transcoding_accepted_audio_codecs_description": "Izberite, katerih zvočnih kodekov ni treba prekodirati. Uporablja se samo za določene politike prekodiranja.", - "transcoding_accepted_containers": "Sprejeti zabojniki", - "transcoding_accepted_containers_description": "Izberite, katerih formatov zabojnika ni treba ponovno muksirati v MP4. Uporablja se samo za določene politike prekodiranja.", - "transcoding_accepted_video_codecs": "Podprti video kodeki", - "transcoding_accepted_video_codecs_description": "Izberite, katerih video kodekov ni treba prekodirati. Uporablja se samo za določene politike prekodiranja.", - "transcoding_advanced_options_description": "Možnosti, ki jih večini uporabnikov ne treba spreminjati", - "transcoding_audio_codec": "Zvočni kodek", - "transcoding_audio_codec_description": "Opus je najbolj kakovostna možnost, vendar ima slabšo združljivost s starimi napravami ali programsko opremo.", - "transcoding_bitrate_description": "Videoposnetki, ki presegajo največjo bitno hitrost ali niso v sprejemljivem formatu", - "transcoding_codecs_learn_more": "Če želite izvedeti več o tukaj uporabljeni terminologiji, glejte dokumentacijo FFmpeg za kodek H.264, kodek HEVC in VP9 kodek.", - "transcoding_constant_quality_mode": "Način stalne kakovosti", - "transcoding_constant_quality_mode_description": "ICQ je boljši od CQP, vendar nekatere naprave za pospeševanje strojne opreme ne podpirajo tega načina. Če nastavite to možnost, bo pri uporabi kodiranja na podlagi kakovosti izbran način. NVENC ga ignorira, ker ne podpira ICQ.", - "transcoding_constant_rate_factor": "Faktor konstantne stopnje (-crf)", - "transcoding_constant_rate_factor_description": "Raven kakovosti videa. Tipične vrednosti so 23 za H.264, 28 za HEVC, 31 za VP9 in 35 za AV1. Nižje je boljše, vendar ustvarja večje datoteke.", - "transcoding_disabled_description": "Ne prekodirajte nobenih videoposnetkov, lahko prekine predvajanje na nekaterih odjemalcih", - "transcoding_encoding_options": "Možnosti kodiranja", - "transcoding_encoding_options_description": "Nastavite kodeke, ločljivost, kakovost in druge možnosti za kodirane videoposnetke", - "transcoding_hardware_acceleration": "Strojno pospeševanje", - "transcoding_hardware_acceleration_description": "Eksperimentalno: hitrejše prekodiranje, vendar se lahko kakovost pri enaki bitni hitrosti zmanjša", - "transcoding_hardware_decoding": "Strojno dekodiranje", - "transcoding_hardware_decoding_setting_description": "Omogoča pospeševanje od konca do konca namesto samo pospeševanja kodiranja. Morda ne bo delovalo na vseh videoposnetkih.", - "transcoding_max_b_frames": "Največji B-okvirji", - "transcoding_max_b_frames_description": "Višje vrednosti izboljšajo učinkovitost stiskanja, vendar upočasnijo kodiranje. Morda ni združljivo s strojnim pospeševanjem na starejših napravah. 0 onemogoči okvirje B, medtem ko -1 samodejno nastavi to vrednost.", - "transcoding_max_bitrate": "Največja bitna hitrost", - "transcoding_max_bitrate_description": "Z nastavitvijo najvišje bitne hitrosti je mogoče povečati predvidljivost velikosti datotek, vendar z manjšim zmanjšanjem kakovosti. Pri ločljivosti 720p so tipične vrednosti 2600 kbit/s za VP9 ali HEVC oziroma 4500 kbit/s za H.264. Onemogočeno, če je nastavljeno na 0. Če enota ni določena, se predpostavlja k (za kbit/s); zato so 5000, 5000 kbit/s in 5 Mbit/s enakovredne.", - "transcoding_max_keyframe_interval": "Največji interval ključnih sličic", - "transcoding_max_keyframe_interval_description": "Nastavi največjo razdaljo med ključnimi slikami. Nižje vrednosti poslabšajo učinkovitost stiskanja, vendar izboljšajo čas iskanja in lahko izboljšajo kakovost prizorov s hitrim gibanjem. 0 samodejno nastavi to vrednost.", - "transcoding_optimal_description": "Videoposnetki, ki so višji od ciljne ločljivosti ali niso v sprejemljivem formatu", - "transcoding_policy": "Politika prekodiranja", - "transcoding_policy_description": "Nastavite kdaj bo videoposnetek prekodiran", - "transcoding_preferred_hardware_device": "Prednostna strojna naprava", - "transcoding_preferred_hardware_device_description": "Velja samo za VAAPI in QSV. Nastavi dri vozlišče, ki se uporablja za strojno prekodiranje.", - "transcoding_preset_preset": "Prednastavitev (-preset)", - "transcoding_preset_preset_description": "Hitrost stiskanja. Počasnejše prednastavitve ustvarijo manjše datoteke in povečajo kakovost pri ciljanju na določeno bitno hitrost. VP9 ignorira hitrosti nad 'hitreje'.", - "transcoding_reference_frames": "Referenčni okvirji", - "transcoding_reference_frames_description": "Število okvirjev, na katere se sklicujete pri stiskanju danega okvira. Višje vrednosti izboljšajo učinkovitost stiskanja, vendar upočasnijo kodiranje. 0 samodejno nastavi to vrednost.", - "transcoding_required_description": "Samo videoposnetki, ki niso v sprejemljivi obliki", - "transcoding_settings": "Nastavitve video prekodiranja", - "transcoding_settings_description": "Upravljajte katere videoposnetke želite prekodirati in kako jih obdelati", - "transcoding_target_resolution": "Ciljna ločljivost", - "transcoding_target_resolution_description": "Višje ločljivosti lahko ohranijo več podrobnosti, vendar kodiranje traja dlje, imajo večje velikosti datotek in lahko zmanjšajo odzivnost aplikacije.", - "transcoding_temporal_aq": "Časovni AQ", - "transcoding_temporal_aq_description": "Velja samo za NVENC. Časovna prilagodljiva kvantizacija izboljša kakovost prizorov z visoko stopnjo podrobnosti in nizko stopnjo gibanja. Morda ni združljivo s starejšimi napravami.", - "transcoding_threads": "Niti", - "transcoding_threads_description": "Višje vrednosti vodijo do hitrejšega kodiranja, vendar pustijo manj prostora strežniku za obdelavo drugih nalog, ko je aktiven. Ta vrednost ne sme biti večja od števila jeder procesorja. Maksimira uporabo, če je nastavljeno na 0.", - "transcoding_tone_mapping": "Tonska preslikava", - "transcoding_tone_mapping_description": "Poskuša ohraniti videz videoposnetkov HDR pri pretvorbi v SDR. Vsak algoritem naredi različne kompromise glede barve, podrobnosti in svetlosti. Hable ohrani podrobnosti, Mobius ohrani barvo, Reinhard pa svetlost.", - "transcoding_transcode_policy": "Politika prekodiranja", - "transcoding_transcode_policy_description": "Pravilnik o tem, kdaj je treba videoposnetek prekodirati. Videoposnetki HDR bodo vedno prekodirani (razen če je prekodiranje onemogočeno).", - "transcoding_two_pass_encoding": "Dvohodno kodiranje", - "transcoding_two_pass_encoding_setting_description": "Prekodirajte v dveh prehodih za ustvarjanje bolje kodiranih videoposnetkov. Ko je omogočena največja bitna hitrost (ki je potrebna za delovanje s H.264 in HEVC), ta način uporablja obseg bitne hitrosti, ki temelji na največji bitni hitrosti, in ignorira CRF. Za VP9 je mogoče uporabiti CRF, če je največja bitna hitrost onemogočena.", - "transcoding_video_codec": "Video kodek", - "transcoding_video_codec_description": "VP9 ima visoko učinkovitost in spletno združljivost, vendar traja dalj časa za prekodiranje. HEVC deluje podobno, vendar ima slabšo spletno združljivost. H.264 je široko združljiv in se hitro prekodira, vendar ustvarja veliko večje datoteke. AV1 je najučinkovitejši kodek, vendar nima podpore na starejših napravah.", - "trash_enabled_description": "Omogoči funkcije smetnjaka", - "trash_number_of_days": "Število dni", - "trash_number_of_days_description": "Število dni za shranjevanje sredstev v smetnjaku, preden jih trajno odstranite", - "trash_settings": "Nastavitve smetnjaka", - "trash_settings_description": "Upravljanje nastavitev smetnjaka", - "unlink_all_oauth_accounts": "Prekini povezavo z vsemi računi OAuth", - "unlink_all_oauth_accounts_description": "Pred selitvijo k novemu ponudniku ne pozabite prekiniti povezave vseh računov OAuth.", - "unlink_all_oauth_accounts_prompt": "Ali ste prepričani, da želite prekiniti povezavo z vsemi računi OAuth? S tem boste ponastavili ID OAuth za vsakega uporabnika in tega ni mogoče razveljaviti.", - "user_cleanup_job": "Čiščenje uporabnika", - "user_delete_delay": "Račun in sredstva {user} bodo načrtovani za trajno brisanje čez {delay, plural, one {# dan} other {# dni}}.", - "user_delete_delay_settings": "Zamakni izbris", - "user_delete_delay_settings_description": "Število dni po odstranitvi za trajno brisanje uporabnikovega računa in sredstev. Opravilo za brisanje uporabnikov se izvaja ob polnoči, da se preveri, ali so uporabniki pripravljeni na izbris. Spremembe te nastavitve bodo ovrednotene pri naslednji izvedbi.", - "user_delete_immediately": "Račun in sredstva uporabnika {user} bodo v čakalni vrsti za trajno brisanje takoj.", - "user_delete_immediately_checkbox": "Uporabnika in sredstva postavite v čakalno vrsto za takojšnje brisanje", - "user_details": "Podrobnosti o uporabniku", - "user_management": "Upravljanje uporabnikov", - "user_password_has_been_reset": "Geslo uporabnika je bilo ponastavljeno:", - "user_password_reset_description": "Uporabniku posredujte začasno geslo in ga obvestite, da bo moral ob naslednji prijavi spremeniti geslo.", - "user_restore_description": "Račun {user} bo obnovljen.", - "user_restore_scheduled_removal": "Obnovi uporabnika – načrtovana odstranitev na {date, date, long}", - "user_settings": "Uporabniške nastavitve", - "user_settings_description": "Upravljanje uporabniških nastavitev", - "user_successfully_removed": "Uporabnik {email} je bil uspešno odstranjen.", - "users_page_description": "Stran skrbniških uporabnikov", - "version_check_enabled_description": "Omogoči preverjanje različice", - "version_check_implications": "Funkcija preverjanja različic se opira na občasno komunikacijo z github.com", - "version_check_settings": "Preverjanje različice", - "version_check_settings_description": "Omogoči/onemogoči obvestilo o novi različici", - "video_conversion_job": "Prekodiranje videoposnetkov", - "video_conversion_job_description": "Prekodirajte videoposnetke za večjo združljivost z brskalniki in napravami" - }, - "admin_email": "Skrbniška e-pošta", - "admin_password": "Skrbniško geslo", - "administration": "Administracija", - "advanced": "Napredno", - "advanced_settings_clear_image_cache": "Počisti predpomnilnik slik", - "advanced_settings_clear_image_cache_error": "Brisanje predpomnilnika slik ni uspelo", - "advanced_settings_clear_image_cache_success": "Uspešno počiščeno {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Uporabite to možnost za filtriranje medijev med sinhronizacijo na podlagi alternativnih meril. To poskusite le, če imate težave z aplikacijo, ki zaznava vse albume.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Uporabite alternativni filter za sinhronizacijo albuma v napravi", - "advanced_settings_log_level_title": "Nivo dnevnika: {level}", - "advanced_settings_prefer_remote_subtitle": "Nekatere naprave zelo počasi nalagajo sličice iz lokalnih sredstev. Aktivirajte to nastavitev, če želite namesto tega naložiti oddaljene slike.", - "advanced_settings_prefer_remote_title": "Uporabi raje oddaljene slike", - "advanced_settings_proxy_headers_subtitle": "Določi proxy glavo, ki jo naj Immich pošlje ob vsaki mrežni zahtevi", - "advanced_settings_proxy_headers_title": "Proxy glave po meri [POSKUSNO]", - "advanced_settings_readonly_mode_subtitle": "Omogoči način samo za branje, kjer si je mogoče fotografije samo ogledati, funkcije, kot so izbiranje več slik, deljenje, predvajanje in brisanje, so onemogočene. Omogoči/onemogoči način samo za branje prek uporabniškega avatarja na glavnem zaslonu", - "advanced_settings_readonly_mode_title": "Način samo za branje", - "advanced_settings_self_signed_ssl_subtitle": "Preskoči preverjanje potrdila SSL za končno točko strežnika. Zahtevano za samopodpisana potrdila.", - "advanced_settings_self_signed_ssl_title": "Dovoli samopodpisana potrdila SSL [POSKUSNO]", - "advanced_settings_sync_remote_deletions_subtitle": "Samodejno izbriši ali obnovi sredstvo v tej napravi, ko je to dejanje izvedeno v spletu", - "advanced_settings_sync_remote_deletions_title": "Sinhroniziraj oddaljene izbrise [EKSPERIMENTALNO]", - "advanced_settings_tile_subtitle": "Napredne uporabniške nastavitve", - "advanced_settings_troubleshooting_subtitle": "Omogočite dodatne funkcije za odpravljanje težav", - "advanced_settings_troubleshooting_title": "Odpravljanje težav", - "age_months": "Starost {months, plural, one {# mesec} two {# meseca} few {# mesece} other {# mesecev}}", - "age_year_months": "Starost 1 leto, {months, plural, one {# mesec} two {# meseca} few {# mesece} other {# mesecev}}", - "age_years": "Starost {years, plural, one {# leto} two {# leti} few {# leta} other {# let}}", - "album": "Album", - "album_added": "Album dodan", - "album_added_notification_setting_description": "Prejmite e-poštno obvestilo, ko ste dodani v album v skupni rabi", - "album_cover_updated": "Naslovnica albuma posodobljena", - "album_delete_confirmation": "Ali ste prepričani, da želite izbrisati album {album}?", - "album_delete_confirmation_description": "Če je ta album v skupni rabi, drugi uporabniki ne bodo mogli več dostopati do njega.", - "album_deleted": "Album izbrisan", - "album_info_card_backup_album_excluded": "IZKLJUČENO", - "album_info_card_backup_album_included": "VKLJUČENO", - "album_info_updated": "Podatki o albumu posodobljeni", - "album_leave": "Zapusti album?", - "album_leave_confirmation": "Ali ste prepričani, da želite zapustiti {album}?", - "album_name": "Ime albuma", - "album_options": "Možnosti albuma", - "album_remove_user": "Odstrani uporabnika?", - "album_remove_user_confirmation": "Ali ste prepričani, da želite odstraniti {user}?", - "album_search_not_found": "Ni najdenih albumov, ki bi ustrezali vašemu iskanju", - "album_selected": "Izbran album", - "album_share_no_users": "Videti je, da ste ta album dali v skupno rabo z vsemi uporabniki ali pa nimate nobenega uporabnika, s katerim bi ga lahko delili.", - "album_summary": "Povzetek albuma", - "album_updated": "Album posodobljen", - "album_updated_setting_description": "Prejmite e-poštno obvestilo, ko ima album v skupni rabi nova sredstva", - "album_upload_assets": "Naložite sredstva iz računalnika in jih dodajte v album", - "album_user_left": "Zapustil {album}", - "album_user_removed": "Odstranjen {user}", - "album_viewer_appbar_delete_confirm": "Ali ste prepričani, da želite izbrisati ta album iz svojega računa?", - "album_viewer_appbar_share_err_delete": "Albuma ni bilo mogoče izbrisati", - "album_viewer_appbar_share_err_leave": "Albuma ni bilo mogoče zapustiti", - "album_viewer_appbar_share_err_remove": "Pri odstranjevanju sredstev iz albuma so težave", - "album_viewer_appbar_share_err_title": "Naslova albuma ni bilo mogoče spremeniti", - "album_viewer_appbar_share_leave": "Zapusti album", - "album_viewer_appbar_share_to": "Deli s/z", - "album_viewer_page_share_add_users": "Dodaj uporabnike", - "album_with_link_access": "Omogočite vsem s povezavo ogled fotografij in ljudi v tem albumu.", - "albums": "Albumi", - "albums_count": "{count, plural, one {{count, number} album} two {{count, number} albuma} few {{count, number} albumi} other {{count, number} albumov}}", - "albums_default_sort_order": "Privzeti vrstni red razvrščanja albumov", - "albums_default_sort_order_description": "Začetni vrstni red razvrščanja sredstev pri ustvarjanju novih albumov.", - "albums_feature_description": "Zbirke sredstev, ki jih je mogoče deliti z drugimi uporabniki.", - "albums_on_device_count": "Albumi v napravi ({count})", - "albums_selected": "{count, plural, one {izbran # album} two {izbrana # albuma} few {izbrani # albumi} other {izbranih # albumov}}", - "all": "Vse", - "all_albums": "Vsi albumi", - "all_people": "Vsi ljudje", - "all_photos": "Vse fotografije", - "all_videos": "Vsi videi", - "allow_dark_mode": "Dovoli temni način", - "allow_edits": "Dovoli urejanja", - "allow_public_user_to_download": "Dovoli javnemu uporabniku prenos", - "allow_public_user_to_upload": "Dovolite javnemu uporabniku nalaganje", - "allowed": "Dovoljeno", - "alt_text_qr_code": "Slika QR kode", - "always_keep": "Vedno ohrani", - "always_keep_photos_hint": "S funkcijo \"Sprosti prostor\" bodo vse fotografije shranjene v tej napravi.", - "always_keep_videos_hint": "S funkcijo \"Sprosti prostor\" bodo vsi videoposnetki shranjeni v tej napravi.", - "anti_clockwise": "V nasprotni smeri urinega kazalca", - "api_key": "API ključ", - "api_key_description": "Ta vrednost bo prikazana samo enkrat. Ne pozabite jo kopirati, preden zaprete okno.", - "api_key_empty": "Ime ključa API ne sme biti prazno", - "api_keys": "API ključi", - "app_architecture_variant": "Različica (Arhitektura)", - "app_bar_signout_dialog_content": "Ste prepričani, da se želite odjaviti?", - "app_bar_signout_dialog_ok": "Da", - "app_bar_signout_dialog_title": "Odjava", - "app_download_links": "Povezave za prenos aplikacij", - "app_settings": "Nastavitve aplikacije", - "app_stores": "Trgovine z aplikacijami", - "app_update_available": "Posodobitev aplikacije je na voljo", - "appears_in": "Pojavi se v", - "apply_count": "Uporabi ({count, number})", - "archive": "Arhiv", - "archive_action_prompt": "v arhiv je dodanih {count}", - "archive_or_unarchive_photo": "Arhivirajte ali odstranite fotografijo iz arhiva", - "archive_page_no_archived_assets": "Ni arhiviranih sredstev", - "archive_page_title": "Arhiv ({count})", - "archive_size": "Velikost arhiva", - "archive_size_description": "Konfigurirajte velikost arhiva za prenose (v GiB)", - "archived": "Arhivirano", - "archived_count": "{count, plural, one {# arhiviran} two {# arhivirana} few {# arhivirani} other {# arhiviranih}}", - "are_these_the_same_person": "Ali je to ista oseba?", - "are_you_sure_to_do_this": "Ste prepričani, da želite to narediti?", - "array_field_not_fully_supported": "Polja matrike zahtevajo ročno urejanje JSON", - "asset_action_delete_err_read_only": "Sredstev samo za branje ni mogoče izbrisati, preskočim", - "asset_action_share_err_offline": "Ni mogoče pridobiti sredstev brez povezave, preskočim", - "asset_added_to_album": "Dodano v album", - "asset_adding_to_album": "Dodajanje v album…", - "asset_created": "Sredstvo ustvarjeno", - "asset_description_updated": "Opis sredstva je posodobljen", - "asset_filename_is_offline": "Sredstvo {filename} je brez povezave", - "asset_has_unassigned_faces": "Sredstvo ima nedodeljene obraze", - "asset_hashing": "Zgoščevanje…", - "asset_list_group_by_sub_title": "Združi po", - "asset_list_layout_settings_dynamic_layout_title": "Dinamična postavitev", - "asset_list_layout_settings_group_automatically": "Samodejno", - "asset_list_layout_settings_group_by": "Združi sredstva po", - "asset_list_layout_settings_group_by_month_day": "Mesec + dan", - "asset_list_layout_sub_title": "Postavitev", - "asset_list_settings_subtitle": "Nastavitve postavitve mreže fotografij", - "asset_list_settings_title": "Mreža fotografij", - "asset_not_found_on_device_android": "Sredstva ni bilo mogoče najti v napravi", - "asset_not_found_on_device_ios": "Sredstva ni bilo mogoče najti v napravi. Če uporabljate iCloud, sredstvo morda ni dostopno zaradi napačne datoteke, shranjene v iCloudu", - "asset_not_found_on_icloud": "Sredstva ni bilo mogoče najti v iCloudu. Sredstvo morda ni dostopno zaradi napačne datoteke, shranjene v iCloudu", - "asset_offline": "Sredstvo brez povezave", - "asset_offline_description": "Tega zunanjega sredstva ni več mogoče najti na disku. Za pomoč kontaktirajte Immich skrbnika.", - "asset_restored_successfully": "Sredstvo uspešno obnovljeno", - "asset_skipped": "Preskočeno", - "asset_skipped_in_trash": "V smetnjak", - "asset_trashed": "Sredstvo je bilo premaknjeno v koš", - "asset_troubleshoot": "Odpravljanje težav s sredstvi", - "asset_uploaded": "Naloženo", - "asset_uploading": "Nalaganje…", - "asset_viewer_settings_subtitle": "Upravljaj nastavitve pregledovalnika galerije", - "asset_viewer_settings_title": "Pregledovalnik sredstev", - "assets": "Sredstva", - "assets_added_count": "Dodano{count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "assets_added_to_album_count": "Dodano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v album", - "assets_added_to_albums_count": "Dodano {assetTotal, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v {albumTotal, plural, one {# album} two {# albuma} few {# albume} other {# albumov}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Sredstvo} two {Sredstvi} few {Sredstva} other {Sredstev}} ni mogoče dodati v album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Sredstvo} two {Sredstvi} few {Sredstva} other {Sredstev}} ni mogoče dodati v noben album", - "assets_count": "{count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "assets_deleted_permanently": "trajno izrisana sredstva {count}", - "assets_deleted_permanently_from_server": "trajno izbrisana sredstva iz strežnika Immich {count}", - "assets_downloaded_failed": "{count, plural, one {Prenešena # datoteka - {error} datoteka ni uspela} two {Prenešeni # datoteki - {error} datoteki nista uspeli} few {Prenešene # datoteke - {error} datoteke niso uspele} other {Prenešenih # datotek - {error} datoteke niso uspele}}", - "assets_downloaded_successfully": "{count, plural, one {Uspešno prenešena # datoteka} two {Uspešno prenešeni # datoteki} few {Uspešno prenešene # datoteke} other {Uspešno prenešenih # datotek}}", - "assets_moved_to_trash_count": "Premaknjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v smetnjak", - "assets_permanently_deleted_count": "Trajno izbrisano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "assets_removed_count": "Odstranjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "assets_removed_permanently_from_device": "trajno odstranjena sredstva iz naprave {count}", - "assets_restore_confirmation": "Ali ste prepričani, da želite obnoviti vsa sredstva, ki ste jih odstranili? Tega dejanja ne morete razveljaviti! Upoštevajte, da sredstev brez povezave ni mogoče obnoviti na ta način.", - "assets_restored_count": "Obnovljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "assets_restored_successfully": "uspešno obnovljena sredstva {count}", - "assets_trashed": "sredstva v smetnjaku {count}", - "assets_trashed_count": "V smetnjak {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "assets_trashed_from_server": "sredstva iz strežnika Immich v smetnjaku {count}", - "assets_were_part_of_album_count": "{count, plural, one {sredstvo je} two {sredstvi sta} few {sredstva so} other {sredstev je}} že del albuma", - "assets_were_part_of_albums_count": "{count, plural, one {Sredstvo je} two {Sredstvi sta} few {Sredstva so} other {Sredstev je}} že del albumov", - "authorized_devices": "Pooblaščene naprave", - "automatic_endpoint_switching_subtitle": "Povežite se lokalno prek določenega omrežja Wi-Fi, ko je na voljo, in uporabite druge povezave drugje", - "automatic_endpoint_switching_title": "Samodejno preklapljanje URL-jev", - "autoplay_slideshow": "Samodejno predvajanje diaprojekcije", - "back": "Nazaj", - "back_close_deselect": "Nazaj, zaprite ali prekličite izbiro", - "background_backup_running_error": "Varnostno kopiranje v ozadju se trenutno izvaja, ročnega varnostnega kopiranja ni mogoče zagnati", - "background_location_permission": "Dovoljenje za iskanje lokacije v ozadju", - "background_location_permission_content": "Ko deluje v ozadju mora imeti Immich za zamenjavo omrežij, *vedno* dostop do natančne lokacije, da lahko aplikacija prebere ime omrežja Wi-Fi", - "background_options": "Možnosti ozadja", - "backup": "Varnostna kopija", - "backup_album_selection_page_albums_device": "Albumi v napravi ({count})", - "backup_album_selection_page_albums_tap": "Tapnite za vključitev, dvakrat tapnite za izključitev", - "backup_album_selection_page_assets_scatter": "Sredstva so lahko razpršena po več albumih. Tako je mogoče med postopkom varnostnega kopiranja albume vključiti ali izključiti.", - "backup_album_selection_page_select_albums": "Izberi albume", - "backup_album_selection_page_selection_info": "Informacije o izbiri", - "backup_album_selection_page_total_assets": "Skupaj unikatnih sredstev", - "backup_albums_sync": "Sinhronizacija varnostnih kopij albumov", - "backup_all": "Vse", - "backup_background_service_backup_failed_message": "Varnostno kopiranje sredstev ni uspelo. Ponovno poskušam…", - "backup_background_service_complete_notification": "Varnostno kopiranje sredstev končano", - "backup_background_service_connection_failed_message": "Povezava s strežnikom ni uspela. Ponovno poskušam…", - "backup_background_service_current_upload_notification": "Nalagam {filename}", - "backup_background_service_default_notification": "Preverjam za novimi sredstvi…", - "backup_background_service_error_title": "Napaka varnostnega kopiranja", - "backup_background_service_in_progress_notification": "Varnostno kopiranje vaših sredstev…", - "backup_background_service_upload_failure_notification": "Nalaganje {filename} ni uspelo", - "backup_controller_page_albums": "Varnostno kopiranje albumov", - "backup_controller_page_background_app_refresh_disabled_content": "Omogočite osveževanje aplikacij v ozadju v Nastavitve > Splošno > Osvežitev aplikacij v ozadju, če želite uporabiti varnostno kopiranje v ozadju.", - "backup_controller_page_background_app_refresh_disabled_title": "Osveževanje aplikacije v ozadju je onemogočeno", - "backup_controller_page_background_app_refresh_enable_button_text": "Pojdi na nastavitve", - "backup_controller_page_background_battery_info_link": "Pokaži mi kako", - "backup_controller_page_background_battery_info_message": "Za najboljšo izkušnjo varnostnega kopiranja v ozadju onemogočite vse optimizacije baterije, ki omejujejo dejavnost v ozadju za Immich.\n\nKer je to odvisno od naprave, poiščite zahtevane informacije za proizvajalca vaše naprave.", - "backup_controller_page_background_battery_info_ok": "V redu", - "backup_controller_page_background_battery_info_title": "Optimizacije baterije", - "backup_controller_page_background_charging": "Samo med polnjenjem", - "backup_controller_page_background_configure_error": "Storitve v ozadju ni bilo mogoče nastaviti", - "backup_controller_page_background_delay": "Zakasni varnostno kopiranje novih sredstev: {duration}", - "backup_controller_page_background_description": "Vklopite storitev v ozadju za samodejno varnostno kopiranje novih sredstev, ne da bi morali odpreti aplikacijo", - "backup_controller_page_background_is_off": "Samodejno varnostno kopiranje v ozadju je izklopljeno", - "backup_controller_page_background_is_on": "Samodejno varnostno kopiranje v ozadju je vklopljeno", - "backup_controller_page_background_turn_off": "Izklopi storitev v ozadju", - "backup_controller_page_background_turn_on": "Vklopi storitev v ozadju", - "backup_controller_page_background_wifi": "Samo na Wi-Fi", - "backup_controller_page_backup": "Varnostna kopija", - "backup_controller_page_backup_selected": "Izbrano: ", - "backup_controller_page_backup_sub": "Varnostno kopirane fotografije in videoposnetki", - "backup_controller_page_created": "Ustvarjeno: {date}", - "backup_controller_page_desc_backup": "Vklopite varnostno kopiranje v ospredju za samodejno nalaganje novih sredstev na strežnik, ko odprete aplikacijo.", - "backup_controller_page_excluded": "Izključeno: ", - "backup_controller_page_failed": "Neuspešno ({count})", - "backup_controller_page_filename": "Ime datoteke: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Informacija o varnostnem kopiranju", - "backup_controller_page_none_selected": "Noben izbran", - "backup_controller_page_remainder": "Ostanek", - "backup_controller_page_remainder_sub": "Še preostale fotografije in videoposnetki za varnostno kopiranje iz izbora", - "backup_controller_page_server_storage": "Shramba strežnika", - "backup_controller_page_start_backup": "Zaženi varnostno kopiranje", - "backup_controller_page_status_off": "Samodejno varnostno kopiranje v ospredju je izklopljeno", - "backup_controller_page_status_on": "Samodejno varnostno kopiranje v ospredju je vklopljeno", - "backup_controller_page_storage_format": "Uporabljeno {used} od {total}", - "backup_controller_page_to_backup": "Albumi, ki bodo varnostno kopirani", - "backup_controller_page_total_sub": "Vse edinstvene fotografije in videi iz izbranih albumov", - "backup_controller_page_turn_off": "Izklopite varnostno kopiranje v ospredju", - "backup_controller_page_turn_on": "Vklopite varnostno kopiranje v ospredju", - "backup_controller_page_uploading_file_info": "Nalaganje podatkov o datoteki", - "backup_err_only_album": "Edinega albuma ni mogoče odstraniti", - "backup_error_sync_failed": "Sinhronizacija ni uspela. Varnostne kopije ni mogoče obdelati.", - "backup_info_card_assets": "sredstva", - "backup_manual_cancelled": "Preklicano", - "backup_manual_in_progress": "Nalaganje že poteka. Poskusite čez nekaj časa", - "backup_manual_success": "Uspeh", - "backup_manual_title": "Status nalaganja", - "backup_options": "Možnosti varnostnega kopiranja", - "backup_options_page_title": "Možnosti varnostne kopije", - "backup_setting_subtitle": "Upravljaj nastavitve nalaganja v ozadju in ospredju", - "backup_settings_subtitle": "Upravljanje nastavitev nalaganja", - "backup_upload_details_page_more_details": "Dotaknite se za več podrobnosti", - "backward": "Nazaj", - "biometric_auth_enabled": "Biometrična avtentikacija omogočena", - "biometric_locked_out": "Biometrična avtentikacija vam je onemogočena", - "biometric_no_options": "Biometrične možnosti niso na voljo", - "biometric_not_available": "Biometrično preverjanje pristnosti ni na voljo v tej napravi", - "birthdate_saved": "Datum rojstva je uspešno shranjen", - "birthdate_set_description": "Datum rojstva se uporablja za izračun starosti te osebe v času fotografije.", - "blurred_background": "Zamegljeno ozadje", - "bugs_and_feature_requests": "Napake in zahteve po funkcijah", - "build": "Različica", - "build_image": "Različica slike", - "bulk_delete_duplicates_confirmation": "Ali ste prepričani, da želite množično izbrisati {count, plural, one {# dvojnik} two {# dvojnika} few {# dvojnike} other {# dvojnikov}}? S tem boste ohranili največje sredstvo vsake skupine in trajno izbrisali vse druge dvojnike. Tega dejanja ne morete razveljaviti!", - "bulk_keep_duplicates_confirmation": "Ali ste prepričani, da želite obdržati {count, plural, one {# dvojnik} two {# dvojnika} few {# dvojnike} other {# dvojnikov}}? S tem boste razrešili vse podvojene skupine, ne da bi karkoli izbrisali.", - "bulk_trash_duplicates_confirmation": "Ali ste prepričani, da želite množično vreči v smetnjak {count, plural, one {# dvojnik} two {# dvojnika} few {# dvojnike} other {# dvojnikov}}? S tem boste obdržali največje sredstvo vsake skupine in odstranili vse druge dvojnike.", - "buy": "Kupi Immich", - "cache_settings_clear_cache_button": "Počisti predpomnilnik", - "cache_settings_clear_cache_button_title": "Počisti predpomnilnik aplikacije. To bo znatno vplivalo na delovanje aplikacije, dokler se predpomnilnik ne obnovi.", - "cache_settings_duplicated_assets_clear_button": "POČISTI", - "cache_settings_duplicated_assets_subtitle": "Fotografije in videoposnetki, ki so prezrti s strani aplikacije", - "cache_settings_duplicated_assets_title": "Podvojena sredstva ({count})", - "cache_settings_statistics_album": "Sličice knjižnice", - "cache_settings_statistics_full": "Izvirne slike", - "cache_settings_statistics_shared": "Sličice albuma v skupni rabi", - "cache_settings_statistics_thumbnail": "Sličice", - "cache_settings_statistics_title": "Uporaba predpomnilnika", - "cache_settings_subtitle": "Nadzirajte delovanje predpomnjenja mobilne aplikacije Immich", - "cache_settings_tile_subtitle": "Nadzoruj vedenje lokalnega shranjevanja", - "cache_settings_tile_title": "Lokalna shramba", - "cache_settings_title": "Nastavitve predpomnjenja", - "camera": "Kamera", - "camera_brand": "Znamka kamere", - "camera_model": "Model kamere", - "cancel": "Prekliči", - "cancel_search": "Prekliči iskanje", - "canceled": "Preklicano", - "canceling": "Preklic", - "cannot_merge_people": "Oseb ni mogoče združiti", - "cannot_undo_this_action": "Tega dejanja ne morete razveljaviti!", - "cannot_update_the_description": "Opisa ni mogoče posodobiti", - "cast": "Pretakaj", - "cast_description": "Konfigurirajte razpoložljive cilje za predvajanje", - "change_date": "Spremeni datum", - "change_description": "Spremeni opis", - "change_display_order": "Spremeni vrstni red prikaza", - "change_expiration_time": "Spremeni čas poteka", - "change_location": "Spremeni lokacijo", - "change_name": "Spremeni ime", - "change_name_successfully": "Ime uspešno spremenjeno", - "change_password": "Zamenjaj geslo", - "change_password_description": "To je bodisi prvič, da se vpisujete v sistem ali pa je bila podana zahteva za spremembo vašega gesla. Spodaj vnesite novo geslo.", - "change_password_form_confirm_password": "Potrdi geslo", - "change_password_form_description": "Pozdravljeni {name},\n\nTo je bodisi prvič, da se vpisujete v sistem ali pa je bila podana zahteva za spremembo vašega gesla. Spodaj vnesite novo geslo.", - "change_password_form_log_out": "Odjava vseh drugih naprav", - "change_password_form_log_out_description": "Priporočljivo je, da se odjavite iz vseh drugih naprav", - "change_password_form_new_password": "Novo geslo", - "change_password_form_password_mismatch": "Gesli se ne ujemata", - "change_password_form_reenter_new_password": "Znova vnesi novo geslo", - "change_pin_code": "Spremeni PIN kodo", - "change_trigger": "Spremeni sprožilec", - "change_trigger_prompt": "Ali ste prepričani, da želite spremeniti sprožilec? S tem boste odstranili vsa obstoječa dejanja in filtre.", - "change_your_password": "Spremenite geslo", - "changed_visibility_successfully": "Uspešno spremenjena vidnost", - "charging": "Polnjenje", - "charging_requirement_mobile_backup": "Za varnostno kopiranje v ozadju je potrebno polnjenje naprave", - "check_corrupt_asset_backup": "Preverite poškodovane varnostne kopije sredstev", - "check_corrupt_asset_backup_button": "Izvedi preverjanje", - "check_corrupt_asset_backup_description": "To preverjanje zaženite samo prek omrežja Wi-Fi in potem, ko so vsa sredstva varnostno kopirana. Postopek lahko traja nekaj minut.", - "check_logs": "Preverite dnevnike", - "checksum": "Kontrolna vsota", - "choose_matching_people_to_merge": "Izberite ujemajoče se osebe za združitev", - "city": "Mesto", - "cleanup_confirm_description": "Immich je našel {count} sredstev (ustvarjenih pred {date}), ki so varno varnostno shranjena na strežniku. Ali želiš odstraniti lokalne kopije iz te naprave?", - "cleanup_confirm_prompt_title": "Odstrani iz te naprave?", - "cleanup_deleted_assets": "{count} sredstev premaknjenih v koš", - "cleanup_deleting": "Premikanje v koš...", - "cleanup_found_assets": "Najdenih je bilo {count} varnostno kopiranih sredstev", - "cleanup_found_assets_with_size": "Najdenih {count} varnostno kopiranih sredstev ({size})", - "cleanup_icloud_shared_albums_excluded": "Skupni albumi iCloud so izključeni iz skeniranja", - "cleanup_no_assets_found": "Ni najdenih sredstev, ki bi ustrezala zgornjim kriterijem. Funkcija \"Sprosti prostor\" lahko odstrani samo sredstva, ki so bila varnostno kopirana na strežnik", - "cleanup_preview_title": "Sredstva za odstranitev ({count})", - "cleanup_step3_description": "Poiščite varnostne kopije sredstev, ki ustrezajo vašemu datumu, in ohranite nastavitve.", - "cleanup_step4_summary": "{count} {count, plural, one {element (ustvarjen} two {elementa (ustvarjena} few {elementi (ustvarjeni} other {elementov (ustvarjenih}} pred {date}) za odstranitev iz vaše lokalne naprave. Fotografije bodo še naprej dostopne iz aplikacije Immich.", - "cleanup_trash_hint": "Če želite v celoti sprostiti prostor za shranjevanje, odprite aplikacijo sistemske galerije in izpraznite koš", - "clear": "Počisti", - "clear_all": "Počisti vse", - "clear_all_recent_searches": "Počisti vsa nedavna iskanja", - "clear_file_cache": "Počisti predpomnilnik datotek", - "clear_message": "Počisti sporočilo", - "clear_value": "Počisti vrednost", - "client_cert_dialog_msg_confirm": "V redu", - "client_cert_enter_password": "Vnesi geslo", - "client_cert_import": "Uvozi", - "client_cert_import_success_msg": "Potrdilo odjemalca je uvoženo", - "client_cert_invalid_msg": "Neveljavna datoteka potrdila ali napačno geslo", - "client_cert_remove_msg": "Potrdilo odjemalca je odstranjeno", - "client_cert_subtitle": "Podpira samo format PKCS12 (.p12, .pfx). Uvoz/odstranitev potrdila je na voljo samo pred prijavo", - "client_cert_title": "Potrdilo odjemalca SSL [POSKUSNO]", - "clockwise": "V smeri urinega kazalca", - "close": "Zapri", - "collapse": "Strni", - "collapse_all": "Strni vse", - "color": "Barva", - "color_theme": "Barva teme", - "command": "Ukaz", - "comment_deleted": "Komentar izbrisan", - "comment_options": "Možnosti komentiranja", - "comments_and_likes": "Komentarji in všečki", - "comments_are_disabled": "Komentarji so onemogočeni", - "common_create_new_album": "Ustvari nov album", - "completed": "Končano", - "confirm": "Potrdi", - "confirm_admin_password": "Potrdite skrbniško geslo", - "confirm_delete_face": "Ali ste prepričani, da želite izbrisati obraz osebe {name} iz sredstva?", - "confirm_delete_shared_link": "Ali ste prepričani, da želite izbrisati to skupno povezavo?", - "confirm_keep_this_delete_others": "Vsa druga sredstva v skladu bodo izbrisana, razen tega sredstva. Ste prepričani, da želite nadaljevati?", - "confirm_new_pin_code": "Potrdi novo PIN kodo", - "confirm_password": "Potrdi geslo", - "confirm_tag_face": "Ali želite označiti ta obraz kot {name}?", - "confirm_tag_face_unnamed": "Ali želite označiti ta obraz?", - "connected_device": "Povezana naprava", - "connected_to": "Povezan s", - "contain": "Vsebuje", - "context": "Kontekst", - "continue": "Nadaljuj", - "control_bottom_app_bar_create_new_album": "Ustvari nov album", - "control_bottom_app_bar_delete_from_immich": "Izbriši iz Immicha", - "control_bottom_app_bar_delete_from_local": "Izbriši iz naprave", - "control_bottom_app_bar_edit_location": "Uredi lokacijo", - "control_bottom_app_bar_edit_time": "Uredi datum & uro", - "control_bottom_app_bar_share_link": "Deli povezavo", - "control_bottom_app_bar_share_to": "Deli s/z", - "control_bottom_app_bar_trash_from_immich": "Prestavi v smetnjak", - "copied_image_to_clipboard": "Slika kopirana v odložišče.", - "copied_to_clipboard": "Kopirano v odložišče!", - "copy_error": "Napaka pri kopiranju", - "copy_file_path": "Kopiraj pot datoteke", - "copy_image": "Kopiraj sliko", - "copy_link": "Kopiraj povezavo", - "copy_link_to_clipboard": "Kopiraj povezavo v odložišče", - "copy_password": "Kopiraj geslo", - "copy_to_clipboard": "Kopiraj v odložišče", - "country": "Država", - "cover": "Prekrij", - "covers": "Prekrivanja", - "create": "Ustvari", - "create_album": "Ustvari album", - "create_album_page_untitled": "Brez naslova", - "create_api_key": "Ustvari API ključ", - "create_first_workflow": "Ustvari prvi potek dela", - "create_library": "Ustvari knjižnico", - "create_link": "Ustvari povezavo", - "create_link_to_share": "Ustvari povezavo za skupno rabo", - "create_link_to_share_description": "Omogoči vsem s povezavo ogled izbranih fotografij", - "create_new": "USTVARI NOVEGA", - "create_new_person": "Ustvari novo osebo", - "create_new_person_hint": "Dodeli izbrana sredstva novi osebi", - "create_new_user": "Ustvari novega uporabnika", - "create_shared_album_page_share_add_assets": "DODAJ SREDSTVA", - "create_shared_album_page_share_select_photos": "Izberi fotografije", - "create_shared_link": "Ustvari deljeno povezavo", - "create_tag": "Ustvari oznako", - "create_tag_description": "Ustvarite novo oznako. Za ugnezdene oznake vnesite celotno pot oznake, vključno s poševnicami.", - "create_user": "Ustvari uporabnika", - "create_workflow": "Ustvari potek dela", - "created": "Ustvarjeno", - "created_at": "Ustvarjeno", - "creating_linked_albums": "Ustvarjanje povezanih albumov ...", - "crop": "Obrezovanje", - "crop_aspect_ratio_fixed": "Fiksno", - "crop_aspect_ratio_free": "Poljubno", - "crop_aspect_ratio_original": "Izvirno", - "curated_object_page_title": "Stvari", - "current_device": "Trenutna naprava", - "current_pin_code": "Trenutna PIN koda", - "current_server_address": "Trenutni naslov strežnika", - "custom_date": "Datum po meri", - "custom_locale": "Jezik po meri", - "custom_locale_description": "Oblikujte datume in številke glede na jezik in regijo", - "custom_url": "URL po meri", - "cutoff_date_description": "Shranite fotografije iz zadnjega…", - "cutoff_day": "{count, plural, one {dan} other {dni}}", - "cutoff_year": "{count, plural, one {leto} two {leti} few {leta} other {let}}", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "E, MMM dd, yyyy", - "dark": "Temno", - "dark_theme": "Preklopi temno temo", - "date": "Datum", - "date_after": "Datum po", - "date_and_time": "Datum in ura", - "date_before": "Datum pred", - "date_format": "E, LLL d, y • h:mm a", - "date_of_birth_saved": "Datum rojstva je uspešno shranjen", - "date_range": "Časovno obdobje", - "day": "Dan", - "days": "Dnevi", - "deduplicate_all": "Odstrani vse podvojene", - "deduplication_criteria_1": "Velikost slike v bajtih", - "deduplication_criteria_2": "Število podatkov EXIF", - "deduplication_info": "Informacije o zaznavanju dvojnikov", - "deduplication_info_description": "Za samodejno vnaprejšnjo izbiro sredstev in množično odstranjevanje dvojnikov si ogledamo:", - "default_locale": "Privzeti jezik", - "default_locale_description": "Oblikujte datume in številke glede na lokalne nastavitve brskalnika", - "delete": "Izbriši", - "delete_action_confirmation_message": "Ali ste prepričani, da želite izbrisati to sredstvo? S tem dejanjem boste sredstvo premaknili v koš na strežniku in vas pozvali, ali ga želite izbrisati lokalno", - "delete_action_prompt": "izbrisano {count}", - "delete_album": "Izbriši album", - "delete_api_key_prompt": "Ali ste prepričani, da želite izbrisati ta API ključ?", - "delete_dialog_alert": "Ti elementi bodo trajno izbrisani iz Immicha in vaše naprave", - "delete_dialog_alert_local": "Ti elementi bodo trajno odstranjeni iz vaše naprave, vendar bodo še vedno na voljo na strežniku Immich", - "delete_dialog_alert_local_non_backed_up": "Nekateri elementi niso varnostno kopirani v Immich in bodo trajno odstranjeni iz vaše naprave", - "delete_dialog_alert_remote": "Ti elementi bodo trajno izbrisani iz strežnika Immich", - "delete_dialog_ok_force": "Vseeno izbriši", - "delete_dialog_title": "Trajno izbriši", - "delete_duplicates_confirmation": "Ali ste prepričani, da želite trajno izbrisati te dvojnike?", - "delete_face": "Izbriši obraz", - "delete_key": "Izbriši ključ", - "delete_library": "Izbriši knjižnico", - "delete_link": "Izbriši povezavo", - "delete_local_action_prompt": "{count} izbrisano lokalno", - "delete_local_dialog_ok_backed_up_only": "Izbriši samo kar je varnostno kopirano", - "delete_local_dialog_ok_force": "Vseeno izbriši", - "delete_others": "Izbriši ostale", - "delete_permanently": "Izbriši trajno", - "delete_permanently_action_prompt": "{count} trajno izbrisano", - "delete_shared_link": "Izbriši povezavo skupne rabe", - "delete_shared_link_dialog_title": "Izbriši povezavo skupne rabe", - "delete_tag": "Izbriši oznako", - "delete_tag_confirmation_prompt": "Ali ste prepričani, da želite izbrisati oznako {tagName}?", - "delete_user": "Izbriši uporabnika", - "deleted_shared_link": "Izbrisana skupna povezava", - "deletes_missing_assets": "Izbriše sredstva, ki manjkajo na disku", - "description": "Opis", - "description_input_hint_text": "Dodaj opis ...", - "description_input_submit_error": "Napaka pri posodabljanju opisa, preverite dnevnik za več podrobnosti", - "deselect_all": "Prekliči vse", - "details": "Podrobnosti", - "direction": "Usmeritev", - "disable": "Onemogoči", - "disabled": "Onemogočeno", - "disallow_edits": "Onemogoči urejanje", - "discord": "Discord", - "discover": "Odkrij", - "discovered_devices": "Odkrite naprave", - "dismiss_all_errors": "Opusti vse napake", - "dismiss_error": "Opusti napako", - "display_options": "Možnosti prikaza", - "display_order": "Vrstni red prikaza", - "display_original_photos": "Prikaži izvirne fotografije", - "display_original_photos_setting_description": "Pri ogledu sredstva raje prikažite izvirno fotografijo kot sličice, če je izvirno sredstvo združljivo s spletom. To lahko povzroči počasnejše hitrosti prikaza fotografij.", - "do_not_show_again": "Ne pokaži več tega sporočila", - "documentation": "Dokumentacija", - "done": "Končano", - "download": "Prenesi", - "download_action_prompt": "Prenašanje {count} sredstev", - "download_canceled": "Prenos preklican", - "download_complete": "Prenos končan", - "download_enqueue": "Prenos v čakalni vrsti", - "download_error": "Napaka pri prenosu", - "download_failed": "Prenos ni uspel", - "download_finished": "Prenos zaključen", - "download_include_embedded_motion_videos": "Vdelani videoposnetki", - "download_include_embedded_motion_videos_description": "Videoposnetke, vdelane v fotografije gibanja, vključite kot ločeno datoteko", - "download_notfound": "Prenosa ni bilo mogoče najti", - "download_original": "Prenesi izvirnik", - "download_paused": "Prenos zaustavljen", - "download_settings": "Prenos", - "download_settings_description": "Upravljajte nastavitve, povezane s prenosom sredstev", - "download_started": "Prenos se je začel", - "download_sucess": "Prenos uspešen", - "download_sucess_android": "Medij je bil prenesen v DCIM/Immich", - "download_waiting_to_retry": "Čakam na ponovni poskus", - "downloading": "Prenašanje", - "downloading_asset_filename": "Prenašanje sredstva {filename}", - "downloading_from_icloud": "Prenos iz iClouda", - "downloading_media": "Prenašanje medijev", - "drop_files_to_upload": "Spustite datoteke kamor koli, da jih naložite", - "duplicates": "Dvojniki", - "duplicates_description": "Razrešite vsako skupino tako, da navedete, kateri so dvojniki, če obstajajo", - "duration": "Trajanje", - "edit": "Uredi", - "edit_album": "Uredi album", - "edit_avatar": "Uredi avatar", - "edit_birthday": "Uredi rojstni dan", - "edit_date": "Uredi datum", - "edit_date_and_time": "Uredi datum in uro", - "edit_date_and_time_action_prompt": "{count} datum in ura urejeno", - "edit_date_and_time_by_offset": "Spremeni datum z odmikom", - "edit_date_and_time_by_offset_interval": "Novo obdobje: {from} - {to}", - "edit_description": "Uredi opis", - "edit_description_prompt": "Izberite nov opis:", - "edit_exclusion_pattern": "Uredi vzorec izključitve", - "edit_faces": "Uredi obraze", - "edit_key": "Uredi ključ", - "edit_link": "Uredi povezavo", - "edit_location": "Uredi lokacijo", - "edit_location_action_prompt": "urejenih {count} lokacij", - "edit_location_dialog_title": "Lokacija", - "edit_name": "Uredi ime", - "edit_people": "Uredi osebe", - "edit_tag": "Uredi oznako", - "edit_title": "Uredi naslov", - "edit_user": "Uredi uporabnika", - "edit_workflow": "Urejanje poteka dela", - "editor": "Urejevalnik", - "editor_close_without_save_prompt": "Spremembe ne bodo shranjene", - "editor_close_without_save_title": "Zapri urejevalnik?", - "editor_confirm_reset_all_changes": "Ali ste prepričani, da želite ponastaviti vse spremembe?", - "editor_flip_horizontal": "Obrni vodoravno", - "editor_flip_vertical": "Obrni navpično", - "editor_orientation": "Usmerjenost", - "editor_reset_all_changes": "Ponastavi spremembe", - "editor_rotate_left": "Zavrtite za 90° v levo", - "editor_rotate_right": "Zavrtite za 90° v desno", - "email": "E-pošta", - "email_notifications": "Obvestila po e-pošti", - "empty_folder": "Ta mapa je prazna", - "empty_trash": "Izprazni smetnjak", - "empty_trash_confirmation": "Ste prepričani, da želite izprazniti smetnjak? S tem boste iz Immicha trajno odstranili vsa sredstva v smetnjaku.\nTega dejanja ne morete razveljaviti!", - "enable": "Omogoči", - "enable_backup": "Omogoči varnostno kopiranje", - "enable_biometric_auth_description": "Vnesite svojo PIN kodo, da omogočite biometrično preverjanje pristnosti", - "enabled": "Omogočeno", - "end_date": "Končni datum", - "enqueued": "V čakalni vrsti", - "enter_wifi_name": "Vnesi Wi-Fi ime", - "enter_your_pin_code": "Vnesite svojo PIN kodo", - "enter_your_pin_code_subtitle": "Vnesite svojo PIN kodo za dostop do zaklenjene mape", - "error": "Napaka", - "error_change_sort_album": "Vrstnega reda albuma ni bilo mogoče spremeniti", - "error_delete_face": "Napaka pri brisanju obraza iz sredstva", - "error_getting_places": "Napaka pri pridobivanju mest", - "error_loading_albums": "Napaka pri nalaganju albumov", - "error_loading_image": "Napaka pri nalaganju slike", - "error_loading_partners": "Napaka pri nalaganju partnerjev: {error}", - "error_retrieving_asset_information": "Napaka pri pridobivanju podatkov o sredstvu", - "error_saving_image": "Napaka: {error}", - "error_tag_face_bounding_box": "Napaka pri označevanju obraza - ni mogoče pridobiti koordinat omejevalnega okvirja", - "error_title": "Napaka - nekaj je šlo narobe", - "error_while_navigating": "Napaka pri navigaciji do sredstva", - "errors": { - "cannot_navigate_next_asset": "Ni mogoče krmariti do naslednjega sredstva", - "cannot_navigate_previous_asset": "Ni mogoče krmariti na prejšnje sredstvo", - "cant_apply_changes": "Sprememb ni mogoče uporabiti", - "cant_change_activity": "Ni mogoče {enabled, select, true {disable} other {enable}} dejavnosti", - "cant_change_asset_favorite": "Ni možno spremeniti priljubljeno za sredstvo", - "cant_change_metadata_assets_count": "Ni mogoče spremeniti metapodatkov za {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "cant_get_faces": "Ne morem dobiti obrazov", - "cant_get_number_of_comments": "Ni mogoče pridobiti števila komentarjev", - "cant_search_people": "Ni mogoče iskati ljudi", - "cant_search_places": "Ne morem iskati mest", - "error_adding_assets_to_album": "Napaka pri dodajanju sredstev v album", - "error_adding_users_to_album": "Napaka pri dodajanju uporabnikov v album", - "error_deleting_shared_user": "Napaka pri brisanju uporabnika v skupni rabi", - "error_downloading": "Napaka pri prenosu datoteke {filename}", - "error_hiding_buy_button": "Napaka pri skrivanju gumba za nakup", - "error_removing_assets_from_album": "Napaka pri odstranjevanju sredstev iz albuma, preverite konzolo za več podrobnosti", - "error_selecting_all_assets": "Napaka pri izbiri vseh sredstev", - "exclusion_pattern_already_exists": "Ta vzorec izključitve že obstaja.", - "failed_to_create_album": "Albuma ni bilo mogoče ustvariti", - "failed_to_create_shared_link": "Povezave v skupni rabi ni bilo mogoče ustvariti", - "failed_to_edit_shared_link": "Povezave v skupni rabi ni bilo mogoče urediti", - "failed_to_get_people": "Oseb ni bilo mogoče pridobiti", - "failed_to_keep_this_delete_others": "Tega sredstva ni bilo mogoče obdržati in izbrisati ostalih sredstev", - "failed_to_load_asset": "Sredstva ni bilo mogoče naložiti", - "failed_to_load_assets": "Sredstev ni bilo mogoče naložiti", - "failed_to_load_notifications": "Nalaganje obvestil ni uspelo", - "failed_to_load_people": "Oseb ni bilo mogoče naložiti", - "failed_to_remove_product_key": "Ključa izdelka ni bilo mogoče odstraniti", - "failed_to_reset_pin_code": "Ponastavitev PIN-kode ni uspela", - "failed_to_stack_assets": "Zlaganje sredstev ni uspelo", - "failed_to_unstack_assets": "Sredstev ni bilo mogoče razložiti", - "failed_to_update_notification_status": "Stanja obvestila ni bilo mogoče posodobiti", - "incorrect_email_or_password": "Napačen e-poštni naslov ali geslo", - "library_folder_already_exists": "Ta pot uvoza že obstaja.", - "paths_validation_failed": "{paths, plural, one {# pot ni bila uspešno preverjena} two {# poti nista bili uspešno preverjeni} few {# poti niso bile uspešno preverjene} other {# poti ni bilo uspešno preverjenih}}", - "profile_picture_transparent_pixels": "Profilne slike ne smejo imeti prosojnih slikovnih pik. Povečajte in/ali premaknite sliko.", - "quota_higher_than_disk_size": "Nastavili ste kvoto, ki je višja od velikosti diska", - "something_went_wrong": "Nekaj je šlo narobe", - "unable_to_add_album_users": "Uporabnikov ni mogoče dodati v album", - "unable_to_add_assets_to_shared_link": "Povezavi v skupni rabi ni mogoče dodati sredstev", - "unable_to_add_comment": "Ni mogoče dodati komentarja", - "unable_to_add_exclusion_pattern": "Vzorca izključitve ni mogoče dodati", - "unable_to_add_partners": "Partnerjev ni mogoče dodati", - "unable_to_add_remove_archive": "Ni mogoče {archived, select, true {odstraniti sredstva iz} other {ter dodati sredstvo v}} archive", - "unable_to_add_remove_favorites": "Ni mogoče {favorite, select, true {dodati sredstva v} other {ter ga odstraniti iz}} priljubljenih", - "unable_to_archive_unarchive": "Ni mogoče {archived, select, true {arhivirano} other {nearhivirano}}", - "unable_to_change_album_user_role": "Ni mogoče spremeniti vloge uporabnika albuma", - "unable_to_change_date": "Datuma ni mogoče spremeniti", - "unable_to_change_description": "Opisa ni mogoče spremeniti", - "unable_to_change_favorite": "Ni mogoče spremeniti priljubljenega za sredstvo", - "unable_to_change_location": "Lokacije ni mogoče spremeniti", - "unable_to_change_password": "Gesla ni mogoče spremeniti", - "unable_to_change_visibility": "Ni mogoče spremeniti vidnosti za {count, plural, one {# osebo} two {# osebi} few {# osebe} other {# oseb}}", - "unable_to_complete_oauth_login": "Prijave OAuth ni mogoče dokončati", - "unable_to_connect": "Ni mogoče vzpostaviti povezave", - "unable_to_copy_to_clipboard": "Ni mogoče kopirati v odložišče, preverite, ali dostopate do strani prek https", - "unable_to_create": "Ni mogoče ustvariti poteka dela", - "unable_to_create_admin_account": "Ni mogoče ustvariti skrbniškega računa", - "unable_to_create_api_key": "Ni mogoče ustvariti novega API ključa", - "unable_to_create_library": "Ni mogoče ustvariti knjižnice", - "unable_to_create_user": "Uporabnika ni mogoče ustvariti", - "unable_to_delete_album": "Albuma ni mogoče izbrisati", - "unable_to_delete_asset": "Sredstva ni mogoče izbrisati", - "unable_to_delete_assets": "Napaka pri brisanju sredstev", - "unable_to_delete_exclusion_pattern": "Vzorca izključitve ni mogoče izbrisati", - "unable_to_delete_shared_link": "Povezave v skupni rabi ni mogoče izbrisati", - "unable_to_delete_user": "Uporabnika ni mogoče izbrisati", - "unable_to_delete_workflow": "Poteka dela ni mogoče izbrisati", - "unable_to_download_files": "Ni mogoče prenesti datotek", - "unable_to_edit_exclusion_pattern": "Vzorca izključitve ni mogoče urediti", - "unable_to_empty_trash": "Smetnjaka ni mogoče izprazniti", - "unable_to_enter_fullscreen": "Celozaslonski način ni mogoč", - "unable_to_exit_fullscreen": "Ni mogoče zapreti celozaslonskega načina", - "unable_to_get_comments_number": "Ni mogoče pridobiti števila komentarjev", - "unable_to_get_shared_link": "Povezave v skupni rabi ni bilo mogoče pridobiti", - "unable_to_hide_person": "Osebe ni mogoče skriti", - "unable_to_link_motion_video": "Ni mogoče povezati videa gibanja", - "unable_to_link_oauth_account": "Računa OAuth ni mogoče povezati", - "unable_to_log_out_all_devices": "Ni mogoče odjaviti vseh naprav", - "unable_to_log_out_device": "Naprave ni mogoče odjaviti", - "unable_to_login_with_oauth": "Prijava z OAuth ni mogoča", - "unable_to_play_video": "Videoposnetka ni mogoče predvajati", - "unable_to_reassign_assets_existing_person": "Ni mogoče dodeliti sredstev {name, select, null {obstoječi osebi} other {{name}}}", - "unable_to_reassign_assets_new_person": "Ponovna dodelitev sredstev novi osebi ni možna", - "unable_to_refresh_user": "Uporabnika ni mogoče osvežiti", - "unable_to_remove_album_users": "Uporabnikov ni mogoče odstraniti iz albuma", - "unable_to_remove_api_key": "Ključa API ni mogoče odstraniti", - "unable_to_remove_assets_from_shared_link": "Ni mogoče odstraniti sredstev iz skupne povezave", - "unable_to_remove_library": "Knjižnice ni mogoče odstraniti", - "unable_to_remove_partner": "Partnerja ni mogoče odstraniti", - "unable_to_remove_reaction": "Reakcije ni mogoče odstraniti", - "unable_to_reset_password": "Gesla ni mogoče ponastaviti", - "unable_to_reset_pin_code": "PIN kode ni mogoče ponastaviti", - "unable_to_resolve_duplicate": "Dvojnika ni mogoče razrešiti", - "unable_to_restore_assets": "Sredstev ni mogoče obnoviti", - "unable_to_restore_trash": "Smetnjaka ni mogoče obnoviti", - "unable_to_restore_user": "Uporabnika ni mogoče obnoviti", - "unable_to_save_album": "Albuma ni mogoče shraniti", - "unable_to_save_api_key": "Ključa API ni mogoče shraniti", - "unable_to_save_date_of_birth": "Datuma rojstva ni mogoče shraniti", - "unable_to_save_name": "Imena ni mogoče shraniti", - "unable_to_save_profile": "Profila ni mogoče shraniti", - "unable_to_save_settings": "Nastavitev ni mogoče shraniti", - "unable_to_scan_libraries": "Ni mogoče pregledati knjižnic", - "unable_to_scan_library": "Knjižnice ni mogoče pregledati", - "unable_to_set_feature_photo": "Ni mogoče nastaviti glavne fotografije", - "unable_to_set_profile_picture": "Profilne slike ni mogoče nastaviti", - "unable_to_set_rating": "Ocene ni mogoče nastaviti", - "unable_to_submit_job": "Naloga ni mogoče oddati", - "unable_to_trash_asset": "Sredstva ni mogoče odstraniti v smetnjak", - "unable_to_unlink_account": "Povezave računa ni mogoče prekiniti", - "unable_to_unlink_motion_video": "Ni mogoče prekiniti povezave z videoposnetkom gibanja", - "unable_to_update_album_cover": "Naslovnice albuma ni mogoče posodobiti", - "unable_to_update_album_info": "Podatkov o albumu ni mogoče posodobiti", - "unable_to_update_library": "Knjižnice ni mogoče posodobiti", - "unable_to_update_location": "Lokacije ni mogoče posodobiti", - "unable_to_update_settings": "Nastavitev ni mogoče posodobiti", - "unable_to_update_timeline_display_status": "Ni mogoče posodobiti stanja prikaza časovnice", - "unable_to_update_user": "Uporabnika ni mogoče posodobiti", - "unable_to_update_workflow": "Poteka dela ni mogoče posodobiti", - "unable_to_upload_file": "Datoteke ni mogoče naložiti" - }, - "errors_text": "Napake", - "exclusion_pattern": "Vzorec izključitve", - "exif": "Exif", - "exif_bottom_sheet_description": "Dodaj opis..", - "exif_bottom_sheet_description_error": "Napaka pri posodabljanju opisa", - "exif_bottom_sheet_details": "PODROBNOSTI", - "exif_bottom_sheet_location": "LOKACIJA", - "exif_bottom_sheet_no_description": "Ni opisa", - "exif_bottom_sheet_people": "OSEBE", - "exif_bottom_sheet_person_add_person": "Dodaj ime", - "exit_slideshow": "Zapustite diaprojekcijo", - "expand_all": "Razširi vse", - "experimental_settings_new_asset_list_subtitle": "Delo v teku", - "experimental_settings_new_asset_list_title": "Omogoči eksperimentalno mrežo fotografij", - "experimental_settings_subtitle": "Uporabljajte na lastno odgovornost!", - "experimental_settings_title": "Eksperimentalno", - "expire_after": "Poteče čez", - "expired": "Poteklo", - "expires_date": "Poteče {date}", - "explore": "Razišči", - "explorer": "Raziskovalec", - "export": "Izvoz", - "export_as_json": "Izvozi kot JSON", - "export_database": "Izvoz baze podatkov", - "export_database_description": "Izvozite bazo podatkov SQLite", - "extension": "Razširitev", - "external": "Zunanji", - "external_libraries": "Zunanje knjižnice", - "external_network": "Zunanje omrežje", - "external_network_sheet_info": "Ko aplikacija ni v želenem omrežju Wi-Fi, se bo povezala s strežnikom prek prvega od spodnjih URL-jev, ki jih lahko doseže, začenši od zgoraj navzdol", - "face_unassigned": "Nedodeljen", - "failed": "Ni uspelo", - "failed_count": "Neuspešno: {count}", - "failed_to_authenticate": "Preverjanje pristnosti ni uspelo", - "failed_to_load_assets": "Sredstev ni bilo mogoče naložiti", - "failed_to_load_folder": "Mape ni bilo mogoče naložiti", - "favorite": "Priljubljen", - "favorite_action_prompt": "med priljubljene je dodanih {count}", - "favorite_or_unfavorite_photo": "Priljubljena ali nepriljubljena fotografija", - "favorites": "Priljubljene", - "favorites_page_no_favorites": "Ni priljubljenih sredstev", - "feature_photo_updated": "Funkcijska fotografija je posodobljena", - "features": "Funkcije", - "features_in_development": "Funkcije v razvoju", - "features_setting_description": "Upravljaj funkcije aplikacije", - "file_name_or_extension": "Ime ali končnica datoteke", - "file_size": "Velikost datoteke", - "filename": "Ime datoteke", - "filetype": "Vrsta datoteke", - "filter": "Filter", - "filter_description": "Pogoji za filtriranje ciljnih sredstev", - "filter_people": "Filtriraj ljudi", - "filter_places": "Filtriraj kraje", - "filters": "Filtri", - "find_them_fast": "Z iskanjem jih hitro poiščite po imenu", - "first": "Prvi", - "fix_incorrect_match": "Popravi napačno ujemanje", - "folder": "Mapa", - "folder_not_found": "Ne najdem mape", - "folders": "Mape", - "folders_feature_description": "Brskanje po pogledu mape za fotografije in videoposnetke v datotečnem sistemu", - "forgot_pin_code_question": "Ste pozabili PIN?", - "forward": "Naprej", - "free_up_space": "Sprostite prostor", - "free_up_space_description": "Varnostno kopirane fotografije in videoposnetke premaknite v koš v napravi, da sprostite prostor. Vaše kopije na strežniku ostanejo varne.", - "free_up_space_settings_subtitle": "Sprostite prostor v napravi", - "full_path": "Celotna pot: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ta funkcija za delovanje nalaga zunanje vire iz Googla.", - "general": "Splošno", - "geolocation_instruction_location": "Kliknite na sredstvo z GPS koordinatami, da uporabite njegovo lokacijo, ali pa izberite lokacijo neposredno na zemljevidu", - "get_help": "Poiščite pomoč", - "get_people_error": "Napaka pri pridobivanju oseb", - "get_wifiname_error": "Imena Wi-Fi ni bilo mogoče dobiti. Prepričajte se, da ste podelili potrebna dovoljenja in ste povezani v omrežje Wi-Fi", - "getting_started": "Začetek", - "go_back": "Pojdi nazaj", - "go_to_folder": "Pojdi na mapo", - "go_to_search": "Pojdi na iskanje", - "gps": "GPS", - "gps_missing": "Brez GPS-a", - "grant_permission": "Dodaj dovoljenje", - "group_albums_by": "Združi albume po ...", - "group_country": "Združi po državah", - "group_no": "Brez združevanja", - "group_owner": "Združi po lastniku", - "group_places_by": "Združi kraje po...", - "group_year": "Združi po letih", - "haptic_feedback_switch": "Uporabi haptičen odziv", - "haptic_feedback_title": "Haptičen odziv", - "has_quota": "Ima kvoto", - "hash_asset": "Zgoščeno sredstvo", - "hashed_assets": "Zgoščena sredstva", - "hashing": "Zgoščevanje", - "header_settings_add_header_tip": "Dodaj glavo", - "header_settings_field_validator_msg": "Vrednost ne sme biti prazna", - "header_settings_header_name_input": "Ime glave", - "header_settings_header_value_input": "Vrednost glave", - "headers_settings_tile_title": "Proxy glave po meri", - "height": "Višina", - "hi_user": "Živijo {name} ({email})", - "hide_all_people": "Skrij vse ljudi", - "hide_gallery": "Skrij galerijo", - "hide_named_person": "Skrij osebo {name}", - "hide_password": "Skrij geslo", - "hide_person": "Skrij osebo", - "hide_schema": "Skrij shemo", - "hide_text_recognition": "Skrij prepoznavanje besedila", - "hide_unnamed_people": "Skrij osebe brez imen", - "home_page_add_to_album_conflicts": "Dodanih {added} sredstev v album {album}. {failed} sredstev je že v albumu.", - "home_page_add_to_album_err_local": "Lokalnih sredstev še ni mogoče dodati v albume, preskakujem", - "home_page_add_to_album_success": "Dodanih {added} sredstev v album {album}.", - "home_page_album_err_partner": "Partnerskih sredstev še ni mogoče dodati v album, preskakujem", - "home_page_archive_err_local": "Lokalnih sredstev še ni mogoče arhivirati, preskakujem", - "home_page_archive_err_partner": "Sredstev partnerja ni mogoče arhivirati, preskakujem", - "home_page_building_timeline": "Gradnja časovnice", - "home_page_delete_err_partner": "Sredstev partnerja ni mogoče izbrisati, preskakujem", - "home_page_delete_remote_err_local": "Lokalna sredstva pri brisanju oddaljenega izbora, preskakujem", - "home_page_favorite_err_local": "Lokalnih sredstev še ni mogoče dodati med priljubljene, preskakujem", - "home_page_favorite_err_partner": "Sredstev partnerja še ni mogoče dodati med priljubljene, preskakujem", - "home_page_first_time_notice": "Če aplikacijo uporabljate prvič, se prepričajte, da ste izbrali rezervne albume, tako da lahko časovna premica zapolni fotografije in videoposnetke v albumih", - "home_page_locked_error_local": "Lokalnih sredstev ni mogoče premakniti v zaklenjeno mapo, preskakovanje", - "home_page_locked_error_partner": "Sredstev partnerjev ni mogoče premakniti v zaklenjeno mapo, preskakovanje", - "home_page_share_err_local": "Lokalnih sredstev ni mogoče deliti prek povezave, preskakujem", - "home_page_upload_err_limit": "Hkrati lahko naložite največ 30 sredstev, preskakujem", - "host": "Gostitelj", - "hour": "Ura", - "hours": "Ure", - "id": "ID", - "idle": "Nedejavnost", - "ignore_icloud_photos": "Ignoriraj fotografije iCloud", - "ignore_icloud_photos_description": "Fotografije, shranjene v iCloud, ne bodo naložene na strežnik Immich", - "image": "Slika", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} zajet {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} zajet z osebo {person1} dne {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} zajet z osebo {person1} in osebo {person2} dne {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} zajet z osebami {person1}, {person2}, in {person3} dne {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} zajet z osebami {person1}, {person2} in ostalimi {additionalCount, number} osebami dne {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} zajet/a v/na {city}, {country} dne {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} zajet/a/e v/na {city}, {country} s/z {person1} dne {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}}zajet/a/e/i v/na {city}, {country} s/z {person1} in {person2} dne {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} zajet/a/e/i v/na {city}, {country} s/z {person1}, {person2} in {person3} dne {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} zajet/a/e/i v/na {city}, {country} s/z {person1}, {person2} on ostalimi {additionalCount, number} osebami dne {date}", - "image_saved_successfully": "Slika shranjena", - "image_viewer_page_state_provider_download_started": "Prenos se je začel", - "image_viewer_page_state_provider_download_success": "Prenos je uspel", - "image_viewer_page_state_provider_share_error": "Napaka skupne rabe", - "immich_logo": "Immich logo", - "immich_web_interface": "Immich spletni vmesnik", - "import_from_json": "Uvoz iz JSON", - "import_path": "Pot uvoza", - "in_albums": "V {count, plural, one {# album} two {# albuma} few {# albume} other {# albumov}}", - "in_archive": "V arhiv", - "in_year": "V {year}", - "in_year_selector": "V", - "include_archived": "Vključi arhivirano", - "include_shared_albums": "Vključite skupne albume", - "include_shared_partner_assets": "Vključite partnerjeva skupna sredstva", - "individual_share": "Samostojna delitev", - "individual_shares": "Posamezne delitve", - "info": "Info", - "interval": { - "day_at_onepm": "Vsak dan ob 13h", - "hours": "Vsakih {hours, plural, one {uro} two {uri} few {ure} other {{hours, number} ur}}", - "night_at_midnight": "Vsak večer ob polnoči", - "night_at_twoam": "Vsako noč ob 2h" - }, - "invalid_date": "Neveljaven datum", - "invalid_date_format": "Neveljavna oblika datuma", - "invite_people": "Povabi ljudi", - "invite_to_album": "Povabi v album", - "ios_debug_info_fetch_ran_at": "Pridobivanje se je izvedlo {dateTime}", - "ios_debug_info_last_sync_at": "Zadnja sinhronizacija {dateTime}", - "ios_debug_info_no_processes_queued": "Ni čakalnih vrst v ozadju", - "ios_debug_info_no_sync_yet": "Ni bilo še izvedeno nobeno opravilo sinhronizacije v ozadju", - "ios_debug_info_processes_queued": "{count, plural, one {{count} proces v ozadju v čakalni vrsti} two {{count} procesa v ozadju v čakalni vrsti} few {{count} procesi v ozadju v čakalni vrsti} other {{count} procesov v ozadju v čakalni vrsti}}", - "ios_debug_info_processing_ran_at": "Obdelava je potekala {dateTime}", - "items_count": "{count, plural, one {# predmet} two {# predmeta} few {# predmeti} other {# predmetov}}", - "jobs": "Opravila", - "json_editor": "Urejevalnik JSON", - "json_error": "Napaka JSON", - "keep": "Obdrži", - "keep_albums": "Ohrani albume", - "keep_albums_count": "Ohrani {count} {count, plural, one {album} two {albuma} few {albume} other {albumov}}", - "keep_all": "Obdrži vse", - "keep_description": "Izberite, kaj ostane v napravi, ko sprostite prostor.", - "keep_favorites": "Obdrži priljubljene", - "keep_on_device": "Shrani v napravi", - "keep_on_device_hint": "Izberite elemente, ki jih želite shraniti v tej napravi", - "keep_this_delete_others": "Obdrži to, izbriši ostalo", - "keeping": "Ohranjanje: {items}", - "kept_this_deleted_others": "Obdrži to sredstvo in izbriši {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "keyboard_shortcuts": "Bližnjice na tipkovnici", - "language": "Jezik", - "language_no_results_subtitle": "Poskusite prilagoditi iskalni izraz", - "language_no_results_title": "Ni najdenih jezikov", - "language_search_hint": "Iskanje jezikov...", - "language_setting_description": "Izberite želeni jezik", - "large_files": "Velike datoteke", - "last": "Zadnji", - "last_months": "{count, plural, one {Zadnji mesec} two {Zadnja # meseca} few {Zadnje # mesece} other {Zadnjih # mesecev}}", - "last_seen": "Nazadnje viden", - "latest_version": "Najnovejša različica", - "latitude": "Zemljepisna širina", - "leave": "Zapusti", - "leave_album": "Zapusti album", - "lens_model": "Model leč", - "let_others_respond": "Naj drugi odgovorijo", - "level": "Raven", - "library": "Knjižnica", - "library_add_folder": "Dodaj mapo", - "library_edit_folder": "Uredi mapo", - "library_options": "Možnosti knjižnice", - "library_page_device_albums": "Albumi v napravi", - "library_page_new_album": "Nov album", - "library_page_sort_asset_count": "Število sredstev", - "library_page_sort_created": "Nazadnje ustvarjeno", - "library_page_sort_last_modified": "Nazadnje spremenjeno", - "library_page_sort_title": "Naslov albuma", - "licenses": "Licence", - "light": "Svetlo", - "like": "Všeč mi je", - "like_deleted": "Všeček izbrisan", - "link_motion_video": "Povezava videa gibanja", - "link_to_oauth": "Povezava do OAuth", - "linked_oauth_account": "Povezan račun OAuth", - "list": "Seznam", - "loading": "Nalaganje", - "loading_search_results_failed": "Nalaganje rezultatov iskanja ni uspelo", - "local": "Lokalno", - "local_asset_cast_failed": "Sredstva, ki niso naložena na strežnik, ni mogoče predvajati", - "local_assets": "Lokalna sredstva", - "local_id": "Lokalni ID", - "local_media_summary": "Povzetek lokalnih medijev", - "local_network": "Lokalno omrežje", - "local_network_sheet_info": "Aplikacija se bo povezala s strežnikom prek tega URL-ja, ko bo uporabljala navedeno omrežje Wi-Fi", - "location": "Lokacija", - "location_permission": "Dovoljenje za lokacijo", - "location_permission_content": "Za uporabo funkcije samodejnega preklapljanja potrebuje Immich dovoljenje za natančno lokacijo, da lahko prebere ime trenutnega omrežja Wi-Fi", - "location_picker_choose_on_map": "Izberi na zemljevidu", - "location_picker_latitude_error": "Vnesi veljavno zemljepisno širino", - "location_picker_latitude_hint": "Tukaj vnesi svojo zemljepisno širino", - "location_picker_longitude_error": "Vnesi veljavno zemljepisno dolžino", - "location_picker_longitude_hint": "Tukaj vnesi svojo zemljepisno dolžino", - "lock": "Zaklepanje", - "locked_folder": "Zaklenjena mapa", - "log_detail_title": "Podrobnosti dnevnika", - "log_out": "Odjava", - "log_out_all_devices": "Odjava vseh naprav", - "logged_in_as": "Prijavljen kot {user}", - "logged_out_all_devices": "Odjavljene so vse naprave", - "logged_out_device": "Odjavljena naprava", - "login": "Prijava", - "login_disabled": "Prijava je bila onemogočena", - "login_form_api_exception": "API napaka. Preverite URL strežnika in poskusite znova.", - "login_form_back_button_text": "Nazaj", - "login_form_email_hint": "vašemail@email.com", - "login_form_endpoint_hint": "http://ip-vašega-strežnika:vrata", - "login_form_endpoint_url": "URL končne točke strežnika", - "login_form_err_http": "Navedi http:// ali https://", - "login_form_err_invalid_email": "Neveljaven e-poštni naslov", - "login_form_err_invalid_url": "Neveljaven URL", - "login_form_err_leading_whitespace": "Vodilni presledek", - "login_form_err_trailing_whitespace": "Končni presledek", - "login_form_failed_get_oauth_server_config": "Napaka pri vpisu z OAuth, preverite URL strežnika", - "login_form_failed_get_oauth_server_disable": "Funkcija OAuth ni na voljo na tem strežniku", - "login_form_failed_login": "Napaka pri prijavi, preverite URL strežnika, e-pošto in geslo", - "login_form_handshake_exception": "Prišlo je do napake pri rokovanju s strežnikom. Če uporabljate samopodpisano potrdilo, v nastavitvah omogočite podporo za samopodpisano potrdilo.", - "login_form_password_hint": "geslo", - "login_form_save_login": "Ostani prijavljen", - "login_form_server_empty": "Vnesite URL strežnika.", - "login_form_server_error": "Neuspešna povezava s strežnikom.", - "login_has_been_disabled": "Prijava je bila onemogočena.", - "login_password_changed_error": "Pri posodabljanju gesla je prišlo do napake", - "login_password_changed_success": "Geslo je bilo uspešno posodobljeno", - "logout_all_device_confirmation": "Ali ste prepričani, da želite odjaviti vse naprave?", - "logout_this_device_confirmation": "Ali ste prepričani, da se želite odjaviti iz te naprave?", - "logs": "Dnevniki", - "longitude": "Zemljepisna dolžina", - "look": "Izgled", - "loop_videos": "Zanka videoposnetkov", - "loop_videos_description": "Omogočite samodejno ponavljanje videoposnetka v pregledovalniku podrobnosti.", - "main_branch_warning": "Uporabljate razvojno različico; močno priporočamo uporabo izdajne različice!", - "main_menu": "Glavni meni", - "maintenance_action_restore": "Obnavljanje baze podatkov", - "maintenance_description": "Immich je bil preklopljen v vzdrževalni način.", - "maintenance_end": "Konec vzdrževalnega načina", - "maintenance_end_error": "Vzdrževalnega načina ni bilo mogoče končati.", - "maintenance_logged_in_as": "Trenutno prijavljen kot {user}", - "maintenance_restore_from_backup": "Obnovi iz varnostne kopije", - "maintenance_restore_library": "Obnovi svojo knjižnico", - "maintenance_restore_library_confirm": "Če je to videti pravilno, nadaljujte z obnovitvijo varnostne kopije!", - "maintenance_restore_library_description": "Obnavljanje baze podatkov", - "maintenance_restore_library_folder_has_files": "{folder} ima {count, plural, one {# mapo} two {# mapi} few {# mape} other {# map}}", - "maintenance_restore_library_folder_no_files": "V mapi {folder} manjkajo datoteke!", - "maintenance_restore_library_folder_pass": "berljivo in zapisljivo", - "maintenance_restore_library_folder_read_fail": "ni berljivo", - "maintenance_restore_library_folder_write_fail": "ni zapisljivo", - "maintenance_restore_library_hint_missing_files": "Morda vam manjkajo pomembne datoteke", - "maintenance_restore_library_hint_regenerate_later": "Te lahko kasneje ponovno ustvarite v nastavitvah", - "maintenance_restore_library_hint_storage_template_missing_files": "Uporabljate predlogo za shranjevanje? Morda vam manjkajo datoteke", - "maintenance_restore_library_loading": "Nalaganje preverjanj integritete in hevristik…", - "maintenance_task_backup": "Ustvarjanje varnostne kopije obstoječe baze podatkov…", - "maintenance_task_migrations": "Izvajanje migracij baz podatkov…", - "maintenance_task_restore": "Obnavljanje izbrane varnostne kopije…", - "maintenance_task_rollback": "Obnovitev ni uspela, vrnitev na obnovitveno točko…", - "maintenance_title": "Trenutno ni na voljo", - "make": "Izdelava", - "manage_geolocation": "Upravljanje lokacije", - "manage_media_access_rationale": "To dovoljenje je potrebno za pravilno premikanje sredstev v koš in njihovo obnovitev iz njega.", - "manage_media_access_settings": "Odpri nastavitve", - "manage_media_access_subtitle": "Dovolite aplikaciji Immich upravljanje in premikanje medijskih datotek.", - "manage_media_access_title": "Dostop do upravljanja medijev", - "manage_shared_links": "Upravljanje povezav v skupni rabi", - "manage_sharing_with_partners": "Upravljajte skupno rabo s partnerji", - "manage_the_app_settings": "Upravljajte nastavitve aplikacije", - "manage_your_account": "Upravljajte svoj račun", - "manage_your_api_keys": "Upravljajte svoje API ključe", - "manage_your_devices": "Upravljajte svoje prijavljene naprave", - "manage_your_oauth_connection": "Upravljajte svojo OAuth povezavo", - "map": "Zemljevid", - "map_assets_in_bounds": "{count, plural, =0 {Na tem območju ni fotografij} one {# slika} two {# sliki} few {# slike} other {# slik}}", - "map_cannot_get_user_location": "Lokacije uporabnika ni mogoče dobiti", - "map_location_dialog_yes": "Da", - "map_location_picker_page_use_location": "Uporabi to lokacijo", - "map_location_service_disabled_content": "Lokacijska storitev mora biti omogočena za prikaz sredstev z vaše trenutne lokacije. Ali jo želite takoj omogočiti?", - "map_location_service_disabled_title": "Lokacijska storitev onemogočena", - "map_marker_for_images": "Oznaka zemljevida za slike, posnete v {city}, {country}", - "map_marker_with_image": "Oznaka zemljevida s sliko", - "map_no_location_permission_content": "Za prikaz sredstev z vaše trenutne lokacije je potrebno dovoljenje za lokacijo. Ali to želite takoj dovoliti?", - "map_no_location_permission_title": "Dovoljenje za lokacijo je zavrnjeno", - "map_settings": "Nastavitve zemljevida", - "map_settings_dark_mode": "Temni način", - "map_settings_date_range_option_day": "Zadnjih 24 ur", - "map_settings_date_range_option_days": "Zadnjih {days} dni", - "map_settings_date_range_option_year": "Zadnje leto", - "map_settings_date_range_option_years": "Zadnjih {years} let", - "map_settings_dialog_title": "Nastavitve zemljevida", - "map_settings_include_show_archived": "Vključi arhivirane", - "map_settings_include_show_partners": "Vključi partnerjeve", - "map_settings_only_show_favorites": "Pokaži samo priljubljene", - "map_settings_theme_settings": "Tema zemljevida", - "map_zoom_to_see_photos": "Pomanjšajte za ogled fotografij", - "mark_all_as_read": "Označi vse kot prebrano", - "mark_as_read": "Označi kot prebrano", - "marked_all_as_read": "Označeno vse kot prebrano", - "matches": "Ujemanja", - "matching_assets": "Ujemajoča se sredstva", - "media_type": "Vrsta medija", - "memories": "Spomini", - "memories_all_caught_up": "Vse dohiteno", - "memories_check_back_tomorrow": "Za več spominov se vrnite jutri", - "memories_setting_description": "Upravljajte s tem, kar vidite v svojih spominih", - "memories_start_over": "Začni od začetka", - "memories_swipe_to_close": "Podrsaj gor za zapiranje", - "memory": "Spomin", - "memory_lane_title": "Spominski trak {title}", - "menu": "Meni", - "merge": "Združi", - "merge_people": "Združi osebe", - "merge_people_limit": "Hkrati lahko združite največ 5 obrazov", - "merge_people_prompt": "Ali želite združiti te osebe? To dejanje je nepovratno.", - "merge_people_successfully": "Združitev ljudi uspešno", - "merged_people_count": "Združeno {count, plural, one {# oseba} two {# osebi} few {# osebe} other {# oseb}}", - "minimize": "Zmanjšaj", - "minute": "minuta", - "minutes": "Minute", - "mirror_horizontal": "Vodoravno", - "mirror_vertical": "Navpično", - "missing": "manjka", - "mobile_app": "Mobilna aplikacija", - "mobile_app_download_onboarding_note": "Prenesite spremljevalno mobilno aplikacijo z uporabo naslednjih možnosti", - "model": "Model", - "month": "Mesec", - "monthly_title_text_date_format": "MMMM y", - "more": "Več", - "move": "Premakni", - "move_down": "Premakni navzdol", - "move_off_locked_folder": "Premakni iz zaklenjene mape", - "move_to": "Premakni v", - "move_to_device_trash": "Premakni v koš naprave", - "move_to_lock_folder_action_prompt": "V zaklenjeno mapo je bilo dodanih {count}", - "move_to_locked_folder": "Premakni v zaklenjeno mapo", - "move_to_locked_folder_confirmation": "Te fotografije in videoposnetki bodo odstranjeni iz vseh albumov in si jih bo mogoče ogledati le v zaklenjeni mapi", - "move_up": "Premakni navzgor", - "moved_to_archive": "Premaknjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v arhiv", - "moved_to_library": "Premaknjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v knjižnico", - "moved_to_trash": "Premaknjeno v smetnjak", - "multiselect_grid_edit_date_time_err_read_only": "Ni mogoče urediti datuma sredstev samo za branje, preskočim", - "multiselect_grid_edit_gps_err_read_only": "Ni mogoče urediti lokacije sredstev samo za branje, preskočim", - "mute_memories": "Utišaj spomine", - "my_albums": "Moji albumi", - "name": "Ime", - "name_or_nickname": "Ime ali vzdevek", - "name_required": "Ime je obvezno", - "navigate": "Navigacija", - "navigate_to_time": "Pomaknite se do časa", - "network_requirement_photos_upload": "Uporaba mobilnih podatkov za varnostno kopiranje fotografij", - "network_requirement_videos_upload": "Uporaba mobilnih podatkov za varnostno kopiranje videoposnetkov", - "network_requirements": "Omrežne zahteve", - "network_requirements_updated": "Omrežne zahteve so se spremenile, ponastavitev čakalne vrste za varnostno kopiranje", - "networking_settings": "Omrežje", - "networking_subtitle": "Upravljaj nastavitve končne točke strežnika", - "never": "nikoli", - "new_album": "Nov album", - "new_api_key": "Nov API ključ", - "new_date_range": "Novo časovno obdobje", - "new_password": "Novo geslo", - "new_person": "Nova oseba", - "new_pin_code": "Nova PIN koda", - "new_pin_code_subtitle": "To je vaš prvi dostop do zaklenjene mape. Ustvarite PIN kodo za varen dostop do te strani", - "new_timeline": "Nova časovnica", - "new_update": "Nova posodobitev", - "new_user_created": "Nov uporabnik ustvarjen", - "new_version_available": "NA VOLJO JE NOVA RAZLIČICA", - "newest_first": "Najprej najnovejše", - "next": "Naslednji", - "next_memory": "Naslednji spomin", - "no": "Ne", - "no_actions_added": "Ni še dodanih dejanj", - "no_albums_found": "Ni najdenih albumov", - "no_albums_message": "Ustvarite album za organiziranje svojih fotografij in videoposnetkov", - "no_albums_with_name_yet": "Videti je, da še nimate nobenega albuma s tem imenom.", - "no_albums_yet": "Videti je, da še nimate nobenega albuma.", - "no_archived_assets_message": "Arhivirajte fotografije in videoposnetke, da jih skrijete v pogledu fotografij", - "no_assets_message": "Kliknite za nalaganje vaše prve fotografije", - "no_assets_to_show": "Ni sredstev za prikaz", - "no_cast_devices_found": "Naprav za predvajanje ni bilo mogoče najti", - "no_checksum_local": "Kontrolna vsota ni na voljo – lokalnih sredstev ni mogoče pridobiti", - "no_checksum_remote": "Kontrolna vsota ni na voljo – oddaljenega sredstva ni mogoče pridobiti", - "no_configuration_needed": "Konfiguracija ni potrebna", - "no_devices": "Ni pooblaščenih naprav", - "no_duplicates_found": "Najden ni bil noben dvojnik.", - "no_exif_info_available": "Podatki o exif niso na voljo", - "no_explore_results_message": "Naložite več fotografij, da raziščete svojo zbirko.", - "no_favorites_message": "Dodajte priljubljene, da hitreje najdete svoje najboljše slike in videoposnetke", - "no_filters_added": "Ni še dodanih filtrov", - "no_libraries_message": "Ustvarite zunanjo knjižnico za ogled svojih fotografij in videoposnetkov", - "no_local_assets_found": "S to kontrolno vsoto ni bilo najdenih lokalnih sredstev", - "no_location_set": "Lokacija ni nastavljena", - "no_locked_photos_message": "Fotografije in videoposnetki v zaklenjeni mapi so skriti in se ne bodo prikazali med brskanjem ali iskanjem po knjižnici.", - "no_name": "Brez imena", - "no_notifications": "Ni obvestil", - "no_people_found": "Ni najdenih ustreznih oseb", - "no_places": "Ni krajev", - "no_remote_assets_found": "S to kontrolno vsoto ni bilo najdenih oddaljenih sredstev", - "no_results": "Brez rezultatov", - "no_results_description": "Poskusite s sinonimom ali bolj splošno ključno besedo", - "no_shared_albums_message": "Ustvarite album za skupno rabo fotografij in videoposnetkov z osebami v vašem omrežju", - "no_uploads_in_progress": "Ni nalaganj v teku", - "none": "Nič", - "not_allowed": "Ni dovoljeno", - "not_available": "Ni na voljo", - "not_in_any_album": "Ni v nobenem albumu", - "not_selected": "Ni izbrano", - "note_apply_storage_label_to_previously_uploaded assets": "Opomba: Če želite oznako za shranjevanje uporabiti za predhodno naložena sredstva, zaženite", - "notes": "Opombe", - "nothing_here_yet": "Tukaj še ni ničesar", - "notification_permission_dialog_content": "Če želite omogočiti obvestila, pojdite v Nastavitve in izberite Dovoli.", - "notification_permission_list_tile_content": "Dodaj dovoljenje za pošiljanje obvestil.", - "notification_permission_list_tile_enable_button": "Omogoči obvestila", - "notification_permission_list_tile_title": "Dovoljenje za obvestila", - "notification_toggle_setting_description": "Omogoči e-poštna obvestila", - "notifications": "Obvestila", - "notifications_setting_description": "Upravljanje obvestil", - "oauth": "OAuth", - "obtainium_configurator": "Konfigurator Obtainium", - "obtainium_configurator_instructions": "Z Obtainium namestite in posodobite aplikacijo za Android neposredno iz izdaje Immich GitHub. Ustvarite ključ API in izberite različico, da ustvarite svojo konfiguracijsko povezavo Obtainium", - "ocr": "Optično prepoznavanje znakov (OCR)", - "official_immich_resources": "Immich uradni viri", - "offline": "Brez povezave", - "offset": "Odmik", - "ok": "V redu", - "oldest_first": "Najprej najstarejši", - "on_this_device": "Na tej napravi", - "onboarding": "Vkrcanje", - "onboarding_locale_description": "Izberite želeni jezik. To lahko pozneje spremenite v nastavitvah.", - "onboarding_privacy_description": "Naslednje (neobvezne) funkcije so odvisne od zunanjih storitev in jih je mogoče kadar koli onemogočiti v nastavitvah.", - "onboarding_server_welcome_description": "Nastavimo vaš primerek z nekaj pogostimi nastavitvami.", - "onboarding_theme_description": "Izberite barvno temo za svoj primer. To lahko pozneje spremenite v nastavitvah.", - "onboarding_user_welcome_description": "Pa začnimo!", - "onboarding_welcome_user": "Pozdravljen/a, {user}", - "online": "Povezano", - "only_favorites": "Samo priljubljene", - "open": "Odpri", - "open_in_map_view": "Odpri v pogledu zemljevida", - "open_in_openstreetmap": "Odpri v OpenStreetMap", - "open_the_search_filters": "Odpri iskalne filtre", - "options": "Možnosti", - "or": "ali", - "organize_into_albums": "Organiziraj v albume", - "organize_into_albums_description": "Dodaj obstoječe fotografije v albume z uporabo trenutnih nastavitev sinhronizacije", - "organize_your_library": "Organiziraj svojo knjižnico", - "original": "izvirnik", - "other": "drugo", - "other_devices": "Druge naprave", - "other_entities": "Drugi subjekti", - "other_variables": "Druge spremenljivke", - "owned": "V lasti", - "owner": "Lastnik", - "page": "Stran", - "partner": "Partner", - "partner_can_access": "{partner} ima dostop", - "partner_can_access_assets": "Vse vaše fotografije in videoposnetki, razen tistih v arhivu in izbrisanih", - "partner_can_access_location": "Lokacija, kjer so bile vaše fotografije posnete", - "partner_list_user_photos": "fotografije od {user}", - "partner_list_view_all": "Poglej vse", - "partner_page_empty_message": "Vaše fotografije še niso v skupni rabi z nobenim partnerjem.", - "partner_page_no_more_users": "Ni več uporabnikov za dodajanje", - "partner_page_partner_add_failed": "Partnerja ni bilo mogoče dodati", - "partner_page_select_partner": "Izberi partnerja", - "partner_page_shared_to_title": "V skupni rabi z", - "partner_page_stop_sharing_content": "{partner} ne bo imel več dostopa do vaših fotografij.", - "partner_sharing": "Skupna raba s partnerjem", - "partners": "Partnerji", - "password": "Geslo", - "password_does_not_match": "Geslo se ne ujema", - "password_required": "Zahtevano je geslo", - "password_reset_success": "Ponastavitev gesla je uspela", - "past_durations": { - "days": "{days, plural, one {Pretekel dan} two {Pretekla # dni} few {Pretekle # dni} other {Preteklih # dni}}", - "hours": "{hours, plural, one {Preteklo uro} two {Pretekli # uri} few {Pretekle # ure} other {Preteklih # ur}}", - "years": "{years, plural, one {Preteklo leto} two {Pretekli # leti} few {Pretekla # leta} other {Preteklih # let}}" - }, - "path": "Pot", - "pattern": "Vzorec", - "pause": "Premor", - "pause_memories": "Zaustavi spomine", - "paused": "Zaustavljeno", - "pending": "Čakanje", - "people": "Osebe", - "people_edits_count": "{count, plural, one {Urejena # oseba} two {Urejeni # osebi} few {Urejene # osebe} other {Urejenih # oseb}}", - "people_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po osebah", - "people_selected": "{count, plural, one {izbrana # oseba} two {izbrani # osebi} few {izbrane # osebe} other {izbranih # oseb}}", - "people_sidebar_description": "Prikažite povezavo do Ljudje v stranski vrstici", - "permanent_deletion_warning": "Opozorilo o trajnem izbrisu", - "permanent_deletion_warning_setting_description": "Pokaži opozorilo pri trajnem brisanju sredstev", - "permanently_delete": "Trajno izbriši", - "permanently_delete_assets_count": "Trajno izbriši {count, plural, one {sredstvo} two {sredstvi} few {sredstva} other {sredstev}}", - "permanently_delete_assets_prompt": "Ali ste prepričani, da želite trajno izbrisati {count, plural, one {to sredstvo?} other {ta # sredstva?}} S tem boste odstranili tudi {count, plural, one {tega od teh} other {telih iz telih}} album- /-ov.", - "permanently_deleted_asset": "Trajno izbrisano sredstvo", - "permanently_deleted_assets_count": "Trajno izbrisano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "permission": "Dovoljenje", - "permission_empty": "Vaše dovoljenje ne sme biti prazno", - "permission_onboarding_back": "Sredstev partnerja ni mogoče izbrisati, preskakujem", - "permission_onboarding_continue_anyway": "Vseeno nadaljuj", - "permission_onboarding_get_started": "Začnimo", - "permission_onboarding_go_to_settings": "Pojdite na nastavitve", - "permission_onboarding_permission_denied": "Dovoljenje zavrnjeno. Če želite uporabljati Immich, v nastavitvah dodajte dovoljenja za fotografije in videoposnetke.", - "permission_onboarding_permission_granted": "Dovoljenje ste dodali! Vse je pripravljeno.", - "permission_onboarding_permission_limited": "Dovoljenje je omejeno. Če želite Immichu dovoliti varnostno kopiranje in upravljanje vaše celotne zbirke galerij, v nastavitvah podelite dovoljenja za fotografije in videoposnetke.", - "permission_onboarding_request": "Immich potrebuje dovoljenje za ogled vaših fotografij in videoposnetkov.", - "person": "Oseba", - "person_age_months": "{months, plural, one {# mesec} two {# meseca} few {# mesece} other {# mesecev}} star/a", - "person_age_year_months": "1 leto, {months, plural, one {# mesec} two {# meseca} few {# mesece} other {# mesecev}} star/a", - "person_age_years": "{years, plural, two {# leti} few {# leta} other {# let}} star/a", - "person_birthdate": "Rojen dne {date}", - "person_hidden": "{name}{hidden, select, true { (skrita)} other {}}", - "person_recognized": "Oseba prepoznana", - "person_selected": "Oseba izbrana", - "photo_shared_all_users": "Videti je, da ste svoje fotografije delili z vsemi uporabniki ali pa nimate nobenega uporabnika, s katerim bi jih delili.", - "photos": "Slike", - "photos_and_videos": "Fotografije & videi", - "photos_count": "{count, plural, one {{count, number} slika} two {{count, number} sliki} few {{count, number} slike} other {{count, number} slik}}", - "photos_from_previous_years": "Fotografije iz prejšnjih let", - "photos_only": "Samo fotografije", - "pick_a_location": "Izberi lokacijo", - "pick_custom_range": "Obseg po meri", - "pick_date_range": "Izberi časovno obdobje", - "pin_code_changed_successfully": "PIN koda je bila uspešno spremenjena", - "pin_code_reset_successfully": "PIN koda je bila uspešno ponastavljena", - "pin_code_setup_successfully": "Uspešno nastavljena PIN koda", - "pin_verification": "Preverjanje PIN kode", - "place": "Lokacija", - "places": "Lokacije", - "places_count": "{count, plural, one {{count, number} kraj} two {{count, number} kraja} few {{count, number} kraji} other {{count, number} krajev}}", - "play": "Predvajaj", - "play_memories": "Predvajaj spomine", - "play_motion_photo": "Predvajaj premikajočo fotografijo", - "play_or_pause_video": "Predvajaj ali zaustavi video", - "play_original_video": "Predvajaj izvirni videoposnetek", - "play_original_video_setting_description": "Prednostno predvajajte originalne videoposnetke pred prekodiranimi. Če originalno sredstvo ni združljivo, se morda ne bo pravilno predvajalo.", - "play_transcoded_video": "Predvajaj prekodiran video", - "please_auth_to_access": "Za dostop se prijavite", - "port": "Vrata", - "preferences_settings_subtitle": "Upravljaj nastavitve aplikacije", - "preferences_settings_title": "Nastavitve", - "preparing": "Priprava", - "preset": "Prednastavitev", - "preview": "Predogled", - "previous": "Prejšnj-a/-i", - "previous_memory": "Prejšnji spomin", - "previous_or_next_day": "Dan naprej/nazaj", - "previous_or_next_month": "Mesec naprej/nazaj", - "previous_or_next_photo": "Fotografija naprej/nazaj", - "previous_or_next_year": "Leto naprej/nazaj", - "primary": "Primarni", - "privacy": "Zasebnost", - "profile": "Profil", - "profile_drawer_app_logs": "Dnevniki", - "profile_drawer_client_server_up_to_date": "Odjemalec in strežnik sta posodobljena", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Način samo za branje je omogočen. Za izhod dolgo pritisnite ikono uporabniškega avatarja.", - "profile_image_of_user": "Profilna slika uporabnika {user}", - "profile_picture_set": "Profilna slika nastavljena.", - "public_album": "Javni album", - "public_share": "Javno deljenje", - "purchase_account_info": "Podpornik", - "purchase_activated_subtitle": "Hvala, ker podpirate Immich in odprtokodno programsko opremo", - "purchase_activated_time": "Aktivirano {date}", - "purchase_activated_title": "Vaš ključ je bil uspešno aktiviran", - "purchase_button_activate": "Aktiviraj", - "purchase_button_buy": "Kupi", - "purchase_button_buy_immich": "Kupi Immich", - "purchase_button_never_show_again": "Nikoli več ne pokaži", - "purchase_button_reminder": "Opomni me čez 30 dni", - "purchase_button_remove_key": "Odstrani ključ", - "purchase_button_select": "Izberi", - "purchase_failed_activation": "Aktivacija ni uspela! Preverite svojo e-pošto za pravilen ključ izdelka!", - "purchase_individual_description_1": "Za posameznika", - "purchase_individual_description_2": "Status podpornika", - "purchase_individual_title": "Posamezno", - "purchase_input_suggestion": "Ali imate ključ izdelka? Spodaj vnesite ključ", - "purchase_license_subtitle": "Kupite Immich, da podprete nadaljnji razvoj storitve", - "purchase_lifetime_description": "Doživljenjski nakup", - "purchase_option_title": "MOŽNOSTI NAKUPA", - "purchase_panel_info_1": "Gradnja Immicha zahteva veliko časa in truda, zato imamo zaposlene inženirje, ki delajo na tem, da bi bil čim boljši. Naše poslanstvo je, da odprtokodna programska oprema in etične poslovne prakse, ki bi postale trajnostni vir dohodka za razvijalce in ustvarjanje ekosistema, ki spoštuje zasebnost z resničnimi alternativami izkoriščevalskim storitvam v oblaku.", - "purchase_panel_info_2": "Ker se zavezujemo, da ne bomo dodajali plačilnih storitev, vam ta nakup ne bo omogočil nobenih dodatnih funkcij v Immichu. Zanašamo se na uporabnike, kot ste vi, ki podpirajo nenehni razvoj Immicha.", - "purchase_panel_title": "Podpri projekt", - "purchase_per_server": "Na strežnik", - "purchase_per_user": "Na uporabnika", - "purchase_remove_product_key": "Odstrani ključ izdelka", - "purchase_remove_product_key_prompt": "Ali ste prepričani, da želite odstraniti ključ izdelka?", - "purchase_remove_server_product_key": "Odstranite ključ izdelka strežnika", - "purchase_remove_server_product_key_prompt": "Ali ste prepričani, da želite odstraniti ključ izdelka strežnika?", - "purchase_server_description_1": "Za celoten strežnik", - "purchase_server_description_2": "Status podpornika", - "purchase_server_title": "Strežnik", - "purchase_settings_server_activated": "Ključ izdelka strežnika upravlja skrbnik", - "query_asset_id": "ID sredstva poizvedbe", - "queue_status": "Čakalna vrsta {count}/{total}", - "rate_asset": "Oceni sredstvo", - "rating": "Ocena z zvezdicami", - "rating_clear": "Počisti oceno", - "rating_count": "{count, plural, one {# zvezdica} two {# zvezdici} few {# zvezdice} other {# zvezdic}}", - "rating_description": "Prikažite oceno EXIF v informacijski plošči", - "rating_set": "Ocena nastavljena na {rating, plural, one {# zvezdo} two {# zvezdi} few {# zvezde} other {# zvezd}}", - "reaction_options": "Možnosti reakcije", - "read_changelog": "Preberi dnevnik sprememb", - "readonly_mode_disabled": "Način samo za branje je onemogočen", - "readonly_mode_enabled": "Način samo za branje je omogočen", - "ready_for_upload": "Pripravljeno za nalaganje", - "reassign": "Prerazporedi", - "reassigned_assets_to_existing_person": "Ponovno dodeljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} za {name, select, null {an existing person} other {{name}}}", - "reassigned_assets_to_new_person": "Ponovno dodeljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} za novo osebo", - "reassing_hint": "Dodeli izbrana sredstva obstoječi osebi", - "recent": "Nedavno", - "recent-albums": "Zadnji albumi", - "recent_searches": "Nedavna iskanja", - "recently_added": "Nedavno dodano", - "recently_added_page_title": "Nedavno dodano", - "recently_taken": "Nedavno posneto", - "recently_taken_page_title": "Nedavno Uporabljen", - "refresh": "Osveži", - "refresh_encoded_videos": "Osveži kodirane videoposnetke", - "refresh_faces": "Osveži obraze", - "refresh_metadata": "Osveži metapodatke", - "refresh_thumbnails": "Osveži sličice", - "refreshed": "Osveženo", - "refreshes_every_file": "Ponovno prebere vse obstoječe in nove datoteke", - "refreshing_encoded_video": "Osveževanje kodiranega videa", - "refreshing_faces": "Osveževanje obrazev", - "refreshing_metadata": "Osveževanje metapodatkov", - "regenerating_thumbnails": "Obnavljanje sličic", - "remote": "Oddaljeno", - "remote_assets": "Oddaljena sredstva", - "remote_media_summary": "Povzetek oddaljenih medijev", - "remove": "Odstrani", - "remove_assets_album_confirmation": "Ali ste prepričani, da želite odstraniti {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} iz albuma?", - "remove_assets_shared_link_confirmation": "Ali ste prepričani, da želite odstraniti {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} iz te skupne povezave?", - "remove_assets_title": "Odstrani sredstva?", - "remove_custom_date_range": "Odstrani časovno obdobje po meri", - "remove_deleted_assets": "Odstrani izbrisana sredstva", - "remove_from_album": "Odstrani iz albuma", - "remove_from_album_action_prompt": "{count} odstranjenih iz albuma", - "remove_from_favorites": "Odstrani iz priljubljenih", - "remove_from_lock_folder_action_prompt": "iz zaklenjene mape je odstranjenih {count}", - "remove_from_locked_folder": "Odstrani iz zaklenjene mape", - "remove_from_locked_folder_confirmation": "Ali ste prepričani, da želite premakniti te fotografije in videoposnetke iz zaklenjene mape? Vidni bodo v vaši knjižnici.", - "remove_from_shared_link": "Odstrani iz skupne povezave", - "remove_memory": "Odstrani spomin", - "remove_photo_from_memory": "Odstrani fotografijo iz tega spomina", - "remove_tag": "Odstrani oznako", - "remove_url": "Odstrani URL", - "remove_user": "Odstrani uporabnika", - "removed_api_key": "Odstranjen ključ API-ja: {name}", - "removed_from_archive": "Odstranjeno iz arhiva", - "removed_from_favorites": "Odstranjeno iz priljubljenih", - "removed_from_favorites_count": "{count, plural, other {odstranen/ih #}} iz priljubljenih", - "removed_memory": "Odstranjen spomin", - "removed_photo_from_memory": "Odstranjena fotografija iz spomina", - "removed_tagged_assets": "Odstranjena oznaka iz {count, plural, one {# sredstva} other {# sredstev}}", - "rename": "Preimenuj", - "repair": "Popravi", - "repair_no_results_message": "Datoteke, ki jim ni sledi in manjkajo, bodo prikazane tukaj", - "replace_with_upload": "Zamenjaj z nalaganjem", - "repository": "Repozitorij", - "require_password": "Zahtevaj geslo", - "require_user_to_change_password_on_first_login": "Od uporabnika zahtevajte spremembo gesla ob prvi prijavi", - "rescan": "Ponovno skeniraj", - "reset": "Ponastavi", - "reset_password": "Ponastavi geslo", - "reset_people_visibility": "Ponastavi vidnost ljudi", - "reset_pin_code": "Ponastavi PIN kodo", - "reset_pin_code_description": "Če ste pozabili PIN kodo, se lahko za ponastavitev obrnete na skrbnika strežnika", - "reset_pin_code_success": "PIN koda je bila uspešno ponastavljena", - "reset_pin_code_with_password": "PIN-kodo lahko vedno ponastavite z geslom", - "reset_sqlite": "Ponastavi bazo podatkov SQLite", - "reset_sqlite_confirmation": "Ali ste prepričani, da želite ponastaviti bazo podatkov SQLite? Za ponovno sinhronizacijo podatkov se boste morali odjaviti in znova prijaviti", - "reset_sqlite_success": "Uspešno ponastavljena baza podatkov SQLite", - "reset_to_default": "Ponastavi na privzeto", - "resolution": "Ločljivost", - "resolve_duplicates": "Razreši dvojnike", - "resolved_all_duplicates": "Razrešeni vsi dvojniki", - "restore": "Obnovi", - "restore_all": "Obnovi vse", - "restore_trash_action_prompt": "{count} obnovljenih iz koša", - "restore_user": "Obnovi uporabnika", - "restored_asset": "Obnovljeno sredstvo", - "resume": "Nadaljuj", - "resume_paused_jobs": "Nadaljuj {count, plural, one {# zaustavljeno opravilo} two {# zaustavljeni opravili} few {# zaustavljena opravila} other {# zaustavljenih opravil}}", - "retry_upload": "Poskusite znova naložiti", - "review_duplicates": "Pregled dvojnikov", - "review_large_files": "Pregled velikih datotek", - "role": "Vloga", - "role_editor": "Urejevalec", - "role_viewer": "Gledalec", - "running": "V teku", - "save": "Shrani", - "save_to_gallery": "Shrani v galerijo", - "saved": "Shranjeno", - "saved_api_key": "Shranjen API ključ", - "saved_profile": "Shranjen profil", - "saved_settings": "Shranjene nastavitve", - "say_something": "Reci kaj", - "scaffold_body_error_occurred": "Prišlo je do napake", - "scan": "Skeniraj", - "scan_all_libraries": "Preglej vse knjižnice", - "scan_library": "Pregled", - "scan_settings": "Nastavitve pregleda", - "scanning": "Skeniranje", - "scanning_for_album": "Iskanje albuma...", - "search": "Iskanje", - "search_albums": "Iskanje albumov", - "search_by_context": "Iskanje po kontekstu", - "search_by_description": "Iskanje po opisu", - "search_by_description_example": "Pohodniški dan v Sapi", - "search_by_filename": "Iskanje po imenu datoteke ali priponi", - "search_by_filename_example": "na primer IMG_1234.JPG ali PNG", - "search_by_ocr": "Iskanje po optičnem prepoznavanju znakov (OCR)", - "search_by_ocr_example": "Bela kava", - "search_camera_lens_model": "Iskanje modela objektiva...", - "search_camera_make": "Iskanje proizvajalca kamere...", - "search_camera_model": "Išči model kamere...", - "search_city": "Iskanje mesta...", - "search_country": "Iskanje države...", - "search_filter_apply": "Uporabi filter", - "search_filter_camera_title": "Izberi vrsto fotoaparata", - "search_filter_date": "Datum", - "search_filter_date_interval": "{start} do {end}", - "search_filter_date_title": "Izberi časovno obdobje", - "search_filter_display_option_not_in_album": "Ni v albumu", - "search_filter_display_options": "Možnosti zaslona", - "search_filter_filename": "Iskanje po imenu datoteke", - "search_filter_location": "Lokacija", - "search_filter_location_title": "Izberi lokacijo", - "search_filter_media_type": "Vrsta medija", - "search_filter_media_type_title": "Izberi vrsto medija", - "search_filter_ocr": "Iskanje po optičnem prepoznavanju znakov (OCR)", - "search_filter_people_title": "Izberi osebe", - "search_filter_star_rating": "Ocena z zvezdicami", - "search_for": "Poišči za", - "search_for_existing_person": "Iskanje obstoječe osebe", - "search_no_more_result": "Ni več rezultatov", - "search_no_people": "Brez oseb", - "search_no_people_named": "Ni oseb z imenom \"{name}\"", - "search_no_result": "Ni rezultatov, poskusite z drugim iskalnim izrazom ali kombinacijo", - "search_options": "Možnosti iskanja", - "search_page_categories": "Kategorije", - "search_page_motion_photos": "Fotografije v gibanju", - "search_page_no_objects": "Informacije o predmetih niso na voljo", - "search_page_no_places": "Informacije o lokacijah niso na voljo", - "search_page_screenshots": "Posnetki zaslona", - "search_page_search_photos_videos": "Poišči svoje fotografije in videoposnetke", - "search_page_selfies": "Selfiji", - "search_page_things": "Stvari", - "search_page_view_all_button": "Poglej vse", - "search_page_your_activity": "Vaša dejavnost", - "search_page_your_map": "Tvoj zemljevid", - "search_people": "Iskanje oseb", - "search_places": "Iskanje krajev", - "search_rating": "Išči po oceni ...", - "search_result_page_new_search_hint": "Novo iskanje", - "search_settings": "Nastavitve iskanja", - "search_state": "Iskanje dežele...", - "search_suggestion_list_smart_search_hint_1": "Pametno iskanje je privzeto omogočeno, za iskanje metapodatkov uporabite sintakso ", - "search_suggestion_list_smart_search_hint_2": "m:vaš-iskani-pojem", - "search_tags": "Iskanje oznak...", - "search_timezone": "Iskanje časovnega pasu...", - "search_type": "Vrsta iskanja", - "search_your_photos": "Poišči svoje fotografije", - "searching_locales": "Iskanje krajev...", - "second": "Sekunda", - "see_all_people": "Oglejte si vse ljudi", - "select": "Izberi", - "select_album": "Izberi album", - "select_album_cover": "Izberi naslovnico albuma", - "select_albums": "Izberi albume", - "select_all": "Izberi vse", - "select_all_duplicates": "Izberi vse dvojnike", - "select_all_in": "Izberi vse v {group}", - "select_avatar_color": "Izberi barvo avatarja", - "select_count": "{count, plural, one {# izbran} two {# izbrana} few {# izbrani} other {# izbranih}}", - "select_cutoff_date": "Izberite datum zaključka", - "select_face": "Izberi obraz", - "select_featured_photo": "Izberi predstavljeno fotografijo", - "select_from_computer": "Izberi iz računalnika", - "select_keep_all": "Izberi obdrži vse", - "select_library_owner": "Izberi lastnika knjižnice", - "select_new_face": "Izberi nov obraz", - "select_people": "Izberi osebe", - "select_person": "Izberi osebo", - "select_person_to_tag": "Izberite osebo, ki jo želite označiti", - "select_photos": "Izberi fotografije", - "select_trash_all": "Izberi vse v smetnjak", - "select_user_for_sharing_page_err_album": "Albuma ni bilo mogoče ustvariti", - "selected": "Izbrano", - "selected_count": "{count, plural, other {# izbranih}}", - "selected_gps_coordinates": "izbrane GPS koordinate", - "send_message": "Pošlji sporočilo", - "send_welcome_email": "Pošlji pozdravno e-pošto", - "server_endpoint": "Končna točka strežnika", - "server_info_box_app_version": "Različica aplikacije", - "server_info_box_server_url": "URL strežnika", - "server_offline": "Strežnik nima povezave", - "server_online": "Strežnik povezan", - "server_privacy": "Zasebnost strežnika", - "server_restarting_description": "Ta stran se bo za trenutek osvežila.", - "server_restarting_title": "Strežnik se znova zaganja", - "server_stats": "Statistika strežnika", - "server_update_available": "Posodobitev strežnika je na voljo", - "server_version": "Različica strežnika", - "set": "Nastavi", - "set_as_album_cover": "Nastavi kot naslovnico albuma", - "set_as_featured_photo": "Nastavi kot glavno fotografijo", - "set_as_profile_picture": "Nastavi kot profilno sliko", - "set_date_of_birth": "Nastavi datum rojstva", - "set_profile_picture": "Nastavi profilno sliko", - "set_slideshow_to_fullscreen": "Nastavi diaprojekcijo na celozaslonski način", - "set_stack_primary_asset": "Nastavi kot glavno sredstvo", - "setting_image_viewer_help": "Pregledovalnik podrobnosti najprej naloži majhno sličico, nato naloži predogled srednje velikosti (če je omogočen), na koncu naloži izvirnik (če je omogočen).", - "setting_image_viewer_original_subtitle": "Omogoči nalaganje originalne slike polne ločljivosti (velike!). Onemogočite, da zmanjšate porabo podatkov (tako v omrežju kot v predpomnilniku naprave).", - "setting_image_viewer_original_title": "Naloži originalno sliko", - "setting_image_viewer_preview_subtitle": "Omogoči nalaganje slike srednje ločljivosti. Onemogočite neposredno nalaganje izvirnika ali uporabo samo sličice.", - "setting_image_viewer_preview_title": "Naloži predogled slike", - "setting_image_viewer_title": "Slike", - "setting_languages_apply": "Uporabi", - "setting_languages_subtitle": "Spremeni jezik aplikacije", - "setting_notifications_notify_failures_grace_period": "Obvesti o napakah varnostnega kopiranja v ozadju: {duration}", - "setting_notifications_notify_hours": "{count} ur", - "setting_notifications_notify_immediately": "takoj", - "setting_notifications_notify_minutes": "{count} minut", - "setting_notifications_notify_never": "nikoli", - "setting_notifications_notify_seconds": "{count} sekund", - "setting_notifications_single_progress_subtitle": "Podrobne informacije o napredku nalaganja po sredstvih", - "setting_notifications_single_progress_title": "Pokaži napredek varnostnega kopiranja v ozadju", - "setting_notifications_subtitle": "Prilagodite svoje nastavitve obvestil", - "setting_notifications_total_progress_subtitle": "Splošni napredek nalaganja (končano/skupaj sredstev)", - "setting_notifications_total_progress_title": "Prikaži skupni napredek varnostnega kopiranja v ozadju", - "setting_video_viewer_auto_play_subtitle": "Samodejno začni predvajati videoposnetke, ko jih odpreš", - "setting_video_viewer_auto_play_title": "Samodejno predvajanje videoposnetkov", - "setting_video_viewer_looping_title": "V zanki", - "setting_video_viewer_original_video_subtitle": "Ko pretakate video iz strežnika, predvajajte izvirnik, tudi če je na voljo prekodiranje. Lahko povzroči medpomnjenje. Videoposnetki, ki so na voljo lokalno, se predvajajo v izvirni kakovosti ne glede na to nastavitev.", - "setting_video_viewer_original_video_title": "Vsili izvirni video", - "settings": "Nastavitve", - "settings_require_restart": "Znova zaženite Immich, da uporabite to nastavitev", - "settings_saved": "Nastavitve shranjene", - "setup_pin_code": "Nastavi PIN kodo", - "share": "Deli", - "share_action_prompt": "Deljena sredstva {count}", - "share_add_photos": "Dodaj fotografije", - "share_assets_selected": "{count} izbrano", - "share_dialog_preparing": "Priprava...", - "share_link": "Deli povezavo", - "shared": "V skupni rabi", - "shared_album_activities_input_disable": "Komentiranje je onemogočeno", - "shared_album_activity_remove_content": "Ali želite izbrisati to dejavnost?", - "shared_album_activity_remove_title": "Izbriši dejavnost", - "shared_album_section_people_action_error": "Napaka pri zapuščanju/odstranjevanju iz albuma", - "shared_album_section_people_action_leave": "Odstrani uporabnika iz albuma", - "shared_album_section_people_action_remove_user": "Odstrani uporabnika iz albuma", - "shared_album_section_people_title": "OSEBE", - "shared_by": "Skupna raba s/z", - "shared_by_user": "Skupna raba s/z {user}", - "shared_by_you": "Deliš", - "shared_from_partner": "Fotografije od {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} naloženo", - "shared_link_app_bar_title": "Povezave v skupni rabi", - "shared_link_clipboard_copied_massage": "Kopirano v odložišče", - "shared_link_clipboard_text": "Povezava: {link}\nGeslo: {password}", - "shared_link_create_error": "Napaka pri ustvarjanju povezave skupne rabe", - "shared_link_custom_url_description": "Dostop do te deljene povezave z URL-jem po meri", - "shared_link_edit_description_hint": "Vnesi opis skupne rabe", - "shared_link_edit_expire_after_option_day": "1 dan", - "shared_link_edit_expire_after_option_days": "{count} dni", - "shared_link_edit_expire_after_option_hour": "1 ura", - "shared_link_edit_expire_after_option_hours": "{count} ur", - "shared_link_edit_expire_after_option_minute": "1 minuta", - "shared_link_edit_expire_after_option_minutes": "{count} minut", - "shared_link_edit_expire_after_option_months": "{count} mesecev", - "shared_link_edit_expire_after_option_year": "{count} let", - "shared_link_edit_password_hint": "Vnesi geslo za skupno rabo", - "shared_link_edit_submit_button": "Posodobi povezavo", - "shared_link_error_server_url_fetch": "URL-ja strežnika ni mogoče pridobiti", - "shared_link_expires_day": "Poteče čez {count} dan", - "shared_link_expires_days": "Poteče čez {count} dni", - "shared_link_expires_hour": "Poteče čez {count} uro", - "shared_link_expires_hours": "Poteče čez {count} ur", - "shared_link_expires_minute": "Poteče čez {count} minuto", - "shared_link_expires_minutes": "Poteče čez {count} minut", - "shared_link_expires_never": "Poteče ∞", - "shared_link_expires_second": "Poteče čez {count} sekundo", - "shared_link_expires_seconds": "Poteče čez {count} sekund", - "shared_link_individual_shared": "Individualno deljeno", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Upravljanje povezav v skupni rabi", - "shared_link_options": "Možnosti skupne povezave", - "shared_link_password_description": "Za dostop do te deljene povezave je potrebno geslo", - "shared_links": "Povezave v skupni rabi", - "shared_links_description": "Deli fotografije in videoposnetke s povezavo", - "shared_photos_and_videos_count": "{assetCount, plural, other {# deljenih fotografij & videoposnetkov.}}", - "shared_with_me": "Deljeno z mano", - "shared_with_partner": "V skupni rabi s/z {partner}", - "sharing": "Skupna raba", - "sharing_enter_password": "Za ogled te strani vnesi geslo.", - "sharing_page_album": "Albumi v skupni rabi", - "sharing_page_description": "Ustvarite albume za skupno rabo fotografij in videoposnetkov z osebami v vašem omrežju.", - "sharing_page_empty_list": "PRAZEN SEZNAM", - "sharing_sidebar_description": "Prikažite povezavo do skupne rabe v stranski vrstici", - "sharing_silver_appbar_create_shared_album": "Ustvari album v skupni rabi", - "sharing_silver_appbar_share_partner": "Deli z partnerjem", - "shift_to_permanent_delete": "pritisni ⇧ za trajno brisanje sredstva", - "show_album_options": "Prikaži možnosti albuma", - "show_albums": "Prikaži albume", - "show_all_people": "Prikaži vse osebe", - "show_and_hide_people": "Prikaži & skrij osebe", - "show_file_location": "Pokaži lokacijo datoteke", - "show_gallery": "Prikaži galerijo", - "show_hidden_people": "Prikaži skrite osebe", - "show_in_timeline": "Pokaži na časovnici", - "show_in_timeline_setting_description": "Prikaži fotografije in videoposnetke tega uporabnika na svoji časovnici", - "show_keyboard_shortcuts": "Prikaži bližnjice na tipkovnici", - "show_metadata": "Pokaži metapodatke", - "show_or_hide_info": "Pokaži ali skrij podatke", - "show_password": "Prikaži geslo", - "show_person_options": "Prikaži možnosti osebe", - "show_progress_bar": "Prikaži vrstico napredka", - "show_schema": "Prikaži shemo", - "show_search_options": "Prikaži možnosti iskanja", - "show_shared_links": "Pokaži povezave v skupni rabi", - "show_slideshow_transition": "Prikaži prehod diaprojekcije", - "show_supporter_badge": "Značka podpornika", - "show_supporter_badge_description": "Prikaži značko podpornika", - "show_text_recognition": "Prikaži prepoznavanje besedila", - "show_text_search_menu": "Prikaži meni za iskanje po besedilu", - "shuffle": "Naključno", - "sidebar": "Stranska vrstica", - "sidebar_display_description": "Prikaži povezavo do pogleda v stranski vrstici", - "sign_out": "Odjavi se", - "sign_up": "Prijavi se", - "size": "Velikost", - "skip_to_content": "Preskoči na vsebino", - "skip_to_folders": "Preskoči na mape", - "skip_to_tags": "Preskoči na oznake", - "slideshow": "Diaprojekcija", - "slideshow_repeat": "Ponavljanje diaprojekcije", - "slideshow_repeat_description": "Po koncu diaprojekcije se zanka vrne na začetek", - "slideshow_settings": "Nastavitve diaprojekcije", - "sort_albums_by": "Razvrsti albume po...", - "sort_created": "Datum nastanka", - "sort_items": "Število predmetov", - "sort_modified": "Datum spremembe", - "sort_newest": "Najnovejša fotografija", - "sort_oldest": "Najstarejša fotografija", - "sort_people_by_similarity": "Razvrsti ljudi po podobnosti", - "sort_recent": "Najnovejša fotografija", - "sort_title": "Naslov", - "source": "Vir", - "stack": "Sklad", - "stack_action_prompt": "{count} naloženih", - "stack_duplicates": "Nabor dvojnikov", - "stack_select_one_photo": "Izberite eno glavno fotografijo za nabor", - "stack_selected_photos": "Nabor izbranih fotografij", - "stacked_assets_count": "Nabor {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "stacktrace": "Sled nabora", - "start": "Začetek", - "start_date": "Začetni datum", - "start_date_before_end_date": "Začetni datum mora biti pred končnim datumom", - "state": "Dežela", - "status": "Status", - "stop_casting": "Ustavi predvajanje", - "stop_motion_photo": "Zaustavi gibljivo fotografijo", - "stop_photo_sharing": "Želite prenehati deliti svoje fotografije?", - "stop_photo_sharing_description": "{partner} ne bo mogel več dostopati do vaših fotografij.", - "stop_sharing_photos_with_user": "Prenehaj deliti svoje fotografije s tem uporabnikom", - "storage": "Prostor za shranjevanje", - "storage_label": "Oznaka za shranjevanje", - "storage_quota": "Kvota shranjevanja", - "storage_usage": "uporabljeno {used} od {available}", - "submit": "Predloži", - "success": "Uspeh", - "suggestions": "Predlogi", - "sunrise_on_the_beach": "Sončni vzhod na plaži", - "support": "Podpora", - "support_and_feedback": "Podpora in povratne informacije", - "support_third_party_description": "Vašo namestitev Immich je pakirala tretja oseba. Težave, ki jih imate, lahko povzroči ta paket, zato prosimo, da težave najprej izpostavite njim, tako da uporabite spodnje povezave.", - "swap_merge_direction": "Zamenjaj smer združevanja", - "sync": "Sinhronizacija", - "sync_albums": "Sinhronizacija albumov", - "sync_albums_manual_subtitle": "Sinhronizirajte vse naložene videoposnetke in fotografije v izbrane varnostne albume", - "sync_local": "Sinhroniziraj lokalno", - "sync_remote": "Sinhroniziraj oddaljeno", - "sync_status": "Stanje sinhronizacije", - "sync_status_subtitle": "Ogled in upravljanje sistema sinhronizacije", - "sync_upload_album_setting_subtitle": "Ustvarite in naložite svoje fotografije in videoposnetke v izbrane albume na Immich", - "tag": "Oznaka", - "tag_assets": "Označi sredstva", - "tag_created": "Ustvarjena oznaka: {tag}", - "tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrščenih po temah logičnih oznak", - "tag_not_found_question": "Ne najdete oznake? Ustvarite novo oznako.", - "tag_people": "Označi osebe", - "tag_updated": "Posodobljena oznaka: {tag}", - "tagged_assets": "Označeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "tags": "Oznake", - "tap_to_run_job": "Dotaknite se za zagon opravila", - "template": "Predloga", - "text_recognition": "Prepoznavanje besedila", - "theme": "Tema", - "theme_selection": "Izbira teme", - "theme_selection_description": "Samodejno nastavi temo na svetlo ali temno glede na sistemske nastavitve brskalnika", - "theme_setting_asset_list_storage_indicator_title": "Pokaži indikator shrambe na ploščicah sredstev", - "theme_setting_asset_list_tiles_per_row_title": "Število sredstev na vrstico ({count})", - "theme_setting_colorful_interface_subtitle": "Nanesi primarno barvo na površine ozadja.", - "theme_setting_colorful_interface_title": "Barvit vmesnik", - "theme_setting_image_viewer_quality_subtitle": "Prilagodite kakovost podrobnega pregledovalnika slik", - "theme_setting_image_viewer_quality_title": "Kakovost pregledovalnika slik", - "theme_setting_primary_color_subtitle": "Izberi barvo za primarna dejanja in poudarke.", - "theme_setting_primary_color_title": "Primarna barva", - "theme_setting_system_primary_color_title": "Uporabi sistemsko barvo", - "theme_setting_system_theme_switch": "Samodejno (Sledi nastavitvi sistema)", - "theme_setting_theme_subtitle": "Izberi nastavitev teme aplikacije", - "theme_setting_three_stage_loading_subtitle": "Tristopenjsko nalaganje lahko poveča zmogljivost nalaganja, vendar povzroči znatno večjo obremenitev omrežja", - "theme_setting_three_stage_loading_title": "Omogoči tristopenjsko nalaganje", - "then": "Potem", - "they_will_be_merged_together": "Združeni bodo skupaj", - "third_party_resources": "Viri tretjih oseb", - "time": "Čas", - "time_based_memories": "Časovni spomini", - "time_based_memories_duration": "Število sekund za prikaz vsake slike.", - "timeline": "Časovnica", - "timezone": "Časovni pas", - "to_archive": "Arhiv", - "to_change_password": "Spremeni geslo", - "to_favorite": "Priljubljen", - "to_login": "Prijava", - "to_multi_select": "izbira več elementov", - "to_parent": "Pojdi na prvotno", - "to_select": "na izbiro", - "to_trash": "Smetnjak", - "toggle_settings": "Preklopi na nastavitve", - "toggle_theme_description": "Preklopi temo", - "total": "Skupno", - "total_usage": "Skupna poraba", - "trash": "Smetnjak", - "trash_action_prompt": "premaknjeno v smetnjak {count}", - "trash_all": "Vse v smetnjak", - "trash_count": "Smetnjak {count, number}", - "trash_delete_asset": "V smetnjak/izbriši sredstvo", - "trash_emptied": "Smetnjak je izpraznjen", - "trash_no_results_message": "Fotografije in videoposnetki, ki so v smetnjaku, bodo prikazani tukaj.", - "trash_page_delete_all": "Izbriši vse", - "trash_page_empty_trash_dialog_content": "Ali želite izprazniti svoja sredstva v smeti? Ti elementi bodo trajno odstranjeni iz Immicha", - "trash_page_info": "Elementi v smeteh bodo trajno izbrisani po {days} dneh", - "trash_page_no_assets": "Ni sredstev v smeteh", - "trash_page_restore_all": "Obnovi vse", - "trash_page_select_assets_btn": "Izberite sredstva", - "trash_page_title": "Smetnjak ({count})", - "trashed_items_will_be_permanently_deleted_after": "Elementi v smetnjaku bodo trajno izbrisani po {days, plural, one {# dnevu} two {# dnevih} few {# dnevih} other {# dneh}}.", - "trigger": "Sprožilec", - "trigger_asset_uploaded": "Sredstvo je naloženo", - "trigger_asset_uploaded_description": "Sproži se ob nalaganju novega sredstva", - "trigger_description": "Dogodek, ki sproži delovni proces", - "trigger_person_recognized": "Oseba prepoznana", - "trigger_person_recognized_description": "Sproži se, ko je zaznana oseba", - "trigger_type": "Vrsta sprožilca", - "troubleshoot": "Odpravljanje težav", - "type": "Vrsta", - "unable_to_change_pin_code": "PIN kode ni mogoče spremeniti", - "unable_to_check_version": "Ni mogoče preveriti različice aplikacije ali strežnika", - "unable_to_setup_pin_code": "PIN kode ni mogoče nastaviti", - "unarchive": "Odstrani iz arhiva", - "unarchive_action_prompt": "{count} odstranjenih iz arhiva", - "unarchived_count": "{count, plural, other {nearhiviranih #}}", - "undo": "Razveljavi", - "unfavorite": "Odznači priljubljeno", - "unfavorite_action_prompt": "{count} odstranjenih iz priljubljenih", - "unhide_person": "Prikaži osebo", - "unknown": "Neznano", - "unknown_country": "Neznana država", - "unknown_date": "Neznan datum", - "unknown_year": "Neznano leto", - "unlimited": "Neomejeno", - "unlink_motion_video": "Prekini povezavo videoposnetka gibanja", - "unlink_oauth": "Prekini povezavo OAuth", - "unlinked_oauth_account": "Nepovezan račun OAuth", - "unmute_memories": "Vklopi zvok spominov", - "unnamed_album": "Neimenovan album", - "unnamed_album_delete_confirmation": "Ali ste prepričani, da želite izbrisati ta album?", - "unnamed_share": "Neimenovana skupna raba", - "unsaved_change": "Neshranjena sprememba", - "unselect_all": "Odznači vse", - "unselect_all_duplicates": "Odznači vse dvojnike", - "unselect_all_in": "Prekliči izbor vseh v {group}", - "unstack": "Razklad", - "unstack_action_prompt": "{count} razloženih", - "unstacked_assets_count": "Razloži {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "unsupported_field_type": "Nepodprta vrsta polja", - "untagged": "Neoznačeno", - "untitled_workflow": "Neimenovani potek dela", - "up_next": "Naslednja", - "update_location_action_prompt": "Posodobi lokacijo izbranih sredstev {count} s/z:", - "updated_at": "Posodobljeno", - "updated_password": "Posodobljeno geslo", - "upload": "Naloži", - "upload_concurrency": "Sočasnost nalaganja", - "upload_details": "Podrobnosti o nalaganju", - "upload_dialog_info": "Ali želite varnostno kopirati izbrana sredstva na strežnik?", - "upload_dialog_title": "Naloži sredstvo", - "upload_error_with_count": "Napaka pri prilaganju za {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", - "upload_errors": "Nalaganje je končano s/z {count, plural, one {# napako} two {# napakama} other {# napakami}}, osvežite stran, da vidite nova sredstva za nalaganje.", - "upload_finished": "Nalaganje končano", - "upload_progress": "Preostalo {remaining, number} - Obdelano {processed, number}/{total, number}", - "upload_skipped_duplicates": "Preskočeno {count, plural, one {# podvojeno sredstvo} two {# podvojeni sredstvi} few {# podvojena sredstva} other {# podvojenih sredstev}}", - "upload_status_duplicates": "Dvojniki", - "upload_status_errors": "Napake", - "upload_status_uploaded": "Naloženo", - "upload_success": "Nalaganje je uspelo, osvežite stran, da vidite nova sredstva za nalaganje.", - "upload_to_immich": "Naloži v Immich ({count})", - "uploading": "Nalagam", - "uploading_media": "Nalaganje medijev", - "url": "URL", - "usage": "Uporaba", - "use_biometric": "Uporabite biometrične podatke", - "use_current_connection": "Uporabi trenutno povezavo", - "use_custom_date_range": "Namesto tega uporabite časovno obdobje po meri", - "user": "Uporabnik", - "user_has_been_deleted": "Ta uporabnik je bil izbrisan.", - "user_id": "ID uporabnika", - "user_liked": "{user} je všeč {type, select, photo {ta fotografija} video {ta video} asset {to sredstvo} other {to}}", - "user_pin_code_settings": "PIN koda", - "user_pin_code_settings_description": "Upravljaj svojo PIN kodo", - "user_privacy": "Zasebnost uporabnika", - "user_purchase_settings": "Nakup", - "user_purchase_settings_description": "Upravljajte svoj nakup", - "user_role_set": "Nastavi {user} kot {role}", - "user_usage_detail": "Podrobnosti o uporabi uporabnika", - "user_usage_stats": "Statistika uporabe računa", - "user_usage_stats_description": "Oglejte si statistiko uporabe računa", - "username": "Uporabniško ime", - "users": "Uporabniki", - "users_added_to_album_count": "V album {count, plural, one {je bil dodan # uporabnik} two {sta bila dodana # uporabnika} few {so bili dodani # uporabniki} other {je bilo dodanih # uporanikov}}", - "utilities": "Pripomočki", - "validate": "Potrdi", - "validate_endpoint_error": "Vnesite veljaven URL", - "validation_error": "Napaka pri preverjanju", - "variables": "Spremenljivke", - "version": "Različica", - "version_announcement_closing": "Tvoj prijatelj, Alex", - "version_announcement_message": "Pozdravljeni! Na voljo je nova različica Immich. Vzemite si nekaj časa in preberite opombe ob izdaji, da zagotovite, da so vaše nastavitve posodobljene, da preprečite morebitne napačne konfiguracije, zlasti če uporabljate WatchTower ali kateri koli mehanizem, ki samodejno posodablja vaš primerek Immich.", - "version_history": "Zgodovina različic", - "version_history_item": "{version} nameščena {date}", - "video": "Video", - "video_hover_setting": "Predvajaj sličico videoposnetka ob lebdenju", - "video_hover_setting_description": "Predvajaj sličico videoposnetka, ko se miška pomakne nad element. Tudi ko je onemogočeno, lahko predvajanje začnete tako, da miškin kazalec premaknete nad ikono za predvajanje.", - "videos": "Videoposnetki", - "videos_count": "{count, plural, one {# video} two {# videa} few {# videi} other {# videov}}", - "videos_only": "Samo videoposnetki", - "view": "Ogled", - "view_album": "Ogled albuma", - "view_all": "Poglej vse", - "view_all_users": "Ogled vseh uporabnikov", - "view_asset_owners": "Ogled lastnikov sredstev", - "view_details": "Ogled podrobnosti", - "view_in_timeline": "Ogled na časovnici", - "view_link": "Odpri povezavo", - "view_links": "Ogled povezav", - "view_name": "Pogled", - "view_next_asset": "Ogled naslednjega sredstva", - "view_previous_asset": "Ogled prejšnjega sredstva", - "view_qr_code": "Oglej si kodo QR", - "view_similar_photos": "Oglejte si podobne fotografije", - "view_stack": "Ogled sklada", - "view_user": "Poglej uporabnika", - "viewer_remove_from_stack": "Odstrani iz sklada", - "viewer_stack_use_as_main_asset": "Uporabi kot glavno sredstvo", - "viewer_unstack": "Razkladi", - "visibility_changed": "Vidnost spremenjena za {count, plural, one {# osebo} two {# osebi} few {# osebe} other {# oseb}}", - "visual": "Vizualno", - "visual_builder": "Vizualni graditelj", - "waiting": "Čakanje", - "waiting_count": "Čakanje: {count}", - "warning": "Opozorilo", - "week": "Teden", - "welcome": "Dobrodošli", - "welcome_to_immich": "Dobrodošli v Immich", - "width": "Širina", - "wifi_name": "Wi-Fi ime", - "workflow_delete_prompt": "Ali ste prepričani, da želite izbrisati ta potek dela?", - "workflow_deleted": "Potek dela izbrisan", - "workflow_description": "Opis poteka dela", - "workflow_info": "Informacije o poteku dela", - "workflow_json": "JSON poteka dela", - "workflow_json_help": "Uredite konfiguracijo poteka dela v formatu JSON. Spremembe se bodo sinhronizirale z vizualnim graditeljem.", - "workflow_name": "Ime poteka dela", - "workflow_navigation_prompt": "Ali ste prepričani, da želite zapustiti stran brez shranjevanja sprememb?", - "workflow_summary": "Povzetek poteka dela", - "workflow_update_success": "Potek dela je bil uspešno posodobljen", - "workflow_updated": "Potek dela posodobljen", - "workflows": "Poteki dela", - "workflows_help_text": "Poteki dela avtomatizirajo dejanja na vaših sredstvih na podlagi sprožilcev in filtrov", - "wrong_pin_code": "Napačna PIN koda", - "year": "Leto", - "years_ago": "{years, plural, one {# leto} two {# leti} few {# leta} other {# let}} nazaj", - "yes": "Da", - "you_dont_have_any_shared_links": "Nimate nobenih skupnih povezav", - "your_wifi_name": "Vaše ime Wi-Fi", - "zero_to_clear_rating": "Pritisnite 0 za brisanje ocene sredstva", - "zoom_image": "Povečava slike", - "zoom_to_bounds": "Povečaj do meja" -} +{} diff --git a/i18n/sq.json b/i18n/sq.json index 13925c212d..0967ef424b 100644 --- a/i18n/sq.json +++ b/i18n/sq.json @@ -1,115 +1 @@ -{ - "about": "Rreth", - "account": "Llogari", - "account_settings": "Cilësimet e Llogarisë", - "acknowledge": "Prano", - "action": "Aksion", - "action_common_update": "Përditëso", - "action_description": "Një grup veprimesh për t'u kryer në asetet e filtruara", - "actions": "Aksione", - "active": "Aktiv", - "active_count": "Aktive: {count}", - "activity": "Aktivitet", - "activity_changed": "Aktiviteti është {enabled, select, true {aktivizuar} other {çaktivizuar}}", - "add": "Shto", - "add_a_description": "Shto një përshkrim", - "add_a_location": "Shto një vendndodhje", - "add_a_name": "Shto një emër", - "add_a_title": "Shto një titull", - "add_action": "Shto veprim", - "add_action_description": "Klikoni për të shtuar një veprim për t'u kryer", - "add_birthday": "Shto një ditëlindje", - "add_endpoint": "Shto një endpoint", - "add_exclusion_pattern": "Shto model përjashtimi", - "add_filter": "Shto filtër", - "add_filter_description": "Klikoni për të shtuar një kusht filtri", - "add_location": "Shto vendndodhje", - "add_more_users": "Shto më shumë përdorues", - "add_partner": "Shto partner", - "add_path": "Shto path", - "add_photos": "Shto foto", - "add_tag": "Shto tag", - "add_to": "Shto në…", - "add_to_album": "Shto në album", - "add_to_album_bottom_sheet_added": "Shtuar në {album}", - "add_to_album_bottom_sheet_already_exists": "Existon në {album}", - "add_to_album_bottom_sheet_some_local_assets": "Disa asete lokale nuk mund të shtoheshin në album", - "add_to_album_toggle": "Aktivizo/çaktivizo zgjedhjen për {album}", - "add_to_albums": "Shto në albume", - "add_to_albums_count": "Shto në albume ({count})", - "add_to_bottom_bar": "Shto në", - "add_to_shared_album": "Shto në album të hapur", - "add_upload_to_stack": "Shto ngarkimin në stivë", - "add_url": "Shto URL", - "add_workflow_step": "Shto hap workflow", - "added_to_archive": "Shtuar në arkiv", - "added_to_favorites": "Shtuar tek të preferuarat", - "added_to_favorites_count": "Shtuar {count, number} në të preferuarat", - "admin": { - "add_exclusion_pattern_description": "Shto modele përjashtimi. Mbështetet globimi duke përdorur *, ** dhe ?. Për të injoruar të gjithë skedarët në çdo drejtori të quajtur \"Raw\", përdorni \"**/Raw/**\". Për të injoruar të gjithë skedarët që mbarojnë me \".tif\", përdorni \"**/*.tif\". Për të injoruar një shteg absolut, përdorni \"/path/to/ignore/**\".", - "admin_user": "Përdorues Administrator", - "asset_offline_description": "Ky aset i bibliotekës së jashtme nuk gjendet më në disk dhe është zhvendosur në koshin e plehrave. Nëse skedari është zhvendosur brenda bibliotekës, kontrolloni kronologjinë tuaj për asetin e ri përkatës. Për të rivendosur këtë aset, sigurohuni që shtegu i skedarit më poshtë të jetë i arritshëm nga Immich dhe skanoni bibliotekën.", - "authentication_settings": "Cilësimet e vërtetimit të përdoruesit", - "authentication_settings_description": "Manaxho passwordin, OAuth, dhe cilësime të tjera të", - "authentication_settings_disable_all": "Je i sigurt që dëshiron të çaktivizosh të gjitha metodat e hyrjes? Hyrja do të çaktivizohet plotësisht.", - "authentication_settings_reenable": "Për ta riaktivizuar, përdorni një Komandë Serveri.", - "background_task_job": "Detyrat në Sfond", - "backup_database": "Krijo demp të databaseit", - "backup_database_enable_description": "Aktivizo demp-et e bazës së të dhënave", - "backup_keep_last_amount": "Sasia e deponive të mëparshme për t'u mbajtur", - "backup_onboarding_1_description": "kopje në cloud ose në një vendndodhje tjetër fizike.", - "backup_onboarding_2_description": "kopje lokale në pajisje të ndryshme. Kjo përfshin skedarët kryesorë dhe një kopje rezervë të këtyre skedarëve lokalisht.", - "backup_onboarding_3_description": "kopje totale të të dhënave tuaja, duke përfshirë skedarët origjinalë. Kjo përfshin 1 kopje jashtë faqes dhe 2 kopje lokale.", - "backup_onboarding_description": "Rekomandohet një strategji 3-2-1 për ruajtjen e të dhënave tuaja. Duhet të ruani kopje të fotove/videove të ngarkuara, si dhe të bazës së të dhënave të Immich për një zgjidhje gjithëpërfshirëse të ruajtjes së të dhënave.", - "backup_onboarding_footer": "Për më shumë informacion për të krijuar një kopje rezervë të Immich, ju lutem referouni tek dokumentimi.", - "backup_onboarding_parts_title": "Një kopje rezervë 3-2-1 ka:", - "backup_onboarding_title": "Kopje rezervë", - "backup_settings": "Cilësimet e eksportimit të databazës", - "backup_settings_description": "Menaxho cilësimet e eksportimit të databazës.", - "cleared_jobs": "Detyrat u pastruan për: {job}", - "config_set_by_file": "Konfigurimi është aktualisht vendosur nga një skedar konfigurimi", - "confirm_delete_library": "A jeni i sigurt që dëshironi të fshini bibliotekën {library}?", - "confirm_delete_library_assets": "A jeni i sigurt që dëshironi ta fshini këtë bibliotekë? Kjo do të fshijë {count, plural, one {# element të përmbajtur} other {të gjithë # elementët e përmbajtur}} nga Immich dhe ky veprim nuk mund të zhbëhet. Skedarët do të mbeten në disk.", - "confirm_email_below": "Për të konfirmuar, shkruani \"{email}\" më poshtë", - "confirm_reprocess_all_faces": "A jeni i sigurt që dëshironi të rindërtoni të gjitha fytyrat? Kjo gjithashtu do të fshijë personat e emëruar.", - "confirm_user_password_reset": "A jeni i sigurt që dëshironi të rivendosni fjalëkalimin e {user}?", - "confirm_user_pin_code_reset": "A jeni i sigurt që dëshironi të rivendosni kodin PIN të {user}?", - "copy_config_to_clipboard_description": "Kopjo konfigurimin aktual të sistemit si objekt JSON në clipboard", - "create_job": "Krijo detyrë", - "cron_expression_description": "Vendosni intervalin e skanimit duke përdorur formatin Cron. Për më shumë informacion, ju lutem shikoni p.sh. Crontab Guru", - "disable_login": "Çaktivizo hyrjen", - "duplicate_detection_job_description": "Ekzekuto mësimin makinerik mbi skedarët për të zbuluar imazhe të ngjashme. Bazohet në Smart Search", - "exclusion_pattern_description": "Modelet e përjashtimit ju lejojnë të injoroni skedarë dhe dosje gjatë skanimit të bibliotekës suaj. Kjo është e dobishme nëse keni dosje që përmbajnë skedarë që nuk dëshironi të importoni, si p.sh. skedarët e papërpunuara.", - "export_config_as_json_description": "Shkarkoni konfigurimin aktual të sistemit si një skedar JSON", - "external_libraries_page_description": "Faqja e bibliotekës së jashtme për administratorin", - "face_detection": "Zbulimi i fytyrave", - "face_detection_description": "Zbulo fytyrat në skedarë duke përdorur mësimin makinerik. Për videot, konsiderohet vetëm miniatura. “Rifresko” (Refresh) përpunon përsëri të gjithë skedarët. “Rivendos” (Reset) gjithashtu fshin të gjitha të dhënat aktuale të fytyrave. “Mungon” (Missing) vendos në pritje skedarët që ende nuk janë përpunuar. Fytyrat e zbuluara do të vendosen në pritje për Njohjen e Fytyrave pas përfundimit të Zbulimit të Fytyrave, duke i grupuar ato te personat ekzistues ose të rinj.", - "failed_job_command": "Komanda {command} dështoi për detyrën: {job}", - "force_delete_user_warning": "KUJDES: Kjo do të heqë menjëherë përdoruesin dhe të gjithë skedarët e tij. Ky veprim nuk mund të zhbëhet dhe skedarët nuk mund të rikuperohen.", - "image_format": "Formati", - "image_format_description": "WebP prodhon skedarë më të vegjël se JPEG, por kodimi i tij është më i ngadaltë.", - "image_fullsize_description": "Imazh me madhësi të plotë pa metadata, përdoret kur zmadhohet", - "image_fullsize_enabled": "Aktivizo gjenerimin e imazhit me madhësi të plotë", - "image_fullsize_quality_description": "Cilësia e imazhit me madhësi të plotë nga 1-100. Sa më e lartë, aq më e mirë, por krijon skedarë më të mëdhenj.", - "image_fullsize_title": "Cilësimet e imazhit me madhësi të plotë", - "image_prefer_embedded_preview": "Prefero parapamjen e integruar", - "image_prefer_embedded_preview_setting_description": "Përdor parapamjet e integruara në fotot te papërpunuara si hyrje për përpunimin e imazhit, kur janë të disponueshme. Kjo mund të japë ngjyra më të sakta për disa imazhe, por cilësia e parapamjes varet nga kamera dhe imazhi mund të ketë më shumë artefakte të kompresimit.", - "image_prefer_wide_gamut": "Prefero gamën e gjerë të ngjyrave", - "image_preview_title": "Cilësimet e parapamjes" - }, - "download_original": "Shkarko origjinalin", - "download_paused": "Shkarkimi u pezullua", - "download_settings": "Shkarko", - "download_started": "Shkarkimi filloi", - "download_sucess": "Shkarkimi u krye me sukses", - "download_sucess_android": "Media u shkarkua tek DCIM/Immich", - "download_waiting_to_retry": "Duke pritur për ta provuar përsëri", - "downloading": "Duke u shkarkuar", - "downloading_asset_filename": "Duke shkarkuar asetin {filename}", - "downloading_from_icloud": "Duke shkarkuar nga iCloud", - "downloading_media": "Duke shkarkuar median", - "you_dont_have_any_shared_links": "Nuk keni asnjë link të shpërndarë", - "your_wifi_name": "Emri i Wi-Fi tuaj", - "zoom_image": "Zmadho imazhin", - "zoom_to_bounds": "Zmadho sipas kufijve" -} +{} diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index d656ac248e..0967ef424b 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -1,1935 +1 @@ -{ - "about": "О апликацији", - "account": "Налог", - "account_settings": "Подешавања налога", - "acknowledge": "Потврди", - "action": "Поступак", - "action_common_update": "Ажурирај", - "actions": "Поступци", - "active": "Активни", - "activity": "Активност", - "activity_changed": "Активност је {enabled, select, true {омогућена} other {онемогућена}}", - "add": "Додај", - "add_a_description": "Додај опис", - "add_a_location": "Додај локацију", - "add_a_name": "Додај име", - "add_a_title": "Додај наслов", - "add_birthday": "Додај рођендан", - "add_endpoint": "Додај адресу", - "add_exclusion_pattern": "Додај образац изузимања", - "add_location": "Додај локацију", - "add_more_users": "Додај кориснике", - "add_partner": "Додај партнера", - "add_path": "Додај путању", - "add_photos": "Додај фотографије", - "add_tag": "Додај ознаку", - "add_to": "Додај у…", - "add_to_album": "Додај у албум", - "add_to_album_bottom_sheet_added": "Додато у {album}", - "add_to_album_bottom_sheet_already_exists": "Већ у {album}", - "add_to_album_bottom_sheet_some_local_assets": "Неке локалне датотеке није могуће додати у албум", - "add_to_album_toggle": "Обрни избор за {album}", - "add_to_albums": "Додај у албуме", - "add_to_albums_count": "Додај у албуме ({count})", - "add_to_shared_album": "Додај у дељени албум", - "add_upload_to_stack": "Додај пренесено у групу", - "add_url": "Додај URL", - "added_to_archive": "Додато у архиву", - "added_to_favorites": "Додато у фаворите", - "added_to_favorites_count": "Додато {count, number} у фаворите", - "admin": { - "add_exclusion_pattern_description": "Додајте обрасце искључења. Кориштење *, ** и ? је подржано. Да бисте игнорисали све датотеке у било ком директоријуму под називом „Рав“, користите „**/Рав/**“. Да бисте игнорисали све датотеке које се завршавају на „.тиф“, користите „**/*.тиф“. Да бисте игнорисали апсолутну путању, користите „/патх/то/игноре/**“.", - "admin_user": "Администратор", - "asset_offline_description": "Ова датотека спољне библиотеке се више не налази на диску и премештена је у смеће. Ако је датотека премештена унутар библиотеке, проверите своју временску линију за нову одговарајућу датотеку. Да бисте вратили ову датотеку, уверите се да Immich може да приступи доле наведеној путањи датотеке и скенирајте библиотеку.", - "authentication_settings": "Подешавања за аутентификацију", - "authentication_settings_description": "Управљајте лозинком, OAuth-ом и другим подешавањима аутентификације", - "authentication_settings_disable_all": "Да ли сте сигурни да желите да онемогућите све методе пријављивања? Пријава ће бити потпуно онемогућена.", - "authentication_settings_reenable": "Да бисте поново омогућили, користите команду сервера.", - "background_task_job": "Позадински задаци", - "backup_database": "Креирајте резервну копију базе података", - "backup_database_enable_description": "Омогући дампове базе података", - "backup_keep_last_amount": "Количина претходних дампова које треба задржати", - "backup_onboarding_1_description": "копија изван места - у облаку или на другом физичком месту.", - "backup_onboarding_title": "Резервне копије", - "backup_settings": "Подешавања дампа базе података", - "backup_settings_description": "Уреди подешавања дампа базе података.", - "cleared_jobs": "Очишћени послови за: {job}", - "config_set_by_file": "Конфигурацију тренутно поставља конфигурациони фајл", - "confirm_delete_library": "Да ли стварно желите да избришете библиотеку {library} ?", - "confirm_delete_library_assets": "Да ли сте сигурни да желите да избришете ову библиотеку? Ово ће избрисати {count, plural, one {1 садржену датотеку} few {# садржене датотеке} other {# садржених датотека}} из Immich-a и акција се не може опозвати. Датотеке ће остати на диску.", - "confirm_email_below": "Да бисте потврдили, унесите \"{email}\" испод", - "confirm_reprocess_all_faces": "Да ли сте сигурни да желите да поново обрадите сва лица? Ово ће такође обрисати именоване особе.", - "confirm_user_password_reset": "Да ли сте сигурни да желите да ресетујете лозинку корисника {user}?", - "confirm_user_pin_code_reset": "Да ли сте сигурни да желите да ресетујете ПИН код корисника {user}?", - "create_job": "Креирајте посао", - "cron_expression": "Црон израз (еxпрессион)", - "cron_expression_description": "Подесите интервал скенирања користећи cron формат. За више информација погледајте нпр. Crontab Guru", - "cron_expression_presets": "Предефинисана подешавања Црон израза (еxпрессион)", - "disable_login": "Онемогући пријаву", - "duplicate_detection_job_description": "Покрените машинско учење на средствима да бисте открили сличне слике. Ослања се на паметну претрагу", - "exclusion_pattern_description": "Обрасци изузимања вам омогућавају да игноришете датотеке и фасцикле када скенирате библиотеку. Ово је корисно ако имате фасцикле које садрже датотеке које не желите да увезете, као што су RAW датотеке.", - "face_detection": "Детекција лица", - "face_detection_description": "Откријте лица у датотекама помоћу машинског учења. За видео снимке се узима у обзир само сличица. „Освежи“ (поновно) обрађује све датотеке. „Ресетовање“ додатно брише све тренутне податке о лицу. „Недостајуће“ додаје датотеке које још нису обрађене. Откривена лица ће бити стављена у ред за препознавање лица након што се откривање лица заврши, групишући их у постојеће или нове особе.", - "facial_recognition_job_description": "Групише откривана лица и додаје их постојећим особама. Овај корак се покреће након што је препознавање лица завршено. „Ресетуј“ (поновно) групише сва лица. „Недостајућа“ додаје лица којима није додељена особа.", - "failed_job_command": "Команда {command} није успела за посао: {job}", - "force_delete_user_warning": "УПОЗОРЕЊЕ: Ово ће одмах уклонити корисника и све датотеке. Ово се не може опозвати и датотеке се не могу опоравити.", - "image_format": "Формат", - "image_format_description": "WебП производи мање датотеке од ЈПЕГ, али се спорије кодира.", - "image_fullsize_description": "Слика у пуној величини са огољеним метаподацима, користи се када је увећана", - "image_fullsize_enabled": "Омогући генерисање слика у пуној величини", - "image_fullsize_enabled_description": "Генерише слику пуне величине за формате који нису прилагођени вебу. Када је „Преферирај уграђени преглед“ омогућен, уграђени прегледи се користе непосредно без конверзије. Не утиче на формате прилагођене вебу као што је JPEG.", - "image_fullsize_quality_description": "Квалитет слике у пуној величини од 1 до 100. Више је боље, али производи веће датотеке.", - "image_fullsize_title": "Подешавања слике у пуној величини", - "image_prefer_embedded_preview": "Преферирајте уграђени преглед", - "image_prefer_embedded_preview_setting_description": "Користите уграђене прегледе у РАW фотографије као улаз за обраду слике када су доступне. Ово може да произведе прецизније боје за неке слике, али квалитет прегледа зависи од камере и слика може имати више неправилности компресије.", - "image_prefer_wide_gamut": "Преферирајте широк спектар", - "image_prefer_wide_gamut_setting_description": "Користите Дисплаy П3 за сличице. Ово боље чува живописност слика са широким просторима боја, али слике могу изгледати другачије на старим уређајима са старом верзијом претраживача. сРГБ слике се чувају као сРГБ да би се избегле промене боја.", - "image_preview_description": "Слика средње величине са уклоњеним метаподацима, која се користи приликом прегледа једног елемента и за машинско учење", - "image_preview_quality_description": "Квалитет прегледа од 1 до 100. Више је боље, али производи веће датотеке и може успорити одзив апликације. Постављање ниске вредности може утицати на квалитет машинског учења.", - "image_preview_title": "Подешавања прегледа", - "image_quality": "Квалитет", - "image_resolution": "Резолуција", - "image_resolution_description": "Веће резолуције могу да сачувају више детаља, али им је потребно више времена за кодирање, имају веће величине датотека и могу да успоре одзив апликације.", - "image_settings": "Подешавања слике", - "image_settings_description": "Управљајте квалитетом и резолуцијом генерисаних слика", - "image_thumbnail_description": "Мала сличица са огољеним метаподацима, која се користи приликом прегледа група фотографија као што је главна временска линија", - "image_thumbnail_quality_description": "Квалитет сличица од 1 до 100. Више је боље, али производи веће датотеке и може успорити одзив апликације.", - "image_thumbnail_title": "Подешавања сличица", - "job_concurrency": "{job} паралелност", - "job_created": "Посао креиран", - "job_not_concurrency_safe": "Овај посао није безбедан да буде паралелно активан.", - "job_settings": "Подешавања посла", - "job_settings_description": "Управљајте паралелношћу послова", - "jobs_delayed": "{jobCount, plural, one {# одложени} few {# одложена} other {# одложених}}", - "jobs_failed": "{jobCount, plural, one {# неуспешни} few {# неуспешна} other {# неуспешних}}", - "library_created": "Направљена библиотека: {library}", - "library_deleted": "Библиотека је избрисана", - "library_scanning": "Периодично скенирање", - "library_scanning_description": "Конфигуришите периодично скенирање библиотеке", - "library_scanning_enable_description": "Омогући периодично скенирање библиотеке", - "library_settings": "Спољна библиотека", - "library_settings_description": "Управљајте подешавањима спољне библиотеке", - "library_tasks_description": "Обављај задатке библиотеке", - "library_watching_enable_description": "Пратите спољне библиотеке за промене датотека", - "library_watching_settings": "Праћење библиотеке [ЕКСПЕРИМЕНТАЛНО]", - "library_watching_settings_description": "Аутоматски пратите промењене датотеке", - "logging_enable_description": "Омогући евидентирање", - "logging_level_description": "Када је омогућено, који ниво евиденције користити.", - "logging_settings": "Евидентирање", - "machine_learning_availability_checks": "Провере доступности", - "machine_learning_availability_checks_enabled": "Омогући провере доступности", - "machine_learning_availability_checks_interval": "Интервал провере", - "machine_learning_availability_checks_interval_description": "Интервал у милисекундама између провера доступности", - "machine_learning_availability_checks_timeout": "Време за чекање на одговор", - "machine_learning_availability_checks_timeout_description": "Време за чекање на одговор у милисекундама", - "machine_learning_clip_model": "Модел ЦЛИП", - "machine_learning_clip_model_description": "Назив ЦЛИП модела је наведен овде. Имајте на уму да морате поново да покренете посао „Паметно претраживање“ за све слике након промене модела.", - "machine_learning_duplicate_detection": "Детекција дупликата", - "machine_learning_duplicate_detection_enabled": "Омогући откривање дупликата", - "machine_learning_duplicate_detection_enabled_description": "Ако је онемогућено, потпуно идентичне датотеке ће и даље бити уклоњене.", - "machine_learning_duplicate_detection_setting_description": "Користите уграђен ЦЛИП да бисте пронашли вероватне дупликате", - "machine_learning_enabled": "Омогући машинско учење", - "machine_learning_enabled_description": "Ако је онемогућено, све функције МЛ ће бити онемогућене без обзира на доле наведена подешавања.", - "machine_learning_facial_recognition": "Препознавање лица", - "machine_learning_facial_recognition_description": "Откривање, препознавање и груписање лица на сликама", - "machine_learning_facial_recognition_model": "Модел за препознавање лица", - "machine_learning_facial_recognition_model_description": "Модели су наведени у опадајућем редоследу величине. Већи модели су спорији и користе више меморије, али дају боље резултате. Имајте на уму да морате поново да покренете задатак откривања лица за све слике након промене модела.", - "machine_learning_facial_recognition_setting": "Омогући препознавање лица", - "machine_learning_facial_recognition_setting_description": "Ако је онемогућено, слике неће бити кодиране за препознавање лица и неће попуњавати одељак Људи на страници Истражи.", - "machine_learning_max_detection_distance": "Максимална удаљеност детекције", - "machine_learning_max_detection_distance_description": "Највеће растојање (разлика) између две слике да би се сматрале дупликатима, у распону од 0,001 до 0,1. Веће вредности ће открити више дупликата, али могу довести до лажних позитивних резултата.", - "machine_learning_max_recognition_distance": "Максимална удаљеност препознавања", - "machine_learning_max_recognition_distance_description": "Највећа удаљеност (разлика) између два лица која се сматрају истом особом, у распону од 0 до 2. Смањење овог броја може спречити означавање два лица као исте особе, док повећање може спречити означавање истог лица као две различите особе. Имајте на уму да је лакше спојити две особе него поделити једну особу на двоје, па је боље грешити ка нижем прагу када је то могуће.", - "machine_learning_min_detection_score": "Најмањи резултат детекције", - "machine_learning_min_detection_score_description": "Најмања вредност поузданости за открување лица, од 0 до 1. Ниже вредности ће открити више лица, али могу довести до лажних позитивних резултата.", - "machine_learning_min_recognized_faces": "Најмање препознатих лица", - "machine_learning_min_recognized_faces_description": "Најмањи број препознатих лица за прављење особе. Повећање овога чини препознавање лица прецизнијим по цену повећања вероватноће да лице не буде додељено особи.", - "machine_learning_settings": "Подешавања машинског учења", - "machine_learning_settings_description": "Управљајте функцијама и подешавањима машинског учења", - "machine_learning_smart_search": "Паметна претрага", - "machine_learning_smart_search_description": "Потраживање слика семантички користећи уграђени CLIP", - "machine_learning_smart_search_enabled": "Омогући паметну претрагу", - "machine_learning_smart_search_enabled_description": "Ако је онемогућено, слике неће бити кодиране за паметну претрагу.", - "machine_learning_url_description": "URL сервера за машинско учење. Ако је наведено више URL адреса, сваки сервер ће бити покушаван појединачно док не одговори успешно, редом од првог до последњег. Сервери који не одговоре биће привремено игнорисани док се поново не повежу са мрежом.", - "manage_concurrency": "Управљање паралелношћу", - "manage_log_settings": "Управљајте подешавањима евиденције", - "map_dark_style": "Тамни стил", - "map_enable_description": "Омогући карактеристике мапе", - "map_gps_settings": "Мап & ГПС подешавања", - "map_gps_settings_description": "Управљајте поставкама мапе и ГПС-а (обрнуто геокодирање)", - "map_implications": "Функција мапе се ослања на екстерну услугу плочица (тилес.иммицх.цлоуд)", - "map_light_style": "Светли стил", - "map_manage_reverse_geocoding_settings": "Управљајте подешавањима Обрнуто геокодирање", - "map_reverse_geocoding": "Обрнуто геокодирање", - "map_reverse_geocoding_enable_description": "Омогући обрнуто геокодирање", - "map_reverse_geocoding_settings": "Подешавања обрнутог геокодирања", - "map_settings": "Подешавање мапе", - "map_settings_description": "Управљајте подешавањима мапе", - "map_style_description": "URL до стyле.јсон мапе тема изгледа", - "memory_cleanup_job": "Чишћење меморије", - "memory_generate_job": "Генерација меморије", - "metadata_extraction_job": "Извод метаподатака", - "metadata_extraction_job_description": "Извуците информације о метаподацима из сваке датотеке, као што су ГПС, лица и резолуција", - "metadata_faces_import_setting": "Омогућите (енабле) додавање лица", - "metadata_faces_import_setting_description": "Додајте лица из EXIF података слике и сличних метаподатака", - "metadata_settings": "Подешавање метаподатака", - "metadata_settings_description": "Управљајте подешавањима метаподатака", - "migration_job": "Миграције", - "migration_job_description": "Пренесите сличице датотека и лица у најновију структуру директоријума", - "nightly_tasks_cluster_new_faces_setting": "Групиши нова лица", - "nightly_tasks_database_cleanup_setting": "Задаци чишћења базе података", - "nightly_tasks_generate_memories_setting": "Прављење успомена", - "nightly_tasks_generate_memories_setting_description": "Направи нове успомене из датотека", - "nightly_tasks_missing_thumbnails_setting": "Прављењњ недостајућих сличица", - "nightly_tasks_missing_thumbnails_setting_description": "Стави датотеке без сличица у ред за прављење сличица", - "nightly_tasks_settings": "Подешавања ноћних задатака", - "nightly_tasks_settings_description": "Управљај ноћним задацима", - "nightly_tasks_start_time_setting": "Време почетка", - "no_paths_added": "Нема додатих путања", - "no_pattern_added": "Није додат образац", - "note_apply_storage_label_previous_assets": "Напомена: Да бисте применили ознаку за складиштење на претходно отпремљена средства, покрените", - "note_cannot_be_changed_later": "НАПОМЕНА: Ово се касније не може променити!", - "notification_email_from_address": "Са адресе", - "notification_email_from_address_description": "Адреса е-поште пошиљаоца, на пример: \"Immich фото сервер \". Користи адресу са које смеш слати поруке.", - "notification_email_host_description": "Хост сервера е-поште (нпр. смтп.иммицх.апп)", - "notification_email_ignore_certificate_errors": "Занемарите грешке сертификата", - "notification_email_ignore_certificate_errors_description": "Игноришите грешке у валидацији ТЛС сертификата (не препоручује се)", - "notification_email_password_description": "Лозинка за употребу при аутентификацији са сервером е-поште", - "notification_email_port_description": "Порт сервера е-поште (нпр. 25, 465 или 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Користи SMTPS (SMTP преко TLS)", - "notification_email_sent_test_email_button": "Пошаљите пробну е-пошту и сачувајте", - "notification_email_setting_description": "Подешавања за слање обавештења путем е-поште", - "notification_email_test_email": "Пошаљите пробну е-пошту", - "notification_email_test_email_failed": "Слање пробне е-поште није успело, проверите вредности", - "notification_email_test_email_sent": "Пробна е-пошта је послата на {email}. Проверите своје пријемно сандуче.", - "notification_email_username_description": "Корисничко име које се користи приликом аутентификације на серверу е-поште", - "notification_enable_email_notifications": "Омогући обавештења путем е-поште", - "notification_settings": "Подешавања обавештења", - "notification_settings_description": "Управљајте подешавањима обавештења, укључујући е-пошту", - "oauth_auto_launch": "Аутоматско покретање", - "oauth_auto_launch_description": "Покрените OAuth ток пријављивања аутоматски након навигације на страницу за пријаву", - "oauth_auto_register": "Аутоматска регистрација", - "oauth_auto_register_description": "Аутоматски региструј нове кориснике након што се пријаве помоћу OAuth-а", - "oauth_button_text": "Текст дугмета", - "oauth_client_secret_description": "Потребно ако OAuth провајдер не подржава ПКЦЕ (Прооф Кеy фор Цоде Еxцханге)", - "oauth_enable_description": "Пријава помоћу OAuth", - "oauth_mobile_redirect_uri": "УРИ за преусмеравање мобилних уређаја", - "oauth_mobile_redirect_uri_override": "Замена УРИ-ја мобилног преусмеравања", - "oauth_mobile_redirect_uri_override_description": "Омогући када OAuth добављач (провајдер) не дозвољава мобилни URI, као што је \"{callback}\"", - "oauth_settings": "ОАуторизација", - "oauth_settings_description": "Управљајте подешавањима за пријаву са ОАуторизацијом", - "oauth_settings_more_details": "За више детаља о овој функцији погледајте документе.", - "oauth_storage_label_claim": "Захтев за налепницу за складиштење", - "oauth_storage_label_claim_description": "Аутоматски подесите ознаку за складиштење корисника на вредност овог захтева.", - "oauth_storage_quota_claim": "Захтев за квоту складиштења", - "oauth_storage_quota_claim_description": "Аутоматски подесите квоту меморијског простора корисника на вредност овог захтева.", - "oauth_storage_quota_default": "Подразумевана квота за складиштење (ГиБ)", - "oauth_storage_quota_default_description": "Квота у GiB која се користи када није задата преко OAuth.", - "oauth_timeout": "Временско ограничење захтева", - "oauth_timeout_description": "Временско ограничење за захтеве у милисекундама", - "password_enable_description": "Пријава помоћу е-поште и лозинке", - "password_settings": "Лозинка за пријаву", - "password_settings_description": "Управљајте подешавањима за пријаву лозинком", - "paths_validated_successfully": "Све путање су успешно потврђене", - "person_cleanup_job": "Чишћење особа", - "quota_size_gib": "Величина квоте (ГиБ)", - "refreshing_all_libraries": "Освежавање свих библиотека", - "registration": "Регистрација администратора", - "registration_description": "Пошто сте први корисник на систему, бићете додељени као Админ и одговорни сте за административне задатке, а додатне кориснике ћете креирати ви.", - "require_password_change_on_login": "Захтевати од корисника да промени лозинку при првом пријављивању", - "reset_settings_to_default": "Ресетујте подешавања на подразумеване вредности", - "reset_settings_to_recent_saved": "Ресетујте подешавања на недавно сачувана подешавања", - "scanning_library": "Скенирање библиотеке", - "search_jobs": "Тражи послове…", - "send_welcome_email": "Пошаљите е-пошту добродошлице", - "server_external_domain_settings": "Екстерни домаин", - "server_external_domain_settings_description": "Домен за јавне дељене везе, укључујући https(s)://", - "server_public_users": "Јавни корисници", - "server_public_users_description": "Сви корисници (име и адреса е-поште) су наведени приликом додавања корисника у дељене албуме. Када је онемогућен, списак корисника ће бити доступан само администраторима.", - "server_settings": "Подешавања сервера", - "server_settings_description": "Управљајте подешавањима сервера", - "server_welcome_message": "Порука добродошлице", - "server_welcome_message_description": "Порука која се приказује на страници за пријаву.", - "sidecar_job": "Бочни (сидецар) метаподаци", - "sidecar_job_description": "Откријте или синхронизујте бочне (сидецар) метаподатке из система датотека", - "slideshow_duration_description": "Број секунди за приказ сваке слике", - "smart_search_job_description": "Покрените машинско учење на датотекама да бисте подржали паметну претрагу", - "storage_template_date_time_description": "Временска ознака креирања датотеке се користи за информације о датуму и времену", - "storage_template_date_time_sample": "Пример времена {date}", - "storage_template_enable_description": "Омогући механизам за обрасце за складиштење", - "storage_template_hash_verification_enabled": "Хеш верификација омогућена", - "storage_template_hash_verification_enabled_description": "Омогућава хеш верификацију, не онемогућавајте ово осим ако нисте сигурни у последице", - "storage_template_migration": "Миграција шаблона за складиштење", - "storage_template_migration_description": "Примените тренутни {template} на претходно отпремљене елементе", - "storage_template_migration_info": "Промене шаблона ће се применити само на нове датотеке. Да бисте ретроактивно применили шаблон на претходно отпремљене датотеке, покрените {job}.", - "storage_template_migration_job": "Посао миграције складишта", - "storage_template_more_details": "За више детаља о овој функцији погледајте Шаблон за складиште и његове импликације", - "storage_template_path_length": "Приближно ограничење дужине путање: {length, number}/{limit, number}", - "storage_template_settings": "Шаблон за складиштење", - "storage_template_settings_description": "Управљајте структуром директоријума и именом датотеке средства за отпремање", - "storage_template_user_label": "{label} је ознака за складиштење корисника", - "system_settings": "Подешавања система", - "tag_cleanup_job": "Чишћење ознака", - "template_email_available_tags": "Можете да користите следеће променљиве у свом шаблону: {tags}", - "template_email_if_empty": "Ако је шаблон празан, користиће се подразумевана адреса е-поште.", - "template_email_invite_album": "Шаблон за позив у албум", - "template_email_preview": "Преглед", - "template_email_settings": "Шаблони е-поште", - "template_email_update_album": "Ажурирајте шаблон албума", - "template_email_welcome": "Шаблон е-поште добродошлице", - "template_settings": "Шаблони обавештења", - "template_settings_description": "Управљајте прилагођеним шаблонима за обавештења", - "theme_custom_css_settings": "Прилагођени ЦСС", - "theme_custom_css_settings_description": "Каскадни листови стилова (ЦСС) омогућавају прилагођавање дизајна Immich-a.", - "theme_settings": "Подешавање тема", - "theme_settings_description": "Управљајте прилагођавањем Immich wеб интерфејса", - "thumbnail_generation_job": "Генеришите сличице", - "thumbnail_generation_job_description": "Генеришите велике, мале и замућене сличице за свако средство, као и сличице за сваку особу", - "transcoding_acceleration_api": "АПИ за убрзање", - "transcoding_acceleration_api_description": "АПИ који ће комуницирати са вашим уређајем да би убрзао транскодирање. Ово подешавање је 'најбољи напор': враћа се на софтверско транскодирање у случају неуспеха. VP9 може или не мора да ради у зависности од вашег хардвера.", - "transcoding_acceleration_nvenc": "НВЕНЦ (захтева НВИДИА ГПУ)", - "transcoding_acceleration_qsv": "Qуицк Сyнц (захтева Интел CPU 7. генерације или новији)", - "transcoding_acceleration_rkmpp": "РКМПП (само на Роцкцхип СОЦ-овима)", - "transcoding_acceleration_vaapi": "Видео акцелерација АПИ (ВААПИ)", - "transcoding_accepted_audio_codecs": "Прихваћени аудио кодеци", - "transcoding_accepted_audio_codecs_description": "Изаберите које аудио кодеке не треба транскодирати. Користи се само за одређене политике транскодирања.", - "transcoding_accepted_containers": "Прихваћени контејнери", - "transcoding_accepted_containers_description": "Изаберите који формати контејнера не морају да се ремуксују у МП4. Користи се само за одређене услове транскодирања.", - "transcoding_accepted_video_codecs": "Прихваћени видео кодеци", - "transcoding_accepted_video_codecs_description": "Изаберите које видео кодеке није потребно транскодирати. Користи се само за одређене политике транскодирања.", - "transcoding_advanced_options_description": "Опције које већина корисника не би требало да мењају", - "transcoding_audio_codec": "Аудио кодек", - "transcoding_audio_codec_description": "Опус је опција највишег квалитета, али има лошију компатибилност са старим уређајима или софтвером.", - "transcoding_bitrate_description": "Видео снимци већи од максималне брзине преноса или нису у прихваћеном формату", - "transcoding_codecs_learn_more": "Да бисте сазнали више о терминологији која се овде користи, погледајте ФФмпег документацију за H.264 кодек, HEVC кодек и VP9 кодек.", - "transcoding_constant_quality_mode": "Режим константног квалитета", - "transcoding_constant_quality_mode_description": "ИЦQ је бољи од ЦQП-а, али неки уређаји за хардверско убрзање не подржавају овај режим. Подешавање ове опције ће преферирати наведени режим када се користи кодирање засновано на квалитету. НВЕНЦ игнорише јер не подржава ИЦQ.", - "transcoding_constant_rate_factor": "Фактор константне стопе (-црф)", - "transcoding_constant_rate_factor_description": "Ниво квалитета видеа. Типичне вредности су 23 за H.264, 28 за HEVC, 31 за VP9 и 35 за АВ1. Ниже је боље, али производи веће датотеке.", - "transcoding_disabled_description": "Немојте транскодирати ниједан видео, може прекинути репродукцију на неким клијентима", - "transcoding_encoding_options": "Опције Кодирања", - "transcoding_encoding_options_description": "Подесите кодеке, резолуцију, квалитет и друге опције за кодиране видео записе", - "transcoding_hardware_acceleration": "Хардверско убрзање", - "transcoding_hardware_acceleration_description": "Експериментално: брже транскодирање али може смањити квалитет при истом протоку", - "transcoding_hardware_decoding": "Хардверско декодирање", - "transcoding_hardware_decoding_setting_description": "Омогућава убрзање од краја до краја уместо да само убрзава кодирање. Можда неће радити на свим видео снимцима.", - "transcoding_max_b_frames": "Максимални Б-кадри", - "transcoding_max_b_frames_description": "Више вредности побољшавају ефикасност компресије, али успоравају кодирање. Можда није компатибилно са хардверским убрзањем на старијим уређајима. 0 онемогућава Б-кадре, док -1 аутоматски поставља ову вредност.", - "transcoding_max_bitrate": "Максимални битрате", - "transcoding_max_bitrate_description": "Подешавање највећег протока може учинити величине датотека предвидљивијим по цену незнатно нижег квалитета. При 720p, типичне вредности су 2600 kbit/s за VP9 или HEVC, или 4500 kbit/s за H.264. Онемогућено ако је постављено на 0. Када није задата јединица мере, k (за kbit/s) се подразумева; тако да 5000, 5000k и 5M представљају исту вредност.", - "transcoding_max_keyframe_interval": "Максимални интервал кеyфраме-а", - "transcoding_max_keyframe_interval_description": "Поставља максималну удаљеност кадрова између кључних кадрова. Ниже вредности погоршавају ефикасност компресије, али побољшавају време тражења и могу побољшати квалитет сцена са брзим кретањем. 0 аутоматски поставља ову вредност.", - "transcoding_optimal_description": "Видео снимци већи од циљне резолуције или нису у прихваћеном формату", - "transcoding_policy": "Услови Транскодирања", - "transcoding_policy_description": "Одреди кад да се транскодира видео", - "transcoding_preferred_hardware_device": "Жељени хардверски уређај", - "transcoding_preferred_hardware_device_description": "Односи се само на ВААПИ и QСВ. Поставља дри ноде који се користи за хардверско транскодирање.", - "transcoding_preset_preset": "Унапред подешена подешавања (-пресет)", - "transcoding_preset_preset_description": "Брзина компресије. Спорије унапред подешене вредности производе мање датотеке и повећавају квалитет када циљате одређену брзину преноса. VP9 игнорише брзине изнад 'брже'.", - "transcoding_reference_frames": "Референтни оквири (фрамес)", - "transcoding_reference_frames_description": "Број оквира (фрамес) за референцу приликом компресије датог оквира. Више вредности побољшавају ефикасност компресије, али успоравају кодирање. 0 аутоматски поставља ову вредност.", - "transcoding_required_description": "Само видео снимци који нису у прихваћеном формату", - "transcoding_settings": "Подешавања видео транскодирања", - "transcoding_settings_description": "Управљајте резолуцијом и информацијама о кодирању видео датотека", - "transcoding_target_resolution": "Циљана резолуција", - "transcoding_target_resolution_description": "Веће резолуције могу да сачувају више детаља, али им је потребно више времена за кодирање, имају веће величине датотека и могу да смање брзину апликације.", - "transcoding_temporal_aq": "Временски (Темпорал) АQ", - "transcoding_temporal_aq_description": "Односи се само на NVENC. Временска адаптивна квантизација (Temporal Adaptive Quantization) повећава квалитет сцена са високим детаљима и спорим покретима. Можда није компатибилан са старијим уређајима.", - "transcoding_threads": "Нити (тхреадс)", - "transcoding_threads_description": "Више вредности доводе до бржег кодирања, али остављају мање простора серверу за обраду других задатака док је активан. Ова вредност не би требало да буде већа од броја CPU језгара. Максимизира искоришћеност ако је подешено на 0.", - "transcoding_tone_mapping": "Мапирање (тone-маппинг)", - "transcoding_tone_mapping_description": "Покушава да се сачува изглед ХДР видео записа када се конвертују у СДР. Сваки алгоритам прави различите компромисе за боју, детаље и осветљеност. Хабле чува детаље, Мобиус чува боју, а Раеинхард светлину.", - "transcoding_transcode_policy": "Услови транскодирања", - "transcoding_transcode_policy_description": "Услови о томе када видео треба транскодирати. ХДР видео снимци ће увек бити транскодирани (осим ако је транскодирање онемогућено).", - "transcoding_two_pass_encoding": "Двопролазно кодирање", - "transcoding_two_pass_encoding_setting_description": "Транскодирајте у два пролаза да бисте произвели боље кодиране видео записе. Када је максимална брзина у битовима омогућена (потребна за рад са H.264 и HEVC), овај режим користи опсег брзине у битовима заснован на максималној брзини (маx битрате) и игнорише ЦРФ. За VP9, ЦРФ се може користити ако је максимална брзина преноса онемогућена.", - "transcoding_video_codec": "Видео кодек", - "transcoding_video_codec_description": "VP9 има високу ефикасност и веб компатибилност, али му је потребно више времена за транскодирање. HEVC ради слично, али има нижу wеб компатибилност. H.264 је широко компатибилан и брзо се транскодира, али производи много веће датотеке. AV1 је најефикаснији кодек, али недостаје подршка за њега на старијим уређајима.", - "trash_enabled_description": "Омогућите функције Отпада", - "trash_number_of_days": "Број дана", - "trash_number_of_days_description": "Број дана за држање датотека у отпаду пре него што их трајно уклоните", - "trash_settings": "Подешавања Отпада", - "trash_settings_description": "Управљајте подешавањима Отпада", - "user_cleanup_job": "Чишћење корисника", - "user_delete_delay": "Налог и датотеке {user} биће заказани за трајно брисање за {delay, plural, one {# дан} other {# дана}}.", - "user_delete_delay_settings": "Избриши уз кашњење", - "user_delete_delay_settings_description": "Број дана након уклањања за трајно брисање корисничког налога и датотека. Посао брисања корисника се покреће у поноћ да би се проверили корисници који су спремни за брисање. Промене ове поставке ће бити процењене при следећем извршењу.", - "user_delete_immediately": "Налог и датотеке {user} ће одмах бити стављени на чекање за трајно брисање.", - "user_delete_immediately_checkbox": "Ставите корисника и датотеке у ред за тренутно брисање", - "user_details": "Детаљи корисника", - "user_management": "Управљање корисницима", - "user_password_has_been_reset": "Лозинка корисника је ресетована:", - "user_password_reset_description": "Молимо да доставите привремену лозинку кориснику и обавестите га да ће морати да промени лозинку приликом следећег пријављивања.", - "user_restore_description": "Налог {user} ће бити враћен.", - "user_restore_scheduled_removal": "Врати корисника - заказано уклањање за {date, date, лонг}", - "user_settings": "Подешавања корисника", - "user_settings_description": "Управљајте корисничким подешавањима", - "version_check_enabled_description": "Омогући проверу нових издања", - "version_check_implications": "Функција провере верзије се ослања на периодичну комуникацију са гитхуб.цом", - "version_check_settings": "Провера верзије", - "version_check_settings_description": "Омогући/онемогући обавештење о новој верзији", - "video_conversion_job": "Транскодирање видео записа", - "video_conversion_job_description": "Транскодирајте видео записе за ширу компатибилност са прегледачима и уређајима" - }, - "admin_email": "Администраторска Е-адреса", - "admin_password": "Администраторска Лозинка", - "administration": "Администрација", - "advanced": "Напредно", - "advanced_settings_enable_alternate_media_filter_subtitle": "Користите ову опцију за филтрирање медија током синхронизације на основу алтернативних критеријума. Покушајте ово само ако имате проблема са апликацијом да открије све албуме.", - "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНТАЛНО] Користите филтер за синхронизацију албума на алтернативном уређају", - "advanced_settings_log_level_title": "Ниво евиденције (лог): {level}", - "advanced_settings_prefer_remote_subtitle": "Неки уређаји веома споро учитавају сличице локалних датотека. Укључи ово подешавање да би се уместо њих користиле слике са сервера.", - "advanced_settings_prefer_remote_title": "Преферирајте удаљене слике", - "advanced_settings_proxy_headers_subtitle": "Дефинишите прокси заглавља које Immich треба да пошаље са сваким мрежним захтевом", - "advanced_settings_proxy_headers_title": "Посебна прокси заглавља (headers) [ЕКСПЕРИМЕНТАЛНО]", - "advanced_settings_self_signed_ssl_subtitle": "Прескаче верификацију SSL сертификата за крајњу тачку сервера. Обавезно за самопотписане сертификате.", - "advanced_settings_self_signed_ssl_title": "Дозволи самопотписане SSL сертификате [ЕКСПЕРИМЕНТАЛНО]", - "advanced_settings_sync_remote_deletions_subtitle": "Аутоматски избришите или вратите средство на овом уређају када се та радња предузме на вебу", - "advanced_settings_sync_remote_deletions_title": "Синхронизујте удаљена брисања [ЕКСПЕРИМЕНТАЛНО]", - "advanced_settings_tile_subtitle": "Напредна корисничка подешавања", - "advanced_settings_troubleshooting_subtitle": "Омогући додатне функције за решавање проблема", - "advanced_settings_troubleshooting_title": "Решавање проблема", - "age_months": "Старост{months, plural, one {# месец} other {# месеци}}", - "age_year_months": "Старост 1 година, {months, plural, one {# месец} other {# месец(а/и)}}", - "age_years": "{years, plural, other {Старост #}}", - "album_added": "Албум додан", - "album_added_notification_setting_description": "Прими обавештење е-поштом кад будеш додан у дељен албум", - "album_cover_updated": "Омот албума ажуриран", - "album_delete_confirmation": "Да ли стварно желите да избришете албум {album}?", - "album_delete_confirmation_description": "Ако се овај албум дели, други корисници више неће моћи да му приступе.", - "album_deleted": "Албум обрисан", - "album_info_card_backup_album_excluded": "ИСКЛЈУЧЕНО", - "album_info_card_backup_album_included": "УКЛЈУЧЕНО", - "album_info_updated": "Информација албума ажурирана", - "album_leave": "Напустити албум?", - "album_leave_confirmation": "Да ли стварно желите да напустите {album}?", - "album_name": "Име албума", - "album_options": "Опције албума", - "album_remove_user": "Уклонити корисника?", - "album_remove_user_confirmation": "Да ли сте сигурни да желите да уклоните {user}?", - "album_share_no_users": "Изгледа да сте поделили овај албум са свим корисницима или да немате ниједног корисника са којим бисте делили.", - "album_summary": "Сажетак албума", - "album_updated": "Албум ажуриран", - "album_updated_setting_description": "Примите обавештење е-поштом када дељени албум има нова својства", - "album_user_left": "Напустио/ла {album}", - "album_user_removed": "Уклоњен {user}", - "album_viewer_appbar_delete_confirm": "Да ли сте сигурни да желите да избришете овај албум са свог налога?", - "album_viewer_appbar_share_err_delete": "Неуспешно брисање албума", - "album_viewer_appbar_share_err_leave": "Неуспешно излажење из албума", - "album_viewer_appbar_share_err_remove": "Проблеми са брисањем записа из албума", - "album_viewer_appbar_share_err_title": "Неуспешно мењање назива албума", - "album_viewer_appbar_share_leave": "Изађи из албума", - "album_viewer_appbar_share_to": "Подели са", - "album_viewer_page_share_add_users": "Додај кориснике", - "album_with_link_access": "Нека свако ко има везу види фотографије и људе у овом албуму.", - "albums": "Албуми", - "albums_count": "{count, plural, one {{count, number} Албум} few {{count, number} Албуми} other {{count, number} Албуми}}", - "albums_default_sort_order": "Подразумевани начин ређања албума", - "all": "Све", - "all_albums": "Сви албуми", - "all_people": "Све особе", - "all_videos": "Сви видео снимци", - "allow_dark_mode": "Дозволи тамни режим", - "allow_edits": "Дозволи уређење", - "allow_public_user_to_download": "Дозволите јавном кориснику да преузме (доwнлоад-ује)", - "allow_public_user_to_upload": "Дозволи јавном кориснику да отпреми (уплоад-ује)", - "alt_text_qr_code": "Слика QР кода", - "anti_clockwise": "У смеру супротном од казаљке на сату", - "api_key": "АПИ кључ (кеy)", - "api_key_description": "Ова вредност ће бити приказана само једном. Обавезно копирајте пре него што затворите прозор.", - "api_key_empty": "Име вашег АПИ кључа не би требало да буде празно", - "api_keys": "АПИ кључеви (кеyс)", - "app_architecture_variant": "Варијанта (архитектура)", - "app_bar_signout_dialog_content": "Да ли сте сигурни да желите да се одјавите?", - "app_bar_signout_dialog_ok": "Да", - "app_bar_signout_dialog_title": "Одјавите се", - "app_settings": "Подешавања апликације", - "app_stores": "Продавнице апликација", - "app_update_available": "Доступно је ажурирање апликације", - "appears_in": "Појављује се у", - "apply_count": "Примени ({count, number})", - "archive": "Архива", - "archive_action_prompt": "{count} додато у архиву", - "archive_or_unarchive_photo": "Архивирајте или поништите архивирање фотографије", - "archive_page_no_archived_assets": "Нису пронађена архивирана средства", - "archive_page_title": "Архива ({count})", - "archive_size": "Величина архиве", - "archive_size_description": "Подеси величину архиве за преузимање (у ГиБ)", - "archived": "Архивирано", - "archived_count": "{count, plural, other {Архивирано #}}", - "are_these_the_same_person": "Да ли су ово иста особа?", - "are_you_sure_to_do_this": "Јесте ли сигурни да желите ово да урадите?", - "asset_action_delete_err_read_only": "Не могу да обришем елемент(е) само за читање, прескачем", - "asset_action_share_err_offline": "Није могуће преузети офлајн датотеку(е), прескачем", - "asset_added_to_album": "Додато у албум", - "asset_adding_to_album": "Додаје се у албум…", - "asset_description_updated": "Опис датотеке је ажуриран", - "asset_filename_is_offline": "Датотека {filename} је ван мреже (offline)", - "asset_has_unassigned_faces": "Датотека има недодељена лица", - "asset_hashing": "Хеширање…", - "asset_list_group_by_sub_title": "Групиши по", - "asset_list_layout_settings_dynamic_layout_title": "Динамични распоред", - "asset_list_layout_settings_group_automatically": "Аутоматски", - "asset_list_layout_settings_group_by": "Групиши записе по", - "asset_list_layout_settings_group_by_month_day": "Месец + Дан", - "asset_list_layout_sub_title": "Лаyоут", - "asset_list_settings_subtitle": "Опције за мрежни приказ фотографија", - "asset_list_settings_title": "Мрежни приказ фотографија", - "asset_offline": "Датотека одсутна", - "asset_offline_description": "Ова спољна датотека се више не налази на диску. Молимо контактирајте свог Immich администратора за помоћ.", - "asset_restored_successfully": "Датотека је успешно враћена", - "asset_skipped": "Прескочено", - "asset_skipped_in_trash": "У отпад", - "asset_trashed": "Датотека бачена у отпад", - "asset_uploaded": "Отпремљено (Уплоадед)", - "asset_uploading": "Отпремање…", - "asset_viewer_settings_subtitle": "Управљајте подешавањима прегледача галерије", - "asset_viewer_settings_title": "Прегледач имовине", - "assets": "Записи", - "assets_added_count": "Додато {count, plural, one {# датотека} other {# датотека}}", - "assets_added_to_album_count": "Додато је {count, plural, one {# датотека} other {# датотека}} у албум", - "assets_count": "{count, plural, one {# датотека} few {# датотеке} other {# датотека}}", - "assets_deleted_permanently": "{count} елемената трајно обрисано", - "assets_deleted_permanently_from_server": "{count} ресурс(а) трајно обрисан(а) са Immich сервера", - "assets_moved_to_trash_count": "Премештено {count, plural, one {# датотека} few {# датотеке} other {# датотека}} у отпад", - "assets_permanently_deleted_count": "Трајно избрисано {count, plural, one {# датотека} few {# датотеке} other {# датотека}}", - "assets_removed_count": "Уклоњено {count, plural, one {# датотека} few {# датотеке} other {# датотека}}", - "assets_removed_permanently_from_device": "{count} елемената трајно уклоњено са вашег уређаја", - "assets_restore_confirmation": "Да ли сте сигурни да желите да вратите све своје датотеке које су у отпаду? Не можете поништити ову радњу! Имајте на уму да се ванмрежна средства не могу вратити на овај начин.", - "assets_restored_count": "Враћено {count, plural, one {# датотека} few {# датотеке} other {# датотека}}", - "assets_restored_successfully": "{count} датотека успешно враћено", - "assets_trashed": "{count} елемената је пребачено у отпад", - "assets_trashed_count": "Бачено у отпад {count, plural, one {# датотека} few{# датотеке} other {# датотека}}", - "assets_trashed_from_server": "{count} ресурс(а) обрисаних са Immich сервера", - "assets_were_part_of_album_count": "{count, plural, one {Датотека је} other {Датотеке су}} већ део албума", - "authorized_devices": "Овлашћени уређаји", - "automatic_endpoint_switching_subtitle": "Повежите се локално преко одређеног Wi-Fi-ја када је доступан и користите алтернативне везе на другим местима", - "automatic_endpoint_switching_title": "Аутоматска промена URL-ова", - "back": "Назад", - "back_close_deselect": "Назад, затворите или опозовите избор", - "background_location_permission": "Дозвола за локацију у позадини", - "background_location_permission_content": "Да би се мењале мреже док се ради у позадини, Имих мора *увек* имати прецизан приступ локацији како би апликација могла да прочита име Wi-Fi мреже", - "background_options": "Позадинске опције", - "backup": "Направи резервну копију", - "backup_album_selection_page_albums_device": "Албума на уређају ({count})", - "backup_album_selection_page_albums_tap": "Додирни да укључиш, додирни двапут да искључиш", - "backup_album_selection_page_assets_scatter": "Записи се могу наћи у више различитих албума. Одатле албуми се могу укључити или искључити током процеса прављења позадинских копија.", - "backup_album_selection_page_select_albums": "Одабери албуме", - "backup_album_selection_page_selection_info": "Информације о селекцији", - "backup_album_selection_page_total_assets": "Укупно јединствених ***", - "backup_all": "Све", - "backup_background_service_backup_failed_message": "Прављење резервне копије елемената није успело. Покушава се поново…", - "backup_background_service_complete_notification": "Завршено прављење резервне копије", - "backup_background_service_connection_failed_message": "Повезивање са сервером није успело. Покушавам поново…", - "backup_background_service_current_upload_notification": "Отпремање {filename}", - "backup_background_service_default_notification": "Проверавање нових записа…", - "backup_background_service_error_title": "Грешка у прављењу резервних копија", - "backup_background_service_in_progress_notification": "Прављење резервних копија записа…", - "backup_background_service_upload_failure_notification": "Неуспешно отпремљено: {filename}", - "backup_controller_page_albums": "Направи резервну копију албума", - "backup_controller_page_background_app_refresh_disabled_content": "Активирај позадинско освежавање у Опције > Генералне > Позадинско Освежавање како би направили резервне копије у позадини.", - "backup_controller_page_background_app_refresh_disabled_title": "Позадинско освежавање искључено", - "backup_controller_page_background_app_refresh_enable_button_text": "Иди у подешавања", - "backup_controller_page_background_battery_info_link": "Покажи ми како", - "backup_controller_page_background_battery_info_message": "За најпоузданије прављење резервних копија, угасите било коју опцију у оптимизацијама које би спречавале Immich са правилним радом.\n\nОвај поступак варира од уређаја до уређаја, проверите потребне кораке за Ваш уређај.", - "backup_controller_page_background_battery_info_ok": "ОК", - "backup_controller_page_background_battery_info_title": "Оптимизација Батерије", - "backup_controller_page_background_charging": "Само током пуњења", - "backup_controller_page_background_configure_error": "Неуспешно конфигурисање позадинског сервиса", - "backup_controller_page_background_delay": "Време између прављејна резервних копија записа: {duration}", - "backup_controller_page_background_description": "Укључи позадински сервис да аутоматски правиш резервне копије, без да отвараш апликацију", - "backup_controller_page_background_is_off": "Аутоматско прављење резервних копија у позадини је искључено", - "backup_controller_page_background_is_on": "Аутоматско прављење резервних копија у позадини је укључено", - "backup_controller_page_background_turn_off": "Искључи позадински сервис", - "backup_controller_page_background_turn_on": "Укључи позадински сервис", - "backup_controller_page_background_wifi": "Само на Wi-Fi", - "backup_controller_page_backup": "Направи резервну копију", - "backup_controller_page_backup_selected": "Одабрано: ", - "backup_controller_page_backup_sub": "Завршено прављење резервне копије фотографија и видеа", - "backup_controller_page_created": "Направљено:{date}", - "backup_controller_page_desc_backup": "Укључи прављење резервних копија у првом плану да аутоматски направите резервне копије када отворите апликацију.", - "backup_controller_page_excluded": "Искључено: ", - "backup_controller_page_failed": "Неуспешно ({count})", - "backup_controller_page_filename": "Име фајла: {filename} [{size}]", - "backup_controller_page_id": "ИД:{id}", - "backup_controller_page_info": "Информације", - "backup_controller_page_none_selected": "Ништа одабрано", - "backup_controller_page_remainder": "Преостало", - "backup_controller_page_remainder_sub": "Остало фотографија и видеа да се сачува од изабраних", - "backup_controller_page_server_storage": "Простор на серверу", - "backup_controller_page_start_backup": "Покрени прављење резервне копије", - "backup_controller_page_status_off": "Аутоматско прављење резервних копија у првом плану је искључено", - "backup_controller_page_status_on": "Аутоматско прављење резервних копија у првом плану је укључено", - "backup_controller_page_storage_format": "{used} од {total} искоришћено", - "backup_controller_page_to_backup": "Албуми који ће се отпремити", - "backup_controller_page_total_sub": "Све јединствене фотографије и видеи из одабраних албума", - "backup_controller_page_turn_off": "Искључи прављење резервних копија у првом плану", - "backup_controller_page_turn_on": "Укључи прављење резервних копија у првом плану", - "backup_controller_page_uploading_file_info": "Отпремање својстава датотеке", - "backup_err_only_album": "Немогуће брисање јединог албума", - "backup_info_card_assets": "записи", - "backup_manual_cancelled": "Отказано", - "backup_manual_in_progress": "Отпремање је већ у току. Покушајте касније", - "backup_manual_success": "Успех", - "backup_manual_title": "Уплоад статус", - "backup_options": "Опције резервне копије", - "backup_options_page_title": "Бацкуп оптионс", - "backup_setting_subtitle": "Управљајте подешавањима отпремања у позадини и предњем плану", - "backward": "Уназад", - "birthdate_saved": "Датум рођења успешно сачуван", - "birthdate_set_description": "Датум рођења се користи да би се израчунале године ове особе у добу одређене фотографије.", - "blurred_background": "Замућена позадина", - "bugs_and_feature_requests": "Грешке (бугс) и захтеви за функције", - "build": "Под-верзија (Буилд)", - "build_image": "Сагради (Буилд) image", - "bulk_delete_duplicates_confirmation": "Да ли сте сигурни да желите групно да избришете {count, plural, one {# дуплиран елеменат} few {# дуплирана елемента} other {# дуплираних елемената}}? Ово ће задржати највећу датотеку сваке групе и трајно избрисати све друге дупликате. Не можете поништити ову радњу!", - "bulk_keep_duplicates_confirmation": "Да ли сте сигурни да желите да задржите {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ће решити све дуплиране групе без брисања било чега.", - "bulk_trash_duplicates_confirmation": "Да ли сте сигурни да желите групно да одбаците {count, plural, one {1 дуплирану датотеку} few {# дуплиране датотеке} other {# дуплираних датотека}}? Ово ће задржати највећу датотеку сваке групе и одбацити све остале дупликате.", - "buy": "Купите лиценцу Immich-a", - "cache_settings_clear_cache_button": "Обриши кеш меморију", - "cache_settings_clear_cache_button_title": "Ова опција брише кеш меморију апликације. Ово ће битно утицати на перформансе апликације док се кеш меморија не учита поново.", - "cache_settings_duplicated_assets_clear_button": "ЦЛЕАР", - "cache_settings_duplicated_assets_subtitle": "Фотографије и видео снимци које апликација игнорише", - "cache_settings_duplicated_assets_title": "Дуплирани елементи ({count})", - "cache_settings_statistics_album": "Минијатуре библиотека", - "cache_settings_statistics_full": "Пуне слике", - "cache_settings_statistics_shared": "Минијатуре дељених албума", - "cache_settings_statistics_thumbnail": "Минијатуре", - "cache_settings_statistics_title": "Искоришћена кеш меморија", - "cache_settings_subtitle": "Контrole за кеш меморију мобилне апликације Immich", - "cache_settings_tile_subtitle": "Контролишите понашање локалног складиштења", - "cache_settings_tile_title": "Локална меморија", - "cache_settings_title": "Опције за кеширање", - "camera": "Камера", - "camera_brand": "Бренд камере", - "camera_model": "Модел камере", - "cancel": "Одустани", - "cancel_search": "Откажи претрагу", - "canceled": "Отказано", - "canceling": "Отказујем", - "cannot_merge_people": "Не може спојити особе", - "cannot_undo_this_action": "Не можете поништити ову радњу!", - "cannot_update_the_description": "Не може ажурирати опис", - "cast": "Шаљи на уређај (каст)", - "change_date": "Промени датум", - "change_description": "Промени опис", - "change_display_order": "Промени редослед приказа", - "change_expiration_time": "Промени време истека", - "change_location": "Промени место", - "change_name": "Промени име", - "change_name_successfully": "Име успешно промењено", - "change_password": "Промени Лозинку", - "change_password_description": "Ово је или први пут да се пријављујете на систем или је поднет захтев за промену лозинке. Унесите нову лозинку испод.", - "change_password_form_confirm_password": "Поново унесите шифру", - "change_password_form_description": "Ћао, {name}\n\nОво је вероватно Ваше прво приступање систему, или је поднешен захтев за промену шифре. Молимо Вас, унесите нову шифру испод.", - "change_password_form_new_password": "Нова шифра", - "change_password_form_password_mismatch": "Шифре се не подударају", - "change_password_form_reenter_new_password": "Поново унесите нову шифру", - "change_pin_code": "Промена ПИН кода", - "change_your_password": "Промени своју шифру", - "changed_visibility_successfully": "Видљивост је успешно промењена", - "charging": "Пуњење", - "check_corrupt_asset_backup": "Провери да ли постоје оштећене резервне копије датотека", - "check_corrupt_asset_backup_button": "Извршите проверу", - "check_corrupt_asset_backup_description": "Покрените ову проверу само преко Wi-Fi мреже и након што се направи резервна копија свих података. Поступак може потрајати неколико минута.", - "check_logs": "Проверите дневнике (логс)", - "choose_matching_people_to_merge": "Изаберите одговарајуће особе за спајање", - "city": "Град", - "clear": "Јасно", - "clear_all": "Избриши све", - "clear_all_recent_searches": "Обришите све недавне претраге", - "clear_message": "Обриши поруку", - "clear_value": "Јасна вредност", - "client_cert_dialog_msg_confirm": "ОК", - "client_cert_enter_password": "Ентер Password", - "client_cert_import": "Импорт", - "client_cert_import_success_msg": "Сертификат клијента је увезен", - "client_cert_invalid_msg": "Неважећа датотека сертификата или погрешна лозинка", - "client_cert_remove_msg": "Сертификат клијента је уклоњен", - "client_cert_subtitle": "Подржава само PKCS12 (.p12, .pfx) формат. Увоз/уклањање сертификата је доступно само пре пријаве", - "client_cert_title": "Клијентски SSL сертификат [ЕКСПЕРИМЕНТАЛНО]", - "clockwise": "У смеру казаљке", - "close": "Затвори", - "collapse": "Скупи", - "collapse_all": "Скупи све", - "color": "Боја", - "color_theme": "Режим боја", - "comment_deleted": "Коментар обрисан", - "comment_options": "Опције коментара", - "comments_and_likes": "Коментари и лајкови", - "comments_are_disabled": "Коментари су онемогућени", - "common_create_new_album": "Креирај нови албум", - "completed": "Завршено", - "confirm": "Потврди", - "confirm_admin_password": "Потврди Административну Лозинку", - "confirm_delete_face": "Да ли сте сигурни да желите да избришете особу {name} из дела?", - "confirm_delete_shared_link": "Да ли сте сигурни да желите да избришете овај дељени link?", - "confirm_keep_this_delete_others": "Све остале датотеке у групи ће бити избрисане осим ове датотеке. Да ли сте сигурни да желите да наставите?", - "confirm_new_pin_code": "Потврдите нови ПИН код", - "confirm_password": "Поново унеси шифру", - "connected_to": "Повезан са", - "contain": "Обухвати", - "context": "Контекст", - "continue": "Настави", - "control_bottom_app_bar_create_new_album": "Креирај нови албум", - "control_bottom_app_bar_delete_from_immich": "Обриши из Immich-a", - "control_bottom_app_bar_delete_from_local": "Обриши са уређаја", - "control_bottom_app_bar_edit_location": "Измени локацију", - "control_bottom_app_bar_edit_time": "Измени датум и време", - "control_bottom_app_bar_share_link": "Дели link", - "control_bottom_app_bar_share_to": "Подели са", - "control_bottom_app_bar_trash_from_immich": "Премести у отпад", - "copied_image_to_clipboard": "Копирана слика у међуспремник (цлипбоард).", - "copied_to_clipboard": "Копирано у међуспремник (цлипбоард)!", - "copy_error": "Грешка при копирању", - "copy_file_path": "Копирај путању датотеке", - "copy_image": "Копирај слику", - "copy_link": "Копирај везу", - "copy_link_to_clipboard": "Копирајте везу у међуспремник (цлипбоард)", - "copy_password": "Копирај лозинку", - "copy_to_clipboard": "Копирај у међуспремник (цлипбоард)", - "country": "Држава", - "cover": "Омот", - "covers": "Омоти", - "create": "Направи", - "create_album": "Направи албум", - "create_album_page_untitled": "Без наслова", - "create_library": "Направи Библиотеку", - "create_link": "Направи везу", - "create_link_to_share": "Направи везу за дељење", - "create_link_to_share_description": "Нека свако са везом види изабране фотографије", - "create_new": "ЦРЕАТЕ НЕW", - "create_new_person": "Направи нову особу", - "create_new_person_hint": "Доделите изабране датотеке новој особи", - "create_new_user": "Направи новог корисника", - "create_shared_album_page_share_add_assets": "ДОДАЈ СРЕДСТВА", - "create_shared_album_page_share_select_photos": "Одабери фотографије", - "create_tag": "Креирајте ознаку (tag)", - "create_tag_description": "Направите нову ознаку (tag). За угнежђене ознаке, унесите пуну путању ознаке укључујћи косе црте.", - "create_user": "Направи корисника", - "created": "Направљен", - "created_at": "Креирано", - "crop": "Обрезивање", - "curated_object_page_title": "Ствари", - "current_device": "Тренутни уређај", - "current_pin_code": "Тренутни ПИН код", - "current_server_address": "Тренутна адреса сервера", - "custom_locale": "Прилагођена локација (лоцале)", - "custom_locale_description": "Форматирајте датуме и бројеве на основу језика и региона", - "daily_title_text_date": "Е дд МММ", - "daily_title_text_date_year": "Е дд МММ yyyy", - "dark": "Тамно", - "date_after": "Датум после", - "date_and_time": "Датум и Време", - "date_before": "Датум пре", - "date_format": "Е д ЛЛЛ y • Х:мм", - "date_of_birth_saved": "Датум рођења успешно сачуван", - "date_range": "Распон датума", - "day": "Дан", - "days": "Дани", - "deduplicate_all": "Де-дуплицирај све", - "deduplication_criteria_1": "Величина слике у бајтовима", - "deduplication_criteria_2": "Број EXIF података", - "deduplication_info": "Информације о дедупликацији", - "deduplication_info_description": "Да бисмо аутоматски унапред одабрали датотеке и уклонили дупликате групно, гледамо:", - "default_locale": "Подразумевана локација (лоцале)", - "default_locale_description": "Форматирајте датуме и бројеве на основу локализације вашег претраживача", - "delete": "Обриши", - "delete_album": "Обриши албум", - "delete_api_key_prompt": "Да ли сте сигурни да желите да избришете овај АПИ кључ (кеy)?", - "delete_dialog_alert": "Ове ствари ће перманентно бити обрисане са Immich-a и Вашег уређаја", - "delete_dialog_alert_local": "Ове ставке ће бити трајно уклоњене са вашег уређаја, али ће и даље бити доступне на Immich серверу", - "delete_dialog_alert_local_non_backed_up": "Неке ставке нису резервно копиране на Immich-u и биће трајно уклоњене са вашег уређаја", - "delete_dialog_alert_remote": "Ове ставке ће бити трајно избрисане са Immich сервера", - "delete_dialog_ok_force": "Ипак обриши", - "delete_dialog_title": "Обриши перманентно", - "delete_duplicates_confirmation": "Да ли сте сигурни да желите да трајно избришете ове дупликате?", - "delete_face": "Избриши особу", - "delete_key": "Избриши кључ", - "delete_library": "Обриши библиотеку", - "delete_link": "Обриши везу", - "delete_local_dialog_ok_backed_up_only": "Обриши само резервне копије", - "delete_local_dialog_ok_force": "Ипак обриши", - "delete_others": "Избришите друге", - "delete_shared_link": "Обриши дељену везу", - "delete_shared_link_dialog_title": "Обриши дељени link", - "delete_tag": "Обриши ознаку (tag)", - "delete_tag_confirmation_prompt": "Да ли стварно желите да избришете ознаку {tagName}?", - "delete_user": "Обриши корисника", - "deleted_shared_link": "Обришена дељена веза", - "deletes_missing_assets": "Брише средства која недостају са диска", - "description": "Опис", - "description_input_hint_text": "Адд десцриптион...", - "description_input_submit_error": "Грешка при ажурирању описа, проверите дневник за више детаља", - "details": "Детаљи", - "direction": "Смер", - "disabled": "Онемогућено", - "disallow_edits": "Забрани измене", - "discord": "Дискорд", - "discover": "Откријте", - "dismiss_all_errors": "Одбаците све грешке", - "dismiss_error": "Одбаци грешку", - "display_options": "Опције приказа", - "display_order": "Редослед приказа", - "display_original_photos": "Прикажите оригиналне фотографије", - "display_original_photos_setting_description": "Радије приказујете оригиналну фотографију када глеdate материјал него сличице када је оригинално дело компатибилно са wебом. Ово може довести до споријег приказа фотографија.", - "do_not_show_again": "Не прикажи поново ову поруку", - "documentation": "Документација", - "done": "Урађено", - "download": "Преузми", - "download_canceled": "Преузми отказано", - "download_complete": "Преузми завршено", - "download_enqueue": "Преузимање је стављено у ред", - "download_error": "Доwнлоад Еррор", - "download_failed": "Преузимање није успело", - "download_finished": "Преузимање завршено", - "download_include_embedded_motion_videos": "Уграђени видео снимци", - "download_include_embedded_motion_videos_description": "Укључите видео записе уграђене у фотографије у покрету као засебну датотеку", - "download_notfound": "Преузимање није пронађено", - "download_paused": "Преузимање је паузирано", - "download_settings": "Преузимање", - "download_settings_description": "Управљајте подешавањима везаним за преузимање датотека", - "download_started": "Преузимање је започето", - "download_sucess": "Преузимање је успешно", - "download_sucess_android": "Медији су преузети на ДЦИМ/Immich", - "download_waiting_to_retry": "Чекање на поновни покушај", - "downloading": "Преузимање у току", - "downloading_asset_filename": "Преузимање датотеке {filename}", - "downloading_media": "Преузимање медија", - "drop_files_to_upload": "Убаците датотеке било где да их отпремите (уплоад-ујете)", - "duplicates": "Дупликати", - "duplicates_description": "Разрешите сваку групу тако што ћете навести дупликате, ако их има", - "duration": "Трајање", - "edit": "Уреди", - "edit_album": "Уреди албум", - "edit_avatar": "Уреди аватар", - "edit_date": "Уреди датум", - "edit_date_and_time": "Уреди датум и време", - "edit_description": "Измени опис", - "edit_exclusion_pattern": "Измените образац изузимања", - "edit_faces": "Уреди лица", - "edit_key": "Измени кључ", - "edit_link": "Уреди везу", - "edit_location": "Уреди локацију", - "edit_location_dialog_title": "Локација", - "edit_name": "Уреди име", - "edit_people": "Уреди особе", - "edit_tag": "Уреди ознаку (tag)", - "edit_title": "Уреди титулу", - "edit_user": "Уреди корисника", - "editor": "Уредник", - "editor_close_without_save_prompt": "Промене неће бити сачуване", - "editor_close_without_save_title": "Затворити уређивач?", - "email": "Е-пошта", - "email_notifications": "Обавештења е-поштом", - "empty_folder": "Ова мапа је празна", - "empty_trash": "Испразните отпад", - "empty_trash_confirmation": "Да ли сте сигурни да желите да испразните смеће? Ово ће трајно уклонити све датотеке у отпаду из Immich-a.\nНе можете поништити ову радњу!", - "enable": "Омогући", - "enabled": "Омогућено", - "end_date": "Крајњи датум", - "enqueued": "Стављено у ред", - "enter_wifi_name": "Унесите назив Wi-Fi мреже", - "error": "Грешка", - "error_change_sort_album": "Промена редоследа сортирања албума није успела", - "error_delete_face": "Грешка при брисању особе из дела", - "error_loading_image": "Грешка при учитавању слике", - "error_saving_image": "Грешка: {error}", - "error_title": "Грешка – Нешто је пошло наопако", - "errors": { - "cannot_navigate_next_asset": "Није могуће доћи до следеће датотеке", - "cannot_navigate_previous_asset": "Није могуће доћи до претходне датотеке", - "cant_apply_changes": "Није могуће применити промене", - "cant_change_activity": "Није могуће {enabled, select, true {онемогућити} other {омогућити}} активност", - "cant_change_asset_favorite": "Није могуће променити омиљено за датотеку", - "cant_change_metadata_assets_count": "Није могуће променити метаподатке за {count, plural, one {# датотеку} other {# датотеке}}", - "cant_get_faces": "Не могу да нађем лица", - "cant_get_number_of_comments": "Не могу добити број коментара", - "cant_search_people": "Не могу претраживати особе", - "cant_search_places": "Не могу претраживати места", - "error_adding_assets_to_album": "Грешка при додавању датотека у албум", - "error_adding_users_to_album": "Грешка при додавању корисника у албум", - "error_deleting_shared_user": "Грешка при брисању дељеног корисника", - "error_downloading": "Грешка при преузимању {filename}", - "error_hiding_buy_button": "Грешка при скривању дугмета за куповину", - "error_removing_assets_from_album": "Грешка при уклањању датотеке из албума, проверите конзолу за више детаља", - "error_selecting_all_assets": "Грешка при избору свих датотека", - "exclusion_pattern_already_exists": "Овај образац искључења већ постоји.", - "failed_to_create_album": "Није могуће креирати албум", - "failed_to_create_shared_link": "Прављење дељеног linkа није успело", - "failed_to_edit_shared_link": "Уређивање дељеног linkа није успело", - "failed_to_get_people": "Неуспело позивање особа", - "failed_to_keep_this_delete_others": "Није успело задржавање овог дела и брисање осталих датотека", - "failed_to_load_asset": "Учитавање датотека није успело", - "failed_to_load_assets": "Није успело учитавање датотека", - "failed_to_load_notifications": "Учитавање обавештења није успело", - "failed_to_load_people": "Учитавање особа није успело", - "failed_to_remove_product_key": "Уклањање кључа производа није успело", - "failed_to_stack_assets": "Слагање датотека није успело", - "failed_to_unstack_assets": "Разгруписање датотека није успело", - "failed_to_update_notification_status": "Ажурирање статуса обавештења није успело", - "incorrect_email_or_password": "Неисправан e-mail или лозинка", - "paths_validation_failed": "{paths, plural, one {# путања није прошла} other {# путањe нису прошле}} проверу ваљаности", - "profile_picture_transparent_pixels": "Слике профила не могу имати прозирне пикселе. Молимо увећајте и/или померите слику.", - "quota_higher_than_disk_size": "Поставили сте квоту већу од величине диска", - "unable_to_add_album_users": "Није могуће додати кориснике у албум", - "unable_to_add_assets_to_shared_link": "Није могуће додати датотеке дељеној вези", - "unable_to_add_comment": "Није могуће додати коментар", - "unable_to_add_exclusion_pattern": "Није могуће додати образац изузимања", - "unable_to_add_partners": "Није могуће додати партнере", - "unable_to_add_remove_archive": "Није могуће {archived, select, true {уклонити датотеке из} other {додати датотеке у}} архиву", - "unable_to_add_remove_favorites": "Није могуће {favorite, select, true {додати датотеке у} other {уклонити датотеке из}} фаворите", - "unable_to_archive_unarchive": "Није могуће {archived, select, true {архивирати} other {де-архивирати}}", - "unable_to_change_album_user_role": "Није могуће променити улогу корисника албума", - "unable_to_change_date": "Није могуће променити датум", - "unable_to_change_favorite": "Није могуће променити омиљено за датотеку", - "unable_to_change_location": "Није могуће променити локацију", - "unable_to_change_password": "Није могуће променити лозинку", - "unable_to_change_visibility": "Није могуће променити видљивост за {count, plural, one {# особу} other {# особе}}", - "unable_to_complete_oauth_login": "Није могуће довршити OAuth пријаву", - "unable_to_connect": "Није могуће повезати се", - "unable_to_copy_to_clipboard": "Није могуће копирати у међуспремник (цлипбоард), проверите да ли приступате страници преко хттпс-а", - "unable_to_create_admin_account": "Није могуће направити администраторски налог", - "unable_to_create_api_key": "Није могуће направити нови АПИ кључ (кеy)", - "unable_to_create_library": "Није могуће направити библиотеку", - "unable_to_create_user": "Није могуће креирати корисника", - "unable_to_delete_album": "Није могуће избрисати албум", - "unable_to_delete_asset": "Није могуће избрисати датотеке", - "unable_to_delete_assets": "Грешка при брисању датотека", - "unable_to_delete_exclusion_pattern": "Није могуће избрисати образац изузимања", - "unable_to_delete_shared_link": "Није могуће избрисати дељени link", - "unable_to_delete_user": "Није могуће избрисати корисника", - "unable_to_download_files": "Није могуће преузети датотеке", - "unable_to_edit_exclusion_pattern": "Није могуће изменити образац изузимања", - "unable_to_empty_trash": "Није могуће испразнити отпад", - "unable_to_enter_fullscreen": "Није могуће отворити преко целог екрана", - "unable_to_exit_fullscreen": "Није могуће изаћи из целог екрана", - "unable_to_get_comments_number": "Није могуће добити број коментара", - "unable_to_get_shared_link": "Преузимање дељене везе није успело", - "unable_to_hide_person": "Није могуће сакрити особу", - "unable_to_link_motion_video": "Није могуће повезати видео са сликом", - "unable_to_link_oauth_account": "Није могуће повезати OAuth налог", - "unable_to_log_out_all_devices": "Није могуће одјавити све уређаје", - "unable_to_log_out_device": "Није могуће одјавити уређај", - "unable_to_login_with_oauth": "Није могуће пријавити се помоћу OAuth-а", - "unable_to_play_video": "Није могуће пустити видео", - "unable_to_reassign_assets_existing_person": "Није могуће прерасподелити датотеке на {name, select, null {постојећу особу} other {{name}}}", - "unable_to_reassign_assets_new_person": "Није могуће пренети средства новој особи", - "unable_to_refresh_user": "Није могуће освежити корисника", - "unable_to_remove_album_users": "Није могуће уклонити кориснике из албума", - "unable_to_remove_api_key": "Није могуће уклонити АПИ кључ (кеy)", - "unable_to_remove_assets_from_shared_link": "Није могуће уклонити елементе са дељеног linkа", - "unable_to_remove_library": "Није могуће уклонити библиотеку", - "unable_to_remove_partner": "Није могуће уклонити партнера", - "unable_to_remove_reaction": "Није могуће уклонити реакцију", - "unable_to_reset_password": "Није могуће ресетовати лозинку", - "unable_to_reset_pin_code": "Није могуће ресетовати ПИН код", - "unable_to_resolve_duplicate": "Није могуће разрешити дупликат", - "unable_to_restore_assets": "Није могуће вратити датотеке", - "unable_to_restore_trash": "Није могуће повратити отпад", - "unable_to_restore_user": "Није могуће повратити корисника", - "unable_to_save_album": "Није могуће сачувати албум", - "unable_to_save_api_key": "Није могуће сачувати АПИ кључ (кеy)", - "unable_to_save_date_of_birth": "Није могуће сачувати датум рођења", - "unable_to_save_name": "Није могуће сачувати име", - "unable_to_save_profile": "Није могуће сачувати профил", - "unable_to_save_settings": "Није могуће сачувати подешавања", - "unable_to_scan_libraries": "Није могуће скенирати библиотеке", - "unable_to_scan_library": "Није могуће скенирати библиотеку", - "unable_to_set_feature_photo": "Није могуће поставити истакнуту фотографију", - "unable_to_set_profile_picture": "Није могуће поставити профилну слику", - "unable_to_submit_job": "Није могуће предати задатак", - "unable_to_trash_asset": "Није могуће избацити материјал у отпад", - "unable_to_unlink_account": "Није могуће раскинути профил", - "unable_to_unlink_motion_video": "Није могуће одвезати видео од слике", - "unable_to_update_album_cover": "Није могуће ажурирати насловницу албума", - "unable_to_update_album_info": "Није могуће ажурирати информације о албуму", - "unable_to_update_library": "Није могуће ажурирати библиотеку", - "unable_to_update_location": "Није могуће ажурирати локацију", - "unable_to_update_settings": "Није могуће ажурирати подешавања", - "unable_to_update_timeline_display_status": "Није могуће ажурирати статус приказа временске линије", - "unable_to_update_user": "Није могуће ажурирати корисника", - "unable_to_upload_file": "Није могуће отпремити датотеку" - }, - "exif": "Exif", - "exif_bottom_sheet_description": "Додај опис...", - "exif_bottom_sheet_details": "ДЕТАЛЈИ", - "exif_bottom_sheet_location": "ЛОКАЦИЈА", - "exif_bottom_sheet_people": "ПЕОПЛЕ", - "exif_bottom_sheet_person_add_person": "Адд name", - "exit_slideshow": "Изађи из пројекције слајдова", - "expand_all": "Прошири све", - "experimental_settings_new_asset_list_subtitle": "У изради", - "experimental_settings_new_asset_list_title": "Активирај експериментални мрежни приказ фотографија", - "experimental_settings_subtitle": "Користити на сопствену одговорност!", - "experimental_settings_title": "Експериментално", - "expire_after": "Да истекне након", - "expired": "Истекло", - "expires_date": "Истиче {date}", - "explore": "Истражите", - "explorer": "Претраживач (Еxплорер)", - "export": "Извези", - "export_as_json": "Извези ЈСОН", - "extension": "Екстензија (Еxтенсион)", - "external": "Спољашњи", - "external_libraries": "Спољашње Библиотеке", - "external_network": "Спољна мрежа", - "external_network_sheet_info": "Када није на жељеној Wi-Fi мрежи, апликација ће се повезати са сервером преко прве од доле наведених URL адреса до којих може да дође, почевши од врха до дна", - "face_unassigned": "Нераспоређени", - "failed": "Неуспешно", - "failed_to_load_assets": "Датотеке нису успешно учитане", - "failed_to_load_folder": "Учитавање фасцикле није успело", - "favorite": "Фаворит", - "favorite_or_unfavorite_photo": "Омиљена или неомиљена фотографија", - "favorites": "Фаворити", - "favorites_page_no_favorites": "Није пронађен ниједан омиљени материјал", - "feature_photo_updated": "Главна фотографија је ажурирана", - "features": "Функције (феатурес)", - "features_setting_description": "Управљајте функцијама апликације", - "file_name_or_extension": "Име датотеке или екстензија", - "filename": "Име датотеке", - "filetype": "Врста документа", - "filter": "Филтер", - "filter_people": "Филтрирање особа", - "filter_places": "Филтрирајте места", - "find_them_fast": "Брзо их пронађите по имену помоћу претраге", - "first": "Први", - "fix_incorrect_match": "Исправите нетачно подударање", - "folder": "Фасцикла", - "folder_not_found": "Фасцикла није пронађена", - "folders": "Фасцикле (Фолдерс)", - "folders_feature_description": "Прегледавање приказа фасцикле за фотографије и видео записа у систему датотека", - "forward": "Напред", - "gcast_enabled": "Google Cast", - "general": "Генерално", - "get_help": "Нађи помоћ", - "get_wifiname_error": "Није могуће добити име Wi-Fi мреже. Уверите се да сте дали потребне дозволе и да сте повезани на Wi-Fi мрежу", - "getting_started": "Почињем", - "go_back": "Врати се", - "go_to_folder": "Иди у фасциклу", - "go_to_search": "Иди на претрагу", - "gps": "GPS", - "grant_permission": "Дај дозволу", - "group_albums_by": "Групни албуми по...", - "group_country": "Група по држава", - "group_no": "Без груписања", - "group_owner": "Групирајте по власнику", - "group_places_by": "Групирајте места по...", - "group_year": "Групирајте по години", - "haptic_feedback_switch": "Омогући хаптичку повратну информацију", - "haptic_feedback_title": "Хаптичке повратне информације", - "has_quota": "Има квоту", - "hashing": "Хеширање", - "header_settings_add_header_tip": "Додај заглавље", - "header_settings_field_validator_msg": "Вредност не може бити празна", - "header_settings_header_name_input": "Назив заглавља", - "header_settings_header_value_input": "Вредност заглавља", - "headers_settings_tile_title": "Прилагођени прокси заглавци", - "hi_user": "Здраво {name} ({email})", - "hide_all_people": "Сакриј све особе", - "hide_gallery": "Сакриј галерију", - "hide_named_person": "Сакриј особу {name}", - "hide_password": "Сакриј лозинку", - "hide_person": "Сакриј особу", - "hide_unnamed_people": "Сакриј неименоване особе", - "home_page_add_to_album_conflicts": "Додат {added} запис у албум {album}. {failed} записи су већ у албуму.", - "home_page_add_to_album_err_local": "Тренутно немогуће додати локалне записе у албуме, прескацу се", - "home_page_add_to_album_success": "Доdate {added} ставке у албум {album}.", - "home_page_album_err_partner": "Још увек није могуће додати партнерска средства у албум, прескачем", - "home_page_archive_err_local": "Још увек није могуће архивирати локалне ресурсе, прескачем", - "home_page_archive_err_partner": "Не могу да архивирам партнерску имовину, прескачем", - "home_page_building_timeline": "Креирање хронолошке линије", - "home_page_delete_err_partner": "Не могу да обришем партнерску имовину, прескачем", - "home_page_delete_remote_err_local": "Локална средства у обрисавању удаљеног избора, прескакање", - "home_page_favorite_err_local": "Тренутно није могуце додати локалне записе у фаворите, прескацу се", - "home_page_favorite_err_partner": "Још увек није могуће означити партнерске ресурсе као омиљене, прескачем", - "home_page_first_time_notice": "Ако је ово први пут да користите апликацију, молимо Вас да одаберете албуме које желите да сачувате", - "home_page_share_err_local": "Не могу да делим локалне ресурсе преко linkа, прескачем", - "home_page_upload_err_limit": "Можете отпремити највише 30 елемената истовремено, прескачући", - "host": "Домаћин (Хост)", - "hour": "Сат", - "hours": "Сати", - "id": "ИД", - "idle": "Неактивно", - "ignore_icloud_photos": "Игноришите иЦлоуд фотографије", - "ignore_icloud_photos_description": "Фотографије које су сачуване на иЦлоуд-у неће бити отпремљене на Immich сервер", - "image": "Фотографија", - "image_alt_text_date": "{isVideo, select, true {Видео} other {Image}} снимљено {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Видео} other {Image}} снимљено са {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Видео} other {Image}} снимљено са {person1} и {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Видео} other {Image}} снимљено са {person1}, {person2} и {person3} {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Видео} other {Image}} снимљено са {person1}, {person2} и још {additionalCount, number} осталих {date}", - "image_alt_text_date_place": "{isVideo, select, true {Видео} other {Image}} снимљено у {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Видео} other {Image}} снимљено у {city}, {country} са {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Видео} other {Image}} снимљено у {city}, {country} са {person1} и {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Видео} other {Image}} снимљеноу {city}, {country} са {person1}, {person2} и {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Видео} other {Image}} снимљено у {city}, {country} са {person1}, {person2} и још {additionalCount, number} других {date}", - "image_saved_successfully": "Слика је сачувана", - "image_viewer_page_state_provider_download_started": "Преузимање је започето", - "image_viewer_page_state_provider_download_success": "Преузимање Успешно", - "image_viewer_page_state_provider_share_error": "Грешка при дељењу", - "immich_logo": "Лого Immich-a", - "immich_web_interface": "Wеб интерфејс Immich-a", - "import_from_json": "Увези из ЈСОН-а", - "import_path": "Путања увоза", - "in_albums": "У {count, plural, one {# албуму} few {# албума} other {# албума}}", - "in_archive": "У архиви", - "include_archived": "Обухвати архивирано", - "include_shared_albums": "Обухвати дељене албуме", - "include_shared_partner_assets": "Обухвати заједничке датотеке партнера", - "individual_share": "Индивидуални удео", - "individual_shares": "Појединачне акције", - "info": "Информација", - "interval": { - "day_at_onepm": "Сваки дан у 1пм", - "hours": "{hours, plural, one {Сваки сат} few {Сваких {hours, number} сата} other {Сваких {hours, number} сати}}", - "night_at_midnight": "Свака ноћ у поноћ", - "night_at_twoam": "Свака ноћ у 2ам" - }, - "invalid_date": "Неважећи датум", - "invalid_date_format": "Неважећи формат датума", - "invite_people": "Позовите људе", - "invite_to_album": "Позови на албум", - "ios_debug_info_fetch_ran_at": "Дохватање покренуто {dateTime}", - "items_count": "{count, plural, one {# датотека} other {# датотека}}", - "jobs": "Послови", - "keep": "Задржи", - "keep_all": "Задржи све", - "keep_this_delete_others": "Задржи ово, избриши друге", - "kept_this_deleted_others": "Задржана је ова датотека и избрисано {count, plural, one {# датотека} other {# датотека}}", - "keyboard_shortcuts": "Пречице на тастатури", - "language": "Језик", - "language_setting_description": "Изаберите жељени језик", - "last": "Последњи", - "last_seen": "Последњи пут виђен", - "latest_version": "Најновија верзија", - "latitude": "Географска ширина", - "leave": "Напусти", - "lens_model": "Модел сочива", - "let_others_respond": "Дозволи да други коментаришу", - "level": "Ниво", - "library": "Библиотека", - "library_options": "Опције библиотеке", - "library_page_device_albums": "Албуми на уређају", - "library_page_new_album": "Нови албум", - "library_page_sort_asset_count": "Број средстава", - "library_page_sort_created": "Најновије креирано", - "library_page_sort_last_modified": "Последња измена", - "library_page_sort_title": "Назив албума", - "licenses": "Лиценце", - "light": "Светло", - "like": "Свиђа ми се", - "like_deleted": "Лајкуј избрисано", - "link_motion_video": "Направи везу за видео запис", - "link_to_oauth": "Веза до OAuth-а", - "linked_oauth_account": "Повезани OAuth налог", - "list": "Излистај", - "loading": "Учитавање", - "loading_search_results_failed": "Учитавање резултата претраге није успело", - "local": "Локално", - "local_network": "Лоцал нетwорк", - "local_network_sheet_info": "Апликација ће се повезати са сервером преко ове URL адресе када користи наведену Ви-Фи мрежу", - "location_permission": "Дозвола за локацију", - "location_permission_content": "Да би користио функцију аутоматског пребацивања, Immich-u је потребна прецизна дозвола за локацију како би могао да прочита назив тренутне Wi-Fi мреже", - "location_picker_choose_on_map": "Изаберите на мапи", - "location_picker_latitude_error": "Унесите важећу географску ширину", - "location_picker_latitude_hint": "Унесите своју географску ширину овде", - "location_picker_longitude_error": "Унесите важећу географску дужину", - "location_picker_longitude_hint": "Унесите своју географску дужину овде", - "lock": "Закључај", - "locked_folder": "Закључана фасцикла", - "log_out": "Одјави се", - "log_out_all_devices": "Одјавите се са свих уређаја", - "logged_out_all_devices": "Одјављени су сви уређаји", - "logged_out_device": "Одјављен уређај", - "login": "Пријава", - "login_disabled": "Пријава је онемогућена", - "login_form_api_exception": "Изузетак АПИ-ја. Молимо вас да проверите URL адресу сервера и покушате поново.", - "login_form_back_button_text": "Назад", - "login_form_email_hint": "вашemail@email.цом", - "login_form_endpoint_hint": "хттп://ип-вашег-сервера:порт", - "login_form_endpoint_url": "URL Сервера", - "login_form_err_http": "Допиши хттп:// или хттпс://", - "login_form_err_invalid_email": "Неважећи Емаил", - "login_form_err_invalid_url": "Не важећи link (URL)", - "login_form_err_leading_whitespace": "Размак испред", - "login_form_err_trailing_whitespace": "Размак иза", - "login_form_failed_get_oauth_server_config": "Евиденција грешака користећи OAuth, проверити серверски link (URL)", - "login_form_failed_get_oauth_server_disable": "OAuth опција није доступна на овом серверу", - "login_form_failed_login": "Неуспешна пријава, провери URL сервера, email и шифру", - "login_form_handshake_exception": "Дошло је до изузетка рукостискања са сервером. Омогућите подршку за самопотписане сертификате у подешавањима ако користите самопотписани сертификат.", - "login_form_password_hint": "шифра", - "login_form_save_login": "Остани пријављен", - "login_form_server_empty": "Ентер а сервер URL.", - "login_form_server_error": "Није могуће повезати се са сервером.", - "login_has_been_disabled": "Пријава је онемогућена.", - "login_password_changed_error": "Дошло је до грешке приликом ажурирања лозинке", - "login_password_changed_success": "Лозинка је успешно ажурирана", - "logout_all_device_confirmation": "Да ли сте сигурни да желите да се одјавите са свих уређаја?", - "logout_this_device_confirmation": "Да ли сте сигурни да желите да се одјавите са овог уређаја?", - "logs": "Евиденција", - "longitude": "Географска дужина", - "look": "Погледај", - "loop_videos": "Понављајте видео записе", - "loop_videos_description": "Омогућите за аутоматско понављање видео записа у прегледнику детаља.", - "main_branch_warning": "Користиш развојну верзију; строго препоручујемо употребу издате верзије!", - "main_menu": "Главни мени", - "make": "Креирај", - "manage_shared_links": "Управљајте дељеним везама", - "manage_sharing_with_partners": "Управљајте дељењем са партнерима", - "manage_the_app_settings": "Управљајте подешавањима апликације", - "manage_your_account": "Управљајте вашим профилом", - "manage_your_api_keys": "Управљајте АПИ кључевима (кеyс)", - "manage_your_devices": "Управљајте својим пријављеним уређајима", - "manage_your_oauth_connection": "Управљајте својом OAuth везом", - "map": "Мапа", - "map_assets_in_bounds": "{count, plural, =0 {Нема фотографија у овом подручју} one {# фотографија} few {# фотографије} other {# фотографија}}", - "map_cannot_get_user_location": "Није могуће добити локацију корисника", - "map_location_dialog_yes": "Да", - "map_location_picker_page_use_location": "Користите ову локацију", - "map_location_service_disabled_content": "Услуга локације мора бити омогућена да би се приказивала средства са ваше тренутне локације. Да ли желите да је сада омогућите?", - "map_location_service_disabled_title": "Услуга локације је онемогућена", - "map_marker_for_images": "Означивач на мапи за слике снимљене у {city}, {country}", - "map_marker_with_image": "Маркер на мапи са сликом", - "map_no_location_permission_content": "Потребна је дозвола за локацију да би се приказали ресурси са ваше тренутне локације. Да ли желите да је сада дозволите?", - "map_no_location_permission_title": "Дозвола за локацију је одбијена", - "map_settings": "Подешавања мапе", - "map_settings_dark_mode": "Тамни режим", - "map_settings_date_range_option_day": "Последња 24 сата", - "map_settings_date_range_option_days": "Претходних {days} дана", - "map_settings_date_range_option_year": "Прошла година", - "map_settings_date_range_option_years": "Протеклих {years} година", - "map_settings_dialog_title": "Подешавања Мапе", - "map_settings_include_show_archived": "Укључи архивирано", - "map_settings_include_show_partners": "Укључи партнере", - "map_settings_only_show_favorites": "Прикажи само омиљене", - "map_settings_theme_settings": "Тема мапе", - "map_zoom_to_see_photos": "Умањите да бисте видели фотографије", - "mark_all_as_read": "Означи све као прочитано", - "mark_as_read": "Означи као прочитано", - "marked_all_as_read": "Све је означено као прочитано", - "matches": "Подударања", - "media_type": "Врста медија", - "memories": "Сећања", - "memories_all_caught_up": "Све је ухваћено", - "memories_check_back_tomorrow": "Вратите се сутра за још успомена", - "memories_setting_description": "Управљајте оним што видите у својим сећањима", - "memories_start_over": "Почни испочетка", - "memories_swipe_to_close": "Превуците нагоре да бисте затворили", - "memory": "Меморија", - "memory_lane_title": "Трака сећања {title}", - "menu": "Мени", - "merge": "Споји", - "merge_people": "Споји особе", - "merge_people_limit": "Можете спојити највише 5 лица од једном", - "merge_people_prompt": "Да ли желите да спојите ове особе? Овај потез се не може повратити.", - "merge_people_successfully": "Спајање особа успешно", - "merged_people_count": "Спојено {count, plural, one {# особа} other {# особе}}", - "minimize": "Минимизирајте", - "minute": "Минут", - "minutes": "Минути", - "missing": "Недостаје", - "model": "Модел", - "month": "Месец", - "monthly_title_text_date_format": "ММММ y", - "more": "Више", - "move": "Премести", - "moved_to_archive": "Премештено {count, plural, one {# датотека} other {# датотеке}} у архиву", - "moved_to_library": "Премештено {count, plural, one {# датотека} other {# датотеке}} у библиотеку", - "moved_to_trash": "Премештено у смеће", - "multiselect_grid_edit_date_time_err_read_only": "Не можете да измените датум елемената само за читање, прескачем", - "multiselect_grid_edit_gps_err_read_only": "Не могу да изменим локацију елемената само за читање, прескачем", - "mute_memories": "Пригуши сећања", - "my_albums": "Моји албуми", - "name": "Име", - "name_or_nickname": "Име или надимак", - "navigate": "Иди", - "networking_settings": "Умрежавање", - "networking_subtitle": "Управљајте подешавањима крајње тачке сервера", - "never": "Никада", - "new_album": "Нови Албум", - "new_api_key": "Нови АПИ кључ (кеy)", - "new_password": "Нова шифра", - "new_person": "Нова особа", - "new_pin_code": "Нови ПИН код", - "new_user_created": "Нови корисник је креиран", - "new_version_available": "ДОСТУПНА НОВА ВЕРЗИЈА", - "newest_first": "Најновије прво", - "next": "Следеће", - "next_memory": "Следеће сећање", - "no": "Не", - "no_albums_message": "Направите албум да бисте организовали своје фотографије и видео записе", - "no_albums_with_name_yet": "Изгледа да још увек немате ниједан албум са овим именом.", - "no_albums_yet": "Изгледа да још немате ниједан албум.", - "no_archived_assets_message": "Архивирајте фотографије и видео записе да бисте их сакрили из приказа фотографија", - "no_assets_message": "КЛИКНИТЕ ДА УПЛОАДИРАТЕ СВОЈУ ПРВУ ФОТОГРАФИЈУ", - "no_assets_to_show": "Нема елемената за приказ", - "no_duplicates_found": "Није пронађен ниједан дупликат.", - "no_exif_info_available": "Нема доступних еxиф информација", - "no_explore_results_message": "Уплоадујте још фотографија да бисте истражили своју колекцију.", - "no_favorites_message": "Поставите фаворите да бисте брзо нашли ваше најбоље слике и видео снимке", - "no_libraries_message": "Направите спољну библиотеку да бисте видели своје фотографије и видео записе", - "no_name": "Нема имена", - "no_notifications": "Нема обавештења", - "no_people_found": "Нису пронађени одговарајући људи", - "no_places": "Нема места", - "no_results": "Нема резултата", - "no_results_description": "Покушајте са синонимом или општијом кључном речи", - "no_shared_albums_message": "Направите албум да бисте делили фотографије и видео записе са људима у вашој мрежи", - "not_available": "Недоступно", - "not_in_any_album": "Нема ни у једном албуму", - "not_selected": "Није изабрано", - "note_apply_storage_label_to_previously_uploaded assets": "Напомена: Да бисте применили ознаку за складиштење на претходно уплоадиране датотеке, покрените", - "notes": "Напомене", - "notification_permission_dialog_content": "Да би укљуцили нотификације, идите у Опције и одаберите Дозволи.", - "notification_permission_list_tile_content": "Дајте дозволу за омогућавање обавештења.", - "notification_permission_list_tile_enable_button": "Укључи Нотификације", - "notification_permission_list_tile_title": "Дозволе за нотификације", - "notification_toggle_setting_description": "Омогућите обавештења путем е-поште", - "notifications": "Нотификације", - "notifications_setting_description": "Управљајте обавештењима", - "oauth": "OAuth", - "official_immich_resources": "Званични Immich ресурси", - "offline": "Одсутан (Оффлине)", - "offset": "Помак", - "ok": "Ок", - "oldest_first": "Најстарије прво", - "on_this_device": "На овом уређају", - "onboarding": "Приступање (Онбоардинг)", - "onboarding_privacy_description": "Следеће (необавезне) функције се ослањају на спољне услуге и могу се онемогућити у било ком тренутку у подешавањима.", - "onboarding_theme_description": "Изаберите тему боја за свој налог. Ово можете касније да промените у подешавањима.", - "onboarding_welcome_user": "Добродошли, {user}", - "online": "Доступан (Онлине)", - "only_favorites": "Само фаворити", - "open": "Отвори", - "open_in_map_view": "Отворите у приказ карте", - "open_in_openstreetmap": "Отворите у ОпенСтреетМап-у", - "open_the_search_filters": "Отворите филтере за претрагу", - "options": "Опције", - "or": "или", - "organize_your_library": "Организујте своју библиотеку", - "original": "Оригинал", - "other": "Остало", - "other_devices": "Остали уређаји", - "other_variables": "Остале варијабле", - "owned": "Власништво", - "owner": "Власник", - "partner": "партнер", - "partner_can_access": "{partner} може да приступи", - "partner_can_access_assets": "Све ваше фотографије и видео снимци осим оних у архивираним и избрисаним", - "partner_can_access_location": "Локација на којој су ваше фотографије снимљене", - "partner_list_user_photos": "Фотографије корисника {user}", - "partner_list_view_all": "Прикажи све", - "partner_page_empty_message": "Ваше фотографије још увек нису дељене ни са једним партнером.", - "partner_page_no_more_users": "Нема више корисника за додавање", - "partner_page_partner_add_failed": "Додавање партнера није успело", - "partner_page_select_partner": "Изаберите партнера", - "partner_page_shared_to_title": "Дељено са", - "partner_page_stop_sharing_content": "{partner} више неће моћи да приступи вашим фотографијама.", - "partner_sharing": "Партнерско дељење", - "partners": "Партнери", - "password": "Шифра", - "password_does_not_match": "Лозинка се не подудара", - "password_required": "Лозинка потребна", - "password_reset_success": "Ресетовање лозинке је успешно", - "past_durations": { - "days": "{days, plural, one {Прошли дан} few {Прошлих # дана} other {Прошлих # дана}}", - "hours": "{hours, plural, one {Прошли сат} few {Прошла # сата} other {Прошлих # сати}}", - "years": "{years, plural, one {Прошле године} few {Прошле # године} other {Прошлих # година}}" - }, - "path": "Путања", - "pattern": "Шаблон", - "pause": "Пауза", - "pause_memories": "Паузирајте сећања", - "paused": "Паузирано", - "pending": "На чекању", - "people": "Особе", - "people_edits_count": "Измењено {count, plural, one {# особа} other {# особе}}", - "people_feature_description": "Прегледавање фотографија и видео снимака груписаних по особама", - "people_sidebar_description": "Прикажите везу до особа на бочној траци", - "permanent_deletion_warning": "Упозорење за трајно брисање", - "permanent_deletion_warning_setting_description": "Прикажи упозорење када трајно бришете датотеке", - "permanently_delete": "Трајно избрисати", - "permanently_delete_assets_count": "Трајно избриши {count, plural, one {датотеку} other {датотеке}}", - "permanently_delete_assets_prompt": "Да ли сте сигурни да желите да трајно избришете {count, plural, one {ову датотеку?} other {ове # датотеке?}}Ово ће их такође уклонити {count, plural, one {из њиховог} other {из њихових}} албума.", - "permanently_deleted_asset": "Трајно избрисана датотека", - "permanently_deleted_assets_count": "Трајно избрисано {count, plural, one {# датотека} other {# датотеке}}", - "permission": "Дозвола", - "permission_onboarding_back": "Назад", - "permission_onboarding_continue_anyway": "Ипак настави", - "permission_onboarding_get_started": "Започните", - "permission_onboarding_go_to_settings": "Иди на подешавања", - "permission_onboarding_permission_denied": "Дозвола одбијена. Да бисте користили Immich, доделите дозволе за фотографије и видео записе у Подешавањима.", - "permission_onboarding_permission_granted": "Дозвола одобрена! Спремни сте.", - "permission_onboarding_permission_limited": "Дозвола ограничена. Да бисте омогућили Immich-u да прави резервне копије и управља целом вашом колекцијом галерије, доделите дозволе за фотографије и видео записе у Подешавањима.", - "permission_onboarding_request": "Immich захтева дозволу да види ваше фотографије и видео записе.", - "person": "Особа", - "person_birthdate": "Рођен(а) {date}", - "person_hidden": "{name}{hidden, select, true { (скривено)} other {}}", - "photo_shared_all_users": "Изгледа да сте поделили своје фотографије са свим корисницима или да немате ниједног корисника са којим бисте делили.", - "photos": "Фотографије", - "photos_and_videos": "Фотографије & Видео записи", - "photos_count": "{count, plural, one {{count, number} фотографија} few {{count, number} фотографије} other {{count, number} фотографија}}", - "photos_from_previous_years": "Фотографије из претходних година", - "pick_a_location": "Одабери локацију", - "pin_code_changed_successfully": "ПИН код је успешно промењен", - "pin_code_reset_successfully": "ПИН код је успешно ресетован", - "pin_code_setup_successfully": "Успешно подешавање ПИН кода", - "place": "Место", - "places": "Места", - "places_count": "{count, plural, one {{count, number} Место} other {{count, number} Места}}", - "play": "Покрени", - "play_memories": "Покрени сећања", - "play_motion_photo": "Покрени покретну фотографију", - "play_or_pause_video": "Покрени или паузирај видео запис", - "port": "порт", - "preferences_settings_subtitle": "Управљајте подешавањима апликације", - "preferences_settings_title": "Подешавања", - "preparing": "Припрема", - "preset": "Унапред подешено", - "preview": "Преглед", - "previous": "Прошло", - "previous_memory": "Претходно сећање", - "previous_or_next_day": "Дан напред/назад", - "previous_or_next_month": "Месец напред/назад", - "previous_or_next_photo": "Фотографија напред/назад", - "previous_or_next_year": "Година напред/назад", - "primary": "Примарна (Примарy)", - "privacy": "Приватност", - "profile": "Профил", - "profile_drawer_app_logs": "Евиденција", - "profile_drawer_client_server_up_to_date": "Клијент и сервер су најновије верзије", - "profile_drawer_github": "ГитХуб", - "profile_image_of_user": "Слика профила од корисника {user}", - "profile_picture_set": "Профилна слика постављена.", - "public_album": "Јавни албум", - "public_share": "Јавно дељење", - "purchase_account_info": "Подржавам софтвер", - "purchase_activated_subtitle": "Хвала вам што подржавате Immich и софтвер отвореног кода", - "purchase_activated_time": "Активирано {date}", - "purchase_activated_title": "Ваш кључ је успешно активиран", - "purchase_button_activate": "Активирај", - "purchase_button_buy": "Купи", - "purchase_button_buy_immich": "Купите Immich", - "purchase_button_never_show_again": "Никада више не приказуј", - "purchase_button_reminder": "Подсети ме за 30 дана", - "purchase_button_remove_key": "Уклоните кључ", - "purchase_button_select": "Изаберите", - "purchase_failed_activation": "Активација није успела! Проверите своју е-пошту да бисте пронашли тачан кључ производа!", - "purchase_individual_description_1": "За појединца", - "purchase_individual_description_2": "Статус подршке", - "purchase_individual_title": "Индивидуална лиценца", - "purchase_input_suggestion": "Имате кључ производа? Унесите кључ испод", - "purchase_license_subtitle": "Купите Immich да бисте подржали континуирани развој услуге", - "purchase_lifetime_description": "Доживотна лиценца", - "purchase_option_title": "ОПЦИЈЕ КУПОВИНЕ", - "purchase_panel_info_1": "Изградња Immich-a захтева много времена и труда, а имамо инжењере који раде на томе са пуним радним временом како бисмо је учинили што је могуће бољом. Наша мисија је да софтвер отвореног кода и етичке пословне праксе постану одржив извор прихода за програмере и да створимо екосистем који поштује приватност са стварним алтернативама експлоатативним услугама у облаку.", - "purchase_panel_info_2": "Пошто смо се обавезали да нећемо додавати платне зидове, ова куповина ти неће дати никакве додатне функције у Immich-у. Ослањамо се на кориснике попут тебе да подрже Immich-ов стални развој.", - "purchase_panel_title": "Подржите пројекат", - "purchase_per_server": "По серверу", - "purchase_per_user": "По кориснику", - "purchase_remove_product_key": "Уклоните кључ производа", - "purchase_remove_product_key_prompt": "Да ли сте сигурни да желите да уклоните шифру производа?", - "purchase_remove_server_product_key": "Уклоните шифру производа са сервера", - "purchase_remove_server_product_key_prompt": "Да ли сте сигурни да желите да уклоните шифру производа са сервера?", - "purchase_server_description_1": "За цео сервер", - "purchase_server_description_2": "Статус подршке", - "purchase_server_title": "Сервер", - "purchase_settings_server_activated": "Кључем производа сервера управља администратор", - "rating": "Оцена звездица", - "rating_clear": "Обриши оцену", - "rating_count": "{count, plural, one {# звезда} other {# звезде}}", - "rating_description": "Прикажите EXIF оцену у инфо панелу", - "reaction_options": "Опције реакције", - "read_changelog": "Прочитајте дневник промена", - "reassign": "Поново додај", - "reassigned_assets_to_existing_person": "Поново додељено {count, plural, one {# датотека} other {# датотеке}} постојећој {name, select, null {особи} other {{name}}}", - "reassigned_assets_to_new_person": "Поново додељено {count, plural, one {# датотека} other {# датотеке}} новој особи", - "reassing_hint": "Доделите изабрана средства постојећој особи", - "recent": "Скорашњи", - "recent-albums": "Недавни албуми", - "recent_searches": "Скорашње претраге", - "recently_added": "Недавно додато", - "recently_added_page_title": "Недавно Додато", - "recently_taken": "Недавно снимљено", - "recently_taken_page_title": "Недавно Снимљено", - "refresh": "Освежи", - "refresh_encoded_videos": "Освежите кодиране (енcodeд) видео записе", - "refresh_faces": "Освежи лица", - "refresh_metadata": "Освежите метаподатке", - "refresh_thumbnails": "Освежите сличице", - "refreshed": "Освежено", - "refreshes_every_file": "Поново чита све постојеће и нове датотеке", - "refreshing_encoded_video": "Освежавање кодираног (енcodeд) видеа", - "refreshing_faces": "Освежавање лица", - "refreshing_metadata": "Освежавање мета-података", - "regenerating_thumbnails": "Обнављање сличица", - "remote": "Удаљено", - "remove": "Уклони", - "remove_assets_album_confirmation": "Да ли сте сигурни да желите да уклоните {count, plural, one {# датотеку} other {# датотеке}} из албума?", - "remove_assets_shared_link_confirmation": "Да ли сте сигурни да желите да уклоните {count, plural, one {# датотеку} other {# датотеке}} са ове дељене везе?", - "remove_assets_title": "Уклонити датотеке?", - "remove_custom_date_range": "Уклоните прилагођени период", - "remove_deleted_assets": "Уклоните ванмрежне (offline) датотеке", - "remove_from_album": "Обриши из албума", - "remove_from_favorites": "Уклони из фаворита", - "remove_from_shared_link": "Уклоните са дељене везе", - "remove_memory": "Уклоните меморију", - "remove_photo_from_memory": "Уклоните фотографију из ове меморије", - "remove_url": "Уклони URL", - "remove_user": "Уклони корисника", - "removed_api_key": "Уклоњен АПИ кључ (кеy): {name}", - "removed_from_archive": "Уклоњено из архиве", - "removed_from_favorites": "Уклоњено из омиљених (фаворитес)", - "removed_from_favorites_count": "{count, plural, other {Уклоњено #}} из омиљених", - "removed_memory": "Уклоњена меморија", - "removed_photo_from_memory": "Слика је уклоњена из меморије", - "removed_tagged_assets": "Уклоњена ознака из {count, plural, one {# датотеке} other {# датотека}}", - "rename": "Преименуј", - "repair": "Поправи", - "repair_no_results_message": "Овде ће се појавити датотеке које нису праћене и недостају", - "replace_with_upload": "Замените са уплоад-ом", - "repository": "Репозиторијум (Репоситорy)", - "require_password": "Потребна лозинка", - "require_user_to_change_password_on_first_login": "Захтевати од корисника да промени лозинку при првом пријављивању", - "rescan": "Поново скенирај", - "reset": "Ресетовати", - "reset_password": "Ресетовати лозинку", - "reset_people_visibility": "Ресетујте видљивост особа", - "reset_pin_code": "Ресетуј ПИН код", - "reset_to_default": "Ресетујте на подразумеване вредности", - "resolve_duplicates": "Реши дупликате", - "resolved_all_duplicates": "Сви дупликати су разрешени", - "restore": "Поврати", - "restore_all": "Поврати све", - "restore_user": "Поврати корисника", - "restored_asset": "Повраћено средство", - "resume": "Поново покрени", - "retry_upload": "Покушајте поново да уплоадујете", - "review_duplicates": "Прегледајте дупликате", - "role": "Улога", - "role_editor": "Уредник", - "role_viewer": "Гледалац", - "running": "У току", - "save": "Сачувај", - "save_to_gallery": "Сачувај у галерију", - "saved_api_key": "Сачуван АПИ кључ (кеy)", - "saved_profile": "Сачуван профил", - "saved_settings": "Сачувана подешавања", - "say_something": "Реци нешто", - "scaffold_body_error_occurred": "Дошло је до грешке", - "scan_all_libraries": "Скенирај све библиотеке", - "scan_library": "Скенирај", - "scan_settings": "Подешавања скенирања", - "scanning_for_album": "Скенирање албума...", - "search": "Претрага", - "search_albums": "Претражи албуме", - "search_by_context": "Претражујте по контексту", - "search_by_description": "Тражи по опису", - "search_by_description_example": "Дан пешачења у Сапи", - "search_by_filename": "Претражите по имену датотеке или екстензији", - "search_by_filename_example": "нпр. ИМГ_1234.ЈПГ или ПНГ", - "search_camera_make": "Претрага произвођача камере...", - "search_camera_model": "Претражи модел камере...", - "search_city": "Претражи град...", - "search_country": "Тражи земљу...", - "search_filter_apply": "Примени филтер", - "search_filter_camera_title": "Изаберите тип камере", - "search_filter_date": "Дате", - "search_filter_date_interval": "{start} до {end}", - "search_filter_date_title": "Изаберите период", - "search_filter_display_option_not_in_album": "Нот ин албум", - "search_filter_display_options": "Опције приказа", - "search_filter_filename": "Претрага по имену датотеке", - "search_filter_location": "Локација", - "search_filter_location_title": "Изаберите локацију", - "search_filter_media_type": "Медиа Тyпе", - "search_filter_media_type_title": "Изаберите тип медија", - "search_filter_people_title": "Изаберите људе", - "search_for": "Тражи", - "search_for_existing_person": "Потражите постојећу особу", - "search_no_more_result": "Нема више резултата", - "search_no_people": "Без особа", - "search_no_people_named": "Нема особа са именом „{name}“", - "search_no_result": "Нису пронађени резултати, покушајте са другим термином за претрагу или комбинацијом", - "search_options": "Опције претраге", - "search_page_categories": "Категорије", - "search_page_motion_photos": "Фотографије у покрету", - "search_page_no_objects": "Без информација", - "search_page_no_places": "Нема информација о месту", - "search_page_screenshots": "Снимци екрана", - "search_page_search_photos_videos": "Претражите своје фотографије и видео записе", - "search_page_selfies": "Селфији", - "search_page_things": "Ствари", - "search_page_view_all_button": "Прикажи све", - "search_page_your_activity": "Ваша активност", - "search_page_your_map": "Ваша мапа", - "search_people": "Претражи особе", - "search_places": "Претражи места", - "search_rating": "Претрага по оцени...", - "search_result_page_new_search_hint": "Нова претрага", - "search_settings": "Претрага подешавања", - "search_state": "Тражи регион...", - "search_suggestion_list_smart_search_hint_1": "Паметна претрага је подразумевано омогућена, за претрагу метаподатака користите синтаксу ", - "search_suggestion_list_smart_search_hint_2": "м:ваш-појам-за-претрагу", - "search_tags": "Претражи ознаке (tags)...", - "search_timezone": "Претражи временску зону...", - "search_type": "Врста претраге", - "search_your_photos": "Претражи своје фотографије", - "searching_locales": "Претраживање превода...", - "second": "Секунда", - "see_all_people": "Види све особе", - "select": "Изаберите", - "select_album_cover": "Изаберите омот албума", - "select_all": "Изабери све", - "select_all_duplicates": "Изаберите све дупликате", - "select_avatar_color": "Изаберите боју аватара", - "select_face": "Изаберите лице", - "select_featured_photo": "Изаберите истакнуту фотографију", - "select_from_computer": "Изаберите са рачунара", - "select_keep_all": "Изаберите да задржите све", - "select_library_owner": "Изаберите власника библиотеке", - "select_new_face": "Изаберите ново лице", - "select_person_to_tag": "Изаберите особу за означавање", - "select_photos": "Одабери фотографије", - "select_trash_all": "Изаберите да све баците на отпад", - "select_user_for_sharing_page_err_album": "Неуспешно креирање албума", - "selected": "Одабрано", - "selected_count": "{count, plural, other {# изабрано}}", - "send_message": "Пошаљи поруку", - "send_welcome_email": "Пошаљите е-пошту добродошлице", - "server_endpoint": "Крајња тачка сервера", - "server_info_box_app_version": "Верзија Апликације", - "server_info_box_server_url": "Сервер URL", - "server_offline": "Сервер ван мреже (offline)", - "server_online": "Сервер на мрежи (online)", - "server_privacy": "Приватност сервера", - "server_stats": "Статистика сервера", - "server_version": "Верзија сервера", - "set": "Постави", - "set_as_album_cover": "Постави као омот албума", - "set_as_featured_photo": "Постави као истакнуту фотографију", - "set_as_profile_picture": "Постави као профилну слику", - "set_date_of_birth": "Подесите датум рођења", - "set_profile_picture": "Постави профилну слику", - "set_slideshow_to_fullscreen": "Поставите пројекцију слајдова на цео екран", - "set_stack_primary_asset": "Постави као примарну датотеку групе", - "setting_image_viewer_help": "Прегледач детаља прво учитава малу сличицу, затим преглед средње величине (ако је омогућен), и на крају оригинал (ако је омогућен).", - "setting_image_viewer_original_subtitle": "Активирај учитавање слика у пуној резолуцији (Велика!). Деактивацијом ове ставке можеш да смањиш потрошњу интернета и заузетог простора на уређају.", - "setting_image_viewer_original_title": "Учитај оригиналну слику", - "setting_image_viewer_preview_subtitle": "Активирај учитавање слика у средњој резолуцији. Деактивирај да се директно учитава оригинал, или да се само користи минијатура.", - "setting_image_viewer_preview_title": "Прегледај слику", - "setting_image_viewer_title": "Слике", - "setting_languages_apply": "Примени", - "setting_languages_subtitle": "Промените језик апликације", - "setting_notifications_notify_failures_grace_period": "Обавести о грешкама у прављењу резервних копија у позадини: {duration}", - "setting_notifications_notify_hours": "{count} сати", - "setting_notifications_notify_immediately": "одмах", - "setting_notifications_notify_minutes": "{count} минута", - "setting_notifications_notify_never": "никада", - "setting_notifications_notify_seconds": "{count} секунди", - "setting_notifications_single_progress_subtitle": "Детаљне информације о отпремању, по запису", - "setting_notifications_single_progress_title": "Прикажи детаље позадинског прављења резервних копија", - "setting_notifications_subtitle": "Измени нотификације", - "setting_notifications_total_progress_subtitle": "Укупно отпремљених ставки (завршено/укупно ставки)", - "setting_notifications_total_progress_title": "Прикажи укупан напредак прављења резервних копија у позадини", - "setting_video_viewer_looping_title": "Петљање (Лоопинг)", - "setting_video_viewer_original_video_subtitle": "Приликом стримовања видеа са сервера, репродукујте оригинал чак и када је доступно транскодирање. Може довести до баферовања. Видео снимци доступни локално се репродукују у оригиналном квалитету без обзира на ово подешавање.", - "setting_video_viewer_original_video_title": "Присилно оригинални видео", - "settings": "Подешавања", - "settings_require_restart": "Рестартујте Immich да примените ову промену", - "settings_saved": "Подешавања сачувана", - "setup_pin_code": "Подесите ПИН код", - "share": "Подели", - "share_add_photos": "Додај фотографије", - "share_assets_selected": "Изабрано је {count}", - "share_dialog_preparing": "Припремање...", - "share_link": "Подели везу", - "shared": "Дељено", - "shared_album_activities_input_disable": "Коментар је онемогућен", - "shared_album_activity_remove_content": "Да ли желите да обришете ову активност?", - "shared_album_activity_remove_title": "Обриши активност", - "shared_album_section_people_action_error": "Грешка при напуштању/уклањању из албума", - "shared_album_section_people_action_leave": "Уклони корисника из албума", - "shared_album_section_people_action_remove_user": "Уклони корисника из албума", - "shared_album_section_people_title": "ПЕОПЛЕ", - "shared_by": "Поделио", - "shared_by_user": "Дели {user}", - "shared_by_you": "Ви делите", - "shared_from_partner": "Слике од {partner}", - "shared_intent_upload_button_progress_text": "Отпремљено је {current} / {total}", - "shared_link_app_bar_title": "Дељени linkови", - "shared_link_clipboard_copied_massage": "Копирано у међуспремник (цлипбоард)", - "shared_link_clipboard_text": "Линк: {link}\nЛозинка: {password}", - "shared_link_create_error": "Грешка при креирању дељеног linkа", - "shared_link_edit_description_hint": "Унесите опис дељења", - "shared_link_edit_expire_after_option_day": "1 дан", - "shared_link_edit_expire_after_option_days": "{count} дана", - "shared_link_edit_expire_after_option_hour": "1 сат", - "shared_link_edit_expire_after_option_hours": "{count} сати", - "shared_link_edit_expire_after_option_minute": "1 минуте", - "shared_link_edit_expire_after_option_minutes": "{count} минута", - "shared_link_edit_expire_after_option_months": "{count} месеци", - "shared_link_edit_expire_after_option_year": "{count} година", - "shared_link_edit_password_hint": "Унесите лозинку за дељење", - "shared_link_edit_submit_button": "Упdate link", - "shared_link_error_server_url_fetch": "Не могу да преузмем URL сервера", - "shared_link_expires_day": "Истиче за {count} дан(а)", - "shared_link_expires_days": "Истиче за {count} дана", - "shared_link_expires_hour": "Истиче за {count} сат", - "shared_link_expires_hours": "Истиче за {count} сати(а)", - "shared_link_expires_minute": "Истиче за {count} минут", - "shared_link_expires_minutes": "Истиче за {count} минута", - "shared_link_expires_never": "Истиче ∞", - "shared_link_expires_second": "Истиче за {count} секунду", - "shared_link_expires_seconds": "Истиче за {count} секунди", - "shared_link_individual_shared": "Појединачно дељено", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Управљајте дељеним linkовима", - "shared_link_options": "Опције дељене везе", - "shared_links": "Дељене везе", - "shared_links_description": "Делите фотографије и видео записе помоћу linkа", - "shared_photos_and_videos_count": "{assetCount, plural, other {# дељене фотографије и видео записе.}}", - "shared_with_me": "Дељено са мном", - "shared_with_partner": "Дели се са {partner}", - "sharing": "Дељење", - "sharing_enter_password": "Унесите лозинку да бисте видели ову страницу.", - "sharing_page_album": "Дељени албуми", - "sharing_page_description": "Направи дељене албуме да делиш фотографије и видео записе са људима на твојој мрежи.", - "sharing_page_empty_list": "ПРАЗНА ЛИСТА", - "sharing_sidebar_description": "Прикажите везу до Дељења на бочној траци", - "sharing_silver_appbar_create_shared_album": "Направи дељени албум", - "sharing_silver_appbar_share_partner": "Подели са партнером", - "shift_to_permanent_delete": "притисните ⇧ да трајно избришете датотеку", - "show_album_options": "Прикажи опције албума", - "show_albums": "Прикажи албуме", - "show_all_people": "Покажи све особе", - "show_and_hide_people": "Откриј и сакриј особе", - "show_file_location": "Прикажи локацију датотеке", - "show_gallery": "Прикажи галерију", - "show_hidden_people": "Прикажи скривене особе", - "show_in_timeline": "Прикажи на временској линији", - "show_in_timeline_setting_description": "Прикажите фотографије и видео записе овог корисника на временској линији", - "show_keyboard_shortcuts": "Прикажи пречице на тастатури", - "show_metadata": "Прикажи метаподатке", - "show_or_hide_info": "Откриј или сакриј информацију", - "show_password": "Прикажи лозинку", - "show_person_options": "Прикажи опције особе", - "show_progress_bar": "Прикажи траку напретка", - "show_search_options": "Прикажи опције претраге", - "show_shared_links": "Прикажи дељене везе", - "show_slideshow_transition": "Прикажи прелаз пројекције слајдова", - "show_supporter_badge": "Значка подршке", - "show_supporter_badge_description": "Покажите значку подршке", - "shuffle": "Мешање", - "sidebar": "Бочна трака", - "sidebar_display_description": "Прикажите везу до приказа на бочној траци", - "sign_out": "Одјава", - "sign_up": "Пријави се", - "size": "Величина", - "skip_to_content": "Пређи на садржај", - "skip_to_folders": "Прескочи до мапа (фолдерс)", - "skip_to_tags": "Прескочи до ознака (tags)", - "slideshow": "Слајдови", - "slideshow_settings": "Подешавања слајдова", - "sort_albums_by": "Сортирај албуме по...", - "sort_created": "Датум креирања", - "sort_items": "Број ставки", - "sort_modified": "Датум измене", - "sort_oldest": "Најстарија фотографија", - "sort_people_by_similarity": "Сортирајте особе по сличности", - "sort_recent": "Најновија фотографија", - "sort_title": "Наслов", - "source": "Извор", - "stack": "Групиши", - "stack_action_prompt": "{count} груписано", - "stack_duplicates": "Групиши дупликате", - "stack_select_one_photo": "Изабери једну главну фотографију за групу", - "stack_selected_photos": "Групиши изабране фотографије", - "stacked_assets_count": "Груписано {count, plural, one {# датотека} other {# датотеке}}", - "stacktrace": "Стектрејс", - "start": "Почетак", - "start_date": "Датум почетка", - "state": "Стање", - "status": "Статус", - "stop_motion_photo": "Заустави покретну фотографију", - "stop_photo_sharing": "Желите да зауставите дељење фотографија?", - "stop_photo_sharing_description": "{partner} више неће моћи да приступи вашим фотографијама.", - "stop_sharing_photos_with_user": "Престаните да делите своје фотографије са овим корисником", - "storage": "Складиште (Storage space)", - "storage_label": "Ознака за складиштење", - "storage_quota": "Квота складиштења", - "storage_usage": "Користи се {used} од {available}", - "submit": "Достави", - "success": "Успешно", - "suggestions": "Сугестије", - "sunrise_on_the_beach": "Излазак сунца на плажи", - "support": "Подршка", - "support_and_feedback": "Подршка и повратне информације", - "support_third_party_description": "Ваша иммицх инсталација је спакована од стране треће стране. Проблеми са којима се суочавате могу бити узроковани тим пакетом, па вас молимо да им прво поставите проблеме користећи доње везе.", - "swap_merge_direction": "Замените правац спајања", - "sync": "Синхронизација", - "sync_albums": "Синхронизуј албуме", - "sync_albums_manual_subtitle": "Синхронизујте све отпремљене видео записе и фотографије са изабраним резервним албумима", - "sync_upload_album_setting_subtitle": "Креирајте и отпремите своје фотографије и видео записе у одабране албуме на Immich-u", - "tag": "Ознака (tag)", - "tag_assets": "Означите (tag) средства", - "tag_created": "Направљена ознака (tag): {tag}", - "tag_feature_description": "Прегледавање фотографија и видео снимака груписаних по логичним темама ознака", - "tag_not_found_question": "Не можете да пронађете ознаку (tag)? Направите нову ознаку", - "tag_people": "Означите људе", - "tag_updated": "Ажурирана ознака (tag): {tag}", - "tagged_assets": "Означено (tagгед) {count, plural, one {# датотека} other {# датотеке}}", - "tags": "Ознаке (tags)", - "template": "Шаблон (Темплате)", - "theme": "Теме", - "theme_selection": "Избор теме", - "theme_selection_description": "Аутоматски поставите тему на светлу или тамну на основу системских преференција вашег претраживача", - "theme_setting_asset_list_storage_indicator_title": "Прикажи индикатор простора на записима", - "theme_setting_asset_list_tiles_per_row_title": "Број записа по реду {count}", - "theme_setting_colorful_interface_subtitle": "Нанесите основну боју на позадинске површине.", - "theme_setting_colorful_interface_title": "Шарени интерфејс", - "theme_setting_image_viewer_quality_subtitle": "Прилагодите квалитет приказа за детаљно прегледавање слике", - "theme_setting_image_viewer_quality_title": "Квалитет прегледача слика", - "theme_setting_primary_color_subtitle": "Изаберите боју за главне радње и акценте.", - "theme_setting_primary_color_title": "Примарна боја", - "theme_setting_system_primary_color_title": "Користи системску боју", - "theme_setting_system_theme_switch": "Аутоматски (Прати опције система)", - "theme_setting_theme_subtitle": "Одабери тему система", - "theme_setting_three_stage_loading_subtitle": "Тростепено учитавање можда убрза учитавање, по цену потрошње података", - "theme_setting_three_stage_loading_title": "Активирај тростепено учитавање", - "they_will_be_merged_together": "Они ће бити спојени заједно", - "third_party_resources": "Ресурси трећих страна", - "time_based_memories": "Сећања заснована на времену", - "timeline": "Временска линија", - "timezone": "Временска зона", - "to_archive": "Архивирај", - "to_change_password": "Промени лозинку", - "to_favorite": "Постави као фаворит", - "to_login": "Пријава", - "to_parent": "Врати се назад", - "to_trash": "Смеће", - "toggle_settings": "Nameсти подешавања", - "total": "Укупно", - "total_usage": "Укупна употреба", - "trash": "Отпад", - "trash_all": "Баци све у отпад", - "trash_count": "Отпад {count, number}", - "trash_delete_asset": "Отпад/Избриши датотеку", - "trash_emptied": "Испразнио смеће", - "trash_no_results_message": "Слике и видео записи у отпаду ће се појавити овде.", - "trash_page_delete_all": "Обриши све", - "trash_page_empty_trash_dialog_content": "Да ли желите да испразните своја премештена средства? Ови предмети ће бити трајно уклоњени из Immich-a", - "trash_page_info": "Ставке избачене из отпада биће трајно обрисане након {days} дана", - "trash_page_no_assets": "Нема елемената у отпаду", - "trash_page_restore_all": "Врати све", - "trash_page_select_assets_btn": "Изаберите средства", - "trash_page_title": "Отпад ({count})", - "trashed_items_will_be_permanently_deleted_after": "Датотеке у отпаду ће бити трајно избрисане након {days, plural, one {# дан} few {# дана} other {# дана}}.", - "troubleshoot": "Решавање проблема", - "type": "Врста", - "unable_to_change_pin_code": "Није могуће променити ПИН код", - "unable_to_setup_pin_code": "Није могуће подесити ПИН код", - "unarchive": "Врати из архиве", - "unarchived_count": "{count, plural, other {Неархивирано#}}", - "undo": "Опозови", - "unfavorite": "Избаци из омиљених (унфаворите)", - "unhide_person": "Откриј особу", - "unknown": "Непознат", - "unknown_country": "Непозната земља", - "unknown_year": "Непозната Година", - "unlimited": "Неограничено", - "unlink_motion_video": "Одвежи видео од слике", - "unlink_oauth": "Прекини везу са Оаутх-ом", - "unlinked_oauth_account": "Опозвана веза OAuth налога", - "unmute_memories": "Укључи успомене", - "unnamed_album": "Неименовани албум", - "unnamed_album_delete_confirmation": "Да ли сте сигурни да желите да избришете овај албум?", - "unnamed_share": "Неименовано делење", - "unsaved_change": "Несачувана промена", - "unselect_all": "Поништи све", - "unselect_all_duplicates": "Поништи избор свих дупликата", - "unstack": "Разгрупиши", - "unstack_action_prompt": "{count} разгруписано", - "unstacked_assets_count": "Разгруписано {count, plural, one {# датотека} other {# датотеке}}", - "untagged": "Неозначено", - "up_next": "Следеће", - "updated_at": "Ажурирано", - "updated_password": "Ажурирана лозинка", - "upload": "Уплоадуј", - "upload_concurrency": "Паралелно уплоадовање", - "upload_dialog_info": "Да ли желите да направите резервну копију изабраних елемената на серверу?", - "upload_dialog_title": "Отпреми елемент", - "upload_errors": "Отпремање је завршено са {count, plural, one {# грешком} other {# грешака}}, освежите страницу да бисте видели нове датотеке за отпремање (уплоад).", - "upload_progress": "Преостало {remaining, number} – Обрађено {processed, number}/{total, number}", - "upload_skipped_duplicates": "Прескочено {count, plural, one {# дупла датотека} other {# дуплих датотека}}", - "upload_status_duplicates": "Дупликати", - "upload_status_errors": "Грешке", - "upload_status_uploaded": "Отпремљено (Уплоадед)", - "upload_success": "Отпремање је успешно, освежите страницу да бисте видели нова средства за отпремање (уплоад).", - "upload_to_immich": "Отпреми у Immich ({count})", - "uploading": "Отпремање", - "url": "URL", - "usage": "Употреба", - "use_biometric": "Користи биометрију", - "use_current_connection": "користи тренутну везу", - "use_custom_date_range": "Уместо тога користите прилагођени период", - "user": "Корисник", - "user_id": "ИД корисника", - "user_liked": "{user} је лајковао {type, select, photo {ову фотографију} video {овај видео запис} asset {ову датотеку} other {ово}}", - "user_pin_code_settings": "ПИН код", - "user_pin_code_settings_description": "Управљајте својим ПИН кодом", - "user_privacy": "Приватност корисника", - "user_purchase_settings": "Куповина", - "user_purchase_settings_description": "Управљајте куповином", - "user_role_set": "Постави {user} као {role}", - "user_usage_detail": "Детаљи коришћења корисника", - "user_usage_stats": "Статистика коришћења налога", - "user_usage_stats_description": "Погледајте статистику коришћења налога", - "username": "Корисничко име", - "users": "Корисници", - "utilities": "Алати", - "validate": "Провери", - "validate_endpoint_error": "Молимо вас да унесете важећи URL", - "variables": "Променљиве (вариаблес)", - "version": "Верзија", - "version_announcement_closing": "Твој пријатељ, Алекс", - "version_announcement_message": "Здраво! Доступна је нова верзија Immich-a. Молимо вас да одвојите мало времена да прочитате белешке о издању како бисте били сигурни да је ваше подешавање ажурирано и спречили евентуалне погрешне конфигурације, посебно ако користите WатцхТоwер или било који механизам који аутоматски ажурира вашу Immich инстанцу.", - "version_history": "Историја верзија", - "version_history_item": "Инсталирано {version} {date}", - "video": "Видео запис", - "video_hover_setting": "Пусти сличицу видеа када лебди", - "video_hover_setting_description": "Пусти сличицу видеа када миш пређе преко ставке. Чак и када је онемогућена, репродукција се може покренути преласком миша преко иконе за репродукцију.", - "videos": "Видео записи", - "videos_count": "{count, plural, one {# видео запис} few {# видео записа} other {# видео записа}}", - "view": "Гледај (виеw)", - "view_album": "Погледај албум", - "view_all": "Прикажи Све", - "view_all_users": "Прикажи све кориснике", - "view_in_timeline": "Прикажи у временској линији", - "view_link": "Погледај везу", - "view_links": "Прикажи везе", - "view_name": "Погледати", - "view_next_asset": "Погледајте следећу датотеку", - "view_previous_asset": "Погледај претходну датотеку", - "view_qr_code": "Погледајте QР код", - "view_stack": "Прикажи групу", - "view_user": "Прикажи корисника", - "viewer_remove_from_stack": "Уклони из групе", - "viewer_stack_use_as_main_asset": "Користи као главну датотеку", - "viewer_unstack": "Разгрупиши", - "visibility_changed": "Видљивост је промењена за {count, plural, one {# особу} other {# особе}}", - "waiting": "На чекању", - "warning": "Упозорење", - "week": "Недеља", - "welcome": "Добродошли", - "welcome_to_immich": "Добродошли у иммицх", - "wifi_name": "Назив Wi-Fi мреже", - "year": "Година", - "years_ago": "пре {years, plural, one {# године} other {# година}}", - "yes": "Да", - "you_dont_have_any_shared_links": "Немате ниједно дељење везе", - "your_wifi_name": "Име ваше Wi-Fi мреже", - "zoom_image": "Зумирај слику" -} +{} diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index b6f36d8c70..0967ef424b 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -1,1859 +1 @@ -{ - "about": "O aplikaciji", - "account": "Nalog", - "account_settings": "Podešavanja za Profil", - "acknowledge": "Potvrdi", - "action": "Postupak", - "action_common_update": "Ažuriraj", - "action_description": "Skup akcija da se obave na filtriranim aktivima", - "actions": "Postupci", - "active": "Aktivni", - "active_count": "Aktivno: {count}", - "activity": "Aktivnost", - "activity_changed": "Aktivnost je {enabled, select, true {omogućena} other {onemogućena}}", - "add": "Dodaj", - "add_a_description": "Dodaj opis", - "add_a_location": "Dodaj lokaciju", - "add_a_name": "Dodaj ime", - "add_a_title": "Dodaj naslov", - "add_action": "Dodaj akciju", - "add_action_description": "Klikni da dodas akciju", - "add_assets": "Dodaj aktive", - "add_birthday": "Dodaj rođendan", - "add_endpoint": "Dodajte krajnju tačku", - "add_exclusion_pattern": "Dodajte obrazac izuzimanja", - "add_filter": "Dodaj filter", - "add_filter_description": "Klikni da dodas stanje filtera", - "add_location": "Dodaj lokaciju", - "add_more_users": "Dodaj korisnike", - "add_partner": "Dodaj partner", - "add_path": "Dodaj putanju", - "add_photos": "Dodaj fotografije", - "add_tag": "Dodaj oznaku", - "add_to": "Dodaj u…", - "add_to_album": "Dodaj u album", - "add_to_album_bottom_sheet_added": "Dodato u {album}", - "add_to_album_bottom_sheet_already_exists": "Već u {album}", - "add_to_album_bottom_sheet_some_local_assets": "Neki lokalni aktivi se ne mogu dodati u album", - "add_to_album_toggle": "Uključi/isključi izbor za {album}", - "add_to_albums": "Dodaj u albume", - "add_to_albums_count": "Dodaj u albume ({count})", - "add_to_bottom_bar": "Dodaj u", - "add_to_shared_album": "Dodaj u deljen album", - "add_upload_to_stack": "Dodaj fajl u snop", - "add_url": "Dodaj URL", - "added_to_archive": "Dodato u arhivu", - "added_to_favorites": "Dodato u favorite", - "added_to_favorites_count": "Dodato {count, number} u favorite", - "admin": { - "add_exclusion_pattern_description": "Dodajte obrasce isključenja. Korištenje *, ** i ? je podržano. Da biste ignorisali sve datoteke u bilo kom direktorijumu pod nazivom „Rav“, koristite „**/Rav/**“. Da biste ignorisali sve datoteke koje se završavaju na „.tif“, koristite „**/*.tif“. Da biste ignorisali apsolutnu putanju, koristite „/path/to/ignore/**“.", - "admin_user": "Administrator", - "asset_offline_description": "Ovo eksterno bibliotečko sredstvo se više ne nalazi na disku i premešteno je u smeće. Ako je datoteka premeštena unutar biblioteke, proverite svoju vremensku liniju za novo odgovarajuće sredstvo. Da biste vratili ovo sredstvo, uverite se da Immich može da pristupi dole navedenoj putanji datoteke i skenirajte biblioteku.", - "authentication_settings": "Podešavanja za autentifikaciju", - "authentication_settings_description": "Upravljajte lozinkom, OAuth-om i drugim podešavanjima autentifikacije", - "authentication_settings_disable_all": "Da li ste sigurni da želite da onemogućite sve metode prijavljivanja? Prijava će biti potpuno onemogućena.", - "authentication_settings_reenable": "Da biste ponovo omogućili, koristite komandu servera.", - "background_task_job": "Pozadinski zadaci", - "backup_database": "Kreirajte rezervnu kopiju baze podataka", - "backup_database_enable_description": "Omogući dampove baze podataka", - "backup_keep_last_amount": "Količina prethodnih dampova koje treba zadržati", - "backup_onboarding_1_description": "kopija na oblaku ili na drugoj fizičkoj lokaciji.", - "backup_onboarding_2_description": "lokalne kopije na različitim uređajima. Ovo uključuje glavne datoteke i rezervnu kopiju tih datoteka lokalno.", - "backup_onboarding_3_description": "ukupno kopija vaših podataka, uklučujući originalne datoteke. Ovo uključuje 1 udaljenu kopiju i 2 lokalne kopije.", - "backup_onboarding_description": "3-2-1 strategija rezervnih kopija je preporučena da zaštiti vaše podatke. Trebali biste čuvati kopije vaših otpremljenih slika/videa kao i Immich bazu podataka za sveobuhvatno rešenje za rezervne kopije.", - "backup_onboarding_footer": "Za više informacija o pravljenju rezervne kopije Immich-a, molimo vas pogledajte dokumentaciju.", - "backup_onboarding_parts_title": "3-2-1 rezervna kopija uključuje:", - "backup_onboarding_title": "Rezervne kopije", - "backup_settings": "Podešavanja dampa baze podataka", - "backup_settings_description": "Upravljajte podešavanjima dampa baze podataka.", - "cleared_jobs": "Očišćeni poslovi za: {job}", - "config_set_by_file": "Konfiguraciju trenutno postavlja konfiguracioni fajl", - "confirm_delete_library": "Da li stvarno želite da izbrišete biblioteku {library} ?", - "confirm_delete_library_assets": "Da li ste sigurni da želite da izbrišete ovu biblioteku? Ovo će izbrisati {count, plural, one {1 sadrženu datoteku} few {# sadržene datoteke} other {# sadrženih datoteka}} iz Immich-a i akcija se ne može opozvati. Datoteke će ostati na disku.", - "confirm_email_below": "Da biste potvrdili, unesite \"{email}\" ispod", - "confirm_reprocess_all_faces": "Da li ste sigurni da želite da ponovo obradite sva lica? Ovo će takođe obrisati imenovane osobe.", - "confirm_user_password_reset": "Da li ste sigurni da želite da resetujete lozinku korisnika {user}?", - "confirm_user_pin_code_reset": "Da li ste sigurni da želite da resetujete PIN kod korisnika {user}?", - "copy_config_to_clipboard_description": "Kopirajte trenutnu konfiguraciju kao JSON objekat u klip", - "create_job": "Kreirajte posao", - "cron_expression": "Cron izraz (expression)", - "cron_expression_description": "Podesite interval skeniranja koristeći cron format. Za više informacija pogledajte npr. Crontab Guru", - "cron_expression_presets": "Predefinisana podešavanja Cron izraza (expression)", - "disable_login": "Onemogući prijavu", - "duplicate_detection_job_description": "Pokrenite mašinsko učenje na sredstvima da biste otkrili slične slike. Oslanja se na pametnu pretragu", - "exclusion_pattern_description": "Obrasci izuzimanja vam omogućavaju da ignorišete datoteke i fascikle kada skenirate biblioteku. Ovo je korisno ako imate fascikle koje sadrže datoteke koje ne želite da uvezete, kao što su RAW datoteke.", - "export_config_as_json_description": "Skini trenutnu sistemsku konfiguraciju kao JSON fajl", - "face_detection": "Detekcija lica", - "face_detection_description": "Otkrijte lica u datotekama pomoću mašinskog učenja. Za video snimke se uzima u obzir samo sličica. „Osveži“ (ponovno) obrađuje sve datoteke. „Resetovanje“ dodatno briše sve trenutne podatke o licu. „Nedostaju“ datoteke u redu koje još nisu obrađene. Otkrivena lica će biti stavljena u red za prepoznavanje lica nakon što se prepoznavanje lica završi, grupišući ih u postojeće ili nove osobe.", - "facial_recognition_job_description": "Grupa je detektovala lica i dodala ih postojećim osobama. Ovaj korak se pokreće nakon što je prepoznavanje lica završeno. „Resetuj“ (ponovno) grupiše sva lica. „Nedostaju“ lica u redovima kojima nije dodeljena osoba.", - "failed_job_command": "Komanda {command} nije uspela za posao: {job}", - "force_delete_user_warning": "UPOZORENJE: Ovo će odmah ukloniti korisnika i sve datoteke. Ovo se ne može opozvati i datoteke se ne mogu oporaviti.", - "image_format": "Format", - "image_format_description": "WebP proizvodi manje datoteke od JPEG, ali se sporije kodira.", - "image_fullsize_description": "Slika u punoj veličini sa ogoljenim metapodacima, koristi se kada je uvećana", - "image_fullsize_enabled": "Omogućite generisanje slike u punoj veličini", - "image_fullsize_enabled_description": "Generišite sliku pune veličine za formate koji nisu prilagođeni vebu. Kada je „Preferiraj ugrađeni pregled“ omogućen, ugrađeni pregledi se koriste direktno bez konverzije. Ne utiče na formate prilagođene vebu kao što je JPEG.", - "image_fullsize_quality_description": "Kvalitet slike u punoj veličini od 1-100. Više je bolje, ali proizvodi veće datoteke.", - "image_fullsize_title": "Podešavanja slike u punoj veličini", - "image_prefer_embedded_preview": "Preferirajte ugrađeni pregled", - "image_prefer_embedded_preview_setting_description": "Koristite ugrađene preglede u RAW fotografije kao ulaz za obradu slike kada su dostupne. Ovo može da proizvede preciznije boje za neke slike, ali kvalitet pregleda zavisi od kamere i slika može imati više nepravilnosti kompresije.", - "image_prefer_wide_gamut": "Preferirajte širok spektar", - "image_prefer_wide_gamut_setting_description": "Koristite Display P3 za sličice. Ovo bolje čuva živopisnost slika sa širokim prostorima boja, ali slike mogu izgledati drugačije na starim uređajima sa starom verzijom pretraživača. sRGB slike se čuvaju kao sRGB da bi se izbegle promene boja.", - "image_preview_description": "Slika srednje veličine sa uklonjenim metapodacima, koja se koristi prilikom pregleda jednog elementa i za mašinsko učenje", - "image_preview_quality_description": "Kvalitet pregleda od 1-100. Više je bolje, ali proizvodi veće datoteke i može smanjiti odziv aplikacije. Postavljanje niske vrednosti može uticati na kvalitet mašinskog učenja.", - "image_preview_title": "Podešavanja pregleda", - "image_progressive": "Napredan", - "image_quality": "Kvalitet", - "image_resolution": "Rezolucija", - "image_resolution_description": "Veće rezolucije mogu da sačuvaju više detalja, ali im je potrebno više vremena za kodiranje, imaju veće veličine datoteka i mogu da smanje odziv aplikacije.", - "image_settings": "Podešavanja slike", - "image_settings_description": "Upravljajte kvalitetom i rezolucijom generisanih slika", - "image_thumbnail_description": "Mala sličica sa ogoljenim metapodacima, koja se koristi prilikom pregleda grupa fotografija kao što je glavna vremenska linija", - "image_thumbnail_quality_description": "Kvalitet sličica od 1-100. Više je bolje, ali proizvodi veće datoteke i može smanjiti odziv aplikacije.", - "image_thumbnail_title": "Podešavanja sličica", - "job_concurrency": "{job} paralelnost", - "job_created": "Posao kreiran", - "job_not_concurrency_safe": "Ovaj posao nije bezbedan da bude paralelno aktivan.", - "job_settings": "Podešavanja posla", - "job_settings_description": "Upravljajte paralelnošću poslova", - "jobs_delayed": "{jobCount, plural, one {# odloženi} few {# odložena} other {# odloženih}}", - "jobs_failed": "{jobCount, plural, one {# neuspešni} few {# neuspešna} other {# neuspešnih}}", - "library_created": "Napravljena biblioteka: {library}", - "library_deleted": "Biblioteka je izbrisana", - "library_scanning": "Periodično skeniranje", - "library_scanning_description": "Konfigurišite periodično skeniranje biblioteke", - "library_scanning_enable_description": "Omogućite periodično skeniranje biblioteke", - "library_settings": "Spoljna biblioteka", - "library_settings_description": "Upravljajte podešavanjima spoljne biblioteke", - "library_tasks_description": "Obavljaj zadatke biblioteke", - "library_watching_enable_description": "Pratite spoljne biblioteke za promene datoteka", - "library_watching_settings": "Nadgledanje biblioteke (EKSPERIMENTALNO)", - "library_watching_settings_description": "Automatski pratite promenjene datoteke", - "logging_enable_description": "Omogući evidentiranje", - "logging_level_description": "Kada je omogućeno, koji nivo evidencije koristiti.", - "logging_settings": "Evidentiranje", - "machine_learning_availability_checks": "Provere dostupnosti", - "machine_learning_availability_checks_enabled": "Omogući provere dostupnosti", - "machine_learning_availability_checks_interval": "Interval provere", - "machine_learning_availability_checks_interval_description": "Interval u milisekundama između provera dostupnosti", - "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "Naziv CLIP modela je naveden ovde. Imajte na umu da morate ponovo da pokrenete posao „Pametno pretraživanje“ za sve slike nakon promene modela.", - "machine_learning_duplicate_detection": "Detekcija duplikata", - "machine_learning_duplicate_detection_enabled": "Omogućite otkrivanje duplikata", - "machine_learning_duplicate_detection_enabled_description": "Ako je onemogućeno, potpuno identična sredstva će i dalje biti uklonjena.", - "machine_learning_duplicate_detection_setting_description": "Koristite ugrađen CLIP da biste pronašli verovatne duplikate", - "machine_learning_enabled": "Omogućite mašinsko učenje", - "machine_learning_enabled_description": "Ako je onemogućeno, sve funkcije ML će biti onemogućene bez obzira na dole-navedena podešavanja.", - "machine_learning_facial_recognition": "Prepoznavanje lica", - "machine_learning_facial_recognition_description": "Otkrivanje, prepoznavanje i grupisanje lica na slikama", - "machine_learning_facial_recognition_model": "Model za prepoznavanje lica", - "machine_learning_facial_recognition_model_description": "Modeli su navedeni u opadajućem redosledu veličine. Veći modeli su sporiji i koriste više memorije, ali daju bolje rezultate. Imajte na umu da morate ponovo da pokrenete zadatak detekcije lica za sve slike nakon promene modela.", - "machine_learning_facial_recognition_setting": "Omogućite prepoznavanje lica", - "machine_learning_facial_recognition_setting_description": "Ako je onemogućeno, slike neće biti kodirane za prepoznavanje lica i neće popunjavati odeljak Ljudi na stranici Istraži.", - "machine_learning_max_detection_distance": "Maksimalna udaljenost detekcije", - "machine_learning_max_detection_distance_description": "Maksimalno rastojanje između dve slike da se smatraju duplikatima, u rasponu od 0,001-0,1. Veće vrednosti će otkriti više duplikata, ali mogu dovesti do lažnih pozitivnih rezultata.", - "machine_learning_max_recognition_distance": "Maksimalna udaljenost prepoznavanja", - "machine_learning_max_recognition_distance_description": "Maksimalna udaljenost između dva lica koja se smatra istom osobom, u rasponu od 0-2. Smanjenje ovog broja može sprečiti označavanje dve osobe kao iste osobe, dok povećanje može sprečiti etiketiranje iste osobe kao dve različite osobe. Imajte na umu da je lakše spojiti dve osobe nego podeliti jednu osobu na dvoje, pa pogrešite na strani nižeg praga kada je to moguće.", - "machine_learning_min_detection_score": "Najmanji rezultat detekcije", - "machine_learning_min_detection_score_description": "Minimalni rezultat pouzdanosti za lice koje treba otkriti od 0-1. Niže vrednosti će otkriti više lica, ali mogu dovesti do lažnih pozitivnih rezultata.", - "machine_learning_min_recognized_faces": "Najmanje prepoznatih lica", - "machine_learning_min_recognized_faces_description": "Minimalni broj prepoznatih lica za kreiranje osobe. Povećanje ovoga čini prepoznavanje lica preciznijim po cenu povećanja šanse da lice nije dodeljeno osobi.", - "machine_learning_settings": "Podešavanja mašinskog učenja", - "machine_learning_settings_description": "Upravljajte funkcijama i podešavanjima mašinskog učenja", - "machine_learning_smart_search": "Pametna pretraga", - "machine_learning_smart_search_description": "Potražite slike semantički koristeći ugrađeni CLIP", - "machine_learning_smart_search_enabled": "Omogućite pametnu pretragu", - "machine_learning_smart_search_enabled_description": "Ako je onemogućeno, slike neće biti kodirane za pametnu pretragu.", - "machine_learning_url_description": "URL servera za mašinsko učenje. Ako je navedeno više URL adresa, svaki server će biti pokušavan pojedinačno dok ne odgovori uspešno, redom od prvog do poslednjeg. Serveri koji ne odgovore biće privremeno ignorisani dok se ponovo ne povežu sa mrežom.", - "manage_concurrency": "Upravljanje paralelnošću", - "manage_log_settings": "Upravljajte podešavanjima evidencije", - "map_dark_style": "Tamni stil", - "map_enable_description": "Omogućite karakteristike mape", - "map_gps_settings": "Map & GPS podešavanja", - "map_gps_settings_description": "Upravljajte postavkama mape i GPS-a (obrnuto geokodiranje)", - "map_implications": "Funkcija mape se oslanja na eksternu uslugu pločica (tiles.immich.cloud)", - "map_light_style": "Svetli stil", - "map_manage_reverse_geocoding_settings": "Upravljajte podešavanjima Obrnuto geokodiranje", - "map_reverse_geocoding": "Obrnuto geokodiranje", - "map_reverse_geocoding_enable_description": "Omogućite obrnuto geokodiranje", - "map_reverse_geocoding_settings": "Podešavanja obrnutog geokodiranja", - "map_settings": "Podešavanje mape", - "map_settings_description": "Upravljajte podešavanjima mape", - "map_style_description": "URL do style.json mape tema izgleda", - "memory_cleanup_job": "Čišćenje memorije", - "memory_generate_job": "Generacija memorije", - "metadata_extraction_job": "Izvod metapodataka", - "metadata_extraction_job_description": "Izvucite informacije o metapodacima iz svake datoteke, kao što su GPS, lica i rezolucija", - "metadata_faces_import_setting": "Omogućite (enable) dodavanje lica", - "metadata_faces_import_setting_description": "Dodajte lica iz EXIF podataka slike i sličnih metapodataka", - "metadata_settings": "Podešavanje metapodataka", - "metadata_settings_description": "Upravljajte podešavanjima metapodataka", - "migration_job": "Migracije", - "migration_job_description": "Prenesite sličice datoteka i lica u najnoviju strukturu direktorijuma", - "nightly_tasks_cluster_faces_setting_description": "Pokreni prepoznavanje lica na novodetektovanim licima", - "nightly_tasks_cluster_new_faces_setting": "Združi nova lica", - "nightly_tasks_database_cleanup_setting": "Zadaci čiščenja baze podataka", - "nightly_tasks_database_cleanup_setting_description": "Očisti stare, istekle podatke iz baze podataka", - "nightly_tasks_generate_memories_setting": "Generiši sjećanja", - "nightly_tasks_generate_memories_setting_description": "Stvorite nova sećanja iz imovine", - "nightly_tasks_missing_thumbnails_setting": "Generiši nedostajuće sličice", - "nightly_tasks_missing_thumbnails_setting_description": "Dodajte elemente bez sličica u red za generisanje sličica", - "nightly_tasks_settings": "Podešavanja noćnih zadataka", - "nightly_tasks_settings_description": "Upravljaj noćnim zadacima", - "nightly_tasks_start_time_setting": "Vreme početka", - "nightly_tasks_start_time_setting_description": "Vreme kada server započinje noćne zadatke", - "nightly_tasks_sync_quota_usage_setting_description": "Ažurirajte kvotu memorijskog prostora korisnika na osnovu trenutne upotrebe", - "no_paths_added": "Nema dodatih putanja", - "no_pattern_added": "Nije dodat obrazac", - "note_apply_storage_label_previous_assets": "Napomena: Da biste primenili oznaku za skladištenje na prethodno otpremljena sredstva, pokrenite", - "note_cannot_be_changed_later": "NAPOMENA: Ovo se kasnije ne može promeniti!", - "notification_email_from_address": "Sa adrese", - "notification_email_from_address_description": "Adresa e-pošte pošiljaoca, na primer: \"Immich foto server \". Pobrinite se da koristite adresu sa koje vam je dozovljeno slati e-poštu.", - "notification_email_host_description": "Host servera e-pošte (npr. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Zanemarite greške sertifikata", - "notification_email_ignore_certificate_errors_description": "Ignorišite greške u validaciji TLS sertifikata (ne preporučuje se)", - "notification_email_password_description": "Lozinka za upotrebu pri autentifikaciji sa serverom e-pošte", - "notification_email_port_description": "Port servera e-pošte (npr. 25, 465 ili 587)", - "notification_email_sent_test_email_button": "Pošaljite probnu e-poštu i sačuvajte", - "notification_email_setting_description": "Podešavanja za slanje obaveštenja putem e-pošte", - "notification_email_test_email": "Pošaljite probnu e-poštu", - "notification_email_test_email_failed": "Slanje probne e-pošte nije uspelo, proverite vrednosti", - "notification_email_test_email_sent": "Probna e-pošta je poslata na {email}. Proverite svoje prijemno sanduče.", - "notification_email_username_description": "Korisničko ime koje se koristi prilikom autentifikacije na serveru e-pošte", - "notification_enable_email_notifications": "Omogućite obaveštenja putem e-pošte", - "notification_settings": "Podešavanja obaveštenja", - "notification_settings_description": "Upravljajte podešavanjima obaveštenja, uključujući e-poštu", - "oauth_auto_launch": "Automatsko pokretanje", - "oauth_auto_launch_description": "Pokrenite OAuth tok prijavljivanja automatski nakon navigacije na stranicu za prijavu", - "oauth_auto_register": "Automatska registracija", - "oauth_auto_register_description": "Automatski registrujte nove korisnike nakon što se prijavite pomoću OAuth-a", - "oauth_button_text": "Tekst dugmeta", - "oauth_client_secret_description": "Potrebno ako OAuth provajder ne podržava PKCE (Proof Key for Code Exchange)", - "oauth_enable_description": "Prijavite se pomoću OAuth-a", - "oauth_mobile_redirect_uri": "URI za preusmeravanje mobilnih uređaja", - "oauth_mobile_redirect_uri_override": "Zamena URI-ja mobilnog preusmeravanja", - "oauth_mobile_redirect_uri_override_description": "Omogući kada OAuth dobavljač (provider) ne dozvoljava mobilni URI, kao što je ''{callback}''", - "oauth_settings": "OAutorizacija", - "oauth_settings_description": "Upravljajte podešavanjima za prijavu sa OAutorizacijom", - "oauth_settings_more_details": "Za više detalja o ovoj funkciji pogledajte dokumente.", - "oauth_storage_label_claim": "Zahtev za nalepnicu za skladištenje", - "oauth_storage_label_claim_description": "Automatski podesite oznaku za skladištenje korisnika na vrednost ovog zahteva.", - "oauth_storage_quota_claim": "Zahtev za kvotu skladištenja", - "oauth_storage_quota_claim_description": "Automatski podesite kvotu memorijskog prostora korisnika na vrednost ovog zahteva.", - "oauth_storage_quota_default": "Podrazumevana kvota za skladištenje (GiB)", - "oauth_storage_quota_default_description": "Kvota u GiB koja se koristi kada nema potraživanja.", - "oauth_timeout": "Vremensko ograničenje zahteva", - "oauth_timeout_description": "Vremensko ograničenje za zahteve u milisekundama", - "password_enable_description": "Prijavite se pomoću e-pošte i lozinke", - "password_settings": "Lozinka za prijavu", - "password_settings_description": "Upravljajte podešavanjima za prijavu lozinkom", - "paths_validated_successfully": "Sve putanje su uspešno potvrđene", - "person_cleanup_job": "Čišćenje osoba", - "quota_size_gib": "Veličina kvote (GiB)", - "refreshing_all_libraries": "Osvežavanje svih biblioteka", - "registration": "Registracija administratora", - "registration_description": "Pošto ste prvi korisnik na sistemu, bićete dodeljeni kao Admin i odgovorni ste za administrativne zadatke, a dodatne korisnike ćete kreirati vi.", - "require_password_change_on_login": "Zahtevati od korisnika da promeni lozinku pri prvom prijavljivanju", - "reset_settings_to_default": "Resetujte podešavanja na podrazumevane vrednosti", - "reset_settings_to_recent_saved": "Resetujte podešavanja na nedavno sačuvana podešavanja", - "scanning_library": "Skeniranje biblioteke", - "search_jobs": "Traži poslove…", - "send_welcome_email": "Pošaljite e-poštu dobrodošlice", - "server_external_domain_settings": "Eksterni domain", - "server_external_domain_settings_description": "Domain za javne deljene veze, uključujući http(s)://", - "server_public_users": "Javni korisnici", - "server_public_users_description": "Svi korisnici (ime i adresa e-pošte) su navedeni prilikom dodavanja korisnika u deljene albume. Kada je onemogućena, lista korisnika će biti dostupna samo administratorima.", - "server_settings": "Podešavanja servera", - "server_settings_description": "Upravljajte podešavanjima servera", - "server_welcome_message": "Poruka dobrodošlice", - "server_welcome_message_description": "Poruka koja se prikazuje na stranici za prijavu.", - "sidecar_job": "Bočni (sidecar) metapodaci", - "sidecar_job_description": "Otkrijte ili sinhronizujte bočne (sidecar) metapodatke iz sistema datoteka", - "slideshow_duration_description": "Broj sekundi za prikaz svake slike", - "smart_search_job_description": "Pokrenite mašinsko učenje na datotekama da biste podržali pametnu pretragu", - "storage_template_date_time_description": "Vremenska oznaka kreiranja datoteke se koristi za informacije o datumu i vremenu", - "storage_template_date_time_sample": "Primer vremena {date}", - "storage_template_enable_description": "Omogući mehanizam za šablone za skladištenje", - "storage_template_hash_verification_enabled": "Heš verifikacija omogućena", - "storage_template_hash_verification_enabled_description": "Omogućava heš verifikaciju, ne onemogućavajte ovo osim ako niste sigurni u posledice", - "storage_template_migration": "Migracija šablona za skladištenje", - "storage_template_migration_description": "Primenite trenutni {template} na prethodno otpremljene elemente", - "storage_template_migration_info": "Promene šablona će se primeniti samo na nove datoteke. Da biste retroaktivno primenili šablon na prethodno otpremljene datoteke, pokrenite {job}.", - "storage_template_migration_job": "Posao migracije skladišta", - "storage_template_more_details": "Za više detalja o ovoj funkciji pogledajte Šablon za skladište i njegove implikacije", - "storage_template_path_length": "Približno ograničenje dužine putanje: {length, number}/{limit, number}", - "storage_template_settings": "Šablon za skladištenje", - "storage_template_settings_description": "Upravljajte strukturom direktorijuma i imenom datoteke sredstva za otpremanje", - "storage_template_user_label": "{label} je oznaka za skladištenje korisnika", - "system_settings": "Podešavanja sistema", - "tag_cleanup_job": "Čišćenje oznaka (tags)", - "template_email_available_tags": "Možete da koristite sledeće promenljive u svom šablonu: {tags}", - "template_email_if_empty": "Ako je šablon prazan, koristiće se podrazumevana adresa e-pošte.", - "template_email_invite_album": "Šablon za poziv u album", - "template_email_preview": "Pregled", - "template_email_settings": "Šabloni e-pošte", - "template_email_update_album": "Ažurirajte šablon albuma", - "template_email_welcome": "Šablon e-pošte dobrodošlice", - "template_settings": "Šabloni obaveštenja", - "template_settings_description": "Upravljajte prilagođenim šablonima za obaveštenja", - "theme_custom_css_settings": "Prilagođeni CSS", - "theme_custom_css_settings_description": "Kaskadni listovi stilova (CSS) omogućavaju prilagođavanje dizajna Immich-a.", - "theme_settings": "Podešavanje tema", - "theme_settings_description": "Upravljajte prilagođavanjem Immich web interfejsa", - "thumbnail_generation_job": "Generišite sličice", - "thumbnail_generation_job_description": "Generišite velike, male i zamućene sličice za svako sredstvo, kao i sličice za svaku osobu", - "transcoding_acceleration_api": "API za ubrzanje", - "transcoding_acceleration_api_description": "API koji će komunicirati sa vašim uređajem da bi ubrzao transkodiranje. Ovo podešavanje je 'najbolji napor': vraća se na softversko transkodiranje u slučaju neuspeha. VP9 može ili ne mora da radi u zavisnosti od vašeg hardvera.", - "transcoding_acceleration_nvenc": "NVENC (zahteva NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (zahteva Intel CPU 7. generacije ili noviji)", - "transcoding_acceleration_rkmpp": "RKMPP (samo na Rockchip SOC-ovima)", - "transcoding_acceleration_vaapi": "Video akceleracija API (VAAPI)", - "transcoding_accepted_audio_codecs": "Prihvaćeni audio kodeci", - "transcoding_accepted_audio_codecs_description": "Izaberite koje audio kodeke ne treba transkodirati. Koristi se samo za određene politike transkodiranja.", - "transcoding_accepted_containers": "Prihvaćeni kontejneri", - "transcoding_accepted_containers_description": "Izaberite koji formati kontejnera ne moraju da se remuksuju u MP4. Koristi se samo za određene uslove transkodiranja.", - "transcoding_accepted_video_codecs": "Prihvaćeni video kodeci", - "transcoding_accepted_video_codecs_description": "Izaberite koje video kodeke nije potrebno transkodirati. Koristi se samo za određene politike transkodiranja.", - "transcoding_advanced_options_description": "Opcije koje većina korisnika ne bi trebalo da menjaju", - "transcoding_audio_codec": "Audio kodek", - "transcoding_audio_codec_description": "Opus je opcija najvišeg kvaliteta, ali ima lošiju kompatibilnost sa starim uređajima ili softverom.", - "transcoding_bitrate_description": "Video snimci veći od maksimalne brzine prenosa ili nisu u prihvaćenom formatu", - "transcoding_codecs_learn_more": "Da biste saznali više o terminologiji koja se ovde koristi, pogledajte FFmpeg dokumentaciju za H.264 kodek, HEVC kodek i VP9 kodek.", - "transcoding_constant_quality_mode": "Režim konstantnog kvaliteta", - "transcoding_constant_quality_mode_description": "ICQ je bolji od CQP-a, ali neki uređaji za hardversko ubrzanje ne podržavaju ovaj režim. Podešavanje ove opcije će preferirati navedeni režim kada se koristi kodiranje zasnovano na kvalitetu. NVENC ignoriše jer ne podržava ICQ.", - "transcoding_constant_rate_factor": "Faktor konstantne stope (-crf)", - "transcoding_constant_rate_factor_description": "Nivo kvaliteta videa. Tipične vrednosti su 23 za H.264, 28 za HEVC, 31 za VP9 i 35 za AV1. Niže je bolje, ali proizvodi veće datoteke.", - "transcoding_disabled_description": "Nemojte transkodirati nijedan video, može prekinuti reprodukciju na nekim klijentima", - "transcoding_encoding_options": "Opcije Kodiranja", - "transcoding_encoding_options_description": "Podesite kodeke, rezoluciju, kvalitet i druge opcije za kodirane video zapise", - "transcoding_hardware_acceleration": "Hardversko ubrzanje", - "transcoding_hardware_acceleration_description": "Ekperimentalno; mnogo brže, ali će imati niži kvalitet pri istoj brzini prenosa", - "transcoding_hardware_decoding": "Hardversko dekodiranje", - "transcoding_hardware_decoding_setting_description": "Omogućava ubrzanje od kraja do kraja umesto da samo ubrzava kodiranje. Možda neće raditi na svim video snimcima.", - "transcoding_max_b_frames": "Maksimalni B-kadri", - "transcoding_max_b_frames_description": "Više vrednosti poboljšavaju efikasnost kompresije, ali usporavaju kodiranje. Možda nije kompatibilno sa hardverskim ubrzanjem na starijim uređajima. 0 onemogućava B-kadre, dok -1 automatski postavlja ovu vrednost.", - "transcoding_max_bitrate": "Maksimalni bitrate", - "transcoding_max_bitrate_description": "Podešavanje maksimalnog bitrate-a može učiniti veličine datoteka predvidljivijim uz manju cenu kvaliteta. Pri 720p, tipične vrednosti su 2600k za VP9 ili HEVC, ili 4500k za H.264. Onemogućeno ako je postavljeno na 0.", - "transcoding_max_keyframe_interval": "Maksimalni interval keyframe-a", - "transcoding_max_keyframe_interval_description": "Postavlja maksimalnu udaljenost kadrova između ključnih kadrova. Niže vrednosti pogoršavaju efikasnost kompresije, ali poboljšavaju vreme traženja i mogu poboljšati kvalitet scena sa brzim kretanjem. 0 automatski postavlja ovu vrednost.", - "transcoding_optimal_description": "Video snimci veći od ciljne rezolucije ili nisu u prihvaćenom formatu", - "transcoding_policy": "Uslovi Transkodiranja", - "transcoding_policy_description": "Odredi kad da se transkodira video", - "transcoding_preferred_hardware_device": "Željeni hardverski uređaj", - "transcoding_preferred_hardware_device_description": "Odnosi se samo na VAAPI i QSV. Postavlja dri node koji se koristi za hardversko transkodiranje.", - "transcoding_preset_preset": "Unapred podešena podešavanja (-preset)", - "transcoding_preset_preset_description": "Brzina kompresije. Sporije unapred podešene vrednosti proizvode manje datoteke i povećavaju kvalitet kada ciljate određenu brzinu prenosa. VP9 ignoriše brzine iznad 'brže'.", - "transcoding_reference_frames": "Referentni okviri (frames)", - "transcoding_reference_frames_description": "Broj okvira (frames) za referencu prilikom kompresije datog okvira. Više vrednosti poboljšavaju efikasnost kompresije, ali usporavaju kodiranje. 0 automatski postavlja ovu vrednost.", - "transcoding_required_description": "Samo video snimci koji nisu u prihvaćenom formatu", - "transcoding_settings": "Podešavanja video transkodiranja", - "transcoding_settings_description": "Upravljajte rezolucijom i informacijama o kodiranju video datoteka", - "transcoding_target_resolution": "Ciljana rezolucija", - "transcoding_target_resolution_description": "Veće rezolucije mogu da sačuvaju više detalja, ali im je potrebno više vremena za kodiranje, imaju veće veličine datoteka i mogu da smanje brzinu aplikacije.", - "transcoding_temporal_aq": "Vremenski (Temporal) AQ", - "transcoding_temporal_aq_description": "Odnosi se samo na NVENC. Povećava kvalitet scena sa visokim detaljima i niskim pokretima. Možda nije kompatibilan sa starijim uređajima.", - "transcoding_threads": "Niti (threads)", - "transcoding_threads_description": "Više vrednosti dovode do bržeg kodiranja, ali ostavljaju manje prostora serveru za obradu drugih zadataka dok je aktivan. Ova vrednost ne bi trebalo da bude veća od broja CPU jezgara. Maksimizira iskorišćenost ako je podešeno na 0.", - "transcoding_tone_mapping": "Mapiranje (tone-mapping)", - "transcoding_tone_mapping_description": "Pokušava da se sačuva izgled HDR video zapisa kada se konvertuju u SDR. Svaki algoritam pravi različite kompromise za boju, detalje i osvetljenost. Hable čuva detalje, Mobius čuva boju, a Raeinhard svetlinu.", - "transcoding_transcode_policy": "Uslovi transkodiranja", - "transcoding_transcode_policy_description": "Uslovi o tome kada video treba transkodirati. HDR video snimci će uvek biti transkodirani (osim ako je transkodiranje onemogućeno).", - "transcoding_two_pass_encoding": "Dvoprolazno kodiranje", - "transcoding_two_pass_encoding_setting_description": "Transkodirajte u dva prolaza da biste proizveli bolje kodirane video zapise. Kada je maksimalna brzina u bitovima omogućena (potrebna za rad sa H.264 i HEVC), ovaj režim koristi opseg brzine u bitovima zasnovan na maksimalnoj brzini (max bitrate) i ignoriše CRF. Za VP9, CRF se može koristiti ako je maksimalna brzina prenosa onemogućena.", - "transcoding_video_codec": "Video kodek", - "transcoding_video_codec_description": "VP9 ima visoku efikasnost i web kompatibilnost, ali mu je potrebno više vremena za transkodiranje. HEVC radi slično, ali ima nižu web kompatibilnost. H.264 je široko kompatibilan i brzo se transkodira, ali proizvodi mnogo veće datoteke. AV1 je najefikasniji kodek, ali mu nedostaje podrška na starijim uređajima.", - "trash_enabled_description": "Omogućite funkcije Otpada", - "trash_number_of_days": "Broj dana", - "trash_number_of_days_description": "Broj dana za držanje datoteka u otpadu pre nego što ih trajno uklonite", - "trash_settings": "Podešavanja smeća", - "trash_settings_description": "Upravljajte podešavanjima smeća", - "user_cleanup_job": "Čišćenje korisnika", - "user_delete_delay": "Nalog i datoteke {user} biće zakazani za trajno brisanje za {delay, plural, one {# dan} other {# dana}}.", - "user_delete_delay_settings": "Izbriši uz kašnjenje", - "user_delete_delay_settings_description": "Broj dana nakon uklanjanja za trajno brisanje korisničkog naloga i datoteka. Posao brisanja korisnika se pokreće u ponoć da bi se proverili korisnici koji su spremni za brisanje. Promene ove postavke će biti procenjene pri sledećem izvršenju.", - "user_delete_immediately": "Nalog i datoteke {user} će biti stavljeni na čekanje za trajno brisanje odmah.", - "user_delete_immediately_checkbox": "Stavite korisnika i datoteke u red za trenutno brisanje", - "user_details": "Detalji korisnika", - "user_management": "Upravljanje korisnicima", - "user_password_has_been_reset": "Lozinka korisnika je resetovana:", - "user_password_reset_description": "Molimo da dostavite privremenu lozinku korisniku i obavestite ga da će morati da promeni lozinku prilikom sledećeg prijavljivanja.", - "user_restore_description": "Nalog {user} će biti vraćen.", - "user_restore_scheduled_removal": "Vrati korisnika - zakazano uklanjanje za {date, date, long}", - "user_settings": "Podešavanja korisnika", - "user_settings_description": "Upravljajte korisničkim podešavanjima", - "version_check_enabled_description": "Omogućite proveru novih izdanja", - "version_check_implications": "Funkcija provere verzije se oslanja na periodičnu komunikaciju sa github.com", - "version_check_settings": "Provera verzije", - "version_check_settings_description": "Omogućite/onemogućite obaveštenje o novoj verziji", - "video_conversion_job": "Transkodiranje video zapisa", - "video_conversion_job_description": "Transkodirajte video zapise za širu kompatibilnost sa pregledačima i uređajima" - }, - "admin_email": "Administratorska E-adresa", - "admin_password": "Administratorska Lozinka", - "administration": "Administracija", - "advanced": "Napredno", - "advanced_settings_enable_alternate_media_filter_subtitle": "Koristite ovu opciju za filtriranje medija tokom sinhronizacije na osnovu alternativnih kriterijuma. Pokušajte ovo samo ako imate problema sa aplikacijom da otkrije sve albume.", - "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Koristite filter za sinhronizaciju albuma na alternativnom uređaju", - "advanced_settings_log_level_title": "Nivo evidencije (log): {level}", - "advanced_settings_prefer_remote_subtitle": "Neki uređaji veoma sporo učitavaju sličice sa sredstava na uređaju. Aktivirajte ovo podešavanje da biste umesto toga učitali udaljene slike.", - "advanced_settings_prefer_remote_title": "Preferirajte udaljene slike", - "advanced_settings_proxy_headers_subtitle": "Definišite proksi zaglavlja koje Immich treba da pošalje sa svakim mrežnim zahtevom", - "advanced_settings_proxy_headers_title": "Proksi Headeri (headers)", - "advanced_settings_self_signed_ssl_subtitle": "Preskače verifikaciju SSL sertifikata za krajnju tačku servera. Obavezno za samopotpisane sertifikate.", - "advanced_settings_self_signed_ssl_title": "Dozvoli samopotpisane SSL sertifikate", - "advanced_settings_sync_remote_deletions_subtitle": "Automatski izbrišite ili vratite sredstvo na ovom uređaju kada se ta radnja preduzme na vebu", - "advanced_settings_sync_remote_deletions_title": "Sinhronizujte udaljena brisanja [EKSPERIMENTALNO]", - "advanced_settings_tile_subtitle": "Napredna korisnička podešavanja", - "advanced_settings_troubleshooting_subtitle": "Omogućite dodatne funkcije za rešavanje problema", - "advanced_settings_troubleshooting_title": "Rešavanje problema", - "age_months": "Starost{months, plural, one {# mesec} other {# meseci}}", - "age_year_months": "Starost 1 godina, {months, plural, one {# mesec} other {# mesec(a/i)}}", - "age_years": "{years, plural, other {Starost #}}", - "album_added": "Album dodan", - "album_added_notification_setting_description": "Primi obaveštenje e-poštom kad budeš dodan u deljen album", - "album_cover_updated": "Omot albuma ažuriran", - "album_delete_confirmation": "Da li stvarno želite da izbrišete album {album}?", - "album_delete_confirmation_description": "Ako se ovaj album deli, drugi korisnici više neće moći da mu pristupe.", - "album_info_card_backup_album_excluded": "ISKLJUČENO", - "album_info_card_backup_album_included": "UKLJUČENO", - "album_info_updated": "Informacija albuma ažurirana", - "album_leave": "Napustiti album?", - "album_leave_confirmation": "Da li stvarno želite da napustite {album}?", - "album_name": "Ime albuma", - "album_options": "Opcije albuma", - "album_remove_user": "Ukloniti korisnika?", - "album_remove_user_confirmation": "Da li ste sigurni da želite da uklonite {user}?", - "album_share_no_users": "Izgleda da ste podelili ovaj album sa svim korisnicima ili da nemate nijednog korisnika sa kojim biste delili.", - "album_updated": "Album ažuriran", - "album_updated_setting_description": "Primite obaveštenje e-poštom kada deljeni album ima nova svojstva", - "album_user_left": "Napustio/la {album}", - "album_user_removed": "Uklonjen {user}", - "album_viewer_appbar_delete_confirm": "Da li ste sigurni da želite da izbrišete ovaj album sa svog naloga?", - "album_viewer_appbar_share_err_delete": "Neuspešno brisanje albuma", - "album_viewer_appbar_share_err_leave": "Neuspešno izlaženje iz albuma", - "album_viewer_appbar_share_err_remove": "Problemi sa brisanjem zapisa iz albuma", - "album_viewer_appbar_share_err_title": "Neuspešno menjanje naziva albuma", - "album_viewer_appbar_share_leave": "Izađi iz albuma", - "album_viewer_appbar_share_to": "Podeli sa", - "album_viewer_page_share_add_users": "Dodaj korisnike", - "album_with_link_access": "Neka svako ko ima vezu vidi fotografije i ljude u ovom albumu.", - "albums": "Albumi", - "albums_count": "{count, plural, one {{count, number} Album} few {{count, number} Albumi} other {{count, number} Albumi}}", - "all": "Sve", - "all_albums": "Svi albumi", - "all_people": "Sve osobe", - "all_videos": "Svi video snimci", - "allow_dark_mode": "Dozvoli tamni režim", - "allow_edits": "Dozvoli uređenje", - "allow_public_user_to_download": "Dozvolite javnom korisniku da preuzme (download-uje)", - "allow_public_user_to_upload": "Dozvoli javnom korisniku da otpremi (upload-uje)", - "alt_text_qr_code": "Slika QR koda", - "anti_clockwise": "U smeru suprotnom od kazaljke na satu", - "api_key": "API ključ (key)", - "api_key_description": "Ova vrednost će biti prikazana samo jednom. Obavezno kopirajte pre nego što zatvorite prozor.", - "api_key_empty": "Ime vašeg API ključa ne bi trebalo da bude prazno", - "api_keys": "API ključevi (keys)", - "app_bar_signout_dialog_content": "Da li ste sigurni da želite da se odjavite?", - "app_bar_signout_dialog_ok": "Da", - "app_bar_signout_dialog_title": "Odjavite se", - "app_settings": "Podešavanja aplikacije", - "appears_in": "Pojavljuje se u", - "archive": "Arhiva", - "archive_or_unarchive_photo": "Arhivirajte ili poništite arhiviranje fotografije", - "archive_page_no_archived_assets": "Nisu pronađena arhivirana sredstva", - "archive_page_title": "Arhiva ({count})", - "archive_size": "Veličina arhive", - "archive_size_description": "Podesi veličinu arhive za preuzimanje (u GiB)", - "archived": "Arhivirano", - "archived_count": "{count, plural, other {Arhivirano #}}", - "are_these_the_same_person": "Da li su ovo ista osoba?", - "are_you_sure_to_do_this": "Jeste li sigurni da želite ovo da uradite?", - "asset_action_delete_err_read_only": "Ne mogu da obrišem element(e) samo za čitanje, preskačem", - "asset_action_share_err_offline": "Nije moguće preuzeti oflajn resurs(e), preskačem", - "asset_added_to_album": "Dodato u album", - "asset_adding_to_album": "Dodaje se u album…", - "asset_description_updated": "Opis datoteke je ažuriran", - "asset_filename_is_offline": "Datoteka {filename} je van mreže (offline)", - "asset_has_unassigned_faces": "Datoteka ima nedodeljena lica", - "asset_hashing": "Heširanje…", - "asset_list_group_by_sub_title": "Grupiši po", - "asset_list_layout_settings_dynamic_layout_title": "Dinamični raspored", - "asset_list_layout_settings_group_automatically": "Automatski", - "asset_list_layout_settings_group_by": "Grupiši zapise po", - "asset_list_layout_settings_group_by_month_day": "Mesec + Dan", - "asset_list_settings_subtitle": "Opcije za mrežni prikaz fotografija", - "asset_list_settings_title": "Mrežni prikaz fotografija", - "asset_offline": "Datoteka odsutna", - "asset_offline_description": "Ova vanjska datoteka se više ne nalazi na disku. Molimo kontaktirajte svog Immich administratora za pomoć.", - "asset_restored_successfully": "Imovina je uspešno vraćena", - "asset_skipped": "Preskočeno", - "asset_skipped_in_trash": "U otpad", - "asset_uploaded": "Otpremljeno (Uploaded)", - "asset_uploading": "Otpremanje…", - "asset_viewer_settings_subtitle": "Upravljajte podešavanjima pregledača galerije", - "asset_viewer_settings_title": "Pregledač imovine", - "assets": "Zapisi", - "assets_added_count": "Dodato {count, plural, one {# datoteka} other {# datoteka}}", - "assets_added_to_album_count": "Dodato je {count, plural, one {# datoteka} other {# datoteka}} u album", - "assets_count": "{count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", - "assets_deleted_permanently": "{count} elemenata trajno obrisano", - "assets_deleted_permanently_from_server": "{count} resurs(a) trajno obrisan(a) sa Immich servera", - "assets_moved_to_trash_count": "Premešteno {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}} u otpad", - "assets_permanently_deleted_count": "Trajno izbrisano {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", - "assets_removed_count": "Uklonjeno {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", - "assets_removed_permanently_from_device": "{count} elemenata trajno uklonjeno sa vašeg uređaja", - "assets_restore_confirmation": "Da li ste sigurni da želite da vratite sve svoje datoteke koje su u otpadu? Ne možete poništiti ovu radnju! Imajte na umu da se vanmrežna sredstva ne mogu vratiti na ovaj način.", - "assets_restored_count": "Vraćeno {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", - "assets_restored_successfully": "{count} elemenata uspešno vraćeno", - "assets_trashed": "{count} elemenata je prebačeno u otpad", - "assets_trashed_count": "Bačeno u otpad {count, plural, one {# datoteka} few{# datoteke} other {# datoteka}}", - "assets_trashed_from_server": "{count} resurs(a) obrisanih sa Immich servera", - "assets_were_part_of_album_count": "{count, plural, one {Datoteka je} other {Datoteke su}} već deo albuma", - "authorized_devices": "Ovlašćeni uređaji", - "automatic_endpoint_switching_subtitle": "Povežite se lokalno preko određenog Wi-Fi-ja kada je dostupan i koristite alternativne veze na drugim mestima", - "automatic_endpoint_switching_title": "Automatska promena URL-ova", - "back": "Nazad", - "back_close_deselect": "Nazad, zatvorite ili opozovite izbor", - "background_location_permission": "Dozvola za lokaciju u pozadini", - "background_location_permission_content": "Da bi se menjale mreže dok se radi u pozadini, Imih mora *uvek* imati precizan pristup lokaciji kako bi aplikacija mogla da pročita ime Wi-Fi mreže", - "backup": "Napravi rezervnu kopiju", - "backup_album_selection_page_albums_device": "Albuma na uređaju ({count})", - "backup_album_selection_page_albums_tap": "Dodirni da uključiš, dodirni dvaput da isključiš", - "backup_album_selection_page_assets_scatter": "Zapisi se mogu naći u više različitih albuma. Odatle albumi se mogu uključiti ili isključiti tokom procesa pravljenja pozadinskih kopija.", - "backup_album_selection_page_select_albums": "Odaberi albume", - "backup_album_selection_page_selection_info": "Informacije o selekciji", - "backup_album_selection_page_total_assets": "Ukupno jedinstvenih ***", - "backup_all": "Sve", - "backup_background_service_backup_failed_message": "Pravljenje rezervne kopije elemenata nije uspelo. Pokušava se ponovo…", - "backup_background_service_connection_failed_message": "Povezivanje sa serverom nije uspelo. Pokušavam ponovo…", - "backup_background_service_current_upload_notification": "Otpremanje {filename}", - "backup_background_service_default_notification": "Proveravanje novih zapisa…", - "backup_background_service_error_title": "Greška u pravljenju rezervnih kopija", - "backup_background_service_in_progress_notification": "Pravljenje rezervnih kopija zapisa…", - "backup_background_service_upload_failure_notification": "Neuspešno otpremljeno: {filename}", - "backup_controller_page_albums": "Napravi rezervnu kopiju albuma", - "backup_controller_page_background_app_refresh_disabled_content": "Aktiviraj pozadinsko osvežavanje u Opcije > Generalne > Pozadinsko Osvežavanje kako bi napravili rezervne kopije u pozadini.", - "backup_controller_page_background_app_refresh_disabled_title": "Pozadinsko osvežavanje isključeno", - "backup_controller_page_background_app_refresh_enable_button_text": "Idi u podešavanja", - "backup_controller_page_background_battery_info_link": "Pokaži mi kako", - "backup_controller_page_background_battery_info_message": "Za najpouzdanije pravljenje rezervnih kopija, ugasite bilo koju opciju u optimizacijama koje bi sprečavale Immich sa pravilnim radom.\n\nOvaj postupak varira od uređaja do uređaja, proverite potrebne korake za Vaš uređaj.", - "backup_controller_page_background_battery_info_title": "Optimizacija Baterije", - "backup_controller_page_background_charging": "Samo tokom punjenja", - "backup_controller_page_background_configure_error": "Neuspešno konfigurisanje pozadinskog servisa", - "backup_controller_page_background_delay": "Vreme između pravljejna rezervnih kopija zapisa: {duration}", - "backup_controller_page_background_description": "Uključi pozadinski servis da automatski praviš rezervne kopije, bez da otvaraš aplikaciju", - "backup_controller_page_background_is_off": "Automatsko pravljenje rezervnih kopija u pozadini je isključeno", - "backup_controller_page_background_is_on": "Automatsko pravljenje rezervnih kopija u pozadini je uključeno", - "backup_controller_page_background_turn_off": "Isključi pozadinski servis", - "backup_controller_page_background_turn_on": "Uključi pozadinski servis", - "backup_controller_page_background_wifi": "Samo na Wi-Fi", - "backup_controller_page_backup": "Rezervne kopije", - "backup_controller_page_backup_selected": "Odabrano: ", - "backup_controller_page_backup_sub": "Završeno pravljenje rezervne kopije fotografija i videa", - "backup_controller_page_created": "Napravljeno:{date}", - "backup_controller_page_desc_backup": "Uključi pravljenje rezervnih kopija u prvom planu da automatski napravite rezervne kopije kada otvorite aplikaciju.", - "backup_controller_page_excluded": "Isključeno: ", - "backup_controller_page_failed": "Neuspešno ({count})", - "backup_controller_page_filename": "Ime fajla: {filename} [{size}]", - "backup_controller_page_id": "ID:{id}", - "backup_controller_page_info": "Informacije", - "backup_controller_page_none_selected": "Ništa odabrano", - "backup_controller_page_remainder": "Ostatak", - "backup_controller_page_remainder_sub": "Ostale fotografije i video snimci za otpremanje od selekcije", - "backup_controller_page_server_storage": "Prostor na serveru", - "backup_controller_page_start_backup": "Pokreni pravljenje rezervne kopije", - "backup_controller_page_status_off": "Automatsko pravljenje rezervnih kopija u prvom planu je isključeno", - "backup_controller_page_status_on": "Automatsko pravljenje rezervnih kopija u prvom planu je uključeno", - "backup_controller_page_storage_format": "{used} od {total} iskorišćeno", - "backup_controller_page_to_backup": "Albumi koji će se otpremiti", - "backup_controller_page_total_sub": "Sve jedinstvene fotografije i videi iz odabranih albuma", - "backup_controller_page_turn_off": "Isključi pravljenje rezervnih kopija u prvom planu", - "backup_controller_page_turn_on": "Uključi pravljenje rezervnih kopija u prvom planu", - "backup_controller_page_uploading_file_info": "Otpremanje svojstava datoteke", - "backup_err_only_album": "Nemoguće brisanje jedinog albuma", - "backup_info_card_assets": "zapisi", - "backup_manual_cancelled": "Otkazano", - "backup_manual_in_progress": "Otpremanje je već u toku. Pokušajte kasnije", - "backup_manual_success": "Uspeh", - "backup_setting_subtitle": "Upravljajte podešavanjima otpremanja u pozadini i prednjem planu", - "backward": "Unazad", - "birthdate_saved": "Datum rođenja uspešno sačuvan", - "birthdate_set_description": "Datum rođenja se koristi da bi se izračunale godine ove osobe u dobu određene fotografije.", - "blurred_background": "Zamućena pozadina", - "bugs_and_feature_requests": "Greške (bugs) i zahtevi za funkcije", - "build": "Pod-verzija (Build)", - "build_image": "Sagradi (Build) image", - "bulk_delete_duplicates_confirmation": "Da li ste sigurni da želite grupno da izbrišete {count, plural, one {# dupliran elemenat} few {# duplirana elementa} other {# dupliranih elemenata}}? Ovo će zadržati najveće sredstvo svake grupe i trajno izbrisati sve druge duplikate. Ne možete poništiti ovu radnju!", - "bulk_keep_duplicates_confirmation": "Da li ste sigurni da želite da zadržite {count, plural, one {1 dupliranu datoteku} few {# duplirane datoteke} other {# dupliranih datoteka}}? Ovo će rešiti sve duplirane grupe bez brisanja bilo čega.", - "bulk_trash_duplicates_confirmation": "Da li ste sigurni da želite grupno da odbacite {count, plural, one {1 dupliranu datoteku} few {# duplirane datoteke} other {# dupliranih datoteka}}? Ovo će zadržati najveću datoteku svake grupe i odbaciti sve ostale duplikate.", - "buy": "Kupite licencu Immich-a", - "cache_settings_clear_cache_button": "Obriši keš memoriju", - "cache_settings_clear_cache_button_title": "Ova opcija briše keš memoriju aplikacije. Ovo će bitno uticati na performanse aplikacije dok se keš memorija ne učita ponovo.", - "cache_settings_duplicated_assets_subtitle": "Fotografije i video snimci koje je aplikacija stavila na crnu listu", - "cache_settings_duplicated_assets_title": "Duplirani elementi ({count})", - "cache_settings_statistics_album": "Minijature biblioteka", - "cache_settings_statistics_full": "Pune slike", - "cache_settings_statistics_shared": "Minijature deljenih albuma", - "cache_settings_statistics_thumbnail": "Minijature", - "cache_settings_statistics_title": "Iskorišćena keš memorija", - "cache_settings_subtitle": "Kontrole za keš memoriju mobilne aplikacije Immich", - "cache_settings_tile_subtitle": "Kontrolišite ponašanje lokalnog skladištenja", - "cache_settings_tile_title": "Lokalna memorija", - "cache_settings_title": "Opcije za keširanje", - "camera": "Kamera", - "camera_brand": "Brend kamere", - "camera_model": "Model kamere", - "cancel": "Odustani", - "cancel_search": "Otkaži pretragu", - "canceled": "Otkazano", - "cannot_merge_people": "Ne može spojiti osobe", - "cannot_undo_this_action": "Ne možete poništiti ovu radnju!", - "cannot_update_the_description": "Ne može ažurirati opis", - "change_date": "Promeni datum", - "change_display_order": "Promeni redosled prikaza", - "change_expiration_time": "Promeni vreme isteka", - "change_location": "Promeni mesto", - "change_name": "Promeni ime", - "change_name_successfully": "Promeni ime uspešno", - "change_password": "Promeni Lozinku", - "change_password_description": "Ovo je ili prvi put da se prijavljujete na sistem ili je podnet zahtev za promenu lozinke. Unesite novu lozinku ispod.", - "change_password_form_confirm_password": "Ponovo unesite šifru", - "change_password_form_description": "Ćao, {name}\n\nOvo je verovatno Vaše prvo pristupanje sistemu, ili je podnešen zahtev za promenu šifre. Molimo Vas, unesite novu šifru ispod.", - "change_password_form_new_password": "Nova šifra", - "change_password_form_password_mismatch": "Šifre se ne podudaraju", - "change_password_form_reenter_new_password": "Ponovo unesite novu šifru", - "change_pin_code": "Promena PIN koda", - "change_your_password": "Promeni svoju šifru", - "changed_visibility_successfully": "Vidljivost je uspešno promenjena", - "check_corrupt_asset_backup": "Proverite da li postoje oštećene rezervne kopije imovine", - "check_corrupt_asset_backup_button": "Izvršite proveru", - "check_corrupt_asset_backup_description": "Pokrenite ovu proveru samo preko Wi-Fi mreže i nakon što se napravi rezervna kopija svih podataka. Postupak može potrajati nekoliko minuta.", - "check_logs": "Proverite dnevnike (logs)", - "choose_matching_people_to_merge": "Izaberite odgovarajuće osobe za spajanje", - "city": "Grad", - "clear": "Jasno", - "clear_all": "Izbriši sve", - "clear_all_recent_searches": "Obrišite sve nedavne pretrage", - "clear_message": "Obriši poruku", - "clear_value": "Jasna vrednost", - "client_cert_import_success_msg": "Sertifikat klijenta je uvezen", - "client_cert_invalid_msg": "Nevažeća datoteka sertifikata ili pogrešna lozinka", - "client_cert_remove_msg": "Sertifikat klijenta je uklonjen", - "client_cert_subtitle": "Podržava samo PKCS12 (.p12, .pfx) format. Uvoz/uklanjanje sertifikata je dostupno samo pre prijave", - "client_cert_title": "SSL klijentski sertifikat", - "clockwise": "U smeru kazaljke", - "close": "Zatvori", - "collapse": "Skupi", - "collapse_all": "Skupi sve", - "color": "Boja", - "color_theme": "Režim boja", - "comment_deleted": "Komentar obrisan", - "comment_options": "Opcije komentara", - "comments_and_likes": "Komentari i lajkovi", - "comments_are_disabled": "Komentari su onemogućeni", - "common_create_new_album": "Kreiraj novi album", - "completed": "Završeno", - "confirm": "Potvrdi", - "confirm_admin_password": "Potvrdi Administrativnu Lozinku", - "confirm_delete_face": "Da li ste sigurni da želite da izbrišete osobu {name} iz dela?", - "confirm_delete_shared_link": "Da li ste sigurni da želite da izbrišete ovaj deljeni link?", - "confirm_keep_this_delete_others": "Sve ostale datoteke u grupi će biti izbrisane osim ove datoteke. Da li ste sigurni da želite da nastavite?", - "confirm_new_pin_code": "Potvrdite novi PIN kod", - "confirm_password": "Ponovo unesi šifru", - "contain": "Obuhvati", - "context": "Kontekst", - "continue": "Nastavi", - "control_bottom_app_bar_create_new_album": "Kreiraj novi album", - "control_bottom_app_bar_delete_from_immich": "Obriši iz Immich-a", - "control_bottom_app_bar_delete_from_local": "Obriši sa uređaja", - "control_bottom_app_bar_edit_location": "Izmeni lokaciju", - "control_bottom_app_bar_edit_time": "Izmeni datum i vreme", - "control_bottom_app_bar_share_link": "Deli link", - "control_bottom_app_bar_share_to": "Podeli sa", - "control_bottom_app_bar_trash_from_immich": "Premesti u otpad", - "copied_image_to_clipboard": "Kopirana slika u međuspremnik (clipboard).", - "copied_to_clipboard": "Kopirano u međuspremnik (clipboard)!", - "copy_error": "Greška pri kopiranju", - "copy_file_path": "Kopiraj putanju datoteke", - "copy_image": "Kopiraj sliku", - "copy_link": "Kopiraj vezu", - "copy_link_to_clipboard": "Kopirajte vezu u međuspremnik (clipboard)", - "copy_password": "Kopiraj lozinku", - "copy_to_clipboard": "Kopiraj u međuspremnik (clipboard)", - "country": "Država", - "cover": "Omot", - "covers": "Omoti", - "create": "Napravi", - "create_album": "Napravi album", - "create_album_page_untitled": "Bez naslova", - "create_library": "Napravi Biblioteku", - "create_link": "Napravi vezu", - "create_link_to_share": "Napravi vezu za deljenje", - "create_link_to_share_description": "Neka svako sa vezom vidi izabrane fotografije", - "create_new_person": "Napravi novu osobu", - "create_new_person_hint": "Dodelite izabrane datoteke novoj osobi", - "create_new_user": "Napravi novog korisnika", - "create_shared_album_page_share_add_assets": "DODAJ SREDSTVA", - "create_shared_album_page_share_select_photos": "Odaberi fotografije", - "create_tag": "Kreirajte oznaku (tag)", - "create_tag_description": "Napravite novu oznaku (tag). Za ugnežđene oznake, unesite punu putanju oznake uključujući kose crte.", - "create_user": "Napravi korisnika", - "created": "Napravljen", - "created_at": "Kreirano", - "crop": "Obrezivanje", - "curated_object_page_title": "Stvari", - "current_device": "Trenutni uređaj", - "current_pin_code": "Trenutni PIN kod", - "current_server_address": "Trenutna adresa servera", - "custom_locale": "Prilagođena lokacija (locale)", - "custom_locale_description": "Formatirajte datume i brojeve na osnovu jezika i regiona", - "daily_title_text_date": "E dd MMM", - "daily_title_text_date_year": "E dd MMM yyyy", - "dark": "Tamno", - "date_after": "Datum posle", - "date_and_time": "Datum i Vreme", - "date_before": "Datum pre", - "date_format": "E d LLL y • H:mm", - "date_of_birth_saved": "Datum rođenja uspešno sačuvan", - "date_range": "Raspon datuma", - "day": "Dan", - "deduplicate_all": "De-dupliciraj sve", - "deduplication_criteria_1": "Veličina slike u bajtovima", - "deduplication_criteria_2": "Broj EXIF podataka", - "deduplication_info": "Informacije o deduplikaciji", - "deduplication_info_description": "Da bismo automatski unapred odabrali datoteke i uklonili duplikate grupno, gledamo:", - "default_locale": "Podrazumevana lokacija (locale)", - "default_locale_description": "Formatirajte datume i brojeve na osnovu lokalizacije vašeg pretraživača", - "delete": "Obriši", - "delete_album": "Obriši album", - "delete_api_key_prompt": "Da li ste sigurni da želite da izbrišete ovaj API ključ (key)?", - "delete_dialog_alert": "Ove stvari će permanentno biti obrisane sa Immich-a i Vašeg uređaja", - "delete_dialog_alert_local": "Ove stavke će biti trajno uklonjene sa vašeg uređaja, ali će i dalje biti dostupne na Immich serveru", - "delete_dialog_alert_local_non_backed_up": "Neke stavke nisu rezervno kopirane na Immich-u i biće trajno uklonjene sa vašeg uređaja", - "delete_dialog_alert_remote": "Ove stavke će biti trajno izbrisane sa Immich servera", - "delete_dialog_ok_force": "Ipak obriši", - "delete_dialog_title": "Obriši permanentno", - "delete_duplicates_confirmation": "Da li ste sigurni da želite da trajno izbrišete ove duplikate?", - "delete_face": "Izbriši osobu", - "delete_key": "Izbriši ključ", - "delete_library": "Obriši biblioteku", - "delete_link": "Obriši vezu", - "delete_local_dialog_ok_backed_up_only": "Obriši samo rezervne kopije", - "delete_local_dialog_ok_force": "Ipak obriši", - "delete_others": "Izbrišite druge", - "delete_shared_link": "Obriši deljenu vezu", - "delete_shared_link_dialog_title": "Obriši deljeni link", - "delete_tag": "Obriši oznaku (tag)", - "delete_tag_confirmation_prompt": "Da li stvarno želite da izbrišete oznaku {tagName}?", - "delete_user": "Obriši korisnika", - "deleted_shared_link": "Obrišena deljena veza", - "deletes_missing_assets": "Briše sredstva koja nedostaju sa diska", - "description": "Opis", - "description_input_submit_error": "Greška pri ažuriranju opisa, proverite dnevnik za više detalja", - "details": "Detalji", - "direction": "Smer", - "disabled": "Onemogućeno", - "disallow_edits": "Zabrani izmene", - "discord": "Diskord", - "discover": "Otkrijte", - "dismiss_all_errors": "Odbacite sve greške", - "dismiss_error": "Odbaci grešku", - "display_options": "Opcije prikaza", - "display_order": "Redosled prikaza", - "display_original_photos": "Prikažite originalne fotografije", - "display_original_photos_setting_description": "Radije prikazujete originalnu fotografiju kada gledate materijal nego sličice kada je originalno delo kompatibilno sa webom. Ovo može dovesti do sporijeg prikaza fotografija.", - "do_not_show_again": "Ne prikaži ponovo ovu poruku", - "documentation": "Dokumentacija", - "done": "Urađeno", - "download": "Preuzmi", - "download_canceled": "Preuzmi otkazano", - "download_complete": "Preuzmi završeno", - "download_enqueue": "Preuzimanje je stavljeno u red", - "download_failed": "Preuzimanje nije uspelo", - "download_finished": "Preuzimanje završeno", - "download_include_embedded_motion_videos": "Ugrađeni video snimci", - "download_include_embedded_motion_videos_description": "Uključite video zapise ugrađene u fotografije u pokretu kao zasebnu datoteku", - "download_notfound": "Preuzimanje nije pronađeno", - "download_paused": "Preuzimanje je pauzirano", - "download_settings": "Preuzimanje", - "download_settings_description": "Upravljajte podešavanjima vezanim za preuzimanje datoteka", - "download_started": "Preuzimanje je započeto", - "download_sucess": "Preuzimanje je uspešno", - "download_sucess_android": "Mediji su preuzeti na DCIM/Immich", - "download_waiting_to_retry": "Čekanje na ponovni pokušaj", - "downloading": "Preuzimanje u toku", - "downloading_asset_filename": "Preuzimanje datoteke {filename}", - "downloading_media": "Preuzimanje medija", - "drop_files_to_upload": "Ubacite datoteke bilo gde da ih otpremite (upload-ujete)", - "duplicates": "Duplikati", - "duplicates_description": "Razrešite svaku grupu tako što ćete navesti duplikate, ako ih ima", - "duration": "Trajanje", - "edit": "Uredi", - "edit_album": "Uredi album", - "edit_avatar": "Uredi avatar", - "edit_date": "Uredi datum", - "edit_date_and_time": "Uredi datum i vreme", - "edit_exclusion_pattern": "Izmenite obrazac izuzimanja", - "edit_faces": "Uredi lica", - "edit_key": "Izmeni ključ", - "edit_link": "Uredi vezu", - "edit_location": "Uredi lokaciju", - "edit_location_dialog_title": "Lokacija", - "edit_name": "Uredi ime", - "edit_people": "Uredi osobe", - "edit_tag": "Uredi oznaku (tag)", - "edit_title": "Uredi titulu", - "edit_user": "Uredi korisnika", - "editor": "Urednik", - "editor_close_without_save_prompt": "Promene neće biti sačuvane", - "editor_close_without_save_title": "Zatvoriti uređivač?", - "email": "E-pošta", - "email_notifications": "Obaveštenja e-poštom", - "empty_folder": "Ova mapa je prazna", - "empty_trash": "Ispraznite smeće", - "empty_trash_confirmation": "Da li ste sigurni da želite da ispraznite smeće? Ovo će trajno ukloniti sve datoteke u smeću iz Immich-a.\nNe možete poništiti ovu radnju!", - "enable": "Omogući (Enable)", - "enabled": "Omogućeno (Enabled)", - "end_date": "Krajnji datum", - "enqueued": "Stavljeno u red", - "enter_wifi_name": "Unesite naziv Wi-Fi mreže", - "error": "Greška", - "error_change_sort_album": "Promena redosleda sortiranja albuma nije uspela", - "error_delete_face": "Greška pri brisanju osobe iz dela", - "error_loading_image": "Greška pri učitavanju slike", - "error_saving_image": "Greška: {error}", - "error_title": "Greška – Nešto je pošlo naopako", - "errors": { - "cannot_navigate_next_asset": "Nije moguće doći do sledeće datoteke", - "cannot_navigate_previous_asset": "Nije moguće doći do prethodne datoteke", - "cant_apply_changes": "Nije moguće primeniti promene", - "cant_change_activity": "Nije moguće {enabled, select, true {onemogućiti} other {omogućiti}} aktivnosti", - "cant_change_asset_favorite": "Nije moguće promeniti favorit za datoteku", - "cant_change_metadata_assets_count": "Nije moguće promeniti metapodatke za {count, plural, one {# datoteku} other {# datoteke}}", - "cant_get_faces": "Ne mogu da nađem lica", - "cant_get_number_of_comments": "Ne mogu dobiti broj komentara", - "cant_search_people": "Ne mogu pretraživati osobe", - "cant_search_places": "Ne mogu pretraživati mesta", - "error_adding_assets_to_album": "Greška pri dodavanju datoteka u album", - "error_adding_users_to_album": "Greška pri dodavanju korisnika u album", - "error_deleting_shared_user": "Greška pri brisanju deljenog korisnika", - "error_downloading": "Greška pri preuzimanju {filename}", - "error_hiding_buy_button": "Greška pri skrivanju dugmeta za kupovinu", - "error_removing_assets_from_album": "Greška pri uklanjanju datoteke iz albuma, proverite konzolu za više detalja", - "error_selecting_all_assets": "Greška pri izboru svih datoteka", - "exclusion_pattern_already_exists": "Ovaj obrazac isključenja već postoji.", - "failed_to_create_album": "Nije moguće kreirati album", - "failed_to_create_shared_link": "Pravljenje deljenog linka nije uspelo", - "failed_to_edit_shared_link": "Uređivanje deljenog linka nije uspelo", - "failed_to_get_people": "Neuspelo pozivanje osoba", - "failed_to_keep_this_delete_others": "Nije uspelo zadržavanje ovog dela i brisanje ostalih datoteka", - "failed_to_load_asset": "Učitavanje datoteka nije uspelo", - "failed_to_load_assets": "Nije uspelo učitavanje datoteka", - "failed_to_load_notifications": "Učitavanje obaveštenja nije uspelo", - "failed_to_load_people": "Učitavanje osoba nije uspelo", - "failed_to_remove_product_key": "Uklanjanje ključa proizvoda nije uspelo", - "failed_to_stack_assets": "Slaganje datoteka nije uspelo", - "failed_to_unstack_assets": "Rasklapanje datoteka nije uspelo", - "failed_to_update_notification_status": "Ažuriranje statusa obaveštenja nije uspelo", - "incorrect_email_or_password": "Neispravan e-mail ili lozinka", - "paths_validation_failed": "{paths, plural, one {# putanja nije prošla} few {# putanje nisu prošle} other {# putanja nisu prošle}} proveru valjanosti", - "profile_picture_transparent_pixels": "Slike profila ne mogu imati prozirne piksele. Molimo uvećajte i/ili pomerite sliku.", - "quota_higher_than_disk_size": "Postavili ste kvotu veću od veličine diska", - "unable_to_add_album_users": "Nije moguće dodati korisnike u album", - "unable_to_add_assets_to_shared_link": "Nije moguće dodati elemente deljenoj vezi", - "unable_to_add_comment": "Nije moguće dodati komentar", - "unable_to_add_exclusion_pattern": "Nije moguće dodati obrazac izuzimanja", - "unable_to_add_partners": "Nije moguće dodati partnere", - "unable_to_add_remove_archive": "Nije moguće {archived, select, true {ukloniti datoteke iz} other {dodati datoteke u}} arhivu", - "unable_to_add_remove_favorites": "Nije moguće {favorite, select, true {dodati datoteke u} other {ukloniti datoteke iz}} favorite", - "unable_to_archive_unarchive": "Nije moguće {archived, select, true {arhivirati} other {de-arhivirati}}", - "unable_to_change_album_user_role": "Nije moguće promeniti ulogu korisnika albuma", - "unable_to_change_date": "Nije moguće promeniti datum", - "unable_to_change_favorite": "Nije moguće promeniti favorit za datoteku/e", - "unable_to_change_location": "Nije moguće promeniti lokaciju", - "unable_to_change_password": "Nije moguće promeniti lozinku", - "unable_to_change_visibility": "Nije moguće promeniti vidljivost za {count, plural, one {# osobu} other {# osobe}}", - "unable_to_complete_oauth_login": "Nije moguće dovršiti OAuth prijavu", - "unable_to_connect": "Nije moguće povezati se", - "unable_to_copy_to_clipboard": "Nije moguće kopirati u međuspremnik (clipboard), proverite da li pristupate stranici preko https-a", - "unable_to_create_admin_account": "Nije moguće napraviti administratorski nalog", - "unable_to_create_api_key": "Nije moguće napraviti novi API ključ (key)", - "unable_to_create_library": "Nije moguće napraviti biblioteku", - "unable_to_create_user": "Nije moguće kreirati korisnika", - "unable_to_delete_album": "Nije moguće izbrisati album", - "unable_to_delete_asset": "Nije moguće izbrisati datoteke", - "unable_to_delete_assets": "Greška pri brisanju datoteka", - "unable_to_delete_exclusion_pattern": "Nije moguće izbrisati obrazac izuzimanja", - "unable_to_delete_shared_link": "Nije moguće izbrisati deljeni link", - "unable_to_delete_user": "Nije moguće izbrisati korisnika", - "unable_to_download_files": "Nije moguće preuzeti datoteke", - "unable_to_edit_exclusion_pattern": "Nije moguće izmeniti obrazac izuzimanja", - "unable_to_empty_trash": "Nije moguće isprazniti otpad", - "unable_to_enter_fullscreen": "Nije moguće otvoriti preko celog ekrana", - "unable_to_exit_fullscreen": "Nije moguće izaći iz celog ekrana", - "unable_to_get_comments_number": "Nije moguće dobiti broj komentara", - "unable_to_get_shared_link": "Preuzimanje deljene veze nije uspelo", - "unable_to_hide_person": "Nije moguće sakriti osobu", - "unable_to_link_motion_video": "Nije moguće povezati video sa slikom", - "unable_to_link_oauth_account": "Nije moguće povezati OAuth nalog", - "unable_to_log_out_all_devices": "Nije moguće odjaviti sve uređaje", - "unable_to_log_out_device": "Nije moguće odjaviti uređaj", - "unable_to_login_with_oauth": "Nije moguće prijaviti se pomoću OAuth-a", - "unable_to_play_video": "Nije moguće pustiti video", - "unable_to_reassign_assets_existing_person": "Nije moguće preraspodeliti datoteke na {name, select, null {postojeću osobu} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nije moguće preneti sredstva novoj osobi", - "unable_to_refresh_user": "Nije moguće osvežiti korisnika", - "unable_to_remove_album_users": "Nije moguće ukloniti korisnike iz albuma", - "unable_to_remove_api_key": "Nije moguće ukloniti API ključ (key)", - "unable_to_remove_assets_from_shared_link": "Nije moguće ukloniti elemente sa deljenog linka", - "unable_to_remove_library": "Nije moguće ukloniti biblioteku", - "unable_to_remove_partner": "Nije moguće ukloniti partnera", - "unable_to_remove_reaction": "Nije moguće ukloniti reakciju", - "unable_to_reset_password": "Nije moguće resetovati lozinku", - "unable_to_reset_pin_code": "Nije moguće resetovati PIN kod", - "unable_to_resolve_duplicate": "Nije moguće razrešiti duplikat", - "unable_to_restore_assets": "Nije moguće vratiti datoteke", - "unable_to_restore_trash": "Nije moguće povratiti otpad", - "unable_to_restore_user": "Nije moguće povratiti korisnika", - "unable_to_save_album": "Nije moguće sačuvati album", - "unable_to_save_api_key": "Nije moguće sačuvati API ključ (key)", - "unable_to_save_date_of_birth": "Nije moguće sačuvati datum rođenja", - "unable_to_save_name": "Nije moguće sačuvati ime", - "unable_to_save_profile": "Nije moguće sačuvati profil", - "unable_to_save_settings": "Nije moguće sačuvati podešavanja", - "unable_to_scan_libraries": "Nije moguće skenirati biblioteke", - "unable_to_scan_library": "Nije moguće skenirati biblioteku", - "unable_to_set_feature_photo": "Nije moguće postaviti istaknutu fotografiju", - "unable_to_set_profile_picture": "Nije moguće postaviti profilnu sliku", - "unable_to_submit_job": "Nije moguće predati zadatak", - "unable_to_trash_asset": "Nije moguće izbaciti materijal u otpad", - "unable_to_unlink_account": "Nije moguće raskinuti profil", - "unable_to_unlink_motion_video": "Nije moguće odvezati video od slike", - "unable_to_update_album_cover": "Nije moguće ažurirati naslovnicu albuma", - "unable_to_update_album_info": "Nije moguće ažurirati informacije o albumu", - "unable_to_update_library": "Nije moguće ažurirati biblioteku", - "unable_to_update_location": "Nije moguće ažurirati lokaciju", - "unable_to_update_settings": "Nije moguće ažurirati podešavanja", - "unable_to_update_timeline_display_status": "Nije moguće ažurirati status prikaza vremenske linije", - "unable_to_update_user": "Nije moguće ažurirati korisnika", - "unable_to_upload_file": "Nije moguće otpremiti datoteku" - }, - "exif": "EXIF", - "exif_bottom_sheet_description": "Dodaj opis...", - "exif_bottom_sheet_details": "DETALJI", - "exif_bottom_sheet_location": "LOKACIJA", - "exit_slideshow": "Izađi iz projekcije slajdova", - "expand_all": "Proširi sve", - "experimental_settings_new_asset_list_subtitle": "U izradi", - "experimental_settings_new_asset_list_title": "Aktiviraj eksperimentalni mrežni prikaz fotografija", - "experimental_settings_subtitle": "Koristiti na sopstvenu odgovornost!", - "experimental_settings_title": "Eksperimentalno", - "expire_after": "Da istekne nakon", - "expired": "Isteklo", - "expires_date": "Ističe {date}", - "explore": "Istražite", - "explorer": "Pretraživač (Explorer)", - "export": "Izvezi", - "export_as_json": "Izvezi JSON", - "extension": "Ekstenzija (Extension)", - "external": "Spoljašnji", - "external_libraries": "Spoljašnje Biblioteke", - "external_network": "Spoljna mreža", - "external_network_sheet_info": "Kada nije na željenoj Wi-Fi mreži, aplikacija će se povezati sa serverom preko prve od dole navedenih URL adresa do kojih može da dođe, počevši od vrha do dna", - "face_unassigned": "Neraspoređeni", - "failed": "Neuspešno", - "failed_to_load_assets": "Datoteke nisu uspešno učitane", - "failed_to_load_folder": "Učitavanje fascikle nije uspelo", - "favorite": "Favorit", - "favorite_or_unfavorite_photo": "Omiljena ili neomiljena fotografija", - "favorites": "Favoriti", - "favorites_page_no_favorites": "Nije pronađen nijedan omiljeni materijal", - "feature_photo_updated": "Glavna fotografija je ažurirana", - "features": "Funkcije (features)", - "features_setting_description": "Upravljajte funkcijama aplikacije", - "file_name_or_extension": "Ime datoteke ili ekstenzija", - "filename": "Ime datoteke", - "filetype": "Vrsta dokumenta", - "filter_people": "Filtriranje osoba", - "filter_places": "Filtrirajte mesta", - "find_them_fast": "Brzo ih pronađite po imenu pomoću pretrage", - "fix_incorrect_match": "Ispravite netačno podudaranje", - "folder": "Fascikla", - "folder_not_found": "Fascikla nije pronađena", - "folders": "Fascikle (Folders)", - "folders_feature_description": "Pregledavanje prikaza fascikle za fotografije i video zapisa u sistemu datoteka", - "forward": "Napred", - "general": "Generalno", - "get_help": "Nađi pomoć", - "get_wifiname_error": "Nije moguće dobiti ime Wi-Fi mreže. Uverite se da ste dali potrebne dozvole i da ste povezani na Wi-Fi mrežu", - "getting_started": "Počinjem", - "go_back": "Vrati se", - "go_to_folder": "Idi u fasciklu", - "go_to_search": "Idi na pretragu", - "grant_permission": "Daj dozvolu", - "group_albums_by": "Grupni albumi po...", - "group_country": "Grupa po država", - "group_no": "Bez grupisanja", - "group_owner": "Grupirajte po vlasniku", - "group_places_by": "Grupirajte mesta po...", - "group_year": "Grupirajte po godini", - "haptic_feedback_switch": "Omogući haptičku povratnu informaciju", - "haptic_feedback_title": "Haptičke povratne informacije", - "has_quota": "Ima kvotu", - "header_settings_add_header_tip": "Dodaj zaglavlje", - "header_settings_field_validator_msg": "Vrednost ne može biti prazna", - "header_settings_header_name_input": "Naziv zaglavlja", - "header_settings_header_value_input": "Vrednost zaglavlja", - "headers_settings_tile_title": "Prilagođeni proksi zaglavci", - "hi_user": "Zdravo {name} ({email})", - "hide_all_people": "Sakrij sve osobe", - "hide_gallery": "Sakrij galeriju", - "hide_named_person": "Sakrij osobu {name}", - "hide_password": "Sakrij lozinku", - "hide_person": "Sakrij osobu", - "hide_unnamed_people": "Sakrij neimenovane osobe", - "home_page_add_to_album_conflicts": "Dodat {added} zapis u album {album}. {failed} zapisi su već u albumu.", - "home_page_add_to_album_err_local": "Trenutno nemoguće dodati lokalne zapise u albume, preskacu se", - "home_page_add_to_album_success": "Dodate {added} stavke u album {album}.", - "home_page_album_err_partner": "Još uvek nije moguće dodati partnerska sredstva u album, preskačem", - "home_page_archive_err_local": "Još uvek nije moguće arhivirati lokalne resurse, preskačem", - "home_page_archive_err_partner": "Ne mogu da arhiviram partnersku imovinu, preskačem", - "home_page_building_timeline": "Kreiranje hronološke linije", - "home_page_delete_err_partner": "Ne mogu da obrišem partnersku imovinu, preskačem", - "home_page_delete_remote_err_local": "Lokalna sredstva u obrisavanju udaljenog izbora, preskakanje", - "home_page_favorite_err_local": "Trenutno nije moguce dodati lokalne zapise u favorite, preskacu se", - "home_page_favorite_err_partner": "Još uvek nije moguće označiti partnerske resurse kao omiljene, preskačem", - "home_page_first_time_notice": "Ako je ovo prvi put da koristite aplikaciju, molimo Vas da odaberete albume koje želite da sačuvate", - "home_page_share_err_local": "Ne mogu da delim lokalne resurse preko linka, preskačem", - "home_page_upload_err_limit": "Možete otpremiti najviše 30 elemenata istovremeno, preskačući", - "host": "Domaćin (Host)", - "hour": "Sat", - "id": "ID", - "ignore_icloud_photos": "Ignorišite iCloud fotografije", - "ignore_icloud_photos_description": "Fotografije koje su sačuvane na iCloud-u neće biti otpremljene na Immich server", - "image": "Fotografija", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} snimljeno {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} snimljeno sa {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} snimljeno sa {person1} i {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} snimljeno sa {person1}, {person2} i {person3} {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} snimljeno sa {person1}, {person2} i još {additionalCount, number} ostalih {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} sa {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} sa {person1} i {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} snimljenou {city}, {country} sa {person1}, {person2} i {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} sa {person1}, {person2} i još {additionalCount, number} drugih {date}", - "image_saved_successfully": "Slika je sačuvana", - "image_viewer_page_state_provider_download_started": "Preuzimanje je započeto", - "image_viewer_page_state_provider_download_success": "Preuzimanje Uspešno", - "image_viewer_page_state_provider_share_error": "Greška pri deljenju", - "immich_logo": "Logo Immich-a", - "immich_web_interface": "Web interfejs Immich-a", - "import_from_json": "Uvezi iz JSON-a", - "import_path": "Putanja uvoza", - "in_albums": "U {count, plural, one {# albumu} few {# albuma} other {# albuma}}", - "in_archive": "U arhivi", - "include_archived": "Obuhvati arhivirano", - "include_shared_albums": "Obuhvati deljene albume", - "include_shared_partner_assets": "Obuhvati zajedničke datoteke partnera", - "individual_share": "Individualni udeo", - "individual_shares": "Pojedinačne akcije", - "info": "Informacija", - "interval": { - "day_at_onepm": "Svaki dan u 1pm", - "hours": "{hours, plural, one {Svaki sat} few {Svakih {hours, number} sata} other {Svakih {hours, number} sati}}", - "night_at_midnight": "Svaka noć u ponoć", - "night_at_twoam": "Svaka noć u 2am" - }, - "invalid_date": "Nevažeći datum", - "invalid_date_format": "Nevažeći format datuma", - "invite_people": "Pozovite ljude", - "invite_to_album": "Pozovi na album", - "items_count": "{count, plural, one {# datoteka} other {# datoteka}}", - "jobs": "Poslovi", - "keep": "Zadrži", - "keep_all": "Zadrži sve", - "keep_this_delete_others": "Zadrži ovo, izbriši druge", - "kept_this_deleted_others": "Zadržana je ova datoteka i izbrisano {count, plural, one {# datoteka} other {# datoteka}}", - "keyboard_shortcuts": "Prečice na tastaturi", - "language": "Jezik", - "language_setting_description": "Izaberite željeni jezik", - "last_seen": "Poslednji put viđen", - "latest_version": "Najnovija verzija", - "latitude": "Geografska širina", - "leave": "Napusti", - "lens_model": "Model sočiva", - "let_others_respond": "Dozvoli da drugi komentarišu", - "level": "Nivo", - "library": "Biblioteka", - "library_options": "Opcije biblioteke", - "library_page_device_albums": "Albumi na uređaju", - "library_page_new_album": "Novi album", - "library_page_sort_asset_count": "Broj sredstava", - "library_page_sort_created": "Najnovije kreirano", - "library_page_sort_last_modified": "Poslednja izmena", - "library_page_sort_title": "Naziv albuma", - "light": "Svetlo", - "like_deleted": "Lajkuj izbrisano", - "link_motion_video": "Napravi vezu za video zapis", - "link_to_oauth": "Veza do OAuth-a", - "linked_oauth_account": "Povezani OAuth nalog", - "list": "Izlistaj", - "loading": "Učitavanje", - "loading_search_results_failed": "Učitavanje rezultata pretrage nije uspelo", - "local_network_sheet_info": "Aplikacija će se povezati sa serverom preko ove URL adrese kada koristi navedenu Vi-Fi mrežu", - "location_permission": "Dozvola za lokaciju", - "location_permission_content": "Da bi koristio funkciju automatskog prebacivanja, Immich-u je potrebna precizna dozvola za lokaciju kako bi mogao da pročita naziv trenutne Wi-Fi mreže", - "location_picker_choose_on_map": "Izaberite na mapi", - "location_picker_latitude_error": "Unesite važeću geografsku širinu", - "location_picker_latitude_hint": "Unesite svoju geografsku širinu ovde", - "location_picker_longitude_error": "Unesite važeću geografsku dužinu", - "location_picker_longitude_hint": "Unesite svoju geografsku dužinu ovde", - "log_out": "Odjavi se", - "log_out_all_devices": "Odjavite se sa svih uređaja", - "logged_out_all_devices": "Odjavljeni su svi uređaji", - "logged_out_device": "Odjavljen uređaj", - "login": "Prijava", - "login_disabled": "Prijava je onemogućena", - "login_form_api_exception": "Izuzetak API-ja. Molimo vas da proverite URL adresu servera i pokušate ponovo.", - "login_form_back_button_text": "Nazad", - "login_form_email_hint": "vašemail@email.com", - "login_form_endpoint_hint": "http://ip-vašeg-servera:port", - "login_form_endpoint_url": "URL Servera", - "login_form_err_http": "Dopiši http:// ili https://", - "login_form_err_invalid_email": "Nevažeći Email", - "login_form_err_invalid_url": "Ne važeći link (URL)", - "login_form_err_leading_whitespace": "Razmak ispred", - "login_form_err_trailing_whitespace": "Razmak iza", - "login_form_failed_get_oauth_server_config": "Evidencija grešaka koristeći OAuth, proveriti serverski link (URL)", - "login_form_failed_get_oauth_server_disable": "OAuth opcija nije dostupna na ovom serveru", - "login_form_failed_login": "Neuspešna prijava, proveri URL servera, email i šifru", - "login_form_handshake_exception": "Došlo je do izuzetka rukostiskanja sa serverom. Omogućite podršku za samopotpisane sertifikate u podešavanjima ako koristite samopotpisani sertifikat.", - "login_form_password_hint": "šifra", - "login_form_save_login": "Ostani prijavljen", - "login_form_server_error": "Nije moguće povezati se sa serverom.", - "login_has_been_disabled": "Prijava je onemogućena.", - "login_password_changed_error": "Došlo je do greške prilikom ažuriranja lozinke", - "login_password_changed_success": "Lozinka je uspešno ažurirana", - "logout_all_device_confirmation": "Da li ste sigurni da želite da se odjavite sa svih uređaja?", - "logout_this_device_confirmation": "Da li ste sigurni da želite da se odjavite sa ovog uređaja?", - "longitude": "Geografska dužina", - "look": "Pogledaj", - "loop_videos": "Ponavljajte video zapise", - "loop_videos_description": "Omogućite za automatsko ponavljanje video zapisa u pregledniku detalja.", - "main_branch_warning": "Upotrebljavate razvojnu verziju; strogo preporučujemo upotrebu izdate verzije!", - "main_menu": "Glavni meni", - "make": "Kreiraj", - "manage_shared_links": "Upravljajte deljenim vezama", - "manage_sharing_with_partners": "Upravljajte deljenjem sa partnerima", - "manage_the_app_settings": "Upravljajte podešavanjima aplikacije", - "manage_your_account": "Upravljajte vašim profilom", - "manage_your_api_keys": "Upravljajte API ključevima (keys)", - "manage_your_devices": "Upravljajte svojim prijavljenim uređajima", - "manage_your_oauth_connection": "Upravljajte svojom OAuth vezom", - "map": "Mapa", - "map_assets_in_bounds": "{count} fotografija", - "map_cannot_get_user_location": "Nije moguće dobiti lokaciju korisnika", - "map_location_dialog_yes": "Da", - "map_location_picker_page_use_location": "Koristite ovu lokaciju", - "map_location_service_disabled_content": "Usluga lokacije mora biti omogućena da bi se prikazivala sredstva sa vaše trenutne lokacije. Da li želite da je sada omogućite?", - "map_location_service_disabled_title": "Usluga lokacije je onemogućena", - "map_marker_for_images": "Označivač na mapi za slike snimljene u {city}, {country}", - "map_marker_with_image": "Marker na mapi sa slikom", - "map_no_location_permission_content": "Potrebna je dozvola za lokaciju da bi se prikazali resursi sa vaše trenutne lokacije. Da li želite da je sada dozvolite?", - "map_no_location_permission_title": "Dozvola za lokaciju je odbijena", - "map_settings": "Podešavanja mape", - "map_settings_dark_mode": "Tamni režim", - "map_settings_date_range_option_day": "Poslednja 24 sata", - "map_settings_date_range_option_days": "Prethodnih {days} dana", - "map_settings_date_range_option_year": "Prošla godina", - "map_settings_date_range_option_years": "Proteklih {years} godina", - "map_settings_dialog_title": "Podešavanja Mape", - "map_settings_include_show_archived": "Uključi arhivirano", - "map_settings_include_show_partners": "Uključi partnere", - "map_settings_only_show_favorites": "Prikaži samo omiljene", - "map_settings_theme_settings": "Tema mape", - "map_zoom_to_see_photos": "Umanjite da biste videli fotografije", - "mark_all_as_read": "Označi sve kao pročitano", - "mark_as_read": "Označi kao pročitano", - "marked_all_as_read": "Sve je označeno kao pročitano", - "matches": "Podudaranja", - "media_type": "Vrsta medija", - "memories": "Sećanja", - "memories_all_caught_up": "Sve je uhvaćeno", - "memories_check_back_tomorrow": "Vratite se sutra za još uspomena", - "memories_setting_description": "Upravljajte onim što vidite u svojim sećanjima", - "memories_start_over": "Počni ispočetka", - "memories_swipe_to_close": "Prevucite nagore da biste zatvorili", - "memory": "Memorija", - "memory_lane_title": "Traka sećanja {title}", - "menu": "Meni", - "merge": "Spoji", - "merge_people": "Spoji osobe", - "merge_people_limit": "Možete spojiti najviše 5 lica od jednom", - "merge_people_prompt": "Da li želite da spojite ove osobe? Ovaj potez se ne može povratiti.", - "merge_people_successfully": "Spajanje osoba uspešno", - "merged_people_count": "Spojeno {count, plural, one {# osoba} other {# osobe}}", - "minimize": "Minimizirajte", - "minute": "Minut", - "missing": "Nedostaje", - "month": "Mesec", - "more": "Više", - "moved_to_archive": "Premešteno {count, plural, one {# datoteka} other {# datoteke}} u arhivu", - "moved_to_library": "Premešteno {count, plural, one {# datoteka} other {# datoteke}} u biblioteku", - "moved_to_trash": "Premešteno u smeće", - "multiselect_grid_edit_date_time_err_read_only": "Ne možete da izmenite datum elemenata samo za čitanje, preskačem", - "multiselect_grid_edit_gps_err_read_only": "Ne mogu da izmenim lokaciju elemenata samo za čitanje, preskačem", - "mute_memories": "Priguši sećanja", - "my_albums": "Moji albumi", - "name": "Ime", - "name_or_nickname": "Ime ili nadimak", - "networking_settings": "Umrežavanje", - "networking_subtitle": "Upravljajte podešavanjima krajnje tačke servera", - "never": "Nikada", - "new_album": "Novi Album", - "new_api_key": "Novi API ključ (key)", - "new_password": "Nova šifra", - "new_person": "Nova osoba", - "new_pin_code": "Novi PIN kod", - "new_user_created": "Novi korisnik je kreiran", - "new_version_available": "DOSTUPNA NOVA VERZIJA", - "newest_first": "Najnovije prvo", - "next": "Sledeće", - "next_memory": "Sledeće sećanje", - "no": "Ne", - "no_albums_message": "Napravite album da biste organizovali svoje fotografije i video zapise", - "no_albums_with_name_yet": "Izgleda da još uvek nemate nijedan album sa ovim imenom.", - "no_albums_yet": "Izgleda da još nemate nijedan album.", - "no_archived_assets_message": "Arhivirajte fotografije i video zapise da biste ih sakrili iz prikaza fotografija", - "no_assets_message": "KLIKNITE DA UPLOADIRATE SVOJU PRVU FOTOGRAFIJU", - "no_assets_to_show": "Nema elemenata za prikaz", - "no_duplicates_found": "Nije pronađen nijedan duplikat.", - "no_exif_info_available": "Nema dostupnih exif informacija", - "no_explore_results_message": "Uploadujte još fotografija da biste istražili svoju kolekciju.", - "no_favorites_message": "Postavite favorite da biste brzo našli vaše najbolje slike i video snimke", - "no_libraries_message": "Napravite spoljnu biblioteku da biste videli svoje fotografije i video zapise", - "no_name": "Nema imena", - "no_notifications": "Nema obaveštenja", - "no_people_found": "Nisu pronađeni odgovarajući ljudi", - "no_places": "Nema mesta", - "no_results": "Nema rezultata", - "no_results_description": "Pokušajte sa sinonimom ili opštijom ključnom reči", - "no_shared_albums_message": "Napravite album da biste delili fotografije i video zapise sa ljudima u vašoj mreži", - "not_in_any_album": "Nema ni u jednom albumu", - "not_selected": "Nije izabrano", - "note_apply_storage_label_to_previously_uploaded assets": "Napomena: Da biste primenili oznaku za skladištenje na prethodno uploadirane datoteke, pokrenite", - "notes": "Napomene", - "notification_permission_dialog_content": "Da bi ukljucili notifikacije, idite u Opcije i odaberite Dozvoli.", - "notification_permission_list_tile_content": "Dajte dozvolu za omogućavanje obaveštenja.", - "notification_permission_list_tile_enable_button": "Uključi Notifikacije", - "notification_permission_list_tile_title": "Dozvole za notifikacije", - "notification_toggle_setting_description": "Omogućite obaveštenja putem e-pošte", - "notifications": "Notifikacije", - "notifications_setting_description": "Upravljajte obaveštenjima", - "official_immich_resources": "Zvanični Immich resursi", - "offline": "Odsutan (Offline)", - "oldest_first": "Najstarije prvo", - "on_this_device": "Na ovom uređaju", - "onboarding": "Pristupanje (Onboarding)", - "onboarding_privacy_description": "Sledeće (opcione) funkcije se oslanjaju na spoljne usluge i mogu se onemogućiti u bilo kom trenutku u podešavanjima administracije.", - "onboarding_theme_description": "Izaberite temu boja za svoj nalog. Ovo možete kasnije da promenite u podešavanjima.", - "onboarding_welcome_user": "Dobrodošli, {user}", - "online": "Dostupan (Online)", - "only_favorites": "Samo favoriti", - "open": "Otvori", - "open_in_map_view": "Otvorite u prikaz karte", - "open_in_openstreetmap": "Otvorite u OpenStreetMap-u", - "open_the_search_filters": "Otvorite filtere za pretragu", - "options": "Opcije", - "or": "ili", - "organize_your_library": "Organizujte svoju biblioteku", - "original": "Original", - "other": "Ostalo", - "other_devices": "Ostali uređaji", - "other_variables": "Ostale varijable", - "owned": "Vlasništvo", - "owner": "Vlasnik", - "partner": "partner", - "partner_can_access": "{partner} može da pristupi", - "partner_can_access_assets": "Sve vaše fotografije i video snimci osim onih u arhiviranim i izbrisanim", - "partner_can_access_location": "Lokacija na kojoj su vaše fotografije snimljene", - "partner_list_user_photos": "Fotografije korisnika {user}", - "partner_list_view_all": "Prikaži sve", - "partner_page_empty_message": "Vaše fotografije još uvek nisu deljene ni sa jednim partnerom.", - "partner_page_no_more_users": "Nema više korisnika za dodavanje", - "partner_page_partner_add_failed": "Dodavanje partnera nije uspelo", - "partner_page_select_partner": "Izaberite partnera", - "partner_page_shared_to_title": "Deljeno sa", - "partner_page_stop_sharing_content": "{partner} više neće moći da pristupi vašim fotografijama.", - "partner_sharing": "Partnersko deljenje", - "partners": "Partneri", - "password": "Šifra", - "password_does_not_match": "Lozinka se ne podudara", - "password_required": "Lozinka potrebna", - "password_reset_success": "Resetovanje lozinke je uspešno", - "past_durations": { - "days": "{days, plural, one {Prošli dan} few {Prošlih # dana} other {Prošlih # dana}}", - "hours": "{hours, plural, one {Prošli sat} few {Prošla # sata} other {Prošlih # sati}}", - "years": "{years, plural, one {Prošle godine} few {Prošle # godine} other {Prošlih # godina}}" - }, - "path": "Putanja", - "pattern": "Šablon", - "pause": "Pauza", - "pause_memories": "Pauzirajte sećanja", - "paused": "Pauzirano", - "pending": "Na čekanju", - "people": "Osobe", - "people_edits_count": "Izmenjeno {count, plural, one {# osoba} other {# osobe}}", - "people_feature_description": "Pregledavanje fotografija i video snimaka grupisanih po osobama", - "people_sidebar_description": "Prikažite vezu do osoba na bočnoj traci", - "permanent_deletion_warning": "Upozorenje za trajno brisanje", - "permanent_deletion_warning_setting_description": "Prikaži upozorenje kada trajno brišete datoteke", - "permanently_delete": "Trajno izbrisati", - "permanently_delete_assets_count": "Trajno izbriši {count, plural, one {datoteku} other {datoteke}}", - "permanently_delete_assets_prompt": "Da li ste sigurni da želite da trajno izbrišete {count, plural, one {ovu datoteku?} other {ove # datoteke?}}Ovo će ih takođe ukloniti {count, plural, one {iz njihovog} other {iz njihovih}} albuma.", - "permanently_deleted_asset": "Trajno izbrisana datoteka", - "permanently_deleted_assets_count": "Trajno izbrisano {count, plural, one {# datoteka} other {# datoteke}}", - "permission_onboarding_back": "Nazad", - "permission_onboarding_continue_anyway": "Ipak nastavi", - "permission_onboarding_get_started": "Započnite", - "permission_onboarding_go_to_settings": "Idi na podešavanja", - "permission_onboarding_permission_denied": "Dozvola odbijena. Da biste koristili Immich, dodelite dozvole za fotografije i video zapise u Podešavanjima.", - "permission_onboarding_permission_granted": "Dozvola odobrena! Spremni ste.", - "permission_onboarding_permission_limited": "Dozvola ograničena. Da biste omogućili Immich-u da pravi rezervne kopije i upravlja celom vašom kolekcijom galerije, dodelite dozvole za fotografije i video zapise u Podešavanjima.", - "permission_onboarding_request": "Immich zahteva dozvolu da vidi vaše fotografije i video zapise.", - "person": "Osoba", - "person_birthdate": "Rođen(a) {date}", - "person_hidden": "{name}{hidden, select, true { (skriveno)} other {}}", - "photo_shared_all_users": "Izgleda da ste podelili svoje fotografije sa svim korisnicima ili da nemate nijednog korisnika sa kojim biste delili.", - "photos": "Fotografije", - "photos_and_videos": "Fotografije & Video zapisi", - "photos_count": "{count, plural, one {{count, number} fotografija} few {{count, number} fotografije} other {{count, number} fotografija}}", - "photos_from_previous_years": "Fotografije iz prethodnih godina", - "pick_a_location": "Odaberi lokaciju", - "pin_code_changed_successfully": "PIN kod je uspešno promenjen", - "pin_code_reset_successfully": "PIN kod je uspešno resetovan", - "pin_code_setup_successfully": "Uspešno podešavanje PIN koda", - "place": "Mesto", - "places": "Mesta", - "places_count": "{count, plural, one {{count, number} Mesto} other {{count, number} Mesta}}", - "play": "Pokreni", - "play_memories": "Pokreni sećanja", - "play_motion_photo": "Pokreni pokretnu fotografiju", - "play_or_pause_video": "Pokreni ili pauziraj video zapis", - "port": "port", - "preferences_settings_subtitle": "Upravljajte podešavanjima aplikacije", - "preferences_settings_title": "Podešavanja", - "preset": "Unapred podešeno", - "preview": "Pregled", - "previous": "Prošlo", - "previous_memory": "Prethodno sećanje", - "previous_or_next_photo": "Prethodna ili sledeća fotografija", - "primary": "Primarna (Primary)", - "privacy": "Privatnost", - "profile": "Profil", - "profile_drawer_app_logs": "Evidencija", - "profile_drawer_client_server_up_to_date": "Klijent i server su najnovije verzije", - "profile_image_of_user": "Slika profila od korisnika {user}", - "profile_picture_set": "Profilna slika postavljena.", - "public_album": "Javni album", - "public_share": "Javno deljenje", - "purchase_account_info": "Podržavam softver", - "purchase_activated_subtitle": "Hvala vam što podržavate Immich i softver otvorenog koda", - "purchase_activated_time": "Aktivirano {date}", - "purchase_activated_title": "Vaš ključ je uspešno aktiviran", - "purchase_button_activate": "Aktiviraj", - "purchase_button_buy": "Kupi", - "purchase_button_buy_immich": "Kupite Immich", - "purchase_button_never_show_again": "Nikada više ne prikazuj", - "purchase_button_reminder": "Podseti me za 30 dana", - "purchase_button_remove_key": "Uklonite ključ", - "purchase_button_select": "Izaberite", - "purchase_failed_activation": "Aktivacija nije uspela! Proverite svoju e-poštu da biste pronašli tačan ključ proizvoda!", - "purchase_individual_description_1": "Za pojedinca", - "purchase_individual_description_2": "Status podrške", - "purchase_individual_title": "Individualna licenca", - "purchase_input_suggestion": "Imate ključ proizvoda? Unesite ključ ispod", - "purchase_license_subtitle": "Kupite Immich da biste podržali kontinuirani razvoj usluge", - "purchase_lifetime_description": "Doživotna licenca", - "purchase_option_title": "OPCIJE KUPOVINE", - "purchase_panel_info_1": "Izgradnja Immich-a zahteva mnogo vremena i truda, a imamo inženjere koji rade na tome sa punim radnim vremenom kako bismo je učinili što je moguće boljom. Naša misija je da softver otvorenog koda i etičke poslovne prakse postanu održiv izvor prihoda za programere i da stvorimo ekosistem koji poštuje privatnost sa stvarnim alternativama eksploatativnim uslugama u oblaku.", - "purchase_panel_info_2": "Pošto smo se obavezali da nećemo dodavati platne zidove, ova kupovina vam neće dati nikakve dodatne funkcije u Immich-u. Oslanjamo se na korisnike poput vas da podrže Immich-ov stalni razvoj.", - "purchase_panel_title": "Podržite projekat", - "purchase_per_server": "Po serveru", - "purchase_per_user": "Po korisniku", - "purchase_remove_product_key": "Uklonite ključ proizvoda", - "purchase_remove_product_key_prompt": "Da li ste sigurni da želite da uklonite šifru proizvoda?", - "purchase_remove_server_product_key": "Uklonite šifru proizvoda sa servera", - "purchase_remove_server_product_key_prompt": "Da li ste sigurni da želite da uklonite šifru proizvoda sa servera?", - "purchase_server_description_1": "Za ceo server", - "purchase_server_description_2": "Status podrške", - "purchase_settings_server_activated": "Ključem proizvoda servera upravlja administrator", - "rating": "Ocena zvezdica", - "rating_clear": "Obriši ocenu", - "rating_count": "{count, plural, one {# zvezda} other {# zvezde}}", - "rating_description": "Prikažite EXIF ocenu u info panelu", - "reaction_options": "Opcije reakcije", - "read_changelog": "Pročitajte dnevnik promena", - "reassign": "Ponovo dodaj", - "reassigned_assets_to_existing_person": "Ponovo dodeljeno {count, plural, one {# datoteka} other {# datoteke}} postojećoj {name, select, null {osobi} other {{name}}}", - "reassigned_assets_to_new_person": "Ponovo dodeljeno {count, plural, one {# datoteka} other {# datoteke}} novoj osobi", - "reassing_hint": "Dodelite izabrana sredstva postojećoj osobi", - "recent": "Skorašnji", - "recent-albums": "Nedavni albumi", - "recent_searches": "Skorašnje pretrage", - "recently_added": "Nedavno dodato", - "recently_added_page_title": "Nedavno Dodato", - "recently_taken": "Nedavno snimljeno", - "recently_taken_page_title": "Nedavno Snimljeno", - "refresh": "Osveži", - "refresh_encoded_videos": "Osvežite kodirane (encoded) video zapise", - "refresh_faces": "Osveži lica", - "refresh_metadata": "Osvežite metapodatke", - "refresh_thumbnails": "Osvežite sličice", - "refreshed": "Osveženo", - "refreshes_every_file": "Ponovo čita sve postojeće i nove datoteke", - "refreshing_encoded_video": "Osvežavanje kodiranog (encoded) videa", - "refreshing_faces": "Osvežavanje lica", - "refreshing_metadata": "Osvežavanje meta-podataka", - "regenerating_thumbnails": "Obnavljanje sličica", - "remove": "Ukloni", - "remove_assets_album_confirmation": "Da li ste sigurni da želite da uklonite {count, plural, one {# datoteku} other {# datoteke}} iz albuma?", - "remove_assets_shared_link_confirmation": "Da li ste sigurni da želite da uklonite {count, plural, one {# datoteku} other {# datoteke}} sa ove deljene veze?", - "remove_assets_title": "Ukloniti datoteke?", - "remove_custom_date_range": "Uklonite prilagođeni period", - "remove_deleted_assets": "Uklonite vanmrežne (offline) datoteke", - "remove_from_album": "Obriši iz albuma", - "remove_from_favorites": "Ukloni iz favorita", - "remove_from_shared_link": "Uklonite sa deljene veze", - "remove_memory": "Uklonite memoriju", - "remove_photo_from_memory": "Uklonite fotografiju iz ove memorije", - "remove_url": "Ukloni URL", - "remove_user": "Ukloni korisnika", - "removed_api_key": "Uklonjen API ključ (key): {name}", - "removed_from_archive": "Uklonjeno iz arhive", - "removed_from_favorites": "Uklonjeno iz omiljenih (favorites)", - "removed_from_favorites_count": "{count, plural, other {Uklonjeno #}} iz omiljenih", - "removed_memory": "Uklonjena memorija", - "removed_photo_from_memory": "Slika je uklonjena iz memorije", - "removed_tagged_assets": "Uklonjena oznaka iz {count, plural, one {# datoteke} other {# datoteka}}", - "rename": "Preimenuj", - "repair": "Popravi", - "repair_no_results_message": "Ovde će se pojaviti datoteke koje nisu praćene i nedostaju", - "replace_with_upload": "Zamenite sa upload-om", - "repository": "Repozitorijum (Repository)", - "require_password": "Potrebna lozinka", - "require_user_to_change_password_on_first_login": "Zahtevati od korisnika da promeni lozinku pri prvom prijavljivanju", - "rescan": "Ponovo skeniraj", - "reset": "Resetovati", - "reset_password": "Resetovati lozinku", - "reset_people_visibility": "Resetujte vidljivost osoba", - "reset_pin_code": "Resetuj PIN kod", - "reset_to_default": "Resetujte na podrazumevane vrednosti", - "resolve_duplicates": "Reši duplikate", - "resolved_all_duplicates": "Svi duplikati su razrešeni", - "restore": "Povrati", - "restore_all": "Povrati sve", - "restore_user": "Povrati korisnika", - "restored_asset": "Povraćeno sredstvo", - "resume": "Ponovo pokreni", - "retry_upload": "Pokušajte ponovo da uploadujete", - "review_duplicates": "Pregledajte duplikate", - "role": "Uloga", - "role_editor": "Urednik", - "role_viewer": "Gledalac", - "save": "Sačuvaj", - "save_to_gallery": "Sačuvaj u galeriju", - "saved_api_key": "Sačuvan API ključ (key)", - "saved_profile": "Sačuvan profil", - "saved_settings": "Sačuvana podešavanja", - "say_something": "Reci nešto", - "scaffold_body_error_occurred": "Došlo je do greške", - "scan_all_libraries": "Skeniraj sve biblioteke", - "scan_library": "Skeniraj", - "scan_settings": "Podešavanja skeniranja", - "scanning_for_album": "Skeniranje albuma...", - "search": "Pretraga", - "search_albums": "Pretraži albume", - "search_by_context": "Pretražujte po kontekstu", - "search_by_description": "Traži po opisu", - "search_by_description_example": "Dan pešačenja u Sapi", - "search_by_filename": "Pretražite po imenu datoteke ili ekstenziji", - "search_by_filename_example": "npr. IMG_1234.JPG ili PNG", - "search_camera_make": "Pretraga proizvođača kamere...", - "search_camera_model": "Pretraži model kamere...", - "search_city": "Pretraži grad...", - "search_country": "Traži zemlju...", - "search_filter_apply": "Primeni filter", - "search_filter_camera_title": "Izaberite tip kamere", - "search_filter_date_title": "Izaberite period", - "search_filter_display_options": "Opcije prikaza", - "search_filter_filename": "Pretraga po imenu datoteke", - "search_filter_location": "Lokacija", - "search_filter_location_title": "Izaberite lokaciju", - "search_filter_media_type_title": "Izaberite tip medija", - "search_filter_people_title": "Izaberite ljude", - "search_for": "Traži", - "search_for_existing_person": "Potražite postojeću osobu", - "search_no_more_result": "Nema više rezultata", - "search_no_people": "Bez osoba", - "search_no_people_named": "Nema osoba sa imenom „{name}“", - "search_no_result": "Nisu pronađeni rezultati, pokušajte sa drugim terminom za pretragu ili kombinacijom", - "search_options": "Opcije pretrage", - "search_page_categories": "Kategorije", - "search_page_motion_photos": "Fotografije u pokretu", - "search_page_no_objects": "Bez informacija", - "search_page_no_places": "Nema informacija o mestu", - "search_page_screenshots": "Snimci ekrana", - "search_page_search_photos_videos": "Pretražite svoje fotografije i video zapise", - "search_page_selfies": "Selfiji", - "search_page_things": "Stvari", - "search_page_view_all_button": "Prikaži sve", - "search_page_your_activity": "Vaša aktivnost", - "search_page_your_map": "Vaša mapa", - "search_people": "Pretraži osobe", - "search_places": "Pretraži mesta", - "search_rating": "Pretraga po oceni...", - "search_result_page_new_search_hint": "Nova pretraga", - "search_settings": "Pretraga podešavanja", - "search_state": "Traži region...", - "search_suggestion_list_smart_search_hint_1": "Pametna pretraga je podrazumevano omogućena, za pretragu metapodataka koristite sintaksu ", - "search_suggestion_list_smart_search_hint_2": "m:vaš-pojam-za-pretragu", - "search_tags": "Pretraži oznake (tags)...", - "search_timezone": "Pretraži vremensku zonu...", - "search_type": "Vrsta pretrage", - "search_your_photos": "Pretraži svoje fotografije", - "searching_locales": "Pretraživanje prevoda...", - "second": "Sekunda", - "see_all_people": "Vidi sve osobe", - "select": "Izaberite", - "select_album_cover": "Izaberite omot albuma", - "select_all": "Izaberi sve", - "select_all_duplicates": "Izaberite sve duplikate", - "select_avatar_color": "Izaberite boju avatara", - "select_face": "Izaberite lice", - "select_featured_photo": "Izaberite istaknutu fotografiju", - "select_from_computer": "Izaberite sa računara", - "select_keep_all": "Izaberite da zadržite sve", - "select_library_owner": "Izaberite vlasnika biblioteke", - "select_new_face": "Izaberite novo lice", - "select_person_to_tag": "Izaberite osobu za označavanje", - "select_photos": "Odaberi fotografije", - "select_trash_all": "Izaberite da sve bacite na otpad", - "select_user_for_sharing_page_err_album": "Neuspešno kreiranje albuma", - "selected": "Odabrano", - "selected_count": "{count, plural, other {# izabrano}}", - "send_message": "Pošalji poruku", - "send_welcome_email": "Pošaljite e-poštu dobrodošlice", - "server_endpoint": "Krajnja tačka servera", - "server_info_box_app_version": "Verzija Aplikacije", - "server_offline": "Server van mreže (offline)", - "server_online": "Server na mreži (online)", - "server_stats": "Statistika servera", - "server_version": "Verzija servera", - "set": "Postavi", - "set_as_album_cover": "Postavi kao omot albuma", - "set_as_featured_photo": "Postavi kao istaknutu fotografiju", - "set_as_profile_picture": "Postavi kao profilnu sliku", - "set_date_of_birth": "Podesite datum rođenja", - "set_profile_picture": "Postavi profilnu sliku", - "set_slideshow_to_fullscreen": "Postavite projekciju slajdova na ceo ekran", - "setting_image_viewer_help": "Pregledač detalja prvo učitava malu sličicu, zatim pregled srednje veličine (ako je omogućen), i na kraju original (ako je omogućen).", - "setting_image_viewer_original_subtitle": "Aktiviraj učitavanje slika u punoj rezoluciji (Velika!). Deaktivacijom ove stavke možeš da smanjiš potrošnju interneta i zauzetog prostora na uređaju.", - "setting_image_viewer_original_title": "Učitaj originalnu sliku", - "setting_image_viewer_preview_subtitle": "Aktiviraj učitavanje slika u srednjoj rezoluciji. Deaktiviraj da se direktno učitava original, ili da se samo koristi minijatura.", - "setting_image_viewer_preview_title": "Pregledaj sliku", - "setting_image_viewer_title": "Slike", - "setting_languages_apply": "Primeni", - "setting_languages_subtitle": "Promenite jezik aplikacije", - "setting_notifications_notify_failures_grace_period": "Obavesti o greškama u pravljenju rezervnih kopija u pozadini: {duration}", - "setting_notifications_notify_hours": "{count} sati", - "setting_notifications_notify_immediately": "odmah", - "setting_notifications_notify_minutes": "{count} minuta", - "setting_notifications_notify_never": "nikada", - "setting_notifications_notify_seconds": "{count} sekundi", - "setting_notifications_single_progress_subtitle": "Detaljne informacije o otpremanju, po zapisu", - "setting_notifications_single_progress_title": "Prikaži detalje pozadinskog pravljenja rezervnih kopija", - "setting_notifications_subtitle": "Izmeni notifikacije", - "setting_notifications_total_progress_subtitle": "Ukupno otpremljenih stavki (završeno/ukupno stavki)", - "setting_notifications_total_progress_title": "Prikaži ukupan napredak pravljenja rezervnih kopija u pozadini", - "setting_video_viewer_looping_title": "Petljanje (Looping)", - "setting_video_viewer_original_video_subtitle": "Prilikom strimovanja videa sa servera, reprodukujte original čak i kada je dostupno transkodiranje. Može dovesti do baferovanja. Video snimci dostupni lokalno se reprodukuju u originalnom kvalitetu bez obzira na ovo podešavanje.", - "setting_video_viewer_original_video_title": "Prisilno originalni video", - "settings": "Podešavanja", - "settings_require_restart": "Restartujte Immich da primenite ovu promenu", - "settings_saved": "Podešavanja sačuvana", - "setup_pin_code": "Podesite PIN kod", - "share": "Podeli", - "share_add_photos": "Dodaj fotografije", - "share_assets_selected": "Izabrano je {count}", - "share_dialog_preparing": "Pripremanje...", - "shared": "Deljeno", - "shared_album_activities_input_disable": "Komentar je onemogućen", - "shared_album_activity_remove_content": "Da li želite da obrišete ovu aktivnost?", - "shared_album_activity_remove_title": "Obriši aktivnost", - "shared_album_section_people_action_error": "Greška pri napuštanju/uklanjanju iz albuma", - "shared_album_section_people_action_leave": "Ukloni korisnika iz albuma", - "shared_album_section_people_action_remove_user": "Ukloni korisnika iz albuma", - "shared_by": "Podelio", - "shared_by_user": "Deli {user}", - "shared_by_you": "Vi delite", - "shared_from_partner": "Slike od {partner}", - "shared_intent_upload_button_progress_text": "Otpremljeno je {current} / {total}", - "shared_link_app_bar_title": "Deljeni linkovi", - "shared_link_clipboard_copied_massage": "Kopirano u međuspremnik (clipboard)", - "shared_link_clipboard_text": "Link: {link}\nLozinka: {password}", - "shared_link_create_error": "Greška pri kreiranju deljenog linka", - "shared_link_edit_description_hint": "Unesite opis deljenja", - "shared_link_edit_expire_after_option_days": "{count} dana", - "shared_link_edit_expire_after_option_hour": "1 sat", - "shared_link_edit_expire_after_option_hours": "{count} sati", - "shared_link_edit_expire_after_option_minutes": "{count} minuta", - "shared_link_edit_expire_after_option_months": "{count} meseci", - "shared_link_edit_expire_after_option_year": "{count} godina", - "shared_link_edit_password_hint": "Unesite lozinku za deljenje", - "shared_link_error_server_url_fetch": "Ne mogu da preuzmem URL servera", - "shared_link_expires_day": "Ističe za {count} dan(a)", - "shared_link_expires_days": "Ističe za {count} dana", - "shared_link_expires_hour": "Ističe za {count} sat", - "shared_link_expires_hours": "Ističe za {count} sati(a)", - "shared_link_expires_minute": "Ističe za {count} minut", - "shared_link_expires_minutes": "Ističe za {count} minuta", - "shared_link_expires_never": "Ističe ∞", - "shared_link_expires_second": "Ističe za {count} sekundu", - "shared_link_expires_seconds": "Ističe za {count} sekundi", - "shared_link_individual_shared": "Pojedinačno deljeno", - "shared_link_manage_links": "Upravljajte deljenim linkovima", - "shared_link_options": "Opcije deljene veze", - "shared_links": "Deljene veze", - "shared_links_description": "Delite fotografije i video zapise pomoću linka", - "shared_photos_and_videos_count": "{assetCount, plural, other {# deljene fotografije i video zapise.}}", - "shared_with_me": "Deljeno sa mnom", - "shared_with_partner": "Deli se sa {partner}", - "sharing": "Deljenje", - "sharing_enter_password": "Unesite lozinku da biste videli ovu stranicu.", - "sharing_page_album": "Deljeni albumi", - "sharing_page_description": "Napravi deljene albume da deliš fotografije i video zapise sa ljudima na tvojoj mreži.", - "sharing_page_empty_list": "PRAZNA LISTA", - "sharing_sidebar_description": "Prikažite vezu do Deljenja na bočnoj traci", - "sharing_silver_appbar_create_shared_album": "Napravi deljeni album", - "sharing_silver_appbar_share_partner": "Podeli sa partnerom", - "shift_to_permanent_delete": "pritisnite ⇧ da trajno izbrišete datoteku", - "show_album_options": "Prikaži opcije albuma", - "show_albums": "Prikaži albume", - "show_all_people": "Pokaži sve osobe", - "show_and_hide_people": "Otkrij i sakrij osobe", - "show_file_location": "Prikaži lokaciju datoteke", - "show_gallery": "Prikaži galeriju", - "show_hidden_people": "Prikaži skrivene osobe", - "show_in_timeline": "Prikaži na vremenskoj liniji", - "show_in_timeline_setting_description": "Prikažite fotografije i video zapise ovog korisnika na vremenskoj liniji", - "show_keyboard_shortcuts": "Prikaži prečice na tastaturi", - "show_metadata": "Prikaži metapodatke", - "show_or_hide_info": "Otkrij ili sakrij informaciju", - "show_password": "Prikaži lozinku", - "show_person_options": "Prikaži opcije osobe", - "show_progress_bar": "Prikaži traku napretka", - "show_search_options": "Prikaži opcije pretrage", - "show_shared_links": "Prikaži deljene veze", - "show_slideshow_transition": "Prikaži prelaz projekcije slajdova", - "show_supporter_badge": "Značka podrške", - "show_supporter_badge_description": "Pokažite značku podrške", - "shuffle": "Mešanje", - "sidebar": "Bočna traka", - "sidebar_display_description": "Prikažite vezu do prikaza na bočnoj traci", - "sign_out": "Odjava", - "sign_up": "Prijavi se", - "size": "Veličina", - "skip_to_content": "Pređi na sadržaj", - "skip_to_folders": "Preskoči do mapa (folders)", - "skip_to_tags": "Preskoči do oznaka (tags)", - "slideshow": "Slajdovi", - "slideshow_settings": "Podešavanja slajdova", - "sort_albums_by": "Sortiraj albume po...", - "sort_created": "Datum kreiranja", - "sort_items": "Broj stavki", - "sort_modified": "Datum izmene", - "sort_oldest": "Najstarija fotografija", - "sort_people_by_similarity": "Sortirajte osobe po sličnosti", - "sort_recent": "Najnovija fotografija", - "sort_title": "Naslov", - "source": "Izvor", - "stack": "Slaganje", - "stack_duplicates": "Duplikati gomile", - "stack_select_one_photo": "Izaberite jednu glavnu fotografiju za gomilu", - "stack_selected_photos": "Složite izabrane fotografije", - "stacked_assets_count": "Naslagano {count, plural, one {# datoteka} other {# datoteke}}", - "stacktrace": "Veza do gomile", - "start": "Početak", - "start_date": "Datum početka", - "state": "Stanje", - "stop_motion_photo": "Zaustavi pokretnu fotografiju", - "stop_photo_sharing": "Želite da zaustavite deljenje fotografija?", - "stop_photo_sharing_description": "{partner} više neće moći da pristupi vašim fotografijama.", - "stop_sharing_photos_with_user": "Prestanite da delite svoje fotografije sa ovim korisnikom", - "storage": "Skladište (Storage space)", - "storage_label": "Oznaka za skladištenje", - "storage_quota": "Kvota skladištenja", - "storage_usage": "Koristi se {used} od {available}", - "submit": "Dostavi", - "suggestions": "Sugestije", - "sunrise_on_the_beach": "Izlazak sunca na plaži", - "support": "Podrška", - "support_and_feedback": "Podrška i povratne informacije", - "support_third_party_description": "Vaša immich instalacija je spakovana od strane treće strane. Problemi sa kojima se suočavate mogu biti uzrokovani tim paketom, pa vas molimo da im prvo postavite probleme koristeći donje veze.", - "swap_merge_direction": "Zamenite pravac spajanja", - "sync": "Sinhronizacija", - "sync_albums": "Sinhronizuj albume", - "sync_albums_manual_subtitle": "Sinhronizujte sve otpremljene video zapise i fotografije sa izabranim rezervnim albumima", - "sync_upload_album_setting_subtitle": "Kreirajte i otpremite svoje fotografije i video zapise u odabrane albume na Immich-u", - "tag": "Oznaka (tag)", - "tag_assets": "Označite (tag) sredstva", - "tag_created": "Napravljena oznaka (tag): {tag}", - "tag_feature_description": "Pregledavanje fotografija i video snimaka grupisanih po logičnim temama oznaka", - "tag_not_found_question": "Ne možete da pronađete oznaku (tag)? Napravite novu oznaku", - "tag_people": "Označite ljude", - "tag_updated": "Ažurirana oznaka (tag): {tag}", - "tagged_assets": "Označeno (tagged) {count, plural, one {# datoteka} other {# datoteke}}", - "tags": "Oznake (tags)", - "template": "Šablon (Template)", - "theme": "Teme", - "theme_selection": "Izbor teme", - "theme_selection_description": "Automatski postavite temu na svetlu ili tamnu na osnovu sistemskih preferencija vašeg pretraživača", - "theme_setting_asset_list_storage_indicator_title": "Prikaži indikator prostora na zapisima", - "theme_setting_asset_list_tiles_per_row_title": "Broj zapisa po redu {count}", - "theme_setting_colorful_interface_subtitle": "Nanesite osnovnu boju na pozadinske površine.", - "theme_setting_colorful_interface_title": "Šareni interfejs", - "theme_setting_image_viewer_quality_subtitle": "Prilagodite kvalitet prikaza za detaljno pregledavanje slike", - "theme_setting_image_viewer_quality_title": "Kvalitet pregledača slika", - "theme_setting_primary_color_subtitle": "Izaberite boju za glavne radnje i akcente.", - "theme_setting_primary_color_title": "Primarna boja", - "theme_setting_system_primary_color_title": "Koristi sistemsku boju", - "theme_setting_system_theme_switch": "Automatski (Prati opcije sistema)", - "theme_setting_theme_subtitle": "Odaberi temu sistema", - "theme_setting_three_stage_loading_subtitle": "Trostepeno učitavanje možda ubrza učitavanje, po cenu potrošnje podataka", - "theme_setting_three_stage_loading_title": "Aktiviraj trostepeno učitavanje", - "they_will_be_merged_together": "Oni će biti spojeni zajedno", - "third_party_resources": "Resursi trećih strana", - "time_based_memories": "Sećanja zasnovana na vremenu", - "timeline": "Vremenska linija", - "timezone": "Vremenska zona", - "to_archive": "Arhiviraj", - "to_change_password": "Promeni lozinku", - "to_favorite": "Postavi kao favorit", - "to_login": "Prijava", - "to_parent": "Vrati se nazad", - "to_trash": "Smeće", - "toggle_settings": "Namesti podešavanja", - "total": "Ukupno", - "total_usage": "Ukupna upotreba", - "trash": "Otpad", - "trash_all": "Baci sve u otpad", - "trash_count": "Otpad {count, number}", - "trash_delete_asset": "Otpad/Izbriši datoteku", - "trash_emptied": "Ispraznio smeće", - "trash_no_results_message": "Slike i video zapisi u otpadu će se pojaviti ovde.", - "trash_page_delete_all": "Obriši sve", - "trash_page_empty_trash_dialog_content": "Da li želite da ispraznite svoja premeštena sredstva? Ovi predmeti će biti trajno uklonjeni iz Immich-a", - "trash_page_info": "Stavke izbačene iz otpada biće trajno obrisane nakon {days} dana", - "trash_page_no_assets": "Nema elemenata u otpadu", - "trash_page_restore_all": "Vrati sve", - "trash_page_select_assets_btn": "Izaberite sredstva", - "trash_page_title": "Otpad ({count})", - "trashed_items_will_be_permanently_deleted_after": "Datoteke u otpadu će biti trajno izbrisane nakon {days, plural, one {# dan} few {# dana} other {# dana}}.", - "type": "Vrsta", - "unable_to_change_pin_code": "Nije moguće promeniti PIN kod", - "unable_to_setup_pin_code": "Nije moguće podesiti PIN kod", - "unarchive": "Vrati iz arhive", - "unarchived_count": "{count, plural, other {Nearhivirano#}}", - "unfavorite": "Izbaci iz omiljenih (unfavorite)", - "unhide_person": "Otkrij osobu", - "unknown": "Nepoznat", - "unknown_country": "Nepoznata zemlja", - "unknown_year": "Nepoznata Godina", - "unlimited": "Neograničeno", - "unlink_motion_video": "Odveži video od slike", - "unlink_oauth": "Prekini vezu sa Oauth-om", - "unlinked_oauth_account": "Opozvana veza OAuth naloga", - "unmute_memories": "Uključi uspomene", - "unnamed_album": "Neimenovani album", - "unnamed_album_delete_confirmation": "Da li ste sigurni da želite da izbrišete ovaj album?", - "unnamed_share": "Neimenovano delenje", - "unsaved_change": "Nesačuvana promena", - "unselect_all": "Poništi sve", - "unselect_all_duplicates": "Poništi izbor svih duplikata", - "unstack": "Razgomilaj (Un-stack)", - "unstacked_assets_count": "Nesloženo {count, plural, one {# datoteka} other {# datoteke}}", - "up_next": "Sledeće", - "updated_at": "Ažurirano", - "updated_password": "Ažurirana lozinka", - "upload": "Uploaduj", - "upload_concurrency": "Paralelno uploadovanje", - "upload_dialog_info": "Da li želite da napravite rezervnu kopiju izabranih elemenata na serveru?", - "upload_dialog_title": "Otpremi element", - "upload_errors": "Otpremanje je završeno sa {count, plural, one {# greškom} other {# grešaka}}, osvežite stranicu da biste videli nove datoteke za otpremanje (upload).", - "upload_progress": "Preostalo {remaining, number} – Obrađeno {processed, number}/{total, number}", - "upload_skipped_duplicates": "Preskočeno {count, plural, one {# dupla datoteka} other {# duplih datoteka}}", - "upload_status_duplicates": "Duplikati", - "upload_status_errors": "Greške", - "upload_status_uploaded": "Otpremljeno (Uploaded)", - "upload_success": "Otpremanje je uspešno, osvežite stranicu da biste videli nova sredstva za otpremanje (upload).", - "upload_to_immich": "Otpremi u Immich ({count})", - "uploading": "Otpremanje", - "usage": "Upotreba", - "use_current_connection": "koristi trenutnu vezu", - "use_custom_date_range": "Umesto toga koristite prilagođeni period", - "user": "Korisnik", - "user_id": "ID korisnika", - "user_liked": "{user} je lajkovao {type, select, photo {ovu fotografiju} video {ovaj video zapis} asset {ovu datoteku} other {ovo}}", - "user_pin_code_settings": "PIN kod", - "user_pin_code_settings_description": "Upravljajte svojim PIN kodom", - "user_purchase_settings": "Kupovina", - "user_purchase_settings_description": "Upravljajte kupovinom", - "user_role_set": "Postavi {user} kao {role}", - "user_usage_detail": "Detalji korišćenja korisnika", - "user_usage_stats": "Statistika korišćenja naloga", - "user_usage_stats_description": "Pogledajte statistiku korišćenja naloga", - "username": "Korisničko ime", - "users": "Korisnici", - "utilities": "Alati", - "validate": "Proveri", - "validate_endpoint_error": "Molimo vas da unesete važeći URL", - "variables": "Promenljive (variables)", - "version": "Verzija", - "version_announcement_closing": "Tvoj prijatelj, Aleks", - "version_announcement_message": "Zdravo! Dostupna je nova verzija Immich-a. Molimo vas da odvojite malo vremena da pročitate beleške o izdanju kako biste bili sigurni da je vaše podešavanje ažurirano i sprečili eventualne pogrešne konfiguracije, posebno ako koristite WatchTower ili bilo koji mehanizam koji automatski ažurira vašu Immich instancu.", - "version_history": "Istorija verzija", - "version_history_item": "Instalirano {version} {date}", - "video": "Video zapis", - "video_hover_setting": "Pusti sličicu videa kada lebdi", - "video_hover_setting_description": "Pusti sličicu videa kada miš pređe preko stavke. Čak i kada je onemogućena, reprodukcija se može pokrenuti prelaskom miša preko ikone za reprodukciju.", - "videos": "Video zapisi", - "videos_count": "{count, plural, one {# video zapis} few {# video zapisa} other {# video zapisa}}", - "view": "Gledaj (view)", - "view_album": "Pogledaj album", - "view_all": "Prikaži Sve", - "view_all_users": "Prikaži sve korisnike", - "view_in_timeline": "Prikaži u vremenskoj liniji", - "view_link": "Pogledaj vezu", - "view_links": "Prikaži veze", - "view_name": "Pogledati", - "view_next_asset": "Pogledajte sledeću datoteku", - "view_previous_asset": "Pogledaj prethodnu datoteku", - "view_qr_code": "Pogledajte QR kod", - "view_stack": "Prikaži gomilu", - "viewer_remove_from_stack": "Ukloni iz steka", - "viewer_stack_use_as_main_asset": "Koristi kao glavni resurs", - "visibility_changed": "Vidljivost je promenjena za {count, plural, one {# osobu} other {# osobe}}", - "waiting": "Čekam", - "warning": "Upozorenje", - "week": "Nedelja", - "welcome": "Dobrodošli", - "welcome_to_immich": "Dobrodošli u immich", - "wifi_name": "Naziv Wi-Fi mreže", - "year": "Godina", - "years_ago": "pre {years, plural, one {# godine} other {# godina}}", - "yes": "Da", - "you_dont_have_any_shared_links": "Nemate nijedno deljenje veze", - "your_wifi_name": "Ime vaše Wi-Fi mreže", - "zoom_image": "Zumiraj sliku" -} +{} diff --git a/i18n/sv.json b/i18n/sv.json index 280af17550..0967ef424b 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -1,2401 +1 @@ -{ - "about": "Om", - "account": "Konto", - "account_settings": "Kontoinställningar", - "acknowledge": "Bekräfta", - "action": "Åtgärd", - "action_common_update": "Uppdatera", - "action_description": "En uppsättning åtgärder som ska utföras på de filtrerade tillgångarna", - "actions": "Händelser", - "active": "Aktiv", - "active_count": "Aktiva: {count}", - "activity": "Aktivitet", - "activity_changed": "Aktiviteten är {enabled, select, true {aktiverad} other {inaktiverad}}", - "add": "Lägg till", - "add_a_description": "Lägg till en beskrivning", - "add_a_location": "Lägg till en plats", - "add_a_name": "Lägg till ett namn", - "add_a_title": "Lägg till en titel", - "add_action": "Lägg till åtgärd", - "add_action_description": "Klicka för att lägga till en åtgärd att utföra", - "add_assets": "Lägg till tillgångar", - "add_birthday": "Lägg till födelsedag", - "add_endpoint": "Lägg till ändpunkt", - "add_exclusion_pattern": "Lägg till uteslutningsmönster", - "add_filter": "Lägg till filter", - "add_filter_description": "Klicka för att lägga till ett filtervillkor", - "add_location": "Lägg till plats", - "add_more_users": "Lägg till fler användare", - "add_partner": "Lägg till partner", - "add_path": "Lägg till sökväg", - "add_photos": "Lägg till foton", - "add_tag": "Lägg till tagg", - "add_to": "Lägg till i…", - "add_to_album": "Lägg till i album", - "add_to_album_bottom_sheet_added": "Tillagd till {album}", - "add_to_album_bottom_sheet_already_exists": "Redan i {album}", - "add_to_album_bottom_sheet_some_local_assets": "Vissa lokala tillgångar kunde inte läggas till i albumet", - "add_to_album_toggle": "Växla val för {album}", - "add_to_albums": "Lägg till i album", - "add_to_albums_count": "Lägg till i album ({count})", - "add_to_bottom_bar": "Lägg till i", - "add_to_shared_album": "Lägg till i delat album", - "add_upload_to_stack": "Lägg till uppladdning till stack", - "add_url": "Lägg till URL", - "add_workflow_step": "Lägg till arbetsflödessteg", - "added_to_archive": "Tillagd i arkiv", - "added_to_favorites": "Tillagd till favoriter", - "added_to_favorites_count": "{count, number} tillagda till favoriter", - "admin": { - "add_exclusion_pattern_description": "Lägg till exkluderande mönster. Matchning med jokertecken *, ** samt ? stödjs. För att ignorera alla filer i samtliga mappar som heter \"Raw\", använd \"**/Raw/**\". För att ignorera alla filer som slutar med \".tif\", använd \"**/*.tif\". För att ignorera en absolut sökväg, använd \"/sökväg/att/ignorera/**\".", - "admin_user": "Adminanvändare", - "asset_offline_description": "Denna externa bibliotekstillgång finns inte längre på disken och har flyttats till papperskorgen. Om filen flyttades inom biblioteket, kontrollera din tidslinje för den nya motsvarande tillgången. För att återställa denna tillgång, se till att filsökvägen nedan kan nås av Immich och skanna biblioteket.", - "authentication_settings": "Autentiseringsinställningar", - "authentication_settings_description": "Hantera lösenord, OAuth, och andra autentiseringsinställningar", - "authentication_settings_disable_all": "Är du säker på att du vill inaktivera alla inloggningsmetoder? Inloggning kommer att helt inaktiveras.", - "authentication_settings_reenable": "För att återaktivera, använd Server Command.", - "background_task_job": "Bakgrundsaktiviteter", - "backup_database": "Skapa Databasdump", - "backup_database_enable_description": "Aktivera dumpning av databas", - "backup_keep_last_amount": "Antal databasdumpar att behålla", - "backup_onboarding_1_description": "extern kopia i molnet eller på en annan fysisk plats.", - "backup_onboarding_2_description": "lokala kopior på olika enheter. Detta inkluderar huvudfilerna och en lokal säkerhetskopia av dessa filer.", - "backup_onboarding_3_description": "totala kopior av dina data, inklusive originalfilerna. Detta inkluderar 1 extern kopia och 2 lokala kopior.", - "backup_onboarding_description": "En 3-2-1 säkerhetskopieringsstrategi rekommenderas för att skydda din data. Du bör även spara kopior av dina uppladdade foton/videor samt Immich-databasen för en heltäckande säkerhetskopieringslösning.", - "backup_onboarding_footer": "För mer information om säkerhetskopiering av Immich, se dokumentationen.", - "backup_onboarding_parts_title": "En 3-2-1 säkerhetskopiering inkluderar:", - "backup_onboarding_title": "Säkerhetskopior", - "backup_settings": "Inställningar databasdump", - "backup_settings_description": "Hantera inställningar för databasdumpning.", - "cleared_jobs": "Rensade jobben för: {job}", - "config_set_by_file": "Konfigurationen är satt av en konfigurationsfil", - "confirm_delete_library": "Är du säker på att du vill radera {library} album?", - "confirm_delete_library_assets": "Är du säker på att du vill radera detta album? {count, plural, one {# objekt} other {Samtliga # objekt}} kommer att tas bort från Immich och åtgärden kan inte ångras. Filerna kommer att behållas på hårddisken.", - "confirm_email_below": "För att bekräfta, skriv ”{email}” nedan", - "confirm_reprocess_all_faces": "Är du säker på att du vill återprocessa alla ansikten? Detta kommer också rensa namngivna personer.", - "confirm_user_password_reset": "Är du säker på att du vill återställa lösenordet för {user}?", - "confirm_user_pin_code_reset": "Är du säker på att du vill återställa PIN-koden för {user}?", - "copy_config_to_clipboard_description": "Kopiera den aktuella systemkonfigurationen som ett JSON-objekt till urklipp", - "create_job": "Skapa jobb", - "cron_expression": "Cron-uttryck", - "cron_expression_description": "Sätt skanningsintervallet genom att använda cron-format. För mer information se Crontab Guru", - "cron_expression_presets": "Cron-uttryck förinställningar", - "disable_login": "Inaktivera inloggning", - "duplicate_detection_job_description": "Kör maskininlärning på objekt för att upptäcka liknande bilder. Bygger på Smart Search", - "exclusion_pattern_description": "Exkluderingsmönster tillåter dig att ignorera filer och mappar när skanning görs av ditt album. Detta är användbart om du har mappar som innehåller filer som du inte vill importera, t.ex. RAW-filer.", - "export_config_as_json_description": "Ladda ner den aktuella systemkonfigurationen som en JSON-fil", - "external_libraries_page_description": "Administratörs externa bibliotekssida", - "face_detection": "Ansiktsdetektering", - "face_detection_description": "Identifiera ansikten i foton med hjälp av maskininlärning. För videor används endast miniatyrbilden. \"Ladda om\" gör om sökningen för alla objekt. \"Återställ\" rensar all gällande ansikts-data. \"Saknade\" letar i de objekt som ännu inte sökts igenom. Alla identifierade ansikten läggs sedan i jobbkön för ansiktsigenkänning där de mappas till nya eller befintliga personer.", - "facial_recognition_job_description": "Gruppera upptäckta ansikten till personer. Det här steget körs efter att ansiktsdetektering är klar. \"Återställ\" (åter-)grupperar alla ansikten. \"Saknade\" köar ansikten som inte har en person tilldelad.", - "failed_job_command": "Kommando {command} misslyckades för jobb: {job}", - "force_delete_user_warning": "VARNING: Detta tar omedelbart bort användaren och alla mediafiler. Detta kan inte ångras och filerna kan inte återställas.", - "image_format": "Format", - "image_format_description": "WebP producerar mindre filer än JPEG, men kodas långsammare.", - "image_fullsize_description": "Fullstor bild med borttagen metadata, används vid inzoomning", - "image_fullsize_enabled": "Använd fullstor bildgenerering", - "image_fullsize_enabled_description": "Generera fullstor bild för icke webbvänliga format. När \"Använd inbäddade förhandsvisningar\" är aktiverat används inbäddad förhandsvisning utan konvertering. Påverkar inte webbvänliga format som JPEG.", - "image_fullsize_quality_description": "Bildkvalitet för fullstora bilder 1-100. Högre värde ger bättre kvalitet men större filer.", - "image_fullsize_title": "Inställningar för fullstora bilder", - "image_prefer_embedded_preview": "Föredra inbäddad förhandsgranskning", - "image_prefer_embedded_preview_setting_description": "Använd inbäddade förhandsvisningar i RAW-foton som indata till bildbehandling och när det är tillgängligt. Detta kan ge mer exakta färger för vissa bilder, men kvaliteten på förhandsgranskningen är kameraberoende och bilden kan ha fler komprimeringsartefakter.", - "image_prefer_wide_gamut": "Föredra brett färgomfång", - "image_prefer_wide_gamut_setting_description": "Använd Display P3 för miniatyrer. Detta bevarar livfullheten bättre hos bilder med bred färgrymd, men bilder kan se annorlunda ut på gamla enheter med en gammal webbläsarversion. sRGB-bilder behålls som sRGB för att undvika färgskiftningar.", - "image_preview_description": "Mellanstor bild med avskalad metadata, används vid visning av en enskild tillgång och för maskininlärning", - "image_preview_quality_description": "Förhandsgranskningskvalitet från 1-100. Högre är bättre, men ger större filer och kan göra appen mindre följsam. Att ställa in ett lågt värde kan påverka kvaliteten på maskininlärning.", - "image_preview_title": "Förhandsvisningsinställningar", - "image_progressive": "Progressiv", - "image_progressive_description": "Koda JPEG-bilder progressivt för gradvis laddning. Detta påverkar inte WebP-bilder.", - "image_quality": "Kvalitet", - "image_resolution": "Upplösning", - "image_resolution_description": "Högre upplösningar kan bevara fler detaljer men tar längre tid att koda, har större filstorlekar och kan minska appens följsamhet.", - "image_settings": "Bildinställningar", - "image_settings_description": "Hantera kvalitet och upplösning på genererade bilder", - "image_thumbnail_description": "Liten miniatyrbild med avskalad metadata, används när du tittar på grupper av foton som huvudtidslinjen", - "image_thumbnail_quality_description": "Miniatyrkvalitet från 1-100. Högre är bättre, men ger större filer och kan minska appens följsamhet.", - "image_thumbnail_title": "Miniatyrbildsinställningar", - "import_config_from_json_description": "Importera systemkonfiguration genom att ladda upp en JSON-konfigurationsfil", - "job_concurrency": "{job} samtidighet", - "job_created": "Jobb skapat", - "job_not_concurrency_safe": "Det här jobbet är inte samtidighetssäkert.", - "job_settings": "Jobbinställningar", - "job_settings_description": "Hantera samtidiga jobb", - "jobs_delayed": "{jobCount, plural, one{# försenad} other {# försenade}}", - "jobs_failed": "{jobCount, plural, other {# misslyckades}}", - "jobs_over_time": "Jobb över tid", - "library_created": "Skapat bibliotek: {library}", - "library_deleted": "Biblioteket har tagits bort", - "library_details": "Biblioteksdetaljer", - "library_folder_description": "Välj en mapp att importera. Denna mapp, inklusive undermappar, kommer sökas igenom efter bilder och videor.", - "library_remove_exclusion_pattern_prompt": "Är du säker på att du vill ta bort detta exkluderingsmönster?", - "library_remove_folder_prompt": "Är du säker på att du vill ta bort denna importmapp?", - "library_scanning": "Periodisk skanning", - "library_scanning_description": "Konfigurera periodisk biblioteksskanning", - "library_scanning_enable_description": "Aktivera periodisk biblioteksskanning", - "library_settings": "Externa bibliotek", - "library_settings_description": "Hantera inställningar för externa bibliotek", - "library_tasks_description": "Sök igenom externa bibliotek efter nya och/eller förändrade objekt", - "library_updated": "Bibliotek uppdaterat", - "library_watching_enable_description": "Bevaka externa bibliotek för filändringar", - "library_watching_settings": "Bevaka bibliotek [EXPERIMENTELLT]", - "library_watching_settings_description": "Bevaka automatiskt filförändringar", - "logging_enable_description": "Aktivera loggning", - "logging_level_description": "Vilken loggnivå som ska användas vid aktivering.", - "logging_settings": "Loggning", - "machine_learning_availability_checks": "Tillgänglighetskontroller", - "machine_learning_availability_checks_description": "Upptäck och föredrar automatiskt tillgängliga maskininlärningsservrar", - "machine_learning_availability_checks_enabled": "Aktivera tillgänglighetskontroller", - "machine_learning_availability_checks_interval": "Kontrollera intervall", - "machine_learning_availability_checks_interval_description": "Intervall i millisekunder mellan tillgänglighetskontroller", - "machine_learning_availability_checks_timeout": "Begär timeout", - "machine_learning_availability_checks_timeout_description": "Timeout i millisekunder för tillgänglighetskontroller", - "machine_learning_clip_model": "CLIP-modell", - "machine_learning_clip_model_description": "Namnet på en CLIP-modell listad här . Observera att du måste köra ett \"Smart Sökning\" jobb för alla bilder när du ändrar modell.", - "machine_learning_duplicate_detection": "Dubblettdetektering", - "machine_learning_duplicate_detection_enabled": "Aktivera dubblettdetektering", - "machine_learning_duplicate_detection_enabled_description": "Om den inaktiveras kommer exakt identiska tillgångar fortfarande att dedupliceras.", - "machine_learning_duplicate_detection_setting_description": "Använd CLIP-inbäddningar för att hitta troliga dubbletter", - "machine_learning_enabled": "Aktivera maskininlärning", - "machine_learning_enabled_description": "Om det är inaktiverat kommer alla ML-funktioner att inaktiveras oavsett inställningarna nedan.", - "machine_learning_facial_recognition": "Ansiktsigenkänning", - "machine_learning_facial_recognition_description": "Upptäck, känna igen och gruppera ansikten i bilder", - "machine_learning_facial_recognition_model": "Ansiktsigenkänningsmodell", - "machine_learning_facial_recognition_model_description": "Modeller är listade i fallande storleksordning. Större modeller är långsammare och använder mer minne, men ger bättre resultat. Observera att du måste köra Face Detection-jobbet för alla bilder när du ändrar en modell.", - "machine_learning_facial_recognition_setting": "Aktivera ansiktsigenkänning", - "machine_learning_facial_recognition_setting_description": "Om avmarkerad kommer bilder inte att kodas till ansiktsigenkänningen vilket innebär att bilder inte kommer att läggas till i listan av igenkända personer på sidan Utforska.", - "machine_learning_max_detection_distance": "Maximal detektions avstånd", - "machine_learning_max_detection_distance_description": "Maximalt avstånd mellan två bilder för att överväga dem dubbletter, från 0,001-0,1. Högre värden kommer att upptäcka fler dubbletter, men kan leda till falsk positivt.", - "machine_learning_max_recognition_distance": "Maximalt igenkänningsavstånd", - "machine_learning_max_recognition_distance_description": "Det maximala avståndet mellan två ansikten för att anses som samma person, från 0-2. Sänkning av denna kan medföra märkning av två personer som samma person, samtidigt som det kan förhindra att märkning av samma person som två olika personer. Observera att det är lättare att slå samman två personer än att dela en person i två, så ligg hellre närmare den lägre tröskel om det är möjligt.", - "machine_learning_min_detection_score": "Minsta detektions poäng", - "machine_learning_min_detection_score_description": "Lägsta självsäkerhetsnivå för att en sida ska upptäckas mellan 0-1. Lägre värden upptäcker fler sidor men kan resultera i falska positiv.", - "machine_learning_min_recognized_faces": "Minsta identifierade ansikten", - "machine_learning_min_recognized_faces_description": "Minsta antal identifierade ansikten för att en person ska kunna skapas. Om detta ökas blir ansiktsigenkänningen mer exakt, men risken för att ett ansikte inte kopplas till en person ökar.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Använd maskininlärning för att känna igen text i bilder", - "machine_learning_ocr_enabled": "Aktivera OCR", - "machine_learning_ocr_enabled_description": "Om den är inaktiverad kommer bilder inte att genomgå textigenkänning.", - "machine_learning_ocr_max_resolution": "Maximal upplösning", - "machine_learning_ocr_max_resolution_description": "Förhandsvisningar över denna upplösning kommer att ändras i storlek samtidigt som bildförhållandet behålls. Högre värden är mer exakta, men tar längre tid att bearbeta och använder mer minne.", - "machine_learning_ocr_min_detection_score": "Lägsta detektionspoäng", - "machine_learning_ocr_min_detection_score_description": "Lägsta konfidenspoäng för att text ska kunna detekteras är mellan 0 och 1. Lägre värden kommer att detektera mer text men kan resultera i falska positiva resultat.", - "machine_learning_ocr_min_recognition_score": "Lägsta igenkänningspoäng", - "machine_learning_ocr_min_score_recognition_description": "Lägsta konfidenspoäng för att detekterad text ska kunna identifieras är 0–1. Lägre värden identifierar mer text men kan resultera i falska positiva resultat.", - "machine_learning_ocr_model": "OCR-modell", - "machine_learning_ocr_model_description": "Servermodeller är mer exakta än mobilmodeller men tar längre tid att bearbeta och använder mer minne.", - "machine_learning_settings": "Inställningar För Maskininlärning", - "machine_learning_settings_description": "Hantera funktioner och inställningar för maskininlärning", - "machine_learning_smart_search": "Smart Sökning", - "machine_learning_smart_search_description": "Sök semantiskt efter bilder med hjälp av CLIP-inbäddningar", - "machine_learning_smart_search_enabled": "Aktivera smart sökning", - "machine_learning_smart_search_enabled_description": "Om inaktiverat kommer bilder inte att kodas för smart sökning.", - "machine_learning_url_description": "Maskininlärningsserverns URL. Om det är mer än en URL tillagd så kommer ett försök per URL att utföras tills någon av dom svarar, försöken görs i kronologisk ordning. Servrar som inte svarar kommer tillfälligt ignoreras tills de är nåbara igen.", - "maintenance_delete_backup": "Ta bort säkerhetskopia", - "maintenance_delete_backup_description": "Den här filen kommer att raderas oåterkalleligt.", - "maintenance_delete_error": "Det gick inte att ta bort säkerhetskopian.", - "maintenance_restore_backup": "Återställ säkerhetskopia", - "maintenance_restore_backup_description": "Immich kommer att återställas från den valda säkerhetskopian. En ny säkerhetskopia kommer att skapas innan du fortsätter.", - "maintenance_restore_backup_different_version": "Denna säkerhetskopia skapades med en annan version av Immich!", - "maintenance_restore_backup_unknown_version": "Kunde inte fastställa säkerhetskopians verison.", - "maintenance_restore_database_backup": "Återställ databasens säkerhetskopia", - "maintenance_restore_database_backup_description": "Återställ till ett tidigare databasläge med hjälp av en säkerhetskopia", - "maintenance_settings": "Underhåll", - "maintenance_settings_description": "Försätt Immich i underhållsläge.", - "maintenance_start": "Växla till underhållsläge", - "maintenance_start_error": "Misslyckades att starta underhållsläget.", - "maintenance_upload_backup": "Ladda upp en säkerhetskopia av databasen", - "maintenance_upload_backup_error": "Det gick inte att ladda upp säkerhetskopian. Är det en .sql/.sql.gz-fil?", - "manage_concurrency": "Hantera samtidighet", - "manage_concurrency_description": "Navigera till jobbsidan för att hantera jobbens samtidighet", - "manage_log_settings": "Hantera logginställningar", - "map_dark_style": "Mörk stil", - "map_enable_description": "Aktivera kartfunktioner", - "map_gps_settings": "Karta & GPS Inställningar", - "map_gps_settings_description": "Ändra kartor & GPS (Omvänd geokodning) inställningar", - "map_implications": "Kartfunktionen är beroende av en extern kartbitstjänst (tiles.immich.cloud)", - "map_light_style": "Ljus stil", - "map_manage_reverse_geocoding_settings": "Hantera inställningar för Omvänd geokodning", - "map_reverse_geocoding": "Omvänd Geokodning", - "map_reverse_geocoding_enable_description": "Aktivera omvänd geokodning", - "map_reverse_geocoding_settings": "Inställningar för omvänd geokodning", - "map_settings": "Karta", - "map_settings_description": "Hantera kartinställningar", - "map_style_description": "URL till en style.json-karto tema", - "memory_cleanup_job": "Rensa minnen", - "memory_generate_job": "Generera minnen", - "metadata_extraction_job": "Extrahera metadata", - "metadata_extraction_job_description": "Läs in metadata (t.ex. GPS, ansikten och upplösning) för varje resurs", - "metadata_faces_import_setting": "Aktivera import av ansikten", - "metadata_faces_import_setting_description": "Importera ansikten från bildens EXIF-data och sidecar-fil", - "metadata_settings": "Metadata-inställningar", - "metadata_settings_description": "Hantera metadata-inställningar", - "migration_job": "Migrering", - "migration_job_description": "Migrera miniatyrbilder för resurser och ansikten till den senaste mappstrukturen", - "nightly_tasks_cluster_faces_setting_description": "Kör ansiktsigenkänning på nyligen upptäckta ansikten", - "nightly_tasks_cluster_new_faces_setting": "Kluster nya ansikten", - "nightly_tasks_database_cleanup_setting": "Uppgifter för databassanering", - "nightly_tasks_database_cleanup_setting_description": "Rensa bort gammal, utgången data från databasen", - "nightly_tasks_generate_memories_setting": "Generera minnen", - "nightly_tasks_generate_memories_setting_description": "Skapa nya minnen från tillgångar", - "nightly_tasks_missing_thumbnails_setting": "Generera saknade miniatyrbilder", - "nightly_tasks_missing_thumbnails_setting_description": "Köa resurser utan miniatyrer för generering av miniatyrer", - "nightly_tasks_settings": "Inställningar för nattliga uppgifter", - "nightly_tasks_settings_description": "Hantera nattliga uppgifter", - "nightly_tasks_start_time_setting": "Starttid", - "nightly_tasks_start_time_setting_description": "Tidpunkten då servern börjar köra de nattliga uppgifterna", - "nightly_tasks_sync_quota_usage_setting": "Synkroniseringskvotanvändning", - "nightly_tasks_sync_quota_usage_setting_description": "Uppdatera användarlagringskvot baserat på aktuell användning", - "no_paths_added": "Inga vägar tillagda", - "no_pattern_added": "Inga mönster tillagda", - "note_apply_storage_label_previous_assets": "Obs: Om du vill använda lagringsetiketten på tidigare uppladdade tillgångar kör du", - "note_cannot_be_changed_later": "OBS: Detta kan inte ändras i efterhand!", - "notification_email_from_address": "Från adress", - "notification_email_from_address_description": "Avsändarens e-post, till exempel: \"Immich Fotoserver \". Säkerställ att du använder en adress som du har tillåtelse att skicka e-post från.", - "notification_email_host_description": "Värd för epostservern (t.ex. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignorera certifikatfel", - "notification_email_ignore_certificate_errors_description": "Ignorera valideringsfel för TLS-certifikat (rekommenderas ej)", - "notification_email_password_description": "Lösenord att använda för att verifiera identitet med epostservern", - "notification_email_port_description": "Port på epostservern (t.ex. 25, 465 eller 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Använd SMTPS (SMTP över TLS)", - "notification_email_sent_test_email_button": "Skicka test-epost och spara", - "notification_email_setting_description": "Inställningar för att skicka epostnotiser", - "notification_email_test_email": "Skicka test-epost", - "notification_email_test_email_failed": "Misslyckades med att skicka test-epost, undersök dina värden", - "notification_email_test_email_sent": "Ett testmail har skickats till {email}. Kontrollera din inkorg.", - "notification_email_username_description": "Användarnamn att använda vid autentisering med epost-servern", - "notification_enable_email_notifications": "Aktivera epost-notiser", - "notification_settings": "Notisinställningar", - "notification_settings_description": "Hantera notisinställingar, inklusive epost", - "oauth_auto_launch": "Autostart", - "oauth_auto_launch_description": "Starta OAuth-loginflödet automatiskt vid navigering till loginsidan", - "oauth_auto_register": "Autoregistrera", - "oauth_auto_register_description": "Registrera nya användare automatiskt efter inloggning med OAuth", - "oauth_button_text": "Knapptext", - "oauth_client_secret_description": "Krävs för konfidentiell klient, eller om PKCE (Proof Key for Code Exchange) inte stöds för publik klient.", - "oauth_enable_description": "Logga in med OAuth", - "oauth_mobile_redirect_uri": "Telefonomdirigernings-URI", - "oauth_mobile_redirect_uri_override": "Telefonomdirigerings-URI överrskridning", - "oauth_mobile_redirect_uri_override_description": "Aktivera om OAuth-leverantören inte tillåter mobila URI:er, så som ''{callback}''", - "oauth_role_claim": "Rollanspråk", - "oauth_role_claim_description": "Bevilja administratörsåtkomst automatiskt baserat på förekomsten av detta påstående. Påståendet kan innehålla antingen 'user' eller 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Hantera OAuth-logininställningar", - "oauth_settings_more_details": "För ytterligare detaljer om denna funktion, se dokumentationen.", - "oauth_storage_label_claim": "Användaranknuten lagringsetikett", - "oauth_storage_label_claim_description": "Sätter automatiskt angiven användares lagringsetikett.", - "oauth_storage_quota_claim": "Användaranknuten lagringskvot", - "oauth_storage_quota_claim_description": "Sätter automatiskt angiven användares lagringskvot.", - "oauth_storage_quota_default": "Standardlagringskvot (GiB)", - "oauth_storage_quota_default_description": "Kvot i GiB som används när ingen fordran angetts.", - "oauth_timeout": "Begäran tog för lång tid", - "oauth_timeout_description": "Timeout för förfrågningar i millisekunder", - "ocr_job_description": "Använd maskininlärning för att känna igen text i bilder", - "password_enable_description": "Logga in med epost och lösenord", - "password_settings": "Lösenordsinloggning", - "password_settings_description": "Hantera inställningar för lösenords-inloggning", - "paths_validated_successfully": "Samtliga sökvägar kunde bekräftas", - "person_cleanup_job": "Person rensning", - "queue_details": "Köinformation", - "queues": "Jobbköer", - "queues_page_description": "Sida för administratörsjobbköer", - "quota_size_gib": "Lagringskvot (GiB)", - "refreshing_all_libraries": "Samtliga bibliotek uppdateras", - "registration": "Administratörsregistrering", - "registration_description": "Du utses till administratör eftersom du är systemets första användare. Du ansvarar för administration och kan skapa ytterligare användare.", - "remove_failed_jobs": "Ta bort misslyckade jobb", - "require_password_change_on_login": "Kräv av användaren att byta lösenord vid första inloggning", - "reset_settings_to_default": "Återställ inställningar till standard", - "reset_settings_to_recent_saved": "Återställ inställningar till de senaste sparade", - "scanning_library": "Skanna bibliotek", - "search_jobs": "Sökjobb…", - "send_welcome_email": "Skicka välkomstmail", - "server_external_domain_settings": "Extern domän", - "server_external_domain_settings_description": "Domän för publikt delade länkar, inklusive http(s)://", - "server_public_users": "Vanlig användare", - "server_public_users_description": "Alla användare (namn och e-post) är listade när man lägger till en användare till ett delat album. Om inaktiverat, kommer användarlistan endast vara synlig för administratörer.", - "server_settings": "Serverinställningar", - "server_settings_description": "Hantera serverinställningar", - "server_stats_page_description": "Statistiksida för administratörsservern", - "server_welcome_message": "Välkomstmeddelande", - "server_welcome_message_description": "Ett meddelande som visas på inloggningssidan.", - "settings_page_description": "Administratörsinställningar", - "sidecar_job": "Medföljande metadata", - "sidecar_job_description": "Upptäck eller synkronisera medföljande metadata från filsystemet", - "slideshow_duration_description": "Antal sekunder att visa varje bild", - "smart_search_job_description": "Kör maskininlärning på objekt för att stödja smart sökning", - "storage_template_date_time_description": "Tidsstämpel för resursens skapande används för datum och tidsinformation", - "storage_template_date_time_sample": "Exempeltid {date}", - "storage_template_enable_description": "Aktivera mallmotor för lagring", - "storage_template_hash_verification_enabled": "Hash-verifiering aktiverat", - "storage_template_hash_verification_enabled_description": "Aktiverar hash-verifiering, deaktiviera inte om du inte är säker på implikationerna", - "storage_template_migration": "Migrering av Lagringsmallar", - "storage_template_migration_description": "Applicera aktiv {template} till tidigare uppladdade resurser", - "storage_template_migration_info": "Lagringsmallen kommer konvertera alla filändelser till gemena bokstäver. Ändringar gäller endast för nya resurser, för att retoaktivt tillämpa mallen på befintliga resurser kör {job}.", - "storage_template_migration_job": "Lagringsmall migreringsjobb", - "storage_template_more_details": "För mer information om den här funktionen se Lagringsmall och dess konsekvenser", - "storage_template_onboarding_description_v2": "Denna funktion kommer när den är aktiverad att auto-organisera filer baserat på en användardefinierad mall. För mer information se dokumentationen.", - "storage_template_path_length": "Uppskattad längdbegränsning på sökväg: {length, number}/{limit, number}", - "storage_template_settings": "Lagringsmall", - "storage_template_settings_description": "Hantera mappstruktur och filnamn för uppladdade resurser", - "storage_template_user_label": "{label} är användarens lagringsmärkning", - "system_settings": "Systeminställningar", - "tag_cleanup_job": "Markera för rensning", - "template_email_available_tags": "Du kan använda följande variablar i din mall: {tags}", - "template_email_if_empty": "Om mallen är tom, kommer standard e-post att användas.", - "template_email_invite_album": "Inbjudan Album Mall", - "template_email_preview": "Förhandsgranskning", - "template_email_settings": "E-post mall", - "template_email_update_album": "Uppdatera Album Mall", - "template_email_welcome": "Välkommen e-post mall", - "template_settings": "Notifikations Mall", - "template_settings_description": "Hantera anpassade mallar för notifikationer", - "theme_custom_css_settings": "Anpassad CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets möjliggör designanpassningar av Immich.", - "theme_settings": "Temainställningar", - "theme_settings_description": "Hantera anpassningar av webbgränssnittet för Immich", - "thumbnail_generation_job": "Generera Miniatyrer", - "thumbnail_generation_job_description": "Generera stora, små och suddiga miniatyrer för varje objekt, samt för varje person", - "transcoding_acceleration_api": "Accelerations-API", - "transcoding_acceleration_api_description": "API som kommer att interagera med din enhet för att accelerera omkodning. Inställning är 'best effort': vid fel kommer den att återgå till mjukvarubaserad omkodning. VP9 kan fungera eller inte, beroende på din hårdvara.", - "transcoding_acceleration_nvenc": "NVENC (kräver NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (kräver 7 generationens Intel CPU eller senare)", - "transcoding_acceleration_rkmpp": "RKMPP (bara med Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Accepterade ljud-codecs", - "transcoding_accepted_audio_codecs_description": "Välj vilka ljud-codecs som inte behöver omkodas. Används endast för vissa omkodningspolicyer.", - "transcoding_accepted_containers": "Accepterade behållare", - "transcoding_accepted_containers_description": "Välj vilka kontainerformat som inte behöver remuxas till MP4. Endast används för vissa transcoding-politischer.", - "transcoding_accepted_video_codecs": "Accepterade video-codecs", - "transcoding_accepted_video_codecs_description": "Välj vilka video-codecs som inte behöver omkodas. Används endast för vissa omkodningspolicyer.", - "transcoding_advanced_options_description": "Val som de flesta användare inte bör behöva ändra", - "transcoding_audio_codec": "Ljud-codec", - "transcoding_audio_codec_description": "Opus är bästa kvalitetsvalet, men är inte lika kompatibelt med äldre enheter eller mjukvara.", - "transcoding_bitrate_description": "Videor som är i högre än max bithastighet eller inte i ett accepterat format", - "transcoding_codecs_learn_more": "För att läsa mer om terminologin här se FFmpeg-dokumentationen för H.264 kodek, HEVC kodek och VP9 kodek.", - "transcoding_constant_quality_mode": "Konstant kvalitetsläge", - "transcoding_constant_quality_mode_description": "ICQ är bättre än CQP, men vissa hårdvaruaccelerationsenheter stöder inte detta läge. Om det här alternativet är inställt föredras det angivna läget när kvalitetsbaserad kodning används. NVENC ignoreras eftersom det inte stöder ICQ.", - "transcoding_constant_rate_factor": "Konstant hastighetsfaktor (-crf)", - "transcoding_constant_rate_factor_description": "Nivå på videokvalitet. Typiska värden är 23 för H.264, 28 för HEVC, 31 för VP9 och 35 för AV1. Lägre är bättre, men producerar större filer.", - "transcoding_disabled_description": "Omkoda inte videofiler, detta kan störa uppspelning på vissa klienter", - "transcoding_encoding_options": "Kodningsval", - "transcoding_encoding_options_description": "Välj codec, upplösning, kvalitet och andra val för kodade videor", - "transcoding_hardware_acceleration": "Hårdvaruacceleration", - "transcoding_hardware_acceleration_description": "Experimentell: snabbare transkodning men kan minska kvaliteten vid samma bithastighet", - "transcoding_hardware_decoding": "Hårdvaruavkodning", - "transcoding_hardware_decoding_setting_description": "Aktiverar end-to-end accelerering i stället för endast kodningsacceleration. Fungerar inte med alla videor.", - "transcoding_max_b_frames": "Max B-ramar", - "transcoding_max_b_frames_description": "Högre värden förbättrar kompressionseffektiviteten, men saktar ner kodningen. Kan vara inkompatibel med hårdvaruacceleration på äldre enheter. 0 avaktiverar B-frames, medan -1 anger detta värde automatiskt.", - "transcoding_max_bitrate": "Max bithastighet", - "transcoding_max_bitrate_description": "En maximal bitrate kan göra filstorlekar mer förutsägbara till en liten kostnad på kvalitet. Vid 720p är typiska värden 2600 kbit/s för VP9 eller HEVC, eller 4500 kbit/s för H.264. Inaktiverad om satt till 0. När ingen enhet anges så kommer k (för kbit/s) användas; därför är 5000, 5000k och 5M (för Mbit/s) likvärdiga.", - "transcoding_max_keyframe_interval": "Max nyckelbildruteintervall", - "transcoding_max_keyframe_interval_description": "Sätter det maximala bildruteavståndet mellan nyckelbildrutor. Lägre värden försämrar kompressionseffektiviteten, men förbättrar söktiderna och kan förbättra kvaliteten i scener med snabb rörelse. 0 ställer in detta värde automatiskt.", - "transcoding_optimal_description": "Videor som är högre än mållösning eller inte i ett accepterat format", - "transcoding_policy": "Omkodningspolicy", - "transcoding_policy_description": "Välj när en video ska omkodas", - "transcoding_preferred_hardware_device": "Föredragen hårdvaruenhet", - "transcoding_preferred_hardware_device_description": "Gäller enbart VAAPI och QSV. Ställer in dri-läget som används för hårdvaruomkodning.", - "transcoding_preset_preset": "Förinställning (-preset)", - "transcoding_preset_preset_description": "Kompressionshastighet. Långsammare preset ger mindre filer och högre kvalitet för en given bitrate. VP9 ignorerar hastigheter högre än 'faster'.", - "transcoding_reference_frames": "Referensbildrutor", - "transcoding_reference_frames_description": "Antalet bildrutor som tas i beaktande när en given bildruta ska komprimeras. Högre värden ger effektivare kompression på bekostnad av långsammare kodning. 0 ställer in detta värde automatiskt.", - "transcoding_required_description": "Enbart videos som inte är ett accepterat format", - "transcoding_settings": "Inställningar för omkodning av video", - "transcoding_settings_description": "Hantera vilka videor som ska omkodas och hur de ska behandlas", - "transcoding_target_resolution": "Förväntad upplösning", - "transcoding_target_resolution_description": "En högre upplösning kan bevara fler detaljer men kan ta längre tid at koda, ha större fil storlek och kan försämra appens svarstid.", - "transcoding_temporal_aq": "Temporär AQ", - "transcoding_temporal_aq_description": "Gäller endast NVENC. Temporal adaptiv kvantisering ökar kvaliteten på scener med hög detaljrikedom och låg rörelse. Kan vara inkompatibel med äldre enheter.", - "transcoding_threads": "Trådar", - "transcoding_threads_description": "Högre värden leder till snabbare kodning, men lämnar mindre utrymme för servern att bearbeta andra uppgifter medan den är aktiv. Detta värde bör inte vara mer än antalet CPU-kärnor. Maximerar användningen om den är inställd på 0.", - "transcoding_tone_mapping": "Ton mappning", - "transcoding_tone_mapping_description": "Försöker att bevara utseendet på HDR-videor när de konverteras till SDR. Varje algoritm gör olika avvägningar för färg, detaljer och ljusstyrka. Hable bevarar detaljer, Mobius bevarar färg och Reinhard bevarar ljusstyrkan.", - "transcoding_transcode_policy": "Omkodningspolicy", - "transcoding_transcode_policy_description": "Policy för när en video ska omkodas. HDR-videor kommer alltid att omkodas (förutom om omkodning är inaktiverad).", - "transcoding_two_pass_encoding": "Två-pass kodning", - "transcoding_two_pass_encoding_setting_description": "Koda om i två omgångar för att producera bättre kodade videor. När max bitrate är aktiverat (krävs för att det ska fungera med H.264 och HEVC), använder det här läget ett bithastighetsområde baserat på max bitrate och ignorerar CRF. För VP9 kan CRF användas om max bitrate är inaktiverat.", - "transcoding_video_codec": "Video codec", - "transcoding_video_codec_description": "VP9 har hög effektivitet och webbkompatibilitet, men tar längre tid att omkoda. HEVC fungerar på liknande sätt, men har lägre webbkompatibilitet. H.264 är allmänt kompatibel och snabb att omkoda, men producerar mycket större filer. AV1 är den mest effektiva codec men saknar stöd på äldre enheter.", - "trash_enabled_description": "Aktivera papperskorgen", - "trash_number_of_days": "Antal dagar", - "trash_number_of_days_description": "Antal dagar för att förvara tillgångarna i papperskorgen innan de permanent tas bort", - "trash_settings": "Papperskorginställningar", - "trash_settings_description": "Hantera papperskorginställningar", - "unlink_all_oauth_accounts": "Ta bort länken till alla OAuth-konton", - "unlink_all_oauth_accounts_description": "Kom ihåg att ta bort länken till alla OAuth-konton innan du migrerar till en ny leverantör.", - "unlink_all_oauth_accounts_prompt": "Är du säker på att du vill ta bort länken till alla OAuth-konton? Detta återställer OAuth-ID:t för varje användare och kan inte ångras.", - "user_cleanup_job": "Användarrensning", - "user_delete_delay": "{user}s konto och tillgångar kommer att schemaläggas för permanent radering om {delay, plural, one {# day} other {# days}}.", - "user_delete_delay_settings": "Borttagningsfördröjning", - "user_delete_delay_settings_description": "Antal dagar efter borttagning för att permanent radera en användares konto och tillgångar. Arbetet med borttagning av användare körs vid midnatt för att söka efter användare som är redo för radering. Ändringar av denna inställning kommer att utvärderas vid nästa körning.", - "user_delete_immediately": "{user} konto och tillgångar kommer att stå i kö för permanent radering.", - "user_delete_immediately_checkbox": "Köa användare och tillgångar för omedelbar radering", - "user_details": "Användardetaljer", - "user_management": "Användarhantering", - "user_password_has_been_reset": "Användarens lösenord har återställts:", - "user_password_reset_description": "Ange det tillfälliga lösenordet till användaren och informera dem om att de kommer att behöva ändra lösenordet vid nästa inloggning.", - "user_restore_description": "{user} konto kommer att återställas.", - "user_restore_scheduled_removal": "Återställ användare - schemalagd borttagning {date, date, long}", - "user_settings": "Användarinställningar", - "user_settings_description": "Hantera användarinställningar", - "user_successfully_removed": "Användaren {email} har tagits bort.", - "users_page_description": "Administratörsanvändare", - "version_check_enabled_description": "Aktivera versionskontroll", - "version_check_implications": "Funktionen för versionskontroll är beroende av periodisk kommunikation med github.com", - "version_check_settings": "Versionskontroll", - "version_check_settings_description": "Aktivera/inaktivera meddelandet om ny versionen", - "video_conversion_job": "Omkoda videor", - "video_conversion_job_description": "Koda om videor för bredare kompatibilitet med webbläsare och enheter" - }, - "admin_email": "E-postadress för administratör", - "admin_password": "Admin Lösenord", - "administration": "Administration", - "advanced": "Avancerat", - "advanced_settings_clear_image_cache": "Rensa bild-cache", - "advanced_settings_clear_image_cache_error": "Misslyckades med att rensa bild-cachen", - "advanced_settings_clear_image_cache_success": "{size} har rensats", - "advanced_settings_enable_alternate_media_filter_subtitle": "Använd det här alternativet för att filtrera media under synkronisering baserat på alternativa kriterier. Prova detta endast om du har problem med att appen inte hittar alla album.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTELLT] Använd alternativ enhetsalbum-synkroniseringsfilter", - "advanced_settings_log_level_title": "Loggnivå: {level}", - "advanced_settings_prefer_remote_subtitle": "Vissa enheter är mycket långsamma på att ladda miniatyrer från objekt på enheten. Aktivera den här inställningen för att ladda bilder från servern istället.", - "advanced_settings_prefer_remote_title": "Föredra bilder från servern", - "advanced_settings_proxy_headers_subtitle": "Definiera proxy-headers som Immich ska skicka med i varje närverksanrop", - "advanced_settings_proxy_headers_title": "Anpassade proxyheaders [EXPERIMENTELLT]", - "advanced_settings_readonly_mode_subtitle": "Aktiverar skrivskyddat-läge där foton endast kan visas. Följande funktioner inaktiveras: välj flera bilder, dela, casta, ta bort bilder. Aktivera/inaktivera skrivskyddat läge via profilbilden på appens hemskärm", - "advanced_settings_readonly_mode_title": "Skrivskyddat läge", - "advanced_settings_self_signed_ssl_subtitle": "Hoppar över verifiering av serverns SSL-certifikat. Krävs för självsignerade certifikat.", - "advanced_settings_self_signed_ssl_title": "Tillåt självsignerade SSL-certifikat [EXPERIMENTELLT]", - "advanced_settings_sync_remote_deletions_subtitle": "Radera eller återställ automatiskt en resurs på den här enheten när den åtgärden utförs på webben", - "advanced_settings_sync_remote_deletions_title": "Synkronisera fjärradering [EXPERIMENTELL]", - "advanced_settings_tile_subtitle": "Avancerade användarinställningar", - "advanced_settings_troubleshooting_subtitle": "Aktivera funktioner för felsökning", - "advanced_settings_troubleshooting_title": "Felsökning", - "age_months": "Ålder {months, plural, one {# månad} other {# månader}}", - "age_year_months": "Ålder 1 år, {months, plural, one {# månad} other {# månader}}", - "age_years": "{years, plural, other {Ålder #}}", - "album": "Album", - "album_added": "Albumet har lagts till", - "album_added_notification_setting_description": "Få ett e-postmeddelande när du läggs till i ett delat album", - "album_cover_updated": "Albumomslaget uppdaterat", - "album_delete_confirmation": "Är du säker på att du vill ta bort albumet {album}?", - "album_delete_confirmation_description": "Om det här albumet delas kommer andra användare inte att kunna komma åt det längre.", - "album_deleted": "Albumet raderat", - "album_info_card_backup_album_excluded": "EXKLUDERAD", - "album_info_card_backup_album_included": "INKLUDERAD", - "album_info_updated": "Albuminformation uppdaterad", - "album_leave": "Lämna albumet?", - "album_leave_confirmation": "Är du säker på att du vill lämna {album}?", - "album_name": "Albumnamn", - "album_options": "Albumalternativ", - "album_remove_user": "Ta bort användare?", - "album_remove_user_confirmation": "Är du säker på att du vill ta bort {user}?", - "album_search_not_found": "Inga album hittades som matchade din sökning", - "album_selected": "Album valt", - "album_share_no_users": "Det verkar som att du har delat det här albumet med alla användare eller så har du inte någon användare att dela med.", - "album_summary": "Albumsammanfattning", - "album_updated": "Albumet uppdaterat", - "album_updated_setting_description": "Få ett e-postmeddelande när ett delat album har nya tillgångar", - "album_upload_assets": "Ladda upp material från din dator och lägg till i album", - "album_user_left": "Lämnade {album}", - "album_user_removed": "Tog bort {user}", - "album_viewer_appbar_delete_confirm": "Är du säker på att du vill ta bort albumet från ditt konto?", - "album_viewer_appbar_share_err_delete": "Kunde inte radera album", - "album_viewer_appbar_share_err_leave": "Kunde inte lämna album", - "album_viewer_appbar_share_err_remove": "Kunde inte ta bort objekt från album", - "album_viewer_appbar_share_err_title": "Kunde inte ändra albumtitel", - "album_viewer_appbar_share_leave": "Lämna album", - "album_viewer_appbar_share_to": "Dela Till", - "album_viewer_page_share_add_users": "Lägg till användare", - "album_with_link_access": "Låt alla med länken se foton och personer i det här albumet.", - "albums": "Album", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", - "albums_default_sort_order": "Standard sorteringsordning för album", - "albums_default_sort_order_description": "Standard sorteringsordning för mediefiler vid skapande av nytt album.", - "albums_feature_description": "Samlingar av mediefiler som kan delas med andra användare.", - "albums_on_device_count": "Album på enheten ({count})", - "albums_selected": "{count, plural, one {# album valt} other {# album valda}}", - "all": "Allt", - "all_albums": "Alla album", - "all_people": "Alla personer", - "all_photos": "Alla foton", - "all_videos": "Alla videor", - "allow_dark_mode": "Tillåt mörkt läge", - "allow_edits": "Tillåt redigeringar", - "allow_public_user_to_download": "Tillåt offentlig användare att ladda ner", - "allow_public_user_to_upload": "Tillåt en offentlig användare att ladda upp", - "allowed": "Tillåten", - "alt_text_qr_code": "QR-kod", - "always_keep": "Behåll alltid", - "always_keep_photos_hint": "Frigör utrymme behåller alla foton på den här enheten.", - "always_keep_videos_hint": "Frigör utrymme behåller alla videor på den här enheten.", - "anti_clockwise": "Moturs", - "api_key": "API Nyckel", - "api_key_description": "Detta värde kommer bara att visas en gång. Se till att kopiera det innan du stänger fönstret.", - "api_key_empty": "Ditt API-nyckelnamn ska inte vara tomt", - "api_keys": "API-Nycklar", - "app_architecture_variant": "Variant (Arkitektur)", - "app_bar_signout_dialog_content": "Är du säker på att du vill logga ut?", - "app_bar_signout_dialog_ok": "Ja", - "app_bar_signout_dialog_title": "Logga ut", - "app_download_links": "Länkar för appnedladdning", - "app_settings": "Appinställningar", - "app_stores": "Appstore", - "app_update_available": "Appuppdatering är tillgänglig", - "appears_in": "Visas i", - "apply_count": "Tillämpa ({count, number})", - "archive": "Arkiv", - "archive_action_prompt": "{count} adderade till Arkiv", - "archive_or_unarchive_photo": "Arkivera eller oarkivera fotot", - "archive_page_no_archived_assets": "Inga arkiverade objekt hittade", - "archive_page_title": "Arkiv ({count})", - "archive_size": "Arkivstorlek", - "archive_size_description": "Konfigurera arkivstorleken för nedladdningar (i GiB)", - "archived": "Arkiverade", - "archived_count": "{count, plural, other {Arkiverade #}}", - "are_these_the_same_person": "Är det samma person?", - "are_you_sure_to_do_this": "Är du säker på att du vill göra det här?", - "array_field_not_fully_supported": "Arrayfält kräver manuell JSON-redigering", - "asset_action_delete_err_read_only": "Kan inte ta bort skrivskyddade objekt, hoppar över", - "asset_action_share_err_offline": "Kan inte hämta offline-objekt, hoppar över", - "asset_added_to_album": "Lades till i album", - "asset_adding_to_album": "Lägger till i album...…", - "asset_created": "Tillgång skapad", - "asset_description_updated": "Tillgångens beskrivning har uppdaterats", - "asset_filename_is_offline": "Tillgången {filename} är offline", - "asset_has_unassigned_faces": "Tillgången har otilldelade ansikten", - "asset_hashing": "Hashing...…", - "asset_list_group_by_sub_title": "Gruppera på", - "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", - "asset_list_layout_settings_group_automatically": "Automatiskt", - "asset_list_layout_settings_group_by": "Gruppera bilder efter", - "asset_list_layout_settings_group_by_month_day": "Månad + dag", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Layoutinställningar för bildrutnät", - "asset_list_settings_title": "Bildrutnät", - "asset_not_found_on_device_android": "Tillgångar hittades inte på enheten", - "asset_not_found_on_device_ios": "Tillgångar hittades inte på enheten. Om du använder iCloud kan tillgången vara oåtkomlig på grund av en felaktig fil som lagrats på iCloud", - "asset_not_found_on_icloud": "Tillgångar hittades inte på iCloud. Tillgången kan vara oåtkomlig på grund av en felaktig fil som lagras på iCloud", - "asset_offline": "Tillgång offline", - "asset_offline_description": "Denna externa tillgång finns inte längre på disken. Kontakta din Immich-administratör för hjälp.", - "asset_restored_successfully": "Objekt återställt", - "asset_skipped": "Överhoppad", - "asset_skipped_in_trash": "I papperskorgen", - "asset_trashed": "Tillgång kasserad", - "asset_troubleshoot": "Felsökning av tillgångar", - "asset_uploaded": "Uppladdad", - "asset_uploading": "Laddar upp...…", - "asset_viewer_settings_subtitle": "Hantera inställningar för gallerivisare", - "asset_viewer_settings_title": "Objektvisare", - "assets": "Objekt", - "assets_added_count": "La till {count, plural, one {# asset} other {# assets}}", - "assets_added_to_album_count": "Lade till {count, plural, one {# asset} other {# assets}} i albumet", - "assets_added_to_albums_count": "Lade till {assetTotal, plural, one {# asset} other {# assets}} till {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} kan inte läggas till i albumet", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} kan inte läggas till i något av albumen", - "assets_count": "{count, plural, one {# objekt} other {# objekt}}", - "assets_deleted_permanently": "{count} objekt har tagits bort permanent", - "assets_deleted_permanently_from_server": "{count} objekt har tagits bort permanent från Immich-servern", - "assets_downloaded_failed": "{count, plural, one {Nedladdad # fil - {error} fil misslyckades} other {Nedladdade # filer - {error} filer misslyckades}}", - "assets_downloaded_successfully": "{count, plural, one {Nedladdade # fil framgångsrikt} other {Nedladdade # filer framgångsrikt}}", - "assets_moved_to_trash_count": "Flyttade {count, plural, one {# asset} other {# assets}} till papperskorgen", - "assets_permanently_deleted_count": "Raderad permanent {count, plural, one {# asset} other {# assets}}", - "assets_removed_count": "Tog bort {count, plural, one {# asset} other {# assets}}", - "assets_removed_permanently_from_device": "{count} objekt har raderats permanent från din enhet", - "assets_restore_confirmation": "Är du säker på att du vill återställa alla dina papperskorgen? Du kan inte ångra den här åtgärden! Observera att offlineobjekt inte kan återställas på detta sätt.", - "assets_restored_count": "Återställd {count, plural, one {# asset} other {# assets}}", - "assets_restored_successfully": "{count} objekt har återställts", - "assets_trashed": "{count} objekt raderade", - "assets_trashed_count": "Till Papperskorgen {count, plural, one {# asset} other {# assets}}", - "assets_trashed_from_server": "{count} objekt raderade från Immich-servern", - "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Asset were}} är redan en del av albumet", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Asset were}} redan del av albumen", - "authorized_devices": "Auktoriserade enheter", - "automatic_endpoint_switching_subtitle": "Anslut lokalt via det angivna Wi-Fi-nätverket när det är tillgängligt och använd alternativa anslutningar på andra platser", - "automatic_endpoint_switching_title": "Automatisk URL-växling", - "autoplay_slideshow": "Spela upp bildspel automatiskt", - "back": "Bakåt", - "back_close_deselect": "Tillbaka, stäng eller avmarkera", - "background_backup_running_error": "Bakgrundssäkerhetskopiering körs för närvarande, kan inte starta manuell säkerhetskopiering", - "background_location_permission": "Tillåtelse för bakgrundsplats", - "background_location_permission_content": "För att kunna byta nätverk när appen körs i bakgrunden måste Immich *alltid* ha åtkomst till exakt plats så att appen kan läsa av Wi-Fi-nätverkets namn", - "background_options": "Bakgrundsalternativ", - "backup": "Säkerhetskopiera", - "backup_album_selection_page_albums_device": "Album på enhet ({count})", - "backup_album_selection_page_albums_tap": "Tryck en gång för att inkludera, tryck två gånger för att exkludera", - "backup_album_selection_page_assets_scatter": "Objekt kan vara utspridda över flera album. Därför kan album inkluderas eller exkluderas under säkerhetskopieringsprocessen.", - "backup_album_selection_page_select_albums": "Välj album", - "backup_album_selection_page_selection_info": "Info om valda objekt", - "backup_album_selection_page_total_assets": "Antal unika objekt", - "backup_albums_sync": "Backup-albumsynkronisering", - "backup_all": "Allt", - "backup_background_service_backup_failed_message": "Säkerhetskopiering av foton och videor misslyckades. Försöker igen…", - "backup_background_service_complete_notification": "Säkerhetskopiering av tillgångar klar", - "backup_background_service_connection_failed_message": "Anslutning till servern misslyckades. Försöker igen…", - "backup_background_service_current_upload_notification": "Laddar upp {filename}", - "backup_background_service_default_notification": "Söker efter nya objekt…", - "backup_background_service_error_title": "Fel vid säkerhetskopiering", - "backup_background_service_in_progress_notification": "Säkerhetskopierar dina foton och videor…", - "backup_background_service_upload_failure_notification": "Kunde inte ladda upp {filename}", - "backup_controller_page_albums": "Säkerhetskopiera album", - "backup_controller_page_background_app_refresh_disabled_content": "Aktivera uppdatering i bakgrunden i Inställningar > Allmänt > Uppdatering I Bakgrunden för att använda säkerhetskopiering i bakgrunden.", - "backup_controller_page_background_app_refresh_disabled_title": "Uppdatering i bakgrunden är avaktiverat", - "backup_controller_page_background_app_refresh_enable_button_text": "Gå till inställningar", - "backup_controller_page_background_battery_info_link": "Visa mig hur", - "backup_controller_page_background_battery_info_message": "För optimal säkerhetskopiering i bakgrunden bör du stänga av batterioptimering som begränsar bakgrundsaktivitet för Immich.\n\nEftersom detta är enhetsspecifikt så bör du söka instruktioner från din enhetstillverkare.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Batterioptimering", - "backup_controller_page_background_charging": "Endast vid laddning", - "backup_controller_page_background_configure_error": "Kunde inte konfigurera bakgrundstjänsten", - "backup_controller_page_background_delay": "Skjut upp säkerhetskopiering av nya foton och videor: {duration}", - "backup_controller_page_background_description": "Aktivera säkerhetskopiering i bakgrunden för att automatiskt säkerhetskopiera nya foton och videor utan att öppna appen", - "backup_controller_page_background_is_off": "Automatisk säkerhetskopiering i bakgrunden är avstängd", - "backup_controller_page_background_is_on": "Automatisk säkerhetskopiering i bakgrunden är aktiverad", - "backup_controller_page_background_turn_off": "Stäng av säkerhetskopiering i bakgrunden", - "backup_controller_page_background_turn_on": "Aktivera säkerhetskopiering i bakgrunden", - "backup_controller_page_background_wifi": "Endast med Wi-Fi", - "backup_controller_page_backup": "Säkerhetskopiera", - "backup_controller_page_backup_selected": "Valt: ", - "backup_controller_page_backup_sub": "Säkerhetskopierade foton och videor", - "backup_controller_page_created": "Skapad: {date}", - "backup_controller_page_desc_backup": "Aktivera förgrunds-säkerhetskopiering för att automatiskt ladda upp nya foton och videor när du öppnar appen.", - "backup_controller_page_excluded": "Exkluderat: ", - "backup_controller_page_failed": "Misslyckade ({count})", - "backup_controller_page_filename": "Filnamn: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Säkerhetskopieringsinformation", - "backup_controller_page_none_selected": "Ingenting valt", - "backup_controller_page_remainder": "Resterande", - "backup_controller_page_remainder_sub": "Återstående foton och album att säkerhetskopiera från valda", - "backup_controller_page_server_storage": "Serverlagring", - "backup_controller_page_start_backup": "Starta säkerhetskopiering", - "backup_controller_page_status_off": "Automatisk säkerhetskopiering är avstängd", - "backup_controller_page_status_on": "Automatisk säkerhetskopiering är aktiverad", - "backup_controller_page_storage_format": "{used} av {total} använt", - "backup_controller_page_to_backup": "Album att säkerhetskopiera", - "backup_controller_page_total_sub": "Alla unika foton och videor från valda album", - "backup_controller_page_turn_off": "Stäng av automatisk säkerhetskopiering", - "backup_controller_page_turn_on": "Aktivera automatisk säkerhetskopiering", - "backup_controller_page_uploading_file_info": "Laddar upp filinformation", - "backup_err_only_album": "Kan inte ta bort det enda albumet", - "backup_error_sync_failed": "Synkroniseringen misslyckades. Det går inte att bearbeta säkerhetskopian.", - "backup_info_card_assets": "objekt", - "backup_manual_cancelled": "Avbrutet", - "backup_manual_in_progress": "Uppladdning pågår redan. Försök igen om en liten stund", - "backup_manual_success": "Klart", - "backup_manual_title": "Uppladdningsstatus", - "backup_options": "Säkerhetskopieringsalternativ", - "backup_options_page_title": "Säkerhetskopieringsinställningar", - "backup_setting_subtitle": "Hantera inställningar för för- och bakgrundsuppladdning", - "backup_settings_subtitle": "Hantera uppladdningsinställningar", - "backup_upload_details_page_more_details": "Tryck för mer detaljer", - "backward": "Bakåt", - "biometric_auth_enabled": "Biometrisk autentisering aktiverad", - "biometric_locked_out": "Du är utelåst från biometrisk autentisering", - "biometric_no_options": "Inga biometriska alternativ tillgängliga", - "biometric_not_available": "Biometrisk autentisering är inte tillgänglig på denna enhet", - "birthdate_saved": "Födelsedatumet har sparats", - "birthdate_set_description": "Födelsedatum används för att beräkna åldern på denna person vid tidpunkten för ett foto.", - "blurred_background": "Suddig bakgrund", - "bugs_and_feature_requests": "Buggar och funktionsförfrågningar", - "build": "Bygge", - "build_image": "Byggfil", - "bulk_delete_duplicates_confirmation": "Är du säker på att du vill massradera {count, plural, one {# duplicate asset} other {# duplicate assets}}? Detta kommer att behålla den största tillgången i varje grupp och permanent radera alla andra dubbletter. Du kan inte ångra den här åtgärden!", - "bulk_keep_duplicates_confirmation": "Är du säker på att du vill behålla {count, plural, one {# duplicate asset} other {# duplicate assets}}? Detta kommer att lösa alla dubbletter av grupper utan att ta bort någonting.", - "bulk_trash_duplicates_confirmation": "Är du säker på att du vill skicka till papperskorgen {count, plural, one {# duplicate asset} other {# duplicate assets}}? Detta kommer att behålla den största tillgången i varje grupp och alla andra dubbletter kasseras.", - "buy": "Köp Immich", - "cache_settings_clear_cache_button": "Rensa cacheminnet", - "cache_settings_clear_cache_button_title": "Rensar appens cacheminne. Detta kommer att avsevärt påverka appens prestanda tills cachen har byggts om.", - "cache_settings_duplicated_assets_clear_button": "RENSA", - "cache_settings_duplicated_assets_subtitle": "Foton och videor som är ignorerade, listas av appen", - "cache_settings_duplicated_assets_title": "Duplicerade Objekt ({count})", - "cache_settings_statistics_album": "Miniatyrbilder för bibliotek", - "cache_settings_statistics_full": "Hela bilder", - "cache_settings_statistics_shared": "Miniatyrbilder till delat album", - "cache_settings_statistics_thumbnail": "Miniatyrbilder", - "cache_settings_statistics_title": "Cacheförbrukning", - "cache_settings_subtitle": "Hantera cachebeteendet för Immich-appen", - "cache_settings_tile_subtitle": "Kontrollera beteende för lokal lagring", - "cache_settings_tile_title": "Lokal Lagring", - "cache_settings_title": "Cache Inställningar", - "camera": "Kamera", - "camera_brand": "Kameramärke", - "camera_model": "Kameramodell", - "cancel": "Avbryt", - "cancel_search": "Avbryt sökning", - "canceled": "Avbruten", - "canceling": "Annullerande", - "cannot_merge_people": "Kan inte slå samman personer", - "cannot_undo_this_action": "Du kan inte ångra den här åtgärden!", - "cannot_update_the_description": "Det går inte att uppdatera beskrivningen", - "cast": "Casta", - "cast_description": "Konfigurera tillgängliga cast-destinationer", - "change_date": "Ändra datum", - "change_description": "Ändra beskrivning", - "change_display_order": "Ändra visningsordning", - "change_expiration_time": "Ändra utgångstid", - "change_location": "Ändra plats", - "change_name": "Byt namn", - "change_name_successfully": "Bytte namn framgångsrikt", - "change_password": "Ändra Lösenord", - "change_password_description": "Detta är antingen första gången du loggar in i systemet eller så har en begäran gjorts om att ändra ditt lösenord. Vänligen ange det nya lösenordet nedan.", - "change_password_form_confirm_password": "Bekräfta lösenord", - "change_password_form_description": "Hej {name},\n\nDet är antingen första gången du loggar in i systemet, eller så har det skett en förfrågan om återställning av ditt lösenord. Ange ditt nya lösenord nedan.", - "change_password_form_log_out": "Logga ut från alla andra enheter", - "change_password_form_log_out_description": "Det rekommenderas att logga ut från alla andra enheter", - "change_password_form_new_password": "Nytt lösenord", - "change_password_form_password_mismatch": "Lösenorden matchar inte", - "change_password_form_reenter_new_password": "Ange Nytt Lösenord Igen", - "change_pin_code": "Ändra PIN-kod", - "change_trigger": "Ändra utlösare", - "change_trigger_prompt": "Är du säker på att du vill ändra utlösaren? Detta tar bort alla befintliga åtgärder och filter.", - "change_your_password": "Ändra ditt lösenord", - "changed_visibility_successfully": "Synligheten har ändrats", - "charging": "Laddar", - "charging_requirement_mobile_backup": "Bakgrundssäkerhetskopiering kräver att enheten laddas", - "check_corrupt_asset_backup": "Kontrollera om det finns korrupta säkerhetskopior av objekt", - "check_corrupt_asset_backup_button": "Kontrollera", - "check_corrupt_asset_backup_description": "Kör kontrollen endast över Wi-Fi och när alla objekt har säkerhetskopierats. Det kan ta några minuter.", - "check_logs": "Kontrollera loggar", - "checksum": "Checksumma", - "choose_matching_people_to_merge": "Välj matchande personer att slå samman", - "city": "Stad", - "cleanup_confirm_description": "Immich hittade {count} material (skapade före {date} som säkerhetskopierats säkert till servern. Ta bort de lokala kopiorna från den här enheten?", - "cleanup_confirm_prompt_title": "Ta bort från den här enheten?", - "cleanup_deleted_assets": "Flyttade {count} material till enhetens papperskorg", - "cleanup_deleting": "Flyttar till papperskorg...", - "cleanup_found_assets": "Hittade {count} säkerhetskopierade material", - "cleanup_found_assets_with_size": "Hittade {count} säkerhetskopierade tillgångar ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud delade album exkluderas från skanningen", - "cleanup_no_assets_found": "Inga tillgångar hittades som matchar kriterierna ovan. Frigör utrymme kan bara ta bort tillgångar som har säkerhetskopierats till servern", - "cleanup_preview_title": "Material att ta bort {count}", - "cleanup_step3_description": "Skanna efter säkerhetskopierade tillgångar som matchar ditt datum och behåll inställningarna.", - "cleanup_step4_summary": "{count} tillgångar (skapade före {date}) att tas bort från din lokala enhet. Foton kommer att förbli tillgängliga från Immich-appen.", - "cleanup_trash_hint": "För att helt frigöra lagringsutrymme, öppna systemgalleriappen och töm papperskorgen", - "clear": "Rensa", - "clear_all": "Rensa allt", - "clear_all_recent_searches": "Rensa alla senaste sökningar", - "clear_file_cache": "Rensa filcachen", - "clear_message": "Rensa meddelande", - "clear_value": "Rensa värde", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Ange Lösenord", - "client_cert_import": "Importera", - "client_cert_import_success_msg": "Klientcertifikatet är importerat", - "client_cert_invalid_msg": "Felaktig certifikatfil eller fel lösenord", - "client_cert_remove_msg": "Klientcertifikatet är borttaget", - "client_cert_subtitle": "Stödjer endast formatet PKCS12 (.p12, .pfx). import/borttagning av certifikat är tillgängligt endast före inloggning", - "client_cert_title": "SSL klientcertifikat [EXPERIMENTELLT]", - "clockwise": "Medsols", - "close": "Stäng", - "collapse": "Kollapsa", - "collapse_all": "Kollapsa alla", - "color": "Färg", - "color_theme": "Färgtema", - "command": "Kommando", - "comment_deleted": "Kommentar raderad", - "comment_options": "Kommentarsalternativ", - "comments_and_likes": "Kommentarer & likes", - "comments_are_disabled": "Kommentarer är avstängda", - "common_create_new_album": "Skapa ett nytt album", - "completed": "Klar", - "confirm": "Bekräfta", - "confirm_admin_password": "Bekräfta administratörslösenord", - "confirm_delete_face": "Är du säker på att du vill ta bort {name}'s ansikte från objektet?", - "confirm_delete_shared_link": "Är du säker på att du vill ta bort den här delade länken?", - "confirm_keep_this_delete_others": "Alla tillgångar förutom den här tas bort från stacken. Är du säker på att du vill fortsätta?", - "confirm_new_pin_code": "Bekräfta ny PIN-kod", - "confirm_password": "Bekräfta lösenord", - "confirm_tag_face": "Vill du tagga det här ansiktet som {name}?", - "confirm_tag_face_unnamed": "Vill du tagga detta ansikte?", - "connected_device": "Ansluten enhet", - "connected_to": "Ansluten till", - "contain": "Anpassa", - "context": "Sammanhang", - "continue": "Fortsätt", - "control_bottom_app_bar_create_new_album": "Skapa nytt album", - "control_bottom_app_bar_delete_from_immich": "Ta bort från Immich", - "control_bottom_app_bar_delete_from_local": "Ta bort från enhet", - "control_bottom_app_bar_edit_location": "Redigera plats", - "control_bottom_app_bar_edit_time": "Redigera Datum & Tid", - "control_bottom_app_bar_share_link": "Dela Länk", - "control_bottom_app_bar_share_to": "Dela Till", - "control_bottom_app_bar_trash_from_immich": "Flytta till Papperskorgen", - "copied_image_to_clipboard": "Kopierade bilden till urklipp.", - "copied_to_clipboard": "Kopierat till urklipp!", - "copy_error": "Kopieringsfel", - "copy_file_path": "Kopiera filsökväg", - "copy_image": "Kopiera Bild", - "copy_link": "Kopiera länk", - "copy_link_to_clipboard": "Kopiera länken till urklipp", - "copy_password": "Kopiera lösenord", - "copy_to_clipboard": "Kopiera till Urklipp", - "country": "Land", - "cover": "Fyll", - "covers": "Omslag", - "create": "Skapa", - "create_album": "Skapa album", - "create_album_page_untitled": "Namnlös", - "create_api_key": "Skapa API-nyckel", - "create_first_workflow": "Skapa första arbetsflödet", - "create_library": "Skapa bibliotek", - "create_link": "Skapa länk", - "create_link_to_share": "Skapa länk att dela", - "create_link_to_share_description": "Låt alla med länken se de valda fotona", - "create_new": "SKAPA NY", - "create_new_person": "Skapa ny person", - "create_new_person_hint": "Tilldela valda objekt till en ny person", - "create_new_user": "Skapa en ny användare", - "create_shared_album_page_share_add_assets": "LÄGG TILL OBJEKT", - "create_shared_album_page_share_select_photos": "Välj bilder", - "create_shared_link": "Skapa delad länk", - "create_tag": "Skapa tagg", - "create_tag_description": "Skapa en ny tagg. För kapslade taggar anger du hela sökvägen för taggen inklusive snedstreck.", - "create_user": "Skapa användare", - "create_workflow": "Skapa arbetsflöde", - "created": "Skapad", - "created_at": "Skapad", - "creating_linked_albums": "Skapar länkade album...", - "crop": "Beskär", - "crop_aspect_ratio_fixed": "Fixat", - "crop_aspect_ratio_free": "Fritt", - "crop_aspect_ratio_original": "Original", - "curated_object_page_title": "Objekt", - "current_device": "Aktuell enhet", - "current_pin_code": "Nuvarande PIN-kod", - "current_server_address": "Aktuell server-adress", - "custom_date": "Anpassat datum", - "custom_locale": "Anpassad plats", - "custom_locale_description": "Formatera datum och siffror baserat på språket och regionen", - "custom_url": "Anpassad URL", - "cutoff_date_description": "Behåll bilder från…", - "cutoff_day": "{count, plural, one {dag} other {dagar}}", - "cutoff_year": "{count, plural, one {år} other {år}}", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Mörk", - "dark_theme": "Växla mörkt tema", - "date": "Datum", - "date_after": "Datum efter", - "date_and_time": "Datum och Tid", - "date_before": "Datum före", - "date_format": "E d. LLL y • hh:mm", - "date_of_birth_saved": "Födelsedatumet har sparats", - "date_range": "Datumintervall", - "day": "Dag", - "days": "Dagar", - "deduplicate_all": "Deduplicera alla", - "deduplication_criteria_1": "Bildstorlek i bytes", - "deduplication_criteria_2": "Räkning av EXIF-data", - "deduplication_info": "Dedupliceringsinformation", - "deduplication_info_description": "För att automatiskt välja filer och ta bort dubletter i bulk analyserar vi:", - "default_locale": "Standardplats", - "default_locale_description": "Formatera datum och siffror baserat på din webbläsares språkversion", - "delete": "Radera", - "delete_action_confirmation_message": "Är du säker på att du vill ta bort den här tillgången? Den här åtgärden flyttar tillgången till serverns papperskorg och frågar om du vill ta bort den lokalt", - "delete_action_prompt": "{count} raderade", - "delete_album": "Ta bort album", - "delete_api_key_prompt": "Är du säker på att du vill ta bort denna API-nyckel?", - "delete_dialog_alert": "Dessa objekt kommer att raderas permanent från Immich och din enhet", - "delete_dialog_alert_local": "Dessa objekt kommer att tas bort från din enhet men fortsatt vara tillgängliga på Immich-servern", - "delete_dialog_alert_local_non_backed_up": "Några objekt har inte säkerhetskopierats till Immich och kommer att tas bort permanent från din enhet", - "delete_dialog_alert_remote": "Dessa objekt kommer att tas bort permanent från Immich-servern", - "delete_dialog_ok_force": "Ta Bort Ändå", - "delete_dialog_title": "Radera permanent", - "delete_duplicates_confirmation": "Är du säker på att du vill ta bort dessa dubbletter permanent?", - "delete_face": "Ta bort ansikte", - "delete_key": "Ta bort nyckel", - "delete_library": "Ta bort bibliotek", - "delete_link": "Ta bort länk", - "delete_local_action_prompt": "{count} raderade lokalt", - "delete_local_dialog_ok_backed_up_only": "Ta Bara Bort Säkerhetskopierade", - "delete_local_dialog_ok_force": "Ta Bort Ändå", - "delete_others": "Radera fler", - "delete_permanently": "Ta bort permanent", - "delete_permanently_action_prompt": "{count} raderade permanent", - "delete_shared_link": "Ta bort delad länk", - "delete_shared_link_dialog_title": "Ta Bort Delad Länk", - "delete_tag": "Ta bort tagg", - "delete_tag_confirmation_prompt": "Är du säker på att du vill ta bort {tagName}-taggen?", - "delete_user": "Ta bort användare", - "deleted_shared_link": "Ta bort delad länk", - "deletes_missing_assets": "Tar bort objekt som saknas från disken", - "description": "Beskrivning", - "description_input_hint_text": "Lägg till beskrivning...", - "description_input_submit_error": "Fel vid uppdatering av beskrivning, se loggen för fler detaljer", - "deselect_all": "Avmarkera alla", - "details": "Detaljer", - "direction": "Riktning", - "disable": "inaktivera", - "disabled": "Inaktiverad", - "disallow_edits": "Tillåt inte redigeringar", - "discord": "Discord", - "discover": "Upptäck", - "discovered_devices": "Funna enheter", - "dismiss_all_errors": "Avvisa alla fel", - "dismiss_error": "Avvisa fel", - "display_options": "Visningsalternativ", - "display_order": "Visa Ordning", - "display_original_photos": "Visa originalfoton", - "display_original_photos_setting_description": "Föredrar att visa originalfotot när du visar en tillgång snarare än miniatyrbilder när den ursprungliga tillgången är webbkompatibel. Detta kan resultera i långsammare bildvisningshastigheter.", - "do_not_show_again": "Visa inte det här meddelandet igen", - "documentation": "Dokumentation", - "done": "Klart", - "download": "Ladda ner", - "download_action_prompt": "Laddar ner {count} resurser", - "download_canceled": "Nedladdning avbruten", - "download_complete": "Nedladdning slutförd", - "download_enqueue": "Nedladdning köad", - "download_error": "Fel vid nedladdning", - "download_failed": "Nedladdning misslyckades", - "download_finished": "Nedladdning klar", - "download_include_embedded_motion_videos": "Inbäddade videor", - "download_include_embedded_motion_videos_description": "Inkludera videor inbäddade i rörliga bilder som en separat fil", - "download_notfound": "Nedladdning kan inte hittas", - "download_original": "Ladda ner ursprunglig fil", - "download_paused": "Nedladdning pausad", - "download_settings": "Ladda ner", - "download_settings_description": "Hantera inställningar relaterade till nedladdning av objekt", - "download_started": "Nedladdning påbörjad", - "download_sucess": "Nedladdning lyckades", - "download_sucess_android": "Media har laddats ner till DCIM/Immich", - "download_waiting_to_retry": "Väntar på omförsök", - "downloading": "Laddar ner", - "downloading_asset_filename": "Laddar ned objekt {filename}", - "downloading_from_icloud": "Laddar ner från iCloud", - "downloading_media": "Laddar ner media", - "drop_files_to_upload": "Släpp filer var som helst för att ladda upp", - "duplicates": "Dubletter", - "duplicates_description": "Lös varje grupp genom att ange vilka, om några, är dubbletter", - "duration": "Varaktighet", - "edit": "Redigera", - "edit_album": "Redigera album", - "edit_avatar": "Redigera avatar", - "edit_birthday": "Redigera födelsedag", - "edit_date": "Redigera datum", - "edit_date_and_time": "Redigera datum och tid", - "edit_date_and_time_action_prompt": "{count} datum och tid redigerad", - "edit_date_and_time_by_offset": "Ändra datum med förskjutning", - "edit_date_and_time_by_offset_interval": "Nytt datumintervall: {from} - {to}", - "edit_description": "Redigera beskrivning", - "edit_description_prompt": "Vänligen välj en ny beskrivning:", - "edit_exclusion_pattern": "Redigera uteslutningsmönster", - "edit_faces": "Redigera ansikten", - "edit_key": "Redigera nyckel", - "edit_link": "Redigera länk", - "edit_location": "Redigera plats", - "edit_location_action_prompt": "{count} plats redigerad", - "edit_location_dialog_title": "Plats", - "edit_name": "Redigera namn", - "edit_people": "Redigera personer", - "edit_tag": "Redigera tagg", - "edit_title": "Redigera titel", - "edit_user": "Redigera användare", - "edit_workflow": "Redigera arbetsflöde", - "editor": "Redigerare", - "editor_close_without_save_prompt": "Ändringarna kommer inte att sparas", - "editor_close_without_save_title": "Stäng redigeraren?", - "editor_confirm_reset_all_changes": "Är du säker på att du vill återställa alla ändringar?", - "editor_flip_horizontal": "Vänd horisontellt", - "editor_flip_vertical": "Vänd vertikalt", - "editor_orientation": "Orientering", - "editor_reset_all_changes": "Återställ ändringar", - "editor_rotate_left": "Rotera 90° moturs", - "editor_rotate_right": "Rotera 90° medurs", - "email": "Epost", - "email_notifications": "E-postaviseringar", - "empty_folder": "Mappen är tom", - "empty_trash": "Töm papperskorg", - "empty_trash_confirmation": "Är du säker på att du vill tömma papperskorgen? Detta tar bort alla objekt i papperskorgen permanent från Immich.\nDu kan inte ångra den här åtgärden!", - "enable": "Aktivera", - "enable_backup": "Aktivera säkerhetskopiering", - "enable_biometric_auth_description": "Skriv in din pinkod för att aktivera biometrisk autentisering", - "enabled": "Aktiverad", - "end_date": "Slutdatum", - "enqueued": "Köad", - "enter_wifi_name": "Ange Wi-Fi-namn", - "enter_your_pin_code": "Skriv in din pinkod", - "enter_your_pin_code_subtitle": "Skriv in din pinkod för att komma åt låst mapp", - "error": "Fel", - "error_change_sort_album": "Kunde inte ändra sorteringsordning för album", - "error_delete_face": "Fel uppstod när ansikte skulle tas bort från objektet", - "error_getting_places": "Det gick inte att hämta platser", - "error_loading_albums": "Fel vid laddning av album", - "error_loading_image": "Fel vid bildladdning", - "error_loading_partners": "Fel vid inläsning av partner: {error}", - "error_retrieving_asset_information": "Fel vid hämtning av tillgångsinformation", - "error_saving_image": "Fel: {error}", - "error_tag_face_bounding_box": "Fel vid taggning av ansikte – kan inte hämta koordinater för begränsningsruta", - "error_title": "Fel – något gick fel", - "error_while_navigating": "Fel vid navigering till objektet", - "errors": { - "cannot_navigate_next_asset": "Det går inte att navigera till nästa objekt", - "cannot_navigate_previous_asset": "Det går inte att navigera till föregående objekt", - "cant_apply_changes": "Det går inte att tillämpa ändringar", - "cant_change_activity": "Kan inte {enabled, select, true {avaktivera} other {aktivera}} aktivitet", - "cant_change_asset_favorite": "Det går inte att byta favorit mot objekt", - "cant_change_metadata_assets_count": "Det går inte att ändra metadata för {count, plural, one {# asset} other {# assets}}", - "cant_get_faces": "Kan inte få ansikten", - "cant_get_number_of_comments": "Kan inte få antal kommentarer", - "cant_search_people": "Kan inte söka efter personer", - "cant_search_places": "Kan inte söka platser", - "error_adding_assets_to_album": "Det gick inte att lägga till objekt i albumet", - "error_adding_users_to_album": "Det gick inte att lägga till användare till albumet", - "error_deleting_shared_user": "Det gick inte att ta bort delad användare", - "error_downloading": "Fel vid nedladdning av {filename}", - "error_hiding_buy_button": "Det gick inte att dölja köpknappen", - "error_removing_assets_from_album": "Det gick inte att ta bort objekt från albumet, kontrollera konsolen för mer information", - "error_selecting_all_assets": "Fel vid val av alla objekt", - "exclusion_pattern_already_exists": "Detta uteslutningsmönster finns redan.", - "failed_to_create_album": "Det gick inte att skapa album", - "failed_to_create_shared_link": "Det gick inte att skapa delad länk", - "failed_to_edit_shared_link": "Det gick inte att redigera delad länk", - "failed_to_get_people": "Det gick inte att hämta personer", - "failed_to_keep_this_delete_others": "Misslyckades att behålla detta objekt radera övriga objekt", - "failed_to_load_asset": "Det gick inte att ladda objekt", - "failed_to_load_assets": "Det gick inte att ladda objekten", - "failed_to_load_notifications": "Misslyckades med att ladda notifikationer", - "failed_to_load_people": "Det gick inte att ladda personer", - "failed_to_remove_product_key": "Det gick inte att ta bort produktnyckeln", - "failed_to_reset_pin_code": "Misslyckades med att återställa PIN-koden", - "failed_to_stack_assets": "Det gick inte att stapla objekt", - "failed_to_unstack_assets": "Det gick inte att avstapla objekt", - "failed_to_update_notification_status": "Misslyckades med att uppdatera aviseringens status", - "incorrect_email_or_password": "Felaktig e-postadress eller lösenord", - "library_folder_already_exists": "Denna sökväg är redan importerad.", - "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} misslyckades valideringen", - "profile_picture_transparent_pixels": "Profilbilder kan inte ha genomskinliga pixlar. Zooma in och/eller flytta bilden.", - "quota_higher_than_disk_size": "Du har angett en kvot som är högre än diskstorleken", - "something_went_wrong": "Något gick fel", - "unable_to_add_album_users": "Kunde inte lägga till använder i album", - "unable_to_add_assets_to_shared_link": "Det går inte att lägga till objekt till delad länk", - "unable_to_add_comment": "Kunde inte lägga till kommentar", - "unable_to_add_exclusion_pattern": "Det gick inte att lägga till uteslutningsmönster", - "unable_to_add_partners": "Kunde inte lägga till partners", - "unable_to_add_remove_archive": "Det går inte att {archived, select, true {ta bort objekt från} other {lägga till objekt till}} arkiv", - "unable_to_add_remove_favorites": "Det går inte att {favorite, select, true {add asset to} other {remove asset from}} favoriter", - "unable_to_archive_unarchive": "Det går inte att {archived, select, true {archive} other {archive}}", - "unable_to_change_album_user_role": "Kunde inte ändra albumanvändarens roll", - "unable_to_change_date": "Kunde inte ändra datum", - "unable_to_change_description": "Kunde inte ändra beskrivning", - "unable_to_change_favorite": "Det går inte att ändra favorit för objekt", - "unable_to_change_location": "Kunde inte ändra plats", - "unable_to_change_password": "Det går inte att ändra lösenord", - "unable_to_change_visibility": "Det gick inte att ändra synligheten för {count, plural, one {# person} other {# people}}", - "unable_to_complete_oauth_login": "Det gick inte att slutföra OAuth-inloggning", - "unable_to_connect": "Det går inte att ansluta", - "unable_to_copy_to_clipboard": "Kan inte kopiera till urklipp, se till att du kommer åt sidan via https", - "unable_to_create": "Det gick inte att skapa arbetsflöde", - "unable_to_create_admin_account": "Det gick inte att skapa ett administratörskonto", - "unable_to_create_api_key": "Det gick inte att skapa en ny API-nyckel", - "unable_to_create_library": "Kunde inte skapa bibliotek", - "unable_to_create_user": "Kunde inte skapa användare", - "unable_to_delete_album": "Kunde inte ta bort album", - "unable_to_delete_asset": "Det gick inte att ta bort objekt", - "unable_to_delete_assets": "Det gick inte att ta bort objekt", - "unable_to_delete_exclusion_pattern": "Det gick inte att ta bort uteslutningsmönster", - "unable_to_delete_shared_link": "Det gick inte att ta bort delad länk", - "unable_to_delete_user": "Kunde inte ta bort användare", - "unable_to_delete_workflow": "Det gick inte att ta bort arbetsflödet", - "unable_to_download_files": "Det går inte att ladda ner filer", - "unable_to_edit_exclusion_pattern": "Det gick inte att redigera uteslutningsmönster", - "unable_to_empty_trash": "Kunde inte tömma papperskorgen", - "unable_to_enter_fullscreen": "Kunde inte växla till fullskärm", - "unable_to_exit_fullscreen": "Kunde inte avsluta fullskärm", - "unable_to_get_comments_number": "Det gick inte att hämta antalet kommentarer", - "unable_to_get_shared_link": "Det gick inte att hämta delad länk", - "unable_to_hide_person": "Det går inte att dölja personen", - "unable_to_link_motion_video": "Det går inte att länka rörlig video", - "unable_to_link_oauth_account": "Det gick inte att länka OAuth-kontot", - "unable_to_log_out_all_devices": "Det gick inte att logga ut alla enheter", - "unable_to_log_out_device": "Det gick inte att logga ut enheten", - "unable_to_login_with_oauth": "Det gick inte att logga in med OAuth", - "unable_to_play_video": "Kunde inte spela upp video", - "unable_to_reassign_assets_existing_person": "Det går inte att tilldela om tillgångar till {name, select, null {an existing person} other {{name}}}", - "unable_to_reassign_assets_new_person": "Kunde inte tilldela objekt till en annan person", - "unable_to_refresh_user": "Kunde inte ladda om användaren", - "unable_to_remove_album_users": "Kunde inte ta bort personen från albumet", - "unable_to_remove_api_key": "Det gick inte att ta bort API Keyet", - "unable_to_remove_assets_from_shared_link": "Kunde inte ta bort objekt från delade länkar", - "unable_to_remove_library": "Kunde inte ta bort bibliotek", - "unable_to_remove_partner": "Kunde inte ta bort partner", - "unable_to_remove_reaction": "Kunde inte ta bort reaktion", - "unable_to_reset_password": "Kunde inte återställa lösenord", - "unable_to_reset_pin_code": "Kunde inte återställa pinkod", - "unable_to_resolve_duplicate": "Det går inte att lösa dubbletter", - "unable_to_restore_assets": "Det går inte att återställa tillgångar", - "unable_to_restore_trash": "Det gick inte att återställa papperskorgen", - "unable_to_restore_user": "Kunde inte återställa användare", - "unable_to_save_album": "Kunde inte spara album", - "unable_to_save_api_key": "Det går inte att spara API Nyckel", - "unable_to_save_date_of_birth": "Det går inte att spara födelsedatum", - "unable_to_save_name": "Kunde inte spara namn", - "unable_to_save_profile": "Kunde inte spara profil", - "unable_to_save_settings": "Kunde inte spara inställningar", - "unable_to_scan_libraries": "Det går inte att söka igenom bibliotek", - "unable_to_scan_library": "Det går inte att skanna biblioteket", - "unable_to_set_feature_photo": "Det går inte att ställa in funktionsfoto", - "unable_to_set_profile_picture": "Det går inte att ställa in profilbilden", - "unable_to_set_rating": "Det gick inte att sätta betyg", - "unable_to_submit_job": "Det går inte att skicka jobbet", - "unable_to_trash_asset": "Det går inte att slänga resursen", - "unable_to_unlink_account": "Det går inte att ta bort länken till kontot", - "unable_to_unlink_motion_video": "Det går inte att ta bort länken till rörelsevideo", - "unable_to_update_album_cover": "Det går inte att uppdatera albumomslaget", - "unable_to_update_album_info": "Det går inte att uppdatera albuminformationen", - "unable_to_update_library": "Kunde inte uppdatera bibliotek", - "unable_to_update_location": "Kunde inte uppdatera plats", - "unable_to_update_settings": "Kunde inte uppdatera inställningar", - "unable_to_update_timeline_display_status": "Det går inte att uppdatera visningsstatus för tidslinjen", - "unable_to_update_user": "Kunde inte uppdatera användare", - "unable_to_update_workflow": "Det gick inte att uppdatera arbetsflödet", - "unable_to_upload_file": "Det går inte att ladda upp filen" - }, - "errors_text": "Fel", - "exclusion_pattern": "Exkluderingsmönster", - "exif": "Exif", - "exif_bottom_sheet_description": "Lägg till beskrivning...", - "exif_bottom_sheet_description_error": "Fel vid uppdatering av beskrivningen", - "exif_bottom_sheet_details": "DETALJER", - "exif_bottom_sheet_location": "PLATS", - "exif_bottom_sheet_no_description": "Ingen beskrivning", - "exif_bottom_sheet_people": "PERSONER", - "exif_bottom_sheet_person_add_person": "Lägg till namn", - "exit_slideshow": "Avsluta bildspel", - "expand_all": "Expandera alla", - "experimental_settings_new_asset_list_subtitle": "Under uppbyggnad", - "experimental_settings_new_asset_list_title": "Aktivera experimentellt fotorutnät", - "experimental_settings_subtitle": "Använd på egen risk!", - "experimental_settings_title": "Experimentellt", - "expire_after": "Går ut efter", - "expired": "Gått ut", - "expires_date": "Går ut {date}", - "explore": "Utforska", - "explorer": "Utforskare", - "export": "Exportera", - "export_as_json": "Exportera som JSON", - "export_database": "Exportera databas", - "export_database_description": "Exportera SQLite-databasen", - "extension": "Förlängning", - "external": "Externt", - "external_libraries": "Externa Bibliotek", - "external_network": "Externt nätverk", - "external_network_sheet_info": "När appen inte är ansluten till det föredragna Wi-Fi-nätverket, kommer den att ansluta till servern via den första av följande URL:er den kan nå, från toppen till botten", - "face_unassigned": "Otilldelade", - "failed": "Misslyckades", - "failed_count": "Misslyckade: {count}", - "failed_to_authenticate": "Misslyckades med autentisering", - "failed_to_load_assets": "Det gick inte att läsa in resurser", - "failed_to_load_folder": "Kunde inte ladda mappen", - "favorite": "Favorit", - "favorite_action_prompt": "{count} har lagts till i favoriter", - "favorite_or_unfavorite_photo": "Favoritfoto eller icke-favoritfoto", - "favorites": "Favoriter", - "favorites_page_no_favorites": "Inga favoritobjekt hittades", - "feature_photo_updated": "Funktionsfoto uppdaterad", - "features": "Funktioner", - "features_in_development": "Funktioner i utveckling", - "features_setting_description": "Hantera appens funktioner", - "file_name_or_extension": "Filnamn eller -tillägg", - "file_size": "Filstorlek", - "filename": "Filnamn", - "filetype": "Filtyp", - "filter": "Filter", - "filter_description": "Villkor för att filtrera måltillgångarna", - "filter_people": "Filtrera personer", - "filter_places": "Filtrera platser", - "filters": "Filter", - "find_them_fast": "Hitta dem snabbt efter namn med sök", - "first": "Först", - "fix_incorrect_match": "Fixa inkorrekt matchning", - "folder": "Mapp", - "folder_not_found": "Mappen hittade inte", - "folders": "Mappar", - "folders_feature_description": "Bläddra i mappvyn för foton och videoklipp i filsystemet", - "forgot_pin_code_question": "Glömt din pinkod?", - "forward": "Framåt", - "free_up_space": "Frigör utrymme", - "free_up_space_description": "Flytta säkerhetskopierade foton och videor till din enhets papperskorg för att frigöra utrymme. Dina kopior på servern förblir säkra.", - "free_up_space_settings_subtitle": "Frigör lagringsutrymme på enheten", - "full_path": "Fullständig sökväg: {path}", - "gcast_enabled": "Google-Cast", - "gcast_enabled_description": "Denna funktion läser in externa resurser från Google för att fungera.", - "general": "Allmänt", - "geolocation_instruction_location": "Klicka på en tillgång med GPS-koordinater för att använda dess plats, eller välj en plats direkt från kartan", - "get_help": "Få hjälp", - "get_people_error": "Fel vid hämtning av personer", - "get_wifiname_error": "Kunde inte hämta Wi-Fi-namn. Säkerställ att du tillåtit nödvändiga rättigheter och är ansluten till ett Wi-Fi-nätverk", - "getting_started": "Komma igång", - "go_back": "Gå tillbaka", - "go_to_folder": "Gå till mapp", - "go_to_search": "Gå till sök", - "gps": "GPS", - "gps_missing": "Ingen GPS", - "grant_permission": "Ge tillåtelse", - "group_albums_by": "Gruppera album efter...", - "group_country": "Gruppera per land", - "group_no": "Ingen gruppering", - "group_owner": "Grupper efter ägare", - "group_places_by": "Gruppera platser efter…", - "group_year": "Gruppera efter årtal", - "haptic_feedback_switch": "Aktivera haptisk feedback", - "haptic_feedback_title": "Haptisk Feedback", - "has_quota": "Har kvot", - "hash_asset": "Hash-tillgång", - "hashed_assets": "Hash-tillgångar", - "hashing": "Hashning", - "header_settings_add_header_tip": "Lägg till header", - "header_settings_field_validator_msg": "Värdet kan inte vara tomt", - "header_settings_header_name_input": "Header-namn", - "header_settings_header_value_input": "Header-värde", - "headers_settings_tile_title": "Anpassade proxy-headers", - "height": "Höjd", - "hi_user": "Hej {name} ({email})", - "hide_all_people": "Göm alla personer", - "hide_gallery": "Dölj galleri", - "hide_named_person": "Göm personen {name}", - "hide_password": "Dölj lösenord", - "hide_person": "Dölj person", - "hide_schema": "Göm schema", - "hide_text_recognition": "Dölj textigenkänning", - "hide_unnamed_people": "Göm personer utan namn", - "home_page_add_to_album_conflicts": "Lade till {added} foton och videor i albumet {album}. {failed} foton och videor finns redan i albumet.", - "home_page_add_to_album_err_local": "Kan inte lägga till lokala objekt till album ännu, hoppar över", - "home_page_add_to_album_success": "Lade till {added} foton och videor i albumet {album}.", - "home_page_album_err_partner": "Kan inte lägga till partner-objekt till album ännu, hoppar över", - "home_page_archive_err_local": "Kan inte arkivera lokala objekt ännu, hoppar över", - "home_page_archive_err_partner": "Kan inte arkivera partner-objekt, hoppar över", - "home_page_building_timeline": "Bygger tidslinjen", - "home_page_delete_err_partner": "Kan inte ta bort partner-objekt, hoppar över", - "home_page_delete_remote_err_local": "Lokala objekt i urvalet för att ta bort från servern, hoppar över", - "home_page_favorite_err_local": "Kan inte favorisera lokala objekt ännu, hoppar över", - "home_page_favorite_err_partner": "Kan inte favorisera partner-objekt ännu, hoppar över", - "home_page_first_time_notice": "Om det här är första gången du använder appen, välj ett eller flera backup-album så att tidslinjen kan fyllas med foton och videor från albumen", - "home_page_locked_error_local": "Kan inte flytta lokala resurser till låst mapp, hoppar över", - "home_page_locked_error_partner": "Kan inte flytta partnerresurser till låst mapp, hoppar över", - "home_page_share_err_local": "Kan inte dela lokalt objekt via länk, hoppar över", - "home_page_upload_err_limit": "Kan bara ladda upp max 30 objekt åt gången, hoppar över", - "host": "Värd", - "hour": "Timme", - "hours": "Timmar", - "id": "ID", - "idle": "Inaktiv", - "ignore_icloud_photos": "Ignorera iCloud-foton", - "ignore_icloud_photos_description": "Foton lagrade i iCloud kommer inte laddas upp till Immich-servern", - "image": "Bild", - "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} tagen {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tagen med {person1} den {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1} och {person2} den {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1}, {person2}, och {person3} den {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1}, {person2}, och {additionalCount, number} andra den {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} den {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1} den {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1} och {person2} den {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1}, {person2}, och {person3} den {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1}, {person2}, och {additionalCount, number} andre den {date}", - "image_saved_successfully": "Bild sparad", - "image_viewer_page_state_provider_download_started": "Nedladdning Påbörjad", - "image_viewer_page_state_provider_download_success": "Nedladdningen Lyckades", - "image_viewer_page_state_provider_share_error": "Delningsfel", - "immich_logo": "Immich-logga", - "immich_web_interface": "Immich Web gränssnitt", - "import_from_json": "Importera från JSON", - "import_path": "Importsökväg", - "in_albums": "I {count, plural, one {# album} other {# albums}}", - "in_archive": "I arkivet", - "in_year": "I {year}", - "in_year_selector": "In", - "include_archived": "Inkludera arkiverade", - "include_shared_albums": "Inkludera delade album", - "include_shared_partner_assets": "Inkludera delade partners tillgångar", - "individual_share": "Enskild delning", - "individual_shares": "Individuella delningar", - "info": "Information", - "interval": { - "day_at_onepm": "Alla dagar vid kl 13.00", - "hours": "Vid varje {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "Varje natt vid midnatt", - "night_at_twoam": "Varje natt vid kl 02.00" - }, - "invalid_date": "Felaktigt datum", - "invalid_date_format": "Felaktigt datumformat", - "invite_people": "Bjud in personer", - "invite_to_album": "Bjuder in till album", - "ios_debug_info_fetch_ran_at": "Hämtning kördes {dateTime}", - "ios_debug_info_last_sync_at": "Senaste synkning {dateTime}", - "ios_debug_info_no_processes_queued": "Inga bakgrundsprocesser köade", - "ios_debug_info_no_sync_yet": "Inget bakgrundssynkroniseringsjobb har körts ännu", - "ios_debug_info_processes_queued": "{count, plural, one {{count} bakgrundsprocess köad} other {{count} bakgrundsprocesser köade}}", - "ios_debug_info_processing_ran_at": "Bearbetningen kördes {dateTime}", - "items_count": "{count, plural, one {# objekt} other {# objekt}}", - "jobs": "Jobb", - "json_editor": "JSON-redigerare", - "json_error": "JSON-fel", - "keep": "Behåll", - "keep_albums": "Behåll album", - "keep_albums_count": "Behåller {count} {count, plural, one {album} other {album}}", - "keep_all": "Behåll alla", - "keep_description": "Välj vad som stannar kvar på din enhet när du frigör utrymme.", - "keep_favorites": "Behåll favoriter", - "keep_on_device": "Behåll på enhet", - "keep_on_device_hint": "Välj objekt som ska behållas på denna enhet", - "keep_this_delete_others": "Behåll denna, radera övriga", - "keeping": "Behåller: {items}", - "kept_this_deleted_others": "Behåll denna tillgång och borttagna {count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "Kortkommandon", - "language": "Språk", - "language_no_results_subtitle": "Försök att justera ditt sökord", - "language_no_results_title": "Inga språk funna", - "language_search_hint": "Sök språk…", - "language_setting_description": "Välj önskat språk", - "large_files": "Stora filer", - "last": "Sista", - "last_months": "{count, plural, one {Senaste månaden} other {Senaste # månaderna}}", - "last_seen": "Senast sedd", - "latest_version": "Senaste versionen", - "latitude": "Latitud", - "leave": "Lämna", - "leave_album": "Lämna albumet", - "lens_model": "Objektiv", - "let_others_respond": "Låt andra svara", - "level": "Nivå", - "library": "Bibliotek", - "library_add_folder": "Lägg till mapp", - "library_edit_folder": "Redigera mapp", - "library_options": "Nivå alternativ", - "library_page_device_albums": "Album på Enheten", - "library_page_new_album": "Nytt album", - "library_page_sort_asset_count": "Antal objekt", - "library_page_sort_created": "Senast skapat", - "library_page_sort_last_modified": "Senast ändrad", - "library_page_sort_title": "Albumtitel", - "licenses": "Licenser", - "light": "Ljus", - "like": "Gilla", - "like_deleted": "Gilla borttagen", - "link_motion_video": "Länka rörlig video", - "link_to_oauth": "Länk till OAuth", - "linked_oauth_account": "Länkat OAuth konto", - "list": "Lista", - "loading": "Inläsning", - "loading_search_results_failed": "Det gick inte att läsa in sökresultat", - "local": "Lokalt", - "local_asset_cast_failed": "Det går inte att casta en tillgång som inte har laddats upp till servern", - "local_assets": "Lokala tillgångar", - "local_id": "Lokalt ID", - "local_media_summary": "Sammanfattning av lokala medier", - "local_network": "Lokalt nätverk", - "local_network_sheet_info": "Appen kommer ansluta till servern via denna URL när det specificerade WiFi-nätverket används", - "location": "Plats", - "location_permission": "Platsrättighet", - "location_permission_content": "För att använda funktionen för automatisk växling behöver Immich behörighet till exakt plats så att appen kan läsa av det aktuella Wi-Fi-nätverkets namn", - "location_picker_choose_on_map": "Välj på karta", - "location_picker_latitude_error": "Ange en giltig latitud", - "location_picker_latitude_hint": "Ange din latitud här", - "location_picker_longitude_error": "Ange en giltig longitud", - "location_picker_longitude_hint": "Ange din longitud här", - "lock": "Lås", - "locked_folder": "Låst Mapp", - "log_detail_title": "Loggdetalj", - "log_out": "Logga ut", - "log_out_all_devices": "Logga ut alla enheter", - "logged_in_as": "Inloggad som {user}", - "logged_out_all_devices": "Loggat ut från alla enheter", - "logged_out_device": "Loggat ut enheten", - "login": "Logga in", - "login_disabled": "Inloggning har inaktiverats", - "login_form_api_exception": "API-undantag. Kontrollera server-URL:en och försök igen.", - "login_form_back_button_text": "Bakåt", - "login_form_email_hint": "din.email@email.com", - "login_form_endpoint_hint": "http://din-server-ip:port", - "login_form_endpoint_url": "Slutpunkt-URL för server", - "login_form_err_http": "Var god ange http:// eller https://", - "login_form_err_invalid_email": "Ogiltig email", - "login_form_err_invalid_url": "Ogiltig webbadress", - "login_form_err_leading_whitespace": "Mellanrum före", - "login_form_err_trailing_whitespace": "Mellanrum efter", - "login_form_failed_get_oauth_server_config": "Kunde inte logga in med OAuth. Kontrollera serverns webbadress", - "login_form_failed_get_oauth_server_disable": "OAuth är inte tillgänglig på den här servern", - "login_form_failed_login": "Kunde inte logga in. Kontrollera serverns webbadress, email och lösenord", - "login_form_handshake_exception": "Ett Undantag vid Handskakning med servern har skett. Aktivera stöd för självsignerade certifikat i inställningar om du använder ett självsignerat certifikat.", - "login_form_password_hint": "lösenord", - "login_form_save_login": "Håll mig inloggad", - "login_form_server_empty": "Ange en server-URL.", - "login_form_server_error": "Kunde inte ansluta till servern.", - "login_has_been_disabled": "Inloggning har blivit inaktiverat.", - "login_password_changed_error": "Ett fel uppstod vid uppdatering av ditt lösenord", - "login_password_changed_success": "Uppdatering av lösenord lyckades", - "logout_all_device_confirmation": "Är du säker på att du vill logga ut från alla enheter?", - "logout_this_device_confirmation": "Är du säker på att du vill logga ut från denna enhet?", - "logs": "Loggar", - "longitude": "Longitud", - "look": "Titta", - "loop_videos": "Loopa videor", - "loop_videos_description": "Aktivera för att automatiskt loopa en video i detaljvisaren.", - "main_branch_warning": "Du använder en utvecklingsversion. Vi rekommenderar starkt att du använder en utgiven version!", - "main_menu": "Huvudmeny", - "maintenance_action_restore": "Återställer databasen", - "maintenance_description": "Immich har försatts i underhållsläge.", - "maintenance_end": "Avsluta underhållsläge", - "maintenance_end_error": "Misslyckades att avsluta underhållsläge.", - "maintenance_logged_in_as": "För närvarande inloggad som {user}", - "maintenance_restore_from_backup": "Återställ från säkerhetskopia", - "maintenance_restore_library": "Återställ ditt bibliotek", - "maintenance_restore_library_confirm": "Om detta ser bra ut, fortsätt med att återställa säkerhetskopian!", - "maintenance_restore_library_description": "Återställer databasen", - "maintenance_restore_library_folder_has_files": "{folder} har {count} mapp(ar)", - "maintenance_restore_library_folder_no_files": "{folder} saknar filer!", - "maintenance_restore_library_folder_pass": "läsbar och skrivbar", - "maintenance_restore_library_folder_read_fail": "inte läsbar", - "maintenance_restore_library_folder_write_fail": "inte skrivbar", - "maintenance_restore_library_hint_missing_files": "Du kanske saknar viktiga filer", - "maintenance_restore_library_hint_regenerate_later": "Du kan återställa dessa senare i inställningarna", - "maintenance_restore_library_hint_storage_template_missing_files": "Använder du en lagringsmall? Du kanske saknar filer", - "maintenance_restore_library_loading": "Laddar integritetskontroller och heuristik…", - "maintenance_task_backup": "Skapar en säkerhetskopia av den befintliga databasen…", - "maintenance_task_migrations": "Kör databasmigreringar…", - "maintenance_task_restore": "Återställer den valda säkerhetskopian…", - "maintenance_task_rollback": "Återställningen misslyckades, återgår till återställningspunkt…", - "maintenance_title": "Tillfälligt otillgänglig", - "make": "Tillverkare", - "manage_geolocation": "Hantera plats", - "manage_media_access_rationale": "Denna behörighet krävs för korrekt hantering av att flytta tillgångar till papperskorgen och återställa dem från den.", - "manage_media_access_settings": "Öppna inställningar", - "manage_media_access_subtitle": "Tillåt Immich-appen att hantera och flytta mediefiler.", - "manage_media_access_title": "Åtkomst till mediehantering", - "manage_shared_links": "Hantera Delade länkar", - "manage_sharing_with_partners": "Hantera delning med partner", - "manage_the_app_settings": "Hantera appinställningarna", - "manage_your_account": "Hantera ditt konto", - "manage_your_api_keys": "Hantera dina API-nycklar", - "manage_your_devices": "Hantera dina inloggade enheter", - "manage_your_oauth_connection": "Hantera din OAuth-anslutning", - "map": "Karta", - "map_assets_in_bounds": "{count, plural, =0 {Inga foton i detta område} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "Kan inte hämta användarens plats", - "map_location_dialog_yes": "Ja", - "map_location_picker_page_use_location": "Använd den här platsen", - "map_location_service_disabled_content": "Platstjänst måste vara aktiverad för att visa objekt från din nuvarande plats. Vill du aktivera den nu?", - "map_location_service_disabled_title": "Platstjänst inaktiverad", - "map_marker_for_images": "Kartmarkering för bilder tagna i {city}, {country}", - "map_marker_with_image": "Kartmarkör med bild", - "map_no_location_permission_content": "Platsrättighet är nödvändigt för att kunna visa objekt från din nuvarande plats. Vill du tillåta det nu?", - "map_no_location_permission_title": "Platsrättighet nekad", - "map_settings": "Kartinställningar", - "map_settings_dark_mode": "Mörkt tema", - "map_settings_date_range_option_day": "Senaste 24 timmarna", - "map_settings_date_range_option_days": "Senaste {days} dagarna", - "map_settings_date_range_option_year": "Senaste året", - "map_settings_date_range_option_years": "Senaste {years} åren", - "map_settings_dialog_title": "Kartinställningar", - "map_settings_include_show_archived": "Inkludera Arkiverade", - "map_settings_include_show_partners": "Inkludera Partners", - "map_settings_only_show_favorites": "Visa Endast Favoriter", - "map_settings_theme_settings": "Kart-tema", - "map_zoom_to_see_photos": "Zooma ut för att se foton", - "mark_all_as_read": "Markera alla som lästa", - "mark_as_read": "Markera som läst", - "marked_all_as_read": "Markerade alla som lästa", - "matches": "Matchar", - "matching_assets": "Matchande tillgångar", - "media_type": "Mediatyp", - "memories": "Minnen", - "memories_all_caught_up": "Du är ikapp", - "memories_check_back_tomorrow": "Kom tillbaka imorgon för fler minnen", - "memories_setting_description": "Hantera det du ser i dina minnen", - "memories_start_over": "Börja Om", - "memories_swipe_to_close": "Svep upp för att stänga", - "memory": "Minne", - "memory_lane_title": "Återupplev {title}", - "menu": "Meny", - "merge": "Sammanfoga", - "merge_people": "Sammanfoga personer", - "merge_people_limit": "Du kan maximalt sammanfoga 5 ansikten på samma gång", - "merge_people_prompt": "Vill du sammanfoga dessa personer? Denna handling är oåterkallelig.", - "merge_people_successfully": "Personer sammanfogades framgångsrikt", - "merged_people_count": "Sammanfogat {count, plural, one {# person} other {# people}}", - "minimize": "Minimera", - "minute": "Minut", - "minutes": "Minuter", - "mirror_horizontal": "Horisontell", - "mirror_vertical": "Vertikallt", - "missing": "Saknade", - "mobile_app": "Mobilapp", - "mobile_app_download_onboarding_note": "Ladda ner den medföljande mobilappen med följande alternativ", - "model": "Modell", - "month": "Månad", - "monthly_title_text_date_format": "MMMM y", - "more": "Mer", - "move": "Flytta", - "move_down": "Flytta nedåt", - "move_off_locked_folder": "Flytta från låst mapp", - "move_to": "Flytta till", - "move_to_device_trash": "Flytta till enhetens papperskorg", - "move_to_lock_folder_action_prompt": "{count} adderades till låst mapp", - "move_to_locked_folder": "Flytta till låst mapp", - "move_to_locked_folder_confirmation": "Dessa foton och videor kommer tas bort från alla album och går endast se i låsta mappen", - "move_up": "Flytta uppåt", - "moved_to_archive": "Flyttade {count, plural, one {# resurs} other {# assets}} till arkivet", - "moved_to_library": "\"Flyttade {count, plural, one {# asset} other {# assets}} till biblioteket.\"", - "moved_to_trash": "Flyttad till papperskorgen", - "multiselect_grid_edit_date_time_err_read_only": "Kan inte ändra datum på skrivskyddade objekt, hoppar över", - "multiselect_grid_edit_gps_err_read_only": "Kan inte ändra plats på skrivskyddade objekt, hoppar över", - "mute_memories": "Tysta minnen", - "my_albums": "Mina album", - "name": "Namn", - "name_or_nickname": "Namn eller smeknamn", - "name_required": "Namn krävs", - "navigate": "Navigera", - "navigate_to_time": "Navigera till tid", - "network_requirement_photos_upload": "Använd mobildata för att säkerhetskopiera foton", - "network_requirement_videos_upload": "Använd mobildata för att säkerhetskopiera videor", - "network_requirements": "Nätverkskrav", - "network_requirements_updated": "Nätverkskraven har ändrats, återställer säkerhetskopieringskön", - "networking_settings": "Nätverk", - "networking_subtitle": "Hantera inställningar för server-endpointen", - "never": "aldrig", - "new_album": "Nytt album", - "new_api_key": "Ny API-nyckel", - "new_date_range": "Nytt datumintervall", - "new_password": "Nytt lösenord", - "new_person": "Ny person", - "new_pin_code": "Ny PIN-kod", - "new_pin_code_subtitle": "Det här är första gången du öppnar den låsta mappen. Skapa en PIN-kod för att säkert få åtkomst till den här sidan", - "new_timeline": "Ny tidslinje", - "new_update": "Ny uppdatering", - "new_user_created": "Ny användare skapad", - "new_version_available": "NY VERSION TILLGÄNGLIG", - "newest_first": "Nyast först", - "next": "Nästa", - "next_memory": "Nästa minne", - "no": "Nej", - "no_actions_added": "Inga åtgärder tillagda än", - "no_albums_found": "Inga album hittades", - "no_albums_message": "Skapa ett album för att organisera dina foton och videor", - "no_albums_with_name_yet": "Du verkar inte ha några album med det här namnet ännu.", - "no_albums_yet": "Det ser ut som att du inte har några album ännu.", - "no_archived_assets_message": "Arkivera bilder och videor för att dölja dem från bild-vyn", - "no_assets_message": "Kicka för att ladda upp din första bild", - "no_assets_to_show": "Inga objekt att visa", - "no_cast_devices_found": "Inga Cast-enheter hittades", - "no_checksum_local": "Ingen kontrollsumma tillgänglig - kan inte hämta lokala tillgångar", - "no_checksum_remote": "Ingen kontrollsumma tillgänglig - kan inte hämta fjärrtillgång", - "no_configuration_needed": "Ingen konfiguration behövs", - "no_devices": "Inga auktoriserade enheter", - "no_duplicates_found": "Inga dubbletter hittades.", - "no_exif_info_available": "Exif-information ej tillgänglig", - "no_explore_results_message": "Ladda upp fler bilder för att utforska din samling.", - "no_favorites_message": "Lägg till favoriter för att snabbt hitta dina bästa bilder och videor", - "no_filters_added": "Inga filter tillagda än", - "no_libraries_message": "Skapa ett externt bibliotek för att se dina bilder och videor", - "no_local_assets_found": "Inga lokala tillgångar hittades med denna kontrollsumma", - "no_location_set": "Ingen plats satt", - "no_locked_photos_message": "Foton och videor i den låsta mappen är dolda och visas inte när du bläddrar eller söker i ditt bibliotek.", - "no_name": "Inget namn", - "no_notifications": "Inga aviseringar", - "no_people_found": "Inga matchande personer hittade", - "no_places": "Inga platser", - "no_remote_assets_found": "Inga fjärrtillgångar hittades med denna kontrollsumma", - "no_results": "Inga resultat", - "no_results_description": "Pröva en synonym eller ett annat mer allmänt sökord", - "no_shared_albums_message": "Skapa ett album för att dela bilder och videor med andra personer", - "no_uploads_in_progress": "Inga uppladdningar pågår", - "none": "Inga", - "not_allowed": "Inte tillåten", - "not_available": "N/A", - "not_in_any_album": "Inte i något album", - "not_selected": "Ej vald", - "note_apply_storage_label_to_previously_uploaded assets": "Obs: Om du vill använda lagringsetiketten på tidigare uppladdade tillgångar kör du", - "notes": "Notera", - "nothing_here_yet": "Inget här ännu", - "notification_permission_dialog_content": "För att aktivera notiser, gå till Inställningar och välj tillåt.", - "notification_permission_list_tile_content": "Tillåt rättighet för att slå på notiser.", - "notification_permission_list_tile_enable_button": "Aktivera Notiser", - "notification_permission_list_tile_title": "Notisrättighet", - "notification_toggle_setting_description": "Aktivera e-postaviseringar", - "notifications": "Notifikationer", - "notifications_setting_description": "Hantera aviseringar", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium-konfigurator", - "obtainium_configurator_instructions": "Använd Obtainium för att installera och uppdatera Android-appen direkt från Immichs GitHub-version. Skapa en API-nyckel och välj en variant för att skapa din Obtainium-konfigurationslänk", - "ocr": "OCR", - "official_immich_resources": "Officiella Immich-resurser", - "offline": "Frånkopplad", - "offset": "Förskjutning", - "ok": "Ok", - "oldest_first": "Äldst först", - "on_this_device": "På enheten", - "onboarding": "Introduktion", - "onboarding_locale_description": "Välj ditt önskade språk. Du kan ändra detta senare i dina inställningar.", - "onboarding_privacy_description": "Följande (valfria) funktioner är beroende av externa tjänster och kan när som helst avaktiveras i inställningarna.", - "onboarding_server_welcome_description": "Låt oss konfigurera din instans med några vanliga inställningar.", - "onboarding_theme_description": "Välj en färg för din instans. Du kan ändra detta senare via inställningarna.", - "onboarding_user_welcome_description": "Nu sätter vi igång!", - "onboarding_welcome_user": "Välkommen, {user}", - "online": "Ansluten", - "only_favorites": "Endast favoriter", - "open": "Öppen", - "open_in_map_view": "Öppna kartvy", - "open_in_openstreetmap": "Öppna med OpenStreetMap", - "open_the_search_filters": "Öppna sökfilter", - "options": "Val", - "or": "eller", - "organize_into_albums": "Organisera i album", - "organize_into_albums_description": "Lägg befintliga foton i album med aktuella synkroniseringsinställningar", - "organize_your_library": "Organisera ditt bibliotek", - "original": "original", - "other": "Övrigt", - "other_devices": "Andra enheter", - "other_entities": "Andra enheter", - "other_variables": "Andra variabler", - "owned": "Ägd", - "owner": "Ägare", - "page": "Sida", - "partner": "Partner", - "partner_can_access": "{partner} har åtkomst", - "partner_can_access_assets": "Alla dina foton och videoklipp förutom de i Arkiverade och Raderade", - "partner_can_access_location": "Platsen där dina foton togs", - "partner_list_user_photos": "{user}s foton", - "partner_list_view_all": "Visa alla", - "partner_page_empty_message": "Dina foton delas inte med någon partner ännu.", - "partner_page_no_more_users": "Inga fler användare att lägga till", - "partner_page_partner_add_failed": "Misslyckades med att lägga till partner", - "partner_page_select_partner": "Välj partner", - "partner_page_shared_to_title": "Delad till", - "partner_page_stop_sharing_content": "{partner} kommer inte längre att komma åt dina foton.", - "partner_sharing": "Partnerdelning", - "partners": "Partners", - "password": "Lösenord", - "password_does_not_match": "Lösenorden stämmer inte överens", - "password_required": "Lösenord krävs", - "password_reset_success": "Lösenord återställt", - "past_durations": { - "days": "Senaste {days, plural, one {day} other {# days}}", - "hours": "Senaste {hours, plural, one {hour} other {# hours}}", - "years": "Senaste {years, plural, one {year} other {# years}}" - }, - "path": "Sökväg", - "pattern": "Mönster", - "pause": "Pause", - "pause_memories": "Pausa minnen", - "paused": "Pausad", - "pending": "Väntande", - "people": "Personer", - "people_edits_count": "Redigerad {count, plural, one {# person} other {# personer}}", - "people_feature_description": "Visar foton och videor grupperade efter personer", - "people_selected": "{count, plural, one {# person vald} other {# personer valda}}", - "people_sidebar_description": "Visa en länk till Personer i sidopanelen", - "permanent_deletion_warning": "Varning om permanent radering", - "permanent_deletion_warning_setting_description": "Visa en varning när tillgångar raderas permanent", - "permanently_delete": "Radera permanent", - "permanently_delete_assets_count": "Radera {count, plural, one {asset} other {assets}} permanent", - "permanently_delete_assets_prompt": "Är du säker på att du permanent vill ta bort {count, plural, one {denna fil?} other{these # filer?}} Detta kommer också ta bort {count, plural, one {dem från } other{them from their}} album.", - "permanently_deleted_asset": "Permanent raderad tillgång", - "permanently_deleted_assets_count": "Permanent borttagning av {count, plural, one {# asset} other {# assets}}", - "permission": "Behörigheter", - "permission_empty": "Dina behörighet kan inte vara blankt", - "permission_onboarding_back": "Bakåt", - "permission_onboarding_continue_anyway": "Fortsätt ändå", - "permission_onboarding_get_started": "Kom igång", - "permission_onboarding_go_to_settings": "Gå till inställningar", - "permission_onboarding_permission_denied": "Rättighet nekad. För att använda Immich, tillåt foto- och video-rättigheter i Inställningar.", - "permission_onboarding_permission_granted": "Rättigheten beviljad! Du är klar.", - "permission_onboarding_permission_limited": "Rättighet begränsad. För att låta Immich säkerhetskopiera och hantera hela ditt galleri, tillåt foto- och video-rättigheter i Inställningar.", - "permission_onboarding_request": "Immich kräver tillstånd för att se dina foton och videor.", - "person": "Individ", - "person_age_months": "{months, plural, one {# månad} other {# månader}} gammal", - "person_age_year_months": "1 år, {months, plural, one {# månad} other {# månader}} gammal", - "person_age_years": "{years, plural, other {# år}} gammal", - "person_birthdate": "Född {date}", - "person_hidden": "{name}{hidden, select, true { (dold)} other {}}", - "person_recognized": "Person igenkänd", - "person_selected": "Person vald", - "photo_shared_all_users": "Du har antingen delat dina foton med alla användare eller så har du inga användare att dela dem med.", - "photos": "Foton", - "photos_and_videos": "Foton & videor", - "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Foton}}", - "photos_from_previous_years": "Foton från tidigare år", - "photos_only": "Foton endast", - "pick_a_location": "Välj en plats", - "pick_custom_range": "Anpassat intervall", - "pick_date_range": "Välj ett datumintervall", - "pin_code_changed_successfully": "Lyckades ändra PIN-koden", - "pin_code_reset_successfully": "Lyckades återställa PIN-kod", - "pin_code_setup_successfully": "Lyckades skapa PIN-kod", - "pin_verification": "Verifiering av PIN-kod", - "place": "Plats", - "places": "Platser", - "places_count": "{count, plural, one {{count, number} Plats} other {{count, number} Platser}}", - "play": "Spela upp", - "play_memories": "Spela upp minnen", - "play_motion_photo": "Spela upp rörligt foto", - "play_or_pause_video": "Spela upp eller pausa video", - "play_original_video": "Spela upp originalvideo", - "play_original_video_setting_description": "Föredra uppspelning av originalvideor framför omkodade videor. Om originalresursen inte är kompatibel kanske den inte spelas upp korrekt.", - "play_transcoded_video": "Spela upp omkodad video", - "please_auth_to_access": "Vänligen autentisera för att få åtkomst", - "port": "Port", - "preferences_settings_subtitle": "Hantera appens inställningar", - "preferences_settings_title": "Inställningar", - "preparing": "Förbereder", - "preset": "Förinställt värde", - "preview": "Förhandsvisning", - "previous": "Föregående", - "previous_memory": "Föregående minne", - "previous_or_next_day": "Föregående eller nästa dag", - "previous_or_next_month": "Föregående eller nästa månad", - "previous_or_next_photo": "Föregående eller nästa foto", - "previous_or_next_year": "Föregående eller nästa år", - "primary": "Primär", - "privacy": "Sekretess", - "profile": "Profil", - "profile_drawer_app_logs": "Loggar", - "profile_drawer_client_server_up_to_date": "Klient och server är uppdaterade", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Skrivskyddat läge aktiverat. Långtryck användaravatarikonen för att avsluta.", - "profile_image_of_user": "{user} profilbild", - "profile_picture_set": "Profilbild vald.", - "public_album": "Publikt album", - "public_share": "Offentlig delning", - "purchase_account_info": "Anhängare", - "purchase_activated_subtitle": "Tack för att du stödjer Immich och open source-mjukvara", - "purchase_activated_time": "Aktiverad {date}", - "purchase_activated_title": "Aktiveringan av din nyckel lyckades", - "purchase_button_activate": "Aktivera", - "purchase_button_buy": "Köp", - "purchase_button_buy_immich": "Köp Immich", - "purchase_button_never_show_again": "Visa aldrig igen", - "purchase_button_reminder": "Påminn mig om 30 dagar", - "purchase_button_remove_key": "Ta bort nyckel", - "purchase_button_select": "Välj", - "purchase_failed_activation": "Aktivering misslyckades! Vänligen kontrollera din e-post för att hitta rätt produktnyckel!", - "purchase_individual_description_1": "För en individ", - "purchase_individual_description_2": "Supporterstatus", - "purchase_individual_title": "Individuell", - "purchase_input_suggestion": "Har du en produktnyckel? Ange nyckeln nedan", - "purchase_license_subtitle": "Köp Immich för att stödja den fortsatta utvecklingen av tjänsten", - "purchase_lifetime_description": "Livstidsköp", - "purchase_option_title": "KÖPALTERNATIV", - "purchase_panel_info_1": "Att bygga Immich kräver mycket tid och engagemang och våra tekniker jobbar heltid för att göra det så bra som vi möjligt kan. Vårt mål är att open source-mjukvara och etiska affärsmetoder ska bli en hållbar inkomstkälla för utvecklare och att skapa ett ekosystem som repekterar personlig integritet med verkliga alternativ till exploaterande molntjänster.", - "purchase_panel_info_2": "Då vi åtagit oss att inte ha betalväggar kommer detta köp inte att ge dig några utökade funktioner i Immich. Vi sätter vår tillit till användare som du som stödjer Immichs fortsatta utveckling.", - "purchase_panel_title": "Stöd projektet", - "purchase_per_server": "Per server", - "purchase_per_user": "Per användare", - "purchase_remove_product_key": "Ta bort produktnyckel", - "purchase_remove_product_key_prompt": "Vill du verkligen ta bort produktnyckeln?", - "purchase_remove_server_product_key": "Ta bort serverns produktnyckel", - "purchase_remove_server_product_key_prompt": "Är du säker på att du vill ta bort serverns produktnyckel?", - "purchase_server_description_1": "För hela servern", - "purchase_server_description_2": "Supporterstatus", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "Produktnyckeln för servern hanteras av administratören", - "query_asset_id": "Fråga om objekts-ID", - "queue_status": "Köande {count}/{total}", - "rate_asset": "Betygsätt materialet", - "rating": "Antal stjärnor", - "rating_clear": "Ta bort betyg", - "rating_count": "{count, plural, one {# stjärna} other {# stjärnor}}", - "rating_description": "Visa EXIF betyget i informationspanelen", - "rating_set": "Rating set to {rating, plural, one {# stjärna} other {# stjärnor}}", - "reaction_options": "Alternativ för reaktion", - "read_changelog": "Läs ändringslogg", - "readonly_mode_disabled": "Skrivskyddat läge inaktiverat", - "readonly_mode_enabled": "Skrivskyddat läge aktiverat", - "ready_for_upload": "Redo för uppladdning", - "reassign": "Omfördela", - "reassigned_assets_to_existing_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till {name, select, null {an existing person} other {{name}}}", - "reassigned_assets_to_new_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till en ny persson", - "reassing_hint": "Tilldela valda tillgångar till en befintlig person", - "recent": "Nyligen", - "recent-albums": "Senaste album", - "recent_searches": "Senaste sökningar", - "recently_added": "Nyligen tillagda", - "recently_added_page_title": "Nyligen tillagda", - "recently_taken": "Nyligen tagna", - "recently_taken_page_title": "Nyligen Tagna", - "refresh": "Ladda om", - "refresh_encoded_videos": "Ladda om kodade videor", - "refresh_faces": "Ladda om ansikten", - "refresh_metadata": "Ladda om metadata", - "refresh_thumbnails": "Uppdatera miniatyrer", - "refreshed": "Omladdad", - "refreshes_every_file": "Läser in alla existerande och nya filer på nytt", - "refreshing_encoded_video": "Återladdar kodad video", - "refreshing_faces": "Återladdar ansikten", - "refreshing_metadata": "Återladdar metadata", - "regenerating_thumbnails": "Uppdaterar miniatyrer", - "remote": "Fjärrr", - "remote_assets": "Fjärrtillgångar", - "remote_media_summary": "Sammanfattning av fjärrmedia", - "remove": "Ta bort", - "remove_assets_album_confirmation": "Är du säker på att du vill ta bort {count, plural, one {# asset} other {# assets}} från albumet?", - "remove_assets_shared_link_confirmation": "Är du säker på att du vill ta bort {count, plural, one {# asset} other {# assets}} från denna delade länk?", - "remove_assets_title": "Ta bort filer?", - "remove_custom_date_range": "Ta bort anpassat datumintervall", - "remove_deleted_assets": "Ta bort borttagna tillgångar", - "remove_from_album": "Ta bort från album", - "remove_from_album_action_prompt": "{count} borttaget från albumet", - "remove_from_favorites": "Ta bort från favoriter", - "remove_from_lock_folder_action_prompt": "{count} borttagen från den låsta mappen", - "remove_from_locked_folder": "Ta bort från låst mapp", - "remove_from_locked_folder_confirmation": "Är du säker på att du vill flytta dessa foton och videor från den låsta mappen? De kommer att vara synliga i ditt bibliotek.", - "remove_from_shared_link": "Ta bort från delad länk", - "remove_memory": "Ta bort minne", - "remove_photo_from_memory": "Ta bort fotot från detta minnet", - "remove_tag": "Ta bort taggen", - "remove_url": "Ta bort URL", - "remove_user": "Ta bort användare", - "removed_api_key": "Tog bort API nyckel: {name}", - "removed_from_archive": "Borttagen från arkivet", - "removed_from_favorites": "Borttagen från favoriter", - "removed_from_favorites_count": "{count, plural, other {Tog bort #}} från favoriter", - "removed_memory": "Tog bort minne", - "removed_photo_from_memory": "Tog bort foto från minnet", - "removed_tagged_assets": "Tog bort tagg från {count, plural, one {# objekt} other {# objekt}}", - "rename": "Döp om", - "repair": "Reparera", - "repair_no_results_message": "Ospårade och saknade filer kommer att dyka upp här", - "replace_with_upload": "Ersätt med uppladdning", - "repository": "Förvar", - "require_password": "Kräver lösenord", - "require_user_to_change_password_on_first_login": "Kräv att användaren ändrar lösenord vid första inloggning", - "rescan": "Skanna igen", - "reset": "Återställ", - "reset_password": "Nollställ lösenord", - "reset_people_visibility": "Återställ personers synlighet", - "reset_pin_code": "Återställ PIN-kod", - "reset_pin_code_description": "Om du har glömt din PIN-kod kan du kontakta serveradministratören för att återställa den", - "reset_pin_code_success": "PIN-koden har återställts", - "reset_pin_code_with_password": "Du kan alltid återställa din PIN-kod med ditt lösenord", - "reset_sqlite": "Återställ SQLite-databasen", - "reset_sqlite_confirmation": "Är du säker på att du vill återställa SQLite-databasen? Du måste logga ut och logga in igen för att synkronisera om data", - "reset_sqlite_success": "Återställde SQLite-databasen", - "reset_to_default": "Återställ till standard", - "resolution": "Upplösning", - "resolve_duplicates": "Lös dubletter", - "resolved_all_duplicates": "Lös alla dubletter", - "restore": "Återställ", - "restore_all": "Återställ alla", - "restore_trash_action_prompt": "{count} återställd från papperskorgen", - "restore_user": "Återställ användare", - "restored_asset": "Återställ tillgång", - "resume": "Återuppta", - "resume_paused_jobs": "Återuppta {count, plural, one {# pausat jobb} other {# pausade jobb}}", - "retry_upload": "Ladda upp igen", - "review_duplicates": "Granska dubbletter", - "review_large_files": "Granska stora filer", - "role": "Roll", - "role_editor": "Redigerare", - "role_viewer": "Visare", - "running": "Igångsatt", - "save": "Spara", - "save_to_gallery": "Spara i galleri", - "saved": "Sparad", - "saved_api_key": "Sparad API-nyckel", - "saved_profile": "Sparade profil", - "saved_settings": "Sparade inställningar", - "say_something": "Säg något", - "scaffold_body_error_occurred": "Fel uppstod", - "scan": "Skanna", - "scan_all_libraries": "Skanna alla bibliotek", - "scan_library": "Skanna", - "scan_settings": "Skanningsinställningar", - "scanning": "Skannar", - "scanning_for_album": "Söker efter album...", - "search": "Sök", - "search_albums": "Sök album", - "search_by_context": "Sök efter sammanhang", - "search_by_description": "Sök via beskrivning", - "search_by_description_example": "Vandringsdag i Sapa", - "search_by_filename": "Sök efter filnamn eller filändelse", - "search_by_filename_example": "t.ex. IMG_1234.JPG eller PNG", - "search_by_ocr": "Sök text i bild", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Sök kameraobjektiv...", - "search_camera_make": "Sök efter kameratillverkare...", - "search_camera_model": "Sök efter kameramodell...", - "search_city": "Sök efter stad...", - "search_country": "Sök efter land...", - "search_filter_apply": "Aktivera filter", - "search_filter_camera_title": "Välj kameratyp", - "search_filter_date": "Datum", - "search_filter_date_interval": "{start} till {end}", - "search_filter_date_title": "Välj datumintervall", - "search_filter_display_option_not_in_album": "Ej i album", - "search_filter_display_options": "Visningsalternativ", - "search_filter_filename": "Sök på filnamn", - "search_filter_location": "Plats", - "search_filter_location_title": "Välj plats", - "search_filter_media_type": "Mediatyp", - "search_filter_media_type_title": "Välj mediatyp", - "search_filter_ocr": "Sök efter OCR", - "search_filter_people_title": "Välj personer", - "search_filter_star_rating": "Stjärnbetyg", - "search_for": "Sök efter", - "search_for_existing_person": "Sök efter befintlig person", - "search_no_more_result": "Inga fler resultat", - "search_no_people": "Inga personer", - "search_no_people_named": "Inga personer med namnet \"{name}\"", - "search_no_result": "Inga resultat, försök med annan sökterm eller kombination", - "search_options": "Sökinställningar", - "search_page_categories": "Kategorier", - "search_page_motion_photos": "Rörelsefoton", - "search_page_no_objects": "Inga objekt är tillgängliga", - "search_page_no_places": "Ingen platsinformation finns tillgänglig", - "search_page_screenshots": "Skärmdumpar", - "search_page_search_photos_videos": "Sök efter dina foton och videor", - "search_page_selfies": "Självporträtt", - "search_page_things": "Objekt", - "search_page_view_all_button": "Visa alla", - "search_page_your_activity": "Dina aktiviteter", - "search_page_your_map": "Din Karta", - "search_people": "Sök personer", - "search_places": "Sök platser", - "search_rating": "Sök efter betyg...", - "search_result_page_new_search_hint": "Ny sökning", - "search_settings": "Sök inställningar", - "search_state": "Sök stat...", - "search_suggestion_list_smart_search_hint_1": "Smartsök är aktiverat som standard, för att söka efter metadata, använd syntaxen ", - "search_suggestion_list_smart_search_hint_2": "m:ditt-sökord", - "search_tags": "Sök taggar...", - "search_timezone": "Sök tidszon...", - "search_type": "Söktyp", - "search_your_photos": "Sök bland dina foton", - "searching_locales": "Söker efter språk...", - "second": "Sekund", - "see_all_people": "Se alla personer", - "select": "Välj", - "select_album": "Välj album", - "select_album_cover": "Välj albumomslag", - "select_albums": "Välj albums", - "select_all": "Välj alla", - "select_all_duplicates": "Välj alla dubletter", - "select_all_in": "Markera alla i {group}", - "select_avatar_color": "Välj färg för avatar", - "select_count": "{count, plural, one {Välj #} other {Välj #}}", - "select_cutoff_date": "Välj slutdatum", - "select_face": "Välj ansikte", - "select_featured_photo": "Välj utvald bild", - "select_from_computer": "Välj från datorn", - "select_keep_all": "Spara alla", - "select_library_owner": "Välj biblioteksägare", - "select_new_face": "Välj nytt ansikte", - "select_people": "Välj personer", - "select_person": "Välj person", - "select_person_to_tag": "Välj en person att tagga", - "select_photos": "Välj foton", - "select_trash_all": "Släng alla", - "select_user_for_sharing_page_err_album": "Kunde inte skapa nytt album", - "selected": "Valda", - "selected_count": "{count, plural, other {# valda}}", - "selected_gps_coordinates": "Valda GPS-koordinater", - "send_message": "Skicka meddelande", - "send_welcome_email": "Skicka välkomstmejl", - "server_endpoint": "Server-endpoint", - "server_info_box_app_version": "App-version", - "server_info_box_server_url": "Server-URL", - "server_offline": "Servern offline", - "server_online": "Server online", - "server_privacy": "Serversekretess", - "server_restarting_description": "Denna sida kommer läsas in på nytt inom kort.", - "server_restarting_title": "Servern startas om", - "server_stats": "Serverstatistik", - "server_update_available": "Serveruppdatering är tillgänglig", - "server_version": "Serverversion", - "set": "Välj", - "set_as_album_cover": "Ange som albumomslag", - "set_as_featured_photo": "Ställ in som utvalt foto", - "set_as_profile_picture": "Ange som profilbild", - "set_date_of_birth": "Ange födelsedatum", - "set_profile_picture": "Ange som profilbild", - "set_slideshow_to_fullscreen": "Ställ in bildspel på helskärm", - "set_stack_primary_asset": "Ange som primär tillgång", - "setting_image_viewer_help": "Detaljerad vy laddar miniatyrer först. Efter detta laddas den medelstora förhandsgranskningen av bilden (om detta är aktiverat), och visar slutligen originalet (om detta är aktiverat).", - "setting_image_viewer_original_subtitle": "Aktivera för att ladda originalbilden i full storlek (stor!). Inaktivera för att minska dataanvändningen (både i nätverket och för enhetscache).", - "setting_image_viewer_original_title": "Ladda originalbilden", - "setting_image_viewer_preview_subtitle": "Aktivera för att ladda en mellanstor bild. Stäng av för att antingen ladda originalet direkt eller bara använda miniatyrbilden.", - "setting_image_viewer_preview_title": "Ladda förhandsgranskning av bild", - "setting_image_viewer_title": "Bilder", - "setting_languages_apply": "Verkställ", - "setting_languages_subtitle": "Ändra appens språk", - "setting_notifications_notify_failures_grace_period": "Rapportera säkerhetskopieringsfel i bakgrunden: {duration}", - "setting_notifications_notify_hours": "{count} timmar", - "setting_notifications_notify_immediately": "omedelbart", - "setting_notifications_notify_minutes": "{count} minuter", - "setting_notifications_notify_never": "aldrig", - "setting_notifications_notify_seconds": "{count} sekunder", - "setting_notifications_single_progress_subtitle": "Detaljerad uppladdningsstatus per bild och video", - "setting_notifications_single_progress_title": "Visa detaljerat uppladdningsförlopp", - "setting_notifications_subtitle": "Anpassa dina notis-inställningar", - "setting_notifications_total_progress_subtitle": "Övergripande uppladdningsförlopp (klar/totala tillgångar)", - "setting_notifications_total_progress_title": "Visa totalt uppladdningsförlopp", - "setting_video_viewer_auto_play_subtitle": "Börja automatiskt spela upp videor när de öppnas", - "setting_video_viewer_auto_play_title": "Automatisk uppspelning av video", - "setting_video_viewer_looping_title": "Loopar", - "setting_video_viewer_original_video_subtitle": "Spela originalet när en video strömmas från servern, även när en transkodad version är tillgänglig. Kan leda till buffring. Videor som är tillgängliga lokalt spelas i originalkvalitet oavsett denna inställning.", - "setting_video_viewer_original_video_title": "Tvinga orginalvideo", - "settings": "Inställningar", - "settings_require_restart": "Starta om Immich för att tillämpa den här inställningen", - "settings_saved": "Inställningar sparade", - "setup_pin_code": "Konfigurera pinkod", - "share": "Dela", - "share_action_prompt": "Delade {count} tillgångar", - "share_add_photos": "Lägg till foton", - "share_assets_selected": "{count} valda", - "share_dialog_preparing": "Förbereder...", - "share_link": "Dela Länk", - "shared": "Delad", - "shared_album_activities_input_disable": "Kommentar är inaktiverad", - "shared_album_activity_remove_content": "Vill du ta bort den här aktiviteten?", - "shared_album_activity_remove_title": "Ta Bort Aktivitet", - "shared_album_section_people_action_error": "Fel vid lämnande/borttagning från album", - "shared_album_section_people_action_leave": "Ta bort användare från album", - "shared_album_section_people_action_remove_user": "Ta bort användare från album", - "shared_album_section_people_title": "PERSONER", - "shared_by": "Delad av", - "shared_by_user": "Delad av {user}", - "shared_by_you": "Delad av dig", - "shared_from_partner": "Foton från {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Uppladdat", - "shared_link_app_bar_title": "Delade Länkar", - "shared_link_clipboard_copied_massage": "Kopierad till urklipp", - "shared_link_clipboard_text": "Länk: {link}\nLösenord: {password}", - "shared_link_create_error": "Fel vid skapande av delad länk", - "shared_link_custom_url_description": "Få åtkomst till den här delade länken med en anpassad URL", - "shared_link_edit_description_hint": "Lägg till delnings-beskrivningen", - "shared_link_edit_expire_after_option_day": "1 dag", - "shared_link_edit_expire_after_option_days": "{count} dagar", - "shared_link_edit_expire_after_option_hour": "1 timme", - "shared_link_edit_expire_after_option_hours": "{count} timmar", - "shared_link_edit_expire_after_option_minute": "1 minut", - "shared_link_edit_expire_after_option_minutes": "{count} minuter", - "shared_link_edit_expire_after_option_months": "{count} månader", - "shared_link_edit_expire_after_option_year": "{count} år", - "shared_link_edit_password_hint": "Ange delningslösenordet", - "shared_link_edit_submit_button": "Uppdatera länk", - "shared_link_error_server_url_fetch": "Kan inte hämta server-urlen", - "shared_link_expires_day": "Går ut om {count} dag", - "shared_link_expires_days": "Går ut om {count} dagar", - "shared_link_expires_hour": "Går ut om {count} timme", - "shared_link_expires_hours": "Går ut om {count} timmar", - "shared_link_expires_minute": "Går ut om {count} minut", - "shared_link_expires_minutes": "Går ut om {count} minuter", - "shared_link_expires_never": "Går ut ∞", - "shared_link_expires_second": "Går ut om {count} sekunder", - "shared_link_expires_seconds": "Går ut om {count} sekunder", - "shared_link_individual_shared": "Individdelad", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Hantera Delade länkar", - "shared_link_options": "Alternativ för delad länk", - "shared_link_password_description": "Kräv ett lösenord för att komma åt den här delade länken", - "shared_links": "Delade Länkar", - "shared_links_description": "Dela foton och videor med en länk", - "shared_photos_and_videos_count": "{assetCount, plural, other {# delade foton och videor.}}", - "shared_with_me": "Delade med mig", - "shared_with_partner": "Delad med {partner}", - "sharing": "Delning", - "sharing_enter_password": "Ange lösenord för att visa denna sidan.", - "sharing_page_album": "Delade album", - "sharing_page_description": "Skapa delade album för att dela foton och video med personer i ditt nätverk.", - "sharing_page_empty_list": "TOM LISTA", - "sharing_sidebar_description": "Visa en länk till Delning i sidopanelen", - "sharing_silver_appbar_create_shared_album": "Skapa delat album", - "sharing_silver_appbar_share_partner": "Dela med partner", - "shift_to_permanent_delete": "tryck på ⇧ för att permanent radera tillgången", - "show_album_options": "Visa albumalternativ", - "show_albums": "Visa album", - "show_all_people": "Visa alla personer", - "show_and_hide_people": "Visa & göm personer", - "show_file_location": "Visa sökväg", - "show_gallery": "Visa galleri", - "show_hidden_people": "Visa gömda personer", - "show_in_timeline": "Visa på tidslinje", - "show_in_timeline_setting_description": "Visa foton och videor från denna användaren på din tidslinje", - "show_keyboard_shortcuts": "Visa kortkommandon", - "show_metadata": "Visa metadata", - "show_or_hide_info": "Visa eller göm information", - "show_password": "Visa lösenord", - "show_person_options": "Visa alternativ för person", - "show_progress_bar": "Visa förloppsindikator", - "show_schema": "Visa schema", - "show_search_options": "Visa sökalternativ", - "show_shared_links": "Visa delade länkar", - "show_slideshow_transition": "Visa bildspelsövergång", - "show_supporter_badge": "Supporteremblem", - "show_supporter_badge_description": "Visa supporteremblem", - "show_text_recognition": "Visa textigenkänning", - "show_text_search_menu": "Visa textsökningsmeny", - "shuffle": "Blanda", - "sidebar": "Sidopanel", - "sidebar_display_description": "Visa en länk till vyn i sidofältet", - "sign_out": "Logga ut", - "sign_up": "Registrera dig", - "size": "Storlek", - "skip_to_content": "Hoppa till innehåll", - "skip_to_folders": "Hoppa till mapp", - "skip_to_tags": "Hoppa till taggar", - "slideshow": "Bildspel", - "slideshow_repeat": "Upprepa bildspel", - "slideshow_repeat_description": "Gå tillbaka till början när bildspelet slutar", - "slideshow_settings": "Bildspelsinställningar", - "sort_albums_by": "Sortera album efter...", - "sort_created": "Skapat datum", - "sort_items": "Antal artiklar", - "sort_modified": "Datum ändrat", - "sort_newest": "Nyaste foto", - "sort_oldest": "Äldsta foto", - "sort_people_by_similarity": "Sortera människor efter likhet", - "sort_recent": "Senaste fotot", - "sort_title": "Rubrik", - "source": "Källa", - "stack": "Stapel", - "stack_action_prompt": "{count} staplade", - "stack_duplicates": "Stapla dubletter", - "stack_select_one_photo": "Välj ett huvudfoto för stapeln", - "stack_selected_photos": "Stapla valda foton", - "stacked_assets_count": "Staplade {count, plural, one {# asset} other {# assets}}", - "stacktrace": "Stapelspårning", - "start": "Starta", - "start_date": "Startdatum", - "start_date_before_end_date": "Startdatumet måste vara före slutdatumet", - "state": "Stat", - "status": "Status", - "stop_casting": "Sluta casta", - "stop_motion_photo": "Stanna rörligt foto", - "stop_photo_sharing": "Sluta dela dina foton?", - "stop_photo_sharing_description": "{partner} kommer inte länga ha tillgång till dina foton.", - "stop_sharing_photos_with_user": "Sluta dela dina bilder med denna användaren", - "storage": "Lagring", - "storage_label": "Förvaringsetikett", - "storage_quota": "Lagringskvot", - "storage_usage": "{used} av {available} används", - "submit": "Skicka", - "success": "Framgång", - "suggestions": "Förslag", - "sunrise_on_the_beach": "Exempel: Soluppgång på stranden", - "support": "Support", - "support_and_feedback": "Support och Feedback", - "support_third_party_description": "Din Immich-installation paketerades av en tredje part. Problem som du upplever kan orsakas av det paketet, så vänligen ta upp problem med dem i första hand med hjälp av länkarna nedan.", - "swap_merge_direction": "Byt sammanfogningsriktning", - "sync": "Synka", - "sync_albums": "Synka album", - "sync_albums_manual_subtitle": "Synka alla uppladdade videor och foton till valda backup-album", - "sync_local": "Synkronisera lokalt", - "sync_remote": "Synkronisera fjärrserver", - "sync_status": "Synk Status", - "sync_status_subtitle": "Visa och hantera synkroniseringssystemet", - "sync_upload_album_setting_subtitle": "Skapa och ladda upp dina foton och videor till de valda albumen på Immich", - "tag": "Tagg", - "tag_assets": "Tagga tillgångar", - "tag_created": "Skapade tagg: {tag}", - "tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar", - "tag_not_found_question": "Kan du inte hitta en tagg? Skapa en ny tagg.", - "tag_people": "Tagga Personer", - "tag_updated": "Uppdaterade tagg: {tag}", - "tagged_assets": "Taggade {count, plural, one {# objekt} other {# objekt}}", - "tags": "Taggar", - "tap_to_run_job": "Tryck för att köra jobbet", - "template": "Mall", - "text_recognition": "Textigenkänning", - "theme": "Tema", - "theme_selection": "Val av tema", - "theme_selection_description": "Ställ in temat automatiskt till ljust eller mörkt baserat på din webbläsares inställningar", - "theme_setting_asset_list_storage_indicator_title": "Visa lagringsindikator på filer", - "theme_setting_asset_list_tiles_per_row_title": "Antal bilder och videor per rad ({count})", - "theme_setting_colorful_interface_subtitle": "Applicera primärfärgen på bakgrundsytor.", - "theme_setting_colorful_interface_title": "Färgglatt gränssnitt", - "theme_setting_image_viewer_quality_subtitle": "Justera kvaliteten i bildvisaren", - "theme_setting_image_viewer_quality_title": "Bildvisarens kvalitet", - "theme_setting_primary_color_subtitle": "Välj en färg för primära åtgärder och accenter.", - "theme_setting_primary_color_title": "Primärfärg", - "theme_setting_system_primary_color_title": "Använd systemfärg", - "theme_setting_system_theme_switch": "Automatisk (Följ systeminställningar)", - "theme_setting_theme_subtitle": "Välj inställning för appens tema", - "theme_setting_three_stage_loading_subtitle": "Trestegsladdning kan öka prestandan, men kan också leda till signifikant högre nätverksbelastning", - "theme_setting_three_stage_loading_title": "Aktivera trestegsladdning", - "then": "Sedan", - "they_will_be_merged_together": "De kommer att slås samman", - "third_party_resources": "Tredjepartsresurser", - "time": "Tid", - "time_based_memories": "Tidsbaserade minnen", - "time_based_memories_duration": "Antal sekunder att visa varje bild.", - "timeline": "Tidslinje", - "timezone": "Tidszon", - "to_archive": "Arkivera", - "to_change_password": "Ändra lösenord", - "to_favorite": "Favorit", - "to_login": "Logga in", - "to_multi_select": "för att välja flera", - "to_parent": "Gå till förälder", - "to_select": "för att välja", - "to_trash": "Papperskorg", - "toggle_settings": "Växla inställningar", - "toggle_theme_description": "Växla tema", - "total": "Totalt", - "total_usage": "Total användning", - "trash": "Papperskorg", - "trash_action_prompt": "{count} flyttad till papperskorgen", - "trash_all": "Kasta alla", - "trash_count": "Papperskorg {count, number}", - "trash_delete_asset": "Papperskorgen/Ta bort tillgång", - "trash_emptied": "Tömd papperskorg", - "trash_no_results_message": "Borttagna foton och videor kommer att visas här.", - "trash_page_delete_all": "Ta Bort Alla", - "trash_page_empty_trash_dialog_content": "Vill du ta bort dina slängda objekt? De kommer att tas bort permanent från Immich", - "trash_page_info": "Objekt i papperskorgen tas bort permanent efter {days} dagar", - "trash_page_no_assets": "Inga slängda objekt", - "trash_page_restore_all": "Återställ Alla", - "trash_page_select_assets_btn": "Välj objekt", - "trash_page_title": "Papperskorg ({count})", - "trashed_items_will_be_permanently_deleted_after": "Objekt i papperskorgen raderas permanent efter {days, plural, one {# dag} other {# dagar}}.", - "trigger": "Utlösare", - "trigger_asset_uploaded": "Tillgång uppladdad", - "trigger_asset_uploaded_description": "Utlöses när en ny tillgång laddas upp", - "trigger_description": "Ett evenemang som sätter igång arbetsflödet", - "trigger_person_recognized": "Person igenkänd", - "trigger_person_recognized_description": "Utlöses när en person upptäcks", - "trigger_type": "Utlösningstyp", - "troubleshoot": "Felsök", - "type": "Typ", - "unable_to_change_pin_code": "Kunde inte ändra pinkod", - "unable_to_check_version": "Det går inte att kontrollera app- eller serverversionen", - "unable_to_setup_pin_code": "Kunde inte konfigurera pinkod", - "unarchive": "Ångra arkivering", - "unarchive_action_prompt": "{count} borttagen från arkivet", - "unarchived_count": "{count, plural, one {# borttagen från arkiv} other {# borttagna från arkiv}}", - "undo": "Ångra", - "unfavorite": "Avfavorisera", - "unfavorite_action_prompt": "{count} borttagen från Favoriter", - "unhide_person": "Visa person", - "unknown": "Okänd", - "unknown_country": "Okänt Land", - "unknown_date": "Okänt datum", - "unknown_year": "Okänt år", - "unlimited": "Obegränsat", - "unlink_motion_video": "Ta bort länken till rörlig video", - "unlink_oauth": "Ta bort länken till OAuth", - "unlinked_oauth_account": "Olänkat OAuth-konto", - "unmute_memories": "Slå på ljud för minnen", - "unnamed_album": "Namnlöst Album", - "unnamed_album_delete_confirmation": "Är du säker på att du vill ta bort detta album?", - "unnamed_share": "Namnlös delning", - "unsaved_change": "Osparade ändringar", - "unselect_all": "Avmarkera alla", - "unselect_all_duplicates": "Avmarkera alla dubletter", - "unselect_all_in": "Avmarkera alla i {group}", - "unstack": "Stapla Av", - "unstack_action_prompt": "{count} ostaplade", - "unstacked_assets_count": "Avstaplade {count, plural, one {# asset} other {# assets}}", - "unsupported_field_type": "Fälttyp som inte stöds", - "untagged": "Otaggad", - "untitled_workflow": "Namnlöst arbetsflöde", - "up_next": "Kommande", - "update_location_action_prompt": "Uppdatera platsen för {count} valda tillgångar med:", - "updated_at": "Uppdaterat", - "updated_password": "Lösenordet har uppdaterats", - "upload": "Ladda upp", - "upload_concurrency": "Uppladdning samtidighet", - "upload_details": "Uppladdningsdetaljer", - "upload_dialog_info": "Vill du säkerhetskopiera de valda objekten till servern?", - "upload_dialog_title": "Ladda Upp Objekt", - "upload_error_with_count": "Uppladdningsfel för {count, plural, one {# asset} other {# assets}}", - "upload_errors": "Uppladdning klar med {count, plural, one {# fel} other {# fel}}, ladda om sidan för att se nya objekt.", - "upload_finished": "Uppladdningen är klar", - "upload_progress": "Återstående {remaining, number} - Bearbetade {processed, number}/{total, number}", - "upload_skipped_duplicates": "Hoppade över {count, plural, one {# dublett} other {# dubletter}}", - "upload_status_duplicates": "Dubbletter", - "upload_status_errors": "Fel", - "upload_status_uploaded": "Uppladdad", - "upload_success": "Uppladdning lyckades, ladda om sidan för att se nya objekt.", - "upload_to_immich": "Ladda upp till Immich ({count})", - "uploading": "Laddar upp", - "uploading_media": "Uppladdning av media", - "url": "URL", - "usage": "Användning", - "use_biometric": "Använd biometri", - "use_current_connection": "Använd aktuell anslutning", - "use_custom_date_range": "Använd anpassat datumintervall istället", - "user": "Användare", - "user_has_been_deleted": "Den här användaren har raderats.", - "user_id": "Användar-ID", - "user_liked": "{user} gillade {type, select, photo {detta fotot} video {denna filmen} asset {detta objekt} other {detta}}", - "user_pin_code_settings": "Pinkod", - "user_pin_code_settings_description": "Hantera pinkod", - "user_privacy": "Användarsekretess", - "user_purchase_settings": "Köp", - "user_purchase_settings_description": "Hantera dina köp", - "user_role_set": "Sätt {user} som {role}", - "user_usage_detail": "Användaranvändningsdetaljer", - "user_usage_stats": "Kontoinformation - statistik", - "user_usage_stats_description": "Se statistik - kontoanvändande", - "username": "Användarnamn", - "users": "Användare", - "users_added_to_album_count": "Lade till {count, plural, one {# user} other {# users}} i albumet", - "utilities": "Verktyg", - "validate": "Validera", - "validate_endpoint_error": "Ange en giltig URL", - "validation_error": "Valideringsskräck", - "variables": "Variabler", - "version": "Version", - "version_announcement_closing": "Din vän, Alex", - "version_announcement_message": "Hej där! En ny version av Immich är tillgänglig. Ta dig tid att läsa versionsfakta för att säkerställa att dina inställningar är uppdaterade för att förhindra eventuella felkonfigurationer, särskilt om du använder WatchTower eller någon mekanism som hanterar uppdatering av din Immich instans automatiskt.", - "version_history": "Versionshistorik", - "version_history_item": "Version {version} installerad {date}", - "video": "Video", - "video_hover_setting": "Spela upp videotumnagel när muspekaren är över den", - "video_hover_setting_description": "Spela upp videotumnagel när muspekaren är över den. Även när den är deaktiverad kan uppspelning startas när muspekaren är över play-ikonen.", - "videos": "Videor", - "videos_count": "{count, plural, one {# Video} other {# Videor}}", - "videos_only": "Videor endast", - "view": "Visa", - "view_album": "Visa Album", - "view_all": "Visa alla", - "view_all_users": "Visa alla användare", - "view_asset_owners": "Visa tillgångsägare", - "view_details": "Visa detaljer", - "view_in_timeline": "Visa i tidslinjen", - "view_link": "Visa länk", - "view_links": "Visa länkar", - "view_name": "Visa", - "view_next_asset": "Visa nästa objekt", - "view_previous_asset": "Visa föregående objekt", - "view_qr_code": "Visa QR-kod", - "view_similar_photos": "Visa liknande foton", - "view_stack": "Visa Stapel", - "view_user": "Visa Användare", - "viewer_remove_from_stack": "Ta bort från Stapeln", - "viewer_stack_use_as_main_asset": "Använd som Huvudobjekt", - "viewer_unstack": "Stapla Av", - "visibility_changed": "Synlighet ändrad för {count, plural, one {# person} other {# personer}}", - "visual": "Visuellt", - "visual_builder": "Visuell byggare", - "waiting": "Väntar", - "waiting_count": "Väntande: {count}", - "warning": "Varning", - "week": "Vecka", - "welcome": "Välkommen", - "welcome_to_immich": "Välkommen till Immich", - "width": "Bredd", - "wifi_name": "Wi-Fi-namn", - "workflow_delete_prompt": "Är du säker på att du vill ta bort det här arbetsflödet?", - "workflow_deleted": "Arbetsflödet raderat", - "workflow_description": "Beskrivning av arbetsflödet", - "workflow_info": "Arbetsflödesinformation", - "workflow_json": "Arbetsflödes-JSON", - "workflow_json_help": "Redigera arbetsflödeskonfigurationen i JSON-format. Ändringarna synkroniseras med den visuella verktygsbyggaren.", - "workflow_name": "Arbetsflödesnamn", - "workflow_navigation_prompt": "Är du säker på att du vill avsluta utan att spara dina ändringar?", - "workflow_summary": "Sammanfattning av arbetsflöde", - "workflow_update_success": "Arbetsflödet har uppdaterats", - "workflow_updated": "Arbetsflödet uppdaterades", - "workflows": "Arbetsflöden", - "workflows_help_text": "Arbetsflöden automatiserar åtgärder på dina resurser baserat på utlösare och filter", - "wrong_pin_code": "Fel pinkod", - "year": "År", - "years_ago": "{years, plural, one {# år} other {# år}} sedan", - "yes": "Ja", - "you_dont_have_any_shared_links": "Du har inga delade länkar", - "your_wifi_name": "Ditt Wi-Fi-namn", - "zero_to_clear_rating": "Tryck 0 för att rensa betygsättningen", - "zoom_image": "Zooma bild", - "zoom_to_bounds": "Zooma till gränser" -} +{} diff --git a/i18n/ta.json b/i18n/ta.json index e27bdfd0cb..0967ef424b 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -1,2227 +1 @@ -{ - "about": "விபரம்", - "account": "கணக்கு", - "account_settings": "கணக்கு அமைவுகள்", - "acknowledge": "ஒப்புக்கொள்கிறேன்", - "action": "செயல்", - "action_common_update": "மேம்படுத்து", - "action_description": "வடிகட்டப்பட்ட சொத்துக்களில் செய்ய வேண்டிய செயல்களின் தொகுப்பு", - "actions": "செயல்கள்", - "active": "செயல்பாட்டில்", - "active_count": "செயலில்: {count}", - "activity": "செயல்பாடுகள்", - "activity_changed": "செயல்பாடு {enabled, select, true {இயக்கப்பட்டது} other {முடக்கப்பட்டது}}", - "add": "சேர்", - "add_a_description": "விவரம் சேர்", - "add_a_location": "இடத்தை சேர்க்கவும்", - "add_a_name": "பெயரை சேர்க்கவும்", - "add_a_title": "தலைப்பு சேர்க்கவும்", - "add_action": "செயலைச் சேர்", - "add_action_description": "செய்ய வேண்டிய செயலைச் சேர்க்க கிளிக் செய்யவும்", - "add_birthday": "பிறந்தநாளைச் சேர்க்கவும்", - "add_endpoint": "சேவை நிரலை சேர்", - "add_exclusion_pattern": "விலக்கு வடிவத்தைச் சேர்க்கவும்", - "add_filter": "வடிகட்டியைச் சேர்க்கவும்", - "add_filter_description": "வடிகட்டி நிபந்தனையைச் சேர்க்க கிளிக் செய்யவும்", - "add_location": "இடத்தைச் சேர்க்கவும்", - "add_more_users": "மேலும் பயனர்களை சேர்க்கவும்", - "add_partner": "துணையை சேர்க்கவும்", - "add_path": "பாதை (பாத்) சேர்க்கவும்", - "add_photos": "புகைப்படங்களை சேர்க்கவும்", - "add_tag": "குறியைச் சேர்க்க", - "add_to": "சேர்க்கவும்…", - "add_to_album": "ஆல்பமில் சேர்க்க", - "add_to_album_bottom_sheet_added": "{album}-இல் சேர்க்கப்பட்டது", - "add_to_album_bottom_sheet_already_exists": "ஏற்கனவே {album}-இல் உள்ளது", - "add_to_album_bottom_sheet_some_local_assets": "சில உள்ளக சொத்துக்களை ஆல்பத்தில் சேர்க்க முடியவில்லை", - "add_to_album_toggle": "{album} க்கான தேர்வை மாற்று", - "add_to_albums": "ஆல்பத்தில் சேர்", - "add_to_albums_count": "ஆல்பங்களில் சேர்({count})", - "add_to_bottom_bar": "சேர்க்கவும்", - "add_to_shared_album": "பகிரப்பட்ட ஆல்பமில் சேர்க்க", - "add_upload_to_stack": "அடுக்கில் பதிவேற்றத்தைச் சேர்", - "add_url": "URL ஐச் சேர்க்கவும்", - "add_workflow_step": "பணிப்பாய்வுப் படியைச் சேர்க்கவும்", - "added_to_archive": "காப்பகத்தில் சேர்க்கப்பட்டது", - "added_to_favorites": "விருப்பங்களில் (பேவரிட்ஸ்) சேர்க்கப்பட்டது", - "added_to_favorites_count": "விருப்பங்களில் {count, number} சேர்க்கப்பட்டது", - "admin": { - "add_exclusion_pattern_description": "விலக்கு வடிவங்களைச் சேர்க்கவும். *, **, மற்றும் ? ஆதரிக்கப்படுகிறது. \"Raw\" என்ற பெயரிடப்பட்ட எந்த கோப்பகத்திலும் உள்ள எல்லா கோப்புகளையும் புறக்கணிக்க, \"**/Raw/**\" ஐப் பயன்படுத்தவும். \".tif\" இல் முடியும் எல்லா கோப்புகளையும் புறக்கணிக்க, \"**/*.tif\" ஐப் பயன்படுத்தவும். ஒரு முழுமையான பாதையை புறக்கணிக்க, \"/path/to/ignore/**\" ஐப் பயன்படுத்தவும்.", - "admin_user": "நிர்வாக பயனர்", - "asset_offline_description": "இந்த வெளிப்புற நூலகச் சொத்து (external library asset) இனி கோப்புப் பதிப்பில் காணப்படவில்லை மற்றும் குப்பைத்தொட்டியில் (trash) நகர்த்தப்பட்டுள்ளது.கோப்பு நூலகத்தில் உள்ளே நகர்த்தப்பட்டிருந்தால், புதிய இணை சொத்தை (corresponding asset) கண்டுபிடிக்க உங்கள் காலவரிசையை (timeline) சரிபார்க்கவும். இந்த சொத்தை மீட்டமைக்க, கீழே உள்ள கோப்புப் பாதை Immich மூலம் அணுகக்கூடியதா என்பதை உறுதி செய்து, நூலகத்தை (library) மீண்டும் ஸ்கேன் செய்யவும்.", - "authentication_settings": "அங்கீகார அமைப்புகள்", - "authentication_settings_description": "அடையாளச் சொல்லுகள் (Password), OAuth மற்றும் பிற அங்கீகார அமைப்புகளை நிர்வகிக்கவும்", - "authentication_settings_disable_all": "நீங்கள் உண்மையில் அனைத்து உள்நுழைவு முறைகளையும் முடக்க விரும்புகிறீர்களா? உள்நுழைவு முழுமையாக முடக்கப்படும்.", - "authentication_settings_reenable": "மீண்டும் செயல்படுத்த, சேவையகம் கட்டளை பயன்படுத்தவும்.", - "background_task_job": "பின்னணி பணிகள்", - "backup_database": "தரவுத்தள நகல் உருவாக்கவும்", - "backup_database_enable_description": "தரவுத்தள நகல்களை இயக்கவும்", - "backup_keep_last_amount": "வைத்திருக்க முந்தைய நகல்களின் அளவு", - "backup_onboarding_1_description": "மேகம் அல்லது வேறு இடத்தில் நகல்.", - "backup_onboarding_2_description": "வெவ்வேறு சாதனங்களில் உள்ள நகல் பிரதிகள். இதில் முக்கிய கோப்புகள் மற்றும் அந்தக் கோப்புகளின் நகல் காப்புப்பிரதி ஆகியவை அடங்கும்.", - "backup_onboarding_3_description": "உங்கள் தரவின் மொத்த கோப்புகள் அசல் மற்றும் நகல்கள் உட்பட. இதில் 1 வெளிப்புற நகல் மற்றும் 2 சாதனப் பிரதிகள் அடங்கும்.", - "backup_onboarding_description": "உங்கள் தரவை பாதுகாப்பதற்காக ஒரு 3-2-1 காப்புப் பிரதி பரிந்துரைக்கப்படுகிறது. முழுமையான காப்பு பாதுகாப்பு தீர்விற்காக, நீங்கள் பதிவேற்றிய புகைப்படங்கள்/வீடியோக்கள் மற்றும் Immich தரவுத்தளத்தின் நகல்களையும் வைத்திருக்க வேண்டும்.", - "backup_onboarding_footer": "Immich-ஐ தரவு நகல் காப்பு எடுப்பது பற்றிய மேலும் தகவலுக்கு, தயவுசெய்து ஆவணத்தை பார்க்கவும்.", - "backup_onboarding_parts_title": "3-2-1 காப்புப்பிரதியில் பின்வருவன அடங்கும்:", - "backup_onboarding_title": "காப்புப்பிரதிகள்", - "backup_settings": "தரவுத்தள திணிப்பு அமைப்புகள்", - "backup_settings_description": "தரவுத்தள நகல் அமைப்புகளை நிர்வகி.", - "cleared_jobs": "முடித்த வேலைகள்: {job}", - "config_set_by_file": "கட்டமைப்பு, தற்போது ஒரு கட்டமைப்பு கோப்பு மூலம் அமைக்கப்பட்டுள்ளது", - "confirm_delete_library": "{library} படங்கள் நூலகத்தை நிச்சயமாக நீக்க விரும்புகிறீர்களா?", - "confirm_delete_library_assets": "இந்த நூலகத்தை நிச்சயமாக நீக்க விரும்புகிறீர்களா? இது Immich இலிருந்து {count, plural, one {# contained asset} other {all # contained assets}} நீக்கிவிடும், மேலும் செயல்தவிர்க்க முடியாது. கோப்புகள் வட்டில் இருக்கும்.", - "confirm_email_below": "உறுதிப்படுத்த, கீழே \"{email}\" என தட்டச்சு செய்யவும்", - "confirm_reprocess_all_faces": "எல்லா முகங்களையும் மீண்டும் செயலாக்க விரும்புகிறீர்களா? இது பெயரிடப்பட்ட நபர்களையும் அழிக்கும்.", - "confirm_user_password_reset": "{user} இன் கடவுச்சொல்லை நிச்சயமாக மீட்டமைக்க விரும்புகிறீர்களா?", - "confirm_user_pin_code_reset": "{user} இன் பின் குறியீட்டை மீட்டமைக்க விரும்புகிறீர்களா?", - "copy_config_to_clipboard_description": "தற்போதைய கணினி உள்ளமைவை JSON பொருளாக கிளிப்போர்டுக்கு நகலெடுக்கவும்", - "create_job": "வேலையை உருவாக்கு", - "cron_expression": "க்ரோன் வெளிப்பாடு", - "cron_expression_description": "க்ரோன் வடிவமைப்பைப் பயன்படுத்தி ச்கேனிங் இடைவெளியை அமைக்கவும். மேலும் தகவலுக்கு எ.கா. க்ரோன்டாப் குரு ", - "cron_expression_presets": "க்ரோன் வெளிப்பாடு முன்னமைவுகள்", - "disable_login": "உள்நுழைவை முடக்கு", - "duplicate_detection_job_description": "ஒத்த படங்களைக் கண்டறிய, சொத்துக்களில் இயந்திரக் கற்றலை இயக்கவும். ஸ்மார்ட் தேடலை நம்பியுள்ளது", - "exclusion_pattern_description": "உங்கள் நூலகத்தை ஸ்கேன் செய்யும் போது கோப்புகளையும் கோப்புறைகளையும் புறக்கணிக்க விலக்கு வடிவங்கள் உங்களை அனுமதிக்கின்றன. RAW கோப்புகள் போன்ற நீங்கள் இறக்குமதி செய்ய விரும்பாத கோப்புகளைக் கொண்ட கோப்புறைகள் உங்களிடம் இருந்தால் இது பயனுள்ளதாக இருக்கும்.", - "export_config_as_json_description": "தற்போதைய கணினி உள்ளமைவை JSON கோப்பாகப் பதிவிறக்கவும்", - "external_libraries_page_description": "நிர்வாகி வெளிப்புற நூலகப் பக்கம்", - "face_detection": "முகம் கண்டறிதல்", - "face_detection_description": "இயந்திர கற்றலைப் பயன்படுத்தி சொத்துக்களில் உள்ள முகங்களைக் கண்டறியவும். வீடியோக்களுக்கு, சிறுபடம் மட்டுமே கருதப்படுகிறது. \"அனைத்து\" (மறு-) அனைத்து சொத்துகளையும் செயலாக்குகிறது. இதுவரை செயலாக்கப்படாத புகைப்பட சொத்துக்களை \"காணவில்லை\" வரிசைப்படுத்துகிறது. முகம் கண்டறிதல் முடிந்ததும், கண்டறியப்பட்ட முகங்கள், ஏற்கனவே இருக்கும் அல்லது புதிய நபர்களாகக் குழுவாக்கப்பட்டு, முக அடையாளத்திற்காக வரிசையில் நிறுத்தப்படும்.", - "facial_recognition_job_description": "நபர்களின் முகங்களைக் குழு கண்டறிந்தது. முகம் கண்டறிதல் முடிந்ததும் இந்தப் படி இயங்கும். அனைத்து முகங்களையும் \"அனைத்து\" (மறு-) கொத்துகள். \"காணவில்லை\" என்பது நபர் நியமிக்கப்படாத முகங்களை வரிசைப்படுத்துகிறது.", - "failed_job_command": "பணிக்கான கட்டளை {command} தோல்வியடைந்தது: {job}", - "force_delete_user_warning": "எச்சரிக்கை: இது பயனரையும் அனைத்து புகைப்பட சொத்துகளையும் உடனடியாக அகற்றும். இதை செயல்தவிர்க்க முடியாது மற்றும் புகைப்படங்களை மீட்டெடுக்க முடியாது.", - "image_format": "வடிவம்", - "image_format_description": "WebP, JPEG ஐ விட சிறிய கோப்புகளை உருவாக்குகிறது, ஆனால் குறியாக்கம் செய்ய மெதுவாக உள்ளது.", - "image_fullsize_description": "பெரிதாக்கும்போது பயன்படுத்தப்படும், அகற்றப்பட்ட மெட்டாடேட்டாவுடன் கூடிய முழு அளவிலான படம்", - "image_fullsize_enabled": "முழு அளவிலான பட உருவாக்கத்தை இயக்கு", - "image_fullsize_enabled_description": "இணைய இணக்கமில்லா வடிவங்களுக்கு முழு அளவிலான படத்தை உருவாக்கவும். \"உட்பொதிக்கப்பட்ட மாதிரிக்காட்சியை விரும்பு\" இயக்கப்பட்டிருக்கும் போது, உட்பொதிக்கப்பட்ட மாதிரிக்காட்சிகள் மாற்றமின்றி நேரடியாகப் பயன்படுத்தப்படும். JPEG போன்ற இணைய நட்பு வடிவங்களைப் பாதிக்காது.", - "image_fullsize_quality_description": "முழு அளவிலான படத் தரம் 1-100 வரை. அதிகமான எண் சிறந்தது, ஆனால் பெரிய கோப்புகளை உருவாக்கும்.", - "image_fullsize_title": "முழு அளவிலான பட அமைப்புகள்", - "image_prefer_embedded_preview": "உட்பொதிந்த படத்தை முன்னிடு", - "image_prefer_embedded_preview_setting_description": "பட செயலாக்கத்திற்கான உள்ளீடாக மூல புகைப்படங்களில் உட்பொதிக்கப்பட்ட முன்னோட்டங்களை பயன்படுத்தவும், கிடைக்கும்போது. இது சில படங்களுக்கு மிகவும் துல்லியமான வண்ணங்களை உருவாக்க முடியும், ஆனால் முன்னோட்டத்தின் தகுதி கேமரா சார்ந்தது மற்றும் படத்தில் அதிக சுருக்க கலைப்பொருட்கள் இருக்கலாம்.", - "image_prefer_wide_gamut": "அகன்ற வண்ணவரம்பு தேர்வு", - "image_prefer_wide_gamut_setting_description": "சிறு உருவங்களுக்கு காட்சி பி 3 ஐப் பயன்படுத்தவும். இது பரந்த வண்ணங்களைக் கொண்ட படங்களின் அதிர்வுகளை சிறப்பாக பாதுகாக்கிறது, ஆனால் பழைய உலாவி பதிப்பைக் கொண்ட பழைய சாதனங்களில் படங்கள் வித்தியாசமாக தோன்றக்கூடும். வண்ண மாற்றங்களைத் தவிர்க்க SRGB படங்கள் SRGB ஆக வைக்கப்படுகின்றன.", - "image_preview_description": "அகற்றப்பட்ட மெட்டாடேட்டாவுடன் நடுத்தர அளவிலான படம், ஒற்றை சொத்தைப் பார்க்கும்போது மற்றும் இயந்திர கற்றலுக்காகப் பயன்படுத்தப்படுகிறது", - "image_preview_quality_description": "1-100 முதல் தரத்தை முன்னோட்டமிடுங்கள். உயர்ந்தது சிறந்தது, ஆனால் பெரிய கோப்புகளை உருவாக்குகிறது மற்றும் பயன்பாட்டு மறுமொழியைக் குறைக்கும். குறைந்த மதிப்பை அமைப்பது இயந்திர கற்றல் தரத்தை பாதிக்கலாம்.", - "image_preview_title": "அமைப்புகள் முன்னோட்டம்", - "image_quality": "தரம்", - "image_resolution": "பகுத்தல்", - "image_resolution_description": "அதிக தீர்மானங்கள் அதிக விவரங்களை பாதுகாக்க முடியும், ஆனால் குறியாக்க அதிக நேரம் எடுக்கும், பெரிய கோப்பு அளவுகளைக் கொண்டிருக்கலாம் மற்றும் பயன்பாட்டு மறுமொழியைக் குறைக்கலாம்.", - "image_settings": "பட அமைப்புகள்", - "image_settings_description": "உருவாக்கப்பட்ட படங்களின் தரம் மற்றும் தெளிவுத்திறனை நிர்வகிக்கவும்", - "image_thumbnail_description": "அகற்றப்பட்ட மெட்டாடேட்டாவுடன் சிறிய சிறுபடம், முதன்மையான காலவரிசை போன்ற புகைப்படங்களின் குழுக்களைப் பார்க்கும்போது பயன்படுத்தப்படுகிறது", - "image_thumbnail_quality_description": "1-100 முதல் சிறு தகுதி. உயர்ந்தது சிறந்தது, ஆனால் பெரிய கோப்புகளை உருவாக்குகிறது மற்றும் பயன்பாட்டு மறுமொழியைக் குறைக்கும்.", - "image_thumbnail_title": "சிறு அமைப்புகள்", - "import_config_from_json_description": "JSON கட்டமைப்பு கோப்பை பதிவேற்றுவதன் மூலம் கணினி கட்டமைப்பை இறக்குமதி செய்யவும்", - "job_concurrency": "{job} ஒத்திசைவு", - "job_created": "வேலை உருவாக்கப்பட்டது", - "job_not_concurrency_safe": "இந்த வேலை ஒரே நேரத்தில் பாதுகாப்பானது அல்ல.", - "job_settings": "வேலை அமைப்புகள்", - "job_settings_description": "வேலை ஒத்திசைவை நிர்வகிக்கவும்", - "jobs_delayed": "{jobCount, plural, other {# தாமதமானது}}", - "jobs_failed": "{jobCount, plural, other {# தோல்வியுற்றது}}", - "jobs_over_time": "காலப்போக்கில் வேலைகள்", - "library_created": "உருவாக்கப்பட்ட நூலகம்: {library}", - "library_deleted": "புகைப்பட நூலகம் நீக்கப்பட்டது", - "library_details": "நூலக விவரங்கள்", - "library_folder_description": "இறக்குமதி செய்ய ஒரு கோப்புறையைக் குறிப்பிடவும். இந்தக் கோப்புறை, துணை கோப்புறைகள் உட்பட படங்கள் மற்றும் வீடியோக்களுக்காக ஸ்கேன் செய்யப்படும்.", - "library_remove_exclusion_pattern_prompt": "இந்த விலக்கு முறையை நீக்க விரும்புகிறீர்களா?", - "library_remove_folder_prompt": "இந்த இறக்குமதி கோப்புறையை நீக்க விரும்புகிறீர்களா?", - "library_scanning": "அவ்வப்போது ஸ்கேனிங்", - "library_scanning_description": "நியமிக்கப்பட்ட புகைப்பட நூலக ஸ்கேனிங்கை அமைக்கவும்", - "library_scanning_enable_description": "நியமிக்கப்பட்ட புகைப்பட நூலக ஸ்கேனிங்கை இயக்கு", - "library_settings": "வெளிப்புற புகைப்பட நூலகம்", - "library_settings_description": "வெளிப்புற புகைப்பட நூலக அமைப்புகளை மேலாண்மை செய்யவும்", - "library_tasks_description": "புதிய மற்றும்/அல்லது மாற்றப்பட்ட சொத்துக்களுக்கு வெளிப்புற நூலகங்களை வருடவும்", - "library_updated": "புதுப்பிக்கப்பட்ட நூலகம்", - "library_watching_enable_description": "கோப்பு மாற்றங்களுக்கு வெளிப்புற நூலகங்களைப் பாருங்கள்", - "library_watching_settings": "நூலகப் பார்ப்பது [சோதனை]", - "library_watching_settings_description": "மாற்றப்பட்ட புகைப்படங்களைத் தானாகவே பார்க்கவும்", - "logging_enable_description": "பதிவு செய்வதை இயக்கு", - "logging_level_description": "இயக்கப்பட்டால், எந்தப் பதிவு நிலை பயன்படுத்த வேண்டும்.", - "logging_settings": "பதிவு செய்தல்", - "machine_learning_availability_checks": "கிடைக்கும் காசோலைகள்", - "machine_learning_availability_checks_description": "கிடைக்கக்கூடிய இயந்திர கற்றல் சேவையகங்களை தானாக கண்டறிந்து விரும்புங்கள்", - "machine_learning_availability_checks_enabled": "கிடைக்கும் காசோலைகளை இயக்கவும்", - "machine_learning_availability_checks_interval": "இடைவெளியை சரிபார்க்கவும்", - "machine_learning_availability_checks_interval_description": "கிடைக்கும் காசோலைகளுக்கு இடையில் மில்லி விநாடிகளில் இடைவெளி", - "machine_learning_availability_checks_timeout": "நேரம் முடிந்தது", - "machine_learning_availability_checks_timeout_description": "கிடைக்கும் காசோலைகளுக்கு மில்லி விநாடிகளில் நேரம் முடிந்தது", - "machine_learning_clip_model": "கிளிப் மாடல்", - "machine_learning_clip_model_description": "CLIP மாடல் பெயர் இங்கே பட்டியலிடப்பட்டுள்ளது. ஒரு மாடயலை மாற்றியவுடன் அனைத்து படங்களுக்கும் 'ஸ்மார்ட் தேடல்' வேலையை மீண்டும் இயக்க வேண்டும் என்பதை நினைவில் கொள்ளவும்.", - "machine_learning_duplicate_detection": "நகல் (டூப்ளிகேட்) கண்டறிதல்", - "machine_learning_duplicate_detection_enabled": "நகல் (டூப்ளிகேட்) கண்டறிதலை இயக்கு", - "machine_learning_duplicate_detection_enabled_description": "முடக்கப்பட்டிருந்தால், 100 சதவீதம் ஒரே மாதிரியான சொத்துக்கள் நகல் (டூப்ளிகேட்) எடுக்கப்படும்.", - "machine_learning_duplicate_detection_setting_description": "சாத்தியமான நகல்களைக் (டூப்ளிகேட்) கண்டறிய CLIP மாடெலைப் பயன்படுத்தவும்", - "machine_learning_enabled": "இயந்திர கற்றலை இயக்கு", - "machine_learning_enabled_description": "முடக்கப்பட்டால், கீழே உள்ள அமைப்புகளைப் பொருட்படுத்தாமல் அனைத்து ML அம்சங்களும் முடக்கப்படும்.", - "machine_learning_facial_recognition": "முகம் அடையாளம் காணும் அமைப்பு", - "machine_learning_facial_recognition_description": "படங்களில் முகங்களைக் கண்டறிந்து, அடையாளம் கண்டு, குழுவாக்கவும்", - "machine_learning_facial_recognition_model": "முக அங்கீகார மாடெல்", - "machine_learning_facial_recognition_model_description": "மாடெல்கள் அளவின் இறங்கு வரிசையில் பட்டியலிடப்பட்டுள்ளன. பெரிய மாடெல்கள் மெதுவாகவும் அதிக நினைவகத்தைப் பயன்படுத்துகின்றன, ஆனால் சிறந்த முடிவுகளைத் தருகின்றன. மாடலை மாற்றியவுடன் அனைத்து படங்களுக்கும் முகம் கண்டறிதல் வேலையை மீண்டும் இயக்க வேண்டும் என்பதை நினைவில் கொள்ளவும்..", - "machine_learning_facial_recognition_setting": "முக அங்கீகாரத்தை இயக்கவும்", - "machine_learning_facial_recognition_setting_description": "முடக்கப்பட்டிருந்தால், முக அங்கீகாரத்திற்காக படங்கள் குறியாக்கம் செய்யப்படாது மற்றும் ஆய்வுப் பக்கத்தில் உள்ள மக்கள் பிரிவில் நிரப்பப்படாது.", - "machine_learning_max_detection_distance": "அதிகபட்ச கண்டறிதல் தூரம்", - "machine_learning_max_detection_distance_description": "0.001-0.1 வரையிலான இரண்டு படங்களுக்கு இடையே உள்ள அதிகபட்ச தூரம் நகல்களாகக் கருதப்படும். அதிக மதிப்புகள் அதிக நகல்களைக் கண்டறியும், ஆனால் தவறான நேர்மறைகளை ஏற்படுத்தலாம்.", - "machine_learning_max_recognition_distance": "அதிகபட்ச அங்கீகார தூரம்", - "machine_learning_max_recognition_distance_description": "0-2 முதல் ஒரே நபராகக் கருதப்பட வேண்டிய இரண்டு முகங்களுக்கிடையில் அதிகபட்ச தூரம். இதைக் குறைப்பது இரண்டு நபர்களை ஒரே நபராக முத்திரை குத்துவதைத் தடுக்கலாம், அதே நேரத்தில் அதை உயர்த்துவது ஒரே நபரை இரண்டு வெவ்வேறு நபர்களாக பெயரிடுவதைத் தடுக்கலாம். ஒரு நபரை இரண்டாகப் பிரிப்பதை விட இரண்டு நபர்களை ஒன்றிணைப்பது எளிது என்பதை நினைவில் கொள்க, எனவே முடிந்தவரை குறைந்த வாசலின் பக்கத்தில் தவறு செய்யுங்கள்.", - "machine_learning_min_detection_score": "குறைந்தபட்ச கண்டறிதல் மதிப்பெண்", - "machine_learning_min_detection_score_description": "ஒரு முகம் 0-1 முதல் கண்டறியப்படுவதற்கு குறைந்தபட்ச நம்பிக்கை மதிப்பெண். குறைந்த மதிப்புகள் அதிக முகங்களைக் கண்டறியும், ஆனால் தவறான நேர்மறைகளை ஏற்படுத்தக்கூடும்.", - "machine_learning_min_recognized_faces": "குறைந்தபட்ச அங்கீகரிக்கப்பட்ட முகங்கள்", - "machine_learning_min_recognized_faces_description": "ஒரு நபருக்கு உருவாக்கப்பட வேண்டிய அங்கீகரிக்கப்பட்ட முகங்களின் குறைந்தபட்ச எண்ணிக்கை. இதை அதிகரிப்பது, ஒரு நபருக்கு முகம் ஒதுக்கப்படாமல் போகும் வாய்ப்பை அதிகரிக்கும் செலவில், முக அங்கீகாரத்தை மிகவும் துல்லியமாக்குகிறது.", - "machine_learning_ocr": "ஓசிஆர்", - "machine_learning_ocr_description": "படங்களில் உள்ள உரையை அடையாளம் காண இயந்திர கற்றலைப் பயன்படுத்தவும்", - "machine_learning_ocr_enabled": "ஓசிஆர் ஐ இயலச்செய்", - "machine_learning_ocr_enabled_description": "முடக்கப்பட்டால், படங்கள் உரை அடையாளம் காணப்படாது.", - "machine_learning_ocr_max_resolution": "அதிகபட்ச தெளிவுத்திறன்", - "machine_learning_ocr_max_resolution_description": "இந்தத் தெளிவுத்திறனுக்கு மேலே உள்ள மாதிரிக்காட்சிகள், தோற்ற விகிதத்தைப் பாதுகாக்கும் அதே வேளையில், அளவு மாற்றப்படும். அதிக மதிப்புகள் மிகவும் துல்லியமானவை, ஆனால் செயலாக்கவும் அதிக நினைவகத்தைப் பயன்படுத்தவும் அதிக நேரம் எடுக்கும்.", - "machine_learning_ocr_min_detection_score": "குறைந்தபட்ச கண்டறிதல் மதிப்பெண்", - "machine_learning_ocr_min_detection_score_description": "உரையைக் கண்டறிய குறைந்தபட்ச நம்பிக்கை மதிப்பெண் 0-1. குறைந்த மதிப்பெண் அதிக உரையைக் கண்டறியும், ஆனால் தவறான தகவல்க்கு வழிவகுக்கும்.", - "machine_learning_ocr_min_recognition_score": "குறைந்தபட்ச அங்கீகார மதிப்பெண்", - "machine_learning_ocr_min_score_recognition_description": "கண்டறியப்பட்ட உரையை அங்கீகரிக்க குறைந்தபட்ச நம்பிக்கை மதிப்பெண் 0-1 ஆகும். குறைந்த மதிப்பெண் அதிக உரையை அங்கீகரிக்கும், ஆனால் தவறான நேர்மறைகளுக்கு வழிவகுக்கும்.", - "machine_learning_ocr_model": "ஓசிஆர் மாதிரி", - "machine_learning_ocr_model_description": "மொபைல் மாடல்களை விட சர்வர் மாடல்கள் மிகவும் துல்லியமானவை, ஆனால் அதிக நினைவகத்தை செயலாக்கி பயன்படுத்த அதிக நேரம் எடுக்கும்.", - "machine_learning_settings": "இயந்திர கற்றல் அமைப்புகள்", - "machine_learning_settings_description": "இயந்திர கற்றல் அம்சங்கள் மற்றும் அமைப்புகளை நிர்வகிக்கவும்", - "machine_learning_smart_search": "ஸ்மார்ட் தேடல்", - "machine_learning_smart_search_description": "CLIP மாடெலைப் பயன்படுத்தி சொற்பொருளில் படங்களைத் தேடுங்கள்", - "machine_learning_smart_search_enabled": "ஸ்மார்ட் தேடலை இயக்கு", - "machine_learning_smart_search_enabled_description": "முடக்கப்பட்டிருந்தால், ஸ்மார்ட் தேடலுக்காக படங்கள் குறியாக்கம் செய்யப்படாது.", - "machine_learning_url_description": "இயந்திர கற்றல் சேவையகத்தின் முகவரி. ஒன்றுக்கு மேற்பட்ட முகவரி வழங்கப்பட்டால், ஒவ்வொரு சேவையகமும் ஒவ்வொன்றாக வெற்றிகரமாக பதிலளிக்கும் வரை, முதலில் இருந்து கடைசி வரை முயற்சிக்கப்படும். பதிலளிக்காத சேவையகங்கள் மீண்டும் ஆன்லைனில் வரும் வரை தற்காலிகமாகப் புறக்கணிக்கப்படும்.", - "maintenance_settings": "பராமரிப்பு", - "maintenance_settings_description": "இம்மிச்சை பராமரிப்பு முறையில் வைக்கவும்.", - "maintenance_start": "பராமரிப்பு பயன்முறையைத் தொடங்கு", - "maintenance_start_error": "பராமரிப்பு பயன்முறையைத் தொடங்க முடியவில்லை.", - "manage_concurrency": "ஒத்திசைவை நிர்வகிக்கவும்", - "manage_concurrency_description": "வேலை ஒருங்கிணைவை நிர்வகிக்க வேலைகள் பக்கத்திற்குச் செல்லவும்", - "manage_log_settings": "பதிவு அமைப்புகளை நிர்வகிக்கவும்", - "map_dark_style": "இருண்ட தீம்", - "map_enable_description": "மேப்பிங் அம்சங்களை இயக்கவும்", - "map_gps_settings": "வரைபடம் & சி.பி.எச் அமைப்புகள்", - "map_gps_settings_description": "வரைபடம் & சி.பி.எச் (தலைகீழ் சியோகோடிங்) அமைப்புகளை நிர்வகிக்கவும்", - "map_implications": "வரைபட நற்பொருத்தம் வெளிப்புற ஓடு சேவையை நம்பியுள்ளது (diles.immich.cloud)", - "map_light_style": "வெள்ளை தீம்", - "map_manage_reverse_geocoding_settings": "ரிவர்ஸ் ஜியோகோடிங் அமைப்புகளை நிர்வகிக்கவும்", - "map_reverse_geocoding": "புவி இருப்பிடத்தை தீர்மானித்தல்", - "map_reverse_geocoding_enable_description": "புவிஇருப்பிட தீர்மானத்தை செயல்படுத்தவும்", - "map_reverse_geocoding_settings": "புவிஇருப்பிடத்தை தீர்மானித்தல் அமைப்புகள்", - "map_settings": "மேப் & ஜிபிஎஸ் (GPS) அமைப்புகள்", - "map_settings_description": "மேப் அமைப்புகளை நிர்வகிக்கவும்", - "map_style_description": "style.json மேப் தீமுக்கான URL", - "memory_cleanup_job": "நினைவகத்தை சுத்தம் செய்தல்", - "memory_generate_job": "நினைவக உருவாக்கம்", - "metadata_extraction_job": "மெட்டாடேட்டாவைப் பிரித்தெடுக்கவும்", - "metadata_extraction_job_description": "ஜிபிஎஸ் மற்றும் தெளிவுத்திறன் போன்ற ஒவ்வொரு சொத்திலிருந்தும் மெட்டாடேட்டா தகவலைப் பிரித்தெடுக்கவும்", - "metadata_faces_import_setting": "முக இறக்குமதியை இயக்கவும்", - "metadata_faces_import_setting_description": "பட EXIF தரவு மற்றும் பக்கவாட்டு கோப்புகளிலிருந்து முகங்களை இறக்குமதி செய்யுங்கள்", - "metadata_settings": "மேனிலை தரவு அமைப்புகள்", - "metadata_settings_description": "மேனிலை தரவு அமைப்புகளை நிர்வகிக்கவும்", - "migration_job": "இடம்பெயர்தல்", - "migration_job_description": "புகைப்படங்கள் மற்றும் முகங்களுக்கான சிறுபடங்களை (தம்ப்னெயில்) சமீபத்திய கோப்புறை அமைப்பிற்கு மாற்றவும்", - "nightly_tasks_cluster_faces_setting_description": "புதிதாகக் கண்டறியப்பட்ட முகங்களில் முக அங்கீகாரத்தை இயக்கு", - "nightly_tasks_cluster_new_faces_setting": "புதிய முகங்களைக் தொகுதிபடுத்து", - "nightly_tasks_database_cleanup_setting": "தரவுத்தளத்தை சுத்தம் செய்யும் பணிகள்", - "nightly_tasks_database_cleanup_setting_description": "தரவுத்தளத்திலிருந்து பழைய, காலாவதியான தரவை சுத்தம் செய்யவும்", - "nightly_tasks_generate_memories_setting": "நினைவுகளை உருவாக்கு", - "nightly_tasks_generate_memories_setting_description": "சொத்துக்களிலிருந்து புதிய நினைவுகளை உருவாக்கு", - "nightly_tasks_missing_thumbnails_setting": "விடுபட்ட சிறுபடங்களை உருவாக்கு", - "nightly_tasks_missing_thumbnails_setting_description": "சிறுபட உருவாக்கத்திற்காக சிறுபடங்கள் இல்லாமல் சொத்துக்களை வரிசைப்படுத்தவும்", - "nightly_tasks_settings": "இரவு நேரப் பணிகள் அமைப்புகள்", - "nightly_tasks_settings_description": "இரவு நேர பணிகளை நிர்வகி", - "nightly_tasks_start_time_setting": "தொடக்க நேரம்", - "nightly_tasks_start_time_setting_description": "சர்வர் இரவு நேர பணிகளை இயக்கத் தொடங்கும் நேரம்", - "nightly_tasks_sync_quota_usage_setting": "ஒத்திசைவு ஒதுக்கீடு பயன்பாடு", - "nightly_tasks_sync_quota_usage_setting_description": "தற்போதைய பயன்பாட்டின் அடிப்படையில், பயனர் சேமிப்பக ஒதுக்கீட்டைப் புதுப்பிக்கவும்", - "no_paths_added": "ஃபோல்ட்டர் பாதைகள் சேர்க்கப்படவில்லை", - "no_pattern_added": "பேட்டர்ன்் சேர்க்கப்படவில்லை", - "note_apply_storage_label_previous_assets": "குறிப்பு: முன்பு பதிவேற்றிய படங்களுக்கு சேமிப்பக லேபிளைப் பயன்படுத்த, இதை இயக்கவும்", - "note_cannot_be_changed_later": "குறிப்பு: இதை பின்னர் மாற்ற முடியாது!", - "notification_email_from_address": "முகவரியிலிருந்து", - "notification_email_from_address_description": "அனுப்புநரின் மின்னஞ்சல் முகவரி, எடுத்துக்காட்டாக: \"இம்மிச் புகைப்பட சேவையகம் \". மின்னஞ்சல்களை அனுப்ப அனுமதிக்கப்பட்ட முகவரியைப் பயன்படுத்துவதை உறுதிசெய்து கொள்ளுங்கள்.", - "notification_email_host_description": "மின்னஞ்சல் சேவையகத்தின் ஹோஸ்ட் (எடுத்துக்காட்டாக: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "சான்றிதழ் பிழைகளை புறக்கணிக்கவும்", - "notification_email_ignore_certificate_errors_description": "TLS சான்றிதழ் சரிபார்ப்பு பிழைகளை புறக்கணிக்கவும் (பரிந்துரைக்கப்படவில்லை)", - "notification_email_password_description": "மின்னஞ்சல் சேவையகத்துடன் அங்கீகரிக்கும்போது பயன்படுத்த வேண்டிய கடவுச்சொல்", - "notification_email_port_description": "மின்னஞ்சல் சேவையகத்தின் போர்ட் (எ.கா. 25, 465, அல்லது 587)", - "notification_email_secure": "எச்எம்டிபிஎச்", - "notification_email_secure_description": "SMTPS (SMTP மூலம் TLS) பயன்படுத்தவும்", - "notification_email_sent_test_email_button": "சோதனை மின்னஞ்சலை அனுப்பி சேமிக்கவும்", - "notification_email_setting_description": "மின்னஞ்சல் அறிவிப்புகளை அனுப்புவதற்கான அமைப்புகள்", - "notification_email_test_email": "சோதனை மின்னஞ்சல் அனுப்பவும்", - "notification_email_test_email_failed": "சோதனை மின்னஞ்சலை அனுப்ப முடியவில்லை, உங்கள் மதிப்புகளைச் சரிபார்க்கவும்", - "notification_email_test_email_sent": "சோதனை மின்னஞ்சல் {email}க்கு அனுப்பப்பட்டது. உங்கள் இன்பாக்ஸை சரிபார்க்கவும்.", - "notification_email_username_description": "மின்னஞ்சல் சேவையகத்துடன் அங்கீகரிக்கும்போது பயன்படுத்த வேண்டிய பயனர்பெயர்", - "notification_enable_email_notifications": "மின்னஞ்சல் அறிவிப்புகளை இயக்கவும்", - "notification_settings": "அறிவிப்பு அமைப்புகள்", - "notification_settings_description": "மின்னஞ்சல் உட்பட அறிவிப்பு அமைப்புகளை நிர்வகிக்கவும்", - "oauth_auto_launch": "தானியங்கி வெளியீடு", - "oauth_auto_launch_description": "உள்நுழைவுப் பக்கத்திற்குச் செல்லும்போது தானாகவே OAuth உள்நுழைவு ஓட்டத்தைத் தொடங்கவும்", - "oauth_auto_register": "தானியங்கு பதிவு", - "oauth_auto_register_description": "OAuth உடன் உள்நுழைந்த பிறகு தானாகவே புதிய பயனர்களைப் பதிவுசெய்யவும்", - "oauth_button_text": "பட்டன் உரை", - "oauth_client_secret_description": "அவசியம், OAuth வழங்குநரால் PKCE (குறியீட்டுப் பரிமாற்றத்திற்கான ஆதார விசை) ஆதரிக்கப்படாவிட்டால்", - "oauth_enable_description": "OAuth மூலம் உள்நுழைக", - "oauth_mobile_redirect_uri": "மொபைல் வழிமாற்று URI", - "oauth_mobile_redirect_uri_override": "மொபைல் வழிமாற்று URI மேலெழுதுதல்", - "oauth_mobile_redirect_uri_override_description": "''{callback}'' போன்ற மொபைல் URI ஐ OAuth வழங்குநர் அனுமதிக்காதபோது இயக்கவும்", - "oauth_role_claim": "பதவி உரிமைகோரல்", - "oauth_role_claim_description": "இந்தக் கோரிக்கையின் இருப்பின் அடிப்படையில் தானாகவே நிர்வாகி அணுகலை வழங்கவும். கோரிக்கையில் 'பயனர்' அல்லது 'நிர்வாகி' இருக்கலாம்.", - "oauth_settings": "ஓஆத்", - "oauth_settings_description": "OAuth உள்நுழைவு அமைப்புகளை நிர்வகிக்கவும்", - "oauth_settings_more_details": "இந்த அம்சத்தைப் பற்றிய கூடுதல் விவரங்களுக்கு, டாக்ஸ் ஐப் பார்க்கவும்.", - "oauth_storage_label_claim": "சேமிப்பக லேபிள் உரிமைகோரல்", - "oauth_storage_label_claim_description": "பயனரின் சேமிப்பக லேபிளை இந்த உரிமைகோரலின் மதிப்புக்கு தானாக அமைக்கவும்.", - "oauth_storage_quota_claim": "சேமிப்பக ஒதுக்கீடு உரிமைகோரல்", - "oauth_storage_quota_claim_description": "இந்த உரிமைகோரலின் மதிப்பிற்கு பயனரின் சேமிப்பக ஒதுக்கீட்டை தானாக அமைக்கவும்.", - "oauth_storage_quota_default": "இயல்புநிலை சேமிப்பக ஒதுக்கீடு (GiB)", - "oauth_storage_quota_default_description": "GiB இல் உள்ள ஒதுக்கீடு எந்த உரிமைகோரலும் வழங்கப்படாதபோது பயன்படுத்தப்படும் .", - "oauth_timeout": "கோரிக்கை நேரம் முடிந்தது", - "oauth_timeout_description": "கோரிக்கைகளுக்கான காலக்கெடு மில்லி வினாடிகளில்", - "ocr_job_description": "படங்களில் உள்ள உரையை அடையாளம் காண இயந்திர கற்றலைப் பயன்படுத்தவும்", - "password_enable_description": "மின்னஞ்சல் மற்றும் கடவுச்சொல் மூலம் உள்நுழையவும்", - "password_settings": "கடவுச்சொல் உள்நுழைவு", - "password_settings_description": "கடவுச்சொல் உள்நுழைவு அமைப்புகளை நிர்வகிக்கவும்", - "paths_validated_successfully": "அனைத்து பாதைகளும் வெற்றிகரமாக சரிபார்க்கப்பட்டன", - "person_cleanup_job": "நபர் தூய்மைப்படுத்துதல்", - "queue_details": "வரிசை விவரங்கள்", - "quota_size_gib": "ஒதுக்கீடு அளவு (GiB)", - "refreshing_all_libraries": "அனைத்து நூலகங்களையும் புதுப்பிக்கிறது", - "registration": "நிர்வாக பதிவு", - "registration_description": "நீங்கள் கணினியில் முதல் பயனராக இருப்பதால், நீங்கள் நிர்வாகியாக நியமிக்கப்படுவீர்கள் மற்றும் நிர்வாகப் பணிகளுக்குப் பொறுப்பாவீர்கள், மேலும் உங்களால் கூடுதல் பயனர்கள் உருவாக்கப்படுவார்கள்.", - "require_password_change_on_login": "முதல் உள்நுழைவில் பயனர் கடவுச்சொல்லை மாற்ற வேண்டும்", - "reset_settings_to_default": "அமைப்புகளை இயல்புநிலைக்கு மீட்டமைக்கவும்", - "reset_settings_to_recent_saved": "அண்மையில் சேமிக்கப்பட்ட அமைப்புகளுக்கு அமைப்புகளை மீட்டமைக்கவும்", - "scanning_library": "ச்கேனிங் நூலகம்", - "search_jobs": "வேலைகளைத் தேடுங்கள்…", - "send_welcome_email": "வரவேற்பு மின்னஞ்சலை அனுப்பவும்", - "server_external_domain_settings": "வெளிப்புற களம்", - "server_external_domain_settings_description": "HTTP (கள்) உட்பட பொது பகிரப்பட்ட இணைப்புகளுக்கான டொமைன்: //", - "server_public_users": "பொது பயனர்கள்", - "server_public_users_description": "பகிரப்பட்ட ஆல்பங்களில் பயனரைச் சேர்க்கும்போது அனைத்து பயனர்களும் (பெயர் மற்றும் மின்னஞ்சல்) பட்டியலிடப்பட்டுள்ளன. முடக்கப்பட்டால், பயனர் பட்டியல் நிர்வாக பயனர்களுக்கு மட்டுமே கிடைக்கும்.", - "server_settings": "சேவையக அமைப்புகள்", - "server_settings_description": "சேவையக அமைப்புகளை நிர்வகிக்கவும்", - "server_stats_page_description": "நிர்வாக சேவையக புள்ளிவிவரப் பக்கம்", - "server_welcome_message": "வரவேற்பு செய்தி", - "server_welcome_message_description": "உள்நுழைவு (லோகின்) பக்கத்தில் காட்டப்படும் ஒரு செய்தி.", - "settings_page_description": "நிர்வாக அமைப்புகள் பக்கம்", - "sidecar_job": "சைட்கார் மெட்டாடேட்டா", - "sidecar_job_description": "ஃபைல்ஸிலிருந்து சைட்கார் மெட்டாடேட்டாவைக் கண்டறியவும் அல்லது ஒத்திசைக்கவும்", - "slideshow_duration_description": "ஒவ்வொரு படத்தையும் காட்ட வேண்டிய வினாடிகளின் எண்ணிக்கை", - "smart_search_job_description": "அறிவுள்ள தேடலை ஆதரிக்க சொத்துக்களில் இயந்திர கற்றலை இயக்கவும்", - "storage_template_date_time_description": "ஸ்மார்ட் தேடலை ஆதரிக்க சொத்துகளில் இயந்திர கற்றலை இயக்கவும்", - "storage_template_date_time_sample": "மாதிரி நேரம் {date}", - "storage_template_enable_description": "ஸ்டோரேஜ் டெம்ப்ளேட் இயந்திரத்தை இயக்கவும்", - "storage_template_hash_verification_enabled": "ஹாஷ் சரிபார்ப்பு இயக்கப்பட்டது", - "storage_template_hash_verification_enabled_description": "ஹாஷ் சரிபார்ப்பை இயக்குகிறது, தாக்கங்கள் குறித்து உறுதியாக தெரியாவிட்டால் இதை முடக்க வேண்டாம்", - "storage_template_migration": "ஸ்டோரேஜ் டெம்ப்ளேட் இடம்பெயர்வு", - "storage_template_migration_description": "ஏற்கனவே பதிவேற்றிய புகைப்படங்களுக்கு தற்போதைய {template} ஐப் பயன்படுத்தவும்", - "storage_template_migration_info": "சேமிப்பக வார்ப்புரு அனைத்து நீட்டிப்புகளையும் சிறிய எழுத்துக்களுக்கு மாற்றும். டெம்ப்ளேட் மாற்றங்கள் புதிய படங்களுக்கு மட்டுமே பொருந்தும். முன்பு பதிவேற்றிய படங்களுக்கு டெம்ப்ளேட்டைப் பயன்படுத்த, {job} ஐ இயக்கவும்.", - "storage_template_migration_job": "ஸ்டோரேஜ் டெம்ப்ளேட் இடம்பெயர்வு வேலை", - "storage_template_more_details": "இந்த அம்சத்தைப் பற்றிய கூடுதல் விவரங்களுக்கு, Storage Template மற்றும் அதன் தாக்கங்கள் ஐப் பார்க்கவும்", - "storage_template_onboarding_description_v2": "இயக்கப்பட்டால், இந்த அம்சம் பயனர் வரையறுக்கப்பட்ட டெம்ப்ளேட்டின் அடிப்படையில் கோப்புகளை தானாக ஒழுங்கமைக்கும். மேலும் தகவலுக்கு, ஆவணங்கள் ஐப் பார்க்கவும்.", - "storage_template_path_length": "தோராயமான பாதை நீள வரம்பு: {length, number}/{limit, number}", - "storage_template_settings": "ஸ்டோரேஜ் டெம்ப்ளேட்", - "storage_template_settings_description": "பதிவேற்ற புகைப்படங்களின் கோப்புறை அமைப்பு மற்றும் கோப்பு பெயரை நிர்வகிக்கவும்", - "storage_template_user_label": "{label} என்பது பயனரின் சேமிப்பக லேபிள்", - "system_settings": "அமைப்புகள்", - "tag_cleanup_job": "குறிச்சொல் தூய்மைப்படுத்துதல்", - "template_email_available_tags": "உங்கள் வார்ப்புருவில் பின்வரும் மாறிகளைப் பயன்படுத்தலாம்: {tags}", - "template_email_if_empty": "வார்ப்புரு காலியாக இருந்தால், இயல்புநிலை மின்னஞ்சல் பயன்படுத்தப்படும்.", - "template_email_invite_album": "ஆல்பம் வார்ப்புருவை அழைக்கவும்", - "template_email_preview": "முன்னோட்டம்", - "template_email_settings": "மின்னஞ்சல் வார்ப்புருக்கள்", - "template_email_update_album": "ஆல்பம் வார்ப்புருவைப் புதுப்பிக்கவும்", - "template_email_welcome": "மின்னஞ்சல் வார்ப்புருவை வரவேற்கிறோம்", - "template_settings": "அறிவிப்பு வார்ப்புருக்கள்", - "template_settings_description": "அறிவிப்புகளுக்கு தனிப்பயன் வார்ப்புருக்கள் நிர்வகிக்கவும்", - "theme_custom_css_settings": "தனிப்பயன் CSS", - "theme_custom_css_settings_description": "CSS அம்சம் Immich வடிவமைப்பை தனிப்பயனாக்க அனுமதிக்கிறது.", - "theme_settings": "தீம் அமைப்புகள்", - "theme_settings_description": "இம்மிச் வலை இடைமுகத்தின் தனிப்பயனாக்கத்தை நிர்வகிக்கவும்", - "thumbnail_generation_job": "சிறுபடங்களை உருவாக்கவும்", - "thumbnail_generation_job_description": "ஒவ்வொரு சொத்துக்கும் பெரிய, சிறிய மற்றும் மங்கலான சிறு உருவங்களையும், ஒவ்வொரு நபருக்கும் சிறு உருவங்களையும் உருவாக்குங்கள்", - "transcoding_acceleration_api": "முடுக்கம் பநிஇ", - "transcoding_acceleration_api_description": "டிரான்ச்கோடிங்கை துரிதப்படுத்த உங்கள் சாதனத்துடன் தொடர்பு கொள்ளும் பநிஇ. இந்த அமைப்பு 'சிறந்த முயற்சி': இது தோல்வியின் மீதான மென்பொருள் டிரான்ச்கோடிங்கிற்கு குறைகிறது. உங்கள் வன்பொருளைப் பொறுத்து VP9 வேலை செய்யலாம் அல்லது இல்லாமல் இருக்கலாம்.", - "transcoding_acceleration_nvenc": "NVENC (என்விடியா சி.பீ.யூ தேவை)", - "transcoding_acceleration_qsv": "விரைவான ஒத்திசைவு (7 வது சென் இன்டெல் சிபியு அல்லது அதற்குப் பிறகு தேவைப்படுகிறது)", - "transcoding_acceleration_rkmpp": "ஆர்.கே.எம்.பி.பி (ராக்சிப் சொக்சில் மட்டுமே)", - "transcoding_acceleration_vaapi": "ஆண்", - "transcoding_accepted_audio_codecs": "ஏற்றுக்கொள்ளப்பட்ட ஆடியோ கோடெக்குகள்", - "transcoding_accepted_audio_codecs_description": "எந்த ஆடியோ கோடெக்குகளை மாற்ற வேண்டிய அவசியமில்லை என்பதைத் தேர்ந்தெடுக்கவும். சில டிரான்ச்கோட் கொள்கைகளுக்கு மட்டுமே பயன்படுத்தப்படுகிறது.", - "transcoding_accepted_containers": "ஏற்றுக்கொள்ளப்பட்ட கொள்கலன்கள்", - "transcoding_accepted_containers_description": "எந்த கொள்கலன் வடிவங்களை MP4 க்கு மறுபரிசீலனை செய்ய தேவையில்லை என்பதைத் தேர்ந்தெடுக்கவும். சில டிரான்ச்கோட் கொள்கைகளுக்கு மட்டுமே பயன்படுத்தப்படுகிறது.", - "transcoding_accepted_video_codecs": "ஏற்றுக்கொள்ளப்பட்ட வீடியோ கோடெக்குகள்", - "transcoding_accepted_video_codecs_description": "எந்த வீடியோ கோடெக்குகளை மாற்ற வேண்டிய அவசியமில்லை என்பதைத் தேர்ந்தெடுக்கவும். சில டிரான்ச்கோட் கொள்கைகளுக்கு மட்டுமே பயன்படுத்தப்படுகிறது.", - "transcoding_advanced_options_description": "விருப்பங்கள் பெரும்பாலான பயனர்கள் மாற்ற தேவையில்லை", - "transcoding_audio_codec": "ஆடியோ கோடெக்", - "transcoding_audio_codec_description": "ஓபச் மிக உயர்ந்த தரமான விருப்பமாகும், ஆனால் பழைய சாதனங்கள் அல்லது மென்பொருளுடன் குறைந்த பொருந்தக்கூடிய தன்மையைக் கொண்டுள்ளது.", - "transcoding_bitrate_description": "மேக்ச் பிட்ரேட்டை விட அதிகமான வீடியோக்கள் அல்லது ஏற்றுக்கொள்ளப்பட்ட வடிவத்தில் இல்லை", - "transcoding_codecs_learn_more": "இங்கே பயன்படுத்தப்படும் சொற்களைப் பற்றி மேலும் அறிய, H.264 கோடெக், HEVC கோடெக் மற்றும் VP9 க்கான கோடெக் FFMPEG ஆவணங்களைப் பார்க்கவும்.", - "transcoding_constant_quality_mode": "நிலையான தர முறை", - "transcoding_constant_quality_mode_description": "CQP ஐ விட ICQ சிறந்தது, ஆனால் சில வன்பொருள் முடுக்கம் சாதனங்கள் இந்த பயன்முறையை ஆதரிக்கவில்லை. இந்த விருப்பத்தை அமைப்பது தர அடிப்படையிலான குறியாக்கத்தைப் பயன்படுத்தும் போது குறிப்பிட்ட பயன்முறையை விரும்புகிறது. NVENC ஆல் புறக்கணிக்கப்பட்டது, ஏனெனில் இது ICQ ஐ ஆதரிக்காது.", - "transcoding_constant_rate_factor": "நிலையான வீத காரணி (-crf)", - "transcoding_constant_rate_factor_description": "வீடியோ தர நிலை. வழக்கமான மதிப்புகள் H.264 க்கு 23, HEVC க்கு 28, VP9 க்கு 31, மற்றும் AV1 க்கு 35 ஆகும். குறைவானது சிறந்தது, ஆனால் பெரிய கோப்புகளை உருவாக்குகிறது.", - "transcoding_disabled_description": "எந்த வீடியோக்களையும் டிரான்ச்கோட் செய்யாதீர்கள், சில வாடிக்கையாளர்களின் பிளேபேக்கை உடைக்கலாம்", - "transcoding_encoding_options": "குறியீட்டு விருப்பங்கள்", - "transcoding_encoding_options_description": "குறியிடப்பட்ட வீடியோக்களுக்கான கோடெக்குகள், தெளிவுத்திறன், தரம் மற்றும் பிற விருப்பங்களை அமைக்கவும்", - "transcoding_hardware_acceleration": "வன்பொருள் முடுக்கம்", - "transcoding_hardware_acceleration_description": "பரிசோதனை: வேகமான டிரான்ஸ்கோடிங் ஆனால் அதே பிட்ரேட்டில் தரத்தைக் குறைக்கலாம்", - "transcoding_hardware_decoding": "வன்பொருள் டிகோடிங்", - "transcoding_hardware_decoding_setting_description": "குறியாக்கத்தை விரைவுபடுத்துவதற்கு பதிலாக இறுதி முதல் இறுதி முடுக்கம் ஆகியவற்றை செயல்படுத்துகிறது. எல்லா வீடியோக்களிலும் வேலை செய்யக்கூடாது.", - "transcoding_max_b_frames": "அதிகபட்ச பி-பிரேம்கள்", - "transcoding_max_b_frames_description": "அதிக மதிப்புகள் சுருக்க செயல்திறனை மேம்படுத்துகின்றன, ஆனால் குறியாக்கத்தை மெதுவாக்குகின்றன. பழைய சாதனங்களில் வன்பொருள் முடுக்கம் உடன் பொருந்தாது. 0 பி -பிரேம்களை முடக்குகிறது, அதே நேரத்தில் -1 இந்த மதிப்பை தானாக அமைக்கிறது.", - "transcoding_max_bitrate": "அதிகபட்ச பிட்ரேட்", - "transcoding_max_bitrate_description": "அதிகபட்ச பிட்ரேட்டை அமைப்பது, தரத்திற்கு குறைந்த செலவில் கோப்பு அளவுகளை மேலும் கணிக்கக்கூடியதாக மாற்றும். 720p இல், வழக்கமான மதிப்புகள் VP9 அல்லது HEVCக்கு 2600 kbit/s அல்லது H.264க்கு 4500 kbit/s ஆகும். 0 என அமைத்தால் முடக்கப்படும். அலகு எதுவும் குறிப்பிடப்படாதபோது, k (kbit/sக்கு) கருதப்படுகிறது; எனவே 5000, 5000k மற்றும் 5M (Mbit/s க்கு) சமமானவை.", - "transcoding_max_keyframe_interval": "அதிகபட்ச கீஃப்ரேம் இடைவெளி", - "transcoding_max_keyframe_interval_description": "கீஃப்ரேம்களுக்கு இடையில் அதிகபட்ச பிரேம் தூரத்தை அமைக்கிறது. குறைந்த மதிப்புகள் சுருக்க செயல்திறனை மோசமாக்குகின்றன, ஆனால் தேடல் நேரங்களை மேம்படுத்துகின்றன, மேலும் வேகமான இயக்கத்துடன் காட்சிகளில் தரத்தை மேம்படுத்தலாம். 0 இந்த மதிப்பை தானாக அமைக்கிறது.", - "transcoding_optimal_description": "இலக்கு தீர்மானத்தை விட உயர்ந்த வீடியோக்கள் அல்லது ஏற்றுக்கொள்ளப்பட்ட வடிவத்தில் இல்லை", - "transcoding_policy": "குறிமாற்றக் கொள்கை", - "transcoding_policy_description": "ஒரு வீடியோ எப்போது குறிமாற்றம் செய்யப்படும் என்பதை அமைக்கவும்", - "transcoding_preferred_hardware_device": "விருப்பமான வன்பொருள் சாதனம்", - "transcoding_preferred_hardware_device_description": "VAAPI மற்றும் QSV க்கு மட்டுமே பொருந்தும். வன்பொருள் டிரான்ச்கோடிங்கிற்கு பயன்படுத்தப்படும் ட்ரை முனையை அமைக்கிறது.", - "transcoding_preset_preset": "முன்னமைக்கப்பட்ட (-பிரசெட்)", - "transcoding_preset_preset_description": "சுருக்க விரைவு. மெதுவான முன்னமைவுகள் சிறிய கோப்புகளை உருவாக்குகின்றன, மேலும் ஒரு குறிப்பிட்ட பிட்ரேட்டை குறிவைக்கும் போது தரத்தை அதிகரிக்கின்றன. VP9 'வேகமாக' மேலே உள்ள வேகத்தை புறக்கணிக்கிறது.", - "transcoding_reference_frames": "குறிப்பு பிரேம்கள்", - "transcoding_reference_frames_description": "கொடுக்கப்பட்ட சட்டகத்தை சுருக்கும்போது குறிப்பிட வேண்டிய பிரேம்களின் எண்ணிக்கை. அதிக மதிப்புகள் சுருக்க செயல்திறனை மேம்படுத்துகின்றன, ஆனால் குறியாக்கத்தை மெதுவாக்குகின்றன. 0 இந்த மதிப்பை தானாக அமைக்கிறது.", - "transcoding_required_description": "ஏற்றுக்கொள்ளப்பட்ட வடிவத்தில் இல்லாத வீடியோக்கள் மட்டுமே", - "transcoding_settings": "வீடியோ டிரான்ச்கோடிங் அமைப்புகள்", - "transcoding_settings_description": "எந்த வீடியோக்களை டிரான்ஸ்கோட் செய்ய வேண்டும், அவற்றை எவ்வாறு செயலாக்க வேண்டும் என்பதை நிர்வகிக்கவும்", - "transcoding_target_resolution": "இலக்கு தீர்மானம்", - "transcoding_target_resolution_description": "அதிக தீர்மானங்கள் அதிக விவரங்களை பாதுகாக்க முடியும், ஆனால் குறியாக்க அதிக நேரம் எடுக்கும், பெரிய கோப்பு அளவுகளைக் கொண்டிருக்கலாம், மேலும் பயன்பாட்டு மறுமொழியைக் குறைக்கலாம்.", - "transcoding_temporal_aq": "தம்போர்ல்", - "transcoding_temporal_aq_description": "NVENC க்கு மட்டுமே பொருந்தும். டெம்போரல் அடாப்டிவ் குவாண்டேசேசன் உயர் விவரம், குறைந்த இயக்கக் காட்சிகளின் தரத்தை அதிகரிக்கிறது. பழைய சாதனங்களுடன் இணங்காமல் இருக்கலாம்.", - "transcoding_threads": "நூல்கள்", - "transcoding_threads_description": "அதிக மதிப்புகள் விரைவான குறியாக்கத்திற்கு வழிவகுக்கும், ஆனால் செயலில் இருக்கும்போது மற்ற பணிகளைச் செயலாக்க சேவையகத்திற்கு குறைந்த இடத்தை விட்டு விடுங்கள். இந்த மதிப்பு சிபியு கோர்களின் எண்ணிக்கையை விட அதிகமாக இருக்கக்கூடாது. 0 என அமைக்கப்பட்டால் பயன்பாட்டை அதிகரிக்கிறது.", - "transcoding_tone_mapping": "தொனி-மேப்பிங்", - "transcoding_tone_mapping_description": "எச்.டி.ஆராக மாற்றப்படும்போது எச்.டி.ஆர் வீடியோக்களின் தோற்றத்தை பாதுகாக்க முயற்சிகள். ஒவ்வொரு வழிமுறையும் வண்ணம், விவரம் மற்றும் பிரகாசத்திற்கு வெவ்வேறு பரிமாற்றங்களை உருவாக்குகிறது. அபிள் விவரங்களை பாதுகாக்கிறார், மொபியச் நிறத்தை பாதுகாக்கிறார், மற்றும் ரெய்ன்ஆர்ட் பிரகாசத்தை பாதுகாக்கிறார்.", - "transcoding_transcode_policy": "டிரான்ச்கோட் கொள்கை", - "transcoding_transcode_policy_description": "ஒரு வீடியோ எப்போது மாற்றப்பட வேண்டும் என்பதற்கான கொள்கை. எச்.டி.ஆர் வீடியோக்கள் எப்போதும் டிரான்ச்கோட் செய்யப்படும் (டிரான்ச்கோடிங் முடக்கப்பட்டிருந்தால் தவிர).", - "transcoding_two_pass_encoding": "இரண்டு-பாச் குறியாக்கம்", - "transcoding_two_pass_encoding_setting_description": "சிறந்த குறியாக்கப்பட்ட வீடியோக்களை உருவாக்க இரண்டு பாச்களில் டிரான்ச்கோட். மேக்ச் பிட்ரேட் இயக்கப்பட்டிருக்கும்போது (H.264 மற்றும் HEVC உடன் வேலை செய்ய இது தேவைப்படுகிறது), இந்த பயன்முறை அதிகபட்ச பிட்ரேட்டை அடிப்படையாகக் கொண்ட பிட்ரேட் வரம்பைப் பயன்படுத்துகிறது மற்றும் CRF ஐ புறக்கணிக்கிறது. VP9 ஐப் பொறுத்தவரை, அதிகபட்ச பிட்ரேட் முடக்கப்பட்டிருந்தால் CRF ஐப் பயன்படுத்தலாம்.", - "transcoding_video_codec": "வீடியோ கோடெக்", - "transcoding_video_codec_description": "VP9 அதிக செயல்திறன் மற்றும் வலை பொருந்தக்கூடிய தன்மையைக் கொண்டுள்ளது, ஆனால் டிரான்ச்கோடிற்கு அதிக நேரம் எடுக்கும். HEVC இதேபோல் செயல்படுகிறது, ஆனால் குறைந்த வலை பொருந்தக்கூடிய தன்மையைக் கொண்டுள்ளது. H.264 பரவலாக இணக்கமானது மற்றும் டிரான்ச்கோடு விரைவானது, ஆனால் மிகப் பெரிய கோப்புகளை உருவாக்குகிறது. ஏ.வி 1 மிகவும் திறமையான கோடெக் ஆனால் பழைய சாதனங்களில் உதவி இல்லை.", - "trash_enabled_description": "குப்பை அம்சங்களை இயக்கவும்", - "trash_number_of_days": "நாட்களின் எண்ணிக்கை", - "trash_number_of_days_description": "சொத்துக்களை நிரந்தரமாக அகற்றுவதற்கு முன் குப்பைத்தொட்டியில் வைத்திருக்க நாட்கள் எண்ணிக்கை", - "trash_settings": "குப்பை அமைப்புகள்", - "trash_settings_description": "குப்பை அமைப்புகளை நிர்வகிக்கவும்", - "unlink_all_oauth_accounts": "அனைத்து OAuth கணக்குகளின் இணைப்பையும் நீக்கு", - "unlink_all_oauth_accounts_description": "புதிய வழங்குநருக்கு மாறுவதற்கு முன், அனைத்து OAuth கணக்குகளின் இணைப்பையும் நீக்க நினைவில் கொள்ளுங்கள்.", - "unlink_all_oauth_accounts_prompt": "எல்லா OAuth கணக்குகளின் இணைப்பையும் நீக்க விரும்புகிறீர்களா? இது ஒவ்வொரு பயனருக்கும் OAuth ஐடியை மீட்டமைக்கும், மேலும் இதை திரும்பப் பெறு முடியாது.", - "user_cleanup_job": "பயனர் தூய்மைப்படுத்துதல்", - "user_delete_delay": "{user}இன் கணக்கு மற்றும் சொத்துக்கள் {delay, plural, one {# நாள்} other {# நாள்கள்}}இல் நிரந்தர நீக்கத் திட்டமிடப்படும்.", - "user_delete_delay_settings": "தாமதத்தை நீக்கு", - "user_delete_delay_settings_description": "எண் of days after நீக்கும் பெறுநர் permanently நீக்கு a user's account and assets. நீக்குவதற்கு தயாராக இருக்கும் பயனர்களைச் சரிபார்க்க பயனர் நீக்குதல் வேலை நள்ளிரவில் இயங்குகிறது. இந்த அமைப்பில் மாற்றங்கள் அடுத்த மரணதண்டனையில் மதிப்பீடு செய்யப்படும்.", - "user_delete_immediately": " {user} இன் கணக்கு மற்றும் சொத்துக்கள் நிரந்தர நீக்குதலுக்காக வரிசையில் நிற்கப்படும் உடனடியாக .", - "user_delete_immediately_checkbox": "உடனடியாக நீக்க பயனர் மற்றும் சொத்துக்கள்", - "user_details": "பயனர் விவரங்கள்", - "user_management": "பயனர் மேலாண்மை", - "user_password_has_been_reset": "பயனரின் கடவுச்சொல் மீட்டமைக்கப்பட்டுள்ளது:", - "user_password_reset_description": "தயவுசெய்து தற்காலிக கடவுச்சொல்லை பயனருக்கு வழங்கவும், அவர்களின் அடுத்த உள்நுழைவில் கடவுச்சொல்லை மாற்ற வேண்டும் என்று அவர்களுக்குத் தெரிவிக்கவும்.", - "user_restore_description": " {user} இன் கணக்கு மீட்டெடுக்கப்படும்.", - "user_restore_scheduled_removal": "பயனரை மீட்டமை - {date, date, long} இல் திட்டமிடப்பட்ட நீக்குதல்", - "user_settings": "பயனர் அமைப்புகள்", - "user_settings_description": "பயனர் அமைப்புகளை நிர்வகிக்கவும்", - "user_successfully_removed": "பயனர் {email} வெற்றிகரமாக அகற்றப்பட்டது.", - "users_page_description": "நிர்வாக பயனர்கள் பக்கம்", - "version_check_enabled_description": "பதிப்பு சரிபார்ப்பு இயக்கவும்", - "version_check_implications": "பதிப்பு சரிபார்ப்பு அம்சம் github .com உடனான அவ்வப்போது தொடர்புகொள்வதை நம்பியுள்ளது", - "version_check_settings": "பதிப்பு சோதனை", - "version_check_settings_description": "புதிய பதிப்பு அறிவிப்பை இயக்கவும்/முடக்கவும்", - "video_conversion_job": "டிரான்ச்கோட் வீடியோக்கள்", - "video_conversion_job_description": "உலாவிகள் மற்றும் சாதனங்களுடன் பரந்த பொருந்தக்கூடிய டிரான்ச்கோட் வீடியோக்கள்" - }, - "admin_email": "நிர்வாக மின்னஞ்சல்", - "admin_password": "நிர்வாகி கடவுச்சொல்", - "administration": "நிர்வாகம்", - "advanced": "மேம்பட்ட", - "advanced_settings_enable_alternate_media_filter_subtitle": "மாற்று அளவுகோல்களின் அடிப்படையில் ஒத்திசைவின் போது மீடியாவை வடிகட்ட இந்த விருப்பத்தைப் பயன்படுத்தவும். எல்லா ஆல்பங்களையும் ஆப்ஸ் கண்டறிவதில் சிக்கல்கள் இருந்தால் மட்டுமே இதை முயற்சிக்கவும்.", - "advanced_settings_enable_alternate_media_filter_title": "[பரிசோதனைக்கு உட்பட்டது] மாற்று சாதன ஆல்ப ஒத்திசைவு வடிப்பானைப் பயன்படுத்தவும்", - "advanced_settings_log_level_title": "பதிவு நிலை: {level}", - "advanced_settings_prefer_remote_subtitle": "சில சாதனங்கள் உள் சொத்துக்களிலிருந்து சிறுபடங்களை ஏற்றுவதில் மிகவும் மெதுவாக இருக்கும். அதற்கு பதிலாக சர்வர் படங்களை ஏற்ற இந்த அமைப்பைச் செயல்படுத்தவும்.", - "advanced_settings_prefer_remote_title": "ரிமோட் படங்களுக்கு முன்னுரிமை கொடு", - "advanced_settings_proxy_headers_subtitle": "ஒவ்வொரு நெட்வொர்க் கோரிக்கையுடனும் இம்மிச் அனுப்ப வேண்டிய ப்ராக்ஸி தலைப்புகளை வரையறுக்கவும்", - "advanced_settings_proxy_headers_title": "தனிப்பயன் பதிலாள் தலைப்புகள் [பரிசோதனை]", - "advanced_settings_readonly_mode_subtitle": "புகைப்படங்களை பார்ப்பதற்கு மட்டுமே அனுமதிக்கும் படிப்பதற்கு மட்டும் பயன்முறையை இயக்குகிறது, பல படங்களைத் தேர்ந்தெடுப்பது, பகிர்தல், அனுப்புதல், நீக்குதல் போன்ற அனைத்தும் முடக்கப்பட்டுள்ளன. பிரதான திரையில் இருந்து பயனர் அவதார் வழியாக படிக்க மட்டும் என்பதை இயக்கு/முடக்கு", - "advanced_settings_readonly_mode_title": "படிக்க-மட்டும் பயன்முறை", - "advanced_settings_self_signed_ssl_subtitle": "சர்வர் எண்ட்பாயிண்டிற்கான SSL சான்றிதழ் சரிபார்ப்பை தவிர்க்கிறது. சுய கையொப்பமிட்ட சான்றிதழ்களுக்கு இது தேவையானது.", - "advanced_settings_self_signed_ssl_title": "தன்வய கையொப்பமிட்ட SSL சான்றிதழ்களை இசைவு [பரிசோதனை]", - "advanced_settings_sync_remote_deletions_subtitle": "இணையத்தில் நடவடிக்கை எடுக்கப்படும்போது, இந்தச் சாதனத்தில் உள்ள ஒரு சொத்தை தானாகவே நீக்கவும் அல்லது மீட்டெடுக்கவும்", - "advanced_settings_sync_remote_deletions_title": "தொலைநிலை நீக்குதல்களை ஒத்திசைக்கவும் [பரிசோதனைக்கு உட்பட்டது]", - "advanced_settings_tile_subtitle": "மேம்பட்ட பயனர் அமைப்புகள்", - "advanced_settings_troubleshooting_subtitle": "சரிசெய்தலுக்கு கூடுதல் அம்சங்களை இயக்கவும்", - "advanced_settings_troubleshooting_title": "சரிசெய்தல்", - "age_months": "அகவை {months, plural, one {# திங்கள்} other {# திங்கள்கள்}}", - "age_year_months": "அகவை 1 ஆண்டு, {months, plural, one {# திங்கள்} other {# திங்கள்கள்}}", - "age_years": "{years, plural, other {அகவை #}}", - "album": "ஆல்பம்", - "album_added": "ஆல்பம் சேர்க்கப்பட்டது", - "album_added_notification_setting_description": "பகிரப்பட்ட ஆல்பத்தில் நீங்கள் சேர்க்கப்படும்போது மின்னஞ்சல் அறிவிப்பைப் பெறுங்கள்", - "album_cover_updated": "ஆல்பம் கவர் புதுப்பிக்கப்பட்டது", - "album_delete_confirmation": "{album} ஆல்பத்தை நீக்க விரும்புகிறீர்களா?", - "album_delete_confirmation_description": "இந்த ஆல்பம் பகிரப்பட்டால், மற்ற பயனர்களால் இதை அணுக முடியாது.", - "album_deleted": "ஆல்பம் நீக்கப்பட்டது", - "album_info_card_backup_album_excluded": "விலக்கப்பட்டது", - "album_info_card_backup_album_included": "சேர்க்கப்பட்டுள்ளது", - "album_info_updated": "ஆல்பம் செய்தி புதுப்பிக்கப்பட்டது", - "album_leave": "ஆல்பத்தை விடுங்கள்?", - "album_leave_confirmation": "நீங்கள் நிச்சயமாக {album}ஐ விட்டு வெளியேற விரும்புகிறீர்களா?", - "album_name": "ஆல்பத்தின் பெயர்", - "album_options": "ஆல்பம் விருப்பங்கள்", - "album_remove_user": "பயனரை அகற்றவா?", - "album_remove_user_confirmation": "{user} ஐ அகற்ற விரும்புகிறீர்களா?", - "album_search_not_found": "உங்கள் தேடலுடன் பொருந்தக்கூடிய ஆல்பங்கள் எதுவும் இல்லை", - "album_share_no_users": "இந்த ஆல்பத்தை நீங்கள் எல்லா பயனர்களுடனும் பகிர்ந்து கொண்டதாகத் தெரிகிறது அல்லது பகிர்வதற்கு உங்களிடம் எந்த பயனரும் இல்லை.", - "album_summary": "ஆல்பம் சுருக்கம்", - "album_updated": "ஆல்பம் புதுப்பிக்கப்பட்டது", - "album_updated_setting_description": "பகிரப்பட்ட ஆல்பத்தில் புதிய சொத்துக்கள் இருக்கும்போது மின்னஞ்சல் அறிவிப்பைப் பெறுங்கள்", - "album_user_left": "இடது {album}", - "album_user_removed": "அகற்றப்பட்டது {user}", - "album_viewer_appbar_delete_confirm": "இந்த ஆல்பத்தை உங்கள் கணக்கிலிருந்து நீக்க விரும்புகிறீர்களா?", - "album_viewer_appbar_share_err_delete": "ஆல்பம் நீக்க முடியவில்லை", - "album_viewer_appbar_share_err_leave": "ஆல்பம் விட்டு வெளியேற முடியவில்லை", - "album_viewer_appbar_share_err_remove": "ஆல்பத்திலிருந்து சொத்துக்களை அகற்றுவதில் சிக்கல்கள் உள்ளன", - "album_viewer_appbar_share_err_title": "ஆல்பத்தின் தலைப்பை மாற்ற முடியவில்லை", - "album_viewer_appbar_share_leave": "ஆல்பத்தை விட்டு வெளியேறு", - "album_viewer_appbar_share_to": "பகிரு", - "album_viewer_page_share_add_users": "பயனர்களைச் சேர்க்கவும்", - "album_with_link_access": "இணைப்பு உள்ள எவரும் இந்த ஆல்பத்தில் புகைப்படங்களையும் நபர்களையும் பார்க்கட்டும்.", - "albums": "ஆல்பம்", - "albums_count": "{count, plural, one {{count, number} தொகுப்பு} other {{count, number} தொகுப்புகள்}}", - "albums_default_sort_order": "இயல்புநிலை ஆல்பம் வரிசையாக்கம்", - "albums_default_sort_order_description": "புதிய ஆல்பங்களை உருவாக்கும்போது ஆரம்ப சொத்து வரிசைப்படுத்தல் வரிசை.", - "albums_feature_description": "பிற பயனர்களுடன் பகிர்ந்து கொள்ளக்கூடிய சொத்துக்களின் தொகுப்புகள்.", - "albums_on_device_count": "சாதனத்தில் ஆல்பங்கள் ({count})", - "all": "அனைத்தும்", - "all_albums": "அனைத்து ஆல்பங்களும்", - "all_people": "அனைத்து மக்களும்", - "all_videos": "அனைத்து வீடியோக்களும்", - "allow_dark_mode": "இருண்ட பயன்முறையை அனுமதிக்கவும்", - "allow_edits": "திருத்தங்களை அனுமதிக்கவும்", - "allow_public_user_to_download": "பொது பயனரை பதிவிறக்கம் செய்ய அனுமதிக்கவும்", - "allow_public_user_to_upload": "பொது பயனரை பதிவேற்ற அனுமதிக்கவும்", - "allowed": "அனுமதித்த", - "alt_text_qr_code": "QR குறியீடு படம்", - "anti_clockwise": "கடிகார எதிர்ப்பு", - "api_key": "பநிஇ விசை", - "api_key_description": "இந்த மதிப்பு ஒரு முறை மட்டுமே காண்பிக்கப்படும். சாளரத்தை மூடுவதற்கு முன் அதை நகலெடுக்க மறக்காதீர்கள்.", - "api_key_empty": "உங்கள் பநிஇ விசை பெயர் காலியாக இருக்கக்கூடாது", - "api_keys": "பநிஇ விசைகள்", - "app_architecture_variant": "மாறுபாடு (கட்டிடக்கலை)", - "app_bar_signout_dialog_content": "நீங்கள் நிச்சயமாக வெளியேற விரும்புகிறீர்களா?", - "app_bar_signout_dialog_ok": "ஆம்", - "app_bar_signout_dialog_title": "வெளியேறு", - "app_download_links": "ஆப் பதிவிறக்க இணைப்புகள்", - "app_settings": "பயன்பாட்டு அமைப்புகள்", - "app_stores": "ஆப் ச்டோர்கள்", - "app_update_available": "பயன்பாட்டு புதுப்பிப்பு கிடைக்கிறது", - "appears_in": "தோன்றும்", - "apply_count": "போடு ({count, number})", - "archive": "காப்பகம்", - "archive_action_prompt": "{count} காப்பகத்தில் சேர்க்கப்பட்டது", - "archive_or_unarchive_photo": "காப்பகம் அல்லது செயலற்ற புகைப்படம்", - "archive_page_no_archived_assets": "காப்பகப்படுத்தப்பட்ட சொத்துக்கள் எதுவும் கிடைக்கவில்லை", - "archive_page_title": "காப்பகம் ({count})", - "archive_size": "காப்பக அளவு", - "archive_size_description": "பதிவிறக்கங்களுக்கான காப்பக அளவை உள்ளமைக்கவும் (கிபில்)", - "archived": "காப்பகப்படுத்தப்பட்டது", - "archived_count": "{count, plural, other {காப்பகப்படுத்தப்பட்டது #}}", - "are_these_the_same_person": "இவர்கள் ஒரே நபரா?", - "are_you_sure_to_do_this": "இதை நீங்கள் செய்ய விரும்புகிறீர்களா?", - "asset_action_delete_err_read_only": "சொத்து (களை) மட்டுமே படிக்க முடியாது", - "asset_action_share_err_offline": "இணைப்பில்லாத சொத்து (களை) பெற முடியாது, தவிர்க்கவும்", - "asset_added_to_album": "ஆல்பத்தில் சேர்க்கப்பட்டது", - "asset_adding_to_album": "ஆல்பத்தில் சேர்க்கிறது…", - "asset_description_updated": "சொத்து விளக்கம் புதுப்பிக்கப்பட்டுள்ளது", - "asset_filename_is_offline": "சொத்து {filename} ஆஃப்லைனில் உள்ளது", - "asset_has_unassigned_faces": "சொத்து ஒதுக்கப்படாத முகங்களைக் கொண்டுள்ளது", - "asset_hashing": "ஏசிங்…", - "asset_list_group_by_sub_title": "குழு", - "asset_list_layout_settings_dynamic_layout_title": "மாறும் தளவமைப்பு", - "asset_list_layout_settings_group_automatically": "தானியங்கி", - "asset_list_layout_settings_group_by": "மூலம் குழு சொத்துக்கள்", - "asset_list_layout_settings_group_by_month_day": "மாதம் + நாள்", - "asset_list_layout_sub_title": "மனையமைவு", - "asset_list_settings_subtitle": "புகைப்பட கட்டம் தளவமைப்பு அமைப்புகள்", - "asset_list_settings_title": "புகைப்பட கட்டம்", - "asset_offline": "சொத்து ஆஃப்லைனில்", - "asset_offline_description": "இந்த வெளிப்புற சொத்து இனி வட்டில் காணப்படவில்லை. உதவிக்கு உங்கள் இம்மிச் நிர்வாகியை தொடர்பு கொள்ளவும்.", - "asset_restored_successfully": "சொத்து வெற்றிகரமாக மீட்டெடுக்கப்பட்டது", - "asset_skipped": "தவிர்க்கப்பட்டது", - "asset_skipped_in_trash": "குப்பையில்", - "asset_trashed": "சொத்து குப்பை", - "asset_troubleshoot": "சொத்து சரிசெய்தல்", - "asset_uploaded": "பதிவேற்றப்பட்டது", - "asset_uploading": "பதிவேற்றுதல்…", - "asset_viewer_settings_subtitle": "உங்கள் கேலரி பார்வையாளர் அமைப்புகளை நிர்வகிக்கவும்", - "asset_viewer_settings_title": "சொத்து பார்வையாளர்", - "assets": "சொத்துக்கள்", - "assets_added_count": "சேர்க்கப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", - "assets_added_to_album_count": "தொகுப்பில் {count, plural, one {# சொத்து} other {# சொத்துக்கள்}} சேர்க்கப்பட்டது", - "assets_added_to_albums_count": "Added {assetTotal, plural, one {# சொத்து} other {# சொத்துக்கள்}} to {albumTotal, plural, one {# தொகுப்பு} other {# தொகுப்புகள்}}சேர்க்கப்பட்டது", - "assets_cannot_be_added_to_album_count": "{count, plural, one {சொத்து} other {சொத்துக்கள்}} செருகேட்டில் சேர்க்க முடியாது", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} எந்தச் செருகேட்டிலும் சேர்க்க முடியாது", - "assets_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", - "assets_deleted_permanently": "{count} சொத்து (கள்) நிரந்தரமாக நீக்கப்பட்டது", - "assets_deleted_permanently_from_server": "{count} சொத்து (கள்) இம்மிச் சேவையகத்திலிருந்து நிரந்தரமாக நீக்கப்பட்டது", - "assets_downloaded_failed": "{count, plural, one {# கோப்பு பதிவிறக்கப்பட்டது - {error} கோப்பு தோல்வியடைந்தது} other {# கோப்புகள் பதிவிறக்கப்பட்டன - {error} கோப்புகள் தோல்வியடைந்தன}}", - "assets_downloaded_successfully": "{count, plural, one {# கோப்பு வெற்றிகரமாகப் பதிவிறக்கம்} other {# கோப்புகள் வெற்றிகரமாகப் பதிவிறக்கம்}}", - "assets_moved_to_trash_count": "குப்பைக்கு {count, plural, one {# சொத்து} other {# சொத்துக்கள்}} நகர்த்தப்பட்டது", - "assets_permanently_deleted_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}} நிரந்தரமாக நீக்கப்பட்டது", - "assets_removed_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}}அகற்றப்பட்டது", - "assets_removed_permanently_from_device": "{count} சொத்து (கள்) உங்கள் சாதனத்திலிருந்து நிரந்தரமாக அகற்றப்பட்டது", - "assets_restore_confirmation": "உங்கள் குப்பைத் தொட்டிகள் அனைத்தையும் மீட்டெடுக்க விரும்புகிறீர்களா? இந்த செயலை நீங்கள் செயல்தவிர்க்க முடியாது! எந்தவொரு இணைப்பில்லாத சொத்துக்களையும் இந்த வழியில் மீட்டெடுக்க முடியாது என்பதை நினைவில் கொள்க.", - "assets_restored_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}}மீட்டெடுக்கப்பட்டது", - "assets_restored_successfully": "{count} சொத்து (கள்) வெற்றிகரமாக மீட்டெடுக்கப்பட்டது", - "assets_trashed": "{count} சொத்து (கள்) குப்பை", - "assets_trashed_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}} குப்பையாகப்பட்டது", - "assets_trashed_from_server": "{count} சொத்து (கள்) இம்மிச் சேவையகத்திலிருந்து குப்பைத் தொட்டியில்", - "assets_were_part_of_album_count": "{count, plural, one {சொத்து} other {சொத்துக்கள்}} ஏற்கனவே தொகுப்பின் ஒரு பகுதி", - "assets_were_part_of_albums_count": "{count, plural, one {சொத்து} other {சொத்துக்கள்}} ஏற்கனவே தொகுப்புகளின் ஒரு பகுதி", - "authorized_devices": "அங்கீகரிக்கப்பட்ட சாதனங்கள்", - "automatic_endpoint_switching_subtitle": "கிடைக்கும்போது நியமிக்கப்பட்ட வைஃபை மீது உள்ளூரில் இணைக்கவும், வேறு இடங்களில் மாற்று இணைப்புகளைப் பயன்படுத்தவும்", - "automatic_endpoint_switching_title": "தானியங்கி முகவரி மாறுதல்", - "autoplay_slideshow": "ஆட்டோபிளே ச்லைடுசோ", - "back": "பின்", - "back_close_deselect": "பின், மூடு அல்லது தேர்வுநீக்கம்", - "background_backup_running_error": "பின்னணி காப்புப்பிரதி தற்போது இயங்குகிறது, கைமுறை காப்புப்பிரதியைத் தொடங்க முடியாது", - "background_location_permission": "பின்னணி இருப்பிட இசைவு", - "background_location_permission_content": "பின்னணியில் இயங்கும் போது நெட்வொர்க்குகளை மாற்ற, இம்மிச் *எப்போதும்* துல்லியமான இருப்பிட அணுகலைக் கொண்டிருக்க வேண்டும், எனவே பயன்பாடு வைஃபை நெட்வொர்க்கின் பெயரைப் படிக்க முடியும்", - "background_options": "பின்னணி விருப்பங்கள்", - "backup": "காப்புப்பிரதி", - "backup_album_selection_page_albums_device": "சாதனத்தில் ஆல்பங்கள் ({count})", - "backup_album_selection_page_albums_tap": "சேர்க்க தட்டவும், விலக்க இரட்டை தட்டவும்", - "backup_album_selection_page_assets_scatter": "சொத்துக்கள் பல ஆல்பங்களில் சிதறக்கூடும். எனவே, காப்பு செயல்பாட்டின் போது ஆல்பங்கள் சேர்க்கப்படலாம் அல்லது விலக்கப்படலாம்.", - "backup_album_selection_page_select_albums": "ஆல்பங்களைத் தேர்ந்தெடுக்கவும்", - "backup_album_selection_page_selection_info": "தேர்வு செய்தி", - "backup_album_selection_page_total_assets": "மொத்த தனித்துவமான சொத்துக்கள்", - "backup_albums_sync": "காப்புப்பிரதி ஆல்பங்கள் ஒத்திசைவு", - "backup_all": "அனைத்தும்", - "backup_background_service_backup_failed_message": "சொத்துக்களை காப்புப்பிரதி எடுக்கத் தவறிவிட்டது. மீண்டும் முயற்சிப்பது…", - "backup_background_service_complete_notification": "சொத்து காப்புப்பிரதி முடிந்தது", - "backup_background_service_connection_failed_message": "சேவையகத்துடன் இணைக்கத் தவறிவிட்டது. மீண்டும் முயற்சிப்பது…", - "backup_background_service_current_upload_notification": "பதிவேற்றுதல் {filename}", - "backup_background_service_default_notification": "புதிய சொத்துக்களைச் சரிபார்க்கிறது…", - "backup_background_service_error_title": "காப்பு பிழை", - "backup_background_service_in_progress_notification": "உங்கள் சொத்துக்களை காப்புப் பிரதி எடுக்கிறது…", - "backup_background_service_upload_failure_notification": "{filename} பதிவேற்றுவதில் தோல்வி", - "backup_controller_page_albums": "காப்புப்பிரதி ஆல்பங்கள்", - "backup_controller_page_background_app_refresh_disabled_content": "பின்னணி காப்புப்பிரதியைப் பயன்படுத்துவதற்காக அமைப்புகளில் பின்னணி பயன்பாட்டு புதுப்பிப்புகளை இயக்கவும்> பொது> பின்னணி பயன்பாட்டு புதுப்பிப்பு.", - "backup_controller_page_background_app_refresh_disabled_title": "பின்னணி பயன்பாட்டு புதுப்பிப்பு முடக்கப்பட்டது", - "backup_controller_page_background_app_refresh_enable_button_text": "அமைப்புகளுக்குச் செல்லுங்கள்", - "backup_controller_page_background_battery_info_link": "எப்படி என்று எனக்குக் காட்டு", - "backup_controller_page_background_battery_info_message": "சிறந்த பின்னணி காப்புப்பிரதி அனுபவத்திற்கு, இம்மிச்சிற்கான பின்னணி செயல்பாட்டைக் கட்டுப்படுத்தும் எந்த பேட்டரி மேம்படுத்தல்களையும் முடக்கவும். \n\nஇது சாதனம் சார்ந்ததாக இருப்பதால், உங்கள் சாதன உற்பத்தியாளருக்கு தேவையான தகவல்களைத் தேடுங்கள்.", - "backup_controller_page_background_battery_info_ok": "சரி", - "backup_controller_page_background_battery_info_title": "பேட்டரி மேம்படுத்தல்கள்", - "backup_controller_page_background_charging": "கட்டணம் வசூலிக்கும் போது மட்டுமே", - "backup_controller_page_background_configure_error": "பின்னணி சேவையை உள்ளமைக்கத் தவறிவிட்டது", - "backup_controller_page_background_delay": "புதிய சொத்துக்களை தாமதப்படுத்துங்கள்: {duration}", - "backup_controller_page_background_description": "பயன்பாட்டைத் திறக்கத் தேவையில்லாமல் எந்த புதிய சொத்துக்களையும் தானாக காப்புப் பிரதி எடுக்க பின்னணி சேவையை இயக்கவும்", - "backup_controller_page_background_is_off": "தானியங்கி பின்னணி காப்புப்பிரதி முடக்கப்பட்டுள்ளது", - "backup_controller_page_background_is_on": "தானியங்கி பின்னணி காப்புப்பிரதி இயக்கத்தில் உள்ளது", - "backup_controller_page_background_turn_off": "பின்னணி சேவையை அணைக்கவும்", - "backup_controller_page_background_turn_on": "பின்னணி சேவையை இயக்கவும்", - "backup_controller_page_background_wifi": "வைஃபை மட்டுமே", - "backup_controller_page_backup": "காப்புப்பிரதி", - "backup_controller_page_backup_selected": "தேர்ந்தெடுக்கப்பட்டது: ", - "backup_controller_page_backup_sub": "புகைப்படங்கள் மற்றும் வீடியோக்களை காப்புப் பிரதி எடுக்கவும்", - "backup_controller_page_created": "உருவாக்கப்பட்டது: {date}", - "backup_controller_page_desc_backup": "பயன்பாட்டைத் திறக்கும்போது புதிய சொத்துக்களை சேவையகத்தில் தானாக பதிவேற்ற முன்புற காப்புப்பிரதியை இயக்கவும்.", - "backup_controller_page_excluded": "விலக்கப்பட்டது: ", - "backup_controller_page_failed": "தோல்வியுற்றது ({count})", - "backup_controller_page_filename": "கோப்பு பெயர்: {filename} [{size}]", - "backup_controller_page_id": "ஐடி: {id}", - "backup_controller_page_info": "காப்புப்பிரதி செய்தி", - "backup_controller_page_none_selected": "எதுவும் தேர்ந்தெடுக்கப்படவில்லை", - "backup_controller_page_remainder": "மீதமுள்ள", - "backup_controller_page_remainder_sub": "தேர்விலிருந்து காப்புப் பிரதி எடுக்க மீதமுள்ள புகைப்படங்கள் மற்றும் வீடியோக்கள்", - "backup_controller_page_server_storage": "சேவையக சேமிப்பு", - "backup_controller_page_start_backup": "காப்புப்பிரதியைத் தொடங்கவும்", - "backup_controller_page_status_off": "தானியங்கி முன்புற காப்புப்பிரதி முடக்கப்பட்டுள்ளது", - "backup_controller_page_status_on": "தானியங்கி முன்புற காப்புப்பிரதி இயக்கத்தில் உள்ளது", - "backup_controller_page_storage_format": "{total} இல் {used} பயன்படுத்தப்பட்டது", - "backup_controller_page_to_backup": "ஆதரிக்கப்பட வேண்டிய ஆல்பங்கள்", - "backup_controller_page_total_sub": "தேர்ந்தெடுக்கப்பட்ட ஆல்பங்களிலிருந்து அனைத்து தனித்துவமான புகைப்படங்கள் மற்றும் வீடியோக்கள்", - "backup_controller_page_turn_off": "முன்புற காப்புப்பிரதியை அணைக்கவும்", - "backup_controller_page_turn_on": "முன்புற காப்புப்பிரதியை இயக்கவும்", - "backup_controller_page_uploading_file_info": "கோப்பு தகவலைப் பதிவேற்றுகிறது", - "backup_err_only_album": "ஒரே ஆல்பத்தை அகற்ற முடியாது", - "backup_error_sync_failed": "ஒத்திசைவு தோல்வியுற்றது. காப்புப்பிரதியை செயலாக்க முடியாது.", - "backup_info_card_assets": "சொத்துக்கள்", - "backup_manual_cancelled": "ரத்து செய்யப்பட்டது", - "backup_manual_in_progress": "ஏற்கனவே முன்னேற்றத்தில் பதிவேற்றவும். சிறிது நேரம் கழித்து முயற்சிக்கவும்", - "backup_manual_success": "செய்", - "backup_manual_title": "நிலை பதிவேற்றும் நிலை", - "backup_options": "காப்பு விருப்பங்கள்", - "backup_options_page_title": "காப்பு விருப்பங்கள்", - "backup_setting_subtitle": "பின்னணி மற்றும் முன்புற பதிவேற்ற அமைப்புகளை நிர்வகிக்கவும்", - "backup_settings_subtitle": "பதிவேற்ற அமைப்புகளை நிர்வகிக்கவும்", - "backward": "பின்னோக்கு", - "biometric_auth_enabled": "பயோமெட்ரிக் ஏற்பு இயக்கப்பட்டது", - "biometric_locked_out": "நீங்கள் பயோமெட்ரிக் அங்கீகாரத்திலிருந்து பூட்டப்பட்டிருக்கிறீர்கள்", - "biometric_no_options": "பயோமெட்ரிக் விருப்பங்கள் எதுவும் கிடைக்கவில்லை", - "biometric_not_available": "இந்த சாதனத்தில் பயோமெட்ரிக் ஏற்பு கிடைக்கவில்லை", - "birthdate_saved": "பிறந்த தேதி வெற்றிகரமாக சேமிக்கப்பட்டது", - "birthdate_set_description": "ஒரு புகைப்படத்தின் போது இந்த நபரின் வயதைக் கணக்கிட பிறந்த தேதி பயன்படுத்தப்படுகிறது.", - "blurred_background": "மங்கலான பின்னணி", - "bugs_and_feature_requests": "பிழைகள் மற்றும் அம்ச கோரிக்கைகள்", - "build": "உருவாக்கு", - "build_image": "படத்தை உருவாக்குங்கள்", - "bulk_delete_duplicates_confirmation": "நீங்கள் நிச்சயமாக {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துகள்}} மொத்தமாக நீக்க விரும்புகிறீர்களா? இது ஒவ்வொரு குழுவின் மிகப்பெரிய சொத்தை வைத்திருக்கும் மற்றும் மற்ற அனைத்து நகல்களையும் நிரந்தரமாக நீக்கும். இந்தச் செயலை நீங்கள் செயல்தவிர்க்க முடியாது!", - "bulk_keep_duplicates_confirmation": "நீங்கள் நிச்சயமாக {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துக்கள்}} வைத்திருக்க விரும்புகிறீர்களா? இது எதையும் நீக்காமல் அனைத்து நகல் குழுக்களையும் தீர்க்கும்.", - "bulk_trash_duplicates_confirmation": "நீங்கள் நிச்சயமாக மொத்தமாகக் குப்பையில் போட விரும்புகிறீர்களா {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துக்கள்}}? இது ஒவ்வொரு குழுவின் மிகப்பெரிய சொத்தையும் வைத்திருக்கும், மற்ற அனைத்து நகல்களையும் குப்பையில் போடும்.", - "buy": "இம்மியை வாங்கவும்", - "cache_settings_clear_cache_button": "தெளிவான தற்காலிக சேமிப்பு", - "cache_settings_clear_cache_button_title": "பயன்பாட்டின் தற்காலிக சேமிப்பை அழிக்கிறது. கேச் மீண்டும் கட்டப்படும் வரை இது பயன்பாட்டின் செயல்திறனை கணிசமாக பாதிக்கும்.", - "cache_settings_duplicated_assets_clear_button": "தெளிவான", - "cache_settings_duplicated_assets_subtitle": "பயன்பாட்டால் பட்டியலிடப்பட்ட புறக்கணிக்கப்பட்ட புகைப்படங்கள் மற்றும் வீடியோக்கள்", - "cache_settings_duplicated_assets_title": "நகல் சொத்துக்கள் ({count})", - "cache_settings_statistics_album": "நூலக சிறு உருவங்கள்", - "cache_settings_statistics_full": "முழு படங்கள்", - "cache_settings_statistics_shared": "பகிரப்பட்ட ஆல்பம் சிறுபடம்", - "cache_settings_statistics_thumbnail": "சிறு உருவங்கள்", - "cache_settings_statistics_title": "கேச் பயன்பாடு", - "cache_settings_subtitle": "இம்மிச் மொபைல் பயன்பாட்டின் தற்காலிக சேமிப்பு நடத்தையை கட்டுப்படுத்தவும்", - "cache_settings_tile_subtitle": "உள்ளக சேமிப்பக நடத்தையை கட்டுப்படுத்தவும்", - "cache_settings_tile_title": "உள்ளக சேமிப்பு", - "cache_settings_title": "தேக்கக அமைப்புகள்", - "camera": "கேமரா", - "camera_brand": "கேமரா சூட்டுக்குறி", - "camera_model": "கேமரா மாதிரி", - "cancel": "ரத்துசெய்", - "cancel_search": "தேடலை ரத்துசெய்", - "canceled": "ரத்து செய்யப்பட்டது", - "canceling": "ரத்துசெய்யும்", - "cannot_merge_people": "மக்களை ஒன்றிணைக்க முடியாது", - "cannot_undo_this_action": "இந்த செயலை நீங்கள் செயல்தவிர்க்க முடியாது!", - "cannot_update_the_description": "விளக்கத்தை புதுப்பிக்க முடியாது", - "cast": "நடிகர்கள்", - "cast_description": "கிடைக்கக்கூடிய வார்ப்பு இடங்களை உள்ளமைக்கவும்", - "change_date": "தேதியை மாற்றவும்", - "change_description": "விளக்கத்தை மாற்றவும்", - "change_display_order": "காட்சி வரிசையை மாற்றவும்", - "change_expiration_time": "காலாவதி நேரத்தை மாற்றவும்", - "change_location": "இருப்பிடத்தை மாற்றவும்", - "change_name": "பெயரை மாற்றவும்", - "change_name_successfully": "பெயர் வெற்றிகரமாக மாற்றப்பட்டது", - "change_password": "கடவுச்சொல்லை மாற்றவும்", - "change_password_description": "நீங்கள் கணினியில் கையொப்பமிடுவது இதுவே முதல் முறை அல்லது உங்கள் கடவுச்சொல்லை மாற்றுவதற்கான கோரிக்கை செய்யப்பட்டுள்ளது. கீழே புதிய கடவுச்சொல்லை உள்ளிடவும்.", - "change_password_form_confirm_password": "கடவுச்சொல்லை உறுதிப்படுத்தவும்", - "change_password_form_description": "ஆய் {name}, \n\nநீங்கள் கணினியில் கையொப்பமிடுவது இதுவே முதல் முறை அல்லது உங்கள் கடவுச்சொல்லை மாற்றுவதற்கான கோரிக்கை செய்யப்பட்டுள்ளது. கீழே புதிய கடவுச்சொல்லை உள்ளிடவும்.", - "change_password_form_log_out": "மற்ற எல்லா சாதனங்களிலிருந்தும் வெளியேறு", - "change_password_form_log_out_description": "மற்ற எல்லா சாதனங்களிலிருந்தும் வெளியேற பரிந்துரைக்கப்படுகிறது", - "change_password_form_new_password": "புதிய கடவுச்சொல்", - "change_password_form_password_mismatch": "கடவுச்சொற்கள் பொருந்தவில்லை", - "change_password_form_reenter_new_password": "புதிய கடவுச்சொல்லை மீண்டும் உள்ளிடவும்", - "change_pin_code": "முள் குறியீட்டை மாற்றவும்", - "change_your_password": "உங்கள் கடவுச்சொல்லை மாற்றவும்", - "changed_visibility_successfully": "தெரிவுநிலை வெற்றிகரமாக மாற்றப்பட்டது", - "charging": "சார்சிங்", - "charging_requirement_mobile_backup": "பின்னணி காப்புப்பிரதிக்கு சாதனம் சார்ச் செய்ய வேண்டும்", - "check_corrupt_asset_backup": "ஊழல் சொத்து காப்புப்பிரதிகளை சரிபார்க்கவும்", - "check_corrupt_asset_backup_button": "காசோலை செய்யுங்கள்", - "check_corrupt_asset_backup_description": "இந்த காசோலையை வைஃபை மீது மட்டுமே இயக்கவும், அனைத்து சொத்துக்களும் காப்புப் பிரதி எடுக்கப்பட்டவுடன். செயல்முறை சில நிமிடங்கள் ஆகலாம்.", - "check_logs": "பதிவுகளை சரிபார்க்கவும்", - "choose_matching_people_to_merge": "ஒன்றிணைக்க பொருந்தக்கூடிய நபர்களைத் தேர்வுசெய்க", - "city": "நகரம்", - "clear": "தெளிவான", - "clear_all": "அனைத்தையும் அழிக்கவும்", - "clear_all_recent_searches": "அண்மைக் கால அனைத்து தேடல்களையும் அழிக்கவும்", - "clear_file_cache": "கோப்பு தற்காலிக சேமிப்பை அழிக்கவும்", - "clear_message": "தெளிவான செய்தி", - "clear_value": "தெளிவான மதிப்பு", - "client_cert_dialog_msg_confirm": "சரி", - "client_cert_enter_password": "கடவுச்சொல்லை உள்ளிடவும்", - "client_cert_import": "இறக்குமதி", - "client_cert_import_success_msg": "கிளையன்ட் சான்றிதழ் இறக்குமதி செய்யப்படுகிறது", - "client_cert_invalid_msg": "தவறான சான்றிதழ் கோப்பு அல்லது தவறான கடவுச்சொல்", - "client_cert_remove_msg": "கிளையன்ட் சான்றிதழ் அகற்றப்பட்டது", - "client_cert_subtitle": "PKCS12 (.p12, .pfx) வடிவமைப்பை மட்டுமே ஆதரிக்கிறது. உள்நுழைவதற்கு முன் மட்டுமே சான்றிதழ் இறக்குமதி/அகற்றுதல் கிடைக்கும்", - "client_cert_title": "SSL கிளையன்ட் சான்றிதழ் [பரிசோதனை]", - "clockwise": "Locklowsy", - "close": "மூடு", - "collapse": "சரிவு", - "collapse_all": "அனைத்தையும் சரக்கு", - "color": "நிறம்", - "color_theme": "வண்ண கருப்பொருள்", - "command": "கட்டளை", - "comment_deleted": "கருத்து நீக்கப்பட்டது", - "comment_options": "கருத்து விருப்பங்கள்", - "comments_and_likes": "கருத்துகள் மற்றும் விருப்பங்கள்", - "comments_are_disabled": "கருத்துகள் முடக்கப்பட்டுள்ளன", - "common_create_new_album": "புதிய ஆல்பத்தை உருவாக்கவும்", - "completed": "முடிந்தது", - "confirm": "உறுதிப்படுத்தவும்", - "confirm_admin_password": "நிர்வாகி கடவுச்சொல்லை உறுதிப்படுத்தவும்", - "confirm_delete_face": "சொத்திலிருந்து {name} முகத்தை நீக்க விரும்புகிறீர்களா?", - "confirm_delete_shared_link": "இந்த பகிரப்பட்ட இணைப்பை நீக்க விரும்புகிறீர்களா?", - "confirm_keep_this_delete_others": "இந்த சொத்தைத் தவிர அடுக்கில் உள்ள மற்ற அனைத்து சொத்துகளும் நீக்கப்படும். நீங்கள் தொடர விரும்புகிறீர்களா?", - "confirm_new_pin_code": "புதிய முள் குறியீட்டை உறுதிப்படுத்தவும்", - "confirm_password": "கடவுச்சொல்லை உறுதிப்படுத்தவும்", - "confirm_tag_face": "இந்த முகத்தை {name} எனக் குறிக்க விரும்புகிறீர்களா?", - "confirm_tag_face_unnamed": "இந்த முகத்தை குறிக்க விரும்புகிறீர்களா?", - "connected_device": "இணைக்கப்பட்ட சாதனம்", - "connected_to": "இணைக்கப்பட்டுள்ளது", - "contain": "கட்டுப்படுத்தவும்", - "context": "சூழல்", - "continue": "தொடரவும்", - "control_bottom_app_bar_create_new_album": "புதிய ஆல்பத்தை உருவாக்கவும்", - "control_bottom_app_bar_delete_from_immich": "இம்மிச்சிலிருந்து நீக்கு", - "control_bottom_app_bar_delete_from_local": "சாதனத்திலிருந்து நீக்கு", - "control_bottom_app_bar_edit_location": "இருப்பிடத்தைத் திருத்தவும்", - "control_bottom_app_bar_edit_time": "தேதி & நேரத்தைத் திருத்தவும்", - "control_bottom_app_bar_share_link": "இணைப்பைப் பகிரவும்", - "control_bottom_app_bar_share_to": "பகிர்ந்து கொள்ளுங்கள்", - "control_bottom_app_bar_trash_from_immich": "குப்பைக்கு நகர்த்தவும்", - "copied_image_to_clipboard": "இடைநிலைப்பலகைக்கு படத்தை நகலெடுத்தது.", - "copied_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது!", - "copy_error": "நகல் பிழை", - "copy_file_path": "கோப்பு பாதையை நகலெடுக்கவும்", - "copy_image": "படத்தை நகலெடுக்கவும்", - "copy_link": "இணைப்பை நகலெடுக்கவும்", - "copy_link_to_clipboard": "இடைநிலைப்பலகைக்கு இணைப்பை நகலெடுக்கவும்", - "copy_password": "கடவுச்சொல்லை நகலெடுக்கவும்", - "copy_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்கவும்", - "country": "நாடு", - "cover": "கவர்", - "covers": "மறையம்", - "create": "உருவாக்கு", - "create_album": "ஆல்பத்தை உருவாக்கவும்", - "create_album_page_untitled": "தலைப்பிடப்படாத", - "create_api_key": "பநிஇ விசையை உருவாக்கவும்", - "create_library": "நூலகத்தை உருவாக்கவும்", - "create_link": "இணைப்பை உருவாக்கவும்", - "create_link_to_share": "பகிர்வுக்கு இணைப்பை உருவாக்கவும்", - "create_link_to_share_description": "இணைப்பு உள்ள எவரும் தேர்ந்தெடுக்கப்பட்ட புகைப்படங்களைக் காணட்டும்)", - "create_new": "புதியதை உருவாக்கவும்", - "create_new_person": "புதிய நபரை உருவாக்கவும்", - "create_new_person_hint": "தேர்ந்தெடுக்கப்பட்ட சொத்துக்களை புதிய நபருக்கு ஒதுக்கவும்", - "create_new_user": "புதிய பயனரை உருவாக்கவும்", - "create_shared_album_page_share_add_assets": "சொத்துக்களைச் சேர்க்கவும்", - "create_shared_album_page_share_select_photos": "புகைப்படங்களைத் தேர்ந்தெடுக்கவும்", - "create_shared_link": "பகிரப்பட்ட இணைப்பை உருவாக்கவும்", - "create_tag": "குறிச்சொல்லை உருவாக்கவும்", - "create_tag_description": "புதிய குறிச்சொல்லை உருவாக்கவும். உள்ளமைக்கப்பட்ட குறிச்சொற்களுக்கு, முன்னோக்கி ச்லாச்கள் உட்பட குறிச்சொல்லின் முழு பாதையையும் உள்ளிடவும்.", - "create_user": "பயனரை உருவாக்கு", - "created": "உருவாக்கப்பட்டது", - "created_at": "உருவாக்கப்பட்டது", - "creating_linked_albums": "இணைக்கப்பட்ட ஆல்பங்களை உருவாக்குதல் ...", - "crop": "பயிர்", - "curated_object_page_title": "விசயங்கள்", - "current_device": "தற்போதைய சாதனம்", - "current_pin_code": "தற்போதைய முள் குறியீடு", - "current_server_address": "தற்போதைய சேவையக முகவரி", - "custom_locale": "தனிப்பயன் இடம்", - "custom_locale_description": "மொழி மற்றும் பிராந்தியத்தின் அடிப்படையில் வடிவமைப்பு தேதிகள் மற்றும் எண்கள்", - "custom_url": "தனிப்பயன் முகவரி", - "daily_title_text_date": "E, mmm dd", - "daily_title_text_date_year": "E, mmm dd, yyyy", - "dark": "இருண்ட", - "dark_theme": "இருண்ட கருப்பொருளை மாற்றவும்", - "date": "தேதி", - "date_after": "தேதி", - "date_and_time": "தேதி மற்றும் நேரம்", - "date_before": "முன் தேதி", - "date_format": "E, lll d, ஒய் • h: mm a", - "date_of_birth_saved": "பிறந்த தேதி வெற்றிகரமாக சேமிக்கப்பட்டது", - "date_range": "தேதி வரம்பு", - "day": "நாள்", - "days": "நாட்கள்", - "deduplicate_all": "அனைத்தையும் கழித்தல்", - "deduplication_criteria_1": "பைட்டுகளில் பட அளவு", - "deduplication_criteria_2": "EXIF தரவின் எண்ணிக்கை", - "deduplication_info": "கழித்தல் செய்தி", - "deduplication_info_description": "சொத்துக்களை தானாக முன்னெடுத்துச் செல்லவும், மொத்தமாக நகல்களை அகற்றவும், நாங்கள் பார்க்கிறோம்:", - "default_locale": "இயல்புநிலை இடம்", - "default_locale_description": "உங்கள் உலாவி இருப்பிடத்தின் அடிப்படையில் வடிவமைப்பு தேதிகள் மற்றும் எண்கள்", - "delete": "நீக்கு", - "delete_action_confirmation_message": "இந்த சொத்தை நீக்க விரும்புகிறீர்களா? இந்த நடவடிக்கை சொத்தை சேவையகத்தின் குப்பைக்கு நகர்த்தும், மேலும் நீங்கள் அதை உள்நாட்டில் நீக்க விரும்பினால் கேட்கும்", - "delete_action_prompt": "{count} நீக்கப்பட்டது", - "delete_album": "ஆல்பத்தை நீக்கு", - "delete_api_key_prompt": "இந்த பநிஇ விசையை நீக்க விரும்புகிறீர்களா?", - "delete_dialog_alert": "இந்த உருப்படிகள் இம்மிச்சிலிருந்தும் உங்கள் சாதனத்திலிருந்தும் நிரந்தரமாக நீக்கப்படும்", - "delete_dialog_alert_local": "இந்த உருப்படிகள் உங்கள் சாதனத்திலிருந்து நிரந்தரமாக அகற்றப்படும், ஆனால் இன்னும் இம்மிச் சேவையகத்தில் கிடைக்கும்", - "delete_dialog_alert_local_non_backed_up": "சில உருப்படிகள் இம்மிச் வரை ஆதரிக்கப்படவில்லை, மேலும் அவை உங்கள் சாதனத்திலிருந்து நிரந்தரமாக அகற்றப்படும்", - "delete_dialog_alert_remote": "இந்த உருப்படிகள் இம்மிச் சேவையகத்திலிருந்து நிரந்தரமாக நீக்கப்படும்", - "delete_dialog_ok_force": "எப்படியும் நீக்கு", - "delete_dialog_title": "நிரந்தரமாக நீக்கு", - "delete_duplicates_confirmation": "இந்த நகல்களை நிரந்தரமாக நீக்க விரும்புகிறீர்களா?", - "delete_face": "முகத்தை நீக்கு", - "delete_key": "விசையை நீக்கு", - "delete_library": "நூலகத்தை நீக்கு", - "delete_link": "இணைப்பை நீக்கு", - "delete_local_action_prompt": "{count} உள்நாட்டில் நீக்கப்பட்டது", - "delete_local_dialog_ok_backed_up_only": "நீக்கு மட்டுமே காப்புப்பிரதி எடுக்கவும்", - "delete_local_dialog_ok_force": "எப்படியும் நீக்கு", - "delete_others": "மற்றவர்களை நீக்கு", - "delete_permanently": "நிரந்தரமாக நீக்கு", - "delete_permanently_action_prompt": "{count} நிரந்தரமாக நீக்கப்பட்டது", - "delete_shared_link": "பகிரப்பட்ட இணைப்பை நீக்கு", - "delete_shared_link_dialog_title": "பகிரப்பட்ட இணைப்பை நீக்கு", - "delete_tag": "குறிச்சொல்லை நீக்கு", - "delete_tag_confirmation_prompt": "{tagName} குறிச்சொல்லை நீக்க விரும்புகிறீர்களா?", - "delete_user": "பயனரை நீக்கு", - "deleted_shared_link": "பகிரப்பட்ட இணைப்பை நீக்கியது", - "deletes_missing_assets": "வட்டில் இருந்து காணாமல் போன சொத்துக்களை நீக்குகிறது", - "description": "விவரம்", - "description_input_hint_text": "விளக்கத்தைச் சேர்க்கவும் ...", - "description_input_submit_error": "விளக்கத்தை புதுப்பிப்பதில் பிழை, மேலும் விவரங்களுக்கு பதிவைச் சரிபார்க்கவும்", - "deselect_all": "அனைத்தையும் தேர்வு செய்யுங்கள்", - "details": "விவரங்கள்", - "direction": "திசை", - "disabled": "முடக்கப்பட்டது", - "disallow_edits": "திருத்தங்களை அனுமதிக்கவும்", - "discord": "முரண்பாடு", - "discover": "கண்டுபிடி", - "discovered_devices": "கண்டுபிடிக்கப்பட்ட சாதனங்கள்", - "dismiss_all_errors": "அனைத்து பிழைகளையும் நிராகரிக்கவும்", - "dismiss_error": "பிழையை நிராகரிக்கவும்", - "display_options": "காட்சி விருப்பங்கள்", - "display_order": "காட்சி வரிசை", - "display_original_photos": "அசல் புகைப்படங்களைக் காண்பி", - "display_original_photos_setting_description": "அசல் சொத்து வலை-இணக்கமாக இருக்கும்போது சிறுபடங்களை விட ஒரு சொத்தைப் பார்க்கும்போது அசல் புகைப்படத்தைக் காண்பிக்க விரும்புகிறேன். இது மெதுவான புகைப்பட காட்சி வேகத்தை ஏற்படுத்தக்கூடும்.", - "do_not_show_again": "இந்த செய்தியை மீண்டும் காட்ட வேண்டாம்", - "documentation": "ஆவணப்படுத்துதல்", - "done": "முடிந்தது", - "download": "பதிவிறக்கம்", - "download_action_prompt": "பதிவிறக்கம் {count} சொத்துக்கள்", - "download_canceled": "பதிவிறக்கம் ரத்து செய்யப்பட்டது", - "download_complete": "முழுமையான பதிவிறக்கம்", - "download_enqueue": "Enqueuted பதிவிறக்க", - "download_error": "பிழையைப் பதிவிறக்கவும்", - "download_failed": "பதிவிறக்கம் தோல்வியடைந்தது", - "download_finished": "பதிவிறக்கம் முடிந்தது", - "download_include_embedded_motion_videos": "உட்பொதிக்கப்பட்ட வீடியோக்கள்", - "download_include_embedded_motion_videos_description": "மோசன் புகைப்படங்களில் உட்பொதிக்கப்பட்ட வீடியோக்களை தனி கோப்பாக சேர்க்கவும்", - "download_notfound": "பதிவிறக்கம் கிடைக்கவில்லை", - "download_paused": "இடைநிறுத்தப்பட்டது", - "download_settings": "பதிவிறக்கம்", - "download_settings_description": "சொத்து பதிவிறக்கம் தொடர்பான அமைப்புகளை நிர்வகிக்கவும்", - "download_started": "பதிவிறக்கம் தொடங்கியது", - "download_sucess": "வெற்றியைப் பதிவிறக்கவும்", - "download_sucess_android": "ஊடகங்கள் DCIM/IMMich க்கு பதிவிறக்கம் செய்யப்பட்டுள்ளன", - "download_waiting_to_retry": "மீண்டும் முயற்சிக்க காத்திருக்கிறது", - "downloading": "பதிவிறக்குகிறது", - "downloading_asset_filename": "சொத்து பதிவிறக்கம் {filename}", - "downloading_media": "ஊடகங்களைப் பதிவிறக்குகிறது", - "drop_files_to_upload": "பதிவேற்ற எங்கும் கோப்புகளை விடுங்கள்", - "duplicates": "நகல்கள்", - "duplicates_description": "ஒவ்வொரு குழுவையும் எந்த நகல்களிலும் குறிப்பிடுவதன் மூலம் தீர்க்கவும்", - "duration": "காலம்", - "edit": "தொகு", - "edit_album": "ஆல்பத்தைத் திருத்து", - "edit_avatar": "அவதாரத்தைத் திருத்து", - "edit_birthday": "பிறந்தநாளைத் திருத்தவும்", - "edit_date": "தேதியைத் திருத்து", - "edit_date_and_time": "தேதி மற்றும் நேரத்தைத் திருத்தவும்", - "edit_date_and_time_action_prompt": "{count} தேதி மற்றும் நேரம் திருத்தப்பட்டது", - "edit_date_and_time_by_offset": "ஆஃப்செட் மூலம் தேதியை மாற்றவும்", - "edit_date_and_time_by_offset_interval": "புதிய தேதி வரம்பு: {from} - {to}", - "edit_description": "விளக்கத்தைத் திருத்து", - "edit_description_prompt": "புதிய விளக்கத்தைத் தேர்ந்தெடுக்கவும்:", - "edit_exclusion_pattern": "விலக்கு முறையைத் திருத்தவும்", - "edit_faces": "முகங்களைத் திருத்தவும்", - "edit_key": "திறனைத் திருத்து", - "edit_link": "இணைப்பைத் திருத்து", - "edit_location": "இருப்பிடத்தைத் திருத்தவும்", - "edit_location_action_prompt": "{count} இருப்பிடம் திருத்தப்பட்டது", - "edit_location_dialog_title": "இடம்", - "edit_name": "பெயரைத் திருத்து", - "edit_people": "மக்களைத் திருத்தவும்", - "edit_tag": "குறிச்சொல்லைத் திருத்து", - "edit_title": "தலைப்பைத் திருத்து", - "edit_user": "பயனரைத் திருத்து", - "editor": "திருத்தி", - "editor_close_without_save_prompt": "மாற்றங்கள் சேமிக்கப்படாது", - "editor_close_without_save_title": "மூடு ஆசிரியர்?", - "email": "மின்னஞ்சல்", - "email_notifications": "மின்னஞ்சல் அறிவிப்புகள்", - "empty_folder": "இந்த கோப்புறை காலியாக உள்ளது", - "empty_trash": "வெற்று குப்பை", - "empty_trash_confirmation": "நீங்கள் குப்பைகளை வெறுமை செய்ய விரும்புகிறீர்களா? இது குப்பையில் உள்ள அனைத்து சொத்துக்களையும் நிரந்தரமாக இம்மிச்சிலிருந்து அகற்றும்.\n இந்த செயலை நீங்கள் செயல்தவிர்க்க முடியாது!", - "enable": "இயக்கு", - "enable_backup": "காப்புப்பிரதியை இயக்கவும்", - "enable_biometric_auth_description": "பயோமெட்ரிக் அங்கீகாரத்தை இயக்க உங்கள் முள் குறியீட்டை உள்ளிடவும்", - "enabled": "இயக்கப்பட்டது", - "end_date": "இறுதி தேதி", - "enqueued": "Enqueuted", - "enter_wifi_name": "வைஃபை பெயரை உள்ளிடவும்", - "enter_your_pin_code": "உங்கள் முள் குறியீட்டை உள்ளிடவும்", - "enter_your_pin_code_subtitle": "பூட்டப்பட்ட கோப்புறையை அணுக உங்கள் முள் குறியீட்டை உள்ளிடவும்", - "error": "பிழை", - "error_change_sort_album": "ஆல்பம் வரிசை வரிசையை மாற்றத் தவறிவிட்டது", - "error_delete_face": "சொத்தில் இருந்து முகத்தை நீக்குவதில் பிழை", - "error_getting_places": "இடங்களைப் பெறுவதில் பிழை", - "error_loading_image": "படத்தை ஏற்றுவதில் பிழை", - "error_loading_partners": "கூட்டாளர்களை ஏற்றுவதில் பிழை: {error}", - "error_saving_image": "பிழை: {error}", - "error_tag_face_bounding_box": "முகத்தை குறிக்கவும் பிழை - எல்லை பெட்டி ஆயத்தொலைவுகளைப் பெற முடியாது", - "error_title": "பிழை - ஏதோ தவறு நடந்தது", - "errors": { - "cannot_navigate_next_asset": "அடுத்த சொத்துக்கு செல்ல முடியாது", - "cannot_navigate_previous_asset": "முந்தைய சொத்துக்கு செல்ல முடியாது", - "cant_apply_changes": "மாற்றங்களைப் பயன்படுத்த முடியாது", - "cant_change_activity": "செயல்பாட்டை {enabled, select, true {முடக்க} other {இயக்க}} முடியாது", - "cant_change_asset_favorite": "சொத்துக்கு பிடித்ததை மாற்ற முடியாது", - "cant_change_metadata_assets_count": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}}இன் மீள்தரவை மாற்ற முடியாது", - "cant_get_faces": "முகங்களைப் பெற முடியாது", - "cant_get_number_of_comments": "கருத்துகளின் எண்ணிக்கையைப் பெற முடியாது", - "cant_search_people": "மக்களைத் தேட முடியாது", - "cant_search_places": "இடங்களைத் தேட முடியாது", - "error_adding_assets_to_album": "ஆல்பத்தில் சொத்துக்களைச் சேர்ப்பதில் பிழை", - "error_adding_users_to_album": "ஆல்பத்தில் பயனர்களைச் சேர்ப்பதில் பிழை", - "error_deleting_shared_user": "பகிரப்பட்ட பயனரை நீக்குவதில் பிழை", - "error_downloading": "பதிவிறக்குவதில் பிழை {filename}", - "error_hiding_buy_button": "பிழை மறைப்பது வாங்க பொத்தானை", - "error_removing_assets_from_album": "ஆல்பத்திலிருந்து சொத்துக்களை அகற்றுவதில் பிழை, மேலும் விவரங்களுக்கு கன்சோலை சரிபார்க்கவும்", - "error_selecting_all_assets": "அனைத்து சொத்துக்களையும் தேர்ந்தெடுப்பதில் பிழை", - "exclusion_pattern_already_exists": "இந்த விலக்கு முறை ஏற்கனவே உள்ளது.", - "failed_to_create_album": "ஆல்பத்தை உருவாக்கத் தவறிவிட்டது", - "failed_to_create_shared_link": "பகிரப்பட்ட இணைப்பை உருவாக்கத் தவறிவிட்டது", - "failed_to_edit_shared_link": "பகிரப்பட்ட இணைப்பைத் திருத்தத் தவறிவிட்டது", - "failed_to_get_people": "மக்களைப் பெறுவதில் தோல்வி", - "failed_to_keep_this_delete_others": "இந்த சொத்தை வைத்து மற்ற சொத்துக்களை நீக்குவதில் தோல்வி", - "failed_to_load_asset": "சொத்தை ஏற்றுவதில் தோல்வி", - "failed_to_load_assets": "சொத்துக்களை ஏற்றுவதில் தோல்வி", - "failed_to_load_notifications": "அறிவிப்புகளை ஏற்றுவதில் தோல்வி", - "failed_to_load_people": "மக்களை ஏற்றுவதில் தோல்வி", - "failed_to_remove_product_key": "தயாரிப்பு விசையை அகற்றுவதில் தோல்வி", - "failed_to_reset_pin_code": "முள் குறியீட்டை மீட்டமைக்கத் தவறிவிட்டது", - "failed_to_stack_assets": "சொத்துக்களை அடுக்கி வைப்பதில் தோல்வி", - "failed_to_unstack_assets": "அன்-ச்டாக் சொத்துக்களில் தோல்வியுற்றது", - "failed_to_update_notification_status": "அறிவிப்பு நிலையைப் புதுப்பிக்கத் தவறிவிட்டது", - "incorrect_email_or_password": "தவறான மின்னஞ்சல் அல்லது கடவுச்சொல்", - "library_folder_already_exists": "இந்த இறக்குமதி பாதை ஏற்கனவே பயன்பாட்டில் உள்ளது.", - "paths_validation_failed": "தோல்வியுற்ற சரிபார்ப்பு {paths, plural, one {# பாதை} other {# பாதைகள்}}", - "profile_picture_transparent_pixels": "சுயவிவரப் படங்களுக்கு வெளிப்படையான படப்புள்ளிகள் இருக்க முடியாது. தயவுசெய்து பெரிதாக்கவும்/அல்லது படத்தை நகர்த்தவும்.", - "quota_higher_than_disk_size": "வட்டு அளவை விட அதிகமாக ஒதுக்கீட்டை அமைத்துள்ளீர்கள்", - "something_went_wrong": "ஏதோ தவறு நடந்தது", - "unable_to_add_album_users": "ஆல்பத்தில் பயனர்களைச் சேர்க்க முடியவில்லை", - "unable_to_add_assets_to_shared_link": "பகிரப்பட்ட இணைப்புக்கு சொத்துக்களைச் சேர்க்க முடியவில்லை", - "unable_to_add_comment": "கருத்து சேர்க்க முடியவில்லை", - "unable_to_add_exclusion_pattern": "விலக்கு முறையைச் சேர்க்க முடியவில்லை", - "unable_to_add_partners": "கூட்டாளர்களைச் சேர்க்க முடியவில்லை", - "unable_to_add_remove_archive": "காப்பகத்தில் {archived, select, true {இருந்து சொத்தை அகற்ற} other {சொத்தைச் சேர்க்க}} முடியவில்லை", - "unable_to_add_remove_favorites": "பிடித்தவையில் {favorite, select, true {சொத்தைச் சேர்க்க} other {சொத்தை அகற்ற}} முடியவில்லை", - "unable_to_archive_unarchive": "{archived, select, true {சுருக்க} other {விரிக்க}} முடியவில்லை", - "unable_to_change_album_user_role": "ஆல்பத்தின் பயனரின் பாத்திரத்தை மாற்ற முடியவில்லை", - "unable_to_change_date": "தேதியை மாற்ற முடியவில்லை", - "unable_to_change_description": "விளக்கத்தை மாற்ற முடியவில்லை", - "unable_to_change_favorite": "சொத்துக்கு பிடித்ததை மாற்ற முடியவில்லை", - "unable_to_change_location": "இருப்பிடத்தை மாற்ற முடியவில்லை", - "unable_to_change_password": "கடவுச்சொல்லை மாற்ற முடியவில்லை", - "unable_to_change_visibility": "{count, plural, one {# நபர்} other {# பேர்}}க்கான தெரிவுநிலையை மாற்ற முடியவில்லை", - "unable_to_complete_oauth_login": "OAuth உள்நுழைவை முடிக்க முடியவில்லை", - "unable_to_connect": "இணைக்க முடியவில்லை", - "unable_to_copy_to_clipboard": "இடைநிலைப்பலகைக்கு நகலெடுக்க முடியாது, நீங்கள் HTTPS மூலம் பக்கத்தை அணுகுகிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்", - "unable_to_create_admin_account": "நிர்வாக கணக்கை உருவாக்க முடியவில்லை", - "unable_to_create_api_key": "புதிய பநிஇ விசையை உருவாக்க முடியவில்லை", - "unable_to_create_library": "நூலகத்தை உருவாக்க முடியவில்லை", - "unable_to_create_user": "பயனரை உருவாக்க முடியவில்லை", - "unable_to_delete_album": "ஆல்பத்தை நீக்க முடியவில்லை", - "unable_to_delete_asset": "சொத்தை நீக்க முடியவில்லை", - "unable_to_delete_assets": "சொத்துக்களை நீக்குவதில் பிழை", - "unable_to_delete_exclusion_pattern": "விலக்கு முறையை நீக்க முடியவில்லை", - "unable_to_delete_shared_link": "பகிரப்பட்ட இணைப்பை நீக்க முடியவில்லை", - "unable_to_delete_user": "பயனரை நீக்க முடியவில்லை", - "unable_to_download_files": "கோப்புகளைப் பதிவிறக்க முடியவில்லை", - "unable_to_edit_exclusion_pattern": "விலக்கு முறையைத் திருத்த முடியவில்லை", - "unable_to_empty_trash": "குப்பைகளை வெற்று செய்ய முடியவில்லை", - "unable_to_enter_fullscreen": "முழுத் திரையில் நுழைய முடியவில்லை", - "unable_to_exit_fullscreen": "முழுத்திரை வெளியேற முடியவில்லை", - "unable_to_get_comments_number": "கருத்துகளின் எண்ணிக்கையைப் பெற முடியவில்லை", - "unable_to_get_shared_link": "பகிரப்பட்ட இணைப்பைப் பெறுவதில் தோல்வி", - "unable_to_hide_person": "நபரை மறைக்க முடியவில்லை", - "unable_to_link_motion_video": "இயக்க வீடியோவை இணைக்க முடியவில்லை", - "unable_to_link_oauth_account": "OAuth கணக்கை இணைக்க முடியவில்லை", - "unable_to_log_out_all_devices": "எல்லா சாதனங்களையும் வெளியேற்ற முடியவில்லை", - "unable_to_log_out_device": "சாதனத்தை விட்டு வெளியேற முடியவில்லை", - "unable_to_login_with_oauth": "OAUTH உடன் உள்நுழைய முடியவில்லை", - "unable_to_play_video": "வீடியோவை இயக்க முடியவில்லை", - "unable_to_reassign_assets_existing_person": "{name, select, null {ஏற்கனவே உள்ள ஒருவர்} other {{name}}}க்கு சொத்துக்களை மறுஒதுக்கீடு செய்ய முடியவில்லை", - "unable_to_reassign_assets_new_person": "ஒரு புதிய நபருக்கு சொத்துக்களை மறுசீரமைக்க முடியவில்லை", - "unable_to_refresh_user": "பயனரைப் புதுப்பிக்க முடியவில்லை", - "unable_to_remove_album_users": "ஆல்பத்திலிருந்து பயனர்களை அகற்ற முடியவில்லை", - "unable_to_remove_api_key": "பநிஇ விசையை அகற்ற முடியவில்லை", - "unable_to_remove_assets_from_shared_link": "பகிரப்பட்ட இணைப்பிலிருந்து சொத்துக்களை அகற்ற முடியவில்லை", - "unable_to_remove_library": "நூலகத்தை அகற்ற முடியவில்லை", - "unable_to_remove_partner": "கூட்டாளரை அகற்ற முடியவில்லை", - "unable_to_remove_reaction": "எதிர்வினையை அகற்ற முடியவில்லை", - "unable_to_reset_password": "கடவுச்சொல்லை மீட்டமைக்க முடியவில்லை", - "unable_to_reset_pin_code": "முள் குறியீட்டை மீட்டமைக்க முடியவில்லை", - "unable_to_resolve_duplicate": "நகல் தீர்க்க முடியவில்லை", - "unable_to_restore_assets": "சொத்துக்களை மீட்டெடுக்க முடியவில்லை", - "unable_to_restore_trash": "குப்பைகளை மீட்டெடுக்க முடியவில்லை", - "unable_to_restore_user": "பயனரை மீட்டெடுக்க முடியவில்லை", - "unable_to_save_album": "ஆல்பத்தை சேமிக்க முடியவில்லை", - "unable_to_save_api_key": "பநிஇ விசையை சேமிக்க முடியவில்லை", - "unable_to_save_date_of_birth": "பிறந்த தேதியை சேமிக்க முடியவில்லை", - "unable_to_save_name": "பெயரை சேமிக்க முடியவில்லை", - "unable_to_save_profile": "சுயவிவரத்தை சேமிக்க முடியவில்லை", - "unable_to_save_settings": "அமைப்புகளைச் சேமிக்க முடியவில்லை", - "unable_to_scan_libraries": "நூலகங்களை ச்கேன் செய்ய முடியவில்லை", - "unable_to_scan_library": "நூலகத்தை ச்கேன் செய்ய முடியவில்லை", - "unable_to_set_feature_photo": "அம்ச புகைப்படத்தை அமைக்க முடியவில்லை", - "unable_to_set_profile_picture": "சுயவிவரப் படத்தை அமைக்க முடியவில்லை", - "unable_to_submit_job": "வேலையைச் சமர்ப்பிக்க முடியவில்லை", - "unable_to_trash_asset": "சொத்தை குப்பை செய்ய முடியவில்லை", - "unable_to_unlink_account": "கணக்கை இணைக்க முடியவில்லை", - "unable_to_unlink_motion_video": "மோசன் வீடியோவை இணைக்க முடியவில்லை", - "unable_to_update_album_cover": "ஆல்பம் அட்டையை புதுப்பிக்க முடியவில்லை", - "unable_to_update_album_info": "ஆல்பம் தகவலைப் புதுப்பிக்க முடியவில்லை", - "unable_to_update_library": "நூலகத்தைப் புதுப்பிக்க முடியவில்லை", - "unable_to_update_location": "இருப்பிடத்தைப் புதுப்பிக்க முடியவில்லை", - "unable_to_update_settings": "அமைப்புகளை புதுப்பிக்க முடியவில்லை", - "unable_to_update_timeline_display_status": "காலவரிசை காட்சி நிலையை புதுப்பிக்க முடியவில்லை", - "unable_to_update_user": "பயனரைப் புதுப்பிக்க முடியவில்லை", - "unable_to_upload_file": "கோப்பைப் பதிவேற்ற முடியவில்லை" - }, - "exclusion_pattern": "விலக்கு முறை", - "exif": "எக்ஸிஃப்", - "exif_bottom_sheet_description": "விளக்கத்தைச் சேர்க்கவும் ...", - "exif_bottom_sheet_description_error": "விளக்கத்தை புதுப்பிப்பதில் பிழை", - "exif_bottom_sheet_details": "விவரங்கள்", - "exif_bottom_sheet_location": "இடம்", - "exif_bottom_sheet_no_description": "விளக்கம் இல்லை", - "exif_bottom_sheet_people": "மக்கள்", - "exif_bottom_sheet_person_add_person": "பெயரைச் சேர்க்கவும்", - "exit_slideshow": "ச்லைடுசோவிலிருந்து வெளியேறவும்", - "expand_all": "அனைத்தையும் விரிவாக்குங்கள்", - "experimental_settings_new_asset_list_subtitle": "வேலை முன்னேற்றத்தில் உள்ளது", - "experimental_settings_new_asset_list_title": "சோதனை புகைப்பட கட்டத்தை இயக்கவும்", - "experimental_settings_subtitle": "உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்!", - "experimental_settings_title": "சோதனை", - "expire_after": "பின்னர் காலாவதியாகுங்கள்", - "expired": "காலாவதியான", - "expires_date": "{date} காலாவதியாகிறது", - "explore": "ஆராயுங்கள்", - "explorer": "எக்ச்ப்ளோரர்", - "export": "ஏற்றுமதி", - "export_as_json": "சாதொபொகு ஆக ஏற்றுமதி", - "export_database": "ஏற்றுமதி தரவுத்தளம்", - "export_database_description": "SQLITE தரவுத்தளத்தை ஏற்றுமதி செய்யுங்கள்", - "extension": "நீட்டிப்பு", - "external": "வெளிப்புறம்", - "external_libraries": "வெளிப்புற நூலகங்கள்", - "external_network": "வெளிப்புற பிணையம்", - "external_network_sheet_info": "விருப்பமான வைஃபை நெட்வொர்க்கில் இல்லாதபோது, பயன்பாடு சேவையகத்துடன் கீழே உள்ள முகவரி களின் முதல் வழியாக இணைக்கப்படும், இது மேலே இருந்து கீழே தொடங்குகிறது", - "face_unassigned": "ஒதுக்கப்படாதது", - "failed": "தோல்வியுற்றது", - "failed_to_authenticate": "அங்கீகரிக்கத் தவறிவிட்டது", - "failed_to_load_assets": "சொத்துக்களை ஏற்றுவதில் தோல்வி", - "failed_to_load_folder": "கோப்புறையை ஏற்றுவதில் தோல்வி", - "favorite": "பிடித்த", - "favorite_action_prompt": "{count} பிடித்தவைகளில் சேர்க்கப்பட்டது", - "favorite_or_unfavorite_photo": "பிடித்த அல்லது சாதகமற்ற புகைப்படம்", - "favorites": "பிடித்தவை", - "favorites_page_no_favorites": "பிடித்த சொத்துக்கள் எதுவும் கிடைக்கவில்லை", - "feature_photo_updated": "அம்ச புகைப்படம் புதுப்பிக்கப்பட்டது", - "features": "நற்பொருத்தங்கள்", - "features_in_development": "வளர்ச்சியில் நற்பொருத்தங்கள்", - "features_setting_description": "பயன்பாட்டு அம்சங்களை நிர்வகிக்கவும்", - "file_name_or_extension": "கோப்பு பெயர் அல்லது நீட்டிப்பு", - "file_size": "கோப்பு அளவு", - "filename": "கோப்புப்பெயர்", - "filetype": "பைல்டைப்", - "filter": "வடிப்பி", - "filter_people": "மக்களை வடிகட்டவும்", - "filter_places": "இடங்களை வடிகட்டவும்", - "find_them_fast": "தேடலுடன் பெயரால் வேகமாக அவற்றைக் கண்டறியவும்", - "first": "முதல்", - "fix_incorrect_match": "தவறான போட்டியை சரிசெய்யவும்", - "folder": "கோப்புறை", - "folder_not_found": "கோப்புறை கிடைக்கவில்லை", - "folders": "கோப்புறைகள்", - "folders_feature_description": "கோப்பு முறைமையில் உள்ள புகைப்படங்கள் மற்றும் வீடியோக்களுக்கான கோப்புறை காட்சியை உலாவுதல்", - "forgot_pin_code_question": "உங்கள் முள் மறந்துவிட்டீர்களா?", - "forward": "முன்னோக்கி", - "full_path": "முழு பாதை: {path}", - "gcast_enabled": "கூகிள் நடிகர்கள்", - "gcast_enabled_description": "இந்த நற்பொருத்தம் வேலை செய்வதற்காக Google இலிருந்து வெளிப்புற வளங்களை ஏற்றுகிறது.", - "general": "பொது", - "geolocation_instruction_location": "அதன் இருப்பிடத்தைப் பயன்படுத்த சி.பி.எச் ஆயத்தொலைவுகளுடன் ஒரு சொத்தில் சொடுக்கு செய்க அல்லது வரைபடத்திலிருந்து நேரடியாக ஒரு இடத்தைத் தேர்ந்தெடுக்கவும்", - "get_help": "உதவி பெறு", - "get_wifiname_error": "வைஃபை பெயரைப் பெற முடியவில்லை. நீங்கள் தேவையான அனுமதிகளை வழங்கியுள்ளீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள் மற்றும் வைஃபை நெட்வொர்க்குடன் இணைக்கப்பட்டுள்ளீர்கள்", - "getting_started": "தொடங்குதல்", - "go_back": "திரும்பிச் செல்லுங்கள்", - "go_to_folder": "கோப்புறைக்குச் செல்லுங்கள்", - "go_to_search": "தேடச் செல்லவும்", - "gps": "உலக இடம் காட்டும் அமைப்பு (இடம் காட்டி)", - "gps_missing": "சி.பி.எச் இல்லை", - "grant_permission": "இசைவு வழங்கவும்", - "group_albums_by": "குழு ஆல்பங்கள் வழங்கியவர் ...", - "group_country": "நாடு மூலம் குழு", - "group_no": "குழு இல்லை", - "group_owner": "உரிமையாளரால் குழு", - "group_places_by": "குழு இடங்கள் ...", - "group_year": "ஆண்டுக்கு குழு", - "haptic_feedback_switch": "ஆப்டிக் பின்னூட்டத்தை இயக்கவும்", - "haptic_feedback_title": "ஆப்டிக் கருத்து", - "has_quota": "ஒதுக்கீடு உள்ளது", - "hash_asset": "ஆச் சொத்து", - "hashed_assets": "சொத்துக்கள்", - "hashing": "ஏசிங்", - "header_settings_add_header_tip": "தலைப்பைச் சேர்க்கவும்", - "header_settings_field_validator_msg": "மதிப்பு காலியாக இருக்க முடியாது", - "header_settings_header_name_input": "தலைப்பு பெயர்", - "header_settings_header_value_input": "தலைப்பு மதிப்பு", - "headers_settings_tile_title": "தனிப்பயன் பதிலாள் தலைப்புகள்", - "hi_user": "ஆய் {name} ({email})", - "hide_all_people": "எல்லா மக்களையும் மறைக்கவும்", - "hide_gallery": "கேலரியை மறைக்கவும்", - "hide_named_person": "நபரை மறைக்க {name}", - "hide_password": "கடவுச்சொல்லை மறைக்கவும்", - "hide_person": "நபரை மறைக்க", - "hide_text_recognition": "உரை அங்கீகாரத்தை மறை", - "hide_unnamed_people": "பெயரிடப்படாதவர்களை மறைக்கவும்", - "home_page_add_to_album_conflicts": "{album} ஆல்பத்தில் {added} சொத்துக்கள் சேர்க்கப்பட்டன. {failed} சொத்துக்கள் ஏற்கனவே ஆல்பத்தில் உள்ளன.", - "home_page_add_to_album_err_local": "ஆல்பங்களில் உள்ளக சொத்துக்களை இன்னும் சேர்க்க முடியாது, தவிர்க்கவும்", - "home_page_add_to_album_success": "{album} ஆல்பத்தில் {added} சொத்துக்கள் சேர்க்கப்பட்டன.", - "home_page_album_err_partner": "ஒரு ஆல்பத்திற்கு இன்னும் கூட்டாளர் சொத்துக்களைச் சேர்க்க முடியவில்லை, தவிர்க்கவும்", - "home_page_archive_err_local": "உள்ளக சொத்துக்களை இன்னும் காப்பகப்படுத்த முடியாது, தவிர்க்கவும்", - "home_page_archive_err_partner": "கூட்டாளர் சொத்துக்களை காப்பகப்படுத்த முடியாது, தவிர்க்கவும்", - "home_page_building_timeline": "காலவரிசையை உருவாக்குதல்", - "home_page_delete_err_partner": "கூட்டாளர் சொத்துக்களை நீக்க முடியாது, தவிர்க்கவும்", - "home_page_delete_remote_err_local": "தொலைநிலை தேர்வை நீக்கு, தவிர்ப்பதில் உள்ளக சொத்துக்கள்", - "home_page_favorite_err_local": "உள்ளக சொத்துக்களை இன்னும் பிடித்திருக்க முடியாது, தவிர்க்கவும்", - "home_page_favorite_err_partner": "இன்னும் பிடித்த கூட்டாளர் சொத்துக்களைத் தவிர்த்து, தவிர்க்க முடியாது", - "home_page_first_time_notice": "பயன்பாட்டைப் பயன்படுத்துவது இது உங்கள் முதல் முறையாக இருந்தால், தயவுசெய்து காப்புப்பிரதி ஆல்பத்தைத் தேர்வுசெய்க, இதன் மூலம் காலவரிசை அதில் உள்ள புகைப்படங்களையும் வீடியோக்களையும் விரிவுபடுத்த முடியும்", - "home_page_locked_error_local": "உள்ளக சொத்துக்களை பூட்டிய கோப்புறைக்கு நகர்த்த முடியாது, தவிர்க்கிறது", - "home_page_locked_error_partner": "பங்குதாரர் சொத்துக்களை பூட்டிய கோப்புறையில் நகர்த்த முடியாது, தவிர்க்கவும்", - "home_page_share_err_local": "இணைப்பு, ச்கிப்பிங் வழியாக உள்ளக சொத்துக்களை பகிர முடியாது", - "home_page_upload_err_limit": "ஒரு நேரத்தில் அதிகபட்சம் 30 சொத்துக்களை மட்டுமே பதிவேற்ற முடியும், தவிர்க்கவும்", - "host": "விருந்தோம்பி", - "hour": "மணி", - "hours": "மணி", - "id": "ஐடி", - "idle": "நிலையிக்கம்", - "ignore_icloud_photos": "ICloud புகைப்படங்களை புறக்கணிக்கவும்", - "ignore_icloud_photos_description": "ICloud இல் சேமிக்கப்படும் புகைப்படங்கள் இம்மிச் சேவையகத்தில் பதிவேற்றப்படாது", - "image": "படம்", - "image_alt_text_date": "{isVideo, select, true {காணொளி} other {படம்}} {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_1_person": "{isVideo, select, true {காணொளி} other {படம்}} {person1} உடன் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_2_people": "{isVideo, select, true {காணொளி} other {படம்}} {person1} மற்றும் {person2} உடன் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_3_people": "{isVideo, select, true {காணொளி} other {படம்}} {person1}, {person2}, மற்றும் {person3} உடன் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {காணொளி} other {படம்}} {person1}, {person2}, மற்றும் {additionalCount, number} உடன் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_place": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_place_1_person": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1} உடன் {date}அன்று எடுக்கப்பட்டது", - "image_alt_text_date_place_2_people": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1} மற்றும் {person2} உடன் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_place_3_people": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1}, {person2}, மற்றும் {person3} உடன் {date} அன்று எடுக்கப்பட்டது", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {காணொளி} other {படம்}} {city}, {country} இல் {person1}, {person2}, மற்றும் {additionalCount, number} பிறருடன் {date} அன்று எடுக்கப்பட்டது", - "image_saved_successfully": "படம் சேமிக்கப்பட்டது", - "image_viewer_page_state_provider_download_started": "பதிவிறக்கம் தொடங்கியது", - "image_viewer_page_state_provider_download_success": "வெற்றியைப் பதிவிறக்கவும்", - "image_viewer_page_state_provider_share_error": "பிழை பகிர்வு", - "immich_logo": "இம்மிச் லோகோ", - "immich_web_interface": "இம்ரிச் வலை இடைமுகம்", - "import_from_json": "சாதொபொகு இலிருந்து இறக்குமதி", - "import_path": "இறக்குமதி பாதை", - "in_albums": "{count, plural, one {# செருகேடு} other {# செருகேடுகள்}} இல்", - "in_archive": "காப்பகத்தில்", - "in_year": "{year} இல்", - "in_year_selector": "உள்ளே", - "include_archived": "காப்பகப்படுத்தப்பட்டவர்", - "include_shared_albums": "பகிரப்பட்ட ஆல்பங்களைச் சேர்க்கவும்", - "include_shared_partner_assets": "பகிரப்பட்ட கூட்டாளர் சொத்துக்களைச் சேர்க்கவும்", - "individual_share": "தனிப்பட்ட பங்கு", - "individual_shares": "தனிப்பட்ட பங்குகள்", - "info": "தகவல்", - "interval": { - "day_at_onepm": "ஒவ்வொரு நாளும் மதியம் 1 மணிக்கு", - "hours": "ஒவ்வொரு {hours, plural, one {மணி} other {{hours, number} மணிகள்}}", - "night_at_midnight": "ஒவ்வொரு இரவும் நள்ளிரவில்", - "night_at_twoam": "ஒவ்வொரு இரவும் அதிகாலை 2 மணிக்கு" - }, - "invalid_date": "தவறான தேதி", - "invalid_date_format": "தவறான தேதி வடிவம்", - "invite_people": "மக்களை அழைக்கவும்", - "invite_to_album": "ஆல்பத்திற்கு அழைக்கவும்", - "ios_debug_info_fetch_ran_at": "ஓடியது {dateTime}", - "ios_debug_info_last_sync_at": "கடைசி ஒத்திசைவு {dateTime}", - "ios_debug_info_no_processes_queued": "பின்னணி செயல்முறைகள் வரிசையில் இல்லை", - "ios_debug_info_no_sync_yet": "பின்னணி ஒத்திசைவு வேலை இன்னும் இயங்கவில்லை", - "ios_debug_info_processes_queued": "{count, plural, one {{count} பின்னணி செயல்முறை வரிசைப்படுத்தப்பட்டது} other {{count} பின்னணி செயல்முறைகள் வரிசைப்படுத்தப்பட்டுள்ளன}}", - "ios_debug_info_processing_ran_at": "செயலாக்கம் {dateTime}", - "items_count": "{count, plural, one {# உருப்படி} other {# உருப்படிகள்}}", - "jobs": "வேலைகள்", - "keep": "வைத்திருங்கள்", - "keep_all": "அனைத்தையும் வைத்திருங்கள்", - "keep_this_delete_others": "இதை வைத்திருங்கள், மற்றவர்களை நீக்கு", - "kept_this_deleted_others": "இந்தச் சொத்தை வைத்து, {count, plural, one {# சொத்து} other {# சொத்துகள்}} நீக்கப்பட்டது", - "keyboard_shortcuts": "விசைப்பலகை குறுக்குவழிகள்", - "language": "மொழி", - "language_no_results_subtitle": "உங்கள் தேடல் காலத்தை சரிசெய்ய முயற்சிக்கவும்", - "language_no_results_title": "எந்த மொழிகளும் கிடைக்கவில்லை", - "language_search_hint": "மொழிகளைத் தேடுங்கள் ...", - "language_setting_description": "உங்களுக்கு விருப்பமான மொழியைத் தேர்ந்தெடுக்கவும்", - "large_files": "பெரிய கோப்புகள்", - "last": "கடைசி", - "last_months": "{count, plural, one {கடந்த மாதம்} other {கடந்த # மாதங்கள்}}", - "last_seen": "கடைசியாக பார்த்தேன்", - "latest_version": "அண்மைக் கால பதிப்பு", - "latitude": "அகலாங்கு", - "leave": "விடுப்பு", - "leave_album": "விடுப்பு ஆல்பம்", - "lens_model": "லென்ச் மாதிரி", - "let_others_respond": "மற்றவர்கள் பதிலளிக்கட்டும்", - "level": "நிலை", - "library": "நூலகம்", - "library_add_folder": "கோப்புறையைச் சேர்", - "library_edit_folder": "கோப்புறையைத் திருத்து", - "library_options": "நூலக விருப்பங்கள்", - "library_page_device_albums": "சாதனத்தில் ஆல்பங்கள்", - "library_page_new_album": "புதிய ஆல்பம்", - "library_page_sort_asset_count": "சொத்துக்களின் எண்ணிக்கை", - "library_page_sort_created": "உருவாக்கப்பட்ட தேதி", - "library_page_sort_last_modified": "கடைசியாக மாற்றப்பட்டது", - "library_page_sort_title": "ஆல்பம் தலைப்பு", - "licenses": "உரிமங்கள்", - "light": "ஒளி", - "like": "போன்ற", - "like_deleted": "நீக்கப்பட்டதைப் போல", - "link_motion_video": "இணைப்பு இயக்க வீடியோ", - "link_to_oauth": "OAUTH உடன் இணைப்பு", - "linked_oauth_account": "இணைக்கப்பட்ட OAUTH கணக்கு", - "list": "பட்டியல்", - "loading": "ஏற்றுகிறது", - "loading_search_results_failed": "தேடல் முடிவுகளை ஏற்றுவது தோல்வியடைந்தது", - "local": "உள்ளக", - "local_asset_cast_failed": "சேவையகத்தில் பதிவேற்றப்படாத ஒரு சொத்தை அனுப்ப முடியவில்லை", - "local_assets": "உள்ளக சொத்துக்கள்", - "local_media_summary": "உள்ளக ஊடக சுருக்கம்", - "local_network": "உள்ளக பிணையம்", - "local_network_sheet_info": "குறிப்பிட்ட வைஃபை நெட்வொர்க்கைப் பயன்படுத்தும் போது பயன்பாடு இந்த முகவரி மூலம் சேவையகத்துடன் இணைக்கப்படும்", - "location": "இடம்", - "location_permission": "இருப்பிட இசைவு", - "location_permission_content": "ஆட்டோ-ச்விட்சிங் அம்சத்தைப் பயன்படுத்த, இம்மிக்கு துல்லியமான இருப்பிட இசைவு தேவை, எனவே இது தற்போதைய வைஃபை நெட்வொர்க்கின் பெயரைப் படிக்க முடியும்", - "location_picker_choose_on_map": "வரைபடத்தில் தேர்வு செய்யவும்", - "location_picker_latitude_error": "செல்லுபடியாகும் அட்சரேகை உள்ளிடவும்", - "location_picker_latitude_hint": "உங்கள் அட்சரேகையை இங்கே உள்ளிடவும்", - "location_picker_longitude_error": "செல்லுபடியாகும் தீர்க்கரேகையை உள்ளிடவும்", - "location_picker_longitude_hint": "உங்கள் தீர்க்கரேகையை இங்கே உள்ளிடவும்", - "lock": "பூட்டு", - "locked_folder": "பூட்டப்பட்ட கோப்புறை", - "log_detail_title": "பதிவு விவரம்", - "log_out": "விடுபதிகை", - "log_out_all_devices": "எல்லா சாதனங்களையும் விட்டு வெளியேறவும்", - "logged_in_as": "{user}", - "logged_out_all_devices": "எல்லா சாதனங்களையும் வெளியேற்றினேன்", - "logged_out_device": "உள்நுழைந்த சாதனம்", - "login": "புகுபதிவு", - "login_disabled": "உள்நுழைவு முடக்கப்பட்டுள்ளது", - "login_form_api_exception": "பநிஇ விதிவிலக்கு. சேவையக முகவரி ஐ சரிபார்த்து மீண்டும் முயற்சிக்கவும்.", - "login_form_back_button_text": "பின்", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http: // your-server-ip: துறைமுகம்", - "login_form_endpoint_url": "சேவையக எண்ட்பாயிண்ட் முகவரி", - "login_form_err_http": "Http: // அல்லது https: // ஐக் குறிப்பிடவும்", - "login_form_err_invalid_email": "தவறான மின்னஞ்சல்", - "login_form_err_invalid_url": "தவறான முகவரி", - "login_form_err_leading_whitespace": "முன்னணி விட்ச்பேச்", - "login_form_err_trailing_whitespace": "பின்திங் வைட்ச்பேச்", - "login_form_failed_get_oauth_server_config": "OAuth ஐப் பயன்படுத்தி பதிவுசெய்தல், சேவையக முகவரி ஐ சரிபார்க்கவும்", - "login_form_failed_get_oauth_server_disable": "இந்த சேவையகத்தில் OAuth நற்பொருத்தம் கிடைக்கவில்லை", - "login_form_failed_login": "உங்களை உள்நுழைவதில் பிழை, சேவையக URL, மின்னஞ்சல் மற்றும் கடவுச்சொல்லை சரிபார்க்கவும்", - "login_form_handshake_exception": "சேவையகத்துடன் ஏண்ட்சேக் விதிவிலக்கு இருந்தது. நீங்கள் தன்வய கையொப்பமிடப்பட்ட சான்றிதழைப் பயன்படுத்துகிறீர்கள் என்றால் அமைப்புகளில் தன்வய கையொப்பமிடப்பட்ட சான்றிதழ் ஆதரவை இயக்கவும்.", - "login_form_password_hint": "கடவுச்சொல்", - "login_form_save_login": "உள்நுழைந்திருங்கள்", - "login_form_server_empty": "சேவையக முகவரி ஐ உள்ளிடவும்.", - "login_form_server_error": "சேவையகத்துடன் இணைக்க முடியவில்லை.", - "login_has_been_disabled": "உள்நுழைவு முடக்கப்பட்டுள்ளது.", - "login_password_changed_error": "உங்கள் கடவுச்சொல்லைப் புதுப்பிப்பதில் பிழை ஏற்பட்டது", - "login_password_changed_success": "கடவுச்சொல் வெற்றிகரமாக புதுப்பிக்கப்பட்டது", - "logout_all_device_confirmation": "எல்லா சாதனங்களையும் விட்டு வெளியேற விரும்புகிறீர்களா?", - "logout_this_device_confirmation": "இந்த சாதனத்தை விட்டு வெளியேற விரும்புகிறீர்களா?", - "logs": "பதிவுகள்", - "longitude": "நெட்டாங்கு", - "look": "பார்", - "loop_videos": "லூப் வீடியோக்கள்", - "loop_videos_description": "விரிவான பார்வையாளரில் ஒரு வீடியோவை தானாக வளையப்படுத்தவும்.", - "main_branch_warning": "நீங்கள் ஒரு மேம்பாட்டு பதிப்பைப் பயன்படுத்துகிறீர்கள்; வெளியீட்டு பதிப்பைப் பயன்படுத்த நாங்கள் கடுமையாக பரிந்துரைக்கிறோம்!", - "main_menu": "பட்டியல் விளையாடுங்கள்", - "maintenance_description": "இம்மிச் பராமரிப்பு பயன்முறையில் வைக்கப்பட்டுள்ளது.", - "maintenance_end": "பராமரிப்பு பயன்முறையை முடிக்கவும்", - "maintenance_end_error": "பராமரிப்பு முறையை முடிக்க முடியவில்லை.", - "maintenance_logged_in_as": "தற்போது {user} ஆக உள்நுழைந்துள்ளீர்கள்", - "maintenance_title": "தற்காலிகமாக கிடைக்கவில்லை", - "make": "உருவாக்கு", - "manage_geolocation": "இருப்பிடத்தை நிர்வகிக்கவும்", - "manage_media_access_rationale": "படங்களை குப்பைக்கு நகர்த்துவதை முறையாகக் கையாளுவதற்கும் அதிலிருந்து அவற்றை மீட்டெடுப்பதற்கும் இந்த அனுமதி தேவை.", - "manage_media_access_settings": "அமைப்புகளைத் திற", - "manage_media_access_subtitle": "இம்மிக் செயலி மீடியா கோப்புகளை நிர்வகிக்கவும் நகர்த்தவும் அனுமதிக்கவும்.", - "manage_media_access_title": "மீடியா மேலாண்மை அணுகல்", - "manage_shared_links": "பகிரப்பட்ட இணைப்புகளை நிர்வகிக்கவும்", - "manage_sharing_with_partners": "கூட்டாளர்களுடன் பகிர்வை நிர்வகிக்கவும்", - "manage_the_app_settings": "பயன்பாட்டு அமைப்புகளை நிர்வகிக்கவும்", - "manage_your_account": "உங்கள் கணக்கை நிர்வகிக்கவும்", - "manage_your_api_keys": "உங்கள் பநிஇ விசைகளை நிர்வகிக்கவும்", - "manage_your_devices": "உங்கள் உள்நுழைந்த சாதனங்களை நிர்வகிக்கவும்", - "manage_your_oauth_connection": "உங்கள் OAuth இணைப்பை நிர்வகிக்கவும்", - "map": "வரைபடம்", - "map_assets_in_bounds": "{count, plural, =0 {இந்தப் பகுதியில் புகைப்படங்கள் எதுவும் இல்லை} one {# புகைப்படம்} other {# புகைப்படங்கள்}}", - "map_cannot_get_user_location": "பயனரின் இருப்பிடத்தைப் பெற முடியாது", - "map_location_dialog_yes": "ஆம்", - "map_location_picker_page_use_location": "இந்த இருப்பிடத்தைப் பயன்படுத்தவும்", - "map_location_service_disabled_content": "உங்கள் தற்போதைய இருப்பிடத்திலிருந்து சொத்துக்களைக் காட்ட இருப்பிட பணி இயக்கப்பட வேண்டும். இப்போது அதை இயக்க விரும்புகிறீர்களா?", - "map_location_service_disabled_title": "இருப்பிட பணி முடக்கப்பட்டது", - "map_marker_for_images": "{city}, {country}", - "map_marker_with_image": "படத்துடன் வரைபட மார்க்கர்", - "map_no_location_permission_content": "உங்கள் தற்போதைய இருப்பிடத்திலிருந்து சொத்துக்களைக் காட்ட இருப்பிட இசைவு தேவை. இப்போது அதை அனுமதிக்க விரும்புகிறீர்களா?", - "map_no_location_permission_title": "இருப்பிட இசைவு மறுக்கப்பட்டது", - "map_settings": "வரைபட அமைப்புகள்", - "map_settings_dark_mode": "இருண்ட முறை", - "map_settings_date_range_option_day": "கடந்த 24 மணி நேரம்", - "map_settings_date_range_option_days": "கடந்த {days} நாட்கள்", - "map_settings_date_range_option_year": "கடந்த ஆண்டு", - "map_settings_date_range_option_years": "கடந்த {years} ஆண்டுகள்", - "map_settings_dialog_title": "வரைபட அமைப்புகள்", - "map_settings_include_show_archived": "காப்பகப்படுத்தப்பட்டவர்", - "map_settings_include_show_partners": "கூட்டாளர்களைச் சேர்க்கவும்", - "map_settings_only_show_favorites": "பிடித்ததை மட்டும் காட்டு", - "map_settings_theme_settings": "வரைபட கருப்பொருள்", - "map_zoom_to_see_photos": "புகைப்படங்களைக் காண பெரிதாக்கவும்", - "mark_all_as_read": "அனைத்தையும் படித்தபடி குறிக்கவும்", - "mark_as_read": "படித்தபடி குறி", - "marked_all_as_read": "அனைத்தையும் வாசிப்பதாக குறிக்கப்பட்டுள்ளது", - "matches": "போட்டிகள்", - "matching_assets": "பொருந்தும் சொத்துக்கள்", - "media_type": "ஊடக வகை", - "memories": "நினைவுகள்", - "memories_all_caught_up": "அனைவரும் பிடிபட்டனர்", - "memories_check_back_tomorrow": "மேலும் நினைவுகளுக்கு நாளை மீண்டும் பார்க்கவும்", - "memories_setting_description": "உங்கள் நினைவுகளில் நீங்கள் பார்ப்பதை நிர்வகிக்கவும்", - "memories_start_over": "தொடக்க", - "memories_swipe_to_close": "மூடுவதற்கு ச்வைப் செய்யவும்", - "memory": "நினைவகம்", - "memory_lane_title": "நினைவக லேன் {title}", - "menu": "பட்டியல்", - "merge": "ஒன்றிணைக்கவும்", - "merge_people": "மக்களை ஒன்றிணைக்கவும்", - "merge_people_limit": "நீங்கள் ஒரு நேரத்தில் 5 முகங்கள் வரை மட்டுமே ஒன்றிணைக்க முடியும்", - "merge_people_prompt": "இந்த மக்களை ஒன்றிணைக்க விரும்புகிறீர்களா? இந்த நடவடிக்கை மாற்ற முடியாதது.", - "merge_people_successfully": "மக்களை வெற்றிகரமாக ஒன்றிணைக்கவும்", - "merged_people_count": "{count, plural, one {# நபர்} other {# பேர்}} இணைக்கப்பட்டது", - "minimize": "குறைக்கவும்", - "minute": "நிமிடங்கள்", - "minutes": "நிமிடங்கள்", - "missing": "இல்லை", - "mobile_app": "மொபைல் ஆப்", - "mobile_app_download_onboarding_note": "பின்வரும் விருப்பங்களைப் பயன்படுத்தி துணை மொபைல் பயன்பாட்டைப் பதிவிறக்கவும்", - "model": "மாதிரியுரு", - "month": "மாதம்", - "monthly_title_text_date_format": "Mmmm ஒய்", - "more": "மேலும்", - "move": "நகர்த்தவும்", - "move_off_locked_folder": "பூட்டப்பட்ட கோப்புறையிலிருந்து வெளியேறவும்", - "move_to": "நகர்த்து", - "move_to_lock_folder_action_prompt": "பூட்டிய கோப்புறையில் {count} சேர்க்கப்பட்டது", - "move_to_locked_folder": "பூட்டப்பட்ட கோப்புறையில் செல்லுங்கள்", - "move_to_locked_folder_confirmation": "இந்த புகைப்படங்கள் மற்றும் வீடியோ அனைத்து ஆல்பங்களிலிருந்தும் அகற்றப்படும், மேலும் பூட்டப்பட்ட கோப்புறையிலிருந்து மட்டுமே பார்க்க முடியும்", - "moved_to_archive": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}} காப்பகத்திற்கு நகர்த்தப்பட்டது", - "moved_to_library": "{count, plural, one {# சொத்து} other {# சொத்துக்கள்}} நூலகத்திற்கு நகர்த்தப்பட்டது", - "moved_to_trash": "குப்பைக்கு நகர்த்தப்பட்டது", - "multiselect_grid_edit_date_time_err_read_only": "சொத்து (களை) மட்டுமே படித்த தேதியைத் திருத்த முடியாது, தவிர்க்கவும்", - "multiselect_grid_edit_gps_err_read_only": "சொத்து (களை) மட்டுமே படிக்க, ச்கிப்பிங் ஆகியவற்றைத் திருத்த முடியாது", - "mute_memories": "முடக்கு நினைவுகள்", - "my_albums": "எனது ஆல்பங்கள்", - "name": "பெயர்", - "name_or_nickname": "பெயர் அல்லது புனைப்பெயர்", - "navigate": "வழிசெலுத்தவும்", - "navigate_to_time": "நேரத்திற்கு செல்லவும்", - "network_requirement_photos_upload": "காப்புப்பிரதி புகைப்படங்களுக்கு செல்லுலார் தரவைப் பயன்படுத்தவும்", - "network_requirement_videos_upload": "காப்புப்பிரதி வீடியோக்களுக்கு செல்லுலார் தரவைப் பயன்படுத்தவும்", - "network_requirements": "பிணைய தேவைகள்", - "network_requirements_updated": "பிணையம் தேவைகள் மாற்றப்பட்டன, காப்புப்பிரதி வரிசையை மீட்டமைக்கிறது", - "networking_settings": "நெட்வொர்க்கிங்", - "networking_subtitle": "சேவையக இறுதிப்புள்ளி அமைப்புகளை நிர்வகிக்கவும்", - "never": "ஒருபோதும்", - "new_album": "புதிய ஆல்பம்", - "new_api_key": "புதிய பநிஇ விசை", - "new_date_range": "புதிய தேதி வரம்பு", - "new_password": "புதிய கடவுச்சொல்", - "new_person": "புதிய நபர்", - "new_pin_code": "புதிய முள் குறியீடு", - "new_pin_code_subtitle": "பூட்டப்பட்ட கோப்புறையை அணுக இது உங்கள் முதல் முறையாகும். இந்த பக்கத்தை பாதுகாப்பாக அணுக ஒரு முள் குறியீட்டை உருவாக்கவும்", - "new_timeline": "புதிய காலவரிசை", - "new_update": "புதிய புதுப்பிப்பு", - "new_user_created": "புதிய பயனர் உருவாக்கப்பட்டது", - "new_version_available": "புதிய பதிப்பு கிடைக்கிறது", - "newest_first": "புதிய முதல்", - "next": "அடுத்தது", - "next_memory": "அடுத்த நினைவகம்", - "no": "இல்லை", - "no_albums_message": "உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்களை ஒழுங்கமைக்க ஒரு ஆல்பத்தை உருவாக்கவும்", - "no_albums_with_name_yet": "இந்த பெயருடன் இன்னும் ஆல்பங்கள் எதுவும் இல்லை என்று தெரிகிறது.", - "no_albums_yet": "உங்களிடம் இதுவரை எந்த ஆல்பங்களும் இல்லை என்று தெரிகிறது.", - "no_archived_assets_message": "உங்கள் புகைப்படக் காட்சியில் இருந்து அவற்றை மறைக்க புகைப்படங்கள் மற்றும் வீடியோக்களை காப்பகப்படுத்தவும்", - "no_assets_message": "உங்கள் முதல் புகைப்படத்தை பதிவேற்ற சொடுக்கு செய்க", - "no_assets_to_show": "காட்ட சொத்துக்கள் இல்லை", - "no_cast_devices_found": "நடிகர்கள் சாதனங்கள் எதுவும் கிடைக்கவில்லை", - "no_checksum_local": "செக்சம் எதுவும் கிடைக்கவில்லை - உள்ளக சொத்துக்களைப் பெற முடியாது", - "no_checksum_remote": "செக்சம் எதுவும் கிடைக்கவில்லை - தொலை சொத்து பெற முடியாது", - "no_devices": "அங்கீகரிக்கப்பட்ட சாதனங்கள் இல்லை", - "no_duplicates_found": "நகல்கள் எதுவும் காணப்படவில்லை.", - "no_exif_info_available": "EXIF செய்தி எதுவும் கிடைக்கவில்லை", - "no_explore_results_message": "உங்கள் தொகுப்பை ஆராய கூடுதல் புகைப்படங்களை பதிவேற்றவும்.", - "no_favorites_message": "உங்கள் சிறந்த படங்கள் மற்றும் வீடியோக்களை விரைவாகக் கண்டுபிடிக்க பிடித்தவைகளைச் சேர்க்கவும்", - "no_libraries_message": "உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்களைக் காண வெளிப்புற நூலகத்தை உருவாக்கவும்", - "no_local_assets_found": "இந்த செக்சம் மூலம் உள்ளக சொத்துக்கள் எதுவும் காணப்படவில்லை", - "no_location_set": "இடம் அமைக்கப்படவில்லை", - "no_locked_photos_message": "பூட்டப்பட்ட கோப்புறையில் உள்ள புகைப்படங்கள் மற்றும் வீடியோக்கள் மறைக்கப்பட்டுள்ளன, மேலும் நீங்கள் உங்கள் நூலகத்தை உலாவும்போது அல்லது தேடும்போது காண்பிக்கப்படாது.", - "no_name": "பெயர் இல்லை", - "no_notifications": "அறிவிப்புகள் இல்லை", - "no_people_found": "பொருந்தக்கூடிய நபர்கள் எதுவும் கிடைக்கவில்லை", - "no_places": "இடங்கள் இல்லை", - "no_remote_assets_found": "இந்த செக்சம் மூலம் தொலைநிலை சொத்துக்கள் எதுவும் காணப்படவில்லை", - "no_results": "முடிவுகள் இல்லை", - "no_results_description": "ஒரு ஒத்த அல்லது பொதுவான முக்கிய சொல்லை முயற்சிக்கவும்", - "no_shared_albums_message": "உங்கள் நெட்வொர்க்கில் உள்ளவர்களுடன் புகைப்படங்களையும் வீடியோக்களையும் பகிர்ந்து கொள்ள ஒரு ஆல்பத்தை உருவாக்கவும்", - "no_uploads_in_progress": "பதிவேற்றங்கள் முன்னேற்றத்தில் இல்லை", - "not_allowed": "அனுமதிக்கப்படவில்லை", - "not_available": "இதற்கில்லை", - "not_in_any_album": "எந்த ஆல்பத்திலும் இல்லை", - "not_selected": "தேர்ந்தெடுக்கப்படவில்லை", - "note_apply_storage_label_to_previously_uploaded assets": "குறிப்பு: முன்னர் பதிவேற்றப்பட்ட சொத்துக்களுக்கு சேமிப்பக லேபிளை பயன்படுத்த, இயக்கவும்", - "notes": "குறிப்புகள்", - "nothing_here_yet": "இன்னும் இங்கே எதுவும் இல்லை", - "notification_permission_dialog_content": "அறிவிப்புகளை இயக்க, அமைப்புகளுக்குச் சென்று இசைவு என்பதைத் தேர்ந்தெடுக்கவும்.", - "notification_permission_list_tile_content": "அறிவிப்புகளை இயக்க இசைவு வழங்கவும்.", - "notification_permission_list_tile_enable_button": "அறிவிப்புகளை இயக்கவும்", - "notification_permission_list_tile_title": "அறிவிப்பு இசைவு", - "notification_toggle_setting_description": "மின்னஞ்சல் அறிவிப்புகளை இயக்கவும்", - "notifications": "அறிவிப்புகள்", - "notifications_setting_description": "அறிவிப்புகளை நிர்வகிக்கவும்", - "oauth": "Oauth", - "obtainium_configurator": "ஒப்டெய்னியம் கட்டமைப்பாளர்", - "obtainium_configurator_instructions": "Immich GitHub வெளியீட்டில் இருந்து நேரடியாக ஆண்ட்ராய்டு பயன்பாட்டை நிறுவவும் புதுப்பிக்கவும் Obtainium ஐப் பயன்படுத்தவும். பநிஇ விசையை உருவாக்கி, உங்கள் ஒப்டெய்னியம் உள்ளமைவு இணைப்பை உருவாக்க ஒரு மாறுபாட்டைத் தேர்ந்தெடுக்கவும்", - "ocr": "ஓசிஆர்", - "official_immich_resources": "உத்தியோகபூர்வ இம்மா வளங்கள்", - "offline": "இணையமில்லாமல்", - "offset": "ஈடுசெய்யும்", - "ok": "சரி", - "oldest_first": "முதலில் பழமையானது", - "on_this_device": "இந்த சாதனத்தில்", - "onboarding": "ஆன் போர்டிங்", - "onboarding_locale_description": "உங்களுக்கு விருப்பமான மொழியைத் தேர்ந்தெடுக்கவும். இதை உங்கள் அமைப்புகளில் பின்னர் மாற்றலாம்.", - "onboarding_privacy_description": "பின்வரும் (விரும்பினால்) நற்பொருத்தங்கள் வெளிப்புற சேவைகளை நம்பியுள்ளன, மேலும் அமைப்புகளில் எந்த நேரத்திலும் முடக்கப்படலாம்.", - "onboarding_server_welcome_description": "சில பொதுவான அமைப்புகளுடன் உங்கள் நிகழ்வை அமைப்போம்.", - "onboarding_theme_description": "உங்கள் உதாரணத்திற்கு வண்ண கருப்பொருளைத் தேர்வுசெய்க. இதை உங்கள் அமைப்புகளில் பின்னர் மாற்றலாம்.", - "onboarding_user_welcome_description": "நீங்கள் தொடங்குவோம்!", - "onboarding_welcome_user": "வரவேற்கிறோம், {user}", - "online": "ஆன்லைனில்", - "only_favorites": "பிடித்தவை மட்டுமே", - "open": "திற", - "open_in_map_view": "வரைபடக் காட்சியில் திறந்திருக்கும்", - "open_in_openstreetmap": "OpenStreetMap இல் திறந்திருக்கும்", - "open_the_search_filters": "தேடல் வடிப்பான்களைத் திறக்கவும்", - "options": "விருப்பங்கள்", - "or": "அல்லது", - "organize_into_albums": "ஆல்பங்களாக ஒழுங்கமைக்கவும்", - "organize_into_albums_description": "தற்போதைய ஒத்திசைவு அமைப்புகளைப் பயன்படுத்தி ஏற்கனவே உள்ள புகைப்படங்களை ஆல்பங்களில் வைக்கவும்", - "organize_your_library": "உங்கள் நூலகத்தை ஒழுங்கமைக்கவும்", - "original": "அசல்", - "other": "மற்றொன்று", - "other_devices": "பிற சாதனங்கள்", - "other_entities": "பிற நிறுவனங்கள்", - "other_variables": "பிற மாறிகள்", - "owned": "சொந்தமானது", - "owner": "உரிமையாளர்", - "page": "பக்கம்", - "partner": "கூட்டாளர்", - "partner_can_access": "{partner} அணுகலாம்", - "partner_can_access_assets": "காப்பகப்படுத்தப்பட்ட மற்றும் நீக்கப்பட்டவை தவிர உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்கள் அனைத்தும்", - "partner_can_access_location": "உங்கள் புகைப்படங்கள் எடுக்கப்பட்ட இடம்", - "partner_list_user_photos": "{user} இன் புகைப்படங்கள்", - "partner_list_view_all": "அனைத்தையும் காண்க", - "partner_page_empty_message": "உங்கள் புகைப்படங்கள் இன்னும் எந்த கூட்டாளருடனும் பகிரப்படவில்லை.", - "partner_page_no_more_users": "சேர்க்க இனி பயனர்கள் இல்லை", - "partner_page_partner_add_failed": "கூட்டாளரைச் சேர்க்கத் தவறிவிட்டது", - "partner_page_select_partner": "கூட்டாளரைத் தேர்ந்தெடுக்கவும்", - "partner_page_shared_to_title": "பகிரப்பட்டது", - "partner_page_stop_sharing_content": "{partner} இனி உங்கள் படங்களை அணுக முடியாது.", - "partner_sharing": "கூட்டாளர் பகிர்வு", - "partners": "கூட்டாளர்கள்", - "password": "கடவுச்சொல்", - "password_does_not_match": "கடவுச்சொல் பொருந்தவில்லை", - "password_required": "கடவுச்சொல் தேவை", - "password_reset_success": "கடவுச்சொல் மீட்டமை செய்", - "past_durations": { - "days": "கடந்த {days, plural, one {நாள்} other {# நாட்கள்}}", - "hours": "கடந்த {hours, plural, one {மணிநேரம்} other {# மணிநேரங்கள்}}", - "years": "கடந்த {years, plural, one {ஆண்டு} other {# ஆண்டுகள்}}" - }, - "path": "பாதை", - "pattern": "முறை", - "pause": "இடைநிறுத்தம்", - "pause_memories": "இடைநிறுத்த நினைவுகள்", - "paused": "இடைநிறுத்தப்பட்டது", - "pending": "நிலுவையில் உள்ளது", - "people": "மக்கள்", - "people_edits_count": "{count, plural, one {# நபர்} other {# பேர்}} திருத்தப்பட்டது", - "people_feature_description": "மக்கள் தொகுத்த புகைப்படங்கள் மற்றும் வீடியோக்களை உலாவுதல்", - "people_sidebar_description": "பக்கப்பட்டியில் உள்ளவர்களுக்கு ஒரு இணைப்பைக் காண்பி", - "permanent_deletion_warning": "நிரந்தர நீக்குதல் எச்சரிக்கை", - "permanent_deletion_warning_setting_description": "சொத்துக்களை நிரந்தரமாக நீக்கும்போது ஒரு எச்சரிக்கையைக் காட்டுங்கள்", - "permanently_delete": "நிரந்தரமாக நீக்கு", - "permanently_delete_assets_count": "நிரந்தரமாக நீக்கப்பட்டது {count, plural, one {சொத்து} other {சொத்துக்கள்}}", - "permanently_delete_assets_prompt": "{count, plural, one {this asset?} other {these # assets?}} என்பதை நிரந்தரமாக நீக்க விரும்புகிறீர்களா? இது {count, plural, one {it from its} other {them from their}} ஆல்பத்தையும் நீக்கும்.", - "permanently_deleted_asset": "நிரந்தரமாக நீக்கப்பட்ட சொத்து", - "permanently_deleted_assets_count": "நிரந்தரமாக நீக்கப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", - "permission": "இசைவு", - "permission_empty": "உங்கள் இசைவு காலியாக இருக்கக்கூடாது", - "permission_onboarding_back": "பின்", - "permission_onboarding_continue_anyway": "எப்படியும் தொடரவும்", - "permission_onboarding_get_started": "தொடங்கவும்", - "permission_onboarding_go_to_settings": "அமைப்புகளுக்குச் செல்லுங்கள்", - "permission_onboarding_permission_denied": "இசைவு மறுக்கப்பட்டது. இம்மிச்சைப் பயன்படுத்த, அமைப்புகளில் புகைப்படம் மற்றும் வீடியோ அனுமதிகளை வழங்கவும்.", - "permission_onboarding_permission_granted": "இசைவு வழங்கப்பட்டது! நீங்கள் அனைவரும் அமைக்கப்பட்டிருக்கிறீர்கள்.", - "permission_onboarding_permission_limited": "இசைவு லிமிடெட். உங்கள் முழு கேலரி சேகரிப்பையும் நிர்வகிக்கவும் நிர்வகிக்கவும், அமைப்புகளில் புகைப்படம் மற்றும் வீடியோ அனுமதிகளை வழங்கவும்.", - "permission_onboarding_request": "உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்களைக் காண இம்மிச்சுக்கு இசைவு தேவை.", - "person": "ஆள்", - "person_age_months": "{months, plural, one {# திங்கள்} other {# திங்கள்கள்}} அகவை", - "person_age_year_months": "1 வருடம், {months, plural, one {# திங்கள்} other {# திங்கள்கள்}} அகவை", - "person_age_years": "{years, plural, other {# ஆண்டுகள்}} பழையது", - "person_birthdate": "{date} இல் பிறந்தார்", - "person_hidden": "{name}{hidden, select, true { (மறைக்கப்பட்ட)} other {}}", - "photo_shared_all_users": "உங்கள் புகைப்படங்களை எல்லா பயனர்களுடனும் பகிர்ந்து கொண்டதாகத் தெரிகிறது அல்லது பகிர்வதற்கு உங்களிடம் எந்த பயனரும் இல்லை.", - "photos": "புகைப்படங்கள்", - "photos_and_videos": "புகைப்படங்கள் & வீடியோக்கள்", - "photos_count": "{count, plural, one {{count, number} படம்} other {{count, number} படங்கள்}}", - "photos_from_previous_years": "முந்தைய ஆண்டுகளின் புகைப்படங்கள்", - "pick_a_location": "ஒரு இடத்தைத் தேர்ந்தெடுங்கள்", - "pick_custom_range": "தனிப்பயன் வரம்பு", - "pick_date_range": "தேதி வரம்பைத் தேர்ந்தெடுக்கவும்", - "pin_code_changed_successfully": "முள் குறியீட்டை வெற்றிகரமாக மாற்றியது", - "pin_code_reset_successfully": "முள் குறியீட்டை வெற்றிகரமாக மீட்டமைக்கவும்", - "pin_code_setup_successfully": "முள் குறியீட்டை வெற்றிகரமாக அமைக்கவும்", - "pin_verification": "குறியீடு சரிபார்ப்பு", - "place": "இடம்", - "places": "இடங்கள்", - "places_count": "{count, plural, one {{count, number} இடம்} other {{count, number} இடங்கள்}}", - "play": "விளையாடுங்கள்", - "play_memories": "பிளேமெமரிகள்", - "play_motion_photo": "இயக்க புகைப்படத்தை விளையாடுங்கள்", - "play_or_pause_video": "வீடியோவை இயக்கவும் அல்லது இடைநிறுத்தவும்", - "play_original_video": "அசல் காணொளியை இயக்கு", - "play_original_video_setting_description": "குறிமாற்றம் செய்யப்பட்ட காணொளிகளைவிட அசல் காணொளிகளின் இயக்கத்தை விரும்புங்கள். அசல் சொத்து இணக்கமாக இல்லாவிட்டால் அது சரியாக இயக்கப்படாமல் போகலாம்.", - "play_transcoded_video": "குறிமாற்றம் செய்யப்பட்ட காணொளியை இயக்கு", - "please_auth_to_access": "அணுகலை அங்கீகரிக்கவும்", - "port": "துறைமுகம்", - "preferences_settings_subtitle": "பயன்பாட்டின் விருப்பங்களை நிர்வகிக்கவும்", - "preferences_settings_title": "விருப்பத்தேர்வுகள்", - "preparing": "தயாராகிறது", - "preset": "முன்னமைவு", - "preview": "முன்னோட்டம்", - "previous": "முந்தைய", - "previous_memory": "முந்தைய நினைவகம்", - "previous_or_next_day": "நாள் முன்னோக்கி/பின்னோக்கி", - "previous_or_next_month": "மாதம் முன்னோக்கி/பின்னோக்கி", - "previous_or_next_photo": "புகைப்படம் முன்னோக்கி/பின்னோக்கி", - "previous_or_next_year": "ஆண்டு முன்னோக்கி/பின்னோக்கி", - "primary": "முதன்மை", - "privacy": "தனியுரிமை", - "profile": "சுயவிவரம்", - "profile_drawer_app_logs": "பதிவுகள்", - "profile_drawer_client_server_up_to_date": "வாங்கி மற்றும் சேவையகம் புதுப்பித்த நிலையில் உள்ளன", - "profile_drawer_github": "கிட்ஹப்", - "profile_drawer_readonly_mode": "படிக்க மட்டும் பயன்முறை இயக்கப்பட்டது. வெளியேற பயனர் அவதார் ஐகானை நீண்ட நேரம் அழுத்தவும்.", - "profile_image_of_user": "{user} இன் சுயவிவரப் படம்", - "profile_picture_set": "சுயவிவரப் பட தொகுப்பு.", - "public_album": "பொது ஆல்பம்", - "public_share": "பொது பங்கு", - "purchase_account_info": "ஆதரவாளர்", - "purchase_activated_subtitle": "இம்மிச் மற்றும் திறந்த மூல மென்பொருளை ஆதரித்ததற்கு நன்றி", - "purchase_activated_time": "{date} இல் செயல்படுத்தப்பட்டது", - "purchase_activated_title": "உங்கள் திறவுகோல் வெற்றிகரமாக செயல்படுத்தப்பட்டுள்ளது", - "purchase_button_activate": "செயல்படுத்து", - "purchase_button_buy": "வாங்க", - "purchase_button_buy_immich": "இம்மியை வாங்கவும்", - "purchase_button_never_show_again": "மீண்டும் ஒருபோதும் காட்ட வேண்டாம்", - "purchase_button_reminder": "30 நாட்களில் எனக்கு நினைவூட்டுங்கள்", - "purchase_button_remove_key": "விசையை அகற்று", - "purchase_button_select": "தேர்ந்தெடு", - "purchase_failed_activation": "செயல்படுத்தத் தவறிவிட்டது! சரியான தயாரிப்பு விசைக்கு உங்கள் மின்னஞ்சலை சரிபார்க்கவும்!", - "purchase_individual_description_1": "ஒரு தனிநபருக்கு", - "purchase_individual_description_2": "ஆதரவாளர் நிலை", - "purchase_individual_title": "தனிப்பட்ட", - "purchase_input_suggestion": "தயாரிப்பு விசை உள்ளதா? கீழே உள்ள விசையை உள்ளிடவும்", - "purchase_license_subtitle": "சேவையின் தொடர்ச்சியான வளர்ச்சியை ஆதரிக்க இம்மியை வாங்கவும்", - "purchase_lifetime_description": "வாழ்நாள் கொள்முதல்", - "purchase_option_title": "விருப்பங்களை வாங்கவும்", - "purchase_panel_info_1": "இம்மியை உருவாக்குவதற்கு நிறைய நேரமும் முயற்சியும் தேவைப்படுகிறது, மேலும் முழுநேர பொறியியலாளர்கள் அதை எங்களால் முடிந்தவரை சிறப்பாகச் செய்ய வேலை செய்கிறார்கள். எங்கள் நோக்கம் திறந்த மூல மென்பொருள் மற்றும் நெறிமுறை வணிக நடைமுறைகள் டெவலப்பர்களுக்கான நிலையான வருமான ஆதாரமாக மாறுவதும், சுரண்டல் முகில் சேவைகளுக்கு உண்மையான மாற்றுகளுடன் தனியுரிமை-மரியாதைக்குரிய சுற்றுச்சூழல் அமைப்பை உருவாக்குவதும் ஆகும்.", - "purchase_panel_info_2": "பேவால்களைச் சேர்க்காமல் இருப்பதில் நாங்கள் உறுதியாக இருப்பதால், இந்த கொள்முதல் இம்மிச்சில் கூடுதல் அம்சங்களை உங்களுக்கு வழங்காது. இம்மிச்சின் தற்போதைய வளர்ச்சியை ஆதரிக்க உங்களைப் போன்ற பயனர்களை நாங்கள் நம்பியுள்ளோம்.", - "purchase_panel_title": "திட்டத்தை ஆதரிக்கவும்", - "purchase_per_server": "ஒரு சேவையகத்திற்கு", - "purchase_per_user": "ஒரு பயனருக்கு", - "purchase_remove_product_key": "தயாரிப்பு விசையை அகற்று", - "purchase_remove_product_key_prompt": "தயாரிப்பு விசையை அகற்ற விரும்புகிறீர்களா?", - "purchase_remove_server_product_key": "சேவையக தயாரிப்பு விசையை அகற்று", - "purchase_remove_server_product_key_prompt": "சேவையக தயாரிப்பு விசையை அகற்ற விரும்புகிறீர்களா?", - "purchase_server_description_1": "முழு சேவையகத்திற்கும்", - "purchase_server_description_2": "ஆதரவாளர் நிலை", - "purchase_server_title": "சேவையகம்", - "purchase_settings_server_activated": "சேவையக தயாரிப்பு விசை நிர்வாகியால் நிர்வகிக்கப்படுகிறது", - "query_asset_id": "வினவல் சொத்து அடையாளம்", - "queue_status": "வரிசை {count}/{total}", - "rating": "நட்சத்திர மதிப்பீடு", - "rating_clear": "தெளிவான மதிப்பீடு", - "rating_count": "{count, plural, one {# விண்மீன்} other {# விண்மீன்கள்}}", - "rating_description": "செய்தி குழுவில் EXIF மதிப்பீட்டைக் காண்பி", - "reaction_options": "எதிர்வினை விருப்பங்கள்", - "read_changelog": "சேஞ்ச்லாக் படிக்கவும்", - "readonly_mode_disabled": "படிக்க மட்டும் பயன்முறை முடக்கப்பட்டுள்ளது", - "readonly_mode_enabled": "படிக்க மட்டும் பயன்முறை இயக்கப்பட்டது", - "ready_for_upload": "பதிவேற்றத் தயார்", - "reassign": "மீண்டும் ஒதுக்கு", - "reassigned_assets_to_existing_person": "மீண்டும் ஒதுக்கப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}} to {name, select, null {ஒரு இருக்கும் நபர்} other {{name}}}", - "reassigned_assets_to_new_person": "புதிய நபருக்கு {count, plural, one {# சொத்து} other {# சொத்துகள்}} மீண்டும் ஒதுக்கப்பட்டது", - "reassing_hint": "தேர்ந்தெடுக்கப்பட்ட சொத்துக்களை ஏற்கனவே இருக்கும் நபருக்கு ஒதுக்குங்கள்", - "recent": "அண்மைக் கால", - "recent-albums": "அண்மைக் கால ஆல்பங்கள்", - "recent_searches": "அண்மைக் கால தேடல்கள்", - "recently_added": "அண்மைக் காலத்தில் சேர்க்கப்பட்டது", - "recently_added_page_title": "அண்மைக் காலத்தில் சேர்க்கப்பட்டது", - "recently_taken": "அண்மைக் காலத்தில் எடுக்கப்பட்டது", - "recently_taken_page_title": "அண்மைக் காலத்தில் எடுக்கப்பட்டது", - "refresh": "புதுப்பிப்பு", - "refresh_encoded_videos": "குறியிடப்பட்ட வீடியோக்களை புதுப்பிக்கவும்", - "refresh_faces": "முகங்களைப் புதுப்பிக்கவும்", - "refresh_metadata": "மெட்டாடேட்டாவை புதுப்பிக்கவும்", - "refresh_thumbnails": "சிறு உருவங்களை புதுப்பிக்கவும்", - "refreshed": "புத்துணர்ச்சி", - "refreshes_every_file": "ஏற்கனவே உள்ள மற்றும் புதிய கோப்புகளை மீண்டும் படிக்கிறது", - "refreshing_encoded_video": "புத்துணர்ச்சியூட்டும் குறியாக்கப்பட்ட வீடியோ", - "refreshing_faces": "புத்துணர்ச்சியூட்டும் முகங்கள்", - "refreshing_metadata": "புத்துணர்ச்சியூட்டும் மேனிலை தரவு", - "regenerating_thumbnails": "சிறுபடங்களை மீண்டும் உருவாக்குகிறது", - "remote": "தொலைநிலை", - "remote_assets": "தொலை சொத்துக்கள்", - "remote_media_summary": "தொலை ஊடக சுருக்கம்", - "remove": "அகற்று", - "remove_assets_album_confirmation": "செருகேட்டிலிருந்து {count, plural, one {# சொத்து} other {# சொத்துக்கள்}} நிச்சயமாக அகற்ற விரும்புகிறீர்களா?", - "remove_assets_shared_link_confirmation": "இந்தப் பகிரப்பட்ட இணைப்பிலிருந்து {count, plural, one {# சொத்து} other {# சொத்துக்களை}} நிச்சயமாக அகற்ற விரும்புகிறீர்களா?", - "remove_assets_title": "சொத்துக்களை அகற்றவா?", - "remove_custom_date_range": "தனிப்பயன் தேதி வரம்பை அகற்று", - "remove_deleted_assets": "நீக்கப்பட்ட சொத்துக்களை அகற்றவும்", - "remove_from_album": "ஆல்பத்திலிருந்து அகற்று", - "remove_from_album_action_prompt": "ஆல்பத்திலிருந்து {count} அகற்றப்பட்டது", - "remove_from_favorites": "பிடித்தவைகளிலிருந்து அகற்று", - "remove_from_lock_folder_action_prompt": "பூட்டிய கோப்புறையிலிருந்து {count} அகற்றப்பட்டது", - "remove_from_locked_folder": "பூட்டப்பட்ட கோப்புறையிலிருந்து அகற்று", - "remove_from_locked_folder_confirmation": "பூட்டப்பட்ட கோப்புறையிலிருந்து இந்த புகைப்படங்களையும் வீடியோக்களையும் நகர்த்த விரும்புகிறீர்களா? அவை உங்கள் நூலகத்தில் தெரியும்.", - "remove_from_shared_link": "பகிரப்பட்ட இணைப்பிலிருந்து அகற்று", - "remove_memory": "நினைவகத்தை அகற்று", - "remove_photo_from_memory": "இந்த நினைவிலிருந்து புகைப்படத்தை அகற்று", - "remove_tag": "குறிச்சொல்லை அகற்று", - "remove_url": "முகவரி ஐ அகற்று", - "remove_user": "பயனரை அகற்று", - "removed_api_key": "அகற்றப்பட்ட பநிஇ விசை: {name}", - "removed_from_archive": "காப்பகத்திலிருந்து அகற்றப்பட்டது", - "removed_from_favorites": "பிடித்தவைகளிலிருந்து அகற்றப்பட்டது", - "removed_from_favorites_count": "பிடித்தவற்றிலிருந்து {count, plural, other {நீக்கப்பட்டது #}}", - "removed_memory": "அகற்றப்பட்ட நினைவகம்", - "removed_photo_from_memory": "நினைவிலிருந்து புகைப்படத்தை அகற்றியது", - "removed_tagged_assets": "{count, plural, one {# asset} other {# சொத்துக்கள்}} என்பதிலிருந்து குறிச்சொல் அகற்றப்பட்டது", - "rename": "மறுபெயரிடுங்கள்", - "repair": "பழுது", - "repair_no_results_message": "கட்டுப்படுத்தப்படாத மற்றும் காணாமல் போன கோப்புகள் இங்கே காண்பிக்கப்படும்", - "replace_with_upload": "பதிவேற்றத்துடன் மாற்றவும்", - "repository": "களஞ்சியம்", - "require_password": "கடவுச்சொல் தேவை", - "require_user_to_change_password_on_first_login": "முதல் உள்நுழைவில் கடவுச்சொல்லை மாற்ற பயனர் தேவை", - "rescan": "ரெச்கான்", - "reset": "மீட்டமை", - "reset_password": "கடவுச்சொல்லை மீட்டமைக்கவும்", - "reset_people_visibility": "மக்களின் தெரிவுநிலையை மீட்டமைக்கவும்", - "reset_pin_code": "முள் குறியீட்டை மீட்டமைக்கவும்", - "reset_pin_code_description": "உங்கள் முள் குறியீட்டை மறந்துவிட்டால், அதை மீட்டமைக்க சேவையக நிர்வாகியை தொடர்பு கொள்ளலாம்", - "reset_pin_code_success": "முள் குறியீட்டை வெற்றிகரமாக மீட்டமைக்கவும்", - "reset_pin_code_with_password": "உங்கள் கடவுச்சொல் மூலம் உங்கள் முள் குறியீட்டை எப்போதும் மீட்டமைக்கலாம்", - "reset_sqlite": "SQLite தரவுத்தளத்தை மீட்டமைக்கவும்", - "reset_sqlite_confirmation": "SQLITE தரவுத்தளத்தை மீட்டமைக்க விரும்புகிறீர்களா? தரவை மீண்டும் ஒத்திசைக்க நீங்கள் வெளியேறி மீண்டும் உள்நுழைய வேண்டும்", - "reset_sqlite_success": "SQLITE தரவுத்தளத்தை வெற்றிகரமாக மீட்டமைக்கவும்", - "reset_to_default": "இயல்புநிலைக்கு மீட்டமைக்கவும்", - "resolution": "தெளிவுத்திறன்", - "resolve_duplicates": "நகல்களைத் தீர்க்கவும்", - "resolved_all_duplicates": "அனைத்து நகல்களையும் தீர்க்கும்", - "restore": "மீட்டமை", - "restore_all": "அனைத்தையும் மீட்டெடுக்கவும்", - "restore_trash_action_prompt": "குப்பையிலிருந்து {count} மீட்டெடுக்கப்பட்டது", - "restore_user": "பயனரை மீட்டமைக்கவும்", - "restored_asset": "மீட்டெடுக்கப்பட்ட சொத்து", - "resume": "மீண்டும் தொடங்குங்கள்", - "resume_paused_jobs": "{count, plural, one {# இடைநிறுத்தப்பட்ட வேலை} other {# இடைநிறுத்தப்பட்ட வேலைகள்}} மறுதொடக்கம்", - "retry_upload": "பதிவேற்ற முயற்சிக்கவும்", - "review_duplicates": "நகல்களை மதிப்பாய்வு செய்யவும்", - "review_large_files": "பெரிய கோப்புகளை மதிப்பாய்வு செய்யவும்", - "role": "பங்கு", - "role_editor": "திருத்தி", - "role_viewer": "பார்வையாளர்", - "running": "இயங்கும்", - "save": "சேமி", - "save_to_gallery": "கேலரியில் சேமிக்கவும்", - "saved": "சேமிக்கப்பட்டது", - "saved_api_key": "சேமித்த பநிஇ விசை", - "saved_profile": "சேமித்த சுயவிவரம்", - "saved_settings": "சேமித்த அமைப்புகள்", - "say_something": "ஏதாவது சொல்லுங்கள்", - "scaffold_body_error_occurred": "பிழை ஏற்பட்டது", - "scan_all_libraries": "அனைத்து நூலகங்களையும் ச்கேன் செய்யுங்கள்", - "scan_library": "ச்கேன்", - "scan_settings": "அமைப்புகளை ச்கேன் செய்யுங்கள்", - "scanning_for_album": "ஆல்பத்திற்கு ச்கேனிங் ...", - "search": "தேடல்", - "search_albums": "ஆல்பங்களைத் தேடுங்கள்", - "search_by_context": "சூழலால் தேடுங்கள்", - "search_by_description": "விளக்கத்தின் மூலம் தேடுங்கள்", - "search_by_description_example": "சப்பாவில் நடைபயணம்", - "search_by_filename": "கோப்பு பெயர் அல்லது நீட்டிப்பு மூலம் தேடுங்கள்", - "search_by_filename_example": "I.E. IMG_1234.JPG அல்லது PNG", - "search_by_ocr": "ஓசிஆர் மூலம் தேடு", - "search_by_ocr_example": "லேட்", - "search_camera_lens_model": "கண்ணாடி வில்லை மாதிரியைத் தேடு...", - "search_camera_make": "தேடல் கேமரா செய்யுங்கள் ...", - "search_camera_model": "கேமரா மாதிரியைத் தேடுங்கள் ...", - "search_city": "தேடல் நகரம் ...", - "search_country": "தேடல் நாடு ...", - "search_filter_apply": "வடிகட்டியைப் பயன்படுத்துங்கள்", - "search_filter_camera_title": "கேமரா வகையைத் தேர்ந்தெடுக்கவும்", - "search_filter_date": "திகதி", - "search_filter_date_interval": "{start} பெறுநர் {end}", - "search_filter_date_title": "தேதி வரம்பைத் தேர்ந்தெடுக்கவும்", - "search_filter_display_option_not_in_album": "ஆல்பத்தில் இல்லை", - "search_filter_display_options": "காட்சி விருப்பங்கள்", - "search_filter_filename": "கோப்பு பெயர் மூலம் தேடுங்கள்", - "search_filter_location": "இடம்", - "search_filter_location_title": "இருப்பிடத்தைத் தேர்ந்தெடுக்கவும்", - "search_filter_media_type": "ஊடக வகை", - "search_filter_media_type_title": "மீடியா வகையைத் தேர்ந்தெடுக்கவும்", - "search_filter_ocr": "ஓசிஆர் மூலம் தேடு", - "search_filter_people_title": "மக்களைத் தேர்ந்தெடுக்கவும்", - "search_for": "தேடுங்கள்", - "search_for_existing_person": "இருக்கும் நபரைத் தேடுங்கள்", - "search_no_more_result": "மேலும் முடிவுகள் இல்லை", - "search_no_people": "மக்கள் இல்லை", - "search_no_people_named": "\"{name}\" என்று பெயரிடப்பட்டவர்கள் யாரும் இல்லை", - "search_no_result": "முடிவுகள் எதுவும் கிடைக்கவில்லை, வேறு தேடல் காலத்தை அல்லது கலவையை முயற்சிக்கவும்", - "search_options": "தேடல் விருப்பங்கள்", - "search_page_categories": "வகைகள்", - "search_page_motion_photos": "இயக்க புகைப்படங்கள்", - "search_page_no_objects": "பொருள் செய்தி எதுவும் கிடைக்கவில்லை", - "search_page_no_places": "இடங்கள் செய்தி கிடைக்கவில்லை", - "search_page_screenshots": "திரைக்காட்சிகள்", - "search_page_search_photos_videos": "உங்கள் புகைப்படங்கள் மற்றும் வீடியோக்களைத் தேடுங்கள்", - "search_page_selfies": "செல்ஃபிகள்", - "search_page_things": "விசயங்கள்", - "search_page_view_all_button": "அனைத்தையும் காண்க", - "search_page_your_activity": "உங்கள் செயல்பாடு", - "search_page_your_map": "உங்கள் வரைபடம்", - "search_people": "மக்களைத் தேடுங்கள்", - "search_places": "இடங்களைத் தேடுங்கள்", - "search_rating": "மதிப்பீட்டின் மூலம் தேடுங்கள் ...", - "search_result_page_new_search_hint": "புதிய தேடல்", - "search_settings": "அமைப்புகளைத் தேடுங்கள்", - "search_state": "தேடல் நிலை ...", - "search_suggestion_list_smart_search_hint_1": "மெட்டாடேட்டாவைத் தேட, நிகழ்வைப் பயன்படுத்த, அறிவுள்ள தேடல் இயல்புநிலையாக இயக்கப்பட்டது ", - "search_suggestion_list_smart_search_hint_2": "எம்: உங்கள் தேடல்-காலநிலை", - "search_tags": "குறிச்சொற்களைத் தேடுங்கள் ...", - "search_timezone": "நேர மண்டலத்தைத் தேடுங்கள் ...", - "search_type": "தேடல் வகை", - "search_your_photos": "உங்கள் புகைப்படங்களைத் தேடுங்கள்", - "searching_locales": "இடங்களைத் தேடுகிறது ...", - "second": "இரண்டாவது", - "see_all_people": "எல்லா மக்களையும் பாருங்கள்", - "select": "தேர்ந்தெடு", - "select_album_cover": "ஆல்பம் அட்டையைத் தேர்ந்தெடுக்கவும்", - "select_all": "அனைத்தையும் தெரிவுசெய்", - "select_all_duplicates": "அனைத்து நகல்களையும் தேர்ந்தெடுக்கவும்", - "select_all_in": "{group} இல் உள்ள அனைத்தையும் தேர்ந்தெடுக்கவும்", - "select_avatar_color": "அவதார் நிறத்தைத் தேர்ந்தெடுக்கவும்", - "select_face": "முகத்தைத் தேர்ந்தெடுக்கவும்", - "select_featured_photo": "பிரத்யேக புகைப்படத்தைத் தேர்ந்தெடுக்கவும்", - "select_from_computer": "கணினியிலிருந்து தேர்ந்தெடுக்கவும்", - "select_keep_all": "அனைத்தையும் வைத்திருங்கள் என்பதைத் தேர்ந்தெடுக்கவும்", - "select_library_owner": "நூலக உரிமையாளரைத் தேர்ந்தெடுக்கவும்", - "select_new_face": "புதிய முகத்தைத் தேர்ந்தெடுக்கவும்", - "select_person_to_tag": "குறிக்க ஒரு நபரைத் தேர்ந்தெடுக்கவும்", - "select_photos": "புகைப்படங்களைத் தேர்ந்தெடுக்கவும்", - "select_trash_all": "குப்பைத் தொட்டியைத் தேர்ந்தெடுக்கவும்", - "select_user_for_sharing_page_err_album": "ஆல்பத்தை உருவாக்கத் தவறிவிட்டது", - "selected": "தேர்ந்தெடுக்கப்பட்டது", - "selected_count": "{count, plural, other {# தேர்ந்தெடுக்கப்பட்டன}}", - "selected_gps_coordinates": "தேர்ந்தெடுக்கப்பட்ட சி.பி.எச் ஆயத்தொலைவுகள்", - "send_message": "செய்தி அனுப்பவும்", - "send_welcome_email": "வரவேற்பு மின்னஞ்சலை அனுப்பவும்", - "server_endpoint": "சேவையக இறுதிப்புள்ளி", - "server_info_box_app_version": "பயன்பாட்டு பதிப்பு", - "server_info_box_server_url": "சேவையக முகவரி", - "server_offline": "சேவையகம் இணைப்பில்லாத", - "server_online": "ஆன்லைனில் சேவையகம்", - "server_privacy": "சேவையக தனியுரிமை", - "server_restarting_description": "இந்தப் பக்கம் சிறிது நேரத்தில் புதுப்பிக்கப்படும்.", - "server_restarting_title": "சேவையகம் மறுதொடக்கம் செய்யப்படுகிறது", - "server_stats": "சேவையக புள்ளிவிவரங்கள்", - "server_update_available": "சேவையக புதுப்பிப்பு கிடைக்கிறது", - "server_version": "சேவையக பதிப்பு", - "set": "கணம்", - "set_as_album_cover": "ஆல்பம் அட்டையாக அமைக்கவும்", - "set_as_featured_photo": "சிறப்பு புகைப்படமாக அமைக்கவும்", - "set_as_profile_picture": "சுயவிவரப் படமாக அமைக்கவும்", - "set_date_of_birth": "பிறந்த தேதியை அமைக்கவும்", - "set_profile_picture": "சுயவிவரப் படத்தை அமைக்கவும்", - "set_slideshow_to_fullscreen": "ச்லைடுசோவை முழுமைக்கு அமைக்கவும்", - "set_stack_primary_asset": "முதன்மை சொத்தாக அமைக்கவும்", - "setting_image_viewer_help": "விவரம் பார்வையாளர் முதலில் சிறிய சிறு உருவத்தை ஏற்றுகிறார், பின்னர் நடுத்தர அளவிலான முன்னோட்டத்தை ஏற்றுகிறார் (இயக்கப்பட்டால்), இறுதியாக அசலை ஏற்றுகிறது (இயக்கப்பட்டால்).", - "setting_image_viewer_original_subtitle": "அசல் முழு தெளிவுத்திறன் படத்தை ஏற்றவும் (பெரியது!). தரவு பயன்பாட்டைக் குறைக்க முடக்கு (பிணையம் மற்றும் சாதன தற்காலிக சேமிப்பு இரண்டும்).", - "setting_image_viewer_original_title": "அசல் படத்தை ஏற்றவும்", - "setting_image_viewer_preview_subtitle": "நடுத்தர-தெளிவுத்திறன் படத்தை ஏற்றவும். அசலை நேரடியாக ஏற்ற முடக்கவும் அல்லது சிறுபடத்தை மட்டுமே பயன்படுத்தவும்.", - "setting_image_viewer_preview_title": "முன்னோட்டம் படத்தை ஏற்றவும்", - "setting_image_viewer_title": "படங்கள்", - "setting_languages_apply": "இடு", - "setting_languages_subtitle": "பயன்பாட்டின் மொழியை மாற்றவும்", - "setting_notifications_notify_failures_grace_period": "பின்னணி காப்புப்பிரதி தோல்விகளை அறிவிக்கவும்: {duration}", - "setting_notifications_notify_hours": "{count} மணிநேரம்", - "setting_notifications_notify_immediately": "உடனடியாக", - "setting_notifications_notify_minutes": "{count} நிமிடங்கள்", - "setting_notifications_notify_never": "ஒருபோதும்", - "setting_notifications_notify_seconds": "{count} விநாடிகள்", - "setting_notifications_single_progress_subtitle": "விரிவான பதிவேற்றும் முன்னேற்ற செய்தி ஒரு சொத்துக்கு", - "setting_notifications_single_progress_title": "பின்னணி காப்புப்பிரதி விவரம் முன்னேற்றத்தைக் காட்டு", - "setting_notifications_subtitle": "உங்கள் அறிவிப்பு விருப்பங்களை சரிசெய்யவும்", - "setting_notifications_total_progress_subtitle": "ஒட்டுமொத்த பதிவேற்ற முன்னேற்றம் (முடிந்தது/மொத்த சொத்துக்கள்)", - "setting_notifications_total_progress_title": "பின்னணி காப்புப்பிரதி மொத்த முன்னேற்றத்தைக் காட்டு", - "setting_video_viewer_auto_play_subtitle": "வீடியோக்கள் திறக்கப்பட்டவுடன் தானாகவே இயங்கத் தொடங்கும்", - "setting_video_viewer_auto_play_title": "வீடியோக்களை தானாக இயக்கவும்", - "setting_video_viewer_looping_title": "லூப்பிங்", - "setting_video_viewer_original_video_subtitle": "சேவையகத்திலிருந்து ஒரு வீடியோவை ச்ட்ரீமிங் செய்யும் போது, ஒரு டிரான்ச்கோடு கிடைக்கும்போது கூட அசலை இயக்கவும். இடையகத்திற்கு வழிவகுக்கும். இந்த அமைப்பைப் பொருட்படுத்தாமல் உள்நாட்டில் கிடைக்கும் வீடியோக்கள் அசல் தரத்தில் இயக்கப்படுகின்றன.", - "setting_video_viewer_original_video_title": "அசல் வீடியோவை கட்டாயப்படுத்துங்கள்", - "settings": "அமைப்புகள்", - "settings_require_restart": "இந்த அமைப்பைப் பயன்படுத்த இம்மியை மறுதொடக்கம் செய்யுங்கள்", - "settings_saved": "அமைப்புகள் சேமிக்கப்பட்டன", - "setup_pin_code": "முள் குறியீட்டை அமைக்கவும்", - "share": "பங்கு", - "share_action_prompt": "பகிரப்பட்ட {count} சொத்துக்கள்", - "share_add_photos": "புகைப்படங்களைச் சேர்க்கவும்", - "share_assets_selected": "{count} தேர்ந்தெடுக்கப்பட்டது", - "share_dialog_preparing": "தயாரித்தல் ...", - "share_link": "இணைப்பைப் பகிரவும்", - "shared": "பகிரப்பட்டது", - "shared_album_activities_input_disable": "கருத்து முடக்கப்பட்டுள்ளது", - "shared_album_activity_remove_content": "இந்தச் செயல்பாட்டை நீக்க விரும்புகிறீர்களா?", - "shared_album_activity_remove_title": "செயல்பாட்டை நீக்கு", - "shared_album_section_people_action_error": "ஆல்பத்திலிருந்து வெளியேறுதல்/நீக்குதல்", - "shared_album_section_people_action_leave": "ஆல்பத்திலிருந்து பயனரை அகற்று", - "shared_album_section_people_action_remove_user": "ஆல்பத்திலிருந்து பயனரை அகற்று", - "shared_album_section_people_title": "மக்கள்", - "shared_by": "பகிரப்பட்டது", - "shared_by_user": "{user} ஆல் பகிரப்பட்டது", - "shared_by_you": "நீங்கள் பகிர்ந்து கொண்டார்", - "shared_from_partner": "{partner} இலிருந்து புகைப்படங்கள்", - "shared_intent_upload_button_progress_text": "{current} / {total} பதிவேற்றப்பட்டது", - "shared_link_app_bar_title": "பகிரப்பட்ட இணைப்புகள்", - "shared_link_clipboard_copied_massage": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்டது", - "shared_link_clipboard_text": "இணைப்பு: {link} \nகடவுச்சொல்: {password}", - "shared_link_create_error": "பகிரப்பட்ட இணைப்பை உருவாக்கும் போது பிழை", - "shared_link_custom_url_description": "தனிப்பயன் முகவரி உடன் இந்த பகிரப்பட்ட இணைப்பை அணுகவும்", - "shared_link_edit_description_hint": "பங்கு விளக்கத்தை உள்ளிடவும்", - "shared_link_edit_expire_after_option_day": "1 நாள்", - "shared_link_edit_expire_after_option_days": "{count} நாட்கள்", - "shared_link_edit_expire_after_option_hour": "1 மணி நேரம்", - "shared_link_edit_expire_after_option_hours": "{count} மணிநேரம்", - "shared_link_edit_expire_after_option_minute": "1 மணித்துளி", - "shared_link_edit_expire_after_option_minutes": "{count} நிமிடங்கள்", - "shared_link_edit_expire_after_option_months": "{count} மாதங்கள்", - "shared_link_edit_expire_after_option_year": "{count} ஆண்டு", - "shared_link_edit_password_hint": "பகிர்வு கடவுச்சொல்லை உள்ளிடவும்", - "shared_link_edit_submit_button": "இணைப்பைப் புதுப்பிக்கவும்", - "shared_link_error_server_url_fetch": "சேவையக முகவரி ஐப் பெற முடியாது", - "shared_link_expires_day": "{count} நாள் காலாவதியாகிறது", - "shared_link_expires_days": "{count} நாட்களில் காலாவதியாகிறது", - "shared_link_expires_hour": "{count} மணிநேரத்தில் காலாவதியாகிறது", - "shared_link_expires_hours": "{count} மணிநேரத்தில் காலாவதியாகிறது", - "shared_link_expires_minute": "{count} நிமிடத்தில் காலாவதியாகிறது", - "shared_link_expires_minutes": "{count} நிமிடங்களில் காலாவதியாகிறது", - "shared_link_expires_never": "காலாவதியாகிறது", - "shared_link_expires_second": "{count} இரண்டாவதாக காலாவதியாகிறது", - "shared_link_expires_seconds": "{count} விநாடிகளில் காலாவதியாகிறது", - "shared_link_individual_shared": "தனிநபர் பகிரப்பட்டவர்", - "shared_link_info_chip_metadata": "Exif", - "shared_link_manage_links": "பகிரப்பட்ட இணைப்புகளை நிர்வகிக்கவும்", - "shared_link_options": "பகிரப்பட்ட இணைப்பு விருப்பங்கள்", - "shared_link_password_description": "இந்த பகிரப்பட்ட இணைப்பை அணுக கடவுச்சொல் தேவை", - "shared_links": "பகிரப்பட்ட இணைப்புகள்", - "shared_links_description": "புகைப்படங்கள் மற்றும் வீடியோக்களை இணைப்புடன் பகிரவும்", - "shared_photos_and_videos_count": "{assetCount, plural, other {# பகிரப்பட்ட புகைப்படங்கள் & காணொளிகள்.}}", - "shared_with_me": "என்னுடன் பகிரப்பட்டது", - "shared_with_partner": "{partner} உடன் பகிரப்பட்டது", - "sharing": "பகிர்வு", - "sharing_enter_password": "இந்த பக்கத்தைக் காண கடவுச்சொல்லை உள்ளிடவும்.", - "sharing_page_album": "பகிரப்பட்ட ஆல்பங்கள்", - "sharing_page_description": "உங்கள் நெட்வொர்க்கில் உள்ளவர்களுடன் புகைப்படங்களையும் வீடியோக்களையும் பகிர்ந்து கொள்ள பகிரப்பட்ட ஆல்பங்களை உருவாக்கவும்.", - "sharing_page_empty_list": "வெற்று பட்டியல்", - "sharing_sidebar_description": "பக்கப்பட்டியில் பகிர்வதற்கான இணைப்பைக் காண்பி", - "sharing_silver_appbar_create_shared_album": "புதிய பகிரப்பட்ட ஆல்பம்", - "sharing_silver_appbar_share_partner": "கூட்டாளருடன் பகிர்ந்து கொள்ளுங்கள்", - "shift_to_permanent_delete": "சொத்தை நிரந்தரமாக நீக்க ⇧ ஐ அழுத்தவும்", - "show_album_options": "ஆல்பம் விருப்பங்களைக் காட்டு", - "show_albums": "ஆல்பங்களைக் காட்டு", - "show_all_people": "எல்லா மக்களையும் காட்டு", - "show_and_hide_people": "மக்களைக் காட்டு மற்றும் மறைக்க", - "show_file_location": "கோப்பு இருப்பிடத்தைக் காட்டு", - "show_gallery": "கேலரியைக் காட்டு", - "show_hidden_people": "மறைக்கப்பட்ட நபர்களைக் காட்டு", - "show_in_timeline": "காலவரிசையில் காட்டு", - "show_in_timeline_setting_description": "உங்கள் காலவரிசையில் இந்த பயனரின் புகைப்படங்கள் மற்றும் வீடியோக்களைக் காட்டு", - "show_keyboard_shortcuts": "விசைப்பலகை குறுக்குவழிகளைக் காட்டு", - "show_metadata": "மெட்டாடேட்டாவைக் காட்டு", - "show_or_hide_info": "தகவலைக் காட்டு அல்லது மறைக்கவும்", - "show_password": "கடவுச்சொல்லைக் காட்டு", - "show_person_options": "நபர் விருப்பங்களைக் காட்டு", - "show_progress_bar": "முன்னேற்றப் பட்டியைக் காட்டு", - "show_search_options": "தேடல் விருப்பங்களைக் காட்டு", - "show_shared_links": "பகிரப்பட்ட இணைப்புகளைக் காட்டு", - "show_slideshow_transition": "ச்லைடுசோ மாற்றத்தைக் காட்டு", - "show_supporter_badge": "ஆதரவாளர் ஒட்டு", - "show_supporter_badge_description": "ஒரு ஆதரவாளர் பேட்சைக் காட்டு", - "show_text_recognition": "உரை அங்கீகாரத்தைக் காட்டு", - "show_text_search_menu": "உரை தேடல் மெனுவைக் காட்டு", - "shuffle": "கலக்கு", - "sidebar": "பக்கப்பட்டி", - "sidebar_display_description": "பக்கப்பட்டியில் பார்வைக்கு ஒரு இணைப்பைக் காண்பி", - "sign_out": "விடுபதிகை", - "sign_up": "பதிவு செய்க", - "size": "அளவு", - "skip_to_content": "உள்ளடக்கத்தைத் தவிர்க்கவும்", - "skip_to_folders": "கோப்புறைகளுக்குச் செல்லுங்கள்", - "skip_to_tags": "குறிச்சொற்களைத் தவிர்க்கவும்", - "slideshow": "ச்லைடுசோ", - "slideshow_settings": "ச்லைடுசோ அமைப்புகள்", - "sort_albums_by": "ஆல்பங்களை வரிசைப்படுத்துங்கள் ...", - "sort_created": "தேதி உருவாக்கப்பட்டது", - "sort_items": "பொருட்களின் எண்ணிக்கை", - "sort_modified": "தேதி மாற்றியமைக்கப்பட்டது", - "sort_newest": "புதிய புகைப்படம்", - "sort_oldest": "பழமையான புகைப்படம்", - "sort_people_by_similarity": "ஒற்றுமையால் மக்களை வரிசைப்படுத்துங்கள்", - "sort_recent": "மிக அண்மைக் கால புகைப்படம்", - "sort_title": "தலைப்பு", - "source": "மூலம்", - "stack": "அடுக்கு", - "stack_action_prompt": "{count} அடுக்கி வைக்கப்பட்டுள்ளது", - "stack_duplicates": "அடுக்கு நகல்கள்", - "stack_select_one_photo": "அடுக்குக்கு ஒரு முக்கிய புகைப்படத்தைத் தேர்ந்தெடுக்கவும்", - "stack_selected_photos": "தேர்ந்தெடுக்கப்பட்ட புகைப்படங்களை அடுக்கி வைக்கவும்", - "stacked_assets_count": "அடுக்கப்பட்ட {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", - "stacktrace": "ச்டாக் ட்ரேச்", - "start": "தொடங்கு", - "start_date": "தொடக்க தேதி", - "start_date_before_end_date": "தொடக்க தேதி இறுதி தேதிக்கு முன் இருக்க வேண்டும்", - "state": "மாநிலம்", - "status": "நிலை", - "stop_casting": "வார்ப்பதை நிறுத்துங்கள்", - "stop_motion_photo": "இயக்க புகைப்படத்தை நிறுத்து", - "stop_photo_sharing": "உங்கள் புகைப்படங்களைப் பகிர்வதை நிறுத்தவா?", - "stop_photo_sharing_description": "{partner} இனி உங்கள் படங்களை அணுக முடியாது.", - "stop_sharing_photos_with_user": "இந்த பயனருடன் உங்கள் புகைப்படங்களைப் பகிர்வதை நிறுத்துங்கள்", - "storage": "சேமிப்பக இடம்", - "storage_label": "சேமிப்பக சிட்டை", - "storage_quota": "சேமிப்பக ஒதுக்கீடு", - "storage_usage": "{available} இல் {used} பயன்படுத்தப்பட்டது", - "submit": "சமர்ப்பிக்கவும்", - "success": "செய்", - "suggestions": "பரிந்துரைகள்", - "sunrise_on_the_beach": "கடற்கரையில் சூரிய தோன்றுகை", - "support": "உதவி", - "support_and_feedback": "உதவி மற்றும் கருத்து", - "support_third_party_description": "உங்கள் இம்மிச் நிறுவல் மூன்றாம் தரப்பினரால் தொகுக்கப்பட்டது. நீங்கள் அனுபவிக்கும் சிக்கல்கள் அந்த தொகுப்பால் ஏற்படலாம், எனவே கீழேயுள்ள இணைப்புகளைப் பயன்படுத்தி முதல் சந்தர்ப்பத்தில் அவர்களுடன் சிக்கல்களை எழுப்புங்கள்.", - "swap_merge_direction": "ஒன்றிணைக்கும் திசையை மாற்றவும்", - "sync": "ஒத்திசைவு", - "sync_albums": "ஆல்பங்களை ஒத்திசைக்கவும்", - "sync_albums_manual_subtitle": "பதிவேற்றிய அனைத்து வீடியோக்களையும் புகைப்படங்களையும் தேர்ந்தெடுக்கப்பட்ட காப்பு ஆல்பங்களில் ஒத்திசைக்கவும்", - "sync_local": "உள்ளக ஒத்திசைக்கவும்", - "sync_remote": "தொலைதூரத்தை ஒத்திசைக்கவும்", - "sync_status": "நிலையை ஒத்திசைக்கவும்", - "sync_status_subtitle": "ஒத்திசைவு அமைப்பை பார்வையிடவும் மற்றும் நிர்வகிக்கவும்", - "sync_upload_album_setting_subtitle": "உங்கள் புகைப்படங்களையும் வீடியோக்களையும் இம்மிச்சில் தேர்ந்தெடுக்கப்பட்ட ஆல்பங்களுக்கு உருவாக்கி பதிவேற்றவும்", - "tag": "குறிச்சொல்", - "tag_assets": "குறிச்சொல் சொத்துக்கள்", - "tag_created": "உருவாக்கப்பட்ட குறிச்சொல்: {tag}", - "tag_feature_description": "தர்க்கரீதியான குறிச்சொல் தலைப்புகளால் தொகுக்கப்பட்ட புகைப்படங்கள் மற்றும் வீடியோக்களை உலாவுதல்", - "tag_not_found_question": "குறிச்சொல்லைக் கண்டுபிடிக்க முடியவில்லையா?புதிய குறிச்சொல்லை உருவாக்கவும்.", - "tag_people": "மக்களை குறிக்கவும்", - "tag_updated": "புதுப்பிக்கப்பட்ட குறிச்சொல்: {tag}", - "tagged_assets": "குறியிடப்பட்டது {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", - "tags": "குறிச்சொற்கள்", - "tap_to_run_job": "வேலையை இயக்க தட்டவும்", - "template": "வார்ப்புரு", - "text_recognition": "உரை அங்கீகாரம்", - "theme": "கருப்பொருள்", - "theme_selection": "கருப்பொருள் தேர்வு", - "theme_selection_description": "உங்கள் உலாவியின் கணினி விருப்பத்தின் அடிப்படையில் தானாகவே கருப்பொருள் ஒளி அல்லது இருட்டாக அமைக்கவும்", - "theme_setting_asset_list_storage_indicator_title": "சொத்து ஓடுகளில் சேமிப்பக குறிகாட்டியைக் காட்டு", - "theme_setting_asset_list_tiles_per_row_title": "ஒரு வரிசையில் சொத்துக்களின் எண்ணிக்கை ({count})", - "theme_setting_colorful_interface_subtitle": "பின்னணி மேற்பரப்புகளுக்கு முதன்மை வண்ணத்தைப் பயன்படுத்துங்கள்.", - "theme_setting_colorful_interface_title": "வண்ணமயமான இடைமுகம்", - "theme_setting_image_viewer_quality_subtitle": "விவரம் பட பார்வையாளரின் தரத்தை சரிசெய்யவும்", - "theme_setting_image_viewer_quality_title": "பட பார்வையாளர் தகுதி", - "theme_setting_primary_color_subtitle": "முதன்மை செயல்கள் மற்றும் உச்சரிப்புகளுக்கு ஒரு வண்ணத்தைத் தேர்ந்தெடுங்கள்.", - "theme_setting_primary_color_title": "முதன்மை நிறம்", - "theme_setting_system_primary_color_title": "கணினி நிறத்தைப் பயன்படுத்துங்கள்", - "theme_setting_system_theme_switch": "தானியங்கி (கணினி அமைப்பைப் பின்பற்றவும்)", - "theme_setting_theme_subtitle": "பயன்பாட்டின் கருப்பொருள் அமைப்பைத் தேர்வுசெய்க", - "theme_setting_three_stage_loading_subtitle": "மூன்று-நிலை ஏற்றுதல் இயக்கினால் ஏற்றுதல் செயல்திறனை அதிகரிக்கக்கூடும், ஆனால் கணிசமாக மிகை பிணையச் சுமையை ஏற்படுத்துகிறது", - "theme_setting_three_stage_loading_title": "மூன்று-நிலை ஏற்றுதலை இயக்கவும்", - "they_will_be_merged_together": "அவர்கள் ஒன்றாக இணைக்கப்படுவார்கள்", - "third_party_resources": "மூன்றாம் தரப்பு வளங்கள்", - "time": "நேரம்", - "time_based_memories": "நேர அடிப்படையிலான நினைவுகள்", - "time_based_memories_duration": "ஒவ்வொரு படத்தையும் காண்பிக்க தேவைப்படும் வினாடிகள்.", - "timeline": "காலவரிசை", - "timezone": "நேர மண்டலம்", - "to_archive": "காப்பகம்", - "to_change_password": "கடவுச்சொல்லை மாற்றவும்", - "to_favorite": "பிடித்த", - "to_login": "புகுபதிவு", - "to_multi_select": "பல-தேர்ந்தெடுக்கப்பட்ட", - "to_parent": "பெற்றோரிடம் செல்லுங்கள்", - "to_select": "தேர்ந்தெடுக்க", - "to_trash": "குப்பை", - "toggle_settings": "அமைப்புகளை மாற்றவும்", - "toggle_theme_description": "கருப்பொருள் மாற்று", - "total": "மொத்தம்", - "total_usage": "மொத்த பயன்பாடு", - "trash": "குப்பை", - "trash_action_prompt": "{count} குப்பைக்கு நகர்த்தப்பட்டது", - "trash_all": "அனைத்தையும் குப்பைக்கு நீக்கு", - "trash_count": "குப்பை {count, number}", - "trash_delete_asset": "குப்பை/நீக்கு சொத்தை", - "trash_emptied": "காலியாக குப்பை", - "trash_no_results_message": "குப்பைத் தொட்டிகள் மற்றும் வீடியோக்கள் இங்கே காண்பிக்கப்படும்.", - "trash_page_delete_all": "அனைத்தையும் நீக்கு", - "trash_page_empty_trash_dialog_content": "உங்கள் குப்பை சொத்துக்களை வெறுமை செய்ய விரும்புகிறீர்களா? இந்த உருப்படிகள் இம்மிச்சிலிருந்து நிரந்தரமாக அகற்றப்படும்", - "trash_page_info": "குப்பைத் தொட்டிகள் {days} நாட்களுக்குப் பிறகு நிரந்தரமாக நீக்கப்படும்", - "trash_page_no_assets": "குப்பை சொத்துக்கள் இல்லை", - "trash_page_restore_all": "அனைத்தையும் மீட்டெடுக்கவும்", - "trash_page_select_assets_btn": "சொத்துக்களைத் தேர்ந்தெடுக்கவும்", - "trash_page_title": "({count})", - "trashed_items_will_be_permanently_deleted_after": "குப்பையில் உள்ள உருப்படிகள் {days, plural, one {# நாளுக்கு} other {# நாட்களுக்கு}}பிறகு நிரந்தரமாக நீக்கப்படும்.", - "troubleshoot": "சரிசெய்தல்", - "type": "வகை", - "unable_to_change_pin_code": "முள் குறியீட்டை மாற்ற முடியவில்லை", - "unable_to_check_version": "ஆப்ச் அல்லது சர்வர் பதிப்பைச் சரிபார்க்க முடியவில்லை", - "unable_to_setup_pin_code": "முள் குறியீட்டை அமைக்க முடியவில்லை", - "unarchive": "அன்கான்", - "unarchive_action_prompt": "காப்பகத்திலிருந்து {count} அகற்றப்பட்டது", - "unarchived_count": "{count, plural, other {காப்பகப்படுத்தப்படவில்லை #}}", - "undo": "செயல்தவிர்", - "unfavorite": "மாறாத", - "unfavorite_action_prompt": "பிடித்தவையிலிருந்து {count} அகற்றப்பட்டது", - "unhide_person": "அருவருப்பான நபர்", - "unknown": "தெரியவில்லை", - "unknown_country": "தெரியாத நாடு", - "unknown_year": "தெரியாத ஆண்டு", - "unlimited": "வரம்பற்றது", - "unlink_motion_video": "இயக்க வீடியோவை இணைக்கவும்", - "unlink_oauth": "OAUTH ஐ இணைக்கவும்", - "unlinked_oauth_account": "இணைக்கப்படாத OAUTH கணக்கு", - "unmute_memories": "ஊடுருவல் நினைவுகள்", - "unnamed_album": "பெயரிடப்படாத ஆல்பம்", - "unnamed_album_delete_confirmation": "இந்த ஆல்பத்தை நீக்க விரும்புகிறீர்களா?", - "unnamed_share": "பெயரிடப்படாத பங்கு", - "unsaved_change": "சேமிக்கப்படாத மாற்றம்", - "unselect_all": "அனைத்தையும் தேர்வு செய்யுங்கள்", - "unselect_all_duplicates": "அனைத்து நகல்களையும் தேர்ந்தெடுக்கவும்", - "unselect_all_in": "{group}", - "unstack": "அன்-ச்டாக்", - "unstack_action_prompt": "{count} தடையின்றி", - "unstacked_assets_count": "அடுக்கப்படாத {count, plural, one {# சொத்து} other {# சொத்துக்கள்}}", - "untagged": "அவிழ்க்கப்படாதது", - "up_next": "அடுத்து", - "update_location_action_prompt": "{count} தேர்ந்தெடுக்கப்பட்ட சொத்துக்களின் இருப்பிடத்தைப் புதுப்பிக்கவும்:", - "updated_at": "புதுப்பிக்கப்பட்டது", - "updated_password": "புதுப்பிக்கப்பட்ட கடவுச்சொல்", - "upload": "பதிவேற்றும்", - "upload_concurrency": "ஒத்திசைவைப் பதிவேற்றவும்", - "upload_details": "விவரங்களை பதிவேற்றவும்", - "upload_dialog_info": "தேர்ந்தெடுக்கப்பட்ட சொத்து (களை) சேவையகத்திற்கு காப்புப் பிரதி எடுக்க விரும்புகிறீர்களா?", - "upload_dialog_title": "சொத்தை பதிவேற்றவும்", - "upload_errors": "{count, plural, one {# பிழை} other {# பிழைகள்}}மூலம் பதிவேற்றம் முடிந்தது, புதிய பதிவேற்ற சொத்துகளைப் பார்க்கப் பக்கத்தைப் புதுப்பிக்கவும்.", - "upload_finished": "பதிவேற்றம் முடிந்தது", - "upload_progress": "மீதமுள்ள {remaining, number} - செயலாக்கப்பட்டது {processed, number}/{total, number}", - "upload_skipped_duplicates": "தவிர்க்கப்பட்டது {count, plural, one {# நகல் சொத்து} other {# நகல் சொத்துக்கள்}}", - "upload_status_duplicates": "நகல்கள்", - "upload_status_errors": "பிழைகள்", - "upload_status_uploaded": "பதிவேற்றப்பட்டது", - "upload_success": "வெற்றியைப் பதிவேற்றவும், புதிய பதிவேற்ற சொத்துக்களைக் காண பக்கத்தைப் புதுப்பிக்கவும்.", - "upload_to_immich": "இம்மிச்சிற்கு பதிவேற்று ({count})", - "uploading": "பதிவேற்றுகிறது", - "uploading_media": "மீடியாவைப் பதிவேற்றுகிறது", - "url": "முகவரி", - "usage": "பயன்பாடு", - "use_biometric": "பயோமெட்ரிக்கைப் பயன்படுத்தவும்", - "use_current_connection": "தற்போதைய இணைப்பைப் பயன்படுத்தவும்", - "use_custom_date_range": "அதற்கு பதிலாக தனிப்பயன் தேதி வரம்பைப் பயன்படுத்தவும்", - "user": "பயனர்", - "user_has_been_deleted": "இந்தப் பயனர் நீக்கப்பட்டார்.", - "user_id": "பயனர் ஐடி", - "user_liked": "{user} விரும்பினார் {type, select, photo {இந்தப் புகைப்படம்} video {இந்தக் காணொளி} asset {இந்தச் சொத்து} other {it}}", - "user_pin_code_settings": "பின் குறியீடு", - "user_pin_code_settings_description": "உங்கள் பின் குறியீட்டை நிர்வகிக்கவும்", - "user_privacy": "பயனர் தனியுரிமை", - "user_purchase_settings": "வாங்க", - "user_purchase_settings_description": "உங்கள் வாங்குதலை நிர்வகிக்கவும்", - "user_role_set": "{user}ஐ {role} ஆக அமை", - "user_usage_detail": "பயனர் பயன்பாட்டு விவரம்", - "user_usage_stats": "கணக்கு பயன்பாட்டு புள்ளிவிவரங்கள்", - "user_usage_stats_description": "கணக்கு உபயோகப் புள்ளிவிவரங்களைப் பார்க்க", - "username": "பயனர்பெயர்", - "users": "பயனர்கள்", - "users_added_to_album_count": "செருகேட்டில் {count, plural, one {# பயனர்} other {# பயனர்கள்}} சேர்க்கப்பட்டார்", - "utilities": "பயன்பாடுகள்", - "validate": "சரிபார்க்கவும்", - "validate_endpoint_error": "தயவுசெய்து ஒரு செல்லுபடியாகும் URL ஐ உள்ளிடவும்", - "variables": "மாறிகள்", - "version": "பதிப்பு", - "version_announcement_closing": "உங்கள் நண்பர், அலெக்ச்", - "version_announcement_message": "வணக்கம்! இம்மியின் புதிய பதிப்பு கிடைக்கிறது. எந்தவொரு தவறான கருத்துக்களையும் தடுக்க உங்கள் அமைப்பு புதுப்பித்த நிலையில் இருப்பதை உறுதிசெய்ய வெளியீட்டுக் குறிப்புகள் ஐப் படிக்க சிறிது நேரம் ஒதுக்குங்கள், குறிப்பாக நீங்கள் காவற்கோபுரத்தைப் பயன்படுத்தினால் அல்லது உங்கள் இம்மிச் நிகழ்வை தானாகவே புதுப்பிப்பதைக் கையாளும் எந்தவொரு பொறிமுறையையும் பயன்படுத்தினால்.", - "version_history": "பதிப்பு வரலாறு", - "version_history_item": "{version} இல் {date} நிறுவப்பட்டது", - "video": "ஒளிதோற்றம்", - "video_hover_setting": "ஓவரில் வீடியோ சிறு உருவத்தை இயக்கவும்", - "video_hover_setting_description": "மவுச் உருப்படியைக் கொண்டு செல்லும்போது வீடியோ சிறு உருவத்தை இயக்கவும். முடக்கப்பட்டாலும் கூட, பிளே ஐகானுக்கு மேல் சுற்றுவதன் மூலம் பிளேபேக்கைத் தொடங்கலாம்.", - "videos": "வீடியோக்கள்", - "videos_count": "{count, plural, one {# காணொளி} other {# காணொளிகள்}}", - "view": "பார்வை", - "view_album": "ஆல்பத்தைக் காண்க", - "view_all": "அனைத்தையும் காண்க", - "view_all_users": "அனைத்து பயனர்களையும் காண்க", - "view_asset_owners": "சொத்து உரிமையாளர்களைக் காண்க", - "view_details": "விவரங்களைப் பார்", - "view_in_timeline": "காலவரிசையில் காண்க", - "view_link": "இணைப்பைக் காண்க", - "view_links": "இணைப்புகளைக் காண்க", - "view_name": "பார்வை", - "view_next_asset": "அடுத்த சொத்தை காண்க", - "view_previous_asset": "முந்தைய சொத்தைப் பார்க்கவும்", - "view_qr_code": "QR குறியீட்டைக் காட்டு", - "view_similar_photos": "இதே போன்ற புகைப்படங்களைக் காட்டு", - "view_stack": "காண்க அடுக்கு", - "view_user": "பயனரைப் பார்க்கவும்", - "viewer_remove_from_stack": "அடுக்கிலிருந்து அகற்று", - "viewer_stack_use_as_main_asset": "பிரதான சொத்தாகப் பயன்படுத்தவும்", - "viewer_unstack": "அடுக்கை நீக்கு", - "visibility_changed": "{count, plural, one {# நபர்} other {# நபர்கள்}} க்கான தெரிவுநிலை மாற்றப்பட்டது", - "waiting": "காத்திருக்கிறது", - "warning": "எச்சரிக்கை", - "week": "வாரம்", - "welcome": "வரவேற்கிறோம்", - "welcome_to_immich": "இம்மிச்சிற்கு வருக", - "wifi_name": "வைஃபை பெயர்", - "wrong_pin_code": "தவறான பின் குறியீடு", - "year": "ஆண்டு", - "years_ago": "{years, plural, one {# ஆண்டு} other {# ஆண்டுகள்}} முன்பு", - "yes": "ஆம்", - "you_dont_have_any_shared_links": "உங்களிடம் பகிரப்பட்ட இணைப்புகள் எதுவும் இல்லை", - "your_wifi_name": "உங்கள் வைஃபை பெயர்", - "zoom_image": "பெரிதாக்க படம்", - "zoom_to_bounds": "எல்லைக்கு பெரிதாக்கு" -} +{} diff --git a/i18n/te.json b/i18n/te.json index d9d24bb3c6..0967ef424b 100644 --- a/i18n/te.json +++ b/i18n/te.json @@ -1,1178 +1 @@ -{ - "about": "గురించి", - "account": "ఖాతా", - "account_settings": "ఖాతా సెట్టింగ్‌లు", - "acknowledge": "గుర్తించండి", - "action": "చర్య", - "action_common_update": "నవీకరించు", - "actions": "చర్యలు", - "active": "చురుకుగా", - "activity": "కార్యాచరణ", - "activity_changed": "కార్యకలాపం {enabled, select, true {enabled} other {disabled}}", - "add": "జోడించు", - "add_a_description": "వివరణ జోడించండి", - "add_a_location": "స్థానాన్ని జోడించండి", - "add_a_name": "పేరును జోడించండి", - "add_a_title": "శీర్షికను జోడించండి", - "add_birthday": "పుట్టినరోజును జోడించండి", - "add_endpoint": "ముగింపు బిందువును జోడించండి", - "add_exclusion_pattern": "మినహాయింపు నమూనాను జోడించండి", - "add_location": "స్థానాన్ని జోడించండి", - "add_more_users": "మరింత మంది వినియోగదారులను జోడించండి", - "add_partner": "భాగస్వామిని జోడించండి", - "add_path": "మార్గాన్ని జోడించండి", - "add_photos": "ఫోటోలను జోడించండి", - "add_tag": "ట్యాగ్ జోడించండి", - "add_to": "జోడించండి…", - "add_to_album": "ఆల్బమ్‌కు జోడించండి", - "add_to_album_bottom_sheet_added": "{album} లోకి చేర్చబడింది", - "add_to_album_bottom_sheet_already_exists": "ఇప్పటికే {album} లో ఉంది", - "add_to_album_bottom_sheet_some_local_assets": "కొన్ని స్థానిక ఆస్తులను ఆల్బమ్‌కు జోడించడం సాధ్యం కాలేదు.", - "add_to_album_toggle": "{album} కోసం ఎంపిక టాగుల్ చేయండి", - "add_to_albums": "ఆల్బమ్స్‌లో చేర్చు", - "add_to_albums_count": "ఆల్బమ్స్‌లో చేర్చు ({count})", - "add_to_bottom_bar": "చేర్చు", - "add_to_shared_album": "భాగస్వామ్య ఆల్బమ్‌కు జోడించండి", - "add_upload_to_stack": "అప్లోడ్‌ను స్టాక్‌లో చేర్చు", - "add_url": "URLని జోడించండి", - "added_to_archive": "ఆర్కైవ్‌కి జోడించబడింది", - "added_to_favorites": "ఇష్టమైన వాటికి జోడించబడింది", - "added_to_favorites_count": "ఇష్టమైన వాటికి {count, number} జోడించబడింది", - "admin": { - "add_exclusion_pattern_description": "మినహాయింపు నమూనాలను జోడించండి. *, ** మరియు ?ని ఉపయోగించి గ్లోబింగ్‌కు మద్దతు ఉంది. \"Raw\" అనే పేరు గల ఏదైనా డైరెక్టరీలోని అన్ని ఫైల్‌లను విస్మరించడానికి, \"**/Raw/**\"ని ఉపయోగించండి. \".tif\"తో ముగిసే అన్ని ఫైల్‌లను విస్మరించడానికి, \"**/*.tif\"ని ఉపయోగించండి. సంపూర్ణ మార్గాన్ని విస్మరించడానికి, \"/path/to/ignore/**\"ని ఉపయోగించండి.", - "admin_user": "నిర్వాహకుడు", - "asset_offline_description": "ఈ బాహ్య లైబ్రరీ ఫైల్ ఇకపై డిస్క్‌లో కనుగొనబడలేదు మరియు ట్రాష్‌కు తరలించబడింది. ఫైల్ లైబ్రరీలోకి తరలించబడితే, కొత్త సంబంధిత ఫైల్ కోసం మీ టైమ్‌లైన్‌ను తనిఖీ చేయండి. ఈ ఫైల్ని పునరుద్ధరించడానికి, దయచేసి దిగువన ఉన్న ఫైల్ పాత్‌ను Immich యాక్సెస్ చేయగలదని నిర్ధారించుకోండి మరియు లైబ్రరీని స్కాన్ చేయండి.", - "authentication_settings": "ప్రమాణీకరణ సెట్టింగ్‌లు", - "authentication_settings_description": "పాస్‌వర్డ్, OAuth మరియు ఇతర ప్రమాణీకరణ సెట్టింగ్‌లను నిర్వహించండి", - "authentication_settings_disable_all": "మీరు ఖచ్చితంగా అన్ని లాగిన్ పద్ధతులను నిలిపివేయాలనుకుంటున్నారా? లాగిన్ పూర్తిగా నిలిపివేయబడుతుంది.", - "authentication_settings_reenable": "మళ్లీ ప్రారంబించటానికి, Server Commandని ఉపయోగించండి.", - "background_task_job": "నేపథ్య పనులు", - "backup_database": "డేటాబేస్ డంప్ సృష్టించు", - "backup_database_enable_description": "డేటాబేస్ పడవెయ్యడాన్నీ ప్రారంభించండి", - "backup_keep_last_amount": "ఉంచుకోవాల్సిన మునుపటి పడవెయ్యడాల్లా మొత్తం", - "backup_onboarding_1_description": "క్లౌడ్‌లో లేదా మరో భౌతిక ప్రదేశంలో ఆఫ్సైట్ కాపీ", - "backup_onboarding_2_description": "వేరువేరు పరికరాల్లో లోకల్ కాపీలు. ఇందులో ప్రధాన ఫైళ్ళు మరియు వాటికి సంబంధించిన లోకల్ బ్యాకప్ కూడా ఉంటాయి", - "backup_onboarding_3_description": "మీ డేటాకు మొత్తం కాపీలు, అసలు ఫైళ్ళు సహా. ఇందులో 1 ఆఫ్సైట్ కాపీ మరియు 2 లోకల్ కాపీలు ఉంటాయి.", - "backup_onboarding_description": "మీ డేటాను రక్షించడానికి 3-2-1 బ్యాకప్ వ్యూహం సిఫార్సు చేయబడింది. పూర్తి స్థాయి బ్యాకప్ పరిష్కారం కోసం మీరు అప్లోడ్ చేసిన ఫోటోలు/వీడియోల కాపీలు మరియు Immich డేటాబేస్ కాపీని ఉంచాలి.", - "backup_onboarding_footer": "Immich బ్యాకప్‌పై మరిన్ని వివరాలకు డాక్యుమెంటేషన్ చూడండి.", - "backup_onboarding_parts_title": "3-2-1 బ్యాకప్‌లో:", - "backup_onboarding_title": "బ్యాకప్లు", - "backup_settings": "డేటాబేస్ పడవెసే సెట్టింగ్‌లు", - "backup_settings_description": "డేటాబేస్ పడవెసే సెట్టింగ్‌లను నిర్వహించండి", - "cleared_jobs": "దీని కోసం ఉద్యోగాలు క్లియర్ చేయబడ్డాయి: {job}", - "config_set_by_file": "కాన్ఫిగరేషన్ ప్రస్తుతం కాన్ఫిగరేషన్ ఫైల్ ద్వారా సెట్ చేయబడింది", - "confirm_delete_library": "మీరు ఖచ్చితంగా {library} లైబ్రరీని తొలగించాలనుకుంటున్నారా?", - "confirm_delete_library_assets": "మీరు ఖచ్చితంగా ఈ లైబ్రరీని తొలగించాలనుకుంటున్నారా? ఇది Immich నుండి {count, plural, one {# కలిగి ఉన్న ఆస్తి} other {all # కలిగి ఉన్న ఆస్తులు}} తొలగిస్తుంది మరియు రద్దు చేయబడదు. ఫైల్‌లు డిస్క్‌లో ఉంటాయి.", - "confirm_email_below": "నిర్ధారించడానికి, క్రింద \"{email}\" టైప్ చేయండి", - "confirm_reprocess_all_faces": "మీరు ఖచ్చితంగా అన్ని ముఖాలను రీప్రాసెస్ చేయాలనుకుంటున్నారా? ఇది పేరున్న వ్యక్తులను కూడా క్లియర్ చేస్తుంది.", - "confirm_user_password_reset": "మీరు ఖచ్చితంగా {user} పాస్‌వర్డ్‌ని రీసెట్ చేయాలనుకుంటున్నారా?", - "confirm_user_pin_code_reset": "మీరు ఖచ్చితంగా {user} యొక్క పిన్ కోడ్ నీ రీసెట్ చేద్దామనిఅనుకుంటున్నారా?", - "create_job": "పనిని సృష్టించండి", - "cron_expression": "క్రాన్ వ్యక్తీకరణ", - "cron_expression_description": "క్రాన్ ఫార్మాట్ ఉపయోగించి స్కానింగ్ విరామాన్ని సెట్ చేయండి. మరిన్ని వివరాల కోసం దయచేసి ఉదా. క్రోంటాబ్ గురు చూడండి", - "cron_expression_presets": "ముందేచేసిన క్రాన్ వ్యక్తీకరణలు", - "disable_login": "లాగిన్‌ను నిలిపివేయండి", - "duplicate_detection_job_description": "సారూప్య చిత్రాలను గుర్తించడానికి ఆస్తులపై యంత్ర అభ్యాసాన్ని అమలు చేయండి. స్మార్ట్ శోధనపై ఆధారపడుతుంది", - "exclusion_pattern_description": "మినహాయింపు నమూనాలు మీ లైబ్రరీని స్కాన్ చేస్తున్నప్పుడు ఫైల్‌లు మరియు ఫోల్డర్‌లను విస్మరించడానికి మిమ్మల్ని అనుమతిస్తాయి. మీరు దిగుమతి చేయకూడదనుకునే RAW ఫైల్‌లు వంటి ఫోల్డర్‌లను కలిగి ఉన్నట్లయితే ఇది ఉపయోగకరంగా ఉంటుంది.", - "face_detection": "ముఖ గమనింపు", - "face_detection_description": "మెషిన్ లెర్నింగ్ ఉపయోగించి ఆస్తులలో ముఖాలను గుర్తించండి. వీడియోల కోసం, సూక్ష్మచిత్రం మాత్రమే పరిగణించబడుతుంది. \"అన్నీ\" (పునః) అన్ని ఆస్తులను ప్రాసెస్ చేస్తుంది. ఇంకా ప్రాసెస్ చేయని ఆస్తులను \"మిస్సింగ్\" క్యూలు చేస్తుంది. గుర్తించబడిన ముఖాలు ఇప్పటికే ఉన్న లేదా కొత్త వ్యక్తులతో సమూహపరచడం పూర్తయిన తర్వాత ముఖ గుర్తింపు కోసం క్యూలో ఉంచబడతాయి.", - "facial_recognition_job_description": "సమూహం వ్యక్తుల ముఖాలను గుర్తించింది. ఫేస్ డిటెక్షన్ పూర్తయిన తర్వాత ఈ దశ అమలవుతుంది. \"అన్ని\" (పునః) అన్ని ముఖాలను క్లస్టర్‌లు చేస్తుంది. \"తప్పిపోయిన\" వ్యక్తిని కేటాయించని ముఖాలను క్యూలో ఉంచుతుంది.", - "failed_job_command": "ఉద్యోగం కోసం కమాండ్ {command} విఫలమైంది: {job}", - "force_delete_user_warning": "హెచ్చరిక: ఇది వినియోగదారుని మరియు అన్ని ఆస్తులను వెంటనే తీసివేస్తుంది. ఇది రద్దు చేయబడదు మరియు ఫైల్‌లను తిరిగి పొందడం సాధ్యం కాదు.", - "image_format": "ఫార్మాట్", - "image_format_description": "WebP JPEG కంటే చిన్న ఫైల్‌లను ఉత్పత్తి చేస్తుంది, కానీ ఎన్‌కోడ్ చేయడం నెమ్మదిగా ఉంటుంది.", - "image_fullsize_description": "జూమ్ చేసినప్పుడు ఉపయోగించే, మెటాడేటా తీసిన పూర్తి పరిమాణ చిత్రము", - "image_fullsize_enabled": "ఫుల్-సైజ్ చిత్రం తయారీని ప్రారంభించు", - "image_fullsize_enabled_description": "వెబ్‌కు అనుకూలం కాని ఫార్మాట్ల కోసం ఫుల్-సైజ్ చిత్రాన్ని సృష్టించు. \"ఎంబెడ్డెడ్ ప్రీవ్యూ ప్రిఫర్\" ప్రారంభించినప్పుడు, ఎంబెడ్డెడ్ ప్రీవ్యూలను మార్పిడిలేకుండా నేరుగా ఉపయోగిస్తారు. JPEG వంటి వెబ్-ఫ్రెండ్లీ ఫార్మాట్లపై ఇది ప్రభావం చూపదు.", - "image_prefer_embedded_preview": "పొందుపరిచిన పరిదృశ్యానికి ప్రాధాన్యత ఇవ్వండి", - "image_prefer_embedded_preview_setting_description": "అందుబాటులో ఉన్నప్పుడు మరియు ఇమేజ్ ప్రాసెసింగ్‌కు ఇన్‌పుట్‌గా RAW ఫోటోలలో ఎంబెడెడ్ ప్రివ్యూలను ఉపయోగించండి. ఇది కొన్ని చిత్రాలకు మరింత ఖచ్చితమైన రంగులను ఉత్పత్తి చేయగలదు, అయితే ప్రివ్యూ నాణ్యత కెమెరాపై ఆధారపడి ఉంటుంది మరియు చిత్రం మరిన్ని కుదింపు కళాఖండాలను కలిగి ఉండవచ్చు.", - "image_prefer_wide_gamut": "విస్తృత స్వరసప్తకానికి ప్రాధాన్యత ఇవ్వండి", - "image_prefer_wide_gamut_setting_description": "థంబ్‌నెయిల్‌ల కోసం డిస్‌ప్లే P3ని ఉపయోగించండి. ఇది విస్తృత రంగుల ఖాళీలతో చిత్రాల వైబ్రెన్స్‌ను మెరుగ్గా భద్రపరుస్తుంది, అయితే పాత బ్రౌజర్ వెర్షన్‌తో పాత పరికరాల్లో చిత్రాలు విభిన్నంగా కనిపించవచ్చు. రంగు మార్పులను నివారించడానికి sRGB చిత్రాలు sRGB వలె ఉంచబడతాయి.", - "image_preview_description": "ఒకే ఆస్తిని వీక్షించేటప్పుడు మరియు యంత్ర అభ్యాసం కోసం మెటాడేటా లేని మధ్యస్థ-పరిమాణ చిత్రం ఉపయోగించబడుతుంది", - "image_preview_quality_description": "1-100 వరకు ప్రివ్యూ నాణ్యత. ఎక్కువ ఉంటే మంచిది, కానీ పెద్ద ఫైళ్లను ఉత్పత్తి చేస్తుంది మరియు యాప్ ప్రతిస్పందనను తగ్గిస్తుంది. తక్కువ విలువను సెట్ చేయడం వల్ల మెషిన్ లెర్నింగ్ నాణ్యత ప్రభావితం కావచ్చు.", - "image_preview_title": "అమరికల ప్రివ్యూ", - "image_quality": "నాణ్యత", - "image_resolution": "రిజల్యూషన్", - "image_resolution_description": "అధిక రిజల్యూషన్‌లు మరింత వివరాలను భద్రపరచగలవు కానీ ఎన్‌కోడ్ చేయడానికి ఎక్కువ సమయం పడుతుంది, పెద్ద ఫైల్ పరిమాణాలను కలిగి ఉంటాయి మరియు యాప్ ప్రతిస్పందనను తగ్గించవచ్చు.", - "image_settings": "చిత్రం సెట్టింగ్‌లు", - "image_settings_description": "రూపొందించబడిన చిత్రాల నాణ్యత మరియు రిజల్యూషన్‌ను నిర్వహించండి", - "image_thumbnail_description": "తీసివేసిన మెటాడేటాతో కూడిన చిన్న సూక్ష్మచిత్రం, ప్రధాన టైమ్‌లైన్ వంటి ఫోటోల సమూహాలను వీక్షిస్తున్నప్పుడు ఉపయోగించబడుతుంది", - "image_thumbnail_quality_description": "థంబ్‌నెయిల్ నాణ్యత 1-100 నుండి. అధికమైనది ఉత్తమం, కానీ పెద్ద ఫైల్‌లను ఉత్పత్తి చేస్తుంది మరియు యాప్ ప్రతిస్పందనను తగ్గిస్తుంది.", - "image_thumbnail_title": "థంబ్‌నెయిల్ సెట్టింగ్‌లు", - "job_concurrency": "{job} సమ్మతి", - "job_created": "పని సృష్టించబడింది", - "job_not_concurrency_safe": "ఈ ఉద్యోగం సమ్మతి-సురక్షితమైనది కాదు.", - "job_settings": "ఉద్యోగ సెట్టింగ్‌లు", - "job_settings_description": "ఉద్యోగ సమ్మతిని నిర్వహించండి", - "jobs_delayed": "{jobCount, plural, other {# ఆలస్యమైంది}}", - "jobs_failed": "{jobCount, plural, other {# విఫలమైంది}}", - "library_created": "లైబ్రరీ సృష్టించబడింది: {library}", - "library_deleted": "లైబ్రరీ తొలగించబడింది", - "library_scanning": "ఆవర్తన స్కానింగ్", - "library_scanning_description": "ఆవర్తన లైబ్రరీ స్కానింగ్‌ని కాన్ఫిగర్ చేయండి", - "library_scanning_enable_description": "ఆవర్తన లైబ్రరీ స్కానింగ్‌ని ప్రారంభించండి", - "library_settings": "బాహ్య లైబ్రరీ", - "library_settings_description": "బాహ్య లైబ్రరీ సెట్టింగ్‌లను నిర్వహించండి", - "library_tasks_description": "కొత్త మరియు/లేదా మార్చబడిన ఆస్తుల కోసం బాహ్య లైబ్రరీలను స్కాన్ చేయండి", - "library_watching_enable_description": "ఫైల్ మార్పుల కోసం బాహ్య లైబ్రరీలను చూడండి", - "library_watching_settings": "లైబ్రరీ చూడటం (ప్రయోగాత్మకం)", - "library_watching_settings_description": "మారిన ఫైల్‌ల కోసం ఆటోమేటిక్‌గా చూడండి", - "logging_enable_description": "లాగింగ్‌ని ప్రారంభించండి", - "logging_level_description": "ప్రారంభించబడినప్పుడు, ఏ లాగ్ స్థాయిని ఉపయోగించాలి.", - "logging_settings": "లాగింగ్", - "machine_learning_clip_model": "CLIP మోడల్", - "machine_learning_clip_model_description": "ఇక్కడ జాబితా చేయబడిన CLIP మోడల్ పేరు. మీరు మోడల్‌ను మార్చిన తర్వాత అన్ని చిత్రాల కోసం 'స్మార్ట్ సెర్చ్' జాబ్‌ని మళ్లీ అమలు చేయాలని గుర్తుంచుకోండి.", - "machine_learning_duplicate_detection": "డూప్లికేట్ డిటెక్షన్", - "machine_learning_duplicate_detection_enabled": "నకిలీ గుర్తింపును ప్రారంభించండి", - "machine_learning_duplicate_detection_enabled_description": "నిలిపివేసినట్లయితే, సరిగ్గా ఒకేలాంటి ఆస్తులు ఇప్పటికీ డీ-డూప్లికేట్ చేయబడతాయి.", - "machine_learning_duplicate_detection_setting_description": "సంభావ్య నకిలీలను కనుగొనడానికి CLIP ఎంబెడ్డింగ్‌లను ఉపయోగించండి", - "machine_learning_enabled": "మెషిన్ లెర్నింగ్ ప్రారంభించండి", - "machine_learning_enabled_description": "డిజేబుల్ చేయబడితే, దిగువ సెట్టింగ్‌లతో సంబంధం లేకుండా అన్ని ML ఫీచర్‌లు నిలిపివేయబడతాయి.", - "machine_learning_facial_recognition": "ముఖ గుర్తింపు", - "machine_learning_facial_recognition_description": "చిత్రాలలో ముఖాలను కనుగొనండి, గుర్తించండి మరియు సమూహపరచండి", - "machine_learning_facial_recognition_model": "ముఖ గుర్తింపు మోడల్", - "machine_learning_facial_recognition_model_description": "నమూనాలు పరిమాణం యొక్క అవరోహణ క్రమంలో జాబితా చేయబడ్డాయి. పెద్ద మోడల్‌లు నెమ్మదిగా ఉంటాయి మరియు ఎక్కువ మెమరీని ఉపయోగిస్తాయి, కానీ మంచి ఫలితాలను ఇస్తాయి. మీరు మోడల్‌ను మార్చిన తర్వాత అన్ని చిత్రాల కోసం తప్పనిసరిగా ఫేస్ డిటెక్షన్ జాబ్‌ని మళ్లీ అమలు చేయాలని గుర్తుంచుకోండి.", - "machine_learning_facial_recognition_setting": "ముఖ గుర్తింపును ప్రారంభించండి", - "machine_learning_facial_recognition_setting_description": "నిలిపివేయబడితే, ముఖ గుర్తింపు కోసం చిత్రాలు ఎన్‌కోడ్ చేయబడవు మరియు అన్వేషణ పేజీలోని వ్యక్తుల విభాగాన్ని నింపవు.", - "machine_learning_max_detection_distance": "గరిష్ట కనుగొను దూరం", - "machine_learning_max_detection_distance_description": "రెండు చిత్రాల మధ్య గరిష్ట దూరం 0.001-0.1 వరకు నకిలీలుగా పరిగణించబడుతుంది. అధిక విలువలు మరిన్ని నకిలీలను గుర్తిస్తాయి, కానీ తప్పుడు పాజిటివ్‌లకు దారితీయవచ్చు.", - "machine_learning_max_recognition_distance": "గరిష్ట గుర్తింపు దూరం", - "machine_learning_max_recognition_distance_description": "ఒకే వ్యక్తిగా పరిగణించబడే రెండు ముఖాల మధ్య గరిష్ట దూరం 0-2 వరకు ఉంటుంది. దీన్ని తగ్గించడం ద్వారా ఇద్దరు వ్యక్తులను ఒకే వ్యక్తిగా లేబుల్ చేయడాన్ని నిరోధించవచ్చు, అయితే పెంచడం ద్వారా ఒకే వ్యక్తిని ఇద్దరు వేర్వేరు వ్యక్తులుగా పేర్కొనడాన్ని నిరోధించవచ్చు. ఒక వ్యక్తిని రెండుగా విభజించడం కంటే ఇద్దరు వ్యక్తులను విలీనం చేయడం సులభమని గుర్తుంచుకోండి, కాబట్టి సాధ్యమైనప్పుడు తక్కువ థ్రెషోల్డ్ వైపు తప్పు చేయండి.", - "machine_learning_min_detection_score": "కనిష్ట గుర్తింపు స్కోర్", - "machine_learning_min_detection_score_description": "ముఖం కోసం కనిష్ట విశ్వాస స్కోరు 0-1 నుండి గుర్తించబడుతుంది. తక్కువ విలువలు ఎక్కువ ముఖాలను గుర్తిస్తాయి కానీ తప్పుడు పాజిటివ్‌లకు దారితీయవచ్చు.", - "machine_learning_min_recognized_faces": "కనిష్టంగా గుర్తించబడిన ముఖాలు", - "machine_learning_min_recognized_faces_description": "ఒక వ్యక్తి సృష్టించడానికి గుర్తించబడిన ముఖాల కనీస సంఖ్య. దీన్ని పెంచడం వలన ఒక వ్యక్తికి ముఖం కేటాయించబడని అవకాశాన్ని పెంచే ఖర్చుతో ఫేషియల్ రికగ్నిషన్ మరింత ఖచ్చితమైనదిగా చేస్తుంది.", - "machine_learning_settings": "మెషిన్ లెర్నింగ్ సెట్టింగ్‌లు", - "machine_learning_settings_description": "మెషిన్ లెర్నింగ్ ఫీచర్‌లు మరియు సెట్టింగ్‌లను నిర్వహించండి", - "machine_learning_smart_search": "స్మార్ట్ శోధన", - "machine_learning_smart_search_description": "CLIP ఎంబెడ్డింగ్‌లను ఉపయోగించి అర్థపరంగా చిత్రాల కోసం శోధించండి", - "machine_learning_smart_search_enabled": "స్మార్ట్ శోధనను ప్రారంభించండి", - "machine_learning_smart_search_enabled_description": "నిలిపివేయబడితే, స్మార్ట్ శోధన కోసం చిత్రాలు ఎన్‌కోడ్ చేయబడవు.", - "machine_learning_url_description": "మెషిన్ లెర్నింగ్ సర్వర్ యొక్క URL. ఒకటి కంటే ఎక్కువ URLలు అందించబడితే, మొదటి నుండి చివరి వరకు క్రమంలో విజయవంతంగా ప్రతిస్పందించే వరకు ప్రతి సర్వర్ ఒక్కోసారి ప్రయత్నించబడుతుంది. ప్రతిస్పందించని సర్వర్‌లు తిరిగి ఆన్‌లైన్‌లోకి వచ్చే వరకు తాత్కాలికంగా విస్మరించబడతాయి.", - "manage_concurrency": "కరెన్సీని నిర్వహించండి", - "manage_log_settings": "లాగ్ సెట్టింగ్‌లను నిర్వహించండి", - "map_dark_style": "చీకటి శైలి", - "map_enable_description": "మ్యాప్ లక్షణాలను ప్రారంభించండి", - "map_gps_settings": "మ్యాప్ & GPS సెట్టింగ్‌లు", - "map_gps_settings_description": "మ్యాప్ & GPS (రివర్స్ జియోకోడింగ్) సెట్టింగ్‌లను నిర్వహించండి", - "map_implications": "మ్యాప్ ఫీచర్ బాహ్య టైల్ సేవపై ఆధారపడి ఉంటుంది (tiles.immich.cloud)", - "map_light_style": "పగటి శైలి", - "map_manage_reverse_geocoding_settings": "రివర్స్ జియోకోడింగ్ సెట్టింగ్‌లను నిర్వహించండి", - "map_reverse_geocoding": "రివర్స్ జియోకోడింగ్", - "map_reverse_geocoding_enable_description": "రివర్స్ జియోకోడింగ్‌ని ప్రారంభించండి", - "map_reverse_geocoding_settings": "రివర్స్ జియోకోడింగ్ సెట్టింగ్‌లు", - "map_settings": "మ్యాప్ సెట్టింగ్‌లు", - "map_settings_description": "మ్యాప్ సెట్టింగ్‌లను నిర్వహించండి", - "map_style_description": "URL నుండి style.json మ్యాప్ థీమ్‌కు", - "memory_cleanup_job": "మెమరీ శుభ్రపరిచడం", - "memory_generate_job": "మెమరీ రూపొందింపు", - "metadata_extraction_job": "మెటాడేటాను సంగ్రహించండి", - "metadata_extraction_job_description": "GPS, ముఖాలు మరియు రిజల్యూషన్ వంటి ప్రతి ఆస్తి నుండి మెటాడేటా సమాచారాన్ని సంగ్రహించండి", - "metadata_faces_import_setting": "ముఖం దిగుమతిని ప్రారంభించండి", - "metadata_faces_import_setting_description": "ఇమేజ్ EXIF డేటా మరియు సైడ్‌కార్ ఫైల్‌ల నుండి ముఖాలను దిగుమతి చేయండి", - "metadata_settings": "మెటాడేటా సెట్టింగ్‌లు", - "metadata_settings_description": "మెటాడేటా సెట్టింగ్‌లను నిర్వహించండి", - "migration_job": "వలస", - "migration_job_description": "ఆస్తులు మరియు ముఖాల కోసం సూక్ష్మచిత్రాలను తాజా ఫోల్డర్ నిర్మాణానికి తరలించండి", - "no_paths_added": "మార్గాలు జోడించబడలేదు", - "no_pattern_added": "నమూనా జోడించబడలేదు", - "note_apply_storage_label_previous_assets": "గమనిక: గతంలో అప్‌లోడ్ చేసిన ఆస్తులకు నిల్వ లేబుల్‌ను వర్తింపజేయడానికి, నడపండి", - "note_cannot_be_changed_later": "గమనిక: దీనిని తర్వాత మార్చలేము!", - "notification_email_from_address": "నుండి", - "notification_email_from_address_description": "పంపినవారి ఇమెయిల్ చిరునామా, ఉదాహరణకు: \"Immich Photo Server \"", - "notification_email_host_description": "ఇమెయిల్ సర్వర్ యొక్క హోస్ట్ (ఉదా. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "సర్టిఫికెట్ లోపాలను విస్మరించండి", - "notification_email_ignore_certificate_errors_description": "TLS సర్టిఫికెట్ ధ్రువీకరణ లోపాలను విస్మరించండి (సిఫార్సు చేయబడలేదు)", - "notification_email_password_description": "ఈమెయిల్ సర్వర్ తో ప్రామాణీకరించేటప్పుడు ఉపయోగించాల్సిన పాస్‌వర్డ్", - "notification_email_port_description": "ఈమెయిల్ సర్వర్ యొక్క పోర్ట్ (ఉదా. 25, 465, లేదా 587)", - "notification_email_sent_test_email_button": "పరీక్ష ఇమెయిల్ పంపి సేవ్ చేయి", - "notification_email_setting_description": "ఇమెయిల్ నోటిఫికేషన్‌లను పంపడానికి సెట్టింగ్‌లు", - "notification_email_test_email": "పరీక్ష ఇమెయిల్ పంపండి", - "notification_email_test_email_failed": "పరీక్ష ఇమెయిల్ పంపడంలో విఫలమైంది, మీ డేటాను తనిఖీ చేయండి", - "notification_email_test_email_sent": "{email} కు ఒక పరీక్ష ఇమెయిల్ పంపబడింది. దయచేసి మీ ఇన్‌బాక్స్‌ని తనిఖీ చేయండి.", - "notification_email_username_description": "ఈమెయిల్ సర్వర్ తో ప్రామాణీకరించేటప్పుడు ఉపయోగించాల్సిన యూజర్ పేరు", - "notification_enable_email_notifications": "ఇమెయిల్ నోటిఫికేషన్‌లను ప్రారంభించండి", - "notification_settings": "నోటిఫికేషన్ సెట్టింగ్‌లు", - "notification_settings_description": "ఇమెయిల్‌తో సహా నోటిఫికేషన్ సెట్టింగ్‌లను నిర్వహించండి", - "oauth_auto_launch": "స్వీయ ప్రారంభం", - "oauth_auto_launch_description": "లాగిన్ పేజీకి నావిగేట్ చేసిన తర్వాత OAuth లాగిన్ ఫ్లోను స్వయంచాలకంగా ప్రారంభించండి", - "oauth_auto_register": "స్వీయ నమోదు", - "oauth_auto_register_description": "OAuth తో సైన్ ఇన్ చేసిన తర్వాత కొత్త వినియోగదారులను స్వయంచాలకంగా నమోదు చేయండి", - "oauth_button_text": "బటన్ వచనం", - "oauth_enable_description": "OAuth తో లాగిన్ అవ్వండి", - "oauth_mobile_redirect_uri": "మొబైల్ దారిమార్పు URI", - "oauth_mobile_redirect_uri_override": "మొబైల్ దారిమార్పు URI ఓవర్‌రైడ్", - "oauth_mobile_redirect_uri_override_description": "OAuth ప్రొవైడర్ ''{callback}''వంటి మొబైల్ URIని అనుమతించనప్పుడు ప్రారంభించండి", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth లాగిన్ సెట్టింగ్‌లను నిర్వహించండి", - "oauth_settings_more_details": "ఈ ఫీచర్ గురించి మరిన్ని వివరాల కోసం, డాక్స్ చూడండి.", - "oauth_storage_label_claim": "నిల్వ లేబుల్ క్లెయిమ్", - "oauth_storage_label_claim_description": "యూజర్ యొక్క నిల్వ లేబుల్‌ను ఈ క్లెయిమ్ విలువకు స్వయంచాలకంగా సెట్ చేయండి.", - "oauth_storage_quota_claim": "నిల్వ కోటా క్లెయిమ్", - "oauth_storage_quota_claim_description": "ఈ క్లెయిమ్ విలువకు యూజర్ నిల్వ కోటాను ఆటోమేటిక్‌గా సెట్ చేయండి.", - "oauth_storage_quota_default": "డిఫాల్ట్ నిల్వ కోటా (GiB)", - "oauth_storage_quota_default_description": "క్లెయిమ్ అందించనప్పుడు GiBలోని కోటా ఉపయోగించబడుతుంది (అపరిమిత కోటా కోసం 0 నమోదు చేయండి).", - "password_enable_description": "ఇమెయిల్ మరియు పాస్‌వర్డ్‌తో లాగిన్ అవ్వండి", - "password_settings": "పాస్‌వర్డ్ లాగిన్", - "password_settings_description": "పాస్‌వర్డ్ లాగిన్ సెట్టింగ్‌లను నిర్వహించండి", - "paths_validated_successfully": "అన్ని మార్గాలు విజయవంతంగా ధృవీకరించబడ్డాయి", - "person_cleanup_job": "వ్యక్తి శుభ్రపరచడం", - "quota_size_gib": "కోటా పరిమాణం (GiB)", - "refreshing_all_libraries": "అన్ని లైబ్రరీలను రిఫ్రెష్ చేస్తోంది", - "registration": "నిర్వాహకుల నమోదు", - "registration_description": "మీరు సిస్టమ్‌లో మొదటి వినియోగదారు కాబట్టి, మీరు నిర్వాహకుడిగా నియమించబడతారు మరియు నిర్వాహక పనులకు బాధ్యత వహిస్తారు మరియు అదనపు వినియోగదారులను మీరే సృష్టిస్తారు.", - "require_password_change_on_login": "మొదటి లాగిన్‌లో యూజర్ పాస్‌వర్డ్ మార్చవలసి ఉంటుంది", - "reset_settings_to_default": "సెట్టింగ్‌లను డిఫాల్ట్‌కు రీసెట్ చేయండి", - "reset_settings_to_recent_saved": "ఇటీవల సేవ్ చేసిన సెట్టింగ్‌లకు సెట్టింగ్‌లను రీసెట్ చేయండి", - "scanning_library": "లైబ్రరీని స్కాన్ చేస్తోంది", - "search_jobs": "ఉద్యోగాల కోసం శోధించండి…", - "send_welcome_email": "స్వాగత ఇమెయిల్ పంపండి", - "server_external_domain_settings": "బాహ్య డొమైన్", - "server_external_domain_settings_description": "http(s):// తో సహా పబ్లిక్ షేర్డ్ లింక్‌ల కోసం డొమైన్", - "server_public_users": "పబ్లిక్ యూజర్లు", - "server_public_users_description": "షేర్డ్ ఆల్బమ్‌లకు యూజర్‌ను జోడించేటప్పుడు అందరు యూజర్లు (పేరు మరియు ఇమెయిల్) జాబితా చేయబడతారు. డిసేబుల్ చేసినప్పుడు, యూజర్ జాబితా అడ్మిన్ యూజర్‌లకు మాత్రమే అందుబాటులో ఉంటుంది.", - "server_settings": "సర్వర్ సెట్టింగ్‌లు", - "server_settings_description": "సర్వర్ సెట్టింగ్‌లను నిర్వహించండి", - "server_welcome_message": "స్వాగత సందేశం", - "server_welcome_message_description": "లాగిన్ పేజీలో ప్రదర్శించబడే సందేశం.", - "sidecar_job": "సైడ్‌కార్ మెటాడేటా", - "sidecar_job_description": "ఫైల్‌సిస్టమ్ నుండి సైడ్‌కార్ మెటాడేటాను కనుగొనండి లేదా సమకాలీకరించండి", - "slideshow_duration_description": "ప్రతి చిత్రాన్ని ఎన్ని సెకన్లు ప్రదర్శించాలి", - "smart_search_job_description": "స్మార్ట్ శోధనకు మద్దతు ఇవ్వడానికి ఆస్తులపై మెషిన్ లెర్నింగ్‌ను అమలు చేయండి", - "storage_template_date_time_description": "తేదీ-సమయ సమాచారం కోసం ఆస్తి సృష్టి సమయ ముద్ర ఉపయోగించబడుతుంది", - "storage_template_date_time_sample": "నమూనా సమయం {date}", - "storage_template_enable_description": "నిల్వ టెంప్లేట్ ఇంజిన్‌ను ప్రారంభించండి", - "storage_template_hash_verification_enabled": "హాష్ ధృవీకరణ ప్రారంభించబడింది", - "storage_template_hash_verification_enabled_description": "హాష్ ధృవీకరణను ప్రారంభిస్తుంది, మీకు దాని చిక్కుల గురించి ఖచ్చితంగా తెలియకపోతే దీన్ని నిలిపివేయవద్దు", - "storage_template_migration": "నిల్వ టెంప్లేట్ మైగ్రేషన్", - "storage_template_migration_description": "గతంలో అప్‌లోడ్ చేసిన ఆస్తులకు ప్రస్తుత {template}ను వర్తింపజేయండి", - "storage_template_migration_info": "నిల్వ టెంప్లేట్ అన్ని పొడిగింపులను చిన్న అక్షరాలకు మారుస్తుంది. టెంప్లేట్ మార్పులు కొత్త ఆస్తులకు మాత్రమే వర్తిస్తాయి. గతంలో అప్‌లోడ్ చేసిన ఆస్తులకు టెంప్లేట్‌ను మునుపు వర్తింపజేయడానికి, {job}ను అమలు చేయండి.", - "storage_template_migration_job": "నిల్వ టెంప్లేట్ మైగ్రేషన్ జాబ్", - "storage_template_more_details": "ఈ ఫీచర్ గురించి మరిన్ని వివరాల కోసం, storage template మరియు దాని implications చూడండి", - "storage_template_path_length": "సుమారు పాత్ పొడవు పరిమితి: {length, number}/{limit, number}", - "storage_template_settings": "నిల్వ టెంప్లేట్", - "storage_template_settings_description": "అప్‌లోడ్ ఆస్తి యొక్క ఫోల్డర్ నిర్మాణం మరియు ఫైల్ పేరును నిర్వహించండి", - "storage_template_user_label": "{label} అనేది వినియోగదారు నిల్వ లేబుల్", - "system_settings": "సిస్టమ్ సెట్టింగ్‌లు", - "tag_cleanup_job": "ట్యాగ్ క్లీనప్", - "template_email_available_tags": "మీరు మీ టెంప్లేట్‌లో ఈ క్రింది వేరియబుల్స్‌ను ఉపయోగించవచ్చు: {tags}", - "template_email_if_empty": "టెంప్లేట్ ఖాళీగా ఉంటే, డిఫాల్ట్ ఇమెయిల్ ఉపయోగించబడుతుంది.", - "template_email_invite_album": "ఆల్బమ్ టెంప్లేట్‌ను ఆహ్వానించండి", - "template_email_preview": "ప్రివ్యూ", - "template_email_settings": "ఇమెయిల్ టెంప్లేట్‌లు", - "template_email_update_album": "ఆల్బమ్ టెంప్లేట్‌ను నవీకరించు", - "template_email_welcome": "స్వాగత ఇమెయిల్ టెంప్లేట్", - "template_settings": "నోటిఫికేషన్ టెంప్లేట్‌లు", - "template_settings_description": "నోటిఫికేషన్‌ల కోసం అనుకూల టెంప్లేట్‌లను నిర్వహించండి.", - "theme_custom_css_settings": "కస్టమ్ CSS", - "theme_custom_css_settings_description": "క్యాస్కేడింగ్ స్టైల్ షీట్‌లు ఇమ్మిచ్ డిజైన్‌ను అనుకూలీకరించడానికి అనుమతిస్తాయి.", - "theme_settings": "థీమ్ సెట్టింగ్‌లు", - "theme_settings_description": "ఇమ్మిచ్ వెబ్ ఇంటర్‌ఫేస్ యొక్క అనుకూలీకరణను నిర్వహించండి", - "thumbnail_generation_job": "థంబ్‌నెయిల్‌లను రూపొందించండి", - "thumbnail_generation_job_description": "ప్రతి ఆస్తికి పెద్ద, చిన్న మరియు అస్పష్టమైన థంబ్‌నెయిల్‌లను, అలాగే ప్రతి వ్యక్తికి థంబ్‌నెయిల్‌లను రూపొందించండి", - "transcoding_acceleration_api": "త్వరణం API", - "transcoding_acceleration_api_description": "ట్రాన్స్‌కోడింగ్‌ను వేగవంతం చేయడానికి మీ పరికరంతో సంకర్షణ చెందే API. ఈ సెట్టింగ్ 'ఉత్తమ ప్రయత్నం': విఫలమైనప్పుడు ఇది సాఫ్ట్‌వేర్ ట్రాన్స్‌కోడింగ్‌కు తిరిగి వస్తుంది. మీ హార్డ్‌వేర్‌పై ఆధారపడి VP9 పనిచేయవచ్చు లేదా పనిచేయకపోవచ్చు.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU అవసరం)", - "transcoding_acceleration_qsv": "త్వరిత సమకాలీకరణ (7వ తరం ఇంటెల్ CPU లేదా తదుపరిది అవసరం)", - "transcoding_acceleration_rkmpp": "RKMPP (రాక్‌చిప్ SOCలలో మాత్రమే)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "ఆమోదించబడిన ఆడియో కోడెక్‌లు", - "transcoding_accepted_audio_codecs_description": "ఏ ఆడియో కోడెక్‌లను ట్రాన్స్‌కోడ్ చేయనవసరం లేదో ఎంచుకోండి. కొన్ని ట్రాన్స్‌కోడ్ విధానాలకు మాత్రమే ఉపయోగించబడుతుంది.", - "transcoding_accepted_containers": "ఆమోదించబడిన కంటైనర్లు", - "transcoding_accepted_containers_description": "MP4 కి రీమక్స్ చేయవలసిన అవసరం లేని కంటైనర్ ఫార్మాట్‌లను ఎంచుకోండి. కొన్ని ట్రాన్స్‌కోడ్ విధానాలకు మాత్రమే ఉపయోగించబడుతుంది.", - "transcoding_accepted_video_codecs": "ఆమోదించబడిన వీడియో కోడెక్‌లు", - "transcoding_accepted_video_codecs_description": "ఏ వీడియో కోడెక్‌లను ట్రాన్స్‌కోడ్ చేయనవసరం లేదో ఎంచుకోండి. కొన్ని ట్రాన్స్‌కోడ్ విధానాలకు మాత్రమే ఉపయోగించబడుతుంది.", - "transcoding_advanced_options_description": "చాలా మంది వినియోగదారులు మార్చాల్సిన అవసరం లేని ఎంపికలు", - "transcoding_audio_codec": "ఆడియో కోడెక్", - "transcoding_audio_codec_description": "ఓపస్ అనేది అత్యధిక నాణ్యత గల ఎంపిక, కానీ పాత పరికరాలు లేదా సాఫ్ట్‌వేర్‌లతో తక్కువ అనుకూలతను కలిగి ఉంటుంది.", - "transcoding_bitrate_description": "గరిష్ట బిట్ రేట్ కంటే ఎక్కువ లేదా ఆమోదించబడిన ఫార్మాట్‌లో లేని వీడియోలు", - "transcoding_codecs_learn_more": "ఇక్కడ ఉపయోగించిన పరిభాష గురించి మరింత తెలుసుకోవడానికి, H.264 కోడెక్, HEVC కోడెక్ మరియు VP9 కోడెక్ కోసం FFmpeg డాక్యుమెంటేషన్‌ను చూడండి.", - "transcoding_constant_quality_mode": "స్థిరమైన నాణ్యత మోడ్", - "transcoding_constant_quality_mode_description": "CQP కంటే ICQ మెరుగైనది, కానీ కొన్ని హార్డ్‌వేర్ త్వరణ పరికరాలు ఈ మోడ్‌కు మద్దతు ఇవ్వవు. ఈ ఎంపికను సెట్ చేయడం వలన నాణ్యత-ఆధారిత ఎన్‌కోడింగ్‌ను ఉపయోగిస్తున్నప్పుడు పేర్కొన్న మోడ్‌కు ప్రాధాన్యత ఇవ్వబడుతుంది. ఇది ICQకి మద్దతు ఇవ్వనందున NVENC ద్వారా విస్మరించబడింది.", - "transcoding_constant_rate_factor": "స్థిర రేటు కారకం (-crf)", - "transcoding_constant_rate_factor_description": "వీడియో నాణ్యత స్థాయి. సాధారణ విలువలు H.264 కి 23, HEVC కి 28, VP9 కి 31 మరియు AV1 కి 35. తక్కువ ఉంటే మంచిది, కానీ పెద్ద ఫైళ్లను ఉత్పత్తి చేస్తుంది.", - "transcoding_disabled_description": "ఏ వీడియోలను ట్రాన్స్‌కోడ్ చేయవద్దు, కొన్ని క్లయింట్‌లలో ప్లేబ్యాక్ అంతరాయం కలిగించవచ్చు", - "transcoding_encoding_options": "ఎన్కోడింగ్ ఎంపికలు", - "transcoding_encoding_options_description": "ఎన్కోడ్ చేసిన వీడియోల కోసం కోడెక్‌లు, రిజల్యూషన్, నాణ్యత మరియు ఇతర ఎంపికలను సెట్ చేయండి", - "transcoding_hardware_acceleration": "హార్డ్‌వేర్ త్వరణం", - "transcoding_hardware_acceleration_description": "ప్రయోగాత్మకం; చాలా వేగంగా ఉంటుంది, కానీ అదే బిట్రేట్ వద్ద తక్కువ నాణ్యతను కలిగి ఉంటుంది", - "transcoding_hardware_decoding": "హార్డ్‌వేర్ డీకోడింగ్", - "transcoding_hardware_decoding_setting_description": "కేవలం యాక్సిలరేటింగ్ ఎన్‌కోడింగ్‌కు బదులుగా ఎండ్-టు-ఎండ్ యాక్సిలరేషన్‌ను ప్రారంభిస్తుంది. అన్ని వీడియోలలో పని చేయకపోవచ్చు.", - "transcoding_max_b_frames": "గరిష్ట B-ఫ్రేమ్‌లు", - "transcoding_max_b_frames_description": "అధిక విలువలు కుదింపు సామర్థ్యాన్ని మెరుగుపరుస్తాయి, కానీ ఎన్‌కోడింగ్‌ను నెమ్మదిస్తాయి. పాత పరికరాల్లో హార్డ్‌వేర్ త్వరణంతో అనుకూలంగా ఉండకపోవచ్చు. 0 B-ఫ్రేమ్‌లను నిలిపివేస్తుంది, అయితే -1 ఈ విలువను స్వయంచాలకంగా సెట్ చేస్తుంది.", - "transcoding_max_bitrate": "గరిష్ట బిట్రేట్", - "transcoding_max_bitrate_description": "గరిష్ట బిట్రేట్‌ను సెట్ చేయడం వలన ఫైల్ పరిమాణాలను నాణ్యతకు తక్కువ ఖర్చుతో మరింత ఊహించవచ్చు. 720p వద్ద, సాధారణ విలువలు VP9 లేదా HEVCకి 2600 kbit/s, లేదా H.264కి 4500 kbit/s. 0కి సెట్ చేస్తే నిలిపివేయబడుతుంది.", - "transcoding_max_keyframe_interval": "గరిష్ట కీఫ్రేమ్ విరామం", - "transcoding_max_keyframe_interval_description": "కీఫ్రేమ్‌ల మధ్య గరిష్ట ఫ్రేమ్ దూరాన్ని సెట్ చేస్తుంది. తక్కువ విలువలు కుదింపు సామర్థ్యాన్ని మరింత దిగజార్చుతాయి, కానీ సీక్ సమయాలను మెరుగుపరుస్తాయి మరియు వేగవంతమైన కదలికతో దృశ్యాలలో నాణ్యతను మెరుగుపరచవచ్చు. 0 ఈ విలువను స్వయంచాలకంగా సెట్ చేస్తుంది.", - "transcoding_optimal_description": "లక్ష్య రిజల్యూషన్ కంటే ఎక్కువ లేదా ఆమోదించబడిన ఫార్మాట్‌లో లేని వీడియోలు", - "transcoding_policy": "ట్రాన్స్‌కోడ్ విధానం", - "transcoding_policy_description": "వీడియో ఎప్పుడు ట్రాన్స్‌కోడ్ చేయబడుతుందో సెట్ చేయండి", - "transcoding_preferred_hardware_device": "ప్రాధాన్య హార్డ్‌వేర్ పరికరం", - "transcoding_preferred_hardware_device_description": "VAAPI మరియు QSV లకు మాత్రమే వర్తిస్తుంది. హార్డ్‌వేర్ ట్రాన్స్‌కోడింగ్ కోసం ఉపయోగించే dri నోడ్‌ను సెట్ చేస్తుంది.", - "transcoding_preset_preset": "ప్రీసెట్ (-ప్రీసెట్)", - "transcoding_preset_preset_description": "కంప్రెషన్ వేగం. నెమ్మదిగా ఉండే ప్రీసెట్‌లు చిన్న ఫైల్‌లను ఉత్పత్తి చేస్తాయి మరియు నిర్దిష్ట బిట్రేట్‌ను లక్ష్యంగా చేసుకున్నప్పుడు నాణ్యతను పెంచుతాయి. VP9 'వేగవంతమైన' కంటే ఎక్కువ వేగాన్ని విస్మరిస్తుంది.", - "transcoding_reference_frames": "రిఫరెన్స్ ఫ్రేమ్‌లు", - "transcoding_reference_frames_description": "ఇచ్చిన ఫ్రేమ్‌ను కుదించేటప్పుడు సూచించాల్సిన ఫ్రేమ్‌ల సంఖ్య. అధిక విలువలు కుదింపు సామర్థ్యాన్ని మెరుగుపరుస్తాయి, కానీ ఎన్‌కోడింగ్‌ను నెమ్మదిస్తాయి. 0 ఈ విలువను స్వయంచాలకంగా సెట్ చేస్తుంది.", - "transcoding_required_description": "ఆమోదించబడిన ఫార్మాట్‌లో లేని వీడియోలు మాత్రమే", - "transcoding_settings": "వీడియో ట్రాన్స్‌కోడింగ్ సెట్టింగ్‌లు", - "transcoding_settings_description": "ఏ వీడియోలను ట్రాన్స్‌కోడ్ చేయాలో మరియు వాటిని ఎలా ప్రాసెస్ చేయాలో నిర్వహించండి", - "transcoding_target_resolution": "లక్ష్య స్పష్టత", - "transcoding_target_resolution_description": "అధిక రిజల్యూషన్‌లు మరిన్ని వివరాలను భద్రపరచగలవు కానీ ఎన్‌కోడ్ చేయడానికి ఎక్కువ సమయం పడుతుంది, పెద్ద ఫైల్ పరిమాణాలను కలిగి ఉంటాయి మరియు యాప్ ప్రతిస్పందనను తగ్గించగలవు.", - "transcoding_temporal_aq": "తాత్కాలిక AQ", - "transcoding_temporal_aq_description": "NVENCకి మాత్రమే వర్తిస్తుంది. అధిక-వివరాలు, తక్కువ-మోషన్ దృశ్యాల నాణ్యతను పెంచుతుంది. పాత పరికరాలతో అనుకూలంగా ఉండకపోవచ్చు.", - "transcoding_threads": "థ్రెడ్‌లు", - "transcoding_threads_description": "అధిక విలువలు వేగవంతమైన ఎన్‌కోడింగ్‌కు దారితీస్తాయి, కానీ సర్వర్ యాక్టివ్‌గా ఉన్నప్పుడు ఇతర పనులను ప్రాసెస్ చేయడానికి తక్కువ స్థలాన్ని వదిలివేస్తుంది. ఈ విలువ CPU కోర్ల సంఖ్య కంటే ఎక్కువగా ఉండకూడదు. 0కి సెట్ చేస్తే వినియోగాన్ని పెంచుతుంది.", - "transcoding_tone_mapping": "టోన్-మ్యాపింగ్", - "transcoding_tone_mapping_description": "SDR కి మార్చినప్పుడు HDR వీడియోల రూపాన్ని సంరక్షించడానికి ప్రయత్నిస్తుంది. ప్రతి అల్గోరిథం రంగు, వివరాలు మరియు ప్రకాశం కోసం వేర్వేరు ట్రేడ్‌ఆఫ్‌లను చేస్తుంది. హేబుల్ వివరాలను సంరక్షిస్తుంది, మోబియస్ రంగును సంరక్షిస్తుంది మరియు రీన్‌హార్డ్ ప్రకాశాన్ని సంరక్షిస్తుంది.", - "transcoding_transcode_policy": "ట్రాన్స్‌కోడ్ విధానం", - "transcoding_transcode_policy_description": "వీడియోను ఎప్పుడు ట్రాన్స్‌కోడ్ చేయాలో పాలసీ. HDR వీడియోలు ఎల్లప్పుడూ ట్రాన్స్‌కోడ్ చేయబడతాయి (ట్రాన్స్‌కోడింగ్ నిలిపివేయబడితే తప్ప).", - "transcoding_two_pass_encoding": "రెండు-పాస్ ఎన్‌కోడింగ్", - "transcoding_two_pass_encoding_setting_description": "మెరుగైన ఎన్‌కోడ్ చేసిన వీడియోలను ఉత్పత్తి చేయడానికి రెండు పాస్‌లలో ట్రాన్స్‌కోడ్ చేయండి. గరిష్ట బిట్‌రేట్ ప్రారంభించబడినప్పుడు (H.264 మరియు HEVCతో పనిచేయడానికి ఇది అవసరం), ఈ మోడ్ గరిష్ట బిట్‌రేట్ ఆధారంగా బిట్‌రేట్ పరిధిని ఉపయోగిస్తుంది మరియు CRFని విస్మరిస్తుంది. VP9 కోసం, గరిష్ట బిట్‌రేట్ నిలిపివేయబడితే CRFని ఉపయోగించవచ్చు.", - "transcoding_video_codec": "వీడియో కోడెక్", - "transcoding_video_codec_description": "VP9 అధిక సామర్థ్యం మరియు వెబ్ అనుకూలతను కలిగి ఉంటుంది, కానీ ట్రాన్స్‌కోడ్ చేయడానికి ఎక్కువ సమయం పడుతుంది. HEVC కూడా అదేవిధంగా పనిచేస్తుంది, కానీ తక్కువ వెబ్ అనుకూలతను కలిగి ఉంటుంది. H.264 విస్తృతంగా అనుకూలంగా ఉంటుంది మరియు ట్రాన్స్‌కోడ్ చేయడానికి త్వరగా ఉంటుంది, కానీ చాలా పెద్ద ఫైల్‌లను ఉత్పత్తి చేస్తుంది. AV1 అత్యంత సమర్థవంతమైన కోడెక్ కానీ పాత పరికరాల్లో మద్దతు లేదు.", - "trash_enabled_description": "ట్రాష్ ఫీచర్‌లను ప్రారంభించండి", - "trash_number_of_days": "రోజుల సంఖ్య", - "trash_number_of_days_description": "ఆస్తులను శాశ్వతంగా తొలగించే ముందు వాటిని చెత్తలో ఉంచాల్సిన రోజుల సంఖ్య", - "trash_settings": "ట్రాష్ సెట్టింగ్‌లు", - "trash_settings_description": "ట్రాష్ సెట్టింగ్‌లను నిర్వహించండి", - "user_cleanup_job": "యూజర్ క్లీనప్", - "user_delete_delay": "{user} యొక్క ఖాతా మరియు ఆస్తులు {delay, plural, one {# day} other {# days}} లో శాశ్వత తొలగింపుకు షెడ్యూల్ చేయబడతాయి.", - "user_delete_delay_settings": "ఆలస్యాన్ని తొలగించు", - "user_delete_delay_settings_description": "వినియోగదారు ఖాతా మరియు ఆస్తులను శాశ్వతంగా తొలగించడానికి తీసివేసిన తర్వాత ఎన్ని రోజులు. తొలగింపుకు సిద్ధంగా ఉన్న వినియోగదారులను తనిఖీ చేయడానికి వినియోగదారు తొలగింపు పని అర్ధరాత్రి నడుస్తుంది. ఈ సెట్టింగ్‌కు మార్పులు తదుపరి అమలు సమయంలో మూల్యాంకనం చేయబడతాయి.", - "user_delete_immediately": "{user} ఖాతా మరియు ఆస్తులు శాశ్వత తొలగింపు కోసం వెంటనే వరుసలో ఉంచబడతాయి.", - "user_delete_immediately_checkbox": "తక్షణ తొలగింపు కోసం వినియోగదారు మరియు ఆస్తులను వరుసలో ఉంచండి", - "user_management": "వినియోగదారు నిర్వహణ", - "user_password_has_been_reset": "యూజర్ పాస్‌వర్డ్ రీసెట్ చేయబడింది:", - "user_password_reset_description": "దయచేసి వినియోగదారునికి తాత్కాలిక పాస్‌వర్డ్‌ను అందించండి మరియు వారు తదుపరి లాగిన్ సమయంలో పాస్‌వర్డ్‌ను మార్చవలసి ఉంటుందని వారికి తెలియజేయండి.", - "user_restore_description": "{user} ఖాతా పునరుద్ధరించబడుతుంది.", - "user_restore_scheduled_removal": "వినియోగదారుని పునరుద్ధరించు - {date, date, long}న తొలగింపు షెడ్యూల్ చేయబడింది", - "user_settings": "వాడుకరి సెట్టింగ్‌లు", - "user_settings_description": "వాడుకరి సెట్టింగ్‌లను నిర్వహించండి", - "version_check_enabled_description": "వర్షన్ తనిఖీని చేయండి", - "version_check_implications": "వర్షన్ తనిఖీ ఫీచర్ github.comతో క్రమానుగత కమ్యూనికేషన్‌పై ఆధారపడుతుంది", - "version_check_settings": "వర్షన్ తనిఖీ", - "version_check_settings_description": "కొత్త వర్షన్ నోటిఫికేషన్‌ను ప్రారంభించండి/ఆపివేయండి", - "video_conversion_job": "వీడియోలను ట్రాన్స్‌కోడ్ చేయండి", - "video_conversion_job_description": "బ్రౌజర్లు మరియు డివైస్‌లతో విస్తృత అనుకూలత కోసం వీడియోలను ట్రాన్స్‌కోడ్ చేయండి" - }, - "admin_email": "అడ్మిన్ ఇమెయిల్", - "admin_password": "అడ్మిన్ పాస్‌వర్డ్", - "administration": "పరిపాలన", - "advanced": "ఉన్నతమైన", - "age_months": "వయస్సు {months, plural, one {# నెల} other {# నెలలు}}", - "age_year_months": "వయస్సు 1 సంవత్సరం, {months, plural, one {# నెల} other {# నెలలు}}", - "age_years": "{years, plural, other {వయస్సు #}}", - "album_added": "ఆల్బమ్ జోడించబడింది", - "album_added_notification_setting_description": "మీరు షేర్ చేసిన ఆల్బమ్‌కి జోడించబడినప్పుడు ఇమెయిల్ నోటిఫికేషన్‌ను స్వీకరించండి", - "album_cover_updated": "ఆల్బమ్ కవర్ అప్డేట్ చేయబడింది", - "album_delete_confirmation": "మీరు ఖచ్చితంగా {album} ఆల్బమ్‌ను తొలగించాలనుకుంటున్నారా?", - "album_delete_confirmation_description": "ఈ ఆల్బమ్ షేర్ చేయబడినట్లయితే, ఇతర వినియోగదారులు దీన్ని ఇకపై యాక్సెస్ చేయలేరు.", - "album_info_updated": "ఆల్బమ్ సమాచారం అప్డేట్ చేయబడింది", - "album_leave": "ఆల్బమ్ నుండి బయటకు రావాలా?", - "album_leave_confirmation": "మీరు నిజంగా {album} నుండి బయటకు రావాలనుకుంటున్నారా?", - "album_name": "ఆల్బమ్ పేరు", - "album_options": "ఆల్బమ్ ఎంపికలు", - "album_remove_user": "వాడుకరిని తీసివేయాలా?", - "album_remove_user_confirmation": "మీరు నిజంగా {user} ను తీసివేయాలనుకుంటున్నారా?", - "album_share_no_users": "మీరు ఈ ఆల్బమ్‌ను అన్ని వినియోగదారులతో షేర్ చేసినట్లుగా ఉంది లేదా మీ వద్ద షేర్ చేయడానికి ఎవరూ లేరు.", - "album_updated": "ఆల్బమ్ అప్డేట్ చేయబడింది", - "album_updated_setting_description": "షేర్ చేసిన ఆల్బమ్‌లో కొత్త అంశాలు ఉన్నప్పుడు ఇమెయిల్ నోటిఫికేషన్‌ను స్వీకరించండి", - "album_user_left": "{album} నుండి బయటకు వచ్చారు", - "album_user_removed": "{user} ను తీసివేశారు", - "album_with_link_access": "ఈ లింక్ ఉన్న ఎవరికైనా ఈ ఆల్బమ్‌లోని ఫోటోలు మరియు వ్యక్తులను చూడటానికి అనుమతించండి.", - "albums": "ఆల్బమ్‌లు", - "albums_count": "{count, plural, one {{count, number} ఆల్బమ్} other {{count, number} ఆల్బమ్‌లు}}", - "all": "అన్నీ", - "all_albums": "అన్ని ఆల్బమ్‌లు", - "all_people": "అన్ని వ్యక్తులు", - "all_videos": "అన్ని వీడియోలు", - "allow_dark_mode": "చీకటి మోడ్‌ను అనుమతించండి", - "allow_edits": "మార్పులను అనుమతించండి", - "allow_public_user_to_download": "పబ్లిక్ వినియోగదారుడు డౌన్‌లోడ్ చేసేందుకు అనుమతించండి", - "allow_public_user_to_upload": "పబ్లిక్ వినియోగదారుడు అప్‌లోడ్ చేసేందుకు అనుమతించండి", - "alt_text_qr_code": "క్యూఆర్ కోడ్ చిత్రం", - "anti_clockwise": "అపసవ్య-దిశ", - "api_key": "API కీ", - "api_key_description": "ఈ విలువ ఒక్కసారి మాత్రమే చూపబడుతుంది. విండోను మూసివేసే ముందు దయచేసి దీనిని ఖచ్చితంగా కాపీ చేసి ఎక్కడైనా భద్రపరచండి.", - "api_key_empty": "మీ API కీ పేరు ఖాళీగా ఉండకూడదు", - "api_keys": "API కీలు", - "app_settings": "యాప్ సెట్టింగ్‌లు", - "appears_in": "లో కనిపిస్తుంది", - "archive": "ఆర్కైవ్", - "archive_or_unarchive_photo": "ఫోటోను ఆర్కైవ్ లేదా అన్‌ఆర్కైవ్ చేయండి", - "archive_size": "ఆర్కైవ్ పరిమాణం", - "archive_size_description": "డౌన్‌లోడ్‌ల కోసం ఆర్కైవ్ పరిమాణాన్ని (GiBలో) కాన్ఫిగర్ చేయండి", - "archived_count": "{count, plural, other {# ఆర్కైవ్ చేయబడింది}}", - "are_these_the_same_person": "వీళ్లందరు ఒకే వ్యక్తినా?", - "are_you_sure_to_do_this": "మీరు నిజంగా ఇది చేయాలనుకుంటున్నారా?", - "asset_added_to_album": "ఆల్బమ్‌కు జోడించబడింది", - "asset_adding_to_album": "ఆల్బమ్‌కు జోడిస్తున్నాము…", - "asset_description_updated": "ఆస్తి వివరణ అప్డేట్ చేయబడింది", - "asset_filename_is_offline": "ఆస్తి {filename} ఆఫ్‌లైన్‌లో ఉంది", - "asset_has_unassigned_faces": "ఆస్తికి కేటాయించని ముఖాలు ఉన్నాయి", - "asset_hashing": "హాషింగ్ చేస్తున్నాము…", - "asset_offline": "ఆస్తి ఆఫ్‌లైన్‌లో ఉంది", - "asset_offline_description": "ఈ బాహ్య ఆస్తి ఇకపై డిస్క్‌లో కనబడటం లేదు. సహాయానికి దయచేసి మీ Immich నిర్వాహకుడిని సంప్రదించండి.", - "asset_skipped": "దాటవేయబడింది", - "asset_skipped_in_trash": "చెత్తబుట్టలో ఉంది", - "asset_uploaded": "అప్‌లోడ్ చేయబడింది", - "asset_uploading": "అప్‌లోడ్ జరుగుతోంది…", - "assets": "ఆస్తులు", - "assets_added_count": "జోడించబడినవి {count, plural, one {# ఆస్తి} other {# ఆస్తులు}}", - "assets_added_to_album_count": "{count, plural, one {# ఆస్తి} other {# ఆస్తులు}} ఆల్బమ్‌కి జోడించబడినవి", - "assets_count": "{count, plural, one {# ఆస్తి} other {# ఆస్తులు}}", - "assets_moved_to_trash_count": "{count, plural, one {# ఆస్తి} other {# ఆస్తులు}} చెత్తబుట్టలోకి తరలించారు", - "assets_permanently_deleted_count": "{count, plural, one {# ఆస్తి} other {# ఆస్తులు}} శాశ్వతంగా తొలగించబడినవి", - "assets_removed_count": "{count, plural, one {# ఆస్తి} other {# ఆస్తులు}} తీసివేయబడినవి", - "assets_restore_confirmation": "మీరు మీ చెత్తబుట్టలోని అన్ని ఆస్తులను పునరుద్ధరించాలనుకుంటున్నారా? మీరు ఈ చర్యను ఆపలేరు/రద్దు చేయలేరు! దయచేసి గమనించండి, ఆఫ్‌లైన్‌లో ఉన్న ఆస్తులు ఈ విధంగా పునరుద్ధరించబడవు.", - "assets_restored_count": "{count, plural, one {# ఆస్తి} other {# ఆస్తులు}} పునరుద్ధరించబడినవి", - "assets_trashed_count": "తొలగించబడింది {count, plural, one {# అసెట్} other {# అసెట్‌లు}}", - "assets_were_part_of_album_count": "{count, plural, one {అసెట్ ఇప్పటికే} other {అసెట్‌లు ఇప్పటికే}} ఆల్బమ్‌లో భాగంగా ఉన్నాయి", - "authorized_devices": "అనుమతించబడిన పరికరాలు", - "back": "తిరిగి", - "back_close_deselect": "వెనక్కి, మూసివేయి, లేదా ఎంచుకున్నది తీసివేయి", - "backward": "వెనుకకు", - "birthdate_saved": "జన్మతేది విజయవంతంగా సేవ్ చేయబడింది", - "birthdate_set_description": "జన్మతేది ఈ వ్యక్తి ఫోటో తీసిన సమయంలో వయస్సును గణించేందుకు ఉపయోగించబడుతుంది.", - "blurred_background": "ముదురు నేపధ్యం", - "bugs_and_feature_requests": "లోపాలు & ఫీచర్ అభ్యర్థనలు", - "build": "నిర్మాణం", - "build_image": "నిర్మాణ సంఖ్య", - "bulk_delete_duplicates_confirmation": "మీరు నిజంగా {count, plural, one {# నకిలీ అసెట్} other {# నకిలీ అసెట్‌లు}} సమూహంగా తొలగించాలనుకుంటున్నారా? ప్రతి సమూహంలోని అతిపెద్ద అసెట్‌ను ఉంచి, మిగతా అన్ని నకిలీలను శాశ్వతంగా తొలగించబడతాయి. ఈ చర్యను వెనుకకు తీసుకోలేరు!", - "bulk_keep_duplicates_confirmation": "మీరు నిజంగానే {count, plural, one {# నకిలీ అసెట్‌ను} other {# నకిలీ అసెట్‌లను}} ఉంచాలనుకుంటున్నారా? ఇది అన్ని నకిలీ సమూహాలను ఏవీ తొలగించకుండా పరిష్కరిస్తుంది.", - "bulk_trash_duplicates_confirmation": "మీరు నిజంగా {count, plural, one {# నకిలీ అసెట్} other {# నకిలీ అసెట్‌లు}} సమూహంగా చెత్తబుట్టలో వేయాలనుకుంటున్నారా? ప్రతి సమూహంలోని అతిపెద్ద అసెట్‌ను ఉంచి, మిగతా అన్ని నకిలీలను చెత్తబుట్టలో వేయబడతాయి.", - "buy": "Immichను కొనండి", - "camera": "కెమెరా", - "camera_brand": "కెమెరా బ్రాండ్", - "camera_model": "కెమెరా మోడల్", - "cancel": "రద్దు చేయి", - "cancel_search": "వెతకడం రద్దు చేయి", - "cannot_merge_people": "వ్యక్తులను విలీనం చేయలేరు", - "cannot_undo_this_action": "మీరు ఈ చర్యను వెనుకకు తీసుకోలేరు!", - "cannot_update_the_description": "వివరణను నవీకరించలేరు", - "change_date": "తేదీ మార్చు", - "change_expiration_time": "గడువు సమయాన్ని మార్చు", - "change_location": "స్థానాన్ని మార్చు", - "change_name": "పేరు మార్చు", - "change_name_successfully": "పేరు విజయవంతంగా మార్చబడింది", - "change_password": "పాస్వర్డ్ మార్చు", - "change_password_description": "ఇదే మీరు వ్యవస్థలోకి మొట్టమొదటిసారి సైన్ఇన్ చేయడం, లేదా మీ పాస్వర్డ్ మార్చే అభ్యర్ధన చేయబడింది. దయచేసి కొత్త పాస్వర్డ్ కింద ఇవ్వండి.", - "change_your_password": "మీ పాస్వర్డ్‌ను మార్చండి", - "changed_visibility_successfully": "దృశ్యమానత విజయవంతంగా మార్చబడింది", - "check_logs": "లాగ్‌లను తనిఖీ చేయండి", - "choose_matching_people_to_merge": "విలీనం చేయడానికి సరిపోలిన వ్యక్తులను ఎంచుకోండి", - "city": "నగరం", - "clear": "ఖాళీ చేయి", - "clear_all": "అన్నీ ఖాళీ చేయి", - "clear_all_recent_searches": "ఇటీవల చేసిన అన్ని శోధనలను ఖాళీ చేయి", - "clear_message": "సందేశాన్ని ఖాళీ చేయి", - "clear_value": "విలువను ఖాళీ చేయి", - "clockwise": "సవ్యదిశ", - "close": "మూసివేయి", - "collapse": "సంకుచితం చేయి", - "collapse_all": "అన్నీ సంకుచితం చేయి", - "color": "రంగు", - "color_theme": "రంగు థీమ్", - "comment_deleted": "వ్యాఖ్య తొలగించబడింది", - "comment_options": "వ్యాఖ్య ఎంపికలు", - "comments_and_likes": "వ్యాఖ్యలు & లైక్‌లు", - "comments_are_disabled": "వ్యాఖ్యలు అచేతనంగా ఉన్నాయి", - "confirm": "నిర్ధారించండి", - "confirm_admin_password": "అడ్మిన్ పాస్వర్డ్‌ను నిర్ధారించండి", - "confirm_delete_face": "మీరు నిజంగా {name} ముఖాన్ని అసెట్ నుండి తొలగించాలనుకుంటున్నారా?", - "confirm_delete_shared_link": "మీరు నిజంగా ఈ పంచుకున్న లింక్‌ను తొలగించాలనుకుంటున్నారా?", - "confirm_keep_this_delete_others": "స్టాక్‌లోని ఈ అసెట్‌ను మినహాయించి మిగతా అన్ని అసెట్‌లు తొలగించబడతాయి. మీరు నిజంగా కొనసాగించాలనుకుంటున్నారా?", - "confirm_password": "పాస్వర్డ్‌ను నిర్ధారించండి", - "contain": "ఇరికించు", - "context": "సందర్భం", - "continue": "కొనసాగించు", - "copied_image_to_clipboard": "చిత్రాన్ని క్లిప్‌బోర్డ్‌కు కాపీ చేయబడింది.", - "copied_to_clipboard": "క్లిప్‌బోర్డ్‌కు కాపీ చేయబడింది!", - "copy_error": "కాపీ ఎర్రర్", - "copy_file_path": "ఫైల్ పాత్‌ను కాపీ చేయండి", - "copy_image": "చిత్రాన్ని కాపీ చేయి", - "copy_link": "లింక్‌ను కాపీ చేయి", - "copy_link_to_clipboard": "లింక్‌ను క్లిప్‌బోర్డ్‌కు కాపీ చేయండి", - "copy_password": "పాస్‌వర్డ్‌ను కాపీ చేయండి", - "copy_to_clipboard": "క్లిప్‌బోర్డ్‌కు కాపీ చేయి", - "country": "దేశం", - "cover": "నింపు", - "covers": "నింపుతుంది", - "create": "సృష్టించు", - "create_album": "ఆల్బమ్‌ను సృష్టించండి", - "create_library": "లైబ్రరీని సృష్టించండి", - "create_link": "లింక్‌ను సృష్టించండి", - "create_link_to_share": "షేర్ చేయడానికి లింక్‌ను సృష్టించండి", - "create_link_to_share_description": "లింక్ ఉన్న ఎవరైనా ఎంచుకున్న ఫోటో(ల)ను చూడనివ్వండి", - "create_new_person": "కొత్త వ్యక్తిని సృష్టించండి", - "create_new_person_hint": "ఎంచుకున్న ఆస్తులను కొత్త వ్యక్తికి కేటాయించండి", - "create_new_user": "కొత్త వినియోగదారుని సృష్టించండి", - "create_tag": "ట్యాగ్‌ను సృష్టించండి", - "create_tag_description": "కొత్త ట్యాగ్‌ను సృష్టించండి. నెస్టెడ్ ట్యాగ్‌ల కోసం, దయచేసి ఫార్వర్డ్ స్లాష్‌లతో సహా ట్యాగ్ యొక్క పూర్తి మార్గాన్ని నమోదు చేయండి.", - "create_user": "వినియోగదారుని సృష్టించండి", - "created": "సృష్టించబడింది", - "current_device": "ప్రస్తుత పరికరం", - "custom_locale": "అనుకూల భాష", - "custom_locale_description": "భాష మరియు ప్రాంతం ఆధారంగా తేదీలు మరియు సంఖ్యలను ఫార్మాట్ చేయండి", - "dark": "చీకటి", - "date_after": "తేదీ తర్వాత", - "date_and_time": "తేదీ మరియు సమయం", - "date_before": "తేదీ ముందు", - "date_of_birth_saved": "పుట్టిన తేదీ విజయవంతంగా సేవ్ చేయబడింది", - "date_range": "తేదీ పరిధి", - "day": "రోజు", - "deduplicate_all": "అన్నీ నకిలీలు తొలగించు", - "deduplication_criteria_1": "బైట్‌లలో చిత్ర పరిమాణం", - "deduplication_criteria_2": "EXIF డేటా సంఖ్య", - "deduplication_info": "నకిలీల తొలగింపు సమాచారం", - "deduplication_info_description": "ఆస్తులను స్వయంచాలకంగా ముందస్తుగా ఎంచుకోవడానికి మరియు నకిలీలను పెద్దమొత్తంలో తొలగించడానికి, మేము వీటిని పరిశీలిస్తాము:", - "default_locale": "పూర్వనియోజిత భాష", - "default_locale_description": "మీ బ్రౌజర్ లొకేల్ ఆధారంగా తేదీలు మరియు సంఖ్యలను ఫార్మాట్ చేయండి", - "delete": "తొలగించు", - "delete_album": "ఆల్బమ్‌ను తొలగించు", - "delete_api_key_prompt": "మీరు ఈ API కీని ఖచ్చితంగా తొలగించాలనుకుంటున్నారా?", - "delete_duplicates_confirmation": "మీరు ఈ నకిలీలను శాశ్వతంగా తొలగించాలనుకుంటున్నారా?", - "delete_face": "ముఖాన్ని తొలగించు", - "delete_key": "కీని తొలగించు", - "delete_library": "లైబ్రరీని తొలగించు", - "delete_link": "లింక్‌ను తొలగించు", - "delete_others": "ఇతరులను తొలగించండి", - "delete_shared_link": "షేర్ చేసిన లింక్‌ను తొలగించండి", - "delete_tag": "ట్యాగ్‌ను తొలగించు", - "delete_tag_confirmation_prompt": "మీరు ఖచ్చితంగా {tagName} ట్యాగ్‌ను తొలగించాలనుకుంటున్నారా?", - "delete_user": "వినియోగదారుని తొలగించు", - "deleted_shared_link": "షేర్ చేసిన లింక్ తొలగించబడింది", - "deletes_missing_assets": "డిస్క్ నుండి తప్పిపోయిన ఆస్తులను తొలగిస్తుంది", - "description": "వివరణ", - "details": "వివరాలు", - "direction": "దిశ", - "disabled": "నిలిపివేయబడింది", - "disallow_edits": "సవరణలను అనుమతించవద్దు", - "discord": "డిస్కోర్డ్", - "discover": "కనుగొనండి", - "dismiss_all_errors": "అన్ని లోపాలను తీసివేయి", - "dismiss_error": "లోపాన్ని తీసివేయండి", - "display_options": "ప్రదర్శన ఎంపికలు", - "display_order": "ప్రదర్శన క్రమం", - "display_original_photos": "అసలు ఫోటోలను ప్రదర్శించు", - "display_original_photos_setting_description": "ఆస్తిని వీక్షిస్తున్నప్పుడు అసలు ఆస్తి వెబ్-అనుకూలంగా ఉన్నట్టయితే, థంబ్‌నెయిల్‌ల కంటే అసలు ఫోటోను ప్రదర్శించడానికి ఇష్టపడండి. దీని ఫలితంగా ఫోటో ప్రదర్శన వేగం మందగించవచ్చు.", - "do_not_show_again": "ఈ సందేశాన్ని మళ్ళీ చూపించవద్దు", - "documentation": "డాక్యుమెంటేషన్", - "done": "పూర్తయింది", - "download": "డౌన్లోడ్", - "download_include_embedded_motion_videos": "పొందుపరిచిన వీడియోలు", - "download_include_embedded_motion_videos_description": "మోషన్ ఫోటోలలో పొందుపరిచిన వీడియోలను ప్రత్యేక ఫైల్‌గా చేర్చండి", - "download_settings": "డౌన్లోడ్", - "download_settings_description": "ఆస్తి డౌన్‌లోడ్‌కు సంబంధించిన సెట్టింగ్‌లను నిర్వహించండి", - "downloading": "డౌన్‌లోడ్ చేస్తోంది", - "downloading_asset_filename": "ఆస్తి {filename}ని డౌన్‌లోడ్ చేస్తోంది", - "drop_files_to_upload": "అప్‌లోడ్ చేయడానికి ఫైల్‌లను ఎక్కడైనా వదలండి", - "duplicates": "నకిలీలు", - "duplicates_description": "ప్రతి సమూహాన్ని నకిలీలు అని సూచించడం ద్వారా పరిష్కరించండి", - "duration": "వ్యవధి", - "edit": "సవరించు", - "edit_album": "ఆల్బమ్‌ను సవరించు", - "edit_avatar": "అవతార్‌ను సవరించు", - "edit_date": "తేదీని సవరించు", - "edit_date_and_time": "తేదీ మరియు సమయాన్ని సవరించు", - "edit_exclusion_pattern": "మినహాయింపు నమూనాను సవరించు", - "edit_faces": "ముఖాలను సవరించు", - "edit_key": "కీని సవరించు", - "edit_link": "లింక్‌ను సవరించు", - "edit_location": "స్థానాన్ని సవరించు", - "edit_name": "పేరును సవరించు", - "edit_people": "వ్యక్తులను సవరించండి", - "edit_tag": "ట్యాగ్‌ను సవరించు", - "edit_title": "శీర్షికను సవరించు", - "edit_user": "వినియోగదారుని సవరించు", - "editor": "ఎడిటర్", - "editor_close_without_save_prompt": "మార్పులు సేవ్ చేయబడవు", - "editor_close_without_save_title": "ఎడిటర్‌ను మూసివేయాలా?", - "email": "ఇ-మెయిల్", - "empty_trash": "చెత్తను ఖాళీ చేయి", - "empty_trash_confirmation": "మీరు ఖచ్చితంగా ట్రాష్‌ను ఖాళీ చేయాలనుకుంటున్నారా? ఇది ట్రాష్‌లోని అన్ని ఆస్తులను ఇమ్మిచ్ నుండి శాశ్వతంగా తొలగిస్తుంది.\nమీరు ఈ చర్యను రద్దు చేయలేరు!", - "enable": "ప్రారంభించు", - "enabled": "ప్రారంభించబడింది", - "end_date": "ముగింపు తేదీ", - "error": "లోపం", - "error_delete_face": "ఆస్తి నుండి ముఖాన్ని తొలగించడంలో లోపం ఏర్పడింది", - "error_loading_image": "చిత్రాన్ని లోడ్ చేయడంలో లోపం ఏర్పడింది", - "error_title": "లోపం - ఏదో తప్పు జరిగింది", - "errors": { - "cannot_navigate_next_asset": "తదుపరి ఆస్తికి నావిగేట్ చేయలేరు", - "cannot_navigate_previous_asset": "మునుపటి ఆస్తికి నావిగేట్ చేయలేరు", - "cant_apply_changes": "మార్పులను వర్తింపజేయడం సాధ్యం కాదు", - "cant_change_activity": "ఇతర యాక్టివిటీని {enabled, select, true {disable} other {enable}} చేయలేరు", - "cant_change_asset_favorite": "ఆస్తికి ఇష్టమైనదాన్ని మార్చలేరు", - "cant_change_metadata_assets_count": "{count, plural, one {# asset} other {# assets}} యొక్క మెటాడేటాను మార్చలేము", - "cant_get_faces": "ముఖాలను పొందలేకపోతున్నాను", - "cant_get_number_of_comments": "వ్యాఖ్యల సంఖ్యను పొందలేకపోతున్నాను", - "cant_search_people": "వ్యక్తులను శోధించడం సాధ్యం కాదు", - "cant_search_places": "స్థలాలను శోధించడం సాధ్యం కాలేదు", - "error_adding_assets_to_album": "ఆల్బమ్‌కు ఆస్తులను జోడించడంలో లోపం ఏర్పడింది", - "error_adding_users_to_album": "ఆల్బమ్‌కు వినియోగదారులను జోడించడంలో లోపం ఏర్పడింది", - "error_deleting_shared_user": "షేర్డ్ యూజర్‌ను తొలగించడంలో ఎర్రర్ ఏర్పడింది", - "error_downloading": "{filename} ని డౌన్‌లోడ్ చేయడంలో లోపం ఏర్పడింది", - "error_hiding_buy_button": "కొనుగోలు బటన్‌ను దాచడంలో లోపం ఏర్పడింది", - "error_removing_assets_from_album": "ఆల్బమ్ నుండి ఆస్తులను తీసివేయడంలో లోపం ఏర్పడింది, మరిన్ని వివరాల కోసం కన్సోల్‌ని తనిఖీ చేయండి", - "error_selecting_all_assets": "అన్ని ఆస్తులను ఎంచుకోవడంలో లోపం ఏర్పడింది", - "exclusion_pattern_already_exists": "ఈ మినహాయింపు నమూనా ఇప్పటికే ఉంది.", - "failed_to_create_album": "ఆల్బమ్‌ను సృష్టించడంలో విఫలమైంది", - "failed_to_create_shared_link": "షేర్ చేయాల్సిన లింక్‌ను సృష్టించడంలో విఫలమైంది", - "failed_to_edit_shared_link": "షేర్ చేయాల్సిన లింక్‌ను సవరించడంలో విఫలమైంది", - "failed_to_get_people": "వ్యక్తులను పొందడంలో విఫలమైంది", - "failed_to_keep_this_delete_others": "ఈ ఆస్తిని ఉంచుకోవడంలో మరియు ఇతర ఆస్తులను తొలగించడంలో విఫలమైంది", - "failed_to_load_asset": "ఆస్తిని లోడ్ చేయడంలో విఫలమైంది", - "failed_to_load_assets": "ఆస్తులను లోడ్ చేయడంలో విఫలమైంది", - "failed_to_load_people": "వ్యక్తులను లోడ్ చేయడంలో విఫలమైంది", - "failed_to_remove_product_key": "ఉత్పత్తి కీని తీసివేయడంలో విఫలమైంది", - "failed_to_stack_assets": "ఆస్తులను పేర్చడంలో విఫలమైంది", - "failed_to_unstack_assets": "ఆస్తులను అన్-స్టాక్ చేయడంలో విఫలమైంది", - "incorrect_email_or_password": "తప్పు ఇమెయిల్ లేదా పాస్‌వర్డ్", - "paths_validation_failed": "{paths, plural, one {# path} other {# paths}} ధ్రువీకరణ విఫలమైంది", - "profile_picture_transparent_pixels": "ప్రొఫైల్ చిత్రాలలో పారదర్శక పిక్సెల్‌లు ఉండకూడదు. దయచేసి చిత్రాన్ని జూమ్ చేయండి మరియు/లేదా తరలించండి.", - "quota_higher_than_disk_size": "మీరు డిస్క్ పరిమాణం కంటే ఎక్కువ కోటాను సెట్ చేసారు", - "unable_to_add_album_users": "ఆల్బమ్‌కు వినియోగదారులను జోడించడం సాధ్యం కాలేదు", - "unable_to_add_assets_to_shared_link": "షేర్ చేసిన లింక్‌కు ఆస్తులను జోడించడం సాధ్యం కాలేదు", - "unable_to_add_comment": "వ్యాఖ్యను జోడించడం సాధ్యం కాలేదు", - "unable_to_add_exclusion_pattern": "మినహాయింపు నమూనాను జోడించడం సాధ్యం కాలేదు", - "unable_to_add_partners": "భాగస్వాములను జోడించడం సాధ్యం కాలేదు", - "unable_to_add_remove_archive": "ఆర్కైవ్ చేయడం {archived, select, true {remove asset from} other {add asset to}} సాధ్యం కాలేదు", - "unable_to_add_remove_favorites": "ఇష్టమైనవిగా {favorite, select, true {add asset to} other {remove asset from}} సాధ్యం కాలేదు", - "unable_to_archive_unarchive": "{archived, select, true {archive} other {unarchive}} సాధ్యం కాలేదు", - "unable_to_change_album_user_role": "ఆల్బమ్ వినియోగదారు పాత్రను మార్చలేకపోయాము", - "unable_to_change_date": "తేదీని మార్చడం సాధ్యం కాలేదు", - "unable_to_change_favorite": "ఆస్తికి ఇష్టమైనదాన్ని మార్చడం సాధ్యం కాలేదు", - "unable_to_change_location": "స్థానాన్ని మార్చడం సాధ్యం కాలేదు", - "unable_to_change_password": "పాస్‌వర్డ్‌ను మార్చలేకపోయాం", - "unable_to_change_visibility": "{count, plural, one {# person} other {# people}} కి దృశ్యమానతను మార్చలేకపోయాము", - "unable_to_complete_oauth_login": "OAuth లాగిన్‌ను పూర్తి చేయడం సాధ్యం కాలేదు", - "unable_to_connect": "కనెక్ట్ చేయడం సాధ్యం కాలేదు", - "unable_to_copy_to_clipboard": "క్లిప్‌బోర్డ్‌కు కాపీ చేయడం సాధ్యం కాదు, మీరు https ద్వారా పేజీని యాక్సెస్ చేస్తున్నారని నిర్ధారించుకోండి", - "unable_to_create_admin_account": "నిర్వాహక ఖాతాను సృష్టించడం సాధ్యం కాలేదు", - "unable_to_create_api_key": "కొత్త API కీని సృష్టించడం సాధ్యం కాలేదు", - "unable_to_create_library": "లైబ్రరీని సృష్టించడం సాధ్యం కాలేదు", - "unable_to_create_user": "వినియోగదారుని సృష్టించలేకపోయింది", - "unable_to_delete_album": "ఆల్బమ్‌ను తొలగించడం సాధ్యం కాలేదు", - "unable_to_delete_asset": "ఆస్తిని తొలగించడం సాధ్యం కాలేదు", - "unable_to_delete_assets": "ఆస్తులను తొలగించడంలో లోపం ఏర్పడింది", - "unable_to_delete_exclusion_pattern": "మినహాయింపు నమూనాను తొలగించడం సాధ్యం కాలేదు", - "unable_to_delete_shared_link": "షేర్ చేసిన లింక్‌ను తొలగించడం సాధ్యం కాలేదు", - "unable_to_delete_user": "వినియోగదారుని తొలగించడం సాధ్యం కాలేదు", - "unable_to_download_files": "ఫైళ్లను డౌన్‌లోడ్ చేయడం సాధ్యం కాలేదు", - "unable_to_edit_exclusion_pattern": "మినహాయింపు నమూనాను సవరించడం సాధ్యం కాలేదు", - "unable_to_empty_trash": "ట్రాష్‌ను ఖాళీ చేయడం సాధ్యం కాలేదు", - "unable_to_enter_fullscreen": "పూర్తి స్క్రీన్‌లోకి ప్రవేశించడం సాధ్యం కాలేదు", - "unable_to_exit_fullscreen": "పూర్తి స్క్రీన్ నుండి నిష్క్రమించడం సాధ్యం కాలేదు", - "unable_to_get_comments_number": "వ్యాఖ్యల సంఖ్యను పొందలేకపోయింది", - "unable_to_get_shared_link": "షేర్ చేసిన లింక్‌ను పొందడం విఫలమైంది", - "unable_to_hide_person": "వ్యక్తిని దాచలేకపోయారు", - "unable_to_link_motion_video": "మోషన్ వీడియోను లింక్ చేయడం సాధ్యం కాలేదు", - "unable_to_link_oauth_account": "OAuth ఖాతాను లింక్ చేయడం సాధ్యం కాలేదు", - "unable_to_log_out_all_devices": "అన్ని పరికరాలను లాగ్ అవుట్ చేయడం సాధ్యం కాలేదు", - "unable_to_log_out_device": "పరికరం లాగ్ అవుట్ చేయడం సాధ్యం కాలేదు", - "unable_to_login_with_oauth": "OAuth తో లాగిన్ అవ్వడం సాధ్యం కాలేదు", - "unable_to_play_video": "వీడియో ప్లే చేయడం సాధ్యం కాలేదు", - "unable_to_reassign_assets_existing_person": "{name, select, null {an existing person} other {{name}}}కు ఆస్తులను తిరిగి కేటాయించడం సాధ్యం కాలేదు", - "unable_to_reassign_assets_new_person": "కొత్త వ్యక్తికి ఆస్తులను తిరిగి కేటాయించడం సాధ్యం కాలేదు", - "unable_to_refresh_user": "వినియోగదారుని రిఫ్రెష్ చేయడం సాధ్యం కాలేదు", - "unable_to_remove_album_users": "ఆల్బమ్ నుండి వినియోగదారులను తీసివేయడం సాధ్యం కాలేదు", - "unable_to_remove_api_key": "API కీని తీసివేయడం సాధ్యం కాలేదు", - "unable_to_remove_assets_from_shared_link": "షేర్ చేసిన లింక్ నుండి ఆస్తులను తీసివేయడం సాధ్యం కాలేదు", - "unable_to_remove_library": "లైబ్రరీని తీసివేయడం సాధ్యం కాలేదు", - "unable_to_remove_partner": "భాగస్వామిని తీసివేయడం సాధ్యం కాలేదు", - "unable_to_remove_reaction": "ప్రతిస్పందనను తీసివేయడం సాధ్యం కాలేదు", - "unable_to_reset_password": "పాస్‌వర్డ్‌ను రీసెట్ చేయడం సాధ్యం కాలేదు", - "unable_to_resolve_duplicate": "నకిలీని పరిష్కరించలేకపోయింది", - "unable_to_restore_assets": "ఆస్తులను పునరుద్ధరించడం సాధ్యం కాలేదు", - "unable_to_restore_trash": "ట్రాష్‌ను పునరుద్ధరించడం సాధ్యం కాలేదు", - "unable_to_restore_user": "వినియోగదారుని పునరుద్ధరించడం సాధ్యం కాలేదు", - "unable_to_save_album": "ఆల్బమ్‌ను సేవ్ చేయడం సాధ్యం కాలేదు", - "unable_to_save_api_key": "API కీని సేవ్ చేయడం సాధ్యం కాలేదు", - "unable_to_save_date_of_birth": "పుట్టిన తేదీని సేవ్ చేయడం సాధ్యం కాలేదు", - "unable_to_save_name": "పేరును సేవ్ చేయడం సాధ్యం కాలేదు", - "unable_to_save_profile": "ప్రొఫైల్‌ను సేవ్ చేయడం సాధ్యం కాలేదు", - "unable_to_save_settings": "సెట్టింగ్‌లను సేవ్ చేయడం సాధ్యం కాలేదు", - "unable_to_scan_libraries": "లైబ్రరీలను స్కాన్ చేయడం సాధ్యం కాలేదు", - "unable_to_scan_library": "లైబ్రరీని స్కాన్ చేయడం సాధ్యం కాలేదు", - "unable_to_set_feature_photo": "ఫీచర్ ఫోటోను సెట్ చేయడం సాధ్యం కాలేదు", - "unable_to_set_profile_picture": "ప్రొఫైల్ చిత్రాన్ని సెట్ చేయడం సాధ్యం కాలేదు", - "unable_to_submit_job": "ఉద్యోగాన్ని సమర్పించలేకపోయింది", - "unable_to_trash_asset": "ఆస్తిని ట్రాష్ చేయడం సాధ్యం కాలేదు", - "unable_to_unlink_account": "ఖాతాను అన్‌లింక్ చేయడం సాధ్యం కాలేదు", - "unable_to_unlink_motion_video": "మోషన్ వీడియోను అన్‌లింక్ చేయడం సాధ్యం కాలేదు", - "unable_to_update_album_cover": "ఆల్బమ్ కవర్‌ను నవీకరించలేకపోయింది", - "unable_to_update_album_info": "ఆల్బమ్ సమాచారాన్ని నవీకరించలేకపోయింది", - "unable_to_update_library": "లైబ్రరీని నవీకరించడం సాధ్యం కాలేదు", - "unable_to_update_location": "స్థానాన్ని నవీకరించడం సాధ్యం కాలేదు", - "unable_to_update_settings": "సెట్టింగ్‌లను నవీకరించడం సాధ్యం కాలేదు", - "unable_to_update_timeline_display_status": "టైమ్‌లైన్ డిస్‌ప్లే స్థితిని నవీకరించడం సాధ్యం కాలేదు", - "unable_to_update_user": "వినియోగదారుని నవీకరించలేకపోయింది", - "unable_to_upload_file": "ఫైల్‌ను అప్‌లోడ్ చేయడం సాధ్యం కాలేదు" - }, - "exif": "ఏక్సిఫ్", - "exit_slideshow": "స్లయిడ్ షో నుండి నిష్క్రమించు", - "expand_all": "అన్నీ విస్తరించు", - "expire_after": "దీని తర్వాత గడువు ముగుస్తుంది", - "expired": "గడువు ముగిసింది", - "expires_date": "{date}న గడువు ముగుస్తుంది", - "explore": "అన్వేషించండి", - "explorer": "అన్వేషకుడు", - "export": "ఎగుమతి", - "export_as_json": "JSONగా ఎగుమతి చేయి", - "extension": "పొడిగింపు", - "external": "బాహ్య", - "external_libraries": "బాహ్య గ్రంథాలయాలు", - "face_unassigned": "కేటాయించబడలేదు", - "failed_to_load_assets": "ఆస్తులను లోడ్ చేయడంలో విఫలమైంది", - "favorite": "ఇష్టమైనది", - "favorite_or_unfavorite_photo": "ఇష్టమైన లేదా ఇష్టమైన ఫోటో నుండి తీసివేయి", - "favorites": "ఇష్టమైనవి", - "feature_photo_updated": "ఫీచర్ ఫోటో నవీకరించబడింది", - "features": "లక్షణాలు", - "features_setting_description": "యాప్ ఫీచర్‌లను నిర్వహించండి", - "file_name_or_extension": "ఫైల్ పేరు లేదా పొడిగింపు", - "filename": "ఫైలుపేరు", - "filetype": "ఫైల్ రకం", - "filter_people": "వ్యక్తులను ఫిల్టర్ చేయండి", - "find_them_fast": "పేరుతో శోధన ద్వారా వాటిని వేగంగా కనుగొనండి", - "fix_incorrect_match": "తప్పు సరిపోలికను పరిష్కరించండి", - "folders": "ఫోల్డర్లు", - "folders_feature_description": "ఫైల్ సిస్టమ్‌లోని ఫోటోలు మరియు వీడియోల కోసం ఫోల్డర్ వీక్షణను బ్రౌజ్ చేయడం", - "forward": "ముందుకు", - "general": "సాధారణ", - "get_help": "సహాయం పొందండి", - "getting_started": "మొదలు అవుతుంది", - "go_back": "వెనక్కి వెళ్ళు", - "go_to_folder": "ఫోల్డర్‌కు వెళ్లండి", - "go_to_search": "శోధనకు వెళ్లండి", - "group_albums_by": "ఆల్బమ్‌లను దీని ద్వారా సమూహపరచండి...", - "group_country": "దేశం వారీగా సమూహం", - "group_no": "గ్రూపింగ్ లేదు", - "group_owner": "యజమాని వారీగా సమూహం చేయండి", - "group_places_by": "స్థలాల ద్వారా సమూహపరచండి...", - "group_year": "సంవత్సరం వారీగా సమూహం చేయండి", - "has_quota": "కోటా ఉంది", - "hi_user": "హాయ్ {name} ({email})", - "hide_all_people": "అందరినీ దాచు", - "hide_gallery": "గ్యాలరీని దాచు", - "hide_named_person": "{name} వ్యక్తిని దాచు", - "hide_password": "పాస్‌వర్డ్‌ను దాచు", - "hide_person": "వ్యక్తిని దాచు", - "hide_unnamed_people": "పేరులేని వ్యక్తులను దాచు", - "host": "హోస్ట్", - "hour": "గంట", - "image": "చిత్రం", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} {date}న తీయబడింది", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} {person1} తో {date}న తీయబడింది", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} {person1} మరియు {person2} తో {date}న తీయబడింది", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} {person1}, {person2}, మరియు {person3} తో {date}న తీయబడింది", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} {person1}, {person2}, మరియు {additionalCount, number} others తో తీసినది {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} {date}న {city}, {country}లో తీయబడింది", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} {date}న {person1} తో {city}, {country} లో తీయబడింది", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} {date}న {person1} మరియు {person2}తో {city}, {country}లో తీయబడింది", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} లో {person1}, {person2}, మరియు {person3} లతో {date}న తీయబడింది", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} లో {person1}, {person2}, మరియు {additionalCount, number} others తో {date}న తీయబడింది", - "immich_logo": "ఇమ్మిచ్ లోగో", - "immich_web_interface": "ఇమ్మిచ్ వెబ్ ఇంటర్‌ఫేస్", - "import_from_json": "JSON నుండి దిగుమతి చేయండి", - "import_path": "దిగుమతి మార్గం", - "in_albums": "{count, plural, one {# album} other {# album}} లో", - "in_archive": "ఆర్కైవ్‌లో ఉంది", - "include_archived": "ఆర్కైవ్ చేసిన వాటిని చేర్చు", - "include_shared_albums": "షేర్ చేసిన ఆల్బమ్‌లను చేర్చండి", - "include_shared_partner_assets": "భాగస్వామ్య భాగస్వామి ఆస్తులను చేర్చండి", - "individual_share": "వ్యక్తిగత వాటా", - "individual_shares": "వ్యక్తిగత షేర్లు", - "info": "సమాచారం", - "interval": { - "day_at_onepm": "ప్రతిరోజు మధ్యాహ్నం 1 గంటలకు", - "hours": "ప్రతి {hours, plural, one {hour} other {{hours, number} గంటలు}}", - "night_at_midnight": "ప్రతి రాత్రి అర్ధరాత్రి", - "night_at_twoam": "ప్రతి రాత్రి 2 గంటలకు" - }, - "invite_people": "వ్యక్తులను ఆహ్వానించండి", - "invite_to_album": "ఆల్బమ్‌కు ఆహ్వానించండి", - "items_count": "{count, plural, one {# అంశం} other {# అంశాలు}}", - "jobs": "ఉద్యోగాలు", - "keep": "ఉంచండి", - "keep_all": "అన్ని ఉంచు", - "keep_this_delete_others": "దీన్ని ఉంచు, మిగతా వాటిని తొలగించు", - "kept_this_deleted_others": "ఈ ఆస్తిని ఉంచుకుని తొలగించారు {count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "కీబోర్డ్ సత్వరమార్గాలు", - "language": "భాష", - "language_setting_description": "మీకు ఇష్టమైన భాషను ఎంచుకోండి", - "last_seen": "ఆఖరి సారిగా చూచింది", - "latest_version": "తాజా వెర్షన్", - "latitude": "అక్షాంశం", - "leave": "వదిలేయ్", - "lens_model": "లెన్స్ మోడల్", - "let_others_respond": "ఇతరులు ప్రతిస్పందించనివ్వండి", - "level": "స్థాయి", - "library": "గ్రంధాలయం", - "library_options": "లైబ్రరీ ఎంపికలు", - "light": "వెలుతురు", - "like_deleted": "ఇస్టం తొలగించబడింది", - "link_motion_video": "మోషన్ వీడియో లింక్ చేయండి", - "link_to_oauth": "OAuth కి లింక్ చేయండి", - "linked_oauth_account": "లింక్ చేయబడిన OAuth ఖాతా", - "list": "జాబితా", - "loading": "లోడ్", - "loading_search_results_failed": "శోధన ఫలితాలను లోడ్ చేయడం విఫలమైంది", - "log_out": "లాగ్ అవుట్", - "log_out_all_devices": "అన్ని పరికరాలను లాగ్ అవుట్ చేయండి", - "logged_out_all_devices": "అన్ని పరికరాలను లాగ్ అవుట్ చేసారు", - "logged_out_device": "పరికరం లాగ్ అవుట్ చేయబడింది", - "login": "లాగిన్", - "login_has_been_disabled": "లాగిన్ నిలిపివేయబడింది.", - "logout_all_device_confirmation": "మీరు ఖచ్చితంగా అన్ని పరికరాల నుండి లాగ్ అవుట్ చేయాలనుకుంటున్నారా?", - "logout_this_device_confirmation": "మీరు ఖచ్చితంగా ఈ పరికరాన్ని లాగ్ అవుట్ చేయాలనుకుంటున్నారా?", - "longitude": "రేఖాంశం", - "look": "చూడు", - "loop_videos": "లూప్ వీడియోలు", - "loop_videos_description": "వివరాల వ్యూయర్‌లో వీడియోను స్వయంచాలకంగా లూప్ చేయడానికి ప్రారంభించండి.", - "main_branch_warning": "మీరు డెవలప్‌మెంట్ వెర్షన్‌ను ఉపయోగిస్తున్నారు; విడుదల వెర్షన్‌ను ఉపయోగించమని మేము గట్టిగా సిఫార్సు చేస్తున్నాము!", - "make": "తయారు చేయండి", - "manage_shared_links": "భాగస్వామ్య లింక్‌లను నిర్వహించండి", - "manage_sharing_with_partners": "భాగస్వాములతో భాగస్వామ్యాన్ని నిర్వహించండి", - "manage_the_app_settings": "యాప్ సెట్టింగ్‌లను నిర్వహించండి", - "manage_your_account": "మీ ఖాతా నిర్వహించుకొనండి", - "manage_your_api_keys": "మీ API కీలను నిర్వహించండి", - "manage_your_devices": "మీ లాగిన్ అయిన పరికరాలను నిర్వహించండి", - "manage_your_oauth_connection": "మీ OAuth కనెక్షన్‌ని నిర్వహించండి", - "map": "మ్యాప్", - "map_marker_for_images": "{city}, {country} లో తీసిన చిత్రాల కోసం మ్యాప్ మార్కర్", - "map_marker_with_image": "చిత్రంతో మ్యాప్ మార్కర్", - "map_settings": "మ్యాప్ సెట్టింగ్‌లు", - "matches": "మ్యాచ్‌లు", - "media_type": "మీడియా రకం", - "memories": "జ్ఞాపకాలు", - "memories_setting_description": "మీ జ్ఞాపకాలలో మీరు చూసే వాటిని నిర్వహించండి", - "memory": "గ్నాపకం", - "memory_lane_title": "మెమరీ లేన్ {title }", - "menu": "మెను", - "merge": "విలీనం", - "merge_people": "వ్యక్తులను విలీనం చేయండి", - "merge_people_limit": "మీరు ఒకేసారి 5 ముఖాలను మాత్రమే విలీనం చేయగలరు", - "merge_people_prompt": "మీరు ఈ వ్యక్తులను విలీనం చేయాలనుకుంటున్నారా? ఈ చర్య తిరుగులేనిది.", - "merge_people_successfully": "వ్యక్తులను విజయవంతంగా విలీనం చేసారు", - "merged_people_count": "విలీనం చేయబడింది {count, plural, one {# person} other {# people}}", - "minimize": "తగ్గించండి", - "minute": "నిమిషం", - "missing": "తప్పిపోయింది", - "model": "మోడల్", - "month": "నెల", - "more": "మరింత", - "moved_to_trash": "ట్రాష్‌కి తరలించబడింది", - "mute_memories": "జ్ఞాపకాలను మ్యూట్ చేయి", - "my_albums": "నా ఆల్బమ్‌లు", - "name": "పేరు", - "name_or_nickname": "పేరు లేదా మారుపేరు", - "never": "ఎప్పుడు కాదు", - "new_album": "కొత్త ఆల్బమ్", - "new_api_key": "కొత్త API కీ", - "new_password": "కొత్త పాస్వర్డ్", - "new_person": "కొత్త వ్యక్తి", - "new_user_created": "కొత్త వినియోగదారి సృష్టించబడ్డారు", - "new_version_available": "కొత్త వెర్షన్ అందుబాటులో ఉంది", - "newest_first": "మొదటిది సరికొత్తది", - "next": "తరువాత", - "next_memory": "తదుపరి జ్ఞాపకం", - "no": "కాదు", - "no_albums_message": "మీ ఫోటోలు మరియు వీడియోలను నిర్వహించడానికి ఆల్బమ్‌ను సృష్టించండి", - "no_albums_with_name_yet": "మీకు ఇంకా ఈ పేరుతో ఆల్బమ్‌లు ఏవీ లేనట్లు కనిపిస్తోంది.", - "no_albums_yet": "మీ వద్ద ఇంకా ఆల్బమ్‌లు ఏవీ లేనట్లు కనిపిస్తోంది.", - "no_archived_assets_message": "మీ ఫోటోల వీక్షణ నుండి వాటిని దాచడానికి ఫోటోలు మరియు వీడియోలను ఆర్కైవ్ చేయండి", - "no_assets_message": "మీ మొదటి ఫోటోను అప్‌లోడ్ చేయడానికి క్లిక్ చేయండి", - "no_duplicates_found": "నకిలీలు ఏవీ కనుగొనబడలేదు.", - "no_exif_info_available": "ఎక్సిఫ్ సమాచారం అందుబాటులో లేదు", - "no_explore_results_message": "మీ సేకరణను అన్వేషించడానికి మరిన్ని ఫోటోలను అప్‌లోడ్ చేయండి.", - "no_favorites_message": "మీ ఉత్తమ చిత్రాలు మరియు వీడియోలను త్వరగా కనుగొనడానికి ఇష్టమైన వాటిని జోడించండి", - "no_libraries_message": "మీ ఫోటోలు మరియు వీడియోలను వీక్షించడానికి బాహ్య లైబ్రరీని సృష్టించండి", - "no_name": "పేరు లేదు", - "no_places": "స్థలాలు లేవు", - "no_results": "ఫలితాలు లేవు", - "no_results_description": "పర్యాయపదం లేదా మరింత సాధారణ కీవర్డ్‌ని ప్రయత్నించండి", - "no_shared_albums_message": "మీ నెట్‌వర్క్‌లోని వ్యక్తులతో ఫోటోలు మరియు వీడియోలను భాగస్వామ్యం చేయడానికి ఆల్బమ్‌ను సృష్టించండి", - "not_in_any_album": "ఏ ఆల్బమ్‌లోనూ లేదు", - "note_apply_storage_label_to_previously_uploaded assets": "గమనిక: గతంలో అప్‌లోడ్ చేసిన ఆస్తులకు నిల్వ లేబుల్‌ను వర్తింపజేయడానికి,", - "notes": "గమనికలు", - "notification_toggle_setting_description": "ఇమెయిల్ నోటిఫికేషన్‌లను ప్రారంభించండి", - "notifications": "నోటిఫికేషన్‌లు", - "notifications_setting_description": "నోటిఫికేషన్‌లను నిర్వహించండి", - "official_immich_resources": "అధికారిక ఇమ్మిచ్ వనరులు", - "offline": "ఆఫ్‌లైన్", - "ok": "సరే", - "oldest_first": "ముందుగా పాతది", - "onboarding": "ఆన్‌బోర్డింగ్", - "onboarding_privacy_description": "కింది (ఐచ్ఛికం) లక్షణాలు బాహ్య సేవలపై ఆధారపడి ఉంటాయి మరియు పరిపాలన సెట్టింగ్‌లలో ఎప్పుడైనా నిలిపివేయబడతాయి.", - "onboarding_theme_description": "మీ ఇన్స్టాన్స్‌ కోసం రంగు థీమ్‌ను ఎంచుకోండి. మీరు దీన్ని తర్వాత మీ సెట్టింగ్‌లలో మార్చవచ్చు.", - "onboarding_welcome_user": "స్వాగతం, {user }", - "online": "ఆన్‌లైన్", - "only_favorites": "ఇష్టమైనవి మాత్రమే", - "open_in_map_view": "మ్యాప్ వీక్షణలో తెరవండి", - "open_in_openstreetmap": "ఓపెన్‌స్ట్రీట్‌మ్యాప్‌లో తెరవండి", - "open_the_search_filters": "శోధన ఫిల్టర్‌లను తెరవండి", - "options": "ఎంపికలు", - "or": "లేదా", - "organize_your_library": "మీ లైబ్రరీని నిర్వహించండి", - "original": "అసలు", - "other": "ఇతర", - "other_devices": "ఇతర పరికరాలు", - "other_variables": "ఇతర వేరియబుల్స్", - "owned": "స్వంతం చేసుకున్నవి", - "owner": "యజమాని", - "partner": "భాగస్వామి", - "partner_can_access": "{partner} యాక్సెస్ చేయగలరు", - "partner_can_access_assets": "ఆర్కైవ్ చేయబడినవి మరియు తొలగించబడినవి తప్ప మీ అన్ని ఫోటోలు మరియు వీడియోలు", - "partner_can_access_location": "మీ ఫోటోలు తీసిన ప్రదేశం", - "partner_sharing": "భాగస్వామి భాగస్వామ్యం", - "partners": "భాగస్వాములు", - "password": "పాస్‌వర్డ్", - "password_does_not_match": "పాస్‌వర్డ్ సరిపోలలేదు", - "password_required": "పాస్‌వర్డ్ అవసరం", - "password_reset_success": "పాస్‌వర్డ్ రీసెట్ విజయవంతమైంది", - "past_durations": { - "days": "గత {days, plural, one {day} other {# days}}", - "hours": "గత {hours, plural, one {hour} other {# hours}}", - "years": "గత {years, plural, one {year} other {# years}}" - }, - "path": "మార్గం", - "pattern": "నమూనా", - "pause": "పాజ్ చేయండి", - "pause_memories": "జ్ఞాపకాలను పాజ్ చేయి", - "paused": "పాజ్ చేయబడింది", - "pending": "పెండింగ్‌లో ఉంది", - "people": "ప్రజలు", - "people_edits_count": "సవరించబడింది {count, plural, one {# person} other {# people}}", - "people_feature_description": "వ్యక్తుల వారీగా సమూహపరచబడిన ఫోటోలు మరియు వీడియోలను బ్రౌజ్ చేయడం", - "people_sidebar_description": "సైడ్‌బార్‌లో వ్యక్తులకు లింక్‌ను ప్రదర్శించండి", - "permanent_deletion_warning": "శాశ్వత తొలగింపు హెచ్చరిక", - "permanent_deletion_warning_setting_description": "ఆస్తులను శాశ్వతంగా తొలగిస్తున్నప్పుడు హెచ్చరికను చూపించు", - "permanently_delete": "శాశ్వతంగా తొలగించు", - "permanently_delete_assets_count": "శాశ్వతంగా తొలగించు {count, plural, one {asset} other {assets}}", - "permanently_delete_assets_prompt": "మీరు {count, plural, one {this asset?} other {these # assets?}}లను శాశ్వతంగా తొలగించాలనుకుంటున్నారా? ఇది {count, plural, one {it from its} other {them from their}} ఆల్బమ్(లు) ను కూడా తొలగిస్తుంది.", - "permanently_deleted_asset": "శాశ్వతంగా తొలగించబడిన ఆస్తి", - "permanently_deleted_assets_count": "శాశ్వతంగా తొలగించబడింది {count, plural, one {# asset} other {# assets}}", - "person": "వ్యక్తి", - "person_birthdate": "{date}న జన్మించారు", - "person_hidden": "{name}{hidden, select, true { (దాయబడింది)} other {}}", - "photo_shared_all_users": "మీరు మీ ఫోటోలను అందరు వినియోగదారులతో పంచుకున్నట్లు కనిపిస్తోంది లేదా మీకు షేర్ చేయడానికి ఎవరూ లేరు.", - "photos": "ఫోటోలు", - "photos_and_videos": "ఫోటోలు & వీడియోలు", - "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} ఫోటోలు}}", - "photos_from_previous_years": "గత సంవత్సరాల ఫోటోలు", - "pick_a_location": "ఒక స్థానాన్ని ఎంచుకోండి", - "place": "స్థలం", - "places": "స్థలాలు", - "play": "ప్లే", - "play_memories": "ప్లే మెమరీస్", - "play_motion_photo": "మోషన్ ఫోటోను ప్లే చేయి", - "play_or_pause_video": "వీడియోను ప్లే చేయండి లేదా పాజ్ చేయండి", - "port": "పోర్ట్", - "preset": "ప్రీసెట్", - "preview": "ప్రివ్యూ", - "previous": "మునుపటి", - "previous_memory": "మునుపటి జ్ఞాపకం", - "previous_or_next_photo": "మునుపటి లేదా తదుపరి ఫోటో", - "primary": "ప్రాథమిక", - "privacy": "గోప్యత", - "profile_image_of_user": "{user} ప్రొఫైల్ ఇమేజ్", - "profile_picture_set": "ప్రొఫైల్ చిత్రం సెట్ చేయబడింది.", - "public_album": "పబ్లిక్ ఆల్బమ్", - "public_share": "పబ్లిక్ షేర్", - "purchase_account_info": "మద్దతుదారు", - "purchase_activated_subtitle": "Immich మరియు ఓపెన్ సోర్స్ సాఫ్ట్‌వేర్‌లకు మద్దతు ఇచ్చినందుకు ధన్యవాదాలు", - "purchase_activated_time": "{date}న యాక్టివేట్ చేయబడింది", - "purchase_activated_title": "మీ కీ విజయవంతంగా యాక్టివేట్ చేయబడింది", - "purchase_button_activate": "యాక్టివేట్ చేయండి", - "purchase_button_buy": "కొను", - "purchase_button_buy_immich": "Immich ని కొను", - "purchase_button_never_show_again": "మళ్ళీ ఎప్పుడూ చూపించవద్దు", - "purchase_button_reminder": "30 రోజుల్లో నాకు గుర్తు చేయి", - "purchase_button_remove_key": "కీని తీసివేయండి", - "purchase_button_select": "ఎంచుకోండి", - "purchase_failed_activation": "యాక్టివేట్ చేయడం విఫలమైంది! దయచేసి సరైన ఉత్పత్తి కీ కోసం మీ ఇమెయిల్‌ను తనిఖీ చేయండి!", - "purchase_individual_description_1": "ఒక వ్యక్తి కోసం", - "purchase_individual_description_2": "మద్దతుదారు స్థితి", - "purchase_individual_title": "వ్యక్తిగత", - "purchase_input_suggestion": "ఉత్పత్తి కీ ఉందా? కింద కీని నమోదు చేయండి", - "purchase_license_subtitle": "సేవ యొక్క నిరంతర అభివృద్ధికి మద్దతు ఇవ్వడానికి Immichను కొనుగోలు చేయండి", - "purchase_lifetime_description": "జీవితకాల కొనుగోలు", - "purchase_option_title": "కొనుగోలు ఎంపికలు", - "purchase_panel_info_1": "Immich ను నిర్మించడానికి చాలా సమయం మరియు కృషి అవసరం, మరియు మేము దానిని సాధ్యమైనంత ఉత్తమంగా చేయడానికి పూర్తి సమయం ఇంజనీర్లు దానిపై పనిచేస్తున్నారు. ఓపెన్-సోర్స్ సాఫ్ట్‌వేర్ మరియు నైతిక వ్యాపార పద్ధతులు డెవలపర్‌లకు స్థిరమైన ఆదాయ వనరుగా మారడం మరియు దోపిడీ క్లౌడ్ సేవలకు నిజమైన ప్రత్యామ్నాయాలతో గోప్యతను గౌరవించే పర్యావరణ వ్యవస్థను సృష్టించడం మా లక్ష్యం.", - "purchase_panel_info_2": "మేము పేవాల్‌లను జోడించకూడదని కట్టుబడి ఉన్నందున, ఈ కొనుగోలు మీకు ఇమ్మిచ్‌లో ఎటువంటి అదనపు ఫీచర్‌లను మంజూరు చేయదు. ఇమ్మిచ్ యొక్క కొనసాగుతున్న అభివృద్ధికి మద్దతు ఇవ్వడానికి మేము మీలాంటి వినియోగదారులపై ఆధారపడతాము.", - "purchase_panel_title": "ప్రాజెక్ట్‌కు మద్దతు ఇవ్వండి", - "purchase_per_server": "ప్రతీ సర్వర్‌కు", - "purchase_per_user": "ప్రతి వినియోగదారునికి", - "purchase_remove_product_key": "ఉత్పత్తి కీని తీసివేయండి", - "purchase_remove_product_key_prompt": "మీరు ఖచ్చితంగా ఉత్పత్తి కీని తీసివేయాలనుకుంటున్నారా?", - "purchase_remove_server_product_key": "సర్వర్ ఉత్పత్తి కీని తీసివేయండి", - "purchase_remove_server_product_key_prompt": "మీరు సర్వర్ ఉత్పత్తి కీని ఖచ్చితంగా తీసివేయాలనుకుంటున్నారా?", - "purchase_server_description_1": "మొత్తం సర్వర్ కోసం", - "purchase_server_description_2": "మద్దతుదారు స్థితి", - "purchase_server_title": "సర్వర్", - "purchase_settings_server_activated": "సర్వర్ ఉత్పత్తి కీని నిర్వాహకుడు నిర్వహిస్తారు", - "rating": "స్టార్ రేటింగ్", - "rating_clear": "రేటింగ్‌ను క్లియర్ చేయి", - "rating_description": "సమాచార ప్యానెల్‌లో EXIF రేటింగ్‌ను ప్రదర్శించండి", - "reaction_options": "ప్రతిచర్య ఎంపికలు", - "read_changelog": "చేంజ్‌లాగ్ చదవండి", - "reassign": "తిరిగి కేటాయించు", - "reassing_hint": "ఎంచుకున్న ఆస్తులను ఇప్పటికే ఉన్న వ్యక్తికి కేటాయించండి", - "recent": "ఇటీవలి", - "recent-albums": "ఇటీవలి ఆల్బమ్‌లు", - "recent_searches": "ఇటీవలి శోధనలు", - "refresh": "రిఫ్రెష్ చేయి", - "refresh_encoded_videos": "ఎన్‌కోడ్ చేసిన వీడియోలను రిఫ్రెష్ చేయండి", - "refresh_faces": "ముఖాలను రిఫ్రెష్ చేయి", - "refresh_metadata": "మెటాడేటాను రిఫ్రెష్ చేయి", - "refresh_thumbnails": "థంబ్‌నెయిల్స్‌ను రిఫ్రెష్ చేయి", - "refreshed": "రిఫ్రెష్ చేయబడింది", - "refreshes_every_file": "ఇప్పటికే ఉన్న మరియు కొత్త అన్ని ఫైళ్ళను తిరిగి చదువుతుంది", - "refreshing_encoded_video": "ఎన్కోడ్ చేసిన వీడియోను రిఫ్రెష్ చేస్తోంది", - "refreshing_faces": "ముఖాలను రిఫ్రెష్ చేస్తోంది", - "refreshing_metadata": "మెటాడేటాను రిఫ్రెష్ చేస్తోంది", - "regenerating_thumbnails": "థంబ్‌నెయిల్‌లను పునరుజ్జీవింపజేస్తోంది", - "remove_assets_title": "ఆస్తులను తీసివేయాలా?", - "remove_custom_date_range": "అనుకూల తేదీ పరిధిని తీసివేయండి", - "remove_deleted_assets": "తొలగించబడిన ఆస్తులను తీసివేయండి", - "remove_from_album": "ఆల్బమ్ నుండి తీసివేయి", - "remove_from_favorites": "ఇష్టమైన వాటి నుండి తీసివేయి", - "remove_from_shared_link": "షేర్ చేసిన లింక్ నుండి తీసివేయండి", - "remove_memory": "మెమరీని తీసివేయండి", - "remove_photo_from_memory": "ఈ జ్ఞాపకం నుండి ఫోటోను తీసివేయండి", - "remove_url": "URL ను తొలగించు", - "remove_user": "వినియోగదారుని తీసివేయి", - "removed_api_key": "తొలగించబడిన API కీ: {name}", - "removed_from_archive": "ఆర్కైవ్ నుండి తీసివేయబడింది", - "removed_from_favorites": "ఇష్టమైనవి నుండి తీసివేయబడింది", - "removed_memory": "మెమరీ తీసివేయబడింది", - "removed_photo_from_memory": "మెమరీ నుండి ఫోటో తీసివేయబడింది", - "rename": "పేరు మార్చండి", - "repair": "మరమ్మత్తు", - "repair_no_results_message": "ట్రాక్ చేయని మరియు తప్పిపోయిన ఫైల్‌లు ఇక్కడ కనిపిస్తాయి", - "replace_with_upload": "అప్‌లోడ్‌తో భర్తీ చేయి", - "repository": "రిపోజిటరీ", - "require_password": "పాస్‌వర్డ్ కావాలి", - "require_user_to_change_password_on_first_login": "మొదటి లాగిన్‌లో యూజర్ పాస్‌వర్డ్ మార్చవలసి ఉంటుంది", - "rescan": "మళ్ళీ స్కాన్ చేయి", - "reset": "తిరిగి నిర్దారించు", - "reset_password": "పాస్‌వర్డ్‌ను రీసెట్ చేయండి", - "reset_people_visibility": "వ్యక్తుల దృశ్యమానతను రీసెట్ చేయండి", - "reset_to_default": "డిఫాల్ట్‌కు రీసెట్ చేయి", - "resolve_duplicates": "నకిలీలను పరిష్కరించండి", - "resolved_all_duplicates": "అన్ని నకిలీలను పరిష్కరించారు", - "restore": "పునరుద్ధరించు", - "restore_all": "అన్నీ పునరుద్ధరించు", - "restore_user": "వినియోగదారుని పునరుద్ధరించు", - "restored_asset": "ఆస్తి పునరుద్ధరించబడింది", - "resume": "పునఃప్రారంభం", - "retry_upload": "అప్‌లోడ్‌ను మళ్లీ ప్రయత్నించండి", - "review_duplicates": "నకిలీలను సమీక్షించండి", - "role": "పాత్ర", - "role_editor": "సవరించగలిగేవారు", - "role_viewer": "చూడగలిగేవారు", - "save": "సేవ్", - "saved_api_key": "సేవ్ చేయబడిన API కీ", - "saved_profile": "సేవ్ చేయబడిన ప్రొఫైల్", - "saved_settings": "సేవ్ చేసిన సెట్టింగ్‌లు", - "say_something": "ఏదైనా చెప్పు", - "scan_all_libraries": "అన్ని లైబ్రరీలను స్కాన్ చేయండి", - "scan_library": "స్కాన్ చేయండి", - "scan_settings": "స్కాన్ సెట్టింగ్‌లు", - "scanning_for_album": "ఆల్బమ్ కోసం స్కాన్ చేస్తోంది...", - "search": "వెతకండి", - "search_albums": "ఆల్బమ్‌లను శోధించు", - "search_by_context": "సందర్భం ఆధారంగా శోధించండి", - "search_by_description": "వివరణ ద్వారా శోధించండి", - "search_by_description_example": "సాపాలో హైకింగ్ డే", - "search_by_filename": "ఫైల్ పేరు లేదా పొడిగింపు ద్వారా శోధించండి", - "search_by_filename_example": "అంటే IMG_1234.JPG లేదా PNG", - "search_camera_make": "కెమెరా తయారీని శోధించండి...", - "search_camera_model": "కెమెరా మోడల్‌ను శోధించండి...", - "search_city": "నగరాన్ని శోధించు...", - "search_country": "దేశాన్ని శోధించు...", - "search_for": "వెతుకు", - "search_for_existing_person": "ఇప్పటికే ఉన్న వ్యక్తి కోసం శోధించండి", - "search_no_people": "వ్యక్తులు లేరు", - "search_no_people_named": "\"{name}\" అనే పేరు గల వ్యక్తులు లేరు", - "search_options": "శోధన ఎంపికలు", - "search_people": "వ్యక్తులను శోధించు", - "search_places": "స్థలాలను శోధించు", - "search_rating": "రేటింగ్ ద్వారా శోధించండి...", - "search_settings": "శోధన సెట్టింగ్‌లు", - "search_state": "శోధన స్థితి...", - "search_tags": "ట్యాగ్‌లను శోధించు...", - "search_timezone": "సమయ మండలిని శోధించు...", - "search_type": "శోధన రకం", - "search_your_photos": "మీ ఫోటోలను శోధించండి", - "searching_locales": "స్థానిక ప్రదేశాలను శోధిస్తోంది...", - "second": "రెండవది", - "see_all_people": "అందరు వ్యక్తులను చూడండి", - "select": "ఎంచుకోండి", - "select_album_cover": "ఆల్బమ్ కవర్‌ను ఎంచుకోండి", - "select_all": "అన్నీ ఎంచుకోండి", - "select_all_duplicates": "అన్ని నకిలీలను ఎంచుకోండి", - "select_avatar_color": "అవతార్ రంగును ఎంచుకోండి", - "select_face": "ముఖాన్ని ఎంచుకోండి", - "select_featured_photo": "ఫీచర్ చేయబడిన ఫోటోను ఎంచుకోండి", - "select_from_computer": "కంప్యూటర్ నుండి ఎంచుకోండి", - "select_keep_all": "అన్నీ ఉంచు ఎంచుకోండి", - "select_library_owner": "లైబ్రరీ యజమానిని ఎంచుకోండి", - "select_new_face": "కొత్త ముఖాన్ని ఎంచుకోండి", - "select_photos": "ఫోటోలను ఎంచుకోండి", - "select_trash_all": "అన్నీ చెత్తకు ఎంచుకోండి", - "selected": "ఎంచుకున్నారు", - "send_message": "సందేశం పంపండి", - "send_welcome_email": "స్వాగత ఇమెయిల్ పంపండి", - "server_offline": "సర్వర్ ఆఫ్‌లైన్", - "server_online": "సర్వర్ ఆన్‌లైన్", - "server_stats": "సర్వర్ గణాంకాలు", - "server_version": "సర్వర్ వెర్షన్", - "set": "సెట్", - "set_as_album_cover": "ఆల్బమ్ కవర్‌గా సెట్ చేయి", - "set_as_featured_photo": "ఫీచర్ చేయబడిన ఫోటోగా సెట్ చేయి", - "set_as_profile_picture": "ప్రొఫైల్ చిత్రంగా సెట్ చేయి", - "set_date_of_birth": "పుట్టిన తేదీని సెట్ చేయండి", - "unsaved_change": "సేవ్ చేయని మార్పు", - "unselect_all": "ఎంచుకున్నవన్నీ తొలగించు", - "unselect_all_duplicates": "అన్ని నకిలీల ఎంపికను తీసివేయండి", - "unstack": "అన్-స్టాక్", - "up_next": "తదుపరి", - "updated_password": "నవీకరించబడిన పాస్‌వర్డ్", - "upload": "అప్‌లోడ్", - "upload_concurrency": "కాన్కరెన్సీని అప్‌లోడ్", - "upload_status_duplicates": "నకిలీలు", - "upload_status_errors": "లోపాలు", - "upload_status_uploaded": "అప్‌లోడ్ చేయబడింది", - "upload_success": "అప్‌లోడ్ విజయవంతమైంది, కొత్త అప్‌లోడ్ ఆస్తులను చూడటానికి పేజీని రిఫ్రెష్ చేయండి.", - "usage": "వాడుక", - "use_custom_date_range": "బదులుగా అనుకూల తేదీ పరిధిని ఉపయోగించండి", - "user": "విన్యోగధారి", - "user_id": "విన్యోగధారి గుర్తింపు", - "user_purchase_settings": "కొనుగోలు", - "user_purchase_settings_description": "మీ కొనుగోలును నిర్వహించండి", - "user_usage_detail": "వినియోగదారు వినియోగ వివరాలు", - "username": "వినియోగదారి పేరు", - "users": "వినియోగదారులు", - "utilities": "యుటిలిటీస్", - "validate": "ధృవీకరించండి", - "variables": "వేరియబుల్స్", - "video": "వీడియో", - "video_hover_setting": "థంబ్‌నెయిల్ పైనా హోవర్ చేయగానే వీడియో ప్లే చెయ్", - "video_hover_setting_description": "థంబ్‌నెయిల్ పైనా హోవర్ చేయగానే చిహ్నం ప్లే చేయు. నిలిపివేయబడినప్పటికీ, ప్లే చిహ్నంపై హోవర్ చేయడం ద్వారా ప్లేబ్యాక్ ప్రారంభించబడుతుంది.", - "videos": "వీడియోలు", - "view": "చూడండి", - "view_album": "ఆల్బమ్‌ని వీక్షించండి", - "view_all": "అన్నీ వీక్షించండి", - "view_all_users": "వినియోగదారులందరినీ వీక్షించండి", - "view_links": "లింక్‌లను వీక్షించండి", - "view_next_asset": "తదుపరి ఆస్తిని వీక్షించండి", - "view_previous_asset": "మునుపటి ఆస్తిని వీక్షించండి", - "view_stack": "స్టాక్ చూడండి", - "waiting": "వేచి ఉంది", - "warning": "హెచ్చరిక", - "week": "వారం", - "welcome": "స్వాగతం" -} +{} diff --git a/i18n/th.json b/i18n/th.json index abe9b93f19..0967ef424b 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -1,1890 +1 @@ -{ - "about": "เกี่ยวกับ", - "account": "บัญชีผู้ใช้", - "account_settings": "การตั้งค่าบัญชี", - "acknowledge": "รับทราบ", - "action": "ดำเนินการ", - "action_common_update": "อัปเดต", - "actions": "การดำเนินการ", - "active": "ใช้งานอยู่", - "active_count": "ใช้งานอยู่: {count}", - "activity": "กิจกรรม", - "activity_changed": "กิจกรรม{enabled, select, true {เปิด} other {ปิด}}อยู่", - "add": "เพิ่ม", - "add_a_description": "เพิ่มคำอธิบาย", - "add_a_location": "เพิ่มตำแหน่ง", - "add_a_name": "เพิ่มชื่อ", - "add_a_title": "เพิ่มหัวข้อ", - "add_action": "เพิ่มการดำเนินการ", - "add_birthday": "เพิ่มวันเกิด", - "add_endpoint": "เพิ่มปลายทาง", - "add_exclusion_pattern": "เพิ่มข้อยกเว้น", - "add_filter": "เพิ่มตัวกรอง", - "add_location": "เพิ่มตำแหน่ง", - "add_more_users": "เพิ่มผู้ใช้งาน", - "add_partner": "เพิ่มคู่หู", - "add_path": "เพิ่มพาทที่ตั้ง", - "add_photos": "เพิ่มรูปภาพ", - "add_tag": "เพิ่มแท็ก", - "add_to": "เพิ่มไปยัง …", - "add_to_album": "เพิ่มไปยังอัลบั้ม", - "add_to_album_bottom_sheet_added": "เพิ่มไปยัง {album} แล้ว", - "add_to_album_bottom_sheet_already_exists": "อยู่ใน {album} อยู่แล้ว", - "add_to_album_bottom_sheet_some_local_assets": "ไฟล์บางส่วนไม่สามารถเพิ่มไปยังอัลบั้มได้", - "add_to_albums": "เพิ่มเข้าในอัลบั้ม", - "add_to_albums_count": "เพิ่มไปยังอัลบั้ม ({count})", - "add_to_bottom_bar": "เพิ่มไปยัง", - "add_to_shared_album": "เพิ่มไปยังอัลบั้มที่แชร์", - "add_upload_to_stack": "เพิ่มที่อัปโหลดเข้า stack", - "add_url": "เพิ่ม URL", - "added_to_archive": "เพิ่มไปยังที่จัดเก็บถาวร", - "added_to_favorites": "เพิ่มเข้ารายการโปรด", - "added_to_favorites_count": "เพิ่ม {count, number} รูปเข้ารายการโปรดแล้ว", - "admin": { - "add_exclusion_pattern_description": "เพิ่มรูปแบบข้อยกเว้น รองรับการใช้ *, ** และ ? หากต้องการละเว้นไฟล์ทั้งหมดในไดเร็กทอรีที่ชื่อว่า \"Raw\" ให้ใช้ \"**/Raw/**\" ถ้าต้องการละเว้นไฟล์ทั้งหมดที่ลงท้ายด้วย \".tif\" ให้ใช้ \"**/*.tif\" ถ้าต้องการละเว้นพาธที่เริ่มจากไดเรกทอรีบนสุดให้ใช้ \"/พาธ/ที่ต้องการ/ละเว้น/**\"", - "admin_user": "ผู้ดูแล", - "asset_offline_description": "ไม่พบไฟล์สื่อของไลบรารีภายนอกนี้ในดิสก์ และถูกย้ายไปที่ถังขยะแล้ว หากไฟล์ถูกย้ายภายในไลบรารี โปรดตรวจสอบไทม์ไลน์ของคุณเพื่อหาสื่อที่เกี่ยวข้องใหม่ หากต้องการกู้คืนสื่อนี้ โปรดตรวจสอบว่า Immich สามารถเข้าถึงไฟล์ด้านล่างได้ และทำการสแกนไลบรารีอีกครั้ง", - "authentication_settings": "การตั้งค่าการเข้าถึง", - "authentication_settings_description": "จัดการรหัสผ่าน, OAuth, และตั้งค่าการเข้าถึงอื่นๆ", - "authentication_settings_disable_all": "คุณแน่ใจว่าต้องการปิดวิธีการล็อกอินทั้งหมดหรือไม่? ล็อกอินจะถูกปิดทั้งหมด", - "authentication_settings_reenable": "เพื่อเปิดใหม่ ให้ใช้คำสั่งเซิร์ฟเวอร์", - "background_task_job": "งานเบื้องหลัง", - "backup_database": "สำรองฐานข้อมูล", - "backup_database_enable_description": "เปิดใช้งานสำรองฐานข้อมูล", - "backup_keep_last_amount": "จำนวนข้อมูลสำรองก่อนหน้าที่ต้องเก็บไว้", - "backup_onboarding_1_description": "สำเนานอกสถานที่บนคลาวด์หรือที่ตั้งอื่น", - "backup_onboarding_2_description": "สำเนาที่อยู่บนเครื่องต่างกัน ซึ่งรวมถึงไฟล์หลักและไฟล์สำรองบนเครื่อง", - "backup_onboarding_3_description": "จำนวนชุดของข้อมูลทั้งหมด รวมถึงไฟล์เดิม ซึ่งรวมถึง 1 ชุดที่ตั้งอยู่คนละถิ่น และสำเนาบนเครื่อง 2 ชุด", - "backup_onboarding_description": "แนะนำให้ใช้ การสำรองข้อมูลแบบ 3-2-1เพื่อปกป้องข้อมูล ควรเก็บสำเนาของรูปภาพ/วิดีโอที่อัปโหลดและฐานข้อมูลของ Immich เพื่อสำรองข้อมูลได้อย่างทั่วถึง", - "backup_onboarding_footer": "สำหรับข้อมูลเพิ่มเติมที่เกี่ยวกับการสำรองข้อมูลของ Immich โปรดดูที่ documentation", - "backup_onboarding_parts_title": "การสำรองข้อมูลแบบ 3-2-1 ประกอบไปด้วย:", - "backup_onboarding_title": "สำรองข้อมูล", - "backup_settings": "ตั้งค่าการสำรองข้อมูล", - "backup_settings_description": "จัดการการตั้งค่าการสำรองฐานข้อมูล", - "cleared_jobs": "เคลียร์งานสำหรับ: {job}", - "config_set_by_file": "การตั้งค่าคอนฟิกกำลังถูกกำหนดโดยไฟล์คอนฟิก", - "confirm_delete_library": "คุณแน่ใจว่าอยากลบคลังภาพ {library} หรือไม่?", - "confirm_delete_library_assets": "คุณแน่ใจว่าอยากลบคลังภาพนี้หรือไม่? สี่อทั้งหมด {count, plural, one {# สื่อ} other {all # สื่อ}} สี่อในคลังจะถูกลบออกจาก Immich โดยถาวร ไฟล์จะยังคงอยู่บนดิสก์", - "confirm_email_below": "โปรดยืนยัน โดยการพิมพ์ \"{email}\" ข้างล่าง", - "confirm_reprocess_all_faces": "คุณแน่ใจว่าคุณต้องการประมวลผลใบหน้าทั้งหมดใหม่? ชื่อคนจะถูกลบไปด้วย", - "confirm_user_password_reset": "คุณแน่ใจว่าต้องการรีเซ็ตรหัสผ่านของ {user} หรือไม่?", - "confirm_user_pin_code_reset": "คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตรหัส PIN ของ {user}", - "create_job": "สร้างงาน", - "cron_expression": "รูปแบบ cron", - "cron_expression_description": "ตั้งช่วงเวลาในการสแกนโดยใช้รูปแบบ cron สำหรับข้อมูลเพิ่มเติมกรุณาอิง Crontab Guru", - "cron_expression_presets": "พรีเซ็ตรูปแบบ cron", - "disable_login": "ปิดการล็อกอิน", - "duplicate_detection_job_description": "ใช้ machine learning กับสี่อเพื่อตรวจจับรูปภาพที่คล้ายกัน โดยใช้การค้นหาอัจฉริยะ", - "exclusion_pattern_description": "ข้อยกเว้นสามารถละเว้นไฟล์และโฟลเดอร์ขณะสแกนคลังภาพของคุณ มีประโยชน์เมื่อโฟลเดอร์มีไฟล์ที่ไม่อยากนำเข้า เช่นไฟล์ RAW", - "external_libraries_page_description": "หน้าต่างคลังแอดมินภายนอก", - "face_detection": "การตรวจจับใบหน้า", - "face_detection_description": "ตรวจจับใบหน้าในสี่อโดยใช้ machine learning วิดีโอจะใช้ภาพตัวอย่างจากวิดีโอเท่านั้น \"ทั้งหมด\" จะประมวลผลสี่อทั้งหมด \"ขาดหาย\" จะประมวลผลสี่อที่ยังไม่ได้ประมวลผล ใบหน้าที่ถูกตรวจจับแล้วจะถูกเข้าคิวประมวลผลการจดจำใบหน้า เพิ่มเข้าไปในกลุ่มที่มีอยู่แล้วหรือคนใหม่", - "facial_recognition_job_description": "นำใบหน้าที่ตรวจจับได้ไปจับกลุ่มตามผู้คน ขั้นตอนนี้ทำงานหลังจากตรวจจับใบหน้าสำเร็จ \"ทั้งหมด\" จะจำกลุ่มใบหน้าทั้งหมดใหม่ \"ขาดหาย\" จะจัดคิวใบหน้าที่ยังไม่ได้ระบุคน", - "failed_job_command": "คำสั่ง {command} ของงาน {job} ล้มเหลว", - "force_delete_user_warning": "คําเตือน: ขั้นตอนนี้จะลบผู้ใช้งานและสื่อทั้งหมดทันที ไม่สามารถย้อนกลับมาได้และกู้คืนไฟล์ไม่ได้", - "image_format": "Format", - "image_format_description": "WebP จะให้ไฟล์ที่เล็กกว่า JPEG แต่ใช้เวลาแปลงไฟล์นานกว่า", - "image_fullsize_description": "รูปภาพขนาดเต็มที่ถูกถอดข้อมูล metadata ออก ใช้ในขณะทำการขยายรูปภาพดู", - "image_fullsize_enabled": "เปิดใช้งานการสร้างรูปภาพขนาดเต็ม", - "image_fullsize_enabled_description": "สร้างรูปภาพเต็มขนาดสำหรับรูปแบบที่ไม่เข้ากันกับเว็บ เมื่อเปิดใช้งาน \"ถ้าต้องการดูตัวอย่าง\" ตัวอย่างแบบฝังจะถูกใช้โดยตรงโดยตรง และไม่ส่งผลต่อรูปแบบไม่เข้ากันกับเว็บ เช่น JPEG", - "image_fullsize_quality_description": "คุณภาพรูปภาพขนาดเต็มจาก 1-100 ค่ายิ่งสูงคุณภาพยิ่งสูง แต่แลกมาด้วยขนาดไฟล์ที่ใหญ่ขึ้น", - "image_fullsize_title": "ตั้งค่ารูปภาพขนาดเต็ม", - "image_prefer_embedded_preview": "ใช้พรีวิวแบบฝังตัว", - "image_prefer_embedded_preview_setting_description": "ใช้การแสดงภาพแบบฝังตัวในรูปภาพ RAW ในการวิเคราะห์รูปภาพหากสามารถใช้ได้ สิ่งนี้จะช่วยให้รูปภาพมีสีสันที่ถูกต้องมากยิ่งขึ้น แต่อย่างไรก็ตาม คุณภาพรูปภาพขึ้นอยู่กับกล้องถ่ายรูป และอาจจะเกิดร่องรอย ลาย ที่ไม่พึงประสงค์บนรูปภาพ จากการย่อขนาดไฟล์", - "image_prefer_wide_gamut": "ใช้ช่วงสีกว้าง", - "image_prefer_wide_gamut_setting_description": "ใช้ Display P3 สำหรับภาพตัวอย่าง (thumbnails) เพื่อรักษาความสดใสของภาพที่มีช่วงสีที่กว้างขึ้น อย่างไรก็ตาม ภาพอาจแสดงผลแตกต่างกันบนอุปกรณ์เก่าที่ใช้เว็บเบราว์เซอร์เวอร์ชันเก่า สำหรับภาพที่อยู่ใน sRGB จะยังคงใช้ sRGB ต่อไปเพื่อหลีกเลี่ยงการเปลี่ยนแปลงของสี", - "image_preview_description": "ภาพขนาดปานกลางที่ถูกลบข้อมูลเมตา ใช้สำหรับการดูแอสเซ็ตเดี่ยวและสำหรับการเรียนรู้ของเครื่อง (Machine Learning)", - "image_preview_quality_description": "คุณภาพการแสดงตัวอย่างตั้งแต่ 1-100 ยิ่งสูงยิ่งดี แต่จะทำให้ไฟล์มีขนาดใหญ่ขึ้นและอาจทำให้แอปตอบสนองช้าลง การตั้งค่าต่ำอาจส่งผลต่อคุณภาพ Machine Learning", - "image_preview_title": "ตั้งค่าพรีวิว", - "image_quality": "คุณภาพ", - "image_resolution": "ความละเอียด", - "image_resolution_description": "ความละเอียดสูกว่าสามารถเก็บรายละเอียดได้มากกว่าแต่ใช้เวลา encode นานกว่า ไฟล์ใหญ่กว่า และลดความตอบสนองของแอป", - "image_settings": "การตั้งค่ารูปภาพ", - "image_settings_description": "จัดการคุณภาพและความคมชัดของภาพที่สร้างขึ้น", - "image_thumbnail_description": "รูปขนาดย่อที่มีการลบข้อมูลเมตาด้าต้า ใช้เมื่อดูภาพถ่ายในกลุ่ม เช่น ในไทม์ไลน์หลัก", - "image_thumbnail_quality_description": "คุณภาพของภาพขนาดย่อตั้งแต่ 1-100 ยิ่งสูงยิ่งดี แต่จะทำให้ไฟล์มีขนาดใหญ่ขึ้นและอาจทำให้แอปตอบสนองช้าลง", - "image_thumbnail_title": "ตั้งค่า Thumbnail", - "job_concurrency": "{job} งานพร้อมกัน", - "job_created": "สร้างงานเรียบร้อย", - "job_not_concurrency_safe": "งานนี้ทำงานพร้อมกันแบบปลอดภัยไม่ได้", - "job_settings": "การตั้งค่างาน", - "job_settings_description": "จัดการการทำหลายงานพร้อมกัน", - "jobs_delayed": "{jobCount, plural, other {# ล่าช้า}}", - "jobs_failed": "{jobCount, plural, other {# ล้มเหลว}}", - "library_created": "สร้างคลังภาพ: {library}", - "library_deleted": "คลังภาพถูกลบ", - "library_details": "รายละเอียดคลังภาพ", - "library_folder_description": "เลือกโฟล์เดอร์เพื่อนำเข้า โฟลเดอร์นี้ รวมถึงโฟลเดอร์ย่อยจะถูกสแกนเพื่อรูปภาพและวิดีโอ", - "library_remove_exclusion_pattern_prompt": "คุณแน่ใจว่าต้องการลบรูปแบบข้อยกเว้นนี้ออกหรือไม่?", - "library_remove_folder_prompt": "คุณแน่ใจว่าต้องการลบโฟล์เดอร์นำเข้านี้หรือไม่?", - "library_scanning": "การสแกนเป็นระยะ", - "library_scanning_description": "ตั้งค่าการสแกนคลังภาพเป็นระยะ", - "library_scanning_enable_description": "เปิดการสแกนคลังภาพเป็นระยะ", - "library_settings": "คลังภาพภายนอก", - "library_settings_description": "จัดการการตั้งค่าคลังภาพภายนอก", - "library_tasks_description": "สแกนคลังภาพภายนอกสำหรับทรัพยากรใหม่และ/หรือที่เปลี่ยนแปลง", - "library_updated": "คลังภาพถูกอัปเดต", - "library_watching_enable_description": "ดูคลังภาพภายนอกสำหรับการเปลี่ยนแปลงของไฟล์", - "library_watching_settings": "การดูคลังภาพ [ฟีเจอร์ทดลอง]", - "library_watching_settings_description": "หาไฟล์ที่เปลี่ยนแปลงโดยอัตโนมัติ", - "logging_enable_description": "เปิดการบันทึก", - "logging_level_description": "เมื่อเปิดใช้งาน ใช้ระดับการบันทึกอะไร", - "logging_settings": "การบันทึก", - "machine_learning_availability_checks_description": "ตรวจจับและเลือกใช้เซิร์ฟเวอร์ machine learning โดยอัตโนมัติ", - "machine_learning_clip_model": "โมเดล Clip", - "machine_learning_clip_model_description": "ชื่อของโมเดล CLIP ที่ระบุตรงนี้ โปรดทราบว่าจำเป็นต้องดำเนินงาน 'ค้นหาอัจฉริยะ' ใหม่สำหรับทุกรูปเมื่อเปลี่ยนโมเดล", - "machine_learning_duplicate_detection": "ตรวจจับการซ้ำกัน", - "machine_learning_duplicate_detection_enabled": "เปิดใช้งานการตรวจจับการซ้ำ", - "machine_learning_duplicate_detection_enabled_description": "หากปิดใช้งาน สื่อที่เหมือนกันจะยังถูกลบออก", - "machine_learning_duplicate_detection_setting_description": "ใช้ CLIP เพื่อแสดงที่มีแนวโน้มซ้ํา", - "machine_learning_enabled": "เปิดใช้ machine learning", - "machine_learning_enabled_description": "หากปิดใช้งาน คุณสมบัติ ML ทั้งหมดจะปิดการใช้งานโดยไม่คํานึงถึงการตั้งค่าด้านล่าง.", - "machine_learning_facial_recognition": "การจดจำใบหน้า", - "machine_learning_facial_recognition_description": "ตรวจจับ จดจำ และจำแนกใบหน้าในภาพ", - "machine_learning_facial_recognition_model": "โมเดลสำหรับการจดจำใบหน้า", - "machine_learning_facial_recognition_model_description": "โมเดลเรียงตามขนาดลดหลั่นลงมา โมเดลที่ใหญ่กว่าจะประมวลผลช้ากว่าและใช้หน่วยความจำมากกว่า แต่ให้ผลลัพธ์ที่ดีขึ้น หมายเหตุไว้ว่าเมื่อเปลี่ยนโมเดล คุณต้องรันงานตรวจจับใบหน้าทุกภาพใหม่ทั้งหมด", - "machine_learning_facial_recognition_setting": "เปิดใช้การจดจําใบหน้า", - "machine_learning_facial_recognition_setting_description": "หากปิดใช้งาน จะไม่มีการจดจำใบหน้าบนรูปภาพและจะไม่มีส่วนผู้คนในหน้าเว็บ", - "machine_learning_max_detection_distance": "ระยะทางการตรวจจับสูงสุด", - "machine_learning_max_detection_distance_description": "ระยะห่างระหว่างสองภาพที่ไกลสุดที่ถือว่าเป็นภาพซ้ำ ค่าระหว่าง 0.001-0.1 ค่ายิ่งสูงจะยิ่งเจอภาพซ้ำมากขึ้น แต่อาจมีผลผิดพลาด", - "machine_learning_max_recognition_distance": "ระยะทางการจดจำสูงสุด", - "machine_learning_max_recognition_distance_description": "ความต่างของใบหน้าสูงสุดระหว่างสองใบหน้าที่จะจัดว่าเป็นคนเดียวกัน ระหว่าง 0-2 การทำค่านี้ต่ำลงสามารถป้องกันไม่ให้สองคนถูกจัดเป็นคนเดียวกัน ในขณะที่การทำค่านี้สูงขึ้นสามารถป้องกันไม่ให้คนเดียวถูกจัดเป็นคนต่างกัน", - "machine_learning_min_detection_score": "คะแนนตรวจจับขั้นต่ำ", - "machine_learning_min_detection_score_description": "ค่าความมั่นใจในการตรวจจับใบหน้า จาก 0-1 ค่ายิ่งต่ำจะยิ่งตรวจจับใบหน้ามากขึ้น แต่อาจมีผลผิดพลาด", - "machine_learning_min_recognized_faces": "จดจำใบหน้าขั้นต่ำ", - "machine_learning_min_recognized_faces_description": "จำนวนใบหน้าขั้นต่ำที่จะสร้างคนขึ้นมา การเพิ่มค่านี้จะทำให้การจดจำใบหน้าแม่นยำกว่าแต่เพิ่มโอกาสที่ใบหน้าจะไม่ถูกมอบหมายให้กับบุคคล", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "ใช้ machine learning ตรวจจับข้อความในภาพ", - "machine_learning_ocr_enabled": "เปิดใช้งาน OCR", - "machine_learning_ocr_enabled_description": "ถ้าปิด ภาพจะไม่ถูกนำเข้ากระบวนการตรวจจับข้อความ", - "machine_learning_ocr_max_resolution": "ความละเอียดสูงสุด", - "machine_learning_ocr_max_resolution_description": "Preview ที่มีความละเอียดสูงกว่าค่านี้จะถูกปรับขนาดโดยคงอัตราส่วนของภาพไว้ ค่าที่สูงจะทำให้มีความแม่นยำมากขึ้นแต่จะใช้เวลาประมวลผลและหน่วยความจำมากขึ้น", - "machine_learning_ocr_min_detection_score": "คะแนนการตรวจจับต่ำสุด", - "machine_learning_ocr_min_detection_score_description": "คะแนนการตรวจจับต่ำสุดที่ข้อความจะถูกตรวจจับ ค่าระหว่าง 0-1 ค่าที่ต่ำจะทำให้ข้อความถูกตรวจจับมากขึ้นแต่อาจทำให้ผลบวกปลอมมากขึ้น", - "machine_learning_ocr_model": "โมเดล OCR", - "machine_learning_settings": "การตั้งค่า machine learning", - "machine_learning_settings_description": "จัดการการตั้งค่า machine learning", - "machine_learning_smart_search": "การค้นหาอัจฉริยะ", - "machine_learning_smart_search_description": "ค้นหาภาพโดยใช้ความหมายจากการใช้ CLIP", - "machine_learning_smart_search_enabled": "เปิดใช้งานการค้นหาอัจฉริยะ", - "machine_learning_smart_search_enabled_description": "หากปิดใช้งาน ภาพจะไม่ถูกใช้สําหรับการค้นหาอัจฉริยะ", - "machine_learning_url_description": "URL ของเซิร์ฟเวอร์ machine learning กรณีมี URL มากกว่าหนึ่ง URL จะทำการทดลองส่งข้อมูลเรียงไปทีละอันตามลำดับจนกว่าจะพบ URL ที่ตอบสนอง และจะเลิกส่งข้อมูลชั่วคราวในส่วนของ URL ที่ไม่ตอบสนอง", - "manage_concurrency": "จัดการการทำงานพร้อมกัน", - "manage_log_settings": "จัดการการตั้งค่าจดบันทึก", - "map_dark_style": "แบบมืด", - "map_enable_description": "เปิดใช้งานแผนที่", - "map_gps_settings": "การตั้งค่าแผนที่และ GPS", - "map_gps_settings_description": "จัดการการตั้งค่าแผนที่และ GPS (Reverse Geocoding)", - "map_implications": "ฟีเจอร์แผนที่ต้องการบริการแผ่นแผนที่จากภายนอก (tiles.immich.cloud)", - "map_light_style": "แบบสว่าง", - "map_manage_reverse_geocoding_settings": "จัดการการตั้งค่าแปลงพิกัดภูมิศาสตร์ ", - "map_reverse_geocoding": "ประมวลผลชื่อทางภูมิศาสตร์", - "map_reverse_geocoding_enable_description": "เปิดใช้งานประมวลผลชื่อทางภูมิศาสตร์", - "map_reverse_geocoding_settings": "การตั้งค่าประมวลผลชื่อทางภูมิศาสตร์", - "map_settings": "การตั้งค่าแผนที่และ GPS", - "map_settings_description": "จัดการการตั้งค่าแผนที่", - "map_style_description": "URL ไปยังธีมแผนที่ style.json", - "memory_cleanup_job": "ล้างข้อมูลในหน่วยความจำ (memory)", - "memory_generate_job": "การสร้างความทรงจำ", - "metadata_extraction_job": "ดึงข้อมูล metadata", - "metadata_extraction_job_description": "ดึงข้อมูล metadata จากสื่อ เช่น GPS และความคมชัด", - "metadata_faces_import_setting": "เปิดการนำเข้าข้อมูลใบหน้า", - "metadata_faces_import_setting_description": "นำเข้าข้อมูลใบหน้าจาก EXIF ของไฟล์ภาพและไฟล์ประกอบ", - "metadata_settings": "การตั้งค่า Metadata", - "metadata_settings_description": "จัดการการตั้งค่า Metadata", - "migration_job": "การโยกย้าย", - "migration_job_description": "ย้ายภาพตัวอย่างสื่อและใบหน้าไปยังโครงสร้างโฟลเดอร์ล่าสุด", - "nightly_tasks_cluster_new_faces_setting": "คลัสเตอร์ใบหน้าใหม่", - "nightly_tasks_generate_memories_setting": "สร้างความทรงจำ", - "no_paths_added": "ไม่ได้เพิ่มพาธ", - "no_pattern_added": "ไม่ได้เพิ่มรูปแบบ", - "note_apply_storage_label_previous_assets": "หากต้องการใช้ Storage Label กับไฟล์ที่อัปโหลดก่อนหน้านี้ ให้รันคำสั่งนี้", - "note_cannot_be_changed_later": "หมายเหตุ: ไม่สามารถเปลี่ยนภายหลังได้!", - "notification_email_from_address": "จากที่อยู่", - "notification_email_from_address_description": "ที่อยู่อีเมลผู้ส่ง ตัวอย่าง \"Immich Photo Server \" (กรุณายีนยันว่าสามารถส่งเมลจากที่อยู่นี้ได้)", - "notification_email_host_description": "ที่อยู่เซิร์ฟเวอร์อีเมล (เช่น smtp.immich.app)", - "notification_email_ignore_certificate_errors": "ไม่สนใจข้อผิดพลาดเกี่ยวกับใบรับรอง", - "notification_email_ignore_certificate_errors_description": "ไม่สนใจการยืนยันใบรับรอง TLS ผิดพลาด (ไม่แนะนำ)", - "notification_email_password_description": "รหัสผ่านที่ใช้เมื่อเข้าถึงเซิร์ฟเวอร์อีเมล", - "notification_email_port_description": "พอร์ตของเซิร์ฟเวอร์อีเมล (เช่น 25, 465, หรือ 587)", - "notification_email_sent_test_email_button": "ส่งอีเมลทดลองและบันทึก", - "notification_email_setting_description": "การตั้งค่าสำหรับการส่งการแจ้งเตือนอีเมล", - "notification_email_test_email": "ส่งอีเมลทดลอง", - "notification_email_test_email_failed": "ส่งอีเมลทดลองล้มเหลว โปรดตรวจสอบค่าที่ตั้งไว้", - "notification_email_test_email_sent": "อีเมลทดลองถูกส่งไปยัง {email} กรุณาตรวจสอบกล่องจดหมาย", - "notification_email_username_description": "ชื่อผู้ใช้งานเมื่อเข้าถึงเซิร์ฟเวอร์อีเมล", - "notification_enable_email_notifications": "เปิดการแจ้งเตือนผ่านอีเมล", - "notification_settings": "การตั้งค่าการแจ้งเตือน", - "notification_settings_description": "จัดการการตั้งค่าการแจ้งเตือน รวมถึงอีเมล", - "oauth_auto_launch": "เปิดอัตโนมัติ", - "oauth_auto_launch_description": "เริ่มขั้นตอนการล็อกอิน OAuth โดยอัตโนมัติเมื่อถึงหน้าล็อกอิน", - "oauth_auto_register": "ลงทะเบียนอัตโนมัติ", - "oauth_auto_register_description": "ลงทะเบียนผู้ใช้งานใหม่อัตโนมัติเมื่อล็อกอินผ่าน OAuth", - "oauth_button_text": "ข้อความปุ่มกด", - "oauth_client_secret_description": "จำเป็นต้องระบุหากผู้ให้บริการ OAuth ไม่รองรับ PKCE (Proof Key for Code Exchange)", - "oauth_enable_description": "ล็อกอินผ่าน OAuth", - "oauth_mobile_redirect_uri": "URI เปลี่ยนเส้นทางบนโทรศัพท์", - "oauth_mobile_redirect_uri_override": "แทนที่ URI เปลี่ยนเส้นทางบนโทรศัพท์", - "oauth_mobile_redirect_uri_override_description": "เปิดเมื่อผู้ให้บริการ OAuth ไม่อนุญาต URI เช่น \"{callback}\"", - "oauth_settings": "OAuth", - "oauth_settings_description": "จัดการการตั้งค่าล็อกอินผ่าน OAuth", - "oauth_settings_more_details": "สำหรับรายละเอียดเพิ่มเติม ให้อ้างถึงเอกสาร", - "oauth_storage_label_claim": "สิทธิ์ที่ใช้อ้างถึงป้ายกำกับการจัดเก็บ", - "oauth_storage_label_claim_description": "ตั้งป้ายกำกับการจัดเก็บของผู้ใช้งานตามสิทธิ์ที่ใช้อ้างถึงโดยอัตโนมัติ", - "oauth_storage_quota_claim": "สิทธิ์ที่ใช้อ้างถึงโควต้าพื้นที่จัดเก็บ", - "oauth_storage_quota_claim_description": "ตั้งโควต้าพื้นที่จัดเก็บของผู้ใช้งานตามสิทธิ์ที่ใช้อ้างถึงโดยอัตโนมัติ", - "oauth_storage_quota_default": "โควต้าพื้นที่เก็บข้อมูลเริ่มต้น (GiB)", - "oauth_storage_quota_default_description": "โควต้าในหน่วย GiB ที่จะใช้เมื่อไม่มีการอ้างสิทธิ์", - "oauth_timeout": "หมดเวลาการร้องขอ", - "oauth_timeout_description": "ระยะเวลาหมดเวลาสำหรับการร้องขอ (หน่วยเป็นมิลลิวินาที)", - "password_enable_description": "ล็อกอินกับอีเมลและรหัสผ่าน", - "password_settings": "ล็อกอินผ่านรหัสผ่าน", - "password_settings_description": "จัดการการตั้งค่าของการล็อกอินผ่านรหัสผ่าน", - "paths_validated_successfully": "เส้นทางทั้งหมดถูกตรวจสอบสำเร็จแล้ว", - "person_cleanup_job": "การทำความสะอาด", - "quota_size_gib": "โควตา (GiB)", - "refreshing_all_libraries": "รีเฟรชคลังภาพทั้งหมด", - "registration": "ลงทะเบียนผู้จัดการ", - "registration_description": "เนื่องจากคุณเป็นผู้ใช้งานแรกของระบบ คุณจะถูกแต่งตั้งเป็นผู้จัดการและรับผิดชอบงานบริหาร ผู้ใช้งานเพิ่มเติมจะถูกสร้างโดยคุณ", - "require_password_change_on_login": "บังคับผู้ใช้งานให้เปลี่ยนรหัสผ่านเมื่อเข้าสู่ระบบครั้งแรก", - "reset_settings_to_default": "ตั้งค่าการตั้งค่าเป็นค่าเริ่มต้น", - "reset_settings_to_recent_saved": "ตั้งค่าการตั้งค่าเป็นค่าล่าสุด", - "scanning_library": "กำลังสแกนคลัง", - "search_jobs": "ค้นหางาน…", - "send_welcome_email": "ส่งอีเมลต้อนรับ", - "server_external_domain_settings": "โดเมนภายนอก", - "server_external_domain_settings_description": "โดเมนสำหรับลิงก์แชร์สาธารณะ แบบมี http(s)://", - "server_public_users": "ผู้ใช้สาธารณะ", - "server_public_users_description": "ผู้ใช้ทั้งหมด (ชื่อและอีเมล) จะแสดงรายการเมื่อเพิ่มผู้ใช้ไปยังอัลบั้มที่แชร์ เมื่อปิดใช้งาน รายชื่อผู้ใช้จะพร้อมใช้งานสำหรับผู้ใช้ที่เป็นผู้ดูแลระบบเท่านั้น", - "server_settings": "การตั้งค่าเซิร์ฟเวอร์", - "server_settings_description": "จัดการการตั้งค่าเซิร์ฟเวอร์", - "server_welcome_message": "ข้อความต้อนรับ", - "server_welcome_message_description": "ข้อความที่แสดงบนหน้าล็อกอิน", - "sidecar_job": "ไฟล์ metadata", - "sidecar_job_description": "ค้นหาหรือซิงค์ไฟล์ metadata จากระบบ", - "slideshow_duration_description": "จำนวนวินาทีที่จะแสดงแต่ละรูปภาพ", - "smart_search_job_description": "เรียกใช้ machine learning บนสื่อเพื่อรองรับการค้นหาอัจฉริยะ", - "storage_template_date_time_description": "เวลาประทับบนสื่อถูกใช้สำหรับข้อมูลวันเวลา", - "storage_template_date_time_sample": "ตัวอย่างเวลา {date}", - "storage_template_enable_description": "เปิดใช้งานการจัดเทมเพลตที่เก็บข้อมูล", - "storage_template_hash_verification_enabled": "ตรวจสอบ hash ไม่ผ่าน", - "storage_template_hash_verification_enabled_description": "เปิดใช้งานการตรวจสอบ hash ห้ามปิดใช้งานเว้นแต่คุณจะเข้าใจผลกระทบ", - "storage_template_migration": "การย้ายเทมเพลตที่เก็บข้อมูล", - "storage_template_migration_description": "ใช้{template}ปัจจุบันกับสื่อที่อัปโหลดก่อนหน้านี้", - "storage_template_migration_info": "เทมเพลตของการจัดเก็บข้อมูลจะเปลี่ยนตัวอักษรเป็นตัวพิมพ์เล็กทั้งหมด การเปลี่ยนแปลงเทมเพลตจะมีผลกับแอสเซ็ตใหม่เท่านั้น หากต้องการนำเทมเพลตไปใช้กับ Asset ที่อัปโหลดก่อนหน้านี้ ให้รัน {job}.", - "storage_template_migration_job": "เทมเพลตการ Migration ข้อมูล", - "storage_template_more_details": "สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ โปรดดูที่ Storage Template และ ผลกระทบ", - "storage_template_onboarding_description_v2": "เมื่อเปิด ฟีเจอร์จะจัดเก็บข้อมูลตามเทมเพลตที่ผู้ใช้กำหนด อ่านเพิ่มเติม", - "storage_template_path_length": "ขีดจำกัดของความยาวพาธโดยประมาณ: {length, number}/{limit, number}", - "storage_template_settings": "เทมเพลตการจัดเก็บข้อมูล", - "storage_template_settings_description": "จัดการโครงสร้างโฟลเดอร์และชื่อไฟล์ที่อัปโหลด", - "storage_template_user_label": "{label} ป้ายกำกับพื้นที่จักเก็บข้อมูล", - "system_settings": "การตั้งค่าระบบ", - "tag_cleanup_job": "เคลียร์ Tags", - "template_email_available_tags": "คุณสามารถใช้ตัวแปรต่อไปนี้ในเทมเพลตของคุณได้: {tags}", - "template_email_if_empty": "หากเทมเพลตว่างเปล่า ระบบจะใช้อีเมลเริ่มต้น", - "template_email_invite_album": "เทมเพลตเชิญเข้าอัลบั้ม", - "template_email_preview": "ตัวอย่าง", - "template_email_settings": "อีเมลเท็มเพลต", - "template_email_update_album": "อัปเดตเทมเพลตอัลบั้ม", - "template_email_welcome": "เทมเพลตสำหรับอีเมลต้อนรับ", - "template_settings": "เทมเพลตการแจ้งเตือน", - "template_settings_description": "ปรับแต่งเทมเพลตการแจ้งเตือน", - "theme_custom_css_settings": "CSS กําหนดเอง", - "theme_custom_css_settings_description": "Cascading Style Sheets ช่วยให้ปรับแต่งเค้าโครง Immich ได้", - "theme_settings": "การตั้งค่าธีม", - "theme_settings_description": "จัดการการปรับแต่งหน้าเว็บ Immich", - "thumbnail_generation_job": "สร้างภาพตัวอย่าง", - "thumbnail_generation_job_description": "สร้างภาพตัวอย่างขนาดใหญ่ ขนาดเล็กและแบบเบลอ สําหรับแต่ละสื่อและบุคคล", - "transcoding_acceleration_api": "API เร่งความเร็วแปลงสื่อ", - "transcoding_acceleration_api_description": "API ที่จะโต้ตอบกับอุปกรณ์ของคุณเพื่อเร่งการแปลงรหัส การตั้งค่านี้คือ 'ความพยายามที่ดีที่สุด': มันจะย้อนกลับไปยังการการแปลงรหัสของซอฟต์แวร์เมื่อเกิดความล้มเหลว VP9 อาจทำงานหรือไม่ทำงานก็ได้ ขึ้นอยู่กับฮาร์ดแวร์ของคุณ", - "transcoding_acceleration_nvenc": "NVENC (ต้องมีการ์ดจอ NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (ต้องมี Intel CPU รุ่นที่ 7 หรือใหม่กว่า)", - "transcoding_acceleration_rkmpp": "RKMPP (สำหรับ Rockchip SOCs เท่านั้น)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "แบบไฟล์เสียงที่ยอมรับ", - "transcoding_accepted_audio_codecs_description": "เลือกแบบไฟล์เสียงที่จะไม่ถูกแปลงใหม่ ใช้สําหรับกฏการแปลงแบบไฟล์", - "transcoding_accepted_containers": "Containers ที่ยอมรับ", - "transcoding_accepted_containers_description": "เลือกรูปแบบคอนเทนเนอร์ที่ไม่จำเป็นต้องรีมิกซ์เป็น MP4 ใช้สำหรับนโยบายการแปลงบางอย่างเท่านั้น", - "transcoding_accepted_video_codecs": "แบบไฟล์วิดีโอที่ยอมรับ", - "transcoding_accepted_video_codecs_description": "เลือกแบบไฟล์วิดีโอที่จะไม่ถูกแปลงใหม่ ใช้สําหรับกฎการแปลงแบบไฟล์", - "transcoding_advanced_options_description": "ตัวเลือกที่ผู้ใช้งานส่วนใหญ่ไม่จำเป็นต้องเปลี่ยน", - "transcoding_audio_codec": "แบบไฟล์เสียง", - "transcoding_audio_codec_description": "Opus ให้คุณภาพสูงสุด แต่อาจจะเข้ากันไม่ได้กับอุปกรณ์เก่าหรือซอฟต์แวร์เก่า", - "transcoding_bitrate_description": "วิดีโอมีค่า bitrate สูงกว่าค่าสูงสุดหรือไฟล์วิดีโอไม่รองรับ", - "transcoding_codecs_learn_more": "หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับคำศัพท์ที่ใช้ที่นี่ โปรดดูเอกสารประกอบของ FFmpeg สำหรับ H.264 codec, HEVC codec และ VP9 codec", - "transcoding_constant_quality_mode": "โหมดคุณภาพคงที่", - "transcoding_constant_quality_mode_description": "ICQ ดีกว่า CQP แต่อุปกรณ์บางตัวอาจจะไม่รองรับโหมดนี้ การตั้งค่าตัวนี้จะเลือกโหมดที่ระบุไว้เมื่อใช้การแปลงคุณภาพไฟล์ ไม่สนใจ NVENC เพราะไม่รองรับ ICQ", - "transcoding_constant_rate_factor": "ตัวแปรค่าคงที่ (-crf)", - "transcoding_constant_rate_factor_description": "คุณภาพของวิดีโอ ค่าโดยปกติคือ 23 สําหรับ H.264, 28 สําหรับ HEVC, 31 สําหรับ VP9 และ 35 สําหรับ AV1 ค่าต่ำกว่าคุณภาพจะดีกว่า แต่ไฟล์จะขนาดใหญ่กว่า", - "transcoding_disabled_description": "ไม่แปลงไฟล์วิดีโอเลย อาจเล่นวิดีโอในเครื่องเล่นบางตัวไม่ได้", - "transcoding_encoding_options": "ตัวเลือกการเข้ารหัส", - "transcoding_encoding_options_description": "ตั้งค่า codecs, ความละเอียด, คุณภาพ (และตัวเลือกอื่นๆ สำหรับวิดีโอที่เข้ารหัส (encoded videos)", - "transcoding_hardware_acceleration": "การเร่งความเร็วด้วยฮาร์ดแวร์", - "transcoding_hardware_acceleration_description": "คุณสมบัติทดลอง: การแปลงรหัสที่เร็วขึ้น แต่อาจทำให้คุณภาพลดลงที่บิตเรตเท่าเดิม", - "transcoding_hardware_decoding": "การถอดรหัสด้วยฮาร์ดแวร์", - "transcoding_hardware_decoding_setting_description": "เปิดใช้งานการเร่งความเร็วแบบทั้งหมด แทนการเร่งความเร็วการเข้ารหัสเพียงอย่างเดียว อาจใช้ไม่ได้กับวิดีโอทั้งหมด", - "transcoding_max_b_frames": "B-frames สูงสุด", - "transcoding_max_b_frames_description": "ค่าที่สูงขึ้นจะช่วยเพิ่มประสิทธิภาพในการบีบอัด แต่จะทำให้การเข้ารหัสช้าลง อาจไม่สามารถใช้งานร่วมกับการเร่งความเร็วฮาร์ดแวร์บนอุปกรณ์เก่าได้ ค่าที่เป็น 0 จะปิดการใช้งาน B-frame ในขณะที่ค่า -1 จะตั้งค่าค่านี้โดยอัตโนมัติ", - "transcoding_max_bitrate": "bitrate สูงสุด", - "transcoding_max_bitrate_description": "การตั้งค่า bitrate สูงสุดจะสามารถคาดเดาขนาดไฟล์ได้มากขึ้นโดยไม่กระทบคุณภาพ สำหรับความคมชัด 720p ค่าทั่วไปคือ 2600 kbit/s สําหรับ VP9 หรือ HEVC, 4500 kbit/s สําหรับ H.264 ปิดการตั้งค่าเมี่อตั้งค่าเป็น 0", - "transcoding_max_keyframe_interval": "ช่วงเวลาสูงสุดระหว่างกราฟฟ์เคลื่อนไหว", - "transcoding_max_keyframe_interval_description": "ตั้งค่าระยะห่างสูงสุดระหว่างคีย์เฟรม (keyframes) ค่าที่ต่ำลงจะทำให้ประสิทธิภาพการบีบอัดแย่ลง แต่จะช่วยปรับปรุงเวลาในการค้นหาภาพ (seek times) และอาจช่วยปรับปรุงคุณภาพในฉากที่มีการเคลื่อนไหวเร็ว ค่า 0 จะตั้งค่านี้โดยอัตโนมัติ", - "transcoding_optimal_description": "วีดิโอมีความคมชัดสูงกว่าเป้าหมายหรืออยู่ในรูปแบบที่รับไม่ได้", - "transcoding_policy": "นโยบายการเข้ารหัส", - "transcoding_policy_description": "ตั้งค่าเวลาที่วิดีโอจะถูกแปลงรหัส", - "transcoding_preferred_hardware_device": "อุปกรณ์ฮาร์ดแวร์ที่ต้องการ", - "transcoding_preferred_hardware_device_description": "ใช้ได้กับ VAAPI และ QSV เท่านั้น ตั้งค่าโหนด dri ที่ใช้สำหรับทรานส์โค้ดฮาร์ดแวร์", - "transcoding_preset_preset": "พรีเซ็ต (-preset)", - "transcoding_preset_preset_description": "ความเร็วในการบีบอัด พรีเซ็ตที่ช้ากว่าจะสร้างไฟล์ที่มีขนาดเล็กลงและเพิ่มคุณภาพเมื่อกำหนดเป้าหมายที่อัตราบิตเรตที่กำหนด VP9 จะไม่สนใจความเร็วที่สูงกว่า 'เร็วกว่า'", - "transcoding_reference_frames": "frames อ้างอิง", - "transcoding_reference_frames_description": "จำนวนเฟรมที่จะอ้างอิงเมื่อบีบอัดเฟรมที่กำหนด ค่าที่สูงขึ้นจะช่วยเพิ่มประสิทธิภาพในการบีบอัด แต่จะทำให้การเข้ารหัสช้าลง ค่า 0 จะตั้งค่านี้โดยอัตโนมัติ", - "transcoding_required_description": "เฉพาะวิดีโอที่ไม่อยู่ในรูปแบบที่ยอมรับเท่านั้น", - "transcoding_settings": "การตั้งค่าการแปลงไฟล์วิดีโอ", - "transcoding_settings_description": "จัดการว่าวีดีโอไหนจะถูกแปลงการเข้ารหัส (Transcode) และวิธีการประมวลผลไฟล์ดังกล่าว", - "transcoding_target_resolution": "เป้าหมายความคมชัด", - "transcoding_target_resolution_description": "ความคมชัดที่สูงกว่าจะเก็บรายละเอียดดีกว่าแต่ใช้เวลาแปลงไฟล์นานกว่า ขนาดไฟล์ใหญ่กว่า และลดการตอบสนองของแอป", - "transcoding_temporal_aq": "AQ ชั่วคราว", - "transcoding_temporal_aq_description": "เฉพาะ NVENC เท่านั้น เพิ่มคุณภาพของฉากที่มีรายละเอียดสูงและการเคลื่อนไหวต่ำ อาจไม่รองรับอุปกรณ์ที่เก่ากว่า", - "transcoding_threads": "เธรด", - "transcoding_threads_description": "ค่ายิ่งเยอะจะแปลงไฟล์เร็วกว่า แต่จะเหลือพื้นที่ให้เซิร์ฟเวอร์ประมวลผลงานอื่นน้อยลงเมื่อทํางานนี้ ค่านี้ไม่ควรมากกว่าจํานวน CPU core จะประมวลผลเต็มที่เมื่อตั้งเป็น 0", - "transcoding_tone_mapping": "การฉายโทนสี", - "transcoding_tone_mapping_description": "พยายามรักษารูปแบบวิดีโอ HDR เมื่อแปลงเป็น SDR อัลกอริทึมแต่ละตัวจะปรับสี,รายละเอียด และความสว่างแตกต่างกัน Hable จะรักษารายละเอียด Mobius จะรักษาสี และ Reinhard จะรักษาความสว่าง", - "transcoding_transcode_policy": "กฎการแปลงไฟล์", - "transcoding_transcode_policy_description": "นโยบายเกี่ยวกับเวลาที่วิดีโอจะต้องได้รับการแปลงรหัส วิดีโอ HDR จะได้รับการแปลงรหัสเสมอ (ยกเว้นในกรณีที่ปิดใช้งานการแปลงรหัส)", - "transcoding_two_pass_encoding": "การแปลงไฟล์สองรอบ", - "transcoding_two_pass_encoding_setting_description": "การแปลงไฟล์สองรอบจะช่วยให้ได้วิดีโอที่ดีขึ้น เมื่อเปิดใช้งาน bitrate สูงสุด (จำเป็นสำหรับไฟล์ H.264 และ HEVC) โหมดนี้จะใช้ช่วง bitrate ที่ขึ้นอยู่กับค่า bitrate สูงสุดและไม่สนใจ CRF สำหรับ VP9 สามารถใช้ค่า CRF ได้ถ้าปิดใช้งาน bitrate สูงสุด", - "transcoding_video_codec": "แบบไฟล์วิดีโอ", - "transcoding_video_codec_description": "VP9 มีประสิทธิภาพสูงและเข้ากันกับเว็บได้ดี แต่ใช้เวลาแปลงไฟล์นานกว่า HEVC มีประสิทธิภาพคล้ายกัน แต่เข้ากันกับเว็บได้น้อยกว่า H.264 เข้ากันกับทุกอุปกรณ์ และแปลงไฟล์เร็ว แต่ได้ไฟล์ที่ใหญ่ขึ้น AV1 เป็นไฟล์ที่มีประสิทธิภาพมากที่สุด แต่ไม่เข้ากันกับอุปกรณ์เก่า", - "trash_enabled_description": "เปิดใช้งานถังขยะ", - "trash_number_of_days": "จํานวนวัน", - "trash_number_of_days_description": "จํานวนวันที่เก็บสื่อไว้ในถังขยะก่อนที่จะลบถาวร", - "trash_settings": "การตั้งค่าถังขยะ", - "trash_settings_description": "จัดการการตั้งค่าถังขยะ", - "user_cleanup_job": "ล้างผู้ใช้", - "user_delete_delay": "บัญชีและสื่อของ {user} จะถูกตั้งเวลาสำหรับการลบถาวรใน {delay, plural, one {# วัน} other {# วัน}}", - "user_delete_delay_settings": "ลบการถ่วงเวลา", - "user_delete_delay_settings_description": "จํานวนวันหลังจากที่เอาออกเพื่อลบบัญชีผู้ใช้และสื่อถาวร งานลบบัญชีผู้ใช้ทํางานทุกเที่ยงคืนเพื่อตรวจสอบผู้ใช้ที่พร้อมที่จะถูกลบข้อมูลแล้ว การตั้งค่าครั้งนี้จะมีผลครั้งต่อไป", - "user_delete_immediately": "บัญชีและสื่อของ {user} จะอยู่ในคิวสำหรับการลบถาวร โดยทันที", - "user_delete_immediately_checkbox": "คิวผู้ใช้และสื่อสำหรับการลบทันที", - "user_details": "รายละเอียดผู้ใช้", - "user_management": "การจัดการผู้ใช้", - "user_password_has_been_reset": "รหัสผ่านของผู้ใช้ถูกตั้งค่าใหม่แล้ว:", - "user_password_reset_description": "รหัสผ่านของผู้ใช้จะถูกตั้งค่าใหม่และส่งไปยังอีเมลที่ลงทะเบียน", - "user_restore_description": "บัญชีของ {user} จะได้รับการคืนค่า", - "user_restore_scheduled_removal": "กู้คืนผู้ใช้ - กำหนดการลบในวันที่ {date, date,long}", - "user_settings": "การตั้งค่าผู้ใช้", - "user_settings_description": "จัดการการตั้งค่าผู้ใช้", - "version_check_enabled_description": "เช็ค GitHub เป็นระยะ ๆ เพื่อตรวจสอบรุ่นใหม่", - "version_check_implications": "การตรวจสอบเวอร์ชันใหม่จะต้องติดต่อกับ github.com เป็นระยะ", - "version_check_settings": "ตรวจสอบรุ่น", - "version_check_settings_description": "เปิด/ปิดการแจ้งเตือนรุ่นใหม่", - "video_conversion_job": "เข้ารหัสวีดีโอ (transcode)", - "video_conversion_job_description": "แปลงไฟล์วิดีโอเพึ่อรองรับบราวเซอร์และเครื่องเล่นอื่น ๆ มากขึ้น" - }, - "admin_email": "อีเมลผู้ดูแลระบบ", - "admin_password": "รหัสผ่านผู้ดูแลระบบ", - "administration": "การดูแลระบบ", - "advanced": "ขั้นสูง", - "advanced_settings_enable_alternate_media_filter_subtitle": "ใช้ตัวเลือกนี้เพื่อกรองสื่อระหว่างการซิงก์โดยอิงตามเงื่อนไขทางเลือก แนะนำให้ใช้เฉพาะเมื่อแอปมีปัญหาในการตรวจจับอัลบั้มทั้งหมด", - "advanced_settings_enable_alternate_media_filter_title": "[คุณสมบัติทดลอง] ใช้ตัวกรองการซิงก์อัลบั้มของอุปกรณ์แบบทางเลือก", - "advanced_settings_log_level_title": "ระดับการ Log: {level}", - "advanced_settings_prefer_remote_subtitle": "อุปกรณ์บางเครื่องโหลดรูปย่อช้ามาก เปิดการตั้งค่านี้เพื่อโหลดรูปภาพจากรีโมทแทน", - "advanced_settings_prefer_remote_title": "ให้ความสำคัญกับรูปภาพรีโมท", - "advanced_settings_proxy_headers_subtitle": "กำหนด proxy headers ที่ Immich ควรส่งพร้อมกับแต่ละคำขอเครือข่าย", - "advanced_settings_proxy_headers_title": "พ็อกซี่ เฮดเดอร์", - "advanced_settings_self_signed_ssl_subtitle": "ข้ามการตรวจสอบใบรับรอง SSL จำเป็นสำหรับใบรับรองแบบ self-signed", - "advanced_settings_self_signed_ssl_title": "อนุญาตใบรับรอง SSL แบบ self-signed", - "advanced_settings_sync_remote_deletions_subtitle": "บหรือกู้คืนไฟล์บนอุปกรณ์นี้โดยอัตโนมัติเมื่อดำเนินการดังกล่าวผ่านเว็บ", - "advanced_settings_sync_remote_deletions_title": "ซิงก์การลบจากระยะไกล [คุณสมบัติทดลอง]", - "advanced_settings_tile_subtitle": "ตั้งค่าผู้ใช้งานขั้นสูง", - "advanced_settings_troubleshooting_subtitle": "เปิดฟีเจอร์เพิ่มเติมเพื่อแก้ไขปัญหา", - "advanced_settings_troubleshooting_title": "แก้ไขปัญหา", - "age_months": "อายุ {months, plural, one {# เดือน} other {# เดือน}}", - "age_year_months": "อายุ 1 ปี {months, plural, one {# เดือน} other {# เดือน}}", - "age_years": "{years, plural, other {อายุ #}}", - "album_added": "เพิ่มอัลบั้มแล้ว", - "album_added_notification_setting_description": "แจ้งเตือนอีเมลเมื่อคุณถูกเพิ่มไปในอัลบั้มที่แชร์กัน", - "album_cover_updated": "อัพเดทหน้าปกอัลบั้มแล้ว", - "album_delete_confirmation": "คุณแน่ใจที่จะลบอัลบั้ม {album} นี้ ?", - "album_delete_confirmation_description": "หากแชร์อัลบั้มนี้ ผู้ใช้รายอื่นจะไม่สามารถเข้าถึงได้อีก", - "album_info_card_backup_album_excluded": "ถูกยกเว้น", - "album_info_card_backup_album_included": "รวม", - "album_info_updated": "อัปเดทข้อมูลอัลบั้มแล้ว", - "album_leave": "ออกจากอัลบั้ม ?", - "album_leave_confirmation": "คุณต้องการออกจากอัลบั้ม {album} ใช่หรือไม่?", - "album_name": "ชื่ออัลบั้ม", - "album_options": "ตัวเลือกอัลบั้ม", - "album_remove_user": "ลบผู้ใช้ ?", - "album_remove_user_confirmation": "คุณต้องการที่จะลบผู้ใช้ {user} ?", - "album_share_no_users": "ดูเหมือนว่าคุณได้แชร์อัลบั้มนี้กับผู้ใช้ทั้งหมดแล้ว", - "album_updated": "อัปเดทอัลบั้มแล้ว", - "album_updated_setting_description": "แจ้งเตือนอีเมลเมื่ออัลบั้มที่แชร์กันมีสื่อใหม่", - "album_user_left": "ออกจาก {album}", - "album_user_removed": "ลบผู้ใช้ {user} แล้ว", - "album_viewer_appbar_delete_confirm": "คุณแน่ใจว่าอยากลบอัลบั้มนี้จากบัญชีคุณหรือไม่", - "album_viewer_appbar_share_err_delete": "ลบอัลบั้มไม่สำเร็จ", - "album_viewer_appbar_share_err_leave": "ออกจากอัลบั้มไม่สำเร็จ", - "album_viewer_appbar_share_err_remove": "มีปัญหาในการนำทรัพยากรออกจากอัลบั้ม", - "album_viewer_appbar_share_err_title": "เปลี่ยนชื่ออัลบั้มไม่สำเร็จ", - "album_viewer_appbar_share_leave": "ออกจากอัลบั้ม", - "album_viewer_appbar_share_to": "แชร์ให้", - "album_viewer_page_share_add_users": "เพิ่มผู้ใช้งาน", - "album_with_link_access": "อนุญาตให้ทุกคนที่มีลิงก์สามารถดูรูปภาพและผู้คนที่อยู่ในอัลบั้มนี้", - "albums": "อัลบั้ม", - "albums_count": "{count, plural, one {{count, number} อัลบั้ม} other {{count, number} อัลบั้ม}}", - "albums_default_sort_order": "การจัดเรียงอัลบั้มเริ่มต้น", - "albums_default_sort_order_description": "การจัดเรียงแอสเซ็ตเริ่มต้นเมื่อสร้างอัลบั้มใหม่", - "albums_feature_description": "กลุ่มของแอสเซ็ตที่สามารถส่งให้ผู้ใช้อื่นได้", - "all": "ทั้งหมด", - "all_albums": "อัลบั้มทั้งหมด", - "all_people": "ทุกคน", - "all_videos": "วิดีโอทั้งหมด", - "allow_dark_mode": "อนุญาตโหมดมืด", - "allow_edits": "อนุญาตให้แก้ไขได้", - "allow_public_user_to_download": "อนุญาตให้ผู้ใช้สาธารณะดาวน์โหลดได้", - "allow_public_user_to_upload": "อนุญาตให้ผู้ใช้สาธารณะอัปโหลดได้", - "alt_text_qr_code": "รูปภาพ QR code", - "always_keep_photos_hint": "\"เพิ่มพื้นที่ว่าง\" จะเก็บรูปภาพทั้งหมดบนอุปกรณ์นี้", - "always_keep_videos_hint": "\"เพิ่มพื้นที่ว่าง\" จะเก็บวิดีโอทั้งหมดบนอุปกรณ์นี้", - "anti_clockwise": "ทวนเข็มนาฬิกา", - "api_key": "API key", - "api_key_description": "ค่านี้จะแสดงเพียงครั้งเดียว โปรดคัดลอกก่อนปิดหน้าต่าง", - "api_key_empty": "ชื่อ API Key ของคุณไม่ควรว่างเปล่า", - "api_keys": "API Key", - "app_bar_signout_dialog_content": "คุณแน่ใจว่าอยากออกจากระบบ", - "app_bar_signout_dialog_ok": "ใช่", - "app_bar_signout_dialog_title": "ออกจากระบบ", - "app_settings": "การตั้งค่าแอป", - "appears_in": "อยู่ใน", - "archive": "เก็บถาวร", - "archive_or_unarchive_photo": "เก็บ/ไม่เก็บภาพถาวร", - "archive_page_no_archived_assets": "ไม่พบทรัพยากรในที่เก็บถาวร", - "archive_page_title": "เก็บถาวร ({count})", - "archive_size": "ขนาดเก็บถาวร", - "archive_size_description": "ตั้งค่าขนาดสูงสุดสำหรับการดาวน์โหลด (GiB)", - "archived": "เก็บถาวรแล้ว", - "archived_count": "{count, plural, other {เก็บถาวร # รายการ}}", - "are_these_the_same_person": "เป็นบุคคลเดียวกันหรือไม่?", - "are_you_sure_to_do_this": "คุณแน่ใจว่าต้องการทำสิ่งนี้หรือไม่?", - "asset_action_delete_err_read_only": "ไม่สามารถลบทรัพยากรแบบอ่านอย่างเดียวได้ กำลังข้าม", - "asset_action_share_err_offline": "ไม่สามารถดึงข้อมูลทรัพยากรออฟไลน์ กำลังข้าม", - "asset_added_to_album": "เพิ่มไปยังอัลบั้มแล้ว", - "asset_adding_to_album": "กำลังเพิ่มไปยังอัลบั้ม…", - "asset_description_updated": "อัปเดตรายละเอียดสำเร็จ", - "asset_filename_is_offline": "สื่อ {filename} ออฟไลน์อยู่", - "asset_has_unassigned_faces": "สื่อไม่ได้ระบุใบหน้า", - "asset_hashing": "กำลังแฮช…", - "asset_list_group_by_sub_title": "กลุ่มโดย", - "asset_list_layout_settings_dynamic_layout_title": "แผนผังปรับตัว", - "asset_list_layout_settings_group_automatically": "อัตโนมัติ", - "asset_list_layout_settings_group_by": "จัดกลุ่มทรัพยากรโดย", - "asset_list_layout_settings_group_by_month_day": "เดือน + วัน", - "asset_list_layout_sub_title": "การจัดวาง", - "asset_list_settings_subtitle": "ตั้งค่าการจัดวางตารางรูปภาพ", - "asset_list_settings_title": "ตารางรูปภาพ", - "asset_offline": "สื่อออฟไลน์", - "asset_offline_description": "ไม่พบทรัพยากรภายนอกนี้ในดิสก์อีกต่อไป โปรดติดต่อผู้ดูแลระบบ Immich ของคุณเพื่อขอความช่วยเหลือ", - "asset_restored_successfully": "กู้คืนสื่อสำเร็จ", - "asset_skipped": "ข้ามแล้ว", - "asset_skipped_in_trash": "ในถังขยะ", - "asset_uploaded": "อัปโหลดแล้ว", - "asset_uploading": "กำลังอัปโหลด…", - "asset_viewer_settings_subtitle": "ตั้งค่าการแสดงแกลเลอรี", - "asset_viewer_settings_title": "ตัวดูทรัพยากร", - "assets": "สื่อ", - "assets_added_count": "เพิ่ม {count, plural, one{# สื่อ} other {# สื่อ}} แล้ว", - "assets_added_to_album_count": "เพิ่ม {count, plural, one {# asset} other {# assets}} ไปยังอัลบั้ม", - "assets_cannot_be_added_to_album_count": "ไม่สามารถเพิ่ม {count, plural, one {สื่อ} other {สื่อ}} ไปยังอัลบั้ม", - "assets_count": "{count, plural, one { สื่อ} other { สื่อ}}", - "assets_deleted_permanently": "{count} สื่อถูกลบอย่างถาวร", - "assets_deleted_permanently_from_server": "ลบ {count} สื่อออกจาก Immich อย่างถาวร", - "assets_downloaded_failed": "ดาวน์โหลด {count, plural, one {ไฟล์} other {ไฟล์}} ไม่สำเร็จ - {error}", - "assets_downloaded_successfully": "ดาวน์โหลด {count, plural, one {ไฟล์} other {ไฟล์}} สำเร็จ", - "assets_moved_to_trash_count": "ย้าย {count, plural, one {# asset} other {# assets}} ไปยังถังขยะแล้ว", - "assets_permanently_deleted_count": "ลบ {count, plural, one {# asset} other {# assets}} ทิ้งถาวร", - "assets_removed_count": "{count, plural, one {# asset} other {# assets}} ถูกลบแล้ว", - "assets_removed_permanently_from_device": "นำ {count} สื่อออกจากอุปกรณ์อย่างถาวร", - "assets_restore_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการกู้คืนสื่อที่ทิ้งทั้งหมด? คุณไม่สามารถย้อนกลับการดำเนินการนี้ได้! โปรดทราบว่าสื่อออฟไลน์ใดๆ ไม่สามารถกู้คืนได้ด้วยวิธีนี้", - "assets_restored_count": "{count, plural, one {# asset} other {# assets}} คืนค่า", - "assets_restored_successfully": "กู้คืน {count} สื่อสำเร็จ", - "assets_trashed": "ย้าย {count} สื่อไปยังถังขยะ", - "assets_trashed_count": "{count, plural, one {# asset} other {# assets}} ถูกลบ", - "assets_trashed_from_server": "ย้าย {count} สื่อจาก Immich ไปยังถังขยะ", - "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} อยู่ในอัลบั้มอยู่แล้ว", - "authorized_devices": "อุปกรณ์ที่ได้รับอนุญาต", - "automatic_endpoint_switching_subtitle": "เชื่อมต่อด้วย LAN ภายในวง Wi-Fi ที่ระบุไว้ และเชื่อมต่อด้วยวิธีอื่นเมื่ออยู่นอก Wi-Fi ที่ระบุไว้", - "automatic_endpoint_switching_title": "สลับ URL อัตโนมัติ", - "autoplay_slideshow": "เล่นสไลด์โชว์", - "back": "กลับ", - "back_close_deselect": "ย้อนกลับ, ปิด, หรือยกเลิกการเลือก", - "background_location_permission": "การอนุญาตระบุตำแหน่งพื้นหลัง", - "background_location_permission_content": "เพื่อที่จะสลับการเชื่อมต่อขณะที่รันในพื้นหลัง Immich ต้องรู้ตำแหน่งที่แม่ยำตลอดเวลา เพื่อจะสามารถอ่านชื่อ Wi-Fi", - "backup": "สำรองข้อมูล", - "backup_album_selection_page_albums_device": "อัลบั้มบนเครื่อง ({count})", - "backup_album_selection_page_albums_tap": "กดเพื่อรวม กดสองครั้งเพื่อยกเว้น", - "backup_album_selection_page_assets_scatter": "ทรัพยาการสามารถกระจายไปในหลายอัลบั้ม ดังนั้นอัลบั้มสามารถถูกรวมหรือยกเว้นในกระบวนการสำรองข้อมูล", - "backup_album_selection_page_select_albums": "เลือกอัลบั้ม", - "backup_album_selection_page_selection_info": "ข้อมูลของที่เลือก", - "backup_album_selection_page_total_assets": "ทรัพยากรทั้งหมด", - "backup_all": "ทั้งหมด", - "backup_background_service_backup_failed_message": "ไม่สามารถสำรองทรัพยากรได้ กำลังลองใหม่...", - "backup_background_service_connection_failed_message": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้ กำลังลองใหม่...", - "backup_background_service_current_upload_notification": "กำลังอัพโหลด {filename}", - "backup_background_service_default_notification": "ตรวจสอบหาทรัพยากรใหม่...", - "backup_background_service_error_title": "สำรองข้อมูลผิดพลาด", - "backup_background_service_in_progress_notification": "กำลังสำรองทรัพยากรของคุณ...", - "backup_background_service_upload_failure_notification": "อัพโหลดล้มเหลว {filename}", - "backup_controller_page_albums": "สำรองข้อมูลอัลบั้ม", - "backup_controller_page_background_app_refresh_disabled_content": "เปิดการดึงข้อมูลแอพอยู่เบื้องหลังโดยการไปที่ ตั้งค่า > ทั่วไป > ดึงข้อมูลแอปอยู่เบื้องหลัง เพื่อใช้การดึงข้อมูลในเบื้องหลัง", - "backup_controller_page_background_app_refresh_disabled_title": "การรีเฟรชแอพในฉากหลังปิด", - "backup_controller_page_background_app_refresh_enable_button_text": "ไปยังการตั้งค่า", - "backup_controller_page_background_battery_info_link": "แสดงให้ฉันเห็น", - "backup_controller_page_background_battery_info_message": "เพื่อประสบการณ์สำรองข้อมูลที่ดีที่สุด กรุณาปิดการตั้งค่าประสิทธิภาพแบตเตอรี่จำกัดกิจกรรมในเบื้องหลังสำหรับ Immich\n\nเนื่องจากการตั้งค่าดังกล่าวเฉพาะเจาะจงสำหรับโทรศัพท์แต่ละเครื่อง กรุณาค้นหาข้อมูลจากผู้ผลิตโทรศัพท์ของคุณ", - "backup_controller_page_background_battery_info_ok": "โอเค", - "backup_controller_page_background_battery_info_title": "ประสิทธิภาพแบตเตอรี่", - "backup_controller_page_background_charging": "ขณะชาร์จอย่างเดียว", - "backup_controller_page_background_configure_error": "ไม่สามารถติดตั้งบริการเบื้องหลัง", - "backup_controller_page_background_delay": "ล่าช้าการลำรองทรัพยากรใหม่: {duration}", - "backup_controller_page_background_description": "เปิดบริการเบื้องหลังเพื่อที่จะสำรองทรัพยากรใหม่โดยที่ไม่จำเป็นต้องเปิดแอป", - "backup_controller_page_background_is_off": "การสำรองข้อมูลอัตโนมัติปิดอยู่", - "backup_controller_page_background_is_on": "การสำรองข้อมูลอัตโนมัติเปิดอยู่", - "backup_controller_page_background_turn_off": "ปิดบริการเบื้องหลัง", - "backup_controller_page_background_turn_on": "เปิดบริการเบื้องหลัง", - "backup_controller_page_background_wifi": "บน Wi-Fi เท่านั้น", - "backup_controller_page_backup": "สำรองข้อมูล", - "backup_controller_page_backup_selected": "ที่เลือก: ", - "backup_controller_page_backup_sub": "รูปภาพและวิดีโอที่สำรองแล้ว", - "backup_controller_page_created": "สร้างเมื่อ: {date}", - "backup_controller_page_desc_backup": "เปิดการสำรองข้อมูลในฉากหน้าเพื่อที่จะอัพโหลดทรัพยากรใหม่ไปยังเซิร์ฟเวอร์เมื่อเปิดแอพ", - "backup_controller_page_excluded": "ยกเว้น: ", - "backup_controller_page_failed": "ล้มเหลว ({count})", - "backup_controller_page_filename": "ชื่อไฟล์: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "ข้อมูลเกี่ยวกับการสำรองข้อมูล", - "backup_controller_page_none_selected": "ไม่มีที่เลือก", - "backup_controller_page_remainder": "ที่เหลือ", - "backup_controller_page_remainder_sub": "รูปภาพและวิดีโอที่เลือกสำรองที่เหลือ", - "backup_controller_page_server_storage": "พื้นที่จัดเก็บเซิร์ฟเวอร์", - "backup_controller_page_start_backup": "เริ่มสำรองข้อมูล", - "backup_controller_page_status_off": "การสำรองข้อมูลในฉากหน้าปิดอยู่", - "backup_controller_page_status_on": "การสำรองข้อมูลในฉากหน้าเปิดอยู่", - "backup_controller_page_storage_format": "{used} จาก {total} ถูกใช้งาน", - "backup_controller_page_to_backup": "อัลบั้มที่จะสำรองข้อมูล", - "backup_controller_page_total_sub": "รูปภาพและวิดีโอที่ไม่ซ้ำทั้งหมดจากอัลบั้มที่เลือก", - "backup_controller_page_turn_off": "ปิดการสำรองข้อมูลในฉากหน้า", - "backup_controller_page_turn_on": "เปิดการสำรองข้อมูลในฉากหน้า", - "backup_controller_page_uploading_file_info": "กำลังอัพโหลดข้อมูลไฟล์", - "backup_err_only_album": "ไม่สามารถนำอัลบั้มสุดท้ายออกได้", - "backup_info_card_assets": "ทรัพยากร", - "backup_manual_cancelled": "ถูกยกเลิก", - "backup_manual_in_progress": "อัปโหลดกำลังดำเนินการอยู่ โปรดลองใหม่ในสักพัก", - "backup_manual_success": "สำเร็จ", - "backup_manual_title": "สถานะอัพโหลด", - "backup_options_page_title": "ตัวเลือกการสำรองข้อมูล", - "backup_setting_subtitle": "ตั้งค่าการอัพโหลดในฉากหน้า และพื้นหลัง", - "backward": "กลับหลัง", - "biometric_auth_enabled": "การพิสูจน์อัตลักษณ์เพื่อยืนยันตัวบุคคลถูกเปิด", - "biometric_locked_out": "การพิสูจน์อัตลักษณ์เพื่อยืนยันตัวบุคคลถูกล็อค", - "biometric_no_options": "ไม่มีตัวเลือกการพิสูจน์อัตลักษณ์เพื่อยืนยันตัวบุคคล", - "biometric_not_available": "ไม่สามารถใช้งานการพิสูจน์อัตลักษณ์เพื่อยืนยันตัวบุคคลได้บนอุปกรณ์นี้", - "birthdate_saved": "บันทึกวันเกิดแล้ว", - "birthdate_set_description": "วันที่เกิดจะนำมาใช้ในการคำนวณอายุของบุคคลนี้ในขณะที่ถ่ายรูป", - "blurred_background": "พื้นหลังแบบเบลอ", - "bugs_and_feature_requests": "รายงานข้อผิดพลาด & ข้อเสนอแนะ", - "build": "สร้าง", - "build_image": "สร้าง Image", - "bulk_delete_duplicates_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการลบ {count, plural, one {# duplicate asset} other {# duplicate asset}} เป็นกลุ่ม การดำเนินการนี้จะเก็บสื่อที่ใหญ่ที่สุดของแต่ละกลุ่มและลบสื่อที่ซ้ำกันทั้งหมดอย่างถาวร คุณไม่สามารถย้อนกลับการดำเนินการนี้ได้!", - "bulk_keep_duplicates_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการเก็บ {count, plural, one {# duplicate asset} other {# duplicate asset}} ไว้ การดำเนินการนี้จะแก้ไขกลุ่มที่ซ้ำกันทั้งหมดโดยไม่ต้องลบสิ่งใดเลย", - "bulk_trash_duplicates_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการลบข้อมูลจำนวนมาก {count, plural, one {# duplicate asset} other {# duplicate asset}} การทำเช่นนี้จะเก็บสื่อที่ใหญ่ที่สุดของแต่ละกลุ่มและลบข้อมูลซ้ำอื่น ๆ ทั้งหมด", - "buy": "ซื้อ Immich", - "cache_settings_clear_cache_button": "ล้างแคช", - "cache_settings_clear_cache_button_title": "ล้างแคชของแอพ จะส่งผลกระทบต่อประสิทธิภาพแอพจนกว่าแคชจะถูกสร้างใหม่", - "cache_settings_duplicated_assets_clear_button": "ล้าง", - "cache_settings_duplicated_assets_subtitle": "รูปภาพและวิดีโอที่ถูกนำเข้าแบล็กลิสต์โดยแอป", - "cache_settings_duplicated_assets_title": "ทรัพยากรที่ซ้ำกัน ({count})", - "cache_settings_statistics_album": "รูปย่อคลังภาพ", - "cache_settings_statistics_full": "รูปภาพเต็ม", - "cache_settings_statistics_shared": "รูปย่ออัลบั้มที่แชร์", - "cache_settings_statistics_thumbnail": "รูปย่อ", - "cache_settings_statistics_title": "การใช้งานแคช", - "cache_settings_subtitle": "ควบคุมพฤติกรรมการแคชของแอปพลิเคชัน Immich", - "cache_settings_tile_subtitle": "ควบคุมพฤติกรรมของที่จัดเก็บในตัวเครื่อง", - "cache_settings_tile_title": "ที่จัดเก็บในตัวเครื่อง", - "cache_settings_title": "ตั้งค่าแคช", - "camera": "กล้อง", - "camera_brand": "ยี่ห้อกล้อง", - "camera_model": "รุ่นกล้อง", - "cancel": "ยกเลิก", - "cancel_search": "ยกเลิกการค้นหา", - "canceled": "ยกเลิก", - "cannot_merge_people": "ไม่สามารถรวมกลุ่มคนได้", - "cannot_undo_this_action": "การกระทำนี้ไม่สามารถย้อนกลับได้!", - "cannot_update_the_description": "ไม่สามารถอัพเดทรายละเอียดได้", - "cast": "แคสต์", - "cast_description": "ตั้งค่าปลายทางแคสต์", - "change_date": "เปลี่ยนวันที่", - "change_description": "แก้ไขคำอธิบาย", - "change_display_order": "เปลี่ยนลำดับการแสดงผล", - "change_expiration_time": "เปลี่ยนเวลาหมดอายุ", - "change_location": "เปลี่ยนตําแหน่ง", - "change_name": "เปลี่ยนชื่อ", - "change_name_successfully": "เปลี่ยนชื่อสำเร็จ", - "change_password": "เปลี่ยนรหัสผ่าน", - "change_password_description": "การเข้าสู่ระบบครั้งแรก จำเป็นจต้องเปลี่ยนรหัสผ่านของคุณเพื่อความปลอดภัย โปรดป้อนรหัสผ่านใหม่ด้านล่าง", - "change_password_form_confirm_password": "ยืนยันรหัสผ่าน", - "change_password_form_description": "สวัสดี {name},\n\nครั้งนี้อาจจะเป็นครั้งแรกที่คุณเข้าสู่ระบบ หรือมีคำขอเพื่อที่จะเปลี่ยนรหัสผ่านของคุI กรุณาเพิ่มรหัสผ่านใหม่ข้างล่าง", - "change_password_form_new_password": "รหัสผ่านใหม่", - "change_password_form_password_mismatch": "รหัสผ่านไม่ตรงกัน", - "change_password_form_reenter_new_password": "กรอกรหัสผ่านใหม่", - "change_pin_code": "เปลี่ยนรหัสประจำตัว (PIN)", - "change_your_password": "เปลี่ยนรหัสผ่านของคุณ", - "changed_visibility_successfully": "เปลี่ยนการมองเห็นเรียบร้อยแล้ว", - "check_corrupt_asset_backup": "ตรวจสอบสำรองสื่อที่ผิดปกติ", - "check_corrupt_asset_backup_button": "ตรวจสอบ", - "check_corrupt_asset_backup_description": "ตรวจสอบเมื่อเชื่อมต่อ Wi-Fi และสื่อทั้งหมดถูกสำรองข้อมูลแล้วเท่านั้น การตรวจสอบอาจใช้เวลาหลายนาที", - "check_logs": "ตรวจสอบบันทึก", - "choose_matching_people_to_merge": "เลือกคนที่ตรงกันเพื่อรวมเข้าด้วยกัน", - "city": "เมือง", - "clear": "ล้าง", - "clear_all": "ล้างทั้งหมด", - "clear_all_recent_searches": "ล้างประวัติการค้นหา", - "clear_message": "ล้างข้อความ", - "clear_value": "ล้างค่า", - "client_cert_dialog_msg_confirm": "เสร็จ", - "client_cert_enter_password": "ใส่รหัสผ่าน", - "client_cert_import": "นำเข้า", - "client_cert_import_success_msg": "นำเข้าใบรับรองสำเร็จ", - "client_cert_invalid_msg": "ใบรับรอง หรือรหัสผ่านไม่ถูกต้อง", - "client_cert_remove_msg": "ลบใบรับรองสำเร็จ", - "client_cert_subtitle": "รองรับเฉพาะ PKCS12 (.p12, .pfx) เท่านั้น การนำเข้า/ลบใบรับรองสามารถทำได้ก่อนล็อคอินเท่านั้น", - "client_cert_title": "ใบรับรอง SSL ไคลเอนต์", - "clockwise": "ตามเข็มนาฬิกา", - "close": "ปิด", - "collapse": "ย่อ", - "collapse_all": "ย่อทั้งหมด", - "color": "สี", - "color_theme": "สีธีม", - "comment_deleted": "ลบความคิดเห็นแล้ว", - "comment_options": "ตัวเลือกความคิดเห็น", - "comments_and_likes": "ความคิดเห็นและการถูกใจ", - "comments_are_disabled": "ความคิดเห็นถูกปิดใช้งาน", - "common_create_new_album": "สร้างอัลบั้มใหม่", - "completed": "สำเร็จ", - "confirm": "ยืนยัน", - "confirm_admin_password": "ยืนยันรหัสผ่านผู้ดูแลระบบ", - "confirm_delete_face": "คุณแน่ใจว่าต้องการลบใบหน้า{name}ออกหรือไม่?", - "confirm_delete_shared_link": "คุณต้องการที่จะลบลิงก์ที่แชร์ใช่หรือไม่ ?", - "confirm_keep_this_delete_others": "จะลบทั้งหมดในรายการ และยกเว้นสื่อนี้หรือไม่ คุณแน่ใจใช่ไหมที่ต้องการดำเนินการต่อ?", - "confirm_new_pin_code": "ยืนยันรหัสประจำตัว (PIN)", - "confirm_password": "ยืนยันรหัสผ่าน", - "confirm_tag_face": "คุณต้องการแท็กใบหน้านี้ด้วยชื่อ {name} หรือไม่", - "confirm_tag_face_unnamed": "คุณต้องการแท็กใบหน้านี้หรือไม่", - "connected_device": "อุปกรณ์ที่เชื่อมต่อแล้ว", - "connected_to": "เชื่อมต่อไปยัง", - "contain": "มีอยู่", - "context": "บริบท", - "continue": "ต่อไป", - "control_bottom_app_bar_create_new_album": "สร้างอัลบั้มใหม่", - "control_bottom_app_bar_delete_from_immich": "ลบจาก Immich", - "control_bottom_app_bar_delete_from_local": "ลบจากเรื่อง", - "control_bottom_app_bar_edit_location": "แก้ไขตำแหน่ง", - "control_bottom_app_bar_edit_time": "แก้ไขวันและเวลา", - "control_bottom_app_bar_share_link": "แชร์ลิงค์", - "control_bottom_app_bar_share_to": "แชร์ให้", - "control_bottom_app_bar_trash_from_immich": "ย้ายเข้าถังขยะ", - "copied_image_to_clipboard": "คัดลอกภาพไปยังคลิปบอร์ดแล้ว", - "copied_to_clipboard": "คัดลอกไปยังคลิปบอร์ดแล้ว!", - "copy_error": "คัดลอกข้อผิดพลาด", - "copy_file_path": "คัดลอกพาธของไฟล์", - "copy_image": "คัดลอกภาพ", - "copy_link": "คัดลอกลิงก์", - "copy_link_to_clipboard": "คัดลอกลิงก์ไปยังคลิปบอร์ด", - "copy_password": "คัดลอกรหัสผ่าน", - "copy_to_clipboard": "คัดลอกไปยังคลิปบอร์ด", - "country": "ประเทศ", - "cover": "ปก", - "covers": "ปก", - "create": "สร้าง", - "create_album": "สร้างอัลบั้ม", - "create_album_page_untitled": "ไม่มีชื่อ", - "create_library": "สร้างคลังภาพ", - "create_link": "สร้างลิงก์", - "create_link_to_share": "สร้างลิงก์เพื่อแชร์", - "create_link_to_share_description": "ผู้ที่มีลิงก์ สามารถดูรูปที่เลือกได้", - "create_new": "สร้างใหม่", - "create_new_person": "สร้างคนใหม่", - "create_new_person_hint": "กำหนดสื่อที่เลือกให้กับคนใหม่", - "create_new_user": "สร้างผู้ใช้งานใหม่", - "create_shared_album_page_share_add_assets": "เพิ่มทรัพยากร", - "create_shared_album_page_share_select_photos": "เลือกรูปภาพ", - "create_tag": "สร้างแท็กใหม่", - "create_tag_description": "สร้างแท็กใหม่ สำหรับแท็กที่ซ้อนกัน โปรดป้อนเส้นทางทั้งหมดของแท็ก รวมถึงเครื่องหมายทับ", - "create_user": "สร้างผู้ใช้", - "created": "สร้างแล้ว", - "created_at": "สร้างเมื่อ", - "crop": "ครอป", - "curated_object_page_title": "สิ่งของ", - "current_device": "อุปกรณ์ปัจจุบัน", - "current_pin_code": "รหัสประจำตัว (PIN) ปัจจุบัน", - "current_server_address": "ที่อยู่เซิร์ฟเวอร์ปัจจุบัน", - "custom_locale": "ปรับภาษาท้องถิ่นเอง", - "custom_locale_description": "ใช้รูปแบบวันที่และตัวเลขจากภาษาและขอบเขต", - "daily_title_text_date": "E dd MMM", - "daily_title_text_date_year": "E dd MMM yyyy", - "dark": "มืด", - "date_after": "วันที่หลังจาก", - "date_and_time": "วันและเวลา", - "date_before": "วันที่ก่อน", - "date_of_birth_saved": "บันทึกวันเกิดเรียบร้อยแล้ว", - "date_range": "ช่วงวันที่", - "day": "วัน", - "deduplicate_all": "รวมเข้าด้วยกันทั้งหมด", - "deduplication_criteria_1": "ขนาดไบต์ของรูปภาพ", - "deduplication_criteria_2": "จำนวนข้อมูล EXIF", - "deduplication_info": "ข้อมูลการขจัดข้อมูลซ้ำซ้อน", - "deduplication_info_description": "เลือกสื่อล่วงหน้าโดยอัตโนมัติและลบรายการซ้ำซ้อนจำนวนมาก เราจะดูที่:", - "default_locale": "ภาษาท้องถิ่นปกติ", - "default_locale_description": "ใช้รูปแบบวันที่และตัวเลขจากเบราว์เซอร์ของคุณ", - "delete": "ลบออก", - "delete_album": "ลบอัลบั้ม", - "delete_api_key_prompt": "คุณต้องการลบ API คีย์ นี้ใช่ไหม ?", - "delete_dialog_alert": "รายการดังกล่าวจะถูกลบจาก Immich และเครื่องอย่างถาวร", - "delete_dialog_alert_local": "รายการดังกล่าวจะถูกลบจากเครื่องคุณอย่างถาวร แต่จะยังคงอยู่บนเซิร์ฟเวอร์ Immich", - "delete_dialog_alert_local_non_backed_up": "รายการบางตัวไม่ได้ถูกสำรองบน Immich และจะถูกลบจากเครื่องคุณอย่างถาวร", - "delete_dialog_alert_remote": "รายการดังกล่าวจะถูกลบจากเซิร์ฟเวอร์ Immich อย่างถาวร", - "delete_dialog_ok_force": "ลบต่อไป", - "delete_dialog_title": "ลบถาวร", - "delete_duplicates_confirmation": "คุณแน่ใจที่ต้องการลบรายการซ้ำอย่างถาวรใช่ไหม ?", - "delete_face": "ลบใบหน้า", - "delete_key": "ลบกุญแจ", - "delete_library": "ลบคลังภาพ", - "delete_link": "ลบลิงก์", - "delete_local_dialog_ok_backed_up_only": "ลบที่สำรองไว้เท่านั้น", - "delete_local_dialog_ok_force": "ลบต่อไป", - "delete_others": "ลบผู้อื่น", - "delete_shared_link": "ลบลิงก์ที่แชร์", - "delete_shared_link_dialog_title": "ลบลิงก์ที่แชร์", - "delete_tag": "ลบแท็ก", - "delete_tag_confirmation_prompt": "คุณแน่ใจว่าต้องการลบแท็ก {tagName} ใช่หรือไม่?", - "delete_user": "ลบผู้ใช้", - "deleted_shared_link": "ลบลิงก์ที่แชร์แล้ว", - "deletes_missing_assets": "ลบสื่อที่หายไปออกจากดิสถ์", - "description": "รายละเอียด", - "description_input_hint_text": "เพื่มรายละเอียด...", - "description_input_submit_error": "อัพเดตรายละเอียดผิดพลาด ตรวจสอบ log เพื่อรายละเอียดเพิ่มเติม", - "details": "รายละเอียด", - "direction": "เส้นทาง", - "disabled": "ปิดการใช้งาน", - "disallow_edits": "ไม่อนุญาตให้แก้ไข", - "discord": "ดิสคอร์ด", - "discover": "ค้นพบ", - "discovered_devices": "ค้นหาอุปกรณ์", - "dismiss_all_errors": "ปฏิเสธข้อผิดพลาดทั้งหมด", - "dismiss_error": "ปฏิเสธข้อผิดพลาด", - "display_options": "ตัวเลือกการแสดง", - "display_order": "ลำดับการแสดงผล", - "display_original_photos": "แสดงภาพต้นฉบับ", - "display_original_photos_setting_description": "การตั้งค่าแสดงผลรูปภาพต้นฉบับ เมื่อเปิดรูปภาพ การตั้งค่านี้อาจจะทำให้การแสดงภาพได้ช้าลง", - "do_not_show_again": "ไม่แสดงข้อความนี้อีก", - "documentation": "เอกสาร", - "done": "ดำเนินการสำเร็จ", - "download": "ดาวน์โหลด", - "download_canceled": "การดาวน์โหลดยกเลิก", - "download_complete": "การดาวน์โหลดเสร็จสิ้น", - "download_enqueue": "การดาวน์โหลดอยู่ในคิว", - "download_error": "ดาวน์โหลดผิดพลาด", - "download_failed": "ดาวน์โหลดไม่สำเร็จ", - "download_finished": "ดาวน์โหลดเสร็จสิ้น", - "download_include_embedded_motion_videos": "รวมวิดีโอที่ฝังอยู่ในภาพเคลื่อนไหว", - "download_include_embedded_motion_videos_description": "รวมวิดีโอที่ฝังอยู่ในภาพเคลื่อนไหวเมื่อดาวน์โหลดอัลบั้ม", - "download_notfound": "ไม่พบดาวน์โหลด", - "download_paused": "หยุดการดาวน์โหลดชั่วคราว", - "download_settings": "การตั้งค่าการดาวน์โหลด", - "download_settings_description": "จัดการการตั้งค่าการดาวน์โหลด", - "download_started": "เริ่มการดาวน์โหลด", - "download_sucess": "ดาวน์โหลดสำเร็จ", - "download_sucess_android": "สื่อถูกดาวน์โหลดไปยัง DCIM/Immich", - "download_waiting_to_retry": "รอลองใหม่", - "downloading": "กำลังดาวน์โหลด", - "downloading_asset_filename": "กำลังดาวน์โหลด {filename}", - "downloading_media": "กำลังดาวน์โหลดสื่อ", - "drop_files_to_upload": "วางไฟล์ในช่องอัปโหลด", - "duplicates": "รายการที่ซ้ำกัน", - "duplicates_description": "แก้ไขแต่ละกลุ่มโดยระบุว่ากลุ่มใดซ้ำกันหากมี", - "duration": "ระยะเวลา", - "edit": "แก้ไข", - "edit_album": "แก้ไขอัลบั้ม", - "edit_avatar": "แก้ไขตัวละคร", - "edit_date": "แก้ไขวันที่", - "edit_date_and_time": "แก้ไขวันที่และเวลา", - "edit_description": "แก้ไขคำอธิบาย", - "edit_description_prompt": "โปรดเลื่อกคำอธิบายใหม่", - "edit_exclusion_pattern": "แก้ไขข้อยกเว้น", - "edit_faces": "แก้ไขหน้า", - "edit_key": "แก้ไขกุญแจ", - "edit_link": "แก้ไขลิงก์", - "edit_location": "แก้ไขตำแหน่ง", - "edit_location_dialog_title": "ตำแหน่ง", - "edit_name": "แก้ไขชื่อ", - "edit_people": "แก้ไขผู้คน", - "edit_tag": "แก้ไขแท็ก", - "edit_title": "แก้ไขชื่อ", - "edit_user": "แก้ไขผู้ใช้", - "editor": "ผู้แก้ไข", - "editor_close_without_save_prompt": "การเปลี่ยนแปลงนี้จะไม่ได้รับการบันทึก", - "editor_close_without_save_title": "ปิดโปรแกรมแก้ไข?", - "email": "อีเมล", - "email_notifications": "แจ้งเตือนผ่านอีเมล", - "empty_folder": "โฟลเดอร์นี้ว่างเปล่า", - "empty_trash": "ทิ้งจากถังขยะ", - "empty_trash_confirmation": "คุณแน่ใจหรือไม่ว่าต้องการล้างถังขยะ การดำเนินการนี้จะลบทรัพยากรทั้งหมดในถังขยะออกจาก Immich อย่างถาวร\nคุณไม่สามารถย้อนกลับการดำเนินการนี้ได้!", - "enable": "เปิดใช้งาน", - "enable_biometric_auth_description": "ใส่พินเพื่อเปิดการพิสูจน์อัตลักษณ์เพื่อยืนยันตัวบุคคล", - "enabled": "เปิดใช้งาน", - "end_date": "วันสิ้นสุด", - "enqueued": "รอคิว", - "enter_wifi_name": "ใส่ชื่อ Wi-Fi", - "enter_your_pin_code": "ใส่พินโค้ด", - "enter_your_pin_code_subtitle": "ใส่พินโค้ดเพื่อเข้าถึงโฟลเดอร์ล็อค", - "error": "เกิดข้อผิดพลาด", - "error_change_sort_album": "เปลี่ยนการเรียงลำดับอัลบั้มไม่สำเร็จ", - "error_delete_face": "เกิดเออเรอร์ ไม่สามารถลบใบหน้าออกได้", - "error_loading_image": "เกิดข้อผิดพลาดระหว่างโหลดภาพ", - "error_saving_image": "เกิดข้อผิดพลาดระหว่างเซฟภาพ: {error}", - "error_tag_face_bounding_box": "การแท็กใบหน้าผิดพลาด - ไม่สามารถตีกรอบใบหน้าได้", - "error_title": "เกิดข้อผิดพลาด", - "errors": { - "cannot_navigate_next_asset": "ไม่สามารถเปลี่ยนเส้นทางได้", - "cannot_navigate_previous_asset": "ไม่สามารถเปลี่ยนเส้นทางก่อนหน้าได้", - "cant_apply_changes": "เกิดข้อผิดพลาดในการเปลี่ยนแปลง", - "cant_change_activity": "Can't {enabled, select, true {disable} other {enable}} activity", - "cant_change_asset_favorite": "ไม่สามารถเปลี่ยนสื่อที่ชื่นชอบได้", - "cant_change_metadata_assets_count": "ไม่สามารถแก้ไขข้อมูล metadata ของ {count, plural, one {# สื่อ} other {# สื่อ}}", - "cant_get_faces": "เกิดข้อผิดพลาดในการเรียกดูใบหน้า", - "cant_get_number_of_comments": "ไม่สามารถเรียกดูจำนวนความคิดเห็นได้", - "cant_search_people": "ไม่สามารถค้นหาบุคคลคนได้", - "cant_search_places": "ไม่สามารถค้นหาสถานที่ได้", - "error_adding_assets_to_album": "เกิดข้อผิดพลาดในการเพิ่มสื่อไปยังอัลบั้ม", - "error_adding_users_to_album": "เกิดข้อผิดพลาดในการเพิ่มผู้ใช้ไปยังอัลบั้ม", - "error_deleting_shared_user": "เกิดข้อผิดพลาดในการลบผู้ใช้ที่แชร์", - "error_downloading": "ไม่สามารถดาวน์โหลด {filename} ได้", - "error_hiding_buy_button": "เกิดข้อผิดพลาด ไม่สามารถซ่อนปุ่มซื้อได้", - "error_removing_assets_from_album": "เกิดข้อผิดพลาดในการลบสื่อจากอัลบั้ม", - "error_selecting_all_assets": "เกิดข้อผิดพลาดในการเลือกสื่อทั้งหมด", - "exclusion_pattern_already_exists": "ข้อยกเว้นนี้มีอยู่แล้ว", - "failed_to_create_album": "ไม่สามารถสร้างอัลบั้มได้", - "failed_to_create_shared_link": "ไม่สามารถสร้างลิงก์ที่แชร์ได้", - "failed_to_edit_shared_link": "ไม่สามารถแก้ไขลิงก์ที่แชร์ได้", - "failed_to_get_people": "ไม่สามารถเรียกดูบุคคลได้", - "failed_to_keep_this_delete_others": "ไม่สามารถเก็บหรือลบได้", - "failed_to_load_asset": "ไม่สามารถโหลดสื่อได้", - "failed_to_load_assets": "ไม่สามารถโหลดสื่อได้", - "failed_to_load_notifications": "โหลดการแจ้งเตือนไม่สำเร็จ", - "failed_to_load_people": "ไม่สามารถโหลดบุคคลได้", - "failed_to_remove_product_key": "ไม่สามารถลบ product key ได้", - "failed_to_stack_assets": "Failed to stack assets", - "failed_to_unstack_assets": "Failed to un-stack assets", - "failed_to_update_notification_status": "อัพเดทสถานะการแจ้งเตือนไม่สำเร็จ", - "incorrect_email_or_password": "อีเมลหรือรหัสผ่านไม่ถูกต้อง", - "paths_validation_failed": "การตรวจสอบ {paths, plural, one {# path} other {# paths}} ล้มเหลว", - "profile_picture_transparent_pixels": "รูปโปรไฟล์ไม่สามารถมีพิกเซลโปร่งใสได้ โปรดซูมเข้าและ/หรือย้ายรูปภาพ", - "quota_higher_than_disk_size": "คุณตั้งโควตาไว้สูงกว่าขนาดดิสก์", - "unable_to_add_album_users": "ไม่สามารถเพิ่มผู้ใช้ไปยังอัลบั้มได้", - "unable_to_add_assets_to_shared_link": "ไม่สามารถเพิ่มลงในลิงก์ที่แชร์ได้", - "unable_to_add_comment": "ไม่สามารถเพิ่มความเห็นได้", - "unable_to_add_exclusion_pattern": "ไม่สามารถเพิ่มรูปแบบข้อยกเว้นได้", - "unable_to_add_partners": "ไม่สามารถเพิ่มคู่หูได้", - "unable_to_add_remove_archive": "ไม่สามารถจัดเก็บรายการ {archived, select, true {remove asset from} other {add asset to}} ไปยังการจัดเก็บถาวรได้", - "unable_to_add_remove_favorites": "ไม่สามารถทำรายการ {favorite, select, true {add asset to} other {remove asset from}} เข้ารายการโปรดได้", - "unable_to_archive_unarchive": "ไม่สามารถทำรายการ {archived, select, true {archive} other {unarchive}}", - "unable_to_change_album_user_role": "ไม่สามารถเปลี่ยนบทบาทผู้ใช้ในอัลบั้มได้", - "unable_to_change_date": "ไม่สามารถเปลี่ยนวันที่ได้", - "unable_to_change_description": "ไม่สามารถเปลี่ยนคำอธิบาย", - "unable_to_change_favorite": "ไม่สามารถเปลี่ยนแปลงสื่อรายการโปรดได้", - "unable_to_change_location": "ไม่สามารถเปลี่ยนตําแหน่งได้", - "unable_to_change_password": "ไม่สามารถเปลี่ยนรหัสผ่านได้", - "unable_to_change_visibility": "ไม่สามารถเปลี่ยนการมองเห็นสำหรับ {count, plural, one {# person} other {# people}}", - "unable_to_complete_oauth_login": "ไม่สามารถทำการเข้าสู่ระบบ OAuth ให้เสร็จสมบูรณ์ได้", - "unable_to_connect": "ไม่สามารถเชื่อมต่อได้", - "unable_to_copy_to_clipboard": "ไม่สามารถคัดลอกไปยังคลิปบอร์ดได้ ตรวจสอบให้แน่ใจว่าคุณเข้าถึงหน้าผ่านทาง https", - "unable_to_create_admin_account": "ไม่สามารถสร้างบัญชีผู้ดูแลระบบได้", - "unable_to_create_api_key": "ไม่สามารถสร้าง API คีย์ ได้", - "unable_to_create_library": "ไม่สามารถสร้างคลังภาพได้", - "unable_to_create_user": "ไม่สามารถสร้างผู้ใช้ได้", - "unable_to_delete_album": "ไม่สามารถลบอัลบั้มได้", - "unable_to_delete_asset": "ไม่สามารถลบสื่อได้", - "unable_to_delete_assets": "เกิดผิดพลาดในการลบ", - "unable_to_delete_exclusion_pattern": "ไม่สามารถลบรูปแบบที่ยกเว้น", - "unable_to_delete_shared_link": "ไม่สามารถลบลิงก์ที่แชร์ได้", - "unable_to_delete_user": "ไม่สามารถลบผู้ใช้ได้", - "unable_to_download_files": "ไม่สามารถดาวน์โหลดไฟล์ได้", - "unable_to_edit_exclusion_pattern": "ไม่สามารถแก้ไขรูปแบบยกเว้นได้", - "unable_to_empty_trash": "ไม่สามารถลบถังขยะได้", - "unable_to_enter_fullscreen": "ไม่สามารถเปิดเต็มจอได้", - "unable_to_exit_fullscreen": "ไม่สามารถออกโหมดเต็มจอได้", - "unable_to_get_comments_number": "ไม่สามารถร้องขอจำนวนแสดงความคิดเห็น", - "unable_to_get_shared_link": "การร้องขอลิงก์ที่แชร์ล้มเหลว", - "unable_to_hide_person": "ไม่สามารถซ่อนบุคคลได้", - "unable_to_link_motion_video": "ไม่สามารถเชื่อมโยงวิดีโอเคลื่อนไหวได้", - "unable_to_link_oauth_account": "ไม่สามารถเชื่อมโยงบัญชี OAuth ได้", - "unable_to_log_out_all_devices": "ไม่สามารถออกจากของอุปกรณ์ทั้งหมดได้", - "unable_to_log_out_device": "ไม่สามารถออกจากระบบได้", - "unable_to_login_with_oauth": "ไม่สามารถเข้าสู่ระบบกับ OAuth ได้", - "unable_to_play_video": "ไม่สามารถเล่นวิดีโอได้", - "unable_to_reassign_assets_existing_person": "ไม่สามารถกำหนดให้กับ {name, select, null {an existing person} other {{name}}}", - "unable_to_reassign_assets_new_person": "ไม่สามารถมอบหมาย ให้กับบุคคลใหม่ได้", - "unable_to_refresh_user": "ไม่สามารถรีเฟรชผู้ใช้ได้", - "unable_to_remove_album_users": "ไม่สามารถลบผู้ใช้ออกจากอัลบั้มได้", - "unable_to_remove_api_key": "ไม่สามารถลบ API Key ได้", - "unable_to_remove_assets_from_shared_link": "ไม่สามารถลบออกจากลิงก์ที่แชร์ได้", - "unable_to_remove_library": "ไม่สามารถลบคลังภาพได้", - "unable_to_remove_partner": "ไม่สามารถลบคู่หูได้", - "unable_to_remove_reaction": "ไม่สามารถลบ reaction ได้", - "unable_to_reset_password": "ไม่สามารถตั้งรหัสผ่านใหม่ได้", - "unable_to_reset_pin_code": "ไม่สามารถรีเซ็ตพินโค้ด", - "unable_to_resolve_duplicate": "ไม่สามารถแก้ไขของซ้ำได้", - "unable_to_restore_assets": "ไม่สามารถเรียกคืนสื่อได้", - "unable_to_restore_trash": "ไม่สามารถเรียกคืนถังขยะได้", - "unable_to_restore_user": "ไม่สามารถเรียกคืนผู้ใช้ได้", - "unable_to_save_album": "ไม่สามารถบันทึกอัลบั้มได้", - "unable_to_save_api_key": "ไม่สามารถบันทึก API คีย์ ได้", - "unable_to_save_date_of_birth": "ไม่สามารถตั้งวันเกิดได้", - "unable_to_save_name": "ไม่สามารถบันทึกชื่อได้", - "unable_to_save_profile": "ไม่สามารถบันทึกโปรไฟล์ได้", - "unable_to_save_settings": "ไม่สามารถบันทึกการตั้งค่าได้", - "unable_to_scan_libraries": "ไม่สามารถสแกนคลังภาพได้", - "unable_to_scan_library": "ไม่สามารถสแกนคลังภาพได้", - "unable_to_set_feature_photo": "ไม่สามารถตั้งรูปภาพเป็น Feature ได้", - "unable_to_set_profile_picture": "ไม่สามารถตั้งภาพโปรไฟล์ได้", - "unable_to_submit_job": "ไม่สามารถส่งงานได้", - "unable_to_trash_asset": "ไม่สามารถทิ้งสื่อได้", - "unable_to_unlink_account": "ไม่สามารถยกเลิกการเชื่อมโยงบัญชีผู้ใช้ได้", - "unable_to_unlink_motion_video": "ไม่สามารถแยกการเคลื่อนไหวได้", - "unable_to_update_album_cover": "ไม่สามารถอัปเดตรูปภาพปกอัลบั้มได้", - "unable_to_update_album_info": "ไม่สามารถอัปเดตข้อมูลอัลบั้มได้", - "unable_to_update_library": "ไม่สามารถอัพเดทคลังภาพได้", - "unable_to_update_location": "ไม่สามารถอัพเดทตําแหน่งได้", - "unable_to_update_settings": "ไม่สามารถอัพเดทการตั้งค่าได้", - "unable_to_update_timeline_display_status": "ไม่สามารถแก้ไขสถานะการแสดงลำดับเวลาได้", - "unable_to_update_user": "ไม่สามารถอัพเดทผู้ใช้ได้", - "unable_to_upload_file": "ไม่สามารถอัปโหลดได้" - }, - "exif": "Exif", - "exif_bottom_sheet_description": "เพิ่มคำอธิบาย", - "exif_bottom_sheet_details": "รายละเอียด", - "exif_bottom_sheet_location": "ตำแหน่ง", - "exif_bottom_sheet_people": "คน", - "exif_bottom_sheet_person_add_person": "เพิ่มชื่อ", - "exit_slideshow": "ออกจากการนำเสนอ", - "expand_all": "ขยายทั้งหมด", - "experimental_settings_new_asset_list_subtitle": "กำลังพัฒนา", - "experimental_settings_new_asset_list_title": "เปิดตารางรูปภาพที่กำลังทดลอง", - "experimental_settings_subtitle": "ใช้ภายใต้ความเสี่ยงของคุณเอง!", - "experimental_settings_title": "ทดลอง", - "expire_after": "หมดอายุหลังจาก", - "expired": "หมดอายุแล้ว", - "expires_date": "หมดอายุวันที่ {date}", - "explore": "สํารวจ", - "explorer": "เครื่องมือสำรวจ", - "export": "ส่งออก", - "export_as_json": "ส่งออกเป็นไฟล์ JSON", - "extension": "ส่วนต่อขยาย", - "external": "ภายนอก", - "external_libraries": "ภายนอกคลังภาพ", - "external_network": "การเชื่อมต่อภายนอก", - "external_network_sheet_info": "เมื่อไม่ได้เชื่อมต่อ Wi-Fi ที่เลือกไว้ แอพจะเชื่อมต่อเซิร์ฟเวอร์ผ่าน URL ด้านล่างตามลำดับ", - "face_unassigned": "ไม่กำหนดมอบหมาย", - "failed": "ล้มเหลว", - "failed_to_authenticate": "การยืนยันตัวตนไม่สำเร็จ", - "failed_to_load_assets": "เกิดข้อผิดพลาดในการโหลดสื่อ", - "failed_to_load_folder": "โหลดโฟลเดอร์ไม่สำเร็จ", - "favorite": "รายการโปรด", - "favorite_or_unfavorite_photo": "โปรดหรือไม่โปรดภาพ", - "favorites": "รายการโปรด", - "favorites_page_no_favorites": "ไม่พบทรัพยากรในรายการโปรด", - "feature_photo_updated": "อัพเดทภาพเด่นแล้ว", - "features": "ฟีเจอร์", - "features_setting_description": "จัดการฟีเจอร์แอป", - "file_name_or_extension": "นามสกุลหรือชื่อไฟล์", - "filename": "ชื่อไฟล์", - "filetype": "ชนิดไฟล์", - "filter": "ตัวกรอง", - "filter_people": "กรองผู้คน", - "filter_places": "กรองสถานที่", - "find_them_fast": "ค้นหาโดยชื่ออย่างรวดเร็ว", - "fix_incorrect_match": "แก้ไขการจับคู่ที่ไม่ถูกต้อง", - "folder": "โฟลเดอร์", - "folder_not_found": "ไม่พบโฟลเดอร์", - "folders": "โฟล์เดอร์", - "folders_feature_description": "การเรียกดูมุมมองโฟลเดอร์สำหรับภาพถ่ายและวิดีโอในระบบไฟล์", - "forward": "ไปข้างหน้า", - "free_up_space": "เพิ่มพื้นที่ว่าง", - "free_up_space_description": "เพิ่มพื้นที่ว่างโดยการย้ายรูปภาพและวิดีโอที่สำรองข้อมูลแล้วไปยังถังขยะของอุปกรณ์ของคุณ สำเนาที่อยู่บนเซิร์ฟเวอร์ยังคงอยู่อย่างปลอดภัย", - "free_up_space_settings_subtitle": "เพิ่มพื้นที่จัดเก็บอุปกรณ์", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "ฟีเจอร์นี้ต้องโหลดทรัพยากรจาก Google เพื่อทำงาน", - "general": "ทั่วไป", - "get_help": "ขอความช่วยเหลือ", - "get_wifiname_error": "ไม่สามารถรับชื่อ Wi-Fi กรุณายืนยันการให้อนุญาตแอพ และยืนยันว่า Wi-Fi เชื่อมต่ออยู่", - "getting_started": "เริ่มต้นใช้งาน", - "go_back": "กลับ", - "go_to_folder": "ไปที่โฟล์เดอร์", - "go_to_search": "กลับไปยังการค้นหา", - "grant_permission": "ให้อนุญาต", - "group_albums_by": "จัดกลุ่มอัลบั้มตาม", - "group_country": "จัดเรียงกลุ่มตามประเทศ", - "group_no": "ไม่จัดกลุ่ม", - "group_owner": "จัดกลุ่มโดยเจ้าของ", - "group_places_by": "จัดเรียงกลุ่มของสถานที่ด้วยการ...", - "group_year": "จัดกลุ่มตามปี", - "haptic_feedback_switch": "เปิดการตอบสนองแบบสัมผัส", - "haptic_feedback_title": "การตอบสนองแบบสัมผัส", - "has_quota": "เหลือพื้นที่", - "header_settings_add_header_tip": "เพิ่มส่วนหัว", - "header_settings_field_validator_msg": "ค่าต้องไม่ว่างเปล่า", - "header_settings_header_name_input": "ชื่อ Header", - "header_settings_header_value_input": "ค่า Header", - "headers_settings_tile_title": "ปรับแต่ง proxy headers", - "hi_user": "สวัสดีคุณ {name} {email}", - "hide_all_people": "ซ่อนบุคคลทั้งหมด", - "hide_gallery": "ซ่อนคลังภาพ", - "hide_named_person": "ซ่อน {name}", - "hide_password": "ซ่อนรหัสผ่าน", - "hide_person": "ซ่อนบุคคล", - "hide_unnamed_people": "ซ่อนบุคคลที่ไม่ได้ระบุชื่อ", - "home_page_add_to_album_conflicts": "เพิ่ม {added} ทรัพยากรเข้าอัลบั้ม {album}. {failed} ทรัพยากรอยู่ในอัลบั้มอยู่แล้ว", - "home_page_add_to_album_err_local": "ยังไม่สามารถเพิ่มสื่อบนอุปกรณ์เข้าอัลบั้ม ข้าม", - "home_page_add_to_album_success": "เพิ่มทรัพยากร {added} เข้าอัลบั้ม {album}", - "home_page_album_err_partner": "ยังไม่สามารถเพิ่มสื่อของคู่หูได้ กำลังข้าม", - "home_page_archive_err_local": "ยังไม่สามารถเก็บถาวรได้ กำลังข้าม", - "home_page_archive_err_partner": "ไม่สามารถเก็บสื่อของคู่หูได้ กำลังข้าม", - "home_page_building_timeline": "กำลังสร้าง timeline", - "home_page_delete_err_partner": "ไม่สามารถลบสื่อของคู่ได้ กำลังข้าม", - "home_page_delete_remote_err_local": "ทรัพยากรบนเครื่องอยู่ในลบจากรีโมท กำลังข้าม", - "home_page_favorite_err_local": "ยังไม่สามารถตั้งทรัพยากรบนเครื่องเป็นรายการโปรด กำลังข้าม", - "home_page_favorite_err_partner": "ยังไม่สามารถเพิ่มสื่อของคู่หูในรายการโปรดได้ กำลังข้าม", - "home_page_first_time_notice": "ถ้าครั้งนี้เป็นครั้งแรกที่ใช้แอปนี้ กรุณาเลือกอัลบั้มที่จะสำรองข้อมูล ไทม์ไลน์จะได้เพิ่มรูปภาพและวิดีโอที่อยู่ในอัลบั้ม", - "home_page_locked_error_local": "ไม่สามารถย้ายสื่อบนอุปกรณ์ไปยังโฟลเดอร์ล็อค ข้าม", - "home_page_locked_error_partner": "ยังไม่สามารถเพิ่มสื่อของคู่หูไปยังโฟลเดอร์ล็อคได้ กำลังข้าม", - "home_page_share_err_local": "ไม่สามารถแชร์ผ่านลิงค์ได้ กำลังข้าม", - "home_page_upload_err_limit": "สามารถอัพโหลดได้มากสุดครั้งละ 30 ทรัพยากร กำลังข้าม", - "host": "โฮสต์", - "hour": "ชั่วโมง", - "id": "ไอดี", - "ignore_icloud_photos": "ข้ามภาพบน iCloud", - "ignore_icloud_photos_description": "ภาพที่ถูกเก็บบน iCloud จะไม่ถูกอัพโหลดขึ้น Immich", - "image": "รูปภาพ", - "image_alt_text_date": "{isVideo, select, true {วิดีโอ} other {รูปภาพ}}ถูกถ่ายเมื่อ {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} ถ่ายกับ {person1} วันที่ {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} ถ่ายกับ {person1} และ {person2} วันที่ {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} ถ่ายกับ {person1}, {person2},และ {person3} วันที่ {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} ถ่ายกับ {person1}, {person2},และ {additionalCount, number} วันที่ {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} ถ่ายใน {city}, {country} วันที่ {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} ถ่ายใน {city}, {country} กับ {person1} วันที่ {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} ถ่ายใน {city}, {country} กับ {person1} และ {person2} วันที่ {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} ถ่ายใน {city}, {country} กับ {person1}, {person2},และ {person3} วันที่ {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} ถ่ายใน {city}, {country} กับ {person1}, {person2}, และ {additionalCount, number} ในวันที่ {date}", - "image_saved_successfully": "รูปภาพถูกเซฟ", - "image_viewer_page_state_provider_download_started": "ดาวน์โหลดเริ่มต้น", - "image_viewer_page_state_provider_download_success": "ดาวน์โหลดสำเร็จ", - "image_viewer_page_state_provider_share_error": "แชร์ผิดพลาด", - "immich_logo": "โลโก้ Immich", - "immich_web_interface": "หน้าตาเว็บไซต์ Immich", - "import_from_json": "นำเข้าจาก JSON", - "import_path": "นำเข้าเส้นทาง", - "in_albums": "ใน {count, plural, one {# album} other {# albums}}", - "in_archive": "ในที่เก็บถาวร", - "include_archived": "รวมไฟล์เก็บถาวร", - "include_shared_albums": "รวมอัลบั้มที่แชร์กัน", - "include_shared_partner_assets": "รวมสื่อที่แชร์กับคู่หู", - "individual_share": "แชร์ส่วนตัว", - "individual_shares": "การแชร์เดี่ยว", - "info": "ข้อมูล", - "interval": { - "day_at_onepm": "ทุกวันเวลาบ่ายโมง", - "hours": "ทุก ๆ {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "ทุกเที่ยงคืน", - "night_at_twoam": "ทุกวันเวลาตี 2" - }, - "invalid_date": "วันที่ไม่ถูกต้อง", - "invalid_date_format": "รูปแบบวันที่ไม่ถูกต้อง", - "invite_people": "เชิญผู้คน", - "invite_to_album": "เชิญเข้าอัลบั้ม", - "ios_debug_info_fetch_ran_at": "รับข้อมูลเมื่อ {dateTime}", - "ios_debug_info_last_sync_at": "ซิงค์ล่าสุด {dateTime}", - "ios_debug_info_no_processes_queued": "ไม่มีคิวในพื้นหลัง", - "ios_debug_info_no_sync_yet": "ยังไม่มีงานซิงค์รันในพื้นหลัง", - "ios_debug_info_processes_queued": "{count} โพรเซสรอคิวในพื้นหลัง", - "ios_debug_info_processing_ran_at": "โพรเซสรันเมื่อ {dateTime}", - "items_count": "{count, plural, one {# รายการ} other {# รายการ}}", - "jobs": "งาน", - "keep": "เก็บ", - "keep_all": "เก็บทั้งหมด", - "keep_description": "เลือกสิ่งที่จะเก็บไว้บนอุปกรณ์ของคุณขณะเพิ่มพื้นที่ว่าง", - "keep_this_delete_others": "เก็บสิ่งนี้ไว้ ลบอันอื่นออก", - "kept_this_deleted_others": "เก็บเนื้อหานี้และลบ {count, plural, one {# Asset} other {# Asset}}", - "keyboard_shortcuts": "ปุ่มพิมพ์ลัด", - "language": "ภาษา", - "language_no_results_subtitle": "กรุณาปรับเปลี่ยนคำค้นหา", - "language_no_results_title": "ไม่พบภาษา", - "language_search_hint": "ค้นหาภาษา...", - "language_setting_description": "เลือกภาษาที่ต้องการ", - "large_files": "ไฟล์ขนาดใหญ่", - "last_seen": "เห็นล่าสุด", - "latest_version": "เวอร์ชันล่าสุด", - "latitude": "ละติจูด", - "leave": "ทิ้ง", - "lens_model": "รูปแบบเลนช์", - "let_others_respond": "ให้คนอื่นตอบ", - "level": "ระดับ", - "library": "คลังภาพ", - "library_options": "ตัวเลือกคลังภาพ", - "library_page_device_albums": "อัลบั้มบนเครื่อง", - "library_page_new_album": "อัลบั้มใหม่", - "library_page_sort_asset_count": "จำนวนทรัพยากรที่มี", - "library_page_sort_created": "สร้างล่าสุด", - "library_page_sort_last_modified": "แก้ไขล่าสุด", - "library_page_sort_title": "ชื่ออัลบั้ม", - "light": "สว่าง", - "like_deleted": "ลบที่ถูกใจแล้ว", - "link_motion_video": "ลิงก์วิดีโอเคลื่อนไหว", - "link_to_oauth": "ลิงก์ไปยัง OAuth", - "linked_oauth_account": "ลิงก์บัญชีผู้ใช้ OAuth", - "list": "รายการ", - "loading": "กำลังโหลด", - "loading_search_results_failed": "โหลดผลการค้นหาล้มเหลว", - "local_asset_cast_failed": "ไม่สามารถแคสสื่อที่ไม่ถูกอัพโหลดไปยังเซิร์ฟเวอร์", - "local_network": "เครือข่ายระยะใกล้", - "local_network_sheet_info": "แอพจะทำการเชื่อมต่อไปยังเซิร์ฟเวอร์ผ่าน URL นี้เมื่อเชื่อต่อกับ Wi-Fi ที่เลือกไว้", - "location_permission": "การอนุญาตเข้าถึงตำแหน่ง", - "location_permission_content": "เพื่อใช้ฟีเจอร์การสับโดยอัตโนมัติ Immich ต้องการการอนุญาตเข้าถึงต่ำแหน่งที่แม่นยำเพื่ออ่านชื่อ Wi-Fi ที่เชื่อมต่ออยู่", - "location_picker_choose_on_map": "เลือกบนแผนที่", - "location_picker_latitude_error": "กรุณาเพิ่มละติจูตที่ถูกต้อง", - "location_picker_latitude_hint": "เพิ่มละติจูตตรงนี้", - "location_picker_longitude_error": "กรุณาเพิ่มลองจิจูตที่ถูกต้อง", - "location_picker_longitude_hint": "เพิ่มลองจิจูตตรงนี้", - "lock": "ล็อค", - "locked_folder": "โฟลเดอร์ล็อค", - "log_out": "ออกจากระบบ", - "log_out_all_devices": "ให้ทุกอุปกรณ์ออกจากระบบทั้งหมด", - "logged_in_as": "{user} กำลังล็อคอิน", - "logged_out_all_devices": "ออกจากระบบทั้งหมดแล้ว", - "logged_out_device": "ออกจากระบบแล้ว", - "login": "เข้าสู่ระบบ", - "login_disabled": "ล็อกอินถูกปิด", - "login_form_api_exception": "ข้อผิดพลาด API กรุณาตรวจสอบ URL แล้วลองใหม่", - "login_form_back_button_text": "กลับ", - "login_form_email_hint": "อีเมลคุณ@อีเมล.com", - "login_form_endpoint_hint": "http://ไอพีเชอร์ฟเวอร์คุณ:พอร์ต", - "login_form_endpoint_url": "URL ปลายทางของเซิร์ฟเวอร์", - "login_form_err_http": "โปรดระบุ http:// หรือ https://", - "login_form_err_invalid_email": "อีเมลไม่ถูกต้อง", - "login_form_err_invalid_url": "URL ไม่ถูกต้อง", - "login_form_err_leading_whitespace": "เว้นว่างข้างหน้า", - "login_form_err_trailing_whitespace": "เว้นว่างข้างหลัง", - "login_form_failed_get_oauth_server_config": "เกิดข้อผิดพลาดในการล็อกอินผ่าน OAuth ตรวจสอบ URL เซิร์ฟเวอร์", - "login_form_failed_get_oauth_server_disable": "ฟีเจอร์ OAuth ไม่สามารถใช้งานบนเซิร์ฟเวอร์นี้", - "login_form_failed_login": "เกิดข้อผิดพลาดในการล็อกอิน โปรดตรวจสอบ URL ของเซิร์ฟเวอร์ อีเมล และรหัสผ่าน", - "login_form_handshake_exception": "เกิดข้อผิดพลาดกระบวนการ Handshake กับเซิร์ฟเวอร์ ถ้ากำลังใช้ใบรับบรองแบบ self-signed กรุณาตั้งค่าอนุญาตใบรับรองแบบ self-signed", - "login_form_password_hint": "รหัสผ่าน", - "login_form_save_login": "อยู่ในระบบต่อไป", - "login_form_server_empty": "กรอก URL เซิร์ฟเวอร์", - "login_form_server_error": "ไม่สามารถติดต่อกับเซิร์ฟเวอร์", - "login_has_been_disabled": "ปิดการใช้งานการเข้าสู่ระบบแล้ว", - "login_password_changed_error": "เกิดข้อผิดพลาดขณะเปลี่ยนรหัสผ่าน", - "login_password_changed_success": "เปลี่ยนรหัสผ่านสำเร็จ", - "logout_all_device_confirmation": "คุณต้องการออกจากระบบทุกอุปกรณ์ ใช่หรือไม่ ?", - "logout_this_device_confirmation": "คุณต้องการออกจากระบบใช่หรือไม่ ?", - "longitude": "ลองจิจูด", - "look": "ดู", - "loop_videos": "วนวิดีโอ", - "loop_videos_description": "เปิดเพื่อให้วิดีโอวนลูปในที่ดูรายละเอียด", - "main_branch_warning": "คุณกำลังใช้เวอร์ชันการพัฒนา เราขอแนะนำอย่างยิ่งให้ใช้เวอร์ชันเสถียร !", - "main_menu": "เมนูหลัก", - "make": "สร้าง", - "manage_geolocation": "จัดการตำแหน่ง", - "manage_shared_links": "จัดการลิงก์ที่แชร์", - "manage_sharing_with_partners": "จัดการการแชร์กับคู่หู", - "manage_the_app_settings": "จัดการการตั้งค่าแอป", - "manage_your_account": "จัดการบัญชีของคุณ", - "manage_your_api_keys": "จัดการกุญแจ API ของคุณ", - "manage_your_devices": "จัดการอุปกรณ์ของคุณ", - "manage_your_oauth_connection": "จัดการการเชื่อมต่อ OAuth ของคุณ", - "map": "แผนที่", - "map_assets_in_bounds": "{count} รูปภาพ", - "map_cannot_get_user_location": "ไม่สามารถหาตำแหน่งผู้ใช้งานได้", - "map_location_dialog_yes": "ใช่", - "map_location_picker_page_use_location": "ใช้ตำแหน่งนี้", - "map_location_service_disabled_content": "ต้องเปิดตำแหน่งเพื่อแสดงทรัพยากรจากตำแหน่งปัจจุบัน เปิดตอนนี้?", - "map_location_service_disabled_title": "บริการตำแหน่งถูกปิด", - "map_marker_for_images": "หมุดแผนที่สำหรับรูปถ่ายที่ {city}, {country}", - "map_marker_with_image": "หมุดแผนที่กับรูปถ่าย", - "map_no_location_permission_content": "จำเป็นต้องมีสิทธิ์เข้าถึงตำแหน่งเพื่อแสดงทรัพยากรจากตำแหน่งปัจจุบัน อนุญาตตอนนี้?", - "map_no_location_permission_title": "สิทธิ์เข้าถึงตำแหน่งถูกปฏิเสธ", - "map_settings": "การตั้งค่าแผนที่", - "map_settings_dark_mode": "โหมดมืด", - "map_settings_date_range_option_day": "24 ชั่วโมงที่ผ่านมา", - "map_settings_date_range_option_days": "{days} วันที่ผ่านมา", - "map_settings_date_range_option_year": "ปีที่ผ่านมา", - "map_settings_date_range_option_years": "{years} ปีผ่านมา", - "map_settings_dialog_title": "ตั้งค่าแผนที่", - "map_settings_include_show_archived": "รวมเก็บถาวร", - "map_settings_include_show_partners": "รวมคู่หู", - "map_settings_only_show_favorites": "แสดงรายการโปรดเท่านั้น", - "map_settings_theme_settings": "ธีมแผนที่", - "map_zoom_to_see_photos": "ซูมออกเพื่อดูรูป", - "matches": "ตรงกัน", - "media_type": "ชนิดสื่อ", - "memories": "ความทรงจำ", - "memories_all_caught_up": "ตามทันหมดแล้ว", - "memories_check_back_tomorrow": "กลับมาดูความทรงจำมากกว่านี้ในวันถัดไป", - "memories_setting_description": "จัดการสิ่งที่คุณเห็นในความทรงจําของคุณ", - "memories_start_over": "เริ่มใหม่", - "memories_swipe_to_close": "ปัดขึ้นเพื่อปิด", - "memory": "ความทรงจำ", - "memory_lane_title": "ความทรงจำ {title}", - "menu": "เมนู", - "merge": "รวม", - "merge_people": "รวมผู้คน", - "merge_people_limit": "คุณรวมใบหน้าได้มากถึง 5 รูปต่อครั้ง", - "merge_people_prompt": "คุณต้องการรวมคนพวกนี้หรือไม่ การกระทำนี้ไม่สามารถย้อนกลับได้", - "merge_people_successfully": "รวมผู้คนเรียบร้อยแล้ว", - "merged_people_count": "{count, plural, one {# person} other {# people}} ถูกรวมเข้าด้วยกัน", - "minimize": "ย่อลง", - "minute": "นาที", - "missing": "ขาดหาย", - "model": "โมเดล", - "month": "เดือน", - "more": "เพิ่มเติม", - "move": "ย้าย", - "move_off_locked_folder": "ย้ายออกจากโฟลเดอร์ล็อค", - "move_to_locked_folder": "ย้ายไปโฟลเดอร์ล็อค", - "moved_to_trash": "ทิ้งลงถังขยะแล้ว", - "multiselect_grid_edit_date_time_err_read_only": "ไม่สามารถแก้ไขวันที่ทรัพยากรแบบอ่านอย่างเดียว กำลังข้าม", - "multiselect_grid_edit_gps_err_read_only": "ไม่สามารถแก้ตำแหน่งของทรัพยากรแบบอ่านอย่างเดียว กำลังข้าม", - "my_albums": "อัลบั้มของฉัน", - "name": "ชื่อ", - "name_or_nickname": "ชื่อหรือชื่อเล่น", - "networking_settings": "การเชื่อมต่อ", - "networking_subtitle": "ตั้งค่าปลายทางเซิร์ฟเวอร์", - "never": "ไม่เคย", - "new_album": "อัลบั้มใหม่", - "new_api_key": "สร้าง API คีย์ใหม่", - "new_password": "รหัสผ่านใหม่", - "new_person": "คนใหม่", - "new_pin_code": "รหัสประจำตัว (PIN) ใหม่", - "new_user_created": "สร้างผู้ใช้ใหม่แล้ว", - "new_version_available": "มีเวอร์ชันใหม่ให้ใช้งาน", - "newest_first": "ใหม่สุดก่อน", - "next": "ต่อไป", - "next_memory": "ความทรงจำต่อไป", - "no": "ไม่", - "no_albums_message": "สร้างอัลบั้มเพื่อจัดการรูปภาพและวิดีโอของคุณ", - "no_albums_with_name_yet": "ดูเหมือนว่าไม่มีอัลบั้มไหนที่ใช้ชื่อนี้", - "no_albums_yet": "ดูเหมือนว่าคุณยังไม่มีอัลบั้มใดๆ", - "no_archived_assets_message": "จัดเก็บรูปภาพและวีดิโอถาวรเพื่อซ่อนจากมุมมองคุณ", - "no_assets_message": "กดเพื่อใส่ภาพคุณภาพแรก", - "no_assets_to_show": "ไม่มีทรัพยากรให้แสดง", - "no_duplicates_found": "ไม่พบรายการที่ซ้ำกัน", - "no_exif_info_available": "ไม่มีข้อมูล exif", - "no_explore_results_message": "ไม่พบผลลัพธ์ ลองใช้คำค้นหาอื่น ๆ", - "no_favorites_message": "เพิ่มรายการโปรดเพื่อค้นหาภาพและวิดีโอที่ดีที่สุดของคุณอย่างรวดเร็ว", - "no_libraries_message": "สร้างคลังภาพภายนอกเพื่อดูภาพถ่ายและวิดีโอต่าง ๆ ของคุณ", - "no_name": "ไม่มีชื่อ", - "no_places": "ไม่มีสถานที่", - "no_results": "ไม่มีผลลัพธ์", - "no_results_description": "ลองใช้คำพ้องหรือคำหลักที่กว้างกว่านี้", - "no_shared_albums_message": "สร้างอัลบั้มเพื่อแชร์รูปภาพและวิดีโอกับคนในเครือข่ายของคุณ", - "not_in_any_album": "ไม่อยู่ในอัลบั้มใด ๆ", - "note_apply_storage_label_to_previously_uploaded assets": "หมายเหตุ: หากต้องการใช้ป้ายกำกับพื้นที่เก็บข้อมูลกับเนื้อหาที่อัปโหลดก่อนหน้านี้ ให้เรียกใช้", - "notes": "หมายเหตุ", - "notification_permission_dialog_content": "เพื่อเปิดการแจ้งเตือน เข้าตั้งค่าแล้วกดอนุญาต", - "notification_permission_list_tile_content": "อนุญาตการแจ้งเตือน", - "notification_permission_list_tile_enable_button": "เปิดการแจ้งเดือน", - "notification_permission_list_tile_title": "สิทธิ์การแจ้งเตือน", - "notification_toggle_setting_description": "เปิด/ปิด การแจ้งเตือนอีเมล", - "notifications": "การแจ้งเตือน", - "notifications_setting_description": "จัดการการแจ้งเตือน", - "official_immich_resources": "แหล่งข้อมูล Immich อย่างเป็นทางการ", - "offline": "ออฟไลน์", - "ok": "ตกลง", - "oldest_first": "เรียงเก่าสุดก่อน", - "onboarding": "การเริ่มต้นใช้งาน", - "onboarding_privacy_description": "ฟีเจอร์ (ตัวเลือก) ต่อไปนี้ต้องอาศัยบริการภายนอก และสามารถปิดใช้งานได้ตลอดเวลาในการตั้งค่าการ", - "onboarding_theme_description": "เลือกธีมสี คุณสามารถเปลี่ยนแปลงได้ในภายหลังในการตั้งค่าของคุณ", - "onboarding_welcome_user": "ยินดีต้อนรับคุณ {user}", - "online": "ออนไลน์", - "only_favorites": "รายการโปรดเท่านั้น", - "open_in_map_view": "เปิดดูในแผนที่", - "open_in_openstreetmap": "เปิดใน OpenStreetMap", - "open_the_search_filters": "เปิดตัวกรองการค้นหา", - "options": "ตัวเลือก", - "or": "หรือ", - "organize_your_library": "จัดเรียงคลังภาพของคุณ", - "original": "ต้นฉบับ", - "other": "อื่น ๆ", - "other_devices": "เครื่องอื่น", - "other_variables": "ตัวแปรอื่น", - "owned": "เป็นเจ้าของ", - "owner": "เจ้าของ", - "partner": "คู่หู", - "partner_can_access": "{partner} สามารถเข้าถึง", - "partner_can_access_assets": "รูปภาพและวิดีโอทั้งหมดยกเว้นที่อยู่ในเก็บถาวรและถูกลบทิ้ง", - "partner_can_access_location": "ตำแหน่งที่รูปถูกถ่าย", - "partner_list_user_photos": "รูปภาพของ {user}", - "partner_list_view_all": "ดูทั้งหมด", - "partner_page_empty_message": "รูปภาพของคุณยังไม่ถูกแชร์กับคู่หู", - "partner_page_no_more_users": "ไม่มีผู้ใช้งานให้เพิ่ม", - "partner_page_partner_add_failed": "การเพิ่มคู่หูล้มเหลว", - "partner_page_select_partner": "เลือกคู่หู", - "partner_page_shared_to_title": "แชร์กับ", - "partner_page_stop_sharing_content": "{partner} จะไม่สามารถเข้าถึงรูปภาพของคุณ", - "partner_sharing": "แชร์สำหรับคู่หู", - "partners": "คู่หู", - "password": "รหัสผ่าน", - "password_does_not_match": "รหัสผ่านไม่ตรงกัน", - "password_required": "จำเป็นต้องมีรหัสผ่าน", - "password_reset_success": "รีเซ็ตรหัสผ่านสำเร็จ", - "past_durations": { - "days": "{days, plural, one {วัน} other {# วัน}}ที่ผ่านมา", - "hours": "{hours, plural, one {ชั่วโมง} other {# ชั่วโมง}}ที่ผ่านมา", - "years": "{years, plural, one {ปี} other {# ปี}}ที่ผ่านมา" - }, - "path": "เส้นทาง", - "pattern": "รูปแบบ", - "pause": "หยุด", - "pause_memories": "หยุดดูความทรงจํา", - "paused": "หยุด", - "pending": "กำลังรอ", - "people": "ผู้คน", - "people_edits_count": "{count, plural, one {# person} other {# people}} ถูกแก้ไข", - "people_feature_description": "เรียกดูภาพถ่ายและวิดีโอที่จัดกลุ่มตามผู้คน", - "people_sidebar_description": "แสดงลิงก์ไปยังผู้คนในแถบด้านข้าง", - "permanent_deletion_warning": "แจ้งเตือนการลบถาวร", - "permanent_deletion_warning_setting_description": "เตือนเมื่อจะลบสื่อถาวร", - "permanently_delete": "ลบถาวร", - "permanently_delete_assets_count": "ลบ {count, plural, one {asset} other {assets}} ทิ้งถาวร", - "permanently_delete_assets_prompt": "คุณแน่ใจหรือไม่ว่าต้องการลบ {count, plural, one {this asset?} other {these # asset?}}อย่างถาวร การดำเนินการนี้จะลบ {count, plural, one {it from its} other {them from their}} อัลบั้มด้วย", - "permanently_deleted_asset": "ลบสื่อถาวรแล้ว", - "permanently_deleted_assets_count": "ลบ {count, plural, one {# asset} other {# assets}} เรียบร้อยแล้ว", - "permission_onboarding_back": "กลับ", - "permission_onboarding_continue_anyway": "ดำเนินการต่อ", - "permission_onboarding_get_started": "เริ่มต้น", - "permission_onboarding_go_to_settings": "ไปยังการตั้งค่า", - "permission_onboarding_permission_denied": "ไม่อนุญาต ตั้งค่าสิทธิ์เข้าถึงรูปภาพและวิดีโอเพื่อใช้งาน Immich", - "permission_onboarding_permission_granted": "ให้สิทธิ์สำเร็จ คุณพร้อมใช้งานแล้ว", - "permission_onboarding_permission_limited": "สิทธ์จำกัด เพื่อให้ Immich สำรองข้อมูลและจัดการคลังภาพได้ ตั้งค่าสิทธิเข้าถึงรูปภาพและวิดีโอ", - "permission_onboarding_request": "Immich จำเป็นจะต้องได้รับสิทธิ์ดูรูปภาพและวิดีโอ", - "person": "บุคคล", - "person_birthdate": "เกิดวัน{date}", - "photo_shared_all_users": "ดูเหมือนว่าคุณได้แชร์รูปภาพของคุณกับผู้ใช้ทั้งหมด หรือคุณไม่มีผู้ใช้ใดที่จะแชร์ด้วย", - "photos": "รูปภาพ", - "photos_and_videos": "รูปภาพ และ วิดีโอ", - "photos_count": "{count, plural, one {{count, number} รูป} other {{count, number} รูป}}", - "photos_from_previous_years": "ภาพถ่ายจากปีก่อน", - "pick_a_location": "เลือกตําแหน่ง", - "pin_code_changed_successfully": "เปลี่ยนรหัสประจำตัว (PIN) สำเร็จ", - "pin_code_reset_successfully": "ตั้งรหัสประจำตัว (PIN) ใหม่สำเร็จ", - "pin_code_setup_successfully": "ตั้งรหัสประจำตัว (PIN) สำเร็จ", - "place": "สถานที่", - "places": "สถานที่", - "play": "เล่น", - "play_memories": "เล่นความทรงจํา", - "play_motion_photo": "เล่นภาพวัตถุเคลื่อนไหว", - "play_or_pause_video": "เล่นหรือหยุดวิดีโอ", - "port": "พอร์ต", - "preferences_settings_title": "การตั้งค่า", - "preset": "พรีเซ็ต", - "preview": "ตัวอย่าง", - "previous": "ก่อนหน้า", - "previous_memory": "ความทรงจําก่อนหน้า", - "previous_or_next_photo": "ภาพก่อนหน้าหรือภาพถัดไป", - "primary": "หลัก", - "privacy": "ความเป็นส่วนตัว", - "profile_drawer_app_logs": "การบันทึก", - "profile_drawer_client_server_up_to_date": "ไคลเอนต์และเซิร์ฟเวอร์เป็นปัจจุบัน", - "profile_image_of_user": "รูปภาพโปรไฟล์ของ {user}", - "profile_picture_set": "ตั้งภาพโปรไฟล์แล้ว", - "public_album": "อัลบั้มสาธารณะ", - "public_share": "แชร์แบบสาธารณะ", - "purchase_account_info": "ผู้สนับสนุน", - "purchase_activated_subtitle": "ขอบคุณสำหรับการสนับสนุน Immich และซอฟต์แวร์เสรี (Open source software)", - "purchase_activated_time": "เปิดใช้งานวันที่ {date}", - "purchase_activated_title": "รหัสของคุณถูกเปิดใช้งานเรียบร้อยแล้ว", - "purchase_button_activate": "เปิดใช้งาน", - "purchase_button_buy": "ซื้อ", - "purchase_button_buy_immich": "ซื้อ Immich", - "purchase_button_never_show_again": "ยังไม่ต้องแสดง", - "purchase_button_reminder": "เตือนฉันในอีก 30 วัน", - "purchase_button_remove_key": "ลบรหัส", - "purchase_button_select": "เลือก", - "purchase_failed_activation": "เปิดใช้งานไม่สำเร็จ! โปรดตรวจสอบอีเมลของคุณเพื่อดูรหัสผลิตภัณฑ์ที่ถูกต้อง!", - "purchase_individual_description_1": "สำหรับบุคคลทั่วไป", - "purchase_individual_description_2": "สถานะผู้สนับสนุน", - "purchase_individual_title": "บุคคลทั่วไป", - "purchase_input_suggestion": "มีรหัสผลิตภัณฑ์หรือไม่? ใส่รหัสด้านล่าง", - "purchase_license_subtitle": "ซื้อ Immich เพื่อรองรับการพัฒนาบริการอย่างต่อเนื่อง", - "purchase_lifetime_description": "ซื้อตลอดชีพ", - "purchase_option_title": "ตัวเลือกการซื้อ", - "purchase_panel_info_1": "ทางทีม Immich ต้องใช้เวลาและความพยายามอย่างมากในการพัฒนาระบบนี้ขึ้นมา และเรามีวิศวกรที่ทำงานเต็มเวลาเพื่อพัฒนาให้ดีที่สุดเท่าที่จะทำได้ ภารกิจของเราคือการทำให้ซอฟต์แวร์โอเพ่นซอร์สและแนวทางปฏิบัติทางธุรกิจที่ถูกต้องตามจริยธรรมกลายเป็นแหล่งรายได้ที่ยั่งยืนสำหรับนักพัฒนา และสร้างระบบนิเวศที่เคารพความเป็นส่วนตัวพร้อมทางเลือกอื่นที่เป็นรูปธรรมแทนบริการคลาวด์ที่เอารัดเอาเปรียบ", - "purchase_panel_info_2": "เนื่องจากเราให้คำมั่นว่า จะไม่เพิ่มระบบชำระเงินในระบบของเรา ดังนั้นการซื้อครั้งนี้จะไม่ทำให้คุณได้รับฟีเจอร์เพิ่มเติมใน Immich เป็นพิเศษ เราอาศัยผู้คนแบบคุณในการสนับสนุนการพัฒนาอย่างต่อเนื่องของ Immich", - "purchase_panel_title": "สนับสนุนโครงการนี้", - "purchase_per_server": "ต่อเซิร์ฟเวอร์", - "purchase_per_user": "ต่อผู้ใช้งาน", - "purchase_remove_product_key": "ลบรหัสผลิตภันฑ์", - "purchase_remove_product_key_prompt": "คุณแน่ใจว่าต้องการลบรหัสผลิตภัณฑ์หรือไม่?", - "purchase_remove_server_product_key": "ลบรหัสผลิตภัณฑ์เซิร์ฟเวอร์", - "purchase_remove_server_product_key_prompt": "คุณแน่ใจหรือไม่ว่าต้องการลบรหัสผลิตภัณฑ์เซิร์ฟเวอร์?", - "purchase_server_description_1": "สำหรับทั้งเซิฟเวอร์", - "purchase_server_description_2": "สถานะผู้สนับสนุน", - "purchase_server_title": "เซิฟเวอร์", - "purchase_settings_server_activated": "รหัสผลิตภัณฑ์เซิร์ฟเวอร์ได้รับการจัดการโดยผู้ดูแลระบบ", - "rating": "การให้คะแนน", - "rating_clear": "ล้างคะแนน", - "rating_count": "{count, plural, one {# ดาว} other {# ดาว}}", - "rating_description": "แสดงคะแนน EXIF ใน Info panel", - "reaction_options": "ตัวเลือก reaction", - "read_changelog": "อ่านบันทึกการเปลี่ยนแปลง", - "reassign": "มอบหมายใหม่", - "reassigned_assets_to_existing_person": "มอบหมาย {count, plural, one {# สื่อ} other {# สื่อ}} ให้กับ {name, select, null {an existing person} other {{name}}}", - "reassigned_assets_to_new_person": "มอบหมาย {count, plural, one {# สื่อ} other {# สื่อ}} ให้กับบุคคลใหม่", - "reassing_hint": "มอบหมายสื่อที่เลือกให้กับบุคคลที่มีอยู่แล้ว", - "recent": "ล่าสุด", - "recent-albums": "อัลบั้มล่าสุด", - "recent_searches": "การค้นหาล่าสุด", - "recently_added_page_title": "เพิ่มล่าสุด", - "refresh": "รีเฟรช", - "refresh_encoded_videos": "โหลดการ encoded วิดีโอใหม่", - "refresh_faces": "รีเฟรชใบหน้า", - "refresh_metadata": "รีเฟรชข้อมูลเมตาดาต้า", - "refresh_thumbnails": "รีโหลดรูป thumbnails", - "refreshed": "รีเฟรช", - "refreshes_every_file": "รีเฟรชทุกไฟล์", - "refreshing_encoded_video": "กำลังรีเฟรชการเข้ารหัสวิดีโอ", - "refreshing_faces": "กำลังรีเฟรชใบหน้า", - "refreshing_metadata": "กำลังรีเฟรชข้อมูลเมตาดาต้า", - "regenerating_thumbnails": "กำลังสร้างรูป thumbnails ใหม่", - "remove": "ลบ", - "remove_assets_album_confirmation": "คุณแน่ใจที่จะลบ {count, plural, one {# สื่อ} other {# สื่อ}} ออกจากอัลบั้ม ?", - "remove_assets_shared_link_confirmation": "คุณแน่ใจที่จะลบ {count, plural, one {# สื่อ} other {# สื่อ}} ออกจากลิงก์ที่แชร์นี้ ?", - "remove_assets_title": "ลบสื่อใช่ไหม ?", - "remove_custom_date_range": "ลบการปรับช่วงเวลา", - "remove_deleted_assets": "ลบสื่อที่ถูกลบ", - "remove_from_album": "ลบออกจากอัลบั้ม", - "remove_from_favorites": "เอาออกจากรายการโปรด", - "remove_from_shared_link": "ลบออกจากลิงก์ที่แชร์", - "remove_memory": "ลบความทรงจำ", - "remove_photo_from_memory": "ลบรูปออกจากความทรงจำนี้", - "remove_url": "ลบ URL", - "remove_user": "ลบผู้ใช้", - "removed_api_key": "API คีย์ของ: {name} ถูกลบแล้ว", - "removed_from_archive": "ลบจากเก็บถาวรแล้ว", - "removed_from_favorites": "ลบจากรายการโปรดแล้ว", - "removed_from_favorites_count": "{count, plural, other {ถูกลบ#}} จากรายการโปรดแล้ว", - "removed_memory": "ความทรงจำที่ถูกลบ", - "removed_photo_from_memory": "รูปที่ถูกลบออกจากความทรงจำ", - "removed_tagged_assets": "ลบแท็กจาก {count, plural, one {# สื่อ} other {# สื่อ}}", - "rename": "เปลี่ยนชื่อ", - "repair": "ซ่อมแซม", - "repair_no_results_message": "ไม่สามารถซ่อมแซมได้", - "replace_with_upload": "อัปโหลดทับรูปหรือวิดีโอนี้", - "require_password": "ต้องการรหัสผ่าน", - "require_user_to_change_password_on_first_login": "จำเป็นต้องเปลี่ยนรหัสผ่าน ในการเข้าสู่ระบบครั้งแรก", - "rescan": "สแกนใหม่", - "reset": "รีเซ็ต", - "reset_password": "ตั้งค่ารหัสผ่านใหม่", - "reset_people_visibility": "ปรับการมองเห็นใหม่", - "reset_pin_code": "ตั้งรหัสประจำตัว (PIN) ใหม่", - "reset_to_default": "กลับไปค่าเริ่มต้น", - "resolve_duplicates": "แก้ไขข้อมูลซ้ำซ้อน", - "resolved_all_duplicates": "แก้ไขข้อมูลซ้ำซ้อนทั้งหมด", - "restore": "เรียกคืน", - "restore_all": "เรียกคืนทั้งหมด", - "restore_user": "เรียกคืนผู้ใช้", - "restored_asset": "asset ถูกคืนค่า", - "resume": "กลับคืน", - "retry_upload": "ลองอัปโหลดใหม่", - "review_duplicates": "ตรวจสอบรายการที่ซ้ำกัน", - "review_large_files": "ตรวจสอบไฟล์ที่มีขนาดใหญ่", - "role": "บทบาท", - "role_editor": "เครื่องมือแก้ไข", - "role_viewer": "ดู", - "save": "บันทึก", - "saved_api_key": "บันทึก API คีย์ แล้ว", - "saved_profile": "แก้ไขโปรไฟล์สำเร็จ", - "saved_settings": "บันทึกการตั้งค่าสำเร็จ", - "say_something": "พูดอะไรสักอย่าง", - "scaffold_body_error_occurred": "เกิดข้อผิดพลาด", - "scan_all_libraries": "สแกนคลังภาพทั้งหมด", - "scan_library": "สแกน", - "scan_settings": "ตั้งค่าการสแกน", - "scanning_for_album": "กำลังสแกนอัลบั้ม...", - "search": "ค้นหา", - "search_albums": "ค้นหาอัลบั้ม", - "search_by_context": "ค้นหาตามบริบท", - "search_by_description": "ค้นหาด้วยคำอธิบาย", - "search_by_description_example": "วันปีนเขาในซาปา", - "search_by_filename": "ค้นหาชื่อไฟล์ชื่อ หรือ ชนิดไฟล์", - "search_by_filename_example": "ตัวอย่าง. IMG_1234.JPG หรือ PNG", - "search_camera_make": "ค้นหายี่ห้อกล้อง", - "search_camera_model": "ค้นหารุ่นกล้อง", - "search_city": "ค้นหาตามเมือง", - "search_country": "ค้นหาตามประเทศ", - "search_filter_apply": "บันทึกตัวกรอง", - "search_filter_display_option_not_in_album": "ไม่อยู่ในอัลบั้ม", - "search_for": "การค้นหาสำหรับ", - "search_for_existing_person": "ค้นหาบุคคลที่มีอยู่", - "search_no_people": "ไม่พบบุคคลคน", - "search_no_people_named": "ไม่พบ \"{name}\"", - "search_options": "ตัวเลือกการค้นหา", - "search_page_categories": "หมวดหมู่", - "search_page_motion_photos": "ภาพเคลื่อนไหว", - "search_page_no_objects": "ไม่มีข้อมูลรายการ", - "search_page_no_places": "ไม่มีข้อมูลสถานที่", - "search_page_screenshots": "แคปหน้าจอ", - "search_page_selfies": "เซลฟี่", - "search_page_things": "สิ่งของ", - "search_page_view_all_button": "ดูทั้งหมด", - "search_page_your_activity": "กิจกรรมของคุณ", - "search_page_your_map": "แผนที่คุณ", - "search_people": "ค้นหาผู้คน", - "search_places": "ค้นหาสถานที่", - "search_rating": "ค้นหาตามเรตติ้ง...", - "search_result_page_new_search_hint": "ค้นหาใหม่", - "search_settings": "ตั้งค่าการค้นหา", - "search_state": "ค้นหาตามรัฐ", - "search_suggestion_list_smart_search_hint_1": "การค้นหาอัจฉริยะเปิดเป็นค่าเริ่มต้น เพื่อค้นหา metadata ให้ใช้ไวยากรณ์", - "search_suggestion_list_smart_search_hint_2": "m:คำค้นหา", - "search_tags": "ค้นหาแท็ก", - "search_timezone": "ค้นหาตามวันที่และเวลา", - "search_type": "ค้นหาตามประเภท", - "search_your_photos": "ค้นหารูปภาพของคุณ", - "searching_locales": "ค้นหาตามภูมิภาค", - "second": "วินาที", - "see_all_people": "ดูบุคคลทั้งหมด", - "select": "เลือก", - "select_album_cover": "เลือกภาพปกอัลบั้ม", - "select_all": "เลือกทั้งหมด", - "select_all_duplicates": "เลือกรายการที่ซ้ำทั้งหมด", - "select_avatar_color": "เลือกสีพื้นหลังของรูปโปรไฟล์", - "select_face": "เลือกใบหน้า", - "select_featured_photo": "เลือกภาพเด่น", - "select_from_computer": "เลือกจากคอมพิวเตอร์", - "select_keep_all": "เลือกเก็บทั้งหมด", - "select_library_owner": "เลือกเจ้าของคลังภาพ", - "select_new_face": "เลือกใบหน้าใหม่", - "select_person_to_tag": "เลือกบุคคล", - "select_photos": "เลือกรูปภาพ", - "select_trash_all": "เลือกในถังขยะทั้งหมด", - "select_user_for_sharing_page_err_album": "สร้างอัลบั้มล้มเหลว", - "selected": "เลือก", - "selected_count": "{count, plural, other {# เลือกแล้ว}}", - "send_message": "ส่งข้อความ", - "send_welcome_email": "ส่งอีเมลต้อนรับ", - "server_endpoint": "ปลายทางเซิร์ฟเวอร์", - "server_info_box_app_version": "เวอร์ชันแอพ", - "server_info_box_server_url": "URL เซิร์ฟเวอร์", - "server_offline": "Server ออฟไลน์", - "server_online": "Server ออนไลน์", - "server_privacy": "ความเป็นส่วนตัวเซิร์ฟเวอร์", - "server_stats": "สถิติเซิร์ฟเวอร์", - "server_version": "เวอร์ชันของเซิร์ฟเวอร์", - "set": "ตั้ง", - "set_as_album_cover": "ตั้งเป็นภาพปกอัลบั้ม", - "set_as_featured_photo": "ตั้งเป็นรูปสำคัญ", - "set_as_profile_picture": "ตั้งเป็นรูปโปรไฟล์", - "set_date_of_birth": "ตั้งวันเกิด", - "set_profile_picture": "ตั้งรูปโปรไฟล์", - "set_slideshow_to_fullscreen": "ตั้งค่าการนำเสนอเต็มจอ", - "setting_image_viewer_help": "ตัวดูรายละเอียดโหลดรูปย่อก่อน แล้วจะโหลดพรีวิวขนาดกลาง (ถ้าเปิด) แล้วค่อยโหลดรูปต้นฉบับ (ถ้าเปิด)", - "setting_image_viewer_original_subtitle": "เปิดเพื่อโหลดรูปต้นฉบับความละเอียดเต็ม (ใหญ่!) ปิดเพื่อลดการใช้ข้อมูล (ทั้งบนเครือข่ายและแคชบนเครื่อง)", - "setting_image_viewer_original_title": "โหลดรูปต้นฉบับ", - "setting_image_viewer_preview_subtitle": "เปิดเพื่อโหลดรูปภาพความละเอียดปานกลาง เปิดเพื่อโหลดรูปต้นฉบับโดยตรงหรือใช้แค่รูปย่อ", - "setting_image_viewer_preview_title": "โหลดรูปภาพตัวอย่าง", - "setting_image_viewer_title": "รูปภาพ", - "setting_languages_apply": "บันทึก", - "setting_notifications_notify_failures_grace_period": "แจ้งการสำรองข้อมูลในเบื้องหลังล้มเหลว: {duration}", - "setting_notifications_notify_hours": "{count} ชั่วโมง", - "setting_notifications_notify_immediately": "โดยทันที", - "setting_notifications_notify_minutes": "{count} นาที", - "setting_notifications_notify_never": "ไม่เคย", - "setting_notifications_notify_seconds": "{count} วินาที", - "setting_notifications_single_progress_subtitle": "ข้อมูลความคืบหน้าการอัปโหลดโดยละเอียดต่อทรัพยากร", - "setting_notifications_single_progress_title": "แสดงรายละเอียดสถานะการสำรองข้อมูลในเบื้องหลัง", - "setting_notifications_subtitle": "ตั้งค่าการแจ้งเตือน", - "setting_notifications_total_progress_subtitle": "ความคืบหน้าการอัปโหลดโดยรวม (เสร็จสิ้น/ทรัพยากรทั้งหมด)", - "setting_notifications_total_progress_title": "แสดงสถานะการสำรองข้อมูลในเบื้องหลังทั้งหมด", - "setting_video_viewer_looping_title": "วนลูป", - "settings": "ตั้งค่า", - "settings_require_restart": "กรุณารีสตาร์ท Immmich เพื่อใช้การตั้งค่า", - "settings_saved": "บันทึกการตั้งค่าแล้ว", - "setup_pin_code": "ตั้งรหัสประจำตัว (PIN)", - "share": "แชร์", - "share_add_photos": "เพิ่มรูปภาพ", - "share_assets_selected": "{count} ถูกเลือก", - "share_dialog_preparing": "กำลังเตรียม...", - "shared": "แชร์", - "shared_album_activities_input_disable": "คอมเมนต์ถูกปิด", - "shared_album_activity_remove_content": "ุคุณต้องการลบกิจกรรมนี้หรือไม่?", - "shared_album_activity_remove_title": "ลบกิจกรรม", - "shared_album_section_people_action_error": "เกิดข้อผิดพลาดในการออก/นำออกจากอัลบั้ม", - "shared_album_section_people_action_leave": "นำผู้ใช้งานออกจากอัลบั้ม", - "shared_album_section_people_action_remove_user": "นำผู้ใช้งานออกจากอัลบั้ม", - "shared_album_section_people_title": "ผู้คน", - "shared_by": "แชร์โดย", - "shared_by_user": "แชร์โดย {user}", - "shared_by_you": "แชร์โดยคุณ", - "shared_from_partner": "รูปจาก {partner}", - "shared_link_app_bar_title": "ลิงก์ที่แชร์", - "shared_link_clipboard_copied_massage": "คัดลอกลงคลิปบอร์ด", - "shared_link_clipboard_text": "ลิงก์: {link}\nรหัสผ่าน: {password}", - "shared_link_create_error": "เกิดข้อผิดพลาดขณะสร้างลิงก์แชร์", - "shared_link_edit_description_hint": "เพิ่มรายละเอียดการแชร์", - "shared_link_edit_expire_after_option_day": "1 วัน", - "shared_link_edit_expire_after_option_days": "{count} วัน", - "shared_link_edit_expire_after_option_hour": "1 ชั่วโมง", - "shared_link_edit_expire_after_option_hours": "{count} ชั่วโมง", - "shared_link_edit_expire_after_option_minute": "1 นาที", - "shared_link_edit_expire_after_option_minutes": "{count} นาที", - "shared_link_edit_expire_after_option_months": "{count} เดือน", - "shared_link_edit_expire_after_option_year": "{count} ปี", - "shared_link_edit_password_hint": "กรอกรหัสผ่านแชร์", - "shared_link_edit_submit_button": "อัปเดตลิงก์", - "shared_link_error_server_url_fetch": "ไม่สามารถรับ URL จากเซิร์ฟเวอร์", - "shared_link_expires_day": "หมดอายุในอีก {count} วัน", - "shared_link_expires_days": "หมดอายุในอีก {count} วัน", - "shared_link_expires_hour": "หมดอายุในอีก {count} ชั่วโมง", - "shared_link_expires_hours": "หมดอายุในอีก {count} ชั่วโมง", - "shared_link_expires_minute": "หมดอายุในอีก {count} นาที", - "shared_link_expires_minutes": "หมดอายุในอีก {count} นาที", - "shared_link_expires_never": "ไม่มีวันหมดอายุ", - "shared_link_expires_second": "หมดอายุในอีก {count} วินาที", - "shared_link_expires_seconds": "หมดอายุในอีก {count} วินาที", - "shared_link_individual_shared": "แชร์รายบุคคล", - "shared_link_manage_links": "บริหารลิงก์", - "shared_link_options": "ตั้งค่าลิงก์ที่แชร์", - "shared_links": "ลิงก์ที่แชร์", - "shared_links_description": "แบ่งปันรูปและวีดีโอด้วยลิ้งค์", - "shared_with_partner": "แชร์กับ {partner}", - "sharing": "การแชร์", - "sharing_enter_password": "โปรดป้อนรหัสผ่าน สำหรับเปิดดูหน้านี้", - "sharing_page_album": "อัลบั้มที่แชร์", - "sharing_page_description": "สร้างอัลบั้มที่แชร์เพื่อแชร์รูปภาพและวิดีโอให้กับคนบนเครือข่ายคุณ", - "sharing_page_empty_list": "รายการว่างเปล่า", - "sharing_sidebar_description": "แสดงลิงก์ที่แชร์ในแถบด้านข้าง", - "sharing_silver_appbar_create_shared_album": "อัลบั้มที่แชร์ใหม่", - "sharing_silver_appbar_share_partner": "แชร์กับคู่หู", - "shift_to_permanent_delete": "กด ⇧ to สำหรับลบสื่อถาวร", - "show_album_options": "แสดงตัวเลือกอัลบั้ม", - "show_albums": "แสดงอัลบั้ม", - "show_all_people": "แสดงบุคคลทั้งหมด", - "show_and_hide_people": "แสดง & ซ่อนบุคคล", - "show_file_location": "แสดงตําแหน่งของไฟล์", - "show_gallery": "แสดงคลังภาพ", - "show_hidden_people": "แสดงคนที่ซ่อนไว้", - "show_in_timeline": "แสดงในไทม์ไลน์", - "show_in_timeline_setting_description": "แสดงรูปภาพและวิดีโอของผู้ใช้นี้ในไทม์ไลน์ของคุณ", - "show_keyboard_shortcuts": "แสดงปุ่มลัดแป้นพิมพ์", - "show_metadata": "แสดง metadata", - "show_or_hide_info": "แสดงหรือซ่อนข้อมูล", - "show_password": "แสดงรหัสผ่าน", - "show_person_options": "แสดงตัวเลือกของตัวบุคคล", - "show_progress_bar": "แสดงความคืบหน้า แถบ", - "show_search_options": "แสดงตัวเลือกการค้นหา", - "show_shared_links": "แสดงลิ้งค์ที่ถูกแบ่งปัน", - "show_slideshow_transition": "แสดงสไลค์โชว์", - "show_supporter_badge": "เครื่องหมายผู้สนับสนุน", - "show_supporter_badge_description": "แสดงเครื่องหมายผู้สนับสนุน", - "shuffle": "สับเปลี่ยน", - "sidebar": "แถบด้านข้าง", - "sidebar_display_description": "เปิดหรือปิดแถบด้านข้าง", - "sign_out": "ออกจากระบบ", - "sign_up": "ลงทะเบียน", - "size": "ขนาด", - "skip_to_content": "ข้ามไปยังเนื้อหา", - "skip_to_folders": "ข้ามโฟล์เดอร์", - "skip_to_tags": "ข้ามแท็ก", - "slideshow": "สไลด์", - "slideshow_settings": "ตั้งค่าสไลด์", - "sort_albums_by": "จัดเรียงอัลบั้มโดย...", - "sort_created": "จัดเรียงตามวันที่สร้าง", - "sort_items": "จัดเรียงรายการ", - "sort_modified": "จัดเรียงตามวันที่แก้ไข", - "sort_oldest": "จัดเรียงตามเก่าสุด", - "sort_people_by_similarity": "จุดเรียงบุคคลตามความคล้ายคลึง", - "sort_recent": "จัดเรียงใหม่ล่าสุด", - "sort_title": "ไตเติ้ล", - "source": "แหล่ง", - "stack": "ซ้อน", - "stack_duplicates": "นำสิ่งที่ซ้ำมาซ้อนอยู่ด้วยกัน", - "stack_select_one_photo": "เลือกรูปหลักหนึ่งรูปสำหรับรูปที่ซ้อนกันนี้", - "stack_selected_photos": "ซ้อนรูปที่ถูกเลือก", - "start": "เริ่มต้น", - "start_date": "วันที่เริ่ม", - "state": "รัฐ", - "status": "สถานะ", - "stop_motion_photo": "ภาพวัตถุเคลื่อนไหว", - "stop_photo_sharing": "หยุดแชร์รูปภาพ?", - "stop_photo_sharing_description": "{partner} จะไม่สามารถเข้าถึงรูปของคุณได้อีก", - "stop_sharing_photos_with_user": "หยุดการแชร์รูปภาพของคุณกับผู้ใช้นี้", - "storage": "พื้นที่จัดเก็บ", - "storage_label": "เนื้อที่จัดเก็บ", - "storage_usage": "ใช้ไป {used} จาก {available}", - "submit": "ส่ง", - "suggestions": "ข้อเสนอแนะ", - "sunrise_on_the_beach": "พระอาทิตย์ขึ้นบนชายหาด", - "support": "สนับสนุน", - "support_and_feedback": "สนับสนุน & ข้อเสนอแนะ", - "support_third_party_description": "การติดตั้ง Immich ของคุณถูกจัดทำแพ็กเกจโดยบุคคลที่สาม ปัญหาที่คุณพบอาจเกิดจากแพ็กเกจดังกล่าว ดังนั้นโปรดแจ้งปัญหาที่เกิดขึ้นกับบุคคลที่สามก่อนโดยใช้ลิงก์ด้านล่าง", - "swap_merge_direction": "สลับด้านรวม", - "sync": "ซิงค์", - "tag": "แท็ก", - "tag_created": "สร้างแท็ก: {tag}", - "tag_not_found_question": "ไม่สามารถหาแท็กได้ใช่หรือไม่?สร้างแท็กใหม่", - "tag_people": "แท็กผู้คน", - "tag_updated": "แท็กที่ถูกอัพเดต: {tag}", - "tags": "แท็ก", - "template": "เท็มเพลต", - "theme": "ธีม", - "theme_selection": "การเลือกธีม", - "theme_selection_description": "ตั้งค่าธีมให้สว่างหรือมืดโดยอัตโนมัติ อิงจากค่าของเบราว์เซอร์ของคุณ", - "theme_setting_asset_list_storage_indicator_title": "แสดงตัวพื้นที่จัดเก็บบนตารางทรัพยากร", - "theme_setting_asset_list_tiles_per_row_title": "จำนวนทรัพยากรต่อแถว ({count})", - "theme_setting_image_viewer_quality_subtitle": "ปรับคุณภาพขอตัวดูรูปภาพละเอียด", - "theme_setting_image_viewer_quality_title": "คุณภาพตังดูรูปภาพ", - "theme_setting_primary_color_title": "สีหลัก", - "theme_setting_system_primary_color_title": "ใช้สีของระบบ", - "theme_setting_system_theme_switch": "อัตโนมัติ (การตั้งค่าระบบ)", - "theme_setting_theme_subtitle": "เลือกธีมของแอพ", - "theme_setting_three_stage_loading_subtitle": "การโหลดแบบสามขั้นตอนอาจเพิ่มประสิทธิภาพในการโหลดแต่จะทำให้โหลดเครื่อข่ายเพิ่มขึ้นมาก", - "theme_setting_three_stage_loading_title": "เปิดการโหลดสามขั้นตอน", - "they_will_be_merged_together": "จะถูกรวมเข้าด้วยกัน", - "third_party_resources": "ทรัพยากรบุคคลที่สาม", - "time_based_memories": "ความทรงจําตามเวลา", - "timeline": "ทามไลน์", - "timezone": "เขตเวลา", - "to_archive": "จัดเก็บถาวร", - "to_change_password": "เปลี่ยนรหัสผ่าน", - "to_favorite": "รายการโปรด", - "to_login": "เข้าสู่ระบบ", - "to_parent": "ไปยังบนสุด", - "to_trash": "ถังขยะ", - "toggle_settings": "สลับการตั้งค่า", - "total": "ทั้งหมด", - "total_usage": "การใช้งานรวม", - "trash": "ถังขยะ", - "trash_action_prompt": "{count} ย้ายไปถังขยะ", - "trash_all": "ทิ้งทั้งหมด", - "trash_count": "{count, number} ในถังขยะ", - "trash_no_results_message": "รูปภาพหรือวิดีโอที่ถูกลบจะอยู่ที่นี่", - "trash_page_delete_all": "ลบทั้งหมด", - "trash_page_empty_trash_dialog_content": "คุณแน่ใจว่าต้องการทิ้งขยะทั้งหมด ทรัพยากรพวกนี้จะถูกลบจาก Immich ถาวร", - "trash_page_info": "ทรัพยากรที่ทิ้งจะถูกลบถาวรหลัง {days} วัน", - "trash_page_no_assets": "ไม่มีทรัพยากรในขยะ", - "trash_page_restore_all": "กู้คืนทั้งหมด", - "trash_page_select_assets_btn": "เลือกทรัพยากร", - "trash_page_title": "ขยะ ({count})", - "trashed_items_will_be_permanently_deleted_after": "รายการที่ถูกลบจะถูกลบทิ้งภายใน {days, plural, one {# วัน} other {# วัน}}.", - "troubleshoot": "การแก้ปัญหา", - "type": "ประเภท", - "unable_to_change_pin_code": "ไม่สามารถเปลี่ยนรหัสประจำตัว (PIN)", - "unable_to_setup_pin_code": "ไม่สามารถตั้งรหัสประจำตัว (PIN)", - "unarchive": "นำออกจากที่เก็บถาวร", - "unarchive_action_prompt": "{count} ถูกนำออกจากที่เก็บถาวร", - "undo": "เลิกทำ", - "unfavorite": "นำออกจากรายการโปรด", - "unfavorite_action_prompt": "{count} ถูกนำออกจากรายการโปรด", - "unhide_person": "ยกเลิกซ่อนบุคคล", - "unknown": "ไม่ทราบ", - "unknown_country": "ไม่ทราบประเทศ", - "unknown_date": "ไม่ทราบวัน", - "unknown_year": "ไม่ทราบปี", - "unlimited": "ไม่จำกัด", - "unlink_oauth": "ยกเลิกเชื่อมต่อ OAuth", - "unlinked_oauth_account": "ยกเลิกการเชื่อมต่อ OAuth", - "unnamed_album": "อัลบั้มไม่มีชื่อ", - "unnamed_album_delete_confirmation": "คุณต้องการจะลบอัลบั้มนี้ ใช่หรือไม่ ?", - "unnamed_share": "แชร์แบบไม่ระบุชื่อ", - "unselect_all": "ยกเลิกการเลือกทั้งหมด", - "unselect_all_in": "ยกเลิกการเลือกทั้งหมดใน {group}", - "unstack": "หยุดซ้อน", - "up_next": "ต่อไป", - "updated_at": "อัพเดท", - "updated_password": "รหัสผ่านเปลี่ยนแล้ว", - "upload": "อัปโหลด", - "upload_concurrency": "อัปโหลดพร้อมกัน", - "upload_details": "รายละเอียดการอัปโหลด", - "upload_dialog_info": "คุณต้องการอัพโหลดทรัพยากรดังกล่าวบนเซิร์ฟเวอร์หรือไม่?", - "upload_dialog_title": "อัปโหลดทรัพยากร", - "upload_status_duplicates": "รวมเข้าด้วยกัน", - "upload_status_errors": "ข้อผิดพลาด", - "upload_status_uploaded": "อัปโหลดแล้ว", - "upload_success": "อัปโหลดสำเร็จ, รีเฟรชหน้านี้ใหม่คุณจะเห็นสื่อที่เพิ่มล่าสุด", - "uploading": "กำลังอัพโหลด", - "uploading_media": "กำลังอัปโหลดสื่อ", - "usage": "การใช้งาน", - "use_biometric": "ใช้การพิสูจน์อัตลักษณ์", - "use_current_connection": "ใช้การเชื่อมต่อปัจจุบัน", - "use_custom_date_range": "ใช้การปรับแต่งช่วงเวลา", - "user": "ผู้ใช้", - "user_has_been_deleted": "ผู้ใช้รายนี้ถูกลบไปแล้ว", - "user_id": "ไอดีผู้ใช้", - "user_pin_code_settings": "รหัสประจำตัว (PIN)", - "user_pin_code_settings_description": "จัดการรหัสประจำตัว (PIN)", - "user_privacy": "ความเป็นส่วนตัวผู้ใช้", - "user_purchase_settings": "ซื้อ", - "user_purchase_settings_description": "จัดการการซื้อ", - "user_role_set": "ตั้ง {role} ให้กับ {user}", - "user_usage_detail": "รายละเอียดการใช้งานของผู้ใช้", - "user_usage_stats": "สถิติการใช้งานบัญชี", - "user_usage_stats_description": "ดูสถิติการใช้งานบัญชี", - "username": "ชื่อผู้ใช้", - "users": "ผู้ใช้", - "utilities": "เครื่องมือ", - "validate": "ตรวจสอบ", - "validate_endpoint_error": "กรุณาระบุ URL ที่ถูกต้อง", - "validation_error": "การตรวจสอบข้อมูลล้มเหลว", - "variables": "ตัวแปร", - "version": "รุ่น", - "version_announcement_closing": "เพื่อนของคุณ อเล็กซ์", - "version_announcement_message": "สวัสดี! Immich เวอร์ชันใหม่พร้อมให้ใช้งานแล้ว โปรดใช้เวลาสักครู่เพื่ออ่าน หมายเหตุการเผยแพร่ เพื่อให้แน่ใจว่าการตั้งค่าของคุณได้รับการอัปเดตแล้ว เพื่อป้องกันการกำหนดค่าผิดพลาด โดยเฉพาะอย่างยิ่งหากคุณใช้ WatchTower หรือกลไกอื่นๆ ที่จัดการการอัปเดตอินสแตนซ์ Immich ของคุณโดยอัตโนมัติ", - "version_history": "การเปลี่ยนแปลง", - "version_history_item": "ติดตั้ง {version} วันที่ {date}", - "video": "วิดีโอ", - "video_hover_setting": "เล่นวิดีโอแบบย่อเมื่อเลื่อนเมาส์อยู่บน", - "video_hover_setting_description": "เล่นวิดีโอตัวอย่างเมื่อเมาส์จ่อข้างบน เมื่อปิดใช้งาน วิดีโอตัวอย่างยังสามารถเล่นได้โดยกดปุ่มเล่น", - "videos": "วิดีโอ", - "videos_only": "วิดีโอเท่านั้น", - "view": "ดู", - "view_album": "ดูอัลบั้ม", - "view_all": "ดูทั้งหมด", - "view_all_users": "ดูผู้ใช้ทั้งหมด", - "view_details": "ดูรายละเอียด", - "view_in_timeline": "ดูไทม์ไลน์", - "view_link": "ดูลิงก์", - "view_links": "ดูลิงก์", - "view_next_asset": "ดูสื่อถัดไป", - "view_previous_asset": "ดูสื่อก่อนหน้า", - "view_qr_code": "ดูคิวอาร์โค้ด", - "view_similar_photos": "ดูรูปที่คล้ายกัน", - "view_user": "ดูผู้ใช้งาน", - "viewer_remove_from_stack": "เอาออกจากที่ซ้อน", - "viewer_stack_use_as_main_asset": "ใช้เป็นทรัพยากรหลัก", - "viewer_unstack": "หยุดซ้อน", - "visibility_changed": "เปลี่ยนแปลงการมองเห็นสำหรับ {count, plural, one {# บุคคล} other {# บุคคล}}", - "waiting": "กำลังรอ", - "warning": "คำเตือน", - "week": "สัปดาห์", - "welcome": "ยินดีต้อนรับ", - "welcome_to_immich": "ยินดีต้อนรับสู่ immich", - "width": "ความกว้าง", - "wifi_name": "ชื่อ Wi-Fi", - "wrong_pin_code": "รหัส PIN ไม่ถูกต้อง", - "year": "ปี", - "years_ago": "{years, plural, one {# ปี} other {# ปี}} ที่แล้ว", - "yes": "ใช่", - "you_dont_have_any_shared_links": "คุณไม่ได้มีลิงก์ที่แชร์", - "your_wifi_name": "ชื่อ Wi-Fi", - "zoom_image": "ซูมรูปภาพ" -} +{} diff --git a/i18n/tr.json b/i18n/tr.json index a334ab789e..0967ef424b 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -1,2400 +1 @@ -{ - "about": "Hakkında", - "account": "Hesap", - "account_settings": "Hesap Ayarları", - "acknowledge": "Onayla", - "action": "Eylem", - "action_common_update": "Güncelle", - "action_description": "Filtrelenmiş öğeler üzerinde gerçekleştirilecek bir dizi eylem", - "actions": "Eylemler", - "active": "Aktif", - "active_count": "Aktif: {count}", - "activity": "Etkinlik", - "activity_changed": "Etkinlik {enabled, select, true {etkin} other {devre dışı}}", - "add": "Ekle", - "add_a_description": "Açıklama ekle", - "add_a_location": "Bir konum ekle", - "add_a_name": "İsim ekle", - "add_a_title": "Bir başlık ekleyin", - "add_action": "Eylem ekle", - "add_action_description": "Gerçekleştirmek istediğiniz eylemi eklemek için tıklayın", - "add_assets": "Varlık ekle", - "add_birthday": "Doğum günü ekle", - "add_endpoint": "Uç nokta ekle", - "add_exclusion_pattern": "Hariç tutma deseni ekle", - "add_filter": "Filtre ekle", - "add_filter_description": "Filtre koşulu eklemek için tıklayın", - "add_location": "Konum ekle", - "add_more_users": "Daha fazla kullanıcı ekle", - "add_partner": "Ortak ekle", - "add_path": "Yol ekle", - "add_photos": "Fotoğraf ekle", - "add_tag": "Etiket ekle", - "add_to": "Şuraya ekle…", - "add_to_album": "Albüme ekle", - "add_to_album_bottom_sheet_added": "{album} albümüne eklendi", - "add_to_album_bottom_sheet_already_exists": "Zaten {album} albümüne ekli", - "add_to_album_bottom_sheet_some_local_assets": "Bazı yerel öğeler albüme eklenemedi", - "add_to_album_toggle": "{album} için seçimi değiştir", - "add_to_albums": "Albümlere ekle", - "add_to_albums_count": "{count} albümlerine ekle", - "add_to_bottom_bar": "Şuraya ekle", - "add_to_shared_album": "Paylaşılan albüme ekle", - "add_upload_to_stack": "Yüklemeyi yığına ekle", - "add_url": "URL ekle", - "add_workflow_step": "İş akışı adımı ekle", - "added_to_archive": "Arşive eklendi", - "added_to_favorites": "Favorilere eklendi", - "added_to_favorites_count": "{count, number} fotoğraf favorilere eklendi", - "admin": { - "add_exclusion_pattern_description": "Hariç tutma desenleri ekleyin. *, ** ve ? kullanılarak Globbing (temsili yer doldurucu karakter) desteklenir. Farzedelim \"Raw\" adlı bir dizininiz var, içinde ki tüm dosyaları yoksaymak için \"**/Raw/**\" şeklinde yazabilirsiniz. \".tif\" ile biten tüm dosyaları yoksaymak için \"**/*.tif\" yazabilirsiniz. Mutlak yolu yoksaymak için \"/yoksayılacak/olan/yol/**\" şeklinde yazabilirsiniz.", - "admin_user": "Yönetici Kullanıcı", - "asset_offline_description": "Bu harici kütüphane öğesi artık diskte bulunmuyor ve çöp kutusuna taşındı. Dosya kütüphane içinde taşındıysa, yeni karşılık gelen öğe için zaman çizelgenizi kontrol edin. Bu öğeyi geri yüklemek için lütfen aşağıdaki dosya yolunun Immich tarafından erişilebilir olduğundan emin olun ve kütüphaneyi tarayın.", - "authentication_settings": "Yetkilendirme Ayarları", - "authentication_settings_description": "Şifre, OAuth, ve diğer yetkilendirme ayarlarını yönet", - "authentication_settings_disable_all": "Tüm giriş yöntemlerini devre dışı bırakmak istediğinize emin misiniz? Giriş yapma fonksiyonu tamamen devre dışı bırakılacak.", - "authentication_settings_reenable": "Yeniden aktif etmek için Sunucu Komutu'nu kullanın.", - "background_task_job": "Arka Plan Görevleri", - "backup_database": "Veritabanı Yığını Oluştur", - "backup_database_enable_description": "Veritabanı yığınlarını etkinleştir", - "backup_keep_last_amount": "Tutulması gereken geçmiş yığını miktarı", - "backup_onboarding_1_description": "bulutta veya başka bir fiziksel konumda bulunan yedek kopya.", - "backup_onboarding_2_description": "farklı cihazlarda yerel kopyalar. Bu, ana dosyaları ve bu dosyaların yerel yedeklerini içerir.", - "backup_onboarding_3_description": "Verilerinizin toplam kopyaları, orijinal dosyalar dahil. Bu, 1 adet dış mekan kopyası ve 2 adet yerel kopya içerir.", - "backup_onboarding_description": "Verilerinizi korumak için 3-2-1 yedekleme stratejisi önerilir. Kapsamlı bir yedekleme çözümü için, yüklediğiniz fotoğrafların/videoların yanı sıra Immich veritabanının kopyalarını da saklamalısınız.", - "backup_onboarding_footer": "Immich'i yedekleme hakkında daha fazla bilgi için lütfen belgelere bakın.", - "backup_onboarding_parts_title": "3-2-1 yedekleme şunları içerir:", - "backup_onboarding_title": "Yedeklemeler", - "backup_settings": "Veritabanı Yığını Ayarları", - "backup_settings_description": "Veritabanı döküm ayarlarını yönet.", - "cleared_jobs": "{job} için işler temizlendi", - "config_set_by_file": "Ayarlar şuanda config dosyası tarafından ayarlanmıştır", - "confirm_delete_library": "{library} kütüphanesini silmek istediğinize emin misiniz?", - "confirm_delete_library_assets": "Bu kütüphaneyi silmek istediğinize emin misiniz? Bu işlem {count, plural, one {# tane öğeyi} other {all # tane öğeyi}} Immich'den silecek ve bu işlem geri alınamaz. Dosyalar diskte kalacaktır.", - "confirm_email_below": "Onaylamak için aşağıya \"{email}\" yazın", - "confirm_reprocess_all_faces": "Tüm yüzleri tekrardan işlemek istediğinize emin misiniz? Bu işlem isimlendirilmiş insanları da silecek.", - "confirm_user_password_reset": "{user} adlı kullanıcının şifresini sıfırlamak istediğinize emin misiniz?", - "confirm_user_pin_code_reset": "{user} adlı kullanıcının PIN kodunu sıfırlamak istediğinize emin misiniz?", - "copy_config_to_clipboard_description": "Geçerli sistem yapılandırmasını bir JSON nesnesi olarak panoya kopyala", - "create_job": "Görev oluştur", - "cron_expression": "Cron ifadesi", - "cron_expression_description": "Cron formatını kullanarak tarama aralığını belirle. Daha fazla bilgi için örneğin Crontab Guru’ya bakın", - "cron_expression_presets": "Cron ifadesi ön ayarları", - "disable_login": "Girişi devre dışı bırak", - "duplicate_detection_job_description": "Benzer fotoğrafları bulmak için makine öğrenmesini çalıştır. Bu işlem Akıllı Arama'ya bağlıdır", - "exclusion_pattern_description": "Kütüphaneyi tararken dosya ve klasörleri görmezden gelmek için dışlama desenlerini kullanabilirsiniz. RAW dosyaları gibi bazı dosya ve klasörleri içe aktarmak istemediğinizde bu seçeneği kullanabilirsiniz.", - "export_config_as_json_description": "Geçerli sistem yapılandırmasını JSON dosyası olarak indir", - "external_libraries_page_description": "Yönetici harici kütüphane sayfası", - "face_detection": "Yüz tarama", - "face_detection_description": "Makine öğrenimi kullanarak varlıklardaki yüzleri tespit et. Videolar için sadece küçük resim (thumbnail) dikkate alınır. 'Yenile' tüm varlıkları yeniden işler. 'Sıfırla', mevcut tüm yüz verilerini temizleyerek işlemi yeniden başlatır. 'Eksik' henüz işlenmemiş varlıkları sıraya alır. Tespit edilen yüzler, Yüz Tanıma işlemi tamamlandıktan sonra mevcut ya da yeni kişilere gruplanmak üzere Yüz Tanıma için sıraya alınacaktır.", - "facial_recognition_job_description": "Algılanan yüzleri kişilere grupla. Bu adım, Yüz Tespit işlemi tamamlandıktan sonra çalışır. \"Sıfırla\", tüm yüzleri yeniden gruplandırır. \"Eksik\" ise henüz bir kişiye atanmamış yüzleri sıraya alır.", - "failed_job_command": "{job} görevi için {command} komutu başarısız", - "force_delete_user_warning": "UYARI: Bu işlem kullanıcıyı ve tüm öğeleri anında kaldıracaktır. Bu geri alınamaz ve dosyalar geri getirilemez.", - "image_format": "Biçim", - "image_format_description": "WebP, JPEG'e göre daha küçük dosya boyutu sunar fakat işlemesi daha uzun sürer.", - "image_fullsize_description": "Yakınlaştırıldığında kullanılan, meta verileri kaldırılmış tam boyutlu görüntü", - "image_fullsize_enabled": "Tam boyutlu görüntü üretimini etkinleştir", - "image_fullsize_enabled_description": "Yerleşik önizlemeyi tercih et” seçeneği etkinleştirildiğinde, yerleşik önizlemeler dönüştürme yapılmadan doğrudan kullanılır. JPEG gibi web dostu formatlar bu ayardan etkilenmez.", - "image_fullsize_quality_description": "1-100 arasında tam boyutlu görüntü kalitesi. Daha yüksek kalitelidir, ancak daha büyük dosyalar üretir.", - "image_fullsize_title": "Tam Boyutlu Görüntü Ayarları", - "image_prefer_embedded_preview": "Gömülü ön izlemeyi tercih et", - "image_prefer_embedded_preview_setting_description": "RAW fotoğrafları için mümkün olduğunda gömülü ön izlemeyi kullan. Bu, bazı fotoğraflarda daha gerçekçi renkler n kameraya bağlıdır ve fotoğrafta normalden daha fazla görüntü bozukluklarına sebep olabilir.", - "image_prefer_wide_gamut": "Geniş renk aralığını tercih et", - "image_prefer_wide_gamut_setting_description": "Önizleme görseli için P3 renk paletini tercih et. Bu, geniş renk paletli fotoğraflarda renk canlılığını daha iyi korur, fakat fotoğraflar eski tarayıcılarda ve eski cihazlarda daha farklı görünebilir. sRGB fotoğraflar renk paletini korumak için sRGB olarak tutulur.", - "image_preview_description": "Orta boyutlu görüntü, meta verisi çıkarılmış, tekil bir öğe görüntülenirken ve makine öğrenimi için kullanılır", - "image_preview_quality_description": "Ön izleme kalitesi 1-100 arasıdır. Yüksek değerler daha iyi kalite sağlar, ancak daha büyük dosyalar üretir ve uygulama yanıt verme hızını düşürebilir. Düşük bir değer belirlemek, makine öğrenimi kalitesini etkileyebilir.", - "image_preview_title": "Ön İzleme Ayarları", - "image_progressive": "Aşamalı", - "image_progressive_description": "JPEG görsellerini, yüklenirken kademeli (aşamalı) görüntülenecek şekilde “progressive” olarak kodlayın. WebP görselleri için etkisi yoktur.", - "image_quality": "Kalite", - "image_resolution": "Çözünürlük", - "image_resolution_description": "Daha yüksek çözünürlükle, daha fazla detayı koruyabilir ancak kodlanması daha uzun sürer, daha büyük dosya boyutlarına sahip olur ve uygulamanın yanıt verme hızını azaltabilir.", - "image_settings": "Fotoğraf Ayarları", - "image_settings_description": "Oluşturulan fotoğrafların kalite ve çözünürlüklerini yönet", - "image_thumbnail_description": "Meta verisi çıkarılmış küçük boyutlu küçük resim, ana zaman çizelgesi gibi fotoğraf gruplarını görüntülerken kullanılır", - "image_thumbnail_quality_description": "Küçük resim kalitesi 1-100 arasında. Daha yüksek değerler daha iyidir, ancak daha büyük dosyalar üretir ve uygulamanın yanıt hızını azaltabilir.", - "image_thumbnail_title": "Küçük Fotoğraf Ayarları", - "import_config_from_json_description": "JSON yapılandırma dosyası yükleyerek sistem yapılandırmasını içe aktar", - "job_concurrency": "{job} eş zamanlılık", - "job_created": "Görev oluşturuldu", - "job_not_concurrency_safe": "Bu işlem eşzamanlama için uygun değil.", - "job_settings": "Görev Ayarları", - "job_settings_description": "Aynı anda çalışacak görevleri yönet", - "jobs_delayed": "{jobCount, plural, other {# gecikmeli}}", - "jobs_failed": "{jobCount, plural, other {# Başarısız}}", - "jobs_over_time": "Zaman içinde işler", - "library_created": "Oluşturulan kütüphane : {library}", - "library_deleted": "Kütüphane silindi", - "library_details": "Kütüphane detayları", - "library_folder_description": "İçe aktarılacak klasörü belirtin. Bu klasör, alt klasörler dahil olmak üzere, resim ve videolar için taranacaktır.", - "library_remove_exclusion_pattern_prompt": "Bu hariç tutma modelini kaldırmak istediğinizden emin misiniz?", - "library_remove_folder_prompt": "Bu içe aktarma klasörünü kaldırmak istediğinizden emin misiniz?", - "library_scanning": "Periyodik Tarama", - "library_scanning_description": "Periyodik kütüphane taramasını yönet", - "library_scanning_enable_description": "Periyodik kütüphane taramasını etkinleştir", - "library_settings": "Harici Kütüphane", - "library_settings_description": "Harici kütüphane ayarlarını yönet", - "library_tasks_description": "Yeni yada değiştirilmiş öğeler için dış kütüphaneleri tara", - "library_updated": "Güncellenmiş kütüphane", - "library_watching_enable_description": "Harici kütüphanelerdeki dosya değişikliklerini izle", - "library_watching_settings": "Kütüphane izleme [DENEYSEL]", - "library_watching_settings_description": "Değişen dosyalar için otomatik olarak izle", - "logging_enable_description": "Günlüğü etkinleştir", - "logging_level_description": "Etkinleştirildiğinde hangi günlük seviyesi kullanılır.", - "logging_settings": "Günlük Tutma", - "machine_learning_availability_checks": "Kullanılabilirlik kontrolleri", - "machine_learning_availability_checks_description": "Kullanılabilir makine öğrenimi sunucularını otomatik olarak algılayın ve tercih edin", - "machine_learning_availability_checks_enabled": "Kullanılabilirlik kontrollerini etkinleştir", - "machine_learning_availability_checks_interval": "Kontrol aralığı", - "machine_learning_availability_checks_interval_description": "Kullanılabilirlik kontrolleri arasındaki milisaniye cinsinden aralık", - "machine_learning_availability_checks_timeout": "İstek zaman aşımı", - "machine_learning_availability_checks_timeout_description": "Kullanılabilirlik kontrolleri için milisaniye cinsinden zaman aşımı", - "machine_learning_clip_model": "CLIP modeli", - "machine_learning_clip_model_description": "Link burada listelenen CLIP modelinin adı. Bu özelliği değiştirdikten sonra \"Akıllı Arama\" işini tüm fotoğraflar için tekrardan çalıştırmalısınız.", - "machine_learning_duplicate_detection": "Kopya Fotoğraf Tespiti", - "machine_learning_duplicate_detection_enabled": "Kopya fotoğraf tespitini etkinleştir", - "machine_learning_duplicate_detection_enabled_description": "Devre dışı bırakılırsa aynı öğeler yine de temizlenecek.", - "machine_learning_duplicate_detection_setting_description": "Birbirinin kopyası olan varlıkları bulmak için CLIP kullan", - "machine_learning_enabled": "Makine öğrenmesini etkinleştir", - "machine_learning_enabled_description": "Eğer devre dışı bırakılırsa bütün Makine Öğrenmesi özellikleri devre dışı bırakılacak.", - "machine_learning_facial_recognition": "Yüz Tanıma", - "machine_learning_facial_recognition_description": "Fotoğraflardaki yüzleri tara, tanı ve gruplandır", - "machine_learning_facial_recognition_model": "Yüz tanıma modeli", - "machine_learning_facial_recognition_model_description": "Modeller, azalan boyut sırasına göre listelenmiştir. Daha büyük modeller daha yavaştır ve daha fazla bellek kullanır, ancak daha iyi sonuçlar üretir. Bir modeli değiştirdikten sonra tüm görüntüler için yüz algılama işini yeniden çalıştırmanız gerektiğini unutmayın.", - "machine_learning_facial_recognition_setting": "Yüz tanımayı etkinleştir", - "machine_learning_facial_recognition_setting_description": "Devre dışı bırakıldığında fotoğraflar yüz tanıma için işlenmeyecek ve Keşfet sayfasındaki Kişiler sekmesini doldurmayacak.", - "machine_learning_max_detection_distance": "Maksimum tespit uzaklığı", - "machine_learning_max_detection_distance_description": "Resimleri birbirinin çifti saymak için hesap edilecek azami benzerlik ölçüsü, 0.001-0.1 aralığında. Daha yüksek değer daha hassas olup daha fazla çift tespit eder ancak çift olmayan resimleri birbirinin çifti sayabilir.", - "machine_learning_max_recognition_distance": "Maksimum tanıma uzaklığı", - "machine_learning_max_recognition_distance_description": "İki suretin aynı kişi olarak kabul edildiği azami benzerlik oranı; 0-2 aralığında bir değerdir. Düşük değerler iki farklı kişinin sehven aynı kişi olarak algılanmasını engeller ama aynı kişinin farklı pozlarının farklı suretler olarak algılanmasına sebep olabilir. İki sureti birleştirmek daha kolay olduğu için mümkün olduğunca düşük değerler seçin.", - "machine_learning_min_detection_score": "Minimum tespit skoru", - "machine_learning_min_detection_score_description": "Bir yüzün algılanması için gerekli asgari kararlılık miktarı; 0-1 aralığında bir değerdir. Düşük değerler daha fazla yüz tanır ama hatalı tanıma oranı artar.", - "machine_learning_min_recognized_faces": "Minimum tanınan yüzler", - "machine_learning_min_recognized_faces_description": "Kişi oluşturulması için gereken minimum yüzler. Bu değeri yükseltmek yüz tanıma doğruluğunu arttırır fakat yüzün bir kişiye atanmama olasılığını arttırır.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Resimlerdeki metni tanımak için makine öğrenimini kullan", - "machine_learning_ocr_enabled": "OCR'yi etkinleştir", - "machine_learning_ocr_enabled_description": "Devre dışı bırakılırsa, resimler metin tanıma işleminden geçmeyecektir.", - "machine_learning_ocr_max_resolution": "En yüksek çözünürlük", - "machine_learning_ocr_max_resolution_description": "Bu çözünürlüğün üzerindeki önizlemeler, en-boy oranı korunarak yeniden boyutlandırılacaktır. Daha yüksek değerler daha doğru sonuç verir, ancak işlemesi daha uzun sürer ve daha fazla bellek kullanır.", - "machine_learning_ocr_min_detection_score": "En düşük tespit puanı", - "machine_learning_ocr_min_detection_score_description": "Metnin tespit edilmesi için minimum güven puanı 0-1 arasındadır. Düşük değerler daha fazla metin tespit eder, ancak yanlış pozitif sonuçlara yol açabilir.", - "machine_learning_ocr_min_recognition_score": "Minimum tespit puanı", - "machine_learning_ocr_min_score_recognition_description": "Algılanan metnin tanınması için minimum güven puanı 0-1 arasındadır. Daha düşük değerler daha fazla metni tanır, ancak yanlış pozitif sonuçlara neden olabilir.", - "machine_learning_ocr_model": "OCR modeli", - "machine_learning_ocr_model_description": "Sunucu modelleri mobil modellerden daha doğrudur, ancak işlenmesi daha uzun sürer ve daha fazla bellek kullanır.", - "machine_learning_settings": "Makine Öğrenmesi ayarları", - "machine_learning_settings_description": "Makine öğrenmesi özelliklerini ve ayarlarını yönet", - "machine_learning_smart_search": "Akıllı Arama", - "machine_learning_smart_search_description": "Fotoğrafları CLIP kullanarak semantik olarak ara", - "machine_learning_smart_search_enabled": "Akıllı aramayı etkinleştir", - "machine_learning_smart_search_enabled_description": "Eğer devre dışı bırakılırsa fotoğraflar akıllı arama için işlenmeyecek.", - "machine_learning_url_description": "Makine öğrenimi sunucusunun URL’si. Birden fazla URL sağlanırsa, her sunucu sırayla tek tek denenir ve biri başarılı yanıt verene kadar devam edilir. Yanıt vermeyen sunucular, çevrimiçi duruma gelene kadar geçici olarak yok sayılır.", - "maintenance_delete_backup": "Yedeği Sil", - "maintenance_delete_backup_description": "Bu dosya geri alınamaz şekilde silinecektir.", - "maintenance_delete_error": "Yedek silinemedi.", - "maintenance_restore_backup": "Yedeği Geri Yükle", - "maintenance_restore_backup_description": "Immich tamamen silinecek ve seçilen yedekten geri yüklenecektir. İşleme devam etmeden önce bir yedek oluşturulacaktır.", - "maintenance_restore_backup_different_version": "Bu yedek, Immich’in farklı bir sürümüyle oluşturulmuş!", - "maintenance_restore_backup_unknown_version": "Yedek sürümü belirlenemedi.", - "maintenance_restore_database_backup": "Veritabanı yedeğini geri yükle", - "maintenance_restore_database_backup_description": "Bir yedek dosyası kullanarak veritabanını daha önceki bir duruma geri döndürün", - "maintenance_settings": "Bakım", - "maintenance_settings_description": "Immich'i bakım moduna alın.", - "maintenance_start": "Bakım moduna geç", - "maintenance_start_error": "Bakım modu başlatılamadı.", - "maintenance_upload_backup": "Veritabanı yedek dosyasını yükle", - "maintenance_upload_backup_error": "Yedek yüklenemedi, dosya .sql veya .sql.gz formatında mı?", - "manage_concurrency": "Aynı anda çalışmayı yönet", - "manage_concurrency_description": "İş eşzamanlılığını yönetmek için işler sayfasına gidin", - "manage_log_settings": "Günlük ayarlarını yönet", - "map_dark_style": "Koyu mod", - "map_enable_description": "Harita ayarlarını etkinleştir", - "map_gps_settings": "Harita & GPS Ayarları", - "map_gps_settings_description": "Harita Yönetimi & GPS (Ters Jeokodlama) Ayarları", - "map_implications": "Harita özelliği, harici bir döşeme hizmetine (tiles.immich.cloud) bağlıdır", - "map_light_style": "Açık mod", - "map_manage_reverse_geocoding_settings": "Coğrafi Kodlama ayarlarını yönet", - "map_reverse_geocoding": "Coğrafi Kodlama", - "map_reverse_geocoding_enable_description": "Coğrafi Kodlamayı etkinleştir", - "map_reverse_geocoding_settings": "Coğrafi Kodlama ayarları", - "map_settings": "Harita", - "map_settings_description": "Harita ayarlarını yönet", - "map_style_description": "style.json Harita ayarlarının URL'si", - "memory_cleanup_job": "Anı temizliği", - "memory_generate_job": "Anı oluşturma", - "metadata_extraction_job": "Meta verilerinden Ayıkla", - "metadata_extraction_job_description": "GPS, yüzler ve çözünürlük gibi her bir öğeden meta veri bilgilerini çıkarın", - "metadata_faces_import_setting": "Yüz içe aktarmayı etkinleştir", - "metadata_faces_import_setting_description": "Yüzleri, EXIF verileri ve sidecar dosyalardan getir", - "metadata_settings": "Metaveri Ayarları", - "metadata_settings_description": "Metaveri ayarlarını yönet", - "migration_job": "Birleştirme", - "migration_job_description": "Öğeler ve yüzler için küçük resimleri en son klasör yapısına taşıyın", - "nightly_tasks_cluster_faces_setting_description": "Yeni algılanan yüzlerde yüz tanıma işlemini çalıştırın", - "nightly_tasks_cluster_new_faces_setting": "Yeni yüzleri bir araya getirin", - "nightly_tasks_database_cleanup_setting": "Veritabanı temizleme görevleri", - "nightly_tasks_database_cleanup_setting_description": "Veritabanından eski, süresi dolmuş verileri temizleyin", - "nightly_tasks_generate_memories_setting": "Anılar oluşturun", - "nightly_tasks_generate_memories_setting_description": "Öğelerden yeni anılar yaratın", - "nightly_tasks_missing_thumbnails_setting": "Eksik küçük resimleri oluştur", - "nightly_tasks_missing_thumbnails_setting_description": "Küçük resim oluşturmak için küçük resim içermeyen öğeleri sıraya alın", - "nightly_tasks_settings": "Gece Görevleri Ayarları", - "nightly_tasks_settings_description": "Gece görevlerini yönet", - "nightly_tasks_start_time_setting": "Başlangıç saati", - "nightly_tasks_start_time_setting_description": "Sunucunun gece görevlerini çalıştırmaya başladığı saat", - "nightly_tasks_sync_quota_usage_setting": "Kota kullanımını eşzamanla", - "nightly_tasks_sync_quota_usage_setting_description": "Mevcut kullanıma göre kullanıcı depolama kotasını güncelle", - "no_paths_added": "Yol eklenmedi", - "no_pattern_added": "Desen eklenmedi", - "note_apply_storage_label_previous_assets": "Not: Daha önce yüklenen öğelere Depolama Etiketi uygulamak için şu komutu çalıştırın", - "note_cannot_be_changed_later": "NOT: Bu daha sonra değiştirilemez!", - "notification_email_from_address": "Şu adresten", - "notification_email_from_address_description": "Gönderen e-posta adresi, örneğin: \"Immich Görsel Sunucusu \". E-posta gönderilmesine izin verdiğiniz bir adres kullandığınızdan emin olun.", - "notification_email_host_description": "E-posta sunucusunun ana bilgisayarı (örneğin, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Sertifika hatalarını görmezden gel", - "notification_email_ignore_certificate_errors_description": "TLS sertifika doğrulama ayarlarını görmezden gel (Önerilmez)", - "notification_email_password_description": "E-posta sunucusunda kimlik doğrulama yaparken kullanılacak şifre", - "notification_email_port_description": "Email sunucusunun port numarası (25, 465, 587 gibi)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "SMTPS kullan (TLS üzerinden SMTP)", - "notification_email_sent_test_email_button": "Test emaili yolla ve kaydet", - "notification_email_setting_description": "Email yollama bildirim ayarları", - "notification_email_test_email": "Test emaili yolla", - "notification_email_test_email_failed": "Test emaili yollanamadı, ayarları kontrol edin", - "notification_email_test_email_sent": "Test emaili {email} adresine yollandı. Lütfen gelen kutunuzu kontrol edin.", - "notification_email_username_description": "Email sunucu doğrulamasında kullanılacak olan kullanıcı adı", - "notification_enable_email_notifications": "Email bildirimlerini etkinleştir", - "notification_settings": "Bildirim Ayarları", - "notification_settings_description": "Email ve bildirim ayarlarını yönet", - "oauth_auto_launch": "Otomatik başlat", - "oauth_auto_launch_description": "Giriş sayfasına girildiğinde OAuth akışını otomatik olarak başlat", - "oauth_auto_register": "Otomatik kayıt", - "oauth_auto_register_description": "OAuth ile giriş yapan yeni kullanıcıları otomatik kaydet", - "oauth_button_text": "Buton yazısı", - "oauth_client_secret_description": "OAuth sağlayıcısı PKCE (Kod Değişimi İçin Kanıt Anahtarı) desteği sunmuyorsa gereklidir", - "oauth_enable_description": "OAuth ile giriş yap", - "oauth_mobile_redirect_uri": "Mobil yönlendirme URL'si", - "oauth_mobile_redirect_uri_override": "Mobilde zorla kullanılacak Yönlendirme Adresi", - "oauth_mobile_redirect_uri_override_description": "Mobil URI'ye izin vermeyen OAuth sağlayıcısı olduğunda etkinleştir, örneğin ''{callback}''", - "oauth_role_claim": "Rol Talebi", - "oauth_role_claim_description": "Bu iddianın varlığına göre otomatik olarak yönetici erişimi verin. İddia, 'kullanıcı' veya 'yönetici' olabilir.", - "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth giriş ayarlarını yönet", - "oauth_settings_more_details": "Bu özellik hakkında daha fazla bilgi için bu sayfayı ziyaret edin Dökümanlar.", - "oauth_storage_label_claim": "Depolama etiketi talebi", - "oauth_storage_label_claim_description": "Kullanıcının dosyalarını depolarken kullanılan alt klasörün adını belirlerken kulanılacak değer (en: OAuth claim).", - "oauth_storage_quota_claim": "Depolama kotası talebi", - "oauth_storage_quota_claim_description": "Kullanıcıya depolama kotası koymak için kullanılacak değer (en: OAuth claim).", - "oauth_storage_quota_default": "Varsayılan depolama kotası (GiB)", - "oauth_storage_quota_default_description": "Değer (en: OAuth claim) mevcut değilse GiB cinsinden konulacak kota.", - "oauth_timeout": "İstek Zaman Aşımı", - "oauth_timeout_description": "Milisaniye cinsinden istek zaman aşımı", - "ocr_job_description": "Resimlerdeki metni tanımak için makine öğrenimini kullan", - "password_enable_description": "E-posta ve şifre ile giriş yapın", - "password_settings": "Şifre ile Giriş", - "password_settings_description": "Şifre giriş ayarlarını yönet", - "paths_validated_successfully": "Tüm yollar başarıyla doğrulandı", - "person_cleanup_job": "Kişi temizleme", - "queue_details": "Kuyruk Detayları", - "queues": "İş Kuyrukları", - "queues_page_description": "Yönetici iş kuyrukları sayfası", - "quota_size_gib": "Kota Boyutu (GiB)", - "refreshing_all_libraries": "Tüm kütüphaneler yenileniyor", - "registration": "Yönetici Kaydı", - "registration_description": "Sistemdeki ilk kullanıcı olduğunuz için hesabınız Yönetici olarak ayarlandı. Yeni oluşturulan üyeliklerin, ve yönetici görevlerinin sorumlusu olarak atandınız.", - "remove_failed_jobs": "Başarısız işleri kaldır", - "require_password_change_on_login": "Kullanıcının ilk girişinde şifre değiştirmesini zorunlu kıl", - "reset_settings_to_default": "Ayarları varsayılana sıfırla", - "reset_settings_to_recent_saved": "Ayarları kaydedilmiş önceki ayarlara döndür", - "scanning_library": "Kütüphaneyi tarama", - "search_jobs": "Görevler ara…", - "send_welcome_email": "Hoş geldin e-postası gönder", - "server_external_domain_settings": "Dış domain", - "server_external_domain_settings_description": "Paylaşılan fotoğraflar için domain, http(s):// dahil", - "server_public_users": "Harici Kullanıcılar", - "server_public_users_description": "Paylaşılan albümlere bir kullanıcı eklenirken tüm kullanıcılar (ad ve e-posta) listelenir. Devre dışı bırakıldığında, kullanıcı listesi yalnızca yönetici kullanıcılar tarafından kullanılabilir.", - "server_settings": "Sunucu Ayarları", - "server_settings_description": "Sunucu ayarlarını yönet", - "server_stats_page_description": "Yönetici sunucu istatistikleri sayfası", - "server_welcome_message": "Hoş geldin mesajı", - "server_welcome_message_description": "Giriş sayfasında gösterilen mesaj.", - "settings_page_description": "Yönetici ayarlar sayfası", - "sidecar_job": "Ek dosya ile taşınan metadata", - "sidecar_job_description": "Dosya sisteminden yan araç meta verilerini keşfedin veya eşzamanlayın", - "slideshow_duration_description": "Her fotoğrafın kaç saniye görüntüleneceği", - "smart_search_job_description": "Akıllı aramayı desteklemek için tüm öğelerde makine öğrenmesini çalıştırın", - "storage_template_date_time_description": "Öğenin oluşturulma zaman damgası, tarih ve saat bilgisi için kullanılır", - "storage_template_date_time_sample": "Örnek tarih {date}", - "storage_template_enable_description": "Depolama şablon motorunu etkinleştir", - "storage_template_hash_verification_enabled": "Hash doğrulama etkinleştirildi", - "storage_template_hash_verification_enabled_description": "Hash doğrulamayı etkinleştirir, eğer ne işe yaradığını bilmiyorsanız bunu devre dışı bırakmayın", - "storage_template_migration": "Depolama şablonu birleştirme", - "storage_template_migration_description": "Geçerli {template} ayarlarını daha önce yüklenmiş olan öğelere uygula", - "storage_template_migration_info": "Depolama şablonu tüm dosya uzantılarını küçük harfe dönüştürecektir. Şablon ayarlarındaki değişiklikler sadece yeni öğelere uygulanacak. Şablon ayarlarını daha önce yüklenmiş olan öğelere uygulamak için {job} çalıştırın.", - "storage_template_migration_job": "Depolama Adreslerini Değiştirme Görevi", - "storage_template_more_details": "Bu özellik hakkında daha fazla bilgi için, Depolama Şablonu ve onun etkileri kısmına bakın", - "storage_template_onboarding_description_v2": "Etkinleştirildiğinde, bu özellik dosyaları kullanıcı tanımlı bir şablona göre otomatik olarak organize eder. Daha fazla bilgi için lütfen belgelere bakın.", - "storage_template_path_length": "Tahmini dosya adresi uzunluğu: {length, number}/{limit, number}", - "storage_template_settings": "Depolama Şablonu", - "storage_template_settings_description": "Yüklenen öğenin ismini ve klasör yapısını düzenle", - "storage_template_user_label": "{label} kullanıcını dosyaları için kullanılan alt klasördür", - "system_settings": "Sistem Ayarları", - "tag_cleanup_job": "Etiket temizleme", - "template_email_available_tags": "Şablonunuzda şu değişkenler kullanılabilir: {tags}", - "template_email_if_empty": "Şablon boş ise, varsayılan e-posta kullanılacak.", - "template_email_invite_album": "Albüm Daveti Şablonu", - "template_email_preview": "Ön izleme", - "template_email_settings": "Eposta Taslakları", - "template_email_update_album": "Albüm Şablonunu Güncelle", - "template_email_welcome": "Hoş geldiniz e-posta şablonu", - "template_settings": "Bildirim Şablonları", - "template_settings_description": "Bildirim şablonlarını yönet", - "theme_custom_css_settings": "Özel CSS", - "theme_custom_css_settings_description": "CSS (Cascading Style Sheets) kullanılarak Immich'in tasarımı değiştirilebilir.", - "theme_settings": "Tema Ayarları", - "theme_settings_description": "Immich web arayüzünün özelleştirilmesi ayarlarını yönet", - "thumbnail_generation_job": "Önizlemeleri oluştur", - "thumbnail_generation_job_description": "Her bir öğe için büyük, küçük ve bulanık küçük resimler ile her kişi için küçük resimler oluşturun", - "transcoding_acceleration_api": "Hızlandırma API", - "transcoding_acceleration_api_description": "Video formatı çevriminde kullanılacak API. Bu ayara 'mümkün olduğunca' uyulmaktadır; seçilen API'da sorun çıkarsa yazılım tabanlı çevirime dönülür. VP9 donanımınıza bağlı olarak çalışmayabilir.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU gerektirir)", - "transcoding_acceleration_qsv": "Hızlı Eşzamanlama (7. nesil veya daha yeni bir Intel CPU gerektirir)", - "transcoding_acceleration_rkmpp": "RKMPP (Sadece Rockchip SOC'ler)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Kabul edilen ses kodekleri", - "transcoding_accepted_audio_codecs_description": "Hangi ses codeclerinin çevrilmeyeceğini seçin. Sadece bazı çeviri tercihleri için gereklidir.", - "transcoding_accepted_containers": "Kabul edilen taşıcı dosya türleri", - "transcoding_accepted_containers_description": "Hangi taşıyıcı dosya türlerinin MP4'e çevrilmeyeceğini seçin. Sadece bazı çeviri tercihleri için gereklidir.", - "transcoding_accepted_video_codecs": "Kabul edilen video kodekleri", - "transcoding_accepted_video_codecs_description": "Hangi video codeclerinin çevrilmeyeceğini seçin. Sadece bazı çeviri tercihleri için gereklidir.", - "transcoding_advanced_options_description": "Çoğu kullanıcının değiştirmesine gerek olmayan ayarlar", - "transcoding_audio_codec": "Ses kodek", - "transcoding_audio_codec_description": "En yüksek kaliteye sahip olan Opus, fakat eski cihaz ve yazılımlarla uyumluluğu daha az.", - "transcoding_bitrate_description": "Videolar maksimum bir oranından yürksek ya da kabul edilir bir formatta değil", - "transcoding_codecs_learn_more": "Buradaki terminolojiyi öğrenmek için FFmpeg dokümantasyonlarına bakabilirsiniz: H.264, HEVC ve VP9.", - "transcoding_constant_quality_mode": "Sabit kalite modu", - "transcoding_constant_quality_mode_description": "ICQ, CQP'den daha iyidir, ancak bazı donanım hızlandırma cihazları bu modu desteklemez. Bu seçeneğin ayarlanması, kalite tabanlı kodlama kullanırken belirtilen modu tercih eder. ICQ'yu desteklemediği için NVENC tarafından göz ardı edilir.", - "transcoding_constant_rate_factor": "Sabit oran faktörü (-SOF)", - "transcoding_constant_rate_factor_description": "Video kalite seviyesi. Tipik değerler H.264 için 23, HEVC için 28, VP9 için 31 ve AV1 için 35'tir. Daha düşük değerler daha iyi kalite sağlar, ancak daha büyük dosyalar üretir.", - "transcoding_disabled_description": "Videoları dönüştürmeyin, bazı istemcilerde oynatma bozulabilir", - "transcoding_encoding_options": "Kodlama Seçenekleri", - "transcoding_encoding_options_description": "Kodlanmış videolar için kodekleri, çözünürlüğü, kaliteyi ve diğer seçenekleri ayarlayın", - "transcoding_hardware_acceleration": "Donanım Hızlandırma", - "transcoding_hardware_acceleration_description": "Deneysel: daha hızlı dönüştürme, ancak aynı bit hızında kaliteyi düşürebilir", - "transcoding_hardware_decoding": "Donanım çözücü", - "transcoding_hardware_decoding_setting_description": "Uçtan uca hızlandırmayı, sadece kodlamayı hızlandırmanın yerine etkinleştirir. Tüm videolarda çalışmayabilir.", - "transcoding_max_b_frames": "Maksimum B-kareler", - "transcoding_max_b_frames_description": "Daha yüksek değerler sıkıştırma verimliliğini artırır, ancak kodlamayı yavaşlatır. Eski cihazlarda donanım hızlandırma ile uyumlu olmayabilir. 0, B-çerçevelerini devre dışı bırakır, -1 ise bu değeri otomatik olarak ayarlar.", - "transcoding_max_bitrate": "Maksimum bitrate", - "transcoding_max_bitrate_description": "Maksimum bit hızı ayarlamak, kaliteyi az bir maliyetle düşürerek dosya boyutlarını daha öngörülebilir hale getirebilir. 720p çözünürlükte, tipik değerler VP9 veya HEVC için 2600 kbit/s, H.264 için ise 4500 kbit/s’dir. 0 olarak ayarlanırsa devre dışı bırakılır. Birim belirtilmediğinde, k (kbit/s için) varsayılır; bu nedenle 5000, 5000k ve 5M (Mbit/s için) eşdeğerdir.", - "transcoding_max_keyframe_interval": "Maksimum ana kare aralığı", - "transcoding_max_keyframe_interval_description": "Ana kareler arasındaki maksimum kare mesafesini ayarlar. Düşük değerler sıkıştırma verimliliğini kötüleştirir, ancak arama sürelerini iyileştirir ve hızlı hareket içeren sahnelerde kaliteyi artırabilir. 0 bu değeri otomatik olarak ayarlar.", - "transcoding_optimal_description": "Hedef çözünürlükten yüksek veya kabul edilen formatta olmayan videolar", - "transcoding_policy": "Kod Dönüştürme Politikası", - "transcoding_policy_description": "Bir videonun ne zaman kod dönüştürüleceğini ayarlama", - "transcoding_preferred_hardware_device": "Tercih edilen donanım cihazı", - "transcoding_preferred_hardware_device_description": "Sadece VAAPI ve QSV için uygulanır. Donanım kod çevrimi için DRI Node ayarlar.", - "transcoding_preset_preset": "Ön ayar (-ön)", - "transcoding_preset_preset_description": "Sıkıştırma hızı. Daha yavaş olan ayarlar belirli bitrate ayarları için daha küçük ve daha kaliteli dosya üretir. VP9 ayarı 'daha hızlı' ayarının üstündeki ayarları görmezden gelir.", - "transcoding_reference_frames": "Referans kareler", - "transcoding_reference_frames_description": "Belirli bir kareyi sıkıştırırken referans alınacak kare sayısı. Daha yüksek değerler sıkıştırma verimliliğini artırır, ancak kodlamayı yavaşlatır. 0 bu değeri otomatik olarak ayarlar.", - "transcoding_required_description": "Yalnızca kabul edilen formatta olmayan videolar", - "transcoding_settings": "Video Dönüştürme Ayarları", - "transcoding_settings_description": "Hangi videoların dönüştürüleceğini ve nasıl işleneceğini yönetin", - "transcoding_target_resolution": "Hedef çözünürlük", - "transcoding_target_resolution_description": "Daha yüksek çözünürlükler daha fazla detayı koruyabilir fakat işlemesi daha uzun sürer, dosya boyutu daha yüksek olur ve uygulamanın akıcılığını etkileyebilir.", - "transcoding_temporal_aq": "Zamansal AQ", - "transcoding_temporal_aq_description": "Sadece NVENC için geçerlidir. Ge.ici Uyarlamalı Kuantizasyon yüksek-detayların ve düşük-hareket sahnelerin kalitesini arttır. Eski cihazlarla uyumlu olmayabilir.", - "transcoding_threads": "İş Parçacıkları", - "transcoding_threads_description": "Daha yüksek değerler daha hızlı kodlamaya yol açar, ancak sunucunun etkin durumdayken diğer görevleri işlemesi için daha az alan bırakır. Bu değer İşlemci çekirdeği sayısından fazla olmamalıdır. 0'a ayarlanırsa kullanımı en üst düzeye çıkarır.", - "transcoding_tone_mapping": "Ton-haritalama", - "transcoding_tone_mapping_description": "HDR videoların SDR'ye dönüştürülürken görünümünü korumayı amaçlar. Her algoritma renk, detay ve parlaklık için farklı dengeleme yapar. Hable detayları korur, Mobius renkleri korur ve Reinhard parlaklığı korur.", - "transcoding_transcode_policy": "Dönüştürme (çevirme) politikası", - "transcoding_transcode_policy_description": "Bir videonun ne zaman kod dönüştürülmesi gerektiğine ilişkin ilke. Dönüştürme devre dışı bırakılmadığı sürece HDR videolar her zaman dönüştürülür.", - "transcoding_two_pass_encoding": "İki geçişli kodlama", - "transcoding_two_pass_encoding_setting_description": "Daha iyi kodlanmış videolar üretmek için iki geçişte kod dönüştürün. Maksimum bit hızı etkinleştirildiğinde (H.264 ve HEVC ile çalışması için gereklidir), bu mod maksimum bit hızına dayalı bir bit hızı aralığı kullanır ve CRF'yi yok sayar. VP9 için, maksimum bit hızı devre dışı bırakılırsa CRF kullanılabilir.", - "transcoding_video_codec": "Video kodlayıcı", - "transcoding_video_codec_description": "VP9 yüksek verimliliğe ve web uyumluluğuna sahiptir, ancak kod dönüştürme işlemi daha uzun sürer. HEVC benzer performans gösterir ancak web uyumluluğu daha düşüktür. H.264 geniş çapta uyumludur ve kod dönüştürmesi hızlıdır, ancak çok daha büyük dosyalar üretir. AV1 en verimli codec'tir ancak eski cihazlarda desteği yoktur.", - "trash_enabled_description": "Çöp özelliklerini etkinleştir", - "trash_number_of_days": "Gün sayısı", - "trash_number_of_days_description": "Öğeleri kalıcı olarak silmeden önce çöp kutusunda tutma süresi (gün)", - "trash_settings": "Çöp Kutusu Ayarları", - "trash_settings_description": "Çöp kutusu ayarlarını yönet", - "unlink_all_oauth_accounts": "Tüm OAuth hesaplarının bağlantısını kaldır", - "unlink_all_oauth_accounts_description": "Yeni bir sağlayıcıya geçmeden önce tüm OAuth hesaplarını kaldırılmayı unutmayın.", - "unlink_all_oauth_accounts_prompt": "Tüm OAuth hesaplarını kaldırmak istediğinizden emin misiniz? Bu, her kullanıcı için OAuth kimliğini sıfırlar ve geri alınamaz.", - "user_cleanup_job": "Kullanıcı temizleme", - "user_delete_delay": "{user} hesabı ve öğeleri {delay, plural, one {# day} other {# days}} gün içinde kalıcı olarak silinecektir.", - "user_delete_delay_settings": "Silme gecikmesi", - "user_delete_delay_settings_description": "Bir kullanıcının hesabını ve öğelerini kalıcı olarak silmek için kaldırıldıktan sonra gereken gün sayısı. Kullanıcı silme işi, silinmeye hazır kullanıcıları kontrol etmek için gece yarısı çalışır. Bu ayardaki değişiklikler bir sonraki yürütmede değerlendirilecektir.", - "user_delete_immediately": "{user}'in hesabı ve öğeleri hemen kalıcı olarak silinmek üzere sıraya alınacak.", - "user_delete_immediately_checkbox": "Kullanıcı ve öğeleri hemen silmek için sıraya alın", - "user_details": "Kullanıcı Ayrıntıları", - "user_management": "Kullanıcı Yönetimi", - "user_password_has_been_reset": "Kullanıcının şifresi sıfırlandı:", - "user_password_reset_description": "Lütfen kullanıcıya geçici şifreyi sağlayın ve bir sonraki oturum açışında şifreyi değiştirmesi gerektiğini bildirin.", - "user_restore_description": "{user} kullanıcısı geri yüklenecek.", - "user_restore_scheduled_removal": "Kullanıcıyı geri yükle - {date, date, long} tarihinde planlanan kaldırma", - "user_settings": "Kullanıcı Ayarları", - "user_settings_description": "Kullanıcı ayarlarını yönet", - "user_successfully_removed": "Kullanıcı {email} başarıyla kaldırıldı.", - "users_page_description": "Yönetici kullanıcılar sayfası", - "version_check_enabled_description": "Sürüm kontrolü etkin", - "version_check_implications": "Sürüm kontrol özelliği, github.com ile periyodik iletişime dayanır", - "version_check_settings": "Sürüm Kontrolü", - "version_check_settings_description": "Yeni sürüm bildirimini etkinleştir/devre dışı bırak", - "video_conversion_job": "Videoları dönüştür", - "video_conversion_job_description": "Tarayıcılar ve cihazlarla daha geniş uyumluluk için videoları dönüştür" - }, - "admin_email": "Yönetici E-postası", - "admin_password": "Yönetici Şifresi", - "administration": "Yönetim", - "advanced": "Gelişmiş", - "advanced_settings_clear_image_cache": "Görsel Önbelleğini Temizle", - "advanced_settings_clear_image_cache_error": "Görsel önbelleği temizlenemedi", - "advanced_settings_clear_image_cache_success": "Başarıyla temizlendi: {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Bu seçeneği, senkronizasyon sırasında medyayı alternatif ölçütlere göre filtrelemek için kullanın. Uygulamanın tüm albümleri algılamasında sorun yaşıyorsanız yalnızca bu durumda deneyin.", - "advanced_settings_enable_alternate_media_filter_title": "[DENEYSEL] Alternatif cihaz albüm senkronizasyon filtresini kullan", - "advanced_settings_log_level_title": "Günlük seviyesi: {level}", - "advanced_settings_prefer_remote_subtitle": "Bazı cihazlar yerel öğelerden küçük resimleri yüklerken çok yavaş çalışır. Bunun yerine uzak görüntüleri yüklemek için bu ayarı etkinleştirin.", - "advanced_settings_prefer_remote_title": "Uzak görüntüleri tercih et", - "advanced_settings_proxy_headers_subtitle": "Immich'in her ağ isteğiyle birlikte göndermesi gereken proxy header'ları tanımlayın", - "advanced_settings_proxy_headers_title": "Özel proxy başlıkları [DENEYSEL]", - "advanced_settings_readonly_mode_subtitle": "Fotoğrafların yalnızca görüntülenebildiği salt okunur modu etkinleştirir; birden fazla görüntü seçme, paylaşma, aktarma, silme gibi işlemler devre dışı bırakılır. Ana ekrandan kullanıcı avatarı aracılığıyla salt okunur modu Etkinleştirin/Devre dışı bırakın", - "advanced_settings_readonly_mode_title": "Salt okunur mod", - "advanced_settings_self_signed_ssl_subtitle": "Sunucu uç noktası için SSL sertifika doğrulamasını atlar. Kendinden imzalı sertifikalar için gereklidir.", - "advanced_settings_self_signed_ssl_title": "Kendinden imzalı SSL sertifikalarına izin ver [DENEYSEL]", - "advanced_settings_sync_remote_deletions_subtitle": "Web üzerinde işlem yapıldığında, bu aygıttaki öğeyi otomatik olarak sil veya geri yükle", - "advanced_settings_sync_remote_deletions_title": "Uzaktan silmeleri eşzamanla [DENEYSEL]", - "advanced_settings_tile_subtitle": "Gelişmiş kullanıcı ayarları", - "advanced_settings_troubleshooting_subtitle": "Sorun giderme için ek özellikleri etkinleştirin", - "advanced_settings_troubleshooting_title": "Sorun Giderme", - "age_months": "Yaş {months, plural, one {# ay} other {# ay}}", - "age_year_months": "1 yaş, {months, plural, one {# ay} other {# ay}}", - "age_years": "{years, plural, other {Yaş #}}", - "album": "Albüm", - "album_added": "Albüm eklendi", - "album_added_notification_setting_description": "Paylaşılan bir albüme eklendiğinizde e-posta bildirimi alın", - "album_cover_updated": "Albüm kapağı güncellendi", - "album_delete_confirmation": "{album} albümünü silmek istediğinize emin misiniz?", - "album_delete_confirmation_description": "Albüm paylaşılıyorsa, diğer kullanıcılar artık bu albüme erişemeyecektir.", - "album_deleted": "Albüm silindi", - "album_info_card_backup_album_excluded": "HARİÇ", - "album_info_card_backup_album_included": "DAHİL", - "album_info_updated": "Albüm bilgisi güncellendi", - "album_leave": "Albümden ayrıl?", - "album_leave_confirmation": "{album} albümünden ayrılmak istediğinize emin misiniz?", - "album_name": "Albüm Adı", - "album_options": "Albüm seçenekleri", - "album_remove_user": "Kullanıcıyı kaldır?", - "album_remove_user_confirmation": "{user} kullanıcısını kaldırmak istediğinize emin misiniz?", - "album_search_not_found": "Aramanızla eşleşen albüm bulunamadı", - "album_selected": "Seçilen albüm", - "album_share_no_users": "Görünüşe göre bu albümü tüm kullanıcılarla paylaştınız veya paylaşacak herhangi bir başka kullanıcınız yok.", - "album_summary": "Albüm özeti", - "album_updated": "Albüm güncellendi", - "album_updated_setting_description": "Paylaşılan bir albüme yeni bir öğe eklendiğinde e-posta bildirimi alın", - "album_upload_assets": "Bilgisayarınızdan görseller yükleyin ve albüme ekleyin", - "album_user_left": "{album}den ayrıldınız", - "album_user_removed": "{user} kaldırıldı", - "album_viewer_appbar_delete_confirm": "Bu albümü hesabınızdan silmek istediğinizden emin misiniz?", - "album_viewer_appbar_share_err_delete": "Albüm silinemedi", - "album_viewer_appbar_share_err_leave": "Albümden çıkılamadı", - "album_viewer_appbar_share_err_remove": "Albümden öğeler kaldırırken sorunlar yaşanıyor", - "album_viewer_appbar_share_err_title": "Albüm başlığı değiştirilemedi", - "album_viewer_appbar_share_leave": "Albümden çık", - "album_viewer_appbar_share_to": "Paylaşma", - "album_viewer_page_share_add_users": "Kullanıcı ekle", - "album_with_link_access": "Link'e sahip olan herhangi bir kişinin bu albümdeki fotoğrafları ve kişileri görmesine izin ver.", - "albums": "Albümler", - "albums_count": "{count, plural, one {{count, number} Albüm} other {{count, number} Albüm}}", - "albums_default_sort_order": "Varsayılan albüm sıralama düzeni", - "albums_default_sort_order_description": "Yeni albüm oluştururken kullanılacak başlangıç öğe sıralama düzeni.", - "albums_feature_description": "Diğer kullanıcılarla paylaşılabilen öğe koleksiyonları.", - "albums_on_device_count": "Cihazdaki albümler ({count})", - "albums_selected": "{count, plural, one {# albüm seçildi} other {# albüm seçildi}}", - "all": "Tümü", - "all_albums": "Tüm Albümler", - "all_people": "Tüm Kişiler", - "all_photos": "Tüm fotoğraflar", - "all_videos": "Tüm Videolar", - "allow_dark_mode": "Koyu moda izin ver", - "allow_edits": "Düzenlemeye izin ver", - "allow_public_user_to_download": "Genel kullanıcının indirmesine aç", - "allow_public_user_to_upload": "Genel kullanıcının yüklemesine aç", - "allowed": "İzin verildi", - "alt_text_qr_code": "QR kodu görseli", - "always_keep": "Her zaman sakla", - "always_keep_photos_hint": "Alan Aç, bu cihazdaki tüm fotoğrafları saklar.", - "always_keep_videos_hint": "Alan Aç, bu cihazdaki tüm videoları saklar.", - "anti_clockwise": "Saat yönünün tersine", - "api_key": "API Anahtarı", - "api_key_description": "Bu değer sadece bir kere gösterilecek. Lütfen bu pencereyi kapatmadan önce kopyaladığınıza emin olun.", - "api_key_empty": "Apı Anahtarı isminiz boş olmamalı", - "api_keys": "API Anahtarları", - "app_architecture_variant": "Varyant (Mimari)", - "app_bar_signout_dialog_content": "Çıkış yapmak istediğinize emin misiniz?", - "app_bar_signout_dialog_ok": "Evet", - "app_bar_signout_dialog_title": "Çıkış", - "app_download_links": "Uygulama İndirme Linkleri", - "app_settings": "Uygulama Ayarları", - "app_stores": "Uygulama Mağazaları", - "app_update_available": "Uygulama güncellemesi mevcut", - "appears_in": "Şurada görünür", - "apply_count": "Uygula ({count, number})", - "archive": "Arşiv", - "archive_action_prompt": "{count} arşive eklendi", - "archive_or_unarchive_photo": "Fotoğrafı arşivle/arşivden çıkar", - "archive_page_no_archived_assets": "Arşivlenmiş öğe bulunamadı", - "archive_page_title": "Arşiv ({count})", - "archive_size": "Arşiv boyutu", - "archive_size_description": "İndirmeler için arşiv boyutunu yapılandırın (GiB cinsinden)", - "archived": "Arşivlenen", - "archived_count": "{count, plural, other {# arşivlendi}}", - "are_these_the_same_person": "Bunlar aynı kişi mi?", - "are_you_sure_to_do_this": "Bunu yapmak istediğinize emin misiniz?", - "array_field_not_fully_supported": "Dizi alanları manuel JSON düzenlemesi gerektirir", - "asset_action_delete_err_read_only": "Salt okunur öğeler silinemez, atlanıyor", - "asset_action_share_err_offline": "Çevrimdışı öğeler alınamıyor, atlanıyor", - "asset_added_to_album": "Albüme eklendi", - "asset_adding_to_album": "Albüme ekleniyor…", - "asset_created": "Öğe oluşturuldu", - "asset_description_updated": "Öğe açıklaması güncellendi", - "asset_filename_is_offline": "Öğe {filename} çevrimdışı", - "asset_has_unassigned_faces": "Öğe, atanmamış yüzler içeriyor", - "asset_hashing": "Karma (hashleme) oluşturuluyor…", - "asset_list_group_by_sub_title": "Grupla", - "asset_list_layout_settings_dynamic_layout_title": "Dinamik düzen", - "asset_list_layout_settings_group_automatically": "Otomatik", - "asset_list_layout_settings_group_by": "Öğeleri grupla", - "asset_list_layout_settings_group_by_month_day": "Ay + gün", - "asset_list_layout_sub_title": "Düzen", - "asset_list_settings_subtitle": "Fotoğraf ızgara düzeni ayarları", - "asset_list_settings_title": "Fotoğraf Izgarası", - "asset_not_found_on_device_android": "Cihazda varlık bulunamadı", - "asset_not_found_on_device_ios": "Cihazınızda varlık bulunamadı. eğer icloud kullanıyorsanız, icloud'da depolanan dosyanın hatalı olması nedeniyle varlığa erişilemeyebilir.", - "asset_not_found_on_icloud": "Varlık icloud'da bulunamadı. İcloud'da depolanan dosyanın hatalı olması nedeniyle varlığa erişilemeyebilir.", - "asset_offline": "Öğe Çevrim Dışı", - "asset_offline_description": "Bu harici öğe artık diskte bulunmuyor. Yardım için lütfen Immich yöneticinizle iletişime geçin.", - "asset_restored_successfully": "Öğe başarıyla geri yüklendi", - "asset_skipped": "Atlandı", - "asset_skipped_in_trash": "Çöpte", - "asset_trashed": "Öğe çöpe atıldı", - "asset_troubleshoot": "Öğe Sorun Giderme", - "asset_uploaded": "Yüklendi", - "asset_uploading": "Yükleniyor…", - "asset_viewer_settings_subtitle": "Galeri görüntüleyici ayarlarını düzenle", - "asset_viewer_settings_title": "İçerik Görüntüleyici", - "assets": "Öğeler", - "assets_added_count": "Eklendi {count, plural, one {# asset} other {# assets}}", - "assets_added_to_album_count": "Albüme {count, plural, one {# asset} other {# assets}} eklendi", - "assets_added_to_albums_count": "Eklendi {assetTotal, plural, one {# asset} other {# assets}} buraya {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} albüme eklenemiyor", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} hiçbir albüme eklenemez", - "assets_count": "{count, plural, one {# öğe} other {# öğeler}}", - "assets_deleted_permanently": "{count} öğe kalıcı olarak silindi", - "assets_deleted_permanently_from_server": "{count} öğe kalıcı olarak Immich sunucusundan silindi", - "assets_downloaded_failed": "{count, plural, one {İndirilen # dosya - {error} dosya başarısız} other {İndirilen # dosyalar - {error} dosyalar başarısız oldu}}", - "assets_downloaded_successfully": "{count, plural, one {# dosya başarıyla indirildi} other {# dosya başarıyla indirildi}}", - "assets_moved_to_trash_count": "{count, plural, one {# öğe} other {# öğeler}} çöpe taşındı", - "assets_permanently_deleted_count": "Kalıcı olarak silindi {count, plural, one {# öğe} other {# öğeler}}", - "assets_removed_count": "Kaldırıldı {count, plural, one {# öğe} other {# öğeler}}", - "assets_removed_permanently_from_device": "{count} öğe cihazınızdan kalıcı olarak silindi", - "assets_restore_confirmation": "Tüm çöp kutusundaki öğeleri geri yüklemek istediğinizden emin misiniz? Bu işlemi geri alamazsınız! Ayrıca, çevrim dışı olan öğelerin bu şekilde geri yüklenemeyeceğini unutmayın.", - "assets_restored_count": "{count, plural, one {# öğe} other {# öğeler}} geri yüklendi", - "assets_restored_successfully": "{count} öğe başarıyla geri yüklendi", - "assets_trashed": "{count} öğe çöpe atıldı", - "assets_trashed_count": "{count, plural, one {# öğe} other {# öğeler}} çöp kutusuna taşındı", - "assets_trashed_from_server": "{count} öğe Immich sunucusundan çöpe atıldı", - "assets_were_part_of_album_count": "{count, plural, one {Öğe zaten} other {Öğeler zaten}} albümün parçasıydı", - "assets_were_part_of_albums_count": "{count, plural, one {Öğe zaten} other {Öğeler zaten}} albümlerin bir parçasıydı", - "authorized_devices": "Yetki Verilmiş Cihazlar", - "automatic_endpoint_switching_subtitle": "Belirlenmiş Wi-Fi ağına bağlıyken yerel olarak bağlanıp başka yerlerde alternatif bağlantıyı kullan", - "automatic_endpoint_switching_title": "Otomatik URL değiştirme", - "autoplay_slideshow": "Otomatik slayt gösterisi", - "back": "Geri", - "back_close_deselect": "Geri, kapat veya seçimi kaldır", - "background_backup_running_error": "Arka plan yedekleme şu anda çalışıyor, manuel yedekleme başlatılamıyor", - "background_location_permission": "Arka plan konum izni", - "background_location_permission_content": "Arka planda çalışırken ağ değiştirmek için Immich'in *her zaman* tam konum erişimine sahip olması gerekir, böylece uygulama Wi-Fi ağının adını okuyabilir", - "background_options": "Arka Plan Seçenekleri", - "backup": "Yedekle", - "backup_album_selection_page_albums_device": "Cihazdaki albümler ({count})", - "backup_album_selection_page_albums_tap": "Seçmek için dokunun, hariç tutmak için çift dokunun", - "backup_album_selection_page_assets_scatter": "Öğeler birden fazla albüme dağılabilir. Bu nedenle, yedekleme işlemi sırasında albümler dahil edilebilir veya hariç tutulabilir.", - "backup_album_selection_page_select_albums": "Albümleri seç", - "backup_album_selection_page_selection_info": "Seçim Bilgileri", - "backup_album_selection_page_total_assets": "Toplam eşsiz öğeler", - "backup_albums_sync": "Albüm Senkronizasyonunu Yedekle", - "backup_all": "Tümü", - "backup_background_service_backup_failed_message": "Yedekleme başarısız. Tekrar deneniyor…", - "backup_background_service_complete_notification": "Öğe yedekleme tamamlandı", - "backup_background_service_connection_failed_message": "Sunucuya bağlanılamadı. Tekrar deneniyor…", - "backup_background_service_current_upload_notification": "{filename} yükleniyor", - "backup_background_service_default_notification": "Yeni öğeler kontrol ediliyor…", - "backup_background_service_error_title": "Yedekleme hatası", - "backup_background_service_in_progress_notification": "Öğeleriniz yedekleniyor…", - "backup_background_service_upload_failure_notification": "{filename} yüklemesi başarısız oldu", - "backup_controller_page_albums": "Yedekleme Albümleri", - "backup_controller_page_background_app_refresh_disabled_content": "Arka planda yedeklemeyi kullanabilmek için Ayarlar > Genel > Arka Planda Uygulama Yenileme bölümünden arka planda uygulama yenilemeyi etkinleştirin.", - "backup_controller_page_background_app_refresh_disabled_title": "Arka planda uygulama yenileme devre dışı bırakıldı", - "backup_controller_page_background_app_refresh_enable_button_text": "Ayarlara git", - "backup_controller_page_background_battery_info_link": "Bana nasıl olduğunu göster", - "backup_controller_page_background_battery_info_message": "En iyi arka plan yedekleme deneyimi için lütfen Immich'in arka plan etkinliğini kısıtlayabilecek tüm pil optimizasyonlarını devre dışı bırakın.\n\nBu ayarın yeri cihaz modeline göre değişiklik gösterdiğinden cihazınızda nerede olduğunu lütfen araştırın.", - "backup_controller_page_background_battery_info_ok": "Tamam", - "backup_controller_page_background_battery_info_title": "Pil optimizasyonları", - "backup_controller_page_background_charging": "Sadece şarjda", - "backup_controller_page_background_configure_error": "Arka plan hizmeti yapılandırılamadı", - "backup_controller_page_background_delay": "Yeni öğelerin yedeklemesini geciktir: {duration}", - "backup_controller_page_background_description": "Uygulamayı açmaya gerek kalmadan yeni öğeleri otomatik olarak yedeklemek için arka plan hizmetini açın", - "backup_controller_page_background_is_off": "Otomatik arka planda yedekleme kapalı", - "backup_controller_page_background_is_on": "Otomatik arka planda yedekleme açık", - "backup_controller_page_background_turn_off": "Arka plan hizmetini kapat", - "backup_controller_page_background_turn_on": "Arka plan hizmetini aç", - "backup_controller_page_background_wifi": "Sadece Wi-Fi", - "backup_controller_page_backup": "Yedek", - "backup_controller_page_backup_selected": "Seçilen: ", - "backup_controller_page_backup_sub": "Yedeklenen fotoğraflar ve videolar", - "backup_controller_page_created": "Oluşturma tarihi: {date}", - "backup_controller_page_desc_backup": "Uygulamayı açtığınızda yeni öğelerin sunucuya otomatik olarak yüklenmesi için ön planda yedeklemeyi açın.", - "backup_controller_page_excluded": "Hariç tutuldu: ", - "backup_controller_page_failed": "Başarısız ({count})", - "backup_controller_page_filename": "Dosya adı: {filename} [{size}]", - "backup_controller_page_id": "KNu: {id}", - "backup_controller_page_info": "Yedekleme Bilgileri", - "backup_controller_page_none_selected": "Hiçbiri seçilmedi", - "backup_controller_page_remainder": "Kalan", - "backup_controller_page_remainder_sub": "Seçili albümlerden yedeklenecek kalan öğeler", - "backup_controller_page_server_storage": "Sunucu Depolama Alanı", - "backup_controller_page_start_backup": "Yedeklemeye Başla", - "backup_controller_page_status_off": "Otomatik ön planda yedekleme kapalı", - "backup_controller_page_status_on": "Otomatik ön planda yedekleme açık", - "backup_controller_page_storage_format": "{used}/{total} kullanılıyor", - "backup_controller_page_to_backup": "Yedeklenecek albümler", - "backup_controller_page_total_sub": "Seçili albümlerden tüm benzersiz öğeler", - "backup_controller_page_turn_off": "Ön planda yedeklemeyi kapat", - "backup_controller_page_turn_on": "Ön planda yedeklemeyi aç", - "backup_controller_page_uploading_file_info": "Dosya bilgisi yükleniyor", - "backup_err_only_album": "Tek albüm kaldırılamaz", - "backup_error_sync_failed": "Senkronizasyon başarısız. Yedekleme işlemi gerçekleştirilemiyor.", - "backup_info_card_assets": "öğeler", - "backup_manual_cancelled": "İptal Edildi", - "backup_manual_in_progress": "Yükleme halihazırda devam ediyor. Bir süre sonra deneyin", - "backup_manual_success": "Başarılı", - "backup_manual_title": "Yükleme durumu", - "backup_options": "Yedekleme Seçenekleri", - "backup_options_page_title": "Yedekleme seçenekleri", - "backup_setting_subtitle": "Arka planda ve ön planda yükleme ayarlarını düzenle", - "backup_settings_subtitle": "Yükleme ayarlarını yönet", - "backup_upload_details_page_more_details": "Daha fazla ayrıntı için dokunun", - "backward": "Geriye doğru", - "biometric_auth_enabled": "Biyometrik kimlik doğrulama etkin", - "biometric_locked_out": "Biyometrik kimlik doğrulaması kilitli", - "biometric_no_options": "Biyometrik seçenek yok", - "biometric_not_available": "Bu cihazda biyometrik kimlik doğrulama mevcut değil", - "birthdate_saved": "Doğum günü başarılı bir şekilde kaydedildi", - "birthdate_set_description": "Doğum günü, fotoğraftaki insanın fotoğraf çekildiği zamandaki yaşının hesaplanması için kullanılır.", - "blurred_background": "Bulanık arka plan", - "bugs_and_feature_requests": "Hatalar ve Özellik Talepleri", - "build": "Yapı", - "build_image": "Görüntü Oluştur", - "bulk_delete_duplicates_confirmation": "Toplu olarak {count, plural, one {# kopya öğeyi} other {# kopya öğeleri}} silmek istediğinizden emin misiniz? Bu işlem, her gruptaki en büyük öğeyi tutacak ve diğer tüm kopyaları kalıcı olarak silecektir. Bu işlemi geri alamazsınız!", - "bulk_keep_duplicates_confirmation": "{count, plural, one {# kopya öğeyi} other {# kopya öğeleri}} tutmak istediğinizden emin misiniz? Bu işlem, hiçbir şeyi silmeden tüm kopya gruplarını çözecektir.", - "bulk_trash_duplicates_confirmation": "{count, plural, one {# kopya öğeyi} other {# kopya öğeleri}} toplu olarak çöp kutusuna taşımak istediğinizden emin misiniz? Bu işlem, her grubun en büyük öğesini tutacak ve diğer tüm kopyaları çöp kutusuna taşıyacaktır.", - "buy": "Immich'i Satın Alın", - "cache_settings_clear_cache_button": "Önbelleği temizle", - "cache_settings_clear_cache_button_title": "Uygulamanın önbelleğini temizleyin. Önbellek yeniden oluşturulana kadar uygulamanın performansını önemli ölçüde etkileyecektir.", - "cache_settings_duplicated_assets_clear_button": "TEMİZLE", - "cache_settings_duplicated_assets_subtitle": "Uygulama tarafından yok sayılan fotoğraflar ve videolar", - "cache_settings_duplicated_assets_title": "Yinelenen Öğeler ({count})", - "cache_settings_statistics_album": "Kütüphane küçük resimleri", - "cache_settings_statistics_full": "Tam çözünürlükte resimler", - "cache_settings_statistics_shared": "Paylaşılan albüm küçük resimleri", - "cache_settings_statistics_thumbnail": "Küçük resimler", - "cache_settings_statistics_title": "Önbellek kullanımı", - "cache_settings_subtitle": "Immich mobil uygulamasının önbelleğe alma davranışını kontrol edin", - "cache_settings_tile_subtitle": "Yerel depolama davranışını kontrol et", - "cache_settings_tile_title": "Yerel Depolama", - "cache_settings_title": "Önbellek Ayarları", - "camera": "Kamera", - "camera_brand": "Kamera markası", - "camera_model": "Kamera modeli", - "cancel": "İptal", - "cancel_search": "Aramayı iptal et", - "canceled": "İptal edildi", - "canceling": "Vazgeçmek", - "cannot_merge_people": "Kişiler birleştirilemiyor", - "cannot_undo_this_action": "Bu işlem geri alınamaz!", - "cannot_update_the_description": "Açıklama güncellenemiyor", - "cast": "Yansıt", - "cast_description": "Kullanılabilir yansıtma hedeflerini yapılandır", - "change_date": "Tarihi değiştir", - "change_description": "Açıklamayı değiştir", - "change_display_order": "Görüntüleme sırasını değiştir", - "change_expiration_time": "Son kullanma süresini değiştir", - "change_location": "Konumu değiştir", - "change_name": "İsim değiştir", - "change_name_successfully": "Adı başarıyla değiştirildi", - "change_password": "Şifre Değiştir", - "change_password_description": "Bu sisteme ilk kez giriş yapıyorsunuz veya şifrenizi değiştirmek için bir istekte bulunuldu. Lütfen aşağıya yeni şifrenizi girin.", - "change_password_form_confirm_password": "Şifreyi Onayla", - "change_password_form_description": "Merhaba {name},\n\nBu sisteme ilk kez giriş yapıyorsunuz veya şifrenizi değiştirmek için bir istekte bulunuldu. Lütfen aşağıya yeni şifrenizi girin.", - "change_password_form_log_out": "Diğer tüm cihazlardan çıkış yap", - "change_password_form_log_out_description": "Diğer tüm cihazlardan çıkış yapmanız önerilir", - "change_password_form_new_password": "Yeni Şifre", - "change_password_form_password_mismatch": "Şifreler eşleşmiyor", - "change_password_form_reenter_new_password": "Yeni Şifreyi Tekrar Giriniz", - "change_pin_code": "PIN kodunu değiştirin", - "change_trigger": "Tetikleyiciyi değiştir", - "change_trigger_prompt": "Tetikleyiciyi değiştirmek istediğinizden emin misiniz? Bu, mevcut tüm eylemleri ve filtreleri kaldıracaktır.", - "change_your_password": "Şifreni değiştir", - "changed_visibility_successfully": "Görünürlük başarıyla değiştirildi", - "charging": "Şarj oluyor", - "charging_requirement_mobile_backup": "Arka plan yedekleme için cihazın şarjda olması gerekir", - "check_corrupt_asset_backup": "Bozuk öğe yedeklemelerini kontrol et", - "check_corrupt_asset_backup_button": "Kontrol et", - "check_corrupt_asset_backup_description": "Bu kontrolü yalnızca Wi-Fi üzerinden ve tüm öğeler yedeklendikten sonra çalıştırın. İşlem birkaç dakika sürebilir.", - "check_logs": "Günlükleri Kontrol Et", - "checksum": "Sağlama toplamı", - "choose_matching_people_to_merge": "Birleştirmek için eşleşen kişileri seçiniz", - "city": "Şehir", - "cleanup_confirm_description": "Immich, sunucuya güvenli bir şekilde yedeklenmiş {count} adet görsel ( {date} tarihinden önce oluşturulmuş) buldu. Yerel kopyaları bu cihazdan kaldırmak istiyor musunuz?", - "cleanup_confirm_prompt_title": "Bu cihazdan silinsin mi?", - "cleanup_deleted_assets": "{count} adet görsel çöp kutusuna taşındı", - "cleanup_deleting": "Çöp kutusuna taşınıyor...", - "cleanup_found_assets": "{count} adet yedeklenmiş görsel bulundu", - "cleanup_found_assets_with_size": "{count} yedeklenmiş öğe bulundu ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud Paylaşılan Albümleri tarama kapsamı dışında tutulmuştur", - "cleanup_no_assets_found": "Yukarıdaki ölçütlere uyan hiçbir öğe bulunamadı. Alan Aç yalnızca sunucuya yedeklenmiş öğeleri kaldırabilir.", - "cleanup_preview_title": "Silinecek görseller ({count})", - "cleanup_step3_description": "Tarih ve saklama ayarlarınıza uyan, yedeklenmiş öğeleri tarayın.", - "cleanup_step4_summary": "Yerel cihazınızdan kaldırılacak {count} öğe ({date} tarihinden önce oluşturulmuş). Fotoğraflara Immich uygulaması üzerinden erişmeye devam edebilirsiniz.", - "cleanup_trash_hint": "Depolama alanını tamamen geri kazanmak için sistem galerisi uygulamasını açın ve çöp kutusunu boşaltın", - "clear": "Temizle", - "clear_all": "Hepsini temizle", - "clear_all_recent_searches": "Son aramaların hepsini temizle", - "clear_file_cache": "Dosya Önbelleğini Temizle", - "clear_message": "Mesajı temizle", - "clear_value": "Değeri temizle", - "client_cert_dialog_msg_confirm": "Tamam", - "client_cert_enter_password": "Şifreyi Girin", - "client_cert_import": "İçe Aktar", - "client_cert_import_success_msg": "İstemci sertifikası içe aktarıldı", - "client_cert_invalid_msg": "Geçersiz sertifika dosyası veya yanlış şifre", - "client_cert_remove_msg": "İstemci sertifikası kaldırıldı", - "client_cert_subtitle": "Yalnızca PKCS12 (.p12, .pfx) formatını destekler. Sertifika içe aktarma/kaldırma işlemi yalnızca oturum açmadan önce yapılabilir", - "client_cert_title": "SSL istemci sertifikası [DENEYSEL]", - "clockwise": "Saat yönü", - "close": "Kapat", - "collapse": "Daralt", - "collapse_all": "Tümünü Daralt", - "color": "Renk", - "color_theme": "Renk teması", - "command": "Komut", - "comment_deleted": "Yorum silindi", - "comment_options": "Yorum seçenekleri", - "comments_and_likes": "Yorumlar & beğeniler", - "comments_are_disabled": "Yorumlar devre dışı", - "common_create_new_album": "Yeni Albüm", - "completed": "Tamamlandı", - "confirm": "Onayla", - "confirm_admin_password": "Yönetici Şifresini Onayla", - "confirm_delete_face": "Öğeden {name} yüzünü silmek istediğinizden emin misiniz?", - "confirm_delete_shared_link": "Bu paylaşılan bağlantıyı silmek istediğinizden emin misiniz?", - "confirm_keep_this_delete_others": "Bu öğe hariç, yığındaki diğer tüm öğeler silinecektir. Devam etmek istediğinizden emin misiniz?", - "confirm_new_pin_code": "Yeni PIN kodunu onaylayın", - "confirm_password": "Şifreyi onayla", - "confirm_tag_face": "Bu yüzü {name} olarak etiketlemek ister misiniz?", - "confirm_tag_face_unnamed": "Bu yüzü etiketlemek ister misin?", - "connected_device": "Cihaz bağlandı", - "connected_to": "Bağlı", - "contain": "Sığdır", - "context": "Bağlam", - "continue": "Devam et", - "control_bottom_app_bar_create_new_album": "Yeni albüm", - "control_bottom_app_bar_delete_from_immich": "Immich'ten sil", - "control_bottom_app_bar_delete_from_local": "Cihazdan sil", - "control_bottom_app_bar_edit_location": "Konumu Düzenle", - "control_bottom_app_bar_edit_time": "Tarih ve Saati Düzenle", - "control_bottom_app_bar_share_link": "Bağlantıyı Paylaş", - "control_bottom_app_bar_share_to": "Paylaşma", - "control_bottom_app_bar_trash_from_immich": "Çöp Kutusuna At", - "copied_image_to_clipboard": "Resim, panoya kopyalandı.", - "copied_to_clipboard": "Panoya kopyalandı!", - "copy_error": "Kopyalama hatası", - "copy_file_path": "Dosya yolunu kopyala", - "copy_image": "Resmi Kopyala", - "copy_link": "Bağlantıyı kopyala", - "copy_link_to_clipboard": "Bağlantıyı panoya kopyala", - "copy_password": "Şifreyi kopyala", - "copy_to_clipboard": "Panoya Kopyala", - "country": "Ülke", - "cover": "Kapla", - "covers": "Kapak", - "create": "Oluştur", - "create_album": "Albüm oluştur", - "create_album_page_untitled": "Başlıksız", - "create_api_key": "API anahtarı oluştur", - "create_first_workflow": "İlk iş akışını oluştur", - "create_library": "Kütüphane Oluştur", - "create_link": "Link oluştur", - "create_link_to_share": "Paylaşmak için link oluştur", - "create_link_to_share_description": "Bağlantıya sahip olan herkesin seçilen fotoğrafları görmesine izin ver", - "create_new": "YENİ OLUŞTUR", - "create_new_person": "Yeni kişi oluştur", - "create_new_person_hint": "Seçili öğeleri yeni bir kişiye atayın", - "create_new_user": "Yeni kullanıcı oluştur", - "create_shared_album_page_share_add_assets": "ÖĞELER EKLE", - "create_shared_album_page_share_select_photos": "Fotoğrafları Seç", - "create_shared_link": "Paylaşılan bağlantı oluştur", - "create_tag": "Etiket oluştur", - "create_tag_description": "Yeni bir etiket oluşturun. İç içe geçmiş etiketler için, etiketi tam yolu ve eğik çizgileri de dahil ederek giriniz.", - "create_user": "Kullanıcı oluştur", - "create_workflow": "İş akışı oluştur", - "created": "Oluşturuldu", - "created_at": "Oluşturuldu", - "creating_linked_albums": "Bağlantılı albümler oluşturuluyor...", - "crop": "Kes", - "crop_aspect_ratio_fixed": "Sabitlenmiş", - "crop_aspect_ratio_free": "Boş", - "crop_aspect_ratio_original": "Orijinal", - "curated_object_page_title": "Nesneler", - "current_device": "Mevcut cihaz", - "current_pin_code": "Mevcut PIN kodu", - "current_server_address": "Mevcut sunucu adresi", - "custom_date": "Özel tarih", - "custom_locale": "Özel Yerel Ayar", - "custom_locale_description": "Tarihleri ve sayıları dile ve bölgeye göre biçimlendirin", - "custom_url": "Özel URL", - "cutoff_date_description": "Son döneme ait fotoğrafları tut …", - "cutoff_day": "{count, plural, one {gün} other {gün}}", - "cutoff_year": "{count, plural, one {yıl} other {yıl}}", - "daily_title_text_date": "dd MMM E", - "daily_title_text_date_year": "dd MMM yyyy E", - "dark": "Koyu", - "dark_theme": "Karanlık temaya geç", - "date": "Tarih", - "date_after": "Sonraki tarih", - "date_and_time": "Tarih ve Zaman", - "date_before": "Önceki tarih", - "date_format": "LLL d, y E • H:mm", - "date_of_birth_saved": "Doğum günü başarı ile kaydedildi", - "date_range": "Tarih aralığı", - "day": "Gün", - "days": "Günler", - "deduplicate_all": "Tüm kopyaları kaldır", - "deduplication_criteria_1": "Resim boyutu (bayt olarak)", - "deduplication_criteria_2": "EXIF veri sayısı", - "deduplication_info": "Tekilleştirme Bilgileri", - "deduplication_info_description": "Öğeleri otomatik olarak önceden seçmek ve yinelenenleri toplu olarak kaldırmak için şunlara bakıyoruz:", - "default_locale": "Varsayılan Yerel Ayar", - "default_locale_description": "Tarihleri ve sayıları tarayıcınızın yerel ayarına göre biçimlendirin", - "delete": "Sil", - "delete_action_confirmation_message": "Bu öğeyi silmek istediğinizden emin misiniz? Bu işlem, öğeyi sunucunun çöp kutusuna taşıyacak ve yerel olarak silmek isteyip istemediğinizi soracaktır", - "delete_action_prompt": "{count} silindi", - "delete_album": "Albümü sil", - "delete_api_key_prompt": "Bu API anahtarını silmek istediğinizden emin misiniz?", - "delete_dialog_alert": "Bu öğeler cihazınızdan ve Immich'ten kalıcı olarak silinecektir", - "delete_dialog_alert_local": "Bu öğeler cihazınızdan kalıcı olarak silinecek ancak Immich sunucusunda mevcut kalacaklardır", - "delete_dialog_alert_local_non_backed_up": "Bazı öğeler Immich'e yedeklenmemiş ve cihazınızdan kalıcı olarak silinecek", - "delete_dialog_alert_remote": "Bu öğeler Immich sunucusundan kalıcı olarak silinecektir", - "delete_dialog_ok_force": "Yine de Sil", - "delete_dialog_title": "Kalıcı Olarak Sil", - "delete_duplicates_confirmation": "Bu kopyaları kalıcı olarak silmek istediğinizden emin misiniz?", - "delete_face": "Yüzü sil", - "delete_key": "Anahtarı sil", - "delete_library": "Kütüphaneyi sil", - "delete_link": "Bağlantıyı sil", - "delete_local_action_prompt": "{count} yerel olarak silindi", - "delete_local_dialog_ok_backed_up_only": "Sadece Yedeklenmişleri Sil", - "delete_local_dialog_ok_force": "Yine de Sil", - "delete_others": "Diğerlerini sil", - "delete_permanently": "Kalıcı olarak sil", - "delete_permanently_action_prompt": "{count} kalıcı olarak silindi", - "delete_shared_link": "Paylaşılan bağlantıyı sil", - "delete_shared_link_dialog_title": "Paylaşılan Bağlantıyı Sil", - "delete_tag": "Etiketi sil", - "delete_tag_confirmation_prompt": "{tagName} etiketini silmek istediğinizden emin misiniz?", - "delete_user": "Kullanıcıyı sil", - "deleted_shared_link": "Paylaşılan bağlantı silindi", - "deletes_missing_assets": "Diskte eksik olan öğeleri siler", - "description": "Açıklama", - "description_input_hint_text": "Açıklama ekle...", - "description_input_submit_error": "Açıklama güncellenirken hata oluştu, daha fazla ayrıntı için günlüğü kontrol edin", - "deselect_all": "Tümünü Seçimi Kaldır", - "details": "Detaylar", - "direction": "Yön", - "disable": "Devre dışı bırak", - "disabled": "Devre dışı bırakıldı", - "disallow_edits": "Değişikliklere izin verme", - "discord": "Discord", - "discover": "Keşfet", - "discovered_devices": "Keşfedilen aygıtlar", - "dismiss_all_errors": "Tüm hataları yoksay", - "dismiss_error": "Hatayı yoksay", - "display_options": "Görüntüleme seçenekleri", - "display_order": "Gösterim sıralaması", - "display_original_photos": "Orijinal fotoğrafları göster", - "display_original_photos_setting_description": "Orijinal öğe web uyumlu olduğunda, bir öğeyi görüntülerken küçük resimler yerine orijinal fotoğrafı görüntülemeyi tercih edin. Bu, fotoğraf görüntüleme hızlarının yavaşlamasına neden olabilir.", - "do_not_show_again": "Bu mesajı bir daha gösterme", - "documentation": "Dokümantasyon", - "done": "Bitti", - "download": "İndir", - "download_action_prompt": "{count} öğe indiriliyor", - "download_canceled": "İndirme iptal edildi", - "download_complete": "İndirme tamamlandı", - "download_enqueue": "İndirme sıraya alındı", - "download_error": "İndirme Hatası", - "download_failed": "İndirme başarısız oldu", - "download_finished": "İndirme tamamlandı", - "download_include_embedded_motion_videos": "Gömülü videolar", - "download_include_embedded_motion_videos_description": "Görsel hareketli fotoğraflarda yer alan gömülü videoları ayrı bir dosya olarak dahil et", - "download_notfound": "İndirme bulunamadı", - "download_original": "Orijinali indir", - "download_paused": "İndirme duraklatıldı", - "download_settings": "İndir", - "download_settings_description": "Öğe indirme ile ilgili ayarları yönetin", - "download_started": "İndirme başladı", - "download_sucess": "İndirme başarılı", - "download_sucess_android": "Medya DCIM/Immich klasörüne indirildi", - "download_waiting_to_retry": "Yeniden denemek için bekleniyor", - "downloading": "İndiriliyor", - "downloading_asset_filename": "Öğe indiriliyor {filename}", - "downloading_from_icloud": "iCloud’dan indiriliyor", - "downloading_media": "Medya indiriliyor", - "drop_files_to_upload": "Dosyaları yüklemek için herhangi bir yere bırakın", - "duplicates": "Kopyalar", - "duplicates_description": "Her grubu çözmek için, varsa hangilerinin kopya olduğunu belirtin", - "duration": "Süre", - "edit": "Düzenle", - "edit_album": "Albümü düzenle", - "edit_avatar": "Avatarı Düzenle", - "edit_birthday": "Doğum gününü düzenle", - "edit_date": "Tarihi Düzenle", - "edit_date_and_time": "Tarih ve zamanı düzenleyin", - "edit_date_and_time_action_prompt": "{count} tarih ve zaman düzenlendi", - "edit_date_and_time_by_offset": "Tarihi ofset ile değiştir", - "edit_date_and_time_by_offset_interval": "Yeni tarih aralığı: {from}'dan {to}'a kadar", - "edit_description": "Açıklamayı düzenle", - "edit_description_prompt": "Lütfen yeni bir açıklama seçin:", - "edit_exclusion_pattern": "Hariç tutma desenini düzenle", - "edit_faces": "Yüzleri Düzenleyin", - "edit_key": "Anahtarı düzenle", - "edit_link": "Bağlantıyı düzenle", - "edit_location": "Lokasyonu düzenleyin", - "edit_location_action_prompt": "{count} konum düzenlendi", - "edit_location_dialog_title": "Konum", - "edit_name": "İsmi düzenleyin", - "edit_people": "Kişileri düzenle", - "edit_tag": "Etiketi düzenle", - "edit_title": "Başlığı düzenle", - "edit_user": "Kullanıcıyı düzenle", - "edit_workflow": "İş akışını düzenle", - "editor": "Editör", - "editor_close_without_save_prompt": "Değişiklikler kaydedilmeyecek", - "editor_close_without_save_title": "Düzenleyici kapatılsın mı?", - "editor_confirm_reset_all_changes": "Tüm değişikleri iptal edilecek. Emin misiniz?", - "editor_flip_horizontal": "Yatay çevir", - "editor_flip_vertical": "Dikey çevir", - "editor_orientation": "Yönlendirme", - "editor_reset_all_changes": "Değişiklikleri sıfırla", - "editor_rotate_left": "90° Saat yönünün tersine çevir", - "editor_rotate_right": "90° saat yönünde çevir", - "email": "E-posta", - "email_notifications": "E-posta bildirimleri", - "empty_folder": "Bu klasör boş", - "empty_trash": "Çöpü boşalt", - "empty_trash_confirmation": "Çöp kutusunu boşaltmak istediğinizden emin misiniz? Bu işlem, çöp kutusundaki tüm varlıkları Immich'ten kalıcı olarak silecektir.\nBu işlemi geri alamazsınız!", - "enable": "Etkinleştir", - "enable_backup": "Yedeklemeyi Etkinleştir", - "enable_biometric_auth_description": "Biyometrik kimlik doğrulamasını etkinleştirmek için PIN kodu girin", - "enabled": "Etkinleştirildi", - "end_date": "Bitiş tarihi", - "enqueued": "Kuyruğa alındı", - "enter_wifi_name": "Wi-Fi adını girin", - "enter_your_pin_code": "PIN kodunuzu girin", - "enter_your_pin_code_subtitle": "Kilitli klasöre erişmek için PIN kodunuzu girin", - "error": "Hata", - "error_change_sort_album": "Albüm sıralama düzeni değiştirilemedi", - "error_delete_face": "Öğeden yüz silme hatası", - "error_getting_places": "Konum bilgisi alınırken hata oluştu", - "error_loading_albums": "Albümler yüklenirken hata oluştu", - "error_loading_image": "Resim yüklenirken hata oluştu", - "error_loading_partners": "Ortakları yükleme hatası: {error}", - "error_retrieving_asset_information": "Öğe bilgileri alınırken hata oluştu", - "error_saving_image": "Hata: {error}", - "error_tag_face_bounding_box": "Yüz etiketleme hatası – sınırlayıcı kutu koordinatları alınamadı", - "error_title": "Bir Hata Oluştu - Bir şeyler ters gitti", - "error_while_navigating": "Öğeye giderken hata oluştu", - "errors": { - "cannot_navigate_next_asset": "Sonraki öğeye geçiş yapılamıyor", - "cannot_navigate_previous_asset": "Önceki öğeye geçiş yapılamıyor", - "cant_apply_changes": "Değişiklikler uygulanamıyor", - "cant_change_activity": "Etkinliği {enabled, select, true {devre dışı bırakamıyor} other {etkinleştiremiyor}}", - "cant_change_asset_favorite": "Öğenin favori durumu değiştirilemiyor", - "cant_change_metadata_assets_count": "{count, plural, one {# öğenin} other {# öğelerin}} meta verisi değiştirilemiyor", - "cant_get_faces": "Yüzler alınamadı", - "cant_get_number_of_comments": "Yorumların sayısı alınamadı", - "cant_search_people": "Kişiler aranamıyor", - "cant_search_places": "Mekanlar aranamıyor", - "error_adding_assets_to_album": "Albüme öğe ekleme hatası", - "error_adding_users_to_album": "Albüme kullanıcı ekleme hatası", - "error_deleting_shared_user": "Paylaşılan kullanıcı silme hatası", - "error_downloading": "{filename} indirme hatası", - "error_hiding_buy_button": "Satın alma butonu gizleme hatası", - "error_removing_assets_from_album": "Öğeyi albümden silme hatası, daha fazla detay için konsolu kontrol et", - "error_selecting_all_assets": "Tüm öğeleri seçerken hata oluştu", - "exclusion_pattern_already_exists": "Bu dışlama modeli halihazırda mevcut.", - "failed_to_create_album": "Albüm oluşturulamadı", - "failed_to_create_shared_link": "Paylaşılan bağlantı oluşturulamadı", - "failed_to_edit_shared_link": "Paylaşılan bağlantı düzenlenemedi", - "failed_to_get_people": "Kişiler alınamadı", - "failed_to_keep_this_delete_others": "Bu öğenin tutulması ve diğer öğenin silinmesi başarısız oldu", - "failed_to_load_asset": "Öğe yüklenemedi", - "failed_to_load_assets": "Öğeler yüklenemedi", - "failed_to_load_notifications": "Bildirim yüklenemedi", - "failed_to_load_people": "Kişiler yüklenemedi", - "failed_to_remove_product_key": "Ürün anahtarı kaldırılamadı", - "failed_to_reset_pin_code": "PIN kodu sıfırlanamadı", - "failed_to_stack_assets": "Öğeler yığınlanamadı", - "failed_to_unstack_assets": "Öğelerin yığını kaldırılamadı", - "failed_to_update_notification_status": "Bildirim durumu güncellenemedi", - "incorrect_email_or_password": "Yanlış e-posta veya şifre", - "library_folder_already_exists": "Bu içe aktarma yolu zaten mevcut.", - "paths_validation_failed": "{paths, plural, one {# Yol} other {# Yollar}} doğrulanamadı", - "profile_picture_transparent_pixels": "Profil resimleri şeffaf piksele sahip olamaz. Lütfen resme yakınlaştırın ve/veya resmi hareket ettirin.", - "quota_higher_than_disk_size": "Disk boyutundan daha yüksek bir kota belirlediniz", - "something_went_wrong": "Bir şeyler ters gitti", - "unable_to_add_album_users": "Kullanıcılar albüme eklenemiyor", - "unable_to_add_assets_to_shared_link": "Öğeler paylaşılan bağlantıya eklenemiyor", - "unable_to_add_comment": "Yorum eklenemiyor", - "unable_to_add_exclusion_pattern": "Hariç tutma modeli eklenemiyor", - "unable_to_add_partners": "Ortaklar eklenemiyor", - "unable_to_add_remove_archive": "Arşive {archived, select, true {dosyayı kaldır} other {dosya ekle}} işlemi yapılamıyor", - "unable_to_add_remove_favorites": "Favorilere {favorite, select, true {dosya ekle} other {dosyayı kaldır}} işlemi yapılamıyor", - "unable_to_archive_unarchive": "{archived, select, true {Arşivleme} other {Arşivden çıkarma}} işlemi yapılamıyor", - "unable_to_change_album_user_role": "Albüm kullanıcı rolü değiştirilemiyor", - "unable_to_change_date": "Tarih değiştirilemiyor", - "unable_to_change_description": "Açıklama değiştirilemiyor", - "unable_to_change_favorite": "Favori durumu değiştirilemiyor", - "unable_to_change_location": "Konum değiştirilemiyor", - "unable_to_change_password": "Şifre değiştirilemiyor", - "unable_to_change_visibility": "{count, plural, one {# kişi} other {# kişi}} için görünürlük değiştirilemedi", - "unable_to_complete_oauth_login": "OAuth giriş işlemi tamamlanamadı", - "unable_to_connect": "Bağlanılamıyor", - "unable_to_copy_to_clipboard": "Panoya kopyalanamıyor, sayfaya https üzerinden eriştiğinizden emin olun", - "unable_to_create": "İş akışı oluşturulamıyor", - "unable_to_create_admin_account": "Yönetici hesabı oluşturulamıyor", - "unable_to_create_api_key": "Yeni API anahtarı oluşturulamıyor", - "unable_to_create_library": "Kütüphane oluşturulamıyor", - "unable_to_create_user": "Kullanıcı oluşturulamıyor", - "unable_to_delete_album": "Albüm silinemiyor", - "unable_to_delete_asset": "Öğe silinemiyor", - "unable_to_delete_assets": "Öğeler silinemiyor", - "unable_to_delete_exclusion_pattern": "Hariç tutma deseni silinemiyor", - "unable_to_delete_shared_link": "Paylaşılan bağlantı silinemiyor", - "unable_to_delete_user": "Kullanıcı silinemiyor", - "unable_to_delete_workflow": "İş akışı silinemiyor", - "unable_to_download_files": "Dosyalar indirilemiyor", - "unable_to_edit_exclusion_pattern": "Hariç tutma deseni düzenlenemiyor", - "unable_to_empty_trash": "Çöp boşaltılamıyor", - "unable_to_enter_fullscreen": "Tam ekran yapılamıyor", - "unable_to_exit_fullscreen": "Tam ekrandan çıkılamıyor", - "unable_to_get_comments_number": "Yorum sayısı alınamıyor", - "unable_to_get_shared_link": "Paylaşılan bağlantı alınamadı", - "unable_to_hide_person": "Kişi gizlenemiyor", - "unable_to_link_motion_video": "Hareket videosu bağlanamıyor", - "unable_to_link_oauth_account": "OAuth hesabı bağlanamıyor", - "unable_to_log_out_all_devices": "Tüm cihazlardan çıkış yapılamıyor", - "unable_to_log_out_device": "Cihazdan çıkış yapılamıyor", - "unable_to_login_with_oauth": "OAuth ile giriş yapılamıyor", - "unable_to_play_video": "Video oynatılamıyor", - "unable_to_reassign_assets_existing_person": "Öğeler {name, select, null {mevcut bir kişiye} other {{name}}} yeniden atanamıyor", - "unable_to_reassign_assets_new_person": "Öğeler yeni bir kişiye yeniden atanamıyor", - "unable_to_refresh_user": "Kullanıcı yenilenemiyor", - "unable_to_remove_album_users": "Albüm kullanıcıları kaldırılamıyor", - "unable_to_remove_api_key": "API anahtarı kaldırılamıyor", - "unable_to_remove_assets_from_shared_link": "Öğeler paylaşılan bağlantıdan kaldırılamıyor", - "unable_to_remove_library": "Kütüphane kaldırılamadı", - "unable_to_remove_partner": "Ortak kaldırılamıyor", - "unable_to_remove_reaction": "Reaksiyon kaldırılamıyor", - "unable_to_reset_password": "Şifre sıfırlanamıyor", - "unable_to_reset_pin_code": "PIN kodu sıfırlanamıyor", - "unable_to_resolve_duplicate": "Çiftler çözümlenemiyor", - "unable_to_restore_assets": "Öğeler geri yüklenemiyor", - "unable_to_restore_trash": "Çöp geri yüklenemiyor", - "unable_to_restore_user": "Kullanıcı geri yüklenemiyor", - "unable_to_save_album": "Albüm kaydedilemiyor", - "unable_to_save_api_key": "API anahtarı kaydedilemiyor", - "unable_to_save_date_of_birth": "Doğum günü kaydedilemiyor", - "unable_to_save_name": "İsim kaydedilemyor", - "unable_to_save_profile": "Profil kaydedilemiyor", - "unable_to_save_settings": "Ayarlar kaydedilemiyor", - "unable_to_scan_libraries": "Kütüphaneler taranamıyor", - "unable_to_scan_library": "Kütüphane taranamıyor", - "unable_to_set_feature_photo": "Özellikli fotoğraf ayarlanamıyor", - "unable_to_set_profile_picture": "Profil resmi ayarlanamıyor", - "unable_to_set_rating": "Derecelendirme ayarlanamıyor", - "unable_to_submit_job": "Görev gönderilemiyor", - "unable_to_trash_asset": "Öğe çöp kutusuna taşınamıyor", - "unable_to_unlink_account": "Hesap bağlantısı kaldırılamıyor", - "unable_to_unlink_motion_video": "Hareket videosunun bağlantısı kaldırılamıyor", - "unable_to_update_album_cover": "Albüm resmi güncellenemiyor", - "unable_to_update_album_info": "Albüm açıklaması güncellenemiyor", - "unable_to_update_library": "Kütüphane güncellenemiyor", - "unable_to_update_location": "Konum güncellenemiyor", - "unable_to_update_settings": "Ayarlar güncellenemiyor", - "unable_to_update_timeline_display_status": "Zaman çizelgesi görüntüleme durumu güncellenemiyor", - "unable_to_update_user": "Kullanıcı güncellenemiyor", - "unable_to_update_workflow": "İş akışı güncelleyemiyor", - "unable_to_upload_file": "Dosya yüklenemiyor" - }, - "errors_text": "Hatalar", - "exclusion_pattern": "Hariç tutma modeli", - "exif": "EXIF", - "exif_bottom_sheet_description": "Açıklama Ekle...", - "exif_bottom_sheet_description_error": "Açıklama güncelleme hatası", - "exif_bottom_sheet_details": "DETAYLAR", - "exif_bottom_sheet_location": "KONUM", - "exif_bottom_sheet_no_description": "Açıklama yok", - "exif_bottom_sheet_people": "KİŞİLER", - "exif_bottom_sheet_person_add_person": "İsim ekle", - "exit_slideshow": "Slayt gösterisinden çık", - "expand_all": "Hepsini genişlet", - "experimental_settings_new_asset_list_subtitle": "Çalışmalar devam ediyor", - "experimental_settings_new_asset_list_title": "Deneysel fotoğraf ızgarasını etkinleştir", - "experimental_settings_subtitle": "Riskleri kabul ederek kullanın!", - "experimental_settings_title": "Deneysel", - "expire_after": "Sonlanma süresi", - "expired": "Süresi dolmuş", - "expires_date": "{date} tarihinde sona eriyor", - "explore": "Keşfet", - "explorer": "Geçmiş", - "export": "Dışa Aktar", - "export_as_json": "JSON olarak Dışa Aktar", - "export_database": "Veritabanını Dışa Aktar", - "export_database_description": "SQLite veritabanını dışa aktarın", - "extension": "Uzantı", - "external": "Harici", - "external_libraries": "Harici kütüphaneler", - "external_network": "Harici ağlar", - "external_network_sheet_info": "Belirlenmiş Wi-Fi ağına bağlı olmadığında uygulama, yukarıdan aşağıya doğru ulaşabileceği aşağıdaki URL'lerden ilki aracılığıyla sunucuya bağlanacaktır", - "face_unassigned": "Yüz atanmadı", - "failed": "Başarısız", - "failed_count": "Başarısız: {count}", - "failed_to_authenticate": "Kimlik doğrulaması yapılamadı", - "failed_to_load_assets": "Öğeler yüklenemedi", - "failed_to_load_folder": "Klasör yüklenemedi", - "favorite": "Favori", - "favorite_action_prompt": "{count} Favorilere eklendi", - "favorite_or_unfavorite_photo": "Favorilere ekle veya çıkar", - "favorites": "Favoriler", - "favorites_page_no_favorites": "Favori öğe bulunamadı", - "feature_photo_updated": "Öne çıkan fotoğraf güncellendi", - "features": "Özellikler", - "features_in_development": "Geliştirme Aşamasındaki Özellikler", - "features_setting_description": "Uygulamanın özelliklerini yönet", - "file_name_or_extension": "Dosya adı veya uzantı", - "file_size": "Dosya boyutu", - "filename": "Dosya adı", - "filetype": "Dosya tipi", - "filter": "Filtre", - "filter_description": "Hedef öğeleri filtreleme koşulları", - "filter_people": "Kişileri filtrele", - "filter_places": "Yerleri süz", - "filters": "Filtreler", - "find_them_fast": "Adlarına göre hızlıca bul", - "first": "İlk", - "fix_incorrect_match": "Yanlış eşleştirmeyi düzelt", - "folder": "Klasör", - "folder_not_found": "Klasör bulunamadı", - "folders": "Klasörler", - "folders_feature_description": "Dosya sistemindeki fotoğraf ve videoları klasör görünümüyle keşfedin", - "forgot_pin_code_question": "PIN kodunuzu mu unuttunuz?", - "forward": "İleri", - "free_up_space": "Alanı boşalt", - "free_up_space_description": "Alan açmak için yedeklenmiş fotoğraf ve videoları cihazınızın çöp kutusuna taşıyın. Sunucudaki kopyalarınız güvende kalır.", - "free_up_space_settings_subtitle": "Cihaz depolama alanını boşalt", - "full_path": "Tam yol: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Bu özellik, çalışabilmek için Google'dan harici kaynaklar yükler.", - "general": "Genel", - "geolocation_instruction_location": "GPS koordinatları olan bir öğeyi tıklayarak konumunu kullanın veya haritadan doğrudan bir konum seçin", - "get_help": "Yardım Al", - "get_people_error": "Kişileri alırken hata oluştu", - "get_wifiname_error": "Wi-Fi adı alınamadı. Gerekli izinleri verdiğinizden ve bir Wi-Fi ağına bağlı olduğunuzdan emin olun", - "getting_started": "Başlarken", - "go_back": "Geri git", - "go_to_folder": "Klasöre git", - "go_to_search": "Aramaya git", - "gps": "GPS", - "gps_missing": "GPS yok", - "grant_permission": "İzin ver", - "group_albums_by": "Albümleri gruplandır...", - "group_country": "Ülkeye göre grupla", - "group_no": "Gruplama yok", - "group_owner": "Sahibe göre gruplandır", - "group_places_by": "Yerleri gruplandır...", - "group_year": "Yıla göre grupla", - "haptic_feedback_switch": "Dokunsal geri bildirimi aç", - "haptic_feedback_title": "Dokunsal Geri Bildirim (Haptic Feedback)", - "has_quota": "Kota var", - "hash_asset": "Karma öğe", - "hashed_assets": "Karma öğeler", - "hashing": "Hashleme", - "header_settings_add_header_tip": "Başlık ekle", - "header_settings_field_validator_msg": "Değer boş olamaz", - "header_settings_header_name_input": "Header adı", - "header_settings_header_value_input": "Header değeri", - "headers_settings_tile_title": "Özel proxy headers", - "height": "Yükseklik", - "hi_user": "Merhaba {name} {email}", - "hide_all_people": "Tüm kişileri gizle", - "hide_gallery": "Galeriyi gizle", - "hide_named_person": "{name} adlı kişiyi gizle", - "hide_password": "Şifreyi gizle", - "hide_person": "Kişiyi gizle", - "hide_schema": "Şemayı gizle", - "hide_text_recognition": "Metin tanımayı gizle", - "hide_unnamed_people": "İsimsiz kişileri gizle", - "home_page_add_to_album_conflicts": "{album} albümüne {added} öğe eklendi. {failed} öğe zaten albümdeydi.", - "home_page_add_to_album_err_local": "Yerel öğeler henüz albümlere eklenemiyor, atlanıyor", - "home_page_add_to_album_success": "{album} albümüne {added} öğe eklendi.", - "home_page_album_err_partner": "Ortak öğeler henüz bir albüme eklenemiyor, atlanıyor", - "home_page_archive_err_local": "Yerel öğeler henüz arşivlenemiyor, atlanıyor", - "home_page_archive_err_partner": "Ortak öğeler henüz arşivlenemiyor, atlanıyor", - "home_page_building_timeline": "Zaman çizelgesi oluşturuluyor", - "home_page_delete_err_partner": "Ortak öğeler silinemez, atlanıyor", - "home_page_delete_remote_err_local": "Uzaktan silme seçimindeki yerel öğeler atlanıyor", - "home_page_favorite_err_local": "Yerel öğeler henüz favorilere eklenemiyor, atlanıyor", - "home_page_favorite_err_partner": "Ortak öğeler henüz favorilere eklenemiyor, atlanıyor", - "home_page_first_time_notice": "Uygulamayı ilk kez kullanıyorsanız, zaman çizelgesinin albümlerdeki fotoğraf ve videolar ile oluşturulabilmesi için lütfen yedekleme için albüm seçtiğinizden emin olun", - "home_page_locked_error_local": "Yerel öğeler kilitli klasöre taşınamıyor, atlanıyor", - "home_page_locked_error_partner": "Ortak öğeler kilitli klasöre taşınamıyor, atlanıyor", - "home_page_share_err_local": "Yerel öğeler bağlantı ile paylaşılamaz, atlanıyor", - "home_page_upload_err_limit": "Aynı anda en fazla 30 öğe yüklenebilir, atlanabilir", - "host": "Ana bilgisayar", - "hour": "Saat", - "hours": "Saatler", - "id": "ID", - "idle": "Boşta", - "ignore_icloud_photos": "iCloud Fotoğraflarını Yok Say", - "ignore_icloud_photos_description": "iCloud'a yüklenmiş fotoğraflar Immich sunucusuna yüklenmesin", - "image": "Resim", - "image_alt_text_date": "{isVideo, select, true {Video} other {Fotoğraf}} {date} tarihinde çekildi", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Fotoğraf}} {person1} ile {date} tarihinde çekildi", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Fotoğraf}} {person1} ve {person2} ile {date} tarihinde çekildi", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Fotoğraf}} {person1}, {person2} ve {person3} ile {date} tarihinde çekildi", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Fotoğraf}} {person1}, {person2} ve diğer {additionalCount, number} kişi ile {date} tarihinde çekildi", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {date} tarihinde çekildi", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1} ile {date} tarihinde çekildi", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1} ve {person2} ile {date} tarihinde çekildi", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1}, {person2} ve {person3} ile {date} tarihinde çekildi", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1}, {person2} ve diğer {additionalCount, number} kişi ile {date} tarihinde çekildi", - "image_saved_successfully": "Resim kaydedildi", - "image_viewer_page_state_provider_download_started": "İndirme Başladı", - "image_viewer_page_state_provider_download_success": "İndirme Başarılı", - "image_viewer_page_state_provider_share_error": "Paylaşım Hatası", - "immich_logo": "Immich Logosu", - "immich_web_interface": "Immich Web Arayüzü", - "import_from_json": "JSON'dan İçe Aktar", - "import_path": "İçe aktarma yolu", - "in_albums": "{count, plural, one {# Albüm} other {# Albümde}}", - "in_archive": "Arşivde", - "in_year": "{year} yılı içinde", - "in_year_selector": "İçinde", - "include_archived": "Arşivlenenleri dahil et", - "include_shared_albums": "Paylaşılmış albümleri dahil et", - "include_shared_partner_assets": "Paylaşılan ortak öğeleri dahil et", - "individual_share": "Bireysel paylaşım", - "individual_shares": "Kişisel paylaşımlar", - "info": "Bilgi", - "interval": { - "day_at_onepm": "Her gün saat 13:00'te", - "hours": "{hours, plural, one {Her saat} other {Her {hours, number} saatte}}", - "night_at_midnight": "Her akşam geceyarısında", - "night_at_twoam": "Her gün gece 2:00'de" - }, - "invalid_date": "Geçersiz tarih", - "invalid_date_format": "Geçersiz tarih formatı", - "invite_people": "Kişileri Davet Et", - "invite_to_album": "Albüme davet et", - "ios_debug_info_fetch_ran_at": "Veri çekme {dateTime} tarihinde çalıştırıldı", - "ios_debug_info_last_sync_at": "Son eşzamanlama {dateTime}", - "ios_debug_info_no_processes_queued": "Hiçbir arka plan işlemi kuyruğa alınmadı", - "ios_debug_info_no_sync_yet": "Henüz arka plan eşzamanlama görevi çalıştırılmadı", - "ios_debug_info_processes_queued": "{count, plural, one {{count} arka plan işlemi kuyruğa alındı} other {{count} arka plan işlemi kuyruğa alındı}}", - "ios_debug_info_processing_ran_at": "İşleme {dateTime} tarihinde çalıştırıldı", - "items_count": "{count, plural, one {# Öğe} other {# Öğe}}", - "jobs": "Görevler", - "json_editor": "JSON düzenleyici", - "json_error": "JSON hatası", - "keep": "Koru", - "keep_albums": "Albümleri sakla", - "keep_albums_count": "{count} {count, plural, one {albüm} other {albüm}} saklanıyor", - "keep_all": "Hepsini koru", - "keep_description": "Alan açarken cihazınızda kalacak öğeleri seçin.", - "keep_favorites": "Favorileri tut", - "keep_on_device": "Cihazda sakla", - "keep_on_device_hint": "Bu cihazda saklanacak öğeleri seçin", - "keep_this_delete_others": "Bunu sakla, diğerlerini sil", - "keeping": "Saklananlar: {items}", - "kept_this_deleted_others": "Bu öğe tutuldu ve {count, plural, one {# varlık} other {# varlık}} silindi", - "keyboard_shortcuts": "Klavye kısayolları", - "language": "Dil", - "language_no_results_subtitle": "Arama teriminizi değiştirmeyi deneyin", - "language_no_results_title": "Dil bulunamadı", - "language_search_hint": "Dilleri ara...", - "language_setting_description": "Tercih ettiğiniz dili seçiniz", - "large_files": "Büyük Dosyalar", - "last": "Son", - "last_months": "{count, plural, one {Geçen ay} other {Son # ay}}", - "last_seen": "Son görülme", - "latest_version": "En Son Sürüm", - "latitude": "Enlem", - "leave": "Ayrıl", - "leave_album": "Albümden çık", - "lens_model": "Mercek modeli", - "let_others_respond": "Diğerlerinin yanıt vermesine izin ver", - "level": "Seviye", - "library": "Kütüphane", - "library_add_folder": "Klasör ekle", - "library_edit_folder": "Klasörü düzenle", - "library_options": "Kütüphane ayarları", - "library_page_device_albums": "Cihazdaki Albümler", - "library_page_new_album": "Yeni albüm", - "library_page_sort_asset_count": "Öğe sayısı", - "library_page_sort_created": "Oluşturma tarihi", - "library_page_sort_last_modified": "Son düzenleme", - "library_page_sort_title": "Albüm başlığı", - "licenses": "Lisanslar", - "light": "Açık", - "like": "Beğen", - "like_deleted": "Beğeni silindi", - "link_motion_video": "Hareket videosunu bağla", - "link_to_oauth": "OAuth'a bağla", - "linked_oauth_account": "Bağlı OAuth hesabı", - "list": "Liste", - "loading": "Yükleniyor", - "loading_search_results_failed": "Arama sonuçları yüklenemedi", - "local": "Yerel", - "local_asset_cast_failed": "Sunucuya yüklenmemiş bir öğe yansıtılamaz", - "local_assets": "Yerel Öğeler", - "local_id": "Yerel Kimlik", - "local_media_summary": "Yerel Medya Özeti", - "local_network": "Yerel ağ", - "local_network_sheet_info": "Uygulama belirlenmiş Wi-Fi ağını kullanırken bu URL üzerinden sunucuya bağlanacaktır", - "location": "Konum", - "location_permission": "Konum izni", - "location_permission_content": "Otomatik geçiş özelliğinin çalışabilmesi için Immich'in mevcut Wi-Fi ağının adını bilmesi, bunu sağlamak için de tam konum iznine ihtiyacı vardır", - "location_picker_choose_on_map": "Haritada seç", - "location_picker_latitude_error": "Geçerli bir enlem yazın", - "location_picker_latitude_hint": "Buraya enlem yazın", - "location_picker_longitude_error": "Geçerli bir boylam yazın", - "location_picker_longitude_hint": "Buraya boylam yazın", - "lock": "Kilitle", - "locked_folder": "Kilitli Klasör", - "log_detail_title": "Günlük Ayrıntıları", - "log_out": "Oturumu kapat", - "log_out_all_devices": "Tüm Cihazlarda Oturumu Kapat", - "logged_in_as": "{user} olarak oturum açıldı", - "logged_out_all_devices": "Tüm cihazlarda oturum kapatıldı", - "logged_out_device": "Oturum kapatılmış cihaz", - "login": "Giriş yap", - "login_disabled": "Giriş devre dışı bırakıldı", - "login_form_api_exception": "API hatası. Lütfen sunucu URL'sini kontrol edin ve tekrar deneyin.", - "login_form_back_button_text": "Geri", - "login_form_email_hint": "mail@adresiniz.com", - "login_form_endpoint_hint": "http://sunucu-ip:port", - "login_form_endpoint_url": "Sunucu Uç Nokta URL", - "login_form_err_http": "Lütfen http:// veya https:// olarak belirtin", - "login_form_err_invalid_email": "Geçersiz E-posta", - "login_form_err_invalid_url": "Geçersiz URL", - "login_form_err_leading_whitespace": "Baştaki boşluk", - "login_form_err_trailing_whitespace": "Sondaki boşluk", - "login_form_failed_get_oauth_server_config": "OAuth kullanırken bir hata oluştu, sunucu URL'sini kontrol edin", - "login_form_failed_get_oauth_server_disable": "OAuth özelliği bu sunucuda mevcut değil", - "login_form_failed_login": "Giriş yaparken hata oluştu, sunucu URL'sini, e-postayı ve şifreyi kontrol edin", - "login_form_handshake_exception": "Sunucuda bir El Sıkışma İstisnası vardı. Kendi kendine imzalanmış bir sertifika kullanıyorsanız, ayarlar menüsünden kendi kendine imzalanmış sertifikalara izin verin.", - "login_form_password_hint": "şifre", - "login_form_save_login": "Oturum açık kalsın", - "login_form_server_empty": "Sunucu URL'si girin.", - "login_form_server_error": "Sunucuya bağlanılamadı.", - "login_has_been_disabled": "Giriş devre dışı bırakıldı.", - "login_password_changed_error": "Şifreniz güncellenirken bir hata oluştu", - "login_password_changed_success": "Şifre başarıyla güncellendi", - "logout_all_device_confirmation": "Tüm cihazlarda oturum kapatmak istediğinizden emin misiniz?", - "logout_this_device_confirmation": "Bu cihazda oturum kapatmak istediğinizden emin misiniz?", - "logs": "Kayıtlar", - "longitude": "Boylam", - "look": "Görünüş", - "loop_videos": "Videoları döngüye al", - "loop_videos_description": "Ayrıntı görünümünde videoların otomatik döngüye alınmasını etkinleştir.", - "main_branch_warning": "Geliştirme sürümü kullanıyorsunuz. Yayınlanan bir sürüm kullanmanızı önemle tavsiye ederiz!", - "main_menu": "Ana menü", - "maintenance_action_restore": "Veritabanı geri yükleniyor", - "maintenance_description": "Immich, bakım moduna alınmıştır.", - "maintenance_end": "Bakım modunu sonlandır", - "maintenance_end_error": "Bakım modu sonlandırılamadı.", - "maintenance_logged_in_as": "Şu anda {user} olarak oturum açılmış durumda", - "maintenance_restore_from_backup": "Yedekten geri yükle", - "maintenance_restore_library": "Kütüphaneni Geri Yükle", - "maintenance_restore_library_confirm": "Her şey doğru görünüyorsa yedeği geri yüklemeye devam edin!", - "maintenance_restore_library_description": "Veritabanı geri yükleniyor", - "maintenance_restore_library_folder_has_files": "{folder} içinde {count} klasör var", - "maintenance_restore_library_folder_no_files": "{folder} içinde eksik dosyalar var!", - "maintenance_restore_library_folder_pass": "okunabilir ve yazılabilir", - "maintenance_restore_library_folder_read_fail": "okunamıyor", - "maintenance_restore_library_folder_write_fail": "yazılamıyor", - "maintenance_restore_library_hint_missing_files": "Önemli dosyalar eksik olabilir", - "maintenance_restore_library_hint_regenerate_later": "Bunları daha sonra ayarlardan yeniden oluşturabilirsiniz", - "maintenance_restore_library_hint_storage_template_missing_files": "Depolama şablonu kullanılıyor mu? Dosyalar eksik olabilir", - "maintenance_restore_library_loading": "Bütünlük kontrolleri ve sezgisel analizler yükleniyor…", - "maintenance_task_backup": "Mevcut veritabanının yedeği oluşturuluyor…", - "maintenance_task_migrations": "Veritabanı geçişleri çalıştırılıyor…", - "maintenance_task_restore": "Seçilen yedek geri yükleniyor…", - "maintenance_task_rollback": "Geri yükleme başarısız oldu, geri dönüş noktasına alınıyor…", - "maintenance_title": "Geçici Olarak Kullanılamıyor", - "make": "Marka", - "manage_geolocation": "Konumu yönet", - "manage_media_access_rationale": "Bu izin, öğelerin çöp kutusuna taşınması ve çöp kutusundan geri yüklenmesi için gereklidir.", - "manage_media_access_settings": "Ayarları aç", - "manage_media_access_subtitle": "Immich uygulamasının medya dosyalarını yönetmesine ve taşımasına izin verin.", - "manage_media_access_title": "Medya Yönetimi Erişimi", - "manage_shared_links": "Paylaşılan bağlantıları yönet", - "manage_sharing_with_partners": "Ortaklarla paylaşımı yönet", - "manage_the_app_settings": "Uygulama ayarlarını yönet", - "manage_your_account": "Hesabınızı yönetin", - "manage_your_api_keys": "API anahtarlarınızı yönetin", - "manage_your_devices": "Cihazlarınızı yönetin", - "manage_your_oauth_connection": "OAuth bağlantınızı yönetin", - "map": "Harita", - "map_assets_in_bounds": "{count, plural, =0 {Bu alanda fotoğraf yok} one {# photo} other {# photos}}", - "map_cannot_get_user_location": "Kullanıcının konumu alınamıyor", - "map_location_dialog_yes": "Evet", - "map_location_picker_page_use_location": "Bu konumu kullan", - "map_location_service_disabled_content": "Mevcut konumunuzdan öğeleri görüntülemek için konum hizmetinin etkinleştirilmesi gerekiyor. Şimdi etkinleştirmek istiyor musunuz?", - "map_location_service_disabled_title": "Konum hizmeti devre dışı bırakıldı", - "map_marker_for_images": "{city}, {country} şehrinde çekilen fotoğraflar için harita işaretleyicisi", - "map_marker_with_image": "Resimli harita işaretleyicisi", - "map_no_location_permission_content": "Mevcut konumunuzdan öğeleri görüntülemek için konum iznine ihtiyaç var. Şimdi izin vermek istiyor musunuz?", - "map_no_location_permission_title": "Konum izni reddedildi", - "map_settings": "Harita ayarları", - "map_settings_dark_mode": "Koyu tema", - "map_settings_date_range_option_day": "Son 24 saat", - "map_settings_date_range_option_days": "Son {days} gün", - "map_settings_date_range_option_year": "Son yıl", - "map_settings_date_range_option_years": "Son {years} yıl", - "map_settings_dialog_title": "Harita Ayarları", - "map_settings_include_show_archived": "Arşivdekileri dahil et", - "map_settings_include_show_partners": "Ortakları Dahil Et", - "map_settings_only_show_favorites": "Sadece Favorileri Göster", - "map_settings_theme_settings": "Harita Teması", - "map_zoom_to_see_photos": "Fotoğrafları görmek için uzaklaştırın", - "mark_all_as_read": "Tümünü okundu olarak işaretle", - "mark_as_read": "Okundu olarak işaretle", - "marked_all_as_read": "Tümü okundu olarak işaretlendi", - "matches": "Eşleşenler", - "matching_assets": "Eşleşen Öğeler", - "media_type": "Medya türü", - "memories": "Anılar", - "memories_all_caught_up": "Tümü görüldü", - "memories_check_back_tomorrow": "Daha fazla anı için yarın tekrar ziyaret edin", - "memories_setting_description": "Anılarınızda görmek istediklerinizi yönetin", - "memories_start_over": "Baştan Başla", - "memories_swipe_to_close": "Kapatmak için yukarı kaydırın", - "memory": "Anı", - "memory_lane_title": "Anılara Yolculuk {title}", - "menu": "Menü", - "merge": "Birleştir", - "merge_people": "Kişileri birleştir", - "merge_people_limit": "Aynı anda 5 yüzü birleştirebilirsiniz", - "merge_people_prompt": "Bu kişileri birleştirmek istiyor musunuz? Bu işlem geri alınamaz.", - "merge_people_successfully": "Kişiler başarılı bir şekilde birleştirildi", - "merged_people_count": "{count, plural, one {# kişi} other {# kişi}} birleştirildi", - "minimize": "Küçült", - "minute": "Dakika", - "minutes": "Dakika", - "mirror_horizontal": "Yatay", - "mirror_vertical": "Dikey", - "missing": "Eksik", - "mobile_app": "Mobil Uygulama", - "mobile_app_download_onboarding_note": "Aşağıdaki seçenekleri kullanarak eşlik eden mobil uygulamayı indirin", - "model": "Model", - "month": "Ay", - "monthly_title_text_date_format": "AAAA y", - "more": "Daha fazla", - "move": "Taşı", - "move_down": "Aşağı taşı", - "move_off_locked_folder": "Kilitli klasörden taşı", - "move_to": "Şuraya taşı", - "move_to_device_trash": "Cihaz çöp kutusuna taşı", - "move_to_lock_folder_action_prompt": "{count} kilitli klasöre eklendi", - "move_to_locked_folder": "Kilitli klasöre taşı", - "move_to_locked_folder_confirmation": "Bu fotoğraflar ve videolar tüm albümlerden kaldırılacak ve yalnızca kilitli klasörden görüntülenebilecektir", - "move_up": "Yukarı taşı", - "moved_to_archive": "{count, plural, one {# öğe} other {# öğeler}} arşive taşındı", - "moved_to_library": "{count, plural, one {# öğe} other {# öğeler}} kitaplığa taşındı", - "moved_to_trash": "Çöp kutusuna taşındı", - "multiselect_grid_edit_date_time_err_read_only": "Salt okunur öğelerin tarihi düzenlenemedi, atlanıyor", - "multiselect_grid_edit_gps_err_read_only": "Salt okunur öğelerin konumu düzenlenemedi, atlanıyor", - "mute_memories": "Anıları sessize al", - "my_albums": "Albümlerim", - "name": "İsim", - "name_or_nickname": "İsim veya takma isim", - "name_required": "Ad girilmesi zorunludur", - "navigate": "Gezin", - "navigate_to_time": "Zamana Git", - "network_requirement_photos_upload": "Fotoğrafları yedeklemek için mobil veriyi kullan", - "network_requirement_videos_upload": "Videoları yedeklemek için mobil veriyi kullan", - "network_requirements": "Ağ Gereksinimleri", - "network_requirements_updated": "Ağ durumu değişti, yedekleme kuyruğu sıfırlandı", - "networking_settings": "Ağ Ayarları", - "networking_subtitle": "Sunucu uç nokta ayarlarını düzenle", - "never": "Asla", - "new_album": "Yeni albüm", - "new_api_key": "Yeni API Anahtarı", - "new_date_range": "Yeni tarih aralığı", - "new_password": "Yeni şifre", - "new_person": "Yeni kişi", - "new_pin_code": "Yeni PIN kodu", - "new_pin_code_subtitle": "Kilitli klasöre ilk kez erişiyorsunuz. Bu sayfaya güvenli erişim için bir PIN kodu oluşturun", - "new_timeline": "Yeni Zaman Çizelgesi", - "new_update": "Yeni güncelleme", - "new_user_created": "Yeni kullanıcı oluşturuldu", - "new_version_available": "YENİ SÜRÜM MEVCUT", - "newest_first": "Önce en yeniler", - "next": "Sonraki", - "next_memory": "Sonraki anı", - "no": "Hayır", - "no_actions_added": "Henüz eklenen eylem yok", - "no_albums_found": "Albüm bulunamadı", - "no_albums_message": "Fotoğraf ve videolarınızı düzenlemek için yeni bir albüm oluşturun", - "no_albums_with_name_yet": "Henüz bu isimde bir albümünüz bulunmuyor.", - "no_albums_yet": "Henüz albüm oluşturmadınız.", - "no_archived_assets_message": "Fotoğraf görünümünüzden kaldırmak için fotoğrafları ve videoları arşivleyin", - "no_assets_message": "İlk fotoğrafınızı yüklemek için tıklayın", - "no_assets_to_show": "Gösterilecek öğe yok", - "no_cast_devices_found": "Yansıtılacak cihaz bulunamadı", - "no_checksum_local": "Sağlama toplamı mevcut değil - yerel varlıkları alamıyor", - "no_checksum_remote": "Sağlama toplamı mevcut değil - uzak varlık alınamıyor", - "no_configuration_needed": "Yapılandırmaya gerek yok", - "no_devices": "Yetkili cihaz yok", - "no_duplicates_found": "Hiçbir kopya bulunamadı.", - "no_exif_info_available": "EXIF bilgisi mevcut değil", - "no_explore_results_message": "Koleksiyonunuzu keşfetmek için daha fazla fotoğraf yükleyin.", - "no_favorites_message": "En sevdiğiniz fotoğraf ve videoları hızlıca bulmak için favorilere ekleyin", - "no_filters_added": "Henüz filtre eklenmedi", - "no_libraries_message": "Fotoğraf ve videolarınızı görmek için bir harici kütüphane oluşturun", - "no_local_assets_found": "Bu sağlama toplamı ile yerel varlık bulunamadı", - "no_location_set": "Konum ayarlanmadı", - "no_locked_photos_message": "Kilitli klasördeki fotoğraf ve videolar gizlidir; kitaplığınızda gezinirken veya arama yaparken görünmezler.", - "no_name": "İsim Yok", - "no_notifications": "Bildirim yok", - "no_people_found": "Eşleşen kişi bulunamadı", - "no_places": "Yer yok", - "no_remote_assets_found": "Bu sağlama toplamı ile uzaktaki varlık bulunamadı", - "no_results": "Sonuç bulunamadı", - "no_results_description": "Eş anlamlı ya da daha genel anlamlı bir kelime deneyin", - "no_shared_albums_message": "Fotoğrafları ve videoları ağınızdaki kişilerle paylaşmak için bir albüm oluşturun", - "no_uploads_in_progress": "Yükleme işlemi yok", - "none": "Yok", - "not_allowed": "İzin verilmiyor", - "not_available": "YOK", - "not_in_any_album": "Hiçbir albümde değil", - "not_selected": "Seçilmedi", - "note_apply_storage_label_to_previously_uploaded assets": "Not: Daha önce yüklenen öğeler için bir depolama yolu etiketi uygulamak üzere şunu başlatın", - "notes": "Notlar", - "nothing_here_yet": "Burada henüz bir şey yok", - "notification_permission_dialog_content": "Bildirimleri etkinleştirmek için cihaz ayarlarına gidin ve izin verin.", - "notification_permission_list_tile_content": "Bildirimleri etkinleştirmek için izin verin.", - "notification_permission_list_tile_enable_button": "Bildirimleri Etkinleştir", - "notification_permission_list_tile_title": "Bildirim İzni", - "notification_toggle_setting_description": "E-posta bildirimlerine izin ver", - "notifications": "Bildirimler", - "notifications_setting_description": "Bildirimleri yönetin", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium Yapılandırıcı", - "obtainium_configurator_instructions": "Obtainium kullanarak Android uygulamasını doğrudan Immich GitHub sürümünden yükleyin ve güncelleyin. Bir API anahtarı oluşturun ve bir varyant seçerek Obtainium yapılandırma bağlantınızı oluşturun", - "ocr": "OCR", - "official_immich_resources": "Resmi Immich Kaynakları", - "offline": "Çevrim dışı", - "offset": "Ofset", - "ok": "Tamam", - "oldest_first": "Eski olan önce", - "on_this_device": "Bu cihazda", - "onboarding": "Uyum Süreci", - "onboarding_locale_description": "Tercih ettiğiniz dili seçin. Bu ayarı daha sonra değiştirebilirsiniz.", - "onboarding_privacy_description": "Şu (isteğe bağlı) özellikler harici hizmetlere dayanır ve ayarlardan herhangi bir zamanda devre dışı bırakılabilir.", - "onboarding_server_welcome_description": "Örneğinizi bazı yaygın ayarlarla ayarlayalım.", - "onboarding_theme_description": "İnstance’ınız için bir renk teması seçin. Bunu daha sonra ayarlarınızdan değiştirebilirsiniz.", - "onboarding_user_welcome_description": "Haydi başlayalım!", - "onboarding_welcome_user": "Hoş geldin, {user}", - "online": "Çevrimiçi", - "only_favorites": "Sadece favoriler", - "open": "Aç", - "open_in_map_view": "Harita görünümünde aç", - "open_in_openstreetmap": "OpenStreetMap'te Aç", - "open_the_search_filters": "Arama filtrelerini aç", - "options": "Seçenekler", - "or": "veya", - "organize_into_albums": "Albümler halinde düzenle", - "organize_into_albums_description": "Mevcut eşzamanlama ayarlarını kullanarak mevcut fotoğrafları albümlere ekleyin", - "organize_your_library": "Kütüphanenizi düzenleyin", - "original": "orijinal", - "other": "Diğer", - "other_devices": "Diğer cihazlar", - "other_entities": "Diğer kuruluşlar", - "other_variables": "Diğer değişkenler", - "owned": "Sahip olunan", - "owner": "Sahip", - "page": "Sayfa", - "partner": "Ortak", - "partner_can_access": "{partner} erişebilir", - "partner_can_access_assets": "Arşivlenenler ve Silinenler dışındaki tüm fotoğraf ve videolarınız", - "partner_can_access_location": "Fotoğraf ve videolarınızın çekildiği konum", - "partner_list_user_photos": "{user} fotoğrafları", - "partner_list_view_all": "Tümünü gör", - "partner_page_empty_message": "Fotoğraflarınız henüz hiçbir ortakla paylaşılmadı.", - "partner_page_no_more_users": "Eklenecek başka kullanıcı yok", - "partner_page_partner_add_failed": "Ortak eklenemedi", - "partner_page_select_partner": "Ortak seç", - "partner_page_shared_to_title": "Paylaşıldı", - "partner_page_stop_sharing_content": "{partner} artık fotoğraflarınıza erişemeyecek.", - "partner_sharing": "Ortak Paylaşımı", - "partners": "Ortaklar", - "password": "Şifre", - "password_does_not_match": "Şifre eşleşmiyor", - "password_required": "Şifre Gerekiyor", - "password_reset_success": "Şifre başarıyla sıfırlandı", - "past_durations": { - "days": "{days, plural, one {Dün} other {Son # gün}}", - "hours": "Son {hours, plural, one {saat} other {# saat}}", - "years": "{years, plural, one {Geçen yıl} other {Son # yıl}}" - }, - "path": "Yol", - "pattern": "Desen", - "pause": "Duraklat", - "pause_memories": "Anıları duraklat", - "paused": "Durduruldu", - "pending": "Beklemede", - "people": "Kişiler", - "people_edits_count": "{count, plural, one {# kişi} other {# kişi}} düzenlendi", - "people_feature_description": "Kişilere göre gruplanmış fotoğrafları ve videoları inceleyin", - "people_selected": "{count, plural, one {# kişi seçildi} other {# kişi seçildi}}", - "people_sidebar_description": "Yan panelde kişilere hızlı erişim bağlantısı göster", - "permanent_deletion_warning": "Kalıcı silme uyarısı", - "permanent_deletion_warning_setting_description": "Öğeleri kalıcı olarak silerken uyarı göster", - "permanently_delete": "Kalıcı olarak sil", - "permanently_delete_assets_count": "{count, plural, one {öğe} other {öğe}} kalıcı olarak silindi", - "permanently_delete_assets_prompt": "Bu {count, plural, one {öğeyi} other {# öğeleri}} kalıcı olarak silmek istediğinizden emin misiniz? Bu işlem {count, plural, one {bu öğeyi} other {bu öğeleri}} albümlerinizden de kaldırır.", - "permanently_deleted_asset": "Kalıcı olarak silinmiş öğeler", - "permanently_deleted_assets_count": "{count, plural, one {# öğe} other {# öğe}} kalıcı olarak silindi", - "permission": "İzin", - "permission_empty": "İzniniz boş olmamalı", - "permission_onboarding_back": "Geri", - "permission_onboarding_continue_anyway": "Yine de devam et", - "permission_onboarding_get_started": "Haydi başlayalım", - "permission_onboarding_go_to_settings": "Ayarlara git", - "permission_onboarding_permission_denied": "İzin reddedildi. Immich'i kullanmak için Ayarlar'da fotoğraf ve video izinlerini verin.", - "permission_onboarding_permission_granted": "İzin verildi! Artık hazırsınız.", - "permission_onboarding_permission_limited": "Sınırlı izin. Immich'in tüm fotoğrav ve videolarınızı yedeklemesine ve yönetmesine izin vermek için Ayarlar'da fotoğraf ve video izinlerini verin.", - "permission_onboarding_request": "Immich'in fotoğraflarınızı ve videolarınızı görüntüleyebilmesi için izne ihtiyacı var.", - "person": "Kişi", - "person_age_months": "{months, plural, one {# aylık} other {# aylık}}", - "person_age_year_months": "1 yıl, {months, plural, one {# aylık} other {# aylık}}", - "person_age_years": "{years, plural, other {# yaşında}}", - "person_birthdate": "{date} tarihinde doğdu", - "person_hidden": "{name}{hidden, select, true { (gizli)} other {}}", - "person_recognized": "Tanınan kişi", - "person_selected": "Seçilen kişi", - "photo_shared_all_users": "Fotoğraflarınızı tüm kullanıcılarla paylaştınız gibi görünüyor veya paylaşacak kullanıcı bulunmuyor.", - "photos": "Fotoğraflar", - "photos_and_videos": "Fotoğraflar & Videolar", - "photos_count": "{count, plural, one {{count, number} fotoğraf} other {{count, number} fotoğraf}}", - "photos_from_previous_years": "Önceki yıllardan fotoğraflar", - "photos_only": "Sadece Fotoğraflar", - "pick_a_location": "Bir konum seçin", - "pick_custom_range": "Özel aralık", - "pick_date_range": "Bir tarih aralığı seçin", - "pin_code_changed_successfully": "PIN kodu başarıyla değiştirildi", - "pin_code_reset_successfully": "PIN kodu başarıyla sıfırlandı", - "pin_code_setup_successfully": "PIN kodu başarıyla ayarlandı", - "pin_verification": "PIN kodu doğrulama", - "place": "Yer", - "places": "Konumlar", - "places_count": "{count, plural, one {{count, number} yer} other {{count, number} yer}}", - "play": "Oynat", - "play_memories": "Anıları oynat", - "play_motion_photo": "Hareketli Fotoğrafı Oynat", - "play_or_pause_video": "Videoyu oynat ya da durdur", - "play_original_video": "Orijinal videoyu oynat", - "play_original_video_setting_description": "Kodlanmış videolar yerine orijinal videoların oynatılmasını tercih edin. Orijinal öğe uyumlu değilse, doğru şekilde oynatılmayabilir.", - "play_transcoded_video": "Kodlanmış videoyu oynat", - "please_auth_to_access": "Erişim için lütfen kimliğinizi doğrulayın", - "port": "Port", - "preferences_settings_subtitle": "Uygulama tercihlerini düzenle", - "preferences_settings_title": "Tercihler", - "preparing": "Hazırlanıyor", - "preset": "Ön ayar", - "preview": "Önizleme", - "previous": "Önceki", - "previous_memory": "Önceki anı", - "previous_or_next_day": "Gün ileri/geri", - "previous_or_next_month": "Ay ileri/geri", - "previous_or_next_photo": "Fotoğraf ileri/geri", - "previous_or_next_year": "Yıl ileri/geri", - "primary": "Birincil", - "privacy": "Gizlilik", - "profile": "Profil", - "profile_drawer_app_logs": "Günlükler", - "profile_drawer_client_server_up_to_date": "Uygulama ve sunucu güncel", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Salt okunur mod etkinleştirildi. Çıkmak için kullanıcı avatar simgesine uzun basın.", - "profile_image_of_user": "{user} kullanıcısının profil resmi", - "profile_picture_set": "Profil resmi ayarlandı.", - "public_album": "Herkese açık albüm", - "public_share": "Genel paylaşım", - "purchase_account_info": "Destekçi", - "purchase_activated_subtitle": "Immich ve açık kaynak yazılıma destek olduğunuz için teşekkür ederiz", - "purchase_activated_time": "{date} tarihinde etkinleştirildi", - "purchase_activated_title": "Anahtarınız başarıyla etkinleştirildi", - "purchase_button_activate": "Aktifleştir", - "purchase_button_buy": "Satın al", - "purchase_button_buy_immich": "Immich satın al", - "purchase_button_never_show_again": "Bir daha gösterme", - "purchase_button_reminder": "30 gün içinde bana hatırlat", - "purchase_button_remove_key": "Anahtarı kaldır", - "purchase_button_select": "Seç", - "purchase_failed_activation": "Etkinleştirme başarısız oldu! Lütfen e-postadaki ürün anahtarını kontrol edin!", - "purchase_individual_description_1": "Bireysel kullanım için", - "purchase_individual_description_2": "Destekçi statüsü", - "purchase_individual_title": "Bireysel", - "purchase_input_suggestion": "Zaten bir ürün anahtarınız var mı? Lütfen aşağıya girin", - "purchase_license_subtitle": "Immich'i satın alarak devam eden gelişimini destekleyin", - "purchase_lifetime_description": "Ömür boyu geçerli", - "purchase_option_title": "SATIN ALMA SEÇENEKLERİ", - "purchase_panel_info_1": "Immich'in gelişimi zaman ve çaba gerektiriyor ve tam zamanlı geliştiricilerimiz var. Amacımız, açık kaynak yazılımı sürdürülebilir bir gelir kaynağı haline getirmek.", - "purchase_panel_info_2": "Ücretli özellikler (paywall) eklememeye kararlı olduğumuz için, bu satın alma işlemi Immich'te ek işlevsellik sağlamaz. Immich'in sürekli gelişimini desteklemek için sizin gibi kullanıcılara güveniyoruz.", - "purchase_panel_title": "Projeyi destekleyin", - "purchase_per_server": "Sunucu başına", - "purchase_per_user": "Kullanıcı başına", - "purchase_remove_product_key": "Ürün anahtarını kaldır", - "purchase_remove_product_key_prompt": "Ürün anahtarını kaldırmak istediğinize emin misiniz?", - "purchase_remove_server_product_key": "Sunucu ürün anahtarını kaldır", - "purchase_remove_server_product_key_prompt": "Sunucu ürün anahtarını kaldırmak istediğinize emin misiniz?", - "purchase_server_description_1": "Tüm sunucu için", - "purchase_server_description_2": "Destekçi statüsü", - "purchase_server_title": "Sunucu", - "purchase_settings_server_activated": "Sunucu ürün anahtarı, yönetici tarafından yönetilir", - "query_asset_id": "Öğe Kimliği Sorgulama", - "queue_status": "Sırada {count}/{total}", - "rate_asset": "Öğeyi Derecelendir", - "rating": "Derecelendirme", - "rating_clear": "Derecelendirmeyi temizle", - "rating_count": "{count, plural, one {# yıldız} other {# yıldız}}", - "rating_description": "EXIF derecelendirmesini bilgi panelinde göster", - "rating_set": "Derecelendirme {rating, plural, one {# yıldız} other {# yıldız}} olarak ayarlandı", - "reaction_options": "Tepki seçenekleri", - "read_changelog": "Değişiklik günlüğünü oku", - "readonly_mode_disabled": "Salt okunur mod devre dışı", - "readonly_mode_enabled": "Salt okunur mod etkin", - "ready_for_upload": "Yüklemeye hazır", - "reassign": "Yeniden ata", - "reassigned_assets_to_existing_person": "{count, plural, one {# öğe} other {# öğeler}} {name, select, null {mevcut bir kişiye} other {{name}}} atandı", - "reassigned_assets_to_new_person": "{count, plural, one {# öğe} other {# öğeler}} yeni bir kişiye atandı", - "reassing_hint": "Seçili öğeleri mevcut bir kişiye atayın", - "recent": "Son", - "recent-albums": "Son kaydedilen albümler", - "recent_searches": "Son aramalar", - "recently_added": "Son eklenenler", - "recently_added_page_title": "Son Eklenenler", - "recently_taken": "Son çekilenler", - "recently_taken_page_title": "Son Çekilenler", - "refresh": "Yenile", - "refresh_encoded_videos": "Kodlanmış videoları yenile", - "refresh_faces": "Yüzleri yenile", - "refresh_metadata": "Meta verileri yenile", - "refresh_thumbnails": "Küçük resimleri yenile", - "refreshed": "Yenilendi", - "refreshes_every_file": "Tüm mevcut ve yeni dosyaları tekrar yükler", - "refreshing_encoded_video": "Kodlanmış videolar yenileniyor", - "refreshing_faces": "Yüzler yenileniyor", - "refreshing_metadata": "Meta veriler yenileniyor", - "regenerating_thumbnails": "Küçük resimler yeniden oluşturuluyor", - "remote": "Uzaktan", - "remote_assets": "Uzak Öğeler", - "remote_media_summary": "Uzaktan Medya Özeti", - "remove": "Kaldır", - "remove_assets_album_confirmation": "{count, plural, one {# öğe} other {# öğeler}} albümden çıkarmak istediğinizden emin misiniz?", - "remove_assets_shared_link_confirmation": "{count, plural, one {# öğe} other {# öğeler}} bu paylaşılan bağlantıdan çıkarmak istediğinizden emin misiniz?", - "remove_assets_title": "Öğeleri çıkar?", - "remove_custom_date_range": "Özel tarih aralığını kaldır", - "remove_deleted_assets": "Silinen Öğeleri Kaldır", - "remove_from_album": "Albümden çıkar", - "remove_from_album_action_prompt": "{count} albümden kaldırıldı", - "remove_from_favorites": "Favorilerden çıkar", - "remove_from_lock_folder_action_prompt": "{count} kilitli klasörden kaldırıldı", - "remove_from_locked_folder": "Kilitli klasörden kaldır", - "remove_from_locked_folder_confirmation": "Bu fotoğraf ve videoları kilitli klasörden çıkarmak istediğinizden emin misiniz? Çıkarıldıklarında kitaplığınızda görünür olacaklar.", - "remove_from_shared_link": "Paylaşılan bağlantıdan çıkar", - "remove_memory": "Anıyı kaldır", - "remove_photo_from_memory": "Bu anıdan fotoğrafı kaldır", - "remove_tag": "Etiketi kaldır", - "remove_url": "Bağlantıyı kaldır", - "remove_user": "Kullanıcıyı çıkar", - "removed_api_key": "API anahtarı {name} kaldırıldı", - "removed_from_archive": "Arşivden çıkarıldı", - "removed_from_favorites": "Favorilerden kaldırıldı", - "removed_from_favorites_count": "{count, plural, other {#}} favorilerden çıkarıldı", - "removed_memory": "Anı kaldırıldı", - "removed_photo_from_memory": "Fotoğraf anıdan kaldırıldı", - "removed_tagged_assets": "{count, plural, one {# öğenin} other {# öğelerin}} etiketleri kaldırıldı", - "rename": "Yeniden adlandır", - "repair": "Onar", - "repair_no_results_message": "Bulunamayan ve eksik dosyalar burada listelenecektir", - "replace_with_upload": "Yükleme ile değiştir", - "repository": "Depo", - "require_password": "Şifre gerekiyor", - "require_user_to_change_password_on_first_login": "Kullanıcı ilk girişte şifreyi değiştirmeli", - "rescan": "Yeniden tara", - "reset": "Sıfırla", - "reset_password": "Şifreyi sıfırla", - "reset_people_visibility": "Kişilerin görünürlüğünü sıfırla", - "reset_pin_code": "PIN kodunu sıfırlayın", - "reset_pin_code_description": "PIN kodunuzu unuttuysanız, sıfırlamak için sunucu yöneticisiyle iletişime geçebilirsiniz", - "reset_pin_code_success": "PIN kodu başarıyla sıfırlandı", - "reset_pin_code_with_password": "PIN kodunuzu her zaman şifrenizle sıfırlayabilirsiniz", - "reset_sqlite": "SQLite Veritabanını Sıfırla", - "reset_sqlite_confirmation": "SQLite veritabanını sıfırlamak istediğinizden emin misiniz? Verileri yeniden eşzamanlamak için oturumu kapatıp tekrar oturum açmanız gerekecektir", - "reset_sqlite_success": "SQLite veritabanını başarıyla sıfırladınız", - "reset_to_default": "Varsayılana sıfırla", - "resolution": "Çözünürlük", - "resolve_duplicates": "Çiftleri çöz", - "resolved_all_duplicates": "Tüm çiftler çözüldü", - "restore": "Geri yükle", - "restore_all": "Tümünü geri yükle", - "restore_trash_action_prompt": "{count} çöp kutusundan geri yüklendi", - "restore_user": "Kullanıcıyı geri yükle", - "restored_asset": "Öğe geri yüklendi", - "resume": "Devam et", - "resume_paused_jobs": "Sürdür {count, plural, one {# duraklatılmış iş} other {# duraklatılmış işler}}", - "retry_upload": "Yeniden yüklemeyi dene", - "review_duplicates": "Kopyaları gözden geçir", - "review_large_files": "Büyük dosyaları incele", - "role": "Rol", - "role_editor": "Düzenleyici", - "role_viewer": "Görüntüleyici", - "running": "Çalışıyor", - "save": "Kaydet", - "save_to_gallery": "Fotoğraflar'a kaydet", - "saved": "Kaydedildi", - "saved_api_key": "API anahtarı kaydedildi", - "saved_profile": "Profil kaydedildi", - "saved_settings": "Kaydedilen ayarlar", - "say_something": "Bir şey söyle", - "scaffold_body_error_occurred": "Bir hata meydana geldi", - "scan": "Tara", - "scan_all_libraries": "Tüm Kütüphaneleri Tara", - "scan_library": "Kütüphaneyi tara", - "scan_settings": "Ayarları Tara", - "scanning": "Taranıyor", - "scanning_for_album": "Albüm için taranıyor...", - "search": "Ara", - "search_albums": "Albüm ara", - "search_by_context": "Bağlama göre ara", - "search_by_description": "Açıklamaya göre ara", - "search_by_description_example": "Sapa'da yürüyüş günü", - "search_by_filename": "Dosya adına veya uzantısına göre ara", - "search_by_filename_example": "Örn. IMG_1234.JPG veya PNG", - "search_by_ocr": "OCR'ye göre ara", - "search_by_ocr_example": "Sütlü Kahve", - "search_camera_lens_model": "Lens modelini ara...", - "search_camera_make": "Kamera markasına göre ara...", - "search_camera_model": "Kamera modeline göre ara...", - "search_city": "Şehre göre ara...", - "search_country": "Ülkeye göre ara...", - "search_filter_apply": "Filtreyi uygula", - "search_filter_camera_title": "Kamera tipi seç", - "search_filter_date": "Tarih", - "search_filter_date_interval": "{start} -> {end}", - "search_filter_date_title": "Tarih aralığı seç", - "search_filter_display_option_not_in_album": "Albümde değil", - "search_filter_display_options": "Görüntü Seçenekleri", - "search_filter_filename": "Dosya adına göre ara", - "search_filter_location": "Konum", - "search_filter_location_title": "Konum seç", - "search_filter_media_type": "Medya Türü", - "search_filter_media_type_title": "Medya türü seç", - "search_filter_ocr": "OCR'ye göre ara", - "search_filter_people_title": "Kişi seç", - "search_filter_star_rating": "Yıldız Puanı", - "search_for": "Araştır", - "search_for_existing_person": "Mevcut bir kişiyi ara", - "search_no_more_result": "Daha fazla sonuç yok", - "search_no_people": "Kişi yok", - "search_no_people_named": "\"{name}\" isimli bir kişi yok", - "search_no_result": "Sonuç bulunamadı. Farklı bir arama terimi veya kombinasyon deneyin", - "search_options": "Arama seçenekleri", - "search_page_categories": "Kategoriler", - "search_page_motion_photos": "Canlı Fotoğraflar", - "search_page_no_objects": "Hiçbir Nesne Bilgisi Mevcut Değil", - "search_page_no_places": "Konum Bilgisi Bulunamadı", - "search_page_screenshots": "Ekran görüntüleri", - "search_page_search_photos_videos": "Fotoğraf ve videolarda ara", - "search_page_selfies": "Selfie'ler", - "search_page_things": "Nesneler", - "search_page_view_all_button": "Tümü", - "search_page_your_activity": "Etkinliğiniz", - "search_page_your_map": "Haritanız", - "search_people": "Kişilere göre ara", - "search_places": "Yerleri ara", - "search_rating": "Derecelendirerek arayın...", - "search_result_page_new_search_hint": "Yeni Arama", - "search_settings": "Ayarları ara", - "search_state": "Eyalet/İl ara...", - "search_suggestion_list_smart_search_hint_1": "Akıllı arama varsayılan olarak etkindir, meta verileri aramak için şu sözdizimini kullanın ", - "search_suggestion_list_smart_search_hint_2": "m:meta-veri-araması", - "search_tags": "Etiketleri ara...", - "search_timezone": "Saat dilimi ara...", - "search_type": "Arama türü", - "search_your_photos": "Fotoğraflarınızı arayın", - "searching_locales": "Yerleri arıyor...", - "second": "Saniye", - "see_all_people": "Tüm kişileri gör", - "select": "Seç", - "select_album": "Albüm seç", - "select_album_cover": "Albüm kapağı seç", - "select_albums": "Albümleri seç", - "select_all": "Tümünü seç", - "select_all_duplicates": "Tüm çiftleri seç", - "select_all_in": "{group} içindekilerin tümünü seç", - "select_avatar_color": "Avatar rengini seç", - "select_count": "{count, plural, one {Seç #} other {Seç #}}", - "select_cutoff_date": "Tarih sınırını seç", - "select_face": "Yüzü seç", - "select_featured_photo": "Öne çıkan fotoğrafı seç", - "select_from_computer": "Bilgisayardan seç", - "select_keep_all": "Hepsini sakla", - "select_library_owner": "Kütüphane sahibini seç", - "select_new_face": "Yeni yüz seç", - "select_people": "Kişi seç", - "select_person": "Kişileri seç", - "select_person_to_tag": "Etiketlemek için bir kişi seçin", - "select_photos": "Fotoğrafları seç", - "select_trash_all": "Hepsini çöpe at", - "select_user_for_sharing_page_err_album": "Albüm oluşturulamadı", - "selected": "Seçildi", - "selected_count": "{count, plural, other {# seçildi}}", - "selected_gps_coordinates": "Seçilen GPS Koordinatları", - "send_message": "Mesaj gönder", - "send_welcome_email": "Hoş geldin e-postası gönder", - "server_endpoint": "Sunucu Uç Noktası", - "server_info_box_app_version": "Uygulama Sürümü", - "server_info_box_server_url": "Sunucu URL", - "server_offline": "Sunucu çevrimdışı", - "server_online": "Sunucu çevrimiçi", - "server_privacy": "Sunucu Gizliliği", - "server_restarting_description": "Bu sayfa kısa bir süre içinde yenilenecektir.", - "server_restarting_title": "Sunucu yeniden başlatılıyor", - "server_stats": "Sunucu istatistikleri", - "server_update_available": "Sunucu güncellemesi mevcut", - "server_version": "Sunucu Sürümü", - "set": "Ayarla", - "set_as_album_cover": "Albüm resmi olarak ayarla", - "set_as_featured_photo": "Öne çıkan fotoğraf olarak ayarla", - "set_as_profile_picture": "Profil resmi olarak ayarla", - "set_date_of_birth": "Doğum tarihini ayarla", - "set_profile_picture": "Profil resmini ayarla", - "set_slideshow_to_fullscreen": "Slayt gösterisini tam ekran yap", - "set_stack_primary_asset": "Birincil öğe olarak ayarla", - "setting_image_viewer_help": "Görüntüleyici önce küçük resmi gösterir, ardından orta boy önizlemeyi (etkinleştirilmişse) ve son olarak orijinali (etkinleştirilmişse) gösterir.", - "setting_image_viewer_original_subtitle": "Orijinal tam çözünürlüklü görüntüyü (büyük!) yüklemek için etkinleştirin. Veri kullanımını azaltmak için devre dışı bırakın (hem ağ hem de cihaz önbelleği).", - "setting_image_viewer_original_title": "Orijinal görüntüyü yükle", - "setting_image_viewer_preview_subtitle": "Orta çözünürlüklü bir görüntü göstermek için etkinleştirin. Orijinali doğrudan göstermek veya yalnızca küçük resmi kullanmak için devre dışı bırakın.", - "setting_image_viewer_preview_title": "Önizleme görüntüsünü yükle", - "setting_image_viewer_title": "Resimler", - "setting_languages_apply": "Uygula", - "setting_languages_subtitle": "Uygulama dilini değiştir", - "setting_notifications_notify_failures_grace_period": "Arka plan yedekleme hatalarını bildir: {duration}", - "setting_notifications_notify_hours": "{count} saat", - "setting_notifications_notify_immediately": "hemen", - "setting_notifications_notify_minutes": "{count} dakika", - "setting_notifications_notify_never": "hiçbir zaman", - "setting_notifications_notify_seconds": "{count} saniye", - "setting_notifications_single_progress_subtitle": "Öğe başına ayrıntılı yükleme ilerleme bilgisi", - "setting_notifications_single_progress_title": "Arka plan yedeklemesi ayrıntılı ilerlemesini göster", - "setting_notifications_subtitle": "Bildirim tercihlerinizi düzenleyin", - "setting_notifications_total_progress_subtitle": "Toplam yükleme ilerlemesi (tamamlanan/toplam)", - "setting_notifications_total_progress_title": "Arka plan yedeklemesi toplam ilerlemesini göster", - "setting_video_viewer_auto_play_subtitle": "Videolar açıldığında otomatik olarak oynatmaya başla", - "setting_video_viewer_auto_play_title": "Videoları otomatik oynat", - "setting_video_viewer_looping_title": "Döngü", - "setting_video_viewer_original_video_subtitle": "Sunucudan video aktarılırken, transcode (dönüştürülmüş) sürüm mevcut olsa bile orijinal dosya oynatılır. Bu durum, arabelleğe alma (buffering) sorunlarına yol açabilir. Videolar yerel olarak mevcutsa, bu ayardan bağımsız olarak orijinal kalitede oynatılır.", - "setting_video_viewer_original_video_title": "Orijinal videoyu zorla", - "settings": "Ayarlar", - "settings_require_restart": "Bu ayarı uygulamak için lütfen Immich'i yeniden başlatın", - "settings_saved": "Ayarlar kaydedildi", - "setup_pin_code": "PIN kodunu ayarlayın", - "share": "Paylaş", - "share_action_prompt": "Paylaşılan {count} öğe", - "share_add_photos": "Fotoğraf ekle", - "share_assets_selected": "{count} seçili", - "share_dialog_preparing": "Hazırlanıyor...", - "share_link": "Bağlantıyı Paylaş", - "shared": "Paylaşılan", - "shared_album_activities_input_disable": "Yoruma kapalı", - "shared_album_activity_remove_content": "Bu etkinliği silmek istiyor musunuz?", - "shared_album_activity_remove_title": "Etkinlik Sil", - "shared_album_section_people_action_error": "Albümden ayrılırken/kaldırılırken hata oluştu", - "shared_album_section_people_action_leave": "Kullanıcıyı albümden kaldır", - "shared_album_section_people_action_remove_user": "Kullanıcıyı albümden kaldır", - "shared_album_section_people_title": "KİŞİLER", - "shared_by": "Tarafından paylaşılan", - "shared_by_user": "{user} tarafından paylaşıldı", - "shared_by_you": "Senin tarafından paylaşıldı", - "shared_from_partner": "{partner} tarafından paylaşılan fotoğraflar", - "shared_intent_upload_button_progress_text": "{current} / {total} Yüklendi", - "shared_link_app_bar_title": "Paylaşılan Bağlantılar", - "shared_link_clipboard_copied_massage": "Panoya kopyalandı", - "shared_link_clipboard_text": "Bağlantı: {link}\nŞifre: {password}", - "shared_link_create_error": "Paylaşım bağlantısı oluşturulurken hata oluştu", - "shared_link_custom_url_description": "Özel bir URL ile bu paylaşılan bağlantıya erişin", - "shared_link_edit_description_hint": "Açıklama yazın", - "shared_link_edit_expire_after_option_day": "1 gün", - "shared_link_edit_expire_after_option_days": "{count} gün", - "shared_link_edit_expire_after_option_hour": "1 saat", - "shared_link_edit_expire_after_option_hours": "{count} saat", - "shared_link_edit_expire_after_option_minute": "1 dakika", - "shared_link_edit_expire_after_option_minutes": "{count} dakika", - "shared_link_edit_expire_after_option_months": "{count} ay", - "shared_link_edit_expire_after_option_year": "{count} yıl", - "shared_link_edit_password_hint": "Paylaşım şifresini girin", - "shared_link_edit_submit_button": "Bağlantıyı güncelle", - "shared_link_error_server_url_fetch": "Sunucu URL'si alınamadı", - "shared_link_expires_day": "Süresi {count} gün içinde doluyor", - "shared_link_expires_days": "Süresi {count} gün içinde doluyor", - "shared_link_expires_hour": "Süresi {count} saat içinde doluyor", - "shared_link_expires_hours": "Süresi {count} saat içinde doluyor", - "shared_link_expires_minute": "Süresi {count} dakika içinde doluyor", - "shared_link_expires_minutes": "{count} dakika içinde süresi doluyor", - "shared_link_expires_never": "Süresiz", - "shared_link_expires_second": "Süresi {count} saniye içinde doluyor", - "shared_link_expires_seconds": "{count} sanyei içinde süresi doluyor", - "shared_link_individual_shared": "Bireysel paylaşımlı", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Paylaşılan Bağlantıları Yönet", - "shared_link_options": "Paylaşılan bağlantı seçenekleri", - "shared_link_password_description": "Bu paylaşılan bağlantıya erişmek için şifre gereklidir", - "shared_links": "Paylaşılan bağlantılar", - "shared_links_description": "Fotoğraf ve videoları bir bağlantı ile paylaş", - "shared_photos_and_videos_count": "{assetCount, plural, other {# paylaşılan fotoğraflar & videolar.}}", - "shared_with_me": "Benimle paylaşılanlar", - "shared_with_partner": "{partner} ile paylaşıldı", - "sharing": "Paylaşım", - "sharing_enter_password": "Bu sayfayı görebilmek için lütfen şifreyi giriniz.", - "sharing_page_album": "Paylaşılan albümler", - "sharing_page_description": "Ağınızdaki kişilerle fotoğraf ve video paylaşmak için paylaşımlı albümler oluşturun.", - "sharing_page_empty_list": "LİSTEYİ BOŞALT", - "sharing_sidebar_description": "Yan panelde Paylaşım bağlantısını göster", - "sharing_silver_appbar_create_shared_album": "Yeni paylaşılan albüm", - "sharing_silver_appbar_share_partner": "Ortakla paylaş", - "shift_to_permanent_delete": "Öğeyi kalıcı olarak silmek için ⇧ tuşuna basın", - "show_album_options": "Albüm ayarlarını göster", - "show_albums": "Albümleri göster", - "show_all_people": "Tüm kişileri göster", - "show_and_hide_people": "Kişileri göster ve gizle", - "show_file_location": "Dosya konumunu göster", - "show_gallery": "Galeriyi göster", - "show_hidden_people": "Gizli kişileri göster", - "show_in_timeline": "Zaman çizelgesinde göster", - "show_in_timeline_setting_description": "Bu kullanıcının fotoğraf ve videolarını zaman çizelgenizde gösterin", - "show_keyboard_shortcuts": "Klavye kısayollarını göster", - "show_metadata": "Meta verileri göster", - "show_or_hide_info": "Bilgileri göster veya gizle", - "show_password": "Şifreyi göster", - "show_person_options": "Kişi seçeneklerini göster", - "show_progress_bar": "İlerleme Çubuğunu Göster", - "show_schema": "Şemayı göster", - "show_search_options": "Arama seçeneklerini göster", - "show_shared_links": "Paylaşılan bağlantıları göster", - "show_slideshow_transition": "Slayt gösterisi geçişini göster", - "show_supporter_badge": "Destekçi rozeti", - "show_supporter_badge_description": "Destekçi rozetini göster", - "show_text_recognition": "Metin tanımayı göster", - "show_text_search_menu": "Metin arama menüsünü göster", - "shuffle": "Karıştır", - "sidebar": "Yan panel", - "sidebar_display_description": "Yan panelde görünüme bir bağlantı göster", - "sign_out": "Oturumu Kapat", - "sign_up": "Kaydol", - "size": "Boyut", - "skip_to_content": "İçeriğe atla", - "skip_to_folders": "Klasörlere atla", - "skip_to_tags": "Etiketlere atla", - "slideshow": "Slayt gösterisi", - "slideshow_repeat": "Slayt gösterisini tekrarla", - "slideshow_repeat_description": "Slayt gösterisi bittiğinde başa dön", - "slideshow_settings": "Slayt gösterisi ayarları", - "sort_albums_by": "Albümleri sırala...", - "sort_created": "Oluşturulma tarihi", - "sort_items": "Öğe sayısı", - "sort_modified": "Değişiklik tarihi", - "sort_newest": "En yeni fotoğraf", - "sort_oldest": "En eski fotoğraf", - "sort_people_by_similarity": "İnsanları benzerliğe göre sırala", - "sort_recent": "En yeni fotoğraf", - "sort_title": "Başlık", - "source": "Kaynak", - "stack": "Yığın", - "stack_action_prompt": "{count} istiflenmiş", - "stack_duplicates": "Çiftleri yığınla", - "stack_select_one_photo": "Yığın için ana fotoğrafı seç", - "stack_selected_photos": "Seçili fotoğrafları yığınla", - "stacked_assets_count": "{count, plural, one {# öğe} other {# öğeler}} yığınlandı", - "stacktrace": "Yığın izi", - "start": "Başlat", - "start_date": "Başlangıç tarihi", - "start_date_before_end_date": "Başlangıç tarihi bitiş tarihinden önce olmalıdır", - "state": "Eyalet/İl", - "status": "Durum", - "stop_casting": "Yansıtmayı durdur", - "stop_motion_photo": "Hareketli fotoğrafı durdur", - "stop_photo_sharing": "Fotoğraflarınızı paylaşmayı durdurmak mı istiyorsunuz?", - "stop_photo_sharing_description": "{partner} artık fotoğraflarınıza erişemeyecek.", - "stop_sharing_photos_with_user": "Bu kullanıcı ile fotoğraflarınızı paylaşmayı durdurun", - "storage": "Depolama alanı", - "storage_label": "Depolama yolu", - "storage_quota": "Depolama Kotası", - "storage_usage": "{used} / {available} kullanıldı", - "submit": "Gönder", - "success": "Başarılı", - "suggestions": "Öneriler", - "sunrise_on_the_beach": "Plajda gün doğumu", - "support": "Destek", - "support_and_feedback": "Destek & Geri Bildirim", - "support_third_party_description": "Immich kurulumu üçüncü bir tarafça yapıldı. Yaşadığınız sorunlar bu paketle ilgili olabilir. Lütfen öncelikli olarak aşağıdaki bağlantıları kullanarak bu sağlayıcıyla iletişime geçin.", - "swap_merge_direction": "Birleştirme yönünü değiştir", - "sync": "Eşzamanla", - "sync_albums": "Albümleri eşzamanla", - "sync_albums_manual_subtitle": "Yüklenmiş fotoğraf ve videoları yedekleme için seçili albümler ile eşzamanlayın", - "sync_local": "Yerel Eşzamanlama", - "sync_remote": "Uzaktan Eşzamanlama", - "sync_status": "Eşzamanlama Durumu", - "sync_status_subtitle": "Eşzamanlama sistemini görüntüleyin ve yönetin", - "sync_upload_album_setting_subtitle": "Fotoğraflarınızı ve videolarınızı oluşturun ve Immich'te seçtiğiniz albümlere yükleyin", - "tag": "Etiket", - "tag_assets": "Öğeleri etiketle", - "tag_created": "Etiket oluşturuldu: {tag}", - "tag_feature_description": "Etiket temalarına göre gruplandırılmış fotoğraf ve videoları keşfedin", - "tag_not_found_question": "Etiket bulunamadı mı? Yeni bir etiket oluşturun.", - "tag_people": "İnsanları etiketle", - "tag_updated": "Etiket güncellendi: {tag}", - "tagged_assets": "{count, plural, one {# öğe} other {# öğeler}} etiketlendi", - "tags": "Etiketler", - "tap_to_run_job": "Başlatmak için dokunun", - "template": "Şablon", - "text_recognition": "Metin tanıma", - "theme": "Tema", - "theme_selection": "Tema seçimi", - "theme_selection_description": "Temayı otomatik olarak tarayıcınızın sistem tercihine göre açık veya koyu ayarlayın", - "theme_setting_asset_list_storage_indicator_title": "Öğe kutucuklarında depolama göstergesini göster", - "theme_setting_asset_list_tiles_per_row_title": "Satır başına öğe sayısı ({count})", - "theme_setting_colorful_interface_subtitle": "Birincil rengi arka plan yüzeylerine uygulayın.", - "theme_setting_colorful_interface_title": "Renkli arayüz", - "theme_setting_image_viewer_quality_subtitle": "Ayrıntılı görüntüleyicinin kalitesini ayarla", - "theme_setting_image_viewer_quality_title": "Fotoğraf görüntüleyici kalite ayarı", - "theme_setting_primary_color_subtitle": "Birincil eylemler ve vurgular için bir renk seçin.", - "theme_setting_primary_color_title": "Ana renk", - "theme_setting_system_primary_color_title": "Sistem rengini kullan", - "theme_setting_system_theme_switch": "Otomatik (sistem ayarına göre)", - "theme_setting_theme_subtitle": "Uygulama teması seç", - "theme_setting_three_stage_loading_subtitle": "Üç aşamalı yükleme, yükleme performansını artırabilir ancak ağ yükünü önemli ölçüde artırır", - "theme_setting_three_stage_loading_title": "Üç aşamalı yüklemeyi etkinleştir", - "then": "Sonra", - "they_will_be_merged_together": "Birlikte birleştirilecekler", - "third_party_resources": "Üçüncü taraf kaynaklar", - "time": "Zaman", - "time_based_memories": "Zaman bazlı anılar", - "time_based_memories_duration": "Her görüntünün gösterileceği saniye sayısı.", - "timeline": "Zaman Çizelgesi", - "timezone": "Zaman dilimi", - "to_archive": "Arşivle", - "to_change_password": "Şifreyi değiştir", - "to_favorite": "Favorilere ekle", - "to_login": "Oturum aç", - "to_multi_select": "çoklu seçim için", - "to_parent": "Üst öğeye git", - "to_select": "seçmek için", - "to_trash": "Çöpe taşı", - "toggle_settings": "Ayarları değiştir", - "toggle_theme_description": "Temayı değiştir", - "total": "Toplam", - "total_usage": "Toplam kullanım", - "trash": "Çöp", - "trash_action_prompt": "{count} çöp kutusuna taşındı", - "trash_all": "Hepsini sil", - "trash_count": "Çöp kutusu {count, number}", - "trash_delete_asset": "Öğeyi Sil/Çöpe Gönder", - "trash_emptied": "Çöp kutusu temizlendi", - "trash_no_results_message": "Silinen fotoğraf ve videolar burada listelenecektir.", - "trash_page_delete_all": "Tümünü Sil", - "trash_page_empty_trash_dialog_content": "Çöp kutusuna atılmış öğeleri silmek istediğinize emin misiniz? Bu öğeler Immich'ten kalıcı olarak silinecek", - "trash_page_info": "Çöp kutusuna atılan öğeler {days} gün sonra kalıcı olarak silinecektir", - "trash_page_no_assets": "Çöp kutusuna atılmış öğe yok", - "trash_page_restore_all": "Tümünü Geri Yükle", - "trash_page_select_assets_btn": "Öğeleri seç", - "trash_page_title": "Çöp Kutusu ({count})", - "trashed_items_will_be_permanently_deleted_after": "Silinen öğeler {days, plural, one {# gün} other {# gün}} sonra kalıcı olarak silinecek.", - "trigger": "Tetikleyici", - "trigger_asset_uploaded": "Öğe Karşıya Yüklendi", - "trigger_asset_uploaded_description": "Yeni bir öğe karşıya yüklendiğinde tetiklenir", - "trigger_description": "İş akışını başlatan bir olay", - "trigger_person_recognized": "Tanınan Kişi", - "trigger_person_recognized_description": "Bir kişi algılandığında tetiklenir", - "trigger_type": "Tetikleyici türü", - "troubleshoot": "Sorun giderme", - "type": "Tür", - "unable_to_change_pin_code": "PIN kodu değiştirilemedi", - "unable_to_check_version": "Uygulama veya sunucu sürümü kontrol edilemiyor", - "unable_to_setup_pin_code": "PIN kodu ayarlanamadı", - "unarchive": "Arşivden çıkar", - "unarchive_action_prompt": "{count} Arşivden kaldırıldı", - "unarchived_count": "{count, plural, other {# arşivden çıkarıldı}}", - "undo": "Geri al", - "unfavorite": "Favorilerden kaldır", - "unfavorite_action_prompt": "{count} Favorilerden kaldırıldı", - "unhide_person": "Kişiyi göster", - "unknown": "Bilinmeyen", - "unknown_country": "Bilinmeyen Ülke", - "unknown_date": "Bilinmeyen tarih", - "unknown_year": "Bilinmeyen Yıl", - "unlimited": "Sınırsız", - "unlink_motion_video": "Hareketli video bağlantısını kaldır", - "unlink_oauth": "OAuth bağlantısını kaldır", - "unlinked_oauth_account": "Bağlantısı kaldırılmış OAuth hesabı", - "unmute_memories": "Anıların Sesini Aç", - "unnamed_album": "İsimsiz Albüm", - "unnamed_album_delete_confirmation": "Bu albümü silmek istediğinizden emin misiniz?", - "unnamed_share": "İsimsiz Paylaşım", - "unsaved_change": "Kaydedilmemiş değişiklik", - "unselect_all": "Tümünü seçimini kaldır", - "unselect_all_duplicates": "Tüm çiftlerin seçimini kaldır", - "unselect_all_in": "{group} içindeki tüm seçimleri kaldır", - "unstack": "Yığını kaldır", - "unstack_action_prompt": "{count} istiflenmemiş", - "unstacked_assets_count": "{count, plural, one {# öğenin} other {# öğelerin}} yığını kaldırıldı", - "unsupported_field_type": "Desteklenmeyen alan türü", - "untagged": "Etiketlenmemiş", - "untitled_workflow": "Başlıksız iş akışı", - "up_next": "Sıradaki", - "update_location_action_prompt": "Seçilen {count} öğenin konumunu şu şekilde güncelleyin:", - "updated_at": "Güncellenme", - "updated_password": "Güncellenen şifre", - "upload": "Yükle", - "upload_concurrency": "Yükleme eşzamanlılığı", - "upload_details": "Yükleme Ayrıntıları", - "upload_dialog_info": "Seçili öğeleri sunucuya yedeklemek istiyor musunuz?", - "upload_dialog_title": "Öğe Yükle", - "upload_errors": "{count, plural, one {# hata} other {# hatayla}} yükleme tamamlandı, yeni yüklenen öğeleri görmek için sayfayı güncelleyin.", - "upload_finished": "Yükleme tamamlandı", - "upload_progress": "{remaining, number} kalan - {processed, number}/{total, number} işlendi", - "upload_skipped_duplicates": "{count, plural, one {# yinelenen öğe} other {# yinelenen öğeler}} atlandı", - "upload_status_duplicates": "Çiftler", - "upload_status_errors": "Hatalar", - "upload_status_uploaded": "Yüklendi", - "upload_success": "Yükleme başarılı, yüklenen yeni öğeleri görebilmek için sayfayı yenileyin.", - "upload_to_immich": "Immich'e Yükle ({count})", - "uploading": "Yükleniyor", - "uploading_media": "Medya yükleme", - "url": "URL", - "usage": "Kullanım", - "use_biometric": "Biyometri kullan", - "use_current_connection": "Mevcut bağlantıyı kullan", - "use_custom_date_range": "Bunun yerine özel tarih aralığını kullan", - "user": "Kullanıcı", - "user_has_been_deleted": "Bu kullanıcı silindi.", - "user_id": "Kullanıcı ID", - "user_liked": "{type, select, photo {Bu fotoğraf} video {Bu video} asset {Bu öğe} other {Bu}} {user} tarafından beğenildi", - "user_pin_code_settings": "PIN Kodu", - "user_pin_code_settings_description": "PIN kodunuzu yönetin", - "user_privacy": "Kullanıcı Gizliliği", - "user_purchase_settings": "Satın Alma", - "user_purchase_settings_description": "Satın alma işlemlerini yönet", - "user_role_set": "{user}, {role} olarak ayarlandı", - "user_usage_detail": "Kullanıcı kullanım detayı", - "user_usage_stats": "Hesap kullanım istatistikleri", - "user_usage_stats_description": "Hesap kullanım istatistiklerini göster", - "username": "Kullanıcı adı", - "users": "Kullanıcılar", - "users_added_to_album_count": "Albüme {count, plural, one {# user} other {# users}} eklendi", - "utilities": "Yardımcı Programlar", - "validate": "Doğrula", - "validate_endpoint_error": "Lütfen geçerli bir URL girin", - "validation_error": "Doğrulama hatası", - "variables": "Değişkenler", - "version": "Sürüm", - "version_announcement_closing": "Arkadaşınız, Alex", - "version_announcement_message": "Merhaba! Immich'in yeni bir sürümü mevcut. Lütfen yapılandırmanızın güncel olduğundan emin olmak için sürüm notlarını okumak için biraz zaman ayırın, özellikle WatchTower veya Immich kurulumunuzu otomatik olarak güncelleyen bir mekanizma kullanıyorsanız yanlış yapılandırmaların önüne geçmek adına bu önemlidir.", - "version_history": "Sürüm Geçmişi", - "version_history_item": "{version}, {date} tarihinde kuruldu", - "video": "Video", - "video_hover_setting": "Üzerinde durulduğunda video ön izlemesi oynat", - "video_hover_setting_description": "Öğe üzerinde fareyle durulduğunda video küçük resmini oynatır. Bu özellik devre dışıyken, oynatma simgesine fareyle gidilerek oynatma başlatılabilir.", - "videos": "Videolar", - "videos_count": "{count, plural, one {# video} other {# video}}", - "videos_only": "Sadece videolar", - "view": "Görünüm", - "view_album": "Albümü görüntüle", - "view_all": "Tümünü gör", - "view_all_users": "Tüm kullanıcıları görüntüle", - "view_asset_owners": "Öğe sahiplerini görüntüle", - "view_details": "Ayrıntıları Görüntüle", - "view_in_timeline": "Zaman çizelgesinde görüntüle", - "view_link": "Bağlantıyı göster", - "view_links": "Bağlantıları göster", - "view_name": "Görünüm", - "view_next_asset": "Sonraki öğeyi görüntüle", - "view_previous_asset": "Önceki öğeyi görüntüle", - "view_qr_code": "QR kodu görüntüle", - "view_similar_photos": "Benzer fotoğrafları görüntüle", - "view_stack": "Yığını görüntüle", - "view_user": "Kullanıcıyı Görüntüle", - "viewer_remove_from_stack": "Yığından Kaldır", - "viewer_stack_use_as_main_asset": "Ana fotoğraf olarak kullan", - "viewer_unstack": "Yığını Kaldır", - "visibility_changed": "Görünürlük {count, plural, one {# kişi} other {# kişi}} için değiştirildi", - "visual": "Görsel", - "visual_builder": "Görsel oluşturucu", - "waiting": "Bekleniyor", - "waiting_count": "Bekleyen: {count}", - "warning": "Uyarı", - "week": "Hafta", - "welcome": "Hoş geldiniz", - "welcome_to_immich": "Immich'e hoş geldiniz", - "width": "Genişlik", - "wifi_name": "Wi-Fi Adı", - "workflow_delete_prompt": "Bu iş akışını silmek istediğinizden emin misiniz?", - "workflow_deleted": "İş akışı silindi", - "workflow_description": "İş akışı açıklaması", - "workflow_info": "İş akışı bilgileri", - "workflow_json": "İş akışı JSON", - "workflow_json_help": "İş akışı yapılandırmasını JSON biçiminde düzenleyin. Değişiklikler görsel oluşturucuyla eşitlenir.", - "workflow_name": "İş akışı adı", - "workflow_navigation_prompt": "Değişikliklerinizi kaydetmeden ayrılmak istediğinizden emin misiniz?", - "workflow_summary": "İş akışı özeti", - "workflow_update_success": "İş akışı başarıyla güncellendi", - "workflow_updated": "İş akışı güncellendi", - "workflows": "İş akışları", - "workflows_help_text": "İş akışları, tetikleyicilere ve filtrelere dayalı olarak öğelerinizdeki eylemleri otomatikleştirir", - "wrong_pin_code": "Yanlış PIN kodu", - "year": "Yıl", - "years_ago": "{years, plural, one {bir yıl} other {# yıl}} önce", - "yes": "Evet", - "you_dont_have_any_shared_links": "Herhangi bir paylaşılan bağlantınız yok", - "your_wifi_name": "Wi-Fi Adınız", - "zero_to_clear_rating": "Öğe derecelendirmesini temizlemek için 0'a basın", - "zoom_image": "Görüntüyü yakınlaştır", - "zoom_to_bounds": "Sınırlara yakınlaştır" -} +{} diff --git a/i18n/uk.json b/i18n/uk.json index 6834d22fc7..0967ef424b 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -1,2401 +1 @@ -{ - "about": "Про застосунок", - "account": "Обліковий запис", - "account_settings": "Налаштування облікового запису", - "acknowledge": "Прийняти", - "action": "Дія", - "action_common_update": "Оновити", - "action_description": "Набір дій, які потрібно виконати з відфільтрованими фото та відео", - "actions": "Дії", - "active": "Активний", - "active_count": "Активні: {count}", - "activity": "Активність", - "activity_changed": "Активність {enabled, select, true {увімкнено} other {вимкнено}}", - "add": "Додати", - "add_a_description": "Додати опис", - "add_a_location": "Додати місцезнаходження", - "add_a_name": "Додати ім'я", - "add_a_title": "Додати назву", - "add_action": "Додати дію", - "add_action_description": "Натисніть, щоб додати дію", - "add_assets": "Додати файли", - "add_birthday": "Додати день народження", - "add_endpoint": "Додати адресу серверу", - "add_exclusion_pattern": "Додати шаблон виключення", - "add_filter": "Додати фільтр", - "add_filter_description": "Натисніть, щоб додати умову фільтра", - "add_location": "Додати місцезнаходження", - "add_more_users": "Додати користувачів", - "add_partner": "Додати партнера", - "add_path": "Додати шлях", - "add_photos": "Додати фото", - "add_tag": "Додати тег", - "add_to": "Додати у…", - "add_to_album": "Додати до альбому", - "add_to_album_bottom_sheet_added": "Додано до {album}", - "add_to_album_bottom_sheet_already_exists": "Вже є в {album}", - "add_to_album_bottom_sheet_some_local_assets": "Деякі локальні файли не вдалося додати до альбому", - "add_to_album_toggle": "Перемикання вибору для {album}", - "add_to_albums": "Додати до альбомів", - "add_to_albums_count": "Додати до альбомів ({count})", - "add_to_bottom_bar": "Додати до", - "add_to_shared_album": "Додати до спільного альбому", - "add_upload_to_stack": "Додати вивантаження в стек", - "add_url": "Додати URL", - "add_workflow_step": "Додати крок робочого процесу", - "added_to_archive": "Додано до архіву", - "added_to_favorites": "Додано до обраного", - "added_to_favorites_count": "Додано {count, number} до обраного", - "admin": { - "add_exclusion_pattern_description": "Додати шаблони виключень. Підстановка з використанням *, ** та ? підтримується. Для ігнорування всіх файлів у будь-якому каталозі з ім'ям «Raw», використовуйте \"**/Raw/**\". Для ігнорування всіх файлів, що закінчуються на \".tif\", використовуйте \"**/*.tif\". Для ігнорування абсолютного шляху використовуйте \"/path/to/ignore/**\".", - "admin_user": "Адміністратор", - "asset_offline_description": "Цей файл зовнішньої бібліотеки не знайдено на диску і був переміщений до кошика. Якщо файл був переміщений у межах бібліотеки, перевірте свою стрічку на наявність нового відповідного файлу. Щоб відновити цей файл, переконайтеся, що шлях до файлу доступний для Immich, і проскануйте бібліотеку.", - "authentication_settings": "Налаштування аутентифікації", - "authentication_settings_description": "Керування паролями, OAuth та іншими налаштуваннями аутентифікації", - "authentication_settings_disable_all": "Ви впевнені, що хочете вимкнути всі методи входу? Вхід буде повністю вимкнений.", - "authentication_settings_reenable": "Для повторного ввімкнення використовуйте Команду сервера.", - "background_task_job": "Фонові Завдання", - "backup_database": "Створити дамп бази даних", - "backup_database_enable_description": "Увімкнути дампи бази даних", - "backup_keep_last_amount": "Кількість попередніх дампів, які зберігати", - "backup_onboarding_1_description": "віддалена копія у хмарі або в іншому фізичному місці.", - "backup_onboarding_2_description": "локальні копії на різних пристроях. Це включає оригінальні фото та відео і їх локальні резервні копії.", - "backup_onboarding_3_description": "загальні копії ваших даних, включаючи оригінальні фото та відео. Це включає 1 віддалену копію і 2 локальні копії.", - "backup_onboarding_description": "Рекомендовано дотримуватися стратегії резервного копіювання 3-2-1 для захисту ваших даних. Зберігайте копії вивантажених фото й відео, а також бази даних Immich, щоб забезпечити повноцінний захист та відновлення.", - "backup_onboarding_footer": "Докладніше про резервне копіювання Immich можна дізнатися з документації.", - "backup_onboarding_parts_title": "Резервне копіювання за стратегією 3-2-1 включає:", - "backup_onboarding_title": "Резервні копії", - "backup_settings": "Налаштування дампа бази даних", - "backup_settings_description": "Керувати налаштуваннями дампа бази даних.", - "cleared_jobs": "Очищені завдання для: {job}", - "config_set_by_file": "Налаштовано за допомогою конфіг-файлу", - "confirm_delete_library": "Ви дійсно бажаєте видалити бібліотеку \"{library}\"?", - "confirm_delete_library_assets": "Ви впевнені, що хочете видалити цю бібліотеку? Це безповоротно видалить {count, plural, one {# файл} few {# файли} other {# файлів}} з Immich. Файли залишаться на диску.", - "confirm_email_below": "Для підтвердження введіть \"{email}\" нижче", - "confirm_reprocess_all_faces": "Ви впевнені, що хочете повторно визначити всі обличчя? Це також призведе до видалення імен з усіх облич.", - "confirm_user_password_reset": "Ви впевнені, що хочете скинути пароль користувача {user}?", - "confirm_user_pin_code_reset": "Ви впевнені, що хочете скинути PIN-код {user}?", - "copy_config_to_clipboard_description": "Скопіювати поточну конфігурацію системи як об'єкт JSON у буфер обміну", - "create_job": "Створити завдання", - "cron_expression": "Cron вираз", - "cron_expression_description": "Встановіть інтервал сканування у форматі cron. Додаткова інформація: Crontab Guru", - "cron_expression_presets": "Попередні налаштування cron виразів", - "disable_login": "Вимкнути вхід", - "duplicate_detection_job_description": "Запустити машинне навчання для виявлення схожих зображень. Використовує інтелектуальний пошук", - "exclusion_pattern_description": "Шаблони виключень дозволяють ігнорувати файли та папки під час сканування вашої бібліотеки. Це корисно, якщо у вас є папки, які містять файли, які ви не хочете імпортувати, наприклад, RAW-файли.", - "export_config_as_json_description": "Завантажити поточну конфігурацію системи у форматі JSON", - "external_libraries_page_description": "Сторінка зовнішньої бібліотеки адміністратора", - "face_detection": "Виявлення обличчя", - "face_detection_description": "Виявлення облич на зображеннях за допомогою машинного навчання. Для відео обробляється лише мініатюра. \\\"Оновити\\\" повторно обробляє всі зображення. \\\"Скинути\\\" додатково очищає всі поточні дані про обличчя. \\\"Відсутні\\\" ставить у чергу зображення, які ще не були оброблені. Виявлені обличчя будуть поставлені в чергу для розпізнавання після завершення виявлення, групуючи їх у вже існуючих або нових людей.", - "facial_recognition_job_description": "Групування виявлених облич у людей. Цей крок виконується після завершення виявлення облич. \"Скинути\" повторно кластеризує всі обличчя. \"Відсутні\" ставить у чергу обличчя, яким ще не призначено людину.", - "failed_job_command": "Команда {command} не виконалася для завдання: {job}", - "force_delete_user_warning": "ПОПЕРЕДЖЕННЯ: Це негайно призведе до видалення користувача і всіх його файлів. Цю дію не можна скасувати, і файли не можна буде відновити.", - "image_format": "Формат", - "image_format_description": "Формат WebP виробляє менші файли, ніж JPEG, але його кодування вимагає більше часу.", - "image_fullsize_description": "Повнорозмірне зображення з видаленими метаданими, які використовуються під час збільшення", - "image_fullsize_enabled": "Увімкнути створення повнорозмірного зображення", - "image_fullsize_enabled_description": "Генерувати зображення повного розміру для форматів, не призначених для вебу. Якщо увімкнено \"Надавати перевагу вбудованому попередньому перегляду\", вбудовані попередні перегляди використовуються без конвертації. Не впливає на веб-дружні формати, такі як JPEG.", - "image_fullsize_quality_description": "Якість повнорозмірного зображення від 1 до 100. Чим вище значення, тим краще якість, але більше розмір файлу.", - "image_fullsize_title": "Налаштування повнорозмірного зображення", - "image_prefer_embedded_preview": "Надавати перевагу вбудованому попередньому перегляду", - "image_prefer_embedded_preview_setting_description": "Використовувати вбудовані попередні перегляди в RAW-фотографіях як вхідні дані для обробки зображень, якщо вони доступні. Це може забезпечити точніші кольори для деяких зображень, але якість попереднього перегляду залежить від камери і зображення може містити більше артефактів стиснення.", - "image_prefer_wide_gamut": "Віддавати перевагу широкій гамі", - "image_prefer_wide_gamut_setting_description": "Для мініатюр використовуйте дисплей P3. Це краще зберігає яскравість зображень з широким колірним простором, але на старих пристроях зі старою версією браузера зображення можуть виглядати інакше. sRGB-зображення зберігаються у форматі sRGB, щоб уникнути зсуву кольорів.", - "image_preview_description": "Зображення середнього розміру без метаданих, яке використовується при перегляді окремого зображення та для машинного навчання", - "image_preview_quality_description": "Якість попереднього перегляду від 1 до 100. Вища оцінка означає кращу якість, але створює більші файли та може зменшити швидкість роботи застосунку. Низьке значення може вплинути на якість машинного навчання.", - "image_preview_title": "Налаштування попереднього перегляду", - "image_progressive": "Прогресивний", - "image_progressive_description": "Кодуйте зображення JPEG поступово для поступового завантаження відображення. Це не впливає на зображення WebP.", - "image_quality": "Якість", - "image_resolution": "Роздільна здатність", - "image_resolution_description": "Вища роздільна здатність може зберігати більше деталей, але займає більше часу для кодування, має більші розміри файлів і може зменшити швидкість роботи застосунку.", - "image_settings": "Налаштування зображення", - "image_settings_description": "Керувати якістю та роздільною здатністю згенерованих зображень", - "image_thumbnail_description": "Маленька мініатюра із видаленими метаданими, що використовується для перегляду груп фотографій, наприклад, на основній лінії часу", - "image_thumbnail_quality_description": "Якість мініатюри від 1 до 100. Вища оцінка означає кращу якість, але створює більші файли та може зменшити швидкість роботи застосунку.", - "image_thumbnail_title": "Налаштування мініатюр", - "import_config_from_json_description": "Імпортуйте конфігурацію системи, вивантаживши файл конфігурації JSON", - "job_concurrency": "{job} одночасно", - "job_created": "Завдання створено", - "job_not_concurrency_safe": "Це завдання не є безпечним для одночасного виконання.", - "job_settings": "Налаштування завдань", - "job_settings_description": "Керування паралельністю завдань", - "jobs_delayed": "{jobCount, plural, other {# відкладено}}", - "jobs_failed": "{jobCount, plural, other {# не вдалося}}", - "jobs_over_time": "Завдання за часом", - "library_created": "Створена бібліотека: {library}", - "library_deleted": "Бібліотеку видалено", - "library_details": "Деталі бібліотеки", - "library_folder_description": "Вкажіть папку для імпорту. Ця папка, включаючи підпапки, буде просканована на наявність зображень та відео.", - "library_remove_exclusion_pattern_prompt": "Ви впевнені, що хочете видалити цей шаблон виключення?", - "library_remove_folder_prompt": "Ви впевнені, що хочете вилучити цю папку імпорту?", - "library_scanning": "Періодичне сканування", - "library_scanning_description": "Налаштувати періодичне сканування бібліотеки", - "library_scanning_enable_description": "Увімкнути періодичне сканування бібліотеки", - "library_settings": "Зовнішня бібліотека", - "library_settings_description": "Керування налаштуваннями зовнішніх бібліотек", - "library_tasks_description": "Сканувати зовнішні бібліотеки на наявність нових і/або змінених файлів", - "library_updated": "Оновлена бібліотека", - "library_watching_enable_description": "Відстежувати зміни файлів у зовнішніх бібліотеках", - "library_watching_settings": "Спостереження за бібліотекою [ЕКСПЕРИМЕНТАЛЬНЕ]", - "library_watching_settings_description": "Автоматичне спостереження за зміненими файлами", - "logging_enable_description": "Увімкнути ведення журналу", - "logging_level_description": "Коли увімкнено, який рівень журналювання використовувати.", - "logging_settings": "Журналювання", - "machine_learning_availability_checks": "Перевірки доступності", - "machine_learning_availability_checks_description": "Автоматично виявляти та надавати перевагу доступним серверам машинного навчання", - "machine_learning_availability_checks_enabled": "Увімкнути перевірки доступності", - "machine_learning_availability_checks_interval": "Інтервал перевірки", - "machine_learning_availability_checks_interval_description": "Інтервал у мілісекундах між перевірками доступності", - "machine_learning_availability_checks_timeout": "Тайм-аут запиту", - "machine_learning_availability_checks_timeout_description": "Тайм-аут у мілісекундах для перевірки доступності", - "machine_learning_clip_model": "Модель CLIP", - "machine_learning_clip_model_description": "Ім'я однієї з моделей CLIP, яка перерахована тут. Зауважте, що потрібно знову запустити завдання «Розумний пошук» для всіх зображень після зміни моделі.", - "machine_learning_duplicate_detection": "Виявлення дублікатів", - "machine_learning_duplicate_detection_enabled": "Увімкнути виявлення дублікатів", - "machine_learning_duplicate_detection_enabled_description": "Якщо вимкнено, абсолютно ідентичні файли все одно будуть видалені через дублювання.", - "machine_learning_duplicate_detection_setting_description": "Використовуйте вбудовування CLIP для пошуку ймовірних дублікатів", - "machine_learning_enabled": "Увімкнути машинне навчання", - "machine_learning_enabled_description": "Якщо вимкнено, всі функції машинного навчання будуть вимкнені незалежно від налаштувань нижче.", - "machine_learning_facial_recognition": "Розпізнавання облич", - "machine_learning_facial_recognition_description": "Виявляйте, розпізнавайте та групуйте обличчя на зображеннях", - "machine_learning_facial_recognition_model": "Модель розпізнавання облич", - "machine_learning_facial_recognition_model_description": "Моделі відсортовані за зменшенням розміру. Більші моделі працюють повільніше, потребують більше пам'яті, але дають кращі результати. Зверніть увагу, що потрібно знову запустити завдання виявлення обличчя для всіх зображень після зміни моделі.", - "machine_learning_facial_recognition_setting": "Увімкнути розпізнавання облич", - "machine_learning_facial_recognition_setting_description": "Якщо ця функція вимкнена, зображення не будуть кодуватися для розпізнавання облич і не будуть з'являтися в розділі \"Люди\" на сторінці \"Огляд\".", - "machine_learning_max_detection_distance": "Максимальна відстань виявлення", - "machine_learning_max_detection_distance_description": "Максимальна відстань між двома зображеннями, щоб вони вважалися дублікатами, варіюється від 0.001 до 0.1. Вищі значення дозволяють виявляти більше дублікатів, але можуть призводити до помилкових виявлень.", - "machine_learning_max_recognition_distance": "Максимальна відстань розпізнавання", - "machine_learning_max_recognition_distance_description": "Максимальна відстань між двома обличчями, щоб їх вважати однією і тією ж самою людиною, варіюється від 0 до 2. Зниження цього значення може запобігти помилковому визначенню двох різних людей як однієї особи, тоді як підвищення його може запобігти помилковому визначенню однієї і тієї ж самої людини як двох різних осіб. Зверніть увагу, що легше об'єднати двох людей, ніж розділити одну людину на дві, тому рекомендується нахилитися на бік меншого порогу, коли це можливо.", - "machine_learning_min_detection_score": "Мінімальний показник виявлення", - "machine_learning_min_detection_score_description": "Мінімальний рівень впевненості для виявлення обличчя від 0 до 1. Нижчі значення дозволять виявляти більше облич, але можуть призводити до помилкових виявлень.", - "machine_learning_min_recognized_faces": "Мінімум розпізнаних облич", - "machine_learning_min_recognized_faces_description": "Мінімальна кількість розпізнаних облич для створення особи. Збільшення цього параметру робить розпізнавання облич точнішим, але може збільшити ризик того, що обличчя не буде призначено особі.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Використовуйте машинне навчання для розпізнавання тексту на зображеннях", - "machine_learning_ocr_enabled": "Увімкнути OCR", - "machine_learning_ocr_enabled_description": "Якщо вимкнено, зображення не розпізнаватимуться за допомогою тексту.", - "machine_learning_ocr_max_resolution": "Максимальна роздільна здатність", - "machine_learning_ocr_max_resolution_description": "Розмір попереднього перегляду з роздільною здатністю вище цієї буде змінено зі збереженням співвідношення сторін. Вищі значення точніші, але обробляються довше та використовують більше пам’яті.", - "machine_learning_ocr_min_detection_score": "Мінімальний бал виявлення", - "machine_learning_ocr_min_detection_score_description": "Мінімальний бал достовірності для виявлення тексту становить від 0 до 1. Нижчі значення дозволять виявити більше тексту, але можуть призвести до хибнопозитивних результатів.", - "machine_learning_ocr_min_recognition_score": "Мінімальний бал розпізнавання", - "machine_learning_ocr_min_score_recognition_description": "Мінімальний бал достовірності для розпізнавання виявленого тексту становить від 0 до 1. Нижчі значення розпізнають більше тексту, але можуть призвести до хибнопозитивних результатів.", - "machine_learning_ocr_model": "Модель OCR", - "machine_learning_ocr_model_description": "Серверні моделі точніші за мобільні, але обробляють дані довше та використовують більше пам'яті.", - "machine_learning_settings": "Налаштування машинного навчання", - "machine_learning_settings_description": "Керування функціями та налаштуваннями машинного навчання", - "machine_learning_smart_search": "Розумний пошук", - "machine_learning_smart_search_description": "Пошук зображень за допомогою семантичних вбудовувань CLIP", - "machine_learning_smart_search_enabled": "Увімкнути розумний пошук", - "machine_learning_smart_search_enabled_description": "Якщо ця функція вимкнена, зображення не будуть кодуватися для розумного пошуку.", - "machine_learning_url_description": "URL сервера машинного навчання. Якщо надано більше одного URL, сервери будуть опитуватися по черзі, поки один з них не відповість успішно, у порядку від першого до останнього. Сервери, які не відповідають, будуть тимчасово ігноруватися, поки не стануть доступними.", - "maintenance_delete_backup": "Видалити резервну копію", - "maintenance_delete_backup_description": "Цей файл буде безповоротно видалено.", - "maintenance_delete_error": "Не вдалося видалити резервну копію.", - "maintenance_restore_backup": "Відновлення резервної копії", - "maintenance_restore_backup_description": "Immich буде стерто та відновлено з вибраної резервної копії. Перед продовженням буде створено резервну копію.", - "maintenance_restore_backup_different_version": "Цю резервну копію було створено за допомогою іншої версії Immich!", - "maintenance_restore_backup_unknown_version": "Не вдалося визначити версію резервної копії.", - "maintenance_restore_database_backup": "Відновлення резервної копії бази даних", - "maintenance_restore_database_backup_description": "Відкат до попереднього стану бази даних за допомогою файлу резервної копії", - "maintenance_settings": "Технічне обслуговування", - "maintenance_settings_description": "Переведення Immich у режим технічного обслуговування", - "maintenance_start": "Перехід у режим технічного обслуговування", - "maintenance_start_error": "Не вдалося запустити режим обслуговування.", - "maintenance_upload_backup": "Вивантажити файл резервної копії бази даних", - "maintenance_upload_backup_error": "Не вдалося вивантажити резервну копію, це файл .sql/.sql.gz?", - "manage_concurrency": "Керування паралельністю завдань", - "manage_concurrency_description": "Перехід до сторінки завдань для керування паралельністю", - "manage_log_settings": "Керування налаштуваннями журналу", - "map_dark_style": "Темний стиль", - "map_enable_description": "Увімкнути функції мапи", - "map_gps_settings": "Налаштування мапи та геолокації", - "map_gps_settings_description": "Керування налаштуваннями мапи та геолокації (зворотний геокодинг)", - "map_implications": "Функція мапи використовує зовнішній сервіс плиток (tiles.immich.cloud)", - "map_light_style": "Світлий стиль", - "map_manage_reverse_geocoding_settings": "Керувати налаштуваннями зворотного геокодування", - "map_reverse_geocoding": "Зворотне геокодування", - "map_reverse_geocoding_enable_description": "Увімкнути зворотне геокодування", - "map_reverse_geocoding_settings": "Налаштування зворотного геокодування", - "map_settings": "Мапа", - "map_settings_description": "Керування налаштуваннями мапи", - "map_style_description": "URL до теми мапи у форматі style.json", - "memory_cleanup_job": "Очищення спогадів", - "memory_generate_job": "Генерація спогадів", - "metadata_extraction_job": "Витягнути метадані", - "metadata_extraction_job_description": "Видобування метаданих: геодані, розпізнані обличчя та роздільна здатність", - "metadata_faces_import_setting": "Увімкнути імпорт облич", - "metadata_faces_import_setting_description": "Імпортувати обличчя з EXIF-даних зображень та sidecar-файлів", - "metadata_settings": "Налаштування метаданих", - "metadata_settings_description": "Керування налаштуваннями метаданих", - "migration_job": "Міграція", - "migration_job_description": "Перенесення мініатюр файлів та обличь до оновленої структури папок", - "nightly_tasks_cluster_faces_setting_description": "Запустити розпізнавання облич на щойно виявлених обличчях", - "nightly_tasks_cluster_new_faces_setting": "Групувати нові обличчя", - "nightly_tasks_database_cleanup_setting": "Завдання з очищення бази даних", - "nightly_tasks_database_cleanup_setting_description": "Видалення старих і прострочених даних із бази даних", - "nightly_tasks_generate_memories_setting": "Створити спогади", - "nightly_tasks_generate_memories_setting_description": "Автоматично створювати нові спогади з наявних фото та відео щоночі", - "nightly_tasks_missing_thumbnails_setting": "Створити відсутні мініатюри", - "nightly_tasks_missing_thumbnails_setting_description": "Черга для створення мініатюр для фото та відео без мініатюр", - "nightly_tasks_settings": "Налаштування нічних завдань", - "nightly_tasks_settings_description": "Керування нічними завданнями", - "nightly_tasks_start_time_setting": "Час початку", - "nightly_tasks_start_time_setting_description": "Час, коли сервер починає виконувати нічні завдання", - "nightly_tasks_sync_quota_usage_setting": "Синхронізувати використання квоти", - "nightly_tasks_sync_quota_usage_setting_description": "Оновити квоту сховища користувача на основі поточного використання", - "no_paths_added": "Шляхи не додано", - "no_pattern_added": "Шаблон не додано", - "note_apply_storage_label_previous_assets": "Примітка: Щоб застосувати мітку зберігання до раніше вивантажених файлів, запустити", - "note_cannot_be_changed_later": "ПРИМІТКА: Це не можна змінити пізніше!", - "notification_email_from_address": "Адреса надсилача", - "notification_email_from_address_description": "Адреса електронної пошти надсилача, наприклад: \"Immich Photo Server \". Переконайтеся, що використовуєте адресу, з якої вам дозволено надсилати листи.", - "notification_email_host_description": "Адреса поштового сервера (наприклад, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ігнорувати помилки сертифіката", - "notification_email_ignore_certificate_errors_description": "Ігнорувати помилки перевірки сертифікатів TLS (не рекомендується)", - "notification_email_password_description": "Пароль для аутентифікації на поштовому сервері", - "notification_email_port_description": "Порт поштового сервера (наприклад, 25, 465 або 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Використовувати SMTPS (SMTP через TLS)", - "notification_email_sent_test_email_button": "Надіслати тестовий лист і зберегти", - "notification_email_setting_description": "Налаштування для надсилання email-повідомлень", - "notification_email_test_email": "Надіслати тестовий лист", - "notification_email_test_email_failed": "Не вдалося надіслати тестовий лист. Перевірте ваші значення", - "notification_email_test_email_sent": "Тестовий лист було надіслано на {email}. Будь ласка, перевірте свою скриньку вхідних.", - "notification_email_username_description": "Ім'я користувача для автентифікації на поштовому сервері", - "notification_enable_email_notifications": "Увімкнути сповіщення електронною поштою", - "notification_settings": "Налаштування сповіщень", - "notification_settings_description": "Керування налаштуваннями сповіщень, включно із електронною поштою", - "oauth_auto_launch": "Автозапуск", - "oauth_auto_launch_description": "Автоматично запускати процес входу через OAuth при переході на сторінку входу", - "oauth_auto_register": "Автоматична реєстрація", - "oauth_auto_register_description": "Автоматично реєструвати нових користувачів після входу через OAuth", - "oauth_button_text": "Текст кнопки", - "oauth_client_secret_description": "Обов'язково для конфіденційного клієнта або якщо PKCE (ключ підтвердження для обміну кодом) не підтримується для публічного клієнта.", - "oauth_enable_description": "Увійти за допомогою OAuth", - "oauth_mobile_redirect_uri": "URI мобільного перенаправлення", - "oauth_mobile_redirect_uri_override": "Перевизначення URI мобільного перенаправлення", - "oauth_mobile_redirect_uri_override_description": "Увімкнути, якщо OAuth-провайдер не підтримує мобільний URI, як ''{callback}''", - "oauth_role_claim": "Атрибут ролі", - "oauth_role_claim_description": "Автоматично надавати права адміністратора на основі наявності цього атрибуту. Цей атрибут може містити значення ‘user’ або ‘admin’.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Керування налаштуваннями входу через OAuth", - "oauth_settings_more_details": "Для отримання додаткової інформації про цю функцію, зверніться до документації.", - "oauth_storage_label_claim": "Тег папки сховища", - "oauth_storage_label_claim_description": "Автоматично встановити мітку зберігання користувача на значення цієї вимоги.", - "oauth_storage_quota_claim": "Заявка на квоту на зберігання", - "oauth_storage_quota_claim_description": "Автоматично встановити квоту сховища користувача на значення цієї вимоги.", - "oauth_storage_quota_default": "Квота за замовчуванням (GiB)", - "oauth_storage_quota_default_description": "Квота в GiB, що використовується, коли налаштування не надано.", - "oauth_timeout": "Тайм-аут для запитів", - "oauth_timeout_description": "Максимальний час очікування відповіді в мілісекундах", - "ocr_job_description": "Використовуйте машинне навчання для розпізнавання тексту на зображеннях", - "password_enable_description": "Увійти за електронною поштою та паролем", - "password_settings": "Налаштування входу з паролем", - "password_settings_description": "Керування налаштуваннями входу за паролем", - "paths_validated_successfully": "Усі шляхи успішно перевірено", - "person_cleanup_job": "Очищення особи", - "queue_details": "Деталі черги", - "queues": "Черги завдань", - "queues_page_description": "Сторінка черг завдань адміністратора", - "quota_size_gib": "Розмір квоти (GiB)", - "refreshing_all_libraries": "Оновлення всіх бібліотек", - "registration": "Реєстрація адміністратора", - "registration_description": "Оскільки ви перший користувач в системі, ви будете призначені Адміністратором і відповідатимете за адміністративні завдання, а додаткові користувачі будуть створені вами.", - "remove_failed_jobs": "Вилучити невдалі завдання", - "require_password_change_on_login": "Вимагати зміни пароля користувача при першому вході", - "reset_settings_to_default": "Скинути налаштування до початкових значень", - "reset_settings_to_recent_saved": "Скинути налаштування до недавно збережених налаштувань", - "scanning_library": "Сканування бібліотеки", - "search_jobs": "Пошук завдань…", - "send_welcome_email": "Надіслати вітальний лист", - "server_external_domain_settings": "Зовнішній домен", - "server_external_domain_settings_description": "Домен для публічних загальнодоступних посилань, включаючи http(s)://", - "server_public_users": "Публічні користувачі", - "server_public_users_description": "Усі користувачі (ім'я та електронна пошта) відображаються під час додавання користувача до спільних альбомів. Якщо вимкнено, список користувачів буде доступний лише адміністраторам.", - "server_settings": "Налаштування сервера", - "server_settings_description": "Керування налаштуваннями сервера", - "server_stats_page_description": "Сторінка статистики сервера адміністратора", - "server_welcome_message": "Вітальне повідомлення", - "server_welcome_message_description": "Повідомлення, яке відображається на сторінці входу.", - "settings_page_description": "Сторінка налаштувань адміністратора", - "sidecar_job": "Метадані з sidecar-файлів", - "sidecar_job_description": "Пошук або синхронізація сайдкар-метаданих з файлової системи", - "slideshow_duration_description": "Кількість секунд для відображення кожного зображення", - "smart_search_job_description": "Розпізнає вміст файлів для розумного пошуку", - "storage_template_date_time_description": "Датою та часом є позначка часу створення файлу", - "storage_template_date_time_sample": "Час вибірки {date}", - "storage_template_enable_description": "Ввімкнути механізм шаблонів сховища", - "storage_template_hash_verification_enabled": "Увімкнено перевірку хешу", - "storage_template_hash_verification_enabled_description": "Увімкнути перевірку хеша. Не вимикайте це, якщо ви не впевнені в наслідках", - "storage_template_migration": "Міграція шаблонів сховища", - "storage_template_migration_description": "Застосувати поточний {template} до раніше вивантажених файлів", - "storage_template_migration_info": "Шаблон зберігання конвертуватиме всі розширення у нижній регістр. Зміни шаблону застосовуватимуться лише до нових файлів. Щоб застосувати шаблон до раніше вивантажених файлів, запустіть {job}.", - "storage_template_migration_job": "Завдання міграції шаблону зберігання", - "storage_template_more_details": "Для отримання детальнішої інформації про цю функцію, звертайтесь до Шаблону зберігання та його наслідків", - "storage_template_onboarding_description_v2": "Якщо цю функцію увімкнено, файли будуть автоматично впорядковуватися за шаблоном, визначеним користувачем. Докладніше дивіться в документації.", - "storage_template_path_length": "Приблизна максимальна довжина шляху: {length, number}/{limit, number}", - "storage_template_settings": "Шаблон сховища", - "storage_template_settings_description": "Керування структурою папок та іменами вивантажених файлів", - "storage_template_user_label": "{label} - це мітка зберігання користувача", - "system_settings": "Системні налаштування", - "tag_cleanup_job": "Очищення тегів", - "template_email_available_tags": "Ви можете використовувати наступні змінні у своєму шаблоні: {tags}", - "template_email_if_empty": "Якщо шаблон порожній, буде використано стандартний електронний лист.", - "template_email_invite_album": "Шаблон запрошення до альбому", - "template_email_preview": "Перегляд", - "template_email_settings": "Шаблони електронних листів", - "template_email_update_album": "Оновити шаблон альбому", - "template_email_welcome": "Шаблон вітального електронного листа", - "template_settings": "Шаблони повідомлень", - "template_settings_description": "Керувати шаблонами для повідомлень", - "theme_custom_css_settings": "Власний CSS", - "theme_custom_css_settings_description": "Каскадні таблиці стилів дозволяють настроювати дизайн Immich.", - "theme_settings": "Налаштування теми", - "theme_settings_description": "Налаштування персоналізації веб-інтерфейсу Immich", - "thumbnail_generation_job": "Створення мініатюр", - "thumbnail_generation_job_description": "Створити великі, малі та розмиті мініатюри для кожного фото та відео, а також мініатюри для кожної особи", - "transcoding_acceleration_api": "API прискорення", - "transcoding_acceleration_api_description": "API, яка буде взаємодіяти з вашим пристроєм для прискорення транскодування. Це налаштування працює у \"найкращих умовах\" і, в разі невдачі, перейде на програмне транскодування. Підтримка VP9 може або не може працювати, залежно від вашого обладнання.", - "transcoding_acceleration_nvenc": "NVENC (вимагає графічного процесора NVIDIA)", - "transcoding_acceleration_qsv": "Швидка синхронізація (потрібен процесор Intel 7-го покоління або новішої версії)", - "transcoding_acceleration_rkmpp": "RKMPP (тільки на SOC Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Прийняті аудіокодеки", - "transcoding_accepted_audio_codecs_description": "Виберіть аудіокодеки, які не потребують транскодування. Використовується лише для певних політик транскодування.", - "transcoding_accepted_containers": "Прийняті контейнери", - "transcoding_accepted_containers_description": "Виберіть, які формати контейнерів не потрібно перетворювати в MP4. Використовується лише для певних політик перекодування.", - "transcoding_accepted_video_codecs": "Прийняті відеокодеки", - "transcoding_accepted_video_codecs_description": "Виберіть відеокодеки, які не потребують транскодування. Використовується лише для певних політик транскодування.", - "transcoding_advanced_options_description": "Параметри, які більшості користувачів не потрібно змінювати", - "transcoding_audio_codec": "Аудіокодек", - "transcoding_audio_codec_description": "Opus - це опція найвищої якості, але менше сумісна зі старими пристроями або програмним забезпеченням.", - "transcoding_bitrate_description": "Відео з бітрейтом вище максимального або не в прийнятому форматі", - "transcoding_codecs_learn_more": "Для отримання додаткової інформації про термінологію, що використовується тут, звертайтеся до документації FFmpeg для кодеків H.264, HEVC та VP9.", - "transcoding_constant_quality_mode": "Режим постійної якості", - "transcoding_constant_quality_mode_description": "ICQ краще, ніж CQP, але деякі пристрої апаратного прискорення не підтримують цей режим. Встановлення цього параметра буде віддавати перевагу зазначеному режиму під час кодування на основі якості. Ігнорується NVENC, оскільки він не підтримує ICQ.", - "transcoding_constant_rate_factor": "Коефіцієнт постійної якості (-crf)", - "transcoding_constant_rate_factor_description": "Рівень якості відео. Зазвичай значення для H.264 - 23, HEVC - 28, VP9 - 31, AV1 - 35. Нижче значення краще, але створює більші файли.", - "transcoding_disabled_description": "Без транскодування відео — може призвести до проблем з відтворенням на деяких клієнтах", - "transcoding_encoding_options": "Параметри кодування", - "transcoding_encoding_options_description": "Налаштування кодеків, роздільної здатності, якості та інших параметрів для кодованих відео", - "transcoding_hardware_acceleration": "Апаратне прискорення", - "transcoding_hardware_acceleration_description": "Експериментально: швидше перекодування, але може знижувати якість при тому самому бітрейті", - "transcoding_hardware_decoding": "Апаратне декодування", - "transcoding_hardware_decoding_setting_description": "Увімкнення наскрізного прискорення замість прискорення лише кодування. Може не працювати для всіх відео.", - "transcoding_max_b_frames": "Максимальна кількість проміжних кадрів", - "transcoding_max_b_frames_description": "Вищі значення покращують ефективність стиснення, але збільшують час кодування. Можуть бути несумісні з апаратним прискоренням на старих пристроях. Значення 0 вимикає B-фрейми, а -1 автоматично налаштовує це значення.", - "transcoding_max_bitrate": "Максимальний бітрейт", - "transcoding_max_bitrate_description": "Встановлення максимальної швидкості передачі даних може зробити розміри файлів більш передбачуваними за незначної втрати якості. При роздільній здатності 720p типові значення становлять 2600 кбіт/с для VP9 або HEVC, або 4500 кбіт/с для H.264. Вимкнено, якщо встановлено значення 0. Якщо одиниця виміру не вказана, приймається k (для кбіт/с); отже, 5000, 5000k і 5M (для Мбіт/с) є еквівалентними.", - "transcoding_max_keyframe_interval": "Максимальний інтервал ключових кадрів", - "transcoding_max_keyframe_interval_description": "Встановлює максимальну відстань між ключовими кадрами. Нижчі значення погіршують ефективність стиснення, але покращують час пошуку і можуть покращити якість в сценах з швидкими рухами. Значення 0 автоматично встановлює це значення.", - "transcoding_optimal_description": "Відео з роздільною здатністю вище цільової або не в прийнятому форматі", - "transcoding_policy": "Політика транскодування", - "transcoding_policy_description": "Визначає, коли відео буде транскодовано", - "transcoding_preferred_hardware_device": "Переважний апаратний пристрій", - "transcoding_preferred_hardware_device_description": "Застосовується тільки до VAAPI і QSV. Встановлює вузол DRI, який використовується для апаратного транскодування.", - "transcoding_preset_preset": "Параметр (-preset)", - "transcoding_preset_preset_description": "Швидкість стиснення. Повільніші пресети створюють менші файли і підвищують якість при певному бітрейті. VP9 ігнорує швидкості вище 'швидше'.", - "transcoding_reference_frames": "Основні кадри", - "transcoding_reference_frames_description": "Кількість кадрів, на які посилається при стисненні даного кадру. Вищі значення покращують ефективність стиснення, але збільшують час кодування. Значення 0 автоматично налаштовує це значення.", - "transcoding_required_description": "Лише відео, що не у прийнятому форматі", - "transcoding_settings": "Налаштування транскодування відео", - "transcoding_settings_description": "Керування які відео транскодувати і як їх обробляти", - "transcoding_target_resolution": "Роздільна здатність", - "transcoding_target_resolution_description": "Вищі роздільні здатності можуть зберігати більше деталей, але займають більше часу на кодування, мають більші розміри файлів і можуть зменшити швидкість роботи застосунку.", - "transcoding_temporal_aq": "Тимчасове AQ", - "transcoding_temporal_aq_description": "Стосується лише NVENC. Часова адаптивна квантизація підвищує якість сцен з високою деталізацією та низьким рівнем руху. Може бути несумісним зі старими пристроями.", - "transcoding_threads": "Потоки", - "transcoding_threads_description": "Вищі значення прискорюють кодування, але залишають менше місця для обробки інших завдань сервером під час активності. Це значення не повинно бути більше кількості ядер процесора. Максимізує використання, якщо встановлено на 0.", - "transcoding_tone_mapping": "Тонове відображення", - "transcoding_tone_mapping_description": "Намагається зберегти вигляд HDR-відео при конвертації в SDR. Кожен алгоритм робить різні компроміси щодо кольору, деталізації та яскравості. Алгоритм Hable зберігає деталі, Mobius - кольори, Reinhard - яскравість.", - "transcoding_transcode_policy": "Політика перекодування", - "transcoding_transcode_policy_description": "Політика транскодування для відео. HDR відео завжди буде транскодуватись (крім випадків, коли транскодування вимкнено).", - "transcoding_two_pass_encoding": "Кодування з двома проходами", - "transcoding_two_pass_encoding_setting_description": "Транскодування за двома проходами для отримання кращих закодованих відео. Коли ввімкнено максимальний бітрейт (необхідний для роботи з H.264 та HEVC), цей режим використовує діапазон бітрейту, заснований на максимальному бітрейті, і ігнорує CRF. Для VP9 можна використовувати CRF, якщо вимкнено максимальний бітрейт.", - "transcoding_video_codec": "Відеокодек", - "transcoding_video_codec_description": "VP9 має високу ефективність і сумісність з вебом, але потребує більше часу на транскодування. HEVC працює схоже, але має меншу сумісність з вебом. H.264 має широку сумісність і швидко транскодується, але створює значно більші файли. AV1 - найефективніший кодек, але не підтримується на старіших пристроях.", - "trash_enabled_description": "Увімкнення кошика", - "trash_number_of_days": "Кількість днів", - "trash_number_of_days_description": "Кількість днів, протягом яких залишати файли у кошику перед їх остаточним видаленням", - "trash_settings": "Налаштування кошика", - "trash_settings_description": "Керування налаштуваннями кошика", - "unlink_all_oauth_accounts": "Від’єднати всі облікові записи OAuth", - "unlink_all_oauth_accounts_description": "Не забудьте від’єднати всі облікові записи OAuth перед переходом до нового постачальника.", - "unlink_all_oauth_accounts_prompt": "Ви впевнені, що хочете від’єднати всі облікові записи OAuth? Це скине ідентифікатор OAuth для кожного користувача, і цю дію не можна буде скасувати.", - "user_cleanup_job": "Очищення користувача", - "user_delete_delay": "Обліковий запис {user} і його файли будуть заплановані для остаточного видалення через {delay, plural, one {# день} few {# дні} many {# днів} other {# днів}}.", - "user_delete_delay_settings": "Відкладене видалення", - "user_delete_delay_settings_description": "Період відтермінування остаточного видалення облікового запису користувача та його файлів. Завдання з видалення користувача запускається щоночі о півночі і перевіряє облікові записи, призначені для видалення. Зміни цього параметра будуть враховані під час наступного запуску завдання.", - "user_delete_immediately": "Обліковий запис та файли користувача {user} будуть негайно поставлені в чергу на остаточне видалення.", - "user_delete_immediately_checkbox": "Поставити користувача та файли в чергу для негайного видалення", - "user_details": "Дані користувача", - "user_management": "Керування користувачами", - "user_password_has_been_reset": "Пароль користувача було скинуто:", - "user_password_reset_description": "Будь ласка, надайте користувачеві тимчасовий пароль і повідомте йому, що він повинен буде змінити пароль при наступному вході.", - "user_restore_description": "Обліковий запис {user} буде відновлено.", - "user_restore_scheduled_removal": "Відновити користувача - заплановано на видалення {date, date, long}", - "user_settings": "Налаштування користувача", - "user_settings_description": "Керування налаштуваннями користувачів", - "user_successfully_removed": "Користувача {email} успішно видалено.", - "users_page_description": "Сторінка адміністраторів", - "version_check_enabled_description": "Увімкнути перевірку версії", - "version_check_implications": "Функція перевірки версії залежить від періодичної комунікації з github.com", - "version_check_settings": "Перевірка версії", - "version_check_settings_description": "Увімкнути/вимкнути сповіщення про нову версію", - "video_conversion_job": "Перекодувати відео", - "video_conversion_job_description": "Транскодувати відео для ширшої сумісності з браузерами та пристроями" - }, - "admin_email": "Електронна пошта адміністратора", - "admin_password": "Пароль адміністратора", - "administration": "Адміністрування", - "advanced": "Розширені", - "advanced_settings_clear_image_cache": "Очистити кеш зображень", - "advanced_settings_clear_image_cache_error": "Не вдалося очистити кеш зображень", - "advanced_settings_clear_image_cache_success": "Успішно очищено {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Використовуйте цей варіант для фільтрації файлів під час синхронізації за альтернативними критеріями. Спробуйте це, якщо у вас виникають проблеми з тим, що застосунок не виявляє всі альбоми.", - "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНТАЛЬНИЙ] Використовуйте альтернативний фільтр синхронізації альбомів пристрою", - "advanced_settings_log_level_title": "Рівень журналювання: {level}", - "advanced_settings_prefer_remote_subtitle": "Деякі пристрої вельми повільно завантажують мініатюри із файлів на пристрої. Увімкніть цей параметр, щоб завантажувати зображення з серверу.", - "advanced_settings_prefer_remote_title": "Перевага віддаленим зображенням", - "advanced_settings_proxy_headers_subtitle": "Визначте заголовки проксі-сервера, які Immich має надсилати з кожним мережевим запитом", - "advanced_settings_proxy_headers_title": "Користувацькі проксі-заголовки [ЕКСПЕРИМЕНТАЛЬНА ВЕРСІЯ]", - "advanced_settings_readonly_mode_subtitle": "Увімкнення режиму тільки для читання, в якому фотографії можна тільки переглядати, а такі функції, як вибір декількох зображень, спільний доступ, передача, видалення, вимкнені. Увімкнення/вимкнення режиму тільки для читання за допомогою аватара користувача на головному екрані", - "advanced_settings_readonly_mode_title": "Режим лише для читання", - "advanced_settings_self_signed_ssl_subtitle": "Пропускає перевірку SSL-сертифіката сервера. Потрібне для самопідписаних сертифікатів.", - "advanced_settings_self_signed_ssl_title": "Дозволити самопідписані SSL-сертифікати [ЕКСПЕРИМЕНТАЛЬНА ВЕРСІЯ]", - "advanced_settings_sync_remote_deletions_subtitle": "Автоматично видаляти або відновлювати файл на цьому пристрої, коли ця дія виконується в веб-інтерфейсі", - "advanced_settings_sync_remote_deletions_title": "Синхронізація віддалених видалень [ЕКСПЕРИМЕНТАЛЬНО]", - "advanced_settings_tile_subtitle": "Розширені користувацькі налаштування", - "advanced_settings_troubleshooting_subtitle": "Увімкніть додаткові функції для усунення несправностей", - "advanced_settings_troubleshooting_title": "Усунення несправностей", - "age_months": "Вік {months, plural, one {# місяць} few {# місяці} many {# місяців} other {# місяців}}", - "age_year_months": "Вік 1 рік, {months, plural, one {# місяць} few {# місяці} many {# місяців} other {# місяців}}", - "age_years": "{years, plural, other {Вік #}}", - "album": "Альбом", - "album_added": "Альбом додано", - "album_added_notification_setting_description": "Отримувати сповіщення електронною поштою, коли вас додають до спільного альбому", - "album_cover_updated": "Обкладинка альбому оновлена", - "album_delete_confirmation": "Ви впевнені, що хочете видалити альбом {album}?", - "album_delete_confirmation_description": "Якщо альбом був спільним, інші користувачі не зможуть отримати доступ до нього.", - "album_deleted": "Альбом видалено", - "album_info_card_backup_album_excluded": "ВИЛУЧЕНИЙ", - "album_info_card_backup_album_included": "ВКЛЮЧЕНИЙ", - "album_info_updated": "Інформація про альбом оновлена", - "album_leave": "Залишити альбом?", - "album_leave_confirmation": "Ви впевнені, що хочете залишити альбом {album}?", - "album_name": "Назва Альбому", - "album_options": "Параметри альбому", - "album_remove_user": "Видалити користувача?", - "album_remove_user_confirmation": "Ви впевнені, що хочете видалити {user}?", - "album_search_not_found": "Альбомів, що відповідають вашому запиту, не знайдено", - "album_selected": "Альбом вибрано", - "album_share_no_users": "Схоже, ви поділилися цим альбомом з усіма користувачами або у вас немає жодного користувача, з яким можна було б поділитися.", - "album_summary": "Короткий опис альбому", - "album_updated": "Альбом оновлено", - "album_updated_setting_description": "Отримуйте сповіщення на електронну пошту, коли у спільному альбомі з'являються нові фото та відео", - "album_upload_assets": "Вивантажте фото та відео зі свого комп'ютера та додайте їх до альбому", - "album_user_left": "Ви покинули {album}", - "album_user_removed": "Користувач {user} видалений", - "album_viewer_appbar_delete_confirm": "Ви впевнені, що хочете видалити цей альбом зі свого облікового запису?", - "album_viewer_appbar_share_err_delete": "Не вдалося видалити альбом", - "album_viewer_appbar_share_err_leave": "Не вдалося вийти з альбому", - "album_viewer_appbar_share_err_remove": "Виникли проблеми з видаленням файлів з альбому", - "album_viewer_appbar_share_err_title": "Не вдалося змінити назву альбому", - "album_viewer_appbar_share_leave": "Вийти з альбому", - "album_viewer_appbar_share_to": "Поділитися", - "album_viewer_page_share_add_users": "Додати користувачів", - "album_with_link_access": "Будь-хто з посиланням може переглядати фото та відео в цьому альбомі.", - "albums": "Альбоми", - "albums_count": "{count, plural, one {1 альбом} few {{count, number} альбоми} many {{count, number} альбомів} other {{count, number} альбомів}}", - "albums_default_sort_order": "Порядок сортування альбомів за замовчуваням", - "albums_default_sort_order_description": "Початковий порядок сортування файлів під час створення нових альбомів.", - "albums_feature_description": "Колекції файлів, які можна спільно використовувати з іншими користувачами.", - "albums_on_device_count": "Альбоми на пристрої ({count})", - "albums_selected": "{count, plural, one {# альбом вибрано} few {# альбоми вибрано} many {# альбомів вибрано} other {# альбомів вибрано}}", - "all": "Усі", - "all_albums": "Усі альбоми", - "all_people": "Усі люди", - "all_photos": "Усі фотографії", - "all_videos": "Усі відео", - "allow_dark_mode": "Дозволити темний режим", - "allow_edits": "Дозволити редагування", - "allow_public_user_to_download": "Дозволити публічному користувачеві завантажувати файли", - "allow_public_user_to_upload": "Дозволити публічним користувачам вивантажувати", - "allowed": "Дозволено", - "alt_text_qr_code": "Зображення QR-коду", - "always_keep": "Завжди зберігати", - "always_keep_photos_hint": "Функція «Звільнити місце» збереже всі фотографії на цьому пристрої.", - "always_keep_videos_hint": "Функція «Звільнити місце» збереже всі відео на цьому пристрої.", - "anti_clockwise": "Проти годинникової стрілки", - "api_key": "Ключ API", - "api_key_description": "Це значення буде показане лише один раз. Будь ласка, обов'язково скопіюйте його перед закриттям вікна.", - "api_key_empty": "Назва вашого ключа API не може бути порожньою", - "api_keys": "Ключі API", - "app_architecture_variant": "Варіант (Архітектура)", - "app_bar_signout_dialog_content": "Ви впевнені, що хочете вийти з облікового запису?", - "app_bar_signout_dialog_ok": "Так", - "app_bar_signout_dialog_title": "Вийти", - "app_download_links": "Посилання для завантаження застосунків", - "app_settings": "Налаштування застосунку", - "app_stores": "Магазини застосунків", - "app_update_available": "Оновлення застосунку доступне", - "appears_in": "З'являється в", - "apply_count": "Застосувати ({count, number})", - "archive": "Архівувати", - "archive_action_prompt": "{count, plural, one {# файл додано до архіву} few {# файли додано до архіву} other {# файлів додано до архіву}}", - "archive_or_unarchive_photo": "Архівувати або розархівувати фото", - "archive_page_no_archived_assets": "Немає архівних файлів", - "archive_page_title": "Архів ({count})", - "archive_size": "Розмір архіву", - "archive_size_description": "Налаштувати розмір архіву для завантаження (у GiB)", - "archived": "Архів", - "archived_count": "{count, plural, other {Архівовано #}}", - "are_these_the_same_person": "Це та сама людина?", - "are_you_sure_to_do_this": "Ви впевнені, що хочете це зробити?", - "array_field_not_fully_supported": "Поля масиву потребують ручного редагування JSON", - "asset_action_delete_err_read_only": "Неможливо видалити файл(и) лише для читання, пропускаю", - "asset_action_share_err_offline": "Неможливо опрацювати недоступні файл(и), пропускаю", - "asset_added_to_album": "Додано до альбому", - "asset_adding_to_album": "Додати до альбому…", - "asset_created": "Файл додано", - "asset_description_updated": "Оновлено опис файлу", - "asset_filename_is_offline": "Файл {filename} недоступний", - "asset_has_unassigned_faces": "Є нерозпізнані обличчя", - "asset_hashing": "Хешування…", - "asset_list_group_by_sub_title": "Групувати за", - "asset_list_layout_settings_dynamic_layout_title": "Динамічне компонування", - "asset_list_layout_settings_group_automatically": "Автоматично", - "asset_list_layout_settings_group_by": "Групувати файли по", - "asset_list_layout_settings_group_by_month_day": "Місяць + день", - "asset_list_layout_sub_title": "Розмітка", - "asset_list_settings_subtitle": "Налаштування вигляду сітки фото", - "asset_list_settings_title": "Фото-сітка", - "asset_not_found_on_device_android": "Файл не знайдено на пристрої", - "asset_not_found_on_device_ios": "Файл не знайдено на пристрої. Якщо ви використовуєте iCloud, файл може бути недоступним через пошкоджений файл, що зберігається в iCloud", - "asset_not_found_on_icloud": "Файл не знайдено в iCloud. Можливо, файл недоступний через пошкоджений файл, що зберігається в iCloud", - "asset_offline": "Файл недоступний", - "asset_offline_description": "Цей файл не знайдено на диску. Будь ласка, зверніться до адміністратора Immich за допомогою.", - "asset_restored_successfully": "Файл успішно відновлено", - "asset_skipped": "Пропущено", - "asset_skipped_in_trash": "У кошику", - "asset_trashed": "Файл видалено", - "asset_troubleshoot": "Вирішення проблем з файлами", - "asset_uploaded": "Вивантажено", - "asset_uploading": "Вивантаження…", - "asset_viewer_settings_subtitle": "Налаштування переглядача галереї", - "asset_viewer_settings_title": "Переглядач зображень", - "assets": "файли", - "assets_added_count": "Додано {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_added_to_album_count": "Додано {count, plural, one {# файл} few {# файли} other {# файлів}} до альбому", - "assets_added_to_albums_count": "Додано {assetTotal, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} до {albumTotal, plural, one {# альбому} few {# альбомів} many {# альбомів} other {# альбомів}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Файл} few {Файли} many {Файли} other {Файли}} не можна додати до альбому", - "assets_cannot_be_added_to_albums": "{count, plural, one {Файл} few {Файли} many {Файлів} other {Файлів}} не можна додати до жодного з альбомів", - "assets_count": "{count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_deleted_permanently": "Остаточно видалено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_deleted_permanently_from_server": "Видалено назавжди {count, plural, one {# файл} few {# файли} other {# файлів}} з сервера Immich", - "assets_downloaded_failed": "{count, plural, one {Завантажено # файл — {error} не вдалося} few {Завантажено # файли — {error} не вдалося} many {Завантажено # файлів — {error} не вдалося} other {Завантажено # файлів — {error} не вдалося}}", - "assets_downloaded_successfully": "{count, plural, one {Успішно завантажено # файл} few {Успішно завантажено # файли} many {Успішно завантажено # файлів} other {Успішно завантажено # файлів}}", - "assets_moved_to_trash_count": "Переміщено {count, plural, one {# файл} few {# файли} other {# файлів}} до кошика", - "assets_permanently_deleted_count": "Остаточно видалено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_removed_count": "Вилучено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_removed_permanently_from_device": "Назавжди вилучено з вашого пристрою {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_restore_confirmation": "Ви впевнені, що хочете відновити всі свої файли з кошика? Цю дію не можна скасувати! Зверніть увагу, що недоступні файли не можуть бути відновлені таким чином.", - "assets_restored_count": "Відновлено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_restored_successfully": "Успішно відновлено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_trashed": "Переміщено до кошика {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_trashed_count": "Переміщено до кошика {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_trashed_from_server": "Переміщено до кошика на сервері Immich {count, plural, one {# файл} few {# файли} other {# файлів}}", - "assets_were_part_of_album_count": "{count, plural, one {Файл був} few {Файли були} other {Файли були}} вже частиною альбому", - "assets_were_part_of_albums_count": "{count, plural, one {Файл вже був} few {Файли вже були} many {Файлів вже були} other {Файлів вже були}} частиною альбомів", - "authorized_devices": "Авторизовані пристрої", - "automatic_endpoint_switching_subtitle": "Підключатися локально через зазначену Wi-Fi мережу, коли це можливо, і використовувати альтернативні з'єднання в інших випадках", - "automatic_endpoint_switching_title": "Автоматичне перемикання URL", - "autoplay_slideshow": "Автоматичне відтворення слайдшоу", - "back": "Назад", - "back_close_deselect": "Повернутися, закрити або скасувати вибір", - "background_backup_running_error": "Наразі виконується фонове резервне копіювання, неможливо розпочати резервне копіювання вручну", - "background_location_permission": "Дозвіл до місцезнаходження у фоні", - "background_location_permission_content": "Щоб перемикати мережі у фоновому режимі, Immich має *завжди* мати доступ до точної геолокації, щоб зчитувати назву Wi-Fi мережі", - "background_options": "Параметри фону", - "backup": "Резервне копіювання", - "backup_album_selection_page_albums_device": "Альбоми на пристрої ({count})", - "backup_album_selection_page_albums_tap": "Торкніться, щоб додати, двічі, щоб вилучити", - "backup_album_selection_page_assets_scatter": "Файли можуть належати до кількох альбомів водночас. Таким чином, альбоми можуть бути додані або вилучені під час резервного копіювання.", - "backup_album_selection_page_select_albums": "Оберіть альбоми", - "backup_album_selection_page_selection_info": "Інформація про обране", - "backup_album_selection_page_total_assets": "Загальна кількість унікальних файлів", - "backup_albums_sync": "Синхронізація резервних копій альбомів", - "backup_all": "Усі", - "backup_background_service_backup_failed_message": "Не вдалося зробити резервну копію файлів. Повторюю…", - "backup_background_service_complete_notification": "Резервне копіювання файлів завершено", - "backup_background_service_connection_failed_message": "Не вдалося зв'язатися із сервером. Повторюю…", - "backup_background_service_current_upload_notification": "Вивантажується {filename}", - "backup_background_service_default_notification": "Перевіряю наявність нових файлів…", - "backup_background_service_error_title": "Помилка резервного копіювання", - "backup_background_service_in_progress_notification": "Резервне копіювання ваших файлів…", - "backup_background_service_upload_failure_notification": "Не вдалося вивантажити {filename}", - "backup_controller_page_albums": "Резервне копіювання альбомів", - "backup_controller_page_background_app_refresh_disabled_content": "Для фонового резервного копіювання увімкніть фонове оновлення в меню \"Налаштування > Загальні > Фонове оновлення застосунку\".", - "backup_controller_page_background_app_refresh_disabled_title": "Фонове оновлення застосунку вимкнене", - "backup_controller_page_background_app_refresh_enable_button_text": "Перейти до налаштувань", - "backup_controller_page_background_battery_info_link": "Показати як", - "backup_controller_page_background_battery_info_message": "Для найкращого фонового резервного копіювання вимкніть будь-яку оптимізацію акумулятора, яка обмежує фонову активність для Immich.\n\nСпосіб залежить від конкретного пристрою, тому шукайте необхідну інформацію у виробника вашого пристрою.", - "backup_controller_page_background_battery_info_ok": "ОК", - "backup_controller_page_background_battery_info_title": "Оптимізація батареї", - "backup_controller_page_background_charging": "Лише під час заряджання", - "backup_controller_page_background_configure_error": "Не вдалося налаштувати фоновий сервіс", - "backup_controller_page_background_delay": "Затримка резервного копіювання нових файлів: {duration}", - "backup_controller_page_background_description": "Увімкніть фонову службу, щоб автоматично створювати резервні копії будь-яких нових файлів без необхідності відкривати застосунок", - "backup_controller_page_background_is_off": "Автоматичне фонове резервне копіювання вимкнено", - "backup_controller_page_background_is_on": "Автоматичне фонове резервне копіювання ввімкнено", - "backup_controller_page_background_turn_off": "Вимкнути фоновий сервіс", - "backup_controller_page_background_turn_on": "Увімкнути фоновий сервіс", - "backup_controller_page_background_wifi": "Лише на Wi-Fi", - "backup_controller_page_backup": "Резервне копіювання", - "backup_controller_page_backup_selected": "Обрано: ", - "backup_controller_page_backup_sub": "Резервні копії фото та відео", - "backup_controller_page_created": "Створено: {date}", - "backup_controller_page_desc_backup": "Увімкніть резервне копіювання на передньому плані, щоб автоматично вивантажувати нові фото та відео на сервер під час відкриття застосунку.", - "backup_controller_page_excluded": "Вилучено: ", - "backup_controller_page_failed": "Невдалі ({count})", - "backup_controller_page_filename": "Назва файлу: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Інформація про резервну копію", - "backup_controller_page_none_selected": "Нічого не обрано", - "backup_controller_page_remainder": "Залишок", - "backup_controller_page_remainder_sub": "Фото та відео, що залишилися для резервного копіювання з вибраного", - "backup_controller_page_server_storage": "Сховище сервера", - "backup_controller_page_start_backup": "Почати резервне копіювання", - "backup_controller_page_status_off": "Автоматичне резервне копіювання в активному режимі вимкнено", - "backup_controller_page_status_on": "Автоматичне резервне копіювання в активному режимі ввімкнено", - "backup_controller_page_storage_format": "Використано: {used} з {total}", - "backup_controller_page_to_backup": "Альбоми до резервного копіювання", - "backup_controller_page_total_sub": "Усі унікальні фото та відео з вибраних альбомів", - "backup_controller_page_turn_off": "Вимкнути резервне копіювання в активному режимі", - "backup_controller_page_turn_on": "Увімкнути резервне копіювання в активному режимі", - "backup_controller_page_uploading_file_info": "Вивантажую інформацію про файл", - "backup_err_only_album": "Не можу видалити єдиний альбом", - "backup_error_sync_failed": "Помилка синхронізації. Не вдається обробити резервну копію.", - "backup_info_card_assets": "файли", - "backup_manual_cancelled": "Скасовано", - "backup_manual_in_progress": "Вивантаження вже відбувається. Спробуйте згодом", - "backup_manual_success": "Успіх", - "backup_manual_title": "Стан вивантаження", - "backup_options": "Налаштування резервного копіювання", - "backup_options_page_title": "Резервне копіювання", - "backup_setting_subtitle": "Керування налаштуваннями вивантаження у фоновому та активному режимі", - "backup_settings_subtitle": "Керування налаштуваннями вивантаження", - "backup_upload_details_page_more_details": "Натисніть, щоб дізнатися більше", - "backward": "Назад", - "biometric_auth_enabled": "Біометрична автентифікація увімкнена", - "biometric_locked_out": "Вам закрито доступ до біометричної автентифікації", - "biometric_no_options": "Біометричні параметри недоступні", - "biometric_not_available": "Біометрична автентифікація недоступна на цьому пристрої", - "birthdate_saved": "Дата народження успішно збережена", - "birthdate_set_description": "Дата народження використовується для обчислення віку цієї особи на момент фотографії.", - "blurred_background": "Розмитий фон", - "bugs_and_feature_requests": "Помилки та Запити", - "build": "Збірка", - "build_image": "Версія збірки", - "bulk_delete_duplicates_confirmation": "Ви впевнені, що хочете масово видалити {count, plural, one {# дубльований файл} few {# дубльовані файли} other {# дубльованих файлів}}? Ця дія залишить найбільший файл у кожній групі і остаточно видалить всі інші дублікати. Цю дію неможливо скасувати!", - "bulk_keep_duplicates_confirmation": "Ви впевнені, що хочете залишити {count, plural, one {# дубльований файл} few {# дубльовані файли} other {# дубльованих файлів}}? Це дозволить вирішити всі групи дублікатів без видалення чого-небудь.", - "bulk_trash_duplicates_confirmation": "Ви впевнені, що хочете перемістити до кошика {count, plural, one {# дубльований файл} few {# дубльовані файли} other {# дубльованих файлів}}? Це залишить найбільший файл у кожній групі й перемістить до кошика всі інші дублікати.", - "buy": "Придбати Immich", - "cache_settings_clear_cache_button": "Очистити кеш", - "cache_settings_clear_cache_button_title": "Очищає кеш застосунку. Це суттєво знизить продуктивність застосунку, доки кеш не буде перебудовано.", - "cache_settings_duplicated_assets_clear_button": "ОЧИСТИТИ", - "cache_settings_duplicated_assets_subtitle": "Фото та відео, які ігноруються застосунком", - "cache_settings_duplicated_assets_title": "Дубльовані фото та відео ({count})", - "cache_settings_statistics_album": "Бібліотечні мініатюри", - "cache_settings_statistics_full": "Повнорозмірні зображення", - "cache_settings_statistics_shared": "Мініатюри спільних альбомів", - "cache_settings_statistics_thumbnail": "Мініатюри", - "cache_settings_statistics_title": "Використання кешу", - "cache_settings_subtitle": "Контролює кешування у мобільному застосунку", - "cache_settings_tile_subtitle": "Керування поведінкою локального сховища", - "cache_settings_tile_title": "Локальне сховище", - "cache_settings_title": "Налаштування кешування", - "camera": "Камера", - "camera_brand": "Марка камери", - "camera_model": "Модель камери", - "cancel": "Скасувати", - "cancel_search": "Скасувати пошук", - "canceled": "Скасовано", - "canceling": "Скасування", - "cannot_merge_people": "Неможливо об'єднати людей", - "cannot_undo_this_action": "Ви не можете скасувати цю дію!", - "cannot_update_the_description": "Неможливо оновити опис", - "cast": "Транслювати", - "cast_description": "Налаштувати доступні місця трансляції", - "change_date": "Змінити дату", - "change_description": "Змінити опис", - "change_display_order": "Змінити порядок відображення", - "change_expiration_time": "Змінити термін дії", - "change_location": "Змінити місцезнаходження", - "change_name": "Змінити ім'я", - "change_name_successfully": "Ім'я успішно змінено", - "change_password": "Змінити пароль", - "change_password_description": "Це або перший раз, коли ви увійшли в систему, або було зроблено запит на зміну вашого пароля. Будь ласка, введіть новий пароль нижче.", - "change_password_form_confirm_password": "Підтвердити пароль", - "change_password_form_description": "Привіт, {name},\n\nЦе або ваш перший вхід у систему, або було надіслано запит на зміну пароля. Будь ласка, введіть новий пароль нижче.", - "change_password_form_log_out": "Вийти із системи на всіх інших пристроях", - "change_password_form_log_out_description": "Рекомендується вийти з усіх інших пристроїв", - "change_password_form_new_password": "Новий пароль", - "change_password_form_password_mismatch": "Паролі не збігаються", - "change_password_form_reenter_new_password": "Повторіть новий пароль", - "change_pin_code": "Змінити PIN-код", - "change_trigger": "Змінити тригер", - "change_trigger_prompt": "Ви впевнені, що хочете змінити тригер? Це видалить усі наявні дії та фільтри.", - "change_your_password": "Змініть свій пароль", - "changed_visibility_successfully": "Видимість успішно змінено", - "charging": "Зарядка", - "charging_requirement_mobile_backup": "Для фонового резервного копіювання пристрій повинен заряджатися", - "check_corrupt_asset_backup": "Перевірити на пошкоджені резервні копії файлів", - "check_corrupt_asset_backup_button": "Виконати перевірку", - "check_corrupt_asset_backup_description": "Запустити цю перевірку лише через Wi-Fi та після того, як всі файли будуть завантажені на сервер. Процес може зайняти кілька хвилин.", - "check_logs": "Перевірити журнали", - "checksum": "Контрольна сума", - "choose_matching_people_to_merge": "Виберіть людей для об'єднання", - "city": "Місто", - "cleanup_confirm_description": "Immich знайшов {count, plural, one {# файл} few {# файли} other {# файлів}} (створених до {date}), безпечно збережених на сервері. Видалити локальні копії з цього пристрою?", - "cleanup_confirm_prompt_title": "Вилучити з цього пристрою?", - "cleanup_deleted_assets": "Переміщено {count, plural, one {# файл} few {# файли} other {# файлів}} до кошика пристрою", - "cleanup_deleting": "Переміщення до кошика...", - "cleanup_found_assets": "Знайдено {count} резервних копій файлів", - "cleanup_found_assets_with_size": "Знайдено {count} резервних копій файлів ({size})", - "cleanup_icloud_shared_albums_excluded": "Спільні альбоми iCloud виключаються зі сканування", - "cleanup_no_assets_found": "Не знайдено файлів, що відповідають наведеним вище критеріям. Функція «Звільнити місце» може видалити лише файли, резервні копії яких було створено на сервері", - "cleanup_preview_title": "Фото та відео для вилучення ({count})", - "cleanup_step3_description": "Скануйте резервні копії файлів, що відповідають вашій даті, та збережіть налаштування.", - "cleanup_step4_summary": "{count} файлів (створених до {date}) для видалення з вашого локального пристрою. Фотографії залишатимуться доступними із застосунку Immich.", - "cleanup_trash_hint": "Щоб повністю звільнити місце для зберігання, відкрийте системну галерею та очистіть кошик", - "clear": "Очистити", - "clear_all": "Очистити все", - "clear_all_recent_searches": "Очистити всі останні пошукові запити", - "clear_file_cache": "Очистити кеш файлів", - "clear_message": "Очистити повідомлення", - "clear_value": "Очистити значення", - "client_cert_dialog_msg_confirm": "ОК", - "client_cert_enter_password": "Введіть пароль", - "client_cert_import": "Імпорт", - "client_cert_import_success_msg": "Клієнтський сертифікат імпортовано", - "client_cert_invalid_msg": "Недійсний файл сертифіката або неправильний пароль", - "client_cert_remove_msg": "Клієнтський сертифікат видалено", - "client_cert_subtitle": "Підтримує лише формат PKCS12 (.p12, .pfx). Імпорт/видалення сертифіката доступне лише перед входом у систему", - "client_cert_title": "SSL-сертифікат клієнта [ЕКСПЕРИМЕНТАЛЬНИЙ]", - "clockwise": "По годинниковій стрілці", - "close": "Закрити", - "collapse": "Згорнути", - "collapse_all": "Згорнути все", - "color": "Колір", - "color_theme": "Кольорова тема", - "command": "Команда", - "comment_deleted": "Коментар видалено", - "comment_options": "Параметри коментарів", - "comments_and_likes": "Коментарі та вподобання", - "comments_are_disabled": "Коментарі вимкнено", - "common_create_new_album": "Створити новий альбом", - "completed": "Завершено", - "confirm": "Підтвердити", - "confirm_admin_password": "Підтвердити пароль адміністратора", - "confirm_delete_face": "Ви впевнені, що хочете видалити обличчя {name} з цього зображення?", - "confirm_delete_shared_link": "Ви впевнені, що хочете видалити це спільне посилання?", - "confirm_keep_this_delete_others": "Усі інші зображення в стеку буде видалено, окрім цього зображення. Ви впевнені, що хочете продовжити?", - "confirm_new_pin_code": "Підтвердьте новий PIN-код", - "confirm_password": "Підтвердити пароль", - "confirm_tag_face": "Бажаєте позначити це обличчя як {name}?", - "confirm_tag_face_unnamed": "Бажаєте позначити це обличчя?", - "connected_device": "Підключений пристрій", - "connected_to": "Підключено до", - "contain": "Містити", - "context": "Контекст", - "continue": "Продовжити", - "control_bottom_app_bar_create_new_album": "Створити новий альбом", - "control_bottom_app_bar_delete_from_immich": "Видалити з Immich", - "control_bottom_app_bar_delete_from_local": "Видалити з пристрою", - "control_bottom_app_bar_edit_location": "Редагувати місцезнаходження", - "control_bottom_app_bar_edit_time": "Редагувати дату та час", - "control_bottom_app_bar_share_link": "Поділитися", - "control_bottom_app_bar_share_to": "Поділитися", - "control_bottom_app_bar_trash_from_immich": "До кошика", - "copied_image_to_clipboard": "Зображення скопійовано в буфер обміну.", - "copied_to_clipboard": "Скопійовано в буфер обміну!", - "copy_error": "Помилка копіювання", - "copy_file_path": "Скопіювати шлях до файлу", - "copy_image": "Скопіювати зображення", - "copy_link": "Скопіювати посилання", - "copy_link_to_clipboard": "Скопіювати посилання в буфер обміну", - "copy_password": "Скопіювати пароль", - "copy_to_clipboard": "Скопіювати в буфер обміну", - "country": "Країна", - "cover": "Обкладинка", - "covers": "Обкладинки", - "create": "Створити", - "create_album": "Створити альбом", - "create_album_page_untitled": "Без назви", - "create_api_key": "Створити ключ API", - "create_first_workflow": "Створити перший робочий процес", - "create_library": "Створити бібліотеку", - "create_link": "Створити посилання", - "create_link_to_share": "Створити посилання спільного доступу", - "create_link_to_share_description": "Дозволити перегляд вибраних фотографій за посиланням будь-кому", - "create_new": "СТВОРИТИ НОВИЙ", - "create_new_person": "Створити нову особу", - "create_new_person_hint": "Призначити обраним фото нову особу", - "create_new_user": "Створити нового користувача", - "create_shared_album_page_share_add_assets": "ДОДАТИ ФОТО/ВІДЕО", - "create_shared_album_page_share_select_photos": "Вибрати фото", - "create_shared_link": "Створити спільне посилання", - "create_tag": "Створити тег", - "create_tag_description": "Створити новий тег. Для вкладених тегів вкажіть повний шлях тега, включаючи слеші.", - "create_user": "Створити користувача", - "create_workflow": "Створити робочий процес", - "created": "Створено", - "created_at": "Створено", - "creating_linked_albums": "Створення пов’язаних альбомів...", - "crop": "Кадрувати", - "crop_aspect_ratio_fixed": "Фіксоване", - "crop_aspect_ratio_free": "Вільне", - "crop_aspect_ratio_original": "Оригінал", - "curated_object_page_title": "Речі", - "current_device": "Поточний пристрій", - "current_pin_code": "Поточний PIN-код", - "current_server_address": "Поточна адреса сервера", - "custom_date": "Власна дата", - "custom_locale": "Користувацький регіон", - "custom_locale_description": "Форматувати дати та числа з урахуванням мови та регіону", - "custom_url": "Власна URL-адреса", - "cutoff_date_description": "Збережіть фотографії з останнього…", - "cutoff_day": "{count, plural, one {день} few {дні} many {днів} other {днів}}", - "cutoff_year": "{count, plural, one {рік} few {роки} many {років} other {років}}", - "daily_title_text_date": "Е, МММ дд", - "daily_title_text_date_year": "Е, МММ дд, рррр", - "dark": "Темна", - "dark_theme": "Увімкнути темну тему", - "date": "Дата", - "date_after": "Дата після", - "date_and_time": "Дата і час", - "date_before": "Дата до", - "date_format": "Е, ЛЛЛ д, р • г:мм дп", - "date_of_birth_saved": "Дата народження успішно збережена", - "date_range": "Проміжок часу", - "day": "День", - "days": "Дні", - "deduplicate_all": "Видалити всі дублікати", - "deduplication_criteria_1": "Розмір зображення в байтах", - "deduplication_criteria_2": "Кількість даних EXIF", - "deduplication_info": "Інформація про дедуплікацію", - "deduplication_info_description": "Для автоматичного попереднього вибору файлів і масового видалення дублікатів ми враховуємо:", - "default_locale": "Мова та регіон за замовчуванням", - "default_locale_description": "Форматувати дати та числа з урахуванням мови вашого браузера", - "delete": "Видалити", - "delete_action_confirmation_message": "Ви впевнені, що хочете видалити цей файл? Його буде переміщено до кошика на сервері, а також зʼявиться запит на його видалення з пристрою", - "delete_action_prompt": "Видалено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "delete_album": "Видалити альбом", - "delete_api_key_prompt": "Ви впевнені, що хочете видалити цей ключ API?", - "delete_dialog_alert": "Ці файли будуть остаточно видалені з серверу Immich та вашого пристрою", - "delete_dialog_alert_local": "Ці файли будуть остаточно видалені з вашого пристрою, але залишаться доступними на сервері Immich", - "delete_dialog_alert_local_non_backed_up": "Деякі файли не були збережені на сервері Immich і будуть остаточно видалені з вашого пристрою", - "delete_dialog_alert_remote": "Ці файли будуть назавжди видалені з серверу Immich", - "delete_dialog_ok_force": "Все одно видалити", - "delete_dialog_title": "Видалити остаточно", - "delete_duplicates_confirmation": "Ви впевнені, що хочете назавжди видалити ці дублікати?", - "delete_face": "Видалити обличчя", - "delete_key": "Видалити ключ", - "delete_library": "Видалити бібліотеку", - "delete_link": "Видалити посилання", - "delete_local_action_prompt": "Видалено з пристрою {count, plural, one {# файл} few {# файли} other {# файлів}}", - "delete_local_dialog_ok_backed_up_only": "Видалити лише резервні копії", - "delete_local_dialog_ok_force": "Все одно видалити", - "delete_others": "Видалити інші", - "delete_permanently": "Видалити назавжди", - "delete_permanently_action_prompt": "Остаточно видалено {count, plural, one {# файл} few {# файли} other {# файлів}}", - "delete_shared_link": "Видалити спільне посилання", - "delete_shared_link_dialog_title": "Видалити спільне посилання", - "delete_tag": "Видалити Тег", - "delete_tag_confirmation_prompt": "Ви впевнені, що хочете видалити тег {tagName}?", - "delete_user": "Видалити користувача", - "deleted_shared_link": "Видалено спільне посилання", - "deletes_missing_assets": "Видаляє файли, які відсутні на диску", - "description": "Опис", - "description_input_hint_text": "Додати опис...", - "description_input_submit_error": "Помилка оновлення опису, перевірте журнал для подробиць", - "deselect_all": "Скасувати вибір усіх", - "details": "Деталі", - "direction": "Напрям", - "disable": "Вимкнути", - "disabled": "Вимкнено", - "disallow_edits": "Заборонити редагування", - "discord": "Discord", - "discover": "Виявити", - "discovered_devices": "Виявлені пристрої", - "dismiss_all_errors": "Пропустити всі помилки", - "dismiss_error": "Пропустити помилку", - "display_options": "Параметри відображення", - "display_order": "Порядок відображення", - "display_original_photos": "Відображення оригінальних фотографій", - "display_original_photos_setting_description": "Надавати перевагу відображенню оригінального фото при перегляді фотографії, якщо оригінальне фото сумісне з вебом. Це може призвести до повільнішого відображення фотографій.", - "do_not_show_again": "Не показувати це повідомлення знову", - "documentation": "Документація", - "done": "Готово", - "download": "Завантажити", - "download_action_prompt": "Завантаження {count} фото та відео", - "download_canceled": "Завантаження скасовано", - "download_complete": "Завантаження закінчено", - "download_enqueue": "Завантаження поставлено в чергу", - "download_error": "Помилка завантаження", - "download_failed": "Завантаження не вдалося", - "download_finished": "Завантаження закінчено", - "download_include_embedded_motion_videos": "Вбудовані відео", - "download_include_embedded_motion_videos_description": "Включати відео, вбудовані в рухомі фотографії, як окреме відео", - "download_notfound": "Завантаження не виявлено", - "download_original": "Завантажити оригінал", - "download_paused": "Завантаження призупинено", - "download_settings": "Завантажити", - "download_settings_description": "Керування налаштуваннями, пов'язаними з завантаженням фото та відео", - "download_started": "Завантаження розпочато", - "download_sucess": "Успішне завантаження", - "download_sucess_android": "Фото та відео завантажено в DCIM/Immich", - "download_waiting_to_retry": "Очікування повторної спроби", - "downloading": "Завантаження", - "downloading_asset_filename": "Завантаження файлу {filename}", - "downloading_from_icloud": "Завантаження з iCloud", - "downloading_media": "Завантаження медіа", - "drop_files_to_upload": "Перенесіть файли в будь-яке місце для вивантаження", - "duplicates": "Дублікати", - "duplicates_description": "Визначити, які групи є дублікатами", - "duration": "Тривалість", - "edit": "Змінити", - "edit_album": "Редагувати альбом", - "edit_avatar": "Редагувати аватар", - "edit_birthday": "Редагувати дату народження", - "edit_date": "Редагувати дату", - "edit_date_and_time": "Редагувати дату та час", - "edit_date_and_time_action_prompt": "Змінено дату та час у {count, plural, one {# файлі} few {# файлах} other {# файлах}}", - "edit_date_and_time_by_offset": "Змінити дату за зміщенням", - "edit_date_and_time_by_offset_interval": "Новий діапазон дат: {from} - {to}", - "edit_description": "Редагувати опис", - "edit_description_prompt": "Будь ласка, виберіть новий опис:", - "edit_exclusion_pattern": "Редагувати шаблон виключень", - "edit_faces": "Редагування облич", - "edit_key": "Змінити ключ", - "edit_link": "Редагувати посилання", - "edit_location": "Редагувати місцезнаходження", - "edit_location_action_prompt": "Змінено місць зйомки: {count}", - "edit_location_dialog_title": "Місцезнаходження", - "edit_name": "Відредагувати ім'я", - "edit_people": "Редагувати людей", - "edit_tag": "Редагувати тег", - "edit_title": "Редагувати заголовок", - "edit_user": "Редагувати користувача", - "edit_workflow": "Редагувати робочий процес", - "editor": "Редактор", - "editor_close_without_save_prompt": "Зміни не будуть збережені", - "editor_close_without_save_title": "Закрити редактор?", - "editor_confirm_reset_all_changes": "Ви впевнені, що хочете скинути всі зміни?", - "editor_flip_horizontal": "Відобразити горизонтально", - "editor_flip_vertical": "Відобразити вертикально", - "editor_orientation": "Орієнтація", - "editor_reset_all_changes": "Скинути зміни", - "editor_rotate_left": "Повернути на 90° проти годинникової стрілки", - "editor_rotate_right": "Повернути на 90° за годинниковою стрілкою", - "email": "Електронна пошта", - "email_notifications": "Сповіщення ел. поштою", - "empty_folder": "Ця папка порожня", - "empty_trash": "Очистити кошик", - "empty_trash_confirmation": "Ви впевнені, що хочете очистити кошик? Це остаточно видалить всі файли у кошику з Immich.\nЦю дію не можна скасувати!", - "enable": "Увімкнути", - "enable_backup": "Увімкнути резервне копіювання", - "enable_biometric_auth_description": "Введіть свій PIN-код, щоб увімкнути біометричну автентифікацію", - "enabled": "Увімкнено", - "end_date": "Дата завершення", - "enqueued": "У черзі", - "enter_wifi_name": "Введіть назву Wi-Fi", - "enter_your_pin_code": "Введіть свій PIN-код", - "enter_your_pin_code_subtitle": "Введіть свій PIN-код, щоб отримати доступ до особистої папки", - "error": "Помилка", - "error_change_sort_album": "Не вдалося змінити порядок сортування альбому", - "error_delete_face": "Помилка при видаленні обличчя з файлу", - "error_getting_places": "Помилка отримання місць", - "error_loading_albums": "Помилка завантаження альбомів", - "error_loading_image": "Помилка завантаження зображення", - "error_loading_partners": "Помилка завантаження партнерів: {error}", - "error_retrieving_asset_information": "Помилка отримання інформації про файл", - "error_saving_image": "Помилка: {error}", - "error_tag_face_bounding_box": "Помилка під час позначення обличчя – не вдалося отримати координати рамки", - "error_title": "Помилка: щось пішло не так", - "error_while_navigating": "Помилка під час переходу до файлу", - "errors": { - "cannot_navigate_next_asset": "Не вдається перейти до наступного файлу", - "cannot_navigate_previous_asset": "Не вдається перейти до попереднього файлу", - "cant_apply_changes": "Не вдається застосувати зміни", - "cant_change_activity": "Не можна {enabled, select, true {вимкнути} other {увімкнути}} активність", - "cant_change_asset_favorite": "Не вдається змінити обране для файлу", - "cant_change_metadata_assets_count": "Неможливо змінити метадані {count, plural, one {# файл} few {# файли} other {# файлів}}", - "cant_get_faces": "Не можу розпізнати обличчя", - "cant_get_number_of_comments": "Не вдається отримати кількість коментарів", - "cant_search_people": "Не вдається виконати пошук людей", - "cant_search_places": "Не вдається виконати пошук місць", - "error_adding_assets_to_album": "Помилка додавання файлів до альбому", - "error_adding_users_to_album": "Помилка додавання користувачів до альбому", - "error_deleting_shared_user": "Помилка під час видалення користувача зі спільним доступом", - "error_downloading": "Помилка завантаження {filename}", - "error_hiding_buy_button": "Помилка при спробі приховати кнопку покупки", - "error_removing_assets_from_album": "Помилка видалення файлів з альбому, перевірте консоль для отримання додаткових відомостей", - "error_selecting_all_assets": "Помилка вибору всіх файлів", - "exclusion_pattern_already_exists": "Цей шаблон виключення вже існує.", - "failed_to_create_album": "Не вдалося створити альбом", - "failed_to_create_shared_link": "Не вдалося створити спільне посилання", - "failed_to_edit_shared_link": "Не вдалося відредагувати спільне посилання", - "failed_to_get_people": "Не вдалося отримати інформацію про людей", - "failed_to_keep_this_delete_others": "Не вдалося зберегти цей файл і видалити інші файли", - "failed_to_load_asset": "Не вдалося завантажити файл", - "failed_to_load_assets": "Не вдалося завантажити файли", - "failed_to_load_notifications": "Не вдалося завантажити сповіщення", - "failed_to_load_people": "Не вдалося завантажити людей", - "failed_to_remove_product_key": "Не вдалося видалити ключ продукту", - "failed_to_reset_pin_code": "Не вдалося скинути PIN-код", - "failed_to_stack_assets": "Не вдалося згорнути файли", - "failed_to_unstack_assets": "Не вдалося розгорнути файли", - "failed_to_update_notification_status": "Не вдалося оновити статус сповіщення", - "incorrect_email_or_password": "Неправильна адреса електронної пошти або пароль", - "library_folder_already_exists": "Цей шлях імпорту вже існує.", - "paths_validation_failed": "{paths, plural, one {# шлях} few {# шляхи} many {# шляхів} other {# шляху}} не пройшло перевірку", - "profile_picture_transparent_pixels": "Зображення профілю не може містити прозорих пікселів. Будь ласка, збільшіть масштаб та/або перемістіть зображення.", - "quota_higher_than_disk_size": "Ви встановили квоту, що перевищує розмір диска", - "something_went_wrong": "Щось пішло не так", - "unable_to_add_album_users": "Неможливо додати користувачів до альбому", - "unable_to_add_assets_to_shared_link": "Не вдається додати файли до спільного посилання", - "unable_to_add_comment": "Неможливо додати коментар", - "unable_to_add_exclusion_pattern": "Не вдається додати шаблон виключення", - "unable_to_add_partners": "Не вдається додати партнерів", - "unable_to_add_remove_archive": "Неможливо {archived, select, true {вилучити файл із} other {додати файл до}} архіву", - "unable_to_add_remove_favorites": "Неможливо {favorite, select, true {додати файл до} other {вилучити файл із}} обраних", - "unable_to_archive_unarchive": "Неможливо {archived, select, true {архівувати} other {розархівувати}}", - "unable_to_change_album_user_role": "Неможливо змінити роль користувача альбому", - "unable_to_change_date": "Неможливо змінити дату", - "unable_to_change_description": "Не вдалося змінити опис", - "unable_to_change_favorite": "Неможливо змінити статус обраного для файлу", - "unable_to_change_location": "Неможливо змінити місцезнаходження", - "unable_to_change_password": "Не вдається змінити пароль", - "unable_to_change_visibility": "Неможливо змінити видимість для {count, plural, one {# особи} few {# осіб} other {# людей}}", - "unable_to_complete_oauth_login": "Неможливо завершити вхід через OAuth", - "unable_to_connect": "Не вдається підключитися", - "unable_to_copy_to_clipboard": "Неможливо скопіювати в буфер обміну. Переконайтеся, що ви заходите на сторінку через https", - "unable_to_create": "Не вдалося створити робочий процес", - "unable_to_create_admin_account": "Неможливо створити обліковий запис адміністратора", - "unable_to_create_api_key": "Неможливо створити новий ключ API", - "unable_to_create_library": "Не вдалося створити бібліотеку", - "unable_to_create_user": "Не вдалося створити користувача", - "unable_to_delete_album": "Не вдається видалити альбом", - "unable_to_delete_asset": "Не вдається видалити файл", - "unable_to_delete_assets": "Помилка видалення файлів", - "unable_to_delete_exclusion_pattern": "Не вдалося видалити шаблон виключення", - "unable_to_delete_shared_link": "Не вдалося видалити спільне посилання", - "unable_to_delete_user": "Не вдається видалити користувача", - "unable_to_delete_workflow": "Не вдалося видалити робочий процес", - "unable_to_download_files": "Неможливо завантажити файли", - "unable_to_edit_exclusion_pattern": "Не вдалося редагувати шаблон виключення", - "unable_to_empty_trash": "Неможливо очистити кошик", - "unable_to_enter_fullscreen": "Неможливо увійти в повноекранний режим", - "unable_to_exit_fullscreen": "Неможливо вийти з повноекранного режиму", - "unable_to_get_comments_number": "Не вдалося отримати кількість коментарів", - "unable_to_get_shared_link": "Не вдалося отримати спільне посилання", - "unable_to_hide_person": "Неможливо приховати людину", - "unable_to_link_motion_video": "Не вдається зв'язати рухоме відео", - "unable_to_link_oauth_account": "Не вдається прив'язати обліковий запис OAuth", - "unable_to_log_out_all_devices": "Не вдається вийти з усіх пристроїв", - "unable_to_log_out_device": "Не вдається вийти з пристрою", - "unable_to_login_with_oauth": "Не вдається увійти за допомогою OAuth", - "unable_to_play_video": "Не вдається відтворити відео", - "unable_to_reassign_assets_existing_person": "Не вдалося перепризначити файли {name, select, null {існуючій особі} other {{name}}}", - "unable_to_reassign_assets_new_person": "Неможливо перепризначити файли новій особі", - "unable_to_refresh_user": "Не вдалося оновити користувача", - "unable_to_remove_album_users": "Неможливо видалити користувачів з альбому", - "unable_to_remove_api_key": "Не вдається видалити ключ API", - "unable_to_remove_assets_from_shared_link": "Не вдається видалити файли зі спільного посилання", - "unable_to_remove_library": "Не вдається видалити бібліотеку", - "unable_to_remove_partner": "Не вдається видалити партнера", - "unable_to_remove_reaction": "Не вдалося видалити реакцію", - "unable_to_reset_password": "Не вдається скинути пароль", - "unable_to_reset_pin_code": "Неможливо скинути PIN-код", - "unable_to_resolve_duplicate": "Не вдається вирішити дублікат", - "unable_to_restore_assets": "Неможливо відновити файли", - "unable_to_restore_trash": "Не вдалося відновити вміст", - "unable_to_restore_user": "Не вдається відновити користувача", - "unable_to_save_album": "Не вдається зберегти альбом", - "unable_to_save_api_key": "Не вдається зберегти ключ API", - "unable_to_save_date_of_birth": "Не вдалося зберегти дату народження", - "unable_to_save_name": "Не вдається зберегти ім'я", - "unable_to_save_profile": "Не вдається зберегти профіль", - "unable_to_save_settings": "Не вдається зберегти налаштування", - "unable_to_scan_libraries": "Не вдається просканувати бібліотеки", - "unable_to_scan_library": "Не вдалося просканувати бібліотеку", - "unable_to_set_feature_photo": "Не вдалося встановити фотографію на обкладинку", - "unable_to_set_profile_picture": "Не вдається встановити зображення профілю", - "unable_to_set_rating": "Не вдалося встановити рейтинг", - "unable_to_submit_job": "Не вдалося надіслати завдання", - "unable_to_trash_asset": "Неможливо видалити файл", - "unable_to_unlink_account": "Не вдається відв'язати обліковий запис", - "unable_to_unlink_motion_video": "Не вдається від'єднати рухоме відео", - "unable_to_update_album_cover": "Неможливо оновити обкладинку альбому", - "unable_to_update_album_info": "Неможливо оновити інформацію про альбом", - "unable_to_update_library": "Не вдалося оновити бібліотеку", - "unable_to_update_location": "Не вдається оновити місцезнаходження", - "unable_to_update_settings": "Не вдається оновити налаштування", - "unable_to_update_timeline_display_status": "Не вдається оновити стан відображення шкали часу", - "unable_to_update_user": "Неможливо оновити дані користувача", - "unable_to_update_workflow": "Не вдалося оновити робочий процес", - "unable_to_upload_file": "Не вдалося вивантажити файл" - }, - "errors_text": "Помилки", - "exclusion_pattern": "Шаблон виключення", - "exif": "Exif", - "exif_bottom_sheet_description": "Додати опис...", - "exif_bottom_sheet_description_error": "Помилка під час оновлення опису", - "exif_bottom_sheet_details": "Деталі", - "exif_bottom_sheet_location": "МІСЦЕ", - "exif_bottom_sheet_no_description": "Без опису", - "exif_bottom_sheet_people": "ЛЮДИ", - "exif_bottom_sheet_person_add_person": "Додати ім'я", - "exit_slideshow": "Вийти зі слайд-шоу", - "expand_all": "Розгорнути все", - "experimental_settings_new_asset_list_subtitle": "В розробці", - "experimental_settings_new_asset_list_title": "Увімкнути експериментальну сітку фото", - "experimental_settings_subtitle": "На власний ризик!", - "experimental_settings_title": "Експериментальні", - "expire_after": "Термін дії закінчується через", - "expired": "Закінчився термін дії", - "expires_date": "Термін дії закінчується {date}", - "explore": "Дослідити", - "explorer": "Провідник", - "export": "Експортувати", - "export_as_json": "Експорт в JSON", - "export_database": "Експортувати базу даних", - "export_database_description": "Експортувати базу даних SQLite", - "extension": "Розширення", - "external": "Зовнішні", - "external_libraries": "Зовнішні бібліотеки", - "external_network": "Зовнішня мережа", - "external_network_sheet_info": "Коли ви не підключені до обраної мережі Wi-Fi, застосунок підключатиметься до сервера через першу з наведених нижче URL-адрес, яку він зможе досягти, починаючи зверху вниз", - "face_unassigned": "Не призначено", - "failed": "Не вдалося", - "failed_count": "Не вдалося: {count}", - "failed_to_authenticate": "Помилка автентифікації", - "failed_to_load_assets": "Не вдалося завантажити файли", - "failed_to_load_folder": "Не вдалося завантажити папку", - "favorite": "До обраного", - "favorite_action_prompt": "{count} додано до обраного", - "favorite_or_unfavorite_photo": "Додати до обраних або видалити з обраних фото", - "favorites": "Обране", - "favorites_page_no_favorites": "Немає обраних фото та відео", - "feature_photo_updated": "Вибране фото оновлено", - "features": "Додаткові можливості", - "features_in_development": "Функції в розробці", - "features_setting_description": "Керування додатковими можливостями застосунку", - "file_name_or_extension": "Ім'я файлу або розширення", - "file_size": "Розмір файлу", - "filename": "Ім'я файлу", - "filetype": "Тип файлу", - "filter": "Фільтр", - "filter_description": "Умови для фільтрації цільових файлів", - "filter_people": "Фільтр за людьми", - "filter_places": "Фільтр за місцями", - "filters": "Фільтри", - "find_them_fast": "Швидко знаходьте їх за назвою за допомогою пошуку", - "first": "Перший", - "fix_incorrect_match": "Виправити неправильний збіг", - "folder": "Папка", - "folder_not_found": "Папку не знайдено", - "folders": "Папки", - "folders_feature_description": "Перегляд папок з фотографіями та відео у файловій системі", - "forgot_pin_code_question": "Забули свій PIN-код?", - "forward": "Переслати", - "free_up_space": "Звільнити місце", - "free_up_space_description": "Перемістіть резервні копії фотографій і відео до кошика вашого пристрою, щоб звільнити місце. Ваші копії на сервері залишаються в безпеці.", - "free_up_space_settings_subtitle": "Звільнити пам'ять пристрою", - "full_path": "Повний шлях: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ця функція завантажує зовнішні ресурси з Google для своєї роботи.", - "general": "Загальні", - "geolocation_instruction_location": "Натисніть на файл із геоданими, щоб використати його місцезнаходження, або виберіть місцезнаходження безпосередньо на мапі", - "get_help": "Отримати допомогу", - "get_people_error": "Помилка отримання людей", - "get_wifiname_error": "Не вдалося отримати назву Wi-Fi. Переконайтеся, що ви надали необхідні дозволи та підключені до Wi-Fi мережі", - "getting_started": "Початок", - "go_back": "Повернутися назад", - "go_to_folder": "Перейти до папки", - "go_to_search": "Перейти до пошуку", - "gps": "Геолокація", - "gps_missing": "Немає геоданих", - "grant_permission": "Надати дозвіл", - "group_albums_by": "Групувати альбоми за...", - "group_country": "Групувати за країною", - "group_no": "Без групування", - "group_owner": "За власником", - "group_places_by": "Групувати місця за...", - "group_year": "За роком", - "haptic_feedback_switch": "Увімкнути тактильну віддачу", - "haptic_feedback_title": "Тактильна віддача", - "has_quota": "Квота", - "hash_asset": "Хешувати файл", - "hashed_assets": "Хеши", - "hashing": "Хешування", - "header_settings_add_header_tip": "Додати заголовок", - "header_settings_field_validator_msg": "Значення не може бути порожнім", - "header_settings_header_name_input": "Ім'я заголовку", - "header_settings_header_value_input": "Значення заголовку", - "headers_settings_tile_title": "Користувальницькі заголовки проксі", - "height": "Висота", - "hi_user": "Привіт {name} ({email})", - "hide_all_people": "Сховати всіх", - "hide_gallery": "Приховати галерею", - "hide_named_person": "Приховати {name}", - "hide_password": "Приховати пароль", - "hide_person": "Приховати людину", - "hide_schema": "Приховати схему", - "hide_text_recognition": "Приховати розпізнавання тексту", - "hide_unnamed_people": "Приховати людей без ім'я", - "home_page_add_to_album_conflicts": "Додано {added} файлів до альбому {album}. {failed} файлів вже було в альбомі.", - "home_page_add_to_album_err_local": "Неможливо додати локальні файли до альбомів, пропускаю", - "home_page_add_to_album_success": "Додано {added} файлів до альбому {album}.", - "home_page_album_err_partner": "Поки що не вдається додати файли партнера до альбому, пропускаю", - "home_page_archive_err_local": "Поки що неможливо заархівувати локальні файли, пропускаю", - "home_page_archive_err_partner": "Неможливо архівувати файли партнера, пропускаю", - "home_page_building_timeline": "Побудова хронології", - "home_page_delete_err_partner": "Неможливо видалити файли партнера, пропускаю", - "home_page_delete_remote_err_local": "Локальні файл(и) вже в процесі видалення з сервера, пропускаю", - "home_page_favorite_err_local": "Поки що не можна додати до обраного локальні файли, пропускаю", - "home_page_favorite_err_partner": "Поки що не можна додати до обраного файли партнера, пропускаю", - "home_page_first_time_notice": "Якщо ви користуєтеся застосунком вперше, будь ласка, оберіть альбом для резервного копіювання, щоб на шкалі часу з’явилися фото та відео", - "home_page_locked_error_local": "Не вдається перемістити локальні файли до особистої папки, пропускаю", - "home_page_locked_error_partner": "Не вдається перемістити партнерські файли до особистої папки, пропускаю", - "home_page_share_err_local": "Неможливо поділитися локальними файлами через посилання, пропускаю", - "home_page_upload_err_limit": "Можна вивантажувати не більше 30 файлів водночас, пропускаю", - "host": "Хост", - "hour": "Година", - "hours": "Години", - "id": "ID", - "idle": "Простій", - "ignore_icloud_photos": "Пропускати файли з iCloud", - "ignore_icloud_photos_description": "Не завантажувати файли в Immich, якщо вони зберігаються в iCloud", - "image": "Зображення", - "image_alt_text_date": "{isVideo, select, true {Відео} other {Зображення}} знято {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Відео} other {Зображення}} з {person1} зроблено {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Відео} other {Зображення}} з {person1} та {person2} зроблено {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Відео} other {Зображення}} з {person1}, {person2} і {person3} зроблено {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Відео} other {Зображення}} з {person1}, {person2} та ще {additionalCount, number} особами зроблено {date}", - "image_alt_text_date_place": "{isVideo, select, true {Відео} other {Зображення}} зроблено в {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Відео} other {Зображення}} зроблено в {city}, {country} з {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Відео} other {Зображення}} зроблено в {city}, {country} з {person1} та {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Відео} other {Зображення}} зроблено в {city}, {country} з {person1}, {person2} та {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Відео} other {Зображення}} зроблено в {city}, {country} з {person1}, {person2} та ще {additionalCount, number} особами {date}", - "image_saved_successfully": "Зображення збережено", - "image_viewer_page_state_provider_download_started": "Завантаження почалося", - "image_viewer_page_state_provider_download_success": "Успішно завантажено", - "image_viewer_page_state_provider_share_error": "Помилка спільного доступу", - "immich_logo": "Логотип Immich", - "immich_web_interface": "Веб-інтерфейс Immich", - "import_from_json": "Імпорт з JSON", - "import_path": "Шлях імпорту", - "in_albums": "У {count, plural, one {# альбомі} few {# альбомах} many {# альбомах} other {# альбомах}}", - "in_archive": "В архіві", - "in_year": "У {year}", - "in_year_selector": "У", - "include_archived": "Відображати архів", - "include_shared_albums": "Включити спільні альбоми", - "include_shared_partner_assets": "Включити файли партнера", - "individual_share": "Індивідуальний доступ", - "individual_shares": "Окремі спільні доступи", - "info": "Інформація", - "interval": { - "day_at_onepm": "Щодня о 13:00", - "hours": "Кожну {hours, plural, one {годину} few {години} many {годин} other {години}}", - "night_at_midnight": "Кожної ночі о півночі", - "night_at_twoam": "Кожної ночі о 2:00" - }, - "invalid_date": "Недійсна дата", - "invalid_date_format": "Недійсний формат дати", - "invite_people": "Запросити", - "invite_to_album": "Запросити в альбом", - "ios_debug_info_fetch_ran_at": "Отримано дані {dateTime}", - "ios_debug_info_last_sync_at": "Остання синхронізація {dateTime}", - "ios_debug_info_no_processes_queued": "Фонові процеси відсутні в черзі", - "ios_debug_info_no_sync_yet": "Фонове завдання синхронізації ще не запускалося", - "ios_debug_info_processes_queued": "{count, plural, one {{count} фоновий процес у черзі} few {{count} фонові процеси у черзі} many {{count} фонових процесів у черзі} other {{count} фонових процесів у черзі}}", - "ios_debug_info_processing_ran_at": "Обробку виконано {dateTime}", - "items_count": "{count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "jobs": "Завдання", - "json_editor": "JSON-редактор", - "json_error": "Помилка JSON", - "keep": "Залишити", - "keep_albums": "Зберігати альбоми", - "keep_albums_count": "Зберігання {count} {count, plural, one {альбом} few {альбоми} many {альбомів} other {альбомів}}", - "keep_all": "Зберегти все", - "keep_description": "Виберіть, що залишиться на вашому пристрої після звільнення місця.", - "keep_favorites": "Зберегти обране", - "keep_on_device": "Зберегти на пристрої", - "keep_on_device_hint": "Виберіть елементи, які потрібно зберегти на цьому пристрої", - "keep_this_delete_others": "Залишити цей файл, видалити інші", - "keeping": "Зберігання: {items}", - "kept_this_deleted_others": "Збережено цей файл і видалено {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "keyboard_shortcuts": "Сполучення клавіш", - "language": "Мова", - "language_no_results_subtitle": "Спробуйте змінити пошуковий запит", - "language_no_results_title": "Мови не знайдено", - "language_search_hint": "Пошук мов...", - "language_setting_description": "Виберіть мову, якій ви надаєте перевагу", - "large_files": "Великі файли", - "last": "Останній", - "last_months": "{count, plural, one {Минулого місяця} few {Останні # місяці} many {Останні # місяців} other {Останні # місяців}}", - "last_seen": "Востаннє бачили", - "latest_version": "Остання версія", - "latitude": "Широта", - "leave": "Покинути", - "leave_album": "Вийти з альбому", - "lens_model": "Модель об'єктива", - "let_others_respond": "Дозволити іншим відповідати", - "level": "Рівень", - "library": "Бібліотека", - "library_add_folder": "Додати папку", - "library_edit_folder": "Редагувати папку", - "library_options": "Параметри бібліотеки", - "library_page_device_albums": "Альбоми на пристрої", - "library_page_new_album": "Новий альбом", - "library_page_sort_asset_count": "Кількість файлів", - "library_page_sort_created": "Нещодавно створені", - "library_page_sort_last_modified": "Остання зміна", - "library_page_sort_title": "Назва альбому", - "licenses": "Ліцензії", - "light": "Світла", - "like": "Подобається", - "like_deleted": "Вподобання видалено", - "link_motion_video": "Посилання на рухоме відео", - "link_to_oauth": "Приєднання до OAuth", - "linked_oauth_account": "Прив'язаний обліковий запис OAuth", - "list": "Перелік", - "loading": "Завантаження", - "loading_search_results_failed": "Не вдалося завантажити результати пошуку", - "local": "На пристрої", - "local_asset_cast_failed": "Неможливо транслювати файл, який не завантажено на сервер", - "local_assets": "Локальні фото та відео", - "local_id": "Місцевий ідентифікатор", - "local_media_summary": "Зведення локальних медіафайлів", - "local_network": "Локальна мережа", - "local_network_sheet_info": "Застосунок підключатиметься до сервера через цей URL, коли використовується вказана Wi-Fi мережа", - "location": "Розташування", - "location_permission": "Дозвіл до місцезнаходження", - "location_permission_content": "Щоб перемикати мережі у фоновому режимі, Immich має завжди мати доступ до точної геолокації, щоб зчитувати назву Wi-Fi мережі", - "location_picker_choose_on_map": "Обрати на мапі", - "location_picker_latitude_error": "Вкажіть дійсну широту", - "location_picker_latitude_hint": "Вкажіть широту", - "location_picker_longitude_error": "Вкажіть дійсну довготу", - "location_picker_longitude_hint": "Вкажіть довготу", - "lock": "Заблокувати", - "locked_folder": "Особиста папка", - "log_detail_title": "Деталі журналу", - "log_out": "Вийти", - "log_out_all_devices": "Вийти з усіх пристроїв", - "logged_in_as": "Вхід виконано як {user}", - "logged_out_all_devices": "Вийшли з усіх пристроїв", - "logged_out_device": "Вихід з пристрою", - "login": "Вхід", - "login_disabled": "Авторизацію вимкнено", - "login_form_api_exception": "Помилка API. Перевірте адресу сервера і спробуйте знову.", - "login_form_back_button_text": "Назад", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "Адреса серверу", - "login_form_err_http": "Вкажіть http:// або https://", - "login_form_err_invalid_email": "Недійсна електронна адреса", - "login_form_err_invalid_url": "Недійсний URL", - "login_form_err_leading_whitespace": "Пробіл на початку", - "login_form_err_trailing_whitespace": "Пробіл в кінці", - "login_form_failed_get_oauth_server_config": "Помилка входу через OAuth, перевірте адресу сервера", - "login_form_failed_get_oauth_server_disable": "OAuth недоступний на цьому сервері", - "login_form_failed_login": "Помилка входу, перевірте URL-адресу сервера, електронну пошту та пароль", - "login_form_handshake_exception": "Помилка встановлення з'єднання з сервером. Увімкніть підтримку самопідписаного сертифіката в налаштуваннях, якщо ви використовуєте самопідписаний сертифікат.", - "login_form_password_hint": "пароль", - "login_form_save_login": "Запам'ятати вхід", - "login_form_server_empty": "Введіть URL-адресу сервера.", - "login_form_server_error": "Не вдалося підключитися до сервера.", - "login_has_been_disabled": "Вхід було вимкнено.", - "login_password_changed_error": "Помилка у оновлені вашого пароля", - "login_password_changed_success": "Пароль оновлено успішно", - "logout_all_device_confirmation": "Ви впевнені, що хочете вийти з усіх пристроїв?", - "logout_this_device_confirmation": "Ви впевнені, що хочете вийти з цього пристрою?", - "logs": "Журнали", - "longitude": "Довгота", - "look": "Вигляд", - "loop_videos": "Циклічні відео", - "loop_videos_description": "Увімкнути циклічне відтворення відео.", - "main_branch_warning": "Ви використовуєте версію для розробників; настійно рекомендуємо використовувати релізну версію!", - "main_menu": "Головне меню", - "maintenance_action_restore": "Відновлення бази даних", - "maintenance_description": "Immich переведено в режим технічного обслуговування.", - "maintenance_end": "Завершити режим технічного обслуговування", - "maintenance_end_error": "Не вдалося завершити режим обслуговування.", - "maintenance_logged_in_as": "Наразі ви ввійшли як {user}", - "maintenance_restore_from_backup": "Відновлення з резервної копії", - "maintenance_restore_library": "Відновіть свою бібліотеку", - "maintenance_restore_library_confirm": "Якщо це виглядає правильно, продовжуйте відновлення резервної копії!", - "maintenance_restore_library_description": "Відновлення бази даних", - "maintenance_restore_library_folder_has_files": "{folder} має {count} папок(ок)", - "maintenance_restore_library_folder_no_files": "У папці {folder} відсутні файли!", - "maintenance_restore_library_folder_pass": "читабельний та записуваний", - "maintenance_restore_library_folder_read_fail": "нечитабельно", - "maintenance_restore_library_folder_write_fail": "не можна записувати", - "maintenance_restore_library_hint_missing_files": "Можливо, ви пропускаєте важливі файли", - "maintenance_restore_library_hint_regenerate_later": "Ви можете відновити їх пізніше в налаштуваннях", - "maintenance_restore_library_hint_storage_template_missing_files": "Використовуєте шаблон сховища? Можливо, вам бракує файлів", - "maintenance_restore_library_loading": "Завантаження перевірок цілісності та евристик…", - "maintenance_task_backup": "Створення резервної копії існуючої бази даних…", - "maintenance_task_migrations": "Виконання міграції бази даних…", - "maintenance_task_restore": "Відновлення вибраної резервної копії…", - "maintenance_task_rollback": "Не вдалося відновити, повернення до точки відновлення…", - "maintenance_title": "Тимчасово недоступно", - "make": "Виробник", - "manage_geolocation": "Керувати місцезнаходженням", - "manage_media_access_rationale": "Цей дозвіл потрібен для належного переміщення файлів до кошика та їх відновлення з нього.", - "manage_media_access_settings": "Відкрити налаштування", - "manage_media_access_subtitle": "Дозвольте застосунку Immich керувати медіафайлами та переміщувати їх.", - "manage_media_access_title": "Доступ до керування медіа", - "manage_shared_links": "Керування спільними посиланнями", - "manage_sharing_with_partners": "Керування спільним доступом з партнерами", - "manage_the_app_settings": "Керування налаштуваннями застосунку", - "manage_your_account": "Керування обліковим записом", - "manage_your_api_keys": "Керування ключами API", - "manage_your_devices": "Керування авторизованими пристроями", - "manage_your_oauth_connection": "Налаштування підключеного OAuth", - "map": "Мапа", - "map_assets_in_bounds": "{count, plural, =0 {Немає фотографій у цій місцевості} one {# фото} few {# фотографії} many {# фотографій} other {# фотографій}}", - "map_cannot_get_user_location": "Не можу отримати місцезнаходження", - "map_location_dialog_yes": "Так", - "map_location_picker_page_use_location": "Використати це місцезнаходження", - "map_location_service_disabled_content": "Служба геолокації має бути ввімкненою, щоб відображати файли з вашого поточного місцезнаходження. Увімкнути її зараз?", - "map_location_service_disabled_title": "Служба місцезнаходження вимкнена", - "map_marker_for_images": "Маркер на мапі для зображень, зроблених у місті {city}, {country}", - "map_marker_with_image": "Маркер на мапі із зображенням", - "map_no_location_permission_content": "Потрібен дозвіл, аби показувати файли із поточного місцезнаходження. Надати його зараз?", - "map_no_location_permission_title": "Помилка доступу до місцезнаходження", - "map_settings": "Налаштування мапи", - "map_settings_dark_mode": "Темний режим", - "map_settings_date_range_option_day": "Минулі 24 години", - "map_settings_date_range_option_days": "Минулих {days} днів", - "map_settings_date_range_option_year": "Минулий рік", - "map_settings_date_range_option_years": "Минулі {years} роки", - "map_settings_dialog_title": "Налаштування мапи", - "map_settings_include_show_archived": "Відображати архів", - "map_settings_include_show_partners": "Відображати фото партнера", - "map_settings_only_show_favorites": "Лише обрані", - "map_settings_theme_settings": "Тема мапи", - "map_zoom_to_see_photos": "Зменште масштаб, щоб побачити фото", - "mark_all_as_read": "Позначити всі як прочитані", - "mark_as_read": "Позначити як прочитане", - "marked_all_as_read": "Позначено всі як прочитані", - "matches": "Збіги", - "matching_assets": "Відповідні файли", - "media_type": "Тип медіа", - "memories": "Спогади", - "memories_all_caught_up": "Це все на сьогодні", - "memories_check_back_tomorrow": "Завітайте завтра, щоб побачити більше спогадів", - "memories_setting_description": "Налаштування вмісту спогадів", - "memories_start_over": "Почати заново", - "memories_swipe_to_close": "Змахніть вгору, щоб закрити", - "memory": "Спогад", - "memory_lane_title": "Алея Спогадів {title}", - "menu": "Меню", - "merge": "Об'єднати", - "merge_people": "Об'єднати людей", - "merge_people_limit": "Ви можете об'єднати до 5 облич одночасно", - "merge_people_prompt": "Ви хочете об'єднати цих людей? Ця дія незворотна.", - "merge_people_successfully": "Успішне об'єднання людей", - "merged_people_count": "Об'єднано {count, plural, one {# особа} few {# особи} many {# осіб} other {# людей}}", - "minimize": "Мінімізувати", - "minute": "Хвилина", - "minutes": "Хвилини", - "mirror_horizontal": "Горизонтальний", - "mirror_vertical": "Вертикальний", - "missing": "Відсутні", - "mobile_app": "Мобільний застосунок", - "mobile_app_download_onboarding_note": "Завантажте супутній мобільний застосунок, скориставшись наведеними нижче опціями", - "model": "Модель", - "month": "Місяць", - "monthly_title_text_date_format": "ММММ р", - "more": "Більше", - "move": "Перемістити", - "move_down": "Перемістити вниз", - "move_off_locked_folder": "Вийти з особистої папки", - "move_to": "Перемістити до", - "move_to_device_trash": "Перемістити в кошик пристрою", - "move_to_lock_folder_action_prompt": "{count} додано до особистої папки", - "move_to_locked_folder": "Перемістити до особистої папки", - "move_to_locked_folder_confirmation": "Ці фото та відео буде видалено зі всіх альбомів і їх можна буде переглядати лише в особистій папці", - "move_up": "Перемістити вгору", - "moved_to_archive": "Переміщено {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} в архів", - "moved_to_library": "Переміщено {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} в бібліотеку", - "moved_to_trash": "Переміщено до кошика", - "multiselect_grid_edit_date_time_err_read_only": "Неможливо редагувати дату файлів лише для читання, пропускаю", - "multiselect_grid_edit_gps_err_read_only": "Неможливо редагувати геолокацію файлів лише для читання, пропускаю", - "mute_memories": "Приглушити спогади", - "my_albums": "Мої альбоми", - "name": "Ім'я", - "name_or_nickname": "Ім'я або псевдонім", - "name_required": "Ім'я обов'язкове", - "navigate": "Навігація", - "navigate_to_time": "Перейти до Часу", - "network_requirement_photos_upload": "Використовувати стільникові дані для резервного копіювання фото", - "network_requirement_videos_upload": "Використовувати стільникові дані для резервного копіювання відео", - "network_requirements": "Вимоги до мережі", - "network_requirements_updated": "Вимоги до мережі змінилися, черга резервного копіювання очищена", - "networking_settings": "Мережеві налаштування", - "networking_subtitle": "Керування налаштуванням адреси серверу", - "never": "ніколи", - "new_album": "Новий альбом", - "new_api_key": "Новий ключ API", - "new_date_range": "Новий діапазон дат", - "new_password": "Новий пароль", - "new_person": "Нова людина", - "new_pin_code": "Новий PIN-код", - "new_pin_code_subtitle": "Ви вперше отримуєте доступ до особистої папки. Створіть PIN-код для безпечного доступу до цієї сторінки", - "new_timeline": "Нова хронологія", - "new_update": "Нове оновлення", - "new_user_created": "Створено нового користувача", - "new_version_available": "ДОСТУПНА НОВА ВЕРСІЯ", - "newest_first": "Спочатку нові", - "next": "Далі", - "next_memory": "Наступний спогад", - "no": "Ні", - "no_actions_added": "Поки що жодних дій не додано", - "no_albums_found": "Альбоми не знайдено", - "no_albums_message": "Створіть альбом, щоб упорядкувати свої фотографії та відео", - "no_albums_with_name_yet": "Схоже, у вас ще немає альбомів з такою назвою.", - "no_albums_yet": "Схоже, у вас ще немає жодного альбому.", - "no_archived_assets_message": "Заархівувати фотографії та відео, щоб приховати їх у вашому перегляді фото", - "no_assets_message": "Натисніть, щоб завантажити своє перше фото", - "no_assets_to_show": "Фото та відео відсутні", - "no_cast_devices_found": "Пристрої для трансляції не знайдено", - "no_checksum_local": "Контрольна сума недоступна – неможливо отримати локальні файли", - "no_checksum_remote": "Контрольна сума недоступна – неможливо отримати віддалений файл", - "no_configuration_needed": "Не потрібна конфігурація", - "no_devices": "Немає авторизованих пристроїв", - "no_duplicates_found": "Дублікатів не виявлено.", - "no_exif_info_available": "Відсутня інформація про exif", - "no_explore_results_message": "Завантажуйте більше фотографій, щоб насолоджуватися вашою колекцією.", - "no_favorites_message": "Додавайте фото та відео в Обране, щоб швидко знаходити найкращі", - "no_filters_added": "Фільтри ще не додано", - "no_libraries_message": "Створіть зовнішню бібліотеку для перегляду фотографій і відео", - "no_local_assets_found": "З цією контрольною сумою не знайдено локальних файлів", - "no_location_set": "Місцезнаходження не встановлено", - "no_locked_photos_message": "Фото та відео в особистій папці приховані і не відображаються під час перегляду чи пошуку у вашій бібліотеці.", - "no_name": "Без імені", - "no_notifications": "Немає сповіщень", - "no_people_found": "Людей, що відповідають запиту, не знайдено", - "no_places": "Місць немає", - "no_remote_assets_found": "З цією контрольною сумою не знайдено віддалених файлів", - "no_results": "Немає результатів", - "no_results_description": "Спробуйте використовувати синонім або більш загальне ключове слово", - "no_shared_albums_message": "Створіть альбом, щоб ділитися фотографіями та відео з людьми у вашій мережі", - "no_uploads_in_progress": "Немає активних вивантажень", - "none": "Жоден", - "not_allowed": "Не дозволено", - "not_available": "Немає даних", - "not_in_any_album": "У жодному альбомі", - "not_selected": "Не вибрано", - "note_apply_storage_label_to_previously_uploaded assets": "Примітка: Щоб застосувати мітку сховища до раніше вивантажених файлів, виконайте команду", - "notes": "Нотатки", - "nothing_here_yet": "Тут ще нічого немає", - "notification_permission_dialog_content": "Щоб увімкнути сповіщення, перейдіть до Налаштувань і надайте дозвіл.", - "notification_permission_list_tile_content": "Надати дозвіл для сповіщень.", - "notification_permission_list_tile_enable_button": "Увімкнути Сповіщення", - "notification_permission_list_tile_title": "Дозвіл на Сповіщення", - "notification_toggle_setting_description": "Увімкнути сповіщення електронною поштою", - "notifications": "Сповіщення", - "notifications_setting_description": "Керування сповіщеннями", - "oauth": "OAuth", - "obtainium_configurator": "Конфігуратор Obtainium", - "obtainium_configurator_instructions": "Використовуйте Obtainium для встановлення та оновлення застосунку Android безпосередньо з релізу Immich на GitHub. Створіть ключ API та виберіть варіант, щоб створити посилання на конфігурацію Obtainium", - "ocr": "OCR", - "official_immich_resources": "Офіційні ресурси Immich", - "offline": "Недоступний", - "offset": "Зсув", - "ok": "Ок", - "oldest_first": "Спочатку найстарші", - "on_this_device": "На цьому пристрої", - "onboarding": "Введення", - "onboarding_locale_description": "Виберіть бажану мову. Ви зможете змінити це пізніше в налаштуваннях.", - "onboarding_privacy_description": "Наступні (необов’язкові) функції залежать від зовнішніх сервісів і можуть бути вимкнені будь-коли в налаштуваннях.", - "onboarding_server_welcome_description": "Налаштуймо ваш сервер з базовими параметрами.", - "onboarding_theme_description": "Оберіть тему. Ви можете змінити її пізніше в налаштуваннях.", - "onboarding_user_welcome_description": "Почнемо!", - "onboarding_welcome_user": "Ласкаво просимо, {user}", - "online": "Доступний", - "only_favorites": "Лише обрані", - "open": "Відкрити", - "open_in_map_view": "Відкрити на мапі", - "open_in_openstreetmap": "Відкрити в OpenStreetMap", - "open_the_search_filters": "Відкрийте фільтри пошуку", - "options": "Налаштування", - "or": "або", - "organize_into_albums": "Упорядкувати в альбоми", - "organize_into_albums_description": "Помістити наявні фотографії в альбоми, використовуючи поточні налаштування синхронізації", - "organize_your_library": "Організуйте свою бібліотеку", - "original": "оригінал", - "other": "Інше", - "other_devices": "Інші пристрої", - "other_entities": "Інші файли", - "other_variables": "Інші змінні", - "owned": "Власні", - "owner": "Власник", - "page": "Сторінка", - "partner": "Партнер", - "partner_can_access": "{partner} має доступ", - "partner_can_access_assets": "Всі ваші фотографії та відео, окрім тих, що знаходяться в Архіві та Видалені", - "partner_can_access_location": "Місце, де були зроблені ваші фотографії", - "partner_list_user_photos": "Фотографії {user}", - "partner_list_view_all": "Переглянути усі", - "partner_page_empty_message": "Ви ще не поділилися фото з партнером.", - "partner_page_no_more_users": "Більше немає кого додати", - "partner_page_partner_add_failed": "Не вдалося додати партнера", - "partner_page_select_partner": "Обрати партнера", - "partner_page_shared_to_title": "Спільне із", - "partner_page_stop_sharing_content": "{partner} більше не матиме доступу до ваших фото.", - "partner_sharing": "Спільне використання", - "partners": "Партнери", - "password": "Пароль", - "password_does_not_match": "Паролі не збігаються", - "password_required": "Потрібен пароль", - "password_reset_success": "Пароль було успішно скинуто", - "past_durations": { - "days": "Пройшло {days, plural, one {день} few {# дні} many {# днів} other {# днів}}", - "hours": "За останні {hours, plural, one {годину} few {# години} many {# годин} other {# години}}", - "years": "Пройшло {years, plural, one {рік} few {# роки} many {# років} other {# року}}" - }, - "path": "Шлях", - "pattern": "Шаблон", - "pause": "Пауза", - "pause_memories": "Призупинити спогади", - "paused": "Призупинено", - "pending": "На розгляді", - "people": "Люди", - "people_edits_count": "Відредаговано {count, plural, one {# особу} few {# особи} many {# осіб} other {# людей}}", - "people_feature_description": "Перегляд фотографій і відео, згрупованих за людьми", - "people_selected": "{count, plural, one {# обрана особа} few {# вибрані особи} many {# вибраних осіб} other {# вибраних осіб}}", - "people_sidebar_description": "Відображення посилання на людей у бічній панелі", - "permanent_deletion_warning": "Попередження про видалення", - "permanent_deletion_warning_setting_description": "Показувати попередження при остаточному видаленні файлів", - "permanently_delete": "Видалити назавжди", - "permanently_delete_assets_count": "Остаточно видалити {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "permanently_delete_assets_prompt": "Ви впевнені, що хочете назавжди видалити {count, plural, one {цей файл?} few {ці # файли?} many {ці # файлів?} other {ці # файлів?}} Це також видалить {count, plural, one {його з} few {їх з} many {їх з} other {їх з}} альбому(ів).", - "permanently_deleted_asset": "Файл видалено назавжди", - "permanently_deleted_assets_count": "Видалено остаточно {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "permission": "Дозволи", - "permission_empty": "Дозволи не повинні бути порожніми", - "permission_onboarding_back": "Назад", - "permission_onboarding_continue_anyway": "Все одно продовжити", - "permission_onboarding_get_started": "Розпочати", - "permission_onboarding_go_to_settings": "Перейти до налаштувань", - "permission_onboarding_permission_denied": "Доступ заборонено. Для використання Immich надайте дозволи до \"Фото та відео\" в налаштуваннях.", - "permission_onboarding_permission_granted": "Доступ надано! Все готово.", - "permission_onboarding_permission_limited": "Доступ обмежено. Щоби дозволити Immich створювати резервні копії та керувати всією галереєю, надайте дозволи на фото й відео в налаштуваннях.", - "permission_onboarding_request": "Застосунку Immich потрібен дозвіл для перегляду ваших фото та відео.", - "person": "Людина", - "person_age_months": "{months, plural, one {# місяць} few {# місяці} many {# місяців} other {# місяців}}", - "person_age_year_months": "1 рік, {months, plural, one {# місяць} few {# місяці} many {# місяців} other {# місяців}}", - "person_age_years": "{years, plural, one {# рік} few {# роки} many {# років} other {# років}}", - "person_birthdate": "Дата народження: {date}", - "person_hidden": "{name}{hidden, select, true { (приховано)} other {}}", - "person_recognized": "Особу розпізнали", - "person_selected": "Обрана особа", - "photo_shared_all_users": "Виглядає так, що ви поділилися своїми фотографіями з усіма користувачами або у вас немає жодного користувача, з яким можна поділитися.", - "photos": "Фото", - "photos_and_videos": "Фото та відео", - "photos_count": "{count, plural, one {{count, number} Фотографія} few {{count, number} Фотографії} many {{count, number} Фотографій} other {{count, number} Фотографій}}", - "photos_from_previous_years": "Фотографії минулих років у цей день", - "photos_only": "Тільки фотографії", - "pick_a_location": "Виберіть місце розташування", - "pick_custom_range": "Користувацький діапазон", - "pick_date_range": "Виберіть діапазон дат", - "pin_code_changed_successfully": "PIN-код успішно змінено", - "pin_code_reset_successfully": "PIN-код успішно скинуто", - "pin_code_setup_successfully": "PIN-код успішно налаштовано", - "pin_verification": "Перевірка PIN-коду", - "place": "Місце", - "places": "Місця", - "places_count": "{count, plural, one {{count, number} Місце} few {{count, number} Місця} many {{count, number} Місць} other {{count, number} Місць}}", - "play": "Відтворити", - "play_memories": "Відтворити спогади", - "play_motion_photo": "Відтворювати рухомі фото", - "play_or_pause_video": "Відтворення або призупинення відео", - "play_original_video": "Відтворювати оригінальне відео", - "play_original_video_setting_description": "Надавати перевагу відтворенню оригінальних відео, а не перекодованих. Якщо оригінальний файл несумісний, відтворення може бути некоректним.", - "play_transcoded_video": "Відтворювати перекодоване відео", - "please_auth_to_access": "Будь ласка, пройдіть автентифікацію", - "port": "Порт", - "preferences_settings_subtitle": "Керування налаштуваннями застосунку", - "preferences_settings_title": "Параметри", - "preparing": "Підготовка", - "preset": "Передвстановлення", - "preview": "Попередній перегляд", - "previous": "Попереднє", - "previous_memory": "Попередній спогад", - "previous_or_next_day": "День вперед/назад", - "previous_or_next_month": "Місяць вперед/назад", - "previous_or_next_photo": "Фото вперед/назад", - "previous_or_next_year": "Рік вперед/назад", - "primary": "Головне", - "privacy": "Конфіденційність", - "profile": "Профіль", - "profile_drawer_app_logs": "Журнал", - "profile_drawer_client_server_up_to_date": "Клієнт та сервер — актуальні", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Режим лише для читання ввімкнено. Щоб вийти, довго натисніть значок аватара користувача.", - "profile_image_of_user": "Зображення профілю {user}", - "profile_picture_set": "Зображення профілю встановлено.", - "public_album": "Публічний альбом", - "public_share": "Публічний доступ", - "purchase_account_info": "Підтримка", - "purchase_activated_subtitle": "Дякуємо за підтримку Immich та програмного забезпечення з відкритим кодом", - "purchase_activated_time": "Активовано {date}", - "purchase_activated_title": "Ваш ключ було успішно активовано", - "purchase_button_activate": "Активувати", - "purchase_button_buy": "Купити", - "purchase_button_buy_immich": "Купити Immich", - "purchase_button_never_show_again": "Ніколи більше не показувати", - "purchase_button_reminder": "Нагадати через 30 днів", - "purchase_button_remove_key": "Видалити ключ", - "purchase_button_select": "Обрати", - "purchase_failed_activation": "Не вдалося активувати! Будь ласка, перевірте свою електронну пошту для отримання правильного ключа продукту!", - "purchase_individual_description_1": "Для індивідуального використання", - "purchase_individual_description_2": "Статус підтримки", - "purchase_individual_title": "Індивідуальний", - "purchase_input_suggestion": "Маєте ключ продукту? Введіть ключ нижче", - "purchase_license_subtitle": "Купіть Immich, щоб підтримати подальший розвиток сервісу", - "purchase_lifetime_description": "Назавжди", - "purchase_option_title": "ВАРІАНТИ КУПІВЛІ", - "purchase_panel_info_1": "Розробка Immich вимагає багато часу та зусиль. Ми маємо штатних інженерів, які працюють над тим, щоб зробити його якомога кращим. Наша місія — зробити програмне забезпечення з відкритим кодом та етичні бізнес-практики стійким джерелом доходу для розробників і створити екосистему, що поважає конфіденційність, з реальними альтернативами експлуататорським хмарним сервісам.", - "purchase_panel_info_2": "Оскільки ми зобов’язуємося не додавати платні обмеження, ця покупка не надасть вам додаткових функцій в Immich. Ми покладаємося на таких користувачів, як ви, щоб підтримувати подальший розвиток Immich.", - "purchase_panel_title": "Підтримати проєкт", - "purchase_per_server": "На сервер", - "purchase_per_user": "На користувача", - "purchase_remove_product_key": "Видалити ключ продукту", - "purchase_remove_product_key_prompt": "Ви впевнені, що хочете видалити ключ продукту?", - "purchase_remove_server_product_key": "Видалити ключ продукту для сервера", - "purchase_remove_server_product_key_prompt": "Ви впевнені, що хочете видалити ключ продукту для сервера?", - "purchase_server_description_1": "Для всього сервера", - "purchase_server_description_2": "Статус підтримки", - "purchase_server_title": "Сервер", - "purchase_settings_server_activated": "Ключ продукту сервера керується адміністратором", - "query_asset_id": "Ідентифікатор файлу запиту", - "queue_status": "У черзі {count} з {total}", - "rate_asset": "Оцінити файл", - "rating": "Зоряний рейтинг", - "rating_clear": "Очистити рейтинг", - "rating_count": "{count, plural, one {# зірка} few {# зірки} many {# зірок} other {# зірок}}", - "rating_description": "Показувати рейтинг EXIF на інформаційній панелі", - "rating_set": "Рейтинг встановлено на {rating, plural, one {# зірку} few {# зірки} many {# зірок} other {# зірок}}", - "reaction_options": "Параметри реакції", - "read_changelog": "Прочитати зміни в оновленні", - "readonly_mode_disabled": "Режим лише для читання вимкнено", - "readonly_mode_enabled": "Режим лише для читання ввімкнено", - "ready_for_upload": "Готово до вивантаження", - "reassign": "Перепризначити", - "reassigned_assets_to_existing_person": "Перепризначено {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} {name, select, null {існуючій особі} other {{name}}}", - "reassigned_assets_to_new_person": "Перепризначено {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} новій особі", - "reassing_hint": "Призначити обрані файли існуючій особі", - "recent": "Нещодавно", - "recent-albums": "Останні альбоми", - "recent_searches": "Нещодавні пошукові запити", - "recently_added": "Нещодавно додані", - "recently_added_page_title": "Нещодавні", - "recently_taken": "Недавно зроблено", - "recently_taken_page_title": "Недавно зроблені", - "refresh": "Оновити", - "refresh_encoded_videos": "Оновити закодовані відео", - "refresh_faces": "Оновити обличчя", - "refresh_metadata": "Оновити метадані", - "refresh_thumbnails": "Оновити мініатюри", - "refreshed": "Оновлений", - "refreshes_every_file": "Повторно читає всі існуючі та нові файли", - "refreshing_encoded_video": "Оновлення закодованого відео", - "refreshing_faces": "Оновлення облич", - "refreshing_metadata": "Оновлення метаданих", - "regenerating_thumbnails": "Повторне створення мініатюр", - "remote": "На сервері", - "remote_assets": "Віддалені фото та відео", - "remote_media_summary": "Зведення віддалених медіафайлів", - "remove": "Вилучити", - "remove_assets_album_confirmation": "Ви впевнені, що хочете видалити {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} з альбому?", - "remove_assets_shared_link_confirmation": "Ви впевнені, що хочете видалити {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}} з цього спільного посилання?", - "remove_assets_title": "Видалити файли?", - "remove_custom_date_range": "Видалити користувацький діапазон дат", - "remove_deleted_assets": "Видалення автономних файлів", - "remove_from_album": "Видалити з альбому", - "remove_from_album_action_prompt": "{count} видалено з альбому", - "remove_from_favorites": "Видалити з обраного", - "remove_from_lock_folder_action_prompt": "{count} вилучено з особистої папки", - "remove_from_locked_folder": "Видалити з особистої папки", - "remove_from_locked_folder_confirmation": "Ви впевнені, що хочете перемістити ці фото та відео з особистої папки? Вони будуть видимі у вашій бібліотеці.", - "remove_from_shared_link": "Видалити зі спільного посилання", - "remove_memory": "Видалити спогад", - "remove_photo_from_memory": "Видалити фото з цього спогаду", - "remove_tag": "Видалити тег", - "remove_url": "Видалити URL", - "remove_user": "Видалити користувача", - "removed_api_key": "Видалено ключ API: {name}", - "removed_from_archive": "Видалено з архіву", - "removed_from_favorites": "Видалено з обраного", - "removed_from_favorites_count": "{count, plural, other {Видалено #}} з обраних", - "removed_memory": "Видалений спогад", - "removed_photo_from_memory": "Фото видалене зі спогаду", - "removed_tagged_assets": "Видалено тег із {count, plural, one {# файлу} few {# файлів} many {# файлів} other {# файлів}}", - "rename": "Перейменувати", - "repair": "Відновлення", - "repair_no_results_message": "Невідстежувані та відсутні файли будуть відображені тут", - "replace_with_upload": "Замінити на вивантажене", - "repository": "Репозиторій", - "require_password": "Вимагати пароль", - "require_user_to_change_password_on_first_login": "Вимагати від користувача змінювати пароль при першому вході", - "rescan": "Пересканування", - "reset": "Скинути", - "reset_password": "Скинути пароль", - "reset_people_visibility": "Відновити видимість людей", - "reset_pin_code": "Скинути PIN-код", - "reset_pin_code_description": "Якщо ви забули свій PIN-код, ви можете звернутися до адміністратора сервера, щоб скинути його", - "reset_pin_code_success": "PIN-код успішно скинуто", - "reset_pin_code_with_password": "Ви завжди можете скинути свій PIN-код за допомогою пароля", - "reset_sqlite": "Очистити базу даних SQLite", - "reset_sqlite_confirmation": "Ви впевнені, що хочете очистити базу даних SQLite? Після цього потрібно буде вийти з облікового запису та увійти знову для повторної синхронізації даних", - "reset_sqlite_success": "Базу даних SQLite успішно очищено", - "reset_to_default": "Скинути до налаштування за замовчуванням", - "resolution": "Роздільна Здатність", - "resolve_duplicates": "Усунути дублікати", - "resolved_all_duplicates": "Усі дублікати усунуто", - "restore": "Відновити", - "restore_all": "Відновити все", - "restore_trash_action_prompt": "{count} відновлено з кошика", - "restore_user": "Відновити користувача", - "restored_asset": "Відновлений файл", - "resume": "Продовжити", - "resume_paused_jobs": "Відновити {count, plural, one {# призупинене завдання} few {# призупинені завдання} many {# призупинених завдань} other {# призупинених завдань}}", - "retry_upload": "Повторити вивантаження", - "review_duplicates": "Переглянути дублікати", - "review_large_files": "Перегляд великих файлів", - "role": "Роль", - "role_editor": "Редактор", - "role_viewer": "Глядач", - "running": "Активний", - "save": "Зберегти", - "save_to_gallery": "Зберегти в галерею", - "saved": "Збережено", - "saved_api_key": "Збережені ключі API", - "saved_profile": "Профіль збережено", - "saved_settings": "Налаштування збережено", - "say_something": "Скажіть що-небудь", - "scaffold_body_error_occurred": "Виникла помилка", - "scan": "Сканування", - "scan_all_libraries": "Сканувати всі бібліотеки", - "scan_library": "Сканувати", - "scan_settings": "Налаштування сканування", - "scanning": "Сканування", - "scanning_for_album": "Сканування альбому...", - "search": "Пошук", - "search_albums": "Шукати альбоми", - "search_by_context": "Пошук за контекстом", - "search_by_description": "Пошук за описом", - "search_by_description_example": "Похідний день у Сапі", - "search_by_filename": "Пошук за назвою або розширенням файлу", - "search_by_filename_example": "Наприклад, IMG_1234.JPG або PNG", - "search_by_ocr": "Пошук за OCR", - "search_by_ocr_example": "Латте", - "search_camera_lens_model": "Пошук моделі об’єктива…", - "search_camera_make": "Пошук виробника камери...", - "search_camera_model": "Пошук моделі камери...", - "search_city": "Пошук міста...", - "search_country": "Пошук країни...", - "search_filter_apply": "Застосувати фільтр", - "search_filter_camera_title": "Виберіть тип камери", - "search_filter_date": "Дата", - "search_filter_date_interval": "{start} до {end}", - "search_filter_date_title": "Виберіть діапазон дат", - "search_filter_display_option_not_in_album": "Не в альбомі", - "search_filter_display_options": "Параметри відображення", - "search_filter_filename": "Пошук за назвою файлу", - "search_filter_location": "Місцезнаходження", - "search_filter_location_title": "Виберіть місцезнаходження", - "search_filter_media_type": "Тип медіа", - "search_filter_media_type_title": "Виберіть тип медіа", - "search_filter_ocr": "Пошук за OCR", - "search_filter_people_title": "Виберіть людей", - "search_filter_star_rating": "Зоряний рейтинг", - "search_for": "Шукати для", - "search_for_existing_person": "Пошук існуючої особи", - "search_no_more_result": "Більше результатів немає", - "search_no_people": "Немає людей", - "search_no_people_named": "Немає осіб з іменем \"{name}\"", - "search_no_result": "Результатів не знайдено, спробуйте інший запит або комбінацію", - "search_options": "Параметри пошуку", - "search_page_categories": "Категорії", - "search_page_motion_photos": "Живі фото", - "search_page_no_objects": "Немає інформації про файли", - "search_page_no_places": "Інформація про місця недоступна", - "search_page_screenshots": "Знімки екрану", - "search_page_search_photos_videos": "Шукайте ваші фото та відео", - "search_page_selfies": "Селфі", - "search_page_things": "Речі", - "search_page_view_all_button": "Переглянути усі", - "search_page_your_activity": "Ваші дії", - "search_page_your_map": "Ваша мапа", - "search_people": "Шукати людей", - "search_places": "Пошук місць", - "search_rating": "Пошук за рейтингом...", - "search_result_page_new_search_hint": "Новий пошук", - "search_settings": "Пошук налаштування", - "search_state": "Пошук регіону...", - "search_suggestion_list_smart_search_hint_1": "Розумний пошук увімкнено за замовчуванням, для пошуку за метаданими використовуйте синтаксис. ", - "search_suggestion_list_smart_search_hint_2": "m:ваш-пошуковий-термін", - "search_tags": "Пошук тегів...", - "search_timezone": "Пошук часового поясу...", - "search_type": "Тип пошуку", - "search_your_photos": "Пошук серед ваших фото", - "searching_locales": "Триває пошук перекладів...", - "second": "Секунда", - "see_all_people": "Переглянути всіх людей", - "select": "Вибрати", - "select_album": "Вибрати альбом", - "select_album_cover": "Обрати обкладинку альбому", - "select_albums": "Вибрати альбоми", - "select_all": "Вибрати все", - "select_all_duplicates": "Вибрати всі дублікати", - "select_all_in": "Вибрати все в {group}", - "select_avatar_color": "Вибрати колір аватара", - "select_count": "{count, plural, one {Вибрати #} few {Вибрати #} many {Вибрати #} other {Вибрати #}}", - "select_cutoff_date": "Виберіть кінцеву дату", - "select_face": "Виберіть обличчя", - "select_featured_photo": "Обрати обране фото", - "select_from_computer": "Виберіть з комп'ютера", - "select_keep_all": "Залишити все обране", - "select_library_owner": "Вибрати власника бібліотеки", - "select_new_face": "Обрати нове обличчя", - "select_people": "Вибрати людей", - "select_person": "Виберіть особу", - "select_person_to_tag": "Виберіть людину для позначення", - "select_photos": "Вибрати фото", - "select_trash_all": "Видалити все вибране", - "select_user_for_sharing_page_err_album": "Не вдалося створити альбом", - "selected": "Обрано", - "selected_count": "{count, plural, one {# обраний} few {# обрані} many {# обраних} other {# обраних}}", - "selected_gps_coordinates": "Вибрані координати", - "send_message": "Надіслати повідомлення", - "send_welcome_email": "Надішліть вітальний лист", - "server_endpoint": "Адреса серверу", - "server_info_box_app_version": "Версія застосунку", - "server_info_box_server_url": "URL сервера", - "server_offline": "Сервер недоступний", - "server_online": "Сервер доступний", - "server_privacy": "Конфіденційність сервера", - "server_restarting_description": "Ця сторінка оновиться миттєво.", - "server_restarting_title": "Сервер перезавантажується", - "server_stats": "Статистика сервера", - "server_update_available": "Оновлення сервера доступне", - "server_version": "Версія сервера", - "set": "Встановити", - "set_as_album_cover": "Встановити як обкладинку альбому", - "set_as_featured_photo": "Встановити як основне фото", - "set_as_profile_picture": "Встановити як зображення профілю", - "set_date_of_birth": "Встановити дату народження", - "set_profile_picture": "Встановити зображення профілю", - "set_slideshow_to_fullscreen": "Встановити слайд-шоу на весь екран", - "set_stack_primary_asset": "Встановити як основний файл", - "setting_image_viewer_help": "Повноекранний переглядач спочатку завантажує зображення для попереднього перегляду в низькій роздільній здатності, потім завантажує зображення в зменшеній роздільній здатності відносно оригіналу (якщо включено) і зрештою завантажує оригінал (якщо включено).", - "setting_image_viewer_original_subtitle": "Увімкнути для завантаження оригінального зображення з повною роздільною здатністю (велике!). Вимкнути, щоб зменшити використання даних (як через мережу, так і на кеші пристрою).", - "setting_image_viewer_original_title": "Завантажувати оригінальне зображення", - "setting_image_viewer_preview_subtitle": "Увімкнути для завантаження зображення середньої роздільної здатності. Вимкнути, щоб завантажувати оригінал або використовувати тільки мініатюру.", - "setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду", - "setting_image_viewer_title": "Зображення", - "setting_languages_apply": "Застосувати", - "setting_languages_subtitle": "Змінити мову застосунку", - "setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {duration}", - "setting_notifications_notify_hours": "{count} годин", - "setting_notifications_notify_immediately": "негайно", - "setting_notifications_notify_minutes": "{count} хвилин", - "setting_notifications_notify_never": "ніколи", - "setting_notifications_notify_seconds": "{count} секунд", - "setting_notifications_single_progress_subtitle": "Детальна інформація про хід завантаження для кожного фото та відео", - "setting_notifications_single_progress_title": "Показати хід фонового резервного копіювання", - "setting_notifications_subtitle": "Налаштування параметрів сповіщень", - "setting_notifications_total_progress_subtitle": "Загальний прогрес (виконано/загалом)", - "setting_notifications_total_progress_title": "Показати загальний хід фонового резервного копіювання", - "setting_video_viewer_auto_play_subtitle": "Автоматично починати відтворення відео під час їх відкриття", - "setting_video_viewer_auto_play_title": "Автоматичне відтворення відео", - "setting_video_viewer_looping_title": "Циклічне відтворення", - "setting_video_viewer_original_video_subtitle": "При трансляції відео з сервера відтворювати оригінал, навіть якщо доступна транскодування. Може призвести до буферизації. Відео, доступні локально, відтворюються в оригінальній якості, незважаючи на це налаштування.", - "setting_video_viewer_original_video_title": "Примусово відтворювати оригінальне відео", - "settings": "Налаштування", - "settings_require_restart": "Перезавантажте застосунок для застосування цього налаштування", - "settings_saved": "Налаштування збережені", - "setup_pin_code": "Налаштувати PIN-код", - "share": "Поширити", - "share_action_prompt": "{count} фото та відео надіслано", - "share_add_photos": "Додати фото", - "share_assets_selected": "{count} обрано", - "share_dialog_preparing": "Підготовка...", - "share_link": "Поділитися посиланням", - "shared": "Спільні", - "shared_album_activities_input_disable": "Коментування вимкнено", - "shared_album_activity_remove_content": "Ви бажаєте видалити цю активність?", - "shared_album_activity_remove_title": "Видалити активність", - "shared_album_section_people_action_error": "Помилка виходу/видалення з альбому", - "shared_album_section_people_action_leave": "Видалити користувача з альбому", - "shared_album_section_people_action_remove_user": "Видалити користувача з альбому", - "shared_album_section_people_title": "ЛЮДИ", - "shared_by": "Поділився", - "shared_by_user": "Спільний доступ з {user}", - "shared_by_you": "Ви поділились", - "shared_from_partner": "Фото від {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Вивантажено", - "shared_link_app_bar_title": "Спільні посилання", - "shared_link_clipboard_copied_massage": "Скопійовано в буфер обміну", - "shared_link_clipboard_text": "Посилання: {link}\nПароль: {password}", - "shared_link_create_error": "Помилка під час створення спільного посилання", - "shared_link_custom_url_description": "Отримайте доступ до цього спільного посилання за власною URL-адресою", - "shared_link_edit_description_hint": "Введіть опис для спільного доступу", - "shared_link_edit_expire_after_option_day": "1 день", - "shared_link_edit_expire_after_option_days": "{count} днів", - "shared_link_edit_expire_after_option_hour": "1 годину", - "shared_link_edit_expire_after_option_hours": "{count} годин", - "shared_link_edit_expire_after_option_minute": "1 хвилину", - "shared_link_edit_expire_after_option_minutes": "{count} хвилин", - "shared_link_edit_expire_after_option_months": "{count} місяців", - "shared_link_edit_expire_after_option_year": "{count} років", - "shared_link_edit_password_hint": "Введіть пароль для спільного доступу", - "shared_link_edit_submit_button": "Оновити посилання", - "shared_link_error_server_url_fetch": "Неможливо запитати url із сервера", - "shared_link_expires_day": "Закінчується через {count} день", - "shared_link_expires_days": "Закінчується через {count} днів", - "shared_link_expires_hour": "Закінчується через {count} годину", - "shared_link_expires_hours": "Закінчується через {count} годин", - "shared_link_expires_minute": "Закінчується через {count} хвилину", - "shared_link_expires_minutes": "Закінчується через {count} хвилин", - "shared_link_expires_never": "Закінчується ∞", - "shared_link_expires_second": "Закінчується через {count} секунду", - "shared_link_expires_seconds": "Закінчується через {count} секунд", - "shared_link_individual_shared": "Індивідуальний спільний доступ", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Керування спільними посиланнями", - "shared_link_options": "Параметри спільних посилань", - "shared_link_password_description": "Вимагати пароль для доступу до цього спільного посилання", - "shared_links": "Спільні посилання", - "shared_links_description": "Діліться фото та відео за посиланням", - "shared_photos_and_videos_count": "{assetCount, plural, other {# спільні фотографії та відео.}}", - "shared_with_me": "Доступні мені", - "shared_with_partner": "Спільно з {partner}", - "sharing": "Спільні", - "sharing_enter_password": "Будь ласка, введіть пароль для перегляду цієї сторінки.", - "sharing_page_album": "Спільні альбоми", - "sharing_page_description": "Створюйте спільні альбоми, щоб ділитися фото та відео з людьми зі своєї мережі.", - "sharing_page_empty_list": "ПОРОЖНІЙ СПИСОК", - "sharing_sidebar_description": "Відображати посилання на спільний доступ у бічній панелі", - "sharing_silver_appbar_create_shared_album": "Створити спільний альбом", - "sharing_silver_appbar_share_partner": "Поділитися з партнером", - "shift_to_permanent_delete": "натисніть ⇧ щоб видалити файл назавжди", - "show_album_options": "Показати параметри альбому", - "show_albums": "Показувати альбоми", - "show_all_people": "Показати всіх людей", - "show_and_hide_people": "Показати та приховати людей", - "show_file_location": "Показати розташування файлу", - "show_gallery": "Показати галерею", - "show_hidden_people": "Показати прихованих людей", - "show_in_timeline": "Показати на часовій шкалі", - "show_in_timeline_setting_description": "Показуйте фото та відео цього користувача у своїй стрічці", - "show_keyboard_shortcuts": "Показати сполучення клавіш", - "show_metadata": "Показувати метадані", - "show_or_hide_info": "Показати або приховати інформацію", - "show_password": "Показати пароль", - "show_person_options": "Показати параметри людини", - "show_progress_bar": "Показати індикатор прогресу", - "show_schema": "Показати схему", - "show_search_options": "Показати параметри пошуку", - "show_shared_links": "Показати спільні посилання", - "show_slideshow_transition": "Показати перехід слайд-шоу", - "show_supporter_badge": "Значок підтримки", - "show_supporter_badge_description": "Показати значок підтримки", - "show_text_recognition": "Показати розпізнавання тексту", - "show_text_search_menu": "Показати меню текстового пошуку", - "shuffle": "Перемішати", - "sidebar": "Бічна панель", - "sidebar_display_description": "Відобразити посилання на перегляд у бічній панелі", - "sign_out": "Вихід", - "sign_up": "Зареєструватися", - "size": "Розмір", - "skip_to_content": "Перейти до вмісту", - "skip_to_folders": "Перейти до папок", - "skip_to_tags": "Перейти до тегів", - "slideshow": "Слайдшоу", - "slideshow_repeat": "Повторити слайд-шоу", - "slideshow_repeat_description": "Повернення до початку після завершення слайд-шоу", - "slideshow_settings": "Налаштування слайд-шоу", - "sort_albums_by": "Сортувати альбоми за...", - "sort_created": "Дата створення", - "sort_items": "Кількість файлів", - "sort_modified": "Дата зміни", - "sort_newest": "Найновіше фото", - "sort_oldest": "Старі фото", - "sort_people_by_similarity": "Сортувати людей за схожістю", - "sort_recent": "Нещодавні", - "sort_title": "Заголовок", - "source": "Джерело", - "stack": "У стопку", - "stack_action_prompt": "Згруповано: {count}", - "stack_duplicates": "Групувати дублікати", - "stack_select_one_photo": "Вибрати одне основне фото для групи", - "stack_selected_photos": "Згрупувати обрані фотографії", - "stacked_assets_count": "Згруповано {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "stacktrace": "Стек викликів", - "start": "Старт", - "start_date": "Дата початку", - "start_date_before_end_date": "Дата початку має бути раніше дати завершення", - "state": "Регіон", - "status": "Стан", - "stop_casting": "Зупинити трансляцію", - "stop_motion_photo": "Фото \"Стоп-моушен\"", - "stop_photo_sharing": "Зупинити спільний доступ до ваших фото?", - "stop_photo_sharing_description": "{partner} більше не матиме доступу до ваших фотографій.", - "stop_sharing_photos_with_user": "Припинити ділитися своїми фотографіями з цим користувачем", - "storage": "Сховище", - "storage_label": "Мітка для зберігання", - "storage_quota": "Обсяг сховища", - "storage_usage": "{used} з {available} використано", - "submit": "Підтвердити", - "success": "Успішно", - "suggestions": "Пропозиції", - "sunrise_on_the_beach": "Світанок на пляжі", - "support": "Підтримка", - "support_and_feedback": "Підтримка та зворотний зв'язок", - "support_third_party_description": "Вашу установку Immich було упаковано третьою стороною. Проблеми, з якими ви стикаєтесь, можуть бути викликані цим пакетом, тому спочатку зверніться до них за допомогою, використовуючи наведені нижче посилання.", - "swap_merge_direction": "Змінити напрямок об'єднання", - "sync": "Синхронізувати", - "sync_albums": "Синхронізувати альбоми", - "sync_albums_manual_subtitle": "Синхронізувати всі завантажені фото та відео у вибрані альбоми для резервного копіювання", - "sync_local": "Синхронізувати на пристрої", - "sync_remote": "Синхронізувати з сервером", - "sync_status": "Стан синхронізації", - "sync_status_subtitle": "Перегляд та керування системою синхронізації", - "sync_upload_album_setting_subtitle": "Створюйте та вивантажуйте свої фотографії та відео до вибраних альбомів на сервер Immich", - "tag": "Тег", - "tag_assets": "Додати теги", - "tag_created": "Створено тег: {tag}", - "tag_feature_description": "Перегляд фотографій та відео, згрупованих за логічними темами тегів", - "tag_not_found_question": "Не вдається знайти тег? Створити новий тег.", - "tag_people": "Тег людей", - "tag_updated": "Оновлено тег: {tag}", - "tagged_assets": "Позначено тегом {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "tags": "Теги", - "tap_to_run_job": "Торкніться, щоб запустити завдання", - "template": "Шаблон", - "text_recognition": "Розпізнавання тексту", - "theme": "Тема", - "theme_selection": "Вибір теми", - "theme_selection_description": "Автоматично встановлювати тему на світлу або темну залежно від системних налаштувань вашого браузера", - "theme_setting_asset_list_storage_indicator_title": "Показувати піктограму сховища на плитках файлів", - "theme_setting_asset_list_tiles_per_row_title": "Кількість файлів у рядку ({count})", - "theme_setting_colorful_interface_subtitle": "Застосувати основний колір на поверхню фону.", - "theme_setting_colorful_interface_title": "Барвистий інтерфейс", - "theme_setting_image_viewer_quality_subtitle": "Налаштування якості перегляду повноекранних зображень", - "theme_setting_image_viewer_quality_title": "Якість перегляду зображень", - "theme_setting_primary_color_subtitle": "Виберіть колір для основних дій і акцентів.", - "theme_setting_primary_color_title": "Основний колір", - "theme_setting_system_primary_color_title": "Використовувати колір системи", - "theme_setting_system_theme_switch": "Автоматично (як у системі)", - "theme_setting_theme_subtitle": "Налаштування теми застосунку", - "theme_setting_three_stage_loading_subtitle": "Триетапне завантаження може підвищити продуктивність завантаження, але спричинить значно більше навантаження на мережу", - "theme_setting_three_stage_loading_title": "Увімкнути триетапне завантаження", - "then": "Тоді", - "they_will_be_merged_together": "Вони будуть об'єднані разом", - "third_party_resources": "Сторонні ресурси", - "time": "Час", - "time_based_memories": "Спогади, що базуються на часі", - "time_based_memories_duration": "Кількість секунд для відображення кожного зображення.", - "timeline": "Хронологія", - "timezone": "Часовий пояс", - "to_archive": "Архів", - "to_change_password": "Змінити пароль", - "to_favorite": "Обране", - "to_login": "Вхід", - "to_multi_select": "для множинного вибору", - "to_parent": "Повернутись назад", - "to_select": "вибрати", - "to_trash": "Кошик", - "toggle_settings": "Перемикання налаштувань", - "toggle_theme_description": "Перемкнути тему", - "total": "Усього", - "total_usage": "Загальне використання", - "trash": "Кошик", - "trash_action_prompt": "{count} переміщено до кошика", - "trash_all": "Видалити все", - "trash_count": "Видалити {count, number}", - "trash_delete_asset": "У Кошик/Видалити файл", - "trash_emptied": "Кошик очищено", - "trash_no_results_message": "Тут з'являтимуться видалені фото та відео.", - "trash_page_delete_all": "Видалити усе", - "trash_page_empty_trash_dialog_content": "Ви хочете очистити кошик? Ці файли будуть остаточно видалені з Immich", - "trash_page_info": "Переміщені до кошика файли буде остаточно видалено через {days} днів", - "trash_page_no_assets": "Видалені фото та відео відсутні", - "trash_page_restore_all": "Відновити усе", - "trash_page_select_assets_btn": "Вибрати файли", - "trash_page_title": "Кошик ({count})", - "trashed_items_will_be_permanently_deleted_after": "Видалені файли будуть остаточно видалені через {days, plural, one {# день} few {# дні} many {# днів} other {# днів}}.", - "trigger": "Тригер", - "trigger_asset_uploaded": "Файл додано", - "trigger_asset_uploaded_description": "Запускається під час вивантаження нового файлу", - "trigger_description": "Подія, яка запускає автоматизацію", - "trigger_person_recognized": "Особа розпізнана", - "trigger_person_recognized_description": "Спрацьовує, коли виявляється людина", - "trigger_type": "Тип тригера", - "troubleshoot": "Виправлення неполадок", - "type": "Тип", - "unable_to_change_pin_code": "Неможливо змінити PIN-код", - "unable_to_check_version": "Не вдається перевірити версію застосунку або сервера", - "unable_to_setup_pin_code": "Неможливо налаштувати PIN-код", - "unarchive": "Розархівувати", - "unarchive_action_prompt": "{count, plural, one {# файл вилучено з архіву} few {# файли вилучено з архіву} other {# файлів вилучено з архіву}}", - "unarchived_count": "{count, plural, other {Повернуто з архіву #}}", - "undo": "Скасувати", - "unfavorite": "Видалити з обраного", - "unfavorite_action_prompt": "{count} вилучено з обраного", - "unhide_person": "Розкрити особу", - "unknown": "Невідомо", - "unknown_country": "Невідома країна", - "unknown_date": "Невідома дата", - "unknown_year": "Невідомий рік", - "unlimited": "Без обмежень", - "unlink_motion_video": "Від'єднати рухоме відео", - "unlink_oauth": "Від'єднати OAuth", - "unlinked_oauth_account": "Від'єднаний обліковий запис OAuth", - "unmute_memories": "Увімкнути звук спогадів", - "unnamed_album": "Альбом без назви", - "unnamed_album_delete_confirmation": "Ви впевнені, що бажаєте видалити цей альбом?", - "unnamed_share": "Спільний доступ без назви", - "unsaved_change": "Незбережена зміна", - "unselect_all": "Зняти все", - "unselect_all_duplicates": "Скасувати вибір усіх дублікатів", - "unselect_all_in": "Зняти вибір у всьому {group}", - "unstack": "Розібрати стек", - "unstack_action_prompt": "{count} роз’єднано", - "unstacked_assets_count": "Розгорнути {count, plural, one {# файл} few {# файли} many {# файлів} other {# файлів}}", - "unsupported_field_type": "Непідтримуваний тип поля", - "untagged": "Без тегів", - "untitled_workflow": "Безіменний робочий процес", - "up_next": "Наступне", - "update_location_action_prompt": "Оновити розташування вибраних об’єктів ({count}) за допомогою:", - "updated_at": "Оновлено", - "updated_password": "Пароль оновлено", - "upload": "Вивантажити", - "upload_concurrency": "Паралельність вивантаження", - "upload_details": "Деталі вивантаження", - "upload_dialog_info": "Бажаєте створити резервну копію вибраних файлів на сервері?", - "upload_dialog_title": "Вивантажити файли", - "upload_error_with_count": "Помилка вивантаження для {count, plural, one {# файлу} few {# файлів} many {# файлів} other {# файлів}}", - "upload_errors": "Вивантаження завершено з {count, plural, one {# помилкою} few {# помилками} many {# помилками} other {# помилками}}, оновіть сторінку, щоб побачити нові вивантажені файли.", - "upload_finished": "Вивантаження завершено", - "upload_progress": "Залишилось {remaining, number} - Опрацьовано {processed, number}/{total, number}", - "upload_skipped_duplicates": "Пропущено {count, plural, one {# дубльований файл} few {# дубльовані файли} many {# дубльованих файлів} other {# дубльованих файлів}}", - "upload_status_duplicates": "Дублікати", - "upload_status_errors": "Помилки", - "upload_status_uploaded": "Вивантажено", - "upload_success": "Вивантаження успішне. Оновіть сторінку, щоб побачити нові вивантажені файли.", - "upload_to_immich": "Вивантажити в Immich ({count})", - "uploading": "Вивантаження", - "uploading_media": "Виконується вивантаження", - "url": "URL", - "usage": "Використання", - "use_biometric": "Використовувати біометрію", - "use_current_connection": "Використати поточне з'єднання", - "use_custom_date_range": "Використовувати користувацький діапазон дат", - "user": "Користувач", - "user_has_been_deleted": "Користувача видалено.", - "user_id": "ID Користувача", - "user_liked": "{user} вподобав {type, select, photo {це фото} video {це відео} asset {цей файл} other {це}}", - "user_pin_code_settings": "PIN-код", - "user_pin_code_settings_description": "Керування PIN-кодом", - "user_privacy": "Конфіденційність користувача", - "user_purchase_settings": "Придбати", - "user_purchase_settings_description": "Керувати вашою покупкою", - "user_role_set": "Призначити {user} на роль {role}", - "user_usage_detail": "Деталі використання користувача", - "user_usage_stats": "Статистика використання облікового запису", - "user_usage_stats_description": "Переглянути статистику використання облікового запису", - "username": "Ім'я користувача", - "users": "Користувачі", - "users_added_to_album_count": "{count, plural, one {# користувача} few {# користувачі} many {# користувачів} other {# користувачів}} додано до альбому", - "utilities": "Утиліти", - "validate": "Перевірити", - "validate_endpoint_error": "Будь ласка, введіть дійсну URL-адресу", - "validation_error": "Помилка перевірки", - "variables": "Змінні", - "version": "Версія", - "version_announcement_closing": "Твій друг, Алекс", - "version_announcement_message": "Привіт! Доступна нова версія Immich. Будь ласка, приділіть трохи часу для ознайомлення з примітками до випуску, щоб переконатися, що ваша установка оновлена і уникнути можливих помилок у налаштуваннях, особливо якщо ви використовуєте WatchTower або будь-який інший механізм, який автоматично оновлює ваш екземпляр Immich.", - "version_history": "Історія версій", - "version_history_item": "Встановлено {version} {date}", - "video": "Відео", - "video_hover_setting": "Відтворення мініатюри відео під час наведення курсору миші", - "video_hover_setting_description": "Відтворювати зображення відео при наведенні курсора на файл. Навіть якщо вимкнено, відтворення може бути запущено, навівши курсор на піктограму відтворення.", - "videos": "Відео", - "videos_count": "{count, plural, one {# Відео} few {# Відео} many {# Відео} other {# Відео}}", - "videos_only": "Тільки відео", - "view": "Перегляд", - "view_album": "Переглянути альбом", - "view_all": "Переглянути усі", - "view_all_users": "Переглянути всіх користувачів", - "view_asset_owners": "Переглянути власників файлів", - "view_details": "Детальніше", - "view_in_timeline": "Переглянути в хронології", - "view_link": "Переглянути посилання", - "view_links": "Переглянути посилання", - "view_name": "Переглянути", - "view_next_asset": "Переглянути наступний файл", - "view_previous_asset": "Переглянути попередній файл", - "view_qr_code": "Переглянути QR-код", - "view_similar_photos": "Переглянути схожі фотографії", - "view_stack": "Перегляд стеку", - "view_user": "Переглянути користувача", - "viewer_remove_from_stack": "Видалити зі стеку", - "viewer_stack_use_as_main_asset": "Використовувати як основний файл", - "viewer_unstack": "Розібрати стек", - "visibility_changed": "Видимість змінено для {count, plural, one {# особи} few {# осіб} many {# осіб} other {# осіб}}", - "visual": "Візуальний", - "visual_builder": "Візуальний конструктор", - "waiting": "У черзі", - "waiting_count": "Очікують: {count}", - "warning": "Попередження", - "week": "Тиждень", - "welcome": "Ласкаво просимо", - "welcome_to_immich": "Ласкаво просимо до Immich", - "width": "Ширина", - "wifi_name": "Назва Wi-Fi", - "workflow_delete_prompt": "Ви впевнені, що хочете видалити цей робочий процес?", - "workflow_deleted": "Робочий процес видалено", - "workflow_description": "Опис робочого процесу", - "workflow_info": "Інформація про робочий процес", - "workflow_json": "Робочий процес JSON", - "workflow_json_help": "Відредагуйте конфігурацію робочого процесу у форматі JSON. Зміни будуть синхронізовані з візуальним конструктором.", - "workflow_name": "Назва робочого процесу", - "workflow_navigation_prompt": "Ви впевнені, що хочете вийти без збереження змін?", - "workflow_summary": "Зведення робочого процесу", - "workflow_update_success": "Робочий процес успішно оновлено", - "workflow_updated": "Робочий процес оновлено", - "workflows": "Робочі процеси", - "workflows_help_text": "Автоматизації виконують дії з файлами залежно від тригерів і умов", - "wrong_pin_code": "Неправильний PIN-код", - "year": "Рік", - "years_ago": "{years, plural, one {# рік} few {# роки} many {# років} other {# років}} тому", - "yes": "Так", - "you_dont_have_any_shared_links": "У вас немає спільних посилань", - "your_wifi_name": "Назва вашої Wi-Fi мережі", - "zero_to_clear_rating": "натисніть 0, щоб очистити рейтинг файлу", - "zoom_image": "Збільшити зображення", - "zoom_to_bounds": "Збільшити масштаб до меж" -} +{} diff --git a/i18n/ur.json b/i18n/ur.json index 5329f74c5c..0967ef424b 100644 --- a/i18n/ur.json +++ b/i18n/ur.json @@ -1,118 +1 @@ -{ - "about": "متعلق", - "account": "اکاؤنٹ", - "account_settings": "اکاؤنٹ کی ترتیبات", - "acknowledge": "تسلیم کرنا", - "action": "عمل", - "action_common_update": "اپڈیٹ کریں", - "action_description": "فلٹر شدہ اثاثوں پر انجام دینے کے لیے کارروائی کا ایک مجموعہ", - "actions": "اعمال", - "active": "فعال", - "active_count": "فعال: {count}", - "activity": "سرگرمی", - "activity_changed": "سرگرمی {enabled, select, true {فعال ہے} other {غیر فعال ہے}}", - "add": "شامل کریں", - "add_a_description": "تفصیل شامل کریں", - "add_a_location": "مقام شامل کریں", - "add_a_name": "نام کا اندراج کریں", - "add_a_title": "عنوان کا اندراج کریں", - "add_action": "عمل شامل کریں", - "add_action_description": "عمل شامل کرنے کے لیے یہاں کلک کریں", - "add_assets": "اثاثے شامل کریں", - "add_birthday": "سالگرہ شامل کریں", - "add_endpoint": "اینڈ پوائنٹ درج کریں", - "add_exclusion_pattern": "خارج کرنے کا نمونہ شامل کریں", - "add_filter": "فلٹر شامل کریں", - "add_filter_description": "فلٹر کی شرط شامل کرنے کے لیے کلک کریں", - "add_location": "جگہ درج کریں", - "add_more_users": "مزید صارفین شامل کریں", - "add_partner": "ساتھی شامل کریں", - "add_path": "راستہ شامل کریں", - "add_photos": "تصاویر شامل کریں", - "add_tag": "ٹیگ شامل کریں", - "add_to": "اس میں شامل کریں…", - "add_to_album": "البم میں شامل کریں", - "add_to_album_bottom_sheet_added": "{album} میں شامل کردیاگیا", - "add_to_album_bottom_sheet_already_exists": "پہلے سے ہی {album} میں موجود ہے", - "add_to_album_bottom_sheet_some_local_assets": "کچھ مقامی اثاثے البم میں شامل نہیں کیے جا سکے", - "add_to_album_toggle": "منتخب کرنے کا طریقہ {album} کے لیے", - "add_to_albums": "البموں میں شامل کیجیے", - "add_to_albums_count": "البموں میں شامل کیجیے ({count})", - "add_to_bottom_bar": "اس میں شامل کریں", - "add_to_shared_album": "مشترکہ البم میں شامل کریں", - "add_upload_to_stack": "اپ لوڈ کو اسٹیک میں شامل کریں", - "add_url": "URL شامل کریں", - "add_workflow_step": "ورک فلو کا مرحلہ شامل کریں", - "added_to_archive": "آرکائیو میں شامل کر دیا گیا", - "added_to_favorites": "پسندیدہ میں شامل کردیا گیا", - "added_to_favorites_count": "پسندیدہ میں {count, number} شامل کیے گئے", - "admin": { - "add_exclusion_pattern_description": "اخراج کے نمونے شامل کریں۔ *، **، اور ? کا استعمال کرتے ہوئے Globbing کا استعمال کیا جا سکتا ہے۔ \"RAW\" نامی کسی بھی ڈائریکٹری میں تمام فائلوں کو نظر انداز کرنے کے لیے، \"**/Raw/**\" استعمال کریں۔ \".tif\" سے ختم ہونے والی تمام فائلوں کو نظر انداز کرنے کے لیے، \"**/*.tif\" استعمال کریں۔ کسی مطلق راستے کو نظر انداز کرنے کے لیے، \"/path/to/ignore/**\" کا استعمال کریں۔", - "admin_user": "ایڈمن صارف", - "asset_offline_description": "لائبریری کا یہ بیرونی اثاثہ اب ڈسک پر نہیں ملا اور اسے کوڑے دان میں ڈال دیا گیا ہے۔ اگر فائل لائبریری کے اندر منتقل کی گئی تھی، تو نئے متعلقہ اثاثے کے لیے اپنی ٹائم لائن چیک کریں۔ اس اثاثے کو بحال کرنے کے لیے، براہ کرم یقینی بنائیں کہ نیچے دیے گئے فائل کے راستے تک Immich کو رسائی حاصل ہے اور لائبریری کو اسکین کریں۔", - "authentication_settings": "تصدیق کی ترتیبات", - "authentication_settings_description": "پاس ورڈ، OAuth، اور دیگر تصدیقی ترتیبات کا نظم کریں", - "authentication_settings_disable_all": "کیا آپ واقعی لاگ ان کے تمام طریقوں کو غیر فعال کرنا چاہتے ہیں؟ لاگ ان مکمل طور پر غیر فعال ہو جائے گا۔", - "authentication_settings_reenable": "دوبارہ فعال کرنے کے لیے، ایک سرور کمانڈ استعمال کریں.", - "background_task_job": "پس منظر کے کام", - "backup_database": "ڈیٹا بیس کا بیک اپ بنائیں", - "backup_database_enable_description": "ڈیٹا بیس کے بیک اپ کو فعال کریں", - "backup_keep_last_amount": "پرانے بیک اپ کو رکھنے کی مقدار", - "backup_onboarding_1_description": "آف سائٹ کاپی کلاؤڈ میں یا کسی اور مقام پر۔", - "backup_onboarding_2_description": "مختلف آلات پر مقامی کاپیاں۔ اس میں بنیادی فائلیں اور مقامی طور پر ان فائلوں کا بیک اپ شامل ہے۔", - "backup_onboarding_3_description": "اصل فائلوں سمیت آپ کے ڈیٹا کی کل کاپیاں۔ اس میں 1 آف سائٹ کاپی اور 2 مقامی کاپیاں شامل ہیں۔", - "backup_onboarding_parts_title": "ایک 3-2-1 بیک اپ میں شامل ہے:", - "backup_onboarding_title": "بیک اپس", - "backup_settings": "ڈیٹا بیس ڈمپ کی ترتیبات", - "backup_settings_description": "ڈیٹا بیس ڈمپ کی ترتیبات کا نظم کریں.", - "cleared_jobs": "ملازمتیں اس کے لیے صاف کی گئیں: {job}", - "config_set_by_file": "Config فی الحال ایک config فائل کے ذریعہ ترتیب دی گئی ہے", - "confirm_delete_library": "کیا آپ واقعی {library} لائبریری کو حذف کرنا چاہتے ہیں؟", - "confirm_delete_library_assets": "کیا آپ واقعی اس لائبریری کو حذف کرنا چاہتے ہیں؟ یہ Immich سے {count, plural, one {# contained asset} دیگر {all # contained assets}} کو حذف کر دے گا اور اسے کالعدم نہیں کیا جا سکتا۔ فائلیں ڈسک پر موجود رہیں گی۔", - "confirm_email_below": "تصدیق کرنے کے لیے، نیچے ای میل ٹائپ کریں {email}", - "confirm_reprocess_all_faces": "کیا آپ واقعی تمام چہروں کو دوبارہ پروسیس کرنا چاہتے ہیں؟ اس سے نام والے افراد بھی صاف ہو جائیں گے۔", - "confirm_user_password_reset": "کیا آپ {user} کا پاس ورڈ ری سیٹ کرنا چاہتے ہیں؟", - "confirm_user_pin_code_reset": "کیا آپ {user} کا پن کوڈ ری سیٹ کرنا چاہتے ہیں؟", - "create_job": "کام بنائیں", - "face_detection": "چہرے کی پہچان", - "failed_job_command": "کام: {job} کے لیے کمانڈ: {command} ناکام ہو گئی", - "image_preview_title": "پیش نظارہ", - "image_quality": "معیار", - "image_settings": "تصویر کی ترتیبات" - }, - "change_pin_code": "پن کوڈ تبدیل کریں", - "confirm_new_pin_code": "نئے پن کوڈ کی تصدیق کریں", - "crop_aspect_ratio_fixed": "مقررہ", - "crop_aspect_ratio_free": "آزاد", - "crop_aspect_ratio_original": "اصل", - "current_pin_code": "موجودہ پن کوڈ", - "custom_date": "اپنی تاریخ", - "download_original": "صل ڈاؤن لوڈ کریں", - "errors_text": "غلطیاں", - "free_up_space": "جگہ خالی کریں", - "keep_favorites": "پسندیدہ رکھیں", - "new_pin_code": "نیا پن کوڈ", - "photos_only": "صرف تصاویر", - "pin_code_changed_successfully": "پن کوڈ کو کامیابی سے تبدیل کر دیا گیا", - "pin_code_reset_successfully": "پن کوڈ کامیابی کے ساتھ ری سیٹ ہو گیا", - "pin_code_setup_successfully": "پن کوڈ کامیابی کے ساتھ سیٹ اپ ہو گیا", - "reset_pin_code": "پن کوڈ دوبارہ ترتیب دیں", - "setup_pin_code": "ایک نیا پن کوڈ ترتیب دیں", - "sunrise_on_the_beach": "ساحل سمندر پر طلوع آفتاب", - "unable_to_change_pin_code": "پن کوڈ تبدیل کرنے سے قاصر", - "unable_to_setup_pin_code": "پن کوڈ ترتیب کرنے سے قاصر", - "user_pin_code_settings": "پن کوڈ", - "user_pin_code_settings_description": "اپنے پن کوڈ کا نظم کریں", - "user_purchase_settings": "خریداری", - "user_purchase_settings_description": "اپنی خریداری کا انتظام کریں", - "version_announcement_closing": "آپ کا دوست، ایلکس", - "video": "ویڈیو", - "videos": "ویڈیوز", - "videos_only": "صرف ویڈیوز", - "view": "دیکھیں", - "view_all": "سب دیکھیں", - "waiting": "انتظار", - "week": "ہفتہ", - "year": "سال", - "zoom_image": "زوم تصویر" -} +{} diff --git a/i18n/vi.json b/i18n/vi.json index ec1b497449..0967ef424b 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -1,2303 +1 @@ -{ - "about": "Giới thiệu", - "account": "Tài khoản", - "account_settings": "Cài đặt tài khoản", - "acknowledge": "Ghi nhận", - "action": "Hành động", - "action_common_update": "Cập nhật", - "action_description": "Một tập hợp các hành động cần thực hiện trên các tệp đã được lọc", - "actions": "Hành động", - "active": "Đang hoạt động", - "active_count": "Hoạt động: {count}", - "activity": "Hoạt động", - "activity_changed": "Hoạt động đã {enabled, select, true {bật} other {tắt}}", - "add": "Thêm", - "add_a_description": "Thêm mô tả", - "add_a_location": "Thêm địa điểm", - "add_a_name": "Thêm tên", - "add_a_title": "Thêm tên", - "add_action": "Thêm hành động", - "add_action_description": "Nhấn để thêm hành động cần thực hiện", - "add_birthday": "Thêm sinh nhật", - "add_endpoint": "Thêm endpoint", - "add_exclusion_pattern": "Thêm quy tắc loại trừ", - "add_filter": "Thêm bộ lọc", - "add_filter_description": "Nhấn để thêm điều kiện lọc", - "add_location": "Thêm địa điểm", - "add_more_users": "Thêm người dùng", - "add_partner": "Thêm người thân", - "add_path": "Thêm đường dẫn", - "add_photos": "Thêm ảnh", - "add_tag": "Thêm thẻ", - "add_to": "Thêm vào…", - "add_to_album": "Thêm vào album", - "add_to_album_bottom_sheet_added": "Đã thêm vào {album}", - "add_to_album_bottom_sheet_already_exists": "Đã có sẵn trong {album}", - "add_to_album_bottom_sheet_some_local_assets": "Một số tệp trên thiết bị không thể được thêm vào album", - "add_to_album_toggle": "Bật tắt tùy chọn cho {album}", - "add_to_albums": "Thêm vào album", - "add_to_albums_count": "Đã thêm vào album {count}", - "add_to_bottom_bar": "Thêm vào", - "add_to_shared_album": "Thêm vào album chia sẻ", - "add_upload_to_stack": "Tải lên thêm vào nhóm", - "add_url": "Thêm URL", - "add_workflow_step": "Thêm bước workflow", - "added_to_archive": "Đã lưu trữ", - "added_to_favorites": "Đã thích", - "added_to_favorites_count": "Đã thích {count, number} mục", - "admin": { - "add_exclusion_pattern_description": "Thêm quy tắc loại trừ. Hỗ trợ sử dụng ký tự *, **, và ?. Để bỏ qua bất kỳ tệp trong thư mục tên \"Raw\", hãy dùng \"**/Raw/**\". Để bỏ qua các tệp có đuôi \".tif\", hãy dùng \"**/*.tif\". Để bỏ qua một đường dẫn cố định, hãy dùng \"/path/to/ignore/**\".", - "admin_user": "Quản trị viên", - "asset_offline_description": "Tệp thư viện bên ngoài này không còn trên ổ đĩa và đã bị chuyển vào thùng rác. Nếu tệp đã bị di chuyển trong thư viện, kiểm tra dòng thời gian của bạn để tìm ảnh mới tương ứng. Để khôi phục, hãy đảm bảo Immich có thể truy cập đường dẫn tệp bên dưới và quét lại thư viện.", - "authentication_settings": "Xác thực", - "authentication_settings_description": "Quản lý mật khẩu, OAuth và các cài đặt xác thực khác", - "authentication_settings_disable_all": "Bạn có chắc muốn vô hiệu hóa mọi phương thức đăng nhập? Đăng nhập sẽ bị vô hiệu hóa hoàn toàn.", - "authentication_settings_reenable": "Để bật lại, dùng Lệnh Máy chủ.", - "background_task_job": "Các tác vụ nền", - "backup_database": "Tạo bản sao lưu CSDL", - "backup_database_enable_description": "Bật sao lưu cơ sở dữ liệu", - "backup_keep_last_amount": "Số lượng các bản sao lưu CSDL trước đó được giữ lại", - "backup_onboarding_1_description": "sao chép ngoài trang web trên đám mây hoặc tại một vị trí vật lý khác.", - "backup_onboarding_2_description": "bản sao cục bộ trên các thiết bị khác nhau. Bao gồm các tệp chính và bản sao lưu cục bộ của các tệp đó.", - "backup_onboarding_3_description": "tổng số bản sao dữ liệu của bạn, bao gồm cả tệp gốc. Bao gồm 1 bản sao lưu trữ ngoài và 2 bản sao lưu trữ cục bộ.", - "backup_onboarding_description": "Chiến thuật sao lưu 3-2-1 được đề xuất để bảo vệ dữ liệu của bạn. Bạn nên lưu giữ bản sao ảnh/video đã tải lên cũng như cơ sở dữ liệu Immich để có giải pháp sao lưu toàn diện.", - "backup_onboarding_footer": "Để biết thêm thông tin về sao lưu Immich, hãy xem hướng dẫn.", - "backup_onboarding_parts_title": "Phương pháp sao lưu 3-2-1 bao gồm:", - "backup_onboarding_title": "Sao lưu", - "backup_settings": "Cài đặt sao lưu cơ sở dữ liệu", - "backup_settings_description": "Quản lý cài đặt sao lưu CSDL.", - "cleared_jobs": "Đã xóa các tác vụ: {job}", - "config_set_by_file": "Cấu hình hiện tại đang được đặt bởi một tệp cấu hình", - "confirm_delete_library": "Bạn có chắc muốn xóa thư viện {library}?", - "confirm_delete_library_assets": "Bạn có chắc muốn xóa thư viện này? Thao tác này sẽ xóa {count, plural, one {# tệp} other {tất cả # tệp}} khỏi Immich và không thể hoàn tác. Các tệp sẽ vẫn còn trên ổ đĩa.", - "confirm_email_below": "Để xác nhận, nhập \"{email}\" bên dưới", - "confirm_reprocess_all_faces": "Bạn có chắc muốn xử lý lại tất cả khuôn mặt? Thao tác này sẽ xóa tên người đã được gán.", - "confirm_user_password_reset": "Bạn có chắc muốn đặt lại mật khẩu của {user}?", - "confirm_user_pin_code_reset": "Bạn có chắc muốn đặt lại mã PIN của {user}?", - "copy_config_to_clipboard_description": "Sao chép cấu hình hệ thống hiện tại dưới dạng đối tượng JSON vào bộ nhớ tạm", - "create_job": "Tạo tác vụ", - "cron_expression": "Biểu thức Cron", - "cron_expression_description": "Thiết lập khoảng thời gian để quét bằng biểu thức cron. Tham khảo Crontab Guru để biết thêm thông tin", - "cron_expression_presets": "Mẫu biểu thức Cron", - "disable_login": "Vô hiệu hóa đăng nhập", - "duplicate_detection_job_description": "Chạy học máy để phát hiện các hình ảnh giống nhau. Dựa vào Tìm kiếm Thông Minh", - "exclusion_pattern_description": "Quy tắc loại trừ dùng để bỏ qua các tệp và thư mục khi quét thư viện của bạn. Điều này hữu ích nếu bạn có các thư mục chứa tệp bạn không muốn nhập, chẳng hạn như các tệp RAW.", - "export_config_as_json_description": "Tải xuống cấu hình hệ thống hiện tại dưới dạng tệp JSON", - "external_libraries_page_description": "Trang thư viện bên ngoài của quản trị viên", - "face_detection": "Nhận diện khuôn mặt", - "face_detection_description": "Sử dụng học máy để nhận diện khuôn mặt trong ảnh. Đối với video, sẽ sử dụng ảnh thu nhỏ. \"Làm mới\" sẽ xử lý lại tất cả tệp. \"Đặt lại\" sẽ xóa hết tất cả dữ liệu khuôn mặt và nhận dạng lại. \"Còn thiếu\" sẽ xử lý các ảnh còn thiếu. Các khuôn mặt được phát hiện sẽ được xử lý bởi tác vụ Nhận diện khuôn mặt để nhóm chúng vào những người đã có hoặc người mới.", - "facial_recognition_job_description": "Xếp nhóm những khuôn mặt đã nhận diện thành người. Bước này được thực hiện sau khi tác vụ Nhận diện khuôn mặt hoàn tất. \"Đặt lại\" sẽ xếp nhóm lại tất cả khuôn mặt. \"Còn thiếu\" sẽ xử lý các khuôn mặt chưa gán với người nào.", - "failed_job_command": "Lệnh {command} không thực hiện được tác vụ: {job}", - "force_delete_user_warning": "CẢNH BÁO: Thao tác này sẽ ngay lập tức xóa người dùng và tất cả ảnh. Điều không thể hoàn tác và các tệp không thể khôi phục.", - "image_format": "Định dạng", - "image_format_description": "Định dạng WebP dung lượng nhỏ hơn JPEG, nhưng mã hóa chậm hơn.", - "image_fullsize_description": "Ảnh kích thước đẩy đủ với thông tin metadata bị loại bỏ, được dùng khi phóng to", - "image_fullsize_enabled": "Cho phép sinh ảnh đầy đủ kích thước", - "image_fullsize_enabled_description": "Sinh ảnh đầy đủ kích thước cho các định dạng không thân thiện với web. Khi tùy chọn \"Ưu tiên xem trước dạng nhúng\" được bật, việc xem trước dạng nhúng dược sử dụng trực tiếp mà không thông việc chuyển đổi định dạng. Không ảnh hưởng đến các định dạng thân thiện với web như JPEG.", - "image_fullsize_quality_description": "Chất lượng ảnh kích thước đầy đủ từ 1-100. Số lớn hơn là ảnh chất lượng tốt hơn, nhưng sinh ra tệp có kích thước lớn hơn.", - "image_fullsize_title": "Cài đặt ảnh kích thước đầy đủ", - "image_prefer_embedded_preview": "Ưu tiên ảnh xem trước dạng nhúng", - "image_prefer_embedded_preview_setting_description": "Dùng ảnh xem trước trong ảnh RAW khi có sẵn để xử lý hình ảnh. Điều này có thể giúp tái tạo màu sắc chính xác hơn cho một số ảnh, nhưng chất lượng của ảnh xem trước phụ thuộc vào máy ảnh và có thể bị nhiễu do nén.", - "image_prefer_wide_gamut": "Ưu tiên gam màu mở rộng", - "image_prefer_wide_gamut_setting_description": "Dùng gam màu Display P3 để hiển thị ảnh thu nhỏ. Điều này giúp giữ màu sắc rực rỡ của những hình ảnh có gam màu rộng, nhưng ảnh có thể trông khác trên các thiết bị và trình duyệt cũ. Ảnh sRGB được giữ nguyên để tránh thay đổi màu sắc.", - "image_preview_description": "Ảnh kích thước trung bình đã loại bỏ metadata, được sử dụng khi xem một tệp duy nhất và cho học máy", - "image_preview_quality_description": "Chất lượng xem trước từ 1-100. Càng cao càng tốt, nhưng sẽ tạo ra các tệp lớn có thể làm giảm khả năng phản hồi của app. Sử dụng giá trị thấp có thể ảnh hưởng đến chất lượng tác vụ học máy.", - "image_preview_title": "Cài đặt Xem trước", - "image_quality": "Chất lượng", - "image_resolution": "Độ phân giải", - "image_resolution_description": "Độ phân giải cao hơn sẽ rõ nét hơn nhưng tốn nhiều thời gian để mã hóa, kích cỡ tệp lớn hơn và có thể làm giảm khả năng phản hồi của app.", - "image_settings": "Ảnh", - "image_settings_description": "Quản lý chất lượng và độ phân giải của ảnh được tạo", - "image_thumbnail_description": "Ảnh thu nhỏ kích thước nhỏ đã loại bỏ metadata, dùng khi xem nhiều ảnh cùng lúc, ví dụ như Dòng thời gian", - "image_thumbnail_quality_description": "Chất lượng ảnh thu nhỏ từ 1-100. Càng cao càng tốt, nhưng sẽ tạo ra các tệp lớn hơn có thể làm giảm khả năng phản hồi của app.", - "image_thumbnail_title": "Cài đặt Ảnh thu nhỏ", - "import_config_from_json_description": "Nhập cấu hình hệ thống bằng cách tải lên tệp cấu hình JSON", - "job_concurrency": "{job} chạy đồng thời", - "job_created": "Tác vụ đã được tạo", - "job_not_concurrency_safe": "Tác vụ này không an toàn để chạy đồng thời.", - "job_settings": "Tác vụ", - "job_settings_description": "Quản lý số lượng tác vụ chạy đồng thời", - "jobs_delayed": "{jobCount, plural, other {# bị hoãn lại}}", - "jobs_failed": "{jobCount, plural, other {# thất bại}}", - "jobs_over_time": "Tác vụ quá thời gian", - "library_created": "Đã tạo thư viện: {library}", - "library_deleted": "Thư viện đã bị xóa", - "library_details": "Chi tiết thư viện", - "library_folder_description": "Chọn thư mục để nhập. Thư mục này và cả các thư mục con sẽ được quét tìm ảnh và video.", - "library_remove_exclusion_pattern_prompt": "Bạn có chắc muốn xóa mẫu loại trừ này?", - "library_remove_folder_prompt": "Bạn có chắc muốn gỡ thư mục nhập này?", - "library_scanning": "Quét định kỳ", - "library_scanning_description": "Cấu hình quét thư viện định kỳ", - "library_scanning_enable_description": "Bật quét thư viện định kỳ", - "library_settings": "Thư viện bên ngoài", - "library_settings_description": "Quản lý cài đặt thư viện bên ngoài", - "library_tasks_description": "Quét thư viện ngoài để tìm ảnh mới thêm hoặc bị thay đổi", - "library_updated": "Đã cập nhật thư viện", - "library_watching_enable_description": "Tự động cập nhật các tệp bị thay đổi trong thư viện bên ngoài", - "library_watching_settings": "Theo dõi thư viện (THỬ NGHIỆM)", - "library_watching_settings_description": "Tự động cập nhật khi các tệp bị thay đổi", - "logging_enable_description": "Bật ghi log", - "logging_level_description": "Khi được bật, thiết lập mức ghi log.", - "logging_settings": "Ghi log", - "machine_learning_availability_checks": "Kiểm tra khả dụng", - "machine_learning_availability_checks_description": "Tự động phát hiện và ưu tiên máy chủ học máy khả dụng", - "machine_learning_availability_checks_enabled": "Bật kiểm tra tính khả dụng", - "machine_learning_availability_checks_interval": "Kiểm tra khoảng thời gian", - "machine_learning_availability_checks_interval_description": "Khoảng thời gian tính bằng mili giây giữa các lần kiểm tra tính khả dụng", - "machine_learning_availability_checks_timeout": "Thời gian chờ phản hồi", - "machine_learning_availability_checks_timeout_description": "Thời gian chờ tính bằng mili giây để kiểm tra tính khả dụng", - "machine_learning_clip_model": "Mô hình CLIP", - "machine_learning_clip_model_description": "Tên của mô hình CLIP được liệt kê tại đây. Bạn cần chạy lại tác vụ \"Tìm kiếm thông minh\" cho tất cả ảnh sau khi thay đổi mô hình.", - "machine_learning_duplicate_detection": "Tìm trùng lặp", - "machine_learning_duplicate_detection_enabled": "Bật tìm ảnh trùng lặp", - "machine_learning_duplicate_detection_enabled_description": "Nếu bị tắt, các ảnh trùng lặp giống hệt nhau vẫn sẽ bị loại bỏ.", - "machine_learning_duplicate_detection_setting_description": "Sử dụng vector nhúng CLIP để tìm kiếm ảnh trùng lặp", - "machine_learning_enabled": "Bật học máy", - "machine_learning_enabled_description": "Nếu bị tắt, tất cả tính năng và cài đặt học máy sẽ bị loại bỏ.", - "machine_learning_facial_recognition": "Nhận diện khuôn mặt", - "machine_learning_facial_recognition_description": "Phát hiện, nhận diện và nhóm các khuôn mặt trong ảnh", - "machine_learning_facial_recognition_model": "Mô hình nhận dạng khuôn mặt", - "machine_learning_facial_recognition_model_description": "Các mô hình được liệt kê theo thứ tự kích thước giảm dần. Mô hình càng lớn, kết quả càng chính xác nhưng sẽ chạy chậm và tốn nhiều bộ nhớ hơn. Lưu ý rằng sau khi thay đổi mô hình, bạn cần chạy lại tác vụ \"Nhận diện khuôn mặt\" cho tất cả ảnh.", - "machine_learning_facial_recognition_setting": "Bật nhận diện khuôn mặt", - "machine_learning_facial_recognition_setting_description": "Nếu bị tắt, ảnh sẽ không được mã hóa để nhận diện khuôn mặt và sẽ không xuất hiện trong mục Mọi người trên trang Khám phá.", - "machine_learning_max_detection_distance": "Khoảng cách phát hiện tối đa", - "machine_learning_max_detection_distance_description": "Khoảng cách tối đa để hai ảnh được coi là trùng lặp, dao động từ 0,001 đến 0,1. Giá trị càng cao sẽ phát hiện được nhiều ảnh trùng lặp hơn, nhưng có thể bao gồm cả ảnh không thực sự giống nhau.", - "machine_learning_max_recognition_distance": "Khoảng cách nhận diện tối đa", - "machine_learning_max_recognition_distance_description": "Khoảng cách tối đa để hai khuôn mặt được coi là cùng một người, dao động từ 0-2. Giảm giá trị này có thể ngăn chặn việc gán nhãn hai người cùng một người, trong khi tăng giá trị này có thể ngăn chặn việc gán nhãn cùng một người là hai người khác nhau. Lưu ý rằng việc hợp nhất hai người lại với nhau dễ dàng hơn là tách một người thành hai, vì vậy hãy ưu tiên giá trị thấp khi có thể.", - "machine_learning_min_detection_score": "Mức phát hiện tối thiểu", - "machine_learning_min_detection_score_description": "Mức điểm tin cậy tối thiểu để phát hiện khuôn mặt, từ 0 đến 1. Giá trị càng thấp, nhiều khuôn mặt sẽ được phát hiện nhưng có thể tăng khả năng phát hiện sai.", - "machine_learning_min_recognized_faces": "Số khuôn mặt tối thiểu để nhận diện", - "machine_learning_min_recognized_faces_description": "Số khuôn mặt tối thiểu cần nhận dạng để tạo thành một người. Tăng số lượng này sẽ làm cho Nhận diệnkhuôn mặt chính xác hơn, nhưng sẽ tăng khả năng một khuôn mặt không được gán cho người phù hợp.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Dùng học máy để nhận diện chữ trong ảnh", - "machine_learning_ocr_enabled": "Bật OCR", - "machine_learning_ocr_enabled_description": "Nếu tắt, chữ viết trong ảnh sẽ không được nhận diện.", - "machine_learning_ocr_max_resolution": "Độ phân giải tối đa", - "machine_learning_ocr_max_resolution_description": "Bản xem trước có độ phân giải cao hơn mức này sẽ được thay đổi kích thước mà vẫn giữ nguyên tỷ lệ khung hình. Giá trị cao hơn sẽ chính xác hơn, nhưng mất nhiều thời gian xử lý hơn và sử dụng nhiều bộ nhớ hơn.", - "machine_learning_ocr_min_detection_score": "Điểm phát hiện tối thiểu", - "machine_learning_ocr_min_detection_score_description": "Mức điểm tin cậy tối thiểu để phát hiện chữ viết, từ 0 đến 1. Giá trị càng thấp, càng nhiều chữ viết sẽ được phát hiện nhưng có thể tăng khả năng phát hiện sai (dương tính giả).", - "machine_learning_ocr_min_recognition_score": "Điểm nhận dạng tối thiểu", - "machine_learning_ocr_min_score_recognition_description": "Điểm tin cậy tối thiểu để nhận dạng văn bản được phát hiện là từ 0-1. Giá trị thấp hơn sẽ nhận dạng được nhiều văn bản hơn nhưng có thể dẫn đến kết quả dương tính giả.", - "machine_learning_ocr_model": "Mô hình OCR", - "machine_learning_ocr_model_description": "Mô hình máy chủ chính xác hơn mô hình di động, nhưng mất nhiều thời gian xử lý hơn và sử dụng nhiều bộ nhớ hơn.", - "machine_learning_settings": "Cài đặt Học máy", - "machine_learning_settings_description": "Quản lý các tính năng và cài đặt học máy", - "machine_learning_smart_search": "Tìm kiếm Thông minh", - "machine_learning_smart_search_description": "Tìm kiếm hình ảnh theo ngữ cảnh với CLIP", - "machine_learning_smart_search_enabled": "Bật Tìm kiếm Thông minh", - "machine_learning_smart_search_enabled_description": "Nếu tắt, ảnh sẽ không được mã hóa để tìm kiếm thông minh.", - "machine_learning_url_description": "Địa chỉ máy chủ học máy. Nếu có nhiều hơn một địa chỉ được cung cấp, mỗi máy chủ sẽ được kiểm tra một lần cho đến khi có một máy chủ trả lời thành công, theo thứ tự từ máy chủ đầu tiên đến máy chủ cuối cùng. Máy chủ không phản hồi sẽ tạm thời được bỏ qua cho đến khi máy chủ online trở lại.", - "maintenance_delete_backup_description": "Tệp này sẽ bị xoá vĩnh viễn.", - "maintenance_restore_backup": "Khôi phục sao lưu", - "maintenance_settings": "Bảo trì", - "maintenance_settings_description": "Đặt [immich] vào chế độ bảo trì.", - "maintenance_start": "Bắt đầu chế độ bảo trì", - "maintenance_start_error": "Không thể bắt đầu chế độ bảo trì.", - "manage_concurrency": "Quản lý Tác vụ", - "manage_concurrency_description": "Điều hướng đến trang tác vụ để quản lý tác vụ đồng thời", - "manage_log_settings": "Quản lý cài đặt log", - "map_dark_style": "Giao diện tối", - "map_enable_description": "Bật tính năng bản đồ", - "map_gps_settings": "Bản đồ & GPS", - "map_gps_settings_description": "Quản lý cài đặt Bản đồ & GPS (Mã hóa địa lý ngược)", - "map_implications": "Tính năng bản đồ phụ thuộc vào dịch vụ thẻ bản đồ bên ngoài (tiles.immich.cloud)", - "map_light_style": "Giao diện sáng", - "map_manage_reverse_geocoding_settings": "Quản lý cài đặt Mã hóa địa lý ngược", - "map_reverse_geocoding": "Mã hóa Địa lý Ngược (Reverse Geocoding)", - "map_reverse_geocoding_enable_description": "Bật mã hóa địa lý ngược", - "map_reverse_geocoding_settings": "Cài đặt Mã hóa Địa lý Ngược (Reverse Geocoding)", - "map_settings": "Bản đồ", - "map_settings_description": "Quản lý cài đặt bản đồ", - "map_style_description": "URL trỏ đến tệp tùy biến bản đồ style.json", - "memory_cleanup_job": "Dọn dẹp kỷ niệm", - "memory_generate_job": "Tạo kỷ niệm", - "metadata_extraction_job": "Trích xuất Metadata", - "metadata_extraction_job_description": "Trích xuất Metadata từ mỗi ảnh, chẳng hạn như GPS, khuôn mặt và độ phân giải", - "metadata_faces_import_setting": "Bật tính năng nhập khuôn mặt", - "metadata_faces_import_setting_description": "Nhập khuôn mặt từ dữ liệu EXIF ảnh và tệp đi kèm", - "metadata_settings": "Cài đặt Metadata", - "metadata_settings_description": "Quản lý cài đặt Metadata", - "migration_job": "Di chuyển dữ liệu", - "migration_job_description": "Di chuyển ảnh thu nhỏ của ảnh và khuôn mặt sang cấu trúc thư mục mới", - "nightly_tasks_cluster_faces_setting_description": "Chạy nhận diện khuôn mặt trên những khuôn mặt mới được phát hiện", - "nightly_tasks_cluster_new_faces_setting": "Nhóm các khuôn mặt mới", - "nightly_tasks_database_cleanup_setting": "Tác vụ dọn dẹp cơ sở dữ liệu", - "nightly_tasks_database_cleanup_setting_description": "Làm sạch dữ liệu cũ, hết hạn trong CSDL", - "nightly_tasks_generate_memories_setting": "Tạo kỷ niệm", - "nightly_tasks_generate_memories_setting_description": "Tạo ra những kỷ niệm mới từ tệp", - "nightly_tasks_missing_thumbnails_setting": "Tạo ảnh đại diện bị thiếu", - "nightly_tasks_missing_thumbnails_setting_description": "Xếp hàng các tệp không có ảnh thu nhỏ để tạo ảnh thu nhỏ", - "nightly_tasks_settings": "Cài đặt các tác vụ hàng đêm", - "nightly_tasks_settings_description": "Quản lý các tác vụ hàng đêm", - "nightly_tasks_start_time_setting": "Thời gian bắt đầu", - "nightly_tasks_start_time_setting_description": "Thời điểm máy chủ bắt đầu chạy các tác vụ ban đêm", - "nightly_tasks_sync_quota_usage_setting": "Sử dụng hạn mức đồng bộ", - "nightly_tasks_sync_quota_usage_setting_description": "Cập nhật hạn mức dung lượng của người dùng, dựa trên mức sử dụng hiện tại", - "no_paths_added": "Không có đường dẫn nào được thêm vào", - "no_pattern_added": "Không có quy tắc nào được thêm vào", - "note_apply_storage_label_previous_assets": "Lưu ý: Để áp dụng Nhãn lưu trữ cho nội dung đã tải lên trước đó, hãy chạy", - "note_cannot_be_changed_later": "LƯU Ý: Cài đặt này không thể thay đổi được sau khi lưu!", - "notification_email_from_address": "Địa chỉ email người gửi", - "notification_email_from_address_description": "Địa chỉ email của người gửi, ví dụ: \"Immich Photo Server \". Hãy chắc là bạn dùng một địa chỉ mà bạn được phép gửi email ra.", - "notification_email_host_description": "Địa chỉ máy chủ email (ví dụ: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Bỏ qua lỗi chứng chỉ", - "notification_email_ignore_certificate_errors_description": "Bỏ qua lỗi xác thực chứng chỉ TLS (không đề xuất)", - "notification_email_password_description": "Mật khẩu dùng để xác thực với máy chủ email", - "notification_email_port_description": "Cổng của máy chủ email (ví dụ 25, 465, hoặc 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Dùng SMTPS (ưu tiên SMTP hơnTLS)", - "notification_email_sent_test_email_button": "Gửi email kiểm tra và lưu", - "notification_email_setting_description": "Cài đặt gửi thông báo qua email", - "notification_email_test_email": "Đã gửi email kiểm tra", - "notification_email_test_email_failed": "Gửi email thử nghiệm thất bại, vui lòng kiểm tra các giá trị của bạn", - "notification_email_test_email_sent": "Một email thử nghiệm đã được gửi tới {email}. Vui lòng kiểm tra hộp thư của bạn.", - "notification_email_username_description": "Tên đăng nhập email để xác thực với máy chủ email", - "notification_enable_email_notifications": "Bật thông báo qua email", - "notification_settings": "Thông báo", - "notification_settings_description": "Quản lý các cài đặt thông báo, bao gồm email", - "oauth_auto_launch": "Tự động khởi chạy OAuth", - "oauth_auto_launch_description": "Tự động đăng nhập bằng tài khoản OAuth khi bạn truy cập trang đăng nhập", - "oauth_auto_register": "Tự động đăng ký", - "oauth_auto_register_description": "Tự động đăng ký người dùng mới sau khi đăng nhập với OAuth", - "oauth_button_text": "Nội dung nút bấm", - "oauth_client_secret_description": "Bắt buộc nếu nhà cung cấp OAuth không hỗ trợ PKCE (Proof Key for Code Exchange)", - "oauth_enable_description": "Đăng nhập với OAuth", - "oauth_mobile_redirect_uri": "URI chuyển hướng trên thiết bị di động", - "oauth_mobile_redirect_uri_override": "Ghi đè URI chuyển hướng cho thiết bị di động", - "oauth_mobile_redirect_uri_override_description": "Bật khi nhà cung cấp OAuth không cho phép URI di động, như ''{callback}''", - "oauth_role_claim": "Vai trò claim", - "oauth_role_claim_description": "Tự động cấp quyền quản trị dựa trên sự hiện diện của yêu cầu này. Yêu cầu này có thể có tên là 'người dùng' hoặc 'quản trị viên'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Quản lý cài đặt đăng nhập OAuth", - "oauth_settings_more_details": "Để biết thêm chi tiết về tính năng này, hãy tham khảo tài liệu.", - "oauth_storage_label_claim": "Claim cho nhãn lưu trữ", - "oauth_storage_label_claim_description": "Tự động đặt nhãn lưu trữ của người dùng theo giá trị của claim này.", - "oauth_storage_quota_claim": "Claim hạn mức lưu trữ", - "oauth_storage_quota_claim_description": "Tự động đặt hạn mức lưu trữ của người dùng theo giá trị của claim này.", - "oauth_storage_quota_default": "Hạn mức lưu trữ mặc định (GiB)", - "oauth_storage_quota_default_description": "Hạn mức (GiB) được dùng khi không cung cấp yêu cầu nào.", - "oauth_timeout": "Thời gian chờ phản hồi", - "oauth_timeout_description": "Đặt thời gian chờ phản hồi bằng mili giây", - "ocr_job_description": "Sử dụng học máy để nhận dạng chữ viết trong ảnh", - "password_enable_description": "Đăng nhập với email và mật khẩu", - "password_settings": "Mật khẩu đăng nhập", - "password_settings_description": "Quản lý cài đặt mật khẩu đăng nhập", - "paths_validated_successfully": "Tất cả đường dẫn được xác minh thành công", - "person_cleanup_job": "Dọn dẹp người", - "queue_details": "Chi tiết hàng đợi", - "queues": "Hàng đợi tác vụ", - "queues_page_description": "Trang hàng đợi công việc của quản trị viên", - "quota_size_gib": "Hạn mức (GiB)", - "refreshing_all_libraries": "Làm mới tất cả thư viện", - "registration": "Đăng ký Quản trị viên", - "registration_description": "Vì bạn là người dùng đầu tiên, bạn sẽ trở thành Quản trị viên và chịu trách nhiệm cho việc quản lý hệ thống. Ngoài ra, bạn có thể thêm các người dùng khác.", - "remove_failed_jobs": "Xóa các tác vụ không thành công", - "require_password_change_on_login": "Yêu cầu người dùng thay đổi mật khẩu trong lần đăng nhập đầu tiên", - "reset_settings_to_default": "Đặt lại cài đặt về mặc định", - "reset_settings_to_recent_saved": "Đặt lại cài đặt về cài đặt trước đó", - "scanning_library": "Quét thư viện", - "search_jobs": "Tìm kiếm tác vụ…", - "send_welcome_email": "Gửi email chào mừng", - "server_external_domain_settings": "Tên miền công khai", - "server_external_domain_settings_description": "Tên miền dành cho các liên kết chia sẻ công khai, bao gồm http(s)://", - "server_public_users": "Người dùng công khai", - "server_public_users_description": "Tất cả người dùng (tên và email) được liệt kê khi thêm một người dùng vào một album được chia sẻ. Khi tắt lựa chọn này, danh sách người dùng chỉ có thể được thấy bởi người dùng quản trị.", - "server_settings": "Máy chủ", - "server_settings_description": "Quản lý cài đặt máy chủ", - "server_stats_page_description": "Trang thống kê quản trị máy chủ", - "server_welcome_message": "Thông điệp chào mừng", - "server_welcome_message_description": "Thông điệp chào mừng được hiển thị trên trang đăng nhập.", - "settings_page_description": "Trang cài đặt quản trị", - "sidecar_job": "Siêu dữ liệu đi kèm", - "sidecar_job_description": "Tìm hoặc đồng bộ các tệp siêu dữ liệu đi kèm từ hệ thống", - "slideshow_duration_description": "Số giây hiển thị từng ảnh", - "smart_search_job_description": "Chạy học máy trên toàn bộ ảnh để hỗ trợ tìm kiếm thông minh", - "storage_template_date_time_description": "Mốc thời gian tạo tệp được dùng làm thông tin ngày giờ", - "storage_template_date_time_sample": "Thời gian mẫu {date}", - "storage_template_enable_description": "Bật công cụ mẫu lưu trữ", - "storage_template_hash_verification_enabled": "Bật xác minh băm", - "storage_template_hash_verification_enabled_description": "Bật xác minh băm, không tắt tính năng này trừ khi bạn chắc chắn về các rủi ro có thể xảy ra", - "storage_template_migration": "Di chuyển mẫu lưu trữ", - "storage_template_migration_description": "Áp dụng {template} hiện tại cho các ảnh đã được tải lên trước đây", - "storage_template_migration_info": "Mẫu lưu trữ sẽ chuyển tất cả định dạng tập tin thành chữ thường. Những thay đổi của mẫu này chỉ áp dụng cho các ảnh mới tải lên. Để áp dụng mẫu này cho các ảnh đã tải lên trước đây, hãy chạy {job}.", - "storage_template_migration_job": "Tác vụ di chuyển mẫu lưu trữ", - "storage_template_more_details": "Cần thêm thông tin chi tiết về tính năng này, vui lòng tham khảo Mẫu lưu trữ và các hệ quả của nó", - "storage_template_onboarding_description_v2": "Khi được bật, tính năng này sẽ tự động sắp xếp các tệp dựa trên mẫu do người dùng xác định. Để biết thêm thông tin, vui lòng xem tài liệu.", - "storage_template_path_length": "Giới hạn độ dài đường dẫn xấp xỉ: {length, number}/{limit, number}", - "storage_template_settings": "Mẫu lưu trữ", - "storage_template_settings_description": "Quản lý cấu trúc thư mục và tên tệp của ảnh tải lên", - "storage_template_user_label": "Cụm từ {label} là Nhãn lưu trữ của người dùng", - "system_settings": "Cài đặt hệ thống", - "tag_cleanup_job": "Dọn dẹp thẻ", - "template_email_available_tags": "Bạn có thể sử dụng các tham số sau trong mẫu của bạn: {tags}", - "template_email_if_empty": "Nếu mẫu để trống, email mặc định sẽ được sử dụng.", - "template_email_invite_album": "Mẫu mời tham gia album", - "template_email_preview": "Xem trước", - "template_email_settings": "Mẫu Email", - "template_email_update_album": "Mẫu khi album có nội dung mới", - "template_email_welcome": "Mẫu email chào mừng", - "template_settings": "Mẫu thông báo", - "template_settings_description": "Quản lý các mẫu thông báo tùy chỉnh", - "theme_custom_css_settings": "CSS tùy chỉnh", - "theme_custom_css_settings_description": "Cascading Style Sheets cho phép tùy chỉnh thiết kế của Immich.", - "theme_settings": "Chủ đề", - "theme_settings_description": "Tùy biến giao diện web của Immich", - "thumbnail_generation_job": "Tạo ảnh thu nhỏ", - "thumbnail_generation_job_description": "Tạo ảnh thu nhỏ lớn, nhỏ và mờ cho mỗi ảnh, cũng như ảnh thu nhỏ cho mỗi người", - "transcoding_acceleration_api": "API Gia tốc", - "transcoding_acceleration_api_description": "API này sẽ tương tác với thiết bị của bạn để gia tốc quá trình chuyển mã. Cài đặt này hoạt động theo nguyên tắc 'cố gắng hết sức'': nó sẽ quay lại chuyển mã phần mềm nếu gặp lỗi. VP9 có thể hoạt động hoặc không tùy thuộc vào phần cứng của bạn.", - "transcoding_acceleration_nvenc": "NVENC (yêu cầu GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (yêu cầu CPU Intel thế hệ 7 hoặc mới hơn)", - "transcoding_acceleration_rkmpp": "RKMPP (chỉ trên các SOC của Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Các chuẩn nén âm thanh được chấp nhận", - "transcoding_accepted_audio_codecs_description": "Chọn các chuẩn nén âm thanh không cần phải chuyển mã. Chỉ được sử dụng cho một số quy tắc chuyển mã nhất định.", - "transcoding_accepted_containers": "Các định dạng video được chấp nhận", - "transcoding_accepted_containers_description": "Chọn các định dạng tệp không cần chuyển đổi sang MP4. Chỉ được sử dụng cho một số quy tắc chuyển mã nhất định.", - "transcoding_accepted_video_codecs": "Các chuẩn nén video được chấp nhận", - "transcoding_accepted_video_codecs_description": "Chọn các chuẩn nén video không cần phải chuyển mã. Chỉ được sử dụng cho một số quy tắc chuyển mã nhất định.", - "transcoding_advanced_options_description": "Các tùy chọn mà hầu hết người dùng không cần phải thay đổi", - "transcoding_audio_codec": "Chuẩn nén âm thanh", - "transcoding_audio_codec_description": "Opus là tùy chọn chất lượng cao nhất, nhưng có tính tương thích thấp hơn với các thiết bị hoặc phần mềm cũ.", - "transcoding_bitrate_description": "Video có bitrate cao hơn hoặc không ở định dạng được chấp nhận", - "transcoding_codecs_learn_more": "Để hiểu thêm về thuật ngữ ở đây, hãy tham khảo tài liệu FFmpeg cho codec H.264, codec HEVCcodec VP9.", - "transcoding_constant_quality_mode": "Chế độ chất lượng cố định", - "transcoding_constant_quality_mode_description": "ICQ tốt hơn CQP, nhưng một số thiết bị tăng tốc phần cứng không hỗ trợ chế độ này. Cài đặt tùy chọn này sẽ ưu tiên chế độ được chỉ định khi sử dụng mã hóa dựa trên chất lượng. Bị bỏ qua bởi NVENC vì nó không hỗ trợ ICQ.", - "transcoding_constant_rate_factor": "Hệ số tỷ lệ cố định (-crf)", - "transcoding_constant_rate_factor_description": "Mức chất lượng video. Các giá trị điển hình là 23 cho H.264, 28 cho HEVC, 31 cho VP9 và 35 cho AV1. Giá trị thấp hơn thì tốt hơn, nhưng tạo ra các tập tin lớn hơn.", - "transcoding_disabled_description": "Không chuyển mã bất kỳ video nào, có thể gây lỗi phát lại trên một số thiết bị", - "transcoding_encoding_options": "Các tùy chọn mã hóa", - "transcoding_encoding_options_description": "Thiết lập chuẩn nén video, độ phân giải, chất lượng và các tùy chọn khác cho video", - "transcoding_hardware_acceleration": "Tăng tốc phần cứng", - "transcoding_hardware_acceleration_description": "Thử nghiệm: nhanh hơn nhiều nhưng có thể làm giảm chất lượng ở cùng mức bitrate", - "transcoding_hardware_decoding": "Giải mã phần cứng", - "transcoding_hardware_decoding_setting_description": "Cho phép tăng tốc đầu cuối thay vì chỉ tăng tốc mã hóa. Có thể không hoạt động với mọi video.", - "transcoding_max_b_frames": "Số B-frame tối đa", - "transcoding_max_b_frames_description": "Giá trị cao hơn cải thiện hiệu quả nén, nhưng làm chậm mã hóa. Có thể không tương thích với tăng tốc phần cứng trên các thiết bị cũ. Giá trị 0 để tắt B-frames, trong khi giá trị -1 để tự động thiết lập giá trị này.", - "transcoding_max_bitrate": "Bitrate tối đa", - "transcoding_max_bitrate_description": "Cài đặt giới hạn bitrate tối đa có thể giúp kích thước video dễ dự đoán hơn, với một chút hy sinh về chất lượng. Ở độ phân giải 720p, giá trị điển hình là 2600 kbit/s cho VP9 hoặc HEVC, hoặc 4500 kbit/s cho H.264. Nếu đặt thành 0, chức năng này sẽ bị vô hiệu hóa. Khi không có đơn vị nào được chỉ định, giả định sẽ là k (đối với kbit/giây); do đó 5000, 5000k và 5M (đối với Mbit/giây) là tương đương.", - "transcoding_max_keyframe_interval": "Khoảng cách tối đa giữa các khung hình chính", - "transcoding_max_keyframe_interval_description": "Thiết lập khoảng thời gian tối đa giữa các khung hình chính. Giá trị thấp hơn làm giảm hiệu suất nén, nhưng cải thiện thời gian tìm kiếm và có thể cải thiện chất lượng trong các cảnh có chuyển động nhanh. Giá trị 0 để tự động thiết lập giá trị này.", - "transcoding_optimal_description": "Video có độ phân giải cao hơn mục tiêu hoặc không ở định dạng được chấp nhận", - "transcoding_policy": "Quy tắc chuyển mã video", - "transcoding_policy_description": "Chọn khi nào video sẽ chuyển mã", - "transcoding_preferred_hardware_device": "Thiết bị phần cứng ưu tiên", - "transcoding_preferred_hardware_device_description": "Chỉ áp dụng cho VAAPI và QSV. Thiết lập nút dri được sử dụng cho chuyển mã phần cứng.", - "transcoding_preset_preset": "Mẫu có sẵn (-preset)", - "transcoding_preset_preset_description": "Tốc độ nén. Các mẫu có sẵn chậm hơn tạo ra các tệp nhỏ hơn và cải thiện chất lượng khi mục tiêu là một bitrate cụ thể. VP9 chỉ hỗ trợ các mẫu có sẵn từ 'ultrafast' đến 'faster'.", - "transcoding_reference_frames": "Khung hình tham chiếu", - "transcoding_reference_frames_description": "Số lượng khung hình tham chiếu khi nén một khung hình nhất định. Giá trị cao hơn cải thiện hiệu suất nén nhưng làm chậm quá trình mã hóa. Giá trị 0 để tự động thiết lập giá trị này.", - "transcoding_required_description": "Chỉ video không ở định dạng được chấp nhận", - "transcoding_settings": "Chuyển mã video", - "transcoding_settings_description": "Quản lý video chuyển mã và cách xử lý", - "transcoding_target_resolution": "Độ phân giải mục tiêu", - "transcoding_target_resolution_description": "Độ phân giải cao hơn có thể giữ lại nhiều chi tiết hơn nhưng mất nhiều thời gian hơn để mã hóa, có kích thước tập tin lớn hơn và có thể làm giảm khả năng phản hồi của app.", - "transcoding_temporal_aq": "Lượng tử hóa thích ứng (Temporal AQ)", - "transcoding_temporal_aq_description": "Chỉ áp dụng cho NVENC. Lượng tử hóa Thích ứng Thời gian tăng chất lượng cho các cảnh có nhiều chi tiết và ít chuyển động. Có thể không tương thích với các thiết bị cũ.", - "transcoding_threads": "Luồng", - "transcoding_threads_description": "Giá trị cao hơn dẫn đến mã hóa nhanh hơn nhưng để lại ít không gian hơn cho máy chủ xử lý các tác vụ khác khi đang hoạt động. Giá trị này không nên vượt quá số lượng lõi CPU. Tối đa hóa sử dụng nếu đặt thành 0.", - "transcoding_tone_mapping": "Ánh Xạ Sắc Thái (Tone-mapping)", - "transcoding_tone_mapping_description": "Cố gắng duy trì chất lượng video tốt nhất khi chuyển đổi từ HDR sang SDR. Mỗi thuật toán có sự đánh đổi khác nhau về màu sắc, chi tiết và độ sáng. Hable giữ chi tiết, Mobius giữ màu sắc và Reinhard giữ độ sáng.", - "transcoding_transcode_policy": "Quy tắc chuyển mã", - "transcoding_transcode_policy_description": "Quy tắc khi nào video nên được chuyển mã. Các video HDR luôn được chuyển mã (ngoại trừ khi tính năng chuyển mã bị tắt).", - "transcoding_two_pass_encoding": "Mã hóa hai lần", - "transcoding_two_pass_encoding_setting_description": "Chuyển mã hai lần để tạo ra video được mã hóa tốt hơn. Khi bitrate tối đa được bật (bắt buộc để hoạt động với H.264 và HEVC), chế độ này sử dụng một phạm vi bitrate dựa trên bitrate tối đa và bỏ qua CRF. Đối với VP9, CRF có thể được sử dụng nếu bitrate tối đa bị tắt.", - "transcoding_video_codec": "Chuẩn nén video", - "transcoding_video_codec_description": "VP9 có hiệu suất cao và tương thích tốt với web, nhưng thời gian chuyển mã lâu hơn. HEVC có hiệu suất tương tự, nhưng tương thích web thấp hơn. H.264 tương thích rộng rãi và chuyển mã nhanh, nhưng tạo ra các tập tin có kích thước lớn. AV1 là codec hiệu quả nhất nhưng không được hỗ trợ trên các thiết bị cũ.", - "trash_enabled_description": "Bật tính năng Thùng rác", - "trash_number_of_days": "Số ngày", - "trash_number_of_days_description": "Số ngày giữ các ảnh trong thùng rác trước khi xóa chúng vĩnh viễn", - "trash_settings": "Thùng rác", - "trash_settings_description": "Quản lý cài đặt thùng rác", - "unlink_all_oauth_accounts": "Hủy liên kết tất cả tài khoản OAuth", - "unlink_all_oauth_accounts_description": "Hãy nhớ hủy liên kết tất cả tài khoản OAuth trước khi di chuyển sang nhà cung cấp mới.", - "unlink_all_oauth_accounts_prompt": "Bạn có chắc muốn hủy liên kết tất cả tài khoản OAuth không? Thao tác này sẽ đặt lại ID OAuth cho mỗi người dùng và không thể hoàn tác.", - "user_cleanup_job": "Dọn dẹp người dùng", - "user_delete_delay": "Tài khoản và tệp của {user} sẽ được lên lịch xóa vĩnh viễn sau {delay, plural, one {# ngày} other {# ngày}}.", - "user_delete_delay_settings": "Thời gian xóa", - "user_delete_delay_settings_description": "Số ngày chờ xóa để xóa vĩnh viễn tài khoản và tệp của người dùng. Tác vụ xóa người dùng chạy vào giữa đêm để kiểm tra các người dùng sẵn sàng bị xóa. Thay đổi cài đặt này sẽ được đánh giá vào lần thực hiện tiếp theo.", - "user_delete_immediately": "Tài khoản và ảnh của {user} sẽ được đưa vào hàng đợi để xóa vĩnh viễn ngay lập tức.", - "user_delete_immediately_checkbox": "Xếp hàng người dùng và ảnh để xóa ngay lập tức", - "user_details": "Chi tiết Người dùng", - "user_management": "Quản lý người dùng", - "user_password_has_been_reset": "Mật khẩu của người dùng đã được đặt lại:", - "user_password_reset_description": "Vui lòng cung cấp mật khẩu tạm thời cho người dùng và thông báo rằng họ cần thay đổi mật khẩu khi đăng nhập lần tiếp theo.", - "user_restore_description": "Tài khoản của {user} sẽ được khôi phục.", - "user_restore_scheduled_removal": "Khôi phục người dùng - đã lên lịch xóa vào {date, date, long}", - "user_settings": "Người dùng", - "user_settings_description": "Quản lý cài đặt người dùng", - "user_successfully_removed": "Người dùng {email} đã được xóa thành công.", - "users_page_description": "Trang quản trị người dùng", - "version_check_enabled_description": "Bật kiểm tra phiên bản", - "version_check_implications": "Tính năng kiểm tra phiên bản yêu cầu kết nối thường xuyên đến github.com", - "version_check_settings": "Kiểm tra phiên bản", - "version_check_settings_description": "Bật/tắt thông báo phiên bản mới", - "video_conversion_job": "Chuyển mã video", - "video_conversion_job_description": "Chuyển đổi định dạng video để tương thích rộng rãi hơn với trình duyệt và thiết bị" - }, - "admin_email": "Email Quản trị viên", - "admin_password": "Mật khẩu Quản trị viên", - "administration": "Quản trị", - "advanced": "Nâng cao", - "advanced_settings_enable_alternate_media_filter_subtitle": "Dùng tùy chọn này để lọc phương tiện khi đồng bộ theo tiêu chí khác. Chỉ thử khi app không nhận diện được tất cả các album.", - "advanced_settings_enable_alternate_media_filter_title": "[THỬ NGHIỆM] Dùng bộ lọc đồng bộ album thay thế", - "advanced_settings_log_level_title": "Phân loại log: {level}", - "advanced_settings_prefer_remote_subtitle": "Việc tải ảnh thu nhỏ từ ảnh trên một số thiết bị có thể diễn ra chậm. Kích hoạt cài đặt này để tải ảnh từ máy chủ.", - "advanced_settings_prefer_remote_title": "Ưu tiên ảnh từ máy chủ", - "advanced_settings_proxy_headers_subtitle": "Xác định các tiêu đề proxy Immich sẽ gửi kèm mỗi yêu cầu mạng", - "advanced_settings_proxy_headers_title": "Tùy chỉnh tiêu đề proxy [THỬ NGHIỆM]", - "advanced_settings_readonly_mode_subtitle": "Chế độ chỉ-xem chỉ cho phép xem ảnh, các tính năng như chọn nhiều ảnh, chia sẻ, phát, xóa đều bị vô hiệu hóa. Bật/Tắt chế độ chỉ-xem thông qua ảnh đại diện người dùng từ màn hình chính", - "advanced_settings_readonly_mode_title": "Chế độ chỉ-xem", - "advanced_settings_self_signed_ssl_subtitle": "Bỏ qua xác minh chứng chỉ SSL cho endpoint máy chủ. Yêu cầu cho chứng chỉ tự ký.", - "advanced_settings_self_signed_ssl_title": "Cho phép chứng chỉ SSL tự ký [THỬ NGHIỆM]", - "advanced_settings_sync_remote_deletions_subtitle": "Tự động xóa hoặc khôi phục dữ liệu trên thiết bị này khi bạn thao tác trên web", - "advanced_settings_sync_remote_deletions_title": "Đồng bộ việc xóa từ thiết bị khác [THỬ NGHIỆM]", - "advanced_settings_tile_subtitle": "Dành cho người dùng nâng cao", - "advanced_settings_troubleshooting_subtitle": "Bật các tính năng bổ sung để xử lý sự cố", - "advanced_settings_troubleshooting_title": "Xử lý sự cố", - "age_months": "{months, plural, one {# tháng} other {# tháng}} tuổi", - "age_year_months": "1 tuổi, {months, plural, one {# tháng} other {# tháng}}", - "age_years": "{years, plural, other {# tuổi}}", - "album": "Album", - "album_added": "Đã thêm album", - "album_added_notification_setting_description": "Nhận thông báo qua email khi bạn được thêm vào một album chia sẻ", - "album_cover_updated": "Đã cập nhật ảnh bìa album", - "album_delete_confirmation": "Bạn có chắc muốn xóa album {album}?", - "album_delete_confirmation_description": "Nếu album này được chia sẻ, các người dùng khác sẽ không còn truy cập được nữa.", - "album_deleted": "Đã xóa album", - "album_info_card_backup_album_excluded": "ĐÃ BỎ QUA", - "album_info_card_backup_album_included": "ĐÃ THÊM", - "album_info_updated": "Đã cập nhật thông tin album", - "album_leave": "Rời album?", - "album_leave_confirmation": "Bạn có chắc muốn rời khỏi {album}?", - "album_name": "Tên album", - "album_options": "Tùy chọn album", - "album_remove_user": "Xóa người dùng?", - "album_remove_user_confirmation": "Bạn có chắc muốn xóa {user}?", - "album_search_not_found": "Không tìm thấy album trùng khớp", - "album_selected": "Album đã chọn", - "album_share_no_users": "Có vẻ như bạn đã chia sẻ album này với tất cả người dùng hoặc bạn không có người dùng nào để chia sẻ.", - "album_summary": "Mô tả album", - "album_updated": "Đã cập nhật album", - "album_updated_setting_description": "Nhận thông báo qua email khi một album chia sẻ có các ảnh mới", - "album_user_left": "Đã rời khỏi {album}", - "album_user_removed": "Đã xóa {user}", - "album_viewer_appbar_delete_confirm": "Bạn có muốn xóa album này khỏi tài khoản của mình?", - "album_viewer_appbar_share_err_delete": "Xóa album thất bại", - "album_viewer_appbar_share_err_leave": "Rời khỏi album thất bại", - "album_viewer_appbar_share_err_remove": "Có vấn đề khi xóa ảnh khỏi album", - "album_viewer_appbar_share_err_title": "Thay đổi tên album thất bại", - "album_viewer_appbar_share_leave": "Rời khỏi album", - "album_viewer_appbar_share_to": "Chia sẻ với", - "album_viewer_page_share_add_users": "Thêm người dùng", - "album_with_link_access": "Ai có liên kết sẽ xem được các ảnh và người trong album này.", - "albums": "Album", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", - "albums_default_sort_order": "Thứ tự sắp xếp album mặc định", - "albums_default_sort_order_description": "Thứ tự sắp xếp ban đầu cho các ảnh khi tạo album mới.", - "albums_feature_description": "Các bộ sưu tập tệp có thể được chia sẻ với những người dùng khác.", - "albums_on_device_count": "Album trên thiết bị ({count})", - "albums_selected": "{count, plural, one {# album đã chọn} other {# album đã chọn}}", - "all": "Tất cả", - "all_albums": "Tất cả album", - "all_people": "Tất cả mọi người", - "all_videos": "Tất cả video", - "allow_dark_mode": "Cho phép chế độ tối", - "allow_edits": "Cho phép chỉnh sửa", - "allow_public_user_to_download": "Cho phép tải ảnh xuống", - "allow_public_user_to_upload": "Cho phép tải ảnh lên", - "allowed": "Cho phép", - "alt_text_qr_code": "Ảnh mã QR", - "anti_clockwise": "Xoay trái", - "api_key": "Khóa API", - "api_key_description": "Giá trị này chỉ được hiển thị một lần. Vui lòng sao chép nó trước khi đóng cửa sổ.", - "api_key_empty": "Tên khóa API của bạn không được để trống", - "api_keys": "Khóa API", - "app_architecture_variant": "Variant (Kiến trúc)", - "app_bar_signout_dialog_content": "Bạn có muốn đăng xuất?", - "app_bar_signout_dialog_ok": "Có", - "app_bar_signout_dialog_title": "Đăng xuất", - "app_download_links": "Liên kết tải app", - "app_settings": "App", - "app_stores": "Cửa hàng app", - "app_update_available": "Đã có bản cập nhật app", - "appears_in": "Xuất hiện trong", - "apply_count": "Áp dụng ({count, number})", - "archive": "Lưu trữ", - "archive_action_prompt": "{count} đã được thêm vào Lưu trữ", - "archive_or_unarchive_photo": "Lưu trữ hoặc bỏ lưu trữ ảnh", - "archive_page_no_archived_assets": "Không tìm thấy tệp đã lưu trữ", - "archive_page_title": "Kho lưu trữ ({count})", - "archive_size": "Kích cỡ lưu trữ", - "archive_size_description": "Cấu hình kích cỡ nén cho các tệp tải xuống (đơn vị GiB)", - "archived": "Lưu trữ", - "archived_count": "{count, plural, other {Đã lưu trữ # mục}}", - "are_these_the_same_person": "Đây có phải cùng một người không?", - "are_you_sure_to_do_this": "Bạn có chắc muốn thực hiện điều này?", - "array_field_not_fully_supported": "Các trường mảng yêu cầu chỉnh sửa JSON thủ công", - "asset_action_delete_err_read_only": "Không thể xóa tệp chỉ có quyền đọc, bỏ qua", - "asset_action_share_err_offline": "Không thể tải tệp ngoại tuyến, bỏ qua", - "asset_added_to_album": "Đã thêm vào album", - "asset_adding_to_album": "Đang thêm vào album…", - "asset_created": "Đã tạo tệp", - "asset_description_updated": "Mô tả ảnh đã được cập nhật", - "asset_filename_is_offline": "Tệp {filename} đang ngoại tuyến", - "asset_has_unassigned_faces": "Tệp chưa được gán khuôn mặt", - "asset_hashing": "Đang băm…", - "asset_list_group_by_sub_title": "Nhóm theo", - "asset_list_layout_settings_dynamic_layout_title": "Bố cục động", - "asset_list_layout_settings_group_automatically": "Tự động", - "asset_list_layout_settings_group_by": "Nhóm tệp theo", - "asset_list_layout_settings_group_by_month_day": "Tháng + ngày", - "asset_list_layout_sub_title": "Bố cục", - "asset_list_settings_subtitle": "Bố cục lưới ảnh", - "asset_list_settings_title": "Lưới ảnh", - "asset_offline": "Tệp Ngoại tuyến", - "asset_offline_description": "Tệp bên ngoài này không còn trên ổ đĩa. Vui lòng liên hệ quản trị viên Immich của bạn để được trợ giúp.", - "asset_restored_successfully": "Đã khôi phục tệp thành công", - "asset_skipped": "Đã bỏ qua", - "asset_skipped_in_trash": "Trong thùng rác", - "asset_trashed": "Đã chuyển tệp đến thùng rác", - "asset_troubleshoot": "Khắc phục sự cố tệp", - "asset_uploaded": "Đã tải lên", - "asset_uploading": "Đang tải lên…", - "asset_viewer_settings_subtitle": "Cách thư viện hiển thị", - "asset_viewer_settings_title": "Trình xem tệp", - "assets": "Tệp", - "assets_added_count": "Đã thêm {count, plural, one {# tệp} other {# tệp}}", - "assets_added_to_album_count": "Đã thêm {count, plural, one {# tệp} other {# tệp}} vào album", - "assets_added_to_albums_count": "Đã thêm {assetTotal, plural, one {# tệp} other {# tệp}} vào {albumTotal, plural, one {# album} other {# album}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Tệp} other {Tệp}} không thể thêm vào album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Tệp} other {Tệp}} không thể thêm vào bất kỳ album nào", - "assets_count": "{count, plural, one {# tệp} other {# tệp}}", - "assets_deleted_permanently": "Đã xóa vĩnh viễn {count} tệp", - "assets_deleted_permanently_from_server": "Đã xóa vĩnh viễn {count} tệp khỏi máy chủ Immich", - "assets_downloaded_failed": "{count, plural, one {Đã tải xuống # tệp - {error} tệp thất bại} other {Đã tải xuống # tệp - {error} tệp thất bại}}", - "assets_downloaded_successfully": "{count, plural, one {Đã tải xuống # tệp thành công} other {Đã tải xuống # tệp thành công}}", - "assets_moved_to_trash_count": "Đã chuyển {count, plural, one {# tệp} other {# tệp}} vào thùng rác", - "assets_permanently_deleted_count": "Đã xóa vĩnh viễn {count, plural, one {# tệp} other {# tệp}}", - "assets_removed_count": "Đã xóa {count, plural, one {# tệp} other {# tệp}}", - "assets_removed_permanently_from_device": "Đã xóa vĩnh viễn {count} tệp khỏi thiết bị của bạn", - "assets_restore_confirmation": "Bạn có chắc muốn khôi phục tất cả mục đã xóa của mình không? Bạn không thể hoàn tác hành động này! Lưu ý rằng không thể khôi phục các ảnh ngoại tuyến theo cách này.", - "assets_restored_count": "Đã khôi phục {count, plural, one {# tệp} other {# tệp}}", - "assets_restored_successfully": "Đã khôi phục {count} tệp thành công", - "assets_trashed": "Đã chuyển {count} tệp vào thùng rác", - "assets_trashed_count": "Đã chuyển {count, plural, one {# tệp} other {# tệp}} vào thùng rác", - "assets_trashed_from_server": "Đã chuyển {count} tệp từ máy chủ Immich vào thùng rác", - "assets_were_part_of_album_count": "{count, plural, one {Tệp đã} other {Các tệp đã}} có sẵn trong album", - "assets_were_part_of_albums_count": "{count, plural, one {Tệp đã} other {Tệp đã}} có sẵn trong album", - "authorized_devices": "Thiết bị", - "automatic_endpoint_switching_subtitle": "Kết nối nội bộ qua Wi-Fi được chỉ định khi kết nối được và sử dụng các kết nối thay thế ở nơi khác", - "automatic_endpoint_switching_title": "Tự động chuyển đổi địa chỉ máy chủ", - "autoplay_slideshow": "Tự động phát trình chiếu", - "back": "Quay lại", - "back_close_deselect": "Quay lại, đóng, hoặc bỏ chọn", - "background_backup_running_error": "Sao lưu nền hiện đang chạy, không thể bắt đầu sao lưu thủ công", - "background_location_permission": "Quyền truy cập vị trí khi chạy nền", - "background_location_permission_content": "Để chuyển đổi mạng khi chạy ở chế độ nền, Immich *luôn* phải có quyền truy cập vị trí chính xác để có thể đọc tên mạng Wi-Fi", - "background_options": "Tùy chọn nền", - "backup": "Sao lưu", - "backup_album_selection_page_albums_device": "Album trên thiết bị ({count})", - "backup_album_selection_page_albums_tap": "Nhấn để chọn, nhấn đúp để bỏ qua", - "backup_album_selection_page_assets_scatter": "Ảnh có thể có trong nhiều album khác nhau. Trong quá trình sao lưu, bạn có thể chọn để sao lưu tất cả các album hoặc chỉ một số album nhất định.", - "backup_album_selection_page_select_albums": "Chọn album", - "backup_album_selection_page_selection_info": "Thông tin các mục đã chọn", - "backup_album_selection_page_total_assets": "Tổng số tệp không trùng lặp", - "backup_albums_sync": "Đồng bộ hóa album sao lưu", - "backup_all": "Tất cả", - "backup_background_service_backup_failed_message": "Sao lưu tệp thất bại. Đang thử lại…", - "backup_background_service_complete_notification": "Hoàn tất sao lưu tệp", - "backup_background_service_connection_failed_message": "Kết nối tới máy chủ thất bại. Đang thử lại…", - "backup_background_service_current_upload_notification": "Đang tải lên {filename}", - "backup_background_service_default_notification": "Đang kiểm tra tệp mới…", - "backup_background_service_error_title": "Sao lưu không thành công", - "backup_background_service_in_progress_notification": "Đang sao lưu tệp của bạn…", - "backup_background_service_upload_failure_notification": "Tải lên {filename} thất bại", - "backup_controller_page_albums": "Album sao lưu", - "backup_controller_page_background_app_refresh_disabled_content": "Bật làm mới app trong nền tại Cài đặt > Cài đặt chung > Làm mới app trong nền để dùng sao lưu nền.", - "backup_controller_page_background_app_refresh_disabled_title": "Làm mới app trong nền bị vô hiệu hoá", - "backup_controller_page_background_app_refresh_enable_button_text": "Đi tới cài đặt", - "backup_controller_page_background_battery_info_link": "Hướng dẫn tôi", - "backup_controller_page_background_battery_info_message": "Để có trải nghiệm sao lưu nền tốt nhất, vui lòng vô hiệu hóa bất kỳ tối ưu hóa pin nào đang hạn chế hoạt động nền của Immich.\n\nVì điều này phụ thuộc vào thiết bị, vui lòng tham khảo thông tin cần thiết của nhà sản xuất thiết bị của bạn.", - "backup_controller_page_background_battery_info_ok": "Đồng ý", - "backup_controller_page_background_battery_info_title": "Tiết kiệm pin", - "backup_controller_page_background_charging": "Chỉ khi đang sạc", - "backup_controller_page_background_configure_error": "Cấu hình dịch vụ nền thất bại", - "backup_controller_page_background_delay": "Trì hoãn sao lưu tệp mới: {duration}", - "backup_controller_page_background_description": "Bật dịch vụ nền để tự động sao lưu tệp mới mà không cần mở app", - "backup_controller_page_background_is_off": "Sao lưu tự động trong nền đang tắt", - "backup_controller_page_background_is_on": "Sao lưu tự động trong nền đang bật", - "backup_controller_page_background_turn_off": "Tắt dịch vụ nền", - "backup_controller_page_background_turn_on": "Bật dịch vụ nền", - "backup_controller_page_background_wifi": "Chỉ khi dùng Wi-Fi", - "backup_controller_page_backup": "Sao lưu", - "backup_controller_page_backup_selected": "Đã chọn: ", - "backup_controller_page_backup_sub": "Ảnh và video đã sao lưu", - "backup_controller_page_created": "Tạo vào: {date}", - "backup_controller_page_desc_backup": "Bật sao lưu khi app hoạt động để tự động sao lưu tệp mới lên máy chủ khi mở app.", - "backup_controller_page_excluded": "Đã bỏ qua: ", - "backup_controller_page_failed": "Thất bại ({count})", - "backup_controller_page_filename": "Tên tệp: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Thông tin sao lưu", - "backup_controller_page_none_selected": "Không có mục nào được chọn", - "backup_controller_page_remainder": "Còn lại", - "backup_controller_page_remainder_sub": "Số lượng ảnh và video đã chọn chưa được sao lưu", - "backup_controller_page_server_storage": "Dung lượng máy chủ", - "backup_controller_page_start_backup": "Bắt đầu sao lưu", - "backup_controller_page_status_off": "Sao lưu tự động khi app hoạt động đang tắt", - "backup_controller_page_status_on": "Sao lưu tự động khi app hoạt động đang bật", - "backup_controller_page_storage_format": "Đã dùng {used} của {total}", - "backup_controller_page_to_backup": "Các album cần được sao lưu", - "backup_controller_page_total_sub": "Tất cả ảnh và video không trùng lập từ các album được chọn", - "backup_controller_page_turn_off": "Tắt sao lưu khi app hoạt động", - "backup_controller_page_turn_on": "Bật sao lưu khi mở app", - "backup_controller_page_uploading_file_info": "Thông tin tệp đang tải lên", - "backup_err_only_album": "Không thể xóa album duy nhất", - "backup_error_sync_failed": "Đồng bộ thất bại. Không thể tiến hành sao lưu.", - "backup_info_card_assets": "tệp", - "backup_manual_cancelled": "Đã hủy", - "backup_manual_in_progress": "Đang tải lên. Vui lòng thử lại sau", - "backup_manual_success": "Thành công", - "backup_manual_title": "Trạng thái tải lên", - "backup_options": "Tùy chọn sao lưu", - "backup_options_page_title": "Tùy chọn sao lưu", - "backup_setting_subtitle": "Quản lý cài đặt tải lên ở chế độ nền và khi đang mở", - "backup_settings_subtitle": "Cài đặt việc tải lên", - "backup_upload_details_page_more_details": "Nhấn để hiện chi tiết", - "backward": "Lùi lại", - "biometric_auth_enabled": "Đã bật xác thực sinh trắc học", - "biometric_locked_out": "Bạn đã bị khóa xác thực bằng sinh trắc học", - "biometric_no_options": "Không có tùy chọn bằng sinh trắc học", - "biometric_not_available": "Xác thực sinh trắc học không khả dụng trên thiết bị này", - "birthdate_saved": "Sinh nhật đã được lưu thành công", - "birthdate_set_description": "Sinh nhật được sử dụng để tính tuổi của người này tại thời điểm chụp ảnh.", - "blurred_background": "Nền mờ", - "bugs_and_feature_requests": "Báo lỗi & Đề xuất tính năng", - "build": "Dựng", - "build_image": "Bản dựng", - "bulk_delete_duplicates_confirmation": "Bạn có chắc muốn xóa hàng loạt {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}}? Điều này sẽ giữ lại ảnh chất lượng nhất của mỗi nhóm và xóa vĩnh viễn tất cả các bản trùng lặp khác. Bạn không thể hoàn tác hành động này!", - "bulk_keep_duplicates_confirmation": "Bạn có chắc muốn giữ lại {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}}? Điều này sẽ xử lý tất cả các nhóm ảnh trùng lặp mà không xóa bất kỳ thứ gì.", - "bulk_trash_duplicates_confirmation": "Bạn có chắc muốn đưa {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}} vào thùng rác? Điều này sẽ giữ lại ảnh chất lượng nhất của mỗi nhóm và đưa tất cả các bản trùng lặp khác vào thùng rác.", - "buy": "Mua Immich", - "cache_settings_clear_cache_button": "Xóa bộ nhớ đệm", - "cache_settings_clear_cache_button_title": "Xóa bộ nhớ đệm của app. Điều này sẽ ảnh hưởng đến hiệu suất của app đến khi bộ nhớ đệm được tạo lại.", - "cache_settings_duplicated_assets_clear_button": "XÓA", - "cache_settings_duplicated_assets_subtitle": "Ảnh và video không được phép hiển thị trên app", - "cache_settings_duplicated_assets_title": "Tệp bị trùng ({count})", - "cache_settings_statistics_album": "Ảnh thu nhỏ thư viện", - "cache_settings_statistics_full": "Ảnh đầy đủ", - "cache_settings_statistics_shared": "Ảnh thu nhỏ album chia sẻ", - "cache_settings_statistics_thumbnail": "Ảnh thu nhỏ", - "cache_settings_statistics_title": "Mức sử dụng bộ nhớ đệm", - "cache_settings_subtitle": "Kiểm soát hành vi bộ nhớ đệm của app Immich", - "cache_settings_tile_subtitle": "Kiểm soát cách xử lý lưu trữ cục bộ", - "cache_settings_tile_title": "Lưu trữ cục bộ", - "cache_settings_title": "Cài đặt bộ nhớ đệm", - "camera": "Máy ảnh", - "camera_brand": "Thương hiệu máy ảnh", - "camera_model": "Dòng máy ảnh", - "cancel": "Hủy", - "cancel_search": "Hủy tìm kiếm", - "canceled": "Hủy bỏ", - "canceling": "Đang hủy", - "cannot_merge_people": "Không thể hợp nhất người", - "cannot_undo_this_action": "Bạn không thể hoàn tác hành động này!", - "cannot_update_the_description": "Không thể cập nhật mô tả", - "cast": "Chiếu", - "cast_description": "Cấu hình các thiết bị chiếu khả dụng", - "change_date": "Thay đổi ngày", - "change_description": "Thay đổi mô tả", - "change_display_order": "Sắp xếp hiển thị", - "change_expiration_time": "Thay đổi thời gian hết hạn", - "change_location": "Thay đổi vị trí", - "change_name": "Đổi tên", - "change_name_successfully": "Đã đổi tên thành công", - "change_password": "Đổi mật khẩu", - "change_password_description": "Đây có thể là lần đầu tiên bạn đăng nhập vào hệ thống hoặc có yêu cầu thay đổi mật khẩu của bạn. Vui lòng nhập mật khẩu mới bên dưới.", - "change_password_form_confirm_password": "Xác nhận mật khẩu", - "change_password_form_description": "Xin chào {name},\n\nĐây là lần đầu tiên bạn đăng nhập vào hệ thống hoặc đã có yêu cầu thay đổi mật khẩu. Vui lòng nhập mật khẩu mới bên dưới.", - "change_password_form_log_out": "Đăng xuất tất cả thiết bị khác", - "change_password_form_log_out_description": "Được đề xuất", - "change_password_form_new_password": "Mật khẩu mới", - "change_password_form_password_mismatch": "Mật khẩu không giống nhau", - "change_password_form_reenter_new_password": "Nhập lại mật khẩu mới", - "change_pin_code": "Thay đổi mã PIN", - "change_trigger": "Thay đổi trình kích hoạt", - "change_trigger_prompt": "Bạn có chắc muốn thay đổi trình kích hoạt? Thao tác này sẽ xóa tất cả các hành động và bộ lọc hiện có.", - "change_your_password": "Đổi mật khẩu của bạn", - "changed_visibility_successfully": "Đã đổi trạng thái hiển thị thành công", - "charging": "Sạc", - "charging_requirement_mobile_backup": "Sao lưu dưới nền yêu cầu thiết bị phải đang sạc", - "check_corrupt_asset_backup": "Kiểm tra tệp bị hỏng", - "check_corrupt_asset_backup_button": "Tiến hành kiểm tra", - "check_corrupt_asset_backup_description": "Chỉ chạy kiểm tra này khi có Wi-Fi và sau khi đã sao lưu toàn bộ dữ liệu. Quá trình có thể mất vài phút.", - "check_logs": "Kiểm tra log", - "checksum": "Checksum", - "choose_matching_people_to_merge": "Chọn những người trùng khớp để hợp nhất", - "city": "Thành phố", - "clear": "Xóa", - "clear_all": "Xóa tất cả", - "clear_all_recent_searches": "Xóa tất cả tìm kiếm gần đây", - "clear_file_cache": "Xóa bộ nhớ đệm", - "clear_message": "Xóa thông điệp", - "clear_value": "Xóa giá trị", - "client_cert_dialog_msg_confirm": "Đồng ý", - "client_cert_enter_password": "Nhập mật khẩu", - "client_cert_import": "Nhập", - "client_cert_import_success_msg": "Chứng chỉ khách đã được nhập", - "client_cert_invalid_msg": "Tệp chứng chỉ không hợp lệ hoặc sai mật khẩu", - "client_cert_remove_msg": "Chứng chỉ khách đã bị xóa", - "client_cert_subtitle": "Chỉ hỗ trợ định dạng PKCS12 (.p12, .pfx). Chỉ có thể nhập/xóa chứng chỉ trước khi đăng nhập", - "client_cert_title": "Chứng chỉ khách SSL [THỬ NGHIỆM]", - "clockwise": "Xoay phải", - "close": "Đóng", - "collapse": "Thu gọn", - "collapse_all": "Thu gọn tất cả", - "color": "Màu", - "color_theme": "Màu chủ đề", - "command": "Dòng lệnh", - "comment_deleted": "Bình luận đã bị xóa", - "comment_options": "Tùy chọn bình luận", - "comments_and_likes": "Bình luận & lượt thích", - "comments_are_disabled": "Bình luận đã bị tắt", - "common_create_new_album": "Tạo album mới", - "completed": "Hoàn tất", - "confirm": "Xác nhận", - "confirm_admin_password": "Xác nhận mật khẩu quản trị viên", - "confirm_delete_face": "Bạn có chắc muốn xóa khuôn mặt {name} khỏi tệp?", - "confirm_delete_shared_link": "Bạn có chắc muốn xóa liên kết chia sẻ này?", - "confirm_keep_this_delete_others": "Các ảnh còn lại trong nhóm sẽ bị xóa ngoại trừ ảnh này. Bạn có chắc muốn tiếp tục?", - "confirm_new_pin_code": "Xác nhận mã PIN mới", - "confirm_password": "Xác nhận mật khẩu", - "confirm_tag_face": "Bạn có muốn gắn thẻ gương mặt này là {name}?", - "confirm_tag_face_unnamed": "Bạn có muốn gắn thẻ gương mặt này?", - "connected_device": "Thiết bị được kết nối", - "connected_to": "Đã kết nối tới", - "contain": "Vừa màn hình", - "context": "Ngữ cảnh", - "continue": "Tiếp tục", - "control_bottom_app_bar_create_new_album": "Tạo album mới", - "control_bottom_app_bar_delete_from_immich": "Xóa khỏi Immich", - "control_bottom_app_bar_delete_from_local": "Xóa khỏi thiết bị", - "control_bottom_app_bar_edit_location": "Chỉnh sửa vị trí", - "control_bottom_app_bar_edit_time": "Chỉnh sửa Ngày và Giờ", - "control_bottom_app_bar_share_link": "Chia sẻ liên kết", - "control_bottom_app_bar_share_to": "Chia sẻ với", - "control_bottom_app_bar_trash_from_immich": "Di chuyển đến Thùng rác", - "copied_image_to_clipboard": "Đã sao chép ảnh vào bộ nhớ tạm.", - "copied_to_clipboard": "Đã sao chép vào bộ nhớ tạm!", - "copy_error": "Sao chép lỗi", - "copy_file_path": "Sao chép đường dẫn tệp", - "copy_image": "Sao chép ảnh", - "copy_link": "Sao chép liên kết", - "copy_link_to_clipboard": "Sao chép liên kết vào bộ nhớ tạm", - "copy_password": "Sao chép mật khẩu", - "copy_to_clipboard": "Sao chép vào bộ nhớ tạm", - "country": "Quốc gia", - "cover": "Tối đa", - "covers": "Lưới", - "create": "Tạo", - "create_album": "Tạo album", - "create_album_page_untitled": "Không tên", - "create_api_key": "Tạo khóa API", - "create_first_workflow": "Tạo workflow đầu tiên", - "create_library": "Tạo thư viện", - "create_link": "Tạo liên kết", - "create_link_to_share": "Tạo liên kết để chia sẻ", - "create_link_to_share_description": "Ai có liên kết sẽ xem được các ảnh đã chọn", - "create_new": "TẠO MỚI", - "create_new_person": "Tạo người mới", - "create_new_person_hint": "Gán các ảnh đã chọn cho một người mới", - "create_new_user": "Tạo người dùng mới", - "create_shared_album_page_share_add_assets": "THÊM TỆP", - "create_shared_album_page_share_select_photos": "Chọn ảnh", - "create_shared_link": "Chia sẻ qua liên kết", - "create_tag": "Tạo thẻ", - "create_tag_description": "Tạo thẻ mới. Với các thẻ lồng nhau, vui lòng nhập đường dẫn đầy đủ của thẻ bao gồm dấu gạch chéo.", - "create_user": "Tạo người dùng", - "create_workflow": "Tạo workflow", - "created": "Đã tạo", - "created_at": "Đã tạo", - "creating_linked_albums": "Đang tạo album được liên kết...", - "crop": "Cắt", - "curated_object_page_title": "Đối tượng", - "current_device": "Thiết bị hiện tại", - "current_pin_code": "Mã PIN hiện tại", - "current_server_address": "Địa chủ máy chủ hiện tại", - "custom_locale": "Ngôn ngữ và khu vực", - "custom_locale_description": "Định dạng ngày và số dựa trên ngôn ngữ và khu vực", - "custom_url": "URL tùy chỉnh", - "daily_title_text_date": "E, dd MMM", - "daily_title_text_date_year": "E, dd MMM, yyyy", - "dark": "Tối", - "dark_theme": "Đổi giao diện", - "date": "Ngày", - "date_after": "Ngày sau", - "date_and_time": "Ngày và giờ", - "date_before": "Ngày trước", - "date_format": "E, d LLL, y • h:mm a", - "date_of_birth_saved": "Sinh nhật đã được lưu thành công", - "date_range": "Khoảng thời gian", - "day": "Ngày", - "days": "Ngày", - "deduplicate_all": "Xóa tất cả mục trùng lặp", - "deduplication_criteria_1": "Kích cỡ ảnh theo byte", - "deduplication_criteria_2": "Số lượng dữ liệu EXIF", - "deduplication_info": "Thông tin loại bỏ dữ liệu trùng lặp", - "deduplication_info_description": "Để tự động chọn trước và loại bỏ các tệp trùng lặp hàng loạt, chúng tôi sẽ xem xét dựa trên:", - "default_locale": "Định dạng ngày giờ", - "default_locale_description": "Dựa theo trình duyệt của bạn", - "delete": "Xóa", - "delete_action_confirmation_message": "Bạn có chắc muốn xóa tệp này? Thao tác này sẽ chuyển tệp vào thùng rác của máy chủ và sẽ hỏi bạn có muốn xóa nó cục bộ không", - "delete_action_prompt": "{count} đã xóa", - "delete_album": "Xóa album", - "delete_api_key_prompt": "Bạn có chắc muốn xóa khóa API này?", - "delete_dialog_alert": "Những mục này sẽ bị xóa vĩnh viễn khỏi Immich và thiết bị của bạn", - "delete_dialog_alert_local": "Những mục này sẽ bị xóa vĩnh viễn khỏi thiết bị của bạn nhưng vẫn còn lưu trữ trên máy chủ Immich", - "delete_dialog_alert_local_non_backed_up": "Một số mục chưa được sao lưu lên Immich và sẽ bị xóa vĩnh viễn khỏi thiết bị của bạn", - "delete_dialog_alert_remote": "Những mục này sẽ bị xóa vĩnh viễn khỏi máy chủ Immich", - "delete_dialog_ok_force": "Xóa Vĩnh Viễn", - "delete_dialog_title": "Xóa vĩnh viễn", - "delete_duplicates_confirmation": "Bạn có chắc muốn xóa vĩnh viễn các bản trùng lặp này?", - "delete_face": "Xóa khuôn mặt", - "delete_key": "Xóa khóa", - "delete_library": "Xóa Thư viện", - "delete_link": "Xóa liên kết", - "delete_local_action_prompt": "{count} đã xóa trên thiết bị", - "delete_local_dialog_ok_backed_up_only": "Xóa ảnh đã sao lưu", - "delete_local_dialog_ok_force": "Vẫn xóa", - "delete_others": "Xóa ảnh còn lại", - "delete_permanently": "Xóa vĩnh viễn", - "delete_permanently_action_prompt": "{count} đã xóa vĩnh viễn", - "delete_shared_link": "Xóa liên kết chia sẻ", - "delete_shared_link_dialog_title": "Xóa liên kết đã chia sẻ", - "delete_tag": "Xóa thẻ", - "delete_tag_confirmation_prompt": "Bạn có chắc muốn xóa thẻ {tagName}?", - "delete_user": "Xóa người dùng", - "deleted_shared_link": "Đã xóa liên kết chia sẻ", - "deletes_missing_assets": "Xóa các ảnh không còn tồn tại trên ổ đĩa", - "description": "Mô tả", - "description_input_hint_text": "Thêm mô tả...", - "description_input_submit_error": "Cập nhật mô tả không thành công, vui lòng kiểm tra log để biết thêm chi tiết", - "deselect_all": "Bỏ chọn tất cả", - "details": "Chi tiết", - "direction": "Hướng", - "disable": "Vô hiệu hóa", - "disabled": "Đã tắt", - "disallow_edits": "Không cho phép chỉnh sửa", - "discord": "Discord", - "discover": "Khám phá", - "discovered_devices": "Khám phá thiết bị", - "dismiss_all_errors": "Bỏ qua tất cả lỗi", - "dismiss_error": "Bỏ qua lỗi", - "display_options": "Tùy chọn hiển thị", - "display_order": "Thứ tự hiển thị", - "display_original_photos": "Hiển thị ảnh gốc", - "display_original_photos_setting_description": "Ưu tiên hiển thị ảnh gốc thay vì ảnh thu nhỏ, khi ảnh gốc tương thích với web. Điều này có thể khiến tốc độ hiển thị ảnh chậm hơn.", - "do_not_show_again": "Không hiện thông báo này nữa", - "documentation": "Tài liệu", - "done": "Xong", - "download": "Tải xuống", - "download_action_prompt": "Đang tải {count} tệp", - "download_canceled": "Đã hủy tải xuống", - "download_complete": "Tải xuống hoàn tất", - "download_enqueue": "Tải xuống đang chờ", - "download_error": "Lỗi tải xuống", - "download_failed": "Tải xuống thất bại", - "download_finished": "Tải xuống hoàn tất", - "download_include_embedded_motion_videos": "Video nhúng", - "download_include_embedded_motion_videos_description": "Gồm các video được nhúng trong ảnh chuyển động thành một tệp riêng", - "download_notfound": "Không tìm thấy tải xuống", - "download_paused": "Đã tạm dừng tải xuống", - "download_settings": "Tải xuống", - "download_settings_description": "Quản lý cài đặt liên quan đến việc tải ảnh xuống", - "download_started": "Đã bắt đầu tải xuống", - "download_sucess": "Tải xuống thành công", - "download_sucess_android": "Phương tiện đã được lưu vào DCIM/Immich", - "download_waiting_to_retry": "Đang chờ thử lại", - "downloading": "Đang tải xuống", - "downloading_asset_filename": "Đang tải xuống tệp {filename}", - "downloading_media": "Đang tải xuống phương tiện", - "drop_files_to_upload": "Kéo thả các tệp để tải lên", - "duplicates": "Tệp trùng lặp", - "duplicates_description": "Xem lại các nhóm ảnh bị nghi ngờ trùng lặp và chọn những mục bạn muốn giữ hoặc xóa", - "duration": "Thời gian", - "edit": "Chỉnh sửa", - "edit_album": "Chỉnh sửa album", - "edit_avatar": "Chỉnh sửa ảnh đại diện", - "edit_birthday": "Sửa ngày sinh", - "edit_date": "Chỉnh sửa ngày", - "edit_date_and_time": "Chỉnh sửa ngày và giờ", - "edit_date_and_time_action_prompt": "{count} đã sửa ngày và giờ", - "edit_date_and_time_by_offset": "Thay đổi ngày theo độ lệch", - "edit_date_and_time_by_offset_interval": "Khoảng ngày mới: {from} - {to}", - "edit_description": "Chỉnh sửa mô tả", - "edit_description_prompt": "Vui lòng chọn một mô tả mới:", - "edit_exclusion_pattern": "Chỉnh sửa quy tắc loại trừ", - "edit_faces": "Chỉnh sửa khuôn mặt", - "edit_key": "Chỉnh sửa khóa", - "edit_link": "Chỉnh sửa liên kết", - "edit_location": "Chỉnh sửa vị trí", - "edit_location_action_prompt": "{count} đã sửa vị trí", - "edit_location_dialog_title": "Vị trí", - "edit_name": "Chỉnh sửa tên", - "edit_people": "Chỉnh sửa người", - "edit_tag": "Chỉnh sửa thẻ", - "edit_title": "Chỉnh sửa tiêu đề", - "edit_user": "Chỉnh sửa người dùng", - "edit_workflow": "Sửa workflow", - "editor": "Trình chỉnh sửa", - "editor_close_without_save_prompt": "Những thay đổi sẽ không được lưu", - "editor_close_without_save_title": "Đóng trình chỉnh sửa?", - "email": "Email", - "email_notifications": "Thông báo qua email", - "empty_folder": "Thư mục trống", - "empty_trash": "Dọn sạch thùng rác", - "empty_trash_confirmation": "Bạn có chắc muốn dọn sạch thùng rác? Điều này sẽ xóa vĩnh viễn tất cả các tệp trong thùng rác khỏi Immich.\nBạn không thể hoàn tác hành động này!", - "enable": "Bật", - "enable_backup": "Bật sao lưu", - "enable_biometric_auth_description": "Nhập mã PIN của bạn để bật xác thực sinh trắc học", - "enabled": "Đã bật", - "end_date": "Ngày kết thúc", - "enqueued": "Đã xếp hàng", - "enter_wifi_name": "Nhập tên Wi-Fi", - "enter_your_pin_code": "Nhập mã PIN của bạn", - "enter_your_pin_code_subtitle": "Nhập mã PIN của bạn để truy cập thư mục Khóa", - "error": "Lỗi", - "error_change_sort_album": "Thay đổi thứ tự sắp xếp album thất bại", - "error_delete_face": "Lỗi khi xóa khuôn mặt khỏi tệp", - "error_getting_places": "Lỗi khi lấy địa điểm", - "error_loading_image": "Lỗi tải ảnh", - "error_loading_partners": "Lỗi khi lấy người thân: {error}", - "error_saving_image": "Lỗi: {error}", - "error_tag_face_bounding_box": "Lỗi gắn thẻ khuôn mặt: - không thể lấy được tọa độ khung bao", - "error_title": "Lỗi - Có điều gì đó không đúng", - "errors": { - "cannot_navigate_next_asset": "Không thể chuyển đến tệp tiếp theo", - "cannot_navigate_previous_asset": "Không thể chuyển đến tệp trước đó", - "cant_apply_changes": "Không thể áp dụng thay đổi", - "cant_change_activity": "Không thể {enabled, select, true {disable} other {enable}} hoạt động", - "cant_change_asset_favorite": "Không thể thay đổi việc thích tệp", - "cant_change_metadata_assets_count": "Không thể thay đổi siêu dữ liệu của {count, plural, one {# tệp} other {# tệp}}", - "cant_get_faces": "Không thể tải khuôn mặt", - "cant_get_number_of_comments": "Không thể tải số lượng bình luận", - "cant_search_people": "Không thể tìm người", - "cant_search_places": "Không thể tìm kiếm địa điểm", - "error_adding_assets_to_album": "Lỗi khi thêm tệp vào album", - "error_adding_users_to_album": "Lỗi khi thêm người dùng vào album", - "error_deleting_shared_user": "Lỗi khi xóa người dùng chia sẻ", - "error_downloading": "Lỗi khi tải xuống {filename}", - "error_hiding_buy_button": "Lỗi khi ẩn nút mua", - "error_removing_assets_from_album": "Lỗi khi xóa tệp khỏi album, kiểm tra bảng điều khiển để biết thêm chi tiết", - "error_selecting_all_assets": "Lỗi khi chọn tất cả tệp", - "exclusion_pattern_already_exists": "Quy tắc loại trừ này đã tồn tại.", - "failed_to_create_album": "Tạo album thất bại", - "failed_to_create_shared_link": "Không thể tạo liên kết chia sẻ", - "failed_to_edit_shared_link": "Không thể chỉnh sửa liên kết chia sẻ", - "failed_to_get_people": "Không thể tải người", - "failed_to_keep_this_delete_others": "Xảy ra lỗi trong quá trình xóa tệp", - "failed_to_load_asset": "Không thể tải tệp", - "failed_to_load_assets": "Không thể tải các tệp", - "failed_to_load_notifications": "Không thể tải các thông báo", - "failed_to_load_people": "Không thể tải người", - "failed_to_remove_product_key": "Không thể xóa khóa sản phẩm", - "failed_to_reset_pin_code": "Đặt lại mã PIN không thành công", - "failed_to_stack_assets": "Không thể xếp nhóm tệp", - "failed_to_unstack_assets": "Không thể hủy xếp nhóm tệp", - "failed_to_update_notification_status": "Cập nhật trạng thái thông báo thất bại", - "incorrect_email_or_password": "Email hoặc mật khẩu không chính xác", - "library_folder_already_exists": "Đường dẫn nhập này đã tồn tại.", - "paths_validation_failed": "{paths, plural, one {# đường dẫn} other {# đường dẫn}} không hợp lệ", - "profile_picture_transparent_pixels": "Ảnh đại diện không thể có điểm ảnh trong suốt. Vui lòng phóng to và/hoặc di chuyển hình ảnh.", - "quota_higher_than_disk_size": "Bạn đã đặt hạn mức cao hơn dung lượng ổ đĩa", - "something_went_wrong": "Có gì đó không đúng", - "unable_to_add_album_users": "Không thể thêm người dùng vào album", - "unable_to_add_assets_to_shared_link": "Không thể thêm tệp vào liên kết chia sẻ", - "unable_to_add_comment": "Không thể thêm bình luận", - "unable_to_add_exclusion_pattern": "Không thể thêm quy tắc loại trừ", - "unable_to_add_partners": "Không thể thêm người thân", - "unable_to_add_remove_archive": "Không thể {archived, select, true {xóa tệp khỏi} other {thêm tệp vào}} Kho lưu trữ", - "unable_to_add_remove_favorites": "Không thể {favorite, select, true {thêm tệp vào} other {xóa tệp khỏi}} Mục yêu thích", - "unable_to_archive_unarchive": "Không thể {archived, select, true {lưu trữ} other {bỏ lưu trữ}}", - "unable_to_change_album_user_role": "Không thể thay đổi vai trò của người dùng album", - "unable_to_change_date": "Không thể thay đổi ngày", - "unable_to_change_description": "Không thể thay đổi mô tả", - "unable_to_change_favorite": "Không thể thay đổi việc thích tệp", - "unable_to_change_location": "Không thể thay đổi vị trí", - "unable_to_change_password": "Không thể thay đổi mật khẩu", - "unable_to_change_visibility": "Không thể thay đổi trạng thái hiển thị cho {count, plural, one {# người} other {# người}}", - "unable_to_complete_oauth_login": "Không thể hoàn tất đăng nhập OAuth", - "unable_to_connect": "Không thể kết nối", - "unable_to_copy_to_clipboard": "Không thể sao chép vào bộ nhớ tạm, hãy đảm bảo bạn đang truy cập trang qua https", - "unable_to_create": "Không thể tạo workflow", - "unable_to_create_admin_account": "Không thể tạo tài khoản quản trị viên", - "unable_to_create_api_key": "Không thể tạo khóa API mới", - "unable_to_create_library": "Không thể tạo thư viện", - "unable_to_create_user": "Không thể tạo người dùng", - "unable_to_delete_album": "Không thể xóa album", - "unable_to_delete_asset": "Không thể xóa tệp", - "unable_to_delete_assets": "Lỗi khi xóa các tệp", - "unable_to_delete_exclusion_pattern": "Không thể xóa quy tắc loại trừ", - "unable_to_delete_shared_link": "Không thể xóa liên kết chia sẻ", - "unable_to_delete_user": "Không thể xóa người dùng", - "unable_to_delete_workflow": "Không thể xóa workflow", - "unable_to_download_files": "Không thể tải xuống tệp", - "unable_to_edit_exclusion_pattern": "Không thể chỉnh sửa quy tắc loại trừ", - "unable_to_empty_trash": "Không thể dọn sạch thùng rác", - "unable_to_enter_fullscreen": "Không thể vào chế độ toàn màn hình", - "unable_to_exit_fullscreen": "Không thể thoát chế độ toàn màn hình", - "unable_to_get_comments_number": "Không thể lấy số lượng bình luận", - "unable_to_get_shared_link": "Không thể lấy liên kết chia sẻ", - "unable_to_hide_person": "Không thể ẩn người", - "unable_to_link_motion_video": "Không thể liên kết video chuyển động", - "unable_to_link_oauth_account": "Không thể liên kết tài khoản OAuth", - "unable_to_log_out_all_devices": "Không thể đăng xuất khỏi tất cả thiết bị", - "unable_to_log_out_device": "Không thể đăng xuất khỏi thiết bị", - "unable_to_login_with_oauth": "Không thể đăng nhập với OAuth", - "unable_to_play_video": "Không thể phát video", - "unable_to_reassign_assets_existing_person": "Không thể gán lại tệp cho {name, select, null {một người hiện có} other {{name}}}", - "unable_to_reassign_assets_new_person": "Không thể gán lại ảnh cho một người mới", - "unable_to_refresh_user": "Không thể làm mới người dùng", - "unable_to_remove_album_users": "Không thể xóa người dùng khỏi album", - "unable_to_remove_api_key": "Không thể xóa khóa API", - "unable_to_remove_assets_from_shared_link": "Không thể xóa các mục đã chọn khỏi liên kết chia sẻ", - "unable_to_remove_library": "Không thể xóa thư viện", - "unable_to_remove_partner": "Không thể xóa người thân", - "unable_to_remove_reaction": "Không thể xóa phản ứng", - "unable_to_reset_password": "Không thể đặt lại mật khẩu", - "unable_to_reset_pin_code": "Không thể đặt lại mã PIN", - "unable_to_resolve_duplicate": "Không thể xử lý trùng lặp", - "unable_to_restore_assets": "Không thể khôi phục tệp", - "unable_to_restore_trash": "Không thể khôi phục thùng rác", - "unable_to_restore_user": "Không thể khôi phục người dùng", - "unable_to_save_album": "Không thể lưu album", - "unable_to_save_api_key": "Không thể lưu khóa API", - "unable_to_save_date_of_birth": "Không thể lưu sinh nhật", - "unable_to_save_name": "Không thể lưu tên", - "unable_to_save_profile": "Không thể lưu hồ sơ", - "unable_to_save_settings": "Không thể lưu cài đặt", - "unable_to_scan_libraries": "Không thể quét các thư viện", - "unable_to_scan_library": "Không thể quét thư viện", - "unable_to_set_feature_photo": "Không thể đặt ảnh nổi bật", - "unable_to_set_profile_picture": "Không thể đặt ảnh đại diện", - "unable_to_set_rating": "Không thể đặt đánh giá", - "unable_to_submit_job": "Không thể gửi tác vụ", - "unable_to_trash_asset": "Không thể chuyển ảnh vào thùng rác", - "unable_to_unlink_account": "Không thể hủy liên kết tài khoản", - "unable_to_unlink_motion_video": "Không thể hủy liên kết video chuyển động", - "unable_to_update_album_cover": "Không thể cập nhật ảnh bìa album", - "unable_to_update_album_info": "Không thể cập nhật thông tin album", - "unable_to_update_library": "Không thể cập nhật thư viện", - "unable_to_update_location": "Không thể cập nhật vị trí", - "unable_to_update_settings": "Không thể cập nhật cài đặt", - "unable_to_update_timeline_display_status": "Không thể cập nhật trạng thái hiển thị dòng thời gian", - "unable_to_update_user": "Không thể cập nhật người dùng", - "unable_to_update_workflow": "Không thể cập nhật workflow", - "unable_to_upload_file": "Không thể tải tệp lên" - }, - "exclusion_pattern": "Mẫu ngoại lệ", - "exif": "Exif", - "exif_bottom_sheet_description": "Thêm mô tả...", - "exif_bottom_sheet_description_error": "Lỗi cập nhật mô tả", - "exif_bottom_sheet_details": "CHI TIẾT", - "exif_bottom_sheet_location": "ĐỊA ĐIỂM", - "exif_bottom_sheet_no_description": "Không có mô tả", - "exif_bottom_sheet_people": "MỌI NGƯỜI", - "exif_bottom_sheet_person_add_person": "Thêm tên", - "exit_slideshow": "Thoát trình chiếu", - "expand_all": "Mở rộng tất cả", - "experimental_settings_new_asset_list_subtitle": "Đang phát triển", - "experimental_settings_new_asset_list_title": "Bật lưới ảnh thử nghiệm", - "experimental_settings_subtitle": "Sử dụng có thể rủi ro!", - "experimental_settings_title": "Thử nghiệm", - "expire_after": "Hết hạn sau", - "expired": "Hết hạn", - "expires_date": "Hết hạn vào {date}", - "explore": "Khám phá", - "explorer": "Khám phá", - "export": "Xuất", - "export_as_json": "Xuất dưới dạng JSON", - "export_database": "Xuất cơ sở dữ liệu", - "export_database_description": "Xuất cơ sở dữ liệu SQLite", - "extension": "Phần mở rộng", - "external": "Bên ngoài", - "external_libraries": "Thư viện bên ngoài", - "external_network": "Mạng bên ngoài", - "external_network_sheet_info": "Khi không ở trên mạng Wi-Fi ưu tiên, app sẽ kết nối với máy chủ thông qua URL đầu tiên bên dưới mà nó có thể truy cập, bắt đầu từ trên xuống dưới", - "face_unassigned": "Chưa được gán", - "failed": "Thất bại", - "failed_count": "Thất bại: {count}", - "failed_to_authenticate": "Xác thực thất bại", - "failed_to_load_assets": "Không tải được tệp", - "failed_to_load_folder": "Không tải được thư mục", - "favorite": "Thích", - "favorite_action_prompt": "{count} đã thêm vào Đã thích", - "favorite_or_unfavorite_photo": "Thích hoặc bỏ thích ảnh", - "favorites": "Đã thích", - "favorites_page_no_favorites": "Không tìm thấy tệp yêu thích", - "feature_photo_updated": "Đã cập nhật ảnh nổi bật", - "features": "Tính năng", - "features_in_development": "Tính năng đang được phát triển", - "features_setting_description": "Quản lý các tính năng app", - "file_name_or_extension": "Tên hoặc phần mở rộng tập tin", - "file_size": "Kích cỡ tệp tin", - "filename": "Tên tệp", - "filetype": "Loại tệp", - "filter": "Bộ lọc", - "filter_description": "Điều kiện để lọc tệp mục tiêu", - "filter_people": "Lọc người", - "filter_places": "Lọc địa điểm", - "filters": "Bộ lọc", - "find_them_fast": "Tìm nhanh bằng tên với tìm kiếm", - "first": "Đầu tiên", - "fix_incorrect_match": "Sửa lỗi trùng khớp không chính xác", - "folder": "Thư mục", - "folder_not_found": "Không tìm thấy thư mục", - "folders": "Thư mục", - "folders_feature_description": "Duyệt ảnh và video theo thư mục trên hệ thống tệp", - "forgot_pin_code_question": "Quên mã PIN?", - "forward": "Tiến tới", - "full_path": "Đường dẫn đầy đủ: {path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Tính năng này tải các tài nguyên bên ngoài từ Google để hoạt động.", - "general": "Chung", - "geolocation_instruction_location": "Nhấn vào một tệp có tọa độ GPS để sử dụng vị trí của nó hoặc chọn vị trí trực tiếp từ bản đồ", - "get_help": "Nhận trợ giúp", - "get_people_error": "Lỗi khi lấy thông tin người", - "get_wifiname_error": "Không thể lấy tên Wi-Fi. Hãy đảm bảo bạn đã cấp các quyền cần thiết và được kết nối với mạng Wi-Fi", - "getting_started": "Bắt đầu", - "go_back": "Quay lại", - "go_to_folder": "Đi tới thư mục", - "go_to_search": "Đi tới tìm kiếm", - "gps": "GPS", - "gps_missing": "Không GPS", - "grant_permission": "Cấp quyền", - "group_albums_by": "Xếp nhóm album theo...", - "group_country": "Nhóm theo quốc gia", - "group_no": "Không nhóm", - "group_owner": "Xếp nhóm theo chủ sở hữu", - "group_places_by": "Nhóm địa điểm theo…", - "group_year": "Xếp nhóm theo năm", - "haptic_feedback_switch": "Bật phản hồi haptic", - "haptic_feedback_title": "Phản hồi Hapic", - "has_quota": "Hạn mức", - "hash_asset": "Mã hóa tệp", - "hashed_assets": "Tệp mã hóa", - "hashing": "Đang băm", - "header_settings_add_header_tip": "Thêm header", - "header_settings_field_validator_msg": "Không được để trống", - "header_settings_header_name_input": "Tên Header", - "header_settings_header_value_input": "Giá trị Header", - "headers_settings_tile_title": "Tiêu đề proxy tùy chỉnh", - "height": "Chiều cao", - "hi_user": "Chào {name} ({email})", - "hide_all_people": "Ẩn tất cả mọi người", - "hide_gallery": "Ẩn thư viện", - "hide_named_person": "Ẩn người {name}", - "hide_password": "Ẩn mật khẩu", - "hide_person": "Ẩn người", - "hide_schema": "Ẩn lược đồ", - "hide_text_recognition": "Ẩn nhận dạng văn bản", - "hide_unnamed_people": "Ẩn những người không tên", - "home_page_add_to_album_conflicts": "Đã thêm {added} tệp vào album {album}. {failed} tệp đã có sẵn trong album.", - "home_page_add_to_album_err_local": "Không thể thêm tệp trên thiết bị vào album, bỏ qua", - "home_page_add_to_album_success": "Đã thêm {added} tệp vào album {album}.", - "home_page_album_err_partner": "Không thể thêm tệp của nguời thân vào album, bỏ qua", - "home_page_archive_err_local": "Không thể lưu trữ tệp trên thiết bị, bỏ qua", - "home_page_archive_err_partner": "Không thể lưu trữ tệp của người thân, bỏ qua", - "home_page_building_timeline": "Đang tạo dòng thời gian ảnh", - "home_page_delete_err_partner": "Không thể xóa tệp của người thân, bỏ qua", - "home_page_delete_remote_err_local": "Tệp trên thiết bị trong lựa chọn xóa từ xa, bỏ qua", - "home_page_favorite_err_local": "Không thể thích tệp trên thiết bị, bỏ qua", - "home_page_favorite_err_partner": "Không thể thích tệp của người thân, bỏ qua", - "home_page_first_time_notice": "Nếu đây là lần đầu bạn dùng app, hãy chọn một album sao lưu để dòng thời gian có thể hiển thị ảnh và video của bạn", - "home_page_locked_error_local": "Không thể di chuyển tệp trên thiết bị đến thư mục Khóa, bỏ qua", - "home_page_locked_error_partner": "Không thể di chuyển tệp của người thân đến thư mục Khóa, bỏ qua", - "home_page_share_err_local": "Không thể chia sẻ tệp trên thiết bị qua liên kết, bỏ qua", - "home_page_upload_err_limit": "Chỉ có thể tải lên tối đa 30 tệp cùng lúc, bỏ qua", - "host": "Host", - "hour": "Giờ", - "hours": "Giờ", - "id": "ID", - "idle": "Nhàn rỗi", - "ignore_icloud_photos": "Bỏ qua ảnh iCloud", - "ignore_icloud_photos_description": "Ảnh được lưu trữ trên iCloud sẽ không được tải lên máy chủ Immich", - "image": "Ảnh", - "image_alt_text_date": "{isVideo, select, true {Video} other {Ảnh}} được chụp vào {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Ảnh}} được chụp với {person1} vào {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Ảnh}} được chụp với {person1} và {person2} vào {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Ảnh}} được chụp với {person1}, {person2}, và {person3} vào {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Ảnh}} được chụp với {person1}, {person2}, và {additionalCount, number} người khác vào {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Ảnh}} được chụp tại {city}, {country} vào {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Ảnh}} được chụp tại {city}, {country} với {person1} vào {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Ảnh}} được chụp tại {city}, {country} với {person1} và {person2} vào {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Ảnh}} được chụp tại {city}, {country} với {person1}, {person2}, và {person3} vào {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Ảnh}} được chụp tại {city}, {country} với {person1}, {person2}, và {additionalCount, number} người khác vào {date}", - "image_saved_successfully": "Đã lưu ảnh", - "image_viewer_page_state_provider_download_started": "Đã bắt đầu tải xuống", - "image_viewer_page_state_provider_download_success": "Tải xuống thành công", - "image_viewer_page_state_provider_share_error": "Chia sẻ không thành công", - "immich_logo": "Logo Immich", - "immich_web_interface": "Giao diện web Immich", - "import_from_json": "Nhập từ JSON", - "import_path": "Đường dẫn nhập", - "in_albums": "Trong {count, plural, one {# album} other {# album}}", - "in_archive": "Trong kho lưu trữ", - "in_year": "Vào {year}", - "in_year_selector": "Năm", - "include_archived": "Bao gồm ảnh lưu trữ", - "include_shared_albums": "Bao gồm album chia sẻ", - "include_shared_partner_assets": "Bao gồm ảnh người thân chia sẻ", - "individual_share": "Chia sẻ riêng lẻ", - "individual_shares": "Chia sẻ cá nhân", - "info": "Thông tin", - "interval": { - "day_at_onepm": "Mỗi ngày vào lúc 1 giờ chiều", - "hours": "Mỗi {hours, plural, one {giờ} other {{hours, number} giờ}}", - "night_at_midnight": "Mỗi đêm vào lúc nửa đêm", - "night_at_twoam": "Mỗi đêm vào lúc 2 giờ sáng" - }, - "invalid_date": "Ngày không hợp lệ", - "invalid_date_format": "Định dạng ngày không hợp lệ", - "invite_people": "Mời mọi người", - "invite_to_album": "Mời vào album", - "ios_debug_info_fetch_ran_at": "Lấy dữ liệu đã chạy vào {dateTime}", - "ios_debug_info_last_sync_at": "Đồng bộ lần cuối vào {dateTime}", - "ios_debug_info_no_processes_queued": "Không có tiến trình nền trong hàng đợi", - "ios_debug_info_no_sync_yet": "Chưa có tác vụ đồng bộ nền chạy", - "ios_debug_info_processes_queued": "{count, plural, one {{count} tiến trình nền đã được đưa vào hàng đợi} other {{count} tiến trình nền đã được đưa vào hàng đợi}}", - "ios_debug_info_processing_ran_at": "Quá trình xử lý đã chạy vào {dateTime}", - "items_count": "{count, plural, one {# mục} other {# mục}}", - "jobs": "Tác vụ", - "json_editor": "Biên tập JSON", - "json_error": "Lỗi JSON", - "keep": "Giữ", - "keep_all": "Giữ tất cả", - "keep_this_delete_others": "Giữ tệp này, xóa các tệp khác", - "kept_this_deleted_others": "Đã giữ lại tệp này và xóa {count, plural, one {# tệp} other {# tệp}}", - "keyboard_shortcuts": "Phím tắt", - "language": "Ngôn ngữ", - "language_no_results_subtitle": "Thử điều chỉnh quy tắc tìm kiếm", - "language_no_results_title": "Không tìm thấy ngôn ngữ", - "language_search_hint": "Tìm kiếm ngôn ngữ...", - "language_setting_description": "Chọn ngôn ngữ ưa thích của bạn", - "large_files": "Tệp lớn", - "last": "Cuối cùng", - "last_months": "{count, plural, one {Tháng trước} other {# tháng}} trước", - "last_seen": "Lần cuối nhìn thấy", - "latest_version": "Phiên bản mới nhất", - "latitude": "Vĩ độ", - "leave": "Rời khỏi", - "leave_album": "Rời khỏi album", - "lens_model": "Lens", - "let_others_respond": "Cho phép bình luận", - "level": "Mức độ", - "library": "Thư viện", - "library_add_folder": "Thêm thư mục", - "library_edit_folder": "Sửa thư mục", - "library_options": "Tùy chọn thư viện", - "library_page_device_albums": "Album trên thiết bị", - "library_page_new_album": "Album mới", - "library_page_sort_asset_count": "Số lượng tệp", - "library_page_sort_created": "Mới tạo gần đây", - "library_page_sort_last_modified": "Sửa đổi lần cuối", - "library_page_sort_title": "Tên album", - "licenses": "Giấy phép", - "light": "Sáng", - "like": "Thích", - "like_deleted": "Đã bỏ thích", - "link_motion_video": "Liên kết video chuyển động", - "link_to_oauth": "Liên kết đến OAuth", - "linked_oauth_account": "Tài khoản OAuth đã liên kết", - "list": "Danh sách", - "loading": "Đang tải", - "loading_search_results_failed": "Tải kết quả tìm kiếm không thành công", - "local": "Trên thiết bị", - "local_asset_cast_failed": "Không thể chiếu nội dung chưa được tải lên máy chủ", - "local_assets": "Tệp trên thiết bị", - "local_id": "ID cục bộ", - "local_media_summary": "Mô tả phương tiện trên thiết bị", - "local_network": "Mạng nội bộ", - "local_network_sheet_info": "App sẽ kết nối với máy chủ qua URL này khi sử dụng mạng Wi-Fi được chỉ định", - "location": "Địa điểm", - "location_permission": "Quyền truy cập vị trí", - "location_permission_content": "Để sử dụng tính năng tự động chuyển đổi, Immich cần có quyền vị trí chính xác để có thể đọc tên của mạng Wi-Fi hiện tại", - "location_picker_choose_on_map": "Chọn trên bản đồ", - "location_picker_latitude_error": "Nhập vĩ độ hợp lệ", - "location_picker_latitude_hint": "Nhập vĩ độ của bạn", - "location_picker_longitude_error": "Nhập kinh độ hợp lệ", - "location_picker_longitude_hint": "Nhập kinh độ của bạn", - "lock": "Khóa", - "locked_folder": "Khóa", - "log_detail_title": "Chi tiết log", - "log_out": "Đăng xuất", - "log_out_all_devices": "Đăng xuất tất cả thiết bị", - "logged_in_as": "Đã đăng nhập {user}", - "logged_out_all_devices": "Tất cả các thiết bị đã đăng xuất", - "logged_out_device": "Thiết bị đã đăng xuất", - "login": "Đăng nhập", - "login_disabled": "Đăng nhập bị vô hiệu hoá", - "login_form_api_exception": "Lỗi API. Vui lòng kiểm tra địa chỉ máy chủ và thử lại.", - "login_form_back_button_text": "Quay lại", - "login_form_email_hint": "emailcuaban@email.com", - "login_form_endpoint_hint": "http://địa-chỉ-ip-máy-chủ-bạn:cổng", - "login_form_endpoint_url": "Địa chỉ máy chủ", - "login_form_err_http": "Vui lòng xác định http:// hoặc https://", - "login_form_err_invalid_email": "Email không hợp lệ", - "login_form_err_invalid_url": "Địa chỉ không hợp lệ", - "login_form_err_leading_whitespace": "Khoảng trắng trước", - "login_form_err_trailing_whitespace": "Khoảng trắng sau", - "login_form_failed_get_oauth_server_config": "Không thể đăng nhập bằng OAuth, vui lòng kiểm tra địa chỉ máy chủ", - "login_form_failed_get_oauth_server_disable": "Tính năng OAuth không khả dụng trên máy chủ này", - "login_form_failed_login": "Đăng nhập lỗi, vui lòng kiểm tra địa chỉ máy chủ, email và mật khẩu", - "login_form_handshake_exception": "Đã xảy ra lỗi Handshake Exception với máy chủ. Nếu bạn đang sử dụng chứng chỉ tự ký, hãy bật hỗ trợ chứng chỉ tự ký trong cài đặt.", - "login_form_password_hint": "mật khẩu", - "login_form_save_login": "Lưu trạng thái đăng nhập", - "login_form_server_empty": "Nhập địa chỉ máy chủ.", - "login_form_server_error": "Không thể kết nối tới máy chủ.", - "login_has_been_disabled": "Đăng nhập đã bị vô hiệu hóa.", - "login_password_changed_error": "Thay đổi mật khẩu không thành công", - "login_password_changed_success": "Cập nhật mật khẩu thành công", - "logout_all_device_confirmation": "Bạn có chắc muốn đăng xuất tất cả thiết bị?", - "logout_this_device_confirmation": "Bạn có chắc muốn đăng xuất thiết bị này?", - "logs": "Log", - "longitude": "Kinh độ", - "look": "Xem", - "loop_videos": "Lặp video", - "loop_videos_description": "Bật để video tự động lặp lại trong trình xem chi tiết.", - "main_branch_warning": "Bạn đang dùng phiên bản đang phát triển; chúng tôi khuyên bạn nên dùng phiên bản phát hành!", - "main_menu": "Menu chính", - "maintenance_description": "Immich đã được đặt vào chế độ bảo trì.", - "maintenance_end": "Kết thúc chế độ bảo trì", - "maintenance_end_error": "Không thể kết thúc chế độ bảo trì.", - "maintenance_logged_in_as": "Hiện tại đã đăng nhập {user}", - "maintenance_title": "Tạm thời không khả dụng", - "make": "Thương hiệu", - "manage_geolocation": "Quản lý địa điểm", - "manage_media_access_rationale": "Để có thể di chuyển tệp vào thùng rác và khôi phục chúng từ đó.", - "manage_media_access_settings": "Mở cài đặt", - "manage_media_access_subtitle": "Cho phép app [Immich] quản lý và di chuyển tệp.", - "manage_media_access_title": "Quản lý phương tiện", - "manage_shared_links": "Quản lý liên kết chia sẻ", - "manage_sharing_with_partners": "Quản lý chia sẻ với người thân", - "manage_the_app_settings": "Quản lý cài đặt app", - "manage_your_account": "Quản lý tài khoản của bạn", - "manage_your_api_keys": "Quản lý các khóa API của bạn", - "manage_your_devices": "Quản lý các thiết bị đã đăng nhập của bạn", - "manage_your_oauth_connection": "Quản lý kết nối OAuth của bạn", - "map": "Bản đồ", - "map_assets_in_bounds": "{count, plural, =0 {Không có ảnh ở khu vực này} one {# ảnh} other {# ảnh}}", - "map_cannot_get_user_location": "Không thể xác định vị trí của bạn", - "map_location_dialog_yes": "Có", - "map_location_picker_page_use_location": "Dùng vị trí này", - "map_location_service_disabled_content": "Cần bật dịch vụ định vị để hiển thị ảnh hoặc video từ vị trí hiện tại của bạn. Bạn có muốn bật nó ngay bây giờ không?", - "map_location_service_disabled_title": "Dịch vụ vị trí bị vô hiệu hoá", - "map_marker_for_images": "Đánh dấu bản đồ cho ảnh chụp tại {city}, {country}", - "map_marker_with_image": "Đánh dấu bản đồ với ảnh", - "map_no_location_permission_content": "Cần quyền truy cập vị trí để hiển thị tệp từ vị trí hiện tại của bạn. Bạn có muốn cho phép ngay bây giờ không?", - "map_no_location_permission_title": "App không được phép truy cập vị trí", - "map_settings": "Cài đặt bản đồ", - "map_settings_dark_mode": "Chế độ tối", - "map_settings_date_range_option_day": "Trong vòng 24 giờ qua", - "map_settings_date_range_option_days": "Trong {days} ngày qua", - "map_settings_date_range_option_year": "Năm ngoái", - "map_settings_date_range_option_years": "Trong {years} năm qua", - "map_settings_dialog_title": "Cài đặt bản đồ", - "map_settings_include_show_archived": "Bao gồm ảnh đã lưu trữ", - "map_settings_include_show_partners": "Bao gồm người thân", - "map_settings_only_show_favorites": "Chỉ hiển thị mục yêu thích", - "map_settings_theme_settings": "Giao diện bản đồ", - "map_zoom_to_see_photos": "Thu nhỏ để xem ảnh", - "mark_all_as_read": "Đánh dấu đã đọc tất cả", - "mark_as_read": "Đánh dấu đã đọc", - "marked_all_as_read": "Đã đánh dấu tất cả đã đọc", - "matches": "Khớp", - "matching_assets": "Tệp trùng khớp", - "media_type": "Loại phương tiện", - "memories": "Kỷ niệm", - "memories_all_caught_up": "Bạn đã xem hết rồi", - "memories_check_back_tomorrow": "Quay lại ngày mai để xem thêm những kỷ niệm", - "memories_setting_description": "Quản lý những kỷ niệm của bạn", - "memories_start_over": "Bắt đầu lại", - "memories_swipe_to_close": "Vuốt để đóng", - "memory": "Kỷ niệm", - "memory_lane_title": "Kỷ niệm {title}", - "menu": "Menu", - "merge": "Hợp nhất", - "merge_people": "Hợp nhất người", - "merge_people_limit": "Bạn chỉ có thể hợp nhất tối đa 5 khuôn mặt cùng một lúc", - "merge_people_prompt": "Bạn có muốn hợp nhất những người này? Hành động này không thể hoàn tác.", - "merge_people_successfully": "Hợp nhất người thành công", - "merged_people_count": "Đã hợp nhất {count, plural, one {# người} other {# người}}", - "minimize": "Thu nhỏ", - "minute": "Phút", - "minutes": "Phút", - "missing": "Thiếu", - "mobile_app": "Ứng dụng di động", - "mobile_app_download_onboarding_note": "Tải xuống app đồng hành bằng các tùy chọn sau", - "model": "Dòng", - "month": "Tháng", - "monthly_title_text_date_format": "MMMM y", - "more": "Thêm", - "move": "Di chuyển", - "move_down": "Di chuyển xuống", - "move_off_locked_folder": "Di chuyển ra khỏi thư mục Khóa", - "move_to": "Chuyển đến", - "move_to_lock_folder_action_prompt": "{count} đã được thêm vào thư mục Khóa", - "move_to_locked_folder": "Di chuyển đến thư mục Khóa", - "move_to_locked_folder_confirmation": "Ảnh và video này sẽ bị xóa khỏi các album, chỉ có thể xem được trong thư mục Khóa", - "move_up": "Di chuyển lên", - "moved_to_archive": "Đã di chuyển {count, plural, one {# tệp} other {# tệp}} đến lưu trữ", - "moved_to_library": "Đã di chuyển {count, plural, one {# tệp} other {# tệp}} đến thư viện", - "moved_to_trash": "Đã chuyển vào thùng rác", - "multiselect_grid_edit_date_time_err_read_only": "Không thể chỉnh sửa ngày của tệp chỉ có quyền đọc, bỏ qua", - "multiselect_grid_edit_gps_err_read_only": "Không thể chỉnh sửa vị trí của tệp chỉ có quyền đọc, bỏ qua", - "mute_memories": "Tắt tiếng Kỷ niệm", - "my_albums": "Album của tôi", - "name": "Tên", - "name_or_nickname": "Tên hoặc biệt danh", - "name_required": "Bắt buộc nhập tên", - "navigate": "Điều hướng", - "navigate_to_time": "Xem Thời gian", - "network_requirement_photos_upload": "Dùng dữ liệu di động sao lưu ảnh", - "network_requirement_videos_upload": "Dùng dữ liệu di động sao lưu video", - "network_requirements": "Kết nối mạng", - "network_requirements_updated": "Kết nối mạng đã thay đổi, đang đặt lại hàng đợi sao lưu", - "networking_settings": "Mạng", - "networking_subtitle": "Các địa chỉ máy chủ", - "never": "Không bao giờ", - "new_album": "Album mới", - "new_api_key": "Khóa API mới", - "new_date_range": "Khoảng ngày mới", - "new_password": "Mật khẩu mới", - "new_person": "Người mới", - "new_pin_code": "Mã PIN mới", - "new_pin_code_subtitle": "Đây là lần đầu bạn vào thư mục Khóa. Hãy tạo mã PIN để truy cập an toàn", - "new_timeline": "Dòng thời gian kiểu mới", - "new_update": "Cập nhật mới", - "new_user_created": "Người dùng mới đã được tạo", - "new_version_available": "CÓ PHIÊN BẢN MỚI", - "newest_first": "Mới nhất trước", - "next": "Tiếp theo", - "next_memory": "Kỷ niệm tiếp theo", - "no": "Không", - "no_actions_added": "Chưa có hành động nào được thêm vào", - "no_albums_message": "Tạo album để sắp xếp ảnh và video của bạn", - "no_albums_with_name_yet": "Có vẻ như bạn chưa có bất kỳ album nào với tên này.", - "no_albums_yet": "Có vẻ như bạn chưa có bất kỳ album nào.", - "no_archived_assets_message": "Lưu trữ ảnh và video để ẩn chúng khỏi thư viện Ảnh của bạn", - "no_assets_message": "NHẤN VÀO ĐỂ TẢI LÊN ẢNH ĐẦU TIÊN CỦA BẠN", - "no_assets_to_show": "Không có gì để hiển thị", - "no_cast_devices_found": "Không tìm thấy thiết bị chiếu", - "no_checksum_local": "Không có checksum khả dụng - không thể truy xuất tệp trên thiết bị", - "no_checksum_remote": "Không có checksum khả dụng - không thể truy xuất tệp trên mây", - "no_configuration_needed": "Không cần cấu hình", - "no_devices": "Không có thiết bị được cấp quyền", - "no_duplicates_found": "Không tìm thấy các mục trùng lặp.", - "no_exif_info_available": "Không có thông tin exif", - "no_explore_results_message": "Tải thêm ảnh lên để khám phá bộ sưu tập của bạn.", - "no_favorites_message": "Thêm ảnh yêu thích để nhanh chóng tìm thấy những bức ảnh và video đẹp nhất của bạn", - "no_filters_added": "Chưa có bộ lọc nào được thêm vào", - "no_libraries_message": "Tạo một thư viện bên ngoài để xem ảnh và video của bạn", - "no_local_assets_found": "Không tìm thấy tệp trên thiết bị nào với checksum này", - "no_location_set": "Chưa có địa điểm được đặt", - "no_locked_photos_message": "Ảnh và video trong thư mục Khóa sẽ được ẩn đi và không hiển thị khi bạn duyệt hay tìm kiếm trong thư viện.", - "no_name": "Không có tên", - "no_notifications": "Không có thông báo", - "no_people_found": "Không có người nào khớp với tìm kiếm", - "no_places": "Không có địa điểm", - "no_remote_assets_found": "Không tìm thấy tệp trên mây nào với checksum này", - "no_results": "Không có kết quả", - "no_results_description": "Thử một từ đồng nghĩa hoặc từ khóa tổng quát hơn", - "no_shared_albums_message": "Tạo một album để chia sẻ ảnh và video với mọi người trong mạng của bạn", - "no_uploads_in_progress": "Không có tải lên nào đang tiến hành", - "not_allowed": "Không cho phép", - "not_available": "Thiếu", - "not_in_any_album": "Không thuộc album nào", - "not_selected": "Không được chọn", - "note_apply_storage_label_to_previously_uploaded assets": "Lưu ý: Để áp dụng Nhãn lưu trữ cho các ảnh đã tải lên trước đó, hãy chạy", - "notes": "Lưu ý", - "nothing_here_yet": "Chưa có nội dung nào", - "notification_permission_dialog_content": "Để bật thông báo, chuyển tới Cài đặt và chọn cho phép.", - "notification_permission_list_tile_content": "Cấp quyền để bật thông báo.", - "notification_permission_list_tile_enable_button": "Bật thông báo", - "notification_permission_list_tile_title": "Quyền thông báo", - "notification_toggle_setting_description": "Bật thông báo qua email", - "notifications": "Thông báo", - "notifications_setting_description": "Quản lý thông báo", - "oauth": "OAuth", - "obtainium_configurator": "Cấu hình Obtainium", - "obtainium_configurator_instructions": "Sử dụng Obtainium để cài đặt và cập nhật ứng dụng Android trực tiếp từ bản phát hành GitHub của Immich. Tạo khóa API và chọn một biến thể để tạo liên kết cấu hình Obtainium của bạn", - "ocr": "OCR", - "official_immich_resources": "Tài nguyên chính thức của Immich", - "offline": "Ngoại tuyến", - "offset": "Độ lệch", - "ok": "Đồng ý", - "oldest_first": "Cũ nhất trước", - "on_this_device": "Trên thiết bị này", - "onboarding": "Hướng dẫn sử dụng", - "onboarding_locale_description": "Chọn ngôn ngữ ưa thích của bạn. Bạn có thể thay đổi lại sau trong cài đặt.", - "onboarding_privacy_description": "Các tính năng (tùy chọn) sau đây phụ thuộc vào các dịch vụ bên ngoài và có thể bị tắt bất kỳ lúc nào trong cài đặt.", - "onboarding_server_welcome_description": "Hãy thiết lập máy chủ của bạn với một số cài đặt phổ biến.", - "onboarding_theme_description": "Chọn màu chủ đề cho tài khoản riêng của bạn. Bạn có thể thay đổi điều này sau trong cài đặt của bạn.", - "onboarding_user_welcome_description": "Bắt đầu thôi nào!", - "onboarding_welcome_user": "Chào mừng, {user}", - "online": "Trực tuyến", - "only_favorites": "Chỉ lượt thích", - "open": "Mở", - "open_in_map_view": "Mở trong bản đồ", - "open_in_openstreetmap": "Mở trong OpenStreetMap", - "open_the_search_filters": "Mở bộ lọc tìm kiếm", - "options": "Tùy chọn", - "or": "hoặc", - "organize_into_albums": "Sắp xếp thành album", - "organize_into_albums_description": "Đưa ảnh hiện có vào album bằng cách sử dụng cài đặt đồng bộ hiện tại", - "organize_your_library": "Sắp xếp thư viện của bạn", - "original": "gốc", - "other": "Khác", - "other_devices": "Thiết bị khác", - "other_entities": "Các thực thể khác", - "other_variables": "Các tham số khác", - "owned": "Sở hữu", - "owner": "Chủ sở hữu", - "page": "Trang", - "partner": "Người thân", - "partner_can_access": "{partner} có thể truy cập", - "partner_can_access_assets": "Tất cả ảnh và video của bạn ngoại trừ những ảnh và video trong mục Đã lưu trữ và Đã xóa", - "partner_can_access_location": "Vị trí nơi ảnh của bạn được chụp", - "partner_list_user_photos": "Ảnh của {user}", - "partner_list_view_all": "Xem tất cả", - "partner_page_empty_message": "Ảnh của bạn chưa được chia sẻ với bất kỳ ai.", - "partner_page_no_more_users": "Không còn người dùng nào để thêm", - "partner_page_partner_add_failed": "Thêm người thân thất bại", - "partner_page_select_partner": "Chọn người thân", - "partner_page_shared_to_title": "Chia sẻ với", - "partner_page_stop_sharing_content": "{partner} sẽ không thể truy cập ảnh của bạn.", - "partner_sharing": "Người thân", - "partners": "Người thân", - "password": "Mật khẩu", - "password_does_not_match": "Mật khẩu không khớp", - "password_required": "Yêu cầu mật khẩu", - "password_reset_success": "Đặt lại mật khẩu thành công", - "past_durations": { - "days": "Cách đây {days, plural, one {ngày} other {# ngày}}", - "hours": "Cách đây {hours, plural, one {giờ} other {# giờ}}", - "years": "Cách đây {years, plural, one {năm} other {# năm}}" - }, - "path": "Đường dẫn", - "pattern": "Quy tắc", - "pause": "Tạm dừng", - "pause_memories": "Tạm dừng kỷ niệm", - "paused": "Đã tạm dừng", - "pending": "Đang chờ xử lý", - "people": "Mọi người", - "people_edits_count": "Đã chỉnh sửa {count, plural, one {# người} other {# người}}", - "people_feature_description": "Duyệt ảnh và video được xếp nhóm theo người", - "people_selected": "{count, plural, one {# người đã chọn} other {# người đã chọn}}", - "people_sidebar_description": "Hiển thị mục Mọi người trong thanh bên", - "permanent_deletion_warning": "Cảnh báo xóa vĩnh viễn", - "permanent_deletion_warning_setting_description": "Hiển thị cảnh báo khi xóa vĩnh viễn ảnh", - "permanently_delete": "Xóa vĩnh viễn", - "permanently_delete_assets_count": "Xóa vĩnh viễn {count, plural, one {tệp} other {tệp}}", - "permanently_delete_assets_prompt": "Bạn có chắc muốn xóa vĩnh viễn {count, plural, one {tệp này?} other {# tệp này?}} Điều này cũng sẽ xóa {count, plural, one {nó khỏi} other {chúng khỏi}} các album.", - "permanently_deleted_asset": "Tệp đã bị xóa vĩnh viễn", - "permanently_deleted_assets_count": "Đã xóa vĩnh viễn {count, plural, one {# tệp} other {# tệp}}", - "permission": "Quyền", - "permission_empty": "Quyền của bạn không được để trống", - "permission_onboarding_back": "Quay lại", - "permission_onboarding_continue_anyway": "Vẫn tiếp tục", - "permission_onboarding_get_started": "Bắt đầu", - "permission_onboarding_go_to_settings": "Đi tới cài đặt", - "permission_onboarding_permission_denied": "Không có quyền truy cập vào ảnh của bạn. Để sử dụng Immich, hãy cấp quyền truy cập vào thư viện ảnh trong Cài đặt.", - "permission_onboarding_permission_granted": "Cấp quyền hoàn tất! Bạn có thể bắt đầu.", - "permission_onboarding_permission_limited": "Quyền truy cập vào ảnh của bạn bị hạn chế. Để Immich sao lưu và quản lý toàn bộ thư viện ảnh của bạn, hãy cấp quyền truy cập toàn bộ ảnh trong Cài đặt.", - "permission_onboarding_request": "Immich cần quyền để xem ảnh và video của bạn.", - "person": "Mọi người", - "person_age_months": "{months, plural, one {# tháng} other {# tháng}} tuổi", - "person_age_year_months": "1 năm, {months, plural, one {# tháng} other {# tháng}} tuổi", - "person_age_years": "{years, plural, other {# năm}} tuổi", - "person_birthdate": "Sinh vào {date}", - "person_hidden": "{name}{hidden, select, true { (đã ẩn)} other {}}", - "person_recognized": "Người được nhận diện", - "person_selected": "Người đã chọn", - "photo_shared_all_users": "Có vẻ như bạn đã chia sẻ ảnh của mình với tất cả người dùng hoặc bạn không có người dùng nào để chia sẻ.", - "photos": "Ảnh", - "photos_and_videos": "Ảnh & Video", - "photos_count": "{count, plural, one {{count, number} Ảnh} other {{count, number} Ảnh}}", - "photos_from_previous_years": "Ảnh từ các năm trước", - "pick_a_location": "Chọn một vị trí", - "pick_custom_range": "Tùy chỉnh khoảng", - "pick_date_range": "Chọn khoảng ngày", - "pin_code_changed_successfully": "Thay đổi mã PIN thành công", - "pin_code_reset_successfully": "Đặt lại mã PIN thành công", - "pin_code_setup_successfully": "Thiết lập mã PIN thành công", - "pin_verification": "Xác minh mã PIN", - "place": "Địa điểm", - "places": "Địa điểm", - "places_count": "{count, plural, one {{count, number} Địa điểm} other {{count, number} Địa điểm}}", - "play": "Phát", - "play_memories": "Phát kỷ niệm", - "play_motion_photo": "Phát ảnh chuyển động", - "play_or_pause_video": "Phát hoặc dừng video", - "play_original_video": "Phát video gốc", - "play_original_video_setting_description": "Nên phát video gốc thay vì video đã chuyển mã. Nếu tệp gốc không tương thích, video có thể không phát được.", - "play_transcoded_video": "Phát video đã chuyển mã", - "please_auth_to_access": "Vui lòng xác thực để truy cập", - "port": "Cổng", - "preferences_settings_subtitle": "Tùy chỉnh trải nghiệm app", - "preferences_settings_title": "Cá nhân hóa", - "preparing": "Đang chuẩn bị", - "preset": "Mẫu có sẵn", - "preview": "Xem trước", - "previous": "Trước", - "previous_memory": "Kỷ niệm trước", - "previous_or_next_day": "Ngày trước/sau", - "previous_or_next_month": "Tháng trước/sau", - "previous_or_next_photo": "Ảnh trước/sau", - "previous_or_next_year": "Năm trước/sau", - "primary": "Chính", - "privacy": "Bảo mật", - "profile": "Hồ sơ", - "profile_drawer_app_logs": "Log", - "profile_drawer_client_server_up_to_date": "Máy khách và máy chủ đã cập nhật", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Đã bật chế độ chỉ-xem. Nhấn giữ ảnh đại diện người dùng để tắt.", - "profile_image_of_user": "Ảnh đại diện của {user}", - "profile_picture_set": "Ảnh đại diện đã được đặt.", - "public_album": "Album công khai", - "public_share": "Chia sẻ công khai", - "purchase_account_info": "Người hỗ trợ", - "purchase_activated_subtitle": "Cảm ơn bạn đã hỗ trợ Immich và phần mềm mã nguồn mở", - "purchase_activated_time": "Đã kích hoạt vào {date}", - "purchase_activated_title": "Khóa của bạn đã được kích hoạt thành công", - "purchase_button_activate": "Kích hoạt", - "purchase_button_buy": "Mua", - "purchase_button_buy_immich": "Mua Immich", - "purchase_button_never_show_again": "Không hiện lại", - "purchase_button_reminder": "Nhắc tôi trong 30 ngày", - "purchase_button_remove_key": "Xóa khóa", - "purchase_button_select": "Chọn", - "purchase_failed_activation": "Kích hoạt thất bại! Vui lòng kiểm tra email của bạn để biết khóa sản phẩm chính xác!", - "purchase_individual_description_1": "Dành cho cá nhân", - "purchase_individual_description_2": "Trạng thái người hỗ trợ", - "purchase_individual_title": "Cá nhân", - "purchase_input_suggestion": "Có khóa sản phẩm? Nhập khóa bên dưới", - "purchase_license_subtitle": "Mua Immich để hỗ trợ dịch vụ liên tục phát triển", - "purchase_lifetime_description": "Mua trọn đời", - "purchase_option_title": "TÙY CHỌN MUA HÀNG", - "purchase_panel_info_1": "Việc xây dựng Immich tốn nhiều thời gian và công sức, và chúng tôi có các kỹ sư toàn thời gian làm việc để làm cho nó tốt nhất có thể. Sứ mệnh của chúng tôi là phần mềm mã nguồn mở và các hoạt động kinh doanh có đạo đức trở thành nguồn thu nhập bền vững cho các nhà phát triển, đồng thời tạo ra một hệ sinh thái bảo vệ quyền riêng tư với các lựa chọn thay thế thực sự cho các dịch vụ đám mây lợi dụng người dùng.", - "purchase_panel_info_2": "Vì chúng tôi cam kết không thu phí thêm, việc mua này sẽ không cấp cho bạn bất kỳ tính năng bổ sung nào trong Immich. Chúng tôi phụ thuộc vào những người dùng như bạn để hỗ trợ sự phát triển liên tục của Immich.", - "purchase_panel_title": "Hỗ trợ dự án", - "purchase_per_server": "Mỗi máy chủ", - "purchase_per_user": "Mỗi người dùng", - "purchase_remove_product_key": "Xóa khóa sản phẩm", - "purchase_remove_product_key_prompt": "Bạn có chắc muốn xoá khóa sản phẩm?", - "purchase_remove_server_product_key": "Xóa khóa sản phẩm máy chủ", - "purchase_remove_server_product_key_prompt": "Bạn có chắc muốn xoá khóa sản phẩm máy chủ?", - "purchase_server_description_1": "Dành cho toàn bộ máy chủ", - "purchase_server_description_2": "Trạng thái người hỗ trợ", - "purchase_server_title": "Máy chủ", - "purchase_settings_server_activated": "Khóa sản phẩm máy chủ được quản lý bởi quản trị viên", - "query_asset_id": "Truy vấn ID tệp", - "queue_status": "Xếp hàng {count}/{total}", - "rate_asset": "Asset Đánh giá", - "rating": "Xếp hạng sao", - "rating_clear": "Xóa xếp hạng", - "rating_count": "{count, plural, one {# sao} other {# sao}}", - "rating_description": "Hiển thị xếp hạng EXIF trong bảng thông tin", - "rating_set": "Đánh giá đặt thành {rating, plural, one {# sao} other {# sao}}", - "reaction_options": "Tùy chọn phản ứng", - "read_changelog": "Đọc nhật ký thay đổi", - "readonly_mode_disabled": "Đã tắt chế độ chỉ-xem", - "readonly_mode_enabled": "Đã bật chế độ chỉ-xem", - "ready_for_upload": "Sẵn sàng tải lên", - "reassign": "Gán lại", - "reassigned_assets_to_existing_person": "Đã gán lại {count, plural, one {# ảnh} other {# ảnh}} cho {name, select, null {một người hiện có} other {{name}}}", - "reassigned_assets_to_new_person": "Đã gán lại {count, plural, one {# ảnh} other {# ảnh}} cho một người mới", - "reassing_hint": "Gán các ảnh đã chọn cho một người hiện có", - "recent": "Gần đây", - "recent-albums": "Album gần đây", - "recent_searches": "Tìm kiếm gần đây", - "recently_added": "Thêm gần đây", - "recently_added_page_title": "Mới thêm gần đây", - "recently_taken": "Chụp gần đây", - "recently_taken_page_title": "Chụp Gần đây", - "refresh": "Làm mới", - "refresh_encoded_videos": "Làm mới video đã mã hóa", - "refresh_faces": "Làm mới khuôn mặt", - "refresh_metadata": "Làm mới metadata", - "refresh_thumbnails": "Làm mới ảnh thu nhỏ", - "refreshed": "Đã làm mới", - "refreshes_every_file": "Đọc lại tất cả tệp mới và hiện có", - "refreshing_encoded_video": "Đang làm mới video đã mã hóa", - "refreshing_faces": "Đang làm mới khuôn mặt", - "refreshing_metadata": "Đang làm mới metadata", - "regenerating_thumbnails": "Đang tạo lại ảnh thu nhỏ", - "remote": "Trên mây", - "remote_assets": "Tệp trên mây", - "remote_media_summary": "Mô tả phương tiện trên máy chủ", - "remove": "Xóa", - "remove_assets_album_confirmation": "Bạn có chắc muốn xóa {count, plural, one {# tệp} other {# tệp}} khỏi album?", - "remove_assets_shared_link_confirmation": "Bạn có chắc muốn xóa {count, plural, one {# tệp} other {# tệp}} khỏi liên kết chia sẻ này?", - "remove_assets_title": "Xóa tệp?", - "remove_custom_date_range": "Bỏ chọn khoảng ngày tùy chỉnh", - "remove_deleted_assets": "Loại bỏ tệp ngoại tuyến", - "remove_from_album": "Xóa khỏi album", - "remove_from_album_action_prompt": "{count} đã gỡ khỏi album", - "remove_from_favorites": "Xóa khỏi Mục yêu thích", - "remove_from_lock_folder_action_prompt": "{count} đã được xóa khỏi thư mục Khóa", - "remove_from_locked_folder": "Xóa khỏi thư mục Khóa", - "remove_from_locked_folder_confirmation": "Bạn có chắc muốn di chuyển ảnh và video này khỏi thư mục Khóa? Chúng sẽ hiện trong thư viện của bạn.", - "remove_from_shared_link": "Xóa khỏi liên kết chia sẻ", - "remove_memory": "Xóa kỷ niệm", - "remove_photo_from_memory": "Xóa ảnh khỏi kỷ niệm này", - "remove_tag": "Gỡ thẻ", - "remove_url": "Xóa URL", - "remove_user": "Xóa người dùng", - "removed_api_key": "Khóa API đã xóa: {name}", - "removed_from_archive": "Đã xóa khỏi Kho lưu trữ", - "removed_from_favorites": "Đã xóa khỏi Mục yêu thích", - "removed_from_favorites_count": "{count, plural, other {Đã xóa #}} khỏi Mục yêu thích", - "removed_memory": "Đã xóa kỷ niệm", - "removed_photo_from_memory": "Đã xóa ảnh khỏi kỷ niệm", - "removed_tagged_assets": "Đã xóa thẻ khỏi {count, plural, one {# tệp} other {# tệp}}", - "rename": "Đổi tên", - "repair": "Sửa chữa", - "repair_no_results_message": "Các tệp không được theo dõi và bị thiếu sẽ xuất hiện ở đây", - "replace_with_upload": "Thay thế tệp khác", - "repository": "Kho lưu trữ", - "require_password": "Yêu cầu mật khẩu", - "require_user_to_change_password_on_first_login": "Yêu cầu người dùng thay đổi mật khẩu ở lần đầu đăng nhập", - "rescan": "Quét lại", - "reset": "Đặt lại", - "reset_password": "Đặt lại mật khẩu", - "reset_people_visibility": "Đặt lại trạng thái hiển thị của mọi người", - "reset_pin_code": "Đặt lại mã PIN", - "reset_pin_code_description": "Nếu bạn quên mã PIN, bạn có thể liên hệ quản trị viên để đặt lại", - "reset_pin_code_success": "Đặt lại mã PIN thành công", - "reset_pin_code_with_password": "Bạn luôn có thể đặt lại mã PIN của bạn bằng mật khẩu của bạn", - "reset_sqlite": "Đặt lại cơ sở dữ liệu SQLite", - "reset_sqlite_confirmation": "Bạn có chắc muốn đặt lại cơ sở dữ liệu SQLite? Bạn sẽ cần đăng xuất và đăng nhập lại để đồng bộ lại dữ liệu", - "reset_sqlite_success": "Đã thiết lập lại cơ sở dữ liệu SQLite thành công", - "reset_to_default": "Đặt lại về mặc định", - "resolution": "Độ phân giải", - "resolve_duplicates": "Xử lý các bản trùng lặp", - "resolved_all_duplicates": "Đã xử lý tất cả các bản trùng lặp", - "restore": "Khôi phục", - "restore_all": "Khôi phục tất cả", - "restore_trash_action_prompt": "{count} đã khôi phục từ thùng rác", - "restore_user": "Khôi phục người dùng", - "restored_asset": "Tệp đã được khôi phục", - "resume": "Tiếp tục", - "resume_paused_jobs": "Tiếp tục {count, plural, one {# tác vụ đã dừng} other {# tác vụ đã dừng}}", - "retry_upload": "Thử tải lên lại", - "review_duplicates": "Xem lại các mục trùng lặp", - "review_large_files": "Xem lại tệp dung lượng lớn", - "role": "Vai trò", - "role_editor": "Người chỉnh sửa", - "role_viewer": "Người xem", - "running": "Đang chạy", - "save": "Lưu", - "save_to_gallery": "Lưu vào thư viện", - "saved": "Đã lưu", - "saved_api_key": "Khóa API đã lưu", - "saved_profile": "Hồ sơ đã lưu", - "saved_settings": "Cài đặt đã lưu", - "say_something": "Nói điều gì đó", - "scaffold_body_error_occurred": "Xảy ra lỗi", - "scan_all_libraries": "Quét tất cả thư viện", - "scan_library": "Quét", - "scan_settings": "Cài đặt quét", - "scanning_for_album": "Đang quét album...", - "search": "Tìm kiếm", - "search_albums": "Tìm album", - "search_by_context": "Tìm theo ngữ cảnh", - "search_by_description": "Tìm theo mô tả", - "search_by_description_example": "Dạo chơi Sa Pa", - "search_by_filename": "Tìm theo tên hoặc định dạng tệp", - "search_by_filename_example": "Ví dụ: IMG_1234.JPG hoặc PNG", - "search_by_ocr": "Tìm bằng OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Tìm dòng lens...", - "search_camera_make": "Tìm thương hiệu máy ảnh...", - "search_camera_model": "Tìm dòng máy ảnh...", - "search_city": "Tìm thành phố...", - "search_country": "Tìm quốc gia...", - "search_filter_apply": "Áp dụng bộ lọc", - "search_filter_camera_title": "Chọn loại máy ảnh", - "search_filter_date": "Ngày", - "search_filter_date_interval": "{start} đến {end}", - "search_filter_date_title": "Chọn khoảng ngày", - "search_filter_display_option_not_in_album": "Không nằm trong album", - "search_filter_display_options": "Tùy chọn hiển thị", - "search_filter_filename": "Tìm theo tên tệp", - "search_filter_location": "Vị trí", - "search_filter_location_title": "Chọn vị trí", - "search_filter_media_type": "Phương tiện", - "search_filter_media_type_title": "Chọn loại phương tiện", - "search_filter_ocr": "Tìm bằng OCR", - "search_filter_people_title": "Chọn người", - "search_for": "Tìm kiếm", - "search_for_existing_person": "Tìm người hiện có", - "search_no_more_result": "Không còn kết quả", - "search_no_people": "Không có người", - "search_no_people_named": "Không có người tên \"{name}\"", - "search_no_result": "Không tìm thấy kết quả, hãy thử một cụm từ hoặc kết hợp với tìm kiếm khác", - "search_options": "Tùy chọn tìm kiếm", - "search_page_categories": "Danh mục", - "search_page_motion_photos": "Ảnh động", - "search_page_no_objects": "Không có thông tin đối tượng", - "search_page_no_places": "Không có thông tin địa điểm nào", - "search_page_screenshots": "Ảnh chụp màn hình", - "search_page_search_photos_videos": "Tìm ảnh và video của bạn", - "search_page_selfies": "Ảnh selfie", - "search_page_things": "Đối tượng", - "search_page_view_all_button": "Xem tất cả", - "search_page_your_activity": "Hoạt động của bạn", - "search_page_your_map": "Bản đồ của bạn", - "search_people": "Tìm người", - "search_places": "Tìm kiếm địa điểm", - "search_rating": "Tìm kiếm theo xếp hạng…", - "search_result_page_new_search_hint": "Tìm kiếm mới", - "search_settings": "Tìm kiếm cài đặt", - "search_state": "Tìm tỉnh...", - "search_suggestion_list_smart_search_hint_1": "Tìm kiếm thông minh được bật mặc định, để tìm kiếm metadata hãy sử dụng cú pháp ", - "search_suggestion_list_smart_search_hint_2": "m:cụm-từ-tìm-kiếm-của-bạn", - "search_tags": "Tìm thẻ...", - "search_timezone": "Tìm kiếm múi giờ...", - "search_type": "Kiểu tìm kiếm", - "search_your_photos": "Tìm ảnh của bạn", - "searching_locales": "Đang tìm kiếm khu vực...", - "second": "Giây", - "see_all_people": "Xem tất cả mọi người", - "select": "Chọn", - "select_album": "Chọn album", - "select_album_cover": "Chọn ảnh bìa album", - "select_albums": "Chọn các album", - "select_all": "Chọn tất cả", - "select_all_duplicates": "Chọn tất cả các bản trùng lặp", - "select_all_in": "Chọn tất cả trong {group}", - "select_avatar_color": "Chọn màu ảnh đại diện", - "select_count": "{count, plural, one {Chọn #} other {Chọn #}}", - "select_face": "Chọn khuôn mặt", - "select_featured_photo": "Chọn ảnh nổi bật", - "select_from_computer": "Chọn từ máy tính", - "select_keep_all": "Chọn giữ tất cả", - "select_library_owner": "Chọn chủ sở hữu thư viện", - "select_new_face": "Chọn khuôn mặt mới", - "select_people": "Chọn người", - "select_person": "Chọn người", - "select_person_to_tag": "Chọn người để gắn thẻ", - "select_photos": "Chọn ảnh", - "select_trash_all": "Chọn xóa tất cả", - "select_user_for_sharing_page_err_album": "Tạo album thất bại", - "selected": "Đã chọn", - "selected_count": "{count, plural, other {Đã chọn # mục}}", - "selected_gps_coordinates": "Tọa độ GPS đã chọn", - "send_message": "Gửi tin nhắn", - "send_welcome_email": "Gửi email chào mừng", - "server_endpoint": "Địa chỉ máy chủ", - "server_info_box_app_version": "Phiên bản app", - "server_info_box_server_url": "URL máy chủ", - "server_offline": "Máy chủ ngoại tuyến", - "server_online": "Phiên bản", - "server_privacy": "Quyền riêng tư máy chủ", - "server_restarting_description": "Trang này sẽ làm mới trong giây lát.", - "server_restarting_title": "Máy chủ đang khởi động lại", - "server_stats": "Thống kê máy chủ", - "server_update_available": "Có bản cập nhật máy chủ", - "server_version": "Phiên bản máy chủ", - "set": "Đặt", - "set_as_album_cover": "Đặt làm ảnh bìa album", - "set_as_featured_photo": "Đặt làm ảnh nổi bật", - "set_as_profile_picture": "Đặt làm ảnh đại diện", - "set_date_of_birth": "Đặt sinh nhật", - "set_profile_picture": "Đặt ảnh đại diện", - "set_slideshow_to_fullscreen": "Đặt trình chiếu ở chế độ toàn màn hình", - "set_stack_primary_asset": "Đặt làm tài sản chính", - "setting_image_viewer_help": "Trình xem ảnh tải ảnh thu nhỏ cỡ nhỏ trước, sau đó tải cỡ trung bình (nếu được bật), cuối cùng tải bản gốc (nếu được bật).", - "setting_image_viewer_original_subtitle": "Bật để tải ảnh gốc ở độ phân giải đầy đủ (dung lượng lớn). Tắt để giảm mức sử dụng dữ liệu (mức sử dụng mạng và bộ nhớ đệm trên thiết bị).", - "setting_image_viewer_original_title": "Tải ảnh gốc", - "setting_image_viewer_preview_subtitle": "Bật để tải ảnh độ phân giải trung bình. Tắt để tải trực tiếp ảnh gốc hoặc chỉ sử dụng ảnh thu nhỏ.", - "setting_image_viewer_preview_title": "Tải ảnh xem trước", - "setting_image_viewer_title": "Ảnh", - "setting_languages_apply": "Áp dụng", - "setting_languages_subtitle": "Ngôn ngữ app", - "setting_notifications_notify_failures_grace_period": "Thông báo lỗi sao lưu nền: {duration}", - "setting_notifications_notify_hours": "{count} giờ", - "setting_notifications_notify_immediately": "ngay lập tức", - "setting_notifications_notify_minutes": "{count} phút", - "setting_notifications_notify_never": "không bao giờ", - "setting_notifications_notify_seconds": "{count} giây", - "setting_notifications_single_progress_subtitle": "Thông tin chi tiết của từng ảnh đang tải lên", - "setting_notifications_single_progress_title": "Hiện chi tiết sao lưu nền đang thực hiện", - "setting_notifications_subtitle": "Các loại thông báo", - "setting_notifications_total_progress_subtitle": "Toàn bộ quá trình tải lên (hoàn thành/tổng số tệp)", - "setting_notifications_total_progress_title": "Hiện quá trình sao lưu nền đang thực hiện", - "setting_video_viewer_auto_play_subtitle": "Khi mở video", - "setting_video_viewer_auto_play_title": "Tự động phát video", - "setting_video_viewer_looping_title": "Lặp lại", - "setting_video_viewer_original_video_subtitle": "Khi phát trực tuyến video từ máy chủ, hãy phát video gốc ngay cả khi có bản chuyển mã. Có thể dẫn đến tình trạng chờ. Video có sẵn trên máy được phát ở chất lượng gốc bất kể cài đặt này.", - "setting_video_viewer_original_video_title": "Dùng video gốc", - "settings": "Cài đặt", - "settings_require_restart": "Vui lòng khởi động lại Immich để áp dụng cài đặt này", - "settings_saved": "Đã lưu cài đặt", - "setup_pin_code": "Thiết lập mã PIN", - "share": "Chia sẻ", - "share_action_prompt": "Đã chia sẻ {count} tệp", - "share_add_photos": "Thêm ảnh", - "share_assets_selected": "{count} đã chọn", - "share_dialog_preparing": "Đang xử lý...", - "share_link": "Liên kết chia sẻ", - "shared": "Đã chia sẻ", - "shared_album_activities_input_disable": "Nhận xét hiện đã tắt", - "shared_album_activity_remove_content": "Bạn có muốn xóa hoạt động này?", - "shared_album_activity_remove_title": "Xóa hoạt động", - "shared_album_section_people_action_error": "Lỗi khi xóa khỏi album", - "shared_album_section_people_action_leave": "Xóa người dùng khỏi album", - "shared_album_section_people_action_remove_user": "Xóa người dùng khỏi album", - "shared_album_section_people_title": "MỌI NGƯỜI", - "shared_by": "Được chia sẻ bởi", - "shared_by_user": "Được chia sẻ bởi {user}", - "shared_by_you": "Được chia sẻ bởi bạn", - "shared_from_partner": "Ảnh từ {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Đã tải lên", - "shared_link_app_bar_title": "Liên kết đã chia sẻ", - "shared_link_clipboard_copied_massage": "Đã sao chép vào bộ nhớ tạm", - "shared_link_clipboard_text": "Liên kết: {link}\nMật khẩu: {password}", - "shared_link_create_error": "Tạo liên kết chia sẻ không thành công", - "shared_link_custom_url_description": "Truy cập liên kết chia sẻ với một URL tùy chỉnh", - "shared_link_edit_description_hint": "Nhập mô tả chia sẻ", - "shared_link_edit_expire_after_option_day": "1 ngày", - "shared_link_edit_expire_after_option_days": "{count} ngày", - "shared_link_edit_expire_after_option_hour": "1 giờ", - "shared_link_edit_expire_after_option_hours": "{count} giờ", - "shared_link_edit_expire_after_option_minute": "1 phút", - "shared_link_edit_expire_after_option_minutes": "{count} phút", - "shared_link_edit_expire_after_option_months": "{count} tháng", - "shared_link_edit_expire_after_option_year": "{count} năm", - "shared_link_edit_password_hint": "Nhập mật khẩu chia sẻ", - "shared_link_edit_submit_button": "Cập nhật liên kết", - "shared_link_error_server_url_fetch": "Không thể kết nối địa chỉ máy chủ", - "shared_link_expires_day": "Hết hạn sau {count} ngày", - "shared_link_expires_days": "Hết hạn sau {count} ngày", - "shared_link_expires_hour": "Hết hạn sau {count} giờ", - "shared_link_expires_hours": "Hết hạn sau {count} giờ", - "shared_link_expires_minute": "Hết hạn sau {count} phút", - "shared_link_expires_minutes": "Hết hạn sau {count} phút", - "shared_link_expires_never": "Vô hạn ∞", - "shared_link_expires_second": "Hết hạn sau {count} giây", - "shared_link_expires_seconds": "Hết hạn sau {count} giây", - "shared_link_individual_shared": "Chia sẻ riêng tư", - "shared_link_info_chip_metadata": "Dữ liệu EXIF", - "shared_link_manage_links": "Quản lý liên kết đã chia sẻ", - "shared_link_options": "Tùy chọn liên kết chia sẻ", - "shared_link_password_description": "Bắt buộc để truy cập liên kết chia sẻ này", - "shared_links": "Liên kết chia sẻ", - "shared_links_description": "Chia sẻ ảnh và video bằng liên kết", - "shared_photos_and_videos_count": "{assetCount, plural, other {# ảnh & video đã chia sẻ.}}", - "shared_with_me": "Chia sẻ với tôi", - "shared_with_partner": "Đã chia sẻ với {partner}", - "sharing": "Chia sẻ", - "sharing_enter_password": "Vui lòng nhập mật khẩu để xem trang này.", - "sharing_page_album": "Album chia sẻ", - "sharing_page_description": "Tạo album chia sẻ để chia sẻ ảnh và video với những người trong mạng của bạn.", - "sharing_page_empty_list": "DANH SÁCH TRỐNG", - "sharing_sidebar_description": "Hiển thị mục Chia sẻ trong thanh bên", - "sharing_silver_appbar_create_shared_album": "Tạo album chia sẻ", - "sharing_silver_appbar_share_partner": "Chia sẻ với người thân", - "shift_to_permanent_delete": "nhấn ⇧ để xóa vĩnh viễn tệp", - "show_album_options": "Hiện tùy chọn album", - "show_albums": "Hiện album", - "show_all_people": "Hiển thị tất cả mọi người", - "show_and_hide_people": "Hiển thị & ẩn người", - "show_file_location": "Hiển thị vị trí tập tin", - "show_gallery": "Hiển thị thư viện ảnh", - "show_hidden_people": "Hiển thị người bị ẩn", - "show_in_timeline": "Hiển thị trên dòng thời gian", - "show_in_timeline_setting_description": "Hiển thị ảnh và video từ người dùng này trong dòng thời gian của bạn", - "show_keyboard_shortcuts": "Hiện phím tắt", - "show_metadata": "Hiển thị metadata", - "show_or_hide_info": "Hiển thị hoặc ẩn thông tin", - "show_password": "Hiển thị mật khẩu", - "show_person_options": "Hiện tùy chọn người", - "show_progress_bar": "Hiển thị thanh tiến trình", - "show_schema": "Hiện lược đồ", - "show_search_options": "Hiện tùy chọn tìm kiếm", - "show_shared_links": "Hiển thị các liên kết được chia sẻ", - "show_slideshow_transition": "Hiển thị hiệu ứng chuyển tiếp", - "show_supporter_badge": "Huy hiệu người ủng hộ", - "show_supporter_badge_description": "Hiển thị huy hiệu người ủng hộ", - "show_text_recognition": "Hiển thị nhận dạng văn bản", - "show_text_search_menu": "Hiển thị menu tìm kiếm văn bản", - "shuffle": "Ngẫu nhiên", - "sidebar": "Thanh bên", - "sidebar_display_description": "Hiển thị liên kết đến chế độ xem trong thanh bên", - "sign_out": "Đăng xuất", - "sign_up": "Đăng ký", - "size": "Kích thước", - "skip_to_content": "Chuyển đến nội dung", - "skip_to_folders": "Chuyển đến thư mục", - "skip_to_tags": "Chuyển đến thẻ", - "slideshow": "Trình chiếu", - "slideshow_settings": "Cài đặt trình chiếu", - "sort_albums_by": "Sắp xếp album theo...", - "sort_created": "Ngày tạo", - "sort_items": "Số lượng mục", - "sort_modified": "Ngày sửa đổi", - "sort_newest": "Ảnh mới nhất", - "sort_oldest": "Ảnh cũ nhất", - "sort_people_by_similarity": "Sắp xếp người theo độ tương đồng", - "sort_recent": "Ảnh gần đây nhất", - "sort_title": "Tiêu đề", - "source": "Nguồn", - "stack": "Nhóm ảnh", - "stack_action_prompt": "{count} đã được nhóm", - "stack_duplicates": "Nhóm mục trùng lặp", - "stack_select_one_photo": "Chọn ảnh chính cho nhóm ảnh", - "stack_selected_photos": "Nhóm các ảnh đã chọn", - "stacked_assets_count": "Đã nhóm {count, plural, one {# tệp} other {# tệp}}", - "stacktrace": "Thông tin chi tiết lỗi", - "start": "Bắt đầu", - "start_date": "Ngày bắt đầu", - "start_date_before_end_date": "Ngày bắt đầu phải trước ngày kết thúc", - "state": "Tỉnh", - "status": "Trạng thái", - "stop_casting": "Dừng chiếu", - "stop_motion_photo": "Dừng ảnh chuyển động", - "stop_photo_sharing": "Dừng chia sẻ ảnh của bạn?", - "stop_photo_sharing_description": "{partner} sẽ không thể truy cập được ảnh của bạn.", - "stop_sharing_photos_with_user": "Dừng chia sẻ ảnh của bạn với người dùng này", - "storage": "Bộ nhớ", - "storage_label": "Nhãn lưu trữ", - "storage_quota": "Hạn mức dung lượng", - "storage_usage": "Đã dùng {used} của {available}", - "submit": "Gửi", - "success": "Thành công", - "suggestions": "Gợi ý", - "sunrise_on_the_beach": "Bình minh trên bãi biển", - "support": "Hỗ trợ", - "support_and_feedback": "Hỗ trợ & Góp ý", - "support_third_party_description": "Bản cài đặt Immich của bạn được đóng gói bởi một bên thứ ba. Các sự cố bạn gặp phải có thể do gói đó gây ra, vì vậy vui lòng báo cáo sự cố với họ trước bằng cách sử dụng các liên kết bên dưới.", - "swap_merge_direction": "Đổi hướng hợp nhất", - "sync": "Đồng bộ", - "sync_albums": "Đồng bộ album", - "sync_albums_manual_subtitle": "Đồng bộ hóa tất cả video và ảnh đã tải lên vào album sao lưu đã chọn", - "sync_local": "Đồng bộ hóa thiết bị", - "sync_remote": "Đồng bộ hóa trên mây", - "sync_status": "Trạng thái đồng bộ", - "sync_status_subtitle": "Thống kê về việc đồng bộ", - "sync_upload_album_setting_subtitle": "Tạo và tải lên ảnh và video của bạn vào album đã chọn trên Immich", - "tag": "Thẻ", - "tag_assets": "Gắn thẻ", - "tag_created": "Đã tạo thẻ: {tag}", - "tag_feature_description": "Duyệt ảnh và video được nhóm theo chủ đề thẻ hợp lý", - "tag_not_found_question": "Không tìm thấy thẻ? Tạo một thẻ mới", - "tag_people": "Gắn thẻ Mọi người", - "tag_updated": "Đã cập nhật thẻ: {tag}", - "tagged_assets": "Đã gắn thẻ {count, plural, one {# tệp} other {# tệp}}", - "tags": "Thẻ", - "tap_to_run_job": "Nhấn để chạy tác vụ", - "template": "Mẫu", - "text_recognition": "Nhận dạng văn bản", - "theme": "Chủ đề", - "theme_selection": "Chủ đề", - "theme_selection_description": "Dựa theo trình duyệt của bạn", - "theme_setting_asset_list_storage_indicator_title": "Hiện trạng thái sao lưu trên ảnh thu nhỏ", - "theme_setting_asset_list_tiles_per_row_title": "Số lượng tệp trên mỗi hàng ({count})", - "theme_setting_colorful_interface_subtitle": "Áp dụng màu chủ đạo cho nền app.", - "theme_setting_colorful_interface_title": "Giao diện sinh động", - "theme_setting_image_viewer_quality_subtitle": "Điều chỉnh chất lượng của trình xem ảnh", - "theme_setting_image_viewer_quality_title": "Chất lượng trình xem ảnh", - "theme_setting_primary_color_subtitle": "Chọn màu cho các hành động chính và điểm nhấn.", - "theme_setting_primary_color_title": "Màu chủ đạo", - "theme_setting_system_primary_color_title": "Dùng màu hệ thống", - "theme_setting_system_theme_switch": "Tự động (Giống thiết bị)", - "theme_setting_theme_subtitle": "Chọn cài đặt giao diện app", - "theme_setting_three_stage_loading_subtitle": "Tải ba giai đoạn có thể tăng tốc độ tải ảnh nhưng sẽ tốn dữ liệu mạng đáng kể", - "theme_setting_three_stage_loading_title": "Bật tải ba giai đoạn", - "they_will_be_merged_together": "Chúng sẽ được hợp nhất với nhau", - "third_party_resources": "Tài nguyên bên thứ ba", - "time": "Thời gian", - "time_based_memories": "Kỷ niệm dựa trên thời gian", - "time_based_memories_duration": "Số giây hiển thị mỗi ảnh.", - "timeline": "Dòng thời gian", - "timezone": "Múi giờ", - "to_archive": "Lưu trữ", - "to_change_password": "Đổi mật khẩu", - "to_favorite": "Thích", - "to_login": "Đăng nhập", - "to_multi_select": "để chọn-nhiều", - "to_parent": "Về thư mục gốc", - "to_select": "để chọn", - "to_trash": "Xóa", - "toggle_settings": "Chuyển đổi cài đặt", - "toggle_theme_description": "Đổi chủ đề", - "total": "Tổng cộng", - "total_usage": "Dung lượng đã dùng", - "trash": "Thùng rác", - "trash_action_prompt": "{count} đã chuyển vào thùng rác", - "trash_all": "Xóa hết", - "trash_count": "Xóa {count, number} mục", - "trash_delete_asset": "Chuyển vào thùng rác/Xóa vĩnh viễn", - "trash_emptied": "Đã dọn sạch thùng rác", - "trash_no_results_message": "Ảnh và video đã bị xóa sẽ hiển thị ở đây.", - "trash_page_delete_all": "Xóa tất cả", - "trash_page_empty_trash_dialog_content": "Bạn có muốn dọn sạch thùng rác của mình không? Những mục này sẽ bị xóa vĩnh viễn khỏi Immich", - "trash_page_info": "Những mục này sẽ bị xóa sau {days} ngày", - "trash_page_no_assets": "Không có tệp nào", - "trash_page_restore_all": "Khôi phục tất cả", - "trash_page_select_assets_btn": "Chọn tệp", - "trash_page_title": "Thùng rác ({count})", - "trashed_items_will_be_permanently_deleted_after": "Các mục đã xóa sẽ bị xóa vĩnh viễn sau {days, plural, one {# ngày} other {# ngày}}.", - "trigger": "Kích hoạt", - "trigger_asset_uploaded": "Tệp đã được tải lên", - "trigger_asset_uploaded_description": "Sự kiện này được kích hoạt khi một tệp mới được tải lên", - "trigger_description": "Một sự kiện khởi đầu workflow", - "trigger_person_recognized": "Người được nhận diện", - "trigger_person_recognized_description": "Được kích hoạt khi phát hiện thấy một người", - "trigger_type": "Kiểu kích hoạt", - "troubleshoot": "Khắc phục sự cố", - "type": "Loại", - "unable_to_change_pin_code": "Thay đổi mã PIN thất bại", - "unable_to_check_version": "Không thể kiểm tra phiên bản app hoặc máy chủ", - "unable_to_setup_pin_code": "Thiết lập mã PIN thất bại", - "unarchive": "Bỏ lưu trữ", - "unarchive_action_prompt": "{count} đã bỏ khỏi Lưu trữ", - "unarchived_count": "{count, plural, other {Đã bỏ lưu trữ # mục}}", - "undo": "Hoàn tác", - "unfavorite": "Bỏ thích", - "unfavorite_action_prompt": "{count} đã bỏ khỏi Đã thích", - "unhide_person": "Hiện người", - "unknown": "Không xác định", - "unknown_country": "Quốc gia chưa rõ", - "unknown_year": "Năm không rõ", - "unlimited": "Không giới hạn", - "unlink_motion_video": "Hủy liên kết video chuyển động", - "unlink_oauth": "Hủy liên kết OAuth", - "unlinked_oauth_account": "Đã hủy liên kết tài khoản OAuth", - "unmute_memories": "Bật tiếng Kỷ niệm", - "unnamed_album": "Album chưa đặt tên", - "unnamed_album_delete_confirmation": "Bạn có chắc muốn xóa album này?", - "unnamed_share": "Chia sẻ chưa đặt tên", - "unsaved_change": "Chưa lưu thay đổi", - "unselect_all": "Bỏ chọn tất cả", - "unselect_all_duplicates": "Bỏ chọn tất cả các bản trùng lặp", - "unselect_all_in": "Bỏ chọn tất cả trong {group}", - "unstack": "Hủy xếp nhóm", - "unstack_action_prompt": "{count} đã bỏ nhóm", - "unstacked_assets_count": "Đã hủy xếp nhóm {count, plural, one {# tệp} other {# tệp}}", - "unsupported_field_type": "Loại trường không được hỗ trợ", - "untagged": "Chưa gắn thẻ", - "untitled_workflow": "Workflow chưa đặt tên", - "up_next": "Tiếp theo", - "update_location_action_prompt": "Cập nhật địa điểm của {count} tệp đã chọn với:", - "updated_at": "Đã cập nhật", - "updated_password": "Đã cập nhật mật khẩu", - "upload": "Tải lên", - "upload_concurrency": "Tải lên đồng thời", - "upload_details": "Chi tiết tải lên", - "upload_dialog_info": "Bạn có muốn sao lưu những tệp đã chọn lên máy chủ không?", - "upload_dialog_title": "Tải lên tệp", - "upload_errors": "Đã hoàn tất tải lên với {count, plural, one {# lỗi} other {# lỗi}}, làm mới trang để xem các tệp vừa tải lên.", - "upload_finished": "Đã hoàn tất tải lên", - "upload_progress": "Còn lại {remaining, number} - Đã xử lý {processed, number}/{total, number}", - "upload_skipped_duplicates": "Đã bỏ qua {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}}", - "upload_status_duplicates": "Tệp trùng lặp", - "upload_status_errors": "Lỗi", - "upload_status_uploaded": "Đã tải lên", - "upload_success": "Tải lên thành công, làm mới trang để xem các tệp vừa tải lên.", - "upload_to_immich": "Tải lên Immich ({count})", - "uploading": "Đang tải lên", - "uploading_media": "Đang tải lên phương tiện", - "url": "URL", - "usage": "Sử dụng", - "use_biometric": "Dùng sinh trắc học", - "use_current_connection": "dùng kết nối hiện tại", - "use_custom_date_range": "Chọn khoảng thời gian tùy chỉnh", - "user": "Người dùng", - "user_has_been_deleted": "Người dùng này đã bị xóa.", - "user_id": "ID người dùng", - "user_liked": "{user} đã thích {type, select, photo {ảnh này} video {video này} asset {tệp này} other {nó}}", - "user_pin_code_settings": "Mã PIN", - "user_pin_code_settings_description": "Quản lý mã PIN của bạn", - "user_privacy": "Quyền riêng tư người dùng", - "user_purchase_settings": "Mua", - "user_purchase_settings_description": "Quản lý đơn mua của bạn", - "user_role_set": "Chọn {user} làm {role}", - "user_usage_detail": "Chi tiết sử dụng của người dùng", - "user_usage_stats": "Thống kê", - "user_usage_stats_description": "Xem thống kê sử dụng của tài khoản", - "username": "Tên người dùng", - "users": "Người dùng", - "users_added_to_album_count": "Đã thêm {count, plural, one {# người dùng} other {# người dùng}} vào album", - "utilities": "Tiện ích", - "validate": "Xác minh", - "validate_endpoint_error": "Vui lòng nhập URL hợp lệ", - "validation_error": "Lỗi xác thực", - "variables": "Các tham số", - "version": "Phiên bản", - "version_announcement_closing": "Bạn của bạn, Alex", - "version_announcement_message": "Chào bạn! Một phiên bản mới của Immich đã ra mắt. Vui lòng dành thời gian để xem danh sách thay đổi để đảm bảo cấu hình của bạn được cập nhật để tránh lỗi cấu hình sai, đặc biệt nếu bạn sử dụng WatchTower hoặc bất kỳ cơ chế tự động cập nhật Immich của bạn.", - "version_history": "Lịch sử phiên bản", - "version_history_item": "Đã cài đặt {version} vào {date}", - "video": "Video", - "video_hover_setting": "Xem trước video khi di chuột lên", - "video_hover_setting_description": "Phát đoạn video xem trước khi di chuột qua mục. Ngay cả khi tắt chức năng này, vẫn có thể bắt đầu phát video bằng cách di chuột qua biểu tượng phát.", - "videos": "Video", - "videos_count": "{count, plural, one {# Video} other {# Video}}", - "videos_only": "Chỉ video", - "view": "Xem", - "view_album": "Xem Album", - "view_all": "Xem tất cả", - "view_all_users": "Xem tất cả người dùng", - "view_asset_owners": "Xem chủ sở hữu tệp", - "view_details": "Xem thông tin chi tiết", - "view_in_timeline": "Xem trong dòng thời gian", - "view_link": "Xem liên kết", - "view_links": "Xem các liên kết", - "view_name": "Giao diện", - "view_next_asset": "Xem tệp tiếp theo", - "view_previous_asset": "Xem tệp trước đó", - "view_qr_code": "Xem mã QR", - "view_similar_photos": "Xem ảnh tương tự", - "view_stack": "Xem nhóm ảnh", - "view_user": "Xem Người dùng", - "viewer_remove_from_stack": "Xóa khỏi nhóm", - "viewer_stack_use_as_main_asset": "Đặt làm ảnh nổi bật", - "viewer_unstack": "Hủy xếp nhóm", - "visibility_changed": "Đã thay đổi trạng thái hiển thị cho {count, plural, one {# người} other {# người}}", - "visual": "Trực quan", - "visual_builder": "Tạo trực quan", - "waiting": "Đang chờ", - "waiting_count": "Đang chờ: {count}", - "warning": "Cảnh báo", - "week": "Tuần", - "welcome": "Chào mừng", - "welcome_to_immich": "Chào mừng đến với Immich", - "width": "Chiều rộng", - "wifi_name": "Tên Wi-Fi", - "workflow_delete_prompt": "Bạn có chắc muốn xóa luồng công việc này?", - "workflow_deleted": "Đã xóa luồng công việc", - "workflow_description": "Mô tả luồng công việc", - "workflow_info": "Thông tin luồng công việc", - "workflow_json": "JSON của luồng công việc", - "workflow_json_help": "Chỉnh sửa cấu hình luồng công việc ở định dạng JSON. Các thay đổi sẽ được đồng bộ hóa với trình tạo trực quan.", - "workflow_name": "Tên luồng công việc", - "workflow_navigation_prompt": "Bạn có chắc muốn rời đi mà không lưu lại các thay đổi của mình?", - "workflow_summary": "Mô tả luồng công việc", - "workflow_update_success": "Đã cập nhật luồng công việc thành công", - "workflow_updated": "Đã cập nhật Luồng công việc", - "workflows": "Luồng công việc", - "workflows_help_text": "Luồng công việc tự động hóa các hành động trên tập tin của bạn dựa trên các trình kích hoạt và bộ lọc", - "wrong_pin_code": "Mã PIN không đúng", - "year": "Năm", - "years_ago": "{years, plural, one {# năm} other {# năm}} trước", - "yes": "Đồng ý", - "you_dont_have_any_shared_links": "Bạn không có liên kết chia sẻ nào", - "your_wifi_name": "Tên Wi-Fi của bạn", - "zero_to_clear_rating": "nhấn 0 để xóa đánh giá ảnh", - "zoom_image": "Thu phóng ảnh", - "zoom_to_bounds": "Thu phóng vừa khung" -} +{} diff --git a/i18n/yue_Hant.json b/i18n/yue_Hant.json index 823149da9e..0967ef424b 100644 --- a/i18n/yue_Hant.json +++ b/i18n/yue_Hant.json @@ -1,80 +1 @@ -{ - "about": "關於", - "account": "帳號", - "account_settings": "帳號設定", - "acknowledge": "了解", - "action": "動作", - "action_common_update": "更新", - "action_description": "針對篩選後嘅資源執行嘅", - "actions": "動作", - "active": "正在處理", - "active_count": "正在處理:{count}", - "activity": "活動", - "activity_changed": "活動已{enabled, select, true {啟動} other {停止}}", - "add": "加", - "add_a_description": "加一個描述", - "add_a_location": "加一個位置", - "add_a_name": "加一個姓名", - "add_a_title": "加一個標題", - "add_action": "加動作", - "add_action_description": "點擊以加動作", - "add_assets": "加資源", - "add_birthday": "加一個生日", - "add_endpoint": "加端點", - "add_filter": "加過濾器", - "add_filter_description": "點擊以加一個過濾條件", - "add_location": "加位置", - "add_more_users": "加更多用戶", - "add_partner": "加伙伴", - "add_path": "加路徑", - "add_photos": "加多張相片", - "add_tag": "加標籤", - "add_to": "加至…", - "add_to_album": "加至相簿", - "add_to_album_bottom_sheet_added": "已加至{album}", - "add_to_album_bottom_sheet_already_exists": "已在 {album} 中", - "add_to_album_bottom_sheet_some_local_assets": "無法加部分本機資源至相簿", - "add_to_albums": "加至相簿", - "add_to_albums_count": "加 ({count}) 個項目至相簿", - "add_to_bottom_bar": "加至", - "add_to_shared_album": "加至共享相簿", - "add_url": "加網址", - "added_to_favorites": "已加至最愛", - "added_to_favorites_count": "已加{count, number} 個項目至最愛", - "admin": { - "admin_user": "管理員用戶", - "authentication_settings": "驗證設定", - "authentication_settings_description": "管理密碼、OAuth 同其他驗證設定", - "backup_onboarding_parts_title": "一個3-2-1備份包括:", - "backup_onboarding_title": "備份" - }, - "main_menu": "主選單", - "maintenance_action_restore": "還原緊數據庫", - "onboarding_user_welcome_description": "我哋而家開始喇!", - "onboarding_welcome_user": "歡迎,{user}", - "online": "已上線", - "only_favorites": "只顯示最愛", - "open": "開", - "open_in_map_view": "用地圖開", - "open_in_openstreetmap": "用 OpenStreetMap 開", - "open_the_search_filters": "開搜尋過濾器", - "options": "選項", - "or": "或者", - "organize_into_albums": "執成相簿", - "setting_notifications_notify_seconds": "{count} 秒", - "warning": "警告", - "week": "星期", - "welcome": "歡迎", - "welcome_to_immich": "歡迎使用 Immich", - "width": "寬", - "wifi_name": "Wi-Fi 名", - "wrong_pin_code": "PIN 碼唔啱", - "year": "年", - "years_ago": "{years, plural, one {#年} other {#年}}前", - "yes": "是", - "you_dont_have_any_shared_links": "你無共享連結", - "your_wifi_name": "你嘅 Wi-Fi 名稱", - "zero_to_clear_rating": "按0以清除資源評級", - "zoom_image": "縮放相片", - "zoom_to_bounds": "縮放至邊界" -} +{} diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index 3742ca34f8..0967ef424b 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -1,2391 +1 @@ -{ - "about": "關於", - "account": "帳號", - "account_settings": "帳號設定", - "acknowledge": "了解", - "action": "操作", - "action_common_update": "更新", - "action_description": "對篩選後的資產執行的一組操作", - "actions": "進行動作", - "active": "處理中", - "active_count": "處理中:{count}", - "activity": "動態", - "activity_changed": "動態已{enabled, select, true {開啟} other {關閉}}", - "add": "加入", - "add_a_description": "新增描述", - "add_a_location": "新增地點", - "add_a_name": "加入姓名", - "add_a_title": "新增標題", - "add_action": "添加動作", - "add_action_description": "按一下以添加要執行的操作", - "add_assets": "添加資源", - "add_birthday": "新增生日", - "add_endpoint": "新增端點", - "add_exclusion_pattern": "加入篩選條件", - "add_filter": "添加篩選器", - "add_filter_description": "按一下以添加篩選條件", - "add_location": "新增地點", - "add_more_users": "新增其他使用者", - "add_partner": "新增親朋好友", - "add_path": "新增路徑", - "add_photos": "加入照片", - "add_tag": "加入標籤", - "add_to": "加入到…", - "add_to_album": "加入到相簿", - "add_to_album_bottom_sheet_added": "新增到 {album}", - "add_to_album_bottom_sheet_already_exists": "已在 {album} 中", - "add_to_album_bottom_sheet_some_local_assets": "無法將某些本機資產新增到相簿", - "add_to_album_toggle": "選擇相簿{album}", - "add_to_albums": "加入相簿", - "add_to_albums_count": "將 ({count}) 個項目加入相簿", - "add_to_bottom_bar": "新增到", - "add_to_shared_album": "加到共享相簿", - "add_upload_to_stack": "新增上傳到堆疊", - "add_url": "新增 URL", - "add_workflow_step": "添加工作流步驟", - "added_to_archive": "移至封存", - "added_to_favorites": "加入收藏", - "added_to_favorites_count": "將 {count, number} 個項目加入收藏", - "admin": { - "add_exclusion_pattern_description": "新增排除條件。支援使用「*」、「 **」、「?」來找尋符合規則的字串。如果要在任何名為「Raw」的目錄內排除所有符合條件的檔案,請使用「**/Raw/**」。如果要排除所有「.tif」結尾的檔案,請使用「**/*.tif」。如果要排除某個絕對路徑,請使用「/path/to/ignore/**」。", - "admin_user": "管理員", - "asset_offline_description": "此外部媒體庫項目已無法在磁碟上找到,並已移至垃圾桶。若該檔案是在媒體庫內移動,請在時間軸中檢視新的對應項目。若要還原此項目,請確保下方的檔案路徑可供 Immich 存取,並重新掃描媒體庫。", - "authentication_settings": "驗證設定", - "authentication_settings_description": "管理密碼、OAuth 與其他驗證設定", - "authentication_settings_disable_all": "確定要停用所有登入方式嗎?這樣會完全無法登入。", - "authentication_settings_reenable": "如需重新啟用,請使用 伺服器指令 。", - "background_task_job": "背景工作", - "backup_database": "建立資料庫備份", - "backup_database_enable_description": "啟用資料庫備份", - "backup_keep_last_amount": "保留先前備份的數量", - "backup_onboarding_1_description": "在雲端或其他實體位置的異地備份副本。", - "backup_onboarding_2_description": "儲存在不同裝置上的本機副本。這包括主要檔案及其本機備份。", - "backup_onboarding_3_description": "您資料的總備份份數,包括原始檔案在內。這包括 1 份異地備份與 2 份本機副本。", - "backup_onboarding_description": "建議採用 3-2-1 備份策略 來保護您的資料。您應保留已上傳的照片/影片副本,以及 Immich 資料庫,以建立完整的備份方案。", - "backup_onboarding_footer": "更多備份 Immich 資訊,請參考說明文件。", - "backup_onboarding_parts_title": "遵從備份原則 3-2-1:", - "backup_onboarding_title": "備份", - "backup_settings": "資料庫備份設定", - "backup_settings_description": "管理資料庫備份設定。", - "cleared_jobs": "已刪除「{job}」任務", - "config_set_by_file": "目前的設定是由設定檔設定", - "confirm_delete_library": "您確定要刪除外部媒體庫 {library} 嗎?", - "confirm_delete_library_assets": "您確定要刪除此外部媒體庫嗎?這將從 Immich 中刪除 {count, plural, one {# 個項目} other {# 個項目}} ,且無法復原。檔案仍會保留在硬碟中。", - "confirm_email_below": "請在底下輸入 {email} 以確認", - "confirm_reprocess_all_faces": "您確定要重新處理所有臉孔嗎?這會清除已命名的人物。", - "confirm_user_password_reset": "您確定要重設 {user} 的密碼嗎?", - "confirm_user_pin_code_reset": "確定要重設 {user} 的 PIN 碼嗎?", - "copy_config_to_clipboard_description": "將當前系統配寘作為JSON對象複製到剪貼板", - "create_job": "建立任務", - "cron_expression": "Cron 表達式", - "cron_expression_description": "使用 Cron 格式設定掃描間隔。更多資訊請參閱 Crontab Guru", - "cron_expression_presets": "Cron 表達式預設範本", - "disable_login": "停用登入", - "duplicate_detection_job_description": "依靠智慧搜尋。對項目執行機器學習來偵測相似圖片", - "exclusion_pattern_description": "排除規則可讓您在掃描媒體庫時忽略特定的檔案和資料夾。這在您有些資料夾包含不想匯入的檔案(例如 RAW 檔)時特別有用。", - "export_config_as_json_description": "將當前系統配寘下載為JSON檔案", - "external_libraries_page_description": "管理外部庫頁面", - "face_detection": "臉孔偵測", - "face_detection_description": "使用機器學習偵測媒體檔案中的臉孔。對於影片,僅會分析縮圖。「重新整理」會(重新)處理所有媒體檔案。「重設」則會額外清除目前的所有人臉資料。「排入未處理」會將尚未處理過的媒體檔案加入佇列。在完成「臉孔偵測』後,偵測到的臉孔將會被加入「臉孔辨識」的佇列,並依照辨識結果歸類到現有或新的人物群組中。", - "facial_recognition_job_description": "將偵測到的臉孔依照人物分類。此步驟會在臉孔偵測完成後執行。選擇「重設」會重新分組所有臉孔。選擇「排入未處理」會將尚未指派人物的臉孔加入佇列。", - "failed_job_command": "{job} 任務的 {command} 指令執行失敗", - "force_delete_user_warning": "警告:這將立即刪除使用者及其所有項目。此操作無法撤銷並且無法還原刪除的檔案。", - "image_format": "格式", - "image_format_description": "WebP 能產生相對於 JPEG 更小的檔案,但編碼速度較慢。", - "image_fullsize_description": "移除中繼資料的大尺寸影像,在放大圖片時使用", - "image_fullsize_enabled": "啟用大尺寸影像產生", - "image_fullsize_enabled_description": "產生非網頁友善格式的大尺寸影像。啟用「偏好嵌入的預覽」時,會直接使用內嵌預覽而不進行轉換。不會影響 JPEG 等網頁友善格式。", - "image_fullsize_quality_description": "大尺寸影像品質,範圍為 1 到 100。數值越高品質越好,但檔案也會越大。", - "image_fullsize_title": "大尺寸影像設定", - "image_prefer_embedded_preview": "偏好嵌入的預覽", - "image_prefer_embedded_preview_setting_description": "在可行的情況下,將 RAW 照片中的內嵌預覽用作影像處理的輸入來源。這對某些影像能產生更準確的色彩,但預覽的品質取決於相機,影像可能會出現更多壓縮瑕疵。", - "image_prefer_wide_gamut": "偏好廣色域", - "image_prefer_wide_gamut_setting_description": "使用 Display P3 來製作縮圖。這能更好地保留寬廣色域影像的鮮豔度,但在舊裝置與舊版本瀏覽器上,影像可能會呈現不同的效果。sRGB 影像會保持為 sRGB,以避免色彩偏移。", - "image_preview_description": "移除中繼資料的中尺寸影像,用於檢視單一媒體檔案以及機器學習時使用", - "image_preview_quality_description": "預覽品質範圍為 1 到 100。數值越高品質越好,但檔案也會更大,並可能降低應用程式的回應速度。設定過低的數值可能會影響機器學習的品質。", - "image_preview_title": "預覽設定", - "image_progressive": "逐步", - "image_progressive_description": "對JPEG圖像進行逐步編碼,以實現漸進式加載顯示。這不會影響WebP圖像。", - "image_quality": "品質", - "image_resolution": "解析度", - "image_resolution_description": "較高的解析度能保留更多細節,但編碼時間會更長、檔案大小會更大,並可能降低應用程式的回應速度。", - "image_settings": "圖片設定", - "image_settings_description": "管理產生圖片的品質和解析度", - "image_thumbnail_description": "移除中繼資料的小型縮圖,以用於檢視大量照片時使用,例如主時間軸", - "image_thumbnail_quality_description": "縮圖品質範圍為 1 到 100。數值越高品質越好,但檔案也會更大,並可能降低應用程式的回應速度。", - "image_thumbnail_title": "縮圖設定", - "import_config_from_json_description": "通過上傳JSON設定檔導入系統配寘", - "job_concurrency": "{job}併發", - "job_created": "已建立任務", - "job_not_concurrency_safe": "這個任務併發並不安全。", - "job_settings": "任務設定", - "job_settings_description": "併發任務管理", - "jobs_delayed": "{jobCount, plural, other {# 項任務已延後}}", - "jobs_failed": "{jobCount, plural, other {# 項任務已失敗}}", - "jobs_over_time": "組織時間任務數", - "library_created": "已建立媒體庫:{library}", - "library_deleted": "媒體庫已刪除", - "library_details": "媒體庫詳情", - "library_folder_description": "指定要導入的資料夾。 將掃描此資料夾(包括子資料夾)中的影像和視頻。", - "library_remove_exclusion_pattern_prompt": "您確定要删除此排除模式嗎?", - "library_remove_folder_prompt": "您確定要删除此導入資料夾嗎?", - "library_scanning": "定期掃描", - "library_scanning_description": "定期媒體庫掃描設定", - "library_scanning_enable_description": "啟用媒體庫定期掃描", - "library_settings": "外部媒體庫", - "library_settings_description": "管理外部媒體庫設定", - "library_tasks_description": "掃描外部媒體庫以尋找新增或變更的項目", - "library_updated": "更新媒體庫", - "library_watching_enable_description": "監控外部媒體庫的檔案變化", - "library_watching_settings": "媒體庫監控[實驗性]", - "library_watching_settings_description": "自動監控檔案的變化", - "logging_enable_description": "啟用日誌記錄", - "logging_level_description": "啟用時的日誌層級。", - "logging_settings": "日誌", - "machine_learning_availability_checks": "可用性檢查", - "machine_learning_availability_checks_description": "自動偵測並優先選擇可用的機器學習伺服器", - "machine_learning_availability_checks_enabled": "啟用可用性檢查", - "machine_learning_availability_checks_interval": "檢查間隔", - "machine_learning_availability_checks_interval_description": "可用性檢查之間的間隔(毫秒)", - "machine_learning_availability_checks_timeout": "請求超時", - "machine_learning_availability_checks_timeout_description": "可用性檢查超時(毫秒)", - "machine_learning_clip_model": "CLIP 模型", - "machine_learning_clip_model_description": "這裡有份 CLIP 模型名單。注意:更換模型後須對所有圖片重新執行「智慧搜尋」任務。", - "machine_learning_duplicate_detection": "重複項目偵測", - "machine_learning_duplicate_detection_enabled": "啟用重複項目偵測", - "machine_learning_duplicate_detection_enabled_description": "若停用,完全相同的媒體檔案仍會進行重複資料刪除。", - "machine_learning_duplicate_detection_setting_description": "使用 CLIP 向量比對尋找可能的重複項目", - "machine_learning_enabled": "啟用機器學習", - "machine_learning_enabled_description": "若停用,則無視下方的設定,所有機器學習的功能都將停用。", - "machine_learning_facial_recognition": "人臉辨識", - "machine_learning_facial_recognition_description": "偵測、辨識並對圖片中的臉孔分類", - "machine_learning_facial_recognition_model": "人臉辨識模型", - "machine_learning_facial_recognition_model_description": "模型順序由大至小排列。大的模型較慢且使用較多記憶體,但成效較佳。更換模型後需對所有影像重新執行「人臉辨識」。", - "machine_learning_facial_recognition_setting": "啟用人臉辨識", - "machine_learning_facial_recognition_setting_description": "若停用,影像將不會產生人臉辨識編碼,並且在「探索」頁面不會有「人物」功能。", - "machine_learning_max_detection_distance": "偵測距離上限", - "machine_learning_max_detection_distance_description": "若兩張影像間的距離小於此將被判斷為相同,範圍為 0.001-0.1。數值越高能偵測到越多重複,但也更有可能誤判。", - "machine_learning_max_recognition_distance": "辨識距離上限", - "machine_learning_max_recognition_distance_description": "兩張臉孔被視為同一人物的最大距離,範圍為 0 至 2。降低此數值可避免將不同人物標記為同一人;提高此數值則可避免將同一人物標記為兩個不同的人。請注意,合併兩個人物比將一個人物拆分成兩個更容易,因此在可能的情況下,建議將此閾值設定得較低。", - "machine_learning_min_detection_score": "最低偵測分數", - "machine_learning_min_detection_score_description": "臉孔偵測的最低信心分數,範圍為 0 至 1。數值較低時會偵測到更多臉孔,但可能導致誤判。", - "machine_learning_min_recognized_faces": "最低臉部辨識數量", - "machine_learning_min_recognized_faces_description": "建立新人物所需的最低已辨識臉孔數量。提高此數值可讓臉孔辨識更精確,但同時會增加臉孔未被指派給任何人物的可能性。", - "machine_learning_ocr": "文字辨識(OCR)", - "machine_learning_ocr_description": "使用機器學習來識別圖片中的文字", - "machine_learning_ocr_enabled": "啟用OCR", - "machine_learning_ocr_enabled_description": "如果禁用,影像將不會進行文字識別。", - "machine_learning_ocr_max_resolution": "最大分辯率", - "machine_learning_ocr_max_resolution_description": "高於此分辯率的預覽將調整大小,同時保持縱橫比。 更高的值更準確,但處理時間更長,佔用更多記憶體。", - "machine_learning_ocr_min_detection_score": "最低檢測分數", - "machine_learning_ocr_min_detection_score_description": "要檢測的文字的最小置信度分數為0-1。 較低的值將檢測到更多的文字,但可能會導致誤報。", - "machine_learning_ocr_min_recognition_score": "最低識別分數", - "machine_learning_ocr_min_score_recognition_description": "檢測到的文字的最小置信度得分為0-1。 較低的值將識別更多的文字,但可能會導致誤報。", - "machine_learning_ocr_model": "OCR模型", - "machine_learning_ocr_model_description": "服務器模型比移動模型更準確,但需要更長的時間來處理和使用更多的記憶體。", - "machine_learning_settings": "機器學習設定", - "machine_learning_settings_description": "管理機器學習的功能和設定", - "machine_learning_smart_search": "智慧搜尋", - "machine_learning_smart_search_description": "使用 CLIP 嵌入向量以語意方式搜尋影像", - "machine_learning_smart_search_enabled": "啟用智慧搜尋", - "machine_learning_smart_search_enabled_description": "如果停用,影像將不會被編碼以進行智慧搜尋。", - "machine_learning_url_description": "機器學習伺服器的 URL。若提供多個 URL,系統會依序逐一嘗試,直到其中一臺成功回應為止(由前到後)。未回應的伺服器將被暫時忽略,直到其重新上線。", - "maintenance_delete_backup": "刪除備份", - "maintenance_delete_backup_description": "此文件將被永久刪除。", - "maintenance_delete_error": "刪除備份失敗。", - "maintenance_restore_backup": "恢復備份", - "maintenance_restore_backup_description": "Immich數據將被請出,并從選定的備份中恢復。在繼續之前,將先創建一個當前數據的備份。", - "maintenance_restore_backup_different_version": "此備份是由不同版本的Immich創建的!", - "maintenance_restore_backup_unknown_version": "無法確定備份版本。", - "maintenance_restore_database_backup": "恢復數據庫備份", - "maintenance_restore_database_backup_description": "使用備份文件將數據庫回滾到較早的狀態", - "maintenance_settings": "維護", - "maintenance_settings_description": "將Immich置於維護模式。", - "maintenance_start": "啟動維護模式", - "maintenance_start_error": "啟動維護模式失敗。", - "maintenance_upload_backup": "上傳數據庫備份文件", - "maintenance_upload_backup_error": "無法上傳備份,它是.sql或.sql.gz格式的文件嗎?", - "manage_concurrency": "管理併發", - "manage_concurrency_description": "導航到任務頁面以管理任務併發性", - "manage_log_settings": "管理日誌設定", - "map_dark_style": "深色樣式", - "map_enable_description": "啟用地圖功能", - "map_gps_settings": "地圖與 GPS 設定", - "map_gps_settings_description": "管理地圖與 GPS(反向地理編碼)設定", - "map_implications": "地圖功能仰賴外部圖磚服務(tiles.immich.cloud)", - "map_light_style": "淺色樣式", - "map_manage_reverse_geocoding_settings": "管理逆向地理編碼設定", - "map_reverse_geocoding": "反向地理編碼", - "map_reverse_geocoding_enable_description": "啟用反向地理編碼", - "map_reverse_geocoding_settings": "反向地理編碼設定", - "map_settings": "地圖", - "map_settings_description": "管理地圖設定", - "map_style_description": "地圖主題(style.json)的網址", - "memory_cleanup_job": "回憶清理", - "memory_generate_job": "產生回憶", - "metadata_extraction_job": "擷取中繼資料", - "metadata_extraction_job_description": "從每個媒體檔案中擷取中繼資料資訊,例如 GPS、臉孔與解析度", - "metadata_faces_import_setting": "啟用臉孔匯入", - "metadata_faces_import_setting_description": "從影像 EXIF 資料與側接檔案匯入臉孔", - "metadata_settings": "中繼資料設定", - "metadata_settings_description": "管理中繼資料設定", - "migration_job": "遷移", - "migration_job_description": "將媒體檔案與臉孔的縮圖遷移至最新的資料夾結構", - "nightly_tasks_cluster_faces_setting_description": "對新偵測到的臉孔執行臉孔辨識", - "nightly_tasks_cluster_new_faces_setting": "為新臉孔進行分群", - "nightly_tasks_database_cleanup_setting": "資料庫清理作業", - "nightly_tasks_database_cleanup_setting_description": "清除資料庫中舊的與已過期的資料", - "nightly_tasks_generate_memories_setting": "產生回憶", - "nightly_tasks_generate_memories_setting_description": "從媒體檔案建立新回憶", - "nightly_tasks_missing_thumbnails_setting": "產生缺少的縮圖", - "nightly_tasks_missing_thumbnails_setting_description": "將沒有縮圖的媒體檔案排入佇列以產生縮圖", - "nightly_tasks_settings": "夜間任務設定", - "nightly_tasks_settings_description": "管理夜間任務", - "nightly_tasks_start_time_setting": "開始時間", - "nightly_tasks_start_time_setting_description": "伺服器開始執行夜間任務的時間", - "nightly_tasks_sync_quota_usage_setting": "同步配額使用情況", - "nightly_tasks_sync_quota_usage_setting_description": "根據目前的使用量更新使用者的儲存配額", - "no_paths_added": "沒有已新增的路徑", - "no_pattern_added": "尚未新增排除規則", - "note_apply_storage_label_previous_assets": "提示:若要將儲存標籤套用到先前上傳的媒體檔案,請執行", - "note_cannot_be_changed_later": "注意:此設定日後無法變更!", - "notification_email_from_address": "寄件地址", - "notification_email_from_address_description": "寄件者電子郵件地址,例如:\"Immich Photo Server \"。請確保使用的是您有權限寄送郵件的地址。", - "notification_email_host_description": "電子郵件伺服器主機(例如:smtp.immich.app)", - "notification_email_ignore_certificate_errors": "忽略憑證錯誤", - "notification_email_ignore_certificate_errors_description": "忽略 TLS 憑證驗證錯誤(不建議)", - "notification_email_password_description": "用於與電子郵件伺服器驗證的密碼", - "notification_email_port_description": "電子郵件伺服器埠口(例如 25、465 或 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "使用SMTPS(基於TLS的SMTP)", - "notification_email_sent_test_email_button": "傳送測試電子郵件並儲存", - "notification_email_setting_description": "寄送電子郵件通知的設定", - "notification_email_test_email": "傳送測試電子郵件", - "notification_email_test_email_failed": "無法傳送測試電子郵件,請檢查您的設定資訊", - "notification_email_test_email_sent": "測試電子郵件已傳送至 {email}。請檢查您的收件匣。", - "notification_email_username_description": "用於與電子郵件伺服器驗證的使用者名稱", - "notification_enable_email_notifications": "啟用電子郵件通知", - "notification_settings": "通知設定", - "notification_settings_description": "管理通知設定,包括電子郵件", - "oauth_auto_launch": "自動啟動", - "oauth_auto_launch_description": "進入登入頁面時,自動啟動 OAuth 登入流程", - "oauth_auto_register": "自動註冊", - "oauth_auto_register_description": "使用 OAuth 登入後自動註冊新使用者", - "oauth_button_text": "按鈕文字", - "oauth_client_secret_description": "如果 OAuth 提供者不支援 PKCE(授權碼驗證碼交換機制),則此為必填項目", - "oauth_enable_description": "使用 OAuth 登入", - "oauth_mobile_redirect_uri": "行動端重新導向 URI", - "oauth_mobile_redirect_uri_override": "覆蓋行動端重新導向 URI", - "oauth_mobile_redirect_uri_override_description": "當 OAuth 提供者不允許使用行動端 URI(例如 ''{callback}'')時啟用", - "oauth_role_claim": "角色宣告", - "oauth_role_claim_description": "根據此宣告的存在,自動授予管理員權限。該宣告的值可以是 'user' 或 'admin'。", - "oauth_settings": "OAuth", - "oauth_settings_description": "管理 OAuth 登入設定", - "oauth_settings_more_details": "欲瞭解此功能,請參閱說明書。", - "oauth_storage_label_claim": "儲存標籤宣告", - "oauth_storage_label_claim_description": "自動將使用者的儲存標籤定為此宣告之值。", - "oauth_storage_quota_claim": "儲存配額宣告", - "oauth_storage_quota_claim_description": "自動將使用者的儲存配額定為此宣告之值。", - "oauth_storage_quota_default": "預設儲存配額(GiB)", - "oauth_storage_quota_default_description": "未提供宣告時所使用的配額(GiB)。", - "oauth_timeout": "請求逾時", - "oauth_timeout_description": "請求的逾時時間(毫秒)", - "ocr_job_description": "使用機器學習來識別圖片中的文字", - "password_enable_description": "使用電子郵件和密碼登入", - "password_settings": "密碼登入", - "password_settings_description": "管理密碼登入設定", - "paths_validated_successfully": "所有路徑驗證成功", - "person_cleanup_job": "清理人物", - "queue_details": "佇列資訊", - "queues": "任務佇列", - "queues_page_description": "管理者佇列頁面", - "quota_size_gib": "配額大小(GiB)", - "refreshing_all_libraries": "正在重新整理所有媒體庫", - "registration": "管理者註冊", - "registration_description": "由於您是系統上的第一位使用者,您將被指派為系統管理員,並負責管理相關事務,後續的其他使用者也將由您建立。", - "remove_failed_jobs": "移除失敗任務", - "require_password_change_on_login": "要求使用者在首次登入時變更密碼", - "reset_settings_to_default": "將設定重設為預設值", - "reset_settings_to_recent_saved": "將設定重設為最近儲存的設定", - "scanning_library": "掃描媒體庫", - "search_jobs": "搜尋任務…", - "send_welcome_email": "傳送歡迎電子郵件", - "server_external_domain_settings": "外部網域", - "server_external_domain_settings_description": "公開分享連結的網域,包含 http(s)://", - "server_public_users": "公開使用者", - "server_public_users_description": "在將使用者新增到共享相簿時,會列出所有使用者的姓名與電子郵件。停用此功能後,使用者清單將僅供系統管理員檢視。", - "server_settings": "伺服器設定", - "server_settings_description": "管理伺服器設定", - "server_stats_page_description": "管理服務器統計頁面", - "server_welcome_message": "歡迎訊息", - "server_welcome_message_description": "在登入頁面顯示的訊息。", - "settings_page_description": "管理設定頁面", - "sidecar_job": "側接檔案中繼資料", - "sidecar_job_description": "從檔案系統偵測或同步側接檔案中繼資料", - "slideshow_duration_description": "每張圖片放映的秒數", - "smart_search_job_description": "執行機器學習有助於智慧搜尋", - "storage_template_date_time_description": "檔案的建立時間戳會用於日期與時間資訊", - "storage_template_date_time_sample": "取樣時間 {date}", - "storage_template_enable_description": "啟用儲存範本引擎", - "storage_template_hash_verification_enabled": "雜湊函式驗證已啟用", - "storage_template_hash_verification_enabled_description": "啟用雜湊函式驗證,除非您很清楚地知道這個選項的作用,否則請勿停用此功能", - "storage_template_migration": "儲存範本遷移", - "storage_template_migration_description": "將目前的 {template} 套用到先前上傳的項目", - "storage_template_migration_info": "儲存範本會將所有副檔名轉換為小寫。範本變更只會套用到新的項目。若要將範本追溯套用到先前上傳的項目,請執行 {job}。", - "storage_template_migration_job": "儲存範本遷移任務", - "storage_template_more_details": "關於此功能的更多詳細資訊,請參閱儲存範本及其影響", - "storage_template_onboarding_description_v2": "啟用後,此功能會依使用者自訂的範本自動整理檔案。更多資訊請參閱說明文件。", - "storage_template_path_length": "預估路徑長度上限:{length, number}/{limit, number}", - "storage_template_settings": "儲存範本", - "storage_template_settings_description": "管理上傳檔案的資料夾結構和檔名", - "storage_template_user_label": "{label} 是使用者的儲存標籤", - "system_settings": "系統設定", - "tag_cleanup_job": "清理標籤", - "template_email_available_tags": "您可以在您的範本中使用以下變數:{tags}", - "template_email_if_empty": "如果範本為空,將使用預設電子郵件範本。", - "template_email_invite_album": "相簿邀請範本", - "template_email_preview": "預覽", - "template_email_settings": "電子郵件範本", - "template_email_update_album": "相簿更新範本", - "template_email_welcome": "歡迎郵件範本", - "template_settings": "通知範本", - "template_settings_description": "管理通知的自訂範本", - "theme_custom_css_settings": "自訂 CSS", - "theme_custom_css_settings_description": "可以用層疊樣式表(CSS)來自訂 Immich 的設計。", - "theme_settings": "主題設定", - "theme_settings_description": "自訂 Immich 的網頁介面", - "thumbnail_generation_job": "產生縮圖", - "thumbnail_generation_job_description": "為每個檔案產生大、小及模糊縮圖,也為每位人物產生縮圖", - "transcoding_acceleration_api": "加速 API", - "transcoding_acceleration_api_description": "此 API 會使用您的硬體以加速轉碼流程。此設定採「盡力而為」模式——若轉碼失敗,將會回退至軟體轉碼。VP9 是否能運作,取決於您的硬體設定。", - "transcoding_acceleration_nvenc": "NVENC(需要 NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync(需要第 7 代或更新的 Intel 處理器)", - "transcoding_acceleration_rkmpp": "RKMPP(僅適用於 Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "可接受的音訊編解碼器", - "transcoding_accepted_audio_codecs_description": "選擇哪些音訊編解碼器不需要轉碼。此設定僅適用於特定的轉碼策略。", - "transcoding_accepted_containers": "可接受的封裝格式", - "transcoding_accepted_containers_description": "選擇哪些封裝格式不需要重新封裝(remux)為 MP4。此設定僅適用於特定的轉碼策略。", - "transcoding_accepted_video_codecs": "接受的影片編解碼器", - "transcoding_accepted_video_codecs_description": "選擇哪些視訊編解碼器不需要轉碼。此設定僅適用於特定的轉碼策略。", - "transcoding_advanced_options_description": "大多數使用者不需更動的選項", - "transcoding_audio_codec": "音訊編解碼器", - "transcoding_audio_codec_description": "是音質最佳的選項,但與舊裝置或舊版軟體的相容性較低。", - "transcoding_bitrate_description": "位元率高於最大值或格式不在可接受範圍的影片", - "transcoding_codecs_learn_more": "如需進一步了解此處使用的術語,請參閱 FFmpeg 文件中關於 H.264 編解碼器HEVC 編解碼器VP9 編解碼器 的說明。", - "transcoding_constant_quality_mode": "恆定品質模式", - "transcoding_constant_quality_mode_description": "ICQ 的效果優於 CQP,但部分硬體加速裝置不支援此模式。設定此選項時,在使用以品質為基準的編碼時會優先採用所指定的模式。NVENC 不支援 ICQ,因此此設定在 NVENC 下會被忽略。", - "transcoding_constant_rate_factor": "恆定速率因子(-crf)", - "transcoding_constant_rate_factor_description": "視訊品質等級。典型值為 H.264 的 23、HEVC 的 28、VP9 的 31 和 AV1 的 35。數值越低,品質越好,但會產生較大的檔案。", - "transcoding_disabled_description": "不對任何影片進行轉碼,可能會導致部分用戶端無法正常播放", - "transcoding_encoding_options": "編碼選項", - "transcoding_encoding_options_description": "設定編碼影片的編解碼器、解析度、品質和其他選項", - "transcoding_hardware_acceleration": "硬體加速", - "transcoding_hardware_acceleration_description": "實驗性功能:可加快轉碼速度,但在相同位元率下可能降低品質", - "transcoding_hardware_decoding": "硬體解碼", - "transcoding_hardware_decoding_setting_description": "啟用端到端加速,而非僅加速編碼。此功能可能不適用於所有影片。", - "transcoding_max_b_frames": "最大 B 幀數", - "transcoding_max_b_frames_description": "較高的數值可提升壓縮效率,但會降低編碼速度。在較舊的裝置上,可能與硬體加速不相容。0 代表停用 B 幀,而 -1 則會自動設定此數值。", - "transcoding_max_bitrate": "最大位元速率", - "transcoding_max_bitrate_description": "設定最大位元率可以在輕微犧牲品質的情況下,讓檔案大小更容易預測。在 720p 解析度下,VP9 或 HEVC 的典型值為 2600 kbit/s,H.264 則為 4500 kbit/s。設為 0 則停用此功能。當沒有指定組織時,假設k(代表kbit/s); 囙此,5000、5000k和5M(Mbit/s)是等效的。", - "transcoding_max_keyframe_interval": "最大關鍵幀間隔", - "transcoding_max_keyframe_interval_description": "設定關鍵幀之間的最大幀距。較低的值會降低壓縮效率,但可以改善搜尋時間,並有可能會改善快速變動場景的品質。0 會自動設定此值。", - "transcoding_optimal_description": "高於目標解析度或格式不在可接受範圍的影片", - "transcoding_policy": "轉碼策略", - "transcoding_policy_description": "設定影片進行轉碼的條件", - "transcoding_preferred_hardware_device": "首選硬體裝置", - "transcoding_preferred_hardware_device_description": "僅適用於 VAAPI 和 QSV。設定用於硬體轉碼的 dri 節點。", - "transcoding_preset_preset": "預設值(-preset)", - "transcoding_preset_preset_description": "壓縮速度。較慢的預設值會產生較小的檔案,並在鎖定位元率時提升品質。VP9 在速度高於「faster」時將忽略設定。", - "transcoding_reference_frames": "參考幀", - "transcoding_reference_frames_description": "在壓縮特定幀時所參考的幀數量。數值越高可提升壓縮效率,但會降低編碼速度。設為 0 則自動決定此數值。", - "transcoding_required_description": "僅限格式不被接受的影片", - "transcoding_settings": "影片轉碼設定", - "transcoding_settings_description": "管理要轉碼的影片及其處理方式", - "transcoding_target_resolution": "目標解析度", - "transcoding_target_resolution_description": "較高的解析度可以保留更多細節,但編碼時間較長,檔案也較大,且可能降低應用程式的回應速度。", - "transcoding_temporal_aq": "時間自適應量化(Temporal AQ)", - "transcoding_temporal_aq_description": "僅適用於 NVENC,時域自我調整量化可提升高細節、低動態場景的畫質。可能與較舊的裝置不相容。", - "transcoding_threads": "執行緒數量", - "transcoding_threads_description": "較高的值會加快編碼速度,但會減少伺服器在執行過程中處理其他任務的空間。此值不應超過 CPU 核心數。設定為 0 可以最大化利用率。", - "transcoding_tone_mapping": "色調對映", - "transcoding_tone_mapping_description": "在將 HDR 影片轉換為 SDR 時,盡量維持原始觀感。每種演算法在色彩、細節和亮度方面都有不同的權衡。Hable 保留細節,Mobius 保留色彩,Reinhard 保留亮度。", - "transcoding_transcode_policy": "轉碼策略", - "transcoding_transcode_policy_description": "影片何時應進行轉碼的策略。HDR 影片一定會轉碼(除非停用轉碼)。", - "transcoding_two_pass_encoding": "兩階段編碼", - "transcoding_two_pass_encoding_setting_description": "使用兩階段編碼來產生品質更佳的編碼影片。當啟用最大位元速率時(H.264 和 HEVC 必須啟用此選項才能運作),此模式會以最大位元速率來調整位元速率範圍,並忽略 CRF。對於 VP9,如果停用最大位元速率,可以使用 CRF。", - "transcoding_video_codec": "影片編解碼器", - "transcoding_video_codec_description": "VP9 具有高壓縮效率與良好的網頁相容性,但轉碼速度較慢。HEVC 的效能類似,但網頁相容性較差。H.264 具備廣泛的相容性且轉碼速度快,但產生的檔案體積較大。AV1 是效率最高的編解碼器,但在舊裝置上缺乏支援。", - "trash_enabled_description": "啟用垃圾桶功能", - "trash_number_of_days": "天數", - "trash_number_of_days_description": "媒體在垃圾桶中保留的天數,逾期後將永久刪除", - "trash_settings": "垃圾桶設定", - "trash_settings_description": "管理垃圾桶設定", - "unlink_all_oauth_accounts": "解除所有 OAuth 帳號的連結", - "unlink_all_oauth_accounts_description": "在遷移至新的服務提供者前,請不要忘記要先解除所有與 OAuth 帳戶的連結。", - "unlink_all_oauth_accounts_prompt": "您是否確認要解除所有與 OAuth 帳戶的連結?所有相關的使用者身份會被重設,並且不能被還原。", - "user_cleanup_job": "清理使用者", - "user_delete_delay": "{user} 的帳號和項目會在 {delay, plural, one {# 天} other {# 天}} 後永久刪除。", - "user_delete_delay_settings": "延後刪除", - "user_delete_delay_settings_description": "自移除後起算的天數,逾期後將永久刪除使用者帳號與媒體。使用者刪除作業會在每日午夜執行,以檢查符合刪除條件的帳號。此設定的變更會在下一次執行時生效。", - "user_delete_immediately": "{user} 的帳號與媒體將立即排入永久刪除的佇列。", - "user_delete_immediately_checkbox": "立即將使用者與資產排入永久刪除佇列", - "user_details": "使用者詳細資訊", - "user_management": "使用者管理", - "user_password_has_been_reset": "使用者密碼已重設:", - "user_password_reset_description": "請將臨時密碼提供給該使用者,並告知他們在下次登入時需要變更密碼。", - "user_restore_description": "{user} 的帳號將被還原。", - "user_restore_scheduled_removal": "還原使用者 - 預定於 {date, date, long} 移除", - "user_settings": "使用者設定", - "user_settings_description": "管理使用者設定", - "user_successfully_removed": "用戶{email}已成功删除。", - "users_page_description": "管理用戶頁面", - "version_check_enabled_description": "啟用版本檢查", - "version_check_implications": "版本檢查功能會定期與 github.com 通訊", - "version_check_settings": "版本檢查", - "version_check_settings_description": "啟用 / 停用新版本通知", - "video_conversion_job": "影片轉碼", - "video_conversion_job_description": "轉碼影片以提升與瀏覽器及裝置的相容性" - }, - "admin_email": "管理員電子郵件", - "admin_password": "管理員密碼", - "administration": "管理", - "advanced": "進階", - "advanced_settings_clear_image_cache": "清除圖片快取", - "advanced_settings_clear_image_cache_error": "清除圖片快取失敗", - "advanced_settings_clear_image_cache_success": "成功清除{size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "使用此選項可在同步時依其他條件篩選媒體。僅在應用程式無法偵測到所有相簿時再嘗試使用。", - "advanced_settings_enable_alternate_media_filter_title": "[實驗性] 使用替代的裝置相簿同步篩選器", - "advanced_settings_log_level_title": "日誌等級:{level}", - "advanced_settings_prefer_remote_subtitle": "部分裝置從本機媒體庫載入縮圖的速度非常慢。啟用此設定可改為載入遠端圖片。", - "advanced_settings_prefer_remote_title": "偏好遠端影像", - "advanced_settings_proxy_headers_subtitle": "定義 Immich 在每次網路請求時應該傳送的代理標頭", - "advanced_settings_proxy_headers_title": "自定義代理標頭[實驗性]", - "advanced_settings_readonly_mode_subtitle": "開啟唯讀模式後,照片只能瀏覽,像是多選影像、分享、投放、刪除等功能都會關閉。可在主畫面透過使用者頭像來開啟/關閉唯讀模式", - "advanced_settings_readonly_mode_title": "唯讀模式", - "advanced_settings_self_signed_ssl_subtitle": "略過伺服器端點的 SSL 憑證驗證。自簽憑證時必須啟用此設定。", - "advanced_settings_self_signed_ssl_title": "允許自簽的 SSL 憑證[實驗性]", - "advanced_settings_sync_remote_deletions_subtitle": "當在網頁端執行刪除或還原操作時,自動在此裝置上刪除或還原該媒體", - "advanced_settings_sync_remote_deletions_title": "同步遠端刪除 [實驗性]", - "advanced_settings_tile_subtitle": "進階使用者設定", - "advanced_settings_troubleshooting_subtitle": "啟用額外功能以進行疑難排解", - "advanced_settings_troubleshooting_title": "疑難排解", - "age_months": "{months, plural, one {# 個月} other {# 個月}}", - "age_year_months": "1 歲,{months, plural, one {# 個月} other {# 個月}}", - "age_years": "{years, plural, other {# 歲}}", - "album": "相簿", - "album_added": "被加入到相簿", - "album_added_notification_setting_description": "當我被加入共享相簿時,用電子郵件通知我", - "album_cover_updated": "已更新相簿封面", - "album_delete_confirmation": "你確定要刪除相簿 {album} 嗎?", - "album_delete_confirmation_description": "如果此相簿已被分享,其他使用者將無法再存取。", - "album_deleted": "相簿已刪除", - "album_info_card_backup_album_excluded": "已排除", - "album_info_card_backup_album_included": "已選中", - "album_info_updated": "已更新相簿資訊", - "album_leave": "離開相簿?", - "album_leave_confirmation": "您確定要離開 {album} 嗎?", - "album_name": "相簿名稱", - "album_options": "相簿選項", - "album_remove_user": "移除使用者?", - "album_remove_user_confirmation": "確定要移除 {user} 嗎?", - "album_search_not_found": "找不到符合搜尋條件的相簿", - "album_selected": "已選擇相册", - "album_share_no_users": "看來您與所有使用者共享了這本相簿,或沒有其他使用者可供分享。", - "album_summary": "相簿摘要", - "album_updated": "更新相簿時", - "album_updated_setting_description": "當共享相簿有新項目時用電子郵件通知我", - "album_upload_assets": "從您的計算機上傳文件並添加到相冊", - "album_user_left": "離開 {album}", - "album_user_removed": "移除 {user}", - "album_viewer_appbar_delete_confirm": "您確定要從帳號中刪除此相簿嗎?", - "album_viewer_appbar_share_err_delete": "刪除相簿失敗", - "album_viewer_appbar_share_err_leave": "離開相簿失敗", - "album_viewer_appbar_share_err_remove": "從相簿中移除項目時出現錯誤", - "album_viewer_appbar_share_err_title": "編輯相簿標題失敗", - "album_viewer_appbar_share_leave": "離開相簿", - "album_viewer_appbar_share_to": "分享給", - "album_viewer_page_share_add_users": "邀請其他人", - "album_with_link_access": "任何擁有連結的人都能檢視此相簿中的照片與人物。", - "albums": "相簿", - "albums_count": "{count, plural, one {{count, number} 個相簿} other {{count, number} 個相簿}}", - "albums_default_sort_order": "預設相簿排序", - "albums_default_sort_order_description": "建立新相簿時要初始化項目排序方式。", - "albums_feature_description": "一系列可以分享給其他使用者的項目。", - "albums_on_device_count": "此裝置有 ({count}) 個相簿", - "albums_selected": "{count, plural, one {# 個已選擇專輯} other {# 個已選擇專輯}}", - "all": "全部", - "all_albums": "所有相簿", - "all_people": "所有人物", - "all_photos": "所有照片", - "all_videos": "所有影片", - "allow_dark_mode": "允許深色模式", - "allow_edits": "允許編輯", - "allow_public_user_to_download": "允許公開使用者下載", - "allow_public_user_to_upload": "允許公開使用者上傳", - "allowed": "允許", - "alt_text_qr_code": "QR code 圖片", - "always_keep": "一律保留", - "always_keep_photos_hint": "所有的照片將會被保留在此裝置上。", - "always_keep_videos_hint": "所有的影片將會被保留在此裝置上。", - "anti_clockwise": "逆時針", - "api_key": "API 金鑰", - "api_key_description": "此金鑰僅顯示一次。請在關閉前複製它。", - "api_key_empty": "您的 API 金鑰名稱不能為空值", - "api_keys": "API 金鑰", - "app_architecture_variant": "變體(架構)", - "app_bar_signout_dialog_content": "您確定要登出嗎?", - "app_bar_signout_dialog_ok": "是", - "app_bar_signout_dialog_title": "登出", - "app_download_links": "應用下載連結", - "app_settings": "應用程式設定", - "app_stores": "應用商店", - "app_update_available": "應用程序更新可用", - "appears_in": "出現於", - "apply_count": "應用 ({count, number})", - "archive": "封存", - "archive_action_prompt": "已將 ({count}) 個加入進封存", - "archive_or_unarchive_photo": "封存或取消封存照片", - "archive_page_no_archived_assets": "未找到封存媒體", - "archive_page_title": "封存 ({count})", - "archive_size": "封存大小", - "archive_size_description": "設定要下載的封存檔案大小 (單位:GiB)", - "archived": "已封存", - "archived_count": "{count, plural, other {已封存 # 個項目}}", - "are_these_the_same_person": "同一位人物?", - "are_you_sure_to_do_this": "您確定嗎?", - "array_field_not_fully_supported": "數組欄位需要手動JSON編輯", - "asset_action_delete_err_read_only": "略過無法刪除唯讀項目", - "asset_action_share_err_offline": "略過無法取得的離線項目", - "asset_added_to_album": "已建立相簿", - "asset_adding_to_album": "新增到相簿…", - "asset_created": "資產已創建", - "asset_description_updated": "媒體描述已更新", - "asset_filename_is_offline": "媒體 {filename} 已離線", - "asset_has_unassigned_faces": "媒體有未分配的臉孔", - "asset_hashing": "正在計算雜湊…", - "asset_list_group_by_sub_title": "分類方式", - "asset_list_layout_settings_dynamic_layout_title": "動態版面", - "asset_list_layout_settings_group_automatically": "自動", - "asset_list_layout_settings_group_by": "媒體分類方式", - "asset_list_layout_settings_group_by_month_day": "月份和日期", - "asset_list_layout_sub_title": "版面", - "asset_list_settings_subtitle": "相片格狀版面設定", - "asset_list_settings_title": "相片格狀檢視", - "asset_not_found_on_device_android": "無法在裝置上找到項目", - "asset_not_found_on_device_ios": "無法在裝置上找到項目。iCloud 上的項目可能因檔案損失無法查閱", - "asset_not_found_on_icloud": "項目不存在於在iCloud。項目有機會因檔案損毀而無法檢閱", - "asset_offline": "媒體離線", - "asset_offline_description": "此外部媒體已無法在磁碟中找到。請聯絡您的 Immich 管理員以取得協助。", - "asset_restored_successfully": "媒體復原成功", - "asset_skipped": "已跳過", - "asset_skipped_in_trash": "已在垃圾桶", - "asset_trashed": "資產被丟棄", - "asset_troubleshoot": "資產故障排除", - "asset_uploaded": "已上傳", - "asset_uploading": "上傳中…", - "asset_viewer_settings_subtitle": "管理您的媒體庫檢視器設定", - "asset_viewer_settings_title": "媒體檢視器", - "assets": "媒體", - "assets_added_count": "已新增 {count, plural, one {# 個媒體} other {# 個媒體}}", - "assets_added_to_album_count": "已將 {count, plural, one {# 個媒體} other {# 個媒體}}加入相簿", - "assets_added_to_albums_count": "已新增 {assetTotal, plural, one {# 個} other {# 個}}項目到 {albumTotal, plural, one {# 個} other {# 個}}相簿中", - "assets_cannot_be_added_to_album_count": "無法將 {count, plural, one {媒體} other {媒體}} 加入至相簿", - "assets_cannot_be_added_to_albums": "{count, plural, one {個} other {個}}項目無法被加入相簿", - "assets_count": "{count, plural, one {# 個媒體} other {# 個媒體}}", - "assets_deleted_permanently": "{count} 個媒體已被永久刪除", - "assets_deleted_permanently_from_server": "已從 Immich 伺服器中永久移除 {count} 個媒體", - "assets_downloaded_failed": "{count, plural, one {已下載 # 個檔案 - {error} 個檔案失敗} other {已下載 # 個檔案 - {error} 個檔案失敗}}", - "assets_downloaded_successfully": "{count, plural, one {已成功下載 # 個檔案} other {已成功下載 # 個檔案}}", - "assets_moved_to_trash_count": "已將 {count, plural, one {# 個媒體} other {# 個媒體}}移動進垃圾桶", - "assets_permanently_deleted_count": "已永久刪除 {count, plural, one {# 個媒體} other {# 個媒體}}", - "assets_removed_count": "已移除 {count, plural, one {# 個媒體} other {# 個媒體}}", - "assets_removed_permanently_from_device": "已從您的裝置永久移除 {count} 個媒體", - "assets_restore_confirmation": "您確定要還原所有垃圾桶中的媒體嗎?此操作無法復原!請注意,任何離線媒體都無法透過此方式還原。", - "assets_restored_count": "已還原 {count, plural, one {# 個媒體} other {# 個媒體}}", - "assets_restored_successfully": "已成功還原 {count} 個媒體", - "assets_trashed": "已將 {count} 個媒體移至垃圾桶", - "assets_trashed_count": "已將 {count, plural, one {# 個媒體} other {# 個媒體}} 移至垃圾桶", - "assets_trashed_from_server": "已從 Immich 伺服器將 {count} 個媒體移至垃圾桶", - "assets_were_part_of_album_count": "{count, plural, one {該媒體已} other {這些媒體已}}在相簿中", - "assets_were_part_of_albums_count": "{count, plural, one {個} other {個}}項目已被儲存在相簿中", - "authorized_devices": "已授權裝置", - "automatic_endpoint_switching_subtitle": "當可用時,透過指定的 Wi-Fi 在本機連線,其他情況則使用替代連線", - "automatic_endpoint_switching_title": "自動 URL 切換", - "autoplay_slideshow": "自動播放幻燈片", - "back": "上一頁", - "back_close_deselect": "回上一頁、關閉並取消選取", - "background_backup_running_error": "後臺備份目前正在執行,無法啟動手動備份", - "background_location_permission": "背景存取位置權限", - "background_location_permission_content": "為了在背景執行時切換網路,Immich 必須始終具有精確位置存取權限,才能讀取 Wi-Fi 網路名稱", - "background_options": "背景選項", - "backup": "備份", - "backup_album_selection_page_albums_device": "裝置上的相簿({count})", - "backup_album_selection_page_albums_tap": "點一下以選取,點兩下以排除", - "backup_album_selection_page_assets_scatter": "媒體可以分散在多個相簿中,因此在備份過程中可以選擇納入或排除相簿。", - "backup_album_selection_page_select_albums": "選取相簿", - "backup_album_selection_page_selection_info": "選取資訊", - "backup_album_selection_page_total_assets": "總不重複媒體數", - "backup_albums_sync": "備份相簿同步", - "backup_all": "全部", - "backup_background_service_backup_failed_message": "備份媒體失敗。正在重試…", - "backup_background_service_complete_notification": "資產備份完成", - "backup_background_service_connection_failed_message": "連線至伺服器失敗。正在重試…", - "backup_background_service_current_upload_notification": "正在上傳 {filename}", - "backup_background_service_default_notification": "正在檢查新媒體…", - "backup_background_service_error_title": "備份錯誤", - "backup_background_service_in_progress_notification": "正在備份您的媒體…", - "backup_background_service_upload_failure_notification": "{filename} 上傳失敗", - "backup_controller_page_albums": "備份相簿", - "backup_controller_page_background_app_refresh_disabled_content": "請在「設定」>「一般」>「背景 App 重新整理」中啟用,以使用背景備份功能。", - "backup_controller_page_background_app_refresh_disabled_title": "背景 App 重新整理已停用", - "backup_controller_page_background_app_refresh_enable_button_text": "前往設定", - "backup_controller_page_background_battery_info_link": "告訴我怎麼做", - "backup_controller_page_background_battery_info_message": "為獲得最佳的背景備份體驗,請停用任何會限制 Immich 背景活動的電池最佳化設定。\n\n由於這與裝置型號相關,請查詢您裝置製造商的相關說明。", - "backup_controller_page_background_battery_info_ok": "我知道了", - "backup_controller_page_background_battery_info_title": "電池最佳化", - "backup_controller_page_background_charging": "僅在充電時", - "backup_controller_page_background_configure_error": "背景服務設定失敗", - "backup_controller_page_background_delay": "新媒體備份延遲:{duration}", - "backup_controller_page_background_description": "開啟背景服務,即可在不需開啟 App 的情況下,自動備份所有新媒體", - "backup_controller_page_background_is_off": "背景自動備份已關閉", - "backup_controller_page_background_is_on": "背景自動備份已開啟", - "backup_controller_page_background_turn_off": "關閉背景服務", - "backup_controller_page_background_turn_on": "開啟背景服務", - "backup_controller_page_background_wifi": "僅使用 Wi-Fi", - "backup_controller_page_backup": "備份", - "backup_controller_page_backup_selected": "已選中: ", - "backup_controller_page_backup_sub": "已備份的照片和影片", - "backup_controller_page_created": "建立時間:{date}", - "backup_controller_page_desc_backup": "開啟前臺備份,在開啟 App 時自動將新媒體上傳至伺服器。", - "backup_controller_page_excluded": "已排除: ", - "backup_controller_page_failed": "失敗({count})", - "backup_controller_page_filename": "檔案名稱:{filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "備份資訊", - "backup_controller_page_none_selected": "未選取任何項目", - "backup_controller_page_remainder": "剩餘", - "backup_controller_page_remainder_sub": "選取項目中尚未備份的照片與影片", - "backup_controller_page_server_storage": "伺服器儲存空間", - "backup_controller_page_start_backup": "開始備份", - "backup_controller_page_status_off": "前臺自動備份已關閉", - "backup_controller_page_status_on": "前臺自動備份已開啟", - "backup_controller_page_storage_format": "{used} / {total} 已使用", - "backup_controller_page_to_backup": "要備份的相簿", - "backup_controller_page_total_sub": "已選取相簿中的所有不重複的照片與影片", - "backup_controller_page_turn_off": "關閉前臺備份", - "backup_controller_page_turn_on": "開啟前臺備份", - "backup_controller_page_uploading_file_info": "上傳中的檔案資訊", - "backup_err_only_album": "不能移除唯一的相簿", - "backup_error_sync_failed": "同步失敗,無法處理備份。", - "backup_info_card_assets": "個媒體", - "backup_manual_cancelled": "已取消", - "backup_manual_in_progress": "上傳正在進行中,請稍後再試", - "backup_manual_success": "成功", - "backup_manual_title": "上傳狀態", - "backup_options": "備份選項", - "backup_options_page_title": "備份選項", - "backup_setting_subtitle": "管理背景與前臺上傳設定", - "backup_settings_subtitle": "管理上傳設定", - "backup_upload_details_page_more_details": "點擊查看更多詳細資訊", - "backward": "由舊至新", - "biometric_auth_enabled": "生物辨識驗證已啟用", - "biometric_locked_out": "您已被鎖定無法使用生物辨識驗證", - "biometric_no_options": "沒有生物辨識選項可用", - "biometric_not_available": "此裝置上無法使用生物辨識驗證", - "birthdate_saved": "出生日期儲存成功", - "birthdate_set_description": "出生日期用於計算此人在照片拍攝時的年齡。", - "blurred_background": "背景模糊", - "bugs_and_feature_requests": "錯誤及功能請求", - "build": "建置編號", - "build_image": "建置映像", - "bulk_delete_duplicates_confirmation": "您確定要批次刪除 {count, plural, one {# 個重複媒體} other {# 個重複媒體}} 嗎?系統將保留每組中大小最大的媒體,並永久刪除所有其他重複項目。此操作無法復原!", - "bulk_keep_duplicates_confirmation": "您確定要保留 {count, plural, one {# 個重複媒體} other {# 個重複媒體}} 嗎?這將在不刪除任何項目的情況下解決所有重複群組。", - "bulk_trash_duplicates_confirmation": "您確定要批次將 {count, plural, one {# 個重複媒體} other {# 個重複媒體}}移至垃圾桶嗎?系統將保留每組中大小最大的媒體,並將所有其他重複項目移至垃圾桶。", - "buy": "購買 Immich", - "cache_settings_clear_cache_button": "清除快取", - "cache_settings_clear_cache_button_title": "清除 App 的快取。此動作會在快取重新建立前,顯著影響 App 的效能。", - "cache_settings_duplicated_assets_clear_button": "清除", - "cache_settings_duplicated_assets_subtitle": "被應用程式加入忽略清單的照片與影片", - "cache_settings_duplicated_assets_title": "重複項目({count})", - "cache_settings_statistics_album": "媒體庫縮圖", - "cache_settings_statistics_full": "完整圖片", - "cache_settings_statistics_shared": "共享相簿縮圖", - "cache_settings_statistics_thumbnail": "縮圖", - "cache_settings_statistics_title": "快取使用情況", - "cache_settings_subtitle": "控制 Immich 行動應用程式的快取行為", - "cache_settings_tile_subtitle": "設定本機儲存行為", - "cache_settings_tile_title": "本機儲存空間", - "cache_settings_title": "快取設定", - "camera": "相機", - "camera_brand": "相機品牌", - "camera_model": "相機型號", - "cancel": "取消", - "cancel_search": "取消搜尋", - "canceled": "已取消", - "canceling": "取消中", - "cannot_merge_people": "無法合併人物", - "cannot_undo_this_action": "此操作無法復原!", - "cannot_update_the_description": "無法更新描述", - "cast": "投放", - "cast_description": "設定可用的投放裝置", - "change_date": "變更日期", - "change_description": "變更描述", - "change_display_order": "變更顯示順序", - "change_expiration_time": "變更到期時間", - "change_location": "變更位置", - "change_name": "變更名稱", - "change_name_successfully": "變更名稱成功", - "change_password": "變更密碼", - "change_password_description": "這是您首次登入系統,或是已收到變更密碼的請求。請在下方輸入新密碼。", - "change_password_form_confirm_password": "確認密碼", - "change_password_form_description": "您好 {name},\n\n這是您首次登入系統,或是已收到變更密碼的請求。請在下方輸入新密碼。", - "change_password_form_log_out": "註銷所有其他設備", - "change_password_form_log_out_description": "建議退出所有其他設備", - "change_password_form_new_password": "新密碼", - "change_password_form_password_mismatch": "密碼不一致", - "change_password_form_reenter_new_password": "再次輸入新密碼", - "change_pin_code": "變更 PIN 碼", - "change_trigger": "更改觸發器", - "change_trigger_prompt": "您確定要更改觸發器嗎? 這將删除所有現有操作和篩選器。", - "change_your_password": "變更您的密碼", - "changed_visibility_successfully": "已成功變更可見性", - "charging": "充電", - "charging_requirement_mobile_backup": "後臺備份要求裝置正在充電", - "check_corrupt_asset_backup": "檢查損毀的備份項目", - "check_corrupt_asset_backup_button": "執行檢查", - "check_corrupt_asset_backup_description": "僅在已連線至 Wi-Fi 且所有媒體已完成備份後執行此檢查。此程式可能需要數分鐘。", - "check_logs": "檢查日誌", - "checksum": "校驗和", - "choose_matching_people_to_merge": "選擇要合併的相符人物", - "city": "城市", - "cleanup_confirm_description": "Immich 發現 {count} 個項目(在 {date} 之前創建)已安全備份到服務器。是否從此設備中刪除本地副本?", - "cleanup_confirm_prompt_title": "從此裝置刪除?", - "cleanup_deleted_assets": "已將{count}項目移到裝置的垃圾桶裡", - "cleanup_deleting": "正在移動到垃圾桶...", - "cleanup_found_assets": "找到{count}件已上傳的項目", - "cleanup_found_assets_with_size": "找到{count}件,總共({size})已上傳的項目", - "cleanup_icloud_shared_albums_excluded": "iCloud共享相簿被排除於搜尋之外", - "cleanup_no_assets_found": "未找到任何符合條件的項目。釋放內存功能只能移除已備份到伺服器的項目", - "cleanup_preview_title": "{count} 項需要移除的項目", - "cleanup_step3_description": "掃描符合日期和保存設定的已備份項目。", - "cleanup_step4_summary": "從這台裝置上移除{count}件創建於{date}前的項目。照片仍然可以在Immich上查看。", - "cleanup_trash_hint": "要完全恢復內存,請清空相簿中的垃圾桶", - "clear": "清空", - "clear_all": "全部清除", - "clear_all_recent_searches": "清除所有最近的搜尋", - "clear_file_cache": "清除檔案快取", - "clear_message": "清除訊息", - "clear_value": "清除值", - "client_cert_dialog_msg_confirm": "確定", - "client_cert_enter_password": "輸入密碼", - "client_cert_import": "匯入", - "client_cert_import_success_msg": "已匯入用戶端憑證", - "client_cert_invalid_msg": "無效的憑證檔案或密碼錯誤", - "client_cert_remove_msg": "用戶端憑證已移除", - "client_cert_subtitle": "僅支援 PKCS12 (.p12, .pfx) 格式。僅可在登入前進行憑證的匯入和移除", - "client_cert_title": "SSL 用戶端憑證[實驗性]", - "clockwise": "順時針", - "close": "關閉", - "collapse": "折疊", - "collapse_all": "全部折疊", - "color": "顏色", - "color_theme": "色彩主題", - "command": "命令", - "comment_deleted": "留言已刪除", - "comment_options": "留言選項", - "comments_and_likes": "留言與喜歡", - "comments_are_disabled": "留言已停用", - "common_create_new_album": "建立新相簿", - "completed": "已完成", - "confirm": "確認", - "confirm_admin_password": "確認管理員密碼", - "confirm_delete_face": "您確定要從該媒體中刪除{name}的臉孔嗎?", - "confirm_delete_shared_link": "您確定要刪除這個共享連結嗎?", - "confirm_keep_this_delete_others": "除此媒體外,堆疊中的其他媒體都將被刪除。您確定要繼續嗎?", - "confirm_new_pin_code": "確認新 PIN 碼", - "confirm_password": "確認密碼", - "confirm_tag_face": "您想要將此臉孔標籤為 {name} 嗎?", - "confirm_tag_face_unnamed": "您想標籤這張臉嗎?", - "connected_device": "已連線裝置", - "connected_to": "已連線至", - "contain": "等比內縮", - "context": "脈絡", - "continue": "繼續", - "control_bottom_app_bar_create_new_album": "建立新相簿", - "control_bottom_app_bar_delete_from_immich": "從 Immich 伺服器中刪除", - "control_bottom_app_bar_delete_from_local": "從裝置中刪除", - "control_bottom_app_bar_edit_location": "編輯位置資訊", - "control_bottom_app_bar_edit_time": "編輯日期與時間", - "control_bottom_app_bar_share_link": "分享連結", - "control_bottom_app_bar_share_to": "分享給", - "control_bottom_app_bar_trash_from_immich": "移入垃圾桶", - "copied_image_to_clipboard": "已將圖片複製到剪貼簿。", - "copied_to_clipboard": "已複製到剪貼簿!", - "copy_error": "複製錯誤", - "copy_file_path": "複製檔案路徑", - "copy_image": "複製圖片", - "copy_link": "複製連結", - "copy_link_to_clipboard": "複製連結到剪貼簿", - "copy_password": "複製密碼", - "copy_to_clipboard": "複製到剪貼簿", - "country": "國家", - "cover": "封面", - "covers": "封面", - "create": "建立", - "create_album": "建立相簿", - "create_album_page_untitled": "未命名", - "create_api_key": "創建API金鑰", - "create_first_workflow": "創建第一個工作流", - "create_library": "建立媒體庫", - "create_link": "建立連結", - "create_link_to_share": "建立共享連結", - "create_link_to_share_description": "任何持有連結的人都允許檢視所選相片", - "create_new": "新增", - "create_new_person": "建立新人物", - "create_new_person_hint": "將選定的媒體分配給新人物", - "create_new_user": "建立新使用者", - "create_shared_album_page_share_add_assets": "新增膜體", - "create_shared_album_page_share_select_photos": "選擇照片", - "create_shared_link": "建立共享連結", - "create_tag": "建立標籤", - "create_tag_description": "建立新標籤。若要建立巢狀標籤,請輸入包含正斜線的完整標籤路徑。", - "create_user": "建立使用者", - "create_workflow": "創建工作流", - "created": "建立於", - "created_at": "建立於", - "creating_linked_albums": "建立連結相簿 ...", - "crop": "裁剪", - "crop_aspect_ratio_fixed": "已修復", - "crop_aspect_ratio_free": "無限制", - "crop_aspect_ratio_original": "原檔", - "curated_object_page_title": "事物", - "current_device": "目前裝置", - "current_pin_code": "目前 PIN 碼", - "current_server_address": "目前的伺服器位址", - "custom_date": "另選日期", - "custom_locale": "自訂地區設定", - "custom_locale_description": "根據語言與地區格式化日期與數字", - "custom_url": "自訂 URL", - "cutoff_date_description": "保留最近多少天的照片…", - "cutoff_day": "{count, plural, one {天} other {天}}", - "cutoff_year": "{count, plural, one {年} other {年}}", - "daily_title_text_date": "E, MMM dd", - "daily_title_text_date_year": "YYYY 年 M 月 D 日 (E)", - "dark": "深色", - "dark_theme": "切換深色主題", - "date": "日期", - "date_after": "起始日期", - "date_and_time": "日期與時間", - "date_before": "結束日期", - "date_format": "y 年 M 月 d 日 (E) h:mm a", - "date_of_birth_saved": "出生日期儲存成功", - "date_range": "日期範圍", - "day": "日", - "days": "日", - "deduplicate_all": "刪除所有重複項目", - "deduplication_criteria_1": "影像大小(以位元組為單位)", - "deduplication_criteria_2": "EXIF 資料數量", - "deduplication_info": "重複資料刪除資訊", - "deduplication_info_description": "要自動預先選取媒體並批次移除重複項目,我們會檢查:", - "default_locale": "預設地區", - "default_locale_description": "依照您的瀏覽器地區設定格式化日期與數字", - "delete": "刪除", - "delete_action_confirmation_message": "您確定要刪除此媒體嗎?此操作會將該媒體移至伺服器的垃圾桶,並會提示您是否要在本機同時刪除", - "delete_action_prompt": "{count} 個已刪除", - "delete_album": "刪除相簿", - "delete_api_key_prompt": "您確定要刪除這個 API 金鑰嗎?", - "delete_dialog_alert": "這些項目將從 Immich 以及您的裝置中永久刪除", - "delete_dialog_alert_local": "這些項目將會從您的裝置中永久移除,但仍可在 Immich 伺服器上存取", - "delete_dialog_alert_local_non_backed_up": "部分項目尚未備份至 Immich,且將會從您的裝置中永久移除", - "delete_dialog_alert_remote": "這些項目將從 Immich 伺服器中永久刪除", - "delete_dialog_ok_force": "確認刪除", - "delete_dialog_title": "永久刪除", - "delete_duplicates_confirmation": "您確定要永久刪除這些重複項目嗎?", - "delete_face": "刪除臉孔", - "delete_key": "刪除金鑰", - "delete_library": "刪除相簿", - "delete_link": "刪除連結", - "delete_local_action_prompt": "已在本機刪除 {count} 個項目", - "delete_local_dialog_ok_backed_up_only": "僅刪除已備份的項目", - "delete_local_dialog_ok_force": "確認刪除", - "delete_others": "刪除其他", - "delete_permanently": "永久刪除", - "delete_permanently_action_prompt": "已永久刪除 {count} 個項目", - "delete_shared_link": "刪除共享連結", - "delete_shared_link_dialog_title": "刪除共享連結", - "delete_tag": "刪除標籤", - "delete_tag_confirmation_prompt": "您確定要刪除「{tagName}」標籤嗎?", - "delete_user": "刪除使用者", - "deleted_shared_link": "共享連結已刪除", - "deletes_missing_assets": "刪除磁碟中遺失的媒體", - "description": "描述", - "description_input_hint_text": "新增描述...", - "description_input_submit_error": "更新描述時發生錯誤,請檢查日誌以取得更多詳細資訊", - "deselect_all": "取消全選", - "details": "詳細資訊", - "direction": "方向", - "disable": "禁用", - "disabled": "已停用", - "disallow_edits": "不允許編輯", - "discord": "Discord", - "discover": "探索", - "discovered_devices": "已探索的裝置", - "dismiss_all_errors": "忽略所有錯誤", - "dismiss_error": "忽略錯誤", - "display_options": "顯示選項", - "display_order": "顯示順序", - "display_original_photos": "顯示原始照片", - "display_original_photos_setting_description": "在檢視媒體時,若原始媒體與網頁相容,則優先顯示原始相片而非縮圖。這可能會導致照片顯示速度變慢。", - "do_not_show_again": "不再顯示此訊息", - "documentation": "說明文件", - "done": "完成", - "download": "下載", - "download_action_prompt": "正在下載 {count} 個媒體", - "download_canceled": "下載已取消", - "download_complete": "下載完成", - "download_enqueue": "已加入下載佇列", - "download_error": "下載時發生錯誤", - "download_failed": "下載失敗", - "download_finished": "下載完成", - "download_include_embedded_motion_videos": "嵌入影片", - "download_include_embedded_motion_videos_description": "將動態相片中內嵌的影片另存為獨立檔案", - "download_notfound": "無法找到下載", - "download_original": "下載原始文件", - "download_paused": "下載已暫停", - "download_settings": "下載", - "download_settings_description": "管理與媒體下載相關的設定", - "download_started": "已開始下載", - "download_sucess": "下載成功", - "download_sucess_android": "媒體已下載至 DCIM/Immich", - "download_waiting_to_retry": "等待重試", - "downloading": "下載中", - "downloading_asset_filename": "正在下載媒體 {filename}", - "downloading_from_icloud": "正從iCloud下載", - "downloading_media": "正在下載媒體", - "drop_files_to_upload": "將檔案拖放到任何位置以上傳", - "duplicates": "重複項目", - "duplicates_description": "逐一檢查每個群組,並標示其中是否有重複媒體", - "duration": "顯示時長", - "edit": "編輯", - "edit_album": "編輯相簿", - "edit_avatar": "編輯個人資料圖片", - "edit_birthday": "編輯生日", - "edit_date": "編輯日期", - "edit_date_and_time": "編輯日期與時間", - "edit_date_and_time_action_prompt": "已編輯了 {count} 個日期與時間", - "edit_date_and_time_by_offset": "依偏移量變更日期", - "edit_date_and_time_by_offset_interval": "新的日期範圍:{from} 至 {to}", - "edit_description": "編輯描述", - "edit_description_prompt": "請選擇新的描述:", - "edit_exclusion_pattern": "編輯排除條件", - "edit_faces": "編輯臉孔", - "edit_key": "編輯金鑰", - "edit_link": "編輯連結", - "edit_location": "編輯位置", - "edit_location_action_prompt": "{count} 個位置已編輯", - "edit_location_dialog_title": "位置", - "edit_name": "編輯名稱", - "edit_people": "編輯人物", - "edit_tag": "編輯標籤", - "edit_title": "編輯標題", - "edit_user": "編輯使用者", - "edit_workflow": "編輯工作流程", - "editor": "編輯器", - "editor_close_without_save_prompt": "此變更將不會被儲存", - "editor_close_without_save_title": "要關閉編輯器嗎?", - "editor_confirm_reset_all_changes": "你確定要重設所有變更嗎?", - "editor_flip_horizontal": "水平翻轉", - "editor_flip_vertical": "垂直翻轉", - "editor_orientation": "方向", - "editor_reset_all_changes": "重設變更", - "editor_rotate_left": "逆時針旋轉90度", - "editor_rotate_right": "順時針旋轉90度", - "email": "電子郵件", - "email_notifications": "Email 通知", - "empty_folder": "這個資料夾是空的", - "empty_trash": "清空垃圾桶", - "empty_trash_confirmation": "您確定要清空垃圾桶嗎?這會永久刪除 Immich 垃圾桶中所有的媒體。\n您無法撤銷此變更!", - "enable": "啟用", - "enable_backup": "啟用備份", - "enable_biometric_auth_description": "輸入您的 PIN 碼以啟用生物辨識驗證", - "enabled": "己啟用", - "end_date": "結束日期", - "enqueued": "已排入佇列", - "enter_wifi_name": "輸入 Wi-Fi 名稱", - "enter_your_pin_code": "輸入您的 PIN 碼", - "enter_your_pin_code_subtitle": "輸入您的 PIN 碼以存取鎖定的資料夾", - "error": "錯誤", - "error_change_sort_album": "變更相簿排序失敗", - "error_delete_face": "從媒體刪除臉孔時失敗", - "error_getting_places": "取得位置時出錯", - "error_loading_albums": "無法加載相簿", - "error_loading_image": "圖片載入錯誤", - "error_loading_partners": "載入合作夥伴時出錯:{error}", - "error_retrieving_asset_information": "無法獲取項目資訊", - "error_saving_image": "錯誤:{error}", - "error_tag_face_bounding_box": "標記臉部錯誤 - 無法取得邊界框坐標", - "error_title": "錯誤 - 發生錯誤", - "error_while_navigating": "無法引導至項目", - "errors": { - "cannot_navigate_next_asset": "無法導覽至下一個媒體", - "cannot_navigate_previous_asset": "無法導覽至上一個媒體", - "cant_apply_changes": "無法套用變更", - "cant_change_activity": "無法{enabled, select, true {停用} other {啟用}}活動", - "cant_change_asset_favorite": "無法變更檔案的收藏狀態", - "cant_change_metadata_assets_count": "無法變更 {count, plural, other {# 個檔案}}的中繼資料", - "cant_get_faces": "無法取得臉孔", - "cant_get_number_of_comments": "無法取得留言數量", - "cant_search_people": "無法搜尋人物", - "cant_search_places": "無法搜尋地點", - "error_adding_assets_to_album": "將媒體加入相簿時發生錯誤", - "error_adding_users_to_album": "將使用者加入相簿時發生錯誤", - "error_deleting_shared_user": "刪除共享使用者時發生錯誤", - "error_downloading": "下載 {filename} 時發生錯誤", - "error_hiding_buy_button": "隱藏購買按鈕時發生錯誤", - "error_removing_assets_from_album": "從相簿移除媒體時發生錯誤,請檢查主控臺以取得更多詳細資訊", - "error_selecting_all_assets": "選取所有檔案時發生錯誤", - "exclusion_pattern_already_exists": "此排除模式已存在。", - "failed_to_create_album": "相簿建立失敗", - "failed_to_create_shared_link": "建立共享連結失敗", - "failed_to_edit_shared_link": "編輯共享連結失敗", - "failed_to_get_people": "無法取得人物", - "failed_to_keep_this_delete_others": "無法保留此媒體並刪除其他媒體", - "failed_to_load_asset": "媒體載入失敗", - "failed_to_load_assets": "媒體載入失敗", - "failed_to_load_notifications": "載入通知失敗", - "failed_to_load_people": "載入人物失敗", - "failed_to_remove_product_key": "移除產品金鑰失敗", - "failed_to_reset_pin_code": "重設 PIN 碼失敗", - "failed_to_stack_assets": "無法媒體堆疊", - "failed_to_unstack_assets": "解除媒體堆疊失敗", - "failed_to_update_notification_status": "無法更新通知狀態", - "incorrect_email_or_password": "電子郵件或密碼錯誤", - "library_folder_already_exists": "此導入路徑已存在。", - "paths_validation_failed": "{paths, plural, one {# 個路徑} other {# 個路徑}} 驗證失敗", - "profile_picture_transparent_pixels": "個人資料圖片不能有透明畫素。請放大並/或移動影像。", - "quota_higher_than_disk_size": "您所設定的配額大於磁碟大小", - "something_went_wrong": "發生錯誤", - "unable_to_add_album_users": "無法將使用者加入相簿", - "unable_to_add_assets_to_shared_link": "無法加入媒體到共享連結", - "unable_to_add_comment": "無法新增留言", - "unable_to_add_exclusion_pattern": "無法新增篩選條件", - "unable_to_add_partners": "無法新增親朋好友", - "unable_to_add_remove_archive": "無法{archived, select, true {從封存中移除媒體} other {將檔案加入媒體}}", - "unable_to_add_remove_favorites": "無法將媒體{favorite, select, true {加入收藏} other {從收藏中移除}}", - "unable_to_archive_unarchive": "無法{archived, select, true {封存} other {取消封存}}", - "unable_to_change_album_user_role": "無法變更相簿使用者的角色", - "unable_to_change_date": "無法變更日期", - "unable_to_change_description": "無法變更描述", - "unable_to_change_favorite": "無法變更媒體的收藏狀態", - "unable_to_change_location": "無法變更位置", - "unable_to_change_password": "無法變更密碼", - "unable_to_change_visibility": "無法變更 {count, plural, one {# 位人物} other {# 位人物}} 的可見性", - "unable_to_complete_oauth_login": "無法完成 OAuth 登入", - "unable_to_connect": "無法連線", - "unable_to_copy_to_clipboard": "無法複製到剪貼簿,請確保您是以 https 存取本頁面", - "unable_to_create": "無法創建工作流", - "unable_to_create_admin_account": "無法建立管理員帳號", - "unable_to_create_api_key": "無法建立新的 API 金鑰", - "unable_to_create_library": "無法建立媒體庫", - "unable_to_create_user": "無法建立使用者", - "unable_to_delete_album": "無法刪除相簿", - "unable_to_delete_asset": "無法刪除媒體", - "unable_to_delete_assets": "刪除媒體時發生錯誤", - "unable_to_delete_exclusion_pattern": "無法刪除篩選條件", - "unable_to_delete_shared_link": "刪除共享連結失敗", - "unable_to_delete_user": "無法刪除使用者", - "unable_to_delete_workflow": "無法删除工作流", - "unable_to_download_files": "無法下載檔案", - "unable_to_edit_exclusion_pattern": "無法編輯篩選條件", - "unable_to_empty_trash": "無法清空垃圾桶", - "unable_to_enter_fullscreen": "無法進入全螢幕", - "unable_to_exit_fullscreen": "無法結束全螢幕", - "unable_to_get_comments_number": "無法取得留言數量", - "unable_to_get_shared_link": "取得共享連結失敗", - "unable_to_hide_person": "無法隱藏人物", - "unable_to_link_motion_video": "無法連結動態影片", - "unable_to_link_oauth_account": "無法連結 OAuth 帳號", - "unable_to_log_out_all_devices": "無法登出所有裝置", - "unable_to_log_out_device": "無法登出裝置", - "unable_to_login_with_oauth": "無法使用 OAuth 登入", - "unable_to_play_video": "無法播放影片", - "unable_to_reassign_assets_existing_person": "無法將檔案重新指派給 {name, select, null {現有的人員} other {{name}}}", - "unable_to_reassign_assets_new_person": "無法將媒體重新指派給新的人物", - "unable_to_refresh_user": "無法重新整理使用者", - "unable_to_remove_album_users": "無法從相簿中移除使用者", - "unable_to_remove_api_key": "無法移除 API 金鑰", - "unable_to_remove_assets_from_shared_link": "刪除共享連結中媒體失敗", - "unable_to_remove_library": "無法移除媒體庫", - "unable_to_remove_partner": "無法移除親朋好友", - "unable_to_remove_reaction": "無法移除反應", - "unable_to_reset_password": "無法重設密碼", - "unable_to_reset_pin_code": "無法重設 PIN 碼", - "unable_to_resolve_duplicate": "無法解決重複項目", - "unable_to_restore_assets": "無法還原媒體", - "unable_to_restore_trash": "無法還原垃圾桶", - "unable_to_restore_user": "無法還原使用者", - "unable_to_save_album": "無法儲存相簿", - "unable_to_save_api_key": "無法儲存 API 金鑰", - "unable_to_save_date_of_birth": "無法儲存出生日期", - "unable_to_save_name": "無法儲存名稱", - "unable_to_save_profile": "無法儲存個人資料", - "unable_to_save_settings": "無法儲存設定", - "unable_to_scan_libraries": "無法掃描媒體庫", - "unable_to_scan_library": "無法掃描媒體庫", - "unable_to_set_feature_photo": "無法設定封面圖片", - "unable_to_set_profile_picture": "無法設定個人資料圖片", - "unable_to_set_rating": "無法設定評星", - "unable_to_submit_job": "無法提交任務", - "unable_to_trash_asset": "無法將媒體丟進垃圾桶", - "unable_to_unlink_account": "無法解除帳號連結", - "unable_to_unlink_motion_video": "無法解除連結動態影片", - "unable_to_update_album_cover": "無法更新相簿封面", - "unable_to_update_album_info": "無法更新相簿資訊", - "unable_to_update_library": "無法更新媒體庫", - "unable_to_update_location": "無法更新位置", - "unable_to_update_settings": "無法更新設定", - "unable_to_update_timeline_display_status": "無法更新時間軸顯示狀態", - "unable_to_update_user": "無法更新使用者", - "unable_to_update_workflow": "無法更新工作流", - "unable_to_upload_file": "無法上傳檔案" - }, - "errors_text": "錯誤", - "exclusion_pattern": "排除模式", - "exif": "EXIF 可交換影像檔格式", - "exif_bottom_sheet_description": "新增描述...", - "exif_bottom_sheet_description_error": "更新描述時發生錯誤", - "exif_bottom_sheet_details": "詳細資料", - "exif_bottom_sheet_location": "位置", - "exif_bottom_sheet_no_description": "無描述", - "exif_bottom_sheet_people": "人物", - "exif_bottom_sheet_person_add_person": "新增姓名", - "exit_slideshow": "結束幻燈片", - "expand_all": "展開全部", - "experimental_settings_new_asset_list_subtitle": "正在處理", - "experimental_settings_new_asset_list_title": "啟用實驗性相片格狀版面", - "experimental_settings_subtitle": "使用風險自負!", - "experimental_settings_title": "實驗性功能", - "expire_after": "失效時間", - "expired": "已過期", - "expires_date": "失效於 {date}", - "explore": "探索", - "explorer": "總覽", - "export": "匯出", - "export_as_json": "匯出為 JSON", - "export_database": "匯出資料庫", - "export_database_description": "匯出 SQLite 資料庫", - "extension": "副檔名", - "external": "外部", - "external_libraries": "外部媒體庫", - "external_network": "外部網路", - "external_network_sheet_info": "若未連線至偏好的 Wi-Fi,將依列表從上到下選擇可連線的伺服器網址", - "face_unassigned": "未指定", - "failed": "失敗", - "failed_count": "失敗:{count}", - "failed_to_authenticate": "身份驗證失敗", - "failed_to_load_assets": "無法載入媒體", - "failed_to_load_folder": "無法載入資料夾", - "favorite": "收藏", - "favorite_action_prompt": "已新增 {count} 個到收藏", - "favorite_or_unfavorite_photo": "收藏或取消收藏照片", - "favorites": "收藏", - "favorites_page_no_favorites": "未找到收藏項目", - "feature_photo_updated": "特色照片已更新", - "features": "功能", - "features_in_development": "發展中的特點", - "features_setting_description": "管理應用程式功能", - "file_name_or_extension": "檔案名稱或副檔名", - "file_size": "文件大小", - "filename": "檔案名稱", - "filetype": "檔案類型", - "filter": "濾鏡", - "filter_description": "篩選目標資產的條件", - "filter_people": "篩選人物", - "filter_places": "篩選地點", - "filters": "篩檢程式", - "find_them_fast": "透過搜尋名稱快速找到他們", - "first": "第一個", - "fix_incorrect_match": "修復不相符的", - "folder": "資料夾", - "folder_not_found": "找不到資料夾", - "folders": "資料夾", - "folders_feature_description": "透過資料夾檢視瀏覽檔案系統中的相片與影片", - "forgot_pin_code_question": "忘記您的 PIN 碼?", - "forward": "由新至舊", - "free_up_space": "釋放內存", - "free_up_space_description": "已備份照片和影片已經移到裝置的垃圾桶以釋放內存。伺服器上的存檔依然安全。", - "free_up_space_settings_subtitle": "釋放裝置內存", - "full_path": "完整路徑:{path}", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "此功能需要從 Google 載入外部資源才能正常運作。", - "general": "一般", - "geolocation_instruction_location": "點選具有 GPS 座標的項目以使用其位置,或直接從地圖中選擇地點", - "get_help": "取得協助", - "get_people_error": "獲取人員時出錯", - "get_wifiname_error": "無法取得 Wi-Fi 名稱。請確認您已授予必要的權限,並已連線至 Wi-Fi 網路", - "getting_started": "開始使用", - "go_back": "上一頁", - "go_to_folder": "前往資料夾", - "go_to_search": "前往搜尋", - "gps": "GPS", - "gps_missing": "無 GPS", - "grant_permission": "授予權限", - "group_albums_by": "分類群組的方式...", - "group_country": "按照國家分類", - "group_no": "沒有分類", - "group_owner": "按擁有者分類", - "group_places_by": "分類地點的方式...", - "group_year": "按年份分類", - "haptic_feedback_switch": "啟用震動回饋", - "haptic_feedback_title": "震動回饋", - "has_quota": "已設定配額", - "hash_asset": "雜湊媒體", - "hashed_assets": "已雜湊的媒體", - "hashing": "正在計算雜湊值", - "header_settings_add_header_tip": "新增標頭", - "header_settings_field_validator_msg": "值不可為空", - "header_settings_header_name_input": "標頭名稱", - "header_settings_header_value_input": "標頭值", - "headers_settings_tile_title": "自訂代理標頭", - "height": "高度", - "hi_user": "嗨!{name}({email})", - "hide_all_people": "隱藏所有人物", - "hide_gallery": "隱藏媒體庫", - "hide_named_person": "隱藏 {name}", - "hide_password": "隱藏密碼", - "hide_person": "隱藏人物", - "hide_schema": "隱藏架構", - "hide_text_recognition": "隱藏文字識別", - "hide_unnamed_people": "隱藏未命名的人物", - "home_page_add_to_album_conflicts": "已將 {added} 個媒體新增到相簿 {album}。{failed} 個媒體已在該相簿中。", - "home_page_add_to_album_err_local": "暫時不能將本機媒體新增到相簿,已略過", - "home_page_add_to_album_success": "已在 {album} 相簿中新增 {added} 個媒體。", - "home_page_album_err_partner": "暫時不能無法將親朋好友的媒體新增到相簿,已略過", - "home_page_archive_err_local": "暫時不能封存本機媒體,已略過", - "home_page_archive_err_partner": "無法封存親朋好友的媒體,已略過", - "home_page_building_timeline": "正在建立時間軸", - "home_page_delete_err_partner": "無法刪除親朋好友的媒體,已略過", - "home_page_delete_remote_err_local": "刪除遠端媒體的選取中包含本機媒體,已略過", - "home_page_favorite_err_local": "暫不能收藏本機項目,略過", - "home_page_favorite_err_partner": "暫無法收藏親朋好友的項目,略過", - "home_page_first_time_notice": "如果這是您第一次使用本程式,請確保選擇一個要備份的相簿,以將照片與影片加入時間軸", - "home_page_locked_error_local": "無法移動本機檔案至鎖定的資料夾,已略過", - "home_page_locked_error_partner": "無法移動親朋好友分享的媒體至鎖定的資料夾,已略過", - "home_page_share_err_local": "無法透過連結共享本機媒體,已略過", - "home_page_upload_err_limit": "一次最多隻能上傳 30 個媒體,已略過", - "host": "主機", - "hour": "小時", - "hours": "小時", - "id": "ID", - "idle": "閒置", - "ignore_icloud_photos": "忽略 iCloud 照片", - "ignore_icloud_photos_description": "儲存在 iCloud 中的照片不會上傳至 Immich 伺服器", - "image": "圖片", - "image_alt_text_date": "{isVideo, select, true {影片} other {圖片}}拍攝於 {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {影片} other {圖片}} 與 {person1} 一同於 {date} 拍攝", - "image_alt_text_date_2_people": "{person1} 和 {person2} 一同於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_3_people": "{person1}、{person2} 和 {person3} 一同於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_4_or_more_people": "{person1}、{person2} 和其他 {additionalCount, number} 人於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_place": "於 {date} 在 {country} - {city} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_place_1_person": "在 {country} - {city},與 {person1} 一同於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_place_2_people": "在 {country} - {city} 與 {person1} 和 {person2} 一同於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_place_3_people": "在 {country} - {city} 與 {person1}、{person2} 和 {person3} 一同於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_alt_text_date_place_4_or_more_people": "在 {country} - {city} 與 {person1}、{person2} 和其他 {additionalCount, number} 人於 {date} 拍攝的{isVideo, select, true {影片} other {圖片}}", - "image_saved_successfully": "已儲存圖片", - "image_viewer_page_state_provider_download_started": "下載已啟動", - "image_viewer_page_state_provider_download_success": "下載成功", - "image_viewer_page_state_provider_share_error": "分享時發生錯誤", - "immich_logo": "Immich Logo", - "immich_web_interface": "Immich 網頁介面", - "import_from_json": "從 JSON 匯入", - "import_path": "匯入路徑", - "in_albums": "在 {count, plural, one {# 本相簿} other {# 本相簿}}中", - "in_archive": "在封存中", - "in_year": "{year}年", - "in_year_selector": "在", - "include_archived": "包含已封存", - "include_shared_albums": "包含共享相簿", - "include_shared_partner_assets": "包括共享親朋好友的媒體", - "individual_share": "個別分享", - "individual_shares": "個別分享", - "info": "資訊", - "interval": { - "day_at_onepm": "每天下午 1 點", - "hours": "每 {hours, plural, one {小時} other {{hours, number} 小時}}", - "night_at_midnight": "每晚午夜", - "night_at_twoam": "每晚凌晨 2 點" - }, - "invalid_date": "無效的日期", - "invalid_date_format": "無效的日期格式", - "invite_people": "邀請人員", - "invite_to_album": "邀請至相簿", - "ios_debug_info_fetch_ran_at": "抓取已於 {dateTime} 執行", - "ios_debug_info_last_sync_at": "上次同步於 {dateTime}", - "ios_debug_info_no_processes_queued": "無排程中的背景程式", - "ios_debug_info_no_sync_yet": "尚未執行任何背景同步任務", - "ios_debug_info_processes_queued": "{count, plural, one {{count} 個背景程式已排程} other {{count} 個背景程式已排程}}", - "ios_debug_info_processing_ran_at": "於 {dateTime} 執行處理", - "items_count": "{count, plural, one {# 個項目} other {# 個項目}}", - "jobs": "任務", - "json_editor": "JSON編輯器", - "json_error": "JSON錯誤", - "keep": "保留", - "keep_albums": "保留相簿", - "keep_all": "全部保留", - "keep_description": "選擇釋放空間時,保留在裝置上的相片", - "keep_favorites": "保留最愛的相片", - "keep_on_device": "保留在裝置上", - "keep_on_device_hint": "選擇保留在裝置上的相片", - "keep_this_delete_others": "保留這個,刪除其他", - "keeping": "保留:{items}", - "kept_this_deleted_others": "保留這個項目並刪除{count, plural, one {# asset} other {# assets}}", - "keyboard_shortcuts": "鍵盤快捷鍵", - "language": "語言", - "language_no_results_subtitle": "試著調整您的搜尋詞彙", - "language_no_results_title": "未找到語言", - "language_search_hint": "搜尋語言...", - "language_setting_description": "選擇您的首選語言", - "large_files": "大型檔案", - "last": "最後一個", - "last_months": "{count, plural, one {上個月} other {最近 # 個月}}", - "last_seen": "最後上線", - "latest_version": "最新版本", - "latitude": "緯度", - "leave": "離開", - "leave_album": "離開相簿", - "lens_model": "鏡頭型號", - "let_others_respond": "允許他人回覆", - "level": "等級", - "library": "相簿", - "library_add_folder": "添加資料夾", - "library_edit_folder": "編輯資料夾", - "library_options": "資料庫選項", - "library_page_device_albums": "裝置上的相簿", - "library_page_new_album": "新增相簿", - "library_page_sort_asset_count": "項目數量", - "library_page_sort_created": "新增日期", - "library_page_sort_last_modified": "上次修改", - "library_page_sort_title": "相簿標題", - "licenses": "授權", - "light": "淺色", - "like": "喜歡", - "like_deleted": "已取消喜歡", - "link_motion_video": "連結動態影片", - "link_to_oauth": "連結 OAuth", - "linked_oauth_account": "已連結 OAuth 帳號", - "list": "列表", - "loading": "載入中", - "loading_search_results_failed": "載入搜尋結果失敗", - "local": "本機", - "local_asset_cast_failed": "無法投放未上傳至伺服器的項目", - "local_assets": "本機項目", - "local_id": "本地ID", - "local_media_summary": "本機媒體摘要", - "local_network": "本機網路", - "local_network_sheet_info": "當使用指定的 Wi-Fi 網路時,應用程式將透過此網址連線至伺服器", - "location": "位置", - "location_permission": "位置權限", - "location_permission_content": "使用自動切換功能,Immich 需要精確位置權限,以取得已連線的 Wi-Fi 網路名稱", - "location_picker_choose_on_map": "在地圖上選擇", - "location_picker_latitude_error": "輸入有效的緯度值", - "location_picker_latitude_hint": "請在此處輸入您的緯度值", - "location_picker_longitude_error": "輸入有效的經度值", - "location_picker_longitude_hint": "請在此處輸入您的經度值", - "lock": "鎖定", - "locked_folder": "鎖定的資料夾", - "log_detail_title": "日誌詳細資訊", - "log_out": "登出", - "log_out_all_devices": "登出所有裝置", - "logged_in_as": "以{user}身分登入", - "logged_out_all_devices": "已登出所有裝置", - "logged_out_device": "已登出裝置", - "login": "登入", - "login_disabled": "已停用登入", - "login_form_api_exception": "API 發生例外,請檢查伺服器位址後再試。", - "login_form_back_button_text": "上一頁", - "login_form_email_hint": "電子郵件地址", - "login_form_endpoint_hint": "http://您的伺服器位址:連接埠", - "login_form_endpoint_url": "伺服器端點 URL", - "login_form_err_http": "請註明 http:// 或 https://", - "login_form_err_invalid_email": "電子郵件地址無效", - "login_form_err_invalid_url": "無效的 URL", - "login_form_err_leading_whitespace": "帶有前導空格", - "login_form_err_trailing_whitespace": "帶有尾隨空格", - "login_form_failed_get_oauth_server_config": "使用 OAuth 登入時錯誤,請檢查伺服器位址", - "login_form_failed_get_oauth_server_disable": "OAuth 功能在此伺服器上無法使用", - "login_form_failed_login": "登入失敗,請檢查伺服器位址、電子郵件地址與密碼", - "login_form_handshake_exception": "與伺服器通訊時出現握手異常。若使用自簽名憑證,請在設定中啟用自簽名憑證支援。", - "login_form_password_hint": "密碼", - "login_form_save_login": "保持登入", - "login_form_server_empty": "請輸入伺服器網址。", - "login_form_server_error": "無法連線至伺服器。", - "login_has_been_disabled": "已停用登入功能。", - "login_password_changed_error": "密碼更新失敗", - "login_password_changed_success": "密碼更新成功", - "logout_all_device_confirmation": "您確定要登出所有裝置嗎?", - "logout_this_device_confirmation": "要登出這臺裝置嗎?", - "logs": "日誌", - "longitude": "經度", - "look": "樣貌", - "loop_videos": "重播影片", - "loop_videos_description": "啟用後,影片結束會自動重播。", - "main_branch_warning": "您現在使用的是開發版本;我們強烈您建議使用正式發行版!", - "main_menu": "主選單", - "maintenance_action_restore": "復原資料庫", - "maintenance_description": "Immich已進入維護模式。", - "maintenance_end": "結束維護模式", - "maintenance_end_error": "未能結束維護模式。", - "maintenance_logged_in_as": "當前以{user}身份登入", - "maintenance_restore_from_backup": "從備份復原", - "maintenance_restore_library": "復原你的相簿", - "maintenance_restore_library_confirm": "確認是否正確,將繼續從備份復原!", - "maintenance_restore_library_description": "正在復原資料庫", - "maintenance_restore_library_folder_has_files": "{folder}有{count}個資料夾", - "maintenance_restore_library_folder_no_files": "{folder}有缺失的檔案!", - "maintenance_restore_library_folder_pass": "可以讀寫", - "maintenance_restore_library_folder_read_fail": "無法讀取", - "maintenance_restore_library_folder_write_fail": "無法寫入", - "maintenance_restore_library_hint_missing_files": "可能遺失重要檔案", - "maintenance_restore_library_hint_regenerate_later": "之後可以在設定重新產生", - "maintenance_task_backup": "正在建立現有資料庫的備份…", - "maintenance_task_restore": "正在從選擇的備份復原…", - "maintenance_task_rollback": "復原失敗,恢復到之前的儲存…", - "maintenance_title": "暫時不可用", - "make": "製造商", - "manage_geolocation": "管理位置", - "manage_media_access_rationale": "正確處理將資產移至垃圾桶並將其從垃圾桶中恢復需要此許可。", - "manage_media_access_settings": "打開設定", - "manage_media_access_subtitle": "允許Immich應用程序管理和移動媒體檔案。", - "manage_media_access_title": "媒體管理訪問", - "manage_shared_links": "管理共享連結", - "manage_sharing_with_partners": "管理與親朋好友的分享", - "manage_the_app_settings": "管理應用程式設定", - "manage_your_account": "管理您的帳號", - "manage_your_api_keys": "管理您的 API 金鑰", - "manage_your_devices": "管理已登入的裝置", - "manage_your_oauth_connection": "管理您的 OAuth 連結", - "map": "地圖", - "map_assets_in_bounds": "{count, plural, one {# 張照片} other {# 張照片}}", - "map_cannot_get_user_location": "無法取得使用者位置", - "map_location_dialog_yes": "確定", - "map_location_picker_page_use_location": "使用此位置", - "map_location_service_disabled_content": "需要啟用定位服務才能顯示目前位置相關的項目。要現在啟用嗎?", - "map_location_service_disabled_title": "定位服務已停用", - "map_marker_for_images": "在 {city}、{country} 拍攝影像的地圖示記", - "map_marker_with_image": "帶有影像的地圖示記", - "map_no_location_permission_content": "需要位置權限才能顯示與目前位置。要現在就授予位置權限嗎?", - "map_no_location_permission_title": "沒有位置權限", - "map_settings": "地圖設定", - "map_settings_dark_mode": "深色模式", - "map_settings_date_range_option_day": "過去 24 小時", - "map_settings_date_range_option_days": "{days} 天前", - "map_settings_date_range_option_year": "1 年前", - "map_settings_date_range_option_years": "{years} 年前", - "map_settings_dialog_title": "地圖設定", - "map_settings_include_show_archived": "包括已封存項目", - "map_settings_include_show_partners": "包含親朋好友", - "map_settings_only_show_favorites": "僅顯示收藏的項目", - "map_settings_theme_settings": "地圖主題", - "map_zoom_to_see_photos": "縮小以檢視項目", - "mark_all_as_read": "全部標記為已讀", - "mark_as_read": "標記為已讀", - "marked_all_as_read": "已全部標記為已讀", - "matches": "相符", - "matching_assets": "匹配資產", - "media_type": "媒體類型", - "memories": "回憶", - "memories_all_caught_up": "已全部看完", - "memories_check_back_tomorrow": "明天再看", - "memories_setting_description": "管理您的回憶中顯示的內容", - "memories_start_over": "再看一次", - "memories_swipe_to_close": "上滑關閉", - "memory": "回憶", - "memory_lane_title": "回憶長廊{title}", - "menu": "選單", - "merge": "合併", - "merge_people": "合併人物", - "merge_people_limit": "一次最多隻能合併 5 張臉孔", - "merge_people_prompt": "您要合併這些人物嗎?此操作無法撤銷。", - "merge_people_successfully": "成功合併人物", - "merged_people_count": "合併了 {count, plural, one {# 位人士} other {# 位人士}}", - "minimize": "最小化", - "minute": "分", - "minutes": "分鐘", - "mirror_horizontal": "水平", - "mirror_vertical": "垂直", - "missing": "排入未處理", - "mobile_app": "移動應用程序", - "mobile_app_download_onboarding_note": "使用以下選項下載配套移動應用程序", - "model": "型號", - "month": "月", - "monthly_title_text_date_format": "y MMMM", - "more": "更多", - "move": "移動", - "move_down": "向下移動", - "move_off_locked_folder": "移出鎖定的資料夾", - "move_to": "移動到", - "move_to_device_trash": "移動到裝置的垃圾桶", - "move_to_lock_folder_action_prompt": "{count} 已新增至鎖定的資料夾中", - "move_to_locked_folder": "移至鎖定的資料夾", - "move_to_locked_folder_confirmation": "這些照片和影片將從所有相簿中移除,並僅可從鎖定的資料夾檢視", - "move_up": "向上移動", - "moved_to_archive": "已封存 {count, plural, one {# 個項目} other {# 個項目}}", - "moved_to_library": "已移動 {count, plural, one {# 個項目} other {# 個項目}} 至相簿", - "moved_to_trash": "已丟進垃圾桶", - "multiselect_grid_edit_date_time_err_read_only": "無法編輯唯讀項目的日期,略過", - "multiselect_grid_edit_gps_err_read_only": "無法編輯唯讀項目的位置資訊,略過", - "mute_memories": "靜音回憶", - "my_albums": "我的相簿", - "name": "名稱", - "name_or_nickname": "名稱或暱稱", - "name_required": "名稱是必填項", - "navigate": "導航", - "navigate_to_time": "導航到時間", - "network_requirement_photos_upload": "使用行動網路流量備份照片", - "network_requirement_videos_upload": "使用行動網路流量備份影片", - "network_requirements": "網路要求", - "network_requirements_updated": "網路需求已變更,現重設備份佇列", - "networking_settings": "網路", - "networking_subtitle": "管理伺服器端點設定", - "never": "永不失效", - "new_album": "新相簿", - "new_api_key": "新的 API 金鑰", - "new_date_range": "新日期範圍", - "new_password": "新密碼", - "new_person": "新的人物", - "new_pin_code": "新 PIN 碼", - "new_pin_code_subtitle": "這是您第一次存取鎖定的資料夾。建立 PIN 碼以安全存取此頁面", - "new_timeline": "新時間軸", - "new_update": "新更新", - "new_user_created": "已建立新使用者", - "new_version_available": "新版本已發布", - "newest_first": "最新優先", - "next": "下一步", - "next_memory": "下一張回憶", - "no": "否", - "no_actions_added": "尚未添加任何操作", - "no_albums_found": "無相簿", - "no_albums_message": "建立相簿來整理照片和影片", - "no_albums_with_name_yet": "看來還沒有這個名字的相簿。", - "no_albums_yet": "看來您還沒有任何相簿。", - "no_archived_assets_message": "將照片和影片封存,就不會顯示在「照片」中", - "no_assets_message": "按這裡上傳您的第一張照片", - "no_assets_to_show": "無項目展示", - "no_cast_devices_found": "找不到 Google Cast 裝置", - "no_checksum_local": "沒有可用的校驗和 - 無法取得本機資產", - "no_checksum_remote": "沒有可用的校驗和 - 無法取得遠端資產", - "no_configuration_needed": "無需配寘", - "no_devices": "無授權設備", - "no_duplicates_found": "沒發現重複項目。", - "no_exif_info_available": "沒有可用的 Exif 資訊", - "no_explore_results_message": "上傳更多照片以利探索。", - "no_favorites_message": "加入收藏,加速尋找影像", - "no_filters_added": "尚未添加篩檢程式", - "no_libraries_message": "建立外部媒體庫以檢視您的照片和影片", - "no_local_assets_found": "未找到具有此校驗和的本機資產", - "no_location_set": "未設定位置", - "no_locked_photos_message": "鎖定的資料夾中的照片和影片會被隱藏,當您瀏覽或搜尋相簿時不會顯示。", - "no_name": "無名", - "no_notifications": "沒有通知", - "no_people_found": "找不到符合的人物", - "no_places": "沒有地點", - "no_remote_assets_found": "未找到具有此校驗和的遠端資產", - "no_results": "沒有結果", - "no_results_description": "試試同義詞或更通用的關鍵字吧", - "no_shared_albums_message": "建立相簿分享照片和影片", - "no_uploads_in_progress": "沒有正在上傳的項目", - "none": "無", - "not_allowed": "不允許", - "not_available": "不適用", - "not_in_any_album": "不在任何相簿中", - "not_selected": "未選擇", - "note_apply_storage_label_to_previously_uploaded assets": "*註:執行套用儲存標籤前先上傳項目", - "notes": "提示", - "nothing_here_yet": "暫無訊息", - "notification_permission_dialog_content": "開啟通知,請前往「設定」,並選擇「允許」。", - "notification_permission_list_tile_content": "開啟通知權限。", - "notification_permission_list_tile_enable_button": "開啟通知", - "notification_permission_list_tile_title": "通知權限", - "notification_toggle_setting_description": "啟用電子郵件通知", - "notifications": "通知", - "notifications_setting_description": "管理通知", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium配寘器", - "obtainium_configurator_instructions": "使用Obtainium直接從Immich GitHub的版本安裝和更新Android應用程序。 創建一個API金鑰並選擇一個變體來創建您的Obtainium配寘連結", - "ocr": "OCR", - "official_immich_resources": "官方 Immich 資源", - "offline": "離線", - "offset": "移動", - "ok": "確定", - "oldest_first": "由舊至新", - "on_this_device": "在此裝置", - "onboarding": "入門指南", - "onboarding_locale_description": "選擇您想要顯示的語言。設定完成之後生效。", - "onboarding_privacy_description": "以下(可選)功能仰賴外部服務,可隨時在設定中停用。", - "onboarding_server_welcome_description": "讓我們為您的系統進行一些基本設定。", - "onboarding_theme_description": "幫執行個體選色彩主題。之後也可以在設定中變更。", - "onboarding_user_welcome_description": "讓我們開始吧!", - "onboarding_welcome_user": "歡迎,{user}", - "online": "線上", - "only_favorites": "僅顯示己收藏", - "open": "開啟", - "open_in_map_view": "開啟地圖檢視", - "open_in_openstreetmap": "用 OpenStreetMap 開啟", - "open_the_search_filters": "開啟搜尋篩選器", - "options": "選項", - "or": "或", - "organize_into_albums": "整理成相簿", - "organize_into_albums_description": "使用目前同步設定將現有照片放入相簿", - "organize_your_library": "整理您的相簿", - "original": "原圖", - "other": "其他", - "other_devices": "其它裝置", - "other_entities": "其他項目", - "other_variables": "其他變數", - "owned": "我的", - "owner": "所有者", - "page": "頁", - "partner": "親朋好友", - "partner_can_access": "{partner} 可以存取", - "partner_can_access_assets": "除了已封存和已刪除之外,您所有的照片和影片", - "partner_can_access_location": "您照片拍攝的位置", - "partner_list_user_photos": "{user} 的照片", - "partner_list_view_all": "展示全部", - "partner_page_empty_message": "您的照片尚未與任何親朋好友共享。", - "partner_page_no_more_users": "無需新增更多使用者", - "partner_page_partner_add_failed": "新增親朋好友失敗", - "partner_page_select_partner": "選擇親朋好友", - "partner_page_shared_to_title": "共享給", - "partner_page_stop_sharing_content": "{partner} 將無法再存取您的照片。", - "partner_sharing": "親朋好友分享", - "partners": "親朋好友", - "password": "密碼", - "password_does_not_match": "密碼不相符", - "password_required": "需要密碼", - "password_reset_success": "密碼重設成功", - "past_durations": { - "days": "過去 {days, plural, one {一天} other {# 天}}", - "hours": "過去 {hours, plural, one {一小時} other {# 小時}}", - "years": "過去 {years, plural, one {一年} other {# 年}}" - }, - "path": "路徑", - "pattern": "模式", - "pause": "暫停", - "pause_memories": "暫停回憶", - "paused": "已暫停", - "pending": "待處理", - "people": "人物", - "people_edits_count": "編輯了 {count, plural, one {# 位人士} other {# 位人士}}", - "people_feature_description": "以人物分類瀏覽照片和影片", - "people_selected": "{count, plural, one {# 個人已選擇} other {# 個人已選擇}}", - "people_sidebar_description": "在側邊欄顯示「人物」的連結", - "permanent_deletion_warning": "永久刪除警告", - "permanent_deletion_warning_setting_description": "在永久刪除檔案時顯示警告", - "permanently_delete": "永久刪除", - "permanently_delete_assets_count": "永久刪除 {count, plural, one {檔案} other {檔案}}", - "permanently_delete_assets_prompt": "確定要永久刪除 {count, plural, other {這 # 個檔案?}}這樣{count, plural, one {它} other {它們}}也會從自己所在的相簿中消失。", - "permanently_deleted_asset": "永久刪除的檔案", - "permanently_deleted_assets_count": "永久刪除的 {count, plural, one {# 個檔案} other {# 個檔案}}", - "permission": "權限", - "permission_empty": "權限不能為空", - "permission_onboarding_back": "上一頁", - "permission_onboarding_continue_anyway": "確認繼續", - "permission_onboarding_get_started": "開始使用", - "permission_onboarding_go_to_settings": "前往設定", - "permission_onboarding_permission_denied": "如要繼續,請允許 Immich 存取相片和影片權限。", - "permission_onboarding_permission_granted": "已允許!一切就緒。", - "permission_onboarding_permission_limited": "如要繼續,請允許 Immich 備份和管理您的相簿收藏,在設定中授予相片和影片權限。", - "permission_onboarding_request": "Immich 需要權限才能檢視您的相片和短片。", - "person": "人物", - "person_age_months": "{months, plural, one {# 個月} other {# 個月}}", - "person_age_year_months": "1 年 {months, plural, one {# 個月} other {# 個月}}", - "person_age_years": "{years, plural, other {# 歲}}", - "person_birthdate": "生於 {date}", - "person_hidden": "{name}{hidden, select, true {(隱藏)} other {}}", - "person_recognized": "被認可的人", - "person_selected": "已選擇的人", - "photo_shared_all_users": "看來您與所有使用者分享了照片,或沒有其他使用者可供分享。", - "photos": "照片", - "photos_and_videos": "照片及影片", - "photos_count": "{count, plural, other {{count, number} 張照片}}", - "photos_from_previous_years": "往年的照片", - "photos_only": "只允許照片", - "pick_a_location": "選擇位置", - "pick_custom_range": "自定義範圍", - "pick_date_range": "選擇日期範圍", - "pin_code_changed_successfully": "變更 PIN 碼成功", - "pin_code_reset_successfully": "重設 PIN 碼成功", - "pin_code_setup_successfully": "設定 PIN 碼成功", - "pin_verification": "PIN 碼驗證", - "place": "地點", - "places": "地點", - "places_count": "{count, plural, one {{count, number} 個地點} other {{count, number} 個地點}}", - "play": "播放", - "play_memories": "播放回憶", - "play_motion_photo": "播放動態照片", - "play_or_pause_video": "播放或暫停影片", - "play_original_video": "播放原始視頻", - "play_original_video_setting_description": "更喜歡播放原始視頻,而不是轉碼視頻。 如果原始資源不相容,則可能無法正確播放。", - "play_transcoded_video": "播放轉碼視頻", - "please_auth_to_access": "請進行身份驗證才能存取", - "port": "埠口", - "preferences_settings_subtitle": "管理應用程式偏好設定", - "preferences_settings_title": "偏好設定", - "preparing": "準備", - "preset": "預設", - "preview": "預覽", - "previous": "上一張", - "previous_memory": "上一張回憶", - "previous_or_next_day": "前一天/後一天", - "previous_or_next_month": "上一個月/下一個月", - "previous_or_next_photo": "上一張照片/下一張照片", - "previous_or_next_year": "上一年/下一年", - "primary": "首要", - "privacy": "隱私", - "profile": "帳戶設定", - "profile_drawer_app_logs": "日誌", - "profile_drawer_client_server_up_to_date": "用戶端與伺服器端都是最新的", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "唯讀模式已開啟。請長按使用者頭像圖示以結束。", - "profile_image_of_user": "{user} 的個人資料圖片", - "profile_picture_set": "已設定個人資料圖片。", - "public_album": "公開相簿", - "public_share": "公開分享", - "purchase_account_info": "擁護者", - "purchase_activated_subtitle": "感謝您對 Immich 及開源軟體的支援", - "purchase_activated_time": "於 {date} 啟用", - "purchase_activated_title": "金鑰成功啟用了", - "purchase_button_activate": "啟用", - "purchase_button_buy": "購置", - "purchase_button_buy_immich": "購置 Immich", - "purchase_button_never_show_again": "不再顯示", - "purchase_button_reminder": "過 30 天再提醒我", - "purchase_button_remove_key": "移除金鑰", - "purchase_button_select": "選這個", - "purchase_failed_activation": "啟用失敗!請檢查您的電子郵件,取得正確的產品金鑰!", - "purchase_individual_description_1": "針對個人", - "purchase_individual_description_2": "擁護者狀態", - "purchase_individual_title": "個人", - "purchase_input_suggestion": "有產品金鑰嗎?請在下面輸入金鑰", - "purchase_license_subtitle": "購置 Immich 來支援軟體開發", - "purchase_lifetime_description": "終身購置", - "purchase_option_title": "購置選項", - "purchase_panel_info_1": "開發 Immich 可不是件容易的事,花了我們不少功夫。好在有一群全職工程師在背後默默努力,為的就是把它做到最好。我們的目標很簡單:讓開放原始碼軟體和正當的商業模式能成為開發者的長期飯碗,同時打造出重視隱私的生態系統,讓大家有個不被限制的雲端服務新選擇。", - "purchase_panel_info_2": "我們承諾不設付費牆,所以購置 Immich 並不會讓您獲得額外的功能。我們仰賴使用者們的支援來開發 Immich。", - "purchase_panel_title": "支援這項專案", - "purchase_per_server": "每臺伺服器", - "purchase_per_user": "每位使用者", - "purchase_remove_product_key": "移除產品金鑰", - "purchase_remove_product_key_prompt": "確定要移除產品金鑰嗎?", - "purchase_remove_server_product_key": "移除伺服器產品金鑰", - "purchase_remove_server_product_key_prompt": "確定要移除伺服器產品金鑰嗎?", - "purchase_server_description_1": "給整臺伺服器", - "purchase_server_description_2": "擁護者狀態", - "purchase_server_title": "伺服器", - "purchase_settings_server_activated": "伺服器產品金鑰是由管理者管理的", - "query_asset_id": "査詢資產 ID", - "queue_status": "處理中 {count}/{total}", - "rate_asset": "資產評星", - "rating": "評星", - "rating_clear": "清除評等", - "rating_count": "{count, plural, other {# 星}}", - "rating_description": "在資訊面板中顯示 EXIF 評等", - "rating_set": "已設定為{rating, plural, one {# 星} other {# 星}}", - "reaction_options": "反應選項", - "read_changelog": "閱覽變更日誌", - "readonly_mode_disabled": "唯讀模式已關閉", - "readonly_mode_enabled": "唯讀模式已開啟", - "ready_for_upload": "已準備好上傳", - "reassign": "重新指定", - "reassigned_assets_to_existing_person": "已將 {count, plural, other {# 個檔案}}重新指定給{name, select, null {現有的人} other {{name}}}", - "reassigned_assets_to_new_person": "已將 {count, plural, other {# 個檔案}}重新指定給一位新人物", - "reassing_hint": "將選定的檔案分配給己存在的人物", - "recent": "最近", - "recent-albums": "最近相簿", - "recent_searches": "最近搜尋項目", - "recently_added": "近期新增", - "recently_added_page_title": "最近新增", - "recently_taken": "最近拍攝", - "recently_taken_page_title": "最近拍攝", - "refresh": "重新整理", - "refresh_encoded_videos": "重新整理已編碼的影片", - "refresh_faces": "重整面部資料", - "refresh_metadata": "重新整理中繼資料", - "refresh_thumbnails": "重新整理縮圖", - "refreshed": "重新整理完畢", - "refreshes_every_file": "重新讀取現有的所有檔案和新檔案", - "refreshing_encoded_video": "正在重新整理已編碼的影片", - "refreshing_faces": "重整面部資料中", - "refreshing_metadata": "正在重新整理中繼資料", - "regenerating_thumbnails": "重新產生縮圖中", - "remote": "遠端", - "remote_assets": "遠端項目", - "remote_media_summary": "遠端媒體摘要", - "remove": "移除", - "remove_assets_album_confirmation": "確定要從相簿中移除 {count, plural, other {# 個檔案}}嗎?", - "remove_assets_shared_link_confirmation": "確定刪除共享連結中{count, plural, other {# 個項目}}嗎?", - "remove_assets_title": "移除檔案?", - "remove_custom_date_range": "移除自訂日期範圍", - "remove_deleted_assets": "移除離線檔案", - "remove_from_album": "從相簿中移除", - "remove_from_album_action_prompt": "已從相簿中移除了 {count} 個項目", - "remove_from_favorites": "從收藏中移除", - "remove_from_lock_folder_action_prompt": "已從鎖定的資料夾中移除了 {count} 個項目", - "remove_from_locked_folder": "從鎖定的資料夾中移除", - "remove_from_locked_folder_confirmation": "您確定要將這些照片和影片移出鎖定的資料夾嗎?這些內容將會顯示在您的相簿中。", - "remove_from_shared_link": "從共享連結中移除", - "remove_memory": "移除記憶", - "remove_photo_from_memory": "將圖片從此記憶中移除", - "remove_tag": "移除標籤", - "remove_url": "移除 URL", - "remove_user": "移除使用者", - "removed_api_key": "已移除 API 金鑰:{name}", - "removed_from_archive": "從封存中移除", - "removed_from_favorites": "已從收藏中移除", - "removed_from_favorites_count": "已移除收藏的 {count, plural, other {# 個項目}}", - "removed_memory": "已移除記憶", - "removed_photo_from_memory": "已從記憶中移除照片", - "removed_tagged_assets": "已移除 {count, plural, one {# 個檔案} other {# 個檔案}}的標籤", - "rename": "改名", - "repair": "糾正", - "repair_no_results_message": "未被追蹤及未處理的檔案會顯示在這裡", - "replace_with_upload": "用上傳的檔案取代", - "repository": "儲存庫", - "require_password": "需要密碼", - "require_user_to_change_password_on_first_login": "要求使用者在首次登入時變更密碼", - "rescan": "重新掃描", - "reset": "重設", - "reset_password": "重設密碼", - "reset_people_visibility": "重設人物可見性", - "reset_pin_code": "重設 PIN 碼", - "reset_pin_code_description": "若忘記了 PIN 碼,閣下可要求系統伺服器管理員為您重設", - "reset_pin_code_success": "閣下已成功重設 PIN 碼", - "reset_pin_code_with_password": "您可隨時使用您的密碼來重設 PIN 碼", - "reset_sqlite": "重設 SQLite 資料庫", - "reset_sqlite_confirmation": "確定要重設 SQLite 資料庫嗎?閣下需登出並重新登入才能重新同步資料", - "reset_sqlite_success": "已成功重設 SQLite 資料庫", - "reset_to_default": "重設回預設", - "resolution": "分辯率", - "resolve_duplicates": "解決重複項", - "resolved_all_duplicates": "已解決所有重複項目", - "restore": "還原", - "restore_all": "全部還原", - "restore_trash_action_prompt": "已從垃圾桶復原了 {count} 個項目", - "restore_user": "還原使用者", - "restored_asset": "已還原檔案", - "resume": "繼續", - "resume_paused_jobs": "恢復 {count, plural, one {# 暫停的任務} other {# 暫停的任務}}", - "retry_upload": "重新上傳", - "review_duplicates": "檢視重複項目", - "review_large_files": "檢視大型檔案", - "role": "角色", - "role_editor": "編輯者", - "role_viewer": "檢視者", - "running": "執行中", - "save": "儲存", - "save_to_gallery": "儲存到相簿", - "saved": "已保存", - "saved_api_key": "已儲存 API 金鑰", - "saved_profile": "已儲存個人資料", - "saved_settings": "已儲存設定", - "say_something": "說說您的想法吧", - "scaffold_body_error_occurred": "發生錯誤", - "scan": "掃描", - "scan_all_libraries": "掃描所有相簿", - "scan_library": "掃描", - "scan_settings": "掃描設定", - "scanning": "正在掃描", - "scanning_for_album": "掃描相簿中……", - "search": "搜尋", - "search_albums": "搜尋相簿", - "search_by_context": "以情境搜尋", - "search_by_description": "以描述搜尋", - "search_by_description_example": "在沙壩的健行之日", - "search_by_filename": "以檔名或副檔名搜尋", - "search_by_filename_example": "如 IMG_1234.JPG 或 PNG", - "search_by_ocr": "通過OCR蒐索", - "search_by_ocr_example": "拿鐵", - "search_camera_lens_model": "蒐索鏡頭型號...", - "search_camera_make": "搜尋相機製造商…", - "search_camera_model": "搜尋相機型號…", - "search_city": "搜尋城市…", - "search_country": "搜尋國家…", - "search_filter_apply": "套用篩選", - "search_filter_camera_title": "選擇相機類型", - "search_filter_date": "日期", - "search_filter_date_interval": "從 {start} 到 {end}", - "search_filter_date_title": "選擇日期範圍", - "search_filter_display_option_not_in_album": "不在相簿中", - "search_filter_display_options": "顯示選項", - "search_filter_filename": "依檔案名稱搜尋", - "search_filter_location": "位置", - "search_filter_location_title": "選擇位置", - "search_filter_media_type": "媒體類型", - "search_filter_media_type_title": "選擇媒體類型", - "search_filter_ocr": "通過OCR蒐索", - "search_filter_people_title": "選擇人物", - "search_for": "搜尋", - "search_for_existing_person": "搜尋現有的人物", - "search_no_more_result": "無更多結果", - "search_no_people": "沒有人找到", - "search_no_people_named": "沒有名為「{name}」的人物", - "search_no_result": "找不到結果,請嘗試其他搜尋字詞或組合", - "search_options": "搜尋選項", - "search_page_categories": "類別", - "search_page_motion_photos": "動態照片", - "search_page_no_objects": "找不到物件資訊", - "search_page_no_places": "找不到地點資訊", - "search_page_screenshots": "螢幕截圖", - "search_page_search_photos_videos": "搜尋您的照片與影片", - "search_page_selfies": "自拍", - "search_page_things": "事物", - "search_page_view_all_button": "檢視全部", - "search_page_your_activity": "您的活動", - "search_page_your_map": "您的足跡", - "search_people": "搜尋人物", - "search_places": "搜尋地點", - "search_rating": "按評分搜尋...", - "search_result_page_new_search_hint": "搜尋新的", - "search_settings": "搜尋設定", - "search_state": "搜尋地區…", - "search_suggestion_list_smart_search_hint_1": "智慧搜尋功能預設已啟用,如要搜尋中繼資料,請使用語法 ", - "search_suggestion_list_smart_search_hint_2": "m:您的搜尋關鍵詞", - "search_tags": "搜尋標籤...", - "search_timezone": "搜尋時區…", - "search_type": "搜尋類型", - "search_your_photos": "搜尋照片", - "searching_locales": "搜尋區域…", - "second": "秒", - "see_all_people": "檢視所有人物", - "select": "選擇", - "select_album": "選擇相册", - "select_album_cover": "選擇相簿封面", - "select_albums": "選擇相册", - "select_all": "選擇全部", - "select_all_duplicates": "保留所有重複項", - "select_all_in": "選擇在 {group} 中的所有項目", - "select_avatar_color": "選擇個人資料圖片顏色", - "select_count": "{count, plural, one {選擇 #} other {選擇 #}}", - "select_face": "選擇臉孔", - "select_featured_photo": "選擇特色照片", - "select_from_computer": "從電腦中選取", - "select_keep_all": "全部保留", - "select_library_owner": "選擇相簿擁有者", - "select_new_face": "選擇新臉孔", - "select_people": "選擇人員", - "select_person": "選擇人員", - "select_person_to_tag": "選擇要標記的人物", - "select_photos": "選照片", - "select_trash_all": "全部刪除", - "select_user_for_sharing_page_err_album": "新增相簿失敗", - "selected": "已選擇", - "selected_count": "{count, plural, other {選了 # 項}}", - "selected_gps_coordinates": "選定的 GPS 座標", - "send_message": "傳訊息", - "send_welcome_email": "傳送歡迎電子郵件", - "server_endpoint": "伺服器端點", - "server_info_box_app_version": "App 版本", - "server_info_box_server_url": "伺服器網址", - "server_offline": "伺服器已離線", - "server_online": "伺服器已上線", - "server_privacy": "伺服器隱私", - "server_restarting_description": "此頁面將立即重繪。", - "server_restarting_title": "服務器正在重新啟動", - "server_stats": "伺服器統計", - "server_update_available": "服務器更新可用", - "server_version": "目前版本", - "set": "設定", - "set_as_album_cover": "設為相簿封面", - "set_as_featured_photo": "設為特色照片", - "set_as_profile_picture": "設為個人資料圖片", - "set_date_of_birth": "設定出生日期", - "set_profile_picture": "設定個人資料圖片", - "set_slideshow_to_fullscreen": "以全螢幕放映幻燈片", - "set_stack_primary_asset": "設定堆疊的首要項目", - "setting_image_viewer_help": "詳細資訊檢視器首先載入小縮圖,然後載入中等大小的預覽圖(若啟用),最後載入原始圖片。", - "setting_image_viewer_original_subtitle": "啟用以載入原圖,停用以減少資料使用量(包括網路和裝置快取)。", - "setting_image_viewer_original_title": "載入原圖", - "setting_image_viewer_preview_subtitle": "啟用以載入中等品質的圖片,停用以載入原圖或縮圖。", - "setting_image_viewer_preview_title": "載入預覽圖", - "setting_image_viewer_title": "圖片", - "setting_languages_apply": "套用", - "setting_languages_subtitle": "變更應用程式的語言", - "setting_notifications_notify_failures_grace_period": "背景備份失敗通知:{duration}", - "setting_notifications_notify_hours": "{count} 小時", - "setting_notifications_notify_immediately": "立即", - "setting_notifications_notify_minutes": "{count} 分鐘", - "setting_notifications_notify_never": "從不", - "setting_notifications_notify_seconds": "{count} 秒", - "setting_notifications_single_progress_subtitle": "每項的詳細上傳進度資訊", - "setting_notifications_single_progress_title": "顯示背景備份詳細進度", - "setting_notifications_subtitle": "調整通知選項", - "setting_notifications_total_progress_subtitle": "總體上傳進度 (已完成/總計)", - "setting_notifications_total_progress_title": "顯示背景備份總進度", - "setting_video_viewer_auto_play_subtitle": "打開視頻時自動開始播放", - "setting_video_viewer_auto_play_title": "自動播放視頻", - "setting_video_viewer_looping_title": "迴圈播放", - "setting_video_viewer_original_video_subtitle": "從伺服器串流影片時,優先播放原始畫質(即使有轉檔的版本可用)。這可能會導致播放時出現緩衝情況。若影片已儲存在本機,則一律以原始畫質播放,與此設定無關。", - "setting_video_viewer_original_video_title": "一律播放原始影片", - "settings": "設定", - "settings_require_restart": "請重啟 Immich 以使設定生效", - "settings_saved": "設定已儲存", - "setup_pin_code": "設定 PIN 碼", - "share": "分享", - "share_action_prompt": "已分享了 {count} 個項目", - "share_add_photos": "新增項目", - "share_assets_selected": "{count} 已選擇", - "share_dialog_preparing": "正在準備...", - "share_link": "分享連結", - "shared": "共享", - "shared_album_activities_input_disable": "已停用評論", - "shared_album_activity_remove_content": "您確定要刪除此活動嗎?", - "shared_album_activity_remove_title": "刪除活動", - "shared_album_section_people_action_error": "結束/刪除相簿失敗", - "shared_album_section_people_action_leave": "從相簿中刪除使用者", - "shared_album_section_people_action_remove_user": "從相簿中刪除使用者", - "shared_album_section_people_title": "人物", - "shared_by": "共享自", - "shared_by_user": "由 {user} 分享", - "shared_by_you": "由您分享", - "shared_from_partner": "來自 {partner} 的照片", - "shared_intent_upload_button_progress_text": "{current} / {total} 已上傳", - "shared_link_app_bar_title": "共享連結", - "shared_link_clipboard_copied_massage": "複製到剪貼簿", - "shared_link_clipboard_text": "連結: {link}\n密碼: {password}", - "shared_link_create_error": "新增共享連結時發生錯誤", - "shared_link_custom_url_description": "使用自訂 URL", - "shared_link_edit_description_hint": "編輯共享描述", - "shared_link_edit_expire_after_option_day": "1 天", - "shared_link_edit_expire_after_option_days": "{count} 天", - "shared_link_edit_expire_after_option_hour": "1 小時", - "shared_link_edit_expire_after_option_hours": "{count} 小時", - "shared_link_edit_expire_after_option_minute": "1 分鐘", - "shared_link_edit_expire_after_option_minutes": "{count} 分鐘", - "shared_link_edit_expire_after_option_months": "{count} 個月", - "shared_link_edit_expire_after_option_year": "{count} 年", - "shared_link_edit_password_hint": "輸入共享密碼", - "shared_link_edit_submit_button": "更新連結", - "shared_link_error_server_url_fetch": "無法取得伺服器網址", - "shared_link_expires_day": "{count} 天後過期", - "shared_link_expires_days": "{count} 天後過期", - "shared_link_expires_hour": "{count} 小時後過期", - "shared_link_expires_hours": "{count} 小時後過期", - "shared_link_expires_minute": "{count} 分鐘後過期", - "shared_link_expires_minutes": "將在 {count} 分鐘後過期", - "shared_link_expires_never": "永不過期", - "shared_link_expires_second": "將在 {count} 秒後過期", - "shared_link_expires_seconds": "將在 {count} 秒後過期", - "shared_link_individual_shared": "個人共享", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "管理共享連結", - "shared_link_options": "共享連結選項", - "shared_link_password_description": "要求在存取此連結時提供密碼", - "shared_links": "共享連結", - "shared_links_description": "以連結分享照片和影片", - "shared_photos_and_videos_count": "{assetCount, plural, other {已分享 # 張照片及影片。}}", - "shared_with_me": "與我共享", - "shared_with_partner": "與 {partner} 共享", - "sharing": "共享", - "sharing_enter_password": "要檢視此頁面請輸入密碼。", - "sharing_page_album": "共享相簿", - "sharing_page_description": "新增共享相簿以與網路中的人共享照片和短片。", - "sharing_page_empty_list": "空白清單", - "sharing_sidebar_description": "在側邊欄顯示共享連結", - "sharing_silver_appbar_create_shared_album": "新增共享相簿", - "sharing_silver_appbar_share_partner": "共享給親朋好友", - "shift_to_permanent_delete": "按 ⇧ 永久刪除檔案", - "show_album_options": "顯示相簿選項", - "show_albums": "顯示相簿", - "show_all_people": "顯示所有人物", - "show_and_hide_people": "顯示與隱藏人物", - "show_file_location": "顯示檔案位置", - "show_gallery": "顯示畫廊", - "show_hidden_people": "顯示隱藏的人物", - "show_in_timeline": "在時間軸中顯示", - "show_in_timeline_setting_description": "在您的時間軸中顯示這位使用者的照片和影片", - "show_keyboard_shortcuts": "顯示鍵盤快捷鍵", - "show_metadata": "顯示中繼資料", - "show_or_hide_info": "顯示或隱藏資訊", - "show_password": "顯示密碼", - "show_person_options": "顯示人物選項", - "show_progress_bar": "顯示進度條", - "show_schema": "顯示架構", - "show_search_options": "顯示搜尋選項", - "show_shared_links": "顯示共享連結", - "show_slideshow_transition": "顯示幻燈片轉場", - "show_supporter_badge": "擁護者徽章", - "show_supporter_badge_description": "顯示擁護者徽章", - "show_text_recognition": "顯示文字識別", - "show_text_search_menu": "顯示文字蒐索選單", - "shuffle": "隨機排序", - "sidebar": "側邊欄", - "sidebar_display_description": "在側邊欄中顯示連結", - "sign_out": "登出", - "sign_up": "註冊", - "size": "用量", - "skip_to_content": "跳至內容", - "skip_to_folders": "跳到資料夾", - "skip_to_tags": "跳轉到標籤", - "slideshow": "幻燈片", - "slideshow_settings": "幻燈片設定", - "sort_albums_by": "相簿排序依據...", - "sort_created": "建立日期", - "sort_items": "項目數量", - "sort_modified": "日期已修改", - "sort_newest": "最新的相片", - "sort_oldest": "最舊的照片", - "sort_people_by_similarity": "按相似度排序人員", - "sort_recent": "最新的照片", - "sort_title": "標題", - "source": "來源", - "stack": "堆疊", - "stack_action_prompt": "已堆疊了{count} 個項目", - "stack_duplicates": "堆疊重複項目", - "stack_select_one_photo": "為堆疊選一張主要照片", - "stack_selected_photos": "堆疊所選的照片", - "stacked_assets_count": "已堆疊 {count, plural, one {# 個檔案} other {# 個檔案}}", - "stacktrace": "堆疊追蹤", - "start": "開始", - "start_date": "開始日期", - "start_date_before_end_date": "開始日期必須早於結束日期", - "state": "地區", - "status": "狀態", - "stop_casting": "停止投放", - "stop_motion_photo": "停止動態照片", - "stop_photo_sharing": "要停止分享您的照片嗎?", - "stop_photo_sharing_description": "{partner} 將無法再存取您的照片。", - "stop_sharing_photos_with_user": "停止與此使用者共享您的照片", - "storage": "儲存空間", - "storage_label": "儲存標籤", - "storage_quota": "儲存空間", - "storage_usage": "用了 {used} / 共 {available}", - "submit": "提交", - "success": "成功", - "suggestions": "建議", - "sunrise_on_the_beach": "日出的海灘", - "support": "支援", - "support_and_feedback": "支援與回饋", - "support_third_party_description": "您安裝的 Immich 是由第三方打包的。您遇到的問題可能是該套件造成的,所以請先使用下面的連結向他們提出問題。", - "swap_merge_direction": "交換合併方向", - "sync": "同步", - "sync_albums": "同步相簿", - "sync_albums_manual_subtitle": "將所有上傳的短片和照片同步到選定的備份相簿", - "sync_local": "同步本機", - "sync_remote": "同步遠端", - "sync_status": "同步狀態", - "sync_status_subtitle": "檢視和管理同步系統", - "sync_upload_album_setting_subtitle": "新增照片和短片並上傳到 Immich 上的選定相簿中", - "tag": "標籤", - "tag_assets": "標記檔案", - "tag_created": "已建立標籤:{tag}", - "tag_feature_description": "以邏輯標記要旨分類瀏覽照片和影片", - "tag_not_found_question": "找不到標籤?建立新標籤。", - "tag_people": "標籤人物", - "tag_updated": "已更新標籤:{tag}", - "tagged_assets": "已標籤 {count, plural, one {# 個檔案} other {# 個檔案}}", - "tags": "標籤", - "tap_to_run_job": "點選以進行作業", - "template": "模板", - "text_recognition": "文字識別", - "theme": "主題", - "theme_selection": "主題選項", - "theme_selection_description": "依瀏覽器系統偏好自動設定深、淺色主題", - "theme_setting_asset_list_storage_indicator_title": "在項目標題上顯示使用之儲存空間", - "theme_setting_asset_list_tiles_per_row_title": "每行展示 {count} 項", - "theme_setting_colorful_interface_subtitle": "套用主色調到背景。", - "theme_setting_colorful_interface_title": "彩色介面", - "theme_setting_image_viewer_quality_subtitle": "調整檢視大圖時的圖片品質", - "theme_setting_image_viewer_quality_title": "圖片品質", - "theme_setting_primary_color_subtitle": "選擇顏色作為主色調。", - "theme_setting_primary_color_title": "主色調", - "theme_setting_system_primary_color_title": "使用系統顏色", - "theme_setting_system_theme_switch": "自動(跟隨系統設定)", - "theme_setting_theme_subtitle": "選擇套用主題", - "theme_setting_three_stage_loading_subtitle": "三段式載入可能提升載入效能,但會大幅增加網路負載", - "theme_setting_three_stage_loading_title": "啟用三段式載入", - "they_will_be_merged_together": "它們將會被合併在一起", - "third_party_resources": "第三方資源", - "time": "時間", - "time_based_memories": "依時間回憶", - "time_based_memories_duration": "顯示每張影像的秒數。", - "timeline": "時間軸", - "timezone": "時區", - "to_archive": "封存", - "to_change_password": "變更密碼", - "to_favorite": "收藏", - "to_login": "登入", - "to_multi_select": "進行多選", - "to_parent": "到上一級", - "to_select": "選擇", - "to_trash": "垃圾桶", - "toggle_settings": "切換設定", - "toggle_theme_description": "切換主題", - "total": "統計", - "total_usage": "總用量", - "trash": "垃圾桶", - "trash_action_prompt": "{count} 個丟入垃圾桶", - "trash_all": "全部丟掉", - "trash_count": "丟掉 {count, number} 個檔案", - "trash_delete_asset": "將檔案丟進垃圾桶 / 刪除", - "trash_emptied": "已清空回收桶", - "trash_no_results_message": "垃圾桶中的照片和影片將顯示在這裡。", - "trash_page_delete_all": "刪除全部", - "trash_page_empty_trash_dialog_content": "是否清空回收桶?這些項目將被從 Immich 中永久刪除", - "trash_page_info": "回收桶中項目將在 {days} 天後永久刪除", - "trash_page_no_assets": "暫無已刪除項目", - "trash_page_restore_all": "恢復全部", - "trash_page_select_assets_btn": "選擇項目", - "trash_page_title": "垃圾桶 ({count})", - "trashed_items_will_be_permanently_deleted_after": "垃圾桶中的項目會在 {days, plural, other {# 天}}後永久刪除。", - "trigger": "觸發", - "trigger_asset_uploaded": "資產已上傳", - "trigger_asset_uploaded_description": "上傳新資產時觸發", - "trigger_description": "啟動工作流的事件", - "trigger_person_recognized": "被認可的人", - "trigger_person_recognized_description": "當檢測到有人時觸發", - "trigger_type": "觸發類型", - "troubleshoot": "疑難解答", - "type": "類型", - "unable_to_change_pin_code": "無法變更 PIN 碼", - "unable_to_check_version": "無法檢查應用程式或伺服器版本", - "unable_to_setup_pin_code": "無法設定 PIN 碼", - "unarchive": "取消封存", - "unarchive_action_prompt": "已從封存的檔案中移除了 {count} 個項目", - "unarchived_count": "{count, plural, other {已取消封存 # 個項目}}", - "undo": "還原", - "unfavorite": "取消收藏", - "unfavorite_action_prompt": "{count} 個移除收藏", - "unhide_person": "取消隱藏人物", - "unknown": "未知", - "unknown_country": "未知國家", - "unknown_date": "未知的日期", - "unknown_year": "未知年份", - "unlimited": "不限制", - "unlink_motion_video": "解除連結動態影片", - "unlink_oauth": "解除連結 OAuth", - "unlinked_oauth_account": "已解除連結 OAuth 帳號", - "unmute_memories": "取消靜音回憶", - "unnamed_album": "未命名相簿", - "unnamed_album_delete_confirmation": "確定要刪除這本相簿嗎?", - "unnamed_share": "未命名分享", - "unsaved_change": "變更未儲存", - "unselect_all": "取消全選", - "unselect_all_duplicates": "取消選取所有的重複項目", - "unselect_all_in": "{group} 全不選", - "unstack": "取消堆疊", - "unstack_action_prompt": "{count} 個取消堆疊", - "unstacked_assets_count": "已解除堆疊 {count, plural, other {# 個檔案}}", - "unsupported_field_type": "不支持的欄位類型", - "untagged": "無標籤", - "untitled_workflow": "無標題工作流", - "up_next": "下一個", - "update_location_action_prompt": "使用以下命令更新{count}個所選資產的位置:", - "updated_at": "更新於", - "updated_password": "已更新密碼", - "upload": "上傳", - "upload_concurrency": "上傳並行", - "upload_details": "上傳詳細資訊", - "upload_dialog_info": "是否要將所選項目備份到伺服器?", - "upload_dialog_title": "上傳項目", - "upload_errors": "上傳完成,但有 {count, plural, other {# 處時發生錯誤}},要檢視新上傳的檔案請重新整理頁面。", - "upload_finished": "上傳完成", - "upload_progress": "剩餘 {remaining, number} - 已處理 {processed, number}/{total, number}", - "upload_skipped_duplicates": "已略過 {count, plural, other {# 個重複的檔案}}", - "upload_status_duplicates": "重複項目", - "upload_status_errors": "錯誤", - "upload_status_uploaded": "已上傳", - "upload_success": "上傳成功,要檢視新上傳的檔案請重新整理頁面。", - "upload_to_immich": "上傳至 Immich ({count})", - "uploading": "上傳中", - "uploading_media": "媒體上傳中", - "url": "網址", - "usage": "用量", - "use_biometric": "使用生物辨識", - "use_current_connection": "使用目前的連線", - "use_custom_date_range": "改用自訂日期範圍", - "user": "使用者", - "user_has_been_deleted": "此使用者已被刪除。", - "user_id": "使用者 ID", - "user_liked": "{user} 喜歡了 {type, select, photo {這張照片} video {這段影片} asset {這個檔案} other {它}}", - "user_pin_code_settings": "PIN 碼", - "user_pin_code_settings_description": "管理您的 PIN 碼", - "user_privacy": "使用者隱私", - "user_purchase_settings": "購置", - "user_purchase_settings_description": "管理您的購買", - "user_role_set": "設 {user} 為{role}", - "user_usage_detail": "使用者用量詳細資訊", - "user_usage_stats": "帳號使用量統計", - "user_usage_stats_description": "檢視帳號使用量", - "username": "使用者名稱", - "users": "admin", - "users_added_to_album_count": "已在此相簿中新增了 {count, plural, one {# 個} other {# 個}} 使用者", - "utilities": "工具", - "validate": "驗證", - "validate_endpoint_error": "請輸入有效的 URL", - "validation_error": "驗證錯誤", - "variables": "變數", - "version": "版本", - "version_announcement_closing": "敬祝順心,Alex", - "version_announcement_message": "嗨~新版本的 Immich 推出了。為防止設定發生錯誤,請花點時間閱讀發行說明,並確保設定是最新的,特別是使用 WatchTower 等自動更新工具時。", - "version_history": "版本紀錄", - "version_history_item": "{date} 安裝了 {version}", - "video": "影片", - "video_hover_setting": "遊標停留時播放影片縮圖", - "video_hover_setting_description": "當滑鼠停在項目上時播放影片縮圖。即使停用,將滑鼠停在播放圖示上也可以播放。", - "videos": "影片", - "videos_count": "{count, plural, other {# 部影片}}", - "videos_only": "只允許影片", - "view": "檢視", - "view_album": "檢視相簿", - "view_all": "瀏覽全部", - "view_all_users": "檢視所有使用者", - "view_asset_owners": "查看資產所有者", - "view_details": "檢視詳細資訊", - "view_in_timeline": "在時間軸中檢視", - "view_link": "檢視連結", - "view_links": "檢視連結", - "view_name": "檢視分類", - "view_next_asset": "檢視下一項", - "view_previous_asset": "檢視上一項", - "view_qr_code": "檢視 QR code", - "view_similar_photos": "檢視相似照片", - "view_stack": "檢視堆疊", - "view_user": "顯示使用者", - "viewer_remove_from_stack": "從堆疊中移除", - "viewer_stack_use_as_main_asset": "作為主項目使用", - "viewer_unstack": "取消堆疊", - "visibility_changed": "已變更 {count, plural, other {# 位人物}}的可見性", - "visual": "視覺的", - "visual_builder": "視覺構建器", - "waiting": "待處理", - "waiting_count": "待處理:{count}", - "warning": "警告", - "week": "週", - "welcome": "歡迎", - "welcome_to_immich": "歡迎使用 Immich", - "width": "寬度", - "wifi_name": "Wi-Fi 名稱", - "workflow_delete_prompt": "您確定要删除此工作流嗎?", - "workflow_deleted": "工作流已删除", - "workflow_description": "工作流描述", - "workflow_info": "工作流資訊", - "workflow_json": "工作流程JSON", - "workflow_json_help": "以JSON格式編輯工作流配寘。 更改將同步到視覺化構建器。", - "workflow_name": "工作流名稱", - "workflow_navigation_prompt": "您確定不保存更改就離開嗎?", - "workflow_summary": "工作流摘要", - "workflow_update_success": "工作流已成功更新", - "workflow_updated": "工作流已更新", - "workflows": "工作流", - "workflows_help_text": "工作流根據觸發器和篩檢程式自動執行資產操作", - "wrong_pin_code": "PIN 碼錯誤", - "year": "年", - "years_ago": "{years, plural, other {# 年}}前", - "yes": "是", - "you_dont_have_any_shared_links": "您沒有任何共享連結", - "your_wifi_name": "您的 Wi-Fi 名稱", - "zero_to_clear_rating": "按0清除資產評星", - "zoom_image": "縮放圖片", - "zoom_to_bounds": "縮放到邊界" -} +{} diff --git a/i18n/zh_SIMPLIFIED.json b/i18n/zh_SIMPLIFIED.json index 23f3ea4eec..0967ef424b 100644 --- a/i18n/zh_SIMPLIFIED.json +++ b/i18n/zh_SIMPLIFIED.json @@ -1,2401 +1 @@ -{ - "about": "关于", - "account": "账号", - "account_settings": "账号设置", - "acknowledge": "已知悉", - "action": "操作", - "action_common_update": "更新", - "action_description": "针对筛选出的资源要执行的一组操作", - "actions": "操作", - "active": "进行中", - "active_count": "活动: {count}", - "activity": "活动", - "activity_changed": "活动状态{enabled, select, true {已启用} other {已禁用}}", - "add": "添加", - "add_a_description": "添加描述", - "add_a_location": "添加位置", - "add_a_name": "添加人名", - "add_a_title": "添加标题", - "add_action": "添加操作", - "add_action_description": "点击以添加要执行的操作", - "add_assets": "添加资源", - "add_birthday": "添加生日", - "add_endpoint": "添加端点", - "add_exclusion_pattern": "添加排除规则", - "add_filter": "添加筛选条件", - "add_filter_description": "点击添加筛选条件", - "add_location": "添加位置", - "add_more_users": "添加更多用户", - "add_partner": "添加协作者", - "add_path": "添加路径", - "add_photos": "添加照片", - "add_tag": "添加标签", - "add_to": "添加到…", - "add_to_album": "添加到相册", - "add_to_album_bottom_sheet_added": "已添加至 {album}", - "add_to_album_bottom_sheet_already_exists": "已在 {album} 中", - "add_to_album_bottom_sheet_some_local_assets": "部分本地资源无法添加到相册", - "add_to_album_toggle": "切换 {album} 的选中状态", - "add_to_albums": "添加到相册", - "add_to_albums_count": "添加到相册 ({count})", - "add_to_bottom_bar": "添加到", - "add_to_shared_album": "添加到共享相册", - "add_upload_to_stack": "添加上传至堆栈", - "add_url": "添加 URL", - "add_workflow_step": "添加工作流步骤", - "added_to_archive": "添加至存档", - "added_to_favorites": "已添加到收藏", - "added_to_favorites_count": "已将 {count, number} 项添加到收藏", - "admin": { - "add_exclusion_pattern_description": "添加排除模式(支持  * ,  ** ,  ?  通配符)。例如:忽略 \"Raw\" 目录请用  \"**/Raw/**\" ;忽略 \".tif\" 文件请用  \"**/*.tif\" ;忽略绝对路径请用  \"/path/to/ignore/**\" 。", - "admin_user": "管理员用户", - "asset_offline_description": "未找到该外部资产库文件,已将其移至回收站。如果文件是在库内被移动,请在时间线中查找对应的新资产。如需恢复此资产,请确保 Immich 可访问下方的文件路径,并重新扫描该资产库。", - "authentication_settings": "认证设置", - "authentication_settings_description": "管理密码、OAuth 和其它认证设置", - "authentication_settings_disable_all": "您确定要禁用所有登录方式吗?登录功能将完全失效。", - "authentication_settings_reenable": "如需重新启用,请使用 服务器命令。", - "background_task_job": "后台任务", - "backup_database": "创建数据库备份", - "backup_database_enable_description": "启用数据库备份", - "backup_keep_last_amount": "保留的历史备份数量", - "backup_onboarding_1_description": "异地备份,例如存储在云端或另一个物理位置。", - "backup_onboarding_2_description": "本地多设备副本。即在不同设备上保存主文件及其本地备份。", - "backup_onboarding_3_description": "数据的总副本数,包含原始文件。例如:1 份异地备份和 2 份本地副本。", - "backup_onboarding_description": "建议采用 3-2-1 备份策略 来保护您的数据。为了实现全面的备份方案,您应当保存上传的照片/视频副本以及 Immich 数据库。", - "backup_onboarding_footer": "有关备份 Immich 的更多信息,请参阅 文档。", - "backup_onboarding_parts_title": "3-2-1 备份策略包括:", - "backup_onboarding_title": "备份", - "backup_settings": "数据库备份设置", - "backup_settings_description": "管理数据库备份设置。", - "cleared_jobs": "已清除 {job} 的任务", - "config_set_by_file": "当前配置由配置文件设定", - "confirm_delete_library": "确定要删除资产库 \"{library}\" 吗?", - "confirm_delete_library_assets": "确定要删除此资产库吗?此操作将从 Immich 中删除 {count, plural, one {# 个关联资产} other {全部 # 个关联资产}},且无法撤销。注意:文件仍将保留在磁盘上。", - "confirm_email_below": "为确认操作,请在下方输入 \"{email}\"", - "confirm_reprocess_all_faces": "确定要重新处理所有人脸吗?此操作将清除已命名的人物。", - "confirm_user_password_reset": "确定要重置 {user} 的密码吗?", - "confirm_user_pin_code_reset": "确定要重置 {user} 的 PIN 码吗?", - "copy_config_to_clipboard_description": "将当前系统配置作为 JSON 对象复制到剪贴板", - "create_job": "创建任务", - "cron_expression": "Cron 表达式", - "cron_expression_description": "使用 Cron 格式设置扫描间隔。更多信息请参考 Crontab Guru 等网站", - "cron_expression_presets": "Cron 表达式预设", - "disable_login": "禁用登录", - "duplicate_detection_job_description": "运行机器学习来检测相似图像,此功能依赖于智能搜索", - "exclusion_pattern_description": "排除规则允许您在扫描资产库时忽略特定的文件和文件夹。如果您有某些包含不希望导入的文件(例如 RAW 格式文件)的文件夹,此功能将非常有用。", - "export_config_as_json_description": "将当前系统配置下载为 JSON 文件", - "external_libraries_page_description": "管理外部资产库", - "face_detection": "人脸识别", - "face_detection_description": "使用机器学习检测资源中的人脸,对于视频仅处理其缩略图;“刷新”会重新处理所有资源,“重置”会清除所有当前的人脸数据,“缺失”则仅将尚未处理的资源加入队列;人脸检测完成后,检测到的人脸将自动加入人物识别队列,系统会将其归入现有或新建的人物分组中。", - "facial_recognition_job_description": "将检测到的人脸归类为不同的人物。此步骤在“人脸检测”完成后运行。“重置”会(重新)聚类所有人脸。“缺失”则将尚未分配人物的人脸加入队列。", - "failed_job_command": "命令 {command} 在执行任务 {job} 时失败", - "force_delete_user_warning": "警告:此操作将立即删除该用户及其所有资源。此操作不可撤销,且文件无法恢复。", - "image_format": "格式", - "image_format_description": "WebP 格式的文件体积比 JPEG 更小,但编码速度较慢。", - "image_fullsize_description": "已剥离元数据的全尺寸图像,放大查看时使用", - "image_fullsize_enabled": "启用全尺寸图像生成", - "image_fullsize_enabled_description": "为非网页友好格式生成全尺寸图像。启用“优先使用嵌入式预览”后,将直接使用嵌入式预览而无需转换。此设置不影响 JPEG 等网页友好格式。", - "image_fullsize_quality_description": "全尺寸图像质量(1-100)。数值越高画质越好,但生成的文件也越大。", - "image_fullsize_title": "全尺寸图像设置", - "image_prefer_embedded_preview": "优先使用嵌入式预览", - "image_prefer_embedded_preview_setting_description": "使用 RAW 照片中的嵌入式预览作为图像处理的源文件(如果存在)。这能为部分图像生成更准确的色彩,但预览图的质量取决于相机,且图像可能包含更多的压缩伪影。", - "image_prefer_wide_gamut": "优先使用广色域", - "image_prefer_wide_gamut_setting_description": "缩略图使用 Display P3 色彩空间。这能更好地保留广色域图像的色彩鲜艳度,但在使用旧版浏览器的老旧设备上,图像显示效果可能有所不同。sRGB 图像将保持为 sRGB,以避免色彩偏移。", - "image_preview_description": "已剥离元数据的中等尺寸图像,用于查看单个资产时以及机器学习功能", - "image_preview_quality_description": "预览图质量(1-100)。数值越高画质越好,但生成的文件越大,且可能降低应用响应速度。设置过低的数值可能影响机器学习(识别)的准确度。", - "image_preview_title": "预览设置", - "image_progressive": "逐步", - "image_progressive_description": "对 JPEG 图像进行逐步编码,以实现渐进式加载显示。这不会影响 WebP 图像。", - "image_quality": "质量", - "image_resolution": "分辨率", - "image_resolution_description": "较高的分辨率能保留更多图像细节,但编码时间更长、生成的文件更大,且可能导致应用响应变慢。", - "image_settings": "图像设置", - "image_settings_description": "管理生成图像的质量和分辨率", - "image_thumbnail_description": "已剥离元数据的小型缩略图,用于查看主时间线等照片组时显示", - "image_thumbnail_quality_description": "缩略图质量(1-100)。数值越高画质越好,但生成的文件越大,且可能降低应用响应速度。", - "image_thumbnail_title": "缩略图设置", - "import_config_from_json_description": "通过上传 JSON 配置文件导入系统配置", - "job_concurrency": "{job} 并发数", - "job_created": "任务已创建", - "job_not_concurrency_safe": "该任务不支持并发操作。", - "job_settings": "任务设置", - "job_settings_description": "管理任务并发数", - "jobs_delayed": "{jobCount, plural, other {# 个延迟}}", - "jobs_failed": "{jobCount, plural, other {# 个失败}}", - "jobs_over_time": "任务执行趋势", - "library_created": "已创建资产库:{library}", - "library_deleted": "资产库已删除", - "library_details": "资产库详情", - "library_folder_description": "指定一个导入文件夹。系统将扫描该文件夹及其所有子文件夹中的图片和视频。", - "library_remove_exclusion_pattern_prompt": "确定要移除此排除规则吗?", - "library_remove_folder_prompt": "确定要移除此导入文件夹吗?", - "library_scanning": "定期扫描", - "library_scanning_description": "配置定期扫描", - "library_scanning_enable_description": "开启定期扫描", - "library_settings": "外部资产库", - "library_settings_description": "管理外部资产库设置", - "library_tasks_description": "扫描外部资产库以获取新增/变更的资产", - "library_updated": "资产库已更新", - "library_watching_enable_description": "监控外部资产库的文件变更", - "library_watching_settings": "资产库监控 [实验性功能]", - "library_watching_settings_description": "自动监控文件变更", - "logging_enable_description": "启用日志记录", - "logging_level_description": "启用后,所采用的日志级别。", - "logging_settings": "日志", - "machine_learning_availability_checks": "可用性检查", - "machine_learning_availability_checks_description": "自动检测并优先选择可用的机器学习服务器", - "machine_learning_availability_checks_enabled": "启用可用性检查", - "machine_learning_availability_checks_interval": "检查间隔", - "machine_learning_availability_checks_interval_description": "两次可用性检查之间的时间间隔(毫秒)", - "machine_learning_availability_checks_timeout": "请求超时时间", - "machine_learning_availability_checks_timeout_description": "可用性检查的请求超时时间(毫秒)", - "machine_learning_clip_model": "CLIP 模型", - "machine_learning_clip_model_description": "在 此处 列出的 CLIP 模型名称。请注意,更改模型后,必须重新运行所有图片的“智能搜索”任务。", - "machine_learning_duplicate_detection": "重复项检测", - "machine_learning_duplicate_detection_enabled": "启用重复项检测", - "machine_learning_duplicate_detection_enabled_description": "若关闭此功能,完全相同的资源仍会被去重处理。", - "machine_learning_duplicate_detection_setting_description": "利用 CLIP 嵌入向量识别潜在的重复项", - "machine_learning_enabled": "启用机器学习", - "machine_learning_enabled_description": "若关闭此功能,所有机器学习相关特性将失效,且不受下方具体设置的影响。", - "machine_learning_facial_recognition": "人脸识别", - "machine_learning_facial_recognition_description": "检测、识别并自动归类图片中的人脸", - "machine_learning_facial_recognition_model": "人脸识别模型", - "machine_learning_facial_recognition_model_description": "模型按尺寸降序排列。较大的模型运行速度较慢且占用更多内存,但效果更好。请注意,更换模型后,必须重新运行所有图片的“人脸检测”任务。", - "machine_learning_facial_recognition_setting": "启用人脸识别", - "machine_learning_facial_recognition_setting_description": "若关闭此功能,图片将不会进行人脸识别编码,且“探索”页面的“人物”板块将无法显示内容。", - "machine_learning_max_detection_distance": "最大检测距离", - "machine_learning_max_detection_distance_description": "两张图片被视为重复项的最大距离,取值范围为 0.001 - 0.1。数值越高,检测出的重复项越多,但可能出现误判(例如将不同的人识别为同一人)。", - "machine_learning_max_recognition_distance": "最大识别距离", - "machine_learning_max_recognition_distance_description": "两张人脸被视为同一个人的最大距离,取值范围为 0 - 2。调低该值可避免将两个人误标为同一人,调高则可避免将同一个人误标为两个人。请注意,事后合并两个人物比拆分一个人物更容易,因此在可能的情况下,建议优先设置较低的阈值。", - "machine_learning_min_detection_score": "最低检测阈值", - "machine_learning_min_detection_score_description": "人脸检测的最低置信度分数,取值范围为 0-1。数值越低,检测到的人脸越多,但可能出现误判(例如将非人脸区域识别为人脸)。", - "machine_learning_min_recognized_faces": "最小识别数量", - "machine_learning_min_recognized_faces_description": "创建人物所需的最少人脸数量。调高此值可提升人脸识别的精准度,但会增加人脸无法被分配给人物的风险。", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "利用机器学习技术识别图片中的文本内容", - "machine_learning_ocr_enabled": "启用 OCR", - "machine_learning_ocr_enabled_description": "若禁用,图片将不会进行文字识别。", - "machine_learning_ocr_max_resolution": "最大分辨率", - "machine_learning_ocr_max_resolution_description": "超过此分辨率的预览图将按比例调整大小。数值越高,效果越精准,但处理时间更长且更占用内存。", - "machine_learning_ocr_min_detection_score": "最低检测阈值", - "machine_learning_ocr_min_detection_score_description": "文本检测的最低置信度分数(0-1)。数值越低,检测到的文本越多,但可能出现误判。", - "machine_learning_ocr_min_recognition_score": "最低识别阈值", - "machine_learning_ocr_min_score_recognition_description": "已检测文本的最低置信度分数(0-1)。数值越低,识别出的文本越多,但可能出现误判。", - "machine_learning_ocr_model": "OCR 模型", - "machine_learning_ocr_model_description": "服务器端模型比移动端模型更精准,但处理耗时更长且更占用内存。", - "machine_learning_settings": "机器学习设置", - "machine_learning_settings_description": "管理机器学习功能及相关设置", - "machine_learning_smart_search": "智能搜索", - "machine_learning_smart_search_description": "使用 CLIP 嵌入向量进行语义化图片搜索", - "machine_learning_smart_search_enabled": "启用智能搜索", - "machine_learning_smart_search_enabled_description": "若禁用,图片将不会被编码以用于智能搜索。", - "machine_learning_url_description": "机器学习服务器的 URL。若提供多个 URL,系统将按从前往后的顺序逐个尝试连接,直至有服务器成功响应为止。未能响应的服务器将被暂时忽略,直至其恢复在线。", - "maintenance_delete_backup": "删除备份", - "maintenance_delete_backup_description": "此文件将被永久删除。", - "maintenance_delete_error": "删除备份失败。", - "maintenance_restore_backup": "恢复备份", - "maintenance_restore_backup_description": "Immich 数据将被清除,并从选定的备份中恢复。在继续之前,将先创建一个当前数据的备份。", - "maintenance_restore_backup_different_version": "此备份是由不同版本的 Immich 创建的!", - "maintenance_restore_backup_unknown_version": "无法确定备份版本。", - "maintenance_restore_database_backup": "恢复数据库备份", - "maintenance_restore_database_backup_description": "使用备份文件将数据库回滚到较早的状态", - "maintenance_settings": "维护", - "maintenance_settings_description": "启用 Immich 维护模式。", - "maintenance_start": "切换到维护模式", - "maintenance_start_error": "维护模式启动失败。", - "maintenance_upload_backup": "上传数据库备份文件", - "maintenance_upload_backup_error": "无法上传备份,它是 .sql 或 .sql.gz 格式的文件吗?", - "manage_concurrency": "管理并发数量", - "manage_concurrency_description": "前往任务页面以管理任务并发数量", - "manage_log_settings": "管理日志设置", - "map_dark_style": "深色风格", - "map_enable_description": "启用地图功能", - "map_gps_settings": "地图与 GPS 设置", - "map_gps_settings_description": "管理地图与 GPS(逆地理编码)设置", - "map_implications": "地图功能依赖于外部瓦片服务 (tiles.immich.cloud)", - "map_light_style": "亮色风格", - "map_manage_reverse_geocoding_settings": "管理 逆地理编码 设置", - "map_reverse_geocoding": "逆地理编码", - "map_reverse_geocoding_enable_description": "启用逆地理编码", - "map_reverse_geocoding_settings": "逆地理编码设置", - "map_settings": "地图", - "map_settings_description": "管理地图设置", - "map_style_description": "style.json 地图主题的 URL", - "memory_cleanup_job": "清理回忆数据", - "memory_generate_job": "生成回忆", - "metadata_extraction_job": "提取元数据", - "metadata_extraction_job_description": "从每个资产中提取元数据信息,例如 GPS、人脸和分辨率", - "metadata_faces_import_setting": "启用人脸导入", - "metadata_faces_import_setting_description": "从图片 EXIF 数据和附带文件中导入人脸信息", - "metadata_settings": "元数据设置", - "metadata_settings_description": "管理元数据设置", - "migration_job": "迁移", - "migration_job_description": "将媒体文件和人脸的缩略图迁移到最新的文件夹结构", - "nightly_tasks_cluster_faces_setting_description": "对新检测到的人脸运行人脸识别", - "nightly_tasks_cluster_new_faces_setting": "聚类新人脸", - "nightly_tasks_database_cleanup_setting": "数据库清理任务", - "nightly_tasks_database_cleanup_setting_description": "清理数据库中过期的旧数据", - "nightly_tasks_generate_memories_setting": "生成回忆", - "nightly_tasks_generate_memories_setting_description": "基于媒体文件生成新的回忆", - "nightly_tasks_missing_thumbnails_setting": "生成缺失的缩略图", - "nightly_tasks_missing_thumbnails_setting_description": "将无缩略图的媒体文件加入队列以生成缩略图", - "nightly_tasks_settings": "每日任务设置", - "nightly_tasks_settings_description": "管理每日任务", - "nightly_tasks_start_time_setting": "开始时间", - "nightly_tasks_start_time_setting_description": "服务器开始执行每日任务的时间", - "nightly_tasks_sync_quota_usage_setting": "同步配额使用情况", - "nightly_tasks_sync_quota_usage_setting_description": "根据当前使用情况更新用户的存储配额", - "no_paths_added": "尚未添加路径", - "no_pattern_added": "尚未添加筛选规则", - "note_apply_storage_label_previous_assets": "注意:若要将存储标签应用于已上传的文件,请运行", - "note_cannot_be_changed_later": "注意:此项设置无法更改!", - "notification_email_from_address": "发件人地址", - "notification_email_from_address_description": "发件人邮箱地址,例如:“Immich 照片服务器 ”。请确保使用您有权使用的邮箱地址进行发送。", - "notification_email_host_description": "邮件服务器主机(例如:smtp.immich.app)", - "notification_email_ignore_certificate_errors": "忽略证书错误", - "notification_email_ignore_certificate_errors_description": "忽略 TLS 证书验证错误(不推荐)", - "notification_email_password_description": "连接邮件服务器进行身份验证时使用的密码", - "notification_email_port_description": "邮件服务器端口(例如:25、465 或 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "使用SMTPS(基于TLS的SMTP)", - "notification_email_sent_test_email_button": "发送测试邮件并保存", - "notification_email_setting_description": "邮件通知发送设置", - "notification_email_test_email": "发送测试邮件", - "notification_email_test_email_failed": "发送测试邮件失败,请检查您的配置信息", - "notification_email_test_email_sent": "已向 {email} 发送测试邮件,请查收。", - "notification_email_username_description": "连接邮件服务器进行身份验证时使用的用户名", - "notification_enable_email_notifications": "启用邮件通知", - "notification_settings": "通知设置", - "notification_settings_description": "管理通知设置,包括邮件通知", - "oauth_auto_launch": "自动启动", - "oauth_auto_launch_description": "进入登录页面时,自动开始 OAuth 登录流程", - "oauth_auto_register": "自动注册", - "oauth_auto_register_description": "用户通过 OAuth 登录后,自动为其注册新账户", - "oauth_button_text": "按钮文字", - "oauth_client_secret_description": "机密客户端必填,或公共客户端若不支持 PKCE(代码交换证明密钥)时必填。", - "oauth_enable_description": "使用 OAuth 登录", - "oauth_mobile_redirect_uri": "移动端重定向 URI", - "oauth_mobile_redirect_uri_override": "移动端重定向 URI 覆盖", - "oauth_mobile_redirect_uri_override_description": "当 OAuth 提供商不允许使用移动端 URI(例如 “{callback}”)时启用", - "oauth_role_claim": "角色声明", - "oauth_role_claim_description": "根据此声明的存在自动授予管理员访问权限。声明可以是“user”(用户)或“admin”(管理员)。", - "oauth_settings": "OAuth", - "oauth_settings_description": "管理 OAuth 登录设置", - "oauth_settings_more_details": "有关此功能的更多详情,请参阅 相关文档。", - "oauth_storage_label_claim": "存储标签声明", - "oauth_storage_label_claim_description": "自动将用户的存储标签设置为该声明的值。", - "oauth_storage_quota_claim": "存储配额声明", - "oauth_storage_quota_claim_description": "自动将用户的存储配额设置为该声明的值。", - "oauth_storage_quota_default": "默认存储配额(GiB)", - "oauth_storage_quota_default_description": "当未提供声明时,将使用的配额(GiB)。", - "oauth_timeout": "请求超时", - "oauth_timeout_description": "请求超时时间(毫秒)", - "ocr_job_description": "利用机器学习技术识别图像中的文本", - "password_enable_description": "使用邮箱和密码登录", - "password_settings": "密码登录", - "password_settings_description": "管理密码登录设置", - "paths_validated_successfully": "所有路径均已成功验证", - "person_cleanup_job": "人员清理", - "queue_details": "队列详情", - "queues": "任务队列", - "queues_page_description": "管理任务队列页面", - "quota_size_gib": "配额大小(GiB)", - "refreshing_all_libraries": "正在刷新所有库", - "registration": "管理员注册", - "registration_description": "由于您是系统的第一位用户,系统将自动为您分配管理员权限。您需要负责相关的管理任务,后续的其他用户也将由您来创建。", - "remove_failed_jobs": "移除失败任务", - "require_password_change_on_login": "强制用户首次登录时修改密码", - "reset_settings_to_default": "将设置重置为默认值", - "reset_settings_to_recent_saved": "将设置重置为上次保存的值", - "scanning_library": "正在扫描资料库", - "search_jobs": "搜索任务…", - "send_welcome_email": "发送欢迎邮件", - "server_external_domain_settings": "外部域名", - "server_external_domain_settings_description": "公开分享链接的域名,需包含 http(s)://", - "server_public_users": "公开用户", - "server_public_users_description": "所有用户(姓名和邮箱)在将用户添加到共享相册时都会显示。关闭此功能后,用户列表将仅对管理员可见。", - "server_settings": "服务器设置", - "server_settings_description": "管理服务器设置", - "server_stats_page_description": "管理服务器统计页面", - "server_welcome_message": "欢迎信息", - "server_welcome_message_description": "一段显示在登录页面的消息。", - "settings_page_description": "管理员设置页面", - "sidecar_job": "附属元数据", - "sidecar_job_description": "从文件系统中发现或同步附属元数据", - "slideshow_duration_description": "每张图片显示的秒数", - "smart_search_job_description": "对资源运行机器学习以支持智能搜索", - "storage_template_date_time_description": "资源的创建时间戳用于日期时间信息", - "storage_template_date_time_sample": "示例时间:{date}", - "storage_template_enable_description": "启用存储模板引擎", - "storage_template_hash_verification_enabled": "已启用哈希校验", - "storage_template_hash_verification_enabled_description": "开启哈希校验功能。除非你清楚关闭后的后果,否则请勿关闭", - "storage_template_migration": "存储模板迁移", - "storage_template_migration_description": "将当前 {template} 应用于已上传的资源", - "storage_template_migration_info": "存储模板会将所有文件扩展名转换为小写。模板更改仅对新上传的资源生效。若要将模板回溯应用于已上传的资源,请运行 {job}。", - "storage_template_migration_job": "存储模板迁移任务", - "storage_template_more_details": "有关此功能的更多详细信息,请参阅 存储模板 及其 含义", - "storage_template_onboarding_description_v2": "启用后,此功能将根据用户定义的模板自动整理文件。更多信息,请参阅 文档。", - "storage_template_path_length": "近似路径长度限制:{length, number}/{limit, number}", - "storage_template_settings": "存储模板", - "storage_template_settings_description": "管理上传资产文件夹结构和文件名", - "storage_template_user_label": "{label}为用户的存储标签", - "system_settings": "系统设置", - "tag_cleanup_job": "标签清理", - "template_email_available_tags": "您可以在模板中使用以下变量:{tags}", - "template_email_if_empty": "如果模板为空,则使用默认邮箱。", - "template_email_invite_album": "邀请相册模板", - "template_email_preview": "预览", - "template_email_settings": "邮件模板", - "template_email_update_album": "更新相册模板", - "template_email_welcome": "欢迎邮件模板", - "template_settings": "通知模板", - "template_settings_description": "管理通知的自定义模板", - "theme_custom_css_settings": "自定义 CSS", - "theme_custom_css_settings_description": "CSS 允许自定义 Immich 界面设计。", - "theme_settings": "主题设置", - "theme_settings_description": "自定义 Immich Web 界面", - "thumbnail_generation_job": "生成缩略图", - "thumbnail_generation_job_description": "为每个资产生成不同尺寸的缩略图,并为每个人物生成缩略图", - "transcoding_acceleration_api": "硬件加速 API", - "transcoding_acceleration_api_description": "用于与设备交互以加速转码的 API。该设置采用“尽力而为”策略:若硬件加速失败,系统将自动回退到软件转码。VP9 编码的支持情况取决于您的硬件配置。", - "transcoding_acceleration_nvenc": "NVENC(需要 NVIDIA 显卡)", - "transcoding_acceleration_qsv": "Quick Sync(需要 Intel 7代及以上的 CPU)", - "transcoding_acceleration_rkmpp": "RKMPP(仅适用于 Rockchip SOCs)", - "transcoding_acceleration_vaapi": "视频加速 API", - "transcoding_accepted_audio_codecs": "支持的音频编码格式", - "transcoding_accepted_audio_codecs_description": "选择无需转码的音频编码格式。仅在特定的转码策略下生效。", - "transcoding_accepted_containers": "支持的容器格式", - "transcoding_accepted_containers_description": "选择无需重新封装为 MP4 的容器格式。仅在特定的转码策略下生效。", - "transcoding_accepted_video_codecs": "支持的视频编码格式", - "transcoding_accepted_video_codecs_description": "选择无需转码的视频编码格式。仅在特定的转码策略下生效。", - "transcoding_advanced_options_description": "大多数用户不需要更改的选项", - "transcoding_audio_codec": "音频编码格式", - "transcoding_audio_codec_description": "Opus 是音质最高的选项,但在老旧设备或软件上的兼容性较差。", - "transcoding_bitrate_description": "视频码率高于最大限制,或格式不在接受列表中", - "transcoding_codecs_learn_more": "若要了解此处使用的术语详情,请查阅 FFmpeg 文档中的 H.264 编码HEVC 编码VP9 编码。", - "transcoding_constant_quality_mode": "恒定质量模式", - "transcoding_constant_quality_mode_description": "ICQ 比 CQP 效果更好,但部分硬件加速设备不支持此模式。启用该选项后,在基于质量的编码中将优先使用指定的模式。由于 NVENC(NVIDIA 显卡编码器)不支持 ICQ,因此该设置对其无效。", - "transcoding_constant_rate_factor": "恒定码率系数(-crf)", - "transcoding_constant_rate_factor_description": "视频质量等级。典型值为:H.264 使用 23,HEVC 使用 28,VP9 使用 31,AV1 使用 35。数值越低质量越好,但生成的文件也越大。", - "transcoding_disabled_description": "不转码任何视频,可能会导致部分客户端无法播放", - "transcoding_encoding_options": "编码选项", - "transcoding_encoding_options_description": "设置编码视频的编解码器、分辨率、质量和其他选项", - "transcoding_hardware_acceleration": "硬件加速", - "transcoding_hardware_acceleration_description": "实验性功能:速度更快,但在相同码率下质量会降低", - "transcoding_hardware_decoding": "硬件解码", - "transcoding_hardware_decoding_setting_description": "启用端到端加速,而不仅仅是加速编码。可能并不适用于所有视频。", - "transcoding_max_b_frames": "最大B帧数", - "transcoding_max_b_frames_description": "较高的值可以提高压缩效率,但会减慢编码速度。可能与旧设备上的硬件加速不兼容。0表示将禁用B帧,-1表示将自动设置此参数。", - "transcoding_max_bitrate": "最高码率", - "transcoding_max_bitrate_description": "设置最大比特率可在对输出质量影响较小的情况下,使文件体积更为可控。720p 下,VP9 或 HEVC 普遍将其设为 2600 kbit/s,H.264 则为 4500 kbit/s。如果此项设置为 0,则不限制最大比特率。当没有指定单位时,假设k(代表kbit/s);因此,5000、5000k和5M(Mbit/s)是等效的。", - "transcoding_max_keyframe_interval": "最大关键帧间隔", - "transcoding_max_keyframe_interval_description": "设置关键帧之间的最大帧距离。较低的值会降低压缩效率,但可以提高搜索速度,并且可能在快速运动的场景中提高画质。0 表示将自动设置此参数。", - "transcoding_optimal_description": "视频超过目标分辨率或格式不支持", - "transcoding_policy": "转码策略", - "transcoding_policy_description": "设置视频转码时机", - "transcoding_preferred_hardware_device": "首选硬件设备", - "transcoding_preferred_hardware_device_description": "仅适用于 VAAPI 和 QSV。设置用于硬件转码的 DRI 设备节点。", - "transcoding_preset_preset": "预设(-preset)", - "transcoding_preset_preset_description": "压缩速度。预设速度越慢,生成的文件越小;在设定特定码率时,还能提升画质。VP9 编码器会忽略(不支持)高于“faster”速度的选项。", - "transcoding_reference_frames": "参考帧", - "transcoding_reference_frames_description": "在压缩指定帧时,所参考的帧数量。数值越高,压缩效率越高,但会降低编码速度。设为 0 表示由系统自动设置。", - "transcoding_required_description": "仅非标准格式的视频", - "transcoding_settings": "视频转码设置", - "transcoding_settings_description": "管理需要转码的视频范围,以及具体的处理方式", - "transcoding_target_resolution": "目标分辨率", - "transcoding_target_resolution_description": "更高的分辨率虽然能保留更多画面细节,但会延长编码时间、增大文件体积,并可能导致应用响应变慢。", - "transcoding_temporal_aq": "时间域自适应量化", - "transcoding_temporal_aq_description": "仅适用于 NVENC。时间域自适应量化可提升高细节、低运动场景的画质。可能与较旧的设备不兼容。", - "transcoding_threads": "线程数", - "transcoding_threads_description": "数值越高,编码速度越快,但在运行时会减少服务器处理其他任务的余量。该数值不应超过 CPU 核心数。设为 0 可最大化资源利用率。", - "transcoding_tone_mapping": "色调映射", - "transcoding_tone_mapping_description": "旨在将 HDR 视频转换为 SDR 时,尽量保留原有的视觉效果。每种算法都在色彩、细节和亮度之间做出了不同的取舍:Hable 算法侧重保留细节,Mobius 算法侧重保留色彩,而 Reinhard 算法则侧重保留亮度。", - "transcoding_transcode_policy": "转码策略", - "transcoding_transcode_policy_description": "设定视频何时应进行转码的策略。HDR 视频始终会被转码(除非已完全禁用转码功能)。", - "transcoding_two_pass_encoding": "二次编码", - "transcoding_two_pass_encoding_setting_description": "采用两次编码模式以生成质量更优的视频。当开启最大码率限制时(H.264 和 HEVC 编码格式必须开启此选项才能生效),该模式会依据最大码率设定一个码率范围,并忽略 CRF 设置。对于 VP9 编码,若关闭最大码率限制,则可以使用 CRF 设置。", - "transcoding_video_codec": "视频编码格式", - "transcoding_video_codec_description": "VP9 编码效率高,且在网页端兼容性好,但转码耗时较长。HEVC(H.265)性能与之相似,但在网页端的兼容性较差。H.264 兼容性极广且转码速度快,但生成的文件体积要大得多。AV1 是效率最高的编码格式,但在旧设备上缺乏支持。", - "trash_enabled_description": "启用回收站功能", - "trash_number_of_days": "保留天数", - "trash_number_of_days_description": "文件在回收站中保留多少天后被永久删除", - "trash_settings": "回收站设置", - "trash_settings_description": "管理回收站设置", - "unlink_all_oauth_accounts": "解除所有 OAuth 帐户的链接", - "unlink_all_oauth_accounts_description": "在迁移到新服务商之前,请记得解除所有 OAuth 账户的关联。", - "unlink_all_oauth_accounts_prompt": "您确定要解除所有 OAuth 账户的关联吗?此操作将重置每个用户的身份认证 ID,且无法撤销。", - "user_cleanup_job": "用户清理", - "user_delete_delay": "{user}的账户及资产将在{delay, plural, one {#天} other {#天}}后自动永久删除。", - "user_delete_delay_settings": "延期删除", - "user_delete_delay_settings_description": "移除后多少天,永久删除用户的账户及资产。用户删除任务将在午夜运行,以检查是否有待删除的用户。此设置的更改将在下次任务执行时生效。", - "user_delete_immediately": "{user}的账户及资产将被立即加入永久删除队列。", - "user_delete_immediately_checkbox": "将用户及其资产加入立即删除队列", - "user_details": "用户详情", - "user_management": "用户管理", - "user_password_has_been_reset": "用户的密码已重置:", - "user_password_reset_description": "请将临时密码提供给用户,并告知他们需在下次登录时更改密码。", - "user_restore_description": "账户“{user}”将被恢复。", - "user_restore_scheduled_removal": "恢复用户 - 原定于 {date, date, long} 的删除计划已取消", - "user_settings": "用户设置", - "user_settings_description": "管理用户设置", - "user_successfully_removed": "用户 {email} 已成功删除。", - "users_page_description": "管理用户页面", - "version_check_enabled_description": "启用版本检测", - "version_check_implications": "版本检查功能依赖于与 github.com 的定期通信", - "version_check_settings": "版本检查", - "version_check_settings_description": "启用/禁用新版本通知", - "video_conversion_job": "转码视频", - "video_conversion_job_description": "对视频进行转码,以兼容更多的浏览器和设备" - }, - "admin_email": "管理员邮箱", - "admin_password": "管理员密码", - "administration": "系统管理", - "advanced": "高级", - "advanced_settings_clear_image_cache": "清空图像缓存", - "advanced_settings_clear_image_cache_error": "无法清空图像缓存", - "advanced_settings_clear_image_cache_success": "成功清理 {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "使用此选项可根据其他条件筛选同步期间的媒体。仅在应用无法检测到所有相册时尝试此选项。", - "advanced_settings_enable_alternate_media_filter_title": "[实验性] 使用备用设备相册筛选方式", - "advanced_settings_log_level_title": "日志等级: {level}", - "advanced_settings_prefer_remote_subtitle": "部分设备读取本地资源缩略图的速度极慢。开启此设置可改为加载远程图片。", - "advanced_settings_prefer_remote_title": "优先使用远程图片", - "advanced_settings_proxy_headers_subtitle": "定义 Immich 每次网络请求应附带的代理头信息", - "advanced_settings_proxy_headers_title": "自定义代理头信息 [实验性]", - "advanced_settings_readonly_mode_subtitle": "启用只读模式,在此模式下仅可查看照片,多选、分享、投屏、删除等功能将全部禁用。可通过主屏幕上的用户头像开启/关闭只读模式", - "advanced_settings_readonly_mode_title": "只读模式", - "advanced_settings_self_signed_ssl_subtitle": "跳过服务器端点的 SSL 证书验证。自签名证书情况下需要开启此选项。", - "advanced_settings_self_signed_ssl_title": "允许使用自签名 SSL 证书[实验性]", - "advanced_settings_sync_remote_deletions_subtitle": "当在网页端执行删除或恢复操作时,自动在本设备上同步执行该操作", - "advanced_settings_sync_remote_deletions_title": "同步远程删除操作 [实验性]", - "advanced_settings_tile_subtitle": "高级用户设置", - "advanced_settings_troubleshooting_subtitle": "启用额外的故障排查功能", - "advanced_settings_troubleshooting_title": "故障排除", - "age_months": "{months, plural, one {#个月} other {#个月}}", - "age_year_months": "1岁{months, plural, one {#个月} other {#个月}}", - "age_years": "{years, plural, other {#岁}}", - "album": "相册", - "album_added": "相册添加成功", - "album_added_notification_setting_description": "当您被添加到共享相册时,接收邮箱通知", - "album_cover_updated": "封面已更新", - "album_delete_confirmation": "确定要删除相册 “{album}” 吗?", - "album_delete_confirmation_description": "如果此相册已被共享,其他用户将无法再访问它。", - "album_deleted": "相册已删除", - "album_info_card_backup_album_excluded": "已排除", - "album_info_card_backup_album_included": "已包含", - "album_info_updated": "相册信息已更新", - "album_leave": "退出相册?", - "album_leave_confirmation": "确定要退出相册 “{album}” 吗?", - "album_name": "相册名称", - "album_options": "相册选项", - "album_remove_user": "移除用户?", - "album_remove_user_confirmation": "确定要移除 “{user}” 吗?", - "album_search_not_found": "未找到与搜索条件匹配的相册", - "album_selected": "相册已选中", - "album_share_no_users": "看起来您已将此相册共享给所有用户,或者您没有可共享的用户。", - "album_summary": "相册概览", - "album_updated": "相册已更新", - "album_updated_setting_description": "当共享相册有新内容时,接收邮件通知", - "album_upload_assets": "从您的电脑上传文件并添加到相册", - "album_user_left": "已退出 “{album}”", - "album_user_removed": "已移除 “{user}”", - "album_viewer_appbar_delete_confirm": "确定要从账户中删除此相册吗?", - "album_viewer_appbar_share_err_delete": "删除相册失败", - "album_viewer_appbar_share_err_leave": "退出共享失败", - "album_viewer_appbar_share_err_remove": "从相册移除内容时出现问题", - "album_viewer_appbar_share_err_title": "修改相册标题失败", - "album_viewer_appbar_share_leave": "退出相册", - "album_viewer_appbar_share_to": "分享给", - "album_viewer_page_share_add_users": "邀请他人", - "album_with_link_access": "允许任何拥有该链接的人查看此相册中的照片和人物。", - "albums": "相册", - "albums_count": "{count, plural, one {{count, number} 个相册} other {{count, number} 个相册}}", - "albums_default_sort_order": "默认相册排序方式", - "albums_default_sort_order_description": "创建新相册时,初始照片的排序方式。", - "albums_feature_description": "可与其他用户共享的照片/内容合集。", - "albums_on_device_count": "设备上的相册({count} 个)", - "albums_selected": "{count, plural, one {# 个相册已选择} other {# 个相册已选择}}", - "all": "全部", - "all_albums": "所有相册", - "all_people": "全部人物", - "all_photos": "所有照片", - "all_videos": "所有视频", - "allow_dark_mode": "允许深色模式", - "allow_edits": "允许编辑", - "allow_public_user_to_download": "允许所有用户下载", - "allow_public_user_to_upload": "允许所有用户上传", - "allowed": "允许", - "alt_text_qr_code": "二维码图片", - "always_keep": "始终保留", - "always_keep_photos_hint": "开启“释放空间”后,仍会保留所有照片在本设备上。", - "always_keep_videos_hint": "开启“释放空间”后,仍会保留所有视频在本设备上。", - "anti_clockwise": "逆时针", - "api_key": "API 密钥", - "api_key_description": "该应用密钥只会显示一次。请确保在关闭窗口前复制下来。", - "api_key_empty": "API 密钥名称不可为空", - "api_keys": "API 密钥", - "app_architecture_variant": "变体(架构)", - "app_bar_signout_dialog_content": "您确定要退出吗?", - "app_bar_signout_dialog_ok": "是", - "app_bar_signout_dialog_title": "退出登录", - "app_download_links": "APP下载链接", - "app_settings": "应用设置", - "app_stores": "应用商店", - "app_update_available": "应用更新已发布", - "appears_in": "收录于", - "apply_count": "应用 ({count, number})", - "archive": "归档", - "archive_action_prompt": "已将 {count} 项添加到归档", - "archive_or_unarchive_photo": "归档或取消归档照片", - "archive_page_no_archived_assets": "未找到已归的资源", - "archive_page_title": "归档({count})", - "archive_size": "归档大小", - "archive_size_description": "配置下载的归档大小(GiB)", - "archived": "已归档", - "archived_count": "{count, plural, other {已归档 # 项}}", - "are_these_the_same_person": "这是同一个人吗?", - "are_you_sure_to_do_this": "确定要执行此操作?", - "array_field_not_fully_supported": "数组字段需要手动进行 JSON 编辑", - "asset_action_delete_err_read_only": "无法删除只读资源,已跳过", - "asset_action_share_err_offline": "无法获取离线资源,已跳过", - "asset_added_to_album": "已添加至相册", - "asset_adding_to_album": "正在添加至相册…", - "asset_created": "资源已创建", - "asset_description_updated": "资源描述已更新", - "asset_filename_is_offline": "资源“{filename}”已离线", - "asset_has_unassigned_faces": "资源包含未分配的人脸", - "asset_hashing": "正在计算哈希值…", - "asset_list_group_by_sub_title": "分组依据", - "asset_list_layout_settings_dynamic_layout_title": "动态布局", - "asset_list_layout_settings_group_automatically": "自动", - "asset_list_layout_settings_group_by": "资源分组依据", - "asset_list_layout_settings_group_by_month_day": "月份 + 日期", - "asset_list_layout_sub_title": "布局", - "asset_list_settings_subtitle": "照片网格布局设置", - "asset_list_settings_title": "照片网格", - "asset_not_found_on_device_android": "设备上未找到该资源", - "asset_not_found_on_device_ios": "设备上未找到该资源。如果您使用了 iCloud,可能是由于 iCloud 中存储了错误的文件导致资源无法访问", - "asset_not_found_on_icloud": "iCloud 中未找到该资源。可能是由于 iCloud 中存储了错误的文件导致资源无法访问", - "asset_offline": "资源离线", - "asset_offline_description": "磁盘上未找到此外部资源。请联系您的 Immich 管理员寻求帮助。", - "asset_restored_successfully": "资源恢复成功", - "asset_skipped": "已跳过", - "asset_skipped_in_trash": "在回收站中", - "asset_trashed": "资源已移至回收站", - "asset_troubleshoot": "资源诊断", - "asset_uploaded": "已上传", - "asset_uploading": "上传中…", - "asset_viewer_settings_subtitle": "管理画廊查看器设置", - "asset_viewer_settings_title": "资源查看器", - "assets": "资源", - "assets_added_count": "已添加{count, plural, one {#个资源} other {#个资源}}", - "assets_added_to_album_count": "已向相册添加{count, plural, one {#个资源} other {#个资源}}", - "assets_added_to_albums_count": "已向 {albumTotal, plural, one {# 个相册} other {# 个相册}}添加 {assetTotal, plural, one {# 个资源} other {# 个资源}}", - "assets_cannot_be_added_to_album_count": "无法向相册添加{count, plural, one {个资源} other {个资源}}", - "assets_cannot_be_added_to_albums": "无法向任何一个相册添加 {count, plural, one {个资源} other {个资源}}", - "assets_count": "{count, plural, one {#个资源} other {#个资源}}", - "assets_deleted_permanently": "已永久删除 {count} 个资源", - "assets_deleted_permanently_from_server": "已永久移除 {count} 个资产", - "assets_downloaded_failed": "{count, plural, one {已下载#个文件 - {error} 个文件下载失败} other {已下载#个文件 - {error} 个文件下载失败}}", - "assets_downloaded_successfully": "{count, plural, one {已成功下载 # 个文件} other {已成功下载 # 个文件}}", - "assets_moved_to_trash_count": "已将{count, plural, one {#个资源} other {#个资源}}移动到回收站", - "assets_permanently_deleted_count": "已永久删除{count, plural, one {#个资源} other {#个资源}}", - "assets_removed_count": "已移除{count, plural, one {#个资源} other {#个资源}}", - "assets_removed_permanently_from_device": "已从您的设备中永久删除 {count} 个资源", - "assets_restore_confirmation": "您确定要恢复回收站中的所有资源吗?此操作无法撤销!请注意,任何离线资源无法通过此方式恢复。", - "assets_restored_count": "已恢复{count, plural, one {#个资源} other {#个资源}}", - "assets_restored_successfully": "已成功恢复{count}个资源", - "assets_trashed": "{count} 个资源移至回收站", - "assets_trashed_count": "已将{count, plural, one {#个资源} other {#个资源}}移至回收站", - "assets_trashed_from_server": "Immich 服务器上已移除 {count} 个资源", - "assets_were_part_of_album_count": "{count, plural, one {个资源} other {个资源}}已在该相册中", - "assets_were_part_of_albums_count": "{count, plural, one {个资源} other {个资源}} 已存在于这些相册中", - "authorized_devices": "已授权设备", - "automatic_endpoint_switching_subtitle": "在可用时通过指定的 Wi-Fi 进行本地连接,其他位置则使用替代网络连接", - "automatic_endpoint_switching_title": "自动切换 URL", - "autoplay_slideshow": "自动播放幻灯片", - "back": "返回", - "back_close_deselect": "返回、关闭或取消选择", - "background_backup_running_error": "后台备份正在运行中,无法启动手动备份", - "background_location_permission": "后台定位权限", - "background_location_permission_content": "为了在后台运行时实现网络切换,Immich 必须始终拥有精确位置访问权限,以便应用能够读取 Wi-Fi 网络的名称", - "background_options": "后台选项", - "backup": "备份", - "backup_album_selection_page_albums_device": "设备上的相册({count})", - "backup_album_selection_page_albums_tap": "单击包含,双击排除", - "backup_album_selection_page_assets_scatter": "资源文件可能分散在多个相册中。因此,在备份过程中,您可以选择包含或排除特定的相册。", - "backup_album_selection_page_select_albums": "选择相册", - "backup_album_selection_page_selection_info": "选择信息", - "backup_album_selection_page_total_assets": "唯一资源总计", - "backup_albums_sync": "备份相册同步", - "backup_all": "全部", - "backup_background_service_backup_failed_message": "资源备份失败。正在重试…", - "backup_background_service_complete_notification": "资源备份完成", - "backup_background_service_connection_failed_message": "无法连接到服务器。正在重试…", - "backup_background_service_current_upload_notification": "正在上传 “{filename}”", - "backup_background_service_default_notification": "正在检查新资源…", - "backup_background_service_error_title": "备份错误", - "backup_background_service_in_progress_notification": "正在备份您的资源…", - "backup_background_service_upload_failure_notification": "“{filename}”上传失败", - "backup_controller_page_albums": "备份相册", - "backup_controller_page_background_app_refresh_disabled_content": "在“设置”>“通用”>“后台 App 刷新”中启用此功能,以使用后台备份。", - "backup_controller_page_background_app_refresh_disabled_title": "后台 App 刷新已关闭", - "backup_controller_page_background_app_refresh_enable_button_text": "前往设置", - "backup_controller_page_background_battery_info_link": "展示操作步骤", - "backup_controller_page_background_battery_info_message": "为获得最佳的后台备份体验,请在系统设置中禁用针对 Immich 的任何电池优化限制。\n\n由于该设置因设备而异,请查询您设备制造商的具体要求。", - "backup_controller_page_background_battery_info_ok": "我知道了", - "backup_controller_page_background_battery_info_title": "电池优化", - "backup_controller_page_background_charging": "仅在充电时", - "backup_controller_page_background_configure_error": "后台服务配置失败", - "backup_controller_page_background_delay": "延迟新文件备份:{duration}", - "backup_controller_page_background_description": "开启后台服务,即可在无需打开 App 的情况下,自动备份所有新文件", - "backup_controller_page_background_is_off": "后台自动备份未开启", - "backup_controller_page_background_is_on": "后台自动备份已开启", - "backup_controller_page_background_turn_off": "关闭后台服务", - "backup_controller_page_background_turn_on": "开启后台服务", - "backup_controller_page_background_wifi": "仅在 Wi-Fi 下", - "backup_controller_page_backup": "备份", - "backup_controller_page_backup_selected": "已选: ", - "backup_controller_page_backup_sub": "已备份的照片和视频", - "backup_controller_page_created": "创建时间:{date}", - "backup_controller_page_desc_backup": "开启前台备份,打开 App 即自动上传新文件。", - "backup_controller_page_excluded": "已排除: ", - "backup_controller_page_failed": "失败({count})", - "backup_controller_page_filename": "文件名:{filename} [{size}]", - "backup_controller_page_id": "ID:{id}", - "backup_controller_page_info": "备份信息", - "backup_controller_page_none_selected": "暂未选择", - "backup_controller_page_remainder": "剩余", - "backup_controller_page_remainder_sub": "已选项中尚未备份的照片和视频", - "backup_controller_page_server_storage": "服务器存储", - "backup_controller_page_start_backup": "开始备份", - "backup_controller_page_status_off": "未开启前台自动备份", - "backup_controller_page_status_on": "前台自动备份已打开", - "backup_controller_page_storage_format": "已用 {used}(共 {total})", - "backup_controller_page_to_backup": "待备份的相册", - "backup_controller_page_total_sub": "包含所选相册内全部唯一的照片和视频", - "backup_controller_page_turn_off": "关闭前台备份", - "backup_controller_page_turn_on": "开启前台备份", - "backup_controller_page_uploading_file_info": "正在上传中的文件信息", - "backup_err_only_album": "无法删除唯一的相册", - "backup_error_sync_failed": "同步失败。无法处理备份。", - "backup_info_card_assets": "项", - "backup_manual_cancelled": "已取消", - "backup_manual_in_progress": "上传正在进行中,请稍后再试", - "backup_manual_success": "成功", - "backup_manual_title": "上传状态", - "backup_options": "备份选项", - "backup_options_page_title": "备份选项", - "backup_setting_subtitle": "管理后台和前台上传设置", - "backup_settings_subtitle": "管理上传设置", - "backup_upload_details_page_more_details": "点击了解详情", - "backward": "后退", - "biometric_auth_enabled": "生物识别身份验证已启用", - "biometric_locked_out": "您被锁定在生物识别身份验证之外", - "biometric_no_options": "没有可用的生物识别选项", - "biometric_not_available": "生物识别身份验证在此设备上不可用", - "birthdate_saved": "出生日期保存成功", - "birthdate_set_description": "出生日期用于计算照片中该人物在拍照时的年龄。", - "blurred_background": "背景模糊", - "bugs_and_feature_requests": "Bug 与功能请求", - "build": "构建版本", - "build_image": "镜像版本", - "bulk_delete_duplicates_confirmation": "您确定要批量删除{count, plural, one {#个重复资产} other {#个重复资产}}吗?这将保留每个组中最大的项目并永久删除所有其它重复资产。注意:该操作无法被撤消!", - "bulk_keep_duplicates_confirmation": "您确定要保留{count, plural, one {#个重复资产} other {#个重复资产}}吗?这将清空所有重复记录,但不会删除任何内容。", - "bulk_trash_duplicates_confirmation": "您确定要批量删除{count, plural, one {#个重复资产} other {#个重复资产}}吗?这将保留每组中最大的资产并删除所有其它重复资产。", - "buy": "购买 Immich", - "cache_settings_clear_cache_button": "清除缓存", - "cache_settings_clear_cache_button_title": "清除应用缓存。在重新生成缓存之前,将显著影响应用的性能。", - "cache_settings_duplicated_assets_clear_button": "清除", - "cache_settings_duplicated_assets_subtitle": "应用程序忽略的照片和视频", - "cache_settings_duplicated_assets_title": "重复资产({count})", - "cache_settings_statistics_album": "资产库缩略图", - "cache_settings_statistics_full": "完整图像", - "cache_settings_statistics_shared": "共享相册缩略图", - "cache_settings_statistics_thumbnail": "缩略图", - "cache_settings_statistics_title": "缓存使用情况", - "cache_settings_subtitle": "控制 Immich app 的缓存行为", - "cache_settings_tile_subtitle": "设置本地存储行为", - "cache_settings_tile_title": "本地存储", - "cache_settings_title": "缓存设置", - "camera": "相机", - "camera_brand": "相机品牌", - "camera_model": "相机型号", - "cancel": "取消", - "cancel_search": "取消搜索", - "canceled": "已取消", - "canceling": "取消中", - "cannot_merge_people": "无法合并人物", - "cannot_undo_this_action": "注意:该操作无法被撤消!", - "cannot_update_the_description": "无法更新描述", - "cast": "投屏", - "cast_description": "配置可用的投屏设备", - "change_date": "更改日期", - "change_description": "修改描述", - "change_display_order": "更改显示顺序", - "change_expiration_time": "更改过期时间", - "change_location": "更改位置", - "change_name": "更改名称", - "change_name_successfully": "更改名称成功", - "change_password": "修改密码", - "change_password_description": "这是您的第一次登录亦或有人要求更改您的密码。请在下面输入新密码。", - "change_password_form_confirm_password": "确认密码", - "change_password_form_description": "{name} 您好,\n\n这是您首次登录系统,或被管理员要求更改密码。请在下方输入新密码。", - "change_password_form_log_out": "注销所有其他设备", - "change_password_form_log_out_description": "建议退出所有其他设备", - "change_password_form_new_password": "新密码", - "change_password_form_password_mismatch": "密码不匹配", - "change_password_form_reenter_new_password": "再次输入新密码", - "change_pin_code": "修改PIN码", - "change_trigger": "更改触发条件", - "change_trigger_prompt": "您确定要更改触发条件吗?这将删除所有现有操作和筛选。", - "change_your_password": "修改您的密码", - "changed_visibility_successfully": "更改可见性成功", - "charging": "充电", - "charging_requirement_mobile_backup": "后台备份需要设备处于充电状态", - "check_corrupt_asset_backup": "检查备份是否损坏", - "check_corrupt_asset_backup_button": "执行检查", - "check_corrupt_asset_backup_description": "仅在连接到 Wi-Fi 并完成所有资产备份后执行此检查。该过程可能需要几分钟。", - "check_logs": "检查日志", - "checksum": "校验和", - "choose_matching_people_to_merge": "选择匹配的人进行合并", - "city": "城市", - "cleanup_confirm_description": "Immich发现{count}个资产(在{date}之前创建)已安全备份到服务器。是否从此设备中删除本地副本?", - "cleanup_confirm_prompt_title": "从此设备删除?", - "cleanup_deleted_assets": "将{count}个资产移动到设备回收站", - "cleanup_deleting": "移至回收站...", - "cleanup_found_assets": "找到{count}个备份资产", - "cleanup_found_assets_with_size": "找到 {count} 个已备份的文件 ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud共享相册被排除在扫描之外", - "cleanup_no_assets_found": "未找到符合上述条件的文件。释放空间功能只能移除已备份到服务器的文件", - "cleanup_preview_title": "要删除的资产({count}个)", - "cleanup_step3_description": "扫描符合您日期和保留设置的已备份文件。", - "cleanup_step4_summary": "将从本机移除 {count} 个文件(创建于 {date} 之前)。照片仍可在 Immich 应用中查看。", - "cleanup_trash_hint": "要完全回收存储空间,请打开系统库应用程序并清空回收站", - "clear": "清空", - "clear_all": "清空全部", - "clear_all_recent_searches": "清除所有最近搜索", - "clear_file_cache": "清除文件缓存", - "clear_message": "清空消息", - "clear_value": "删除内容", - "client_cert_dialog_msg_confirm": "确定", - "client_cert_enter_password": "输入密码", - "client_cert_import": "导入", - "client_cert_import_success_msg": "客户端证书已导入", - "client_cert_invalid_msg": "无效的证书文件或密码错误", - "client_cert_remove_msg": "客户端证书已移除", - "client_cert_subtitle": "仅支持PKCS12(.p12、.pfx)格式。证书导入/删除仅在登录前可用", - "client_cert_title": "SSL 客户端证书[实验性]", - "clockwise": "顺时针", - "close": "关闭", - "collapse": "折叠", - "collapse_all": "全部折叠", - "color": "颜色", - "color_theme": "颜色主题", - "command": "命令", - "comment_deleted": "评论已删除", - "comment_options": "评论选项", - "comments_and_likes": "评论 & 点赞", - "comments_are_disabled": "评论已禁用", - "common_create_new_album": "新建相册", - "completed": "已完成", - "confirm": "确认", - "confirm_admin_password": "确认管理员密码", - "confirm_delete_face": "您确定要从资产中删除 {name} 的人脸信息吗?", - "confirm_delete_shared_link": "确定要删除此共享链接吗?", - "confirm_keep_this_delete_others": "除了这个资产之外,堆栈中的所有其他资产都将被删除。您确定要继续吗?", - "confirm_new_pin_code": "确认新的PIN码", - "confirm_password": "确认密码", - "confirm_tag_face": "您想将这个人脸标记为“{name}”吗?", - "confirm_tag_face_unnamed": "是否标记此人脸?", - "connected_device": "已连接设备", - "connected_to": "已连接到", - "contain": "包含", - "context": "以文搜图", - "continue": "继续", - "control_bottom_app_bar_create_new_album": "新建相册", - "control_bottom_app_bar_delete_from_immich": "从 Immich 服务器中删除", - "control_bottom_app_bar_delete_from_local": "从手机中删除", - "control_bottom_app_bar_edit_location": "编辑位置信息", - "control_bottom_app_bar_edit_time": "编辑日期和时间", - "control_bottom_app_bar_share_link": "共享链接", - "control_bottom_app_bar_share_to": "发送给", - "control_bottom_app_bar_trash_from_immich": "移入回收站", - "copied_image_to_clipboard": "已复制图片至剪切板。", - "copied_to_clipboard": "已复制到剪切板!", - "copy_error": "复制出错", - "copy_file_path": "复制文件路径", - "copy_image": "复制图片", - "copy_link": "复制链接", - "copy_link_to_clipboard": "复制链接到剪切板", - "copy_password": "复制密码", - "copy_to_clipboard": "复制到剪切板", - "country": "国家", - "cover": "封面", - "covers": "封面", - "create": "创建", - "create_album": "创建相册", - "create_album_page_untitled": "未命名", - "create_api_key": "创建 API Key", - "create_first_workflow": "创建第一个工作流", - "create_library": "创建资产库", - "create_link": "创建链接", - "create_link_to_share": "创建共享链接", - "create_link_to_share_description": "获得此链接的人均可查看所选照片", - "create_new": "新建", - "create_new_person": "创建新人物", - "create_new_person_hint": "指派已选择资产到新的人物", - "create_new_user": "创建新用户", - "create_shared_album_page_share_add_assets": "添加资产", - "create_shared_album_page_share_select_photos": "选择资产", - "create_shared_link": "创建共享链接", - "create_tag": "创建标签", - "create_tag_description": "创建一个新标签。对于嵌套标签,请输入标签的完整路径,包括正斜杠(/)。", - "create_user": "创建用户", - "create_workflow": "创建工作流", - "created": "已创建", - "created_at": "创建时间", - "creating_linked_albums": "正在创建相册链接…", - "crop": "裁剪", - "crop_aspect_ratio_fixed": "固定纵横比", - "crop_aspect_ratio_free": "自由纵横比", - "crop_aspect_ratio_original": "原始纵横比", - "curated_object_page_title": "事物", - "current_device": "当前设备", - "current_pin_code": "当前PIN码", - "current_server_address": "当前服务器地址", - "custom_date": "自定义日期", - "custom_locale": "自定义地区", - "custom_locale_description": "日期和数字显示格式跟随语言和地区", - "custom_url": "自定义URL", - "cutoff_date_description": "保留最近的照片…", - "cutoff_day": "{count, plural, one {天} other {天}}", - "cutoff_year": "{count, plural, one {年} other {年}}", - "daily_title_text_date": "MMM dd (E)", - "daily_title_text_date_year": "YYYY年M月D日 (E)", - "dark": "深色", - "dark_theme": "切换深色主题", - "date": "日期", - "date_after": "开始日期", - "date_and_time": "日期与时间", - "date_before": "结束日期", - "date_format": "y年M月d日 (E) h:mm a", - "date_of_birth_saved": "出生日期保存成功", - "date_range": "日期范围", - "day": "日", - "days": "天", - "deduplicate_all": "删除所有重复项", - "deduplication_criteria_1": "图像大小(字节)", - "deduplication_criteria_2": "EXIF 数据计数", - "deduplication_info": "重复数据删除汇总", - "deduplication_info_description": "要自动预选资产并批量删除重复项,我们会考虑:", - "default_locale": "默认地区", - "default_locale_description": "根据您的浏览器地区设置日期和数字显示格式", - "delete": "删除", - "delete_action_confirmation_message": "您确定要删除此资产吗?此操作会将资产移至服务器回收站,并会提示您是否要在本地删除它", - "delete_action_prompt": "已删除 {count} 项", - "delete_album": "删除相册", - "delete_api_key_prompt": "是否确认删除此 API 密钥?", - "delete_dialog_alert": "这些资产将从 Immich 和您的设备中永久删除", - "delete_dialog_alert_local": "这些项目将从您的移动设备中永久删除,但仍然可以从 Immich 服务器中再次获取", - "delete_dialog_alert_local_non_backed_up": "部分项目还未备份至 Immich 服务器,将从您的移动设备中永久删除", - "delete_dialog_alert_remote": "这些项目将从 Immich 服务器中永久删除", - "delete_dialog_ok_force": "确认删除", - "delete_dialog_title": "永久删除", - "delete_duplicates_confirmation": "确定要永久删除这些重复项吗?", - "delete_face": "删除人脸", - "delete_key": "删除密钥", - "delete_library": "删除资产库", - "delete_link": "删除链接", - "delete_local_action_prompt": "已删除本地项目{count}项", - "delete_local_dialog_ok_backed_up_only": "仅删除已备份项目", - "delete_local_dialog_ok_force": "确认删除", - "delete_others": "删除其它", - "delete_permanently": "永久删除", - "delete_permanently_action_prompt": "已永久删除 {count} 项", - "delete_shared_link": "删除共享链接", - "delete_shared_link_dialog_title": "删除共享链接", - "delete_tag": "删除标签", - "delete_tag_confirmation_prompt": "您确定要删除“{tagName}”标签吗?", - "delete_user": "删除用户", - "deleted_shared_link": "共享链接已删除", - "deletes_missing_assets": "删除磁盘中丢失的资产", - "description": "描述", - "description_input_hint_text": "添加描述...", - "description_input_submit_error": "更新描述时出错,请检查日志以获取更多详细信息", - "deselect_all": "取消全选", - "details": "详情", - "direction": "方向", - "disable": "禁用", - "disabled": "禁用", - "disallow_edits": "不允许编辑", - "discord": "Discord 社区", - "discover": "发现", - "discovered_devices": "已发现的设备", - "dismiss_all_errors": "忽略所有错误", - "dismiss_error": "忽略错误", - "display_options": "显示选项", - "display_order": "显示顺序", - "display_original_photos": "显示原始照片", - "display_original_photos_setting_description": "在网络与原始格式兼容的情况下,查看图片或视频时优先显示原始文件而不是缩略图。这可能导致照片显示速度变慢。", - "do_not_show_again": "不再显示此信息", - "documentation": "帮助文档", - "done": "完成", - "download": "下载", - "download_action_prompt": "正在下载 {count} 个资产", - "download_canceled": "下载已取消", - "download_complete": "下载完成", - "download_enqueue": "已加入下载队列", - "download_error": "下载出错", - "download_failed": "下载失败", - "download_finished": "下载完成", - "download_include_embedded_motion_videos": "内嵌视频", - "download_include_embedded_motion_videos_description": "将实况照片中的内嵌视频作为单独文件纳入", - "download_notfound": "无法找到下载", - "download_original": "下载原始文件", - "download_paused": "下载已暂停", - "download_settings": "下载", - "download_settings_description": "管理项目下载相关设置", - "download_started": "开始下载", - "download_sucess": "下载成功", - "download_sucess_android": "媒体已下载至 DCIM/Immich", - "download_waiting_to_retry": "等待重试", - "downloading": "下载中", - "downloading_asset_filename": "下载项目“{filename}”", - "downloading_from_icloud": "从iCloud下载", - "downloading_media": "正在下载媒体", - "drop_files_to_upload": "拖放文件以上传", - "duplicates": "重复项", - "duplicates_description": "审查每组疑似重复项并标记哪些是重复的(如果有的话)", - "duration": "时长", - "edit": "编辑", - "edit_album": "编辑相册", - "edit_avatar": "编辑头像", - "edit_birthday": "编辑生日", - "edit_date": "编辑日期", - "edit_date_and_time": "编辑日期和时间", - "edit_date_and_time_action_prompt": "已编辑 {count} 项日期时间", - "edit_date_and_time_by_offset": "按时间偏移量更改日期", - "edit_date_and_time_by_offset_interval": "新日期的范围:{from} - {to}", - "edit_description": "修改描述", - "edit_description_prompt": "请选择新的描述:", - "edit_exclusion_pattern": "编辑排除规则", - "edit_faces": "编辑人脸", - "edit_key": "编辑 API 密钥", - "edit_link": "编辑链接", - "edit_location": "编辑位置", - "edit_location_action_prompt": "{count} 个位置已编辑", - "edit_location_dialog_title": "位置", - "edit_name": "编辑名称", - "edit_people": "编辑人物", - "edit_tag": "编辑标签", - "edit_title": "编辑标题", - "edit_user": "编辑用户", - "edit_workflow": "编辑工作流", - "editor": "编辑器", - "editor_close_without_save_prompt": "此更改不会被保存", - "editor_close_without_save_title": "关闭编辑器?", - "editor_confirm_reset_all_changes": "您确定要重置所有更改吗?", - "editor_flip_horizontal": "水平翻转", - "editor_flip_vertical": "垂直翻转", - "editor_orientation": "方向", - "editor_reset_all_changes": "重置更改", - "editor_rotate_left": "逆时针旋转90°", - "editor_rotate_right": "顺时针旋转90度", - "email": "邮箱", - "email_notifications": "邮件通知", - "empty_folder": "此文件夹为空", - "empty_trash": "清空回收站", - "empty_trash_confirmation": "确定要清空回收站?这将永久删除回收站中的所有项目。\n注意:该操作无法撤消!", - "enable": "启用", - "enable_backup": "启用备份", - "enable_biometric_auth_description": "输入您的PIN码以启用生物识别身份验证", - "enabled": "已启用", - "end_date": "结束日期", - "enqueued": "排队中", - "enter_wifi_name": "输入 Wi-Fi 名称", - "enter_your_pin_code": "输入您的PIN码", - "enter_your_pin_code_subtitle": "输入您的PIN码以访问此锁定文件夹", - "error": "错误", - "error_change_sort_album": "更改相册排序失败", - "error_delete_face": "删除人脸失败", - "error_getting_places": "获取位置时出错", - "error_loading_albums": "加载相册失败", - "error_loading_image": "加载图片时出错", - "error_loading_partners": "加载协作者时出错:{error}", - "error_retrieving_asset_information": "获取资产信息时出错", - "error_saving_image": "错误:{error}", - "error_tag_face_bounding_box": "标记人脸出错 - 无法获取人脸框坐标", - "error_title": "错误 - 好像出了问题", - "error_while_navigating": "跳转到文件时出错", - "errors": { - "cannot_navigate_next_asset": "无法导航到下一个项目", - "cannot_navigate_previous_asset": "无法导航到上一个项目", - "cant_apply_changes": "无法应用更改", - "cant_change_activity": "无法{enabled, select, true {禁用} other {启用}}活动", - "cant_change_asset_favorite": "无法修改项目的收藏属性", - "cant_change_metadata_assets_count": "无法修改{count, plural, one {#个项目} other {#个项目}}的元数据", - "cant_get_faces": "无法获取人脸", - "cant_get_number_of_comments": "无法获取评论数量", - "cant_search_people": "无法检索人物", - "cant_search_places": "无法检索地点", - "error_adding_assets_to_album": "添加项目到相册时出错", - "error_adding_users_to_album": "添加用户到相册时出错", - "error_deleting_shared_user": "删除共享用户时出错", - "error_downloading": "下载“{filename}”时出错", - "error_hiding_buy_button": "隐藏购买按钮时出错", - "error_removing_assets_from_album": "从相册中移除项目时出错,请到控制台获取更详细信息", - "error_selecting_all_assets": "选择所有项目时出错", - "exclusion_pattern_already_exists": "已存在相同排除规则。", - "failed_to_create_album": "创建相册失败", - "failed_to_create_shared_link": "创建共享链接失败", - "failed_to_edit_shared_link": "编辑共享链接失败", - "failed_to_get_people": "无法获取人物", - "failed_to_keep_this_delete_others": "无法保留该项目并删除其它项目", - "failed_to_load_asset": "加载项目失败", - "failed_to_load_assets": "加载项目失败", - "failed_to_load_notifications": "加载通知失败", - "failed_to_load_people": "加载人物失败", - "failed_to_remove_product_key": "移除产品密钥失败", - "failed_to_reset_pin_code": "重置PIN码失败", - "failed_to_stack_assets": "无法堆叠项目", - "failed_to_unstack_assets": "无法取消堆叠项目", - "failed_to_update_notification_status": "更新通知状态失败", - "incorrect_email_or_password": "邮箱或密码错误", - "library_folder_already_exists": "导入路径已存在。", - "paths_validation_failed": "{paths, plural, one {#条路径} other {#条路径}} 校验失败", - "profile_picture_transparent_pixels": "个人资料图片不可以包含透明像素。请放大或移动此图片。", - "quota_higher_than_disk_size": "设置的配额大于磁盘容量", - "something_went_wrong": "出了点问题", - "unable_to_add_album_users": "无法添加用户至相册", - "unable_to_add_assets_to_shared_link": "无法添加项目到共享链接", - "unable_to_add_comment": "无法添加评论", - "unable_to_add_exclusion_pattern": "无法添加排除规则", - "unable_to_add_partners": "无法添加协作者", - "unable_to_add_remove_archive": "无法{archived, select, true {从归档中移除} other {添加项目到归档}}", - "unable_to_add_remove_favorites": "无法{favorite, select, true {添加项目到收藏} other {从收藏中移除}}", - "unable_to_archive_unarchive": "无法{archived, select, true {归档} other {取消归档}}", - "unable_to_change_album_user_role": "无法更改相册用户规则", - "unable_to_change_date": "无法更改日期", - "unable_to_change_description": "无法修改描述", - "unable_to_change_favorite": "无法修改项目的收藏属性", - "unable_to_change_location": "无法更改位置", - "unable_to_change_password": "无法修改密码", - "unable_to_change_visibility": "无法修改{count, plural, one {#个人} other {#个人}}的可见性", - "unable_to_complete_oauth_login": "无法完成 OAuth 登录", - "unable_to_connect": "无法连接", - "unable_to_copy_to_clipboard": "无法复制到剪切板,请确保您在使用https访问本页", - "unable_to_create": "无法创建工作流", - "unable_to_create_admin_account": "无法创建管理员账户", - "unable_to_create_api_key": "无法创建新的 API 密钥", - "unable_to_create_library": "无法创建资产库", - "unable_to_create_user": "无法创建用户", - "unable_to_delete_album": "无法删除相册", - "unable_to_delete_asset": "无法删除项目", - "unable_to_delete_assets": "无法删除项目", - "unable_to_delete_exclusion_pattern": "无法删除排除规则", - "unable_to_delete_shared_link": "无法删除共享链接", - "unable_to_delete_user": "无法删除用户", - "unable_to_delete_workflow": "无法删除工作流", - "unable_to_download_files": "无法下载文件", - "unable_to_edit_exclusion_pattern": "无法编辑排除规则", - "unable_to_empty_trash": "无法清空回收站", - "unable_to_enter_fullscreen": "无法进入全屏", - "unable_to_exit_fullscreen": "无法退出全屏", - "unable_to_get_comments_number": "无法获取评论数量", - "unable_to_get_shared_link": "获取共享链接失败", - "unable_to_hide_person": "无法隐藏人物", - "unable_to_link_motion_video": "无法链接到动态视频", - "unable_to_link_oauth_account": "无法关联 OAuth 账户", - "unable_to_log_out_all_devices": "无法从所有设备登出", - "unable_to_log_out_device": "无法从设备登出", - "unable_to_login_with_oauth": "无法使用 OAuth 进行登录", - "unable_to_play_video": "无法播放视频", - "unable_to_reassign_assets_existing_person": "无法将项目重新分配给{name, select, null {已存在的人物} other {{name}}}", - "unable_to_reassign_assets_new_person": "无法重新分配项目给新的人物", - "unable_to_refresh_user": "无法刷新用户", - "unable_to_remove_album_users": "无法从相册中移除用户", - "unable_to_remove_api_key": "无法移除 API 密钥", - "unable_to_remove_assets_from_shared_link": "无法从共享链接中移除项目", - "unable_to_remove_library": "无法移除资产库", - "unable_to_remove_partner": "无法移除协作者", - "unable_to_remove_reaction": "无法删除回复", - "unable_to_reset_password": "无法重置密码", - "unable_to_reset_pin_code": "无法重置PIN码", - "unable_to_resolve_duplicate": "无法解决重复项", - "unable_to_restore_assets": "无法恢复项目", - "unable_to_restore_trash": "无法恢复回收站", - "unable_to_restore_user": "无法恢复用户", - "unable_to_save_album": "无法保存相册", - "unable_to_save_api_key": "无法保存 API 密钥", - "unable_to_save_date_of_birth": "无法保存出生日期", - "unable_to_save_name": "无法保存名称", - "unable_to_save_profile": "无法保存配置文件", - "unable_to_save_settings": "无法保存设置", - "unable_to_scan_libraries": "无法扫描库", - "unable_to_scan_library": "无法扫描资产库", - "unable_to_set_feature_photo": "无法设置人物头像", - "unable_to_set_profile_picture": "无法设置个人资料图片", - "unable_to_set_rating": "无法设置星级", - "unable_to_submit_job": "无法提交任务", - "unable_to_trash_asset": "无法放入回收站", - "unable_to_unlink_account": "无法取消账户链接", - "unable_to_unlink_motion_video": "无法取消链接动态视频", - "unable_to_update_album_cover": "无法更新相册封面", - "unable_to_update_album_info": "无法更新相册信息", - "unable_to_update_library": "无法更新资产库", - "unable_to_update_location": "无法更新位置", - "unable_to_update_settings": "无法更新设置", - "unable_to_update_timeline_display_status": "无法更新时间轴显示状态", - "unable_to_update_user": "无法更新用户", - "unable_to_update_workflow": "无法更新工作流", - "unable_to_upload_file": "无法上传文件" - }, - "errors_text": "错误", - "exclusion_pattern": "排除规则", - "exif": "Exif 信息", - "exif_bottom_sheet_description": "添加描述...", - "exif_bottom_sheet_description_error": "更新描述时出错", - "exif_bottom_sheet_details": "详情", - "exif_bottom_sheet_location": "位置", - "exif_bottom_sheet_no_description": "无描述", - "exif_bottom_sheet_people": "人物", - "exif_bottom_sheet_person_add_person": "添加姓名", - "exit_slideshow": "退出幻灯片放映", - "expand_all": "全部展开", - "experimental_settings_new_asset_list_subtitle": "正在处理", - "experimental_settings_new_asset_list_title": "启用实验性照片网格", - "experimental_settings_subtitle": "使用风险自负!", - "experimental_settings_title": "实验性功能", - "expire_after": "过期时间", - "expired": "已过期", - "expires_date": "过期于 {date}", - "explore": "探索", - "explorer": "资源管理器", - "export": "导出", - "export_as_json": "导出为 JSON", - "export_database": "导出数据库", - "export_database_description": "导出 SQLite 数据库", - "extension": "扩展名", - "external": "外部", - "external_libraries": "外部图库", - "external_network": "外部网络", - "external_network_sheet_info": "当未连接到指定的 Wi-Fi 网络时,应用程序将通过下方第一个可连通的 URL 访问服务器", - "face_unassigned": "未指派", - "failed": "失败", - "failed_count": "失败: {count}", - "failed_to_authenticate": "身份验证失败", - "failed_to_load_assets": "加载项目失败", - "failed_to_load_folder": "加载文件夹失败", - "favorite": "收藏", - "favorite_action_prompt": "已将 {count} 项添加到收藏", - "favorite_or_unfavorite_photo": "收藏或取消收藏照片", - "favorites": "收藏夹", - "favorites_page_no_favorites": "未找到收藏项目", - "feature_photo_updated": "人物头像已更新", - "features": "功能", - "features_in_development": "开发中的功能", - "features_setting_description": "管理 App 功能", - "file_name_or_extension": "文件名或扩展名", - "file_size": "大小", - "filename": "文件名", - "filetype": "文件类型", - "filter": "筛选器", - "filter_description": "目标项目筛选条件", - "filter_people": "筛选人物", - "filter_places": "筛选地点", - "filters": "筛选器", - "find_them_fast": "按名称快速搜索", - "first": "第一个", - "fix_incorrect_match": "修复不正确的匹配", - "folder": "文件夹", - "folder_not_found": "未找到文件夹", - "folders": "文件夹", - "folders_feature_description": "在文件夹视图中浏览文件系统上的照片和视频", - "forgot_pin_code_question": "忘记您的PIN码了?", - "forward": "向前", - "free_up_space": "释放空间", - "free_up_space_description": "将已备份的照片和视频移至设备回收站以释放空间。服务器上的副本将保持安全。", - "free_up_space_settings_subtitle": "释放设备存储空间", - "full_path": "完整路径:{path}", - "gcast_enabled": "Google Cast 投屏", - "gcast_enabled_description": "该功能需要加载来自 Google 的外部资源。", - "general": "通用", - "geolocation_instruction_location": "点击带有GPS坐标的资产以使用其位置,或直接从地图上选择位置", - "get_help": "获取帮助", - "get_people_error": "获取人物错误", - "get_wifiname_error": "无法获取 Wi-Fi 名称。确保已授予必要的权限,并已连接到 Wi-Fi 网络", - "getting_started": "入门", - "go_back": "返回", - "go_to_folder": "进入文件夹", - "go_to_search": "搜索", - "gps": "有GPS信息", - "gps_missing": "无GPS信息", - "grant_permission": "获取权限", - "group_albums_by": "相册分组依据...", - "group_country": "按国家分组", - "group_no": "未分组", - "group_owner": "按所有者分组", - "group_places_by": "地点分组依据...", - "group_year": "按年分组", - "haptic_feedback_switch": "启用振动反馈", - "haptic_feedback_title": "振动反馈", - "has_quota": "配额大小", - "hash_asset": "哈希项目", - "hashed_assets": "已哈希的项目", - "hashing": "正在进行哈希检验", - "header_settings_add_header_tip": "添加标头", - "header_settings_field_validator_msg": "设置不可为空", - "header_settings_header_name_input": "标头名称", - "header_settings_header_value_input": "标头值", - "headers_settings_tile_title": "自定义代理标头", - "height": "高度", - "hi_user": "您好,{name}({email})", - "hide_all_people": "隐藏所有人物", - "hide_gallery": "隐藏相册", - "hide_named_person": "隐藏人物“{name}”", - "hide_password": "隐藏密码", - "hide_person": "隐藏人物", - "hide_schema": "隐藏架构", - "hide_text_recognition": "隐藏文本识别", - "hide_unnamed_people": "隐藏未命名的人物", - "home_page_add_to_album_conflicts": "已向相册 {album} 中添加 {added} 项。其中 {failed} 项在相册中已存在。", - "home_page_add_to_album_err_local": "暂无法将本地项目添加到相册中,跳过", - "home_page_add_to_album_success": "已向相册 {album} 中添加 {added} 项。", - "home_page_album_err_partner": "暂无法将协作者的项目添加到相册,跳过", - "home_page_archive_err_local": "暂无法归档本地项目,跳过", - "home_page_archive_err_partner": "无法存档协作者的项目,跳过", - "home_page_building_timeline": "正在生成时间线", - "home_page_delete_err_partner": "无法删除协作者的项目,跳过", - "home_page_delete_remote_err_local": "远程项目删除模式,跳过本地项目", - "home_page_favorite_err_local": "暂无法收藏本地项目,跳过", - "home_page_favorite_err_partner": "暂无法收藏协作者的项目,跳过", - "home_page_first_time_notice": "如果这是您第一次使用该应用程序,请确保选择一个要备份的本地相册,以便可以在时间线中预览该相册中的照片和视频", - "home_page_locked_error_local": "无法将本地项目移动到锁定文件夹,跳过", - "home_page_locked_error_partner": "无法将协作者的项目移动到锁定文件夹,跳过", - "home_page_share_err_local": "暂无法通过链接共享本地项目,跳过", - "home_page_upload_err_limit": "一次最多只能上传 30 个项目,跳过", - "host": "服务器", - "hour": "时", - "hours": "小时", - "id": "ID", - "idle": "空闲", - "ignore_icloud_photos": "忽略 iCloud 照片", - "ignore_icloud_photos_description": "存储在 iCloud 中的照片不会上传至 Immich 服务器", - "image": "图片", - "image_alt_text_date": "在{date}拍摄的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_1_person": "{date}拍摄的包含{person1}的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_2_people": "{date}拍摄的包含{person1}和{person2}的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_3_people": "{date}拍摄的包含{person1}、{person2}和{person3}的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_4_or_more_people": "{date}拍摄的包含{person1}、{person2}及{additionalCount, number}个其他人物的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_place": "{date}在{country}{city}拍摄的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_place_1_person": "{date}在{country}{city}拍摄的包含{person1}的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_place_2_people": "{date}在{country}{city}拍摄的包含{person1}和{person2}的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_place_3_people": "{date}在{country}{city}拍摄的包含{person1}、{person2}和{person3}的{isVideo, select, true {视频} other {照片}}", - "image_alt_text_date_place_4_or_more_people": "{date}在{country}{city}拍摄的包含{person1}、{person2}及其他{additionalCount, number}个人物的{isVideo, select, true {视频} other {照片}}", - "image_saved_successfully": "图片已保存", - "image_viewer_page_state_provider_download_started": "下载启动", - "image_viewer_page_state_provider_download_success": "下载成功", - "image_viewer_page_state_provider_share_error": "共享出错", - "immich_logo": "Immich 标志", - "immich_web_interface": "Immich Web 界面", - "import_from_json": "从 JSON 导入", - "import_path": "导入路径", - "in_albums": "在{count, plural, one {# 个相册} other {# 个相册}}中", - "in_archive": "在归档中", - "in_year": "{year}年", - "in_year_selector": "在", - "include_archived": "包括已归档", - "include_shared_albums": "包括共享相册", - "include_shared_partner_assets": "包括协作者共享项目", - "individual_share": "个人分享", - "individual_shares": "个人分享", - "info": "信息", - "interval": { - "day_at_onepm": "每天下午 1 点", - "hours": "每隔 {hours, plural, one {小时} other {{hours, number} 小时}}", - "night_at_midnight": "每晚 0 点", - "night_at_twoam": "每晚凌晨 2 点" - }, - "invalid_date": "无效的日期", - "invalid_date_format": "无效的日期格式", - "invite_people": "邀请人员", - "invite_to_album": "邀请加入相册", - "ios_debug_info_fetch_ran_at": "运行拉取 {dateTime}", - "ios_debug_info_last_sync_at": "上次同步 {dateTime}", - "ios_debug_info_no_processes_queued": "没有后台进程在队列中", - "ios_debug_info_no_sync_yet": "尚未后台同步任务在运行", - "ios_debug_info_processes_queued": "{count, plural, one {{count} 个后台进程在队列中} other {{count} 个后台进程在队列中}}", - "ios_debug_info_processing_ran_at": "运行处理 {dateTime}", - "items_count": "{count, plural, one {#个项目} other {#个项目}}", - "jobs": "任务", - "json_editor": "JSON编辑器", - "json_error": "JSON错误", - "keep": "保留", - "keep_albums": "保留相册", - "keep_albums_count": "保留 {count} {count, plural, one {个相册} other {个相册}}", - "keep_all": "全部保留", - "keep_description": "选择释放空间时保留在设备上的内容。", - "keep_favorites": "保留收藏夹", - "keep_on_device": "保留在设备上", - "keep_on_device_hint": "选择要保留在本设备上的项目", - "keep_this_delete_others": "保留此项,其余删除", - "keeping": "保留: {items}", - "kept_this_deleted_others": "保留该项目并删除 {count, plural, one {# 个项目} other {# 个项目}}", - "keyboard_shortcuts": "键盘快捷键", - "language": "语言", - "language_no_results_subtitle": "尝试调整您的搜索词", - "language_no_results_title": "未找到对应语言", - "language_search_hint": "搜索语言...", - "language_setting_description": "选择您的语言偏好", - "large_files": "大文件", - "last": "最后一个", - "last_months": "{count, plural, one {上个月} other {最近 # 个月}}", - "last_seen": "最后上线于", - "latest_version": "最新版本", - "latitude": "纬度", - "leave": "离开", - "leave_album": "离开相册", - "lens_model": "镜头型号", - "let_others_respond": "允许他人回应", - "level": "等级", - "library": "资产库", - "library_add_folder": "添加文件夹", - "library_edit_folder": "编辑文件夹", - "library_options": "资产库选项", - "library_page_device_albums": "设备上的相册", - "library_page_new_album": "新建相册", - "library_page_sort_asset_count": "项目数量", - "library_page_sort_created": "创建日期", - "library_page_sort_last_modified": "上次修改", - "library_page_sort_title": "相册标题", - "licenses": "许可证", - "light": "浅色", - "like": "喜欢", - "like_deleted": "已删除的收藏", - "link_motion_video": "链接动态视频", - "link_to_oauth": "绑定 OAuth", - "linked_oauth_account": "已绑定 OAuth 账户", - "list": "列表", - "loading": "加载中", - "loading_search_results_failed": "加载搜索结果失败", - "local": "本地", - "local_asset_cast_failed": "无法投屏未上传至服务器的项目", - "local_assets": "本地项目", - "local_id": "本地 ID", - "local_media_summary": "本地媒体摘要", - "local_network": "本地网络", - "local_network_sheet_info": "当使用指定的 Wi-Fi 网络时,应用程序将通过此 URL 访问服务器", - "location": "位置", - "location_permission": "定位权限", - "location_permission_content": "使用自动切换功能,Immich 需要精确定位权限,以获取当前 Wi-Fi 网络名称", - "location_picker_choose_on_map": "在地图上定位", - "location_picker_latitude_error": "请输入有效的纬度", - "location_picker_latitude_hint": "请在此处输入纬度", - "location_picker_longitude_error": "请输入有效的经度", - "location_picker_longitude_hint": "请在此处输入经度", - "lock": "锁定", - "locked_folder": "锁定文件夹", - "log_detail_title": "日志详细信息", - "log_out": "注销", - "log_out_all_devices": "注销所有设备", - "logged_in_as": "以 {user} 身份登录", - "logged_out_all_devices": "从所有设备注销", - "logged_out_device": "从设备注销", - "login": "登录", - "login_disabled": "登录已被禁用", - "login_form_api_exception": "API 异常,请检查服务器地址并重试。", - "login_form_back_button_text": "后退", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://您的服务器地址:端口", - "login_form_endpoint_url": "服务器链接地址", - "login_form_err_http": "请注明 http:// 或 https://", - "login_form_err_invalid_email": "无效的电子邮箱", - "login_form_err_invalid_url": "无效的地址", - "login_form_err_leading_whitespace": "带有前导空格", - "login_form_err_trailing_whitespace": "带有尾随空格", - "login_form_failed_get_oauth_server_config": "使用 OAuth 登录时错误,请检查服务器地址", - "login_form_failed_get_oauth_server_disable": "OAuth 功能在此服务器上不可用", - "login_form_failed_login": "登录失败,请检查服务器地址、邮箱和密码", - "login_form_handshake_exception": "与服务器通信时出现握手异常。如果您使用的是自签名证书,请在设置中启用自签名证书支持。", - "login_form_password_hint": "密码", - "login_form_save_login": "保持登录", - "login_form_server_empty": "输入服务器地址。", - "login_form_server_error": "无法连接到服务器。", - "login_has_been_disabled": "登录已禁用。", - "login_password_changed_error": "更新密码时出错", - "login_password_changed_success": "密码更新成功", - "logout_all_device_confirmation": "确定要从所有设备注销?", - "logout_this_device_confirmation": "确定要从本设备注销?", - "logs": "日志", - "longitude": "经度", - "look": "样式", - "loop_videos": "循环视频", - "loop_videos_description": "启用在详细信息中自动循环播放视频。", - "main_branch_warning": "您当前使用的是开发版;我们强烈建议您使用正式发行版(release版)!", - "main_menu": "主菜单", - "maintenance_action_restore": "正在恢复数据库", - "maintenance_description": "Immich已进入维护模式。", - "maintenance_end": "退出维护模式", - "maintenance_end_error": "退出维护模式失败。", - "maintenance_logged_in_as": "当前以{user}身份登录", - "maintenance_restore_from_backup": "从备份中恢复", - "maintenance_restore_library": "恢复您的资产库", - "maintenance_restore_library_confirm": "如果以上信息无误,请继续进行备份恢复!", - "maintenance_restore_library_description": "正在恢复数据库", - "maintenance_restore_library_folder_has_files": "{folder} 包含 {count} 个文件夹", - "maintenance_restore_library_folder_no_files": "{folder} 缺少文件!", - "maintenance_restore_library_folder_pass": "可读且可写", - "maintenance_restore_library_folder_read_fail": "不可读", - "maintenance_restore_library_folder_write_fail": "不可写", - "maintenance_restore_library_hint_missing_files": "您可能丢失了重要文件", - "maintenance_restore_library_hint_regenerate_later": "您可以在设置中稍后重新生成这些内容", - "maintenance_restore_library_hint_storage_template_missing_files": "正在使用存储模板?您可能丢失了文件", - "maintenance_restore_library_loading": "正在加载完整性检查与启发式分析…", - "maintenance_task_backup": "正在创建现有数据库的备份…", - "maintenance_task_migrations": "正在运行数据库迁移…", - "maintenance_task_restore": "正在恢复选定的备份…", - "maintenance_task_rollback": "恢复失败,正在回滚到还原点…", - "maintenance_title": "系统暂时不可用", - "make": "品牌", - "manage_geolocation": "管理坐标位置", - "manage_media_access_rationale": "正确处理将资产移至垃圾桶并将其从垃圾桶中恢复需要此许可。", - "manage_media_access_settings": "打开设置", - "manage_media_access_subtitle": "允许Immich应用程序管理和移动媒体文件。", - "manage_media_access_title": "媒体管理访问", - "manage_shared_links": "管理共享链接", - "manage_sharing_with_partners": "管理与协作者的共享", - "manage_the_app_settings": "管理应用设置", - "manage_your_account": "管理您的账户", - "manage_your_api_keys": "管理您的 API 密钥", - "manage_your_devices": "管理已登录设备", - "manage_your_oauth_connection": "管理您的 OAuth 绑定", - "map": "地图", - "map_assets_in_bounds": "{count, plural, =0 {此处无照片} one {# 张照片} other {# 张照片}}", - "map_cannot_get_user_location": "无法获取用户位置", - "map_location_dialog_yes": "是", - "map_location_picker_page_use_location": "使用此位置", - "map_location_service_disabled_content": "需要启用定位服务才能显示当前位置相关的项目。要现在启用吗?", - "map_location_service_disabled_title": "定位服务已禁用", - "map_marker_for_images": "{country} {city}的图像地图标记", - "map_marker_with_image": "带图像的地图标记", - "map_no_location_permission_content": "需要位置权限才能显示与当前位置相关的项目。要现在就授予位置权限吗?", - "map_no_location_permission_title": "位置权限被拒绝", - "map_settings": "地图设置", - "map_settings_dark_mode": "深色模式", - "map_settings_date_range_option_day": "过去24小时", - "map_settings_date_range_option_days": "{days} 天前", - "map_settings_date_range_option_year": "1年前", - "map_settings_date_range_option_years": "{years} 年前", - "map_settings_dialog_title": "地图设置", - "map_settings_include_show_archived": "包括已归档项目", - "map_settings_include_show_partners": "包含协作者", - "map_settings_only_show_favorites": "仅显示收藏的项目", - "map_settings_theme_settings": "地图主题", - "map_zoom_to_see_photos": "缩小以查看项目", - "mark_all_as_read": "全部标记为已读", - "mark_as_read": "标记为已读", - "marked_all_as_read": "已全部标记为已读", - "matches": "匹配", - "matching_assets": "匹配资产", - "media_type": "媒体类型", - "memories": "那年今日", - "memories_all_caught_up": "已全部看完", - "memories_check_back_tomorrow": "明天再看", - "memories_setting_description": "管理回忆中的内容", - "memories_start_over": "再看一次", - "memories_swipe_to_close": "上划关闭", - "memory": "回忆", - "memory_lane_title": "记忆线{title}", - "menu": "菜单", - "merge": "合并", - "merge_people": "合并人物", - "merge_people_limit": "每次最多只能合并 5 个人", - "merge_people_prompt": "是否确定合并所选人物?该操作无法撤销。", - "merge_people_successfully": "合并人物成功", - "merged_people_count": "已合并{count, plural, one {# 个人} other {# 个人}}", - "minimize": "最小化", - "minute": "分", - "minutes": "分钟", - "mirror_horizontal": "水平", - "mirror_vertical": "垂直", - "missing": "缺失", - "mobile_app": "移动端APP", - "mobile_app_download_onboarding_note": "下载移动应用以访问这些选项", - "model": "型号", - "month": "月", - "monthly_title_text_date_format": "y MMMM", - "more": "更多", - "move": "移动", - "move_down": "向下移动", - "move_off_locked_folder": "移出锁定文件夹", - "move_to": "移动到", - "move_to_device_trash": "移至设备回收站", - "move_to_lock_folder_action_prompt": "已将 {count} 项添加到锁定文件夹", - "move_to_locked_folder": "移动到锁定文件夹", - "move_to_locked_folder_confirmation": "这些照片和视频将从所有相册中移除,只能在锁定文件夹中查看", - "move_up": "向上移动", - "moved_to_archive": "已归档 {count, plural, one {# 个项目} other {# 个项目}}", - "moved_to_library": "已移动 {count, plural, one {# 个项目} other {# 个项目}} 到图库", - "moved_to_trash": "已放入回收站", - "multiselect_grid_edit_date_time_err_read_only": "无法编辑只读项目的日期,跳过", - "multiselect_grid_edit_gps_err_read_only": "无法编辑只读项目的位置信息,跳过", - "mute_memories": "静音回忆", - "my_albums": "我的相册", - "name": "名称", - "name_or_nickname": "名称或昵称", - "name_required": "名称是必填项", - "navigate": "导航", - "navigate_to_time": "导航至时间", - "network_requirement_photos_upload": "使用蜂窝数据备份照片", - "network_requirement_videos_upload": "使用蜂窝数据备份视频", - "network_requirements": "网络要求", - "network_requirements_updated": "网络要求发生变化,正在重置备份队列", - "networking_settings": "网络", - "networking_subtitle": "管理服务器接口设置", - "never": "永不过期", - "new_album": "新相册", - "new_api_key": "新增 API 密钥", - "new_date_range": "新的日期范围", - "new_password": "新密码", - "new_person": "新人物", - "new_pin_code": "新的PIN码", - "new_pin_code_subtitle": "这是您第一次访问此锁定文件夹。创建一个PIN码以安全访问此页面", - "new_timeline": "切换到新版时间线", - "new_update": "新版本", - "new_user_created": "已创建新用户", - "new_version_available": "有新版本发布啦", - "newest_first": "最新优先", - "next": "下一个", - "next_memory": "下一个", - "no": "否", - "no_actions_added": "尚未添加动作", - "no_albums_found": "未找到相册", - "no_albums_message": "创建相册以整理照片和视频", - "no_albums_with_name_yet": "貌似您还没有此名字的相册。", - "no_albums_yet": "貌似您还没有创建相册。", - "no_archived_assets_message": "归档照片和视频以便在照片视图中隐藏它们", - "no_assets_message": "点击此处上传你的第一张照片", - "no_assets_to_show": "没有要显示的资产", - "no_cast_devices_found": "未找到投放设备", - "no_checksum_local": "没有可用的校验和-无法获取本地资产", - "no_checksum_remote": "没有可用的校验和-无法获取远程资产", - "no_configuration_needed": "不需要配置", - "no_devices": "无授权设备", - "no_duplicates_found": "未发现重复项。", - "no_exif_info_available": "没有可用的 EXIF 信息", - "no_explore_results_message": "上传更多照片来探索。", - "no_favorites_message": "添加到收藏夹,快速查找最佳图片和视频", - "no_filters_added": "尚未添加筛选", - "no_libraries_message": "创建外部图库来查看您的照片和视频", - "no_local_assets_found": "未找到具有此校验和的本地资产", - "no_location_set": "未设置地点", - "no_locked_photos_message": "锁定文件夹中的照片和视频将被隐藏,不会在您浏览、搜索图库时出现。", - "no_name": "未命名", - "no_notifications": "没有通知", - "no_people_found": "未找到匹配的人物", - "no_places": "无位置", - "no_remote_assets_found": "未找到具有此校验和的远程资产", - "no_results": "无搜索结果", - "no_results_description": "尝试使用同义词或更通用的关键词", - "no_shared_albums_message": "创建相册以共享照片和视频", - "no_uploads_in_progress": "没有正在进行的上传", - "none": "无", - "not_allowed": "不允许", - "not_available": "不适用", - "not_in_any_album": "不在任何相册中", - "not_selected": "未选择", - "note_apply_storage_label_to_previously_uploaded assets": "提示:要将存储标签应用于之前上传的项目,请运行此", - "notes": "提示", - "nothing_here_yet": "这里什么都没有", - "notification_permission_dialog_content": "要启用通知,请转到“设置”,并选择“允许”。", - "notification_permission_list_tile_content": "授予通知权限。", - "notification_permission_list_tile_enable_button": "启用通知", - "notification_permission_list_tile_title": "通知权限", - "notification_toggle_setting_description": "启用邮件通知", - "notifications": "通知", - "notifications_setting_description": "管理通知", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium配置器", - "obtainium_configurator_instructions": "使用 Obtainium 直接从 Immich GitHub 的发布区安装和更新 Android 应用程序。创建 API Key并选择一个变体来创建您的 Obtainium 配置链接", - "ocr": "文本识别", - "official_immich_resources": "Immich 官方资源", - "offline": "离线", - "offset": "偏移量", - "ok": "确定", - "oldest_first": "最旧优先", - "on_this_device": "在此设备", - "onboarding": "盛大开启", - "onboarding_locale_description": "选择您偏好的语言。您可以在设置中随时更改此选项。", - "onboarding_privacy_description": "以下(可选)功能依赖外部服务,可随时在设置中禁用。", - "onboarding_server_welcome_description": "让我们为您设置系统并配置一些通用设置。", - "onboarding_theme_description": "选择服务的颜色主题。稍后可以在设置中进行修改。", - "onboarding_user_welcome_description": "即刻起程!", - "onboarding_welcome_user": "欢迎,{user}", - "online": "在线", - "only_favorites": "仅显示已收藏", - "open": "打开", - "open_in_map_view": "在地图视图中打开", - "open_in_openstreetmap": "在 OpenStreetMap 中打开", - "open_the_search_filters": "打开搜索筛选", - "options": "选项", - "or": "或", - "organize_into_albums": "整理成相册", - "organize_into_albums_description": "使用当前同步设置将现有照片放入相册", - "organize_your_library": "整理您的图库", - "original": "原图", - "other": "其它", - "other_devices": "其它设备", - "other_entities": "其他实体", - "other_variables": "其它变量", - "owned": "我的", - "owner": "所有者", - "page": "页面", - "partner": "协作者", - "partner_can_access": "{partner}可以访问", - "partner_can_access_assets": "除归档和删除之外的所有照片和视频", - "partner_can_access_location": "定位照片拍摄位置", - "partner_list_user_photos": "{user}的照片", - "partner_list_view_all": "展示全部", - "partner_page_empty_message": "您的照片尚未与任何协作者共享。", - "partner_page_no_more_users": "无需添加更多用户", - "partner_page_partner_add_failed": "添加协作者失败", - "partner_page_select_partner": "选择协作者", - "partner_page_shared_to_title": "共享给", - "partner_page_stop_sharing_content": "{partner} 将无法再访问您的照片。", - "partner_sharing": "协作者共享", - "partners": "协作者", - "password": "密码", - "password_does_not_match": "密码不匹配", - "password_required": "需要密码", - "password_reset_success": "密码重置成功", - "past_durations": { - "days": "{days, plural, one {天} other {#天}}前", - "hours": "{hours, plural, one {小时} other {#小时}}前", - "years": "{years, plural, one {年} other {#年}}前" - }, - "path": "路径", - "pattern": "模式", - "pause": "暂停", - "pause_memories": "暂停回忆", - "paused": "已暂停", - "pending": "待处理", - "people": "人物", - "people_edits_count": "{count, plural, one {#个人物} other {#个人物}}已编辑", - "people_feature_description": "按人物分组进行浏览照片和视频", - "people_selected": "{count, plural, one {已选择 # 人} other {已选择 # 人}}", - "people_sidebar_description": "在侧边栏中显示“人物”链接", - "permanent_deletion_warning": "永久删除警告", - "permanent_deletion_warning_setting_description": "当永久删除项目时显示警告", - "permanently_delete": "永久删除", - "permanently_delete_assets_count": "永久删除{count, plural, one {项目} other {项目}}", - "permanently_delete_assets_prompt": "确定要永久删除{count, plural, other {这#个项目?}}此操作会一并将{count, plural, one {它} other {它们}}从其所属相册中移除。", - "permanently_deleted_asset": "永久删除的项目", - "permanently_deleted_assets_count": "{count, plural, one {#个项目} other {#个项目}}已删除", - "permission": "权限", - "permission_empty": "权限不能为空", - "permission_onboarding_back": "返回", - "permission_onboarding_continue_anyway": "仍然继续", - "permission_onboarding_get_started": "开始使用", - "permission_onboarding_go_to_settings": "转到设置", - "permission_onboarding_permission_denied": "权限被拒:要使用 Immich,请在“设置”中授予照片和视频权限。", - "permission_onboarding_permission_granted": "已授权!一切就绪。", - "permission_onboarding_permission_limited": "权限受限:要让 Immich 备份和管理您的整个图库收藏,请在“设置”中授予照片和视频权限。", - "permission_onboarding_request": "Immich 需要权限才能查看您的照片和视频。", - "person": "人物", - "person_age_months": "{months, plural, one {# 个月} other {# 个月}}", - "person_age_year_months": "1 岁, {months, plural, one {# 个月} other {# 个月}}", - "person_age_years": "{years, plural, other {# 岁}}", - "person_birthdate": "出生于{date}", - "person_hidden": "{name}{hidden, select, true {(已隐藏)} other {}}", - "person_recognized": "识别出的人物", - "person_selected": "选择的人物", - "photo_shared_all_users": "看起来您已与所有用户共享了此相册,或者您根本没有任何用户可共享。", - "photos": "照片", - "photos_and_videos": "照片 & 视频", - "photos_count": "{count, plural, one {{count, number}张照片} other {{count, number}张照片}}", - "photos_from_previous_years": "过往的今昔瞬间", - "photos_only": "仅照片", - "pick_a_location": "选择位置", - "pick_custom_range": "自定义范围", - "pick_date_range": "选择日期范围", - "pin_code_changed_successfully": "修改PIN码成功", - "pin_code_reset_successfully": "重置PIN码成功", - "pin_code_setup_successfully": "设置PIN码成功", - "pin_verification": "PIN码验证", - "place": "地点", - "places": "地点", - "places_count": "{count, plural, one {{count, number} 个地点} other {{count, number} 个地点}}", - "play": "播放", - "play_memories": "播放回忆", - "play_motion_photo": "播放动态图片", - "play_or_pause_video": "播放或暂停视频", - "play_original_video": "播放原始视频", - "play_original_video_setting_description": "更倾向于播放原始视频,而不是转码视频。如果原始资源不兼容,则可能无法正确播放。", - "play_transcoded_video": "播放转码视频", - "please_auth_to_access": "请进行身份验证以访问", - "port": "端口", - "preferences_settings_subtitle": "管理应用的偏好设置", - "preferences_settings_title": "偏好设置", - "preparing": "准备中", - "preset": "预设", - "preview": "预览", - "previous": "上一个", - "previous_memory": "上一个", - "previous_or_next_day": "昨天/明天", - "previous_or_next_month": "上个月/下个月", - "previous_or_next_photo": "下一张/上一张", - "previous_or_next_year": "明年/去年", - "primary": "首要", - "privacy": "隐私", - "profile": "详情", - "profile_drawer_app_logs": "日志", - "profile_drawer_client_server_up_to_date": "客户端和服务端都是最新的", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "只读模式已启用。长按用户头像图标退出。", - "profile_image_of_user": "{user}的个人资料图片", - "profile_picture_set": "个人资料图片已设置。", - "public_album": "公开相册", - "public_share": "公开共享", - "purchase_account_info": "支持者", - "purchase_activated_subtitle": "感谢您对 Immich 和开源软件的支持", - "purchase_activated_time": "激活于{date}", - "purchase_activated_title": "您的密钥已成功激活", - "purchase_button_activate": "激活", - "purchase_button_buy": "购买", - "purchase_button_buy_immich": "购买 Immich", - "purchase_button_never_show_again": "不再显示", - "purchase_button_reminder": "30 天内不再显示", - "purchase_button_remove_key": "移除密钥", - "purchase_button_select": "选择", - "purchase_failed_activation": "激活失败!请检查您的邮箱以获取正确的产品密钥!", - "purchase_individual_description_1": "适用于个人", - "purchase_individual_description_2": "支持者状态", - "purchase_individual_title": "个人", - "purchase_input_suggestion": "已有一个产品密钥?请在下方输入密钥", - "purchase_license_subtitle": "购买 Immich 以支持此服务的持续发展", - "purchase_lifetime_description": "终身许可", - "purchase_option_title": "购买选项", - "purchase_panel_info_1": "开发 Immich 需要大量的时间和精力,我们有全职工程师在努力将其做到最好。我们的使命是通过开源软件和道德商业实践,为开发者提供可持续的收入来源,并创建一个尊重隐私的生态系统,提供一个可以真正替代现有剥削性云服务的选择。", - "purchase_panel_info_2": "由于我们承诺不添加付费功能,此次购买不会为您提供 Immich 的任何额外功能。我们依靠像您这样的用户来支持 Immich 的持续开发。", - "purchase_panel_title": "支持这个项目", - "purchase_per_server": "每台服务器", - "purchase_per_user": "每个用户", - "purchase_remove_product_key": "移除产品密钥", - "purchase_remove_product_key_prompt": "您确定要删除产品密钥吗?", - "purchase_remove_server_product_key": "移除服务器产品密钥", - "purchase_remove_server_product_key_prompt": "您确定要删除服务器产品密钥吗?", - "purchase_server_description_1": "适用于整个服务器", - "purchase_server_description_2": "支持者状态", - "purchase_server_title": "服务器", - "purchase_settings_server_activated": "服务器产品密钥正在由管理员管理", - "query_asset_id": "查询资产ID", - "queue_status": "排队中 {count}/{total}", - "rate_asset": "资产星级", - "rating": "星级", - "rating_clear": "删除星级", - "rating_count": "{count, plural, one {#星} other {#星}}", - "rating_description": "在信息面板中展示 EXIF 星级", - "rating_set": "已设置为 {rating, plural, one {# 星} other {# 星}}", - "reaction_options": "回复选项", - "read_changelog": "阅读更新日志", - "readonly_mode_disabled": "只读模式已禁用", - "readonly_mode_enabled": "只读模式已启用", - "ready_for_upload": "准备上传", - "reassign": "重新指派", - "reassigned_assets_to_existing_person": "重新指派{count, plural, one {#个项目} other {#个项目}}到{name, select, null {已存在的人物} other {{name}}}", - "reassigned_assets_to_new_person": "重新指派{count, plural, one {#个项目} other {#个项目}}到新的人物", - "reassing_hint": "指派选择的项目到已存在的人物", - "recent": "最近", - "recent-albums": "最近的相册", - "recent_searches": "最近搜索", - "recently_added": "近期添加", - "recently_added_page_title": "最近添加", - "recently_taken": "近期拍摄", - "recently_taken_page_title": "最近拍摄", - "refresh": "刷新", - "refresh_encoded_videos": "刷新已编码的视频", - "refresh_faces": "刷新人脸", - "refresh_metadata": "刷新元数据", - "refresh_thumbnails": "刷新缩略图", - "refreshed": "已刷新", - "refreshes_every_file": "重新扫描所有现有文件和新文件", - "refreshing_encoded_video": "正在刷新已编码视频", - "refreshing_faces": "刷新面部识别", - "refreshing_metadata": "刷新元数据", - "regenerating_thumbnails": "重新生成缩略图", - "remote": "远程", - "remote_assets": "远程项目", - "remote_media_summary": "远程媒体摘要", - "remove": "擦除", - "remove_assets_album_confirmation": "确定要从图库中移除{count, plural, one {#个项目} other {#个项目}}?", - "remove_assets_shared_link_confirmation": "确定要从共享链接中移除{count, plural, one {#个项目} other {#个项目}}?", - "remove_assets_title": "移除项目?", - "remove_custom_date_range": "取消自定义日期范围", - "remove_deleted_assets": "彻底删除文件", - "remove_from_album": "从相册中移除", - "remove_from_album_action_prompt": "从相册中移除了 {count} 项", - "remove_from_favorites": "移出收藏", - "remove_from_lock_folder_action_prompt": "已从锁定的文件夹中移除 {count} 项", - "remove_from_locked_folder": "从锁定文件夹中移除", - "remove_from_locked_folder_confirmation": "您确定要将这些照片和视频移出锁定文件夹吗?移出后它们将会在您的图库中可见。", - "remove_from_shared_link": "从共享链接中移除", - "remove_memory": "移出回忆区", - "remove_photo_from_memory": "从当前回忆区移除照片", - "remove_tag": "移除标签", - "remove_url": "移除 URL", - "remove_user": "移除用户", - "removed_api_key": "已移除 API 密钥:{name}", - "removed_from_archive": "从归档中移除", - "removed_from_favorites": "从收藏中移除", - "removed_from_favorites_count": "从收藏中移除{count, plural, other {#项}}", - "removed_memory": "已删除的回忆", - "removed_photo_from_memory": "从回忆区中删除的照片", - "removed_tagged_assets": "从 {count, plural, one {# 个项目} other {# 个项目}}中删除标签", - "rename": "重命名", - "repair": "修复", - "repair_no_results_message": "未跟踪和缺失的文件将在此处显示", - "replace_with_upload": "上传并替换", - "repository": "库", - "require_password": "需要密码", - "require_user_to_change_password_on_first_login": "用户在首次登录时必须更改密码", - "rescan": "重新扫描", - "reset": "重置", - "reset_password": "重置密码", - "reset_people_visibility": "重置人物识别", - "reset_pin_code": "重置PIN码", - "reset_pin_code_description": "如果您忘记了您的PIN码,您可以联系服务器管理员进行重置", - "reset_pin_code_success": "成功重置PIN码", - "reset_pin_code_with_password": "您始终可以使用您的密码来重置您的PIN码", - "reset_sqlite": "重置 SQLite 数据库", - "reset_sqlite_confirmation": "您确定要重置 SQLite 数据库吗?您需要注销并重新登录才能重新同步数据", - "reset_sqlite_success": "已成功重置 SQLite 数据库", - "reset_to_default": "恢复默认值", - "resolution": "分辨率", - "resolve_duplicates": "处理重复项", - "resolved_all_duplicates": "处理所有重复项", - "restore": "恢复", - "restore_all": "恢复全部", - "restore_trash_action_prompt": "从回收站中恢复了 {count} 项", - "restore_user": "恢复用户", - "restored_asset": "已恢复项目", - "resume": "继续", - "resume_paused_jobs": "继续 {count, plural, one {# 已暂停的任务} other {# 已暂停的任务}}", - "retry_upload": "重新上传", - "review_duplicates": "检查重复项", - "review_large_files": "查看大文件", - "role": "选择用户权限", - "role_editor": "可编辑", - "role_viewer": "仅查看", - "running": "正在运行", - "save": "保存", - "save_to_gallery": "保存到图库", - "saved": "已保存", - "saved_api_key": "已保存的 API 密钥", - "saved_profile": "已保存资料", - "saved_settings": "已保存设置", - "say_something": "说点什么", - "scaffold_body_error_occurred": "发生错误", - "scan": "扫描", - "scan_all_libraries": "扫描所有图库", - "scan_library": "扫描", - "scan_settings": "扫描设置", - "scanning": "扫描中", - "scanning_for_album": "扫描相册中...", - "search": "搜索", - "search_albums": "搜索相册", - "search_by_context": "通过描述的场景查找", - "search_by_description": "通过描述查找", - "search_by_description_example": "在沙巴徒步的日子", - "search_by_filename": "通过文件名或扩展名查找", - "search_by_filename_example": "如 IMG_1234.JPG 或 PNG", - "search_by_ocr": "通过文本识别查找", - "search_by_ocr_example": "拿铁咖啡", - "search_camera_lens_model": "按镜头型号查找...", - "search_camera_make": "按相机品牌查找...", - "search_camera_model": "按相机型号查找...", - "search_city": "按城市查找...", - "search_country": "按国家查找...", - "search_filter_apply": "应用筛选", - "search_filter_camera_title": "选择相机类型", - "search_filter_date": "日期", - "search_filter_date_interval": "从{start}到{end}", - "search_filter_date_title": "选择日期范围", - "search_filter_display_option_not_in_album": "不在相册中", - "search_filter_display_options": "显示选项", - "search_filter_filename": "通过文件名搜索", - "search_filter_location": "位置", - "search_filter_location_title": "选择位置", - "search_filter_media_type": "媒体类型", - "search_filter_media_type_title": "选择媒体类型", - "search_filter_ocr": "通过文本识别搜索", - "search_filter_people_title": "选择人物", - "search_filter_star_rating": "星级评分", - "search_for": "查找", - "search_for_existing_person": "查找已有人物", - "search_no_more_result": "无更多结果", - "search_no_people": "找不到人物", - "search_no_people_named": "人物“{name}”不存在", - "search_no_result": "未找到结果,请尝试不同的搜索词或搜索组合", - "search_options": "搜索选项", - "search_page_categories": "类别", - "search_page_motion_photos": "动图", - "search_page_no_objects": "没有事物信息", - "search_page_no_places": "地点信息不存在", - "search_page_screenshots": "屏幕截图", - "search_page_search_photos_videos": "搜索您的照片和视频", - "search_page_selfies": "自拍", - "search_page_things": "事物", - "search_page_view_all_button": "查看全部", - "search_page_your_activity": "您的活动", - "search_page_your_map": "足迹", - "search_people": "按人物查找", - "search_places": "按地点查找", - "search_rating": "按星级查找...", - "search_result_page_new_search_hint": "搜索新的", - "search_settings": "搜索设置", - "search_state": "按省份查找...", - "search_suggestion_list_smart_search_hint_1": "默认情况下启用智能搜索,要搜索元数据,请使用相关语法 ", - "search_suggestion_list_smart_search_hint_2": "m:您的搜索关键词", - "search_tags": "按标签查找…", - "search_timezone": "按时区查找...", - "search_type": "按类型查找", - "search_your_photos": "查找您的照片", - "searching_locales": "根据地区查找...", - "second": "秒", - "see_all_people": "查看所有人物", - "select": "选择", - "select_album": "选择相册", - "select_album_cover": "选择相册封面", - "select_albums": "选择相册", - "select_all": "全选", - "select_all_duplicates": "选择所有重复项", - "select_all_in": "选择 {group} 中的所有内容", - "select_avatar_color": "选择头像颜色", - "select_count": "{count, plural, one {选择 # 项} other {选择 # 项}}", - "select_cutoff_date": "选择截止日期", - "select_face": "选择人脸", - "select_featured_photo": "选择个性头像", - "select_from_computer": "从计算机中选择", - "select_keep_all": "全部保留", - "select_library_owner": "选择图库所有者", - "select_new_face": "选择新人脸", - "select_people": "选择人物", - "select_person": "选择人物", - "select_person_to_tag": "选择要标记的人物", - "select_photos": "选择照片", - "select_trash_all": "全部删除", - "select_user_for_sharing_page_err_album": "创建相册失败", - "selected": "已选择", - "selected_count": "{count, plural, other {#项已选择}}", - "selected_gps_coordinates": "已选定的GPS坐标", - "send_message": "发送消息", - "send_welcome_email": "发送欢迎邮件", - "server_endpoint": "服务器 URL", - "server_info_box_app_version": "App 版本", - "server_info_box_server_url": "服务器地址", - "server_offline": "服务器离线", - "server_online": "服务器在线", - "server_privacy": "服务器隐私", - "server_restarting_description": "此页面将立即刷新。", - "server_restarting_title": "服务器正在重新启动", - "server_stats": "服务器状态", - "server_update_available": "服务器更新可用", - "server_version": "服务器版本", - "set": "设置", - "set_as_album_cover": "设为相册封面", - "set_as_featured_photo": "设置为特色照片", - "set_as_profile_picture": "设为个人资料图片", - "set_date_of_birth": "设置出生日期", - "set_profile_picture": "设置个人资料图片", - "set_slideshow_to_fullscreen": "全屏放映幻灯片", - "set_stack_primary_asset": "设为主要项目", - "setting_image_viewer_help": "详细信息查看器首先加载小缩略图,然后加载中等大小的预览图(若启用),最后加载原始图像。", - "setting_image_viewer_original_subtitle": "启用以加载原图,禁用以减少数据使用量(网络和设备缓存)。", - "setting_image_viewer_original_title": "加载原图", - "setting_image_viewer_preview_subtitle": "启用以加载中等质量的图像,禁用以加载原图或缩略图。", - "setting_image_viewer_preview_title": "加载预览图", - "setting_image_viewer_title": "图片", - "setting_languages_apply": "应用", - "setting_languages_subtitle": "更改应用语言", - "setting_notifications_notify_failures_grace_period": "后台备份失败通知:{duration}", - "setting_notifications_notify_hours": "{count} 小时", - "setting_notifications_notify_immediately": "立即", - "setting_notifications_notify_minutes": "{count} 分钟", - "setting_notifications_notify_never": "从不", - "setting_notifications_notify_seconds": "{count} 秒", - "setting_notifications_single_progress_subtitle": "每项的详细上传进度信息", - "setting_notifications_single_progress_title": "显示后台备份详细进度", - "setting_notifications_subtitle": "调整通知首选项", - "setting_notifications_total_progress_subtitle": "总体上传进度(已完成/总计)", - "setting_notifications_total_progress_title": "显示后台备份总进度", - "setting_video_viewer_auto_play_subtitle": "打开视频时自动开始播放", - "setting_video_viewer_auto_play_title": "自动播放视频", - "setting_video_viewer_looping_title": "循环播放", - "setting_video_viewer_original_video_subtitle": "从服务器流式传输视频时,即使有转码,也播放原始视频。可能会导致缓冲。本地视频则以原始质量播放,与此设置无关。", - "setting_video_viewer_original_video_title": "强制播放原始视频", - "settings": "设置", - "settings_require_restart": "请重启 Immich 以使设置生效", - "settings_saved": "设置已保存", - "setup_pin_code": "设置PIN码", - "share": "分享", - "share_action_prompt": "已共享 {count} 项目", - "share_add_photos": "添加项目", - "share_assets_selected": "{count} 已选择", - "share_dialog_preparing": "准备中...", - "share_link": "分享链接", - "shared": "共享", - "shared_album_activities_input_disable": "评论已禁用", - "shared_album_activity_remove_content": "是否删除此活动?", - "shared_album_activity_remove_title": "删除活动", - "shared_album_section_people_action_error": "退出/删除相册失败", - "shared_album_section_people_action_leave": "从相册中删除用户", - "shared_album_section_people_action_remove_user": "从相册中删除用户", - "shared_album_section_people_title": "人物", - "shared_by": "共享自", - "shared_by_user": "由“{user}”共享", - "shared_by_you": "您的共享", - "shared_from_partner": "来自“{partner}”的照片", - "shared_intent_upload_button_progress_text": "{current} / {total} 已上传", - "shared_link_app_bar_title": "共享链接", - "shared_link_clipboard_copied_massage": "复制到剪贴板", - "shared_link_clipboard_text": "链接:{link}\n密码:{password}", - "shared_link_create_error": "创建共享链接出错", - "shared_link_custom_url_description": "使用自定义URL访问此共享链接", - "shared_link_edit_description_hint": "编辑共享描述", - "shared_link_edit_expire_after_option_day": "1天", - "shared_link_edit_expire_after_option_days": "{count} 天", - "shared_link_edit_expire_after_option_hour": "1小时", - "shared_link_edit_expire_after_option_hours": "{count} 小时", - "shared_link_edit_expire_after_option_minute": "1分钟", - "shared_link_edit_expire_after_option_minutes": "{count} 分钟", - "shared_link_edit_expire_after_option_months": "{count} 个月", - "shared_link_edit_expire_after_option_year": "{count} 年", - "shared_link_edit_password_hint": "输入共享密码", - "shared_link_edit_submit_button": "更新链接", - "shared_link_error_server_url_fetch": "无法获取服务器地址", - "shared_link_expires_day": "{count} 天后过期", - "shared_link_expires_days": "{count} 天后过期", - "shared_link_expires_hour": "{count} 小时后过期", - "shared_link_expires_hours": "{count} 小时后过期", - "shared_link_expires_minute": "{count} 分钟后过期", - "shared_link_expires_minutes": "{count} 分钟后过期", - "shared_link_expires_never": "过期时间 ∞", - "shared_link_expires_second": "{count} 秒后过期", - "shared_link_expires_seconds": "{count} 秒后过期", - "shared_link_individual_shared": "个人共享", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "管理共享链接", - "shared_link_options": "共享链接选项", - "shared_link_password_description": "需要密码才能访问此共享链接", - "shared_links": "共享链接", - "shared_links_description": "通过链接分享照片和视频", - "shared_photos_and_videos_count": "{assetCount, plural, other {#项已共享照片&视频。}}", - "shared_with_me": "共享给我", - "shared_with_partner": "与“{partner}”共享", - "sharing": "共享", - "sharing_enter_password": "请输入密码后查看此页面。", - "sharing_page_album": "共享相册", - "sharing_page_description": "创建共享相册以与网络中的人共享照片和视频。", - "sharing_page_empty_list": "空", - "sharing_sidebar_description": "在侧边栏中显示“共享”链接", - "sharing_silver_appbar_create_shared_album": "创建共享相册", - "sharing_silver_appbar_share_partner": "共享给协作者", - "shift_to_permanent_delete": "按住 ⇧ Shift 键永久删除项目", - "show_album_options": "显示相册选项", - "show_albums": "显示相册", - "show_all_people": "显示所有人物", - "show_and_hide_people": "显示和隐藏人物", - "show_file_location": "显示文件位置", - "show_gallery": "显示图库", - "show_hidden_people": "显示隐藏人物", - "show_in_timeline": "在时间轴中显示", - "show_in_timeline_setting_description": "在时间轴中显示此用户的照片和视频", - "show_keyboard_shortcuts": "显示键盘快捷键", - "show_metadata": "显示元数据", - "show_or_hide_info": "显示或隐藏信息", - "show_password": "显示密码", - "show_person_options": "显示人物选项", - "show_progress_bar": "显示进度条", - "show_schema": "显示架构", - "show_search_options": "显示搜索选项", - "show_shared_links": "显示共享链接", - "show_slideshow_transition": "显示幻灯片过渡效果", - "show_supporter_badge": "支持者徽章", - "show_supporter_badge_description": "展示支持者徽章", - "show_text_recognition": "显示文本识别", - "show_text_search_menu": "显示文本搜索菜单", - "shuffle": "随机", - "sidebar": "侧边栏", - "sidebar_display_description": "在侧边栏中显示链接", - "sign_out": "退出登录", - "sign_up": "注册", - "size": "大小", - "skip_to_content": "跳转到内容", - "skip_to_folders": "跳转到文件夹", - "skip_to_tags": "跳转到标签", - "slideshow": "幻灯片放映", - "slideshow_repeat": "重复幻灯片", - "slideshow_repeat_description": "幻灯片结束后循环播放", - "slideshow_settings": "放映设置", - "sort_albums_by": "相册排序依据...", - "sort_created": "创建日期", - "sort_items": "项目数量", - "sort_modified": "修改日期", - "sort_newest": "最新照片", - "sort_oldest": "最早的照片", - "sort_people_by_similarity": "按相似性对人物进行排序", - "sort_recent": "最新的照片", - "sort_title": "标题", - "source": "GitHub 源代码", - "stack": "堆叠", - "stack_action_prompt": "{count} 个已堆叠", - "stack_duplicates": "堆叠重复项目", - "stack_select_one_photo": "为堆叠选择一张展示图", - "stack_selected_photos": "堆叠选定的照片", - "stacked_assets_count": "已堆叠{count, plural, one {#个项目} other {#个项目}}", - "stacktrace": "堆栈跟踪", - "start": "开始", - "start_date": "开始日期", - "start_date_before_end_date": "开始日期必须在结束日期之前", - "state": "省份", - "status": "状态", - "stop_casting": "停止投放", - "stop_motion_photo": "定格照片", - "stop_photo_sharing": "停止共享照片?", - "stop_photo_sharing_description": "“{partner}”将不能访问您的照片。", - "stop_sharing_photos_with_user": "停止与此用户共享照片", - "storage": "存储空间", - "storage_label": "存储标签", - "storage_quota": "存储配额", - "storage_usage": "已用:{used}/{available}", - "submit": "提交", - "success": "成功", - "suggestions": "建议", - "sunrise_on_the_beach": "海滩上的日出", - "support": "支持", - "support_and_feedback": "支持和反馈", - "support_third_party_description": "您的 Immich 安装程序由第三方打包。当前问题可能由该软件包引起,因此请优先通过下方链接向对应作者提交 Issue。", - "swap_merge_direction": "互换合并方向", - "sync": "同步", - "sync_albums": "同步相册", - "sync_albums_manual_subtitle": "将所有上传的视频和照片同步到选定的备份相册", - "sync_local": "同步本地", - "sync_remote": "同步远程", - "sync_status": "同步状态", - "sync_status_subtitle": "查看和管理同步系统", - "sync_upload_album_setting_subtitle": "创建照片和视频并上传到 Immich 上的选定相册中", - "tag": "标签", - "tag_assets": "标记项目", - "tag_created": "已创建标签:{tag}", - "tag_feature_description": "按逻辑标签分组并浏览照片和视频", - "tag_not_found_question": "找不到标签吗?创建新标签", - "tag_people": "命名人物", - "tag_updated": "已更新标签:{tag}", - "tagged_assets": "{count, plural, one {# 个项目} other {# 个项目}}被加上标签", - "tags": "标签", - "tap_to_run_job": "点击运行任务", - "template": "模版", - "text_recognition": "文本识别", - "theme": "主题", - "theme_selection": "主题选项", - "theme_selection_description": "跟随浏览器自动设置主题颜色", - "theme_setting_asset_list_storage_indicator_title": "在项目标题上显示存储占用", - "theme_setting_asset_list_tiles_per_row_title": "每行展示 {count} 项", - "theme_setting_colorful_interface_subtitle": "应用主色调到背景。", - "theme_setting_colorful_interface_title": "彩色界面", - "theme_setting_image_viewer_quality_subtitle": "调整查看大图时的图像质量", - "theme_setting_image_viewer_quality_title": "图像质量", - "theme_setting_primary_color_subtitle": "选择颜色作为主色调。", - "theme_setting_primary_color_title": "主色调", - "theme_setting_system_primary_color_title": "使用系统颜色", - "theme_setting_system_theme_switch": "自动(跟随系统设置)", - "theme_setting_theme_subtitle": "选择应用主题", - "theme_setting_three_stage_loading_subtitle": "三段式加载可能会提升加载性能,但可能会导致更高的网络负载", - "theme_setting_three_stage_loading_title": "启用三段式加载", - "then": "然后", - "they_will_be_merged_together": "项目将会合并到一起", - "third_party_resources": "第三方资源", - "time": "时间", - "time_based_memories": "那年今日", - "time_based_memories_duration": "显示每张图像的秒数。", - "timeline": "时间线", - "timezone": "时区", - "to_archive": "归档", - "to_change_password": "修改密码", - "to_favorite": "收藏", - "to_login": "登录", - "to_multi_select": "多选", - "to_parent": "返回上一级", - "to_select": "选择", - "to_trash": "放入回收站", - "toggle_settings": "切换设置", - "toggle_theme_description": "切换主题", - "total": "总计", - "total_usage": "总用量", - "trash": "回收站", - "trash_action_prompt": "已将 {count} 项移至回收站", - "trash_all": "全部删除", - "trash_count": "删除{count, number}项", - "trash_delete_asset": "将项目放入回收站/删除", - "trash_emptied": "空回收站", - "trash_no_results_message": "删除的照片和视频将在此处展示。", - "trash_page_delete_all": "删除全部", - "trash_page_empty_trash_dialog_content": "是否清空回收站?这些项目将从 Immich 中永久删除", - "trash_page_info": "回收站中项目将在 {days} 天后永久删除", - "trash_page_no_assets": "暂无已删除项目", - "trash_page_restore_all": "恢复全部", - "trash_page_select_assets_btn": "选择项目", - "trash_page_title": "回收站 ({count})", - "trashed_items_will_be_permanently_deleted_after": "回收站中的项目将在{days, plural, one {#天} other {#天}}后被永久删除。", - "trigger": "触发条件", - "trigger_asset_uploaded": "项目已上传", - "trigger_asset_uploaded_description": "当上传新项目时触发", - "trigger_description": "启动工作流的事件", - "trigger_person_recognized": "人物已识别", - "trigger_person_recognized_description": "当检测到人物时触发", - "trigger_type": "触发类型", - "troubleshoot": "故障排除", - "type": "类型", - "unable_to_change_pin_code": "无法修改PIN码", - "unable_to_check_version": "无法检查应用程序或服务器版本", - "unable_to_setup_pin_code": "无法设置PIN码", - "unarchive": "取消归档", - "unarchive_action_prompt": "已从归档中移除 {count} 项", - "unarchived_count": "{count, plural, other {取消归档 # 项}}", - "undo": "撤销", - "unfavorite": "取消收藏", - "unfavorite_action_prompt": "已从收藏中移除 {count} 项", - "unhide_person": "显示人物", - "unknown": "未知", - "unknown_country": "未知的国家", - "unknown_date": "未知日期", - "unknown_year": "未知年份", - "unlimited": "无限制", - "unlink_motion_video": "取消链接动态视频", - "unlink_oauth": "解绑 OAuth", - "unlinked_oauth_account": "解绑 OAuth 账户", - "unmute_memories": "取消静音回忆", - "unnamed_album": "未命名相册", - "unnamed_album_delete_confirmation": "您确定要删除该相册吗?", - "unnamed_share": "未命名共享", - "unsaved_change": "修改未保存", - "unselect_all": "取消全选", - "unselect_all_duplicates": "取消选择所有重复项", - "unselect_all_in": "取消选择 {group} 中的所有内容", - "unstack": "取消堆叠", - "unstack_action_prompt": "{count} 个未堆叠", - "unstacked_assets_count": "{count, plural, one {#个项目} other {#个项目}}已取消堆叠", - "unsupported_field_type": "不支持的字段类型", - "untagged": "无标签", - "untitled_workflow": "无标题工作流", - "up_next": "下一个", - "update_location_action_prompt": "更新 {count} 个所选资产的位置:", - "updated_at": "最后更新时间", - "updated_password": "更新密码", - "upload": "上传", - "upload_concurrency": "上传并发", - "upload_details": "上传详情", - "upload_dialog_info": "是否要将所选项目备份到服务器?", - "upload_dialog_title": "上传项目", - "upload_error_with_count": "{count, plural, one {# 个项目} other {# 个项目}}上传错误", - "upload_errors": "上传完成,出现{count, plural, one {#个错误} other {#个错误}},刷新页面以查看新上传的项目。", - "upload_finished": "上传完成", - "upload_progress": "剩余{remaining, number} - 已处理 {processed, number}/{total, number}", - "upload_skipped_duplicates": "已跳过{count, plural, one {#个重复项} other {#个重复项}}", - "upload_status_duplicates": "重复项", - "upload_status_errors": "错误", - "upload_status_uploaded": "已上传", - "upload_success": "上传成功,刷新页面查看新上传的项目。", - "upload_to_immich": "上传至 Immich({count})", - "uploading": "正在上传", - "uploading_media": "文件上传中", - "url": "URL", - "usage": "用量", - "use_biometric": "使用生物识别", - "use_current_connection": "使用当前连接", - "use_custom_date_range": "自定义日期范围", - "user": "用户", - "user_has_been_deleted": "此用户已被删除。", - "user_id": "用户 ID", - "user_liked": "“{user}”点赞了{type, select, photo {该照片} video {该视频} asset {该项目} other {它}}", - "user_pin_code_settings": "PIN码", - "user_pin_code_settings_description": "管理你的PIN码", - "user_privacy": "用户隐私", - "user_purchase_settings": "购买", - "user_purchase_settings_description": "管理购买订单", - "user_role_set": "设置“{user}”为“{role}”", - "user_usage_detail": "用户用量详情", - "user_usage_stats": "帐户使用统计", - "user_usage_stats_description": "查看帐户使用统计信息", - "username": "用户名", - "users": "用户", - "users_added_to_album_count": "已将 {count, plural, one {# 个用户} other {# 个用户}} 添加到相册", - "utilities": "实用工具", - "validate": "验证", - "validate_endpoint_error": "请输入有效的 URL", - "validation_error": "验证错误", - "variables": "变量", - "version": "版本", - "version_announcement_closing": "您的朋友,Alex", - "version_announcement_message": "您好!已经检测到 Immich 有新版本。请抽空阅读一下发行说明,以确保您的配置文件是最新的,避免存在配置错误,特别是当您是使用 WatchTower 或其它类似的自动升级工具时。", - "version_history": "版本更新历史记录", - "version_history_item": "在 {date} 安装 {version} 版本", - "video": "视频", - "video_hover_setting": "鼠标悬停时播放视频缩略图", - "video_hover_setting_description": "当鼠标悬停在项目上时播放视频缩略图。即使禁用了此功能,也可以通过将鼠标悬停在播放图标上来开始播放。", - "videos": "视频", - "videos_count": "{count, plural, one {#个视频} other {#个视频}}", - "videos_only": "仅视频", - "view": "查看", - "view_album": "查看相册", - "view_all": "查看全部", - "view_all_users": "查看全部用户", - "view_asset_owners": "查看资产所有者", - "view_details": "查看详情", - "view_in_timeline": "在时间轴中查看", - "view_link": "查看链接", - "view_links": "查看链接", - "view_name": "查看", - "view_next_asset": "查看下一项", - "view_previous_asset": "查看上一项", - "view_qr_code": "查看二维码", - "view_similar_photos": "查看相似照片", - "view_stack": "查看堆叠项目", - "view_user": "查看用户", - "viewer_remove_from_stack": "从堆叠中移除", - "viewer_stack_use_as_main_asset": "作为主项目使用", - "viewer_unstack": "取消堆叠", - "visibility_changed": "{count, plural, one {#个人物} other {#个人物}}的可见性已修改", - "visual": "可视化", - "visual_builder": "可视化生成器", - "waiting": "等待处理", - "waiting_count": "等待: {count}", - "warning": "警告", - "week": "周", - "welcome": "欢迎", - "welcome_to_immich": "欢迎使用 Immich", - "width": "宽度", - "wifi_name": "Wi-Fi 名称", - "workflow_delete_prompt": "您确定要删除此工作流吗?", - "workflow_deleted": "工作流已删除", - "workflow_description": "工作流描述", - "workflow_info": "工作流信息", - "workflow_json": "工作流JSON", - "workflow_json_help": "以JSON格式编辑工作流配置。变动会同步到可视化生成器。", - "workflow_name": "工作流名称", - "workflow_navigation_prompt": "你确定不保存而退出?", - "workflow_summary": "工作流摘要", - "workflow_update_success": "工作流成功更新", - "workflow_updated": "工作流已更新", - "workflows": "工作流", - "workflows_help_text": "工作流可根据触发和筛选条件自动执行项目操作", - "wrong_pin_code": "错误的PIN码", - "year": "年", - "years_ago": "{years, plural, one {#年} other {#年}}前", - "yes": "是", - "you_dont_have_any_shared_links": "您没有任何共享链接", - "your_wifi_name": "您的 Wi-Fi 名称", - "zero_to_clear_rating": "按0清除资产星级", - "zoom_image": "缩放图像", - "zoom_to_bounds": "缩放到边界" -} +{} diff --git a/install.sh b/install.sh deleted file mode 100755 index ccefe4e894..0000000000 --- a/install.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env bash -set -o nounset -set -o pipefail - -create_immich_directory() { - local -r Tgt='./immich-app' - echo "Creating Immich directory..." - if [[ -e $Tgt ]]; then - echo "Found existing directory $Tgt, will overwrite YAML files" - else - mkdir "$Tgt" || return - fi - cd "$Tgt" || return 1 -} - -download_docker_compose_file() { - echo "Downloading docker-compose.yml..." - "${Curl[@]}" "$RepoUrl"/docker-compose.yml -o ./docker-compose.yml -} - -download_dot_env_file() { - echo "Downloading .env file..." - "${Curl[@]}" "$RepoUrl"/example.env -o ./.env -} - -generate_random_password() { - echo "Generate random password for .env file..." - rand_pass=$(echo "$RANDOM$(date)$RANDOM" | sha256sum | base64 | head -c10) - if [ -z "$rand_pass" ]; then - sed -i -e "s/DB_PASSWORD=postgres/DB_PASSWORD=postgres${RANDOM}${RANDOM}/" ./.env - else - sed -i -e "s/DB_PASSWORD=postgres/DB_PASSWORD=${rand_pass}/" ./.env - fi -} - -start_docker_compose() { - echo "Starting Immich's docker containers" - - if ! docker compose >/dev/null 2>&1; then - echo "failed to find 'docker compose'" - return 1 - fi - - if ! docker compose up --remove-orphans -d; then - echo "Could not start. Check for errors above." - return 1 - fi - show_friendly_message -} - -show_friendly_message() { - local ip_address - ip_address=$(hostname -I | awk '{print $1}') - # If length of ip_address is 0, then we are on a Mac - if [ ${#ip_address} -eq 0 ]; then - ip_address=$(ipconfig getifaddr en0) - fi - cat </dev/null; then - Curl=(curl -fsSL) - else - echo 'no curl binary found; please install curl and try again' - return 14 - fi - - create_immich_directory || { - echo 'error creating Immich directory' - return 10 - } - download_docker_compose_file || { - echo 'error downloading Docker Compose file' - return 11 - } - download_dot_env_file || { - echo 'error downloading .env' - return 12 - } - generate_random_password - start_docker_compose || { - echo 'error starting Docker' - return 13 - } - return 0 -} - -main -Exit=$? -[[ $Exit == 0 ]] || echo "There was an error installing Immich. Exit code: $Exit. Please provide these logs when asking for assistance." -exit "$Exit" diff --git a/machine-learning/.dockerignore b/machine-learning/.dockerignore deleted file mode 100644 index d5a4cf7d2b..0000000000 --- a/machine-learning/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -venv/ -*.zip -*.onnx \ No newline at end of file diff --git a/machine-learning/.gitignore b/machine-learning/.gitignore deleted file mode 100644 index f6dcbc4a21..0000000000 --- a/machine-learning/.gitignore +++ /dev/null @@ -1,198 +0,0 @@ -*.zip -*.onnx -*.rknn -*.npy -*_attr__value -*.weight -*.bias -onnx__* -*in_proj_bias -*.proj -*.latent -*.pos_embed -vocab.txt -export/immich_model_exporter/models/**/README.md -export/**/results/*.json -export/**/root -*.armnn -tokenizer.json -tokenizer_config.json -special_tokens_map.json -preprocess_cfg.json -config.json -merges.txt -vocab.json -upload/ -venv/ -__pycache__/ -model-cache/ - - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -.idea/ - -# VS Code -.vscode - -*.onnx -*.zip - -core \ No newline at end of file diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile deleted file mode 100644 index dfc217c118..0000000000 --- a/machine-learning/Dockerfile +++ /dev/null @@ -1,206 +0,0 @@ -ARG DEVICE=cpu - -FROM python:3.11-bookworm@sha256:667cf70698924920f29ebdb8d749ab665811503b87093d4f11826d114fd7255e AS builder-cpu - -FROM python:3.13-slim-trixie@sha256:0222b795db95bf7412cede36ab46a266cfb31f632e64051aac9806dabf840a61 AS builder-openvino - -FROM builder-cpu AS builder-cuda - -FROM builder-cpu AS builder-armnn - -# renovate: datasource=github-releases depName=ARM-software/armnn -ARG ARMNN_VERSION="v24.05" - -ENV ARMNN_PATH=/opt/armnn -COPY ann /opt/ann -RUN mkdir /opt/armnn && \ - curl -SL "https://github.com/ARM-software/armnn/releases/download/${ARMNN_VERSION}/ArmNN-linux-aarch64.tar.gz" | tar -zx -C /opt/armnn && \ - cd /opt/ann && \ - sh build.sh - -FROM builder-cpu AS builder-rknn - -# Warning: 25GiB+ disk space required to pull this image -# TODO: find a way to reduce the image size -FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS builder-rocm - -# renovate: datasource=github-releases depName=Microsoft/onnxruntime -ARG ONNXRUNTIME_VERSION="v1.22.1" -WORKDIR /code - -RUN apt-get update && apt-get install -y --no-install-recommends wget git -RUN wget -nv https://github.com/Kitware/CMake/releases/download/v3.31.9/cmake-3.31.9-linux-x86_64.sh && \ - chmod +x cmake-3.31.9-linux-x86_64.sh && \ - mkdir -p /code/cmake-3.31.9-linux-x86_64 && \ - ./cmake-3.31.9-linux-x86_64.sh --skip-license --prefix=/code/cmake-3.31.9-linux-x86_64 && \ - rm cmake-3.31.9-linux-x86_64.sh - -RUN git clone --single-branch --branch "${ONNXRUNTIME_VERSION}" --recursive "https://github.com/Microsoft/onnxruntime" onnxruntime -WORKDIR /code/onnxruntime -# Fix for multi-threading based on comments in https://github.com/microsoft/onnxruntime/pull/19567 -# TODO: find a way to fix this without disabling algo caching -COPY ./patches/* /tmp/ -RUN git apply /tmp/*.patch - -RUN /bin/sh ./dockerfiles/scripts/install_common_deps.sh - -ENV PATH=/opt/rocm-venv/bin:/code/cmake-3.31.9-linux-x86_64/bin:${PATH} -ENV CCACHE_DIR="/ccache" -# Note: the `parallel` setting uses a substantial amount of RAM -RUN --mount=type=cache,target=/ccache \ - ./build.sh \ - --allow_running_as_root \ - --config Release \ - --build_wheel \ - --update \ - --build \ - --parallel 17 \ - --cmake_extra_defines \ - ONNXRUNTIME_VERSION="${ONNXRUNTIME_VERSION}" \ - CMAKE_HIP_ARCHITECTURES="gfx900;gfx906;gfx908;gfx90a;gfx940;gfx941;gfx942;gfx1030;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201" \ - --skip_tests \ - --use_rocm \ - --rocm_home=/opt/rocm \ - --use_cache \ - --compile_no_warning_as_error -RUN mv /code/onnxruntime/build/Linux/Release/dist/*.whl /opt/ - -FROM builder-${DEVICE} AS builder - -ARG DEVICE -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - VIRTUAL_ENV=/opt/venv - -RUN apt-get update && apt-get install -y --no-install-recommends g++ - -COPY --from=ghcr.io/astral-sh/uv:0.8.15@sha256:a5727064a0de127bdb7c9d3c1383f3a9ac307d9f2d8a391edc7896c54289ced0 /uv /uvx /bin/ -RUN --mount=type=cache,target=/root/.cache/uv \ - --mount=type=bind,source=uv.lock,target=uv.lock \ - --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ - uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy -RUN if [ "$DEVICE" = "rocm" ]; then \ - uv pip install /opt/onnxruntime_rocm-*.whl; \ - fi - -FROM python:3.11-slim-bookworm@sha256:917ec0e42cd6af87657a768449c2f604a6b67c7ab8e10ff917b8724799f816d3 AS prod-cpu - -ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ - MACHINE_LEARNING_MODEL_ARENA=false - -FROM python:3.13-slim-trixie@sha256:0222b795db95bf7412cede36ab46a266cfb31f632e64051aac9806dabf840a61 AS prod-openvino - -RUN apt-get update && \ - apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-core-2_2.27.10+20617_amd64.deb && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-opencl-2_2.27.10+20617_amd64.deb && \ - wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/intel-opencl-icd_26.01.36711.4-0_amd64.deb && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \ - wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \ - # TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file - wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/libigdgmm12_22.9.0_amd64.deb && \ - dpkg -i *.deb && \ - rm *.deb && \ - apt-get remove wget -yqq && \ - rm -rf /var/lib/apt/lists/* - -FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04@sha256:94c1577b2cd9dd6c0312dc04dff9cb2fdce2b268018abc3d7c2dbcacf1155000 AS prod-cuda - -ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ - MACHINE_LEARNING_MODEL_ARENA=false - -RUN apt-get update && \ - # Pascal support was dropped in 9.11 - apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3 -COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11 -COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so - -FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS prod-rocm - -FROM prod-cpu AS prod-armnn - -ENV LD_LIBRARY_PATH=/opt/armnn \ - LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ - MACHINE_LEARNING_MODEL_ARENA=false - -RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \ - rm -rf /var/lib/apt/lists/* && \ - mkdir --parents /etc/OpenCL/vendors && \ - echo "/usr/lib/libmali.so" > /etc/OpenCL/vendors/mali.icd && \ - mkdir /opt/armnn - -COPY --from=builder-armnn \ - /opt/armnn/libarmnn.so.?? \ - /opt/armnn/libarmnnOnnxParser.so.?? \ - /opt/armnn/libarmnnDeserializer.so.?? \ - /opt/armnn/libarmnnTfLiteParser.so.?? \ - /opt/armnn/libprotobuf.so.?.??.?.? \ - /opt/ann/libann.s[o] \ - /opt/ann/build.sh \ - /opt/armnn/ - -FROM prod-cpu AS prod-rknn - -# renovate: datasource=github-tags depName=airockchip/rknn-toolkit2 -ARG RKNN_TOOLKIT_VERSION="v2.3.0" - -ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ - MACHINE_LEARNING_MODEL_ARENA=false - -ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/ - -FROM prod-${DEVICE} AS prod - -ARG DEVICE - -RUN apt-get update && \ - apt-get install -y --no-install-recommends tini ccache libgl1 libglib2.0-0 libgomp1 $(if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then echo "libmimalloc2.0"; fi) && \ - apt-get autoremove -yqq && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -RUN ln -s "/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2" /usr/lib/libmimalloc.so.2 - -WORKDIR /usr/src -ENV TRANSFORMERS_CACHE=/cache \ - PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - PATH="/opt/venv/bin:$PATH" \ - PYTHONPATH=/usr/src \ - DEVICE=${DEVICE} \ - VIRTUAL_ENV=/opt/venv \ - MACHINE_LEARNING_CACHE_FOLDER=/cache - -# prevent core dumps -RUN echo "hard core 0" >> /etc/security/limits.conf && \ - echo "fs.suid_dumpable 0" >> /etc/sysctl.conf && \ - echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile - -COPY --from=builder /opt/venv /opt/venv -COPY scripts/healthcheck.py . -COPY immich_ml immich_ml - -ARG BUILD_ID -ARG BUILD_IMAGE -ARG BUILD_SOURCE_REF -ARG BUILD_SOURCE_COMMIT - -ENV IMMICH_BUILD=${BUILD_ID} -ENV IMMICH_BUILD_URL=https://github.com/immich-app/immich/actions/runs/${BUILD_ID} -ENV IMMICH_BUILD_IMAGE=${BUILD_IMAGE} -ENV IMMICH_BUILD_IMAGE_URL=https://github.com/immich-app/immich/pkgs/container/immich-machine-learning -ENV IMMICH_REPOSITORY=immich-app/immich -ENV IMMICH_REPOSITORY_URL=https://github.com/immich-app/immich -ENV IMMICH_SOURCE_REF=${BUILD_SOURCE_REF} -ENV IMMICH_SOURCE_COMMIT=${BUILD_SOURCE_COMMIT} -ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE_COMMIT} - -ENTRYPOINT ["tini", "--"] -CMD ["python", "-m", "immich_ml"] - -HEALTHCHECK CMD python3 healthcheck.py diff --git a/machine-learning/README.md b/machine-learning/README.md deleted file mode 100644 index 3d1f09d70b..0000000000 --- a/machine-learning/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Immich Machine Learning - -- CLIP embeddings -- Facial recognition - -# Setup - -This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/), so be sure to install it first. -Running `uv sync --extra cpu` will install everything you need in an isolated virtual environment. -CUDA, ROCM and OpenVINO are supported as acceleration APIs. To use them, you can replace `--extra cpu` with either of `--extra cuda`, `--extra rocm` or `--extra openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required. - -To add or remove dependencies, you can use the commands `uv add $PACKAGE_NAME` and `uv remove $PACKAGE_NAME`, respectively. -Be sure to commit the `uv.lock` and `pyproject.toml` files with `uv lock` to reflect any changes in dependencies. - -# Load Testing - -To measure inference throughput and latency, you can use [Locust](https://locust.io/) using the provided `locustfile.py`. -Locust works by querying the model endpoints and aggregating their statistics, meaning the app must be deployed. -You can change the models or adjust options like score thresholds through the Locust UI. - -To get started, you can simply run `locust --web-host 127.0.0.1` and open `localhost:8089` in a browser to access the UI. See the [Locust documentation](https://docs.locust.io/en/stable/index.html) for more info on running Locust. - -Note that in Locust's jargon, concurrency is measured in `users`, and each user runs one task at a time. To achieve a particular per-endpoint concurrency, multiply that number by the number of endpoints to be queried. For example, if there are 3 endpoints and you want each of them to receive 8 requests at a time, you should set the number of users to 24. - -# Facial Recognition - -## Acknowledgements - -This project utilizes facial recognition models from the [InsightFace](https://github.com/deepinsight/insightface/tree/master/model_zoo) project. We appreciate the work put into developing these models, which have been beneficial to the machine learning part of this project. - -### Used Models - -- antelopev2 -- buffalo_l -- buffalo_m -- buffalo_s - -## License and Use Restrictions - -We have received permission to use the InsightFace facial recognition models in our project, as granted via email by Jia Guo (guojia@insightface.ai) on 18th March 2023. However, it's important to note that this permission does not extend to the redistribution or commercial use of their models by third parties. Users and developers interested in using these models should review the licensing terms provided in the InsightFace GitHub repository. - -For more information on the capabilities of the InsightFace models and to ensure compliance with their license, please refer to their [official repository](https://github.com/deepinsight/insightface). Adhering to the specified licensing terms is crucial for the respectful and lawful use of their work. diff --git a/machine-learning/README_es_ES.md b/machine-learning/README_es_ES.md deleted file mode 100644 index f58bb6d0ab..0000000000 --- a/machine-learning/README_es_ES.md +++ /dev/null @@ -1,21 +0,0 @@ -# Immich Machine Learning - -- Clasificación de imágenes -- Incorporación de CLIP -- Reconocimiento facial - -# Configuración - -Este proyecto utiliza [Poetry](https://python-poetry.org/docs/#installation), así que asegúrate de instalarlo primero. -Ejecutar `poetry install --no-root --with dev` instalará todo lo necesario en un entorno virtual aislado. - -Para agregar o eliminar dependencias, puedes utilizar los comandos `poetry add $PACKAGE_NAME` y `poetry remove $PACKAGE_NAME`, respectivamente. -Asegúrate de hacer commit de los archivos `poetry.lock` y `pyproject.toml` para reflejar cualquier cambio en las dependencias. - -# Pruebas de carga - -Para medir la velocidad y latencia de inferencia, puedes utilizar [Locust](https://locust.io/) con el archivo `locustfile.py` proporcionado. -Locust funciona haciendo consultas a los puntos finales del modelo y agregando estadísticas, lo que significa que la aplicación debe estar desplegada. -Puedes ejecutar `load_test.sh` para implementar automáticamente la aplicación localmente e iniciar Locust, ajustando opcionalmente sus variables de entorno según sea necesario. - -Alternativamente, para pruebas más personalizadas, también puedes ejecutar `locust` directamente: consulta la [documentación](https://docs.locust.io/en/stable/index.html). Ten en cuenta que, en la jerga de Locust, la concurrencia se mide en `usuarios`, y cada usuario ejecuta una tarea a la vez. Para lograr una concurrencia específica por punto final, multiplica ese número por la cantidad de puntos finales que se desean consultar. Por ejemplo, si hay 3 puntos finales y deseas que cada uno de ellos reciba 8 solicitudes al mismo tiempo, debes configurar el número de usuarios en 24. diff --git a/machine-learning/README_fr_FR.md b/machine-learning/README_fr_FR.md deleted file mode 100644 index 4f5c689a39..0000000000 --- a/machine-learning/README_fr_FR.md +++ /dev/null @@ -1,22 +0,0 @@ -# Immich Apprentissage machine - -- Classification d'images -- Embarquement de CLIP -- Reconnaissance faciale - -# Mise en place - -Ce projet utilise [Poetry](https://python-poetry.org/docs/#installation), donc soyez certain de l'installer en premier. -Exécuter `poetry install --no-root --with dev` installera tout ce dont vous avez besoin dans un environnement virtuel isolé. - -Pour ajouter ou supprimer des dépendances, vous pouvez utiliser les commandes `poetry add $PACKAGE_NAME` et `poetry remove $PACKAGE_NAME` respectivement. -Soyez sûr de commit les fichiers `poetry.lock` et `pyproject.toml` pour refléter les changements de dépendances. - - -# Test de charge - -Pour mesurer le débit d'inférence et la latence, vous pouvez utiliser [Locust](https://locust.io/) avec le fichier fourni `locustfile.py`. -Locust fonctionne en interrogeant les endpoints des modèles et en aggrégeant leurs statistiques, signifiant que l'application doit être déployée. -Vous pouvez exécuter `load_test.sh` pour automatiquement déployer l'application localement et démarrer Locust, en ajustant si besoin ses variables d'environnement. - -En alternative, pour réaliser plus de tests customisés, vous pourriez aussi exécuter `locust` directement : voir la [documentation](https://docs.locust.io/en/stable/index.html). Notez que dans le jargon de Locust, la concurrence est mesurée en `users` et que chaque user exécute une tâche après l'autre. Pour parvenir à une concurrence par endpoint, multipliez ce nombre par le nombre d'endpoints à interroger. Par exemple, s'il y a 3 endpoints et que vous voulez que chacun d'entre eux reçoive 8 requêtes à la fois, vous devrez mettre ce nombre d'users à 24. diff --git a/machine-learning/ann/__init__.py b/machine-learning/ann/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/machine-learning/ann/ann.cpp b/machine-learning/ann/ann.cpp deleted file mode 100644 index 5771759508..0000000000 --- a/machine-learning/ann/ann.cpp +++ /dev/null @@ -1,310 +0,0 @@ -#include -#include -#include - -#include "armnn/IRuntime.hpp" -#include "armnn/INetwork.hpp" -#include "armnn/Types.hpp" -#include "armnnDeserializer/IDeserializer.hpp" -#include "armnnTfLiteParser/ITfLiteParser.hpp" -#include "armnnOnnxParser/IOnnxParser.hpp" - -using namespace armnn; - -struct IOInfos -{ - std::vector inputInfos; - std::vector outputInfos; -}; - -// from https://rigtorp.se/spinlock/ -struct SpinLock -{ - std::atomic lock_ = {false}; - - void lock() - { - for (;;) - { - if (!lock_.exchange(true, std::memory_order_acquire)) - { - break; - } - while (lock_.load(std::memory_order_relaxed)) - ; - } - } - - void unlock() { lock_.store(false, std::memory_order_release); } -}; - -class Ann -{ - -public: - int load(const char *modelPath, - bool fastMath, - bool fp16, - bool saveCachedNetwork, - const char *cachedNetworkPath) - { - NetworkId netId = -2; - while (netId == -2) - { - try - { - netId = loadInternal(modelPath, fastMath, fp16, saveCachedNetwork, cachedNetworkPath); - } - catch (InvalidArgumentException e) - { - // fp16 models do not support the forced fp16-turbo (runtime fp32->fp16 conversion) - if (fp16) - fp16 = false; - else - netId = -1; - } - } - return netId; - } - - void execute(NetworkId netId, const void **inputData, void **outputData) - { - spinLock.lock(); - const IOInfos *infos = &ioInfos[netId]; - auto m = mutexes[netId].get(); - spinLock.unlock(); - InputTensors inputTensors; - inputTensors.reserve(infos->inputInfos.size()); - size_t i = 0; - for (const BindingPointInfo &info : infos->inputInfos) - inputTensors.emplace_back(info.first, ConstTensor(info.second, inputData[i++])); - OutputTensors outputTensors; - outputTensors.reserve(infos->outputInfos.size()); - i = 0; - for (const BindingPointInfo &info : infos->outputInfos) - outputTensors.emplace_back(info.first, Tensor(info.second, outputData[i++])); - m->lock(); - runtime->EnqueueWorkload(netId, inputTensors, outputTensors); - m->unlock(); - } - - void unload(NetworkId netId) - { - mutex.lock(); - runtime->UnloadNetwork(netId); - mutex.unlock(); - } - - int tensors(NetworkId netId, bool isInput = false) - { - spinLock.lock(); - const IOInfos *infos = &ioInfos[netId]; - spinLock.unlock(); - return (int)(isInput ? infos->inputInfos.size() : infos->outputInfos.size()); - } - - unsigned long shape(NetworkId netId, bool isInput = false, int index = 0) - { - spinLock.lock(); - const IOInfos *infos = &ioInfos[netId]; - spinLock.unlock(); - const TensorShape shape = (isInput ? infos->inputInfos : infos->outputInfos)[index].second.GetShape(); - unsigned long s = 0; - for (unsigned int d = 0; d < shape.GetNumDimensions(); d++) - s |= ((unsigned long)shape[d]) << (d * 16); // stores up to 4 16-bit values in a 64-bit value - return s; - } - - Ann(int tuningLevel, const char *tuningFile) - { - IRuntime::CreationOptions runtimeOptions; - runtimeOptions.m_ProfilingOptions.m_EnableProfiling = false; - runtimeOptions.m_ProfilingOptions.m_TimelineEnabled = false; - BackendOptions backendOptions{"GpuAcc", - { - {"TuningLevel", tuningLevel}, - {"MemoryOptimizerStrategy", "ConstantMemoryStrategy"}, // SingleAxisPriorityList or ConstantMemoryStrategy - }}; - if (tuningFile) - backendOptions.AddOption({"TuningFile", tuningFile}); - runtimeOptions.m_BackendOptions.emplace_back(backendOptions); - runtime = IRuntime::CreateRaw(runtimeOptions); - }; - ~Ann() - { - IRuntime::Destroy(runtime); - }; - -private: - int loadInternal(const char *modelPath, - bool fastMath, - bool fp16, - bool saveCachedNetwork, - const char *cachedNetworkPath) - { - NetworkId netId = -1; - INetworkPtr network = loadModel(modelPath); - IOptimizedNetworkPtr optNet = OptimizeNetwork(network.get(), fastMath, fp16, saveCachedNetwork, cachedNetworkPath); - const IOInfos infos = getIOInfos(optNet.get()); - mutex.lock(); - Status status = runtime->LoadNetwork(netId, std::move(optNet)); - mutex.unlock(); - if (status != Status::Success) - { - return -1; - } - spinLock.lock(); - ioInfos[netId] = infos; - mutexes.emplace(netId, std::make_unique()); - spinLock.unlock(); - return netId; - } - - INetworkPtr loadModel(const char *modelPath) - { - const auto path = std::string(modelPath); - if (path.rfind(".tflite") == path.length() - 7) // endsWith() - { - auto parser = armnnTfLiteParser::ITfLiteParser::CreateRaw(); - return parser->CreateNetworkFromBinaryFile(modelPath); - } - else if (path.rfind(".onnx") == path.length() - 5) // endsWith() - { - auto parser = armnnOnnxParser::IOnnxParser::CreateRaw(); - return parser->CreateNetworkFromBinaryFile(modelPath); - } - else - { - std::ifstream ifs(path, std::ifstream::in | std::ifstream::binary); - auto parser = armnnDeserializer::IDeserializer::CreateRaw(); - return parser->CreateNetworkFromBinary(ifs); - } - } - - static BindingPointInfo getInputTensorInfo(LayerBindingId inputBindingId, TensorInfo info) - { - const auto newInfo = TensorInfo{info.GetShape(), info.GetDataType(), - info.GetQuantizationScale(), - info.GetQuantizationOffset(), - true}; - return {inputBindingId, newInfo}; - } - - IOptimizedNetworkPtr OptimizeNetwork(INetwork *network, bool fastMath, bool fp16, bool saveCachedNetwork, const char *cachedNetworkPath) - { - const bool allowExpandedDims = false; - const ShapeInferenceMethod shapeInferenceMethod = ShapeInferenceMethod::ValidateOnly; - - OptimizerOptionsOpaque options; - options.SetReduceFp32ToFp16(fp16); - options.SetShapeInferenceMethod(shapeInferenceMethod); - options.SetAllowExpandedDims(allowExpandedDims); - options.SetDebugToFileEnabled(false); - options.SetProfilingEnabled(false); - - BackendOptions gpuAcc("GpuAcc", {{"FastMathEnabled", fastMath}}); - if (cachedNetworkPath) - { - gpuAcc.AddOption({"SaveCachedNetwork", saveCachedNetwork}); - gpuAcc.AddOption({"CachedNetworkFilePath", cachedNetworkPath}); - } - options.AddModelOption(gpuAcc); - - // No point in using ARMNN for CPU, use ONNX (quantized) instead. - // BackendOptions cpuAcc("CpuAcc", - // { - // {"FastMathEnabled", fastMath}, - // {"NumberOfThreads", 0}, - // }); - // options.AddModelOption(cpuAcc); - - BackendOptions allowExDimOpt("AllowExpandedDims", - {{"AllowExpandedDims", allowExpandedDims}}); - options.AddModelOption(allowExDimOpt); - BackendOptions shapeInferOpt("ShapeInferenceMethod", - {{"InferAndValidate", shapeInferenceMethod == ShapeInferenceMethod::InferAndValidate}}); - options.AddModelOption(shapeInferOpt); - - std::vector backends = { - BackendId("GpuAcc"), - // BackendId("CpuAcc"), - // BackendId("CpuRef"), - }; - return Optimize(*network, backends, runtime->GetDeviceSpec(), options); - } - - IOInfos getIOInfos(IOptimizedNetwork *optNet) - { - struct InfoStrategy : IStrategy - { - void ExecuteStrategy(const IConnectableLayer *layer, - const BaseDescriptor &descriptor, - const std::vector &constants, - const char *name, - const LayerBindingId id = 0) override - { - IgnoreUnused(descriptor, constants, id); - const LayerType lt = layer->GetType(); - if (lt == LayerType::Input) - ioInfos.inputInfos.push_back(getInputTensorInfo(id, layer->GetOutputSlot(0).GetTensorInfo())); - else if (lt == LayerType::Output) - ioInfos.outputInfos.push_back({id, layer->GetInputSlot(0).GetTensorInfo()}); - } - IOInfos ioInfos; - }; - - InfoStrategy infoStrategy; - optNet->ExecuteStrategy(infoStrategy); - return infoStrategy.ioInfos; - } - - IRuntime *runtime; - std::map ioInfos; - std::map> mutexes; // mutex per network to not execute the same the same network concurrently - std::mutex mutex; // global mutex for load/unload calls to the runtime - SpinLock spinLock; // fast spin lock to guard access to the ioInfos and mutexes maps -}; - -extern "C" void *init(int logLevel, int tuningLevel, const char *tuningFile) -{ - LogSeverity level = static_cast(logLevel); - ConfigureLogging(true, true, level); - - Ann *ann = new Ann(tuningLevel, tuningFile); - return ann; -} - -extern "C" void destroy(void *ann) -{ - delete ((Ann *)ann); -} - -extern "C" int load(void *ann, - const char *path, - bool fastMath, - bool fp16, - bool saveCachedNetwork, - const char *cachedNetworkPath) -{ - return ((Ann *)ann)->load(path, fastMath, fp16, saveCachedNetwork, cachedNetworkPath); -} - -extern "C" void unload(void *ann, NetworkId netId) -{ - ((Ann *)ann)->unload(netId); -} - -extern "C" void execute(void *ann, NetworkId netId, const void **inputData, void **outputData) -{ - ((Ann *)ann)->execute(netId, inputData, outputData); -} - -extern "C" unsigned long shape(void *ann, NetworkId netId, bool isInput, int index) -{ - return ((Ann *)ann)->shape(netId, isInput, index); -} - -extern "C" int tensors(void *ann, NetworkId netId, bool isInput) -{ - return ((Ann *)ann)->tensors(netId, isInput); -} \ No newline at end of file diff --git a/machine-learning/ann/build.sh b/machine-learning/ann/build.sh deleted file mode 100644 index 219c0ef1b1..0000000000 --- a/machine-learning/ann/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env sh - -g++ -shared -O3 -o libann.so -fuse-ld=gold -std=c++17 -I"$ARMNN_PATH"/include -larmnn -larmnnDeserializer -larmnnTfLiteParser -larmnnOnnxParser -L"$ARMNN_PATH" ann.cpp diff --git a/machine-learning/ann/export/.gitignore b/machine-learning/ann/export/.gitignore deleted file mode 100644 index eeebcd1c35..0000000000 --- a/machine-learning/ann/export/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -armnn* -output/ diff --git a/machine-learning/ann/export/build-converter.sh b/machine-learning/ann/export/build-converter.sh deleted file mode 100755 index 94e9ebec2b..0000000000 --- a/machine-learning/ann/export/build-converter.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh - -cd armnn-23.11/ || exit -g++ -o ../armnnconverter -O1 -DARMNN_ONNX_PARSER -DARMNN_SERIALIZER -DARMNN_TF_LITE_PARSER -fuse-ld=gold -std=c++17 -Iinclude -Isrc/armnnUtils -Ithird-party -larmnn -larmnnDeserializer -larmnnTfLiteParser -larmnnOnnxParser -larmnnSerializer -L../armnn src/armnnConverter/ArmnnConverter.cpp diff --git a/machine-learning/ann/export/download-armnn.sh b/machine-learning/ann/export/download-armnn.sh deleted file mode 100755 index e138e34f57..0000000000 --- a/machine-learning/ann/export/download-armnn.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# binaries -mkdir armnn -curl -SL "https://github.com/ARM-software/armnn/releases/download/v23.11/ArmNN-linux-x86_64.tar.gz" | tar -zx -C armnn - -# source to build ArmnnConverter -curl -SL "https://github.com/ARM-software/armnn/archive/refs/tags/v23.11.tar.gz" | tar -zx diff --git a/machine-learning/ann/export/env.yaml b/machine-learning/ann/export/env.yaml deleted file mode 100644 index c5e656cd46..0000000000 --- a/machine-learning/ann/export/env.yaml +++ /dev/null @@ -1,201 +0,0 @@ -name: annexport -channels: - - pytorch - - nvidia - - conda-forge -dependencies: - - _libgcc_mutex=0.1=conda_forge - - _openmp_mutex=4.5=2_kmp_llvm - - aiohttp=3.9.1=py310h2372a71_0 - - aiosignal=1.3.1=pyhd8ed1ab_0 - - arpack=3.8.0=nompi_h0baa96a_101 - - async-timeout=4.0.3=pyhd8ed1ab_0 - - attrs=23.1.0=pyh71513ae_1 - - aws-c-auth=0.7.3=h28f7589_1 - - aws-c-cal=0.6.1=hc309b26_1 - - aws-c-common=0.9.0=hd590300_0 - - aws-c-compression=0.2.17=h4d4d85c_2 - - aws-c-event-stream=0.3.1=h2e3709c_4 - - aws-c-http=0.7.11=h00aa349_4 - - aws-c-io=0.13.32=he9a53bd_1 - - aws-c-mqtt=0.9.3=hb447be9_1 - - aws-c-s3=0.3.14=hf3aad02_1 - - aws-c-sdkutils=0.1.12=h4d4d85c_1 - - aws-checksums=0.1.17=h4d4d85c_1 - - aws-crt-cpp=0.21.0=hb942446_5 - - aws-sdk-cpp=1.10.57=h85b1a90_19 - - blas=2.120=openblas - - blas-devel=3.9.0=20_linux64_openblas - - brotli-python=1.0.9=py310hd8f1fbe_9 - - bzip2=1.0.8=hd590300_5 - - c-ares=1.23.0=hd590300_0 - - ca-certificates=2023.11.17=hbcca054_0 - - certifi=2023.11.17=pyhd8ed1ab_0 - - charset-normalizer=3.3.2=pyhd8ed1ab_0 - - click=8.1.7=unix_pyh707e725_0 - - colorama=0.4.6=pyhd8ed1ab_0 - - coloredlogs=15.0.1=pyhd8ed1ab_3 - - cuda-cudart=11.7.99=0 - - cuda-cupti=11.7.101=0 - - cuda-libraries=11.7.1=0 - - cuda-nvrtc=11.7.99=0 - - cuda-nvtx=11.7.91=0 - - cuda-runtime=11.7.1=0 - - dataclasses=0.8=pyhc8e2a94_3 - - datasets=2.14.7=pyhd8ed1ab_0 - - dill=0.3.7=pyhd8ed1ab_0 - - filelock=3.13.1=pyhd8ed1ab_0 - - flatbuffers=23.5.26=h59595ed_1 - - freetype=2.12.1=h267a509_2 - - frozenlist=1.4.0=py310h2372a71_1 - - fsspec=2023.10.0=pyhca7485f_0 - - ftfy=6.1.3=pyhd8ed1ab_0 - - gflags=2.2.2=he1b5a44_1004 - - glog=0.6.0=h6f12383_0 - - glpk=5.0=h445213a_0 - - gmp=6.3.0=h59595ed_0 - - gmpy2=2.1.2=py310h3ec546c_1 - - huggingface_hub=0.17.3=pyhd8ed1ab_0 - - humanfriendly=10.0=pyhd8ed1ab_6 - - icu=73.2=h59595ed_0 - - idna=3.6=pyhd8ed1ab_0 - - importlib-metadata=7.0.0=pyha770c72_0 - - importlib_metadata=7.0.0=hd8ed1ab_0 - - joblib=1.3.2=pyhd8ed1ab_0 - - keyutils=1.6.1=h166bdaf_0 - - krb5=1.21.2=h659d440_0 - - lcms2=2.15=h7f713cb_2 - - ld_impl_linux-64=2.40=h41732ed_0 - - lerc=4.0.0=h27087fc_0 - - libabseil=20230125.3=cxx17_h59595ed_0 - - libarrow=12.0.1=hb87d912_8_cpu - - libblas=3.9.0=20_linux64_openblas - - libbrotlicommon=1.0.9=h166bdaf_9 - - libbrotlidec=1.0.9=h166bdaf_9 - - libbrotlienc=1.0.9=h166bdaf_9 - - libcblas=3.9.0=20_linux64_openblas - - libcrc32c=1.1.2=h9c3ff4c_0 - - libcublas=11.10.3.66=0 - - libcufft=10.7.2.124=h4fbf590_0 - - libcufile=1.8.1.2=0 - - libcurand=10.3.4.101=0 - - libcurl=8.5.0=hca28451_0 - - libcusolver=11.4.0.1=0 - - libcusparse=11.7.4.91=0 - - libdeflate=1.19=hd590300_0 - - libedit=3.1.20191231=he28a2e2_2 - - libev=4.33=hd590300_2 - - libevent=2.1.12=hf998b51_1 - - libffi=3.4.2=h7f98852_5 - - libgcc-ng=13.2.0=h807b86a_3 - - libgfortran-ng=13.2.0=h69a702a_3 - - libgfortran5=13.2.0=ha4646dd_3 - - libgoogle-cloud=2.12.0=hac9eb74_1 - - libgrpc=1.54.3=hb20ce57_0 - - libhwloc=2.9.3=default_h554bfaf_1009 - - libiconv=1.17=hd590300_1 - - libjpeg-turbo=2.1.5.1=hd590300_1 - - liblapack=3.9.0=20_linux64_openblas - - liblapacke=3.9.0=20_linux64_openblas - - libnghttp2=1.58.0=h47da74e_1 - - libnpp=11.7.4.75=0 - - libnsl=2.0.1=hd590300_0 - - libnuma=2.0.16=h0b41bf4_1 - - libnvjpeg=11.8.0.2=0 - - libopenblas=0.3.25=pthreads_h413a1c8_0 - - libpng=1.6.39=h753d276_0 - - libprotobuf=3.21.12=hfc55251_2 - - libsentencepiece=0.1.99=h180e1df_0 - - libsqlite=3.44.2=h2797004_0 - - libssh2=1.11.0=h0841786_0 - - libstdcxx-ng=13.2.0=h7e041cc_3 - - libthrift=0.18.1=h8fd135c_2 - - libtiff=4.6.0=h29866fb_1 - - libutf8proc=2.8.0=h166bdaf_0 - - libuuid=2.38.1=h0b41bf4_0 - - libwebp-base=1.3.2=hd590300_0 - - libxcb=1.15=h0b41bf4_0 - - libxml2=2.11.6=h232c23b_0 - - libzlib=1.2.13=hd590300_5 - - llvm-openmp=17.0.6=h4dfa4b3_0 - - lz4-c=1.9.4=hcb278e6_0 - - mkl=2022.2.1=h84fe81f_16997 - - mkl-devel=2022.2.1=ha770c72_16998 - - mkl-include=2022.2.1=h84fe81f_16997 - - mpc=1.3.1=hfe3b2da_0 - - mpfr=4.2.1=h9458935_0 - - mpmath=1.3.0=pyhd8ed1ab_0 - - multidict=6.0.4=py310h2372a71_1 - - multiprocess=0.70.15=py310h2372a71_1 - - ncurses=6.4=h59595ed_2 - - numpy=1.26.2=py310hb13e2d6_0 - - onnx=1.14.0=py310ha3deec4_1 - - onnx2torch=1.5.13=pyhd8ed1ab_0 - - onnxruntime=1.16.3=py310hd4b7fbc_1_cpu - - open-clip-torch=2.23.0=pyhd8ed1ab_1 - - openblas=0.3.25=pthreads_h7a3da1a_0 - - openjpeg=2.5.0=h488ebb8_3 - - openssl=3.2.0=hd590300_1 - - orc=1.9.0=h2f23424_1 - - packaging=23.2=pyhd8ed1ab_0 - - pandas=2.1.4=py310hcc13569_0 - - pillow=10.0.1=py310h29da1c1_1 - - pip=23.3.1=pyhd8ed1ab_0 - - protobuf=4.21.12=py310heca2aa9_0 - - pthread-stubs=0.4=h36c2ea0_1001 - - pyarrow=12.0.1=py310h0576679_8_cpu - - pyarrow-hotfix=0.6=pyhd8ed1ab_0 - - pysocks=1.7.1=pyha2e5f31_6 - - python=3.10.13=hd12c33a_0_cpython - - python-dateutil=2.8.2=pyhd8ed1ab_0 - - python-flatbuffers=23.5.26=pyhd8ed1ab_0 - - python-tzdata=2023.3=pyhd8ed1ab_0 - - python-xxhash=3.4.1=py310h2372a71_0 - - python_abi=3.10=4_cp310 - - pytorch=1.13.1=cpu_py310hd11e9c7_1 - - pytorch-cuda=11.7=h778d358_5 - - pytorch-mutex=1.0=cuda - - pytz=2023.3.post1=pyhd8ed1ab_0 - - pyyaml=6.0.1=py310h2372a71_1 - - rdma-core=28.9=h59595ed_1 - - re2=2023.03.02=h8c504da_0 - - readline=8.2=h8228510_1 - - regex=2023.10.3=py310h2372a71_0 - - requests=2.31.0=pyhd8ed1ab_0 - - s2n=1.3.49=h06160fa_0 - - sacremoses=0.0.53=pyhd8ed1ab_0 - - safetensors=0.3.3=py310hcb5633a_1 - - sentencepiece=0.1.99=hff52083_0 - - sentencepiece-python=0.1.99=py310hebdb9f0_0 - - sentencepiece-spm=0.1.99=h180e1df_0 - - setuptools=68.2.2=pyhd8ed1ab_0 - - six=1.16.0=pyh6c4a22f_0 - - sleef=3.5.1=h9b69904_2 - - snappy=1.1.10=h9fff704_0 - - sympy=1.12=pypyh9d50eac_103 - - tbb=2021.11.0=h00ab1b0_0 - - texttable=1.7.0=pyhd8ed1ab_0 - - timm=0.9.12=pyhd8ed1ab_0 - - tk=8.6.13=noxft_h4845f30_101 - - tokenizers=0.14.1=py310h320607d_2 - - torchvision=0.14.1=cpu_py310hd3d2ac3_1 - - tqdm=4.66.1=pyhd8ed1ab_0 - - transformers=4.35.2=pyhd8ed1ab_0 - - typing-extensions=4.9.0=hd8ed1ab_0 - - typing_extensions=4.9.0=pyha770c72_0 - - tzdata=2023c=h71feb2d_0 - - ucx=1.14.1=h64cca9d_5 - - urllib3=2.1.0=pyhd8ed1ab_0 - - wcwidth=0.2.12=pyhd8ed1ab_0 - - wheel=0.42.0=pyhd8ed1ab_0 - - xorg-libxau=1.0.11=hd590300_0 - - xorg-libxdmcp=1.1.3=h7f98852_0 - - xxhash=0.8.2=hd590300_0 - - xz=5.2.6=h166bdaf_0 - - yaml=0.2.5=h7f98852_2 - - yarl=1.9.3=py310h2372a71_0 - - zipp=3.17.0=pyhd8ed1ab_0 - - zlib=1.2.13=hd590300_5 - - zstd=1.5.5=hfc55251_0 - - pip: - - git+https://github.com/fyfrey/TinyNeuralNetwork.git diff --git a/machine-learning/ann/export/run.py b/machine-learning/ann/export/run.py deleted file mode 100644 index 91c659a02c..0000000000 --- a/machine-learning/ann/export/run.py +++ /dev/null @@ -1,157 +0,0 @@ -import logging -import os -import platform -import subprocess -from abc import abstractmethod - -import onnx -import open_clip -import torch -from onnx2torch import convert -from onnxruntime.tools.onnx_model_utils import fix_output_shapes, make_input_shape_fixed -from tinynn.converter import TFLiteConverter - - -class ExportBase(torch.nn.Module): - input_shape: tuple[int, ...] - - def __init__(self, device: torch.device, name: str): - super().__init__() - self.device = device - self.name = name - self.optimize = 5 - self.nchw_transpose = False - - @abstractmethod - def forward(self, input_tensor: torch.Tensor) -> torch.Tensor | tuple[torch.Tensor]: - pass - - def dummy_input(self) -> torch.FloatTensor: - return torch.rand((1, 3, 224, 224), device=self.device) - - -class ArcFace(ExportBase): - input_shape = (1, 3, 112, 112) - - def __init__(self, onnx_model_path: str, device: torch.device): - name, _ = os.path.splitext(os.path.basename(onnx_model_path)) - super().__init__(device, name) - onnx_model = onnx.load_model(onnx_model_path) - make_input_shape_fixed(onnx_model.graph, onnx_model.graph.input[0].name, self.input_shape) - fix_output_shapes(onnx_model) - self.model = convert(onnx_model).to(device) - if self.device.type == "cuda": - self.model = self.model.half() - - def forward(self, input_tensor: torch.Tensor) -> torch.FloatTensor: - embedding: torch.FloatTensor = self.model( - input_tensor.half() if self.device.type == "cuda" else input_tensor - ).float() - assert isinstance(embedding, torch.FloatTensor) - return embedding - - def dummy_input(self) -> torch.FloatTensor: - return torch.rand(self.input_shape, device=self.device) - - -class RetinaFace(ExportBase): - input_shape = (1, 3, 640, 640) - - def __init__(self, onnx_model_path: str, device: torch.device): - name, _ = os.path.splitext(os.path.basename(onnx_model_path)) - super().__init__(device, name) - self.optimize = 3 - self.model = convert(onnx_model_path).eval().to(device) - if self.device.type == "cuda": - self.model = self.model.half() - - def forward(self, input_tensor: torch.Tensor) -> tuple[torch.FloatTensor]: - out: torch.Tensor = self.model(input_tensor.half() if self.device.type == "cuda" else input_tensor) - return tuple(o.float() for o in out) - - def dummy_input(self) -> torch.FloatTensor: - return torch.rand(self.input_shape, device=self.device) - - -class ClipVision(ExportBase): - input_shape = (1, 3, 224, 224) - - def __init__(self, model_name: str, weights: str, device: torch.device): - super().__init__(device, model_name + "__" + weights) - self.model = open_clip.create_model( - model_name, - weights, - precision="fp16" if device.type == "cuda" else "fp32", - jit=False, - require_pretrained=True, - device=device, - ) - - def forward(self, input_tensor: torch.Tensor) -> torch.FloatTensor: - embedding: torch.Tensor = self.model.encode_image( - input_tensor.half() if self.device.type == "cuda" else input_tensor, - normalize=True, - ).float() - return embedding - - -def export(model: ExportBase) -> None: - model.eval() - for param in model.parameters(): - param.requires_grad = False - dummy_input = model.dummy_input() - model(dummy_input) - jit = torch.jit.trace(model, dummy_input) # type: ignore[no-untyped-call,attr-defined] - tflite_model_path = f"output/{model.name}.tflite" - os.makedirs("output", exist_ok=True) - - converter = TFLiteConverter( - jit, - dummy_input, - tflite_model_path, - optimize=model.optimize, - nchw_transpose=model.nchw_transpose, - ) - # segfaults on ARM, must run on x86_64 / AMD64 - converter.convert() - - armnn_model_path = f"output/{model.name}.armnn" - os.environ["LD_LIBRARY_PATH"] = "armnn" - subprocess.run( - [ - "./armnnconverter", - "-f", - "tflite-binary", - "-m", - tflite_model_path, - "-i", - "input_tensor", - "-o", - "output_tensor", - "-p", - armnn_model_path, - ] - ) - - -def main() -> None: - if platform.machine() not in ("x86_64", "AMD64"): - raise RuntimeError(f"Can only run on x86_64 / AMD64, not {platform.machine()}") - - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - if device.type != "cuda": - logging.warning( - "No CUDA available, cannot create fp16 model! proceeding to create a fp32 model (use only for testing)" - ) - models = [ - ClipVision("ViT-B-32", "openai", device), - ArcFace("buffalo_l_rec.onnx", device), - RetinaFace("buffalo_l_det.onnx", device), - ] - for model in models: - export(model) - - -if __name__ == "__main__": - with torch.no_grad(): - main() diff --git a/machine-learning/conftest.py b/machine-learning/conftest.py deleted file mode 100644 index 57741f3807..0000000000 --- a/machine-learning/conftest.py +++ /dev/null @@ -1,185 +0,0 @@ -import json -from typing import Any, Iterator -from unittest import mock - -import numpy as np -import pytest -from fastapi.testclient import TestClient -from numpy.typing import NDArray -from PIL import Image - -from immich_ml.config import log -from immich_ml.main import app - - -@pytest.fixture -def pil_image() -> Image.Image: - return Image.new("RGB", (600, 800)) - - -@pytest.fixture -def cv_image(pil_image: Image.Image) -> NDArray[np.float32]: - return np.asarray(pil_image)[:, :, ::-1] # PIL uses RGB while cv2 uses BGR - - -@pytest.fixture -def mock_get_model() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.models.cache.from_model_type", autospec=True) as mocked: - yield mocked - - -@pytest.fixture(scope="session") -def deployed_app() -> Iterator[TestClient]: - with TestClient(app) as client: - yield client - - -@pytest.fixture(scope="session") -def responses() -> dict[str, Any]: - responses: dict[str, Any] = json.load(open("responses.json", "r")) - return responses - - -@pytest.fixture(scope="session") -def clip_model_cfg() -> dict[str, Any]: - return { - "embed_dim": 512, - "vision_cfg": {"image_size": 224, "layers": 12, "width": 768, "patch_size": 32}, - "text_cfg": {"context_length": 77, "vocab_size": 49408, "width": 512, "heads": 8, "layers": 12}, - } - - -@pytest.fixture(scope="session") -def clip_preprocess_cfg() -> dict[str, Any]: - return { - "size": [224, 224], - "mode": "RGB", - "mean": [0.48145466, 0.4578275, 0.40821073], - "std": [0.26862954, 0.26130258, 0.27577711], - "interpolation": "bicubic", - "resize_mode": "shortest", - "fill_color": 0, - } - - -@pytest.fixture(scope="session") -def clip_tokenizer_cfg() -> dict[str, Any]: - return { - "add_prefix_space": False, - "added_tokens_decoder": { - "49406": { - "content": "<|startoftext|>", - "lstrip": False, - "normalized": True, - "rstrip": False, - "single_word": False, - "special": True, - }, - "49407": { - "content": "<|endoftext|>", - "lstrip": False, - "normalized": True, - "rstrip": False, - "single_word": False, - "special": True, - }, - }, - "bos_token": "<|startoftext|>", - "clean_up_tokenization_spaces": True, - "do_lower_case": True, - "eos_token": "<|endoftext|>", - "errors": "replace", - "model_max_length": 77, - "pad_token": "<|endoftext|>", - "tokenizer_class": "CLIPTokenizer", - "unk_token": "<|endoftext|>", - } - - -@pytest.fixture(scope="function") -def providers(request: pytest.FixtureRequest) -> Iterator[mock.Mock]: - marker = request.node.get_closest_marker("providers") - if marker is None: - raise ValueError("Missing marker 'providers'") - - providers = marker.args[0] - with mock.patch("immich_ml.sessions.ort.ort.get_available_providers") as mocked: - mocked.return_value = providers - yield providers - - -@pytest.fixture(scope="function") -def ort_pybind() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.sessions.ort.ort.capi._pybind_state") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def ov_device_ids(request: pytest.FixtureRequest, ort_pybind: mock.Mock) -> Iterator[mock.Mock]: - marker = request.node.get_closest_marker("ov_device_ids") - if marker is None: - raise ValueError("Missing marker 'ov_device_ids'") - ort_pybind.get_available_openvino_device_ids.return_value = marker.args[0] - return ort_pybind - - -@pytest.fixture(scope="function") -def ort_session() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.sessions.ort.ort.InferenceSession") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def ann_session() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.sessions.ann.Ann") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def rknn_session() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.sessions.rknn.RknnPoolExecutor") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def rmtree() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.models.base.rmtree", autospec=True) as mocked: - mocked.avoids_symlink_attacks = True - yield mocked - - -@pytest.fixture(scope="function") -def path() -> Iterator[mock.Mock]: - path = mock.MagicMock() - path.exists.return_value = True - path.is_dir.return_value = True - path.is_file.return_value = True - path.with_suffix.return_value = path - path.return_value = path - - with mock.patch("immich_ml.models.base.Path", return_value=path) as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def info() -> Iterator[mock.Mock]: - with mock.patch.object(log, "info") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def warning() -> Iterator[mock.Mock]: - with mock.patch.object(log, "warning") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def exception() -> Iterator[mock.Mock]: - with mock.patch.object(log, "exception") as mocked: - yield mocked - - -@pytest.fixture(scope="function") -def snapshot_download() -> Iterator[mock.Mock]: - with mock.patch("immich_ml.models.base.snapshot_download") as mocked: - yield mocked diff --git a/machine-learning/immich_ml/__init__.py b/machine-learning/immich_ml/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/machine-learning/immich_ml/__main__.py b/machine-learning/immich_ml/__main__.py deleted file mode 100644 index 8d575a58d5..0000000000 --- a/machine-learning/immich_ml/__main__.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import signal -import subprocess -from ipaddress import ip_address -from pathlib import Path - -from .config import log, non_prefixed_settings, settings - -if source_ref := os.getenv("IMMICH_SOURCE_REF"): - log.info(f"Initializing Immich ML [{source_ref}]") -else: - log.info("Initializing Immich ML") - -module_dir = Path(__file__).parent - - -def is_ipv6(host: str) -> bool: - try: - return ip_address(host).version == 6 - except ValueError: - return False - - -bind_host = non_prefixed_settings.immich_host -if is_ipv6(bind_host): - bind_host = f"[{bind_host}]" -bind_address = f"{bind_host}:{non_prefixed_settings.immich_port}" - -try: - with subprocess.Popen( - [ - "python", - "-m", - "gunicorn", - "immich_ml.main:app", - "-k", - "immich_ml.config.CustomUvicornWorker", - "-c", - module_dir / "gunicorn_conf.py", - "-b", - bind_address, - "-w", - str(settings.workers), - "-t", - str(settings.worker_timeout), - "--log-config-json", - module_dir / "log_conf.json", - "--keep-alive", - str(settings.http_keepalive_timeout_s), - "--graceful-timeout", - "10", - ], - ) as cmd: - cmd.wait() -except KeyboardInterrupt: - cmd.send_signal(signal.SIGINT) -exit(cmd.returncode) diff --git a/machine-learning/immich_ml/config.py b/machine-learning/immich_ml/config.py deleted file mode 100644 index 19fd5300df..0000000000 --- a/machine-learning/immich_ml/config.py +++ /dev/null @@ -1,165 +0,0 @@ -import concurrent.futures -import logging -import os -import sys -from pathlib import Path -from socket import socket - -from gunicorn.arbiter import Arbiter -from pydantic import BaseModel -from pydantic_settings import BaseSettings, SettingsConfigDict -from rich.console import Console -from rich.logging import RichHandler -from uvicorn import Server -from uvicorn.workers import UvicornWorker - -from .schemas import ModelPrecision - - -class ClipSettings(BaseModel): - textual: str | None = None - visual: str | None = None - - -class FacialRecognitionSettings(BaseModel): - recognition: str | None = None - detection: str | None = None - - -class OcrSettings(BaseModel): - recognition: str | None = None - detection: str | None = None - - -class PreloadModelData(BaseModel): - clip_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__CLIP", None) - facial_recognition_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION", None) - if clip_fallback is not None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = clip_fallback - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = clip_fallback - del os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] - if facial_recognition_fallback is not None: - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = facial_recognition_fallback - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = facial_recognition_fallback - del os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] - clip: ClipSettings = ClipSettings() - facial_recognition: FacialRecognitionSettings = FacialRecognitionSettings() - ocr: OcrSettings = OcrSettings() - - -class MaxBatchSize(BaseModel): - facial_recognition: int | None = None - text_recognition: int | None = None - - -class Settings(BaseSettings): - model_config = SettingsConfigDict( - env_prefix="MACHINE_LEARNING_", - case_sensitive=False, - env_nested_delimiter="__", - protected_namespaces=("settings_",), - ) - - cache_folder: Path = (Path.home() / ".cache" / "immich_ml").resolve() - model_ttl: int = 300 - model_ttl_poll_s: int = 10 - workers: int = 1 - worker_timeout: int = 300 - http_keepalive_timeout_s: int = 2 - test_full: bool = False - request_threads: int = os.cpu_count() or 4 - model_inter_op_threads: int = 0 - model_intra_op_threads: int = 0 - model_arena: bool = True - ann: bool = True - ann_fp16_turbo: bool = False - ann_tuning_level: int = 2 - rknn: bool = True - rknn_threads: int = 1 - preload: PreloadModelData | None = None - max_batch_size: MaxBatchSize | None = None - openvino_precision: ModelPrecision = ModelPrecision.FP32 - - @property - def device_id(self) -> str: - return os.environ.get("MACHINE_LEARNING_DEVICE_ID", "0") - - -class NonPrefixedSettings(BaseSettings): - model_config = SettingsConfigDict(case_sensitive=False) - - immich_host: str = "[::]" - immich_port: int = 3003 - immich_log_level: str = "info" - no_color: bool = False - - -_clean_name = str.maketrans(":\\/", "___", ".") - - -def clean_name(model_name: str) -> str: - return model_name.split("/")[-1].translate(_clean_name) - - -LOG_LEVELS: dict[str, int] = { - "critical": logging.ERROR, - "error": logging.ERROR, - "warning": logging.WARNING, - "warn": logging.WARNING, - "info": logging.INFO, - "log": logging.INFO, - "debug": logging.DEBUG, - "verbose": logging.DEBUG, -} - -settings = Settings() -non_prefixed_settings = NonPrefixedSettings() - -LOG_LEVEL = LOG_LEVELS.get(non_prefixed_settings.immich_log_level.lower(), logging.INFO) - - -class CustomRichHandler(RichHandler): - def __init__(self) -> None: - console = Console(color_system="standard", no_color=non_prefixed_settings.no_color) - self.excluded = ["uvicorn", "starlette", "fastapi"] - super().__init__( - show_path=False, - omit_repeated_times=False, - console=console, - rich_tracebacks=True, - tracebacks_suppress=[*self.excluded, concurrent.futures], - tracebacks_show_locals=LOG_LEVEL == logging.DEBUG, - ) - - # hack to exclude certain modules from rich tracebacks - def emit(self, record: logging.LogRecord) -> None: - if record.exc_info is not None: - tb = record.exc_info[2] - while tb is not None: - if any(excluded in tb.tb_frame.f_code.co_filename for excluded in self.excluded): - tb.tb_frame.f_locals["_rich_traceback_omit"] = True - tb = tb.tb_next - - return super().emit(record) - - -log = logging.getLogger("ml.log") -log.setLevel(LOG_LEVEL) - - -# patches this issue https://github.com/encode/uvicorn/discussions/1803 -class CustomUvicornServer(Server): - async def shutdown(self, sockets: list[socket] | None = None) -> None: - for sock in sockets or []: - sock.close() - await super().shutdown() - - -class CustomUvicornWorker(UvicornWorker): - async def _serve(self) -> None: - self.config.app = self.wsgi - server = CustomUvicornServer(config=self.config) - self._install_sigquit_handler() - await server.serve(sockets=self.sockets) - if not server.started: - sys.exit(Arbiter.WORKER_BOOT_ERROR) diff --git a/machine-learning/immich_ml/gunicorn_conf.py b/machine-learning/immich_ml/gunicorn_conf.py deleted file mode 100644 index efec3a95aa..0000000000 --- a/machine-learning/immich_ml/gunicorn_conf.py +++ /dev/null @@ -1,12 +0,0 @@ -import os - -from gunicorn.arbiter import Arbiter -from gunicorn.workers.base import Worker - -device_ids = os.environ.get("MACHINE_LEARNING_DEVICE_IDS", "0").replace(" ", "").split(",") -env = os.environ - - -# Round-robin device assignment for each worker -def pre_fork(arbiter: Arbiter, _: Worker) -> None: - env["MACHINE_LEARNING_DEVICE_ID"] = device_ids[len(arbiter.WORKERS) % len(device_ids)] diff --git a/machine-learning/immich_ml/log_conf.json b/machine-learning/immich_ml/log_conf.json deleted file mode 100644 index d30b86d486..0000000000 --- a/machine-learning/immich_ml/log_conf.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": 1, - "disable_existing_loggers": false, - "handlers": { - "console": { - "class": "immich_ml.config.CustomRichHandler" - } - }, - "loggers": { - "gunicorn.error": { - "handlers": [ - "console" - ] - } - }, - "root": { - "handlers": [ - "console" - ] - } -} diff --git a/machine-learning/immich_ml/main.py b/machine-learning/immich_ml/main.py deleted file mode 100644 index e7e3a719bb..0000000000 --- a/machine-learning/immich_ml/main.py +++ /dev/null @@ -1,272 +0,0 @@ -import asyncio -import gc -import os -import signal -import threading -import time -from concurrent.futures import ThreadPoolExecutor -from contextlib import asynccontextmanager -from functools import partial -from typing import Any, AsyncGenerator, Callable, Iterator -from zipfile import BadZipFile - -import orjson -from fastapi import Depends, FastAPI, File, Form, HTTPException -from fastapi.responses import ORJSONResponse, PlainTextResponse -from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile -from PIL.Image import Image -from pydantic import ValidationError -from starlette.formparsers import MultiPartParser - -from immich_ml.models import get_model_deps -from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import decode_pil - -from .config import PreloadModelData, log, settings -from .models.cache import ModelCache -from .schemas import ( - InferenceEntries, - InferenceEntry, - InferenceResponse, - ModelFormat, - ModelIdentity, - ModelTask, - ModelType, - PipelineRequest, - T, -) - -MultiPartParser.spool_max_size = 2**26 # spools to disk if payload is 64 MiB or larger - -model_cache = ModelCache(revalidate=settings.model_ttl > 0) -thread_pool: ThreadPoolExecutor | None = None -lock = threading.Lock() -active_requests = 0 -last_called: float | None = None - - -@asynccontextmanager -async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: - global thread_pool - log.info( - ( - "Created in-memory cache with unloading " - f"{f'after {settings.model_ttl}s of inactivity' if settings.model_ttl > 0 else 'disabled'}." - ) - ) - - try: - if settings.request_threads > 0: - # asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code - thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None - log.info(f"Initialized request thread pool with {settings.request_threads} threads.") - if settings.model_ttl > 0 and settings.model_ttl_poll_s > 0: - asyncio.ensure_future(idle_shutdown_task()) - if settings.preload is not None: - await preload_models(settings.preload) - yield - finally: - log.handlers.clear() - for model in model_cache.cache._cache.values(): - del model - if thread_pool is not None: - thread_pool.shutdown() - gc.collect() - - -async def preload_models(preload: PreloadModelData) -> None: - log.info(f"Preloading models: clip:{preload.clip} facial_recognition:{preload.facial_recognition}") - - async def load_models(model_string: str, model_type: ModelType, model_task: ModelTask) -> None: - for model_name in model_string.split(","): - model_name = model_name.strip() - model = await model_cache.get(model_name, model_type, model_task) - await load(model) - - if preload.clip.textual is not None: - await load_models(preload.clip.textual, ModelType.TEXTUAL, ModelTask.SEARCH) - - if preload.clip.visual is not None: - await load_models(preload.clip.visual, ModelType.VISUAL, ModelTask.SEARCH) - - if preload.facial_recognition.detection is not None: - await load_models( - preload.facial_recognition.detection, - ModelType.DETECTION, - ModelTask.FACIAL_RECOGNITION, - ) - - if preload.facial_recognition.recognition is not None: - await load_models( - preload.facial_recognition.recognition, - ModelType.RECOGNITION, - ModelTask.FACIAL_RECOGNITION, - ) - - if preload.ocr.detection is not None: - await load_models( - preload.ocr.detection, - ModelType.DETECTION, - ModelTask.OCR, - ) - - if preload.ocr.recognition is not None: - await load_models( - preload.ocr.recognition, - ModelType.RECOGNITION, - ModelTask.OCR, - ) - - if preload.clip_fallback is not None: - log.warning( - "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__CLIP'. " - "Use 'MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL' and " - "'MACHINE_LEARNING_PRELOAD__CLIP__VISUAL' instead." - ) - - if preload.facial_recognition_fallback is not None: - log.warning( - "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION'. " - "Use 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION' and " - "'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION' instead." - ) - - -def update_state() -> Iterator[None]: - global active_requests, last_called - active_requests += 1 - last_called = time.time() - try: - yield - finally: - active_requests -= 1 - - -def get_entries(entries: str = Form()) -> InferenceEntries: - try: - request: PipelineRequest = orjson.loads(entries) - without_deps: list[InferenceEntry] = [] - with_deps: list[InferenceEntry] = [] - for task, types in request.items(): - for type, entry in types.items(): - parsed: InferenceEntry = { - "name": entry["modelName"], - "task": task, - "type": type, - "options": entry.get("options", {}), - } - dep = get_model_deps(parsed["name"], type, task) - (with_deps if dep else without_deps).append(parsed) - return without_deps, with_deps - except (orjson.JSONDecodeError, ValidationError, KeyError, AttributeError) as e: - log.error(f"Invalid request format: {e}") - raise HTTPException(422, "Invalid request format.") - - -app = FastAPI(lifespan=lifespan) - - -@app.get("/") -async def root() -> ORJSONResponse: - return ORJSONResponse({"message": "Immich ML"}) - - -@app.get("/ping") -def ping() -> PlainTextResponse: - return PlainTextResponse("pong") - - -@app.post("/predict", dependencies=[Depends(update_state)]) -async def predict( - entries: InferenceEntries = Depends(get_entries), - image: bytes | None = File(default=None), - text: str | None = Form(default=None), -) -> Any: - if image is not None: - inputs: Image | str = await run(lambda: decode_pil(image)) - elif text is not None: - inputs = text - else: - raise HTTPException(400, "Either image or text must be provided") - response = await run_inference(inputs, entries) - return ORJSONResponse(response) - - -async def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse: - outputs: dict[ModelIdentity, Any] = {} - response: InferenceResponse = {} - - async def _run_inference(entry: InferenceEntry) -> None: - model = await model_cache.get( - entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl, **entry["options"] - ) - inputs = [payload] - for dep in model.depends: - try: - inputs.append(outputs[dep]) - except KeyError: - message = f"Task {entry['task']} of type {entry['type']} depends on output of {dep}" - raise HTTPException(400, message) - model = await load(model) - output = await run(model.predict, *inputs, **entry["options"]) - outputs[model.identity] = output - response[entry["task"]] = output - - without_deps, with_deps = entries - await asyncio.gather(*[_run_inference(entry) for entry in without_deps]) - if with_deps: - await asyncio.gather(*[_run_inference(entry) for entry in with_deps]) - if isinstance(payload, Image): - response["imageHeight"], response["imageWidth"] = payload.height, payload.width - - return response - - -async def run(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: - if thread_pool is None: - return func(*args, **kwargs) - partial_func = partial(func, *args, **kwargs) - return await asyncio.get_running_loop().run_in_executor(thread_pool, partial_func) - - -async def load(model: InferenceModel) -> InferenceModel: - if model.loaded: - return model - - def _load(model: InferenceModel) -> InferenceModel: - if model.load_attempts > 1: - raise HTTPException(500, f"Failed to load model '{model.model_name}'") - with lock: - try: - model.load() - except FileNotFoundError as e: - if model.model_format == ModelFormat.ONNX: - raise e - log.warning( - f"{model.model_format.upper()} is available, but model '{model.model_name}' does not support it.", - exc_info=e, - ) - model.model_format = ModelFormat.ONNX - model.load() - return model - - try: - return await run(_load, model) - except (OSError, InvalidProtobuf, BadZipFile, NoSuchFile): - log.warning(f"Failed to load {model.model_type.replace('_', ' ')} model '{model.model_name}'. Clearing cache.") - model.clear_cache() - return await run(_load, model) - - -async def idle_shutdown_task() -> None: - while True: - if ( - last_called is not None - and not active_requests - and not lock.locked() - and time.time() - last_called > settings.model_ttl - ): - log.info("Shutting down due to inactivity.") - os.kill(os.getpid(), signal.SIGINT) - break - await asyncio.sleep(settings.model_ttl_poll_s) diff --git a/machine-learning/immich_ml/models/__init__.py b/machine-learning/immich_ml/models/__init__.py deleted file mode 100644 index 6c3a74c963..0000000000 --- a/machine-learning/immich_ml/models/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import Any - -from immich_ml.models.base import InferenceModel -from immich_ml.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder -from immich_ml.models.clip.visual import OpenClipVisualEncoder -from immich_ml.models.ocr.detection import TextDetector -from immich_ml.models.ocr.recognition import TextRecognizer -from immich_ml.schemas import ModelSource, ModelTask, ModelType - -from .constants import get_model_source -from .facial_recognition.detection import FaceDetector -from .facial_recognition.recognition import FaceRecognizer - - -def get_model_class(model_name: str, model_type: ModelType, model_task: ModelTask) -> type[InferenceModel]: - source = get_model_source(model_name) - match source, model_type, model_task: - case ModelSource.OPENCLIP | ModelSource.MCLIP, ModelType.VISUAL, ModelTask.SEARCH: - return OpenClipVisualEncoder - - case ModelSource.OPENCLIP, ModelType.TEXTUAL, ModelTask.SEARCH: - return OpenClipTextualEncoder - - case ModelSource.MCLIP, ModelType.TEXTUAL, ModelTask.SEARCH: - return MClipTextualEncoder - - case ModelSource.INSIGHTFACE, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION: - return FaceDetector - - case ModelSource.INSIGHTFACE, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION: - return FaceRecognizer - - case ModelSource.PADDLE, ModelType.DETECTION, ModelTask.OCR: - return TextDetector - - case ModelSource.PADDLE, ModelType.RECOGNITION, ModelTask.OCR: - return TextRecognizer - - case _: - raise ValueError(f"Unknown model combination: {source}, {model_type}, {model_task}") - - -def from_model_type(model_name: str, model_type: ModelType, model_task: ModelTask, **kwargs: Any) -> InferenceModel: - return get_model_class(model_name, model_type, model_task)(model_name, **kwargs) - - -def get_model_deps(model_name: str, model_type: ModelType, model_task: ModelTask) -> list[tuple[ModelType, ModelTask]]: - return get_model_class(model_name, model_type, model_task).depends diff --git a/machine-learning/immich_ml/models/base.py b/machine-learning/immich_ml/models/base.py deleted file mode 100644 index 00790ce744..0000000000 --- a/machine-learning/immich_ml/models/base.py +++ /dev/null @@ -1,176 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod -from pathlib import Path -from shutil import rmtree -from typing import Any, ClassVar - -from huggingface_hub import snapshot_download - -import immich_ml.sessions.ann.loader -import immich_ml.sessions.rknn as rknn -from immich_ml.sessions.ort import OrtSession - -from ..config import clean_name, log, settings -from ..schemas import ModelFormat, ModelIdentity, ModelSession, ModelTask, ModelType -from ..sessions.ann import AnnSession - - -class InferenceModel(ABC): - depends: ClassVar[list[ModelIdentity]] - identity: ClassVar[ModelIdentity] - - def __init__( - self, - model_name: str, - cache_dir: Path | str | None = None, - model_format: ModelFormat | None = None, - session: ModelSession | None = None, - **model_kwargs: Any, - ) -> None: - self.loaded = session is not None - self.load_attempts = 0 - self.model_name = clean_name(model_name) - self.cache_dir = Path(cache_dir) if cache_dir is not None else self._cache_dir_default - self.model_format = model_format if model_format is not None else self._model_format_default - if session is not None: - self.session = session - - def download(self) -> None: - if not self.cached: - model_type = self.model_type.replace("-", " ") - log.info(f"Downloading {model_type} model '{self.model_name}' to {self.model_path}. This may take a while.") - self._download() - - def load(self) -> None: - if self.loaded: - return - self.load_attempts += 1 - - self.download() - attempt = f"Attempt #{self.load_attempts} to load" if self.load_attempts > 1 else "Loading" - log.info(f"{attempt} {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory") - self.session = self._load() - self.loaded = True - - def predict(self, *inputs: Any, **model_kwargs: Any) -> Any: - self.load() - if model_kwargs: - self.configure(**model_kwargs) - return self._predict(*inputs) - - @abstractmethod - def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ... - - def configure(self, **kwargs: Any) -> None: - pass - - def _download(self) -> None: - ignored_patterns: dict[ModelFormat, list[str]] = { - ModelFormat.ONNX: ["*.armnn", "*.rknn"], - ModelFormat.ARMNN: ["*.rknn"], - ModelFormat.RKNN: ["*.armnn"], - } - - snapshot_download( - f"immich-app/{clean_name(self.model_name)}", - cache_dir=self.cache_dir, - local_dir=self.cache_dir, - ignore_patterns=ignored_patterns.get(self.model_format, []), - ) - - def _load(self) -> ModelSession: - return self._make_session(self.model_path) - - def clear_cache(self) -> None: - if not self.cache_dir.exists(): - log.warning( - f"Attempted to clear cache for model '{self.model_name}', but cache directory does not exist", - ) - return - if not rmtree.avoids_symlink_attacks: - raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform") - - if self.cache_dir.is_dir(): - log.info(f"Cleared cache directory for model '{self.model_name}'.") - rmtree(self.cache_dir) - else: - log.warning( - ( - f"Encountered file instead of directory at cache path " - f"for '{self.model_name}'. Removing file and replacing with a directory." - ), - ) - self.cache_dir.unlink() - self.cache_dir.mkdir(parents=True, exist_ok=True) - - def _make_session(self, model_path: Path) -> ModelSession: - if not model_path.is_file(): - raise FileNotFoundError(f"Model file not found: {model_path}") - - match model_path.suffix: - case ".armnn": - session: ModelSession = AnnSession(model_path) - case ".onnx": - session = OrtSession(model_path) - case ".rknn": - session = rknn.RknnSession(model_path) - case _: - raise ValueError(f"Unsupported model file type: {model_path.suffix}") - return session - - def model_path_for_format(self, model_format: ModelFormat) -> Path: - model_path_prefix = rknn.model_prefix if model_format == ModelFormat.RKNN else None - if model_path_prefix: - return self.model_dir / model_path_prefix / f"model.{model_format}" - return self.model_dir / f"model.{model_format}" - - @property - def model_dir(self) -> Path: - return self.cache_dir / self.model_type.value - - @property - def model_path(self) -> Path: - return self.model_path_for_format(self.model_format) - - @property - def model_task(self) -> ModelTask: - return self.identity[1] - - @property - def model_type(self) -> ModelType: - return self.identity[0] - - @property - def cache_dir(self) -> Path: - return self._cache_dir - - @cache_dir.setter - def cache_dir(self, cache_dir: Path) -> None: - self._cache_dir = cache_dir - - @property - def _cache_dir_default(self) -> Path: - return settings.cache_folder / self.model_task.value / self.model_name - - @property - def cached(self) -> bool: - return self.model_path.is_file() - - @property - def model_format(self) -> ModelFormat: - return self._model_format - - @model_format.setter - def model_format(self, model_format: ModelFormat) -> None: - log.debug(f"Setting model format to {model_format}") - self._model_format = model_format - - @property - def _model_format_default(self) -> ModelFormat: - if rknn.is_available: - return ModelFormat.RKNN - elif immich_ml.sessions.ann.loader.is_available and settings.ann: - return ModelFormat.ARMNN - else: - return ModelFormat.ONNX diff --git a/machine-learning/immich_ml/models/cache.py b/machine-learning/immich_ml/models/cache.py deleted file mode 100644 index d8f9ca81bd..0000000000 --- a/machine-learning/immich_ml/models/cache.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Any - -from aiocache.backends.memory import SimpleMemoryCache -from aiocache.lock import OptimisticLock -from aiocache.plugins import TimingPlugin - -from immich_ml.models import from_model_type -from immich_ml.models.base import InferenceModel - -from ..schemas import ModelTask, ModelType, has_profiling - - -class ModelCache: - """Fetches a model from an in-memory cache, instantiating it if it's missing.""" - - def __init__( - self, - revalidate: bool = False, - timeout: int | None = None, - profiling: bool = False, - ) -> None: - """ - Args: - revalidate: Resets TTL on cache hit. Useful to keep models in memory while active. Defaults to False. - timeout: Maximum allowed time for model to load. Disabled if None. Defaults to None. - profiling: Collects metrics for cache operations, adding slight overhead. Defaults to False. - """ - - plugins = [] - - if profiling: - plugins.append(TimingPlugin()) - - self.should_revalidate = revalidate - - self.cache = SimpleMemoryCache(timeout=timeout, plugins=plugins, namespace=None) - - async def get( - self, model_name: str, model_type: ModelType, model_task: ModelTask, **model_kwargs: Any - ) -> InferenceModel: - key = f"{model_name}{model_type}{model_task}" - - async with OptimisticLock(self.cache, key) as lock: - model: InferenceModel | None = await self.cache.get(key) - if model is None: - model = from_model_type(model_name, model_type, model_task, **model_kwargs) - await lock.cas(model, ttl=model_kwargs.get("ttl", None)) - elif self.should_revalidate: - await self.revalidate(key, model_kwargs.get("ttl", None)) - return model - - async def get_profiling(self) -> dict[str, float] | None: - if not has_profiling(self.cache): - return None - - return self.cache.profiling - - async def revalidate(self, key: str, ttl: int | None) -> None: - if ttl is not None and key in self.cache._handlers: - await self.cache.expire(key, ttl) diff --git a/machine-learning/immich_ml/models/clip/textual.py b/machine-learning/immich_ml/models/clip/textual.py deleted file mode 100644 index 7e1908e120..0000000000 --- a/machine-learning/immich_ml/models/clip/textual.py +++ /dev/null @@ -1,120 +0,0 @@ -import json -from abc import abstractmethod -from functools import cached_property -from pathlib import Path -from typing import Any - -import numpy as np -from numpy.typing import NDArray -from tokenizers import Encoding, Tokenizer - -from immich_ml.config import log -from immich_ml.models.base import InferenceModel -from immich_ml.models.constants import WEBLATE_TO_FLORES200 -from immich_ml.models.transforms import clean_text, serialize_np_array -from immich_ml.schemas import ModelSession, ModelTask, ModelType - - -class BaseCLIPTextualEncoder(InferenceModel): - depends = [] - identity = (ModelType.TEXTUAL, ModelTask.SEARCH) - - def _predict(self, inputs: str, language: str | None = None) -> str: - tokens = self.tokenize(inputs, language=language) - res: NDArray[np.float32] = self.session.run(None, tokens)[0][0] - return serialize_np_array(res) - - def _load(self) -> ModelSession: - session = super()._load() - log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'") - self.tokenizer = self._load_tokenizer() - tokenizer_kwargs: dict[str, Any] | None = self.text_cfg.get("tokenizer_kwargs") - self.canonicalize = tokenizer_kwargs is not None and tokenizer_kwargs.get("clean") == "canonicalize" - self.is_nllb = self.model_name.startswith("nllb") - log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'") - - return session - - @abstractmethod - def _load_tokenizer(self) -> Tokenizer: - pass - - @abstractmethod - def tokenize(self, text: str, language: str | None = None) -> dict[str, NDArray[np.int32]]: - pass - - @property - def model_cfg_path(self) -> Path: - return self.cache_dir / "config.json" - - @property - def tokenizer_file_path(self) -> Path: - return self.model_dir / "tokenizer.json" - - @property - def tokenizer_cfg_path(self) -> Path: - return self.model_dir / "tokenizer_config.json" - - @cached_property - def model_cfg(self) -> dict[str, Any]: - log.debug(f"Loading model config for CLIP model '{self.model_name}'") - model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open()) - log.debug(f"Loaded model config for CLIP model '{self.model_name}'") - return model_cfg - - @property - def text_cfg(self) -> dict[str, Any]: - text_cfg: dict[str, Any] = self.model_cfg["text_cfg"] - return text_cfg - - @cached_property - def tokenizer_file(self) -> dict[str, Any]: - log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'") - tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open()) - log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'") - return tokenizer_file - - @cached_property - def tokenizer_cfg(self) -> dict[str, Any]: - log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'") - tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open()) - log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'") - return tokenizer_cfg - - -class OpenClipTextualEncoder(BaseCLIPTextualEncoder): - def _load_tokenizer(self) -> Tokenizer: - context_length: int = self.text_cfg.get("context_length", 77) - pad_token: str = self.tokenizer_cfg["pad_token"] - - tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix()) - - pad_id: int = tokenizer.token_to_id(pad_token) - tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id) - tokenizer.enable_truncation(max_length=context_length) - - return tokenizer - - def tokenize(self, text: str, language: str | None = None) -> dict[str, NDArray[np.int32]]: - text = clean_text(text, canonicalize=self.canonicalize) - if self.is_nllb and language is not None: - flores_code = WEBLATE_TO_FLORES200.get(language) - if flores_code is None: - no_country = language.split("-")[0] - flores_code = WEBLATE_TO_FLORES200.get(no_country) - if flores_code is None: - log.warning(f"Language '{language}' not found, defaulting to 'en'") - flores_code = "eng_Latn" - text = f"{flores_code}{text}" - tokens: Encoding = self.tokenizer.encode(text) - return {"text": np.array([tokens.ids], dtype=np.int32)} - - -class MClipTextualEncoder(OpenClipTextualEncoder): - def tokenize(self, text: str, language: str | None = None) -> dict[str, NDArray[np.int32]]: - text = clean_text(text, canonicalize=self.canonicalize) - tokens: Encoding = self.tokenizer.encode(text) - return { - "input_ids": np.array([tokens.ids], dtype=np.int32), - "attention_mask": np.array([tokens.attention_mask], dtype=np.int32), - } diff --git a/machine-learning/immich_ml/models/clip/visual.py b/machine-learning/immich_ml/models/clip/visual.py deleted file mode 100644 index 16993f9c01..0000000000 --- a/machine-learning/immich_ml/models/clip/visual.py +++ /dev/null @@ -1,77 +0,0 @@ -import json -from abc import abstractmethod -from functools import cached_property -from pathlib import Path -from typing import Any - -import numpy as np -from numpy.typing import NDArray -from PIL import Image - -from immich_ml.config import log -from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import ( - crop_pil, - decode_pil, - get_pil_resampling, - normalize, - resize_pil, - serialize_np_array, - to_numpy, -) -from immich_ml.schemas import ModelSession, ModelTask, ModelType - - -class BaseCLIPVisualEncoder(InferenceModel): - depends = [] - identity = (ModelType.VISUAL, ModelTask.SEARCH) - - def _predict(self, inputs: Image.Image | bytes) -> str: - image = decode_pil(inputs) - res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0] - return serialize_np_array(res) - - @abstractmethod - def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]: - pass - - @property - def model_cfg_path(self) -> Path: - return self.cache_dir / "config.json" - - @property - def preprocess_cfg_path(self) -> Path: - return self.model_dir / "preprocess_cfg.json" - - @cached_property - def model_cfg(self) -> dict[str, Any]: - log.debug(f"Loading model config for CLIP model '{self.model_name}'") - model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open()) - log.debug(f"Loaded model config for CLIP model '{self.model_name}'") - return model_cfg - - @cached_property - def preprocess_cfg(self) -> dict[str, Any]: - log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'") - preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open()) - log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'") - return preprocess_cfg - - -class OpenClipVisualEncoder(BaseCLIPVisualEncoder): - def _load(self) -> ModelSession: - size: list[int] | int = self.preprocess_cfg["size"] - self.size = size[0] if isinstance(size, list) else size - - self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"]) - self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32) - self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32) - - return super()._load() - - def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]: - image = resize_pil(image, self.size) - image = crop_pil(image, self.size) - image_np = to_numpy(image) - image_np = normalize(image_np, self.mean, self.std) - return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)} diff --git a/machine-learning/immich_ml/models/constants.py b/machine-learning/immich_ml/models/constants.py deleted file mode 100644 index db9e7cfa4d..0000000000 --- a/machine-learning/immich_ml/models/constants.py +++ /dev/null @@ -1,178 +0,0 @@ -from immich_ml.config import clean_name -from immich_ml.schemas import ModelSource - -_OPENCLIP_MODELS = { - "RN101__openai", - "RN101__yfcc15m", - "RN50__cc12m", - "RN50__openai", - "RN50__yfcc15m", - "RN50x16__openai", - "RN50x4__openai", - "RN50x64__openai", - "ViT-B-16-SigLIP-256__webli", - "ViT-B-16-SigLIP-384__webli", - "ViT-B-16-SigLIP-512__webli", - "ViT-B-16-SigLIP-i18n-256__webli", - "ViT-B-16-SigLIP__webli", - "ViT-B-16-plus-240__laion400m_e31", - "ViT-B-16-plus-240__laion400m_e32", - "ViT-B-16__laion400m_e31", - "ViT-B-16__laion400m_e32", - "ViT-B-16__openai", - "ViT-B-32__laion2b-s34b-b79k", - "ViT-B-32__laion2b_e16", - "ViT-B-32__laion400m_e31", - "ViT-B-32__laion400m_e32", - "ViT-B-32__openai", - "ViT-H-14-378-quickgelu__dfn5b", - "ViT-H-14-quickgelu__dfn5b", - "ViT-H-14__laion2b-s32b-b79k", - "ViT-L-14-336__openai", - "ViT-L-14-quickgelu__dfn2b", - "ViT-L-14__laion2b-s32b-b82k", - "ViT-L-14__laion400m_e31", - "ViT-L-14__laion400m_e32", - "ViT-L-14__openai", - "ViT-L-16-SigLIP-256__webli", - "ViT-L-16-SigLIP-384__webli", - "ViT-SO400M-14-SigLIP-384__webli", - "ViT-g-14__laion2b-s12b-b42k", - "XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k", - "XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k", - "nllb-clip-base-siglip__mrl", - "nllb-clip-base-siglip__v1", - "nllb-clip-large-siglip__mrl", - "nllb-clip-large-siglip__v1", - "ViT-B-16-SigLIP2__webli", - "ViT-B-32-SigLIP2-256__webli", - "ViT-L-16-SigLIP2-256__webli", - "ViT-L-16-SigLIP2-384__webli", - "ViT-L-16-SigLIP2-512__webli", - "ViT-SO400M-14-SigLIP2-378__webli", - "ViT-SO400M-14-SigLIP2__webli", - "ViT-SO400M-16-SigLIP2-256__webli", - "ViT-SO400M-16-SigLIP2-384__webli", - "ViT-SO400M-16-SigLIP2-512__webli", - "ViT-gopt-16-SigLIP2-256__webli", - "ViT-gopt-16-SigLIP2-384__webli", -} - - -_MCLIP_MODELS = { - "LABSE-Vit-L-14", - "XLM-Roberta-Large-Vit-B-16Plus", - "XLM-Roberta-Large-Vit-B-32", - "XLM-Roberta-Large-Vit-L-14", -} - - -_INSIGHTFACE_MODELS = { - "antelopev2", - "buffalo_s", - "buffalo_m", - "buffalo_l", -} - - -_PADDLE_MODELS = { - "PP-OCRv5_server", - "PP-OCRv5_mobile", - "CH__PP-OCRv5_server", - "CH__PP-OCRv5_mobile", - "EL__PP-OCRv5_mobile", - "EN__PP-OCRv5_mobile", - "ESLAV__PP-OCRv5_mobile", - "KOREAN__PP-OCRv5_mobile", - "LATIN__PP-OCRv5_mobile", - "TH__PP-OCRv5_mobile", -} - -SUPPORTED_PROVIDERS = [ - "CUDAExecutionProvider", - "ROCMExecutionProvider", - "OpenVINOExecutionProvider", - "CoreMLExecutionProvider", - "CPUExecutionProvider", -] - -RKNN_SUPPORTED_SOCS = ["rk3566", "rk3568", "rk3576", "rk3588"] -RKNN_COREMASK_SUPPORTED_SOCS = ["rk3576", "rk3588"] - - -WEBLATE_TO_FLORES200 = { - "af": "afr_Latn", - "ar": "arb_Arab", - "az": "azj_Latn", - "be": "bel_Cyrl", - "bg": "bul_Cyrl", - "ca": "cat_Latn", - "cs": "ces_Latn", - "da": "dan_Latn", - "de": "deu_Latn", - "el": "ell_Grek", - "en": "eng_Latn", - "es": "spa_Latn", - "et": "est_Latn", - "fa": "pes_Arab", - "fi": "fin_Latn", - "fr": "fra_Latn", - "he": "heb_Hebr", - "hi": "hin_Deva", - "hr": "hrv_Latn", - "hu": "hun_Latn", - "hy": "hye_Armn", - "id": "ind_Latn", - "it": "ita_Latn", - "ja": "jpn_Hira", - "kmr": "kmr_Latn", - "ko": "kor_Hang", - "lb": "ltz_Latn", - "lt": "lit_Latn", - "lv": "lav_Latn", - "mfa": "zsm_Latn", - "mk": "mkd_Cyrl", - "mn": "khk_Cyrl", - "mr": "mar_Deva", - "ms": "zsm_Latn", - "nb-NO": "nob_Latn", - "nn": "nno_Latn", - "nl": "nld_Latn", - "pl": "pol_Latn", - "pt-BR": "por_Latn", - "pt": "por_Latn", - "ro": "ron_Latn", - "ru": "rus_Cyrl", - "sk": "slk_Latn", - "sl": "slv_Latn", - "sr-Cyrl": "srp_Cyrl", - "sv": "swe_Latn", - "ta": "tam_Taml", - "te": "tel_Telu", - "th": "tha_Thai", - "tr": "tur_Latn", - "uk": "ukr_Cyrl", - "ur": "urd_Arab", - "vi": "vie_Latn", - "zh-CN": "zho_Hans", - "zh-Hans": "zho_Hans", - "zh-TW": "zho_Hant", -} - - -def get_model_source(model_name: str) -> ModelSource | None: - cleaned_name = clean_name(model_name) - - if cleaned_name in _INSIGHTFACE_MODELS: - return ModelSource.INSIGHTFACE - - if cleaned_name in _MCLIP_MODELS: - return ModelSource.MCLIP - - if cleaned_name in _OPENCLIP_MODELS: - return ModelSource.OPENCLIP - - if cleaned_name in _PADDLE_MODELS: - return ModelSource.PADDLE - - return None diff --git a/machine-learning/immich_ml/models/facial_recognition/detection.py b/machine-learning/immich_ml/models/facial_recognition/detection.py deleted file mode 100644 index 26c9208e48..0000000000 --- a/machine-learning/immich_ml/models/facial_recognition/detection.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any - -import numpy as np -from insightface.model_zoo import RetinaFace -from numpy.typing import NDArray - -from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import decode_cv2 -from immich_ml.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType - - -class FaceDetector(InferenceModel): - depends = [] - identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION) - - def __init__(self, model_name: str, min_score: float = 0.7, **model_kwargs: Any) -> None: - self.min_score = model_kwargs.pop("minScore", min_score) - super().__init__(model_name, **model_kwargs) - - def _load(self) -> ModelSession: - session = self._make_session(self.model_path) - self.model = RetinaFace(session=session) - self.model.prepare(ctx_id=0, det_thresh=self.min_score, input_size=(640, 640)) - - return session - - def _predict(self, inputs: NDArray[np.uint8] | bytes) -> FaceDetectionOutput: - inputs = decode_cv2(inputs) - - bboxes, landmarks = self._detect(inputs) - return { - "boxes": bboxes[:, :4].round(), - "scores": bboxes[:, 4], - "landmarks": landmarks, - } - - def _detect(self, inputs: NDArray[np.uint8] | bytes) -> tuple[NDArray[np.float32], NDArray[np.float32]]: - return self.model.detect(inputs) # type: ignore - - def configure(self, **kwargs: Any) -> None: - self.model.det_thresh = kwargs.pop("minScore", self.model.det_thresh) diff --git a/machine-learning/immich_ml/models/facial_recognition/recognition.py b/machine-learning/immich_ml/models/facial_recognition/recognition.py deleted file mode 100644 index 759992a600..0000000000 --- a/machine-learning/immich_ml/models/facial_recognition/recognition.py +++ /dev/null @@ -1,92 +0,0 @@ -from pathlib import Path -from typing import Any - -import numpy as np -import onnx -import onnxruntime as ort -from insightface.model_zoo import ArcFaceONNX -from insightface.utils.face_align import norm_crop -from numpy.typing import NDArray -from onnx.tools.update_model_dims import update_inputs_outputs_dims -from PIL import Image - -from immich_ml.config import log, settings -from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import decode_cv2, serialize_np_array -from immich_ml.schemas import ( - FaceDetectionOutput, - FacialRecognitionOutput, - ModelFormat, - ModelSession, - ModelTask, - ModelType, -) - - -class FaceRecognizer(InferenceModel): - depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)] - identity = (ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION) - - def __init__(self, model_name: str, **model_kwargs: Any) -> None: - super().__init__(model_name, **model_kwargs) - max_batch_size = settings.max_batch_size.facial_recognition if settings.max_batch_size else None - self.batch_size = max_batch_size if max_batch_size else self._batch_size_default - - def _load(self) -> ModelSession: - session = self._make_session(self.model_path) - if (not self.batch_size or self.batch_size > 1) and str(session.get_inputs()[0].shape[0]) != "batch": - self._add_batch_axis(self.model_path) - session = self._make_session(self.model_path) - self.model = ArcFaceONNX( - self.model_path_for_format(ModelFormat.ONNX).as_posix(), - session=session, - ) - return session - - def _predict( - self, inputs: NDArray[np.uint8] | bytes | Image.Image, faces: FaceDetectionOutput - ) -> FacialRecognitionOutput: - if faces["boxes"].shape[0] == 0: - return [] - inputs = decode_cv2(inputs) - cropped_faces = self._crop(inputs, faces) - embeddings = self._predict_batch(cropped_faces) - return self.postprocess(faces, embeddings) - - def _predict_batch(self, cropped_faces: list[NDArray[np.uint8]]) -> NDArray[np.float32]: - if not self.batch_size or len(cropped_faces) <= self.batch_size: - embeddings: NDArray[np.float32] = self.model.get_feat(cropped_faces) - return embeddings - - batch_embeddings: list[NDArray[np.float32]] = [] - for i in range(0, len(cropped_faces), self.batch_size): - batch_embeddings.append(self.model.get_feat(cropped_faces[i : i + self.batch_size])) - return np.concatenate(batch_embeddings, axis=0) - - def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32]) -> FacialRecognitionOutput: - return [ - { - "boundingBox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2}, - "embedding": serialize_np_array(embedding), - "score": score, - } - for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"]) - ] - - def _crop(self, image: NDArray[np.uint8], faces: FaceDetectionOutput) -> list[NDArray[np.uint8]]: - return [norm_crop(image, landmark) for landmark in faces["landmarks"]] - - def _add_batch_axis(self, model_path: Path) -> None: - log.debug(f"Adding batch axis to model {model_path}") - proto = onnx.load(model_path) - static_input_dims = [shape.dim_value for shape in proto.graph.input[0].type.tensor_type.shape.dim[1:]] - static_output_dims = [shape.dim_value for shape in proto.graph.output[0].type.tensor_type.shape.dim[1:]] - input_dims = {proto.graph.input[0].name: ["batch"] + static_input_dims} - output_dims = {proto.graph.output[0].name: ["batch"] + static_output_dims} - updated_proto = update_inputs_outputs_dims(proto, input_dims, output_dims) - onnx.save(updated_proto, model_path) - - @property - def _batch_size_default(self) -> int | None: - providers = ort.get_available_providers() - return None if self.model_format == ModelFormat.ONNX and "OpenVINOExecutionProvider" not in providers else 1 diff --git a/machine-learning/immich_ml/models/ocr/detection.py b/machine-learning/immich_ml/models/ocr/detection.py deleted file mode 100644 index d34a51684e..0000000000 --- a/machine-learning/immich_ml/models/ocr/detection.py +++ /dev/null @@ -1,125 +0,0 @@ -from typing import Any - -import cv2 -import numpy as np -from numpy.typing import NDArray -from PIL import Image -from rapidocr.ch_ppocr_det.utils import DBPostProcess -from rapidocr.inference_engine.base import FileInfo, InferSession -from rapidocr.utils.download_file import DownloadFile, DownloadFileInput -from rapidocr.utils.typings import EngineType, LangDet, OCRVersion, TaskType -from rapidocr.utils.typings import ModelType as RapidModelType - -from immich_ml.config import log -from immich_ml.models.base import InferenceModel -from immich_ml.schemas import ModelFormat, ModelSession, ModelTask, ModelType -from immich_ml.sessions.ort import OrtSession - -from .schemas import TextDetectionOutput - - -class TextDetector(InferenceModel): - depends = [] - identity = (ModelType.DETECTION, ModelTask.OCR) - - def __init__(self, model_name: str, **model_kwargs: Any) -> None: - super().__init__(model_name.split("__")[-1], **model_kwargs, model_format=ModelFormat.ONNX) - self.max_resolution = 736 - self.mean = np.array([0.5, 0.5, 0.5], dtype=np.float32) - self.std_inv = np.float32(1.0) / (np.array([0.5, 0.5, 0.5], dtype=np.float32) * 255.0) - self._empty: TextDetectionOutput = { - "boxes": np.empty(0, dtype=np.float32), - "scores": np.empty(0, dtype=np.float32), - } - self.postprocess = DBPostProcess( - thresh=0.3, - box_thresh=model_kwargs.get("minScore", 0.5), - max_candidates=1000, - unclip_ratio=1.6, - use_dilation=True, - score_mode="fast", - ) - - def _download(self) -> None: - model_info = InferSession.get_model_url( - FileInfo( - engine_type=EngineType.ONNXRUNTIME, - ocr_version=OCRVersion.PPOCRV5, - task_type=TaskType.DET, - lang_type=LangDet.CH, - model_type=RapidModelType.MOBILE if "mobile" in self.model_name else RapidModelType.SERVER, - ) - ) - download_params = DownloadFileInput( - file_url=model_info["model_dir"], - sha256=model_info["SHA256"], - save_path=self.model_path, - logger=log, - ) - DownloadFile.run(download_params) - - def _load(self) -> ModelSession: - # TODO: support other runtime sessions - return OrtSession(self.model_path) - - # partly adapted from RapidOCR - def _predict(self, inputs: Image.Image) -> TextDetectionOutput: - w, h = inputs.size - if w < 32 or h < 32: - return self._empty - out = self.session.run(None, {"x": self._transform(inputs)})[0] - boxes, scores = self.postprocess(out, (h, w)) - if len(boxes) == 0: - return self._empty - return { - "boxes": self.sorted_boxes(boxes), - "scores": np.array(scores, dtype=np.float32), - } - - # adapted from RapidOCR - def _transform(self, img: Image.Image) -> NDArray[np.float32]: - if img.height < img.width: - ratio = float(self.max_resolution) / img.height - else: - ratio = float(self.max_resolution) / img.width - ratio = min(ratio, 1.0) - - resize_h = int(img.height * ratio) - resize_w = int(img.width * ratio) - - resize_h = int(round(resize_h / 32) * 32) - resize_w = int(round(resize_w / 32) * 32) - resized_img = img.resize((int(resize_w), int(resize_h)), resample=Image.Resampling.LANCZOS) - - img_np: NDArray[np.float32] = cv2.cvtColor(np.array(resized_img, dtype=np.float32), cv2.COLOR_RGB2BGR) # type: ignore - img_np -= self.mean - img_np *= self.std_inv - img_np = np.transpose(img_np, (2, 0, 1)) - return np.expand_dims(img_np, axis=0) - - def sorted_boxes(self, dt_boxes: NDArray[np.float32]) -> NDArray[np.float32]: - if len(dt_boxes) == 0: - return dt_boxes - - # Sort by y, then identify lines, then sort by (line, x) - y_order = np.argsort(dt_boxes[:, 0, 1], kind="stable") - sorted_y = dt_boxes[y_order, 0, 1] - - line_ids = np.empty(len(dt_boxes), dtype=np.int32) - line_ids[0] = 0 - np.cumsum(np.abs(np.diff(sorted_y)) >= 10, out=line_ids[1:]) - - # Create composite sort key for final ordering - # Shift line_ids by large factor, add x for tie-breaking - sort_key = line_ids[y_order] * 1e6 + dt_boxes[y_order, 0, 0] - final_order = np.argsort(sort_key, kind="stable") - sorted_boxes: NDArray[np.float32] = dt_boxes[y_order[final_order]] - return sorted_boxes - - def configure(self, **kwargs: Any) -> None: - if (max_resolution := kwargs.get("maxResolution")) is not None: - self.max_resolution = max_resolution - if (min_score := kwargs.get("minScore")) is not None: - self.postprocess.box_thresh = min_score - if (score_mode := kwargs.get("scoreMode")) is not None: - self.postprocess.score_mode = score_mode diff --git a/machine-learning/immich_ml/models/ocr/recognition.py b/machine-learning/immich_ml/models/ocr/recognition.py deleted file mode 100644 index e968392881..0000000000 --- a/machine-learning/immich_ml/models/ocr/recognition.py +++ /dev/null @@ -1,153 +0,0 @@ -from typing import Any - -import numpy as np -from numpy.typing import NDArray -from PIL import Image -from rapidocr.ch_ppocr_rec import TextRecInput -from rapidocr.ch_ppocr_rec import TextRecognizer as RapidTextRecognizer -from rapidocr.inference_engine.base import FileInfo, InferSession -from rapidocr.utils.download_file import DownloadFile, DownloadFileInput -from rapidocr.utils.typings import EngineType, LangRec, OCRVersion, TaskType -from rapidocr.utils.typings import ModelType as RapidModelType -from rapidocr.utils.vis_res import VisRes - -from immich_ml.config import log, settings -from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import pil_to_cv2 -from immich_ml.schemas import ModelFormat, ModelSession, ModelTask, ModelType -from immich_ml.sessions.ort import OrtSession - -from .schemas import OcrOptions, TextDetectionOutput, TextRecognitionOutput - - -class TextRecognizer(InferenceModel): - depends = [(ModelType.DETECTION, ModelTask.OCR)] - identity = (ModelType.RECOGNITION, ModelTask.OCR) - - def __init__(self, model_name: str, **model_kwargs: Any) -> None: - self.language = LangRec[model_name.split("__")[0]] if "__" in model_name else LangRec.CH - self.min_score = model_kwargs.get("minScore", 0.9) - self._empty: TextRecognitionOutput = { - "box": np.empty(0, dtype=np.float32), - "boxScore": np.empty(0, dtype=np.float32), - "text": [], - "textScore": np.empty(0, dtype=np.float32), - } - VisRes.__init__ = lambda self, **kwargs: None # pyright: ignore[reportAttributeAccessIssue] - super().__init__(model_name, **model_kwargs, model_format=ModelFormat.ONNX) - - def _download(self) -> None: - model_info = InferSession.get_model_url( - FileInfo( - engine_type=EngineType.ONNXRUNTIME, - ocr_version=OCRVersion.PPOCRV5, - task_type=TaskType.REC, - lang_type=self.language, - model_type=RapidModelType.MOBILE if "mobile" in self.model_name else RapidModelType.SERVER, - ) - ) - download_params = DownloadFileInput( - file_url=model_info["model_dir"], - sha256=model_info["SHA256"], - save_path=self.model_path, - logger=log, - ) - DownloadFile.run(download_params) - - def _load(self) -> ModelSession: - # TODO: support other runtimes - session = OrtSession(self.model_path) - self.model = RapidTextRecognizer( - OcrOptions( - session=session.session, - rec_batch_num=settings.max_batch_size.text_recognition if settings.max_batch_size is not None else 6, - rec_img_shape=(3, 48, 320), - lang_type=self.language, - ) - ) - return session - - def _predict(self, img: Image.Image, texts: TextDetectionOutput) -> TextRecognitionOutput: - boxes, box_scores = texts["boxes"], texts["scores"] - if boxes.shape[0] == 0: - return self._empty - rec = self.model(TextRecInput(img=self.get_crop_img_list(img, boxes))) - if rec.txts is None: - return self._empty - - boxes[:, :, 0] /= img.width - boxes[:, :, 1] /= img.height - - text_scores = np.array(rec.scores) - valid_text_score_idx = text_scores > self.min_score - valid_score_idx_list = valid_text_score_idx.tolist() - return { - "box": boxes.reshape(-1, 8)[valid_text_score_idx].reshape(-1), - "text": [rec.txts[i] for i in range(len(rec.txts)) if valid_score_idx_list[i]], - "boxScore": box_scores[valid_text_score_idx], - "textScore": text_scores[valid_text_score_idx], - } - - def get_crop_img_list(self, img: Image.Image, boxes: NDArray[np.float32]) -> list[NDArray[np.uint8]]: - img_crop_width = np.maximum( - np.linalg.norm(boxes[:, 1] - boxes[:, 0], axis=1), np.linalg.norm(boxes[:, 2] - boxes[:, 3], axis=1) - ).astype(np.int32) - img_crop_height = np.maximum( - np.linalg.norm(boxes[:, 0] - boxes[:, 3], axis=1), np.linalg.norm(boxes[:, 1] - boxes[:, 2], axis=1) - ).astype(np.int32) - pts_std = np.zeros((img_crop_width.shape[0], 4, 2), dtype=np.float32) - pts_std[:, 1:3, 0] = img_crop_width[:, None] - pts_std[:, 2:4, 1] = img_crop_height[:, None] - - img_crop_sizes = np.stack([img_crop_width, img_crop_height], axis=1) - all_coeffs = self._get_perspective_transform(pts_std, boxes) - imgs: list[NDArray[np.uint8]] = [] - for coeffs, dst_size in zip(all_coeffs, img_crop_sizes): - dst_img = img.transform( - size=tuple(dst_size), - method=Image.Transform.PERSPECTIVE, - data=tuple(coeffs), - resample=Image.Resampling.BICUBIC, - ) - - dst_width, dst_height = dst_img.size - if dst_height * 1.0 / dst_width >= 1.5: - dst_img = dst_img.rotate(90, expand=True) - imgs.append(pil_to_cv2(dst_img)) - - return imgs - - def _get_perspective_transform(self, src: NDArray[np.float32], dst: NDArray[np.float32]) -> NDArray[np.float32]: - N = src.shape[0] - x, y = src[:, :, 0], src[:, :, 1] - u, v = dst[:, :, 0], dst[:, :, 1] - A = np.zeros((N, 8, 9), dtype=np.float32) - - # Fill even rows (0, 2, 4, 6): [x, y, 1, 0, 0, 0, -u*x, -u*y, -u] - A[:, ::2, 0] = x - A[:, ::2, 1] = y - A[:, ::2, 2] = 1 - A[:, ::2, 6] = -u * x - A[:, ::2, 7] = -u * y - A[:, ::2, 8] = -u - - # Fill odd rows (1, 3, 5, 7): [0, 0, 0, x, y, 1, -v*x, -v*y, -v] - A[:, 1::2, 3] = x - A[:, 1::2, 4] = y - A[:, 1::2, 5] = 1 - A[:, 1::2, 6] = -v * x - A[:, 1::2, 7] = -v * y - A[:, 1::2, 8] = -v - - # Solve using SVD for all matrices at once - _, _, Vt = np.linalg.svd(A) - H = Vt[:, -1, :].reshape(N, 3, 3) - H = H / H[:, 2:3, 2:3] - - # Extract the 8 coefficients for each transformation - return np.column_stack( - [H[:, 0, 0], H[:, 0, 1], H[:, 0, 2], H[:, 1, 0], H[:, 1, 1], H[:, 1, 2], H[:, 2, 0], H[:, 2, 1]] - ) # pyright: ignore[reportReturnType] - - def configure(self, **kwargs: Any) -> None: - self.min_score = kwargs.get("minScore", self.min_score) diff --git a/machine-learning/immich_ml/models/ocr/schemas.py b/machine-learning/immich_ml/models/ocr/schemas.py deleted file mode 100644 index 78e8619a0b..0000000000 --- a/machine-learning/immich_ml/models/ocr/schemas.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Any, Iterable - -import numpy as np -import numpy.typing as npt -from rapidocr.utils.typings import EngineType, LangRec -from typing_extensions import TypedDict - - -class TextDetectionOutput(TypedDict): - boxes: npt.NDArray[np.float32] - scores: npt.NDArray[np.float32] - - -class TextRecognitionOutput(TypedDict): - box: npt.NDArray[np.float32] - boxScore: npt.NDArray[np.float32] - text: Iterable[str] - textScore: npt.NDArray[np.float32] - - -# RapidOCR expects `engine_type`, `lang_type`, and `font_path` to be attributes -class OcrOptions(dict[str, Any]): - def __init__(self, lang_type: LangRec | None = None, **options: Any) -> None: - super().__init__(**options) - self.engine_type = EngineType.ONNXRUNTIME - self.lang_type = lang_type - self.font_path = None diff --git a/machine-learning/immich_ml/models/transforms.py b/machine-learning/immich_ml/models/transforms.py deleted file mode 100644 index 3f9d93bed3..0000000000 --- a/machine-learning/immich_ml/models/transforms.py +++ /dev/null @@ -1,80 +0,0 @@ -import string -from io import BytesIO -from typing import IO - -import cv2 -import numpy as np -import orjson -from numpy.typing import NDArray -from PIL import Image - -_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling} -_PUNCTUATION_TRANS = str.maketrans("", "", string.punctuation) - - -def resize_pil(img: Image.Image, size: int) -> Image.Image: - if img.width < img.height: - return img.resize((size, int((img.height / img.width) * size)), resample=Image.Resampling.BICUBIC) - else: - return img.resize((int((img.width / img.height) * size), size), resample=Image.Resampling.BICUBIC) - - -# https://stackoverflow.com/a/60883103 -def crop_pil(img: Image.Image, size: int) -> Image.Image: - left = int((img.size[0] / 2) - (size / 2)) - upper = int((img.size[1] / 2) - (size / 2)) - right = left + size - lower = upper + size - - return img.crop((left, upper, right, lower)) - - -def to_numpy(img: Image.Image) -> NDArray[np.float32]: - return np.asarray(img if img.mode == "RGB" else img.convert("RGB"), dtype=np.float32) / 255.0 - - -def normalize( - img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32] -) -> NDArray[np.float32]: - return (img - mean) / std - - -def get_pil_resampling(resample: str) -> Image.Resampling: - return _PIL_RESAMPLING_METHODS[resample.lower()] - - -def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]: - return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # type: ignore - - -def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image: - if isinstance(image_bytes, Image.Image): - return image_bytes - image: Image.Image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes) - image.load() - if not image.mode == "RGB": - image = image.convert("RGB") - return image - - -def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]: - match image_bytes: - case bytes() | memoryview() | bytearray(): - return pil_to_cv2(decode_pil(image_bytes)) # pillow is much faster than cv2 - case Image.Image(): - return pil_to_cv2(image_bytes) - case _: - return image_bytes - - -def clean_text(text: str, canonicalize: bool = False) -> str: - text = " ".join(text.split()) - if canonicalize: - text = text.translate(_PUNCTUATION_TRANS).lower() - return text - - -# this allows the client to use the array as a string without deserializing only to serialize back to a string -# TODO: use this in a less invasive way -def serialize_np_array(arr: NDArray[np.float32]) -> str: - return orjson.dumps(arr, option=orjson.OPT_SERIALIZE_NUMPY).decode() diff --git a/machine-learning/immich_ml/schemas.py b/machine-learning/immich_ml/schemas.py deleted file mode 100644 index 41706180de..0000000000 --- a/machine-learning/immich_ml/schemas.py +++ /dev/null @@ -1,122 +0,0 @@ -from enum import Enum -from typing import Any, Literal, Protocol, TypeGuard, TypeVar - -import numpy as np -import numpy.typing as npt -from typing_extensions import TypedDict - - -class StrEnum(str, Enum): - value: str - - def __str__(self) -> str: - return self.value - - -class BoundingBox(TypedDict): - x1: int - y1: int - x2: int - y2: int - - -class ModelTask(StrEnum): - FACIAL_RECOGNITION = "facial-recognition" - SEARCH = "clip" - OCR = "ocr" - - -class ModelType(StrEnum): - DETECTION = "detection" - RECOGNITION = "recognition" - TEXTUAL = "textual" - VISUAL = "visual" - - -class ModelFormat(StrEnum): - ARMNN = "armnn" - ONNX = "onnx" - RKNN = "rknn" - - -class ModelSource(StrEnum): - INSIGHTFACE = "insightface" - MCLIP = "mclip" - OPENCLIP = "openclip" - PADDLE = "paddle" - - -class ModelPrecision(StrEnum): - FP16 = "FP16" - FP32 = "FP32" - - -ModelIdentity = tuple[ModelType, ModelTask] - - -class SessionNode(Protocol): - @property - def name(self) -> str | None: ... - - @property - def shape(self) -> tuple[int, ...]: ... - - -class ModelSession(Protocol): - def run( - self, - output_names: list[str] | None, - input_feed: dict[str, npt.NDArray[np.float32]] | dict[str, npt.NDArray[np.int32]], - run_options: Any = None, - ) -> list[npt.NDArray[np.float32]]: ... - - def get_inputs(self) -> list[SessionNode]: ... - - def get_outputs(self) -> list[SessionNode]: ... - - -class HasProfiling(Protocol): - profiling: dict[str, float] - - -class FaceDetectionOutput(TypedDict): - boxes: npt.NDArray[np.float32] - scores: npt.NDArray[np.float32] - landmarks: npt.NDArray[np.float32] - - -class DetectedFace(TypedDict): - boundingBox: BoundingBox - embedding: str - score: float - - -FacialRecognitionOutput = list[DetectedFace] - - -class PipelineEntry(TypedDict): - modelName: str - options: dict[str, Any] - - -PipelineRequest = dict[ModelTask, dict[ModelType, PipelineEntry]] - - -class InferenceEntry(TypedDict): - name: str - task: ModelTask - type: ModelType - options: dict[str, Any] - - -InferenceEntries = tuple[list[InferenceEntry], list[InferenceEntry]] - - -InferenceResponse = dict[ModelTask | Literal["imageHeight"] | Literal["imageWidth"], Any] - - -def has_profiling(obj: Any) -> TypeGuard[HasProfiling]: - return hasattr(obj, "profiling") and isinstance(obj.profiling, dict) - - -T = TypeVar("T") diff --git a/machine-learning/immich_ml/sessions/__init__.py b/machine-learning/immich_ml/sessions/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/machine-learning/immich_ml/sessions/ann/__init__.py b/machine-learning/immich_ml/sessions/ann/__init__.py deleted file mode 100644 index 6f36f675f6..0000000000 --- a/machine-learning/immich_ml/sessions/ann/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from typing import Any, NamedTuple - -import numpy as np -from numpy.typing import NDArray - -from immich_ml.config import log, settings -from immich_ml.schemas import SessionNode - -from .loader import Ann - - -class AnnSession: - """ - Wrapper for ANN to be drop-in replacement for ONNX session. - """ - - def __init__(self, model_path: Path, cache_dir: Path = settings.cache_folder) -> None: - self.model_path = model_path - self.cache_dir = cache_dir - self.ann = Ann(tuning_level=settings.ann_tuning_level, tuning_file=(cache_dir / "gpu-tuning.ann").as_posix()) - - log.info("Loading ANN model %s ...", model_path) - self.model = self.ann.load( - model_path.as_posix(), - cached_network_path=model_path.with_suffix(".anncache").as_posix(), - fp16=settings.ann_fp16_turbo, - ) - log.info("Loaded ANN model with ID %d", self.model) - - def __del__(self) -> None: - self.ann.unload(self.model) - log.info("Unloaded ANN model %d", self.model) - self.ann.destroy() - - def get_inputs(self) -> list[SessionNode]: - shapes = self.ann.input_shapes[self.model] - return [AnnNode(None, s) for s in shapes] - - def get_outputs(self) -> list[SessionNode]: - shapes = self.ann.output_shapes[self.model] - return [AnnNode(None, s) for s in shapes] - - def run( - self, - output_names: list[str] | None, - input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], - run_options: Any = None, - ) -> list[NDArray[np.float32]]: - inputs: list[NDArray[np.float32]] = [np.ascontiguousarray(v) for v in input_feed.values()] - return self.ann.execute(self.model, inputs) - - -class AnnNode(NamedTuple): - name: str | None - shape: tuple[int, ...] diff --git a/machine-learning/immich_ml/sessions/ann/loader.py b/machine-learning/immich_ml/sessions/ann/loader.py deleted file mode 100644 index 41a90dbe74..0000000000 --- a/machine-learning/immich_ml/sessions/ann/loader.py +++ /dev/null @@ -1,169 +0,0 @@ -from __future__ import annotations - -from ctypes import CDLL, Array, c_bool, c_char_p, c_int, c_ulong, c_void_p -from os.path import exists -from typing import Any, Protocol, TypeVar - -import numpy as np -from numpy.typing import NDArray - -from immich_ml.config import log - -try: - CDLL("libmali.so") # fail if libmali.so is not mounted into container - libann = CDLL("libann.so") - libann.init.argtypes = c_int, c_int, c_char_p - libann.init.restype = c_void_p - libann.load.argtypes = c_void_p, c_char_p, c_bool, c_bool, c_bool, c_char_p - libann.load.restype = c_int - libann.execute.argtypes = c_void_p, c_int, Array[c_void_p], Array[c_void_p] - libann.unload.argtypes = c_void_p, c_int - libann.destroy.argtypes = (c_void_p,) - libann.shape.argtypes = c_void_p, c_int, c_bool, c_int - libann.shape.restype = c_ulong - libann.tensors.argtypes = c_void_p, c_int, c_bool - libann.tensors.restype = c_int - is_available = True -except OSError as e: - log.debug("Could not load ANN shared libraries, using ONNX: %s", e) - is_available = False - -T = TypeVar("T", covariant=True) - - -class Newable(Protocol[T]): - def new(self) -> None: ... - - -class _Singleton(type, Newable[T]): - _instances: dict[_Singleton[T], Newable[T]] = {} - - def __call__(cls, *args: Any, **kwargs: Any) -> Newable[T]: - if cls not in cls._instances: - obj: Newable[T] = super(_Singleton, cls).__call__(*args, **kwargs) - cls._instances[cls] = obj - else: - obj = cls._instances[cls] - obj.new() - return obj - - -class Ann(metaclass=_Singleton): - def __init__(self, log_level: int = 3, tuning_level: int = 1, tuning_file: str | None = None) -> None: - if not is_available: - raise RuntimeError("libann is not available!") - if tuning_level == 0 and tuning_file is None: - raise ValueError("tuning_level == 0 reads existing tuning information and requires a tuning_file") - if tuning_level < 0 or tuning_level > 3: - raise ValueError("tuning_level must be 0 (load from tuning_file), 1, 2 or 3.") - if log_level < 0 or log_level > 5: - raise ValueError("log_level must be 0 (trace), 1 (debug), 2 (info), 3 (warning), 4 (error) or 5 (fatal)") - self.log_level = log_level - self.tuning_level = tuning_level - self.tuning_file = tuning_file - self.output_shapes: dict[int, tuple[tuple[int], ...]] = {} - self.input_shapes: dict[int, tuple[tuple[int], ...]] = {} - self.ann: int | None = None - self.new() - - if self.tuning_file is not None: - # make sure tuning file exists (without clearing contents) - # once filled, the tuning file reduces the cost/time of the first - # inference after model load by 10s of seconds - open(self.tuning_file, "a").close() - - def new(self) -> None: - if self.ann is None: - self.ann = libann.init( - self.log_level, - self.tuning_level, - self.tuning_file.encode() if self.tuning_file is not None else None, - ) - self.ref_count = 0 - - self.ref_count += 1 - - def destroy(self) -> None: - self.ref_count -= 1 - if self.ref_count <= 0 and self.ann is not None: - libann.destroy(self.ann) - self.ann = None - - def __del__(self) -> None: - if self.ann is not None: - libann.destroy(self.ann) - self.ann = None - - def load( - self, - model_path: str, - fast_math: bool = True, - fp16: bool = False, - cached_network_path: str | None = None, - ) -> int: - if not model_path.endswith((".armnn", ".tflite", ".onnx")): - raise ValueError("model_path must be a file with extension .armnn, .tflite or .onnx") - if not exists(model_path): - raise ValueError("model_path must point to an existing file!") - - save_cached_network = False - if cached_network_path is not None and not exists(cached_network_path): - save_cached_network = True - # create empty model cache file - open(cached_network_path, "a").close() - - net_id: int = libann.load( - self.ann, - model_path.encode(), - fast_math, - fp16, - save_cached_network, - cached_network_path.encode() if cached_network_path is not None else None, - ) - if net_id < 0: - raise ValueError("Cannot load model!") - - self.input_shapes[net_id] = tuple( - self.shape(net_id, input=True, index=i) for i in range(self.tensors(net_id, input=True)) - ) - self.output_shapes[net_id] = tuple( - self.shape(net_id, input=False, index=i) for i in range(self.tensors(net_id, input=False)) - ) - return net_id - - def unload(self, network_id: int) -> None: - libann.unload(self.ann, network_id) - del self.output_shapes[network_id] - - def execute(self, network_id: int, input_tensors: list[NDArray[np.float32]]) -> list[NDArray[np.float32]]: - if not isinstance(input_tensors, list): - raise ValueError("input_tensors needs to be a list!") - net_input_shapes = self.input_shapes[network_id] - if len(input_tensors) != len(net_input_shapes): - raise ValueError(f"input_tensors lengths {len(input_tensors)} != network inputs {len(net_input_shapes)}") - for net_input_shape, input_tensor in zip(net_input_shapes, input_tensors): - if net_input_shape != input_tensor.shape: - raise ValueError(f"input_tensor shape {input_tensor.shape} != network input shape {net_input_shape}") - if not input_tensor.flags.c_contiguous: - raise ValueError("input_tensors must be c_contiguous numpy ndarrays") - output_tensors: list[NDArray[np.float32]] = [ - np.ndarray(s, dtype=np.float32) for s in self.output_shapes[network_id] - ] - input_type = c_void_p * len(input_tensors) - inputs = input_type(*[t.ctypes.data_as(c_void_p) for t in input_tensors]) - output_type = c_void_p * len(output_tensors) - outputs = output_type(*[t.ctypes.data_as(c_void_p) for t in output_tensors]) - libann.execute(self.ann, network_id, inputs, outputs) - return output_tensors - - def shape(self, network_id: int, input: bool = False, index: int = 0) -> tuple[int]: - s = libann.shape(self.ann, network_id, input, index) - a = [] - while s != 0: - a.append(s & 0xFFFF) - s >>= 16 - return tuple(a) - - def tensors(self, network_id: int, input: bool = False) -> int: - tensors: int = libann.tensors(self.ann, network_id, input) - return tensors diff --git a/machine-learning/immich_ml/sessions/ort.py b/machine-learning/immich_ml/sessions/ort.py deleted file mode 100644 index 6c52936722..0000000000 --- a/machine-learning/immich_ml/sessions/ort.py +++ /dev/null @@ -1,147 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from typing import Any - -import numpy as np -import onnxruntime as ort -from numpy.typing import NDArray - -from immich_ml.models.constants import SUPPORTED_PROVIDERS -from immich_ml.schemas import SessionNode - -from ..config import log, settings - - -class OrtSession: - session: ort.InferenceSession - - def __init__( - self, - model_path: Path | str, - providers: list[str] | None = None, - provider_options: list[dict[str, Any]] | None = None, - sess_options: ort.SessionOptions | None = None, - ): - self.model_path = Path(model_path) - self.providers = providers if providers is not None else self._providers_default - self.provider_options = provider_options if provider_options is not None else self._provider_options_default - self.sess_options = sess_options if sess_options is not None else self._sess_options_default - self.session = ort.InferenceSession( - self.model_path.as_posix(), - providers=self.providers, - provider_options=self.provider_options, - sess_options=self.sess_options, - ) - - def get_inputs(self) -> list[SessionNode]: - inputs: list[SessionNode] = self.session.get_inputs() - return inputs - - def get_outputs(self) -> list[SessionNode]: - outputs: list[SessionNode] = self.session.get_outputs() - return outputs - - def run( - self, - output_names: list[str] | None, - input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], - run_options: Any = None, - ) -> list[NDArray[np.float32]]: - outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options) - return outputs - - @property - def providers(self) -> list[str]: - return self._providers - - @providers.setter - def providers(self, providers: list[str]) -> None: - log.info(f"Setting execution providers to {providers}, in descending order of preference") - self._providers = providers - - @property - def _providers_default(self) -> list[str]: - available_providers = set(ort.get_available_providers()) - log.debug(f"Available ORT providers: {available_providers}") - if (openvino := "OpenVINOExecutionProvider") in available_providers: - device_ids: list[str] = ort.capi._pybind_state.get_available_openvino_device_ids() - log.debug(f"Available OpenVINO devices: {device_ids}") - - gpu_devices = [device_id for device_id in device_ids if device_id.startswith("GPU")] - if not gpu_devices: - log.warning("No GPU device found in OpenVINO. Falling back to CPU.") - available_providers.remove(openvino) - return [provider for provider in SUPPORTED_PROVIDERS if provider in available_providers] - - @property - def provider_options(self) -> list[dict[str, Any]]: - return self._provider_options - - @provider_options.setter - def provider_options(self, provider_options: list[dict[str, Any]]) -> None: - log.debug(f"Setting execution provider options to {provider_options}") - self._provider_options = provider_options - - @property - def _provider_options_default(self) -> list[dict[str, Any]]: - provider_options = [] - for provider in self.providers: - match provider: - case "CPUExecutionProvider": - options = {"arena_extend_strategy": "kSameAsRequested"} - case "CUDAExecutionProvider" | "ROCMExecutionProvider": - options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id} - case "OpenVINOExecutionProvider": - openvino_dir = self.model_path.parent / "openvino" - device = f"GPU.{settings.device_id}" - options = { - "device_type": device, - "precision": settings.openvino_precision.value, - "cache_dir": openvino_dir.as_posix(), - } - case "CoreMLExecutionProvider": - options = { - "ModelFormat": "MLProgram", - "MLComputeUnits": "ALL", - "SpecializationStrategy": "FastPrediction", - "AllowLowPrecisionAccumulationOnGPU": "1", - "ModelCacheDirectory": (self.model_path.parent / "coreml").as_posix(), - } - case _: - options = {} - provider_options.append(options) - return provider_options - - @property - def sess_options(self) -> ort.SessionOptions: - return self._sess_options - - @sess_options.setter - def sess_options(self, sess_options: ort.SessionOptions) -> None: - log.debug(f"Setting execution_mode to {sess_options.execution_mode.name}") - log.debug(f"Setting inter_op_num_threads to {sess_options.inter_op_num_threads}") - log.debug(f"Setting intra_op_num_threads to {sess_options.intra_op_num_threads}") - self._sess_options = sess_options - - @property - def _sess_options_default(self) -> ort.SessionOptions: - sess_options = ort.SessionOptions() - sess_options.enable_cpu_mem_arena = settings.model_arena - - # avoid thread contention between models - if settings.model_inter_op_threads > 0: - sess_options.inter_op_num_threads = settings.model_inter_op_threads - # these defaults work well for CPU, but bottleneck GPU - elif settings.model_inter_op_threads == 0 and self.providers == ["CPUExecutionProvider"]: - sess_options.inter_op_num_threads = 1 - - if settings.model_intra_op_threads > 0: - sess_options.intra_op_num_threads = settings.model_intra_op_threads - elif settings.model_intra_op_threads == 0 and self.providers == ["CPUExecutionProvider"]: - sess_options.intra_op_num_threads = 2 - - if sess_options.inter_op_num_threads > 1: - sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL - - return sess_options diff --git a/machine-learning/immich_ml/sessions/rknn/__init__.py b/machine-learning/immich_ml/sessions/rknn/__init__.py deleted file mode 100644 index e388e4febc..0000000000 --- a/machine-learning/immich_ml/sessions/rknn/__init__.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from typing import Any, NamedTuple - -import numpy as np -from numpy.typing import NDArray - -from immich_ml.config import log, settings -from immich_ml.schemas import SessionNode - -from .rknnpool import RknnPoolExecutor, is_available, soc_name - -is_available = is_available and settings.rknn -model_prefix = Path("rknpu") / soc_name if is_available and soc_name is not None else None - - -def run_inference(rknn_lite: Any, input: list[NDArray[np.float32]]) -> list[NDArray[np.float32]]: - outputs: list[NDArray[np.float32]] = rknn_lite.inference(inputs=input, data_format="nchw") - return outputs - - -input_output_mapping: dict[str, dict[str, Any]] = { - "detection": { - "input": {"norm_tensor:0": (1, 3, 640, 640)}, - "output": { - "norm_tensor:1": (12800, 1), - "norm_tensor:2": (3200, 1), - "norm_tensor:3": (800, 1), - "norm_tensor:4": (12800, 4), - "norm_tensor:5": (3200, 4), - "norm_tensor:6": (800, 4), - "norm_tensor:7": (12800, 10), - "norm_tensor:8": (3200, 10), - "norm_tensor:9": (800, 10), - }, - }, - "recognition": {"input": {"norm_tensor:0": (1, 3, 112, 112)}, "output": {"norm_tensor:1": (1, 512)}}, -} - - -class RknnSession: - def __init__(self, model_path: Path) -> None: - self.model_type = "detection" if "detection" in model_path.parts else "recognition" - self.tpe = settings.rknn_threads - - log.info(f"Loading RKNN model from {model_path} with {self.tpe} threads.") - self.rknnpool = RknnPoolExecutor(model_path=model_path.as_posix(), tpes=self.tpe, func=run_inference) - log.info(f"Loaded RKNN model from {model_path} with {self.tpe} threads.") - - def get_inputs(self) -> list[SessionNode]: - return [RknnNode(name=k, shape=v) for k, v in input_output_mapping[self.model_type]["input"].items()] - - def get_outputs(self) -> list[SessionNode]: - return [RknnNode(name=k, shape=v) for k, v in input_output_mapping[self.model_type]["output"].items()] - - def run( - self, - output_names: list[str] | None, - input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], - run_options: Any = None, - ) -> list[NDArray[np.float32]]: - input_data: list[NDArray[np.float32]] = [np.ascontiguousarray(v) for v in input_feed.values()] - self.rknnpool.put(input_data) - res = self.rknnpool.get() - if res is None: - raise RuntimeError("RKNN inference failed!") - return res - - -class RknnNode(NamedTuple): - name: str | None - shape: tuple[int, ...] - - -__all__ = ["RknnSession", "RknnNode", "is_available", "soc_name", "model_prefix"] diff --git a/machine-learning/immich_ml/sessions/rknn/rknnpool.py b/machine-learning/immich_ml/sessions/rknn/rknnpool.py deleted file mode 100644 index fd0af8bcc4..0000000000 --- a/machine-learning/immich_ml/sessions/rknn/rknnpool.py +++ /dev/null @@ -1,91 +0,0 @@ -# This code is from leafqycc/rknn-multi-threaded -# Following Apache License 2.0 - -import logging -from concurrent.futures import Future, ThreadPoolExecutor -from pathlib import Path -from queue import Queue -from typing import Callable - -import numpy as np -from numpy.typing import NDArray - -from immich_ml.config import log -from immich_ml.models.constants import RKNN_COREMASK_SUPPORTED_SOCS, RKNN_SUPPORTED_SOCS - - -def get_soc(device_tree_path: Path | str) -> str | None: - try: - with Path(device_tree_path).open() as f: - device_compatible_str = f.read() - for soc in RKNN_SUPPORTED_SOCS: - if soc in device_compatible_str: - return soc - log.warning("Device is not supported for RKNN") - except OSError as e: - log.warning(f"Could not read {device_tree_path}. Reason: %s", e) - return None - - -soc_name = None -is_available = False -try: - from rknnlite.api import RKNNLite - - soc_name = get_soc("/proc/device-tree/compatible") - is_available = soc_name is not None -except ImportError: - log.debug("RKNN is not available") - - -def init_rknn(model_path: str) -> "RKNNLite": - if not is_available: - raise RuntimeError("rknn is not available!") - rknn_lite = RKNNLite() - rknn_lite.rknn_log.logger.setLevel(logging.ERROR) - ret = rknn_lite.load_rknn(model_path) - if ret != 0: - raise RuntimeError("Failed to load RKNN model") - - if soc_name in RKNN_COREMASK_SUPPORTED_SOCS: - ret = rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO) - else: - ret = rknn_lite.init_runtime() # Please do not set this parameter on other platforms. - - if ret != 0: - raise RuntimeError("Failed to initialize RKNN runtime environment") - - return rknn_lite - - -class RknnPoolExecutor: - def __init__( - self, - model_path: str, - tpes: int, - func: Callable[["RKNNLite", list[NDArray[np.float32]]], list[NDArray[np.float32]]], - ) -> None: - self.tpes = tpes - self.queue: Queue[Future[list[NDArray[np.float32]]]] = Queue() - self.rknn_pool = [init_rknn(model_path) for _ in range(tpes)] - self.pool = ThreadPoolExecutor(max_workers=tpes) - self.func = func - self.num = 0 - - def put(self, inputs: list[NDArray[np.float32]]) -> None: - self.queue.put(self.pool.submit(self.func, self.rknn_pool[self.num % self.tpes], inputs)) - self.num += 1 - - def get(self) -> list[NDArray[np.float32]] | None: - if self.queue.empty(): - return None - fut = self.queue.get() - return fut.result() - - def release(self) -> None: - self.pool.shutdown() - for rknn_lite in self.rknn_pool: - rknn_lite.release() - - def __del__(self) -> None: - self.release() diff --git a/machine-learning/locustfile.py b/machine-learning/locustfile.py deleted file mode 100644 index 9a07a99688..0000000000 --- a/machine-learning/locustfile.py +++ /dev/null @@ -1,81 +0,0 @@ -import json -from argparse import ArgumentParser -from io import BytesIO -from typing import Any - -from locust import HttpUser, events, task -from locust.env import Environment -from PIL import Image - -byte_image = BytesIO() - - -@events.init_command_line_parser.add_listener -def _(parser: ArgumentParser) -> None: - parser.add_argument("--clip-model", type=str, default="ViT-B-32::openai") - parser.add_argument("--face-model", type=str, default="buffalo_l") - parser.add_argument( - "--face-min-score", - type=int, - default=0.034, - help=( - "Returns all faces at or above this score. The default returns 1 face per request; " - "setting this to 0 blows up the number of faces to the thousands." - ), - ) - parser.add_argument("--image-size", type=int, default=1000) - - -@events.test_start.add_listener -def on_test_start(environment: Environment, **kwargs: Any) -> None: - global byte_image - assert environment.parsed_options is not None - image = Image.new("RGB", (environment.parsed_options.image_size, environment.parsed_options.image_size)) - image.save(byte_image, format="jpeg") - - -class InferenceLoadTest(HttpUser): - abstract: bool = True - host = "http://127.0.0.1:3003" - data: bytes - - # re-use the image across all instances in a process - def on_start(self) -> None: - self.data = byte_image.getvalue() - - -class CLIPTextFormDataLoadTest(InferenceLoadTest): - @task - def encode_text(self) -> None: - request = {"clip": {"textual": {"modelName": self.environment.parsed_options.clip_model}}} - data = [("entries", json.dumps(request)), ("text", "test search query")] - self.client.post("/predict", data=data) - - -class CLIPVisionFormDataLoadTest(InferenceLoadTest): - @task - def encode_image(self) -> None: - request = {"clip": {"visual": {"modelName": self.environment.parsed_options.clip_model, "options": {}}}} - data = [("entries", json.dumps(request))] - files = {"image": self.data} - self.client.post("/predict", data=data, files=files) - - -class RecognitionFormDataLoadTest(InferenceLoadTest): - @task - def recognize(self) -> None: - request = { - "facial-recognition": { - "recognition": { - "modelName": self.environment.parsed_options.face_model, - }, - "detection": { - "modelName": self.environment.parsed_options.face_model, - "options": {"minScore": self.environment.parsed_options.face_min_score}, - }, - } - } - data = [("entries", json.dumps(request))] - files = {"image": self.data} - - self.client.post("/predict", data=data, files=files) diff --git a/machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch b/machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch deleted file mode 100644 index 6627f67778..0000000000 --- a/machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch +++ /dev/null @@ -1,179 +0,0 @@ -commit 16839b58d9b3c3162a67ce5d776b36d4d24e801f -Author: mertalev <101130780+mertalev@users.noreply.github.com> -Date: Wed Mar 5 11:25:38 2025 -0500 - - disable algo caching (attributed to @dmnieto in https://github.com/microsoft/onnxruntime/pull/19567) - -diff --git a/onnxruntime/core/providers/rocm/nn/conv.cc b/onnxruntime/core/providers/rocm/nn/conv.cc -index d7f47d07a8..4060a2af52 100644 ---- a/onnxruntime/core/providers/rocm/nn/conv.cc -+++ b/onnxruntime/core/providers/rocm/nn/conv.cc -@@ -127,7 +127,6 @@ Status Conv::UpdateState(OpKernelContext* context, bool bias_expected) - - if (w_dims_changed) { - s_.last_w_dims = gsl::make_span(w_dims); -- s_.cached_benchmark_fwd_results.clear(); - } - - ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X->Shape(), W->Shape(), channels_last, channels_last)); -@@ -277,35 +276,6 @@ Status Conv::UpdateState(OpKernelContext* context, bool bias_expected) - HIP_CALL_THROW(hipMalloc(&s_.b_zero, malloc_size)); - HIP_CALL_THROW(hipMemsetAsync(s_.b_zero, 0, malloc_size, Stream(context))); - } -- -- if (!s_.cached_benchmark_fwd_results.contains(x_dims_miopen)) { -- miopenConvAlgoPerf_t perf; -- int algo_count = 1; -- const ROCMExecutionProvider* rocm_ep = static_cast(this->Info().GetExecutionProvider()); -- static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT; -- size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId()) -- : AlgoSearchWorkspaceSize; -- IAllocatorUniquePtr algo_search_workspace = GetTransientScratchBuffer(max_ws_size); -- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm( -- GetMiopenHandle(context), -- s_.x_tensor, -- s_.x_data, -- s_.w_desc, -- s_.w_data, -- s_.conv_desc, -- s_.y_tensor, -- s_.y_data, -- 1, // requestedAlgoCount -- &algo_count, // returnedAlgoCount -- &perf, -- algo_search_workspace.get(), -- max_ws_size, -- false)); // Do not do exhaustive algo search. -- s_.cached_benchmark_fwd_results.insert(x_dims_miopen, {perf.fwd_algo, perf.memory}); -- } -- const auto& perf = s_.cached_benchmark_fwd_results.at(x_dims_miopen); -- s_.fwd_algo = perf.fwd_algo; -- s_.workspace_bytes = perf.memory; - } else { - // set Y - s_.Y = context->Output(0, TensorShape(s_.y_dims)); -@@ -319,6 +289,31 @@ Status Conv::UpdateState(OpKernelContext* context, bool bias_expected) - s_.y_data = reinterpret_cast(s_.Y->MutableData()); - } - } -+ -+ miopenConvAlgoPerf_t perf; -+ int algo_count = 1; -+ const ROCMExecutionProvider* rocm_ep = static_cast(this->Info().GetExecutionProvider()); -+ static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT; -+ size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId()) -+ : AlgoSearchWorkspaceSize; -+ IAllocatorUniquePtr algo_search_workspace = GetTransientScratchBuffer(max_ws_size); -+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm( -+ GetMiopenHandle(context), -+ s_.x_tensor, -+ s_.x_data, -+ s_.w_desc, -+ s_.w_data, -+ s_.conv_desc, -+ s_.y_tensor, -+ s_.y_data, -+ 1, // requestedAlgoCount -+ &algo_count, // returnedAlgoCount -+ &perf, -+ algo_search_workspace.get(), -+ max_ws_size, -+ false)); // Do not do exhaustive algo search. -+ s_.fwd_algo = perf.fwd_algo; -+ s_.workspace_bytes = perf.memory; - return Status::OK(); - } - -diff --git a/onnxruntime/core/providers/rocm/nn/conv.h b/onnxruntime/core/providers/rocm/nn/conv.h -index bc9846203e..d54218f258 100644 ---- a/onnxruntime/core/providers/rocm/nn/conv.h -+++ b/onnxruntime/core/providers/rocm/nn/conv.h -@@ -108,9 +108,6 @@ class lru_unordered_map { - list_type lru_list_; - }; - --// cached miopen descriptors --constexpr size_t MAX_CACHED_ALGO_PERF_RESULTS = 10000; -- - template - struct MiopenConvState { - // if x/w dims changed, update algo and miopenTensors -@@ -148,9 +145,6 @@ struct MiopenConvState { - decltype(AlgoPerfType().memory) memory; - }; - -- lru_unordered_map cached_benchmark_fwd_results{MAX_CACHED_ALGO_PERF_RESULTS}; -- lru_unordered_map cached_benchmark_bwd_results{MAX_CACHED_ALGO_PERF_RESULTS}; -- - // Some properties needed to support asymmetric padded Conv nodes - bool post_slicing_required; - TensorShapeVector slice_starts; -diff --git a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc -index 7447113fdf..a662e35b2e 100644 ---- a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc -+++ b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc -@@ -76,7 +76,6 @@ Status ConvTranspose::DoConvTranspose(OpKernelContext* context, bool dy - - if (w_dims_changed) { - s_.last_w_dims = gsl::make_span(w_dims); -- s_.cached_benchmark_bwd_results.clear(); - } - - ConvTransposeAttributes::Prepare p; -@@ -126,35 +125,29 @@ Status ConvTranspose::DoConvTranspose(OpKernelContext* context, bool dy - } - - y_data = reinterpret_cast(p.Y->MutableData()); -- -- if (!s_.cached_benchmark_bwd_results.contains(x_dims)) { -- IAllocatorUniquePtr algo_search_workspace = GetScratchBuffer(AlgoSearchWorkspaceSize, context->GetComputeStream()); -- -- miopenConvAlgoPerf_t perf; -- int algo_count = 1; -- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm( -- GetMiopenHandle(context), -- s_.x_tensor, -- x_data, -- s_.w_desc, -- w_data, -- s_.conv_desc, -- s_.y_tensor, -- y_data, -- 1, -- &algo_count, -- &perf, -- algo_search_workspace.get(), -- AlgoSearchWorkspaceSize, -- false)); -- s_.cached_benchmark_bwd_results.insert(x_dims, {perf.bwd_data_algo, perf.memory}); -- } -- -- const auto& perf = s_.cached_benchmark_bwd_results.at(x_dims); -- s_.bwd_data_algo = perf.bwd_data_algo; -- s_.workspace_bytes = perf.memory; - } - -+ IAllocatorUniquePtr algo_search_workspace = GetScratchBuffer(AlgoSearchWorkspaceSize, context->GetComputeStream()); -+ miopenConvAlgoPerf_t perf; -+ int algo_count = 1; -+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm( -+ GetMiopenHandle(context), -+ s_.x_tensor, -+ x_data, -+ s_.w_desc, -+ w_data, -+ s_.conv_desc, -+ s_.y_tensor, -+ y_data, -+ 1, -+ &algo_count, -+ &perf, -+ algo_search_workspace.get(), -+ AlgoSearchWorkspaceSize, -+ false)); -+ s_.bwd_data_algo = perf.bwd_data_algo; -+ s_.workspace_bytes = perf.memory; -+ - // The following block will be executed in case there has been no change in the shapes of the - // input and the filter compared to the previous run - if (!y_data) { diff --git a/machine-learning/patches/0002-install-system-deps.patch b/machine-learning/patches/0002-install-system-deps.patch deleted file mode 100644 index 6e76b1e243..0000000000 --- a/machine-learning/patches/0002-install-system-deps.patch +++ /dev/null @@ -1,33 +0,0 @@ -diff --git a/dockerfiles/scripts/install_common_deps.sh b/dockerfiles/scripts/install_common_deps.sh -index bbb672a99e..0dc652fbda 100644 ---- a/dockerfiles/scripts/install_common_deps.sh -+++ b/dockerfiles/scripts/install_common_deps.sh -@@ -8,16 +8,23 @@ apt-get update && apt-get install -y --no-install-recommends \ - curl \ - libcurl4-openssl-dev \ - libssl-dev \ -- python3-dev -+ python3-dev \ -+ ccache - - # Dependencies: conda --wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh -O ~/miniconda.sh --no-check-certificate && /bin/bash ~/miniconda.sh -b -p /opt/miniconda -+wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py312_25.9.1-1-Linux-x86_64.sh -O ~/miniconda.sh && /bin/bash ~/miniconda.sh -b -p /opt/miniconda - rm ~/miniconda.sh - /opt/miniconda/bin/conda clean -ya - --pip install numpy --pip install packaging --pip install "wheel>=0.35.1" -+# Dependencies: venv and packages -+/opt/miniconda/bin/python3 -m venv /opt/rocm-venv -+/opt/rocm-venv/bin/pip install --no-cache-dir --upgrade pip -+/opt/rocm-venv/bin/pip install --no-cache-dir \ -+ "numpy==2.3.4" \ -+ "packaging==25.0" \ -+ "wheel==0.45.1" \ -+ "setuptools==80.9.0" -+ - rm -rf /opt/miniconda/pkgs - - # Dependencies: cmake diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml deleted file mode 100644 index ed34c6a338..0000000000 --- a/machine-learning/pyproject.toml +++ /dev/null @@ -1,96 +0,0 @@ -[project] -name = "immich-ml" -version = "2.5.5" -description = "" -authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] -requires-python = ">=3.11,<4.0" -readme = "README.md" -dependencies = [ - "aiocache>=0.12.1,<1.0", - "fastapi>=0.95.2,<1.0", - "ftfy>=6.1.1", - "gunicorn>=21.1.0", - "huggingface-hub>=0.20.1,<1.0", - "insightface>=0.7.3,<1.0", - "numpy>=2.3.4", - "opencv-python-headless>=4.7.0.72,<5.0", - "orjson>=3.9.5", - "pillow>=9.5.0,<11.0", - "pydantic>=2.0.0,<3", - "pydantic-settings>=2.5.2,<3", - "python-multipart>=0.0.6,<1.0", - "rich>=13.4.2", - "tokenizers>=0.15.0,<1.0", - "uvicorn[standard]>=0.22.0,<1.0", - "rapidocr>=3.1.0", -] - -[dependency-groups] -test = [ - "httpx>=0.24.1", - "pytest>=7.3.1", - "pytest-asyncio>=0.21.0", - "pytest-cov>=4.1.0", - "pytest-mock>=3.11.1", -] -types = [ - "types-pyyaml>=6.0.12.20241230", - "types-requests>=2.32.0.20250306", - "types-setuptools>=75.8.2.20250305", - "types-simplejson>=3.20.0.20250218", - "types-ujson>=5.10.0.20240515", -] -lint = [ - "mypy>=1.3.0", - "ruff>=0.0.272", - { include-group = "types" }, -] -dev = ["locust>=2.15.1", { include-group = "test" }, { include-group = "lint" }] - -[project.optional-dependencies] -cpu = ["onnxruntime>=1.23.2,<2"] -cuda = ["onnxruntime-gpu>=1.23.2,<2"] -openvino = ["onnxruntime-openvino>=1.23.0,<2"] -armnn = ["onnxruntime>=1.23.2,<2"] -rknn = ["onnxruntime>=1.23.2,<2", "rknn-toolkit-lite2>=2.3.0,<3"] -rocm = [] - -[tool.uv] -compile-bytecode = true - -[tool.hatch.build.targets.sdist] -include = ["immich_ml"] - -[tool.hatch.build.targets.wheel] -include = ["immich_ml"] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.mypy] -python_version = "3.11" -plugins = "pydantic.mypy" -follow_imports = "silent" -warn_redundant_casts = true -disallow_any_generics = true -check_untyped_defs = true -disallow_untyped_defs = true -ignore_missing_imports = true - -[tool.pydantic-mypy] -init_forbid_extra = true -init_typed = true -warn_required_dynamic_aliases = true -warn_untyped_fields = true - -[tool.ruff] -line-length = 120 -target-version = "py311" - -[tool.ruff.lint] -select = ["E", "F", "I"] -per-file-ignores = { "test_main.py" = ["F403"] } - -[tool.pytest.ini_options] -markers = ["providers", "ov_device_ids"] diff --git a/machine-learning/responses.json b/machine-learning/responses.json deleted file mode 100644 index 8fe9cd61cc..0000000000 --- a/machine-learning/responses.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "clip": { - "image": [ - -0.013126737, -0.022995953, -0.0493738, -0.0063057775, 0.013601424, - -0.003761688, -0.03379882, 0.11106285, 0.024760082, 0.023903701, - 0.04418207, -0.013594999, 0.030850016, 0.0012876489, -0.012471005, - 0.009750715, 0.0095717255, 0.013320666, 0.0027921356, 0.03240264, - 0.033538498, 0.013624318, -0.0069946186, -0.0036184592, -0.009846507, - -0.017311024, -0.036686428, -0.0041808123, 0.030871637, -0.028624479, - -0.016515259, 0.014418001, -0.024542322, -0.0025438748, -0.049111884, - -0.023928944, 0.012270045, -0.016418075, 0.004895335, -0.15801854, - -0.026325515, 0.03166467, -0.017224329, 0.0411128, -0.022944424, - 0.015693054, -0.020919899, -0.010764121, -0.008499815, -0.020263294, - -0.009743323, -0.035395622, 0.03474742, 0.003049183, 0.009424564, - 0.010707678, 0.01664117, -0.0059027374, -0.013055344, 0.0018035833, - -0.003976456, -0.04325922, 0.014407317, 0.035728276, 0.002226939, - -0.006235411, -0.0073032235, -0.035129357, 0.001095443, -0.028552389, - -0.044300288, -0.012959393, 0.02259977, 0.017141517, -0.029432472, - -0.017583484, 0.010974336, 0.018141218, 0.0015389329, -0.008220305, - -0.0060965014, 0.004929384, 0.019477025, -0.033071984, 0.025183259, - 0.013607688, 0.01836233, 0.04586782, 0.0103442725, -0.036077496, - -0.029715508, 0.007203621, -0.7949153, 0.046866275, 0.026910711, - -0.0047834567, 0.033243995, 0.009379981, -0.03749048, -0.055274535, - -0.01955359, 0.012887587, 0.00922838, -0.0032776103, -0.011456734, - 0.0045412215, -0.11506394, 0.0348558, 0.029478835, -0.011811103, - -0.00483158, -0.010586126, -0.018853206, -0.01591496, -0.019360982, - -0.03211199, -0.013473663, -0.019630248, -0.017012835, 0.059128772, - -0.03396129, 0.0045991736, -0.015158291, 0.008241974, 0.004403056, - -0.007536049, -0.023821214, -0.0059521003, 0.015564905, 0.020600233, - 0.008175, 0.02100119, -0.0034459834, 0.1058016, 0.008383205, 0.03100292, - -0.023814196, -0.016157096, -0.008210107, -0.004146204, 0.016350364, - -0.056028433, 0.013261071, 0.034839876, -0.03236049, 0.026573967, - -0.018140865, 0.018515658, 0.013251766, 0.007693613, 0.0067239976, - -0.0013857568, 0.038114607, 0.0068016117, 0.036603037, 0.0040935865, - 0.010394745, -0.00082285365, -0.009811308, 0.020343611, -0.012164189, - -0.012208623, 0.0005465415, -0.015394064, 0.02499845, 0.021941017, - -0.016571017, -0.011810332, 0.017864, -0.010639794, -0.008609091, - -0.0007239709, 0.015229945, -0.0035874692, 0.018922612, -0.011209458, - -0.013052865, -0.009626533, -0.004419959, 0.007915186, 0.01094836, - 0.005509159, -0.0034862005, 0.01012292, -0.0059307595, -0.029599648, - 0.032845, -0.007011692, -0.014218981, 0.00790071, 0.017027052, - -0.022314077, -0.03041719, 0.015665755, 0.036747217, -0.018942915, - 0.008623111, 0.02179961, -0.022312569, 0.007024427, 0.016751591, - -0.0034192575, 0.024101255, -0.0046198783, 0.022274924, -0.015562676, - -0.0092551885, -0.0063787713, 0.045996074, 0.026235346, 0.009622556, - 0.05728027, 0.03168525, -0.017600676, 0.029278612, 0.01467962, - 0.032169178, 0.022459356, -0.012175933, -0.009438608, 0.027234165, - 0.013514767, -0.008831029, 0.010888894, 0.004518216, 0.009855367, - 0.012112431, -0.0073178695, 0.0072642234, 4.877679e-5, -0.01221576, - 0.023542404, -0.009026452, -0.055442516, 0.006579068, 0.033202186, - -0.007669379, 0.0010604112, -0.04271919, -0.029112164, 0.021844024, - 0.029739635, -0.026083348, 0.008940292, -0.039301652, -0.047215454, - 0.0018794702, -0.008740231, 0.029195482, 0.0037629968, -0.024923965, - -0.021407248, 0.009952853, -0.0055059143, 0.0044912454, 0.016966008, - -0.00081178773, -0.022250004, -0.014063889, -0.006170697, -0.0008208651, - -0.036218595, -0.0029040456, 0.03943083, -0.021814227, 0.017567957, - 0.035849728, -0.049075987, 0.0040634805, 0.009878297, 0.028557112, - 0.02336673, 0.010714448, 0.020129073, -0.030503238, 0.009350441, - 0.039086528, -0.0037483997, -0.0034365985, 0.019824414, 0.014027232, - 0.030565958, 0.0036307913, 0.0030920429, -0.009908996, 0.0027933475, - -7.140754e-5, -0.027733125, 0.0022445584, -0.032248124, 0.050226185, - 0.030529078, -0.040353864, 0.031086015, -0.0063569676, 0.031343475, - -0.020244656, -0.011442288, 0.018035123, -0.005479394, 0.01783419, - -0.036066547, -0.0106600635, 0.044636995, -0.030209303, -0.07192714, - 0.0128155155, 0.003505818, -0.0005725083, -0.01584388, -0.025725754, - 0.025868604, 0.10576061, -0.012738124, 0.0012224225, 0.0472961, - 0.021650923, 0.0061313445, 0.014010678, 0.016864019, 0.004049639, - 0.10989465, 0.011927816, 0.013589654, 0.011258818, 0.022496557, - -0.018828733, 0.021635532, 0.0116777215, 0.11320542, -0.0011280471, - 0.018990291, 0.001824643, -0.03793715, 0.0206918, -0.0050228164, - -0.013865701, 0.022277884, 0.019400347, 0.028610364, -0.023974052, - 0.0030309444, 0.027177742, 0.024541136, 0.023737634, 0.0012539584, - 0.0187086, -0.015451178, 0.015066189, -0.019812824, 0.050507285, - -0.0021846944, 0.041644007, -0.0070109894, -0.014599777, -0.05985813, - -0.036328156, -0.02293525, -0.0065515027, 0.016792618, -0.0059018973, - -0.008319917, 0.008072106, 0.0073447954, -0.052924518, -0.037344936, - 0.015524772, -0.0012835241, 0.014405327, 0.0057144985, 0.004945561, - -0.024654018, 0.011967616, 0.01832056, 0.019411784, 0.019788045, - -0.0006405928, -0.0015148119, -0.05064218, -0.031875107, -0.03803604, - -0.0096240705, 0.012371131, -0.019090319, 0.0075365147, -0.024229601, - 0.014469528, -0.004786435, 0.0011314518, 0.009256282, -0.04957284, - -0.0068631344, -0.010091242, -0.023295002, 0.03268865, 0.022269772, - 0.037733294, -0.015292435, -0.06330943, -0.00854154, 0.0027765913, - 0.0015374947, 0.0377278, 0.008772586, -0.01810512, -0.0025668603, - 0.014428339, 0.0027927365, 0.07493676, -0.022829408, -0.028912589, - 0.008928177, 0.011323267, 0.008405796, 0.016925976, 0.001739356, - -0.021090876, -0.0062678503, 0.010898773, -0.010470923, 0.015523946, - -0.027888289, 0.023872118, -0.048326068, 0.025968319, 0.0047795917, - -0.016123952, 0.00698612, -0.05154045, -0.003691712, -0.0101406425, - -0.0241034, 0.004006022, 0.0021649078, 0.0019942294, -0.009274028, - -0.006467623, -0.0010948133, -0.012350769, -0.0060371486, -0.0006392645, - 0.031422533, 0.015165475, -0.012650007, -0.005918423, 0.005781174, - -0.023262534, -0.0043034274, -0.010881872, -0.015937665, -0.0043740096, - -0.02981798, -0.0037422387, -0.029688178, 0.022320364, -0.0014900378, - -0.026122924, -0.04360404, 0.016354023, -0.02447563, 0.0205314, - 0.0042775236, -0.020184014, -0.0017819501, 0.036122557, 0.0036566693, - 0.07459051, -0.0035548757, 0.004874807, -0.028627345, -0.023153499, - 0.03710664, -0.000639956, -0.030509725, -0.005146651, -0.010251552, - 0.028408762, -0.008056198, -0.018420909, 0.02850364, -0.0075958185, - -0.008918139, 0.002778187, 0.06220242, -0.016280292, -0.026200369, - 0.05900717, -0.013802131, 0.005442568, -0.033114687, 0.010976371, - 0.008192846, 0.0031891295, 0.024811232, 0.009066575, -0.026441244, - 0.030676885, -0.014591597, -0.024314625, -0.037472498, -0.015021544, - -0.016501956, -0.0069196, 0.013831272, 0.056646723, 0.007946148, - -0.002477574, -0.030496774, -0.011770325, 0.06742689, -0.03180974, - -0.025615396 - ], - "text": [ - -0.0040579583, -0.00084722764, -0.008696951, -0.006850008, -0.010870523, - 0.014495447, -0.010678498, -0.09618138, 0.016697474, -0.014809047, - -0.0035991871, 0.020752821, 0.0020757387, 0.0018064519, 0.02969283, - -0.0040159826, 0.02335311, 0.015918557, -0.0015919582, 0.013545261, - 0.011341818, -0.006991808, 0.0020565446, -0.016662853, -0.0064206184, - 0.011536576, -0.01144098, 0.015054818, -0.013258694, 0.0046747606, - -0.00681864, -0.012852865, 0.012708946, 0.006093663, 0.0029938417, - 0.015458671, 0.0040865405, -0.0004354532, 0.0037405093, -0.015085074, - 0.0007998808, -0.021485215, -0.0066235093, 0.015721628, -0.002462181, - -0.0049815965, -0.011028703, 0.0041498104, -0.00070322485, 0.0031991813, - -0.0075132507, -0.008273014, 0.0125206, 0.019671565, 0.02124969, - 0.0076838327, 0.0015366874, 0.0004413452, -0.0027475145, 0.031049952, - 0.01782742, -0.01759819, 0.0040917504, -0.011803108, 0.0051114787, - -0.0075210207, -0.0062834355, 0.010283767, 0.02023746, -0.020258851, - 0.004795256, -0.011313993, -0.014636256, 0.004900588, -0.0026439666, - -0.0004062344, -0.040196564, -0.014539185, 0.014600707, 0.004162044, - -0.0155277, -0.016300475, 0.0039491425, 0.022403471, 0.0055926195, - 0.03543051, 0.01047029, -0.03262415, 0.02942466, -0.011440199, - -0.012713757, 0.0062652803, -0.15837249, 0.028388312, 7.452041e-5, - 0.007136255, -0.010753105, 0.016393846, 0.004432782, -0.010688704, - 0.015814407, 0.01654759, 0.0008900756, -0.007162077, -6.0264443e-5, - 6.894444e-5, -0.034889467, -0.026710762, 0.005202752, -0.012675916, - 0.010903986, 0.017916093, 0.005404525, -0.003624909, -0.012261727, - -0.021104869, -0.01593513, -0.009227664, -0.022844192, 0.008606035, - 0.0013098373, 0.00637147, 0.0027185818, -0.0032128745, 0.018255865, - -0.002964337, -0.006976183, -0.0063263937, 0.0075582284, 0.014295236, - 0.00485664, -0.029005948, -0.014672015, 0.61821824, -0.010903263, - 0.022125997, -0.018154604, -0.007414954, -0.012344926, 0.0029751004, - -0.010787629, -0.0056861844, -0.025484746, -0.0004887071, -0.036681578, - 0.010114145, -0.009012449, -0.00048479583, -0.011162415, -0.0057421126, - -0.0019520421, -0.0013580753, 0.0037870558, 0.0012404326, -0.00089634134, - -0.022112457, 0.0034537334, 0.014985147, -0.010455136, 0.018100852, - -0.010999219, -0.027524924, -0.009551776, -0.0047603208, -0.001092369, - 0.008849578, 0.021949856, -0.034437556, -0.0051499153, -0.0006772509, - -0.011200381, -0.009206776, 0.021897016, -0.0013931778, 0.0041396013, - -0.025534542, 0.0074160174, 0.00039215147, 0.025992293, 0.0069832364, - -0.0175616, 0.01272807, -0.020147255, 0.02455081, -0.01236127, - -0.011840565, 0.011820177, 0.018173985, 0.017230362, -0.016969377, - 0.0010091222, -0.04185319, -0.030467693, -0.012564729, 0.030740628, - -0.004086395, 0.0013223978, -0.0013041743, -0.01975558, -0.014959637, - 0.033446018, -0.014724724, -0.028613493, 0.010436393, -0.009841343, - -0.013723956, -0.0010025625, 0.016992576, 0.0056477, -0.026704773, - 0.018927934, 0.013758461, 0.016924908, -0.026889605, -0.01496036, - 0.02078507, -0.0149594685, -0.021289647, 0.027369255, -0.00205557, - 0.0129268635, -0.014446633, 0.0039108247, 0.014774828, 0.004396043, - 0.0038431762, 0.012223014, 0.0061016707, -0.006525442, -0.018426975, - 0.03081795, 0.024269402, -0.020132616, -0.008118887, 0.025062446, - -0.0033954307, -0.019662865, -0.0032548332, -0.008575233, -0.003158561, - 0.0012930515, 0.02213235, 0.017646195, -0.016638828, -0.0154889, - 0.031743307, 0.001081875, -0.0019133464, 0.034760594, -0.008569126, - -0.019119555, 0.020908207, -0.0047135833, 0.00984879, -0.016712308, - 0.028532412, 0.0038664932, -0.0071539935, 0.0013488994, 0.0060503725, - 0.0021401793, -0.032594826, 0.010918716, 0.0075080344, 0.00020341178, - -0.030393362, 0.014375046, 0.018798219, -0.0040685013, 0.020957684, - -0.012454064, 0.014856742, 0.0017268835, -0.008762698, 0.007062434, - 0.024501909, 0.0011791736, -0.023002177, -0.012701125, -0.0053904364, - 0.015551624, 0.018748082, 0.00704452, 0.0047835982, 0.0013530678, - 0.0033350172, 0.0056562345, 0.009079597, 0.0059383595, 0.011405316, - -0.004795079, 0.007274586, -0.0011659514, -0.0001364172, -0.0050517535, - -0.010681983, -0.023743946, -0.020241234, 0.0009631201, 0.014073974, - -0.00665422, 0.011845411, -0.0001289105, -0.024006248, -0.0009306585, - 0.0139923245, 0.020409467, -0.017118154, -0.0151973255, -0.016737074, - -0.002157259, -0.0060298163, 0.61768156, 0.01275426, -0.023746304, - -0.010786622, -0.00050265377, -0.010761652, -0.013012264, 0.013700237, - 0.019240098, 0.049388826, -0.007853694, -0.014966961, -0.026477624, - 0.02809707, 0.009291939, -0.028884491, 0.015102742, -0.27225503, - 0.01782282, -0.016989257, -0.0051341387, 0.0037056766, -0.004537146, - -0.026184445, 0.020256622, 0.0073146136, -0.0070027146, -0.009025792, - -0.015298917, -0.0052380697, -0.0005596046, -0.0041900063, 0.015934054, - 0.008158574, -0.0038807616, -0.0048019756, 0.0061978237, 0.022159556, - -0.02619826, 0.0013973896, -0.00012341494, 0.030957809, -0.009596324, - 0.008263321, 0.0017040323, -0.0010236687, 0.017982712, -0.012567677, - 0.007361281, 0.0028631007, 0.032613713, 0.035072606, -0.045417674, - 0.016303446, -0.009096281, 0.012163677, -0.008316459, 0.006423764, - -0.008586175, -0.0009862242, 0.009973197, 0.020825483, -0.005682246, - 0.0066081304, 0.0061441967, -0.00670868, -0.024878936, -0.024288971, - -0.009822955, -0.011659227, -0.0067634145, -0.0011930552, 0.017096667, - 0.01974797, -0.020388834, -0.008245143, -0.0071634515, 0.0012492571, - -0.010288493, -0.0025248309, -0.0039965925, 0.037344053, -0.019459987, - 0.022098366, -0.021084892, -0.014823354, -0.010007409, -0.005560381, - -0.012292843, 0.0132691385, 0.0066421456, 0.0045196814, 0.0044144704, - 0.0062646614, 0.0050272197, 0.020296281, 0.011412983, -0.0040745772, - 0.00542041, 0.0021500897, 0.005183101, 0.00985178, 0.014477596, - -0.0131016085, -0.0064126155, -0.004809687, -0.016441243, 0.010445765, - 0.0013761928, -0.0135576585, 0.0003352349, -0.010797083, -0.0058007324, - 0.021649584, 0.012650062, -0.009740497, -0.025809184, -0.026720846, - 0.029149767, 0.014593344, -0.0134959705, -0.004710099, -0.0062580137, - -0.0047687683, -0.029818097, -0.004622532, 0.02532894, 0.0051457905, - -0.0046252706, -0.02905562, -0.019097809, -0.035888474, -0.006897086, - -0.0035953831, -0.0013759647, 0.0027531807, -0.002395984, -0.040570017, - -0.02462688, 0.009387292, 0.0025142033, 0.02404064, -0.0014443685, - 1.0727288e-5, -0.024033979, 0.011659959, -0.016820917, 0.018782362, - -0.019061793, 0.0043488434, 0.00040266776, -0.0022744886, 0.0024185092, - -0.0041366024, 0.0028075825, -0.0085624885, -0.012087987, 0.013551666, - 0.0019014167, 0.007896904, 0.031102024, 0.0091334, -0.0030707342, - 0.0066130627, -0.002711352, -0.012097188, 0.017067473, 0.021030908, - 0.0014250687, -0.092848144, -0.0034704215, 0.013624546, 0.013779425, - -0.0025326884, -0.0018633928, 0.00014903376, 0.01547092, 0.008385425, - -0.0033495796, -0.015248458, -0.0356735, 0.005223496, -0.018293105, - -0.043073945, 0.016345823, -0.0050947615, 0.023554962, 0.034400985, - 0.0045644785, -0.00011241743, 0.0060564913, 0.0021182992, 0.01914424, - 0.019295372, -0.00551076, -0.00017086207, 0.0032044165, 0.010140755, - -0.022354674, 0.026089797 - ] - }, - "facial-recognition": [ - { - "boundingBox": { - "x1": 690.0, - "y1": -89.0, - "x2": 833.0, - "y2": 96.0 - }, - "score": 0.03575617074966431, - "embedding": [ - -0.43665668, -0.59305364, -0.12699714, 0.3985032, 0.1878969, - -0.25987914, 0.14818184, -0.542229, -0.06710237, -0.1319032, - 0.056408346, 0.046093762, -0.14984925, 0.043225512, 0.023826078, - -0.09063442, 0.07891726, -0.29357076, -0.6277133, -0.29042292, - 0.18038993, 0.21837695, 0.17909442, -0.040304773, -0.035560638, - -0.07568607, 0.1277122, -0.13466191, -0.2368693, 0.3642968, 0.29558533, - 0.20867407, 0.11252518, 0.47691494, -0.054775044, 0.030100197, - -0.049531147, 0.04045874, 0.23517768, 0.17130391, 0.17269331, - 0.08591308, 0.046999797, -0.17151847, -0.2443775, 0.3110528, - -0.23971468, -0.31744513, -0.026422635, -0.26203394, -0.18553479, - -0.31044272, 0.6385251, 0.27497086, 0.006674953, 0.053785797, - -0.20257844, -0.48399794, 0.21708605, -0.4781224, -0.12367296, - -0.099010885, 0.18633766, 0.31143454, -0.12165704, 0.13010044, - 0.12534627, 0.107288495, 0.37471777, -0.123026475, -0.1263274, - -0.15621608, 0.26027548, 0.15841314, 0.5164254, -0.31015784, 0.24754328, - 0.10240883, -0.1181829, -0.14073256, 0.027111322, 0.09927598, - -0.10066943, 0.4808423, -0.042361684, -0.08512197, 0.13695274, - 0.30378994, 0.11138052, -0.318214, -0.5708592, -0.14786953, 0.49985552, - -0.23231967, 0.13856675, -0.5383139, -0.059954256, 0.2796868, - -0.32447946, 0.16510965, 0.57146084, -0.15120608, 0.20110571, - -0.49805385, -0.2008879, -0.046678245, 0.24653266, 0.022508677, - -0.14091778, 0.38075653, 0.33811444, 0.05011098, -0.2371835, - -0.20052075, -0.14081016, -0.3422103, 0.11998144, 0.24423985, - 0.13769919, -0.25340003, -0.41080874, -0.28673622, -0.20673269, - 0.4604351, 0.4178845, 0.105202496, -0.1446912, 0.0807363, -0.37372503, - 0.13030809, -0.08456054, 0.21937889, -0.22700784, 0.3039499, - 0.009784861, -0.07245704, 0.50291365, -0.24968931, 0.3178813, - 0.12665558, -0.036484346, 0.21702805, -0.09277919, 0.17766781, - -0.12018812, 0.008044228, -0.26986086, 0.29888278, -0.28485933, - 0.30066437, -0.14316985, 0.53800535, 0.030840248, 0.023039162, - 0.73862207, 0.0034680888, 0.23797399, -0.11183337, 0.067846656, - -0.23546576, 0.39354736, 0.0053778216, 0.13494004, 0.1370637, - -0.029445097, 0.14705376, -0.48120612, 0.27262342, -0.05196667, - -0.3097266, 0.08714986, 0.10841283, -0.11757159, -0.5010461, - -0.32369986, -0.21964747, -0.19810468, 0.14780998, -0.04624281, - 0.24638015, -0.06710279, -0.31719172, 0.26955876, 0.37117082, - -0.3964724, 0.21541706, -0.12243534, -0.5392555, 0.04640211, 0.3657012, - -0.042127043, -0.030638859, 0.21909437, 0.16005577, -0.03320134, - -0.0949998, 0.33176076, 0.22538322, -0.016216129, -0.42417043, - 0.52940613, -0.011592716, -0.21875188, -0.06394625, 0.24449442, - -0.05658462, -0.09727913, -0.3978734, -0.11175068, 0.085142605, - -0.057618782, -0.0498557, 0.17287247, 0.41813853, -0.30433404, - 0.3087585, -0.6604493, -0.13869359, 0.072916515, -0.043251924, - 0.37401634, 0.17014223, -0.26469553, -0.34653437, 0.13010754, - 0.21517499, 0.74030113, 0.3460628, -0.5115478, 0.4696753, -0.009848075, - -0.1330159, -0.0061842054, 0.013667986, 0.16993025, -0.3161455, - 0.29015008, 0.65197945, 0.13776428, 0.5275149, 0.1472181, -0.114682674, - -0.05685012, 0.21696919, -0.34107065, 0.09352806, -0.03968816, - -0.13109599, 0.07406853, 0.15091223, 0.18835881, 0.19146737, -0.3898828, - 0.469747, -0.11145213, 0.039727956, 0.8268787, -0.09761663, - -0.043320894, 0.27001414, 0.12079324, 0.05877747, 0.028245524, - 0.20692128, 0.68440485, -0.34984088, -0.119763374, -0.39637753, - 0.23799005, 0.057573274, 0.07855352, 0.37982583, -0.0365879, - 0.068318695, 0.10845077, -0.18650186, 0.08927679, -0.27789003, - 0.31810492, 0.4251458, -0.03525705, -0.28072172, 0.07316002, 0.13499324, - -0.11333761, -0.0008841604, 0.10874095, 0.29681873, 0.008288942, - 0.24116173, 0.011309357, -0.3009541, -0.4752865, 0.19921738, - -0.16108191, 0.017838746, 0.51260126, -0.086799264, 0.34165853, - 0.32359147, 0.25770876, 0.21442738, -0.15971375, -0.26682994, - 0.22788364, -0.38956794, 0.084580205, -0.15929273, 0.24211408, - -0.24793725, -0.31528267, 0.15945697, -0.16866091, 0.19472758, 0.408394, - 0.24238603, -0.23643477, 0.29852632, 0.12915722, 0.327068, 0.501809, - -0.40538347, -0.023235738, -0.11315605, 0.007632144, -0.22626217, - 0.28817925, -0.5816528, 0.1551521, -0.016097836, -0.01634605, - 0.095855944, -0.010664792, 0.1402924, -0.22450349, -0.13961065, - -0.40732136, -0.24776831, 0.12040292, -0.06779129, 0.44510496, - 0.33206633, 0.19807269, -0.06460787, -0.2524265, -0.12726343, - 0.44656014, -0.09844789, -0.18762295, 0.16189753, 0.23589599, - -0.44798508, 0.2135099, -0.33205217, 0.28407755, -0.0951985, - 0.035582896, -0.51807857, -0.27382392, 0.03172898, 0.22928514, - 0.47157723, -0.48383215, 0.014225766, -0.08102345, 0.19384615, - -0.060681015, -0.037799604, -0.2875836, 0.024652202, 0.052712113, - -0.22610298, 0.46830428, 0.29616976, 0.14641494, -0.24234764, - 0.30126396, -0.011165038, 0.38622355, -0.12484505, 0.33650652, - 0.17399745, -0.2703057, 0.36919123, 0.26170117, -0.1537327, -0.43157104, - 0.35697, 0.043892622, -0.065475196, 0.5542902, 0.019970104, 0.43128124, - -0.014292087, -0.33983213, 0.3250854, 0.21585244, -0.34458104, - 0.23752448, 0.18115376, -0.2586738, -0.16033548, -0.16151018, - -0.23306333, 0.14865296, -0.31790328, 0.27215546, -0.059920013, - 0.16193654, 0.075943366, -0.16281635, 0.4489306, -0.43052202, - -0.038787995, -0.11722573, 0.07254093, -0.2997051, 0.16540596, - -0.15089649, 0.12507877, 0.43725327, 0.13540109, 0.13391787, - 0.013777234, 0.26951605, -0.2999856, -0.08645636, 0.12768297, - 0.23375636, -0.07325045, -0.04433371, 0.04709586, 0.09582621, - 0.23509142, 0.18061984, 0.35379466, 0.12938409, 0.33010754, 0.18966632, - 0.07585195, 0.0059688687, -0.13233723, 0.17105722, -0.020040989, - -0.2805646, -0.091034755, 0.1950869, -0.21115655, -0.16249251, - 0.07147664, -0.20138165, 0.15193966, 0.041464765, 0.01074836, - 0.029091328, -0.22078216, 0.06446775, -0.27403125, -0.51904315, - -0.20539844, 0.176225, -0.28688902, 0.030568387, 0.2964594, - -0.088931546, -0.4425866, 0.09070322, 0.08005672, 0.009866249, - -0.07386999, 0.06683251, -0.34370828, 0.23668535, -0.0847823, - -0.27400133, -0.31668398, -0.116622224, 0.20027944, 0.33772525, - -0.3041445, -0.61801887, 0.043022886, -0.24733649, -0.20657904, - -0.37058303, 0.00644885, 0.2548513, 0.029221226, -0.41749227, - 0.065117866, -0.3745206, 0.22699282, 0.22139677, 0.28097618, 0.10008535, - -0.039953396, -0.33505437, 0.28511694, 0.18131426, -0.879614, - -0.041319087, -0.62370497, 0.05170501, 0.23541749, -0.0033701807, - 0.15842043, 0.020002551, -0.22027364, -0.2730838, -0.23035137, - -0.077056274, 0.002099529 - ] - } - ], - "imageWidth": 600, - "imageHeight": 800 -} diff --git a/machine-learning/scripts/configure-apt.sh b/machine-learning/scripts/configure-apt.sh deleted file mode 100755 index 42958dbf6b..0000000000 --- a/machine-learning/scripts/configure-apt.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e - -sed -i -e's/ main/ main contrib non-free non-free-firmware/g' /etc/apt/sources.list.d/debian.sources -sed -i -e's/ bookworm-updates/ bookworm-updates sid/g' /etc/apt/sources.list.d/debian.sources - -# default priority is 500, so we set unstable to 450 to prefer stable packages -cat > /etc/apt/preferences.d/preferences << EOL -Package: * -Pin: release a=unstable -Pin-Priority: 450 -EOL diff --git a/machine-learning/scripts/healthcheck.py b/machine-learning/scripts/healthcheck.py deleted file mode 100644 index 38c0a522f1..0000000000 --- a/machine-learning/scripts/healthcheck.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -import sys -from ipaddress import ip_address - -import requests - -port = os.getenv("IMMICH_PORT", 3003) -host = os.getenv("IMMICH_HOST", "0.0.0.0") - - -def is_ipv6(host: str) -> bool: - try: - return ip_address(host).version == 6 - except ValueError: - return False - - -host = "localhost" if host == "0.0.0.0" else host -host = f"[{host}]" if is_ipv6(host) else host - -try: - response = requests.get(f"http://{host}:{port}/ping", timeout=2) - if response.status_code == 200: - sys.exit(0) - sys.exit(1) -except requests.RequestException: - sys.exit(1) diff --git a/machine-learning/test_main.py b/machine-learning/test_main.py deleted file mode 100644 index eb8706fc19..0000000000 --- a/machine-learning/test_main.py +++ /dev/null @@ -1,1172 +0,0 @@ -import json -import os -from io import BytesIO -from pathlib import Path -from random import randint -from types import SimpleNamespace -from typing import Any, Callable -from unittest import mock - -import cv2 -import numpy as np -import onnxruntime as ort -import orjson -import pytest -from fastapi import HTTPException -from fastapi.testclient import TestClient -from PIL import Image -from pytest import MonkeyPatch -from pytest_mock import MockerFixture - -from immich_ml.config import Settings, settings -from immich_ml.main import load, preload_models -from immich_ml.models.base import InferenceModel -from immich_ml.models.cache import ModelCache -from immich_ml.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder -from immich_ml.models.clip.visual import OpenClipVisualEncoder -from immich_ml.models.facial_recognition.detection import FaceDetector -from immich_ml.models.facial_recognition.recognition import FaceRecognizer -from immich_ml.schemas import ModelFormat, ModelPrecision, ModelTask, ModelType -from immich_ml.sessions.ann import AnnSession -from immich_ml.sessions.ort import OrtSession -from immich_ml.sessions.rknn import RknnSession, run_inference - - -class TestBase: - def test_sets_default_cache_dir(self) -> None: - encoder = OpenClipTextualEncoder("ViT-B-32__openai") - - assert encoder.cache_dir == Path(settings.cache_folder) / "clip" / "ViT-B-32__openai" - - def test_sets_cache_dir_kwarg(self) -> None: - cache_dir = Path("/test_cache") - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir) - - assert encoder.cache_dir == cache_dir - - def test_sets_default_model_format(self, mocker: MockerFixture) -> None: - mocker.patch.object(settings, "ann", True) - mocker.patch("immich_ml.sessions.ann.loader.is_available", False) - - encoder = OpenClipTextualEncoder("ViT-B-32__openai") - - assert encoder.model_format == ModelFormat.ONNX - - def test_sets_default_model_format_to_armnn_if_available(self, path: mock.Mock, mocker: MockerFixture) -> None: - mocker.patch.object(settings, "ann", True) - mocker.patch("immich_ml.sessions.ann.loader.is_available", True) - path.suffix = ".armnn" - - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) - - assert encoder.model_format == ModelFormat.ARMNN - - def test_sets_model_format_kwarg(self, mocker: MockerFixture) -> None: - mocker.patch.object(settings, "ann", False) - mocker.patch("immich_ml.sessions.ann.loader.is_available", False) - - encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.ARMNN) - - assert encoder.model_format == ModelFormat.ARMNN - - def test_sets_default_model_format_to_rknn_if_available(self, mocker: MockerFixture) -> None: - mocker.patch.object(settings, "rknn", True) - mocker.patch("immich_ml.sessions.rknn.is_available", True) - - encoder = OpenClipTextualEncoder("ViT-B-32__openai") - - assert encoder.model_format == ModelFormat.RKNN - - def test_casts_cache_dir_string_to_path(self) -> None: - cache_dir = "/test_cache" - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir) - - assert encoder.cache_dir == Path(cache_dir) - - def test_clear_cache(self, rmtree: mock.Mock, path: mock.Mock, info: mock.Mock) -> None: - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) - encoder.clear_cache() - - rmtree.assert_called_once_with(encoder.cache_dir) - info.assert_called_with(f"Cleared cache directory for model '{encoder.model_name}'.") - - def test_clear_cache_warns_if_path_does_not_exist( - self, rmtree: mock.Mock, path: mock.Mock, warning: mock.Mock - ) -> None: - path.return_value.exists.return_value = False - - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) - encoder.clear_cache() - - rmtree.assert_not_called() - warning.assert_called_once() - - def test_clear_cache_raises_exception_if_vulnerable_to_symlink_attack( - self, rmtree: mock.Mock, path: mock.Mock - ) -> None: - rmtree.avoids_symlink_attacks = False - - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) - with pytest.raises(RuntimeError): - encoder.clear_cache() - - rmtree.assert_not_called() - - def test_clear_cache_replaces_file_with_dir_if_path_is_file( - self, rmtree: mock.Mock, path: mock.Mock, warning: mock.Mock - ) -> None: - path.return_value.is_dir.return_value = False - - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) - encoder.clear_cache() - - rmtree.assert_not_called() - path.return_value.unlink.assert_called_once() - path.return_value.mkdir.assert_called_once() - warning.assert_called_once() - - def test_download(self, snapshot_download: mock.Mock) -> None: - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="/path/to/cache") - encoder.download() - - snapshot_download.assert_called_once_with( - "immich-app/ViT-B-32__openai", - cache_dir=encoder.cache_dir, - local_dir=encoder.cache_dir, - ignore_patterns=["*.armnn", "*.rknn"], - ) - - def test_download_downloads_armnn_if_preferred_format(self, snapshot_download: mock.Mock) -> None: - encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.ARMNN) - encoder.download() - - snapshot_download.assert_called_once_with( - "immich-app/ViT-B-32__openai", - cache_dir=encoder.cache_dir, - local_dir=encoder.cache_dir, - ignore_patterns=["*.rknn"], - ) - - def test_download_downloads_rknn_if_preferred_format(self, snapshot_download: mock.Mock) -> None: - encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.RKNN) - encoder.download() - - snapshot_download.assert_called_once_with( - "immich-app/ViT-B-32__openai", - cache_dir=encoder.cache_dir, - local_dir=encoder.cache_dir, - ignore_patterns=["*.armnn"], - ) - - def test_throws_exception_if_model_path_does_not_exist( - self, snapshot_download: mock.Mock, ort_session: mock.Mock, path: mock.Mock - ) -> None: - path.return_value.__truediv__.return_value.__truediv__.return_value.is_file.return_value = False - - encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path) - - with pytest.raises(FileNotFoundError): - encoder.load() - - snapshot_download.assert_called_once() - ort_session.assert_not_called() - - -@pytest.mark.usefixtures("ort_session") -class TestOrtSession: - CPU_EP = ["CPUExecutionProvider"] - CUDA_EP = ["CUDAExecutionProvider", "CPUExecutionProvider"] - OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"] - CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"] - TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"] - ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"] - COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"] - - @pytest.mark.providers(CPU_EP) - def test_sets_cpu_provider(self, providers: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.CPU_EP - - @pytest.mark.providers(CUDA_EP) - def test_sets_cuda_provider_if_available(self, providers: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.CUDA_EP - - @pytest.mark.ov_device_ids(["GPU.0", "CPU"]) - @pytest.mark.providers(OV_EP) - def test_sets_openvino_provider_if_available(self, providers: list[str], ov_device_ids: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.OV_EP - - @pytest.mark.ov_device_ids(["CPU"]) - @pytest.mark.providers(OV_EP) - def test_avoids_openvino_if_gpu_not_available(self, providers: list[str], ov_device_ids: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.CPU_EP - - @pytest.mark.providers(CUDA_EP_OUT_OF_ORDER) - def test_sets_providers_in_correct_order(self, providers: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.CUDA_EP - - @pytest.mark.providers(TRT_EP) - def test_ignores_unsupported_providers(self, providers: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.CUDA_EP - - @pytest.mark.providers(ROCM_EP) - def test_uses_rocm(self, providers: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.ROCM_EP - - @pytest.mark.providers(COREML_EP) - def test_uses_coreml(self, providers: list[str]) -> None: - session = OrtSession("ViT-B-32__openai") - - assert session.providers == self.COREML_EP - - def test_sets_provider_kwarg(self) -> None: - providers = ["CUDAExecutionProvider"] - session = OrtSession("ViT-B-32__openai", providers=providers) - - assert session.providers == providers - - @pytest.mark.ov_device_ids(["GPU.0", "CPU"]) - def test_sets_default_provider_options(self, ov_device_ids: list[str]) -> None: - model_path = "/cache/ViT-B-32__openai/textual/model.onnx" - - session = OrtSession(model_path, providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"]) - - assert session.provider_options == [ - { - "device_type": "GPU.0", - "precision": "FP32", - "cache_dir": "/cache/ViT-B-32__openai/textual/openvino", - }, - {"arena_extend_strategy": "kSameAsRequested"}, - ] - - def test_sets_provider_options_for_openvino(self) -> None: - model_path = "/cache/ViT-B-32__openai/textual/model.onnx" - os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" - - session = OrtSession(model_path, providers=["OpenVINOExecutionProvider"]) - - assert session.provider_options == [ - { - "device_type": "GPU.1", - "precision": "FP32", - "cache_dir": "/cache/ViT-B-32__openai/textual/openvino", - } - ] - - def test_sets_openvino_to_fp16_if_enabled(self, mocker: MockerFixture) -> None: - model_path = "/cache/ViT-B-32__openai/textual/model.onnx" - os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" - mocker.patch.object(settings, "openvino_precision", ModelPrecision.FP16) - - session = OrtSession(model_path, providers=["OpenVINOExecutionProvider"]) - - assert session.provider_options == [ - { - "device_type": "GPU.1", - "precision": "FP16", - "cache_dir": "/cache/ViT-B-32__openai/textual/openvino", - } - ] - - def test_sets_provider_options_for_cuda(self) -> None: - os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" - - session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider"]) - - assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}] - - def test_sets_provider_options_for_rocm(self) -> None: - os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" - - session = OrtSession("ViT-B-32__openai", providers=["ROCMExecutionProvider"]) - - assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}] - - def test_sets_provider_options_kwarg(self) -> None: - session = OrtSession( - "ViT-B-32__openai", - providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"], - provider_options=[], - ) - - assert session.provider_options == [] - - def test_sets_default_sess_options_if_cpu(self) -> None: - session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"]) - - assert session.sess_options.execution_mode == ort.ExecutionMode.ORT_SEQUENTIAL - assert session.sess_options.inter_op_num_threads == 1 - assert session.sess_options.intra_op_num_threads == 2 - - def test_sets_default_sess_options_does_not_set_threads_if_non_cpu_and_default_threads(self) -> None: - session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) - - assert session.sess_options.inter_op_num_threads == 0 - assert session.sess_options.intra_op_num_threads == 0 - - def test_sets_default_sess_options_sets_threads_if_non_cpu_and_set_threads(self, mocker: MockerFixture) -> None: - mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True) - mock_settings.model_inter_op_threads = 2 - mock_settings.model_intra_op_threads = 4 - - session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) - - assert session.sess_options.inter_op_num_threads == 2 - assert session.sess_options.intra_op_num_threads == 4 - - def test_uses_arena_if_enabled(self, mocker: MockerFixture) -> None: - mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True) - mock_settings.model_inter_op_threads = 0 - mock_settings.model_intra_op_threads = 0 - mock_settings.model_arena = True - - session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"]) - - assert session.sess_options.enable_cpu_mem_arena - - def test_does_not_use_arena_if_disabled(self, mocker: MockerFixture) -> None: - mock_settings = mocker.patch("immich_ml.sessions.ort.settings", autospec=True) - mock_settings.model_inter_op_threads = 0 - mock_settings.model_intra_op_threads = 0 - mock_settings.model_arena = False - - session = OrtSession("ViT-B-32__openai", providers=["CPUExecutionProvider"]) - - assert not session.sess_options.enable_cpu_mem_arena - - def test_sets_sess_options_kwarg(self) -> None: - sess_options = ort.SessionOptions() - session = OrtSession( - "ViT-B-32__openai", - providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"], - provider_options=[], - sess_options=sess_options, - ) - - assert sess_options is session.sess_options - - -class TestAnnSession: - def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None: - model_path = mock.MagicMock(spec=Path) - cache_dir = mock.MagicMock(spec=Path) - - AnnSession(model_path, cache_dir) - - ann_session.assert_called_once_with(tuning_level=2, tuning_file=(cache_dir / "gpu-tuning.ann").as_posix()) - ann_session.return_value.load.assert_called_once_with( - model_path.as_posix(), cached_network_path=model_path.with_suffix(".anncache").as_posix(), fp16=False - ) - info.assert_has_calls( - [ - mock.call("Loading ANN model %s ...", model_path), - mock.call("Loaded ANN model with ID %d", ann_session.return_value.load.return_value), - ] - ) - - def test_get_inputs(self, ann_session: mock.Mock) -> None: - ann_session.return_value.load.return_value = 123 - ann_session.return_value.input_shapes = {123: [(1, 3, 224, 224)]} - session = AnnSession(Path("ViT-B-32__openai")) - - inputs = session.get_inputs() - - assert len(inputs) == 1 - assert inputs[0].name is None - assert inputs[0].shape == (1, 3, 224, 224) - - def test_get_outputs(self, ann_session: mock.Mock) -> None: - ann_session.return_value.load.return_value = 123 - ann_session.return_value.output_shapes = {123: [(1, 3, 224, 224)]} - session = AnnSession(Path("ViT-B-32__openai")) - - outputs = session.get_outputs() - - assert len(outputs) == 1 - assert outputs[0].name is None - assert outputs[0].shape == (1, 3, 224, 224) - - def test_run(self, ann_session: mock.Mock, mocker: MockerFixture) -> None: - ann_session.return_value.load.return_value = 123 - np_spy = mocker.spy(np, "ascontiguousarray") - session = AnnSession(Path("ViT-B-32__openai")) - [input1, input2] = [np.random.rand(1, 3, 224, 224).astype(np.float32) for _ in range(2)] - input_feed = {"input.1": input1, "input.2": input2} - - session.run(None, input_feed) - - ann_session.return_value.execute.assert_called_once_with(123, [input1, input2]) - assert np_spy.call_count == 2 - np_spy.assert_has_calls([mock.call(input1), mock.call(input2)]) - - -class TestRknnSession: - def test_creates_rknn_session(self, rknn_session: mock.Mock, info: mock.Mock, mocker: MockerFixture) -> None: - model_path = mock.MagicMock(spec=Path) - tpe = 1 - mocker.patch("immich_ml.sessions.rknn.soc_name", "rk3566") - mocker.patch("immich_ml.sessions.rknn.is_available", True) - RknnSession(model_path) - - rknn_session.assert_called_once_with(model_path=model_path.as_posix(), tpes=tpe, func=run_inference) - - info.assert_has_calls([mock.call(f"Loaded RKNN model from {model_path} with {tpe} threads.")]) - - def test_run_rknn(self, rknn_session: mock.Mock, mocker: MockerFixture) -> None: - rknn_session.return_value.load.return_value = 123 - np_spy = mocker.spy(np, "ascontiguousarray") - mocker.patch("immich_ml.sessions.rknn.soc_name", "rk3566") - session = RknnSession(Path("ViT-B-32__openai")) - [input1, input2] = [np.random.rand(1, 3, 224, 224).astype(np.float32) for _ in range(2)] - input_feed = {"input.1": input1, "input.2": input2} - - session.run(None, input_feed) - - rknn_session.return_value.put.assert_called_once_with([input1, input2]) - assert np_spy.call_count == 2 - np_spy.assert_has_calls([mock.call(input1), mock.call(input2)]) - - -class TestCLIP: - embedding = np.random.rand(512).astype(np.float32) - cache_dir = Path("test_cache") - - def test_basic_image( - self, - pil_image: Image.Image, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_preprocess_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(OpenClipVisualEncoder, "download") - mocker.patch.object(OpenClipVisualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipVisualEncoder, "preprocess_cfg", clip_preprocess_cfg) - - mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mocked.run.return_value = [[self.embedding]] - - clip_encoder = OpenClipVisualEncoder("ViT-B-32__openai", cache_dir="test_cache") - embedding_str = clip_encoder.predict(pil_image) - assert isinstance(embedding_str, str) - embedding = orjson.loads(embedding_str) - assert isinstance(embedding, list) - assert len(embedding) == clip_model_cfg["embed_dim"] - mocked.run.assert_called_once() - - def test_basic_text( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - - mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mocked.run.return_value = [[self.embedding]] - mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True) - - clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") - embedding_str = clip_encoder.predict("test search query") - assert isinstance(embedding_str, str) - embedding = orjson.loads(embedding_str) - assert isinstance(embedding, list) - assert len(embedding) == clip_model_cfg["embed_dim"] - mocked.run.assert_called_once() - - def test_openclip_tokenizer( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) - - clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") - clip_encoder._load() - tokens = clip_encoder.tokenize("test search query") - - assert "text" in tokens - assert isinstance(tokens["text"], np.ndarray) - assert tokens["text"].shape == (1, 77) - assert tokens["text"].dtype == np.int32 - assert np.allclose(tokens["text"], np.array([mock_ids], dtype=np.int32), atol=0) - mock_tokenizer.encode.assert_called_once_with("test search query") - - def test_openclip_tokenizer_canonicalizes_text( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - clip_model_cfg["text_cfg"]["tokenizer_kwargs"] = {"clean": "canonicalize"} - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) - - clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") - clip_encoder._load() - tokens = clip_encoder.tokenize("Test Search Query!") - - assert "text" in tokens - assert isinstance(tokens["text"], np.ndarray) - assert tokens["text"].shape == (1, 77) - assert tokens["text"].dtype == np.int32 - assert np.allclose(tokens["text"], np.array([mock_ids], dtype=np.int32), atol=0) - mock_tokenizer.encode.assert_called_once_with("test search query") - - def test_openclip_tokenizer_adds_flores_token_for_nllb( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) - - clip_encoder = OpenClipTextualEncoder("nllb-clip-base-siglip__mrl", cache_dir="test_cache") - clip_encoder._load() - clip_encoder.tokenize("test search query", language="de") - - mock_tokenizer.encode.assert_called_once_with("deu_Latntest search query") - - def test_openclip_tokenizer_removes_country_code_from_language_for_nllb_if_not_found( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) - - clip_encoder = OpenClipTextualEncoder("nllb-clip-base-siglip__mrl", cache_dir="test_cache") - clip_encoder._load() - clip_encoder.tokenize("test search query", language="de-CH") - - mock_tokenizer.encode.assert_called_once_with("deu_Latntest search query") - - def test_openclip_tokenizer_falls_back_to_english_for_nllb_if_language_code_not_found( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - warning: mock.Mock, - ) -> None: - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) - - clip_encoder = OpenClipTextualEncoder("nllb-clip-base-siglip__mrl", cache_dir="test_cache") - clip_encoder._load() - clip_encoder.tokenize("test search query", language="unknown") - - mock_tokenizer.encode.assert_called_once_with("eng_Latntest search query") - warning.assert_called_once_with("Language 'unknown' not found, defaulting to 'en'") - - def test_openclip_tokenizer_does_not_add_flores_token_for_non_nllb_model( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(OpenClipTextualEncoder, "download") - mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids) - - clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") - clip_encoder._load() - clip_encoder.tokenize("test search query", language="de") - - mock_tokenizer.encode.assert_called_once_with("test search query") - - def test_mclip_tokenizer( - self, - mocker: MockerFixture, - clip_model_cfg: dict[str, Any], - clip_tokenizer_cfg: Callable[[Path], dict[str, Any]], - ) -> None: - mocker.patch.object(MClipTextualEncoder, "download") - mocker.patch.object(MClipTextualEncoder, "model_cfg", clip_model_cfg) - mocker.patch.object(MClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg) - mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value - mock_tokenizer = mocker.patch("immich_ml.models.clip.textual.Tokenizer.from_file", autospec=True).return_value - mock_ids = [randint(0, 50000) for _ in range(77)] - mock_attention_mask = [randint(0, 1) for _ in range(77)] - mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids, attention_mask=mock_attention_mask) - - clip_encoder = MClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache") - clip_encoder._load() - tokens = clip_encoder.tokenize("test search query") - - assert "input_ids" in tokens - assert "attention_mask" in tokens - assert isinstance(tokens["input_ids"], np.ndarray) - assert isinstance(tokens["attention_mask"], np.ndarray) - assert tokens["input_ids"].shape == (1, 77) - assert tokens["attention_mask"].shape == (1, 77) - assert np.allclose(tokens["input_ids"], np.array([mock_ids], dtype=np.int32), atol=0) - assert np.allclose(tokens["attention_mask"], np.array([mock_attention_mask], dtype=np.int32), atol=0) - - -class TestFaceRecognition: - def test_set_min_score(self, snapshot_download: mock.Mock, ort_session: mock.Mock, path: mock.Mock) -> None: - path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" - - face_detector = FaceDetector("buffalo_s", min_score=0.5, cache_dir="test_cache") - face_detector.load() - - assert face_detector.min_score == 0.5 - assert face_detector.model.det_thresh == 0.5 - - def test_detection(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: - mocker.patch.object(FaceDetector, "load") - face_detector = FaceDetector("buffalo_s", min_score=0.0, cache_dir="test_cache") - - det_model = mock.Mock() - num_faces = 2 - bbox = np.random.rand(num_faces, 4).astype(np.float32) - scores = np.array([[0.67]] * num_faces).astype(np.float32) - kpss = np.random.rand(num_faces, 5, 2).astype(np.float32) - det_model.detect.return_value = (np.concatenate([bbox, scores], axis=-1), kpss) - face_detector.model = det_model - - faces = face_detector.predict(cv_image) - - assert isinstance(faces, dict) - assert isinstance(faces.get("boxes", None), np.ndarray) - assert isinstance(faces.get("landmarks", None), np.ndarray) - assert isinstance(faces.get("scores", None), np.ndarray) - assert np.equal(faces["boxes"], bbox.round()).all() - assert np.equal(faces["landmarks"], kpss).all() - assert np.equal(faces["scores"], scores).all() - det_model.detect.assert_called_once() - - def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: - mocker.patch.object(FaceRecognizer, "load") - face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache") - - num_faces = 2 - bbox = np.random.rand(num_faces, 4).astype(np.float32) - scores = np.array([0.67] * num_faces).astype(np.float32) - kpss = np.random.rand(num_faces, 5, 2).astype(np.float32) - faces = {"boxes": bbox, "landmarks": kpss, "scores": scores} - - rec_model = mock.Mock() - embedding = np.random.rand(num_faces, 512).astype(np.float32) - rec_model.get_feat.return_value = embedding - face_recognizer.model = rec_model - - faces = face_recognizer.predict(cv_image, faces) - - assert isinstance(faces, list) - assert len(faces) == num_faces - for face in faces: - assert isinstance(face.get("boundingBox"), dict) - assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"} - assert all(isinstance(val, np.float32) for val in face["boundingBox"].values()) - embedding_str = face.get("embedding") - assert isinstance(embedding_str, str) - embedding = orjson.loads(embedding_str) - assert isinstance(embedding, list) - assert len(embedding) == 512 - assert isinstance(face.get("score", None), np.float32) - - rec_model.get_feat.assert_called_once() - call_args = rec_model.get_feat.call_args_list[0].args - assert len(call_args) == 1 - assert isinstance(call_args[0], list) - assert isinstance(call_args[0][0], np.ndarray) - assert call_args[0][0].shape == (112, 112, 3) - - def test_recognition_adds_batch_axis_for_ort( - self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture - ) -> None: - onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) - update_dims = mocker.patch( - "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True - ) - mocker.patch("immich_ml.models.base.InferenceModel.download") - mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") - ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] - ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))] - path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" - - proto = mock.Mock() - - input_dims = mock.Mock() - input_dims.name = "input.1" - input_dims.type.tensor_type.shape.dim = [SimpleNamespace(dim_value=size) for size in [1, 3, 224, 224]] - proto.graph.input = [input_dims] - - output_dims = mock.Mock() - output_dims.name = "output.1" - output_dims.type.tensor_type.shape.dim = [SimpleNamespace(dim_value=size) for size in [1, 800]] - proto.graph.output = [output_dims] - - onnx.load.return_value = proto - - face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path) - face_recognizer.load() - - assert face_recognizer.batch_size is None - update_dims.assert_called_once_with(proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]}) - onnx.save.assert_called_once_with(update_dims.return_value, face_recognizer.model_path) - - def test_recognition_does_not_add_batch_axis_if_exists( - self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture - ) -> None: - onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) - update_dims = mocker.patch( - "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True - ) - mocker.patch("immich_ml.models.base.InferenceModel.download") - mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") - path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" - - inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] - outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))] - ort_session.return_value.get_inputs.return_value = inputs - ort_session.return_value.get_outputs.return_value = outputs - - face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path) - face_recognizer.load() - - assert face_recognizer.batch_size is None - update_dims.assert_not_called() - onnx.load.assert_not_called() - onnx.save.assert_not_called() - - def test_recognition_does_not_add_batch_axis_for_armnn( - self, ann_session: mock.Mock, path: mock.Mock, mocker: MockerFixture - ) -> None: - onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) - update_dims = mocker.patch( - "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True - ) - mocker.patch("immich_ml.models.base.InferenceModel.download") - mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") - path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".armnn" - - inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] - outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))] - ann_session.return_value.get_inputs.return_value = inputs - ann_session.return_value.get_outputs.return_value = outputs - - face_recognizer = FaceRecognizer("buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path) - face_recognizer.load() - - assert face_recognizer.batch_size == 1 - update_dims.assert_not_called() - onnx.load.assert_not_called() - onnx.save.assert_not_called() - - def test_recognition_does_not_add_batch_axis_for_openvino( - self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture - ) -> None: - onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) - update_dims = mocker.patch( - "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True - ) - mocker.patch("immich_ml.models.base.InferenceModel.download") - mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") - path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" - - inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] - outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))] - ort_session.return_value.get_inputs.return_value = inputs - ort_session.return_value.get_outputs.return_value = outputs - - face_recognizer = FaceRecognizer( - "buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path, providers=["OpenVINOExecutionProvider"] - ) - face_recognizer.load() - - assert face_recognizer.batch_size == 1 - update_dims.assert_not_called() - onnx.load.assert_not_called() - onnx.save.assert_not_called() - - -@pytest.mark.asyncio -class TestCache: - async def test_caches(self, mock_get_model: mock.Mock) -> None: - model_cache = ModelCache() - await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION) - await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION) - assert len(model_cache.cache._cache) == 1 - mock_get_model.assert_called_once() - - async def test_kwargs_used(self, mock_get_model: mock.Mock) -> None: - model_cache = ModelCache() - await model_cache.get( - "test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, cache_dir="test_cache" - ) - mock_get_model.assert_called_once_with( - "test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, cache_dir="test_cache" - ) - - async def test_different_clip(self, mock_get_model: mock.Mock) -> None: - model_cache = ModelCache() - await model_cache.get("test_model_name", ModelType.VISUAL, ModelTask.SEARCH) - await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH) - mock_get_model.assert_has_calls( - [ - mock.call("test_model_name", ModelType.VISUAL, ModelTask.SEARCH), - mock.call("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH), - ] - ) - assert len(model_cache.cache._cache) == 2 - - @mock.patch("immich_ml.models.cache.OptimisticLock", autospec=True) - async def test_model_ttl(self, mock_lock_cls: mock.Mock, mock_get_model: mock.Mock) -> None: - model_cache = ModelCache() - await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) - mock_lock_cls.return_value.__aenter__.return_value.cas.assert_called_with(mock.ANY, ttl=100) - - @mock.patch("immich_ml.models.cache.SimpleMemoryCache.expire") - async def test_revalidate_get(self, mock_cache_expire: mock.Mock, mock_get_model: mock.Mock) -> None: - model_cache = ModelCache(revalidate=True) - await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) - await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) - mock_cache_expire.assert_called_once_with(mock.ANY, 100) - - async def test_profiling(self, mock_get_model: mock.Mock) -> None: - model_cache = ModelCache(profiling=True) - await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100) - profiling = await model_cache.get_profiling() - assert isinstance(profiling, dict) - assert profiling == model_cache.cache.profiling - - async def test_loads_mclip(self) -> None: - model_cache = ModelCache() - - model = await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.TEXTUAL, ModelTask.SEARCH) - - assert isinstance(model, MClipTextualEncoder) - assert model.model_name == "XLM-Roberta-Large-Vit-B-32" - - async def test_raises_exception_if_invalid_model_type(self) -> None: - invalid: Any = SimpleNamespace(value="invalid") - model_cache = ModelCache() - - with pytest.raises(ValueError): - await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.TEXTUAL, invalid) - - async def test_raises_exception_if_unknown_model_name(self) -> None: - model_cache = ModelCache() - - with pytest.raises(ValueError): - await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH) - - async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" - - settings = Settings() - assert settings.preload is not None - assert settings.preload.clip.textual == "ViT-B-32__openai" - assert settings.preload.clip.visual == "ViT-B-32__openai" - - model_cache = ModelCache() - monkeypatch.setattr("immich_ml.main.model_cache", model_cache) - - await preload_models(settings.preload) - mock_get_model.assert_has_calls( - [ - mock.call("ViT-B-32__openai", ModelType.TEXTUAL, ModelTask.SEARCH), - mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH), - ], - any_order=True, - ) - - async def test_preloads_facial_recognition_models( - self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock - ) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" - - settings = Settings() - assert settings.preload is not None - assert settings.preload.facial_recognition.detection == "buffalo_s" - assert settings.preload.facial_recognition.recognition == "buffalo_s" - - model_cache = ModelCache() - monkeypatch.setattr("immich_ml.main.model_cache", model_cache) - - await preload_models(settings.preload) - mock_get_model.assert_has_calls( - [ - mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION), - mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION), - ], - any_order=True, - ) - - async def test_preloads_ocr_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__OCR__DETECTION"] = "PP-OCRv5_mobile" - os.environ["MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION"] = "PP-OCRv5_mobile" - - settings = Settings() - assert settings.preload is not None - assert settings.preload.ocr.detection == "PP-OCRv5_mobile" - assert settings.preload.ocr.recognition == "PP-OCRv5_mobile" - - model_cache = ModelCache() - monkeypatch.setattr("immich_ml.main.model_cache", model_cache) - - await preload_models(settings.preload) - mock_get_model.assert_has_calls( - [ - mock.call("PP-OCRv5_mobile", ModelType.DETECTION, ModelTask.OCR), - mock.call("PP-OCRv5_mobile", ModelType.RECOGNITION, ModelTask.OCR), - ], - any_order=True, - ) - - async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" - os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" - os.environ["MACHINE_LEARNING_PRELOAD__OCR__DETECTION"] = "PP-OCRv5_mobile" - os.environ["MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION"] = "PP-OCRv5_mobile" - - settings = Settings() - assert settings.preload is not None - assert settings.preload.clip.visual == "ViT-B-32__openai" - assert settings.preload.clip.textual == "ViT-B-32__openai" - assert settings.preload.facial_recognition.recognition == "buffalo_s" - assert settings.preload.facial_recognition.detection == "buffalo_s" - assert settings.preload.ocr.detection == "PP-OCRv5_mobile" - assert settings.preload.ocr.recognition == "PP-OCRv5_mobile" - - model_cache = ModelCache() - monkeypatch.setattr("immich_ml.main.model_cache", model_cache) - - await preload_models(settings.preload) - mock_get_model.assert_has_calls( - [ - mock.call("ViT-B-32__openai", ModelType.TEXTUAL, ModelTask.SEARCH), - mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH), - mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION), - mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION), - mock.call("PP-OCRv5_mobile", ModelType.DETECTION, ModelTask.OCR), - mock.call("PP-OCRv5_mobile", ModelType.RECOGNITION, ModelTask.OCR), - ], - any_order=True, - ) - - -@pytest.mark.asyncio -class TestLoad: - async def test_load(self) -> None: - mock_model = mock.Mock(spec=InferenceModel) - mock_model.loaded = False - mock_model.load_attempts = 0 - - res = await load(mock_model) - - assert res is mock_model - mock_model.load.assert_called_once() - mock_model.clear_cache.assert_not_called() - - async def test_load_returns_model_if_loaded(self) -> None: - mock_model = mock.Mock(spec=InferenceModel) - mock_model.loaded = True - - res = await load(mock_model) - - assert res is mock_model - mock_model.load.assert_not_called() - - async def test_load_clears_cache_and_retries_if_os_error(self) -> None: - mock_model = mock.Mock(spec=InferenceModel) - mock_model.model_name = "test_model_name" - mock_model.model_type = ModelType.VISUAL - mock_model.model_task = ModelTask.SEARCH - mock_model.load.side_effect = [OSError, None] - mock_model.loaded = False - mock_model.load_attempts = 0 - - res = await load(mock_model) - - assert res is mock_model - mock_model.clear_cache.assert_called_once() - assert mock_model.load.call_count == 2 - - async def test_load_raises_if_os_error_and_already_retried(self) -> None: - mock_model = mock.Mock(spec=InferenceModel) - mock_model.model_name = "test_model_name" - mock_model.model_type = ModelType.VISUAL - mock_model.model_task = ModelTask.SEARCH - mock_model.loaded = False - mock_model.load_attempts = 2 - - with pytest.raises(HTTPException): - await load(mock_model) - - mock_model.clear_cache.assert_not_called() - mock_model.load.assert_not_called() - - async def test_falls_back_to_onnx_if_other_format_does_not_exist(self, warning: mock.Mock) -> None: - mock_model = mock.Mock(spec=InferenceModel) - mock_model.model_name = "test_model_name" - mock_model.model_type = ModelType.VISUAL - mock_model.model_task = ModelTask.SEARCH - mock_model.model_format = ModelFormat.ARMNN - mock_model.loaded = False - mock_model.load_attempts = 0 - error = FileNotFoundError() - mock_model.load.side_effect = [error, None] - - await load(mock_model) - - mock_model.clear_cache.assert_not_called() - assert mock_model.load.call_count == 2 - warning.assert_called_once_with( - "ARMNN is available, but model 'test_model_name' does not support it.", exc_info=error - ) - mock_model.model_format = ModelFormat.ONNX - - -def test_root_endpoint(deployed_app: TestClient) -> None: - response = deployed_app.get("http://localhost:3003") - - body = response.json() - assert response.status_code == 200 - assert body == {"message": "Immich ML"} - - -def test_ping_endpoint(deployed_app: TestClient) -> None: - response = deployed_app.get("http://localhost:3003/ping") - - assert response.status_code == 200 - assert response.text == "pong" - - -@pytest.mark.skipif( - not settings.test_full, - reason="More time-consuming since it deploys the app and loads models.", -) -class TestPredictionEndpoints: - def test_clip_image_endpoint( - self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient - ) -> None: - byte_image = BytesIO() - pil_image.save(byte_image, format="jpeg") - expected = responses["clip"]["image"] - - response = deployed_app.post( - "http://localhost:3003/predict", - data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})}, - files={"image": byte_image.getvalue()}, - ) - - actual = response.json() - assert response.status_code == 200 - assert isinstance(actual, dict) - embedding = actual.get("clip", None) - assert isinstance(embedding, str) - parsed_embedding = orjson.loads(embedding) - assert np.allclose(expected, parsed_embedding) - - def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None: - expected = responses["clip"]["text"] - - response = deployed_app.post( - "http://localhost:3003/predict", - data={ - "entries": json.dumps( - { - "clip": {"textual": {"modelName": "ViT-B-32__openai"}}, - }, - ), - "text": "test search query", - }, - ) - - actual = response.json() - assert response.status_code == 200 - assert isinstance(actual, dict) - embedding = actual.get("clip", None) - assert isinstance(embedding, str) - parsed_embedding = orjson.loads(embedding) - assert np.allclose(expected, parsed_embedding) - - def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None: - byte_image = BytesIO() - pil_image.save(byte_image, format="jpeg") - - response = deployed_app.post( - "http://localhost:3003/predict", - data={ - "entries": json.dumps( - { - "facial-recognition": { - "detection": {"modelName": "buffalo_l", "options": {"minScore": 0.034}}, - "recognition": {"modelName": "buffalo_l"}, - } - } - ) - }, - files={"image": byte_image.getvalue()}, - ) - - actual = response.json() - assert response.status_code == 200 - assert isinstance(actual, dict) - assert actual.get("imageHeight", None) == responses["imageHeight"] - assert actual.get("imageWidth", None) == responses["imageWidth"] - assert "facial-recognition" in actual and isinstance(actual["facial-recognition"], list) - assert len(actual["facial-recognition"]) == len(responses["facial-recognition"]) - - for expected_face, actual_face in zip(responses["facial-recognition"], actual["facial-recognition"]): - assert expected_face["boundingBox"] == actual_face["boundingBox"] - embedding = actual_face.get("embedding", None) - assert isinstance(embedding, str) - parsed_embedding = orjson.loads(embedding) - assert np.allclose(expected_face["embedding"], parsed_embedding) - assert np.allclose(expected_face["score"], actual_face["score"]) diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock deleted file mode 100644 index d0b502283f..0000000000 --- a/machine-learning/uv.lock +++ /dev/null @@ -1,3087 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.11, <4.0" -resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", - "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", -] - -[[package]] -name = "aiocache" -version = "0.12.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7a/64/b945b8025a9d1e6e2138845f4022165d3b337f55f50984fbc6a4c0a1e355/aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713", size = 132196, upload-time = "2024-09-25T13:20:23.823Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199, upload-time = "2024-09-25T13:20:22.688Z" }, -] - -[[package]] -name = "albumentations" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "pyyaml" }, - { name = "qudida" }, - { name = "scikit-image" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/14/d6/8dd5b690d28a332a0b2c3179a345808b5d4c7ad5ddc079b7e116098dff35/albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6", size = 176371, upload-time = "2023-06-10T07:44:32.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f6/c486cedb4f75147232f32ec4c97026714cfef7c7e247a1f0427bc5489f66/albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd", size = 125706, upload-time = "2023-06-10T07:44:30.373Z" }, -] - -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "antlr4-python3-runtime" -version = "4.9.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } - -[[package]] -name = "anyio" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "sniffio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/b8/7333d87d5f03247215d86a86362fd3e324111788c6cdd8d2e6196a6ba833/anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f", size = 158770, upload-time = "2023-12-16T17:06:57.709Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481, upload-time = "2023-12-16T17:06:55.989Z" }, -] - -[[package]] -name = "bidict" -version = "0.23.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, -] - -[[package]] -name = "blinker" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/13/6df5fc090ff4e5d246baf1f45fe9e5623aa8565757dfa5bd243f6a545f9e/blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182", size = 28134, upload-time = "2023-11-01T22:06:01.588Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9", size = 13068, upload-time = "2023-11-01T22:06:00.162Z" }, -] - -[[package]] -name = "brotli" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068, upload-time = "2023-09-07T14:03:37.779Z" }, - { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244, upload-time = "2023-09-07T14:03:39.223Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500, upload-time = "2023-09-07T14:03:40.858Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950, upload-time = "2023-09-07T14:03:42.896Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527, upload-time = "2023-09-07T14:03:44.552Z" }, - { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489, upload-time = "2023-09-07T14:03:46.594Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080, upload-time = "2023-09-07T14:03:48.204Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051, upload-time = "2023-09-07T14:03:50.348Z" }, - { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172, upload-time = "2023-09-07T14:03:52.395Z" }, - { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023, upload-time = "2023-09-07T14:03:53.96Z" }, - { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871, upload-time = "2024-10-18T12:32:16.688Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784, upload-time = "2024-10-18T12:32:18.459Z" }, - { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905, upload-time = "2024-10-18T12:32:20.192Z" }, - { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467, upload-time = "2024-10-18T12:32:21.774Z" }, - { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169, upload-time = "2023-09-07T14:03:55.404Z" }, - { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253, upload-time = "2023-09-07T14:03:56.643Z" }, - { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, - { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, - { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, - { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, - { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, - { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, - { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, - { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, - { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, - { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, - { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, - { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, - { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, - { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, - { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, -] - -[[package]] -name = "certifi" -version = "2025.11.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, -] - -[[package]] -name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", size = 104809, upload-time = "2023-11-01T04:04:59.997Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", size = 191647, upload-time = "2023-11-01T04:02:55.329Z" }, - { url = "https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", size = 121434, upload-time = "2023-11-01T04:02:57.173Z" }, - { url = "https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", size = 118979, upload-time = "2023-11-01T04:02:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", size = 136582, upload-time = "2023-11-01T04:02:59.776Z" }, - { url = "https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", size = 146645, upload-time = "2023-11-01T04:03:02.186Z" }, - { url = "https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", size = 139398, upload-time = "2023-11-01T04:03:04.255Z" }, - { url = "https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", size = 140273, upload-time = "2023-11-01T04:03:05.983Z" }, - { url = "https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", size = 142577, upload-time = "2023-11-01T04:03:07.567Z" }, - { url = "https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", size = 137747, upload-time = "2023-11-01T04:03:08.886Z" }, - { url = "https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", size = 143375, upload-time = "2023-11-01T04:03:10.613Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", size = 148474, upload-time = "2023-11-01T04:03:11.973Z" }, - { url = "https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", size = 140232, upload-time = "2023-11-01T04:03:13.505Z" }, - { url = "https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", size = 140859, upload-time = "2023-11-01T04:03:17.362Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c2/4a583f800c0708dd22096298e49f887b49d9746d0e78bfc1d7e29816614c/charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", size = 92509, upload-time = "2023-11-01T04:03:21.453Z" }, - { url = "https://files.pythonhosted.org/packages/57/ec/80c8d48ac8b1741d5b963797b7c0c869335619e13d4744ca2f67fc11c6fc/charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", size = 99870, upload-time = "2023-11-01T04:03:22.723Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b2/fcedc8255ec42afee97f9e6f0145c734bbe104aac28300214593eb326f1d/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", size = 192892, upload-time = "2023-11-01T04:03:24.135Z" }, - { url = "https://files.pythonhosted.org/packages/2e/7d/2259318c202f3d17f3fe6438149b3b9e706d1070fe3fcbb28049730bb25c/charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", size = 122213, upload-time = "2023-11-01T04:03:25.66Z" }, - { url = "https://files.pythonhosted.org/packages/3a/52/9f9d17c3b54dc238de384c4cb5a2ef0e27985b42a0e5cc8e8a31d918d48d/charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", size = 119404, upload-time = "2023-11-01T04:03:27.04Z" }, - { url = "https://files.pythonhosted.org/packages/99/b0/9c365f6d79a9f0f3c379ddb40a256a67aa69c59609608fe7feb6235896e1/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", size = 137275, upload-time = "2023-11-01T04:03:28.466Z" }, - { url = "https://files.pythonhosted.org/packages/91/33/749df346e93d7a30cdcb90cbfdd41a06026317bfbfb62cd68307c1a3c543/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", size = 147518, upload-time = "2023-11-01T04:03:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/72/1a/641d5c9f59e6af4c7b53da463d07600a695b9824e20849cb6eea8a627761/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", size = 140182, upload-time = "2023-11-01T04:03:31.511Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fb/14d30eb4956408ee3ae09ad34299131fb383c47df355ddb428a7331cfa1e/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", size = 141869, upload-time = "2023-11-01T04:03:32.887Z" }, - { url = "https://files.pythonhosted.org/packages/df/3e/a06b18788ca2eb6695c9b22325b6fde7dde0f1d1838b1792a0076f58fe9d/charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", size = 144042, upload-time = "2023-11-01T04:03:34.412Z" }, - { url = "https://files.pythonhosted.org/packages/45/59/3d27019d3b447a88fe7e7d004a1e04be220227760264cc41b405e863891b/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", size = 138275, upload-time = "2023-11-01T04:03:35.759Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ef/5eb105530b4da8ae37d506ccfa25057961b7b63d581def6f99165ea89c7e/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", size = 144819, upload-time = "2023-11-01T04:03:37.216Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/e5023f937d7f307c948ed3e5c29c4b7a3e42ed2ee0b8cdf8f3a706089bf0/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", size = 149415, upload-time = "2023-11-01T04:03:38.694Z" }, - { url = "https://files.pythonhosted.org/packages/24/9d/2e3ef673dfd5be0154b20363c5cdcc5606f35666544381bee15af3778239/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", size = 141212, upload-time = "2023-11-01T04:03:40.07Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ae/ce2c12fcac59cb3860b2e2d76dc405253a4475436b1861d95fe75bdea520/charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", size = 142167, upload-time = "2023-11-01T04:03:41.491Z" }, - { url = "https://files.pythonhosted.org/packages/ed/3a/a448bf035dce5da359daf9ae8a16b8a39623cc395a2ffb1620aa1bce62b0/charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", size = 93041, upload-time = "2023-11-01T04:03:42.836Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63242c23851bdf00a532489fba57fef3416e40/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", size = 100397, upload-time = "2023-11-01T04:03:44.467Z" }, - { url = "https://files.pythonhosted.org/packages/28/76/e6222113b83e3622caa4bb41032d0b1bf785250607392e1b778aca0b8a7d/charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", size = 48543, upload-time = "2023-11-01T04:04:58.622Z" }, -] - -[[package]] -name = "click" -version = "8.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "coloredlogs" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "humanfriendly" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, -] - -[[package]] -name = "colorlog" -version = "6.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624, upload-time = "2024-10-29T18:34:51.011Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424, upload-time = "2024-10-29T18:34:49.815Z" }, -] - -[[package]] -name = "configargparse" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/4d/6c9ef746dfcc2a32e26f3860bb4a011c008c392b83eabdfb598d1a8bbe5d/configargparse-1.7.1.tar.gz", hash = "sha256:79c2ddae836a1e5914b71d58e4b9adbd9f7779d4e6351a637b7d2d9b6c46d3d9", size = 43958, upload-time = "2025-05-23T14:26:17.369Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607, upload-time = "2025-05-23T14:26:15.923Z" }, -] - -[[package]] -name = "contourpy" -version = "1.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, -] - -[[package]] -name = "coverage" -version = "7.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716, upload-time = "2024-10-20T22:57:39.682Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/31/9c0cf84f0dfcbe4215b7eb95c31777cdc0483c13390e69584c8150c85175/coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", size = 206819, upload-time = "2024-10-20T22:56:20.132Z" }, - { url = "https://files.pythonhosted.org/packages/53/ed/a38401079ad320ad6e054a01ec2b61d270511aeb3c201c80e99c841229d5/coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", size = 207263, upload-time = "2024-10-20T22:56:21.88Z" }, - { url = "https://files.pythonhosted.org/packages/20/e7/c3ad33b179ab4213f0d70da25a9c214d52464efa11caeab438592eb1d837/coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", size = 239205, upload-time = "2024-10-20T22:56:23.03Z" }, - { url = "https://files.pythonhosted.org/packages/36/91/fc02e8d8e694f557752120487fd982f654ba1421bbaa5560debf96ddceda/coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", size = 236612, upload-time = "2024-10-20T22:56:24.882Z" }, - { url = "https://files.pythonhosted.org/packages/cc/57/cb08f0eda0389a9a8aaa4fc1f9fec7ac361c3e2d68efd5890d7042c18aa3/coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", size = 238479, upload-time = "2024-10-20T22:56:26.749Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c9/2c7681a9b3ca6e6f43d489c2e6653a53278ed857fd6e7010490c307b0a47/coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", size = 237405, upload-time = "2024-10-20T22:56:27.958Z" }, - { url = "https://files.pythonhosted.org/packages/b5/4e/ebfc6944b96317df8b537ae875d2e57c27b84eb98820bc0a1055f358f056/coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", size = 236038, upload-time = "2024-10-20T22:56:29.816Z" }, - { url = "https://files.pythonhosted.org/packages/13/f2/3a0bf1841a97c0654905e2ef531170f02c89fad2555879db8fe41a097871/coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", size = 236812, upload-time = "2024-10-20T22:56:31.654Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9c/66bf59226b52ce6ed9541b02d33e80a6e816a832558fbdc1111a7bd3abd4/coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", size = 209400, upload-time = "2024-10-20T22:56:33.569Z" }, - { url = "https://files.pythonhosted.org/packages/2a/a0/b0790934c04dfc8d658d4a62acb8f7ca0efdf3818456fcad757b11c6479d/coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", size = 210243, upload-time = "2024-10-20T22:56:34.863Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013, upload-time = "2024-10-20T22:56:36.034Z" }, - { url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251, upload-time = "2024-10-20T22:56:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268, upload-time = "2024-10-20T22:56:40.051Z" }, - { url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298, upload-time = "2024-10-20T22:56:41.929Z" }, - { url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367, upload-time = "2024-10-20T22:56:43.141Z" }, - { url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853, upload-time = "2024-10-20T22:56:44.33Z" }, - { url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160, upload-time = "2024-10-20T22:56:46.258Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824, upload-time = "2024-10-20T22:56:48.666Z" }, - { url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639, upload-time = "2024-10-20T22:56:50.664Z" }, - { url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428, upload-time = "2024-10-20T22:56:52.468Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039, upload-time = "2024-10-20T22:56:53.656Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298, upload-time = "2024-10-20T22:56:54.979Z" }, - { url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813, upload-time = "2024-10-20T22:56:56.209Z" }, - { url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959, upload-time = "2024-10-20T22:56:58.06Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950, upload-time = "2024-10-20T22:56:59.329Z" }, - { url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610, upload-time = "2024-10-20T22:57:00.645Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697, upload-time = "2024-10-20T22:57:01.944Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541, upload-time = "2024-10-20T22:57:03.848Z" }, - { url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707, upload-time = "2024-10-20T22:57:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439, upload-time = "2024-10-20T22:57:06.35Z" }, - { url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784, upload-time = "2024-10-20T22:57:07.857Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058, upload-time = "2024-10-20T22:57:09.845Z" }, - { url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772, upload-time = "2024-10-20T22:57:11.147Z" }, - { url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490, upload-time = "2024-10-20T22:57:13.02Z" }, - { url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848, upload-time = "2024-10-20T22:57:14.927Z" }, - { url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340, upload-time = "2024-10-20T22:57:16.246Z" }, - { url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229, upload-time = "2024-10-20T22:57:17.546Z" }, - { url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510, upload-time = "2024-10-20T22:57:18.925Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353, upload-time = "2024-10-20T22:57:20.891Z" }, - { url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502, upload-time = "2024-10-20T22:57:22.21Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - -[[package]] -name = "cycler" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, -] - -[[package]] -name = "cython" -version = "3.0.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/09/ffb61f29b8e3d207c444032b21328327d753e274ea081bc74e009827cc81/Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6", size = 2744096, upload-time = "2024-01-10T11:01:02.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/a7/f4a0bc9a80e23b380daa2ebb4879bf434aaa0b3b91f7ad8a7f9762b4bd1b/Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba", size = 3113615, upload-time = "2024-01-10T11:34:05.899Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/e9295df74246c165b91253a473bfa179debf739c9bee961cbb3ae56c2b79/Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3", size = 3436320, upload-time = "2024-01-10T11:02:08.689Z" }, - { url = "https://files.pythonhosted.org/packages/26/2c/6a887c957aa53e44f928119dea628a5dfacc8e875424034f5fecac9daba4/Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee", size = 3591755, upload-time = "2024-01-10T11:02:11.773Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b8/f9c97bae6281da50b3ecb1f7fef0f7f7851eae084609b364717a2b366bf1/Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1", size = 3636099, upload-time = "2024-01-10T11:02:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/17/ae/cd055c2c081c67a6fcad1d8d17d82bd6395b14c6741e3a938f40318c8bc5/Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b", size = 3458119, upload-time = "2024-01-10T11:02:19.103Z" }, - { url = "https://files.pythonhosted.org/packages/72/ab/ac6f5548d6194f4bb2fc8c6c996aa7369f0fa1403e4d4de787d9e9309b27/Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795", size = 3614418, upload-time = "2024-01-10T11:02:22.732Z" }, - { url = "https://files.pythonhosted.org/packages/70/e2/3e3e448b7a94887bec3235bcb71957b6681dc42b4536459f8f54d46fa936/Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871", size = 2572819, upload-time = "2024-01-10T11:02:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/85/7d/58635941dfbb5b4e197adb88080b9cbfb230dc3b75683698a530a1989bdb/Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02", size = 2784167, upload-time = "2024-01-10T11:02:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8e/28f8c6109990eef7317ab7e43644092b49a88a39f9373dcd19318946df09/Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f", size = 3135638, upload-time = "2024-01-10T11:34:22.889Z" }, - { url = "https://files.pythonhosted.org/packages/83/1f/4720cb682b8ed1ab9749dea35351a66dd29b6a022628cce038415660c384/Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419", size = 3340052, upload-time = "2024-01-10T11:02:32.471Z" }, - { url = "https://files.pythonhosted.org/packages/8a/47/ec3fceb9e8f7d6fa130216b8740038e1df7c8e5f215bba363fcf1272a6c1/Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6", size = 3510079, upload-time = "2024-01-10T11:02:35.312Z" }, - { url = "https://files.pythonhosted.org/packages/71/31/b458127851e248effb909e2791b55870914863cde7c60b94db5ee65d7867/Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717", size = 3573972, upload-time = "2024-01-10T11:02:39.044Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d5/ca6513844d0634abd05ba12304053a454bb70441a9520afa9897d4300156/Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658", size = 3356158, upload-time = "2024-01-10T11:02:42.125Z" }, - { url = "https://files.pythonhosted.org/packages/33/59/98a87b6264f4ad45c820db13c4ec657567476efde020c49443cc842a86af/Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388", size = 3522312, upload-time = "2024-01-10T11:02:45.056Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cb/132115d07a0b9d4f075e0741db70a5416b424dcd875b2bb0dd805e818222/Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c", size = 2602579, upload-time = "2024-01-10T11:02:48.368Z" }, - { url = "https://files.pythonhosted.org/packages/b4/69/cb4620287cd9ef461103e122c0a2ae7f7ecf183e02510676fb5a15c95b05/Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a", size = 2791268, upload-time = "2024-01-10T11:02:51.483Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7f/f584f5d15323feb897d42ef0e9d910649e2150d7a30cf7e7a8cc1d236e6f/Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6", size = 1168213, upload-time = "2024-01-10T11:00:56.857Z" }, -] - -[[package]] -name = "easydict" -version = "1.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd622d423c0ec8b68b78c1704b3f1545326f6ce05c75c/easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78", size = 6644, upload-time = "2023-10-23T23:01:37.686Z" } - -[[package]] -name = "fastapi" -version = "0.127.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/8a/6b9ba6eb8ff3817caae83120495965d9e70afb4d6348cb120e464ee199f4/fastapi-0.127.1.tar.gz", hash = "sha256:946a87ee5d931883b562b6bada787d6c8178becee2683cb3f9b980d593206359", size = 391876, upload-time = "2025-12-26T13:04:47.075Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/f3/a6858d147ed2645c095d11dc2440f94a5f1cd8f4df888e3377e6b5281a0f/fastapi-0.127.1-py3-none-any.whl", hash = "sha256:31d670a4f9373cc6d7994420f98e4dc46ea693145207abc39696746c83a44430", size = 112332, upload-time = "2025-12-26T13:04:45.329Z" }, -] - -[[package]] -name = "filelock" -version = "3.20.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, -] - -[[package]] -name = "flask" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "blinker" }, - { name = "click" }, - { name = "itsdangerous" }, - { name = "jinja2" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/09/c1a7354d3925a3c6c8cfdebf4245bae67d633ffda1ba415add06ffc839c5/flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58", size = 674171, upload-time = "2023-09-30T14:36:12.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/42/015c23096649b908c809c69388a805a571a3bea44362fe87e33fc3afa01f/flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638", size = 99724, upload-time = "2023-09-30T14:36:10.961Z" }, -] - -[[package]] -name = "flask-cors" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flask" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/6a/a8d56d60bcfa1ec3e4fdad81b45aafd508c3bd5c244a16526fa29139d7d4/flask_cors-4.0.1.tar.gz", hash = "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4", size = 30306, upload-time = "2024-05-04T19:49:43.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/52/2aa6285f104616f73ee1ad7905a16b2b35af0143034ad0cf7b64bcba715c/Flask_Cors-4.0.1-py2.py3-none-any.whl", hash = "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677", size = 14290, upload-time = "2024-05-04T19:49:41.721Z" }, -] - -[[package]] -name = "flask-login" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flask" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834, upload-time = "2023-10-30T14:53:21.151Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303, upload-time = "2023-10-30T14:53:19.636Z" }, -] - -[[package]] -name = "flatbuffers" -version = "23.5.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/6e/3e52cd294d8e7a61e010973cce076a0cb2c6c0dfd4d0b7a13648c1b98329/flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89", size = 22114, upload-time = "2023-05-26T17:35:16.034Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/12/d5c79ee252793ffe845d58a913197bfa02ae9a0b5c9bc3dc4b58d477b9e7/flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1", size = 26744, upload-time = "2023-05-26T17:35:14.269Z" }, -] - -[[package]] -name = "fonttools" -version = "4.61.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, - { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, - { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, - { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, -] - -[[package]] -name = "fsspec" -version = "2023.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/08/cac914ff6ff46c4500fc4323a939dbe7a0f528cca04e7fd3e859611dea41/fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb", size = 167507, upload-time = "2023-12-11T21:19:54.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" }, -] - -[[package]] -name = "ftfy" -version = "6.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, -] - -[[package]] -name = "gevent" -version = "24.10.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, - { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, - { name = "zope-event" }, - { name = "zope-interface" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/70/f0/be10ed5d7721ed2317d7feb59e167603217156c2a6d57f128523e24e673d/gevent-24.10.3.tar.gz", hash = "sha256:aa7ee1bd5cabb2b7ef35105f863b386c8d5e332f754b60cfc354148bd70d35d1", size = 6108837, upload-time = "2024-10-18T16:06:25.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/01/1be5cf013826d8baae235976d6a94f3628014fd2db7c071aeec13f82b4d1/gevent-24.10.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:68c3a0d8402755eba7f69022e42e8021192a721ca8341908acc222ea597029b6", size = 2966909, upload-time = "2024-10-18T15:37:31.43Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3e/7fa9ab023f24d8689e2c77951981f8ea1f25089e0349a0bf8b35ee9b9277/gevent-24.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d850a453d66336272be4f1d3a8126777f3efdaea62d053b4829857f91e09755", size = 4913247, upload-time = "2024-10-18T16:19:41.792Z" }, - { url = "https://files.pythonhosted.org/packages/db/63/6e40eaaa3c2abd1561faff11dc3e6781f8c25e975354b8835762834415af/gevent-24.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e58ee3723f1fbe07d66892f1caa7481c306f653a6829b6fd16cb23d618a5915", size = 5049036, upload-time = "2024-10-18T16:18:47.419Z" }, - { url = "https://files.pythonhosted.org/packages/94/89/158bc32cdc898dda0481040ac18650022e73133d93460c5af56ca622fe9a/gevent-24.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b52382124eca13135a3abe4f65c6bd428656975980a48e51b17aeab68bdb14db", size = 5107299, upload-time = "2024-10-18T16:23:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/1abe62ee350fdfac186d33f615d0d3a0b3b140e7ccf23c73547aa0deec44/gevent-24.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ca2266e08f43c0e22c028801dff7d92a0b102ef20e4caeb6a46abfb95f6a328", size = 6819625, upload-time = "2024-10-18T15:59:38.226Z" }, - { url = "https://files.pythonhosted.org/packages/92/8b/0b2fe0d36b7c4d463e46cc68eaf6c14488bd7d86cc37e995c64a0ff7d02f/gevent-24.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d758f0d4dbf32502ec87bb9b536ca8055090a16f8305f0ada3ce6f34e70f2fd7", size = 5474079, upload-time = "2024-10-18T16:38:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/12/7b/9f5abbf0021a50321314f850697e0f46d2e5081168223af2d8544af9d19f/gevent-24.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0de6eb3d55c03138fda567d9bfed28487ce5d0928c5107549767a93efdf2be26", size = 6901323, upload-time = "2024-10-18T16:02:50.066Z" }, - { url = "https://files.pythonhosted.org/packages/8a/63/607715c621ae78ed581b7ba36d076df63feeb352993d521327f865056771/gevent-24.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:385710355eadecdb70428a5ae3e7e5a45dcf888baa1426884588be9d25ac4290", size = 1549468, upload-time = "2024-10-18T16:01:30.331Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e4/4edbe17001bb3e6fade4ad2d85ca8f9e4eabcbde4aa29aa6889281616e3e/gevent-24.10.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ad8fb70aa0ebc935729c9699ac31b210a49b689a7b27b7ac9f91676475f3f53", size = 2970952, upload-time = "2024-10-18T15:37:31.389Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a6/ce0824fe9398ba6b00028a74840f12be1165d5feaacdc028ea953db3d6c3/gevent-24.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18689f7a70d2ed0e75bad5036ec3c89690a493d4cfac8d7cdb258ac04b132bd", size = 5172230, upload-time = "2024-10-18T16:19:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/25/d4/9002cfb585bfa52c860ed4b1349d1a6400bdf2df9f1bd21df5ff33eea33c/gevent-24.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f4f171d4d2018170454d84c934842e1b5f6ce7468ba298f6e7f7cff15000a3", size = 5338394, upload-time = "2024-10-18T16:18:49.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/98/222f1a14f22ad2d1cbcc37edb74095264c1f9c7ab49e6423693383462b8a/gevent-24.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7021e26d70189b33c27173d4173f27bf4685d6b6f1c0ea50e5335f8491cb110c", size = 5437989, upload-time = "2024-10-18T16:23:13.851Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e8/cbb46afea3c7ecdc7289e15cb4a6f89903f4f9754a27ca320d3e465abc78/gevent-24.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34aea15f9c79f27a8faeaa361bc1e72c773a9b54a1996a2ec4eefc8bcd59a824", size = 6838539, upload-time = "2024-10-18T15:59:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/69/c3/e43e348f23da404a6d4368a14453ed097cdfca97d5212eaceb987d04a0e1/gevent-24.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8af65a4d4feaec6042c666d22c322a310fba3b47e841ad52f724b9c3ce5da48e", size = 5513842, upload-time = "2024-10-18T16:38:29.538Z" }, - { url = "https://files.pythonhosted.org/packages/c2/76/84b7c19c072a80900118717a85236859127d630cdf8b079fe42f19649f12/gevent-24.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:89c4115e3f5ada55f92b61701a46043fe42f702b5af863b029e4c1a76f6cc2d4", size = 6927374, upload-time = "2024-10-18T16:02:51.669Z" }, - { url = "https://files.pythonhosted.org/packages/5e/69/0ab1b04c363547058fb5035275c144957b80b36cb6aee715fe6181b0cee9/gevent-24.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:1ce6dab94c0b0d24425ba55712de2f8c9cb21267150ca63f5bb3a0e1f165da99", size = 1546701, upload-time = "2024-10-18T15:54:53.562Z" }, - { url = "https://files.pythonhosted.org/packages/f7/2d/c783583d7999cd2f2e7aa2d6a1c333d663003ca61255a89ff6a891be95f4/gevent-24.10.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f147e38423fbe96e8731f60a63475b3d2cab2f3d10578d8ee9d10c507c58a2ff", size = 2962857, upload-time = "2024-10-18T15:37:33.098Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/d3ce96fd49406f61976e9a3b6c742b97bb274d3b30c68ff190c5b5f81afd/gevent-24.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e6984ec96fc95fd67488555c38ece3015be1f38b1bcceb27b7d6c36b343008", size = 5141676, upload-time = "2024-10-18T16:19:45.484Z" }, - { url = "https://files.pythonhosted.org/packages/49/f4/f99f893770c316b9d2f03bd684947126cbed0321b89fe5423838974c2025/gevent-24.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:051b22e2758accfddb0457728bfc9abf8c3f2ce6bca43f1ff6e07b5ed9e49bf4", size = 5310248, upload-time = "2024-10-18T16:18:51.175Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0c/67257ba906f76ed82e8f0bd8c00c2a0687b360a1050b70db7e58dff749ab/gevent-24.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5edb6433764119a664bbb148d2aea9990950aa89cc3498f475c2408d523ea3", size = 5407304, upload-time = "2024-10-18T16:23:15.348Z" }, - { url = "https://files.pythonhosted.org/packages/35/6c/3a72da7c224b0111728130c0f1abc3ee07feff91b37e0ea83db98f4a3eaf/gevent-24.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce417bcaaab496bc9c77f75566531e9d93816262037b8b2dbb88b0fdcd66587c", size = 6818624, upload-time = "2024-10-18T15:59:42.068Z" }, - { url = "https://files.pythonhosted.org/packages/a3/96/cc5f6ecba032a45fc312fe0db2908a893057fd81361eea93845d6c325556/gevent-24.10.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:1c3a828b033fb02b7c31da4d75014a1f82e6c072fc0523456569a57f8b025861", size = 5484356, upload-time = "2024-10-18T16:38:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/7c/97/e680b2b2f0c291ae4db9813ffbf02c22c2a0f14c8f1a613971385e29ef67/gevent-24.10.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f2ae3efbbd120cdf4a68b7abc27a37e61e6f443c5a06ec2c6ad94c37cd8471ec", size = 6903191, upload-time = "2024-10-18T16:02:53.888Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1c/b4181957da062d1c060974ec6cb798cc24aeeb28e8cd2ece84eb4b4991f7/gevent-24.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:9e1210334a9bc9f76c3d008e0785ca62214f8a54e1325f6c2ecab3b6a572a015", size = 1545117, upload-time = "2024-10-18T15:45:47.375Z" }, -] - -[[package]] -name = "geventhttpclient" -version = "2.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "brotli" }, - { name = "certifi" }, - { name = "gevent" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8c/14/d4eddae757de44985718a9e38d9e6f2a923d764ed97d0f1cbc1a8aa2b0ef/geventhttpclient-2.3.1.tar.gz", hash = "sha256:b40ddac8517c456818942c7812f555f84702105c82783238c9fcb8dc12675185", size = 69345, upload-time = "2024-04-18T21:39:50.83Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/ad/1fcbbea0465f04d4425960e3737d4d8ae6407043cfc88688fb17b9064160/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0ae055b9ce1704f2ce72c0847df28f4e14dbb3eea79256cda6c909d82688ea3", size = 71733, upload-time = "2024-04-18T21:38:30.357Z" }, - { url = "https://files.pythonhosted.org/packages/06/1a/10e547adb675beea407ff7117ecb4e5063534569ac14bb4360279d2888dd/geventhttpclient-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f087af2ac439495b5388841d6f3c4de8d2573ca9870593d78f7b554aa5cfa7f5", size = 52060, upload-time = "2024-04-18T21:38:32.561Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c0/9960ac6e8818a00702743cd2a9637d6f26909ac7ac59ca231f446e367b20/geventhttpclient-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76c367d175810facfe56281e516c9a5a4a191eff76641faaa30aa33882ed4b2f", size = 51649, upload-time = "2024-04-18T21:38:34.265Z" }, - { url = "https://files.pythonhosted.org/packages/58/3a/b032cd8f885dafdfa002a8a0e4e21b633713798ec08e19010b815fbfead6/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a58376d0d461fe0322ff2ad362553b437daee1eeb92b4c0e3b1ffef9e77defbe", size = 117987, upload-time = "2024-04-18T21:38:37.661Z" }, - { url = "https://files.pythonhosted.org/packages/94/36/6493a5cbc20c269a51186946947f3ca2eae687e05831289891027bd038c3/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f440cc704f8a9869848a109b2c401805c17c070539b2014e7b884ecfc8591e33", size = 123356, upload-time = "2024-04-18T21:38:39.705Z" }, - { url = "https://files.pythonhosted.org/packages/2f/07/b66d9a13b97a7e59d84b4faf704113aa963aaf3a0f71c9138c8740d57d5c/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f10c62994f9052f23948c19de930b2d1f063240462c8bd7077c2b3290e61f4fa", size = 114460, upload-time = "2024-04-18T21:38:40.947Z" }, - { url = "https://files.pythonhosted.org/packages/4e/72/1467b9e1ef63aecfe3b42333fb7607f66129dffaeca231f97e4be6f71803/geventhttpclient-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c45d9f3dd9627844c12e9ca347258c7be585bed54046336220e25ea6eac155", size = 112808, upload-time = "2024-04-18T21:38:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ef/64894efd67cb3459074c734736ecacff398cd841a5538dc70e3e77d35500/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:77c1a2c6e3854bf87cd5588b95174640c8a881716bd07fa0d131d082270a6795", size = 122049, upload-time = "2024-04-18T21:38:44.184Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c8/1b13b4ea4bb88d7c2db56d070a52daf4757b3139afd83885e81455cb422f/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ce649d4e25c2d56023471df0bf1e8e2ab67dfe4ff12ce3e8fe7e6fae30cd672a", size = 118755, upload-time = "2024-04-18T21:38:45.654Z" }, - { url = "https://files.pythonhosted.org/packages/d1/06/95ac63fa1ee118a4d5824aa0a6b0dc3a2934a2f4ce695bf6747e1744d813/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:265d9f31b4ac8f688eebef0bd4c814ffb37a16f769ad0c8c8b8c24a84db8eab5", size = 128053, upload-time = "2024-04-18T21:38:47.247Z" }, - { url = "https://files.pythonhosted.org/packages/8a/27/3d6dbbd128e1b965bae198bffa4b5552cd635397e3d2bbcc7d9592890ca9/geventhttpclient-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2de436a9d61dae877e4e811fb3e2594e2a1df1b18f4280878f318aef48a562b9", size = 117316, upload-time = "2024-04-18T21:38:49.086Z" }, - { url = "https://files.pythonhosted.org/packages/ed/9a/8b65daf417ff982fa1928ebc6ebdfb081750d426f877f0056288aaa689e8/geventhttpclient-2.3.1-cp311-cp311-win32.whl", hash = "sha256:83e22178b9480b0a95edf0053d4f30b717d0b696b3c262beabe6964d9c5224b1", size = 47598, upload-time = "2024-04-18T21:38:50.919Z" }, - { url = "https://files.pythonhosted.org/packages/ab/83/ed0d14787861cf30beddd3aadc29ad07d75555de43c629ba514ddd2978d0/geventhttpclient-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b072a282233384c1302a7dee88ad8bfedc916f06b1bc1da54f84980f1406a9", size = 48301, upload-time = "2024-04-18T21:38:52.14Z" }, - { url = "https://files.pythonhosted.org/packages/82/ee/bf3d26170a518d2b1254f44202f2fa4490496b476ee24046ff6c34e79c08/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e1c90abcc2735cd8dd2d2572a13da32f6625392dc04862decb5c6476a3ddee22", size = 71742, upload-time = "2024-04-18T21:38:54.167Z" }, - { url = "https://files.pythonhosted.org/packages/77/72/bd64b2a491094a3fbf7f3c314bb3c3918afb652783a8a9db07b86072da35/geventhttpclient-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5deb41c2f51247b4e568c14964f59d7b8e537eff51900564c88af3200004e678", size = 52070, upload-time = "2024-04-18T21:38:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/85/96/e25becfde16c5551ba04ed2beac1f018e2efc70275ec19ae3765ff634ff2/geventhttpclient-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f1a56a66a90c4beae2f009b5e9d42db9a58ced165aa35441ace04d69cb7b37", size = 51650, upload-time = "2024-04-18T21:38:57.022Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b8/fe6e938a369b3742103d04e5771e1ec7b18c047ac30b06a8e9704e2d34fc/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ee6e741849c29e3129b1ec3828ac3a5e5dcb043402f852ea92c52334fb8cabf", size = 118507, upload-time = "2024-04-18T21:38:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/68/0b/381d01de049b02dc70addbcc1c8e24d15500bff6a9e89103c4aa8eb352c3/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d0972096a63b1ddaa73fa3dab2c7a136e3ab8bf7999a2f85a5dee851fa77cdd", size = 124061, upload-time = "2024-04-18T21:39:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e6/7c97b5bf41cc403b2936a0887a85550b3153aa4b60c0c5062c49cd6286f2/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00675ba682fb7d19d659c14686fa8a52a65e3f301b56c2a4ee6333b380dd9467", size = 115060, upload-time = "2024-04-18T21:39:02.323Z" }, - { url = "https://files.pythonhosted.org/packages/45/1f/3e02464449c74a8146f27218471578c1dfabf18731cf047520b76e1b6331/geventhttpclient-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea77b67c186df90473416f4403839728f70ef6cf1689cec97b4f6bbde392a8a8", size = 113762, upload-time = "2024-04-18T21:39:03.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a4/08551776f7d6b219d6f73ca25be88806007b16af51a1dbfed7192528e1c3/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ddcc3f0fdffd9a3801e1005b73026202cffed8199863fdef9315bea9a860a032", size = 122018, upload-time = "2024-04-18T21:39:05.781Z" }, - { url = "https://files.pythonhosted.org/packages/70/14/ba91417ac7cbce8d553f72c885a19c6b9d7f9dc7de81b7814551cf020a57/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c9f1ef4ec048563cc621a47ff01a4f10048ff8b676d7a4d75e5433ed8e703e56", size = 118884, upload-time = "2024-04-18T21:39:08.001Z" }, - { url = "https://files.pythonhosted.org/packages/7c/78/e1f2c30e11bda8347a74b3a7254f727ff53ea260244da77d76b96779a006/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:a364b30bec7a0a00dbe256e2b6807e4dc866bead7ac84aaa51ca5e2c3d15c258", size = 128224, upload-time = "2024-04-18T21:39:09.31Z" }, - { url = "https://files.pythonhosted.org/packages/ac/2f/b7fd96e9cfa9d9719b0c9feb50b4cbb341d1940e34fd3305006efa8c3e33/geventhttpclient-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:25d255383d3d6a6fbd643bb51ae1a7e4f6f7b0dbd5f3225b537d0bd0432eaf39", size = 117758, upload-time = "2024-04-18T21:39:11.287Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e0/1384c9a76379ab257b75df92283797861dcae592dd98e471df254f87c635/geventhttpclient-2.3.1-cp312-cp312-win32.whl", hash = "sha256:ad0b507e354d2f398186dcb12fe526d0594e7c9387b514fb843f7a14fdf1729a", size = 47595, upload-time = "2024-04-18T21:39:12.535Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/6b8dbb24e3941e20abbe7736e59290c5d4182057ea1d984d46c853208bcd/geventhttpclient-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:7924e0883bc2b177cfe27aa65af6bb9dd57f3e26905c7675a2d1f3ef69df7cca", size = 48271, upload-time = "2024-04-18T21:39:14.479Z" }, -] - -[[package]] -name = "greenlet" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022, upload-time = "2024-09-20T18:21:04.506Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/62/1c2665558618553c42922ed47a4e6d6527e2fa3516a8256c2f431c5d0441/greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", size = 272479, upload-time = "2024-09-20T17:07:22.332Z" }, - { url = "https://files.pythonhosted.org/packages/76/9d/421e2d5f07285b6e4e3a676b016ca781f63cfe4a0cd8eaecf3fd6f7a71ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", size = 640404, upload-time = "2024-09-20T17:36:45.588Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/6e05f5c59262a584e502dd3d261bbdd2c97ab5416cc9c0b91ea38932a901/greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", size = 652813, upload-time = "2024-09-20T17:39:19.052Z" }, - { url = "https://files.pythonhosted.org/packages/49/93/d5f93c84241acdea15a8fd329362c2c71c79e1a507c3f142a5d67ea435ae/greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", size = 648517, upload-time = "2024-09-20T17:44:24.101Z" }, - { url = "https://files.pythonhosted.org/packages/15/85/72f77fc02d00470c86a5c982b8daafdf65d38aefbbe441cebff3bf7037fc/greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", size = 647831, upload-time = "2024-09-20T17:08:40.577Z" }, - { url = "https://files.pythonhosted.org/packages/f7/4b/1c9695aa24f808e156c8f4813f685d975ca73c000c2a5056c514c64980f6/greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", size = 602413, upload-time = "2024-09-20T17:08:31.728Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ad6e5b31ef330f03b12559d19fda2606a522d3849cde46b24f223d6d1619/greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", size = 1129619, upload-time = "2024-09-20T17:44:14.222Z" }, - { url = "https://files.pythonhosted.org/packages/f4/fb/201e1b932e584066e0f0658b538e73c459b34d44b4bd4034f682423bc801/greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", size = 1155198, upload-time = "2024-09-20T17:09:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/b9ed5e310bb8b89661b80cbcd4db5a067903bbcd7fc854923f5ebb4144f0/greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", size = 298930, upload-time = "2024-09-20T17:25:18.656Z" }, - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260, upload-time = "2024-09-20T17:08:07.301Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064, upload-time = "2024-09-20T17:36:47.628Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420, upload-time = "2024-09-20T17:39:21.258Z" }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035, upload-time = "2024-09-20T17:44:26.501Z" }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105, upload-time = "2024-09-20T17:08:42.048Z" }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077, upload-time = "2024-09-20T17:08:33.707Z" }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975, upload-time = "2024-09-20T17:44:15.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955, upload-time = "2024-09-20T17:09:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655, upload-time = "2024-09-20T17:21:22.427Z" }, - { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990, upload-time = "2024-09-20T17:08:26.312Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175, upload-time = "2024-09-20T17:36:48.983Z" }, - { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425, upload-time = "2024-09-20T17:39:22.705Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736, upload-time = "2024-09-20T17:44:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347, upload-time = "2024-09-20T17:08:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583, upload-time = "2024-09-20T17:08:36.85Z" }, - { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039, upload-time = "2024-09-20T17:44:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716, upload-time = "2024-09-20T17:09:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490, upload-time = "2024-09-20T17:17:09.501Z" }, - { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731, upload-time = "2024-09-20T17:36:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304, upload-time = "2024-09-20T17:39:24.55Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537, upload-time = "2024-09-20T17:44:31.102Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506, upload-time = "2024-09-20T17:08:47.852Z" }, - { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753, upload-time = "2024-09-20T17:08:38.079Z" }, - { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731, upload-time = "2024-09-20T17:44:20.556Z" }, - { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112, upload-time = "2024-09-20T17:09:28.753Z" }, -] - -[[package]] -name = "gunicorn" -version = "23.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "hf-xet" -version = "1.1.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" }, - { url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" }, - { url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" }, - { url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" }, - { url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httptools" -version = "0.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "huggingface-hub" -version = "0.34.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, -] - -[[package]] -name = "humanfriendly" -version = "10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "imageio" -version = "2.33.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pillow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/38/f4c568318c656352d211eec6954460dc3af0b7583a6682308f8a66e4c19b/imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce", size = 387374, upload-time = "2023-12-11T02:26:44.715Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/69/3aaa69cb0748e33e644fda114c9abd3186ce369edd4fca11107e9f39c6a7/imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d", size = 313345, upload-time = "2023-12-11T02:26:42.724Z" }, -] - -[[package]] -name = "immich-ml" -version = "2.5.5" -source = { editable = "." } -dependencies = [ - { name = "aiocache" }, - { name = "fastapi" }, - { name = "ftfy" }, - { name = "gunicorn" }, - { name = "huggingface-hub" }, - { name = "insightface" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "orjson" }, - { name = "pillow" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "python-multipart" }, - { name = "rapidocr" }, - { name = "rich" }, - { name = "tokenizers" }, - { name = "uvicorn", extra = ["standard"] }, -] - -[package.optional-dependencies] -armnn = [ - { name = "onnxruntime" }, -] -cpu = [ - { name = "onnxruntime" }, -] -cuda = [ - { name = "onnxruntime-gpu" }, -] -openvino = [ - { name = "onnxruntime-openvino" }, -] -rknn = [ - { name = "onnxruntime" }, - { name = "rknn-toolkit-lite2" }, -] - -[package.dev-dependencies] -dev = [ - { name = "httpx" }, - { name = "locust" }, - { name = "mypy" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-cov" }, - { name = "pytest-mock" }, - { name = "ruff" }, - { name = "types-pyyaml" }, - { name = "types-requests" }, - { name = "types-setuptools" }, - { name = "types-simplejson" }, - { name = "types-ujson" }, -] -lint = [ - { name = "mypy" }, - { name = "ruff" }, - { name = "types-pyyaml" }, - { name = "types-requests" }, - { name = "types-setuptools" }, - { name = "types-simplejson" }, - { name = "types-ujson" }, -] -test = [ - { name = "httpx" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-cov" }, - { name = "pytest-mock" }, -] -types = [ - { name = "types-pyyaml" }, - { name = "types-requests" }, - { name = "types-setuptools" }, - { name = "types-simplejson" }, - { name = "types-ujson" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiocache", specifier = ">=0.12.1,<1.0" }, - { name = "fastapi", specifier = ">=0.95.2,<1.0" }, - { name = "ftfy", specifier = ">=6.1.1" }, - { name = "gunicorn", specifier = ">=21.1.0" }, - { name = "huggingface-hub", specifier = ">=0.20.1,<1.0" }, - { name = "insightface", specifier = ">=0.7.3,<1.0" }, - { name = "numpy", specifier = ">=2.3.4" }, - { name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" }, - { name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" }, - { name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" }, - { name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.23.2,<2" }, - { name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.23.0,<2" }, - { name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" }, - { name = "orjson", specifier = ">=3.9.5" }, - { name = "pillow", specifier = ">=9.5.0,<11.0" }, - { name = "pydantic", specifier = ">=2.0.0,<3" }, - { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, - { name = "python-multipart", specifier = ">=0.0.6,<1.0" }, - { name = "rapidocr", specifier = ">=3.1.0" }, - { name = "rich", specifier = ">=13.4.2" }, - { name = "rknn-toolkit-lite2", marker = "extra == 'rknn'", specifier = ">=2.3.0,<3" }, - { name = "tokenizers", specifier = ">=0.15.0,<1.0" }, - { name = "uvicorn", extras = ["standard"], specifier = ">=0.22.0,<1.0" }, -] -provides-extras = ["cpu", "cuda", "openvino", "armnn", "rknn", "rocm"] - -[package.metadata.requires-dev] -dev = [ - { name = "httpx", specifier = ">=0.24.1" }, - { name = "locust", specifier = ">=2.15.1" }, - { name = "mypy", specifier = ">=1.3.0" }, - { name = "pytest", specifier = ">=7.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.21.0" }, - { name = "pytest-cov", specifier = ">=4.1.0" }, - { name = "pytest-mock", specifier = ">=3.11.1" }, - { name = "ruff", specifier = ">=0.0.272" }, - { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, - { name = "types-requests", specifier = ">=2.32.0.20250306" }, - { name = "types-setuptools", specifier = ">=75.8.2.20250305" }, - { name = "types-simplejson", specifier = ">=3.20.0.20250218" }, - { name = "types-ujson", specifier = ">=5.10.0.20240515" }, -] -lint = [ - { name = "mypy", specifier = ">=1.3.0" }, - { name = "ruff", specifier = ">=0.0.272" }, - { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, - { name = "types-requests", specifier = ">=2.32.0.20250306" }, - { name = "types-setuptools", specifier = ">=75.8.2.20250305" }, - { name = "types-simplejson", specifier = ">=3.20.0.20250218" }, - { name = "types-ujson", specifier = ">=5.10.0.20240515" }, -] -test = [ - { name = "httpx", specifier = ">=0.24.1" }, - { name = "pytest", specifier = ">=7.3.1" }, - { name = "pytest-asyncio", specifier = ">=0.21.0" }, - { name = "pytest-cov", specifier = ">=4.1.0" }, - { name = "pytest-mock", specifier = ">=3.11.1" }, -] -types = [ - { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, - { name = "types-requests", specifier = ">=2.32.0.20250306" }, - { name = "types-setuptools", specifier = ">=75.8.2.20250305" }, - { name = "types-simplejson", specifier = ">=3.20.0.20250218" }, - { name = "types-ujson", specifier = ">=5.10.0.20240515" }, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, -] - -[[package]] -name = "insightface" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "albumentations" }, - { name = "cython" }, - { name = "easydict" }, - { name = "matplotlib" }, - { name = "numpy" }, - { name = "onnx" }, - { name = "pillow" }, - { name = "prettytable" }, - { name = "requests" }, - { name = "scikit-image" }, - { name = "scikit-learn" }, - { name = "scipy" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490, upload-time = "2023-04-02T08:01:54.541Z" } - -[[package]] -name = "itsdangerous" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a", size = 56143, upload-time = "2022-03-24T15:12:15.102Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", size = 15749, upload-time = "2022-03-24T15:12:13.2Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245, upload-time = "2024-05-05T23:42:02.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271, upload-time = "2024-05-05T23:41:59.928Z" }, -] - -[[package]] -name = "joblib" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720, upload-time = "2023-08-09T09:23:40.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207, upload-time = "2023-08-09T09:23:34.583Z" }, -] - -[[package]] -name = "kiwisolver" -version = "1.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552, upload-time = "2023-08-24T09:30:39.861Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403, upload-time = "2023-08-24T09:28:39.3Z" }, - { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156, upload-time = "2023-08-24T09:28:40.301Z" }, - { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166, upload-time = "2023-08-24T09:28:41.235Z" }, - { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300, upload-time = "2023-08-24T09:28:42.409Z" }, - { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579, upload-time = "2023-08-24T09:28:43.677Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360, upload-time = "2023-08-24T09:28:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091, upload-time = "2023-08-24T09:28:47.959Z" }, - { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259, upload-time = "2023-08-24T09:28:49.224Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516, upload-time = "2023-08-24T09:28:50.979Z" }, - { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228, upload-time = "2023-08-24T09:28:52.812Z" }, - { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716, upload-time = "2023-08-24T09:28:54.115Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871, upload-time = "2023-08-24T09:28:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265, upload-time = "2023-08-24T09:28:56.855Z" }, - { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649, upload-time = "2023-08-24T09:28:58.021Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116, upload-time = "2023-08-24T09:28:58.994Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484, upload-time = "2023-08-24T09:28:59.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332, upload-time = "2023-08-24T09:29:01.733Z" }, - { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987, upload-time = "2023-08-24T09:29:02.789Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613, upload-time = "2023-08-24T09:29:03.912Z" }, - { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183, upload-time = "2023-08-24T09:29:05.244Z" }, - { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248, upload-time = "2023-08-24T09:29:06.531Z" }, - { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815, upload-time = "2023-08-24T09:29:07.867Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042, upload-time = "2023-08-24T09:29:09.403Z" }, - { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159, upload-time = "2023-08-24T09:29:10.66Z" }, - { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694, upload-time = "2023-08-24T09:29:12.469Z" }, - { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579, upload-time = "2023-08-24T09:29:13.743Z" }, - { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168, upload-time = "2023-08-24T09:29:15.097Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464, upload-time = "2023-08-24T09:29:16.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473, upload-time = "2023-08-24T09:29:17.956Z" }, - { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004, upload-time = "2023-08-24T09:29:19.329Z" }, -] - -[[package]] -name = "lazy-loader" -version = "0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, -] - -[[package]] -name = "librt" -version = "0.7.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/64/44089b12d8b4714a7f0e2f33fb19285ba87702d4be0829f20b36ebeeee07/librt-0.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3485b9bb7dfa66167d5500ffdafdc35415b45f0da06c75eb7df131f3357b174a", size = 54709, upload-time = "2025-12-15T16:51:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/26/ef/6fa39fb5f37002f7d25e0da4f24d41b457582beea9369eeb7e9e73db5508/librt-0.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:188b4b1a770f7f95ea035d5bbb9d7367248fc9d12321deef78a269ebf46a5729", size = 56663, upload-time = "2025-12-15T16:51:17.856Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e4/cbaca170a13bee2469c90df9e47108610b4422c453aea1aec1779ac36c24/librt-0.7.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b668b1c840183e4e38ed5a99f62fac44c3a3eef16870f7f17cfdfb8b47550ed", size = 161703, upload-time = "2025-12-15T16:51:19.421Z" }, - { url = "https://files.pythonhosted.org/packages/d0/32/0b2296f9cc7e693ab0d0835e355863512e5eac90450c412777bd699c76ae/librt-0.7.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e8f864b521f6cfedb314d171630f827efee08f5c3462bcbc2244ab8e1768cd6", size = 171027, upload-time = "2025-12-15T16:51:20.721Z" }, - { url = "https://files.pythonhosted.org/packages/d8/33/c70b6d40f7342716e5f1353c8da92d9e32708a18cbfa44897a93ec2bf879/librt-0.7.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df7c9def4fc619a9c2ab402d73a0c5b53899abe090e0100323b13ccb5a3dd82", size = 184700, upload-time = "2025-12-15T16:51:22.272Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c8/555c405155da210e4c4113a879d378f54f850dbc7b794e847750a8fadd43/librt-0.7.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f79bc3595b6ed159a1bf0cdc70ed6ebec393a874565cab7088a219cca14da727", size = 180719, upload-time = "2025-12-15T16:51:23.561Z" }, - { url = "https://files.pythonhosted.org/packages/6b/88/34dc1f1461c5613d1b73f0ecafc5316cc50adcc1b334435985b752ed53e5/librt-0.7.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77772a4b8b5f77d47d883846928c36d730b6e612a6388c74cba33ad9eb149c11", size = 174535, upload-time = "2025-12-15T16:51:25.031Z" }, - { url = "https://files.pythonhosted.org/packages/b6/5a/f3fafe80a221626bcedfa9fe5abbf5f04070989d44782f579b2d5920d6d0/librt-0.7.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:064a286e6ab0b4c900e228ab4fa9cb3811b4b83d3e0cc5cd816b2d0f548cb61c", size = 195236, upload-time = "2025-12-15T16:51:26.328Z" }, - { url = "https://files.pythonhosted.org/packages/d8/77/5c048d471ce17f4c3a6e08419be19add4d291e2f7067b877437d482622ac/librt-0.7.4-cp311-cp311-win32.whl", hash = "sha256:42da201c47c77b6cc91fc17e0e2b330154428d35d6024f3278aa2683e7e2daf2", size = 42930, upload-time = "2025-12-15T16:51:27.853Z" }, - { url = "https://files.pythonhosted.org/packages/fb/3b/514a86305a12c3d9eac03e424b07cd312c7343a9f8a52719aa079590a552/librt-0.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:d31acb5886c16ae1711741f22504195af46edec8315fe69b77e477682a87a83e", size = 49240, upload-time = "2025-12-15T16:51:29.037Z" }, - { url = "https://files.pythonhosted.org/packages/ba/01/3b7b1914f565926b780a734fac6e9a4d2c7aefe41f4e89357d73697a9457/librt-0.7.4-cp311-cp311-win_arm64.whl", hash = "sha256:114722f35093da080a333b3834fff04ef43147577ed99dd4db574b03a5f7d170", size = 42613, upload-time = "2025-12-15T16:51:30.194Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e7/b805d868d21f425b7e76a0ea71a2700290f2266a4f3c8357fcf73efc36aa/librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92", size = 55688, upload-time = "2025-12-15T16:51:31.571Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/69a2b02e62a14cfd5bfd9f1e9adea294d5bcfeea219c7555730e5d068ee4/librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108", size = 57141, upload-time = "2025-12-15T16:51:32.714Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6b/05dba608aae1272b8ea5ff8ef12c47a4a099a04d1e00e28a94687261d403/librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94", size = 165322, upload-time = "2025-12-15T16:51:33.986Z" }, - { url = "https://files.pythonhosted.org/packages/8f/bc/199533d3fc04a4cda8d7776ee0d79955ab0c64c79ca079366fbc2617e680/librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab", size = 174216, upload-time = "2025-12-15T16:51:35.384Z" }, - { url = "https://files.pythonhosted.org/packages/62/ec/09239b912a45a8ed117cb4a6616d9ff508f5d3131bd84329bf2f8d6564f1/librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba", size = 189005, upload-time = "2025-12-15T16:51:36.687Z" }, - { url = "https://files.pythonhosted.org/packages/46/2e/e188313d54c02f5b0580dd31476bb4b0177514ff8d2be9f58d4a6dc3a7ba/librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9", size = 183960, upload-time = "2025-12-15T16:51:37.977Z" }, - { url = "https://files.pythonhosted.org/packages/eb/84/f1d568d254518463d879161d3737b784137d236075215e56c7c9be191cee/librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74", size = 177609, upload-time = "2025-12-15T16:51:40.584Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/060bbc1c002f0d757c33a1afe6bf6a565f947a04841139508fc7cef6c08b/librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f", size = 199269, upload-time = "2025-12-15T16:51:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/ff/7f/708f8f02d8012ee9f366c07ea6a92882f48bd06cc1ff16a35e13d0fbfb08/librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286", size = 43186, upload-time = "2025-12-15T16:51:43.149Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a5/4e051b061c8b2509be31b2c7ad4682090502c0a8b6406edcf8c6b4fe1ef7/librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20", size = 49455, upload-time = "2025-12-15T16:51:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d2/90d84e9f919224a3c1f393af1636d8638f54925fdc6cd5ee47f1548461e5/librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a", size = 42828, upload-time = "2025-12-15T16:51:45.498Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" }, - { url = "https://files.pythonhosted.org/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" }, - { url = "https://files.pythonhosted.org/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" }, - { url = "https://files.pythonhosted.org/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" }, - { url = "https://files.pythonhosted.org/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" }, - { url = "https://files.pythonhosted.org/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" }, - { url = "https://files.pythonhosted.org/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" }, - { url = "https://files.pythonhosted.org/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" }, - { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" }, - { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" }, - { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" }, - { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" }, - { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" }, - { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" }, - { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" }, - { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" }, - { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" }, - { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" }, - { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" }, - { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" }, - { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" }, - { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" }, - { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" }, - { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" }, - { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" }, -] - -[[package]] -name = "locust" -version = "2.42.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "configargparse" }, - { name = "flask" }, - { name = "flask-cors" }, - { name = "flask-login" }, - { name = "gevent" }, - { name = "geventhttpclient" }, - { name = "locust-cloud" }, - { name = "msgpack" }, - { name = "psutil" }, - { name = "pytest" }, - { name = "python-engineio" }, - { name = "python-socketio", extra = ["client"] }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "pyzmq" }, - { name = "requests" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, - { name = "werkzeug" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/19/dd816835679c80eba9c339a4bfcb6380fa8b059a5da45894ac80d73bc504/locust-2.42.6.tar.gz", hash = "sha256:fa603f4ac1c48b9ac56f4c34355944ebfd92590f4197b6d126ea216bd81cc036", size = 1418806, upload-time = "2025-11-29T17:40:10.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/4f/be2b7b87a4cea00d89adabeee5c61e8831c2af8a0eca3cbe931516f0e155/locust-2.42.6-py3-none-any.whl", hash = "sha256:2d02502489c8a2e959e2ca4b369c81bbd6b9b9e831d9422ab454541a3c2c6252", size = 1437376, upload-time = "2025-11-29T17:40:08.37Z" }, -] - -[[package]] -name = "locust-cloud" -version = "1.30.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "configargparse" }, - { name = "gevent" }, - { name = "platformdirs" }, - { name = "python-engineio" }, - { name = "python-socketio", extra = ["client"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/86/cd6b611f008387ffce5bcb6132ba7431aec7d1b09d8ce27e152e96d94315/locust_cloud-1.30.0.tar.gz", hash = "sha256:324ae23754d49816df96d3f7472357a61cd10e56cebcb26e2def836675cb3c68", size = 457297, upload-time = "2025-12-15T13:35:50.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/db/35c1cc8e01dfa570913255c55eb983a7e2e532060b4d1ee5f1fb543a6a0b/locust_cloud-1.30.0-py3-none-any.whl", hash = "sha256:2324b690efa1bfc8d1871340276953cf265328bd6333e07a5ba8ff7dc5e99e6c", size = 413446, upload-time = "2025-12-15T13:35:48.75Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, -] - -[[package]] -name = "markupsafe" -version = "2.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", size = 19132, upload-time = "2023-06-02T21:43:45.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/09/c31503cb8150cf688c1534a7135cc39bb9092f8e0e6369ec73494d16ee0e/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", size = 17862, upload-time = "2023-06-02T21:42:48.569Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/171f5ac6b065e1425e8fabf4a4dfbeca76fd8070072c6a41bd5c07d90d8b/MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", size = 13738, upload-time = "2023-06-02T21:42:49.727Z" }, - { url = "https://files.pythonhosted.org/packages/a2/f7/9175ad1b8152092f7c3b78c513c1bdfe9287e0564447d1c2d3d1a2471540/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", size = 28891, upload-time = "2023-06-02T21:42:51.33Z" }, - { url = "https://files.pythonhosted.org/packages/fe/21/2eff1de472ca6c99ec3993eab11308787b9879af9ca8bbceb4868cf4f2ca/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", size = 28096, upload-time = "2023-06-02T21:42:52.966Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a0/103f94793c3bf829a18d2415117334ece115aeca56f2df1c47fa02c6dbd6/MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", size = 27631, upload-time = "2023-06-02T21:42:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/43/70/f24470f33b2035b035ef0c0ffebf57006beb2272cf3df068fc5154e04ead/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", size = 33863, upload-time = "2023-06-02T21:42:55.777Z" }, - { url = "https://files.pythonhosted.org/packages/32/d4/ce98c4ca713d91c4a17c1a184785cc00b9e9c25699d618956c2b9999500a/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", size = 32591, upload-time = "2023-06-02T21:42:57.415Z" }, - { url = "https://files.pythonhosted.org/packages/bb/82/f88ccb3ca6204a4536cf7af5abdad7c3657adac06ab33699aa67279e0744/MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", size = 33186, upload-time = "2023-06-02T21:42:59.107Z" }, - { url = "https://files.pythonhosted.org/packages/44/53/93405d37bb04a10c43b1bdd6f548097478d494d7eadb4b364e3e1337f0cc/MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", size = 16537, upload-time = "2023-06-02T21:43:00.927Z" }, - { url = "https://files.pythonhosted.org/packages/be/bb/08b85bc194034efbf572e70c3951549c8eca0ada25363afc154386b5390a/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", size = 17089, upload-time = "2023-06-02T21:43:02.355Z" }, - { url = "https://files.pythonhosted.org/packages/89/5a/ee546f2aa73a1d6fcfa24272f356fe06d29acca81e76b8d32ca53e429a2e/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", size = 17849, upload-time = "2023-09-07T16:00:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/3a/72/9f683a059bde096776e8acf9aa34cbbba21ddc399861fe3953790d4f2cde/MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", size = 13700, upload-time = "2023-09-07T16:00:45.384Z" }, - { url = "https://files.pythonhosted.org/packages/9d/78/92f15eb9b1e8f1668a9787ba103cf6f8d19a9efed8150245404836145c24/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11", size = 29319, upload-time = "2023-09-07T16:00:46.48Z" }, - { url = "https://files.pythonhosted.org/packages/51/94/9a04085114ff2c24f7424dbc890a281d73c5a74ea935dc2e69c66a3bd558/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", size = 28314, upload-time = "2023-09-07T16:00:47.64Z" }, - { url = "https://files.pythonhosted.org/packages/ec/53/fcb3214bd370185e223b209ce6bb010fb887ea57173ca4f75bd211b24e10/MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", size = 27696, upload-time = "2023-09-07T16:00:48.92Z" }, - { url = "https://files.pythonhosted.org/packages/e7/33/54d29854716725d7826079b8984dd235fac76dab1c32321e555d493e61f5/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", size = 33746, upload-time = "2023-09-07T16:00:50.081Z" }, - { url = "https://files.pythonhosted.org/packages/11/40/ea7f85e2681d29bc9301c757257de561923924f24de1802d9c3baa396bb4/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", size = 32131, upload-time = "2023-09-07T16:00:51.822Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/bc770c37ecd58638c18f8ec85df205dacb818ccf933692082fd93010a4bc/MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", size = 32878, upload-time = "2023-09-07T16:00:53.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/bf95630aab0a9ed6a67556cd4e54f6aeb0e74f4cb0fd2f229154873a4be4/MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", size = 16426, upload-time = "2023-09-07T16:00:55.987Z" }, - { url = "https://files.pythonhosted.org/packages/44/44/dbaf65876e258facd65f586dde158387ab89963e7f2235551afc9c2e24c2/MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", size = 16979, upload-time = "2023-09-07T16:00:57.77Z" }, -] - -[[package]] -name = "matplotlib" -version = "3.10.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "contourpy" }, - { name = "cycler" }, - { name = "fonttools" }, - { name = "kiwisolver" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyparsing" }, - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044, upload-time = "2025-07-31T18:09:33.805Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/c7/1f2db90a1d43710478bb1e9b57b162852f79234d28e4f48a28cc415aa583/matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf", size = 8239216, upload-time = "2025-07-31T18:07:51.947Z" }, - { url = "https://files.pythonhosted.org/packages/82/6d/ca6844c77a4f89b1c9e4d481c412e1d1dbabf2aae2cbc5aa2da4a1d6683e/matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf", size = 8102130, upload-time = "2025-07-31T18:07:53.65Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1e/5e187a30cc673a3e384f3723e5f3c416033c1d8d5da414f82e4e731128ea/matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a", size = 8666471, upload-time = "2025-07-31T18:07:55.304Z" }, - { url = "https://files.pythonhosted.org/packages/03/c0/95540d584d7d645324db99a845ac194e915ef75011a0d5e19e1b5cee7e69/matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc", size = 9500518, upload-time = "2025-07-31T18:07:57.199Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2e/e019352099ea58b4169adb9c6e1a2ad0c568c6377c2b677ee1f06de2adc7/matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89", size = 9552372, upload-time = "2025-07-31T18:07:59.41Z" }, - { url = "https://files.pythonhosted.org/packages/b7/81/3200b792a5e8b354f31f4101ad7834743ad07b6d620259f2059317b25e4d/matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903", size = 8100634, upload-time = "2025-07-31T18:08:01.801Z" }, - { url = "https://files.pythonhosted.org/packages/52/46/a944f6f0c1f5476a0adfa501969d229ce5ae60cf9a663be0e70361381f89/matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420", size = 7978880, upload-time = "2025-07-31T18:08:03.407Z" }, - { url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056, upload-time = "2025-07-31T18:08:05.385Z" }, - { url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131, upload-time = "2025-07-31T18:08:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603, upload-time = "2025-07-31T18:08:09.064Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127, upload-time = "2025-07-31T18:08:10.845Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926, upload-time = "2025-07-31T18:08:13.186Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599, upload-time = "2025-07-31T18:08:15.116Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173, upload-time = "2025-07-31T18:08:21.518Z" }, - { url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586, upload-time = "2025-07-31T18:08:23.107Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715, upload-time = "2025-07-31T18:08:24.675Z" }, - { url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397, upload-time = "2025-07-31T18:08:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646, upload-time = "2025-07-31T18:08:28.848Z" }, - { url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424, upload-time = "2025-07-31T18:08:30.726Z" }, - { url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809, upload-time = "2025-07-31T18:08:33.928Z" }, - { url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078, upload-time = "2025-07-31T18:08:36.764Z" }, - { url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590, upload-time = "2025-07-31T18:08:38.521Z" }, - { url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518, upload-time = "2025-07-31T18:08:40.195Z" }, - { url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815, upload-time = "2025-07-31T18:08:42.238Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814, upload-time = "2025-07-31T18:08:44.914Z" }, - { url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917, upload-time = "2025-07-31T18:08:47.038Z" }, - { url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034, upload-time = "2025-07-31T18:08:48.943Z" }, - { url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337, upload-time = "2025-07-31T18:08:50.791Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591, upload-time = "2025-07-31T18:08:53.254Z" }, - { url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566, upload-time = "2025-07-31T18:08:55.116Z" }, - { url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281, upload-time = "2025-07-31T18:08:56.885Z" }, - { url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873, upload-time = "2025-07-31T18:08:59.241Z" }, - { url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954, upload-time = "2025-07-31T18:09:01.244Z" }, - { url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465, upload-time = "2025-07-31T18:09:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898, upload-time = "2025-07-31T18:09:05.231Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636, upload-time = "2025-07-31T18:09:07.306Z" }, - { url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575, upload-time = "2025-07-31T18:09:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815, upload-time = "2025-07-31T18:09:11.191Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514, upload-time = "2025-07-31T18:09:13.307Z" }, - { url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932, upload-time = "2025-07-31T18:09:15.335Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003, upload-time = "2025-07-31T18:09:17.416Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849, upload-time = "2025-07-31T18:09:19.673Z" }, - { url = "https://files.pythonhosted.org/packages/dc/d6/e921be4e1a5f7aca5194e1f016cb67ec294548e530013251f630713e456d/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f", size = 8233224, upload-time = "2025-07-31T18:09:27.512Z" }, - { url = "https://files.pythonhosted.org/packages/ec/74/a2b9b04824b9c349c8f1b2d21d5af43fa7010039427f2b133a034cb09e59/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b", size = 8098539, upload-time = "2025-07-31T18:09:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192, upload-time = "2025-07-31T18:09:31.407Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "ml-dtypes" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/f1/720cb1409b5d0c05cff9040c0e9fba73fa4c67897d33babf905d5d46a070/ml_dtypes-0.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a177b882667c69422402df6ed5c3428ce07ac2c1f844d8a1314944651439458", size = 667412, upload-time = "2025-07-29T18:38:25.275Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d5/05861ede5d299f6599f86e6bc1291714e2116d96df003cfe23cc54bcc568/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9849ce7267444c0a717c80c6900997de4f36e2815ce34ac560a3edb2d9a64cd2", size = 4964606, upload-time = "2025-07-29T18:38:27.045Z" }, - { url = "https://files.pythonhosted.org/packages/db/dc/72992b68de367741bfab8df3b3fe7c29f982b7279d341aa5bf3e7ef737ea/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f5ae0309d9f888fd825c2e9d0241102fadaca81d888f26f845bc8c13c1e4ee", size = 4938435, upload-time = "2025-07-29T18:38:29.193Z" }, - { url = "https://files.pythonhosted.org/packages/81/1c/d27a930bca31fb07d975a2d7eaf3404f9388114463b9f15032813c98f893/ml_dtypes-0.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:58e39349d820b5702bb6f94ea0cb2dc8ec62ee81c0267d9622067d8333596a46", size = 206334, upload-time = "2025-07-29T18:38:30.687Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d8/6922499effa616012cb8dc445280f66d100a7ff39b35c864cfca019b3f89/ml_dtypes-0.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:66c2756ae6cfd7f5224e355c893cfd617fa2f747b8bbd8996152cbdebad9a184", size = 157584, upload-time = "2025-07-29T18:38:32.187Z" }, - { url = "https://files.pythonhosted.org/packages/0d/eb/bc07c88a6ab002b4635e44585d80fa0b350603f11a2097c9d1bfacc03357/ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057", size = 663864, upload-time = "2025-07-29T18:38:33.777Z" }, - { url = "https://files.pythonhosted.org/packages/cf/89/11af9b0f21b99e6386b6581ab40fb38d03225f9de5f55cf52097047e2826/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177", size = 4951313, upload-time = "2025-07-29T18:38:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55", size = 4928805, upload-time = "2025-07-29T18:38:38.29Z" }, - { url = "https://files.pythonhosted.org/packages/50/c1/85e6be4fc09c6175f36fb05a45917837f30af9a5146a5151cb3a3f0f9e09/ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e", size = 208182, upload-time = "2025-07-29T18:38:39.72Z" }, - { url = "https://files.pythonhosted.org/packages/9e/17/cf5326d6867be057f232d0610de1458f70a8ce7b6290e4b4a277ea62b4cd/ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d", size = 161560, upload-time = "2025-07-29T18:38:41.072Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/1bcc98a66de7b2455dfb292f271452cac9edc4e870796e0d87033524d790/ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af", size = 663781, upload-time = "2025-07-29T18:38:42.984Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2c/bd2a79ba7c759ee192b5601b675b180a3fd6ccf48ffa27fe1782d280f1a7/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113", size = 4956217, upload-time = "2025-07-29T18:38:44.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/f3/091ba84e5395d7fe5b30c081a44dec881cd84b408db1763ee50768b2ab63/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7", size = 4933109, upload-time = "2025-07-29T18:38:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/bc/24/054036dbe32c43295382c90a1363241684c4d6aaa1ecc3df26bd0c8d5053/ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4", size = 208187, upload-time = "2025-07-29T18:38:48.24Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/7dc3ec6794a4a9004c765e0c341e32355840b698f73fd2daff46f128afc1/ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd", size = 161559, upload-time = "2025-07-29T18:38:50.493Z" }, - { url = "https://files.pythonhosted.org/packages/12/91/e6c7a0d67a152b9330445f9f0cf8ae6eee9b83f990b8c57fe74631e42a90/ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770", size = 689321, upload-time = "2025-07-29T18:38:52.03Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6c/b7b94b84a104a5be1883305b87d4c6bd6ae781504474b4cca067cb2340ec/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc", size = 5274495, upload-time = "2025-07-29T18:38:53.797Z" }, - { url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422, upload-time = "2025-07-29T18:38:56.603Z" }, - { url = "https://files.pythonhosted.org/packages/7c/88/8612ff177d043a474b9408f0382605d881eeb4125ba89d4d4b3286573a83/ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70", size = 661182, upload-time = "2025-07-29T18:38:58.414Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2b/0569a5e88b29240d373e835107c94ae9256fb2191d3156b43b2601859eff/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035", size = 4956187, upload-time = "2025-07-29T18:39:00.611Z" }, - { url = "https://files.pythonhosted.org/packages/51/66/273c2a06ae44562b104b61e6b14444da00061fd87652506579d7eb2c40b1/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9", size = 4930911, upload-time = "2025-07-29T18:39:02.405Z" }, - { url = "https://files.pythonhosted.org/packages/93/ab/606be3e87dc0821bd360c8c1ee46108025c31a4f96942b63907bb441b87d/ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea", size = 216664, upload-time = "2025-07-29T18:39:03.927Z" }, - { url = "https://files.pythonhosted.org/packages/30/a2/e900690ca47d01dffffd66375c5de8c4f8ced0f1ef809ccd3b25b3e6b8fa/ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e", size = 160203, upload-time = "2025-07-29T18:39:05.671Z" }, - { url = "https://files.pythonhosted.org/packages/53/21/783dfb51f40d2660afeb9bccf3612b99f6a803d980d2a09132b0f9d216ab/ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3", size = 689324, upload-time = "2025-07-29T18:39:07.567Z" }, - { url = "https://files.pythonhosted.org/packages/09/f7/a82d249c711abf411ac027b7163f285487f5e615c3e0716c61033ce996ab/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93", size = 5275917, upload-time = "2025-07-29T18:39:09.339Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3c/541c4b30815ab90ebfbb51df15d0b4254f2f9f1e2b4907ab229300d5e6f2/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39", size = 5285284, upload-time = "2025-07-29T18:39:11.532Z" }, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, -] - -[[package]] -name = "msgpack" -version = "1.0.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311, upload-time = "2023-09-28T13:20:36.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096, upload-time = "2023-09-28T13:18:49.678Z" }, - { url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210, upload-time = "2023-09-28T13:18:51.039Z" }, - { url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952, upload-time = "2023-09-28T13:18:52.871Z" }, - { url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511, upload-time = "2023-09-28T13:18:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980, upload-time = "2023-09-28T13:18:56.058Z" }, - { url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547, upload-time = "2023-09-28T13:18:57.396Z" }, - { url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669, upload-time = "2023-09-28T13:18:58.957Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353, upload-time = "2023-09-28T13:19:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455, upload-time = "2023-09-28T13:19:03.201Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367, upload-time = "2023-09-28T13:19:04.554Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860, upload-time = "2023-09-28T13:19:06.397Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759, upload-time = "2023-09-28T13:19:08.148Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330, upload-time = "2023-09-28T13:19:09.417Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537, upload-time = "2023-09-28T13:19:10.898Z" }, - { url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561, upload-time = "2023-09-28T13:19:12.779Z" }, - { url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009, upload-time = "2023-09-28T13:19:14.373Z" }, - { url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882, upload-time = "2023-09-28T13:19:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949, upload-time = "2023-09-28T13:19:18.114Z" }, - { url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836, upload-time = "2023-09-28T13:19:19.729Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587, upload-time = "2023-09-28T13:19:21.666Z" }, - { url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509, upload-time = "2023-09-28T13:19:23.161Z" }, - { url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287, upload-time = "2023-09-28T13:19:25.097Z" }, -] - -[[package]] -name = "mypy" -version = "1.19.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, - { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, -] - -[[package]] -name = "networkx" -version = "3.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928, upload-time = "2023-10-28T08:41:39.364Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772, upload-time = "2023-10-28T08:41:36.945Z" }, -] - -[[package]] -name = "numpy" -version = "2.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb", size = 21259519, upload-time = "2025-10-15T16:15:19.012Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f", size = 14452796, upload-time = "2025-10-15T16:15:23.094Z" }, - { url = "https://files.pythonhosted.org/packages/e4/04/ff11611200acd602a1e5129e36cfd25bf01ad8e5cf927baf2e90236eb02e/numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36", size = 5381639, upload-time = "2025-10-15T16:15:25.572Z" }, - { url = "https://files.pythonhosted.org/packages/ea/77/e95c757a6fe7a48d28a009267408e8aa382630cc1ad1db7451b3bc21dbb4/numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032", size = 6914296, upload-time = "2025-10-15T16:15:27.079Z" }, - { url = "https://files.pythonhosted.org/packages/a3/d2/137c7b6841c942124eae921279e5c41b1c34bab0e6fc60c7348e69afd165/numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7", size = 14591904, upload-time = "2025-10-15T16:15:29.044Z" }, - { url = "https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda", size = 16939602, upload-time = "2025-10-15T16:15:31.106Z" }, - { url = "https://files.pythonhosted.org/packages/95/22/9639c30e32c93c4cee3ccdb4b09c2d0fbff4dcd06d36b357da06146530fb/numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0", size = 16372661, upload-time = "2025-10-15T16:15:33.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/e9/a685079529be2b0156ae0c11b13d6be647743095bb51d46589e95be88086/numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a", size = 18884682, upload-time = "2025-10-15T16:15:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", size = 6570076, upload-time = "2025-10-15T16:15:38.225Z" }, - { url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", size = 13089358, upload-time = "2025-10-15T16:15:40.404Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", size = 10462292, upload-time = "2025-10-15T16:15:42.896Z" }, - { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, - { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, - { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, - { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, - { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, - { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, - { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, - { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, - { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, - { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, - { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, - { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, - { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, - { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, - { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, - { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, - { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, - { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, - { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, - { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, - { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, - { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, - { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, - { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, - { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, - { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, - { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, - { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, - { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, - { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, - { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, - { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, - { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, - { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, - { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, - { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", size = 21131552, upload-time = "2025-10-15T16:17:55.845Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", size = 14377796, upload-time = "2025-10-15T16:17:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", size = 5306904, upload-time = "2025-10-15T16:18:00.596Z" }, - { url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", size = 6819682, upload-time = "2025-10-15T16:18:02.32Z" }, - { url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", size = 14422300, upload-time = "2025-10-15T16:18:04.271Z" }, - { url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", size = 16760806, upload-time = "2025-10-15T16:18:06.668Z" }, - { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130, upload-time = "2025-10-15T16:18:09.397Z" }, -] - -[[package]] -name = "omegaconf" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, -] - -[[package]] -name = "onnx" -version = "1.19.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "protobuf" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/2f/c619eb65769357e9b6de9212c9a821ab39cd484448e5d6b3fb5fb0a64c6d/onnx-1.19.1.tar.gz", hash = "sha256:737524d6eb3907d3499ea459c6f01c5a96278bb3a0f2ff8ae04786fb5d7f1ed5", size = 12033525, upload-time = "2025-10-10T04:01:34.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/07/0019c72924909e4f64b9199770630ab7b8d7914b912b03230e68f5eda7ae/onnx-1.19.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:17aaf5832126de0a5197a5864e4f09a764dd7681d3035135547959b4b6b77a09", size = 18320936, upload-time = "2025-10-10T04:00:04.235Z" }, - { url = "https://files.pythonhosted.org/packages/af/2f/5c47acf740dc35f0decc640844260fbbdc0efa0565657c93fd7ff30f13f3/onnx-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01b292a4d0b197c45d8184545bbc8ae1df83466341b604187c1b05902cb9c920", size = 18044269, upload-time = "2025-10-10T04:00:07.449Z" }, - { url = "https://files.pythonhosted.org/packages/d5/61/6c457ee8c3a62a3cad0a4bfa4c5436bb3ac4df90c3551d40bee1224b5b51/onnx-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1839af08ab4a909e4af936b8149c27f8c64b96138981024e251906e0539d8bf9", size = 18218092, upload-time = "2025-10-10T04:00:11.135Z" }, - { url = "https://files.pythonhosted.org/packages/54/d5/ab832e1369505e67926a70e9a102061f89ad01f91aa296c4b1277cb81b25/onnx-1.19.1-cp311-cp311-win32.whl", hash = "sha256:0bdbb676e3722bd32f9227c465d552689f49086f986a696419d865cb4e70b989", size = 16344809, upload-time = "2025-10-10T04:00:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/8b/b5/6eb4611d24b85002f878ba8476b4cecbe6f9784c0236a3c5eff85236cc0a/onnx-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:1346853df5c1e3ebedb2e794cf2a51e0f33759affd655524864ccbcddad7035b", size = 16464319, upload-time = "2025-10-10T04:00:18.235Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ff/f0e1f06420c70e20d497fec7c94a864d069943b6312bedd4224c0ab946f8/onnx-1.19.1-cp311-cp311-win_arm64.whl", hash = "sha256:2d69c280c0e665b7f923f499243b9bb84fe97970b7a4668afa0032045de602c8", size = 16437503, upload-time = "2025-10-10T04:00:21.247Z" }, - { url = "https://files.pythonhosted.org/packages/50/07/f6c5b2cffef8c29e739616d1415aea22f7b7ef1f19c17f02b7cff71f5498/onnx-1.19.1-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:3612193a89ddbce5c4e86150869b9258780a82fb8c4ca197723a4460178a6ce9", size = 18327840, upload-time = "2025-10-10T04:00:24.259Z" }, - { url = "https://files.pythonhosted.org/packages/93/20/0568ebd52730287ae80cac8ac893a7301c793ea1630984e2519ee92b02a9/onnx-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c2fd2f744e7a3880ad0c262efa2edf6d965d0bd02b8f327ec516ad4cb0f2f15", size = 18042539, upload-time = "2025-10-10T04:00:27.693Z" }, - { url = "https://files.pythonhosted.org/packages/14/fd/cd7a0fd10a04f8cc5ae436b63e0022e236fe51b9dbb8ee6317fd48568c72/onnx-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:485d3674d50d789e0ee72fa6f6e174ab81cb14c772d594f992141bd744729d8a", size = 18218271, upload-time = "2025-10-10T04:00:30.495Z" }, - { url = "https://files.pythonhosted.org/packages/65/68/cc8b8c05469fe08384b446304ad7e6256131ca0463bf6962366eebec98c0/onnx-1.19.1-cp312-cp312-win32.whl", hash = "sha256:638bc56ff1a5718f7441e887aeb4e450f37a81c6eac482040381b140bd9ba601", size = 16345111, upload-time = "2025-10-10T04:00:34.982Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/d1cb16693598a512c2cf9ffe0841d8d8fd2c83ae8e889efd554f5aa427cf/onnx-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc7e2e4e163e679721e547958b5a7db875bf822cad371b7c1304aa4401a7c7a4", size = 16465621, upload-time = "2025-10-10T04:00:39.107Z" }, - { url = "https://files.pythonhosted.org/packages/90/32/da116cc61fdef334782aa7f87a1738431dd1af1a5d1a44bd95d6d51ad260/onnx-1.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:17c215b1c0f20fe93b4cbe62668247c1d2294b9bc7f6be0ca9ced28e980c07b7", size = 16437505, upload-time = "2025-10-10T04:00:42.255Z" }, - { url = "https://files.pythonhosted.org/packages/b4/b8/ab1fdfe2e8502f4dc4289fc893db35816bd20d080d8370f86e74dda5f598/onnx-1.19.1-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:4e5f938c68c4dffd3e19e4fd76eb98d298174eb5ebc09319cdd0ec5fe50050dc", size = 18327815, upload-time = "2025-10-10T04:00:45.682Z" }, - { url = "https://files.pythonhosted.org/packages/04/40/eb875745a4b92aea10e5e32aa2830f409c4d7b6f7b48ca1c4eaad96636c5/onnx-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:86e20a5984b017feeef2dbf4ceff1c7c161ab9423254968dd77d3696c38691d0", size = 18041464, upload-time = "2025-10-10T04:00:48.557Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/8586135f40dbe4989cec4d413164bc8fc5c73d37c566f33f5ea3a7f2b6f6/onnx-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d9c467f0f29993c12f330736af87972f30adb8329b515f39d63a0db929cb2c", size = 18218244, upload-time = "2025-10-10T04:00:51.891Z" }, - { url = "https://files.pythonhosted.org/packages/51/b5/4201254b8683129db5da3fb55aa1f7e56d0a8d45c66ce875dec21ca1ff25/onnx-1.19.1-cp313-cp313-win32.whl", hash = "sha256:65eee353a51b4e4ca3e797784661e5376e2b209f17557e04921eac9166a8752e", size = 16345330, upload-time = "2025-10-10T04:00:54.858Z" }, - { url = "https://files.pythonhosted.org/packages/69/67/c6d239afbcdbeb6805432969b908b5c9f700c96d332b34e3f99518d76caf/onnx-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3bc87e38b53554b1fc9ef7b275c81c6f5c93c90a91935bb0aa8d4d498a6d48e", size = 16465567, upload-time = "2025-10-10T04:00:57.893Z" }, - { url = "https://files.pythonhosted.org/packages/99/fe/89f1e40f5bc54595ff0dcf5391ce19e578b528973ccc74dd99800196d30d/onnx-1.19.1-cp313-cp313-win_arm64.whl", hash = "sha256:e41496f400afb980ec643d80d5164753a88a85234fa5c06afdeebc8b7d1ec252", size = 16437562, upload-time = "2025-10-10T04:01:00.703Z" }, - { url = "https://files.pythonhosted.org/packages/86/43/b186ccbc8fe7e93643a6a6d40bbf2bb6ce4fb9469bbd3453c77e270c50ad/onnx-1.19.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:5f6274abf0fd74e80e78ecbb44bd44509409634525c89a9b38276c8af47dc0a2", size = 18355703, upload-time = "2025-10-10T04:01:03.735Z" }, - { url = "https://files.pythonhosted.org/packages/60/f1/22ee4d8b8f9fa4cb1d1b9579da3b4b5187ddab33846ec5ac744af02c0e2b/onnx-1.19.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07dcd4d83584eb4bf8f21ac04c82643712e5e93ac2a0ed10121ec123cb127e1e", size = 18047830, upload-time = "2025-10-10T04:01:06.552Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a4/8f3d51e3a095d42cdf2039a590cff06d024f2a10efbd0b1a2a6b3825f019/onnx-1.19.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1975860c3e720db25d37f1619976582828264bdcc64fa7511c321ac4fc01add3", size = 18221126, upload-time = "2025-10-10T04:01:09.77Z" }, - { url = "https://files.pythonhosted.org/packages/4f/0d/f9d6c2237083f1aac14b37f0b03b0d81f1147a8e2af0c3828165e0a6a67b/onnx-1.19.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9807d0e181f6070ee3a6276166acdc571575d1bd522fc7e89dba16fd6e7ffed9", size = 16465560, upload-time = "2025-10-10T04:01:13.212Z" }, - { url = "https://files.pythonhosted.org/packages/36/70/8418a58faa7d606d6a92cab69ae8d361b3b3969bf7e7e9a65a86d5d1b674/onnx-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6ee83e6929d75005482d9f304c502ac7c9b8d6db153aa6b484dae74d0f28570", size = 18042812, upload-time = "2025-10-10T04:01:15.919Z" }, -] - -[[package]] -name = "onnxruntime" -version = "1.23.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/be/467b00f09061572f022ffd17e49e49e5a7a789056bad95b54dfd3bee73ff/onnxruntime-1.23.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:6f91d2c9b0965e86827a5ba01531d5b669770b01775b23199565d6c1f136616c", size = 17196113, upload-time = "2025-10-22T03:47:33.526Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a8/3c23a8f75f93122d2b3410bfb74d06d0f8da4ac663185f91866b03f7da1b/onnxruntime-1.23.2-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:87d8b6eaf0fbeb6835a60a4265fde7a3b60157cf1b2764773ac47237b4d48612", size = 19153857, upload-time = "2025-10-22T03:46:37.578Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d8/506eed9af03d86f8db4880a4c47cd0dffee973ef7e4f4cff9f1d4bcf7d22/onnxruntime-1.23.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbfd2fca76c855317568c1b36a885ddea2272c13cb0e395002c402f2360429a6", size = 15220095, upload-time = "2025-10-22T03:46:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/e9/80/113381ba832d5e777accedc6cb41d10f9eca82321ae31ebb6bcede530cea/onnxruntime-1.23.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da44b99206e77734c5819aa2142c69e64f3b46edc3bd314f6a45a932defc0b3e", size = 17372080, upload-time = "2025-10-22T03:47:00.265Z" }, - { url = "https://files.pythonhosted.org/packages/3a/db/1b4a62e23183a0c3fe441782462c0ede9a2a65c6bbffb9582fab7c7a0d38/onnxruntime-1.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:902c756d8b633ce0dedd889b7c08459433fbcf35e9c38d1c03ddc020f0648c6e", size = 13468349, upload-time = "2025-10-22T03:47:25.783Z" }, - { url = "https://files.pythonhosted.org/packages/1b/9e/f748cd64161213adeef83d0cb16cb8ace1e62fa501033acdd9f9341fff57/onnxruntime-1.23.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:b8f029a6b98d3cf5be564d52802bb50a8489ab73409fa9db0bf583eabb7c2321", size = 17195929, upload-time = "2025-10-22T03:47:36.24Z" }, - { url = "https://files.pythonhosted.org/packages/91/9d/a81aafd899b900101988ead7fb14974c8a58695338ab6a0f3d6b0100f30b/onnxruntime-1.23.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:218295a8acae83905f6f1aed8cacb8e3eb3bd7513a13fe4ba3b2664a19fc4a6b", size = 19157705, upload-time = "2025-10-22T03:46:40.415Z" }, - { url = "https://files.pythonhosted.org/packages/3c/35/4e40f2fba272a6698d62be2cd21ddc3675edfc1a4b9ddefcc4648f115315/onnxruntime-1.23.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76ff670550dc23e58ea9bc53b5149b99a44e63b34b524f7b8547469aaa0dcb8c", size = 15226915, upload-time = "2025-10-22T03:46:27.773Z" }, - { url = "https://files.pythonhosted.org/packages/ef/88/9cc25d2bafe6bc0d4d3c1db3ade98196d5b355c0b273e6a5dc09c5d5d0d5/onnxruntime-1.23.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f9b4ae77f8e3c9bee50c27bc1beede83f786fe1d52e99ac85aa8d65a01e9b77", size = 17382649, upload-time = "2025-10-22T03:47:02.782Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b4/569d298f9fc4d286c11c45e85d9ffa9e877af12ace98af8cab52396e8f46/onnxruntime-1.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:25de5214923ce941a3523739d34a520aac30f21e631de53bba9174dc9c004435", size = 13470528, upload-time = "2025-10-22T03:47:28.106Z" }, - { url = "https://files.pythonhosted.org/packages/3d/41/fba0cabccecefe4a1b5fc8020c44febb334637f133acefc7ec492029dd2c/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:2ff531ad8496281b4297f32b83b01cdd719617e2351ffe0dba5684fb283afa1f", size = 17196337, upload-time = "2025-10-22T03:46:35.168Z" }, - { url = "https://files.pythonhosted.org/packages/fe/f9/2d49ca491c6a986acce9f1d1d5fc2099108958cc1710c28e89a032c9cfe9/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:162f4ca894ec3de1a6fd53589e511e06ecdc3ff646849b62a9da7489dee9ce95", size = 19157691, upload-time = "2025-10-22T03:46:43.518Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a1/428ee29c6eaf09a6f6be56f836213f104618fb35ac6cc586ff0f477263eb/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45d127d6e1e9b99d1ebeae9bcd8f98617a812f53f46699eafeb976275744826b", size = 15226898, upload-time = "2025-10-22T03:46:30.039Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/b57c8a2466a3126dbe0a792f56ad7290949b02f47b86216cd47d857e4b77/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bace4e0d46480fbeeb7bbe1ffe1f080e6663a42d1086ff95c1551f2d39e7872", size = 17382518, upload-time = "2025-10-22T03:47:05.407Z" }, - { url = "https://files.pythonhosted.org/packages/4a/93/aba75358133b3a941d736816dd392f687e7eab77215a6e429879080b76b6/onnxruntime-1.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:1f9cc0a55349c584f083c1c076e611a7c35d5b867d5d6e6d6c823bf821978088", size = 13470276, upload-time = "2025-10-22T03:47:31.193Z" }, - { url = "https://files.pythonhosted.org/packages/7c/3d/6830fa61c69ca8e905f237001dbfc01689a4e4ab06147020a4518318881f/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d2385e774f46ac38f02b3a91a91e30263d41b2f1f4f26ae34805b2a9ddef466", size = 15229610, upload-time = "2025-10-22T03:46:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ca/862b1e7a639460f0ca25fd5b6135fb42cf9deea86d398a92e44dfda2279d/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2b9233c4947907fd1818d0e581c049c41ccc39b2856cc942ff6d26317cee145", size = 17394184, upload-time = "2025-10-22T03:47:08.127Z" }, -] - -[[package]] -name = "onnxruntime-gpu" -version = "1.23.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/a4/e3d7fbe32b44e814ae24ed642f05fac5d96d120efd82db7a7cac936e85a9/onnxruntime_gpu-1.23.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d76d1ac7a479ecc3ac54482eea4ba3b10d68e888a0f8b5f420f0bdf82c5eec59", size = 300525715, upload-time = "2025-10-22T16:56:19.928Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5c/dba7c009e73dcce02e7f714574345b5e607c5c75510eb8d7bef682b45e5d/onnxruntime_gpu-1.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:054282614c2fc9a4a27d74242afbae706a410f1f63cc35bc72f99709029a5ba4", size = 244506823, upload-time = "2025-10-22T16:55:09.526Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d9/b7140a4f1615195938c7e358c0804bb84271f0d6886b5cbf105c6cb58aae/onnxruntime_gpu-1.23.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f2d1f720685d729b5258ec1b36dee1de381b8898189908c98cbeecdb2f2b5c2", size = 300509596, upload-time = "2025-10-22T16:56:31.728Z" }, - { url = "https://files.pythonhosted.org/packages/87/da/2685c79e5ea587beddebe083601fead0bdf3620bc2f92d18756e7de8a636/onnxruntime_gpu-1.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:fe925a84b00e291e0ad3fac29bfd8f8e06112abc760cdc82cb711b4f3935bd95", size = 244508327, upload-time = "2025-10-22T16:55:19.397Z" }, - { url = "https://files.pythonhosted.org/packages/03/05/40d561636e4114b54aa06d2371bfbca2d03e12cfdf5d4b85814802f18a75/onnxruntime_gpu-1.23.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e8f75af5da07329d0c3a5006087f4051d8abd133b4be7c9bae8cdab7bea4c26", size = 300515567, upload-time = "2025-10-22T16:56:43.794Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3b/418300438063d403384c79eaef1cb13c97627042f2247b35a887276a355a/onnxruntime_gpu-1.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:7f1b3f49e5e126b99e23ec86b4203db41c2a911f6165f7624f2bc8267aaca767", size = 244507535, upload-time = "2025-10-22T16:55:28.532Z" }, - { url = "https://files.pythonhosted.org/packages/b8/dc/80b145e3134d7eba31309b3299a2836e37c76e4c419a261ad9796f8f8d65/onnxruntime_gpu-1.23.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20959cd4ae358aab6579ab9123284a7b1498f7d51ec291d429a5edc26511306f", size = 300525759, upload-time = "2025-10-22T16:56:56.925Z" }, -] - -[[package]] -name = "onnxruntime-openvino" -version = "1.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/10/adcd4ac68ffc8dee003553125ef5c091be822e2d7c1077d0bb85690baa9c/onnxruntime_openvino-1.23.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:91938837e6e92e30c63d12fad68a8a4959c40d2eade2bd60f38bdd5b6392f8d3", size = 70481480, upload-time = "2025-10-14T15:19:45.882Z" }, - { url = "https://files.pythonhosted.org/packages/97/95/25f28d6fecf300aa0af393e96af9e00cc676e5dab650ab84f2122610df50/onnxruntime_openvino-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f05d2d6a804fb70d3f4329d777ac62439773dcc2df827dd5f42644b10bf1fea", size = 13117353, upload-time = "2025-10-14T15:19:49.014Z" }, - { url = "https://files.pythonhosted.org/packages/42/0c/8d97419dfeedf419c5fe5293f3dbc59284855a63ad22e71f46c0010c9dc4/onnxruntime_openvino-1.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b963ea19bf9856f3d6b2f719d451f2eeae482a8f69c729906465aa4f27f4d39c", size = 70483359, upload-time = "2025-10-14T15:19:52.88Z" }, - { url = "https://files.pythonhosted.org/packages/29/30/ff6111b16ffb4187c462824aa4e95acc20fdd90f856d44a339d56c6dacd6/onnxruntime_openvino-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:937e52657f94c56990a6e5bd4c3705bd6e970834c7c94e23d300dde6848f2889", size = 13117933, upload-time = "2025-10-14T15:19:58.319Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/e42f618a8ec5fcf825fed4fdc8125f7105256cc6020b84567ecb88d5e2b7/onnxruntime_openvino-1.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2e93b9a8323e196b7433866054a59260f2206ab6fb0e7223dda91da71f1db8c5", size = 70483088, upload-time = "2025-10-14T15:20:02.425Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f9/a531dc497dc113dc14df9a9de5aacb1676cadebc3ec6cc7cd3ca65cb3db0/onnxruntime_openvino-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ebbf70929de4ce269371cb255536bbedef588932d744da0b40e66c38a620f35", size = 13118206, upload-time = "2025-10-14T15:20:05.587Z" }, -] - -[[package]] -name = "opencv-python" -version = "4.11.0.86" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322, upload-time = "2025-01-16T13:52:25.887Z" }, - { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197, upload-time = "2025-01-16T13:55:21.222Z" }, - { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439, upload-time = "2025-01-16T13:51:35.822Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597, upload-time = "2025-01-16T13:52:08.836Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337, upload-time = "2025-01-16T13:52:13.549Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" }, -] - -[[package]] -name = "opencv-python-headless" -version = "4.11.0.86" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460, upload-time = "2025-01-16T13:52:57.015Z" }, - { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330, upload-time = "2025-01-16T13:55:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060, upload-time = "2025-01-16T13:51:59.625Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856, upload-time = "2025-01-16T13:53:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425, upload-time = "2025-01-16T13:52:49.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, -] - -[[package]] -name = "orjson" -version = "3.11.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/b8/333fdb27840f3bf04022d21b654a35f58e15407183aeb16f3b41aa053446/orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5", size = 5972347, upload-time = "2025-12-06T15:55:39.458Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/68/6b3659daec3a81aed5ab47700adb1a577c76a5452d35b91c88efee89987f/orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8", size = 245318, upload-time = "2025-12-06T15:54:02.355Z" }, - { url = "https://files.pythonhosted.org/packages/e9/00/92db122261425f61803ccf0830699ea5567439d966cbc35856fe711bfe6b/orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc", size = 129491, upload-time = "2025-12-06T15:54:03.877Z" }, - { url = "https://files.pythonhosted.org/packages/94/4f/ffdcb18356518809d944e1e1f77589845c278a1ebbb5a8297dfefcc4b4cb/orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968", size = 132167, upload-time = "2025-12-06T15:54:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/97/c6/0a8caff96f4503f4f7dd44e40e90f4d14acf80d3b7a97cb88747bb712d3e/orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7", size = 130516, upload-time = "2025-12-06T15:54:06.274Z" }, - { url = "https://files.pythonhosted.org/packages/4d/63/43d4dc9bd9954bff7052f700fdb501067f6fb134a003ddcea2a0bb3854ed/orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd", size = 135695, upload-time = "2025-12-06T15:54:07.702Z" }, - { url = "https://files.pythonhosted.org/packages/87/6f/27e2e76d110919cb7fcb72b26166ee676480a701bcf8fc53ac5d0edce32f/orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9", size = 139664, upload-time = "2025-12-06T15:54:08.828Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f8/5966153a5f1be49b5fbb8ca619a529fde7bc71aa0a376f2bb83fed248bcd/orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef", size = 137289, upload-time = "2025-12-06T15:54:09.898Z" }, - { url = "https://files.pythonhosted.org/packages/a7/34/8acb12ff0299385c8bbcbb19fbe40030f23f15a6de57a9c587ebf71483fb/orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9", size = 138784, upload-time = "2025-12-06T15:54:11.022Z" }, - { url = "https://files.pythonhosted.org/packages/ee/27/910421ea6e34a527f73d8f4ee7bdffa48357ff79c7b8d6eb6f7b82dd1176/orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125", size = 141322, upload-time = "2025-12-06T15:54:12.427Z" }, - { url = "https://files.pythonhosted.org/packages/87/a3/4b703edd1a05555d4bb1753d6ce44e1a05b7a6d7c164d5b332c795c63d70/orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814", size = 413612, upload-time = "2025-12-06T15:54:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/1b/36/034177f11d7eeea16d3d2c42a1883b0373978e08bc9dad387f5074c786d8/orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5", size = 150993, upload-time = "2025-12-06T15:54:15.189Z" }, - { url = "https://files.pythonhosted.org/packages/44/2f/ea8b24ee046a50a7d141c0227c4496b1180b215e728e3b640684f0ea448d/orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880", size = 141774, upload-time = "2025-12-06T15:54:16.451Z" }, - { url = "https://files.pythonhosted.org/packages/8a/12/cc440554bf8200eb23348a5744a575a342497b65261cd65ef3b28332510a/orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d", size = 135109, upload-time = "2025-12-06T15:54:17.73Z" }, - { url = "https://files.pythonhosted.org/packages/a3/83/e0c5aa06ba73a6760134b169f11fb970caa1525fa4461f94d76e692299d9/orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1", size = 133193, upload-time = "2025-12-06T15:54:19.426Z" }, - { url = "https://files.pythonhosted.org/packages/cb/35/5b77eaebc60d735e832c5b1a20b155667645d123f09d471db0a78280fb49/orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c", size = 126830, upload-time = "2025-12-06T15:54:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a4/8052a029029b096a78955eadd68ab594ce2197e24ec50e6b6d2ab3f4e33b/orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d", size = 245347, upload-time = "2025-12-06T15:54:22.061Z" }, - { url = "https://files.pythonhosted.org/packages/64/67/574a7732bd9d9d79ac620c8790b4cfe0717a3d5a6eb2b539e6e8995e24a0/orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626", size = 129435, upload-time = "2025-12-06T15:54:23.615Z" }, - { url = "https://files.pythonhosted.org/packages/52/8d/544e77d7a29d90cf4d9eecd0ae801c688e7f3d1adfa2ebae5e1e94d38ab9/orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f", size = 132074, upload-time = "2025-12-06T15:54:24.694Z" }, - { url = "https://files.pythonhosted.org/packages/6e/57/b9f5b5b6fbff9c26f77e785baf56ae8460ef74acdb3eae4931c25b8f5ba9/orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85", size = 130520, upload-time = "2025-12-06T15:54:26.185Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6d/d34970bf9eb33f9ec7c979a262cad86076814859e54eb9a059a52f6dc13d/orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9", size = 136209, upload-time = "2025-12-06T15:54:27.264Z" }, - { url = "https://files.pythonhosted.org/packages/e7/39/bc373b63cc0e117a105ea12e57280f83ae52fdee426890d57412432d63b3/orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626", size = 139837, upload-time = "2025-12-06T15:54:28.75Z" }, - { url = "https://files.pythonhosted.org/packages/cb/aa/7c4818c8d7d324da220f4f1af55c343956003aa4d1ce1857bdc1d396ba69/orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa", size = 137307, upload-time = "2025-12-06T15:54:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/46/bf/0993b5a056759ba65145effe3a79dd5a939d4a070eaa5da2ee3180fbb13f/orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477", size = 139020, upload-time = "2025-12-06T15:54:31.024Z" }, - { url = "https://files.pythonhosted.org/packages/65/e8/83a6c95db3039e504eda60fc388f9faedbb4f6472f5aba7084e06552d9aa/orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e", size = 141099, upload-time = "2025-12-06T15:54:32.196Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b4/24fdc024abfce31c2f6812973b0a693688037ece5dc64b7a60c1ce69e2f2/orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69", size = 413540, upload-time = "2025-12-06T15:54:33.361Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/01c0ec95d55ed0c11e4cae3e10427e479bba40c77312b63e1f9665e0737d/orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3", size = 151530, upload-time = "2025-12-06T15:54:34.6Z" }, - { url = "https://files.pythonhosted.org/packages/f9/d4/f9ebc57182705bb4bbe63f5bbe14af43722a2533135e1d2fb7affa0c355d/orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca", size = 141863, upload-time = "2025-12-06T15:54:35.801Z" }, - { url = "https://files.pythonhosted.org/packages/0d/04/02102b8d19fdcb009d72d622bb5781e8f3fae1646bf3e18c53d1bc8115b5/orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98", size = 135255, upload-time = "2025-12-06T15:54:37.209Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fb/f05646c43d5450492cb387de5549f6de90a71001682c17882d9f66476af5/orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875", size = 133252, upload-time = "2025-12-06T15:54:38.401Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/7b8c0b26ba18c793533ac1cd145e131e46fcf43952aa94c109b5b913c1f0/orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe", size = 126777, upload-time = "2025-12-06T15:54:39.515Z" }, - { url = "https://files.pythonhosted.org/packages/10/43/61a77040ce59f1569edf38f0b9faadc90c8cf7e9bec2e0df51d0132c6bb7/orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629", size = 245271, upload-time = "2025-12-06T15:54:40.878Z" }, - { url = "https://files.pythonhosted.org/packages/55/f9/0f79be617388227866d50edd2fd320cb8fb94dc1501184bb1620981a0aba/orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3", size = 129422, upload-time = "2025-12-06T15:54:42.403Z" }, - { url = "https://files.pythonhosted.org/packages/77/42/f1bf1549b432d4a78bfa95735b79b5dac75b65b5bb815bba86ad406ead0a/orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39", size = 132060, upload-time = "2025-12-06T15:54:43.531Z" }, - { url = "https://files.pythonhosted.org/packages/25/49/825aa6b929f1a6ed244c78acd7b22c1481fd7e5fda047dc8bf4c1a807eb6/orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f", size = 130391, upload-time = "2025-12-06T15:54:45.059Z" }, - { url = "https://files.pythonhosted.org/packages/42/ec/de55391858b49e16e1aa8f0bbbb7e5997b7345d8e984a2dec3746d13065b/orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51", size = 135964, upload-time = "2025-12-06T15:54:46.576Z" }, - { url = "https://files.pythonhosted.org/packages/1c/40/820bc63121d2d28818556a2d0a09384a9f0262407cf9fa305e091a8048df/orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8", size = 139817, upload-time = "2025-12-06T15:54:48.084Z" }, - { url = "https://files.pythonhosted.org/packages/09/c7/3a445ca9a84a0d59d26365fd8898ff52bdfcdcb825bcc6519830371d2364/orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706", size = 137336, upload-time = "2025-12-06T15:54:49.426Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b3/dc0d3771f2e5d1f13368f56b339c6782f955c6a20b50465a91acb79fe961/orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f", size = 138993, upload-time = "2025-12-06T15:54:50.939Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a2/65267e959de6abe23444659b6e19c888f242bf7725ff927e2292776f6b89/orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863", size = 141070, upload-time = "2025-12-06T15:54:52.414Z" }, - { url = "https://files.pythonhosted.org/packages/63/c9/da44a321b288727a322c6ab17e1754195708786a04f4f9d2220a5076a649/orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228", size = 413505, upload-time = "2025-12-06T15:54:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/7f/17/68dc14fa7000eefb3d4d6d7326a190c99bb65e319f02747ef3ebf2452f12/orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2", size = 151342, upload-time = "2025-12-06T15:54:55.113Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c5/ccee774b67225bed630a57478529fc026eda33d94fe4c0eac8fe58d4aa52/orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05", size = 141823, upload-time = "2025-12-06T15:54:56.331Z" }, - { url = "https://files.pythonhosted.org/packages/67/80/5d00e4155d0cd7390ae2087130637671da713959bb558db9bac5e6f6b042/orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef", size = 135236, upload-time = "2025-12-06T15:54:57.507Z" }, - { url = "https://files.pythonhosted.org/packages/95/fe/792cc06a84808dbdc20ac6eab6811c53091b42f8e51ecebf14b540e9cfe4/orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583", size = 133167, upload-time = "2025-12-06T15:54:58.71Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/d158bd8b50e3b1cfdcf406a7e463f6ffe3f0d167b99634717acdaf5e299f/orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287", size = 126712, upload-time = "2025-12-06T15:54:59.892Z" }, - { url = "https://files.pythonhosted.org/packages/c2/60/77d7b839e317ead7bb225d55bb50f7ea75f47afc489c81199befc5435b50/orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0", size = 245252, upload-time = "2025-12-06T15:55:01.127Z" }, - { url = "https://files.pythonhosted.org/packages/f1/aa/d4639163b400f8044cef0fb9aa51b0337be0da3a27187a20d1166e742370/orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81", size = 129419, upload-time = "2025-12-06T15:55:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/30/94/9eabf94f2e11c671111139edf5ec410d2f21e6feee717804f7e8872d883f/orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f", size = 132050, upload-time = "2025-12-06T15:55:03.918Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c8/ca10f5c5322f341ea9a9f1097e140be17a88f88d1cfdd29df522970d9744/orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e", size = 130370, upload-time = "2025-12-06T15:55:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/25/d4/e96824476d361ee2edd5c6290ceb8d7edf88d81148a6ce172fc00278ca7f/orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7", size = 136012, upload-time = "2025-12-06T15:55:06.402Z" }, - { url = "https://files.pythonhosted.org/packages/85/8e/9bc3423308c425c588903f2d103cfcfe2539e07a25d6522900645a6f257f/orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb", size = 139809, upload-time = "2025-12-06T15:55:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3c/b404e94e0b02a232b957c54643ce68d0268dacb67ac33ffdee24008c8b27/orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4", size = 137332, upload-time = "2025-12-06T15:55:08.961Z" }, - { url = "https://files.pythonhosted.org/packages/51/30/cc2d69d5ce0ad9b84811cdf4a0cd5362ac27205a921da524ff42f26d65e0/orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad", size = 138983, upload-time = "2025-12-06T15:55:10.595Z" }, - { url = "https://files.pythonhosted.org/packages/0e/87/de3223944a3e297d4707d2fe3b1ffb71437550e165eaf0ca8bbe43ccbcb1/orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829", size = 141069, upload-time = "2025-12-06T15:55:11.832Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/81d5087ae74be33bcae3ff2d80f5ccaa4a8fedc6d39bf65a427a95b8977f/orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac", size = 413491, upload-time = "2025-12-06T15:55:13.314Z" }, - { url = "https://files.pythonhosted.org/packages/d0/6f/f6058c21e2fc1efaf918986dbc2da5cd38044f1a2d4b7b91ad17c4acf786/orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d", size = 151375, upload-time = "2025-12-06T15:55:14.715Z" }, - { url = "https://files.pythonhosted.org/packages/54/92/c6921f17d45e110892899a7a563a925b2273d929959ce2ad89e2525b885b/orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439", size = 141850, upload-time = "2025-12-06T15:55:15.94Z" }, - { url = "https://files.pythonhosted.org/packages/88/86/cdecb0140a05e1a477b81f24739da93b25070ee01ce7f7242f44a6437594/orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499", size = 135278, upload-time = "2025-12-06T15:55:17.202Z" }, - { url = "https://files.pythonhosted.org/packages/e4/97/b638d69b1e947d24f6109216997e38922d54dcdcdb1b11c18d7efd2d3c59/orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310", size = 133170, upload-time = "2025-12-06T15:55:18.468Z" }, - { url = "https://files.pythonhosted.org/packages/8f/dd/f4fff4a6fe601b4f8f3ba3aa6da8ac33d17d124491a3b804c662a70e1636/orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5", size = 126713, upload-time = "2025-12-06T15:55:19.738Z" }, -] - -[[package]] -name = "packaging" -version = "23.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/2b/9b9c33ffed44ee921d0967086d653047286054117d584f1b1a7c22ceaf7b/packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", size = 146714, upload-time = "2023-10-01T13:50:05.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011, upload-time = "2023-10-01T13:50:03.745Z" }, -] - -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - -[[package]] -name = "pillow" -version = "10.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/62/c9449f9c3043c37f73e7487ec4ef0c03eb9c9afc91a92b977a67b3c0bbc5/pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", size = 3509265, upload-time = "2024-07-01T09:45:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/f4/5f/491dafc7bbf5a3cc1845dc0430872e8096eb9e2b6f8161509d124594ec2d/pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", size = 3375655, upload-time = "2024-07-01T09:45:52.462Z" }, - { url = "https://files.pythonhosted.org/packages/73/d5/c4011a76f4207a3c151134cd22a1415741e42fa5ddecec7c0182887deb3d/pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", size = 4340304, upload-time = "2024-07-01T09:45:55.006Z" }, - { url = "https://files.pythonhosted.org/packages/ac/10/c67e20445a707f7a610699bba4fe050583b688d8cd2d202572b257f46600/pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", size = 4452804, upload-time = "2024-07-01T09:45:58.437Z" }, - { url = "https://files.pythonhosted.org/packages/a9/83/6523837906d1da2b269dee787e31df3b0acb12e3d08f024965a3e7f64665/pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", size = 4365126, upload-time = "2024-07-01T09:46:00.713Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e5/8c68ff608a4203085158cff5cc2a3c534ec384536d9438c405ed6370d080/pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", size = 4533541, upload-time = "2024-07-01T09:46:03.235Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7c/01b8dbdca5bc6785573f4cee96e2358b0918b7b2c7b60d8b6f3abf87a070/pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", size = 4471616, upload-time = "2024-07-01T09:46:05.356Z" }, - { url = "https://files.pythonhosted.org/packages/c8/57/2899b82394a35a0fbfd352e290945440e3b3785655a03365c0ca8279f351/pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", size = 4600802, upload-time = "2024-07-01T09:46:08.145Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d7/a44f193d4c26e58ee5d2d9db3d4854b2cfb5b5e08d360a5e03fe987c0086/pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", size = 2235213, upload-time = "2024-07-01T09:46:10.211Z" }, - { url = "https://files.pythonhosted.org/packages/c1/d0/5866318eec2b801cdb8c82abf190c8343d8a1cd8bf5a0c17444a6f268291/pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", size = 2554498, upload-time = "2024-07-01T09:46:12.685Z" }, - { url = "https://files.pythonhosted.org/packages/d4/c8/310ac16ac2b97e902d9eb438688de0d961660a87703ad1561fd3dfbd2aa0/pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", size = 2243219, upload-time = "2024-07-01T09:46:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" }, - { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" }, - { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" }, - { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" }, - { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" }, - { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685, upload-time = "2024-07-01T09:46:45.194Z" }, - { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883, upload-time = "2024-07-01T09:46:47.331Z" }, - { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837, upload-time = "2024-07-01T09:46:49.647Z" }, - { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562, upload-time = "2024-07-01T09:46:51.811Z" }, - { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761, upload-time = "2024-07-01T09:46:53.961Z" }, - { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767, upload-time = "2024-07-01T09:46:56.664Z" }, - { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989, upload-time = "2024-07-01T09:46:58.977Z" }, - { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255, upload-time = "2024-07-01T09:47:01.189Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603, upload-time = "2024-07-01T09:47:03.918Z" }, - { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972, upload-time = "2024-07-01T09:47:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375, upload-time = "2024-07-01T09:47:09.065Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, -] - -[[package]] -name = "prettytable" -version = "3.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/c0/5e9c4d2a643a00a6f67578ef35485173de273a4567279e4f0c200c01386b/prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34", size = 47874, upload-time = "2023-09-11T14:04:14.548Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/81/316b6a55a0d1f327d04cc7b0ba9d04058cb62de6c3a4d4b0df280cbe3b0b/prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8", size = 27772, upload-time = "2023-09-11T14:03:45.582Z" }, -] - -[[package]] -name = "protobuf" -version = "6.33.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, - { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, - { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, -] - -[[package]] -name = "psutil" -version = "5.9.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a0/d0/c9ae661a302931735237791f04cb7086ac244377f78692ba3b3eae3a9619/psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c", size = 498429, upload-time = "2023-12-17T11:25:21.22Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/63/86a4ccc640b4ee1193800f57bbd20b766853c0cdbdbb248a27cdfafe6cbf/psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e", size = 245972, upload-time = "2023-12-17T11:25:48.202Z" }, - { url = "https://files.pythonhosted.org/packages/58/80/cc6666b3968646f2d94de66bbc63d701d501f4aa04de43dd7d1f5dc477dd/psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284", size = 282514, upload-time = "2023-12-17T11:25:51.371Z" }, - { url = "https://files.pythonhosted.org/packages/be/fa/f1f626620e3b47e6237dcc64cb8cc1472f139e99422e5b9fa5bbcf457f48/psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe", size = 285469, upload-time = "2023-12-17T11:25:54.25Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b8/dc6ebfc030b47cccc5f5229eeb15e64142b4782796c3ce169ccd60b4d511/psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68", size = 248406, upload-time = "2023-12-17T12:38:50.326Z" }, - { url = "https://files.pythonhosted.org/packages/50/28/92b74d95dd991c837813ffac0c79a581a3d129eb0fa7c1dd616d9901e0f3/psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414", size = 252245, upload-time = "2023-12-17T12:39:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8a/000d0e80156f0b96c55bda6c60f5ed6543d7b5e893ccab83117e50de1400/psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340", size = 246739, upload-time = "2023-12-17T11:25:57.305Z" }, -] - -[[package]] -name = "pyclipper" -version = "1.3.0.post6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909, upload-time = "2024-10-18T12:23:09.069Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/a9/66ca5f252dcac93ca076698591b838ba17f9729591edf4b74fef7fbe1414/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4247e7c44b34c87acbf38f99d48fb1acaf5da4a2cf4dcd601a9b24d431be4ef", size = 270930, upload-time = "2024-10-18T12:22:06.066Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/2ab5818b3504e179086e54a37ecc245525d069267b8c31b18ec3d0830cbf/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:851b3e58106c62a5534a1201295fe20c21714dee2eda68081b37ddb0367e6caa", size = 143411, upload-time = "2024-10-18T12:22:07.598Z" }, - { url = "https://files.pythonhosted.org/packages/09/f7/b58794f643e033a6d14da7c70f517315c3072f3c5fccdf4232fa8c8090c1/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16cc1705a915896d2aff52131c427df02265631279eac849ebda766432714cc0", size = 951754, upload-time = "2024-10-18T12:22:08.966Z" }, - { url = "https://files.pythonhosted.org/packages/c1/77/846a21957cd4ed266c36705ee340beaa923eb57d2bba013cfd7a5c417cfd/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace1f0753cf71c5c5f6488b8feef5dd0fa8b976ad86b24bb51f708f513df4aac", size = 969608, upload-time = "2024-10-18T12:22:10.321Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2b/580703daa6606d160caf596522d4cfdf62ae619b062a7ce6f905821a57e8/pyclipper-1.3.0.post6-cp311-cp311-win32.whl", hash = "sha256:dbc828641667142751b1127fd5c4291663490cf05689c85be4c5bcc89aaa236a", size = 100227, upload-time = "2024-10-18T12:22:11.991Z" }, - { url = "https://files.pythonhosted.org/packages/17/4b/a4cda18e8556d913ff75052585eb0d658500596b5f97fe8401d05123d47b/pyclipper-1.3.0.post6-cp311-cp311-win_amd64.whl", hash = "sha256:1c03f1ae43b18ee07730c3c774cc3cf88a10c12a4b097239b33365ec24a0a14a", size = 110442, upload-time = "2024-10-18T12:22:13.121Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c8/197d9a1d8354922d24d11d22fb2e0cc1ebc182f8a30496b7ddbe89467ce1/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e", size = 270487, upload-time = "2024-10-18T12:22:14.852Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8e/eb14eadf054494ad81446e21c4ea163b941747610b0eb9051644395f567e/pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229", size = 143469, upload-time = "2024-10-18T12:22:16.109Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e5/6c4a8df6e904c133bb4c5309d211d31c751db60cbd36a7250c02b05494a1/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d", size = 944206, upload-time = "2024-10-18T12:22:17.216Z" }, - { url = "https://files.pythonhosted.org/packages/76/65/cb014acc41cd5bf6bbfa4671c7faffffb9cee01706642c2dec70c5209ac8/pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c", size = 963797, upload-time = "2024-10-18T12:22:18.881Z" }, - { url = "https://files.pythonhosted.org/packages/80/ec/b40cd81ab7598984167508a5369a2fa31a09fe3b3e3d0b73aa50e06d4b3f/pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf", size = 99456, upload-time = "2024-10-18T12:22:20.084Z" }, - { url = "https://files.pythonhosted.org/packages/24/3a/7d6292e3c94fb6b872d8d7e80d909dc527ee6b0af73b753c63fdde65a7da/pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548", size = 110278, upload-time = "2024-10-18T12:22:21.178Z" }, - { url = "https://files.pythonhosted.org/packages/8c/b3/75232906bd13f869600d23bdb8fe6903cc899fa7e96981ae4c9b7d9c409e/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f129284d2c7bcd213d11c0f35e1ae506a1144ce4954e9d1734d63b120b0a1b58", size = 268254, upload-time = "2024-10-18T12:22:22.272Z" }, - { url = "https://files.pythonhosted.org/packages/0b/db/35843050a3dd7586781497a21ca6c8d48111afb66061cb40c3d3c288596d/pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:188fbfd1d30d02247f92c25ce856f5f3c75d841251f43367dbcf10935bc48f38", size = 142204, upload-time = "2024-10-18T12:22:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d7/1faa0ff35caa02cb32cb0583688cded3f38788f33e02bfe6461fbcc1bee1/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d129d0c2587f2f5904d201a4021f859afbb45fada4261c9fdedb2205b09d23", size = 943835, upload-time = "2024-10-18T12:22:26.233Z" }, - { url = "https://files.pythonhosted.org/packages/31/10/c0bf140bee2844e2c0617fdcc8a4e8daf98e71710046b06034e6f1963404/pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9c80b5c46eef38ba3f12dd818dc87f5f2a0853ba914b6f91b133232315f526", size = 962510, upload-time = "2024-10-18T12:22:27.573Z" }, - { url = "https://files.pythonhosted.org/packages/85/6f/8c6afc49b51b1bf16d5903ecd5aee657cf88f52c83cb5fabf771deeba728/pyclipper-1.3.0.post6-cp313-cp313-win32.whl", hash = "sha256:b15113ec4fc423b58e9ae80aa95cf5a0802f02d8f02a98a46af3d7d66ff0cc0e", size = 98836, upload-time = "2024-10-18T12:22:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/d5/19/9ff4551b42f2068686c50c0d199072fa67aee57fc5cf86770cacf71efda3/pyclipper-1.3.0.post6-cp313-cp313-win_amd64.whl", hash = "sha256:e5ff68fa770ac654c7974fc78792978796f068bd274e95930c0691c31e192889", size = 109672, upload-time = "2024-10-18T12:22:30.411Z" }, -] - -[[package]] -name = "pycparser" -version = "2.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206", size = 170877, upload-time = "2021-11-06T12:48:46.095Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", size = 118697, upload-time = "2021-11-06T12:50:13.61Z" }, -] - -[[package]] -name = "pydantic" -version = "2.11.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.33.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, -] - -[[package]] -name = "pygments" -version = "2.17.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/55/59/8bccf4157baf25e4aa5a0bb7fa3ba8600907de105ebc22b0c78cfbf6f565/pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367", size = 4827772, upload-time = "2023-11-21T20:43:53.875Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756, upload-time = "2023-11-21T20:43:49.423Z" }, -] - -[[package]] -name = "pyparsing" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/fe/65c989f70bd630b589adfbbcd6ed238af22319e90f059946c26b4835e44b/pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db", size = 884814, upload-time = "2023-07-30T15:07:02.617Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" }, -] - -[[package]] -name = "pyreadline3" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465, upload-time = "2022-01-24T20:05:11.66Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203, upload-time = "2022-01-24T20:05:10.442Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - -[[package]] -name = "pytest-cov" -version = "6.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, -] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324, upload-time = "2021-07-14T08:19:19.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702, upload-time = "2021-07-14T08:19:18.161Z" }, -] - -[[package]] -name = "python-dotenv" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/06/1ef763af20d0572c032fa22882cfbfb005fba6e7300715a37840858c919e/python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", size = 37399, upload-time = "2023-02-24T06:46:37.282Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/2f/62ea1c8b593f4e093cc1a7768f0d46112107e790c3e478532329e434f00b/python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a", size = 19482, upload-time = "2023-02-24T06:46:36.009Z" }, -] - -[[package]] -name = "python-engineio" -version = "4.12.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "simple-websocket" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677, upload-time = "2025-06-04T19:22:18.789Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536, upload-time = "2025-06-04T19:22:16.916Z" }, -] - -[[package]] -name = "python-multipart" -version = "0.0.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" }, -] - -[[package]] -name = "python-socketio" -version = "5.16.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bidict" }, - { name = "python-engineio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b8/55/5d8af5884283b58e4405580bcd84af1d898c457173c708736e065f10ca4a/python_socketio-5.16.0.tar.gz", hash = "sha256:f79403c7f1ba8b84460aa8fe4c671414c8145b21a501b46b676f3740286356fd", size = 127120, upload-time = "2025-12-24T23:51:48.826Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d2/2ccc2b69a187b80fda3152745670cfba936704f296a9fa54c6c8ac694d12/python_socketio-5.16.0-py3-none-any.whl", hash = "sha256:d95802961e15c7bd54ecf884c6e7644f81be8460f0a02ee66b473df58088ee8a", size = 79607, upload-time = "2025-12-24T23:51:47.2Z" }, -] - -[package.optional-dependencies] -client = [ - { name = "requests" }, - { name = "websocket-client" }, -] - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, -] - -[[package]] -name = "pyzmq" -version = "27.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, - { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, - { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, - { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, - { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, - { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, - { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, - { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, - { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, -] - -[[package]] -name = "qudida" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "scikit-learn" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/2d/bab8babd9dc9a9e4df6eb115540cee4322c1a74078fb6f3b3ebc452a22b3/qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8", size = 3100, upload-time = "2021-08-09T16:47:55.807Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/a1/a5f4bebaa31d109003909809d88aeb0d4b201463a9ea29308d9e4f9e7655/qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6", size = 3478, upload-time = "2021-08-09T16:47:54.637Z" }, -] - -[[package]] -name = "rapidocr" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorlog" }, - { name = "numpy" }, - { name = "omegaconf" }, - { name = "opencv-python" }, - { name = "pillow" }, - { name = "pyclipper" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "shapely" }, - { name = "six" }, - { name = "tqdm" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/5a/9a61f7c3250d7651c2043e763045e1181fe2fd12d0d5879f726f351818ad/rapidocr-3.4.5-py3-none-any.whl", hash = "sha256:6fb21ffb55b3aa49fee2a7c5cc5190851180e5be538b076b2166b7f44213cd5c", size = 15060573, upload-time = "2025-12-18T03:16:15.738Z" }, -] - -[[package]] -name = "requests" -version = "2.32.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, -] - -[[package]] -name = "rich" -version = "14.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, -] - -[[package]] -name = "rknn-toolkit-lite2" -version = "2.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "psutil" }, - { name = "ruamel-yaml" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/3d/e80e1742420f62cb628d40a8bf547d6f7c9dbe4e13dcb7b7e7c0b5620e74/rknn_toolkit_lite2-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda74f1179e15fccb8726054a24898982522784b65bb340b20146955d254e800", size = 569160, upload-time = "2025-04-09T09:39:56.149Z" }, - { url = "https://files.pythonhosted.org/packages/ff/db/64c756f3f06b219e92ff4f0fd4e000870ee49f214d505ff01c8b0275e26d/rknn_toolkit_lite2-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1e4ec691fed900c0e6fde5e7d8eeba17f806aa45092b63b361ee775e2c1b50e", size = 527458, upload-time = "2025-04-09T09:39:58.881Z" }, -] - -[[package]] -name = "ruamel-yaml" -version = "0.18.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447, upload-time = "2025-01-06T14:08:51.334Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729, upload-time = "2025-01-06T14:08:47.471Z" }, -] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, - { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, - { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, - { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, - { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, - { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, - { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, - { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, - { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, - { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011, upload-time = "2024-10-20T10:13:04.377Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" }, - { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" }, - { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583, upload-time = "2024-10-20T10:13:09.658Z" }, - { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" }, -] - -[[package]] -name = "ruff" -version = "0.14.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, - { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, - { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, - { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, -] - -[[package]] -name = "scikit-image" -version = "0.25.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "imageio" }, - { name = "lazy-loader" }, - { name = "networkx" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "scipy" }, - { name = "tifffile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, - { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, - { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, - { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, - { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, - { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, - { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, - { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, - { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, - { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, -] - -[[package]] -name = "scikit-learn" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "joblib" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "threadpoolctl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838, upload-time = "2025-07-18T08:01:11.239Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241, upload-time = "2025-07-18T08:01:13.234Z" }, - { url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677, upload-time = "2025-07-18T08:01:15.649Z" }, - { url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189, upload-time = "2025-07-18T08:01:18.013Z" }, - { url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794, upload-time = "2025-07-18T08:01:20.357Z" }, - { url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431, upload-time = "2025-07-18T08:01:22.77Z" }, - { url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191, upload-time = "2025-07-18T08:01:24.731Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346, upload-time = "2025-07-18T08:01:26.713Z" }, - { url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988, upload-time = "2025-07-18T08:01:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568, upload-time = "2025-07-18T08:01:30.936Z" }, - { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, - { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, - { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, - { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, - { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, - { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, - { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, - { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, -] - -[[package]] -name = "scipy" -version = "1.16.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" }, - { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" }, - { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" }, - { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" }, - { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" }, - { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, - { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, - { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, - { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, - { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, - { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, - { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, - { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, - { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, - { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, - { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, - { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, - { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, - { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, - { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, - { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, - { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, - { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, - { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, - { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, - { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, - { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, - { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, - { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, - { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, - { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, - { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, - { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, -] - -[[package]] -name = "setuptools" -version = "80.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, -] - -[[package]] -name = "shapely" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368, upload-time = "2025-05-19T11:03:55.937Z" }, - { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362, upload-time = "2025-05-19T11:03:57.06Z" }, - { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005, upload-time = "2025-05-19T11:03:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489, upload-time = "2025-05-19T11:04:00.059Z" }, - { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727, upload-time = "2025-05-19T11:04:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311, upload-time = "2025-05-19T11:04:03.134Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982, upload-time = "2025-05-19T11:04:05.217Z" }, - { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872, upload-time = "2025-05-19T11:04:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021, upload-time = "2025-05-19T11:04:08.022Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018, upload-time = "2025-05-19T11:04:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417, upload-time = "2025-05-19T11:04:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224, upload-time = "2025-05-19T11:04:11.903Z" }, - { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982, upload-time = "2025-05-19T11:04:13.224Z" }, - { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122, upload-time = "2025-05-19T11:04:14.477Z" }, - { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437, upload-time = "2025-05-19T11:04:16.203Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479, upload-time = "2025-05-19T11:04:18.497Z" }, - { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107, upload-time = "2025-05-19T11:04:19.736Z" }, - { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355, upload-time = "2025-05-19T11:04:21.035Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871, upload-time = "2025-05-19T11:04:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830, upload-time = "2025-05-19T11:04:23.997Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961, upload-time = "2025-05-19T11:04:25.702Z" }, - { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623, upload-time = "2025-05-19T11:04:27.171Z" }, - { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916, upload-time = "2025-05-19T11:04:28.405Z" }, - { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746, upload-time = "2025-05-19T11:04:29.643Z" }, - { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482, upload-time = "2025-05-19T11:04:30.852Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256, upload-time = "2025-05-19T11:04:32.068Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614, upload-time = "2025-05-19T11:04:33.7Z" }, - { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542, upload-time = "2025-05-19T11:04:34.952Z" }, - { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961, upload-time = "2025-05-19T11:04:36.32Z" }, - { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514, upload-time = "2025-05-19T11:04:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607, upload-time = "2025-05-19T11:04:38.925Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, -] - -[[package]] -name = "simple-websocket" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wsproto" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, -] - -[[package]] -name = "six" -version = "1.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", size = 17103, upload-time = "2022-09-01T12:31:36.968Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384", size = 10165, upload-time = "2022-09-01T12:31:34.186Z" }, -] - -[[package]] -name = "starlette" -version = "0.50.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, -] - -[[package]] -name = "sympy" -version = "1.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/57/3485a1a3dff51bfd691962768b14310dae452431754bfc091250be50dd29/sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8", size = 6722203, upload-time = "2023-05-10T18:23:00.378Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5", size = 5742435, upload-time = "2023-05-10T18:22:14.76Z" }, -] - -[[package]] -name = "threadpoolctl" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/8a/c05f7831beb32aff70f808766224f11c650f7edfd49b27a8fc6666107006/threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355", size = 36266, upload-time = "2023-07-13T14:53:53.299Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/12/fd4dea011af9d69e1cad05c75f3f7202cdcbeac9b712eea58ca779a72865/threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", size = 15539, upload-time = "2023-07-13T14:53:39.336Z" }, -] - -[[package]] -name = "tifffile" -version = "2023.12.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/6c0eadea1ccfcda27e6cce400c366098b5b082138a073f4252fe399f4148/tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8", size = 353467, upload-time = "2023-12-09T20:46:29.203Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/a4/569fc717831969cf48bced350bdaf070cdeab06918d179429899e144358d/tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f", size = 223627, upload-time = "2023-12-09T20:46:26.569Z" }, -] - -[[package]] -name = "tokenizers" -version = "0.21.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, - { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, - { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, - { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, - { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, - { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, - { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, - { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, -] - -[[package]] -name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, -] - -[[package]] -name = "tqdm" -version = "4.66.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/03/00/6a9b3aedb0b60a80995ade30f718f1a9902612f22a1aaf531c85a02987f7/tqdm-4.66.3.tar.gz", hash = "sha256:23097a41eba115ba99ecae40d06444c15d1c0c698d527a01c6c8bd1c5d0647e5", size = 169551, upload-time = "2024-05-02T21:44:05.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374, upload-time = "2024-05-02T21:44:01.541Z" }, -] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20250822" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/85/90a442e538359ab5c9e30de415006fb22567aa4301c908c09f19e42975c2/types_pyyaml-6.0.12.20250822.tar.gz", hash = "sha256:259f1d93079d335730a9db7cff2bcaf65d7e04b4a56b5927d49a612199b59413", size = 17481, upload-time = "2025-08-22T03:02:16.209Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/8e/8f0aca667c97c0d76024b37cffa39e76e2ce39ca54a38f285a64e6ae33ba/types_pyyaml-6.0.12.20250822-py3-none-any.whl", hash = "sha256:1fe1a5e146aa315483592d292b72a172b65b946a6d98aa6ddd8e4aa838ab7098", size = 20314, upload-time = "2025-08-22T03:02:15.002Z" }, -] - -[[package]] -name = "types-requests" -version = "2.32.4.20250809" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/b0/9355adb86ec84d057fea765e4c49cce592aaf3d5117ce5609a95a7fc3dac/types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3", size = 23027, upload-time = "2025-08-09T03:17:10.664Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/6f/ec0012be842b1d888d46884ac5558fd62aeae1f0ec4f7a581433d890d4b5/types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163", size = 20644, upload-time = "2025-08-09T03:17:09.716Z" }, -] - -[[package]] -name = "types-setuptools" -version = "80.9.0.20250822" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/bd/1e5f949b7cb740c9f0feaac430e301b8f1c5f11a81e26324299ea671a237/types_setuptools-80.9.0.20250822.tar.gz", hash = "sha256:070ea7716968ec67a84c7f7768d9952ff24d28b65b6594797a464f1b3066f965", size = 41296, upload-time = "2025-08-22T03:02:08.771Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/2d/475bf15c1cdc172e7a0d665b6e373ebfb1e9bf734d3f2f543d668b07a142/types_setuptools-80.9.0.20250822-py3-none-any.whl", hash = "sha256:53bf881cb9d7e46ed12c76ef76c0aaf28cfe6211d3fab12e0b83620b1a8642c3", size = 63179, upload-time = "2025-08-22T03:02:07.643Z" }, -] - -[[package]] -name = "types-simplejson" -version = "3.20.0.20250822" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/6b/96d43a90cd202bd552cdd871858a11c138fe5ef11aeb4ed8e8dc51389257/types_simplejson-3.20.0.20250822.tar.gz", hash = "sha256:2b0bfd57a6beed3b932fd2c3c7f8e2f48a7df3978c9bba43023a32b3741a95b0", size = 10608, upload-time = "2025-08-22T03:03:35.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/9f/8e2c9e6aee9a2ff34f2ffce6ccd9c26edeef6dfd366fde611dc2e2c00ab9/types_simplejson-3.20.0.20250822-py3-none-any.whl", hash = "sha256:b5e63ae220ac7a1b0bb9af43b9cb8652237c947981b2708b0c776d3b5d8fa169", size = 10417, upload-time = "2025-08-22T03:03:34.485Z" }, -] - -[[package]] -name = "types-ujson" -version = "5.10.0.20250822" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437, upload-time = "2025-08-22T03:02:19.433Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657, upload-time = "2025-08-22T03:02:18.699Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, -] - -[[package]] -name = "uvicorn" -version = "0.35.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.22.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, - { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, - { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, - { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, - { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, - { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, - { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, - { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, - { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, - { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, - { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, - { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, - { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, - { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, - { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, -] - -[[package]] -name = "watchfiles" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/79/0ee412e1228aaf6f9568aa180b43cb482472de52560fbd7c283c786534af/watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", size = 37098, upload-time = "2023-10-13T13:06:39.809Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b9/2667286003dd305b81d3a3aa824d3dfc63dacbf2a96faae09e72d953c430/watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", size = 428210, upload-time = "2023-10-13T13:04:56.894Z" }, - { url = "https://files.pythonhosted.org/packages/a3/87/6793ac60d2e20c9c1883aec7431c2e7b501ee44a839f6da1b747c13baa23/watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", size = 418196, upload-time = "2023-10-13T13:04:58.19Z" }, - { url = "https://files.pythonhosted.org/packages/5d/12/e1d1d220c5b99196eea38c9a878964f30a2b55ec9d72fd713191725b35e8/watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", size = 1380287, upload-time = "2023-10-13T13:04:59.923Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cf/126f0a8683f326d190c3539a769e45e747a80a5fcbf797de82e738c946ae/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", size = 1349653, upload-time = "2023-10-13T13:05:01.622Z" }, - { url = "https://files.pythonhosted.org/packages/20/6e/6cffd795ec65dbc82f15d95b73d3042c1ddaffc4dd25f6c8240bfcf0640f/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", size = 1348844, upload-time = "2023-10-13T13:05:03.805Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2a/f9633279d8937ad84c532997405dd106fa6100e8d2b83e364f1c87561f96/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", size = 1464343, upload-time = "2023-10-13T13:05:05.248Z" }, - { url = "https://files.pythonhosted.org/packages/d7/49/9b2199bbf3c89e7c8dd795fced9dac29f201be8a28a5df0c8ff625737df6/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d", size = 1542858, upload-time = "2023-10-13T13:05:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/35/e0/e8a9c1fe30e98c5b3507ad381abc4d9ee2c3b9c0ae62ffe9c164a5838186/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", size = 1347464, upload-time = "2023-10-13T13:05:08.622Z" }, - { url = "https://files.pythonhosted.org/packages/ba/66/873739dd7defdfaee4b880114de9463fae18ba13ae2ddd784806b0ee33b6/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", size = 1464343, upload-time = "2023-10-13T13:05:10.584Z" }, - { url = "https://files.pythonhosted.org/packages/bd/51/d7539aa258d8f0e5d7b870af8b9b8964b4f88a1e4517eeb8a2efb838e9b3/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", size = 1463338, upload-time = "2023-10-13T13:05:12.671Z" }, - { url = "https://files.pythonhosted.org/packages/ee/92/219c539a2a93b6870fa7b84eace946983126b20a7e15c6c034d8d0472682/watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", size = 267658, upload-time = "2023-10-13T13:05:13.972Z" }, - { url = "https://files.pythonhosted.org/packages/f3/dc/2a8a447b783f5059c4bf7a6bad8fe59375a5a9ce872774763b25c21c2860/watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", size = 280113, upload-time = "2023-10-13T13:05:15.289Z" }, - { url = "https://files.pythonhosted.org/packages/22/15/e4085181cf0210a6ec6eb29fee0c6088de867ee33d81555076a4a2726e8b/watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", size = 268688, upload-time = "2023-10-13T13:05:17.144Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fd/2f009eb17809afd32a143b442856628585c9ce3a9c6d5c1841e44e35a16c/watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", size = 426902, upload-time = "2023-10-13T13:05:18.828Z" }, - { url = "https://files.pythonhosted.org/packages/e0/62/a2605f212a136e06f2d056ee7491ede9935ba0f1d5ceafd1f7da2a0c8625/watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", size = 417300, upload-time = "2023-10-13T13:05:20.116Z" }, - { url = "https://files.pythonhosted.org/packages/69/0e/29f158fa22eb2cc1f188b5ec20fb5c0a64eb801e3901ad5b7ad546cbaed0/watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", size = 1378126, upload-time = "2023-10-13T13:05:21.508Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f3/c67865cb5a174201c52d34e870cc7956b8408ee83ce9a02909d6a2a93a14/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", size = 1348275, upload-time = "2023-10-13T13:05:22.995Z" }, - { url = "https://files.pythonhosted.org/packages/d7/eb/b6f1184d1c7b9670f5bd1e184e4c221ecf25fd817cf2fcac6adc387882b5/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", size = 1347255, upload-time = "2023-10-13T13:05:24.618Z" }, - { url = "https://files.pythonhosted.org/packages/c8/27/e534e4b3fe739f4bf8bd5dc4c26cbc5d3baa427125d8ef78a6556acd6ff4/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", size = 1462845, upload-time = "2023-10-13T13:05:26.531Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ba/a0d1c1c55f75e7e47c8f79f2314f7ec670b5177596f6d27764aecc7048cd/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", size = 1528957, upload-time = "2023-10-13T13:05:28.365Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3a/4e38518c4dff58090c01fc8cc051fa08ac9ae00b361c855075809b0058ce/watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", size = 1345542, upload-time = "2023-10-13T13:05:29.862Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b7/783097f8137a710d5cd9ccbfcd92e4b453d38dab05cfcb5dbd2c587752e5/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", size = 1462238, upload-time = "2023-10-13T13:05:32.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4c/b741eb38f2c408ae9c5a25235f6506b1dda43486ae0fdb4c462ef75bce11/watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", size = 1462406, upload-time = "2023-10-13T13:05:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/77/e4/8d2b3c67364671b0e1c0ce383895a5415f45ecb3e8586982deff4a8e85c9/watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", size = 266789, upload-time = "2023-10-13T13:05:35.606Z" }, - { url = "https://files.pythonhosted.org/packages/da/f2/6b1de38aeb21eb9dac1ae6a1ee4521566e79690117032036c737cfab52fa/watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", size = 280292, upload-time = "2023-10-13T13:05:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a5/7aba9435beb863c2490bae3173a45f42044ac7a48155d3dd42ab49cfae45/watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", size = 268026, upload-time = "2023-10-13T13:05:38.591Z" }, -] - -[[package]] -name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, -] - -[[package]] -name = "websocket-client" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, -] - -[[package]] -name = "websockets" -version = "12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994, upload-time = "2023-10-21T14:21:11.88Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/73/9c1e168a2e7fdf26841dc98f5f5502e91dea47428da7690a08101f616169/websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", size = 124047, upload-time = "2023-10-21T14:19:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/e4/2d/9a683359ad2ed11b2303a7a94800db19c61d33fa3bde271df09e99936022/websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", size = 121282, upload-time = "2023-10-21T14:19:47.739Z" }, - { url = "https://files.pythonhosted.org/packages/95/aa/75fa3b893142d6d98a48cb461169bd268141f2da8bfca97392d6462a02eb/websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", size = 121325, upload-time = "2023-10-21T14:19:49.4Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/51a25e591d645df71ee0dc3a2c880b28e5514c00ce752f98a40a87abcd1e/websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", size = 131502, upload-time = "2023-10-21T14:19:50.683Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ea/0ceeea4f5b87398fe2d9f5bcecfa00a1bcd542e2bfcac2f2e5dd612c4e9e/websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", size = 130491, upload-time = "2023-10-21T14:19:51.835Z" }, - { url = "https://files.pythonhosted.org/packages/e3/05/f52a60b66d9faf07a4f7d71dc056bffafe36a7e98c4eb5b78f04fe6e4e85/websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", size = 130872, upload-time = "2023-10-21T14:19:53.071Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4e/c7361b2d7b964c40fea924d64881145164961fcd6c90b88b7e3ab2c4f431/websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", size = 136318, upload-time = "2023-10-21T14:19:54.41Z" }, - { url = "https://files.pythonhosted.org/packages/0a/31/337bf35ae5faeaf364c9cddec66681cdf51dc4414ee7a20f92a18e57880f/websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", size = 135594, upload-time = "2023-10-21T14:19:55.982Z" }, - { url = "https://files.pythonhosted.org/packages/95/aa/1ac767825c96f9d7e43c4c95683757d4ef28cf11fa47a69aca42428d3e3a/websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", size = 136191, upload-time = "2023-10-21T14:19:57.349Z" }, - { url = "https://files.pythonhosted.org/packages/28/4b/344ec5cfeb6bc417da097f8253607c3aed11d9a305fb58346f506bf556d8/websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", size = 124453, upload-time = "2023-10-21T14:19:59.11Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/6b169cd1957476374f51f4486a3e85003149e62a14e6b78a958c2222337a/websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", size = 124971, upload-time = "2023-10-21T14:20:00.243Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061, upload-time = "2023-10-21T14:20:02.221Z" }, - { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296, upload-time = "2023-10-21T14:20:03.591Z" }, - { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326, upload-time = "2023-10-21T14:20:04.956Z" }, - { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807, upload-time = "2023-10-21T14:20:06.153Z" }, - { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751, upload-time = "2023-10-21T14:20:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176, upload-time = "2023-10-21T14:20:09.212Z" }, - { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246, upload-time = "2023-10-21T14:20:10.423Z" }, - { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466, upload-time = "2023-10-21T14:20:11.826Z" }, - { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083, upload-time = "2023-10-21T14:20:13.451Z" }, - { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460, upload-time = "2023-10-21T14:20:14.719Z" }, - { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985, upload-time = "2023-10-21T14:20:15.817Z" }, - { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370, upload-time = "2023-10-21T14:21:10.075Z" }, -] - -[[package]] -name = "werkzeug" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/02/51/2e0fc149e7a810d300422ab543f87f2bcf64d985eb6f1228c4efd6e4f8d4/werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", size = 803342, upload-time = "2024-05-05T23:10:31.999Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/6e/e792999e816d19d7fcbfa94c730936750036d65656a76a5a688b57a656c4/werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8", size = 227274, upload-time = "2024-05-05T23:10:29.567Z" }, -] - -[[package]] -name = "wsproto" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, -] - -[[package]] -name = "zope-event" -version = "5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350, upload-time = "2023-06-23T06:28:35.709Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824, upload-time = "2023-06-23T06:28:32.652Z" }, -] - -[[package]] -name = "zope-interface" -version = "6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/03/6b85c1df2dca1b9acca38b423d1e226d8ffdf30ebd78bcb398c511de8b54/zope.interface-6.1.tar.gz", hash = "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", size = 293914, upload-time = "2023-10-05T11:24:38.943Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/0b/1d8817b8a3631384a26ff7faa4c1f3e6726f7e4950c3442721cfef2c95eb/zope.interface-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", size = 202441, upload-time = "2023-10-05T11:24:20.414Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1f/43557bb2b6e8537002a5a26af9b899171e26ddfcdf17a00ff729b00c036b/zope.interface-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", size = 202530, upload-time = "2023-10-05T11:24:22.975Z" }, - { url = "https://files.pythonhosted.org/packages/37/a1/5d2b265f4b7371630cad5873d0873965e35ca3de993d11b9336c720f7259/zope.interface-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", size = 249584, upload-time = "2023-10-05T11:49:22.978Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6d/547bfa7465e5b296adba0aff5c7ace1150f2a9e429fbf6c33d6618275162/zope.interface-6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", size = 243737, upload-time = "2023-10-05T11:25:35.439Z" }, - { url = "https://files.pythonhosted.org/packages/db/5f/46946b588c43eb28efe0e46f4cf455b1ed8b2d1ea62a21b0001c6610662f/zope.interface-6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de", size = 249104, upload-time = "2023-10-05T11:25:51.355Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9c/9d3c0e7e5362ea59da3c42b3b2b9fc073db433a0fe3bc6cae0809ccec395/zope.interface-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", size = 204155, upload-time = "2023-10-05T11:39:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/3c/91/68a0bbc97c2554f87d39572091954e94d043bcd83897cd6a779ca85cb5cc/zope.interface-6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", size = 202757, upload-time = "2023-10-05T11:25:05.865Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/850092a8ab7e87a3ea615daf3f822f7196c52592e3e92f264621b4cfe5a2/zope.interface-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", size = 202654, upload-time = "2023-10-05T11:25:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/57/23/508f7f79619ae4e025f5b264a9283efc3c805ed4c0ad75cb28c091179ced/zope.interface-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", size = 254400, upload-time = "2023-10-05T11:49:25.326Z" }, - { url = "https://files.pythonhosted.org/packages/7c/0d/db0ccf0d12767015f23b302aebe98d5eca218aaadc70c2e3908b85fecd2a/zope.interface-6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", size = 248853, upload-time = "2023-10-05T11:25:37.37Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4f/8e80173ebcdefe0ff4164444c22b171cf8bd72533026befc2adf079f3ac8/zope.interface-6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", size = 255127, upload-time = "2023-10-05T11:25:53.819Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d5/81f9789311d9773a02ed048af7452fc6cedce059748dba956c1dc040340a/zope.interface-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", size = 204268, upload-time = "2023-10-05T11:41:22.778Z" }, -] diff --git a/misc/release/archive-version.js b/misc/release/archive-version.js deleted file mode 100755 index 5c0ed9f22f..0000000000 --- a/misc/release/archive-version.js +++ /dev/null @@ -1,45 +0,0 @@ -#! /usr/bin/env node -const { readFileSync, writeFileSync } = require('node:fs'); - -const asVersion = (item) => { - const { label, url } = item; - const [major, minor, patch] = label.substring(1).split('.').map(Number); - return { major, minor, patch, label, url }; -}; - -const nextVersion = process.argv[2]; -if (!nextVersion) { - console.log('Usage: archive-version.js '); - process.exit(1); -} - -const filename = './docs/static/archived-versions.json'; -let versions = JSON.parse(readFileSync(filename)); -const newVersion = { - label: `v${nextVersion}`, - url: `https://docs.v${nextVersion}.archive.immich.app`, -}; - -let lastVersion = asVersion(newVersion); -for (const item of versions) { - const version = asVersion(item); - // only keep the latest patch version for each minor release - if ( - lastVersion.major === version.major && - lastVersion.minor === version.minor && - lastVersion.patch >= version.patch - ) { - versions = versions.filter((item) => item.label !== version.label); - console.log( - `Removed ${version.label} (replaced with ${lastVersion.label})` - ); - continue; - } - - lastVersion = version; -} - -writeFileSync( - filename, - JSON.stringify([newVersion, ...versions], null, 2) + '\n' -); diff --git a/misc/release/notes.tmpl b/misc/release/notes.tmpl deleted file mode 100644 index 697a4e1277..0000000000 --- a/misc/release/notes.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -## Highlights - -{{RELEASE HIGHLIGHTS}} - -As always, please consider supporting the project. - -🎉 Cheers! 🎉 - -- - - - - -And as always, bugs are fixed, and many other improvements also come with this release. diff --git a/misc/release/pump-version.sh b/misc/release/pump-version.sh deleted file mode 100755 index 6be0ddebb9..0000000000 --- a/misc/release/pump-version.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash - -# -# Pump one or both of the server/mobile versions in appropriate files -# -# usage: './scripts/pump-version.sh -s <-m> -# -# examples: -# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50 -# ./scripts/pump-version.sh -s minor -m true # 1.0.0+50 => 1.1.0+51 -# ./scripts/pump-version.sh -m true # 1.0.0+50 => 1.0.0+51 -# - -SERVER_PUMP="false" -MOBILE_PUMP="false" - -while getopts 's:m:' flag; do - case "${flag}" in - s) SERVER_PUMP=${OPTARG} ;; - m) MOBILE_PUMP=${OPTARG} ;; - *) - echo "Invalid args" - exit 1 - ;; - esac -done - -CURRENT_SERVER=$(jq -r '.version' server/package.json) -MAJOR=$(echo "$CURRENT_SERVER" | cut -d '.' -f1) -MINOR=$(echo "$CURRENT_SERVER" | cut -d '.' -f2) -PATCH=$(echo "$CURRENT_SERVER" | cut -d '.' -f3) - -if [[ $SERVER_PUMP == "major" ]]; then - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 -elif [[ $SERVER_PUMP == "minor" ]]; then - MINOR=$((MINOR + 1)) - PATCH=0 -elif [[ $SERVER_PUMP == "patch" ]]; then - PATCH=$((PATCH + 1)) -elif [[ $SERVER_PUMP == "false" ]]; then - echo 'Skipping Server Pump' -else - echo 'Expected for the server argument' - exit 1 -fi - -NEXT_SERVER=$MAJOR.$MINOR.$PATCH - -CURRENT_MOBILE=$(grep "^version: .*+[0-9]\+$" mobile/pubspec.yaml | cut -d "+" -f2) -NEXT_MOBILE=$CURRENT_MOBILE -if [[ $MOBILE_PUMP == "true" ]]; then - set $((NEXT_MOBILE++)) -elif [[ $MOBILE_PUMP == "false" ]]; then - echo 'Skipping Mobile Pump' -else - echo "Fatal: MOBILE_PUMP value $MOBILE_PUMP is invalid" - exit 1 -fi - -if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then - echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER" - - pnpm version "$NEXT_SERVER" --no-git-tag-version - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix server - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix i18n - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix cli - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix web - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix e2e - pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix open-api/typescript-sdk - - # copy version to open-api spec - pnpm install --frozen-lockfile --prefix server - pnpm --prefix server run build - ( cd ./open-api && bash ./bin/generate-open-api.sh ) - - uv version --directory machine-learning "$NEXT_SERVER" - - ./misc/release/archive-version.js "$NEXT_SERVER" -fi - -if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then - echo "Pumping Mobile: $CURRENT_MOBILE => $NEXT_MOBILE" -fi - -sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile -sed -i "s/\"android\.injected\.version\.code\" => $CURRENT_MOBILE,/\"android\.injected\.version\.code\" => $NEXT_MOBILE,/" mobile/android/fastlane/Fastfile -sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml -perl -i -p0e "s/(CFBundleShortVersionString<\/key>\s*)$CURRENT_SERVER(<\/string>)/\${1}$NEXT_SERVER\${2}/s" mobile/ios/Runner/Info.plist - - -echo "IMMICH_VERSION=v$NEXT_SERVER" >>"$GITHUB_ENV" diff --git a/mise.toml b/mise.toml deleted file mode 100644 index 0e7237be20..0000000000 --- a/mise.toml +++ /dev/null @@ -1,50 +0,0 @@ -experimental_monorepo_root = true - -[monorepo] -config_roots = [ - "plugins", - "server", - "cli", - "deployment", - "mobile", - "e2e", - "web", - "docs", - ".github", -] - -[tools] -node = "24.13.0" -flutter = "3.35.7" -pnpm = "10.28.0" -terragrunt = "0.98.0" -opentofu = "1.11.4" -java = "21.0.2" - -[tools."github:CQLabs/homebrew-dcm"] -version = "1.30.0" -bin = "dcm" -postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" - -[settings] -experimental = true -pin = true - -# SDK tasks -[tasks."sdk:install"] -dir = "open-api/typescript-sdk" -run = "pnpm install --filter @immich/sdk --frozen-lockfile" - -[tasks."sdk:build"] -dir = "open-api/typescript-sdk" -env._.path = "./node_modules/.bin" -run = "tsc" - -# i18n tasks -[tasks."i18n:format"] -dir = "i18n" -run = { task = ":i18n:format-fix" } - -[tasks."i18n:format-fix"] -dir = "i18n" -run = "pnpm run format:fix" diff --git a/plugins/.gitignore b/plugins/.gitignore deleted file mode 100644 index 76add878f8..0000000000 --- a/plugins/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist \ No newline at end of file diff --git a/plugins/LICENSE b/plugins/LICENSE deleted file mode 100644 index 53f0fa6953..0000000000 --- a/plugins/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright 2024, The Extism Authors. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/esbuild.js b/plugins/esbuild.js deleted file mode 100644 index 04cb6e85aa..0000000000 --- a/plugins/esbuild.js +++ /dev/null @@ -1,12 +0,0 @@ -const esbuild = require('esbuild'); - -esbuild - .build({ - entryPoints: ['src/index.ts'], - outdir: 'dist', - bundle: true, - sourcemap: true, - minify: false, // might want to use true for production build - format: 'cjs', // needs to be CJS for now - target: ['es2020'] // don't go over es2020 because quickjs doesn't support it - }) \ No newline at end of file diff --git a/plugins/manifest.json b/plugins/manifest.json deleted file mode 100644 index 4d2de275ca..0000000000 --- a/plugins/manifest.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "name": "immich-core", - "version": "2.0.1", - "title": "Immich Core", - "description": "Core workflow capabilities for Immich", - "author": "Immich Team", - "wasm": { - "path": "dist/plugin.wasm" - }, - "filters": [ - { - "methodName": "filterFileName", - "title": "Filter by filename", - "description": "Filter assets by filename pattern using text matching or regular expressions", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "pattern": { - "type": "string", - "title": "Filename pattern", - "description": "Text or regex pattern to match against filename" - }, - "matchType": { - "type": "string", - "title": "Match type", - "enum": [ - "contains", - "regex", - "exact" - ], - "default": "contains", - "description": "Type of pattern matching to perform" - }, - "caseSensitive": { - "type": "boolean", - "default": false, - "description": "Whether matching should be case-sensitive" - } - }, - "required": [ - "pattern" - ] - } - }, - { - "methodName": "filterFileType", - "title": "Filter by file type", - "description": "Filter assets by file type", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "fileTypes": { - "type": "array", - "title": "File types", - "items": { - "type": "string", - "enum": [ - "image", - "video" - ] - }, - "description": "Allowed file types" - } - }, - "required": [ - "fileTypes" - ] - } - }, - { - "methodName": "filterPerson", - "title": "Filter by person", - "description": "Filter by detected person", - "supportedContexts": [ - "person" - ], - "schema": { - "type": "object", - "properties": { - "personIds": { - "type": "array", - "title": "Person IDs", - "items": { - "type": "string" - }, - "description": "List of person to match", - "subType": "people-picker" - }, - "matchAny": { - "type": "boolean", - "default": true, - "description": "Match any name (true) or require all names (false)" - } - }, - "required": [ - "personIds" - ] - } - } - ], - "actions": [ - { - "methodName": "actionArchive", - "title": "Archive", - "description": "Move the asset to archive", - "supportedContexts": [ - "asset" - ], - "schema": {} - }, - { - "methodName": "actionFavorite", - "title": "Favorite", - "description": "Mark the asset as favorite or unfavorite", - "supportedContexts": [ - "asset" - ], - "schema": { - "type": "object", - "properties": { - "favorite": { - "type": "boolean", - "default": true, - "description": "Set favorite (true) or unfavorite (false)" - } - } - } - }, - { - "methodName": "actionAddToAlbum", - "title": "Add to Album", - "description": "Add the item to a specified album", - "supportedContexts": [ - "asset", - "person" - ], - "schema": { - "type": "object", - "properties": { - "albumId": { - "type": "string", - "title": "Album ID", - "description": "Target album ID", - "subType": "album-picker" - } - }, - "required": [ - "albumId" - ] - } - } - ] -} diff --git a/plugins/mise.toml b/plugins/mise.toml deleted file mode 100644 index c1001e574b..0000000000 --- a/plugins/mise.toml +++ /dev/null @@ -1,11 +0,0 @@ -[tools] -"github:extism/cli" = "1.6.3" -"github:webassembly/binaryen" = "version_124" -"github:extism/js-pdk" = "1.5.1" - -[tasks.install] -run = "pnpm install --frozen-lockfile" - -[tasks.build] -depends = ["install"] -run = "pnpm run build" diff --git a/plugins/package-lock.json b/plugins/package-lock.json deleted file mode 100644 index 1d8b9cb1ad..0000000000 --- a/plugins/package-lock.json +++ /dev/null @@ -1,533 +0,0 @@ -{ - "name": "plugins", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "plugins", - "version": "1.0.0", - "license": "AGPL-3.0", - "devDependencies": { - "@extism/js-pdk": "^1.0.1", - "esbuild": "^0.27.0", - "typescript": "^5.3.2" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "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" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@extism/js-pdk": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.1.1.tgz", - "integrity": "sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==", - "dev": true, - "license": "BSD-Clause-3", - "dependencies": { - "urlpattern-polyfill": "^8.0.2" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@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/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/urlpattern-polyfill": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", - "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/plugins/package.json b/plugins/package.json deleted file mode 100644 index abeabe7161..0000000000 --- a/plugins/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "plugins", - "version": "1.0.0", - "description": "", - "main": "src/index.ts", - "scripts": { - "build": "pnpm build:tsc && pnpm build:wasm", - "build:tsc": "tsc --noEmit && node esbuild.js", - "build:wasm": "extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm" - }, - "keywords": [], - "author": "", - "license": "AGPL-3.0", - "devDependencies": { - "@extism/js-pdk": "^1.0.1", - "esbuild": "^0.27.0", - "typescript": "^5.3.2" - } -} diff --git a/plugins/src/index.d.ts b/plugins/src/index.d.ts deleted file mode 100644 index 7f805aafe6..0000000000 --- a/plugins/src/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'main' { - export function filterFileName(): I32; - export function actionAddToAlbum(): I32; - export function actionArchive(): I32; -} - -declare module 'extism:host' { - interface user { - updateAsset(ptr: PTR): I32; - addAssetToAlbum(ptr: PTR): I32; - } -} diff --git a/plugins/src/index.ts b/plugins/src/index.ts deleted file mode 100644 index 9566c02cd8..0000000000 --- a/plugins/src/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -const { updateAsset, addAssetToAlbum } = Host.getFunctions(); - -function parseInput() { - return JSON.parse(Host.inputString()); -} - -function returnOutput(output: any) { - Host.outputString(JSON.stringify(output)); - return 0; -} - -export function filterFileName() { - const input = parseInput(); - const { data, config } = input; - const { pattern, matchType = 'contains', caseSensitive = false } = config; - - const fileName = data.asset.originalFileName || data.asset.fileName || ''; - const searchName = caseSensitive ? fileName : fileName.toLowerCase(); - const searchPattern = caseSensitive ? pattern : pattern.toLowerCase(); - - let passed = false; - - if (matchType === 'exact') { - passed = searchName === searchPattern; - } else if (matchType === 'regex') { - const flags = caseSensitive ? '' : 'i'; - const regex = new RegExp(searchPattern, flags); - passed = regex.test(fileName); - } else { - // contains - passed = searchName.includes(searchPattern); - } - - return returnOutput({ passed }); -} - -export function actionAddToAlbum() { - const input = parseInput(); - const { authToken, config, data } = input; - const { albumId } = config; - - const ptr = Memory.fromString( - JSON.stringify({ - authToken, - assetId: data.asset.id, - albumId: albumId, - }) - ); - - addAssetToAlbum(ptr.offset); - ptr.free(); - - return returnOutput({ success: true }); -} - -export function actionArchive() { - const input = parseInput(); - const { authToken, data } = input; - const ptr = Memory.fromString( - JSON.stringify({ - authToken, - id: data.asset.id, - visibility: 'archive', - }) - ); - - updateAsset(ptr.offset); - ptr.free(); - - return returnOutput({ success: true }); -} diff --git a/plugins/tsconfig.json b/plugins/tsconfig.json deleted file mode 100644 index 86c9e766bf..0000000000 --- a/plugins/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", // Specify ECMAScript target version - "module": "commonjs", // Specify module code generation - "lib": [ - "es2020" - ], // Specify a list of library files to be included in the compilation - "types": [ - "@extism/js-pdk", - "./src/index.d.ts" - ], // Specify a list of type definition files to be included in the compilation - "strict": true, // Enable all strict type-checking options - "esModuleInterop": true, // Enables compatibility with Babel-style module imports - "skipLibCheck": true, // Skip type checking of declaration files - "allowJs": true, // Allow JavaScript files to be compiled - "noEmit": true // Do not emit outputs (no .js or .d.ts files) - }, - "include": [ - "src/**/*.ts" // Include all TypeScript files in src directory - ], - "exclude": [ - "node_modules" // Exclude the node_modules directory - ] -} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index be30451965..261e3278ac 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,37 +1,18 @@ packages: - - cli - - docs - e2e - - e2e-auth-server - i18n - open-api/typescript-sdk - server - - plugins - web - - .github ignoredBuiltDependencies: - '@nestjs/core' - '@parcel/watcher' - - '@scarf/scarf' - '@swc/core' - - canvas - - core-js - - core-js-pure - - cpu-features - - es5-ext - esbuild - - msgpackr-extract - - postman-code-generators - protobufjs - - ssh2 - - utimes onlyBuiltDependencies: - - sharp - '@tailwindcss/oxide' - bcrypt -overrides: - canvas: 2.11.2 - sharp: ^0.34.5 packageExtensions: nestjs-kysely: dependencies: @@ -39,22 +20,6 @@ packageExtensions: nestjs-otel: dependencies: tslib: '*' - '@photo-sphere-viewer/equirectangular-video-adapter': - dependencies: - three: '*' - '@photo-sphere-viewer/video-plugin': - dependencies: - three: '*' - sharp: - dependencies: - node-addon-api: '*' - node-gyp: '*' - '@immich/ui': - dependencies: - tailwindcss: '>=4.1' - tailwind-variants: - dependencies: - tailwindcss: '>=4.1' bcrypt: dependencies: node-addon-api: '*' @@ -63,4 +28,3 @@ dedupePeerDependents: false preferWorkspacePackages: true injectWorkspacePackages: true shamefullyHoist: false -verifyDepsBeforeRun: install diff --git a/readme_i18n/README_ar_JO.md b/readme_i18n/README_ar_JO.md deleted file mode 100644 index e0e13eeaf6..0000000000 --- a/readme_i18n/README_ar_JO.md +++ /dev/null @@ -1,129 +0,0 @@ -

-
- License: AGPLv3 - - Discord - -
-
-

- -

- -

-

حل إدارة الصور والفيديو عالي الأداء مستضاف ذاتيًا

-
- - - -
-

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - ภาษาไทย -

- -## تنصل - -- ⚠️ هذا التطبيق قيد التطوير النشط للغاية -- ⚠️ توقع الأخطاء والتغييرات العاجلة -- ⚠️ **لا تستخدم التطبيق باعتباره الطريقة الوحيدة لتخزين الصور ومقاطع الفيديو الخاصة بك** -- ⚠️ اتبع دائمًا خطة النسخ الاحتياطي [١-٢-٣](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) لصورك ومقاطع الفيديو الثمينة الخاصة بك - - -## محتوى - -- [الوثائق الرسمية](https://docs.immich.app) -- [خريطة الطريق](https://github.com/orgs/immich-app/projects/1) -- [تجريبي](#demo) -- [سمات](#features) -- [مقدمة](https://docs.immich.app/overview/introduction) -- [تعليمات التحميل](https://docs.immich.app/install/requirements) -- [قواعد المساهمة](https://docs.immich.app/overview/support-the-project) - -## توثيق - -يمكنك العثور على الوثائق الرئيسية، بما في ذلك أدلة التثبيت، هنا -https://immich.app - -## تجريبي - -يمكنك الوصول إلى العرض التوضيحي على الويب على -https://demo.immich.app - -بالنسبة لتطبيق الهاتف المحمول، يمكنك استخدام -`https://demo.immich.app` -ل `نقطة نهاية الخادم` - -```bash title="Demo Credential" -تفاصيل تسجيل الدخول -email: demo@immich.app -password: demo -``` - -## نشاط المساهمة -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## سمات - - -| Features | Mobile | Web | -| :--------------------------------------------- | -------- | ----- | -| Upload and view videos and photos | Yes | Yes | -| Auto backup when the app is opened | Yes | N/A | -| Prevent duplication of assets | Yes | Yes | -| Selective album(s) for backup | Yes | N/A | -| Download photos and videos to local device | Yes | Yes | -| Multi-user support | Yes | Yes | -| Album and Shared albums | Yes | Yes | -| Scrubbable/draggable scrollbar | Yes | Yes | -| Support raw formats | Yes | Yes | -| Metadata view (EXIF, map) | Yes | Yes | -| Search by metadata, objects, faces, and CLIP | Yes | Yes | -| Administrative functions (user management) | No | Yes | -| Background backup | Yes | N/A | -| Virtual scroll | Yes | Yes | -| OAuth support | Yes | Yes | -| API Keys | N/A | Yes | -| LivePhoto/MotionPhoto backup and playback | Yes | Yes | -| Support 360 degree image display | No | Yes | -| User-defined storage structure | Yes | Yes | -| Public Sharing | No | Yes | -| Archive and Favorites | Yes | Yes | -| Global Map | Yes | Yes | -| Partner Sharing | Yes | Yes | -| Facial recognition and clustering | Yes | Yes | -| Memories (x years ago) | Yes | Yes | -| Offline support | Yes | No | -| Read-only gallery | Yes | Yes | -| Stacked Photos | Yes | Yes | - -## المساهمين - - - - - -## Star History - - - - - - Star History Chart - - diff --git a/readme_i18n/README_ca_ES.md b/readme_i18n/README_ca_ES.md deleted file mode 100644 index d09362aa0f..0000000000 --- a/readme_i18n/README_ca_ES.md +++ /dev/null @@ -1,98 +0,0 @@ -

-
- Llicència: MIT - - - -
-
-

- -

- -

-

Immich - Solució de còpia de seguretat d'alta rendiment per a fotos i vídeos auto-allotjada

-
- - - -
-

- English - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## Avís legal - -- ⚠️ El projecte està en desenvolupament **molt actiu**. -- ⚠️ Espereu errors i canvis que poden trencar coses. -- ⚠️ **No utilitzeu l'aplicació com a única manera de guardar les vostres fotos i vídeos!** - -## Contingut - -- [Documentació oficial](https://docs.immich.app) -- [Mapa de ruta](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funcionalitats](#funcionalitats) -- [Introducció](https://docs.immich.app/overview/introduction) -- [Instal·lació](https://docs.immich.app/install/requirements) -- [Directrius de contribució](https://docs.immich.app/overview/support-the-project) - -## Documentació - -Podeu trobar la documentació principal, incloent les guies d'instal·lació, a https://immich.app/. - -## Demo - -Podeu accedir a la demostració web a https://demo.immich.app. Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app` com a "URL de punt final del servidor". - -```bash title="Credencials de la demo" -Les credencials -email: demo@immich.app -contrasenya: demo -``` - -# Funcionalitats - -| Característiques | Mòbil | Web | -| -------------------------------------------- | ------ | --- | -| Pujar i veure vídeos i fotos | Sí | Sí | -| Còpia de seguretat automàtica en obrir l'aplicació | Sí | N/A | -| Selecció d'àlbums per a la còpia de seguretat | Sí | N/A | -| Descarregar fotos i vídeos a l'aparell local | Sí | Sí | -| Suport per a múltiples usuaris | Sí | Sí | -| Àlbums i àlbums compartits | Sí | Sí | -| Barra de desplaçament amb funció de rasclet/arrossegament | Sí | Sí | -| Suport per a formats raw | Sí | Sí | -| Visualització de metadades (EXIF, mapa) | Sí | Sí | -| Cerca per metadades, objectes, cares i CLIP | Sí | Sí | -| Funcions administratives (gestió d'usuaris) | No | Sí | -| Còpia de seguretat en segon pla | Sí | N/A | -| Desplaçament virtual | Sí | Sí | -| Suport per a OAuth | Sí | Sí | -| Claus d'API | N/A | Sí | -| Còpia de seguretat i reproducció de LivePhoto | iOS | Sí | -| Estructura d'emmagatzematge definida per l'usuari | Sí | Sí | -| Compartició pública | No | Sí | -| Arxiu i preferits | Sí | Sí | -| Mapa global | No | Sí | -| Compartició amb associats | Sí | Sí | -| Reconeixement facial i agrupament | Sí | Sí | -| Records (fa x anys) | Sí | Sí | -| Suport fora de línia | Sí | No | -| Galeria de només lectura | Sí | Sí | diff --git a/readme_i18n/README_de_DE.md b/readme_i18n/README_de_DE.md deleted file mode 100644 index 488b05abcc..0000000000 --- a/readme_i18n/README_de_DE.md +++ /dev/null @@ -1,130 +0,0 @@ -

-
- Lizenz: MIT - - Discord - -
-
-

- -

- -

-

Immich - Hoch performante, selbst gehostete Backup-Lösung für Fotos und Videos

-
- - - -
-

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - Tiếng Việt - ภาษาไทย -

- -- ⚠️ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel für deine wertvollen Fotos und Videos! - -> [!NOTE] -> Die Hauptdokumentation, einschließlich der Installationsanleitungen, befinden sich unter https://immich.app/. - - -## Inhalt - -- [Offizielle Dokumentation](https://docs.immich.app) -- [Über Immich](https://docs.immich.app/overview/introduction) -- [Installation](https://docs.immich.app/install/requirements) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funktionen](#funktionen) -- [Übersetzungen](https://docs.immich.app/developer/translations) -- [Beitragsrichtlinien](https://docs.immich.app/overview/support-the-project) - -## Demo - -Die Web-Demo kannst Du unter https://demo.immich.app finden. Für die Smartphone-App kannst Du `https://demo.immich.app` als `Server Endpoint URL` angeben. - -### Login Daten - -| Email | Password | -| --------------- | -------- | -| demo@immich.app | demo | - -## Funktionen - -| Funktionen | Mobil | Web | -| ---------------------------------------------------- | ------ | ----- | -| Fotos & Videos hochladen und ansehen | Ja | Ja | -| Automatische Sicherung beim öffnen der App | Ja | k. A | -| Selektive Auswahl von Alben zum Sichern | Ja | k. A | -| Vermeidung von Duplikaten | Ja | Ja | -| Fotos und Videos auf das Gerät herunterladen | Ja | Ja | -| Unterstützung mehrerer Benutzer | Ja | Ja | -| Alben und geteilte Alben | Ja | Ja | -| Scrollbar mit Scrubbing-/Drag-Funktion | Ja | Ja | -| Unterstützung für RAW Formate | Ja | Ja | -| Metadaten anzeigen (EXIF, Karte) | Ja | Ja | -| Suchen nach Metadaten, Objekten, Gesichtern und CLIP | Ja | Ja | -| Administrative Funktionen (Benutzerverwaltung) | Nein | Ja | -| Hintergrundsicherung | Ja | k. A | -| Virtuelles Scrollen | Ja | Ja | -| OAuth Unterstützung | Ja | Ja | -| API-Schlüssel | k. A | Ja | -| LivePhoto/MotionPhoto Sicherung und Wiedergabe | Ja | Ja | -| Unterstützung für 360-Grad-Bilder | Nein | Ja | -| Benutzerdefinierte Speicherstruktur | Ja | Ja | -| Öffentliches Teilen | Ja | Ja | -| Archiv und Favoriten | Ja | Ja | -| Globale Karte | Ja | Ja | -| Partnerfreigabe (Teilen) | Ja | Ja | -| Gesichtserkennung und -gruppierung | Ja | Ja | -| Rückblicke (heute vor x Jahren) | Ja | Ja | -| Offline Unterstützung | Ja | Nein | -| Schreibgeschützte Gallerie | Ja | Ja | -| Gestapelte Bilder | Ja | Ja | -| Tags | Nein | Ja | -| Ordner-Ansicht | Ja | Ja | - - -## Übersetzungen - -Mehr zum Thema Übersetzungen kannst du [hier](https://docs.immich.app/developer/translations) erfahren. - - -Translation status - - -## Repository-Aktivität - -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## Github Sterne - - - - - - Star History Chart - - - -## Mitwirkende - - - - diff --git a/readme_i18n/README_es_ES.md b/readme_i18n/README_es_ES.md deleted file mode 100644 index 032f8c50a8..0000000000 --- a/readme_i18n/README_es_ES.md +++ /dev/null @@ -1,127 +0,0 @@ -

-
- Licencia: MIT - - - -
-
-

- -

- -

-

Immich: Una solución Self-Hosted de alto rendimiento para la copia de seguridad de fotos y videos

-
- - - -
-

- English - Català - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## Advertencia - -- ⚠️ El proyecto está en **activo desarrollo**. -- ⚠️ Es probable que haya errores y cambios disruptivos. -- ⚠️ **¡No utilices la aplicación como única forma de almacenar tus fotos y videos!** -- ⚠️ Siempre sigue el plan de backups [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) para tus fotos y videos. - -## Contenido - -- [Documentación oficial](https://docs.immich.app) -- [Hoja de ruta](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funciones](#funciones) -- [Introducción](https://docs.immich.app/overview/introduction) -- [Instalación](https://docs.immich.app/install/requirements) -- [Directrices para contribuir](https://docs.immich.app/overview/support-the-project) - -## Documentación - -Puedes encontrar la documentación oficial, incluidas las guías de instalación, en . - -## Demo - -Puedes acceder a la demostración web en . Para la aplicación móvil, puedes usar `https://demo.immich.app` en la `URL del servidor`. - -```bash title="Credenciales de la demo" -Credenciales -correo: demo@immich.app -contraseña: demo -``` - -## Funciones - -| Funcionalidades | Móvil | Web | -| ----------------------------------------------------- | ------ | --- | -| Cargar y ver videos y fotos | Sí | Sí | -| Copia de seguridad automática al abrir la aplicación | Sí | N/D | -| Álbum(es) selectivo(s) para copia de seguridad | Sí | N/D | -| Descargar fotos y videos al dispositivo local | Sí | Sí | -| Soporte multiusuario | Sí | Sí | -| Álbum y álbumes compartidos | Sí | Sí | -| Barra de desplazamiento con función de búsqueda | Sí | Sí | -| Soporte para formatos RAW | Sí | Sí | -| Visualización de metadatos (EXIF, map) | Sí | Sí | -| Búsqueda por metadatos, objetos, rostros y CLIP | Sí | Sí | -| Funciones administrativas (gestión de usuarios) | No | Sí | -| Copia de seguridad en segundo plano | Sí | N/D | -| Desplazamiento virtual | Sí | Sí | -| Soporte de OAuth | Sí | Sí | -| Claves de API | N/D | Sí | -| Copia de seguridad y reproducción de LivePhoto | iOS | Sí | -| Estructura de almacenamiento definida por el usuario | Sí | Sí | -| Compartir públicamente | No | Sí | -| Archivar y marcar como favorito | Sí | Sí | -| Mapa global | No | Sí | -| Compartir con colaboradores | Sí | Sí | -| Reconocimiento facial y agrupación | Sí | Sí | -| Recuerdos (hace x años) | Sí | Sí | -| Soporte sin conexión | Sí | No | -| Galería de solo lectura | Sí | Sí | - -## Traducciones - -Lea mas acerca de las traducciones [acá](https://docs.immich.app/developer/translations). - - -Translation status - - -## Actividad del repositorio - -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## Historial de Estrellas - - - - - - Star History Chart - - - -## Contribuidores - - - - diff --git a/readme_i18n/README_fr_FR.md b/readme_i18n/README_fr_FR.md deleted file mode 100644 index 349a0c49ce..0000000000 --- a/readme_i18n/README_fr_FR.md +++ /dev/null @@ -1,123 +0,0 @@ -

-
- License: AGPLv3 - - - -
-
-

- -

- -

-

Immich - Solution de sauvegarde performante et auto-hébergée de photos et de vidéos

-
- - - -
-

- English - Català - Español - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## Clause de non-responsabilité - -- ⚠️ Le projet est en **très fort** développement. -- ⚠️ Attendez-vous à rencontrer des bogues et des changements importants. -- ⚠️ **N'utilisez pas cette application comme seul support de sauvegarde de vos photos et vos vidéos.** -- ⚠️ Ayez toujours un plan de sauvegarde en [3-2-1](https://www.seagate.com/fr/fr/blog/what-is-a-3-2-1-backup-strategy/) pour vos précieuses photos et vidéos ! - -## Sommaire - -- [Documentation officielle](https://docs.immich.app) -- [Feuille de route](https://github.com/orgs/immich-app/projects/1) -- [Démo](#démo) -- [Fonctionnalités](#fonctionnalités) -- [Introduction](https://docs.immich.app/overview/introduction) -- [Installation](https://docs.immich.app/install/requirements) -- [Contribution](https://docs.immich.app/overview/support-the-project) - -## Documentation - -Vous pouvez trouver la documentation principale ainsi que les guides d'installation sur https://immich.app/. - -## Démo - -Vous pouvez accéder à la démo en ligne sur https://demo.immich.app. Pour l'application mobile, vous pouvez utiliser `https://demo.immich.app` dans le champ `URL du point d'accès au serveur` - -```bash title="Identifiants pour la démo" -Les identifiants -email: demo@immich.app -mot de passe: demo -``` - -## Activités - -![Activités](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Image des statistiques Repobeats") - -## Fonctionnalités - -| Fonctionnalités | Mobile | Web | -| ---------------------------------------------------------------- | ------ | --- | -| Téléverser et voir les vidéos et photos | Oui | Oui | -| Sauvegarde automatique quand l'application est ouverte | Oui | N/A | -| Prévention contre la duplication des photos et des vidéos | Oui | Oui | -| Sélection des albums à sauvegarder | Oui | N/A | -| Télécharger les photos et les vidéos sur l'appareil | Oui | Oui | -| Support multi-utilisateur | Oui | Oui | -| Albums et albums partagés | Oui | Oui | -| Barre de défilement mobile | Oui | Oui | -| Support des formats raw | Oui | Oui | -| Vue sur les métadonnées (EXIF, carte) | Oui | Oui | -| Rechercher par métadonnées, objets, faces et CLIP | Oui | Oui | -| Fonctions d'administration (gestion des utilisateurs) | Non | Oui | -| Sauvegarde en tâche de fond | Oui | N/A | -| Défilement virtuel | Oui | Oui | -| Support de l'OAuth | Oui | Oui | -| Clés d'API | N/A | Oui | -| Sauvegarde et lecture des LivePhoto/MotionPhoto | Oui | Oui | -| Support de l'affichage des images à 360° | Non | Oui | -| Structure de stockage définissable | Oui | Oui | -| Partage public | Non | Oui | -| Archives et favoris | Oui | Oui | -| Carte globale | Oui | Oui | -| Partage entre utilisateurs | Oui | Oui | -| Reconnaissance et regroupement facial | Oui | Oui | -| Souvenirs (il y a x années) | Oui | Oui | -| Support hors-ligne | Oui | Non | -| Gallerie en lecture seule | Oui | Oui | -| Empilage de photos | Oui | Oui | - - -## Contributeurs - - - - - -## Historique des favoris - - - - - - Star History Chart - - diff --git a/readme_i18n/README_it_IT.md b/readme_i18n/README_it_IT.md deleted file mode 100644 index 711840fd9d..0000000000 --- a/readme_i18n/README_it_IT.md +++ /dev/null @@ -1,134 +0,0 @@ -

-
- Licenza: AGPLv3 - - Discord - -
-
-

- -

- -

-

Soluzione ad alte prestazioni per la gestione self-hosted di foto e video

-
- - - -
- -

- English - Català - Español - Français - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## Avvertenze - -- ⚠️ Il progetto è in fase di sviluppo **molto attivo**. -- ⚠️ Possono esserci bug o cambiamenti radicali, che possono non essere retrocompatibili (breaking changes). -- ⚠️ **Non usare l’app come unico modo per archiviare le tue foto e i tuoi video.** -- ⚠️ Segui sempre la regola di backup [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) per proteggere i tuoi ricordi e le foto a cui tieni! - -> [!NOTE] -> La documentazione principale, comprese le guide all’installazione, si trova su https://immich.app/. - -## Link utili - -- [Documentazione](https://docs.immich.app) -- [Informazioni](https://docs.immich.app/overview/introduction) -- [Installazione](https://docs.immich.app/install/requirements) -- [Roadmap](https://immich.app/roadmap) -- [Demo](#demo) -- [Funzionalità](#funzionalità) -- [Traduzioni](https://docs.immich.app/developer/translations) -- [Contribuire](https://docs.immich.app/overview/support-the-project) - -## Demo - -Accedi alla demo [qui](https://demo.immich.app). -Per l’app mobile puoi usare `https://demo.immich.app` come `Server Endpoint URL`. - -### Credenziali di accesso - -| Email | Password | -| --------------- | -------- | -| demo@immich.app | demo | - -## Funzionalità - -| Funzionalità | Mobile | Web | -| :------------------------------------------ | ------ | --- | -| Caricare e visualizzare foto e video | Sì | Sì | -| Backup automatico all’apertura dell’app | Sì | N/D | -| Evita la duplicazione dei file | Sì | Sì | -| Backup selettivo di album | Sì | N/D | -| Scaricare foto e video sul dispositivo | Sì | Sì | -| Supporto multi-utente | Sì | Sì | -| Album e album condivisi | Sì | Sì | -| Barra di scorrimento trascinabile | Sì | Sì | -| Supporto ai formati RAW | Sì | Sì | -| Visualizzazione metadati (EXIF, mappa) | Sì | Sì | -| Ricerca per metadati, oggetti, volti, CLIP | Sì | Sì | -| Funzioni amministrative (gestione utenti) | No | Sì | -| Backup in background | Sì | N/D | -| Scorrimento virtuale | Sì | Sì | -| Supporto OAuth | Sì | Sì | -| Chiavi API | N/D | Sì | -| Backup e riproduzione LivePhoto/MotionPhoto | Sì | Sì | -| Supporto immagini a 360° | No | Sì | -| Struttura di archiviazione personalizzata | Sì | Sì | -| Condivisione pubblica | Sì | Sì | -| Archivio e preferiti | Sì | Sì | -| Mappa globale | Sì | Sì | -| Condivisione con partner | Sì | Sì | -| Riconoscimento e raggruppamento facciale | Sì | Sì | -| Ricordi (anni fa) | Sì | Sì | -| Supporto offline | Sì | No | -| Galleria in sola lettura | Sì | Sì | -| Foto impilate | Sì | Sì | -| Tag | No | Sì | -| Vista per cartelle | Sì | Sì | - -## Traduzioni - -Scopri di più sulle traduzioni [qui](https://docs.immich.app/developer/translations). - - -Stato traduzioni - - -## Attività del repository - -![Attività](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Immagine analisi repobeats") - -## Cronologia delle stelle - - - - - - Grafico storico delle stelle - - - -## Contributori - - - - diff --git a/readme_i18n/README_ja_JP.md b/readme_i18n/README_ja_JP.md deleted file mode 100644 index 0e74077895..0000000000 --- a/readme_i18n/README_ja_JP.md +++ /dev/null @@ -1,98 +0,0 @@ -

-
- License: AGPLv3 - - - -
-
-

- -

- -

-

Immich - 高性能なセルフホスト 写真/ビデオバックアップソリューション

-
- - - -
-

- English - Català - Español - Français - Italiano - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## 免責事項 - -- ⚠️ このプロジェクトは **非常に活発に** 開発中です。 -- ⚠️ バグの存在や変更が入ることも予想されます。 -- ⚠️ **写真やビデオを保存する唯一の方法としてこのアプリを使用しないでください。** -- ⚠️ 大切な写真やビデオは、常に [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) のバックアッププランに従ってください! - -## コンテンツ - -- [公式ドキュメント](https://docs.immich.app) -- [ロードマップ](https://github.com/orgs/immich-app/projects/1) -- [デモ](#デモ) -- [機能](#機能) -- [紹介](https://docs.immich.app/overview/introduction) -- [インストール](https://docs.immich.app/install/requirements) -- [コントリビューションガイド](https://docs.immich.app/overview/support-the-project) - -## ドキュメント - -インストールガイドを含む主なドキュメントは、https://immich.app/ です。 - -## デモ - -web デモは https://demo.immich.app からアクセスできます。モバイルアプリの場合、`Server Endpoint URL` には `https://demo.immich.app` を使用することができます - -```bash title="Demo Credential" -The credential -email: demo@immich.app -password: demo -``` - -# 機能 - -| 機能 | モバイル | Web | -| ------------------------------------------- | ------ | --- | -| ビデオや写真のアップロードと表示 | はい | はい | -| アプリを開いたとき自動バックアップ | はい | N/A | -| バックアップ用アルバム選択 | はい | N/A | -| 写真やビデオをローカルデバイスにダウンロード | はい | はい | -| マルチユーザー対応 | はい | はい | -| アルバムと共有アルバム | はい | はい | -| スクラブ可能/ドラッグ可能スクロールバ | はい | はい | -| 生のフォーマットに対応 | はい | はい | -| メタデータ表示(EXIF、地図) | はい | はい | -| メタデータ、オブジェクト、フェース、CLIPによる検索 | はい | はい | -| 管理機能(ユーザー管理) | いいえ | はい | -| バックグラウンドバックアップ | はい | N/A | -| 仮想スクロール | はい | はい | -| OAuth サポート | はい | はい | -| API キー | N/A | はい | -| LivePhoto のバックアップと再生 | iOS | はい | -| ユーザー定義のストレージ構造 | はい | はい | -| 公開シェアリング | いいえ | はい | -| アーカイブとお気に入り | はい | はい | -| グローバルマップ | はい | はい | -| パートナー共有 | はい | はい | -| 思い出(x 年前)顔認識とクラスタリング | はい | はい | -| 思い出(x 年前) | はい | はい | -| オフラインサポート | はい | いいえ | -| 読み取り専用ギャラリー | はい | はい | diff --git a/readme_i18n/README_ko_KR.md b/readme_i18n/README_ko_KR.md deleted file mode 100644 index c2dfd11dd3..0000000000 --- a/readme_i18n/README_ko_KR.md +++ /dev/null @@ -1,132 +0,0 @@ -

-
- 라이선스: AGPLv3 - - Discord - -
-
-

- -

- -

-

고성능 자체 호스팅 사진 및 동영상 관리 솔루션

-
- - - -
-

- -English -Català -Español -Français -Italiano -日本語 -Deutsch -Nederlands -Türkçe -简体中文 -正體中文 -Українська -Русский -Português Brasileiro -Svenska -العربية -ภาษาไทย - -

- -## 주의 사항 - -- ⚠️ 이 프로젝트는 **매우 활발하게** 개발 중입니다. -- ⚠️ 버그와 잦은 변경 사항이 있을 것으로 예상됩니다. -- ⚠️ **사진과 동영상을 이 앱에만 단독으로 저장하지 마세요.** -- ⚠️ 중요한 사진과 동영상을 위해 항상 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 백업 계획을 따르세요! - -> [!NOTE] -> 설치하는 방법을 포함한 주요 문서는 https://immich.app/ 에서 확인할 수 있습니다. - -## 링크 - -- [문서](https://docs.immich.app) -- [소개](https://docs.immich.app/overview/introduction) -- [설치](https://docs.immich.app/install/requirements) -- [로드맵](https://immich.app/roadmap) -- [데모](#데모) -- [기능](#기능) -- [번역](https://docs.immich.app/developer/tranlations) -- [기여](https://docs.immich.app/overview/support-the-project) - -## 데모 - -[이곳](https://demo.immich.app)에서 데모를 체험해보세요. 모바일 앱의 경우, `서버 엔드포인트 URL`에 `https://demo.immich.app`를 입력하세요. - -### 로그인 정보 - -| 이메일 | 비밀번호 | -| --------------- | -------- | -| demo@immich.app | demo | - -## 기능 - -| 기능 | 모바일 | 웹 | -| :------------------------------------ | ------ | ------ | -| 사진, 동영상 업로드 및 보기 | 예 | 예 | -| 앱 실행 시 자동 백업 | 예 | N/A | -| 콘텐츠 중복 방지 | 예 | 예 | -| 백업을 위한 앨범 선택 | 예 | N/A | -| 로컬 기기에 사진 및 동영상 다운로드 | 예 | 예 | -| 여러 사용자 지원 | 예 | 예 | -| 앨범 및 공유 앨범 | 예 | 예 | -| 스크롤/드래그 가능한 스크롤 바 | 예 | 예 | -| RAW 형식 지원 | 예 | 예 | -| 메타데이터 보기 (EXIF, 지도) | 예 | 예 | -| 메타데이터, 사물, 얼굴, CLIP 검색 | 예 | 예 | -| 관리 기능 (사용자 관리) | 아니요 | 예 | -| 백그라운드 백업 | 예 | N/A | -| 가상 스크롤 | 예 | 예 | -| OAuth 지원 | 예 | 예 | -| API 키 | N/A | 예 | -| 라이브 포토/모션 포토 백업 및 재생 | 예 | 예 | -| 360도 이미지 지원 | 아니요 | 예 | -| 사용자 정의 스토리지 구조 | 예 | 예 | -| 공개 공유 | 아니요 | 예 | -| 보관 및 즐겨찾기 | 예 | 예 | -| 글로벌 지도 | 예 | 예 | -| 파트너와 공유 | 예 | 예 | -| 얼굴 인식 및 클러스터링 | 예 | 예 | -| 추억 (~년 전) | 예 | 예 | -| 오프라인 지원 | 예 | 아니요 | -| 읽기 전용 갤러리 | 예 | 예 | -| 사진 스택 | 예 | 예 | - -## 번역 - -번역에 대한 자세한 정보는 [이곳](https://docs.immich.app/developer/translations)에서 확인하세요. - - -번역 현황 - - -## 리포지터리 활동 - -![활동](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## 스타 기록 - - - - - - 스타 기록 차트 - - - -## 기여자 - - - - diff --git a/readme_i18n/README_nl_NL.md b/readme_i18n/README_nl_NL.md deleted file mode 100644 index ac72e9d238..0000000000 --- a/readme_i18n/README_nl_NL.md +++ /dev/null @@ -1,126 +0,0 @@ -

-
- License: AGPLv3 - - - -
-
-

- -

- -

-

Immich - Hoogwaardige, self-hosted back-up oplossing voor foto's en video's

-
- - - -
-

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## Disclaimer - -- ⚠️ Het project wordt momenteel **zeer actief** ontwikkeld. -- ⚠️ Verwacht bugs en ingrijpende wijzigingen. -- ⚠️ **Gebruik de app niet als de enige manier om uw foto's en video's op te slaan.** -- ⚠️ Volg altijd het [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan voor je kostbare foto's en video's! - -## Inhoud - -- [Officiële documentatie](https://docs.immich.app) -- [Toekomstplannen](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Functies](#functies) -- [Introductie](https://docs.immich.app/overview/introduction) -- [Installatie](https://docs.immich.app/install/requirements) -- [Richtlijnen voor bijdragen](https://docs.immich.app/overview/support-the-project) - -## Documentatie - -De belangrijkste documentatie, inclusief installatie handleidingen, zijn te vinden op https://immich.app/. - -## Demo - -Je kunt de demo [hier](https://demo.immich.app/) bekijken. Voor de mobiele app kun je gebruik maken van `https://demo.immich.app` voor de `Server Endpoint URL`. - -### Login gegevens - -| Email | Wachtwoord | -| --------------- | ---------- | -| demo@immich.app | demo | - -# Functies - -| Functies | Mobiel | Web | -|-----------------------------------------------------|--------|-----| -| Upload en bekijk video's en foto's | Ja | Ja | -| Automatische back-up wanneer de app wordt geopend | Ja | NVT | -| Duplicatie van bestanden voorkomen | Ja | Ja | -| Selectieve album(s) voor back-up | Ja | NVT | -| Download foto's en video's naar een lokaal apparaat | Ja | Ja | -| Ondersteuning voor meerdere gebruikers | Ja | Ja | -| Album en gedeelde albums | Ja | Ja | -| Versleepbare scroll balk | Ja | Ja | -| Ondersteuning voor het RAW formaat | Ja | Ja | -| Metagegevensweergave (EXIF, kaart) | Ja | Ja | -| Zoek op metagegevens, objecten, gezichten en CLIP | Ja | Ja | -| Administratieve functies (gebruikersbeheer) | Nee | Ja | -| Back-up op de achtergrond | Ja | NVT | -| Virtueel scrollen | Ja | Ja | -| OAuth-ondersteuning | Ja | Ja | -| API-sleutels | NVT | Ja | -| LivePhoto-back-up en weergave | iOS | Ja | -| Ondersteuning 360 Graden foto weergave | Nee | Ja | -| Door de gebruiker gedefinieerde opslagstructuur | Ja | Ja | -| Openbaar delen | Nee | Ja | -| Archief en Favorieten | Ja | Ja | -| Wereldkaart | Ja | Ja | -| Delen met partner | Ja | Ja | -| Gezichtsherkenning en groepering | Ja | Ja | -| Herinneringen (x jaar geleden) | Ja | Ja | -| Offline-ondersteuning | Ja | Nee | -| Alleen-lezen galerij | Ja | Ja | -| Gestapelde foto's | Ja | Ja | - -## Vertalingen - -Je kunt [hier](https://docs.immich.app/developer/translations) meer over vertalingen lezen. - -## Repository activiteit - -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## Ster geschiedenis - - - - - - Star History Chart - - - -## Contributie-leden - - - - diff --git a/readme_i18n/README_pt_BR.md b/readme_i18n/README_pt_BR.md deleted file mode 100644 index d6f51cd779..0000000000 --- a/readme_i18n/README_pt_BR.md +++ /dev/null @@ -1,143 +0,0 @@ -

-
- Licença: AGPLv3 - - Discord - -
-
-

- -

- -

-

Solução self-hosted de alta performance para backup de fotos e vídeos

-
- - - -
-

- -English -Català -Español -Français -Italiano -日本語 -한국어 -Deutsch -Nederlands -Türkçe -简体中文 -正體中文 -Українська -Русский -Svenska -العربية -Tiếng Việt -ภาษาไทย - -

- -## Avisos - -- ⚠️ Este projeto está sob **desenvolvimento constante**. -- ⚠️ Podem ocorrer bugs e _breaking changes_ (alterações que quebram a - compatibilidade com versões anteriores). -- ⚠️ **Não use esta solução como a única forma de fazer backup das suas fotos e - vídeos.** -- ⚠️ Sempre siga o plano - [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup - para as suas mídias preciosas! - -> [!NOTE] -> Você pode encontrar a documentação principal, incluindo guias de instalação, em https://immich.app/. - -## Links - -- [Documentação](https://docs.immich.app) -- [Sobre](https://docs.immich.app/overview/introduction) -- [Instalação](https://docs.immich.app/install/requirements) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) -- [Demonstração](#demonstração) -- [Funcionalidades](#funcionalidades) -- [Traduções](https://docs.immich.app/developer/translations) -- [Diretrizes de Contribuição](https://docs.immich.app/overview/support-the-project) - -## Demonstração - -Acesse a demonstração [aqui](https://demo.immich.app). No aplicativo para dispositivos móveis, você pode usar -`https://demo.immich.app` no campo `Server Endpoint URL` - -### Credenciais de login - -| Email | Senha | -| --------------- | ----- | -| demo@immich.app | demo | - -## Atividades - -![Atividades](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Imagem de Analytics do Repobeats") - -## Funcionalidades - -| Funcionalidades | Aplicativo Móvel | Web | -| :-------------------------------------------------- | ---------------- | --- | -| Fazer upload e visualizar fotos e vídeos | Sim | Sim | -| Backup automático ao abrir o aplicativo | Sim | N/A | -| Prevenir a duplicação de arquivos | Sim | Sim | -| Backup de álbuns específicos | Sim | N/A | -| Baixar fotos e vídeos para o dispositivo | Sim | Sim | -| Suporte multi-usuários | Sim | Sim | -| Criação de álbuns e álbuns compartilhados | Sim | Sim | -| Barra de rolagem arrastável | Sim | Sim | -| Suporta formatos RAW | Sim | Sim | -| Visualização de metadados (EXIF, mapa) | Sim | Sim | -| Pesquisar por metadados, objetos, rostos, e CLIP | Sim | Sim | -| Funções administrativas (gerenciamento de usuários) | Não | Sim | -| Backup em segundo plano | Sim | N/A | -| Rolagem virtual | Sim | Sim | -| Suporte OAuth | Sim | Sim | -| Chaves de API | N/A | Sim | -| Backup e reprodução de LivePhoto/MotionPhoto | Sim | Sim | -| Visualização de imagens 360º | Não | Sim | -| Estrutura de armazenamento definida pelo usuário | Sim | Sim | -| Compartilhar com o público | Sim | Sim | -| Arquivo e Favoritos | Sim | Sim | -| Mapa Global | Sim | Sim | -| Compartilhamento com parceiro | Sim | Sim | -| Reconhecimento facial e agrupamento | Sim | Sim | -| Memórias (x anos atrás) | Sim | Sim | -| Suporte off-line | Sim | Não | -| Galeria em modo apenas leitura | Sim | Sim | -| Empilhamento de fotos | Sim | Sim | - -## Traduções - -Leia mais sobre as traduções -[aqui](https://docs.immich.app/developer/translations). - - -Status da tradução - - -## Atividade do repositório - -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Imagem de análise de atividade Repobeats") - -## Histórico de estrelas - - - - - - Gráfico de histórico de estrelas - - - -## Contribuidores - - - - diff --git a/readme_i18n/README_ru_RU.md b/readme_i18n/README_ru_RU.md deleted file mode 100644 index 9c60e5f772..0000000000 --- a/readme_i18n/README_ru_RU.md +++ /dev/null @@ -1,135 +0,0 @@ -

-
- License: AGPLv3 - - Discord - -
-
-

- -

- -

-

Высокопроизводительное автономное решение для хранения и группировки фото и видео

-
- - - -
-

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Português Brasileiro - Svenska -
- العربية - Tiếng Việt - ภาษาไทย -

- -## Предупреждение - -- ⚠️ Этот проект находится **в очень активной** разработке. -- ⚠️ Ожидайте недоработки и глобальные изменения. -- ⚠️ **Не используйте это приложение как единственное хранилище своих фото и видео.** -- ⚠️ Всегда следуйте [плану резервного копирования «3-2-1»](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/ "Стратегии резервного копирования: Почему стратегия резервного копирования «3-2-1» — лучшая") для ваших драгоценных фотографий и видео! - - -> [!NOTE] -> Инструкции по установке и документация по ссылке https://immich.app/ - -## Содержание - -- [Официальная документация](https://docs.immich.app) -- [Введение](https://docs.immich.app/overview/introduction) -- [Установка](https://docs.immich.app/install/requirements) -- [План разработки](https://github.com/orgs/immich-app/projects/1) -- [Демо](#demo) -- [Возможности](#features) -- [Перевод](https://docs.immich.app/developer/translations) -- [Гид по участию и поддержке проекта](https://docs.immich.app/overview/support-the-project) - -## Демо - -Вы можете опробовать [Web демонстрационную версию](https://demo.immich.app/). В мобильном приложении укажите `https://demo.immich.app` в поле `URL-адрес сервера` - -### Данные для входа - -| Email | Пароль | -| --------------- | -------- | -| demo@immich.app | demo | - -## Возможности - -| Возможности | Приложение | Веб | -| :----------------------------------------------------- | ---------- | --- | -| Загрузка, просмотр видео и фото | Да | Да | -| Автоматический бекап при запуске приложения | Да | Н/Д | -| Предотвращение дупликации данных | Да | Да | -| Выбор альбома (-ов) для бекапа | Да | Н/Д | -| Скачивание фото и видео с сервера на устройство | Да | Да | -| Поддержка нескольких аккаунтов пользователей | Да | Да | -| Альбомы и общие альбомы | Да | Да | -| Прокручиваемая/перетаскиваемая полоса прокрутки | Да | Да | -| Поддержка raw-форматов | Да | Да | -| Просмотр метаданных (EXIF, map) | Да | Да | -| Поиск до метаданным, объектам, лицам и CLIP | Да | Да | -| Функции администрирования (управление пользователями) | Нет | Да | -| Фоновое резервное копирование | Да | Н/Д | -| Виртуальная прокрутка | Да | Да | -| Поддержка OAuth | Да | Да | -| API Ключи | Н/Д | Да | -| LivePhoto/MotionPhoto воспроизведение и бекап | Да | Да | -| Отображение 360° изображений | Нет | Да | -| Настраиваемая структура хранилища | Да | Да | -| Общий доступ к контенту | Да | Да | -| Архив и избранное | Да | Да | -| Мировая карта | Да | Да | -| Совместное использование | Да | Да | -| Распознавание и группировка по лицам | Да | Да | -| Воспоминания (в этот день x лет назад) | Да | Да | -| Работа без интернета | Да | Нет | -| Галереи только для просмотра | Да | Да | -| Коллажи | Да | Да | -| Метки (теги) | Нет | Да | -| Просмотр папкой | Да | Да | - -## Перевод - -Всё про перевод проекта [Здесь](https://docs.immich.app/developer/translations). - - -Translation status - - -## Активность репозитория - -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## История звёзд - - - - - - Star History Chart - - - -## Участники - - - - diff --git a/readme_i18n/README_sv_SE.md b/readme_i18n/README_sv_SE.md deleted file mode 100644 index a421c23c2e..0000000000 --- a/readme_i18n/README_sv_SE.md +++ /dev/null @@ -1,125 +0,0 @@ -

-
- License: AGPLv3 - - Discord - -
-
-

- -

- -

-

Högpresterande self-hostad lösning för hantering av foton och videor

-
- - - -
-

- - English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - العربية - ภาษาไทย -

- -## Ansvarsfriskrivning - -- ⚠️ Projektet är under **mycket aktiv** utveckling. -- ⚠️ Förvänta dig buggar och brytande förändringar. -- ⚠️ **Använd inte appen som enda lagringssätt för dina foton och videor.** -- ⚠️ Tillämpa alltid [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/)-strategin för säkerhetskopiering av dina foton och videor! - -## Innehåll - -- [Officiell Dokumentation](https://docs.immich.app) -- [Roadmap](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Funktioner](#features) -- [Introduktion](https://docs.immich.app/overview/introduction) -- [Installation](https://docs.immich.app/install/requirements) -- [Riktlinjer för Bidrag](https://docs.immich.app/overview/support-the-project) - -## Dokumentation - -Dokumentation och installationsguider hittas på https://imiich.app/. - -## Demo - -Ett webb-demo finns att testa på https://demo.immich.app. Använd `https://demo.immich.app` i mobilappen som `Server Endpoint URL` - -```bash title="Inloggningsuppgifter För Demo" -Inloggsningsuppgifter -epost: demo@immich.app -lösenord: demo -``` - -## Aktiviteter - -![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## Funktioner - - -| Funktioner | Mobil | Webb | -| :-----------------------------------------------------------| ----- | ---- | -| Ladda upp och visa videor och foton | Ja | Ja | -| Automatisk säkerhetskopiering när appen öppnas | Ja | N/A | -| Förhindra duplicering av resurser | Ja | Ja | -| Valbara album för säkerhetskopiering | Ja | N/A | -| Ladda ner foton och videor lokalt till en enhet | Ja | Ja | -| Stöd för flera användare | Ja | Ja | -| Album and Delade album | Ja | Ja | -| Rullningslist | Ja | Ja | -| Stöd för råformat | Ja | Ja | -| Visning av metadata (EXIF, karta) | Ja | Ja | -| Sök via metadata, objekt, ansikten och CLIP | Ja | Ja | -| Administrative functions (user management) | Nej | Ja | -| Administrativa funktioner (hantering av användare) | Nej | Ja | -| Säkerhetskopiering i bakgrunden | Ja | N/A | -| Virtuell skroll | Ja | Ja | -| Stöd för OAuth | Ja | Ja | -| API-nycklar | N/A | Ja | -| Säkerhetskopiering och uppspelning av LivePhoto/MotionPhoto | Ja | Ja | -| Stöd för visning av 360-graders bilder | Nej | Ja | -| Användardefinierad lagringsstruktur | Ja | Ja | -| Publik Delning | Nej | Ja | -| Arkiv och Favoriter | Ja | Ja | -| Världskarta | Ja | Ja | -| Dela med Partner | Ja | Ja | -| Ansiktsigenkänning och klustring | Ja | Ja | -| Minnen (x år sedan) | Ja | Ja | -| Offline-stöd | Ja | Nej | -| Skrivskyddat galleri | Ja | Ja | -| Bildstapling | Ja | Ja | - -## Medverkande - - - - - -## Stjärn-Historik - - - - - - Star History Chart - - diff --git a/readme_i18n/README_th_TH.md b/readme_i18n/README_th_TH.md deleted file mode 100644 index 22a7f6a501..0000000000 --- a/readme_i18n/README_th_TH.md +++ /dev/null @@ -1,133 +0,0 @@ -

-
- License: AGPLv3 - - Discord - -
-
-

- -

- -

- -

โซลูชันการจัดการภาพถ่ายและวิดีโอแบบโฮสต์เองที่มีประสิทธิภาพสูง

-
- - - - -
- -

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - Tiếng Việt -

- - -> [!WARNING] -> ⚠️ ปฏิบัติตามแผนการสำรองข้อมูลแบบ [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) สำหรับภาพถ่ายและวิดีโอที่สำคัญของคุณอยู่เสมอ -> - - -> [!NOTE] -> คุณสามารถหาคู่มือหลัก รวมถึงคู่มือการติดตั้ง ได้ที่ https://immich.app/ - -## ลิงก์ - -- [คู่มือ](https://docs.immich.app) -- [เกี่ยวกับ](https://docs.immich.app/overview/introduction) -- [การติดตั้ง](https://docs.immich.app/install/requirements) -- [โรดแมป](https://immich.app/roadmap) -- [สาธิต](#สาธิต) -- [คุณสมบัติ](#คุณสมบัติ) -- [การแปลภาษา](https://docs.immich.app/developer/translations) -- [สนับสนุนโพรเจกต์](https://docs.immich.app/overview/support-the-project) - -## สาธิต - -เข้าถึงการสาธิตได้ [ที่นี่](https://demo.immich.app) สำหรับแอปมือถือ คุณสามารถใช้ `https://demo.immich.app` เป็น `Server Endpoint URL` - -### ข้อมูลการเข้าสู่ระบบ - -| อีเมล | รหัสผ่าน | -| --------------- | -------- | -| demo@immich.app | demo | - -## คุณสมบัติ - -| คุณสมบัติ | มือถือ | เว็บ | -| :----------------------------------------- | ------ | ------ | -| อัปโหลดและดูวิดีโอและภาพถ่าย | ใช่ | ใช่ | -| การสำรองข้อมูลอัตโนมัติเมื่อเปิดแอป | ใช่ | N/A | -| ป้องกันการซ้ำของไฟล์ | ใช่ | ใช่ | -| เลือกอัลบั้มสำหรับสำรองข้อมูล | ใช่ | N/A | -| ดาวน์โหลดภาพถ่ายและวิดีโอไปยังอุปกรณ์ | ใช่ | ใช่ | -| รองรับผู้ใช้หลายคน | ใช่ | ใช่ | -| อัลบั้มและอัลบั้มแชร์ | ใช่ | ใช่ | -| แถบเลื่อนแบบลากได้ | ใช่ | ใช่ | -| รองรับรูปแบบไฟล์ RAW | ใช่ | ใช่ | -| ดูข้อมูลเมตาดาต้า (EXIF, แผนที่) | ใช่ | ใช่ | -| ค้นหาจากข้อมูลเมตาดาต้า วัตถุ ใบหน้า และ CLIP | ใช่ | ใช่ | -| ฟังก์ชันการจัดการผู้ดูแลระบบ | ไม่ใช่ | ใช่ | -| การสำรองข้อมูลพื้นหลัง | ใช่ | N/A | -| การเลื่อนแบบเสมือน | ใช่ | ใช่ | -| รองรับ OAuth | ใช่ | ใช่ | -| คีย์ API | N/A | ใช่ | -| การสำรองและเล่น LivePhoto/MotionPhoto | ใช่ | ใช่ | -| รองรับการแสดงภาพ 360 องศา | ไม่ใช่ | ใช่ | -| โครงสร้างการจัดเก็บข้อมูลที่ผู้ใช้กำหนดเอง | ใช่ | ใช่ | -| การแชร์สาธารณะ | ใช่ | ใช่ | -| การจัดเก็บและรายการโปรด | ใช่ | ใช่ | -| แผนที่ทั่วโลก | ใช่ | ใช่ | -| การแชร์กับคู่หู | ใช่ | ใช่ | -| ระบบจดจำใบหน้าและการจัดกลุ่ม | ใช่ | ใช่ | -| ความทรงจำ (x ปีที่แล้ว) | ใช่ | ใช่ | -| รองรับแบบออฟไลน์ | ใช่ | ไม่ใช่ | -| แกลเลอรีแบบอ่านอย่างเดียว | ใช่ | ใช่ | -| ภาพถ่ายซ้อนกัน | ใช่ | ใช่ | - -## การแปลภาษา - -อ่านเพิ่มเติมเกี่ยวกับการแปล [ที่นี่](https://docs.immich.app/developer/translations) - - - สถานะการแปล - - -## กิจกรรมของ Repository - -![กิจกรรม](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "ภาพการวิเคราะห์ของ Repobeats") - -## ประวัติการให้ดาว - - - - - - แผนภูมิประวัติการให้ดาว - - - -## ผู้ร่วมพัฒนา - - - - diff --git a/readme_i18n/README_tr_TR.md b/readme_i18n/README_tr_TR.md deleted file mode 100644 index 46aef49745..0000000000 --- a/readme_i18n/README_tr_TR.md +++ /dev/null @@ -1,96 +0,0 @@ -

-
- License: AGPLv3 - - - -
-
-

- -

- -

-

Immich - Yüksek performanslı, kendine ait barındırılan fotoğraf ve video yedekleme çözümü

-
- - - -
-

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - ภาษาไทย -

- -## Feragatname - -- ⚠️ Proje **çok aktif** bir şekilde geliştirilmektedir. -- ⚠️ Hatalar ve uygulama yapısını bozan değişiklikler olabilir. -- ⚠️ **Uygulamayı, fotoğraflarınızı ve videolarınızı saklamanın tek yöntemi olarak kullanmayın!** - -## Content - -- [Resmi Belgeler](https://docs.immich.app) -- [Yol Haritası](https://github.com/orgs/immich-app/projects/1) -- [Demo](#demo) -- [Özellikler](#özellikler) -- [Giriş](https://docs.immich.app/overview/introduction) -- [Kurulum](https://docs.immich.app/install/requirements) -- [Katkı Sağlama Rehberi](https://docs.immich.app/overview/support-the-project) - -## Belgeler - -Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz. - -## Demo - -Web demo adresi: https://demo.immich.app. Mobil uygulama için `Server Endpoint URL` olarak `https://demo.immich.app` adresini kullanabilirsiniz. - -```bash title="Demo Bilgileri" -Giriş bilgileri: -email: demo@immich.app -password: demo -``` - -# Özellikler - -| Özellikler | Mobile | Web | -| ----------------------------------------------------| ------ | --- | -| Videoları ve fotoğrafları yükleme ve görüntüleme | Evet | Evet | -| Uygulama açıldığında otomatik yedekleme | Evet | N/A | -| Yedekleme için seçilebilir albüm(ler) | Evet | N/A | -| Fotoğrafları ve videoları yerel cihaza yükleme | Evet | Evet | -| Çoklu kullanıcı desteği | Evet | Evet | -| Albüm ve paylaşılan albümler | Evet | Evet | -| Silinebilir/sürüklenebilir kaydırma çubuğu | Evet | Evet | -| RAW (HEIC, HEIF, DNG, Apple ProRaw) format desteği | Evet | Evet | -| Metadata'ya uygun görüntüleme (EXIF, map) | Evet | Evet | -| Metadata, objects, faces ve CLIP'e göre arama | Evet | Evet | -| Yönetimsel işlevler (kullanıcı yönetimi) | Hayır | Evet | -| Arka planda yedekleme | Evet | N/A | -| Sanal kaydırma | Evet | Evet | -| OAuth desteği | Evet | Evet | -| API anahtarları | N/A | Evet | -| LivePhoto yedekleme ve oynatma | iOS | Evet | -| Kullanıcı tanımlı depolama yapısı | Evet | Evet | -| Herkese açık paylaşım | Hayır | Evet | -| Arşiv ve Favoriler | Evet | Evet | -| Dünya haritası | Hayır | Evet | -| Partner paylaşımı | Evet | Evet | -| Yüz tanıma ve kümeleme | Hayır | Evet | -| Çevrimdışı destek | Evet | Hayır| diff --git a/readme_i18n/README_uk_UA.md b/readme_i18n/README_uk_UA.md deleted file mode 100644 index 297054ee42..0000000000 --- a/readme_i18n/README_uk_UA.md +++ /dev/null @@ -1,134 +0,0 @@ -

-
- License: AGPLv3 - - Discord - -
-
-

- -

- -

-

Високопродуктивне рішення для управління фото та відео на власному хостингу

-
- - - -
- -

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Русский - Português Brasileiro - Svenska - العربية - Tiếng Việt - ภาษาไทย -

- -## Застереження - -- ⚠️ Цей проєкт перебуває **в дуже активній** розробці. -- ⚠️ Очікуйте безліч помилок і глобальних змін. -- ⚠️ **Не використовуйте цей застосунок як єдине сховище своїх фото та відео.** -- ⚠️ Завжди дотримуйтесь [плану резервного копіювання 3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) для ваших дорогоцінних фотографій та відео! - -> [!NOTE] -> Основну документацію, зокрема посібники зі встановлення, можна знайти за адресою https://immich.app/. - -## Посилання - -- [Документація](https://docs.immich.app) -- [Про проєкт](https://docs.immich.app/overview/introduction) -- [Встановлення](https://docs.immich.app/install/requirements) -- [Дорожня карта](https://immich.app/roadmap) -- [Демо](#демо) -- [Функції](#функції) -- [Переклади](https://docs.immich.app/developer/translations) -- [Гід для розробки проєкту](https://docs.immich.app/overview/support-the-project) - -## Демо - -Доступ до демо-версії [тут](https://demo.immich.app). Для мобільного застосунку ви можете використовувати `https://demo.immich.app` в якості `Server Endpoint URL`. - -### Облікові дані для входу - -| Електронна пошта | Пароль | -| ---------------- | ------ | -| demo@immich.app | demo | - -## Функції - -| Функції | Додаток | Веб | -| :------------------------------------------------------- | ------- | --- | -| Завантаження та перегляд відео й фото | Так | Так | -| Автоматичне резервне копіювання при відкритті застосунку | Так | Н/Д | -| Запобігання дублюванню файлів | Так | Так | -| Вибір альбомів для резервного копіювання | Так | Н/Д | -| Завантаження фото та відео на локальний пристрій | Так | Так | -| Підтримка кількох користувачів | Так | Так | -| Альбоми та спільні альбоми | Так | Так | -| Прокрутка/перетягування повзунка | Так | Так | -| Підтримка сирих форматів (RAW) | Так | Так | -| Перегляд метаданих (EXIF, карта) | Так | Так | -| Пошук за метаданими, об'єктами, обличчями та CLIP | Так | Так | -| Адміністративні функції (управління користувачами) | Ні | Так | -| Фонове резервне копіювання | Так | Н/Д | -| Віртуальна прокрутка | Так | Так | -| Підтримка OAuth | Так | Так | -| API ключі | Н/Д | Так | -| Резервне копіювання та відтворення LivePhoto/MotionPhoto | Так | Так | -| Підтримка відображення 360-градусних зображень | Ні | Так | -| Користувацька структура зберігання | Так | Так | -| Публічний доступ | Так | Так | -| Архів і Вибране | Так | Так | -| Глобальна карта | Так | Так | -| Спільний доступ між партнерами | Так | Так | -| Розпізнавання облич та їх кластеризація | Так | Так | -| Спогади (x років тому) | Так | Так | -| Офлайн підтримка | Так | Ні | -| Галерея тільки для читання | Так | Так | -| Стекування фото | Так | Так | -| Теги | Ні | Так | -| Перегляд папок | Ні | Так | - -## Переклади - -Більше про переклади [тут](https://docs.immich.app/developer/translations). - - -Статус перекладів - - -## Активність репозиторію - -![Діяльність](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Зображення аналітики Repobeats") - -## Історія зірок - - - - - - Star History Chart - - - -## Автори - - - - diff --git a/readme_i18n/README_vi_VN.md b/readme_i18n/README_vi_VN.md deleted file mode 100644 index b6b22ff610..0000000000 --- a/readme_i18n/README_vi_VN.md +++ /dev/null @@ -1,134 +0,0 @@ -

-
- Giấy phép: AGPLv3 - - Discord - -
-
-

- -

- -

-

Giải pháp quản lý ảnh và video tự lưu trữ hiệu suất cao

-
- - - -
-

- -English -Català -Español -Français -Italiano -日本語 -한국어 -Deutsch -Nederlands -Türkçe -简体中文 -正體中文 -Українська -Русский -Português Brasileiro -Svenska -العربية -Tiếng Việt -ภาษาไทย - -

- -## Tuyên bố miễn trừ trách nhiệm - -- ⚠️ Dự án đang được phát triển **rất tích cực**. -- ⚠️ Dự kiến ​​sẽ có lỗi và thay đổi đột ngột. -- ⚠️ **Không sử dụng ứng dụng như là cách duy nhất để lưu trữ ảnh và video của bạn.** -- ⚠️ Luôn tuân thủ kế hoạch sao lưu [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) cho những bức ảnh và video quý giá của bạn! - -> [!NOTE] -> Bạn có thể tìm thấy tài liệu chính, bao gồm hướng dẫn cài đặt, tại https://immich.app/. - -## Liên kết - -- [Tài liệu](https://docs.immich.app) -- [Giới thiệu](https://docs.immich.app/overview/introduction) -- [Cài đặt](https://docs.immich.app/install/requirements) -- [Lộ trình](https://immich.app/roadmap) -- [Demo](#demo) -- [Tính năng](#Tính-năng) -- [Dịch thuật](https://docs.immich.app/developer/translations) -- [Đóng góp](https://docs.immich.app/overview/support-the-project) - -## Demo - -Truy cập bản demo [tại đây](https://demo.immich.app). Đối với ứng dụng di động, bạn có thể sử dụng `https://demo.immich.app` cho `Server Endpoint URL` - -### Thông tin đăng nhập - -| Email | Mật khẩu | -| --------------- | -------- | -| demo@immich.app | demo | - -## Tính năng - -| Tính năng | Mobile | Web | -| :--------------------------------------------------- | ------ | ----- | -| Tải lên và xem video, ảnh | Có | Có | -| Tự động sao lưu khi ứng dụng được mở | Có | N/A | -| Ngăn chặn sự trùng lặp nội dung | Có | Có | -| Album được chọn để sao lưu | Có | N/A | -| Tải ảnh và video xuống thiết bị cục bộ | Có | Có | -| Hỗ trợ nhiều người dùng | Có | Có | -| Album và Album được chia sẻ | Có | Có | -| Thanh cuộn có thể chà / kéo | Có | Có | -| Hỗ trợ định dạng raw | Có | Có | -| Xem metadata (EXIF, bản đồ) | Có | Có | -| Tìm kiếm theo metadata, đối tượng, khuôn mặt và CLIP | Có | Có | -| Chức năng quản trị (quản lý người dùng) | Không | Có | -| Sao lưu trong nền | Có | N/A | -| Cuộn ảo | Có | Có | -| Hỗ trợ OAuth | Có | Có | -| API Keys | N/A | Có | -| Sao lưu và phát lại Live Photo/Motion Photo | Có | Có | -| Hỗ trợ hiển thị hình ảnh 360 độ | Không | Có | -| Cấu trúc lưu trữ do người dùng xác định | Có | Có | -| Chia sẻ công khai | Có | Có | -| Lưu trữ và Yêu thích | Có | Có | -| Bản đồ toàn cầu | Có | Có | -| Chia sẻ đối tác | Có | Có | -| Nhận dạng khuôn mặt và phân cụm | Có | Có | -| Kỷ niệm (x năm trước) | Có | Có | -| Hỗ trợ ngoại tuyến | Có | Không | -| Thư viện chỉ đọc | Có | Có | -| Ảnh xếp chồng | Có | Có | - -## Dịch thuật - -Đọc thêm về dịch thuật [tại đây](https://docs.immich.app/developer/translations). - - -Tình trạng dịch thuật - - -## Hoạt động của repository - -![Hoạt động](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Hình ảnh phân tích Repobeats") - -## Lịch sử Đánh dấu sao - - - - - - Biểu đồ Lịch sử Đánh dấu - - - -## Người đóng góp - - - - diff --git a/readme_i18n/README_zh_CN.md b/readme_i18n/README_zh_CN.md deleted file mode 100644 index b48e69f94d..0000000000 --- a/readme_i18n/README_zh_CN.md +++ /dev/null @@ -1,138 +0,0 @@ -

-
- License: AGPLv3 - - - -
-
-

- -

- -

-

高性能的照片和视频自托管解决方案

-

-请注意: 此 README 不是由 Immich 团队维护, 而是依靠贡献者来更新的,这意味着它可能并不会被及时更新。感谢理解。 -

-
- - - -
-

- - English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - Tiếng Việt - ภาษาไทย - -

- -## 免责声明 - -- ⚠️ 本项目正在 **非常活跃** 地开发中。 -- ⚠️ 可能存在 bug 或者随时有重大变更。 -- ⚠️ **不要把本软件作为您存储照片或视频的唯一方式。** -- ⚠️ 为了您宝贵的照片与视频,请始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案! - -> [!NOTE] -> 完整的项目文档以及安装教程请参见:。 - -## 目录 - -- [官方文档](https://docs.immich.app) -- [项目总览](https://docs.immich.app/overview/introduction) -- [安装教程](https://docs.immich.app/install/requirements) -- [路线图](https://immich.app/roadmap) -- [在线演示](#示例) -- [功能特性](#功能特性) -- [多语言](https://docs.immich.app/developer/translations) -- [贡献者](https://docs.immich.app/overview/support-the-project) - -## 示例 - -您可以在[此处](https://demo.immich.app)访问在线演示网站。在移动端,您可以使用 `https://demo.immich.app` 作为 `服务终端链接` - -### 登录认证信息 - -| 邮箱 | 密码 | -| --------------- | -------- | -| demo@immich.app | demo | - -## 功能特性 - -| 功能特性 | 移动端 | 网页端 | -| :------------------------------------------ | ------ | ------ | -| 上传并查看照片和视频 | 是 | 是 | -| 软件运行时自动备份 | 是 | N/A | -| 忽略重复的项目 | 是 | 是 | -| 选择需要备份的相册 | 是 | N/A | -| 下载照片和视频到本地 | 是 | 是 | -| 多用户支持 | 是 | 是 | -| 相册与共享相册 | 是 | 是 | -| 可拖动的快速滚动条 | 是 | 是 | -| 支持RAW格式 | 是 | 是 | -| 元数据视图(EXIF、地图) | 是 | 是 | -| 通过元数据、对象、人脸和标签进行搜索 | 是 | 是 | -| 管理功能(用户管理) | 否 | 是 | -| 后台备份 | 是 | N/A | -| 虚拟滚动 | 是 | 是 | -| OAuth 支持 | 是 | 是 | -| API Keys | N/A | 是 | -| 实况照片备份和查看 | 是 | 是 | -| 支持360度全景图显示 | 否 | 是 | -| 用户自定义存储结构 | 是 | 是 | -| 公共分享 | 是 | 是 | -| 归档与收藏功能 | 是 | 是 | -| 足迹地图 | 是 | 是 | -| 好友分享 | 是 | 是 | -| 人脸识别与分组 | 是 | 是 | -| 回忆(那年今日) | 是 | 是 | -| 离线支持 | 是 | 否 | -| 只读相册 | 是 | 是 | -| 照片堆叠 | 是 | 是 | -| 标签 | 否 | 是 | -| 文件夹浏览 | 否 | 是 | - -## 多语言 - -关于翻译的更多信息请参见[此处](https://docs.immich.app/developer/translations)。 - - -翻译进度 - - -## 活跃度 - -![活跃度](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image") - -## Star增长曲线 - - - - - - Star History Chart - - - -## 贡献者 - - - - diff --git a/readme_i18n/README_zh_TW.md b/readme_i18n/README_zh_TW.md deleted file mode 100644 index bf14ce2dc0..0000000000 --- a/readme_i18n/README_zh_TW.md +++ /dev/null @@ -1,132 +0,0 @@ -

-
- 授權條款:AGPLv3 - - Discord - -
-
-

- -

- -

-

高效能的自架照片和影片管理解決方案

-
- - - -
- -

- English - Català - Español - Français - Italiano - 日本語 - 한국어 - Deutsch - Nederlands - Türkçe - 简体中文 - 正體中文 - Українська - Русский - Português Brasileiro - Svenska - العربية - Tiếng Việt - ภาษาไทย -

- -> [!WARNING] -> ⚠️ 請務必遵循 [3-2-1 備份原則](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/),守護珍貴的照片與影片! -> - -> [!NOTE] -> 主要說明文件(包含安裝指南)可於 https://immich.app/ 取得。 - -## 連結 - -- [說明文件](https://docs.immich.app/) -- [關於](https://docs.immich.app/overview/introduction) -- [安裝](https://docs.immich.app/install/requirements) -- [發展藍圖](https://immich.app/roadmap) -- [線上體驗](#線上體驗) -- [功能](#功能) -- [翻譯](https://docs.immich.app/developer/translations) -- [貢獻指南](https://docs.immich.app/overview/support-the-project) - -## 線上體驗 - -請前往 [Demoo 網站](https://demo.immich.app) 立即體驗 Immich。若要在手機 App 試用,請在 `伺服器端點 URL` 欄位輸入 `https://demo.immich.app`。 - -### 登入資訊 - -| 電子郵件 | 密碼 | -| --------------- | ------ | -| demo@immich.app | demo | - -## 功能 - -| 功能 | 手機版 | 網頁版 | -| :----------------------------------------- | ------ | ------ | -| 上傳與檢視照片與影片 | 是 | 是 | -| 開啟 App 時自動備份 | 是 | 不適用 | -| 避免重複媒體 | 是 | 是 | -| 選擇要備份的相簿 | 是 | 不適用 | -| 下載照片與影片到本機裝置 | 是 | 是 | -| 多使用者支援 | 是 | 是 | -| 相簿與共享相簿 | 是 | 是 | -| 可拖曳的捲軸 | 是 | 是 | -| 支援 RAW 格式 | 是 | 是 | -| 中繼資料檢視(EXIF、地圖) | 是 | 是 | -| 依中繼資料、物件、臉孔與 CLIP 搜尋 | 是 | 是 | -| 管理功能(使用者管理) | 否 | 是 | -| 背景備份 | 是 | 不適用 | -| 虛擬滾動 | 是 | 是 | -| 支援 OAuth | 是 | 是 | -| API 金鑰 | 不適用 | 是 | -| Live Photo/動態照片備份與播放 | 是 | 是 | -| 支援 360 度全景照片顯示 | 否 | 是 | -| 使用者自訂儲存結構 | 是 | 是 | -| 公開分享 | 是 | 是 | -| 封存與收藏 | 是 | 是 | -| 世界地圖 | 是 | 是 | -| 親朋好友分享 | 是 | 是 | -| 臉部辨識與分群 | 是 | 是 | -| 回憶(x 年前) | 是 | 是 | -| 離線支援 | 是 | 否 | -| 唯讀媒體庫 | 是 | 是 | -| 照片堆疊 | 是 | 是 | -| 標籤 | 否 | 是 | -| 資料夾檢視 | 是 | 是 | - -## 翻譯 - -更多翻譯相關資訊請見 [此處](https://docs.immich.app/developer/translations)。 - - -翻譯狀態 - - -## 專案活躍度 - -![專案活躍度](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats 分析圖片") - -## Star 數量歷史紀錄 - - - - - - Star 歷史紀錄圖表 - - - -## 貢獻者 - - - - diff --git a/renovate.json b/renovate.json deleted file mode 100644 index fbbc8976bd..0000000000 --- a/renovate.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["local>immich-app/.github:renovate-config"], - "packageRules": [ - { - "matchFileNames": [ - "machine-learning/**" - ], - "groupName": "machine-learning", - "rangeStrategy": "in-range-only", - }, - { - "matchFileNames": [ - "mobile/**" - ], - "groupName": "mobile", - "matchUpdateTypes": [ - "minor", - "patch" - ], - "addLabels": [ - "📱mobile" - ] - }, - { - "matchPackageNames": ["ghcr.io/immich-app/postgres"], - "matchUpdateTypes": ["major"], - "enabled": false - }, - { - "matchPackageNames": ["ruby"], - "groupName": "ruby", - "matchCurrentVersion": "< 3.4", - "enabled": false - } - ], - "ignorePaths": [ - "mobile/openapi/pubspec.yaml", - "mobile/ios", - "mobile/android" - ], - "ignoreDeps": [ - "http", - "intl" - ] -} diff --git a/server/src/app.common.ts b/server/src/app.common.ts deleted file mode 100644 index 934c13343f..0000000000 --- a/server/src/app.common.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { NestExpressApplication } from '@nestjs/platform-express'; -import { json } from 'body-parser'; -import compression from 'compression'; -import cookieParser from 'cookie-parser'; -import { existsSync } from 'node:fs'; -import sirv from 'sirv'; -import { excludePaths, serverVersion } from 'src/constants'; -import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; -import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { bootstrapTelemetry } from 'src/repositories/telemetry.repository'; -import { ApiService } from 'src/services/api.service'; -import { useSwagger } from 'src/utils/misc'; - -export function configureTelemetry() { - const { telemetry } = new ConfigRepository().getEnv(); - if (telemetry.metrics.size > 0) { - bootstrapTelemetry(telemetry.apiPort); - } -} - -export async function configureExpress( - app: NestExpressApplication, - { - permitSwaggerWrite = true, - ssr, - }: { - /** - * Whether to allow swagger module to write to the specs.json - * This is not desirable when the API is not available - * @default true - */ - permitSwaggerWrite?: boolean; - /** - * Service to use for server-side rendering - */ - ssr: typeof ApiService | typeof MaintenanceWorkerService; - }, -) { - const configRepository = app.get(ConfigRepository); - const { environment, host, port, resourcePaths, network } = configRepository.getEnv(); - - const logger = await app.resolve(LoggingRepository); - logger.setContext('Bootstrap'); - app.useLogger(logger); - - app.set('trust proxy', ['loopback', ...network.trustedProxies]); - app.set('etag', 'strong'); - app.use(cookieParser()); - app.use(json({ limit: '10mb' })); - - if (configRepository.isDev()) { - app.enableCors(); - } - - app.setGlobalPrefix('api', { exclude: excludePaths }); - app.useWebSocketAdapter(new WebSocketAdapter(app)); - - useSwagger(app, { write: configRepository.isDev() && permitSwaggerWrite }); - - if (existsSync(resourcePaths.web.root)) { - // copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46 - // provides serving of precompressed assets and caching of immutable assets - app.use( - sirv(resourcePaths.web.root, { - etag: true, - gzip: true, - brotli: true, - extensions: [], - setHeaders: (res, pathname) => { - if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) { - res.setHeader('cache-control', 'public,max-age=31536000,immutable'); - } - }, - }), - ); - } - - app.use(app.get(ssr).ssr(excludePaths)); - app.use(compression()); - - const server = await (host ? app.listen(port, host) : app.listen(port)); - server.requestTimeout = 24 * 60 * 60 * 1000; - - logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${environment}] `); -} diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 7d622ea23d..c9e225413b 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -5,35 +5,22 @@ import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { ClsModule } from 'nestjs-cls'; import { KyselyModule } from 'nestjs-kysely'; import { OpenTelemetryModule } from 'nestjs-otel'; -import { commandsAndQuestions } from 'src/commands'; import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; import { ImmichWorker } from 'src/enum'; -import { MaintenanceAuthGuard } from 'src/maintenance/maintenance-auth.guard'; -import { MaintenanceHealthRepository } from 'src/maintenance/maintenance-health.repository'; -import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository'; -import { MaintenanceWorkerController } from 'src/maintenance/maintenance-worker.controller'; -import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; import { AuthGuard } from 'src/middleware/auth.guard'; import { ErrorInterceptor } from 'src/middleware/error.interceptor'; -import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; import { LoggingInterceptor } from 'src/middleware/logging.interceptor'; import { repositories } from 'src/repositories'; -import { AppRepository } from 'src/repositories/app.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; -import { DatabaseRepository } from 'src/repositories/database.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { ProcessRepository } from 'src/repositories/process.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; import { services } from 'src/services'; import { AuthService } from 'src/services/auth.service'; -import { CliService } from 'src/services/cli.service'; -import { QueueService } from 'src/services/queue.service'; + import { getKyselyConfig } from 'src/utils/database'; const common = [...repositories, ...services, GlobalExceptionFilter]; @@ -45,7 +32,7 @@ const commonMiddleware = [ { provide: APP_INTERCEPTOR, useClass: ErrorInterceptor }, ]; -const apiMiddleware = [FileUploadInterceptor, ...commonMiddleware, { provide: APP_GUARD, useClass: AuthGuard }]; +const apiMiddleware = [...commonMiddleware, { provide: APP_GUARD, useClass: AuthGuard }]; const configRepository = new ConfigRepository(); const { bull, cls, database, otel } = configRepository.getEnv(); @@ -64,7 +51,7 @@ export class BaseModule implements OnModuleInit, OnModuleDestroy { logger: LoggingRepository, private authService: AuthService, private eventRepository: EventRepository, - private queueService: QueueService, + private telemetryRepository: TelemetryRepository, private websocketRepository: WebsocketRepository, ) { @@ -74,22 +61,20 @@ export class BaseModule implements OnModuleInit, OnModuleDestroy { async onModuleInit() { this.telemetryRepository.setup({ repositories }); - this.queueService.setServices(services); - this.websocketRepository.setAuthFn(async (client) => this.authService.authenticate({ headers: client.request.headers, queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' }, + metadata: { adminRoute: false, uri: '/api/socket.io' }, }), ); this.eventRepository.setup({ services }); - await this.eventRepository.emit('AppBootstrap'); + await this.eventRepository.emit('app.bootstrap'); } async onModuleDestroy() { - await this.eventRepository.emit('AppShutdown'); + await this.eventRepository.emit('app.shutdown'); await teardownTelemetry(); } } @@ -101,53 +86,8 @@ export class BaseModule implements OnModuleInit, OnModuleDestroy { }) export class ApiModule extends BaseModule {} -@Module({ - imports: [...commonImports], - controllers: [MaintenanceWorkerController], - providers: [ - ConfigRepository, - LoggingRepository, - StorageRepository, - ProcessRepository, - DatabaseRepository, - SystemMetadataRepository, - AppRepository, - MaintenanceHealthRepository, - MaintenanceWebsocketRepository, - MaintenanceWorkerService, - ...commonMiddleware, - { provide: APP_GUARD, useClass: MaintenanceAuthGuard }, - { provide: IWorker, useValue: ImmichWorker.Maintenance }, - ], -}) -export class MaintenanceModule { - constructor( - @Inject(IWorker) private worker: ImmichWorker, - logger: LoggingRepository, - private maintenanceWorkerService: MaintenanceWorkerService, - ) { - logger.setAppName(this.worker); - } - - async onModuleInit() { - await this.maintenanceWorkerService.init(); - } -} - @Module({ imports: [...bullImports, ...commonImports], providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry], }) export class MicroservicesModule extends BaseModule {} - -@Module({ - imports: [...bullImports, ...commonImports], - providers: [...common, ...commandsAndQuestions, SchedulerRegistry], -}) -export class ImmichAdminModule implements OnModuleDestroy { - constructor(private service: CliService) {} - - async onModuleDestroy() { - await this.service.cleanup(); - } -} diff --git a/server/src/bin/migrations.ts b/server/src/bin/migrations.ts deleted file mode 100644 index 588f358023..0000000000 --- a/server/src/bin/migrations.ts +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env node -process.env.DB_URL = process.env.DB_URL || 'postgres://postgres:postgres@localhost:5432/immich'; - -import { Kysely, sql } from 'kysely'; -import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'; -import { basename, dirname, extname, join } from 'node:path'; -import postgres from 'postgres'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { DatabaseRepository } from 'src/repositories/database.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import 'src/schema'; -import { schemaDiff, schemaFromCode, schemaFromDatabase } from 'src/sql-tools'; -import { asPostgresConnectionConfig, getKyselyConfig } from 'src/utils/database'; - -const main = async () => { - const command = process.argv[2]; - const path = process.argv[3] || 'src/Migration'; - - switch (command) { - case 'debug': { - await debug(); - return; - } - - case 'run': { - await runMigrations(); - return; - } - - case 'revert': { - await revert(); - return; - } - - case 'query': { - const query = process.argv[3]; - await runQuery(query); - return; - } - - case 'create': { - create(path, [], []); - return; - } - - case 'generate': { - await generate(path); - return; - } - - default: { - console.log(`Usage: - node dist/bin/migrations.js create - node dist/bin/migrations.js generate - node dist/bin/migrations.js run - node dist/bin/migrations.js revert -`); - } - } -}; - -const getDatabaseClient = () => { - const configRepository = new ConfigRepository(); - const { database } = configRepository.getEnv(); - return new Kysely(getKyselyConfig(database.config)); -}; - -const runQuery = async (query: string) => { - const db = getDatabaseClient(); - await sql.raw(query).execute(db); - await db.destroy(); -}; - -const runMigrations = async () => { - const configRepository = new ConfigRepository(); - const logger = LoggingRepository.create(); - const db = getDatabaseClient(); - const databaseRepository = new DatabaseRepository(db, logger, configRepository); - await databaseRepository.runMigrations(); - await db.destroy(); -}; - -const revert = async () => { - const configRepository = new ConfigRepository(); - const logger = LoggingRepository.create(); - const db = getDatabaseClient(); - const databaseRepository = new DatabaseRepository(db, logger, configRepository); - - try { - const migrationName = await databaseRepository.revertLastMigration(); - if (!migrationName) { - console.log('No migrations to revert'); - return; - } - - markMigrationAsReverted(migrationName); - } finally { - await db.destroy(); - } -}; - -const debug = async () => { - const { up } = await compare(); - const upSql = '-- UP\n' + up.asSql({ comments: true }).join('\n'); - // const downSql = '-- DOWN\n' + down.asSql({ comments: true }).join('\n'); - writeFileSync('./migrations.sql', upSql + '\n\n'); - console.log('Wrote migrations.sql'); -}; - -const generate = async (path: string) => { - const { up, down } = await compare(); - if (up.items.length === 0) { - console.log('No changes detected'); - return; - } - create(path, up.asSql(), down.asSql()); -}; - -const create = (path: string, up: string[], down: string[]) => { - const timestamp = Date.now(); - const name = basename(path, extname(path)); - const filename = `${timestamp}-${name}.ts`; - const folder = dirname(path); - const fullPath = join(folder, filename); - mkdirSync(folder, { recursive: true }); - writeFileSync(fullPath, asMigration({ up, down })); - console.log(`Wrote ${fullPath}`); -}; - -const compare = async () => { - const configRepository = new ConfigRepository(); - const { database } = configRepository.getEnv(); - const db = postgres(asPostgresConnectionConfig(database.config)); - - const source = schemaFromCode({ overrides: true, namingStrategy: 'default' }); - const target = await schemaFromDatabase(db, {}); - - console.log(source.warnings.join('\n')); - - const up = schemaDiff(source, target, { - tables: { ignoreExtra: true }, - functions: { ignoreExtra: false }, - parameters: { ignoreExtra: true }, - }); - const down = schemaDiff(target, source, { - tables: { ignoreExtra: false, ignoreMissing: true }, - functions: { ignoreExtra: false }, - extensions: { ignoreMissing: true }, - parameters: { ignoreMissing: true }, - }); - - return { up, down }; -}; - -type MigrationProps = { - up: string[]; - down: string[]; -}; - -const asMigration = ({ up, down }: MigrationProps) => { - const upSql = up.map((sql) => ` await sql\`${sql}\`.execute(db);`).join('\n'); - const downSql = down.map((sql) => ` await sql\`${sql}\`.execute(db);`).join('\n'); - - return `import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { -${upSql} -} - -export async function down(db: Kysely): Promise { -${downSql} -} -`; -}; - -const markMigrationAsReverted = (migrationName: string) => { - // eslint-disable-next-line unicorn/prefer-module - const distRoot = join(__dirname, '..'); - const projectRoot = join(distRoot, '..'); - const sourceFolder = join(projectRoot, 'src', 'schema', 'migrations'); - const distFolder = join(distRoot, 'schema', 'migrations'); - - const sourcePath = join(sourceFolder, `${migrationName}.ts`); - const revertedFolder = join(sourceFolder, 'reverted'); - const revertedPath = join(revertedFolder, `${migrationName}.ts`); - - if (existsSync(revertedPath)) { - console.log(`Migration ${migrationName} is already marked as reverted`); - } else if (existsSync(sourcePath)) { - mkdirSync(revertedFolder, { recursive: true }); - renameSync(sourcePath, revertedPath); - console.log(`Moved ${sourcePath} to ${revertedPath}`); - } else { - console.warn(`Source migration file not found for ${migrationName}`); - } - - const distBase = join(distFolder, migrationName); - for (const extension of ['.js', '.js.map', '.d.ts']) { - const filePath = `${distBase}${extension}`; - if (existsSync(filePath)) { - rmSync(filePath, { force: true }); - console.log(`Removed ${filePath}`); - } - } -}; - -main() - .then(() => { - process.exit(0); - }) - .catch((error) => { - console.error(error); - console.log('Something went wrong'); - process.exit(1); - }); diff --git a/server/src/bin/sync-open-api.ts b/server/src/bin/sync-open-api.ts deleted file mode 100644 index d5316b34cf..0000000000 --- a/server/src/bin/sync-open-api.ts +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node -process.env.DB_URL = 'postgres://postgres:postgres@localhost:5432/immich'; -import { NestFactory } from '@nestjs/core'; -import { NestExpressApplication } from '@nestjs/platform-express'; -import { ApiModule } from 'src/app.module'; -import { useSwagger } from 'src/utils/misc'; - -const sync = async () => { - const app = await NestFactory.create(ApiModule, { preview: true }); - useSwagger(app, { write: true }); - await app.close(); -}; - -sync() - .then(() => { - console.log('Done'); - process.exit(0); - }) - .catch((error) => { - console.error(error); - console.log('Something went wrong'); - process.exit(1); - }); diff --git a/server/src/bin/sync-sql.ts b/server/src/bin/sync-sql.ts deleted file mode 100644 index b632332069..0000000000 --- a/server/src/bin/sync-sql.ts +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env node -import { INestApplication } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { Test } from '@nestjs/testing'; -import { ClassConstructor } from 'class-transformer'; -import { ClsModule } from 'nestjs-cls'; -import { KyselyModule } from 'nestjs-kysely'; -import { OpenTelemetryModule } from 'nestjs-otel'; -import { mkdir, rm, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import { format } from 'sql-formatter'; -import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; -import { repositories } from 'src/repositories'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; -import { SyncRepository } from 'src/repositories/sync.repository'; -import { AuthService } from 'src/services/auth.service'; -import { getKyselyConfig } from 'src/utils/database'; - -const handleError = (label: string, error: Error | any) => { - console.error(`${label} error: ${error}`); -}; - -export class SqlLogger { - queries: string[] = []; - errors: Array<{ error: string | Error; query: string }> = []; - - clear() { - this.queries = []; - this.errors = []; - } - - logQuery(query: string) { - this.queries.push(format(query, { language: 'postgresql' })); - } - - logQueryError(error: string | Error, query: string) { - this.errors.push({ error, query }); - } -} - -const reflector = new Reflector(); - -type Repository = ClassConstructor; -type SqlGeneratorOptions = { targetDir: string }; - -class SqlGenerator { - private app: INestApplication | null = null; - private sqlLogger = new SqlLogger(); - private results: Record = {}; - - constructor(private options: SqlGeneratorOptions) {} - - async run() { - try { - await this.setup(); - for (const Repository of repositories) { - if (Repository === LoggingRepository || Repository === MachineLearningRepository) { - continue; - } - await this.process(Repository); - } - await this.write(); - this.stats(); - } finally { - await this.close(); - } - } - - private async setup() { - await rm(this.options.targetDir, { force: true, recursive: true }); - await mkdir(this.options.targetDir); - - if (!process.env.DB_HOSTNAME) { - process.env.DB_HOSTNAME = 'localhost'; - } - const { database, cls, otel } = new ConfigRepository().getEnv(); - - const moduleFixture = await Test.createTestingModule({ - imports: [ - KyselyModule.forRoot({ - ...getKyselyConfig(database.config), - log: (event) => { - if (event.level === 'query') { - this.sqlLogger.logQuery(event.query.sql); - } else if (event.level === 'error') { - this.sqlLogger.logQueryError(event.error as Error, event.query.sql); - this.sqlLogger.logQuery(event.query.sql); - } - }, - }), - ClsModule.forRoot(cls.config), - OpenTelemetryModule.forRoot(otel), - ], - providers: [...repositories, AuthService, SchedulerRegistry], - }).compile(); - - this.app = await moduleFixture.createNestApplication().init(); - } - - async process(Repository: Repository) { - if (!this.app) { - throw new Error('Not initialized'); - } - - const data: string[] = [`-- NOTE: This file is auto generated by ./sql-generator`]; - const instance = this.app.get(Repository); - - // normal repositories - data.push(...(await this.runTargets(instance, `${Repository.name}`))); - - // nested repositories - if (Repository.name === AccessRepository.name || Repository.name === SyncRepository.name) { - for (const key of Object.keys(instance)) { - const subInstance = (instance as any)[key]; - data.push(...(await this.runTargets(subInstance, `${Repository.name}.${key}`))); - } - } - - this.results[Repository.name] = data; - } - - private async runTargets(instance: any, label: string) { - const data: string[] = []; - - for (const key of this.getPropertyNames(instance)) { - const target = instance[key]; - if (!(typeof target === 'function')) { - continue; - } - - const queries = reflector.get(GENERATE_SQL_KEY, target); - if (!queries) { - continue; - } - - // empty decorator implies calling with no arguments - if (queries.length === 0) { - queries.push({ params: [] }); - } - - for (const { name, params, stream } of queries) { - let queryLabel = `${label}.${key}`; - if (name) { - queryLabel += ` (${name})`; - } - - this.sqlLogger.clear(); - - if (stream) { - try { - const result: AsyncIterableIterator = target.apply(instance, params); - for await (const _ of result) { - break; - } - } catch (error) { - handleError(queryLabel, error); - } - } else { - // errors still generate sql, which is all we care about - await target.apply(instance, params).catch((error: Error) => handleError(queryLabel, error)); - } - - if (this.sqlLogger.queries.length === 0) { - console.warn(`No queries recorded for ${queryLabel}`); - continue; - } - - data.push([`-- ${queryLabel}`, ...this.sqlLogger.queries].join('\n')); - } - } - - return data; - } - - private async write() { - for (const [repoName, data] of Object.entries(this.results)) { - // only contains the header - if (data.length === 1) { - continue; - } - const filename = repoName.replaceAll(/[A-Z]/g, (letter) => `.${letter.toLowerCase()}`).replace('.', ''); - const file = join(this.options.targetDir, `${filename}.sql`); - await writeFile(file, data.join('\n\n') + '\n'); - } - } - - private stats() { - console.log(`Wrote ${Object.keys(this.results).length} files`); - console.log(`Generated ${Object.values(this.results).flat().length} queries`); - } - - private async close() { - if (this.app) { - await this.app.close(); - } - } - - private getPropertyNames(instance: any): string[] { - return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)) as any[]; - } -} - -new SqlGenerator({ targetDir: './src/queries' }) - .run() - .then(() => { - console.log('Done'); - process.exit(0); - }) - .catch((error) => { - console.error(error); - console.log('Something went wrong'); - process.exit(1); - }); diff --git a/server/src/commands/grant-admin.ts b/server/src/commands/grant-admin.ts deleted file mode 100644 index 41fd4f2340..0000000000 --- a/server/src/commands/grant-admin.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander'; -import { CliService } from 'src/services/cli.service'; - -const prompt = (inquirer: InquirerService) => { - return function ask(): Promise { - return inquirer.ask<{ email: string }>('prompt-email', {}).then(({ email }: { email: string }) => email); - }; -}; - -@Command({ - name: 'grant-admin', - description: 'Grant admin privileges to a user (by email)', -}) -export class GrantAdminCommand extends CommandRunner { - constructor( - private service: CliService, - private inquirer: InquirerService, - ) { - super(); - } - - async run(): Promise { - try { - const email = await prompt(this.inquirer)(); - await this.service.grantAdminAccess(email); - console.debug('Admin access has been granted to', email); - } catch (error) { - console.error(error); - console.error('Unable to grant admin access to user'); - } - } -} - -@Command({ - name: 'revoke-admin', - description: 'Revoke admin privileges from a user (by email)', -}) -export class RevokeAdminCommand extends CommandRunner { - constructor( - private service: CliService, - private inquirer: InquirerService, - ) { - super(); - } - - async run(): Promise { - try { - const email = await prompt(this.inquirer)(); - await this.service.revokeAdminAccess(email); - console.debug('Admin access has been revoked from', email); - } catch (error) { - console.error(error); - console.error('Unable to revoke admin access from user'); - } - } -} - -@QuestionSet({ name: 'prompt-email' }) -export class PromptEmailQuestion { - @Question({ - message: 'Please enter the user email: ', - name: 'email', - }) - parseEmail(value: string) { - return value; - } -} diff --git a/server/src/commands/index.ts b/server/src/commands/index.ts deleted file mode 100644 index 2aef2e8c8b..0000000000 --- a/server/src/commands/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GrantAdminCommand, PromptEmailQuestion, RevokeAdminCommand } from 'src/commands/grant-admin'; -import { ListUsersCommand } from 'src/commands/list-users.command'; -import { DisableMaintenanceModeCommand, EnableMaintenanceModeCommand } from 'src/commands/maintenance-mode'; -import { - ChangeMediaLocationCommand, - PromptConfirmMoveQuestions, - PromptMediaLocationQuestions, -} from 'src/commands/media-location.command'; -import { DisableOAuthLogin, EnableOAuthLogin } from 'src/commands/oauth-login'; -import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from 'src/commands/password-login'; -import { PromptPasswordQuestions, ResetAdminPasswordCommand } from 'src/commands/reset-admin-password.command'; -import { VersionCommand } from 'src/commands/version.command'; - -export const commandsAndQuestions = [ - ResetAdminPasswordCommand, - PromptPasswordQuestions, - PromptEmailQuestion, - EnablePasswordLoginCommand, - DisablePasswordLoginCommand, - EnableMaintenanceModeCommand, - DisableMaintenanceModeCommand, - EnableOAuthLogin, - DisableOAuthLogin, - ListUsersCommand, - VersionCommand, - GrantAdminCommand, - RevokeAdminCommand, - ChangeMediaLocationCommand, - PromptMediaLocationQuestions, - PromptConfirmMoveQuestions, -]; diff --git a/server/src/commands/list-users.command.ts b/server/src/commands/list-users.command.ts deleted file mode 100644 index 299ea82283..0000000000 --- a/server/src/commands/list-users.command.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; -import { CliService } from 'src/services/cli.service'; - -@Command({ - name: 'list-users', - description: 'List Immich users', -}) -export class ListUsersCommand extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - try { - const users = await this.service.listUsers(); - console.dir(users); - } catch (error) { - console.error(error); - console.error('Unable to load users'); - } - } -} diff --git a/server/src/commands/maintenance-mode.ts b/server/src/commands/maintenance-mode.ts deleted file mode 100644 index 3416acf05d..0000000000 --- a/server/src/commands/maintenance-mode.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; -import { CliService } from 'src/services/cli.service'; - -@Command({ - name: 'enable-maintenance-mode', - description: 'Enable maintenance mode or regenerate the maintenance token', -}) -export class EnableMaintenanceModeCommand extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - const { authUrl, alreadyEnabled } = await this.service.enableMaintenanceMode(); - - console.info(alreadyEnabled ? 'The server is already in maintenance mode!' : 'Maintenance mode has been enabled.'); - console.info(`\nLog in using the following URL:\n${authUrl}`); - } -} - -@Command({ - name: 'disable-maintenance-mode', - description: 'Disable maintenance mode', -}) -export class DisableMaintenanceModeCommand extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - const { alreadyDisabled } = await this.service.disableMaintenanceMode(); - - console.log( - alreadyDisabled ? 'The server is already out of maintenance mode!' : 'Maintenance mode has been disabled.', - ); - } -} diff --git a/server/src/commands/media-location.command.ts b/server/src/commands/media-location.command.ts deleted file mode 100644 index 0d32749c02..0000000000 --- a/server/src/commands/media-location.command.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander'; -import { CliService } from 'src/services/cli.service'; - -@Command({ - name: 'change-media-location', - description: 'Change database file paths to align with a new media location', -}) -export class ChangeMediaLocationCommand extends CommandRunner { - constructor( - private service: CliService, - private inquirer: InquirerService, - ) { - super(); - } - - private async showSamplePaths(hint?: string) { - hint = hint ? ` (${hint})` : ''; - - const paths = await this.service.getSampleFilePaths(); - if (paths.length > 0) { - let message = ` Examples from the database${hint}:\n`; - for (const path of paths) { - message += ` - ${path}\n`; - } - - console.log(`\n${message}`); - } - } - - async run(): Promise { - try { - await this.showSamplePaths(); - - const { oldValue, newValue } = await this.inquirer.ask<{ oldValue: string; newValue: string }>( - 'prompt-media-location', - {}, - ); - - const success = await this.service.migrateFilePaths({ - oldValue, - newValue, - confirm: async ({ sourceFolder, targetFolder }) => { - console.log(` - Previous value: ${oldValue} - Current value: ${newValue} - - Changing from "${sourceFolder}/*" to "${targetFolder}/*" -`); - - const { value: confirmed } = await this.inquirer.ask<{ value: boolean }>('prompt-confirm-move', {}); - return confirmed; - }, - }); - - const successMessage = `Matching database file paths were updated successfully! 🎉 - - You may now set IMMICH_MEDIA_LOCATION=${newValue} and restart! - - (please remember to update applicable volume mounts e.g - services: - immich-server: - ... - volumes: - - \${UPLOAD_LOCATION}:/data - ... - )`; - - console.log(`\n ${success ? successMessage : 'No rows were updated'}\n`); - - await this.showSamplePaths('after'); - } catch (error) { - console.error(error); - console.error('Unable to update database file paths.'); - } - } -} - -const currentValue = process.env.IMMICH_MEDIA_LOCATION || ''; - -const makePrompt = (which: string) => { - return `Enter the ${which} value of IMMICH_MEDIA_LOCATION:${currentValue ? ` [${currentValue}]` : ''}`; -}; - -@QuestionSet({ name: 'prompt-media-location' }) -export class PromptMediaLocationQuestions { - @Question({ message: makePrompt('previous'), name: 'oldValue' }) - oldValue(value: string) { - return value || currentValue; - } - - @Question({ message: makePrompt('new'), name: 'newValue' }) - newValue(value: string) { - return value || currentValue; - } -} - -@QuestionSet({ name: 'prompt-confirm-move' }) -export class PromptConfirmMoveQuestions { - @Question({ - message: 'Do you want to proceed? [Y/n]', - name: 'value', - }) - value(value: string): boolean { - return ['yes', 'y'].includes((value || 'y').toLowerCase()); - } -} diff --git a/server/src/commands/oauth-login.ts b/server/src/commands/oauth-login.ts deleted file mode 100644 index 9ec7013fa5..0000000000 --- a/server/src/commands/oauth-login.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; -import { CliService } from 'src/services/cli.service'; - -@Command({ - name: 'enable-oauth-login', - description: 'Enable OAuth login', -}) -export class EnableOAuthLogin extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - await this.service.enableOAuthLogin(); - console.log('OAuth login has been enabled.'); - } -} - -@Command({ - name: 'disable-oauth-login', - description: 'Disable OAuth login', -}) -export class DisableOAuthLogin extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - await this.service.disableOAuthLogin(); - console.log('OAuth login has been disabled.'); - } -} diff --git a/server/src/commands/password-login.ts b/server/src/commands/password-login.ts deleted file mode 100644 index 057abd1649..0000000000 --- a/server/src/commands/password-login.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; -import { CliService } from 'src/services/cli.service'; - -@Command({ - name: 'enable-password-login', - description: 'Enable password login', -}) -export class EnablePasswordLoginCommand extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - await this.service.enablePasswordLogin(); - console.log('Password login has been enabled.'); - } -} - -@Command({ - name: 'disable-password-login', - description: 'Disable password login', -}) -export class DisablePasswordLoginCommand extends CommandRunner { - constructor(private service: CliService) { - super(); - } - - async run(): Promise { - await this.service.disablePasswordLogin(); - console.log('Password login has been disabled.'); - } -} diff --git a/server/src/commands/reset-admin-password.command.ts b/server/src/commands/reset-admin-password.command.ts deleted file mode 100644 index e5dee49837..0000000000 --- a/server/src/commands/reset-admin-password.command.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander'; -import { UserAdminResponseDto } from 'src/dtos/user.dto'; -import { CliService } from 'src/services/cli.service'; - -const prompt = (inquirer: InquirerService) => { - return function ask(admin: UserAdminResponseDto) { - const { id, oauthId, email, name } = admin; - console.log(`Found Admin: -- ID=${id} -- OAuth ID=${oauthId} -- Email=${email} -- Name=${name}`); - - return inquirer.ask<{ password: string }>('prompt-password', {}).then(({ password }) => password); - }; -}; - -@Command({ - name: 'reset-admin-password', - description: 'Reset the admin password', -}) -export class ResetAdminPasswordCommand extends CommandRunner { - constructor( - private service: CliService, - private inquirer: InquirerService, - ) { - super(); - } - - async run(): Promise { - try { - const { password, provided } = await this.service.resetAdminPassword(prompt(this.inquirer)); - - if (provided) { - console.log(`The admin password has been updated.`); - } else { - console.log(`The admin password has been updated to:\n${password}`); - } - } catch (error) { - console.error(error); - console.error('Unable to reset admin password'); - } - } -} - -@QuestionSet({ name: 'prompt-password' }) -export class PromptPasswordQuestions { - @Question({ - message: 'Please choose a new password (optional)', - name: 'password', - }) - parsePassword(value: string) { - return value; - } -} diff --git a/server/src/commands/version.command.ts b/server/src/commands/version.command.ts deleted file mode 100644 index 585f097b6a..0000000000 --- a/server/src/commands/version.command.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Command, CommandRunner } from 'nest-commander'; -import { VersionService } from 'src/services/version.service'; - -@Command({ - name: 'version', - description: 'Print Immich version', -}) -export class VersionCommand extends CommandRunner { - constructor(private service: VersionService) { - super(); - } - - run(): Promise { - try { - const version = this.service.getVersion(); - console.log(`v${version.major}.${version.minor}.${version.patch}`); - } catch (error) { - console.error(error); - console.error('Unable to get version'); - } - - return Promise.resolve(); - } -} diff --git a/server/src/config.ts b/server/src/config.ts index 2a43b51187..a1e3773d29 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,394 +1,43 @@ -import { CronExpression } from '@nestjs/schedule'; -import { - AudioCodec, - Colorspace, - CQMode, - ImageFormat, - LogLevel, - OAuthTokenEndpointAuthMethod, - QueueName, - ToneMapping, - TranscodeHardwareAcceleration, - TranscodePolicy, - VideoCodec, - VideoContainer, -} from 'src/enum'; -import { ConcurrentQueueName, FullsizeImageOptions, ImageOptions } from 'src/types'; +import { LogLevel, QueueName } from 'src/enum'; export type SystemConfig = { - backup: { - database: { - enabled: boolean; - cronExpression: string; - keepLastAmount: number; - }; - }; - ffmpeg: { - crf: number; - threads: number; - preset: string; - targetVideoCodec: VideoCodec; - acceptedVideoCodecs: VideoCodec[]; - targetAudioCodec: AudioCodec; - acceptedAudioCodecs: AudioCodec[]; - acceptedContainers: VideoContainer[]; - targetResolution: string; - maxBitrate: string; - bframes: number; - refs: number; - gopSize: number; - temporalAQ: boolean; - cqMode: CQMode; - twoPass: boolean; - preferredHwDevice: string; - transcode: TranscodePolicy; - accel: TranscodeHardwareAcceleration; - accelDecode: boolean; - tonemap: ToneMapping; - }; - job: Record; + job: Record; logging: { enabled: boolean; level: LogLevel; }; - machineLearning: { - enabled: boolean; - urls: string[]; - availabilityChecks: { - enabled: boolean; - timeout: number; - interval: number; - }; - clip: { - enabled: boolean; - modelName: string; - }; - duplicateDetection: { - enabled: boolean; - maxDistance: number; - }; - facialRecognition: { - enabled: boolean; - modelName: string; - minScore: number; - minFaces: number; - maxDistance: number; - }; - ocr: { - enabled: boolean; - modelName: string; - minDetectionScore: number; - minRecognitionScore: number; - maxResolution: number; - }; - }; - map: { - enabled: boolean; - lightStyle: string; - darkStyle: string; - }; - reverseGeocoding: { - enabled: boolean; - }; - metadata: { - faces: { - import: boolean; - }; - }; - oauth: { - autoLaunch: boolean; - autoRegister: boolean; - buttonText: string; - clientId: string; - clientSecret: string; - defaultStorageQuota: number | null; - enabled: boolean; - issuerUrl: string; - mobileOverrideEnabled: boolean; - mobileRedirectUri: string; - scope: string; - signingAlgorithm: string; - profileSigningAlgorithm: string; - tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod; - timeout: number; - storageLabelClaim: string; - storageQuotaClaim: string; - roleClaim: string; - }; passwordLogin: { enabled: boolean; }; - storageTemplate: { - enabled: boolean; - hashVerificationEnabled: boolean; - template: string; - }; - image: { - thumbnail: ImageOptions; - preview: ImageOptions; - colorspace: Colorspace; - extractEmbedded: boolean; - fullsize: FullsizeImageOptions; - }; - newVersionCheck: { - enabled: boolean; - }; - nightlyTasks: { - startTime: string; - databaseCleanup: boolean; - missingThumbnails: boolean; - clusterNewFaces: boolean; - generateMemories: boolean; - syncQuotaUsage: boolean; - }; - trash: { - enabled: boolean; - days: number; - }; - theme: { - customCss: string; - }; - library: { - scan: { - enabled: boolean; - cronExpression: string; - }; - watch: { - enabled: boolean; - }; - }; - notifications: { - smtp: { - enabled: boolean; - from: string; - replyTo: string; - transport: { - ignoreCert: boolean; - host: string; - port: number; - secure: boolean; - username: string; - password: string; - }; - }; - }; - templates: { - email: { - welcomeTemplate: string; - albumInviteTemplate: string; - albumUpdateTemplate: string; - }; - }; server: { externalDomain: string; loginPageMessage: string; - publicUsers: boolean; + }; + theme: { + customCss: string; }; user: { deleteDelay: number; }; }; -export type MachineLearningConfig = SystemConfig['machineLearning']; - export const defaults = Object.freeze({ - backup: { - database: { - enabled: true, - cronExpression: CronExpression.EVERY_DAY_AT_2AM, - keepLastAmount: 14, - }, - }, - ffmpeg: { - crf: 23, - threads: 0, - preset: 'ultrafast', - targetVideoCodec: VideoCodec.H264, - acceptedVideoCodecs: [VideoCodec.H264], - targetAudioCodec: AudioCodec.Aac, - acceptedAudioCodecs: [AudioCodec.Aac, AudioCodec.Mp3, AudioCodec.LibOpus], - acceptedContainers: [VideoContainer.Mov, VideoContainer.Ogg, VideoContainer.Webm], - targetResolution: '720', - maxBitrate: '0', - bframes: -1, - refs: 0, - gopSize: 0, - temporalAQ: false, - cqMode: CQMode.Auto, - twoPass: false, - preferredHwDevice: 'auto', - transcode: TranscodePolicy.Required, - tonemap: ToneMapping.Hable, - accel: TranscodeHardwareAcceleration.Disabled, - accelDecode: false, - }, job: { [QueueName.BackgroundTask]: { concurrency: 5 }, - [QueueName.SmartSearch]: { concurrency: 2 }, - [QueueName.MetadataExtraction]: { concurrency: 5 }, - [QueueName.FaceDetection]: { concurrency: 2 }, - [QueueName.Search]: { concurrency: 5 }, - [QueueName.Sidecar]: { concurrency: 5 }, - [QueueName.Library]: { concurrency: 5 }, - [QueueName.Migration]: { concurrency: 5 }, - [QueueName.ThumbnailGeneration]: { concurrency: 3 }, - [QueueName.VideoConversion]: { concurrency: 1 }, - [QueueName.Notification]: { concurrency: 5 }, - [QueueName.Ocr]: { concurrency: 1 }, - [QueueName.Workflow]: { concurrency: 5 }, - [QueueName.Editor]: { concurrency: 2 }, }, logging: { enabled: true, level: LogLevel.Log, }, - machineLearning: { - enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false', - urls: [process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003'], - availabilityChecks: { - enabled: true, - timeout: Number(process.env.IMMICH_MACHINE_LEARNING_PING_TIMEOUT) || 2000, - interval: 30_000, - }, - clip: { - enabled: true, - modelName: 'ViT-B-32__openai', - }, - duplicateDetection: { - enabled: true, - maxDistance: 0.01, - }, - facialRecognition: { - enabled: true, - modelName: 'buffalo_l', - minScore: 0.7, - maxDistance: 0.5, - minFaces: 3, - }, - ocr: { - enabled: true, - modelName: 'PP-OCRv5_mobile', - minDetectionScore: 0.5, - minRecognitionScore: 0.8, - maxResolution: 736, - }, - }, - map: { - enabled: true, - lightStyle: 'https://tiles.immich.cloud/v1/style/light.json', - darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json', - }, - reverseGeocoding: { - enabled: true, - }, - metadata: { - faces: { - import: false, - }, - }, - oauth: { - autoLaunch: false, - autoRegister: true, - buttonText: 'Login with OAuth', - clientId: '', - clientSecret: '', - defaultStorageQuota: null, - enabled: false, - issuerUrl: '', - mobileOverrideEnabled: false, - mobileRedirectUri: '', - scope: 'openid email profile', - signingAlgorithm: 'RS256', - profileSigningAlgorithm: 'none', - storageLabelClaim: 'preferred_username', - storageQuotaClaim: 'immich_quota', - roleClaim: 'immich_role', - tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.ClientSecretPost, - timeout: 30_000, - }, passwordLogin: { enabled: true, }, - storageTemplate: { - enabled: false, - hashVerificationEnabled: true, - template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - }, - image: { - thumbnail: { - format: ImageFormat.Webp, - size: 250, - quality: 80, - progressive: false, - }, - preview: { - format: ImageFormat.Jpeg, - size: 1440, - quality: 80, - progressive: false, - }, - colorspace: Colorspace.P3, - extractEmbedded: false, - fullsize: { - enabled: false, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - }, - }, - newVersionCheck: { - enabled: true, - }, - nightlyTasks: { - startTime: '00:00', - databaseCleanup: true, - generateMemories: true, - syncQuotaUsage: true, - missingThumbnails: true, - clusterNewFaces: true, - }, - trash: { - enabled: true, - days: 30, - }, - theme: { - customCss: '', - }, - library: { - scan: { - enabled: true, - cronExpression: CronExpression.EVERY_DAY_AT_MIDNIGHT, - }, - watch: { - enabled: false, - }, - }, server: { externalDomain: '', loginPageMessage: '', - publicUsers: true, }, - notifications: { - smtp: { - enabled: false, - from: '', - replyTo: '', - transport: { - ignoreCert: false, - host: '', - port: 587, - secure: false, - username: '', - password: '', - }, - }, - }, - templates: { - email: { - welcomeTemplate: '', - albumInviteTemplate: '', - albumUpdateTemplate: '', - }, + theme: { + customCss: '', }, user: { deleteDelay: 7, diff --git a/server/src/constants.ts b/server/src/constants.ts index 809c7e45a8..6620c98176 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -1,37 +1,16 @@ -import { Duration } from 'luxon'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { SemVer } from 'semver'; -import { ApiTag, DatabaseExtension, ExifOrientation, VectorIndex } from 'src/enum'; +import { ApiTag, DatabaseExtension } from 'src/enum'; export const POSTGRES_VERSION_RANGE = '>=14.0.0'; -export const VECTORCHORD_VERSION_RANGE = '>=0.3 <2'; -export const VECTORS_VERSION_RANGE = '>=0.2 <0.4'; export const VECTOR_VERSION_RANGE = '>=0.5 <1'; -export const JOBS_ASSET_PAGINATION_SIZE = 1000; -export const JOBS_LIBRARY_PAGINATION_SIZE = 10_000; - export const EXTENSION_NAMES: Record = { - cube: 'cube', - earthdistance: 'earthdistance', vector: 'pgvector', - vectors: 'pgvecto.rs', - vchord: 'VectorChord', } as const; -export const VECTOR_EXTENSIONS = [ - DatabaseExtension.VectorChord, - DatabaseExtension.Vectors, - DatabaseExtension.Vector, -] as const; - -export const VECTOR_INDEX_TABLES = { - [VectorIndex.Clip]: 'smart_search', - [VectorIndex.Face]: 'face_search', -} as const; - -export const VECTORCHORD_LIST_SLACK_FACTOR = 1.2; +export const VECTOR_EXTENSIONS = [DatabaseExtension.Vector] as const; export const SALT_ROUNDS = 10; @@ -43,154 +22,14 @@ const packageFile = join(basePath, '..', 'package.json'); const { version } = JSON.parse(readFileSync(packageFile, 'utf8')); export const serverVersion = new SemVer(version); -export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 }); -export const ONE_HOUR = Duration.fromObject({ hours: 1 }); - -export const citiesFile = 'cities500.txt'; -export const reverseGeocodeMaxDistance = 25_000; - -export const MOBILE_REDIRECT = 'app.immich:///oauth-callback'; export const LOGIN_URL = '/auth/login?autoLaunch=0'; export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico']; -export const FACE_THUMBNAIL_SIZE = 250; - -type ModelInfo = { dimSize: number }; -export const CLIP_MODEL_INFO: Record = { - RN101__openai: { dimSize: 512 }, - RN101__yfcc15m: { dimSize: 512 }, - 'ViT-B-16__laion400m_e31': { dimSize: 512 }, - 'ViT-B-16__laion400m_e32': { dimSize: 512 }, - 'ViT-B-16__openai': { dimSize: 512 }, - 'ViT-B-32__laion2b-s34b-b79k': { dimSize: 512 }, - 'ViT-B-32__laion2b_e16': { dimSize: 512 }, - 'ViT-B-32__laion400m_e31': { dimSize: 512 }, - 'ViT-B-32__laion400m_e32': { dimSize: 512 }, - 'ViT-B-32__openai': { dimSize: 512 }, - 'XLM-Roberta-Base-ViT-B-32__laion5b_s13b_b90k': { dimSize: 512 }, - 'XLM-Roberta-Large-Vit-B-32': { dimSize: 512 }, - RN50x4__openai: { dimSize: 640 }, - 'ViT-B-16-plus-240__laion400m_e31': { dimSize: 640 }, - 'ViT-B-16-plus-240__laion400m_e32': { dimSize: 640 }, - 'XLM-Roberta-Large-Vit-B-16Plus': { dimSize: 640 }, - 'LABSE-Vit-L-14': { dimSize: 768 }, - RN50x16__openai: { dimSize: 768 }, - 'ViT-B-16-SigLIP-256__webli': { dimSize: 768 }, - 'ViT-B-16-SigLIP-384__webli': { dimSize: 768 }, - 'ViT-B-16-SigLIP-512__webli': { dimSize: 768 }, - 'ViT-B-16-SigLIP-i18n-256__webli': { dimSize: 768 }, - 'ViT-B-16-SigLIP__webli': { dimSize: 768 }, - 'ViT-L-14-336__openai': { dimSize: 768 }, - 'ViT-L-14-quickgelu__dfn2b': { dimSize: 768 }, - 'ViT-L-14__laion2b-s32b-b82k': { dimSize: 768 }, - 'ViT-L-14__laion400m_e31': { dimSize: 768 }, - 'ViT-L-14__laion400m_e32': { dimSize: 768 }, - 'ViT-L-14__openai': { dimSize: 768 }, - 'XLM-Roberta-Large-Vit-L-14': { dimSize: 768 }, - 'nllb-clip-base-siglip__mrl': { dimSize: 768 }, - 'nllb-clip-base-siglip__v1': { dimSize: 768 }, - RN50__cc12m: { dimSize: 1024 }, - RN50__openai: { dimSize: 1024 }, - RN50__yfcc15m: { dimSize: 1024 }, - RN50x64__openai: { dimSize: 1024 }, - 'ViT-H-14-378-quickgelu__dfn5b': { dimSize: 1024 }, - 'ViT-H-14-quickgelu__dfn5b': { dimSize: 1024 }, - 'ViT-H-14__laion2b-s32b-b79k': { dimSize: 1024 }, - 'ViT-L-16-SigLIP-256__webli': { dimSize: 1024 }, - 'ViT-L-16-SigLIP-384__webli': { dimSize: 1024 }, - 'ViT-g-14__laion2b-s12b-b42k': { dimSize: 1024 }, - 'XLM-Roberta-Large-ViT-H-14__frozen_laion5b_s13b_b90k': { dimSize: 1024 }, - 'ViT-SO400M-14-SigLIP-384__webli': { dimSize: 1152 }, - 'nllb-clip-large-siglip__mrl': { dimSize: 1152 }, - 'nllb-clip-large-siglip__v1': { dimSize: 1152 }, - 'ViT-B-16-SigLIP2__webli': { dimSize: 768 }, - 'ViT-B-32-SigLIP2-256__webli': { dimSize: 768 }, - 'ViT-L-16-SigLIP2-256__webli': { dimSize: 1024 }, - 'ViT-L-16-SigLIP2-384__webli': { dimSize: 1024 }, - 'ViT-L-16-SigLIP2-512__webli': { dimSize: 1024 }, - 'ViT-SO400M-14-SigLIP2__webli': { dimSize: 1152 }, - 'ViT-SO400M-14-SigLIP2-378__webli': { dimSize: 1152 }, - 'ViT-SO400M-16-SigLIP2-256__webli': { dimSize: 1152 }, - 'ViT-SO400M-16-SigLIP2-384__webli': { dimSize: 1152 }, - 'ViT-SO400M-16-SigLIP2-512__webli': { dimSize: 1152 }, - 'ViT-gopt-16-SigLIP2-256__webli': { dimSize: 1536 }, - 'ViT-gopt-16-SigLIP2-384__webli': { dimSize: 1536 }, -}; - -type SharpRotationData = { - angle?: number; - flip?: boolean; - flop?: boolean; -}; -export const ORIENTATION_TO_SHARP_ROTATION: Record = { - [ExifOrientation.Horizontal]: { angle: 0 }, - [ExifOrientation.MirrorHorizontal]: { angle: 0, flop: true }, - [ExifOrientation.Rotate180]: { angle: 180 }, - [ExifOrientation.MirrorVertical]: { angle: 180, flop: true }, - [ExifOrientation.MirrorHorizontalRotate270CW]: { angle: 270, flip: true }, - [ExifOrientation.Rotate90CW]: { angle: 90 }, - [ExifOrientation.MirrorHorizontalRotate90CW]: { angle: 90, flip: true }, - [ExifOrientation.Rotate270CW]: { angle: 270 }, -} as const; - export const endpointTags: Record = { - [ApiTag.Activities]: 'An activity is a like or a comment made by a user on an asset or album.', - [ApiTag.Albums]: 'An album is a collection of assets that can be shared with other users or via shared links.', - [ApiTag.ApiKeys]: 'An api key can be used to programmatically access the Immich API.', - [ApiTag.Assets]: 'An asset is an image or video that has been uploaded to Immich.', - [ApiTag.Authentication]: 'Endpoints related to user authentication, including OAuth.', - [ApiTag.AuthenticationAdmin]: 'Administrative endpoints related to authentication.', - [ApiTag.DatabaseBackups]: 'Manage backups of the Immich database.', - [ApiTag.Deprecated]: 'Deprecated endpoints that are planned for removal in the next major release.', - [ApiTag.Download]: 'Endpoints for downloading assets or collections of assets.', - [ApiTag.Duplicates]: 'Endpoints for managing and identifying duplicate assets.', - [ApiTag.Faces]: - 'A face is a detected human face within an asset, which can be associated with a person. Faces are normally detected via machine learning, but can also be created via manually.', - [ApiTag.Jobs]: - 'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.', - [ApiTag.Libraries]: - 'An external library is made up of input file paths or expressions that are scanned for asset files. Discovered files are automatically imported. Assets much be unique within a library, but can be duplicated across libraries. Each user has a default upload library, and can have one or more external libraries.', - [ApiTag.Maintenance]: 'Maintenance mode allows you to put Immich in a read-only state to perform various operations.', - [ApiTag.Map]: - 'Map endpoints include supplemental functionality related to geolocation, such as reverse geocoding and retrieving map markers for assets with geolocation data.', - [ApiTag.Memories]: - 'A memory is a specialized collection of assets with dedicated viewing implementations in the web and mobile clients. A memory includes fields related to visibility and are automatically generated per user via a background job.', - [ApiTag.Notifications]: - 'A notification is a specialized message sent to users to inform them of important events. Currently, these notifications are only shown in the Immich web application.', - [ApiTag.NotificationsAdmin]: 'Notification administrative endpoints.', - [ApiTag.Partners]: 'A partner is a link with another user that allows sharing of assets between two users.', - [ApiTag.People]: - 'A person is a collection of faces, which can be favorited and named. A person can also be merged into another person. People are automatically created via the face recognition job.', - [ApiTag.Plugins]: - 'A plugin is an installed module that makes filters and actions available for the workflow feature.', - [ApiTag.Queues]: - 'Queues and background jobs are used for processing tasks asynchronously. Queues can be paused and resumed as needed.', - [ApiTag.Search]: - 'Endpoints related to searching assets via text, smart search, optical character recognition (OCR), and other filters like person, album, and other metadata. Search endpoints usually support pagination and sorting.', - [ApiTag.Server]: - 'Information about the current server deployment, including version and build information, available features, supported media types, and more.', - [ApiTag.Sessions]: - 'A session represents an authenticated login session for a user. Sessions also appear in the web application as "Authorized devices".', - [ApiTag.SharedLinks]: - 'A shared link is a public url that provides access to a specific album, asset, or collection of assets. A shared link can be protected with a password, include a specific slug, allow or disallow downloads, and optionally include an expiration date.', - [ApiTag.Stacks]: - 'A stack is a group of related assets. One asset is the "primary" asset, and the rest are "child" assets. On the main timeline, stack parents are included by default, while child assets are hidden.', - [ApiTag.Sync]: 'A collection of endpoints for the new mobile synchronization implementation.', - [ApiTag.SystemConfig]: 'Endpoints to view, modify, and validate the system configuration settings.', - [ApiTag.SystemMetadata]: - 'Endpoints to view, modify, and validate the system metadata, which includes information about things like admin onboarding status.', - [ApiTag.Tags]: - 'A tag is a user-defined label that can be applied to assets for organizational purposes. Tags can also be hierarchical, allowing for parent-child relationships between tags.', - [ApiTag.Timeline]: - 'Specialized endpoints related to the timeline implementation used in the web application. External applications or tools should not use or rely on these endpoints, as they are subject to change without notice.', - [ApiTag.Trash]: - 'Endpoints for managing the trash can, which includes assets that have been discarded. Items in the trash are automatically deleted after a configured amount of time.', - [ApiTag.UsersAdmin]: - 'Administrative endpoints for managing users, including creating, updating, deleting, and restoring users. Also includes endpoints for resetting passwords and PIN codes.', - [ApiTag.Users]: - 'Endpoints for viewing and updating the current users, including product key information, profile picture data, onboarding progress, and more.', - [ApiTag.Views]: 'Endpoints for specialized views, such as the folder view.', - [ApiTag.Workflows]: - 'A workflow is a set of actions that run whenever a triggering event occurs. Workflows also can include filters to further limit execution.', + [ApiTag.ApiKeys]: 'An API key can be used to programmatically access the API.', + [ApiTag.Authentication]: 'Endpoints related to user authentication.', + [ApiTag.Server]: 'Information about the current server deployment, including version, features, and health.', + [ApiTag.Sessions]: 'A session represents an authenticated login session for a user.', + [ApiTag.Users]: 'Endpoints for viewing and updating the current user.', }; diff --git a/server/src/controllers/activity.controller.spec.ts b/server/src/controllers/activity.controller.spec.ts deleted file mode 100644 index bf2038048f..0000000000 --- a/server/src/controllers/activity.controller.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ActivityController } from 'src/controllers/activity.controller'; -import { ActivityService } from 'src/services/activity.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(ActivityController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(ActivityService); - - beforeAll(async () => { - ctx = await controllerSetup(ActivityController, [{ provide: ActivityService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /activities', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/activities'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require an albumId', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/activities'); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['albumId must be a UUID']))); - }); - - it('should reject an invalid albumId', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/activities').query({ albumId: '123' }); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['albumId must be a UUID']))); - }); - - it('should reject an invalid assetId', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .get('/activities') - .query({ albumId: factory.uuid(), assetId: '123' }); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['assetId must be a UUID']))); - }); - }); - - describe('POST /activities', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/activities'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require an albumId', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/activities').send({ albumId: '123' }); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['albumId must be a UUID']))); - }); - - it('should require a comment when type is comment', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/activities') - .send({ albumId: factory.uuid(), type: 'comment', comment: null }); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(['comment must be a string', 'comment should not be empty'])); - }); - }); - - describe('DELETE /activities/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/activities/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).delete(`/activities/123`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - }); -}); diff --git a/server/src/controllers/activity.controller.ts b/server/src/controllers/activity.controller.ts deleted file mode 100644 index 850e95510f..0000000000 --- a/server/src/controllers/activity.controller.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Query, Res } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - ActivityCreateDto, - ActivityDto, - ActivityResponseDto, - ActivitySearchDto, - ActivityStatisticsResponseDto, -} from 'src/dtos/activity.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { ActivityService } from 'src/services/activity.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Activities) -@Controller('activities') -export class ActivityController { - constructor(private service: ActivityService) {} - - @Get() - @Authenticated({ permission: Permission.ActivityRead }) - @Endpoint({ - summary: 'List all activities', - description: - 'Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise { - return this.service.getAll(auth, dto); - } - - @Post() - @Authenticated({ permission: Permission.ActivityCreate }) - @Endpoint({ - summary: 'Create an activity', - description: 'Create a like or a comment for an album, or an asset in an album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async createActivity( - @Auth() auth: AuthDto, - @Body() dto: ActivityCreateDto, - @Res({ passthrough: true }) res: Response, - ): Promise { - const { duplicate, value } = await this.service.create(auth, dto); - if (duplicate) { - res.status(HttpStatus.OK); - } - return value; - } - - @Get('statistics') - @Authenticated({ permission: Permission.ActivityStatistics }) - @Endpoint({ - summary: 'Retrieve activity statistics', - description: 'Returns the number of likes and comments for a given album or asset in an album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise { - return this.service.getStatistics(auth, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.ActivityDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete an activity', - description: 'Removes a like or comment from a given album or asset in an album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.delete(auth, id); - } -} diff --git a/server/src/controllers/album.controller.spec.ts b/server/src/controllers/album.controller.spec.ts deleted file mode 100644 index d13227555b..0000000000 --- a/server/src/controllers/album.controller.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { AlbumController } from 'src/controllers/album.controller'; -import { AlbumService } from 'src/services/album.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(AlbumController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(AlbumService); - - beforeAll(async () => { - ctx = await controllerSetup(AlbumController, [{ provide: AlbumService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /albums', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/albums'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should reject an invalid shared param', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/albums?shared=invalid'); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(['shared must be a boolean value'])); - }); - - it('should reject an invalid assetId param', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/albums?assetId=invalid'); - expect(status).toEqual(400); - expect(body).toEqual(factory.responses.badRequest(['assetId must be a UUID'])); - }); - }); - - describe('GET /albums/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/albums/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /albums/statistics', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/albums/statistics'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /albums', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/albums').send({ albumName: 'New album' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /albums/:id/assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/albums/${factory.uuid()}/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /albums/assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/albums/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PATCH /albums/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).patch(`/albums/${factory.uuid()}`).send({ albumName: 'New album name' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('DELETE /albums/:id/assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/albums/${factory.uuid()}/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT :id/users', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/albums/${factory.uuid()}/users`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/album.controller.ts b/server/src/controllers/album.controller.ts deleted file mode 100644 index dad70257a7..0000000000 --- a/server/src/controllers/album.controller.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - AddUsersDto, - AlbumInfoDto, - AlbumResponseDto, - AlbumsAddAssetsDto, - AlbumsAddAssetsResponseDto, - AlbumStatisticsResponseDto, - CreateAlbumDto, - GetAlbumsDto, - UpdateAlbumDto, - UpdateAlbumUserDto, -} from 'src/dtos/album.dto'; -import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { AlbumService } from 'src/services/album.service'; -import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Albums) -@Controller('albums') -export class AlbumController { - constructor(private service: AlbumService) {} - - @Get() - @Authenticated({ permission: Permission.AlbumRead }) - @Endpoint({ - summary: 'List all albums', - description: 'Retrieve a list of albums available to the authenticated user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise { - return this.service.getAll(auth, query); - } - - @Post() - @Authenticated({ permission: Permission.AlbumCreate }) - @Endpoint({ - summary: 'Create an album', - description: 'Create a new album. The album can also be created with initial users and assets.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise { - return this.service.create(auth, dto); - } - - @Get('statistics') - @Authenticated({ permission: Permission.AlbumStatistics }) - @Endpoint({ - summary: 'Retrieve album statistics', - description: 'Returns statistics about the albums available to the authenticated user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAlbumStatistics(@Auth() auth: AuthDto): Promise { - return this.service.getStatistics(auth); - } - - @Authenticated({ permission: Permission.AlbumRead, sharedLink: true }) - @Get(':id') - @Endpoint({ - summary: 'Retrieve an album', - description: 'Retrieve information about a specific album by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAlbumInfo( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Query() dto: AlbumInfoDto, - ): Promise { - return this.service.get(auth, id, dto); - } - - @Patch(':id') - @Authenticated({ permission: Permission.AlbumUpdate }) - @Endpoint({ - summary: 'Update an album', - description: - 'Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateAlbumInfo( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: UpdateAlbumDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.AlbumDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete an album', - description: - 'Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) { - return this.service.delete(auth, id); - } - - @Put(':id/assets') - @Authenticated({ permission: Permission.AlbumAssetCreate, sharedLink: true }) - @Endpoint({ - summary: 'Add assets to an album', - description: 'Add multiple assets to a specific album by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - addAssetsToAlbum( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: BulkIdsDto, - ): Promise { - return this.service.addAssets(auth, id, dto); - } - - @Put('assets') - @Authenticated({ permission: Permission.AlbumAssetCreate, sharedLink: true }) - @Endpoint({ - summary: 'Add assets to albums', - description: 'Send a list of asset IDs and album IDs to add each asset to each album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - addAssetsToAlbums(@Auth() auth: AuthDto, @Body() dto: AlbumsAddAssetsDto): Promise { - return this.service.addAssetsToAlbums(auth, dto); - } - - @Delete(':id/assets') - @Authenticated({ permission: Permission.AlbumAssetDelete }) - @Endpoint({ - summary: 'Remove assets from an album', - description: 'Remove multiple assets from a specific album by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removeAssetFromAlbum( - @Auth() auth: AuthDto, - @Body() dto: BulkIdsDto, - @Param() { id }: UUIDParamDto, - ): Promise { - return this.service.removeAssets(auth, id, dto); - } - - @Put(':id/users') - @Authenticated({ permission: Permission.AlbumUserCreate }) - @Endpoint({ - summary: 'Share album with users', - description: 'Share an album with multiple users. Each user can be given a specific role in the album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - addUsersToAlbum( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: AddUsersDto, - ): Promise { - return this.service.addUsers(auth, id, dto); - } - - @Put(':id/user/:userId') - @Authenticated({ permission: Permission.AlbumUserUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Update user role', - description: 'Change the role for a specific user in a specific album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateAlbumUser( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string, - @Body() dto: UpdateAlbumUserDto, - ): Promise { - return this.service.updateUser(auth, id, userId, dto); - } - - @Delete(':id/user/:userId') - @Authenticated({ permission: Permission.AlbumUserDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Remove user from album', - description: 'Remove a user from an album. Use an ID of "me" to leave a shared album.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removeUserFromAlbum( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string, - ): Promise { - return this.service.removeUser(auth, id, userId); - } -} diff --git a/server/src/controllers/api-key.controller.spec.ts b/server/src/controllers/api-key.controller.spec.ts deleted file mode 100644 index c6dab09a3c..0000000000 --- a/server/src/controllers/api-key.controller.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ApiKeyController } from 'src/controllers/api-key.controller'; -import { Permission } from 'src/enum'; -import { ApiKeyService } from 'src/services/api-key.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(ApiKeyController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(ApiKeyService); - - beforeAll(async () => { - ctx = await controllerSetup(ApiKeyController, [{ provide: ApiKeyService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /api-keys', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/api-keys').send({ name: 'API Key' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /api-keys', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/api-keys'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /api-keys/me', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/api-keys/me`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /api-keys/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/api-keys/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/api-keys/123`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - }); - - describe('PUT /api-keys/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/api-keys/${factory.uuid()}`).send({ name: 'new name' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/api-keys/123`) - .send({ name: 'new name', permissions: [Permission.All] }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - - it('should allow updating just the name', async () => { - const { status } = await request(ctx.getHttpServer()) - .put(`/api-keys/${factory.uuid()}`) - .send({ name: 'new name' }); - expect(status).toBe(200); - }); - }); - - describe('DELETE /api-keys/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/api-keys/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).delete(`/api-keys/123`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - }); -}); diff --git a/server/src/controllers/app.controller.spec.ts b/server/src/controllers/app.controller.spec.ts deleted file mode 100644 index 4cc00e78c5..0000000000 --- a/server/src/controllers/app.controller.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { AppController } from 'src/controllers/app.controller'; -import { SystemConfigService } from 'src/services/system-config.service'; -import request from 'supertest'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(AppController.name, () => { - let ctx: ControllerContext; - - beforeAll(async () => { - ctx = await controllerSetup(AppController, [ - { provide: SystemConfigService, useValue: mockBaseService(SystemConfigService) }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - ctx.reset(); - }); - - describe('GET /.well-known/immich', () => { - it('should not be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/.well-known/immich'); - expect(ctx.authenticate).not.toHaveBeenCalled(); - }); - - it('should return a 200 status code', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/.well-known/immich'); - expect(status).toBe(200); - expect(body).toEqual({ - api: { - endpoint: '/api', - }, - }); - }); - }); - - describe('GET /custom.css', () => { - it('should not be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/custom.css'); - expect(ctx.authenticate).not.toHaveBeenCalled(); - }); - - it('should reply with text/css', async () => { - const { status, headers } = await request(ctx.getHttpServer()).get('/custom.css'); - expect(status).toBe(200); - expect(headers['content-type']).toEqual('text/css; charset=utf-8'); - }); - }); -}); diff --git a/server/src/controllers/app.controller.ts b/server/src/controllers/app.controller.ts index 3fe9b49368..b4d7f232eb 100644 --- a/server/src/controllers/app.controller.ts +++ b/server/src/controllers/app.controller.ts @@ -1,25 +1,18 @@ import { Controller, Get, Header } from '@nestjs/common'; import { ApiExcludeEndpoint } from '@nestjs/swagger'; -import { SystemConfigService } from 'src/services/system-config.service'; @Controller() export class AppController { - constructor(private service: SystemConfigService) {} - @ApiExcludeEndpoint() @Get('.well-known/immich') getImmichWellKnown() { - return { - api: { - endpoint: '/api', - }, - }; + return { api: { endpoint: '/api' } }; } @ApiExcludeEndpoint() @Get('custom.css') @Header('Content-Type', 'text/css') getCustomCss() { - return this.service.getCustomCss(); + return ''; } } diff --git a/server/src/controllers/asset-media.controller.spec.ts b/server/src/controllers/asset-media.controller.spec.ts deleted file mode 100644 index c2f6aeacef..0000000000 --- a/server/src/controllers/asset-media.controller.spec.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { AssetMediaController } from 'src/controllers/asset-media.controller'; -import { AssetMediaStatus } from 'src/dtos/asset-media-response.dto'; -import { AssetMetadataKey } from 'src/enum'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AssetMediaService } from 'src/services/asset-media.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -const makeUploadDto = (options?: { omit: string }): Record => { - const dto: Record = { - deviceAssetId: 'example-image', - deviceId: 'TEST', - fileCreatedAt: new Date().toISOString(), - fileModifiedAt: new Date().toISOString(), - isFavorite: 'false', - duration: '0:00:00.000000', - }; - - const omit = options?.omit; - if (omit) { - delete dto[omit]; - } - - return dto; -}; - -describe(AssetMediaController.name, () => { - let ctx: ControllerContext; - const assetData = Buffer.from('123'); - const filename = 'example.png'; - const service = mockBaseService(AssetMediaService); - - beforeAll(async () => { - ctx = await controllerSetup(AssetMediaController, [ - { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, - { provide: AssetMediaService, useValue: service }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - service.uploadAsset.mockResolvedValue({ status: AssetMediaStatus.DUPLICATE, id: factory.uuid() }); - - ctx.reset(); - }); - - describe('POST /assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post(`/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should accept metadata', async () => { - const mobileMetadata = { key: AssetMetadataKey.MobileApp, value: { iCloudId: '123' } }; - const { status } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ - ...makeUploadDto(), - metadata: JSON.stringify([mobileMetadata]), - }); - - expect(service.uploadAsset).toHaveBeenCalledWith( - undefined, - expect.objectContaining({ metadata: [mobileMetadata] }), - expect.objectContaining({ originalName: 'example.png' }), - undefined, - ); - - expect(status).toBe(200); - }); - - it('should handle invalid metadata json', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ - ...makeUploadDto(), - metadata: 'not-a-string-string', - }); - - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['metadata must be valid JSON'])); - }); - - it('should require `deviceAssetId`', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ ...makeUploadDto({ omit: 'deviceAssetId' }) }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(['deviceAssetId must be a string', 'deviceAssetId should not be empty']), - ); - }); - - it('should require `deviceId`', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ ...makeUploadDto({ omit: 'deviceId' }) }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['deviceId must be a string', 'deviceId should not be empty'])); - }); - - it('should require `fileCreatedAt`', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ ...makeUploadDto({ omit: 'fileCreatedAt' }) }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(['fileCreatedAt must be a Date instance', 'fileCreatedAt should not be empty']), - ); - }); - - it('should require `fileModifiedAt`', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field(makeUploadDto({ omit: 'fileModifiedAt' })); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(['fileModifiedAt must be a Date instance', 'fileModifiedAt should not be empty']), - ); - }); - - it('should throw if `isFavorite` is not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ ...makeUploadDto(), isFavorite: 'not-a-boolean' }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['isFavorite must be a boolean value'])); - }); - - it('should throw if `visibility` is not an enum', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/assets') - .attach('assetData', assetData, filename) - .field({ ...makeUploadDto(), visibility: 'not-an-option' }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest([expect.stringContaining('visibility must be one of the following values:')]), - ); - }); - - // TODO figure out how to deal with `sendFile` - describe.skip('GET /assets/:id/original', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/original`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - // TODO figure out how to deal with `sendFile` - describe.skip('GET /assets/:id/thumbnail', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/thumbnail`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/server/src/controllers/asset-media.controller.ts b/server/src/controllers/asset-media.controller.ts deleted file mode 100644 index ec6083cfa8..0000000000 --- a/server/src/controllers/asset-media.controller.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { - Body, - Controller, - Get, - HttpCode, - HttpStatus, - Next, - Param, - ParseFilePipe, - Post, - Put, - Query, - Req, - Res, - UploadedFiles, - UseInterceptors, -} from '@nestjs/common'; -import { ApiBody, ApiConsumes, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { NextFunction, Request, Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - AssetBulkUploadCheckResponseDto, - AssetMediaResponseDto, - AssetMediaStatus, - CheckExistingAssetsResponseDto, -} from 'src/dtos/asset-media-response.dto'; -import { - AssetBulkUploadCheckDto, - AssetMediaCreateDto, - AssetMediaOptionsDto, - AssetMediaReplaceDto, - AssetMediaSize, - CheckExistingAssetsDto, - UploadFieldName, -} from 'src/dtos/asset-media.dto'; -import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ApiTag, ImmichHeader, Permission, RouteKey } from 'src/enum'; -import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor'; -import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; -import { FileUploadInterceptor, getFiles } from 'src/middleware/file-upload.interceptor'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AssetMediaService } from 'src/services/asset-media.service'; -import { UploadFiles } from 'src/types'; -import { ImmichFileResponse, sendFile } from 'src/utils/file'; -import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Assets) -@Controller(RouteKey.Asset) -export class AssetMediaController { - constructor( - private logger: LoggingRepository, - private service: AssetMediaService, - ) {} - - @Post() - @Authenticated({ permission: Permission.AssetUpload, sharedLink: true }) - @UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor) - @ApiConsumes('multipart/form-data') - @ApiHeader({ - name: ImmichHeader.Checksum, - description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded', - required: false, - }) - @ApiBody({ description: 'Asset Upload Information', type: AssetMediaCreateDto }) - @ApiResponse({ - status: 200, - description: 'Asset is a duplicate', - type: AssetMediaResponseDto, - }) - @ApiResponse({ - status: 201, - description: 'Asset uploaded successfully', - type: AssetMediaResponseDto, - }) - @Endpoint({ - summary: 'Upload asset', - description: 'Uploads a new asset to the server.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async uploadAsset( - @Auth() auth: AuthDto, - @UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] })) files: UploadFiles, - @Body() dto: AssetMediaCreateDto, - @Res({ passthrough: true }) res: Response, - ): Promise { - const { file, sidecarFile } = getFiles(files); - const responseDto = await this.service.uploadAsset(auth, dto, file, sidecarFile); - - if (responseDto.status === AssetMediaStatus.DUPLICATE) { - res.status(HttpStatus.OK); - } - - return responseDto; - } - - @Get(':id/original') - @FileResponse() - @Authenticated({ permission: Permission.AssetDownload, sharedLink: true }) - @Endpoint({ - summary: 'Download original asset', - description: 'Downloads the original file of the specified asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async downloadAsset( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Query() dto: AssetDownloadOriginalDto, - @Res() res: Response, - @Next() next: NextFunction, - ) { - await sendFile(res, next, () => this.service.downloadOriginal(auth, id, dto), this.logger); - } - - @Put(':id/original') - @UseInterceptors(FileUploadInterceptor) - @ApiConsumes('multipart/form-data') - @ApiResponse({ - status: 200, - description: 'Asset replaced successfully', - type: AssetMediaResponseDto, - }) - @Endpoint({ - summary: 'Replace asset', - description: 'Replace the asset with new file, without changing its id.', - history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'copyAsset' }), - }) - @Authenticated({ permission: Permission.AssetReplace, sharedLink: true }) - async replaceAsset( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator([UploadFieldName.ASSET_DATA])] })) - files: UploadFiles, - @Body() dto: AssetMediaReplaceDto, - @Res({ passthrough: true }) res: Response, - ): Promise { - const { file } = getFiles(files); - const responseDto = await this.service.replaceAsset(auth, id, dto, file); - if (responseDto.status === AssetMediaStatus.DUPLICATE) { - res.status(HttpStatus.OK); - } - return responseDto; - } - - @Get(':id/thumbnail') - @FileResponse() - @Authenticated({ permission: Permission.AssetView, sharedLink: true }) - @Endpoint({ - summary: 'View asset thumbnail', - description: - 'Retrieve the thumbnail image for the specified asset. Viewing the fullsize thumbnail might redirect to downloadAsset, which requires a different permission.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async viewAsset( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Query() dto: AssetMediaOptionsDto, - @Req() req: Request, - @Res() res: Response, - @Next() next: NextFunction, - ) { - const viewThumbnailRes = await this.service.viewThumbnail(auth, id, dto); - - if (viewThumbnailRes instanceof ImmichFileResponse) { - await sendFile(res, next, () => Promise.resolve(viewThumbnailRes), this.logger); - } else { - // viewThumbnailRes is a AssetMediaRedirectResponse - // which redirects to the original asset or a specific size to make better use of caching - const { targetSize } = viewThumbnailRes; - const [reqPath, reqSearch] = req.url.split('?'); - let redirPath: string; - const redirSearchParams = new URLSearchParams(reqSearch); - if (targetSize === 'original') { - // relative path to this.downloadAsset - redirPath = 'original'; - redirSearchParams.delete('size'); - } else if (Object.values(AssetMediaSize).includes(targetSize)) { - redirPath = reqPath; - redirSearchParams.set('size', targetSize); - } else { - throw new Error('Invalid targetSize: ' + targetSize); - } - const finalRedirPath = redirPath + '?' + redirSearchParams.toString(); - return res.redirect(finalRedirPath); - } - } - - @Get(':id/video/playback') - @FileResponse() - @Authenticated({ permission: Permission.AssetView, sharedLink: true }) - @Endpoint({ - summary: 'Play asset video', - description: 'Streams the video file for the specified asset. This endpoint also supports byte range requests.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async playAssetVideo( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Res() res: Response, - @Next() next: NextFunction, - ) { - await sendFile(res, next, () => this.service.playbackVideo(auth, id), this.logger); - } - - @Post('exist') - @Authenticated({ permission: Permission.AssetUpload }) - @Endpoint({ - summary: 'Check existing assets', - description: 'Checks if multiple assets exist on the server and returns all existing - used by background backup', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - @HttpCode(HttpStatus.OK) - checkExistingAssets( - @Auth() auth: AuthDto, - @Body() dto: CheckExistingAssetsDto, - ): Promise { - return this.service.checkExistingAssets(auth, dto); - } - - @Post('bulk-upload-check') - @Authenticated({ permission: Permission.AssetUpload }) - @Endpoint({ - summary: 'Check bulk upload', - description: 'Determine which assets have already been uploaded to the server based on their SHA1 checksums.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - @HttpCode(HttpStatus.OK) - checkBulkUpload( - @Auth() auth: AuthDto, - @Body() dto: AssetBulkUploadCheckDto, - ): Promise { - return this.service.bulkUploadCheck(auth, dto); - } -} diff --git a/server/src/controllers/asset.controller.spec.ts b/server/src/controllers/asset.controller.spec.ts deleted file mode 100644 index 197e06d02d..0000000000 --- a/server/src/controllers/asset.controller.spec.ts +++ /dev/null @@ -1,393 +0,0 @@ -import { AssetController } from 'src/controllers/asset.controller'; -import { AssetMetadataKey } from 'src/enum'; -import { AssetService } from 'src/services/asset.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(AssetController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(AssetService); - - beforeAll(async () => { - ctx = await controllerSetup(AssetController, [{ provide: AssetService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - ctx.reset(); - service.resetAllMocks(); - }); - - describe('PUT /assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/assets`) - .send({ ids: ['123'] }); - - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['each value in ids must be a UUID'])); - }); - - it('should require duplicateId to be a string', async () => { - const id = factory.uuid(); - const { status, body } = await request(ctx.getHttpServer()) - .put(`/assets`) - .send({ ids: [id], duplicateId: true }); - - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['duplicateId must be a string'])); - }); - - it('should accept a null duplicateId', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()) - .put(`/assets`) - .send({ ids: [id], duplicateId: null }); - - expect(service.updateAll).toHaveBeenCalledWith(undefined, expect.objectContaining({ duplicateId: null })); - }); - }); - - describe('DELETE /assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()) - .delete(`/assets`) - .send({ ids: [factory.uuid()] }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete(`/assets`) - .send({ ids: ['123'] }); - - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['each value in ids must be a UUID'])); - }); - }); - - describe('GET /assets/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/assets/123`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - }); - - describe('PUT /assets/copy', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/copy`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require target and source id', async () => { - const { status, body } = await request(ctx.getHttpServer()).put('/assets/copy').send({}); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(expect.arrayContaining(['sourceId must be a UUID', 'targetId must be a UUID'])), - ); - }); - - it('should work', async () => { - const { status } = await request(ctx.getHttpServer()) - .put('/assets/copy') - .send({ sourceId: factory.uuid(), targetId: factory.uuid() }); - expect(status).toBe(204); - }); - }); - - describe('PUT /assets/metadata', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/assets/metadata`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid assetId', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put('/assets/metadata') - .send({ items: [{ assetId: '123', key: 'test', value: {} }] }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['items.0.assetId must be a UUID']))); - }); - - it('should require a key', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put('/assets/metadata') - .send({ items: [{ assetId: factory.uuid(), value: {} }] }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest( - expect.arrayContaining(['items.0.key must be a string', 'items.0.key should not be empty']), - ), - ); - }); - - it('should work', async () => { - const { status } = await request(ctx.getHttpServer()) - .put('/assets/metadata') - .send({ items: [{ assetId: factory.uuid(), key: AssetMetadataKey.MobileApp, value: { iCloudId: '123' } }] }); - expect(status).toBe(200); - }); - }); - - describe('DELETE /assets/metadata', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/assets/metadata`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid assetId', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete('/assets/metadata') - .send({ items: [{ assetId: '123', key: 'test' }] }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['items.0.assetId must be a UUID']))); - }); - - it('should require a key', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete('/assets/metadata') - .send({ items: [{ assetId: factory.uuid() }] }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest( - expect.arrayContaining(['items.0.key must be a string', 'items.0.key should not be empty']), - ), - ); - }); - - it('should work', async () => { - const { status } = await request(ctx.getHttpServer()) - .delete('/assets/metadata') - .send({ items: [{ assetId: factory.uuid(), key: AssetMetadataKey.MobileApp }] }); - expect(status).toBe(204); - }); - }); - - describe('PUT /assets/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/123`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/assets/123`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - - it('should reject invalid gps coordinates', async () => { - for (const test of [ - { latitude: 12 }, - { longitude: 12 }, - { latitude: 12, longitude: 'abc' }, - { latitude: 'abc', longitude: 12 }, - { latitude: null, longitude: 12 }, - { latitude: 12, longitude: null }, - { latitude: 91, longitude: 12 }, - { latitude: -91, longitude: 12 }, - { latitude: 12, longitude: -181 }, - { latitude: 12, longitude: 181 }, - ]) { - const { status, body } = await request(ctx.getHttpServer()).put(`/assets/${factory.uuid()}`).send(test); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest()); - } - }); - - it('should reject invalid rating', async () => { - for (const test of [{ rating: 7 }, { rating: 3.5 }, { rating: null }]) { - const { status, body } = await request(ctx.getHttpServer()).put(`/assets/${factory.uuid()}`).send(test); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest()); - } - }); - }); - - describe('GET /assets/statistics', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/statistics`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /assets/random', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/random`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should not allow count to be a string', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/assets/random?count=ABC'); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(['count must be a positive number', 'count must be an integer number']), - ); - }); - }); - - describe('GET /assets/:id/metadata', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/metadata`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /assets/:id/metadata', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/assets/${factory.uuid()}/metadata`).send({ items: [] }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/assets/123/metadata`).send({ items: [] }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['id must be a UUID']))); - }); - - it('should require items to be an array', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/assets/${factory.uuid()}/metadata`).send({}); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['items must be an array'])); - }); - - it('should require each item to have a valid key', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/assets/${factory.uuid()}/metadata`) - .send({ items: [{ value: { some: 'value' } }] }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(['items.0.key must be a string', 'items.0.key should not be empty']), - ); - }); - - it('should require each item to have a value', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/assets/${factory.uuid()}/metadata`) - .send({ items: [{ key: 'mobile-app', value: null }] }); - expect(status).toBe(400); - expect(body).toEqual( - factory.responses.badRequest(expect.arrayContaining([expect.stringContaining('value must be an object')])), - ); - }); - - describe(AssetMetadataKey.MobileApp, () => { - it('should accept valid data and pass to service correctly', async () => { - const assetId = factory.uuid(); - const { status } = await request(ctx.getHttpServer()) - .put(`/assets/${assetId}/metadata`) - .send({ items: [{ key: 'mobile-app', value: { iCloudId: '123' } }] }); - expect(service.upsertMetadata).toHaveBeenCalledWith(undefined, assetId, { - items: [{ key: 'mobile-app', value: { iCloudId: '123' } }], - }); - expect(status).toBe(200); - }); - - it('should work without iCloudId', async () => { - const assetId = factory.uuid(); - const { status } = await request(ctx.getHttpServer()) - .put(`/assets/${assetId}/metadata`) - .send({ items: [{ key: 'mobile-app', value: {} }] }); - expect(service.upsertMetadata).toHaveBeenCalledWith(undefined, assetId, { - items: [{ key: 'mobile-app', value: {} }], - }); - expect(status).toBe(200); - }); - }); - }); - - describe('GET /assets/:id/metadata/:key', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/assets/${factory.uuid()}/metadata/mobile-app`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/assets/123/metadata/mobile-app`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['id must be a UUID']))); - }); - }); - - describe('PUT /assets/:id/edits', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/assets/${factory.uuid()}/edits`).send({ edits: [] }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should accept valid edits and pass to service correctly', async () => { - const edits = [ - { - action: 'crop', - parameters: { - x: 0, - y: 0, - width: 100, - height: 100, - }, - }, - ]; - - const assetId = factory.uuid(); - const { status } = await request(ctx.getHttpServer()).put(`/assets/${assetId}/edits`).send({ - edits, - }); - - expect(service.editAsset).toHaveBeenCalledWith(undefined, assetId, { edits }); - expect(status).toBe(200); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/assets/123/edits`) - .send({ - edits: [ - { - action: 'crop', - parameters: { - x: 0, - y: 0, - width: 100, - height: 100, - }, - }, - ], - }); - - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(expect.arrayContaining(['id must be a UUID']))); - }); - - it('should require at least one edit', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/assets/${factory.uuid()}/edits`) - .send({ edits: [] }); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['edits must contain at least 1 elements'])); - }); - }); - - describe('DELETE /assets/:id/metadata/:key', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/assets/${factory.uuid()}/metadata/mobile-app`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).delete(`/assets/123/metadata/mobile-app`); - expect(status).toBe(400); - expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); - }); - }); -}); diff --git a/server/src/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts deleted file mode 100644 index 8eb3a5ce44..0000000000 --- a/server/src/controllers/asset.controller.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { - AssetBulkDeleteDto, - AssetBulkUpdateDto, - AssetCopyDto, - AssetJobsDto, - AssetMetadataBulkDeleteDto, - AssetMetadataBulkResponseDto, - AssetMetadataBulkUpsertDto, - AssetMetadataResponseDto, - AssetMetadataRouteParams, - AssetMetadataUpsertDto, - AssetStatsDto, - AssetStatsResponseDto, - DeviceIdDto, - RandomAssetsDto, - UpdateAssetDto, -} from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetEditActionListDto, AssetEditsDto } from 'src/dtos/editing.dto'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { ApiTag, Permission, RouteKey } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { AssetService } from 'src/services/asset.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Assets) -@Controller(RouteKey.Asset) -export class AssetController { - constructor(private service: AssetService) {} - - @Get('random') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Get random assets', - description: 'Retrieve a specified number of random assets for the authenticated user.', - history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'searchAssets' }), - }) - getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise { - return this.service.getRandom(auth, dto.count ?? 1); - } - - @Get('/device/:deviceId') - @Endpoint({ - summary: 'Retrieve assets by device ID', - description: 'Get all asset of a device that are in the database, ID only.', - history: new HistoryBuilder().added('v1').deprecated('v2'), - }) - @Authenticated() - getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) { - return this.service.getUserAssetsByDeviceId(auth, deviceId); - } - - @Get('statistics') - @Authenticated({ permission: Permission.AssetStatistics }) - @Endpoint({ - summary: 'Get asset statistics', - description: 'Retrieve various statistics about the assets owned by the authenticated user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise { - return this.service.getStatistics(auth, dto); - } - - @Post('jobs') - @Authenticated({ permission: Permission.JobCreate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Run an asset job', - description: 'Run a specific job on a set of assets.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise { - return this.service.run(auth, dto); - } - - @Put() - @Authenticated({ permission: Permission.AssetUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Update assets', - description: 'Updates multiple assets at the same time.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise { - return this.service.updateAll(auth, dto); - } - - @Delete() - @Authenticated({ permission: Permission.AssetDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete assets', - description: 'Deletes multiple assets at the same time.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise { - return this.service.deleteAll(auth, dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.AssetRead, sharedLink: true }) - @Endpoint({ - summary: 'Retrieve an asset', - description: 'Retrieve detailed information about a specific asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id) as Promise; - } - - @Put('copy') - @Authenticated({ permission: Permission.AssetCopy }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Copy asset', - description: 'Copy asset information like albums, tags, etc. from one asset to another.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - copyAsset(@Auth() auth: AuthDto, @Body() dto: AssetCopyDto): Promise { - return this.service.copy(auth, dto); - } - - @Put('metadata') - @Authenticated({ permission: Permission.AssetUpdate }) - @Endpoint({ - summary: 'Upsert asset metadata', - description: 'Upsert metadata key-value pairs for multiple assets.', - history: new HistoryBuilder().added('v1').beta('v2.5.0'), - }) - updateBulkAssetMetadata( - @Auth() auth: AuthDto, - @Body() dto: AssetMetadataBulkUpsertDto, - ): Promise { - return this.service.upsertBulkMetadata(auth, dto); - } - - @Delete('metadata') - @Authenticated({ permission: Permission.AssetUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete asset metadata', - description: 'Delete metadata key-value pairs for multiple assets.', - history: new HistoryBuilder().added('v1').beta('v2.5.0'), - }) - deleteBulkAssetMetadata(@Auth() auth: AuthDto, @Body() dto: AssetMetadataBulkDeleteDto): Promise { - return this.service.deleteBulkMetadata(auth, dto); - } - - @Put(':id') - @Authenticated({ permission: Permission.AssetUpdate }) - @Endpoint({ - summary: 'Update an asset', - description: 'Update information of a specific asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateAsset( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: UpdateAssetDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Get(':id/metadata') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Get asset metadata', - description: 'Retrieve all metadata key-value pairs associated with the specified asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetMetadata(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getMetadata(auth, id); - } - - @Get(':id/ocr') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Retrieve asset OCR data', - description: 'Retrieve all OCR (Optical Character Recognition) data associated with the specified asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetOcr(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getOcr(auth, id); - } - - @Put(':id/metadata') - @Authenticated({ permission: Permission.AssetUpdate }) - @Endpoint({ - summary: 'Update asset metadata', - description: 'Update or add metadata key-value pairs for the specified asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateAssetMetadata( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: AssetMetadataUpsertDto, - ): Promise { - return this.service.upsertMetadata(auth, id, dto); - } - - @Get(':id/metadata/:key') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Retrieve asset metadata by key', - description: 'Retrieve the value of a specific metadata key associated with the specified asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetMetadataByKey( - @Auth() auth: AuthDto, - @Param() { id, key }: AssetMetadataRouteParams, - ): Promise { - return this.service.getMetadataByKey(auth, id, key); - } - - @Delete(':id/metadata/:key') - @Authenticated({ permission: Permission.AssetUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete asset metadata by key', - description: 'Delete a specific metadata key-value pair associated with the specified asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteAssetMetadata(@Auth() auth: AuthDto, @Param() { id, key }: AssetMetadataRouteParams): Promise { - return this.service.deleteMetadataByKey(auth, id, key); - } - - @Get(':id/edits') - @Authenticated({ permission: Permission.AssetEditGet }) - @Endpoint({ - summary: 'Retrieve edits for an existing asset', - description: 'Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset.', - history: new HistoryBuilder().added('v2.5.0').beta('v2.5.0'), - }) - getAssetEdits(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getAssetEdits(auth, id); - } - - @Put(':id/edits') - @Authenticated({ permission: Permission.AssetEditCreate }) - @Endpoint({ - summary: 'Apply edits to an existing asset', - description: 'Apply a series of edit actions (crop, rotate, mirror) to the specified asset.', - history: new HistoryBuilder().added('v2.5.0').beta('v2.5.0'), - }) - editAsset( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: AssetEditActionListDto, - ): Promise { - return this.service.editAsset(auth, id, dto); - } - - @Delete(':id/edits') - @Authenticated({ permission: Permission.AssetEditDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Remove edits from an existing asset', - description: 'Removes all edit actions (crop, rotate, mirror) associated with the specified asset.', - history: new HistoryBuilder().added('v2.5.0').beta('v2.5.0'), - }) - removeAssetEdits(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.removeAssetEdits(auth, id); - } -} diff --git a/server/src/controllers/auth-admin.controller.ts b/server/src/controllers/auth-admin.controller.ts deleted file mode 100644 index d4cada9afc..0000000000 --- a/server/src/controllers/auth-admin.controller.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { AuthAdminService } from 'src/services/auth-admin.service'; - -@ApiTags(ApiTag.AuthenticationAdmin) -@Controller('admin/auth') -export class AuthAdminController { - constructor(private service: AuthAdminService) {} - @Post('unlink-all') - @Authenticated({ permission: Permission.AdminAuthUnlinkAll, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Unlink all OAuth accounts', - description: 'Unlinks all OAuth accounts associated with user accounts in the system.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - unlinkAllOAuthAccountsAdmin(@Auth() auth: AuthDto): Promise { - return this.service.unlinkAll(auth); - } -} diff --git a/server/src/controllers/auth.controller.spec.ts b/server/src/controllers/auth.controller.spec.ts deleted file mode 100644 index 7dd145ff5c..0000000000 --- a/server/src/controllers/auth.controller.spec.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { AuthController } from 'src/controllers/auth.controller'; -import { LoginResponseDto } from 'src/dtos/auth.dto'; -import { AuthService } from 'src/services/auth.service'; -import request from 'supertest'; -import { mediumFactory } from 'test/medium.factory'; -import { errorDto } from 'test/medium/responses'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(AuthController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(AuthService); - - beforeAll(async () => { - ctx = await controllerSetup(AuthController, [{ provide: AuthService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /auth/admin-sign-up', () => { - const name = 'admin'; - const email = 'admin@immich.cloud'; - const password = 'password'; - - it('should require an email address', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/admin-sign-up').send({ name, password }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest()); - }); - - it('should require a password', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/admin-sign-up').send({ name, email }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest()); - }); - - it('should require a name', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/admin-sign-up').send({ email, password }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest()); - }); - - it('should require a valid email', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/auth/admin-sign-up') - .send({ name, email: 'immich', password }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest()); - }); - - it('should transform email to lower case', async () => { - service.adminSignUp.mockReset(); - const { status } = await request(ctx.getHttpServer()) - .post('/auth/admin-sign-up') - .send({ name: 'admin', password: 'password', email: 'aDmIn@IMMICH.cloud' }); - expect(status).toEqual(201); - expect(service.adminSignUp).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@immich.cloud' })); - }); - - it('should accept an email with a local domain', async () => { - const { status } = await request(ctx.getHttpServer()) - .post('/auth/admin-sign-up') - .send({ name: 'admin', password: 'password', email: 'admin@local' }); - expect(status).toEqual(201); - }); - }); - - describe('POST /auth/login', () => { - it(`should require an email and password`, async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/login').send({ name: 'admin' }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([ - 'email should not be empty', - 'email must be an email', - 'password should not be empty', - 'password must be a string', - ]), - ); - }); - - it(`should not allow null email`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/auth/login') - .send({ name: 'admin', email: null, password: 'password' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['email should not be empty', 'email must be an email'])); - }); - - it(`should not allow null password`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/auth/login') - .send({ name: 'admin', email: 'admin@immich.cloud', password: null }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['password should not be empty', 'password must be a string'])); - }); - - it('should reject an invalid email', async () => { - service.login.mockResolvedValue({ accessToken: 'access-token' } as LoginResponseDto); - - const { status, body } = await request(ctx.getHttpServer()) - .post('/auth/login') - .send({ name: 'admin', email: [], password: 'password' }); - - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['email must be an email'])); - }); - - it('should transform the email to all lowercase', async () => { - service.login.mockResolvedValue({ accessToken: 'access-token' } as LoginResponseDto); - - const { status } = await request(ctx.getHttpServer()) - .post('/auth/login') - .send({ name: 'admin', email: 'aDmIn@iMmIcH.ApP', password: 'password' }); - - expect(status).toBe(201); - expect(service.login).toHaveBeenCalledWith( - expect.objectContaining({ email: 'admin@immich.app' }), - expect.anything(), - ); - }); - - it('should accept an email with a local domain', async () => { - service.login.mockResolvedValue({ accessToken: 'access-token' } as LoginResponseDto); - - const { status } = await request(ctx.getHttpServer()) - .post('/auth/login') - .send({ name: 'admin', email: 'admin@local', password: 'password' }); - - expect(status).toEqual(201); - expect(service.login).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@local' }), expect.anything()); - }); - - it('should auth cookies on a secure connection', async () => { - const loginResponse = mediumFactory.loginResponse(); - service.login.mockResolvedValue(loginResponse); - const { status, body, headers } = await request(ctx.getHttpServer()) - .post('/auth/login') - .send({ name: 'admin', email: 'admin@local', password: 'password' }); - - expect(status).toEqual(201); - expect(body).toEqual(loginResponse); - - const cookies = headers['set-cookie']; - expect(cookies).toHaveLength(3); - expect(cookies[0].split(';').map((item) => item.trim())).toEqual([ - `immich_access_token=${loginResponse.accessToken}`, - 'Max-Age=34560000', - 'Path=/', - expect.stringContaining('Expires='), - 'HttpOnly', - 'SameSite=Lax', - ]); - expect(cookies[1].split(';').map((item) => item.trim())).toEqual([ - 'immich_auth_type=password', - 'Max-Age=34560000', - 'Path=/', - expect.stringContaining('Expires='), - 'HttpOnly', - 'SameSite=Lax', - ]); - expect(cookies[2].split(';').map((item) => item.trim())).toEqual([ - 'immich_is_authenticated=true', - 'Max-Age=34560000', - 'Path=/', - expect.stringContaining('Expires='), - 'SameSite=Lax', - ]); - }); - }); - - describe('POST /auth/logout', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/auth/logout'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /auth/change-password', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()) - .post('/auth/change-password') - .send({ password: 'password', newPassword: 'Password1234', invalidateSessions: false }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /auth/pin-code', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/auth/pin-code').send({ pinCode: '123456' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should reject 5 digits', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/pin-code').send({ pinCode: '12345' }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest(['pinCode must be a 6-digit numeric string'])); - }); - - it('should reject 7 digits', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/pin-code').send({ pinCode: '1234567' }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest(['pinCode must be a 6-digit numeric string'])); - }); - - it('should reject non-numbers', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/auth/pin-code').send({ pinCode: 'A12345' }); - expect(status).toEqual(400); - expect(body).toEqual(errorDto.badRequest(['pinCode must be a 6-digit numeric string'])); - }); - }); - - describe('PUT /auth/pin-code', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put('/auth/pin-code').send({ pinCode: '123456', newPinCode: '654321' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('DELETE /auth/pin-code', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete('/auth/pin-code').send({ pinCode: '123456' }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /auth/status', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/auth/status'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts index 63cdce4f32..b04158877c 100644 --- a/server/src/controllers/auth.controller.ts +++ b/server/src/controllers/auth.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Post, Put, Req, Res } from '@nestjs/common'; +import { Body, Controller, Get, HttpCode, HttpStatus, Post, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { Endpoint, HistoryBuilder } from 'src/decorators'; @@ -9,10 +9,6 @@ import { LoginCredentialDto, LoginResponseDto, LogoutResponseDto, - PinCodeChangeDto, - PinCodeResetDto, - PinCodeSetupDto, - SessionUnlockDto, SignUpDto, ValidateAccessTokenResponseDto, } from 'src/dtos/auth.dto'; @@ -97,7 +93,6 @@ export class AuthController { @Auth() auth: AuthDto, ): Promise { const authType = (request.cookies || {})[ImmichCookie.AuthType]; - const body = await this.service.logout(auth, authType); return respondWithoutCookie(res, body, [ ImmichCookie.AccessToken, @@ -110,71 +105,10 @@ export class AuthController { @Authenticated() @Endpoint({ summary: 'Retrieve auth status', - description: - 'Get information about the current session, including whether the user has a password, and if the session can access locked assets.', + description: 'Get information about the current session.', history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) getAuthStatus(@Auth() auth: AuthDto): Promise { return this.service.getAuthStatus(auth); } - - @Post('pin-code') - @Authenticated({ permission: Permission.PinCodeCreate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Setup pin code', - description: 'Setup a new pin code for the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - setupPinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeSetupDto): Promise { - return this.service.setupPinCode(auth, dto); - } - - @Put('pin-code') - @Authenticated({ permission: Permission.PinCodeUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Change pin code', - description: 'Change the pin code for the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async changePinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeChangeDto): Promise { - return this.service.changePinCode(auth, dto); - } - - @Delete('pin-code') - @Authenticated({ permission: Permission.PinCodeDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Reset pin code', - description: 'Reset the pin code for the current user by providing the account password', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async resetPinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeResetDto): Promise { - return this.service.resetPinCode(auth, dto); - } - - @Post('session/unlock') - @Authenticated() - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Unlock auth session', - description: 'Temporarily grant the session elevated access to locked assets by providing the correct PIN code.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async unlockAuthSession(@Auth() auth: AuthDto, @Body() dto: SessionUnlockDto): Promise { - return this.service.unlockSession(auth, dto); - } - - @Post('session/lock') - @Authenticated() - @Endpoint({ - summary: 'Lock auth session', - description: 'Remove elevated access to locked assets from the current session.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - @HttpCode(HttpStatus.NO_CONTENT) - async lockAuthSession(@Auth() auth: AuthDto): Promise { - return this.service.lockSession(auth); - } } diff --git a/server/src/controllers/database-backup.controller.ts b/server/src/controllers/database-backup.controller.ts deleted file mode 100644 index 737c8f3958..0000000000 --- a/server/src/controllers/database-backup.controller.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Body, Controller, Delete, Get, Next, Param, Post, Res, UploadedFile, UseInterceptors } from '@nestjs/common'; -import { FileInterceptor } from '@nestjs/platform-express'; -import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; -import { NextFunction, Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - DatabaseBackupDeleteDto, - DatabaseBackupListResponseDto, - DatabaseBackupUploadDto, -} from 'src/dtos/database-backup.dto'; -import { ApiTag, ImmichCookie, Permission } from 'src/enum'; -import { Authenticated, FileResponse, GetLoginDetails } from 'src/middleware/auth.guard'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { LoginDetails } from 'src/services/auth.service'; -import { DatabaseBackupService } from 'src/services/database-backup.service'; -import { MaintenanceService } from 'src/services/maintenance.service'; -import { sendFile } from 'src/utils/file'; -import { respondWithCookie } from 'src/utils/response'; -import { FilenameParamDto } from 'src/validation'; - -@ApiTags(ApiTag.DatabaseBackups) -@Controller('admin/database-backups') -export class DatabaseBackupController { - constructor( - private logger: LoggingRepository, - private service: DatabaseBackupService, - private maintenanceService: MaintenanceService, - ) {} - - @Get() - @Endpoint({ - summary: 'List database backups', - description: 'Get the list of the successful and failed backups', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - @Authenticated({ permission: Permission.Maintenance, admin: true }) - listDatabaseBackups(): Promise { - return this.service.listBackups(); - } - - @Get(':filename') - @FileResponse() - @Endpoint({ - summary: 'Download database backup', - description: 'Downloads the database backup file', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - @Authenticated({ permission: Permission.BackupDownload, admin: true }) - async downloadDatabaseBackup( - @Param() { filename }: FilenameParamDto, - @Res() res: Response, - @Next() next: NextFunction, - ): Promise { - await sendFile(res, next, () => this.service.downloadBackup(filename), this.logger); - } - - @Delete() - @Endpoint({ - summary: 'Delete database backup', - description: 'Delete a backup by its filename', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - @Authenticated({ permission: Permission.BackupDelete, admin: true }) - async deleteDatabaseBackup(@Body() dto: DatabaseBackupDeleteDto): Promise { - return this.service.deleteBackup(dto.backups); - } - - @Post('start-restore') - @Endpoint({ - summary: 'Start database backup restore flow', - description: 'Put Immich into maintenance mode to restore a backup (Immich must not be configured)', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - async startDatabaseRestoreFlow( - @GetLoginDetails() loginDetails: LoginDetails, - @Res({ passthrough: true }) res: Response, - ): Promise { - const { jwt } = await this.maintenanceService.startRestoreFlow(); - return respondWithCookie(res, undefined, { - isSecure: loginDetails.isSecure, - values: [{ key: ImmichCookie.MaintenanceToken, value: jwt }], - }); - } - - @Post('upload') - @Authenticated({ permission: Permission.BackupUpload, admin: true }) - @ApiConsumes('multipart/form-data') - @ApiBody({ description: 'Backup Upload', type: DatabaseBackupUploadDto }) - @Endpoint({ - summary: 'Upload database backup', - description: 'Uploads .sql/.sql.gz file to restore backup from', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - @UseInterceptors(FileInterceptor('file')) - uploadDatabaseBackup( - @UploadedFile() - file: Express.Multer.File, - ): Promise { - return this.service.uploadBackup(file); - } -} diff --git a/server/src/controllers/download.controller.spec.ts b/server/src/controllers/download.controller.spec.ts deleted file mode 100644 index 00d03fc46f..0000000000 --- a/server/src/controllers/download.controller.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Readable } from 'node:stream'; -import { DownloadController } from 'src/controllers/download.controller'; -import { DownloadService } from 'src/services/download.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(DownloadController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(DownloadService); - - beforeAll(async () => { - ctx = await controllerSetup(DownloadController, [{ provide: DownloadService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /download/info', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()) - .post('/download/info') - .send({ assetIds: [factory.uuid()] }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /download/archive', () => { - it('should be an authenticated route', async () => { - const stream = new Readable({ - read() { - this.push('test'); - this.push(null); - }, - }); - service.downloadArchive.mockResolvedValue({ stream }); - await request(ctx.getHttpServer()) - .post('/download/archive') - .send({ assetIds: [factory.uuid()] }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/download.controller.ts b/server/src/controllers/download.controller.ts deleted file mode 100644 index 942d44f4c3..0000000000 --- a/server/src/controllers/download.controller.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Body, Controller, HttpCode, HttpStatus, Post, StreamableFile } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetIdsDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; -import { DownloadService } from 'src/services/download.service'; -import { asStreamableFile } from 'src/utils/file'; - -@ApiTags(ApiTag.Download) -@Controller('download') -export class DownloadController { - constructor(private service: DownloadService) {} - - @Post('info') - @Authenticated({ permission: Permission.AssetDownload, sharedLink: true }) - @Endpoint({ - summary: 'Retrieve download information', - description: - 'Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise { - return this.service.getDownloadInfo(auth, dto); - } - - @Post('archive') - @Authenticated({ permission: Permission.AssetDownload, sharedLink: true }) - @FileResponse() - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Download asset archive', - description: - 'Download a ZIP archive containing the specified assets. The assets must have been previously requested via the "getDownloadInfo" endpoint.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise { - return this.service.downloadArchive(auth, dto).then(asStreamableFile); - } -} diff --git a/server/src/controllers/duplicate.controller.ts b/server/src/controllers/duplicate.controller.ts deleted file mode 100644 index e8c8e5ef80..0000000000 --- a/server/src/controllers/duplicate.controller.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { DuplicateResponseDto } from 'src/dtos/duplicate.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { DuplicateService } from 'src/services/duplicate.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Duplicates) -@Controller('duplicates') -export class DuplicateController { - constructor(private service: DuplicateService) {} - - @Get() - @Authenticated({ permission: Permission.DuplicateRead }) - @Endpoint({ - summary: 'Retrieve duplicates', - description: 'Retrieve a list of duplicate assets available to the authenticated user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetDuplicates(@Auth() auth: AuthDto): Promise { - return this.service.getDuplicates(auth); - } - - @Delete() - @Authenticated({ permission: Permission.DuplicateDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete duplicates', - description: 'Delete multiple duplicate assets specified by their IDs.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteDuplicates(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { - return this.service.deleteAll(auth, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.DuplicateDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a duplicate', - description: 'Delete a single duplicate asset specified by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteDuplicate(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.delete(auth, id); - } -} diff --git a/server/src/controllers/face.controller.ts b/server/src/controllers/face.controller.ts deleted file mode 100644 index a1c1d6ee4d..0000000000 --- a/server/src/controllers/face.controller.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - AssetFaceCreateDto, - AssetFaceDeleteDto, - AssetFaceResponseDto, - FaceDto, - PersonResponseDto, -} from 'src/dtos/person.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { PersonService } from 'src/services/person.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Faces) -@Controller('faces') -export class FaceController { - constructor(private service: PersonService) {} - - @Post() - @Authenticated({ permission: Permission.FaceCreate }) - @Endpoint({ - summary: 'Create a face', - description: - 'Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createFace(@Auth() auth: AuthDto, @Body() dto: AssetFaceCreateDto) { - return this.service.createFace(auth, dto); - } - - @Get() - @Authenticated({ permission: Permission.FaceRead }) - @Endpoint({ - summary: 'Retrieve faces for asset', - description: 'Retrieve all faces belonging to an asset.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise { - return this.service.getFacesById(auth, dto); - } - - @Put(':id') - @Authenticated({ permission: Permission.FaceUpdate }) - @Endpoint({ - summary: 'Re-assign a face to another person', - description: 'Re-assign the face provided in the body to the person identified by the id in the path parameter.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - reassignFacesById( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: FaceDto, - ): Promise { - return this.service.reassignFacesById(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.FaceDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a face', - description: 'Delete a face identified by the id. Optionally can be force deleted.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: AssetFaceDeleteDto): Promise { - return this.service.deleteFace(auth, id, dto); - } -} diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index dc3754ce24..e446fd6330 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -1,81 +1,15 @@ -import { ActivityController } from 'src/controllers/activity.controller'; -import { AlbumController } from 'src/controllers/album.controller'; import { ApiKeyController } from 'src/controllers/api-key.controller'; import { AppController } from 'src/controllers/app.controller'; -import { AssetMediaController } from 'src/controllers/asset-media.controller'; -import { AssetController } from 'src/controllers/asset.controller'; -import { AuthAdminController } from 'src/controllers/auth-admin.controller'; import { AuthController } from 'src/controllers/auth.controller'; -import { DatabaseBackupController } from 'src/controllers/database-backup.controller'; -import { DownloadController } from 'src/controllers/download.controller'; -import { DuplicateController } from 'src/controllers/duplicate.controller'; -import { FaceController } from 'src/controllers/face.controller'; -import { JobController } from 'src/controllers/job.controller'; -import { LibraryController } from 'src/controllers/library.controller'; -import { MaintenanceController } from 'src/controllers/maintenance.controller'; -import { MapController } from 'src/controllers/map.controller'; -import { MemoryController } from 'src/controllers/memory.controller'; -import { NotificationAdminController } from 'src/controllers/notification-admin.controller'; -import { NotificationController } from 'src/controllers/notification.controller'; -import { OAuthController } from 'src/controllers/oauth.controller'; -import { PartnerController } from 'src/controllers/partner.controller'; -import { PersonController } from 'src/controllers/person.controller'; -import { PluginController } from 'src/controllers/plugin.controller'; -import { QueueController } from 'src/controllers/queue.controller'; -import { SearchController } from 'src/controllers/search.controller'; import { ServerController } from 'src/controllers/server.controller'; import { SessionController } from 'src/controllers/session.controller'; -import { SharedLinkController } from 'src/controllers/shared-link.controller'; -import { StackController } from 'src/controllers/stack.controller'; -import { SyncController } from 'src/controllers/sync.controller'; -import { SystemConfigController } from 'src/controllers/system-config.controller'; -import { SystemMetadataController } from 'src/controllers/system-metadata.controller'; -import { TagController } from 'src/controllers/tag.controller'; -import { TimelineController } from 'src/controllers/timeline.controller'; -import { TrashController } from 'src/controllers/trash.controller'; -import { UserAdminController } from 'src/controllers/user-admin.controller'; import { UserController } from 'src/controllers/user.controller'; -import { ViewController } from 'src/controllers/view.controller'; -import { WorkflowController } from 'src/controllers/workflow.controller'; export const controllers = [ ApiKeyController, - ActivityController, - AlbumController, AppController, - AssetController, - AssetMediaController, AuthController, - AuthAdminController, - DatabaseBackupController, - DownloadController, - DuplicateController, - FaceController, - JobController, - LibraryController, - MaintenanceController, - MapController, - MemoryController, - NotificationController, - NotificationAdminController, - OAuthController, - PartnerController, - PersonController, - PluginController, - QueueController, - SearchController, ServerController, SessionController, - SharedLinkController, - StackController, - SyncController, - SystemConfigController, - SystemMetadataController, - TagController, - TimelineController, - TrashController, - UserAdminController, UserController, - ViewController, - WorkflowController, ]; diff --git a/server/src/controllers/job.controller.ts b/server/src/controllers/job.controller.ts deleted file mode 100644 index 783d5a3133..0000000000 --- a/server/src/controllers/job.controller.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { JobCreateDto } from 'src/dtos/job.dto'; -import { QueueResponseLegacyDto, QueuesResponseLegacyDto } from 'src/dtos/queue-legacy.dto'; -import { QueueCommandDto, QueueNameParamDto } from 'src/dtos/queue.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { JobService } from 'src/services/job.service'; -import { QueueService } from 'src/services/queue.service'; - -@ApiTags(ApiTag.Jobs) -@Controller('jobs') -export class JobController { - constructor( - private service: JobService, - private queueService: QueueService, - ) {} - - @Get() - @Authenticated({ permission: Permission.JobRead, admin: true }) - @Endpoint({ - summary: 'Retrieve queue counts and status', - description: 'Retrieve the counts of the current queue, as well as the current status.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'), - }) - getQueuesLegacy(@Auth() auth: AuthDto): Promise { - return this.queueService.getAllLegacy(auth); - } - - @Post() - @Authenticated({ permission: Permission.JobCreate, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Create a manual job', - description: - 'Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createJob(@Body() dto: JobCreateDto): Promise { - return this.service.create(dto); - } - - @Put(':name') - @Authenticated({ permission: Permission.JobCreate, admin: true }) - @Endpoint({ - summary: 'Run jobs', - description: - 'Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2').deprecated('v2.4.0'), - }) - runQueueCommandLegacy( - @Param() { name }: QueueNameParamDto, - @Body() dto: QueueCommandDto, - ): Promise { - return this.queueService.runCommandLegacy(name, dto); - } -} diff --git a/server/src/controllers/library.controller.ts b/server/src/controllers/library.controller.ts deleted file mode 100644 index 5672e9117a..0000000000 --- a/server/src/controllers/library.controller.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - CreateLibraryDto, - LibraryResponseDto, - LibraryStatsResponseDto, - UpdateLibraryDto, - ValidateLibraryDto, - ValidateLibraryResponseDto, -} from 'src/dtos/library.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Authenticated } from 'src/middleware/auth.guard'; -import { LibraryService } from 'src/services/library.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Libraries) -@Controller('libraries') -export class LibraryController { - constructor(private service: LibraryService) {} - - @Get() - @Authenticated({ permission: Permission.LibraryRead, admin: true }) - @Endpoint({ - summary: 'Retrieve libraries', - description: 'Retrieve a list of external libraries.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAllLibraries(): Promise { - return this.service.getAll(); - } - - @Post() - @Authenticated({ permission: Permission.LibraryCreate, admin: true }) - @Endpoint({ - summary: 'Create a library', - description: 'Create a new external library.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createLibrary(@Body() dto: CreateLibraryDto): Promise { - return this.service.create(dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.LibraryRead, admin: true }) - @Endpoint({ - summary: 'Retrieve a library', - description: 'Retrieve an external library by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getLibrary(@Param() { id }: UUIDParamDto): Promise { - return this.service.get(id); - } - - @Put(':id') - @Authenticated({ permission: Permission.LibraryUpdate, admin: true }) - @Endpoint({ - summary: 'Update a library', - description: 'Update an existing external library.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise { - return this.service.update(id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.LibraryDelete, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a library', - description: 'Delete an external library by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteLibrary(@Param() { id }: UUIDParamDto): Promise { - return this.service.delete(id); - } - - @Post(':id/validate') - @Authenticated({ admin: true }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Validate library settings', - description: 'Validate the settings of an external library.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - // TODO: change endpoint to validate current settings instead - validate(@Param() { id }: UUIDParamDto, @Body() dto: ValidateLibraryDto): Promise { - return this.service.validate(id, dto); - } - - @Get(':id/statistics') - @Authenticated({ permission: Permission.LibraryStatistics, admin: true }) - @Endpoint({ - summary: 'Retrieve library statistics', - description: - 'Retrieve statistics for a specific external library, including number of videos, images, and storage usage.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise { - return this.service.getStatistics(id); - } - - @Post(':id/scan') - @Authenticated({ permission: Permission.LibraryUpdate, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Scan a library', - description: 'Queue a scan for the external library to find and import new assets.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - scanLibrary(@Param() { id }: UUIDParamDto): Promise { - return this.service.queueScan(id); - } -} diff --git a/server/src/controllers/maintenance.controller.spec.ts b/server/src/controllers/maintenance.controller.spec.ts deleted file mode 100644 index 094028687e..0000000000 --- a/server/src/controllers/maintenance.controller.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MaintenanceController } from 'src/controllers/maintenance.controller'; -import { MaintenanceAction } from 'src/enum'; -import { MaintenanceService } from 'src/services/maintenance.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(MaintenanceController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(MaintenanceService); - - beforeAll(async () => { - ctx = await controllerSetup(MaintenanceController, [{ provide: MaintenanceService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /admin/maintenance', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/admin/maintenance').send(); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a backup file when action is restore', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/admin/maintenance').send({ - action: MaintenanceAction.RestoreDatabase, - }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest(['restoreBackupFilename must be a string', 'restoreBackupFilename should not be empty']), - ); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/maintenance.controller.ts b/server/src/controllers/maintenance.controller.ts deleted file mode 100644 index 169fec7890..0000000000 --- a/server/src/controllers/maintenance.controller.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { BadRequestException, Body, Controller, Get, Post, Res } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - MaintenanceAuthDto, - MaintenanceDetectInstallResponseDto, - MaintenanceLoginDto, - MaintenanceStatusResponseDto, - SetMaintenanceModeDto, -} from 'src/dtos/maintenance.dto'; -import { ApiTag, ImmichCookie, MaintenanceAction, Permission } from 'src/enum'; -import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; -import { LoginDetails } from 'src/services/auth.service'; -import { MaintenanceService } from 'src/services/maintenance.service'; -import { respondWithCookie } from 'src/utils/response'; - -@ApiTags(ApiTag.Maintenance) -@Controller('admin/maintenance') -export class MaintenanceController { - constructor(private service: MaintenanceService) {} - - @Get('status') - @Endpoint({ - summary: 'Get maintenance mode status', - description: 'Fetch information about the currently running maintenance action.', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - getMaintenanceStatus(): MaintenanceStatusResponseDto { - return this.service.getMaintenanceStatus(); - } - - @Get('detect-install') - @Endpoint({ - summary: 'Detect existing install', - description: 'Collect integrity checks and other heuristics about local data.', - history: new HistoryBuilder().added('v2.5.0').alpha('v2.5.0'), - }) - @Authenticated({ permission: Permission.Maintenance, admin: true }) - detectPriorInstall(): Promise { - return this.service.detectPriorInstall(); - } - - @Post('login') - @Endpoint({ - summary: 'Log into maintenance mode', - description: 'Login with maintenance token or cookie to receive current information and perform further actions.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - maintenanceLogin(@Body() _dto: MaintenanceLoginDto): MaintenanceAuthDto { - throw new BadRequestException('Not in maintenance mode'); - } - - @Post() - @Endpoint({ - summary: 'Set maintenance mode', - description: 'Put Immich into or take it out of maintenance mode', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - @Authenticated({ permission: Permission.Maintenance, admin: true }) - async setMaintenanceMode( - @Auth() auth: AuthDto, - @Body() dto: SetMaintenanceModeDto, - @GetLoginDetails() loginDetails: LoginDetails, - @Res({ passthrough: true }) res: Response, - ): Promise { - if (dto.action !== MaintenanceAction.End) { - const { jwt } = await this.service.startMaintenance(dto, auth.user.name); - return respondWithCookie(res, undefined, { - isSecure: loginDetails.isSecure, - values: [{ key: ImmichCookie.MaintenanceToken, value: jwt }], - }); - } - } -} diff --git a/server/src/controllers/map.controller.ts b/server/src/controllers/map.controller.ts deleted file mode 100644 index ae3b56af28..0000000000 --- a/server/src/controllers/map.controller.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Controller, Get, HttpCode, HttpStatus, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - MapMarkerDto, - MapMarkerResponseDto, - MapReverseGeocodeDto, - MapReverseGeocodeResponseDto, -} from 'src/dtos/map.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { MapService } from 'src/services/map.service'; - -@ApiTags(ApiTag.Map) -@Controller('map') -export class MapController { - constructor(private service: MapService) {} - - @Get('markers') - @Authenticated({ permission: Permission.MapRead }) - @Endpoint({ - summary: 'Retrieve map markers', - description: 'Retrieve a list of latitude and longitude coordinates for every asset with location data.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise { - return this.service.getMapMarkers(auth, options); - } - - @Get('reverse-geocode') - @Authenticated({ permission: Permission.MapSearch }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Reverse geocode coordinates', - description: 'Retrieve location information (e.g., city, country) for given latitude and longitude coordinates.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - reverseGeocode(@Query() dto: MapReverseGeocodeDto): Promise { - return this.service.reverseGeocode(dto); - } -} diff --git a/server/src/controllers/memory.controller.spec.ts b/server/src/controllers/memory.controller.spec.ts deleted file mode 100644 index 8629b6c799..0000000000 --- a/server/src/controllers/memory.controller.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { MemoryController } from 'src/controllers/memory.controller'; -import { MemoryService } from 'src/services/memory.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(MemoryController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(MemoryService); - - beforeAll(async () => { - ctx = await controllerSetup(MemoryController, [{ provide: MemoryService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /memories', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/memories'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should not require any parameters', async () => { - await request(ctx.getHttpServer()).get('/memories').query({}); - expect(service.search).toHaveBeenCalled(); - }); - }); - - describe('POST /memories', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/memories'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should validate data when type is on this day', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/memories') - .send({ - type: 'on_this_day', - data: {}, - memoryAt: new Date(2021).toISOString(), - }); - - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest(['data.year must be a positive number', 'data.year must be an integer number']), - ); - }); - }); - - describe('GET /memories/statistics', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/memories/statistics'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /memories/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/memories/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/memories/invalid`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); - }); - }); - - describe('PUT /memories/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/memories/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/memories/invalid`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); - }); - }); - - describe('DELETE /memories/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/memories/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /memories/:id/assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/memories/${factory.uuid()}/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/memories/invalid/assets`).send({ ids: [] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); - }); - - it('should require a valid asset id', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/memories/${factory.uuid()}/assets`) - .send({ ids: ['invalid'] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); - }); - }); - - describe('DELETE /memories/:id/assets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/memories/${factory.uuid()}/assets`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid id', async () => { - const { status, body } = await request(ctx.getHttpServer()).delete(`/memories/invalid/assets`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); - }); - - it('should require a valid asset id', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete(`/memories/${factory.uuid()}/assets`) - .send({ ids: ['invalid'] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); - }); - }); -}); diff --git a/server/src/controllers/memory.controller.ts b/server/src/controllers/memory.controller.ts deleted file mode 100644 index cbf86199bb..0000000000 --- a/server/src/controllers/memory.controller.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - MemoryCreateDto, - MemoryResponseDto, - MemorySearchDto, - MemoryStatisticsResponseDto, - MemoryUpdateDto, -} from 'src/dtos/memory.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { MemoryService } from 'src/services/memory.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Memories) -@Controller('memories') -export class MemoryController { - constructor(private service: MemoryService) {} - - @Get() - @Authenticated({ permission: Permission.MemoryRead }) - @Endpoint({ - summary: 'Retrieve memories', - description: - 'Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchMemories(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise { - return this.service.search(auth, dto); - } - - @Post() - @Authenticated({ permission: Permission.MemoryCreate }) - @Endpoint({ - summary: 'Create a memory', - description: - 'Create a new memory by providing a name, description, and a list of asset IDs to include in the memory.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Get('statistics') - @Authenticated({ permission: Permission.MemoryStatistics }) - @Endpoint({ - summary: 'Retrieve memories statistics', - description: 'Retrieve statistics about memories, such as total count and other relevant metrics.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise { - return this.service.statistics(auth, dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.MemoryRead }) - @Endpoint({ - summary: 'Retrieve a memory', - description: 'Retrieve a specific memory by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.MemoryUpdate }) - @Endpoint({ - summary: 'Update a memory', - description: 'Update an existing memory by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateMemory( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: MemoryUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.MemoryDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a memory', - description: 'Delete a specific memory by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.remove(auth, id); - } - - @Put(':id/assets') - @Authenticated({ permission: Permission.MemoryAssetCreate }) - @Endpoint({ - summary: 'Add assets to a memory', - description: 'Add a list of asset IDs to a specific memory.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - addMemoryAssets( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: BulkIdsDto, - ): Promise { - return this.service.addAssets(auth, id, dto); - } - - @Delete(':id/assets') - @Authenticated({ permission: Permission.MemoryAssetDelete }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Remove assets from a memory', - description: 'Remove a list of asset IDs from a specific memory.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removeMemoryAssets( - @Auth() auth: AuthDto, - @Body() dto: BulkIdsDto, - @Param() { id }: UUIDParamDto, - ): Promise { - return this.service.removeAssets(auth, id, dto); - } -} diff --git a/server/src/controllers/notification-admin.controller.spec.ts b/server/src/controllers/notification-admin.controller.spec.ts deleted file mode 100644 index b93726eb32..0000000000 --- a/server/src/controllers/notification-admin.controller.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { NotificationAdminController } from 'src/controllers/notification-admin.controller'; -import { NotificationAdminService } from 'src/services/notification-admin.service'; -import request from 'supertest'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(NotificationAdminController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(NotificationAdminService); - - beforeAll(async () => { - ctx = await controllerSetup(NotificationAdminController, [ - { provide: NotificationAdminService, useValue: service }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /admin/notifications', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/admin/notifications'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should accept a null readAt', async () => { - await request(ctx.getHttpServer()) - .post(`/admin/notifications`) - .send({ title: 'Test', userId: factory.uuid(), readAt: null }); - expect(service.create).toHaveBeenCalledWith(undefined, expect.objectContaining({ readAt: null })); - }); - }); -}); diff --git a/server/src/controllers/notification-admin.controller.ts b/server/src/controllers/notification-admin.controller.ts deleted file mode 100644 index c322c5a2b6..0000000000 --- a/server/src/controllers/notification-admin.controller.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Body, Controller, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - NotificationCreateDto, - NotificationDto, - TemplateDto, - TemplateResponseDto, - TestEmailResponseDto, -} from 'src/dtos/notification.dto'; -import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; -import { ApiTag } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { EmailTemplate } from 'src/repositories/email.repository'; -import { NotificationAdminService } from 'src/services/notification-admin.service'; - -@ApiTags(ApiTag.NotificationsAdmin) -@Controller('admin/notifications') -export class NotificationAdminController { - constructor(private service: NotificationAdminService) {} - - @Post() - @Authenticated({ admin: true }) - @Endpoint({ - summary: 'Create a notification', - description: 'Create a new notification for a specific user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createNotification(@Auth() auth: AuthDto, @Body() dto: NotificationCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Post('test-email') - @Authenticated({ admin: true }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Send test email', - description: 'Send a test email using the provided SMTP configuration.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - sendTestEmailAdmin(@Auth() auth: AuthDto, @Body() dto: SystemConfigSmtpDto): Promise { - return this.service.sendTestEmail(auth.user.id, dto); - } - - @Post('templates/:name') - @Authenticated({ admin: true }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Render email template', - description: 'Retrieve a preview of the provided email template.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getNotificationTemplateAdmin( - @Auth() auth: AuthDto, - @Param('name') name: EmailTemplate, - @Body() dto: TemplateDto, - ): Promise { - return this.service.getTemplate(name, dto.template); - } -} diff --git a/server/src/controllers/notification.controller.spec.ts b/server/src/controllers/notification.controller.spec.ts deleted file mode 100644 index a64aee2912..0000000000 --- a/server/src/controllers/notification.controller.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { NotificationController } from 'src/controllers/notification.controller'; -import { NotificationService } from 'src/services/notification.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(NotificationController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(NotificationService); - - beforeAll(async () => { - ctx = await controllerSetup(NotificationController, [{ provide: NotificationService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /notifications', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/notifications'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should reject an invalid notification level`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .get(`/notifications`) - .query({ level: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('level must be one of the following values')])); - }); - }); - - describe('PUT /notifications', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put('/notifications'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - describe('ids', () => { - it('should require a list', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/notifications`).send({ ids: true }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['ids must be an array']))); - }); - - it('should require uuids', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/notifications`) - .send({ ids: [true] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); - }); - - it('should accept valid uuids', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()) - .put(`/notifications`) - .send({ ids: [id] }); - expect(service.updateAll).toHaveBeenCalledWith(undefined, expect.objectContaining({ ids: [id] })); - }); - }); - }); - - describe('GET /notifications/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/notifications/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/notifications/123`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('id must be a UUID')])); - }); - }); - - describe('PUT /notifications/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/notifications/${factory.uuid()}`).send({ readAt: factory.date() }); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should accept a null readAt', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/notifications/${id}`).send({ readAt: null }); - expect(service.update).toHaveBeenCalledWith(undefined, id, expect.objectContaining({ readAt: null })); - }); - }); -}); diff --git a/server/src/controllers/notification.controller.ts b/server/src/controllers/notification.controller.ts deleted file mode 100644 index 0a28e1bda8..0000000000 --- a/server/src/controllers/notification.controller.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - NotificationDeleteAllDto, - NotificationDto, - NotificationSearchDto, - NotificationUpdateAllDto, - NotificationUpdateDto, -} from 'src/dtos/notification.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { NotificationService } from 'src/services/notification.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Notifications) -@Controller('notifications') -export class NotificationController { - constructor(private service: NotificationService) {} - - @Get() - @Authenticated({ permission: Permission.NotificationRead }) - @Endpoint({ - summary: 'Retrieve notifications', - description: 'Retrieve a list of notifications.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise { - return this.service.search(auth, dto); - } - - @Put() - @Authenticated({ permission: Permission.NotificationUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Update notifications', - description: 'Update a list of notifications. Allows to bulk-set the read status of notifications.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise { - return this.service.updateAll(auth, dto); - } - - @Delete() - @Authenticated({ permission: Permission.NotificationDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete notifications', - description: 'Delete a list of notifications at once.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise { - return this.service.deleteAll(auth, dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.NotificationRead }) - @Endpoint({ - summary: 'Get a notification', - description: 'Retrieve a specific notification identified by id.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.NotificationUpdate }) - @Endpoint({ - summary: 'Update a notification', - description: 'Update a specific notification to set its read status.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateNotification( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: NotificationUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.NotificationDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a notification', - description: 'Delete a specific notification.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.delete(auth, id); - } -} diff --git a/server/src/controllers/oauth.controller.ts b/server/src/controllers/oauth.controller.ts deleted file mode 100644 index 797bf497ef..0000000000 --- a/server/src/controllers/oauth.controller.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Body, Controller, Get, HttpCode, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Request, Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - AuthDto, - LoginResponseDto, - OAuthAuthorizeResponseDto, - OAuthCallbackDto, - OAuthConfigDto, -} from 'src/dtos/auth.dto'; -import { UserAdminResponseDto } from 'src/dtos/user.dto'; -import { ApiTag, AuthType, ImmichCookie } from 'src/enum'; -import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; -import { AuthService, LoginDetails } from 'src/services/auth.service'; -import { respondWithCookie } from 'src/utils/response'; - -@ApiTags(ApiTag.Authentication) -@Controller('oauth') -export class OAuthController { - constructor(private service: AuthService) {} - - @Get('mobile-redirect') - @Redirect() - @Endpoint({ - summary: 'Redirect OAuth to mobile', - description: - 'Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - redirectOAuthToMobile(@Req() request: Request) { - return { - url: this.service.getMobileRedirect(request.url), - statusCode: HttpStatus.TEMPORARY_REDIRECT, - }; - } - - @Post('authorize') - @Endpoint({ - summary: 'Start OAuth', - description: 'Initiate the OAuth authorization process.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async startOAuth( - @Body() dto: OAuthConfigDto, - @Res({ passthrough: true }) res: Response, - @GetLoginDetails() loginDetails: LoginDetails, - ): Promise { - const { url, state, codeVerifier } = await this.service.authorize(dto); - return respondWithCookie( - res, - { url }, - { - isSecure: loginDetails.isSecure, - values: [ - { key: ImmichCookie.OAuthState, value: state }, - { key: ImmichCookie.OAuthCodeVerifier, value: codeVerifier }, - ], - }, - ); - } - - @Post('callback') - @Endpoint({ - summary: 'Finish OAuth', - description: 'Complete the OAuth authorization process by exchanging the authorization code for a session token.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async finishOAuth( - @Req() request: Request, - @Res({ passthrough: true }) res: Response, - @Body() dto: OAuthCallbackDto, - @GetLoginDetails() loginDetails: LoginDetails, - ): Promise { - const body = await this.service.callback(dto, request.headers, loginDetails); - res.clearCookie(ImmichCookie.OAuthState); - res.clearCookie(ImmichCookie.OAuthCodeVerifier); - return respondWithCookie(res, body, { - isSecure: loginDetails.isSecure, - values: [ - { key: ImmichCookie.AccessToken, value: body.accessToken }, - { key: ImmichCookie.AuthType, value: AuthType.OAuth }, - { key: ImmichCookie.IsAuthenticated, value: 'true' }, - ], - }); - } - - @Post('link') - @Authenticated() - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Link OAuth account', - description: 'Link an OAuth account to the authenticated user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - linkOAuthAccount( - @Req() request: Request, - @Auth() auth: AuthDto, - @Body() dto: OAuthCallbackDto, - ): Promise { - return this.service.link(auth, dto, request.headers); - } - - @Post('unlink') - @Authenticated() - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Unlink OAuth account', - description: 'Unlink the OAuth account from the authenticated user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - unlinkOAuthAccount(@Auth() auth: AuthDto): Promise { - return this.service.unlink(auth); - } -} diff --git a/server/src/controllers/partner.controller.spec.ts b/server/src/controllers/partner.controller.spec.ts deleted file mode 100644 index 2c507a634f..0000000000 --- a/server/src/controllers/partner.controller.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { PartnerController } from 'src/controllers/partner.controller'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { PartnerService } from 'src/services/partner.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(PartnerController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(PartnerService); - - beforeAll(async () => { - ctx = await controllerSetup(PartnerController, [ - { provide: PartnerService, useValue: service }, - { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /partners', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/partners'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should require a direction`, async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/partners`).set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([ - 'direction should not be empty', - expect.stringContaining('direction must be one of the following values:'), - ]), - ); - }); - - it(`should require direction to be an enum`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .get(`/partners`) - .query({ direction: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([expect.stringContaining('direction must be one of the following values:')]), - ); - }); - }); - - describe('POST /partners', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/partners'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should require sharedWithId to be a uuid`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post(`/partners`) - .send({ sharedWithId: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - }); - - describe('PUT /partners/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/partners/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should require id to be a uuid`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/partners/invalid`) - .send({ inTimeline: true }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - }); - - describe('DELETE /partners/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/partners/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should require id to be a uuid`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete(`/partners/invalid`) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - }); -}); diff --git a/server/src/controllers/partner.controller.ts b/server/src/controllers/partner.controller.ts deleted file mode 100644 index 951aee7e0c..0000000000 --- a/server/src/controllers/partner.controller.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { PartnerCreateDto, PartnerResponseDto, PartnerSearchDto, PartnerUpdateDto } from 'src/dtos/partner.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { PartnerService } from 'src/services/partner.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Partners) -@Controller('partners') -export class PartnerController { - constructor(private service: PartnerService) {} - - @Get() - @Authenticated({ permission: Permission.PartnerRead }) - @Endpoint({ - summary: 'Retrieve partners', - description: 'Retrieve a list of partners with whom assets are shared.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise { - return this.service.search(auth, dto); - } - - @Post() - @Authenticated({ permission: Permission.PartnerCreate }) - @Endpoint({ - summary: 'Create a partner', - description: 'Create a new partner to share assets with.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createPartner(@Auth() auth: AuthDto, @Body() dto: PartnerCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Post(':id') - @Endpoint({ - summary: 'Create a partner', - description: 'Create a new partner to share assets with.', - history: new HistoryBuilder().added('v1').deprecated('v1', { replacementId: 'createPartner' }), - }) - @Authenticated({ permission: Permission.PartnerCreate }) - createPartnerDeprecated(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.create(auth, { sharedWithId: id }); - } - - @Put(':id') - @Authenticated({ permission: Permission.PartnerUpdate }) - @Endpoint({ - summary: 'Update a partner', - description: "Specify whether a partner's assets should appear in the user's timeline.", - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updatePartner( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: PartnerUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.PartnerDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Remove a partner', - description: 'Stop sharing assets with a partner.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.remove(auth, id); - } -} diff --git a/server/src/controllers/person.controller.spec.ts b/server/src/controllers/person.controller.spec.ts deleted file mode 100644 index a28ac9b659..0000000000 --- a/server/src/controllers/person.controller.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { PersonController } from 'src/controllers/person.controller'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { PersonService } from 'src/services/person.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(PersonController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(PersonService); - - beforeAll(async () => { - ctx = await controllerSetup(PersonController, [ - { provide: PersonService, useValue: service }, - { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /people', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/people'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should require closestPersonId to be a uuid`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .get(`/people`) - .query({ closestPersonId: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - - it(`should require closestAssetId to be a uuid`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .get(`/people`) - .query({ closestAssetId: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - }); - - describe('POST /people', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/people'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should map an empty birthDate to null', async () => { - await request(ctx.getHttpServer()).post('/people').send({ birthDate: '' }); - expect(service.create).toHaveBeenCalledWith(undefined, { birthDate: null }); - }); - - it('should map an empty color to null', async () => { - await request(ctx.getHttpServer()).post('/people').send({ color: '' }); - expect(service.create).toHaveBeenCalledWith(undefined, { color: null }); - }); - }); - - describe('DELETE /people', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete('/people'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require uuids in the body', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete('/people') - .send({ ids: ['invalid'] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - - it('should respond with 204', async () => { - const { status } = await request(ctx.getHttpServer()) - .delete(`/people`) - .send({ ids: [factory.uuid()] }); - expect(status).toBe(204); - expect(service.deleteAll).toHaveBeenCalled(); - }); - }); - - describe('GET /people/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/people/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /people/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/people/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).put(`/people/123`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('id must be a UUID')])); - }); - - it(`should not allow a null name`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post(`/people`) - .send({ name: null }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['name must be a string'])); - }); - - it(`should require featureFaceAssetId to be a uuid`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/people/${factory.uuid()}`) - .send({ featureFaceAssetId: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['featureFaceAssetId must be a UUID'])); - }); - - it(`should require isFavorite to be a boolean`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/people/${factory.uuid()}`) - .send({ isFavorite: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['isFavorite must be a boolean value'])); - }); - - it(`should require isHidden to be a boolean`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/people/${factory.uuid()}`) - .send({ isHidden: 'invalid' }) - .set('Authorization', `Bearer token`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['isHidden must be a boolean value'])); - }); - - it('should map an empty birthDate to null', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/people/${id}`).send({ birthDate: '' }); - expect(service.update).toHaveBeenCalledWith(undefined, id, { birthDate: null }); - }); - - it('should not accept an invalid birth date (false)', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/people/${factory.uuid()}`) - .send({ birthDate: false }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([ - 'birthDate must be a string in the format yyyy-MM-dd', - 'Birth date cannot be in the future', - ]), - ); - }); - - it('should not accept an invalid birth date (number)', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/people/${factory.uuid()}`) - .send({ birthDate: 123_456 }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([ - 'birthDate must be a string in the format yyyy-MM-dd', - 'Birth date cannot be in the future', - ]), - ); - }); - - it('should not accept a birth date in the future)', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/people/${factory.uuid()}`) - .send({ birthDate: '9999-01-01' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['Birth date cannot be in the future'])); - }); - }); - - describe('DELETE /people/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete(`/people/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).delete(`/people/invalid`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('must be a UUID')])); - }); - - it('should respond with 204', async () => { - const { status } = await request(ctx.getHttpServer()).delete(`/people/${factory.uuid()}`); - expect(status).toBe(204); - expect(service.delete).toHaveBeenCalled(); - }); - }); - - describe('POST /people/:id/merge', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post(`/people/${factory.uuid()}/merge`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /people/:id/statistics', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/people/${factory.uuid()}/statistics`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/person.controller.ts b/server/src/controllers/person.controller.ts deleted file mode 100644 index 5abd6eb1b4..0000000000 --- a/server/src/controllers/person.controller.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Next, - Param, - Post, - Put, - Query, - Res, -} from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { NextFunction, Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - AssetFaceUpdateDto, - MergePersonDto, - PeopleResponseDto, - PeopleUpdateDto, - PersonCreateDto, - PersonResponseDto, - PersonSearchDto, - PersonStatisticsResponseDto, - PersonUpdateDto, -} from 'src/dtos/person.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { PersonService } from 'src/services/person.service'; -import { sendFile } from 'src/utils/file'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.People) -@Controller('people') -export class PersonController { - constructor( - private service: PersonService, - private logger: LoggingRepository, - ) { - this.logger.setContext(PersonController.name); - } - - @Get() - @Authenticated({ permission: Permission.PersonRead }) - @Endpoint({ - summary: 'Get all people', - description: 'Retrieve a list of all people.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise { - return this.service.getAll(auth, options); - } - - @Post() - @Authenticated({ permission: Permission.PersonCreate }) - @Endpoint({ - summary: 'Create a person', - description: 'Create a new person that can have multiple faces assigned to them.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Put() - @Authenticated({ permission: Permission.PersonUpdate }) - @Endpoint({ - summary: 'Update people', - description: 'Bulk update multiple people at once.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise { - return this.service.updateAll(auth, dto); - } - - @Delete() - @Authenticated({ permission: Permission.PersonDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete people', - description: 'Bulk delete a list of people at once.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deletePeople(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { - return this.service.deleteAll(auth, dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.PersonRead }) - @Endpoint({ - summary: 'Get a person', - description: 'Retrieve a person by id.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getById(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.PersonUpdate }) - @Endpoint({ - summary: 'Update person', - description: 'Update an individual person.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updatePerson( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: PersonUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.PersonDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete person', - description: 'Delete an individual person.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.delete(auth, id); - } - - @Get(':id/statistics') - @Authenticated({ permission: Permission.PersonStatistics }) - @Endpoint({ - summary: 'Get person statistics', - description: 'Retrieve statistics about a specific person.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getStatistics(auth, id); - } - - @Get(':id/thumbnail') - @FileResponse() - @Authenticated({ permission: Permission.PersonRead }) - @Endpoint({ - summary: 'Get person thumbnail', - description: 'Retrieve the thumbnail file for a person.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async getPersonThumbnail( - @Res() res: Response, - @Next() next: NextFunction, - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - ) { - await sendFile(res, next, () => this.service.getThumbnail(auth, id), this.logger); - } - - @Put(':id/reassign') - @Authenticated({ permission: Permission.PersonReassign }) - @Endpoint({ - summary: 'Reassign faces', - description: 'Bulk reassign a list of faces to a different person.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - reassignFaces( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: AssetFaceUpdateDto, - ): Promise { - return this.service.reassignFaces(auth, id, dto); - } - - @Post(':id/merge') - @Authenticated({ permission: Permission.PersonMerge }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Merge people', - description: 'Merge a list of people into the person specified in the path parameter.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - mergePerson( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: MergePersonDto, - ): Promise { - return this.service.mergePerson(auth, id, dto); - } -} diff --git a/server/src/controllers/plugin.controller.ts b/server/src/controllers/plugin.controller.ts deleted file mode 100644 index 52c833e93d..0000000000 --- a/server/src/controllers/plugin.controller.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Controller, Get, Param } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto'; -import { Permission } from 'src/enum'; -import { Authenticated } from 'src/middleware/auth.guard'; -import { PluginService } from 'src/services/plugin.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags('Plugins') -@Controller('plugins') -export class PluginController { - constructor(private service: PluginService) {} - - @Get('triggers') - @Authenticated({ permission: Permission.PluginRead }) - @Endpoint({ - summary: 'List all plugin triggers', - description: 'Retrieve a list of all available plugin triggers.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getPluginTriggers(): PluginTriggerResponseDto[] { - return this.service.getTriggers(); - } - - @Get() - @Authenticated({ permission: Permission.PluginRead }) - @Endpoint({ - summary: 'List all plugins', - description: 'Retrieve a list of plugins available to the authenticated user.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getPlugins(): Promise { - return this.service.getAll(); - } - - @Get(':id') - @Authenticated({ permission: Permission.PluginRead }) - @Endpoint({ - summary: 'Retrieve a plugin', - description: 'Retrieve information about a specific plugin by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getPlugin(@Param() { id }: UUIDParamDto): Promise { - return this.service.get(id); - } -} diff --git a/server/src/controllers/queue.controller.ts b/server/src/controllers/queue.controller.ts deleted file mode 100644 index 1d8d918c5f..0000000000 --- a/server/src/controllers/queue.controller.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - QueueDeleteDto, - QueueJobResponseDto, - QueueJobSearchDto, - QueueNameParamDto, - QueueResponseDto, - QueueUpdateDto, -} from 'src/dtos/queue.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { QueueService } from 'src/services/queue.service'; - -@ApiTags(ApiTag.Queues) -@Controller('queues') -export class QueueController { - constructor(private service: QueueService) {} - - @Get() - @Authenticated({ permission: Permission.QueueRead, admin: true }) - @Endpoint({ - summary: 'List all queues', - description: 'Retrieves a list of queues.', - history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), - }) - getQueues(@Auth() auth: AuthDto): Promise { - return this.service.getAll(auth); - } - - @Get(':name') - @Authenticated({ permission: Permission.QueueRead, admin: true }) - @Endpoint({ - summary: 'Retrieve a queue', - description: 'Retrieves a specific queue by its name.', - history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), - }) - getQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto): Promise { - return this.service.get(auth, name); - } - - @Put(':name') - @Authenticated({ permission: Permission.QueueUpdate, admin: true }) - @Endpoint({ - summary: 'Update a queue', - description: 'Change the paused status of a specific queue.', - history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), - }) - updateQueue( - @Auth() auth: AuthDto, - @Param() { name }: QueueNameParamDto, - @Body() dto: QueueUpdateDto, - ): Promise { - return this.service.update(auth, name, dto); - } - - @Get(':name/jobs') - @Authenticated({ permission: Permission.QueueJobRead, admin: true }) - @Endpoint({ - summary: 'Retrieve queue jobs', - description: 'Retrieves a list of queue jobs from the specified queue.', - history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), - }) - getQueueJobs( - @Auth() auth: AuthDto, - @Param() { name }: QueueNameParamDto, - @Query() dto: QueueJobSearchDto, - ): Promise { - return this.service.searchJobs(auth, name, dto); - } - - @Delete(':name/jobs') - @Authenticated({ permission: Permission.QueueJobDelete, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Empty a queue', - description: 'Removes all jobs from the specified queue.', - history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), - }) - emptyQueue(@Auth() auth: AuthDto, @Param() { name }: QueueNameParamDto, @Body() dto: QueueDeleteDto): Promise { - return this.service.emptyQueue(auth, name, dto); - } -} diff --git a/server/src/controllers/search.controller.spec.ts b/server/src/controllers/search.controller.spec.ts deleted file mode 100644 index adbc8be0f3..0000000000 --- a/server/src/controllers/search.controller.spec.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { SearchController } from 'src/controllers/search.controller'; -import { SearchService } from 'src/services/search.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(SearchController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(SearchService); - - beforeAll(async () => { - ctx = await controllerSetup(SearchController, [{ provide: SearchService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /search/metadata', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/search/metadata'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should reject page as a string', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ page: 'abc' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['page must not be less than 1', 'page must be an integer number'])); - }); - - it('should reject page as a negative number', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ page: -10 }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['page must not be less than 1'])); - }); - - it('should reject page as 0', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ page: 0 }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['page must not be less than 1'])); - }); - - it('should reject size as a string', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ size: 'abc' }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([ - 'size must not be greater than 1000', - 'size must not be less than 1', - 'size must be an integer number', - ]), - ); - }); - - it('should reject an invalid size', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ size: -1.5 }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['size must not be less than 1', 'size must be an integer number'])); - }); - - it('should reject an visibility as not an enum', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/search/metadata') - .send({ visibility: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest(['visibility must be one of the following values: archive, timeline, hidden, locked']), - ); - }); - - it('should reject an isFavorite as not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/search/metadata') - .send({ isFavorite: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['isFavorite must be a boolean value'])); - }); - - it('should reject an isEncoded as not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/search/metadata') - .send({ isEncoded: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['isEncoded must be a boolean value'])); - }); - - it('should reject an isOffline as not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/search/metadata') - .send({ isOffline: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['isOffline must be a boolean value'])); - }); - - it('should reject an isMotion as not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()).post('/search/metadata').send({ isMotion: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['isMotion must be a boolean value'])); - }); - - describe('POST /search/random', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/search/random'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should reject if withStacked is not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/search/random') - .send({ withStacked: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['withStacked must be a boolean value'])); - }); - - it('should reject if withPeople is not a boolean', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/search/random') - .send({ withPeople: 'immich' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['withPeople must be a boolean value'])); - }); - }); - - describe('POST /search/smart', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/search/smart'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /search/explore', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/search/explore'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /search/person', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/search/person'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a name', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/search/person').send({}); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string'])); - }); - }); - - describe('GET /search/places', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/search/places'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a name', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/search/places').send({}); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['name should not be empty', 'name must be a string'])); - }); - }); - - describe('GET /search/cities', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/search/cities'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /search/suggestions', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/search/suggestions'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a type', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/search/suggestions').send({}); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([ - 'type should not be empty', - expect.stringContaining('type must be one of the following values:'), - ]), - ); - }); - }); - }); -}); diff --git a/server/src/controllers/search.controller.ts b/server/src/controllers/search.controller.ts deleted file mode 100644 index 439a7a5118..0000000000 --- a/server/src/controllers/search.controller.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Body, Controller, Get, HttpCode, HttpStatus, Post, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { PersonResponseDto } from 'src/dtos/person.dto'; -import { - LargeAssetSearchDto, - MetadataSearchDto, - PlacesResponseDto, - RandomSearchDto, - SearchExploreResponseDto, - SearchPeopleDto, - SearchPlacesDto, - SearchResponseDto, - SearchStatisticsResponseDto, - SearchSuggestionRequestDto, - SmartSearchDto, - StatisticsSearchDto, -} from 'src/dtos/search.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { SearchService } from 'src/services/search.service'; - -@ApiTags(ApiTag.Search) -@Controller('search') -export class SearchController { - constructor(private service: SearchService) {} - - @Post('metadata') - @Authenticated({ permission: Permission.AssetRead }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Search assets by metadata', - description: 'Search for assets based on various metadata criteria.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchAssets(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise { - return this.service.searchMetadata(auth, dto); - } - - @Post('statistics') - @Authenticated({ permission: Permission.AssetStatistics }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Search asset statistics', - description: 'Retrieve statistical data about assets based on search criteria, such as the total matching count.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchAssetStatistics(@Auth() auth: AuthDto, @Body() dto: StatisticsSearchDto): Promise { - return this.service.searchStatistics(auth, dto); - } - - @Post('random') - @Authenticated({ permission: Permission.AssetRead }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Search random assets', - description: 'Retrieve a random selection of assets based on the provided criteria.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchRandom(@Auth() auth: AuthDto, @Body() dto: RandomSearchDto): Promise { - return this.service.searchRandom(auth, dto); - } - - @Post('large-assets') - @Authenticated({ permission: Permission.AssetRead }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Search large assets', - description: 'Search for assets that are considered large based on specified criteria.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchLargeAssets(@Auth() auth: AuthDto, @Query() dto: LargeAssetSearchDto): Promise { - return this.service.searchLargeAssets(auth, dto); - } - - @Post('smart') - @Authenticated({ permission: Permission.AssetRead }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Smart asset search', - description: 'Perform a smart search for assets by using machine learning vectors to determine relevance.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise { - return this.service.searchSmart(auth, dto); - } - - @Get('explore') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Retrieve explore data', - description: 'Retrieve data for the explore section, such as popular people and places.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getExploreData(@Auth() auth: AuthDto): Promise { - return this.service.getExploreData(auth); - } - - @Get('person') - @Authenticated({ permission: Permission.PersonRead }) - @Endpoint({ - summary: 'Search people', - description: 'Search for people by name.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise { - return this.service.searchPerson(auth, dto); - } - - @Get('places') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Search places', - description: 'Search for places by name.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchPlaces(@Query() dto: SearchPlacesDto): Promise { - return this.service.searchPlaces(dto); - } - - @Get('cities') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Retrieve assets by city', - description: - 'Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetsByCity(@Auth() auth: AuthDto): Promise { - return this.service.getAssetsByCity(auth); - } - - @Get('suggestions') - @Authenticated({ permission: Permission.AssetRead }) - @Endpoint({ - summary: 'Retrieve search suggestions', - description: - 'Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise { - // TODO fix open api generation to indicate that results can be nullable - return this.service.getSearchSuggestions(auth, dto) as Promise; - } -} diff --git a/server/src/controllers/server.controller.spec.ts b/server/src/controllers/server.controller.spec.ts deleted file mode 100644 index 6b00490d28..0000000000 --- a/server/src/controllers/server.controller.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ServerController } from 'src/controllers/server.controller'; -import { ServerService } from 'src/services/server.service'; -import { SystemMetadataService } from 'src/services/system-metadata.service'; -import { VersionService } from 'src/services/version.service'; -import request from 'supertest'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(ServerController.name, () => { - let ctx: ControllerContext; - const serverService = mockBaseService(ServerService); - const systemMetadataService = mockBaseService(SystemMetadataService); - const versionService = mockBaseService(VersionService); - - beforeAll(async () => { - ctx = await controllerSetup(ServerController, [ - { provide: ServerService, useValue: serverService }, - { provide: SystemMetadataService, useValue: systemMetadataService }, - { provide: VersionService, useValue: versionService }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - serverService.resetAllMocks(); - versionService.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /server/license', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/server/license'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/server.controller.ts b/server/src/controllers/server.controller.ts index ffcb50c674..42ab11e34e 100644 --- a/server/src/controllers/server.controller.ts +++ b/server/src/controllers/server.controller.ts @@ -1,35 +1,21 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Put } from '@nestjs/common'; -import { ApiNotFoundResponse, ApiTags } from '@nestjs/swagger'; +import { Controller, Get } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { ServerAboutResponseDto, - ServerApkLinksDto, ServerConfigDto, ServerFeaturesDto, - ServerMediaTypesResponseDto, ServerPingResponse, - ServerStatsResponseDto, - ServerStorageResponseDto, - ServerThemeDto, - ServerVersionHistoryResponseDto, ServerVersionResponseDto, } from 'src/dtos/server.dto'; -import { VersionCheckStateResponseDto } from 'src/dtos/system-metadata.dto'; import { ApiTag, Permission } from 'src/enum'; import { Authenticated } from 'src/middleware/auth.guard'; import { ServerService } from 'src/services/server.service'; -import { SystemMetadataService } from 'src/services/system-metadata.service'; -import { VersionService } from 'src/services/version.service'; @ApiTags(ApiTag.Server) @Controller('server') export class ServerController { - constructor( - private service: ServerService, - private systemMetadataService: SystemMetadataService, - private versionService: VersionService, - ) {} + constructor(private service: ServerService) {} @Get('about') @Authenticated({ permission: Permission.ServerAbout }) @@ -42,28 +28,6 @@ export class ServerController { return this.service.getAboutInfo(); } - @Get('apk-links') - @Authenticated({ permission: Permission.ServerApkLinks }) - @Endpoint({ - summary: 'Get APK links', - description: 'Retrieve links to the APKs for the current server version.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getApkLinks(): ServerApkLinksDto { - return this.service.getApkLinks(); - } - - @Get('storage') - @Authenticated({ permission: Permission.ServerStorage }) - @Endpoint({ - summary: 'Get storage', - description: 'Retrieve the current storage utilization information of the server.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getStorage(): Promise { - return this.service.getStorage(); - } - @Get('ping') @Endpoint({ summary: 'Ping', @@ -81,17 +45,7 @@ export class ServerController { history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) getServerVersion(): ServerVersionResponseDto { - return this.versionService.getVersion(); - } - - @Get('version-history') - @Endpoint({ - summary: 'Get version history', - description: 'Retrieve a list of past versions the server has been on.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getVersionHistory(): Promise { - return this.versionService.getVersionHistory(); + return this.service.getVersion(); } @Get('features') @@ -104,16 +58,6 @@ export class ServerController { return this.service.getFeatures(); } - @Get('theme') - @Endpoint({ - summary: 'Get theme', - description: 'Retrieve the custom CSS, if existent.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getTheme(): Promise { - return this.service.getTheme(); - } - @Get('config') @Endpoint({ summary: 'Get config', @@ -123,71 +67,4 @@ export class ServerController { getServerConfig(): Promise { return this.service.getSystemConfig(); } - - @Get('statistics') - @Authenticated({ permission: Permission.ServerStatistics, admin: true }) - @Endpoint({ - summary: 'Get statistics', - description: 'Retrieve statistics about the entire Immich instance such as asset counts.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getServerStatistics(): Promise { - return this.service.getStatistics(); - } - - @Get('media-types') - @Endpoint({ - summary: 'Get supported media types', - description: 'Retrieve all media types supported by the server.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getSupportedMediaTypes(): ServerMediaTypesResponseDto { - return this.service.getSupportedMediaTypes(); - } - - @Get('license') - @Authenticated({ permission: Permission.ServerLicenseRead, admin: true }) - @ApiNotFoundResponse() - @Endpoint({ - summary: 'Get product key', - description: 'Retrieve information about whether the server currently has a product key registered.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getServerLicense(): Promise { - return this.service.getLicense(); - } - - @Put('license') - @Authenticated({ permission: Permission.ServerLicenseUpdate, admin: true }) - @Endpoint({ - summary: 'Set server product key', - description: 'Validate and set the server product key if successful.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - setServerLicense(@Body() license: LicenseKeyDto): Promise { - return this.service.setLicense(license); - } - - @Delete('license') - @Authenticated({ permission: Permission.ServerLicenseDelete, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete server product key', - description: 'Delete the currently set server product key.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteServerLicense(): Promise { - return this.service.deleteLicense(); - } - - @Get('version-check') - @Authenticated({ permission: Permission.ServerVersionCheck }) - @Endpoint({ - summary: 'Get version check status', - description: 'Retrieve information about the last time the version check ran.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getVersionCheck(): Promise { - return this.systemMetadataService.getVersionCheckState(); - } } diff --git a/server/src/controllers/session.controller.ts b/server/src/controllers/session.controller.ts index d21cca3a83..8d8dec6b3f 100644 --- a/server/src/controllers/session.controller.ts +++ b/server/src/controllers/session.controller.ts @@ -1,8 +1,8 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { SessionCreateDto, SessionCreateResponseDto, SessionResponseDto, SessionUpdateDto } from 'src/dtos/session.dto'; +import { SessionCreateDto, SessionCreateResponseDto, SessionResponseDto } from 'src/dtos/session.dto'; import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { SessionService } from 'src/services/session.service'; @@ -47,21 +47,6 @@ export class SessionController { return this.service.deleteAll(auth); } - @Put(':id') - @Authenticated({ permission: Permission.SessionUpdate }) - @Endpoint({ - summary: 'Update a session', - description: 'Update a specific session identified by id.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateSession( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: SessionUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - @Delete(':id') @Authenticated({ permission: Permission.SessionDelete }) @HttpCode(HttpStatus.NO_CONTENT) @@ -73,16 +58,4 @@ export class SessionController { deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { return this.service.delete(auth, id); } - - @Post(':id/lock') - @Authenticated({ permission: Permission.SessionLock }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Lock a session', - description: 'Lock a specific session by id.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - lockSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.lock(auth, id); - } } diff --git a/server/src/controllers/shared-link.controller.spec.ts b/server/src/controllers/shared-link.controller.spec.ts deleted file mode 100644 index 96c84040ca..0000000000 --- a/server/src/controllers/shared-link.controller.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SharedLinkController } from 'src/controllers/shared-link.controller'; -import { SharedLinkType } from 'src/enum'; -import { SharedLinkService } from 'src/services/shared-link.service'; -import request from 'supertest'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(SharedLinkController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(SharedLinkService); - - beforeAll(async () => { - ctx = await controllerSetup(SharedLinkController, [{ provide: SharedLinkService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('POST /shared-links', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/shared-links'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should allow an null expiresAt', async () => { - await request(ctx.getHttpServer()) - .post('/shared-links') - .send({ expiresAt: null, type: SharedLinkType.Individual }); - expect(service.create).toHaveBeenCalledWith(undefined, expect.objectContaining({ expiresAt: null })); - }); - }); -}); diff --git a/server/src/controllers/shared-link.controller.ts b/server/src/controllers/shared-link.controller.ts deleted file mode 100644 index 8875127a25..0000000000 --- a/server/src/controllers/shared-link.controller.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Param, - Patch, - Post, - Put, - Query, - Req, - Res, -} from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Request, Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto'; -import { AssetIdsDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - SharedLinkCreateDto, - SharedLinkEditDto, - SharedLinkPasswordDto, - SharedLinkResponseDto, - SharedLinkSearchDto, -} from 'src/dtos/shared-link.dto'; -import { ApiTag, ImmichCookie, Permission } from 'src/enum'; -import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; -import { LoginDetails } from 'src/services/auth.service'; -import { SharedLinkService } from 'src/services/shared-link.service'; -import { respondWithCookie } from 'src/utils/response'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.SharedLinks) -@Controller('shared-links') -export class SharedLinkController { - constructor(private service: SharedLinkService) {} - - @Get() - @Authenticated({ permission: Permission.SharedLinkRead }) - @Endpoint({ - summary: 'Retrieve all shared links', - description: 'Retrieve a list of all shared links.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise { - return this.service.getAll(auth, dto); - } - - @Get('me') - @Authenticated({ sharedLink: true }) - @Endpoint({ - summary: 'Retrieve current shared link', - description: 'Retrieve the current shared link associated with authentication method.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async getMySharedLink( - @Auth() auth: AuthDto, - @Query() dto: SharedLinkPasswordDto, - @Req() request: Request, - @Res({ passthrough: true }) res: Response, - @GetLoginDetails() loginDetails: LoginDetails, - ): Promise { - const sharedLinkToken = request.cookies?.[ImmichCookie.SharedLinkToken]; - if (sharedLinkToken) { - dto.token = sharedLinkToken; - } - const body = await this.service.getMine(auth, dto); - return respondWithCookie(res, body, { - isSecure: loginDetails.isSecure, - values: body.token ? [{ key: ImmichCookie.SharedLinkToken, value: body.token }] : [], - }); - } - - @Get(':id') - @Authenticated({ permission: Permission.SharedLinkRead }) - @Endpoint({ - summary: 'Retrieve a shared link', - description: 'Retrieve a specific shared link by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Post() - @Authenticated({ permission: Permission.SharedLinkCreate }) - @Endpoint({ - summary: 'Create a shared link', - description: 'Create a new shared link.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) { - return this.service.create(auth, dto); - } - - @Patch(':id') - @Authenticated({ permission: Permission.SharedLinkUpdate }) - @Endpoint({ - summary: 'Update a shared link', - description: 'Update an existing shared link by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateSharedLink( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: SharedLinkEditDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.SharedLinkDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a shared link', - description: 'Delete a specific shared link by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.remove(auth, id); - } - - @Put(':id/assets') - @Authenticated({ sharedLink: true }) - @Endpoint({ - summary: 'Add assets to a shared link', - description: - 'Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - addSharedLinkAssets( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: AssetIdsDto, - ): Promise { - return this.service.addAssets(auth, id, dto); - } - - @Delete(':id/assets') - @Authenticated({ sharedLink: true }) - @Endpoint({ - summary: 'Remove assets from a shared link', - description: - 'Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removeSharedLinkAssets( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: AssetIdsDto, - ): Promise { - return this.service.removeAssets(auth, id, dto); - } -} diff --git a/server/src/controllers/stack.controller.ts b/server/src/controllers/stack.controller.ts deleted file mode 100644 index b35b49c786..0000000000 --- a/server/src/controllers/stack.controller.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto } from 'src/dtos/stack.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { StackService } from 'src/services/stack.service'; -import { UUIDAssetIDParamDto, UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Stacks) -@Controller('stacks') -export class StackController { - constructor(private service: StackService) {} - - @Get() - @Authenticated({ permission: Permission.StackRead }) - @Endpoint({ - summary: 'Retrieve stacks', - description: 'Retrieve a list of stacks.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchStacks(@Auth() auth: AuthDto, @Query() query: StackSearchDto): Promise { - return this.service.search(auth, query); - } - - @Post() - @Authenticated({ permission: Permission.StackCreate }) - @Endpoint({ - summary: 'Create a stack', - description: - 'Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createStack(@Auth() auth: AuthDto, @Body() dto: StackCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Delete() - @Authenticated({ permission: Permission.StackDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete stacks', - description: 'Delete multiple stacks by providing a list of stack IDs.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteStacks(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { - return this.service.deleteAll(auth, dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.StackRead }) - @Endpoint({ - summary: 'Retrieve a stack', - description: 'Retrieve a specific stack by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.StackUpdate }) - @Endpoint({ - summary: 'Update a stack', - description: 'Update an existing stack by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateStack( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: StackUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.StackDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a stack', - description: 'Delete a specific stack by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.delete(auth, id); - } - - @Delete(':id/assets/:assetId') - @Authenticated({ permission: Permission.StackUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Remove an asset from a stack', - description: 'Remove a specific asset from a stack by providing the stack ID and asset ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - removeAssetFromStack(@Auth() auth: AuthDto, @Param() dto: UUIDAssetIDParamDto): Promise { - return this.service.removeAsset(auth, dto); - } -} diff --git a/server/src/controllers/sync.controller.spec.ts b/server/src/controllers/sync.controller.spec.ts deleted file mode 100644 index c1f19ddd66..0000000000 --- a/server/src/controllers/sync.controller.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { SyncController } from 'src/controllers/sync.controller'; -import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; -import { SyncService } from 'src/services/sync.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(SyncController.name, () => { - let ctx: ControllerContext; - const syncService = mockBaseService(SyncService); - const errorService = { handleError: vi.fn() }; - - beforeAll(async () => { - ctx = await controllerSetup(SyncController, [ - { provide: SyncService, useValue: syncService }, - { provide: GlobalExceptionFilter, useValue: errorService }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - syncService.resetAllMocks(); - errorService.handleError.mockReset(); - ctx.reset(); - }); - - describe('POST /sync/stream', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/sync/stream'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require sync request type enums', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .post('/sync/stream') - .send({ types: ['invalid'] }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([expect.stringContaining('each value in types must be one of the following values')]), - ); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /sync/ack', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/sync/ack'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /sync/ack', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/sync/ack'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should not allow more than 1,000 entries', async () => { - const acks = Array.from({ length: 1001 }, (_, i) => `ack-${i}`); - const { status, body } = await request(ctx.getHttpServer()).post('/sync/ack').send({ acks }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['acks must contain no more than 1000 elements'])); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('DELETE /sync/ack', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete('/sync/ack'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require sync response type enums', async () => { - const { status, body } = await request(ctx.getHttpServer()) - .delete('/sync/ack') - .send({ types: ['invalid'] }); - expect(status).toBe(400); - expect(body).toEqual( - errorDto.badRequest([expect.stringContaining('each value in types must be one of the following values')]), - ); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts deleted file mode 100644 index de94738f73..0000000000 --- a/server/src/controllers/sync.controller.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Body, Controller, Delete, Get, Header, HttpCode, HttpStatus, Post, Res } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Response } from 'express'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - AssetDeltaSyncDto, - AssetDeltaSyncResponseDto, - AssetFullSyncDto, - SyncAckDeleteDto, - SyncAckDto, - SyncAckSetDto, - SyncStreamDto, -} from 'src/dtos/sync.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter'; -import { SyncService } from 'src/services/sync.service'; - -@ApiTags(ApiTag.Sync) -@Controller('sync') -export class SyncController { - constructor( - private service: SyncService, - private errorService: GlobalExceptionFilter, - ) {} - - @Post('full-sync') - @Authenticated() - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Get full sync for user', - description: 'Retrieve all assets for a full synchronization for the authenticated user.', - history: new HistoryBuilder().added('v1').deprecated('v2'), - }) - getFullSyncForUser(@Auth() auth: AuthDto, @Body() dto: AssetFullSyncDto): Promise { - return this.service.getFullSync(auth, dto); - } - - @Post('delta-sync') - @Authenticated() - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Get delta sync for user', - description: 'Retrieve changed assets since the last sync for the authenticated user.', - history: new HistoryBuilder().added('v1').deprecated('v2'), - }) - getDeltaSync(@Auth() auth: AuthDto, @Body() dto: AssetDeltaSyncDto): Promise { - return this.service.getDeltaSync(auth, dto); - } - - @Post('stream') - @Authenticated({ permission: Permission.SyncStream }) - @Header('Content-Type', 'application/jsonlines+json') - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Stream sync changes', - description: - 'Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async getSyncStream(@Auth() auth: AuthDto, @Res() res: Response, @Body() dto: SyncStreamDto) { - try { - await this.service.stream(auth, res, dto); - } catch (error: Error | any) { - res.setHeader('Content-Type', 'application/json'); - this.errorService.handleError(res, error); - } - } - - @Get('ack') - @Authenticated({ permission: Permission.SyncCheckpointRead }) - @Endpoint({ - summary: 'Retrieve acknowledgements', - description: 'Retrieve the synchronization acknowledgments for the current session.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getSyncAck(@Auth() auth: AuthDto): Promise { - return this.service.getAcks(auth); - } - - @Post('ack') - @Authenticated({ permission: Permission.SyncCheckpointUpdate }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Acknowledge changes', - description: - 'Send a list of synchronization acknowledgements to confirm that the latest changes have been received.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - sendSyncAck(@Auth() auth: AuthDto, @Body() dto: SyncAckSetDto) { - return this.service.setAcks(auth, dto); - } - - @Delete('ack') - @Authenticated({ permission: Permission.SyncCheckpointDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete acknowledgements', - description: 'Delete specific synchronization acknowledgments.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteSyncAck(@Auth() auth: AuthDto, @Body() dto: SyncAckDeleteDto): Promise { - return this.service.deleteAcks(auth, dto); - } -} diff --git a/server/src/controllers/system-config.controller.spec.ts b/server/src/controllers/system-config.controller.spec.ts deleted file mode 100644 index bbd1241dc5..0000000000 --- a/server/src/controllers/system-config.controller.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import _ from 'lodash'; -import { defaults } from 'src/config'; -import { SystemConfigController } from 'src/controllers/system-config.controller'; -import { StorageTemplateService } from 'src/services/storage-template.service'; -import { SystemConfigService } from 'src/services/system-config.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(SystemConfigController.name, () => { - let ctx: ControllerContext; - const systemConfigService = mockBaseService(SystemConfigService); - const templateService = mockBaseService(StorageTemplateService); - - beforeAll(async () => { - ctx = await controllerSetup(SystemConfigController, [ - { provide: SystemConfigService, useValue: systemConfigService }, - { provide: StorageTemplateService, useValue: templateService }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - systemConfigService.resetAllMocks(); - templateService.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /system-config', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/system-config'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /system-config/defaults', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/system-config/defaults'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /system-config', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put('/system-config'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - describe('nightlyTasks', () => { - it('should validate nightly jobs start time', async () => { - const config = _.cloneDeep(defaults); - config.nightlyTasks.startTime = 'invalid'; - const { status, body } = await request(ctx.getHttpServer()).put('/system-config').send(config); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['nightlyTasks.startTime must be in HH:mm format'])); - }); - - it('should accept a valid time', async () => { - const config = _.cloneDeep(defaults); - config.nightlyTasks.startTime = '05:05'; - const { status } = await request(ctx.getHttpServer()).put('/system-config').send(config); - expect(status).toBe(200); - }); - - it('should validate a boolean field', async () => { - const config = _.cloneDeep(defaults); - (config.nightlyTasks.databaseCleanup as any) = 'invalid'; - const { status, body } = await request(ctx.getHttpServer()).put('/system-config').send(config); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['nightlyTasks.databaseCleanup must be a boolean value'])); - }); - }); - - describe('image', () => { - it('should accept config without optional progressive property', async () => { - const config = _.cloneDeep(defaults); - delete config.image.thumbnail.progressive; - delete config.image.preview.progressive; - delete config.image.fullsize.progressive; - const { status } = await request(ctx.getHttpServer()).put('/system-config').send(config); - expect(status).toBe(200); - }); - - it('should accept config with progressive set to true', async () => { - const config = _.cloneDeep(defaults); - config.image.thumbnail.progressive = true; - config.image.preview.progressive = true; - config.image.fullsize.progressive = true; - const { status } = await request(ctx.getHttpServer()).put('/system-config').send(config); - expect(status).toBe(200); - }); - - it('should reject invalid progressive value', async () => { - const config = _.cloneDeep(defaults); - (config.image.thumbnail.progressive as any) = 'invalid'; - const { status, body } = await request(ctx.getHttpServer()).put('/system-config').send(config); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(['image.thumbnail.progressive must be a boolean value'])); - }); - }); - }); -}); diff --git a/server/src/controllers/system-config.controller.ts b/server/src/controllers/system-config.controller.ts deleted file mode 100644 index 6b79b38d98..0000000000 --- a/server/src/controllers/system-config.controller.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Body, Controller, Get, Put } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Authenticated } from 'src/middleware/auth.guard'; -import { StorageTemplateService } from 'src/services/storage-template.service'; -import { SystemConfigService } from 'src/services/system-config.service'; - -@ApiTags(ApiTag.SystemConfig) -@Controller('system-config') -export class SystemConfigController { - constructor( - private service: SystemConfigService, - private storageTemplateService: StorageTemplateService, - ) {} - - @Get() - @Authenticated({ permission: Permission.SystemConfigRead, admin: true }) - @Endpoint({ - summary: 'Get system configuration', - description: 'Retrieve the current system configuration.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getConfig(): Promise { - return this.service.getSystemConfig(); - } - - @Get('defaults') - @Authenticated({ permission: Permission.SystemConfigRead, admin: true }) - @Endpoint({ - summary: 'Get system configuration defaults', - description: 'Retrieve the default values for the system configuration.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getConfigDefaults(): SystemConfigDto { - return this.service.getDefaults(); - } - - @Put() - @Authenticated({ permission: Permission.SystemConfigUpdate, admin: true }) - @Endpoint({ - summary: 'Update system configuration', - description: 'Update the system configuration with a new system configuration.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateConfig(@Body() dto: SystemConfigDto): Promise { - return this.service.updateSystemConfig(dto); - } - - @Get('storage-template-options') - @Authenticated({ permission: Permission.SystemConfigRead, admin: true }) - @Endpoint({ - summary: 'Get storage template options', - description: 'Retrieve exemplary storage template options.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { - return this.storageTemplateService.getStorageTemplateOptions(); - } -} diff --git a/server/src/controllers/system-metadata.controller.ts b/server/src/controllers/system-metadata.controller.ts deleted file mode 100644 index 8f73def3f7..0000000000 --- a/server/src/controllers/system-metadata.controller.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Body, Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { - AdminOnboardingUpdateDto, - ReverseGeocodingStateResponseDto, - VersionCheckStateResponseDto, -} from 'src/dtos/system-metadata.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Authenticated } from 'src/middleware/auth.guard'; -import { SystemMetadataService } from 'src/services/system-metadata.service'; - -@ApiTags(ApiTag.SystemMetadata) -@Controller('system-metadata') -export class SystemMetadataController { - constructor(private service: SystemMetadataService) {} - - @Get('admin-onboarding') - @Authenticated({ permission: Permission.SystemMetadataRead, admin: true }) - @Endpoint({ - summary: 'Retrieve admin onboarding', - description: 'Retrieve the current admin onboarding status.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAdminOnboarding(): Promise { - return this.service.getAdminOnboarding(); - } - - @Post('admin-onboarding') - @Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Update admin onboarding', - description: 'Update the admin onboarding status.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise { - return this.service.updateAdminOnboarding(dto); - } - - @Get('reverse-geocoding-state') - @Authenticated({ permission: Permission.SystemMetadataRead, admin: true }) - @Endpoint({ - summary: 'Retrieve reverse geocoding state', - description: 'Retrieve the current state of the reverse geocoding import.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getReverseGeocodingState(): Promise { - return this.service.getReverseGeocodingState(); - } - - @Get('version-check-state') - @Authenticated({ permission: Permission.SystemMetadataRead, admin: true }) - @Endpoint({ - summary: 'Retrieve version check state', - description: 'Retrieve the current state of the version check process.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getVersionCheckState(): Promise { - return this.service.getVersionCheckState(); - } -} diff --git a/server/src/controllers/tag.controller.spec.ts b/server/src/controllers/tag.controller.spec.ts deleted file mode 100644 index 60fc3d65ae..0000000000 --- a/server/src/controllers/tag.controller.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { TagController } from 'src/controllers/tag.controller'; -import { TagService } from 'src/services/tag.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(TagController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(TagService); - - beforeAll(async () => { - ctx = await controllerSetup(TagController, [{ provide: TagService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /tags', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/tags'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /tags', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/tags'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should a null parentId', async () => { - await request(ctx.getHttpServer()).post(`/tags`).send({ name: 'tag', parentId: null }); - expect(service.create).toHaveBeenCalledWith(undefined, expect.objectContaining({ parentId: null })); - }); - }); - - describe('PUT /tags', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put('/tags'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /tags/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/tags/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should require a valid uuid', async () => { - const { status, body } = await request(ctx.getHttpServer()).get(`/tags/123`); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest([expect.stringContaining('id must be a UUID')])); - }); - }); - - describe('PUT /tags/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/tags/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should allow setting a null color via an empty string', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/tags/${id}`).send({ color: '' }); - expect(service.update).toHaveBeenCalledWith(undefined, id, expect.objectContaining({ color: null })); - }); - }); -}); diff --git a/server/src/controllers/tag.controller.ts b/server/src/controllers/tag.controller.ts deleted file mode 100644 index 101e89f3a5..0000000000 --- a/server/src/controllers/tag.controller.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - TagBulkAssetsDto, - TagBulkAssetsResponseDto, - TagCreateDto, - TagResponseDto, - TagUpdateDto, - TagUpsertDto, -} from 'src/dtos/tag.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { TagService } from 'src/services/tag.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.Tags) -@Controller('tags') -export class TagController { - constructor(private service: TagService) {} - - @Post() - @Authenticated({ permission: Permission.TagCreate }) - @Endpoint({ - summary: 'Create a tag', - description: 'Create a new tag by providing a name and optional color.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createTag(@Auth() auth: AuthDto, @Body() dto: TagCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Get() - @Authenticated({ permission: Permission.TagRead }) - @Endpoint({ - summary: 'Retrieve tags', - description: 'Retrieve a list of all tags.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAllTags(@Auth() auth: AuthDto): Promise { - return this.service.getAll(auth); - } - - @Put() - @Authenticated({ permission: Permission.TagCreate }) - @Endpoint({ - summary: 'Upsert tags', - description: 'Create or update multiple tags in a single request.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - upsertTags(@Auth() auth: AuthDto, @Body() dto: TagUpsertDto): Promise { - return this.service.upsert(auth, dto); - } - - @Put('assets') - @Authenticated({ permission: Permission.TagAsset }) - @Endpoint({ - summary: 'Tag assets', - description: 'Add multiple tags to multiple assets in a single request.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - bulkTagAssets(@Auth() auth: AuthDto, @Body() dto: TagBulkAssetsDto): Promise { - return this.service.bulkTagAssets(auth, dto); - } - - @Get(':id') - @Authenticated({ permission: Permission.TagRead }) - @Endpoint({ - summary: 'Retrieve a tag', - description: 'Retrieve a specific tag by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.TagUpdate }) - @Endpoint({ - summary: 'Update a tag', - description: 'Update an existing tag identified by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.TagDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a tag', - description: 'Delete a specific tag by its ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.remove(auth, id); - } - - @Put(':id/assets') - @Authenticated({ permission: Permission.TagAsset }) - @Endpoint({ - summary: 'Tag assets', - description: 'Add a tag to all the specified assets.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - tagAssets( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: BulkIdsDto, - ): Promise { - return this.service.addAssets(auth, id, dto); - } - - @Delete(':id/assets') - @Authenticated({ permission: Permission.TagAsset }) - @Endpoint({ - summary: 'Untag assets', - description: 'Remove a tag from all the specified assets.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - untagAssets( - @Auth() auth: AuthDto, - @Body() dto: BulkIdsDto, - @Param() { id }: UUIDParamDto, - ): Promise { - return this.service.removeAssets(auth, id, dto); - } -} diff --git a/server/src/controllers/timeline.controller.spec.ts b/server/src/controllers/timeline.controller.spec.ts deleted file mode 100644 index 6d0276c6a3..0000000000 --- a/server/src/controllers/timeline.controller.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { TimelineController } from 'src/controllers/timeline.controller'; -import { TimelineService } from 'src/services/timeline.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(TimelineController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(TimelineService); - - beforeAll(async () => { - ctx = await controllerSetup(TimelineController, [{ provide: TimelineService, useValue: service }]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /timeline/buckets', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/timeline/buckets'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /timeline/bucket', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/timeline/bucket?timeBucket=1900-01-01'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - // TODO enable date string validation while still accepting 5 digit years - it.fails('should fail if time bucket is invalid', async () => { - const { status, body } = await request(ctx.getHttpServer()).get('/timeline/bucket').query({ timeBucket: 'foo' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Invalid time bucket format')); - }); - }); -}); diff --git a/server/src/controllers/timeline.controller.ts b/server/src/controllers/timeline.controller.ts deleted file mode 100644 index f1789a79e8..0000000000 --- a/server/src/controllers/timeline.controller.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Controller, Get, Header, Query } from '@nestjs/common'; -import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { TimeBucketAssetDto, TimeBucketAssetResponseDto, TimeBucketDto } from 'src/dtos/time-bucket.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { TimelineService } from 'src/services/timeline.service'; - -@ApiTags(ApiTag.Timeline) -@Controller('timeline') -export class TimelineController { - constructor(private service: TimelineService) {} - - @Get('buckets') - @Authenticated({ permission: Permission.AssetRead, sharedLink: true }) - @Endpoint({ - summary: 'Get time buckets', - description: 'Retrieve a list of all minimal time buckets.', - history: new HistoryBuilder().added('v1').internal('v1'), - }) - getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) { - return this.service.getTimeBuckets(auth, dto); - } - - @Get('bucket') - @Authenticated({ permission: Permission.AssetRead, sharedLink: true }) - @ApiOkResponse({ type: TimeBucketAssetResponseDto }) - @Header('Content-Type', 'application/json') - @Endpoint({ - summary: 'Get time bucket', - description: 'Retrieve a string of all asset ids in a given time bucket.', - history: new HistoryBuilder().added('v1').internal('v1'), - }) - getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) { - return this.service.getTimeBucket(auth, dto); - } -} diff --git a/server/src/controllers/trash.controller.ts b/server/src/controllers/trash.controller.ts deleted file mode 100644 index ec37c63ecc..0000000000 --- a/server/src/controllers/trash.controller.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { TrashResponseDto } from 'src/dtos/trash.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { TrashService } from 'src/services/trash.service'; - -@ApiTags(ApiTag.Trash) -@Controller('trash') -export class TrashController { - constructor(private service: TrashService) {} - - @Post('empty') - @Authenticated({ permission: Permission.AssetDelete }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Empty trash', - description: 'Permanently delete all items in the trash.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - emptyTrash(@Auth() auth: AuthDto): Promise { - return this.service.empty(auth); - } - - @Post('restore') - @Authenticated({ permission: Permission.AssetDelete }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Restore trash', - description: 'Restore all items in the trash.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - restoreTrash(@Auth() auth: AuthDto): Promise { - return this.service.restore(auth); - } - - @Post('restore/assets') - @Authenticated({ permission: Permission.AssetDelete }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Restore assets', - description: 'Restore specific assets from the trash.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise { - return this.service.restoreAssets(auth, dto); - } -} diff --git a/server/src/controllers/user-admin.controller.spec.ts b/server/src/controllers/user-admin.controller.spec.ts deleted file mode 100644 index edda974476..0000000000 --- a/server/src/controllers/user-admin.controller.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { UserAdminController } from 'src/controllers/user-admin.controller'; -import { UserAdminCreateDto } from 'src/dtos/user.dto'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { UserAdminService } from 'src/services/user-admin.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(UserAdminController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(UserAdminService); - - beforeAll(async () => { - ctx = await controllerSetup(UserAdminController, [ - { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, - { provide: UserAdminService, useValue: service }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /admin/users', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/admin/users'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /admin/users/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/admin/users/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('POST /admin/users', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).post('/admin/users'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it('should allow a null pinCode', async () => { - await request(ctx.getHttpServer()).post(`/admin/users`).send({ - name: 'Test user', - email: 'test@immich.cloud', - password: 'password', - pinCode: null, - }); - expect(service.create).toHaveBeenCalledWith(expect.objectContaining({ pinCode: null })); - }); - - it('should allow a null avatarColor', async () => { - await request(ctx.getHttpServer()).post(`/admin/users`).send({ - name: 'Test user', - email: 'test@immich.cloud', - password: 'password', - avatarColor: null, - }); - expect(service.create).toHaveBeenCalledWith(expect.objectContaining({ avatarColor: null })); - }); - - it(`should `, async () => { - const dto: UserAdminCreateDto = { - email: 'user@immich.app', - password: 'test', - name: 'Test User', - quotaSizeInBytes: 1.2, - }; - - const { status, body } = await request(ctx.getHttpServer()) - .post(`/admin/users`) - .set('Authorization', `Bearer token`) - .send(dto); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['quotaSizeInBytes must be an integer number']))); - }); - - it(`should not allow decimal quota`, async () => { - const dto: UserAdminCreateDto = { - email: 'user@immich.app', - password: 'test', - name: 'Test User', - quotaSizeInBytes: 1.2, - }; - - const { status, body } = await request(ctx.getHttpServer()) - .post(`/admin/users`) - .set('Authorization', `Bearer token`) - .send(dto); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['quotaSizeInBytes must be an integer number']))); - }); - }); - - describe('GET /admin/users/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/admin/users/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /admin/users/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put(`/admin/users/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - it(`should not allow decimal quota`, async () => { - const { status, body } = await request(ctx.getHttpServer()) - .put(`/admin/users/${factory.uuid()}`) - .set('Authorization', `Bearer token`) - .send({ quotaSizeInBytes: 1.2 }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest(expect.arrayContaining(['quotaSizeInBytes must be an integer number']))); - }); - - it('should allow a null pinCode', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/admin/users/${id}`).send({ pinCode: null }); - expect(service.update).toHaveBeenCalledWith(undefined, id, expect.objectContaining({ pinCode: null })); - }); - - it('should allow a null avatarColor', async () => { - const id = factory.uuid(); - await request(ctx.getHttpServer()).put(`/admin/users/${id}`).send({ avatarColor: null }); - expect(service.update).toHaveBeenCalledWith(undefined, id, expect.objectContaining({ avatarColor: null })); - }); - }); -}); diff --git a/server/src/controllers/user-admin.controller.ts b/server/src/controllers/user-admin.controller.ts deleted file mode 100644 index 6dd919e193..0000000000 --- a/server/src/controllers/user-admin.controller.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetStatsDto, AssetStatsResponseDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { SessionResponseDto } from 'src/dtos/session.dto'; -import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; -import { - UserAdminCreateDto, - UserAdminDeleteDto, - UserAdminResponseDto, - UserAdminSearchDto, - UserAdminUpdateDto, -} from 'src/dtos/user.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { UserAdminService } from 'src/services/user-admin.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags(ApiTag.UsersAdmin) -@Controller('admin/users') -export class UserAdminController { - constructor(private service: UserAdminService) {} - - @Get() - @Authenticated({ permission: Permission.AdminUserRead, admin: true }) - @Endpoint({ - summary: 'Search users', - description: 'Search for users.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise { - return this.service.search(auth, dto); - } - - @Post() - @Authenticated({ permission: Permission.AdminUserCreate, admin: true }) - @Endpoint({ - summary: 'Create a user', - description: 'Create a new user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise { - return this.service.create(createUserDto); - } - - @Get(':id') - @Authenticated({ permission: Permission.AdminUserRead, admin: true }) - @Endpoint({ - summary: 'Retrieve a user', - description: 'Retrieve a specific user by their ID.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.AdminUserUpdate, admin: true }) - @Endpoint({ - summary: 'Update a user', - description: 'Update an existing user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateUserAdmin( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: UserAdminUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.AdminUserDelete, admin: true }) - @Endpoint({ - summary: 'Delete a user', - description: 'Delete a user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteUserAdmin( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: UserAdminDeleteDto, - ): Promise { - return this.service.delete(auth, id, dto); - } - - @Get(':id/sessions') - @Authenticated({ permission: Permission.AdminSessionRead, admin: true }) - @Endpoint({ - summary: 'Retrieve user sessions', - description: 'Retrieve all sessions for a specific user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUserSessionsAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getSessions(auth, id); - } - - @Get(':id/statistics') - @Authenticated({ permission: Permission.AdminUserRead, admin: true }) - @Endpoint({ - summary: 'Retrieve user statistics', - description: 'Retrieve asset statistics for a specific user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUserStatisticsAdmin( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Query() dto: AssetStatsDto, - ): Promise { - return this.service.getStatistics(auth, id, dto); - } - - @Get(':id/preferences') - @Authenticated({ permission: Permission.AdminUserRead, admin: true }) - @Endpoint({ - summary: 'Retrieve user preferences', - description: 'Retrieve the preferences of a specific user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.getPreferences(auth, id); - } - - @Put(':id/preferences') - @Authenticated({ permission: Permission.AdminUserUpdate, admin: true }) - @Endpoint({ - summary: 'Update user preferences', - description: 'Update the preferences of a specific user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateUserPreferencesAdmin( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: UserPreferencesUpdateDto, - ): Promise { - return this.service.updatePreferences(auth, id, dto); - } - - @Post(':id/restore') - @Authenticated({ permission: Permission.AdminUserDelete, admin: true }) - @HttpCode(HttpStatus.OK) - @Endpoint({ - summary: 'Restore a deleted user', - description: 'Restore a previously deleted user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.restore(auth, id); - } -} diff --git a/server/src/controllers/user.controller.spec.ts b/server/src/controllers/user.controller.spec.ts deleted file mode 100644 index 3c3e103814..0000000000 --- a/server/src/controllers/user.controller.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { UserController } from 'src/controllers/user.controller'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { UserService } from 'src/services/user.service'; -import request from 'supertest'; -import { errorDto } from 'test/medium/responses'; -import { factory } from 'test/small.factory'; -import { automock, ControllerContext, controllerSetup, mockBaseService } from 'test/utils'; - -describe(UserController.name, () => { - let ctx: ControllerContext; - const service = mockBaseService(UserService); - - beforeAll(async () => { - ctx = await controllerSetup(UserController, [ - { provide: LoggingRepository, useValue: automock(LoggingRepository, { strict: false }) }, - { provide: UserService, useValue: service }, - ]); - return () => ctx.close(); - }); - - beforeEach(() => { - service.resetAllMocks(); - ctx.reset(); - }); - - describe('GET /users', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/users'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('GET /users/me', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get('/users/me'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /users/me', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put('/users/me'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - - for (const key of ['email', 'name']) { - it(`should not allow null ${key}`, async () => { - const dto = { [key]: null }; - const { status, body } = await request(ctx.getHttpServer()) - .put(`/users/me`) - .set('Authorization', `Bearer token`) - .send(dto); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest()); - }); - } - - it('should allow an empty avatarColor', async () => { - await request(ctx.getHttpServer()) - .put(`/users/me`) - .set('Authorization', `Bearer token`) - .send({ avatarColor: null }); - expect(service.updateMe).toHaveBeenCalledWith(undefined, expect.objectContaining({ avatarColor: null })); - }); - }); - - describe('GET /users/:id', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).get(`/users/${factory.uuid()}`); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('PUT /users/me/license', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).put('/users/me/license'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); - - describe('DELETE /users/me/license', () => { - it('should be an authenticated route', async () => { - await request(ctx.getHttpServer()).delete('/users/me/license'); - expect(ctx.authenticate).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/controllers/user.controller.ts b/server/src/controllers/user.controller.ts index 9c0dd3db7a..f88398f017 100644 --- a/server/src/controllers/user.controller.ts +++ b/server/src/controllers/user.controller.ts @@ -1,42 +1,17 @@ -import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Next, - Param, - Post, - Put, - Res, - UploadedFile, - UseInterceptors, -} from '@nestjs/common'; -import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; -import { NextFunction, Response } from 'express'; +import { Body, Controller, Get, Param, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; -import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto'; -import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; -import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto'; import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto } from 'src/dtos/user.dto'; import { ApiTag, Permission, RouteKey } from 'src/enum'; -import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; -import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; -import { LoggingRepository } from 'src/repositories/logging.repository'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { UserService } from 'src/services/user.service'; -import { sendFile } from 'src/utils/file'; import { UUIDParamDto } from 'src/validation'; @ApiTags(ApiTag.Users) @Controller(RouteKey.User) export class UserController { - constructor( - private service: UserService, - private logger: LoggingRepository, - ) {} + constructor(private service: UserService) {} @Get() @Authenticated({ permission: Permission.UserRead }) @@ -64,106 +39,13 @@ export class UserController { @Authenticated({ permission: Permission.UserUpdate }) @Endpoint({ summary: 'Update current user', - description: 'Update the current user making teh API request.', + description: 'Update the current user making the API request.', history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), }) updateMyUser(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto): Promise { return this.service.updateMe(auth, dto); } - @Get('me/preferences') - @Authenticated({ permission: Permission.UserPreferenceRead }) - @Endpoint({ - summary: 'Get my preferences', - description: 'Retrieve the preferences for the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getMyPreferences(@Auth() auth: AuthDto): Promise { - return this.service.getMyPreferences(auth); - } - - @Put('me/preferences') - @Authenticated({ permission: Permission.UserPreferenceUpdate }) - @Endpoint({ - summary: 'Update my preferences', - description: 'Update the preferences of the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - updateMyPreferences( - @Auth() auth: AuthDto, - @Body() dto: UserPreferencesUpdateDto, - ): Promise { - return this.service.updateMyPreferences(auth, dto); - } - - @Get('me/license') - @Authenticated({ permission: Permission.UserLicenseRead }) - @Endpoint({ - summary: 'Retrieve user product key', - description: 'Retrieve information about whether the current user has a registered product key.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUserLicense(@Auth() auth: AuthDto): Promise { - return this.service.getLicense(auth); - } - - @Put('me/license') - @Authenticated({ permission: Permission.UserLicenseUpdate }) - @Endpoint({ - summary: 'Set user product key', - description: 'Register a product key for the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async setUserLicense(@Auth() auth: AuthDto, @Body() license: LicenseKeyDto): Promise { - return this.service.setLicense(auth, license); - } - - @Delete('me/license') - @Authenticated({ permission: Permission.UserLicenseDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete user product key', - description: 'Delete the registered product key for the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async deleteUserLicense(@Auth() auth: AuthDto): Promise { - await this.service.deleteLicense(auth); - } - - @Get('me/onboarding') - @Authenticated({ permission: Permission.UserOnboardingRead }) - @Endpoint({ - summary: 'Retrieve user onboarding', - description: 'Retrieve the onboarding status of the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUserOnboarding(@Auth() auth: AuthDto): Promise { - return this.service.getOnboarding(auth); - } - - @Put('me/onboarding') - @Authenticated({ permission: Permission.UserOnboardingUpdate }) - @Endpoint({ - summary: 'Update user onboarding', - description: 'Update the onboarding status of the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async setUserOnboarding(@Auth() auth: AuthDto, @Body() Onboarding: OnboardingDto): Promise { - return this.service.setOnboarding(auth, Onboarding); - } - - @Delete('me/onboarding') - @Authenticated({ permission: Permission.UserOnboardingDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete user onboarding', - description: 'Delete the onboarding status of the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async deleteUserOnboarding(@Auth() auth: AuthDto): Promise { - await this.service.deleteOnboarding(auth); - } - @Get(':id') @Authenticated({ permission: Permission.UserRead }) @Endpoint({ @@ -174,45 +56,4 @@ export class UserController { getUser(@Param() { id }: UUIDParamDto): Promise { return this.service.get(id); } - - @Post('profile-image') - @Authenticated({ permission: Permission.UserProfileImageUpdate }) - @UseInterceptors(FileUploadInterceptor) - @ApiConsumes('multipart/form-data') - @ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto }) - @Endpoint({ - summary: 'Create user profile image', - description: 'Upload and set a new profile image for the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - createProfileImage( - @Auth() auth: AuthDto, - @UploadedFile() fileInfo: Express.Multer.File, - ): Promise { - return this.service.createProfileImage(auth, fileInfo); - } - - @Delete('profile-image') - @Authenticated({ permission: Permission.UserProfileImageDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete user profile image', - description: 'Delete the profile image of the current user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - deleteProfileImage(@Auth() auth: AuthDto): Promise { - return this.service.deleteProfileImage(auth); - } - - @Get(':id/profile-image') - @FileResponse() - @Authenticated({ permission: Permission.UserProfileImageRead }) - @Endpoint({ - summary: 'Retrieve user profile image', - description: 'Retrieve the profile image file for a user.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - async getProfileImage(@Res() res: Response, @Next() next: NextFunction, @Param() { id }: UUIDParamDto) { - await sendFile(res, next, () => this.service.getProfileImage(id), this.logger); - } } diff --git a/server/src/controllers/view.controller.ts b/server/src/controllers/view.controller.ts deleted file mode 100644 index b07d83fe58..0000000000 --- a/server/src/controllers/view.controller.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Controller, Get, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ApiTag, Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { ViewService } from 'src/services/view.service'; - -@ApiTags(ApiTag.Views) -@Controller('view') -export class ViewController { - constructor(private service: ViewService) {} - - @Get('folder/unique-paths') - @Authenticated({ permission: Permission.FolderRead }) - @Endpoint({ - summary: 'Retrieve unique paths', - description: 'Retrieve a list of unique folder paths from asset original paths.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getUniqueOriginalPaths(@Auth() auth: AuthDto): Promise { - return this.service.getUniqueOriginalPaths(auth); - } - - @Get('folder') - @Authenticated({ permission: Permission.FolderRead }) - @Endpoint({ - summary: 'Retrieve assets by original path', - description: 'Retrieve assets that are children of a specific folder.', - history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), - }) - getAssetsByOriginalPath(@Auth() auth: AuthDto, @Query('path') path: string): Promise { - return this.service.getAssetsByOriginalPath(auth, path); - } -} diff --git a/server/src/controllers/workflow.controller.ts b/server/src/controllers/workflow.controller.ts deleted file mode 100644 index e07b6443f4..0000000000 --- a/server/src/controllers/workflow.controller.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Endpoint, HistoryBuilder } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto } from 'src/dtos/workflow.dto'; -import { Permission } from 'src/enum'; -import { Auth, Authenticated } from 'src/middleware/auth.guard'; -import { WorkflowService } from 'src/services/workflow.service'; -import { UUIDParamDto } from 'src/validation'; - -@ApiTags('Workflows') -@Controller('workflows') -export class WorkflowController { - constructor(private service: WorkflowService) {} - - @Post() - @Authenticated({ permission: Permission.WorkflowCreate }) - @Endpoint({ - summary: 'Create a workflow', - description: 'Create a new workflow, the workflow can also be created with empty filters and actions.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - createWorkflow(@Auth() auth: AuthDto, @Body() dto: WorkflowCreateDto): Promise { - return this.service.create(auth, dto); - } - - @Get() - @Authenticated({ permission: Permission.WorkflowRead }) - @Endpoint({ - summary: 'List all workflows', - description: 'Retrieve a list of workflows available to the authenticated user.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getWorkflows(@Auth() auth: AuthDto): Promise { - return this.service.getAll(auth); - } - - @Get(':id') - @Authenticated({ permission: Permission.WorkflowRead }) - @Endpoint({ - summary: 'Retrieve a workflow', - description: 'Retrieve information about a specific workflow by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - getWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.get(auth, id); - } - - @Put(':id') - @Authenticated({ permission: Permission.WorkflowUpdate }) - @Endpoint({ - summary: 'Update a workflow', - description: - 'Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - updateWorkflow( - @Auth() auth: AuthDto, - @Param() { id }: UUIDParamDto, - @Body() dto: WorkflowUpdateDto, - ): Promise { - return this.service.update(auth, id, dto); - } - - @Delete(':id') - @Authenticated({ permission: Permission.WorkflowDelete }) - @HttpCode(HttpStatus.NO_CONTENT) - @Endpoint({ - summary: 'Delete a workflow', - description: 'Delete a workflow by its ID.', - history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'), - }) - deleteWorkflow(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise { - return this.service.delete(auth, id); - } -} diff --git a/server/src/cores/storage.core.spec.ts b/server/src/cores/storage.core.spec.ts deleted file mode 100644 index 08e410bbe3..0000000000 --- a/server/src/cores/storage.core.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { StorageCore } from 'src/cores/storage.core'; -import { vitest } from 'vitest'; - -vitest.mock('src/constants', () => ({ - IWorker: 'IWorker', -})); - -describe('StorageCore', () => { - describe('isImmichPath', () => { - beforeAll(() => { - StorageCore.setMediaLocation('/photos'); - }); - - it('should return true for APP_MEDIA_LOCATION path', () => { - const immichPath = '/photos'; - expect(StorageCore.isImmichPath(immichPath)).toBe(true); - }); - - it('should return true for paths within the APP_MEDIA_LOCATION', () => { - const immichPath = '/photos/new/'; - expect(StorageCore.isImmichPath(immichPath)).toBe(true); - }); - - it('should return false for paths outside the APP_MEDIA_LOCATION and same starts', () => { - const nonImmichPath = '/photos_new'; - expect(StorageCore.isImmichPath(nonImmichPath)).toBe(false); - }); - - it('should return false for paths outside the APP_MEDIA_LOCATION', () => { - const nonImmichPath = '/some/other/path'; - expect(StorageCore.isImmichPath(nonImmichPath)).toBe(false); - }); - }); -}); diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts deleted file mode 100644 index c6821404dc..0000000000 --- a/server/src/cores/storage.core.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { randomUUID } from 'node:crypto'; -import { dirname, join, resolve } from 'node:path'; -import { StorageAsset } from 'src/database'; -import { - AssetFileType, - AssetPathType, - ImageFormat, - PathType, - PersonPathType, - RawExtractedFormat, - StorageFolder, -} from 'src/enum'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { CryptoRepository } from 'src/repositories/crypto.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { MoveRepository } from 'src/repositories/move.repository'; -import { PersonRepository } from 'src/repositories/person.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { getAssetFile } from 'src/utils/asset.util'; -import { getConfig } from 'src/utils/config'; - -export interface MoveRequest { - entityId: string; - pathType: PathType; - oldPath: string | null; - newPath: string; - assetInfo?: { - sizeInBytes: number; - checksum: Buffer; - }; -} - -export type ThumbnailPathEntity = { id: string; ownerId: string }; - -export type ImagePathOptions = { fileType: AssetFileType; format: ImageFormat | RawExtractedFormat; isEdited: boolean }; - -let instance: StorageCore | null; - -let mediaLocation: string | undefined; - -export class StorageCore { - private constructor( - private assetRepository: AssetRepository, - private configRepository: ConfigRepository, - private cryptoRepository: CryptoRepository, - private moveRepository: MoveRepository, - private personRepository: PersonRepository, - private storageRepository: StorageRepository, - private systemMetadataRepository: SystemMetadataRepository, - private logger: LoggingRepository, - ) { - this.logger.setContext(StorageCore.name); - } - - static create( - assetRepository: AssetRepository, - configRepository: ConfigRepository, - cryptoRepository: CryptoRepository, - moveRepository: MoveRepository, - personRepository: PersonRepository, - storageRepository: StorageRepository, - systemMetadataRepository: SystemMetadataRepository, - logger: LoggingRepository, - ) { - if (!instance) { - instance = new StorageCore( - assetRepository, - configRepository, - cryptoRepository, - moveRepository, - personRepository, - storageRepository, - systemMetadataRepository, - logger, - ); - } - - return instance; - } - - static reset() { - instance = null; - } - - static getMediaLocation(): string { - if (mediaLocation === undefined) { - throw new Error('Media location is not set.'); - } - - return mediaLocation; - } - - static setMediaLocation(location: string) { - mediaLocation = location; - } - - static getFolderLocation(folder: StorageFolder, userId: string) { - return join(StorageCore.getBaseFolder(folder), userId); - } - - static getLibraryFolder(user: { storageLabel: string | null; id: string }) { - return join(StorageCore.getBaseFolder(StorageFolder.Library), user.storageLabel || user.id); - } - - static getBaseFolder(folder: StorageFolder) { - return join(StorageCore.getMediaLocation(), folder); - } - - static getPersonThumbnailPath(person: ThumbnailPathEntity) { - return StorageCore.getNestedPath(StorageFolder.Thumbnails, person.ownerId, `${person.id}.jpeg`); - } - - static getImagePath(asset: ThumbnailPathEntity, { fileType, format, isEdited }: ImagePathOptions) { - return StorageCore.getNestedPath( - StorageFolder.Thumbnails, - asset.ownerId, - `${asset.id}_${fileType}${isEdited ? '_edited' : ''}.${format}`, - ); - } - - static getEncodedVideoPath(asset: ThumbnailPathEntity) { - return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${asset.id}.mp4`); - } - - static getAndroidMotionPath(asset: ThumbnailPathEntity, uuid: string) { - return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${uuid}-MP.mp4`); - } - - static isAndroidMotionPath(originalPath: string) { - return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.EncodedVideo)); - } - - static isImmichPath(path: string) { - const resolvedPath = resolve(path); - const resolvedAppMediaLocation = StorageCore.getMediaLocation(); - const normalizedPath = resolvedPath.endsWith('/') ? resolvedPath : resolvedPath + '/'; - const normalizedAppMediaLocation = resolvedAppMediaLocation.endsWith('/') - ? resolvedAppMediaLocation - : resolvedAppMediaLocation + '/'; - return normalizedPath.startsWith(normalizedAppMediaLocation); - } - - async moveAssetImage(asset: StorageAsset, fileType: AssetFileType, format: ImageFormat) { - const { id: entityId, files } = asset; - const oldFile = getAssetFile(files, fileType, { isEdited: false }); - return this.moveFile({ - entityId, - pathType: fileType, - oldPath: oldFile?.path || null, - newPath: StorageCore.getImagePath(asset, { fileType, format, isEdited: false }), - }); - } - - async moveAssetVideo(asset: StorageAsset) { - return this.moveFile({ - entityId: asset.id, - pathType: AssetPathType.EncodedVideo, - oldPath: asset.encodedVideoPath, - newPath: StorageCore.getEncodedVideoPath(asset), - }); - } - - async movePersonFile(person: { id: string; ownerId: string; thumbnailPath: string }, pathType: PersonPathType) { - const { id: entityId, thumbnailPath } = person; - switch (pathType) { - case PersonPathType.Face: { - await this.moveFile({ - entityId, - pathType, - oldPath: thumbnailPath, - newPath: StorageCore.getPersonThumbnailPath(person), - }); - } - } - } - - async moveFile(request: MoveRequest) { - const { entityId, pathType, oldPath, newPath, assetInfo } = request; - if (!oldPath || oldPath === newPath) { - return; - } - - this.ensureFolders(newPath); - - let move = await this.moveRepository.getByEntity(entityId, pathType); - if (move) { - this.logger.log(`Attempting to finish incomplete move: ${move.oldPath} => ${move.newPath}`); - const oldPathExists = await this.storageRepository.checkFileExists(move.oldPath); - const newPathExists = await this.storageRepository.checkFileExists(move.newPath); - const newPathCheck = newPathExists ? move.newPath : null; - const actualPath = oldPathExists ? move.oldPath : newPathCheck; - if (!actualPath) { - this.logger.warn('Unable to complete move. File does not exist at either location.'); - return; - } - - const fileAtNewLocation = actualPath === move.newPath; - this.logger.log(`Found file at ${fileAtNewLocation ? 'new' : 'old'} location`); - - if ( - fileAtNewLocation && - !(await this.verifyNewPathContentsMatchesExpected(move.oldPath, move.newPath, assetInfo)) - ) { - this.logger.fatal( - `Skipping move as file verification failed, old file is missing and new file is different to what was expected`, - ); - return; - } - - move = await this.moveRepository.update(move.id, { id: move.id, oldPath: actualPath, newPath }); - } else { - move = await this.moveRepository.create({ entityId, pathType, oldPath, newPath }); - } - - if (pathType === AssetPathType.Original && !assetInfo) { - this.logger.warn(`Unable to complete move. Missing asset info for ${entityId}`); - return; - } - - if (move.oldPath !== newPath) { - try { - this.logger.debug(`Attempting to rename file: ${move.oldPath} => ${newPath}`); - await this.storageRepository.rename(move.oldPath, newPath); - } catch (error: any) { - if (error.code !== 'EXDEV') { - this.logger.warn( - `Unable to complete move. Error renaming file with code ${error.code} and message: ${error.message}`, - ); - return; - } - this.logger.debug(`Unable to rename file. Falling back to copy, verify and delete`); - await this.storageRepository.copyFile(move.oldPath, newPath); - - if (!(await this.verifyNewPathContentsMatchesExpected(move.oldPath, newPath, assetInfo))) { - this.logger.warn(`Skipping move due to file size mismatch`); - await this.storageRepository.unlink(newPath); - return; - } - - const { atime, mtime } = await this.storageRepository.stat(move.oldPath); - await this.storageRepository.utimes(newPath, atime, mtime); - - try { - await this.storageRepository.unlink(move.oldPath); - } catch (error: any) { - this.logger.warn(`Unable to delete old file, it will now no longer be tracked by Immich: ${error.message}`); - } - } - } - - await this.savePath(pathType, entityId, newPath); - await this.moveRepository.delete(move.id); - } - - private async verifyNewPathContentsMatchesExpected( - oldPath: string, - newPath: string, - assetInfo?: { sizeInBytes: number; checksum: Buffer }, - ) { - const oldStat = await this.storageRepository.stat(oldPath); - const newStat = await this.storageRepository.stat(newPath); - const oldPathSize = assetInfo ? assetInfo.sizeInBytes : oldStat.size; - const newPathSize = newStat.size; - this.logger.debug(`File size check: ${newPathSize} === ${oldPathSize}`); - if (newPathSize !== oldPathSize) { - this.logger.warn(`Unable to complete move. File size mismatch: ${newPathSize} !== ${oldPathSize}`); - return false; - } - const repos = { - configRepo: this.configRepository, - metadataRepo: this.systemMetadataRepository, - logger: this.logger, - }; - const config = await getConfig(repos, { withCache: true }); - if (assetInfo && config.storageTemplate.hashVerificationEnabled) { - const { checksum } = assetInfo; - const newChecksum = await this.cryptoRepository.hashFile(newPath); - if (!newChecksum.equals(checksum)) { - this.logger.warn( - `Unable to complete move. File checksum mismatch: ${newChecksum.toString('base64')} !== ${checksum.toString( - 'base64', - )}`, - ); - return false; - } - this.logger.debug(`File checksum check: ${newChecksum.toString('base64')} === ${checksum.toString('base64')}`); - } - return true; - } - - ensureFolders(input: string) { - this.storageRepository.mkdirSync(dirname(input)); - } - - removeEmptyDirs(folder: StorageFolder) { - return this.storageRepository.removeEmptyDirs(StorageCore.getBaseFolder(folder)); - } - - private savePath(pathType: PathType, id: string, newPath: string) { - switch (pathType) { - case AssetPathType.Original: { - return this.assetRepository.update({ id, originalPath: newPath }); - } - case AssetFileType.FullSize: { - return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FullSize, path: newPath }); - } - case AssetFileType.Preview: { - return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Preview, path: newPath }); - } - case AssetFileType.Thumbnail: { - return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Thumbnail, path: newPath }); - } - case AssetPathType.EncodedVideo: { - return this.assetRepository.update({ id, encodedVideoPath: newPath }); - } - case AssetFileType.Sidecar: { - return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Sidecar, path: newPath }); - } - case PersonPathType.Face: { - return this.personRepository.update({ id, thumbnailPath: newPath }); - } - } - } - - static getNestedFolder(folder: StorageFolder, ownerId: string, filename: string): string { - return join(StorageCore.getFolderLocation(folder, ownerId), filename.slice(0, 2), filename.slice(2, 4)); - } - - static getNestedPath(folder: StorageFolder, ownerId: string, filename: string): string { - return join(this.getNestedFolder(folder, ownerId, filename), filename); - } - - static getTempPathInDir(dir: string): string { - return join(dir, `${randomUUID()}.tmp`); - } -} diff --git a/server/src/database.ts b/server/src/database.ts index dd979fdea6..7f12ba0f67 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -1,59 +1,10 @@ -import { Selectable } from 'kysely'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { - AlbumUserRole, - AssetFileType, - AssetType, - AssetVisibility, - MemoryType, - Permission, - PluginContext, - PluginTriggerType, - SharedLinkType, - SourceType, - UserAvatarColor, - UserStatus, -} from 'src/enum'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; -import { UserMetadataItem } from 'src/types'; -import type { ActionConfig, FilterConfig, JSONSchema } from 'src/types/plugin-schema.types'; +import { Permission, UserStatus } from 'src/enum'; export type AuthUser = { id: string; isAdmin: boolean; name: string; email: string; - quotaUsageInBytes: number; - quotaSizeInBytes: number | null; -}; - -export type AlbumUser = { - user: User; - role: AlbumUserRole; -}; - -export type AssetFile = { - id: string; - type: AssetFileType; - path: string; - isEdited: boolean; -}; - -export type Library = { - id: string; - ownerId: string; - createdAt: Date; - updatedAt: Date; - updateId: string; - name: string; - importPaths: string[]; - exclusionPatterns: string[]; - deletedAt: Date | null; - refreshedAt: Date | null; - assets?: MapAsset[]; }; export type AuthApiKey = { @@ -61,19 +12,6 @@ export type AuthApiKey = { permissions: Permission[]; }; -export type Activity = { - id: string; - createdAt: Date; - updatedAt: Date; - albumId: string; - userId: string; - user: User; - assetId: string | null; - comment: string | null; - isLiked: boolean; - updateId: string; -}; - export type ApiKey = { id: string; name: string; @@ -83,119 +21,19 @@ export type ApiKey = { permissions: Permission[]; }; -export type Tag = { - id: string; - value: string; - createdAt: Date; - updatedAt: Date; - color: string | null; - parentId: string | null; -}; - -export type Memory = { - id: string; - createdAt: Date; - updatedAt: Date; - deletedAt: Date | null; - memoryAt: Date; - seenAt: Date | null; - showAt: Date | null; - hideAt: Date | null; - type: MemoryType; - data: object; - ownerId: string; - isSaved: boolean; - assets: MapAsset[]; -}; - -export type Asset = { - id: string; - checksum: Buffer; - deviceAssetId: string; - deviceId: string; - fileCreatedAt: Date; - fileModifiedAt: Date; - isExternal: boolean; - visibility: AssetVisibility; - libraryId: string | null; - livePhotoVideoId: string | null; - localDateTime: Date; - originalFileName: string; - originalPath: string; - ownerId: string; - type: AssetType; -}; - export type User = { id: string; name: string; email: string; - avatarColor: UserAvatarColor | null; - profileImagePath: string; - profileChangedAt: Date; }; export type UserAdmin = User & { - storageLabel: string | null; shouldChangePassword: boolean; isAdmin: boolean; createdAt: Date; updatedAt: Date; deletedAt: Date | null; - oauthId: string; - quotaSizeInBytes: number | null; - quotaUsageInBytes: number; status: UserStatus; - metadata: UserMetadataItem[]; -}; - -export type StorageAsset = { - id: string; - ownerId: string; - files: AssetFile[]; - encodedVideoPath: string | null; -}; - -export type Stack = { - id: string; - primaryAssetId: string; - owner?: User; - ownerId: string; - assets: MapAsset[]; - assetCount?: number; -}; - -export type AuthSharedLink = { - id: string; - expiresAt: Date | null; - userId: string; - showExif: boolean; - allowUpload: boolean; - allowDownload: boolean; - password: string | null; -}; - -export type SharedLink = { - id: string; - album?: Album | null; - albumId: string | null; - allowDownload: boolean; - allowUpload: boolean; - assets: MapAsset[]; - createdAt: Date; - description: string | null; - expiresAt: Date | null; - key: Buffer; - password: string | null; - showExif: boolean; - type: SharedLinkType; - userId: string; - slug: string | null; -}; - -export type Album = Selectable & { - owner: User; - assets: MapAsset[]; }; export type AuthSession = { @@ -203,32 +41,6 @@ export type AuthSession = { hasElevatedPermission: boolean; }; -export type Partner = { - sharedById: string; - sharedBy: User; - sharedWithId: string; - sharedWith: User; - createdAt: Date; - createId: string; - updatedAt: Date; - updateId: string; - inTimeline: boolean; -}; - -export type Place = { - admin1Code: string | null; - admin1Name: string | null; - admin2Code: string | null; - admin2Name: string | null; - alternateNames: string | null; - countryCode: string; - id: number; - latitude: number; - longitude: number; - modificationDate: Date; - name: string; -}; - export type Session = { id: string; createdAt: Date; @@ -241,132 +53,13 @@ export type Session = { isPendingSyncReset: boolean; }; -export type Exif = Omit, 'updatedAt' | 'updateId' | 'lockedProperties'>; - -export type Person = { - createdAt: Date; - id: string; - ownerId: string; - updatedAt: Date; - updateId: string; - isFavorite: boolean; - name: string; - birthDate: Date | null; - color: string | null; - faceAssetId: string | null; - isHidden: boolean; - thumbnailPath: string; -}; - -export type AssetFace = { - id: string; - deletedAt: Date | null; - assetId: string; - boundingBoxX1: number; - boundingBoxX2: number; - boundingBoxY1: number; - boundingBoxY2: number; - imageHeight: number; - imageWidth: number; - personId: string | null; - sourceType: SourceType; - person?: Person | null; - updatedAt: Date; - updateId: string; - isVisible: boolean; -}; - -export type Plugin = Selectable; - -export type PluginFilter = Selectable & { - methodName: string; - title: string; - description: string; - supportedContexts: PluginContext[]; - schema: JSONSchema | null; -}; - -export type PluginAction = Selectable & { - methodName: string; - title: string; - description: string; - supportedContexts: PluginContext[]; - schema: JSONSchema | null; -}; - -export type Workflow = Selectable & { - triggerType: PluginTriggerType; - name: string | null; - description: string; - enabled: boolean; -}; - -export type WorkflowFilter = Selectable & { - workflowId: string; - pluginFilterId: string; - filterConfig: FilterConfig | null; - order: number; -}; - -export type WorkflowAction = Selectable & { - workflowId: string; - pluginActionId: string; - actionConfig: ActionConfig | null; - order: number; -}; - -const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const; -const userWithPrefixColumns = [ - 'user2.id', - 'user2.name', - 'user2.email', - 'user2.avatarColor', - 'user2.profileImagePath', - 'user2.profileChangedAt', -] as const; +const userColumns = ['id', 'name', 'email'] as const; export const columns = { - asset: [ - 'asset.id', - 'asset.checksum', - 'asset.deviceAssetId', - 'asset.deviceId', - 'asset.fileCreatedAt', - 'asset.fileModifiedAt', - 'asset.isExternal', - 'asset.visibility', - 'asset.libraryId', - 'asset.livePhotoVideoId', - 'asset.localDateTime', - 'asset.originalFileName', - 'asset.originalPath', - 'asset.ownerId', - 'asset.type', - 'asset.width', - 'asset.height', - ], - assetFiles: ['asset_file.id', 'asset_file.path', 'asset_file.type', 'asset_file.isEdited'], - assetFilesForThumbnail: [ - 'asset_file.id', - 'asset_file.path', - 'asset_file.type', - 'asset_file.isEdited', - 'asset_file.isProgressive', - ], - authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin', 'user.quotaUsageInBytes', 'user.quotaSizeInBytes'], + authUser: ['user.id', 'user.name', 'user.email', 'user.isAdmin'], authApiKey: ['api_key.id', 'api_key.permissions'], authSession: ['session.id', 'session.updatedAt', 'session.pinExpiresAt', 'session.appVersion'], - authSharedLink: [ - 'shared_link.id', - 'shared_link.userId', - 'shared_link.expiresAt', - 'shared_link.showExif', - 'shared_link.allowUpload', - 'shared_link.allowDownload', - 'shared_link.password', - ], user: userColumns, - userWithPrefix: userWithPrefixColumns, userAdmin: [ ...userColumns, 'createdAt', @@ -374,120 +67,7 @@ export const columns = { 'deletedAt', 'isAdmin', 'status', - 'oauthId', - 'profileImagePath', 'shouldChangePassword', - 'storageLabel', - 'quotaSizeInBytes', - 'quotaUsageInBytes', ], - tag: ['tag.id', 'tag.value', 'tag.createdAt', 'tag.updatedAt', 'tag.color', 'tag.parentId'], apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'], - notification: ['id', 'createdAt', 'level', 'type', 'title', 'description', 'data', 'readAt'], - syncAsset: [ - 'asset.id', - 'asset.ownerId', - 'asset.originalFileName', - 'asset.thumbhash', - 'asset.checksum', - 'asset.fileCreatedAt', - 'asset.fileModifiedAt', - 'asset.localDateTime', - 'asset.type', - 'asset.deletedAt', - 'asset.isFavorite', - 'asset.visibility', - 'asset.duration', - 'asset.livePhotoVideoId', - 'asset.stackId', - 'asset.libraryId', - 'asset.width', - 'asset.height', - 'asset.isEdited', - ], - syncAlbumUser: ['album_user.albumId as albumId', 'album_user.userId as userId', 'album_user.role'], - syncStack: ['stack.id', 'stack.createdAt', 'stack.updatedAt', 'stack.primaryAssetId', 'stack.ownerId'], - syncUser: ['id', 'name', 'email', 'avatarColor', 'deletedAt', 'updateId', 'profileImagePath', 'profileChangedAt'], - stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'], - syncAssetExif: [ - 'asset_exif.assetId', - 'asset_exif.description', - 'asset_exif.exifImageWidth', - 'asset_exif.exifImageHeight', - 'asset_exif.fileSizeInByte', - 'asset_exif.orientation', - 'asset_exif.dateTimeOriginal', - 'asset_exif.modifyDate', - 'asset_exif.timeZone', - 'asset_exif.latitude', - 'asset_exif.longitude', - 'asset_exif.projectionType', - 'asset_exif.city', - 'asset_exif.state', - 'asset_exif.country', - 'asset_exif.make', - 'asset_exif.model', - 'asset_exif.lensModel', - 'asset_exif.fNumber', - 'asset_exif.focalLength', - 'asset_exif.iso', - 'asset_exif.exposureTime', - 'asset_exif.profileDescription', - 'asset_exif.rating', - 'asset_exif.fps', - ], - exif: [ - 'asset_exif.assetId', - 'asset_exif.autoStackId', - 'asset_exif.bitsPerSample', - 'asset_exif.city', - 'asset_exif.colorspace', - 'asset_exif.country', - 'asset_exif.dateTimeOriginal', - 'asset_exif.description', - 'asset_exif.exifImageHeight', - 'asset_exif.exifImageWidth', - 'asset_exif.exposureTime', - 'asset_exif.fileSizeInByte', - 'asset_exif.fNumber', - 'asset_exif.focalLength', - 'asset_exif.fps', - 'asset_exif.iso', - 'asset_exif.latitude', - 'asset_exif.lensModel', - 'asset_exif.livePhotoCID', - 'asset_exif.longitude', - 'asset_exif.make', - 'asset_exif.model', - 'asset_exif.modifyDate', - 'asset_exif.orientation', - 'asset_exif.profileDescription', - 'asset_exif.projectionType', - 'asset_exif.rating', - 'asset_exif.state', - 'asset_exif.tags', - 'asset_exif.timeZone', - ], - plugin: [ - 'plugin.id as id', - 'plugin.name as name', - 'plugin.title as title', - 'plugin.description as description', - 'plugin.author as author', - 'plugin.version as version', - 'plugin.wasmPath as wasmPath', - 'plugin.createdAt as createdAt', - 'plugin.updatedAt as updatedAt', - ], } as const; - -export type LockableProperty = (typeof lockableProperties)[number]; -export const lockableProperties = [ - 'description', - 'dateTimeOriginal', - 'latitude', - 'longitude', - 'rating', - 'timeZone', - 'tags', -] as const; diff --git a/server/src/decorators.ts b/server/src/decorators.ts index 87a3900a7f..d9ce4f1a05 100644 --- a/server/src/decorators.ts +++ b/server/src/decorators.ts @@ -1,7 +1,7 @@ import { SetMetadata, applyDecorators } from '@nestjs/common'; -import { ApiOperation, ApiOperationOptions, ApiProperty, ApiPropertyOptions, ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiOperationOptions, ApiProperty, ApiPropertyOptions } from '@nestjs/swagger'; import _ from 'lodash'; -import { ApiCustomExtension, ApiTag, ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum'; +import { ApiCustomExtension, ImmichWorker, JobName, MetadataKey, QueueName } from 'src/enum'; import { EmitEvent } from 'src/repositories/event.repository'; import { immich_uuid_v7, updated_at } from 'src/schema/functions'; import { BeforeUpdateTrigger, Column, ColumnOptions } from 'src/sql-tools'; @@ -163,7 +163,6 @@ export const Endpoint = ({ history, ...options }: EndpointOptions) => { if (history?.isDeprecated()) { options.deprecated = true; - decorators.push(ApiTags(ApiTag.Deprecated)); } decorators.push(ApiOperation({ ...options, ...extensions })); diff --git a/server/src/dtos/activity.dto.ts b/server/src/dtos/activity.dto.ts deleted file mode 100644 index 6464d88508..0000000000 --- a/server/src/dtos/activity.dto.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, ValidateIf } from 'class-validator'; -import { Activity } from 'src/database'; -import { mapUser, UserResponseDto } from 'src/dtos/user.dto'; -import { ValidateEnum, ValidateUUID } from 'src/validation'; - -export enum ReactionType { - COMMENT = 'comment', - LIKE = 'like', -} - -export enum ReactionLevel { - ALBUM = 'album', - ASSET = 'asset', -} - -export type MaybeDuplicate = { duplicate: boolean; value: T }; - -export class ActivityResponseDto { - @ApiProperty({ description: 'Activity ID' }) - id!: string; - @ApiProperty({ description: 'Creation date', format: 'date-time' }) - createdAt!: Date; - @ValidateEnum({ enum: ReactionType, name: 'ReactionType', description: 'Activity type' }) - type!: ReactionType; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - user!: UserResponseDto; - @ApiProperty({ description: 'Asset ID (if activity is for an asset)' }) - assetId!: string | null; - @ApiPropertyOptional({ description: 'Comment text (for comment activities)' }) - comment?: string | null; -} - -export class ActivityStatisticsResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of comments' }) - comments!: number; - - @ApiProperty({ type: 'integer', description: 'Number of likes' }) - likes!: number; -} - -export class ActivityDto { - @ValidateUUID({ description: 'Album ID' }) - albumId!: string; - - @ValidateUUID({ optional: true, description: 'Asset ID (if activity is for an asset)' }) - assetId?: string; -} - -export class ActivitySearchDto extends ActivityDto { - @ValidateEnum({ enum: ReactionType, name: 'ReactionType', description: 'Filter by activity type', optional: true }) - type?: ReactionType; - - @ValidateEnum({ enum: ReactionLevel, name: 'ReactionLevel', description: 'Filter by activity level', optional: true }) - level?: ReactionLevel; - - @ValidateUUID({ optional: true, description: 'Filter by user ID' }) - userId?: string; -} - -const isComment = (dto: ActivityCreateDto) => dto.type === ReactionType.COMMENT; - -export class ActivityCreateDto extends ActivityDto { - @ValidateEnum({ enum: ReactionType, name: 'ReactionType', description: 'Activity type (like or comment)' }) - type!: ReactionType; - - @ApiPropertyOptional({ description: 'Comment text (required if type is comment)' }) - @ValidateIf(isComment) - @IsNotEmpty() - @IsString() - comment?: string; -} - -export const mapActivity = (activity: Activity): ActivityResponseDto => { - return { - id: activity.id, - assetId: activity.assetId, - createdAt: activity.createdAt, - comment: activity.comment, - type: activity.isLiked ? ReactionType.LIKE : ReactionType.COMMENT, - user: mapUser(activity.user), - }; -}; diff --git a/server/src/dtos/album-response.dto.spec.ts b/server/src/dtos/album-response.dto.spec.ts deleted file mode 100644 index dd8642598f..0000000000 --- a/server/src/dtos/album-response.dto.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { mapAlbum } from 'src/dtos/album.dto'; -import { albumStub } from 'test/fixtures/album.stub'; - -describe('mapAlbum', () => { - it('should set start and end dates', () => { - const dto = mapAlbum(albumStub.twoAssets, false); - expect(dto.startDate).toEqual(new Date('2020-12-31T23:59:00.000Z')); - expect(dto.endDate).toEqual(new Date('2025-01-01T01:02:03.456Z')); - }); - - it('should not set start and end dates for empty assets', () => { - const dto = mapAlbum(albumStub.empty, false); - expect(dto.startDate).toBeUndefined(); - expect(dto.endDate).toBeUndefined(); - }); -}); diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts deleted file mode 100644 index 62013fbd92..0000000000 --- a/server/src/dtos/album.dto.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray, IsString, ValidateNested } from 'class-validator'; -import _ from 'lodash'; -import { AlbumUser, AuthSharedLink, User } from 'src/database'; -import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; -import { AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; -import { AlbumUserRole, AssetOrder } from 'src/enum'; -import { Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation'; - -export class AlbumInfoDto { - @ValidateBoolean({ optional: true, description: 'Exclude assets from response' }) - withoutAssets?: boolean; -} - -export class AlbumUserAddDto { - @ValidateUUID({ description: 'User ID' }) - userId!: string; - - @ValidateEnum({ - enum: AlbumUserRole, - name: 'AlbumUserRole', - description: 'Album user role', - default: AlbumUserRole.Editor, - }) - role?: AlbumUserRole; -} - -export class AddUsersDto { - @ApiProperty({ description: 'Album users to add' }) - @ArrayNotEmpty() - albumUsers!: AlbumUserAddDto[]; -} - -export class AlbumUserCreateDto { - @ValidateUUID({ description: 'User ID' }) - userId!: string; - - @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', description: 'Album user role' }) - role!: AlbumUserRole; -} - -export class CreateAlbumDto { - @ApiProperty({ description: 'Album name' }) - @IsString() - albumName!: string; - - @ApiPropertyOptional({ description: 'Album description' }) - @IsString() - @Optional() - description?: string; - - @ApiPropertyOptional({ description: 'Album users' }) - @Optional() - @IsArray() - @ValidateNested({ each: true }) - @Type(() => AlbumUserCreateDto) - albumUsers?: AlbumUserCreateDto[]; - - @ValidateUUID({ optional: true, each: true, description: 'Initial asset IDs' }) - assetIds?: string[]; -} - -export class AlbumsAddAssetsDto { - @ValidateUUID({ each: true, description: 'Album IDs' }) - albumIds!: string[]; - - @ValidateUUID({ each: true, description: 'Asset IDs' }) - assetIds!: string[]; -} - -export class AlbumsAddAssetsResponseDto { - @ApiProperty({ description: 'Operation success' }) - success!: boolean; - @ValidateEnum({ enum: BulkIdErrorReason, name: 'BulkIdErrorReason', description: 'Error reason', optional: true }) - error?: BulkIdErrorReason; -} - -export class UpdateAlbumDto { - @ApiPropertyOptional({ description: 'Album name' }) - @Optional() - @IsString() - albumName?: string; - - @ApiPropertyOptional({ description: 'Album description' }) - @Optional() - @IsString() - description?: string; - - @ValidateUUID({ optional: true, description: 'Album thumbnail asset ID' }) - albumThumbnailAssetId?: string; - - @ValidateBoolean({ optional: true, description: 'Enable activity feed' }) - isActivityEnabled?: boolean; - - @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', description: 'Asset sort order', optional: true }) - order?: AssetOrder; -} - -export class GetAlbumsDto { - @ValidateBoolean({ - optional: true, - description: 'Filter by shared status: true = only shared, false = not shared, undefined = all owned albums', - }) - shared?: boolean; - - @ValidateUUID({ optional: true, description: 'Filter albums containing this asset ID (ignores shared parameter)' }) - assetId?: string; -} - -export class AlbumStatisticsResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of owned albums' }) - owned!: number; - - @ApiProperty({ type: 'integer', description: 'Number of shared albums' }) - shared!: number; - - @ApiProperty({ type: 'integer', description: 'Number of non-shared albums' }) - notShared!: number; -} - -export class UpdateAlbumUserDto { - @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', description: 'Album user role' }) - role!: AlbumUserRole; -} - -export class AlbumUserResponseDto { - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - user!: UserResponseDto; - @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', description: 'Album user role' }) - role!: AlbumUserRole; -} - -export class ContributorCountResponseDto { - @ApiProperty({ description: 'User ID' }) - userId!: string; - - @ApiProperty({ type: 'integer', description: 'Number of assets contributed' }) - assetCount!: number; -} - -export class AlbumResponseDto { - @ApiProperty({ description: 'Album ID' }) - id!: string; - @ApiProperty({ description: 'Owner user ID' }) - ownerId!: string; - @ApiProperty({ description: 'Album name' }) - albumName!: string; - @ApiProperty({ description: 'Album description' }) - description!: string; - @ApiProperty({ description: 'Creation date' }) - createdAt!: Date; - @ApiProperty({ description: 'Last update date' }) - updatedAt!: Date; - @ApiProperty({ description: 'Thumbnail asset ID' }) - albumThumbnailAssetId!: string | null; - @ApiProperty({ description: 'Is shared album' }) - shared!: boolean; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - albumUsers!: AlbumUserResponseDto[]; - @ApiProperty({ description: 'Has shared link' }) - hasSharedLink!: boolean; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - assets!: AssetResponseDto[]; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - owner!: UserResponseDto; - @ApiProperty({ type: 'integer', description: 'Number of assets' }) - assetCount!: number; - @ApiPropertyOptional({ description: 'Last modified asset timestamp' }) - lastModifiedAssetTimestamp?: Date; - @ApiPropertyOptional({ description: 'Start date (earliest asset)' }) - startDate?: Date; - @ApiPropertyOptional({ description: 'End date (latest asset)' }) - endDate?: Date; - @ApiProperty({ description: 'Activity feed enabled' }) - isActivityEnabled!: boolean; - @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', description: 'Asset sort order', optional: true }) - order?: AssetOrder; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Type(() => ContributorCountResponseDto) - contributorCounts?: ContributorCountResponseDto[]; -} - -export type MapAlbumDto = { - albumUsers?: AlbumUser[]; - assets?: MapAsset[]; - sharedLinks?: AuthSharedLink[]; - albumName: string; - description: string; - albumThumbnailAssetId: string | null; - createdAt: Date; - updatedAt: Date; - id: string; - ownerId: string; - owner: User; - isActivityEnabled: boolean; - order: AssetOrder; -}; - -export const mapAlbum = (entity: MapAlbumDto, withAssets: boolean, auth?: AuthDto): AlbumResponseDto => { - const albumUsers: AlbumUserResponseDto[] = []; - - if (entity.albumUsers) { - for (const albumUser of entity.albumUsers) { - const user = mapUser(albumUser.user); - albumUsers.push({ - user, - role: albumUser.role, - }); - } - } - - const albumUsersSorted = _.orderBy(albumUsers, ['role', 'user.name']); - - const assets = entity.assets || []; - - const hasSharedLink = !!entity.sharedLinks && entity.sharedLinks.length > 0; - const hasSharedUser = albumUsers.length > 0; - - let startDate = assets.at(0)?.localDateTime; - let endDate = assets.at(-1)?.localDateTime; - // Swap dates if start date is greater than end date. - if (startDate && endDate && startDate > endDate) { - [startDate, endDate] = [endDate, startDate]; - } - - return { - albumName: entity.albumName, - description: entity.description, - albumThumbnailAssetId: entity.albumThumbnailAssetId, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - id: entity.id, - ownerId: entity.ownerId, - owner: mapUser(entity.owner), - albumUsers: albumUsersSorted, - shared: hasSharedUser || hasSharedLink, - hasSharedLink, - startDate, - endDate, - assets: (withAssets ? assets : []).map((asset) => mapAsset(asset, { auth })), - assetCount: entity.assets?.length || 0, - isActivityEnabled: entity.isActivityEnabled, - order: entity.order, - }; -}; - -export const mapAlbumWithAssets = (entity: MapAlbumDto) => mapAlbum(entity, true); -export const mapAlbumWithoutAssets = (entity: MapAlbumDto) => mapAlbum(entity, false); diff --git a/server/src/dtos/asset-ids.response.dto.ts b/server/src/dtos/asset-ids.response.dto.ts deleted file mode 100644 index 427117518d..0000000000 --- a/server/src/dtos/asset-ids.response.dto.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ValidateUUID } from 'src/validation'; - -/** @deprecated Use `BulkIdResponseDto` instead */ -export enum AssetIdErrorReason { - DUPLICATE = 'duplicate', - NO_PERMISSION = 'no_permission', - NOT_FOUND = 'not_found', -} - -/** @deprecated Use `BulkIdResponseDto` instead */ -export class AssetIdsResponseDto { - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; - @ApiProperty({ description: 'Whether operation succeeded' }) - success!: boolean; - @ApiPropertyOptional({ description: 'Error reason if failed', enum: AssetIdErrorReason }) - error?: AssetIdErrorReason; -} - -export enum BulkIdErrorReason { - DUPLICATE = 'duplicate', - NO_PERMISSION = 'no_permission', - NOT_FOUND = 'not_found', - UNKNOWN = 'unknown', -} - -export class BulkIdsDto { - @ValidateUUID({ each: true, description: 'IDs to process' }) - ids!: string[]; -} - -export class BulkIdResponseDto { - @ApiProperty({ description: 'ID' }) - id!: string; - @ApiProperty({ description: 'Whether operation succeeded' }) - success!: boolean; - @ApiPropertyOptional({ description: 'Error reason if failed', enum: BulkIdErrorReason }) - error?: BulkIdErrorReason; -} diff --git a/server/src/dtos/asset-media-response.dto.ts b/server/src/dtos/asset-media-response.dto.ts deleted file mode 100644 index 345c1bf418..0000000000 --- a/server/src/dtos/asset-media-response.dto.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ValidateEnum } from 'src/validation'; - -export enum AssetMediaStatus { - CREATED = 'created', - REPLACED = 'replaced', - DUPLICATE = 'duplicate', -} -export class AssetMediaResponseDto { - @ValidateEnum({ enum: AssetMediaStatus, name: 'AssetMediaStatus', description: 'Upload status' }) - status!: AssetMediaStatus; - @ApiProperty({ description: 'Asset media ID' }) - id!: string; -} - -export enum AssetUploadAction { - ACCEPT = 'accept', - REJECT = 'reject', -} - -export enum AssetRejectReason { - DUPLICATE = 'duplicate', - UNSUPPORTED_FORMAT = 'unsupported-format', -} - -export class AssetBulkUploadCheckResult { - @ApiProperty({ description: 'Asset ID' }) - id!: string; - @ApiProperty({ description: 'Upload action', enum: AssetUploadAction }) - action!: AssetUploadAction; - @ApiPropertyOptional({ description: 'Rejection reason if rejected', enum: AssetRejectReason }) - reason?: AssetRejectReason; - @ApiPropertyOptional({ description: 'Existing asset ID if duplicate' }) - assetId?: string; - @ApiPropertyOptional({ description: 'Whether existing asset is trashed' }) - isTrashed?: boolean; -} - -export class AssetBulkUploadCheckResponseDto { - @ApiProperty({ description: 'Upload check results' }) - results!: AssetBulkUploadCheckResult[]; -} - -export class CheckExistingAssetsResponseDto { - @ApiProperty({ description: 'Existing asset IDs' }) - existingIds!: string[]; -} diff --git a/server/src/dtos/asset-media.dto.ts b/server/src/dtos/asset-media.dto.ts deleted file mode 100644 index 4655850379..0000000000 --- a/server/src/dtos/asset-media.dto.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { plainToInstance, Transform, Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; -import { AssetMetadataUpsertItemDto } from 'src/dtos/asset.dto'; -import { AssetVisibility } from 'src/enum'; -import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; - -export enum AssetMediaSize { - Original = 'original', - /** - * An full-sized image extracted/converted from non-web-friendly formats like RAW/HIF. - * or otherwise the original image itself. - */ - FULLSIZE = 'fullsize', - PREVIEW = 'preview', - THUMBNAIL = 'thumbnail', -} - -export class AssetMediaOptionsDto { - @ValidateEnum({ enum: AssetMediaSize, name: 'AssetMediaSize', description: 'Asset media size', optional: true }) - size?: AssetMediaSize; - - @ValidateBoolean({ optional: true, description: 'Return edited asset if available', default: false }) - edited?: boolean; -} - -export enum UploadFieldName { - ASSET_DATA = 'assetData', - SIDECAR_DATA = 'sidecarData', - PROFILE_DATA = 'file', -} - -class AssetMediaBase { - @ApiProperty({ description: 'Device asset ID' }) - @IsNotEmpty() - @IsString() - deviceAssetId!: string; - - @ApiProperty({ description: 'Device ID' }) - @IsNotEmpty() - @IsString() - deviceId!: string; - - @ValidateDate({ description: 'File creation date' }) - fileCreatedAt!: Date; - - @ValidateDate({ description: 'File modification date' }) - fileModifiedAt!: Date; - - @ApiPropertyOptional({ description: 'Duration (for videos)' }) - @Optional() - @IsString() - duration?: string; - - @ApiPropertyOptional({ description: 'Filename' }) - @Optional() - @IsString() - filename?: string; - - // The properties below are added to correctly generate the API docs - // and client SDKs. Validation should be handled in the controller. - @ApiProperty({ type: 'string', format: 'binary', description: 'Asset file data' }) - [UploadFieldName.ASSET_DATA]!: any; -} - -export class AssetMediaCreateDto extends AssetMediaBase { - @ValidateBoolean({ optional: true, description: 'Mark as favorite' }) - isFavorite?: boolean; - - @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', description: 'Asset visibility', optional: true }) - visibility?: AssetVisibility; - - @ValidateUUID({ optional: true, description: 'Live photo video ID' }) - livePhotoVideoId?: string; - - @ApiPropertyOptional({ description: 'Asset metadata items' }) - @Transform(({ value }) => { - try { - const json = JSON.parse(value); - const items = Array.isArray(json) ? json : [json]; - return items.map((item) => plainToInstance(AssetMetadataUpsertItemDto, item)); - } catch { - throw new BadRequestException(['metadata must be valid JSON']); - } - }) - @Optional() - @ValidateNested({ each: true }) - @IsArray() - metadata?: AssetMetadataUpsertItemDto[]; - - @ApiProperty({ type: 'string', format: 'binary', required: false, description: 'Sidecar file data' }) - [UploadFieldName.SIDECAR_DATA]?: any; -} - -export class AssetMediaReplaceDto extends AssetMediaBase {} - -export class AssetBulkUploadCheckItem { - @ApiProperty({ description: 'Asset ID' }) - @IsString() - @IsNotEmpty() - id!: string; - - @ApiProperty({ description: 'Base64 or hex encoded SHA1 hash' }) - @IsString() - @IsNotEmpty() - checksum!: string; -} - -export class AssetBulkUploadCheckDto { - @ApiProperty({ description: 'Assets to check' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => AssetBulkUploadCheckItem) - assets!: AssetBulkUploadCheckItem[]; -} - -export class CheckExistingAssetsDto { - @ApiProperty({ description: 'Device asset IDs to check' }) - @ArrayNotEmpty() - @IsString({ each: true }) - @IsNotEmpty({ each: true }) - deviceAssetIds!: string[]; - - @ApiProperty({ description: 'Device ID' }) - @IsNotEmpty() - deviceId!: string; -} diff --git a/server/src/dtos/asset-response.dto.spec.ts b/server/src/dtos/asset-response.dto.spec.ts deleted file mode 100644 index e71ffdadd2..0000000000 --- a/server/src/dtos/asset-response.dto.spec.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { AssetEditAction } from 'src/dtos/editing.dto'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { faceStub } from 'test/fixtures/face.stub'; -import { personStub } from 'test/fixtures/person.stub'; - -describe('mapAsset', () => { - describe('peopleWithFaces', () => { - it('should transform all faces when a person has multiple faces in the same image', () => { - const face1 = { - ...faceStub.primaryFace1, - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const face2 = { - ...faceStub.primaryFace1, - id: 'assetFaceId-second', - boundingBoxX1: 300, - boundingBoxY1: 400, - boundingBoxX2: 400, - boundingBoxY2: 500, - imageWidth: 1000, - imageHeight: 800, - }; - - const asset = { - ...assetStub.withCropEdit, - faces: [face1, face2], - exifInfo: { - exifImageWidth: 1000, - exifImageHeight: 800, - }, - }; - - const result = mapAsset(asset as any); - - expect(result.people).toBeDefined(); - expect(result.people).toHaveLength(1); - expect(result.people![0].faces).toHaveLength(2); - - // Verify that both faces have been transformed (bounding boxes adjusted for crop) - const firstFace = result.people![0].faces[0]; - const secondFace = result.people![0].faces[1]; - - // After crop (x: 216, y: 1512), the coordinates should be adjusted - // Faces outside the crop area will be clamped - expect(firstFace.boundingBoxX1).toBe(-116); // 100 - 216 = -116 - expect(firstFace.boundingBoxY1).toBe(-1412); // 100 - 1512 = -1412 - expect(firstFace.boundingBoxX2).toBe(-16); // 200 - 216 = -16 - expect(firstFace.boundingBoxY2).toBe(-1312); // 200 - 1512 = -1312 - - expect(secondFace.boundingBoxX1).toBe(84); // 300 - 216 - expect(secondFace.boundingBoxY1).toBe(-1112); // 400 - 1512 = -1112 - expect(secondFace.boundingBoxX2).toBe(184); // 400 - 216 - expect(secondFace.boundingBoxY2).toBe(-1012); // 500 - 1512 = -1012 - }); - - it('should transform unassigned faces with edits and dimensions', () => { - const unassignedFace = { - ...faceStub.noPerson1, - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const asset = { - ...assetStub.withCropEdit, - faces: [unassignedFace], - exifInfo: { - exifImageWidth: 1000, - exifImageHeight: 800, - }, - edits: [ - { - action: AssetEditAction.Crop, - parameters: { x: 50, y: 50, width: 500, height: 400 }, - }, - ], - }; - - const result = mapAsset(asset as any); - - expect(result.unassignedFaces).toBeDefined(); - expect(result.unassignedFaces).toHaveLength(1); - - // Verify that unassigned face has been transformed - const face = result.unassignedFaces![0]; - expect(face.boundingBoxX1).toBe(50); // 100 - 50 - expect(face.boundingBoxY1).toBe(50); // 100 - 50 - expect(face.boundingBoxX2).toBe(150); // 200 - 50 - expect(face.boundingBoxY2).toBe(150); // 200 - 50 - }); - - it('should handle multiple people each with multiple faces', () => { - const person1Face1 = { - ...faceStub.primaryFace1, - id: 'face-1-1', - person: personStub.withName, - personId: personStub.withName.id, - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const person1Face2 = { - ...faceStub.primaryFace1, - id: 'face-1-2', - person: personStub.withName, - personId: personStub.withName.id, - boundingBoxX1: 300, - boundingBoxY1: 300, - boundingBoxX2: 400, - boundingBoxY2: 400, - imageWidth: 1000, - imageHeight: 800, - }; - - const person2Face1 = { - ...faceStub.mergeFace1, - id: 'face-2-1', - person: personStub.mergePerson, - personId: personStub.mergePerson.id, - boundingBoxX1: 500, - boundingBoxY1: 100, - boundingBoxX2: 600, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const asset = { - ...assetStub.withCropEdit, - faces: [person1Face1, person1Face2, person2Face1], - exifInfo: { - exifImageWidth: 1000, - exifImageHeight: 800, - }, - edits: [], - }; - - const result = mapAsset(asset as any); - - expect(result.people).toBeDefined(); - expect(result.people).toHaveLength(2); - - const person1 = result.people!.find((p) => p.id === personStub.withName.id); - const person2 = result.people!.find((p) => p.id === personStub.mergePerson.id); - - expect(person1).toBeDefined(); - expect(person1!.faces).toHaveLength(2); - // No edits, so coordinates should be unchanged - expect(person1!.faces[0].boundingBoxX1).toBe(100); - expect(person1!.faces[0].boundingBoxY1).toBe(100); - expect(person1!.faces[1].boundingBoxX1).toBe(300); - expect(person1!.faces[1].boundingBoxY1).toBe(300); - - expect(person2).toBeDefined(); - expect(person2!.faces).toHaveLength(1); - expect(person2!.faces[0].boundingBoxX1).toBe(500); - expect(person2!.faces[0].boundingBoxY1).toBe(100); - }); - - it('should combine faces of the same person into a single entry', () => { - const face1 = { - ...faceStub.primaryFace1, - id: 'face-1', - person: personStub.withName, - personId: personStub.withName.id, - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const face2 = { - ...faceStub.primaryFace1, - id: 'face-2', - person: personStub.withName, - personId: personStub.withName.id, - boundingBoxX1: 300, - boundingBoxY1: 300, - boundingBoxX2: 400, - boundingBoxY2: 400, - imageWidth: 1000, - imageHeight: 800, - }; - - const asset = { - ...assetStub.withCropEdit, - faces: [face1, face2], - exifInfo: { - exifImageWidth: 1000, - exifImageHeight: 800, - }, - edits: [], - }; - - const result = mapAsset(asset as any); - - expect(result.people).toBeDefined(); - expect(result.people).toHaveLength(1); - - const person = result.people![0]; - expect(person.id).toBe(personStub.withName.id); - expect(person.faces).toHaveLength(2); - }); - }); -}); diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts deleted file mode 100644 index df02a0cdea..0000000000 --- a/server/src/dtos/asset-response.dto.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Selectable } from 'kysely'; -import { AssetFace, AssetFile, Exif, Stack, Tag, User } from 'src/database'; -import { HistoryBuilder, Property } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { ExifResponseDto, mapExif } from 'src/dtos/exif.dto'; -import { - AssetFaceWithoutPersonResponseDto, - PersonWithFacesResponseDto, - mapFacesWithoutPerson, - mapPerson, -} from 'src/dtos/person.dto'; -import { TagResponseDto, mapTag } from 'src/dtos/tag.dto'; -import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; -import { AssetStatus, AssetType, AssetVisibility } from 'src/enum'; -import { ImageDimensions } from 'src/types'; -import { getDimensions } from 'src/utils/asset.util'; -import { hexOrBufferToBase64 } from 'src/utils/bytes'; -import { mimeTypes } from 'src/utils/mime-types'; -import { ValidateEnum, ValidateUUID } from 'src/validation'; - -export class SanitizedAssetResponseDto { - @ApiProperty({ description: 'Asset ID' }) - id!: string; - @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', description: 'Asset type' }) - type!: AssetType; - @ApiProperty({ description: 'Thumbhash for thumbnail generation' }) - thumbhash!: string | null; - @ApiPropertyOptional({ description: 'Original MIME type' }) - originalMimeType?: string; - @ApiProperty({ - type: 'string', - format: 'date-time', - description: - 'The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer\'s local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months.', - example: '2024-01-15T14:30:00.000Z', - }) - localDateTime!: Date; - @ApiProperty({ description: 'Video duration (for videos)' }) - duration!: string; - @ApiPropertyOptional({ description: 'Live photo video ID' }) - livePhotoVideoId?: string | null; - @ApiProperty({ description: 'Whether asset has metadata' }) - hasMetadata!: boolean; - @ApiProperty({ description: 'Asset width' }) - width!: number | null; - @ApiProperty({ description: 'Asset height' }) - height!: number | null; -} - -export class AssetResponseDto extends SanitizedAssetResponseDto { - @ApiProperty({ - type: 'string', - format: 'date-time', - description: 'The UTC timestamp when the asset was originally uploaded to Immich.', - example: '2024-01-15T20:30:00.000Z', - }) - createdAt!: Date; - @ApiProperty({ description: 'Device asset ID' }) - deviceAssetId!: string; - @ApiProperty({ description: 'Device ID' }) - deviceId!: string; - @ApiProperty({ description: 'Owner user ID' }) - ownerId!: string; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - owner?: UserResponseDto; - @ValidateUUID({ - nullable: true, - description: 'Library ID', - history: new HistoryBuilder().added('v1').deprecated('v1'), - }) - libraryId?: string | null; - @ApiProperty({ description: 'Original file path' }) - originalPath!: string; - @ApiProperty({ description: 'Original file name' }) - originalFileName!: string; - @ApiProperty({ - type: 'string', - format: 'date-time', - description: - 'The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken.', - example: '2024-01-15T19:30:00.000Z', - }) - fileCreatedAt!: Date; - @ApiProperty({ - type: 'string', - format: 'date-time', - description: - 'The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken.', - example: '2024-01-16T10:15:00.000Z', - }) - fileModifiedAt!: Date; - @ApiProperty({ - type: 'string', - format: 'date-time', - description: - 'The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified.', - example: '2024-01-16T12:45:30.000Z', - }) - updatedAt!: Date; - @ApiProperty({ description: 'Is favorite' }) - isFavorite!: boolean; - @ApiProperty({ description: 'Is archived' }) - isArchived!: boolean; - @ApiProperty({ description: 'Is trashed' }) - isTrashed!: boolean; - @ApiProperty({ description: 'Is offline' }) - isOffline!: boolean; - @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', description: 'Asset visibility' }) - visibility!: AssetVisibility; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - exifInfo?: ExifResponseDto; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - tags?: TagResponseDto[]; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - people?: PersonWithFacesResponseDto[]; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - unassignedFaces?: AssetFaceWithoutPersonResponseDto[]; - @ApiProperty({ description: 'Base64 encoded SHA1 hash' }) - checksum!: string; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - stack?: AssetStackResponseDto | null; - @ApiPropertyOptional({ description: 'Duplicate group ID' }) - duplicateId?: string | null; - - @Property({ description: 'Is resized', history: new HistoryBuilder().added('v1').deprecated('v1.113.0') }) - resized?: boolean; - @Property({ description: 'Is edited', history: new HistoryBuilder().added('v2.5.0').beta('v2.5.0') }) - isEdited!: boolean; -} - -export type MapAsset = { - createdAt: Date; - updatedAt: Date; - deletedAt: Date | null; - id: string; - updateId: string; - status: AssetStatus; - checksum: Buffer; - deviceAssetId: string; - deviceId: string; - duplicateId: string | null; - duration: string | null; - edits?: AssetEditActionItem[]; - encodedVideoPath: string | null; - exifInfo?: Selectable | null; - faces?: AssetFace[]; - fileCreatedAt: Date; - fileModifiedAt: Date; - files?: AssetFile[]; - isExternal: boolean; - isFavorite: boolean; - isOffline: boolean; - visibility: AssetVisibility; - libraryId: string | null; - livePhotoVideoId: string | null; - localDateTime: Date; - originalFileName: string; - originalPath: string; - owner?: User | null; - ownerId: string; - stack?: Stack | null; - stackId: string | null; - tags?: Tag[]; - thumbhash: Buffer | null; - type: AssetType; - width: number | null; - height: number | null; - isEdited: boolean; -}; - -export class AssetStackResponseDto { - @ApiProperty({ description: 'Stack ID' }) - id!: string; - - @ApiProperty({ description: 'Primary asset ID' }) - primaryAssetId!: string; - - @ApiProperty({ type: 'integer', description: 'Number of assets in stack' }) - assetCount!: number; -} - -export type AssetMapOptions = { - stripMetadata?: boolean; - withStack?: boolean; - auth?: AuthDto; -}; - -const peopleWithFaces = ( - faces?: AssetFace[], - edits?: AssetEditActionItem[], - assetDimensions?: ImageDimensions, -): PersonWithFacesResponseDto[] => { - if (!faces) { - return []; - } - - const peopleFaces: Map = new Map(); - - for (const face of faces) { - if (!face.person) { - continue; - } - - if (!peopleFaces.has(face.person.id)) { - peopleFaces.set(face.person.id, { ...mapPerson(face.person), faces: [] }); - } - const mappedFace = mapFacesWithoutPerson(face, edits, assetDimensions); - peopleFaces.get(face.person.id)!.faces.push(mappedFace); - } - - return [...peopleFaces.values()]; -}; - -const mapStack = (entity: { stack?: Stack | null }) => { - if (!entity.stack) { - return null; - } - - return { - id: entity.stack.id, - primaryAssetId: entity.stack.primaryAssetId, - assetCount: entity.stack.assetCount ?? entity.stack.assets.length + 1, - }; -}; - -export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): AssetResponseDto { - const { stripMetadata = false, withStack = false } = options; - - if (stripMetadata) { - const sanitizedAssetResponse: SanitizedAssetResponseDto = { - id: entity.id, - type: entity.type, - originalMimeType: mimeTypes.lookup(entity.originalFileName), - thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null, - localDateTime: entity.localDateTime, - duration: entity.duration ?? '0:00:00.00000', - livePhotoVideoId: entity.livePhotoVideoId, - hasMetadata: false, - width: entity.width, - height: entity.height, - }; - return sanitizedAssetResponse as AssetResponseDto; - } - - const assetDimensions = entity.exifInfo ? getDimensions(entity.exifInfo) : undefined; - - return { - id: entity.id, - createdAt: entity.createdAt, - deviceAssetId: entity.deviceAssetId, - ownerId: entity.ownerId, - owner: entity.owner ? mapUser(entity.owner) : undefined, - deviceId: entity.deviceId, - libraryId: entity.libraryId, - type: entity.type, - originalPath: entity.originalPath, - originalFileName: entity.originalFileName, - originalMimeType: mimeTypes.lookup(entity.originalFileName), - thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null, - fileCreatedAt: entity.fileCreatedAt, - fileModifiedAt: entity.fileModifiedAt, - localDateTime: entity.localDateTime, - updatedAt: entity.updatedAt, - isFavorite: options.auth?.user.id === entity.ownerId && entity.isFavorite, - isArchived: entity.visibility === AssetVisibility.Archive, - isTrashed: !!entity.deletedAt, - visibility: entity.visibility, - duration: entity.duration ?? '0:00:00.00000', - exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, - livePhotoVideoId: entity.livePhotoVideoId, - tags: entity.tags?.map((tag) => mapTag(tag)), - people: peopleWithFaces(entity.faces, entity.edits, assetDimensions), - unassignedFaces: entity.faces - ?.filter((face) => !face.person) - .map((a) => mapFacesWithoutPerson(a, entity.edits, assetDimensions)), - checksum: hexOrBufferToBase64(entity.checksum)!, - stack: withStack ? mapStack(entity) : undefined, - isOffline: entity.isOffline, - hasMetadata: true, - duplicateId: entity.duplicateId, - resized: true, - width: entity.width, - height: entity.height, - isEdited: entity.isEdited, - }; -} diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts deleted file mode 100644 index 00ea46f789..0000000000 --- a/server/src/dtos/asset.dto.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsArray, - IsDateString, - IsInt, - IsLatitude, - IsLongitude, - IsNotEmpty, - IsObject, - IsPositive, - IsString, - IsTimeZone, - Max, - Min, - ValidateIf, - ValidateNested, -} from 'class-validator'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AssetType, AssetVisibility } from 'src/enum'; -import { AssetStats } from 'src/repositories/asset.repository'; -import { IsNotSiblingOf, Optional, ValidateBoolean, ValidateEnum, ValidateString, ValidateUUID } from 'src/validation'; - -export class DeviceIdDto { - @ApiProperty({ description: 'Device ID' }) - @IsNotEmpty() - @IsString() - deviceId!: string; -} - -const hasGPS = (o: { latitude: undefined; longitude: undefined }) => - o.latitude !== undefined || o.longitude !== undefined; -const ValidateGPS = () => ValidateIf(hasGPS); - -export class UpdateAssetBase { - @ValidateBoolean({ optional: true, description: 'Mark as favorite' }) - isFavorite?: boolean; - - @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true, description: 'Asset visibility' }) - visibility?: AssetVisibility; - - @ApiProperty({ description: 'Original date and time' }) - @Optional() - @IsDateString() - dateTimeOriginal?: string; - - @ApiProperty({ description: 'Latitude coordinate' }) - @ValidateGPS() - @IsLatitude() - @IsNotEmpty() - latitude?: number; - - @ApiProperty({ description: 'Longitude coordinate' }) - @ValidateGPS() - @IsLongitude() - @IsNotEmpty() - longitude?: number; - - @ApiProperty({ description: 'Rating' }) - @Optional() - @IsInt() - @Max(5) - @Min(-1) - rating?: number; - - @ApiProperty({ description: 'Asset description' }) - @Optional() - @IsString() - description?: string; -} - -export class AssetBulkUpdateDto extends UpdateAssetBase { - @ValidateUUID({ each: true, description: 'Asset IDs to update' }) - ids!: string[]; - - @ValidateString({ optional: true, nullable: true, description: 'Duplicate ID' }) - duplicateId?: string | null; - - @ApiProperty({ description: 'Relative time offset in seconds' }) - @IsNotSiblingOf(['dateTimeOriginal']) - @Optional() - @IsInt() - dateTimeRelative?: number; - - @ApiProperty({ description: 'Time zone (IANA timezone)' }) - @IsNotSiblingOf(['dateTimeOriginal']) - @IsTimeZone() - @Optional() - timeZone?: string; -} - -export class UpdateAssetDto extends UpdateAssetBase { - @ValidateUUID({ optional: true, nullable: true, description: 'Live photo video ID' }) - livePhotoVideoId?: string | null; -} - -export class RandomAssetsDto { - @ApiProperty({ description: 'Number of random assets to return' }) - @Optional() - @IsInt() - @IsPositive() - @Type(() => Number) - count?: number; -} - -export class AssetBulkDeleteDto extends BulkIdsDto { - @ValidateBoolean({ optional: true, description: 'Force delete even if in use' }) - force?: boolean; -} - -export class AssetIdsDto { - @ValidateUUID({ each: true, description: 'Asset IDs' }) - assetIds!: string[]; -} - -export enum AssetJobName { - REFRESH_FACES = 'refresh-faces', - REFRESH_METADATA = 'refresh-metadata', - REGENERATE_THUMBNAIL = 'regenerate-thumbnail', - TRANSCODE_VIDEO = 'transcode-video', -} - -export class AssetJobsDto extends AssetIdsDto { - @ValidateEnum({ enum: AssetJobName, name: 'AssetJobName', description: 'Job name' }) - name!: AssetJobName; -} - -export class AssetStatsDto { - @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', description: 'Filter by visibility', optional: true }) - visibility?: AssetVisibility; - - @ValidateBoolean({ optional: true, description: 'Filter by favorite status' }) - isFavorite?: boolean; - - @ValidateBoolean({ optional: true, description: 'Filter by trash status' }) - isTrashed?: boolean; -} - -export class AssetStatsResponseDto { - @ApiProperty({ description: 'Number of images', type: 'integer' }) - images!: number; - - @ApiProperty({ description: 'Number of videos', type: 'integer' }) - videos!: number; - - @ApiProperty({ description: 'Total number of assets', type: 'integer' }) - total!: number; -} - -export class AssetMetadataRouteParams { - @ValidateUUID({ description: 'Asset ID' }) - id!: string; - - @ValidateString({ description: 'Metadata key' }) - key!: string; -} - -export class AssetMetadataUpsertDto { - @ApiProperty({ description: 'Metadata items to upsert' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => AssetMetadataUpsertItemDto) - items!: AssetMetadataUpsertItemDto[]; -} - -export class AssetMetadataUpsertItemDto { - @ValidateString({ description: 'Metadata key' }) - key!: string; - - @ApiProperty({ description: 'Metadata value (object)' }) - @IsObject() - value!: object; -} - -export class AssetMetadataBulkUpsertDto { - @ApiProperty({ description: 'Metadata items to upsert' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => AssetMetadataBulkUpsertItemDto) - items!: AssetMetadataBulkUpsertItemDto[]; -} - -export class AssetMetadataBulkUpsertItemDto { - @ValidateUUID({ description: 'Asset ID' }) - assetId!: string; - - @ValidateString({ description: 'Metadata key' }) - key!: string; - - @ApiProperty({ description: 'Metadata value (object)' }) - @IsObject() - value!: object; -} - -export class AssetMetadataBulkDeleteDto { - @ApiProperty({ description: 'Metadata items to delete' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => AssetMetadataBulkDeleteItemDto) - items!: AssetMetadataBulkDeleteItemDto[]; -} - -export class AssetMetadataBulkDeleteItemDto { - @ValidateUUID({ description: 'Asset ID' }) - assetId!: string; - - @ValidateString({ description: 'Metadata key' }) - key!: string; -} - -export class AssetMetadataResponseDto { - @ValidateString({ description: 'Metadata key' }) - key!: string; - - @ApiProperty({ description: 'Metadata value (object)' }) - value!: object; - - @ApiProperty({ description: 'Last update date' }) - updatedAt!: Date; -} - -export class AssetMetadataBulkResponseDto extends AssetMetadataResponseDto { - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; -} - -export class AssetCopyDto { - @ValidateUUID({ description: 'Source asset ID' }) - sourceId!: string; - - @ValidateUUID({ description: 'Target asset ID' }) - targetId!: string; - - @ValidateBoolean({ optional: true, description: 'Copy shared links', default: true }) - sharedLinks?: boolean; - - @ValidateBoolean({ optional: true, description: 'Copy album associations', default: true }) - albums?: boolean; - - @ValidateBoolean({ optional: true, description: 'Copy sidecar file', default: true }) - sidecar?: boolean; - - @ValidateBoolean({ optional: true, description: 'Copy stack association', default: true }) - stack?: boolean; - - @ValidateBoolean({ optional: true, description: 'Copy favorite status', default: true }) - favorite?: boolean; -} - -export class AssetDownloadOriginalDto { - @ValidateBoolean({ optional: true, description: 'Return edited asset if available', default: false }) - edited?: boolean; -} - -export const mapStats = (stats: AssetStats): AssetStatsResponseDto => { - return { - images: stats[AssetType.Image], - videos: stats[AssetType.Video], - total: Object.values(stats).reduce((total, value) => total + value, 0), - }; -}; diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index 3df82f4ef4..7865c0e19c 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -1,10 +1,9 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; -import { AuthApiKey, AuthSession, AuthSharedLink, AuthUser, UserAdmin } from 'src/database'; -import { ImmichCookie, UserMetadataKey } from 'src/enum'; -import { UserMetadataItem } from 'src/types'; -import { Optional, PinCode, toEmail, ValidateBoolean } from 'src/validation'; +import { AuthApiKey, AuthSession, AuthUser, UserAdmin } from 'src/database'; +import { ImmichCookie } from 'src/enum'; +import { toEmail, ValidateBoolean } from 'src/validation'; export type CookieResponse = { isSecure: boolean; @@ -17,8 +16,7 @@ export class AuthDto { @ApiPropertyOptional({ description: 'API key (if authenticated via API key)' }) apiKey?: AuthApiKey; - @ApiPropertyOptional({ description: 'Shared link (if authenticated via shared link)' }) - sharedLink?: AuthSharedLink; + @ApiPropertyOptional({ description: 'Session (if authenticated via session)' }) session?: AuthSession; } @@ -45,30 +43,20 @@ export class LoginResponseDto { userEmail!: string; @ApiProperty({ description: 'User name' }) name!: string; - @ApiProperty({ description: 'Profile image path' }) - profileImagePath!: string; @ApiProperty({ description: 'Is admin user' }) isAdmin!: boolean; @ApiProperty({ description: 'Should change password' }) shouldChangePassword!: boolean; - @ApiProperty({ description: 'Is onboarded' }) - isOnboarded!: boolean; } export function mapLoginResponse(entity: UserAdmin, accessToken: string): LoginResponseDto { - const onboardingMetadata = entity.metadata.find( - (item): item is UserMetadataItem => item.key === UserMetadataKey.Onboarding, - )?.value; - return { accessToken, userId: entity.id, userEmail: entity.email, name: entity.name, isAdmin: entity.isAdmin, - profileImagePath: entity.profileImagePath, shouldChangePassword: entity.shouldChangePassword, - isOnboarded: onboardingMetadata?.isOnboarded ?? false, }; } @@ -102,85 +90,12 @@ export class ChangePasswordDto { invalidateSessions?: boolean; } -export class PinCodeSetupDto { - @ApiProperty({ description: 'PIN code (4-6 digits)' }) - @PinCode() - pinCode!: string; -} - -export class PinCodeResetDto { - @ApiPropertyOptional({ description: 'New PIN code (4-6 digits)' }) - @PinCode({ optional: true }) - pinCode?: string; - - @ApiPropertyOptional({ description: 'User password (required if PIN code is not provided)' }) - @Optional() - @IsString() - @IsNotEmpty() - password?: string; -} - -export class SessionUnlockDto extends PinCodeResetDto {} - -export class PinCodeChangeDto extends PinCodeResetDto { - @ApiProperty({ description: 'New PIN code (4-6 digits)' }) - @PinCode() - newPinCode!: string; -} - export class ValidateAccessTokenResponseDto { @ApiProperty({ description: 'Authentication status' }) authStatus!: boolean; } -export class OAuthCallbackDto { - @ApiProperty({ description: 'OAuth callback URL' }) - @IsNotEmpty() - @IsString() - url!: string; - - @ApiPropertyOptional({ description: 'OAuth state parameter' }) - @Optional() - @IsString() - state?: string; - - @ApiPropertyOptional({ description: 'OAuth code verifier (PKCE)' }) - @Optional() - @IsString() - codeVerifier?: string; -} - -export class OAuthConfigDto { - @ApiProperty({ description: 'OAuth redirect URI' }) - @IsNotEmpty() - @IsString() - redirectUri!: string; - - @ApiPropertyOptional({ description: 'OAuth state parameter' }) - @Optional() - @IsString() - state?: string; - - @ApiPropertyOptional({ description: 'OAuth code challenge (PKCE)' }) - @Optional() - @IsString() - codeChallenge?: string; -} - -export class OAuthAuthorizeResponseDto { - @ApiProperty({ description: 'OAuth authorization URL' }) - url!: string; -} - export class AuthStatusResponseDto { - @ApiProperty({ description: 'Has PIN code set' }) - pinCode!: boolean; @ApiProperty({ description: 'Has password set' }) password!: boolean; - @ApiProperty({ description: 'Is elevated session' }) - isElevated!: boolean; - @ApiPropertyOptional({ description: 'Session expiration date' }) - expiresAt?: string; - @ApiPropertyOptional({ description: 'PIN expiration date' }) - pinExpiresAt?: string; } diff --git a/server/src/dtos/database-backup.dto.ts b/server/src/dtos/database-backup.dto.ts deleted file mode 100644 index dc06cdc6ec..0000000000 --- a/server/src/dtos/database-backup.dto.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; - -export class DatabaseBackupDto { - filename!: string; - filesize!: number; -} - -export class DatabaseBackupListResponseDto { - backups!: DatabaseBackupDto[]; -} - -export class DatabaseBackupUploadDto { - @ApiProperty({ type: 'string', format: 'binary', required: false }) - file?: any; -} - -export class DatabaseBackupDeleteDto { - @IsString({ each: true }) - backups!: string[]; -} diff --git a/server/src/dtos/download.dto.ts b/server/src/dtos/download.dto.ts deleted file mode 100644 index 2f877e3c0b..0000000000 --- a/server/src/dtos/download.dto.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsInt, IsPositive } from 'class-validator'; -import { Optional, ValidateUUID } from 'src/validation'; - -export class DownloadInfoDto { - @ValidateUUID({ each: true, optional: true, description: 'Asset IDs to download' }) - assetIds?: string[]; - - @ValidateUUID({ optional: true, description: 'Album ID to download' }) - albumId?: string; - - @ValidateUUID({ optional: true, description: 'User ID to download assets from' }) - userId?: string; - - @ApiPropertyOptional({ type: 'integer', description: 'Archive size limit in bytes' }) - @IsInt() - @IsPositive() - @Optional() - archiveSize?: number; -} - -export class DownloadResponseDto { - @ApiProperty({ type: 'integer', description: 'Total size in bytes' }) - totalSize!: number; - @ApiProperty({ description: 'Archive information' }) - archives!: DownloadArchiveInfo[]; -} - -export class DownloadArchiveInfo { - @ApiProperty({ type: 'integer', description: 'Archive size in bytes' }) - size!: number; - @ApiProperty({ description: 'Asset IDs in this archive' }) - assetIds!: string[]; -} diff --git a/server/src/dtos/duplicate.dto.ts b/server/src/dtos/duplicate.dto.ts deleted file mode 100644 index 9cd9147ec5..0000000000 --- a/server/src/dtos/duplicate.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; - -export class DuplicateResponseDto { - @ApiProperty({ description: 'Duplicate group ID' }) - duplicateId!: string; - @ApiProperty({ description: 'Duplicate assets' }) - assets!: AssetResponseDto[]; -} diff --git a/server/src/dtos/editing.dto.ts b/server/src/dtos/editing.dto.ts deleted file mode 100644 index 8bb1eef47b..0000000000 --- a/server/src/dtos/editing.dto.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger'; -import { ClassConstructor, plainToInstance, Transform, Type } from 'class-transformer'; -import { ArrayMinSize, IsEnum, IsInt, Min, ValidateNested } from 'class-validator'; -import { IsAxisAlignedRotation, IsUniqueEditActions, ValidateUUID } from 'src/validation'; - -export enum AssetEditAction { - Crop = 'crop', - Rotate = 'rotate', - Mirror = 'mirror', -} - -export enum MirrorAxis { - Horizontal = 'horizontal', - Vertical = 'vertical', -} - -export class CropParameters { - @IsInt() - @Min(0) - @ApiProperty({ description: 'Top-Left X coordinate of crop' }) - x!: number; - - @IsInt() - @Min(0) - @ApiProperty({ description: 'Top-Left Y coordinate of crop' }) - y!: number; - - @IsInt() - @Min(1) - @ApiProperty({ description: 'Width of the crop' }) - width!: number; - - @IsInt() - @Min(1) - @ApiProperty({ description: 'Height of the crop' }) - height!: number; -} - -export class RotateParameters { - @IsAxisAlignedRotation() - @ApiProperty({ description: 'Rotation angle in degrees' }) - angle!: number; -} - -export class MirrorParameters { - @IsEnum(MirrorAxis) - @ApiProperty({ enum: MirrorAxis, enumName: 'MirrorAxis', description: 'Axis to mirror along' }) - axis!: MirrorAxis; -} - -class AssetEditActionBase { - @IsEnum(AssetEditAction) - @ApiProperty({ enum: AssetEditAction, enumName: 'AssetEditAction', description: 'Type of edit action to perform' }) - action!: AssetEditAction; -} - -export class AssetEditActionCrop extends AssetEditActionBase { - @ValidateNested() - @Type(() => CropParameters) - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - parameters!: CropParameters; -} - -export class AssetEditActionRotate extends AssetEditActionBase { - @ValidateNested() - @Type(() => RotateParameters) - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - parameters!: RotateParameters; -} - -export class AssetEditActionMirror extends AssetEditActionBase { - @ValidateNested() - @Type(() => MirrorParameters) - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - parameters!: MirrorParameters; -} - -export type AssetEditActionItem = - | { - action: AssetEditAction.Crop; - parameters: CropParameters; - } - | { - action: AssetEditAction.Rotate; - parameters: RotateParameters; - } - | { - action: AssetEditAction.Mirror; - parameters: MirrorParameters; - }; - -export type AssetEditActionParameter = { - [AssetEditAction.Crop]: CropParameters; - [AssetEditAction.Rotate]: RotateParameters; - [AssetEditAction.Mirror]: MirrorParameters; -}; - -type AssetEditActions = AssetEditActionCrop | AssetEditActionRotate | AssetEditActionMirror; -const actionToClass: Record> = { - [AssetEditAction.Crop]: AssetEditActionCrop, - [AssetEditAction.Rotate]: AssetEditActionRotate, - [AssetEditAction.Mirror]: AssetEditActionMirror, -} as const; - -const getActionClass = (item: { action: AssetEditAction }): ClassConstructor => - actionToClass[item.action]; - -@ApiExtraModels(AssetEditActionRotate, AssetEditActionMirror, AssetEditActionCrop) -export class AssetEditActionListDto { - /** list of edits */ - @ArrayMinSize(1) - @IsUniqueEditActions() - @ValidateNested({ each: true }) - @Transform(({ value: edits }) => - Array.isArray(edits) ? edits.map((item) => plainToInstance(getActionClass(item), item)) : edits, - ) - @ApiProperty({ - anyOf: Object.values(actionToClass).map((target) => ({ $ref: getSchemaPath(target) })), - description: 'List of edit actions to apply (crop, rotate, or mirror)', - }) - edits!: AssetEditActionItem[]; -} - -export class AssetEditsDto extends AssetEditActionListDto { - @ValidateUUID({ description: 'Asset ID to apply edits to' }) - assetId!: string; -} diff --git a/server/src/dtos/env.dto.ts b/server/src/dtos/env.dto.ts deleted file mode 100644 index e088a33413..0000000000 --- a/server/src/dtos/env.dto.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { Transform, Type } from 'class-transformer'; -import { IsEnum, IsInt, IsString, Matches } from 'class-validator'; -import { DatabaseSslMode, ImmichEnvironment, LogFormat, LogLevel } from 'src/enum'; -import { IsIPRange, Optional, ValidateBoolean } from 'src/validation'; - -export class EnvDto { - @IsInt() - @Optional() - @Type(() => Number) - IMMICH_API_METRICS_PORT?: number; - - @IsString() - @Optional() - IMMICH_BUILD_DATA?: string; - - @IsString() - @Optional() - IMMICH_BUILD?: string; - - @IsString() - @Optional() - IMMICH_BUILD_URL?: string; - - @IsString() - @Optional() - IMMICH_BUILD_IMAGE?: string; - - @IsString() - @Optional() - IMMICH_BUILD_IMAGE_URL?: string; - - @IsString() - @Optional() - IMMICH_CONFIG_FILE?: string; - - @IsEnum(ImmichEnvironment) - @Optional() - IMMICH_ENV?: ImmichEnvironment; - - @IsString() - @Optional() - IMMICH_HOST?: string; - - @ValidateBoolean({ optional: true }) - IMMICH_IGNORE_MOUNT_CHECK_ERRORS?: boolean; - - @IsEnum(LogLevel) - @Optional() - IMMICH_LOG_LEVEL?: LogLevel; - - @IsEnum(LogFormat) - @Optional() - IMMICH_LOG_FORMAT?: LogFormat; - - @Optional() - @Matches(/^\//, { message: 'IMMICH_MEDIA_LOCATION must be an absolute path' }) - IMMICH_MEDIA_LOCATION?: string; - - @IsInt() - @Optional() - @Type(() => Number) - IMMICH_MICROSERVICES_METRICS_PORT?: number; - - @ValidateBoolean({ optional: true }) - IMMICH_ALLOW_EXTERNAL_PLUGINS?: boolean; - - @Optional() - @Matches(/^\//, { message: 'IMMICH_PLUGINS_INSTALL_FOLDER must be an absolute path' }) - IMMICH_PLUGINS_INSTALL_FOLDER?: string; - - @IsInt() - @Optional() - @Type(() => Number) - IMMICH_PORT?: number; - - @IsString() - @Optional() - IMMICH_REPOSITORY?: string; - - @IsString() - @Optional() - IMMICH_REPOSITORY_URL?: string; - - @IsString() - @Optional() - IMMICH_SOURCE_REF?: string; - - @IsString() - @Optional() - IMMICH_SOURCE_COMMIT?: string; - - @IsString() - @Optional() - IMMICH_SOURCE_URL?: string; - - @IsString() - @Optional() - IMMICH_TELEMETRY_INCLUDE?: string; - - @IsString() - @Optional() - IMMICH_TELEMETRY_EXCLUDE?: string; - - @IsString() - @Optional() - IMMICH_THIRD_PARTY_SOURCE_URL?: string; - - @IsString() - @Optional() - IMMICH_THIRD_PARTY_BUG_FEATURE_URL?: string; - - @IsString() - @Optional() - IMMICH_THIRD_PARTY_DOCUMENTATION_URL?: string; - - @IsString() - @Optional() - IMMICH_THIRD_PARTY_SUPPORT_URL?: string; - - @ValidateBoolean({ optional: true }) - IMMICH_ALLOW_SETUP?: boolean; - - @IsIPRange({ requireCIDR: false }, { each: true }) - @Transform(({ value }) => - value && typeof value === 'string' - ? value - .split(',') - .map((value) => value.trim()) - .filter(Boolean) - : value, - ) - @Optional() - IMMICH_TRUSTED_PROXIES?: string[]; - - @IsString() - @Optional() - IMMICH_WORKERS_INCLUDE?: string; - - @IsString() - @Optional() - IMMICH_WORKERS_EXCLUDE?: string; - - @IsString() - @Optional() - DB_DATABASE_NAME?: string; - - @IsString() - @Optional() - DB_HOSTNAME?: string; - - @IsString() - @Optional() - DB_PASSWORD?: string; - - @IsInt() - @Optional() - @Type(() => Number) - DB_PORT?: number; - - @ValidateBoolean({ optional: true }) - DB_SKIP_MIGRATIONS?: boolean; - - @IsEnum(DatabaseSslMode) - @Optional() - DB_SSL_MODE?: DatabaseSslMode; - - @IsString() - @Optional() - DB_URL?: string; - - @IsString() - @Optional() - DB_USERNAME?: string; - - @IsEnum(['pgvector', 'pgvecto.rs', 'vectorchord']) - @Optional() - DB_VECTOR_EXTENSION?: 'pgvector' | 'pgvecto.rs' | 'vectorchord'; - - @IsString() - @Optional() - NO_COLOR?: string; - - @IsString() - @Optional() - REDIS_HOSTNAME?: string; - - @IsInt() - @Optional() - @Type(() => Number) - REDIS_PORT?: number; - - @IsInt() - @Optional() - @Type(() => Number) - REDIS_DBINDEX?: number; - - @IsString() - @Optional() - REDIS_USERNAME?: string; - - @IsString() - @Optional() - REDIS_PASSWORD?: string; - - @IsString() - @Optional() - REDIS_SOCKET?: string; - - @IsString() - @Optional() - REDIS_URL?: string; -} diff --git a/server/src/dtos/exif.dto.ts b/server/src/dtos/exif.dto.ts deleted file mode 100644 index 0052b95b6e..0000000000 --- a/server/src/dtos/exif.dto.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Exif } from 'src/database'; - -export class ExifResponseDto { - @ApiPropertyOptional({ description: 'Camera make' }) - make?: string | null = null; - @ApiPropertyOptional({ description: 'Camera model' }) - model?: string | null = null; - @ApiPropertyOptional({ type: 'number', description: 'Image width in pixels' }) - exifImageWidth?: number | null = null; - @ApiPropertyOptional({ type: 'number', description: 'Image height in pixels' }) - exifImageHeight?: number | null = null; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'File size in bytes' }) - fileSizeInByte?: number | null = null; - @ApiPropertyOptional({ description: 'Image orientation' }) - orientation?: string | null = null; - @ApiPropertyOptional({ description: 'Original date/time', format: 'date-time' }) - dateTimeOriginal?: Date | null = null; - @ApiPropertyOptional({ description: 'Modification date/time', format: 'date-time' }) - modifyDate?: Date | null = null; - @ApiPropertyOptional({ description: 'Time zone' }) - timeZone?: string | null = null; - @ApiPropertyOptional({ description: 'Lens model' }) - lensModel?: string | null = null; - @ApiPropertyOptional({ type: 'number', description: 'F-number (aperture)' }) - fNumber?: number | null = null; - @ApiPropertyOptional({ type: 'number', description: 'Focal length in mm' }) - focalLength?: number | null = null; - @ApiPropertyOptional({ type: 'number', description: 'ISO sensitivity' }) - iso?: number | null = null; - @ApiPropertyOptional({ description: 'Exposure time' }) - exposureTime?: string | null = null; - @ApiPropertyOptional({ type: 'number', description: 'GPS latitude' }) - latitude?: number | null = null; - @ApiPropertyOptional({ type: 'number', description: 'GPS longitude' }) - longitude?: number | null = null; - @ApiPropertyOptional({ description: 'City name' }) - city?: string | null = null; - @ApiPropertyOptional({ description: 'State/province name' }) - state?: string | null = null; - @ApiPropertyOptional({ description: 'Country name' }) - country?: string | null = null; - @ApiPropertyOptional({ description: 'Image description' }) - description?: string | null = null; - @ApiPropertyOptional({ description: 'Projection type' }) - projectionType?: string | null = null; - @ApiPropertyOptional({ type: 'number', description: 'Rating' }) - rating?: number | null = null; -} - -export function mapExif(entity: Exif): ExifResponseDto { - return { - make: entity.make, - model: entity.model, - exifImageWidth: entity.exifImageWidth, - exifImageHeight: entity.exifImageHeight, - fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null, - orientation: entity.orientation, - dateTimeOriginal: entity.dateTimeOriginal, - modifyDate: entity.modifyDate, - timeZone: entity.timeZone, - lensModel: entity.lensModel, - fNumber: entity.fNumber, - focalLength: entity.focalLength, - iso: entity.iso, - exposureTime: entity.exposureTime, - latitude: entity.latitude, - longitude: entity.longitude, - city: entity.city, - state: entity.state, - country: entity.country, - description: entity.description, - projectionType: entity.projectionType, - rating: entity.rating, - }; -} - -export function mapSanitizedExif(entity: Exif): ExifResponseDto { - return { - fileSizeInByte: entity.fileSizeInByte ? Number.parseInt(entity.fileSizeInByte.toString()) : null, - orientation: entity.orientation, - dateTimeOriginal: entity.dateTimeOriginal, - timeZone: entity.timeZone, - projectionType: entity.projectionType, - exifImageWidth: entity.exifImageWidth, - exifImageHeight: entity.exifImageHeight, - rating: entity.rating, - }; -} diff --git a/server/src/dtos/job.dto.ts b/server/src/dtos/job.dto.ts deleted file mode 100644 index ef34a41720..0000000000 --- a/server/src/dtos/job.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ManualJobName } from 'src/enum'; -import { ValidateEnum } from 'src/validation'; - -export class JobCreateDto { - @ValidateEnum({ enum: ManualJobName, name: 'ManualJobName', description: 'Job name' }) - name!: ManualJobName; -} diff --git a/server/src/dtos/library.dto.ts b/server/src/dtos/library.dto.ts deleted file mode 100644 index 3f71b8a0ed..0000000000 --- a/server/src/dtos/library.dto.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ArrayMaxSize, ArrayUnique, IsNotEmpty, IsString } from 'class-validator'; -import { Library } from 'src/database'; -import { Optional, ValidateUUID } from 'src/validation'; - -export class CreateLibraryDto { - @ValidateUUID({ description: 'Owner user ID' }) - ownerId!: string; - - @ApiPropertyOptional({ description: 'Library name' }) - @IsString() - @Optional() - @IsNotEmpty() - name?: string; - - @ApiPropertyOptional({ description: 'Import paths (max 128)' }) - @Optional() - @IsString({ each: true }) - @IsNotEmpty({ each: true }) - @ArrayUnique() - @ArrayMaxSize(128) - importPaths?: string[]; - - @ApiPropertyOptional({ description: 'Exclusion patterns (max 128)' }) - @Optional() - @IsString({ each: true }) - @IsNotEmpty({ each: true }) - @ArrayUnique() - @ArrayMaxSize(128) - exclusionPatterns?: string[]; -} - -export class UpdateLibraryDto { - @ApiPropertyOptional({ description: 'Library name' }) - @Optional() - @IsString() - @IsNotEmpty() - name?: string; - - @ApiPropertyOptional({ description: 'Import paths (max 128)' }) - @Optional() - @IsString({ each: true }) - @IsNotEmpty({ each: true }) - @ArrayUnique() - @ArrayMaxSize(128) - importPaths?: string[]; - - @ApiPropertyOptional({ description: 'Exclusion patterns (max 128)' }) - @Optional() - @IsNotEmpty({ each: true }) - @IsString({ each: true }) - @ArrayUnique() - @ArrayMaxSize(128) - exclusionPatterns?: string[]; -} - -export interface CrawlOptionsDto { - pathsToCrawl: string[]; - includeHidden?: boolean; - exclusionPatterns?: string[]; -} - -export interface WalkOptionsDto extends CrawlOptionsDto { - take: number; -} - -export class ValidateLibraryDto { - @ApiPropertyOptional({ description: 'Import paths to validate (max 128)' }) - @Optional() - @IsString({ each: true }) - @IsNotEmpty({ each: true }) - @ArrayUnique() - @ArrayMaxSize(128) - importPaths?: string[]; - - @ApiPropertyOptional({ description: 'Exclusion patterns (max 128)' }) - @Optional() - @IsNotEmpty({ each: true }) - @IsString({ each: true }) - @ArrayUnique() - @ArrayMaxSize(128) - exclusionPatterns?: string[]; -} - -export class ValidateLibraryResponseDto { - @ApiPropertyOptional({ description: 'Validation results for import paths' }) - importPaths?: ValidateLibraryImportPathResponseDto[]; -} - -export class ValidateLibraryImportPathResponseDto { - @ApiProperty({ description: 'Import path' }) - importPath!: string; - @ApiProperty({ description: 'Is valid' }) - isValid: boolean = false; - @ApiPropertyOptional({ description: 'Validation message' }) - message?: string; -} - -export class LibrarySearchDto { - @ValidateUUID({ optional: true, description: 'Filter by user ID' }) - userId?: string; -} - -export class LibraryResponseDto { - @ApiProperty({ description: 'Library ID' }) - id!: string; - @ApiProperty({ description: 'Owner user ID' }) - ownerId!: string; - @ApiProperty({ description: 'Library name' }) - name!: string; - - @ApiProperty({ type: 'integer', description: 'Number of assets' }) - assetCount!: number; - - @ApiProperty({ description: 'Import paths' }) - importPaths!: string[]; - - @ApiProperty({ description: 'Exclusion patterns' }) - exclusionPatterns!: string[]; - - @ApiProperty({ description: 'Creation date' }) - createdAt!: Date; - @ApiProperty({ description: 'Last update date' }) - updatedAt!: Date; - @ApiProperty({ description: 'Last refresh date' }) - refreshedAt!: Date | null; -} - -export class LibraryStatsResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of photos' }) - photos = 0; - - @ApiProperty({ type: 'integer', description: 'Number of videos' }) - videos = 0; - - @ApiProperty({ type: 'integer', description: 'Total number of assets' }) - total = 0; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage in bytes' }) - usage = 0; -} - -export function mapLibrary(entity: Library): LibraryResponseDto { - let assetCount = 0; - if (entity.assets) { - assetCount = entity.assets.length; - } - return { - id: entity.id, - ownerId: entity.ownerId, - name: entity.name, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - refreshedAt: entity.refreshedAt, - assetCount, - importPaths: entity.importPaths, - exclusionPatterns: entity.exclusionPatterns, - }; -} diff --git a/server/src/dtos/license.dto.ts b/server/src/dtos/license.dto.ts deleted file mode 100644 index 14232940b6..0000000000 --- a/server/src/dtos/license.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, Matches } from 'class-validator'; - -export class LicenseKeyDto { - @ApiProperty({ description: 'License key (format: IM(SV|CL)(-XXXX){8})' }) - @IsString() - @IsNotEmpty() - @Matches(/IM(SV|CL)(-[\dA-Za-z]{4}){8}/) - licenseKey!: string; - - @ApiProperty({ description: 'Activation key' }) - @IsString() - @IsNotEmpty() - activationKey!: string; -} - -export class LicenseResponseDto extends LicenseKeyDto { - @ApiProperty({ description: 'Activation date' }) - activatedAt!: Date; -} diff --git a/server/src/dtos/maintenance.dto.ts b/server/src/dtos/maintenance.dto.ts deleted file mode 100644 index f31d9ffa23..0000000000 --- a/server/src/dtos/maintenance.dto.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ValidateIf } from 'class-validator'; -import { MaintenanceAction, StorageFolder } from 'src/enum'; -import { ValidateBoolean, ValidateEnum, ValidateString } from 'src/validation'; - -export class SetMaintenanceModeDto { - @ValidateEnum({ enum: MaintenanceAction, name: 'MaintenanceAction', description: 'Maintenance action' }) - action!: MaintenanceAction; - - @ValidateIf((o) => o.action === MaintenanceAction.RestoreDatabase) - @ValidateString({ description: 'Restore backup filename' }) - restoreBackupFilename?: string; -} - -export class MaintenanceLoginDto { - @ValidateString({ optional: true, description: 'Maintenance token' }) - token?: string; -} - -export class MaintenanceAuthDto { - @ApiProperty({ description: 'Maintenance username' }) - username!: string; -} - -export class MaintenanceStatusResponseDto { - active!: boolean; - - @ValidateEnum({ enum: MaintenanceAction, name: 'MaintenanceAction', description: 'Maintenance action' }) - action!: MaintenanceAction; - - progress?: number; - task?: string; - error?: string; -} - -export class MaintenanceDetectInstallStorageFolderDto { - @ValidateEnum({ enum: StorageFolder, name: 'StorageFolder', description: 'Storage folder' }) - folder!: StorageFolder; - @ValidateBoolean({ description: 'Whether the folder is readable' }) - readable!: boolean; - @ValidateBoolean({ description: 'Whether the folder is writable' }) - writable!: boolean; - @ApiProperty({ description: 'Number of files in the folder' }) - files!: number; -} - -export class MaintenanceDetectInstallResponseDto { - storage!: MaintenanceDetectInstallStorageFolderDto[]; -} diff --git a/server/src/dtos/map.dto.ts b/server/src/dtos/map.dto.ts deleted file mode 100644 index d8db175c28..0000000000 --- a/server/src/dtos/map.dto.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsLatitude, IsLongitude } from 'class-validator'; -import { ValidateBoolean, ValidateDate } from 'src/validation'; - -export class MapReverseGeocodeDto { - @ApiProperty({ format: 'double', description: 'Latitude (-90 to 90)' }) - @Type(() => Number) - @IsLatitude({ message: ({ property }) => `${property} must be a number between -90 and 90` }) - lat!: number; - - @ApiProperty({ format: 'double', description: 'Longitude (-180 to 180)' }) - @Type(() => Number) - @IsLongitude({ message: ({ property }) => `${property} must be a number between -180 and 180` }) - lon!: number; -} - -export class MapReverseGeocodeResponseDto { - @ApiProperty({ description: 'City name' }) - city!: string | null; - - @ApiProperty({ description: 'State/Province name' }) - state!: string | null; - - @ApiProperty({ description: 'Country name' }) - country!: string | null; -} - -export class MapMarkerDto { - @ValidateBoolean({ optional: true, description: 'Filter by archived status' }) - isArchived?: boolean; - - @ValidateBoolean({ optional: true, description: 'Filter by favorite status' }) - isFavorite?: boolean; - - @ValidateDate({ optional: true, description: 'Filter assets created after this date' }) - fileCreatedAfter?: Date; - - @ValidateDate({ optional: true, description: 'Filter assets created before this date' }) - fileCreatedBefore?: Date; - - @ValidateBoolean({ optional: true, description: 'Include partner assets' }) - withPartners?: boolean; - - @ValidateBoolean({ optional: true, description: 'Include shared album assets' }) - withSharedAlbums?: boolean; -} - -export class MapMarkerResponseDto { - @ApiProperty({ description: 'Asset ID' }) - id!: string; - - @ApiProperty({ format: 'double', description: 'Latitude' }) - lat!: number; - - @ApiProperty({ format: 'double', description: 'Longitude' }) - lon!: number; - - @ApiProperty({ description: 'City name' }) - city!: string | null; - - @ApiProperty({ description: 'State/Province name' }) - state!: string | null; - - @ApiProperty({ description: 'Country name' }) - country!: string | null; -} diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts deleted file mode 100644 index 0d73c19b20..0000000000 --- a/server/src/dtos/memory.dto.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; -import { Memory } from 'src/database'; -import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetOrderWithRandom, MemoryType } from 'src/enum'; -import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; - -class MemoryBaseDto { - @ValidateBoolean({ optional: true, description: 'Is memory saved' }) - isSaved?: boolean; - - @ValidateDate({ optional: true, description: 'Date when memory was seen' }) - seenAt?: Date; -} - -export class MemorySearchDto { - @ValidateEnum({ enum: MemoryType, name: 'MemoryType', description: 'Memory type', optional: true }) - type?: MemoryType; - - @ValidateDate({ optional: true, description: 'Filter by date' }) - for?: Date; - - @ValidateBoolean({ optional: true, description: 'Include trashed memories' }) - isTrashed?: boolean; - - @ValidateBoolean({ optional: true, description: 'Filter by saved status' }) - isSaved?: boolean; - - @IsInt() - @IsPositive() - @Type(() => Number) - @Optional() - @ApiProperty({ type: 'integer', description: 'Number of memories to return' }) - size?: number; - - @ValidateEnum({ enum: AssetOrderWithRandom, name: 'MemorySearchOrder', description: 'Sort order', optional: true }) - order?: AssetOrderWithRandom; -} - -class OnThisDayDto { - @ApiProperty({ type: 'number', description: 'Year for on this day memory', minimum: 1 }) - @IsInt() - @IsPositive() - year!: number; -} - -type MemoryData = OnThisDayDto; - -export class MemoryUpdateDto extends MemoryBaseDto { - @ValidateDate({ optional: true, description: 'Memory date' }) - memoryAt?: Date; -} - -export class MemoryCreateDto extends MemoryBaseDto { - @ValidateEnum({ enum: MemoryType, name: 'MemoryType', description: 'Memory type' }) - type!: MemoryType; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @IsObject() - @ValidateNested() - @Type((options) => { - switch (options?.object.type) { - case MemoryType.OnThisDay: { - return OnThisDayDto; - } - - default: { - return Object; - } - } - }) - data!: MemoryData; - - @ValidateDate({ description: 'Memory date' }) - memoryAt!: Date; - - @ValidateUUID({ optional: true, each: true, description: 'Asset IDs to associate with memory' }) - assetIds?: string[]; -} - -export class MemoryStatisticsResponseDto { - @ApiProperty({ type: 'integer', description: 'Total number of memories' }) - total!: number; -} - -export class MemoryResponseDto { - @ApiProperty({ description: 'Memory ID' }) - id!: string; - @ValidateDate({ description: 'Creation date' }) - createdAt!: Date; - @ValidateDate({ description: 'Last update date' }) - updatedAt!: Date; - @ValidateDate({ optional: true, description: 'Deletion date' }) - deletedAt?: Date; - @ValidateDate({ description: 'Memory date' }) - memoryAt!: Date; - @ValidateDate({ optional: true, description: 'Date when memory was seen' }) - seenAt?: Date; - @ValidateDate({ optional: true, description: 'Date when memory should be shown' }) - showAt?: Date; - @ValidateDate({ optional: true, description: 'Date when memory should be hidden' }) - hideAt?: Date; - @ApiProperty({ description: 'Owner user ID' }) - ownerId!: string; - @ValidateEnum({ enum: MemoryType, name: 'MemoryType', description: 'Memory type' }) - type!: MemoryType; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - data!: MemoryData; - @ApiProperty({ description: 'Is memory saved' }) - isSaved!: boolean; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - assets!: AssetResponseDto[]; -} - -export const mapMemory = (entity: Memory, auth: AuthDto): MemoryResponseDto => { - return { - id: entity.id, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - deletedAt: entity.deletedAt ?? undefined, - memoryAt: entity.memoryAt, - seenAt: entity.seenAt ?? undefined, - showAt: entity.showAt ?? undefined, - hideAt: entity.hideAt ?? undefined, - ownerId: entity.ownerId, - type: entity.type as MemoryType, - data: entity.data as unknown as MemoryData, - isSaved: entity.isSaved, - assets: ('assets' in entity ? entity.assets : []).map((asset) => mapAsset(asset, { auth })), - }; -}; diff --git a/server/src/dtos/model-config.dto.ts b/server/src/dtos/model-config.dto.ts deleted file mode 100644 index a75808f95a..0000000000 --- a/server/src/dtos/model-config.dto.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsNotEmpty, IsNumber, IsString, Max, Min } from 'class-validator'; -import { ValidateBoolean } from 'src/validation'; - -export class TaskConfig { - @ValidateBoolean({ description: 'Whether the task is enabled' }) - enabled!: boolean; -} - -export class ModelConfig extends TaskConfig { - @ApiProperty({ description: 'Name of the model to use' }) - @IsString() - @IsNotEmpty() - modelName!: string; -} - -export class CLIPConfig extends ModelConfig {} - -export class DuplicateDetectionConfig extends TaskConfig { - @IsNumber() - @Min(0.001) - @Max(0.1) - @Type(() => Number) - @ApiProperty({ - type: 'number', - format: 'double', - description: 'Maximum distance threshold for duplicate detection', - }) - maxDistance!: number; -} - -export class FacialRecognitionConfig extends ModelConfig { - @IsNumber() - @Min(0.1) - @Max(1) - @Type(() => Number) - @ApiProperty({ type: 'number', format: 'double', description: 'Minimum confidence score for face detection' }) - minScore!: number; - - @IsNumber() - @Min(0.1) - @Max(2) - @Type(() => Number) - @ApiProperty({ - type: 'number', - format: 'double', - description: 'Maximum distance threshold for face recognition', - }) - maxDistance!: number; - - @IsNumber() - @Min(1) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Minimum number of faces required for recognition' }) - minFaces!: number; -} - -export class OcrConfig extends ModelConfig { - @IsNumber() - @Min(1) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Maximum resolution for OCR processing' }) - maxResolution!: number; - - @IsNumber() - @Min(0.1) - @Max(1) - @Type(() => Number) - @ApiProperty({ type: 'number', format: 'double', description: 'Minimum confidence score for text detection' }) - minDetectionScore!: number; - - @IsNumber() - @Min(0.1) - @Max(1) - @Type(() => Number) - @ApiProperty({ - type: 'number', - format: 'double', - description: 'Minimum confidence score for text recognition', - }) - minRecognitionScore!: number; -} diff --git a/server/src/dtos/notification.dto.ts b/server/src/dtos/notification.dto.ts deleted file mode 100644 index 87a15f29e3..0000000000 --- a/server/src/dtos/notification.dto.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { ArrayMinSize, IsString } from 'class-validator'; -import { NotificationLevel, NotificationType } from 'src/enum'; -import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateString, ValidateUUID } from 'src/validation'; - -export class TestEmailResponseDto { - @ApiProperty({ description: 'Email message ID' }) - messageId!: string; -} -export class TemplateResponseDto { - @ApiProperty({ description: 'Template name' }) - name!: string; - @ApiProperty({ description: 'Template HTML content' }) - html!: string; -} - -export class TemplateDto { - @ApiProperty({ description: 'Template name' }) - @IsString() - template!: string; -} - -export class NotificationDto { - @ApiProperty({ description: 'Notification ID' }) - id!: string; - @ValidateDate({ description: 'Creation date' }) - createdAt!: Date; - @ValidateEnum({ enum: NotificationLevel, name: 'NotificationLevel', description: 'Notification level' }) - level!: NotificationLevel; - @ValidateEnum({ enum: NotificationType, name: 'NotificationType', description: 'Notification type' }) - type!: NotificationType; - @ApiProperty({ description: 'Notification title' }) - title!: string; - @ApiPropertyOptional({ description: 'Notification description' }) - description?: string; - @ApiPropertyOptional({ description: 'Additional notification data' }) - data?: any; - @ApiPropertyOptional({ description: 'Date when notification was read', format: 'date-time' }) - readAt?: Date; -} - -export class NotificationSearchDto { - @ValidateUUID({ optional: true, description: 'Filter by notification ID' }) - id?: string; - - @ValidateEnum({ - enum: NotificationLevel, - name: 'NotificationLevel', - optional: true, - description: 'Filter by notification level', - }) - level?: NotificationLevel; - - @ValidateEnum({ - enum: NotificationType, - name: 'NotificationType', - optional: true, - description: 'Filter by notification type', - }) - type?: NotificationType; - - @ValidateBoolean({ optional: true, description: 'Filter by unread status' }) - unread?: boolean; -} - -export class NotificationCreateDto { - @ValidateEnum({ - enum: NotificationLevel, - name: 'NotificationLevel', - optional: true, - description: 'Notification level', - }) - level?: NotificationLevel; - - @ValidateEnum({ enum: NotificationType, name: 'NotificationType', optional: true, description: 'Notification type' }) - type?: NotificationType; - - @ValidateString({ description: 'Notification title' }) - title!: string; - - @ValidateString({ optional: true, nullable: true, description: 'Notification description' }) - description?: string | null; - - @ApiPropertyOptional({ description: 'Additional notification data' }) - @Optional({ nullable: true }) - data?: any; - - @ValidateDate({ optional: true, nullable: true, description: 'Date when notification was read' }) - readAt?: Date | null; - - @ValidateUUID({ description: 'User ID to send notification to' }) - userId!: string; -} - -export class NotificationUpdateDto { - @ValidateDate({ optional: true, nullable: true, description: 'Date when notification was read' }) - readAt?: Date | null; -} - -export class NotificationUpdateAllDto { - @ValidateUUID({ each: true, description: 'Notification IDs to update' }) - @ArrayMinSize(1) - ids!: string[]; - - @ValidateDate({ optional: true, nullable: true, description: 'Date when notifications were read' }) - readAt?: Date | null; -} - -export class NotificationDeleteAllDto { - @ValidateUUID({ each: true, description: 'Notification IDs to delete' }) - @ArrayMinSize(1) - ids!: string[]; -} - -export type MapNotification = { - id: string; - createdAt: Date; - updateId?: string; - level: NotificationLevel; - type: NotificationType; - data: any | null; - title: string; - description: string | null; - readAt: Date | null; -}; -export const mapNotification = (notification: MapNotification): NotificationDto => { - return { - id: notification.id, - createdAt: notification.createdAt, - level: notification.level, - type: notification.type, - title: notification.title, - description: notification.description ?? undefined, - data: notification.data ?? undefined, - readAt: notification.readAt ?? undefined, - }; -}; diff --git a/server/src/dtos/ocr.dto.ts b/server/src/dtos/ocr.dto.ts deleted file mode 100644 index 1e838d0ec0..0000000000 --- a/server/src/dtos/ocr.dto.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class AssetOcrResponseDto { - @ApiProperty({ type: 'string', format: 'uuid' }) - id!: string; - - @ApiProperty({ type: 'string', format: 'uuid' }) - assetId!: string; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 1 (0-1)' }) - x1!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 1 (0-1)' }) - y1!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 2 (0-1)' }) - x2!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 2 (0-1)' }) - y2!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 3 (0-1)' }) - x3!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 3 (0-1)' }) - y3!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized x coordinate of box corner 4 (0-1)' }) - x4!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Normalized y coordinate of box corner 4 (0-1)' }) - y4!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Confidence score for text detection box' }) - boxScore!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Confidence score for text recognition' }) - textScore!: number; - - @ApiProperty({ type: 'string', description: 'Recognized text' }) - text!: string; -} diff --git a/server/src/dtos/onboarding.dto.ts b/server/src/dtos/onboarding.dto.ts deleted file mode 100644 index d2781c6b90..0000000000 --- a/server/src/dtos/onboarding.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ValidateBoolean } from 'src/validation'; - -export class OnboardingDto { - @ValidateBoolean({ description: 'Is user onboarded' }) - isOnboarded!: boolean; -} - -export class OnboardingResponseDto extends OnboardingDto {} diff --git a/server/src/dtos/partner.dto.ts b/server/src/dtos/partner.dto.ts deleted file mode 100644 index 5b949326a4..0000000000 --- a/server/src/dtos/partner.dto.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty } from 'class-validator'; -import { UserResponseDto } from 'src/dtos/user.dto'; -import { PartnerDirection } from 'src/repositories/partner.repository'; -import { ValidateEnum, ValidateUUID } from 'src/validation'; - -export class PartnerCreateDto { - @ValidateUUID({ description: 'User ID to share with' }) - sharedWithId!: string; -} - -export class PartnerUpdateDto { - @ApiProperty({ description: 'Show partner assets in timeline' }) - @IsNotEmpty() - inTimeline!: boolean; -} - -export class PartnerSearchDto { - @ValidateEnum({ enum: PartnerDirection, name: 'PartnerDirection', description: 'Partner direction' }) - direction!: PartnerDirection; -} - -export class PartnerResponseDto extends UserResponseDto { - @ApiPropertyOptional({ description: 'Show in timeline' }) - inTimeline?: boolean; -} diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts deleted file mode 100644 index 983062afcf..0000000000 --- a/server/src/dtos/person.dto.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsArray, IsInt, IsNotEmpty, IsNumber, IsString, Max, Min, ValidateNested } from 'class-validator'; -import { Selectable } from 'kysely'; -import { DateTime } from 'luxon'; -import { AssetFace, Person } from 'src/database'; -import { HistoryBuilder, Property } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { SourceType } from 'src/enum'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { ImageDimensions } from 'src/types'; -import { asDateString } from 'src/utils/date'; -import { transformFaceBoundingBox } from 'src/utils/transform'; -import { - IsDateStringFormat, - MaxDateString, - Optional, - ValidateBoolean, - ValidateEnum, - ValidateHexColor, - ValidateUUID, -} from 'src/validation'; - -export class PersonCreateDto { - @ApiPropertyOptional({ description: 'Person name' }) - @Optional() - @IsString() - name?: string; - - // Note: the mobile app cannot currently set the birth date to null. - @ApiProperty({ format: 'date', description: 'Person date of birth', required: false }) - @MaxDateString(() => DateTime.now(), { message: 'Birth date cannot be in the future' }) - @IsDateStringFormat('yyyy-MM-dd') - @Optional({ nullable: true, emptyToNull: true }) - birthDate?: Date | null; - - @ValidateBoolean({ optional: true, description: 'Person visibility (hidden)' }) - isHidden?: boolean; - - @ValidateBoolean({ optional: true, description: 'Mark as favorite' }) - isFavorite?: boolean; - - @ApiPropertyOptional({ description: 'Person color (hex)' }) - @Optional({ emptyToNull: true, nullable: true }) - @ValidateHexColor() - color?: string | null; -} - -export class PersonUpdateDto extends PersonCreateDto { - @ValidateUUID({ optional: true, description: 'Asset ID used for feature face thumbnail' }) - featureFaceAssetId?: string; -} - -export class PeopleUpdateDto { - @ApiProperty({ description: 'People to update' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => PeopleUpdateItem) - people!: PeopleUpdateItem[]; -} - -export class PeopleUpdateItem extends PersonUpdateDto { - @ApiProperty({ description: 'Person ID' }) - @IsString() - @IsNotEmpty() - id!: string; -} - -export class MergePersonDto { - @ValidateUUID({ each: true, description: 'Person IDs to merge' }) - ids!: string[]; -} - -export class PersonSearchDto { - @ValidateBoolean({ optional: true, description: 'Include hidden people' }) - withHidden?: boolean; - @ValidateUUID({ optional: true, description: 'Closest person ID for similarity search' }) - closestPersonId?: string; - @ValidateUUID({ optional: true, description: 'Closest asset ID for similarity search' }) - closestAssetId?: string; - - @ApiPropertyOptional({ description: 'Page number for pagination', default: 1 }) - @IsInt() - @Min(1) - @Type(() => Number) - page: number = 1; - - @ApiPropertyOptional({ description: 'Number of items per page', default: 500 }) - @IsInt() - @Min(1) - @Max(1000) - @Type(() => Number) - size: number = 500; -} - -export class PersonResponseDto { - @ApiProperty({ description: 'Person ID' }) - id!: string; - @ApiProperty({ description: 'Person name' }) - name!: string; - @ApiProperty({ format: 'date', description: 'Person date of birth' }) - birthDate!: string | null; - @ApiProperty({ description: 'Thumbnail path' }) - thumbnailPath!: string; - @ApiProperty({ description: 'Is hidden' }) - isHidden!: boolean; - @Property({ description: 'Last update date', history: new HistoryBuilder().added('v1.107.0').stable('v2') }) - updatedAt?: Date; - @Property({ description: 'Is favorite', history: new HistoryBuilder().added('v1.126.0').stable('v2') }) - isFavorite?: boolean; - @Property({ description: 'Person color (hex)', history: new HistoryBuilder().added('v1.126.0').stable('v2') }) - color?: string; -} - -export class PersonWithFacesResponseDto extends PersonResponseDto { - @ApiProperty({ description: 'Face detections' }) - faces!: AssetFaceWithoutPersonResponseDto[]; -} - -export class AssetFaceWithoutPersonResponseDto { - @ValidateUUID({ description: 'Face ID' }) - id!: string; - @ApiProperty({ type: 'integer', description: 'Image height in pixels' }) - imageHeight!: number; - @ApiProperty({ type: 'integer', description: 'Image width in pixels' }) - imageWidth!: number; - @ApiProperty({ type: 'integer', description: 'Bounding box X1 coordinate' }) - boundingBoxX1!: number; - @ApiProperty({ type: 'integer', description: 'Bounding box X2 coordinate' }) - boundingBoxX2!: number; - @ApiProperty({ type: 'integer', description: 'Bounding box Y1 coordinate' }) - boundingBoxY1!: number; - @ApiProperty({ type: 'integer', description: 'Bounding box Y2 coordinate' }) - boundingBoxY2!: number; - @ValidateEnum({ enum: SourceType, name: 'SourceType', optional: true, description: 'Face detection source type' }) - sourceType?: SourceType; -} - -export class AssetFaceResponseDto extends AssetFaceWithoutPersonResponseDto { - @ApiProperty({ description: 'Person associated with face' }) - person!: PersonResponseDto | null; -} - -export class AssetFaceUpdateDto { - @ApiProperty({ description: 'Face update items' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => AssetFaceUpdateItem) - data!: AssetFaceUpdateItem[]; -} - -export class FaceDto { - @ValidateUUID({ description: 'Face ID' }) - id!: string; -} - -export class AssetFaceUpdateItem { - @ValidateUUID({ description: 'Person ID' }) - personId!: string; - - @ValidateUUID({ description: 'Asset ID' }) - assetId!: string; -} - -export class AssetFaceCreateDto extends AssetFaceUpdateItem { - @ApiProperty({ type: 'integer', description: 'Image width in pixels' }) - @IsNotEmpty() - @IsNumber() - imageWidth!: number; - - @ApiProperty({ type: 'integer', description: 'Image height in pixels' }) - @IsNotEmpty() - @IsNumber() - imageHeight!: number; - - @ApiProperty({ type: 'integer', description: 'Face bounding box X coordinate' }) - @IsNotEmpty() - @IsNumber() - x!: number; - - @ApiProperty({ type: 'integer', description: 'Face bounding box Y coordinate' }) - @IsNotEmpty() - @IsNumber() - y!: number; - - @ApiProperty({ type: 'integer', description: 'Face bounding box width' }) - @IsNotEmpty() - @IsNumber() - width!: number; - - @ApiProperty({ type: 'integer', description: 'Face bounding box height' }) - @IsNotEmpty() - @IsNumber() - height!: number; -} - -export class AssetFaceDeleteDto { - @ApiProperty({ description: 'Force delete even if person has other faces' }) - @IsNotEmpty() - force!: boolean; -} - -export class PersonStatisticsResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of assets' }) - assets!: number; -} - -export class PeopleResponseDto { - @ApiProperty({ type: 'integer', description: 'Total number of people' }) - total!: number; - @ApiProperty({ type: 'integer', description: 'Number of hidden people' }) - hidden!: number; - @ApiProperty({ description: 'List of people' }) - people!: PersonResponseDto[]; - - // TODO: make required after a few versions - @Property({ - description: 'Whether there are more pages', - history: new HistoryBuilder().added('v1.110.0').stable('v2'), - }) - hasNextPage?: boolean; -} - -export function mapPerson(person: Person): PersonResponseDto { - return { - id: person.id, - name: person.name, - birthDate: asDateString(person.birthDate), - thumbnailPath: person.thumbnailPath, - isHidden: person.isHidden, - isFavorite: person.isFavorite, - color: person.color ?? undefined, - updatedAt: person.updatedAt, - }; -} - -export function mapFacesWithoutPerson( - face: Selectable, - edits?: AssetEditActionItem[], - assetDimensions?: ImageDimensions, -): AssetFaceWithoutPersonResponseDto { - return { - id: face.id, - ...transformFaceBoundingBox( - { - boundingBoxX1: face.boundingBoxX1, - boundingBoxY1: face.boundingBoxY1, - boundingBoxX2: face.boundingBoxX2, - boundingBoxY2: face.boundingBoxY2, - imageWidth: face.imageWidth, - imageHeight: face.imageHeight, - }, - edits ?? [], - assetDimensions ?? { width: face.imageWidth, height: face.imageHeight }, - ), - sourceType: face.sourceType, - }; -} - -export function mapFaces( - face: AssetFace, - auth: AuthDto, - edits?: AssetEditActionItem[], - assetDimensions?: ImageDimensions, -): AssetFaceResponseDto { - return { - ...mapFacesWithoutPerson(face, edits, assetDimensions), - person: face.person?.ownerId === auth.user.id ? mapPerson(face.person) : null, - }; -} diff --git a/server/src/dtos/plugin-manifest.dto.ts b/server/src/dtos/plugin-manifest.dto.ts deleted file mode 100644 index d5d1c52997..0000000000 --- a/server/src/dtos/plugin-manifest.dto.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - ArrayMinSize, - IsArray, - IsEnum, - IsNotEmpty, - IsObject, - IsOptional, - IsSemVer, - IsString, - Matches, - ValidateNested, -} from 'class-validator'; -import { PluginContext } from 'src/enum'; -import { JSONSchema } from 'src/types/plugin-schema.types'; -import { ValidateEnum } from 'src/validation'; - -class PluginManifestWasmDto { - @ApiProperty({ description: 'WASM file path' }) - @IsString() - @IsNotEmpty() - path!: string; -} - -class PluginManifestFilterDto { - @ApiProperty({ description: 'Filter method name' }) - @IsString() - @IsNotEmpty() - methodName!: string; - - @ApiProperty({ description: 'Filter title' }) - @IsString() - @IsNotEmpty() - title!: string; - - @ApiProperty({ description: 'Filter description' }) - @IsString() - @IsNotEmpty() - description!: string; - - @ApiProperty({ description: 'Supported contexts', enum: PluginContext, isArray: true }) - @IsArray() - @ArrayMinSize(1) - @IsEnum(PluginContext, { each: true }) - supportedContexts!: PluginContext[]; - - @ApiPropertyOptional({ description: 'Filter schema' }) - @IsObject() - @IsOptional() - schema?: JSONSchema; -} - -class PluginManifestActionDto { - @ApiProperty({ description: 'Action method name' }) - @IsString() - @IsNotEmpty() - methodName!: string; - - @ApiProperty({ description: 'Action title' }) - @IsString() - @IsNotEmpty() - title!: string; - - @ApiProperty({ description: 'Action description' }) - @IsString() - @IsNotEmpty() - description!: string; - - @ArrayMinSize(1) - @ValidateEnum({ enum: PluginContext, name: 'PluginContext', each: true, description: 'Supported contexts' }) - supportedContexts!: PluginContext[]; - - @ApiPropertyOptional({ description: 'Action schema' }) - @IsObject() - @IsOptional() - schema?: JSONSchema; -} - -export class PluginManifestDto { - @ApiProperty({ description: 'Plugin name (lowercase, numbers, hyphens only)' }) - @IsString() - @IsNotEmpty() - @Matches(/^[a-z0-9-]+[a-z0-9]$/, { - message: 'Plugin name must contain only lowercase letters, numbers, and hyphens, and cannot end with a hyphen', - }) - name!: string; - - @ApiProperty({ description: 'Plugin version (semver)' }) - @IsString() - @IsNotEmpty() - @IsSemVer() - version!: string; - - @ApiProperty({ description: 'Plugin title' }) - @IsString() - @IsNotEmpty() - title!: string; - - @ApiProperty({ description: 'Plugin description' }) - @IsString() - @IsNotEmpty() - description!: string; - - @ApiProperty({ description: 'Plugin author' }) - @IsString() - @IsNotEmpty() - author!: string; - - @ApiProperty({ description: 'WASM configuration' }) - @ValidateNested() - @Type(() => PluginManifestWasmDto) - wasm!: PluginManifestWasmDto; - - @ApiPropertyOptional({ description: 'Plugin filters' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => PluginManifestFilterDto) - @IsOptional() - filters?: PluginManifestFilterDto[]; - - @ApiPropertyOptional({ description: 'Plugin actions' }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => PluginManifestActionDto) - @IsOptional() - actions?: PluginManifestActionDto[]; -} diff --git a/server/src/dtos/plugin.dto.ts b/server/src/dtos/plugin.dto.ts deleted file mode 100644 index de1f1b28d4..0000000000 --- a/server/src/dtos/plugin.dto.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; -import { PluginAction, PluginFilter } from 'src/database'; -import { PluginContext as PluginContextType, PluginTriggerType } from 'src/enum'; -import type { JSONSchema } from 'src/types/plugin-schema.types'; -import { ValidateEnum } from 'src/validation'; - -export class PluginTriggerResponseDto { - @ValidateEnum({ enum: PluginTriggerType, name: 'PluginTriggerType', description: 'Trigger type' }) - type!: PluginTriggerType; - @ValidateEnum({ enum: PluginContextType, name: 'PluginContextType', description: 'Context type' }) - contextType!: PluginContextType; -} - -export class PluginResponseDto { - @ApiProperty({ description: 'Plugin ID' }) - id!: string; - @ApiProperty({ description: 'Plugin name' }) - name!: string; - @ApiProperty({ description: 'Plugin title' }) - title!: string; - @ApiProperty({ description: 'Plugin description' }) - description!: string; - @ApiProperty({ description: 'Plugin author' }) - author!: string; - @ApiProperty({ description: 'Plugin version' }) - version!: string; - @ApiProperty({ description: 'Creation date' }) - createdAt!: string; - @ApiProperty({ description: 'Last update date' }) - updatedAt!: string; - @ApiProperty({ description: 'Plugin filters' }) - filters!: PluginFilterResponseDto[]; - @ApiProperty({ description: 'Plugin actions' }) - actions!: PluginActionResponseDto[]; -} - -export class PluginFilterResponseDto { - @ApiProperty({ description: 'Filter ID' }) - id!: string; - @ApiProperty({ description: 'Plugin ID' }) - pluginId!: string; - @ApiProperty({ description: 'Method name' }) - methodName!: string; - @ApiProperty({ description: 'Filter title' }) - title!: string; - @ApiProperty({ description: 'Filter description' }) - description!: string; - - @ValidateEnum({ enum: PluginContextType, name: 'PluginContextType', each: true, description: 'Supported contexts' }) - supportedContexts!: PluginContextType[]; - @ApiProperty({ description: 'Filter schema' }) - schema!: JSONSchema | null; -} - -export class PluginActionResponseDto { - @ApiProperty({ description: 'Action ID' }) - id!: string; - @ApiProperty({ description: 'Plugin ID' }) - pluginId!: string; - @ApiProperty({ description: 'Method name' }) - methodName!: string; - @ApiProperty({ description: 'Action title' }) - title!: string; - @ApiProperty({ description: 'Action description' }) - description!: string; - - @ValidateEnum({ enum: PluginContextType, name: 'PluginContextType', each: true, description: 'Supported contexts' }) - supportedContexts!: PluginContextType[]; - @ApiProperty({ description: 'Action schema' }) - schema!: JSONSchema | null; -} - -export class PluginInstallDto { - @ApiProperty({ description: 'Path to plugin manifest file' }) - @IsString() - @IsNotEmpty() - manifestPath!: string; -} - -export type MapPlugin = { - id: string; - name: string; - title: string; - description: string; - author: string; - version: string; - wasmPath: string; - createdAt: Date; - updatedAt: Date; - filters: PluginFilter[]; - actions: PluginAction[]; -}; - -export function mapPlugin(plugin: MapPlugin): PluginResponseDto { - return { - id: plugin.id, - name: plugin.name, - title: plugin.title, - description: plugin.description, - author: plugin.author, - version: plugin.version, - createdAt: plugin.createdAt.toISOString(), - updatedAt: plugin.updatedAt.toISOString(), - filters: plugin.filters, - actions: plugin.actions, - }; -} diff --git a/server/src/dtos/queue-legacy.dto.ts b/server/src/dtos/queue-legacy.dto.ts deleted file mode 100644 index 993160a03b..0000000000 --- a/server/src/dtos/queue-legacy.dto.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { QueueResponseDto, QueueStatisticsDto } from 'src/dtos/queue.dto'; -import { QueueName } from 'src/enum'; - -export class QueueStatusLegacyDto { - @ApiProperty({ description: 'Whether the queue is currently active (has running jobs)' }) - isActive!: boolean; - @ApiProperty({ description: 'Whether the queue is paused' }) - isPaused!: boolean; -} - -export class QueueResponseLegacyDto { - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - queueStatus!: QueueStatusLegacyDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - jobCounts!: QueueStatisticsDto; -} - -export class QueuesResponseLegacyDto implements Record { - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.ThumbnailGeneration]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.MetadataExtraction]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.VideoConversion]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.SmartSearch]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.StorageTemplateMigration]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Migration]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.BackgroundTask]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Search]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.DuplicateDetection]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.FaceDetection]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.FacialRecognition]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Sidecar]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Library]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Notification]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.BackupDatabase]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Ocr]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Workflow]!: QueueResponseLegacyDto; - - @ApiProperty({ type: QueueResponseLegacyDto }) - [QueueName.Editor]!: QueueResponseLegacyDto; -} - -export const mapQueueLegacy = (response: QueueResponseDto): QueueResponseLegacyDto => { - return { - queueStatus: { - isPaused: response.isPaused, - isActive: response.statistics.active > 0, - }, - jobCounts: response.statistics, - }; -}; - -export const mapQueuesLegacy = (responses: QueueResponseDto[]): QueuesResponseLegacyDto => { - const legacy = new QueuesResponseLegacyDto(); - - for (const response of responses) { - legacy[response.name] = mapQueueLegacy(response); - } - - return legacy; -}; diff --git a/server/src/dtos/queue.dto.ts b/server/src/dtos/queue.dto.ts deleted file mode 100644 index 7893581444..0000000000 --- a/server/src/dtos/queue.dto.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { HistoryBuilder } from 'src/decorators'; -import { JobName, QueueCommand, QueueJobStatus, QueueName } from 'src/enum'; -import { ValidateBoolean, ValidateEnum } from 'src/validation'; - -export class QueueNameParamDto { - @ValidateEnum({ enum: QueueName, name: 'QueueName', description: 'Queue name' }) - name!: QueueName; -} - -export class QueueCommandDto { - @ValidateEnum({ enum: QueueCommand, name: 'QueueCommand', description: 'Queue command to execute' }) - command!: QueueCommand; - - @ValidateBoolean({ optional: true, description: 'Force the command execution (if applicable)' }) - force?: boolean; // TODO: this uses undefined as a third state, which should be refactored to be more explicit -} - -export class QueueUpdateDto { - @ValidateBoolean({ optional: true, description: 'Whether to pause the queue' }) - isPaused?: boolean; -} - -export class QueueDeleteDto { - @ValidateBoolean({ - optional: true, - description: 'If true, will also remove failed jobs from the queue.', - history: new HistoryBuilder().added('v2.4.0').alpha('v2.4.0'), - }) - failed?: boolean; -} - -export class QueueJobSearchDto { - @ValidateEnum({ - enum: QueueJobStatus, - name: 'QueueJobStatus', - optional: true, - each: true, - description: 'Filter jobs by status', - }) - status?: QueueJobStatus[]; -} -export class QueueJobResponseDto { - @ApiPropertyOptional({ description: 'Job ID' }) - id?: string; - - @ValidateEnum({ enum: JobName, name: 'JobName', description: 'Job name' }) - name!: JobName; - - @ApiProperty({ description: 'Job data payload', type: Object }) - data!: object; - - @ApiProperty({ type: 'integer', description: 'Job creation timestamp' }) - timestamp!: number; -} - -export class QueueStatisticsDto { - @ApiProperty({ type: 'integer', description: 'Number of active jobs' }) - active!: number; - @ApiProperty({ type: 'integer', description: 'Number of completed jobs' }) - completed!: number; - @ApiProperty({ type: 'integer', description: 'Number of failed jobs' }) - failed!: number; - @ApiProperty({ type: 'integer', description: 'Number of delayed jobs' }) - delayed!: number; - @ApiProperty({ type: 'integer', description: 'Number of waiting jobs' }) - waiting!: number; - @ApiProperty({ type: 'integer', description: 'Number of paused jobs' }) - paused!: number; -} - -export class QueueResponseDto { - @ValidateEnum({ enum: QueueName, name: 'QueueName', description: 'Queue name' }) - name!: QueueName; - - @ValidateBoolean({ description: 'Whether the queue is paused' }) - isPaused!: boolean; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - statistics!: QueueStatisticsDto; -} diff --git a/server/src/dtos/search.dto.ts b/server/src/dtos/search.dto.ts deleted file mode 100644 index 47a1889e47..0000000000 --- a/server/src/dtos/search.dto.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; -import { Place } from 'src/database'; -import { HistoryBuilder } from 'src/decorators'; -import { AlbumResponseDto } from 'src/dtos/album.dto'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { AssetOrder, AssetType, AssetVisibility } from 'src/enum'; -import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateString, ValidateUUID } from 'src/validation'; - -class BaseSearchDto { - @ValidateUUID({ optional: true, nullable: true, description: 'Library ID to filter by' }) - libraryId?: string | null; - - @ApiPropertyOptional({ description: 'Device ID to filter by' }) - @IsString() - @IsNotEmpty() - @Optional() - deviceId?: string; - - @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', optional: true, description: 'Asset type filter' }) - type?: AssetType; - - @ValidateBoolean({ optional: true, description: 'Filter by encoded status' }) - isEncoded?: boolean; - - @ValidateBoolean({ optional: true, description: 'Filter by favorite status' }) - isFavorite?: boolean; - - @ValidateBoolean({ optional: true, description: 'Filter by motion photo status' }) - isMotion?: boolean; - - @ValidateBoolean({ optional: true, description: 'Filter by offline status' }) - isOffline?: boolean; - - @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true, description: 'Filter by visibility' }) - visibility?: AssetVisibility; - - @ValidateDate({ optional: true, description: 'Filter by creation date (before)' }) - createdBefore?: Date; - - @ValidateDate({ optional: true, description: 'Filter by creation date (after)' }) - createdAfter?: Date; - - @ValidateDate({ optional: true, description: 'Filter by update date (before)' }) - updatedBefore?: Date; - - @ValidateDate({ optional: true, description: 'Filter by update date (after)' }) - updatedAfter?: Date; - - @ValidateDate({ optional: true, description: 'Filter by trash date (before)' }) - trashedBefore?: Date; - - @ValidateDate({ optional: true, description: 'Filter by trash date (after)' }) - trashedAfter?: Date; - - @ValidateDate({ optional: true, description: 'Filter by taken date (before)' }) - takenBefore?: Date; - - @ValidateDate({ optional: true, description: 'Filter by taken date (after)' }) - takenAfter?: Date; - - @ApiPropertyOptional({ description: 'Filter by city name' }) - @IsString() - @Optional({ nullable: true, emptyToNull: true }) - city?: string | null; - - @ApiPropertyOptional({ description: 'Filter by state/province name' }) - @IsString() - @Optional({ nullable: true, emptyToNull: true }) - state?: string | null; - - @ApiPropertyOptional({ description: 'Filter by country name' }) - @IsString() - @IsNotEmpty() - @Optional({ nullable: true, emptyToNull: true }) - country?: string | null; - - @ApiPropertyOptional({ description: 'Filter by camera make' }) - @IsString() - @Optional({ nullable: true, emptyToNull: true }) - make?: string; - - @ApiPropertyOptional({ description: 'Filter by camera model' }) - @IsString() - @Optional({ nullable: true, emptyToNull: true }) - model?: string | null; - - @ApiPropertyOptional({ description: 'Filter by lens model' }) - @IsString() - @Optional({ nullable: true, emptyToNull: true }) - lensModel?: string | null; - - @ValidateBoolean({ optional: true, description: 'Filter assets not in any album' }) - isNotInAlbum?: boolean; - - @ValidateUUID({ each: true, optional: true, description: 'Filter by person IDs' }) - personIds?: string[]; - - @ValidateUUID({ each: true, optional: true, nullable: true, description: 'Filter by tag IDs' }) - tagIds?: string[] | null; - - @ValidateUUID({ each: true, optional: true, description: 'Filter by album IDs' }) - albumIds?: string[]; - - @ApiPropertyOptional({ type: 'number', description: 'Filter by rating', minimum: -1, maximum: 5 }) - @Optional() - @IsInt() - @Max(5) - @Min(-1) - rating?: number; - - @ApiPropertyOptional({ description: 'Filter by OCR text content' }) - @IsString() - @IsNotEmpty() - @Optional() - ocr?: string; -} - -class BaseSearchWithResultsDto extends BaseSearchDto { - @ValidateBoolean({ optional: true, description: 'Include deleted assets' }) - withDeleted?: boolean; - - @ValidateBoolean({ optional: true, description: 'Include EXIF data in response' }) - withExif?: boolean; - - @ApiPropertyOptional({ type: 'number', description: 'Number of results to return', minimum: 1, maximum: 1000 }) - @IsInt() - @Min(1) - @Max(1000) - @Type(() => Number) - @Optional() - size?: number; -} - -export class RandomSearchDto extends BaseSearchWithResultsDto { - @ValidateBoolean({ optional: true, description: 'Include stacked assets' }) - withStacked?: boolean; - - @ValidateBoolean({ optional: true, description: 'Include assets with people' }) - withPeople?: boolean; -} - -export class LargeAssetSearchDto extends BaseSearchWithResultsDto { - @ApiPropertyOptional({ type: 'integer', description: 'Minimum file size in bytes', minimum: 0 }) - @Optional() - @IsInt() - @Min(0) - @Type(() => Number) - minFileSize?: number; -} - -export class MetadataSearchDto extends RandomSearchDto { - @ValidateUUID({ optional: true, description: 'Filter by asset ID' }) - id?: string; - - @ApiPropertyOptional({ description: 'Filter by device asset ID' }) - @IsString() - @IsNotEmpty() - @Optional() - deviceAssetId?: string; - - @ValidateString({ optional: true, trim: true, description: 'Filter by description text' }) - description?: string; - - @ApiPropertyOptional({ description: 'Filter by file checksum' }) - @IsString() - @IsNotEmpty() - @Optional() - checksum?: string; - - @ValidateString({ optional: true, trim: true, description: 'Filter by original file name' }) - originalFileName?: string; - - @ApiPropertyOptional({ description: 'Filter by original file path' }) - @IsString() - @IsNotEmpty() - @Optional() - originalPath?: string; - - @ApiPropertyOptional({ description: 'Filter by preview file path' }) - @IsString() - @IsNotEmpty() - @Optional() - previewPath?: string; - - @ApiPropertyOptional({ description: 'Filter by thumbnail file path' }) - @IsString() - @IsNotEmpty() - @Optional() - thumbnailPath?: string; - - @ApiPropertyOptional({ description: 'Filter by encoded video file path' }) - @IsString() - @IsNotEmpty() - @Optional() - encodedVideoPath?: string; - - @ValidateEnum({ - enum: AssetOrder, - name: 'AssetOrder', - optional: true, - default: AssetOrder.Desc, - description: 'Sort order', - }) - order?: AssetOrder; - - @ApiPropertyOptional({ type: 'number', description: 'Page number', minimum: 1 }) - @IsInt() - @Min(1) - @Type(() => Number) - @Optional() - page?: number; -} - -export class StatisticsSearchDto extends BaseSearchDto { - @ValidateString({ optional: true, trim: true, description: 'Filter by description text' }) - description?: string; -} - -export class SmartSearchDto extends BaseSearchWithResultsDto { - @ValidateString({ optional: true, trim: true, description: 'Natural language search query' }) - query?: string; - - @ValidateUUID({ optional: true, description: 'Asset ID to use as search reference' }) - queryAssetId?: string; - - @ApiPropertyOptional({ description: 'Search language code' }) - @IsString() - @IsNotEmpty() - @Optional() - language?: string; - - @ApiPropertyOptional({ type: 'number', description: 'Page number', minimum: 1 }) - @IsInt() - @Min(1) - @Type(() => Number) - @Optional() - page?: number; -} - -export class SearchPlacesDto { - @ApiProperty({ description: 'Place name to search for' }) - @IsString() - @IsNotEmpty() - name!: string; -} - -export class SearchPeopleDto { - @ApiProperty({ description: 'Person name to search for' }) - @IsString() - @IsNotEmpty() - name!: string; - - @ValidateBoolean({ optional: true, description: 'Include hidden people' }) - withHidden?: boolean; -} - -export class PlacesResponseDto { - @ApiProperty({ description: 'Place name' }) - name!: string; - @ApiProperty({ type: 'number', description: 'Latitude coordinate' }) - latitude!: number; - @ApiProperty({ type: 'number', description: 'Longitude coordinate' }) - longitude!: number; - @ApiPropertyOptional({ description: 'Administrative level 1 name (state/province)' }) - admin1name?: string; - @ApiPropertyOptional({ description: 'Administrative level 2 name (county/district)' }) - admin2name?: string; -} - -export function mapPlaces(place: Place): PlacesResponseDto { - return { - name: place.name, - latitude: place.latitude, - longitude: place.longitude, - admin1name: place.admin1Name ?? undefined, - admin2name: place.admin2Name ?? undefined, - }; -} - -export enum SearchSuggestionType { - COUNTRY = 'country', - STATE = 'state', - CITY = 'city', - CAMERA_MAKE = 'camera-make', - CAMERA_MODEL = 'camera-model', - CAMERA_LENS_MODEL = 'camera-lens-model', -} - -export class SearchSuggestionRequestDto { - @ValidateEnum({ enum: SearchSuggestionType, name: 'SearchSuggestionType', description: 'Suggestion type' }) - type!: SearchSuggestionType; - - @ApiPropertyOptional({ description: 'Filter by country' }) - @IsString() - @Optional() - country?: string; - - @ApiPropertyOptional({ description: 'Filter by state/province' }) - @IsString() - @Optional() - state?: string; - - @ApiPropertyOptional({ description: 'Filter by camera make' }) - @IsString() - @Optional() - make?: string; - - @ApiPropertyOptional({ description: 'Filter by camera model' }) - @IsString() - @Optional() - model?: string; - - @ApiPropertyOptional({ description: 'Filter by lens model' }) - @IsString() - @Optional() - lensModel?: string; - - @ValidateBoolean({ - optional: true, - description: 'Include null values in suggestions', - history: new HistoryBuilder().added('v1.111.0').stable('v2'), - }) - includeNull?: boolean; -} - -class SearchFacetCountResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of assets with this facet value' }) - count!: number; - @ApiProperty({ description: 'Facet value' }) - value!: string; -} - -class SearchFacetResponseDto { - @ApiProperty({ description: 'Facet field name' }) - fieldName!: string; - @ApiProperty({ description: 'Facet counts' }) - counts!: SearchFacetCountResponseDto[]; -} - -class SearchAlbumResponseDto { - @ApiProperty({ type: 'integer', description: 'Total number of matching albums' }) - total!: number; - @ApiProperty({ type: 'integer', description: 'Number of albums in this page' }) - count!: number; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - items!: AlbumResponseDto[]; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - facets!: SearchFacetResponseDto[]; -} - -class SearchAssetResponseDto { - @ApiProperty({ type: 'integer', description: 'Total number of matching assets' }) - total!: number; - @ApiProperty({ type: 'integer', description: 'Number of assets in this page' }) - count!: number; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - items!: AssetResponseDto[]; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - facets!: SearchFacetResponseDto[]; - @ApiProperty({ description: 'Next page token' }) - nextPage!: string | null; -} - -export class SearchResponseDto { - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - albums!: SearchAlbumResponseDto; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - assets!: SearchAssetResponseDto; -} - -export class SearchStatisticsResponseDto { - @ApiProperty({ type: 'integer', description: 'Total number of matching assets' }) - total!: number; -} - -class SearchExploreItem { - @ApiProperty({ description: 'Explore value' }) - value!: string; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - data!: AssetResponseDto; -} - -export class SearchExploreResponseDto { - @ApiProperty({ description: 'Explore field name' }) - fieldName!: string; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - items!: SearchExploreItem[]; -} - -export class MemoryLaneDto { - @ApiProperty({ type: 'integer', description: 'Day of month' }) - @IsInt() - @Type(() => Number) - @Max(31) - @Min(1) - day!: number; - - @ApiProperty({ type: 'integer', description: 'Month' }) - @IsInt() - @Type(() => Number) - @Max(12) - @Min(1) - month!: number; -} diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 626c94e40a..8a928c68e5 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty, ApiPropertyOptional, ApiResponseProperty } from '@nestjs/swagger'; import { SemVer } from 'semver'; -import { SystemConfigThemeDto } from 'src/dtos/system-config.dto'; export class ServerPingResponse { @ApiResponseProperty({ type: String, example: 'pong' }) @@ -29,65 +28,9 @@ export class ServerAboutResponseDto { build?: string; @ApiPropertyOptional({ description: 'Build URL' }) buildUrl?: string; - @ApiPropertyOptional({ description: 'Build image name' }) - buildImage?: string; - @ApiPropertyOptional({ description: 'Build image URL' }) - buildImageUrl?: string; @ApiPropertyOptional({ description: 'Node.js version' }) nodejs?: string; - @ApiPropertyOptional({ description: 'FFmpeg version' }) - ffmpeg?: string; - @ApiPropertyOptional({ description: 'ImageMagick version' }) - imagemagick?: string; - @ApiPropertyOptional({ description: 'libvips version' }) - libvips?: string; - @ApiPropertyOptional({ description: 'ExifTool version' }) - exiftool?: string; - - @ApiProperty({ description: 'Whether the server is licensed' }) - licensed!: boolean; - - @ApiPropertyOptional({ description: 'Third-party source URL' }) - thirdPartySourceUrl?: string; - @ApiPropertyOptional({ description: 'Third-party bug/feature URL' }) - thirdPartyBugFeatureUrl?: string; - @ApiPropertyOptional({ description: 'Third-party documentation URL' }) - thirdPartyDocumentationUrl?: string; - @ApiPropertyOptional({ description: 'Third-party support URL' }) - thirdPartySupportUrl?: string; -} - -export class ServerApkLinksDto { - @ApiProperty({ description: 'APK download link for ARM64 v8a architecture' }) - arm64v8a!: string; - @ApiProperty({ description: 'APK download link for ARM EABI v7a architecture' }) - armeabiv7a!: string; - @ApiProperty({ description: 'APK download link for universal architecture' }) - universal!: string; - @ApiProperty({ description: 'APK download link for x86_64 architecture' }) - x86_64!: string; -} - -export class ServerStorageResponseDto { - @ApiProperty({ description: 'Total disk size (human-readable format)' }) - diskSize!: string; - @ApiProperty({ description: 'Used disk space (human-readable format)' }) - diskUse!: string; - @ApiProperty({ description: 'Available disk space (human-readable format)' }) - diskAvailable!: string; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Total disk size in bytes' }) - diskSizeRaw!: number; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Used disk space in bytes' }) - diskUseRaw!: number; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Available disk space in bytes' }) - diskAvailableRaw!: number; - - @ApiProperty({ type: 'number', format: 'double', description: 'Disk usage percentage (0-100)' }) - diskUsagePercentage!: number; } export class ServerVersionResponseDto { @@ -103,145 +46,16 @@ export class ServerVersionResponseDto { } } -export class ServerVersionHistoryResponseDto { - @ApiProperty({ description: 'Version history entry ID' }) - id!: string; - @ApiProperty({ description: 'When this version was first seen', format: 'date-time' }) - createdAt!: Date; - @ApiProperty({ description: 'Version string' }) - version!: string; -} - -export class UsageByUserDto { - @ApiProperty({ type: 'string', description: 'User ID' }) - userId!: string; - @ApiProperty({ type: 'string', description: 'User name' }) - userName!: string; - @ApiProperty({ type: 'integer', description: 'Number of photos' }) - photos!: number; - @ApiProperty({ type: 'integer', description: 'Number of videos' }) - videos!: number; - @ApiProperty({ type: 'integer', format: 'int64', description: 'Total storage usage in bytes' }) - usage!: number; - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for photos in bytes' }) - usagePhotos!: number; - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for videos in bytes' }) - usageVideos!: number; - @ApiProperty({ - type: 'integer', - format: 'int64', - nullable: true, - description: 'User quota size in bytes (null if unlimited)', - }) - quotaSizeInBytes!: number | null; -} - -export class ServerStatsResponseDto { - @ApiProperty({ type: 'integer', description: 'Total number of photos' }) - photos = 0; - - @ApiProperty({ type: 'integer', description: 'Total number of videos' }) - videos = 0; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Total storage usage in bytes' }) - usage = 0; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for photos in bytes' }) - usagePhotos = 0; - - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for videos in bytes' }) - usageVideos = 0; - - @ApiProperty({ - isArray: true, - type: UsageByUserDto, - title: 'Array of usage for each user', - example: [ - { - photos: 1, - videos: 1, - diskUsageRaw: 2, - usagePhotos: 1, - usageVideos: 1, - }, - ], - }) - usageByUser: UsageByUserDto[] = []; -} - -export class ServerMediaTypesResponseDto { - @ApiProperty({ description: 'Supported video MIME types' }) - video!: string[]; - @ApiProperty({ description: 'Supported image MIME types' }) - image!: string[]; - @ApiProperty({ description: 'Supported sidecar MIME types' }) - sidecar!: string[]; -} - -export class ServerThemeDto extends SystemConfigThemeDto {} - export class ServerConfigDto { - @ApiProperty({ description: 'OAuth button text' }) - oauthButtonText!: string; @ApiProperty({ description: 'Login page message' }) loginPageMessage!: string; - @ApiProperty({ type: 'integer', description: 'Number of days before trashed assets are permanently deleted' }) - trashDays!: number; @ApiProperty({ type: 'integer', description: 'Delay in days before deleted users are permanently removed' }) userDeleteDelay!: number; @ApiProperty({ description: 'Whether the server has been initialized' }) isInitialized!: boolean; - @ApiProperty({ description: 'Whether the admin has completed onboarding' }) - isOnboarded!: boolean; - @ApiProperty({ description: 'External domain URL' }) - externalDomain!: string; - @ApiProperty({ description: 'Whether public user registration is enabled' }) - publicUsers!: boolean; - @ApiProperty({ description: 'Map dark style URL' }) - mapDarkStyleUrl!: string; - @ApiProperty({ description: 'Map light style URL' }) - mapLightStyleUrl!: string; - @ApiProperty({ description: 'Whether maintenance mode is active' }) - maintenanceMode!: boolean; } export class ServerFeaturesDto { - @ApiProperty({ description: 'Whether smart search is enabled' }) - smartSearch!: boolean; - @ApiProperty({ description: 'Whether duplicate detection is enabled' }) - duplicateDetection!: boolean; - @ApiProperty({ description: 'Whether config file is available' }) - configFile!: boolean; - @ApiProperty({ description: 'Whether facial recognition is enabled' }) - facialRecognition!: boolean; - @ApiProperty({ description: 'Whether map feature is enabled' }) - map!: boolean; - @ApiProperty({ description: 'Whether trash feature is enabled' }) - trash!: boolean; - @ApiProperty({ description: 'Whether reverse geocoding is enabled' }) - reverseGeocoding!: boolean; - @ApiProperty({ description: 'Whether face import is enabled' }) - importFaces!: boolean; - @ApiProperty({ description: 'Whether OAuth is enabled' }) - oauth!: boolean; - @ApiProperty({ description: 'Whether OAuth auto-launch is enabled' }) - oauthAutoLaunch!: boolean; @ApiProperty({ description: 'Whether password login is enabled' }) passwordLogin!: boolean; - @ApiProperty({ description: 'Whether sidecar files are supported' }) - sidecar!: boolean; - @ApiProperty({ description: 'Whether search is enabled' }) - search!: boolean; - @ApiProperty({ description: 'Whether email notifications are enabled' }) - email!: boolean; - @ApiProperty({ description: 'Whether OCR is enabled' }) - ocr!: boolean; -} - -export interface ReleaseNotification { - isAvailable: boolean; - /** ISO8601 */ - checkedAt: string; - serverVersion: ServerVersionResponseDto; - releaseVersion: ServerVersionResponseDto; } diff --git a/server/src/dtos/shared-link.dto.ts b/server/src/dtos/shared-link.dto.ts deleted file mode 100644 index 1465f68953..0000000000 --- a/server/src/dtos/shared-link.dto.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; -import { SharedLink } from 'src/database'; -import { HistoryBuilder } from 'src/decorators'; -import { AlbumResponseDto, mapAlbumWithoutAssets } from 'src/dtos/album.dto'; -import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { SharedLinkType } from 'src/enum'; -import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; - -export class SharedLinkSearchDto { - @ValidateUUID({ optional: true, description: 'Filter by album ID' }) - albumId?: string; - - @ValidateUUID({ - optional: true, - description: 'Filter by shared link ID', - history: new HistoryBuilder().added('v2.5.0'), - }) - id?: string; -} - -export class SharedLinkCreateDto { - @ValidateEnum({ enum: SharedLinkType, name: 'SharedLinkType', description: 'Shared link type' }) - type!: SharedLinkType; - - @ValidateUUID({ each: true, optional: true, description: 'Asset IDs (for individual assets)' }) - assetIds?: string[]; - - @ValidateUUID({ optional: true, description: 'Album ID (for album sharing)' }) - albumId?: string; - - @ApiPropertyOptional({ description: 'Link description' }) - @Optional({ nullable: true, emptyToNull: true }) - @IsString() - description?: string | null; - - @ApiPropertyOptional({ description: 'Link password' }) - @Optional({ nullable: true, emptyToNull: true }) - @IsString() - password?: string | null; - - @ApiPropertyOptional({ description: 'Custom URL slug' }) - @Optional({ nullable: true, emptyToNull: true }) - @IsString() - slug?: string | null; - - @ValidateDate({ optional: true, nullable: true, description: 'Expiration date' }) - expiresAt?: Date | null = null; - - @ValidateBoolean({ optional: true, description: 'Allow uploads' }) - allowUpload?: boolean; - - @ValidateBoolean({ optional: true, description: 'Allow downloads', default: true }) - allowDownload?: boolean = true; - - @ValidateBoolean({ optional: true, description: 'Show metadata', default: true }) - showMetadata?: boolean = true; -} - -export class SharedLinkEditDto { - @ApiPropertyOptional({ description: 'Link description' }) - @Optional({ nullable: true, emptyToNull: true }) - @IsString() - description?: string | null; - - @ApiPropertyOptional({ description: 'Link password' }) - @Optional({ nullable: true, emptyToNull: true }) - @IsString() - password?: string | null; - - @ApiPropertyOptional({ description: 'Custom URL slug' }) - @Optional({ nullable: true, emptyToNull: true }) - @IsString() - slug?: string | null; - - @ApiPropertyOptional({ description: 'Expiration date' }) - @Optional({ nullable: true }) - expiresAt?: Date | null; - - @ValidateBoolean({ optional: true, description: 'Allow uploads' }) - allowUpload?: boolean; - - @ValidateBoolean({ optional: true, description: 'Allow downloads' }) - allowDownload?: boolean; - - @ValidateBoolean({ optional: true, description: 'Show metadata' }) - showMetadata?: boolean; - - @ValidateBoolean({ - optional: true, - description: - 'Whether to change the expiry time. Few clients cannot send null to set the expiryTime to never. Setting this flag and not sending expiryAt is considered as null instead. Clients that can send null values can ignore this.', - }) - changeExpiryTime?: boolean; -} - -export class SharedLinkPasswordDto { - @ApiPropertyOptional({ example: 'password', description: 'Link password' }) - @IsString() - @Optional() - password?: string; - - @ApiPropertyOptional({ description: 'Access token' }) - @IsString() - @Optional() - token?: string; -} -export class SharedLinkResponseDto { - @ApiProperty({ description: 'Shared link ID' }) - id!: string; - @ApiProperty({ description: 'Link description' }) - description!: string | null; - @ApiProperty({ description: 'Has password' }) - password!: string | null; - @ApiPropertyOptional({ description: 'Access token' }) - token?: string | null; - @ApiProperty({ description: 'Owner user ID' }) - userId!: string; - @ApiProperty({ description: 'Encryption key (base64url)' }) - key!: string; - - @ValidateEnum({ enum: SharedLinkType, name: 'SharedLinkType', description: 'Shared link type' }) - type!: SharedLinkType; - @ApiProperty({ description: 'Creation date' }) - createdAt!: Date; - @ApiProperty({ description: 'Expiration date' }) - expiresAt!: Date | null; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - assets!: AssetResponseDto[]; - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - album?: AlbumResponseDto; - @ApiProperty({ description: 'Allow uploads' }) - allowUpload!: boolean; - - @ApiProperty({ description: 'Allow downloads' }) - allowDownload!: boolean; - @ApiProperty({ description: 'Show metadata' }) - showMetadata!: boolean; - - @ApiProperty({ description: 'Custom URL slug' }) - slug!: string | null; -} - -export function mapSharedLink(sharedLink: SharedLink, options: { stripAssetMetadata: boolean }): SharedLinkResponseDto { - const assets = sharedLink.assets || []; - - const response = { - id: sharedLink.id, - description: sharedLink.description, - password: sharedLink.password, - userId: sharedLink.userId, - key: sharedLink.key.toString('base64url'), - type: sharedLink.type, - createdAt: sharedLink.createdAt, - expiresAt: sharedLink.expiresAt, - assets: assets.map((asset) => mapAsset(asset, { stripMetadata: options.stripAssetMetadata })), - album: sharedLink.album ? mapAlbumWithoutAssets(sharedLink.album) : undefined, - allowUpload: sharedLink.allowUpload, - allowDownload: sharedLink.allowDownload, - showMetadata: sharedLink.showExif, - slug: sharedLink.slug, - }; - - // unless we select sharedLink.album.sharedLinks this will be wrong - if (response.album) { - response.album.hasSharedLink = true; - response.album.shared = true; - } - - return response; -} diff --git a/server/src/dtos/stack.dto.ts b/server/src/dtos/stack.dto.ts deleted file mode 100644 index a76b35e08e..0000000000 --- a/server/src/dtos/stack.dto.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ArrayMinSize } from 'class-validator'; -import { Stack } from 'src/database'; -import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ValidateUUID } from 'src/validation'; - -export class StackCreateDto { - @ValidateUUID({ each: true, description: 'Asset IDs (first becomes primary, min 2)' }) - @ArrayMinSize(2) - assetIds!: string[]; -} - -export class StackSearchDto { - @ValidateUUID({ optional: true, description: 'Filter by primary asset ID' }) - primaryAssetId?: string; -} - -export class StackUpdateDto { - @ValidateUUID({ optional: true, description: 'Primary asset ID' }) - primaryAssetId?: string; -} - -export class StackResponseDto { - @ApiProperty({ description: 'Stack ID' }) - id!: string; - @ApiProperty({ description: 'Primary asset ID' }) - primaryAssetId!: string; - @ApiProperty({ description: 'Stack assets' }) - assets!: AssetResponseDto[]; -} - -export const mapStack = (stack: Stack, { auth }: { auth?: AuthDto }) => { - const primary = stack.assets.filter((asset) => asset.id === stack.primaryAssetId); - const others = stack.assets.filter((asset) => asset.id !== stack.primaryAssetId); - - return { - id: stack.id, - primaryAssetId: stack.primaryAssetId, - assets: [...primary, ...others].map((asset) => mapAsset(asset, { auth })), - }; -}; diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts deleted file mode 100644 index 59d7d373f0..0000000000 --- a/server/src/dtos/sync.dto.ts +++ /dev/null @@ -1,539 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-function-type */ -import { ApiProperty } from '@nestjs/swagger'; -import { ArrayMaxSize, IsInt, IsPositive, IsString } from 'class-validator'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; -import { - AlbumUserRole, - AssetOrder, - AssetType, - AssetVisibility, - MemoryType, - SyncEntityType, - SyncRequestType, - UserAvatarColor, - UserMetadataKey, -} from 'src/enum'; -import { UserMetadata } from 'src/types'; -import { ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; - -export class AssetFullSyncDto { - @ValidateUUID({ optional: true, description: 'Last asset ID (pagination)' }) - lastId?: string; - - @ValidateDate({ description: 'Sync assets updated until this date' }) - updatedUntil!: Date; - - @ApiProperty({ type: 'integer', description: 'Maximum number of assets to return' }) - @IsInt() - @IsPositive() - limit!: number; - - @ValidateUUID({ optional: true, description: 'Filter by user ID' }) - userId?: string; -} - -export class AssetDeltaSyncDto { - @ValidateDate({ description: 'Sync assets updated after this date' }) - updatedAfter!: Date; - - @ValidateUUID({ each: true, description: 'User IDs to sync' }) - userIds!: string[]; -} - -export class AssetDeltaSyncResponseDto { - @ApiProperty({ description: 'Whether full sync is needed' }) - needsFullSync!: boolean; - @ApiProperty({ description: 'Upserted assets' }) - upserted!: AssetResponseDto[]; - @ApiProperty({ description: 'Deleted asset IDs' }) - deleted!: string[]; -} - -export const extraSyncModels: Function[] = []; - -export const ExtraModel = (): ClassDecorator => { - // eslint-disable-next-line unicorn/consistent-function-scoping - return (object: Function) => { - extraSyncModels.push(object); - }; -}; - -@ExtraModel() -export class SyncUserV1 { - @ApiProperty({ description: 'User ID' }) - id!: string; - @ApiProperty({ description: 'User name' }) - name!: string; - @ApiProperty({ description: 'User email' }) - email!: string; - @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', description: 'User avatar color' }) - avatarColor!: UserAvatarColor | null; - @ApiProperty({ description: 'User deleted at' }) - deletedAt!: Date | null; - @ApiProperty({ description: 'User has profile image' }) - hasProfileImage!: boolean; - @ApiProperty({ description: 'User profile changed at' }) - profileChangedAt!: Date; -} - -@ExtraModel() -export class SyncAuthUserV1 extends SyncUserV1 { - @ApiProperty({ description: 'User is admin' }) - isAdmin!: boolean; - @ApiProperty({ description: 'User pin code' }) - pinCode!: string | null; - @ApiProperty({ description: 'User OAuth ID' }) - oauthId!: string; - @ApiProperty({ description: 'User storage label' }) - storageLabel!: string | null; - @ApiProperty({ type: 'integer' }) - quotaSizeInBytes!: number | null; - @ApiProperty({ type: 'integer' }) - quotaUsageInBytes!: number; -} - -@ExtraModel() -export class SyncUserDeleteV1 { - @ApiProperty({ description: 'User ID' }) - userId!: string; -} - -@ExtraModel() -export class SyncPartnerV1 { - @ApiProperty({ description: 'Shared by ID' }) - sharedById!: string; - @ApiProperty({ description: 'Shared with ID' }) - sharedWithId!: string; - @ApiProperty({ description: 'In timeline' }) - inTimeline!: boolean; -} - -@ExtraModel() -export class SyncPartnerDeleteV1 { - @ApiProperty({ description: 'Shared by ID' }) - sharedById!: string; - @ApiProperty({ description: 'Shared with ID' }) - sharedWithId!: string; -} - -@ExtraModel() -export class SyncAssetV1 { - @ApiProperty({ description: 'Asset ID' }) - id!: string; - @ApiProperty({ description: 'Owner ID' }) - ownerId!: string; - @ApiProperty({ description: 'Original file name' }) - originalFileName!: string; - @ApiProperty({ description: 'Thumbhash' }) - thumbhash!: string | null; - @ApiProperty({ description: 'Checksum' }) - checksum!: string; - @ApiProperty({ description: 'File created at' }) - fileCreatedAt!: Date | null; - @ApiProperty({ description: 'File modified at' }) - fileModifiedAt!: Date | null; - @ApiProperty({ description: 'Local date time' }) - localDateTime!: Date | null; - @ApiProperty({ description: 'Duration' }) - duration!: string | null; - @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', description: 'Asset type' }) - type!: AssetType; - @ApiProperty({ description: 'Deleted at' }) - deletedAt!: Date | null; - @ApiProperty({ description: 'Is favorite' }) - isFavorite!: boolean; - @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', description: 'Asset visibility' }) - visibility!: AssetVisibility; - @ApiProperty({ description: 'Live photo video ID' }) - livePhotoVideoId!: string | null; - @ApiProperty({ description: 'Stack ID' }) - stackId!: string | null; - @ApiProperty({ description: 'Library ID' }) - libraryId!: string | null; - @ApiProperty({ type: 'integer', description: 'Asset width' }) - width!: number | null; - @ApiProperty({ type: 'integer', description: 'Asset height' }) - height!: number | null; - @ApiProperty({ description: 'Is edited' }) - isEdited!: boolean; -} - -@ExtraModel() -export class SyncAssetDeleteV1 { - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; -} - -@ExtraModel() -export class SyncAssetExifV1 { - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; - @ApiProperty({ description: 'Description' }) - description!: string | null; - @ApiProperty({ type: 'integer', description: 'Exif image width' }) - exifImageWidth!: number | null; - @ApiProperty({ type: 'integer', description: 'Exif image height' }) - exifImageHeight!: number | null; - @ApiProperty({ type: 'integer', description: 'File size in byte' }) - fileSizeInByte!: number | null; - @ApiProperty({ description: 'Orientation' }) - orientation!: string | null; - @ApiProperty({ description: 'Date time original' }) - dateTimeOriginal!: Date | null; - @ApiProperty({ description: 'Modify date' }) - modifyDate!: Date | null; - @ApiProperty({ description: 'Time zone' }) - timeZone!: string | null; - @ApiProperty({ type: 'number', format: 'double', description: 'Latitude' }) - latitude!: number | null; - @ApiProperty({ type: 'number', format: 'double', description: 'Longitude' }) - longitude!: number | null; - @ApiProperty({ description: 'Projection type' }) - projectionType!: string | null; - @ApiProperty({ description: 'City' }) - city!: string | null; - @ApiProperty({ description: 'State' }) - state!: string | null; - @ApiProperty({ description: 'Country' }) - country!: string | null; - @ApiProperty({ description: 'Make' }) - make!: string | null; - @ApiProperty({ description: 'Model' }) - model!: string | null; - @ApiProperty({ description: 'Lens model' }) - lensModel!: string | null; - @ApiProperty({ type: 'number', format: 'double', description: 'F number' }) - fNumber!: number | null; - @ApiProperty({ type: 'number', format: 'double', description: 'Focal length' }) - focalLength!: number | null; - @ApiProperty({ type: 'integer', description: 'ISO' }) - iso!: number | null; - @ApiProperty({ description: 'Exposure time' }) - exposureTime!: string | null; - @ApiProperty({ description: 'Profile description' }) - profileDescription!: string | null; - @ApiProperty({ type: 'integer', description: 'Rating' }) - rating!: number | null; - @ApiProperty({ type: 'number', format: 'double', description: 'FPS' }) - fps!: number | null; -} - -@ExtraModel() -export class SyncAssetMetadataV1 { - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; - @ApiProperty({ description: 'Key' }) - key!: string; - @ApiProperty({ description: 'Value' }) - value!: object; -} - -@ExtraModel() -export class SyncAssetMetadataDeleteV1 { - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; - @ApiProperty({ description: 'Key' }) - key!: string; -} - -@ExtraModel() -export class SyncAlbumDeleteV1 { - @ApiProperty({ description: 'Album ID' }) - albumId!: string; -} - -@ExtraModel() -export class SyncAlbumUserDeleteV1 { - @ApiProperty({ description: 'Album ID' }) - albumId!: string; - @ApiProperty({ description: 'User ID' }) - userId!: string; -} - -@ExtraModel() -export class SyncAlbumUserV1 { - @ApiProperty({ description: 'Album ID' }) - albumId!: string; - @ApiProperty({ description: 'User ID' }) - userId!: string; - @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', description: 'Album user role' }) - role!: AlbumUserRole; -} - -@ExtraModel() -export class SyncAlbumV1 { - @ApiProperty({ description: 'Album ID' }) - id!: string; - @ApiProperty({ description: 'Owner ID' }) - ownerId!: string; - @ApiProperty({ description: 'Album name' }) - name!: string; - @ApiProperty({ description: 'Album description' }) - description!: string; - @ApiProperty({ description: 'Created at' }) - createdAt!: Date; - @ApiProperty({ description: 'Updated at' }) - updatedAt!: Date; - @ApiProperty({ description: 'Thumbnail asset ID' }) - thumbnailAssetId!: string | null; - @ApiProperty({ description: 'Is activity enabled' }) - isActivityEnabled!: boolean; - @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' }) - order!: AssetOrder; -} - -@ExtraModel() -export class SyncAlbumToAssetV1 { - @ApiProperty({ description: 'Album ID' }) - albumId!: string; - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; -} - -@ExtraModel() -export class SyncAlbumToAssetDeleteV1 { - @ApiProperty({ description: 'Album ID' }) - albumId!: string; - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; -} - -@ExtraModel() -export class SyncMemoryV1 { - @ApiProperty({ description: 'Memory ID' }) - id!: string; - @ApiProperty({ description: 'Created at' }) - createdAt!: Date; - @ApiProperty({ description: 'Updated at' }) - updatedAt!: Date; - @ApiProperty({ description: 'Deleted at' }) - deletedAt!: Date | null; - @ApiProperty({ description: 'Owner ID' }) - ownerId!: string; - @ValidateEnum({ enum: MemoryType, name: 'MemoryType', description: 'Memory type' }) - type!: MemoryType; - @ApiProperty({ description: 'Data' }) - data!: object; - @ApiProperty({ description: 'Is saved' }) - isSaved!: boolean; - @ApiProperty({ description: 'Memory at' }) - memoryAt!: Date; - @ApiProperty({ description: 'Seen at' }) - seenAt!: Date | null; - @ApiProperty({ description: 'Show at' }) - showAt!: Date | null; - @ApiProperty({ description: 'Hide at' }) - hideAt!: Date | null; -} - -@ExtraModel() -export class SyncMemoryDeleteV1 { - @ApiProperty({ description: 'Memory ID' }) - memoryId!: string; -} - -@ExtraModel() -export class SyncMemoryAssetV1 { - @ApiProperty({ description: 'Memory ID' }) - memoryId!: string; - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; -} - -@ExtraModel() -export class SyncMemoryAssetDeleteV1 { - @ApiProperty({ description: 'Memory ID' }) - memoryId!: string; - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; -} - -@ExtraModel() -export class SyncStackV1 { - @ApiProperty({ description: 'Stack ID' }) - id!: string; - @ApiProperty({ description: 'Created at' }) - createdAt!: Date; - @ApiProperty({ description: 'Updated at' }) - updatedAt!: Date; - @ApiProperty({ description: 'Primary asset ID' }) - primaryAssetId!: string; - @ApiProperty({ description: 'Owner ID' }) - ownerId!: string; -} - -@ExtraModel() -export class SyncStackDeleteV1 { - @ApiProperty({ description: 'Stack ID' }) - stackId!: string; -} - -@ExtraModel() -export class SyncPersonV1 { - @ApiProperty({ description: 'Person ID' }) - id!: string; - @ApiProperty({ description: 'Created at' }) - createdAt!: Date; - @ApiProperty({ description: 'Updated at' }) - updatedAt!: Date; - @ApiProperty({ description: 'Owner ID' }) - ownerId!: string; - @ApiProperty({ description: 'Person name' }) - name!: string; - @ApiProperty({ description: 'Birth date' }) - birthDate!: Date | null; - @ApiProperty({ description: 'Is hidden' }) - isHidden!: boolean; - @ApiProperty({ description: 'Is favorite' }) - isFavorite!: boolean; - @ApiProperty({ description: 'Color' }) - color!: string | null; - @ApiProperty({ description: 'Face asset ID' }) - faceAssetId!: string | null; -} - -@ExtraModel() -export class SyncPersonDeleteV1 { - @ApiProperty({ description: 'Person ID' }) - personId!: string; -} - -@ExtraModel() -export class SyncAssetFaceV1 { - @ApiProperty({ description: 'Asset face ID' }) - id!: string; - @ApiProperty({ description: 'Asset ID' }) - assetId!: string; - @ApiProperty({ description: 'Person ID' }) - personId!: string | null; - @ApiProperty({ type: 'integer' }) - imageWidth!: number; - @ApiProperty({ type: 'integer' }) - imageHeight!: number; - @ApiProperty({ type: 'integer' }) - boundingBoxX1!: number; - @ApiProperty({ type: 'integer' }) - boundingBoxY1!: number; - @ApiProperty({ type: 'integer' }) - boundingBoxX2!: number; - @ApiProperty({ type: 'integer' }) - boundingBoxY2!: number; - @ApiProperty({ description: 'Source type' }) - sourceType!: string; -} - -@ExtraModel() -export class SyncAssetFaceDeleteV1 { - @ApiProperty({ description: 'Asset face ID' }) - assetFaceId!: string; -} - -@ExtraModel() -export class SyncUserMetadataV1 { - @ApiProperty({ description: 'User ID' }) - userId!: string; - @ValidateEnum({ enum: UserMetadataKey, name: 'UserMetadataKey', description: 'User metadata key' }) - key!: UserMetadataKey; - @ApiProperty({ description: 'User metadata value' }) - value!: UserMetadata[UserMetadataKey]; -} - -@ExtraModel() -export class SyncUserMetadataDeleteV1 { - @ApiProperty({ description: 'User ID' }) - userId!: string; - @ValidateEnum({ enum: UserMetadataKey, name: 'UserMetadataKey', description: 'User metadata key' }) - key!: UserMetadataKey; -} - -@ExtraModel() -export class SyncAckV1 {} - -@ExtraModel() -export class SyncResetV1 {} - -@ExtraModel() -export class SyncCompleteV1 {} - -export type SyncItem = { - [SyncEntityType.AuthUserV1]: SyncAuthUserV1; - [SyncEntityType.UserV1]: SyncUserV1; - [SyncEntityType.UserDeleteV1]: SyncUserDeleteV1; - [SyncEntityType.PartnerV1]: SyncPartnerV1; - [SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1; - [SyncEntityType.AssetV1]: SyncAssetV1; - [SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1; - [SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1; - [SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1; - [SyncEntityType.AssetExifV1]: SyncAssetExifV1; - [SyncEntityType.PartnerAssetV1]: SyncAssetV1; - [SyncEntityType.PartnerAssetBackfillV1]: SyncAssetV1; - [SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1; - [SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1; - [SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1; - [SyncEntityType.AlbumV1]: SyncAlbumV1; - [SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1; - [SyncEntityType.AlbumUserV1]: SyncAlbumUserV1; - [SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1; - [SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1; - [SyncEntityType.AlbumAssetCreateV1]: SyncAssetV1; - [SyncEntityType.AlbumAssetUpdateV1]: SyncAssetV1; - [SyncEntityType.AlbumAssetBackfillV1]: SyncAssetV1; - [SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1; - [SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1; - [SyncEntityType.AlbumAssetExifBackfillV1]: SyncAssetExifV1; - [SyncEntityType.AlbumToAssetV1]: SyncAlbumToAssetV1; - [SyncEntityType.AlbumToAssetBackfillV1]: SyncAlbumToAssetV1; - [SyncEntityType.AlbumToAssetDeleteV1]: SyncAlbumToAssetDeleteV1; - [SyncEntityType.MemoryV1]: SyncMemoryV1; - [SyncEntityType.MemoryDeleteV1]: SyncMemoryDeleteV1; - [SyncEntityType.MemoryToAssetV1]: SyncMemoryAssetV1; - [SyncEntityType.MemoryToAssetDeleteV1]: SyncMemoryAssetDeleteV1; - [SyncEntityType.StackV1]: SyncStackV1; - [SyncEntityType.StackDeleteV1]: SyncStackDeleteV1; - [SyncEntityType.PartnerStackBackfillV1]: SyncStackV1; - [SyncEntityType.PartnerStackDeleteV1]: SyncStackDeleteV1; - [SyncEntityType.PartnerStackV1]: SyncStackV1; - [SyncEntityType.PersonV1]: SyncPersonV1; - [SyncEntityType.PersonDeleteV1]: SyncPersonDeleteV1; - [SyncEntityType.AssetFaceV1]: SyncAssetFaceV1; - [SyncEntityType.AssetFaceDeleteV1]: SyncAssetFaceDeleteV1; - [SyncEntityType.UserMetadataV1]: SyncUserMetadataV1; - [SyncEntityType.UserMetadataDeleteV1]: SyncUserMetadataDeleteV1; - [SyncEntityType.SyncAckV1]: SyncAckV1; - [SyncEntityType.SyncCompleteV1]: SyncCompleteV1; - [SyncEntityType.SyncResetV1]: SyncResetV1; -}; - -export class SyncStreamDto { - @ValidateEnum({ enum: SyncRequestType, name: 'SyncRequestType', each: true, description: 'Sync request types' }) - types!: SyncRequestType[]; - - @ValidateBoolean({ optional: true, description: 'Reset sync state' }) - reset?: boolean; -} - -export class SyncAckDto { - @ValidateEnum({ enum: SyncEntityType, name: 'SyncEntityType', description: 'Sync entity type' }) - type!: SyncEntityType; - @ApiProperty({ description: 'Acknowledgment ID' }) - ack!: string; -} - -export class SyncAckSetDto { - @ApiProperty({ description: 'Acknowledgment IDs (max 1000)' }) - @ArrayMaxSize(1000) - @IsString({ each: true }) - acks!: string[]; -} - -export class SyncAckDeleteDto { - @ValidateEnum({ - enum: SyncEntityType, - name: 'SyncEntityType', - optional: true, - each: true, - description: 'Sync entity types to delete acks for', - }) - types?: SyncEntityType[]; -} diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts deleted file mode 100644 index 7a0dcb6f3a..0000000000 --- a/server/src/dtos/system-config.dto.ts +++ /dev/null @@ -1,854 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - ArrayMinSize, - IsInt, - IsNotEmpty, - IsNumber, - IsObject, - IsPositive, - IsString, - IsUrl, - Max, - Min, - ValidateIf, - ValidateNested, -} from 'class-validator'; -import { SystemConfig } from 'src/config'; -import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig, OcrConfig } from 'src/dtos/model-config.dto'; -import { - AudioCodec, - CQMode, - Colorspace, - ImageFormat, - LogLevel, - OAuthTokenEndpointAuthMethod, - QueueName, - ToneMapping, - TranscodeHardwareAcceleration, - TranscodePolicy, - VideoCodec, - VideoContainer, -} from 'src/enum'; -import { ConcurrentQueueName } from 'src/types'; -import { IsCronExpression, IsDateStringFormat, Optional, ValidateBoolean, ValidateEnum } from 'src/validation'; - -const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled; -const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled; -const isOAuthOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled; -const isEmailNotificationEnabled = (config: SystemConfigSmtpDto) => config.enabled; -const isDatabaseBackupEnabled = (config: DatabaseBackupConfig) => config.enabled; - -export class DatabaseBackupConfig { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @ValidateIf(isDatabaseBackupEnabled) - @IsNotEmpty() - @IsCronExpression() - @IsString() - @ApiProperty({ description: 'Cron expression' }) - cronExpression!: string; - - @IsInt() - @IsPositive() - @IsNotEmpty() - @ApiProperty({ description: 'Keep last amount' }) - keepLastAmount!: number; -} - -export class SystemConfigBackupsDto { - @Type(() => DatabaseBackupConfig) - @ValidateNested() - @IsObject() - database!: DatabaseBackupConfig; -} - -export class SystemConfigFFmpegDto { - @IsInt() - @Min(0) - @Max(51) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'CRF' }) - crf!: number; - - @IsInt() - @Min(0) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Threads' }) - threads!: number; - - @IsString() - @ApiProperty({ description: 'Preset' }) - preset!: string; - - @ValidateEnum({ enum: VideoCodec, name: 'VideoCodec', description: 'Target video codec' }) - targetVideoCodec!: VideoCodec; - - @ValidateEnum({ enum: VideoCodec, name: 'VideoCodec', each: true, description: 'Accepted video codecs' }) - acceptedVideoCodecs!: VideoCodec[]; - - @ValidateEnum({ enum: AudioCodec, name: 'AudioCodec', description: 'Target audio codec' }) - targetAudioCodec!: AudioCodec; - - @ValidateEnum({ enum: AudioCodec, name: 'AudioCodec', each: true, description: 'Accepted audio codecs' }) - acceptedAudioCodecs!: AudioCodec[]; - - @ValidateEnum({ enum: VideoContainer, name: 'VideoContainer', each: true, description: 'Accepted containers' }) - acceptedContainers!: VideoContainer[]; - - @IsString() - @ApiProperty({ description: 'Target resolution' }) - targetResolution!: string; - - @IsString() - @ApiProperty({ description: 'Max bitrate' }) - maxBitrate!: string; - - @IsInt() - @Min(-1) - @Max(16) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'B-frames' }) - bframes!: number; - - @IsInt() - @Min(0) - @Max(6) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'References' }) - refs!: number; - - @IsInt() - @Min(0) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'GOP size' }) - gopSize!: number; - - @ValidateBoolean({ description: 'Temporal AQ' }) - temporalAQ!: boolean; - - @ValidateEnum({ enum: CQMode, name: 'CQMode', description: 'CQ mode' }) - cqMode!: CQMode; - - @ValidateBoolean({ description: 'Two pass' }) - twoPass!: boolean; - - @ApiProperty({ description: 'Preferred hardware device' }) - @IsString() - preferredHwDevice!: string; - - @ValidateEnum({ enum: TranscodePolicy, name: 'TranscodePolicy', description: 'Transcode policy' }) - transcode!: TranscodePolicy; - - @ValidateEnum({ - enum: TranscodeHardwareAcceleration, - name: 'TranscodeHWAccel', - description: 'Transcode hardware acceleration', - }) - accel!: TranscodeHardwareAcceleration; - - @ValidateBoolean({ description: 'Accelerated decode' }) - accelDecode!: boolean; - - @ValidateEnum({ enum: ToneMapping, name: 'ToneMapping', description: 'Tone mapping' }) - tonemap!: ToneMapping; -} - -class JobSettingsDto { - @IsInt() - @IsPositive() - @ApiProperty({ type: 'integer', description: 'Concurrency' }) - concurrency!: number; -} - -class SystemConfigJobDto implements Record { - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.ThumbnailGeneration]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.MetadataExtraction]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.VideoConversion]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.SmartSearch]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Migration]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.BackgroundTask]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Search]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.FaceDetection]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Ocr]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Sidecar]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Library]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Notification]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Workflow]!: JobSettingsDto; - - @ApiProperty({ type: JobSettingsDto, description: undefined }) - @ValidateNested() - @IsObject() - @Type(() => JobSettingsDto) - [QueueName.Editor]!: JobSettingsDto; -} - -class SystemConfigLibraryScanDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @ValidateIf(isLibraryScanEnabled) - @IsNotEmpty() - @IsCronExpression() - @IsString() - cronExpression!: string; -} - -class SystemConfigLibraryWatchDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; -} - -class SystemConfigLibraryDto { - @Type(() => SystemConfigLibraryScanDto) - @ValidateNested() - @IsObject() - scan!: SystemConfigLibraryScanDto; - - @Type(() => SystemConfigLibraryWatchDto) - @ValidateNested() - @IsObject() - watch!: SystemConfigLibraryWatchDto; -} - -class SystemConfigLoggingDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @ValidateEnum({ enum: LogLevel, name: 'LogLevel' }) - level!: LogLevel; -} - -class MachineLearningAvailabilityChecksDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @IsInt() - timeout!: number; - - @IsInt() - interval!: number; -} - -class SystemConfigMachineLearningDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @IsUrl({ require_tld: false, allow_underscores: true }, { each: true }) - @ArrayMinSize(1) - @ValidateIf((dto) => dto.enabled) - @ApiProperty({ type: 'array', items: { type: 'string', format: 'uri' }, minItems: 1 }) - urls!: string[]; - - @Type(() => MachineLearningAvailabilityChecksDto) - @ValidateNested() - @IsObject() - availabilityChecks!: MachineLearningAvailabilityChecksDto; - - @Type(() => CLIPConfig) - @ValidateNested() - @IsObject() - clip!: CLIPConfig; - - @Type(() => DuplicateDetectionConfig) - @ValidateNested() - @IsObject() - duplicateDetection!: DuplicateDetectionConfig; - - @Type(() => FacialRecognitionConfig) - @ValidateNested() - @IsObject() - facialRecognition!: FacialRecognitionConfig; - - @Type(() => OcrConfig) - @ValidateNested() - @IsObject() - ocr!: OcrConfig; -} - -enum MapTheme { - LIGHT = 'light', - DARK = 'dark', -} - -export class MapThemeDto { - @ValidateEnum({ enum: MapTheme, name: 'MapTheme' }) - theme!: MapTheme; -} - -class SystemConfigMapDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @IsNotEmpty() - @IsUrl() - lightStyle!: string; - - @IsNotEmpty() - @IsUrl() - darkStyle!: string; -} - -class SystemConfigNewVersionCheckDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; -} - -class SystemConfigNightlyTasksDto { - @IsDateStringFormat('HH:mm', { message: 'startTime must be in HH:mm format' }) - startTime!: string; - - @ValidateBoolean({ description: 'Database cleanup' }) - databaseCleanup!: boolean; - - @ValidateBoolean({ description: 'Missing thumbnails' }) - missingThumbnails!: boolean; - - @ValidateBoolean({ description: 'Cluster new faces' }) - clusterNewFaces!: boolean; - - @ValidateBoolean({ description: 'Generate memories' }) - generateMemories!: boolean; - - @ValidateBoolean({ description: 'Sync quota usage' }) - syncQuotaUsage!: boolean; -} - -class SystemConfigOAuthDto { - @ValidateBoolean({ description: 'Auto launch' }) - autoLaunch!: boolean; - - @ValidateBoolean({ description: 'Auto register' }) - autoRegister!: boolean; - - @IsString() - @ApiProperty({ description: 'Button text' }) - buttonText!: string; - - @ValidateIf(isOAuthEnabled) - @IsNotEmpty() - @IsString() - @ApiProperty({ description: 'Client ID' }) - clientId!: string; - - @ValidateIf(isOAuthEnabled) - @IsString() - @ApiProperty({ description: 'Client secret' }) - clientSecret!: string; - - @ValidateEnum({ - enum: OAuthTokenEndpointAuthMethod, - name: 'OAuthTokenEndpointAuthMethod', - description: 'Token endpoint auth method', - }) - tokenEndpointAuthMethod!: OAuthTokenEndpointAuthMethod; - - @IsInt() - @IsPositive() - @Optional() - @ApiProperty({ type: 'integer', description: 'Timeout' }) - timeout!: number; - - @IsNumber() - @Min(0) - @Optional({ nullable: true }) - @ApiProperty({ type: 'integer', format: 'int64', description: 'Default storage quota' }) - defaultStorageQuota!: number | null; - - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @ValidateIf(isOAuthEnabled) - @IsNotEmpty() - @IsString() - @ApiProperty({ description: 'Issuer URL' }) - issuerUrl!: string; - - @ValidateBoolean({ description: 'Mobile override enabled' }) - mobileOverrideEnabled!: boolean; - - @ValidateIf(isOAuthOverrideEnabled) - @IsUrl() - @ApiProperty({ description: 'Mobile redirect URI' }) - mobileRedirectUri!: string; - - @IsString() - @ApiProperty({ description: 'Scope' }) - scope!: string; - - @IsString() - @IsNotEmpty() - signingAlgorithm!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ description: 'Profile signing algorithm' }) - profileSigningAlgorithm!: string; - - @IsString() - @ApiProperty({ description: 'Storage label claim' }) - storageLabelClaim!: string; - - @IsString() - @ApiProperty({ description: 'Storage quota claim' }) - storageQuotaClaim!: string; - - @IsString() - @ApiProperty({ description: 'Role claim' }) - roleClaim!: string; -} - -class SystemConfigPasswordLoginDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; -} - -class SystemConfigReverseGeocodingDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; -} - -class SystemConfigFacesDto { - @ValidateBoolean({ description: 'Import' }) - import!: boolean; -} - -class SystemConfigMetadataDto { - @Type(() => SystemConfigFacesDto) - @ValidateNested() - @IsObject() - faces!: SystemConfigFacesDto; -} - -class SystemConfigServerDto { - @ValidateIf((_, value: string) => value !== '') - @IsUrl({ require_tld: false, require_protocol: true, protocols: ['http', 'https'] }) - @ApiProperty({ description: 'External domain' }) - externalDomain!: string; - - @IsString() - @ApiProperty({ description: 'Login page message' }) - loginPageMessage!: string; - - @ValidateBoolean({ description: 'Public users' }) - publicUsers!: boolean; -} - -class SystemConfigSmtpTransportDto { - @ValidateBoolean({ description: 'Whether to ignore SSL certificate errors' }) - ignoreCert!: boolean; - - @ApiProperty({ description: 'SMTP server hostname' }) - @IsNotEmpty() - @IsString() - host!: string; - - @ApiProperty({ description: 'SMTP server port', type: Number, minimum: 0, maximum: 65_535 }) - @IsNumber() - @Min(0) - @Max(65_535) - port!: number; - - @ValidateBoolean({ description: 'Whether to use secure connection (TLS/SSL)' }) - secure!: boolean; - - @ApiProperty({ description: 'SMTP username' }) - @IsString() - username!: string; - - @ApiProperty({ description: 'SMTP password' }) - @IsString() - password!: string; -} - -export class SystemConfigSmtpDto { - @ValidateBoolean({ description: 'Whether SMTP email notifications are enabled' }) - enabled!: boolean; - - @ApiProperty({ description: 'Email address to send from' }) - @ValidateIf(isEmailNotificationEnabled) - @IsNotEmpty() - @IsString() - @IsNotEmpty() - from!: string; - - @ApiProperty({ description: 'Email address for replies' }) - @IsString() - replyTo!: string; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @ValidateIf(isEmailNotificationEnabled) - @Type(() => SystemConfigSmtpTransportDto) - @ValidateNested() - @IsObject() - transport!: SystemConfigSmtpTransportDto; -} - -class SystemConfigNotificationsDto { - @Type(() => SystemConfigSmtpDto) - @ValidateNested() - @IsObject() - smtp!: SystemConfigSmtpDto; -} - -class SystemConfigTemplateEmailsDto { - @IsString() - albumInviteTemplate!: string; - - @IsString() - welcomeTemplate!: string; - - @IsString() - albumUpdateTemplate!: string; -} - -class SystemConfigTemplatesDto { - @Type(() => SystemConfigTemplateEmailsDto) - @ValidateNested() - @IsObject() - email!: SystemConfigTemplateEmailsDto; -} - -class SystemConfigStorageTemplateDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @ValidateBoolean({ description: 'Hash verification enabled' }) - hashVerificationEnabled!: boolean; - - @IsNotEmpty() - @IsString() - @ApiProperty({ description: 'Template' }) - template!: string; -} - -export class SystemConfigTemplateStorageOptionDto { - @ApiProperty({ description: 'Available year format options for storage template' }) - yearOptions!: string[]; - @ApiProperty({ description: 'Available month format options for storage template' }) - monthOptions!: string[]; - @ApiProperty({ description: 'Available week format options for storage template' }) - weekOptions!: string[]; - @ApiProperty({ description: 'Available day format options for storage template' }) - dayOptions!: string[]; - @ApiProperty({ description: 'Available hour format options for storage template' }) - hourOptions!: string[]; - @ApiProperty({ description: 'Available minute format options for storage template' }) - minuteOptions!: string[]; - @ApiProperty({ description: 'Available second format options for storage template' }) - secondOptions!: string[]; - @ApiProperty({ description: 'Available preset template options' }) - presetOptions!: string[]; -} - -export class SystemConfigThemeDto { - @ApiProperty({ description: 'Custom CSS for theming' }) - @IsString() - customCss!: string; -} - -class SystemConfigGeneratedImageDto { - @ValidateEnum({ enum: ImageFormat, name: 'ImageFormat', description: 'Image format' }) - format!: ImageFormat; - - @IsInt() - @Min(1) - @Max(100) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Quality' }) - quality!: number; - - @IsInt() - @Min(1) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Size' }) - size!: number; - - @ValidateBoolean({ optional: true, default: false }) - progressive?: boolean; -} - -class SystemConfigGeneratedFullsizeImageDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @ValidateEnum({ enum: ImageFormat, name: 'ImageFormat', description: 'Image format' }) - format!: ImageFormat; - - @IsInt() - @Min(1) - @Max(100) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Quality' }) - quality!: number; - - @ValidateBoolean({ optional: true, default: false, description: 'Progressive' }) - progressive?: boolean; -} - -export class SystemConfigImageDto { - @Type(() => SystemConfigGeneratedImageDto) - @ValidateNested() - @IsObject() - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - thumbnail!: SystemConfigGeneratedImageDto; - - @Type(() => SystemConfigGeneratedImageDto) - @ValidateNested() - @IsObject() - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - preview!: SystemConfigGeneratedImageDto; - - @Type(() => SystemConfigGeneratedFullsizeImageDto) - @ValidateNested() - @IsObject() - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - fullsize!: SystemConfigGeneratedFullsizeImageDto; - - @ValidateEnum({ enum: Colorspace, name: 'Colorspace', description: 'Colorspace' }) - colorspace!: Colorspace; - - @ValidateBoolean({ description: 'Extract embedded' }) - extractEmbedded!: boolean; -} - -class SystemConfigTrashDto { - @ValidateBoolean({ description: 'Enabled' }) - enabled!: boolean; - - @IsInt() - @Min(0) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Days' }) - days!: number; -} - -class SystemConfigUserDto { - @IsInt() - @Min(1) - @Type(() => Number) - @ApiProperty({ type: 'integer', description: 'Delete delay' }) - deleteDelay!: number; -} - -export class SystemConfigDto implements SystemConfig { - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigBackupsDto) - @ValidateNested() - @IsObject() - backup!: SystemConfigBackupsDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigFFmpegDto) - @ValidateNested() - @IsObject() - ffmpeg!: SystemConfigFFmpegDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigLoggingDto) - @ValidateNested() - @IsObject() - logging!: SystemConfigLoggingDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigMachineLearningDto) - @ValidateNested() - @IsObject() - machineLearning!: SystemConfigMachineLearningDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigMapDto) - @ValidateNested() - @IsObject() - map!: SystemConfigMapDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigNewVersionCheckDto) - @ValidateNested() - @IsObject() - newVersionCheck!: SystemConfigNewVersionCheckDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigNightlyTasksDto) - @ValidateNested() - @IsObject() - nightlyTasks!: SystemConfigNightlyTasksDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigOAuthDto) - @ValidateNested() - @IsObject() - oauth!: SystemConfigOAuthDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigPasswordLoginDto) - @ValidateNested() - @IsObject() - passwordLogin!: SystemConfigPasswordLoginDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigReverseGeocodingDto) - @ValidateNested() - @IsObject() - reverseGeocoding!: SystemConfigReverseGeocodingDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigMetadataDto) - @ValidateNested() - @IsObject() - metadata!: SystemConfigMetadataDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigStorageTemplateDto) - @ValidateNested() - @IsObject() - storageTemplate!: SystemConfigStorageTemplateDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigJobDto) - @ValidateNested() - @IsObject() - job!: SystemConfigJobDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigImageDto) - @ValidateNested() - @IsObject() - image!: SystemConfigImageDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigTrashDto) - @ValidateNested() - @IsObject() - trash!: SystemConfigTrashDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigThemeDto) - @ValidateNested() - @IsObject() - theme!: SystemConfigThemeDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigLibraryDto) - @ValidateNested() - @IsObject() - library!: SystemConfigLibraryDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigNotificationsDto) - @ValidateNested() - @IsObject() - notifications!: SystemConfigNotificationsDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigTemplatesDto) - @ValidateNested() - @IsObject() - templates!: SystemConfigTemplatesDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigServerDto) - @ValidateNested() - @IsObject() - server!: SystemConfigServerDto; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - @Type(() => SystemConfigUserDto) - @ValidateNested() - @IsObject() - user!: SystemConfigUserDto; -} - -export function mapConfig(config: SystemConfig): SystemConfigDto { - return config; -} diff --git a/server/src/dtos/system-metadata.dto.ts b/server/src/dtos/system-metadata.dto.ts deleted file mode 100644 index 0a4d55c970..0000000000 --- a/server/src/dtos/system-metadata.dto.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ValidateBoolean } from 'src/validation'; - -export class AdminOnboardingUpdateDto { - @ValidateBoolean({ description: 'Is admin onboarded' }) - isOnboarded!: boolean; -} - -export class AdminOnboardingResponseDto { - @ValidateBoolean({ description: 'Is admin onboarded' }) - isOnboarded!: boolean; -} - -export class ReverseGeocodingStateResponseDto { - @ApiProperty({ description: 'Last update timestamp' }) - lastUpdate!: string | null; - @ApiProperty({ description: 'Last import file name' }) - lastImportFileName!: string | null; -} - -export class VersionCheckStateResponseDto { - @ApiProperty({ description: 'Last check timestamp' }) - checkedAt!: string | null; - @ApiProperty({ description: 'Release version' }) - releaseVersion!: string | null; -} diff --git a/server/src/dtos/tag.dto.ts b/server/src/dtos/tag.dto.ts deleted file mode 100644 index bb33659bfe..0000000000 --- a/server/src/dtos/tag.dto.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsHexColor, IsNotEmpty, IsString } from 'class-validator'; -import { Tag } from 'src/database'; -import { Optional, ValidateHexColor, ValidateUUID } from 'src/validation'; - -export class TagCreateDto { - @ApiProperty({ description: 'Tag name' }) - @IsString() - @IsNotEmpty() - name!: string; - - @ValidateUUID({ nullable: true, optional: true, description: 'Parent tag ID' }) - parentId?: string | null; - - @ApiPropertyOptional({ description: 'Tag color (hex)' }) - @IsHexColor() - @Optional({ nullable: true, emptyToNull: true }) - color?: string; -} - -export class TagUpdateDto { - @ApiPropertyOptional({ description: 'Tag color (hex)' }) - @Optional({ nullable: true, emptyToNull: true }) - @ValidateHexColor() - color?: string | null; -} - -export class TagUpsertDto { - @ApiProperty({ description: 'Tag names to upsert' }) - @IsString({ each: true }) - @IsNotEmpty({ each: true }) - tags!: string[]; -} - -export class TagBulkAssetsDto { - @ValidateUUID({ each: true, description: 'Tag IDs' }) - tagIds!: string[]; - - @ValidateUUID({ each: true, description: 'Asset IDs' }) - assetIds!: string[]; -} - -export class TagBulkAssetsResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of assets tagged' }) - count!: number; -} - -export class TagResponseDto { - @ApiProperty({ description: 'Tag ID' }) - id!: string; - @ApiPropertyOptional({ description: 'Parent tag ID' }) - parentId?: string; - @ApiProperty({ description: 'Tag name' }) - name!: string; - @ApiProperty({ description: 'Tag value (full path)' }) - value!: string; - @ApiProperty({ description: 'Creation date' }) - createdAt!: Date; - @ApiProperty({ description: 'Last update date' }) - updatedAt!: Date; - @ApiPropertyOptional({ description: 'Tag color (hex)' }) - color?: string; -} - -export function mapTag(entity: Tag): TagResponseDto { - return { - id: entity.id, - parentId: entity.parentId ?? undefined, - name: entity.value.split('/').at(-1) as string, - value: entity.value, - createdAt: entity.createdAt, - updatedAt: entity.updatedAt, - color: entity.color ?? undefined, - }; -} diff --git a/server/src/dtos/time-bucket.dto.ts b/server/src/dtos/time-bucket.dto.ts deleted file mode 100644 index dfd474d885..0000000000 --- a/server/src/dtos/time-bucket.dto.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -import { IsString } from 'class-validator'; -import { AssetOrder, AssetVisibility } from 'src/enum'; -import { ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation'; - -export class TimeBucketDto { - @ValidateUUID({ optional: true, description: 'Filter assets by specific user ID' }) - userId?: string; - - @ValidateUUID({ optional: true, description: 'Filter assets belonging to a specific album' }) - albumId?: string; - - @ValidateUUID({ optional: true, description: 'Filter assets containing a specific person (face recognition)' }) - personId?: string; - - @ValidateUUID({ optional: true, description: 'Filter assets with a specific tag' }) - tagId?: string; - - @ValidateBoolean({ - optional: true, - description: 'Filter by favorite status (true for favorites only, false for non-favorites only)', - }) - isFavorite?: boolean; - - @ValidateBoolean({ - optional: true, - description: 'Filter by trash status (true for trashed assets only, false for non-trashed only)', - }) - isTrashed?: boolean; - - @ValidateBoolean({ - optional: true, - description: 'Include stacked assets in the response. When true, only primary assets from stacks are returned.', - }) - withStacked?: boolean; - - @ValidateBoolean({ optional: true, description: 'Include assets shared by partners' }) - withPartners?: boolean; - - @ValidateEnum({ - enum: AssetOrder, - name: 'AssetOrder', - description: 'Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)', - optional: true, - }) - order?: AssetOrder; - - @ValidateEnum({ - enum: AssetVisibility, - name: 'AssetVisibility', - optional: true, - description: 'Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)', - }) - visibility?: AssetVisibility; - - @ValidateBoolean({ - optional: true, - description: 'Include location data in the response', - }) - withCoordinates?: boolean; -} - -export class TimeBucketAssetDto extends TimeBucketDto { - @ApiProperty({ - type: 'string', - description: 'Time bucket identifier in YYYY-MM-DD format (e.g., "2024-01-01" for January 2024)', - example: '2024-01-01', - }) - @IsString() - timeBucket!: string; -} - -export class TimeBucketAssetResponseDto { - @ApiProperty({ - type: 'array', - items: { type: 'string' }, - description: 'Array of asset IDs in the time bucket', - }) - id!: string[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string' }, - description: 'Array of owner IDs for each asset', - }) - ownerId!: string[]; - - @ApiProperty({ - type: 'array', - items: { type: 'number' }, - description: 'Array of aspect ratios (width/height) for each asset', - }) - ratio!: number[]; - - @ApiProperty({ - type: 'array', - items: { type: 'boolean' }, - description: 'Array indicating whether each asset is favorited', - }) - isFavorite!: boolean[]; - - @ValidateEnum({ - enum: AssetVisibility, - name: 'AssetVisibility', - each: true, - description: 'Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED)', - }) - visibility!: AssetVisibility[]; - - @ApiProperty({ - type: 'array', - items: { type: 'boolean' }, - description: 'Array indicating whether each asset is in the trash', - }) - isTrashed!: boolean[]; - - @ApiProperty({ - type: 'array', - items: { type: 'boolean' }, - description: 'Array indicating whether each asset is an image (false for videos)', - }) - isImage!: boolean[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string', nullable: true }, - description: 'Array of BlurHash strings for generating asset previews (base64 encoded)', - }) - thumbhash!: (string | null)[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string' }, - description: 'Array of file creation timestamps in UTC', - }) - fileCreatedAt!: string[]; - - @ApiProperty({ - type: 'array', - items: { type: 'number' }, - description: - "Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.", - }) - localOffsetHours!: number[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string', nullable: true }, - description: 'Array of video durations in HH:MM:SS format (null for images)', - }) - duration!: (string | null)[]; - - @ApiProperty({ - type: 'array', - items: { - type: 'array', - items: { type: 'string' }, - minItems: 2, - maxItems: 2, - nullable: true, - }, - description: 'Array of stack information as [stackId, assetCount] tuples (null for non-stacked assets)', - }) - stack?: ([string, string] | null)[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string', nullable: true }, - description: 'Array of projection types for 360° content (e.g., "EQUIRECTANGULAR", "CUBEFACE", "CYLINDRICAL")', - }) - projectionType!: (string | null)[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string', nullable: true }, - description: 'Array of live photo video asset IDs (null for non-live photos)', - }) - livePhotoVideoId!: (string | null)[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string', nullable: true }, - description: 'Array of city names extracted from EXIF GPS data', - }) - city!: (string | null)[]; - - @ApiProperty({ - type: 'array', - items: { type: 'string', nullable: true }, - description: 'Array of country names extracted from EXIF GPS data', - }) - country!: (string | null)[]; - - @ApiProperty({ - type: 'array', - required: false, - items: { type: 'number', nullable: true }, - description: 'Array of latitude coordinates extracted from EXIF GPS data', - }) - latitude!: number[]; - - @ApiProperty({ - type: 'array', - required: false, - items: { type: 'number', nullable: true }, - description: 'Array of longitude coordinates extracted from EXIF GPS data', - }) - longitude!: number[]; -} - -export class TimeBucketsResponseDto { - @ApiProperty({ - type: 'string', - description: 'Time bucket identifier in YYYY-MM-DD format representing the start of the time period', - example: '2024-01-01', - }) - timeBucket!: string; - - @ApiProperty({ - type: 'integer', - description: 'Number of assets in this time bucket', - example: 42, - }) - count!: number; -} diff --git a/server/src/dtos/trash.dto.ts b/server/src/dtos/trash.dto.ts deleted file mode 100644 index f1d1f109f6..0000000000 --- a/server/src/dtos/trash.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class TrashResponseDto { - @ApiProperty({ type: 'integer', description: 'Number of items in trash' }) - count!: number; -} diff --git a/server/src/dtos/user-preferences.dto.ts b/server/src/dtos/user-preferences.dto.ts deleted file mode 100644 index cce1994007..0000000000 --- a/server/src/dtos/user-preferences.dto.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { ApiProperty, ApiPropertyOptional, ApiSchema } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsDateString, IsInt, IsPositive, ValidateNested } from 'class-validator'; -import { AssetOrder, UserAvatarColor } from 'src/enum'; -import { UserPreferences } from 'src/types'; -import { Optional, ValidateBoolean, ValidateEnum } from 'src/validation'; - -class AvatarUpdate { - @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', optional: true, description: 'Avatar color' }) - color?: UserAvatarColor; -} - -class MemoriesUpdate { - @ValidateBoolean({ optional: true, description: 'Whether memories are enabled' }) - enabled?: boolean; - - @Optional() - @IsInt() - @IsPositive() - @ApiProperty({ type: 'integer', description: 'Memory duration in seconds' }) - duration?: number; -} - -class RatingsUpdate { - @ValidateBoolean({ optional: true, description: 'Whether ratings are enabled' }) - enabled?: boolean; -} - -@ApiSchema({ description: 'Album preferences' }) -class AlbumsUpdate { - @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, description: 'Default asset order for albums' }) - defaultAssetOrder?: AssetOrder; -} - -class FoldersUpdate { - @ValidateBoolean({ optional: true, description: 'Whether folders are enabled' }) - enabled?: boolean; - - @ValidateBoolean({ optional: true, description: 'Whether folders appear in web sidebar' }) - sidebarWeb?: boolean; -} - -class PeopleUpdate { - @ValidateBoolean({ optional: true, description: 'Whether people are enabled' }) - enabled?: boolean; - - @ValidateBoolean({ optional: true, description: 'Whether people appear in web sidebar' }) - sidebarWeb?: boolean; -} - -class SharedLinksUpdate { - @ValidateBoolean({ optional: true, description: 'Whether shared links are enabled' }) - enabled?: boolean; - - @ValidateBoolean({ optional: true, description: 'Whether shared links appear in web sidebar' }) - sidebarWeb?: boolean; -} - -class TagsUpdate { - @ValidateBoolean({ optional: true, description: 'Whether tags are enabled' }) - enabled?: boolean; - - @ValidateBoolean({ optional: true, description: 'Whether tags appear in web sidebar' }) - sidebarWeb?: boolean; -} - -class EmailNotificationsUpdate { - @ValidateBoolean({ optional: true, description: 'Whether email notifications are enabled' }) - enabled?: boolean; - - @ValidateBoolean({ optional: true, description: 'Whether to receive email notifications for album invites' }) - albumInvite?: boolean; - - @ValidateBoolean({ optional: true, description: 'Whether to receive email notifications for album updates' }) - albumUpdate?: boolean; -} - -class DownloadUpdate implements Partial { - @Optional() - @IsInt() - @IsPositive() - @ApiPropertyOptional({ type: 'integer', description: 'Maximum archive size in bytes' }) - archiveSize?: number; - - @ValidateBoolean({ optional: true, description: 'Whether to include embedded videos in downloads' }) - includeEmbeddedVideos?: boolean; -} - -class PurchaseUpdate { - @ValidateBoolean({ optional: true, description: 'Whether to show support badge' }) - showSupportBadge?: boolean; - - @ApiPropertyOptional({ description: 'Date until which to hide buy button' }) - @IsDateString() - @Optional() - hideBuyButtonUntil?: string; -} - -class CastUpdate { - @ValidateBoolean({ optional: true, description: 'Whether Google Cast is enabled' }) - gCastEnabled?: boolean; -} - -export class UserPreferencesUpdateDto { - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => AlbumsUpdate) - albums?: AlbumsUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => FoldersUpdate) - folders?: FoldersUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => MemoriesUpdate) - memories?: MemoriesUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => PeopleUpdate) - people?: PeopleUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => RatingsUpdate) - ratings?: RatingsUpdate; - - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined, required: false }) - @Optional() - @ValidateNested() - @Type(() => SharedLinksUpdate) - sharedLinks?: SharedLinksUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => TagsUpdate) - tags?: TagsUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => AvatarUpdate) - avatar?: AvatarUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => EmailNotificationsUpdate) - emailNotifications?: EmailNotificationsUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => DownloadUpdate) - download?: DownloadUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => PurchaseUpdate) - purchase?: PurchaseUpdate; - - // Description lives on schema to avoid duplication - @ApiPropertyOptional({ description: undefined }) - @Optional() - @ValidateNested() - @Type(() => CastUpdate) - cast?: CastUpdate; -} - -class AlbumsResponse { - @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', description: 'Default asset order for albums' }) - defaultAssetOrder: AssetOrder = AssetOrder.Desc; -} - -class RatingsResponse { - @ApiProperty({ description: 'Whether ratings are enabled' }) - enabled: boolean = false; -} - -class MemoriesResponse { - @ApiProperty({ description: 'Whether memories are enabled' }) - enabled: boolean = true; - - @ApiProperty({ type: 'integer', description: 'Memory duration in seconds' }) - duration: number = 5; -} - -class FoldersResponse { - @ApiProperty({ description: 'Whether folders are enabled' }) - enabled: boolean = false; - @ApiProperty({ description: 'Whether folders appear in web sidebar' }) - sidebarWeb: boolean = false; -} - -class PeopleResponse { - @ApiProperty({ description: 'Whether people are enabled' }) - enabled: boolean = true; - @ApiProperty({ description: 'Whether people appear in web sidebar' }) - sidebarWeb: boolean = false; -} - -class TagsResponse { - @ApiProperty({ description: 'Whether tags are enabled' }) - enabled: boolean = true; - @ApiProperty({ description: 'Whether tags appear in web sidebar' }) - sidebarWeb: boolean = true; -} - -class SharedLinksResponse { - @ApiProperty({ description: 'Whether shared links are enabled' }) - enabled: boolean = true; - @ApiProperty({ description: 'Whether shared links appear in web sidebar' }) - sidebarWeb: boolean = false; -} - -class EmailNotificationsResponse { - @ApiProperty({ description: 'Whether email notifications are enabled' }) - enabled!: boolean; - @ApiProperty({ description: 'Whether to receive email notifications for album invites' }) - albumInvite!: boolean; - @ApiProperty({ description: 'Whether to receive email notifications for album updates' }) - albumUpdate!: boolean; -} - -class DownloadResponse { - @ApiProperty({ type: 'integer', description: 'Maximum archive size in bytes' }) - archiveSize!: number; - - @ApiProperty({ description: 'Whether to include embedded videos in downloads' }) - includeEmbeddedVideos: boolean = false; -} - -class PurchaseResponse { - @ApiProperty({ description: 'Whether to show support badge' }) - showSupportBadge!: boolean; - @ApiProperty({ description: 'Date until which to hide buy button' }) - hideBuyButtonUntil!: string; -} - -class CastResponse { - @ApiProperty({ description: 'Whether Google Cast is enabled' }) - gCastEnabled: boolean = false; -} - -export class UserPreferencesResponseDto implements UserPreferences { - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - albums!: AlbumsResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - folders!: FoldersResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - memories!: MemoriesResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - people!: PeopleResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - ratings!: RatingsResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - sharedLinks!: SharedLinksResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - tags!: TagsResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - emailNotifications!: EmailNotificationsResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - download!: DownloadResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - purchase!: PurchaseResponse; - // Description lives on schema to avoid duplication - @ApiProperty({ description: undefined }) - cast!: CastResponse; -} - -export const mapPreferences = (preferences: UserPreferences): UserPreferencesResponseDto => { - return preferences; -}; diff --git a/server/src/dtos/user-profile.dto.ts b/server/src/dtos/user-profile.dto.ts deleted file mode 100644 index 6559dd052c..0000000000 --- a/server/src/dtos/user-profile.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { UploadFieldName } from 'src/dtos/asset-media.dto'; - -export class CreateProfileImageDto { - @ApiProperty({ type: 'string', format: 'binary', description: 'Profile image file' }) - [UploadFieldName.PROFILE_DATA]!: Express.Multer.File; -} - -export class CreateProfileImageResponseDto { - @ApiProperty({ description: 'User ID' }) - userId!: string; - @ApiProperty({ description: 'Profile image change date', format: 'date-time' }) - profileChangedAt!: Date; - @ApiProperty({ description: 'Profile image file path' }) - profileImagePath!: string; -} diff --git a/server/src/dtos/user.dto.spec.ts b/server/src/dtos/user.dto.spec.ts deleted file mode 100644 index e6be3b17d1..0000000000 --- a/server/src/dtos/user.dto.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; -import { UserAdminCreateDto, UserUpdateMeDto } from 'src/dtos/user.dto'; - -describe('update user DTO', () => { - it('should allow emails without a tld', async () => { - const someEmail = 'test@test'; - - const dto = plainToInstance(UserUpdateMeDto, { - email: someEmail, - id: '3fe388e4-2078-44d7-b36c-39d9dee3a657', - }); - const errors = await validate(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual(someEmail); - }); -}); - -describe('create user DTO', () => { - it('validates the email', async () => { - const params: Partial = { - email: undefined, - password: 'password', - name: 'name', - }; - let dto: UserAdminCreateDto = plainToInstance(UserAdminCreateDto, params); - let errors = await validate(dto); - expect(errors).toHaveLength(1); - - params.email = 'invalid email'; - dto = plainToInstance(UserAdminCreateDto, params); - errors = await validate(dto); - expect(errors).toHaveLength(1); - - params.email = 'valid@email.com'; - dto = plainToInstance(UserAdminCreateDto, params); - errors = await validate(dto); - expect(errors).toHaveLength(0); - }); - - it('validates invalid email type', async () => { - let dto = plainToInstance(UserAdminCreateDto, { - email: [], - password: 'some password', - name: 'some name', - }); - expect(await validate(dto)).toHaveLength(1); - - dto = plainToInstance(UserAdminCreateDto, { - email: {}, - password: 'some password', - name: 'some name', - }); - expect(await validate(dto)).toHaveLength(1); - }); - - it('should allow emails without a tld', async () => { - const someEmail = 'test@test'; - - const dto = plainToInstance(UserAdminCreateDto, { - email: someEmail, - password: 'some password', - name: 'some name', - }); - const errors = await validate(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual(someEmail); - }); -}); diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 2d4fc3934f..65ffdd9039 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -1,10 +1,9 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsEmail, IsInt, IsNotEmpty, IsString, Min } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { User, UserAdmin } from 'src/database'; -import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum'; -import { UserMetadataItem } from 'src/types'; -import { Optional, PinCode, ValidateBoolean, ValidateEnum, ValidateUUID, toEmail, toSanitized } from 'src/validation'; +import { UserStatus } from 'src/enum'; +import { Optional, ValidateBoolean, ValidateEnum, ValidateUUID, toEmail } from 'src/validation'; export class UserUpdateMeDto { @ApiPropertyOptional({ description: 'User email' }) @@ -13,27 +12,11 @@ export class UserUpdateMeDto { @Transform(toEmail) email?: string; - // TODO: migrate to the other change password endpoint - @ApiPropertyOptional({ description: 'User password (deprecated, use change password endpoint)' }) - @Optional() - @IsNotEmpty() - @IsString() - password?: string; - @ApiPropertyOptional({ description: 'User name' }) @Optional() @IsString() @IsNotEmpty() name?: string; - - @ValidateEnum({ - enum: UserAvatarColor, - name: 'UserAvatarColor', - optional: true, - nullable: true, - description: 'Avatar color', - }) - avatarColor?: UserAvatarColor | null; } export class UserResponseDto { @@ -43,39 +26,13 @@ export class UserResponseDto { name!: string; @ApiProperty({ description: 'User email' }) email!: string; - @ApiProperty({ description: 'Profile image path' }) - profileImagePath!: string; - @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', description: 'Avatar color' }) - avatarColor!: UserAvatarColor; - @ApiProperty({ description: 'Profile change date' }) - profileChangedAt!: Date; } -export class UserLicense { - @ApiProperty({ description: 'License key' }) - licenseKey!: string; - @ApiProperty({ description: 'Activation key' }) - activationKey!: string; - @ApiProperty({ description: 'Activation date' }) - activatedAt!: Date; -} - -const emailToAvatarColor = (email: string): UserAvatarColor => { - const values = Object.values(UserAvatarColor); - const randomIndex = Math.floor( - [...email].map((letter) => letter.codePointAt(0) ?? 0).reduce((a, b) => a + b, 0) % values.length, - ); - return values[randomIndex]; -}; - export const mapUser = (entity: User | UserAdmin): UserResponseDto => { return { id: entity.id, email: entity.email, name: entity.name, - profileImagePath: entity.profileImagePath, - avatarColor: entity.avatarColor ?? emailToAvatarColor(entity.email), - profileChangedAt: entity.profileChangedAt, }; }; @@ -102,37 +59,9 @@ export class UserAdminCreateDto { @IsString() name!: string; - @ValidateEnum({ - enum: UserAvatarColor, - name: 'UserAvatarColor', - optional: true, - nullable: true, - description: 'Avatar color', - }) - avatarColor?: UserAvatarColor | null; - - @ApiPropertyOptional({ description: 'PIN code' }) - @PinCode({ optional: true, nullable: true, emptyToNull: true }) - pinCode?: string | null; - - @ApiPropertyOptional({ description: 'Storage label' }) - @Optional({ nullable: true }) - @IsString() - @Transform(toSanitized) - storageLabel?: string | null; - - @ApiPropertyOptional({ type: 'integer', format: 'int64', description: 'Storage quota in bytes' }) - @Optional({ nullable: true }) - @IsInt() - @Min(0) - quotaSizeInBytes?: number | null; - @ValidateBoolean({ optional: true, description: 'Require password change on next login' }) shouldChangePassword?: boolean; - @ValidateBoolean({ optional: true, description: 'Send notification email' }) - notify?: boolean; - @ValidateBoolean({ optional: true, description: 'Grant admin privileges' }) isAdmin?: boolean; } @@ -150,52 +79,25 @@ export class UserAdminUpdateDto { @IsString() password?: string; - @ApiPropertyOptional({ description: 'PIN code' }) - @PinCode({ optional: true, nullable: true, emptyToNull: true }) - pinCode?: string | null; - @ApiPropertyOptional({ description: 'User name' }) @Optional() @IsString() @IsNotEmpty() name?: string; - @ValidateEnum({ - enum: UserAvatarColor, - name: 'UserAvatarColor', - optional: true, - nullable: true, - description: 'Avatar color', - }) - avatarColor?: UserAvatarColor | null; - - @ApiPropertyOptional({ description: 'Storage label' }) - @Optional({ nullable: true }) - @IsString() - @Transform(toSanitized) - storageLabel?: string | null; - @ValidateBoolean({ optional: true, description: 'Require password change on next login' }) shouldChangePassword?: boolean; - @ApiPropertyOptional({ type: 'integer', format: 'int64', description: 'Storage quota in bytes' }) - @Optional({ nullable: true }) - @IsInt() - @Min(0) - quotaSizeInBytes?: number | null; - @ValidateBoolean({ optional: true, description: 'Grant admin privileges' }) isAdmin?: boolean; } export class UserAdminDeleteDto { - @ValidateBoolean({ optional: true, description: 'Force delete even if user has assets' }) + @ValidateBoolean({ optional: true, description: 'Force delete' }) force?: boolean; } export class UserAdminResponseDto extends UserResponseDto { - @ApiProperty({ description: 'Storage label' }) - storageLabel!: string | null; @ApiProperty({ description: 'Require password change on next login' }) shouldChangePassword!: boolean; @ApiProperty({ description: 'Is admin user' }) @@ -206,36 +108,18 @@ export class UserAdminResponseDto extends UserResponseDto { deletedAt!: Date | null; @ApiProperty({ description: 'Last update date' }) updatedAt!: Date; - @ApiProperty({ description: 'OAuth ID' }) - oauthId!: string; - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage quota in bytes' }) - quotaSizeInBytes!: number | null; - @ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage in bytes' }) - quotaUsageInBytes!: number | null; @ValidateEnum({ enum: UserStatus, name: 'UserStatus', description: 'User status' }) status!: string; - @ApiProperty({ description: 'User license' }) - license!: UserLicense | null; } export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto { - const metadata = entity.metadata || []; - const license = metadata.find( - (item): item is UserMetadataItem => item.key === UserMetadataKey.License, - )?.value; - return { ...mapUser(entity), - storageLabel: entity.storageLabel, shouldChangePassword: entity.shouldChangePassword, isAdmin: entity.isAdmin, createdAt: entity.createdAt, deletedAt: entity.deletedAt, updatedAt: entity.updatedAt, - oauthId: entity.oauthId, - quotaSizeInBytes: entity.quotaSizeInBytes, - quotaUsageInBytes: entity.quotaUsageInBytes, status: entity.status, - license: license ? { ...license, activatedAt: new Date(license?.activatedAt) } : null, }; } diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts deleted file mode 100644 index c4e5ac9c4c..0000000000 --- a/server/src/dtos/workflow.dto.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsNotEmpty, IsObject, IsString, IsUUID, ValidateNested } from 'class-validator'; -import { WorkflowAction, WorkflowFilter } from 'src/database'; -import { PluginTriggerType } from 'src/enum'; -import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; -import { Optional, ValidateBoolean, ValidateEnum } from 'src/validation'; - -export class WorkflowFilterItemDto { - @ApiProperty({ description: 'Plugin filter ID' }) - @IsUUID() - pluginFilterId!: string; - - @ApiPropertyOptional({ description: 'Filter configuration' }) - @IsObject() - @Optional() - filterConfig?: FilterConfig; -} - -export class WorkflowActionItemDto { - @ApiProperty({ description: 'Plugin action ID' }) - @IsUUID() - pluginActionId!: string; - - @ApiPropertyOptional({ description: 'Action configuration' }) - @IsObject() - @Optional() - actionConfig?: ActionConfig; -} - -export class WorkflowCreateDto { - @ValidateEnum({ enum: PluginTriggerType, name: 'PluginTriggerType', description: 'Workflow trigger type' }) - triggerType!: PluginTriggerType; - - @ApiProperty({ description: 'Workflow name' }) - @IsString() - @IsNotEmpty() - name!: string; - - @ApiPropertyOptional({ description: 'Workflow description' }) - @IsString() - @Optional() - description?: string; - - @ValidateBoolean({ optional: true, description: 'Workflow enabled' }) - enabled?: boolean; - - @ApiProperty({ description: 'Workflow filters' }) - @ValidateNested({ each: true }) - @Type(() => WorkflowFilterItemDto) - filters!: WorkflowFilterItemDto[]; - - @ApiProperty({ description: 'Workflow actions' }) - @ValidateNested({ each: true }) - @Type(() => WorkflowActionItemDto) - actions!: WorkflowActionItemDto[]; -} - -export class WorkflowUpdateDto { - @ValidateEnum({ - enum: PluginTriggerType, - name: 'PluginTriggerType', - optional: true, - description: 'Workflow trigger type', - }) - triggerType?: PluginTriggerType; - - @ApiPropertyOptional({ description: 'Workflow name' }) - @IsString() - @IsNotEmpty() - @Optional() - name?: string; - - @ApiPropertyOptional({ description: 'Workflow description' }) - @IsString() - @Optional() - description?: string; - - @ValidateBoolean({ optional: true, description: 'Workflow enabled' }) - enabled?: boolean; - - @ApiPropertyOptional({ description: 'Workflow filters' }) - @ValidateNested({ each: true }) - @Type(() => WorkflowFilterItemDto) - @Optional() - filters?: WorkflowFilterItemDto[]; - - @ApiPropertyOptional({ description: 'Workflow actions' }) - @ValidateNested({ each: true }) - @Type(() => WorkflowActionItemDto) - @Optional() - actions?: WorkflowActionItemDto[]; -} - -export class WorkflowResponseDto { - @ApiProperty({ description: 'Workflow ID' }) - id!: string; - @ApiProperty({ description: 'Owner user ID' }) - ownerId!: string; - @ValidateEnum({ enum: PluginTriggerType, name: 'PluginTriggerType', description: 'Workflow trigger type' }) - triggerType!: PluginTriggerType; - @ApiProperty({ description: 'Workflow name' }) - name!: string | null; - @ApiProperty({ description: 'Workflow description' }) - description!: string; - @ApiProperty({ description: 'Creation date' }) - createdAt!: string; - @ApiProperty({ description: 'Workflow enabled' }) - enabled!: boolean; - @ApiProperty({ description: 'Workflow filters' }) - filters!: WorkflowFilterResponseDto[]; - @ApiProperty({ description: 'Workflow actions' }) - actions!: WorkflowActionResponseDto[]; -} - -export class WorkflowFilterResponseDto { - @ApiProperty({ description: 'Filter ID' }) - id!: string; - @ApiProperty({ description: 'Workflow ID' }) - workflowId!: string; - @ApiProperty({ description: 'Plugin filter ID' }) - pluginFilterId!: string; - @ApiProperty({ description: 'Filter configuration' }) - filterConfig!: FilterConfig | null; - @ApiProperty({ description: 'Filter order', type: 'number' }) - order!: number; -} - -export class WorkflowActionResponseDto { - @ApiProperty({ description: 'Action ID' }) - id!: string; - @ApiProperty({ description: 'Workflow ID' }) - workflowId!: string; - @ApiProperty({ description: 'Plugin action ID' }) - pluginActionId!: string; - @ApiProperty({ description: 'Action configuration' }) - actionConfig!: ActionConfig | null; - @ApiProperty({ description: 'Action order', type: 'number' }) - order!: number; -} - -export function mapWorkflowFilter(filter: WorkflowFilter): WorkflowFilterResponseDto { - return { - id: filter.id, - workflowId: filter.workflowId, - pluginFilterId: filter.pluginFilterId, - filterConfig: filter.filterConfig, - order: filter.order, - }; -} - -export function mapWorkflowAction(action: WorkflowAction): WorkflowActionResponseDto { - return { - id: action.id, - workflowId: action.workflowId, - pluginActionId: action.pluginActionId, - actionConfig: action.actionConfig, - order: action.order, - }; -} diff --git a/server/src/emails/album-invite.email.tsx b/server/src/emails/album-invite.email.tsx deleted file mode 100644 index fdc189af97..0000000000 --- a/server/src/emails/album-invite.email.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Img, Link, Section, Text } from '@react-email/components'; -import * as React from 'react'; -import { ImmichButton } from 'src/emails/components/button.component'; -import ImmichLayout from 'src/emails/components/immich.layout'; -import { AlbumInviteEmailProps } from 'src/repositories/email.repository'; -import { replaceTemplateTags } from 'src/utils/replace-template-tags'; - -export const AlbumInviteEmail = ({ - baseUrl, - albumName, - recipientName, - senderName, - albumId, - cid, - customTemplate, -}: AlbumInviteEmailProps) => { - const variables = { - albumName, - recipientName, - senderName, - albumId, - baseUrl, - }; - - const emailContent = customTemplate ? ( - replaceTemplateTags(customTemplate, variables) - ) : ( - <> - - Hey {recipientName}! - - - - {senderName} has added you to the album {albumName}. - - - ); - - return ( - - {customTemplate && ( - -
-
- )} - - {!customTemplate && emailContent} - - {cid && ( -
- -
- )} - -
- View Album -
- - - If you cannot click the button use the link below to view the album. -
- {`${baseUrl}/albums/${albumId}`} -
-
- ); -}; - -AlbumInviteEmail.PreviewProps = { - baseUrl: 'https://demo.immich.app', - albumName: 'Trip to Europe', - albumId: 'b63f6dae-e1c9-401b-9a85-9dbbf5612539', - senderName: 'Owner User', - recipientName: 'Alan Turing', -} as AlbumInviteEmailProps; - -export default AlbumInviteEmail; diff --git a/server/src/emails/album-update.email.tsx b/server/src/emails/album-update.email.tsx deleted file mode 100644 index 6fd2abb055..0000000000 --- a/server/src/emails/album-update.email.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Img, Link, Section, Text } from '@react-email/components'; -import * as React from 'react'; -import { ImmichButton } from 'src/emails/components/button.component'; -import ImmichLayout from 'src/emails/components/immich.layout'; -import { AlbumUpdateEmailProps } from 'src/repositories/email.repository'; -import { replaceTemplateTags } from 'src/utils/replace-template-tags'; - -export const AlbumUpdateEmail = ({ - baseUrl, - albumName, - recipientName, - albumId, - cid, - customTemplate, -}: AlbumUpdateEmailProps) => { - const usableTemplateVariables = { - albumName, - recipientName, - albumId, - baseUrl, - }; - - const emailContent = customTemplate ? ( - replaceTemplateTags(customTemplate, usableTemplateVariables) - ) : ( - <> - - Hey {recipientName}! - - - - New media has been added to {albumName}. -
Check it out! -
- - ); - - return ( - - {customTemplate && ( - -
-
- )} - - {!customTemplate && emailContent} - - {cid && ( -
- -
- )} - -
- View Album -
- - - If you cannot click the button use the link below to view the album. -
- {`${baseUrl}/albums/${albumId}`} -
-
- ); -}; - -AlbumUpdateEmail.PreviewProps = { - baseUrl: 'https://demo.immich.app', - albumName: 'Trip to Europe', - albumId: 'b63f6dae-e1c9-401b-9a85-9dbbf5612539', - recipientName: 'Alan Turing', - cid: '', - customTemplate: '', -} as AlbumUpdateEmailProps; - -export default AlbumUpdateEmail; diff --git a/server/src/emails/components/button.component.tsx b/server/src/emails/components/button.component.tsx deleted file mode 100644 index a1fc4636cc..0000000000 --- a/server/src/emails/components/button.component.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import { Button, ButtonProps, Text } from '@react-email/components'; - -export const ImmichButton = ({ children, ...props }: ButtonProps) => ( - -); diff --git a/server/src/emails/components/footer.template.tsx b/server/src/emails/components/footer.template.tsx deleted file mode 100644 index 324d7dc003..0000000000 --- a/server/src/emails/components/footer.template.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Column, Img, Link, Row, Text } from '@react-email/components'; -import * as React from 'react'; - -export const ImmichFooter = () => ( - <> - - -
- - Get it on Google Play - -
-
- -
- - Download on the App Store - -
-
-
- - - Immich project is available under GNU AGPL v3 license. - - -); diff --git a/server/src/emails/components/futo.layout.tsx b/server/src/emails/components/futo.layout.tsx deleted file mode 100644 index c531201411..0000000000 --- a/server/src/emails/components/futo.layout.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { - Body, - Container, - Font, - Head, - Hr, - Html, - Img, - Link, - Preview, - Section, - Tailwind, - Text, -} from '@react-email/components'; -import * as React from 'react'; -import { ImmichFooter } from './footer.template'; - -interface FutoLayoutProps { - children: React.ReactNode; - preview: string; -} - -export const FutoLayout = ({ children, preview }: FutoLayoutProps) => ( - - - - - - {preview} - - -
-
- Immich -
- - {children} -
- -
- - FUTO - -
- -
- - -
- -
- -); - -FutoLayout.PreviewProps = { - preview: 'This is the preview shown on some mail clients', - children: Email body goes here., -} as FutoLayoutProps; - -export default FutoLayout; diff --git a/server/src/emails/components/immich.layout.tsx b/server/src/emails/components/immich.layout.tsx deleted file mode 100644 index 911c6b31ee..0000000000 --- a/server/src/emails/components/immich.layout.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Body, Container, Font, Head, Hr, Html, Img, Preview, Section, Tailwind, Text } from '@react-email/components'; -import * as React from 'react'; -import { ImmichFooter } from 'src/emails/components/footer.template'; - -interface ImmichLayoutProps { - children: React.ReactNode; - preview: string; -} - -export const ImmichLayout = ({ children, preview }: ImmichLayoutProps) => ( - - - - - - {preview} - - -
-
- Immich -
- - {children} -
- -
- - -
- -
- -); - -ImmichLayout.PreviewProps = { - preview: 'This is the preview shown on some mail clients', - children: Email body goes here., -} as ImmichLayoutProps; - -export default ImmichLayout; diff --git a/server/src/emails/license.email.tsx b/server/src/emails/license.email.tsx deleted file mode 100644 index 42dc8918ee..0000000000 --- a/server/src/emails/license.email.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Link, Section, Text } from '@react-email/components'; -import * as React from 'react'; -import { ImmichButton } from 'src/emails/components/button.component'; -import FutoLayout from 'src/emails/components/futo.layout'; - -/** - * Template to be used for FUTOPay project - * Variable is {{LICENSEKEY}} - * */ -export const LicenseEmail = () => ( - - Thank you for supporting Immich and open-source software - - - Your Immich key is - - -
- {'{{LICENSEKEY}}'} -
- - - To activate your instance, you can click the following button or copy and paste the link below to your browser. - - -
- - Activate - -
- - - - https://my.immich.app/link?target=activate_license&licenseKey={'{{LICENSEKEY}}'}&activationKey= - {'{{ACTIVATIONKEY}}'} - - -
-); - -LicenseEmail.PreviewProps = {}; - -export default LicenseEmail; diff --git a/server/src/emails/test.email.tsx b/server/src/emails/test.email.tsx deleted file mode 100644 index 0d87307080..0000000000 --- a/server/src/emails/test.email.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Link, Row, Text } from '@react-email/components'; -import * as React from 'react'; -import ImmichLayout from 'src/emails/components/immich.layout'; -import { TestEmailProps } from 'src/repositories/email.repository'; - -export const TestEmail = ({ baseUrl, displayName }: TestEmailProps) => ( - - - Hey {displayName}! - - - This is a test email from your Immich Instance! - - - {baseUrl} - - -); - -TestEmail.PreviewProps = { - baseUrl: 'https://demo.immich.app', - displayName: 'Alan Turing', -} as TestEmailProps; - -export default TestEmail; diff --git a/server/src/emails/welcome.email.tsx b/server/src/emails/welcome.email.tsx deleted file mode 100644 index 57e86ab252..0000000000 --- a/server/src/emails/welcome.email.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Link, Section, Text } from '@react-email/components'; -import * as React from 'react'; -import { ImmichButton } from 'src/emails/components/button.component'; -import ImmichLayout from 'src/emails/components/immich.layout'; -import { WelcomeEmailProps } from 'src/repositories/email.repository'; -import { replaceTemplateTags } from 'src/utils/replace-template-tags'; - -export const WelcomeEmail = ({ baseUrl, displayName, username, password, customTemplate }: WelcomeEmailProps) => { - const usableTemplateVariables = { - displayName, - username, - password, - baseUrl, - }; - - const emailContent = customTemplate ? ( - replaceTemplateTags(customTemplate, usableTemplateVariables) - ) : ( - <> - - Hey {displayName}! - - - A new account has been created for you. - - - Username: {username} - {password && ( - <> -
- Password: {password} - - )} -
- - ); - - return ( - - {customTemplate && ( - -
-
- )} - - {!customTemplate && emailContent} - -
- Login -
- - - If you cannot click the button use the link below to proceed with first login. -
- {baseUrl} -
-
- ); -}; - -WelcomeEmail.PreviewProps = { - baseUrl: 'https://demo.immich.app/auth/login', - displayName: 'Alan Turing', - username: 'alanturing@immich.app', - password: 'mysuperpassword', -} as WelcomeEmailProps; - -export default WelcomeEmail; diff --git a/server/src/enum.ts b/server/src/enum.ts index 8f509754da..6e242c0b81 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -1,345 +1,61 @@ export enum AuthType { Password = 'password', - OAuth = 'oauth', } export enum ImmichCookie { AccessToken = 'immich_access_token', - MaintenanceToken = 'immich_maintenance_token', AuthType = 'immich_auth_type', IsAuthenticated = 'immich_is_authenticated', - SharedLinkToken = 'immich_shared_link_token', - OAuthState = 'immich_oauth_state', - OAuthCodeVerifier = 'immich_oauth_code_verifier', } export enum ImmichHeader { ApiKey = 'x-api-key', UserToken = 'x-immich-user-token', SessionToken = 'x-immich-session-token', - SharedLinkKey = 'x-immich-share-key', - SharedLinkSlug = 'x-immich-share-slug', Checksum = 'x-immich-checksum', Cid = 'x-immich-cid', } export enum ImmichQuery { - SharedLinkKey = 'key', - SharedLinkSlug = 'slug', ApiKey = 'apiKey', SessionKey = 'sessionKey', } -export enum AssetType { - Image = 'IMAGE', - Video = 'VIDEO', - Audio = 'AUDIO', - Other = 'OTHER', -} - -export enum AssetFileType { - /** - * An full/large-size image extracted/converted from RAW photos - */ - FullSize = 'fullsize', - Preview = 'preview', - Thumbnail = 'thumbnail', - Sidecar = 'sidecar', -} - -export enum AlbumUserRole { - Editor = 'editor', - Viewer = 'viewer', -} - -export enum AssetOrder { - Asc = 'asc', - Desc = 'desc', -} - export enum DatabaseAction { Create = 'CREATE', Update = 'UPDATE', Delete = 'DELETE', } -export enum EntityType { - Asset = 'ASSET', - Album = 'ALBUM', -} - -export enum MemoryType { - /** pictures taken on this day X years ago */ - OnThisDay = 'on_this_day', -} - -export enum AssetOrderWithRandom { - // Include existing values - Asc = AssetOrder.Asc, - Desc = AssetOrder.Desc, - /** Randomly Ordered */ - Random = 'random', -} - export enum Permission { All = 'all', - ActivityCreate = 'activity.create', - ActivityRead = 'activity.read', - ActivityUpdate = 'activity.update', - ActivityDelete = 'activity.delete', - ActivityStatistics = 'activity.statistics', - ApiKeyCreate = 'apiKey.create', ApiKeyRead = 'apiKey.read', ApiKeyUpdate = 'apiKey.update', ApiKeyDelete = 'apiKey.delete', - // ASSET_CREATE = 'asset.create', - AssetRead = 'asset.read', - AssetUpdate = 'asset.update', - AssetDelete = 'asset.delete', - AssetStatistics = 'asset.statistics', - AssetShare = 'asset.share', - AssetView = 'asset.view', - AssetDownload = 'asset.download', - AssetUpload = 'asset.upload', - AssetReplace = 'asset.replace', - AssetCopy = 'asset.copy', - AssetDerive = 'asset.derive', - - AssetEditGet = 'asset.edit.get', - AssetEditCreate = 'asset.edit.create', - AssetEditDelete = 'asset.edit.delete', - - AlbumCreate = 'album.create', - AlbumRead = 'album.read', - AlbumUpdate = 'album.update', - AlbumDelete = 'album.delete', - AlbumStatistics = 'album.statistics', - AlbumShare = 'album.share', - AlbumDownload = 'album.download', - - AlbumAssetCreate = 'albumAsset.create', - AlbumAssetDelete = 'albumAsset.delete', - - AlbumUserCreate = 'albumUser.create', - AlbumUserUpdate = 'albumUser.update', - AlbumUserDelete = 'albumUser.delete', - AuthChangePassword = 'auth.changePassword', AuthDeviceDelete = 'authDevice.delete', - ArchiveRead = 'archive.read', - - BackupList = 'backup.list', - BackupDownload = 'backup.download', - BackupUpload = 'backup.upload', - BackupDelete = 'backup.delete', - - DuplicateRead = 'duplicate.read', - DuplicateDelete = 'duplicate.delete', - - FaceCreate = 'face.create', - FaceRead = 'face.read', - FaceUpdate = 'face.update', - FaceDelete = 'face.delete', - - FolderRead = 'folder.read', - - JobCreate = 'job.create', - JobRead = 'job.read', - - LibraryCreate = 'library.create', - LibraryRead = 'library.read', - LibraryUpdate = 'library.update', - LibraryDelete = 'library.delete', - LibraryStatistics = 'library.statistics', - - TimelineRead = 'timeline.read', - TimelineDownload = 'timeline.download', - - Maintenance = 'maintenance', - - MapRead = 'map.read', - MapSearch = 'map.search', - - MemoryCreate = 'memory.create', - MemoryRead = 'memory.read', - MemoryUpdate = 'memory.update', - MemoryDelete = 'memory.delete', - MemoryStatistics = 'memory.statistics', - - MemoryAssetCreate = 'memoryAsset.create', - MemoryAssetDelete = 'memoryAsset.delete', - - NotificationCreate = 'notification.create', - NotificationRead = 'notification.read', - NotificationUpdate = 'notification.update', - NotificationDelete = 'notification.delete', - - PartnerCreate = 'partner.create', - PartnerRead = 'partner.read', - PartnerUpdate = 'partner.update', - PartnerDelete = 'partner.delete', - - PersonCreate = 'person.create', - PersonRead = 'person.read', - PersonUpdate = 'person.update', - PersonDelete = 'person.delete', - PersonStatistics = 'person.statistics', - PersonMerge = 'person.merge', - PersonReassign = 'person.reassign', - - PinCodeCreate = 'pinCode.create', - PinCodeUpdate = 'pinCode.update', - PinCodeDelete = 'pinCode.delete', - - PluginCreate = 'plugin.create', - PluginRead = 'plugin.read', - PluginUpdate = 'plugin.update', - PluginDelete = 'plugin.delete', - - ServerAbout = 'server.about', - ServerApkLinks = 'server.apkLinks', - ServerStorage = 'server.storage', - ServerStatistics = 'server.statistics', - ServerVersionCheck = 'server.versionCheck', - - ServerLicenseRead = 'serverLicense.read', - ServerLicenseUpdate = 'serverLicense.update', - ServerLicenseDelete = 'serverLicense.delete', - SessionCreate = 'session.create', SessionRead = 'session.read', SessionUpdate = 'session.update', SessionDelete = 'session.delete', - SessionLock = 'session.lock', - - SharedLinkCreate = 'sharedLink.create', - SharedLinkRead = 'sharedLink.read', - SharedLinkUpdate = 'sharedLink.update', - SharedLinkDelete = 'sharedLink.delete', - - StackCreate = 'stack.create', - StackRead = 'stack.read', - StackUpdate = 'stack.update', - StackDelete = 'stack.delete', - - SyncStream = 'sync.stream', - SyncCheckpointRead = 'syncCheckpoint.read', - SyncCheckpointUpdate = 'syncCheckpoint.update', - SyncCheckpointDelete = 'syncCheckpoint.delete', - - SystemConfigRead = 'systemConfig.read', - SystemConfigUpdate = 'systemConfig.update', SystemMetadataRead = 'systemMetadata.read', SystemMetadataUpdate = 'systemMetadata.update', - TagCreate = 'tag.create', - TagRead = 'tag.read', - TagUpdate = 'tag.update', - TagDelete = 'tag.delete', - TagAsset = 'tag.asset', - UserRead = 'user.read', UserUpdate = 'user.update', - UserLicenseCreate = 'userLicense.create', - UserLicenseRead = 'userLicense.read', - UserLicenseUpdate = 'userLicense.update', - UserLicenseDelete = 'userLicense.delete', - - UserOnboardingRead = 'userOnboarding.read', - UserOnboardingUpdate = 'userOnboarding.update', - UserOnboardingDelete = 'userOnboarding.delete', - - UserPreferenceRead = 'userPreference.read', - UserPreferenceUpdate = 'userPreference.update', - - UserProfileImageCreate = 'userProfileImage.create', - UserProfileImageRead = 'userProfileImage.read', - UserProfileImageUpdate = 'userProfileImage.update', - UserProfileImageDelete = 'userProfileImage.delete', - - QueueRead = 'queue.read', - QueueUpdate = 'queue.update', - - QueueJobCreate = 'queueJob.create', - QueueJobRead = 'queueJob.read', - QueueJobUpdate = 'queueJob.update', - QueueJobDelete = 'queueJob.delete', - - WorkflowCreate = 'workflow.create', - WorkflowRead = 'workflow.read', - WorkflowUpdate = 'workflow.update', - WorkflowDelete = 'workflow.delete', + ServerAbout = 'server.about', AdminUserCreate = 'adminUser.create', AdminUserRead = 'adminUser.read', AdminUserUpdate = 'adminUser.update', AdminUserDelete = 'adminUser.delete', - - AdminSessionRead = 'adminSession.read', - - AdminAuthUnlinkAll = 'adminAuth.unlinkAll', -} - -export enum SharedLinkType { - Album = 'ALBUM', - - /** - * Individual asset - * or group of assets that are not in an album - */ - Individual = 'INDIVIDUAL', -} - -export enum StorageFolder { - EncodedVideo = 'encoded-video', - Library = 'library', - Upload = 'upload', - Profile = 'profile', - Thumbnails = 'thumbs', - Backups = 'backups', -} - -export enum SystemMetadataKey { - MediaLocation = 'MediaLocation', - ReverseGeocodingState = 'reverse-geocoding-state', - FacialRecognitionState = 'facial-recognition-state', - MemoriesState = 'memories-state', - AdminOnboarding = 'admin-onboarding', - MaintenanceMode = 'maintenance-mode', - SystemConfig = 'system-config', - SystemFlags = 'system-flags', - VersionCheckState = 'version-check-state', - License = 'license', -} - -export enum UserMetadataKey { - Preferences = 'preferences', - License = 'license', - Onboarding = 'onboarding', -} - -export enum AssetMetadataKey { - MobileApp = 'mobile-app', -} - -export enum UserAvatarColor { - Primary = 'primary', - Pink = 'pink', - Red = 'red', - Yellow = 'yellow', - Blue = 'blue', - Green = 'green', - Purple = 'purple', - Orange = 'orange', - Gray = 'gray', - Amber = 'amber', } export enum UserStatus { @@ -348,112 +64,10 @@ export enum UserStatus { Deleted = 'deleted', } -export enum AssetStatus { - Active = 'active', - Trashed = 'trashed', - Deleted = 'deleted', -} - -export enum SourceType { - MachineLearning = 'machine-learning', - Exif = 'exif', - Manual = 'manual', -} - -export enum ManualJobName { - PersonCleanup = 'person-cleanup', - TagCleanup = 'tag-cleanup', - UserCleanup = 'user-cleanup', - MemoryCleanup = 'memory-cleanup', - MemoryCreate = 'memory-create', - BackupDatabase = 'backup-database', -} - -export enum AssetPathType { - Original = 'original', - EncodedVideo = 'encoded_video', -} - -export enum PersonPathType { - Face = 'face', -} - -export enum UserPathType { - Profile = 'profile', -} - -export type PathType = AssetFileType | AssetPathType | PersonPathType | UserPathType; - -export enum TranscodePolicy { - All = 'all', - Optimal = 'optimal', - Bitrate = 'bitrate', - Required = 'required', - Disabled = 'disabled', -} - -export enum TranscodeTarget { - None = 'NONE', - Audio = 'AUDIO', - Video = 'VIDEO', - All = 'ALL', -} - -export enum VideoCodec { - H264 = 'h264', - Hevc = 'hevc', - Vp9 = 'vp9', - Av1 = 'av1', -} - -export enum AudioCodec { - Mp3 = 'mp3', - Aac = 'aac', - LibOpus = 'libopus', - PcmS16le = 'pcm_s16le', -} - -export enum VideoContainer { - Mov = 'mov', - Mp4 = 'mp4', - Ogg = 'ogg', - Webm = 'webm', -} - -export enum TranscodeHardwareAcceleration { - Nvenc = 'nvenc', - Qsv = 'qsv', - Vaapi = 'vaapi', - Rkmpp = 'rkmpp', - Disabled = 'disabled', -} - -export enum ToneMapping { - Hable = 'hable', - Mobius = 'mobius', - Reinhard = 'reinhard', - Disabled = 'disabled', -} - -export enum CQMode { - Auto = 'auto', - Cqp = 'cqp', - Icq = 'icq', -} - -export enum Colorspace { - Srgb = 'srgb', - P3 = 'p3', -} - -export enum ImageFormat { - Jpeg = 'jpeg', - Webp = 'webp', -} - -export enum RawExtractedFormat { - Jpeg = 'jpeg', - Jxl = 'jxl', +export enum SystemMetadataKey { + AdminOnboarding = 'admin-onboarding', + SystemConfig = 'system-config', + SystemFlags = 'system-flags', } export enum LogLevel { @@ -488,7 +102,6 @@ export enum MetadataKey { } export enum RouteKey { - Asset = 'assets', User = 'users', } @@ -506,7 +119,6 @@ export enum ImmichEnvironment { export enum ImmichWorker { Api = 'api', - Maintenance = 'maintenance', Microservices = 'microservices', } @@ -518,156 +130,24 @@ export enum ImmichTelemetry { Job = 'job', } -export enum ExifOrientation { - Horizontal = 1, - MirrorHorizontal = 2, - Rotate180 = 3, - MirrorVertical = 4, - MirrorHorizontalRotate270CW = 5, - Rotate90CW = 6, - MirrorHorizontalRotate90CW = 7, - Rotate270CW = 8, -} - export enum DatabaseExtension { - Cube = 'cube', - EarthDistance = 'earthdistance', Vector = 'vector', - Vectors = 'vectors', - VectorChord = 'vchord', } export enum BootstrapEventPriority { - // Database service should be initialized before anything else, most other services need database access DatabaseService = -200, - // Detect and configure the media location before jobs are queued which may use it - StorageService = -195, - // Other services may need to queue jobs on bootstrap. JobService = -190, - // Initialise config after other bootstrap services, stop other services from using config on bootstrap SystemConfig = 100, } export enum QueueName { - ThumbnailGeneration = 'thumbnailGeneration', - MetadataExtraction = 'metadataExtraction', - VideoConversion = 'videoConversion', - FaceDetection = 'faceDetection', - FacialRecognition = 'facialRecognition', - SmartSearch = 'smartSearch', - DuplicateDetection = 'duplicateDetection', BackgroundTask = 'backgroundTask', - StorageTemplateMigration = 'storageTemplateMigration', - Migration = 'migration', - Search = 'search', - Sidecar = 'sidecar', - Library = 'library', - Notification = 'notifications', - BackupDatabase = 'backupDatabase', - Ocr = 'ocr', - Workflow = 'workflow', - Editor = 'editor', -} - -export enum QueueJobStatus { - Active = 'active', - Failed = 'failed', - Complete = 'completed', - Delayed = 'delayed', - Waiting = 'waiting', - Paused = 'paused', } export enum JobName { - AssetDelete = 'AssetDelete', - AssetDeleteCheck = 'AssetDeleteCheck', - AssetDetectFacesQueueAll = 'AssetDetectFacesQueueAll', - AssetDetectFaces = 'AssetDetectFaces', - AssetDetectDuplicatesQueueAll = 'AssetDetectDuplicatesQueueAll', - AssetDetectDuplicates = 'AssetDetectDuplicates', - AssetEditThumbnailGeneration = 'AssetEditThumbnailGeneration', - AssetEncodeVideoQueueAll = 'AssetEncodeVideoQueueAll', - AssetEncodeVideo = 'AssetEncodeVideo', - AssetEmptyTrash = 'AssetEmptyTrash', - AssetExtractMetadataQueueAll = 'AssetExtractMetadataQueueAll', - AssetExtractMetadata = 'AssetExtractMetadata', - AssetFileMigration = 'AssetFileMigration', - AssetGenerateThumbnailsQueueAll = 'AssetGenerateThumbnailsQueueAll', - AssetGenerateThumbnails = 'AssetGenerateThumbnails', - - AuditLogCleanup = 'AuditLogCleanup', - AuditTableCleanup = 'AuditTableCleanup', - - DatabaseBackup = 'DatabaseBackup', - - FacialRecognitionQueueAll = 'FacialRecognitionQueueAll', - FacialRecognition = 'FacialRecognition', - - FileDelete = 'FileDelete', - FileMigrationQueueAll = 'FileMigrationQueueAll', - - LibraryDeleteCheck = 'LibraryDeleteCheck', - LibraryDelete = 'LibraryDelete', - LibraryRemoveAsset = 'LibraryRemoveAsset', - LibrarySyncAssetsQueueAll = 'LibraryScanAssetsQueueAll', - LibrarySyncAssets = 'LibrarySyncAssets', - LibrarySyncFilesQueueAll = 'LibrarySyncFilesQueueAll', - LibrarySyncFiles = 'LibrarySyncFiles', - LibraryScanQueueAll = 'LibraryScanQueueAll', - - MemoryCleanup = 'MemoryCleanup', - MemoryGenerate = 'MemoryGenerate', - - NotificationsCleanup = 'NotificationsCleanup', - - NotifyUserSignup = 'NotifyUserSignup', - NotifyAlbumInvite = 'NotifyAlbumInvite', - NotifyAlbumUpdate = 'NotifyAlbumUpdate', - - UserDelete = 'UserDelete', UserDeleteCheck = 'UserDeleteCheck', - UserSyncUsage = 'UserSyncUsage', - - PersonCleanup = 'PersonCleanup', - PersonFileMigration = 'PersonFileMigration', - PersonGenerateThumbnail = 'PersonGenerateThumbnail', - + UserDelete = 'UserDelete', SessionCleanup = 'SessionCleanup', - - SendMail = 'SendMail', - - SidecarQueueAll = 'SidecarQueueAll', - SidecarCheck = 'SidecarCheck', - SidecarWrite = 'SidecarWrite', - - SmartSearchQueueAll = 'SmartSearchQueueAll', - SmartSearch = 'SmartSearch', - - StorageTemplateMigration = 'StorageTemplateMigration', - StorageTemplateMigrationSingle = 'StorageTemplateMigrationSingle', - - TagCleanup = 'TagCleanup', - - VersionCheck = 'VersionCheck', - - // OCR - OcrQueueAll = 'OcrQueueAll', - Ocr = 'Ocr', - - // Workflow - WorkflowRun = 'WorkflowRun', -} - -export enum QueueCommand { - Start = 'start', - /** @deprecated Use `updateQueue` instead */ - Pause = 'pause', - /** @deprecated Use `updateQueue` instead */ - Resume = 'resume', - /** @deprecated Use `emptyQueue` instead */ - Empty = 'empty', - /** @deprecated Use `emptyQueue` instead */ - ClearFailed = 'clear-failed', } export enum JobStatus { @@ -676,222 +156,19 @@ export enum JobStatus { Skipped = 'skipped', } -export enum QueueCleanType { - Failed = 'failed', -} - -export enum VectorIndex { - Clip = 'clip_index', - Face = 'face_index', -} - export enum DatabaseLock { - GeodataImport = 100, Migrations = 200, - SystemFileMounts = 300, - StorageTemplateMigration = 420, - VersionHistory = 500, - CLIPDimSize = 512, - Library = 1337, - NightlyJobs = 600, - MediaLocation = 700, GetSystemConfig = 69, - BackupDatabase = 42, - MaintenanceOperation = 621, - MemoryCreation = 777, -} - -export enum MaintenanceAction { - Start = 'start', - End = 'end', - SelectDatabaseRestore = 'select_database_restore', - RestoreDatabase = 'restore_database', } export enum ExitCode { AppRestart = 7, } -export enum SyncRequestType { - AlbumsV1 = 'AlbumsV1', - AlbumUsersV1 = 'AlbumUsersV1', - AlbumToAssetsV1 = 'AlbumToAssetsV1', - AlbumAssetsV1 = 'AlbumAssetsV1', - AlbumAssetExifsV1 = 'AlbumAssetExifsV1', - AssetsV1 = 'AssetsV1', - AssetExifsV1 = 'AssetExifsV1', - AssetMetadataV1 = 'AssetMetadataV1', - AuthUsersV1 = 'AuthUsersV1', - MemoriesV1 = 'MemoriesV1', - MemoryToAssetsV1 = 'MemoryToAssetsV1', - PartnersV1 = 'PartnersV1', - PartnerAssetsV1 = 'PartnerAssetsV1', - PartnerAssetExifsV1 = 'PartnerAssetExifsV1', - PartnerStacksV1 = 'PartnerStacksV1', - StacksV1 = 'StacksV1', - UsersV1 = 'UsersV1', - PeopleV1 = 'PeopleV1', - AssetFacesV1 = 'AssetFacesV1', - UserMetadataV1 = 'UserMetadataV1', -} - -export enum SyncEntityType { - AuthUserV1 = 'AuthUserV1', - - UserV1 = 'UserV1', - UserDeleteV1 = 'UserDeleteV1', - - AssetV1 = 'AssetV1', - AssetDeleteV1 = 'AssetDeleteV1', - AssetExifV1 = 'AssetExifV1', - AssetMetadataV1 = 'AssetMetadataV1', - AssetMetadataDeleteV1 = 'AssetMetadataDeleteV1', - - PartnerV1 = 'PartnerV1', - PartnerDeleteV1 = 'PartnerDeleteV1', - - PartnerAssetV1 = 'PartnerAssetV1', - PartnerAssetBackfillV1 = 'PartnerAssetBackfillV1', - PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1', - PartnerAssetExifV1 = 'PartnerAssetExifV1', - PartnerAssetExifBackfillV1 = 'PartnerAssetExifBackfillV1', - PartnerStackBackfillV1 = 'PartnerStackBackfillV1', - PartnerStackDeleteV1 = 'PartnerStackDeleteV1', - PartnerStackV1 = 'PartnerStackV1', - - AlbumV1 = 'AlbumV1', - AlbumDeleteV1 = 'AlbumDeleteV1', - - AlbumUserV1 = 'AlbumUserV1', - AlbumUserBackfillV1 = 'AlbumUserBackfillV1', - AlbumUserDeleteV1 = 'AlbumUserDeleteV1', - - AlbumAssetCreateV1 = 'AlbumAssetCreateV1', - AlbumAssetUpdateV1 = 'AlbumAssetUpdateV1', - AlbumAssetBackfillV1 = 'AlbumAssetBackfillV1', - AlbumAssetExifCreateV1 = 'AlbumAssetExifCreateV1', - AlbumAssetExifUpdateV1 = 'AlbumAssetExifUpdateV1', - AlbumAssetExifBackfillV1 = 'AlbumAssetExifBackfillV1', - - AlbumToAssetV1 = 'AlbumToAssetV1', - AlbumToAssetDeleteV1 = 'AlbumToAssetDeleteV1', - AlbumToAssetBackfillV1 = 'AlbumToAssetBackfillV1', - - MemoryV1 = 'MemoryV1', - MemoryDeleteV1 = 'MemoryDeleteV1', - - MemoryToAssetV1 = 'MemoryToAssetV1', - MemoryToAssetDeleteV1 = 'MemoryToAssetDeleteV1', - - StackV1 = 'StackV1', - StackDeleteV1 = 'StackDeleteV1', - - PersonV1 = 'PersonV1', - PersonDeleteV1 = 'PersonDeleteV1', - - AssetFaceV1 = 'AssetFaceV1', - AssetFaceDeleteV1 = 'AssetFaceDeleteV1', - - UserMetadataV1 = 'UserMetadataV1', - UserMetadataDeleteV1 = 'UserMetadataDeleteV1', - - SyncAckV1 = 'SyncAckV1', - SyncResetV1 = 'SyncResetV1', - SyncCompleteV1 = 'SyncCompleteV1', -} - -export enum NotificationLevel { - Success = 'success', - Error = 'error', - Warning = 'warning', - Info = 'info', -} - -export enum NotificationType { - JobFailed = 'JobFailed', - BackupFailed = 'BackupFailed', - SystemMessage = 'SystemMessage', - AlbumInvite = 'AlbumInvite', - AlbumUpdate = 'AlbumUpdate', - Custom = 'Custom', -} - -export enum OAuthTokenEndpointAuthMethod { - ClientSecretPost = 'client_secret_post', - ClientSecretBasic = 'client_secret_basic', -} - -export enum DatabaseSslMode { - Disable = 'disable', - Allow = 'allow', - Prefer = 'prefer', - Require = 'require', - VerifyFull = 'verify-full', -} - -export enum AssetVisibility { - Archive = 'archive', - Timeline = 'timeline', - - /** - * Video part of the LivePhotos and MotionPhotos - */ - Hidden = 'hidden', - Locked = 'locked', -} - -export enum CronJob { - LibraryScan = 'LibraryScan', - NightlyJobs = 'NightlyJobs', -} - export enum ApiTag { - Activities = 'Activities', - Albums = 'Albums', ApiKeys = 'API keys', Authentication = 'Authentication', - AuthenticationAdmin = 'Authentication (admin)', - Assets = 'Assets', - DatabaseBackups = 'Database Backups (admin)', - Deprecated = 'Deprecated', - Download = 'Download', - Duplicates = 'Duplicates', - Faces = 'Faces', - Jobs = 'Jobs', - Libraries = 'Libraries', - Maintenance = 'Maintenance (admin)', - Map = 'Map', - Memories = 'Memories', - Notifications = 'Notifications', - NotificationsAdmin = 'Notifications (admin)', - Partners = 'Partners', - People = 'People', - Plugins = 'Plugins', - Queues = 'Queues', - Search = 'Search', Server = 'Server', Sessions = 'Sessions', - SharedLinks = 'Shared links', - Stacks = 'Stacks', - Sync = 'Sync', - SystemConfig = 'System config', - SystemMetadata = 'System metadata', - Tags = 'Tags', - Timeline = 'Timeline', - Trash = 'Trash', - UsersAdmin = 'Users (admin)', Users = 'Users', - Views = 'Views', - Workflows = 'Workflows', -} - -export enum PluginContext { - Asset = 'asset', - Album = 'album', - Person = 'person', -} - -export enum PluginTriggerType { - AssetCreate = 'AssetCreate', - PersonRecognized = 'PersonRecognized', } diff --git a/server/src/main.ts b/server/src/main.ts index a8e3178a43..1a2c9418af 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,99 +1,23 @@ -import { Kysely, sql } from 'kysely'; -import { CommandFactory } from 'nest-commander'; import { ChildProcess, fork } from 'node:child_process'; import { dirname, join } from 'node:path'; import { Worker } from 'node:worker_threads'; -import { PostgresError } from 'postgres'; -import { ImmichAdminModule } from 'src/app.module'; -import { DatabaseLock, ExitCode, ImmichWorker, LogLevel, SystemMetadataKey } from 'src/enum'; +import { ExitCode, ImmichWorker } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { type DB } from 'src/schema'; -import { getKyselyConfig } from 'src/utils/database'; /** * Manages worker lifecycle */ class Workers { - /** - * Currently running workers - */ workers: Partial Promise | void }>> = {}; - - /** - * Fail-safe in case anything dies during restart - */ restarting = false; - /** - * Boot all enabled workers - */ - async bootstrap() { - const isMaintenanceMode = await this.isMaintenanceMode(); + bootstrap() { const { workers } = new ConfigRepository().getEnv(); - - if (isMaintenanceMode) { - this.startWorker(ImmichWorker.Maintenance); - } else { - await this.waitForFreeLock(); - - for (const worker of workers) { - this.startWorker(worker); - } + for (const worker of workers) { + this.startWorker(worker); } } - private async isMaintenanceMode(): Promise { - const { database } = new ConfigRepository().getEnv(); - const { log: _, ...kyselyConfig } = getKyselyConfig(database.config); - const kysely = new Kysely(kyselyConfig); - const systemMetadataRepository = new SystemMetadataRepository(kysely); - - try { - const value = await systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode); - return value?.isMaintenanceMode || false; - } catch (error) { - // Table doesn't exist (migrations haven't run yet) - if (error instanceof PostgresError && error.code === '42P01') { - return false; - } - - throw error; - } finally { - await kysely.destroy(); - } - } - - private async waitForFreeLock() { - const { database } = new ConfigRepository().getEnv(); - const kysely = new Kysely(getKyselyConfig(database.config)); - - let locked = false; - while (!locked) { - locked = await kysely.connection().execute(async (conn) => { - const { rows } = await sql<{ - pg_try_advisory_lock: boolean; - }>`SELECT pg_try_advisory_lock(${DatabaseLock.MaintenanceOperation})`.execute(conn); - - const isLocked = rows[0].pg_try_advisory_lock; - - if (isLocked) { - await sql`SELECT pg_advisory_unlock(${DatabaseLock.MaintenanceOperation})`.execute(conn); - } - - return isLocked; - }); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - - await kysely.destroy(); - } - - /** - * Start an individual worker - * @param name Worker - */ private startWorker(name: ImmichWorker) { console.log(`Starting ${name} worker`); @@ -108,12 +32,10 @@ class Workers { const worker = fork(workerFile, [], { execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)), }); - kill = (signal) => void worker.kill(signal); anyWorker = worker; } else { const worker = new Worker(workerFile); - kill = async () => void (await worker.terminate()); anyWorker = worker; } @@ -129,28 +51,22 @@ class Workers { } onExit(name: ImmichWorker, exitCode: number | null) { - // restart immich server if (exitCode === ExitCode.AppRestart || this.restarting) { this.restarting = true; - console.info(`${name} worker shutdown for restart`); delete this.workers[name]; - // once all workers shut down, bootstrap again if (Object.keys(this.workers).length === 0) { void this.bootstrap(); this.restarting = false; } - return; } - // shutdown the entire process delete this.workers[name]; if (exitCode !== 0) { console.error(`${name} worker exited with code ${exitCode}`); - if (this.workers[ImmichWorker.Api] && name !== ImmichWorker.Api) { console.error('Killing api process'); void this.workers[ImmichWorker.Api].kill('SIGTERM'); @@ -162,30 +78,6 @@ class Workers { } function main() { - const immichApp = process.argv[2]; - if (immichApp) { - process.argv.splice(2, 1); - } - - if (immichApp === 'immich-admin') { - process.title = 'immich_admin_cli'; - process.env.IMMICH_LOG_LEVEL = LogLevel.Warn; - - return CommandFactory.run(ImmichAdminModule); - } - - if (immichApp === 'immich' || immichApp === 'microservices') { - console.error( - `Using "start.sh ${immichApp}" has been deprecated. See https://github.com/immich-app/immich/releases/tag/v1.118.0 for more information.`, - ); - process.exit(1); - } - - if (immichApp) { - console.error(`Unknown command: "${immichApp}"`); - process.exit(1); - } - process.title = 'immich'; void new Workers().bootstrap(); } diff --git a/server/src/maintenance/maintenance-auth.guard.ts b/server/src/maintenance/maintenance-auth.guard.ts deleted file mode 100644 index 08aaad516b..0000000000 --- a/server/src/maintenance/maintenance-auth.guard.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - SetMetadata, - applyDecorators, - createParamDecorator, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { Request } from 'express'; -import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto'; -import { MetadataKey } from 'src/enum'; -import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -export const MaintenanceRoute = (options = {}): MethodDecorator => { - const decorators: MethodDecorator[] = [SetMetadata(MetadataKey.AuthRoute, options)]; - return applyDecorators(...decorators); -}; - -export interface MaintenanceAuthRequest extends Request { - auth?: MaintenanceAuthDto; -} - -export interface MaintenanceAuthenticatedRequest extends Request { - auth: MaintenanceAuthDto; -} - -export const MaintenanceAuth = createParamDecorator((data, context: ExecutionContext): MaintenanceAuthDto => { - return context.switchToHttp().getRequest().auth; -}); - -@Injectable() -export class MaintenanceAuthGuard implements CanActivate { - constructor( - private logger: LoggingRepository, - private reflector: Reflector, - private service: MaintenanceWorkerService, - ) { - this.logger.setContext(MaintenanceAuthGuard.name); - } - - async canActivate(context: ExecutionContext): Promise { - const targets = [context.getHandler()]; - const options = this.reflector.getAllAndOverride<{ _emptyObject: never } | undefined>( - MetadataKey.AuthRoute, - targets, - ); - if (!options) { - return true; - } - - const request = context.switchToHttp().getRequest(); - request.auth = await this.service.authenticate(request.headers); - - return true; - } -} diff --git a/server/src/maintenance/maintenance-health.repository.ts b/server/src/maintenance/maintenance-health.repository.ts deleted file mode 100644 index aeef93ec51..0000000000 --- a/server/src/maintenance/maintenance-health.repository.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { fork } from 'node:child_process'; -import { dirname, join } from 'node:path'; - -@Injectable() -export class MaintenanceHealthRepository { - checkApiHealth(): Promise { - return new Promise((resolve, reject) => { - // eslint-disable-next-line unicorn/prefer-module - const basePath = dirname(__filename); - const workerFile = join(basePath, '..', 'workers', `api.js`); - - const worker = fork(workerFile, [], { - execArgv: process.execArgv.filter((arg) => !arg.startsWith('--inspect')), - env: { - ...process.env, - IMMICH_HOST: '127.0.0.1', - IMMICH_PORT: '33001', - }, - stdio: ['ignore', 'pipe', 'ignore', 'ipc'], - }); - - async function checkHealth() { - try { - const response = await fetch('http://127.0.0.1:33001/api/server/config'); - const { isOnboarded } = await response.json(); - if (isOnboarded) { - resolve(); - } else { - reject(new Error('Server health check failed, no admin exists.')); - } - } catch (error) { - reject(error); - } finally { - if (worker.exitCode === null) { - worker.kill('SIGTERM'); - } - } - } - - let output = '', - alive = false; - - worker.stdout?.on('data', (data) => { - if (alive) { - return; - } - - output += data; - - if (output.includes('Immich Server is listening')) { - alive = true; - void checkHealth(); - } - }); - - worker.on('exit', reject); - worker.on('error', reject); - - setTimeout(() => { - if (worker.exitCode === null) { - worker.kill('SIGTERM'); - } - }, 20_000); - }); - } -} diff --git a/server/src/maintenance/maintenance-websocket.repository.ts b/server/src/maintenance/maintenance-websocket.repository.ts deleted file mode 100644 index d13ceb083f..0000000000 --- a/server/src/maintenance/maintenance-websocket.repository.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { - OnGatewayConnection, - OnGatewayDisconnect, - OnGatewayInit, - WebSocketGateway, - WebSocketServer, -} from '@nestjs/websockets'; -import { Server, Socket } from 'socket.io'; -import { MaintenanceAuthDto, MaintenanceStatusResponseDto } from 'src/dtos/maintenance.dto'; -import { AppRepository } from 'src/repositories/app.repository'; -import { AppRestartEvent, ArgsOf } from 'src/repositories/event.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -interface ServerEventMap { - AppRestart: [AppRestartEvent]; - MaintenanceStatus: [MaintenanceStatusResponseDto]; -} - -interface ClientEventMap { - AppRestartV1: [AppRestartEvent]; - MaintenanceStatusV1: [MaintenanceStatusResponseDto]; -} - -type AuthFn = (client: Socket) => Promise; -type StatusUpdateFn = (status: MaintenanceStatusResponseDto) => void; - -@WebSocketGateway({ - cors: true, - path: '/api/socket.io', - transports: ['websocket'], -}) -@Injectable() -export class MaintenanceWebsocketRepository implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { - private authFn?: AuthFn; - private statusUpdateFn?: StatusUpdateFn; - - @WebSocketServer() - private server?: Server; - - constructor( - private logger: LoggingRepository, - private appRepository: AppRepository, - ) { - this.logger.setContext(MaintenanceWebsocketRepository.name); - } - - afterInit(server: Server) { - this.logger.log('Initialized websocket server'); - server.on('MaintenanceStatus', (status) => this.statusUpdateFn?.(status)); - server.on('AppRestart', (event: ArgsOf<'AppRestart'>, ack?: (ok: 'ok') => void) => { - this.logger.log(`Restarting due to event... ${JSON.stringify(event)}`); - - ack?.('ok'); - this.appRepository.exitApp(); - }); - } - - clientSend(event: T, room: string, ...data: ClientEventMap[T]) { - this.server?.to(room).emit(event, ...data); - } - - clientBroadcast(event: T, ...data: ClientEventMap[T]) { - this.server?.emit(event, ...data); - } - - serverSend(event: T, ...args: ServerEventMap[T]): void { - this.logger.debug(`Server event: ${event} (send)`); - this.server?.serverSideEmit(event, ...args); - } - - async handleConnection(client: Socket) { - try { - await this.authFn!(client); - await client.join('private'); - this.logger.log(`Websocket Connect: ${client.id} (private)`); - } catch { - await client.join('public'); - this.logger.log(`Websocket Connect: ${client.id} (public)`); - } - } - - async handleDisconnect(client: Socket) { - this.logger.log(`Websocket Disconnect: ${client.id}`); - await Promise.allSettled([client.leave('private'), client.leave('public')]); - } - - setAuthFn(fn: (client: Socket) => Promise) { - this.authFn = fn; - } - - setStatusUpdateFn(fn: (status: MaintenanceStatusResponseDto) => void) { - this.statusUpdateFn = fn; - } -} diff --git a/server/src/maintenance/maintenance-worker.controller.ts b/server/src/maintenance/maintenance-worker.controller.ts deleted file mode 100644 index 72527e27c0..0000000000 --- a/server/src/maintenance/maintenance-worker.controller.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - Body, - Controller, - Delete, - Get, - Next, - Param, - Post, - Req, - Res, - UploadedFile, - UseInterceptors, -} from '@nestjs/common'; -import { FileInterceptor } from '@nestjs/platform-express'; -import { NextFunction, Request, Response } from 'express'; -import { - MaintenanceAuthDto, - MaintenanceDetectInstallResponseDto, - MaintenanceLoginDto, - MaintenanceStatusResponseDto, - SetMaintenanceModeDto, -} from 'src/dtos/maintenance.dto'; -import { ServerConfigDto, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { ImmichCookie } from 'src/enum'; -import { MaintenanceRoute } from 'src/maintenance/maintenance-auth.guard'; -import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; -import { GetLoginDetails } from 'src/middleware/auth.guard'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { LoginDetails } from 'src/services/auth.service'; -import { sendFile } from 'src/utils/file'; -import { respondWithCookie } from 'src/utils/response'; -import { FilenameParamDto } from 'src/validation'; - -import type { DatabaseBackupController as _DatabaseBackupController } from 'src/controllers/database-backup.controller'; -import type { ServerController as _ServerController } from 'src/controllers/server.controller'; -import { DatabaseBackupDeleteDto, DatabaseBackupListResponseDto } from 'src/dtos/database-backup.dto'; - -@Controller() -export class MaintenanceWorkerController { - constructor( - private logger: LoggingRepository, - private service: MaintenanceWorkerService, - ) {} - - /** - * {@link _ServerController.getServerConfig } - */ - @Get('server/config') - getServerConfig(): ServerConfigDto { - return this.service.getSystemConfig(); - } - - @Get('server/version') - getServerVersion(): ServerVersionResponseDto { - return this.service.getVersion(); - } - - /** - * {@link _DatabaseBackupController.listDatabaseBackups} - */ - @Get('admin/database-backups') - @MaintenanceRoute() - listDatabaseBackups(): Promise { - return this.service.listBackups(); - } - - /** - * {@link _DatabaseBackupController.downloadDatabaseBackup} - */ - @Get('admin/database-backups/:filename') - @MaintenanceRoute() - async downloadDatabaseBackup( - @Param() { filename }: FilenameParamDto, - @Res() res: Response, - @Next() next: NextFunction, - ) { - await sendFile(res, next, () => this.service.downloadBackup(filename), this.logger); - } - - /** - * {@link _DatabaseBackupController.deleteDatabaseBackup} - */ - @Delete('admin/database-backups') - @MaintenanceRoute() - async deleteDatabaseBackup(@Body() dto: DatabaseBackupDeleteDto): Promise { - return this.service.deleteBackup(dto.backups); - } - - /** - * {@link _DatabaseBackupController.uploadDatabaseBackup} - */ - @Post('admin/database-backups/upload') - @MaintenanceRoute() - @UseInterceptors(FileInterceptor('file')) - uploadDatabaseBackup( - @UploadedFile() - file: Express.Multer.File, - ): Promise { - return this.service.uploadBackup(file); - } - - @Get('admin/maintenance/status') - maintenanceStatus(@Req() request: Request): Promise { - return this.service.status(request.cookies[ImmichCookie.MaintenanceToken]); - } - - @Get('admin/maintenance/detect-install') - detectPriorInstall(): Promise { - return this.service.detectPriorInstall(); - } - - @Post('admin/maintenance/login') - async maintenanceLogin( - @Req() request: Request, - @Body() dto: MaintenanceLoginDto, - @GetLoginDetails() loginDetails: LoginDetails, - @Res({ passthrough: true }) res: Response, - ): Promise { - const token = dto.token ?? request.cookies[ImmichCookie.MaintenanceToken]; - const auth = await this.service.login(token); - return respondWithCookie(res, auth, { - isSecure: loginDetails.isSecure, - values: [{ key: ImmichCookie.MaintenanceToken, value: token }], - }); - } - - @Post('admin/maintenance') - @MaintenanceRoute() - setMaintenanceMode(@Body() dto: SetMaintenanceModeDto): void { - void this.service.setAction(dto); - } -} diff --git a/server/src/maintenance/maintenance-worker.service.spec.ts b/server/src/maintenance/maintenance-worker.service.spec.ts deleted file mode 100644 index 9fd8f38fcb..0000000000 --- a/server/src/maintenance/maintenance-worker.service.spec.ts +++ /dev/null @@ -1,544 +0,0 @@ -import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { SignJWT } from 'jose'; -import { DateTime } from 'luxon'; -import { PassThrough, Readable } from 'node:stream'; -import { StorageCore } from 'src/cores/storage.core'; -import { MaintenanceAction, StorageFolder, SystemMetadataKey } from 'src/enum'; -import { MaintenanceHealthRepository } from 'src/maintenance/maintenance-health.repository'; -import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository'; -import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; -import { automock, AutoMocked, getMocks, mockDuplex, mockSpawn, ServiceMocks } from 'test/utils'; - -function* mockData() { - yield ''; -} - -describe(MaintenanceWorkerService.name, () => { - let sut: MaintenanceWorkerService; - let mocks: ServiceMocks; - let maintenanceWebsocketRepositoryMock: AutoMocked; - let maintenanceHealthRepositoryMock: AutoMocked; - - beforeEach(() => { - mocks = getMocks(); - maintenanceWebsocketRepositoryMock = automock(MaintenanceWebsocketRepository, { - args: [mocks.logger], - strict: false, - }); - maintenanceHealthRepositoryMock = automock(MaintenanceHealthRepository, { - args: [mocks.logger], - strict: false, - }); - - sut = new MaintenanceWorkerService( - mocks.logger as never, - mocks.app, - mocks.config, - mocks.systemMetadata as never, - maintenanceWebsocketRepositoryMock, - maintenanceHealthRepositoryMock, - mocks.storage as never, - mocks.process, - mocks.database as never, - ); - - sut.mock({ - active: true, - action: MaintenanceAction.Start, - }); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getSystemConfig', () => { - it('should respond the server is in maintenance mode', () => { - expect(sut.getSystemConfig()).toMatchObject( - expect.objectContaining({ - maintenanceMode: true, - }), - ); - - expect(mocks.systemMetadata.get).toHaveBeenCalledTimes(0); - }); - }); - - describe.skip('ssr'); - describe.skip('detectMediaLocation'); - - describe('setStatus', () => { - it('should broadcast status', () => { - sut.setStatus({ - active: true, - action: MaintenanceAction.Start, - task: 'abc', - error: 'def', - }); - - expect(maintenanceWebsocketRepositoryMock.serverSend).toHaveBeenCalled(); - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledTimes(2); - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'private', { - active: true, - action: 'start', - task: 'abc', - error: 'def', - }); - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'public', { - active: true, - action: 'start', - task: 'abc', - error: 'Something went wrong, see logs!', - }); - }); - }); - - describe('logSecret', () => { - const RE_LOGIN_URL = /https:\/\/my.immich.app\/maintenance\?token=([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)/; - - it('should log a valid login URL', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - await expect(sut.logSecret()).resolves.toBeUndefined(); - expect(mocks.logger.log).toHaveBeenCalledWith(expect.stringMatching(RE_LOGIN_URL)); - - const [url] = mocks.logger.log.mock.lastCall!; - const token = RE_LOGIN_URL.exec(url)![1]; - - await expect(sut.login(token)).resolves.toEqual( - expect.objectContaining({ - username: 'immich-admin', - }), - ); - }); - }); - - describe('authenticate', () => { - it('should fail without a cookie', async () => { - await expect(sut.authenticate({})).rejects.toThrowError(new UnauthorizedException('Missing JWT Token')); - }); - - it('should parse cookie properly', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - await expect( - sut.authenticate({ - cookie: 'immich_maintenance_token=invalid-jwt', - }), - ).rejects.toThrowError(new UnauthorizedException('Invalid JWT Token')); - }); - }); - - describe('status', () => { - beforeEach(() => { - sut.mock({ - active: true, - action: MaintenanceAction.Start, - error: 'secret value!', - }); - }); - - it('generates private status', async () => { - const jwt = await new SignJWT({ _mockValue: true }) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setExpirationTime('4h') - .sign(new TextEncoder().encode('secret')); - - await expect(sut.status(jwt)).resolves.toEqual( - expect.objectContaining({ - error: 'secret value!', - }), - ); - }); - - it('generates public status', async () => { - await expect(sut.status()).resolves.toEqual( - expect.objectContaining({ - error: 'Something went wrong, see logs!', - }), - ); - }); - }); - - describe('detectPriorInstall', () => { - it('generate report about prior installation', async () => { - mocks.storage.readdir.mockResolvedValue(['.immich', 'file1', 'file2']); - mocks.storage.readFile.mockResolvedValue(undefined as never); - mocks.storage.overwriteFile.mockRejectedValue(undefined as never); - - await expect(sut.detectPriorInstall()).resolves.toMatchInlineSnapshot(` - { - "storage": [ - { - "files": 2, - "folder": "encoded-video", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "library", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "upload", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "profile", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "thumbs", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "backups", - "readable": true, - "writable": false, - }, - ], - } - `); - }); - }); - - describe('login', () => { - it('should fail without token', async () => { - await expect(sut.login()).rejects.toThrowError(new UnauthorizedException('Missing JWT Token')); - }); - - it('should fail with expired JWT', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - const jwt = await new SignJWT({}) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setExpirationTime('0s') - .sign(new TextEncoder().encode('secret')); - - await expect(sut.login(jwt)).rejects.toThrowError(new UnauthorizedException('Invalid JWT Token')); - }); - - it('should succeed with valid JWT', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - const jwt = await new SignJWT({ _mockValue: true }) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setExpirationTime('4h') - .sign(new TextEncoder().encode('secret')); - - await expect(sut.login(jwt)).resolves.toEqual( - expect.objectContaining({ - _mockValue: true, - }), - ); - }); - }); - - describe.skip('setAction'); // just calls setStatus+runAction - - /** - * Actions - */ - - describe('action: start', () => { - it('should not do anything', async () => { - await sut.runAction({ - action: MaintenanceAction.Start, - }); - - expect(mocks.logger.log).toHaveBeenCalledTimes(0); - }); - }); - - describe('action: end', () => { - it('should set maintenance mode', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false }); - await sut.runAction({ - action: MaintenanceAction.End, - }); - - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: false, - }); - - expect(maintenanceWebsocketRepositoryMock.clientBroadcast).toHaveBeenCalledWith('AppRestartV1', { - isMaintenanceMode: false, - }); - - expect(maintenanceWebsocketRepositoryMock.serverSend).toHaveBeenCalledWith('AppRestart', { - isMaintenanceMode: false, - }); - }); - }); - - describe('action: restore database', () => { - beforeEach(() => { - mocks.database.tryLock.mockResolvedValueOnce(true); - - mocks.storage.readdir.mockResolvedValue([]); - mocks.process.spawn.mockReturnValue(mockSpawn(0, 'data', '')); - mocks.process.spawnDuplexStream.mockImplementation(() => mockDuplex('command', 0, 'data', '')); - mocks.process.fork.mockImplementation(() => mockSpawn(0, 'Immich Server is listening', '')); - mocks.storage.rename.mockResolvedValue(); - mocks.storage.unlink.mockResolvedValue(); - mocks.storage.createPlainReadStream.mockReturnValue(Readable.from(mockData())); - mocks.storage.createWriteStream.mockReturnValue(new PassThrough()); - mocks.storage.createGzip.mockReturnValue(new PassThrough()); - mocks.storage.createGunzip.mockReturnValue(new PassThrough()); - }); - - it('should update maintenance mode state', async () => { - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'filename', - }); - - expect(mocks.database.tryLock).toHaveBeenCalled(); - expect(mocks.logger.log).toHaveBeenCalledWith('Running maintenance action restore_database'); - - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: true, - secret: 'secret', - action: { - action: 'start', - }, - }); - }); - - it('should fail to restore invalid backup', async () => { - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'filename', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'private', { - active: true, - action: MaintenanceAction.RestoreDatabase, - error: 'Error: Invalid backup file format!', - task: 'error', - }); - }); - - it('should successfully run a backup', async () => { - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'development-filename.sql', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith( - 'MaintenanceStatusV1', - expect.any(String), - { - active: true, - action: MaintenanceAction.RestoreDatabase, - task: 'ready', - progress: expect.any(Number), - }, - ); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenLastCalledWith( - 'MaintenanceStatusV1', - expect.any(String), - { - active: true, - action: 'end', - }, - ); - - expect(maintenanceHealthRepositoryMock.checkApiHealth).toHaveBeenCalled(); - expect(mocks.process.spawnDuplexStream).toHaveBeenCalledTimes(3); - }); - - it('should fail if backup creation fails', async () => { - mocks.process.spawnDuplexStream.mockReturnValueOnce(mockDuplex('pg_dump', 1, '', 'error')); - - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'development-filename.sql', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'private', { - active: true, - action: MaintenanceAction.RestoreDatabase, - error: 'Error: pg_dump non-zero exit code (1)\nerror', - task: 'error', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenLastCalledWith( - 'MaintenanceStatusV1', - expect.any(String), - expect.objectContaining({ - task: 'error', - }), - ); - }); - - it('should fail if restore itself fails', async () => { - mocks.process.spawnDuplexStream - .mockReturnValueOnce(mockDuplex('pg_dump', 0, 'data', '')) - .mockReturnValueOnce(mockDuplex('gzip', 0, 'data', '')) - .mockReturnValueOnce(mockDuplex('psql', 1, '', 'error')); - - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'development-filename.sql', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'private', { - active: true, - action: MaintenanceAction.RestoreDatabase, - error: 'Error: psql non-zero exit code (1)\nerror', - task: 'error', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenLastCalledWith( - 'MaintenanceStatusV1', - expect.any(String), - expect.objectContaining({ - task: 'error', - }), - ); - }); - - it('should rollback if database migrations fail', async () => { - mocks.database.runMigrations.mockRejectedValue(new Error('Migrations Error')); - - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'development-filename.sql', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'private', { - active: true, - action: MaintenanceAction.RestoreDatabase, - error: 'Error: Migrations Error', - task: 'error', - }); - - expect(maintenanceHealthRepositoryMock.checkApiHealth).toHaveBeenCalledTimes(0); - expect(mocks.process.spawnDuplexStream).toHaveBeenCalledTimes(4); - }); - - it('should rollback if API healthcheck fails', async () => { - maintenanceHealthRepositoryMock.checkApiHealth.mockRejectedValue(new Error('Health Error')); - - await sut.runAction({ - action: MaintenanceAction.RestoreDatabase, - restoreBackupFilename: 'development-filename.sql', - }); - - expect(maintenanceWebsocketRepositoryMock.clientSend).toHaveBeenCalledWith('MaintenanceStatusV1', 'private', { - active: true, - action: MaintenanceAction.RestoreDatabase, - error: 'Error: Health Error', - task: 'error', - }); - - expect(maintenanceHealthRepositoryMock.checkApiHealth).toHaveBeenCalled(); - expect(mocks.process.spawnDuplexStream).toHaveBeenCalledTimes(4); - }); - }); - - /** - * Backups - */ - - describe('listBackups', () => { - it('should give us all backups', async () => { - mocks.storage.readdir.mockResolvedValue([ - `immich-db-backup-${DateTime.fromISO('2025-07-25T11:02:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz.tmp`, - `immich-db-backup-${DateTime.fromISO('2025-07-27T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - 'immich-db-backup-1753789649000.sql.gz', - `immich-db-backup-${DateTime.fromISO('2025-07-29T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - ]); - mocks.storage.stat.mockResolvedValue({ size: 1024 } as any); - - await expect(sut.listBackups()).resolves.toMatchObject({ - backups: [ - { filename: 'immich-db-backup-20250729T110116-v1.234.5-pg14.5.sql.gz', filesize: 1024 }, - { filename: 'immich-db-backup-20250727T110116-v1.234.5-pg14.5.sql.gz', filesize: 1024 }, - { filename: 'immich-db-backup-1753789649000.sql.gz', filesize: 1024 }, - ], - }); - }); - }); - - describe('deleteBackup', () => { - it('should reject invalid file names', async () => { - await expect(sut.deleteBackup(['filename'])).rejects.toThrowError( - new BadRequestException('Invalid backup name!'), - ); - }); - - it('should unlink the target file', async () => { - await sut.deleteBackup(['filename.sql']); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/filename.sql`, - ); - }); - }); - - describe('uploadBackup', () => { - it('should reject invalid file names', async () => { - await expect(sut.uploadBackup({ originalname: 'invalid backup' } as never)).rejects.toThrowError( - new BadRequestException('Invalid backup name!'), - ); - }); - - it('should write file', async () => { - await sut.uploadBackup({ originalname: 'path.sql.gz', buffer: 'buffer' } as never); - expect(mocks.storage.createOrOverwriteFile).toBeCalledWith('/data/backups/uploaded-path.sql.gz', 'buffer'); - }); - }); - - describe('downloadBackup', () => { - it('should reject invalid file names', () => { - expect(() => sut.downloadBackup('invalid backup')).toThrowError(new BadRequestException('Invalid backup name!')); - }); - - it('should get backup path', () => { - expect(sut.downloadBackup('hello.sql.gz')).toEqual( - expect.objectContaining({ - path: '/data/backups/hello.sql.gz', - }), - ); - }); - }); -}); diff --git a/server/src/maintenance/maintenance-worker.service.ts b/server/src/maintenance/maintenance-worker.service.ts deleted file mode 100644 index 6415693733..0000000000 --- a/server/src/maintenance/maintenance-worker.service.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { parse } from 'cookie'; -import { NextFunction, Request, Response } from 'express'; -import { jwtVerify } from 'jose'; -import { readFileSync } from 'node:fs'; -import { IncomingHttpHeaders } from 'node:http'; -import { serverVersion } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; -import { - MaintenanceAuthDto, - MaintenanceDetectInstallResponseDto, - MaintenanceStatusResponseDto, - SetMaintenanceModeDto, -} from 'src/dtos/maintenance.dto'; -import { ServerConfigDto, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { DatabaseLock, ImmichCookie, MaintenanceAction, SystemMetadataKey } from 'src/enum'; -import { MaintenanceHealthRepository } from 'src/maintenance/maintenance-health.repository'; -import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository'; -import { AppRepository } from 'src/repositories/app.repository'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { DatabaseRepository } from 'src/repositories/database.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { ProcessRepository } from 'src/repositories/process.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { type ApiService as _ApiService } from 'src/services/api.service'; -import { type BaseService as _BaseService } from 'src/services/base.service'; -import { type DatabaseBackupService as _DatabaseBackupService } from 'src/services/database-backup.service'; -import { type ServerService as _ServerService } from 'src/services/server.service'; -import { type VersionService as _VersionService } from 'src/services/version.service'; -import { MaintenanceModeState } from 'src/types'; -import { getConfig } from 'src/utils/config'; -import { - deleteDatabaseBackup, - downloadDatabaseBackup, - listDatabaseBackups, - restoreDatabaseBackup, - uploadDatabaseBackup, -} from 'src/utils/database-backups'; -import { ImmichFileResponse } from 'src/utils/file'; -import { createMaintenanceLoginUrl, detectPriorInstall } from 'src/utils/maintenance'; -import { getExternalDomain } from 'src/utils/misc'; - -/** - * This service is available inside of maintenance mode to manage maintenance mode - */ -@Injectable() -export class MaintenanceWorkerService { - #secret: string | null = null; - #status: MaintenanceStatusResponseDto = { - active: true, - action: MaintenanceAction.Start, - }; - - constructor( - protected logger: LoggingRepository, - private appRepository: AppRepository, - private configRepository: ConfigRepository, - private systemMetadataRepository: SystemMetadataRepository, - private maintenanceWebsocketRepository: MaintenanceWebsocketRepository, - private maintenanceHealthRepository: MaintenanceHealthRepository, - private storageRepository: StorageRepository, - private processRepository: ProcessRepository, - private databaseRepository: DatabaseRepository, - ) { - this.logger.setContext(this.constructor.name); - } - - mock(status: MaintenanceStatusResponseDto) { - this.#secret = 'secret'; - this.#status = status; - } - - async init() { - const state = (await this.systemMetadataRepository.get( - SystemMetadataKey.MaintenanceMode, - )) as MaintenanceModeState & { isMaintenanceMode: true }; - - this.#secret = state.secret; - this.#status = { - active: true, - action: state.action?.action ?? MaintenanceAction.Start, - }; - - StorageCore.setMediaLocation(this.detectMediaLocation()); - - this.maintenanceWebsocketRepository.setAuthFn(async (client) => this.authenticate(client.request.headers)); - this.maintenanceWebsocketRepository.setStatusUpdateFn((status) => (this.#status = status)); - - await this.logSecret(); - - if (state.action) { - void this.runAction(state.action); - } - } - - /** - * {@link _BaseService.configRepos} - */ - private get configRepos() { - return { - configRepo: this.configRepository, - metadataRepo: this.systemMetadataRepository, - logger: this.logger, - }; - } - - /** - * {@link _BaseService.prototype.getConfig} - */ - private getConfig(options: { withCache: boolean }) { - return getConfig(this.configRepos, options); - } - - /** - * {@link _ServerService.getSystemConfig} - */ - getSystemConfig() { - return { - maintenanceMode: true, - } as ServerConfigDto; - } - - /** - * {@link _VersionService.getVersion} - */ - getVersion() { - return ServerVersionResponseDto.fromSemVer(serverVersion); - } - - /** - * {@link _ApiService.ssr} - */ - ssr(excludePaths: string[]) { - const { resourcePaths } = this.configRepository.getEnv(); - - let index = ''; - try { - index = readFileSync(resourcePaths.web.indexHtml).toString(); - } catch { - this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`); - } - - return (request: Request, res: Response, next: NextFunction) => { - if ( - request.url.startsWith('/api') || - request.method.toLowerCase() !== 'get' || - excludePaths.some((item) => request.url.startsWith(item)) - ) { - return next(); - } - - const maintenancePath = '/maintenance'; - if (!request.url.startsWith(maintenancePath)) { - const params = new URLSearchParams(); - params.set('continue', request.path); - return res.redirect(`${maintenancePath}?${params}`); - } - - res.status(200).type('text/html').header('Cache-Control', 'no-store').send(index); - }; - } - - /** - * {@link _StorageService.detectMediaLocation} - */ - detectMediaLocation(): string { - const envData = this.configRepository.getEnv(); - if (envData.storage.mediaLocation) { - return envData.storage.mediaLocation; - } - - const targets: string[] = []; - const candidates = ['/data', '/usr/src/app/upload']; - - for (const candidate of candidates) { - const exists = this.storageRepository.existsSync(candidate); - if (exists) { - targets.push(candidate); - } - } - - if (targets.length === 1) { - return targets[0]; - } - - return '/usr/src/app/upload'; - } - - /** - * {@link _DatabaseBackupService.listBackups} - */ - async listBackups(): Promise<{ backups: { filename: string; filesize: number }[] }> { - const backups = await listDatabaseBackups(this.backupRepos); - return { backups }; - } - - /** - * {@link _DatabaseBackupService.deleteBackup} - */ - async deleteBackup(files: string[]): Promise { - return deleteDatabaseBackup(this.backupRepos, files); - } - - /** - * {@link _DatabaseBackupService.uploadBackup} - */ - async uploadBackup(file: Express.Multer.File): Promise { - return uploadDatabaseBackup(this.backupRepos, file); - } - - /** - * {@link _DatabaseBackupService.downloadBackup} - */ - downloadBackup(fileName: string): ImmichFileResponse { - return downloadDatabaseBackup(fileName); - } - - private get secret() { - if (!this.#secret) { - throw new Error('Secret is not initialised yet.'); - } - - return this.#secret; - } - - private get backupRepos() { - return { - logger: this.logger, - storage: this.storageRepository, - config: this.configRepository, - process: this.processRepository, - database: this.databaseRepository, - health: this.maintenanceHealthRepository, - }; - } - - private getStatus(): MaintenanceStatusResponseDto { - return this.#status; - } - - private getPublicStatus(): MaintenanceStatusResponseDto { - const state = structuredClone(this.#status); - - if (state.error) { - state.error = 'Something went wrong, see logs!'; - } - - return state; - } - - setStatus(status: MaintenanceStatusResponseDto): void { - this.#status = status; - this.maintenanceWebsocketRepository.serverSend('MaintenanceStatus', status); - this.maintenanceWebsocketRepository.clientSend('MaintenanceStatusV1', 'private', status); - this.maintenanceWebsocketRepository.clientSend('MaintenanceStatusV1', 'public', this.getPublicStatus()); - } - - async logSecret(): Promise { - const { server } = await this.getConfig({ withCache: true }); - - const baseUrl = getExternalDomain(server); - const url = await createMaintenanceLoginUrl( - baseUrl, - { - username: 'immich-admin', - }, - this.secret, - ); - - this.logger.log(`\n\n🚧 Immich is in maintenance mode, you can log in using the following URL:\n${url}\n`); - } - - async authenticate(headers: IncomingHttpHeaders): Promise { - const jwtToken = parse(headers.cookie || '')[ImmichCookie.MaintenanceToken]; - return this.login(jwtToken); - } - - async status(potentiallyJwt?: string): Promise { - try { - await this.login(potentiallyJwt); - return this.getStatus(); - } catch { - return this.getPublicStatus(); - } - } - - detectPriorInstall(): Promise { - return detectPriorInstall(this.storageRepository); - } - - async login(jwt?: string): Promise { - if (!jwt) { - throw new UnauthorizedException('Missing JWT Token'); - } - - try { - const result = await jwtVerify(jwt, new TextEncoder().encode(this.secret)); - return result.payload; - } catch { - throw new UnauthorizedException('Invalid JWT Token'); - } - } - - async setAction(action: SetMaintenanceModeDto) { - this.setStatus({ - active: true, - action: action.action, - }); - - await this.runAction(action); - } - - async runAction(action: SetMaintenanceModeDto) { - switch (action.action) { - case MaintenanceAction.Start: { - return; - } - case MaintenanceAction.End: { - return this.endMaintenance(); - } - case MaintenanceAction.SelectDatabaseRestore: { - return; - } - } - - const lock = await this.databaseRepository.tryLock(DatabaseLock.MaintenanceOperation); - if (!lock) { - return; - } - - this.logger.log(`Running maintenance action ${action.action}`); - - await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: true, - secret: this.secret, - action: { - action: MaintenanceAction.Start, - }, - }); - - try { - if (!action.restoreBackupFilename) { - throw new Error("Expected restoreBackupFilename but it's missing!"); - } - - await this.restoreBackup(action.restoreBackupFilename); - } catch (error) { - this.logger.error(`Encountered error running action: ${error}`); - this.setStatus({ - active: true, - action: action.action, - task: 'error', - error: '' + error, - }); - } - } - - private async restoreBackup(filename: string): Promise { - this.setStatus({ - active: true, - action: MaintenanceAction.RestoreDatabase, - task: 'ready', - progress: 0, - }); - - await restoreDatabaseBackup(this.backupRepos, filename, (task, progress) => - this.setStatus({ - active: true, - action: MaintenanceAction.RestoreDatabase, - progress, - task, - }), - ); - - await this.setAction({ - action: MaintenanceAction.End, - }); - } - - private async endMaintenance(): Promise { - const state: MaintenanceModeState = { isMaintenanceMode: false as const }; - await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, state); - - // => corresponds to notification.service.ts#onAppRestart - this.maintenanceWebsocketRepository.clientBroadcast('AppRestartV1', state); - this.maintenanceWebsocketRepository.serverSend('AppRestart', state); - this.appRepository.exitApp(); - } -} diff --git a/server/src/middleware/asset-upload.interceptor.ts b/server/src/middleware/asset-upload.interceptor.ts deleted file mode 100644 index 0f1eaa4ce5..0000000000 --- a/server/src/middleware/asset-upload.interceptor.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; -import { Response } from 'express'; -import { of } from 'rxjs'; -import { AssetMediaResponseDto, AssetMediaStatus } from 'src/dtos/asset-media-response.dto'; -import { ImmichHeader } from 'src/enum'; -import { AuthenticatedRequest } from 'src/middleware/auth.guard'; -import { AssetMediaService } from 'src/services/asset-media.service'; -import { fromMaybeArray } from 'src/utils/request'; - -@Injectable() -export class AssetUploadInterceptor implements NestInterceptor { - constructor(private service: AssetMediaService) {} - - async intercept(context: ExecutionContext, next: CallHandler) { - const req = context.switchToHttp().getRequest(); - const res = context.switchToHttp().getResponse>(); - - const checksum = fromMaybeArray(req.headers[ImmichHeader.Checksum]); - const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum); - if (response) { - res.status(200); - return of({ status: AssetMediaStatus.DUPLICATE, id: response.id }); - } - - return next.handle(); - } -} diff --git a/server/src/middleware/auth.guard.ts b/server/src/middleware/auth.guard.ts index 4964fefbbc..28b18d19ff 100644 --- a/server/src/middleware/auth.guard.ts +++ b/server/src/middleware/auth.guard.ts @@ -7,17 +7,16 @@ import { createParamDecorator, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { ApiBearerAuth, ApiCookieAuth, ApiExtension, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiCookieAuth, ApiExtension, ApiOkResponse, ApiSecurity } from '@nestjs/swagger'; import { Request } from 'express'; import { AuthDto } from 'src/dtos/auth.dto'; -import { ApiCustomExtension, ImmichQuery, MetadataKey, Permission } from 'src/enum'; +import { ApiCustomExtension, MetadataKey, Permission } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService, LoginDetails } from 'src/services/auth.service'; import { getUserAgentDetails } from 'src/utils/request'; type AdminRoute = { admin?: true }; -type SharedLinkRoute = { sharedLink?: true }; -type AuthenticatedOptions = { permission?: Permission | false } & (AdminRoute | SharedLinkRoute); +type AuthenticatedOptions = { permission?: Permission | false } & AdminRoute; export const Authenticated = (options: AuthenticatedOptions = {}): MethodDecorator => { const decorators: MethodDecorator[] = [ @@ -35,13 +34,6 @@ export const Authenticated = (options: AuthenticatedOptions = {}): MethodDecorat decorators.push(ApiExtension(ApiCustomExtension.Permission, options.permission)); } - if ((options as SharedLinkRoute)?.sharedLink) { - decorators.push( - ApiQuery({ name: ImmichQuery.SharedLinkKey, type: String, required: false }), - ApiQuery({ name: ImmichQuery.SharedLinkSlug, type: String, required: false }), - ); - } - return applyDecorators(...decorators); }; @@ -92,17 +84,13 @@ export class AuthGuard implements CanActivate { return true; } - const { - admin: adminRoute, - sharedLink: sharedLinkRoute, - permission, - } = { sharedLink: false, admin: false, ...options }; + const { admin: adminRoute, permission } = { admin: false, ...options }; const request = context.switchToHttp().getRequest(); request.user = await this.authService.authenticate({ headers: request.headers, queryParams: request.query as Record, - metadata: { adminRoute, sharedLinkRoute, permission, uri: request.path }, + metadata: { adminRoute, permission, uri: request.path }, }); return true; diff --git a/server/src/middleware/error.interceptor.ts b/server/src/middleware/error.interceptor.ts index 3c0c09aa54..ab09ffe71d 100644 --- a/server/src/middleware/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -8,7 +8,6 @@ import { } from '@nestjs/common'; import { Observable, catchError, throwError } from 'rxjs'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { logGlobalError } from 'src/utils/logger'; import { routeToErrorMessage } from 'src/utils/misc'; @Injectable() @@ -25,7 +24,7 @@ export class ErrorInterceptor implements NestInterceptor { return error; } - logGlobalError(this.logger, error); + this.logger.error(`Error in handler: ${error}`, error instanceof Error ? error.stack : undefined); const message = routeToErrorMessage(context.getHandler().name); return new InternalServerErrorException(message); diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts deleted file mode 100644 index 6dfd11ee4b..0000000000 --- a/server/src/middleware/file-upload.interceptor.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; -import { PATH_METADATA } from '@nestjs/common/constants'; -import { Reflector } from '@nestjs/core'; -import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils'; -import { NextFunction, RequestHandler } from 'express'; -import multer, { StorageEngine, diskStorage } from 'multer'; -import { createHash, randomUUID } from 'node:crypto'; -import { Observable } from 'rxjs'; -import { UploadFieldName } from 'src/dtos/asset-media.dto'; -import { RouteKey } from 'src/enum'; -import { AuthRequest } from 'src/middleware/auth.guard'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AssetMediaService } from 'src/services/asset-media.service'; -import { ImmichFile, UploadFile, UploadFiles } from 'src/types'; -import { asUploadRequest, mapToUploadFile } from 'src/utils/asset.util'; - -export function getFile(files: UploadFiles, property: 'assetData' | 'sidecarData') { - const file = files[property]?.[0]; - return file ? mapToUploadFile(file) : file; -} - -export function getFiles(files: UploadFiles) { - return { - file: getFile(files, 'assetData') as UploadFile, - sidecarFile: getFile(files, 'sidecarData'), - }; -} - -type DiskStorageCallback = (error: Error | null, result: string) => void; - -type ImmichMulterFile = Express.Multer.File & { uuid: string }; - -interface Callback { - (error: Error): void; - (error: null, result: T): void; -} - -const callbackify = (target: (...arguments_: any[]) => T, callback: Callback) => { - try { - return callback(null, target()); - } catch (error: Error | any) { - return callback(error); - } -}; - -@Injectable() -export class FileUploadInterceptor implements NestInterceptor { - private handlers: { - userProfile: RequestHandler; - assetUpload: RequestHandler; - }; - private defaultStorage: StorageEngine; - - constructor( - private reflect: Reflector, - private assetService: AssetMediaService, - private logger: LoggingRepository, - ) { - this.logger.setContext(FileUploadInterceptor.name); - - this.defaultStorage = diskStorage({ - filename: this.filename.bind(this), - destination: this.destination.bind(this), - }); - - const instance = multer({ - fileFilter: this.fileFilter.bind(this), - storage: { - _handleFile: this.handleFile.bind(this), - _removeFile: this.removeFile.bind(this), - }, - }); - - this.handlers = { - userProfile: instance.single(UploadFieldName.PROFILE_DATA), - assetUpload: instance.fields([ - { name: UploadFieldName.ASSET_DATA, maxCount: 1 }, - { name: UploadFieldName.SIDECAR_DATA, maxCount: 1 }, - ]), - }; - } - - async intercept(context: ExecutionContext, next: CallHandler): Promise> { - const context_ = context.switchToHttp(); - const route = this.reflect.get(PATH_METADATA, context.getClass()); - - const handler: RequestHandler | null = this.getHandler(route as RouteKey); - if (handler) { - await new Promise((resolve, reject) => { - const next: NextFunction = (error) => (error ? reject(transformException(error)) : resolve()); - const maybePromise = handler(context_.getRequest(), context_.getResponse(), next); - Promise.resolve(maybePromise).catch((error) => reject(error)); - }); - } else { - this.logger.warn(`Skipping invalid file upload route: ${route}`); - } - - return next.handle(); - } - - private fileFilter(request: AuthRequest, file: Express.Multer.File, callback: multer.FileFilterCallback) { - return callbackify(() => this.assetService.canUploadFile(asUploadRequest(request, file)), callback); - } - - private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { - return callbackify( - () => this.assetService.getUploadFilename(asUploadRequest(request, file)), - callback as Callback, - ); - } - - private destination(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { - return callbackify( - () => this.assetService.getUploadFolder(asUploadRequest(request, file)), - callback as Callback, - ); - } - - private handleFile(request: AuthRequest, file: Express.Multer.File, callback: Callback>) { - (file as ImmichMulterFile).uuid = randomUUID(); - - request.on('error', (error) => { - this.logger.warn('Request error while uploading file, cleaning up', error); - this.assetService.onUploadError(request, file).catch(this.logger.error); - }); - - if (!this.isAssetUploadFile(file)) { - this.defaultStorage._handleFile(request, file, callback); - return; - } - - const hash = createHash('sha1'); - file.stream.on('data', (chunk) => hash.update(chunk)); - this.defaultStorage._handleFile(request, file, (error, info) => { - if (error) { - hash.destroy(); - callback(error); - } else { - callback(null, { ...info, checksum: hash.digest() }); - } - }); - } - - private removeFile(request: AuthRequest, file: Express.Multer.File, callback: (error: Error | null) => void) { - this.defaultStorage._removeFile(request, file, callback); - } - - private isAssetUploadFile(file: Express.Multer.File) { - switch (file.fieldname as UploadFieldName) { - case UploadFieldName.ASSET_DATA: { - return true; - } - } - - return false; - } - - private getHandler(route: RouteKey) { - switch (route) { - case RouteKey.Asset: { - return this.handlers.assetUpload; - } - - case RouteKey.User: { - return this.handlers.userProfile; - } - - default: { - return null; - } - } - } -} diff --git a/server/src/middleware/global-exception.filter.ts b/server/src/middleware/global-exception.filter.ts index a8afa91cbc..6bae7fda0c 100644 --- a/server/src/middleware/global-exception.filter.ts +++ b/server/src/middleware/global-exception.filter.ts @@ -2,7 +2,6 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/co import { Response } from 'express'; import { ClsService } from 'nestjs-cls'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { logGlobalError } from 'src/utils/logger'; @Catch() export class GlobalExceptionFilter implements ExceptionFilter { @@ -30,7 +29,7 @@ export class GlobalExceptionFilter implements ExceptionFilter { } private fromError(error: Error) { - logGlobalError(this.logger, error); + this.logger.error(`Unhandled exception: ${error}`, error instanceof Error ? error.stack : undefined); if (error instanceof HttpException) { const status = error.getStatus(); diff --git a/server/src/plugins.ts b/server/src/plugins.ts deleted file mode 100644 index 77f35e79f6..0000000000 --- a/server/src/plugins.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PluginContext, PluginTriggerType } from 'src/enum'; - -export type PluginTrigger = { - type: PluginTriggerType; - contextType: PluginContext; -}; - -export const pluginTriggers: PluginTrigger[] = [ - { - type: PluginTriggerType.AssetCreate, - contextType: PluginContext.Asset, - }, - { - type: PluginTriggerType.PersonRecognized, - contextType: PluginContext.Person, - }, -]; diff --git a/server/src/queries/access.repository.sql b/server/src/queries/access.repository.sql deleted file mode 100644 index 1239260dce..0000000000 --- a/server/src/queries/access.repository.sql +++ /dev/null @@ -1,254 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AccessRepository.activity.checkOwnerAccess -select - "activity"."id" -from - "activity" -where - "activity"."id" in ($1) - and "activity"."userId" = $2 - --- AccessRepository.activity.checkAlbumOwnerAccess -select - "activity"."id" -from - "activity" - left join "album" on "activity"."albumId" = "album"."id" - and "album"."deletedAt" is null -where - "activity"."id" in ($1) - and "album"."ownerId" = $2::uuid - --- AccessRepository.activity.checkCreateAccess -select - "album"."id" -from - "album" - left join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id" - left join "user" on "user"."id" = "albumUsers"."userId" - and "user"."deletedAt" is null -where - "album"."id" in ($1) - and "album"."isActivityEnabled" = $2 - and ( - "album"."ownerId" = $3 - or "user"."id" = $4 - ) - and "album"."deletedAt" is null - --- AccessRepository.album.checkOwnerAccess -select - "album"."id" -from - "album" -where - "album"."id" in ($1) - and "album"."ownerId" = $2 - and "album"."deletedAt" is null - --- AccessRepository.album.checkSharedAlbumAccess -select - "album"."id" -from - "album" - left join "album_user" on "album_user"."albumId" = "album"."id" - left join "user" on "user"."id" = "album_user"."userId" - and "user"."deletedAt" is null -where - "album"."id" in ($1) - and "album"."deletedAt" is null - and "user"."id" = $2 - and "album_user"."role" in ($3, $4) - --- AccessRepository.album.checkSharedLinkAccess -select - "shared_link"."albumId" -from - "shared_link" -where - "shared_link"."id" = $1 - and "shared_link"."albumId" in ($2) - --- AccessRepository.asset.checkAlbumAccess -with - "target" as ( - select - array[$1]::uuid[] as "ids" - ) -select - "asset"."id", - "asset"."livePhotoVideoId" -from - "album" - inner join "album_asset" as "albumAssets" on "album"."id" = "albumAssets"."albumId" - inner join "asset" on "asset"."id" = "albumAssets"."assetId" - and "asset"."deletedAt" is null - left join "album_user" as "albumUsers" on "albumUsers"."albumId" = "album"."id" - left join "user" on "user"."id" = "albumUsers"."userId" - and "user"."deletedAt" is null - cross join "target" -where - ( - "asset"."id" = any (target.ids) - or "asset"."livePhotoVideoId" = any (target.ids) - ) - and ( - "album"."ownerId" = $2 - or "user"."id" = $3 - ) - and "album"."deletedAt" is null - --- AccessRepository.asset.checkOwnerAccess -select - "asset"."id" -from - "asset" -where - "asset"."id" in ($1) - and "asset"."ownerId" = $2 - and "asset"."visibility" != $3 - --- AccessRepository.asset.checkPartnerAccess -select - "asset"."id" -from - "partner" - inner join "user" as "sharedBy" on "sharedBy"."id" = "partner"."sharedById" - and "sharedBy"."deletedAt" is null - inner join "asset" on "asset"."ownerId" = "sharedBy"."id" - and "asset"."deletedAt" is null -where - "partner"."sharedWithId" = $1 - and ( - "asset"."visibility" = 'timeline' - or "asset"."visibility" = 'hidden' - ) - and "asset"."id" in ($2) - --- AccessRepository.asset.checkSharedLinkAccess -select - "asset"."id" as "assetId", - "asset"."livePhotoVideoId" as "assetLivePhotoVideoId", - "albumAssets"."id" as "albumAssetId", - "albumAssets"."livePhotoVideoId" as "albumAssetLivePhotoVideoId" -from - "shared_link" - left join "album" on "album"."id" = "shared_link"."albumId" - and "album"."deletedAt" is null - left join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "shared_link"."id" - left join "asset" on "asset"."id" = "shared_link_asset"."assetId" - and "asset"."deletedAt" is null - left join "album_asset" on "album_asset"."albumId" = "album"."id" - left join "asset" as "albumAssets" on "albumAssets"."id" = "album_asset"."assetId" - and "albumAssets"."deletedAt" is null -where - "shared_link"."id" = $1 - and array[ - "asset"."id", - "asset"."livePhotoVideoId", - "albumAssets"."id", - "albumAssets"."livePhotoVideoId" - ] && array[$2]::uuid[] - --- AccessRepository.authDevice.checkOwnerAccess -select - "session"."id" -from - "session" -where - "session"."userId" = $1 - and "session"."id" in ($2) - --- AccessRepository.memory.checkOwnerAccess -select - "memory"."id" -from - "memory" -where - "memory"."id" in ($1) - and "memory"."ownerId" = $2 - and "memory"."deletedAt" is null - --- AccessRepository.notification.checkOwnerAccess -select - "notification"."id" -from - "notification" -where - "notification"."id" in ($1) - and "notification"."userId" = $2 - --- AccessRepository.person.checkOwnerAccess -select - "person"."id" -from - "person" -where - "person"."id" in ($1) - and "person"."ownerId" = $2 - --- AccessRepository.person.checkFaceOwnerAccess -select - "asset_face"."id" -from - "asset_face" - left join "asset" on "asset"."id" = "asset_face"."assetId" - and "asset"."deletedAt" is null -where - "asset_face"."id" in ($1) - and "asset"."ownerId" = $2 - --- AccessRepository.partner.checkUpdateAccess -select - "partner"."sharedById" -from - "partner" -where - "partner"."sharedById" in ($1) - and "partner"."sharedWithId" = $2 - --- AccessRepository.session.checkOwnerAccess -select - "session"."id" -from - "session" -where - "session"."id" in ($1) - and "session"."userId" = $2 - --- AccessRepository.stack.checkOwnerAccess -select - "stack"."id" -from - "stack" -where - "stack"."id" in ($1) - and "stack"."ownerId" = $2 - --- AccessRepository.tag.checkOwnerAccess -select - "tag"."id" -from - "tag" -where - "tag"."id" in ($1) - and "tag"."userId" = $2 - --- AccessRepository.timeline.checkPartnerAccess -select - "partner"."sharedById" -from - "partner" -where - "partner"."sharedById" in ($1) - and "partner"."sharedWithId" = $2 - --- AccessRepository.workflow.checkOwnerAccess -select - "workflow"."id" -from - "workflow" -where - "workflow"."id" in ($1) - and "workflow"."ownerId" = $2 diff --git a/server/src/queries/activity.repository.sql b/server/src/queries/activity.repository.sql deleted file mode 100644 index 228e5cb0ba..0000000000 --- a/server/src/queries/activity.repository.sql +++ /dev/null @@ -1,87 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- ActivityRepository.search -select - "activity".*, - to_json("user") as "user" -from - "activity" - inner join "user" as "user2" on "user2"."id" = "activity"."userId" - and "user2"."deletedAt" is null - inner join lateral ( - select - "user2"."id", - "user2"."name", - "user2"."email", - "user2"."avatarColor", - "user2"."profileImagePath", - "user2"."profileChangedAt" - from - ( - select - 1 - ) as "dummy" - ) as "user" on true - left join "asset" on "asset"."id" = "activity"."assetId" -where - "activity"."albumId" = $1 - and "asset"."deletedAt" is null -order by - "activity"."createdAt" asc - --- ActivityRepository.create -insert into - "activity" ("albumId", "userId") -values - ($1, $2) -returning - *, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "activity"."userId" - ) as obj - ) as "user" - --- ActivityRepository.delete -delete from "activity" -where - "id" = $1::uuid - --- ActivityRepository.getStatistics -select - count(*) filter ( - where - "activity"."isLiked" = $1 - ) as "comments", - count(*) filter ( - where - "activity"."isLiked" = $2 - ) as "likes" -from - "activity" - inner join "user" on "user"."id" = "activity"."userId" - and "user"."deletedAt" is null - left join "asset" on "asset"."id" = "activity"."assetId" -where - "activity"."assetId" = $3 - and "activity"."albumId" = $4 - and ( - ( - "asset"."deletedAt" is null - and "asset"."visibility" != 'locked' - ) - or "asset"."id" is null - ) diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql deleted file mode 100644 index f62e769a17..0000000000 --- a/server/src/queries/album.repository.sql +++ /dev/null @@ -1,436 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AlbumRepository.getById -select - "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "album_user"."role", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album_user"."userId" - ) as obj - ) as "user" - from - "album_user" - where - "album_user"."albumId" = "album"."id" - ) as agg - ) as "albumUsers", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - ) as agg - ) as "sharedLinks", - ( - select - json_agg("asset") as "assets" - from - ( - select - "asset".*, - "asset_exif" as "exifInfo" - from - "asset" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - inner join "album_asset" on "album_asset"."assetId" = "asset"."id" - where - "album_asset"."albumId" = "album"."id" - and "asset"."deletedAt" is null - and "asset"."visibility" in ('archive', 'timeline') - order by - "asset"."fileCreatedAt" desc - ) as "asset" - ) as "assets" -from - "album" -where - "album"."id" = $1 - and "album"."deletedAt" is null - --- AlbumRepository.getByAssetId -select - "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "album_user"."role", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album_user"."userId" - ) as obj - ) as "user" - from - "album_user" - where - "album_user"."albumId" = "album"."id" - ) as agg - ) as "albumUsers" -from - "album" - inner join "album_asset" on "album_asset"."albumId" = "album"."id" -where - ( - "album"."ownerId" = $1 - or exists ( - select - from - "album_user" - where - "album_user"."albumId" = "album"."id" - and "album_user"."userId" = $2 - ) - ) - and "album_asset"."assetId" = $3 - and "album"."deletedAt" is null -order by - "album"."createdAt" desc, - "album"."createdAt" desc - --- AlbumRepository.getMetadataForIds -select - "album_asset"."albumId" as "albumId", - min( - ("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date - ) as "startDate", - max( - ("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date - ) as "endDate", - max("asset"."updatedAt") as "lastModifiedAssetTimestamp", - count("asset"."id")::int as "assetCount" -from - "asset" - inner join "album_asset" on "album_asset"."assetId" = "asset"."id" -where - "asset"."visibility" in ('archive', 'timeline') - and "album_asset"."albumId" in ($1) - and "asset"."deletedAt" is null -group by - "album_asset"."albumId" - --- AlbumRepository.getOwned -select - "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "album_user"."role", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album_user"."userId" - ) as obj - ) as "user" - from - "album_user" - where - "album_user"."albumId" = "album"."id" - ) as agg - ) as "albumUsers", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - ) as agg - ) as "sharedLinks" -from - "album" -where - "album"."ownerId" = $1 - and "album"."deletedAt" is null -order by - "album"."createdAt" desc - --- AlbumRepository.getShared -select - "album".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "album_user"."role", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album_user"."userId" - ) as obj - ) as "user" - from - "album_user" - where - "album_user"."albumId" = "album"."id" - ) as agg - ) as "albumUsers", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - ) as agg - ) as "sharedLinks" -from - "album" -where - ( - exists ( - select - from - "album_user" - where - "album_user"."albumId" = "album"."id" - and ( - "album"."ownerId" = $1 - or "album_user"."userId" = $2 - ) - ) - or exists ( - select - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - and "shared_link"."userId" = $3 - ) - ) - and "album"."deletedAt" is null -order by - "album"."createdAt" desc - --- AlbumRepository.getNotShared -select - "album".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - ) as obj - ) as "owner" -from - "album" -where - "album"."ownerId" = $1 - and "album"."deletedAt" is null - and not exists ( - select - from - "album_user" - where - "album_user"."albumId" = "album"."id" - ) - and not exists ( - select - from - "shared_link" - where - "shared_link"."albumId" = "album"."id" - ) -order by - "album"."createdAt" desc - --- AlbumRepository.removeAssetsFromAll -delete from "album_asset" -where - "album_asset"."assetId" in ($1) - --- AlbumRepository.getAssetIds -select - * -from - "album_asset" -where - "album_asset"."albumId" = $1 - and "album_asset"."assetId" in ($2) - --- AlbumRepository.getContributorCounts -select - "asset"."ownerId" as "userId", - count(*) as "assetCount" -from - "album_asset" - inner join "asset" on "asset"."id" = "assetId" -where - "asset"."deletedAt" is null - and "album_asset"."albumId" = $1 -group by - "asset"."ownerId" -order by - "assetCount" desc - --- AlbumRepository.copyAlbums -insert into - "album_asset" -select - "album_asset"."albumId", - $1 as "assetId" -from - "album_asset" -where - "album_asset"."assetId" = $2 -on conflict do nothing diff --git a/server/src/queries/album.user.repository.sql b/server/src/queries/album.user.repository.sql deleted file mode 100644 index fc4a52bae2..0000000000 --- a/server/src/queries/album.user.repository.sql +++ /dev/null @@ -1,25 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AlbumUserRepository.create -insert into - "album_user" ("userId", "albumId") -values - ($1, $2) -returning - "userId", - "albumId", - "role" - --- AlbumUserRepository.update -update "album_user" -set - "role" = $1 -where - "userId" = $2 - and "albumId" = $3 - --- AlbumUserRepository.delete -delete from "album_user" -where - "userId" = $1 - and "albumId" = $2 diff --git a/server/src/queries/api.key.repository.sql b/server/src/queries/api.key.repository.sql deleted file mode 100644 index 43f3155ab8..0000000000 --- a/server/src/queries/api.key.repository.sql +++ /dev/null @@ -1,58 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- ApiKeyRepository.getKey -select - "api_key"."id", - "api_key"."permissions", - ( - select - to_json(obj) - from - ( - select - "user"."id", - "user"."name", - "user"."email", - "user"."isAdmin", - "user"."quotaUsageInBytes", - "user"."quotaSizeInBytes" - from - "user" - where - "user"."id" = "api_key"."userId" - and "user"."deletedAt" is null - ) as obj - ) as "user" -from - "api_key" -where - "api_key"."key" = $1 - --- ApiKeyRepository.getById -select - "id", - "name", - "userId", - "createdAt", - "updatedAt", - "permissions" -from - "api_key" -where - "id" = $1::uuid - and "userId" = $2 - --- ApiKeyRepository.getByUserId -select - "id", - "name", - "userId", - "createdAt", - "updatedAt", - "permissions" -from - "api_key" -where - "userId" = $1 -order by - "createdAt" desc diff --git a/server/src/queries/asset.edit.repository.sql b/server/src/queries/asset.edit.repository.sql deleted file mode 100644 index 0cf62882db..0000000000 --- a/server/src/queries/asset.edit.repository.sql +++ /dev/null @@ -1,19 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AssetEditRepository.replaceAll -begin -delete from "asset_edit" -where - "assetId" = $1 -rollback - --- AssetEditRepository.getAll -select - "action", - "parameters" -from - "asset_edit" -where - "assetId" = $1 -order by - "sequence" asc diff --git a/server/src/queries/asset.job.repository.sql b/server/src/queries/asset.job.repository.sql deleted file mode 100644 index 63174f0b0f..0000000000 --- a/server/src/queries/asset.job.repository.sql +++ /dev/null @@ -1,669 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AssetJobRepository.getForSearchDuplicatesJob -select - "id", - "type", - "ownerId", - "duplicateId", - "stackId", - "visibility", - "smart_search"."embedding" -from - "asset" - left join "smart_search" on "asset"."id" = "smart_search"."assetId" -where - "asset"."id" = $1::uuid -limit - $2 - --- AssetJobRepository.getForSidecarWriteJob -select - "id", - "originalPath", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as agg - ) as "files", - to_json("asset_exif") as "exifInfo" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."id" = $2::uuid -limit - $3 - --- AssetJobRepository.getForSidecarCheckJob -select - "id", - "originalPath", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as agg - ) as "files" -from - "asset" -where - "asset"."id" = $2::uuid -limit - $3 - --- AssetJobRepository.streamForThumbnailJob -select - "asset"."id", - "asset"."thumbhash", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - ) as agg - ) as "files", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_edit"."action", - "asset_edit"."parameters" - from - "asset_edit" - where - "asset_edit"."assetId" = "asset"."id" - ) as agg - ) as "edits" -from - "asset" - inner join "asset_job_status" on "asset_job_status"."assetId" = "asset"."id" -where - "asset"."deletedAt" is null - and "asset"."visibility" != $1 - and ( - not exists ( - select - from - "asset_file" - where - "assetId" = "asset"."id" - and "asset_file"."type" = $2 - ) - or not exists ( - select - from - "asset_file" - where - "assetId" = "asset"."id" - and "asset_file"."type" = $3 - ) - or not exists ( - select - from - "asset_file" - where - "assetId" = "asset"."id" - and "asset_file"."type" = $4 - ) - or "asset"."thumbhash" is null - ) - --- AssetJobRepository.getForMigrationJob -select - "asset"."id", - "asset"."ownerId", - "asset"."encodedVideoPath", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - ) as agg - ) as "files" -from - "asset" -where - "asset"."id" = $1 - --- AssetJobRepository.getForGenerateThumbnailJob -select - "asset"."id", - "asset"."visibility", - "asset"."originalFileName", - "asset"."originalPath", - "asset"."ownerId", - "asset"."thumbhash", - "asset"."type", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited", - "asset_file"."isProgressive" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" in ($1, $2, $3) - ) as agg - ) as "files", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_edit"."action", - "asset_edit"."parameters" - from - "asset_edit" - where - "asset_edit"."assetId" = "asset"."id" - ) as agg - ) as "edits", - to_json("asset_exif") as "exifInfo" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."id" = $4 - --- AssetJobRepository.getForMetadataExtraction -select - "asset"."id", - "asset"."checksum", - "asset"."deviceAssetId", - "asset"."deviceId", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."isExternal", - "asset"."visibility", - "asset"."libraryId", - "asset"."livePhotoVideoId", - "asset"."localDateTime", - "asset"."originalFileName", - "asset"."originalPath", - "asset"."ownerId", - "asset"."type", - "asset"."width", - "asset"."height", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_face".* - from - "asset_face" - where - "asset_face"."assetId" = "asset"."id" - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" = $1 - ) as agg - ) as "faces", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $2 - ) as agg - ) as "files" -from - "asset" -where - "asset"."id" = $3 - --- AssetJobRepository.getLockedPropertiesForMetadataExtraction -select - "asset_exif"."lockedProperties" -from - "asset_exif" -where - "asset_exif"."assetId" = $1 - --- AssetJobRepository.getAlbumThumbnailFiles -select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" -from - "asset_file" -where - "asset_file"."assetId" = $1 - and "asset_file"."type" = $2 - --- AssetJobRepository.streamForSearchDuplicates -select - "asset"."id" -from - "asset" - inner join "smart_search" on "asset"."id" = "smart_search"."assetId" - inner join "asset_job_status" as "job_status" on "job_status"."assetId" = "asset"."id" -where - "asset"."deletedAt" is null - and "asset"."visibility" in ('archive', 'timeline') - and "job_status"."duplicatesDetectedAt" is null - --- AssetJobRepository.streamForEncodeClip -select - "asset"."id" -from - "asset" - inner join "asset_job_status" as "job_status" on "assetId" = "asset"."id" -where - "asset"."visibility" != $1 - and "asset"."deletedAt" is null - and exists ( - select - from - "asset_file" - where - "assetId" = "asset"."id" - and "asset_file"."type" = $2 - ) - and not exists ( - select - from - "smart_search" - where - "assetId" = "asset"."id" - ) - --- AssetJobRepository.getForClipEncoding -select - "asset"."id", - "asset"."visibility", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as agg - ) as "files" -from - "asset" -where - "asset"."id" = $2 - --- AssetJobRepository.getForDetectFacesJob -select - "asset"."id", - "asset"."visibility", - to_json("asset_exif") as "exifInfo", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_face".* - from - "asset_face" - where - "asset_face"."assetId" = "asset"."id" - ) as agg - ) as "faces", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as agg - ) as "files" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."id" = $2 - --- AssetJobRepository.getForOcr -select - "asset"."visibility", - ( - select - "asset_file"."path" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as "previewFile" -from - "asset" -where - "asset"."id" = $2 - --- AssetJobRepository.getForSyncAssets -select - "asset"."id", - "asset"."isOffline", - "asset"."libraryId", - "asset"."originalPath", - "asset"."status", - "asset"."fileModifiedAt" -from - "asset" -where - "asset"."id" = any ($1::uuid[]) - --- AssetJobRepository.getForAssetDeletion -select - "asset"."id", - "asset"."visibility", - "asset"."libraryId", - "asset"."ownerId", - "asset"."livePhotoVideoId", - "asset"."encodedVideoPath", - "asset"."originalPath", - "asset"."isOffline", - to_json("asset_exif") as "exifInfo", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - ) as agg - ) as "files", - to_json("stack_result") as "stack" -from - "asset" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - left join lateral ( - select - "stack"."id", - "stack"."primaryAssetId", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "stack_asset"."id" - from - "asset" as "stack_asset" - where - "stack_asset"."stackId" = "stack"."id" - and "stack_asset"."id" != "stack"."primaryAssetId" - and "stack_asset"."visibility" = $1 - and "stack_asset"."status" != $2 - ) as agg - ) as "assets" - from - "stack" - where - "stack"."id" = "asset"."stackId" - ) as "stack_result" on true -where - "asset"."id" = $3 - --- AssetJobRepository.streamForVideoConversion -select - "asset"."id" -from - "asset" -where - "asset"."type" = $1 - and ( - "asset"."encodedVideoPath" is null - or "asset"."encodedVideoPath" = $2 - ) - and "asset"."visibility" != $3 - and "asset"."deletedAt" is null - --- AssetJobRepository.getForVideoConversion -select - "asset"."id", - "asset"."ownerId", - "asset"."originalPath", - "asset"."encodedVideoPath" -from - "asset" -where - "asset"."id" = $1 - and "asset"."type" = $2 - --- AssetJobRepository.streamForMetadataExtraction -select - "asset"."id" -from - "asset" - left join "asset_job_status" on "asset_job_status"."assetId" = "asset"."id" -where - ( - "asset_job_status"."metadataExtractedAt" is null - or "asset_job_status"."assetId" is null - ) - and "asset"."deletedAt" is null - --- AssetJobRepository.getForStorageTemplateJob -select - "asset"."id", - "asset"."ownerId", - "asset"."type", - "asset"."checksum", - "asset"."originalPath", - "asset"."isExternal", - "asset"."originalFileName", - "asset"."livePhotoVideoId", - "asset"."fileCreatedAt", - "asset_exif"."timeZone", - "asset_exif"."fileSizeInByte", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as agg - ) as "files" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."deletedAt" is null - and "asset"."id" = $2 - --- AssetJobRepository.streamForStorageTemplateJob -select - "asset"."id", - "asset"."ownerId", - "asset"."type", - "asset"."checksum", - "asset"."originalPath", - "asset"."isExternal", - "asset"."originalFileName", - "asset"."livePhotoVideoId", - "asset"."fileCreatedAt", - "asset_exif"."timeZone", - "asset_exif"."fileSizeInByte", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) as agg - ) as "files" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."deletedAt" is null - --- AssetJobRepository.streamForDeletedJob -select - "id", - "isOffline" -from - "asset" -where - "asset"."deletedAt" <= $1 - --- AssetJobRepository.streamForSidecar -select - "asset"."id" -from - "asset" -where - not exists ( - select - "asset_file"."id" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = $1 - ) - --- AssetJobRepository.streamForDetectFacesJob -select - "asset"."id" -from - "asset" - inner join "asset_job_status" as "job_status" on "assetId" = "asset"."id" -where - "asset"."visibility" != $1 - and "asset"."deletedAt" is null - and exists ( - select - from - "asset_file" - where - "assetId" = "asset"."id" - and "asset_file"."type" = $2 - ) -order by - "asset"."fileCreatedAt" desc - --- AssetJobRepository.streamForOcrJob -select - "asset"."id" -from - "asset" - inner join "asset_job_status" on "asset_job_status"."assetId" = "asset"."id" -where - "asset_job_status"."ocrAt" is null - and "asset"."deletedAt" is null - and "asset"."visibility" != $1 - --- AssetJobRepository.streamForMigrationJob -select - "id" -from - "asset" -where - "asset"."deletedAt" is null diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql deleted file mode 100644 index 0f3a458c35..0000000000 --- a/server/src/queries/asset.repository.sql +++ /dev/null @@ -1,623 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AssetRepository.upsertExif -insert into - "asset_exif" ("dateTimeOriginal", "lockedProperties") -values - ($1, $2) -on conflict ("assetId") do update -set - "dateTimeOriginal" = "excluded"."dateTimeOriginal", - "lockedProperties" = nullif( - array( - select distinct - unnest("asset_exif"."lockedProperties" || $3) - ), - '{}' - ) - --- AssetRepository.updateAllExif -update "asset_exif" -set - "model" = $1, - "lockedProperties" = nullif( - array( - select distinct - unnest("asset_exif"."lockedProperties" || $2) - ), - '{}' - ) -where - "assetId" in ($3) - --- AssetRepository.updateDateTimeOriginal -update "asset_exif" -set - "dateTimeOriginal" = "dateTimeOriginal" + $1::interval, - "timeZone" = $2, - "lockedProperties" = nullif( - array( - select distinct - unnest("asset_exif"."lockedProperties" || $3) - ), - '{}' - ) -where - "assetId" in ($4) -returning - "assetId", - "dateTimeOriginal", - "timeZone" - --- AssetRepository.unlockProperties -update "asset_exif" -set - "lockedProperties" = nullif( - array( - select distinct - property - from - unnest("asset_exif"."lockedProperties") property - where - not property = any ($1) - ), - '{}' - ) -where - "assetId" = $2 - --- AssetRepository.getMetadata -select - "key", - "value", - "updatedAt" -from - "asset_metadata" -where - "assetId" = $1 - --- AssetRepository.getMetadataByKey -select - "key", - "value", - "updatedAt" -from - "asset_metadata" -where - "assetId" = $1 - and "key" = $2 - --- AssetRepository.deleteMetadataByKey -delete from "asset_metadata" -where - "assetId" = $1 - and "key" = $2 - --- AssetRepository.deleteBulkMetadata -begin -delete from "asset_metadata" -where - "assetId" = $1 - and "key" = $2 -commit - --- AssetRepository.getByDayOfYear -with - "res" as ( - with - "today" as ( - select - make_date(year::int, $1::int, $2::int) as "date" - from - generate_series( - ( - select - date_part( - 'year', - min(("localDateTime" at time zone 'UTC')::date) - )::int - from - asset - ), - $3 - ) as "year" - ) - select - "a".*, - to_json("asset_exif") as "exifInfo" - from - "today" - inner join lateral ( - select - "asset".* - from - "asset" - inner join "asset_job_status" on "asset"."id" = "asset_job_status"."assetId" - where - (asset."localDateTime" at time zone 'UTC')::date = today.date - and "asset"."ownerId" = any ($4::uuid[]) - and "asset"."visibility" = $5 - and exists ( - select - from - "asset_file" - where - "assetId" = "asset"."id" - and "asset_file"."type" = $6 - ) - and "asset"."deletedAt" is null - order by - (asset."localDateTime" at time zone 'UTC')::date desc - limit - $7 - ) as "a" on true - inner join "asset_exif" on "a"."id" = "asset_exif"."assetId" - ) -select - date_part( - 'year', - ("localDateTime" at time zone 'UTC')::date - )::int as "year", - json_agg("res") as "assets" -from - "res" -group by - ("localDateTime" at time zone 'UTC')::date -order by - ("localDateTime" at time zone 'UTC')::date desc - --- AssetRepository.getByIds -select - "asset".* -from - "asset" -where - "asset"."id" = any ($1::uuid[]) - --- AssetRepository.getByIdsWithAllRelationsButStacks -select - "asset".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_face".*, - "person" as "person" - from - "asset_face" - left join lateral ( - select - "person".* - from - "person" - where - "asset_face"."personId" = "person"."id" - ) as "person" on true - where - "asset_face"."assetId" = "asset"."id" - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" is true - ) as agg - ) as "faces", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "tag"."id", - "tag"."value", - "tag"."createdAt", - "tag"."updatedAt", - "tag"."color", - "tag"."parentId" - from - "tag" - inner join "tag_asset" on "tag"."id" = "tag_asset"."tagId" - where - "asset"."id" = "tag_asset"."assetId" - ) as agg - ) as "tags", - to_json("asset_exif") as "exifInfo" -from - "asset" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."id" = any ($1::uuid[]) - --- AssetRepository.deleteAll -delete from "asset" -where - "ownerId" = $1 - --- AssetRepository.getByLibraryIdAndOriginalPath -select - "asset".* -from - "asset" -where - "libraryId" = $1::uuid - and "originalPath" = $2 -limit - $3 - --- AssetRepository.getAllByDeviceId -select - "deviceAssetId" -from - "asset" -where - "ownerId" = $1::uuid - and "deviceId" = $2 - and "visibility" != $3 - and "deletedAt" is null - --- AssetRepository.getLivePhotoCount -select - count(*) as "count" -from - "asset" -where - "livePhotoVideoId" = $1::uuid - --- AssetRepository.getFileSamples -select - "assetId", - "path" -from - "asset_file" -limit - 3 - --- AssetRepository.getForCopy -select - "id", - "stackId", - "originalPath", - "isFavorite", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset_file"."id", - "asset_file"."path", - "asset_file"."type", - "asset_file"."isEdited" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - ) as agg - ) as "files" -from - "asset" -where - "id" = $1::uuid -limit - $2 - --- AssetRepository.getById -select - "asset".* -from - "asset" -where - "asset"."id" = $1::uuid -limit - $2 - --- AssetRepository.updateAll -update "asset" -set - "deviceId" = $1 -where - "id" = any ($2::uuid[]) - --- AssetRepository.getByChecksum -select - "asset".* -from - "asset" -where - "ownerId" = $1::uuid - and "checksum" = $2 - and "libraryId" = $3::uuid -limit - $4 - --- AssetRepository.getByChecksums -select - "id", - "checksum", - "deletedAt" -from - "asset" -where - "ownerId" = $1::uuid - and "checksum" in ($2) - --- AssetRepository.getUploadAssetIdByChecksum -select - "id" -from - "asset" -where - "ownerId" = $1::uuid - and "checksum" = $2 - and "libraryId" is null -limit - $3 - --- AssetRepository.getTimeBuckets -with - "asset" as ( - select - date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' as "timeBucket" - from - "asset" - where - "asset"."deletedAt" is null - and "asset"."visibility" in ('archive', 'timeline') - ) -select - ("timeBucket" AT TIME ZONE 'UTC')::date::text as "timeBucket", - count(*) as "count" -from - "asset" -group by - "timeBucket" -order by - "timeBucket" desc - --- AssetRepository.getTimeBucket -with - "cte" as ( - select - "asset"."duration", - "asset"."id", - "asset"."visibility", - asset."isFavorite" - and asset."ownerId" = $1 as "isFavorite", - asset.type = 'IMAGE' as "isImage", - asset."deletedAt" is not null as "isTrashed", - "asset"."livePhotoVideoId", - extract( - epoch - from - ( - asset."localDateTime" AT TIME ZONE 'UTC' - asset."fileCreatedAt" at time zone 'UTC' - ) - )::real / 3600 as "localOffsetHours", - "asset"."ownerId", - "asset"."status", - asset."fileCreatedAt" at time zone 'utc' as "fileCreatedAt", - encode("asset"."thumbhash", 'base64') as "thumbhash", - "asset_exif"."city", - "asset_exif"."country", - "asset_exif"."projectionType", - coalesce( - case - when asset."height" = 0 - or asset."width" = 0 then 1 - else round( - asset."width"::numeric / asset."height"::numeric, - 3 - ) - end, - 1 - ) as "ratio", - "stack" - from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - left join lateral ( - select - array[stacked."stackId"::text, count('stacked')::text] as "stack" - from - "asset" as "stacked" - where - "stacked"."stackId" = "asset"."stackId" - and "stacked"."deletedAt" is null - and "stacked"."visibility" = $2 - group by - "stacked"."stackId" - ) as "stacked_assets" on true - where - "asset"."deletedAt" is null - and "asset"."visibility" in ('archive', 'timeline') - and date_trunc('MONTH', "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC' = $3 - and not exists ( - select - from - "stack" - where - "stack"."id" = "asset"."stackId" - and "stack"."primaryAssetId" != "asset"."id" - ) - order by - "asset"."fileCreatedAt" desc - ), - "agg" as ( - select - coalesce(array_agg("city"), '{}') as "city", - coalesce(array_agg("country"), '{}') as "country", - coalesce(array_agg("duration"), '{}') as "duration", - coalesce(array_agg("id"), '{}') as "id", - coalesce(array_agg("visibility"), '{}') as "visibility", - coalesce(array_agg("isFavorite"), '{}') as "isFavorite", - coalesce(array_agg("isImage"), '{}') as "isImage", - coalesce(array_agg("isTrashed"), '{}') as "isTrashed", - coalesce(array_agg("livePhotoVideoId"), '{}') as "livePhotoVideoId", - coalesce(array_agg("fileCreatedAt"), '{}') as "fileCreatedAt", - coalesce(array_agg("localOffsetHours"), '{}') as "localOffsetHours", - coalesce(array_agg("ownerId"), '{}') as "ownerId", - coalesce(array_agg("projectionType"), '{}') as "projectionType", - coalesce(array_agg("ratio"), '{}') as "ratio", - coalesce(array_agg("status"), '{}') as "status", - coalesce(array_agg("thumbhash"), '{}') as "thumbhash", - coalesce(json_agg("stack"), '[]') as "stack" - from - "cte" - ) -select - to_json(agg)::text as "assets" -from - "agg" - --- AssetRepository.getAssetIdByCity -with - "cities" as ( - select - "city" - from - "asset_exif" - where - "city" is not null - group by - "city" - having - count("assetId") >= $1 - ) -select distinct - on ("asset_exif"."city") "assetId" as "data", - "asset_exif"."city" as "value" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - inner join "cities" on "asset_exif"."city" = "cities"."city" -where - "ownerId" = $2::uuid - and "visibility" = $3 - and "type" = $4 - and "deletedAt" is null -limit - $5 - --- AssetRepository.getAllForUserFullSync -select - "asset".*, - to_json("asset_exif") as "exifInfo", - to_json("stacked_assets") as "stack" -from - "asset" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - left join "stack" on "stack"."id" = "asset"."stackId" - left join lateral ( - select - "stack".*, - count("stacked") as "assetCount" - from - "asset" as "stacked" - where - "stacked"."stackId" = "stack"."id" - group by - "stack"."id" - ) as "stacked_assets" on "stack"."id" is not null -where - "asset"."ownerId" = $1::uuid - and "asset"."visibility" != $2 - and "asset"."updatedAt" <= $3 - and "asset"."id" > $4 -order by - "asset"."id" -limit - $5 - --- AssetRepository.getChangedDeltaSync -select - "asset".*, - to_json("asset_exif") as "exifInfo", - to_json("stacked_assets") as "stack" -from - "asset" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - left join "stack" on "stack"."id" = "asset"."stackId" - left join lateral ( - select - "stack".*, - count("stacked") as "assetCount" - from - "asset" as "stacked" - where - "stacked"."stackId" = "stack"."id" - group by - "stack"."id" - ) as "stacked_assets" on "stack"."id" is not null -where - "asset"."ownerId" = any ($1::uuid[]) - and "asset"."visibility" != $2 - and "asset"."updatedAt" > $3 -limit - $4 - --- AssetRepository.detectOfflineExternalAssets -update "asset" -set - "isOffline" = $1, - "deletedAt" = $2 -where - "isOffline" = $3 - and "isExternal" = $4 - and "libraryId" = $5::uuid - and ( - not "originalPath" like $6 - or "originalPath" like $7 - ) - --- AssetRepository.filterNewExternalAssetPaths -select - "path" -from - unnest(array[$1]::text[]) as "path" -where - not exists ( - select - "originalPath" - from - "asset" - where - "asset"."originalPath" = "path" - and "libraryId" = $2::uuid - and "isExternal" = $3 - ) - --- AssetRepository.getForOriginal -select - "originalFileName", - "asset_file"."path" as "editedPath", - "originalPath" -from - "asset" - left join "asset_file" on "asset"."id" = "asset_file"."assetId" - and "asset_file"."isEdited" = $1 - and "asset_file"."type" = $2 -where - "asset"."id" = $3 - --- AssetRepository.getForThumbnail -select - "asset"."originalPath", - "asset"."originalFileName", - "asset_file"."path" as "path" -from - "asset" - left join "asset_file" on "asset"."id" = "asset_file"."assetId" - and "asset_file"."type" = $1 -where - "asset"."id" = $2 -order by - "asset_file"."isEdited" desc - --- AssetRepository.getForVideo -select - "asset"."encodedVideoPath", - "asset"."originalPath" -from - "asset" -where - "asset"."id" = $1 - and "asset"."type" = $2 diff --git a/server/src/queries/audit.repository.sql b/server/src/queries/audit.repository.sql deleted file mode 100644 index b1a10abf48..0000000000 --- a/server/src/queries/audit.repository.sql +++ /dev/null @@ -1,16 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- AuditRepository.getAfter -select distinct - on ("audit"."entityId", "audit"."entityType") "audit"."entityId" -from - "audit" -where - "audit"."createdAt" > $1 - and "audit"."action" = $2 - and "audit"."entityType" = $3 - and "audit"."ownerId" in ($4) -order by - "audit"."entityId" desc, - "audit"."entityType" desc, - "audit"."createdAt" desc diff --git a/server/src/queries/database.repository.sql b/server/src/queries/database.repository.sql deleted file mode 100644 index be27f1846c..0000000000 --- a/server/src/queries/database.repository.sql +++ /dev/null @@ -1,14 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- DatabaseRepository.getExtensionVersions -SELECT - name, - default_version as "availableVersion", - installed_version as "installedVersion" -FROM - pg_available_extensions -WHERE - name in ($1) - --- DatabaseRepository.getPostgresVersion -SHOW server_version diff --git a/server/src/queries/duplicate.repository.sql b/server/src/queries/duplicate.repository.sql deleted file mode 100644 index 3f718f84c2..0000000000 --- a/server/src/queries/duplicate.repository.sql +++ /dev/null @@ -1,119 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- DuplicateRepository.getAll -with - "duplicates" as ( - select - "asset"."duplicateId", - json_agg( - "asset2" - order by - "asset"."localDateTime" asc - ) as "assets" - from - "asset" - inner join lateral ( - select - "asset".*, - "asset_exif" as "exifInfo" - from - "asset_exif" - where - "asset_exif"."assetId" = "asset"."id" - ) as "asset2" on true - where - "asset"."visibility" in ('archive', 'timeline') - and "asset"."ownerId" = $1::uuid - and "asset"."duplicateId" is not null - and "asset"."deletedAt" is null - and "asset"."stackId" is null - group by - "asset"."duplicateId" - ), - "unique" as ( - select - "duplicateId" - from - "duplicates" - where - json_array_length("assets") = $2 - ), - "removed_unique" as ( - update "asset" - set - "duplicateId" = $3 - from - "unique" - where - "asset"."duplicateId" = "unique"."duplicateId" - ) -select - * -from - "duplicates" -where - not exists ( - select - from - "unique" - where - "unique"."duplicateId" = "duplicates"."duplicateId" - ) - --- DuplicateRepository.delete -update "asset" -set - "duplicateId" = $1 -where - "ownerId" = $2 - and "duplicateId" = $3 - --- DuplicateRepository.deleteAll -update "asset" -set - "duplicateId" = $1 -where - "ownerId" = $2 - and "duplicateId" in ($3) - --- DuplicateRepository.search -begin -set - local vchordrq.probes = 1 -with - "cte" as ( - select - "asset"."id" as "assetId", - "asset"."duplicateId", - smart_search.embedding <=> $1 as "distance" - from - "asset" - inner join "smart_search" on "asset"."id" = "smart_search"."assetId" - where - "asset"."visibility" in ('archive', 'timeline') - and "asset"."ownerId" = any ($2::uuid[]) - and "asset"."deletedAt" is null - and "asset"."type" = $3 - and "asset"."id" != $4::uuid - and "asset"."stackId" is null - order by - "distance" - limit - $5 - ) -select - * -from - "cte" -where - "cte"."distance" <= $6 -commit - --- DuplicateRepository.merge -update "asset" -set -where - ( - "duplicateId" = any ($1::uuid[]) - or "id" = any ($2::uuid[]) - ) diff --git a/server/src/queries/library.repository.sql b/server/src/queries/library.repository.sql deleted file mode 100644 index f0bd05973f..0000000000 --- a/server/src/queries/library.repository.sql +++ /dev/null @@ -1,65 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- LibraryRepository.get -select - "library".* -from - "library" -where - "library"."id" = $1 - and "library"."deletedAt" is null - --- LibraryRepository.getAll -select - "library".* -from - "library" -where - "library"."deletedAt" is null -order by - "createdAt" asc - --- LibraryRepository.getAllDeleted -select - "library".* -from - "library" -where - "library"."deletedAt" is not null -order by - "createdAt" asc - --- LibraryRepository.getStatistics -select - count(*) filter ( - where - ( - "asset"."type" = $1 - and "asset"."visibility" != $2 - ) - ) as "photos", - count(*) filter ( - where - ( - "asset"."type" = $3 - and "asset"."visibility" != $4 - ) - ) as "videos", - coalesce(sum("asset_exif"."fileSizeInByte"), $5) as "usage" -from - "library" - inner join "asset" on "asset"."libraryId" = "library"."id" - left join "asset_exif" on "asset_exif"."assetId" = "asset"."id" -where - "library"."id" = $6 -group by - "library"."id" -select - 0::int as "photos", - 0::int as "videos", - 0::int as "usage", - 0::int as "total" -from - "library" -where - "library"."id" = $1 diff --git a/server/src/queries/map.repository.sql b/server/src/queries/map.repository.sql deleted file mode 100644 index d7e98b1cd2..0000000000 --- a/server/src/queries/map.repository.sql +++ /dev/null @@ -1,31 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- MapRepository.getMapMarkers -select - "id", - "asset_exif"."latitude" as "lat", - "asset_exif"."longitude" as "lon", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - and "asset_exif"."latitude" is not null - and "asset_exif"."longitude" is not null -where - "asset"."visibility" = $1 - and "deletedAt" is null - and ( - "ownerId" in ($2) - or exists ( - select - from - "album_asset" - where - "asset"."id" = "album_asset"."assetId" - and "album_asset"."albumId" in ($3) - ) - ) -order by - "fileCreatedAt" desc diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql deleted file mode 100644 index da970c2c78..0000000000 --- a/server/src/queries/memory.repository.sql +++ /dev/null @@ -1,173 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- MemoryRepository.statistics -select - count(*) as "total" -from - "memory" -where - "deletedAt" is null - and "ownerId" = $1 - --- MemoryRepository.statistics (date filter) -select - count(*) as "total" -from - "memory" -where - ( - "showAt" is null - or "showAt" <= $1 - ) - and ( - "hideAt" is null - or "hideAt" >= $2 - ) - and "deletedAt" is null - and "ownerId" = $3 - --- MemoryRepository.search -select - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset".* - from - "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" - where - "memory_asset"."memoriesId" = "memory"."id" - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null - order by - "asset"."fileCreatedAt" asc - ) as agg - ) as "assets", - "memory".* -from - "memory" -where - "deletedAt" is null - and "ownerId" = $1 -order by - "memoryAt" desc - --- MemoryRepository.search (date filter) -select - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset".* - from - "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" - where - "memory_asset"."memoriesId" = "memory"."id" - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null - order by - "asset"."fileCreatedAt" asc - ) as agg - ) as "assets", - "memory".* -from - "memory" -where - ( - "showAt" is null - or "showAt" <= $1 - ) - and ( - "hideAt" is null - or "hideAt" >= $2 - ) - and "deletedAt" is null - and "ownerId" = $3 -order by - "memoryAt" desc - --- MemoryRepository.get -select - "memory".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset".* - from - "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" - where - "memory_asset"."memoriesId" = "memory"."id" - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null - order by - "asset"."fileCreatedAt" asc - ) as agg - ) as "assets" -from - "memory" -where - "id" = $1 - and "deletedAt" is null - --- MemoryRepository.update -update "memory" -set - "ownerId" = $1, - "isSaved" = $2 -where - "id" = $3 -select - "memory".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset".* - from - "asset" - inner join "memory_asset" on "asset"."id" = "memory_asset"."assetId" - where - "memory_asset"."memoriesId" = "memory"."id" - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null - order by - "asset"."fileCreatedAt" asc - ) as agg - ) as "assets" -from - "memory" -where - "id" = $1 - and "deletedAt" is null - --- MemoryRepository.delete -delete from "memory" -where - "id" = $1 - --- MemoryRepository.getAssetIds -select - "assetId" -from - "memory_asset" -where - "memoriesId" = $1 - and "assetId" in ($2) - --- MemoryRepository.addAssetIds -insert into - "memory_asset" ("memoriesId", "assetId") -values - ($1, $2) diff --git a/server/src/queries/move.repository.sql b/server/src/queries/move.repository.sql deleted file mode 100644 index 50c9ad7dd9..0000000000 --- a/server/src/queries/move.repository.sql +++ /dev/null @@ -1,23 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- MoveRepository.getByEntity -select - * -from - "move_history" -where - "entityId" = $1 - and "pathType" = $2 - --- MoveRepository.delete -delete from "move_history" -where - "id" = $1 -returning - * - --- MoveRepository.cleanMoveHistorySingle -delete from "move_history" -where - "move_history"."pathType" = 'original' - and "entityId" = $1 diff --git a/server/src/queries/notification.repository.sql b/server/src/queries/notification.repository.sql deleted file mode 100644 index cc704af2be..0000000000 --- a/server/src/queries/notification.repository.sql +++ /dev/null @@ -1,40 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- NotificationRepository.search -select - "id", - "createdAt", - "level", - "type", - "title", - "description", - "data", - "readAt" -from - "notification" -where - "userId" = $1 - and "deletedAt" is null -order by - "createdAt" desc - --- NotificationRepository.search (unread) -select - "id", - "createdAt", - "level", - "type", - "title", - "description", - "data", - "readAt" -from - "notification" -where - ( - "userId" = $1 - and "readAt" is null - ) - and "deletedAt" is null -order by - "createdAt" desc diff --git a/server/src/queries/ocr.repository.sql b/server/src/queries/ocr.repository.sql deleted file mode 100644 index fc8991dea0..0000000000 --- a/server/src/queries/ocr.repository.sql +++ /dev/null @@ -1,78 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- OcrRepository.getById -select - "asset_ocr".* -from - "asset_ocr" -where - "asset_ocr"."id" = $1 - --- OcrRepository.getByAssetId -select - "asset_ocr".* -from - "asset_ocr" -where - "asset_ocr"."assetId" = $1 - and "asset_ocr"."isVisible" = $2 - --- OcrRepository.upsert -with - "deleted_ocr" as ( - delete from "asset_ocr" - where - "assetId" = $1 - ), - "inserted_ocr" as ( - insert into - "asset_ocr" ( - "assetId", - "x1", - "y1", - "x2", - "y2", - "x3", - "y3", - "x4", - "y4", - "text", - "boxScore", - "textScore" - ) - values - ( - $2, - $3, - $4, - $5, - $6, - $7, - $8, - $9, - $10, - $11, - $12, - $13 - ) - ), - "inserted_search" as ( - insert into - "ocr_search" ("assetId", "text") - values - ($14, $15) - on conflict ("assetId") do update - set - "text" = "excluded"."text" - ) -select - 1 as "dummy" - --- OcrRepository.updateOcrVisibilities -begin -update "ocr_search" -set - "text" = $1 -where - "assetId" = $2 -commit diff --git a/server/src/queries/partner.repository.sql b/server/src/queries/partner.repository.sql deleted file mode 100644 index 213aa27d93..0000000000 --- a/server/src/queries/partner.repository.sql +++ /dev/null @@ -1,153 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- PartnerRepository.getAll -select - "partner".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" as "sharedBy" - where - "sharedBy"."id" = "partner"."sharedById" - ) as obj - ) as "sharedBy", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" as "sharedWith" - where - "sharedWith"."id" = "partner"."sharedWithId" - ) as obj - ) as "sharedWith" -from - "partner" - inner join "user" as "sharedBy" on "partner"."sharedById" = "sharedBy"."id" - and "sharedBy"."deletedAt" is null - inner join "user" as "sharedWith" on "partner"."sharedWithId" = "sharedWith"."id" - and "sharedWith"."deletedAt" is null -where - ( - "sharedWithId" = $1 - or "sharedById" = $2 - ) - --- PartnerRepository.get -select - "partner".*, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" as "sharedBy" - where - "sharedBy"."id" = "partner"."sharedById" - ) as obj - ) as "sharedBy", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" as "sharedWith" - where - "sharedWith"."id" = "partner"."sharedWithId" - ) as obj - ) as "sharedWith" -from - "partner" - inner join "user" as "sharedBy" on "partner"."sharedById" = "sharedBy"."id" - and "sharedBy"."deletedAt" is null - inner join "user" as "sharedWith" on "partner"."sharedWithId" = "sharedWith"."id" - and "sharedWith"."deletedAt" is null -where - "sharedWithId" = $1 - and "sharedById" = $2 - --- PartnerRepository.update -update "partner" -set - "inTimeline" = $1 -where - "sharedWithId" = $2 - and "sharedById" = $3 -returning - *, - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" as "sharedBy" - where - "sharedBy"."id" = "partner"."sharedById" - ) as obj - ) as "sharedBy", - ( - select - to_json(obj) - from - ( - select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt" - from - "user" as "sharedWith" - where - "sharedWith"."id" = "partner"."sharedWithId" - ) as obj - ) as "sharedWith" - --- PartnerRepository.remove -delete from "partner" -where - "sharedWithId" = $1 - and "sharedById" = $2 diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql deleted file mode 100644 index 356f5af8f6..0000000000 --- a/server/src/queries/person.repository.sql +++ /dev/null @@ -1,356 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- PersonRepository.reassignFaces -update "asset_face" -set - "personId" = $1 -where - "asset_face"."personId" = $2 - --- PersonRepository.delete -delete from "person" -where - "person"."id" in ($1) - --- PersonRepository.getFileSamples -select - "id", - "thumbnailPath" -from - "person" -where - "thumbnailPath" != '' -limit - 3 - --- PersonRepository.getAllForUser -select - "person".* -from - "person" - inner join "asset_face" on "asset_face"."personId" = "person"."id" - inner join "asset" on "asset_face"."assetId" = "asset"."id" - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null -where - "person"."ownerId" = $1 - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" is true - and "person"."isHidden" = $2 -group by - "person"."id" -having - ( - "person"."name" != $3 - or count("asset_face"."assetId") >= $4 - ) -order by - "person"."isHidden" asc, - "person"."isFavorite" desc, - NULLIF(person.name, '') is null asc, - count("asset_face"."assetId") desc, - NULLIF(person.name, '') asc nulls last, - "person"."createdAt" -limit - $5 -offset - $6 - --- PersonRepository.getAllWithoutFaces -select - "person".* -from - "person" - left join "asset_face" on "asset_face"."personId" = "person"."id" -where - "asset_face"."deletedAt" is null - and "asset_face"."isVisible" is true -group by - "person"."id" -having - count("asset_face"."assetId") = $1 - --- PersonRepository.getFaces -select - "asset_face".*, - ( - select - to_json(obj) - from - ( - select - "person".* - from - "person" - where - "person"."id" = "asset_face"."personId" - ) as obj - ) as "person" -from - "asset_face" -where - "asset_face"."assetId" = $1 - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" = $2 -order by - "asset_face"."boundingBoxX1" asc - --- PersonRepository.getFaceById -select - "asset_face".*, - ( - select - to_json(obj) - from - ( - select - "person".* - from - "person" - where - "person"."id" = "asset_face"."personId" - ) as obj - ) as "person" -from - "asset_face" -where - "asset_face"."id" = $1 - and "asset_face"."deletedAt" is null - --- PersonRepository.getFaceForFacialRecognitionJob -select - "asset_face"."id", - "asset_face"."personId", - "asset_face"."sourceType", - ( - select - to_json(obj) - from - ( - select - "asset"."ownerId", - "asset"."visibility", - "asset"."fileCreatedAt" - from - "asset" - where - "asset"."id" = "asset_face"."assetId" - ) as obj - ) as "asset", - ( - select - to_json(obj) - from - ( - select - "face_search".* - from - "face_search" - where - "face_search"."faceId" = "asset_face"."id" - ) as obj - ) as "faceSearch" -from - "asset_face" -where - "asset_face"."id" = $1 - and "asset_face"."deletedAt" is null - --- PersonRepository.getDataForThumbnailGenerationJob -select - "person"."ownerId", - "asset_face"."boundingBoxX1" as "x1", - "asset_face"."boundingBoxY1" as "y1", - "asset_face"."boundingBoxX2" as "x2", - "asset_face"."boundingBoxY2" as "y2", - "asset_face"."imageWidth" as "oldWidth", - "asset_face"."imageHeight" as "oldHeight", - "asset"."type", - "asset"."originalPath", - "asset_exif"."orientation" as "exifOrientation", - ( - select - "asset_file"."path" - from - "asset_file" - where - "asset_file"."assetId" = "asset"."id" - and "asset_file"."type" = 'preview' - ) as "previewPath" -from - "person" - inner join "asset_face" on "asset_face"."id" = "person"."faceAssetId" - inner join "asset" on "asset_face"."assetId" = "asset"."id" - left join "asset_exif" on "asset_exif"."assetId" = "asset"."id" -where - "person"."id" = $1 - and "asset_face"."deletedAt" is null - --- PersonRepository.reassignFace -update "asset_face" -set - "personId" = $1 -where - "asset_face"."id" = $2 - --- PersonRepository.getByName -select - "person".* -from - "person" -where - ( - "person"."ownerId" = $1 - and ( - lower("person"."name") like $2 - or lower("person"."name") like $3 - ) - ) -limit - $4 - --- PersonRepository.getDistinctNames -select distinct - on (lower("person"."name")) "person"."id", - "person"."name" -from - "person" -where - ( - "person"."ownerId" = $1 - and "person"."name" != $2 - ) - --- PersonRepository.getStatistics -select - count(distinct ("asset"."id")) as "count" -from - "asset_face" - left join "asset" on "asset"."id" = "asset_face"."assetId" - and "asset_face"."personId" = $1 - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null -where - "asset_face"."deletedAt" is null - and "asset_face"."isVisible" is true - --- PersonRepository.getNumberOfPeople -select - coalesce(count(*), 0) as "total", - coalesce( - count(*) filter ( - where - "isHidden" = $1 - ), - 0 - ) as "hidden" -from - "person" -where - exists ( - select - from - "asset_face" - where - "asset_face"."personId" = "person"."id" - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" = $2 - and exists ( - select - from - "asset" - where - "asset"."id" = "asset_face"."assetId" - and "asset"."visibility" = 'timeline' - and "asset"."deletedAt" is null - ) - ) - and "person"."ownerId" = $3 - --- PersonRepository.refreshFaces -with - "added_embeddings" as ( - insert into - "face_search" ("faceId", "embedding") - values - ($1, $2) - ) -select -from - ( - select - 1 - ) as "dummy" - --- PersonRepository.getFacesByIds -select - "asset_face".*, - ( - select - to_json(obj) - from - ( - select - "asset".* - from - "asset" - where - "asset"."id" = "asset_face"."assetId" - ) as obj - ) as "asset", - ( - select - to_json(obj) - from - ( - select - "person".* - from - "person" - where - "person"."id" = "asset_face"."personId" - ) as obj - ) as "person" -from - "asset_face" -where - "asset_face"."assetId" in ($1) - and "asset_face"."personId" in ($2) - and "asset_face"."deletedAt" is null - --- PersonRepository.getRandomFace -select - "asset_face".* -from - "asset_face" -where - "asset_face"."personId" = $1 - and "asset_face"."deletedAt" is null - and "asset_face"."isVisible" is true - --- PersonRepository.getLatestFaceDate -select - max("asset_job_status"."facesRecognizedAt")::text as "latestDate" -from - "asset_job_status" - --- PersonRepository.deleteAssetFace -delete from "asset_face" -where - "asset_face"."id" = $1 - --- PersonRepository.softDeleteAssetFaces -update "asset_face" -set - "deletedAt" = $1 -where - "asset_face"."id" = $2 - --- PersonRepository.getForPeopleDelete -select - "id", - "thumbnailPath" -from - "person" -where - "id" in ($1) diff --git a/server/src/queries/plugin.repository.sql b/server/src/queries/plugin.repository.sql deleted file mode 100644 index 82c203dafd..0000000000 --- a/server/src/queries/plugin.repository.sql +++ /dev/null @@ -1,159 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- PluginRepository.getPlugin -select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_filter" - where - "plugin_filter"."pluginId" = "plugin"."id" - ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" -from - "plugin" -where - "plugin"."id" = $1 - --- PluginRepository.getPluginByName -select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_filter" - where - "plugin_filter"."pluginId" = "plugin"."id" - ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" -from - "plugin" -where - "plugin"."name" = $1 - --- PluginRepository.getAllPlugins -select - "plugin"."id" as "id", - "plugin"."name" as "name", - "plugin"."title" as "title", - "plugin"."description" as "description", - "plugin"."author" as "author", - "plugin"."version" as "version", - "plugin"."wasmPath" as "wasmPath", - "plugin"."createdAt" as "createdAt", - "plugin"."updatedAt" as "updatedAt", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_filter" - where - "plugin_filter"."pluginId" = "plugin"."id" - ) as agg - ) as "filters", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - * - from - "plugin_action" - where - "plugin_action"."pluginId" = "plugin"."id" - ) as agg - ) as "actions" -from - "plugin" -order by - "plugin"."name" - --- PluginRepository.getFilter -select - * -from - "plugin_filter" -where - "id" = $1 - --- PluginRepository.getFiltersByPlugin -select - * -from - "plugin_filter" -where - "pluginId" = $1 - --- PluginRepository.getAction -select - * -from - "plugin_action" -where - "id" = $1 - --- PluginRepository.getActionsByPlugin -select - * -from - "plugin_action" -where - "pluginId" = $1 diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql deleted file mode 100644 index ef5fbe09be..0000000000 --- a/server/src/queries/search.repository.sql +++ /dev/null @@ -1,304 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SearchRepository.searchMetadata -select - "asset".* -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."visibility" = $1 - and "asset"."fileCreatedAt" >= $2 - and "asset_exif"."lensModel" = $3 - and "asset"."ownerId" = any ($4::uuid[]) - and "asset"."isFavorite" = $5 - and "asset"."deletedAt" is null -order by - "asset"."fileCreatedAt" desc -limit - $6 -offset - $7 - --- SearchRepository.searchStatistics -select - count(*) as "total" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."visibility" = $1 - and "asset"."fileCreatedAt" >= $2 - and "asset_exif"."lensModel" = $3 - and "asset"."ownerId" = any ($4::uuid[]) - and "asset"."isFavorite" = $5 - and "asset"."deletedAt" is null - --- SearchRepository.searchRandom -( - select - "asset".* - from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - where - "asset"."visibility" = $1 - and "asset"."fileCreatedAt" >= $2 - and "asset_exif"."lensModel" = $3 - and "asset"."ownerId" = any ($4::uuid[]) - and "asset"."isFavorite" = $5 - and "asset"."deletedAt" is null - and "asset"."id" < $6 - order by - random() - limit - $7 -) -union all -( - select - "asset".* - from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - where - "asset"."visibility" = $8 - and "asset"."fileCreatedAt" >= $9 - and "asset_exif"."lensModel" = $10 - and "asset"."ownerId" = any ($11::uuid[]) - and "asset"."isFavorite" = $12 - and "asset"."deletedAt" is null - and "asset"."id" > $13 - order by - random() - limit - $14 -) -limit - $15 - --- SearchRepository.searchLargeAssets -select - "asset".*, - to_json("asset_exif") as "exifInfo" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "asset"."visibility" = $1 - and "asset"."fileCreatedAt" >= $2 - and "asset_exif"."lensModel" = $3 - and "asset"."ownerId" = any ($4::uuid[]) - and "asset"."isFavorite" = $5 - and "asset"."deletedAt" is null - and "asset_exif"."fileSizeInByte" > $6 -order by - "asset_exif"."fileSizeInByte" desc -limit - $7 - --- SearchRepository.searchSmart -begin -set - local vchordrq.probes = 1 -select - "asset".* -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - inner join "smart_search" on "asset"."id" = "smart_search"."assetId" -where - "asset"."visibility" = $1 - and "asset"."fileCreatedAt" >= $2 - and "asset_exif"."lensModel" = $3 - and "asset"."ownerId" = any ($4::uuid[]) - and "asset"."isFavorite" = $5 - and "asset"."deletedAt" is null -order by - smart_search.embedding <=> $6 -limit - $7 -offset - $8 -commit - --- SearchRepository.getEmbedding -select - * -from - "smart_search" -where - "assetId" = $1 - --- SearchRepository.searchFaces -begin -set - local vchordrq.probes = 1 -with - "cte" as ( - select - "asset_face"."id", - "asset_face"."personId", - face_search.embedding <=> $1 as "distance" - from - "asset_face" - inner join "asset" on "asset"."id" = "asset_face"."assetId" - inner join "face_search" on "face_search"."faceId" = "asset_face"."id" - left join "person" on "person"."id" = "asset_face"."personId" - where - "asset"."ownerId" = any ($2::uuid[]) - and "asset"."deletedAt" is null - order by - "distance" - limit - $3 - ) -select - * -from - "cte" -where - "cte"."distance" <= $4 -commit - --- SearchRepository.searchPlaces -select - * -from - "geodata_places" -where - f_unaccent (name) %>> f_unaccent ($1) - or f_unaccent ("admin2Name") %>> f_unaccent ($2) - or f_unaccent ("admin1Name") %>> f_unaccent ($3) - or f_unaccent ("alternateNames") %>> f_unaccent ($4) -order by - coalesce(f_unaccent (name) <->>> f_unaccent ($5), 0.1) + coalesce( - f_unaccent ("admin2Name") <->>> f_unaccent ($6), - 0.1 - ) + coalesce( - f_unaccent ("admin1Name") <->>> f_unaccent ($7), - 0.1 - ) + coalesce( - f_unaccent ("alternateNames") <->>> f_unaccent ($8), - 0.1 - ) -limit - $9 - --- SearchRepository.getAssetsByCity -with recursive - "cte" as ( - ( - select - "city", - "assetId" - from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" - where - "asset"."ownerId" = any ($1::uuid[]) - and "asset"."visibility" = $2 - and "asset"."type" = $3 - and "asset"."deletedAt" is null - order by - "city" - limit - $4 - ) - union all - ( - select - "l"."city", - "l"."assetId" - from - "cte" - inner join lateral ( - select - "city", - "assetId" - from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" - where - "asset"."ownerId" = any ($5::uuid[]) - and "asset"."visibility" = $6 - and "asset"."type" = $7 - and "asset"."deletedAt" is null - and "asset_exif"."city" > "cte"."city" - order by - "city" - limit - $8 - ) as "l" on true - ) - ) -select - "asset".*, - to_jsonb("asset_exif") as "exifInfo" -from - "asset" - inner join "asset_exif" on "asset"."id" = "asset_exif"."assetId" - inner join "cte" on "asset"."id" = "cte"."assetId" -order by - "asset_exif"."city" - --- SearchRepository.getStates -select distinct - on ("state") "state" -from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" -where - "ownerId" = any ($1::uuid[]) - and "visibility" = $2 - and "deletedAt" is null - and "state" is not null - --- SearchRepository.getCities -select distinct - on ("city") "city" -from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" -where - "ownerId" = any ($1::uuid[]) - and "visibility" = $2 - and "deletedAt" is null - and "city" is not null - --- SearchRepository.getCameraMakes -select distinct - on ("make") "make" -from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" -where - "ownerId" = any ($1::uuid[]) - and "visibility" = $2 - and "deletedAt" is null - and "make" is not null - --- SearchRepository.getCameraModels -select distinct - on ("model") "model" -from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" -where - "ownerId" = any ($1::uuid[]) - and "visibility" = $2 - and "deletedAt" is null - and "model" is not null - --- SearchRepository.getCameraLensModels -select distinct - on ("lensModel") "lensModel" -from - "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" -where - "ownerId" = any ($1::uuid[]) - and "visibility" = $2 - and "deletedAt" is null - and "lensModel" is not null diff --git a/server/src/queries/session.repository.sql b/server/src/queries/session.repository.sql deleted file mode 100644 index b399646409..0000000000 --- a/server/src/queries/session.repository.sql +++ /dev/null @@ -1,100 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SessionRepository.get -select - "id", - "expiresAt", - "pinExpiresAt" -from - "session" -where - "id" = $1 - --- SessionRepository.isPendingSyncReset -select - "isPendingSyncReset" -from - "session" -where - "id" = $1 - --- SessionRepository.getByToken -select - "session"."id", - "session"."updatedAt", - "session"."pinExpiresAt", - "session"."appVersion", - ( - select - to_json(obj) - from - ( - select - "user"."id", - "user"."name", - "user"."email", - "user"."isAdmin", - "user"."quotaUsageInBytes", - "user"."quotaSizeInBytes" - from - "user" - where - "user"."id" = "session"."userId" - and "user"."deletedAt" is null - ) as obj - ) as "user" -from - "session" -where - "session"."token" = $1 - and ( - "session"."expiresAt" is null - or "session"."expiresAt" > $2 - ) - --- SessionRepository.getByUserId -select - "session".* -from - "session" - inner join "user" on "user"."id" = "session"."userId" - and "user"."deletedAt" is null -where - "session"."userId" = $1 - and ( - "session"."expiresAt" is null - or "session"."expiresAt" > $2 - ) -order by - "session"."updatedAt" desc, - "session"."createdAt" desc - --- SessionRepository.delete -delete from "session" -where - "id" = $1::uuid - --- SessionRepository.invalidate -delete from "session" -where - "userId" = $1 - and "id" != $2 - --- SessionRepository.lockAll -update "session" -set - "pinExpiresAt" = $1 -where - "userId" = $2 - --- SessionRepository.resetSyncProgress -begin -update "session" -set - "isPendingSyncReset" = $1 -where - "id" = $2 -delete from "session_sync_checkpoint" -where - "sessionId" = $1 -commit diff --git a/server/src/queries/shared.link.asset.repository.sql b/server/src/queries/shared.link.asset.repository.sql deleted file mode 100644 index 7f9ebc03d1..0000000000 --- a/server/src/queries/shared.link.asset.repository.sql +++ /dev/null @@ -1,13 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SharedLinkAssetRepository.copySharedLinks -insert into - "shared_link_asset" -select - $1 as "assetId", - "shared_link_asset"."sharedLinkId" -from - "shared_link_asset" -where - "shared_link_asset"."assetId" = $2 -on conflict do nothing diff --git a/server/src/queries/shared.link.repository.sql b/server/src/queries/shared.link.repository.sql deleted file mode 100644 index 8540da91c8..0000000000 --- a/server/src/queries/shared.link.repository.sql +++ /dev/null @@ -1,238 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SharedLinkRepository.get -select - "shared_link".*, - coalesce( - json_agg("a") filter ( - where - "a"."id" is not null - ), - '[]' - ) as "assets", - to_json("album") as "album" -from - "shared_link" - left join lateral ( - select - "asset".*, - to_json("exifInfo") as "exifInfo" - from - "shared_link_asset" - inner join "asset" on "asset"."id" = "shared_link_asset"."assetId" - inner join lateral ( - select - "asset_exif".* - from - "asset_exif" - where - "asset_exif"."assetId" = "asset"."id" - ) as "exifInfo" on true - where - "shared_link"."id" = "shared_link_asset"."sharedLinkId" - and "asset"."deletedAt" is null - order by - "asset"."fileCreatedAt" asc - ) as "a" on true - left join lateral ( - select - "album".*, - coalesce( - json_agg( - "assets" - order by - "assets"."fileCreatedAt" asc - ) filter ( - where - "assets"."id" is not null - ), - '[]' - ) as "assets", - to_json("owner") as "owner" - from - "album" - left join "album_asset" on "album_asset"."albumId" = "album"."id" - left join lateral ( - select - "asset".*, - to_json("exifInfo") as "exifInfo" - from - "asset" - inner join lateral ( - select - "asset_exif".* - from - "asset_exif" - where - "asset_exif"."assetId" = "asset"."id" - ) as "exifInfo" on true - where - "album_asset"."assetId" = "asset"."id" - and "asset"."deletedAt" is null - order by - "asset"."fileCreatedAt" asc - ) as "assets" on true - inner join lateral ( - select - "user".* - from - "user" - where - "user"."id" = "album"."ownerId" - and "user"."deletedAt" is null - ) as "owner" on true - where - "album"."id" = "shared_link"."albumId" - and "album"."deletedAt" is null - group by - "album"."id", - "owner".* - ) as "album" on true -where - "shared_link"."id" = $1 - and "shared_link"."userId" = $2 - and ( - "shared_link"."type" = $3 - or "album"."id" is not null - ) -group by - "shared_link"."id", - "album".* -order by - "shared_link"."createdAt" desc - --- SharedLinkRepository.getAll -select distinct - on ("shared_link"."createdAt") "shared_link".*, - "assets"."assets", - to_json("album") as "album" -from - "shared_link" - left join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "shared_link"."id" - left join lateral ( - select - json_agg("asset") as "assets" - from - "asset" - where - "asset"."id" = "shared_link_asset"."assetId" - and "asset"."deletedAt" is null - ) as "assets" on true - left join lateral ( - select - "album".*, - to_json("owner") as "owner" - from - "album" - inner join lateral ( - select - "user"."id", - "user"."email", - "user"."createdAt", - "user"."profileImagePath", - "user"."isAdmin", - "user"."shouldChangePassword", - "user"."deletedAt", - "user"."oauthId", - "user"."updatedAt", - "user"."storageLabel", - "user"."name", - "user"."quotaSizeInBytes", - "user"."quotaUsageInBytes", - "user"."status", - "user"."profileChangedAt" - from - "user" - where - "user"."id" = "album"."ownerId" - and "user"."deletedAt" is null - ) as "owner" on true - where - "album"."id" = "shared_link"."albumId" - and "album"."deletedAt" is null - ) as "album" on true -where - "shared_link"."userId" = $1 - and ( - "shared_link"."type" = $2 - or "album"."id" is not null - ) - and "shared_link"."albumId" = $3 -order by - "shared_link"."createdAt" desc - --- SharedLinkRepository.getByKey -select - "shared_link"."id", - "shared_link"."userId", - "shared_link"."expiresAt", - "shared_link"."showExif", - "shared_link"."allowUpload", - "shared_link"."allowDownload", - "shared_link"."password", - ( - select - to_json(obj) - from - ( - select - "user"."id", - "user"."name", - "user"."email", - "user"."isAdmin", - "user"."quotaUsageInBytes", - "user"."quotaSizeInBytes" - from - "user" - where - "user"."id" = "shared_link"."userId" - ) as obj - ) as "user" -from - "shared_link" - left join "album" on "album"."id" = "shared_link"."albumId" -where - "album"."deletedAt" is null - and ( - "shared_link"."type" = $1 - or "album"."id" is not null - ) - and "shared_link"."key" = $2 - --- SharedLinkRepository.getBySlug -select - "shared_link"."id", - "shared_link"."userId", - "shared_link"."expiresAt", - "shared_link"."showExif", - "shared_link"."allowUpload", - "shared_link"."allowDownload", - "shared_link"."password", - ( - select - to_json(obj) - from - ( - select - "user"."id", - "user"."name", - "user"."email", - "user"."isAdmin", - "user"."quotaUsageInBytes", - "user"."quotaSizeInBytes" - from - "user" - where - "user"."id" = "shared_link"."userId" - ) as obj - ) as "user" -from - "shared_link" - left join "album" on "album"."id" = "shared_link"."albumId" -where - "album"."deletedAt" is null - and ( - "shared_link"."type" = $1 - or "album"."id" is not null - ) - and "shared_link"."slug" = $2 diff --git a/server/src/queries/stack.repository.sql b/server/src/queries/stack.repository.sql deleted file mode 100644 index b5f1dc7d18..0000000000 --- a/server/src/queries/stack.repository.sql +++ /dev/null @@ -1,164 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- StackRepository.search -select - "stack".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset".*, - to_json("exifInfo") as "exifInfo" - from - "asset" - inner join lateral ( - select - "asset_exif"."assetId", - "asset_exif"."autoStackId", - "asset_exif"."bitsPerSample", - "asset_exif"."city", - "asset_exif"."colorspace", - "asset_exif"."country", - "asset_exif"."dateTimeOriginal", - "asset_exif"."description", - "asset_exif"."exifImageHeight", - "asset_exif"."exifImageWidth", - "asset_exif"."exposureTime", - "asset_exif"."fileSizeInByte", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."fps", - "asset_exif"."iso", - "asset_exif"."latitude", - "asset_exif"."lensModel", - "asset_exif"."livePhotoCID", - "asset_exif"."longitude", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."modifyDate", - "asset_exif"."orientation", - "asset_exif"."profileDescription", - "asset_exif"."projectionType", - "asset_exif"."rating", - "asset_exif"."state", - "asset_exif"."tags", - "asset_exif"."timeZone" - from - "asset_exif" - where - "asset_exif"."assetId" = "asset"."id" - ) as "exifInfo" on true - where - "asset"."deletedAt" is null - and "asset"."stackId" = "stack"."id" - and "asset"."visibility" in ('archive', 'timeline') - ) as agg - ) as "assets" -from - "stack" -where - "stack"."ownerId" = $1 - --- StackRepository.delete -delete from "stack" -where - "id" = $1::uuid - --- StackRepository.getById -select - *, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "asset".*, - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "tag"."id", - "tag"."value", - "tag"."createdAt", - "tag"."updatedAt", - "tag"."color", - "tag"."parentId" - from - "tag" - inner join "tag_asset" on "tag"."id" = "tag_asset"."tagId" - where - "tag_asset"."assetId" = "asset"."id" - ) as agg - ) as "tags", - to_json("exifInfo") as "exifInfo" - from - "asset" - inner join lateral ( - select - "asset_exif"."assetId", - "asset_exif"."autoStackId", - "asset_exif"."bitsPerSample", - "asset_exif"."city", - "asset_exif"."colorspace", - "asset_exif"."country", - "asset_exif"."dateTimeOriginal", - "asset_exif"."description", - "asset_exif"."exifImageHeight", - "asset_exif"."exifImageWidth", - "asset_exif"."exposureTime", - "asset_exif"."fileSizeInByte", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."fps", - "asset_exif"."iso", - "asset_exif"."latitude", - "asset_exif"."lensModel", - "asset_exif"."livePhotoCID", - "asset_exif"."longitude", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."modifyDate", - "asset_exif"."orientation", - "asset_exif"."profileDescription", - "asset_exif"."projectionType", - "asset_exif"."rating", - "asset_exif"."state", - "asset_exif"."tags", - "asset_exif"."timeZone" - from - "asset_exif" - where - "asset_exif"."assetId" = "asset"."id" - ) as "exifInfo" on true - where - "asset"."deletedAt" is null - and "asset"."stackId" = "stack"."id" - and "asset"."visibility" in ('archive', 'timeline') - ) as agg - ) as "assets" -from - "stack" -where - "id" = $1::uuid - --- StackRepository.getForAssetRemoval -select - "stackId" as "id", - "stack"."primaryAssetId" -from - "asset" - left join "stack" on "stack"."id" = "asset"."stackId" -where - "asset"."id" = $1 - --- StackRepository.merge -update "asset" -set - "stackId" = $1 -where - "asset"."stackId" = $2 diff --git a/server/src/queries/sync.checkpoint.repository.sql b/server/src/queries/sync.checkpoint.repository.sql deleted file mode 100644 index e99d90cd54..0000000000 --- a/server/src/queries/sync.checkpoint.repository.sql +++ /dev/null @@ -1,19 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SyncCheckpointRepository.getAll -select - "type", - "ack" -from - "session_sync_checkpoint" -where - "sessionId" = $1 - --- SyncCheckpointRepository.deleteAll -delete from "session_sync_checkpoint" -where - "sessionId" = $1 - --- SyncCheckpointRepository.getNow -select - immich_uuid_v7 (now() - interval '1 millisecond') as "nowId" diff --git a/server/src/queries/sync.repository.sql b/server/src/queries/sync.repository.sql deleted file mode 100644 index f817ad57b3..0000000000 --- a/server/src/queries/sync.repository.sql +++ /dev/null @@ -1,1102 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SyncRepository.album.getCreatedAfter -select - "albumId" as "id", - "createId" -from - "album_user" -where - "userId" = $1 - and "createId" >= $2 - and "createId" < $3 -order by - "createId" asc - --- SyncRepository.album.getDeletes -select - "id", - "albumId" -from - "album_audit" as "album_audit" -where - "album_audit"."id" < $1 - and "album_audit"."id" > $2 - and "userId" = $3 -order by - "album_audit"."id" asc - --- SyncRepository.album.getUpserts -select distinct - on ("album"."id", "album"."updateId") "album"."id", - "album"."ownerId", - "album"."albumName" as "name", - "album"."description", - "album"."createdAt", - "album"."updatedAt", - "album"."albumThumbnailAssetId" as "thumbnailAssetId", - "album"."isActivityEnabled", - "album"."order", - "album"."updateId" -from - "album" as "album" - left join "album_user" as "album_users" on "album"."id" = "album_users"."albumId" -where - "album"."updateId" < $1 - and "album"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_users"."userId" = $4 - ) -order by - "album"."updateId" asc - --- SyncRepository.albumAsset.getBackfill -select - "asset"."id", - "asset"."ownerId", - "asset"."originalFileName", - "asset"."thumbhash", - "asset"."checksum", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."localDateTime", - "asset"."type", - "asset"."deletedAt", - "asset"."isFavorite", - "asset"."visibility", - "asset"."duration", - "asset"."livePhotoVideoId", - "asset"."stackId", - "asset"."libraryId", - "asset"."width", - "asset"."height", - "asset"."isEdited", - "album_asset"."updateId" -from - "album_asset" as "album_asset" - inner join "asset" on "asset"."id" = "album_asset"."assetId" -where - "album_asset"."updateId" < $1 - and "album_asset"."updateId" <= $2 - and "album_asset"."updateId" >= $3 - and "album_asset"."albumId" = $4 -order by - "album_asset"."updateId" asc - --- SyncRepository.albumAsset.getUpdates -select - "asset"."id", - "asset"."ownerId", - "asset"."originalFileName", - "asset"."thumbhash", - "asset"."checksum", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."localDateTime", - "asset"."type", - "asset"."deletedAt", - "asset"."isFavorite", - "asset"."visibility", - "asset"."duration", - "asset"."livePhotoVideoId", - "asset"."stackId", - "asset"."libraryId", - "asset"."width", - "asset"."height", - "asset"."isEdited", - "asset"."updateId" -from - "asset" as "asset" - inner join "album_asset" on "album_asset"."assetId" = "asset"."id" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" -where - "asset"."updateId" < $1 - and "asset"."updateId" > $2 - and "album_asset"."updateId" <= $3 - and ( - "album"."ownerId" = $4 - or "album_user"."userId" = $5 - ) -order by - "asset"."updateId" asc - --- SyncRepository.albumAsset.getCreates -select - "album_asset"."updateId", - "asset"."id", - "asset"."ownerId", - "asset"."originalFileName", - "asset"."thumbhash", - "asset"."checksum", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."localDateTime", - "asset"."type", - "asset"."deletedAt", - "asset"."isFavorite", - "asset"."visibility", - "asset"."duration", - "asset"."livePhotoVideoId", - "asset"."stackId", - "asset"."libraryId", - "asset"."width", - "asset"."height", - "asset"."isEdited" -from - "album_asset" as "album_asset" - inner join "asset" on "asset"."id" = "album_asset"."assetId" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" -where - "album_asset"."updateId" < $1 - and "album_asset"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_user"."userId" = $4 - ) -order by - "album_asset"."updateId" asc - --- SyncRepository.albumAssetExif.getBackfill -select - "asset_exif"."assetId", - "asset_exif"."description", - "asset_exif"."exifImageWidth", - "asset_exif"."exifImageHeight", - "asset_exif"."fileSizeInByte", - "asset_exif"."orientation", - "asset_exif"."dateTimeOriginal", - "asset_exif"."modifyDate", - "asset_exif"."timeZone", - "asset_exif"."latitude", - "asset_exif"."longitude", - "asset_exif"."projectionType", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."iso", - "asset_exif"."exposureTime", - "asset_exif"."profileDescription", - "asset_exif"."rating", - "asset_exif"."fps", - "album_asset"."updateId" -from - "album_asset" as "album_asset" - inner join "asset_exif" on "asset_exif"."assetId" = "album_asset"."assetId" -where - "album_asset"."updateId" < $1 - and "album_asset"."updateId" <= $2 - and "album_asset"."updateId" >= $3 - and "album_asset"."albumId" = $4 -order by - "album_asset"."updateId" asc - --- SyncRepository.albumAssetExif.getUpdates -select - "asset_exif"."assetId", - "asset_exif"."description", - "asset_exif"."exifImageWidth", - "asset_exif"."exifImageHeight", - "asset_exif"."fileSizeInByte", - "asset_exif"."orientation", - "asset_exif"."dateTimeOriginal", - "asset_exif"."modifyDate", - "asset_exif"."timeZone", - "asset_exif"."latitude", - "asset_exif"."longitude", - "asset_exif"."projectionType", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."iso", - "asset_exif"."exposureTime", - "asset_exif"."profileDescription", - "asset_exif"."rating", - "asset_exif"."fps", - "asset_exif"."updateId" -from - "asset_exif" as "asset_exif" - inner join "album_asset" on "album_asset"."assetId" = "asset_exif"."assetId" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" -where - "asset_exif"."updateId" < $1 - and "asset_exif"."updateId" > $2 - and "album_asset"."updateId" <= $3 - and ( - "album"."ownerId" = $4 - or "album_user"."userId" = $5 - ) -order by - "asset_exif"."updateId" asc - --- SyncRepository.albumAssetExif.getCreates -select - "album_asset"."updateId", - "asset_exif"."assetId", - "asset_exif"."description", - "asset_exif"."exifImageWidth", - "asset_exif"."exifImageHeight", - "asset_exif"."fileSizeInByte", - "asset_exif"."orientation", - "asset_exif"."dateTimeOriginal", - "asset_exif"."modifyDate", - "asset_exif"."timeZone", - "asset_exif"."latitude", - "asset_exif"."longitude", - "asset_exif"."projectionType", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."iso", - "asset_exif"."exposureTime", - "asset_exif"."profileDescription", - "asset_exif"."rating", - "asset_exif"."fps" -from - "album_asset" as "album_asset" - inner join "asset_exif" on "asset_exif"."assetId" = "album_asset"."assetId" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" -where - "album_asset"."updateId" < $1 - and "album_asset"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_user"."userId" = $4 - ) -order by - "album_asset"."updateId" asc - --- SyncRepository.albumToAsset.getBackfill -select - "album_asset"."assetId" as "assetId", - "album_asset"."albumId" as "albumId", - "album_asset"."updateId" -from - "album_asset" as "album_asset" -where - "album_asset"."updateId" < $1 - and "album_asset"."updateId" <= $2 - and "album_asset"."updateId" >= $3 - and "album_asset"."albumId" = $4 -order by - "album_asset"."updateId" asc - --- SyncRepository.albumToAsset.getDeletes -select - "id", - "assetId", - "albumId" -from - "album_asset_audit" as "album_asset_audit" -where - "album_asset_audit"."id" < $1 - and "album_asset_audit"."id" > $2 - and "albumId" in ( - select - "id" - from - "album" - where - "ownerId" = $3 - union - ( - select - "album_user"."albumId" as "id" - from - "album_user" - where - "album_user"."userId" = $4 - ) - ) -order by - "album_asset_audit"."id" asc - --- SyncRepository.albumToAsset.getUpserts -select - "album_asset"."assetId" as "assetId", - "album_asset"."albumId" as "albumId", - "album_asset"."updateId" -from - "album_asset" as "album_asset" - inner join "album" on "album"."id" = "album_asset"."albumId" - left join "album_user" on "album_user"."albumId" = "album_asset"."albumId" -where - "album_asset"."updateId" < $1 - and "album_asset"."updateId" > $2 - and ( - "album"."ownerId" = $3 - or "album_user"."userId" = $4 - ) -order by - "album_asset"."updateId" asc - --- SyncRepository.albumUser.getBackfill -select - "album_user"."albumId" as "albumId", - "album_user"."userId" as "userId", - "album_user"."role", - "album_user"."updateId" -from - "album_user" as "album_user" -where - "album_user"."updateId" < $1 - and "album_user"."updateId" <= $2 - and "album_user"."updateId" >= $3 - and "albumId" = $4 -order by - "album_user"."updateId" asc - --- SyncRepository.albumUser.getDeletes -select - "id", - "userId", - "albumId" -from - "album_user_audit" as "album_user_audit" -where - "album_user_audit"."id" < $1 - and "album_user_audit"."id" > $2 - and "albumId" in ( - select - "id" - from - "album" - where - "ownerId" = $3 - union - ( - select - "album_user"."albumId" as "id" - from - "album_user" - where - "album_user"."userId" = $4 - ) - ) -order by - "album_user_audit"."id" asc - --- SyncRepository.albumUser.getUpserts -select - "album_user"."albumId" as "albumId", - "album_user"."userId" as "userId", - "album_user"."role", - "album_user"."updateId" -from - "album_user" as "album_user" -where - "album_user"."updateId" < $1 - and "album_user"."updateId" > $2 - and "album_user"."albumId" in ( - select - "id" - from - "album" - where - "ownerId" = $3 - union - ( - select - "albumUsers"."albumId" as "id" - from - "album_user" as "albumUsers" - where - "albumUsers"."userId" = $4 - ) - ) -order by - "album_user"."updateId" asc - --- SyncRepository.asset.getDeletes -select - "id", - "assetId" -from - "asset_audit" as "asset_audit" -where - "asset_audit"."id" < $1 - and "asset_audit"."id" > $2 - and "ownerId" = $3 -order by - "asset_audit"."id" asc - --- SyncRepository.asset.getUpserts -select - "asset"."id", - "asset"."ownerId", - "asset"."originalFileName", - "asset"."thumbhash", - "asset"."checksum", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."localDateTime", - "asset"."type", - "asset"."deletedAt", - "asset"."isFavorite", - "asset"."visibility", - "asset"."duration", - "asset"."livePhotoVideoId", - "asset"."stackId", - "asset"."libraryId", - "asset"."width", - "asset"."height", - "asset"."isEdited", - "asset"."updateId" -from - "asset" as "asset" -where - "asset"."updateId" < $1 - and "asset"."updateId" > $2 - and "ownerId" = $3 -order by - "asset"."updateId" asc - --- SyncRepository.assetExif.getUpserts -select - "asset_exif"."assetId", - "asset_exif"."description", - "asset_exif"."exifImageWidth", - "asset_exif"."exifImageHeight", - "asset_exif"."fileSizeInByte", - "asset_exif"."orientation", - "asset_exif"."dateTimeOriginal", - "asset_exif"."modifyDate", - "asset_exif"."timeZone", - "asset_exif"."latitude", - "asset_exif"."longitude", - "asset_exif"."projectionType", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."iso", - "asset_exif"."exposureTime", - "asset_exif"."profileDescription", - "asset_exif"."rating", - "asset_exif"."fps", - "asset_exif"."updateId" -from - "asset_exif" as "asset_exif" -where - "asset_exif"."updateId" < $1 - and "asset_exif"."updateId" > $2 - and "assetId" in ( - select - "id" - from - "asset" - where - "ownerId" = $3 - ) -order by - "asset_exif"."updateId" asc - --- SyncRepository.assetFace.getDeletes -select - "asset_face_audit"."id", - "assetFaceId" -from - "asset_face_audit" as "asset_face_audit" - left join "asset" on "asset"."id" = "asset_face_audit"."assetId" -where - "asset_face_audit"."id" < $1 - and "asset_face_audit"."id" > $2 - and "asset"."ownerId" = $3 -order by - "asset_face_audit"."id" asc - --- SyncRepository.assetFace.getUpserts -select - "asset_face"."id", - "assetId", - "personId", - "imageWidth", - "imageHeight", - "boundingBoxX1", - "boundingBoxY1", - "boundingBoxX2", - "boundingBoxY2", - "sourceType", - "asset_face"."updateId" -from - "asset_face" as "asset_face" - left join "asset" on "asset"."id" = "asset_face"."assetId" -where - "asset_face"."updateId" < $1 - and "asset_face"."updateId" > $2 - and "asset"."ownerId" = $3 - and "asset_face"."isVisible" = $4 -order by - "asset_face"."updateId" asc - --- SyncRepository.assetMetadata.getDeletes -select - "asset_metadata_audit"."id", - "assetId", - "key" -from - "asset_metadata_audit" as "asset_metadata_audit" - left join "asset" on "asset"."id" = "asset_metadata_audit"."assetId" -where - "asset_metadata_audit"."id" < $1 - and "asset_metadata_audit"."id" > $2 - and "asset"."ownerId" = $3 -order by - "asset_metadata_audit"."id" asc - --- SyncRepository.assetMetadata.getUpserts -select - "assetId", - "key", - "value", - "asset_metadata"."updateId" -from - "asset_metadata" as "asset_metadata" - inner join "asset" on "asset"."id" = "asset_metadata"."assetId" -where - "asset_metadata"."updateId" < $1 - and "asset_metadata"."updateId" > $2 - and "asset"."ownerId" = $3 -order by - "asset_metadata"."updateId" asc - --- SyncRepository.authUser.getUpserts -select - "id", - "name", - "email", - "avatarColor", - "deletedAt", - "updateId", - "profileImagePath", - "profileChangedAt", - "isAdmin", - "pinCode", - "oauthId", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes" -from - "user" as "user" -where - "user"."updateId" < $1 - and "user"."updateId" > $2 - and "id" = $3 -order by - "user"."updateId" asc - --- SyncRepository.memory.getDeletes -select - "id", - "memoryId" -from - "memory_audit" as "memory_audit" -where - "memory_audit"."id" < $1 - and "memory_audit"."id" > $2 - and "userId" = $3 -order by - "memory_audit"."id" asc - --- SyncRepository.memory.getUpserts -select - "id", - "createdAt", - "updatedAt", - "deletedAt", - "ownerId", - "type", - "data", - "isSaved", - "memoryAt", - "seenAt", - "showAt", - "hideAt", - "updateId" -from - "memory" as "memory" -where - "memory"."updateId" < $1 - and "memory"."updateId" > $2 - and "ownerId" = $3 -order by - "memory"."updateId" asc - --- SyncRepository.memoryToAsset.getDeletes -select - "id", - "memoryId", - "assetId" -from - "memory_asset_audit" as "memory_asset_audit" -where - "memory_asset_audit"."id" < $1 - and "memory_asset_audit"."id" > $2 - and "memoryId" in ( - select - "id" - from - "memory" - where - "ownerId" = $3 - ) -order by - "memory_asset_audit"."id" asc - --- SyncRepository.memoryToAsset.getUpserts -select - "memoriesId" as "memoryId", - "assetId" as "assetId", - "updateId" -from - "memory_asset" as "memory_asset" -where - "memory_asset"."updateId" < $1 - and "memory_asset"."updateId" > $2 - and "memoriesId" in ( - select - "id" - from - "memory" - where - "ownerId" = $3 - ) -order by - "memory_asset"."updateId" asc - --- SyncRepository.partner.getCreatedAfter -select - "sharedById", - "createId" -from - "partner" -where - "sharedWithId" = $1 - and "createId" >= $2 - and "createId" < $3 -order by - "partner"."createId" asc - --- SyncRepository.partner.getDeletes -select - "id", - "sharedById", - "sharedWithId" -from - "partner_audit" as "partner_audit" -where - "partner_audit"."id" < $1 - and "partner_audit"."id" > $2 - and ( - "sharedById" = $3 - or "sharedWithId" = $4 - ) -order by - "partner_audit"."id" asc - --- SyncRepository.partner.getUpserts -select - "sharedById", - "sharedWithId", - "inTimeline", - "updateId" -from - "partner" as "partner" -where - "partner"."updateId" < $1 - and "partner"."updateId" > $2 - and ( - "sharedById" = $3 - or "sharedWithId" = $4 - ) -order by - "partner"."updateId" asc - --- SyncRepository.partnerAsset.getBackfill -select - "asset"."id", - "asset"."ownerId", - "asset"."originalFileName", - "asset"."thumbhash", - "asset"."checksum", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."localDateTime", - "asset"."type", - "asset"."deletedAt", - "asset"."isFavorite", - "asset"."visibility", - "asset"."duration", - "asset"."livePhotoVideoId", - "asset"."stackId", - "asset"."libraryId", - "asset"."width", - "asset"."height", - "asset"."isEdited", - "asset"."updateId" -from - "asset" as "asset" -where - "asset"."updateId" < $1 - and "asset"."updateId" <= $2 - and "asset"."updateId" >= $3 - and "ownerId" = $4 -order by - "asset"."updateId" asc - --- SyncRepository.partnerAsset.getDeletes -select - "id", - "assetId" -from - "asset_audit" as "asset_audit" -where - "asset_audit"."id" < $1 - and "asset_audit"."id" > $2 - and "ownerId" in ( - select - "sharedById" - from - "partner" - where - "sharedWithId" = $3 - ) -order by - "asset_audit"."id" asc - --- SyncRepository.partnerAsset.getUpserts -select - "asset"."id", - "asset"."ownerId", - "asset"."originalFileName", - "asset"."thumbhash", - "asset"."checksum", - "asset"."fileCreatedAt", - "asset"."fileModifiedAt", - "asset"."localDateTime", - "asset"."type", - "asset"."deletedAt", - "asset"."isFavorite", - "asset"."visibility", - "asset"."duration", - "asset"."livePhotoVideoId", - "asset"."stackId", - "asset"."libraryId", - "asset"."width", - "asset"."height", - "asset"."isEdited", - "asset"."updateId" -from - "asset" as "asset" -where - "asset"."updateId" < $1 - and "asset"."updateId" > $2 - and "ownerId" in ( - select - "sharedById" - from - "partner" - where - "sharedWithId" = $3 - ) -order by - "asset"."updateId" asc - --- SyncRepository.partnerAssetExif.getBackfill -select - "asset_exif"."assetId", - "asset_exif"."description", - "asset_exif"."exifImageWidth", - "asset_exif"."exifImageHeight", - "asset_exif"."fileSizeInByte", - "asset_exif"."orientation", - "asset_exif"."dateTimeOriginal", - "asset_exif"."modifyDate", - "asset_exif"."timeZone", - "asset_exif"."latitude", - "asset_exif"."longitude", - "asset_exif"."projectionType", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."iso", - "asset_exif"."exposureTime", - "asset_exif"."profileDescription", - "asset_exif"."rating", - "asset_exif"."fps", - "asset_exif"."updateId" -from - "asset_exif" as "asset_exif" - inner join "asset" on "asset"."id" = "asset_exif"."assetId" -where - "asset_exif"."updateId" < $1 - and "asset_exif"."updateId" <= $2 - and "asset_exif"."updateId" >= $3 - and "asset"."ownerId" = $4 -order by - "asset_exif"."updateId" asc - --- SyncRepository.partnerAssetExif.getUpserts -select - "asset_exif"."assetId", - "asset_exif"."description", - "asset_exif"."exifImageWidth", - "asset_exif"."exifImageHeight", - "asset_exif"."fileSizeInByte", - "asset_exif"."orientation", - "asset_exif"."dateTimeOriginal", - "asset_exif"."modifyDate", - "asset_exif"."timeZone", - "asset_exif"."latitude", - "asset_exif"."longitude", - "asset_exif"."projectionType", - "asset_exif"."city", - "asset_exif"."state", - "asset_exif"."country", - "asset_exif"."make", - "asset_exif"."model", - "asset_exif"."lensModel", - "asset_exif"."fNumber", - "asset_exif"."focalLength", - "asset_exif"."iso", - "asset_exif"."exposureTime", - "asset_exif"."profileDescription", - "asset_exif"."rating", - "asset_exif"."fps", - "asset_exif"."updateId" -from - "asset_exif" as "asset_exif" -where - "asset_exif"."updateId" < $1 - and "asset_exif"."updateId" > $2 - and "assetId" in ( - select - "id" - from - "asset" - where - "ownerId" in ( - select - "sharedById" - from - "partner" - where - "sharedWithId" = $3 - ) - ) -order by - "asset_exif"."updateId" asc - --- SyncRepository.partnerStack.getDeletes -select - "id", - "stackId" -from - "stack_audit" as "stack_audit" -where - "stack_audit"."id" < $1 - and "stack_audit"."id" > $2 - and "userId" in ( - select - "sharedById" - from - "partner" - where - "sharedWithId" = $3 - ) -order by - "stack_audit"."id" asc - --- SyncRepository.partnerStack.getBackfill -select - "stack"."id", - "stack"."createdAt", - "stack"."updatedAt", - "stack"."primaryAssetId", - "stack"."ownerId", - "updateId" -from - "stack" as "stack" -where - "stack"."updateId" < $1 - and "stack"."updateId" <= $2 - and "stack"."updateId" >= $3 - and "ownerId" = $4 -order by - "stack"."updateId" asc - --- SyncRepository.partnerStack.getUpserts -select - "stack"."id", - "stack"."createdAt", - "stack"."updatedAt", - "stack"."primaryAssetId", - "stack"."ownerId", - "updateId" -from - "stack" as "stack" -where - "stack"."updateId" < $1 - and "stack"."updateId" > $2 - and "ownerId" in ( - select - "sharedById" - from - "partner" - where - "sharedWithId" = $3 - ) -order by - "stack"."updateId" asc - --- SyncRepository.person.getDeletes -select - "id", - "personId" -from - "person_audit" as "person_audit" -where - "person_audit"."id" < $1 - and "person_audit"."id" > $2 - and "ownerId" = $3 -order by - "person_audit"."id" asc - --- SyncRepository.person.getUpserts -select - "id", - "createdAt", - "updatedAt", - "ownerId", - "name", - "birthDate", - "isHidden", - "isFavorite", - "color", - "updateId", - "faceAssetId" -from - "person" as "person" -where - "person"."updateId" < $1 - and "person"."updateId" > $2 - and "ownerId" = $3 -order by - "person"."updateId" asc - --- SyncRepository.stack.getDeletes -select - "id", - "stackId" -from - "stack_audit" as "stack_audit" -where - "stack_audit"."id" < $1 - and "stack_audit"."id" > $2 - and "userId" = $3 -order by - "stack_audit"."id" asc - --- SyncRepository.stack.getUpserts -select - "stack"."id", - "stack"."createdAt", - "stack"."updatedAt", - "stack"."primaryAssetId", - "stack"."ownerId", - "updateId" -from - "stack" as "stack" -where - "stack"."updateId" < $1 - and "stack"."updateId" > $2 - and "ownerId" = $3 -order by - "stack"."updateId" asc - --- SyncRepository.user.getDeletes -select - "id", - "userId" -from - "user_audit" as "user_audit" -where - "user_audit"."id" < $1 - and "user_audit"."id" > $2 -order by - "user_audit"."id" asc - --- SyncRepository.user.getUpserts -select - "id", - "name", - "email", - "avatarColor", - "deletedAt", - "updateId", - "profileImagePath", - "profileChangedAt" -from - "user" as "user" -where - "user"."updateId" < $1 - and "user"."updateId" > $2 -order by - "user"."updateId" asc - --- SyncRepository.userMetadata.getDeletes -select - "id", - "userId", - "key" -from - "user_metadata_audit" as "user_metadata_audit" -where - "user_metadata_audit"."id" < $1 - and "user_metadata_audit"."id" > $2 - and "userId" = $3 -order by - "user_metadata_audit"."id" asc - --- SyncRepository.userMetadata.getUpserts -select - "userId", - "key", - "value", - "updateId" -from - "user_metadata" as "user_metadata" -where - "user_metadata"."updateId" < $1 - and "user_metadata"."updateId" > $2 - and "userId" = $3 -order by - "user_metadata"."updateId" asc diff --git a/server/src/queries/system.metadata.repository.sql b/server/src/queries/system.metadata.repository.sql deleted file mode 100644 index 8bdf1b3ad7..0000000000 --- a/server/src/queries/system.metadata.repository.sql +++ /dev/null @@ -1,14 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SystemMetadataRepository.get -select - "value" -from - "system_metadata" -where - "key" = $1 - --- SystemMetadataRepository.delete -delete from "system_metadata" -where - "key" = $1 diff --git a/server/src/queries/tag.repository.sql b/server/src/queries/tag.repository.sql deleted file mode 100644 index c3b46dd9f3..0000000000 --- a/server/src/queries/tag.repository.sql +++ /dev/null @@ -1,118 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- TagRepository.get -select - "tag"."id", - "tag"."value", - "tag"."createdAt", - "tag"."updatedAt", - "tag"."color", - "tag"."parentId" -from - "tag" -where - "id" = $1 - --- TagRepository.getByValue -select - "tag"."id", - "tag"."value", - "tag"."createdAt", - "tag"."updatedAt", - "tag"."color", - "tag"."parentId" -from - "tag" -where - "userId" = $1 - and "value" = $2 - --- TagRepository.upsertValue -begin -insert into - "tag" ("userId", "value", "parentId") -values - ($1, $2, $3) -on conflict ("userId", "value") do update -set - "parentId" = $4 -returning - "tag"."id", - "tag"."value", - "tag"."createdAt", - "tag"."updatedAt", - "tag"."color", - "tag"."parentId" -rollback - --- TagRepository.getAll -select - "tag"."id", - "tag"."value", - "tag"."createdAt", - "tag"."updatedAt", - "tag"."color", - "tag"."parentId" -from - "tag" -where - "userId" = $1 -order by - "value" - --- TagRepository.create -insert into - "tag" ("userId", "color", "value") -values - ($1, $2, $3) -returning - * - --- TagRepository.update -update "tag" -set - "color" = $1 -where - "id" = $2 -returning - * - --- TagRepository.delete -delete from "tag" -where - "id" = $1 - --- TagRepository.addAssetIds -insert into - "tag_asset" ("tagId", "assetId") -values - ($1, $2) - --- TagRepository.removeAssetIds -delete from "tag_asset" -where - "tagId" = $1 - and "assetId" in ($2) - --- TagRepository.upsertAssetIds -insert into - "tag_asset" ("assetId", "tagIds") -values - ($1, $2) -on conflict do nothing -returning - * - --- TagRepository.replaceAssetTags -begin -delete from "tag_asset" -where - "assetId" = $1 -insert into - "tag_asset" ("tagId", "assetId") -values - ($1, $2) -on conflict do nothing -returning - * -rollback diff --git a/server/src/queries/trash.repository.sql b/server/src/queries/trash.repository.sql deleted file mode 100644 index 57b2f32e37..0000000000 --- a/server/src/queries/trash.repository.sql +++ /dev/null @@ -1,27 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- TrashRepository.restore -update "asset" -set - "status" = $1, - "deletedAt" = $2 -where - "ownerId" = $3 - and "status" = $4 - --- TrashRepository.empty -update "asset" -set - "status" = $1 -where - "ownerId" = $2 - and "status" = $3 - --- TrashRepository.restoreAll -update "asset" -set - "status" = $1, - "deletedAt" = $2 -where - "status" = $3 - and "id" in ($4) diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql deleted file mode 100644 index c5a4f139a7..0000000000 --- a/server/src/queries/user.repository.sql +++ /dev/null @@ -1,399 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- UserRepository.get -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "user_metadata"."key", - "user_metadata"."value" - from - "user_metadata" - where - "user"."id" = "user_metadata"."userId" - ) as agg - ) as "metadata" -from - "user" -where - "user"."id" = $1 - and "user"."deletedAt" is null - --- UserRepository.getAdmin -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "user_metadata"."key", - "user_metadata"."value" - from - "user_metadata" - where - "user"."id" = "user_metadata"."userId" - ) as agg - ) as "metadata" -from - "user" -where - "user"."isAdmin" = $1 - and "user"."deletedAt" is null - --- UserRepository.getFileSamples -select - "id", - "profileImagePath" -from - "user" -where - "profileImagePath" != '' -limit - 3 - --- UserRepository.hasAdmin -select - "user"."id" -from - "user" -where - "user"."isAdmin" = $1 - and "user"."deletedAt" is null - --- UserRepository.getForPinCode -select - "user"."pinCode", - "user"."password" -from - "user" -where - "user"."id" = $1 - and "user"."deletedAt" is null - --- UserRepository.getForChangePassword -select - "user"."id", - "user"."password" -from - "user" -where - "user"."id" = $1 - and "user"."deletedAt" is null - --- UserRepository.getByEmail -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "user_metadata"."key", - "user_metadata"."value" - from - "user_metadata" - where - "user"."id" = "user_metadata"."userId" - ) as agg - ) as "metadata" -from - "user" -where - "email" = $1 - and "user"."deletedAt" is null - --- UserRepository.getByStorageLabel -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes" -from - "user" -where - "user"."storageLabel" = $1 - and "user"."deletedAt" is null - --- UserRepository.getByOAuthId -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "user_metadata"."key", - "user_metadata"."value" - from - "user_metadata" - where - "user"."id" = "user_metadata"."userId" - ) as agg - ) as "metadata" -from - "user" -where - "user"."oauthId" = $1 - and "user"."deletedAt" is null - --- UserRepository.getDeletedAfter -select - "id" -from - "user" -where - "user"."deletedAt" < $1 - --- UserRepository.getList (with deleted) -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "user_metadata"."key", - "user_metadata"."value" - from - "user_metadata" - where - "user"."id" = "user_metadata"."userId" - ) as agg - ) as "metadata" -from - "user" -order by - "createdAt" desc - --- UserRepository.getList (without deleted) -select - "id", - "name", - "email", - "avatarColor", - "profileImagePath", - "profileChangedAt", - "createdAt", - "updatedAt", - "deletedAt", - "isAdmin", - "status", - "oauthId", - "profileImagePath", - "shouldChangePassword", - "storageLabel", - "quotaSizeInBytes", - "quotaUsageInBytes", - ( - select - coalesce(json_agg(agg), '[]') - from - ( - select - "user_metadata"."key", - "user_metadata"."value" - from - "user_metadata" - where - "user"."id" = "user_metadata"."userId" - ) as agg - ) as "metadata" -from - "user" -where - "user"."deletedAt" is null -order by - "createdAt" desc - --- UserRepository.getUserStats -select - "user"."id" as "userId", - "user"."name" as "userName", - "user"."quotaSizeInBytes", - count(*) filter ( - where - ( - "asset"."type" = 'IMAGE' - and "asset"."visibility" != 'hidden' - ) - ) as "photos", - count(*) filter ( - where - ( - "asset"."type" = 'VIDEO' - and "asset"."visibility" != 'hidden' - ) - ) as "videos", - coalesce( - sum("asset_exif"."fileSizeInByte") filter ( - where - "asset"."libraryId" is null - ), - 0 - ) as "usage", - coalesce( - sum("asset_exif"."fileSizeInByte") filter ( - where - ( - "asset"."libraryId" is null - and "asset"."type" = 'IMAGE' - ) - ), - 0 - ) as "usagePhotos", - coalesce( - sum("asset_exif"."fileSizeInByte") filter ( - where - ( - "asset"."libraryId" is null - and "asset"."type" = 'VIDEO' - ) - ), - 0 - ) as "usageVideos" -from - "user" - left join "asset" on "asset"."ownerId" = "user"."id" - and "asset"."deletedAt" is null - left join "asset_exif" on "asset_exif"."assetId" = "asset"."id" -group by - "user"."id" -order by - "user"."createdAt" asc - --- UserRepository.getCount -select - count(*) as "count" -from - "user" -where - "user"."deletedAt" is null - --- UserRepository.updateUsage -update "user" -set - "quotaUsageInBytes" = "quotaUsageInBytes" + $1, - "updatedAt" = $2 -where - "id" = $3::uuid - and "user"."deletedAt" is null - --- UserRepository.syncUsage -update "user" -set - "quotaUsageInBytes" = ( - select - coalesce(sum("asset_exif"."fileSizeInByte"), 0) as "usage" - from - "asset" - left join "asset_exif" on "asset_exif"."assetId" = "asset"."id" - where - "asset"."libraryId" is null - and "asset"."ownerId" = "user"."id" - ), - "updatedAt" = $1 -where - "user"."deletedAt" is null - and "user"."id" = $2::uuid diff --git a/server/src/queries/version.history.repository.sql b/server/src/queries/version.history.repository.sql deleted file mode 100644 index 2e898cac31..0000000000 --- a/server/src/queries/version.history.repository.sql +++ /dev/null @@ -1,17 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- VersionHistoryRepository.getAll -select - * -from - "version_history" -order by - "createdAt" desc - --- VersionHistoryRepository.getLatest -select - * -from - "version_history" -order by - "createdAt" desc diff --git a/server/src/queries/view.repository.sql b/server/src/queries/view.repository.sql deleted file mode 100644 index 31da10123f..0000000000 --- a/server/src/queries/view.repository.sql +++ /dev/null @@ -1,35 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- ViewRepository.getUniqueOriginalPaths -select distinct - substring("asset"."originalPath", $1) as "directoryPath" -from - "asset" -where - "ownerId" = $2::uuid - and "visibility" = $3 - and "deletedAt" is null - and "fileCreatedAt" is not null - and "fileModifiedAt" is not null - and "localDateTime" is not null -order by - "directoryPath" asc - --- ViewRepository.getAssetsByOriginalPath -select - "asset".*, - to_json("asset_exif") as "exifInfo" -from - "asset" - left join "asset_exif" on "asset"."id" = "asset_exif"."assetId" -where - "ownerId" = $1::uuid - and "visibility" = $2 - and "deletedAt" is null - and "fileCreatedAt" is not null - and "fileModifiedAt" is not null - and "localDateTime" is not null - and "originalPath" like $3 - and "originalPath" not like $4 -order by - regexp_replace("asset"."originalPath", $5, $6) asc diff --git a/server/src/queries/workflow.repository.sql b/server/src/queries/workflow.repository.sql deleted file mode 100644 index 27dc21dffe..0000000000 --- a/server/src/queries/workflow.repository.sql +++ /dev/null @@ -1,70 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- WorkflowRepository.getWorkflow -select - * -from - "workflow" -where - "id" = $1 -order by - "createdAt" desc - --- WorkflowRepository.getWorkflowsByOwner -select - * -from - "workflow" -where - "ownerId" = $1 -order by - "createdAt" desc - --- WorkflowRepository.getWorkflowsByTrigger -select - * -from - "workflow" -where - "triggerType" = $1 - and "enabled" = $2 - --- WorkflowRepository.getWorkflowByOwnerAndTrigger -select - * -from - "workflow" -where - "ownerId" = $1 - and "triggerType" = $2 - and "enabled" = $3 - --- WorkflowRepository.deleteWorkflow -delete from "workflow" -where - "id" = $1 - --- WorkflowRepository.getFilters -select - * -from - "workflow_filter" -where - "workflowId" = $1 -order by - "order" asc - --- WorkflowRepository.deleteFiltersByWorkflow -delete from "workflow_filter" -where - "workflowId" = $1 - --- WorkflowRepository.getActions -select - * -from - "workflow_action" -where - "workflowId" = $1 -order by - "order" asc diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts deleted file mode 100644 index 533e74a311..0000000000 --- a/server/src/repositories/access.repository.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely, sql } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { AlbumUserRole, AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { asUuid } from 'src/utils/database'; - -class ActivityAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, activityIds: Set) { - if (activityIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('activity') - .select('activity.id') - .where('activity.id', 'in', [...activityIds]) - .where('activity.userId', '=', userId) - .execute() - .then((activities) => new Set(activities.map((activity) => activity.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkAlbumOwnerAccess(userId: string, activityIds: Set) { - if (activityIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('activity') - .select('activity.id') - .leftJoin('album', (join) => join.onRef('activity.albumId', '=', 'album.id').on('album.deletedAt', 'is', null)) - .where('activity.id', 'in', [...activityIds]) - .whereRef('album.ownerId', '=', asUuid(userId)) - .execute() - .then((activities) => new Set(activities.map((activity) => activity.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkCreateAccess(userId: string, albumIds: Set) { - if (albumIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('album') - .select('album.id') - .leftJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null)) - .where('album.id', 'in', [...albumIds]) - .where('album.isActivityEnabled', '=', true) - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)])) - .where('album.deletedAt', 'is', null) - .execute() - .then((albums) => new Set(albums.map((album) => album.id))); - } -} - -class AlbumAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, albumIds: Set) { - if (albumIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('album') - .select('album.id') - .where('album.id', 'in', [...albumIds]) - .where('album.ownerId', '=', userId) - .where('album.deletedAt', 'is', null) - .execute() - .then((albums) => new Set(albums.map((album) => album.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole) { - if (albumIds.size === 0) { - return new Set(); - } - - const accessRole = - access === AlbumUserRole.Editor ? [AlbumUserRole.Editor] : [AlbumUserRole.Editor, AlbumUserRole.Viewer]; - - return this.db - .selectFrom('album') - .select('album.id') - .leftJoin('album_user', 'album_user.albumId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'album_user.userId').on('user.deletedAt', 'is', null)) - .where('album.id', 'in', [...albumIds]) - .where('album.deletedAt', 'is', null) - .where('user.id', '=', userId) - .where('album_user.role', 'in', [...accessRole]) - .execute() - .then((albums) => new Set(albums.map((album) => album.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkSharedLinkAccess(sharedLinkId: string, albumIds: Set) { - if (albumIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('shared_link') - .select('shared_link.albumId') - .where('shared_link.id', '=', sharedLinkId) - .where('shared_link.albumId', 'in', [...albumIds]) - .execute() - .then( - (sharedLinks) => new Set(sharedLinks.flatMap((sharedLink) => (sharedLink.albumId ? [sharedLink.albumId] : []))), - ); - } -} - -class AssetAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkAlbumAccess(userId: string, assetIds: Set) { - if (assetIds.size === 0) { - return new Set(); - } - - return this.db - .with('target', (qb) => qb.selectNoFrom(sql`array[${sql.join([...assetIds])}]::uuid[]`.as('ids'))) - .selectFrom('album') - .innerJoin('album_asset as albumAssets', 'album.id', 'albumAssets.albumId') - .innerJoin('asset', (join) => - join.onRef('asset.id', '=', 'albumAssets.assetId').on('asset.deletedAt', 'is', null), - ) - .leftJoin('album_user as albumUsers', 'albumUsers.albumId', 'album.id') - .leftJoin('user', (join) => join.onRef('user.id', '=', 'albumUsers.userId').on('user.deletedAt', 'is', null)) - .crossJoin('target') - .select(['asset.id', 'asset.livePhotoVideoId']) - .where((eb) => - eb.or([ - eb('asset.id', '=', sql`any(target.ids)`), - eb('asset.livePhotoVideoId', '=', sql`any(target.ids)`), - ]), - ) - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('user.id', '=', userId)])) - .where('album.deletedAt', 'is', null) - .execute() - .then((assets) => { - const allowedIds = new Set(); - for (const asset of assets) { - if (asset.id && assetIds.has(asset.id)) { - allowedIds.add(asset.id); - } - if (asset.livePhotoVideoId && assetIds.has(asset.livePhotoVideoId)) { - allowedIds.add(asset.livePhotoVideoId); - } - } - return allowedIds; - }); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, assetIds: Set, hasElevatedPermission: boolean | undefined) { - if (assetIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('asset') - .select('asset.id') - .where('asset.id', 'in', [...assetIds]) - .where('asset.ownerId', '=', userId) - .$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.Locked)) - .execute() - .then((assets) => new Set(assets.map((asset) => asset.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkPartnerAccess(userId: string, assetIds: Set) { - if (assetIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('partner') - .innerJoin('user as sharedBy', (join) => - join.onRef('sharedBy.id', '=', 'partner.sharedById').on('sharedBy.deletedAt', 'is', null), - ) - .innerJoin('asset', (join) => join.onRef('asset.ownerId', '=', 'sharedBy.id').on('asset.deletedAt', 'is', null)) - .select('asset.id') - .where('partner.sharedWithId', '=', userId) - .where((eb) => - eb.or([ - eb('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)), - eb('asset.visibility', '=', sql.lit(AssetVisibility.Hidden)), - ]), - ) - - .where('asset.id', 'in', [...assetIds]) - .execute() - .then((assets) => new Set(assets.map((asset) => asset.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkSharedLinkAccess(sharedLinkId: string, assetIds: Set) { - if (assetIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('shared_link') - .leftJoin('album', (join) => join.onRef('album.id', '=', 'shared_link.albumId').on('album.deletedAt', 'is', null)) - .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id') - .leftJoin('asset', (join) => - join.onRef('asset.id', '=', 'shared_link_asset.assetId').on('asset.deletedAt', 'is', null), - ) - .leftJoin('album_asset', 'album_asset.albumId', 'album.id') - .leftJoin('asset as albumAssets', (join) => - join.onRef('albumAssets.id', '=', 'album_asset.assetId').on('albumAssets.deletedAt', 'is', null), - ) - .select([ - 'asset.id as assetId', - 'asset.livePhotoVideoId as assetLivePhotoVideoId', - 'albumAssets.id as albumAssetId', - 'albumAssets.livePhotoVideoId as albumAssetLivePhotoVideoId', - ]) - .where('shared_link.id', '=', sharedLinkId) - .where( - sql`array["asset"."id", "asset"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"]`, - '&&', - sql`array[${sql.join([...assetIds])}]::uuid[] `, - ) - .execute() - .then((rows) => { - const allowedIds = new Set(); - for (const row of rows) { - if (row.assetId && assetIds.has(row.assetId)) { - allowedIds.add(row.assetId); - } - if (row.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) { - allowedIds.add(row.assetLivePhotoVideoId); - } - if (row.albumAssetId && assetIds.has(row.albumAssetId)) { - allowedIds.add(row.albumAssetId); - } - if (row.albumAssetLivePhotoVideoId && assetIds.has(row.albumAssetLivePhotoVideoId)) { - allowedIds.add(row.albumAssetLivePhotoVideoId); - } - } - return allowedIds; - }); - } -} - -class AuthDeviceAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, deviceIds: Set) { - if (deviceIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('session') - .select('session.id') - .where('session.userId', '=', userId) - .where('session.id', 'in', [...deviceIds]) - .execute() - .then((tokens) => new Set(tokens.map((token) => token.id))); - } -} - -class NotificationAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, notificationIds: Set) { - if (notificationIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('notification') - .select('notification.id') - .where('notification.id', 'in', [...notificationIds]) - .where('notification.userId', '=', userId) - .execute() - .then((stacks) => new Set(stacks.map((stack) => stack.id))); - } -} - -class SessionAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, sessionIds: Set) { - if (sessionIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('session') - .select('session.id') - .where('session.id', 'in', [...sessionIds]) - .where('session.userId', '=', userId) - .execute() - .then((sessions) => new Set(sessions.map((session) => session.id))); - } -} -class StackAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, stackIds: Set) { - if (stackIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('stack') - .select('stack.id') - .where('stack.id', 'in', [...stackIds]) - .where('stack.ownerId', '=', userId) - .execute() - .then((stacks) => new Set(stacks.map((stack) => stack.id))); - } -} - -class TimelineAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkPartnerAccess(userId: string, partnerIds: Set) { - if (partnerIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('partner') - .select('partner.sharedById') - .where('partner.sharedById', 'in', [...partnerIds]) - .where('partner.sharedWithId', '=', userId) - .execute() - .then((partners) => new Set(partners.map((partner) => partner.sharedById))); - } -} - -class MemoryAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, memoryIds: Set) { - if (memoryIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('memory') - .select('memory.id') - .where('memory.id', 'in', [...memoryIds]) - .where('memory.ownerId', '=', userId) - .where('memory.deletedAt', 'is', null) - .execute() - .then((memories) => new Set(memories.map((memory) => memory.id))); - } -} - -class PersonAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, personIds: Set) { - if (personIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('person') - .select('person.id') - .where('person.id', 'in', [...personIds]) - .where('person.ownerId', '=', userId) - .execute() - .then((persons) => new Set(persons.map((person) => person.id))); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkFaceOwnerAccess(userId: string, assetFaceIds: Set) { - if (assetFaceIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('asset_face') - .select('asset_face.id') - .leftJoin('asset', (join) => join.onRef('asset.id', '=', 'asset_face.assetId').on('asset.deletedAt', 'is', null)) - .where('asset_face.id', 'in', [...assetFaceIds]) - .where('asset.ownerId', '=', userId) - .execute() - .then((faces) => new Set(faces.map((face) => face.id))); - } -} - -class PartnerAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkUpdateAccess(userId: string, partnerIds: Set) { - if (partnerIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('partner') - .select('partner.sharedById') - .where('partner.sharedById', 'in', [...partnerIds]) - .where('partner.sharedWithId', '=', userId) - .execute() - .then((partners) => new Set(partners.map((partner) => partner.sharedById))); - } -} - -class TagAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, tagIds: Set) { - if (tagIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('tag') - .select('tag.id') - .where('tag.id', 'in', [...tagIds]) - .where('tag.userId', '=', userId) - .execute() - .then((tags) => new Set(tags.map((tag) => tag.id))); - } -} - -class WorkflowAccess { - constructor(private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] }) - @ChunkedSet({ paramIndex: 1 }) - async checkOwnerAccess(userId: string, workflowIds: Set) { - if (workflowIds.size === 0) { - return new Set(); - } - - return this.db - .selectFrom('workflow') - .select('workflow.id') - .where('workflow.id', 'in', [...workflowIds]) - .where('workflow.ownerId', '=', userId) - .execute() - .then((workflows) => new Set(workflows.map((workflow) => workflow.id))); - } -} - -@Injectable() -export class AccessRepository { - activity: ActivityAccess; - album: AlbumAccess; - asset: AssetAccess; - authDevice: AuthDeviceAccess; - memory: MemoryAccess; - notification: NotificationAccess; - person: PersonAccess; - partner: PartnerAccess; - session: SessionAccess; - stack: StackAccess; - tag: TagAccess; - timeline: TimelineAccess; - workflow: WorkflowAccess; - - constructor(@InjectKysely() db: Kysely) { - this.activity = new ActivityAccess(db); - this.album = new AlbumAccess(db); - this.asset = new AssetAccess(db); - this.authDevice = new AuthDeviceAccess(db); - this.memory = new MemoryAccess(db); - this.notification = new NotificationAccess(db); - this.person = new PersonAccess(db); - this.partner = new PartnerAccess(db); - this.session = new SessionAccess(db); - this.stack = new StackAccess(db); - this.tag = new TagAccess(db); - this.timeline = new TimelineAccess(db); - this.workflow = new WorkflowAccess(db); - } -} diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts deleted file mode 100644 index 1a1104b118..0000000000 --- a/server/src/repositories/activity.repository.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, NotNull, sql } from 'kysely'; -import { jsonObjectFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { ActivityTable } from 'src/schema/tables/activity.table'; -import { asUuid } from 'src/utils/database'; - -export interface ActivitySearch { - albumId?: string; - assetId?: string | null; - userId?: string; - isLiked?: boolean; -} - -@Injectable() -export class ActivityRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [{ albumId: DummyValue.UUID }] }) - search(options: ActivitySearch) { - const { userId, assetId, albumId, isLiked } = options; - - return this.db - .selectFrom('activity') - .selectAll('activity') - .innerJoin('user as user2', (join) => - join.onRef('user2.id', '=', 'activity.userId').on('user2.deletedAt', 'is', null), - ) - .innerJoinLateral( - (eb) => - eb - .selectFrom(sql`(select 1)`.as('dummy')) - .select(columns.userWithPrefix) - .as('user'), - (join) => join.onTrue(), - ) - .select((eb) => eb.fn.toJson('user').as('user')) - .leftJoin('asset', 'asset.id', 'activity.assetId') - .$if(!!userId, (qb) => qb.where('activity.userId', '=', userId!)) - .$if(assetId === null, (qb) => qb.where('assetId', 'is', null)) - .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!)) - .$if(!!albumId, (qb) => qb.where('activity.albumId', '=', albumId!)) - .$if(isLiked !== undefined, (qb) => qb.where('activity.isLiked', '=', isLiked!)) - .where('asset.deletedAt', 'is', null) - .orderBy('activity.createdAt', 'asc') - .execute(); - } - - @GenerateSql({ params: [{ albumId: DummyValue.UUID, userId: DummyValue.UUID }] }) - async create(activity: Insertable) { - return this.db - .insertInto('activity') - .values(activity) - .returningAll() - .returning((eb) => - jsonObjectFrom(eb.selectFrom('user').whereRef('user.id', '=', 'activity.userId').select(columns.user)).as( - 'user', - ), - ) - .$narrowType<{ user: NotNull }>() - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async delete(id: string) { - await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute(); - } - - @GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] }) - async getStatistics({ - albumId, - assetId, - }: { - albumId: string; - assetId?: string; - }): Promise<{ comments: number; likes: number }> { - const result = await this.db - .selectFrom('activity') - .select((eb) => [ - eb.fn.countAll().filterWhere('activity.isLiked', '=', false).as('comments'), - eb.fn.countAll().filterWhere('activity.isLiked', '=', true).as('likes'), - ]) - .innerJoin('user', (join) => join.onRef('user.id', '=', 'activity.userId').on('user.deletedAt', 'is', null)) - .leftJoin('asset', 'asset.id', 'activity.assetId') - .$if(!!assetId, (qb) => qb.where('activity.assetId', '=', assetId!)) - .where('activity.albumId', '=', albumId) - .where(({ or, and, eb }) => - or([ - and([eb('asset.deletedAt', 'is', null), eb('asset.visibility', '!=', sql.lit(AssetVisibility.Locked))]), - eb('asset.id', 'is', null), - ]), - ) - .executeTakeFirstOrThrow(); - - return result; - } -} diff --git a/server/src/repositories/album-user.repository.ts b/server/src/repositories/album-user.repository.ts deleted file mode 100644 index 558a0c05d7..0000000000 --- a/server/src/repositories/album-user.repository.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, Updateable } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AlbumUserRole } from 'src/enum'; -import { DB } from 'src/schema'; -import { AlbumUserTable } from 'src/schema/tables/album-user.table'; - -export type AlbumPermissionId = { - albumId: string; - userId: string; -}; - -@Injectable() -export class AlbumUserRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] }) - create(albumUser: Insertable) { - return this.db - .insertInto('album_user') - .values(albumUser) - .returning(['userId', 'albumId', 'role']) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }, { role: AlbumUserRole.Viewer }] }) - async update({ userId, albumId }: AlbumPermissionId, dto: Updateable) { - await this.db - .updateTable('album_user') - .set(dto) - .where('userId', '=', userId) - .where('albumId', '=', albumId) - .execute(); - } - - @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] }) - async delete({ userId, albumId }: AlbumPermissionId): Promise { - await this.db.deleteFrom('album_user').where('userId', '=', userId).where('albumId', '=', albumId).execute(); - } -} diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts deleted file mode 100644 index 100ab908c0..0000000000 --- a/server/src/repositories/album.repository.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, NotNull, sql, Updateable } from 'kysely'; -import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns, Exif } from 'src/database'; -import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { AlbumUserCreateDto } from 'src/dtos/album.dto'; -import { DB } from 'src/schema'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { withDefaultVisibility } from 'src/utils/database'; - -export interface AlbumAssetCount { - albumId: string; - assetCount: number; - startDate: Date | null; - endDate: Date | null; - lastModifiedAssetTimestamp: Date | null; -} - -export interface AlbumInfoOptions { - withAssets: boolean; -} - -const withOwner = (eb: ExpressionBuilder) => { - return jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album.ownerId')) - .$notNull() - .as('owner'); -}; - -const withAlbumUsers = (eb: ExpressionBuilder) => { - return jsonArrayFrom( - eb - .selectFrom('album_user') - .select('album_user.role') - .select((eb) => - jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'album_user.userId')) - .$notNull() - .as('user'), - ) - .whereRef('album_user.albumId', '=', 'album.id'), - ) - .$notNull() - .as('albumUsers'); -}; - -const withSharedLink = (eb: ExpressionBuilder) => { - return jsonArrayFrom(eb.selectFrom('shared_link').selectAll().whereRef('shared_link.albumId', '=', 'album.id')).as( - 'sharedLinks', - ); -}; - -const withAssets = (eb: ExpressionBuilder) => { - return eb - .selectFrom((eb) => - eb - .selectFrom('asset') - .selectAll('asset') - .leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .select((eb) => eb.table('asset_exif').$castTo().as('exifInfo')) - .innerJoin('album_asset', 'album_asset.assetId', 'asset.id') - .whereRef('album_asset.albumId', '=', 'album.id') - .where('asset.deletedAt', 'is', null) - .$call(withDefaultVisibility) - .orderBy('asset.fileCreatedAt', 'desc') - .as('asset'), - ) - .select((eb) => eb.fn.jsonAgg('asset').as('assets')) - .as('assets'); -}; - -@Injectable() -export class AlbumRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, { withAssets: true }] }) - async getById(id: string, options: AlbumInfoOptions) { - return this.db - .selectFrom('album') - .selectAll('album') - .where('album.id', '=', id) - .where('album.deletedAt', 'is', null) - .select(withOwner) - .select(withAlbumUsers) - .select(withSharedLink) - .$if(options.withAssets, (eb) => eb.select(withAssets)) - .$narrowType<{ assets: NotNull }>() - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - async getByAssetId(ownerId: string, assetId: string) { - return this.db - .selectFrom('album') - .selectAll('album') - .innerJoin('album_asset', 'album_asset.albumId', 'album.id') - .where((eb) => - eb.or([ - eb('album.ownerId', '=', ownerId), - eb.exists( - eb - .selectFrom('album_user') - .whereRef('album_user.albumId', '=', 'album.id') - .where('album_user.userId', '=', ownerId), - ), - ]), - ) - .where('album_asset.assetId', '=', assetId) - .where('album.deletedAt', 'is', null) - .orderBy('album.createdAt', 'desc') - .select(withOwner) - .select(withAlbumUsers) - .orderBy('album.createdAt', 'desc') - .execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - @ChunkedArray() - async getMetadataForIds(ids: string[]): Promise { - // Guard against running invalid query when ids list is empty. - if (ids.length === 0) { - return []; - } - - return ( - this.db - .selectFrom('asset') - .$call(withDefaultVisibility) - .innerJoin('album_asset', 'album_asset.assetId', 'asset.id') - .select('album_asset.albumId as albumId') - .select((eb) => eb.fn.min(sql`("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('startDate')) - .select((eb) => eb.fn.max(sql`("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('endDate')) - // lastModifiedAssetTimestamp is only used in mobile app, please remove if not need - .select((eb) => eb.fn.max('asset.updatedAt').as('lastModifiedAssetTimestamp')) - .select((eb) => sql`${eb.fn.count('asset.id')}::int`.as('assetCount')) - .where('album_asset.albumId', 'in', ids) - .where('asset.deletedAt', 'is', null) - .groupBy('album_asset.albumId') - .execute() - ); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async getOwned(ownerId: string) { - return this.db - .selectFrom('album') - .selectAll('album') - .select(withOwner) - .select(withAlbumUsers) - .select(withSharedLink) - .where('album.ownerId', '=', ownerId) - .where('album.deletedAt', 'is', null) - .orderBy('album.createdAt', 'desc') - .execute(); - } - - /** - * Get albums shared with and shared by owner. - */ - @GenerateSql({ params: [DummyValue.UUID] }) - async getShared(ownerId: string) { - return this.db - .selectFrom('album') - .selectAll('album') - .where((eb) => - eb.or([ - eb.exists( - eb - .selectFrom('album_user') - .whereRef('album_user.albumId', '=', 'album.id') - .where((eb) => eb.or([eb('album.ownerId', '=', ownerId), eb('album_user.userId', '=', ownerId)])), - ), - eb.exists( - eb - .selectFrom('shared_link') - .whereRef('shared_link.albumId', '=', 'album.id') - .where('shared_link.userId', '=', ownerId), - ), - ]), - ) - .where('album.deletedAt', 'is', null) - .select(withAlbumUsers) - .select(withOwner) - .select(withSharedLink) - .orderBy('album.createdAt', 'desc') - .execute(); - } - - /** - * Get albums of owner that are _not_ shared - */ - @GenerateSql({ params: [DummyValue.UUID] }) - async getNotShared(ownerId: string) { - return this.db - .selectFrom('album') - .selectAll('album') - .where('album.ownerId', '=', ownerId) - .where('album.deletedAt', 'is', null) - .where((eb) => eb.not(eb.exists(eb.selectFrom('album_user').whereRef('album_user.albumId', '=', 'album.id')))) - .where((eb) => eb.not(eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id')))) - .select(withOwner) - .orderBy('album.createdAt', 'desc') - .execute(); - } - - async restoreAll(userId: string): Promise { - await this.db.updateTable('album').set({ deletedAt: null }).where('ownerId', '=', userId).execute(); - } - - async softDeleteAll(userId: string): Promise { - await this.db.updateTable('album').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute(); - } - - async deleteAll(userId: string): Promise { - await this.db.deleteFrom('album').where('ownerId', '=', userId).execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - @Chunked() - async removeAssetsFromAll(assetIds: string[]): Promise { - await this.db.deleteFrom('album_asset').where('album_asset.assetId', 'in', assetIds).execute(); - } - - @Chunked({ paramIndex: 1 }) - async removeAssetIds(albumId: string, assetIds: string[]): Promise { - if (assetIds.length === 0) { - return; - } - - await this.db - .deleteFrom('album_asset') - .where('album_asset.albumId', '=', albumId) - .where('album_asset.assetId', 'in', assetIds) - .execute(); - } - - /** - * Get asset IDs for the given album ID. - * - * @param albumId Album ID to get asset IDs for. - * @param assetIds Optional list of asset IDs to filter on. - * @returns Set of Asset IDs for the given album ID. - */ - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - @ChunkedSet({ paramIndex: 1 }) - async getAssetIds(albumId: string, assetIds: string[]): Promise> { - if (assetIds.length === 0) { - return new Set(); - } - - return this.db - .selectFrom('album_asset') - .selectAll() - .where('album_asset.albumId', '=', albumId) - .where('album_asset.assetId', 'in', assetIds) - .execute() - .then((results) => new Set(results.map(({ assetId }) => assetId))); - } - - async addAssetIds(albumId: string, assetIds: string[]): Promise { - await this.addAssets(this.db, albumId, assetIds); - } - - create(album: Insertable, assetIds: string[], albumUsers: AlbumUserCreateDto[]) { - return this.db.transaction().execute(async (tx) => { - const newAlbum = await tx.insertInto('album').values(album).returning('album.id').executeTakeFirst(); - - if (!newAlbum) { - throw new Error('Failed to create album'); - } - - if (assetIds.length > 0) { - await this.addAssets(tx, newAlbum.id, assetIds); - } - - if (albumUsers.length > 0) { - await tx - .insertInto('album_user') - .values( - albumUsers.map((albumUser) => ({ albumId: newAlbum.id, userId: albumUser.userId, role: albumUser.role })), - ) - .execute(); - } - - return tx - .selectFrom('album') - .selectAll() - .where('id', '=', newAlbum.id) - .select(withOwner) - .select(withAssets) - .select(withAlbumUsers) - .$narrowType<{ assets: NotNull }>() - .executeTakeFirstOrThrow(); - }); - } - - update(id: string, album: Updateable) { - return this.db - .updateTable('album') - .set(album) - .where('id', '=', id) - .returningAll('album') - .returning(withOwner) - .returning(withSharedLink) - .returning(withAlbumUsers) - .executeTakeFirstOrThrow(); - } - - async delete(id: string): Promise { - await this.db.deleteFrom('album').where('id', '=', id).execute(); - } - - @Chunked({ paramIndex: 2, chunkSize: 30_000 }) - private async addAssets(db: Kysely, albumId: string, assetIds: string[]): Promise { - if (assetIds.length === 0) { - return; - } - - await db - .insertInto('album_asset') - .values(assetIds.map((assetId) => ({ albumId, assetId }))) - .execute(); - } - - @Chunked({ chunkSize: 30_000 }) - async addAssetIdsToAlbums(values: { albumId: string; assetId: string }[]): Promise { - if (values.length === 0) { - return; - } - await this.db.insertInto('album_asset').values(values).execute(); - } - - /** - * Makes sure all thumbnails for albums are updated by: - * - Removing thumbnails from albums without assets - * - Removing references of thumbnails to assets outside the album - * - Setting a thumbnail when none is set and the album contains assets - * - * @returns Amount of updated album thumbnails or undefined when unknown - */ - async updateThumbnails(): Promise { - // Subquery for getting a new thumbnail. - - const result = await this.db - .updateTable('album') - .set((eb) => ({ - albumThumbnailAssetId: this.updateThumbnailBuilder(eb) - .select('album_asset.assetId') - .orderBy('asset.fileCreatedAt', 'desc') - .limit(1), - })) - .where((eb) => - eb.or([ - eb.and([ - eb('albumThumbnailAssetId', 'is', null), - eb.exists(this.updateThumbnailBuilder(eb).select(sql`1`.as('1'))), // Has assets - ]), - eb.and([ - eb('albumThumbnailAssetId', 'is not', null), - eb.not( - eb.exists( - this.updateThumbnailBuilder(eb) - .select(sql`1`.as('1')) - .whereRef('album.albumThumbnailAssetId', '=', 'album_asset.assetId'), // Has invalid assets - ), - ), - ]), - ]), - ) - .execute(); - - return Number(result[0].numUpdatedRows); - } - - private updateThumbnailBuilder(eb: ExpressionBuilder) { - return eb - .selectFrom('album_asset') - .innerJoin('asset', (join) => - join.onRef('album_asset.assetId', '=', 'asset.id').on('asset.deletedAt', 'is', null), - ) - .whereRef('album_asset.albumId', '=', 'album.id'); - } - - /** - * Get per-user asset contribution counts for a single album. - * Excludes deleted assets, orders by count desc. - */ - @GenerateSql({ params: [DummyValue.UUID] }) - getContributorCounts(id: string) { - return this.db - .selectFrom('album_asset') - .innerJoin('asset', 'asset.id', 'assetId') - .where('asset.deletedAt', 'is', sql.lit(null)) - .where('album_asset.albumId', '=', id) - .select('asset.ownerId as userId') - .select((eb) => eb.fn.countAll().as('assetCount')) - .groupBy('asset.ownerId') - .orderBy('assetCount', 'desc') - .execute(); - } - - @GenerateSql({ params: [{ sourceAssetId: DummyValue.UUID, targetAssetId: DummyValue.UUID }] }) - async copyAlbums({ sourceAssetId, targetAssetId }: { sourceAssetId: string; targetAssetId: string }) { - return this.db - .insertInto('album_asset') - .expression((eb) => - eb - .selectFrom('album_asset') - .select((eb) => ['album_asset.albumId', eb.val(targetAssetId).as('assetId')]) - .where('album_asset.assetId', '=', sourceAssetId), - ) - .onConflict((oc) => oc.doNothing()) - .execute(); - } -} diff --git a/server/src/repositories/api-key.repository.ts b/server/src/repositories/api-key.repository.ts index 28307d7c83..755defa30a 100644 --- a/server/src/repositories/api-key.repository.ts +++ b/server/src/repositories/api-key.repository.ts @@ -1,12 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, Updateable } from 'kysely'; +import { Insertable, Kysely, sql, Updateable } from 'kysely'; import { jsonObjectFrom } from 'kysely/helpers/postgres'; import { InjectKysely } from 'nestjs-kysely'; import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; import { DB } from 'src/schema'; import { ApiKeyTable } from 'src/schema/tables/api-key.table'; -import { asUuid } from 'src/utils/database'; + +const asUuid = (id: string) => sql`${id}::uuid`; @Injectable() export class ApiKeyRepository { diff --git a/server/src/repositories/app.repository.ts b/server/src/repositories/app.repository.ts index 96e413232f..e6181ef7f3 100644 --- a/server/src/repositories/app.repository.ts +++ b/server/src/repositories/app.repository.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { createAdapter } from '@socket.io/redis-adapter'; -import Redis from 'ioredis'; -import { Server as SocketIO } from 'socket.io'; import { ExitCode } from 'src/enum'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { AppRestartEvent } from 'src/repositories/event.repository'; @Injectable() export class AppRepository { @@ -22,26 +17,4 @@ export class AppRepository { setCloseFn(fn: () => Promise) { this.closeFn = fn; } - - async sendOneShotAppRestart(state: AppRestartEvent): Promise { - const server = new SocketIO(); - const { redis } = new ConfigRepository().getEnv(); - const pubClient = new Redis({ ...redis, lazyConnect: true }); - const subClient = pubClient.duplicate(); - - await Promise.all([pubClient.connect(), subClient.connect()]); - - server.adapter(createAdapter(pubClient, subClient)); - - // => corresponds to notification.service.ts#onAppRestart - server.emit('AppRestartV1', state, async () => { - const responses = await server.serverSideEmitWithAck('AppRestart', state); - if (responses.some((response) => response !== 'ok')) { - throw new Error("One or more node(s) returned a non-'ok' response to our restart request!"); - } - - pubClient.disconnect(); - subClient.disconnect(); - }); - } } diff --git a/server/src/repositories/asset-edit.repository.ts b/server/src/repositories/asset-edit.repository.ts deleted file mode 100644 index 088cb1ccff..0000000000 --- a/server/src/repositories/asset-edit.repository.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { DB } from 'src/schema'; - -@Injectable() -export class AssetEditRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ - params: [DummyValue.UUID], - }) - replaceAll(assetId: string, edits: AssetEditActionItem[]): Promise { - return this.db.transaction().execute(async (trx) => { - await trx.deleteFrom('asset_edit').where('assetId', '=', assetId).execute(); - - if (edits.length > 0) { - return trx - .insertInto('asset_edit') - .values(edits.map((edit, i) => ({ assetId, sequence: i, ...edit }))) - .returning(['action', 'parameters']) - .execute() as Promise; - } - - return []; - }); - } - - @GenerateSql({ - params: [DummyValue.UUID], - }) - getAll(assetId: string): Promise { - return this.db - .selectFrom('asset_edit') - .select(['action', 'parameters']) - .where('assetId', '=', assetId) - .orderBy('sequence', 'asc') - .execute() as Promise; - } -} diff --git a/server/src/repositories/asset-job.repository.ts b/server/src/repositories/asset-job.repository.ts deleted file mode 100644 index 4c6e665c4b..0000000000 --- a/server/src/repositories/asset-job.repository.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely, sql } from 'kysely'; -import { jsonArrayFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetFileType, AssetStatus, AssetType, AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { - anyUuid, - asUuid, - toJson, - withDefaultVisibility, - withEdits, - withExif, - withExifInner, - withFaces, - withFilePath, - withFiles, -} from 'src/utils/database'; - -@Injectable() -export class AssetJobRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - getForSearchDuplicatesJob(id: string) { - return this.db - .selectFrom('asset') - .where('asset.id', '=', asUuid(id)) - .leftJoin('smart_search', 'asset.id', 'smart_search.assetId') - .select(['id', 'type', 'ownerId', 'duplicateId', 'stackId', 'visibility', 'smart_search.embedding']) - .limit(1) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForSidecarWriteJob(id: string) { - return this.db - .selectFrom('asset') - .where('asset.id', '=', asUuid(id)) - .select(['id', 'originalPath']) - .select((eb) => withFiles(eb, AssetFileType.Sidecar)) - .$call(withExifInner) - .limit(1) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForSidecarCheckJob(id: string) { - return this.db - .selectFrom('asset') - .where('asset.id', '=', asUuid(id)) - .select(['id', 'originalPath']) - .select((eb) => withFiles(eb, AssetFileType.Sidecar)) - .limit(1) - .executeTakeFirst(); - } - - @GenerateSql({ params: [{ force: false, fullsizeEnabled: true }], stream: true }) - streamForThumbnailJob(options: { force: boolean | undefined; fullsizeEnabled: boolean }) { - return this.db - .selectFrom('asset') - .select(['asset.id', 'asset.thumbhash']) - .select(withFiles) - .select(withEdits) - .where('asset.deletedAt', 'is', null) - .where('asset.visibility', '!=', AssetVisibility.Hidden) - .$if(!options.force, (qb) => - qb - // If there aren't any entries, metadata extraction hasn't run yet which is required for thumbnails - .innerJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id') - .where((eb) => { - const conditions = [ - eb.not((eb) => - eb.exists((qb) => - qb - .selectFrom('asset_file') - .whereRef('assetId', '=', 'asset.id') - .where('asset_file.type', '=', AssetFileType.Preview), - ), - ), - eb.not((eb) => - eb.exists((qb) => - qb - .selectFrom('asset_file') - .whereRef('assetId', '=', 'asset.id') - .where('asset_file.type', '=', AssetFileType.Thumbnail), - ), - ), - ]; - - if (options.fullsizeEnabled) { - conditions.push( - eb.not((eb) => - eb.exists((qb) => - qb - .selectFrom('asset_file') - .whereRef('assetId', '=', 'asset.id') - .where('asset_file.type', '=', AssetFileType.FullSize), - ), - ), - ); - } - - conditions.push(eb('asset.thumbhash', 'is', null)); - - return eb.or(conditions); - }), - ) - .stream(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForMigrationJob(id: string) { - return this.db - .selectFrom('asset') - .select(['asset.id', 'asset.ownerId', 'asset.encodedVideoPath']) - .select(withFiles) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForGenerateThumbnailJob(id: string) { - return this.db - .selectFrom('asset') - .select([ - 'asset.id', - 'asset.visibility', - 'asset.originalFileName', - 'asset.originalPath', - 'asset.ownerId', - 'asset.thumbhash', - 'asset.type', - ]) - .select((eb) => - jsonArrayFrom( - eb - .selectFrom('asset_file') - .select(columns.assetFilesForThumbnail) - .whereRef('asset_file.assetId', '=', 'asset.id') - .where('asset_file.type', 'in', [AssetFileType.Thumbnail, AssetFileType.Preview, AssetFileType.FullSize]), - ).as('files'), - ) - .select(withEdits) - .$call(withExifInner) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForMetadataExtraction(id: string) { - return this.db - .selectFrom('asset') - .select(columns.asset) - .select(withFaces) - .select((eb) => withFiles(eb, AssetFileType.Sidecar)) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async getLockedPropertiesForMetadataExtraction(assetId: string) { - return this.db - .selectFrom('asset_exif') - .select('asset_exif.lockedProperties') - .where('asset_exif.assetId', '=', assetId) - .executeTakeFirst() - .then((row) => row?.lockedProperties ?? []); - } - - @GenerateSql({ params: [DummyValue.UUID, AssetFileType.Thumbnail] }) - getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) { - return this.db - .selectFrom('asset_file') - .select(columns.assetFiles) - .where('asset_file.assetId', '=', id) - .$if(!!fileType, (qb) => qb.where('asset_file.type', '=', fileType!)) - .execute(); - } - - private assetsWithPreviews() { - return this.db - .selectFrom('asset') - .where('asset.visibility', '!=', AssetVisibility.Hidden) - .where('asset.deletedAt', 'is', null) - .innerJoin('asset_job_status as job_status', 'assetId', 'asset.id') - .where((eb) => - eb.exists((qb) => - qb - .selectFrom('asset_file') - .whereRef('assetId', '=', 'asset.id') - .where('asset_file.type', '=', AssetFileType.Preview), - ), - ); - } - - @GenerateSql({ params: [], stream: true }) - streamForSearchDuplicates(force?: boolean) { - return this.db - .selectFrom('asset') - .select(['asset.id']) - .where('asset.deletedAt', 'is', null) - .innerJoin('smart_search', 'asset.id', 'smart_search.assetId') - .$call(withDefaultVisibility) - .$if(!force, (qb) => - qb - .innerJoin('asset_job_status as job_status', 'job_status.assetId', 'asset.id') - .where('job_status.duplicatesDetectedAt', 'is', null), - ) - .stream(); - } - - @GenerateSql({ params: [], stream: true }) - streamForEncodeClip(force?: boolean) { - return this.assetsWithPreviews() - .select(['asset.id']) - .$if(!force, (qb) => - qb.where((eb) => eb.not((eb) => eb.exists(eb.selectFrom('smart_search').whereRef('assetId', '=', 'asset.id')))), - ) - .stream(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForClipEncoding(id: string) { - return this.db - .selectFrom('asset') - .select(['asset.id', 'asset.visibility']) - .select((eb) => withFiles(eb, AssetFileType.Preview)) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForDetectFacesJob(id: string) { - return this.db - .selectFrom('asset') - .select(['asset.id', 'asset.visibility']) - .$call(withExifInner) - .select((eb) => withFaces(eb, true, true)) - .select((eb) => withFiles(eb, AssetFileType.Preview)) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForOcr(id: string) { - return this.db - .selectFrom('asset') - .select((eb) => ['asset.visibility', withFilePath(eb, AssetFileType.Preview).as('previewFile')]) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - getForSyncAssets(ids: string[]) { - return this.db - .selectFrom('asset') - .select([ - 'asset.id', - 'asset.isOffline', - 'asset.libraryId', - 'asset.originalPath', - 'asset.status', - 'asset.fileModifiedAt', - ]) - .where('asset.id', '=', anyUuid(ids)) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForAssetDeletion(id: string) { - return this.db - .selectFrom('asset') - .select([ - 'asset.id', - 'asset.visibility', - 'asset.libraryId', - 'asset.ownerId', - 'asset.livePhotoVideoId', - 'asset.encodedVideoPath', - 'asset.originalPath', - 'asset.isOffline', - ]) - .$call(withExif) - .select(withFiles) - .leftJoinLateral( - (eb) => - eb - .selectFrom('stack') - .whereRef('stack.id', '=', 'asset.stackId') - .select((eb) => [ - 'stack.id', - 'stack.primaryAssetId', - jsonArrayFrom( - eb - .selectFrom('asset as stack_asset') - .select(['stack_asset.id']) - .whereRef('stack_asset.stackId', '=', 'stack.id') - .whereRef('stack_asset.id', '!=', 'stack.primaryAssetId') - .where('stack_asset.visibility', '=', sql.val(AssetVisibility.Timeline)) - .where('stack_asset.status', '!=', sql.val(AssetStatus.Deleted)), - ).as('assets'), - ]) - .as('stack_result'), - (join) => join.onTrue(), - ) - .select((eb) => toJson(eb, 'stack_result').as('stack')) - .where('asset.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [], stream: true }) - streamForVideoConversion(force?: boolean) { - return this.db - .selectFrom('asset') - .select(['asset.id']) - .where('asset.type', '=', AssetType.Video) - .$if(!force, (qb) => - qb - .where((eb) => eb.or([eb('asset.encodedVideoPath', 'is', null), eb('asset.encodedVideoPath', '=', '')])) - .where('asset.visibility', '!=', AssetVisibility.Hidden), - ) - .where('asset.deletedAt', 'is', null) - .stream(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForVideoConversion(id: string) { - return this.db - .selectFrom('asset') - .select(['asset.id', 'asset.ownerId', 'asset.originalPath', 'asset.encodedVideoPath']) - .where('asset.id', '=', id) - .where('asset.type', '=', AssetType.Video) - .executeTakeFirst(); - } - - @GenerateSql({ params: [], stream: true }) - streamForMetadataExtraction(force?: boolean) { - return this.db - .selectFrom('asset') - .select(['asset.id']) - .$if(!force, (qb) => - qb - .leftJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id') - .where((eb) => - eb.or([eb('asset_job_status.metadataExtractedAt', 'is', null), eb('asset_job_status.assetId', 'is', null)]), - ), - ) - .where('asset.deletedAt', 'is', null) - .stream(); - } - - private storageTemplateAssetQuery() { - return this.db - .selectFrom('asset') - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .select([ - 'asset.id', - 'asset.ownerId', - 'asset.type', - 'asset.checksum', - 'asset.originalPath', - 'asset.isExternal', - 'asset.originalFileName', - 'asset.livePhotoVideoId', - 'asset.fileCreatedAt', - 'asset_exif.timeZone', - 'asset_exif.fileSizeInByte', - 'asset_exif.make', - 'asset_exif.model', - 'asset_exif.lensModel', - ]) - .select((eb) => withFiles(eb, AssetFileType.Sidecar)) - .where('asset.deletedAt', 'is', null); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForStorageTemplateJob(id: string) { - return this.storageTemplateAssetQuery().where('asset.id', '=', id).executeTakeFirst(); - } - - @GenerateSql({ params: [], stream: true }) - streamForStorageTemplateJob() { - return this.storageTemplateAssetQuery().stream(); - } - - @GenerateSql({ params: [DummyValue.DATE], stream: true }) - streamForDeletedJob(trashedBefore: Date) { - return this.db - .selectFrom('asset') - .select(['id', 'isOffline']) - .where('asset.deletedAt', '<=', trashedBefore) - .stream(); - } - - @GenerateSql({ params: [], stream: true }) - streamForSidecar(force?: boolean) { - return this.db - .selectFrom('asset') - .select(['asset.id']) - .$if(!force, (qb) => - qb.where((eb) => - eb.not( - eb.exists( - eb - .selectFrom('asset_file') - .select('asset_file.id') - .whereRef('asset_file.assetId', '=', 'asset.id') - .where('asset_file.type', '=', AssetFileType.Sidecar), - ), - ), - ), - ) - .stream(); - } - - @GenerateSql({ params: [], stream: true }) - streamForDetectFacesJob(force?: boolean) { - return this.assetsWithPreviews() - .$if(force === false, (qb) => qb.where('job_status.facesRecognizedAt', 'is', null)) - .select(['asset.id']) - .orderBy('asset.fileCreatedAt', 'desc') - .stream(); - } - - @GenerateSql({ params: [], stream: true }) - streamForOcrJob(force?: boolean) { - return this.db - .selectFrom('asset') - .select(['asset.id']) - .$if(!force, (qb) => - qb - .innerJoin('asset_job_status', 'asset_job_status.assetId', 'asset.id') - .where('asset_job_status.ocrAt', 'is', null), - ) - .where('asset.deletedAt', 'is', null) - .where('asset.visibility', '!=', AssetVisibility.Hidden) - .stream(); - } - - @GenerateSql({ params: [DummyValue.DATE], stream: true }) - streamForMigrationJob() { - return this.db.selectFrom('asset').select(['id']).where('asset.deletedAt', 'is', null).stream(); - } -} diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts deleted file mode 100644 index 1a060c4715..0000000000 --- a/server/src/repositories/asset.repository.ts +++ /dev/null @@ -1,1053 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, NotNull, Selectable, sql, Updateable, UpdateResult } from 'kysely'; -import { isEmpty, isUndefined, omitBy } from 'lodash'; -import { InjectKysely } from 'nestjs-kysely'; -import { LockableProperty, Stack } from 'src/database'; -import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { AssetFileTable } from 'src/schema/tables/asset-file.table'; -import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table'; -import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { - anyUuid, - asUuid, - hasPeople, - removeUndefinedKeys, - truncatedDate, - unnest, - withDefaultVisibility, - withEdits, - withExif, - withFaces, - withFacesAndPeople, - withFiles, - withLibrary, - withOwner, - withSmartSearch, - withTagId, - withTags, -} from 'src/utils/database'; -import { globToSqlPattern } from 'src/utils/misc'; - -export type AssetStats = Record; - -interface AssetStatsOptions { - isFavorite?: boolean; - isTrashed?: boolean; - visibility?: AssetVisibility; -} - -interface LivePhotoSearchOptions { - ownerId: string; - libraryId?: string | null; - livePhotoCID: string; - otherAssetId: string; - type: AssetType; -} - -interface AssetBuilderOptions { - isFavorite?: boolean; - isTrashed?: boolean; - isDuplicate?: boolean; - albumId?: string; - tagId?: string; - personId?: string; - userIds?: string[]; - withStacked?: boolean; - exifInfo?: boolean; - status?: AssetStatus; - assetType?: AssetType; - visibility?: AssetVisibility; - withCoordinates?: boolean; -} - -export interface TimeBucketOptions extends AssetBuilderOptions { - order?: AssetOrder; -} - -export interface TimeBucketItem { - timeBucket: string; - count: number; -} - -export interface YearMonthDay { - day: number; - month: number; - year: number; -} - -interface AssetExploreFieldOptions { - maxFields: number; - minAssetsPerField: number; -} - -interface AssetFullSyncOptions { - ownerId: string; - lastId?: string; - updatedUntil: Date; - limit: number; -} - -interface AssetDeltaSyncOptions { - userIds: string[]; - updatedAfter: Date; - limit: number; -} - -interface AssetGetByChecksumOptions { - ownerId: string; - checksum: Buffer; - libraryId?: string; -} - -interface GetByIdsRelations { - exifInfo?: boolean; - faces?: { person?: boolean; withDeleted?: boolean }; - files?: boolean; - library?: boolean; - owner?: boolean; - smartSearch?: boolean; - stack?: { assets?: boolean }; - tags?: boolean; - edits?: boolean; -} - -const distinctLocked = (eb: ExpressionBuilder, columns: T) => - sql`nullif(array(select distinct unnest(${eb.ref('asset_exif.lockedProperties')} || ${columns})), '{}')`; - -@Injectable() -export class AssetRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ - params: [ - { dateTimeOriginal: DummyValue.DATE, lockedProperties: ['dateTimeOriginal'] }, - { lockedPropertiesBehavior: 'append' }, - ], - }) - async upsertExif( - exif: Insertable, - { lockedPropertiesBehavior }: { lockedPropertiesBehavior: 'override' | 'append' | 'skip' }, - ): Promise { - await this.db - .insertInto('asset_exif') - .values(exif) - .onConflict((oc) => - oc.column('assetId').doUpdateSet((eb) => { - const updateLocked = (col: T) => eb.ref(`excluded.${col}`); - const skipLocked = (col: T) => - eb - .case() - .when(sql`${col}`, '=', eb.fn.any('asset_exif.lockedProperties')) - .then(eb.ref(`asset_exif.${col}`)) - .else(eb.ref(`excluded.${col}`)) - .end(); - const ref = lockedPropertiesBehavior === 'skip' ? skipLocked : updateLocked; - return { - ...removeUndefinedKeys( - { - description: ref('description'), - exifImageWidth: ref('exifImageWidth'), - exifImageHeight: ref('exifImageHeight'), - fileSizeInByte: ref('fileSizeInByte'), - orientation: ref('orientation'), - dateTimeOriginal: ref('dateTimeOriginal'), - modifyDate: ref('modifyDate'), - timeZone: ref('timeZone'), - latitude: ref('latitude'), - longitude: ref('longitude'), - projectionType: ref('projectionType'), - city: ref('city'), - livePhotoCID: ref('livePhotoCID'), - autoStackId: ref('autoStackId'), - state: ref('state'), - country: ref('country'), - make: ref('make'), - model: ref('model'), - lensModel: ref('lensModel'), - fNumber: ref('fNumber'), - focalLength: ref('focalLength'), - iso: ref('iso'), - exposureTime: ref('exposureTime'), - profileDescription: ref('profileDescription'), - colorspace: ref('colorspace'), - bitsPerSample: ref('bitsPerSample'), - rating: ref('rating'), - fps: ref('fps'), - tags: ref('tags'), - lockedProperties: - lockedPropertiesBehavior === 'append' - ? distinctLocked(eb, exif.lockedProperties ?? null) - : ref('lockedProperties'), - }, - exif, - ), - }; - }), - ) - .execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID], { model: DummyValue.STRING }] }) - @Chunked() - async updateAllExif(ids: string[], options: Updateable): Promise { - if (ids.length === 0) { - return; - } - - await this.db - .updateTable('asset_exif') - .set((eb) => ({ - ...options, - lockedProperties: distinctLocked(eb, Object.keys(options) as LockableProperty[]), - })) - .where('assetId', 'in', ids) - .execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.NUMBER, DummyValue.STRING] }) - @Chunked() - updateDateTimeOriginal(ids: string[], delta?: number, timeZone?: string) { - return this.db - .updateTable('asset_exif') - .set((eb) => ({ - dateTimeOriginal: sql`"dateTimeOriginal" + ${(delta ?? 0) + ' minute'}::interval`, - timeZone, - lockedProperties: distinctLocked(eb, ['dateTimeOriginal', 'timeZone']), - })) - .where('assetId', 'in', ids) - .returning(['assetId', 'dateTimeOriginal', 'timeZone']) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, ['description']] }) - unlockProperties(assetId: string, properties: LockableProperty[]) { - return this.db - .updateTable('asset_exif') - .where('assetId', '=', assetId) - .set((eb) => ({ - lockedProperties: sql`nullif(array(select distinct property from unnest(${eb.ref('asset_exif.lockedProperties')}) property where not property = any(${properties})), '{}')`, - })) - .execute(); - } - - async upsertJobStatus(...jobStatus: Insertable[]): Promise { - if (jobStatus.length === 0) { - return; - } - - const values = jobStatus.map((row) => ({ ...row, assetId: asUuid(row.assetId) })); - await this.db - .insertInto('asset_job_status') - .values(values) - .onConflict((oc) => - oc.column('assetId').doUpdateSet((eb) => - removeUndefinedKeys( - { - duplicatesDetectedAt: eb.ref('excluded.duplicatesDetectedAt'), - facesRecognizedAt: eb.ref('excluded.facesRecognizedAt'), - metadataExtractedAt: eb.ref('excluded.metadataExtractedAt'), - ocrAt: eb.ref('excluded.ocrAt'), - }, - values[0], - ), - ), - ) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getMetadata(assetId: string) { - return this.db - .selectFrom('asset_metadata') - .select(['key', 'value', 'updatedAt']) - .where('assetId', '=', assetId) - .execute(); - } - - upsertMetadata(id: string, items: Array<{ key: string; value: object }>) { - if (items.length === 0) { - return []; - } - - return this.db - .insertInto('asset_metadata') - .values(items.map((item) => ({ assetId: id, ...item }))) - .onConflict((oc) => - oc - .columns(['assetId', 'key']) - .doUpdateSet((eb) => ({ key: eb.ref('excluded.key'), value: eb.ref('excluded.value') })), - ) - .returning(['key', 'value', 'updatedAt']) - .execute(); - } - - upsertBulkMetadata(items: Insertable[]) { - return this.db - .insertInto('asset_metadata') - .values(items) - .onConflict((oc) => - oc - .columns(['assetId', 'key']) - .doUpdateSet((eb) => ({ key: eb.ref('excluded.key'), value: eb.ref('excluded.value') })), - ) - .returning(['assetId', 'key', 'value', 'updatedAt']) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - getMetadataByKey(assetId: string, key: string) { - return this.db - .selectFrom('asset_metadata') - .select(['key', 'value', 'updatedAt']) - .where('assetId', '=', assetId) - .where('key', '=', key) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - async deleteMetadataByKey(id: string, key: string) { - await this.db.deleteFrom('asset_metadata').where('assetId', '=', id).where('key', '=', key).execute(); - } - - @GenerateSql({ params: [[{ assetId: DummyValue.UUID, key: DummyValue.STRING }]] }) - async deleteBulkMetadata(items: Array<{ assetId: string; key: string }>) { - if (items.length === 0) { - return; - } - - await this.db.transaction().execute(async (tx) => { - for (const { assetId, key } of items) { - await tx.deleteFrom('asset_metadata').where('assetId', '=', assetId).where('key', '=', key).execute(); - } - }); - } - - create(asset: Insertable) { - return this.db.insertInto('asset').values(asset).returningAll().executeTakeFirstOrThrow(); - } - - createAll(assets: Insertable[]) { - return this.db.insertInto('asset').values(assets).returningAll().execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, { year: 2000, day: 1, month: 1 }] }) - getByDayOfYear(ownerIds: string[], { year, day, month }: YearMonthDay) { - return this.db - .with('res', (qb) => - qb - .with('today', (qb) => - qb - .selectFrom((eb) => - eb - .fn('generate_series', [ - sql`(select date_part('year', min(("localDateTime" at time zone 'UTC')::date))::int from asset)`, - sql`${year - 1}`, - ]) - .as('year'), - ) - .select((eb) => eb.fn('make_date', [sql`year::int`, sql`${month}::int`, sql`${day}::int`]).as('date')), - ) - .selectFrom('today') - .innerJoinLateral( - (qb) => - qb - .selectFrom('asset') - .selectAll('asset') - .innerJoin('asset_job_status', 'asset.id', 'asset_job_status.assetId') - .where(sql`(asset."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`) - .where('asset.ownerId', '=', anyUuid(ownerIds)) - .where('asset.visibility', '=', AssetVisibility.Timeline) - .where((eb) => - eb.exists((qb) => - qb - .selectFrom('asset_file') - .whereRef('assetId', '=', 'asset.id') - .where('asset_file.type', '=', AssetFileType.Preview), - ), - ) - .where('asset.deletedAt', 'is', null) - .orderBy(sql`(asset."localDateTime" at time zone 'UTC')::date`, 'desc') - .limit(20) - .as('a'), - (join) => join.onTrue(), - ) - .innerJoin('asset_exif', 'a.id', 'asset_exif.assetId') - .selectAll('a') - .select((eb) => eb.fn.toJson(eb.table('asset_exif')).as('exifInfo')), - ) - .selectFrom('res') - .select(sql`date_part('year', ("localDateTime" at time zone 'UTC')::date)::int`.as('year')) - .select((eb) => eb.fn.jsonAgg(eb.table('res')).as('assets')) - .groupBy(sql`("localDateTime" at time zone 'UTC')::date`) - .orderBy(sql`("localDateTime" at time zone 'UTC')::date`, 'desc') - .execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - @ChunkedArray() - getByIds(ids: string[]) { - return this.db.selectFrom('asset').selectAll('asset').where('asset.id', '=', anyUuid(ids)).execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - @ChunkedArray() - getByIdsWithAllRelationsButStacks(ids: string[]) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .select(withFacesAndPeople) - .select(withTags) - .$call(withExif) - .where('asset.id', '=', anyUuid(ids)) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async deleteAll(ownerId: string): Promise { - await this.db.deleteFrom('asset').where('ownerId', '=', ownerId).execute(); - } - - async getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise { - const assets = await this.db - .selectFrom('asset') - .select(['deviceAssetId']) - .where('deviceAssetId', 'in', deviceAssetIds) - .where('deviceId', '=', deviceId) - .where('ownerId', '=', asUuid(ownerId)) - .execute(); - - return assets.map((asset) => asset.deviceAssetId); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .where('libraryId', '=', asUuid(libraryId)) - .where('originalPath', '=', originalPath) - .limit(1) - .executeTakeFirst(); - } - - /** - * Get assets by device's Id on the database - * @param ownerId - * @param deviceId - * - * @returns Promise - Array of assetIds belong to the device - */ - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - async getAllByDeviceId(ownerId: string, deviceId: string): Promise { - const items = await this.db - .selectFrom('asset') - .select(['deviceAssetId']) - .where('ownerId', '=', asUuid(ownerId)) - .where('deviceId', '=', deviceId) - .where('visibility', '!=', AssetVisibility.Hidden) - .where('deletedAt', 'is', null) - .execute(); - - return items.map((asset) => asset.deviceAssetId); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async getLivePhotoCount(motionId: string): Promise { - const [{ count }] = await this.db - .selectFrom('asset') - .select((eb) => eb.fn.countAll().as('count')) - .where('livePhotoVideoId', '=', asUuid(motionId)) - .execute(); - return count; - } - - @GenerateSql() - getFileSamples() { - return this.db.selectFrom('asset_file').select(['assetId', 'path']).limit(sql.lit(3)).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForCopy(id: string) { - return this.db - .selectFrom('asset') - .select(['id', 'stackId', 'originalPath', 'isFavorite']) - .select(withFiles) - .where('id', '=', asUuid(id)) - .limit(1) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getById( - id: string, - { exifInfo, faces, files, library, owner, smartSearch, stack, tags, edits }: GetByIdsRelations = {}, - ) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .where('asset.id', '=', asUuid(id)) - .$if(!!exifInfo, withExif) - .$if(!!faces, (qb) => qb.select(faces?.person ? withFacesAndPeople : withFaces).$narrowType<{ faces: NotNull }>()) - .$if(!!library, (qb) => qb.select(withLibrary)) - .$if(!!owner, (qb) => qb.select(withOwner)) - .$if(!!smartSearch, withSmartSearch) - .$if(!!stack, (qb) => - qb - .leftJoin('stack', 'stack.id', 'asset.stackId') - .$if(!stack!.assets, (qb) => - qb.select((eb) => eb.fn.toJson(eb.table('stack')).$castTo().as('stack')), - ) - .$if(!!stack!.assets, (qb) => - qb - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset as stacked') - .selectAll('stack') - .select((eb) => eb.fn('array_agg', [eb.table('stacked')]).as('assets')) - .whereRef('stacked.stackId', '=', 'stack.id') - .whereRef('stacked.id', '!=', 'stack.primaryAssetId') - .where('stacked.deletedAt', 'is', null) - .where('stacked.visibility', '=', AssetVisibility.Timeline) - .groupBy('stack.id') - .as('stacked_assets'), - (join) => join.on('stack.id', 'is not', null), - ) - .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo().as('stack')), - ), - ) - .$if(!!files, (qb) => qb.select(withFiles)) - .$if(!!tags, (qb) => qb.select(withTags)) - .$if(!!edits, (qb) => qb.select(withEdits)) - .limit(1) - .executeTakeFirst(); - } - - @GenerateSql({ params: [[DummyValue.UUID], { deviceId: DummyValue.STRING }] }) - @Chunked() - async updateAll(ids: string[], options: Updateable): Promise { - if (ids.length === 0) { - return; - } - await this.db.updateTable('asset').set(options).where('id', '=', anyUuid(ids)).execute(); - } - - async updateByLibraryId(libraryId: string, options: Updateable): Promise { - await this.db.updateTable('asset').set(options).where('libraryId', '=', asUuid(libraryId)).execute(); - } - - async update(asset: Updateable & { id: string }) { - const value = omitBy(asset, isUndefined); - delete value.id; - if (!isEmpty(value)) { - return this.db - .with('asset', (qb) => qb.updateTable('asset').set(asset).where('id', '=', asUuid(asset.id)).returningAll()) - .selectFrom('asset') - .selectAll('asset') - .$call(withExif) - .$call((qb) => qb.select(withFacesAndPeople)) - .$call((qb) => qb.select(withEdits)) - .executeTakeFirst(); - } - - return this.getById(asset.id, { exifInfo: true, faces: { person: true }, edits: true }); - } - - async remove(asset: { id: string }): Promise { - await this.db.deleteFrom('asset').where('id', '=', asUuid(asset.id)).execute(); - } - - @GenerateSql({ params: [{ ownerId: DummyValue.UUID, libraryId: DummyValue.UUID, checksum: DummyValue.BUFFER }] }) - getByChecksum({ ownerId, libraryId, checksum }: AssetGetByChecksumOptions) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .where('ownerId', '=', asUuid(ownerId)) - .where('checksum', '=', checksum) - .$call((qb) => (libraryId ? qb.where('libraryId', '=', asUuid(libraryId)) : qb.where('libraryId', 'is', null))) - .limit(1) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.BUFFER]] }) - getByChecksums(userId: string, checksums: Buffer[]) { - return this.db - .selectFrom('asset') - .select(['id', 'checksum', 'deletedAt']) - .where('ownerId', '=', asUuid(userId)) - .where('checksum', 'in', checksums) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.BUFFER] }) - async getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise { - const asset = await this.db - .selectFrom('asset') - .select('id') - .where('ownerId', '=', asUuid(ownerId)) - .where('checksum', '=', checksum) - .where('libraryId', 'is', null) - .limit(1) - .executeTakeFirst(); - - return asset?.id; - } - - findLivePhotoMatch(options: LivePhotoSearchOptions) { - const { ownerId, otherAssetId, livePhotoCID, type } = options; - return this.db - .selectFrom('asset') - .select(['asset.id', 'asset.ownerId']) - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('id', '!=', asUuid(otherAssetId)) - .where('ownerId', '=', asUuid(ownerId)) - .where('type', '=', type) - .where('asset_exif.livePhotoCID', '=', livePhotoCID) - .limit(1) - .executeTakeFirst(); - } - - getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise { - return this.db - .selectFrom('asset') - .select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.Audio).as(AssetType.Audio)) - .select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.Image).as(AssetType.Image)) - .select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.Video).as(AssetType.Video)) - .select((eb) => eb.fn.countAll().filterWhere('type', '=', AssetType.Other).as(AssetType.Other)) - .where('ownerId', '=', asUuid(ownerId)) - .$if(visibility === undefined, withDefaultVisibility) - .$if(!!visibility, (qb) => qb.where('asset.visibility', '=', visibility!)) - .$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!)) - .$if(!!isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted)) - .where('deletedAt', isTrashed ? 'is not' : 'is', null) - .executeTakeFirstOrThrow(); - } - - getRandom(userIds: string[], take: number) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .$call(withExif) - .$call(withDefaultVisibility) - .where('ownerId', '=', anyUuid(userIds)) - .where('deletedAt', 'is', null) - .orderBy((eb) => eb.fn('random')) - .limit(take) - .execute(); - } - - @GenerateSql({ params: [{}] }) - async getTimeBuckets(options: TimeBucketOptions): Promise { - return this.db - .with('asset', (qb) => - qb - .selectFrom('asset') - .select(truncatedDate().as('timeBucket')) - .$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted)) - .where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null) - .$if(options.visibility === undefined, withDefaultVisibility) - .$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!)) - .$if(!!options.albumId, (qb) => - qb - .innerJoin('album_asset', 'asset.id', 'album_asset.assetId') - .where('album_asset.albumId', '=', asUuid(options.albumId!)), - ) - .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!])) - .$if(!!options.withStacked, (qb) => - qb - .leftJoin('stack', (join) => - join.onRef('stack.id', '=', 'asset.stackId').onRef('stack.primaryAssetId', '=', 'asset.id'), - ) - .where((eb) => eb.or([eb('asset.stackId', 'is', null), eb(eb.table('stack'), 'is not', null)])), - ) - .$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!))) - .$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!)) - .$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!)) - .$if(options.isDuplicate !== undefined, (qb) => - qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null), - ) - .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)), - ) - .selectFrom('asset') - .select(sql`("timeBucket" AT TIME ZONE 'UTC')::date::text`.as('timeBucket')) - .select((eb) => eb.fn.countAll().as('count')) - .groupBy('timeBucket') - .orderBy('timeBucket', options.order ?? 'desc') - .execute() as any as Promise; - } - - @GenerateSql({ - params: [DummyValue.TIME_BUCKET, { withStacked: true }, { user: { id: DummyValue.UUID } }], - }) - getTimeBucket(timeBucket: string, options: TimeBucketOptions, auth: AuthDto) { - const query = this.db - .with('cte', (qb) => - qb - .selectFrom('asset') - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .select((eb) => [ - 'asset.duration', - 'asset.id', - 'asset.visibility', - sql`asset."isFavorite" and asset."ownerId" = ${auth.user.id}`.as('isFavorite'), - sql`asset.type = 'IMAGE'`.as('isImage'), - sql`asset."deletedAt" is not null`.as('isTrashed'), - 'asset.livePhotoVideoId', - sql`extract(epoch from (asset."localDateTime" AT TIME ZONE 'UTC' - asset."fileCreatedAt" at time zone 'UTC'))::real / 3600`.as( - 'localOffsetHours', - ), - 'asset.ownerId', - 'asset.status', - sql`asset."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'), - eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'), - 'asset_exif.city', - 'asset_exif.country', - 'asset_exif.projectionType', - eb.fn - .coalesce( - eb - .case() - .when(sql`asset."height" = 0 or asset."width" = 0`) - .then(eb.lit(1)) - .else(sql`round(asset."width"::numeric / asset."height"::numeric, 3)`) - .end(), - eb.lit(1), - ) - .as('ratio'), - ]) - .$if(!!options.withCoordinates, (qb) => qb.select(['asset_exif.latitude', 'asset_exif.longitude'])) - .where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null) - .$if(options.visibility == undefined, withDefaultVisibility) - .$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!)) - .where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, '')) - .$if(!!options.albumId, (qb) => - qb.where((eb) => - eb.exists( - eb - .selectFrom('album_asset') - .whereRef('album_asset.assetId', '=', 'asset.id') - .where('album_asset.albumId', '=', asUuid(options.albumId!)), - ), - ), - ) - .$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!])) - .$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!))) - .$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!)) - .$if(!!options.withStacked, (qb) => - qb - .where((eb) => - eb.not( - eb.exists( - eb - .selectFrom('stack') - .whereRef('stack.id', '=', 'asset.stackId') - .whereRef('stack.primaryAssetId', '!=', 'asset.id'), - ), - ), - ) - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset as stacked') - .select(sql`array[stacked."stackId"::text, count('stacked')::text]`.as('stack')) - .whereRef('stacked.stackId', '=', 'asset.stackId') - .where('stacked.deletedAt', 'is', null) - .where('stacked.visibility', '=', AssetVisibility.Timeline) - .groupBy('stacked.stackId') - .as('stacked_assets'), - (join) => join.onTrue(), - ) - .select('stack'), - ) - .$if(!!options.assetType, (qb) => qb.where('asset.type', '=', options.assetType!)) - .$if(options.isDuplicate !== undefined, (qb) => - qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null), - ) - .$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted)) - .$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)) - .orderBy('asset.fileCreatedAt', options.order ?? 'desc'), - ) - .with('agg', (qb) => - qb - .selectFrom('cte') - .select((eb) => [ - eb.fn.coalesce(eb.fn('array_agg', ['city']), sql.lit('{}')).as('city'), - eb.fn.coalesce(eb.fn('array_agg', ['country']), sql.lit('{}')).as('country'), - eb.fn.coalesce(eb.fn('array_agg', ['duration']), sql.lit('{}')).as('duration'), - eb.fn.coalesce(eb.fn('array_agg', ['id']), sql.lit('{}')).as('id'), - eb.fn.coalesce(eb.fn('array_agg', ['visibility']), sql.lit('{}')).as('visibility'), - eb.fn.coalesce(eb.fn('array_agg', ['isFavorite']), sql.lit('{}')).as('isFavorite'), - eb.fn.coalesce(eb.fn('array_agg', ['isImage']), sql.lit('{}')).as('isImage'), - // TODO: isTrashed is redundant as it will always be all true or false depending on the options - eb.fn.coalesce(eb.fn('array_agg', ['isTrashed']), sql.lit('{}')).as('isTrashed'), - eb.fn.coalesce(eb.fn('array_agg', ['livePhotoVideoId']), sql.lit('{}')).as('livePhotoVideoId'), - eb.fn.coalesce(eb.fn('array_agg', ['fileCreatedAt']), sql.lit('{}')).as('fileCreatedAt'), - eb.fn.coalesce(eb.fn('array_agg', ['localOffsetHours']), sql.lit('{}')).as('localOffsetHours'), - eb.fn.coalesce(eb.fn('array_agg', ['ownerId']), sql.lit('{}')).as('ownerId'), - eb.fn.coalesce(eb.fn('array_agg', ['projectionType']), sql.lit('{}')).as('projectionType'), - eb.fn.coalesce(eb.fn('array_agg', ['ratio']), sql.lit('{}')).as('ratio'), - eb.fn.coalesce(eb.fn('array_agg', ['status']), sql.lit('{}')).as('status'), - eb.fn.coalesce(eb.fn('array_agg', ['thumbhash']), sql.lit('{}')).as('thumbhash'), - ]) - .$if(!!options.withCoordinates, (qb) => - qb.select((eb) => [ - eb.fn.coalesce(eb.fn('array_agg', ['latitude']), sql.lit('{}')).as('latitude'), - eb.fn.coalesce(eb.fn('array_agg', ['longitude']), sql.lit('{}')).as('longitude'), - ]), - ) - .$if(!!options.withStacked, (qb) => - qb.select((eb) => eb.fn.coalesce(eb.fn('json_agg', ['stack']), sql.lit('[]')).as('stack')), - ), - ) - .selectFrom('agg') - .select(sql`to_json(agg)::text`.as('assets')); - - return query.executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID, { minAssetsPerField: 5, maxFields: 12 }] }) - async getAssetIdByCity(ownerId: string, { minAssetsPerField, maxFields }: AssetExploreFieldOptions) { - const items = await this.db - .with('cities', (qb) => - qb - .selectFrom('asset_exif') - .select('city') - .where('city', 'is not', null) - .groupBy('city') - .having((eb) => eb.fn('count', [eb.ref('assetId')]), '>=', minAssetsPerField), - ) - .selectFrom('asset') - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .innerJoin('cities', 'asset_exif.city', 'cities.city') - .distinctOn('asset_exif.city') - .select(['assetId as data', 'asset_exif.city as value']) - .$narrowType<{ value: NotNull }>() - .where('ownerId', '=', asUuid(ownerId)) - .where('visibility', '=', AssetVisibility.Timeline) - .where('type', '=', AssetType.Image) - .where('deletedAt', 'is', null) - .limit(maxFields) - .execute(); - - return { fieldName: 'exifInfo.city', items }; - } - - @GenerateSql({ - params: [ - { - ownerId: DummyValue.UUID, - lastId: DummyValue.UUID, - updatedUntil: DummyValue.DATE, - limit: 10, - }, - ], - }) - getAllForUserFullSync(options: AssetFullSyncOptions) { - const { ownerId, lastId, updatedUntil, limit } = options; - return this.db - .selectFrom('asset') - .selectAll('asset') - .$call(withExif) - .leftJoin('stack', 'stack.id', 'asset.stackId') - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset as stacked') - .selectAll('stack') - .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount')) - .whereRef('stacked.stackId', '=', 'stack.id') - .groupBy('stack.id') - .as('stacked_assets'), - (join) => join.on('stack.id', 'is not', null), - ) - .select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo().as('stack')) - .where('asset.ownerId', '=', asUuid(ownerId)) - .where('asset.visibility', '!=', AssetVisibility.Hidden) - .where('asset.updatedAt', '<=', updatedUntil) - .$if(!!lastId, (qb) => qb.where('asset.id', '>', lastId!)) - .orderBy('asset.id') - .limit(limit) - .execute(); - } - - @GenerateSql({ params: [{ userIds: [DummyValue.UUID], updatedAfter: DummyValue.DATE, limit: 100 }] }) - async getChangedDeltaSync(options: AssetDeltaSyncOptions) { - return this.db - .selectFrom('asset') - .selectAll('asset') - .$call(withExif) - .leftJoin('stack', 'stack.id', 'asset.stackId') - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset as stacked') - .selectAll('stack') - .select((eb) => eb.fn.count(eb.table('stacked')).as('assetCount')) - .whereRef('stacked.stackId', '=', 'stack.id') - .groupBy('stack.id') - .as('stacked_assets'), - (join) => join.on('stack.id', 'is not', null), - ) - .select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo()).as('stack')) - .where('asset.ownerId', '=', anyUuid(options.userIds)) - .where('asset.visibility', '!=', AssetVisibility.Hidden) - .where('asset.updatedAt', '>', options.updatedAfter) - .limit(options.limit) - .execute(); - } - - async upsertFile( - file: Pick, 'assetId' | 'path' | 'type' | 'isEdited' | 'isProgressive'>, - ): Promise { - await this.db - .insertInto('asset_file') - .values(file) - .onConflict((oc) => - oc.columns(['assetId', 'type', 'isEdited']).doUpdateSet((eb) => ({ - path: eb.ref('excluded.path'), - })), - ) - .execute(); - } - - async upsertFiles( - files: Pick, 'assetId' | 'path' | 'type' | 'isEdited' | 'isProgressive'>[], - ): Promise { - if (files.length === 0) { - return; - } - - await this.db - .insertInto('asset_file') - .values(files) - .onConflict((oc) => - oc.columns(['assetId', 'type', 'isEdited']).doUpdateSet((eb) => ({ - path: eb.ref('excluded.path'), - isProgressive: eb.ref('excluded.isProgressive'), - })), - ) - .execute(); - } - - async deleteFile({ assetId, type }: { assetId: string; type: AssetFileType }): Promise { - await this.db.deleteFrom('asset_file').where('assetId', '=', asUuid(assetId)).where('type', '=', type).execute(); - } - - async deleteFiles(files: Pick, 'id'>[]): Promise { - if (files.length === 0) { - return; - } - - await this.db - .deleteFrom('asset_file') - .where('id', '=', anyUuid(files.map((file) => file.id))) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.STRING], [DummyValue.STRING]] }) - async detectOfflineExternalAssets( - libraryId: string, - importPaths: string[], - exclusionPatterns: string[], - ): Promise { - const paths = importPaths.map((importPath) => `${importPath}%`); - const exclusions = exclusionPatterns.map((pattern) => globToSqlPattern(pattern)); - - return this.db - .updateTable('asset') - .set({ - isOffline: true, - deletedAt: new Date(), - }) - .where('isOffline', '=', false) - .where('isExternal', '=', true) - .where('libraryId', '=', asUuid(libraryId)) - .where((eb) => - eb.or([ - eb.not(eb.or(paths.map((path) => eb('originalPath', 'like', path)))), - eb.or(exclusions.map((path) => eb('originalPath', 'like', path))), - ]), - ) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.STRING]] }) - async filterNewExternalAssetPaths(libraryId: string, paths: string[]): Promise { - const result = await this.db - .selectFrom(unnest(paths).as('path')) - .select('path') - .where((eb) => - eb.not( - eb.exists( - this.db - .selectFrom('asset') - .select('originalPath') - .whereRef('asset.originalPath', '=', eb.ref('path')) - .where('libraryId', '=', asUuid(libraryId)) - .where('isExternal', '=', true), - ), - ), - ) - .execute(); - - return result.map((row) => row.path as string); - } - - async getLibraryAssetCount(libraryId: string): Promise { - const { count } = await this.db - .selectFrom('asset') - .select((eb) => eb.fn.countAll().as('count')) - .where('libraryId', '=', asUuid(libraryId)) - .executeTakeFirstOrThrow(); - - return count; - } - - @GenerateSql({ params: [DummyValue.UUID, true] }) - async getForOriginal(id: string, isEdited: boolean) { - return this.db - .selectFrom('asset') - .select('originalFileName') - .where('asset.id', '=', id) - .$if(isEdited, (qb) => - qb - .leftJoin('asset_file', (join) => - join - .onRef('asset.id', '=', 'asset_file.assetId') - .on('asset_file.isEdited', '=', true) - .on('asset_file.type', '=', AssetFileType.FullSize), - ) - .select('asset_file.path as editedPath'), - ) - .select('originalPath') - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID, AssetFileType.Preview, true] }) - async getForThumbnail(id: string, type: AssetFileType, isEdited: boolean) { - return this.db - .selectFrom('asset') - .where('asset.id', '=', id) - .leftJoin('asset_file', (join) => - join.onRef('asset.id', '=', 'asset_file.assetId').on('asset_file.type', '=', type), - ) - .select(['asset.originalPath', 'asset.originalFileName', 'asset_file.path as path']) - .orderBy('asset_file.isEdited', isEdited ? 'desc' : 'asc') - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async getForVideo(id: string) { - return this.db - .selectFrom('asset') - .select(['asset.encodedVideoPath', 'asset.originalPath']) - .where('asset.id', '=', id) - .where('asset.type', '=', AssetType.Video) - .executeTakeFirst(); - } -} diff --git a/server/src/repositories/audit.repository.ts b/server/src/repositories/audit.repository.ts deleted file mode 100644 index 2d56eddc9a..0000000000 --- a/server/src/repositories/audit.repository.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { DatabaseAction, EntityType } from 'src/enum'; -import { DB } from 'src/schema'; - -export interface AuditSearch { - action?: DatabaseAction; - entityType?: EntityType; - userIds: string[]; -} - -@Injectable() -export class AuditRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ - params: [ - DummyValue.DATE, - { action: DatabaseAction.Create, entityType: EntityType.Asset, userIds: [DummyValue.UUID] }, - ], - }) - async getAfter(since: Date, options: AuditSearch): Promise { - const records = await this.db - .selectFrom('audit') - .where('audit.createdAt', '>', since) - .$if(!!options.action, (qb) => qb.where('audit.action', '=', options.action!)) - .$if(!!options.entityType, (qb) => qb.where('audit.entityType', '=', options.entityType!)) - .where('audit.ownerId', 'in', options.userIds) - .distinctOn(['audit.entityId', 'audit.entityType']) - .orderBy('audit.entityId', 'desc') - .orderBy('audit.entityType', 'desc') - .orderBy('audit.createdAt', 'desc') - .select('audit.entityId') - .execute(); - - return records.map(({ entityId }) => entityId); - } - - async removeBefore(before: Date): Promise { - await this.db.deleteFrom('audit').where('createdAt', '<', before).execute(); - } -} diff --git a/server/src/repositories/config.repository.spec.ts b/server/src/repositories/config.repository.spec.ts deleted file mode 100644 index a3dc8ba5cb..0000000000 --- a/server/src/repositories/config.repository.spec.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { ImmichTelemetry } from 'src/enum'; -import { clearEnvCache, ConfigRepository } from 'src/repositories/config.repository'; - -const getEnv = () => { - clearEnvCache(); - return new ConfigRepository().getEnv(); -}; - -const resetEnv = () => { - for (const env of [ - 'IMMICH_ALLOW_EXTERNAL_PLUGINS', - 'IMMICH_ALLOW_SETUP', - 'IMMICH_ENV', - 'IMMICH_WORKERS_INCLUDE', - 'IMMICH_WORKERS_EXCLUDE', - 'IMMICH_TRUSTED_PROXIES', - 'IMMICH_API_METRICS_PORT', - 'IMMICH_MEDIA_LOCATION', - 'IMMICH_MICROSERVICES_METRICS_PORT', - 'IMMICH_TELEMETRY_INCLUDE', - 'IMMICH_TELEMETRY_EXCLUDE', - - 'DB_URL', - 'DB_HOSTNAME', - 'DB_PORT', - 'DB_USERNAME', - 'DB_PASSWORD', - 'DB_DATABASE_NAME', - 'DB_SSL_MODE', - 'DB_SKIP_MIGRATIONS', - 'DB_VECTOR_EXTENSION', - - 'REDIS_HOSTNAME', - 'REDIS_PORT', - 'REDIS_DBINDEX', - 'REDIS_USERNAME', - 'REDIS_PASSWORD', - 'REDIS_SOCKET', - 'REDIS_URL', - - 'NO_COLOR', - ]) { - delete process.env[env]; - } -}; - -const sentinelConfig = { - sentinels: [ - { - host: 'redis-sentinel-node-0', - port: 26_379, - }, - { - host: 'redis-sentinel-node-1', - port: 26_379, - }, - { - host: 'redis-sentinel-node-2', - port: 26_379, - }, - ], - name: 'redis-sentinel', -}; - -describe('getEnv', () => { - beforeEach(() => { - resetEnv(); - }); - - it('should use defaults', () => { - const config = getEnv(); - - expect(config).toMatchObject({ - host: undefined, - port: 2283, - environment: 'production', - configFile: undefined, - logLevel: undefined, - }); - - expect(config.plugins.external).toEqual({ allow: false }); - expect(config.setup).toEqual({ allow: true }); - }); - - describe('IMMICH_MEDIA_LOCATION', () => { - it('should throw an error for relative paths', () => { - process.env.IMMICH_MEDIA_LOCATION = './relative/path'; - expect(() => getEnv()).toThrowError('IMMICH_MEDIA_LOCATION must be an absolute path'); - }); - }); - - describe('IMMICH_ALLOW_EXTERNAL_PLUGINS', () => { - it('should disable plugins', () => { - process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'false'; - const config = getEnv(); - expect(config.plugins.external).toEqual({ allow: false }); - }); - - it('should throw an error for invalid value', () => { - process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'invalid'; - expect(() => getEnv()).toThrowError('IMMICH_ALLOW_EXTERNAL_PLUGINS must be a boolean value'); - }); - }); - - describe('IMMICH_ALLOW_SETUP', () => { - it('should disable setup', () => { - process.env.IMMICH_ALLOW_SETUP = 'false'; - const { setup } = getEnv(); - expect(setup).toEqual({ allow: false }); - }); - - it('should throw an error for invalid value', () => { - process.env.IMMICH_ALLOW_SETUP = 'invalid'; - expect(() => getEnv()).toThrowError('IMMICH_ALLOW_SETUP must be a boolean value'); - }); - }); - - describe('database', () => { - it('should use defaults', () => { - const { database } = getEnv(); - expect(database).toEqual({ - config: { - connectionType: 'parts', - host: 'database', - port: 5432, - database: 'immich', - username: 'postgres', - password: 'postgres', - }, - skipMigrations: false, - vectorExtension: undefined, - }); - }); - - it('should validate DB_SSL_MODE', () => { - process.env.DB_SSL_MODE = 'invalid'; - expect(() => getEnv()).toThrowError('DB_SSL_MODE must be one of the following values:'); - }); - - it('should accept a valid DB_SSL_MODE', () => { - process.env.DB_SSL_MODE = 'prefer'; - const { database } = getEnv(); - expect(database.config).toMatchObject(expect.objectContaining({ ssl: 'prefer' })); - }); - - it('should allow skipping migrations', () => { - process.env.DB_SKIP_MIGRATIONS = 'true'; - const { database } = getEnv(); - expect(database).toMatchObject({ skipMigrations: true }); - }); - - it('should use DB_URL', () => { - process.env.DB_URL = 'postgres://postgres1:postgres2@database1:54320/immich'; - const { database } = getEnv(); - expect(database.config).toMatchObject({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich', - }); - }); - }); - - describe('redis', () => { - it('should use defaults', () => { - const { redis } = getEnv(); - expect(redis).toEqual({ - host: 'redis', - port: 6379, - db: 0, - username: undefined, - password: undefined, - path: undefined, - }); - }); - - it('should parse base64 encoded config, ignore other env', () => { - process.env.REDIS_URL = `ioredis://${Buffer.from(JSON.stringify(sentinelConfig)).toString('base64')}`; - process.env.REDIS_HOSTNAME = 'redis-host'; - process.env.REDIS_USERNAME = 'redis-user'; - process.env.REDIS_PASSWORD = 'redis-password'; - const { redis } = getEnv(); - expect(redis).toEqual(sentinelConfig); - }); - - it('should reject invalid json', () => { - process.env.REDIS_URL = `ioredis://${Buffer.from('{ "invalid json"').toString('base64')}`; - expect(() => getEnv()).toThrowError('Failed to decode redis options'); - }); - }); - - describe('noColor', () => { - beforeEach(() => { - delete process.env.NO_COLOR; - }); - - it('should default noColor to false', () => { - const { noColor } = getEnv(); - expect(noColor).toBe(false); - }); - - it('should map NO_COLOR=1 to true', () => { - process.env.NO_COLOR = '1'; - const { noColor } = getEnv(); - expect(noColor).toBe(true); - }); - - it('should map NO_COLOR=true to true', () => { - process.env.NO_COLOR = 'true'; - const { noColor } = getEnv(); - expect(noColor).toBe(true); - }); - }); - - describe('workers', () => { - it('should return default workers', () => { - const { workers } = getEnv(); - expect(workers).toEqual(['api', 'microservices']); - }); - - it('should return included workers', () => { - process.env.IMMICH_WORKERS_INCLUDE = 'api'; - const { workers } = getEnv(); - expect(workers).toEqual(['api']); - }); - - it('should excluded workers from defaults', () => { - process.env.IMMICH_WORKERS_EXCLUDE = 'api'; - const { workers } = getEnv(); - expect(workers).toEqual(['microservices']); - }); - - it('should exclude workers from include list', () => { - process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice'; - process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices'; - const { workers } = getEnv(); - expect(workers).toEqual(['api']); - }); - - it('should remove whitespace from included workers before parsing', () => { - process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices'; - const { workers } = getEnv(); - expect(workers).toEqual(['api', 'microservices']); - }); - - it('should remove whitespace from excluded workers before parsing', () => { - process.env.IMMICH_WORKERS_EXCLUDE = 'api, microservices'; - const { workers } = getEnv(); - expect(workers).toEqual([]); - }); - - it('should remove whitespace from included and excluded workers before parsing', () => { - process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices, randomservice,randomservice2'; - process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices, randomservice2'; - const { workers } = getEnv(); - expect(workers).toEqual(['api']); - }); - - it('should throw error for invalid workers', () => { - process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice'; - expect(getEnv).toThrowError('Invalid worker(s) found: api,microservices,randomservice'); - }); - }); - - describe('network', () => { - it('should return default network options', () => { - const { network } = getEnv(); - expect(network).toEqual({ - trustedProxies: ['linklocal', 'uniquelocal'], - }); - }); - - it('should parse trusted proxies', () => { - process.env.IMMICH_TRUSTED_PROXIES = '10.1.0.0,10.2.0.0, 169.254.0.0/16'; - const { network } = getEnv(); - expect(network).toEqual({ - trustedProxies: ['10.1.0.0', '10.2.0.0', '169.254.0.0/16'], - }); - }); - - it('should reject invalid trusted proxies', () => { - process.env.IMMICH_TRUSTED_PROXIES = '10.1'; - expect(() => getEnv()).toThrow('IMMICH_TRUSTED_PROXIES must be an ip address, or ip address range'); - }); - }); - - describe('telemetry', () => { - it('should have default values', () => { - const { telemetry } = getEnv(); - expect(telemetry).toEqual({ - apiPort: 8081, - microservicesPort: 8082, - metrics: new Set(), - }); - }); - - it('should parse custom ports', () => { - process.env.IMMICH_API_METRICS_PORT = '2001'; - process.env.IMMICH_MICROSERVICES_METRICS_PORT = '2002'; - const { telemetry } = getEnv(); - expect(telemetry).toMatchObject({ - apiPort: 2001, - microservicesPort: 2002, - metrics: expect.any(Set), - }); - }); - - it('should run with telemetry enabled', () => { - process.env.IMMICH_TELEMETRY_INCLUDE = 'all'; - const { telemetry } = getEnv(); - expect(telemetry.metrics).toEqual(new Set(Object.values(ImmichTelemetry))); - }); - - it('should run with telemetry enabled and jobs disabled', () => { - process.env.IMMICH_TELEMETRY_INCLUDE = 'all'; - process.env.IMMICH_TELEMETRY_EXCLUDE = 'job'; - const { telemetry } = getEnv(); - expect(telemetry.metrics).toEqual( - new Set([ImmichTelemetry.Api, ImmichTelemetry.Host, ImmichTelemetry.Io, ImmichTelemetry.Repo]), - ); - }); - - it('should run with specific telemetry metrics', () => { - process.env.IMMICH_TELEMETRY_INCLUDE = 'io, host, api'; - const { telemetry } = getEnv(); - expect(telemetry.metrics).toEqual(new Set([ImmichTelemetry.Api, ImmichTelemetry.Host, ImmichTelemetry.Io])); - }); - }); -}); diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index 54a5d1987f..fcd59b63d9 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -1,16 +1,13 @@ import { RegisterQueueOptions } from '@nestjs/bullmq'; import { Inject, Injectable, Optional } from '@nestjs/common'; import { QueueOptions } from 'bullmq'; -import { plainToInstance } from 'class-transformer'; -import { validateSync } from 'class-validator'; import { Request, Response } from 'express'; import { RedisOptions } from 'ioredis'; import { CLS_ID, ClsModuleOptions } from 'nestjs-cls'; import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; import { join } from 'node:path'; -import { citiesFile, excludePaths, IWorker } from 'src/constants'; +import { excludePaths, IWorker } from 'src/constants'; import { Telemetry } from 'src/decorators'; -import { EnvDto } from 'src/dtos/env.dto'; import { DatabaseExtension, ImmichEnvironment, @@ -35,17 +32,11 @@ export interface EnvData { buildMetadata: { build?: string; buildUrl?: string; - buildImage?: string; - buildImageUrl?: string; repository?: string; repositoryUrl?: string; sourceRef?: string; sourceCommit?: string; sourceUrl?: string; - thirdPartySourceUrl?: string; - thirdPartyBugFeatureUrl?: string; - thirdPartyDocumentationUrl?: string; - thirdPartySupportUrl?: string; }; bull: { @@ -63,11 +54,6 @@ export interface EnvData { vectorExtension?: VectorExtension; }; - licensePublicKey: { - client: string; - server: string; - }; - network: { trustedProxies: string[]; }; @@ -75,67 +61,26 @@ export interface EnvData { otel: OpenTelemetryModuleOptions; resourcePaths: { - lockFile: string; - geodata: { - dateFile: string; - admin1: string; - admin2: string; - cities500: string; - naturalEarthCountriesPath: string; - }; web: { root: string; indexHtml: string; }; - corePlugin: string; }; redis: RedisOptions; - setup: { - allow: boolean; - }; - telemetry: { apiPort: number; microservicesPort: number; metrics: Set; }; - storage: { - ignoreMountCheckErrors: boolean; - mediaLocation?: string; - }; - workers: ImmichWorker[]; - plugins: { - external: { - allow: boolean; - installFolder?: string; - }; - }; - noColor: boolean; - nodeVersion?: string; } -const productionKeys = { - client: - 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2LzdTMzJjUkE1KysxTm5WRHNDTQpzcFAvakpISU1xT0pYRm5oNE53QTJPcHorUk1mZGNvOTJQc09naCt3d1FlRXYxVTJjMnBqelRpUS8ybHJLcS9rCnpKUmxYd2M0Y1Vlc1FETUpPRitQMnFPTlBiQUprWHZDWFlCVUxpdENJa29Md2ZoU0dOanlJS2FSRGhkL3ROeU4KOCtoTlJabllUMWhTSWo5U0NrS3hVQ096YXRQVjRtQ0RlclMrYkUrZ0VVZVdwOTlWOWF6dkYwRkltblRXcFFTdwpjOHdFWmdPTWg0c3ZoNmFpY3dkemtQQ3dFTGFrMFZhQkgzMUJFVUNRTGI5K0FJdEhBVXRKQ0t4aGI1V2pzMXM5CmJyWGZpMHZycGdjWi82RGFuWTJxZlNQem5PbXZEMkZycmxTMXE0SkpOM1ZvN1d3LzBZeS95TWNtelRXWmhHdWgKVVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=', - server: - 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFvcG5ZRGEwYS9kVTVJZUc3NGlFRQpNd2RBS2pzTmN6TGRDcVJkMVo5eTVUMndqTzdlWUlPZUpUc2wzNTBzUjBwNEtmU1VEU1h2QzlOcERwYzF0T0tsCjVzaEMvQXhwdlFBTENva0Y0anQ4dnJyZDlmQ2FYYzFUcVJiT21uaGl1Z0Q2dmtyME8vRmIzVURpM1UwVHZoUFAKbFBkdlNhd3pMcldaUExmbUhWVnJiclNLbW45SWVTZ3kwN3VrV1RJeUxzY2lOcnZuQnl3c0phUmVEdW9OV1BCSApVL21vMm1YYThtNHdNV2hpWGVoaUlPUXFNdVNVZ1BlQ3NXajhVVngxQ0dsUnpQREEwYlZOUXZlS1hXVnhjRUk2ClVMRWdKeTJGNDlsSDArYVlDbUJmN05FcjZWUTJXQjk1ZXZUS1hLdm4wcUlNN25nRmxjVUF3NmZ1VjFjTkNUSlMKNndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=', -}; - -const stagingKeys = { - client: - 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFuSUNyTm5jbGpPSC9JdTNtWVVaRQp0dGJLV1c3OGRuajl5M0U2ekk3dU1NUndEckdYWFhkTGhkUDFxSWtlZHh0clVVeUpCMWR4R04yQW91S082MlNGCldrbU9PTmNGQlRBWFZTdjhUNVY0S0VwWnFQYWEwaXpNaGxMaE5sRXEvY1ZKdllrWlh1Z2x6b1o3cG1nbzFSdHgKam1iRm5NNzhrYTFRUUJqOVdLaEw2eWpWRUl2MDdVS0lKWHBNTnNuS2g1V083MjZhYmMzSE9udTlETjY5VnFFRQo3dGZrUnRWNmx2U1NzMkFVMngzT255cHA4ek53b0lPTWRibGsyb09aWWROZzY0Y3l2SzJoU0FlU3NVMFRyOVc5Ckgra0Y5QlNCNlk0QXl0QlVkSmkrK2pMSW5HM2Q5cU9ieFVzTlYrN05mRkF5NjJkL0xNR0xSOC9OUFc0U0s3c0MKRlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=', - server: - 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE3Sy8yd3ZLUS9NdU8ydi9MUm5saAoyUy9zTHhDOGJiTEw1UUlKOGowQ3BVZW40YURlY2dYMUpKUmtGNlpUVUtpNTdTbEhtS3RSM2JOTzJmdTBUUVg5Ck5WMEJzVzllZVB0MmlTMWl4VVFmTzRObjdvTjZzbEtac01qd29RNGtGRGFmM3VHTlZJc0dMb3UxVWRLUVhpeDEKUlRHcXVTb3NZVjNWRlk3Q1hGYTVWaENBL3poVXNsNGFuVXp3eEF6M01jUFVlTXBaenYvbVZiQlRKVzBPSytWZgpWQUJvMXdYMkVBanpBekVHVzQ3Vko4czhnMnQrNHNPaHFBNStMQjBKVzlORUg5QUpweGZzWE4zSzVtM00yNUJVClZXcTlRYStIdHRENnJ0bnAvcUFweXVkWUdwZk9HYTRCUlZTR1MxMURZM0xrb2FlRzYwUEU5NHpoYjduOHpMWkgKelFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=', -}; - const WORKER_TYPES = new Set(Object.values(ImmichWorker)); -const TELEMETRY_TYPES = new Set(Object.values(ImmichTelemetry)); const asSet = (value: string | undefined, defaults: T[]) => { const values = (value || '').replaceAll(/\s/g, '').split(',').filter(Boolean); @@ -143,18 +88,10 @@ const asSet = (value: string | undefined, defaults: T[]) => { }; const getEnv = (): EnvData => { - const dto = plainToInstance(EnvDto, process.env); - const errors = validateSync(dto); - if (errors.length > 0) { - const messages = [`Invalid environment variables: `]; - for (const error of errors) { - messages.push(` - ${error.property}=${error.value} (${Object.values(error.constraints || {}).join(', ')})`); - } - throw new Error(messages.join('\n')); - } + const env = process.env; - const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]); - const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []); + const includedWorkers = asSet(env.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]); + const excludedWorkers = asSet(env.IMMICH_WORKERS_EXCLUDE, []); const workers = [...setDifference(includedWorkers, excludedWorkers)]; for (const worker of workers) { if (!WORKER_TYPES.has(worker)) { @@ -162,95 +99,54 @@ const getEnv = (): EnvData => { } } - const environment = dto.IMMICH_ENV || ImmichEnvironment.Production; - const isProd = environment === ImmichEnvironment.Production; - const buildFolder = dto.IMMICH_BUILD_DATA || '/build'; - const folders = { - geodata: join(buildFolder, 'geodata'), - web: join(buildFolder, 'www'), - }; + const environment = (env.IMMICH_ENV as ImmichEnvironment) || ImmichEnvironment.Production; + const buildFolder = env.IMMICH_BUILD_DATA || '/build'; + const webFolder = join(buildFolder, 'www'); - let redisConfig = { - host: dto.REDIS_HOSTNAME || 'redis', - port: dto.REDIS_PORT || 6379, - db: dto.REDIS_DBINDEX || 0, - username: dto.REDIS_USERNAME || undefined, - password: dto.REDIS_PASSWORD || undefined, - path: dto.REDIS_SOCKET || undefined, + const redisConfig: RedisOptions = { + host: env.REDIS_HOSTNAME || 'redis', + port: Number(env.REDIS_PORT) || 6379, + db: Number(env.REDIS_DBINDEX) || 0, + username: env.REDIS_USERNAME || undefined, + password: env.REDIS_PASSWORD || undefined, + path: env.REDIS_SOCKET || undefined, }; - const redisUrl = dto.REDIS_URL; - if (redisUrl && redisUrl.startsWith('ioredis://')) { - try { - redisConfig = JSON.parse(Buffer.from(redisUrl.slice(10), 'base64').toString()); - } catch (error) { - throw new Error(`Failed to decode redis options: ${error}`); - } - } - const includedTelemetries = - dto.IMMICH_TELEMETRY_INCLUDE === 'all' + env.IMMICH_TELEMETRY_INCLUDE === 'all' ? new Set(Object.values(ImmichTelemetry)) - : asSet(dto.IMMICH_TELEMETRY_INCLUDE, []); - - const excludedTelemetries = asSet(dto.IMMICH_TELEMETRY_EXCLUDE, []); + : asSet(env.IMMICH_TELEMETRY_INCLUDE, []); + const excludedTelemetries = asSet(env.IMMICH_TELEMETRY_EXCLUDE, []); const telemetries = setDifference(includedTelemetries, excludedTelemetries); - for (const telemetry of telemetries) { - if (!TELEMETRY_TYPES.has(telemetry)) { - throw new Error(`Invalid telemetry found: ${telemetry}`); - } - } - const databaseConnection: DatabaseConnectionParams = dto.DB_URL - ? { connectionType: 'url', url: dto.DB_URL } + const databaseConnection: DatabaseConnectionParams = env.DB_URL + ? { connectionType: 'url', url: env.DB_URL } : { connectionType: 'parts', - host: dto.DB_HOSTNAME || 'database', - port: dto.DB_PORT || 5432, - username: dto.DB_USERNAME || 'postgres', - password: dto.DB_PASSWORD || 'postgres', - database: dto.DB_DATABASE_NAME || 'immich', - ssl: dto.DB_SSL_MODE || undefined, + host: env.DB_HOSTNAME || 'database', + port: Number(env.DB_PORT) || 5432, + username: env.DB_USERNAME || 'postgres', + password: env.DB_PASSWORD || 'postgres', + database: env.DB_DATABASE_NAME || 'immich', + ssl: env.DB_SSL_MODE || undefined, }; - let vectorExtension: VectorExtension | undefined; - switch (dto.DB_VECTOR_EXTENSION) { - case 'pgvector': { - vectorExtension = DatabaseExtension.Vector; - break; - } - case 'pgvecto.rs': { - vectorExtension = DatabaseExtension.Vectors; - break; - } - case 'vectorchord': { - vectorExtension = DatabaseExtension.VectorChord; - break; - } - } - return { - host: dto.IMMICH_HOST, - port: dto.IMMICH_PORT || 2283, + host: env.IMMICH_HOST, + port: Number(env.IMMICH_PORT) || 2283, environment, - configFile: dto.IMMICH_CONFIG_FILE, - logLevel: dto.IMMICH_LOG_LEVEL, - logFormat: dto.IMMICH_LOG_FORMAT || LogFormat.Console, + configFile: env.IMMICH_CONFIG_FILE, + logLevel: env.IMMICH_LOG_LEVEL as LogLevel | undefined, + logFormat: (env.IMMICH_LOG_FORMAT as LogFormat) || LogFormat.Console, buildMetadata: { - build: dto.IMMICH_BUILD, - buildUrl: dto.IMMICH_BUILD_URL, - buildImage: dto.IMMICH_BUILD_IMAGE, - buildImageUrl: dto.IMMICH_BUILD_IMAGE_URL, - repository: dto.IMMICH_REPOSITORY, - repositoryUrl: dto.IMMICH_REPOSITORY_URL, - sourceRef: dto.IMMICH_SOURCE_REF, - sourceCommit: dto.IMMICH_SOURCE_COMMIT, - sourceUrl: dto.IMMICH_SOURCE_URL, - thirdPartySourceUrl: dto.IMMICH_THIRD_PARTY_SOURCE_URL, - thirdPartyBugFeatureUrl: dto.IMMICH_THIRD_PARTY_BUG_FEATURE_URL, - thirdPartyDocumentationUrl: dto.IMMICH_THIRD_PARTY_DOCUMENTATION_URL, - thirdPartySupportUrl: dto.IMMICH_THIRD_PARTY_SUPPORT_URL, + build: env.IMMICH_BUILD, + buildUrl: env.IMMICH_BUILD_URL, + repository: env.IMMICH_REPOSITORY, + repositoryUrl: env.IMMICH_REPOSITORY_URL, + sourceRef: env.IMMICH_SOURCE_REF, + sourceCommit: env.IMMICH_SOURCE_COMMIT, + sourceUrl: env.IMMICH_SOURCE_URL, }, bull: { @@ -284,14 +180,12 @@ const getEnv = (): EnvData => { database: { config: databaseConnection, - skipMigrations: dto.DB_SKIP_MIGRATIONS ?? false, - vectorExtension, + skipMigrations: env.DB_SKIP_MIGRATIONS === 'true', + vectorExtension: DatabaseExtension.Vector, }, - licensePublicKey: isProd ? productionKeys : stagingKeys, - network: { - trustedProxies: dto.IMMICH_TRUSTED_PROXIES ?? ['linklocal', 'uniquelocal'], + trustedProxies: ['linklocal', 'uniquelocal'], }, otel: { @@ -307,46 +201,20 @@ const getEnv = (): EnvData => { redis: redisConfig, resourcePaths: { - lockFile: join(buildFolder, 'build-lock.json'), - geodata: { - dateFile: join(folders.geodata, 'geodata-date.txt'), - admin1: join(folders.geodata, 'admin1CodesASCII.txt'), - admin2: join(folders.geodata, 'admin2Codes.txt'), - cities500: join(folders.geodata, citiesFile), - naturalEarthCountriesPath: join(folders.geodata, 'ne_10m_admin_0_countries.geojson'), - }, web: { - root: folders.web, - indexHtml: join(folders.web, 'index.html'), + root: webFolder, + indexHtml: join(webFolder, 'index.html'), }, - corePlugin: join(buildFolder, 'corePlugin'), - }, - - setup: { - allow: dto.IMMICH_ALLOW_SETUP ?? true, - }, - - storage: { - ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS, - mediaLocation: dto.IMMICH_MEDIA_LOCATION, }, telemetry: { - apiPort: dto.IMMICH_API_METRICS_PORT || 8081, - microservicesPort: dto.IMMICH_MICROSERVICES_METRICS_PORT || 8082, + apiPort: Number(env.IMMICH_API_METRICS_PORT) || 8081, + microservicesPort: Number(env.IMMICH_MICROSERVICES_METRICS_PORT) || 8082, metrics: telemetries, }, workers, - - plugins: { - external: { - allow: dto.IMMICH_ALLOW_EXTERNAL_PLUGINS ?? false, - installFolder: dto.IMMICH_PLUGINS_INSTALL_FOLDER, - }, - }, - - noColor: !!dto.NO_COLOR, + noColor: !!env.NO_COLOR, }; }; @@ -361,7 +229,6 @@ export class ConfigRepository { if (!cached) { cached = getEnv(); } - return cached; } diff --git a/server/src/repositories/cron.repository.ts b/server/src/repositories/cron.repository.ts deleted file mode 100644 index 03fecca9ad..0000000000 --- a/server/src/repositories/cron.repository.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { CronJob, CronTime } from 'cron'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -type CronBase = { - name: string; - start?: boolean; -}; - -export type CronCreate = CronBase & { - expression: string; - onTick: () => void; -}; - -export type CronUpdate = CronBase & { - expression?: string; -}; - -@Injectable() -export class CronRepository { - constructor( - private schedulerRegistry: SchedulerRegistry, - private logger: LoggingRepository, - ) { - this.logger.setContext(CronRepository.name); - } - - create({ name, expression, onTick, start = true }: CronCreate): void { - const job = new CronJob( - expression, - onTick, - // function to run onComplete - undefined, - // whether it should start directly - start, - // timezone - undefined, - // context - undefined, - // runOnInit - undefined, - // utcOffset - undefined, - // prevents memory leaking by automatically stopping when the node process finishes - true, - ); - - this.schedulerRegistry.addCronJob(name, job); - } - - update({ name, expression, start }: CronUpdate): void { - const job = this.schedulerRegistry.getCronJob(name); - if (expression) { - job.setTime(new CronTime(expression)); - } - if (start !== undefined) { - if (start) { - job.start(); - } else { - void job.stop(); - } - } - } -} diff --git a/server/src/repositories/database.repository.ts b/server/src/repositories/database.repository.ts index 55ed2c1176..79765cb4d9 100644 --- a/server/src/repositories/database.repository.ts +++ b/server/src/repositories/database.repository.ts @@ -1,54 +1,14 @@ import { Injectable } from '@nestjs/common'; import AsyncLock from 'async-lock'; -import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely'; +import { FileMigrationProvider, Kysely, Migrator, sql } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { readdir } from 'node:fs/promises'; import { join } from 'node:path'; -import semver from 'semver'; -import { - EXTENSION_NAMES, - POSTGRES_VERSION_RANGE, - VECTOR_EXTENSIONS, - VECTOR_INDEX_TABLES, - VECTOR_VERSION_RANGE, - VECTORCHORD_LIST_SLACK_FACTOR, - VECTORCHORD_VERSION_RANGE, - VECTORS_VERSION_RANGE, -} from 'src/constants'; -import { GenerateSql } from 'src/decorators'; -import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum'; +import { EXTENSION_NAMES, POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE } from 'src/constants'; +import { DatabaseExtension, DatabaseLock } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { DB } from 'src/schema'; -import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types'; -import { vectorIndexQuery } from 'src/utils/database'; -import { isValidInteger } from 'src/validation'; - -export let cachedVectorExtension: VectorExtension | undefined; -export async function getVectorExtension(runner: Kysely): Promise { - if (cachedVectorExtension) { - return cachedVectorExtension; - } - - cachedVectorExtension = new ConfigRepository().getEnv().database.vectorExtension; - if (cachedVectorExtension) { - return cachedVectorExtension; - } - - const query = `SELECT name FROM pg_available_extensions WHERE name IN (${VECTOR_EXTENSIONS.map((ext) => `'${ext}'`).join(', ')})`; - const { rows: availableExtensions } = await sql.raw<{ name: VectorExtension }>(query).execute(runner); - const extensionNames = new Set(availableExtensions.map((row) => row.name)); - cachedVectorExtension = VECTOR_EXTENSIONS.find((ext) => extensionNames.has(ext)); - if (!cachedVectorExtension) { - throw new Error(`No vector extension found. Available extensions: ${VECTOR_EXTENSIONS.join(', ')}`); - } - return cachedVectorExtension; -} - -export const probes: Record = { - [VectorIndex.Clip]: 1, - [VectorIndex.Face]: 1, -}; @Injectable() export class DatabaseRepository { @@ -66,38 +26,6 @@ export class DatabaseRepository { await this.db.destroy(); } - getVectorExtension(): Promise { - return getVectorExtension(this.db); - } - - @GenerateSql({ params: [[DatabaseExtension.Vectors]] }) - async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise { - const { rows } = await sql` - SELECT name, default_version as "availableVersion", installed_version as "installedVersion" - FROM pg_available_extensions - WHERE name in (${sql.join(extensions)}) - `.execute(this.db); - return rows; - } - - getExtensionVersionRange(extension: VectorExtension): string { - switch (extension) { - case DatabaseExtension.VectorChord: { - return VECTORCHORD_VERSION_RANGE; - } - case DatabaseExtension.Vectors: { - return VECTORS_VERSION_RANGE; - } - case DatabaseExtension.Vector: { - return VECTOR_VERSION_RANGE; - } - default: { - throw new Error(`Unsupported vector extension: '${extension}'`); - } - } - } - - @GenerateSql() async getPostgresVersion(): Promise { const { rows } = await sql<{ server_version: string }>`SHOW server_version`.execute(this.db); return rows[0].server_version; @@ -107,14 +35,22 @@ export class DatabaseRepository { return POSTGRES_VERSION_RANGE; } + getExtensionVersionRange(): string { + return VECTOR_VERSION_RANGE; + } + + async getExtensionVersion(name: string): Promise { + const { rows } = await sql<{ installed_version: string | null }>` + SELECT installed_version + FROM pg_available_extensions + WHERE name = ${name} + `.execute(this.db); + return rows[0]?.installed_version ?? undefined; + } + async createExtension(extension: DatabaseExtension): Promise { this.logger.log(`Creating ${EXTENSION_NAMES[extension]} extension`); await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db); - if (extension === DatabaseExtension.VectorChord) { - const dbName = sql.id(await this.getDatabaseName()); - await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db); - await sql`SET vchordrq.probes = 1`.execute(this.db); - } } async dropExtension(extension: DatabaseExtension): Promise { @@ -122,246 +58,10 @@ export class DatabaseRepository { await sql`DROP EXTENSION IF EXISTS ${sql.raw(extension)}`.execute(this.db); } - async updateVectorExtension(extension: VectorExtension, targetVersion?: string): Promise { - const [{ availableVersion, installedVersion }] = await this.getExtensionVersions([extension]); - if (!installedVersion) { - throw new Error(`${EXTENSION_NAMES[extension]} extension is not installed`); - } - - if (!availableVersion) { - throw new Error(`No available version for ${EXTENSION_NAMES[extension]} extension`); - } - targetVersion ??= availableVersion; - - let restartRequired = false; - const diff = semver.diff(installedVersion, targetVersion); - if (!diff) { - return { restartRequired: false }; - } - - await Promise.all([ - this.db.schema.dropIndex(VectorIndex.Clip).ifExists().execute(), - this.db.schema.dropIndex(VectorIndex.Face).ifExists().execute(), - ]); - - await this.db.transaction().execute(async (tx) => { - await this.setSearchPath(tx); - - await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx); - - if (extension === DatabaseExtension.Vectors && (diff === 'major' || diff === 'minor')) { - await sql`SELECT pgvectors_upgrade()`.execute(tx); - restartRequired = true; - } - }); - - if (!restartRequired) { - await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]); - } - - return { restartRequired }; - } - - async prewarm(index: VectorIndex): Promise { - const vectorExtension = await getVectorExtension(this.db); - if (vectorExtension !== DatabaseExtension.VectorChord) { - return; - } - this.logger.debug(`Prewarming ${index}`); - await sql`SELECT vchordrq_prewarm(${index})`.execute(this.db); - } - - async reindexVectorsIfNeeded(names: VectorIndex[]): Promise { - const { rows } = await sql<{ - indexdef: string; - indexname: string; - }>`SELECT indexdef, indexname FROM pg_indexes WHERE indexname = ANY(ARRAY[${sql.join(names)}])`.execute(this.db); - - const vectorExtension = await getVectorExtension(this.db); - - const promises = []; - for (const indexName of names) { - const row = rows.find((index) => index.indexname === indexName); - const table = VECTOR_INDEX_TABLES[indexName]; - if (!row) { - promises.push(this.reindexVectors(indexName)); - continue; - } - - switch (vectorExtension) { - case DatabaseExtension.Vector: { - if (!row.indexdef.toLowerCase().includes('using hnsw')) { - promises.push(this.reindexVectors(indexName)); - } - break; - } - case DatabaseExtension.Vectors: { - if (!row.indexdef.toLowerCase().includes('using vectors')) { - promises.push(this.reindexVectors(indexName)); - } - break; - } - case DatabaseExtension.VectorChord: { - const matches = row.indexdef.match(/(?<=lists = \[)\d+/g); - const lists = matches && matches.length > 0 ? Number(matches[0]) : 1; - promises.push( - this.getRowCount(table).then((count) => { - const targetLists = this.targetListCount(count); - this.logger.log(`targetLists=${targetLists}, current=${lists} for ${indexName} of ${count} rows`); - if ( - !row.indexdef.toLowerCase().includes('using vchordrq') || - // slack factor is to avoid frequent reindexing if the count is borderline - (lists !== targetLists && lists !== this.targetListCount(count * VECTORCHORD_LIST_SLACK_FACTOR)) - ) { - probes[indexName] = this.targetProbeCount(targetLists); - return this.reindexVectors(indexName, { lists: targetLists }); - } else { - probes[indexName] = this.targetProbeCount(lists); - } - }), - ); - break; - } - } - } - - if (promises.length > 0) { - await Promise.all(promises); - } - } - - private async reindexVectors(indexName: VectorIndex, { lists }: { lists?: number } = {}): Promise { - this.logger.log(`Reindexing ${indexName} (This may take a while, do not restart)`); - const table = VECTOR_INDEX_TABLES[indexName]; - const vectorExtension = await getVectorExtension(this.db); - - const { rows } = await sql<{ - columnName: string; - }>`SELECT column_name as "columnName" FROM information_schema.columns WHERE table_name = ${table}`.execute(this.db); - if (rows.length === 0) { - this.logger.warn( - `Table ${table} does not exist, skipping reindexing. This is only normal if this is a new Immich instance.`, - ); - return; - } - const dimSize = await this.getDimensionSize(table); - lists ||= this.targetListCount(await this.getRowCount(table)); - await this.db.schema.dropIndex(indexName).ifExists().execute(); - if (table === 'smart_search') { - await this.db.schema.alterTable(table).dropConstraint('dim_size_constraint').ifExists().execute(); - } - await this.db.transaction().execute(async (tx) => { - if (!rows.some((row) => row.columnName === 'embedding')) { - this.logger.warn(`Column 'embedding' does not exist in table '${table}', truncating and adding column.`); - await sql`TRUNCATE TABLE ${sql.raw(table)}`.execute(tx); - await sql`ALTER TABLE ${sql.raw(table)} ADD COLUMN embedding real[] NOT NULL`.execute(tx); - } - await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.execute(tx); - const schema = vectorExtension === DatabaseExtension.Vectors ? 'vectors.' : ''; - await sql` - ALTER TABLE ${sql.raw(table)} - ALTER COLUMN embedding - SET DATA TYPE ${sql.raw(schema)}vector(${sql.raw(String(dimSize))})`.execute(tx); - await sql.raw(vectorIndexQuery({ vectorExtension, table, indexName, lists })).execute(tx); - }); - try { - await sql`VACUUM ANALYZE ${sql.raw(table)}`.execute(this.db); - } catch (error: any) { - this.logger.warn(`Failed to vacuum table '${table}'. The DB will temporarily use more disk space: ${error}`); - } - this.logger.log(`Reindexed ${indexName}`); - } - - private async setSearchPath(tx: Transaction): Promise { - await sql`SET search_path TO "$user", public, vectors`.execute(tx); - } - - private async getDatabaseName(): Promise { - const { rows } = await sql<{ db: string }>`SELECT current_database() as db`.execute(this.db); - return rows[0].db; - } - - async getDimensionSize(table: string, column = 'embedding'): Promise { - const { rows } = await sql<{ dimsize: number }>` - SELECT atttypmod as dimsize - FROM pg_attribute f - JOIN pg_class c ON c.oid = f.attrelid - WHERE c.relkind = 'r'::char - AND f.attnum > 0 - AND c.relname = ${table}::text - AND f.attname = ${column}::text - `.execute(this.db); - - const dimSize = rows[0]?.dimsize; - if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { - this.logger.warn(`Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512`); - return 512; - } - return dimSize; - } - - async setDimensionSize(dimSize: number): Promise { - if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { - throw new Error(`Invalid CLIP dimension size: ${dimSize}`); - } - - // this is done in two transactions to handle concurrent writes - await this.db.transaction().execute(async (trx) => { - await sql`delete from ${sql.table('smart_search')}`.execute(trx); - await trx.schema.alterTable('smart_search').dropConstraint('dim_size_constraint').ifExists().execute(); - await sql`alter table ${sql.table('smart_search')} add constraint dim_size_constraint check (array_length(embedding::real[], 1) = ${sql.lit(dimSize)})`.execute( - trx, - ); - }); - - const vectorExtension = await this.getVectorExtension(); - await this.db.transaction().execute(async (trx) => { - await sql`drop index if exists clip_index`.execute(trx); - await trx.schema - .alterTable('smart_search') - .alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`))) - .execute(); - await sql - .raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.Clip })) - .execute(trx); - await trx.schema.alterTable('smart_search').dropConstraint('dim_size_constraint').ifExists().execute(); - }); - probes[VectorIndex.Clip] = 1; - - await sql`vacuum analyze ${sql.table('smart_search')}`.execute(this.db); - } - - async deleteAllSearchEmbeddings(): Promise { - await sql`truncate ${sql.table('smart_search')}`.execute(this.db); - } - - private targetListCount(count: number) { - if (count < 128_000) { - return 1; - } else if (count < 2_048_000) { - return 1 << (32 - Math.clz32(count / 1000)); - } else { - return 1 << (33 - Math.clz32(Math.sqrt(count))); - } - } - - private targetProbeCount(lists: number) { - return Math.ceil(lists / 8); - } - - private async getRowCount(table: keyof DB): Promise { - const { count } = await this.db - .selectFrom(this.db.dynamic.table(table).as('t')) - .select((eb) => eb.fn.countAll().as('count')) - .executeTakeFirstOrThrow(); - return count; - } - async runMigrations(): Promise { this.logger.log('Running migrations'); const migrator = this.createMigrator(); - const { error, results } = await migrator.migrateToLatest(); for (const result of results ?? []) { @@ -382,45 +82,35 @@ export class DatabaseRepository { this.logger.log('Finished running migrations'); } - async migrateFilePaths(sourceFolder: string, targetFolder: string): Promise { - // remove trailing slashes - if (sourceFolder.endsWith('/')) { - sourceFolder = sourceFolder.slice(0, -1); + async revertLastMigration(): Promise { + this.logger.debug('Reverting last migration'); + + const migrator = this.createMigrator(); + const { error, results } = await migrator.migrateDown(); + + for (const result of results ?? []) { + if (result.status === 'Success') { + this.logger.log(`Reverted migration "${result.migrationName}"`); + } + + if (result.status === 'Error') { + this.logger.warn(`Failed to revert migration "${result.migrationName}"`); + } } - if (targetFolder.endsWith('/')) { - targetFolder = targetFolder.slice(0, -1); + if (error) { + this.logger.error(`Failed to revert migrations: ${error}`); + throw error; } - // escaping regex special characters with a backslash - const sourceRegex = '^' + sourceFolder.replaceAll(/[-[\]{}()*+?.,\\^$|#\s]/g, String.raw`\$&`); - const source = sql.raw(`'${sourceRegex}'`); - const target = sql.lit(targetFolder); + const reverted = results?.find((result) => result.direction === 'Down' && result.status === 'Success'); + if (!reverted) { + this.logger.debug('No migrations to revert'); + return undefined; + } - await this.db.transaction().execute(async (tx) => { - await tx - .updateTable('asset') - .set((eb) => ({ - originalPath: eb.fn('REGEXP_REPLACE', ['originalPath', source, target]), - encodedVideoPath: eb.fn('REGEXP_REPLACE', ['encodedVideoPath', source, target]), - })) - .execute(); - - await tx - .updateTable('asset_file') - .set((eb) => ({ path: eb.fn('REGEXP_REPLACE', ['path', source, target]) })) - .execute(); - - await tx - .updateTable('person') - .set((eb) => ({ thumbnailPath: eb.fn('REGEXP_REPLACE', ['thumbnailPath', source, target]) })) - .execute(); - - await tx - .updateTable('user') - .set((eb) => ({ profileImagePath: eb.fn('REGEXP_REPLACE', ['profileImagePath', source, target]) })) - .execute(); - }); + this.logger.debug('Finished reverting migration'); + return reverted.migrationName; } async withLock(lock: DatabaseLock, callback: () => Promise): Promise { @@ -466,37 +156,6 @@ export class DatabaseRepository { await sql`SELECT pg_advisory_unlock(${lock})`.execute(connection); } - async revertLastMigration(): Promise { - this.logger.debug('Reverting last migration'); - - const migrator = this.createMigrator(); - const { error, results } = await migrator.migrateDown(); - - for (const result of results ?? []) { - if (result.status === 'Success') { - this.logger.log(`Reverted migration "${result.migrationName}"`); - } - - if (result.status === 'Error') { - this.logger.warn(`Failed to revert migration "${result.migrationName}"`); - } - } - - if (error) { - this.logger.error(`Failed to revert migrations: ${error}`); - throw error; - } - - const reverted = results?.find((result) => result.direction === 'Down' && result.status === 'Success'); - if (!reverted) { - this.logger.debug('No migrations to revert'); - return undefined; - } - - this.logger.debug('Finished reverting migration'); - return reverted.migrationName; - } - private createMigrator(): Migrator { return new Migrator({ db: this.db, diff --git a/server/src/repositories/download.repository.ts b/server/src/repositories/download.repository.ts deleted file mode 100644 index 61a0f23d5e..0000000000 --- a/server/src/repositories/download.repository.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { anyUuid } from 'src/utils/database'; - -const builder = (db: Kysely) => - db - .selectFrom('asset') - .innerJoin('asset_exif', 'assetId', 'id') - .select(['asset.id', 'asset.livePhotoVideoId', 'asset_exif.fileSizeInByte as size']) - .where('asset.deletedAt', 'is', null); - -@Injectable() -export class DownloadRepository { - constructor(@InjectKysely() private db: Kysely) {} - - downloadAssetIds(ids: string[]) { - return builder(this.db).where('asset.id', '=', anyUuid(ids)).stream(); - } - - downloadMotionAssetIds(ids: string[]) { - return builder(this.db).select(['asset.originalPath']).where('asset.id', '=', anyUuid(ids)).stream(); - } - - downloadAlbumId(albumId: string) { - return builder(this.db) - .innerJoin('album_asset', 'asset.id', 'album_asset.assetId') - .where('album_asset.albumId', '=', albumId) - .stream(); - } - - downloadUserId(userId: string) { - return builder(this.db) - .where('asset.ownerId', '=', userId) - .where('asset.visibility', '!=', AssetVisibility.Hidden) - .stream(); - } -} diff --git a/server/src/repositories/duplicate.repository.ts b/server/src/repositories/duplicate.repository.ts deleted file mode 100644 index 95ccbea63d..0000000000 --- a/server/src/repositories/duplicate.repository.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely, NotNull, sql } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { Chunked, DummyValue, GenerateSql } from 'src/decorators'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { AssetType, VectorIndex } from 'src/enum'; -import { probes } from 'src/repositories/database.repository'; -import { DB } from 'src/schema'; -import { anyUuid, asUuid, withDefaultVisibility } from 'src/utils/database'; - -interface DuplicateSearch { - assetId: string; - embedding: string; - maxDistance: number; - type: AssetType; - userIds: string[]; -} - -interface DuplicateMergeOptions { - targetId: string | null; - assetIds: string[]; - sourceIds: string[]; -} - -@Injectable() -export class DuplicateRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - getAll(userId: string) { - return ( - this.db - .with('duplicates', (qb) => - qb - .selectFrom('asset') - .$call(withDefaultVisibility) - .innerJoinLateral( - (qb) => - qb - .selectFrom('asset_exif') - .selectAll('asset') - .select((eb) => eb.table('asset_exif').as('exifInfo')) - .whereRef('asset_exif.assetId', '=', 'asset.id') - .as('asset2'), - (join) => join.onTrue(), - ) - .select('asset.duplicateId') - .select((eb) => - eb.fn.jsonAgg('asset2').orderBy('asset.localDateTime', 'asc').$castTo().as('assets'), - ) - .where('asset.ownerId', '=', asUuid(userId)) - .where('asset.duplicateId', 'is not', null) - .$narrowType<{ duplicateId: NotNull }>() - .where('asset.deletedAt', 'is', null) - .where('asset.stackId', 'is', null) - .groupBy('asset.duplicateId'), - ) - .with('unique', (qb) => - qb - .selectFrom('duplicates') - .select('duplicateId') - .where((eb) => eb(eb.fn('json_array_length', ['assets']), '=', 1)), - ) - .with('removed_unique', (qb) => - qb - .updateTable('asset') - .set({ duplicateId: null }) - .from('unique') - .whereRef('asset.duplicateId', '=', 'unique.duplicateId'), - ) - .selectFrom('duplicates') - .selectAll() - // TODO: compare with filtering by json_array_length > 1 - .where(({ not, exists }) => - not(exists((eb) => eb.selectFrom('unique').whereRef('unique.duplicateId', '=', 'duplicates.duplicateId'))), - ) - .execute() - ); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - async delete(userId: string, id: string): Promise { - await this.db - .updateTable('asset') - .set({ duplicateId: null }) - .where('ownerId', '=', userId) - .where('duplicateId', '=', id) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - @Chunked({ paramIndex: 1 }) - async deleteAll(userId: string, ids: string[]): Promise { - if (ids.length === 0) { - return; - } - - await this.db - .updateTable('asset') - .set({ duplicateId: null }) - .where('ownerId', '=', userId) - .where('duplicateId', 'in', ids) - .execute(); - } - - @GenerateSql({ - params: [ - { - assetId: DummyValue.UUID, - embedding: DummyValue.VECTOR, - maxDistance: 0.6, - type: AssetType.Image, - userIds: [DummyValue.UUID], - }, - ], - }) - search({ assetId, embedding, maxDistance, type, userIds }: DuplicateSearch) { - return this.db.transaction().execute(async (trx) => { - await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx); - return await trx - .with('cte', (qb) => - qb - .selectFrom('asset') - .$call(withDefaultVisibility) - .select([ - 'asset.id as assetId', - 'asset.duplicateId', - sql`smart_search.embedding <=> ${embedding}`.as('distance'), - ]) - .innerJoin('smart_search', 'asset.id', 'smart_search.assetId') - .where('asset.ownerId', '=', anyUuid(userIds)) - .where('asset.deletedAt', 'is', null) - .where('asset.type', '=', type) - .where('asset.id', '!=', asUuid(assetId)) - .where('asset.stackId', 'is', null) - .orderBy('distance') - .limit(64), - ) - .selectFrom('cte') - .selectAll() - .where('cte.distance', '<=', maxDistance as number) - .execute(); - }); - } - - @GenerateSql({ - params: [{ targetDuplicateId: DummyValue.UUID, duplicateIds: [DummyValue.UUID], assetIds: [DummyValue.UUID] }], - }) - async merge(options: DuplicateMergeOptions): Promise { - await this.db - .updateTable('asset') - .set({ duplicateId: options.targetId }) - .where((eb) => - eb.or([eb('duplicateId', '=', anyUuid(options.sourceIds)), eb('id', '=', anyUuid(options.assetIds))]), - ) - .execute(); - } -} diff --git a/server/src/repositories/email.repository.spec.ts b/server/src/repositories/email.repository.spec.ts deleted file mode 100644 index 5640b26bf6..0000000000 --- a/server/src/repositories/email.repository.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { EmailRenderRequest, EmailRepository, EmailTemplate } from 'src/repositories/email.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { automock } from 'test/utils'; - -describe(EmailRepository.name, () => { - let sut: EmailRepository; - - beforeEach(() => { - // eslint-disable-next-line no-sparse-arrays - sut = new EmailRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false })); - }); - - describe('renderEmail', () => { - it('should render the email correctly for TEST_EMAIL template', async () => { - const request: EmailRenderRequest = { - template: EmailTemplate.TEST_EMAIL, - data: { displayName: 'Alen Turing', baseUrl: 'http://localhost' }, - customTemplate: '', - }; - - const result = await sut.renderEmail(request); - - expect(result.html).toContain(' { - const request: EmailRenderRequest = { - template: EmailTemplate.WELCOME, - data: { displayName: 'Alen Turing', username: 'turing', baseUrl: 'http://localhost' }, - customTemplate: '', - }; - - const result = await sut.renderEmail(request); - - expect(result.html).toContain(' { - const request: EmailRenderRequest = { - template: EmailTemplate.ALBUM_INVITE, - data: { - albumName: 'Vacation', - albumId: '123', - senderName: 'John', - recipientName: 'Jane', - baseUrl: 'http://localhost', - }, - customTemplate: '', - }; - - const result = await sut.renderEmail(request); - - expect(result.html).toContain(' { - const request: EmailRenderRequest = { - template: EmailTemplate.ALBUM_UPDATE, - data: { albumName: 'Holiday', albumId: '123', recipientName: 'Jane', baseUrl: 'http://localhost' }, - customTemplate: '', - }; - - const result = await sut.renderEmail(request); - - expect(result.html).toContain(' { - const transport = this.createTransport(options); - try { - return transport.verify(); - } finally { - transport.close(); - } - } - - async renderEmail(request: EmailRenderRequest): Promise<{ html: string; text: string }> { - const component = this.render(request); - const html = await render(component, { pretty: false }); - const text = await render(component, { plainText: true }); - return { html, text }; - } - - sendEmail({ to, from, subject, html, text, smtp, imageAttachments }: SendEmailOptions): Promise { - this.logger.debug(`Sending email to ${to} with subject: ${subject}`); - const transport = this.createTransport(smtp); - - const attachments = imageAttachments?.map((attachment) => ({ - filename: attachment.filename, - path: attachment.path, - cid: attachment.cid, - })); - - try { - return transport.sendMail({ to, from, subject, html, text, attachments }); - } finally { - transport.close(); - } - } - - private render({ template, data, customTemplate }: EmailRenderRequest): React.FunctionComponentElement { - switch (template) { - case EmailTemplate.TEST_EMAIL: { - return React.createElement(TestEmail, { ...data, customTemplate }); - } - - case EmailTemplate.WELCOME: { - return React.createElement(WelcomeEmail, { ...data, customTemplate }); - } - - case EmailTemplate.ALBUM_INVITE: { - return React.createElement(AlbumInviteEmail, { ...data, customTemplate }); - } - - case EmailTemplate.ALBUM_UPDATE: { - return React.createElement(AlbumUpdateEmail, { ...data, customTemplate }); - } - } - } - - private createTransport(options: SmtpOptions) { - return createTransport({ - host: options.host, - port: options.port, - tls: { rejectUnauthorized: !options.ignoreCert }, - auth: - options.username || options.password - ? { - user: options.username, - pass: options.password, - } - : undefined, - connectionTimeout: 5000, - }); - } -} diff --git a/server/src/repositories/event.repository.ts b/server/src/repositories/event.repository.ts index fbc281ccb3..3838d66782 100644 --- a/server/src/repositories/event.repository.ts +++ b/server/src/repositories/event.repository.ts @@ -4,13 +4,12 @@ import { ClassConstructor } from 'class-transformer'; import _ from 'lodash'; import { Socket } from 'socket.io'; import { SystemConfig } from 'src/config'; -import { Asset } from 'src/database'; import { EventConfig } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; -import { ImmichWorker, JobStatus, MetadataKey, QueueName, UserAvatarColor, UserStatus } from 'src/enum'; +import { ImmichWorker, JobStatus, MetadataKey, QueueName, UserStatus } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { JobItem, JobSource } from 'src/types'; +import { JobItem } from 'src/types'; type EmitHandlers = Partial<{ [T in EmitEvent]: Array> }>; @@ -24,70 +23,28 @@ type Item = { type EventMap = { // app events - AppBootstrap: []; - AppShutdown: []; - AppRestart: [AppRestartEvent]; + 'app.bootstrap': []; + 'app.shutdown': []; - ConfigInit: [{ newConfig: SystemConfig }]; // config events - ConfigUpdate: [ - { - newConfig: SystemConfig; - oldConfig: SystemConfig; - }, - ]; + ConfigInit: [{ newConfig: SystemConfig }]; + ConfigUpdate: [{ newConfig: SystemConfig; oldConfig: SystemConfig }]; ConfigValidate: [{ newConfig: SystemConfig; oldConfig: SystemConfig }]; - // album events - AlbumUpdate: [{ id: string; recipientId: string }]; - AlbumInvite: [{ id: string; userId: string }]; - - // asset events - AssetCreate: [{ asset: Asset }]; - AssetTag: [{ assetId: string }]; - AssetUntag: [{ assetId: string }]; - AssetHide: [{ assetId: string; userId: string }]; - AssetShow: [{ assetId: string; userId: string }]; - AssetTrash: [{ assetId: string; userId: string }]; - AssetDelete: [{ assetId: string; userId: string }]; - AssetMetadataExtracted: [{ assetId: string; userId: string; source?: JobSource }]; - - // asset bulk events - AssetTrashAll: [{ assetIds: string[]; userId: string }]; - AssetDeleteAll: [{ assetIds: string[]; userId: string }]; - AssetRestoreAll: [{ assetIds: string[]; userId: string }]; - - /** a worker receives a job and emits this event to run it */ + // job events JobRun: [QueueName, JobItem]; - /** job pre-hook */ JobStart: [QueueName, JobItem]; - /** job post-hook */ JobComplete: [QueueName, JobItem]; - /** job finishes without error */ - JobSuccess: [JobSuccessEvent]; - /** job finishes with error */ - JobError: [JobErrorEvent]; - - // queue events - QueueStart: [QueueStartEvent]; + JobSuccess: [{ job: JobItem; response?: JobStatus }]; + JobError: [{ job: JobItem; error: Error | unknown }]; + QueueStart: [{ name: QueueName }]; // session events SessionDelete: [{ sessionId: string }]; - // stack events - StackCreate: [{ stackId: string; userId: string }]; - StackUpdate: [{ stackId: string; userId: string }]; - StackDelete: [{ stackId: string; userId: string }]; - - // stack bulk events - StackDeleteAll: [{ stackIds: string[]; userId: string }]; - // user events - UserSignup: [{ notify: boolean; id: string; password?: string }]; UserCreate: [UserEvent]; - /** user is soft deleted */ UserTrash: [UserEvent]; - /** user is permanently deleted */ UserDelete: [UserEvent]; UserRestore: [UserEvent]; @@ -97,17 +54,6 @@ type EventMap = { WebsocketConnect: [{ userId: string }]; }; -export type AppRestartEvent = { - isMaintenanceMode: boolean; -}; - -type JobSuccessEvent = { job: JobItem; response?: JobStatus }; -type JobErrorEvent = { job: JobItem; error: Error | any }; - -type QueueStartEvent = { - name: QueueName; -}; - type UserEvent = { name: string; id: string; @@ -116,15 +62,8 @@ type UserEvent = { deletedAt: Date | null; status: UserStatus; email: string; - profileImagePath: string; isAdmin: boolean; shouldChangePassword: boolean; - avatarColor: UserAvatarColor | null; - oauthId: string; - storageLabel: string | null; - quotaSizeInBytes: number | null; - quotaUsageInBytes: number; - profileChangedAt: Date; }; export type EmitEvent = keyof EventMap; @@ -160,7 +99,6 @@ export class EventRepository { throw new Error('Unable to determine worker type'); } - // discovery for (const Service of services) { const instance = this.moduleRef.get(Service); const ctx = Object.getPrototypeOf(instance); @@ -196,8 +134,6 @@ export class EventRepository { } const handlers = _.orderBy(items, ['priority'], ['asc']); - - // register by priority for (const handler of handlers) { this.addHandler(handler); } @@ -205,12 +141,10 @@ export class EventRepository { private addHandler(item: Item): void { const event = item.event; - if (!this.emitHandlers[event]) { this.emitHandlers[event] = []; } - - this.emitHandlers[event].push(item); + this.emitHandlers[event]!.push(item); } emit(event: T, ...args: ArgsOf): Promise { @@ -220,11 +154,9 @@ export class EventRepository { async onEvent(event: { name: T; args: ArgsOf; server: boolean }): Promise { const handlers = this.emitHandlers[event.name] || []; for (const { handler, server } of handlers) { - // exclude handlers that ignore server events if (!server && event.server) { continue; } - await handler(...event.args); } } diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 361a2e7179..0d31ac1b20 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -1,107 +1,31 @@ -import { AccessRepository } from 'src/repositories/access.repository'; -import { ActivityRepository } from 'src/repositories/activity.repository'; -import { AlbumUserRepository } from 'src/repositories/album-user.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; import { ApiKeyRepository } from 'src/repositories/api-key.repository'; import { AppRepository } from 'src/repositories/app.repository'; -import { AssetEditRepository } from 'src/repositories/asset-edit.repository'; -import { AssetJobRepository } from 'src/repositories/asset-job.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { AuditRepository } from 'src/repositories/audit.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; -import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; -import { DownloadRepository } from 'src/repositories/download.repository'; -import { DuplicateRepository } from 'src/repositories/duplicate.repository'; -import { EmailRepository } from 'src/repositories/email.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; -import { LibraryRepository } from 'src/repositories/library.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; -import { MapRepository } from 'src/repositories/map.repository'; -import { MediaRepository } from 'src/repositories/media.repository'; -import { MemoryRepository } from 'src/repositories/memory.repository'; -import { MetadataRepository } from 'src/repositories/metadata.repository'; -import { MoveRepository } from 'src/repositories/move.repository'; -import { NotificationRepository } from 'src/repositories/notification.repository'; -import { OAuthRepository } from 'src/repositories/oauth.repository'; -import { OcrRepository } from 'src/repositories/ocr.repository'; -import { PartnerRepository } from 'src/repositories/partner.repository'; -import { PersonRepository } from 'src/repositories/person.repository'; -import { PluginRepository } from 'src/repositories/plugin.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; -import { SearchRepository } from 'src/repositories/search.repository'; -import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; -import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; -import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; -import { StackRepository } from 'src/repositories/stack.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { SyncCheckpointRepository } from 'src/repositories/sync-checkpoint.repository'; -import { SyncRepository } from 'src/repositories/sync.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { TagRepository } from 'src/repositories/tag.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; -import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; -import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; -import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; -import { WorkflowRepository } from 'src/repositories/workflow.repository'; export const repositories = [ - AccessRepository, - ActivityRepository, - AlbumRepository, - AlbumUserRepository, - AuditRepository, ApiKeyRepository, AppRepository, - AssetRepository, - AssetEditRepository, - AssetJobRepository, ConfigRepository, - CronRepository, CryptoRepository, DatabaseRepository, - DownloadRepository, - DuplicateRepository, - EmailRepository, EventRepository, JobRepository, - LibraryRepository, LoggingRepository, - MachineLearningRepository, - MapRepository, - MediaRepository, - MemoryRepository, - MetadataRepository, - MoveRepository, - NotificationRepository, - OAuthRepository, - OcrRepository, - PartnerRepository, - PersonRepository, - PluginRepository, ProcessRepository, - SearchRepository, SessionRepository, - ServerInfoRepository, - SharedLinkRepository, - SharedLinkAssetRepository, - StackRepository, - StorageRepository, - SyncRepository, - SyncCheckpointRepository, SystemMetadataRepository, - TagRepository, TelemetryRepository, - TrashRepository, UserRepository, - ViewRepository, - VersionHistoryRepository, WebsocketRepository, - WorkflowRepository, ]; diff --git a/server/src/repositories/job.repository.ts b/server/src/repositories/job.repository.ts index b12accb68e..67cb011b09 100644 --- a/server/src/repositories/job.repository.ts +++ b/server/src/repositories/job.repository.ts @@ -5,18 +5,17 @@ import { JobsOptions, Queue, Worker } from 'bullmq'; import { ClassConstructor } from 'class-transformer'; import { setTimeout } from 'node:timers/promises'; import { JobConfig } from 'src/decorators'; -import { QueueJobResponseDto, QueueJobSearchDto } from 'src/dtos/queue.dto'; -import { JobName, JobStatus, MetadataKey, QueueCleanType, QueueJobStatus, QueueName } from 'src/enum'; +import { JobName, JobStatus, MetadataKey, QueueName } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { JobCounts, JobItem, JobOf } from 'src/types'; +import { JobItem } from 'src/types'; import { getKeyByValue, getMethodNames, ImmichStartupError } from 'src/utils/misc'; type JobMapItem = { jobName: JobName; queueName: QueueName; - handler: (job: JobOf) => Promise; + handler: (data: any) => Promise; label: string; }; @@ -138,21 +137,6 @@ export class JobRepository { return this.getQueue(name).drain(); } - clear(name: QueueName, type: QueueCleanType) { - return this.getQueue(name).clean(0, 1000, type); - } - - getJobCounts(name: QueueName): Promise { - return this.getQueue(name).getJobCounts( - 'active', - 'completed', - 'failed', - 'delayed', - 'waiting', - 'paused', - ) as unknown as Promise; - } - private getQueueName(name: JobName) { return (this.handlers[name] as JobMapItem).queueName; } @@ -162,25 +146,19 @@ export class JobRepository { return; } - const promises = []; - const itemsByQueue = {} as Record; + const itemsByQueue = {} as Record; for (const item of items) { const queueName = this.getQueueName(item.name); const job = { name: item.name, data: item.data || {}, - options: this.getJobOptions(item) || undefined, - } as JobItem & { data: any; options: JobsOptions | undefined }; + }; - if (job.options?.jobId) { - // need to use add() instead of addBulk() for jobId deduplication - promises.push(this.getQueue(queueName).add(item.name, item.data, job.options)); - } else { - itemsByQueue[queueName] = itemsByQueue[queueName] || []; - itemsByQueue[queueName].push(job); - } + itemsByQueue[queueName] = itemsByQueue[queueName] || []; + itemsByQueue[queueName].push(job); } + const promises = []; for (const [queueName, jobs] of Object.entries(itemsByQueue)) { const queue = this.getQueue(queueName as QueueName); promises.push(queue.addBulk(jobs)); @@ -208,47 +186,7 @@ export class JobRepository { } } - async searchJobs(name: QueueName, dto: QueueJobSearchDto): Promise { - const jobs = await this.getQueue(name).getJobs(dto.status ?? Object.values(QueueJobStatus), 0, 1000); - return jobs.map((job) => { - const { id, name, timestamp, data } = job; - return { id, name: name as JobName, timestamp, data }; - }); - } - - private getJobOptions(item: JobItem): JobsOptions | null { - switch (item.name) { - case JobName.NotifyAlbumUpdate: { - return { - jobId: `${item.data.id}/${item.data.recipientId}`, - delay: item.data?.delay, - }; - } - case JobName.StorageTemplateMigrationSingle: { - return { jobId: item.data.id }; - } - case JobName.PersonGenerateThumbnail: { - return { priority: 1 }; - } - case JobName.FacialRecognitionQueueAll: { - return { jobId: JobName.FacialRecognitionQueueAll }; - } - default: { - return null; - } - } - } - private getQueue(queue: QueueName): Queue { return this.moduleRef.get(getQueueToken(queue), { strict: false }); } - - /** @deprecated */ - // todo: remove this when asset notifications no longer need it. - public async removeJob(name: JobName, jobID: string): Promise { - const existingJob = await this.getQueue(this.getQueueName(name)).getJob(jobID); - if (existingJob) { - await existingJob.remove(); - } - } } diff --git a/server/src/repositories/library.repository.ts b/server/src/repositories/library.repository.ts deleted file mode 100644 index 68102ab765..0000000000 --- a/server/src/repositories/library.repository.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql, Updateable } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { LibraryStatsResponseDto } from 'src/dtos/library.dto'; -import { AssetType, AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { LibraryTable } from 'src/schema/tables/library.table'; - -export enum AssetSyncResult { - DO_NOTHING, - UPDATE, - OFFLINE, - CHECK_OFFLINE, -} - -@Injectable() -export class LibraryRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - get(id: string, withDeleted = false) { - return this.db - .selectFrom('library') - .selectAll('library') - .where('library.id', '=', id) - .$if(!withDeleted, (qb) => qb.where('library.deletedAt', 'is', null)) - .executeTakeFirst(); - } - - @GenerateSql({ params: [] }) - getAll(withDeleted = false) { - return this.db - .selectFrom('library') - .selectAll('library') - .orderBy('createdAt', 'asc') - .$if(!withDeleted, (qb) => qb.where('library.deletedAt', 'is', null)) - .execute(); - } - - @GenerateSql() - getAllDeleted() { - return this.db - .selectFrom('library') - .selectAll('library') - .where('library.deletedAt', 'is not', null) - .orderBy('createdAt', 'asc') - .execute(); - } - - create(library: Insertable) { - return this.db.insertInto('library').values(library).returningAll().executeTakeFirstOrThrow(); - } - - async delete(id: string) { - await this.db.deleteFrom('library').where('library.id', '=', id).execute(); - } - - async softDelete(id: string) { - await this.db.updateTable('library').set({ deletedAt: new Date() }).where('library.id', '=', id).execute(); - } - - update(id: string, library: Updateable) { - return this.db - .updateTable('library') - .set(library) - .where('library.id', '=', id) - .returningAll() - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async getStatistics(id: string): Promise { - const stats = await this.db - .selectFrom('library') - .innerJoin('asset', 'asset.libraryId', 'library.id') - .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') - .select((eb) => - eb.fn - .countAll() - .filterWhere((eb) => - eb.and([eb('asset.type', '=', AssetType.Image), eb('asset.visibility', '!=', AssetVisibility.Hidden)]), - ) - .as('photos'), - ) - .select((eb) => - eb.fn - .countAll() - .filterWhere((eb) => - eb.and([eb('asset.type', '=', AssetType.Video), eb('asset.visibility', '!=', AssetVisibility.Hidden)]), - ) - .as('videos'), - ) - .select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('asset_exif.fileSizeInByte'), eb.val(0)).as('usage')) - .groupBy('library.id') - .where('library.id', '=', id) - .executeTakeFirst(); - - // possibly a new library with 0 assets - if (!stats) { - const zero = sql`0::int`; - return this.db - .selectFrom('library') - .select(zero.as('photos')) - .select(zero.as('videos')) - .select(zero.as('usage')) - .select(zero.as('total')) - .where('library.id', '=', id) - .executeTakeFirst(); - } - - return { - photos: stats.photos, - videos: stats.videos, - usage: stats.usage, - total: stats.photos + stats.videos, - }; - } - - streamAssetIds(libraryId: string) { - return this.db.selectFrom('asset').select(['id']).where('libraryId', '=', libraryId).stream(); - } -} diff --git a/server/src/repositories/logging.repository.spec.ts b/server/src/repositories/logging.repository.spec.ts deleted file mode 100644 index 99bb1dbf18..0000000000 --- a/server/src/repositories/logging.repository.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ClsService } from 'nestjs-cls'; -import { ImmichWorker } from 'src/enum'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { LoggingRepository, MyConsoleLogger } from 'src/repositories/logging.repository'; -import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; -import { Mocked } from 'vitest'; - -describe(LoggingRepository.name, () => { - let sut: LoggingRepository; - - let configMock: Mocked; - let clsMock: Mocked; - - beforeEach(() => { - configMock = newConfigRepositoryMock(); - clsMock = { - getId: vitest.fn(), - } as unknown as Mocked; - }); - - describe(MyConsoleLogger.name, () => { - describe('formatContext', () => { - it('should use colors', () => { - sut = new LoggingRepository(clsMock, configMock); - sut.setAppName(ImmichWorker.Api); - - const logger = new MyConsoleLogger(clsMock, { color: true }); - - expect(logger.formatContext('context')).toBe('\u001B[33m[Api:context]\u001B[39m '); - }); - - it('should not use colors when color is false', () => { - sut = new LoggingRepository(clsMock, configMock); - sut.setAppName(ImmichWorker.Api); - - const logger = new MyConsoleLogger(clsMock, { color: false }); - - expect(logger.formatContext('context')).toBe('[Api:context] '); - }); - }); - }); -}); diff --git a/server/src/repositories/machine-learning.repository.ts b/server/src/repositories/machine-learning.repository.ts deleted file mode 100644 index 49778b5193..0000000000 --- a/server/src/repositories/machine-learning.repository.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Duration } from 'luxon'; -import { readFile } from 'node:fs/promises'; -import { MachineLearningConfig } from 'src/config'; -import { CLIPConfig } from 'src/dtos/model-config.dto'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -export interface BoundingBox { - x1: number; - y1: number; - x2: number; - y2: number; -} - -export enum ModelTask { - FACIAL_RECOGNITION = 'facial-recognition', - SEARCH = 'clip', - OCR = 'ocr', -} - -export enum ModelType { - DETECTION = 'detection', - PIPELINE = 'pipeline', - RECOGNITION = 'recognition', - TEXTUAL = 'textual', - VISUAL = 'visual', - OCR = 'ocr', -} - -export type ModelPayload = { imagePath: string } | { text: string }; - -type ModelOptions = { modelName: string }; - -export type FaceDetectionOptions = ModelOptions & { minScore: number }; -export type OcrOptions = ModelOptions & { - minDetectionScore: number; - minRecognitionScore: number; - maxResolution: number; -}; -type VisualResponse = { imageHeight: number; imageWidth: number }; -export type ClipVisualRequest = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: ModelOptions } }; -export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse; - -export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } }; -export type ClipTextualResponse = { [ModelTask.SEARCH]: string }; - -export type OCR = { - text: string[]; - box: number[]; - boxScore: number[]; - textScore: number[]; -}; - -export type OcrRequest = { - [ModelTask.OCR]: { - [ModelType.DETECTION]: ModelOptions & { options: { minScore: number; maxResolution: number } }; - [ModelType.RECOGNITION]: ModelOptions & { options: { minScore: number } }; - }; -}; -export type OcrResponse = { [ModelTask.OCR]: OCR } & VisualResponse; - -export type FacialRecognitionRequest = { - [ModelTask.FACIAL_RECOGNITION]: { - [ModelType.DETECTION]: ModelOptions & { options: { minScore: number } }; - [ModelType.RECOGNITION]: ModelOptions; - }; -}; - -export interface Face { - boundingBox: BoundingBox; - embedding: string; - score: number; -} - -export type FacialRecognitionResponse = { [ModelTask.FACIAL_RECOGNITION]: Face[] } & VisualResponse; -export type DetectedFaces = { faces: Face[] } & VisualResponse; -export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest | OcrRequest; -export type TextEncodingOptions = ModelOptions & { language?: string }; - -@Injectable() -export class MachineLearningRepository { - private healthyMap: Record = {}; - private interval?: ReturnType; - private _config?: MachineLearningConfig; - - private get config(): MachineLearningConfig { - if (!this._config) { - throw new Error('Machine learning repository not been setup'); - } - - return this._config; - } - - constructor(private logger: LoggingRepository) { - this.logger.setContext(MachineLearningRepository.name); - } - - setup(config: MachineLearningConfig) { - this._config = config; - this.teardown(); - - // delete old servers - for (const url of Object.keys(this.healthyMap)) { - if (!config.urls.includes(url)) { - delete this.healthyMap[url]; - } - } - - if (!config.enabled || !config.availabilityChecks.enabled) { - return; - } - - this.tick(); - this.interval = setInterval( - () => this.tick(), - Duration.fromObject({ milliseconds: config.availabilityChecks.interval }).as('milliseconds'), - ); - } - - teardown() { - if (this.interval) { - clearInterval(this.interval); - } - } - - private tick() { - for (const url of this.config.urls) { - void this.check(url); - } - } - - private async check(url: string) { - let healthy = false; - try { - const response = await fetch(new URL('/ping', url), { - signal: AbortSignal.timeout(this.config.availabilityChecks.timeout), - }); - if (response.ok) { - healthy = true; - } - } catch { - // nothing to do here - } - - this.setHealthy(url, healthy); - } - - private setHealthy(url: string, healthy: boolean) { - if (this.healthyMap[url] !== healthy) { - this.logger.log(`Machine learning server became ${healthy ? 'healthy' : 'unhealthy'} (${url}).`); - } - - this.healthyMap[url] = healthy; - } - - private isHealthy(url: string) { - if (!this.config.availabilityChecks.enabled) { - return true; - } - - return this.healthyMap[url]; - } - - private async predict(payload: ModelPayload, config: MachineLearningRequest): Promise { - const formData = await this.getFormData(payload, config); - - for (const url of [ - // try healthy servers first - ...this.config.urls.filter((url) => this.isHealthy(url)), - ...this.config.urls.filter((url) => !this.isHealthy(url)), - ]) { - try { - const response = await fetch(new URL('/predict', url), { method: 'POST', body: formData }); - if (response.ok) { - this.setHealthy(url, true); - return response.json(); - } - - this.logger.warn( - `Machine learning request to "${url}" failed with status ${response.status}: ${response.statusText}`, - ); - } catch (error: Error | unknown) { - this.logger.warn( - `Machine learning request to "${url}" failed: ${error instanceof Error ? error.message : error}`, - ); - } - - this.setHealthy(url, false); - } - - throw new Error(`Machine learning request '${JSON.stringify(config)}' failed for all URLs`); - } - - async detectFaces(imagePath: string, { modelName, minScore }: FaceDetectionOptions) { - const request = { - [ModelTask.FACIAL_RECOGNITION]: { - [ModelType.DETECTION]: { modelName, options: { minScore } }, - [ModelType.RECOGNITION]: { modelName }, - }, - }; - const response = await this.predict({ imagePath }, request); - return { - imageHeight: response.imageHeight, - imageWidth: response.imageWidth, - faces: response[ModelTask.FACIAL_RECOGNITION], - }; - } - - async encodeImage(imagePath: string, { modelName }: CLIPConfig) { - const request = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: { modelName } } }; - const response = await this.predict({ imagePath }, request); - return response[ModelTask.SEARCH]; - } - - async encodeText(text: string, { language, modelName }: TextEncodingOptions) { - const request = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: { modelName, options: { language } } } }; - const response = await this.predict({ text }, request); - return response[ModelTask.SEARCH]; - } - - async ocr(imagePath: string, { modelName, minDetectionScore, minRecognitionScore, maxResolution }: OcrOptions) { - const request = { - [ModelTask.OCR]: { - [ModelType.DETECTION]: { modelName, options: { minScore: minDetectionScore, maxResolution } }, - [ModelType.RECOGNITION]: { modelName, options: { minScore: minRecognitionScore } }, - }, - }; - const response = await this.predict({ imagePath }, request); - return response[ModelTask.OCR]; - } - - private async getFormData(payload: ModelPayload, config: MachineLearningRequest): Promise { - const formData = new FormData(); - formData.append('entries', JSON.stringify(config)); - - if ('imagePath' in payload) { - const fileBuffer = await readFile(payload.imagePath); - formData.append('image', new Blob([new Uint8Array(fileBuffer)])); - } else if ('text' in payload) { - formData.append('text', payload.text); - } else { - throw new Error('Invalid input'); - } - - return formData; - } -} diff --git a/server/src/repositories/map.repository.ts b/server/src/repositories/map.repository.ts deleted file mode 100644 index 304cf89c32..0000000000 --- a/server/src/repositories/map.repository.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { getName } from 'i18n-iso-countries'; -import { Expression, Insertable, Kysely, NotNull, sql, SqlBool } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { createReadStream, existsSync } from 'node:fs'; -import { readFile } from 'node:fs/promises'; -import readLine from 'node:readline'; -import { citiesFile, reverseGeocodeMaxDistance } from 'src/constants'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetVisibility, SystemMetadataKey } from 'src/enum'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { DB } from 'src/schema'; -import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table'; -import { NaturalEarthCountriesTable } from 'src/schema/tables/natural-earth-countries.table'; - -export interface MapMarkerSearchOptions { - isArchived?: boolean; - isFavorite?: boolean; - fileCreatedBefore?: Date; - fileCreatedAfter?: Date; -} - -export interface GeoPoint { - latitude: number; - longitude: number; -} - -export interface ReverseGeocodeResult { - country: string | null; - state: string | null; - city: string | null; -} - -export interface MapMarker extends ReverseGeocodeResult { - id: string; - lat: number; - lon: number; -} - -interface MapDB extends DB { - geodata_places_tmp: GeodataPlacesTable; - naturalearth_countries_tmp: NaturalEarthCountriesTable; -} - -@Injectable() -export class MapRepository { - constructor( - private configRepository: ConfigRepository, - private metadataRepository: SystemMetadataRepository, - private logger: LoggingRepository, - @InjectKysely() private db: Kysely, - ) { - this.logger.setContext(MapRepository.name); - } - - async init(): Promise { - this.logger.log('Initializing metadata repository'); - const { resourcePaths } = this.configRepository.getEnv(); - const geodataDate = await readFile(resourcePaths.geodata.dateFile, 'utf8'); - - // TODO move to service init - const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.ReverseGeocodingState); - if (geocodingMetadata?.lastUpdate === geodataDate) { - return; - } - - await Promise.all([this.importGeodata(), this.importNaturalEarthCountries()]); - - await this.metadataRepository.set(SystemMetadataKey.ReverseGeocodingState, { - lastUpdate: geodataDate, - lastImportFileName: citiesFile, - }); - - this.logger.log('Geodata import completed'); - } - - @GenerateSql({ params: [[DummyValue.UUID], [DummyValue.UUID]] }) - getMapMarkers( - ownerIds: string[], - albumIds: string[], - { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore }: MapMarkerSearchOptions = {}, - ) { - return this.db - .selectFrom('asset') - .innerJoin('asset_exif', (builder) => - builder - .onRef('asset.id', '=', 'asset_exif.assetId') - .on('asset_exif.latitude', 'is not', null) - .on('asset_exif.longitude', 'is not', null), - ) - .select([ - 'id', - 'asset_exif.latitude as lat', - 'asset_exif.longitude as lon', - 'asset_exif.city', - 'asset_exif.state', - 'asset_exif.country', - ]) - .$narrowType<{ lat: NotNull; lon: NotNull }>() - .$if(isArchived === true, (qb) => - qb.where((eb) => - eb.or([ - eb('asset.visibility', '=', AssetVisibility.Timeline), - eb('asset.visibility', '=', AssetVisibility.Archive), - ]), - ), - ) - .$if(isArchived === false || isArchived === undefined, (qb) => - qb.where('asset.visibility', '=', AssetVisibility.Timeline), - ) - .$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!)) - .$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!)) - .$if(fileCreatedBefore !== undefined, (q) => q.where('fileCreatedAt', '<=', fileCreatedBefore!)) - .where('deletedAt', 'is', null) - .where((eb) => { - const expression: Expression[] = []; - - if (ownerIds.length > 0) { - expression.push(eb('ownerId', 'in', ownerIds)); - } - - if (albumIds.length > 0) { - expression.push( - eb.exists((eb) => - eb - .selectFrom('album_asset') - .whereRef('asset.id', '=', 'album_asset.assetId') - .where('album_asset.albumId', 'in', albumIds), - ), - ); - } - - return eb.or(expression); - }) - .orderBy('fileCreatedAt', 'desc') - .execute(); - } - - async reverseGeocode(point: GeoPoint): Promise { - this.logger.debug(`Request: ${point.latitude},${point.longitude}`); - - const response = await this.db - .selectFrom('geodata_places') - .selectAll() - .where( - sql`earth_box(ll_to_earth_public(${point.latitude}, ${point.longitude}), ${reverseGeocodeMaxDistance})`, - '@>', - sql`ll_to_earth_public(latitude, longitude)`, - ) - .orderBy( - sql`(earth_distance(ll_to_earth_public(${point.latitude}, ${point.longitude}), ll_to_earth_public(latitude, longitude)))`, - ) - .limit(1) - .executeTakeFirst(); - - if (response) { - this.logger.verboseFn(() => `Raw: ${JSON.stringify(response, null, 2)}`); - - const { countryCode, name: city, admin1Name } = response; - const country = getName(countryCode, 'en') ?? null; - const state = admin1Name; - - return { country, state, city }; - } - - this.logger.log( - `Empty response from database for city reverse geocoding lat: ${point.latitude}, lon: ${point.longitude}. Likely cause: no nearby large populated place (500+ within ${reverseGeocodeMaxDistance / 1000}km). Falling back to country boundaries.`, - ); - - const ne_response = await this.db - .selectFrom('naturalearth_countries') - .selectAll() - .where('coordinates', '@>', sql`point(${point.longitude}, ${point.latitude})`) - .limit(1) - .executeTakeFirst(); - - if (!ne_response) { - this.logger.log( - `Empty response from database for natural earth country reverse geocoding lat: ${point.latitude}, lon: ${point.longitude}`, - ); - - return { country: null, state: null, city: null }; - } - - this.logger.verboseFn(() => `Raw: ${JSON.stringify(ne_response, ['id', 'admin', 'admin_a3', 'type'], 2)}`); - - const { admin_a3 } = ne_response; - const country = getName(admin_a3, 'en') ?? null; - const state = null; - const city = null; - - return { country, state, city }; - } - - private async importNaturalEarthCountries() { - const { resourcePaths } = this.configRepository.getEnv(); - const geoJSONData = JSON.parse(await readFile(resourcePaths.geodata.naturalEarthCountriesPath, 'utf8')); - if (geoJSONData.type !== 'FeatureCollection' || !Array.isArray(geoJSONData.features)) { - this.logger.fatal('Invalid GeoJSON FeatureCollection'); - return; - } - - const entities: Insertable[] = []; - for (const feature of geoJSONData.features) { - for (const entry of feature.geometry.coordinates) { - const coordinates: number[][][] = feature.geometry.type === 'MultiPolygon' ? entry[0] : entry; - const featureRecord: Insertable = { - admin: feature.properties.ADMIN, - admin_a3: feature.properties.ADM0_A3, - type: feature.properties.TYPE, - coordinates: `(${coordinates.map((point) => `(${point[0]},${point[1]})`).join(', ')})`, - }; - entities.push(featureRecord); - if (feature.geometry.type === 'Polygon') { - break; - } - } - } - - await this.db.transaction().execute(async (manager) => { - await sql`CREATE TABLE naturalearth_countries_tmp - ( - LIKE naturalearth_countries INCLUDING ALL EXCLUDING INDEXES - )`.execute(manager); - await manager.schema.dropTable('naturalearth_countries').execute(); - await manager.schema.alterTable('naturalearth_countries_tmp').renameTo('naturalearth_countries').execute(); - }); - - await this.db.insertInto('naturalearth_countries').values(entities).execute(); - await sql`ALTER TABLE naturalearth_countries ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db); - } - - private async importGeodata() { - const { resourcePaths } = this.configRepository.getEnv(); - const [admin1, admin2] = await Promise.all([ - this.loadAdmin(resourcePaths.geodata.admin1), - this.loadAdmin(resourcePaths.geodata.admin2), - ]); - - await this.db.schema.dropTable('geodata_places_tmp').ifExists().execute(); - await this.db.transaction().execute(async (manager) => { - await sql`CREATE TABLE geodata_places_tmp - ( - LIKE geodata_places INCLUDING ALL EXCLUDING INDEXES - )`.execute(manager); - await manager.schema.dropTable('geodata_places').execute(); - await manager.schema.alterTable('geodata_places_tmp').renameTo('geodata_places').execute(); - }); - await this.db.schema - .createIndex('IDX_geodata_gist_earthcoord') - .on('geodata_places') - .using('gist') - .expression(sql`ll_to_earth_public(latitude, longitude)`) - .execute(); - await this.loadCities500(admin1, admin2); - await this.createGeodataIndices(); - } - - private async loadCities500(admin1Map: Map, admin2Map: Map) { - const { resourcePaths } = this.configRepository.getEnv(); - const cities500 = resourcePaths.geodata.cities500; - if (!existsSync(cities500)) { - throw new Error(`Geodata file ${cities500} not found`); - } - - this.logger.log(`Starting geodata import`); - const startTime = performance.now(); - - const input = createReadStream(cities500, { highWaterMark: 512 * 1024 * 1024 }); - let bufferGeodata = []; - const lineReader = readLine.createInterface({ input }); - let count = 0; - - let futures = []; - for await (const line of lineReader) { - const lineSplit = line.split('\t'); - if ((lineSplit[7] === 'PPLX' && lineSplit[8] !== 'AU') || lineSplit[7] === 'PPLH') { - continue; - } - - const geoData = { - id: Number.parseInt(lineSplit[0]), - name: lineSplit[1], - alternateNames: lineSplit[3], - latitude: Number.parseFloat(lineSplit[4]), - longitude: Number.parseFloat(lineSplit[5]), - countryCode: lineSplit[8], - admin1Code: lineSplit[10], - admin2Code: lineSplit[11], - modificationDate: lineSplit[18], - admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`) ?? null, - admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`) ?? null, - }; - bufferGeodata.push(geoData); - if (bufferGeodata.length >= 5000) { - const curLength = bufferGeodata.length; - futures.push( - this.db - .insertInto('geodata_places') - .values(bufferGeodata) - .execute() - .then(() => { - count += curLength; - if (count % 10_000 === 0) { - this.logger.log(`${count} geodata records imported`); - } - }), - ); - bufferGeodata = []; - // leave spare connection for other queries - if (futures.length >= 9) { - await Promise.all(futures); - futures = []; - } - } - } - - if (bufferGeodata.length > 0) { - await this.db.insertInto('geodata_places').values(bufferGeodata).execute(); - count += bufferGeodata.length; - } - - await Promise.all(futures); - - const duration = performance.now() - startTime; - const seconds = duration / 1000; - const recordsPerSecond = Math.round(count / seconds); - - this.logger.log( - `Successfully imported ${count} geodata records in ${seconds.toFixed(2)}s (${recordsPerSecond} records/second)`, - ); - } - - private async loadAdmin(filePath: string) { - if (!existsSync(filePath)) { - this.logger.error(`Geodata file ${filePath} not found`); - throw new Error(`Geodata file ${filePath} not found`); - } - - const input = createReadStream(filePath, { highWaterMark: 512 * 1024 * 1024 }); - const lineReader = readLine.createInterface({ input }); - - const adminMap = new Map(); - for await (const line of lineReader) { - const lineSplit = line.split('\t'); - adminMap.set(lineSplit[0], lineSplit[1]); - } - - return adminMap; - } - - private createGeodataIndices() { - return Promise.all([ - sql`ALTER TABLE geodata_places ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db), - this.db.schema - .createIndex(`idx_geodata_places_alternate_names`) - .on('geodata_places') - .using('gin (f_unaccent("alternateNames") gin_trgm_ops)') - .execute(), - this.db.schema - .createIndex(`idx_geodata_places_name`) - .on('geodata_places') - .using('gin (f_unaccent(name) gin_trgm_ops)') - .execute(), - this.db.schema - .createIndex(`idx_geodata_places_admin1_name`) - .on('geodata_places') - .using('gin (f_unaccent("admin1Name") gin_trgm_ops)') - .execute(), - this.db.schema - .createIndex(`idx_geodata_places_admin2_name`) - .on('geodata_places') - .using('gin (f_unaccent("admin2Name") gin_trgm_ops)') - .execute(), - ]); - } -} diff --git a/server/src/repositories/media.repository.spec.ts b/server/src/repositories/media.repository.spec.ts deleted file mode 100644 index a5380852ee..0000000000 --- a/server/src/repositories/media.repository.spec.ts +++ /dev/null @@ -1,667 +0,0 @@ -import sharp from 'sharp'; -import { AssetFace } from 'src/database'; -import { AssetEditAction, MirrorAxis } from 'src/dtos/editing.dto'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { SourceType } from 'src/enum'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { BoundingBox } from 'src/repositories/machine-learning.repository'; -import { MediaRepository } from 'src/repositories/media.repository'; -import { checkFaceVisibility, checkOcrVisibility } from 'src/utils/editor'; -import { automock } from 'test/utils'; - -const getPixelColor = async (buffer: Buffer, x: number, y: number) => { - const metadata = await sharp(buffer).metadata(); - const width = metadata.width!; - const { data } = await sharp(buffer).raw().toBuffer({ resolveWithObject: true }); - const idx = (y * width + x) * 4; - return { - r: data[idx], - g: data[idx + 1], - b: data[idx + 2], - }; -}; - -const buildTestQuadImage = async () => { - // build a 4 quadrant image for testing mirroring - const base = sharp({ - create: { width: 1000, height: 1000, channels: 3, background: { r: 0, g: 0, b: 0 } }, - }).png(); - - const tl = await sharp({ - create: { width: 500, height: 500, channels: 3, background: { r: 255, g: 0, b: 0 } }, - }) - .png() - .toBuffer(); - - const tr = await sharp({ - create: { width: 500, height: 500, channels: 3, background: { r: 0, g: 255, b: 0 } }, - }) - .png() - .toBuffer(); - - const bl = await sharp({ - create: { width: 500, height: 500, channels: 3, background: { r: 0, g: 0, b: 255 } }, - }) - .png() - .toBuffer(); - - const br = await sharp({ - create: { width: 500, height: 500, channels: 3, background: { r: 255, g: 255, b: 0 } }, - }) - .png() - .toBuffer(); - - const image = base.composite([ - { input: tl, left: 0, top: 0 }, // top-left - { input: tr, left: 500, top: 0 }, // top-right - { input: bl, left: 0, top: 500 }, // bottom-left - { input: br, left: 500, top: 500 }, // bottom-right - ]); - - return image.png().toBuffer(); -}; - -describe(MediaRepository.name, () => { - let sut: MediaRepository; - - beforeEach(() => { - // eslint-disable-next-line no-sparse-arrays - sut = new MediaRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false })); - }); - - describe('applyEdits (single actions)', () => { - it('should apply crop edit correctly', async () => { - const result = await sut['applyEdits']( - sharp({ - create: { - width: 1000, - height: 1000, - channels: 4, - background: { r: 255, g: 0, b: 0, alpha: 0.5 }, - }, - }).png(), - [ - { - action: AssetEditAction.Crop, - parameters: { - x: 100, - y: 200, - width: 700, - height: 300, - }, - }, - ], - ); - - const metadata = await result.toBuffer().then((buf) => sharp(buf).metadata()); - expect(metadata.width).toBe(700); - expect(metadata.height).toBe(300); - }); - it('should apply rotate edit correctly', async () => { - const result = await sut['applyEdits']( - sharp({ - create: { - width: 500, - height: 1000, - channels: 4, - background: { r: 255, g: 0, b: 0, alpha: 0.5 }, - }, - }).png(), - [ - { - action: AssetEditAction.Rotate, - parameters: { - angle: 90, - }, - }, - ], - ); - - const metadata = await result.toBuffer().then((buf) => sharp(buf).metadata()); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(500); - }); - - it('should apply mirror edit correctly', async () => { - const resultHorizontal = await sut['applyEdits'](sharp(await buildTestQuadImage()), [ - { - action: AssetEditAction.Mirror, - parameters: { - axis: MirrorAxis.Horizontal, - }, - }, - ]); - - const bufferHorizontal = await resultHorizontal.toBuffer(); - const metadataHorizontal = await resultHorizontal.metadata(); - expect(metadataHorizontal.width).toBe(1000); - expect(metadataHorizontal.height).toBe(1000); - - expect(await getPixelColor(bufferHorizontal, 10, 10)).toEqual({ r: 0, g: 255, b: 0 }); - expect(await getPixelColor(bufferHorizontal, 990, 10)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(bufferHorizontal, 10, 990)).toEqual({ r: 255, g: 255, b: 0 }); - expect(await getPixelColor(bufferHorizontal, 990, 990)).toEqual({ r: 0, g: 0, b: 255 }); - - const resultVertical = await sut['applyEdits'](sharp(await buildTestQuadImage()), [ - { - action: AssetEditAction.Mirror, - parameters: { - axis: MirrorAxis.Vertical, - }, - }, - ]); - - const bufferVertical = await resultVertical.toBuffer(); - const metadataVertical = await resultVertical.metadata(); - expect(metadataVertical.width).toBe(1000); - expect(metadataVertical.height).toBe(1000); - - // top-left should now be bottom-left (blue) - expect(await getPixelColor(bufferVertical, 10, 10)).toEqual({ r: 0, g: 0, b: 255 }); - // top-right should now be bottom-right (yellow) - expect(await getPixelColor(bufferVertical, 990, 10)).toEqual({ r: 255, g: 255, b: 0 }); - // bottom-left should now be top-left (red) - expect(await getPixelColor(bufferVertical, 10, 990)).toEqual({ r: 255, g: 0, b: 0 }); - // bottom-right should now be top-right (blue) - expect(await getPixelColor(bufferVertical, 990, 990)).toEqual({ r: 0, g: 255, b: 0 }); - }); - }); - - describe('applyEdits (multiple sequential edits)', () => { - it('should apply horizontal mirror then vertical mirror (equivalent to 180° rotation)', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(1000); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 0, g: 0, b: 255 }); - expect(await getPixelColor(buffer, 10, 990)).toEqual({ r: 0, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 990)).toEqual({ r: 255, g: 0, b: 0 }); - }); - - it('should apply rotate 90° then horizontal mirror', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(1000); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 0, g: 0, b: 255 }); - expect(await getPixelColor(buffer, 10, 990)).toEqual({ r: 0, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 990)).toEqual({ r: 255, g: 255, b: 0 }); - }); - - it('should apply 180° rotation', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Rotate, parameters: { angle: 180 } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(1000); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 0, g: 0, b: 255 }); - expect(await getPixelColor(buffer, 10, 990)).toEqual({ r: 0, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 990)).toEqual({ r: 255, g: 0, b: 0 }); - }); - - it('should apply 270° rotations', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Rotate, parameters: { angle: 270 } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(1000); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 0, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 255, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 10, 990)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 990, 990)).toEqual({ r: 0, g: 0, b: 255 }); - }); - - it('should apply crop then rotate 90°', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 1000, height: 500 } }, - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(500); - expect(metadata.height).toBe(1000); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 10, 990)).toEqual({ r: 0, g: 255, b: 0 }); - }); - - it('should apply rotate 90° then crop', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 1000 } }, - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(500); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 0, g: 0, b: 255 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 255, g: 0, b: 0 }); - }); - - it('should apply vertical mirror then horizontal mirror then rotate 90°', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(1000); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 0, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 255, g: 255, b: 0 }); - expect(await getPixelColor(buffer, 10, 990)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 990, 990)).toEqual({ r: 0, g: 0, b: 255 }); - }); - - it('should apply crop to single quadrant then mirror', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 500 } }, - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(500); - expect(metadata.height).toBe(500); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 490, 10)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 10, 490)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 490, 490)).toEqual({ r: 255, g: 0, b: 0 }); - }); - - it('should apply all operations: crop, rotate, mirror', async () => { - const imageBuffer = await buildTestQuadImage(); - const result = await sut['applyEdits'](sharp(imageBuffer), [ - { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 1000 } }, - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - ]); - - const buffer = await result.png().toBuffer(); - const metadata = await sharp(buffer).metadata(); - expect(metadata.width).toBe(1000); - expect(metadata.height).toBe(500); - - expect(await getPixelColor(buffer, 10, 10)).toEqual({ r: 255, g: 0, b: 0 }); - expect(await getPixelColor(buffer, 990, 10)).toEqual({ r: 0, g: 0, b: 255 }); - }); - }); - - describe('checkFaceVisibility', () => { - const baseFace: AssetFace = { - id: 'face-1', - assetId: 'asset-1', - personId: 'person-1', - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - sourceType: SourceType.MachineLearning, - isVisible: true, - updatedAt: new Date(), - deletedAt: null, - updateId: '', - }; - - const assetDimensions = { width: 1000, height: 800 }; - - describe('with no crop edit', () => { - it('should return only currently invisible faces when no crop is provided', () => { - const visibleFace = { ...baseFace, id: 'face-visible', isVisible: true }; - const invisibleFace = { ...baseFace, id: 'face-invisible', isVisible: false }; - const faces = [visibleFace, invisibleFace]; - const result = checkFaceVisibility(faces, assetDimensions); - - expect(result.visible).toEqual([invisibleFace]); - expect(result.hidden).toEqual([]); - }); - - it('should return empty arrays when all faces are already visible and no crop is provided', () => { - const faces = [baseFace]; - const result = checkFaceVisibility(faces, assetDimensions); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual([]); - }); - - it('should return all faces when all are invisible and no crop is provided', () => { - const face1 = { ...baseFace, id: 'face-1', isVisible: false }; - const face2 = { ...baseFace, id: 'face-2', isVisible: false }; - const faces = [face1, face2]; - const result = checkFaceVisibility(faces, assetDimensions); - - expect(result.visible).toEqual([face1, face2]); - expect(result.hidden).toEqual([]); - }); - }); - - describe('with crop edit', () => { - it('should mark face as visible when fully inside crop area', () => { - const crop: BoundingBox = { x1: 0, y1: 0, x2: 500, y2: 400 }; - const faces = [baseFace]; - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toEqual(faces); - expect(result.hidden).toEqual([]); - }); - - it('should mark face as visible when more than 50% inside crop area', () => { - const crop: BoundingBox = { x1: 150, y1: 150, x2: 650, y2: 550 }; - // Face at (100,100)-(200,200), crop starts at (150,150) - // Overlap: (150,150)-(200,200) = 50x50 = 2500 - // Face area: 100x100 = 10000 - // Overlap percentage: 25% - should be hidden - const faces = [baseFace]; - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual(faces); - }); - - it('should mark face as hidden when less than 50% inside crop area', () => { - const crop: BoundingBox = { x1: 250, y1: 250, x2: 750, y2: 650 }; - // Face completely outside crop area - const faces = [baseFace]; - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual(faces); - }); - - it('should mark face as hidden when completely outside crop area', () => { - const crop: BoundingBox = { x1: 500, y1: 500, x2: 700, y2: 700 }; - const faces = [baseFace]; - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual(faces); - }); - - it('should handle multiple faces with mixed visibility', () => { - const crop: BoundingBox = { x1: 0, y1: 0, x2: 300, y2: 300 }; - const faceInside: AssetFace = { - ...baseFace, - id: 'face-inside', - boundingBoxX1: 50, - boundingBoxY1: 50, - boundingBoxX2: 150, - boundingBoxY2: 150, - }; - const faceOutside: AssetFace = { - ...baseFace, - id: 'face-outside', - boundingBoxX1: 400, - boundingBoxY1: 400, - boundingBoxX2: 500, - boundingBoxY2: 500, - }; - const faces = [faceInside, faceOutside]; - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toEqual([faceInside]); - expect(result.hidden).toEqual([faceOutside]); - }); - - it('should handle face at exactly 50% overlap threshold', () => { - // Face at (0,0)-(100,100), crop at (50,0)-(150,100) - // Overlap: (50,0)-(100,100) = 50x100 = 5000 - // Face area: 100x100 = 10000 - // Overlap percentage: 50% - exactly at threshold, should be visible - const faceAtEdge: AssetFace = { - ...baseFace, - id: 'face-edge', - boundingBoxX1: 0, - boundingBoxY1: 0, - boundingBoxX2: 100, - boundingBoxY2: 100, - }; - const crop: BoundingBox = { x1: 50, y1: 0, x2: 150, y2: 100 }; - const faces = [faceAtEdge]; - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toEqual([faceAtEdge]); - expect(result.hidden).toEqual([]); - }); - }); - - describe('with scaled dimensions', () => { - it('should handle faces when asset dimensions differ from face image dimensions', () => { - // Face stored at 1000x800 resolution, but displaying at 500x400 - const scaledDimensions = { width: 500, height: 400 }; - const crop: BoundingBox = { x1: 0, y1: 0, x2: 250, y2: 200 }; - // Face at (100,100)-(200,200) on 1000x800 - // Scaled to 500x400: (50,50)-(100,100) - // Crop at (0,0)-(250,200) - face is fully inside - const faces = [baseFace]; - const result = checkFaceVisibility(faces, scaledDimensions, crop); - - expect(result.visible).toEqual(faces); - expect(result.hidden).toEqual([]); - }); - }); - }); - - describe('checkOcrVisibility', () => { - const baseOcr: AssetOcrResponseDto & { isVisible: boolean } = { - id: 'ocr-1', - assetId: 'asset-1', - x1: 0.1, - y1: 0.1, - x2: 0.2, - y2: 0.1, - x3: 0.2, - y3: 0.2, - x4: 0.1, - y4: 0.2, - boxScore: 0.9, - textScore: 0.85, - text: 'Test OCR', - isVisible: false, - }; - - const assetDimensions = { width: 1000, height: 800 }; - - describe('with no crop edit', () => { - it('should return only currently invisible OCR items when no crop is provided', () => { - const visibleOcr = { ...baseOcr, id: 'ocr-visible', isVisible: true }; - const invisibleOcr = { ...baseOcr, id: 'ocr-invisible', isVisible: false }; - const ocrs = [visibleOcr, invisibleOcr]; - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toEqual([invisibleOcr]); - expect(result.hidden).toEqual([]); - }); - - it('should return empty arrays when all OCR items are already visible and no crop is provided', () => { - const visibleOcr = { ...baseOcr, isVisible: true }; - const ocrs = [visibleOcr]; - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual([]); - }); - - it('should return all OCR items when all are invisible and no crop is provided', () => { - const ocr1 = { ...baseOcr, id: 'ocr-1', isVisible: false }; - const ocr2 = { ...baseOcr, id: 'ocr-2', isVisible: false }; - const ocrs = [ocr1, ocr2]; - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toEqual([ocr1, ocr2]); - expect(result.hidden).toEqual([]); - }); - }); - - describe('with crop edit', () => { - it('should mark OCR as visible when fully inside crop area', () => { - const crop: BoundingBox = { x1: 0, y1: 0, x2: 500, y2: 400 }; - // OCR box: (0.1,0.1)-(0.2,0.2) on 1000x800 = (100,80)-(200,160) - // Crop: (0,0)-(500,400) - OCR fully inside - const ocrs = [baseOcr]; - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toEqual(ocrs); - expect(result.hidden).toEqual([]); - }); - - it('should mark OCR as hidden when completely outside crop area', () => { - const crop: BoundingBox = { x1: 500, y1: 500, x2: 700, y2: 700 }; - // OCR box: (100,80)-(200,160) - completely outside crop - const ocrs = [baseOcr]; - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual(ocrs); - }); - - it('should mark OCR as hidden when less than 50% inside crop area', () => { - const crop: BoundingBox = { x1: 150, y1: 120, x2: 650, y2: 520 }; - // OCR box: (100,80)-(200,160) - // Crop: (150,120)-(650,520) - // Overlap: (150,120)-(200,160) = 50x40 = 2000 - // OCR area: 100x80 = 8000 - // Overlap percentage: 25% - should be hidden - const ocrs = [baseOcr]; - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toEqual([]); - expect(result.hidden).toEqual(ocrs); - }); - - it('should handle multiple OCR items with mixed visibility', () => { - const crop: BoundingBox = { x1: 0, y1: 0, x2: 300, y2: 300 }; - const ocrInside = { - ...baseOcr, - id: 'ocr-inside', - }; - const ocrOutside = { - ...baseOcr, - id: 'ocr-outside', - x1: 0.5, - y1: 0.5, - x2: 0.6, - y2: 0.5, - x3: 0.6, - y3: 0.6, - x4: 0.5, - y4: 0.6, - }; - const ocrs = [ocrInside, ocrOutside]; - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toEqual([ocrInside]); - expect(result.hidden).toEqual([ocrOutside]); - }); - - it('should handle OCR boxes with rotated/skewed polygons', () => { - // OCR with a rotated bounding box (not axis-aligned) - const rotatedOcr = { - ...baseOcr, - id: 'ocr-rotated', - x1: 0.15, - y1: 0.1, - x2: 0.25, - y2: 0.15, - x3: 0.2, - y3: 0.25, - x4: 0.1, - y4: 0.2, - }; - const crop: BoundingBox = { x1: 0, y1: 0, x2: 300, y2: 300 }; - const ocrs = [rotatedOcr]; - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toEqual([rotatedOcr]); - expect(result.hidden).toEqual([]); - }); - }); - - describe('visibility is only affected by crop (not rotate or mirror)', () => { - it('should keep all OCR items visible when there is no crop regardless of other transforms', () => { - // Rotate and mirror edits don't affect visibility - only crop does - // The visibility functions only take an optional crop parameter - const ocrs = [baseOcr]; - - // Without any crop, all OCR items remain visible - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toEqual(ocrs); - expect(result.hidden).toEqual([]); - }); - - it('should only consider crop for visibility calculation', () => { - // Even if the image will be rotated/mirrored, visibility is determined - // solely by whether the OCR box overlaps with the crop area - const crop: BoundingBox = { x1: 0, y1: 0, x2: 300, y2: 300 }; - - const ocrInsideCrop = { - ...baseOcr, - id: 'ocr-inside', - // OCR at (0.1,0.1)-(0.2,0.2) = (100,80)-(200,160) on 1000x800, inside crop - }; - - const ocrOutsideCrop = { - ...baseOcr, - id: 'ocr-outside', - x1: 0.5, - y1: 0.5, - x2: 0.6, - y2: 0.5, - x3: 0.6, - y3: 0.6, - x4: 0.5, - y4: 0.6, - // OCR at (500,400)-(600,480) on 1000x800, outside crop - }; - - const ocrs = [ocrInsideCrop, ocrOutsideCrop]; - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - // OCR inside crop area is visible, OCR outside is hidden - // This is true regardless of any subsequent rotate/mirror operations - expect(result.visible).toEqual([ocrInsideCrop]); - expect(result.hidden).toEqual([ocrOutsideCrop]); - }); - }); - }); -}); diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts deleted file mode 100644 index 33025e73cf..0000000000 --- a/server/src/repositories/media.repository.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ExifDateTime, exiftool, WriteTags } from 'exiftool-vendored'; -import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; -import { Duration } from 'luxon'; -import fs from 'node:fs/promises'; -import { Writable } from 'node:stream'; -import sharp from 'sharp'; -import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants'; -import { Exif } from 'src/database'; -import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { Colorspace, LogLevel, RawExtractedFormat } from 'src/enum'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { - DecodeToBufferOptions, - GenerateThumbhashOptions, - GenerateThumbnailOptions, - ImageDimensions, - ProbeOptions, - TranscodeCommand, - VideoInfo, -} from 'src/types'; -import { handlePromiseError } from 'src/utils/misc'; -import { createAffineMatrix } from 'src/utils/transform'; - -const probe = (input: string, options: string[]): Promise => - new Promise((resolve, reject) => - ffmpeg.ffprobe(input, options, (error, data) => (error ? reject(error) : resolve(data))), - ); -sharp.concurrency(0); -sharp.cache({ files: 0 }); - -type ProgressEvent = { - frames: number; - currentFps: number; - currentKbps: number; - targetSize: number; - timemark: string; - percent?: number; -}; - -export type ExtractResult = { - buffer: Buffer; - format: RawExtractedFormat; -}; - -@Injectable() -export class MediaRepository { - constructor(private logger: LoggingRepository) { - this.logger.setContext(MediaRepository.name); - } - - /** - * - * @param input file path to the input image - * @returns ExtractResult if succeeded, or null if failed - */ - async extract(input: string): Promise { - try { - const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input); - return { buffer, format: RawExtractedFormat.Jpeg }; - } catch (error: any) { - this.logger.debug(`Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next: ${error}`); - } - - try { - const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input); - return { buffer, format: RawExtractedFormat.Jpeg }; - } catch (error: any) { - this.logger.debug(`Could not extract JPEG buffer from image, trying PreviewJXL next: ${error}`); - } - - try { - const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input); - return { buffer, format: RawExtractedFormat.Jxl }; - } catch (error: any) { - this.logger.debug(`Could not extract PreviewJXL buffer from image, trying PreviewImage next: ${error}`); - } - - try { - const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input); - return { buffer, format: RawExtractedFormat.Jpeg }; - } catch (error: any) { - this.logger.debug(`Could not extract preview buffer from image: ${error}`); - return null; - } - } - - async writeExif(tags: Partial, output: string): Promise { - try { - const tagsToWrite: WriteTags = { - ExifImageWidth: tags.exifImageWidth, - ExifImageHeight: tags.exifImageHeight, - DateTimeOriginal: tags.dateTimeOriginal && ExifDateTime.fromMillis(tags.dateTimeOriginal.getTime()), - ModifyDate: tags.modifyDate && ExifDateTime.fromMillis(tags.modifyDate.getTime()), - TimeZone: tags.timeZone, - GPSLatitude: tags.latitude, - GPSLongitude: tags.longitude, - ProjectionType: tags.projectionType, - City: tags.city, - Country: tags.country, - Make: tags.make, - Model: tags.model, - LensModel: tags.lensModel, - Fnumber: tags.fNumber?.toFixed(1), - FocalLength: tags.focalLength?.toFixed(1), - ISO: tags.iso, - ExposureTime: tags.exposureTime, - ProfileDescription: tags.profileDescription, - ColorSpace: tags.colorspace, - Rating: tags.rating, - // specially convert Orientation to numeric Orientation# for exiftool - 'Orientation#': tags.orientation ? Number(tags.orientation) : undefined, - }; - - await exiftool.write(output, tagsToWrite, { - ignoreMinorErrors: true, - writeArgs: ['-overwrite_original'], - }); - return true; - } catch (error: any) { - this.logger.warn(`Could not write exif data to image: ${error.message}`); - return false; - } - } - - async copyTagGroup(tagGroup: string, source: string, target: string): Promise { - try { - await exiftool.write( - target, - {}, - { - ignoreMinorErrors: true, - writeArgs: ['-TagsFromFile', source, `-${tagGroup}:all>${tagGroup}:all`, '-overwrite_original'], - }, - ); - return true; - } catch (error: any) { - this.logger.warn(`Could not copy tag data to image: ${error.message}`); - return false; - } - } - - async decodeImage(input: string | Buffer, options: DecodeToBufferOptions) { - const pipeline = await this.getImageDecodingPipeline(input, options); - return pipeline.raw().toBuffer({ resolveWithObject: true }); - } - - private async applyEdits(pipeline: sharp.Sharp, edits: AssetEditActionItem[]): Promise { - const affineEditOperations = edits.filter((edit) => edit.action !== 'crop'); - const matrix = createAffineMatrix(affineEditOperations); - - const crop = edits.find((edit) => edit.action === 'crop'); - const dimensions = await pipeline.metadata(); - - if (crop) { - pipeline = pipeline.extract({ - left: crop ? Math.round(crop.parameters.x) : 0, - top: crop ? Math.round(crop.parameters.y) : 0, - width: crop ? Math.round(crop.parameters.width) : dimensions.width || 0, - height: crop ? Math.round(crop.parameters.height) : dimensions.height || 0, - }); - } - - const { a, b, c, d } = matrix; - pipeline = pipeline.affine([ - [a, b], - [c, d], - ]); - - return pipeline; - } - - async generateThumbnail(input: string | Buffer, options: GenerateThumbnailOptions, output: string): Promise { - const pipeline = await this.getImageDecodingPipeline(input, options); - const decoded = pipeline.toFormat(options.format, { - quality: options.quality, - // this is default in libvips (except the threshold is 90), but we need to set it manually in sharp - chromaSubsampling: options.quality >= 80 ? '4:4:4' : '4:2:0', - progressive: options.progressive, - }); - - await decoded.toFile(output); - } - - private async getImageDecodingPipeline(input: string | Buffer, options: DecodeToBufferOptions) { - let pipeline = sharp(input, { - // some invalid images can still be processed by sharp, but we want to fail on them by default to avoid crashes - failOn: options.processInvalidImages ? 'none' : 'error', - limitInputPixels: false, - raw: options.raw, - unlimited: true, - }) - .pipelineColorspace(options.colorspace === Colorspace.Srgb ? 'srgb' : 'rgb16') - .withIccProfile(options.colorspace); - - if (!options.raw) { - const { angle, flip, flop } = options.orientation ? ORIENTATION_TO_SHARP_ROTATION[options.orientation] : {}; - pipeline = pipeline.rotate(angle); - if (flip) { - pipeline = pipeline.flip(); - } - - if (flop) { - pipeline = pipeline.flop(); - } - } - - if (options.edits && options.edits.length > 0) { - pipeline = await this.applyEdits(pipeline, options.edits); - } - - if (options.size !== undefined) { - pipeline = pipeline.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }); - } - return pipeline; - } - - async generateThumbhash(input: string | Buffer, options: GenerateThumbhashOptions): Promise { - const [{ rgbaToThumbHash }, decodingPipeline] = await Promise.all([ - import('thumbhash'), - this.getImageDecodingPipeline(input, { - colorspace: options.colorspace, - processInvalidImages: options.processInvalidImages, - raw: options.raw, - edits: options.edits, - }), - ]); - - const pipeline = decodingPipeline.resize(100, 100, { fit: 'inside', withoutEnlargement: true }).raw().ensureAlpha(); - - const { data, info } = await pipeline.toBuffer({ resolveWithObject: true }); - - return Buffer.from(rgbaToThumbHash(info.width, info.height, data)); - } - - async probe(input: string, options?: ProbeOptions): Promise { - const results = await probe(input, options?.countFrames ? ['-count_packets'] : []); // gets frame count quickly: https://stackoverflow.com/a/28376817 - return { - format: { - formatName: results.format.format_name, - formatLongName: results.format.format_long_name, - duration: this.parseFloat(results.format.duration), - bitrate: this.parseInt(results.format.bit_rate), - }, - videoStreams: results.streams - .filter((stream) => stream.codec_type === 'video') - .filter((stream) => !stream.disposition?.attached_pic) - .map((stream) => ({ - index: stream.index, - height: this.parseInt(stream.height), - width: this.parseInt(stream.width), - codecName: stream.codec_name === 'h265' ? 'hevc' : stream.codec_name, - codecType: stream.codec_type, - frameCount: this.parseInt(options?.countFrames ? stream.nb_read_packets : stream.nb_frames), - rotation: this.parseInt(stream.rotation), - isHDR: stream.color_transfer === 'smpte2084' || stream.color_transfer === 'arib-std-b67', - bitrate: this.parseInt(stream.bit_rate), - pixelFormat: stream.pix_fmt || 'yuv420p', - colorPrimaries: stream.color_primaries, - colorSpace: stream.color_space, - colorTransfer: stream.color_transfer, - })), - audioStreams: results.streams - .filter((stream) => stream.codec_type === 'audio') - .map((stream) => ({ - index: stream.index, - codecType: stream.codec_type, - codecName: stream.codec_name, - bitrate: this.parseInt(stream.bit_rate), - })), - }; - } - - transcode(input: string, output: string | Writable, options: TranscodeCommand): Promise { - if (!options.twoPass) { - return new Promise((resolve, reject) => { - this.configureFfmpegCall(input, output, options) - .on('error', reject) - .on('end', () => resolve()) - .run(); - }); - } - - if (typeof output !== 'string') { - throw new TypeError('Two-pass transcoding does not support writing to a stream'); - } - - // two-pass allows for precise control of bitrate at the cost of running twice - // recommended for vp9 for better quality and compression - return new Promise((resolve, reject) => { - // first pass output is not saved as only the .log file is needed - this.configureFfmpegCall(input, '/dev/null', options) - .addOptions('-pass', '1') - .addOptions('-passlogfile', output) - .addOptions('-f null') - .on('error', reject) - .on('end', () => { - // second pass - this.configureFfmpegCall(input, output, options) - .addOptions('-pass', '2') - .addOptions('-passlogfile', output) - .on('error', reject) - .on('end', () => handlePromiseError(fs.unlink(`${output}-0.log`), this.logger)) - .on('end', () => handlePromiseError(fs.rm(`${output}-0.log.mbtree`, { force: true }), this.logger)) - .on('end', () => resolve()) - .run(); - }) - .run(); - }); - } - - async getImageDimensions(input: string | Buffer): Promise { - const { width = 0, height = 0 } = await sharp(input).metadata(); - return { width, height }; - } - - private configureFfmpegCall(input: string, output: string | Writable, options: TranscodeCommand) { - const ffmpegCall = ffmpeg(input, { niceness: 10 }) - .inputOptions(options.inputOptions) - .outputOptions(options.outputOptions) - .output(output) - .on('start', (command: string) => this.logger.debug(command)) - .on('error', (error, _, stderr) => this.logger.error(stderr || error)); - - const { frameCount, percentInterval } = options.progress; - const frameInterval = Math.ceil(frameCount / (100 / percentInterval)); - if (this.logger.isLevelEnabled(LogLevel.Debug) && frameCount && frameInterval) { - let lastProgressFrame: number = 0; - ffmpegCall.on('progress', (progress: ProgressEvent) => { - if (progress.frames - lastProgressFrame < frameInterval) { - return; - } - - lastProgressFrame = progress.frames; - const percent = ((progress.frames / frameCount) * 100).toFixed(2); - const ms = progress.currentFps ? Math.floor((frameCount - progress.frames) / progress.currentFps) * 1000 : 0; - const duration = ms ? Duration.fromMillis(ms).rescale().toHuman({ unitDisplay: 'narrow' }) : ''; - const outputText = output instanceof Writable ? 'stream' : output.split('/').pop(); - this.logger.debug( - `Transcoding ${percent}% done${duration ? `, estimated ${duration} remaining` : ''} for output ${outputText}`, - ); - }); - } - - return ffmpegCall; - } - - private parseInt(value: string | number | undefined): number { - return Number.parseInt(value as string) || 0; - } - - private parseFloat(value: string | number | undefined): number { - return Number.parseFloat(value as string) || 0; - } -} diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts deleted file mode 100644 index e62c083839..0000000000 --- a/server/src/repositories/memory.repository.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, OrderByDirection, sql, Updateable } from 'kysely'; -import { jsonArrayFrom } from 'kysely/helpers/postgres'; -import { DateTime } from 'luxon'; -import { InjectKysely } from 'nestjs-kysely'; -import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { MemorySearchDto } from 'src/dtos/memory.dto'; -import { AssetOrderWithRandom, AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { MemoryTable } from 'src/schema/tables/memory.table'; -import { IBulkAsset } from 'src/types'; - -@Injectable() -export class MemoryRepository implements IBulkAsset { - constructor(@InjectKysely() private db: Kysely) {} - - async cleanup() { - await this.db - .deleteFrom('memory_asset') - .using('asset') - .whereRef('memory_asset.assetId', '=', 'asset.id') - .where('asset.visibility', '!=', AssetVisibility.Timeline) - .execute(); - - return this.db - .deleteFrom('memory') - .where('createdAt', '<', DateTime.now().minus({ days: 30 }).toJSDate()) - .where('isSaved', '=', false) - .execute(); - } - - searchBuilder(ownerId: string, dto: MemorySearchDto) { - return this.db - .selectFrom('memory') - .$if(dto.isSaved !== undefined, (qb) => qb.where('isSaved', '=', dto.isSaved!)) - .$if(dto.type !== undefined, (qb) => qb.where('type', '=', dto.type!)) - .$if(dto.for !== undefined, (qb) => - qb - .where((where) => where.or([where('showAt', 'is', null), where('showAt', '<=', dto.for!)])) - .where((where) => where.or([where('hideAt', 'is', null), where('hideAt', '>=', dto.for!)])), - ) - .where('deletedAt', dto.isTrashed ? 'is not' : 'is', null) - .where('ownerId', '=', ownerId); - } - - @GenerateSql( - { params: [DummyValue.UUID, {}] }, - { name: 'date filter', params: [DummyValue.UUID, { for: DummyValue.DATE }] }, - ) - statistics(ownerId: string, dto: MemorySearchDto) { - return this.searchBuilder(ownerId, dto) - .select((qb) => qb.fn.countAll().as('total')) - .executeTakeFirstOrThrow(); - } - - @GenerateSql( - { params: [DummyValue.UUID, {}] }, - { name: 'date filter', params: [DummyValue.UUID, { for: DummyValue.DATE }] }, - ) - search(ownerId: string, dto: MemorySearchDto) { - return this.searchBuilder(ownerId, dto) - .select((eb) => - jsonArrayFrom( - eb - .selectFrom('asset') - .selectAll('asset') - .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetId') - .whereRef('memory_asset.memoriesId', '=', 'memory.id') - .orderBy('asset.fileCreatedAt', 'asc') - .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .where('asset.deletedAt', 'is', null), - ).as('assets'), - ) - .selectAll('memory') - .$call((qb) => - dto.order === AssetOrderWithRandom.Random - ? qb.orderBy(sql`RANDOM()`) - : qb.orderBy('memoryAt', (dto.order?.toLowerCase() || 'desc') as OrderByDirection), - ) - .$if(dto.size !== undefined, (qb) => qb.limit(dto.size!)) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - get(id: string) { - return this.getByIdBuilder(id).executeTakeFirst(); - } - - async create(memory: Insertable, assetIds: Set) { - const id = await this.db.transaction().execute(async (tx) => { - const { id } = await tx.insertInto('memory').values(memory).returning('id').executeTakeFirstOrThrow(); - - if (assetIds.size > 0) { - const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetId })); - await tx.insertInto('memory_asset').values(values).execute(); - } - - return id; - }); - - return this.getByIdBuilder(id).executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] }) - async update(id: string, memory: Updateable) { - await this.db.updateTable('memory').set(memory).where('id', '=', id).execute(); - return this.getByIdBuilder(id).executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async delete(id: string) { - await this.db.deleteFrom('memory').where('id', '=', id).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - @ChunkedSet({ paramIndex: 1 }) - async getAssetIds(id: string, assetIds: string[]) { - if (assetIds.length === 0) { - return new Set(); - } - - const results = await this.db - .selectFrom('memory_asset') - .select(['assetId']) - .where('memoriesId', '=', id) - .where('assetId', 'in', assetIds) - .execute(); - - return new Set(results.map(({ assetId }) => assetId)); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - async addAssetIds(id: string, assetIds: string[]) { - if (assetIds.length === 0) { - return; - } - - await this.db - .insertInto('memory_asset') - .values(assetIds.map((assetId) => ({ memoriesId: id, assetId }))) - .execute(); - } - - @Chunked({ paramIndex: 1 }) - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - async removeAssetIds(id: string, assetIds: string[]) { - if (assetIds.length === 0) { - return; - } - - await this.db.deleteFrom('memory_asset').where('memoriesId', '=', id).where('assetId', 'in', assetIds).execute(); - } - - private getByIdBuilder(id: string) { - return this.db - .selectFrom('memory') - .selectAll('memory') - .select((eb) => - jsonArrayFrom( - eb - .selectFrom('asset') - .selectAll('asset') - .innerJoin('memory_asset', 'asset.id', 'memory_asset.assetId') - .whereRef('memory_asset.memoriesId', '=', 'memory.id') - .orderBy('asset.fileCreatedAt', 'asc') - .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .where('asset.deletedAt', 'is', null), - ).as('assets'), - ) - .where('id', '=', id) - .where('deletedAt', 'is', null); - } -} diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts deleted file mode 100644 index 1334d1220f..0000000000 --- a/server/src/repositories/metadata.repository.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored'; -import geotz from 'geo-tz'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { mimeTypes } from 'src/utils/mime-types'; - -interface ExifDuration { - Value: number; - Scale?: number; -} - -type StringOrNumber = string | number; - -type TagsWithWrongTypes = - | 'FocalLength' - | 'Duration' - | 'Description' - | 'ImageDescription' - | 'RegionInfo' - | 'TagsList' - | 'Keywords' - | 'HierarchicalSubject' - | 'ISO'; - -export interface ImmichTags extends Omit { - ContentIdentifier?: string; - MotionPhoto?: number; - MotionPhotoVersion?: number; - MotionPhotoPresentationTimestampUs?: number; - MediaGroupUUID?: string; - ImagePixelDepth?: string; - FocalLength?: number; - Duration?: number | string | ExifDuration; - EmbeddedVideoType?: string; - EmbeddedVideoFile?: BinaryField; - MotionPhotoVideo?: BinaryField; - TagsList?: StringOrNumber[]; - HierarchicalSubject?: StringOrNumber[]; - Keywords?: StringOrNumber | StringOrNumber[]; - ISO?: number | number[]; - - // Type is wrong, can also be number. - Description?: StringOrNumber; - ImageDescription?: StringOrNumber; - - // Extended properties for image regions, such as faces - RegionInfo?: { - AppliedToDimensions: { - W: number; - H: number; - Unit: string; - }; - RegionList: { - Area: { - // (X,Y) // center of the rectangle - X: number; - Y: number; - W: number; - H: number; - Unit: string; - }; - Rotation?: number; - Type?: string; - Name?: string; - }[]; - }; - - Device?: { - Manufacturer?: string; - ModelName?: string; - }; - - AndroidMake?: string; - AndroidModel?: string; -} - -@Injectable() -export class MetadataRepository { - private exiftool = new ExifTool({ - defaultVideosToUTC: true, - backfillTimezones: true, - inferTimezoneFromDatestamps: true, - inferTimezoneFromTimeStamp: true, - useMWG: true, - numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength', 'FileSize'], - /* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */ - geoTz: (lat, lon) => geotz.find(lat, lon)[0], - geolocation: true, - // Enable exiftool LFS to parse metadata for files larger than 2GB. - readArgs: ['-api', 'largefilesupport=1'], - writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'], - taskTimeoutMillis: 2 * 60 * 1000, - }); - - constructor(private logger: LoggingRepository) { - this.logger.setContext(MetadataRepository.name); - } - - setMaxConcurrency(concurrency: number) { - this.exiftool.batchCluster.setMaxProcs(concurrency); - } - - async teardown() { - await this.exiftool.end(); - } - - readTags(path: string): Promise { - const args = mimeTypes.isVideo(path) ? ['-ee'] : []; - return this.exiftool.read(path, args).catch((error) => { - this.logger.warn(`Error reading exif data (${path}): ${error}\n${error?.stack}`); - return {}; - }) as Promise; - } - - extractBinaryTag(path: string, tagName: string): Promise { - return this.exiftool.extractBinaryTagToBuffer(tagName, path); - } - - async writeTags(path: string, tags: Partial): Promise { - try { - await this.exiftool.write(path, tags); - } catch (error) { - this.logger.warn(`Error writing exif data (${path}): ${error}`); - } - } -} diff --git a/server/src/repositories/move.repository.ts b/server/src/repositories/move.repository.ts deleted file mode 100644 index 2ea69eba27..0000000000 --- a/server/src/repositories/move.repository.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql, Updateable } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetPathType, PathType } from 'src/enum'; -import { DB } from 'src/schema'; -import { MoveTable } from 'src/schema/tables/move.table'; - -@Injectable() -export class MoveRepository { - constructor(@InjectKysely() private db: Kysely) {} - - create(entity: Insertable) { - return this.db.insertInto('move_history').values(entity).returningAll().executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - getByEntity(entityId: string, pathType: PathType) { - return this.db - .selectFrom('move_history') - .selectAll() - .where('entityId', '=', entityId) - .where('pathType', '=', pathType) - .executeTakeFirst(); - } - - update(id: string, entity: Updateable) { - return this.db - .updateTable('move_history') - .set(entity) - .where('id', '=', id) - .returningAll() - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - delete(id: string) { - return this.db.deleteFrom('move_history').where('id', '=', id).returningAll().executeTakeFirstOrThrow(); - } - - async cleanMoveHistory(): Promise { - await this.db - .deleteFrom('move_history') - .where((eb) => - eb( - 'move_history.entityId', - 'not in', - eb.selectFrom('asset').select('id').whereRef('asset.id', '=', 'move_history.entityId'), - ), - ) - .where('move_history.pathType', '=', sql.lit(AssetPathType.Original)) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async cleanMoveHistorySingle(assetId: string): Promise { - await this.db - .deleteFrom('move_history') - .where('move_history.pathType', '=', sql.lit(AssetPathType.Original)) - .where('entityId', '=', assetId) - .execute(); - } -} diff --git a/server/src/repositories/notification.repository.ts b/server/src/repositories/notification.repository.ts deleted file mode 100644 index e892403416..0000000000 --- a/server/src/repositories/notification.repository.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Insertable, Kysely, Updateable } from 'kysely'; -import { DateTime } from 'luxon'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { NotificationSearchDto } from 'src/dtos/notification.dto'; -import { DB } from 'src/schema'; -import { NotificationTable } from 'src/schema/tables/notification.table'; - -export class NotificationRepository { - constructor(@InjectKysely() private db: Kysely) {} - - cleanup() { - return this.db - .deleteFrom('notification') - .where((eb) => - eb.or([ - // remove soft-deleted notifications - eb.and([eb('deletedAt', 'is not', null), eb('deletedAt', '<', DateTime.now().minus({ days: 3 }).toJSDate())]), - - // remove old, read notifications - eb.and([ - // keep recently read messages around for a few days - eb('readAt', '>', DateTime.now().minus({ days: 2 }).toJSDate()), - eb('createdAt', '<', DateTime.now().minus({ days: 15 }).toJSDate()), - ]), - - eb.and([ - // remove super old, unread notifications - eb('readAt', '=', null), - eb('createdAt', '<', DateTime.now().minus({ days: 30 }).toJSDate()), - ]), - ]), - ) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, {}] }, { name: 'unread', params: [DummyValue.UUID, { unread: true }] }) - search(userId: string, dto: NotificationSearchDto) { - return this.db - .selectFrom('notification') - .select(columns.notification) - .where((qb) => - qb.and({ - userId, - id: dto.id, - level: dto.level, - type: dto.type, - readAt: dto.unread ? null : undefined, - }), - ) - .where('deletedAt', 'is', null) - .orderBy('createdAt', 'desc') - .execute(); - } - - create(notification: Insertable) { - return this.db - .insertInto('notification') - .values(notification) - .returning(columns.notification) - .executeTakeFirstOrThrow(); - } - - get(id: string) { - return this.db - .selectFrom('notification') - .select(columns.notification) - .where('id', '=', id) - .where('deletedAt', 'is not', null) - .executeTakeFirst(); - } - - update(id: string, notification: Updateable) { - return this.db - .updateTable('notification') - .set(notification) - .where('deletedAt', 'is', null) - .where('id', '=', id) - .returning(columns.notification) - .executeTakeFirstOrThrow(); - } - - async updateAll(ids: string[], notification: Updateable) { - await this.db.updateTable('notification').set(notification).where('id', 'in', ids).execute(); - } - - async delete(id: string) { - await this.db - .updateTable('notification') - .set({ deletedAt: DateTime.now().toJSDate() }) - .where('id', '=', id) - .execute(); - } - - async deleteAll(ids: string[]) { - await this.db - .updateTable('notification') - .set({ deletedAt: DateTime.now().toJSDate() }) - .where('id', 'in', ids) - .execute(); - } -} diff --git a/server/src/repositories/oauth.repository.ts b/server/src/repositories/oauth.repository.ts deleted file mode 100644 index a42955ba10..0000000000 --- a/server/src/repositories/oauth.repository.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Injectable, InternalServerErrorException } from '@nestjs/common'; -import type { UserInfoResponse } from 'openid-client' with { 'resolution-mode': 'import' }; -import { OAuthTokenEndpointAuthMethod } from 'src/enum'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -export type OAuthConfig = { - clientId: string; - clientSecret?: string; - issuerUrl: string; - mobileOverrideEnabled: boolean; - mobileRedirectUri: string; - profileSigningAlgorithm: string; - scope: string; - signingAlgorithm: string; - tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod; - timeout: number; -}; -export type OAuthProfile = UserInfoResponse; - -@Injectable() -export class OAuthRepository { - constructor(private logger: LoggingRepository) { - this.logger.setContext(OAuthRepository.name); - } - - async authorize(config: OAuthConfig, redirectUrl: string, state?: string, codeChallenge?: string) { - const { buildAuthorizationUrl, randomState, randomPKCECodeVerifier, calculatePKCECodeChallenge } = - await import('openid-client'); - const client = await this.getClient(config); - state ??= randomState(); - - let codeVerifier: string | null; - if (codeChallenge) { - codeVerifier = null; - } else { - codeVerifier = randomPKCECodeVerifier(); - codeChallenge = await calculatePKCECodeChallenge(codeVerifier); - } - - const params: Record = { - redirect_uri: redirectUrl, - scope: config.scope, - state, - }; - - if (client.serverMetadata().supportsPKCE()) { - params.code_challenge = codeChallenge; - params.code_challenge_method = 'S256'; - } - - const url = buildAuthorizationUrl(client, params).toString(); - - return { url, state, codeVerifier }; - } - - async getLogoutEndpoint(config: OAuthConfig) { - const client = await this.getClient(config); - return client.serverMetadata().end_session_endpoint; - } - - async getProfile( - config: OAuthConfig, - url: string, - expectedState: string, - codeVerifier: string, - ): Promise { - const { authorizationCodeGrant, fetchUserInfo, ...oidc } = await import('openid-client'); - const client = await this.getClient(config); - const pkceCodeVerifier = client.serverMetadata().supportsPKCE() ? codeVerifier : undefined; - - try { - const tokens = await authorizationCodeGrant(client, new URL(url), { expectedState, pkceCodeVerifier }); - const profile = await fetchUserInfo(client, tokens.access_token, oidc.skipSubjectCheck); - if (!profile.sub) { - throw new Error('Unexpected profile response, no `sub`'); - } - - return profile; - } catch (error: Error | any) { - if (error.message.includes('unexpected JWT alg received')) { - this.logger.warn( - [ - 'Algorithm mismatch. Make sure the signing algorithm is set correctly in the OAuth settings.', - 'Or, that you have specified a signing key in your OAuth provider.', - ].join(' '), - ); - } - - this.logger.error(`OAuth login failed: ${error.message}`); - this.logger.error(error); - - throw new Error('OAuth login failed', { cause: error }); - } - } - - async getProfilePicture(url: string) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch picture: ${response.statusText}`); - } - - return { - data: await response.arrayBuffer(), - contentType: response.headers.get('content-type'), - }; - } - - private async getClient({ - issuerUrl, - clientId, - clientSecret, - profileSigningAlgorithm, - signingAlgorithm, - tokenEndpointAuthMethod, - timeout, - }: OAuthConfig) { - try { - const { allowInsecureRequests, discovery } = await import('openid-client'); - return await discovery( - new URL(issuerUrl), - clientId, - { - client_secret: clientSecret, - response_types: ['code'], - userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm, - id_token_signed_response_alg: signingAlgorithm, - }, - await this.getTokenAuthMethod(tokenEndpointAuthMethod, clientSecret), - { - execute: [allowInsecureRequests], - timeout, - }, - ); - } catch (error: any | AggregateError) { - this.logger.error(`Error in OAuth discovery: ${error}`, error?.stack, error?.errors); - throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error }); - } - } - - private async getTokenAuthMethod(tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod, clientSecret?: string) { - const { None, ClientSecretPost, ClientSecretBasic } = await import('openid-client'); - - if (!clientSecret) { - return None(); - } - - switch (tokenEndpointAuthMethod) { - case OAuthTokenEndpointAuthMethod.ClientSecretPost: { - return ClientSecretPost(clientSecret); - } - - case OAuthTokenEndpointAuthMethod.ClientSecretBasic: { - return ClientSecretBasic(clientSecret); - } - - default: { - return None(); - } - } - } -} diff --git a/server/src/repositories/ocr.repository.ts b/server/src/repositories/ocr.repository.ts deleted file mode 100644 index 63375cf57d..0000000000 --- a/server/src/repositories/ocr.repository.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { DB } from 'src/schema'; -import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table'; - -@Injectable() -export class OcrRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - getById(id: string) { - return this.db.selectFrom('asset_ocr').selectAll('asset_ocr').where('asset_ocr.id', '=', id).executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getByAssetId(id: string, options?: { isVisible?: boolean }) { - const isVisible = options === undefined ? true : options.isVisible; - - return this.db - .selectFrom('asset_ocr') - .selectAll('asset_ocr') - .where('asset_ocr.assetId', '=', id) - .$if(isVisible !== undefined, (qb) => qb.where('asset_ocr.isVisible', '=', isVisible!)) - .execute(); - } - - deleteAll() { - return this.db.transaction().execute(async (trx: Kysely) => { - await sql`truncate ${sql.table('asset_ocr')}`.execute(trx); - await sql`truncate ${sql.table('ocr_search')}`.execute(trx); - }); - } - - @GenerateSql({ - params: [ - DummyValue.UUID, - [ - { - assetId: DummyValue.UUID, - x1: DummyValue.NUMBER, - y1: DummyValue.NUMBER, - x2: DummyValue.NUMBER, - y2: DummyValue.NUMBER, - x3: DummyValue.NUMBER, - y3: DummyValue.NUMBER, - x4: DummyValue.NUMBER, - y4: DummyValue.NUMBER, - text: DummyValue.STRING, - boxScore: DummyValue.NUMBER, - textScore: DummyValue.NUMBER, - }, - ], - DummyValue.STRING, - ], - }) - upsert(assetId: string, ocrDataList: Insertable[], searchText: string) { - let query = this.db.with('deleted_ocr', (db) => db.deleteFrom('asset_ocr').where('assetId', '=', assetId)); - if (ocrDataList.length > 0) { - (query as any) = query - .with('inserted_ocr', (db) => db.insertInto('asset_ocr').values(ocrDataList)) - .with('inserted_search', (db) => - db - .insertInto('ocr_search') - .values({ assetId, text: searchText }) - .onConflict((oc) => oc.column('assetId').doUpdateSet((eb) => ({ text: eb.ref('excluded.text') }))), - ); - } else { - (query as any) = query.with('deleted_search', (db) => db.deleteFrom('ocr_search').where('assetId', '=', assetId)); - } - - return query.selectNoFrom(sql`1`.as('dummy')).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, [], []] }) - async updateOcrVisibilities( - assetId: string, - visible: AssetOcrResponseDto[], - hidden: AssetOcrResponseDto[], - ): Promise { - await this.db.transaction().execute(async (trx) => { - if (visible.length > 0) { - await trx - .updateTable('asset_ocr') - .set({ isVisible: true }) - .where( - 'asset_ocr.id', - 'in', - visible.map((i) => i.id), - ) - .execute(); - } - - if (hidden.length > 0) { - await trx - .updateTable('asset_ocr') - .set({ isVisible: false }) - .where( - 'asset_ocr.id', - 'in', - hidden.map((i) => i.id), - ) - .execute(); - } - - const searchText = visible.map((item) => item.text.trim()).join(' '); - await trx.updateTable('ocr_search').set({ text: searchText }).where('assetId', '=', assetId).execute(); - }); - } -} diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts deleted file mode 100644 index e386386be8..0000000000 --- a/server/src/repositories/partner.repository.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, NotNull, Updateable } from 'kysely'; -import { jsonObjectFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { DB } from 'src/schema'; -import { PartnerTable } from 'src/schema/tables/partner.table'; - -export interface PartnerIds { - sharedById: string; - sharedWithId: string; -} - -export enum PartnerDirection { - SharedBy = 'shared-by', - SharedWith = 'shared-with', -} - -const withSharedBy = (eb: ExpressionBuilder) => { - return jsonObjectFrom( - eb.selectFrom('user as sharedBy').select(columns.user).whereRef('sharedBy.id', '=', 'partner.sharedById'), - ).as('sharedBy'); -}; - -const withSharedWith = (eb: ExpressionBuilder) => { - return jsonObjectFrom( - eb.selectFrom('user as sharedWith').select(columns.user).whereRef('sharedWith.id', '=', 'partner.sharedWithId'), - ).as('sharedWith'); -}; - -@Injectable() -export class PartnerRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - getAll(userId: string) { - return this.builder() - .where((eb) => eb.or([eb('sharedWithId', '=', userId), eb('sharedById', '=', userId)])) - .execute(); - } - - @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) - get({ sharedWithId, sharedById }: PartnerIds) { - return this.builder() - .where('sharedWithId', '=', sharedWithId) - .where('sharedById', '=', sharedById) - .executeTakeFirst(); - } - - create(values: Insertable) { - return this.db - .insertInto('partner') - .values(values) - .returningAll() - .returning(withSharedBy) - .returning(withSharedWith) - .$narrowType<{ sharedWith: NotNull; sharedBy: NotNull }>() - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] }) - update({ sharedWithId, sharedById }: PartnerIds, values: Updateable) { - return this.db - .updateTable('partner') - .set(values) - .where('sharedWithId', '=', sharedWithId) - .where('sharedById', '=', sharedById) - .returningAll() - .returning(withSharedBy) - .returning(withSharedWith) - .$narrowType<{ sharedWith: NotNull; sharedBy: NotNull }>() - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] }) - async remove({ sharedWithId, sharedById }: PartnerIds) { - await this.db - .deleteFrom('partner') - .where('sharedWithId', '=', sharedWithId) - .where('sharedById', '=', sharedById) - .execute(); - } - - private builder() { - return this.db - .selectFrom('partner') - .innerJoin('user as sharedBy', (join) => - join.onRef('partner.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null), - ) - .innerJoin('user as sharedWith', (join) => - join.onRef('partner.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null), - ) - .selectAll('partner') - .select(withSharedBy) - .select(withSharedWith); - } -} diff --git a/server/src/repositories/person.repository.ts b/server/src/repositories/person.repository.ts deleted file mode 100644 index b03112821b..0000000000 --- a/server/src/repositories/person.repository.ts +++ /dev/null @@ -1,585 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, NotNull, Selectable, sql, Updateable } from 'kysely'; -import { jsonObjectFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { AssetFace } from 'src/database'; -import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; -import { AssetFileType, AssetVisibility, SourceType } from 'src/enum'; -import { DB } from 'src/schema'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { FaceSearchTable } from 'src/schema/tables/face-search.table'; -import { PersonTable } from 'src/schema/tables/person.table'; -import { removeUndefinedKeys } from 'src/utils/database'; -import { paginationHelper, PaginationOptions } from 'src/utils/pagination'; - -export interface PersonSearchOptions { - minimumFaceCount: number; - withHidden: boolean; - closestFaceAssetId?: string; -} - -export interface PersonNameSearchOptions { - withHidden?: boolean; -} - -export interface PersonNameResponse { - id: string; - name: string; -} - -export interface AssetFaceId { - assetId: string; - personId: string; -} - -export interface UpdateFacesData { - oldPersonId?: string; - faceIds?: string[]; - newPersonId: string; -} - -export interface PersonStatistics { - assets: number; -} - -export interface DeleteFacesOptions { - sourceType: SourceType; -} - -export interface GetAllPeopleOptions { - ownerId?: string; - thumbnailPath?: string; - faceAssetId?: string | null; - isHidden?: boolean; -} - -export interface GetAllFacesOptions { - personId?: string | null; - assetId?: string; - sourceType?: SourceType; -} - -export type UnassignFacesOptions = DeleteFacesOptions; - -export type SelectFaceOptions = (keyof Selectable)[]; - -const withPerson = (eb: ExpressionBuilder) => { - return jsonObjectFrom( - eb.selectFrom('person').selectAll('person').whereRef('person.id', '=', 'asset_face.personId'), - ).as('person'); -}; - -const withFaceSearch = (eb: ExpressionBuilder) => { - return jsonObjectFrom( - eb.selectFrom('face_search').selectAll('face_search').whereRef('face_search.faceId', '=', 'asset_face.id'), - ).as('faceSearch'); -}; - -@Injectable() -export class PersonRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] }) - async reassignFaces({ oldPersonId, faceIds, newPersonId }: UpdateFacesData): Promise { - const result = await this.db - .updateTable('asset_face') - .set({ personId: newPersonId }) - .$if(!!oldPersonId, (qb) => qb.where('asset_face.personId', '=', oldPersonId!)) - .$if(!!faceIds, (qb) => qb.where('asset_face.id', 'in', faceIds!)) - .executeTakeFirst(); - - return Number(result.numChangedRows ?? 0); - } - - async unassignFaces({ sourceType }: UnassignFacesOptions): Promise { - await this.db - .updateTable('asset_face') - .set({ personId: null }) - .where('asset_face.sourceType', '=', sourceType) - .execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - @Chunked() - async delete(ids: string[]): Promise { - if (ids.length === 0) { - return; - } - - await this.db.deleteFrom('person').where('person.id', 'in', ids).execute(); - } - - async deleteFaces({ sourceType }: DeleteFacesOptions): Promise { - await this.db.deleteFrom('asset_face').where('asset_face.sourceType', '=', sourceType).execute(); - } - - getAllFaces(options: GetAllFacesOptions = {}) { - return this.db - .selectFrom('asset_face') - .selectAll('asset_face') - .$if(options.personId === null, (qb) => qb.where('asset_face.personId', 'is', null)) - .$if(!!options.personId, (qb) => qb.where('asset_face.personId', '=', options.personId!)) - .$if(!!options.sourceType, (qb) => qb.where('asset_face.sourceType', '=', options.sourceType!)) - .$if(!!options.assetId, (qb) => qb.where('asset_face.assetId', '=', options.assetId!)) - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', 'is', true) - .stream(); - } - - getAll(options: GetAllPeopleOptions = {}) { - return this.db - .selectFrom('person') - .selectAll('person') - .$if(!!options.ownerId, (qb) => qb.where('person.ownerId', '=', options.ownerId!)) - .$if(options.thumbnailPath !== undefined, (qb) => qb.where('person.thumbnailPath', '=', options.thumbnailPath!)) - .$if(options.faceAssetId === null, (qb) => qb.where('person.faceAssetId', 'is', null)) - .$if(!!options.faceAssetId, (qb) => qb.where('person.faceAssetId', '=', options.faceAssetId!)) - .$if(options.isHidden !== undefined, (qb) => qb.where('person.isHidden', '=', options.isHidden!)) - .stream(); - } - - @GenerateSql() - getFileSamples() { - return this.db - .selectFrom('person') - .select(['id', 'thumbnailPath']) - .where('thumbnailPath', '!=', sql.lit('')) - .limit(sql.lit(3)) - .execute(); - } - - @GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] }) - async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) { - const items = await this.db - .selectFrom('person') - .selectAll('person') - .innerJoin('asset_face', 'asset_face.personId', 'person.id') - .innerJoin('asset', (join) => - join - .onRef('asset_face.assetId', '=', 'asset.id') - .on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .on('asset.deletedAt', 'is', null), - ) - .where('person.ownerId', '=', userId) - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', 'is', true) - .orderBy('person.isHidden', 'asc') - .orderBy('person.isFavorite', 'desc') - .having((eb) => - eb.or([ - eb('person.name', '!=', ''), - eb((innerEb) => innerEb.fn.count('asset_face.assetId'), '>=', options?.minimumFaceCount || 1), - ]), - ) - .groupBy('person.id') - .$if(!!options?.closestFaceAssetId, (qb) => - qb.orderBy((eb) => - eb( - (eb) => - eb - .selectFrom('face_search') - .select('face_search.embedding') - .whereRef('face_search.faceId', '=', 'person.faceAssetId'), - '<=>', - (eb) => - eb - .selectFrom('face_search') - .select('face_search.embedding') - .where('face_search.faceId', '=', options!.closestFaceAssetId!), - ), - ), - ) - .$if(!options?.closestFaceAssetId, (qb) => - qb - .orderBy(sql`NULLIF(person.name, '') is null`, 'asc') - .orderBy((eb) => eb.fn.count('asset_face.assetId'), 'desc') - .orderBy(sql`NULLIF(person.name, '')`, (om) => om.asc().nullsLast()) - .orderBy('person.createdAt'), - ) - .$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false)) - .offset(pagination.skip ?? 0) - .limit(pagination.take + 1) - .execute(); - - return paginationHelper(items, pagination.take); - } - - @GenerateSql() - getAllWithoutFaces() { - return this.db - .selectFrom('person') - .selectAll('person') - .leftJoin('asset_face', 'asset_face.personId', 'person.id') - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', 'is', true) - .having((eb) => eb.fn.count('asset_face.assetId'), '=', 0) - .groupBy('person.id') - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getFaces(assetId: string, options?: { isVisible?: boolean }) { - const isVisible = options === undefined ? true : options.isVisible; - - return this.db - .selectFrom('asset_face') - .selectAll('asset_face') - .select(withPerson) - .where('asset_face.assetId', '=', assetId) - .where('asset_face.deletedAt', 'is', null) - .$if(isVisible !== undefined, (qb) => qb.where('asset_face.isVisible', '=', isVisible!)) - .orderBy('asset_face.boundingBoxX1', 'asc') - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getFaceById(id: string) { - // TODO return null instead of find or fail - return this.db - .selectFrom('asset_face') - .selectAll('asset_face') - .select(withPerson) - .where('asset_face.id', '=', id) - .where('asset_face.deletedAt', 'is', null) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getFaceForFacialRecognitionJob(id: string) { - return this.db - .selectFrom('asset_face') - .select(['asset_face.id', 'asset_face.personId', 'asset_face.sourceType']) - .select((eb) => - jsonObjectFrom( - eb - .selectFrom('asset') - .select(['asset.ownerId', 'asset.visibility', 'asset.fileCreatedAt']) - .whereRef('asset.id', '=', 'asset_face.assetId'), - ).as('asset'), - ) - .select(withFaceSearch) - .where('asset_face.id', '=', id) - .where('asset_face.deletedAt', 'is', null) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getDataForThumbnailGenerationJob(id: string) { - return this.db - .selectFrom('person') - .innerJoin('asset_face', 'asset_face.id', 'person.faceAssetId') - .innerJoin('asset', 'asset_face.assetId', 'asset.id') - .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') - .select([ - 'person.ownerId', - 'asset_face.boundingBoxX1 as x1', - 'asset_face.boundingBoxY1 as y1', - 'asset_face.boundingBoxX2 as x2', - 'asset_face.boundingBoxY2 as y2', - 'asset_face.imageWidth as oldWidth', - 'asset_face.imageHeight as oldHeight', - 'asset.type', - 'asset.originalPath', - 'asset_exif.orientation as exifOrientation', - ]) - .select((eb) => - eb - .selectFrom('asset_file') - .select('asset_file.path') - .whereRef('asset_file.assetId', '=', 'asset.id') - .where('asset_file.type', '=', sql.lit(AssetFileType.Preview)) - .as('previewPath'), - ) - .where('person.id', '=', id) - .where('asset_face.deletedAt', 'is', null) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - async reassignFace(assetFaceId: string, newPersonId: string): Promise { - const result = await this.db - .updateTable('asset_face') - .set({ personId: newPersonId }) - .where('asset_face.id', '=', assetFaceId) - .executeTakeFirst(); - - return Number(result.numChangedRows ?? 0); - } - - getById(personId: string) { - return this.db // - .selectFrom('person') - .selectAll('person') - .where('person.id', '=', personId) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING, { withHidden: true }] }) - getByName(userId: string, personName: string, { withHidden }: PersonNameSearchOptions) { - return this.db - .selectFrom('person') - .selectAll('person') - .where((eb) => - eb.and([ - eb('person.ownerId', '=', userId), - eb.or([ - eb(eb.fn('lower', ['person.name']), 'like', `${personName.toLowerCase()}%`), - eb(eb.fn('lower', ['person.name']), 'like', `% ${personName.toLowerCase()}%`), - ]), - ]), - ) - .limit(1000) - .$if(!withHidden, (qb) => qb.where('person.isHidden', '=', false)) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, { withHidden: true }] }) - getDistinctNames(userId: string, { withHidden }: PersonNameSearchOptions): Promise { - return this.db - .selectFrom('person') - .select(['person.id', 'person.name']) - .distinctOn((eb) => eb.fn('lower', ['person.name'])) - .where((eb) => eb.and([eb('person.ownerId', '=', userId), eb('person.name', '!=', '')])) - .$if(!withHidden, (qb) => qb.where('person.isHidden', '=', false)) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async getStatistics(personId: string): Promise { - const result = await this.db - .selectFrom('asset_face') - .leftJoin('asset', (join) => - join - .onRef('asset.id', '=', 'asset_face.assetId') - .on('asset_face.personId', '=', personId) - .on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .on('asset.deletedAt', 'is', null), - ) - .select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count')) - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', 'is', true) - .executeTakeFirst(); - - return { - assets: result ? Number(result.count) : 0, - }; - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getNumberOfPeople(userId: string) { - const zero = sql.lit(0); - return this.db - .selectFrom('person') - .where((eb) => - eb.exists((eb) => - eb - .selectFrom('asset_face') - .whereRef('asset_face.personId', '=', 'person.id') - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', '=', true) - .where((eb) => - eb.exists((eb) => - eb - .selectFrom('asset') - .whereRef('asset.id', '=', 'asset_face.assetId') - .where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)) - .where('asset.deletedAt', 'is', null), - ), - ), - ), - ) - .where('person.ownerId', '=', userId) - .select((eb) => eb.fn.coalesce(eb.fn.countAll(), zero).as('total')) - .select((eb) => eb.fn.coalesce(eb.fn.countAll().filterWhere('isHidden', '=', true), zero).as('hidden')) - .executeTakeFirstOrThrow(); - } - - create(person: Insertable) { - return this.db.insertInto('person').values(person).returningAll().executeTakeFirstOrThrow(); - } - - async createAll(people: Insertable[]): Promise { - if (people.length === 0) { - return []; - } - - const results = await this.db.insertInto('person').values(people).returningAll().execute(); - return results.map(({ id }) => id); - } - - @GenerateSql({ params: [[], [], [{ faceId: DummyValue.UUID, embedding: DummyValue.VECTOR }]] }) - async refreshFaces( - facesToAdd: (Insertable & { assetId: string })[], - faceIdsToRemove: string[], - embeddingsToAdd?: Insertable[], - ): Promise { - let query = this.db; - if (facesToAdd.length > 0) { - (query as any) = query.with('added', (db) => db.insertInto('asset_face').values(facesToAdd)); - } - - if (faceIdsToRemove.length > 0) { - (query as any) = query.with('removed', (db) => - db.deleteFrom('asset_face').where('asset_face.id', '=', (eb) => eb.fn.any(eb.val(faceIdsToRemove))), - ); - } - - if (embeddingsToAdd?.length) { - (query as any) = query.with('added_embeddings', (db) => db.insertInto('face_search').values(embeddingsToAdd)); - } - - await query.selectFrom(sql`(select 1)`.as('dummy')).execute(); - } - - async update(person: Updateable & { id: string }) { - return this.db - .updateTable('person') - .set(person) - .where('person.id', '=', person.id) - .returningAll() - .executeTakeFirstOrThrow(); - } - - async updateAll(people: Insertable[]): Promise { - if (people.length === 0) { - return; - } - - await this.db - .insertInto('person') - .values(people) - .onConflict((oc) => - oc.column('id').doUpdateSet((eb) => - removeUndefinedKeys( - { - name: eb.ref('excluded.name'), - birthDate: eb.ref('excluded.birthDate'), - thumbnailPath: eb.ref('excluded.thumbnailPath'), - faceAssetId: eb.ref('excluded.faceAssetId'), - isHidden: eb.ref('excluded.isHidden'), - isFavorite: eb.ref('excluded.isFavorite'), - color: eb.ref('excluded.color'), - }, - people[0], - ), - ), - ) - .execute(); - } - - @GenerateSql({ params: [[{ assetId: DummyValue.UUID, personId: DummyValue.UUID }]] }) - @ChunkedArray() - getFacesByIds(ids: AssetFaceId[]) { - if (ids.length === 0) { - return Promise.resolve([]); - } - - const assetIds: string[] = []; - const personIds: string[] = []; - for (const { assetId, personId } of ids) { - assetIds.push(assetId); - personIds.push(personId); - } - - return this.db - .selectFrom('asset_face') - .selectAll('asset_face') - .select((eb) => - jsonObjectFrom(eb.selectFrom('asset').selectAll('asset').whereRef('asset.id', '=', 'asset_face.assetId')).as( - 'asset', - ), - ) - .$narrowType<{ asset: NotNull }>() - .select(withPerson) - .where('asset_face.assetId', 'in', assetIds) - .where('asset_face.personId', 'in', personIds) - .where('asset_face.deletedAt', 'is', null) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getRandomFace(personId: string) { - return this.db - .selectFrom('asset_face') - .selectAll('asset_face') - .where('asset_face.personId', '=', personId) - .where('asset_face.deletedAt', 'is', null) - .where('asset_face.isVisible', 'is', true) - .executeTakeFirst(); - } - - @GenerateSql() - async getLatestFaceDate(): Promise { - const result = (await this.db - .selectFrom('asset_job_status') - .select((eb) => sql`${eb.fn.max('asset_job_status.facesRecognizedAt')}::text`.as('latestDate')) - .executeTakeFirst()) as { latestDate: string } | undefined; - - return result?.latestDate; - } - - async createAssetFace(face: Insertable): Promise { - await this.db.insertInto('asset_face').values(face).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async deleteAssetFace(id: string): Promise { - await this.db.deleteFrom('asset_face').where('asset_face.id', '=', id).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async softDeleteAssetFaces(id: string): Promise { - await this.db.updateTable('asset_face').set({ deletedAt: new Date() }).where('asset_face.id', '=', id).execute(); - } - - async vacuum({ reindexVectors }: { reindexVectors: boolean }): Promise { - await sql`VACUUM ANALYZE asset_face, face_search, person`.execute(this.db); - await sql`REINDEX TABLE asset_face`.execute(this.db); - await sql`REINDEX TABLE person`.execute(this.db); - if (reindexVectors) { - await sql`REINDEX TABLE face_search`.execute(this.db); - } - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - @Chunked() - getForPeopleDelete(ids: string[]) { - if (ids.length === 0) { - return Promise.resolve([]); - } - return this.db.selectFrom('person').select(['id', 'thumbnailPath']).where('id', 'in', ids).execute(); - } - - @GenerateSql({ params: [[], []] }) - async updateVisibility(visible: AssetFace[], hidden: AssetFace[]): Promise { - if (visible.length === 0 && hidden.length === 0) { - return; - } - - await this.db.transaction().execute(async (trx) => { - if (visible.length > 0) { - await trx - .updateTable('asset_face') - .set({ isVisible: true }) - .where( - 'asset_face.id', - 'in', - visible.map(({ id }) => id), - ) - .execute(); - } - - if (hidden.length > 0) { - await trx - .updateTable('asset_face') - .set({ isVisible: false }) - .where( - 'asset_face.id', - 'in', - hidden.map(({ id }) => id), - ) - .execute(); - } - }); - } -} diff --git a/server/src/repositories/plugin.repository.ts b/server/src/repositories/plugin.repository.ts deleted file mode 100644 index 6217237947..0000000000 --- a/server/src/repositories/plugin.repository.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely } from 'kysely'; -import { jsonArrayFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { readdir } from 'node:fs/promises'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; -import { DB } from 'src/schema'; - -@Injectable() -export class PluginRepository { - constructor(@InjectKysely() private db: Kysely) {} - - /** - * Loads a plugin from a validated manifest file in a transaction. - * This ensures all plugin, filter, and action operations are atomic. - * @param manifest The validated plugin manifest - * @param basePath The base directory path where the plugin is located - */ - async loadPlugin(manifest: PluginManifestDto, basePath: string) { - return this.db.transaction().execute(async (tx) => { - // Upsert the plugin - const plugin = await tx - .insertInto('plugin') - .values({ - name: manifest.name, - title: manifest.title, - description: manifest.description, - author: manifest.author, - version: manifest.version, - wasmPath: `${basePath}/${manifest.wasm.path}`, - }) - .onConflict((oc) => - oc.column('name').doUpdateSet({ - title: manifest.title, - description: manifest.description, - author: manifest.author, - version: manifest.version, - wasmPath: `${basePath}/${manifest.wasm.path}`, - }), - ) - .returningAll() - .executeTakeFirstOrThrow(); - - const filters = manifest.filters - ? await tx - .insertInto('plugin_filter') - .values( - manifest.filters.map((filter) => ({ - pluginId: plugin.id, - methodName: filter.methodName, - title: filter.title, - description: filter.description, - supportedContexts: filter.supportedContexts, - schema: filter.schema, - })), - ) - .onConflict((oc) => - oc.column('methodName').doUpdateSet((eb) => ({ - pluginId: eb.ref('excluded.pluginId'), - title: eb.ref('excluded.title'), - description: eb.ref('excluded.description'), - supportedContexts: eb.ref('excluded.supportedContexts'), - schema: eb.ref('excluded.schema'), - })), - ) - .returningAll() - .execute() - : []; - - const actions = manifest.actions - ? await tx - .insertInto('plugin_action') - .values( - manifest.actions.map((action) => ({ - pluginId: plugin.id, - methodName: action.methodName, - title: action.title, - description: action.description, - supportedContexts: action.supportedContexts, - schema: action.schema, - })), - ) - .onConflict((oc) => - oc.column('methodName').doUpdateSet((eb) => ({ - pluginId: eb.ref('excluded.pluginId'), - title: eb.ref('excluded.title'), - description: eb.ref('excluded.description'), - supportedContexts: eb.ref('excluded.supportedContexts'), - schema: eb.ref('excluded.schema'), - })), - ) - .returningAll() - .execute() - : []; - - return { plugin, filters, actions }; - }); - } - - async readDirectory(path: string) { - return readdir(path, { withFileTypes: true }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getPlugin(id: string) { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .where('plugin.id', '=', id) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.STRING] }) - getPluginByName(name: string) { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .where('plugin.name', '=', name) - .executeTakeFirst(); - } - - @GenerateSql() - getAllPlugins() { - return this.db - .selectFrom('plugin') - .select((eb) => [ - ...columns.plugin, - jsonArrayFrom( - eb.selectFrom('plugin_filter').selectAll().whereRef('plugin_filter.pluginId', '=', 'plugin.id'), - ).as('filters'), - jsonArrayFrom( - eb.selectFrom('plugin_action').selectAll().whereRef('plugin_action.pluginId', '=', 'plugin.id'), - ).as('actions'), - ]) - .orderBy('plugin.name') - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getFilter(id: string) { - return this.db.selectFrom('plugin_filter').selectAll().where('id', '=', id).executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getFiltersByPlugin(pluginId: string) { - return this.db.selectFrom('plugin_filter').selectAll().where('pluginId', '=', pluginId).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getAction(id: string) { - return this.db.selectFrom('plugin_action').selectAll().where('id', '=', id).executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getActionsByPlugin(pluginId: string) { - return this.db.selectFrom('plugin_action').selectAll().where('pluginId', '=', pluginId).execute(); - } -} diff --git a/server/src/repositories/process.repository.spec.ts b/server/src/repositories/process.repository.spec.ts deleted file mode 100644 index a3f44bd78b..0000000000 --- a/server/src/repositories/process.repository.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ChildProcessWithoutNullStreams } from 'node:child_process'; -import { Readable, Writable } from 'node:stream'; -import { pipeline } from 'node:stream/promises'; -import { ProcessRepository } from 'src/repositories/process.repository'; - -function* data() { - yield 'Hello, world!'; -} - -describe(ProcessRepository.name, () => { - let sut: ProcessRepository; - let sink: Writable; - - beforeAll(() => { - sut = new ProcessRepository(); - }); - - beforeEach(() => { - sink = new Writable({ - write(_chunk, _encoding, callback) { - callback(); - }, - - final(callback) { - callback(); - }, - }); - }); - - describe('createSpawnDuplexStream', () => { - it('should work (drain to stdout)', async () => { - const process = sut.spawnDuplexStream('bash', ['-c', 'exit 0']); - await pipeline(process, sink); - }); - - it('should throw on non-zero exit code', async () => { - const process = sut.spawnDuplexStream('bash', ['-c', 'echo "error message" >&2; exit 1']); - await expect(pipeline(process, sink)).rejects.toThrowErrorMatchingInlineSnapshot(` - [Error: bash non-zero exit code (1) - error message - ] - `); - }); - - it('should accept stdin / output stdout', async () => { - let output = ''; - const sink = new Writable({ - write(chunk, _encoding, callback) { - output += chunk; - callback(); - }, - - final(callback) { - callback(); - }, - }); - - const echoProcess = sut.spawnDuplexStream('cat'); - await pipeline(Readable.from(data()), echoProcess, sink); - expect(output).toBe('Hello, world!'); - }); - - it('should drain stdin on process exit', async () => { - let resolve1: () => void; - let resolve2: () => void; - const promise1 = new Promise((r) => (resolve1 = r)); - const promise2 = new Promise((r) => (resolve2 = r)); - - async function* data() { - yield 'Hello, world!'; - await promise1; - await promise2; - yield 'Write after stdin close / process exit!'; - } - - const process = sut.spawnDuplexStream('bash', ['-c', 'exit 0']); - - const realProcess = (process as never as { _process: ChildProcessWithoutNullStreams })._process; - realProcess.on('close', () => setImmediate(() => resolve1())); - realProcess.stdin.on('close', () => setImmediate(() => resolve2())); - - await pipeline(Readable.from(data()), process); - }); - }); -}); diff --git a/server/src/repositories/search.repository.ts b/server/src/repositories/search.repository.ts deleted file mode 100644 index 615b35c417..0000000000 --- a/server/src/repositories/search.repository.ts +++ /dev/null @@ -1,519 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely, OrderByDirection, Selectable, sql } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { randomUUID } from 'node:crypto'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { AssetStatus, AssetType, AssetVisibility, VectorIndex } from 'src/enum'; -import { probes } from 'src/repositories/database.repository'; -import { DB } from 'src/schema'; -import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { anyUuid, searchAssetBuilder, withExif } from 'src/utils/database'; -import { paginationHelper } from 'src/utils/pagination'; -import { isValidInteger } from 'src/validation'; - -export interface SearchAssetIdOptions { - checksum?: Buffer; - deviceAssetId?: string; - id?: string; -} - -export interface SearchUserIdOptions { - deviceId?: string; - libraryId?: string | null; - userIds?: string[]; -} - -export type SearchIdOptions = SearchAssetIdOptions & SearchUserIdOptions; - -export interface SearchStatusOptions { - isEncoded?: boolean; - isFavorite?: boolean; - isMotion?: boolean; - isOffline?: boolean; - isNotInAlbum?: boolean; - type?: AssetType; - status?: AssetStatus; - withArchived?: boolean; - withDeleted?: boolean; - visibility?: AssetVisibility; -} - -export interface SearchOneToOneRelationOptions { - withExif?: boolean; - withStacked?: boolean; -} - -export interface SearchRelationOptions extends SearchOneToOneRelationOptions { - withFaces?: boolean; - withPeople?: boolean; -} - -export interface SearchDateOptions { - createdBefore?: Date; - createdAfter?: Date; - takenBefore?: Date; - takenAfter?: Date; - trashedBefore?: Date; - trashedAfter?: Date; - updatedBefore?: Date; - updatedAfter?: Date; -} - -export interface SearchPathOptions { - encodedVideoPath?: string; - originalFileName?: string; - originalPath?: string; - previewPath?: string; - thumbnailPath?: string; -} - -export interface SearchExifOptions { - city?: string | null; - country?: string | null; - lensModel?: string | null; - make?: string | null; - model?: string | null; - state?: string | null; - description?: string | null; - rating?: number | null; -} - -export interface SearchEmbeddingOptions { - embedding: string; - userIds: string[]; -} - -export interface SearchOcrOptions { - ocr?: string; -} - -export interface SearchPeopleOptions { - personIds?: string[]; -} - -export interface SearchTagOptions { - tagIds?: string[] | null; -} - -export interface SearchAlbumOptions { - albumIds?: string[]; -} - -export interface SearchOrderOptions { - orderDirection?: 'asc' | 'desc'; -} - -export interface SearchPaginationOptions { - page: number; - size: number; -} - -type BaseAssetSearchOptions = SearchDateOptions & - SearchIdOptions & - SearchExifOptions & - SearchOrderOptions & - SearchPathOptions & - SearchStatusOptions & - SearchUserIdOptions & - SearchPeopleOptions & - SearchTagOptions & - SearchAlbumOptions & - SearchOcrOptions; - -export type AssetSearchOptions = BaseAssetSearchOptions & SearchRelationOptions; - -export type AssetSearchBuilderOptions = Omit; - -export type SmartSearchOptions = SearchDateOptions & - SearchEmbeddingOptions & - SearchExifOptions & - SearchOneToOneRelationOptions & - SearchStatusOptions & - SearchUserIdOptions & - SearchPeopleOptions & - SearchTagOptions & - SearchOcrOptions; - -export type OcrSearchOptions = SearchDateOptions & SearchOcrOptions; - -export type LargeAssetSearchOptions = AssetSearchOptions & { minFileSize?: number }; - -export interface FaceEmbeddingSearch extends SearchEmbeddingOptions { - hasPerson?: boolean; - numResults: number; - maxDistance: number; - minBirthDate?: Date | null; -} - -export interface FaceSearchResult { - distance: number; - id: string; - personId: string | null; -} - -export interface AssetDuplicateResult { - assetId: string; - duplicateId: string | null; - distance: number; -} - -export interface GetStatesOptions { - country?: string; -} - -export interface GetCitiesOptions extends GetStatesOptions { - state?: string; -} - -export interface GetCameraModelsOptions { - make?: string; - lensModel?: string; -} - -export interface GetCameraMakesOptions { - model?: string; - lensModel?: string; -} - -export interface GetCameraLensModelsOptions { - make?: string; - model?: string; -} - -@Injectable() -export class SearchRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ - params: [ - { page: 1, size: 100 }, - { - takenAfter: DummyValue.DATE, - lensModel: DummyValue.STRING, - withStacked: true, - isFavorite: true, - userIds: [DummyValue.UUID], - }, - ], - }) - async searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions) { - const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection; - const items = await searchAssetBuilder(this.db, options) - .selectAll('asset') - .orderBy('asset.fileCreatedAt', orderDirection) - .limit(pagination.size + 1) - .offset((pagination.page - 1) * pagination.size) - .execute(); - - return paginationHelper(items, pagination.size); - } - - @GenerateSql({ - params: [ - { - takenAfter: DummyValue.DATE, - lensModel: DummyValue.STRING, - isFavorite: true, - userIds: [DummyValue.UUID], - }, - ], - }) - searchStatistics(options: AssetSearchOptions) { - return searchAssetBuilder(this.db, options) - .select((qb) => qb.fn.countAll().as('total')) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ - params: [ - 100, - { - takenAfter: DummyValue.DATE, - lensModel: DummyValue.STRING, - withStacked: true, - isFavorite: true, - userIds: [DummyValue.UUID], - }, - ], - }) - async searchRandom(size: number, options: AssetSearchOptions) { - const uuid = randomUUID(); - const builder = searchAssetBuilder(this.db, options); - const lessThan = builder - .selectAll('asset') - .where('asset.id', '<', uuid) - .orderBy(sql`random()`) - .limit(size); - const greaterThan = builder - .selectAll('asset') - .where('asset.id', '>', uuid) - .orderBy(sql`random()`) - .limit(size); - const { rows } = await sql`${lessThan} union all ${greaterThan} limit ${size}`.execute(this.db); - return rows; - } - - @GenerateSql({ - params: [ - 100, - { - takenAfter: DummyValue.DATE, - lensModel: DummyValue.STRING, - withStacked: true, - isFavorite: true, - userIds: [DummyValue.UUID], - }, - ], - }) - searchLargeAssets(size: number, options: LargeAssetSearchOptions) { - const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection; - return searchAssetBuilder(this.db, options) - .selectAll('asset') - .$call(withExif) - .where('asset_exif.fileSizeInByte', '>', options.minFileSize || 0) - .orderBy('asset_exif.fileSizeInByte', orderDirection) - .limit(size) - .execute(); - } - - @GenerateSql({ - params: [ - { page: 1, size: 200 }, - { - takenAfter: DummyValue.DATE, - embedding: DummyValue.VECTOR, - lensModel: DummyValue.STRING, - withStacked: true, - isFavorite: true, - userIds: [DummyValue.UUID], - }, - ], - }) - searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions) { - if (!isValidInteger(pagination.size, { min: 1, max: 1000 })) { - throw new Error(`Invalid value for 'size': ${pagination.size}`); - } - - return this.db.transaction().execute(async (trx) => { - await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx); - const items = await searchAssetBuilder(trx, options) - .selectAll('asset') - .innerJoin('smart_search', 'asset.id', 'smart_search.assetId') - .orderBy(sql`smart_search.embedding <=> ${options.embedding}`) - .limit(pagination.size + 1) - .offset((pagination.page - 1) * pagination.size) - .execute(); - return paginationHelper(items, pagination.size); - }); - } - - @GenerateSql({ - params: [DummyValue.UUID], - }) - async getEmbedding(assetId: string) { - return this.db.selectFrom('smart_search').selectAll().where('assetId', '=', assetId).executeTakeFirst(); - } - - @GenerateSql({ - params: [ - { - userIds: [DummyValue.UUID], - embedding: DummyValue.VECTOR, - numResults: 10, - maxDistance: 0.6, - }, - ], - }) - searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) { - if (!isValidInteger(numResults, { min: 1, max: 1000 })) { - throw new Error(`Invalid value for 'numResults': ${numResults}`); - } - - return this.db.transaction().execute(async (trx) => { - await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Face])}`.execute(trx); - return await trx - .with('cte', (qb) => - qb - .selectFrom('asset_face') - .select([ - 'asset_face.id', - 'asset_face.personId', - sql`face_search.embedding <=> ${embedding}`.as('distance'), - ]) - .innerJoin('asset', 'asset.id', 'asset_face.assetId') - .innerJoin('face_search', 'face_search.faceId', 'asset_face.id') - .leftJoin('person', 'person.id', 'asset_face.personId') - .where('asset.ownerId', '=', anyUuid(userIds)) - .where('asset.deletedAt', 'is', null) - .$if(!!hasPerson, (qb) => qb.where('asset_face.personId', 'is not', null)) - .$if(!!minBirthDate, (qb) => - qb.where((eb) => - eb.or([eb('person.birthDate', 'is', null), eb('person.birthDate', '<=', minBirthDate!)]), - ), - ) - .orderBy('distance') - .limit(numResults), - ) - .selectFrom('cte') - .selectAll() - .where('cte.distance', '<=', maxDistance) - .execute(); - }); - } - - @GenerateSql({ params: [DummyValue.STRING] }) - searchPlaces(placeName: string) { - return this.db - .selectFrom('geodata_places') - .selectAll() - .where( - () => - // kysely doesn't support trigram %>> or <->>> operators - sql` - f_unaccent(name) %>> f_unaccent(${placeName}) or - f_unaccent("admin2Name") %>> f_unaccent(${placeName}) or - f_unaccent("admin1Name") %>> f_unaccent(${placeName}) or - f_unaccent("alternateNames") %>> f_unaccent(${placeName}) - `, - ) - .orderBy( - sql` - coalesce(f_unaccent(name) <->>> f_unaccent(${placeName}), 0.1) + - coalesce(f_unaccent("admin2Name") <->>> f_unaccent(${placeName}), 0.1) + - coalesce(f_unaccent("admin1Name") <->>> f_unaccent(${placeName}), 0.1) + - coalesce(f_unaccent("alternateNames") <->>> f_unaccent(${placeName}), 0.1) - `, - ) - .limit(20) - .execute(); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - getAssetsByCity(userIds: string[]) { - return this.db - .withRecursive('cte', (qb) => { - const base = qb - .selectFrom('asset_exif') - .select(['city', 'assetId']) - .innerJoin('asset', 'asset.id', 'asset_exif.assetId') - .where('asset.ownerId', '=', anyUuid(userIds)) - .where('asset.visibility', '=', AssetVisibility.Timeline) - .where('asset.type', '=', AssetType.Image) - .where('asset.deletedAt', 'is', null) - .orderBy('city') - .limit(1); - - const recursive = qb - .selectFrom('cte') - .select(['l.city', 'l.assetId']) - .innerJoinLateral( - (qb) => - qb - .selectFrom('asset_exif') - .select(['city', 'assetId']) - .innerJoin('asset', 'asset.id', 'asset_exif.assetId') - .where('asset.ownerId', '=', anyUuid(userIds)) - .where('asset.visibility', '=', AssetVisibility.Timeline) - .where('asset.type', '=', AssetType.Image) - .where('asset.deletedAt', 'is', null) - .whereRef('asset_exif.city', '>', 'cte.city') - .orderBy('city') - .limit(1) - .as('l'), - (join) => join.onTrue(), - ); - - return sql<{ city: string; assetId: string }>`(${base} union all ${recursive})`; - }) - .selectFrom('asset') - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .innerJoin('cte', 'asset.id', 'cte.assetId') - .selectAll('asset') - .select((eb) => - eb - .fn('to_jsonb', [eb.table('asset_exif')]) - .$castTo>() - .as('exifInfo'), - ) - .orderBy('asset_exif.city') - .execute(); - } - - async upsert(assetId: string, embedding: string): Promise { - await this.db - .insertInto('smart_search') - .values({ assetId, embedding }) - .onConflict((oc) => oc.column('assetId').doUpdateSet((eb) => ({ embedding: eb.ref('excluded.embedding') }))) - .execute(); - } - - async getCountries(userIds: string[]): Promise { - const res = await this.getExifField('country', userIds).execute(); - return res.map((row) => row.country!); - } - - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING] }) - async getStates(userIds: string[], { country }: GetStatesOptions): Promise { - const res = await this.getExifField('state', userIds) - .$if(!!country, (qb) => qb.where('country', '=', country!)) - .execute(); - - return res.map((row) => row.state!); - } - - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING, DummyValue.STRING] }) - async getCities(userIds: string[], { country, state }: GetCitiesOptions): Promise { - const res = await this.getExifField('city', userIds) - .$if(!!country, (qb) => qb.where('country', '=', country!)) - .$if(!!state, (qb) => qb.where('state', '=', state!)) - .execute(); - - return res.map((row) => row.city!); - } - - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING, DummyValue.STRING] }) - async getCameraMakes(userIds: string[], { model, lensModel }: GetCameraMakesOptions): Promise { - const res = await this.getExifField('make', userIds) - .$if(!!model, (qb) => qb.where('model', '=', model!)) - .$if(!!lensModel, (qb) => qb.where('lensModel', '=', lensModel!)) - .execute(); - - return res.map((row) => row.make!); - } - - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING, DummyValue.STRING] }) - async getCameraModels(userIds: string[], { make, lensModel }: GetCameraModelsOptions): Promise { - const res = await this.getExifField('model', userIds) - .$if(!!make, (qb) => qb.where('make', '=', make!)) - .$if(!!lensModel, (qb) => qb.where('lensModel', '=', lensModel!)) - .execute(); - - return res.map((row) => row.model!); - } - - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.STRING] }) - async getCameraLensModels(userIds: string[], { make, model }: GetCameraLensModelsOptions): Promise { - const res = await this.getExifField('lensModel', userIds) - .$if(!!make, (qb) => qb.where('make', '=', make!)) - .$if(!!model, (qb) => qb.where('model', '=', model!)) - .execute(); - - return res.map((row) => row.lensModel!); - } - - private getExifField( - field: K, - userIds: string[], - ) { - return this.db - .selectFrom('asset_exif') - .select(field) - .distinctOn(field) - .innerJoin('asset', 'asset.id', 'asset_exif.assetId') - .where('ownerId', '=', anyUuid(userIds)) - .where('visibility', '=', AssetVisibility.Timeline) - .where('deletedAt', 'is', null) - .where(field, 'is not', null); - } -} diff --git a/server/src/repositories/server-info.repository.ts b/server/src/repositories/server-info.repository.ts deleted file mode 100644 index 4500094899..0000000000 --- a/server/src/repositories/server-info.repository.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { exiftool } from 'exiftool-vendored'; -import { exec as execCallback } from 'node:child_process'; -import { readFile } from 'node:fs/promises'; -import { promisify } from 'node:util'; -import sharp from 'sharp'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -export interface GitHubRelease { - id: number; - url: string; - tag_name: string; - name: string; - created_at: string; - published_at: string; - body: string; -} - -export interface ServerBuildVersions { - nodejs: string; - ffmpeg: string; - libvips: string; - exiftool: string; - imagemagick: string; -} - -const exec = promisify(execCallback); -const maybeFirstLine = async (command: string): Promise => { - try { - const { stdout } = await exec(command); - return stdout.trim().split('\n')[0] || ''; - } catch { - return ''; - } -}; - -type BuildLockfile = { - sources: Array<{ name: string; version: string }>; - packages: Array<{ name: string; version: string }>; -}; - -const getLockfileVersion = (name: string, lockfile?: BuildLockfile) => { - if (!lockfile) { - return; - } - - const items = [...(lockfile.sources || []), ...(lockfile?.packages || [])]; - const item = items.find((item) => item.name === name); - return item?.version; -}; - -@Injectable() -export class ServerInfoRepository { - constructor( - private configRepository: ConfigRepository, - private logger: LoggingRepository, - ) { - this.logger.setContext(ServerInfoRepository.name); - } - - async getGitHubRelease(): Promise { - try { - const response = await fetch('https://api.github.com/repos/immich-app/immich/releases/latest'); - - if (!response.ok) { - throw new Error(`GitHub API request failed with status ${response.status}: ${await response.text()}`); - } - - return response.json(); - } catch (error) { - throw new Error(`Failed to fetch GitHub release: ${error}`); - } - } - - buildVersions?: ServerBuildVersions; - - private async retrieveVersionFallback( - command: string, - commandTransform?: (output: string) => string, - version?: string, - ): Promise { - if (!version) { - const output = await maybeFirstLine(command); - version = commandTransform ? commandTransform(output) : output; - } - return version; - } - - async getBuildVersions(): Promise { - if (!this.buildVersions) { - const { nodeVersion, resourcePaths } = this.configRepository.getEnv(); - - const lockfile: BuildLockfile | undefined = await readFile(resourcePaths.lockFile) - .then((buffer) => JSON.parse(buffer.toString())) - .catch(() => this.logger.warn(`Failed to read ${resourcePaths.lockFile}`)); - - const [nodejsVersion, ffmpegVersion, magickVersion, exiftoolVersion] = await Promise.all([ - this.retrieveVersionFallback('node --version', undefined, nodeVersion), - this.retrieveVersionFallback( - 'ffmpeg -version', - (output) => output.replaceAll('ffmpeg version ', ''), - getLockfileVersion('ffmpeg', lockfile), - ), - this.retrieveVersionFallback( - 'magick --version', - (output) => output.replaceAll('Version: ImageMagick ', ''), - getLockfileVersion('imagemagick', lockfile), - ), - exiftool.version(), - ]); - - const libvipsVersion = getLockfileVersion('libvips', lockfile) || sharp.versions.vips; - - this.buildVersions = { - nodejs: nodejsVersion, - exiftool: exiftoolVersion, - ffmpeg: ffmpegVersion, - libvips: libvipsVersion, - imagemagick: magickVersion, - }; - } - - return this.buildVersions; - } -} diff --git a/server/src/repositories/session.repository.ts b/server/src/repositories/session.repository.ts index 52292b8e4a..93600fc01a 100644 --- a/server/src/repositories/session.repository.ts +++ b/server/src/repositories/session.repository.ts @@ -7,7 +7,6 @@ import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; import { DB } from 'src/schema'; import { SessionTable } from 'src/schema/tables/session.table'; -import { asUuid } from 'src/utils/database'; export type SessionSearchOptions = { updatedBefore: Date }; @@ -91,14 +90,14 @@ export class SessionRepository { return this.db .updateTable('session') .set(dto) - .where('session.id', '=', asUuid(id)) + .where('session.id', '=', id) .returningAll() .executeTakeFirstOrThrow(); } @GenerateSql({ params: [DummyValue.UUID] }) async delete(id: string) { - await this.db.deleteFrom('session').where('id', '=', asUuid(id)).execute(); + await this.db.deleteFrom('session').where('id', '=', id).execute(); } @GenerateSql({ params: [{ userId: DummyValue.UUID, excludeId: DummyValue.UUID }] }) @@ -114,14 +113,4 @@ export class SessionRepository { async lockAll(userId: string) { await this.db.updateTable('session').set({ pinExpiresAt: null }).where('userId', '=', userId).execute(); } - - @GenerateSql({ params: [DummyValue.UUID] }) - async resetSyncProgress(sessionId: string) { - await this.db.transaction().execute((tx) => { - return Promise.all([ - tx.updateTable('session').set({ isPendingSyncReset: false }).where('id', '=', sessionId).execute(), - tx.deleteFrom('session_sync_checkpoint').where('sessionId', '=', sessionId).execute(), - ]); - }); - } } diff --git a/server/src/repositories/shared-link-asset.repository.ts b/server/src/repositories/shared-link-asset.repository.ts deleted file mode 100644 index 1136546455..0000000000 --- a/server/src/repositories/shared-link-asset.repository.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { DB } from 'src/schema'; - -export class SharedLinkAssetRepository { - constructor(@InjectKysely() private db: Kysely) {} - - async remove(sharedLinkId: string, assetId: string[]) { - const deleted = await this.db - .deleteFrom('shared_link_asset') - .where('shared_link_asset.sharedLinkId', '=', sharedLinkId) - .where('shared_link_asset.assetId', 'in', assetId) - .returning('assetId') - .execute(); - - return deleted.map((row) => row.assetId); - } - - @GenerateSql({ params: [{ sourceAssetId: DummyValue.UUID, targetAssetId: DummyValue.UUID }] }) - async copySharedLinks({ sourceAssetId, targetAssetId }: { sourceAssetId: string; targetAssetId: string }) { - return this.db - .insertInto('shared_link_asset') - .expression((eb) => - eb - .selectFrom('shared_link_asset') - .select((eb) => [eb.val(targetAssetId).as('assetId'), 'shared_link_asset.sharedLinkId']) - .where('shared_link_asset.assetId', '=', sourceAssetId), - ) - .onConflict((oc) => oc.doNothing()) - .execute(); - } -} diff --git a/server/src/repositories/shared-link.repository.ts b/server/src/repositories/shared-link.repository.ts deleted file mode 100644 index 8fab087156..0000000000 --- a/server/src/repositories/shared-link.repository.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, NotNull, sql, Updateable } from 'kysely'; -import { jsonObjectFrom } from 'kysely/helpers/postgres'; -import _ from 'lodash'; -import { InjectKysely } from 'nestjs-kysely'; -import { Album, columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { SharedLinkType } from 'src/enum'; -import { DB } from 'src/schema'; -import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; - -export type SharedLinkSearchOptions = { - userId: string; - id?: string; - albumId?: string; -}; - -@Injectable() -export class SharedLinkRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - get(userId: string, id: string) { - return this.db - .selectFrom('shared_link') - .selectAll('shared_link') - .leftJoinLateral( - (eb) => - eb - .selectFrom('shared_link_asset') - .whereRef('shared_link.id', '=', 'shared_link_asset.sharedLinkId') - .innerJoin('asset', 'asset.id', 'shared_link_asset.assetId') - .where('asset.deletedAt', 'is', null) - .selectAll('asset') - .innerJoinLateral( - (eb) => - eb - .selectFrom('asset_exif') - .selectAll('asset_exif') - .whereRef('asset_exif.assetId', '=', 'asset.id') - .as('exifInfo'), - (join) => join.onTrue(), - ) - .select((eb) => eb.fn.toJson('exifInfo').as('exifInfo')) - .orderBy('asset.fileCreatedAt', 'asc') - .as('a'), - (join) => join.onTrue(), - ) - .leftJoinLateral( - (eb) => - eb - .selectFrom('album') - .selectAll('album') - .whereRef('album.id', '=', 'shared_link.albumId') - .where('album.deletedAt', 'is', null) - .leftJoin('album_asset', 'album_asset.albumId', 'album.id') - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset') - .selectAll('asset') - .whereRef('album_asset.assetId', '=', 'asset.id') - .where('asset.deletedAt', 'is', null) - .innerJoinLateral( - (eb) => - eb - .selectFrom('asset_exif') - .selectAll('asset_exif') - .whereRef('asset_exif.assetId', '=', 'asset.id') - .as('exifInfo'), - (join) => join.onTrue(), - ) - .select((eb) => eb.fn.toJson(eb.table('exifInfo')).as('exifInfo')) - .orderBy('asset.fileCreatedAt', 'asc') - .as('assets'), - (join) => join.onTrue(), - ) - .innerJoinLateral( - (eb) => - eb - .selectFrom('user') - .selectAll('user') - .whereRef('user.id', '=', 'album.ownerId') - .where('user.deletedAt', 'is', null) - .as('owner'), - (join) => join.onTrue(), - ) - .select((eb) => - eb.fn - .coalesce( - eb.fn - .jsonAgg('assets') - .orderBy('assets.fileCreatedAt', 'asc') - .filterWhere('assets.id', 'is not', null), - - sql`'[]'`, - ) - .as('assets'), - ) - .select((eb) => eb.fn.toJson('owner').as('owner')) - .groupBy(['album.id', sql`"owner".*`]) - .as('album'), - (join) => join.onTrue(), - ) - .select((eb) => - eb.fn - .coalesce(eb.fn.jsonAgg('a').filterWhere('a.id', 'is not', null), sql`'[]'`) - .$castTo() - .as('assets'), - ) - .groupBy(['shared_link.id', sql`"album".*`]) - .select((eb) => eb.fn.toJson('album').$castTo().as('album')) - .where('shared_link.id', '=', id) - .where('shared_link.userId', '=', userId) - .where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)])) - .orderBy('shared_link.createdAt', 'desc') - .executeTakeFirst(); - } - - @GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] }) - getAll({ userId, id, albumId }: SharedLinkSearchOptions) { - return this.db - .selectFrom('shared_link') - .selectAll('shared_link') - .where('shared_link.userId', '=', userId) - .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id') - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset') - .select((eb) => eb.fn.jsonAgg('asset').as('assets')) - .whereRef('asset.id', '=', 'shared_link_asset.assetId') - .where('asset.deletedAt', 'is', null) - .as('assets'), - (join) => join.onTrue(), - ) - .select('assets.assets') - .$narrowType<{ assets: NotNull }>() - .leftJoinLateral( - (eb) => - eb - .selectFrom('album') - .selectAll('album') - .whereRef('album.id', '=', 'shared_link.albumId') - .innerJoinLateral( - (eb) => - eb - .selectFrom('user') - .select([ - 'user.id', - 'user.email', - 'user.createdAt', - 'user.profileImagePath', - 'user.isAdmin', - 'user.shouldChangePassword', - 'user.deletedAt', - 'user.oauthId', - 'user.updatedAt', - 'user.storageLabel', - 'user.name', - 'user.quotaSizeInBytes', - 'user.quotaUsageInBytes', - 'user.status', - 'user.profileChangedAt', - ]) - .whereRef('user.id', '=', 'album.ownerId') - .where('user.deletedAt', 'is', null) - .as('owner'), - (join) => join.onTrue(), - ) - .select((eb) => eb.fn.toJson('owner').as('owner')) - .where('album.deletedAt', 'is', null) - .as('album'), - (join) => join.onTrue(), - ) - .select((eb) => eb.fn.toJson('album').$castTo().as('album')) - .where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)])) - .$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!)) - .$if(!!id, (eb) => eb.where('shared_link.id', '=', id!)) - .orderBy('shared_link.createdAt', 'desc') - .distinctOn(['shared_link.createdAt']) - .execute(); - } - - @GenerateSql({ params: [DummyValue.BUFFER] }) - getByKey(key: Buffer) { - return this.authBuilder().where('shared_link.key', '=', key).executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.BUFFER] }) - getBySlug(slug: string) { - return this.authBuilder().where('shared_link.slug', '=', slug).executeTakeFirst(); - } - - private authBuilder() { - return this.db - .selectFrom('shared_link') - .leftJoin('album', 'album.id', 'shared_link.albumId') - .where('album.deletedAt', 'is', null) - .select((eb) => [ - ...columns.authSharedLink, - jsonObjectFrom( - eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'), - ).as('user'), - ]) - .where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)])); - } - - async create(entity: Insertable & { assetIds?: string[] }) { - const { id } = await this.db - .insertInto('shared_link') - .values(_.omit(entity, 'assetIds')) - .returningAll() - .executeTakeFirstOrThrow(); - - if (entity.assetIds && entity.assetIds.length > 0) { - await this.db - .insertInto('shared_link_asset') - .values(entity.assetIds!.map((assetId) => ({ assetId, sharedLinkId: id }))) - .execute(); - } - - return this.getSharedLinks(id); - } - - async update(entity: Updateable & { id: string; assetIds?: string[] }) { - const { id } = await this.db - .updateTable('shared_link') - .set(_.omit(entity, 'assets', 'album', 'assetIds')) - .where('shared_link.id', '=', entity.id) - .returningAll() - .executeTakeFirstOrThrow(); - - if (entity.assetIds && entity.assetIds.length > 0) { - await this.db - .insertInto('shared_link_asset') - .values(entity.assetIds!.map((assetId) => ({ assetId, sharedLinkId: id }))) - .execute(); - } - - return this.getSharedLinks(id); - } - - async remove(id: string): Promise { - await this.db.deleteFrom('shared_link').where('shared_link.id', '=', id).execute(); - } - - private getSharedLinks(id: string) { - return this.db - .selectFrom('shared_link') - .selectAll('shared_link') - .where('shared_link.id', '=', id) - .leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id') - .leftJoinLateral( - (eb) => - eb - .selectFrom('asset') - .whereRef('asset.id', '=', 'shared_link_asset.assetId') - .selectAll('asset') - .innerJoinLateral( - (eb) => - eb.selectFrom('asset_exif').whereRef('asset_exif.assetId', '=', 'asset.id').selectAll().as('exif'), - (join) => join.onTrue(), - ) - .as('assets'), - (join) => join.onTrue(), - ) - .select((eb) => - eb.fn - .coalesce(eb.fn.jsonAgg('assets').filterWhere('assets.id', 'is not', null), sql`'[]'`) - .$castTo() - .as('assets'), - ) - .groupBy('shared_link.id') - .executeTakeFirstOrThrow(); - } -} diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts deleted file mode 100644 index d313d682bd..0000000000 --- a/server/src/repositories/stack.repository.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely'; -import { jsonArrayFrom } from 'kysely/helpers/postgres'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { DB } from 'src/schema'; -import { StackTable } from 'src/schema/tables/stack.table'; -import { asUuid, withDefaultVisibility } from 'src/utils/database'; - -export interface StackSearch { - ownerId: string; - primaryAssetId?: string; -} - -const withAssets = (eb: ExpressionBuilder, withTags = false) => { - return jsonArrayFrom( - eb - .selectFrom('asset') - .selectAll('asset') - .innerJoinLateral( - (eb) => - eb - .selectFrom('asset_exif') - .select(columns.exif) - .whereRef('asset_exif.assetId', '=', 'asset.id') - .as('exifInfo'), - (join) => join.onTrue(), - ) - .$if(withTags, (eb) => - eb.select((eb) => - jsonArrayFrom( - eb - .selectFrom('tag') - .select(columns.tag) - .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagId') - .whereRef('tag_asset.assetId', '=', 'asset.id'), - ).as('tags'), - ), - ) - .select((eb) => eb.fn.toJson('exifInfo').as('exifInfo')) - .where('asset.deletedAt', 'is', null) - .whereRef('asset.stackId', '=', 'stack.id') - .$call(withDefaultVisibility), - ).as('assets'); -}; - -@Injectable() -export class StackRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [{ ownerId: DummyValue.UUID }] }) - search(query: StackSearch) { - return this.db - .selectFrom('stack') - .selectAll('stack') - .select(withAssets) - .where('stack.ownerId', '=', query.ownerId) - .$if(!!query.primaryAssetId, (eb) => eb.where('stack.primaryAssetId', '=', query.primaryAssetId!)) - .execute(); - } - - async create(entity: Omit, 'primaryAssetId'>, assetIds: string[]) { - return this.db.transaction().execute(async (tx) => { - const stacks = await tx - .selectFrom('stack') - .where('stack.ownerId', '=', entity.ownerId) - .where('stack.primaryAssetId', 'in', assetIds) - .select('stack.id') - .select((eb) => - jsonArrayFrom( - eb - .selectFrom('asset') - .select('asset.id') - .whereRef('asset.stackId', '=', 'stack.id') - .where('asset.deletedAt', 'is', null), - ).as('assets'), - ) - .execute(); - - const uniqueIds = new Set(assetIds); - - // children - for (const stack of stacks) { - if (stack.assets && stack.assets.length > 0) { - for (const asset of stack.assets) { - uniqueIds.add(asset.id); - } - } - } - - if (stacks.length > 0) { - await tx - .deleteFrom('stack') - .where( - 'id', - 'in', - stacks.map((stack) => stack.id), - ) - .execute(); - } - - const newRecord = await tx - .insertInto('stack') - .values({ ...entity, primaryAssetId: assetIds[0] }) - .returning('id') - .executeTakeFirstOrThrow(); - - await tx - .updateTable('asset') - .set({ - stackId: newRecord.id, - updatedAt: new Date(), - }) - .where('id', 'in', [...uniqueIds]) - .execute(); - - return tx - .selectFrom('stack') - .selectAll('stack') - .select(withAssets) - .where('id', '=', newRecord.id) - .executeTakeFirstOrThrow(); - }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async delete(id: string): Promise { - await this.db.deleteFrom('stack').where('id', '=', asUuid(id)).execute(); - } - - async deleteAll(ids: string[]): Promise { - await this.db.deleteFrom('stack').where('id', 'in', ids).execute(); - } - - update(id: string, entity: Updateable) { - return this.db - .updateTable('stack') - .set(entity) - .where('id', '=', asUuid(id)) - .returningAll('stack') - .returning((eb) => withAssets(eb, true)) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getById(id: string) { - return this.db - .selectFrom('stack') - .selectAll() - .select((eb) => withAssets(eb, true)) - .where('id', '=', asUuid(id)) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] }) - getForAssetRemoval(assetId: string) { - return this.db - .selectFrom('asset') - .leftJoin('stack', 'stack.id', 'asset.stackId') - .select(['stackId as id', 'stack.primaryAssetId']) - .where('asset.id', '=', assetId) - .executeTakeFirst(); - } - - @GenerateSql({ params: [{ sourceId: DummyValue.UUID, targetId: DummyValue.UUID }] }) - merge({ sourceId, targetId }: { sourceId: string; targetId: string }) { - return this.db.updateTable('asset').set({ stackId: targetId }).where('asset.stackId', '=', sourceId).execute(); - } -} diff --git a/server/src/repositories/storage.repository.spec.ts b/server/src/repositories/storage.repository.spec.ts deleted file mode 100644 index aaf875d3b3..0000000000 --- a/server/src/repositories/storage.repository.spec.ts +++ /dev/null @@ -1,208 +0,0 @@ -import mockfs from 'mock-fs'; -import { CrawlOptionsDto } from 'src/dtos/library.dto'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { automock } from 'test/utils'; - -interface Test { - test: string; - options: CrawlOptionsDto; - files: Record; -} - -const cwd = process.cwd(); - -const tests: Test[] = [ - { - test: 'should return empty when crawling an empty path list', - options: { - pathsToCrawl: [], - }, - files: {}, - }, - { - test: 'should crawl a single path', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - }, - }, - { - test: 'should exclude by file extension', - options: { - pathsToCrawl: ['/photos/'], - exclusionPatterns: ['**/*.tif'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.tif': false, - }, - }, - { - test: 'should exclude by file extension without case sensitivity', - options: { - pathsToCrawl: ['/photos/'], - exclusionPatterns: ['**/*.TIF'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.tif': false, - }, - }, - { - test: 'should exclude by folder', - options: { - pathsToCrawl: ['/photos/'], - exclusionPatterns: ['**/raw/**'], - }, - files: { - '/photos/image.jpg': true, - '/photos/raw/image.jpg': false, - '/photos/raw2/image.jpg': true, - '/photos/folder/raw/image.jpg': false, - '/photos/crawl/image.jpg': true, - }, - }, - { - test: 'should crawl multiple paths', - options: { - pathsToCrawl: ['/photos/', '/images/', '/albums/'], - }, - files: { - '/photos/image1.jpg': true, - '/images/image2.jpg': true, - '/albums/image3.jpg': true, - }, - }, - { - test: 'should crawl a single path without trailing slash', - options: { - pathsToCrawl: ['/photos'], - }, - files: { - '/photos/image.jpg': true, - }, - }, - { - test: 'should crawl a single path', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/subfolder/image1.jpg': true, - '/photos/subfolder/image2.jpg': true, - '/image1.jpg': false, - }, - }, - { - test: 'should filter file extensions', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.txt': false, - '/photos/1': false, - }, - }, - { - test: 'should include photo and video extensions', - options: { - pathsToCrawl: ['/photos/', '/videos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.jpeg': true, - '/photos/image.heic': true, - '/photos/image.heif': true, - '/photos/image.png': true, - '/photos/image.gif': true, - '/photos/image.tif': true, - '/photos/image.tiff': true, - '/photos/image.webp': true, - '/photos/image.dng': true, - '/photos/image.nef': true, - '/videos/video.mp4': true, - '/videos/video.mov': true, - '/videos/video.webm': true, - }, - }, - { - test: 'should check file extensions without case sensitivity', - options: { - pathsToCrawl: ['/photos/'], - }, - files: { - '/photos/image.jpg': true, - '/photos/image.Jpg': true, - '/photos/image.jpG': true, - '/photos/image.JPG': true, - '/photos/image.jpEg': true, - '/photos/image.TIFF': true, - '/photos/image.tif': true, - '/photos/image.dng': true, - '/photos/image.NEF': true, - }, - }, - { - test: 'should normalize the path', - options: { - pathsToCrawl: ['/photos/1/../2'], - }, - files: { - '/photos/1/image.jpg': false, - '/photos/2/image.jpg': true, - }, - }, - { - test: 'should return absolute paths', - options: { - pathsToCrawl: ['photos'], - }, - files: { - [`${cwd}/photos/1.jpg`]: true, - [`${cwd}/photos/2.jpg`]: true, - [`/photos/3.jpg`]: false, - }, - }, - { - test: 'should support special characters in paths', - options: { - pathsToCrawl: ['/photos (new)'], - }, - files: { - ['/photos (new)/1.jpg']: true, - }, - }, -]; - -describe(StorageRepository.name, () => { - let sut: StorageRepository; - - beforeEach(() => { - // eslint-disable-next-line no-sparse-arrays - sut = new StorageRepository(automock(LoggingRepository, { args: [, { getEnv: () => ({}) }], strict: false })); - }); - - afterEach(() => { - mockfs.restore(); - }); - - describe('crawl', () => { - for (const { test, options, files } of tests) { - it(test, async () => { - mockfs(Object.fromEntries(Object.keys(files).map((file) => [file, '']))); - - const actual = await sut.crawl(options); - const expected = Object.entries(files) - .filter((entry) => entry[1]) - .map(([file]) => file); - - expect(actual.toSorted()).toEqual(expected.toSorted()); - }); - } - }); -}); diff --git a/server/src/repositories/storage.repository.ts b/server/src/repositories/storage.repository.ts deleted file mode 100644 index 5a1a936e77..0000000000 --- a/server/src/repositories/storage.repository.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import archiver from 'archiver'; -import chokidar, { ChokidarOptions } from 'chokidar'; -import { escapePath, glob, globStream } from 'fast-glob'; -import { constants, createReadStream, createWriteStream, existsSync, mkdirSync, ReadOptionsWithBuffer } from 'node:fs'; -import fs from 'node:fs/promises'; -import path from 'node:path'; -import { PassThrough, Readable, Writable } from 'node:stream'; -import { createGunzip, createGzip } from 'node:zlib'; -import { CrawlOptionsDto, WalkOptionsDto } from 'src/dtos/library.dto'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { mimeTypes } from 'src/utils/mime-types'; - -export interface WatchEvents { - onReady(): void; - onAdd(path: string): void; - onChange(path: string): void; - onUnlink(path: string): void; - onError(error: Error): void; -} - -export interface ImmichReadStream { - stream: Readable; - type?: string; - length?: number; -} - -export interface ImmichZipStream extends ImmichReadStream { - addFile: (inputPath: string, filename: string) => void; - finalize: () => Promise; -} - -export interface DiskUsage { - available: number; - free: number; - total: number; -} - -@Injectable() -export class StorageRepository { - constructor(private logger: LoggingRepository) { - this.logger.setContext(StorageRepository.name); - } - - realpath(filepath: string) { - return fs.realpath(filepath); - } - - readdir(folder: string): Promise { - return fs.readdir(folder); - } - - copyFile(source: string, target: string) { - return fs.copyFile(source, target); - } - - stat(filepath: string) { - return fs.stat(filepath); - } - - createFile(filepath: string, buffer: Buffer) { - return fs.writeFile(filepath, buffer, { flag: 'wx' }); - } - - createWriteStream(filepath: string): Writable { - return createWriteStream(filepath, { flags: 'w' }); - } - - createOrOverwriteFile(filepath: string, buffer: Buffer) { - return fs.writeFile(filepath, buffer, { flag: 'w' }); - } - - overwriteFile(filepath: string, buffer: Buffer) { - return fs.writeFile(filepath, buffer, { flag: 'r+' }); - } - - rename(source: string, target: string) { - return fs.rename(source, target); - } - - utimes(filepath: string, atime: Date, mtime: Date) { - return fs.utimes(filepath, atime, mtime); - } - - createZipStream(): ImmichZipStream { - const archive = archiver('zip', { store: true }); - - const addFile = (input: string, filename: string) => { - archive.file(input, { name: filename, mode: 0o644 }); - }; - - const finalize = () => archive.finalize(); - - return { stream: archive, addFile, finalize }; - } - - createGzip(): PassThrough { - return createGzip(); - } - - createGunzip(): PassThrough { - return createGunzip(); - } - - createPlainReadStream(filepath: string): Readable { - return createReadStream(filepath); - } - - async createReadStream(filepath: string, mimeType?: string | null): Promise { - const { size } = await fs.stat(filepath); - await fs.access(filepath, constants.R_OK); - return { - stream: createReadStream(filepath), - length: size, - type: mimeType || undefined, - }; - } - - async readFile(filepath: string, options?: ReadOptionsWithBuffer): Promise { - const file = await fs.open(filepath); - try { - const { buffer } = await file.read(options); - return buffer as Buffer; - } finally { - await file.close(); - } - } - - async readTextFile(filepath: string): Promise { - return fs.readFile(filepath, 'utf8'); - } - - async checkFileExists(filepath: string, mode = constants.F_OK): Promise { - try { - await fs.access(filepath, mode); - return true; - } catch { - return false; - } - } - - async unlink(file: string) { - try { - await fs.unlink(file); - } catch (error) { - if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') { - this.logger.warn(`File ${file} does not exist.`); - } else { - throw error; - } - } - } - - async unlinkDir(folder: string, options: { recursive?: boolean; force?: boolean }) { - await fs.rm(folder, { ...options, maxRetries: 5, retryDelay: 100 }); - } - - async removeEmptyDirs(directory: string, self: boolean = false) { - // lstat does not follow symlinks (in contrast to stat) - const stats = await fs.lstat(directory); - if (!stats.isDirectory()) { - return; - } - - const files = await fs.readdir(directory); - await Promise.all(files.map((file) => this.removeEmptyDirs(path.join(directory, file), true))); - - if (self) { - const updated = await fs.readdir(directory); - if (updated.length === 0) { - try { - await fs.rmdir(directory); - } catch (error: Error | any) { - if (error.code !== 'ENOTEMPTY') { - this.logger.warn(`Attempted to remove directory, but failed: ${error}`); - } - } - } - } - } - - mkdirSync(filepath: string): void { - if (!existsSync(filepath)) { - mkdirSync(filepath, { recursive: true }); - } - } - - existsSync(filepath: string) { - return existsSync(filepath); - } - - async checkDiskUsage(folder: string): Promise { - const stats = await fs.statfs(folder); - return { - available: stats.bavail * stats.bsize, - free: stats.bfree * stats.bsize, - total: stats.blocks * stats.bsize, - }; - } - - crawl(crawlOptions: CrawlOptionsDto): Promise { - const { pathsToCrawl, exclusionPatterns, includeHidden } = crawlOptions; - if (pathsToCrawl.length === 0) { - return Promise.resolve([]); - } - - const globbedPaths = pathsToCrawl.map((path) => this.asGlob(path)); - - return glob(globbedPaths, { - absolute: true, - caseSensitiveMatch: false, - onlyFiles: true, - dot: includeHidden, - ignore: exclusionPatterns, - }); - } - - async *walk(walkOptions: WalkOptionsDto): AsyncGenerator { - const { pathsToCrawl, exclusionPatterns, includeHidden } = walkOptions; - if (pathsToCrawl.length === 0) { - async function* emptyGenerator() {} - return emptyGenerator(); - } - - const globbedPaths = pathsToCrawl.map((path) => this.asGlob(path)); - - const stream = globStream(globbedPaths, { - absolute: true, - caseSensitiveMatch: false, - onlyFiles: true, - dot: includeHidden, - ignore: exclusionPatterns, - }); - - let batch: string[] = []; - for await (const value of stream) { - batch.push(value.toString()); - if (batch.length === walkOptions.take) { - yield batch; - batch = []; - } - } - - if (batch.length > 0) { - yield batch; - } - } - - watch(paths: string[], options: ChokidarOptions, events: Partial) { - const watcher = chokidar.watch(paths, options); - - watcher.on('ready', () => events.onReady?.()); - watcher.on('add', (path) => events.onAdd?.(path)); - watcher.on('change', (path) => events.onChange?.(path)); - watcher.on('unlink', (path) => events.onUnlink?.(path)); - watcher.on('error', (error) => events.onError?.(error as Error)); - - return () => watcher.close(); - } - - private asGlob(pathToCrawl: string): string { - const escapedPath = escapePath(pathToCrawl).replaceAll('"', '["]').replaceAll("'", "[']").replaceAll('`', '[`]'); - const extensions = `*{${mimeTypes.getSupportedFileExtensions().join(',')}}`; - return `${escapedPath}/**/${extensions}`; - } -} diff --git a/server/src/repositories/sync-checkpoint.repository.ts b/server/src/repositories/sync-checkpoint.repository.ts deleted file mode 100644 index 9db56e1bfe..0000000000 --- a/server/src/repositories/sync-checkpoint.repository.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { SyncEntityType } from 'src/enum'; -import { DB } from 'src/schema'; -import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table'; - -@Injectable() -export class SyncCheckpointRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - getAll(sessionId: string) { - return this.db - .selectFrom('session_sync_checkpoint') - .select(['type', 'ack']) - .where('sessionId', '=', sessionId) - .execute(); - } - - upsertAll(items: Insertable[]) { - return this.db - .insertInto('session_sync_checkpoint') - .values(items) - .onConflict((oc) => - oc.columns(['sessionId', 'type']).doUpdateSet((eb) => ({ - ack: eb.ref('excluded.ack'), - })), - ) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - deleteAll(sessionId: string, types?: SyncEntityType[]) { - return this.db - .deleteFrom('session_sync_checkpoint') - .where('sessionId', '=', sessionId) - .$if(!!types, (qb) => qb.where('type', 'in', types!)) - .execute(); - } - - @GenerateSql() - getNow() { - return this.db - .selectNoFrom((eb) => [ - eb.fn('immich_uuid_v7', [sql.raw("now() - interval '1 millisecond'")]).as('nowId'), - ]) - .executeTakeFirstOrThrow(); - } -} diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts deleted file mode 100644 index 511d7b589f..0000000000 --- a/server/src/repositories/sync.repository.ts +++ /dev/null @@ -1,772 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Kysely, sql } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { DB } from 'src/schema'; -import { SyncAck } from 'src/types'; - -export type SyncBackfillOptions = { - nowId: string; - afterUpdateId?: string; - beforeUpdateId: string; -}; - -const dummyBackfillOptions = { - nowId: DummyValue.UUID, - beforeUpdateId: DummyValue.UUID, - afterUpdateId: DummyValue.UUID, -}; - -export type SyncCreatedAfterOptions = { - nowId: string; - userId: string; - afterCreateId?: string; -}; - -const dummyCreateAfterOptions = { - nowId: DummyValue.UUID, - userId: DummyValue.UUID, - afterCreateId: DummyValue.UUID, -}; - -export type SyncQueryOptions = { - nowId: string; - userId: string; - ack?: SyncAck; -}; - -const dummyQueryOptions = { - nowId: DummyValue.UUID, - userId: DummyValue.UUID, - ack: { - updateId: DummyValue.UUID, - }, -}; - -@Injectable() -export class SyncRepository { - album: AlbumSync; - albumAsset: AlbumAssetSync; - albumAssetExif: AlbumAssetExifSync; - albumToAsset: AlbumToAssetSync; - albumUser: AlbumUserSync; - asset: AssetSync; - assetExif: AssetExifSync; - assetFace: AssetFaceSync; - assetMetadata: AssetMetadataSync; - authUser: AuthUserSync; - memory: MemorySync; - memoryToAsset: MemoryToAssetSync; - partner: PartnerSync; - partnerAsset: PartnerAssetsSync; - partnerAssetExif: PartnerAssetExifsSync; - partnerStack: PartnerStackSync; - person: PersonSync; - stack: StackSync; - user: UserSync; - userMetadata: UserMetadataSync; - - constructor(@InjectKysely() private db: Kysely) { - this.album = new AlbumSync(this.db); - this.albumAsset = new AlbumAssetSync(this.db); - this.albumAssetExif = new AlbumAssetExifSync(this.db); - this.albumToAsset = new AlbumToAssetSync(this.db); - this.albumUser = new AlbumUserSync(this.db); - this.asset = new AssetSync(this.db); - this.assetExif = new AssetExifSync(this.db); - this.assetFace = new AssetFaceSync(this.db); - this.assetMetadata = new AssetMetadataSync(this.db); - this.authUser = new AuthUserSync(this.db); - this.memory = new MemorySync(this.db); - this.memoryToAsset = new MemoryToAssetSync(this.db); - this.partner = new PartnerSync(this.db); - this.partnerAsset = new PartnerAssetsSync(this.db); - this.partnerAssetExif = new PartnerAssetExifsSync(this.db); - this.partnerStack = new PartnerStackSync(this.db); - this.person = new PersonSync(this.db); - this.stack = new StackSync(this.db); - this.user = new UserSync(this.db); - this.userMetadata = new UserMetadataSync(this.db); - } -} - -class BaseSync { - constructor(protected db: Kysely) {} - - protected backfillQuery(t: T, { nowId, beforeUpdateId, afterUpdateId }: SyncBackfillOptions) { - const { table, ref } = this.db.dynamic; - const updateIdRef = ref(`${t}.updateId`); - - return this.db - .selectFrom(table(t).as(t)) - .where(updateIdRef, '<', nowId) - .where(updateIdRef, '<=', beforeUpdateId) - .$if(!!afterUpdateId, (qb) => qb.where(updateIdRef, '>=', afterUpdateId!)) - .orderBy(updateIdRef, 'asc'); - } - - protected auditQuery(t: T, { nowId, ack }: SyncQueryOptions) { - const { table, ref } = this.db.dynamic; - const idRef = ref(`${t}.id`); - - return this.db - .selectFrom(table(t).as(t)) - .where(idRef, '<', nowId) - .$if(!!ack, (qb) => qb.where(idRef, '>', ack!.updateId)) - .orderBy(idRef, 'asc'); - } - - protected auditCleanup(t: T, days: number) { - const { table, ref } = this.db.dynamic; - - return this.db - .deleteFrom(table(t).as(t)) - .where(ref(`${t}.deletedAt`), '<', sql.raw(`now() - interval '${days} days'`)) - .execute(); - } - - protected upsertQuery(t: T, { nowId, ack }: SyncQueryOptions) { - const { table, ref } = this.db.dynamic; - const updateIdRef = ref(`${t}.updateId`); - - return this.db - .selectFrom(table(t).as(t)) - .where(updateIdRef, '<', nowId) - .$if(!!ack, (qb) => qb.where(updateIdRef, '>', ack!.updateId)) - .orderBy(updateIdRef, 'asc'); - } -} - -class AlbumSync extends BaseSync { - @GenerateSql({ params: [dummyCreateAfterOptions] }) - getCreatedAfter({ nowId, userId, afterCreateId }: SyncCreatedAfterOptions) { - return this.db - .selectFrom('album_user') - .select(['albumId as id', 'createId']) - .where('userId', '=', userId) - .$if(!!afterCreateId, (qb) => qb.where('createId', '>=', afterCreateId!)) - .where('createId', '<', nowId) - .orderBy('createId', 'asc') - .execute(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('album_audit', options) - .select(['id', 'albumId']) - .where('userId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('album_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - const userId = options.userId; - return this.upsertQuery('album', options) - .distinctOn(['album.id', 'album.updateId']) - .leftJoin('album_user as album_users', 'album.id', 'album_users.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_users.userId', '=', userId)])) - .select([ - 'album.id', - 'album.ownerId', - 'album.albumName as name', - 'album.description', - 'album.createdAt', - 'album.updatedAt', - 'album.albumThumbnailAssetId as thumbnailAssetId', - 'album.isActivityEnabled', - 'album.order', - 'album.updateId', - ]) - .stream(); - } -} - -class AlbumAssetSync extends BaseSync { - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, albumId: string) { - return this.backfillQuery('album_asset', options) - .innerJoin('asset', 'asset.id', 'album_asset.assetId') - .select(columns.syncAsset) - .select('album_asset.updateId') - .where('album_asset.albumId', '=', albumId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions, { updateId: DummyValue.UUID }], stream: true }) - getUpdates(options: SyncQueryOptions, albumToAssetAck: SyncAck) { - const userId = options.userId; - return this.upsertQuery('asset', options) - .innerJoin('album_asset', 'album_asset.assetId', 'asset.id') - .select(columns.syncAsset) - .select('asset.updateId') - .where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send updates for assets that the client already knows about - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getCreates(options: SyncQueryOptions) { - const userId = options.userId; - return this.upsertQuery('album_asset', options) - .select('album_asset.updateId') - .innerJoin('asset', 'asset.id', 'album_asset.assetId') - .select(columns.syncAsset) - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) - .stream(); - } -} - -class AlbumAssetExifSync extends BaseSync { - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, albumId: string) { - return this.backfillQuery('album_asset', options) - .innerJoin('asset_exif', 'asset_exif.assetId', 'album_asset.assetId') - .select(columns.syncAssetExif) - .select('album_asset.updateId') - .where('album_asset.albumId', '=', albumId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions, { updateId: DummyValue.UUID }], stream: true }) - getUpdates(options: SyncQueryOptions, albumToAssetAck: SyncAck) { - const userId = options.userId; - return this.upsertQuery('asset_exif', options) - .innerJoin('album_asset', 'album_asset.assetId', 'asset_exif.assetId') - .select(columns.syncAssetExif) - .select('asset_exif.updateId') - .where('album_asset.updateId', '<=', albumToAssetAck.updateId) // Ensure we only send exif updates for assets that the client already knows about - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getCreates(options: SyncQueryOptions) { - const userId = options.userId; - return this.upsertQuery('album_asset', options) - .select('album_asset.updateId') - .innerJoin('asset_exif', 'asset_exif.assetId', 'album_asset.assetId') - .select(columns.syncAssetExif) - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) - .stream(); - } -} - -class AlbumToAssetSync extends BaseSync { - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, albumId: string) { - return this.backfillQuery('album_asset', options) - .select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId']) - .where('album_asset.albumId', '=', albumId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - const userId = options.userId; - return this.auditQuery('album_asset_audit', options) - .select(['id', 'assetId', 'albumId']) - .where((eb) => - eb( - 'albumId', - 'in', - eb - .selectFrom('album') - .select(['id']) - .where('ownerId', '=', userId) - .union((eb) => - eb.parens( - eb - .selectFrom('album_user') - .select(['album_user.albumId as id']) - .where('album_user.userId', '=', userId), - ), - ), - ), - ) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('album_asset_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - const userId = options.userId; - return this.upsertQuery('album_asset', options) - .select(['album_asset.assetId as assetId', 'album_asset.albumId as albumId', 'album_asset.updateId']) - .innerJoin('album', 'album.id', 'album_asset.albumId') - .leftJoin('album_user', 'album_user.albumId', 'album_asset.albumId') - .where((eb) => eb.or([eb('album.ownerId', '=', userId), eb('album_user.userId', '=', userId)])) - .stream(); - } -} - -class AlbumUserSync extends BaseSync { - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, albumId: string) { - return this.backfillQuery('album_user', options) - .select(columns.syncAlbumUser) - .select('album_user.updateId') - .where('albumId', '=', albumId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - const userId = options.userId; - return this.auditQuery('album_user_audit', options) - .select(['id', 'userId', 'albumId']) - .where((eb) => - eb( - 'albumId', - 'in', - eb - .selectFrom('album') - .select(['id']) - .where('ownerId', '=', userId) - .union((eb) => - eb.parens( - eb - .selectFrom('album_user') - .select(['album_user.albumId as id']) - .where('album_user.userId', '=', userId), - ), - ), - ), - ) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('album_user_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - const userId = options.userId; - return this.upsertQuery('album_user', options) - .select(columns.syncAlbumUser) - .select('album_user.updateId') - .where((eb) => - eb( - 'album_user.albumId', - 'in', - eb - .selectFrom('album') - .select(['id']) - .where('ownerId', '=', userId) - .union((eb) => - eb.parens( - eb - .selectFrom('album_user as albumUsers') - .select(['albumUsers.albumId as id']) - .where('albumUsers.userId', '=', userId), - ), - ), - ), - ) - .stream(); - } -} - -class AssetSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('asset_audit', options) - .select(['id', 'assetId']) - .where('ownerId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('asset_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('asset', options) - .select(columns.syncAsset) - .select('asset.updateId') - .where('ownerId', '=', options.userId) - .stream(); - } -} - -class AuthUserSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('user', options) - .select(columns.syncUser) - .select(['isAdmin', 'pinCode', 'oauthId', 'storageLabel', 'quotaSizeInBytes', 'quotaUsageInBytes']) - .where('id', '=', options.userId) - .stream(); - } -} - -class PersonSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('person_audit', options) - .select(['id', 'personId']) - .where('ownerId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('person_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('person', options) - .select([ - 'id', - 'createdAt', - 'updatedAt', - 'ownerId', - 'name', - 'birthDate', - 'isHidden', - 'isFavorite', - 'color', - 'updateId', - 'faceAssetId', - ]) - .where('ownerId', '=', options.userId) - .stream(); - } -} - -class AssetFaceSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('asset_face_audit', options) - .select(['asset_face_audit.id', 'assetFaceId']) - .leftJoin('asset', 'asset.id', 'asset_face_audit.assetId') - .where('asset.ownerId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('asset_face_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('asset_face', options) - .select([ - 'asset_face.id', - 'assetId', - 'personId', - 'imageWidth', - 'imageHeight', - 'boundingBoxX1', - 'boundingBoxY1', - 'boundingBoxX2', - 'boundingBoxY2', - 'sourceType', - 'asset_face.updateId', - ]) - .leftJoin('asset', 'asset.id', 'asset_face.assetId') - .where('asset.ownerId', '=', options.userId) - .where('asset_face.isVisible', '=', true) - .stream(); - } -} - -class AssetExifSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('asset_exif', options) - .select(columns.syncAssetExif) - .select('asset_exif.updateId') - .where('assetId', 'in', (eb) => eb.selectFrom('asset').select('id').where('ownerId', '=', options.userId)) - .stream(); - } -} - -class MemorySync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('memory_audit', options) - .select(['id', 'memoryId']) - .where('userId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('memory_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('memory', options) - .select([ - 'id', - 'createdAt', - 'updatedAt', - 'deletedAt', - 'ownerId', - 'type', - 'data', - 'isSaved', - 'memoryAt', - 'seenAt', - 'showAt', - 'hideAt', - ]) - .select('updateId') - .where('ownerId', '=', options.userId) - .stream(); - } -} - -class MemoryToAssetSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('memory_asset_audit', options) - .select(['id', 'memoryId', 'assetId']) - .where('memoryId', 'in', (eb) => eb.selectFrom('memory').select('id').where('ownerId', '=', options.userId)) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('memory_asset_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('memory_asset', options) - .select(['memoriesId as memoryId', 'assetId as assetId']) - .select('updateId') - .where('memoriesId', 'in', (eb) => eb.selectFrom('memory').select('id').where('ownerId', '=', options.userId)) - .stream(); - } -} - -class PartnerSync extends BaseSync { - @GenerateSql({ params: [dummyCreateAfterOptions] }) - getCreatedAfter({ nowId, userId, afterCreateId }: SyncCreatedAfterOptions) { - return this.db - .selectFrom('partner') - .select(['sharedById', 'createId']) - .where('sharedWithId', '=', userId) - .$if(!!afterCreateId, (qb) => qb.where('createId', '>=', afterCreateId!)) - .where('createId', '<', nowId) - .orderBy('partner.createId', 'asc') - .execute(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - const userId = options.userId; - return this.auditQuery('partner_audit', options) - .select(['id', 'sharedById', 'sharedWithId']) - .where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)])) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('partner_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - const userId = options.userId; - return this.upsertQuery('partner', options) - .select(['sharedById', 'sharedWithId', 'inTimeline', 'updateId']) - .where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)])) - .stream(); - } -} - -class PartnerAssetsSync extends BaseSync { - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, partnerId: string) { - return this.backfillQuery('asset', options) - .select(columns.syncAsset) - .select('asset.updateId') - .where('ownerId', '=', partnerId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('asset_audit', options) - .select(['id', 'assetId']) - .where('ownerId', 'in', (eb) => - eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', options.userId), - ) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('asset', options) - .select(columns.syncAsset) - .select('asset.updateId') - .where('ownerId', 'in', (eb) => - eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', options.userId), - ) - .stream(); - } -} - -class PartnerAssetExifsSync extends BaseSync { - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, partnerId: string) { - return this.backfillQuery('asset_exif', options) - .select(columns.syncAssetExif) - .select('asset_exif.updateId') - .innerJoin('asset', 'asset.id', 'asset_exif.assetId') - .where('asset.ownerId', '=', partnerId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('asset_exif', options) - .select(columns.syncAssetExif) - .select('asset_exif.updateId') - .where('assetId', 'in', (eb) => - eb - .selectFrom('asset') - .select('id') - .where('ownerId', 'in', (eb) => - eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', options.userId), - ), - ) - .stream(); - } -} - -class StackSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('stack_audit', options) - .select(['id', 'stackId']) - .where('userId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('stack_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('stack', options) - .select(columns.syncStack) - .select('updateId') - .where('ownerId', '=', options.userId) - .stream(); - } -} - -class PartnerStackSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('stack_audit', options) - .select(['id', 'stackId']) - .where('userId', 'in', (eb) => - eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', options.userId), - ) - .stream(); - } - - @GenerateSql({ params: [dummyBackfillOptions, DummyValue.UUID], stream: true }) - getBackfill(options: SyncBackfillOptions, partnerId: string) { - return this.backfillQuery('stack', options) - .select(columns.syncStack) - .select('updateId') - .where('ownerId', '=', partnerId) - .stream(); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('stack', options) - .select(columns.syncStack) - .select('updateId') - .where('ownerId', 'in', (eb) => - eb.selectFrom('partner').select(['sharedById']).where('sharedWithId', '=', options.userId), - ) - .stream(); - } -} - -class UserSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('user_audit', options).select(['id', 'userId']).stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('user_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('user', options).select(columns.syncUser).stream(); - } -} - -class UserMetadataSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getDeletes(options: SyncQueryOptions) { - return this.auditQuery('user_metadata_audit', options) - .select(['id', 'userId', 'key']) - .where('userId', '=', options.userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('user_metadata_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions], stream: true }) - getUpserts(options: SyncQueryOptions) { - return this.upsertQuery('user_metadata', options) - .select(['userId', 'key', 'value', 'updateId']) - .where('userId', '=', options.userId) - .stream(); - } -} - -class AssetMetadataSync extends BaseSync { - @GenerateSql({ params: [dummyQueryOptions, DummyValue.UUID], stream: true }) - getDeletes(options: SyncQueryOptions, userId: string) { - return this.auditQuery('asset_metadata_audit', options) - .select(['asset_metadata_audit.id', 'assetId', 'key']) - .leftJoin('asset', 'asset.id', 'asset_metadata_audit.assetId') - .where('asset.ownerId', '=', userId) - .stream(); - } - - cleanupAuditTable(daysAgo: number) { - return this.auditCleanup('asset_metadata_audit', daysAgo); - } - - @GenerateSql({ params: [dummyQueryOptions, DummyValue.UUID], stream: true }) - getUpserts(options: SyncQueryOptions, userId: string) { - return this.upsertQuery('asset_metadata', options) - .select(['assetId', 'key', 'value', 'asset_metadata.updateId']) - .innerJoin('asset', 'asset.id', 'asset_metadata.assetId') - .where('asset.ownerId', '=', userId) - .stream(); - } -} diff --git a/server/src/repositories/tag.repository.ts b/server/src/repositories/tag.repository.ts deleted file mode 100644 index d4572886af..0000000000 --- a/server/src/repositories/tag.repository.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, sql, Updateable } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { columns } from 'src/database'; -import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { DB } from 'src/schema'; -import { TagAssetTable } from 'src/schema/tables/tag-asset.table'; -import { TagTable } from 'src/schema/tables/tag.table'; - -@Injectable() -export class TagRepository { - constructor( - @InjectKysely() private db: Kysely, - private logger: LoggingRepository, - ) { - this.logger.setContext(TagRepository.name); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - get(id: string) { - return this.db.selectFrom('tag').select(columns.tag).where('id', '=', id).executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - getByValue(userId: string, value: string) { - return this.db - .selectFrom('tag') - .select(columns.tag) - .where('userId', '=', userId) - .where('value', '=', value) - .executeTakeFirst(); - } - - @GenerateSql({ params: [{ userId: DummyValue.UUID, value: DummyValue.STRING, parentId: DummyValue.UUID }] }) - async upsertValue({ userId, value, parentId: _parentId }: { userId: string; value: string; parentId?: string }) { - const parentId = _parentId ?? null; - return this.db.transaction().execute(async (tx) => { - const tag = await this.db - .insertInto('tag') - .values({ userId, value, parentId }) - .onConflict((oc) => oc.columns(['userId', 'value']).doUpdateSet({ parentId })) - .returning(columns.tag) - .executeTakeFirstOrThrow(); - - // update closure table - await tx - .insertInto('tag_closure') - .values({ id_ancestor: tag.id, id_descendant: tag.id }) - .onConflict((oc) => oc.doNothing()) - .execute(); - - if (parentId) { - await tx - .insertInto('tag_closure') - .columns(['id_ancestor', 'id_descendant']) - .expression( - this.db - .selectFrom('tag_closure') - .select(['id_ancestor', sql.raw(`'${tag.id}'`).as('id_descendant')]) - .where('id_descendant', '=', parentId), - ) - .onConflict((oc) => oc.doNothing()) - .execute(); - } - - return tag; - }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getAll(userId: string) { - return this.db.selectFrom('tag').select(columns.tag).where('userId', '=', userId).orderBy('value').execute(); - } - - @GenerateSql({ params: [{ userId: DummyValue.UUID, color: DummyValue.STRING, value: DummyValue.STRING }] }) - create(tag: Insertable) { - return this.db.insertInto('tag').values(tag).returningAll().executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID, { color: DummyValue.STRING }] }) - update(id: string, dto: Updateable) { - return this.db.updateTable('tag').set(dto).where('id', '=', id).returningAll().executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async delete(id: string) { - await this.db.deleteFrom('tag').where('id', '=', id).execute(); - } - - @ChunkedSet({ paramIndex: 1 }) - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - async getAssetIds(tagId: string, assetIds: string[]): Promise> { - if (assetIds.length === 0) { - return new Set(); - } - - const results = await this.db - .selectFrom('tag_asset') - .select(['assetId as assetId']) - .where('tagId', '=', tagId) - .where('assetId', 'in', assetIds) - .execute(); - - return new Set(results.map(({ assetId }) => assetId)); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - @Chunked({ paramIndex: 1 }) - async addAssetIds(tagId: string, assetIds: string[]): Promise { - if (assetIds.length === 0) { - return; - } - - await this.db - .insertInto('tag_asset') - .values(assetIds.map((assetId) => ({ tagId, assetId }))) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - @Chunked({ paramIndex: 1 }) - async removeAssetIds(tagId: string, assetIds: string[]): Promise { - if (assetIds.length === 0) { - return; - } - - await this.db.deleteFrom('tag_asset').where('tagId', '=', tagId).where('assetId', 'in', assetIds).execute(); - } - - @GenerateSql({ params: [[{ assetId: DummyValue.UUID, tagIds: DummyValue.UUID }]] }) - @Chunked() - upsertAssetIds(items: Insertable[]) { - if (items.length === 0) { - return Promise.resolve([]); - } - - return this.db - .insertInto('tag_asset') - .values(items) - .onConflict((oc) => oc.doNothing()) - .returningAll() - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) - @Chunked({ paramIndex: 1 }) - replaceAssetTags(assetId: string, tagIds: string[]) { - return this.db.transaction().execute(async (tx) => { - await tx.deleteFrom('tag_asset').where('assetId', '=', assetId).execute(); - - if (tagIds.length === 0) { - return; - } - - return tx - .insertInto('tag_asset') - .values(tagIds.map((tagId) => ({ tagId, assetId }))) - .onConflict((oc) => oc.doNothing()) - .returningAll() - .execute(); - }); - } - - async deleteEmptyTags() { - const result = await this.db - .deleteFrom('tag') - .where(({ not, exists, selectFrom }) => - not( - exists( - selectFrom('tag_closure') - .whereRef('tag.id', '=', 'tag_closure.id_ancestor') - .innerJoin('tag_asset', 'tag_closure.id_descendant', 'tag_asset.tagId'), - ), - ), - ) - .executeTakeFirst(); - - const deletedRows = Number(result.numDeletedRows); - if (deletedRows > 0) { - this.logger.log(`Deleted ${deletedRows} empty tags`); - } - } -} diff --git a/server/src/repositories/trash.repository.ts b/server/src/repositories/trash.repository.ts deleted file mode 100644 index f6f13188d4..0000000000 --- a/server/src/repositories/trash.repository.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetStatus } from 'src/enum'; -import { DB } from 'src/schema'; - -export class TrashRepository { - constructor(@InjectKysely() private db: Kysely) {} - - getDeletedIds(): AsyncIterableIterator<{ id: string }> { - return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.Deleted).stream(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async restore(userId: string): Promise { - const { numUpdatedRows } = await this.db - .updateTable('asset') - .where('ownerId', '=', userId) - .where('status', '=', AssetStatus.Trashed) - .set({ status: AssetStatus.Active, deletedAt: null }) - .executeTakeFirst(); - - return Number(numUpdatedRows); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async empty(userId: string): Promise { - const { numUpdatedRows } = await this.db - .updateTable('asset') - .where('ownerId', '=', userId) - .where('status', '=', AssetStatus.Trashed) - .set({ status: AssetStatus.Deleted }) - .executeTakeFirst(); - - return Number(numUpdatedRows); - } - - @GenerateSql({ params: [[DummyValue.UUID]] }) - async restoreAll(ids: string[]): Promise { - if (ids.length === 0) { - return 0; - } - - const { numUpdatedRows } = await this.db - .updateTable('asset') - .where('status', '=', AssetStatus.Trashed) - .where('id', 'in', ids) - .set({ status: AssetStatus.Active, deletedAt: null }) - .executeTakeFirst(); - - return Number(numUpdatedRows); - } -} diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 20b41c80f8..5a4f3c4c8b 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -1,91 +1,50 @@ import { Injectable } from '@nestjs/common'; -import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely'; -import { jsonArrayFrom } from 'kysely/helpers/postgres'; -import { DateTime } from 'luxon'; +import { Insertable, Kysely, Updateable } from 'kysely'; import { InjectKysely } from 'nestjs-kysely'; import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetType, AssetVisibility, UserStatus } from 'src/enum'; +import { UserStatus } from 'src/enum'; import { DB } from 'src/schema'; import { UserTable } from 'src/schema/tables/user.table'; -import { UserMetadata, UserMetadataItem } from 'src/types'; -import { asUuid } from 'src/utils/database'; export interface UserListFilter { - id?: string; withDeleted?: boolean; } -export interface UserStatsQueryResponse { - userId: string; - userName: string; - photos: number; - videos: number; - usage: number; - usagePhotos: number; - usageVideos: number; - quotaSizeInBytes: number | null; -} - export interface UserFindOptions { withDeleted?: boolean; } -const withMetadata = (eb: ExpressionBuilder) => { - return jsonArrayFrom( - eb - .selectFrom('user_metadata') - .select(['user_metadata.key', 'user_metadata.value']) - .whereRef('user.id', '=', 'user_metadata.userId'), - ).as('metadata'); -}; - @Injectable() export class UserRepository { constructor(@InjectKysely() private db: Kysely) {} - @GenerateSql({ params: [DummyValue.UUID, DummyValue.BOOLEAN] }) - get(userId: string, options: UserFindOptions) { - options = options || {}; - + @GenerateSql({ params: [DummyValue.UUID] }) + get(id: string, options?: UserFindOptions) { return this.db .selectFrom('user') - .select(columns.userAdmin) - .select(withMetadata) - .where('user.id', '=', userId) - .$if(!options.withDeleted, (eb) => eb.where('user.deletedAt', 'is', null)) + .select(columns.user) + .$if(!options?.withDeleted, (eb) => eb.where('user.deletedAt', 'is', null)) + .where('user.id', '=', id) .executeTakeFirst(); } - getMetadata(userId: string) { - return this.db - .selectFrom('user_metadata') - .select(['key', 'value']) - .where('user_metadata.userId', '=', userId) - .execute() as Promise; + @GenerateSql({ params: [DummyValue.UUID] }) + getAdmin(id: string) { + return this.db.selectFrom('user').select(columns.userAdmin).where('user.id', '=', id).executeTakeFirst(); } - @GenerateSql() - getAdmin() { + @GenerateSql({ params: [DummyValue.EMAIL] }) + getByEmail(email: string, options?: { withPassword?: boolean }) { return this.db .selectFrom('user') .select(columns.userAdmin) - .select(withMetadata) - .where('user.isAdmin', '=', true) + .$if(!!options?.withPassword, (eb) => eb.select('password')) + .where('email', '=', email) .where('user.deletedAt', 'is', null) .executeTakeFirst(); } - @GenerateSql() - getFileSamples() { - return this.db - .selectFrom('user') - .select(['id', 'profileImagePath']) - .where('profileImagePath', '!=', sql.lit('')) - .limit(sql.lit(3)) - .execute(); - } - @GenerateSql() async hasAdmin(): Promise { const admin = await this.db @@ -98,192 +57,46 @@ export class UserRepository { return !!admin; } - @GenerateSql({ params: [DummyValue.UUID] }) - getForPinCode(id: string) { - return this.db - .selectFrom('user') - .select(['user.pinCode', 'user.password']) - .where('user.id', '=', id) - .where('user.deletedAt', 'is', null) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getForChangePassword(id: string) { - return this.db - .selectFrom('user') - .select(['user.id', 'user.password']) - .where('user.id', '=', id) - .where('user.deletedAt', 'is', null) - .executeTakeFirstOrThrow(); - } - - @GenerateSql({ params: [DummyValue.EMAIL] }) - getByEmail(email: string, options?: { withPassword?: boolean }) { - return this.db - .selectFrom('user') - .select(columns.userAdmin) - .select(withMetadata) - .$if(!!options?.withPassword, (eb) => eb.select('password')) - .where('email', '=', email) - .where('user.deletedAt', 'is', null) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.STRING] }) - getByStorageLabel(storageLabel: string) { - return this.db - .selectFrom('user') - .select(columns.userAdmin) - .where('user.storageLabel', '=', storageLabel) - .where('user.deletedAt', 'is', null) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.STRING] }) - getByOAuthId(oauthId: string) { - return this.db - .selectFrom('user') - .select(columns.userAdmin) - .select(withMetadata) - .where('user.oauthId', '=', oauthId) - .where('user.deletedAt', 'is', null) - .executeTakeFirst(); - } - - @GenerateSql({ params: [DateTime.now().minus({ years: 1 })] }) - getDeletedAfter(target: DateTime) { - return this.db.selectFrom('user').select(['id']).where('user.deletedAt', '<', target.toJSDate()).execute(); - } - @GenerateSql( { name: 'with deleted', params: [{ withDeleted: true }] }, { name: 'without deleted', params: [{ withDeleted: false }] }, ) - getList({ id, withDeleted }: UserListFilter = {}) { + getList({ withDeleted }: UserListFilter = {}) { return this.db .selectFrom('user') .select(columns.userAdmin) - .select(withMetadata) .$if(!withDeleted, (eb) => eb.where('user.deletedAt', 'is', null)) - .$if(!!id, (eb) => eb.where('user.id', '=', id!)) .orderBy('createdAt', 'desc') .execute(); } + @GenerateSql({ params: [new Date()] }) + getDeletedAfter(date: Date) { + return this.db.selectFrom('user').select(['id']).where('user.deletedAt', '<', date).execute(); + } + async create(dto: Insertable) { - return this.db - .insertInto('user') - .values(dto) - .returning(columns.userAdmin) - .returning(withMetadata) - .executeTakeFirstOrThrow(); + return this.db.insertInto('user').values(dto).returning(columns.userAdmin).executeTakeFirstOrThrow(); } update(id: string, dto: Updateable) { return this.db .updateTable('user') .set(dto) - .where('user.id', '=', asUuid(id)) + .where('user.id', '=', id) .where('user.deletedAt', 'is', null) .returning(columns.userAdmin) - .returning(withMetadata) .executeTakeFirstOrThrow(); } - async updateAll(dto: Updateable) { - await this.db.updateTable('user').set(dto).execute(); - } - - restore(id: string) { - return this.db - .updateTable('user') - .set({ status: UserStatus.Active, deletedAt: null }) - .where('user.id', '=', asUuid(id)) - .returning(columns.userAdmin) - .returning(withMetadata) - .executeTakeFirstOrThrow(); - } - - async upsertMetadata(id: string, { key, value }: { key: T; value: UserMetadata[T] }) { - await this.db - .insertInto('user_metadata') - .values({ userId: id, key, value }) - .onConflict((oc) => - oc.columns(['userId', 'key']).doUpdateSet({ - key, - value, - }), - ) - .execute(); - } - - async deleteMetadata(id: string, key: T) { - await this.db.deleteFrom('user_metadata').where('userId', '=', id).where('key', '=', key).execute(); - } - - delete(user: { id: string }, hard?: boolean) { + delete(id: string, hard?: boolean) { return hard - ? this.db.deleteFrom('user').where('id', '=', user.id).execute() - : this.db.updateTable('user').set({ deletedAt: new Date() }).where('id', '=', user.id).execute(); - } - - @GenerateSql() - getUserStats() { - return this.db - .selectFrom('user') - .leftJoin('asset', (join) => join.onRef('asset.ownerId', '=', 'user.id').on('asset.deletedAt', 'is', null)) - .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') - .select(['user.id as userId', 'user.name as userName', 'user.quotaSizeInBytes']) - .select((eb) => [ - eb.fn - .countAll() - .filterWhere((eb) => - eb.and([ - eb('asset.type', '=', sql.lit(AssetType.Image)), - eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)), - ]), - ) - .as('photos'), - eb.fn - .countAll() - .filterWhere((eb) => - eb.and([ - eb('asset.type', '=', sql.lit(AssetType.Video)), - eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)), - ]), - ) - .as('videos'), - eb.fn - .coalesce( - eb.fn.sum('asset_exif.fileSizeInByte').filterWhere('asset.libraryId', 'is', null), - eb.lit(0), - ) - .as('usage'), - eb.fn - .coalesce( - eb.fn - .sum('asset_exif.fileSizeInByte') - .filterWhere((eb) => - eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.Image))]), - ), - eb.lit(0), - ) - .as('usagePhotos'), - eb.fn - .coalesce( - eb.fn - .sum('asset_exif.fileSizeInByte') - .filterWhere((eb) => - eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.Video))]), - ), - eb.lit(0), - ) - .as('usageVideos'), - ]) - .groupBy('user.id') - .orderBy('user.createdAt', 'asc') - .execute(); + ? this.db.deleteFrom('user').where('id', '=', id).execute() + : this.db + .updateTable('user') + .set({ deletedAt: new Date(), status: UserStatus.Removing }) + .where('id', '=', id) + .execute(); } @GenerateSql() @@ -295,34 +108,4 @@ export class UserRepository { .executeTakeFirstOrThrow(); return Number(result.count); } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] }) - async updateUsage(id: string, delta: number): Promise { - await this.db - .updateTable('user') - .set({ quotaUsageInBytes: sql`"quotaUsageInBytes" + ${delta}`, updatedAt: new Date() }) - .where('id', '=', asUuid(id)) - .where('user.deletedAt', 'is', null) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async syncUsage(id?: string) { - const query = this.db - .updateTable('user') - .set({ - quotaUsageInBytes: (eb) => - eb - .selectFrom('asset') - .leftJoin('asset_exif', 'asset_exif.assetId', 'asset.id') - .select((eb) => eb.fn.coalesce(eb.fn.sum('asset_exif.fileSizeInByte'), eb.lit(0)).as('usage')) - .where('asset.libraryId', 'is', null) - .where('asset.ownerId', '=', eb.ref('user.id')), - updatedAt: new Date(), - }) - .where('user.deletedAt', 'is', null) - .$if(id != undefined, (eb) => eb.where('user.id', '=', asUuid(id!))); - - await query.execute(); - } } diff --git a/server/src/repositories/version-history.repository.ts b/server/src/repositories/version-history.repository.ts deleted file mode 100644 index b69e0e5faa..0000000000 --- a/server/src/repositories/version-history.repository.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { GenerateSql } from 'src/decorators'; -import { DB } from 'src/schema'; -import { VersionHistoryTable } from 'src/schema/tables/version-history.table'; - -@Injectable() -export class VersionHistoryRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql() - getAll() { - return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').execute(); - } - - @GenerateSql() - getLatest() { - return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').executeTakeFirst(); - } - - create(version: Insertable) { - return this.db.insertInto('version_history').values(version).returningAll().executeTakeFirstOrThrow(); - } -} diff --git a/server/src/repositories/view-repository.ts b/server/src/repositories/view-repository.ts deleted file mode 100644 index ceab79f6eb..0000000000 --- a/server/src/repositories/view-repository.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Kysely } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { AssetVisibility } from 'src/enum'; -import { DB } from 'src/schema'; -import { asUuid, withExif } from 'src/utils/database'; - -export class ViewRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - async getUniqueOriginalPaths(userId: string) { - const results = await this.db - .selectFrom('asset') - .select((eb) => eb.fn('substring', ['asset.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath')) - .distinct() - .where('ownerId', '=', asUuid(userId)) - .where('visibility', '=', AssetVisibility.Timeline) - .where('deletedAt', 'is', null) - .where('fileCreatedAt', 'is not', null) - .where('fileModifiedAt', 'is not', null) - .where('localDateTime', 'is not', null) - .orderBy('directoryPath', 'asc') - .execute(); - - return results.map((row) => row.directoryPath.replaceAll(/\/$/g, '')); - } - - @GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] }) - async getAssetsByOriginalPath(userId: string, partialPath: string) { - const normalizedPath = partialPath.replaceAll(/\/$/g, ''); - - return this.db - .selectFrom('asset') - .selectAll('asset') - .$call(withExif) - .where('ownerId', '=', asUuid(userId)) - .where('visibility', '=', AssetVisibility.Timeline) - .where('deletedAt', 'is', null) - .where('fileCreatedAt', 'is not', null) - .where('fileModifiedAt', 'is not', null) - .where('localDateTime', 'is not', null) - .where('originalPath', 'like', `%${normalizedPath}/%`) - .where('originalPath', 'not like', `%${normalizedPath}/%/%`) - .orderBy( - (eb) => eb.fn('regexp_replace', ['asset.originalPath', eb.val('.*/(.+)'), eb.val(String.raw`\1`)]), - 'asc', - ) - .execute(); - } -} diff --git a/server/src/repositories/websocket.repository.ts b/server/src/repositories/websocket.repository.ts index bfed556895..c8232c38c9 100644 --- a/server/src/repositories/websocket.repository.ts +++ b/server/src/repositories/websocket.repository.ts @@ -7,37 +7,20 @@ import { WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; -import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; -import { NotificationDto } from 'src/dtos/notification.dto'; -import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto'; -import { AppRestartEvent, ArgsOf, EventRepository } from 'src/repositories/event.repository'; +import { ServerVersionResponseDto } from 'src/dtos/server.dto'; +import { ArgsOf, EventRepository } from 'src/repositories/event.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { handlePromiseError } from 'src/utils/misc'; -export const serverEvents = ['ConfigUpdate', 'AppRestart'] as const; +export const serverEvents = ['ConfigUpdate'] as const; export type ServerEvents = (typeof serverEvents)[number]; export interface ClientEventMap { - on_upload_success: [AssetResponseDto]; on_user_delete: [string]; - on_asset_delete: [string]; - on_asset_trash: [string[]]; - on_asset_update: [AssetResponseDto]; - on_asset_hidden: [string]; - on_asset_restore: [string[]]; - on_asset_stack_update: string[]; - on_person_thumbnail: [string]; on_server_version: [ServerVersionResponseDto]; on_config_update: []; - on_new_release: [ReleaseNotification]; - on_notification: [NotificationDto]; on_session_delete: [string]; - - AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }]; - AppRestartV1: [AppRestartEvent]; - AssetEditReadyV1: [{ asset: SyncAssetV1 }]; } export type AuthFn = (client: Socket) => Promise; diff --git a/server/src/repositories/workflow.repository.ts b/server/src/repositories/workflow.repository.ts deleted file mode 100644 index deaf2aa2fc..0000000000 --- a/server/src/repositories/workflow.repository.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Insertable, Kysely, Updateable } from 'kysely'; -import { InjectKysely } from 'nestjs-kysely'; -import { DummyValue, GenerateSql } from 'src/decorators'; -import { PluginTriggerType } from 'src/enum'; -import { DB } from 'src/schema'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; - -@Injectable() -export class WorkflowRepository { - constructor(@InjectKysely() private db: Kysely) {} - - @GenerateSql({ params: [DummyValue.UUID] }) - getWorkflow(id: string) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('id', '=', id) - .orderBy('createdAt', 'desc') - .executeTakeFirst(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getWorkflowsByOwner(ownerId: string) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('ownerId', '=', ownerId) - .orderBy('createdAt', 'desc') - .execute(); - } - - @GenerateSql({ params: [PluginTriggerType.AssetCreate] }) - getWorkflowsByTrigger(type: PluginTriggerType) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('triggerType', '=', type) - .where('enabled', '=', true) - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID, PluginTriggerType.AssetCreate] }) - getWorkflowByOwnerAndTrigger(ownerId: string, type: PluginTriggerType) { - return this.db - .selectFrom('workflow') - .selectAll() - .where('ownerId', '=', ownerId) - .where('triggerType', '=', type) - .where('enabled', '=', true) - .execute(); - } - - async createWorkflow( - workflow: Insertable, - filters: Insertable[], - actions: Insertable[], - ) { - return await this.db.transaction().execute(async (tx) => { - const createdWorkflow = await tx.insertInto('workflow').values(workflow).returningAll().executeTakeFirstOrThrow(); - - if (filters.length > 0) { - const newFilters = filters.map((filter) => ({ - ...filter, - workflowId: createdWorkflow.id, - })); - - await tx.insertInto('workflow_filter').values(newFilters).execute(); - } - - if (actions.length > 0) { - const newActions = actions.map((action) => ({ - ...action, - workflowId: createdWorkflow.id, - })); - await tx.insertInto('workflow_action').values(newActions).execute(); - } - - return createdWorkflow; - }); - } - - async updateWorkflow( - id: string, - workflow: Updateable, - filters: Insertable[] | undefined, - actions: Insertable[] | undefined, - ) { - return await this.db.transaction().execute(async (trx) => { - if (Object.keys(workflow).length > 0) { - await trx.updateTable('workflow').set(workflow).where('id', '=', id).execute(); - } - - if (filters !== undefined) { - await trx.deleteFrom('workflow_filter').where('workflowId', '=', id).execute(); - if (filters.length > 0) { - const filtersWithWorkflowId = filters.map((filter) => ({ - ...filter, - workflowId: id, - })); - await trx.insertInto('workflow_filter').values(filtersWithWorkflowId).execute(); - } - } - - if (actions !== undefined) { - await trx.deleteFrom('workflow_action').where('workflowId', '=', id).execute(); - if (actions.length > 0) { - const actionsWithWorkflowId = actions.map((action) => ({ - ...action, - workflowId: id, - })); - await trx.insertInto('workflow_action').values(actionsWithWorkflowId).execute(); - } - } - - return await trx.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirstOrThrow(); - }); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async deleteWorkflow(id: string) { - await this.db.deleteFrom('workflow').where('id', '=', id).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getFilters(workflowId: string) { - return this.db - .selectFrom('workflow_filter') - .selectAll() - .where('workflowId', '=', workflowId) - .orderBy('order', 'asc') - .execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - async deleteFiltersByWorkflow(workflowId: string) { - await this.db.deleteFrom('workflow_filter').where('workflowId', '=', workflowId).execute(); - } - - @GenerateSql({ params: [DummyValue.UUID] }) - getActions(workflowId: string) { - return this.db - .selectFrom('workflow_action') - .selectAll() - .where('workflowId', '=', workflowId) - .orderBy('order', 'asc') - .execute(); - } -} diff --git a/server/src/schema/enums.ts b/server/src/schema/enums.ts index a1134df6bc..dd7b46350c 100644 --- a/server/src/schema/enums.ts +++ b/server/src/schema/enums.ts @@ -1,17 +1 @@ -import { AssetStatus, AssetVisibility, SourceType } from 'src/enum'; -import { registerEnum } from 'src/sql-tools'; - -export const assets_status_enum = registerEnum({ - name: 'assets_status_enum', - values: Object.values(AssetStatus), -}); - -export const asset_face_source_type = registerEnum({ - name: 'sourcetype', - values: Object.values(SourceType), -}); - -export const asset_visibility_enum = registerEnum({ - name: 'asset_visibility_enum', - values: Object.values(AssetVisibility), -}); +// No custom PG enums needed for the template diff --git a/server/src/schema/functions.ts b/server/src/schema/functions.ts index d7dabfef4c..9da24a0d2a 100644 --- a/server/src/schema/functions.ts +++ b/server/src/schema/functions.ts @@ -22,18 +22,6 @@ export const immich_uuid_v7 = registerFunction({ `, }); -export const album_user_after_insert = registerFunction({ - name: 'album_user_after_insert', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows); - RETURN NULL; - END`, -}); - export const updated_at = registerFunction({ name: 'updated_at', returnType: 'TRIGGER', @@ -47,242 +35,3 @@ export const updated_at = registerFunction({ return new; END;`, }); - -export const f_concat_ws = registerFunction({ - name: 'f_concat_ws', - arguments: ['text', 'text[]'], - returnType: 'text', - language: 'SQL', - parallel: 'safe', - behavior: 'immutable', - body: `SELECT array_to_string($2, $1)`, -}); - -export const f_unaccent = registerFunction({ - name: 'f_unaccent', - arguments: ['text'], - returnType: 'text', - language: 'SQL', - parallel: 'safe', - strict: true, - behavior: 'immutable', - return: `unaccent('unaccent', $1)`, -}); - -export const ll_to_earth_public = registerFunction({ - name: 'll_to_earth_public', - arguments: ['latitude double precision', 'longitude double precision'], - returnType: 'public.earth', - language: 'SQL', - parallel: 'safe', - strict: true, - behavior: 'immutable', - body: `SELECT public.cube(public.cube(public.cube(public.earth()*cos(radians(latitude))*cos(radians(longitude))),public.earth()*cos(radians(latitude))*sin(radians(longitude))),public.earth()*sin(radians(latitude)))::public.earth`, -}); - -export const user_delete_audit = registerFunction({ - name: 'user_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO user_audit ("userId") - SELECT "id" - FROM OLD; - RETURN NULL; - END`, -}); - -export const partner_delete_audit = registerFunction({ - name: 'partner_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO partner_audit ("sharedById", "sharedWithId") - SELECT "sharedById", "sharedWithId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const asset_delete_audit = registerFunction({ - name: 'asset_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO asset_audit ("assetId", "ownerId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const album_delete_audit = registerFunction({ - name: 'album_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO album_audit ("albumId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const album_asset_delete_audit = registerFunction({ - name: 'album_asset_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO album_asset_audit ("albumId", "assetId") - SELECT "albumId", "assetId" FROM OLD - WHERE "albumId" IN (SELECT "id" FROM album WHERE "id" IN (SELECT "albumId" FROM OLD)); - RETURN NULL; - END`, -}); - -export const album_user_delete_audit = registerFunction({ - name: 'album_user_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO album_audit ("albumId", "userId") - SELECT "albumId", "userId" - FROM OLD; - - IF pg_trigger_depth() = 1 THEN - INSERT INTO album_user_audit ("albumId", "userId") - SELECT "albumId", "userId" - FROM OLD; - END IF; - - RETURN NULL; - END`, -}); - -export const memory_delete_audit = registerFunction({ - name: 'memory_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO memory_audit ("memoryId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const memory_asset_delete_audit = registerFunction({ - name: 'memory_asset_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO memory_asset_audit ("memoryId", "assetId") - SELECT "memoriesId", "assetId" FROM OLD - WHERE "memoriesId" IN (SELECT "id" FROM memory WHERE "id" IN (SELECT "memoriesId" FROM OLD)); - RETURN NULL; - END`, -}); - -export const stack_delete_audit = registerFunction({ - name: 'stack_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO stack_audit ("stackId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const person_delete_audit = registerFunction({ - name: 'person_delete_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO person_audit ("personId", "ownerId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const user_metadata_audit = registerFunction({ - name: 'user_metadata_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO user_metadata_audit ("userId", "key") - SELECT "userId", "key" - FROM OLD; - RETURN NULL; - END`, -}); - -export const asset_metadata_audit = registerFunction({ - name: 'asset_metadata_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO asset_metadata_audit ("assetId", "key") - SELECT "assetId", "key" - FROM OLD; - RETURN NULL; - END`, -}); - -export const asset_face_audit = registerFunction({ - name: 'asset_face_audit', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - INSERT INTO asset_face_audit ("assetFaceId", "assetId") - SELECT "id", "assetId" - FROM OLD; - RETURN NULL; - END`, -}); - -export const asset_edit_insert = registerFunction({ - name: 'asset_edit_insert', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - UPDATE asset - SET "isEdited" = true - FROM inserted_edit - WHERE asset.id = inserted_edit."assetId" AND NOT asset."isEdited"; - RETURN NULL; - END - `, -}); - -export const asset_edit_delete = registerFunction({ - name: 'asset_edit_delete', - returnType: 'TRIGGER', - language: 'PLPGSQL', - body: ` - BEGIN - UPDATE asset - SET "isEdited" = false - FROM deleted_edit - WHERE asset.id = deleted_edit."assetId" AND asset."isEdited" - AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit."assetId" = asset.id); - RETURN NULL; - END - `, -}); diff --git a/server/src/schema/index.ts b/server/src/schema/index.ts index 59c9f53d1a..d2f3aab077 100644 --- a/server/src/schema/index.ts +++ b/server/src/schema/index.ts @@ -1,164 +1,26 @@ -import { asset_face_source_type, asset_visibility_enum, assets_status_enum } from 'src/schema/enums'; -import { - album_delete_audit, - album_user_after_insert, - album_user_delete_audit, - asset_delete_audit, - asset_face_audit, - asset_metadata_audit, - f_concat_ws, - f_unaccent, - immich_uuid_v7, - ll_to_earth_public, - memory_asset_delete_audit, - memory_delete_audit, - partner_delete_audit, - person_delete_audit, - stack_delete_audit, - updated_at, - user_delete_audit, - user_metadata_audit, -} from 'src/schema/functions'; -import { ActivityTable } from 'src/schema/tables/activity.table'; -import { AlbumAssetAuditTable } from 'src/schema/tables/album-asset-audit.table'; -import { AlbumAssetTable } from 'src/schema/tables/album-asset.table'; -import { AlbumAuditTable } from 'src/schema/tables/album-audit.table'; -import { AlbumUserAuditTable } from 'src/schema/tables/album-user-audit.table'; -import { AlbumUserTable } from 'src/schema/tables/album-user.table'; -import { AlbumTable } from 'src/schema/tables/album.table'; +import { immich_uuid_v7, updated_at } from 'src/schema/functions'; import { ApiKeyTable } from 'src/schema/tables/api-key.table'; -import { AssetAuditTable } from 'src/schema/tables/asset-audit.table'; -import { AssetEditTable } from 'src/schema/tables/asset-edit.table'; -import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { AssetFaceAuditTable } from 'src/schema/tables/asset-face-audit.table'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { AssetFileTable } from 'src/schema/tables/asset-file.table'; -import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table'; -import { AssetMetadataAuditTable } from 'src/schema/tables/asset-metadata-audit.table'; -import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table'; -import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { AuditTable } from 'src/schema/tables/audit.table'; -import { FaceSearchTable } from 'src/schema/tables/face-search.table'; -import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table'; -import { LibraryTable } from 'src/schema/tables/library.table'; -import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table'; -import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table'; -import { MemoryAuditTable } from 'src/schema/tables/memory-audit.table'; -import { MemoryTable } from 'src/schema/tables/memory.table'; -import { MoveTable } from 'src/schema/tables/move.table'; -import { NaturalEarthCountriesTable } from 'src/schema/tables/natural-earth-countries.table'; -import { NotificationTable } from 'src/schema/tables/notification.table'; -import { OcrSearchTable } from 'src/schema/tables/ocr-search.table'; -import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table'; -import { PartnerTable } from 'src/schema/tables/partner.table'; -import { PersonAuditTable } from 'src/schema/tables/person-audit.table'; -import { PersonTable } from 'src/schema/tables/person.table'; -import { PluginActionTable, PluginFilterTable, PluginTable } from 'src/schema/tables/plugin.table'; import { SessionTable } from 'src/schema/tables/session.table'; -import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table'; -import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; -import { SmartSearchTable } from 'src/schema/tables/smart-search.table'; -import { StackAuditTable } from 'src/schema/tables/stack-audit.table'; -import { StackTable } from 'src/schema/tables/stack.table'; -import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table'; import { SystemMetadataTable } from 'src/schema/tables/system-metadata.table'; -import { TagAssetTable } from 'src/schema/tables/tag-asset.table'; -import { TagClosureTable } from 'src/schema/tables/tag-closure.table'; -import { TagTable } from 'src/schema/tables/tag.table'; -import { UserAuditTable } from 'src/schema/tables/user-audit.table'; -import { UserMetadataAuditTable } from 'src/schema/tables/user-metadata-audit.table'; -import { UserMetadataTable } from 'src/schema/tables/user-metadata.table'; import { UserTable } from 'src/schema/tables/user.table'; -import { VersionHistoryTable } from 'src/schema/tables/version-history.table'; -import { WorkflowActionTable, WorkflowFilterTable, WorkflowTable } from 'src/schema/tables/workflow.table'; import { Database, Extensions, Generated, Int8 } from 'src/sql-tools'; -@Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql']) +@Extensions(['uuid-ossp', 'plpgsql', 'vector']) @Database({ name: 'immich' }) export class ImmichDatabase { tables = [ - ActivityTable, - AlbumAssetTable, - AlbumAssetAuditTable, - AlbumAuditTable, - AlbumUserAuditTable, - AlbumUserTable, - AlbumTable, - ApiKeyTable, - AssetAuditTable, - AssetEditTable, - AssetFaceTable, - AssetFaceAuditTable, - AssetMetadataTable, - AssetMetadataAuditTable, - AssetJobStatusTable, - AssetOcrTable, - AssetTable, - AssetFileTable, - AuditTable, - AssetExifTable, - FaceSearchTable, - GeodataPlacesTable, - LibraryTable, - MemoryTable, - MemoryAuditTable, - MemoryAssetTable, - MemoryAssetAuditTable, - MoveTable, - NaturalEarthCountriesTable, - NotificationTable, - OcrSearchTable, - PartnerAuditTable, - PartnerTable, - PersonTable, - PersonAuditTable, - SessionTable, - SharedLinkAssetTable, - SharedLinkTable, - SmartSearchTable, - StackTable, - StackAuditTable, - SessionSyncCheckpointTable, - SystemMetadataTable, - TagTable, - TagAssetTable, - TagClosureTable, - UserAuditTable, - UserMetadataTable, - UserMetadataAuditTable, UserTable, - VersionHistoryTable, - PluginTable, - PluginFilterTable, - PluginActionTable, - WorkflowTable, - WorkflowFilterTable, - WorkflowActionTable, + SessionTable, + ApiKeyTable, + SystemMetadataTable, ]; functions = [ immich_uuid_v7, updated_at, - f_concat_ws, - f_unaccent, - ll_to_earth_public, - user_delete_audit, - partner_delete_audit, - asset_delete_audit, - album_delete_audit, - album_user_after_insert, - album_user_delete_audit, - memory_delete_audit, - memory_asset_delete_audit, - stack_delete_audit, - person_delete_audit, - user_metadata_audit, - asset_metadata_audit, - asset_face_audit, ]; - enum = [assets_status_enum, asset_face_source_type, asset_visibility_enum]; + enum = []; } export interface Migrations { @@ -168,86 +30,9 @@ export interface Migrations { } export interface DB { - activity: ActivityTable; - - album: AlbumTable; - album_audit: AlbumAuditTable; - album_asset: AlbumAssetTable; - album_asset_audit: AlbumAssetAuditTable; - album_user: AlbumUserTable; - album_user_audit: AlbumUserAuditTable; - api_key: ApiKeyTable; - - asset: AssetTable; - asset_audit: AssetAuditTable; - asset_edit: AssetEditTable; - asset_exif: AssetExifTable; - asset_face: AssetFaceTable; - asset_face_audit: AssetFaceAuditTable; - asset_file: AssetFileTable; - asset_metadata: AssetMetadataTable; - asset_metadata_audit: AssetMetadataAuditTable; - asset_job_status: AssetJobStatusTable; - asset_ocr: AssetOcrTable; - ocr_search: OcrSearchTable; - - audit: AuditTable; - - face_search: FaceSearchTable; - - geodata_places: GeodataPlacesTable; - - library: LibraryTable; - - memory: MemoryTable; - memory_audit: MemoryAuditTable; - memory_asset: MemoryAssetTable; - memory_asset_audit: MemoryAssetAuditTable; - migrations: Migrations; - - notification: NotificationTable; - - move_history: MoveTable; - - naturalearth_countries: NaturalEarthCountriesTable; - - partner: PartnerTable; - partner_audit: PartnerAuditTable; - - person: PersonTable; - person_audit: PersonAuditTable; - session: SessionTable; - session_sync_checkpoint: SessionSyncCheckpointTable; - - shared_link: SharedLinkTable; - shared_link_asset: SharedLinkAssetTable; - - smart_search: SmartSearchTable; - - stack: StackTable; - stack_audit: StackAuditTable; - system_metadata: SystemMetadataTable; - - tag: TagTable; - tag_asset: TagAssetTable; - tag_closure: TagClosureTable; - user: UserTable; - user_audit: UserAuditTable; - user_metadata: UserMetadataTable; - user_metadata_audit: UserMetadataAuditTable; - - version_history: VersionHistoryTable; - - plugin: PluginTable; - plugin_filter: PluginFilterTable; - plugin_action: PluginActionTable; - - workflow: WorkflowTable; - workflow_filter: WorkflowFilterTable; - workflow_action: WorkflowActionTable; } diff --git a/server/src/schema/migrations/0000000000000-InitialMigration.ts b/server/src/schema/migrations/0000000000000-InitialMigration.ts new file mode 100644 index 0000000000..75f7d0ca8a --- /dev/null +++ b/server/src/schema/migrations/0000000000000-InitialMigration.ts @@ -0,0 +1,129 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // Enable extensions + await sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`.execute(db); + await sql`CREATE EXTENSION IF NOT EXISTS "plpgsql"`.execute(db); + await sql`CREATE EXTENSION IF NOT EXISTS "vector"`.execute(db); + + // UUID v7 function + await sql` + CREATE OR REPLACE FUNCTION immich_uuid_v7(p_timestamp timestamp with time zone DEFAULT clock_timestamp()) + RETURNS uuid + LANGUAGE SQL + VOLATILE + AS $$ + SELECT encode( + set_bit( + set_bit( + overlay(uuid_send(gen_random_uuid()) + placing substring(int8send(floor(extract(epoch from p_timestamp) * 1000)::bigint) from 3) + from 1 for 6 + ), + 52, 1 + ), + 53, 1 + ), + 'hex')::uuid; + $$ + `.execute(db); + + // Updated at trigger function + await sql` + CREATE OR REPLACE FUNCTION updated_at() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS $$ + DECLARE + clock_timestamp TIMESTAMP := clock_timestamp(); + BEGIN + new."updatedAt" = clock_timestamp; + new."updateId" = immich_uuid_v7(clock_timestamp); + return new; + END; + $$ + `.execute(db); + + // User table + await db.schema + .createTable('user') + .addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`immich_uuid_v7()`)) + .addColumn('email', 'varchar', (col) => col.unique().notNull()) + .addColumn('password', 'varchar', (col) => col.notNull().defaultTo('')) + .addColumn('name', 'varchar', (col) => col.notNull().defaultTo('')) + .addColumn('isAdmin', 'boolean', (col) => col.notNull().defaultTo(false)) + .addColumn('shouldChangePassword', 'boolean', (col) => col.notNull().defaultTo(true)) + .addColumn('status', 'varchar', (col) => col.notNull().defaultTo('active')) + .addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`)) + .addColumn('updatedAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`)) + .addColumn('deletedAt', 'timestamptz') + .addColumn('updateId', 'uuid', (col) => col.notNull().defaultTo(sql`immich_uuid_v7()`)) + .execute(); + + await db.schema.createIndex('IDX_user_updatedAt_id').on('user').columns(['updatedAt', 'id']).execute(); + await db.schema.createIndex('IDX_user_updateId').on('user').column('updateId').execute(); + await sql`CREATE TRIGGER "user_updatedAt" BEFORE UPDATE ON "user" FOR EACH ROW EXECUTE FUNCTION updated_at()`.execute(db); + + // Session table + await db.schema + .createTable('session') + .addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`immich_uuid_v7()`)) + .addColumn('token', 'varchar', (col) => col.notNull()) + .addColumn('userId', 'uuid', (col) => col.notNull().references('user.id').onUpdate('cascade').onDelete('cascade')) + .addColumn('parentId', 'uuid', (col) => col.references('session.id').onUpdate('cascade').onDelete('cascade')) + .addColumn('deviceType', 'varchar', (col) => col.notNull().defaultTo('')) + .addColumn('deviceOS', 'varchar', (col) => col.notNull().defaultTo('')) + .addColumn('appVersion', 'varchar') + .addColumn('isPendingSyncReset', 'boolean', (col) => col.notNull().defaultTo(false)) + .addColumn('expiresAt', 'timestamptz') + .addColumn('pinExpiresAt', 'timestamptz') + .addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`)) + .addColumn('updatedAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`)) + .addColumn('updateId', 'uuid', (col) => col.notNull().defaultTo(sql`immich_uuid_v7()`)) + .execute(); + + await db.schema.createIndex('IDX_session_updateId').on('session').column('updateId').execute(); + await sql`CREATE TRIGGER "session_updatedAt" BEFORE UPDATE ON "session" FOR EACH ROW EXECUTE FUNCTION updated_at()`.execute(db); + + // API Key table + await db.schema + .createTable('api_key') + .addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`immich_uuid_v7()`)) + .addColumn('name', 'varchar', (col) => col.notNull()) + .addColumn('key', 'varchar', (col) => col.notNull()) + .addColumn('userId', 'uuid', (col) => col.notNull().references('user.id').onUpdate('cascade').onDelete('cascade')) + .addColumn('permissions', sql`character varying[]`, (col) => col.notNull()) + .addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`)) + .addColumn('updatedAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`)) + .addColumn('updateId', 'uuid', (col) => col.notNull().defaultTo(sql`immich_uuid_v7()`)) + .execute(); + + await db.schema.createIndex('IDX_api_key_updateId').on('api_key').column('updateId').execute(); + await sql`CREATE TRIGGER "api_key_updatedAt" BEFORE UPDATE ON "api_key" FOR EACH ROW EXECUTE FUNCTION updated_at()`.execute(db); + + // System metadata table + await db.schema + .createTable('system_metadata') + .addColumn('key', 'varchar', (col) => col.primaryKey()) + .addColumn('value', 'jsonb', (col) => col.notNull()) + .execute(); + + // Migrations tracking table + await db.schema + .createTable('migrations') + .addColumn('id', 'serial', (col) => col.primaryKey()) + .addColumn('name', 'varchar', (col) => col.notNull()) + .addColumn('timestamp', 'bigint', (col) => col.notNull()) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('migrations').ifExists().execute(); + await db.schema.dropTable('system_metadata').ifExists().execute(); + await db.schema.dropTable('api_key').ifExists().execute(); + await db.schema.dropTable('session').ifExists().execute(); + await db.schema.dropTable('user').ifExists().execute(); + await sql`DROP FUNCTION IF EXISTS updated_at CASCADE`.execute(db); + await sql`DROP FUNCTION IF EXISTS immich_uuid_v7 CASCADE`.execute(db); + await sql`DROP EXTENSION IF EXISTS vector`.execute(db); +} diff --git a/server/src/schema/migrations/1744910873969-InitialMigration.ts b/server/src/schema/migrations/1744910873969-InitialMigration.ts deleted file mode 100644 index b703a47536..0000000000 --- a/server/src/schema/migrations/1744910873969-InitialMigration.ts +++ /dev/null @@ -1,617 +0,0 @@ -import { Kysely, sql } from 'kysely'; -import { DatabaseExtension } from 'src/enum'; -import { getVectorExtension } from 'src/repositories/database.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { vectorIndexQuery } from 'src/utils/database'; - -const lastMigrationSql = sql<{ name: string }>`SELECT "name" FROM "migrations" ORDER BY "timestamp" DESC LIMIT 1;`; -const tableExists = sql<{ result: string | null }>`select to_regclass('migrations') as "result"`; -const logger = LoggingRepository.create(); - -export async function up(db: Kysely): Promise { - const { rows } = await tableExists.execute(db); - const hasTypeOrmMigrations = !!rows[0]?.result; - if (hasTypeOrmMigrations) { - const { - rows: [lastMigration], - } = await lastMigrationSql.execute(db); - if (lastMigration?.name !== 'AddMissingIndex1744910873956') { - throw new Error( - 'Invalid upgrade path. For more information, see https://docs.immich.app/errors/#typeorm-upgrade', - ); - } - logger.log('Database has up to date TypeORM migrations, skipping initial Kysely migration'); - return; - } - - const vectorExtension = await getVectorExtension(db); - - await sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`.execute(db); - await sql`CREATE EXTENSION IF NOT EXISTS "unaccent";`.execute(db); - await sql`CREATE EXTENSION IF NOT EXISTS "cube";`.execute(db); - await sql`CREATE EXTENSION IF NOT EXISTS "earthdistance";`.execute(db); - await sql`CREATE EXTENSION IF NOT EXISTS "pg_trgm";`.execute(db); - await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(vectorExtension)} CASCADE`.execute(db); - await sql`CREATE OR REPLACE FUNCTION immich_uuid_v7(p_timestamp timestamp with time zone default clock_timestamp()) - RETURNS uuid - VOLATILE LANGUAGE SQL - AS $$ - select encode( - set_bit( - set_bit( - overlay(uuid_send(gen_random_uuid()) - placing substring(int8send(floor(extract(epoch from p_timestamp) * 1000)::bigint) from 3) - from 1 for 6 - ), - 52, 1 - ), - 53, 1 - ), - 'hex')::uuid; - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION updated_at() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - DECLARE - clock_timestamp TIMESTAMP := clock_timestamp(); - BEGIN - new."updatedAt" = clock_timestamp; - new."updateId" = immich_uuid_v7(clock_timestamp); - return new; - END; - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION f_concat_ws(text, text[]) - RETURNS text - PARALLEL SAFE IMMUTABLE LANGUAGE SQL - AS $$SELECT array_to_string($2, $1)$$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION f_unaccent(text) - RETURNS text - PARALLEL SAFE STRICT IMMUTABLE LANGUAGE SQL - RETURN unaccent('unaccent', $1)`.execute(db); - await sql`CREATE OR REPLACE FUNCTION ll_to_earth_public(latitude double precision, longitude double precision) - RETURNS public.earth - PARALLEL SAFE STRICT IMMUTABLE LANGUAGE SQL - AS $$ - SELECT public.cube(public.cube(public.cube(public.earth()*cos(radians(latitude))*cos(radians(longitude))),public.earth()*cos(radians(latitude))*sin(radians(longitude))),public.earth()*sin(radians(latitude)))::public.earth - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION users_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO users_audit ("userId") - SELECT "id" - FROM OLD; - RETURN NULL; - END; - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION partners_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO partners_audit ("sharedById", "sharedWithId") - SELECT "sharedById", "sharedWithId" - FROM OLD; - RETURN NULL; - END; - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION assets_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO assets_audit ("assetId", "ownerId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END; - $$;`.execute(db); - if (vectorExtension === DatabaseExtension.Vectors) { - await sql`SET search_path TO "$user", public, vectors`.execute(db); - } - await sql`CREATE TYPE "assets_status_enum" AS ENUM ('active','trashed','deleted');`.execute(db); - await sql`CREATE TYPE "sourcetype" AS ENUM ('machine-learning','exif','manual');`.execute(db); - await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "libraries" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "ownerId" uuid NOT NULL, "importPaths" text[] NOT NULL, "exclusionPatterns" text[] NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "refreshedAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "asset_stack" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "primaryAssetId" uuid NOT NULL, "ownerId" uuid NOT NULL);`.execute( - db, - ); - await sql`CREATE TABLE "assets" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "deviceAssetId" character varying NOT NULL, "ownerId" uuid NOT NULL, "deviceId" character varying NOT NULL, "type" character varying NOT NULL, "originalPath" character varying NOT NULL, "fileCreatedAt" timestamp with time zone NOT NULL, "fileModifiedAt" timestamp with time zone NOT NULL, "isFavorite" boolean NOT NULL DEFAULT false, "duration" character varying, "encodedVideoPath" character varying DEFAULT '', "checksum" bytea NOT NULL, "isVisible" boolean NOT NULL DEFAULT true, "livePhotoVideoId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "originalFileName" character varying NOT NULL, "sidecarPath" character varying, "thumbhash" bytea, "isOffline" boolean NOT NULL DEFAULT false, "libraryId" uuid, "isExternal" boolean NOT NULL DEFAULT false, "deletedAt" timestamp with time zone, "localDateTime" timestamp with time zone NOT NULL, "stackId" uuid, "duplicateId" uuid, "status" assets_status_enum NOT NULL DEFAULT 'active', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "albums" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "ownerId" uuid NOT NULL, "albumName" character varying NOT NULL DEFAULT 'Untitled Album', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "albumThumbnailAssetId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "description" text NOT NULL DEFAULT '', "deletedAt" timestamp with time zone, "isActivityEnabled" boolean NOT NULL DEFAULT true, "order" character varying NOT NULL DEFAULT 'desc', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`COMMENT ON COLUMN "albums"."albumThumbnailAssetId" IS 'Asset ID to be used as thumbnail';`.execute(db); - await sql`CREATE TABLE "activity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "assetId" uuid, "comment" text, "isLiked" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "albums_assets_assets" ("albumsId" uuid NOT NULL, "assetsId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute( - db, - ); - await sql`CREATE TABLE "albums_shared_users_users" ("albumsId" uuid NOT NULL, "usersId" uuid NOT NULL, "role" character varying NOT NULL DEFAULT 'editor');`.execute( - db, - ); - await sql`CREATE TABLE "api_keys" ("name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "permissions" character varying[] NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "assetId" uuid NOT NULL, "ownerId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute( - db, - ); - await sql`CREATE TABLE "person" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ownerId" uuid NOT NULL, "name" character varying NOT NULL DEFAULT '', "thumbnailPath" character varying NOT NULL DEFAULT '', "isHidden" boolean NOT NULL DEFAULT false, "birthDate" date, "faceAssetId" uuid, "isFavorite" boolean NOT NULL DEFAULT false, "color" character varying, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "asset_faces" ("assetId" uuid NOT NULL, "personId" uuid, "imageWidth" integer NOT NULL DEFAULT 0, "imageHeight" integer NOT NULL DEFAULT 0, "boundingBoxX1" integer NOT NULL DEFAULT 0, "boundingBoxY1" integer NOT NULL DEFAULT 0, "boundingBoxX2" integer NOT NULL DEFAULT 0, "boundingBoxY2" integer NOT NULL DEFAULT 0, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "sourceType" sourcetype NOT NULL DEFAULT 'machine-learning', "deletedAt" timestamp with time zone);`.execute( - db, - ); - await sql`CREATE TABLE "asset_files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "type" character varying NOT NULL, "path" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "asset_job_status" ("assetId" uuid NOT NULL, "facesRecognizedAt" timestamp with time zone, "metadataExtractedAt" timestamp with time zone, "duplicatesDetectedAt" timestamp with time zone, "previewAt" timestamp with time zone, "thumbnailAt" timestamp with time zone);`.execute( - db, - ); - await sql`CREATE TABLE "audit" ("id" serial NOT NULL, "entityType" character varying NOT NULL, "entityId" uuid NOT NULL, "action" character varying NOT NULL, "ownerId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute( - db, - ); - await sql`CREATE TABLE "exif" ("assetId" uuid NOT NULL, "make" character varying, "model" character varying, "exifImageWidth" integer, "exifImageHeight" integer, "fileSizeInByte" bigint, "orientation" character varying, "dateTimeOriginal" timestamp with time zone, "modifyDate" timestamp with time zone, "lensModel" character varying, "fNumber" double precision, "focalLength" double precision, "iso" integer, "latitude" double precision, "longitude" double precision, "city" character varying, "state" character varying, "country" character varying, "description" text NOT NULL DEFAULT '', "fps" double precision, "exposureTime" character varying, "livePhotoCID" character varying, "timeZone" character varying, "projectionType" character varying, "profileDescription" character varying, "colorspace" character varying, "bitsPerSample" integer, "autoStackId" character varying, "rating" integer, "updatedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "face_search" ("faceId" uuid NOT NULL, "embedding" vector(512) NOT NULL);`.execute(db); - await sql`CREATE TABLE "geodata_places" ("id" integer NOT NULL, "name" character varying(200) NOT NULL, "longitude" double precision NOT NULL, "latitude" double precision NOT NULL, "countryCode" character(2) NOT NULL, "admin1Code" character varying(20), "admin2Code" character varying(80), "modificationDate" date NOT NULL, "admin1Name" character varying, "admin2Name" character varying, "alternateNames" character varying);`.execute( - db, - ); - await sql`CREATE TABLE "memories" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "ownerId" uuid NOT NULL, "type" character varying NOT NULL, "data" jsonb NOT NULL, "isSaved" boolean NOT NULL DEFAULT false, "memoryAt" timestamp with time zone NOT NULL, "seenAt" timestamp with time zone, "showAt" timestamp with time zone, "hideAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "memories_assets_assets" ("memoriesId" uuid NOT NULL, "assetsId" uuid NOT NULL);`.execute(db); - await sql`CREATE TABLE "move_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "entityId" uuid NOT NULL, "pathType" character varying NOT NULL, "oldPath" character varying NOT NULL, "newPath" character varying NOT NULL);`.execute( - db, - ); - await sql`CREATE TABLE "naturalearth_countries" ("id" integer NOT NULL GENERATED ALWAYS AS IDENTITY, "admin" character varying(50) NOT NULL, "admin_a3" character varying(3) NOT NULL, "type" character varying(50) NOT NULL, "coordinates" polygon NOT NULL);`.execute( - db, - ); - await sql`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute( - db, - ); - await sql`CREATE TABLE "partners" ("sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "inTimeline" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "sessions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "token" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "userId" uuid NOT NULL, "deviceType" character varying NOT NULL DEFAULT '', "deviceOS" character varying NOT NULL DEFAULT '', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "shared_links" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "description" character varying, "userId" uuid NOT NULL, "key" bytea NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "expiresAt" timestamp with time zone, "allowUpload" boolean NOT NULL DEFAULT false, "albumId" uuid, "allowDownload" boolean NOT NULL DEFAULT true, "showExif" boolean NOT NULL DEFAULT true, "password" character varying);`.execute( - db, - ); - await sql`CREATE TABLE "shared_link__asset" ("assetsId" uuid NOT NULL, "sharedLinksId" uuid NOT NULL);`.execute(db); - await sql`CREATE TABLE "smart_search" ("assetId" uuid NOT NULL, "embedding" vector(512) NOT NULL);`.execute(db); - await sql`ALTER TABLE "smart_search" ALTER COLUMN "embedding" SET STORAGE EXTERNAL;`.execute(db); - await sql`CREATE TABLE "session_sync_checkpoints" ("sessionId" uuid NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ack" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "system_metadata" ("key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db); - await sql`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "value" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "color" character varying, "parentId" uuid, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "tag_asset" ("assetsId" uuid NOT NULL, "tagsId" uuid NOT NULL);`.execute(db); - await sql`CREATE TABLE "tags_closure" ("id_ancestor" uuid NOT NULL, "id_descendant" uuid NOT NULL);`.execute(db); - await sql`CREATE TABLE "users_audit" ("userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "id" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute( - db, - ); - await sql`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute( - db, - ); - await sql`CREATE TABLE "version_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "version" character varying NOT NULL);`.execute( - db, - ); - await sql`ALTER TABLE "users" ADD CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "libraries" ADD CONSTRAINT "PK_505fedfcad00a09b3734b4223de" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "PK_74a27e7fcbd5852463d0af3034b" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "assets" ADD CONSTRAINT "PK_da96729a8b113377cfb6a62439c" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "albums" ADD CONSTRAINT "PK_7f71c7b5bc7c87b8f94c9a93a00" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "activity" ADD CONSTRAINT "PK_24625a1d6b1b089c8ae206fe467" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId");`.execute( - db, - ); - await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId");`.execute( - db, - ); - await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "assets_audit" ADD CONSTRAINT "PK_99bd5c015f81a641927a32b4212" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "person" ADD CONSTRAINT "PK_5fdaf670315c4b7e70cce85daa3" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "PK_6df76ab2eb6f5b57b7c2f1fc684" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "PK_c41dc3e9ef5e1c57ca5a08a0004" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" PRIMARY KEY ("assetId");`.execute( - db, - ); - await sql`ALTER TABLE "audit" ADD CONSTRAINT "PK_1d3d120ddaf7bc9b1ed68ed463a" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "exif" ADD CONSTRAINT "PK_c0117fdbc50b917ef9067740c44" PRIMARY KEY ("assetId");`.execute(db); - await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_pkey" PRIMARY KEY ("faceId");`.execute(db); - await sql`ALTER TABLE "geodata_places" ADD CONSTRAINT "PK_c29918988912ef4036f3d7fbff4" PRIMARY KEY ("id");`.execute( - db, - ); - await sql`ALTER TABLE "memories" ADD CONSTRAINT "PK_aaa0692d9496fe827b0568612f8" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" PRIMARY KEY ("memoriesId", "assetsId");`.execute( - db, - ); - await sql`ALTER TABLE "move_history" ADD CONSTRAINT "PK_af608f132233acf123f2949678d" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "naturalearth_countries" ADD CONSTRAINT "PK_21a6d86d1ab5d841648212e5353" PRIMARY KEY ("id");`.execute( - db, - ); - await sql`ALTER TABLE "partners_audit" ADD CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id");`.execute( - db, - ); - await sql`ALTER TABLE "partners" ADD CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId");`.execute( - db, - ); - await sql`ALTER TABLE "sessions" ADD CONSTRAINT "PK_48cb6b5c20faa63157b3c1baf7f" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "PK_642e2b0f619e4876e5f90a43465" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" PRIMARY KEY ("assetsId", "sharedLinksId");`.execute( - db, - ); - await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_pkey" PRIMARY KEY ("assetId");`.execute(db); - await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type");`.execute( - db, - ); - await sql`ALTER TABLE "system_metadata" ADD CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" PRIMARY KEY ("key");`.execute( - db, - ); - await sql`ALTER TABLE "tags" ADD CONSTRAINT "PK_e7dc17249a1148a1970748eda99" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId");`.execute( - db, - ); - await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" PRIMARY KEY ("id_ancestor", "id_descendant");`.execute( - db, - ); - await sql`ALTER TABLE "users_audit" ADD CONSTRAINT "PK_e9b2bdfd90e7eb5961091175180" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key");`.execute( - db, - ); - await sql`ALTER TABLE "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute( - db, - ); - await sql`ALTER TABLE "libraries" ADD CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;`.execute( - db, - ); - await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute( - db, - ); - await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute( - db, - ); - await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute( - db, - ); - await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_1af8519996fbfb3684b58df280b" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_427c350ad49bd3935a50baab737" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" FOREIGN KEY ("usersId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_2bbabe31656b6778c6b87b61023" FOREIGN KEY ("faceAssetId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE SET NULL;`.execute( - db, - ); - await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute( - db, - ); - await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_faceId_fkey" FOREIGN KEY ("faceId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "memories" ADD CONSTRAINT "FK_575842846f0c28fa5da46c99b19" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" FOREIGN KEY ("memoriesId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" FOREIGN KEY ("sharedById") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "sessions" ADD CONSTRAINT "FK_57de40bc620f456c7311aa3a1e6" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" FOREIGN KEY ("sharedLinksId") REFERENCES "shared_links" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" FOREIGN KEY ("sessionId") REFERENCES "sessions" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" FOREIGN KEY ("parentId") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_15fbcbc67663c6bfc07b354c22c" FOREIGN KEY ("id_ancestor") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_b1a2a7ed45c29179b5ad51548a1" FOREIGN KEY ("id_descendant") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email");`.execute(db); - await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a" UNIQUE ("storageLabel");`.execute(db); - await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "REL_91704e101438fd0653f582426d" UNIQUE ("primaryAssetId");`.execute( - db, - ); - await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "UQ_assetId_type" UNIQUE ("assetId", "type");`.execute(db); - await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_newPath" UNIQUE ("newPath");`.execute(db); - await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_entityId_pathType" UNIQUE ("entityId", "pathType");`.execute( - db, - ); - await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "UQ_sharedlink_key" UNIQUE ("key");`.execute(db); - await sql`ALTER TABLE "tags" ADD CONSTRAINT "UQ_79d6f16e52bb2c7130375246793" UNIQUE ("userId", "value");`.execute(db); - await sql`ALTER TABLE "activity" ADD CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" CHECK (("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false));`.execute( - db, - ); - await sql`ALTER TABLE "person" ADD CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" CHECK ("birthDate" <= CURRENT_DATE);`.execute( - db, - ); - await sql`CREATE INDEX "IDX_users_updated_at_asc_id_asc" ON "users" ("updatedAt", "id")`.execute(db); - await sql`CREATE INDEX "IDX_users_update_id" ON "users" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_0f6fc2fb195f24d19b0fb0d57c" ON "libraries" ("ownerId")`.execute(db); - await sql`CREATE INDEX "IDX_libraries_update_id" ON "libraries" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_91704e101438fd0653f582426d" ON "asset_stack" ("primaryAssetId")`.execute(db); - await sql`CREATE INDEX "IDX_c05079e542fd74de3b5ecb5c1c" ON "asset_stack" ("ownerId")`.execute(db); - await sql`CREATE INDEX "idx_originalfilename_trigram" ON "assets" USING gin (f_unaccent("originalFileName") gin_trgm_ops)`.execute( - db, - ); - await sql`CREATE INDEX "IDX_asset_id_stackId" ON "assets" ("id", "stackId")`.execute(db); - await sql`CREATE INDEX "IDX_originalPath_libraryId" ON "assets" ("originalPath", "libraryId")`.execute(db); - await sql`CREATE INDEX "idx_local_date_time_month" ON "assets" ((date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text))`.execute( - db, - ); - await sql`CREATE INDEX "idx_local_date_time" ON "assets" ((("localDateTime" at time zone 'UTC')::date))`.execute(db); - await sql`CREATE UNIQUE INDEX "UQ_assets_owner_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE ("libraryId" IS NOT NULL)`.execute( - db, - ); - await sql`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE ("libraryId" IS NULL)`.execute( - db, - ); - await sql`CREATE INDEX "IDX_2c5ac0d6fb58b238fd2068de67" ON "assets" ("ownerId")`.execute(db); - await sql`CREATE INDEX "idx_asset_file_created_at" ON "assets" ("fileCreatedAt")`.execute(db); - await sql`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum")`.execute(db); - await sql`CREATE INDEX "IDX_16294b83fa8c0149719a1f631e" ON "assets" ("livePhotoVideoId")`.execute(db); - await sql`CREATE INDEX "IDX_4d66e76dada1ca180f67a205dc" ON "assets" ("originalFileName")`.execute(db); - await sql`CREATE INDEX "IDX_9977c3c1de01c3d848039a6b90" ON "assets" ("libraryId")`.execute(db); - await sql`CREATE INDEX "IDX_f15d48fa3ea5e4bda05ca8ab20" ON "assets" ("stackId")`.execute(db); - await sql`CREATE INDEX "IDX_assets_duplicateId" ON "assets" ("duplicateId")`.execute(db); - await sql`CREATE INDEX "IDX_assets_update_id" ON "assets" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_b22c53f35ef20c28c21637c85f" ON "albums" ("ownerId")`.execute(db); - await sql`CREATE INDEX "IDX_05895aa505a670300d4816debc" ON "albums" ("albumThumbnailAssetId")`.execute(db); - await sql`CREATE INDEX "IDX_albums_update_id" ON "albums" ("updateId")`.execute(db); - await sql`CREATE UNIQUE INDEX "IDX_activity_like" ON "activity" ("assetId", "userId", "albumId") WHERE ("isLiked" = true)`.execute( - db, - ); - await sql`CREATE INDEX "IDX_1af8519996fbfb3684b58df280" ON "activity" ("albumId")`.execute(db); - await sql`CREATE INDEX "IDX_3571467bcbe021f66e2bdce96e" ON "activity" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_8091ea76b12338cb4428d33d78" ON "activity" ("assetId")`.execute(db); - await sql`CREATE INDEX "IDX_activity_update_id" ON "activity" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_e590fa396c6898fcd4a50e4092" ON "albums_assets_assets" ("albumsId")`.execute(db); - await sql`CREATE INDEX "IDX_4bd1303d199f4e72ccdf998c62" ON "albums_assets_assets" ("assetsId")`.execute(db); - await sql`CREATE INDEX "IDX_f48513bf9bccefd6ff3ad30bd0" ON "albums_shared_users_users" ("usersId")`.execute(db); - await sql`CREATE INDEX "IDX_427c350ad49bd3935a50baab73" ON "albums_shared_users_users" ("albumsId")`.execute(db); - await sql`CREATE INDEX "IDX_6c2e267ae764a9413b863a2934" ON "api_keys" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_api_keys_update_id" ON "api_keys" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_assets_audit_asset_id" ON "assets_audit" ("assetId")`.execute(db); - await sql`CREATE INDEX "IDX_assets_audit_owner_id" ON "assets_audit" ("ownerId")`.execute(db); - await sql`CREATE INDEX "IDX_assets_audit_deleted_at" ON "assets_audit" ("deletedAt")`.execute(db); - await sql`CREATE INDEX "IDX_5527cc99f530a547093f9e577b" ON "person" ("ownerId")`.execute(db); - await sql`CREATE INDEX "IDX_2bbabe31656b6778c6b87b6102" ON "person" ("faceAssetId")`.execute(db); - await sql`CREATE INDEX "IDX_person_update_id" ON "person" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_bf339a24070dac7e71304ec530" ON "asset_faces" ("personId", "assetId")`.execute(db); - await sql`CREATE INDEX "IDX_asset_faces_assetId_personId" ON "asset_faces" ("assetId", "personId")`.execute(db); - await sql`CREATE INDEX "IDX_02a43fd0b3c50fb6d7f0cb7282" ON "asset_faces" ("assetId")`.execute(db); - await sql`CREATE INDEX "IDX_95ad7106dd7b484275443f580f" ON "asset_faces" ("personId")`.execute(db); - await sql`CREATE INDEX "IDX_asset_files_assetId" ON "asset_files" ("assetId")`.execute(db); - await sql`CREATE INDEX "IDX_asset_files_update_id" ON "asset_files" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_ownerId_createdAt" ON "audit" ("ownerId", "createdAt")`.execute(db); - await sql`CREATE INDEX "exif_city" ON "exif" ("city")`.execute(db); - await sql`CREATE INDEX "IDX_live_photo_cid" ON "exif" ("livePhotoCID")`.execute(db); - await sql`CREATE INDEX "IDX_auto_stack_id" ON "exif" ("autoStackId")`.execute(db); - await sql`CREATE INDEX "IDX_asset_exif_update_id" ON "exif" ("updateId")`.execute(db); - await sql.raw(vectorIndexQuery({ vectorExtension, table: 'face_search', indexName: 'face_index' })).execute(db); - await sql`CREATE INDEX "IDX_geodata_gist_earthcoord" ON "geodata_places" (ll_to_earth_public(latitude, longitude))`.execute( - db, - ); - await sql`CREATE INDEX "idx_geodata_places_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute( - db, - ); - await sql`CREATE INDEX "idx_geodata_places_admin2_name" ON "geodata_places" USING gin (f_unaccent("admin2Name") gin_trgm_ops)`.execute( - db, - ); - await sql`CREATE INDEX "idx_geodata_places_admin1_name" ON "geodata_places" USING gin (f_unaccent("admin1Name") gin_trgm_ops)`.execute( - db, - ); - await sql`CREATE INDEX "idx_geodata_places_alternate_names" ON "geodata_places" USING gin (f_unaccent("alternateNames") gin_trgm_ops)`.execute( - db, - ); - await sql`CREATE INDEX "IDX_575842846f0c28fa5da46c99b1" ON "memories" ("ownerId")`.execute(db); - await sql`CREATE INDEX "IDX_memories_update_id" ON "memories" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_984e5c9ab1f04d34538cd32334" ON "memories_assets_assets" ("memoriesId")`.execute(db); - await sql`CREATE INDEX "IDX_6942ecf52d75d4273de19d2c16" ON "memories_assets_assets" ("assetsId")`.execute(db); - await sql`CREATE INDEX "IDX_partners_audit_shared_by_id" ON "partners_audit" ("sharedById")`.execute(db); - await sql`CREATE INDEX "IDX_partners_audit_shared_with_id" ON "partners_audit" ("sharedWithId")`.execute(db); - await sql`CREATE INDEX "IDX_partners_audit_deleted_at" ON "partners_audit" ("deletedAt")`.execute(db); - await sql`CREATE INDEX "IDX_7e077a8b70b3530138610ff5e0" ON "partners" ("sharedById")`.execute(db); - await sql`CREATE INDEX "IDX_d7e875c6c60e661723dbf372fd" ON "partners" ("sharedWithId")`.execute(db); - await sql`CREATE INDEX "IDX_partners_update_id" ON "partners" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_57de40bc620f456c7311aa3a1e" ON "sessions" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_sessions_update_id" ON "sessions" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_66fe3837414c5a9f1c33ca4934" ON "shared_links" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_sharedlink_key" ON "shared_links" ("key")`.execute(db); - await sql`CREATE INDEX "IDX_sharedlink_albumId" ON "shared_links" ("albumId")`.execute(db); - await sql`CREATE INDEX "IDX_5b7decce6c8d3db9593d6111a6" ON "shared_link__asset" ("assetsId")`.execute(db); - await sql`CREATE INDEX "IDX_c9fab4aa97ffd1b034f3d6581a" ON "shared_link__asset" ("sharedLinksId")`.execute(db); - await sql.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: 'clip_index' })).execute(db); - await sql`CREATE INDEX "IDX_d8ddd9d687816cc490432b3d4b" ON "session_sync_checkpoints" ("sessionId")`.execute(db); - await sql`CREATE INDEX "IDX_session_sync_checkpoints_update_id" ON "session_sync_checkpoints" ("updateId")`.execute( - db, - ); - await sql`CREATE INDEX "IDX_92e67dc508c705dd66c9461557" ON "tags" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_9f9590cc11561f1f48ff034ef9" ON "tags" ("parentId")`.execute(db); - await sql`CREATE INDEX "IDX_tags_update_id" ON "tags" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_tag_asset_assetsId_tagsId" ON "tag_asset" ("assetsId", "tagsId")`.execute(db); - await sql`CREATE INDEX "IDX_f8e8a9e893cb5c54907f1b798e" ON "tag_asset" ("assetsId")`.execute(db); - await sql`CREATE INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4" ON "tag_asset" ("tagsId")`.execute(db); - await sql`CREATE INDEX "IDX_15fbcbc67663c6bfc07b354c22" ON "tags_closure" ("id_ancestor")`.execute(db); - await sql`CREATE INDEX "IDX_b1a2a7ed45c29179b5ad51548a" ON "tags_closure" ("id_descendant")`.execute(db); - await sql`CREATE INDEX "IDX_users_audit_deleted_at" ON "users_audit" ("deletedAt")`.execute(db); - await sql`CREATE INDEX "IDX_6afb43681a21cf7815932bc38a" ON "user_metadata" ("userId")`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "users_delete_audit" - AFTER DELETE ON "users" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION users_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "users_updated_at" - BEFORE UPDATE ON "users" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "libraries_updated_at" - BEFORE UPDATE ON "libraries" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "assets_delete_audit" - AFTER DELETE ON "assets" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION assets_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "assets_updated_at" - BEFORE UPDATE ON "assets" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "albums_updated_at" - BEFORE UPDATE ON "albums" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "activity_updated_at" - BEFORE UPDATE ON "activity" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "api_keys_updated_at" - BEFORE UPDATE ON "api_keys" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "person_updated_at" - BEFORE UPDATE ON "person" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_files_updated_at" - BEFORE UPDATE ON "asset_files" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_exif_updated_at" - BEFORE UPDATE ON "exif" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memories_updated_at" - BEFORE UPDATE ON "memories" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "partners_delete_audit" - AFTER DELETE ON "partners" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION partners_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "partners_updated_at" - BEFORE UPDATE ON "partners" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "sessions_updated_at" - BEFORE UPDATE ON "sessions" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "session_sync_checkpoints_updated_at" - BEFORE UPDATE ON "session_sync_checkpoints" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "tags_updated_at" - BEFORE UPDATE ON "tags" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(): Promise { - // not implemented -} diff --git a/server/src/schema/migrations/1744991379464-AddNotificationsTable.ts b/server/src/schema/migrations/1744991379464-AddNotificationsTable.ts deleted file mode 100644 index 28dca6658c..0000000000 --- a/server/src/schema/migrations/1744991379464-AddNotificationsTable.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "notifications" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7(), "userId" uuid, "level" character varying NOT NULL DEFAULT 'info', "type" character varying NOT NULL DEFAULT 'info', "data" jsonb, "title" character varying NOT NULL, "description" text, "readAt" timestamp with time zone);`.execute(db); - await sql`ALTER TABLE "notifications" ADD CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "notifications" ADD CONSTRAINT "FK_692a909ee0fa9383e7859f9b406" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`CREATE INDEX "IDX_notifications_update_id" ON "notifications" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_692a909ee0fa9383e7859f9b40" ON "notifications" ("userId")`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "notifications_updated_at" - BEFORE UPDATE ON "notifications" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "notifications_updated_at" ON "notifications";`.execute(db); - await sql`DROP INDEX "IDX_notifications_update_id";`.execute(db); - await sql`DROP INDEX "IDX_692a909ee0fa9383e7859f9b40";`.execute(db); - await sql`ALTER TABLE "notifications" DROP CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a";`.execute(db); - await sql`ALTER TABLE "notifications" DROP CONSTRAINT "FK_692a909ee0fa9383e7859f9b406";`.execute(db); - await sql`DROP TABLE "notifications";`.execute(db); -} diff --git a/server/src/schema/migrations/1745244781846-AddUserAvatarColorColumn.ts b/server/src/schema/migrations/1745244781846-AddUserAvatarColorColumn.ts deleted file mode 100644 index 5f3fdbedc8..0000000000 --- a/server/src/schema/migrations/1745244781846-AddUserAvatarColorColumn.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "users" ADD "avatarColor" character varying;`.execute(db); - await sql` - UPDATE "users" - SET "avatarColor" = "user_metadata"."value"->'avatar'->>'color' - FROM "user_metadata" - WHERE "users"."id" = "user_metadata"."userId" AND "user_metadata"."key" = 'preferences';`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "users" DROP COLUMN "avatarColor";`.execute(db); -} diff --git a/server/src/schema/migrations/1745902563899-AddAssetVisibilityColumn.ts b/server/src/schema/migrations/1745902563899-AddAssetVisibilityColumn.ts deleted file mode 100644 index 6fe9dab1a0..0000000000 --- a/server/src/schema/migrations/1745902563899-AddAssetVisibilityColumn.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TYPE "asset_visibility_enum" AS ENUM ('archive','timeline','hidden');`.execute(db); - await sql`ALTER TABLE "assets" - ADD "visibility" asset_visibility_enum NOT NULL DEFAULT 'timeline';`.execute(db); - - await sql` - UPDATE "assets" - SET "visibility" = CASE - WHEN "isArchived" THEN 'archive'::asset_visibility_enum - WHEN "isVisible" THEN 'timeline'::asset_visibility_enum - ELSE 'hidden'::asset_visibility_enum - END; - `.execute(db); - - await sql`ALTER TABLE "assets" DROP COLUMN "isVisible";`.execute(db); - await sql`ALTER TABLE "assets" DROP COLUMN "isArchived";`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "assets" ADD COLUMN "isArchived" BOOLEAN NOT NULL DEFAULT FALSE;`.execute(db); - await sql`ALTER TABLE "assets" ADD COLUMN "isVisible" BOOLEAN NOT NULL DEFAULT TRUE;`.execute(db); - - await sql` - UPDATE "assets" - SET - "isArchived" = ("visibility" = 'archive'::asset_visibility_enum), - "isVisible" = CASE - WHEN "visibility" = 'timeline'::asset_visibility_enum THEN TRUE - WHEN "visibility" = 'archive'::asset_visibility_enum THEN TRUE - ELSE FALSE - END; - `.execute(db); - await sql`ALTER TABLE "assets" DROP COLUMN "visibility";`.execute(db); - await sql`DROP TYPE "asset_visibility_enum";`.execute(db); -} diff --git a/server/src/schema/migrations/1746636476623-DropExtraIndexes.ts b/server/src/schema/migrations/1746636476623-DropExtraIndexes.ts deleted file mode 100644 index 1518593428..0000000000 --- a/server/src/schema/migrations/1746636476623-DropExtraIndexes.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - const { rows } = await sql<{ db: string }>`SELECT current_database() as db`.execute(db); - const databaseName = rows[0].db; - await sql.raw(`ALTER DATABASE "${databaseName}" SET search_path TO "$user", public, vectors`).execute(db); - const naturalearth_pkey = await sql<{ constraint_name: string }>`SELECT constraint_name - FROM information_schema.table_constraints - WHERE table_schema = 'public' - AND table_name = 'naturalearth_countries' - AND constraint_type = 'PRIMARY KEY';`.execute(db); - const naturalearth_pkey_name = naturalearth_pkey.rows[0]?.constraint_name; - if(naturalearth_pkey_name) { - await sql`ALTER TABLE "naturalearth_countries" - DROP CONSTRAINT ${sql.ref(naturalearth_pkey_name)};`.execute(db); - } - await sql`ALTER TABLE "naturalearth_countries" ADD CONSTRAINT "naturalearth_countries_pkey" PRIMARY KEY ("id") WITH (FILLFACTOR = 100);`.execute(db); - await sql`DROP INDEX IF EXISTS "IDX_02a43fd0b3c50fb6d7f0cb7282";`.execute(db); - await sql`DROP INDEX IF EXISTS "IDX_95ad7106dd7b484275443f580f";`.execute(db); - await sql`DROP INDEX IF EXISTS "IDX_7e077a8b70b3530138610ff5e0";`.execute(db); - await sql`DROP INDEX IF EXISTS "IDX_92e67dc508c705dd66c9461557";`.execute(db); - await sql`DROP INDEX IF EXISTS "IDX_6afb43681a21cf7815932bc38a";`.execute(db); -} - -export async function down(db: Kysely): Promise { - const { rows } = await sql<{ db: string }>`SELECT current_database() as db`.execute(db); - const databaseName = rows[0].db; - await sql.raw(`ALTER DATABASE "${databaseName}" RESET "search_path"`).execute(db); -} diff --git a/server/src/schema/migrations/1746768490606-AddUserPincode.ts b/server/src/schema/migrations/1746768490606-AddUserPincode.ts deleted file mode 100644 index 12dc3c2d12..0000000000 --- a/server/src/schema/migrations/1746768490606-AddUserPincode.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "users" ADD "pinCode" character varying;`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "users" DROP COLUMN "pinCode";`.execute(db); -} diff --git a/server/src/schema/migrations/1746844028242-AddLockedVisibilityEnum.ts b/server/src/schema/migrations/1746844028242-AddLockedVisibilityEnum.ts deleted file mode 100644 index 9a344be66d..0000000000 --- a/server/src/schema/migrations/1746844028242-AddLockedVisibilityEnum.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TYPE "asset_visibility_enum" ADD VALUE IF NOT EXISTS 'locked';`.execute(db); -} - -export async function down(): Promise { - // noop -} diff --git a/server/src/schema/migrations/1746987967923-AddPinExpiresAtColumn.ts b/server/src/schema/migrations/1746987967923-AddPinExpiresAtColumn.ts deleted file mode 100644 index b0f7d072d5..0000000000 --- a/server/src/schema/migrations/1746987967923-AddPinExpiresAtColumn.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" ADD "pinExpiresAt" timestamp with time zone;`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" DROP COLUMN "pinExpiresAt";`.execute(db); -} diff --git a/server/src/schema/migrations/1747329504572-AddNewSessionColumns.ts b/server/src/schema/migrations/1747329504572-AddNewSessionColumns.ts deleted file mode 100644 index d3cf8de173..0000000000 --- a/server/src/schema/migrations/1747329504572-AddNewSessionColumns.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" ADD "expiredAt" timestamp with time zone;`.execute(db); - await sql`ALTER TABLE "sessions" ADD "parentId" uuid;`.execute(db); - await sql`ALTER TABLE "sessions" ADD CONSTRAINT "FK_afbbabbd7daf5b91de4dca84de8" FOREIGN KEY ("parentId") REFERENCES "sessions" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`CREATE INDEX "IDX_afbbabbd7daf5b91de4dca84de" ON "sessions" ("parentId")`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP INDEX "IDX_afbbabbd7daf5b91de4dca84de";`.execute(db); - await sql`ALTER TABLE "sessions" DROP CONSTRAINT "FK_afbbabbd7daf5b91de4dca84de8";`.execute(db); - await sql`ALTER TABLE "sessions" DROP COLUMN "expiredAt";`.execute(db); - await sql`ALTER TABLE "sessions" DROP COLUMN "parentId";`.execute(db); -} diff --git a/server/src/schema/migrations/1747338664832-SessionRename.ts b/server/src/schema/migrations/1747338664832-SessionRename.ts deleted file mode 100644 index 5ba532d136..0000000000 --- a/server/src/schema/migrations/1747338664832-SessionRename.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" RENAME "expiredAt" TO "expiresAt";`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" RENAME "expiresAt" TO "expiredAt";`.execute(db); -} diff --git a/server/src/schema/migrations/1747664684909-AddAlbumAuditTables.ts b/server/src/schema/migrations/1747664684909-AddAlbumAuditTables.ts deleted file mode 100644 index 25ccfee710..0000000000 --- a/server/src/schema/migrations/1747664684909-AddAlbumAuditTables.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION album_user_after_insert() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE albums SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumsId" FROM inserted_rows); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION albums_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO albums_audit ("albumId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION album_users_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO albums_audit ("albumId", "userId") - SELECT "albumsId", "usersId" - FROM OLD; - - IF pg_trigger_depth() = 1 THEN - INSERT INTO album_users_audit ("albumId", "userId") - SELECT "albumsId", "usersId" - FROM OLD; - END IF; - - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE TABLE "albums_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db); - await sql`CREATE TABLE "album_users_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db); - await sql`ALTER TABLE "albums_audit" ADD CONSTRAINT "PK_c75efea8d4dce316ad29b851a8b" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "album_users_audit" ADD CONSTRAINT "PK_f479a2e575b7ebc9698362c1688" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "albums_shared_users_users" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`ALTER TABLE "albums_shared_users_users" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`CREATE INDEX "IDX_album_users_update_id" ON "albums_shared_users_users" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_albums_audit_album_id" ON "albums_audit" ("albumId")`.execute(db); - await sql`CREATE INDEX "IDX_albums_audit_user_id" ON "albums_audit" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_albums_audit_deleted_at" ON "albums_audit" ("deletedAt")`.execute(db); - await sql`CREATE INDEX "IDX_album_users_audit_album_id" ON "album_users_audit" ("albumId")`.execute(db); - await sql`CREATE INDEX "IDX_album_users_audit_user_id" ON "album_users_audit" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_album_users_audit_deleted_at" ON "album_users_audit" ("deletedAt")`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "albums_delete_audit" - AFTER DELETE ON "albums" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION albums_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_users_delete_audit" - AFTER DELETE ON "albums_shared_users_users" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() <= 1) - EXECUTE FUNCTION album_users_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_user_after_insert" - AFTER INSERT ON "albums_shared_users_users" - REFERENCING NEW TABLE AS "inserted_rows" - FOR EACH STATEMENT - EXECUTE FUNCTION album_user_after_insert();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_users_updated_at" - BEFORE UPDATE ON "albums_shared_users_users" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "albums_delete_audit" ON "albums";`.execute(db); - await sql`DROP TRIGGER "album_users_delete_audit" ON "albums_shared_users_users";`.execute(db); - await sql`DROP TRIGGER "album_user_after_insert" ON "albums_shared_users_users";`.execute(db); - await sql`DROP INDEX "IDX_albums_audit_album_id";`.execute(db); - await sql`DROP INDEX "IDX_albums_audit_user_id";`.execute(db); - await sql`DROP INDEX "IDX_albums_audit_deleted_at";`.execute(db); - await sql`DROP INDEX "IDX_album_users_audit_album_id";`.execute(db); - await sql`DROP INDEX "IDX_album_users_audit_user_id";`.execute(db); - await sql`DROP INDEX "IDX_album_users_audit_deleted_at";`.execute(db); - await sql`ALTER TABLE "albums_audit" DROP CONSTRAINT "PK_c75efea8d4dce316ad29b851a8b";`.execute(db); - await sql`ALTER TABLE "album_users_audit" DROP CONSTRAINT "PK_f479a2e575b7ebc9698362c1688";`.execute(db); - await sql`DROP TABLE "albums_audit";`.execute(db); - await sql`DROP TABLE "album_users_audit";`.execute(db); - await sql`DROP FUNCTION album_user_after_insert;`.execute(db); - await sql`DROP FUNCTION albums_delete_audit;`.execute(db); - await sql`DROP FUNCTION album_users_delete_audit;`.execute(db); -} diff --git a/server/src/schema/migrations/1749067526135-UserOnboardingDefault.ts b/server/src/schema/migrations/1749067526135-UserOnboardingDefault.ts deleted file mode 100644 index e6aabec27d..0000000000 --- a/server/src/schema/migrations/1749067526135-UserOnboardingDefault.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Kysely, sql } from 'kysely'; -import { UserMetadataKey } from 'src/enum'; - -export async function up(db: Kysely): Promise { - await sql`INSERT INTO user_metadata SELECT id, ${UserMetadataKey.Onboarding}, '{"isOnboarded": true}' FROM users - ON CONFLICT ("userId", key) DO NOTHING - `.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DELETE FROM user_metadata WHERE key = ${UserMetadataKey.Onboarding}`.execute(db); -} diff --git a/server/src/schema/migrations/1750107668827-PartnerCreateId.ts b/server/src/schema/migrations/1750107668827-PartnerCreateId.ts deleted file mode 100644 index 56d78bf25a..0000000000 --- a/server/src/schema/migrations/1750107668827-PartnerCreateId.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "partners" ADD "createId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`UPDATE "partners" SET "createId" = immich_uuid_v7("createdAt")`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "partners" DROP COLUMN "createId";`.execute(db); -} diff --git a/server/src/schema/migrations/1750189909087-AddAlbumUserCreateFields.ts b/server/src/schema/migrations/1750189909087-AddAlbumUserCreateFields.ts deleted file mode 100644 index 0ad59f9e82..0000000000 --- a/server/src/schema/migrations/1750189909087-AddAlbumUserCreateFields.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "albums_shared_users_users" ADD "createId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`ALTER TABLE "albums_shared_users_users" ADD "createdAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`CREATE INDEX "IDX_album_users_create_id" ON "albums_shared_users_users" ("createId")`.execute(db); - await sql`CREATE INDEX "IDX_partners_create_id" ON "partners" ("createId")`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP INDEX "IDX_partners_create_id";`.execute(db); - await sql`DROP INDEX "IDX_album_users_create_id";`.execute(db); - await sql`ALTER TABLE "albums_shared_users_users" DROP COLUMN "createId";`.execute(db); - await sql`ALTER TABLE "albums_shared_users_users" DROP COLUMN "createdAt";`.execute(db); -} diff --git a/server/src/schema/migrations/1750323941566-UnsetPrewarmDimParameter.ts b/server/src/schema/migrations/1750323941566-UnsetPrewarmDimParameter.ts deleted file mode 100644 index e7a1a06ec9..0000000000 --- a/server/src/schema/migrations/1750323941566-UnsetPrewarmDimParameter.ts +++ /dev/null @@ -1,10 +0,0 @@ -// this file used to try to reset the `vchordrq.prewarm_dim;` parameter -// that ends up being a problem on pg 15 + since the extension is not installed. - -export async function up(): Promise { - // noop -} - -export async function down(): Promise { - // noop -} diff --git a/server/src/schema/migrations/1750676477029-AlbumAssetUpdateId.ts b/server/src/schema/migrations/1750676477029-AlbumAssetUpdateId.ts deleted file mode 100644 index 8feba6f11c..0000000000 --- a/server/src/schema/migrations/1750676477029-AlbumAssetUpdateId.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "albums_assets_assets" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`ALTER TABLE "albums_assets_assets" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`CREATE INDEX "IDX_album_assets_update_id" ON "albums_assets_assets" ("updateId")`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_assets_updated_at" - BEFORE UPDATE ON "albums_assets_assets" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP INDEX "IDX_album_assets_update_id";`.execute(db); - await sql`ALTER TABLE "albums_assets_assets" DROP COLUMN "updatedAt";`.execute(db); - await sql`ALTER TABLE "albums_assets_assets" DROP COLUMN "updateId";`.execute(db); - await sql`DROP TRIGGER "album_assets_updated_at" ON "albums_assets_assets";`.execute(db); -} diff --git a/server/src/schema/migrations/1750694237564-AlbumAssetAuditTable.ts b/server/src/schema/migrations/1750694237564-AlbumAssetAuditTable.ts deleted file mode 100644 index 0191b5d07a..0000000000 --- a/server/src/schema/migrations/1750694237564-AlbumAssetAuditTable.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "album_assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "albumId" uuid NOT NULL, "assetId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db); - await sql`ALTER TABLE "album_assets_audit" ADD CONSTRAINT "PK_32969b576ec8f78d84f37c2eb2d" PRIMARY KEY ("id");`.execute(db); - await sql`CREATE INDEX "IDX_album_assets_audit_album_id" ON "album_assets_audit" ("albumId")`.execute(db); - await sql`CREATE INDEX "IDX_album_assets_audit_asset_id" ON "album_assets_audit" ("assetId")`.execute(db); - await sql`CREATE INDEX "IDX_album_assets_audit_deleted_at" ON "album_assets_audit" ("deletedAt")`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_assets_updated_at" - BEFORE UPDATE ON "albums_assets_assets" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "album_assets_updated_at" ON "albums_assets_assets";`.execute(db); - await sql`DROP INDEX "IDX_album_assets_audit_album_id";`.execute(db); - await sql`DROP INDEX "IDX_album_assets_audit_asset_id";`.execute(db); - await sql`DROP INDEX "IDX_album_assets_audit_deleted_at";`.execute(db); - await sql`ALTER TABLE "album_assets_audit" DROP CONSTRAINT "PK_32969b576ec8f78d84f37c2eb2d";`.execute(db); - await sql`DROP TABLE "album_assets_audit";`.execute(db); -} diff --git a/server/src/schema/migrations/1750780093818-AddAlbumToAssetDeleteTrigger.ts b/server/src/schema/migrations/1750780093818-AddAlbumToAssetDeleteTrigger.ts deleted file mode 100644 index f3cb75d10e..0000000000 --- a/server/src/schema/migrations/1750780093818-AddAlbumToAssetDeleteTrigger.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION album_assets_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO album_assets_audit ("albumId", "assetId") - SELECT "albumsId", "assetsId" FROM OLD - WHERE "albumsId" IN (SELECT "id" FROM albums WHERE "id" IN (SELECT "albumsId" FROM OLD)); - RETURN NULL; - END - $$;`.execute(db); - await sql`ALTER TABLE "album_assets_audit" ADD CONSTRAINT "FK_8047b44b812619a3c75a2839b0d" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_assets_delete_audit" - AFTER DELETE ON "albums_assets_assets" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() <= 1) - EXECUTE FUNCTION album_assets_delete_audit();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "album_assets_delete_audit" ON "albums_assets_assets";`.execute(db); - await sql`ALTER TABLE "album_assets_audit" DROP CONSTRAINT "FK_8047b44b812619a3c75a2839b0d";`.execute(db); - await sql`DROP FUNCTION album_assets_delete_audit;`.execute(db); -} diff --git a/server/src/schema/migrations/1751035357937-MemorySyncChanges.ts b/server/src/schema/migrations/1751035357937-MemorySyncChanges.ts deleted file mode 100644 index 8704c30ca3..0000000000 --- a/server/src/schema/migrations/1751035357937-MemorySyncChanges.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "memory_assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "memoryId" uuid NOT NULL, "assetId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db); - await sql`CREATE TABLE "memories_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "memoryId" uuid NOT NULL, "userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" ADD "createdAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`ALTER TABLE "memory_assets_audit" ADD CONSTRAINT "PK_35ef16910228f980e0766dcc59b" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "memories_audit" ADD CONSTRAINT "PK_19de798c033a710dcfa5c72f81b" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "memory_assets_audit" ADD CONSTRAINT "FK_225a204afcb0bd6de015080fb03" FOREIGN KEY ("memoryId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`CREATE INDEX "IDX_memory_assets_audit_memory_id" ON "memory_assets_audit" ("memoryId")`.execute(db); - await sql`CREATE INDEX "IDX_memory_assets_audit_asset_id" ON "memory_assets_audit" ("assetId")`.execute(db); - await sql`CREATE INDEX "IDX_memory_assets_audit_deleted_at" ON "memory_assets_audit" ("deletedAt")`.execute(db); - await sql`CREATE INDEX "IDX_memory_assets_update_id" ON "memories_assets_assets" ("updateId")`.execute(db); - await sql`CREATE INDEX "IDX_memories_audit_memory_id" ON "memories_audit" ("memoryId")`.execute(db); - await sql`CREATE INDEX "IDX_memories_audit_user_id" ON "memories_audit" ("userId")`.execute(db); - await sql`CREATE INDEX "IDX_memories_audit_deleted_at" ON "memories_audit" ("deletedAt")`.execute(db); - await sql`CREATE OR REPLACE FUNCTION memories_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO memories_audit ("memoryId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION memory_assets_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO memory_assets_audit ("memoryId", "assetId") - SELECT "memoriesId", "assetsId" FROM OLD - WHERE "memoriesId" IN (SELECT "id" FROM memories WHERE "id" IN (SELECT "memoriesId" FROM OLD)); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memories_delete_audit" - AFTER DELETE ON "memories" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION memories_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_assets_delete_audit" - AFTER DELETE ON "memories_assets_assets" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() <= 1) - EXECUTE FUNCTION memory_assets_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_assets_updated_at" - BEFORE UPDATE ON "memories_assets_assets" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "memories_delete_audit" ON "memories";`.execute(db); - await sql`DROP TRIGGER "memory_assets_delete_audit" ON "memories_assets_assets";`.execute(db); - await sql`DROP TRIGGER "memory_assets_updated_at" ON "memories_assets_assets";`.execute(db); - await sql`DROP INDEX "IDX_memory_assets_update_id";`.execute(db); - await sql`DROP INDEX "IDX_memory_assets_audit_memory_id";`.execute(db); - await sql`DROP INDEX "IDX_memory_assets_audit_asset_id";`.execute(db); - await sql`DROP INDEX "IDX_memory_assets_audit_deleted_at";`.execute(db); - await sql`DROP INDEX "IDX_memories_audit_memory_id";`.execute(db); - await sql`DROP INDEX "IDX_memories_audit_user_id";`.execute(db); - await sql`DROP INDEX "IDX_memories_audit_deleted_at";`.execute(db); - await sql`ALTER TABLE "memory_assets_audit" DROP CONSTRAINT "FK_225a204afcb0bd6de015080fb03";`.execute(db); - await sql`ALTER TABLE "memory_assets_audit" DROP CONSTRAINT "PK_35ef16910228f980e0766dcc59b";`.execute(db); - await sql`ALTER TABLE "memories_audit" DROP CONSTRAINT "PK_19de798c033a710dcfa5c72f81b";`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" DROP COLUMN "createdAt";`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" DROP COLUMN "updatedAt";`.execute(db); - await sql`ALTER TABLE "memories_assets_assets" DROP COLUMN "updateId";`.execute(db); - await sql`DROP TABLE "memory_assets_audit";`.execute(db); - await sql`DROP TABLE "memories_audit";`.execute(db); - await sql`DROP FUNCTION memories_delete_audit;`.execute(db); - await sql`DROP FUNCTION memory_assets_delete_audit;`.execute(db); -} diff --git a/server/src/schema/migrations/1751304834247-StackSyncChanges.ts b/server/src/schema/migrations/1751304834247-StackSyncChanges.ts deleted file mode 100644 index 83744988b9..0000000000 --- a/server/src/schema/migrations/1751304834247-StackSyncChanges.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "stacks_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "stackId" uuid NOT NULL, "userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db); - await sql`ALTER TABLE "asset_stack" ADD "createdAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`ALTER TABLE "asset_stack" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`ALTER TABLE "asset_stack" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`ALTER TABLE "stacks_audit" ADD CONSTRAINT "PK_dbe4ec648fa032e8973297de07e" PRIMARY KEY ("id");`.execute(db); - await sql`CREATE INDEX "IDX_stacks_audit_deleted_at" ON "stacks_audit" ("deletedAt")`.execute(db); - await sql`CREATE OR REPLACE FUNCTION stacks_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO stacks_audit ("stackId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "stacks_delete_audit" - AFTER DELETE ON "asset_stack" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION stacks_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "stacks_updated_at" - BEFORE UPDATE ON "asset_stack" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "stacks_delete_audit" ON "asset_stack";`.execute(db); - await sql`DROP TRIGGER "stacks_updated_at" ON "asset_stack";`.execute(db); - await sql`DROP INDEX "IDX_stacks_audit_deleted_at";`.execute(db); - await sql`ALTER TABLE "stacks_audit" DROP CONSTRAINT "PK_dbe4ec648fa032e8973297de07e";`.execute(db); - await sql`ALTER TABLE "asset_stack" DROP COLUMN "createdAt";`.execute(db); - await sql`ALTER TABLE "asset_stack" DROP COLUMN "updatedAt";`.execute(db); - await sql`ALTER TABLE "asset_stack" DROP COLUMN "updateId";`.execute(db); - await sql`DROP TABLE "stacks_audit";`.execute(db); - await sql`DROP FUNCTION stacks_delete_audit;`.execute(db); -} diff --git a/server/src/schema/migrations/1751924596408-AddOverrides.ts b/server/src/schema/migrations/1751924596408-AddOverrides.ts deleted file mode 100644 index 14d3359f4d..0000000000 --- a/server/src/schema/migrations/1751924596408-AddOverrides.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "migration_overrides" ("name" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db); - await sql`ALTER TABLE "migration_overrides" ADD CONSTRAINT "migration_overrides_pkey" PRIMARY KEY ("name");`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_immich_uuid_v7', '{"type":"function","name":"immich_uuid_v7","sql":"CREATE OR REPLACE FUNCTION immich_uuid_v7(p_timestamp timestamp with time zone default clock_timestamp())\\n RETURNS uuid\\n VOLATILE LANGUAGE SQL\\n AS $$\\n SELECT encode(\\n set_bit(\\n set_bit(\\n overlay(uuid_send(gen_random_uuid())\\n placing substring(int8send(floor(extract(epoch from p_timestamp) * 1000)::bigint) from 3)\\n from 1 for 6\\n ),\\n 52, 1\\n ),\\n 53, 1\\n ),\\n ''hex'')::uuid;\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_user_after_insert', '{"type":"function","name":"album_user_after_insert","sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE albums SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumsId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_updated_at', '{"type":"function","name":"updated_at","sql":"CREATE OR REPLACE FUNCTION updated_at()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n DECLARE\\n clock_timestamp TIMESTAMP := clock_timestamp();\\n BEGIN\\n new.\\"updatedAt\\" = clock_timestamp;\\n new.\\"updateId\\" = immich_uuid_v7(clock_timestamp);\\n return new;\\n END;\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_f_concat_ws', '{"type":"function","name":"f_concat_ws","sql":"CREATE OR REPLACE FUNCTION f_concat_ws(text, text[])\\n RETURNS text\\n PARALLEL SAFE IMMUTABLE LANGUAGE SQL\\n AS $$SELECT array_to_string($2, $1)$$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_f_unaccent', '{"type":"function","name":"f_unaccent","sql":"CREATE OR REPLACE FUNCTION f_unaccent(text)\\n RETURNS text\\n PARALLEL SAFE STRICT IMMUTABLE LANGUAGE SQL\\n RETURN unaccent(''unaccent'', $1)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_ll_to_earth_public', '{"type":"function","name":"ll_to_earth_public","sql":"CREATE OR REPLACE FUNCTION ll_to_earth_public(latitude double precision, longitude double precision)\\n RETURNS public.earth\\n PARALLEL SAFE STRICT IMMUTABLE LANGUAGE SQL\\n AS $$SELECT public.cube(public.cube(public.cube(public.earth()*cos(radians(latitude))*cos(radians(longitude))),public.earth()*cos(radians(latitude))*sin(radians(longitude))),public.earth()*sin(radians(latitude)))::public.earth$$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_users_delete_audit', '{"type":"function","name":"users_delete_audit","sql":"CREATE OR REPLACE FUNCTION users_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO users_audit (\\"userId\\")\\n SELECT \\"id\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_partners_delete_audit', '{"type":"function","name":"partners_delete_audit","sql":"CREATE OR REPLACE FUNCTION partners_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO partners_audit (\\"sharedById\\", \\"sharedWithId\\")\\n SELECT \\"sharedById\\", \\"sharedWithId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_assets_delete_audit', '{"type":"function","name":"assets_delete_audit","sql":"CREATE OR REPLACE FUNCTION assets_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO assets_audit (\\"assetId\\", \\"ownerId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_albums_delete_audit', '{"type":"function","name":"albums_delete_audit","sql":"CREATE OR REPLACE FUNCTION albums_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO albums_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_assets_delete_audit', '{"type":"function","name":"album_assets_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_assets_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_assets_audit (\\"albumId\\", \\"assetId\\")\\n SELECT \\"albumsId\\", \\"assetsId\\" FROM OLD\\n WHERE \\"albumsId\\" IN (SELECT \\"id\\" FROM albums WHERE \\"id\\" IN (SELECT \\"albumsId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_users_delete_audit', '{"type":"function","name":"album_users_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_users_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO albums_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumsId\\", \\"usersId\\"\\n FROM OLD;\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO album_users_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumsId\\", \\"usersId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_memories_delete_audit', '{"type":"function","name":"memories_delete_audit","sql":"CREATE OR REPLACE FUNCTION memories_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memories_audit (\\"memoryId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_memory_assets_delete_audit', '{"type":"function","name":"memory_assets_delete_audit","sql":"CREATE OR REPLACE FUNCTION memory_assets_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memory_assets_audit (\\"memoryId\\", \\"assetId\\")\\n SELECT \\"memoriesId\\", \\"assetsId\\" FROM OLD\\n WHERE \\"memoriesId\\" IN (SELECT \\"id\\" FROM memories WHERE \\"id\\" IN (SELECT \\"memoriesId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_stacks_delete_audit', '{"type":"function","name":"stacks_delete_audit","sql":"CREATE OR REPLACE FUNCTION stacks_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO stacks_audit (\\"stackId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_users_delete_audit', '{"type":"trigger","name":"users_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"users_delete_audit\\"\\n AFTER DELETE ON \\"users\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION users_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_users_updated_at', '{"type":"trigger","name":"users_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"users_updated_at\\"\\n BEFORE UPDATE ON \\"users\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_libraries_updated_at', '{"type":"trigger","name":"libraries_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"libraries_updated_at\\"\\n BEFORE UPDATE ON \\"libraries\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_stacks_delete_audit', '{"type":"trigger","name":"stacks_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"stacks_delete_audit\\"\\n AFTER DELETE ON \\"asset_stack\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION stacks_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_stacks_updated_at', '{"type":"trigger","name":"stacks_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"stacks_updated_at\\"\\n BEFORE UPDATE ON \\"asset_stack\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_assets_delete_audit', '{"type":"trigger","name":"assets_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"assets_delete_audit\\"\\n AFTER DELETE ON \\"assets\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION assets_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_assets_updated_at', '{"type":"trigger","name":"assets_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"assets_updated_at\\"\\n BEFORE UPDATE ON \\"assets\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_originalfilename_trigram', '{"type":"index","name":"idx_originalfilename_trigram","sql":"CREATE INDEX \\"idx_originalfilename_trigram\\" ON \\"assets\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_local_date_time_month', '{"type":"index","name":"idx_local_date_time_month","sql":"CREATE INDEX \\"idx_local_date_time_month\\" ON \\"assets\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text))"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_local_date_time', '{"type":"index","name":"idx_local_date_time","sql":"CREATE INDEX \\"idx_local_date_time\\" ON \\"assets\\" (((\\"localDateTime\\" at time zone ''UTC'')::date))"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_UQ_assets_owner_library_checksum', '{"type":"index","name":"UQ_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_library_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_UQ_assets_owner_checksum', '{"type":"index","name":"UQ_assets_owner_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_albums_delete_audit', '{"type":"trigger","name":"albums_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"albums_delete_audit\\"\\n AFTER DELETE ON \\"albums\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION albums_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_albums_updated_at', '{"type":"trigger","name":"albums_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"albums_updated_at\\"\\n BEFORE UPDATE ON \\"albums\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_activity_updated_at', '{"type":"trigger","name":"activity_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"activity_updated_at\\"\\n BEFORE UPDATE ON \\"activity\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_IDX_activity_like', '{"type":"index","name":"IDX_activity_like","sql":"CREATE UNIQUE INDEX \\"IDX_activity_like\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_assets_delete_audit', '{"type":"trigger","name":"album_assets_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"album_assets_delete_audit\\"\\n AFTER DELETE ON \\"albums_assets_assets\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_assets_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_assets_updated_at', '{"type":"trigger","name":"album_assets_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"album_assets_updated_at\\"\\n BEFORE UPDATE ON \\"albums_assets_assets\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_users_delete_audit', '{"type":"trigger","name":"album_users_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"album_users_delete_audit\\"\\n AFTER DELETE ON \\"albums_shared_users_users\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_users_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_user_after_insert', '{"type":"trigger","name":"album_user_after_insert","sql":"CREATE OR REPLACE TRIGGER \\"album_user_after_insert\\"\\n AFTER INSERT ON \\"albums_shared_users_users\\"\\n REFERENCING NEW TABLE AS \\"inserted_rows\\"\\n FOR EACH STATEMENT\\n EXECUTE FUNCTION album_user_after_insert();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_users_updated_at', '{"type":"trigger","name":"album_users_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"album_users_updated_at\\"\\n BEFORE UPDATE ON \\"albums_shared_users_users\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_api_keys_updated_at', '{"type":"trigger","name":"api_keys_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"api_keys_updated_at\\"\\n BEFORE UPDATE ON \\"api_keys\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_person_updated_at', '{"type":"trigger","name":"person_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"person_updated_at\\"\\n BEFORE UPDATE ON \\"person\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_files_updated_at', '{"type":"trigger","name":"asset_files_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"asset_files_updated_at\\"\\n BEFORE UPDATE ON \\"asset_files\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_exif_updated_at', '{"type":"trigger","name":"asset_exif_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"asset_exif_updated_at\\"\\n BEFORE UPDATE ON \\"exif\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_face_index', '{"type":"index","name":"face_index","sql":"CREATE INDEX \\"face_index\\" ON \\"face_search\\" USING hnsw (embedding vector_cosine_ops) WITH (ef_construction = 300, m = 16)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_IDX_geodata_gist_earthcoord', '{"type":"index","name":"IDX_geodata_gist_earthcoord","sql":"CREATE INDEX \\"IDX_geodata_gist_earthcoord\\" ON \\"geodata_places\\" (ll_to_earth_public(latitude, longitude))"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_geodata_places_name', '{"type":"index","name":"idx_geodata_places_name","sql":"CREATE INDEX \\"idx_geodata_places_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"name\\") gin_trgm_ops)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_geodata_places_admin2_name', '{"type":"index","name":"idx_geodata_places_admin2_name","sql":"CREATE INDEX \\"idx_geodata_places_admin2_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin2Name\\") gin_trgm_ops)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_geodata_places_admin1_name', '{"type":"index","name":"idx_geodata_places_admin1_name","sql":"CREATE INDEX \\"idx_geodata_places_admin1_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin1Name\\") gin_trgm_ops)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_geodata_places_alternate_names', '{"type":"index","name":"idx_geodata_places_alternate_names","sql":"CREATE INDEX \\"idx_geodata_places_alternate_names\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"alternateNames\\") gin_trgm_ops)"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memories_delete_audit', '{"type":"trigger","name":"memories_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"memories_delete_audit\\"\\n AFTER DELETE ON \\"memories\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION memories_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memories_updated_at', '{"type":"trigger","name":"memories_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"memories_updated_at\\"\\n BEFORE UPDATE ON \\"memories\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_assets_delete_audit', '{"type":"trigger","name":"memory_assets_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"memory_assets_delete_audit\\"\\n AFTER DELETE ON \\"memories_assets_assets\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION memory_assets_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_assets_updated_at', '{"type":"trigger","name":"memory_assets_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"memory_assets_updated_at\\"\\n BEFORE UPDATE ON \\"memories_assets_assets\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_notifications_updated_at', '{"type":"trigger","name":"notifications_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"notifications_updated_at\\"\\n BEFORE UPDATE ON \\"notifications\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_partners_delete_audit', '{"type":"trigger","name":"partners_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"partners_delete_audit\\"\\n AFTER DELETE ON \\"partners\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION partners_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_partners_updated_at', '{"type":"trigger","name":"partners_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"partners_updated_at\\"\\n BEFORE UPDATE ON \\"partners\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_sessions_updated_at', '{"type":"trigger","name":"sessions_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"sessions_updated_at\\"\\n BEFORE UPDATE ON \\"sessions\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_session_sync_checkpoints_updated_at', '{"type":"trigger","name":"session_sync_checkpoints_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"session_sync_checkpoints_updated_at\\"\\n BEFORE UPDATE ON \\"session_sync_checkpoints\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_tags_updated_at', '{"type":"trigger","name":"tags_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"tags_updated_at\\"\\n BEFORE UPDATE ON \\"tags\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TABLE "migration_overrides";`.execute(db); -} diff --git a/server/src/schema/migrations/1752004072340-UpdateIndexOverrides.ts b/server/src/schema/migrations/1752004072340-UpdateIndexOverrides.ts deleted file mode 100644 index 55638ab122..0000000000 --- a/server/src/schema/migrations/1752004072340-UpdateIndexOverrides.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_originalfilename_trigram","sql":"CREATE INDEX \\"idx_originalfilename_trigram\\" ON \\"assets\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_originalfilename_trigram';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_local_date_time_month","sql":"CREATE INDEX \\"idx_local_date_time_month\\" ON \\"assets\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text));"}'::jsonb WHERE "name" = 'index_idx_local_date_time_month';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_local_date_time","sql":"CREATE INDEX \\"idx_local_date_time\\" ON \\"assets\\" (((\\"localDateTime\\" at time zone ''UTC'')::date));"}'::jsonb WHERE "name" = 'index_idx_local_date_time';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"UQ_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_library_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL);"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_library_checksum';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"UQ_assets_owner_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL);"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_checksum';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"IDX_activity_like","sql":"CREATE UNIQUE INDEX \\"IDX_activity_like\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true);"}'::jsonb WHERE "name" = 'index_IDX_activity_like';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"face_index","sql":"CREATE INDEX \\"face_index\\" ON \\"face_search\\" USING hnsw (embedding vector_cosine_ops) WITH (ef_construction = 300, m = 16);"}'::jsonb WHERE "name" = 'index_face_index';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"IDX_geodata_gist_earthcoord","sql":"CREATE INDEX \\"IDX_geodata_gist_earthcoord\\" ON \\"geodata_places\\" (ll_to_earth_public(latitude, longitude));"}'::jsonb WHERE "name" = 'index_IDX_geodata_gist_earthcoord';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_name","sql":"CREATE INDEX \\"idx_geodata_places_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"name\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_name';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_admin2_name","sql":"CREATE INDEX \\"idx_geodata_places_admin2_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin2Name\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin2_name';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_admin1_name","sql":"CREATE INDEX \\"idx_geodata_places_admin1_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin1Name\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin1_name';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_alternate_names","sql":"CREATE INDEX \\"idx_geodata_places_alternate_names\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"alternateNames\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_alternate_names';`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_originalfilename_trigram\\" ON \\"assets\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops)","name":"idx_originalfilename_trigram","type":"index"}'::jsonb WHERE "name" = 'index_idx_originalfilename_trigram';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_local_date_time_month\\" ON \\"assets\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text))","name":"idx_local_date_time_month","type":"index"}'::jsonb WHERE "name" = 'index_idx_local_date_time_month';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_local_date_time\\" ON \\"assets\\" (((\\"localDateTime\\" at time zone ''UTC'')::date))","name":"idx_local_date_time","type":"index"}'::jsonb WHERE "name" = 'index_idx_local_date_time';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_library_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL)","name":"UQ_assets_owner_library_checksum","type":"index"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_library_checksum';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL)","name":"UQ_assets_owner_checksum","type":"index"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_checksum';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"IDX_activity_like\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true)","name":"IDX_activity_like","type":"index"}'::jsonb WHERE "name" = 'index_IDX_activity_like';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"face_index\\" ON \\"face_search\\" USING hnsw (embedding vector_cosine_ops) WITH (ef_construction = 300, m = 16)","name":"face_index","type":"index"}'::jsonb WHERE "name" = 'index_face_index';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"IDX_geodata_gist_earthcoord\\" ON \\"geodata_places\\" (ll_to_earth_public(latitude, longitude))","name":"IDX_geodata_gist_earthcoord","type":"index"}'::jsonb WHERE "name" = 'index_IDX_geodata_gist_earthcoord';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"name\\") gin_trgm_ops)","name":"idx_geodata_places_name","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_name';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_admin2_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin2Name\\") gin_trgm_ops)","name":"idx_geodata_places_admin2_name","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin2_name';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_admin1_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin1Name\\") gin_trgm_ops)","name":"idx_geodata_places_admin1_name","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin1_name';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_alternate_names\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"alternateNames\\") gin_trgm_ops)","name":"idx_geodata_places_alternate_names","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_alternate_names';`.execute(db); -} diff --git a/server/src/schema/migrations/1752152941084-PeopleAuditTable.ts b/server/src/schema/migrations/1752152941084-PeopleAuditTable.ts deleted file mode 100644 index 3a3da0ded5..0000000000 --- a/server/src/schema/migrations/1752152941084-PeopleAuditTable.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION person_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO person_audit ("personId", "ownerId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE TABLE "person_audit" ( - "id" uuid NOT NULL DEFAULT immich_uuid_v7(), - "personId" uuid NOT NULL, - "ownerId" uuid NOT NULL, - "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), - CONSTRAINT "PK_46c1ad23490b9312ffaa052aa59" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "IDX_person_audit_person_id" ON "person_audit" ("personId");`.execute(db); - await sql`CREATE INDEX "IDX_person_audit_owner_id" ON "person_audit" ("ownerId");`.execute(db); - await sql`CREATE INDEX "IDX_person_audit_deleted_at" ON "person_audit" ("deletedAt");`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "person_delete_audit" - AFTER DELETE ON "person" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION person_delete_audit();`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_person_delete_audit', '{"type":"function","name":"person_delete_audit","sql":"CREATE OR REPLACE FUNCTION person_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO person_audit (\\"personId\\", \\"ownerId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_person_delete_audit', '{"type":"trigger","name":"person_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"person_delete_audit\\"\\n AFTER DELETE ON \\"person\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION person_delete_audit();"}'::jsonb);`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "person_delete_audit" ON "person";`.execute(db); - await sql`DROP TABLE "person_audit";`.execute(db); - await sql`DROP FUNCTION person_delete_audit;`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_person_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_person_delete_audit';`.execute(db); -} diff --git a/server/src/schema/migrations/1752161055253-RenameGeodataPKConstraint.ts b/server/src/schema/migrations/1752161055253-RenameGeodataPKConstraint.ts deleted file mode 100644 index 086b7c1273..0000000000 --- a/server/src/schema/migrations/1752161055253-RenameGeodataPKConstraint.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql` - DO $$ - DECLARE - constraint_name text; - BEGIN - SELECT con.conname - INTO constraint_name - FROM pg_catalog.pg_constraint con - JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid - WHERE rel.relname = 'geodata_places' AND con.contype = 'p'; - - IF constraint_name IS NOT NULL THEN - EXECUTE 'ALTER TABLE "geodata_places" DROP CONSTRAINT "' || constraint_name || '"'; - END IF; - END; - $$; - `.execute(db); - await sql`ALTER TABLE "geodata_places" ADD CONSTRAINT "geodata_places_pkey" PRIMARY KEY ("id");`.execute(db); -} - -export async function down(): Promise {} diff --git a/server/src/schema/migrations/1752161055254-AddActivityAssetFk.ts b/server/src/schema/migrations/1752161055254-AddActivityAssetFk.ts deleted file mode 100644 index 938c716014..0000000000 --- a/server/src/schema/migrations/1752161055254-AddActivityAssetFk.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`DELETE FROM activity AS a - WHERE a."assetId" IS NOT NULL - AND NOT EXISTS ( - SELECT 1 - FROM albums_assets_assets AS aaa - WHERE a."albumId" = aaa."albumsId" - AND a."assetId" = aaa."assetsId" - );`.execute(db); - await sql`ALTER TABLE "activity" ADD CONSTRAINT "fk_activity_album_asset_composite" FOREIGN KEY ("albumId", "assetId") REFERENCES "albums_assets_assets" ("albumsId", "assetsId") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db); - await sql`CREATE INDEX "IDX_86102d85cfa7f196073aebff68" ON "activity" ("albumId", "assetId")`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP INDEX "IDX_86102d85cfa7f196073aebff68";`.execute(db); - await sql`ALTER TABLE "activity" DROP CONSTRAINT "fk_activity_album_asset_composite";`.execute(db); -} diff --git a/server/src/schema/migrations/1752169992364-AddIsPendingSyncReset.ts b/server/src/schema/migrations/1752169992364-AddIsPendingSyncReset.ts deleted file mode 100644 index 6264831181..0000000000 --- a/server/src/schema/migrations/1752169992364-AddIsPendingSyncReset.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" ADD "isPendingSyncReset" boolean NOT NULL DEFAULT false;`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "sessions" DROP COLUMN "isPendingSyncReset";`.execute(db); -} diff --git a/server/src/schema/migrations/1752250924342-UserMetadataSync.ts b/server/src/schema/migrations/1752250924342-UserMetadataSync.ts deleted file mode 100644 index 20778d8010..0000000000 --- a/server/src/schema/migrations/1752250924342-UserMetadataSync.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION user_metadata_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO user_metadata_audit ("userId", "key") - SELECT "userId", "key" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE TABLE "user_metadata_audit" ( - "id" uuid NOT NULL DEFAULT immich_uuid_v7(), - "userId" uuid NOT NULL, - "key" character varying NOT NULL, - "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), - CONSTRAINT "PK_15d5cc4d65ac966233b9921acac" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "IDX_user_metadata_audit_user_id" ON "user_metadata_audit" ("userId");`.execute(db); - await sql`CREATE INDEX "IDX_user_metadata_audit_key" ON "user_metadata_audit" ("key");`.execute(db); - await sql`CREATE INDEX "IDX_user_metadata_audit_deleted_at" ON "user_metadata_audit" ("deletedAt");`.execute(db); - await sql`ALTER TABLE "user_metadata" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`ALTER TABLE "user_metadata" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`CREATE INDEX "IDX_user_metadata_update_id" ON "user_metadata" ("updateId");`.execute(db); - await sql`CREATE INDEX "IDX_user_metadata_updated_at" ON "user_metadata" ("updatedAt");`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "user_metadata_audit" - AFTER DELETE ON "user_metadata" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION user_metadata_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "user_metadata_updated_at" - BEFORE UPDATE ON "user_metadata" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_user_metadata_audit', '{"type":"function","name":"user_metadata_audit","sql":"CREATE OR REPLACE FUNCTION user_metadata_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO user_metadata_audit (\\"userId\\", \\"key\\")\\n SELECT \\"userId\\", \\"key\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_user_metadata_audit', '{"type":"trigger","name":"user_metadata_audit","sql":"CREATE OR REPLACE TRIGGER \\"user_metadata_audit\\"\\n AFTER DELETE ON \\"user_metadata\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION user_metadata_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_user_metadata_updated_at', '{"type":"trigger","name":"user_metadata_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"user_metadata_updated_at\\"\\n BEFORE UPDATE ON \\"user_metadata\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "user_metadata_audit" ON "user_metadata";`.execute(db); - await sql`DROP TRIGGER "user_metadata_updated_at" ON "user_metadata";`.execute(db); - await sql`DROP INDEX "IDX_user_metadata_update_id";`.execute(db); - await sql`DROP INDEX "IDX_user_metadata_updated_at";`.execute(db); - await sql`ALTER TABLE "user_metadata" DROP COLUMN "updateId";`.execute(db); - await sql`ALTER TABLE "user_metadata" DROP COLUMN "updatedAt";`.execute(db); - await sql`DROP TABLE "user_metadata_audit";`.execute(db); - await sql`DROP FUNCTION user_metadata_audit;`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_user_metadata_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_user_metadata_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_user_metadata_updated_at';`.execute(db); -} diff --git a/server/src/schema/migrations/1752267649968-StandardizeNames.ts b/server/src/schema/migrations/1752267649968-StandardizeNames.ts deleted file mode 100644 index 991c9d3ce2..0000000000 --- a/server/src/schema/migrations/1752267649968-StandardizeNames.ts +++ /dev/null @@ -1,1200 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -type RenameItem = { oldName: string; newName: string }; - -const tables: RenameItem[] = [ - { oldName: 'album_assets_audit', newName: 'album_asset_audit' }, - { oldName: 'albums_assets_assets', newName: 'album_asset' }, - { oldName: 'albums_audit', newName: 'album_audit' }, - { oldName: 'album_users_audit', newName: 'album_user_audit' }, - { oldName: 'albums_shared_users_users', newName: 'album_user' }, - { oldName: 'albums', newName: 'album' }, - { oldName: 'api_keys', newName: 'api_key' }, - { oldName: 'assets_audit', newName: 'asset_audit' }, - { oldName: 'assets', newName: 'asset' }, - { oldName: 'asset_faces', newName: 'asset_face' }, - { oldName: 'asset_files', newName: 'asset_file' }, - { oldName: 'exif', newName: 'asset_exif' }, - { oldName: 'libraries', newName: 'library' }, - { oldName: 'memory_assets_audit', newName: 'memory_asset_audit' }, - { oldName: 'memories_assets_assets', newName: 'memory_asset' }, - { oldName: 'memories_audit', newName: 'memory_audit' }, - { oldName: 'memories', newName: 'memory' }, - { oldName: 'notifications', newName: 'notification' }, - { oldName: 'partners_audit', newName: 'partner_audit' }, - { oldName: 'partners', newName: 'partner' }, - { oldName: 'sessions', newName: 'session' }, - { oldName: 'shared_link__asset', newName: 'shared_link_asset' }, - { oldName: 'shared_links', newName: 'shared_link' }, - { oldName: 'stacks_audit', newName: 'stack_audit' }, - { oldName: 'asset_stack', newName: 'stack' }, - { oldName: 'session_sync_checkpoints', newName: 'session_sync_checkpoint' }, - { oldName: 'tags_closure', newName: 'tag_closure' }, - { oldName: 'tags', newName: 'tag' }, - { oldName: 'users_audit', newName: 'user_audit' }, - { oldName: 'users', newName: 'user' }, -]; - -export async function up(db: Kysely): Promise { - for (const { oldName, newName } of tables) { - await sql.raw(`ALTER TABLE "${oldName}" RENAME TO "${newName}"`).execute(db); - } - - await sql`CREATE OR REPLACE FUNCTION album_user_after_insert() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumsId" FROM inserted_rows); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION user_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO user_audit ("userId") - SELECT "id" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION partner_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO partner_audit ("sharedById", "sharedWithId") - SELECT "sharedById", "sharedWithId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION asset_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO asset_audit ("assetId", "ownerId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION album_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO album_audit ("albumId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION album_asset_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO album_asset_audit ("albumId", "assetId") - SELECT "albumsId", "assetsId" FROM OLD - WHERE "albumsId" IN (SELECT "id" FROM album WHERE "id" IN (SELECT "albumsId" FROM OLD)); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION album_user_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO album_audit ("albumId", "userId") - SELECT "albumsId", "usersId" - FROM OLD; - - IF pg_trigger_depth() = 1 THEN - INSERT INTO album_user_audit ("albumId", "userId") - SELECT "albumsId", "usersId" - FROM OLD; - END IF; - - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION memory_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO memory_audit ("memoryId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION memory_asset_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO memory_asset_audit ("memoryId", "assetId") - SELECT "memoriesId", "assetsId" FROM OLD - WHERE "memoriesId" IN (SELECT "id" FROM memory WHERE "id" IN (SELECT "memoriesId" FROM OLD)); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION stack_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO stack_audit ("stackId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`DROP TRIGGER "users_delete_audit" ON "user";`.execute(db); - await sql`DROP TRIGGER "users_updated_at" ON "user";`.execute(db); - await sql`DROP TRIGGER "libraries_updated_at" ON "library";`.execute(db); - await sql`DROP TRIGGER "stacks_delete_audit" ON "stack";`.execute(db); - await sql`DROP TRIGGER "stacks_updated_at" ON "stack";`.execute(db); - await sql`DROP TRIGGER "assets_delete_audit" ON "asset";`.execute(db); - await sql`DROP TRIGGER "assets_updated_at" ON "asset";`.execute(db); - await sql`DROP TRIGGER "albums_updated_at" ON "album";`.execute(db); - await sql`DROP TRIGGER "albums_delete_audit" ON "album";`.execute(db); - await sql`DROP TRIGGER "album_assets_updated_at" ON "album_asset";`.execute(db); - await sql`DROP TRIGGER "album_assets_delete_audit" ON "album_asset";`.execute(db); - await sql`DROP TRIGGER "activity_updated_at" ON "activity";`.execute(db); - await sql`DROP TRIGGER "album_users_delete_audit" ON "album_user";`.execute(db); - await sql`DROP TRIGGER "album_users_updated_at" ON "album_user";`.execute(db); - await sql`DROP TRIGGER "api_keys_updated_at" ON "api_key";`.execute(db); - await sql`DROP TRIGGER "asset_exif_updated_at" ON "asset_exif";`.execute(db); - await sql`DROP TRIGGER "person_updated_at" ON "person";`.execute(db); - await sql`DROP TRIGGER "asset_files_updated_at" ON "asset_file";`.execute(db); - await sql`DROP TRIGGER "memories_updated_at" ON "memory";`.execute(db); - await sql`DROP TRIGGER "memories_delete_audit" ON "memory";`.execute(db); - await sql`DROP TRIGGER "memory_assets_updated_at" ON "memory_asset";`.execute(db); - await sql`DROP TRIGGER "memory_assets_delete_audit" ON "memory_asset";`.execute(db); - await sql`DROP TRIGGER "notifications_updated_at" ON "notification";`.execute(db); - await sql`DROP TRIGGER "partners_delete_audit" ON "partner";`.execute(db); - await sql`DROP TRIGGER "partners_updated_at" ON "partner";`.execute(db); - await sql`DROP TRIGGER "sessions_updated_at" ON "session";`.execute(db); - await sql`DROP TRIGGER "session_sync_checkpoints_updated_at" ON "session_sync_checkpoint";`.execute(db); - await sql`DROP TRIGGER "tags_updated_at" ON "tag";`.execute(db); - await sql`ALTER TABLE "user_metadata_audit" RENAME CONSTRAINT "PK_15d5cc4d65ac966233b9921acac" TO "user_metadata_audit_pkey";`.execute(db); - await sql`ALTER TABLE "user" RENAME CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" TO "user_pkey";`.execute(db); - await sql`ALTER TABLE "user" RENAME CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" TO "user_email_uq";`.execute(db); - await sql`ALTER TABLE "user" RENAME CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a" TO "user_storageLabel_uq";`.execute(db); - await sql`ALTER TABLE "library" RENAME CONSTRAINT "PK_505fedfcad00a09b3734b4223de" TO "library_pkey";`.execute(db); - await sql`ALTER TABLE "library" RENAME CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" TO "library_ownerId_fkey";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "PK_74a27e7fcbd5852463d0af3034b" TO "stack_pkey";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "FK_91704e101438fd0653f582426dc" TO "stack_primaryAssetId_fkey";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" TO "stack_ownerId_fkey";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "REL_91704e101438fd0653f582426d" TO "stack_primaryAssetId_uq";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "PK_da96729a8b113377cfb6a62439c" TO "asset_pkey";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" TO "asset_ownerId_fkey";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" TO "asset_livePhotoVideoId_fkey";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" TO "asset_libraryId_fkey";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" TO "asset_stackId_fkey";`.execute(db); - await sql`ALTER TABLE "album" RENAME CONSTRAINT "PK_7f71c7b5bc7c87b8f94c9a93a00" TO "album_pkey";`.execute(db); - await sql`ALTER TABLE "album" RENAME CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" TO "album_ownerId_fkey";`.execute(db); - await sql`ALTER TABLE "album" RENAME CONSTRAINT "FK_05895aa505a670300d4816debce" TO "album_albumThumbnailAssetId_fkey";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" TO "album_asset_pkey";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" TO "album_asset_albumsId_fkey";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" TO "album_asset_assetsId_fkey";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "PK_24625a1d6b1b089c8ae206fe467" TO "activity_pkey";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "FK_1af8519996fbfb3684b58df280b" TO "activity_albumId_fkey";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" TO "activity_userId_fkey";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "FK_8091ea76b12338cb4428d33d782" TO "activity_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" TO "activity_like_check";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "fk_activity_album_asset_composite" TO "activity_albumId_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "album_asset_audit" RENAME CONSTRAINT "PK_32969b576ec8f78d84f37c2eb2d" TO "album_asset_audit_pkey";`.execute(db); - await sql`ALTER TABLE "album_asset_audit" RENAME CONSTRAINT "FK_8047b44b812619a3c75a2839b0d" TO "album_asset_audit_albumId_fkey";`.execute(db); - await sql`ALTER TABLE "album_audit" RENAME CONSTRAINT "PK_c75efea8d4dce316ad29b851a8b" TO "album_audit_pkey";`.execute(db); - await sql`ALTER TABLE "album_user_audit" RENAME CONSTRAINT "PK_f479a2e575b7ebc9698362c1688" TO "album_user_audit_pkey";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" TO "album_user_pkey";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "FK_427c350ad49bd3935a50baab737" TO "album_user_albumsId_fkey";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" TO "album_user_usersId_fkey";`.execute(db); - await sql`ALTER TABLE "api_key" RENAME CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" TO "api_key_pkey";`.execute(db); - await sql`ALTER TABLE "api_key" RENAME CONSTRAINT "FK_6c2e267ae764a9413b863a29342" TO "api_key_userId_fkey";`.execute(db); - await sql`ALTER TABLE "asset_audit" RENAME CONSTRAINT "PK_99bd5c015f81a641927a32b4212" TO "asset_audit_pkey";`.execute(db); - await sql`ALTER TABLE "asset_exif" RENAME CONSTRAINT "PK_c0117fdbc50b917ef9067740c44" TO "asset_exif_pkey";`.execute(db); - await sql`ALTER TABLE "asset_exif" RENAME CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" TO "asset_exif_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "PK_5fdaf670315c4b7e70cce85daa3" TO "person_pkey";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "FK_5527cc99f530a547093f9e577b6" TO "person_ownerId_fkey";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "FK_2bbabe31656b6778c6b87b61023" TO "person_faceAssetId_fkey";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" TO "person_birthDate_chk";`.execute(db); - await sql`ALTER TABLE "asset_face" RENAME CONSTRAINT "PK_6df76ab2eb6f5b57b7c2f1fc684" TO "asset_face_pkey";`.execute(db); - await sql`ALTER TABLE "asset_face" RENAME CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" TO "asset_face_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "asset_face" RENAME CONSTRAINT "FK_95ad7106dd7b484275443f580f9" TO "asset_face_personId_fkey";`.execute(db); - await sql`ALTER TABLE "asset_file" RENAME CONSTRAINT "PK_c41dc3e9ef5e1c57ca5a08a0004" TO "asset_file_pkey";`.execute(db); - await sql`ALTER TABLE "asset_file" RENAME CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" TO "asset_file_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "asset_file" RENAME CONSTRAINT "UQ_assetId_type" TO "asset_file_assetId_type_uq";`.execute(db); - await sql`ALTER TABLE "asset_job_status" RENAME CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" TO "asset_job_status_pkey";`.execute(db); - await sql`ALTER TABLE "asset_job_status" RENAME CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" TO "asset_job_status_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "audit" RENAME CONSTRAINT "PK_1d3d120ddaf7bc9b1ed68ed463a" TO "audit_pkey";`.execute(db); - await sql`ALTER TABLE "memory" RENAME CONSTRAINT "PK_aaa0692d9496fe827b0568612f8" TO "memory_pkey";`.execute(db); - await sql`ALTER TABLE "memory" RENAME CONSTRAINT "FK_575842846f0c28fa5da46c99b19" TO "memory_ownerId_fkey";`.execute(db); - await sql`ALTER TABLE "memory_asset_audit" RENAME CONSTRAINT "PK_35ef16910228f980e0766dcc59b" TO "memory_asset_audit_pkey";`.execute(db); - await sql`ALTER TABLE "memory_asset_audit" RENAME CONSTRAINT "FK_225a204afcb0bd6de015080fb03" TO "memory_asset_audit_memoryId_fkey";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" TO "memory_asset_pkey";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" TO "memory_asset_memoriesId_fkey";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" TO "memory_asset_assetsId_fkey";`.execute(db); - await sql`ALTER TABLE "memory_audit" RENAME CONSTRAINT "PK_19de798c033a710dcfa5c72f81b" TO "memory_audit_pkey";`.execute(db); - await sql`ALTER TABLE "move_history" RENAME CONSTRAINT "PK_af608f132233acf123f2949678d" TO "move_history_pkey";`.execute(db); - await sql`ALTER TABLE "notification" RENAME CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" TO "notification_pkey";`.execute(db); - await sql`ALTER TABLE "notification" RENAME CONSTRAINT "FK_692a909ee0fa9383e7859f9b406" TO "notification_userId_fkey";`.execute(db); - await sql`ALTER TABLE "partner_audit" RENAME CONSTRAINT "PK_952b50217ff78198a7e380f0359" TO "partner_audit_pkey";`.execute(db); - await sql`ALTER TABLE "partner" RENAME CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" TO "partner_pkey";`.execute(db); - await sql`ALTER TABLE "partner" RENAME CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" TO "partner_sharedById_fkey";`.execute(db); - await sql`ALTER TABLE "partner" RENAME CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" TO "partner_sharedWithId_fkey";`.execute(db); - await sql`ALTER TABLE "person_audit" RENAME CONSTRAINT "PK_46c1ad23490b9312ffaa052aa59" TO "person_audit_pkey";`.execute(db); - await sql`ALTER TABLE "session" RENAME CONSTRAINT "PK_48cb6b5c20faa63157b3c1baf7f" TO "session_pkey";`.execute(db); - await sql`ALTER TABLE "session" RENAME CONSTRAINT "FK_57de40bc620f456c7311aa3a1e6" TO "session_userId_fkey";`.execute(db); - await sql`ALTER TABLE "session" RENAME CONSTRAINT "FK_afbbabbd7daf5b91de4dca84de8" TO "session_parentId_fkey";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "PK_642e2b0f619e4876e5f90a43465" TO "shared_link_pkey";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" TO "shared_link_userId_fkey";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" TO "shared_link_albumId_fkey";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "UQ_sharedlink_key" TO "shared_link_key_uq";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" TO "shared_link_asset_pkey";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" TO "shared_link_asset_assetsId_fkey";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" TO "shared_link_asset_sharedLinksId_fkey";`.execute(db); - await sql`ALTER TABLE "stack_audit" RENAME CONSTRAINT "PK_dbe4ec648fa032e8973297de07e" TO "stack_audit_pkey";`.execute(db); - await sql`ALTER TABLE "session_sync_checkpoint" RENAME CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" TO "session_sync_checkpoint_pkey";`.execute(db); - await sql`ALTER TABLE "session_sync_checkpoint" RENAME CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" TO "session_sync_checkpoint_sessionId_fkey";`.execute(db); - await sql`ALTER TABLE "system_metadata" RENAME CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" TO "system_metadata_pkey";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "PK_e7dc17249a1148a1970748eda99" TO "tag_pkey";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "FK_92e67dc508c705dd66c94615576" TO "tag_userId_fkey";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" TO "tag_parentId_fkey";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "UQ_79d6f16e52bb2c7130375246793" TO "tag_userId_value_uq";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" TO "tag_asset_pkey";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" TO "tag_asset_assetsId_fkey";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" TO "tag_asset_tagsId_fkey";`.execute(db); - await sql`ALTER TABLE "tag_closure" RENAME CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" TO "tag_closure_pkey";`.execute(db); - await sql`ALTER TABLE "tag_closure" RENAME CONSTRAINT "FK_15fbcbc67663c6bfc07b354c22c" TO "tag_closure_id_ancestor_fkey";`.execute(db); - await sql`ALTER TABLE "tag_closure" RENAME CONSTRAINT "FK_b1a2a7ed45c29179b5ad51548a1" TO "tag_closure_id_descendant_fkey";`.execute(db); - await sql`ALTER TABLE "user_audit" RENAME CONSTRAINT "PK_e9b2bdfd90e7eb5961091175180" TO "user_audit_pkey";`.execute(db); - await sql`ALTER TABLE "user_metadata" RENAME CONSTRAINT "PK_5931462150b3438cbc83277fe5a" TO "user_metadata_pkey";`.execute(db); - await sql`ALTER TABLE "user_metadata" RENAME CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" TO "user_metadata_userId_fkey";`.execute(db); - await sql`ALTER TABLE "version_history" RENAME CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" TO "version_history_pkey";`.execute(db); - await sql`ALTER INDEX "IDX_users_updated_at_asc_id_asc" RENAME TO "user_updatedAt_id_idx";`.execute(db); - await sql`ALTER INDEX "IDX_users_update_id" RENAME TO "user_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_0f6fc2fb195f24d19b0fb0d57c" RENAME TO "library_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_libraries_update_id" RENAME TO "library_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_c05079e542fd74de3b5ecb5c1c" RENAME TO "stack_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_91704e101438fd0653f582426d" RENAME TO "stack_primaryAssetId_idx";`.execute(db); - await sql`ALTER INDEX "idx_local_date_time" RENAME TO "asset_localDateTime_idx";`.execute(db); - await sql`ALTER INDEX "IDX_9977c3c1de01c3d848039a6b90" RENAME TO "asset_libraryId_idx";`.execute(db); - await sql`ALTER INDEX "UQ_assets_owner_library_checksum" RENAME TO "asset_ownerId_libraryId_checksum_idx";`.execute(db); - await sql`ALTER INDEX "IDX_asset_id_stackId" RENAME TO "asset_id_stackId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_f15d48fa3ea5e4bda05ca8ab20" RENAME TO "asset_stackId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_originalPath_libraryId" RENAME TO "asset_originalPath_libraryId_idx";`.execute(db); - await sql`ALTER INDEX "idx_local_date_time_month" RENAME TO "asset_localDateTime_month_idx";`.execute(db); - await sql`ALTER INDEX "IDX_4d66e76dada1ca180f67a205dc" RENAME TO "asset_originalFileName_idx";`.execute(db); - await sql`ALTER INDEX "idx_originalfilename_trigram" RENAME TO "asset_originalFilename_trigram_idx";`.execute(db); - await sql`ALTER INDEX "IDX_16294b83fa8c0149719a1f631e" RENAME TO "asset_livePhotoVideoId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_2c5ac0d6fb58b238fd2068de67" RENAME TO "asset_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "idx_asset_file_created_at" RENAME TO "asset_fileCreatedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_8d3efe36c0755849395e6ea866" RENAME TO "asset_checksum_idx";`.execute(db); - await sql`ALTER INDEX "IDX_assets_duplicateId" RENAME TO "asset_duplicateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_assets_update_id" RENAME TO "asset_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_albums_update_id" RENAME TO "album_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_b22c53f35ef20c28c21637c85f" RENAME TO "album_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_05895aa505a670300d4816debc" RENAME TO "album_albumThumbnailAssetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_assets_update_id" RENAME TO "album_asset_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_e590fa396c6898fcd4a50e4092" RENAME TO "album_asset_albumsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_4bd1303d199f4e72ccdf998c62" RENAME TO "album_asset_assetsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_activity_like" RENAME TO "activity_like_idx";`.execute(db); - await sql`ALTER INDEX "IDX_86102d85cfa7f196073aebff68" RENAME TO "activity_albumId_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_3571467bcbe021f66e2bdce96e" RENAME TO "activity_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_1af8519996fbfb3684b58df280" RENAME TO "activity_albumId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_8091ea76b12338cb4428d33d78" RENAME TO "activity_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_activity_update_id" RENAME TO "activity_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_assets_audit_album_id" RENAME TO "album_asset_audit_albumId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_assets_audit_deleted_at" RENAME TO "album_asset_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_assets_audit_asset_id" RENAME TO "album_asset_audit_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_albums_audit_deleted_at" RENAME TO "album_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_albums_audit_album_id" RENAME TO "album_audit_albumId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_albums_audit_user_id" RENAME TO "album_audit_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_users_audit_user_id" RENAME TO "album_user_audit_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_users_audit_album_id" RENAME TO "album_user_audit_albumId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_users_audit_deleted_at" RENAME TO "album_user_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_users_update_id" RENAME TO "album_user_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_album_users_create_id" RENAME TO "album_user_createId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_f48513bf9bccefd6ff3ad30bd0" RENAME TO "album_user_usersId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_427c350ad49bd3935a50baab73" RENAME TO "album_user_albumsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_6c2e267ae764a9413b863a2934" RENAME TO "api_key_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_api_keys_update_id" RENAME TO "api_key_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_assets_audit_asset_id" RENAME TO "asset_audit_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_assets_audit_deleted_at" RENAME TO "asset_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_assets_audit_owner_id" RENAME TO "asset_audit_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_asset_exif_update_id" RENAME TO "asset_exif_updateId_idx";`.execute(db); - await sql`ALTER INDEX "exif_city" RENAME TO "asset_exif_city_idx";`.execute(db); - await sql`ALTER INDEX "IDX_auto_stack_id" RENAME TO "asset_exif_autoStackId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_live_photo_cid" RENAME TO "asset_exif_livePhotoCID_idx";`.execute(db); - await sql`ALTER INDEX "IDX_person_update_id" RENAME TO "person_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_5527cc99f530a547093f9e577b" RENAME TO "person_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_2bbabe31656b6778c6b87b6102" RENAME TO "person_faceAssetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_bf339a24070dac7e71304ec530" RENAME TO "asset_face_personId_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_asset_faces_assetId_personId" RENAME TO "asset_face_assetId_personId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_asset_files_assetId" RENAME TO "asset_file_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_asset_files_update_id" RENAME TO "asset_file_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_ownerId_createdAt" RENAME TO "audit_ownerId_createdAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memories_update_id" RENAME TO "memory_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_575842846f0c28fa5da46c99b1" RENAME TO "memory_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memory_assets_audit_memory_id" RENAME TO "memory_asset_audit_memoryId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memory_assets_audit_asset_id" RENAME TO "memory_asset_audit_assetId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memory_assets_audit_deleted_at" RENAME TO "memory_asset_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_6942ecf52d75d4273de19d2c16" RENAME TO "memory_asset_assetsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memory_assets_update_id" RENAME TO "memory_asset_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_984e5c9ab1f04d34538cd32334" RENAME TO "memory_asset_memoriesId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memories_audit_deleted_at" RENAME TO "memory_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memories_audit_user_id" RENAME TO "memory_audit_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_memories_audit_memory_id" RENAME TO "memory_audit_memoryId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_notifications_update_id" RENAME TO "notification_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_692a909ee0fa9383e7859f9b40" RENAME TO "notification_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_partners_audit_shared_with_id" RENAME TO "partner_audit_sharedWithId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_partners_audit_deleted_at" RENAME TO "partner_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_partners_audit_shared_by_id" RENAME TO "partner_audit_sharedById_idx";`.execute(db); - await sql`ALTER INDEX "IDX_d7e875c6c60e661723dbf372fd" RENAME TO "partner_sharedWithId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_partners_create_id" RENAME TO "partner_createId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_partners_update_id" RENAME TO "partner_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_person_audit_owner_id" RENAME TO "person_audit_ownerId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_person_audit_deleted_at" RENAME TO "person_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_person_audit_person_id" RENAME TO "person_audit_personId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_sessions_update_id" RENAME TO "session_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_57de40bc620f456c7311aa3a1e" RENAME TO "session_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_afbbabbd7daf5b91de4dca84de" RENAME TO "session_parentId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_sharedlink_key" RENAME TO "shared_link_key_idx";`.execute(db); - await sql`ALTER INDEX "IDX_sharedlink_albumId" RENAME TO "shared_link_albumId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_66fe3837414c5a9f1c33ca4934" RENAME TO "shared_link_userId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_c9fab4aa97ffd1b034f3d6581a" RENAME TO "shared_link_asset_sharedLinksId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_5b7decce6c8d3db9593d6111a6" RENAME TO "shared_link_asset_assetsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_stacks_audit_deleted_at" RENAME TO "stack_audit_deletedAt_idx";`.execute(db); - await sql`ALTER INDEX "IDX_d8ddd9d687816cc490432b3d4b" RENAME TO "session_sync_checkpoint_sessionId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_session_sync_checkpoints_update_id" RENAME TO "session_sync_checkpoint_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_9f9590cc11561f1f48ff034ef9" RENAME TO "tag_parentId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_tags_update_id" RENAME TO "tag_updateId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_f8e8a9e893cb5c54907f1b798e" RENAME TO "tag_asset_assetsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4" RENAME TO "tag_asset_tagsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_tag_asset_assetsId_tagsId" RENAME TO "tag_asset_assetsId_tagsId_idx";`.execute(db); - await sql`ALTER INDEX "IDX_15fbcbc67663c6bfc07b354c22" RENAME TO "tag_closure_id_ancestor_idx";`.execute(db); - await sql`ALTER INDEX "IDX_b1a2a7ed45c29179b5ad51548a" RENAME TO "tag_closure_id_descendant_idx";`.execute(db); - await sql`ALTER INDEX "IDX_users_audit_deleted_at" RENAME TO "user_audit_deletedAt_idx";`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "user_delete_audit" - AFTER DELETE ON "user" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION user_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "user_updatedAt" - BEFORE UPDATE ON "user" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "library_updatedAt" - BEFORE UPDATE ON "library" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "stack_delete_audit" - AFTER DELETE ON "stack" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION stack_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "stack_updatedAt" - BEFORE UPDATE ON "stack" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_delete_audit" - AFTER DELETE ON "asset" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION asset_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_updatedAt" - BEFORE UPDATE ON "asset" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_delete_audit" - AFTER DELETE ON "album" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION album_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_updatedAt" - BEFORE UPDATE ON "album" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_asset_delete_audit" - AFTER DELETE ON "album_asset" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() <= 1) - EXECUTE FUNCTION album_asset_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_asset_updatedAt" - BEFORE UPDATE ON "album_asset" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "activity_updatedAt" - BEFORE UPDATE ON "activity" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_user_delete_audit" - AFTER DELETE ON "album_user" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() <= 1) - EXECUTE FUNCTION album_user_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_user_updatedAt" - BEFORE UPDATE ON "album_user" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "api_key_updatedAt" - BEFORE UPDATE ON "api_key" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_exif_updatedAt" - BEFORE UPDATE ON "asset_exif" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "person_updatedAt" - BEFORE UPDATE ON "person" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_file_updatedAt" - BEFORE UPDATE ON "asset_file" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_delete_audit" - AFTER DELETE ON "memory" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION memory_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_updatedAt" - BEFORE UPDATE ON "memory" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_asset_delete_audit" - AFTER DELETE ON "memory_asset" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() <= 1) - EXECUTE FUNCTION memory_asset_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_asset_updatedAt" - BEFORE UPDATE ON "memory_asset" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "notification_updatedAt" - BEFORE UPDATE ON "notification" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "partner_delete_audit" - AFTER DELETE ON "partner" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION partner_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "partner_updatedAt" - BEFORE UPDATE ON "partner" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "session_updatedAt" - BEFORE UPDATE ON "session" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "session_sync_checkpoint_updatedAt" - BEFORE UPDATE ON "session_sync_checkpoint" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "tag_updatedAt" - BEFORE UPDATE ON "tag" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`DROP FUNCTION users_delete_audit;`.execute(db); - await sql`DROP FUNCTION partners_delete_audit;`.execute(db); - await sql`DROP FUNCTION assets_delete_audit;`.execute(db); - await sql`DROP FUNCTION albums_delete_audit;`.execute(db); - await sql`DROP FUNCTION album_users_delete_audit;`.execute(db); - await sql`DROP FUNCTION album_assets_delete_audit;`.execute(db); - await sql`DROP FUNCTION memories_delete_audit;`.execute(db); - await sql`DROP FUNCTION memory_assets_delete_audit;`.execute(db); - await sql`DROP FUNCTION stacks_delete_audit;`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_user_delete_audit', '{"type":"function","name":"user_delete_audit","sql":"CREATE OR REPLACE FUNCTION user_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO user_audit (\\"userId\\")\\n SELECT \\"id\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_partner_delete_audit', '{"type":"function","name":"partner_delete_audit","sql":"CREATE OR REPLACE FUNCTION partner_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO partner_audit (\\"sharedById\\", \\"sharedWithId\\")\\n SELECT \\"sharedById\\", \\"sharedWithId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_delete_audit', '{"type":"function","name":"asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO asset_audit (\\"assetId\\", \\"ownerId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_delete_audit', '{"type":"function","name":"album_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_asset_delete_audit', '{"type":"function","name":"album_asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_asset_audit (\\"albumId\\", \\"assetId\\")\\n SELECT \\"albumsId\\", \\"assetsId\\" FROM OLD\\n WHERE \\"albumsId\\" IN (SELECT \\"id\\" FROM album WHERE \\"id\\" IN (SELECT \\"albumsId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_user_delete_audit', '{"type":"function","name":"album_user_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_user_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumsId\\", \\"usersId\\"\\n FROM OLD;\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO album_user_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumsId\\", \\"usersId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_memory_delete_audit', '{"type":"function","name":"memory_delete_audit","sql":"CREATE OR REPLACE FUNCTION memory_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memory_audit (\\"memoryId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_memory_asset_delete_audit', '{"type":"function","name":"memory_asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION memory_asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memory_asset_audit (\\"memoryId\\", \\"assetId\\")\\n SELECT \\"memoriesId\\", \\"assetsId\\" FROM OLD\\n WHERE \\"memoriesId\\" IN (SELECT \\"id\\" FROM memory WHERE \\"id\\" IN (SELECT \\"memoriesId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_stack_delete_audit', '{"type":"function","name":"stack_delete_audit","sql":"CREATE OR REPLACE FUNCTION stack_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO stack_audit (\\"stackId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_user_delete_audit', '{"type":"trigger","name":"user_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"user_delete_audit\\"\\n AFTER DELETE ON \\"user\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION user_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_user_updatedAt', '{"type":"trigger","name":"user_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"user_updatedAt\\"\\n BEFORE UPDATE ON \\"user\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_library_updatedAt', '{"type":"trigger","name":"library_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"library_updatedAt\\"\\n BEFORE UPDATE ON \\"library\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_stack_delete_audit', '{"type":"trigger","name":"stack_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"stack_delete_audit\\"\\n AFTER DELETE ON \\"stack\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION stack_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_stack_updatedAt', '{"type":"trigger","name":"stack_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"stack_updatedAt\\"\\n BEFORE UPDATE ON \\"stack\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_delete_audit', '{"type":"trigger","name":"asset_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"asset_delete_audit\\"\\n AFTER DELETE ON \\"asset\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_updatedAt', '{"type":"trigger","name":"asset_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"asset_updatedAt\\"\\n BEFORE UPDATE ON \\"asset\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_asset_originalFilename_trigram_idx', '{"type":"index","name":"asset_originalFilename_trigram_idx","sql":"CREATE INDEX \\"asset_originalFilename_trigram_idx\\" ON \\"asset\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops);"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_asset_localDateTime_month_idx', '{"type":"index","name":"asset_localDateTime_month_idx","sql":"CREATE INDEX \\"asset_localDateTime_month_idx\\" ON \\"asset\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text));"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_asset_localDateTime_idx', '{"type":"index","name":"asset_localDateTime_idx","sql":"CREATE INDEX \\"asset_localDateTime_idx\\" ON \\"asset\\" (((\\"localDateTime\\" at time zone ''UTC'')::date));"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_asset_ownerId_libraryId_checksum_idx', '{"type":"index","name":"asset_ownerId_libraryId_checksum_idx","sql":"CREATE UNIQUE INDEX \\"asset_ownerId_libraryId_checksum_idx\\" ON \\"asset\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL);"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_delete_audit', '{"type":"trigger","name":"album_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"album_delete_audit\\"\\n AFTER DELETE ON \\"album\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION album_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_updatedAt', '{"type":"trigger","name":"album_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"album_updatedAt\\"\\n BEFORE UPDATE ON \\"album\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_asset_delete_audit', '{"type":"trigger","name":"album_asset_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"album_asset_delete_audit\\"\\n AFTER DELETE ON \\"album_asset\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_asset_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_asset_updatedAt', '{"type":"trigger","name":"album_asset_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"album_asset_updatedAt\\"\\n BEFORE UPDATE ON \\"album_asset\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_activity_updatedAt', '{"type":"trigger","name":"activity_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"activity_updatedAt\\"\\n BEFORE UPDATE ON \\"activity\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_activity_like_idx', '{"type":"index","name":"activity_like_idx","sql":"CREATE UNIQUE INDEX \\"activity_like_idx\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true);"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_user_delete_audit', '{"type":"trigger","name":"album_user_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"album_user_delete_audit\\"\\n AFTER DELETE ON \\"album_user\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_user_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_user_updatedAt', '{"type":"trigger","name":"album_user_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"album_user_updatedAt\\"\\n BEFORE UPDATE ON \\"album_user\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_api_key_updatedAt', '{"type":"trigger","name":"api_key_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"api_key_updatedAt\\"\\n BEFORE UPDATE ON \\"api_key\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_exif_updatedAt', '{"type":"trigger","name":"asset_exif_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"asset_exif_updatedAt\\"\\n BEFORE UPDATE ON \\"asset_exif\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_person_updatedAt', '{"type":"trigger","name":"person_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"person_updatedAt\\"\\n BEFORE UPDATE ON \\"person\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_file_updatedAt', '{"type":"trigger","name":"asset_file_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"asset_file_updatedAt\\"\\n BEFORE UPDATE ON \\"asset_file\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_delete_audit', '{"type":"trigger","name":"memory_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"memory_delete_audit\\"\\n AFTER DELETE ON \\"memory\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION memory_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_updatedAt', '{"type":"trigger","name":"memory_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"memory_updatedAt\\"\\n BEFORE UPDATE ON \\"memory\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_asset_delete_audit', '{"type":"trigger","name":"memory_asset_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"memory_asset_delete_audit\\"\\n AFTER DELETE ON \\"memory_asset\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION memory_asset_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_asset_updatedAt', '{"type":"trigger","name":"memory_asset_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"memory_asset_updatedAt\\"\\n BEFORE UPDATE ON \\"memory_asset\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_notification_updatedAt', '{"type":"trigger","name":"notification_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"notification_updatedAt\\"\\n BEFORE UPDATE ON \\"notification\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_partner_delete_audit', '{"type":"trigger","name":"partner_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"partner_delete_audit\\"\\n AFTER DELETE ON \\"partner\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION partner_delete_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_partner_updatedAt', '{"type":"trigger","name":"partner_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"partner_updatedAt\\"\\n BEFORE UPDATE ON \\"partner\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_session_updatedAt', '{"type":"trigger","name":"session_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"session_updatedAt\\"\\n BEFORE UPDATE ON \\"session\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_session_sync_checkpoint_updatedAt', '{"type":"trigger","name":"session_sync_checkpoint_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"session_sync_checkpoint_updatedAt\\"\\n BEFORE UPDATE ON \\"session_sync_checkpoint\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_tag_updatedAt', '{"type":"trigger","name":"tag_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"tag_updatedAt\\"\\n BEFORE UPDATE ON \\"tag\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_after_insert","sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumsId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"UQ_assets_owner_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"asset\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL);"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_checksum';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"trigger","name":"album_user_after_insert","sql":"CREATE OR REPLACE TRIGGER \\"album_user_after_insert\\"\\n AFTER INSERT ON \\"album_user\\"\\n REFERENCING NEW TABLE AS \\"inserted_rows\\"\\n FOR EACH STATEMENT\\n EXECUTE FUNCTION album_user_after_insert();"}'::jsonb WHERE "name" = 'trigger_album_user_after_insert';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_users_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_partners_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_assets_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_albums_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_assets_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_users_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_memories_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_memory_assets_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_stacks_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_users_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_users_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_libraries_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_stacks_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_stacks_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_assets_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_assets_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_albums_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_albums_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_activity_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_assets_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_assets_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_users_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_users_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_api_keys_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_person_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_files_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_exif_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memories_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memories_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memory_assets_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memory_assets_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_notifications_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_partners_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_partners_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_sessions_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_session_sync_checkpoints_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_tags_updated_at';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_idx_originalfilename_trigram';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_idx_local_date_time_month';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_idx_local_date_time';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_UQ_assets_owner_library_checksum';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_IDX_activity_like';`.execute(db); -} - -export async function down(db: Kysely): Promise { - for (const { oldName, newName } of tables) { - await sql.raw(`ALTER TABLE "${newName}" RENAME TO "${oldName}"`).execute(db); - } - await sql`CREATE OR REPLACE FUNCTION public.album_user_after_insert() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - UPDATE albums SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumsId" FROM inserted_rows); - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.users_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO users_audit ("userId") - SELECT "id" - FROM OLD; - RETURN NULL; - END; - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.partners_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO partners_audit ("sharedById", "sharedWithId") - SELECT "sharedById", "sharedWithId" - FROM OLD; - RETURN NULL; - END; - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.assets_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO assets_audit ("assetId", "ownerId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END; - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.albums_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO albums_audit ("albumId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.album_users_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO albums_audit ("albumId", "userId") - SELECT "albumsId", "usersId" - FROM OLD; - - IF pg_trigger_depth() = 1 THEN - INSERT INTO album_users_audit ("albumId", "userId") - SELECT "albumsId", "usersId" - FROM OLD; - END IF; - - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.album_assets_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO album_assets_audit ("albumId", "assetId") - SELECT "albumsId", "assetsId" FROM OLD - WHERE "albumsId" IN (SELECT "id" FROM albums WHERE "id" IN (SELECT "albumsId" FROM OLD)); - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.memories_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO memories_audit ("memoryId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.memory_assets_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO memory_assets_audit ("memoryId", "assetId") - SELECT "memoriesId", "assetsId" FROM OLD - WHERE "memoriesId" IN (SELECT "id" FROM memories WHERE "id" IN (SELECT "memoriesId" FROM OLD)); - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.stacks_delete_audit() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - INSERT INTO stacks_audit ("stackId", "userId") - SELECT "id", "ownerId" - FROM OLD; - RETURN NULL; - END - $function$ -`.execute(db); - await sql`DROP TRIGGER "activity_updatedAt" ON "activity";`.execute(db); - await sql`DROP TRIGGER "person_updatedAt" ON "person";`.execute(db); - await sql`DROP TRIGGER "album_asset_delete_audit" ON "album_asset";`.execute(db); - await sql`DROP TRIGGER "album_asset_updatedAt" ON "album_asset";`.execute(db); - await sql`DROP TRIGGER "album_user_delete_audit" ON "album_user";`.execute(db); - await sql`DROP TRIGGER "album_user_updatedAt" ON "album_user";`.execute(db); - await sql`DROP TRIGGER "album_delete_audit" ON "album";`.execute(db); - await sql`DROP TRIGGER "album_updatedAt" ON "album";`.execute(db); - await sql`DROP TRIGGER "api_key_updatedAt" ON "api_key";`.execute(db); - await sql`DROP TRIGGER "asset_delete_audit" ON "asset";`.execute(db); - await sql`DROP TRIGGER "asset_updatedAt" ON "asset";`.execute(db); - await sql`DROP TRIGGER "asset_file_updatedAt" ON "asset_file";`.execute(db); - await sql`DROP TRIGGER "asset_exif_updatedAt" ON "asset_exif";`.execute(db); - await sql`DROP TRIGGER "library_updatedAt" ON "library";`.execute(db); - await sql`DROP TRIGGER "memory_asset_delete_audit" ON "memory_asset";`.execute(db); - await sql`DROP TRIGGER "memory_asset_updatedAt" ON "memory_asset";`.execute(db); - await sql`DROP TRIGGER "memory_delete_audit" ON "memory";`.execute(db); - await sql`DROP TRIGGER "memory_updatedAt" ON "memory";`.execute(db); - await sql`DROP TRIGGER "notification_updatedAt" ON "notification";`.execute(db); - await sql`DROP TRIGGER "partner_delete_audit" ON "partner";`.execute(db); - await sql`DROP TRIGGER "partner_updatedAt" ON "partner";`.execute(db); - await sql`DROP TRIGGER "session_updatedAt" ON "session";`.execute(db); - await sql`DROP TRIGGER "stack_delete_audit" ON "stack";`.execute(db); - await sql`DROP TRIGGER "stack_updatedAt" ON "stack";`.execute(db); - await sql`DROP TRIGGER "session_sync_checkpoint_updatedAt" ON "session_sync_checkpoint";`.execute(db); - await sql`DROP TRIGGER "tag_updatedAt" ON "tag";`.execute(db); - await sql`DROP TRIGGER "user_delete_audit" ON "user";`.execute(db); - await sql`DROP TRIGGER "user_updatedAt" ON "user";`.execute(db); - await sql`ALTER TABLE "asset_audit" RENAME CONSTRAINT "asset_audit_pkey" TO "PK_99bd5c015f81a641927a32b4212";`.execute(db); - await sql`ALTER TABLE "audit" RENAME CONSTRAINT "audit_pkey" TO "PK_1d3d120ddaf7bc9b1ed68ed463a";`.execute(db); - await sql`ALTER TABLE "move_history" RENAME CONSTRAINT "move_history_pkey" TO "PK_af608f132233acf123f2949678d";`.execute(db); - await sql`ALTER TABLE "partner_audit" RENAME CONSTRAINT "partner_audit_pkey" TO "PK_952b50217ff78198a7e380f0359";`.execute(db); - await sql`ALTER TABLE "user_audit" RENAME CONSTRAINT "user_audit_pkey" TO "PK_e9b2bdfd90e7eb5961091175180";`.execute(db); - await sql`ALTER TABLE "system_metadata" RENAME CONSTRAINT "system_metadata_pkey" TO "PK_fa94f6857470fb5b81ec6084465";`.execute(db); - await sql`ALTER TABLE "version_history" RENAME CONSTRAINT "version_history_pkey" TO "PK_5db259cbb09ce82c0d13cfd1b23";`.execute(db); - await sql`ALTER TABLE "asset_job_status" RENAME CONSTRAINT "asset_job_status_assetId_fkey" TO "FK_420bec36fc02813bddf5c8b73d4";`.execute(db); - await sql`ALTER TABLE "asset_job_status" RENAME CONSTRAINT "asset_job_status_pkey" TO "PK_420bec36fc02813bddf5c8b73d4";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_assetsId_fkey" TO "FK_f8e8a9e893cb5c54907f1b798e9";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_tagsId_fkey" TO "FK_e99f31ea4cdf3a2c35c7287eb42";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_pkey" TO "PK_ef5346fe522b5fb3bc96454747e";`.execute(db); - await sql`ALTER TABLE "user_metadata" RENAME CONSTRAINT "user_metadata_userId_fkey" TO "FK_6afb43681a21cf7815932bc38ac";`.execute(db); - await sql`ALTER TABLE "user_metadata" RENAME CONSTRAINT "user_metadata_pkey" TO "PK_5931462150b3438cbc83277fe5a";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "activity_albumId_fkey" TO "FK_1af8519996fbfb3684b58df280b";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "activity_userId_fkey" TO "FK_3571467bcbe021f66e2bdce96ea";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "activity_assetId_fkey" TO "FK_8091ea76b12338cb4428d33d782";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "activity_albumId_assetId_fkey" TO "fk_activity_album_asset_composite";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "activity_like_check" TO "CHK_2ab1e70f113f450eb40c1e3ec8";`.execute(db); - await sql`ALTER TABLE "activity" RENAME CONSTRAINT "activity_pkey" TO "PK_24625a1d6b1b089c8ae206fe467";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "person_ownerId_fkey" TO "FK_5527cc99f530a547093f9e577b6";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "person_faceAssetId_fkey" TO "FK_2bbabe31656b6778c6b87b61023";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "person_birthDate_chk" TO "CHK_b0f82b0ed662bfc24fbb58bb45";`.execute(db); - await sql`ALTER TABLE "person" RENAME CONSTRAINT "person_pkey" TO "PK_5fdaf670315c4b7e70cce85daa3";`.execute(db); - await sql`ALTER TABLE "person_audit" RENAME CONSTRAINT "person_audit_pkey" TO "PK_46c1ad23490b9312ffaa052aa59";`.execute(db); - await sql`ALTER TABLE "album_asset_audit" RENAME CONSTRAINT "album_asset_audit_albumId_fkey" TO "FK_8047b44b812619a3c75a2839b0d";`.execute(db); - await sql`ALTER TABLE "album_asset_audit" RENAME CONSTRAINT "album_asset_audit_pkey" TO "PK_32969b576ec8f78d84f37c2eb2d";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_albumsId_fkey" TO "FK_e590fa396c6898fcd4a50e40927";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_assetsId_fkey" TO "FK_4bd1303d199f4e72ccdf998c621";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_pkey" TO "PK_c67bc36fa845fb7b18e0e398180";`.execute(db); - await sql`ALTER TABLE "album_audit" RENAME CONSTRAINT "album_audit_pkey" TO "PK_c75efea8d4dce316ad29b851a8b";`.execute(db); - await sql`ALTER TABLE "album_user_audit" RENAME CONSTRAINT "album_user_audit_pkey" TO "PK_f479a2e575b7ebc9698362c1688";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_albumsId_fkey" TO "FK_427c350ad49bd3935a50baab737";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_usersId_fkey" TO "FK_f48513bf9bccefd6ff3ad30bd06";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_pkey" TO "PK_7df55657e0b2e8b626330a0ebc8";`.execute(db); - await sql`ALTER TABLE "album" RENAME CONSTRAINT "album_ownerId_fkey" TO "FK_b22c53f35ef20c28c21637c85f4";`.execute(db); - await sql`ALTER TABLE "album" RENAME CONSTRAINT "album_albumThumbnailAssetId_fkey" TO "FK_05895aa505a670300d4816debce";`.execute(db); - await sql`ALTER TABLE "album" RENAME CONSTRAINT "album_pkey" TO "PK_7f71c7b5bc7c87b8f94c9a93a00";`.execute(db); - await sql`ALTER TABLE "api_key" RENAME CONSTRAINT "api_key_userId_fkey" TO "FK_6c2e267ae764a9413b863a29342";`.execute(db); - await sql`ALTER TABLE "api_key" RENAME CONSTRAINT "api_key_pkey" TO "PK_5c8a79801b44bd27b79228e1dad";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "asset_ownerId_fkey" TO "FK_2c5ac0d6fb58b238fd2068de67d";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "asset_livePhotoVideoId_fkey" TO "FK_16294b83fa8c0149719a1f631ef";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "asset_libraryId_fkey" TO "FK_9977c3c1de01c3d848039a6b90c";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "asset_stackId_fkey" TO "FK_f15d48fa3ea5e4bda05ca8ab207";`.execute(db); - await sql`ALTER TABLE "asset" RENAME CONSTRAINT "asset_pkey" TO "PK_da96729a8b113377cfb6a62439c";`.execute(db); - await sql`ALTER TABLE "asset_face" RENAME CONSTRAINT "asset_face_assetId_fkey" TO "FK_02a43fd0b3c50fb6d7f0cb7282c";`.execute(db); - await sql`ALTER TABLE "asset_face" RENAME CONSTRAINT "asset_face_personId_fkey" TO "FK_95ad7106dd7b484275443f580f9";`.execute(db); - await sql`ALTER TABLE "asset_face" RENAME CONSTRAINT "asset_face_pkey" TO "PK_6df76ab2eb6f5b57b7c2f1fc684";`.execute(db); - await sql`ALTER TABLE "asset_file" RENAME CONSTRAINT "asset_file_assetId_fkey" TO "FK_e3e103a5f1d8bc8402999286040";`.execute(db); - await sql`ALTER TABLE "asset_file" RENAME CONSTRAINT "asset_file_assetId_type_uq" TO "UQ_assetId_type";`.execute(db); - await sql`ALTER TABLE "asset_file" RENAME CONSTRAINT "asset_file_pkey" TO "PK_c41dc3e9ef5e1c57ca5a08a0004";`.execute(db); - await sql`ALTER TABLE "asset_exif" RENAME CONSTRAINT "asset_exif_assetId_fkey" TO "FK_c0117fdbc50b917ef9067740c44";`.execute(db); - await sql`ALTER TABLE "asset_exif" RENAME CONSTRAINT "asset_exif_pkey" TO "PK_c0117fdbc50b917ef9067740c44";`.execute(db); - await sql`ALTER TABLE "library" RENAME CONSTRAINT "library_ownerId_fkey" TO "FK_0f6fc2fb195f24d19b0fb0d57c1";`.execute(db); - await sql`ALTER TABLE "library" RENAME CONSTRAINT "library_pkey" TO "PK_505fedfcad00a09b3734b4223de";`.execute(db); - await sql`ALTER TABLE "memory_asset_audit" RENAME CONSTRAINT "memory_asset_audit_memoryId_fkey" TO "FK_225a204afcb0bd6de015080fb03";`.execute(db); - await sql`ALTER TABLE "memory_asset_audit" RENAME CONSTRAINT "memory_asset_audit_pkey" TO "PK_35ef16910228f980e0766dcc59b";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "memory_asset_memoriesId_fkey" TO "FK_984e5c9ab1f04d34538cd32334e";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "memory_asset_assetsId_fkey" TO "FK_6942ecf52d75d4273de19d2c16f";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "memory_asset_pkey" TO "PK_fcaf7112a013d1703c011c6793d";`.execute(db); - await sql`ALTER TABLE "memory_audit" RENAME CONSTRAINT "memory_audit_pkey" TO "PK_19de798c033a710dcfa5c72f81b";`.execute(db); - await sql`ALTER TABLE "memory" RENAME CONSTRAINT "memory_ownerId_fkey" TO "FK_575842846f0c28fa5da46c99b19";`.execute(db); - await sql`ALTER TABLE "memory" RENAME CONSTRAINT "memory_pkey" TO "PK_aaa0692d9496fe827b0568612f8";`.execute(db); - await sql`ALTER TABLE "notification" RENAME CONSTRAINT "notification_userId_fkey" TO "FK_692a909ee0fa9383e7859f9b406";`.execute(db); - await sql`ALTER TABLE "notification" RENAME CONSTRAINT "notification_pkey" TO "PK_6a72c3c0f683f6462415e653c3a";`.execute(db); - await sql`ALTER TABLE "partner" RENAME CONSTRAINT "partner_sharedById_fkey" TO "FK_7e077a8b70b3530138610ff5e04";`.execute(db); - await sql`ALTER TABLE "partner" RENAME CONSTRAINT "partner_sharedWithId_fkey" TO "FK_d7e875c6c60e661723dbf372fd3";`.execute(db); - await sql`ALTER TABLE "partner" RENAME CONSTRAINT "partner_pkey" TO "PK_f1cc8f73d16b367f426261a8736";`.execute(db); - await sql`ALTER TABLE "session" RENAME CONSTRAINT "session_userId_fkey" TO "FK_57de40bc620f456c7311aa3a1e6";`.execute(db); - await sql`ALTER TABLE "session" RENAME CONSTRAINT "session_parentId_fkey" TO "FK_afbbabbd7daf5b91de4dca84de8";`.execute(db); - await sql`ALTER TABLE "session" RENAME CONSTRAINT "session_pkey" TO "PK_48cb6b5c20faa63157b3c1baf7f";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_assetsId_fkey" TO "FK_5b7decce6c8d3db9593d6111a66";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_sharedLinksId_fkey" TO "FK_c9fab4aa97ffd1b034f3d6581ab";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_pkey" TO "PK_9b4f3687f9b31d1e311336b05e3";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "shared_link_userId_fkey" TO "FK_66fe3837414c5a9f1c33ca49340";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "shared_link_albumId_fkey" TO "FK_0c6ce9058c29f07cdf7014eac66";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "shared_link_key_uq" TO "UQ_sharedlink_key";`.execute(db); - await sql`ALTER TABLE "shared_link" RENAME CONSTRAINT "shared_link_pkey" TO "PK_642e2b0f619e4876e5f90a43465";`.execute(db); - await sql`ALTER TABLE "stack_audit" RENAME CONSTRAINT "stack_audit_pkey" TO "PK_dbe4ec648fa032e8973297de07e";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "stack_primaryAssetId_fkey" TO "FK_91704e101438fd0653f582426dc";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "stack_primaryAssetId_uq" TO "REL_91704e101438fd0653f582426d";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "stack_ownerId_fkey" TO "FK_c05079e542fd74de3b5ecb5c1c8";`.execute(db); - await sql`ALTER TABLE "stack" RENAME CONSTRAINT "stack_pkey" TO "PK_74a27e7fcbd5852463d0af3034b";`.execute(db); - await sql`ALTER TABLE "session_sync_checkpoint" RENAME CONSTRAINT "session_sync_checkpoint_sessionId_fkey" TO "FK_d8ddd9d687816cc490432b3d4bc";`.execute(db); - await sql`ALTER TABLE "session_sync_checkpoint" RENAME CONSTRAINT "session_sync_checkpoint_pkey" TO "PK_b846ab547a702863ef7cd9412fb";`.execute(db); - await sql`ALTER TABLE "tag_closure" RENAME CONSTRAINT "tag_closure_id_ancestor_fkey" TO "FK_15fbcbc67663c6bfc07b354c22c";`.execute(db); - await sql`ALTER TABLE "tag_closure" RENAME CONSTRAINT "tag_closure_id_descendant_fkey" TO "FK_b1a2a7ed45c29179b5ad51548a1";`.execute(db); - await sql`ALTER TABLE "tag_closure" RENAME CONSTRAINT "tag_closure_pkey" TO "PK_eab38eb12a3ec6df8376c95477c";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "tag_userId_fkey" TO "FK_92e67dc508c705dd66c94615576";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "tag_parentId_fkey" TO "FK_9f9590cc11561f1f48ff034ef99";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "tag_userId_value_uq" TO "UQ_79d6f16e52bb2c7130375246793";`.execute(db); - await sql`ALTER TABLE "tag" RENAME CONSTRAINT "tag_pkey" TO "PK_e7dc17249a1148a1970748eda99";`.execute(db); - await sql`ALTER TABLE "user" RENAME CONSTRAINT "user_email_uq" TO "UQ_97672ac88f789774dd47f7c8be3";`.execute(db); - await sql`ALTER TABLE "user" RENAME CONSTRAINT "user_storageLabel_uq" TO "UQ_b309cf34fa58137c416b32cea3a";`.execute(db); - await sql`ALTER TABLE "user" RENAME CONSTRAINT "user_pkey" TO "PK_a3ffb1c0c8416b9fc6f907b7433";`.execute(db); - await sql`ALTER TABLE "user_metadata_audit" RENAME CONSTRAINT "user_metadata_audit_pkey" TO "PK_15d5cc4d65ac966233b9921acac";`.execute(db); - await sql`ALTER INDEX "asset_audit_assetId_idx" RENAME TO "IDX_assets_audit_asset_id";`.execute(db); - await sql`ALTER INDEX "asset_audit_ownerId_idx" RENAME TO "IDX_assets_audit_owner_id";`.execute(db); - await sql`ALTER INDEX "asset_audit_deletedAt_idx" RENAME TO "IDX_assets_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "audit_ownerId_createdAt_idx" RENAME TO "IDX_ownerId_createdAt";`.execute(db); - await sql`ALTER INDEX "partner_audit_sharedById_idx" RENAME TO "IDX_partners_audit_shared_by_id";`.execute(db); - await sql`ALTER INDEX "partner_audit_sharedWithId_idx" RENAME TO "IDX_partners_audit_shared_with_id";`.execute(db); - await sql`ALTER INDEX "partner_audit_deletedAt_idx" RENAME TO "IDX_partners_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "user_audit_deletedAt_idx" RENAME TO "IDX_users_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "tag_asset_assetsId_tagsId_idx" RENAME TO "IDX_tag_asset_assetsId_tagsId";`.execute(db); - await sql`ALTER INDEX "tag_asset_assetsId_idx" RENAME TO "IDX_f8e8a9e893cb5c54907f1b798e";`.execute(db); - await sql`ALTER INDEX "tag_asset_tagsId_idx" RENAME TO "IDX_e99f31ea4cdf3a2c35c7287eb4";`.execute(db); - await sql`ALTER INDEX "activity_albumId_assetId_idx" RENAME TO "IDX_86102d85cfa7f196073aebff68";`.execute(db); - await sql`ALTER INDEX "activity_like_idx" RENAME TO "IDX_activity_like";`.execute(db); - await sql`ALTER INDEX "activity_albumId_idx" RENAME TO "IDX_1af8519996fbfb3684b58df280";`.execute(db); - await sql`ALTER INDEX "activity_userId_idx" RENAME TO "IDX_3571467bcbe021f66e2bdce96e";`.execute(db); - await sql`ALTER INDEX "activity_assetId_idx" RENAME TO "IDX_8091ea76b12338cb4428d33d78";`.execute(db); - await sql`ALTER INDEX "activity_updateId_idx" RENAME TO "IDX_activity_update_id";`.execute(db); - await sql`ALTER INDEX "person_ownerId_idx" RENAME TO "IDX_5527cc99f530a547093f9e577b";`.execute(db); - await sql`ALTER INDEX "person_faceAssetId_idx" RENAME TO "IDX_2bbabe31656b6778c6b87b6102";`.execute(db); - await sql`ALTER INDEX "person_updateId_idx" RENAME TO "IDX_person_update_id";`.execute(db); - await sql`ALTER INDEX "person_audit_personId_idx" RENAME TO "IDX_person_audit_person_id";`.execute(db); - await sql`ALTER INDEX "person_audit_ownerId_idx" RENAME TO "IDX_person_audit_owner_id";`.execute(db); - await sql`ALTER INDEX "person_audit_deletedAt_idx" RENAME TO "IDX_person_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "album_asset_audit_albumId_idx" RENAME TO "IDX_album_assets_audit_album_id";`.execute(db); - await sql`ALTER INDEX "album_asset_audit_assetId_idx" RENAME TO "IDX_album_assets_audit_asset_id";`.execute(db); - await sql`ALTER INDEX "album_asset_audit_deletedAt_idx" RENAME TO "IDX_album_assets_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "album_asset_albumsId_idx" RENAME TO "IDX_e590fa396c6898fcd4a50e4092";`.execute(db); - await sql`ALTER INDEX "album_asset_assetsId_idx" RENAME TO "IDX_4bd1303d199f4e72ccdf998c62";`.execute(db); - await sql`ALTER INDEX "album_asset_updateId_idx" RENAME TO "IDX_album_assets_update_id";`.execute(db); - await sql`ALTER INDEX "album_audit_albumId_idx" RENAME TO "IDX_albums_audit_album_id";`.execute(db); - await sql`ALTER INDEX "album_audit_userId_idx" RENAME TO "IDX_albums_audit_user_id";`.execute(db); - await sql`ALTER INDEX "album_audit_deletedAt_idx" RENAME TO "IDX_albums_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "album_user_audit_albumId_idx" RENAME TO "IDX_album_users_audit_album_id";`.execute(db); - await sql`ALTER INDEX "album_user_audit_userId_idx" RENAME TO "IDX_album_users_audit_user_id";`.execute(db); - await sql`ALTER INDEX "album_user_audit_deletedAt_idx" RENAME TO "IDX_album_users_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "album_user_albumsId_idx" RENAME TO "IDX_427c350ad49bd3935a50baab73";`.execute(db); - await sql`ALTER INDEX "album_user_usersId_idx" RENAME TO "IDX_f48513bf9bccefd6ff3ad30bd0";`.execute(db); - await sql`ALTER INDEX "album_user_createId_idx" RENAME TO "IDX_album_users_create_id";`.execute(db); - await sql`ALTER INDEX "album_user_updateId_idx" RENAME TO "IDX_album_users_update_id";`.execute(db); - await sql`ALTER INDEX "album_ownerId_idx" RENAME TO "IDX_b22c53f35ef20c28c21637c85f";`.execute(db); - await sql`ALTER INDEX "album_albumThumbnailAssetId_idx" RENAME TO "IDX_05895aa505a670300d4816debc";`.execute(db); - await sql`ALTER INDEX "album_updateId_idx" RENAME TO "IDX_albums_update_id";`.execute(db); - await sql`ALTER INDEX "api_key_userId_idx" RENAME TO "IDX_6c2e267ae764a9413b863a2934";`.execute(db); - await sql`ALTER INDEX "api_key_updateId_idx" RENAME TO "IDX_api_keys_update_id";`.execute(db); - await sql`ALTER INDEX "asset_originalFilename_trigram_idx" RENAME TO "idx_originalfilename_trigram";`.execute(db); - await sql`ALTER INDEX "asset_id_stackId_idx" RENAME TO "IDX_asset_id_stackId";`.execute(db); - await sql`ALTER INDEX "asset_originalPath_libraryId_idx" RENAME TO "IDX_originalPath_libraryId";`.execute(db); - await sql`ALTER INDEX "asset_localDateTime_month_idx" RENAME TO "idx_local_date_time_month";`.execute(db); - await sql`ALTER INDEX "asset_localDateTime_idx" RENAME TO "idx_local_date_time";`.execute(db); - await sql`ALTER INDEX "asset_ownerId_libraryId_checksum_idx" RENAME TO "UQ_assets_owner_library_checksum";`.execute(db); - await sql`ALTER INDEX "asset_ownerId_idx" RENAME TO "IDX_2c5ac0d6fb58b238fd2068de67";`.execute(db); - await sql`ALTER INDEX "asset_fileCreatedAt_idx" RENAME TO "idx_asset_file_created_at";`.execute(db); - await sql`ALTER INDEX "asset_checksum_idx" RENAME TO "IDX_8d3efe36c0755849395e6ea866";`.execute(db); - await sql`ALTER INDEX "asset_livePhotoVideoId_idx" RENAME TO "IDX_16294b83fa8c0149719a1f631e";`.execute(db); - await sql`ALTER INDEX "asset_originalFileName_idx" RENAME TO "IDX_4d66e76dada1ca180f67a205dc";`.execute(db); - await sql`ALTER INDEX "asset_libraryId_idx" RENAME TO "IDX_9977c3c1de01c3d848039a6b90";`.execute(db); - await sql`ALTER INDEX "asset_stackId_idx" RENAME TO "IDX_f15d48fa3ea5e4bda05ca8ab20";`.execute(db); - await sql`ALTER INDEX "asset_duplicateId_idx" RENAME TO "IDX_assets_duplicateId";`.execute(db); - await sql`ALTER INDEX "asset_updateId_idx" RENAME TO "IDX_assets_update_id";`.execute(db); - await sql`ALTER INDEX "asset_face_assetId_personId_idx" RENAME TO "IDX_asset_faces_assetId_personId";`.execute(db); - await sql`ALTER INDEX "asset_file_assetId_idx" RENAME TO "IDX_asset_files_assetId";`.execute(db); - await sql`ALTER INDEX "asset_file_updateId_idx" RENAME TO "IDX_asset_files_update_id";`.execute(db); - await sql`ALTER INDEX "asset_exif_city_idx" RENAME TO "exif_city";`.execute(db); - await sql`ALTER INDEX "asset_exif_livePhotoCID_idx" RENAME TO "IDX_live_photo_cid";`.execute(db); - await sql`ALTER INDEX "asset_exif_autoStackId_idx" RENAME TO "IDX_auto_stack_id";`.execute(db); - await sql`ALTER INDEX "asset_exif_updateId_idx" RENAME TO "IDX_asset_exif_update_id";`.execute(db); - await sql`ALTER INDEX "library_ownerId_idx" RENAME TO "IDX_0f6fc2fb195f24d19b0fb0d57c";`.execute(db); - await sql`ALTER INDEX "library_updateId_idx" RENAME TO "IDX_libraries_update_id";`.execute(db); - await sql`ALTER INDEX "memory_asset_audit_memoryId_idx" RENAME TO "IDX_memory_assets_audit_memory_id";`.execute(db); - await sql`ALTER INDEX "memory_asset_audit_assetId_idx" RENAME TO "IDX_memory_assets_audit_asset_id";`.execute(db); - await sql`ALTER INDEX "memory_asset_audit_deletedAt_idx" RENAME TO "IDX_memory_assets_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "memory_asset_memoriesId_idx" RENAME TO "IDX_984e5c9ab1f04d34538cd32334";`.execute(db); - await sql`ALTER INDEX "memory_asset_assetsId_idx" RENAME TO "IDX_6942ecf52d75d4273de19d2c16";`.execute(db); - await sql`ALTER INDEX "memory_asset_updateId_idx" RENAME TO "IDX_memory_assets_update_id";`.execute(db); - await sql`ALTER INDEX "memory_audit_memoryId_idx" RENAME TO "IDX_memories_audit_memory_id";`.execute(db); - await sql`ALTER INDEX "memory_audit_userId_idx" RENAME TO "IDX_memories_audit_user_id";`.execute(db); - await sql`ALTER INDEX "memory_audit_deletedAt_idx" RENAME TO "IDX_memories_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "memory_ownerId_idx" RENAME TO "IDX_575842846f0c28fa5da46c99b1";`.execute(db); - await sql`ALTER INDEX "memory_updateId_idx" RENAME TO "IDX_memories_update_id";`.execute(db); - await sql`ALTER INDEX "notification_updateId_idx" RENAME TO "IDX_notifications_update_id";`.execute(db); - await sql`ALTER INDEX "notification_userId_idx" RENAME TO "IDX_692a909ee0fa9383e7859f9b40";`.execute(db); - await sql`ALTER INDEX "partner_sharedWithId_idx" RENAME TO "IDX_d7e875c6c60e661723dbf372fd";`.execute(db); - await sql`ALTER INDEX "partner_createId_idx" RENAME TO "IDX_partners_create_id";`.execute(db); - await sql`ALTER INDEX "partner_updateId_idx" RENAME TO "IDX_partners_update_id";`.execute(db); - await sql`ALTER INDEX "session_userId_idx" RENAME TO "IDX_57de40bc620f456c7311aa3a1e";`.execute(db); - await sql`ALTER INDEX "session_parentId_idx" RENAME TO "IDX_afbbabbd7daf5b91de4dca84de";`.execute(db); - await sql`ALTER INDEX "session_updateId_idx" RENAME TO "IDX_sessions_update_id";`.execute(db); - await sql`ALTER INDEX "shared_link_asset_assetsId_idx" RENAME TO "IDX_5b7decce6c8d3db9593d6111a6";`.execute(db); - await sql`ALTER INDEX "shared_link_asset_sharedLinksId_idx" RENAME TO "IDX_c9fab4aa97ffd1b034f3d6581a";`.execute(db); - await sql`ALTER INDEX "shared_link_userId_idx" RENAME TO "IDX_66fe3837414c5a9f1c33ca4934";`.execute(db); - await sql`ALTER INDEX "shared_link_key_idx" RENAME TO "IDX_sharedlink_key";`.execute(db); - await sql`ALTER INDEX "shared_link_albumId_idx" RENAME TO "IDX_sharedlink_albumId";`.execute(db); - await sql`ALTER INDEX "stack_audit_deletedAt_idx" RENAME TO "IDX_stacks_audit_deleted_at";`.execute(db); - await sql`ALTER INDEX "stack_primaryAssetId_idx" RENAME TO "IDX_91704e101438fd0653f582426d";`.execute(db); - await sql`ALTER INDEX "stack_ownerId_idx" RENAME TO "IDX_c05079e542fd74de3b5ecb5c1c";`.execute(db); - await sql`ALTER INDEX "session_sync_checkpoint_sessionId_idx" RENAME TO "IDX_d8ddd9d687816cc490432b3d4b";`.execute(db); - await sql`ALTER INDEX "session_sync_checkpoint_updateId_idx" RENAME TO "IDX_session_sync_checkpoints_update_id";`.execute(db); - await sql`ALTER INDEX "tag_closure_id_ancestor_idx" RENAME TO "IDX_15fbcbc67663c6bfc07b354c22";`.execute(db); - await sql`ALTER INDEX "tag_closure_id_descendant_idx" RENAME TO "IDX_b1a2a7ed45c29179b5ad51548a";`.execute(db); - await sql`ALTER INDEX "tag_parentId_idx" RENAME TO "IDX_9f9590cc11561f1f48ff034ef9";`.execute(db); - await sql`ALTER INDEX "tag_updateId_idx" RENAME TO "IDX_tags_update_id";`.execute(db); - await sql`ALTER INDEX "user_updateId_idx" RENAME TO "IDX_users_update_id";`.execute(db); - await sql`ALTER INDEX "user_updatedAt_id_idx" RENAME TO "IDX_users_updated_at_asc_id_asc";`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "activity_updated_at" - BEFORE UPDATE ON "activity" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "person_updated_at" - BEFORE UPDATE ON "person" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_assets_updated_at" - BEFORE UPDATE ON "album_asset" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_assets_delete_audit" - AFTER DELETE ON "album_asset" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() <= 1)) - EXECUTE FUNCTION album_assets_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_users_delete_audit" - AFTER DELETE ON "album_user" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() <= 1)) - EXECUTE FUNCTION album_users_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "album_users_updated_at" - BEFORE UPDATE ON "album_user" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "albums_updated_at" - BEFORE UPDATE ON "album" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "albums_delete_audit" - AFTER DELETE ON "album" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION albums_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "api_keys_updated_at" - BEFORE UPDATE ON "api_key" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "assets_delete_audit" - AFTER DELETE ON "asset" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION assets_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "assets_updated_at" - BEFORE UPDATE ON "asset" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_files_updated_at" - BEFORE UPDATE ON "asset_file" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_exif_updated_at" - BEFORE UPDATE ON "asset_exif" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "libraries_updated_at" - BEFORE UPDATE ON "library" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_assets_updated_at" - BEFORE UPDATE ON "memory_asset" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memory_assets_delete_audit" - AFTER DELETE ON "memory_asset" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() <= 1)) - EXECUTE FUNCTION memory_assets_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memories_updated_at" - BEFORE UPDATE ON "memory" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "memories_delete_audit" - AFTER DELETE ON "memory" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION memories_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "notifications_updated_at" - BEFORE UPDATE ON "notification" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "partners_delete_audit" - AFTER DELETE ON "partner" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION partners_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "partners_updated_at" - BEFORE UPDATE ON "partner" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "sessions_updated_at" - BEFORE UPDATE ON "session" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "stacks_delete_audit" - AFTER DELETE ON "stack" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION stacks_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "stacks_updated_at" - BEFORE UPDATE ON "stack" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "session_sync_checkpoints_updated_at" - BEFORE UPDATE ON "session_sync_checkpoint" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "tags_updated_at" - BEFORE UPDATE ON "tag" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "users_delete_audit" - AFTER DELETE ON "user" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION users_delete_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "users_updated_at" - BEFORE UPDATE ON "user" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`DROP FUNCTION user_delete_audit;`.execute(db); - await sql`DROP FUNCTION partner_delete_audit;`.execute(db); - await sql`DROP FUNCTION asset_delete_audit;`.execute(db); - await sql`DROP FUNCTION album_delete_audit;`.execute(db); - await sql`DROP FUNCTION album_asset_delete_audit;`.execute(db); - await sql`DROP FUNCTION album_user_delete_audit;`.execute(db); - await sql`DROP FUNCTION memory_delete_audit;`.execute(db); - await sql`DROP FUNCTION memory_asset_delete_audit;`.execute(db); - await sql`DROP FUNCTION stack_delete_audit;`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_users_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION users_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO users_audit (\\"userId\\")\\n SELECT \\"id\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"users_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_partners_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION partners_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO partners_audit (\\"sharedById\\", \\"sharedWithId\\")\\n SELECT \\"sharedById\\", \\"sharedWithId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"partners_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_assets_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION assets_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO assets_audit (\\"assetId\\", \\"ownerId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"assets_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_albums_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION albums_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO albums_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"albums_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_assets_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION album_assets_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_assets_audit (\\"albumId\\", \\"assetId\\")\\n SELECT \\"albumsId\\", \\"assetsId\\" FROM OLD\\n WHERE \\"albumsId\\" IN (SELECT \\"id\\" FROM albums WHERE \\"id\\" IN (SELECT \\"albumsId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;","name":"album_assets_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_album_users_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION album_users_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO albums_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumsId\\", \\"usersId\\"\\n FROM OLD;\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO album_users_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumsId\\", \\"usersId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;","name":"album_users_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_memories_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION memories_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memories_audit (\\"memoryId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"memories_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_memory_assets_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION memory_assets_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memory_assets_audit (\\"memoryId\\", \\"assetId\\")\\n SELECT \\"memoriesId\\", \\"assetsId\\" FROM OLD\\n WHERE \\"memoriesId\\" IN (SELECT \\"id\\" FROM memories WHERE \\"id\\" IN (SELECT \\"memoriesId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;","name":"memory_assets_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_stacks_delete_audit', '{"sql":"CREATE OR REPLACE FUNCTION stacks_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO stacks_audit (\\"stackId\\", \\"userId\\")\\n SELECT \\"id\\", \\"ownerId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;","name":"stacks_delete_audit","type":"function"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_users_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"users_delete_audit\\"\\n AFTER DELETE ON \\"users\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION users_delete_audit();","name":"users_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_users_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"users_updated_at\\"\\n BEFORE UPDATE ON \\"users\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"users_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_libraries_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"libraries_updated_at\\"\\n BEFORE UPDATE ON \\"libraries\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"libraries_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_stacks_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"stacks_delete_audit\\"\\n AFTER DELETE ON \\"asset_stack\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION stacks_delete_audit();","name":"stacks_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_stacks_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"stacks_updated_at\\"\\n BEFORE UPDATE ON \\"asset_stack\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"stacks_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_assets_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"assets_delete_audit\\"\\n AFTER DELETE ON \\"assets\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION assets_delete_audit();","name":"assets_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_assets_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"assets_updated_at\\"\\n BEFORE UPDATE ON \\"assets\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"assets_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_albums_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"albums_delete_audit\\"\\n AFTER DELETE ON \\"albums\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION albums_delete_audit();","name":"albums_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_albums_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"albums_updated_at\\"\\n BEFORE UPDATE ON \\"albums\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"albums_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_activity_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"activity_updated_at\\"\\n BEFORE UPDATE ON \\"activity\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"activity_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_assets_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"album_assets_delete_audit\\"\\n AFTER DELETE ON \\"albums_assets_assets\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_assets_delete_audit();","name":"album_assets_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_assets_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"album_assets_updated_at\\"\\n BEFORE UPDATE ON \\"albums_assets_assets\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"album_assets_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_users_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"album_users_delete_audit\\"\\n AFTER DELETE ON \\"albums_shared_users_users\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION album_users_delete_audit();","name":"album_users_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_album_users_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"album_users_updated_at\\"\\n BEFORE UPDATE ON \\"albums_shared_users_users\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"album_users_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_api_keys_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"api_keys_updated_at\\"\\n BEFORE UPDATE ON \\"api_keys\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"api_keys_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_person_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"person_updated_at\\"\\n BEFORE UPDATE ON \\"person\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"person_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_files_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"asset_files_updated_at\\"\\n BEFORE UPDATE ON \\"asset_files\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"asset_files_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_exif_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"asset_exif_updated_at\\"\\n BEFORE UPDATE ON \\"exif\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"asset_exif_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memories_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"memories_delete_audit\\"\\n AFTER DELETE ON \\"memories\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION memories_delete_audit();","name":"memories_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memories_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"memories_updated_at\\"\\n BEFORE UPDATE ON \\"memories\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"memories_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_assets_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"memory_assets_delete_audit\\"\\n AFTER DELETE ON \\"memories_assets_assets\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() <= 1)\\n EXECUTE FUNCTION memory_assets_delete_audit();","name":"memory_assets_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_memory_assets_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"memory_assets_updated_at\\"\\n BEFORE UPDATE ON \\"memories_assets_assets\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"memory_assets_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_notifications_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"notifications_updated_at\\"\\n BEFORE UPDATE ON \\"notifications\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"notifications_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_partners_delete_audit', '{"sql":"CREATE OR REPLACE TRIGGER \\"partners_delete_audit\\"\\n AFTER DELETE ON \\"partners\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION partners_delete_audit();","name":"partners_delete_audit","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_partners_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"partners_updated_at\\"\\n BEFORE UPDATE ON \\"partners\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"partners_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_sessions_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"sessions_updated_at\\"\\n BEFORE UPDATE ON \\"sessions\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"sessions_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_session_sync_checkpoints_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"session_sync_checkpoints_updated_at\\"\\n BEFORE UPDATE ON \\"session_sync_checkpoints\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"session_sync_checkpoints_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_tags_updated_at', '{"sql":"CREATE OR REPLACE TRIGGER \\"tags_updated_at\\"\\n BEFORE UPDATE ON \\"tags\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();","name":"tags_updated_at","type":"trigger"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_originalfilename_trigram', '{"sql":"CREATE INDEX \\"idx_originalfilename_trigram\\" ON \\"assets\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops);","name":"idx_originalfilename_trigram","type":"index"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_local_date_time_month', '{"sql":"CREATE INDEX \\"idx_local_date_time_month\\" ON \\"assets\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text));","name":"idx_local_date_time_month","type":"index"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_local_date_time', '{"sql":"CREATE INDEX \\"idx_local_date_time\\" ON \\"assets\\" (((\\"localDateTime\\" at time zone ''UTC'')::date));","name":"idx_local_date_time","type":"index"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_UQ_assets_owner_library_checksum', '{"sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_library_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL);","name":"UQ_assets_owner_library_checksum","type":"index"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_IDX_activity_like', '{"sql":"CREATE UNIQUE INDEX \\"IDX_activity_like\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true);","name":"IDX_activity_like","type":"index"}'::jsonb);`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE albums SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumsId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;","name":"album_user_after_insert","type":"function"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE TRIGGER \\"album_user_after_insert\\"\\n AFTER INSERT ON \\"albums_shared_users_users\\"\\n REFERENCING NEW TABLE AS \\"inserted_rows\\"\\n FOR EACH STATEMENT\\n EXECUTE FUNCTION album_user_after_insert();","name":"album_user_after_insert","type":"trigger"}'::jsonb WHERE "name" = 'trigger_album_user_after_insert';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL);","name":"UQ_assets_owner_checksum","type":"index"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_checksum';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_user_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_partner_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_asset_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_album_user_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_memory_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_memory_asset_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_stack_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_user_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_user_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_library_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_stack_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_stack_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_asset_originalFilename_trigram_idx';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_asset_localDateTime_month_idx';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_asset_localDateTime_idx';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_asset_ownerId_libraryId_checksum_idx';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_asset_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_asset_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_activity_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_activity_like_idx';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_user_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_album_user_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_api_key_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_exif_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_person_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_file_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memory_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memory_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memory_asset_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_memory_asset_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_notification_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_partner_delete_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_partner_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_session_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_session_sync_checkpoint_updatedAt';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_tag_updatedAt';`.execute(db); - - -} diff --git a/server/src/schema/migrations/1752759108283-ConvertToAbsolutePaths.ts b/server/src/schema/migrations/1752759108283-ConvertToAbsolutePaths.ts deleted file mode 100644 index 839af40cf5..0000000000 --- a/server/src/schema/migrations/1752759108283-ConvertToAbsolutePaths.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Kysely, sql } from 'kysely'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -const logger = LoggingRepository.create('Migrations'); - -export async function up(db: Kysely): Promise { - if (process.env.IMMICH_MEDIA_LOCATION) { - // do not automatically convert paths for a custom location/setting - return; - } - - // we construct paths using `path.join(mediaLocation, ...)`, which strips the leading './' - const source = 'upload'; - const target = '/usr/src/app/upload'; - - logger.log(`Converting database file paths from relative to absolute (source=${source}/*, target=${target}/*)`); - - // escaping regex special characters with a backslash - const sourceRegex = '^' + source.replaceAll(/[-[\]{}()*+?.,\\^$|#\s]/g, String.raw`\$&`); - - const items: Array<{ table: string; column: string }> = [ - { table: 'asset', column: 'originalPath' }, - { table: 'asset', column: 'encodedVideoPath' }, - { table: 'asset', column: 'sidecarPath' }, - { table: 'asset_file', column: 'path' }, - { table: 'person', column: 'thumbnailPath' }, - { table: 'user', column: 'profileImagePath' }, - ]; - - for (const { table, column } of items) { - const query = `UPDATE "${table}" SET "${column}" = REGEXP_REPLACE("${column}", '${sourceRegex}', '${target}') WHERE "${column}" IS NOT NULL`; - await sql.raw(query).execute(db); - } -} - -export async function down(): Promise { - // not supported -} diff --git a/server/src/schema/migrations/1753104909784-AssetFaceUpdateIdAndAuditTable.ts b/server/src/schema/migrations/1753104909784-AssetFaceUpdateIdAndAuditTable.ts deleted file mode 100644 index 1f4072e34e..0000000000 --- a/server/src/schema/migrations/1753104909784-AssetFaceUpdateIdAndAuditTable.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION asset_face_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO asset_face_audit ("assetFaceId", "assetId") - SELECT "id", "assetId" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE TABLE "asset_face_audit" ( - "id" uuid NOT NULL DEFAULT immich_uuid_v7(), - "assetFaceId" uuid NOT NULL, - "assetId" uuid NOT NULL, - "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), - CONSTRAINT "asset_face_audit_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "asset_face_audit_assetFaceId_idx" ON "asset_face_audit" ("assetFaceId");`.execute(db); - await sql`CREATE INDEX "asset_face_audit_assetId_idx" ON "asset_face_audit" ("assetId");`.execute(db); - await sql`CREATE INDEX "asset_face_audit_deletedAt_idx" ON "asset_face_audit" ("deletedAt");`.execute(db); - await sql`ALTER TABLE "asset_face" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db); - await sql`ALTER TABLE "asset_face" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_face_audit" - AFTER DELETE ON "asset_face" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION asset_face_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_face_updatedAt" - BEFORE UPDATE ON "asset_face" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_face_audit', '{"type":"function","name":"asset_face_audit","sql":"CREATE OR REPLACE FUNCTION asset_face_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO asset_face_audit (\\"assetFaceId\\", \\"assetId\\")\\n SELECT \\"id\\", \\"assetId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_audit', '{"type":"trigger","name":"asset_face_audit","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_audit\\"\\n AFTER DELETE ON \\"asset_face\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_face_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_updatedAt', '{"type":"trigger","name":"asset_face_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_updatedAt\\"\\n BEFORE UPDATE ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "asset_face_audit" ON "asset_face";`.execute(db); - await sql`DROP TRIGGER "asset_face_updatedAt" ON "asset_face";`.execute(db); - await sql`ALTER TABLE "asset_face" DROP COLUMN "updatedAt";`.execute(db); - await sql`ALTER TABLE "asset_face" DROP COLUMN "updateId";`.execute(db); - await sql`DROP TABLE "asset_face_audit";`.execute(db); - await sql`DROP FUNCTION asset_face_audit;`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_face_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_updatedAt';`.execute(db); -} diff --git a/server/src/schema/migrations/1753464178233-RenameApiKeyPermissions.ts b/server/src/schema/migrations/1753464178233-RenameApiKeyPermissions.ts deleted file mode 100644 index 0293a96607..0000000000 --- a/server/src/schema/migrations/1753464178233-RenameApiKeyPermissions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -const items = [ - { oldName: 'album.addAsset', newName: 'albumAsset.create' }, - { oldName: 'album.removeAsset', newName: 'albumAsset.delete' }, - { oldName: 'admin.user.create', newName: 'adminUser.create' }, - { oldName: 'admin.user.read', newName: 'adminUser.read' }, - { oldName: 'admin.user.update', newName: 'adminUser.update' }, - { oldName: 'admin.user.delete', newName: 'adminUser.delete' }, -]; - -export async function up(db: Kysely): Promise { - for (const { oldName, newName } of items) { - await sql`UPDATE "api_key" SET "permissions" = array_replace("permissions", ${oldName}, ${newName})`.execute(db); - } -} - -export async function down(db: Kysely): Promise { - for (const { oldName, newName } of items) { - await sql`UPDATE "api_key" SET "permissions" = array_replace("permissions", ${newName}, ${oldName})`.execute(db); - } -} diff --git a/server/src/schema/migrations/1753471866748-AddSharedLinkSlug.ts b/server/src/schema/migrations/1753471866748-AddSharedLinkSlug.ts deleted file mode 100644 index 1d77eddd07..0000000000 --- a/server/src/schema/migrations/1753471866748-AddSharedLinkSlug.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "shared_link" ADD "slug" character varying;`.execute(db); - await sql`ALTER TABLE "shared_link" ADD CONSTRAINT "shared_link_slug_uq" UNIQUE ("slug");`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "shared_link" DROP CONSTRAINT "shared_link_slug_uq";`.execute(db); - await sql`ALTER TABLE "shared_link" DROP COLUMN "slug";`.execute(db); -} diff --git a/server/src/schema/migrations/1753800911775-ProfileImageCheckpointRemoval.ts b/server/src/schema/migrations/1753800911775-ProfileImageCheckpointRemoval.ts deleted file mode 100644 index 4f741f2113..0000000000 --- a/server/src/schema/migrations/1753800911775-ProfileImageCheckpointRemoval.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`DELETE FROM session_sync_checkpoint - WHERE type IN ( - 'UserV1', - 'AssetV1', - 'PartnerAssetV1', - 'PartnerAssetBackfillV1', - 'AlbumAssetV1', - 'AlbumAssetBackfillV1' - )`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DELETE FROM session_sync_checkpoint - WHERE type IN ( - 'UserV1', - 'AssetV1', - 'PartnerAssetV1', - 'PartnerAssetBackfillV1', - 'AlbumAssetV1', - 'AlbumAssetBackfillV1' - )`.execute(db); -} diff --git a/server/src/schema/migrations/1754389095885-ResetAlbumAssetSync.ts b/server/src/schema/migrations/1754389095885-ResetAlbumAssetSync.ts deleted file mode 100644 index 34bdfbd4ab..0000000000 --- a/server/src/schema/migrations/1754389095885-ResetAlbumAssetSync.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`DELETE FROM session_sync_checkpoint WHERE type IN ('AlbumAssetBackfillV1', 'AlbumAssetExifV1', 'AlbumAssetV1')`.execute(db); -} - -export async function down(): Promise {} diff --git a/server/src/schema/migrations/1756318797207-AssetMetadataTables.ts b/server/src/schema/migrations/1756318797207-AssetMetadataTables.ts deleted file mode 100644 index ba0bad9d9a..0000000000 --- a/server/src/schema/migrations/1756318797207-AssetMetadataTables.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION asset_metadata_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO asset_metadata_audit ("assetId", "key") - SELECT "assetId", "key" - FROM OLD; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE TABLE "asset_metadata_audit" ( - "id" uuid NOT NULL DEFAULT immich_uuid_v7(), - "assetId" uuid NOT NULL, - "key" character varying NOT NULL, - "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), - CONSTRAINT "asset_metadata_audit_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "asset_metadata_audit_assetId_idx" ON "asset_metadata_audit" ("assetId");`.execute(db); - await sql`CREATE INDEX "asset_metadata_audit_key_idx" ON "asset_metadata_audit" ("key");`.execute(db); - await sql`CREATE INDEX "asset_metadata_audit_deletedAt_idx" ON "asset_metadata_audit" ("deletedAt");`.execute(db); - await sql`CREATE TABLE "asset_metadata" ( - "assetId" uuid NOT NULL, - "key" character varying NOT NULL, - "value" jsonb NOT NULL, - "updateId" uuid NOT NULL DEFAULT immich_uuid_v7(), - "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), - CONSTRAINT "asset_metadata_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "asset_metadata_pkey" PRIMARY KEY ("assetId", "key") -);`.execute(db); - await sql`CREATE INDEX "asset_metadata_updateId_idx" ON "asset_metadata" ("updateId");`.execute(db); - await sql`CREATE INDEX "asset_metadata_updatedAt_idx" ON "asset_metadata" ("updatedAt");`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_metadata_audit" - AFTER DELETE ON "asset_metadata" - REFERENCING OLD TABLE AS "old" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION asset_metadata_audit();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_metadata_updated_at" - BEFORE UPDATE ON "asset_metadata" - FOR EACH ROW - EXECUTE FUNCTION updated_at();`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_metadata_audit', '{"type":"function","name":"asset_metadata_audit","sql":"CREATE OR REPLACE FUNCTION asset_metadata_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO asset_metadata_audit (\\"assetId\\", \\"key\\")\\n SELECT \\"assetId\\", \\"key\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_metadata_audit', '{"type":"trigger","name":"asset_metadata_audit","sql":"CREATE OR REPLACE TRIGGER \\"asset_metadata_audit\\"\\n AFTER DELETE ON \\"asset_metadata\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_metadata_audit();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_metadata_updated_at', '{"type":"trigger","name":"asset_metadata_updated_at","sql":"CREATE OR REPLACE TRIGGER \\"asset_metadata_updated_at\\"\\n BEFORE UPDATE ON \\"asset_metadata\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TABLE "asset_metadata_audit";`.execute(db); - await sql`DROP TABLE "asset_metadata";`.execute(db); - await sql`DROP FUNCTION asset_metadata_audit;`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_metadata_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_metadata_audit';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_metadata_updated_at';`.execute(db); -} diff --git a/server/src/schema/migrations/1758705774125-CreateAssetOCRTable.ts b/server/src/schema/migrations/1758705774125-CreateAssetOCRTable.ts deleted file mode 100644 index 611aac8398..0000000000 --- a/server/src/schema/migrations/1758705774125-CreateAssetOCRTable.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "asset_ocr" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "x1" real NOT NULL, "y1" real NOT NULL, "x2" real NOT NULL, "y2" real NOT NULL, "x3" real NOT NULL, "y3" real NOT NULL, "x4" real NOT NULL, "y4" real NOT NULL, "boxScore" real NOT NULL, "textScore" real NOT NULL, "text" text NOT NULL);`.execute( - db, - ); - await sql`ALTER TABLE "asset_ocr" ADD CONSTRAINT "asset_ocr_pkey" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "asset_ocr" ADD CONSTRAINT "asset_ocr_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`CREATE INDEX "asset_ocr_assetId_idx" ON "asset_ocr" ("assetId")`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TABLE "asset_ocr";`.execute(db); -} diff --git a/server/src/schema/migrations/1758705789125-CreateOCRSearchTable.ts b/server/src/schema/migrations/1758705789125-CreateOCRSearchTable.ts deleted file mode 100644 index 1b3eefd57d..0000000000 --- a/server/src/schema/migrations/1758705789125-CreateOCRSearchTable.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "ocr_search" ("assetId" uuid NOT NULL, "text" text NOT NULL);`.execute(db); - await sql`ALTER TABLE "ocr_search" ADD CONSTRAINT "ocr_search_pkey" PRIMARY KEY ("assetId");`.execute(db); - await sql`ALTER TABLE "ocr_search" ADD CONSTRAINT "ocr_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`CREATE INDEX "idx_ocr_search_text" ON "ocr_search" USING gin (f_unaccent("text") gin_trgm_ops);`.execute( - db, - ); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_idx_ocr_search_text', '{"type":"index","name":"idx_ocr_search_text","sql":"CREATE INDEX \\"idx_ocr_search_text\\" ON \\"ocr_search\\" USING gin (f_unaccent(\\"text\\") gin_trgm_ops);"}'::jsonb);`.execute( - db, - ); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TABLE "ocr_search";`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_idx_ocr_search_text';`.execute(db); -} diff --git a/server/src/schema/migrations/1758705804128-UpsertOcrAssetJobStatus.ts b/server/src/schema/migrations/1758705804128-UpsertOcrAssetJobStatus.ts deleted file mode 100644 index c7c3ba4449..0000000000 --- a/server/src/schema/migrations/1758705804128-UpsertOcrAssetJobStatus.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_job_status" ADD "ocrAt" timestamp with time zone;`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_job_status" DROP COLUMN "ocrAt";`.execute(db); -} diff --git a/server/src/schema/migrations/1761078763279-AddAppVersionColumnToSession.ts b/server/src/schema/migrations/1761078763279-AddAppVersionColumnToSession.ts deleted file mode 100644 index 8175788517..0000000000 --- a/server/src/schema/migrations/1761078763279-AddAppVersionColumnToSession.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "session" ADD "appVersion" character varying;`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "session" DROP COLUMN "appVersion";`.execute(db); -} diff --git a/server/src/schema/migrations/1761755618862-FixColumnNames.ts b/server/src/schema/migrations/1761755618862-FixColumnNames.ts deleted file mode 100644 index 25131a1640..0000000000 --- a/server/src/schema/migrations/1761755618862-FixColumnNames.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - // rename columns - await sql`ALTER TABLE "album_asset" RENAME COLUMN "albumsId" TO "albumId";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME COLUMN "albumsId" TO "albumId";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME COLUMN "usersId" TO "userId";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME COLUMN "sharedLinksId" TO "sharedLinkId";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME COLUMN "assetsId" TO "assetId";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME COLUMN "tagsId" TO "tagId";`.execute(db); - - // rename constraints - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_albumsId_fkey" TO "album_asset_albumId_fkey";`.execute(db); - await sql`ALTER TABLE "album_asset" RENAME CONSTRAINT "album_asset_assetsId_fkey" TO "album_asset_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_albumsId_fkey" TO "album_user_albumId_fkey";`.execute(db); - await sql`ALTER TABLE "album_user" RENAME CONSTRAINT "album_user_usersId_fkey" TO "album_user_userId_fkey";`.execute(db); - await sql`ALTER TABLE "memory_asset" RENAME CONSTRAINT "memory_asset_assetsId_fkey" TO "memory_asset_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_assetsId_fkey" TO "shared_link_asset_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "shared_link_asset" RENAME CONSTRAINT "shared_link_asset_sharedLinksId_fkey" TO "shared_link_asset_sharedLinkId_fkey";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_assetsId_fkey" TO "tag_asset_assetId_fkey";`.execute(db); - await sql`ALTER TABLE "tag_asset" RENAME CONSTRAINT "tag_asset_tagsId_fkey" TO "tag_asset_tagId_fkey";`.execute(db); - - // rename indexes - await sql`ALTER INDEX "album_asset_albumsId_idx" RENAME TO "album_asset_albumId_idx";`.execute(db); - await sql`ALTER INDEX "album_asset_assetsId_idx" RENAME TO "album_asset_assetId_idx";`.execute(db); - await sql`ALTER INDEX "album_user_usersId_idx" RENAME TO "album_user_userId_idx";`.execute(db); - await sql`ALTER INDEX "album_user_albumsId_idx" RENAME TO "album_user_albumId_idx";`.execute(db); - await sql`ALTER INDEX "memory_asset_assetsId_idx" RENAME TO "memory_asset_assetId_idx";`.execute(db); - await sql`ALTER INDEX "shared_link_asset_sharedLinksId_idx" RENAME TO "shared_link_asset_sharedLinkId_idx";`.execute(db); - await sql`ALTER INDEX "shared_link_asset_assetsId_idx" RENAME TO "shared_link_asset_assetId_idx";`.execute(db); - await sql`ALTER INDEX "tag_asset_assetsId_idx" RENAME TO "tag_asset_assetId_idx";`.execute(db); - await sql`ALTER INDEX "tag_asset_tagsId_idx" RENAME TO "tag_asset_tagId_idx";`.execute(db); - await sql`ALTER INDEX "tag_asset_assetsId_tagsId_idx" RENAME TO "tag_asset_assetId_tagId_idx";`.execute(db); - - // update triggers and functions - await sql`CREATE OR REPLACE FUNCTION album_user_after_insert() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE album SET "updatedAt" = clock_timestamp(), "updateId" = immich_uuid_v7(clock_timestamp()) - WHERE "id" IN (SELECT DISTINCT "albumId" FROM inserted_rows); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION album_asset_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO album_asset_audit ("albumId", "assetId") - SELECT "albumId", "assetId" FROM OLD - WHERE "albumId" IN (SELECT "id" FROM album WHERE "id" IN (SELECT "albumId" FROM OLD)); - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION album_user_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO album_audit ("albumId", "userId") - SELECT "albumId", "userId" - FROM OLD; - - IF pg_trigger_depth() = 1 THEN - INSERT INTO album_user_audit ("albumId", "userId") - SELECT "albumId", "userId" - FROM OLD; - END IF; - - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION memory_asset_delete_audit() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - INSERT INTO memory_asset_audit ("memoryId", "assetId") - SELECT "memoriesId", "assetId" FROM OLD - WHERE "memoriesId" IN (SELECT "id" FROM memory WHERE "id" IN (SELECT "memoriesId" FROM OLD)); - RETURN NULL; - END - $$;`.execute(db); - - // update overrides - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_after_insert","sql":"CREATE OR REPLACE FUNCTION album_user_after_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE album SET \\"updatedAt\\" = clock_timestamp(), \\"updateId\\" = immich_uuid_v7(clock_timestamp())\\n WHERE \\"id\\" IN (SELECT DISTINCT \\"albumId\\" FROM inserted_rows);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_after_insert';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_asset_audit (\\"albumId\\", \\"assetId\\")\\n SELECT \\"albumId\\", \\"assetId\\" FROM OLD\\n WHERE \\"albumId\\" IN (SELECT \\"id\\" FROM album WHERE \\"id\\" IN (SELECT \\"albumId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_asset_delete_audit';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"album_user_delete_audit","sql":"CREATE OR REPLACE FUNCTION album_user_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO album_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumId\\", \\"userId\\"\\n FROM OLD;\\n\\n IF pg_trigger_depth() = 1 THEN\\n INSERT INTO album_user_audit (\\"albumId\\", \\"userId\\")\\n SELECT \\"albumId\\", \\"userId\\"\\n FROM OLD;\\n END IF;\\n\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_album_user_delete_audit';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"memory_asset_delete_audit","sql":"CREATE OR REPLACE FUNCTION memory_asset_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO memory_asset_audit (\\"memoryId\\", \\"assetId\\")\\n SELECT \\"memoriesId\\", \\"assetId\\" FROM OLD\\n WHERE \\"memoriesId\\" IN (SELECT \\"id\\" FROM memory WHERE \\"id\\" IN (SELECT \\"memoriesId\\" FROM OLD));\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_memory_asset_delete_audit';`.execute(db); -} - -export function down() { - // not implemented -} diff --git a/server/src/schema/migrations/1762297277677-AddPluginAndWorkflowTables.ts b/server/src/schema/migrations/1762297277677-AddPluginAndWorkflowTables.ts deleted file mode 100644 index 6dacc1056b..0000000000 --- a/server/src/schema/migrations/1762297277677-AddPluginAndWorkflowTables.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE TABLE "plugin" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "name" character varying NOT NULL, - "title" character varying NOT NULL, - "description" character varying NOT NULL, - "author" character varying NOT NULL, - "version" character varying NOT NULL, - "wasmPath" character varying NOT NULL, - "createdAt" timestamp with time zone NOT NULL DEFAULT now(), - "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), - CONSTRAINT "plugin_name_uq" UNIQUE ("name"), - CONSTRAINT "plugin_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "plugin_name_idx" ON "plugin" ("name");`.execute(db); - await sql`CREATE TABLE "plugin_filter" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "pluginId" uuid NOT NULL, - "methodName" character varying NOT NULL, - "title" character varying NOT NULL, - "description" character varying NOT NULL, - "supportedContexts" character varying[] NOT NULL, - "schema" jsonb, - CONSTRAINT "plugin_filter_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "plugin_filter_methodName_uq" UNIQUE ("methodName"), - CONSTRAINT "plugin_filter_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "plugin_filter_supportedContexts_idx" ON "plugin_filter" USING gin ("supportedContexts");`.execute( - db, - ); - await sql`CREATE INDEX "plugin_filter_pluginId_idx" ON "plugin_filter" ("pluginId");`.execute(db); - await sql`CREATE INDEX "plugin_filter_methodName_idx" ON "plugin_filter" ("methodName");`.execute(db); - await sql`CREATE TABLE "plugin_action" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "pluginId" uuid NOT NULL, - "methodName" character varying NOT NULL, - "title" character varying NOT NULL, - "description" character varying NOT NULL, - "supportedContexts" character varying[] NOT NULL, - "schema" jsonb, - CONSTRAINT "plugin_action_pluginId_fkey" FOREIGN KEY ("pluginId") REFERENCES "plugin" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "plugin_action_methodName_uq" UNIQUE ("methodName"), - CONSTRAINT "plugin_action_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "plugin_action_supportedContexts_idx" ON "plugin_action" USING gin ("supportedContexts");`.execute( - db, - ); - await sql`CREATE INDEX "plugin_action_pluginId_idx" ON "plugin_action" ("pluginId");`.execute(db); - await sql`CREATE INDEX "plugin_action_methodName_idx" ON "plugin_action" ("methodName");`.execute(db); - await sql`CREATE TABLE "workflow" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "ownerId" uuid NOT NULL, - "triggerType" character varying NOT NULL, - "name" character varying, - "description" character varying NOT NULL, - "createdAt" timestamp with time zone NOT NULL DEFAULT now(), - "enabled" boolean NOT NULL DEFAULT true, - CONSTRAINT "workflow_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "workflow_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "workflow_ownerId_idx" ON "workflow" ("ownerId");`.execute(db); - await sql`CREATE TABLE "workflow_filter" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "workflowId" uuid NOT NULL, - "filterId" uuid NOT NULL, - "filterConfig" jsonb, - "order" integer NOT NULL, - CONSTRAINT "workflow_filter_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "workflow_filter_filterId_fkey" FOREIGN KEY ("filterId") REFERENCES "plugin_filter" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "workflow_filter_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "workflow_filter_filterId_idx" ON "workflow_filter" ("filterId");`.execute(db); - await sql`CREATE INDEX "workflow_filter_workflowId_order_idx" ON "workflow_filter" ("workflowId", "order");`.execute( - db, - ); - await sql`CREATE INDEX "workflow_filter_workflowId_idx" ON "workflow_filter" ("workflowId");`.execute(db); - await sql`CREATE TABLE "workflow_action" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "workflowId" uuid NOT NULL, - "actionId" uuid NOT NULL, - "actionConfig" jsonb, - "order" integer NOT NULL, - CONSTRAINT "workflow_action_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflow" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "workflow_action_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "plugin_action" ("id") ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT "workflow_action_pkey" PRIMARY KEY ("id") -);`.execute(db); - await sql`CREATE INDEX "workflow_action_actionId_idx" ON "workflow_action" ("actionId");`.execute(db); - await sql`CREATE INDEX "workflow_action_workflowId_order_idx" ON "workflow_action" ("workflowId", "order");`.execute( - db, - ); - await sql`CREATE INDEX "workflow_action_workflowId_idx" ON "workflow_action" ("workflowId");`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_plugin_filter_supportedContexts_idx', '{"type":"index","name":"plugin_filter_supportedContexts_idx","sql":"CREATE INDEX \\"plugin_filter_supportedContexts_idx\\" ON \\"plugin_filter\\" (\\"supportedContexts\\") USING gin;"}'::jsonb);`.execute( - db, - ); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('index_plugin_action_supportedContexts_idx', '{"type":"index","name":"plugin_action_supportedContexts_idx","sql":"CREATE INDEX \\"plugin_action_supportedContexts_idx\\" ON \\"plugin_action\\" (\\"supportedContexts\\") USING gin;"}'::jsonb);`.execute( - db, - ); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TABLE "workflow";`.execute(db); - await sql`DROP TABLE "workflow_filter";`.execute(db); - await sql`DROP TABLE "workflow_action";`.execute(db); - - await sql`DROP TABLE "plugin";`.execute(db); - await sql`DROP TABLE "plugin_filter";`.execute(db); - await sql`DROP TABLE "plugin_action";`.execute(db); - - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_filter_supportedContexts_idx';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'index_plugin_action_supportedContexts_idx';`.execute(db); -} diff --git a/server/src/schema/migrations/1764483051488-OCRBigramsForCJK.ts b/server/src/schema/migrations/1764483051488-OCRBigramsForCJK.ts deleted file mode 100644 index 3f5cb5fa8f..0000000000 --- a/server/src/schema/migrations/1764483051488-OCRBigramsForCJK.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Kysely, sql } from 'kysely'; -import { tokenizeForSearch } from 'src/utils/database'; - -export async function up(db: Kysely): Promise { - await sql`truncate ${sql.table('ocr_search')}`.execute(db); - - let lastAssetId: string | undefined; - while (true) { - const rows = await db - .selectFrom('asset_ocr') - .select(['assetId', sql`string_agg(text, ' ')`.as('text')]) - .$if(lastAssetId !== undefined, (qb) => qb.where('assetId', '>', lastAssetId)) - .groupBy('assetId') - .orderBy('assetId') - .limit(5000) - .execute(); - - if (rows.length === 0) { - break; - } - - await db - .insertInto('ocr_search') - .values(rows.map(({ assetId, text }) => ({ assetId, text: tokenizeForSearch(text).join(' ') }))) - .execute(); - - lastAssetId = rows.at(-1)!.assetId; - } -} - -export async function down(): Promise {} diff --git a/server/src/schema/migrations/1764698859174-SidecarInAssetFile.ts b/server/src/schema/migrations/1764698859174-SidecarInAssetFile.ts deleted file mode 100644 index 183a77832e..0000000000 --- a/server/src/schema/migrations/1764698859174-SidecarInAssetFile.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`INSERT INTO "asset_file" ("assetId", "path", "type") - SELECT - id, "sidecarPath", 'sidecar' - FROM "asset" - WHERE "sidecarPath" IS NOT NULL AND "sidecarPath" != '';`.execute(db); - - await sql`ALTER TABLE "asset" DROP COLUMN "sidecarPath";`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset" ADD "sidecarPath" character varying;`.execute(db); - - await sql` - UPDATE "asset" - SET "sidecarPath" = "asset_file"."path" - FROM "asset_file" - WHERE "asset"."id" = "asset_file"."assetId" AND "asset_file"."type" = 'sidecar'; - `.execute(db); - - await sql`DELETE FROM "asset_file" WHERE "type" = 'sidecar';`.execute(db); -} diff --git a/server/src/schema/migrations/1764705680041-ChangeWorkflowTableColumnsName.ts b/server/src/schema/migrations/1764705680041-ChangeWorkflowTableColumnsName.ts deleted file mode 100644 index c495d2a8e2..0000000000 --- a/server/src/schema/migrations/1764705680041-ChangeWorkflowTableColumnsName.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`DROP INDEX "workflow_filter_filterId_idx";`.execute(db); - await sql`DROP INDEX "workflow_action_actionId_idx";`.execute(db); - await sql`ALTER TABLE "workflow_filter" DROP CONSTRAINT "workflow_filter_filterId_fkey";`.execute(db); - await sql`ALTER TABLE "workflow_action" DROP CONSTRAINT "workflow_action_actionId_fkey";`.execute(db); - await sql`ALTER TABLE "workflow_filter" RENAME COLUMN "filterId" TO "pluginFilterId";`.execute(db); - await sql`ALTER TABLE "workflow_action" RENAME COLUMN "actionId" TO "pluginActionId";`.execute(db); - await sql`ALTER TABLE "workflow_filter" ADD CONSTRAINT "workflow_filter_pluginFilterId_fkey" FOREIGN KEY ("pluginFilterId") REFERENCES "plugin_filter" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`ALTER TABLE "workflow_action" ADD CONSTRAINT "workflow_action_pluginActionId_fkey" FOREIGN KEY ("pluginActionId") REFERENCES "plugin_action" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`CREATE INDEX "workflow_filter_pluginFilterId_idx" ON "workflow_filter" ("pluginFilterId");`.execute(db); - await sql`CREATE INDEX "workflow_action_pluginActionId_idx" ON "workflow_action" ("pluginActionId");`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP INDEX "workflow_filter_pluginFilterId_idx";`.execute(db); - await sql`DROP INDEX "workflow_action_pluginActionId_idx";`.execute(db); - await sql`ALTER TABLE "workflow_filter" DROP CONSTRAINT "workflow_filter_pluginFilterId_fkey";`.execute(db); - await sql`ALTER TABLE "workflow_action" DROP CONSTRAINT "workflow_action_pluginActionId_fkey";`.execute(db); - await sql`ALTER TABLE "workflow_filter" RENAME COLUMN "pluginFilterId" TO "filterId";`.execute(db); - await sql`ALTER TABLE "workflow_action" RENAME COLUMN "pluginActionId" TO "actionId";`.execute(db); - await sql`ALTER TABLE "workflow_filter" ADD CONSTRAINT "workflow_filter_filterId_fkey" FOREIGN KEY ("filterId") REFERENCES "plugin_filter" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`ALTER TABLE "workflow_action" ADD CONSTRAINT "workflow_action_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "plugin_action" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db); - await sql`CREATE INDEX "workflow_filter_filterId_idx" ON "workflow_filter" ("filterId");`.execute(db); - await sql`CREATE INDEX "workflow_action_actionId_idx" ON "workflow_action" ("actionId");`.execute(db); -} diff --git a/server/src/schema/migrations/1764957138636-AddLockedPropertiesToAssetExif.ts b/server/src/schema/migrations/1764957138636-AddLockedPropertiesToAssetExif.ts deleted file mode 100644 index 0cd024a4d8..0000000000 --- a/server/src/schema/migrations/1764957138636-AddLockedPropertiesToAssetExif.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_exif" ADD "lockedProperties" character varying[];`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_exif" DROP COLUMN "lockedProperties";`.execute(db); -} diff --git a/server/src/schema/migrations/1768336661963-AddAssetWidthHeight.ts b/server/src/schema/migrations/1768336661963-AddAssetWidthHeight.ts deleted file mode 100644 index 90ae32bebf..0000000000 --- a/server/src/schema/migrations/1768336661963-AddAssetWidthHeight.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset" ADD COLUMN "width" integer;`.execute(db); - await sql`ALTER TABLE "asset" ADD COLUMN "height" integer;`.execute(db); - - // Populate width and height from exif data with orientation-aware swapping - await sql` - UPDATE "asset" - SET - "width" = CASE - WHEN "asset_exif"."orientation" IN ('5', '6', '7', '8', '-90', '90') THEN "asset_exif"."exifImageHeight" - ELSE "asset_exif"."exifImageWidth" - END, - "height" = CASE - WHEN "asset_exif"."orientation" IN ('5', '6', '7', '8', '-90', '90') THEN "asset_exif"."exifImageWidth" - ELSE "asset_exif"."exifImageHeight" - END - FROM "asset_exif" - WHERE "asset"."id" = "asset_exif"."assetId" - AND ("asset_exif"."exifImageWidth" IS NOT NULL OR "asset_exif"."exifImageHeight" IS NOT NULL) - `.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset" DROP COLUMN "width";`.execute(db); - await sql`ALTER TABLE "asset" DROP COLUMN "height";`.execute(db); -} diff --git a/server/src/schema/migrations/1768336671610-CreateAssetEditTable.ts b/server/src/schema/migrations/1768336671610-CreateAssetEditTable.ts deleted file mode 100644 index ef2ef74726..0000000000 --- a/server/src/schema/migrations/1768336671610-CreateAssetEditTable.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql` - CREATE TABLE "asset_edit" ( - "id" uuid NOT NULL DEFAULT uuid_generate_v4(), - "assetId" uuid NOT NULL, - "action" varchar NOT NULL, - "parameters" jsonb NOT NULL - ); - `.execute(db); - - await sql`ALTER TABLE "asset_edit" ADD CONSTRAINT "asset_edit_pkey" PRIMARY KEY ("id");`.execute(db); - await sql`ALTER TABLE "asset_edit" ADD CONSTRAINT "asset_edit_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "asset" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute( - db, - ); - await sql`CREATE INDEX "asset_edit_assetId_idx" ON "asset_edit" ("assetId")`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TABLE IF EXISTS "asset_edit";`.execute(db); -} diff --git a/server/src/schema/migrations/1768336694315-CreateIsVisibleColumns.ts b/server/src/schema/migrations/1768336694315-CreateIsVisibleColumns.ts deleted file mode 100644 index 74e4d3bf17..0000000000 --- a/server/src/schema/migrations/1768336694315-CreateIsVisibleColumns.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_ocr" ADD COLUMN "isVisible" boolean NOT NULL DEFAULT TRUE`.execute(db); - await sql`ALTER TABLE "asset_face" ADD COLUMN "isVisible" boolean NOT NULL DEFAULT TRUE`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_ocr" DROP COLUMN "isVisible";`.execute(db); - await sql`ALTER TABLE "asset_face" DROP COLUMN "isVisible";`.execute(db); -} diff --git a/server/src/schema/migrations/1768587436457-AddEditCountToAsset.ts b/server/src/schema/migrations/1768587436457-AddEditCountToAsset.ts deleted file mode 100644 index 3dd60ccda0..0000000000 --- a/server/src/schema/migrations/1768587436457-AddEditCountToAsset.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION asset_edit_insert() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE asset - SET "editCount" = "editCount" + 1 - WHERE "id" = NEW."assetId"; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION asset_edit_delete() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE asset - SET "editCount" = "editCount" - 1 - WHERE "id" = OLD."assetId"; - RETURN NULL; - END - $$;`.execute(db); - await sql`ALTER TABLE "asset" ADD "editCount" integer NOT NULL DEFAULT 0;`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_edit_delete" - AFTER DELETE ON "asset_edit" - REFERENCING OLD TABLE AS "old" - FOR EACH ROW - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION asset_edit_delete();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_edit_insert" - AFTER INSERT ON "asset_edit" - FOR EACH ROW - EXECUTE FUNCTION asset_edit_insert();`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_edit_insert', '{"type":"function","name":"asset_edit_insert","sql":"CREATE OR REPLACE FUNCTION asset_edit_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"editCount\\" = \\"editCount\\" + 1\\n WHERE \\"id\\" = NEW.\\"assetId\\";\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_edit_delete', '{"type":"function","name":"asset_edit_delete","sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"editCount\\" = \\"editCount\\" - 1\\n WHERE \\"id\\" = OLD.\\"assetId\\";\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_edit_delete', '{"type":"trigger","name":"asset_edit_delete","sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_delete\\"\\n AFTER DELETE ON \\"asset_edit\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH ROW\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_edit_delete();"}'::jsonb);`.execute(db); - await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_edit_insert', '{"type":"trigger","name":"asset_edit_insert","sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_insert\\"\\n AFTER INSERT ON \\"asset_edit\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION asset_edit_insert();"}'::jsonb);`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`DROP TRIGGER "asset_edit_delete" ON "asset_edit";`.execute(db); - await sql`DROP TRIGGER "asset_edit_insert" ON "asset_edit";`.execute(db); - await sql`ALTER TABLE "asset" DROP COLUMN "editCount";`.execute(db); - await sql`DROP FUNCTION asset_edit_insert;`.execute(db); - await sql`DROP FUNCTION asset_edit_delete;`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_edit_insert';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_edit_delete';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_edit_delete';`.execute(db); - await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_edit_insert';`.execute(db); -} diff --git a/server/src/schema/migrations/1768757482271-SwitchToIsEdited.ts b/server/src/schema/migrations/1768757482271-SwitchToIsEdited.ts deleted file mode 100644 index 0660b7303d..0000000000 --- a/server/src/schema/migrations/1768757482271-SwitchToIsEdited.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION asset_edit_insert() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE asset - SET "isEdited" = true - FROM inserted_edit - WHERE asset.id = inserted_edit."assetId" AND NOT asset."isEdited"; - RETURN NULL; - END - $$;`.execute(db); - await sql`CREATE OR REPLACE FUNCTION asset_edit_delete() - RETURNS TRIGGER - LANGUAGE PLPGSQL - AS $$ - BEGIN - UPDATE asset - SET "isEdited" = false - FROM deleted_edit - WHERE asset.id = deleted_edit."assetId" AND asset."isEdited" - AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit."assetId" = asset.id); - RETURN NULL; - END - $$;`.execute(db); - await sql`ALTER TABLE "asset" ADD "isEdited" boolean NOT NULL DEFAULT false;`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_edit_delete" - AFTER DELETE ON "asset_edit" - REFERENCING OLD TABLE AS "deleted_edit" - FOR EACH STATEMENT - WHEN (pg_trigger_depth() = 0) - EXECUTE FUNCTION asset_edit_delete();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_edit_insert" - AFTER INSERT ON "asset_edit" - REFERENCING NEW TABLE AS "inserted_edit" - FOR EACH STATEMENT - EXECUTE FUNCTION asset_edit_insert();`.execute(db); - await sql`ALTER TABLE "asset" DROP COLUMN "editCount";`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"asset_edit_insert","sql":"CREATE OR REPLACE FUNCTION asset_edit_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"isEdited\\" = true\\n FROM inserted_edit\\n WHERE asset.id = inserted_edit.\\"assetId\\" AND NOT asset.\\"isEdited\\";\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_asset_edit_insert';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"asset_edit_delete","sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"isEdited\\" = false\\n FROM deleted_edit\\n WHERE asset.id = deleted_edit.\\"assetId\\" AND asset.\\"isEdited\\" \\n AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit.\\"assetId\\" = asset.id);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_asset_edit_delete';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"trigger","name":"asset_edit_delete","sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_delete\\"\\n AFTER DELETE ON \\"asset_edit\\"\\n REFERENCING OLD TABLE AS \\"deleted_edit\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_edit_delete();"}'::jsonb WHERE "name" = 'trigger_asset_edit_delete';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"type":"trigger","name":"asset_edit_insert","sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_insert\\"\\n AFTER INSERT ON \\"asset_edit\\"\\n REFERENCING NEW TABLE AS \\"inserted_edit\\"\\n FOR EACH STATEMENT\\n EXECUTE FUNCTION asset_edit_insert();"}'::jsonb WHERE "name" = 'trigger_asset_edit_insert';`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`CREATE OR REPLACE FUNCTION public.asset_edit_insert() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - UPDATE asset - SET "editCount" = "editCount" + 1 - WHERE "id" = NEW."assetId"; - RETURN NULL; - END - $function$ -`.execute(db); - await sql`CREATE OR REPLACE FUNCTION public.asset_edit_delete() - RETURNS trigger - LANGUAGE plpgsql -AS $function$ - BEGIN - UPDATE asset - SET "editCount" = "editCount" - 1 - WHERE "id" = OLD."assetId"; - RETURN NULL; - END - $function$ -`.execute(db); - await sql`ALTER TABLE "asset" ADD "editCount" integer NOT NULL DEFAULT 0;`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_edit_delete" - AFTER DELETE ON "asset_edit" - REFERENCING OLD TABLE AS "old" - FOR EACH ROW - WHEN ((pg_trigger_depth() = 0)) - EXECUTE FUNCTION asset_edit_delete();`.execute(db); - await sql`CREATE OR REPLACE TRIGGER "asset_edit_insert" - AFTER INSERT ON "asset_edit" - FOR EACH ROW - EXECUTE FUNCTION asset_edit_insert();`.execute(db); - await sql`ALTER TABLE "asset" DROP COLUMN "isEdited";`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION asset_edit_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"editCount\\" = \\"editCount\\" + 1\\n WHERE \\"id\\" = NEW.\\"assetId\\";\\n RETURN NULL;\\n END\\n $$;","name":"asset_edit_insert","type":"function"}'::jsonb WHERE "name" = 'function_asset_edit_insert';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"editCount\\" = \\"editCount\\" - 1\\n WHERE \\"id\\" = OLD.\\"assetId\\";\\n RETURN NULL;\\n END\\n $$;","name":"asset_edit_delete","type":"function"}'::jsonb WHERE "name" = 'function_asset_edit_delete';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_delete\\"\\n AFTER DELETE ON \\"asset_edit\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH ROW\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_edit_delete();","name":"asset_edit_delete","type":"trigger"}'::jsonb WHERE "name" = 'trigger_asset_edit_delete';`.execute(db); - await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_insert\\"\\n AFTER INSERT ON \\"asset_edit\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION asset_edit_insert();","name":"asset_edit_insert","type":"trigger"}'::jsonb WHERE "name" = 'trigger_asset_edit_insert';`.execute(db); -} diff --git a/server/src/schema/migrations/1768828334807-AddIsEditedToAssetFile.ts b/server/src/schema/migrations/1768828334807-AddIsEditedToAssetFile.ts deleted file mode 100644 index b1daa3d72f..0000000000 --- a/server/src/schema/migrations/1768828334807-AddIsEditedToAssetFile.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_file" DROP CONSTRAINT "asset_file_assetId_type_uq";`.execute(db); - await sql`ALTER TABLE "asset_file" ADD "isEdited" boolean NOT NULL DEFAULT false;`.execute(db); - await sql`ALTER TABLE "asset_file" ADD CONSTRAINT "asset_file_assetId_type_isEdited_uq" UNIQUE ("assetId", "type", "isEdited");`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_file" DROP CONSTRAINT "asset_file_assetId_type_isEdited_uq";`.execute(db); - await sql`ALTER TABLE "asset_file" ADD CONSTRAINT "asset_file_assetId_type_uq" UNIQUE ("assetId", "type");`.execute(db); - await sql`ALTER TABLE "asset_file" DROP COLUMN "isEdited";`.execute(db); -} diff --git a/server/src/schema/migrations/1768847456553-AddTagsToExif.ts b/server/src/schema/migrations/1768847456553-AddTagsToExif.ts deleted file mode 100644 index 6839468cae..0000000000 --- a/server/src/schema/migrations/1768847456553-AddTagsToExif.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_exif" ADD "tags" character varying[];`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_exif" DROP COLUMN "tags";`.execute(db); -} diff --git a/server/src/schema/migrations/1769105700133-AddAssetEditSequence.ts b/server/src/schema/migrations/1769105700133-AddAssetEditSequence.ts deleted file mode 100644 index 40c1723cd6..0000000000 --- a/server/src/schema/migrations/1769105700133-AddAssetEditSequence.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`DELETE FROM "asset_edit";`.execute(db); - await sql`ALTER TABLE "asset_edit" ADD "sequence" integer NOT NULL;`.execute(db); - await sql`ALTER TABLE "asset_edit" ADD CONSTRAINT "asset_edit_assetId_sequence_uq" UNIQUE ("assetId", "sequence");`.execute( - db, - ); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_edit" DROP CONSTRAINT "asset_edit_assetId_sequence_uq";`.execute(db); - await sql`ALTER TABLE "asset_edit" DROP COLUMN "sequence";`.execute(db); -} diff --git a/server/src/schema/migrations/1769441657564-AddIsProgressiveColumn.ts b/server/src/schema/migrations/1769441657564-AddIsProgressiveColumn.ts deleted file mode 100644 index 6377dc1059..0000000000 --- a/server/src/schema/migrations/1769441657564-AddIsProgressiveColumn.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_file" ADD "isProgressive" boolean NOT NULL DEFAULT false;`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_file" DROP COLUMN "isProgressive";`.execute(db); -} diff --git a/server/src/schema/migrations/1769635093204-DropThumbnailJobStatusColumns.ts b/server/src/schema/migrations/1769635093204-DropThumbnailJobStatusColumns.ts deleted file mode 100644 index 9cd2f91b47..0000000000 --- a/server/src/schema/migrations/1769635093204-DropThumbnailJobStatusColumns.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Kysely, sql } from 'kysely'; - -export async function up(db: Kysely): Promise { - await sql`ALTER TABLE "asset_job_status" DROP COLUMN "previewAt";`.execute(db); - await sql`ALTER TABLE "asset_job_status" DROP COLUMN "thumbnailAt";`.execute(db); -} - -export async function down(db: Kysely): Promise { - await sql`ALTER TABLE "asset_job_status" ADD "previewAt" timestamp with time zone;`.execute(db); - await sql`ALTER TABLE "asset_job_status" ADD "thumbnailAt" timestamp with time zone;`.execute(db); -} diff --git a/server/src/schema/tables/activity.table.ts b/server/src/schema/tables/activity.table.ts deleted file mode 100644 index dfa7c98e42..0000000000 --- a/server/src/schema/tables/activity.table.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AlbumAssetTable } from 'src/schema/tables/album-asset.table'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - Check, - Column, - CreateDateColumn, - ForeignKeyColumn, - ForeignKeyConstraint, - Generated, - Index, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('activity') -@UpdatedAtTrigger('activity_updatedAt') -@Index({ - name: 'activity_like_idx', - columns: ['assetId', 'userId', 'albumId'], - unique: true, - where: '("isLiked" = true)', -}) -@Check({ - name: 'activity_like_check', - expression: `(comment IS NULL AND "isLiked" = true) OR (comment IS NOT NULL AND "isLiked" = false)`, -}) -@ForeignKeyConstraint({ - columns: ['albumId', 'assetId'], - referenceTable: () => AlbumAssetTable, - referenceColumns: ['albumId', 'assetId'], - onUpdate: 'NO ACTION', - onDelete: 'CASCADE', -}) -export class ActivityTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @ForeignKeyColumn(() => AlbumTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - albumId!: string; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - userId!: string; - - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true }) - assetId!: string | null; - - @Column({ type: 'text', default: null }) - comment!: string | null; - - @Column({ type: 'boolean', default: false }) - isLiked!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/album-asset-audit.table.ts b/server/src/schema/tables/album-asset-audit.table.ts deleted file mode 100644 index ab8fd9ae89..0000000000 --- a/server/src/schema/tables/album-asset-audit.table.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('album_asset_audit') -export class AlbumAssetAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @ForeignKeyColumn(() => AlbumTable, { - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - }) - albumId!: string; - - @Column({ type: 'uuid', index: true }) - assetId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/album-asset.table.ts b/server/src/schema/tables/album-asset.table.ts deleted file mode 100644 index dea271239b..0000000000 --- a/server/src/schema/tables/album-asset.table.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { album_asset_delete_audit } from 'src/schema/functions'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { - AfterDeleteTrigger, - CreateDateColumn, - ForeignKeyColumn, - Generated, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table({ name: 'album_asset' }) -@UpdatedAtTrigger('album_asset_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: album_asset_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() <= 1', -}) -export class AlbumAssetTable { - @ForeignKeyColumn(() => AlbumTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false, primary: true }) - albumId!: string; - - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false, primary: true }) - assetId!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/album-audit.table.ts b/server/src/schema/tables/album-audit.table.ts deleted file mode 100644 index 432c51c36a..0000000000 --- a/server/src/schema/tables/album-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('album_audit') -export class AlbumAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - albumId!: string; - - @Column({ type: 'uuid', index: true }) - userId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/album-user-audit.table.ts b/server/src/schema/tables/album-user-audit.table.ts deleted file mode 100644 index 2259511bdd..0000000000 --- a/server/src/schema/tables/album-user-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('album_user_audit') -export class AlbumUserAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - albumId!: string; - - @Column({ type: 'uuid', index: true }) - userId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/album-user.table.ts b/server/src/schema/tables/album-user.table.ts deleted file mode 100644 index 761aabc1af..0000000000 --- a/server/src/schema/tables/album-user.table.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AlbumUserRole } from 'src/enum'; -import { album_user_after_insert, album_user_delete_audit } from 'src/schema/functions'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - AfterInsertTrigger, - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table({ name: 'album_user' }) -// Pre-existing indices from original album <--> user ManyToMany mapping -@UpdatedAtTrigger('album_user_updatedAt') -@AfterInsertTrigger({ - name: 'album_user_after_insert', - scope: 'statement', - referencingNewTableAs: 'inserted_rows', - function: album_user_after_insert, -}) -@AfterDeleteTrigger({ - scope: 'statement', - function: album_user_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() <= 1', -}) -export class AlbumUserTable { - @ForeignKeyColumn(() => AlbumTable, { - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - nullable: false, - primary: true, - }) - albumId!: string; - - @ForeignKeyColumn(() => UserTable, { - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - nullable: false, - primary: true, - }) - userId!: string; - - @Column({ type: 'character varying', default: AlbumUserRole.Editor }) - role!: Generated; - - @CreateIdColumn({ index: true }) - createId!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; -} diff --git a/server/src/schema/tables/album.table.ts b/server/src/schema/tables/album.table.ts deleted file mode 100644 index 5628db3d03..0000000000 --- a/server/src/schema/tables/album.table.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetOrder } from 'src/enum'; -import { album_delete_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - Column, - CreateDateColumn, - DeleteDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table({ name: 'album' }) -@UpdatedAtTrigger('album_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: album_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -export class AlbumTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - - @Column({ default: 'Untitled Album' }) - albumName!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @ForeignKeyColumn(() => AssetTable, { - nullable: true, - onDelete: 'SET NULL', - onUpdate: 'CASCADE', - comment: 'Asset ID to be used as thumbnail', - }) - albumThumbnailAssetId!: string | null; - - @UpdateDateColumn() - updatedAt!: Generated; - - @Column({ type: 'text', default: '' }) - description!: Generated; - - @DeleteDateColumn() - deletedAt!: Timestamp | null; - - @Column({ type: 'boolean', default: true }) - isActivityEnabled!: Generated; - - @Column({ default: AssetOrder.Desc }) - order!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/asset-audit.table.ts b/server/src/schema/tables/asset-audit.table.ts deleted file mode 100644 index 86c3f6f28b..0000000000 --- a/server/src/schema/tables/asset-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('asset_audit') -export class AssetAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - assetId!: string; - - @Column({ type: 'uuid', index: true }) - ownerId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/asset-edit.table.ts b/server/src/schema/tables/asset-edit.table.ts deleted file mode 100644 index 886b62dc0b..0000000000 --- a/server/src/schema/tables/asset-edit.table.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { AssetEditAction, AssetEditActionParameter } from 'src/dtos/editing.dto'; -import { asset_edit_delete, asset_edit_insert } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { - AfterDeleteTrigger, - AfterInsertTrigger, - Column, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Unique, -} from 'src/sql-tools'; - -@Table('asset_edit') -@AfterInsertTrigger({ scope: 'statement', function: asset_edit_insert, referencingNewTableAs: 'inserted_edit' }) -@AfterDeleteTrigger({ - scope: 'statement', - function: asset_edit_delete, - referencingOldTableAs: 'deleted_edit', - when: 'pg_trigger_depth() = 0', -}) -@Unique({ columns: ['assetId', 'sequence'] }) -export class AssetEditTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - assetId!: string; - - @Column() - action!: T; - - @Column({ type: 'jsonb' }) - parameters!: AssetEditActionParameter[T]; - - @Column({ type: 'integer' }) - sequence!: number; -} diff --git a/server/src/schema/tables/asset-exif.table.ts b/server/src/schema/tables/asset-exif.table.ts deleted file mode 100644 index 9dacb547cf..0000000000 --- a/server/src/schema/tables/asset-exif.table.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { LockableProperty } from 'src/database'; -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Generated, Int8, Table, Timestamp, UpdateDateColumn } from 'src/sql-tools'; - -@Table('asset_exif') -@UpdatedAtTrigger('asset_exif_updatedAt') -export class AssetExifTable { - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', primary: true }) - assetId!: string; - - @Column({ type: 'character varying', nullable: true }) - make!: string | null; - - @Column({ type: 'character varying', nullable: true }) - model!: string | null; - - @Column({ type: 'integer', nullable: true }) - exifImageWidth!: number | null; - - @Column({ type: 'integer', nullable: true }) - exifImageHeight!: number | null; - - @Column({ type: 'bigint', nullable: true }) - fileSizeInByte!: Int8 | null; - - @Column({ type: 'character varying', nullable: true }) - orientation!: string | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - dateTimeOriginal!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - modifyDate!: Timestamp | null; - - @Column({ type: 'character varying', nullable: true }) - lensModel!: string | null; - - @Column({ type: 'double precision', nullable: true }) - fNumber!: number | null; - - @Column({ type: 'double precision', nullable: true }) - focalLength!: number | null; - - @Column({ type: 'integer', nullable: true }) - iso!: number | null; - - @Column({ type: 'double precision', nullable: true }) - latitude!: number | null; - - @Column({ type: 'double precision', nullable: true }) - longitude!: number | null; - - @Column({ type: 'character varying', nullable: true, index: true }) - city!: string | null; - - @Column({ type: 'character varying', nullable: true }) - state!: string | null; - - @Column({ type: 'character varying', nullable: true }) - country!: string | null; - - @Column({ type: 'text', default: '' }) - description!: Generated; // or caption - - @Column({ type: 'double precision', nullable: true }) - fps!: number | null; - - @Column({ type: 'character varying', nullable: true }) - exposureTime!: string | null; - - @Column({ type: 'character varying', nullable: true, index: true }) - livePhotoCID!: string | null; - - @Column({ type: 'character varying', nullable: true }) - timeZone!: string | null; - - @Column({ type: 'character varying', nullable: true }) - projectionType!: string | null; - - @Column({ type: 'character varying', nullable: true }) - profileDescription!: string | null; - - @Column({ type: 'character varying', nullable: true }) - colorspace!: string | null; - - @Column({ type: 'integer', nullable: true }) - bitsPerSample!: number | null; - - @Column({ type: 'character varying', nullable: true, index: true }) - autoStackId!: string | null; - - @Column({ type: 'integer', nullable: true }) - rating!: number | null; - - @Column({ type: 'character varying', array: true, nullable: true }) - tags!: string[] | null; - - @UpdateDateColumn({ default: () => 'clock_timestamp()' }) - updatedAt!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; - - @Column({ type: 'character varying', array: true, nullable: true }) - lockedProperties!: Array | null; -} diff --git a/server/src/schema/tables/asset-face-audit.table.ts b/server/src/schema/tables/asset-face-audit.table.ts deleted file mode 100644 index 4f03c22aa0..0000000000 --- a/server/src/schema/tables/asset-face-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('asset_face_audit') -export class AssetFaceAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - assetFaceId!: string; - - @Column({ type: 'uuid', index: true }) - assetId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/asset-face.table.ts b/server/src/schema/tables/asset-face.table.ts deleted file mode 100644 index 8b156f2a17..0000000000 --- a/server/src/schema/tables/asset-face.table.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { SourceType } from 'src/enum'; -import { asset_face_source_type } from 'src/schema/enums'; -import { asset_face_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { PersonTable } from 'src/schema/tables/person.table'; -import { - AfterDeleteTrigger, - Column, - DeleteDateColumn, - ForeignKeyColumn, - Generated, - Index, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table({ name: 'asset_face' }) -@UpdatedAtTrigger('asset_face_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: asset_face_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -// schemaFromDatabase does not preserve column order -@Index({ name: 'asset_face_assetId_personId_idx', columns: ['assetId', 'personId'] }) -@Index({ columns: ['personId', 'assetId'] }) -export class AssetFaceTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => AssetTable, { - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - // [assetId, personId] is the PK constraint - index: false, - }) - assetId!: string; - - @ForeignKeyColumn(() => PersonTable, { - onDelete: 'SET NULL', - onUpdate: 'CASCADE', - nullable: true, - // [personId, assetId] makes this redundant - index: false, - }) - personId!: string | null; - - @Column({ default: 0, type: 'integer' }) - imageWidth!: Generated; - - @Column({ default: 0, type: 'integer' }) - imageHeight!: Generated; - - @Column({ default: 0, type: 'integer' }) - boundingBoxX1!: Generated; - - @Column({ default: 0, type: 'integer' }) - boundingBoxY1!: Generated; - - @Column({ default: 0, type: 'integer' }) - boundingBoxX2!: Generated; - - @Column({ default: 0, type: 'integer' }) - boundingBoxY2!: Generated; - - @Column({ default: SourceType.MachineLearning, enum: asset_face_source_type }) - sourceType!: Generated; - - @DeleteDateColumn() - deletedAt!: Timestamp | null; - - @UpdateDateColumn() - updatedAt!: Generated; - - @UpdateIdColumn() - updateId!: Generated; - - @Column({ type: 'boolean', default: true }) - isVisible!: Generated; -} diff --git a/server/src/schema/tables/asset-file.table.ts b/server/src/schema/tables/asset-file.table.ts deleted file mode 100644 index 73b5171a47..0000000000 --- a/server/src/schema/tables/asset-file.table.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetFileType } from 'src/enum'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - Unique, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('asset_file') -@Unique({ columns: ['assetId', 'type', 'isEdited'] }) -@UpdatedAtTrigger('asset_file_updatedAt') -export class AssetFileTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - assetId!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @Column() - type!: AssetFileType; - - @Column() - path!: string; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; - - @Column({ type: 'boolean', default: false }) - isEdited!: Generated; - - @Column({ type: 'boolean', default: false }) - isProgressive!: Generated; -} diff --git a/server/src/schema/tables/asset-job-status.table.ts b/server/src/schema/tables/asset-job-status.table.ts deleted file mode 100644 index 62194825e5..0000000000 --- a/server/src/schema/tables/asset-job-status.table.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Table, Timestamp } from 'src/sql-tools'; - -@Table('asset_job_status') -export class AssetJobStatusTable { - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', primary: true }) - assetId!: string; - - @Column({ type: 'timestamp with time zone', nullable: true }) - facesRecognizedAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - metadataExtractedAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - duplicatesDetectedAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - ocrAt!: Timestamp | null; -} diff --git a/server/src/schema/tables/asset-metadata-audit.table.ts b/server/src/schema/tables/asset-metadata-audit.table.ts deleted file mode 100644 index 16272eacf7..0000000000 --- a/server/src/schema/tables/asset-metadata-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('asset_metadata_audit') -export class AssetMetadataAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - assetId!: string; - - @Column({ index: true }) - key!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/asset-metadata.table.ts b/server/src/schema/tables/asset-metadata.table.ts deleted file mode 100644 index 8a7af1360f..0000000000 --- a/server/src/schema/tables/asset-metadata.table.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetMetadataKey } from 'src/enum'; -import { asset_metadata_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { - AfterDeleteTrigger, - Column, - ForeignKeyColumn, - Generated, - PrimaryColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@UpdatedAtTrigger('asset_metadata_updated_at') -@Table('asset_metadata') -@AfterDeleteTrigger({ - scope: 'statement', - function: asset_metadata_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -export class AssetMetadataTable { - @ForeignKeyColumn(() => AssetTable, { - onUpdate: 'CASCADE', - onDelete: 'CASCADE', - primary: true, - // [assetId, key] is the PK constraint - index: false, - }) - assetId!: string; - - @PrimaryColumn({ type: 'character varying' }) - key!: AssetMetadataKey | string; - - @Column({ type: 'jsonb' }) - value!: object; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; - - @UpdateDateColumn({ index: true }) - updatedAt!: Generated; -} diff --git a/server/src/schema/tables/asset-ocr.table.ts b/server/src/schema/tables/asset-ocr.table.ts deleted file mode 100644 index b9b0838cbe..0000000000 --- a/server/src/schema/tables/asset-ocr.table.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; - -@Table('asset_ocr') -export class AssetOcrTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - assetId!: string; - - // box positions are normalized, with values between 0 and 1 - @Column({ type: 'real' }) - x1!: number; - - @Column({ type: 'real' }) - y1!: number; - - @Column({ type: 'real' }) - x2!: number; - - @Column({ type: 'real' }) - y2!: number; - - @Column({ type: 'real' }) - x3!: number; - - @Column({ type: 'real' }) - y3!: number; - - @Column({ type: 'real' }) - x4!: number; - - @Column({ type: 'real' }) - y4!: number; - - @Column({ type: 'real' }) - boxScore!: number; - - @Column({ type: 'real' }) - textScore!: number; - - @Column({ type: 'text' }) - text!: string; - - @Column({ type: 'boolean', default: true }) - isVisible!: Generated; -} diff --git a/server/src/schema/tables/asset.table.ts b/server/src/schema/tables/asset.table.ts deleted file mode 100644 index 0b3da710ac..0000000000 --- a/server/src/schema/tables/asset.table.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { AssetStatus, AssetType, AssetVisibility } from 'src/enum'; -import { asset_visibility_enum, assets_status_enum } from 'src/schema/enums'; -import { asset_delete_audit } from 'src/schema/functions'; -import { LibraryTable } from 'src/schema/tables/library.table'; -import { StackTable } from 'src/schema/tables/stack.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - Column, - CreateDateColumn, - DeleteDateColumn, - ForeignKeyColumn, - Generated, - Index, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; -import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database'; - -@Table('asset') -@UpdatedAtTrigger('asset_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: asset_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -// Checksums must be unique per user and library -@Index({ - name: ASSET_CHECKSUM_CONSTRAINT, - columns: ['ownerId', 'checksum'], - unique: true, - where: '("libraryId" IS NULL)', -}) -@Index({ - columns: ['ownerId', 'libraryId', 'checksum'], - unique: true, - where: '("libraryId" IS NOT NULL)', -}) -@Index({ - name: 'asset_localDateTime_idx', - expression: `(("localDateTime" at time zone 'UTC')::date)`, -}) -@Index({ - name: 'asset_localDateTime_month_idx', - expression: `(date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text)`, -}) -@Index({ columns: ['originalPath', 'libraryId'] }) -@Index({ columns: ['id', 'stackId'] }) -@Index({ - name: 'asset_originalFilename_trigram_idx', - using: 'gin', - expression: 'f_unaccent("originalFileName") gin_trgm_ops', -}) -// For all assets, each originalpath must be unique per user and library -export class AssetTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @Column() - deviceAssetId!: string; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - - @Column() - deviceId!: string; - - @Column() - type!: AssetType; - - @Column() - originalPath!: string; - - @Column({ type: 'timestamp with time zone', index: true }) - fileCreatedAt!: Timestamp; - - @Column({ type: 'timestamp with time zone' }) - fileModifiedAt!: Timestamp; - - @Column({ type: 'boolean', default: false }) - isFavorite!: Generated; - - @Column({ type: 'character varying', nullable: true }) - duration!: string | null; - - @Column({ type: 'character varying', nullable: true, default: '' }) - encodedVideoPath!: string | null; - - @Column({ type: 'bytea', index: true }) - checksum!: Buffer; // sha1 checksum - - @ForeignKeyColumn(() => AssetTable, { nullable: true, onUpdate: 'CASCADE', onDelete: 'SET NULL' }) - livePhotoVideoId!: string | null; - - @UpdateDateColumn() - updatedAt!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @Column({ index: true }) - originalFileName!: string; - - @Column({ type: 'bytea', nullable: true }) - thumbhash!: Buffer | null; - - @Column({ type: 'boolean', default: false }) - isOffline!: Generated; - - @ForeignKeyColumn(() => LibraryTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true }) - libraryId!: string | null; - - @Column({ type: 'boolean', default: false }) - isExternal!: Generated; - - @DeleteDateColumn() - deletedAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone' }) - localDateTime!: Timestamp; - - @ForeignKeyColumn(() => StackTable, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' }) - stackId!: string | null; - - @Column({ type: 'uuid', nullable: true, index: true }) - duplicateId!: string | null; - - @Column({ enum: assets_status_enum, default: AssetStatus.Active }) - status!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; - - @Column({ enum: asset_visibility_enum, default: AssetVisibility.Timeline }) - visibility!: Generated; - - @Column({ type: 'integer', nullable: true }) - width!: number | null; - - @Column({ type: 'integer', nullable: true }) - height!: number | null; - - @Column({ type: 'boolean', default: false }) - isEdited!: Generated; -} diff --git a/server/src/schema/tables/audit.table.ts b/server/src/schema/tables/audit.table.ts deleted file mode 100644 index 15b4990814..0000000000 --- a/server/src/schema/tables/audit.table.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { DatabaseAction, EntityType } from 'src/enum'; -import { Column, CreateDateColumn, Generated, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools'; - -@Table('audit') -@Index({ columns: ['ownerId', 'createdAt'] }) -export class AuditTable { - @PrimaryColumn({ type: 'serial', synchronize: false }) - id!: Generated; - - @Column() - entityType!: EntityType; - - @Column({ type: 'uuid' }) - entityId!: string; - - @Column() - action!: DatabaseAction; - - @Column({ type: 'uuid' }) - ownerId!: string; - - @CreateDateColumn() - createdAt!: Generated; -} diff --git a/server/src/schema/tables/face-search.table.ts b/server/src/schema/tables/face-search.table.ts deleted file mode 100644 index ff63879404..0000000000 --- a/server/src/schema/tables/face-search.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; - -@Table({ name: 'face_search' }) -@Index({ - name: 'face_index', - using: 'hnsw', - expression: `embedding vector_cosine_ops`, - with: 'ef_construction = 300, m = 16', -}) -export class FaceSearchTable { - @ForeignKeyColumn(() => AssetFaceTable, { onDelete: 'CASCADE', primary: true }) - faceId!: string; - - @Column({ type: 'vector', length: 512, synchronize: false }) - embedding!: string; -} diff --git a/server/src/schema/tables/geodata-places.table.ts b/server/src/schema/tables/geodata-places.table.ts deleted file mode 100644 index eec2b240d0..0000000000 --- a/server/src/schema/tables/geodata-places.table.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Column, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools'; - -@Table({ name: 'geodata_places', primaryConstraintName: 'geodata_places_pkey' }) -@Index({ - name: 'idx_geodata_places_alternate_names', - using: 'gin', - expression: 'f_unaccent("alternateNames") gin_trgm_ops', -}) -@Index({ - name: 'idx_geodata_places_admin1_name', - using: 'gin', - expression: 'f_unaccent("admin1Name") gin_trgm_ops', -}) -@Index({ - name: 'idx_geodata_places_admin2_name', - using: 'gin', - expression: 'f_unaccent("admin2Name") gin_trgm_ops', -}) -@Index({ - name: 'idx_geodata_places_name', - using: 'gin', - expression: 'f_unaccent("name") gin_trgm_ops', -}) -@Index({ - name: 'IDX_geodata_gist_earthcoord', - expression: 'll_to_earth_public(latitude, longitude)', -}) -export class GeodataPlacesTable { - @PrimaryColumn({ type: 'integer' }) - id!: number; - - @Column({ type: 'character varying', length: 200 }) - name!: string; - - @Column({ type: 'double precision' }) - longitude!: number; - - @Column({ type: 'double precision' }) - latitude!: number; - - @Column({ type: 'character', length: 2 }) - countryCode!: string; - - @Column({ type: 'character varying', length: 20, nullable: true }) - admin1Code!: string | null; - - @Column({ type: 'character varying', length: 80, nullable: true }) - admin2Code!: string | null; - - @Column({ type: 'date' }) - modificationDate!: Timestamp; - - @Column({ type: 'character varying', nullable: true }) - admin1Name!: string | null; - - @Column({ type: 'character varying', nullable: true }) - admin2Name!: string | null; - - @Column({ type: 'character varying', nullable: true }) - alternateNames!: string | null; -} diff --git a/server/src/schema/tables/library.table.ts b/server/src/schema/tables/library.table.ts deleted file mode 100644 index 57ad144c8e..0000000000 --- a/server/src/schema/tables/library.table.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - Column, - CreateDateColumn, - DeleteDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('library') -@UpdatedAtTrigger('library_updatedAt') -export class LibraryTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @Column() - name!: string; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - - @Column({ type: 'text', array: true }) - importPaths!: string[]; - - @Column({ type: 'text', array: true }) - exclusionPatterns!: string[]; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @DeleteDateColumn() - deletedAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - refreshedAt!: Timestamp | null; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/memory-asset-audit.table.ts b/server/src/schema/tables/memory-asset-audit.table.ts deleted file mode 100644 index 218c2f19ff..0000000000 --- a/server/src/schema/tables/memory-asset-audit.table.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { MemoryTable } from 'src/schema/tables/memory.table'; -import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('memory_asset_audit') -export class MemoryAssetAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @ForeignKeyColumn(() => MemoryTable, { type: 'uuid', onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - memoryId!: string; - - @Column({ type: 'uuid', index: true }) - assetId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/memory-asset.table.ts b/server/src/schema/tables/memory-asset.table.ts deleted file mode 100644 index b162000ca0..0000000000 --- a/server/src/schema/tables/memory-asset.table.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { memory_asset_delete_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { MemoryTable } from 'src/schema/tables/memory.table'; -import { - AfterDeleteTrigger, - CreateDateColumn, - ForeignKeyColumn, - Generated, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('memory_asset') -@UpdatedAtTrigger('memory_asset_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: memory_asset_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() <= 1', -}) -export class MemoryAssetTable { - @ForeignKeyColumn(() => MemoryTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - memoriesId!: string; - - @ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - assetId!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/memory-audit.table.ts b/server/src/schema/tables/memory-audit.table.ts deleted file mode 100644 index 167caf8e6e..0000000000 --- a/server/src/schema/tables/memory-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('memory_audit') -export class MemoryAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - memoryId!: string; - - @Column({ type: 'uuid', index: true }) - userId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/memory.table.ts b/server/src/schema/tables/memory.table.ts deleted file mode 100644 index 408f7bca19..0000000000 --- a/server/src/schema/tables/memory.table.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { MemoryType } from 'src/enum'; -import { memory_delete_audit } from 'src/schema/functions'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - Column, - CreateDateColumn, - DeleteDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('memory') -@UpdatedAtTrigger('memory_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: memory_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -export class MemoryTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @DeleteDateColumn() - deletedAt!: Timestamp | null; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - - @Column() - type!: MemoryType; - - @Column({ type: 'jsonb' }) - data!: object; - - /** unless set to true, will be automatically deleted in the future */ - @Column({ type: 'boolean', default: false }) - isSaved!: Generated; - - /** memories are sorted in ascending order by this value */ - @Column({ type: 'timestamp with time zone' }) - memoryAt!: Timestamp; - - /** when the user last viewed the memory */ - @Column({ type: 'timestamp with time zone', nullable: true }) - seenAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - showAt!: Timestamp | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - hideAt!: Timestamp | null; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/move.table.ts b/server/src/schema/tables/move.table.ts deleted file mode 100644 index 1afda2767a..0000000000 --- a/server/src/schema/tables/move.table.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { PathType } from 'src/enum'; -import { Column, Generated, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools'; - -@Table('move_history') -// path lock (per entity) -@Unique({ name: 'UQ_entityId_pathType', columns: ['entityId', 'pathType'] }) -// new path lock (global) -@Unique({ name: 'UQ_newPath', columns: ['newPath'] }) -export class MoveTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @Column({ type: 'uuid' }) - entityId!: string; - - @Column({ type: 'character varying' }) - pathType!: PathType; - - @Column({ type: 'character varying' }) - oldPath!: string; - - @Column({ type: 'character varying' }) - newPath!: string; -} diff --git a/server/src/schema/tables/natural-earth-countries.table.ts b/server/src/schema/tables/natural-earth-countries.table.ts deleted file mode 100644 index c59d15fc21..0000000000 --- a/server/src/schema/tables/natural-earth-countries.table.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Column, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools'; - -@Table({ name: 'naturalearth_countries', primaryConstraintName: 'naturalearth_countries_pkey' }) -export class NaturalEarthCountriesTable { - @PrimaryGeneratedColumn({ strategy: 'identity' }) - id!: Generated; - - @Column({ type: 'character varying', length: 50 }) - admin!: string; - - @Column({ type: 'character varying', length: 3 }) - admin_a3!: string; - - @Column({ type: 'character varying', length: 50 }) - type!: string; - - @Column({ type: 'polygon' }) - coordinates!: string; -} diff --git a/server/src/schema/tables/notification.table.ts b/server/src/schema/tables/notification.table.ts deleted file mode 100644 index 01a93a73e5..0000000000 --- a/server/src/schema/tables/notification.table.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { NotificationLevel, NotificationType } from 'src/enum'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - Column, - CreateDateColumn, - DeleteDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('notification') -@UpdatedAtTrigger('notification_updatedAt') -export class NotificationTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @DeleteDateColumn() - deletedAt!: Timestamp | null; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true }) - userId!: string; - - @Column({ default: NotificationLevel.Info }) - level!: Generated; - - @Column({ default: NotificationLevel.Info }) - type!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - data!: any | null; - - @Column() - title!: string; - - @Column({ type: 'text', nullable: true }) - description!: string | null; - - @Column({ type: 'timestamp with time zone', nullable: true }) - readAt!: Timestamp | null; -} diff --git a/server/src/schema/tables/ocr-search.table.ts b/server/src/schema/tables/ocr-search.table.ts deleted file mode 100644 index 3449725adb..0000000000 --- a/server/src/schema/tables/ocr-search.table.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; - -@Table('ocr_search') -@Index({ - name: 'idx_ocr_search_text', - using: 'gin', - expression: 'f_unaccent("text") gin_trgm_ops', -}) -export class OcrSearchTable { - @ForeignKeyColumn(() => AssetTable, { - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - primary: true, - }) - assetId!: string; - - @Column({ type: 'text' }) - text!: string; -} diff --git a/server/src/schema/tables/partner-audit.table.ts b/server/src/schema/tables/partner-audit.table.ts deleted file mode 100644 index fa2f0c27cc..0000000000 --- a/server/src/schema/tables/partner-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('partner_audit') -export class PartnerAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - sharedById!: string; - - @Column({ type: 'uuid', index: true }) - sharedWithId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/partner.table.ts b/server/src/schema/tables/partner.table.ts deleted file mode 100644 index 8fc332cb12..0000000000 --- a/server/src/schema/tables/partner.table.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { partner_delete_audit } from 'src/schema/functions'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('partner') -@UpdatedAtTrigger('partner_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: partner_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -export class PartnerTable { - @ForeignKeyColumn(() => UserTable, { - onDelete: 'CASCADE', - primary: true, - // [sharedById, sharedWithId] is the PK constraint - index: false, - }) - sharedById!: string; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', primary: true }) - sharedWithId!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @CreateIdColumn({ index: true }) - createId!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @Column({ type: 'boolean', default: false }) - inTimeline!: Generated; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/person-audit.table.ts b/server/src/schema/tables/person-audit.table.ts deleted file mode 100644 index 8a899a1808..0000000000 --- a/server/src/schema/tables/person-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('person_audit') -export class PersonAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', index: true }) - personId!: string; - - @Column({ type: 'uuid', index: true }) - ownerId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/person.table.ts b/server/src/schema/tables/person.table.ts deleted file mode 100644 index 3b523a39d2..0000000000 --- a/server/src/schema/tables/person.table.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { person_delete_audit } from 'src/schema/functions'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - Check, - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('person') -@UpdatedAtTrigger('person_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: person_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -@Check({ name: 'person_birthDate_chk', expression: `"birthDate" <= CURRENT_DATE` }) -export class PersonTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - - @Column({ default: '' }) - name!: Generated; - - @Column({ default: '' }) - thumbnailPath!: Generated; - - @Column({ type: 'boolean', default: false }) - isHidden!: Generated; - - @Column({ type: 'date', nullable: true }) - birthDate!: Timestamp | null; - - @ForeignKeyColumn(() => AssetFaceTable, { onDelete: 'SET NULL', nullable: true }) - faceAssetId!: string | null; - - @Column({ type: 'boolean', default: false }) - isFavorite!: Generated; - - @Column({ type: 'character varying', nullable: true, default: null }) - color!: string | null; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/plugin.table.ts b/server/src/schema/tables/plugin.table.ts deleted file mode 100644 index 3de7ca63c9..0000000000 --- a/server/src/schema/tables/plugin.table.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { PluginContext } from 'src/enum'; -import { - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - Index, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; -import type { JSONSchema } from 'src/types/plugin-schema.types'; - -@Table('plugin') -export class PluginTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @Column({ index: true, unique: true }) - name!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column() - author!: string; - - @Column() - version!: string; - - @Column() - wasmPath!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; -} - -@Index({ columns: ['supportedContexts'], using: 'gin' }) -@Table('plugin_filter') -export class PluginFilterTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Column({ index: true }) - pluginId!: string; - - @Column({ index: true, unique: true }) - methodName!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column({ type: 'character varying', array: true }) - supportedContexts!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - schema!: JSONSchema | null; -} - -@Index({ columns: ['supportedContexts'], using: 'gin' }) -@Table('plugin_action') -export class PluginActionTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => PluginTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - @Column({ index: true }) - pluginId!: string; - - @Column({ index: true, unique: true }) - methodName!: string; - - @Column() - title!: string; - - @Column() - description!: string; - - @Column({ type: 'character varying', array: true }) - supportedContexts!: Generated; - - @Column({ type: 'jsonb', nullable: true }) - schema!: JSONSchema | null; -} diff --git a/server/src/schema/tables/shared-link-asset.table.ts b/server/src/schema/tables/shared-link-asset.table.ts deleted file mode 100644 index 37e6a3d9f0..0000000000 --- a/server/src/schema/tables/shared-link-asset.table.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AssetTable } from 'src/schema/tables/asset.table'; -import { SharedLinkTable } from 'src/schema/tables/shared-link.table'; -import { ForeignKeyColumn, Table } from 'src/sql-tools'; - -@Table('shared_link_asset') -export class SharedLinkAssetTable { - @ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - assetId!: string; - - @ForeignKeyColumn(() => SharedLinkTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true }) - sharedLinkId!: string; -} diff --git a/server/src/schema/tables/shared-link.table.ts b/server/src/schema/tables/shared-link.table.ts deleted file mode 100644 index 80e2d7cdf4..0000000000 --- a/server/src/schema/tables/shared-link.table.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { SharedLinkType } from 'src/enum'; -import { AlbumTable } from 'src/schema/tables/album.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, -} from 'src/sql-tools'; - -@Table('shared_link') -export class SharedLinkTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @Column({ type: 'character varying', nullable: true }) - description!: string | null; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - userId!: string; - - @Column({ type: 'bytea', index: true, unique: true }) - key!: Buffer; // use to access the individual asset - - @Column() - type!: SharedLinkType; - - @CreateDateColumn() - createdAt!: Generated; - - @Column({ type: 'timestamp with time zone', nullable: true }) - expiresAt!: Timestamp | null; - - @Column({ type: 'boolean', default: false }) - allowUpload!: boolean; - - @ForeignKeyColumn(() => AlbumTable, { nullable: true, onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - albumId!: string | null; - - @Column({ type: 'boolean', default: true }) - allowDownload!: Generated; - - @Column({ type: 'boolean', default: true }) - showExif!: Generated; - - @Column({ type: 'character varying', nullable: true }) - password!: string | null; - - @Column({ type: 'character varying', nullable: true, unique: true }) - slug!: string | null; -} diff --git a/server/src/schema/tables/smart-search.table.ts b/server/src/schema/tables/smart-search.table.ts deleted file mode 100644 index dc140efb2f..0000000000 --- a/server/src/schema/tables/smart-search.table.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AssetTable } from 'src/schema/tables/asset.table'; -import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools'; - -@Table({ name: 'smart_search' }) -@Index({ - name: 'clip_index', - using: 'hnsw', - expression: `embedding vector_cosine_ops`, - with: `ef_construction = 300, m = 16`, - synchronize: false, -}) -export class SmartSearchTable { - @ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', primary: true }) - assetId!: string; - - @Column({ type: 'vector', length: 512, storage: 'external', synchronize: false }) - embedding!: string; -} diff --git a/server/src/schema/tables/stack-audit.table.ts b/server/src/schema/tables/stack-audit.table.ts deleted file mode 100644 index d46ff95e57..0000000000 --- a/server/src/schema/tables/stack-audit.table.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('stack_audit') -export class StackAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid' }) - stackId!: string; - - @Column({ type: 'uuid' }) - userId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/stack.table.ts b/server/src/schema/tables/stack.table.ts deleted file mode 100644 index 9c9eb81373..0000000000 --- a/server/src/schema/tables/stack.table.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { stack_delete_audit } from 'src/schema/functions'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - CreateDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('stack') -@UpdatedAtTrigger('stack_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: stack_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -export class StackTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @UpdateIdColumn() - updateId!: Generated; - - //TODO: Add constraint to ensure primary asset exists in the assets array - @ForeignKeyColumn(() => AssetTable, { nullable: false, unique: true }) - primaryAssetId!: string; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - ownerId!: string; -} diff --git a/server/src/schema/tables/sync-checkpoint.table.ts b/server/src/schema/tables/sync-checkpoint.table.ts deleted file mode 100644 index 6ad4c54a86..0000000000 --- a/server/src/schema/tables/sync-checkpoint.table.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { SyncEntityType } from 'src/enum'; -import { SessionTable } from 'src/schema/tables/session.table'; -import { - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - PrimaryColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('session_sync_checkpoint') -@UpdatedAtTrigger('session_sync_checkpoint_updatedAt') -export class SessionSyncCheckpointTable { - @ForeignKeyColumn(() => SessionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', primary: true }) - sessionId!: string; - - @PrimaryColumn({ type: 'character varying' }) - type!: SyncEntityType; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @Column() - ack!: string; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/tag-asset.table.ts b/server/src/schema/tables/tag-asset.table.ts deleted file mode 100644 index 3ea2361b4f..0000000000 --- a/server/src/schema/tables/tag-asset.table.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AssetTable } from 'src/schema/tables/asset.table'; -import { TagTable } from 'src/schema/tables/tag.table'; -import { ForeignKeyColumn, Index, Table } from 'src/sql-tools'; - -@Index({ columns: ['assetId', 'tagId'] }) -@Table('tag_asset') -export class TagAssetTable { - @ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true, index: true }) - assetId!: string; - - @ForeignKeyColumn(() => TagTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true, index: true }) - tagId!: string; -} diff --git a/server/src/schema/tables/tag-closure.table.ts b/server/src/schema/tables/tag-closure.table.ts deleted file mode 100644 index aeb8c8cf11..0000000000 --- a/server/src/schema/tables/tag-closure.table.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TagTable } from 'src/schema/tables/tag.table'; -import { ForeignKeyColumn, Table } from 'src/sql-tools'; - -@Table('tag_closure') -export class TagClosureTable { - @ForeignKeyColumn(() => TagTable, { primary: true, onDelete: 'CASCADE', onUpdate: 'NO ACTION', index: true }) - id_ancestor!: string; - - @ForeignKeyColumn(() => TagTable, { primary: true, onDelete: 'CASCADE', onUpdate: 'NO ACTION', index: true }) - id_descendant!: string; -} diff --git a/server/src/schema/tables/tag.table.ts b/server/src/schema/tables/tag.table.ts deleted file mode 100644 index dc1fa2947b..0000000000 --- a/server/src/schema/tables/tag.table.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - PrimaryGeneratedColumn, - Table, - Timestamp, - Unique, - UpdateDateColumn, -} from 'src/sql-tools'; - -@Table('tag') -@UpdatedAtTrigger('tag_updatedAt') -@Unique({ columns: ['userId', 'value'] }) -export class TagTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => UserTable, { - onUpdate: 'CASCADE', - onDelete: 'CASCADE', - // [userId, value] makes this redundant - index: false, - }) - userId!: string; - - @Column() - value!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @UpdateDateColumn() - updatedAt!: Generated; - - @Column({ type: 'character varying', nullable: true, default: null }) - color!: string | null; - - @ForeignKeyColumn(() => TagTable, { nullable: true, onDelete: 'CASCADE' }) - parentId!: string | null; - - @UpdateIdColumn({ index: true }) - updateId!: Generated; -} diff --git a/server/src/schema/tables/user-audit.table.ts b/server/src/schema/tables/user-audit.table.ts deleted file mode 100644 index 084b42fb65..0000000000 --- a/server/src/schema/tables/user-audit.table.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('user_audit') -export class UserAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid' }) - userId!: string; - - @CreateDateColumn({ default: () => 'clock_timestamp()', index: true }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/user-metadata-audit.table.ts b/server/src/schema/tables/user-metadata-audit.table.ts deleted file mode 100644 index 63f503ab85..0000000000 --- a/server/src/schema/tables/user-metadata-audit.table.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { PrimaryGeneratedUuidV7Column } from 'src/decorators'; -import { UserMetadataKey } from 'src/enum'; -import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools'; - -@Table('user_metadata_audit') -export class UserMetadataAuditTable { - @PrimaryGeneratedUuidV7Column() - id!: Generated; - - @Column({ type: 'uuid', indexName: 'IDX_user_metadata_audit_user_id' }) - userId!: string; - - @Column({ indexName: 'IDX_user_metadata_audit_key' }) - key!: UserMetadataKey; - - @CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_user_metadata_audit_deleted_at' }) - deletedAt!: Generated; -} diff --git a/server/src/schema/tables/user-metadata.table.ts b/server/src/schema/tables/user-metadata.table.ts deleted file mode 100644 index a453ec6677..0000000000 --- a/server/src/schema/tables/user-metadata.table.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserMetadataKey } from 'src/enum'; -import { user_metadata_audit } from 'src/schema/functions'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - AfterDeleteTrigger, - Column, - ForeignKeyColumn, - Generated, - PrimaryColumn, - Table, - Timestamp, - UpdateDateColumn, -} from 'src/sql-tools'; -import { UserMetadata, UserMetadataItem } from 'src/types'; - -@UpdatedAtTrigger('user_metadata_updated_at') -@Table('user_metadata') -@AfterDeleteTrigger({ - scope: 'statement', - function: user_metadata_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) -export class UserMetadataTable implements UserMetadataItem { - @ForeignKeyColumn(() => UserTable, { - onUpdate: 'CASCADE', - onDelete: 'CASCADE', - primary: true, - // [userId, key] is the PK constraint - index: false, - }) - userId!: string; - - @PrimaryColumn({ type: 'character varying' }) - key!: T; - - @Column({ type: 'jsonb' }) - value!: UserMetadata[T]; - - @UpdateIdColumn({ indexName: 'IDX_user_metadata_update_id' }) - updateId!: Generated; - - @UpdateDateColumn({ indexName: 'IDX_user_metadata_updated_at' }) - updatedAt!: Generated; -} diff --git a/server/src/schema/tables/user.table.ts b/server/src/schema/tables/user.table.ts index 46d6656382..e84ccb162c 100644 --- a/server/src/schema/tables/user.table.ts +++ b/server/src/schema/tables/user.table.ts @@ -1,9 +1,6 @@ -import { ColumnType } from 'kysely'; import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators'; -import { UserAvatarColor, UserStatus } from 'src/enum'; -import { user_delete_audit } from 'src/schema/functions'; +import { UserStatus } from 'src/enum'; import { - AfterDeleteTrigger, Column, CreateDateColumn, DeleteDateColumn, @@ -17,12 +14,6 @@ import { @Table('user') @UpdatedAtTrigger('user_updatedAt') -@AfterDeleteTrigger({ - scope: 'statement', - function: user_delete_audit, - referencingOldTableAs: 'old', - when: 'pg_trigger_depth() = 0', -}) @Index({ columns: ['updatedAt', 'id'] }) export class UserTable { @PrimaryGeneratedColumn() @@ -34,51 +25,27 @@ export class UserTable { @Column({ default: '' }) password!: Generated; - @Column({ nullable: true }) - pinCode!: string | null; - @CreateDateColumn() createdAt!: Generated; - @Column({ default: '' }) - profileImagePath!: Generated; - @Column({ type: 'boolean', default: false }) isAdmin!: Generated; @Column({ type: 'boolean', default: true }) shouldChangePassword!: Generated; - @Column({ default: null }) - avatarColor!: UserAvatarColor | null; - @DeleteDateColumn() deletedAt!: Timestamp | null; - @Column({ default: '' }) - oauthId!: Generated; - @UpdateDateColumn() updatedAt!: Generated; - @Column({ unique: true, nullable: true, default: null }) - storageLabel!: string | null; - @Column({ default: '' }) name!: Generated; - @Column({ type: 'bigint', nullable: true }) - quotaSizeInBytes!: ColumnType | null; - - @Column({ type: 'bigint', default: 0 }) - quotaUsageInBytes!: Generated>; - @Column({ type: 'character varying', default: UserStatus.Active }) status!: Generated; - @Column({ type: 'timestamp with time zone', default: () => 'now()' }) - profileChangedAt!: Generated; - @UpdateIdColumn({ index: true }) updateId!: Generated; } diff --git a/server/src/schema/tables/version-history.table.ts b/server/src/schema/tables/version-history.table.ts deleted file mode 100644 index 143852c527..0000000000 --- a/server/src/schema/tables/version-history.table.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Column, CreateDateColumn, Generated, PrimaryGeneratedColumn, Table, Timestamp } from 'src/sql-tools'; - -@Table('version_history') -export class VersionHistoryTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @CreateDateColumn() - createdAt!: Generated; - - @Column() - version!: string; -} diff --git a/server/src/schema/tables/workflow.table.ts b/server/src/schema/tables/workflow.table.ts deleted file mode 100644 index 62a5531d8e..0000000000 --- a/server/src/schema/tables/workflow.table.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { PluginTriggerType } from 'src/enum'; -import { PluginActionTable, PluginFilterTable } from 'src/schema/tables/plugin.table'; -import { UserTable } from 'src/schema/tables/user.table'; -import { - Column, - CreateDateColumn, - ForeignKeyColumn, - Generated, - Index, - PrimaryGeneratedColumn, - Table, - Timestamp, -} from 'src/sql-tools'; -import type { ActionConfig, FilterConfig } from 'src/types/plugin-schema.types'; - -@Table('workflow') -export class WorkflowTable { - @PrimaryGeneratedColumn() - id!: Generated; - - @ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false }) - ownerId!: string; - - @Column() - triggerType!: PluginTriggerType; - - @Column({ nullable: true }) - name!: string | null; - - @Column() - description!: string; - - @CreateDateColumn() - createdAt!: Generated; - - @Column({ type: 'boolean', default: true }) - enabled!: boolean; -} - -@Index({ columns: ['workflowId', 'order'] }) -@Index({ columns: ['pluginFilterId'] }) -@Table('workflow_filter') -export class WorkflowFilterTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - workflowId!: Generated; - - @ForeignKeyColumn(() => PluginFilterTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - pluginFilterId!: string; - - @Column({ type: 'jsonb', nullable: true }) - filterConfig!: FilterConfig | null; - - @Column({ type: 'integer' }) - order!: number; -} - -@Index({ columns: ['workflowId', 'order'] }) -@Index({ columns: ['pluginActionId'] }) -@Table('workflow_action') -export class WorkflowActionTable { - @PrimaryGeneratedColumn('uuid') - id!: Generated; - - @ForeignKeyColumn(() => WorkflowTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - workflowId!: Generated; - - @ForeignKeyColumn(() => PluginActionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) - pluginActionId!: string; - - @Column({ type: 'jsonb', nullable: true }) - actionConfig!: ActionConfig | null; - - @Column({ type: 'integer' }) - order!: number; -} diff --git a/server/src/services/activity.service.spec.ts b/server/src/services/activity.service.spec.ts deleted file mode 100644 index aea547e6db..0000000000 --- a/server/src/services/activity.service.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { ReactionType } from 'src/dtos/activity.dto'; -import { ActivityService } from 'src/services/activity.service'; -import { factory, newUuid, newUuids } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(ActivityService.name, () => { - let sut: ActivityService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(ActivityService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getAll', () => { - it('should get all', async () => { - const [albumId, assetId, userId] = newUuids(); - - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.search.mockResolvedValue([]); - - await expect(sut.getAll(factory.auth({ user: { id: userId } }), { assetId, albumId })).resolves.toEqual([]); - - expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: undefined }); - }); - - it('should filter by type=like', async () => { - const [albumId, assetId, userId] = newUuids(); - - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.search.mockResolvedValue([]); - - await expect( - sut.getAll(factory.auth({ user: { id: userId } }), { assetId, albumId, type: ReactionType.LIKE }), - ).resolves.toEqual([]); - - expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: true }); - }); - - it('should filter by type=comment', async () => { - const [albumId, assetId] = newUuids(); - - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.search.mockResolvedValue([]); - - await expect(sut.getAll(factory.auth(), { assetId, albumId, type: ReactionType.COMMENT })).resolves.toEqual([]); - - expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: false }); - }); - }); - - describe('getStatistics', () => { - it('should get the comment and like count', async () => { - const [albumId, assetId] = newUuids(); - - mocks.activity.getStatistics.mockResolvedValue({ comments: 1, likes: 3 }); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); - - await expect(sut.getStatistics(factory.auth(), { assetId, albumId })).resolves.toEqual({ comments: 1, likes: 3 }); - }); - }); - - describe('addComment', () => { - it('should require access to the album', async () => { - const [albumId, assetId] = newUuids(); - - await expect( - sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should create a comment', async () => { - const [albumId, assetId, userId] = newUuids(); - const activity = factory.activity({ albumId, assetId, userId }); - - mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.create.mockResolvedValue(activity); - - await sut.create(factory.auth({ user: { id: userId } }), { - albumId, - assetId, - type: ReactionType.COMMENT, - comment: 'comment', - }); - - expect(mocks.activity.create).toHaveBeenCalledWith({ - userId: activity.userId, - albumId: activity.albumId, - assetId: activity.assetId, - comment: 'comment', - isLiked: false, - }); - }); - - it('should fail because activity is disabled for the album', async () => { - const [albumId, assetId] = newUuids(); - const activity = factory.activity({ albumId, assetId }); - - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.create.mockResolvedValue(activity); - - await expect( - sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should create a like', async () => { - const [albumId, assetId, userId] = newUuids(); - const activity = factory.activity({ userId, albumId, assetId, isLiked: true }); - - mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.create.mockResolvedValue(activity); - mocks.activity.search.mockResolvedValue([]); - - await sut.create(factory.auth({ user: { id: userId } }), { albumId, assetId, type: ReactionType.LIKE }); - - expect(mocks.activity.create).toHaveBeenCalledWith({ userId: activity.userId, albumId, assetId, isLiked: true }); - }); - - it('should skip if like exists', async () => { - const [albumId, assetId] = newUuids(); - const activity = factory.activity({ albumId, assetId, isLiked: true }); - - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId])); - mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId])); - mocks.activity.search.mockResolvedValue([activity]); - - await sut.create(factory.auth(), { albumId, assetId, type: ReactionType.LIKE }); - - expect(mocks.activity.create).not.toHaveBeenCalled(); - }); - }); - - describe('delete', () => { - it('should require access', async () => { - await expect(sut.delete(factory.auth(), newUuid())).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.activity.delete).not.toHaveBeenCalled(); - }); - - it('should let the activity owner delete a comment', async () => { - const activity = factory.activity(); - - mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set([activity.id])); - mocks.activity.delete.mockResolvedValue(); - - await sut.delete(factory.auth(), activity.id); - - expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id); - }); - - it('should let the album owner delete a comment', async () => { - const activity = factory.activity(); - - mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set([activity.id])); - mocks.activity.delete.mockResolvedValue(); - - await sut.delete(factory.auth(), activity.id); - - expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id); - }); - }); -}); diff --git a/server/src/services/activity.service.ts b/server/src/services/activity.service.ts deleted file mode 100644 index b1c25f8286..0000000000 --- a/server/src/services/activity.service.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Activity } from 'src/database'; -import { - ActivityCreateDto, - ActivityDto, - ActivityResponseDto, - ActivitySearchDto, - ActivityStatisticsResponseDto, - mapActivity, - MaybeDuplicate, - ReactionLevel, - ReactionType, -} from 'src/dtos/activity.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class ActivityService extends BaseService { - async getAll(auth: AuthDto, dto: ActivitySearchDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [dto.albumId] }); - const activities = await this.activityRepository.search({ - userId: dto.userId, - albumId: dto.albumId, - assetId: dto.level === ReactionLevel.ALBUM ? null : dto.assetId, - isLiked: dto.type && dto.type === ReactionType.LIKE, - }); - - return activities.map((activity) => mapActivity(activity)); - } - - async getStatistics(auth: AuthDto, dto: ActivityDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [dto.albumId] }); - return await this.activityRepository.getStatistics({ albumId: dto.albumId, assetId: dto.assetId }); - } - - async create(auth: AuthDto, dto: ActivityCreateDto): Promise> { - await this.requireAccess({ auth, permission: Permission.ActivityCreate, ids: [dto.albumId] }); - - const common = { - userId: auth.user.id, - assetId: dto.assetId, - albumId: dto.albumId, - }; - - let activity: Activity | undefined; - let duplicate = false; - - if (dto.type === ReactionType.LIKE) { - delete dto.comment; - [activity] = await this.activityRepository.search({ - ...common, - // `null` will search for an album like - assetId: dto.assetId ?? null, - isLiked: true, - }); - duplicate = !!activity; - } - - if (!activity) { - activity = await this.activityRepository.create({ - ...common, - isLiked: dto.type === ReactionType.LIKE, - comment: dto.comment, - }); - } - - return { duplicate, value: mapActivity(activity) }; - } - - async delete(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.ActivityDelete, ids: [id] }); - await this.activityRepository.delete(id); - } -} diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts deleted file mode 100644 index 209716db3a..0000000000 --- a/server/src/services/album.service.spec.ts +++ /dev/null @@ -1,1550 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import _ from 'lodash'; -import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; -import { AlbumUserRole, AssetOrder, UserMetadataKey } from 'src/enum'; -import { AlbumService } from 'src/services/album.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(AlbumService.name, () => { - let sut: AlbumService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(AlbumService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getStatistics', () => { - it('should get the album count', async () => { - mocks.album.getOwned.mockResolvedValue([]); - mocks.album.getShared.mockResolvedValue([]); - mocks.album.getNotShared.mockResolvedValue([]); - await expect(sut.getStatistics(authStub.admin)).resolves.toEqual({ - owned: 0, - shared: 0, - notShared: 0, - }); - - expect(mocks.album.getOwned).toHaveBeenCalledWith(authStub.admin.user.id); - expect(mocks.album.getShared).toHaveBeenCalledWith(authStub.admin.user.id); - expect(mocks.album.getNotShared).toHaveBeenCalledWith(authStub.admin.user.id); - }); - }); - - describe('getAll', () => { - it('gets list of albums for auth user', async () => { - const owner = factory.userAdmin(); - const album = { ...factory.album({ ownerId: owner.id }), owner }; - const sharedWithUserAlbum = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user: factory.user(), role: AlbumUserRole.Editor }], - }; - mocks.album.getOwned.mockResolvedValue([album, sharedWithUserAlbum]); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 0, - startDate: null, - endDate: null, - lastModifiedAssetTimestamp: null, - }, - { - albumId: sharedWithUserAlbum.id, - assetCount: 0, - startDate: null, - endDate: null, - lastModifiedAssetTimestamp: null, - }, - ]); - - const result = await sut.getAll(factory.auth({ user: owner }), {}); - expect(result).toHaveLength(2); - expect(result[0].id).toEqual(album.id); - expect(result[1].id).toEqual(sharedWithUserAlbum.id); - }); - - it('gets list of albums that have a specific asset', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset({ ownerId: owner.id }); - const album = { ...factory.album({ ownerId: owner.id }), owner, assets: [asset] }; - mocks.album.getByAssetId.mockResolvedValue([album]); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 1, - startDate: new Date('1970-01-01'), - endDate: new Date('1970-01-01'), - lastModifiedAssetTimestamp: new Date('1970-01-01'), - }, - ]); - - const result = await sut.getAll(factory.auth({ user: owner }), { assetId: asset.id }); - expect(result).toHaveLength(1); - expect(result[0].id).toEqual(album.id); - expect(mocks.album.getByAssetId).toHaveBeenCalledTimes(1); - }); - - it('gets list of albums that are shared', async () => { - const owner = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user: factory.user(), role: AlbumUserRole.Editor }], - }; - mocks.album.getShared.mockResolvedValue([album]); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 0, - startDate: null, - endDate: null, - lastModifiedAssetTimestamp: null, - }, - ]); - - const result = await sut.getAll(factory.auth({ user: owner }), { shared: true }); - expect(result).toHaveLength(1); - expect(result[0].id).toEqual(album.id); - expect(mocks.album.getShared).toHaveBeenCalledTimes(1); - }); - - it('gets list of albums that are NOT shared', async () => { - const owner = factory.userAdmin(); - const album = { ...factory.album({ ownerId: owner.id }), owner }; - mocks.album.getNotShared.mockResolvedValue([album]); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 0, - startDate: null, - endDate: null, - lastModifiedAssetTimestamp: null, - }, - ]); - - const result = await sut.getAll(factory.auth({ user: owner }), { shared: false }); - expect(result).toHaveLength(1); - expect(result[0].id).toEqual(album.id); - expect(mocks.album.getNotShared).toHaveBeenCalledTimes(1); - }); - }); - - it('counts assets correctly', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset({ ownerId: owner.id }); - const album = { ...factory.album({ ownerId: owner.id }), owner, assets: [asset] }; - mocks.album.getOwned.mockResolvedValue([album]); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 1, - startDate: new Date('1970-01-01'), - endDate: new Date('1970-01-01'), - lastModifiedAssetTimestamp: new Date('1970-01-01'), - }, - ]); - - const result = await sut.getAll(factory.auth({ user: owner }), {}); - - expect(result).toHaveLength(1); - expect(result[0].assetCount).toEqual(1); - expect(mocks.album.getOwned).toHaveBeenCalledTimes(1); - }); - - describe('create', () => { - it('creates album', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const asset = { ...factory.asset({ ownerId: owner.id }), exifInfo: factory.exif() }; - const album = { - ...factory.album({ ownerId: owner.id, albumName: 'Empty album' }), - owner, - assets: [asset], - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.create.mockResolvedValue(album); - mocks.user.get.mockResolvedValue(user); - mocks.user.getMetadata.mockResolvedValue([]); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - - await sut.create(factory.auth({ user: owner }), { - albumName: 'Empty album', - albumUsers: [{ userId: user.id, role: AlbumUserRole.Editor }], - description: 'Album description', - assetIds: [asset.id], - }); - - expect(mocks.album.create).toHaveBeenCalledWith( - { - ownerId: owner.id, - albumName: album.albumName, - description: album.description, - order: album.order, - albumThumbnailAssetId: asset.id, - }, - [asset.id], - [{ userId: user.id, role: AlbumUserRole.Editor }], - ); - - expect(mocks.user.get).toHaveBeenCalledWith(user.id, {}); - expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]), false); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { - id: album.id, - userId: user.id, - }); - }); - - it('creates album with assetOrder from user preferences', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const asset = { ...factory.asset({ ownerId: owner.id }), exifInfo: factory.exif() }; - const album = { - ...factory.album({ ownerId: owner.id, albumName: 'Empty album' }), - owner, - assets: [asset], - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.create.mockResolvedValue(album); - mocks.user.get.mockResolvedValue(user); - mocks.user.getMetadata.mockResolvedValue([ - { - key: UserMetadataKey.Preferences, - value: { - albums: { - defaultAssetOrder: AssetOrder.Asc, - }, - }, - }, - ]); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - - await sut.create(factory.auth({ user: owner }), { - albumName: 'Empty album', - albumUsers: [{ userId: user.id, role: AlbumUserRole.Editor }], - description: 'Album description', - assetIds: [asset.id], - }); - - expect(mocks.album.create).toHaveBeenCalledWith( - { - ownerId: owner.id, - albumName: album.albumName, - description: album.description, - order: 'asc', - albumThumbnailAssetId: asset.id, - }, - [asset.id], - [{ userId: user.id, role: AlbumUserRole.Editor }], - ); - - expect(mocks.user.get).toHaveBeenCalledWith(user.id, {}); - expect(mocks.user.getMetadata).toHaveBeenCalledWith(owner.id); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]), false); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { - id: album.id, - userId: user.id, - }); - }); - - it('should require valid userIds', async () => { - mocks.user.get.mockResolvedValue(void 0); - await expect( - sut.create(factory.auth(), { - albumName: 'Empty album', - albumUsers: [{ userId: 'unknown-user', role: AlbumUserRole.Editor }], - }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {}); - expect(mocks.album.create).not.toHaveBeenCalled(); - }); - - it('should only add assets the user is allowed to access', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const asset = { ...factory.asset({ ownerId: owner.id }), exifInfo: factory.exif() }; - const album = { - ...factory.album({ ownerId: owner.id, albumName: 'Test album' }), - owner, - assets: [asset], - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.user.get.mockResolvedValue(user); - mocks.album.create.mockResolvedValue(album); - mocks.user.getMetadata.mockResolvedValue([]); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - - await sut.create(factory.auth({ user: owner }), { - albumName: 'Test album', - description: 'Album description', - assetIds: [asset.id, 'asset-2'], - }); - - expect(mocks.album.create).toHaveBeenCalledWith( - { - ownerId: owner.id, - albumName: album.albumName, - description: album.description, - order: 'desc', - albumThumbnailAssetId: asset.id, - }, - [asset.id], - [], - ); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id, 'asset-2']), false); - }); - - it('should throw an error if the userId is the ownerId', async () => { - const owner = factory.userAdmin(); - mocks.user.get.mockResolvedValue(owner); - await expect( - sut.create(factory.auth({ user: owner }), { - albumName: 'Empty album', - albumUsers: [{ userId: owner.id, role: AlbumUserRole.Editor }], - }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.album.create).not.toHaveBeenCalled(); - }); - }); - - describe('update', () => { - it('should prevent updating an album that does not exist', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set()); - mocks.album.getById.mockResolvedValue(void 0); - - await expect( - sut.update(factory.auth(), 'invalid-id', { - albumName: 'Album', - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should prevent updating a not owned album (shared with auth user)', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { ...factory.album({ ownerId: user.id }), user }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); - await expect( - sut.update(factory.auth({ user: owner }), album.id, { - albumName: 'new album name', - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should require a valid thumbnail asset id', async () => { - const owner = factory.userAdmin(); - const album = { ...factory.album({ ownerId: owner.id }), owner }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValue(new Set()); - - await expect( - sut.update(factory.auth({ user: owner }), album.id, { - albumThumbnailAssetId: 'not-in-album', - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.album.getAssetIds).toHaveBeenCalledWith(album.id, ['not-in-album']); - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should allow the owner to update the album', async () => { - const owner = factory.userAdmin(); - const album = { ...factory.album({ ownerId: owner.id }), owner }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - - mocks.album.getById.mockResolvedValue(album); - mocks.album.update.mockResolvedValue(album); - - await sut.update(factory.auth({ user: owner }), album.id, { - albumName: 'new album name', - }); - - expect(mocks.album.update).toHaveBeenCalledTimes(1); - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - albumName: 'new album name', - }); - }); - }); - - describe('delete', () => { - it('should require permissions', async () => { - const album = factory.album(); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); - - await expect(sut.delete(factory.auth(), album.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.album.delete).not.toHaveBeenCalled(); - }); - - it('should not let a shared user delete the album', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { ...factory.album({ ownerId: user.id }), owner: user }; - mocks.album.getById.mockResolvedValue(album); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); - - await expect(sut.delete(factory.auth({ user: owner }), album.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.album.delete).not.toHaveBeenCalled(); - }); - - it('should let the owner delete an album', async () => { - const owner = factory.userAdmin(); - const album = { ...factory.album({ ownerId: owner.id }), owner }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - - await sut.delete(factory.auth({ user: owner }), album.id); - - expect(mocks.album.delete).toHaveBeenCalledTimes(1); - expect(mocks.album.delete).toHaveBeenCalledWith(album.id); - }); - }); - - describe('addUsers', () => { - it('should throw an error if the auth user is not the owner', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { ...factory.album({ ownerId: owner.id }), owner }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set()); - await expect( - sut.addUsers(factory.auth({ user }), album.id, { albumUsers: [{ userId: owner.id }] }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should throw an error if the userId is already added', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - await expect( - sut.addUsers(factory.auth({ user: owner }), album.id, { albumUsers: [{ userId: user.id }] }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.album.update).not.toHaveBeenCalled(); - expect(mocks.user.get).not.toHaveBeenCalled(); - }); - - it('should throw an error if the userId does not exist', async () => { - const owner = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.user.get.mockResolvedValue(void 0); - await expect( - sut.addUsers(factory.auth({ user: owner }), album.id, { albumUsers: [{ userId: 'unknown-user' }] }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.album.update).not.toHaveBeenCalled(); - expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {}); - }); - - it('should throw an error if the userId is the ownerId', async () => { - const owner = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - await expect( - sut.addUsers(factory.auth({ user: owner }), album.id, { - albumUsers: [{ userId: owner.id }], - }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.album.update).not.toHaveBeenCalled(); - expect(mocks.user.get).not.toHaveBeenCalled(); - }); - - it('should add valid shared users', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.update.mockResolvedValue(album); - mocks.user.get.mockResolvedValue(user); - mocks.albumUser.create.mockResolvedValue({ - userId: user.id, - albumId: album.id, - role: AlbumUserRole.Editor, - }); - await sut.addUsers(factory.auth({ user: owner }), album.id, { - albumUsers: [{ userId: user.id }], - }); - expect(mocks.albumUser.create).toHaveBeenCalledWith({ - userId: user.id, - albumId: album.id, - }); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { - id: album.id, - userId: user.id, - }); - }); - }); - - describe('removeUser', () => { - it('should require a valid album id', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-1'])); - mocks.album.getById.mockResolvedValue(void 0); - await expect(sut.removeUser(factory.auth(), 'album-1', 'user-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should remove a shared user from an owned album', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.albumUser.delete.mockResolvedValue(); - - await expect(sut.removeUser(factory.auth({ user: owner }), album.id, user.id)).resolves.toBeUndefined(); - - expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); - expect(mocks.albumUser.delete).toHaveBeenCalledWith({ - albumId: album.id, - userId: user.id, - }); - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false }); - }); - - it('should prevent removing a shared user from a not-owned album (shared with auth user)', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const user2 = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: user.id }), - owner: user, - albumUsers: [ - { user: owner, role: AlbumUserRole.Editor }, - { user: user2, role: AlbumUserRole.Editor }, - ], - }; - mocks.album.getById.mockResolvedValue(album); - - await expect(sut.removeUser(factory.auth({ user: owner }), album.id, user2.id)).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.albumUser.delete).not.toHaveBeenCalled(); - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([album.id])); - }); - - it('should allow a shared user to remove themselves', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: user.id }), - owner: user, - albumUsers: [{ user: owner, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - mocks.albumUser.delete.mockResolvedValue(); - - await sut.removeUser(factory.auth({ user: owner }), album.id, owner.id); - - expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); - expect(mocks.albumUser.delete).toHaveBeenCalledWith({ - albumId: album.id, - userId: owner.id, - }); - }); - - it('should allow a shared user to remove themselves using "me"', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - mocks.albumUser.delete.mockResolvedValue(); - - await sut.removeUser(factory.auth({ user }), album.id, 'me'); - - expect(mocks.albumUser.delete).toHaveBeenCalledTimes(1); - expect(mocks.albumUser.delete).toHaveBeenCalledWith({ - albumId: album.id, - userId: user.id, - }); - }); - - it('should not allow the owner to be removed', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - - await expect(sut.removeUser(factory.auth({ user: owner }), album.id, owner.id)).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should throw an error for a user not in the album', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - - await expect(sut.removeUser(factory.auth({ user: owner }), album.id, 'user-3')).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - }); - - describe('updateUser', () => { - it('should update user role', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.albumUser.update.mockResolvedValue(); - - await sut.updateUser(factory.auth({ user: owner }), album.id, user.id, { - role: AlbumUserRole.Viewer, - }); - expect(mocks.albumUser.update).toHaveBeenCalledWith( - { albumId: album.id, userId: user.id }, - { role: AlbumUserRole.Viewer }, - ); - }); - }); - - describe('getAlbumInfo', () => { - it('should get a shared album', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 1, - startDate: new Date('1970-01-01'), - endDate: new Date('1970-01-01'), - lastModifiedAssetTimestamp: new Date('1970-01-01'), - }, - ]); - - await sut.get(factory.auth({ user: owner }), album.id, {}); - - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true }); - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([album.id])); - }); - - it('should get a shared album via a shared link', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - mocks.access.album.checkSharedLinkAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 1, - startDate: new Date('1970-01-01'), - endDate: new Date('1970-01-01'), - lastModifiedAssetTimestamp: new Date('1970-01-01'), - }, - ]); - - const auth = factory.auth({ sharedLink: {} }); - await sut.get(auth, album.id, {}); - - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true }); - expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id])); - }); - - it('should get a shared album via shared with user', async () => { - const owner = factory.userAdmin(); - const user = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - mocks.album.getById.mockResolvedValue(album); - mocks.access.album.checkSharedAlbumAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getMetadataForIds.mockResolvedValue([ - { - albumId: album.id, - assetCount: 1, - startDate: new Date('1970-01-01'), - endDate: new Date('1970-01-01'), - lastModifiedAssetTimestamp: new Date('1970-01-01'), - }, - ]); - - await sut.get(factory.auth({ user }), album.id, {}); - - expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true }); - expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith( - user.id, - new Set([album.id]), - AlbumUserRole.Viewer, - ); - }); - - it('should throw an error for no access', async () => { - const auth = factory.auth(); - await expect(sut.get(auth, 'album-123', {})).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(auth.user.id, new Set(['album-123'])); - expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith( - auth.user.id, - new Set(['album-123']), - AlbumUserRole.Viewer, - ); - }); - }); - - describe('addAssets', () => { - it('should allow the owner to add assets', async () => { - const owner = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssets(factory.auth({ user: owner }), album.id, { ids: [asset1.id, asset2.id, asset3.id] }), - ).resolves.toEqual([ - { success: true, id: asset1.id }, - { success: true, id: asset2.id }, - { success: true, id: asset3.id }, - ]); - - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]); - }); - - it('should not set the thumbnail if the album has one already', async () => { - const owner = factory.userAdmin(); - const [asset1, asset2] = [factory.asset(), factory.asset()]; - const album = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: asset1.id }), - owner, - assets: [{ ...asset1, exifInfo: factory.exif() }], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset2.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - - await expect(sut.addAssets(factory.auth({ user: owner }), album.id, { ids: [asset2.id] })).resolves.toEqual([ - { success: true, id: asset2.id }, - ]); - - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIds).toHaveBeenCalled(); - }); - - it('should allow a shared user to add assets', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkSharedAlbumAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssets(factory.auth({ user }), album.id, { ids: [asset1.id, asset2.id, asset3.id] }), - ).resolves.toEqual([ - { success: true, id: asset1.id }, - { success: true, id: asset2.id }, - { success: true, id: asset3.id }, - ]); - - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { - id: album.id, - recipientId: owner.id, - }); - }); - - it('should not allow a shared user with viewer access to add assets', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Viewer }], - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkSharedAlbumAccess.mockResolvedValue(new Set()); - mocks.album.getById.mockResolvedValue(album); - - await expect( - sut.addAssets(factory.auth({ user }), album.id, { ids: [asset1.id, asset2.id, asset3.id] }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should allow a shared link user to add assets', async () => { - const owner = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - const auth = factory.auth({ sharedLink: { allowUpload: true } }); - mocks.access.album.checkSharedLinkAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - - await expect(sut.addAssets(auth, album.id, { ids: [asset1.id, asset2.id, asset3.id] })).resolves.toEqual([ - { success: true, id: asset1.id }, - { success: true, id: asset2.id }, - { success: true, id: asset3.id }, - ]); - - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIds).toHaveBeenCalledWith(album.id, [asset1.id, asset2.id, asset3.id]); - - expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink?.id, new Set([album.id])); - }); - - it('should allow adding assets shared via partner sharing', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const asset = factory.asset({ ownerId: user.id }); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([asset.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - - await expect(sut.addAssets(factory.auth({ user: owner }), album.id, { ids: [asset.id] })).resolves.toEqual([ - { success: true, id: asset.id }, - ]); - - expect(mocks.album.update).toHaveBeenCalledWith(album.id, { - id: album.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset.id, - }); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id])); - }); - - it('should skip duplicate assets', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - assets: [{ ...asset, exifInfo: factory.exif() }], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set([asset.id])); - - await expect(sut.addAssets(factory.auth({ user: owner }), album.id, { ids: [asset.id] })).resolves.toEqual([ - { success: false, id: asset.id, error: BulkIdErrorReason.DUPLICATE }, - ]); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should skip assets not shared with user', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()); - - await expect(sut.addAssets(factory.auth({ user: owner }), album.id, { ids: [asset.id] })).resolves.toEqual([ - { success: false, id: asset.id, error: BulkIdErrorReason.NO_PERMISSION }, - ]); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id]), false); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(owner.id, new Set([asset.id])); - }); - - it('should not allow unauthorized access to the album', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const asset = factory.asset({ ownerId: user.id }); - mocks.album.getById.mockResolvedValue(album); - - await expect(sut.addAssets(factory.auth({ user }), album.id, { ids: [asset.id] })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalled(); - expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalled(); - }); - - it('should not allow unauthorized shared link access to the album', async () => { - const owner = factory.userAdmin(); - const album = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const asset = factory.asset(); - mocks.album.getById.mockResolvedValue(album); - - await expect( - sut.addAssets(factory.auth({ sharedLink: { allowUpload: true } }), album.id, { ids: [asset.id] }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalled(); - }); - }); - - describe('addAssetsToAlbums', () => { - it('should allow the owner to add assets', async () => { - const owner = factory.userAdmin(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user: owner }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ success: true, error: undefined }); - - expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumId: album1.id, assetId: asset1.id }, - { albumId: album1.id, assetId: asset2.id }, - { albumId: album1.id, assetId: asset3.id }, - { albumId: album2.id, assetId: asset1.id }, - { albumId: album2.id, assetId: asset2.id }, - { albumId: album2.id, assetId: asset3.id }, - ]); - }); - - it('should not set the thumbnail if the album has one already', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset(); - const album1 = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: asset.id }), - owner, - albumAssets: [asset], - }; - const album2 = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: asset.id }), - owner, - albumAssets: [asset], - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user: owner }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ success: true, error: undefined }); - - expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset.id, - }); - expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumId: album1.id, assetId: asset1.id }, - { albumId: album1.id, assetId: asset2.id }, - { albumId: album1.id, assetId: asset3.id }, - { albumId: album2.id, assetId: asset1.id }, - { albumId: album2.id, assetId: asset2.id }, - { albumId: album2.id, assetId: asset3.id }, - ]); - }); - - it('should allow a shared user to add assets', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Editor }], - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ success: true, error: undefined }); - - expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumId: album1.id, assetId: asset1.id }, - { albumId: album1.id, assetId: asset2.id }, - { albumId: album1.id, assetId: asset3.id }, - { albumId: album2.id, assetId: asset1.id }, - { albumId: album2.id, assetId: asset2.id }, - { albumId: album2.id, assetId: asset3.id }, - ]); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { - id: album1.id, - recipientId: owner.id, - }); - expect(mocks.event.emit).toHaveBeenCalledWith('AlbumUpdate', { - id: album2.id, - recipientId: owner.id, - }); - }); - - it('should not allow a shared user with viewer access to add assets', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Viewer }], - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumUsers: [{ user, role: AlbumUserRole.Viewer }], - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkSharedAlbumAccess.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ - success: false, - error: BulkIdErrorReason.NO_PERMISSION, - }); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should not allow a shared link user to add assets to multiple albums', async () => { - const owner = factory.userAdmin(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.access.album.checkSharedLinkAccess.mockResolvedValueOnce(new Set([album1.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - const auth = factory.auth({ user: owner, sharedLink: { allowUpload: true } }); - await expect( - sut.addAssetsToAlbums(auth, { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ success: true, error: undefined }); - - expect(mocks.album.update).toHaveBeenCalledTimes(1); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumId: album1.id, assetId: asset1.id }, - { albumId: album1.id, assetId: asset2.id }, - { albumId: album1.id, assetId: asset3.id }, - ]); - expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith( - auth.sharedLink?.id, - new Set([album1.id, album2.id]), - ); - }); - - it('should allow adding assets shared via partner sharing', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [ - factory.asset({ ownerId: user.id }), - factory.asset({ ownerId: user.id }), - factory.asset({ ownerId: user.id }), - ]; - mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); - mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user: owner }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ success: true, error: undefined }); - - expect(mocks.album.update).toHaveBeenCalledTimes(2); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album1.id, { - id: album1.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.update).toHaveBeenNthCalledWith(2, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumId: album1.id, assetId: asset1.id }, - { albumId: album1.id, assetId: asset2.id }, - { albumId: album1.id, assetId: asset3.id }, - { albumId: album2.id, assetId: asset1.id }, - { albumId: album2.id, assetId: asset2.id }, - { albumId: album2.id, assetId: asset3.id }, - ]); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( - owner.id, - new Set([asset1.id, asset2.id, asset3.id]), - ); - }); - - it('should skip some duplicate assets', async () => { - const owner = factory.userAdmin(); - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumAssets: [asset1, asset2, asset3], - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - mocks.access.album.checkOwnerAccess.mockResolvedValueOnce(new Set([album1.id, album2.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getAssetIds - .mockResolvedValueOnce(new Set([asset1.id, asset2.id, asset3.id])) - .mockResolvedValueOnce(new Set()); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user: owner }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ success: true, error: undefined }); - - expect(mocks.album.update).toHaveBeenCalledTimes(1); - expect(mocks.album.update).toHaveBeenNthCalledWith(1, album2.id, { - id: album2.id, - updatedAt: expect.any(Date), - albumThumbnailAssetId: asset1.id, - }); - expect(mocks.album.addAssetIdsToAlbums).toHaveBeenCalledWith([ - { albumId: album2.id, assetId: asset1.id }, - { albumId: album2.id, assetId: asset2.id }, - { albumId: album2.id, assetId: asset3.id }, - ]); - }); - - it('should skip all duplicate assets', async () => { - const owner = factory.userAdmin(); - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumAssets: [asset1, asset2, asset3], - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - albumAssets: [asset1, asset2, asset3], - }; - mocks.access.album.checkOwnerAccess - .mockResolvedValueOnce(new Set([album1.id])) - .mockResolvedValueOnce(new Set([album2.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id, asset3.id])); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user: owner }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ - success: false, - error: BulkIdErrorReason.DUPLICATE, - }); - - expect(mocks.album.update).not.toHaveBeenCalled(); - expect(mocks.album.addAssetIds).not.toHaveBeenCalled(); - }); - - it('should skip assets not shared with user', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [ - factory.asset({ ownerId: user.id }), - factory.asset({ ownerId: user.id }), - factory.asset({ ownerId: user.id }), - ]; - mocks.access.album.checkSharedAlbumAccess - .mockResolvedValueOnce(new Set([album1.id])) - .mockResolvedValueOnce(new Set([album2.id])); - mocks.album.getById.mockResolvedValueOnce(_.cloneDeep(album1)).mockResolvedValueOnce(_.cloneDeep(album2)); - mocks.album.getAssetIds.mockResolvedValueOnce(new Set()).mockResolvedValueOnce(new Set()); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user: owner }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ - success: false, - error: BulkIdErrorReason.NO_PERMISSION, - }); - - expect(mocks.album.update).not.toHaveBeenCalled(); - expect(mocks.album.addAssetIds).not.toHaveBeenCalled(); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - owner.id, - new Set([asset1.id, asset2.id, asset3.id]), - false, - ); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( - owner.id, - new Set([asset1.id, asset2.id, asset3.id]), - ); - }); - - it('should not allow unauthorized access to the albums', async () => { - const owner = factory.userAdmin(); - const user = factory.user(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - - await expect( - sut.addAssetsToAlbums(factory.auth({ user }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ - success: false, - error: BulkIdErrorReason.NO_PERMISSION, - }); - - expect(mocks.album.update).not.toHaveBeenCalled(); - expect(mocks.album.addAssetIds).not.toHaveBeenCalled(); - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalled(); - expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalled(); - }); - - it('should not allow unauthorized shared link access to the album', async () => { - const owner = factory.userAdmin(); - const album1 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const album2 = { - ...factory.album({ ownerId: owner.id }), - owner, - }; - const [asset1, asset2, asset3] = [factory.asset(), factory.asset(), factory.asset()]; - mocks.album.getById.mockResolvedValueOnce(album1).mockResolvedValueOnce(album2); - - await expect( - sut.addAssetsToAlbums(factory.auth({ sharedLink: { allowUpload: true } }), { - albumIds: [album1.id, album2.id], - assetIds: [asset1.id, asset2.id, asset3.id], - }), - ).resolves.toEqual({ - success: false, - error: BulkIdErrorReason.NO_PERMISSION, - }); - - expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalled(); - }); - }); - - describe('removeAssets', () => { - it('should allow the owner to remove assets', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset(); - const album = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: asset.id }), - owner, - albumAssets: [asset, factory.asset()], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id])); - - await expect(sut.removeAssets(factory.auth({ user: owner }), album.id, { ids: [asset.id] })).resolves.toEqual([ - { success: true, id: asset.id }, - ]); - - expect(mocks.album.removeAssetIds).toHaveBeenCalledWith(album.id, [asset.id]); - }); - - it('should skip assets not in the album', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset(); - const albumAsset = factory.asset(); - const album = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: albumAsset.id }), - owner, - albumAssets: [albumAsset], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValue(new Set()); - - await expect(sut.removeAssets(factory.auth({ user: owner }), album.id, { ids: [asset.id] })).resolves.toEqual([ - { success: false, id: asset.id, error: BulkIdErrorReason.NOT_FOUND }, - ]); - - expect(mocks.album.update).not.toHaveBeenCalled(); - }); - - it('should allow owner to remove all assets from the album', async () => { - const owner = factory.userAdmin(); - const asset = factory.asset(); - const album = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: asset.id }), - owner, - albumAssets: [asset], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValue(new Set([asset.id])); - - await expect(sut.removeAssets(factory.auth({ user: owner }), album.id, { ids: [asset.id] })).resolves.toEqual([ - { success: true, id: asset.id }, - ]); - }); - - it('should reset the thumbnail if it is removed', async () => { - const owner = factory.userAdmin(); - const asset1 = factory.asset(); - const asset2 = factory.asset(); - const album = { - ...factory.album({ ownerId: owner.id, albumThumbnailAssetId: asset1.id }), - owner, - albumAssets: [asset1, asset2], - }; - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset1.id])); - mocks.album.getById.mockResolvedValue(album); - mocks.album.getAssetIds.mockResolvedValue(new Set([asset1.id, asset2.id])); - - await expect(sut.removeAssets(factory.auth({ user: owner }), album.id, { ids: [asset1.id] })).resolves.toEqual([ - { success: true, id: asset1.id }, - ]); - - expect(mocks.album.updateThumbnails).toHaveBeenCalled(); - }); - }); - - // // it('removes assets from shared album (shared with auth user)', async () => { - // // const albumEntity = _getOwnedSharedAlbum(); - // // albumRepositoryMock.get.mockImplementation(() => Promise.resolve(albumEntity)); - // // albumRepositoryMock.removeAssets.mockImplementation(() => Promise.resolve(albumEntity)); - - // // await expect( - // // sut.removeAssetsFromAlbum( - // // auth, - // // { - // // ids: ['1'], - // // }, - // // albumEntity.id, - // // ), - // // ).resolves.toBeUndefined(); - // // expect(albumRepositoryMock.removeAssets).toHaveBeenCalledTimes(1); - // // expect(albumRepositoryMock.removeAssets).toHaveBeenCalledWith(albumEntity, { - // // ids: ['1'], - // // }); - // // }); - - // it('prevents removing assets from a not owned / shared album', async () => { - // const albumEntity = _getNotOwnedNotSharedAlbum(); - - // const albumResponse: AddAssetsResponseDto = { - // alreadyInAlbum: [], - // successfullyAdded: 1, - // }; - - // const albumId = albumEntity.id; - - // albumRepositoryMock.get.mockImplementation(() => Promise.resolve(albumEntity)); - // albumRepositoryMock.addAssets.mockImplementation(() => Promise.resolve(albumResponse)); - - // await expect(sut.removeAssets(auth, albumId, { ids: ['1'] })).rejects.toBeInstanceOf(ForbiddenException); - // }); -}); diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts deleted file mode 100644 index 18747dbc3a..0000000000 --- a/server/src/services/album.service.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { - AddUsersDto, - AlbumInfoDto, - AlbumResponseDto, - AlbumsAddAssetsDto, - AlbumsAddAssetsResponseDto, - AlbumStatisticsResponseDto, - CreateAlbumDto, - GetAlbumsDto, - mapAlbum, - MapAlbumDto, - mapAlbumWithAssets, - mapAlbumWithoutAssets, - UpdateAlbumDto, - UpdateAlbumUserDto, -} from 'src/dtos/album.dto'; -import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { Permission } from 'src/enum'; -import { AlbumAssetCount, AlbumInfoOptions } from 'src/repositories/album.repository'; -import { BaseService } from 'src/services/base.service'; -import { addAssets, removeAssets } from 'src/utils/asset.util'; -import { getPreferences } from 'src/utils/preferences'; - -@Injectable() -export class AlbumService extends BaseService { - async getStatistics(auth: AuthDto): Promise { - const [owned, shared, notShared] = await Promise.all([ - this.albumRepository.getOwned(auth.user.id), - this.albumRepository.getShared(auth.user.id), - this.albumRepository.getNotShared(auth.user.id), - ]); - - return { - owned: owned.length, - shared: shared.length, - notShared: notShared.length, - }; - } - - async getAll({ user: { id: ownerId } }: AuthDto, { assetId, shared }: GetAlbumsDto): Promise { - await this.albumRepository.updateThumbnails(); - - let albums: MapAlbumDto[]; - if (assetId) { - albums = await this.albumRepository.getByAssetId(ownerId, assetId); - } else if (shared === true) { - albums = await this.albumRepository.getShared(ownerId); - } else if (shared === false) { - albums = await this.albumRepository.getNotShared(ownerId); - } else { - albums = await this.albumRepository.getOwned(ownerId); - } - - // Get asset count for each album. Then map the result to an object: - // { [albumId]: assetCount } - const results = await this.albumRepository.getMetadataForIds(albums.map((album) => album.id)); - const albumMetadata: Record = {}; - for (const metadata of results) { - albumMetadata[metadata.albumId] = metadata; - } - - return albums.map((album) => ({ - ...mapAlbumWithoutAssets(album), - sharedLinks: undefined, - startDate: albumMetadata[album.id]?.startDate ?? undefined, - endDate: albumMetadata[album.id]?.endDate ?? undefined, - assetCount: albumMetadata[album.id]?.assetCount ?? 0, - // lastModifiedAssetTimestamp is only used in mobile app, please remove if not need - lastModifiedAssetTimestamp: albumMetadata[album.id]?.lastModifiedAssetTimestamp ?? undefined, - })); - } - - async get(auth: AuthDto, id: string, dto: AlbumInfoDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] }); - await this.albumRepository.updateThumbnails(); - const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets; - const album = await this.findOrFail(id, { withAssets }); - const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]); - - const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0; - const hasSharedLink = album.sharedLinks && album.sharedLinks.length > 0; - const isShared = hasSharedUsers || hasSharedLink; - - return { - ...mapAlbum(album, withAssets, auth), - startDate: albumMetadataForIds?.startDate ?? undefined, - endDate: albumMetadataForIds?.endDate ?? undefined, - assetCount: albumMetadataForIds?.assetCount ?? 0, - lastModifiedAssetTimestamp: albumMetadataForIds?.lastModifiedAssetTimestamp ?? undefined, - contributorCounts: isShared ? await this.albumRepository.getContributorCounts(album.id) : undefined, - }; - } - - async create(auth: AuthDto, dto: CreateAlbumDto): Promise { - const albumUsers = dto.albumUsers || []; - - for (const { userId } of albumUsers) { - const exists = await this.userRepository.get(userId, {}); - if (!exists) { - throw new BadRequestException('User not found'); - } - - if (userId == auth.user.id) { - throw new BadRequestException('Cannot share album with owner'); - } - } - - const allowedAssetIdsSet = await this.checkAccess({ - auth, - permission: Permission.AssetShare, - ids: dto.assetIds || [], - }); - const assetIds = [...allowedAssetIdsSet].map((id) => id); - - const userMetadata = await this.userRepository.getMetadata(auth.user.id); - - const album = await this.albumRepository.create( - { - ownerId: auth.user.id, - albumName: dto.albumName, - description: dto.description, - albumThumbnailAssetId: assetIds[0] || null, - order: getPreferences(userMetadata).albums.defaultAssetOrder, - }, - assetIds, - albumUsers, - ); - - for (const { userId } of albumUsers) { - await this.eventRepository.emit('AlbumInvite', { id: album.id, userId }); - } - - return mapAlbumWithAssets(album); - } - - async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumUpdate, ids: [id] }); - - const album = await this.findOrFail(id, { withAssets: true }); - - if (dto.albumThumbnailAssetId) { - const results = await this.albumRepository.getAssetIds(id, [dto.albumThumbnailAssetId]); - if (results.size === 0) { - throw new BadRequestException('Invalid album thumbnail'); - } - } - const updatedAlbum = await this.albumRepository.update(album.id, { - id: album.id, - albumName: dto.albumName, - description: dto.description, - albumThumbnailAssetId: dto.albumThumbnailAssetId, - isActivityEnabled: dto.isActivityEnabled, - order: dto.order, - }); - - return mapAlbumWithoutAssets({ ...updatedAlbum, assets: album.assets }); - } - - async delete(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumDelete, ids: [id] }); - await this.albumRepository.delete(id); - } - - async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - const album = await this.findOrFail(id, { withAssets: false }); - await this.requireAccess({ auth, permission: Permission.AlbumAssetCreate, ids: [id] }); - - const results = await addAssets( - auth, - { access: this.accessRepository, bulk: this.albumRepository }, - { parentId: id, assetIds: dto.ids }, - ); - - const { id: firstNewAssetId } = results.find(({ success }) => success) || {}; - if (firstNewAssetId) { - await this.albumRepository.update(id, { - id, - updatedAt: new Date(), - albumThumbnailAssetId: album.albumThumbnailAssetId ?? firstNewAssetId, - }); - - const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter( - (userId) => userId !== auth.user.id, - ); - - for (const recipientId of allUsersExceptUs) { - await this.eventRepository.emit('AlbumUpdate', { id, recipientId }); - } - } - - return results; - } - - async addAssetsToAlbums(auth: AuthDto, dto: AlbumsAddAssetsDto): Promise { - const results: AlbumsAddAssetsResponseDto = { - success: false, - error: BulkIdErrorReason.DUPLICATE, - }; - - const allowedAlbumIds = await this.checkAccess({ - auth, - permission: Permission.AlbumAssetCreate, - ids: dto.albumIds, - }); - if (allowedAlbumIds.size === 0) { - results.error = BulkIdErrorReason.NO_PERMISSION; - return results; - } - - const allowedAssetIds = await this.checkAccess({ auth, permission: Permission.AssetShare, ids: dto.assetIds }); - if (allowedAssetIds.size === 0) { - results.error = BulkIdErrorReason.NO_PERMISSION; - return results; - } - - const albumAssetValues: { albumId: string; assetId: string }[] = []; - const events: { id: string; recipients: string[] }[] = []; - for (const albumId of allowedAlbumIds) { - const existingAssetIds = await this.albumRepository.getAssetIds(albumId, [...allowedAssetIds]); - const notPresentAssetIds = [...allowedAssetIds].filter((id) => !existingAssetIds.has(id)); - if (notPresentAssetIds.length === 0) { - continue; - } - const album = await this.findOrFail(albumId, { withAssets: false }); - results.error = undefined; - results.success = true; - - for (const assetId of notPresentAssetIds) { - albumAssetValues.push({ albumId, assetId }); - } - await this.albumRepository.update(albumId, { - id: albumId, - updatedAt: new Date(), - albumThumbnailAssetId: album.albumThumbnailAssetId ?? notPresentAssetIds[0], - }); - const allUsersExceptUs = [...album.albumUsers.map(({ user }) => user.id), album.owner.id].filter( - (userId) => userId !== auth.user.id, - ); - events.push({ id: albumId, recipients: allUsersExceptUs }); - } - - await this.albumRepository.addAssetIdsToAlbums(albumAssetValues); - for (const event of events) { - for (const recipientId of event.recipients) { - await this.eventRepository.emit('AlbumUpdate', { id: event.id, recipientId }); - } - } - - return results; - } - - async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumAssetDelete, ids: [id] }); - - const album = await this.findOrFail(id, { withAssets: false }); - const results = await removeAssets( - auth, - { access: this.accessRepository, bulk: this.albumRepository }, - { parentId: id, assetIds: dto.ids, canAlwaysRemove: Permission.AlbumDelete }, - ); - - const removedIds = results.filter(({ success }) => success).map(({ id }) => id); - if (removedIds.length > 0 && album.albumThumbnailAssetId && removedIds.includes(album.albumThumbnailAssetId)) { - await this.albumRepository.updateThumbnails(); - } - - return results; - } - - async addUsers(auth: AuthDto, id: string, { albumUsers }: AddUsersDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); - - const album = await this.findOrFail(id, { withAssets: false }); - - for (const { userId, role } of albumUsers) { - if (album.ownerId === userId) { - throw new BadRequestException('Cannot be shared with owner'); - } - - const exists = album.albumUsers.find(({ user: { id } }) => id === userId); - if (exists) { - throw new BadRequestException('User already added'); - } - - const user = await this.userRepository.get(userId, {}); - if (!user) { - throw new BadRequestException('User not found'); - } - - await this.albumUserRepository.create({ userId, albumId: id, role }); - await this.eventRepository.emit('AlbumInvite', { id, userId }); - } - - return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets); - } - - async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise { - if (userId === 'me') { - userId = auth.user.id; - } - - const album = await this.findOrFail(id, { withAssets: false }); - - if (album.ownerId === userId) { - throw new BadRequestException('Cannot remove album owner'); - } - - const exists = album.albumUsers.find(({ user: { id } }) => id === userId); - if (!exists) { - throw new BadRequestException('Album not shared with user'); - } - - // non-admin can remove themselves - if (auth.user.id !== userId) { - await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); - } - - await this.albumUserRepository.delete({ albumId: id, userId }); - } - - async updateUser(auth: AuthDto, id: string, userId: string, dto: UpdateAlbumUserDto): Promise { - await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [id] }); - await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role }); - } - - private async findOrFail(id: string, options: AlbumInfoOptions) { - const album = await this.albumRepository.getById(id, options); - if (!album) { - throw new BadRequestException('Album not found'); - } - return album; - } -} diff --git a/server/src/services/api-key.service.spec.ts b/server/src/services/api-key.service.spec.ts deleted file mode 100644 index 14544f454f..0000000000 --- a/server/src/services/api-key.service.spec.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { BadRequestException, ForbiddenException } from '@nestjs/common'; -import { Permission } from 'src/enum'; -import { ApiKeyService } from 'src/services/api-key.service'; -import { factory, newUuid } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(ApiKeyService.name, () => { - let sut: ApiKeyService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(ApiKeyService)); - }); - - describe('create', () => { - it('should create a new key', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id, permissions: [Permission.All] }); - const key = 'super-secret'; - - mocks.crypto.randomBytesAsText.mockReturnValue(key); - mocks.apiKey.create.mockResolvedValue(apiKey); - - await sut.create(auth, { name: apiKey.name, permissions: apiKey.permissions }); - - expect(mocks.apiKey.create).toHaveBeenCalledWith({ - key: 'super-secret (hashed)', - name: apiKey.name, - permissions: apiKey.permissions, - userId: apiKey.userId, - }); - expect(mocks.crypto.randomBytesAsText).toHaveBeenCalled(); - expect(mocks.crypto.hashSha256).toHaveBeenCalled(); - }); - - it('should not require a name', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - const key = 'super-secret'; - - mocks.crypto.randomBytesAsText.mockReturnValue(key); - mocks.apiKey.create.mockResolvedValue(apiKey); - - await sut.create(auth, { permissions: [Permission.All] }); - - expect(mocks.apiKey.create).toHaveBeenCalledWith({ - key: 'super-secret (hashed)', - name: 'API Key', - permissions: [Permission.All], - userId: auth.user.id, - }); - expect(mocks.crypto.randomBytesAsText).toHaveBeenCalled(); - expect(mocks.crypto.hashSha256).toHaveBeenCalled(); - }); - - it('should throw an error if the api key does not have sufficient permissions', async () => { - const auth = factory.auth({ apiKey: { permissions: [Permission.AssetRead] } }); - - await expect(sut.create(auth, { permissions: [Permission.AssetUpdate] })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - }); - - describe('update', () => { - it('should throw an error if the key is not found', async () => { - const id = newUuid(); - const auth = factory.auth(); - - mocks.apiKey.getById.mockResolvedValue(void 0); - - await expect(sut.update(auth, id, { name: 'New Name', permissions: [Permission.All] })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.apiKey.update).not.toHaveBeenCalledWith(id); - }); - - it('should update a key', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - const newName = 'New name'; - - mocks.apiKey.getById.mockResolvedValue(apiKey); - mocks.apiKey.update.mockResolvedValue(apiKey); - - await sut.update(auth, apiKey.id, { name: newName, permissions: [Permission.All] }); - - expect(mocks.apiKey.update).toHaveBeenCalledWith(auth.user.id, apiKey.id, { - name: newName, - permissions: [Permission.All], - }); - }); - - it('should update permissions', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - const newPermissions = [Permission.ActivityCreate, Permission.ActivityRead, Permission.ActivityUpdate]; - - mocks.apiKey.getById.mockResolvedValue(apiKey); - mocks.apiKey.update.mockResolvedValue(apiKey); - - await sut.update(auth, apiKey.id, { name: apiKey.name, permissions: newPermissions }); - - expect(mocks.apiKey.update).toHaveBeenCalledWith(auth.user.id, apiKey.id, { - name: apiKey.name, - permissions: newPermissions, - }); - }); - - describe('api key auth', () => { - it('should prevent adding Permission.all', async () => { - const permissions = [Permission.ApiKeyCreate, Permission.ApiKeyUpdate, Permission.AssetRead]; - const auth = factory.auth({ apiKey: { permissions } }); - const apiKey = factory.apiKey({ userId: auth.user.id, permissions }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - - await expect(sut.update(auth, apiKey.id, { permissions: [Permission.All] })).rejects.toThrow( - 'Cannot grant permissions you do not have', - ); - - expect(mocks.apiKey.update).not.toHaveBeenCalled(); - }); - - it('should prevent adding a new permission', async () => { - const permissions = [Permission.ApiKeyCreate, Permission.ApiKeyUpdate, Permission.AssetRead]; - const auth = factory.auth({ apiKey: { permissions } }); - const apiKey = factory.apiKey({ userId: auth.user.id, permissions }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - - await expect(sut.update(auth, apiKey.id, { permissions: [Permission.AssetCopy] })).rejects.toThrow( - 'Cannot grant permissions you do not have', - ); - - expect(mocks.apiKey.update).not.toHaveBeenCalled(); - }); - - it('should allow removing permissions', async () => { - const auth = factory.auth({ apiKey: { permissions: [Permission.ApiKeyUpdate, Permission.AssetRead] } }); - const apiKey = factory.apiKey({ - userId: auth.user.id, - permissions: [Permission.AssetRead, Permission.AssetDelete], - }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - mocks.apiKey.update.mockResolvedValue(apiKey); - - // remove Permission.AssetDelete - await sut.update(auth, apiKey.id, { permissions: [Permission.AssetRead] }); - - expect(mocks.apiKey.update).toHaveBeenCalledWith( - auth.user.id, - apiKey.id, - expect.objectContaining({ permissions: [Permission.AssetRead] }), - ); - }); - - it('should allow adding new permissions', async () => { - const auth = factory.auth({ - apiKey: { permissions: [Permission.ApiKeyUpdate, Permission.AssetRead, Permission.AssetUpdate] }, - }); - const apiKey = factory.apiKey({ userId: auth.user.id, permissions: [Permission.AssetRead] }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - mocks.apiKey.update.mockResolvedValue(apiKey); - - // add Permission.AssetUpdate - await sut.update(auth, apiKey.id, { - name: apiKey.name, - permissions: [Permission.AssetRead, Permission.AssetUpdate], - }); - - expect(mocks.apiKey.update).toHaveBeenCalledWith( - auth.user.id, - apiKey.id, - expect.objectContaining({ permissions: [Permission.AssetRead, Permission.AssetUpdate] }), - ); - }); - }); - }); - - describe('delete', () => { - it('should throw an error if the key is not found', async () => { - const auth = factory.auth(); - const id = newUuid(); - - mocks.apiKey.getById.mockResolvedValue(void 0); - - await expect(sut.delete(auth, id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.apiKey.delete).not.toHaveBeenCalledWith(id); - }); - - it('should delete a key', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - mocks.apiKey.delete.mockResolvedValue(); - - await sut.delete(auth, apiKey.id); - - expect(mocks.apiKey.delete).toHaveBeenCalledWith(auth.user.id, apiKey.id); - }); - }); - - describe('getMine', () => { - it('should not work with a session token', async () => { - const session = factory.session(); - const auth = factory.auth({ session }); - - mocks.apiKey.getById.mockResolvedValue(void 0); - - await expect(sut.getMine(auth)).rejects.toBeInstanceOf(ForbiddenException); - - expect(mocks.apiKey.getById).not.toHaveBeenCalled(); - }); - - it('should throw an error if the key is not found', async () => { - const apiKey = factory.authApiKey(); - const auth = factory.auth({ apiKey }); - - mocks.apiKey.getById.mockResolvedValue(void 0); - - await expect(sut.getMine(auth)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, apiKey.id); - }); - - it('should get a key by id', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - - await sut.getById(auth, apiKey.id); - - expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, apiKey.id); - }); - }); - - describe('getById', () => { - it('should throw an error if the key is not found', async () => { - const auth = factory.auth(); - const id = newUuid(); - - mocks.apiKey.getById.mockResolvedValue(void 0); - - await expect(sut.getById(auth, id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, id); - }); - - it('should get a key by id', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - - mocks.apiKey.getById.mockResolvedValue(apiKey); - - await sut.getById(auth, apiKey.id); - - expect(mocks.apiKey.getById).toHaveBeenCalledWith(auth.user.id, apiKey.id); - }); - }); - - describe('getAll', () => { - it('should return all the keys for a user', async () => { - const auth = factory.auth(); - const apiKey = factory.apiKey({ userId: auth.user.id }); - - mocks.apiKey.getByUserId.mockResolvedValue([apiKey]); - - await expect(sut.getAll(auth)).resolves.toHaveLength(1); - - expect(mocks.apiKey.getByUserId).toHaveBeenCalledWith(auth.user.id); - }); - }); -}); diff --git a/server/src/services/api.service.ts b/server/src/services/api.service.ts index ed1b4095d6..2bd8449f2d 100644 --- a/server/src/services/api.service.ts +++ b/server/src/services/api.service.ts @@ -1,111 +1,26 @@ import { Injectable } from '@nestjs/common'; -import { Interval } from '@nestjs/schedule'; import { NextFunction, Request, Response } from 'express'; -import { readFileSync } from 'node:fs'; -import sanitizeHtml from 'sanitize-html'; -import { ONE_HOUR } from 'src/constants'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; -import { SharedLinkService } from 'src/services/shared-link.service'; -import { VersionService } from 'src/services/version.service'; -import { OpenGraphTags } from 'src/utils/misc'; - -export const render = (index: string, meta: OpenGraphTags) => { - const [title, description, imageUrl] = [meta.title, meta.description, meta.imageUrl].map((item) => - item ? sanitizeHtml(item, { allowedTags: [] }) : '', - ); - - const tags = ` - - - - - - - ${imageUrl ? `` : ''} - - - - - - - ${imageUrl ? `` : ''}`; - - return index.replace('', tags); -}; @Injectable() export class ApiService { constructor( private authService: AuthService, - private sharedLinkService: SharedLinkService, - private versionService: VersionService, private configRepository: ConfigRepository, private logger: LoggingRepository, ) { this.logger.setContext(ApiService.name); } - @Interval(ONE_HOUR.as('milliseconds')) - async onVersionCheck() { - await this.versionService.handleQueueVersionCheck(); + async onBootstrap(): Promise { + this.logger.log('API service started'); } - ssr(excludePaths: string[]) { - const { resourcePaths } = this.configRepository.getEnv(); - - let index = ''; - try { - index = readFileSync(resourcePaths.web.indexHtml).toString(); - } catch { - this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`); - } - + ssr(_excludePaths: string[]) { return async (request: Request, res: Response, next: NextFunction) => { - const method = request.method.toLowerCase(); - if ( - request.url.startsWith('/api') || - (method !== 'get' && method !== 'head') || - excludePaths.some((item) => request.url.startsWith(item)) - ) { - return next(); - } - - let status = 200; - let html = index; - - const defaultDomain = request.host ? `${request.protocol}://${request.host}` : undefined; - - let meta: OpenGraphTags | null = null; - - const shareKey = request.url.match(/^\/share\/(.+)$/); - if (shareKey) { - try { - const key = shareKey[1]; - const auth = await this.authService.validateSharedLinkKey(key); - meta = await this.sharedLinkService.getMetadataTags(auth, defaultDomain); - } catch { - status = 404; - } - } - - const shareSlug = request.url.match(/^\/s\/(.+)$/); - if (shareSlug) { - try { - const slug = shareSlug[1]; - const auth = await this.authService.validateSharedLinkSlug(slug); - meta = await this.sharedLinkService.getMetadataTags(auth, defaultDomain); - } catch { - status = 404; - } - } - - if (meta) { - html = render(index, meta); - } - - res.status(status).type('text/html').header('Cache-Control', 'no-store').send(html); + next(); }; } } diff --git a/server/src/services/asset-media.service.spec.ts b/server/src/services/asset-media.service.spec.ts deleted file mode 100644 index 0bcb87e2f4..0000000000 --- a/server/src/services/asset-media.service.spec.ts +++ /dev/null @@ -1,1089 +0,0 @@ -import { - BadRequestException, - InternalServerErrorException, - NotFoundException, - UnauthorizedException, -} from '@nestjs/common'; -import { Stats } from 'node:fs'; -import { AssetFile } from 'src/database'; -import { AssetMediaStatus, AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-media-response.dto'; -import { AssetMediaCreateDto, AssetMediaReplaceDto, AssetMediaSize, UploadFieldName } from 'src/dtos/asset-media.dto'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { AssetFileType, AssetStatus, AssetType, AssetVisibility, CacheControl, JobName } from 'src/enum'; -import { AuthRequest } from 'src/middleware/auth.guard'; -import { AssetMediaService } from 'src/services/asset-media.service'; -import { UploadBody } from 'src/types'; -import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database'; -import { ImmichFileResponse } from 'src/utils/file'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { fileStub } from 'test/fixtures/file.stub'; -import { userStub } from 'test/fixtures/user.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex'); - -const uploadFile = { - nullAuth: { - auth: null, - body: {}, - fieldName: UploadFieldName.ASSET_DATA, - file: { - uuid: 'random-uuid', - checksum: Buffer.from('checksum', 'utf8'), - originalPath: '/data/library/admin/image.jpeg', - originalName: 'image.jpeg', - size: 1000, - }, - }, - filename: (fieldName: UploadFieldName, filename: string, body?: UploadBody) => { - return { - auth: authStub.admin, - body: body || {}, - fieldName, - file: { - uuid: 'random-uuid', - mimeType: 'image/jpeg', - checksum: Buffer.from('checksum', 'utf8'), - originalPath: `/data/admin/${filename}`, - originalName: filename, - size: 1000, - }, - }; - }, -}; - -const validImages = [ - '.3fr', - '.ari', - '.arw', - '.avif', - '.cap', - '.cin', - '.cr2', - '.cr3', - '.crw', - '.dcr', - '.dng', - '.erf', - '.fff', - '.gif', - '.heic', - '.heif', - '.iiq', - '.jp2', - '.jpeg', - '.jpg', - '.jxl', - '.k25', - '.kdc', - '.mrw', - '.nef', - '.orf', - '.ori', - '.pef', - '.png', - '.psd', - '.raf', - '.raw', - '.rwl', - '.sr2', - '.srf', - '.srw', - '.svg', - '.tiff', - '.webp', - '.x3f', -]; - -const validVideos = [ - '.3gp', - '.avi', - '.flv', - '.m2t', - '.m2ts', - '.mkv', - '.mov', - '.mp4', - '.mpg', - '.mts', - '.vob', - '.webm', - '.wmv', -]; - -const uploadTests = [ - { - label: 'asset images', - fieldName: UploadFieldName.ASSET_DATA, - valid: validImages, - invalid: ['.html', '.xml'], - }, - { - label: 'asset videos', - fieldName: UploadFieldName.ASSET_DATA, - valid: validVideos, - invalid: ['.html', '.xml'], - }, - { - label: 'sidecar', - fieldName: UploadFieldName.SIDECAR_DATA, - valid: ['.xmp'], - invalid: ['.html', '.jpeg', '.jpg', '.mov', '.mp4', '.xml'], - }, - { - label: 'profile', - fieldName: UploadFieldName.PROFILE_DATA, - valid: ['.avif', '.dng', '.heic', '.heif', '.jpeg', '.jpg', '.png', '.webp'], - invalid: ['.arf', '.cr2', '.html', '.mov', '.mp4', '.xml'], - }, -]; - -const createDto = Object.freeze({ - deviceAssetId: 'deviceAssetId', - deviceId: 'deviceId', - fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), - isFavorite: false, - duration: '0:00:00.000000', -}) as AssetMediaCreateDto; - -const replaceDto = Object.freeze({ - deviceAssetId: 'deviceAssetId', - deviceId: 'deviceId', - fileModifiedAt: new Date('2024-04-15T23:41:36.910Z'), - fileCreatedAt: new Date('2024-04-15T23:41:36.910Z'), -}) as AssetMediaReplaceDto; - -const assetEntity = Object.freeze({ - id: 'id_1', - ownerId: 'user_id_1', - deviceAssetId: 'device_asset_id_1', - deviceId: 'device_id_1', - type: AssetType.Video, - originalPath: 'fake_path/asset_1.jpeg', - fileModifiedAt: new Date('2022-06-19T23:41:36.910Z'), - fileCreatedAt: new Date('2022-06-19T23:41:36.910Z'), - updatedAt: new Date('2022-06-19T23:41:36.910Z'), - isFavorite: false, - encodedVideoPath: '', - duration: '0:00:00.000000', - files: [] as AssetFile[], - exifInfo: { - latitude: 49.533_547, - longitude: 10.703_075, - }, - livePhotoVideoId: null, -} as MapAsset); - -const existingAsset = Object.freeze({ - ...assetEntity, - duration: null, - type: AssetType.Image, - checksum: Buffer.from('_getExistingAsset', 'utf8'), - libraryId: 'libraryId', - originalFileName: 'existing-filename.jpeg', -}) as MapAsset; - -const sidecarAsset = Object.freeze({ - ...existingAsset, - checksum: Buffer.from('_getExistingAssetWithSideCar', 'utf8'), -}) as MapAsset; - -const copiedAsset = Object.freeze({ - id: 'copied-asset', - originalPath: 'copied-path', -}) as MapAsset; - -describe(AssetMediaService.name, () => { - let sut: AssetMediaService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(AssetMediaService)); - }); - - describe('getUploadAssetIdByChecksum', () => { - it('should return if checksum is undefined', async () => { - await expect(sut.getUploadAssetIdByChecksum(authStub.admin)).resolves.toBe(undefined); - }); - - it('should handle a non-existent asset', async () => { - await expect(sut.getUploadAssetIdByChecksum(authStub.admin, file1.toString('hex'))).resolves.toBeUndefined(); - expect(mocks.asset.getUploadAssetIdByChecksum).toHaveBeenCalledWith(authStub.admin.user.id, file1); - }); - - it('should find an existing asset', async () => { - mocks.asset.getUploadAssetIdByChecksum.mockResolvedValue('asset-id'); - await expect(sut.getUploadAssetIdByChecksum(authStub.admin, file1.toString('hex'))).resolves.toEqual({ - id: 'asset-id', - status: AssetMediaStatus.DUPLICATE, - }); - expect(mocks.asset.getUploadAssetIdByChecksum).toHaveBeenCalledWith(authStub.admin.user.id, file1); - }); - - it('should find an existing asset by base64', async () => { - mocks.asset.getUploadAssetIdByChecksum.mockResolvedValue('asset-id'); - await expect(sut.getUploadAssetIdByChecksum(authStub.admin, file1.toString('base64'))).resolves.toEqual({ - id: 'asset-id', - status: AssetMediaStatus.DUPLICATE, - }); - expect(mocks.asset.getUploadAssetIdByChecksum).toHaveBeenCalledWith(authStub.admin.user.id, file1); - }); - }); - - describe('canUpload', () => { - it('should require an authenticated user', () => { - expect(() => sut.canUploadFile(uploadFile.nullAuth)).toThrowError(UnauthorizedException); - }); - - for (const { fieldName, valid, invalid } of uploadTests) { - describe(fieldName, () => { - for (const filetype of valid) { - it(`should accept ${filetype}`, () => { - expect(sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toEqual(true); - }); - } - - for (const filetype of invalid) { - it(`should reject ${filetype}`, () => { - expect(() => sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toThrowError( - BadRequestException, - ); - }); - } - - it('should be sorted (valid)', () => { - expect(valid).toEqual(valid.toSorted()); - }); - - it('should be sorted (invalid)', () => { - expect(invalid).toEqual(invalid.toSorted()); - }); - }); - } - - it('should prefer filename from body over name from path', () => { - const pathFilename = 'invalid-file-name'; - const body = { filename: 'video.mov' }; - expect(() => sut.canUploadFile(uploadFile.filename(UploadFieldName.ASSET_DATA, pathFilename))).toThrowError( - BadRequestException, - ); - expect(sut.canUploadFile(uploadFile.filename(UploadFieldName.ASSET_DATA, pathFilename, body))).toEqual(true); - }); - }); - - describe('getUploadFilename', () => { - it('should require authentication', () => { - expect(() => sut.getUploadFilename(uploadFile.nullAuth)).toThrowError(UnauthorizedException); - }); - - it('should be the original extension for asset upload', () => { - expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.ASSET_DATA, 'image.jpg'))).toEqual( - 'random-uuid.jpg', - ); - }); - - it('should be the xmp extension for sidecar upload', () => { - expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.SIDECAR_DATA, 'image.html'))).toEqual( - 'random-uuid.xmp', - ); - }); - - it('should be the original extension for profile upload', () => { - expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.PROFILE_DATA, 'image.jpg'))).toEqual( - 'random-uuid.jpg', - ); - }); - }); - - describe('getUploadFolder', () => { - it('should require authentication', () => { - expect(() => sut.getUploadFolder(uploadFile.nullAuth)).toThrowError(UnauthorizedException); - }); - - it('should return profile for profile uploads', () => { - expect(sut.getUploadFolder(uploadFile.filename(UploadFieldName.PROFILE_DATA, 'image.jpg'))).toEqual( - expect.stringContaining('/data/profile/admin_id'), - ); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/profile/admin_id')); - }); - - it('should return upload for everything else', () => { - expect(sut.getUploadFolder(uploadFile.filename(UploadFieldName.ASSET_DATA, 'image.jpg'))).toEqual( - expect.stringContaining('/data/upload/admin_id/ra/nd'), - ); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/upload/admin_id/ra/nd')); - }); - }); - - describe('uploadAsset', () => { - it('should throw an error if the quota is exceeded', async () => { - const file = { - uuid: 'random-uuid', - originalPath: 'fake_path/asset_1.jpeg', - mimeType: 'image/jpeg', - checksum: Buffer.from('file hash', 'utf8'), - originalName: 'asset_1.jpeg', - size: 42, - }; - - mocks.asset.create.mockResolvedValue(assetEntity); - - await expect( - sut.uploadAsset( - { ...authStub.admin, user: { ...authStub.admin.user, quotaSizeInBytes: 42, quotaUsageInBytes: 1 } }, - createDto, - file, - ), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.create).not.toHaveBeenCalled(); - expect(mocks.user.updateUsage).not.toHaveBeenCalledWith(authStub.user1.user.id, file.size); - expect(mocks.storage.utimes).not.toHaveBeenCalledWith( - file.originalPath, - expect.any(Date), - new Date(createDto.fileModifiedAt), - ); - }); - - it('should handle a file upload', async () => { - const file = { - uuid: 'random-uuid', - originalPath: 'fake_path/asset_1.jpeg', - mimeType: 'image/jpeg', - checksum: Buffer.from('file hash', 'utf8'), - originalName: 'asset_1.jpeg', - size: 42, - }; - - mocks.asset.create.mockResolvedValue(assetEntity); - - await expect(sut.uploadAsset(authStub.user1, createDto, file)).resolves.toEqual({ - id: 'id_1', - status: AssetMediaStatus.CREATED, - }); - - expect(mocks.asset.create).toHaveBeenCalled(); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, file.size); - expect(mocks.storage.utimes).toHaveBeenCalledWith( - file.originalPath, - expect.any(Date), - new Date(createDto.fileModifiedAt), - ); - }); - - it('should handle a duplicate', async () => { - const file = { - uuid: 'random-uuid', - originalPath: 'fake_path/asset_1.jpeg', - mimeType: 'image/jpeg', - checksum: Buffer.from('file hash', 'utf8'), - originalName: 'asset_1.jpeg', - size: 0, - }; - const error = new Error('unique key violation'); - (error as any).constraint_name = ASSET_CHECKSUM_CONSTRAINT; - - mocks.asset.create.mockRejectedValue(error); - mocks.asset.getUploadAssetIdByChecksum.mockResolvedValue(assetEntity.id); - - await expect(sut.uploadAsset(authStub.user1, createDto, file)).resolves.toEqual({ - id: 'id_1', - status: AssetMediaStatus.DUPLICATE, - }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: ['fake_path/asset_1.jpeg', undefined] }, - }); - expect(mocks.user.updateUsage).not.toHaveBeenCalled(); - }); - - it('should throw an error if the duplicate could not be found by checksum', async () => { - const file = { - uuid: 'random-uuid', - originalPath: 'fake_path/asset_1.jpeg', - mimeType: 'image/jpeg', - checksum: Buffer.from('file hash', 'utf8'), - originalName: 'asset_1.jpeg', - size: 0, - }; - const error = new Error('unique key violation'); - (error as any).constraint_name = ASSET_CHECKSUM_CONSTRAINT; - - mocks.asset.create.mockRejectedValue(error); - - await expect(sut.uploadAsset(authStub.user1, createDto, file)).rejects.toBeInstanceOf( - InternalServerErrorException, - ); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: ['fake_path/asset_1.jpeg', undefined] }, - }); - expect(mocks.user.updateUsage).not.toHaveBeenCalled(); - }); - - it('should handle a live photo', async () => { - mocks.asset.getById.mockResolvedValueOnce(assetStub.livePhotoMotionAsset); - mocks.asset.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset); - - await expect( - sut.uploadAsset( - authStub.user1, - { ...createDto, livePhotoVideoId: 'live-photo-motion-asset' }, - fileStub.livePhotoStill, - ), - ).resolves.toEqual({ - status: AssetMediaStatus.CREATED, - id: 'live-photo-still-asset', - }); - - expect(mocks.asset.getById).toHaveBeenCalledWith('live-photo-motion-asset'); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should hide the linked motion asset', async () => { - mocks.asset.getById.mockResolvedValueOnce({ - ...assetStub.livePhotoMotionAsset, - visibility: AssetVisibility.Timeline, - }); - mocks.asset.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset); - - await expect( - sut.uploadAsset( - authStub.user1, - { ...createDto, livePhotoVideoId: 'live-photo-motion-asset' }, - fileStub.livePhotoStill, - ), - ).resolves.toEqual({ - status: AssetMediaStatus.CREATED, - id: 'live-photo-still-asset', - }); - - expect(mocks.asset.getById).toHaveBeenCalledWith('live-photo-motion-asset'); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: 'live-photo-motion-asset', - visibility: AssetVisibility.Hidden, - }); - }); - - it('should handle a sidecar file', async () => { - mocks.asset.getById.mockResolvedValueOnce(assetStub.image); - mocks.asset.create.mockResolvedValueOnce(assetStub.image); - - await expect(sut.uploadAsset(authStub.user1, createDto, fileStub.photo, fileStub.photoSidecar)).resolves.toEqual({ - status: AssetMediaStatus.CREATED, - id: assetStub.image.id, - }); - - expect(mocks.storage.utimes).toHaveBeenCalledWith( - fileStub.photoSidecar.originalPath, - expect.any(Date), - new Date(createDto.fileModifiedAt), - ); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - }); - - describe('downloadOriginal', () => { - it('should require the asset.download permission', async () => { - await expect(sut.downloadOriginal(authStub.admin, 'asset-1', {})).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set(['asset-1']), - undefined, - ); - expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1'])); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['asset-1'])); - }); - - it('should download a file', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getForOriginal.mockResolvedValue(assetStub.image); - - await expect(sut.downloadOriginal(authStub.admin, 'asset-1', {})).resolves.toEqual( - new ImmichFileResponse({ - path: '/original/path.jpg', - fileName: 'asset-id.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.PrivateWithCache, - }), - ); - }); - - it('should download edited file by default when edits exist', async () => { - const editedAsset = { - ...assetStub.withCropEdit, - files: [ - ...assetStub.withCropEdit.files, - { - id: 'edited-file', - type: AssetFileType.FullSize, - path: '/uploads/user-id/fullsize/edited.jpg', - isEdited: true, - }, - ], - }; - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getForOriginal.mockResolvedValue({ - ...editedAsset, - editedPath: '/uploads/user-id/fullsize/edited.jpg', - }); - - await expect(sut.downloadOriginal(authStub.admin, 'asset-1', { edited: true })).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/fullsize/edited.jpg', - fileName: 'asset-id.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.PrivateWithCache, - }), - ); - }); - - it('should download edited file when edited=true', async () => { - const editedAsset = { - ...assetStub.withCropEdit, - files: [ - ...assetStub.withCropEdit.files, - { - id: 'edited-file', - type: AssetFileType.FullSize, - path: '/uploads/user-id/fullsize/edited.jpg', - isEdited: true, - }, - ], - }; - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getForOriginal.mockResolvedValue({ - ...editedAsset, - editedPath: '/uploads/user-id/fullsize/edited.jpg', - }); - - await expect(sut.downloadOriginal(authStub.admin, 'asset-1', { edited: true })).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/fullsize/edited.jpg', - fileName: 'asset-id.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.PrivateWithCache, - }), - ); - }); - - it('should not return the unedited version if requested using a shared link', async () => { - const editedAsset = { - ...assetStub.withCropEdit, - files: [ - ...assetStub.withCropEdit.files, - { - id: 'edited-file', - type: AssetFileType.FullSize, - path: '/uploads/user-id/fullsize/edited.jpg', - isEdited: true, - }, - ], - }; - mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForOriginal.mockResolvedValue({ - ...editedAsset, - editedPath: '/uploads/user-id/fullsize/edited.jpg', - }); - - await expect(sut.downloadOriginal(authStub.adminSharedLink, 'asset-id', { edited: false })).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/fullsize/edited.jpg', - fileName: 'asset-id.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.PrivateWithCache, - }), - ); - }); - - it('should download original file when edited=false', async () => { - const editedAsset = { - ...assetStub.withCropEdit, - files: [ - ...assetStub.withCropEdit.files, - { - id: 'edited-file', - type: AssetFileType.FullSize, - path: '/uploads/user-id/fullsize/edited.jpg', - isEdited: true, - }, - ], - }; - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getForOriginal.mockResolvedValue(editedAsset); - - await expect(sut.downloadOriginal(authStub.admin, 'asset-1', { edited: false })).resolves.toEqual( - new ImmichFileResponse({ - path: '/original/path.jpg', - fileName: 'asset-id.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.PrivateWithCache, - }), - ); - }); - }); - - describe('viewThumbnail', () => { - it('should require asset.view permissions', async () => { - await expect(sut.viewThumbnail(authStub.admin, 'id', {})).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['id']), undefined); - expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['id'])); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['id'])); - }); - - it('should fall back to preview if the requested thumbnail file does not exist', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ ...assetStub.image, path: '/path/to/preview.jpg' }); - - await expect( - sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.THUMBNAIL }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/path/to/preview.jpg', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'image/jpeg', - fileName: 'asset-id_thumbnail.jpg', - }), - ); - }); - - it('should get preview file', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ ...assetStub.image, path: '/uploads/user-id/thumbs/path.jpg' }); - await expect( - sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.PREVIEW }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/thumbs/path.jpg', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'image/jpeg', - fileName: 'asset-id_preview.jpg', - }), - ); - }); - - it('should get thumbnail file', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ ...assetStub.image, path: '/uploads/user-id/webp/path.ext' }); - await expect( - sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.THUMBNAIL }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/webp/path.ext', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'application/octet-stream', - fileName: 'asset-id_thumbnail.ext', - }), - ); - expect(mocks.asset.getForThumbnail).toHaveBeenCalledWith(assetStub.image.id, AssetFileType.Thumbnail, false); - }); - - it('should get original thumbnail by default', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ - ...assetStub.image, - path: '/uploads/user-id/thumbs/original-thumbnail.jpg', - }); - await expect( - sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.THUMBNAIL }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/thumbs/original-thumbnail.jpg', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'image/jpeg', - fileName: 'asset-id_thumbnail.jpg', - }), - ); - expect(mocks.asset.getForThumbnail).toHaveBeenCalledWith(assetStub.image.id, AssetFileType.Thumbnail, false); - }); - - it('should get edited thumbnail when edited=true', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ - ...assetStub.image, - path: '/uploads/user-id/thumbs/edited-thumbnail.jpg', - }); - await expect( - sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.THUMBNAIL, edited: true }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/thumbs/edited-thumbnail.jpg', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'image/jpeg', - fileName: 'asset-id_thumbnail.jpg', - }), - ); - expect(mocks.asset.getForThumbnail).toHaveBeenCalledWith(assetStub.image.id, AssetFileType.Thumbnail, true); - }); - - it('should get original thumbnail when edited=false', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ - ...assetStub.image, - path: '/uploads/user-id/thumbs/original-thumbnail.jpg', - }); - await expect( - sut.viewThumbnail(authStub.admin, assetStub.image.id, { size: AssetMediaSize.THUMBNAIL, edited: false }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/thumbs/original-thumbnail.jpg', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'image/jpeg', - fileName: 'asset-id_thumbnail.jpg', - }), - ); - expect(mocks.asset.getForThumbnail).toHaveBeenCalledWith(assetStub.image.id, AssetFileType.Thumbnail, false); - }); - - it('should not return the unedited version if requested using a shared link', async () => { - mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getForThumbnail.mockResolvedValue({ - ...assetStub.image, - path: '/uploads/user-id/thumbs/edited-thumbnail.jpg', - }); - await expect( - sut.viewThumbnail(authStub.adminSharedLink, assetStub.image.id, { - size: AssetMediaSize.THUMBNAIL, - edited: true, - }), - ).resolves.toEqual( - new ImmichFileResponse({ - path: '/uploads/user-id/thumbs/edited-thumbnail.jpg', - cacheControl: CacheControl.PrivateWithCache, - contentType: 'image/jpeg', - fileName: 'asset-id_thumbnail.jpg', - }), - ); - expect(mocks.asset.getForThumbnail).toHaveBeenCalledWith(assetStub.image.id, AssetFileType.Thumbnail, true); - }); - }); - - describe('playbackVideo', () => { - it('should require asset.view permissions', async () => { - await expect(sut.playbackVideo(authStub.admin, 'id')).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['id']), undefined); - expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['id'])); - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(userStub.admin.id, new Set(['id'])); - }); - - it('should throw an error if the video asset could not be found', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - - await expect(sut.playbackVideo(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(NotFoundException); - }); - - it('should return the encoded video path if available', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.hasEncodedVideo.id])); - mocks.asset.getForVideo.mockResolvedValue(assetStub.hasEncodedVideo); - - await expect(sut.playbackVideo(authStub.admin, assetStub.hasEncodedVideo.id)).resolves.toEqual( - new ImmichFileResponse({ - path: assetStub.hasEncodedVideo.encodedVideoPath!, - cacheControl: CacheControl.PrivateWithCache, - contentType: 'video/mp4', - }), - ); - }); - - it('should fall back to the original path', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.video.id])); - mocks.asset.getForVideo.mockResolvedValue(assetStub.video); - - await expect(sut.playbackVideo(authStub.admin, assetStub.video.id)).resolves.toEqual( - new ImmichFileResponse({ - path: assetStub.video.originalPath, - cacheControl: CacheControl.PrivateWithCache, - contentType: 'application/octet-stream', - }), - ); - }); - }); - - describe('checkExistingAssets', () => { - it('should get existing asset ids', async () => { - mocks.asset.getByDeviceIds.mockResolvedValue(['42']); - await expect( - sut.checkExistingAssets(authStub.admin, { deviceId: '420', deviceAssetIds: ['69'] }), - ).resolves.toEqual({ existingIds: ['42'] }); - - expect(mocks.asset.getByDeviceIds).toHaveBeenCalledWith(userStub.admin.id, '420', ['69']); - }); - }); - - describe('replaceAsset', () => { - it('should fail the auth check when update photo does not exist', async () => { - await expect(sut.replaceAsset(authStub.user1, 'id', replaceDto, fileStub.photo)).rejects.toThrow( - 'Not found or no asset.update access', - ); - - expect(mocks.asset.create).not.toHaveBeenCalled(); - }); - - it('should fail if asset cannot be fetched', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([existingAsset.id])); - await expect(sut.replaceAsset(authStub.user1, existingAsset.id, replaceDto, fileStub.photo)).rejects.toThrow( - 'Asset not found', - ); - - expect(mocks.asset.create).not.toHaveBeenCalled(); - }); - - it('should update a photo with no sidecar to photo with no sidecar', async () => { - const updatedFile = fileStub.photo; - const updatedAsset = { ...existingAsset, ...updatedFile }; - mocks.asset.getById.mockResolvedValueOnce(existingAsset); - mocks.asset.getById.mockResolvedValueOnce(updatedAsset); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([existingAsset.id])); - // this is the original file size - mocks.storage.stat.mockResolvedValue({ size: 0 } as Stats); - // this is for the clone call - mocks.asset.create.mockResolvedValue(copiedAsset); - - await expect(sut.replaceAsset(authStub.user1, existingAsset.id, replaceDto, updatedFile)).resolves.toEqual({ - status: AssetMediaStatus.REPLACED, - id: 'copied-asset', - }); - - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: existingAsset.id, - originalFileName: 'photo1.jpeg', - originalPath: 'fake_path/photo1.jpeg', - }), - ); - expect(mocks.asset.create).toHaveBeenCalledWith( - expect.objectContaining({ - originalFileName: 'existing-filename.jpeg', - originalPath: 'fake_path/asset_1.jpeg', - }), - ); - expect(mocks.asset.deleteFile).toHaveBeenCalledWith( - expect.objectContaining({ - assetId: existingAsset.id, - type: AssetFileType.Sidecar, - }), - ); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], { - deletedAt: expect.any(Date), - status: AssetStatus.Trashed, - }); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size); - expect(mocks.storage.utimes).toHaveBeenCalledWith( - updatedFile.originalPath, - expect.any(Date), - new Date(replaceDto.fileModifiedAt), - ); - }); - - it('should update a photo with sidecar to photo with sidecar', async () => { - const updatedFile = fileStub.photo; - const sidecarFile = fileStub.photoSidecar; - const updatedAsset = { ...sidecarAsset, ...updatedFile }; - mocks.asset.getById.mockResolvedValueOnce(existingAsset); - mocks.asset.getById.mockResolvedValueOnce(updatedAsset); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([sidecarAsset.id])); - // this is the original file size - mocks.storage.stat.mockResolvedValue({ size: 0 } as Stats); - // this is for the clone call - mocks.asset.create.mockResolvedValue(copiedAsset); - - await expect( - sut.replaceAsset(authStub.user1, sidecarAsset.id, replaceDto, updatedFile, sidecarFile), - ).resolves.toEqual({ - status: AssetMediaStatus.REPLACED, - id: 'copied-asset', - }); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], { - deletedAt: expect.any(Date), - status: AssetStatus.Trashed, - }); - expect(mocks.asset.upsertFile).toHaveBeenCalledWith( - expect.objectContaining({ - assetId: existingAsset.id, - path: sidecarFile.originalPath, - type: AssetFileType.Sidecar, - }), - ); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size); - expect(mocks.storage.utimes).toHaveBeenCalledWith( - updatedFile.originalPath, - expect.any(Date), - new Date(replaceDto.fileModifiedAt), - ); - }); - - it('should update a photo with a sidecar to photo with no sidecar', async () => { - const updatedFile = fileStub.photo; - - const updatedAsset = { ...sidecarAsset, ...updatedFile }; - mocks.asset.getById.mockResolvedValueOnce(sidecarAsset); - mocks.asset.getById.mockResolvedValueOnce(updatedAsset); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([sidecarAsset.id])); - // this is the original file size - mocks.storage.stat.mockResolvedValue({ size: 0 } as Stats); - // this is for the copy call - mocks.asset.create.mockResolvedValue(copiedAsset); - - await expect(sut.replaceAsset(authStub.user1, existingAsset.id, replaceDto, updatedFile)).resolves.toEqual({ - status: AssetMediaStatus.REPLACED, - id: 'copied-asset', - }); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith([copiedAsset.id], { - deletedAt: expect.any(Date), - status: AssetStatus.Trashed, - }); - expect(mocks.asset.deleteFile).toHaveBeenCalledWith( - expect.objectContaining({ - assetId: existingAsset.id, - type: AssetFileType.Sidecar, - }), - ); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(authStub.user1.user.id, updatedFile.size); - expect(mocks.storage.utimes).toHaveBeenCalledWith( - updatedFile.originalPath, - expect.any(Date), - new Date(replaceDto.fileModifiedAt), - ); - }); - - it('should handle a photo with sidecar to duplicate photo ', async () => { - const updatedFile = fileStub.photo; - const error = new Error('unique key violation'); - (error as any).constraint_name = ASSET_CHECKSUM_CONSTRAINT; - - mocks.asset.update.mockRejectedValue(error); - mocks.asset.getById.mockResolvedValueOnce(sidecarAsset); - mocks.asset.getUploadAssetIdByChecksum.mockResolvedValue(sidecarAsset.id); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([sidecarAsset.id])); - // this is the original file size - mocks.storage.stat.mockResolvedValue({ size: 0 } as Stats); - // this is for the clone call - mocks.asset.create.mockResolvedValue(copiedAsset); - - await expect(sut.replaceAsset(authStub.user1, sidecarAsset.id, replaceDto, updatedFile)).resolves.toEqual({ - status: AssetMediaStatus.DUPLICATE, - id: sidecarAsset.id, - }); - - expect(mocks.asset.create).not.toHaveBeenCalled(); - expect(mocks.asset.updateAll).not.toHaveBeenCalled(); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.asset.deleteFile).not.toHaveBeenCalled(); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: [updatedFile.originalPath, undefined] }, - }); - expect(mocks.user.updateUsage).not.toHaveBeenCalled(); - }); - }); - - describe('bulkUploadCheck', () => { - it('should accept hex and base64 checksums', async () => { - const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex'); - const file2 = Buffer.from('53be335e99f18a66ff12e9a901c7a6171dd76573', 'hex'); - - mocks.asset.getByChecksums.mockResolvedValue([ - { id: 'asset-1', checksum: file1, deletedAt: null }, - { id: 'asset-2', checksum: file2, deletedAt: null }, - ]); - - await expect( - sut.bulkUploadCheck(authStub.admin, { - assets: [ - { id: '1', checksum: file1.toString('hex') }, - { id: '2', checksum: file2.toString('base64') }, - ], - }), - ).resolves.toEqual({ - results: [ - { - id: '1', - assetId: 'asset-1', - action: AssetUploadAction.REJECT, - reason: AssetRejectReason.DUPLICATE, - isTrashed: false, - }, - { - id: '2', - assetId: 'asset-2', - action: AssetUploadAction.REJECT, - reason: AssetRejectReason.DUPLICATE, - isTrashed: false, - }, - ], - }); - - expect(mocks.asset.getByChecksums).toHaveBeenCalledWith(authStub.admin.user.id, [file1, file2]); - }); - - it('should return non-duplicates as well', async () => { - const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex'); - const file2 = Buffer.from('53be335e99f18a66ff12e9a901c7a6171dd76573', 'hex'); - - mocks.asset.getByChecksums.mockResolvedValue([{ id: 'asset-1', checksum: file1, deletedAt: null }]); - - await expect( - sut.bulkUploadCheck(authStub.admin, { - assets: [ - { id: '1', checksum: file1.toString('hex') }, - { id: '2', checksum: file2.toString('base64') }, - ], - }), - ).resolves.toEqual({ - results: [ - { - id: '1', - assetId: 'asset-1', - action: AssetUploadAction.REJECT, - reason: AssetRejectReason.DUPLICATE, - isTrashed: false, - }, - { - id: '2', - action: AssetUploadAction.ACCEPT, - }, - ], - }); - - expect(mocks.asset.getByChecksums).toHaveBeenCalledWith(authStub.admin.user.id, [file1, file2]); - }); - }); - - describe('onUploadError', () => { - it('should queue a job to delete the uploaded file', async () => { - const request = { - body: {}, - user: authStub.user1, - } as AuthRequest; - - const file = { - fieldname: UploadFieldName.ASSET_DATA, - originalname: 'image.jpg', - mimetype: 'image/jpeg', - buffer: Buffer.from(''), - size: 1000, - uuid: 'random-uuid', - checksum: Buffer.from('checksum', 'utf8'), - originalPath: '/data/upload/user-id/ra/nd/random-uuid.jpg', - } as unknown as Express.Multer.File; - - await sut.onUploadError(request, file); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: [expect.stringContaining('/data/upload/user-id/ra/nd/random-uuid.jpg')] }, - }); - }); - }); -}); diff --git a/server/src/services/asset-media.service.ts b/server/src/services/asset-media.service.ts deleted file mode 100644 index 020bda4df7..0000000000 --- a/server/src/services/asset-media.service.ts +++ /dev/null @@ -1,480 +0,0 @@ -import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { extname } from 'node:path'; -import sanitize from 'sanitize-filename'; -import { StorageCore } from 'src/cores/storage.core'; -import { Asset } from 'src/database'; -import { - AssetBulkUploadCheckResponseDto, - AssetMediaResponseDto, - AssetMediaStatus, - AssetRejectReason, - AssetUploadAction, - CheckExistingAssetsResponseDto, -} from 'src/dtos/asset-media-response.dto'; -import { - AssetBulkUploadCheckDto, - AssetMediaCreateDto, - AssetMediaOptionsDto, - AssetMediaReplaceDto, - AssetMediaSize, - CheckExistingAssetsDto, - UploadFieldName, -} from 'src/dtos/asset-media.dto'; -import { AssetDownloadOriginalDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - AssetFileType, - AssetStatus, - AssetVisibility, - CacheControl, - JobName, - Permission, - StorageFolder, -} from 'src/enum'; -import { AuthRequest } from 'src/middleware/auth.guard'; -import { BaseService } from 'src/services/base.service'; -import { UploadFile, UploadRequest } from 'src/types'; -import { requireUploadAccess } from 'src/utils/access'; -import { asUploadRequest, onBeforeLink } from 'src/utils/asset.util'; -import { isAssetChecksumConstraint } from 'src/utils/database'; -import { getFilenameExtension, getFileNameWithoutExtension, ImmichFileResponse } from 'src/utils/file'; -import { mimeTypes } from 'src/utils/mime-types'; -import { fromChecksum } from 'src/utils/request'; - -export interface AssetMediaRedirectResponse { - targetSize: AssetMediaSize | 'original'; -} - -@Injectable() -export class AssetMediaService extends BaseService { - async getUploadAssetIdByChecksum(auth: AuthDto, checksum?: string): Promise { - if (!checksum) { - return; - } - - const assetId = await this.assetRepository.getUploadAssetIdByChecksum(auth.user.id, fromChecksum(checksum)); - if (!assetId) { - return; - } - - return { id: assetId, status: AssetMediaStatus.DUPLICATE }; - } - - canUploadFile({ auth, fieldName, file, body }: UploadRequest): true { - requireUploadAccess(auth); - - const filename = body.filename || file.originalName; - - switch (fieldName) { - case UploadFieldName.ASSET_DATA: { - if (mimeTypes.isAsset(filename)) { - return true; - } - break; - } - - case UploadFieldName.SIDECAR_DATA: { - if (mimeTypes.isSidecar(filename)) { - return true; - } - break; - } - - case UploadFieldName.PROFILE_DATA: { - if (mimeTypes.isProfile(filename)) { - return true; - } - break; - } - } - - this.logger.error(`Unsupported file type ${filename}`); - throw new BadRequestException(`Unsupported file type ${filename}`); - } - - getUploadFilename({ auth, fieldName, file, body }: UploadRequest): string { - requireUploadAccess(auth); - - const extension = extname(body.filename || file.originalName); - - const lookup = { - [UploadFieldName.ASSET_DATA]: extension, - [UploadFieldName.SIDECAR_DATA]: '.xmp', - [UploadFieldName.PROFILE_DATA]: extension, - }; - - return sanitize(`${file.uuid}${lookup[fieldName]}`); - } - - getUploadFolder({ auth, fieldName, file }: UploadRequest): string { - auth = requireUploadAccess(auth); - - let folder = StorageCore.getNestedFolder(StorageFolder.Upload, auth.user.id, file.uuid); - if (fieldName === UploadFieldName.PROFILE_DATA) { - folder = StorageCore.getFolderLocation(StorageFolder.Profile, auth.user.id); - } - - this.storageRepository.mkdirSync(folder); - - return folder; - } - - async onUploadError(request: AuthRequest, file: Express.Multer.File) { - const uploadFilename = this.getUploadFilename(asUploadRequest(request, file)); - const uploadFolder = this.getUploadFolder(asUploadRequest(request, file)); - const uploadPath = `${uploadFolder}/${uploadFilename}`; - - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [uploadPath] } }); - } - - async uploadAsset( - auth: AuthDto, - dto: AssetMediaCreateDto, - file: UploadFile, - sidecarFile?: UploadFile, - ): Promise { - try { - await this.requireAccess({ - auth, - permission: Permission.AssetUpload, - // do not need an id here, but the interface requires it - ids: [auth.user.id], - }); - - this.requireQuota(auth, file.size); - - if (dto.livePhotoVideoId) { - await onBeforeLink( - { asset: this.assetRepository, event: this.eventRepository }, - { userId: auth.user.id, livePhotoVideoId: dto.livePhotoVideoId }, - ); - } - const asset = await this.create(auth.user.id, dto, file, sidecarFile); - - await this.userRepository.updateUsage(auth.user.id, file.size); - - return { id: asset.id, status: AssetMediaStatus.CREATED }; - } catch (error: any) { - return this.handleUploadError(error, auth, file, sidecarFile); - } - } - - async replaceAsset( - auth: AuthDto, - id: string, - dto: AssetMediaReplaceDto, - file: UploadFile, - sidecarFile?: UploadFile, - ): Promise { - try { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] }); - const asset = await this.assetRepository.getById(id); - - if (!asset) { - throw new Error('Asset not found'); - } - - this.requireQuota(auth, file.size); - - await this.replaceFileData(asset.id, dto, file, sidecarFile?.originalPath); - - // Next, create a backup copy of the existing record. The db record has already been updated above, - // but the local variable holds the original file data paths. - const copiedPhoto = await this.createCopy(asset); - // and immediate trash it - await this.assetRepository.updateAll([copiedPhoto.id], { deletedAt: new Date(), status: AssetStatus.Trashed }); - await this.eventRepository.emit('AssetTrash', { assetId: copiedPhoto.id, userId: auth.user.id }); - - await this.userRepository.updateUsage(auth.user.id, file.size); - - return { status: AssetMediaStatus.REPLACED, id: copiedPhoto.id }; - } catch (error: any) { - return this.handleUploadError(error, auth, file, sidecarFile); - } - } - - async downloadOriginal(auth: AuthDto, id: string, dto: AssetDownloadOriginalDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: [id] }); - - if (auth.sharedLink) { - dto.edited = true; - } - - const { originalPath, originalFileName, editedPath } = await this.assetRepository.getForOriginal( - id, - dto.edited ?? false, - ); - - const path = editedPath ?? originalPath!; - - return new ImmichFileResponse({ - path, - fileName: getFileNameWithoutExtension(originalFileName) + getFilenameExtension(path), - contentType: mimeTypes.lookup(path), - cacheControl: CacheControl.PrivateWithCache, - }); - } - - async viewThumbnail( - auth: AuthDto, - id: string, - dto: AssetMediaOptionsDto, - ): Promise { - await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] }); - - if (dto.size === AssetMediaSize.Original) { - throw new BadRequestException('May not request original file'); - } - - if (auth.sharedLink) { - dto.edited = true; - } - - const size = (dto.size ?? AssetMediaSize.THUMBNAIL) as unknown as AssetFileType; - const { originalPath, originalFileName, path } = await this.assetRepository.getForThumbnail( - id, - size, - dto.edited ?? false, - ); - - if (size === AssetFileType.FullSize && mimeTypes.isWebSupportedImage(originalPath) && !dto.edited) { - // use original file for web supported images - return { targetSize: 'original' }; - } - - if (dto.size === AssetMediaSize.FULLSIZE && !path) { - // downgrade to preview if fullsize is not available. - // e.g. disabled or not yet (re)generated - return { targetSize: AssetMediaSize.PREVIEW }; - } - - if (!path) { - throw new NotFoundException('Asset media not found'); - } - - const fileName = `${getFileNameWithoutExtension(originalFileName)}_${size}${getFilenameExtension(path)}`; - - return new ImmichFileResponse({ - fileName, - path, - contentType: mimeTypes.lookup(path), - cacheControl: CacheControl.PrivateWithCache, - }); - } - - async playbackVideo(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] }); - - const asset = await this.assetRepository.getForVideo(id); - - if (!asset) { - throw new NotFoundException('Asset not found or asset is not a video'); - } - - const filepath = asset.encodedVideoPath || asset.originalPath; - - return new ImmichFileResponse({ - path: filepath, - contentType: mimeTypes.lookup(filepath), - cacheControl: CacheControl.PrivateWithCache, - }); - } - - async checkExistingAssets( - auth: AuthDto, - checkExistingAssetsDto: CheckExistingAssetsDto, - ): Promise { - const existingIds = await this.assetRepository.getByDeviceIds( - auth.user.id, - checkExistingAssetsDto.deviceId, - checkExistingAssetsDto.deviceAssetIds, - ); - return { existingIds }; - } - - async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise { - const checksums: Buffer[] = dto.assets.map((asset) => fromChecksum(asset.checksum)); - const results = await this.assetRepository.getByChecksums(auth.user.id, checksums); - const checksumMap: Record = {}; - - for (const { id, deletedAt, checksum } of results) { - checksumMap[checksum.toString('hex')] = { id, isTrashed: !!deletedAt }; - } - - return { - results: dto.assets.map(({ id, checksum }) => { - const duplicate = checksumMap[fromChecksum(checksum).toString('hex')]; - if (duplicate) { - return { - id, - action: AssetUploadAction.REJECT, - reason: AssetRejectReason.DUPLICATE, - assetId: duplicate.id, - isTrashed: duplicate.isTrashed, - }; - } - - return { - id, - action: AssetUploadAction.ACCEPT, - }; - }), - }; - } - - private async handleUploadError( - error: any, - auth: AuthDto, - file: UploadFile, - sidecarFile?: UploadFile, - ): Promise { - // clean up files - await this.jobRepository.queue({ - name: JobName.FileDelete, - data: { files: [file.originalPath, sidecarFile?.originalPath] }, - }); - - // handle duplicates with a success response - if (isAssetChecksumConstraint(error)) { - const duplicateId = await this.assetRepository.getUploadAssetIdByChecksum(auth.user.id, file.checksum); - if (!duplicateId) { - this.logger.error(`Error locating duplicate for checksum constraint`); - throw new InternalServerErrorException(); - } - return { status: AssetMediaStatus.DUPLICATE, id: duplicateId }; - } - - this.logger.error(`Error uploading file ${error}`, error?.stack); - throw error; - } - - /** - * Updates the specified assetId to the specified photo data file properties: checksum, path, - * timestamps, deviceIds, and sidecar. Derived properties like: faces, smart search info, etc - * are UNTOUCHED. The photo data files modification times on the filesysytem are updated to - * the specified timestamps. The exif db record is upserted, and then A METADATA_EXTRACTION - * job is queued to update these derived properties. - */ - private async replaceFileData( - assetId: string, - dto: AssetMediaReplaceDto, - file: UploadFile, - sidecarPath?: string, - ): Promise { - await this.assetRepository.update({ - id: assetId, - - checksum: file.checksum, - originalPath: file.originalPath, - type: mimeTypes.assetType(file.originalPath), - originalFileName: file.originalName, - - deviceAssetId: dto.deviceAssetId, - deviceId: dto.deviceId, - fileCreatedAt: dto.fileCreatedAt, - fileModifiedAt: dto.fileModifiedAt, - localDateTime: dto.fileCreatedAt, - duration: dto.duration || null, - - livePhotoVideoId: null, - }); - - await (sidecarPath - ? this.assetRepository.upsertFile({ assetId, type: AssetFileType.Sidecar, path: sidecarPath }) - : this.assetRepository.deleteFile({ assetId, type: AssetFileType.Sidecar })); - - await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt)); - await this.assetRepository.upsertExif( - { assetId, fileSizeInByte: file.size }, - { lockedPropertiesBehavior: 'override' }, - ); - await this.jobRepository.queue({ - name: JobName.AssetExtractMetadata, - data: { id: assetId, source: 'upload' }, - }); - } - - /** - * Create a 'shallow' copy of the specified asset record creating a new asset record in the database. - * Uses only vital properties excluding things like: stacks, faces, smart search info, etc, - * and then queues a METADATA_EXTRACTION job. - */ - private async createCopy(asset: Omit) { - const created = await this.assetRepository.create({ - ownerId: asset.ownerId, - originalPath: asset.originalPath, - originalFileName: asset.originalFileName, - libraryId: asset.libraryId, - deviceAssetId: asset.deviceAssetId, - deviceId: asset.deviceId, - type: asset.type, - checksum: asset.checksum, - fileCreatedAt: asset.fileCreatedAt, - localDateTime: asset.localDateTime, - fileModifiedAt: asset.fileModifiedAt, - livePhotoVideoId: asset.livePhotoVideoId, - }); - - const { size } = await this.storageRepository.stat(created.originalPath); - await this.assetRepository.upsertExif( - { assetId: created.id, fileSizeInByte: size }, - { lockedPropertiesBehavior: 'override' }, - ); - await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: created.id, source: 'copy' } }); - return created; - } - - private async create(ownerId: string, dto: AssetMediaCreateDto, file: UploadFile, sidecarFile?: UploadFile) { - const asset = await this.assetRepository.create({ - ownerId, - libraryId: null, - - checksum: file.checksum, - originalPath: file.originalPath, - - deviceAssetId: dto.deviceAssetId, - deviceId: dto.deviceId, - - fileCreatedAt: dto.fileCreatedAt, - fileModifiedAt: dto.fileModifiedAt, - localDateTime: dto.fileCreatedAt, - - type: mimeTypes.assetType(file.originalPath), - isFavorite: dto.isFavorite, - duration: dto.duration || null, - visibility: dto.visibility ?? AssetVisibility.Timeline, - livePhotoVideoId: dto.livePhotoVideoId, - originalFileName: dto.filename || file.originalName, - }); - - if (dto.metadata?.length) { - await this.assetRepository.upsertMetadata(asset.id, dto.metadata); - } - - if (sidecarFile) { - await this.assetRepository.upsertFile({ - assetId: asset.id, - path: sidecarFile.originalPath, - type: AssetFileType.Sidecar, - }); - await this.storageRepository.utimes(sidecarFile.originalPath, new Date(), new Date(dto.fileModifiedAt)); - } - await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt)); - await this.assetRepository.upsertExif( - { assetId: asset.id, fileSizeInByte: file.size }, - { lockedPropertiesBehavior: 'override' }, - ); - - await this.eventRepository.emit('AssetCreate', { asset }); - - await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: asset.id, source: 'upload' } }); - - return asset; - } - - private requireQuota(auth: AuthDto, size: number) { - if (auth.user.quotaSizeInBytes !== null && auth.user.quotaSizeInBytes < auth.user.quotaUsageInBytes + size) { - throw new BadRequestException('Quota has been exceeded!'); - } - } -} diff --git a/server/src/services/asset.service.spec.ts b/server/src/services/asset.service.spec.ts deleted file mode 100755 index 707faa326d..0000000000 --- a/server/src/services/asset.service.spec.ts +++ /dev/null @@ -1,827 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { DateTime } from 'luxon'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { AssetJobName, AssetStatsResponseDto } from 'src/dtos/asset.dto'; -import { AssetEditAction } from 'src/dtos/editing.dto'; -import { AssetMetadataKey, AssetStatus, AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum'; -import { AssetStats } from 'src/repositories/asset.repository'; -import { AssetService } from 'src/services/asset.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { userStub } from 'test/fixtures/user.stub'; -import { factory } from 'test/small.factory'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -const stats: AssetStats = { - [AssetType.Image]: 10, - [AssetType.Video]: 23, - [AssetType.Audio]: 0, - [AssetType.Other]: 0, -}; - -const statResponse: AssetStatsResponseDto = { - images: 10, - videos: 23, - total: 33, -}; - -describe(AssetService.name, () => { - let sut: AssetService; - let mocks: ServiceMocks; - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - const mockGetById = (assets: MapAsset[]) => { - mocks.asset.getById.mockImplementation((assetId) => Promise.resolve(assets.find((asset) => asset.id === assetId))); - }; - - beforeEach(() => { - ({ sut, mocks } = newTestService(AssetService)); - - mockGetById([assetStub.livePhotoStillAsset, assetStub.livePhotoMotionAsset]); - }); - - describe('getStatistics', () => { - it('should get the statistics for a user, excluding archived assets', async () => { - mocks.asset.getStatistics.mockResolvedValue(stats); - await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.Timeline })).resolves.toEqual( - statResponse, - ); - expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { - visibility: AssetVisibility.Timeline, - }); - }); - - it('should get the statistics for a user for archived assets', async () => { - mocks.asset.getStatistics.mockResolvedValue(stats); - await expect(sut.getStatistics(authStub.admin, { visibility: AssetVisibility.Archive })).resolves.toEqual( - statResponse, - ); - expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { - visibility: AssetVisibility.Archive, - }); - }); - - it('should get the statistics for a user for favorite assets', async () => { - mocks.asset.getStatistics.mockResolvedValue(stats); - await expect(sut.getStatistics(authStub.admin, { isFavorite: true })).resolves.toEqual(statResponse); - expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, { isFavorite: true }); - }); - - it('should get the statistics for a user for all assets', async () => { - mocks.asset.getStatistics.mockResolvedValue(stats); - await expect(sut.getStatistics(authStub.admin, {})).resolves.toEqual(statResponse); - expect(mocks.asset.getStatistics).toHaveBeenCalledWith(authStub.admin.user.id, {}); - }); - }); - - describe('getRandom', () => { - it('should get own random assets', async () => { - mocks.partner.getAll.mockResolvedValue([]); - mocks.asset.getRandom.mockResolvedValue([assetStub.image]); - - await sut.getRandom(authStub.admin, 1); - - expect(mocks.asset.getRandom).toHaveBeenCalledWith([authStub.admin.user.id], 1); - }); - - it('should not include partner assets if not in timeline', async () => { - const partner = factory.partner({ inTimeline: false }); - const auth = factory.auth({ user: { id: partner.sharedWithId } }); - - mocks.asset.getRandom.mockResolvedValue([assetStub.image]); - mocks.partner.getAll.mockResolvedValue([partner]); - - await sut.getRandom(auth, 1); - - expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id], 1); - }); - - it('should include partner assets if in timeline', async () => { - const partner = factory.partner({ inTimeline: true }); - const auth = factory.auth({ user: { id: partner.sharedWithId } }); - - mocks.asset.getRandom.mockResolvedValue([assetStub.image]); - mocks.partner.getAll.mockResolvedValue([partner]); - - await sut.getRandom(auth, 1); - - expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id, partner.sharedById], 1); - }); - }); - - describe('get', () => { - it('should allow owner access', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - - await sut.get(authStub.admin, assetStub.image.id); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set([assetStub.image.id]), - undefined, - ); - }); - - it('should allow shared link access', async () => { - mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - - await sut.get(authStub.adminSharedLink, assetStub.image.id); - - expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith( - authStub.adminSharedLink.sharedLink?.id, - new Set([assetStub.image.id]), - ); - }); - - it('should strip metadata for shared link if exif is disabled', async () => { - mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - - const result = await sut.get( - { ...authStub.adminSharedLink, sharedLink: { ...authStub.adminSharedLink.sharedLink!, showExif: false } }, - assetStub.image.id, - ); - - expect(result).toEqual(expect.objectContaining({ hasMetadata: false })); - expect(result).not.toHaveProperty('exifInfo'); - expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith( - authStub.adminSharedLink.sharedLink?.id, - new Set([assetStub.image.id]), - ); - }); - - it('should allow partner sharing access', async () => { - mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - - await sut.get(authStub.admin, assetStub.image.id); - - expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set([assetStub.image.id]), - ); - }); - - it('should allow shared album access', async () => { - mocks.access.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - - await sut.get(authStub.admin, assetStub.image.id); - - expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set([assetStub.image.id]), - ); - }); - - it('should throw an error for no access', async () => { - await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.getById).not.toHaveBeenCalled(); - }); - - it('should throw an error for an invalid shared link', async () => { - await expect(sut.get(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.asset.checkOwnerAccess).not.toHaveBeenCalled(); - expect(mocks.asset.getById).not.toHaveBeenCalled(); - }); - - it('should throw an error if the asset could not be found', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - - await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException); - }); - }); - - describe('update', () => { - it('should require asset write access for the id', async () => { - await expect( - sut.update(authStub.admin, 'asset-1', { visibility: AssetVisibility.Timeline }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should update the asset', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - mocks.asset.update.mockResolvedValue(assetStub.image); - - await sut.update(authStub.admin, 'asset-1', { isFavorite: true }); - - expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true }); - }); - - it('should update the exif description', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getById.mockResolvedValue(assetStub.image); - mocks.asset.update.mockResolvedValue(assetStub.image); - - await sut.update(authStub.admin, 'asset-1', { description: 'Test description' }); - - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-1', description: 'Test description', lockedProperties: ['description'] }, - { lockedPropertiesBehavior: 'append' }, - ); - }); - - it('should update the exif rating', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getById.mockResolvedValueOnce(assetStub.image); - mocks.asset.update.mockResolvedValueOnce(assetStub.image); - - await sut.update(authStub.admin, 'asset-1', { rating: 3 }); - - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { - assetId: 'asset-1', - rating: 3, - lockedProperties: ['rating'], - }, - { lockedPropertiesBehavior: 'append' }, - ); - }); - - it('should fail linking a live video if the motion part could not be found', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id])); - - await expect( - sut.update(authStub.admin, assetStub.livePhotoStillAsset.id, { - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - expect(mocks.asset.update).not.toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: AssetVisibility.Timeline, - }); - expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', { - assetId: assetStub.livePhotoMotionAsset.id, - userId: userStub.admin.id, - }); - }); - - it('should fail linking a live video if the motion part is not a video', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id])); - mocks.asset.getById.mockResolvedValue(assetStub.livePhotoStillAsset); - - await expect( - sut.update(authStub.admin, assetStub.livePhotoStillAsset.id, { - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - expect(mocks.asset.update).not.toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: AssetVisibility.Timeline, - }); - expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', { - assetId: assetStub.livePhotoMotionAsset.id, - userId: userStub.admin.id, - }); - }); - - it('should fail linking a live video if the motion part has a different owner', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id])); - mocks.asset.getById.mockResolvedValue(assetStub.livePhotoMotionAsset); - - await expect( - sut.update(authStub.admin, assetStub.livePhotoStillAsset.id, { - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - expect(mocks.asset.update).not.toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: AssetVisibility.Timeline, - }); - expect(mocks.event.emit).not.toHaveBeenCalledWith('AssetShow', { - assetId: assetStub.livePhotoMotionAsset.id, - userId: userStub.admin.id, - }); - }); - - it('should link a live video', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id])); - mocks.asset.getById.mockResolvedValueOnce({ - ...assetStub.livePhotoMotionAsset, - ownerId: authStub.admin.user.id, - visibility: AssetVisibility.Timeline, - }); - mocks.asset.getById.mockResolvedValueOnce(assetStub.image); - mocks.asset.update.mockResolvedValue(assetStub.image); - - await sut.update(authStub.admin, assetStub.livePhotoStillAsset.id, { - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: AssetVisibility.Hidden, - }); - expect(mocks.event.emit).toHaveBeenCalledWith('AssetHide', { - assetId: assetStub.livePhotoMotionAsset.id, - userId: userStub.admin.id, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - }); - - it('should throw an error if asset could not be found after update', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - await expect(sut.update(authStub.admin, 'asset-1', { isFavorite: true })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - - it('should unlink a live video', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id])); - mocks.asset.getById.mockResolvedValueOnce(assetStub.livePhotoStillAsset); - mocks.asset.getById.mockResolvedValueOnce(assetStub.livePhotoMotionAsset); - mocks.asset.update.mockResolvedValueOnce(assetStub.image); - - await sut.update(authStub.admin, assetStub.livePhotoStillAsset.id, { livePhotoVideoId: null }); - - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: null, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: assetStub.livePhotoStillAsset.visibility, - }); - expect(mocks.event.emit).toHaveBeenCalledWith('AssetShow', { - assetId: assetStub.livePhotoMotionAsset.id, - userId: userStub.admin.id, - }); - }); - - it('should fail unlinking a live video if the asset could not be found', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.livePhotoStillAsset.id])); - // eslint-disable-next-line unicorn/no-useless-undefined - mocks.asset.getById.mockResolvedValueOnce(undefined); - - await expect( - sut.update(authStub.admin, assetStub.livePhotoStillAsset.id, { livePhotoVideoId: null }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - }); - - describe('updateAll', () => { - it('should require asset write access for all ids', async () => { - await expect( - sut.updateAll(authStub.admin, { - ids: ['asset-1'], - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should update all assets', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - - await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], visibility: AssetVisibility.Archive }); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { - visibility: AssetVisibility.Archive, - }); - }); - - it('should not update Assets table if no relevant fields are provided', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.updateAll(authStub.admin, { - ids: ['asset-1'], - latitude: 0, - longitude: 0, - isFavorite: undefined, - duplicateId: undefined, - rating: undefined, - }); - expect(mocks.asset.updateAll).not.toHaveBeenCalled(); - }); - - it('should update Assets table if visibility field is provided', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.updateAll(authStub.admin, { - ids: ['asset-1'], - latitude: 0, - longitude: 0, - visibility: AssetVisibility.Archive, - isFavorite: false, - duplicateId: undefined, - rating: undefined, - }); - expect(mocks.asset.updateAll).toHaveBeenCalled(); - expect(mocks.asset.updateAllExif).toHaveBeenCalledWith(['asset-1'], { latitude: 0, longitude: 0 }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.SidecarWrite, data: { id: 'asset-1' } }]); - }); - - it('should update exif table if latitude field is provided', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - const dateTimeOriginal = new Date().toISOString(); - await sut.updateAll(authStub.admin, { - ids: ['asset-1'], - latitude: 30, - longitude: 50, - dateTimeOriginal, - isFavorite: false, - duplicateId: undefined, - rating: undefined, - }); - expect(mocks.asset.updateAll).toHaveBeenCalled(); - expect(mocks.asset.updateAllExif).toHaveBeenCalledWith(['asset-1'], { - dateTimeOriginal, - latitude: 30, - longitude: 50, - }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.SidecarWrite, data: { id: 'asset-1' } }]); - }); - - it('should update Assets table if duplicateId is provided as null', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.updateAll(authStub.admin, { - ids: ['asset-1'], - latitude: 0, - longitude: 0, - isFavorite: undefined, - duplicateId: null, - rating: undefined, - }); - expect(mocks.asset.updateAll).toHaveBeenCalled(); - }); - - it('should update exif table if dateTimeRelative and timeZone field is provided', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - const dateTimeRelative = 35; - const timeZone = 'UTC+2'; - mocks.asset.updateDateTimeOriginal.mockResolvedValue([ - { assetId: 'asset-1', dateTimeOriginal: new Date('2020-02-25T04:41:00'), timeZone }, - ]); - await sut.updateAll(authStub.admin, { - ids: ['asset-1'], - dateTimeRelative, - timeZone, - }); - expect(mocks.asset.updateDateTimeOriginal).toHaveBeenCalledWith(['asset-1'], dateTimeRelative, timeZone); - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.SidecarWrite, data: { id: 'asset-1' } }]); - }); - }); - - describe('deleteAll', () => { - it('should require asset delete access for all ids', async () => { - await expect( - sut.deleteAll(authStub.user1, { - ids: ['asset-1'], - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should force delete a batch of assets', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2'])); - - await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: true }); - - expect(mocks.event.emit).toHaveBeenCalledWith('AssetDeleteAll', { - assetIds: ['asset1', 'asset2'], - userId: 'user-id', - }); - }); - - it('should soft delete a batch of assets', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2'])); - - await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: false }); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset1', 'asset2'], { - deletedAt: expect.any(Date), - status: AssetStatus.Trashed, - }); - expect(mocks.job.queue.mock.calls).toEqual([]); - }); - }); - - describe('handleAssetDeletionCheck', () => { - beforeAll(() => { - vi.useFakeTimers(); - }); - - afterAll(() => { - vi.useRealTimers(); - }); - - it('should immediately queue assets for deletion if trash is disabled', async () => { - const asset = factory.asset({ isOffline: false }); - - mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset])); - mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: false } }); - - await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.Success); - - expect(mocks.assetJob.streamForDeletedJob).toHaveBeenCalledWith(new Date()); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetDelete, data: { id: asset.id, deleteOnDisk: true } }, - ]); - }); - - it('should queue assets for deletion after trash duration', async () => { - const asset = factory.asset({ isOffline: false }); - - mocks.assetJob.streamForDeletedJob.mockReturnValue(makeStream([asset])); - mocks.systemMetadata.get.mockResolvedValue({ trash: { enabled: true, days: 7 } }); - - await expect(sut.handleAssetDeletionCheck()).resolves.toBe(JobStatus.Success); - - expect(mocks.assetJob.streamForDeletedJob).toHaveBeenCalledWith(DateTime.now().minus({ days: 7 }).toJSDate()); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetDelete, data: { id: asset.id, deleteOnDisk: true } }, - ]); - }); - }); - - describe('handleAssetDeletion', () => { - it('should clean up files', async () => { - const asset = assetStub.image; - mocks.assetJob.getForAssetDeletion.mockResolvedValue(asset); - - await sut.handleAssetDeletion({ id: asset.id, deleteOnDisk: true }); - - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.FileDelete, - data: { - files: [ - '/uploads/user-id/webp/path.ext', - '/uploads/user-id/thumbs/path.jpg', - '/uploads/user-id/fullsize/path.webp', - asset.originalPath, - ], - }, - }, - ], - ]); - expect(mocks.asset.remove).toHaveBeenCalledWith(asset); - }); - - it('should delete the entire stack if deleted asset was the primary asset and the stack would only contain one asset afterwards', async () => { - mocks.stack.delete.mockResolvedValue(); - mocks.assetJob.getForAssetDeletion.mockResolvedValue({ - ...assetStub.primaryImage, - stack: { - id: 'stack-id', - primaryAssetId: assetStub.primaryImage.id, - assets: [{ id: 'one-asset' }], - }, - }); - - await sut.handleAssetDeletion({ id: assetStub.primaryImage.id, deleteOnDisk: true }); - - expect(mocks.stack.delete).toHaveBeenCalledWith('stack-id'); - }); - - it('should delete a live photo', async () => { - mocks.assetJob.getForAssetDeletion.mockResolvedValue(assetStub.livePhotoStillAsset as any); - mocks.asset.getLivePhotoCount.mockResolvedValue(0); - - await sut.handleAssetDeletion({ - id: assetStub.livePhotoStillAsset.id, - deleteOnDisk: true, - }); - - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.AssetDelete, - data: { - id: assetStub.livePhotoMotionAsset.id, - deleteOnDisk: true, - }, - }, - ], - [ - { - name: JobName.FileDelete, - data: { - files: [ - '/uploads/user-id/webp/path.ext', - '/uploads/user-id/thumbs/path.jpg', - '/uploads/user-id/fullsize/path.webp', - 'fake_path/asset_1.jpeg', - ], - }, - }, - ], - ]); - }); - - it('should not delete a live motion part if it is being used by another asset', async () => { - mocks.asset.getLivePhotoCount.mockResolvedValue(2); - mocks.assetJob.getForAssetDeletion.mockResolvedValue(assetStub.livePhotoStillAsset as any); - - await sut.handleAssetDeletion({ - id: assetStub.livePhotoStillAsset.id, - deleteOnDisk: true, - }); - - expect(mocks.job.queue.mock.calls).toEqual([ - [ - { - name: JobName.FileDelete, - data: { - files: [ - '/uploads/user-id/webp/path.ext', - '/uploads/user-id/thumbs/path.jpg', - '/uploads/user-id/fullsize/path.webp', - 'fake_path/asset_1.jpeg', - ], - }, - }, - ], - ]); - }); - - it('should update usage', async () => { - mocks.assetJob.getForAssetDeletion.mockResolvedValue(assetStub.image); - await sut.handleAssetDeletion({ id: assetStub.image.id, deleteOnDisk: true }); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.image.ownerId, -5000); - }); - - it('should fail if asset could not be found', async () => { - mocks.assetJob.getForAssetDeletion.mockResolvedValue(void 0); - await expect(sut.handleAssetDeletion({ id: assetStub.image.id, deleteOnDisk: true })).resolves.toBe( - JobStatus.Failed, - ); - }); - }); - - describe('getOcr', () => { - it('should require asset read permission', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set()); - - await expect(sut.getOcr(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.ocr.getByAssetId).not.toHaveBeenCalled(); - }); - - it('should return OCR data for an asset', async () => { - const ocr1 = factory.assetOcr({ text: 'Hello World' }); - const ocr2 = factory.assetOcr({ text: 'Test Image' }); - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.ocr.getByAssetId.mockResolvedValue([ocr1, ocr2]); - mocks.asset.getById.mockResolvedValue(assetStub.image); - - await expect(sut.getOcr(authStub.admin, 'asset-1')).resolves.toEqual([ocr1, ocr2]); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set(['asset-1']), - undefined, - ); - expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith('asset-1'); - }); - - it('should return empty array when no OCR data exists', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.ocr.getByAssetId.mockResolvedValue([]); - mocks.asset.getById.mockResolvedValue(assetStub.image); - await expect(sut.getOcr(authStub.admin, 'asset-1')).resolves.toEqual([]); - - expect(mocks.ocr.getByAssetId).toHaveBeenCalledWith('asset-1'); - }); - }); - - describe('run', () => { - it('should run the refresh faces job', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_FACES }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.AssetDetectFaces, data: { id: 'asset-1' } }]); - }); - - it('should run the refresh metadata job', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetExtractMetadata, data: { id: 'asset-1' } }, - ]); - }); - - it('should run the refresh thumbnails job', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1' } }, - ]); - }); - - it('should run the transcode video', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - - await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.AssetEncodeVideo, data: { id: 'asset-1' } }]); - }); - }); - - describe('getUserAssetsByDeviceId', () => { - it('get assets by device id', async () => { - const assets = [assetStub.image, assetStub.image1]; - - mocks.asset.getAllByDeviceId.mockResolvedValue(assets.map((asset) => asset.deviceAssetId)); - - const deviceId = 'device-id'; - const result = await sut.getUserAssetsByDeviceId(authStub.user1, deviceId); - - expect(result.length).toEqual(2); - expect(result).toEqual(assets.map((asset) => asset.deviceAssetId)); - }); - }); - - describe('upsertMetadata', () => { - it('should throw a bad request exception if duplicate keys are sent', async () => { - const asset = factory.asset(); - const items = [ - { key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, - { key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, - ]; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - - await expect(sut.upsertMetadata(authStub.admin, asset.id, { items })).rejects.toThrowError( - 'Duplicate items are not allowed:', - ); - - expect(mocks.asset.upsertBulkMetadata).not.toHaveBeenCalled(); - }); - }); - - describe('upsertBulkMetadata', () => { - it('should throw a bad request exception if duplicate keys are sent', async () => { - const asset = factory.asset(); - const items = [ - { assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, - { assetId: asset.id, key: AssetMetadataKey.MobileApp, value: { iCloudId: 'id1' } }, - ]; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - - await expect(sut.upsertBulkMetadata(authStub.admin, { items })).rejects.toThrowError( - 'Duplicate items are not allowed:', - ); - - expect(mocks.asset.upsertBulkMetadata).not.toHaveBeenCalled(); - }); - }); - - describe('editAsset', () => { - it('should enforce crop first', async () => { - await expect( - sut.editAsset(authStub.admin, 'asset-1', { - edits: [ - { - action: AssetEditAction.Rotate, - parameters: { angle: 90 }, - }, - { - action: AssetEditAction.Crop, - parameters: { x: 0, y: 0, width: 100, height: 100 }, - }, - ], - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.assetEdit.replaceAll).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/services/asset.service.ts b/server/src/services/asset.service.ts deleted file mode 100644 index ed427684f1..0000000000 --- a/server/src/services/asset.service.ts +++ /dev/null @@ -1,620 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import _ from 'lodash'; -import { DateTime, Duration } from 'luxon'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { AssetFile } from 'src/database'; -import { OnJob } from 'src/decorators'; -import { AssetResponseDto, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { - AssetBulkDeleteDto, - AssetBulkUpdateDto, - AssetCopyDto, - AssetJobName, - AssetJobsDto, - AssetMetadataBulkDeleteDto, - AssetMetadataBulkResponseDto, - AssetMetadataBulkUpsertDto, - AssetMetadataResponseDto, - AssetMetadataUpsertDto, - AssetStatsDto, - UpdateAssetDto, - mapStats, -} from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetEditAction, AssetEditActionCrop, AssetEditActionListDto, AssetEditsDto } from 'src/dtos/editing.dto'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { - AssetFileType, - AssetStatus, - AssetType, - AssetVisibility, - JobName, - JobStatus, - Permission, - QueueName, -} from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { JobItem, JobOf } from 'src/types'; -import { requireElevatedPermission } from 'src/utils/access'; -import { - getAssetFiles, - getDimensions, - getMyPartnerIds, - isPanorama, - onAfterUnlink, - onBeforeLink, - onBeforeUnlink, -} from 'src/utils/asset.util'; -import { updateLockedColumns } from 'src/utils/database'; -import { extractTimeZone } from 'src/utils/date'; -import { transformOcrBoundingBox } from 'src/utils/transform'; - -@Injectable() -export class AssetService extends BaseService { - async getStatistics(auth: AuthDto, dto: AssetStatsDto) { - if (dto.visibility === AssetVisibility.Locked) { - requireElevatedPermission(auth); - } - - const stats = await this.assetRepository.getStatistics(auth.user.id, dto); - return mapStats(stats); - } - - async getRandom(auth: AuthDto, count: number): Promise { - const partnerIds = await getMyPartnerIds({ - userId: auth.user.id, - repository: this.partnerRepository, - timelineEnabled: true, - }); - const assets = await this.assetRepository.getRandom([auth.user.id, ...partnerIds], count); - return assets.map((a) => mapAsset(a, { auth })); - } - - async getUserAssetsByDeviceId(auth: AuthDto, deviceId: string) { - return this.assetRepository.getAllByDeviceId(auth.user.id, deviceId); - } - - async get(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); - - const asset = await this.assetRepository.getById(id, { - exifInfo: true, - owner: true, - faces: { person: true }, - stack: { assets: true }, - edits: true, - tags: true, - }); - - if (!asset) { - throw new BadRequestException('Asset not found'); - } - - if (auth.sharedLink && !auth.sharedLink.showExif) { - return mapAsset(asset, { stripMetadata: true, withStack: true, auth }); - } - - const data = mapAsset(asset, { withStack: true, auth }); - - if (auth.sharedLink) { - delete data.owner; - } - - if (data.ownerId !== auth.user.id || auth.sharedLink) { - data.people = []; - } - - return data; - } - - async update(auth: AuthDto, id: string, dto: UpdateAssetDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] }); - - const { description, dateTimeOriginal, latitude, longitude, rating, ...rest } = dto; - const repos = { asset: this.assetRepository, event: this.eventRepository }; - - let previousMotion: { id: string } | null = null; - if (rest.livePhotoVideoId) { - await onBeforeLink(repos, { userId: auth.user.id, livePhotoVideoId: rest.livePhotoVideoId }); - } else if (rest.livePhotoVideoId === null) { - const asset = await this.findOrFail(id); - if (asset.livePhotoVideoId) { - previousMotion = await onBeforeUnlink(repos, { livePhotoVideoId: asset.livePhotoVideoId }); - } - } - - await this.updateExif({ id, description, dateTimeOriginal, latitude, longitude, rating }); - - const asset = await this.assetRepository.update({ id, ...rest }); - - if (previousMotion && asset) { - await onAfterUnlink(repos, { - userId: auth.user.id, - livePhotoVideoId: previousMotion.id, - visibility: asset.visibility, - }); - } - - if (!asset) { - throw new BadRequestException('Asset not found'); - } - - return mapAsset(asset, { auth }); - } - - async updateAll(auth: AuthDto, dto: AssetBulkUpdateDto): Promise { - const { - ids, - isFavorite, - visibility, - dateTimeOriginal, - latitude, - longitude, - rating, - description, - duplicateId, - dateTimeRelative, - timeZone, - } = dto; - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids }); - - const assetDto = _.omitBy({ isFavorite, visibility, duplicateId }, _.isUndefined); - const exifDto = _.omitBy( - { - latitude, - longitude, - rating, - description, - dateTimeOriginal, - }, - _.isUndefined, - ); - - if (Object.keys(exifDto).length > 0) { - await this.assetRepository.updateAllExif(ids, exifDto); - } - - const extractedTimeZone = extractTimeZone(dateTimeOriginal); - - if ( - (dateTimeRelative !== undefined && dateTimeRelative !== 0) || - timeZone !== undefined || - extractedTimeZone?.type === 'fixed' - ) { - await this.assetRepository.updateDateTimeOriginal(ids, dateTimeRelative, timeZone ?? extractedTimeZone?.name); - } - - if (Object.keys(assetDto).length > 0) { - await this.assetRepository.updateAll(ids, assetDto); - } - - if (visibility === AssetVisibility.Locked) { - await this.albumRepository.removeAssetsFromAll(ids); - } - - await this.jobRepository.queueAll(ids.map((id) => ({ name: JobName.SidecarWrite, data: { id } }))); - } - - async copy( - auth: AuthDto, - { - sourceId, - targetId, - albums = true, - sidecar = true, - sharedLinks = true, - stack = true, - favorite = true, - }: AssetCopyDto, - ) { - await this.requireAccess({ auth, permission: Permission.AssetCopy, ids: [sourceId, targetId] }); - const sourceAsset = await this.assetRepository.getForCopy(sourceId); - const targetAsset = await this.assetRepository.getForCopy(targetId); - - if (!sourceAsset || !targetAsset) { - throw new BadRequestException('Both assets must exist'); - } - - if (sourceId === targetId) { - throw new BadRequestException('Source and target id must be distinct'); - } - - if (albums) { - await this.albumRepository.copyAlbums({ sourceAssetId: sourceId, targetAssetId: targetId }); - } - - if (sharedLinks) { - await this.sharedLinkAssetRepository.copySharedLinks({ sourceAssetId: sourceId, targetAssetId: targetId }); - } - - if (stack) { - await this.copyStack({ sourceAsset, targetAsset }); - } - - if (favorite) { - await this.assetRepository.update({ id: targetId, isFavorite: sourceAsset.isFavorite }); - } - - if (sidecar) { - await this.copySidecar({ sourceAsset, targetAsset }); - } - } - - private async copyStack({ - sourceAsset, - targetAsset, - }: { - sourceAsset: { id: string; stackId: string | null }; - targetAsset: { id: string; stackId: string | null }; - }) { - if (!sourceAsset.stackId) { - return; - } - - if (targetAsset.stackId) { - await this.stackRepository.merge({ sourceId: sourceAsset.stackId, targetId: targetAsset.stackId }); - await this.stackRepository.delete(sourceAsset.stackId); - } else { - await this.assetRepository.update({ id: targetAsset.id, stackId: sourceAsset.stackId }); - } - } - - private async copySidecar({ - sourceAsset, - targetAsset, - }: { - sourceAsset: { files: AssetFile[] }; - targetAsset: { id: string; files: AssetFile[]; originalPath: string }; - }) { - const { sidecarFile: sourceFile } = getAssetFiles(sourceAsset.files); - if (!sourceFile?.path) { - return; - } - - const { sidecarFile: targetFile } = getAssetFiles(targetAsset.files ?? []); - if (targetFile?.path) { - await this.storageRepository.unlink(targetFile.path); - } - - await this.storageRepository.copyFile(sourceFile.path, `${targetAsset.originalPath}.xmp`); - await this.assetRepository.upsertFile({ - assetId: targetAsset.id, - path: `${targetAsset.originalPath}.xmp`, - type: AssetFileType.Sidecar, - }); - await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: targetAsset.id } }); - } - - @OnJob({ name: JobName.AssetDeleteCheck, queue: QueueName.BackgroundTask }) - async handleAssetDeletionCheck(): Promise { - const config = await this.getConfig({ withCache: false }); - const trashedDays = config.trash.enabled ? config.trash.days : 0; - const trashedBefore = DateTime.now() - .minus(Duration.fromObject({ days: trashedDays })) - .toJSDate(); - - let chunk: Array<{ id: string; isOffline: boolean }> = []; - const queueChunk = async () => { - if (chunk.length > 0) { - await this.jobRepository.queueAll( - chunk.map(({ id, isOffline }) => ({ - name: JobName.AssetDelete, - data: { id, deleteOnDisk: !isOffline }, - })), - ); - chunk = []; - } - }; - - const assets = this.assetJobRepository.streamForDeletedJob(trashedBefore); - for await (const asset of assets) { - chunk.push(asset); - if (chunk.length >= JOBS_ASSET_PAGINATION_SIZE) { - await queueChunk(); - } - } - - await queueChunk(); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetDelete, queue: QueueName.BackgroundTask }) - async handleAssetDeletion(job: JobOf): Promise { - const { id, deleteOnDisk } = job; - - const asset = await this.assetJobRepository.getForAssetDeletion(id); - - if (!asset) { - return JobStatus.Failed; - } - - // replace the parent of the stack children with a new asset - if (asset.stack?.primaryAssetId === id) { - // this only includes timeline visible assets and excludes the primary asset - const stackAssetIds = asset.stack.assets.map((a) => a.id); - if (stackAssetIds.length >= 2) { - const newPrimaryAssetId = stackAssetIds.find((a) => a !== id)!; - await this.stackRepository.update(asset.stack.id, { - id: asset.stack.id, - primaryAssetId: newPrimaryAssetId, - }); - } else { - await this.stackRepository.delete(asset.stack.id); - } - } - - await this.assetRepository.remove(asset); - if (!asset.libraryId) { - await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0)); - } - - await this.eventRepository.emit('AssetDelete', { assetId: id, userId: asset.ownerId }); - - // delete the motion if it is not used by another asset - if (asset.livePhotoVideoId) { - const count = await this.assetRepository.getLivePhotoCount(asset.livePhotoVideoId); - if (count === 0) { - await this.jobRepository.queue({ - name: JobName.AssetDelete, - data: { id: asset.livePhotoVideoId, deleteOnDisk }, - }); - } - } - - const assetFiles = getAssetFiles(asset.files ?? []); - const files = [ - assetFiles.thumbnailFile?.path, - assetFiles.previewFile?.path, - assetFiles.fullsizeFile?.path, - assetFiles.editedFullsizeFile?.path, - assetFiles.editedPreviewFile?.path, - assetFiles.editedThumbnailFile?.path, - asset.encodedVideoPath, - ]; - - if (deleteOnDisk && !asset.isOffline) { - files.push(assetFiles.sidecarFile?.path, asset.originalPath); - } - - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: files.filter(Boolean) } }); - - return JobStatus.Success; - } - - async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise { - const { ids, force } = dto; - - await this.requireAccess({ auth, permission: Permission.AssetDelete, ids }); - await this.assetRepository.updateAll(ids, { - deletedAt: new Date(), - status: force ? AssetStatus.Deleted : AssetStatus.Trashed, - }); - await this.eventRepository.emit(force ? 'AssetDeleteAll' : 'AssetTrashAll', { - assetIds: ids, - userId: auth.user.id, - }); - } - - async getMetadata(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); - return this.assetRepository.getMetadata(id); - } - - async getOcr(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); - const ocr = await this.ocrRepository.getByAssetId(id); - const asset = await this.assetRepository.getById(id, { exifInfo: true, edits: true }); - - if (!asset || !asset.exifInfo || !asset.edits) { - throw new BadRequestException('Asset not found'); - } - - const dimensions = getDimensions(asset.exifInfo); - - return ocr.map((item) => transformOcrBoundingBox(item, asset.edits!, dimensions)); - } - - async upsertBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkUpsertDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) }); - - const uniqueKeys = new Set(); - for (const item of dto.items) { - const key = `(${item.assetId}, ${item.key})`; - if (uniqueKeys.has(key)) { - throw new BadRequestException(`Duplicate items are not allowed: "${key}"`); - } - - uniqueKeys.add(key); - } - - return this.assetRepository.upsertBulkMetadata(dto.items); - } - - async upsertMetadata(auth: AuthDto, id: string, dto: AssetMetadataUpsertDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] }); - - const uniqueKeys = new Set(); - for (const { key } of dto.items) { - if (uniqueKeys.has(key)) { - throw new BadRequestException(`Duplicate items are not allowed: "${key}"`); - } - - uniqueKeys.add(key); - } - - return this.assetRepository.upsertMetadata(id, dto.items); - } - - async getMetadataByKey(auth: AuthDto, id: string, key: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); - - const item = await this.assetRepository.getMetadataByKey(id, key); - if (!item) { - throw new BadRequestException(`Metadata with key "${key}" not found for asset with id "${id}"`); - } - return item; - } - - async deleteMetadataByKey(auth: AuthDto, id: string, key: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: [id] }); - return this.assetRepository.deleteMetadataByKey(id, key); - } - - async deleteBulkMetadata(auth: AuthDto, dto: AssetMetadataBulkDeleteDto) { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.items.map((item) => item.assetId) }); - await this.assetRepository.deleteBulkMetadata(dto.items); - } - - async run(auth: AuthDto, dto: AssetJobsDto) { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.assetIds }); - - const jobs: JobItem[] = []; - - for (const id of dto.assetIds) { - switch (dto.name) { - case AssetJobName.REFRESH_FACES: { - jobs.push({ name: JobName.AssetDetectFaces, data: { id } }); - break; - } - - case AssetJobName.REFRESH_METADATA: { - jobs.push({ name: JobName.AssetExtractMetadata, data: { id } }); - break; - } - - case AssetJobName.REGENERATE_THUMBNAIL: { - jobs.push({ name: JobName.AssetGenerateThumbnails, data: { id } }); - break; - } - - case AssetJobName.TRANSCODE_VIDEO: { - jobs.push({ name: JobName.AssetEncodeVideo, data: { id } }); - break; - } - } - } - - await this.jobRepository.queueAll(jobs); - } - - private async findOrFail(id: string) { - const asset = await this.assetRepository.getById(id); - if (!asset) { - throw new BadRequestException('Asset not found'); - } - return asset; - } - - private async updateExif(dto: { - id: string; - description?: string; - dateTimeOriginal?: string; - latitude?: number; - longitude?: number; - rating?: number; - }) { - const { id, description, dateTimeOriginal, latitude, longitude, rating } = dto; - const writes = _.omitBy( - { - description, - dateTimeOriginal, - timeZone: extractTimeZone(dateTimeOriginal)?.name, - latitude, - longitude, - rating, - }, - _.isUndefined, - ); - - if (Object.keys(writes).length > 0) { - await this.assetRepository.upsertExif( - updateLockedColumns({ - assetId: id, - ...writes, - }), - { lockedPropertiesBehavior: 'append' }, - ); - await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id } }); - } - } - - async getAssetEdits(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [id] }); - const edits = await this.assetEditRepository.getAll(id); - return { - assetId: id, - edits, - }; - } - - async editAsset(auth: AuthDto, id: string, dto: AssetEditActionListDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetEditCreate, ids: [id] }); - - const asset = await this.assetRepository.getById(id, { exifInfo: true }); - if (!asset) { - throw new BadRequestException('Asset not found'); - } - - if (asset.type !== AssetType.Image) { - throw new BadRequestException('Only images can be edited'); - } - - if (asset.livePhotoVideoId) { - throw new BadRequestException('Editing live photos is not supported'); - } - - if (isPanorama(asset)) { - throw new BadRequestException('Editing panorama images is not supported'); - } - - if (asset.originalPath?.toLowerCase().endsWith('.gif')) { - throw new BadRequestException('Editing GIF images is not supported'); - } - - if (asset.originalPath?.toLowerCase().endsWith('.svg')) { - throw new BadRequestException('Editing SVG images is not supported'); - } - - const cropIndex = dto.edits.findIndex((e) => e.action === AssetEditAction.Crop); - if (cropIndex > 0) { - throw new BadRequestException('Crop action must be the first edit action'); - } - - const crop = cropIndex === -1 ? null : (dto.edits[cropIndex] as AssetEditActionCrop); - if (crop) { - // check that crop parameters will not go out of bounds - const { width: assetWidth, height: assetHeight } = getDimensions(asset.exifInfo!); - - if (!assetWidth || !assetHeight) { - throw new BadRequestException('Asset dimensions are not available for editing'); - } - - const { x, y, width, height } = crop.parameters; - if (x + width > assetWidth || y + height > assetHeight) { - throw new BadRequestException('Crop parameters are out of bounds'); - } - } - - const newEdits = await this.assetEditRepository.replaceAll(id, dto.edits); - await this.jobRepository.queue({ name: JobName.AssetEditThumbnailGeneration, data: { id } }); - - // Return the asset and its applied edits - return { - assetId: id, - edits: newEdits, - }; - } - - async removeAssetEdits(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AssetEditDelete, ids: [id] }); - - const asset = await this.assetRepository.getById(id); - if (!asset) { - throw new BadRequestException('Asset not found'); - } - - await this.assetEditRepository.replaceAll(id, []); - await this.jobRepository.queue({ name: JobName.AssetEditThumbnailGeneration, data: { id } }); - } -} diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts deleted file mode 100644 index 7363ea74e1..0000000000 --- a/server/src/services/audit.service.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { JobStatus } from 'src/enum'; -import { AuditService } from 'src/services/audit.service'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(AuditService.name, () => { - let sut: AuditService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(AuditService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('handleCleanup', () => { - it('should delete old audit entries', async () => { - mocks.audit.removeBefore.mockResolvedValue(); - - await expect(sut.handleCleanup()).resolves.toBe(JobStatus.Success); - - expect(mocks.audit.removeBefore).toHaveBeenCalledWith(expect.any(Date)); - }); - }); -}); diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts deleted file mode 100644 index 498d99b82c..0000000000 --- a/server/src/services/audit.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { DateTime } from 'luxon'; -import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; -import { OnJob } from 'src/decorators'; -import { JobName, JobStatus, QueueName } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class AuditService extends BaseService { - @OnJob({ name: JobName.AuditLogCleanup, queue: QueueName.BackgroundTask }) - async handleCleanup(): Promise { - await this.auditRepository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate()); - return JobStatus.Success; - } -} diff --git a/server/src/services/auth-admin.service.ts b/server/src/services/auth-admin.service.ts deleted file mode 100644 index 3648a19957..0000000000 --- a/server/src/services/auth-admin.service.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class AuthAdminService extends BaseService { - async unlinkAll(_auth: AuthDto) { - // TODO replace '' with null - await this.userRepository.updateAll({ oauthId: '' }); - } -} diff --git a/server/src/services/auth.service.spec.ts b/server/src/services/auth.service.spec.ts deleted file mode 100644 index a34efedfb0..0000000000 --- a/server/src/services/auth.service.spec.ts +++ /dev/null @@ -1,1127 +0,0 @@ -import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common'; -import { DateTime } from 'luxon'; -import { SALT_ROUNDS } from 'src/constants'; -import { UserAdmin } from 'src/database'; -import { AuthDto, SignUpDto } from 'src/dtos/auth.dto'; -import { AuthType, Permission } from 'src/enum'; -import { AuthService } from 'src/services/auth.service'; -import { UserMetadataItem } from 'src/types'; -import { sharedLinkStub } from 'test/fixtures/shared-link.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { factory, newUuid } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const oauthResponse = ({ - id, - email, - name, - profileImagePath, -}: { - id: string; - email: string; - name: string; - profileImagePath?: string; -}) => ({ - accessToken: 'cmFuZG9tLWJ5dGVz', - userId: id, - userEmail: email, - name, - profileImagePath, - isAdmin: false, - isOnboarded: false, - shouldChangePassword: false, -}); - -// const token = Buffer.from('my-api-key', 'utf8').toString('base64'); - -const email = 'test@immich.com'; -const sub = 'my-auth-user-sub'; -const loginDetails = { - isSecure: true, - clientIp: '127.0.0.1', - deviceOS: '', - deviceType: '', - appVersion: null, -}; - -const fixtures = { - login: { - email, - password: 'password', - }, -}; - -describe(AuthService.name, () => { - let sut: AuthService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(AuthService)); - - mocks.oauth.authorize.mockResolvedValue({ url: 'http://test', state: 'state', codeVerifier: 'codeVerifier' }); - mocks.oauth.getProfile.mockResolvedValue({ sub, email }); - mocks.oauth.getLogoutEndpoint.mockResolvedValue('http://end-session-endpoint'); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('login', () => { - it('should throw an error if password login is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.disabled); - - await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should check the user exists', async () => { - mocks.user.getByEmail.mockResolvedValue(void 0); - - await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); - - expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); - }); - - it('should check the user has a password', async () => { - mocks.user.getByEmail.mockResolvedValue({} as UserAdmin); - - await expect(sut.login(fixtures.login, loginDetails)).rejects.toBeInstanceOf(UnauthorizedException); - - expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); - }); - - it('should successfully log the user in', async () => { - const user = { ...(factory.user() as UserAdmin), password: 'immich_password' }; - const session = factory.session(); - mocks.user.getByEmail.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(session); - - await expect(sut.login(fixtures.login, loginDetails)).resolves.toEqual({ - accessToken: 'cmFuZG9tLWJ5dGVz', - userId: user.id, - userEmail: user.email, - name: user.name, - profileImagePath: user.profileImagePath, - isAdmin: user.isAdmin, - isOnboarded: false, - shouldChangePassword: user.shouldChangePassword, - }); - - expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); - }); - }); - - describe('changePassword', () => { - it('should change the password', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - const dto = { password: 'old-password', newPassword: 'new-password' }; - - mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: 'hash-password' }); - mocks.user.update.mockResolvedValue(user); - - await sut.changePassword(auth, dto); - - expect(mocks.user.getForChangePassword).toHaveBeenCalledWith(user.id); - expect(mocks.crypto.compareBcrypt).toHaveBeenCalledWith('old-password', 'hash-password'); - expect(mocks.event.emit).toHaveBeenCalledWith('AuthChangePassword', { - userId: user.id, - currentSessionId: auth.session?.id, - shouldLogoutSessions: undefined, - }); - }); - - it('should throw when password does not match existing password', async () => { - const user = factory.user(); - const auth = factory.auth({ user }); - const dto = { password: 'old-password', newPassword: 'new-password' }; - - mocks.crypto.compareBcrypt.mockReturnValue(false); - - mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: 'hash-password' }); - - await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should throw when user does not have a password', async () => { - const user = factory.user(); - const auth = factory.auth({ user }); - const dto = { password: 'old-password', newPassword: 'new-password' }; - - mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: '' }); - - await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should change the password and logout other sessions', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - const dto = { password: 'old-password', newPassword: 'new-password', invalidateSessions: true }; - - mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: 'hash-password' }); - mocks.user.update.mockResolvedValue(user); - - await sut.changePassword(auth, dto); - - expect(mocks.user.getForChangePassword).toHaveBeenCalledWith(user.id); - expect(mocks.crypto.compareBcrypt).toHaveBeenCalledWith('old-password', 'hash-password'); - expect(mocks.event.emit).toHaveBeenCalledWith('AuthChangePassword', { - userId: user.id, - invalidateSessions: true, - currentSessionId: auth.session?.id, - }); - }); - }); - - describe('logout', () => { - it('should return the end session endpoint', async () => { - const auth = factory.auth(); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - - await expect(sut.logout(auth, AuthType.OAuth)).resolves.toEqual({ - successful: true, - redirectUri: 'http://end-session-endpoint', - }); - }); - - it('should return the default redirect', async () => { - const auth = factory.auth(); - - await expect(sut.logout(auth, AuthType.Password)).resolves.toEqual({ - successful: true, - redirectUri: '/auth/login?autoLaunch=0', - }); - }); - - it('should delete the access token', async () => { - const auth = { user: { id: '123' }, session: { id: 'token123' } } as AuthDto; - mocks.session.delete.mockResolvedValue(); - - await expect(sut.logout(auth, AuthType.Password)).resolves.toEqual({ - successful: true, - redirectUri: '/auth/login?autoLaunch=0', - }); - - expect(mocks.session.delete).toHaveBeenCalledWith('token123'); - expect(mocks.event.emit).toHaveBeenCalledWith('SessionDelete', { sessionId: 'token123' }); - }); - - it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => { - const auth = { user: { id: '123' } } as AuthDto; - - await expect(sut.logout(auth, AuthType.OAuth)).resolves.toEqual({ - successful: true, - redirectUri: '/auth/login?autoLaunch=0', - }); - }); - }); - - describe('adminSignUp', () => { - const dto: SignUpDto = { email: 'test@immich.com', password: 'password', name: 'immich admin' }; - - it('should only allow one admin', async () => { - mocks.user.getAdmin.mockResolvedValue({} as UserAdmin); - - await expect(sut.adminSignUp(dto)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.getAdmin).toHaveBeenCalled(); - }); - - it('should sign up the admin', async () => { - mocks.user.getAdmin.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue({ - ...dto, - id: 'admin', - createdAt: new Date('2021-01-01'), - metadata: [] as UserMetadataItem[], - } as unknown as UserAdmin); - - await expect(sut.adminSignUp(dto)).resolves.toMatchObject({ - avatarColor: expect.any(String), - id: 'admin', - createdAt: new Date('2021-01-01'), - email: 'test@immich.com', - name: 'immich admin', - }); - - expect(mocks.user.getAdmin).toHaveBeenCalled(); - expect(mocks.user.create).toHaveBeenCalled(); - }); - }); - - describe('validate - socket connections', () => { - it('should throw when token is not provided', async () => { - await expect( - sut.authenticate({ - headers: {}, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should validate using authorization header', async () => { - const session = factory.session(); - const sessionWithToken = { - id: session.id, - updatedAt: session.updatedAt, - user: factory.authUser(), - pinExpiresAt: null, - appVersion: null, - }; - - mocks.session.getByToken.mockResolvedValue(sessionWithToken); - - await expect( - sut.authenticate({ - headers: { authorization: 'Bearer auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).resolves.toEqual({ - user: sessionWithToken.user, - session: { - id: session.id, - hasElevatedPermission: false, - }, - }); - }); - }); - - describe('validate - shared key', () => { - it('should not accept a non-existent key', async () => { - mocks.sharedLink.getByKey.mockResolvedValue(void 0); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-key': 'key' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should not accept an expired key', async () => { - mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired as any); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-key': 'key' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should not accept a key on a non-shared route', async () => { - mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid as any); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-key': 'key' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(ForbiddenException); - }); - - it('should not accept a key without a user', async () => { - mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.expired as any); - mocks.user.get.mockResolvedValue(void 0); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-key': 'key' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should accept a base64url key', async () => { - const user = factory.userAdmin(); - const sharedLink = { ...sharedLinkStub.valid, user } as any; - - mocks.sharedLink.getByKey.mockResolvedValue(sharedLink); - mocks.user.get.mockResolvedValue(user); - - const buffer = sharedLink.key; - const key = buffer.toString('base64url'); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-key': key }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).resolves.toEqual({ user, sharedLink }); - - expect(mocks.sharedLink.getByKey).toHaveBeenCalledWith(buffer); - }); - - it('should accept a hex key', async () => { - const user = factory.userAdmin(); - const sharedLink = { ...sharedLinkStub.valid, user } as any; - - mocks.sharedLink.getByKey.mockResolvedValue(sharedLink); - mocks.user.get.mockResolvedValue(user); - - const buffer = sharedLink.key; - const key = buffer.toString('hex'); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-key': key }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).resolves.toEqual({ user, sharedLink }); - - expect(mocks.sharedLink.getByKey).toHaveBeenCalledWith(buffer); - }); - }); - - describe('validate - shared link slug', () => { - it('should not accept a non-existent slug', async () => { - mocks.sharedLink.getBySlug.mockResolvedValue(void 0); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-slug': 'slug' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should accept a valid slug', async () => { - const user = factory.userAdmin(); - const sharedLink = { ...sharedLinkStub.valid, slug: 'slug-123', user } as any; - - mocks.sharedLink.getBySlug.mockResolvedValue(sharedLink); - mocks.user.get.mockResolvedValue(user); - - await expect( - sut.authenticate({ - headers: { 'x-immich-share-slug': 'slug-123' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' }, - }), - ).resolves.toEqual({ user, sharedLink }); - - expect(mocks.sharedLink.getBySlug).toHaveBeenCalledWith('slug-123'); - }); - }); - - describe('validate - user token', () => { - it('should throw if no token is found', async () => { - mocks.session.getByToken.mockResolvedValue(void 0); - - await expect( - sut.authenticate({ - headers: { 'x-immich-user-token': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - }); - - it('should return an auth dto', async () => { - const session = factory.session(); - const sessionWithToken = { - id: session.id, - updatedAt: session.updatedAt, - user: factory.authUser(), - pinExpiresAt: null, - appVersion: null, - }; - - mocks.session.getByToken.mockResolvedValue(sessionWithToken); - - await expect( - sut.authenticate({ - headers: { cookie: 'immich_access_token=auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).resolves.toEqual({ - user: sessionWithToken.user, - session: { - id: session.id, - hasElevatedPermission: false, - }, - }); - }); - - it('should throw if admin route and not an admin', async () => { - const session = factory.session(); - const sessionWithToken = { - id: session.id, - updatedAt: session.updatedAt, - user: factory.authUser(), - isPendingSyncReset: false, - pinExpiresAt: null, - appVersion: null, - }; - - mocks.session.getByToken.mockResolvedValue(sessionWithToken); - - await expect( - sut.authenticate({ - headers: { cookie: 'immich_access_token=auth_token' }, - queryParams: {}, - metadata: { adminRoute: true, sharedLinkRoute: false, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(ForbiddenException); - }); - - it('should update when access time exceeds an hour', async () => { - const session = factory.session({ updatedAt: DateTime.now().minus({ hours: 2 }).toJSDate() }); - const sessionWithToken = { - id: session.id, - updatedAt: session.updatedAt, - user: factory.authUser(), - isPendingSyncReset: false, - pinExpiresAt: null, - appVersion: null, - }; - - mocks.session.getByToken.mockResolvedValue(sessionWithToken); - mocks.session.update.mockResolvedValue(session); - - await expect( - sut.authenticate({ - headers: { cookie: 'immich_access_token=auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).resolves.toBeDefined(); - - expect(mocks.session.update).toHaveBeenCalled(); - }); - }); - - describe('validate - api key', () => { - it('should throw an error if no api key is found', async () => { - mocks.apiKey.getKey.mockResolvedValue(void 0); - - await expect( - sut.authenticate({ - headers: { 'x-api-key': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).rejects.toBeInstanceOf(UnauthorizedException); - expect(mocks.apiKey.getKey).toHaveBeenCalledWith('auth_token (hashed)'); - }); - - it('should throw an error if api key has insufficient permissions', async () => { - const authUser = factory.authUser(); - const authApiKey = factory.authApiKey({ permissions: [] }); - - mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); - - const result = sut.authenticate({ - headers: { 'x-api-key': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.AssetRead }, - }); - - await expect(result).rejects.toBeInstanceOf(ForbiddenException); - await expect(result).rejects.toThrow('Missing required permission: asset.read'); - }); - - it('should default to requiring the all permission when omitted', async () => { - const authUser = factory.authUser(); - const authApiKey = factory.authApiKey({ permissions: [Permission.AssetRead] }); - - mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); - - const result = sut.authenticate({ - headers: { 'x-api-key': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }); - await expect(result).rejects.toBeInstanceOf(ForbiddenException); - await expect(result).rejects.toThrow('Missing required permission: all'); - }); - - it('should not require any permission when metadata is set to `false`', async () => { - const authUser = factory.authUser(); - const authApiKey = factory.authApiKey({ permissions: [Permission.ActivityRead] }); - - mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); - - const result = sut.authenticate({ - headers: { 'x-api-key': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: false }, - }); - await expect(result).resolves.toEqual({ user: authUser, apiKey: expect.objectContaining(authApiKey) }); - }); - - it('should return an auth dto', async () => { - const authUser = factory.authUser(); - const authApiKey = factory.authApiKey({ permissions: [Permission.All] }); - - mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser }); - - await expect( - sut.authenticate({ - headers: { 'x-api-key': 'auth_token' }, - queryParams: {}, - metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' }, - }), - ).resolves.toEqual({ user: authUser, apiKey: expect.objectContaining(authApiKey) }); - expect(mocks.apiKey.getKey).toHaveBeenCalledWith('auth_token (hashed)'); - }); - }); - - describe('getMobileRedirect', () => { - it('should pass along the query params', () => { - expect(sut.getMobileRedirect('http://immich.app?code=123&state=456')).toEqual( - 'app.immich:///oauth-callback?code=123&state=456', - ); - }); - - it('should work if called without query params', () => { - expect(sut.getMobileRedirect('http://immich.app')).toEqual('app.immich:///oauth-callback?'); - }); - }); - - describe('authorize', () => { - it('should fail if oauth is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ oauth: { enabled: false } }); - - await expect(sut.authorize({ redirectUri: 'https://demo.immich.app' })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - - it('should authorize the user', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride); - - await sut.authorize({ redirectUri: 'https://demo.immich.app' }); - }); - }); - - describe('callback', () => { - it('should throw an error if OAuth is not enabled', async () => { - await expect( - sut.callback({ url: '', state: 'xyz789', codeVerifier: 'foo' }, {}, loginDetails), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should not allow auto registering', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled); - mocks.user.getByEmail.mockResolvedValue(void 0); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); - }); - - it('should link an existing user', async () => { - const user = factory.userAdmin(); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled); - mocks.user.getByEmail.mockResolvedValue(user); - mocks.user.update.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foobar' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1); - expect(mocks.user.update).toHaveBeenCalledWith(user.id, { oauthId: sub }); - }); - - it('should not link to a user with a different oauth sub', async () => { - const user = factory.userAdmin({ isAdmin: true, oauthId: 'existing-sub' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithAutoRegister); - mocks.user.getByEmail.mockResolvedValueOnce(user); - mocks.user.getAdmin.mockResolvedValue(user); - mocks.user.create.mockResolvedValue(user); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foobar' }, - {}, - loginDetails, - ), - ).rejects.toThrow(BadRequestException); - - expect(mocks.user.update).not.toHaveBeenCalled(); - expect(mocks.user.create).not.toHaveBeenCalled(); - }); - - it('should allow auto registering by default', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foobar' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.getByEmail).toHaveBeenCalledTimes(2); // second call is for domain check before create - expect(mocks.user.create).toHaveBeenCalledTimes(1); - }); - - it('should throw an error if user should be auto registered but the email claim does not exist', async () => { - const user = factory.userAdmin({ isAdmin: true }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getAdmin.mockResolvedValue(user); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - mocks.oauth.getProfile.mockResolvedValue({ sub, email: undefined }); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foobar' }, - {}, - loginDetails, - ), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.getByEmail).not.toHaveBeenCalled(); - expect(mocks.user.create).not.toHaveBeenCalled(); - }); - - for (const url of [ - 'app.immich:/oauth-callback?code=abc123', - 'app.immich://oauth-callback?code=abc123', - 'app.immich:///oauth-callback?code=abc123', - ]) { - it(`should use the mobile redirect override for a url of ${url}`, async () => { - const user = factory.userAdmin(); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride); - mocks.user.getByOAuthId.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await sut.callback({ url, state: 'xyz789', codeVerifier: 'foo' }, {}, loginDetails); - - expect(mocks.oauth.getProfile).toHaveBeenCalledWith( - expect.objectContaining({}), - 'http://mobile-redirect?code=abc123', - 'xyz789', - 'foo', - ); - }); - } - - it('should use the default quota', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith(expect.objectContaining({ quotaSizeInBytes: 1_073_741_824 })); - }); - - it('should ignore an invalid storage quota', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: 'abc' }); - mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith(expect.objectContaining({ quotaSizeInBytes: 1_073_741_824 })); - }); - - it('should ignore a negative quota', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: -5 }); - mocks.user.getAdmin.mockResolvedValue(user); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith(expect.objectContaining({ quotaSizeInBytes: 1_073_741_824 })); - }); - - it('should set quota for 0 quota', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: 0 }); - mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith({ - email: user.email, - isAdmin: false, - name: ' ', - oauthId: user.oauthId, - quotaSizeInBytes: 0, - storageLabel: null, - }); - }); - - it('should use a valid storage quota', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: 5 }); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - mocks.user.getByOAuthId.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith({ - email: user.email, - isAdmin: false, - name: ' ', - oauthId: user.oauthId, - quotaSizeInBytes: 5_368_709_120, - storageLabel: null, - }); - }); - - it('should sync the profile picture', async () => { - const fileId = newUuid(); - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - const pictureUrl = 'https://auth.immich.cloud/profiles/1.jpg'; - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled); - mocks.oauth.getProfile.mockResolvedValue({ - sub: user.oauthId, - email: user.email, - picture: pictureUrl, - }); - mocks.user.getByOAuthId.mockResolvedValue(user); - mocks.crypto.randomUUID.mockReturnValue(fileId); - mocks.oauth.getProfilePicture.mockResolvedValue({ - contentType: 'image/jpeg', - data: new Uint8Array([1, 2, 3, 4, 5]).buffer, - }); - mocks.user.update.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.update).toHaveBeenCalledWith(user.id, { - profileImagePath: expect.stringContaining(`/data/profile/${user.id}/${fileId}.jpg`), - profileChangedAt: expect.any(Date), - }); - expect(mocks.oauth.getProfilePicture).toHaveBeenCalledWith(pictureUrl); - }); - - it('should not sync the profile picture if the user already has one', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id', profileImagePath: 'not-empty' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled); - mocks.oauth.getProfile.mockResolvedValue({ - sub: user.oauthId, - email: user.email, - picture: 'https://auth.immich.cloud/profiles/1.jpg', - }); - mocks.user.getByOAuthId.mockResolvedValue(user); - mocks.user.update.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.update).not.toHaveBeenCalled(); - expect(mocks.oauth.getProfilePicture).not.toHaveBeenCalled(); - }); - - it('should only allow "admin" and "user" for the role claim', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithAutoRegister); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_role: 'foo' }); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - mocks.user.getByOAuthId.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith({ - email: user.email, - name: ' ', - oauthId: user.oauthId, - quotaSizeInBytes: null, - storageLabel: null, - isAdmin: false, - }); - }); - - it('should create an admin user if the role claim is set to admin', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithAutoRegister); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_role: 'admin' }); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getByOAuthId.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith({ - email: user.email, - name: ' ', - oauthId: user.oauthId, - quotaSizeInBytes: null, - storageLabel: null, - isAdmin: true, - }); - }); - - it('should accept a custom role claim', async () => { - const user = factory.userAdmin({ oauthId: 'oauth-id' }); - - mocks.systemMetadata.get.mockResolvedValue({ - oauth: { ...systemConfigStub.oauthWithAutoRegister, roleClaim: 'my_role' }, - }); - mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, my_role: 'admin' }); - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getByOAuthId.mockResolvedValue(void 0); - mocks.user.create.mockResolvedValue(user); - mocks.session.create.mockResolvedValue(factory.session()); - - await expect( - sut.callback( - { url: 'http://immich/auth/login?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - loginDetails, - ), - ).resolves.toEqual(oauthResponse(user)); - - expect(mocks.user.create).toHaveBeenCalledWith({ - email: user.email, - name: ' ', - oauthId: user.oauthId, - quotaSizeInBytes: null, - storageLabel: null, - isAdmin: true, - }); - }); - }); - - describe('link', () => { - it('should link an account', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ apiKey: { permissions: [] }, user }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - mocks.user.update.mockResolvedValue(user); - - await sut.link( - auth, - { url: 'http://immich/user-settings?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, - {}, - ); - - expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: sub }); - }); - - it('should not link an already linked oauth.sub', async () => { - const authUser = factory.authUser(); - const authApiKey = factory.authApiKey({ permissions: [] }); - const auth = { user: authUser, apiKey: authApiKey }; - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - mocks.user.getByOAuthId.mockResolvedValue({ id: 'other-user' } as UserAdmin); - - await expect( - sut.link(auth, { url: 'http://immich/user-settings?code=abc123', state: 'xyz789', codeVerifier: 'foo' }, {}), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - }); - - describe('unlink', () => { - it('should unlink an account', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user, apiKey: { permissions: [] } }); - - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled); - mocks.user.update.mockResolvedValue(user); - - await sut.unlink(auth); - - expect(mocks.user.update).toHaveBeenCalledWith(auth.user.id, { oauthId: '' }); - }); - }); - - describe('setupPinCode', () => { - it('should setup a PIN code', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - const dto = { pinCode: '123456' }; - - mocks.user.getForPinCode.mockResolvedValue({ pinCode: null, password: '' }); - mocks.user.update.mockResolvedValue(user); - - await sut.setupPinCode(auth, dto); - - expect(mocks.user.getForPinCode).toHaveBeenCalledWith(user.id); - expect(mocks.crypto.hashBcrypt).toHaveBeenCalledWith('123456', SALT_ROUNDS); - expect(mocks.user.update).toHaveBeenCalledWith(user.id, { pinCode: expect.any(String) }); - }); - - it('should fail if the user already has a PIN code', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - - mocks.user.getForPinCode.mockResolvedValue({ pinCode: '123456 (hashed)', password: '' }); - - await expect(sut.setupPinCode(auth, { pinCode: '123456' })).rejects.toThrow('User already has a PIN code'); - }); - }); - - describe('changePinCode', () => { - it('should change the PIN code', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - const dto = { pinCode: '123456', newPinCode: '012345' }; - - mocks.user.getForPinCode.mockResolvedValue({ pinCode: '123456 (hashed)', password: '' }); - mocks.user.update.mockResolvedValue(user); - mocks.crypto.compareBcrypt.mockImplementation((a, b) => `${a} (hashed)` === b); - - await sut.changePinCode(auth, dto); - - expect(mocks.crypto.compareBcrypt).toHaveBeenCalledWith('123456', '123456 (hashed)'); - expect(mocks.user.update).toHaveBeenCalledWith(user.id, { pinCode: '012345 (hashed)' }); - }); - - it('should fail if the PIN code does not match', async () => { - const user = factory.userAdmin(); - mocks.user.getForPinCode.mockResolvedValue({ pinCode: '123456 (hashed)', password: '' }); - mocks.crypto.compareBcrypt.mockImplementation((a, b) => `${a} (hashed)` === b); - - await expect( - sut.changePinCode(factory.auth({ user }), { pinCode: '000000', newPinCode: '012345' }), - ).rejects.toThrow('Wrong PIN code'); - }); - }); - - describe('resetPinCode', () => { - it('should reset the PIN code', async () => { - const currentSession = factory.session(); - const user = factory.userAdmin(); - mocks.user.getForPinCode.mockResolvedValue({ pinCode: '123456 (hashed)', password: '' }); - mocks.crypto.compareBcrypt.mockImplementation((a, b) => `${a} (hashed)` === b); - mocks.session.lockAll.mockResolvedValue(void 0); - mocks.session.update.mockResolvedValue(currentSession); - - await sut.resetPinCode(factory.auth({ user }), { pinCode: '123456' }); - - expect(mocks.user.update).toHaveBeenCalledWith(user.id, { pinCode: null }); - expect(mocks.session.lockAll).toHaveBeenCalledWith(user.id); - }); - - it('should throw if the PIN code does not match', async () => { - const user = factory.userAdmin(); - mocks.user.getForPinCode.mockResolvedValue({ pinCode: '123456 (hashed)', password: '' }); - mocks.crypto.compareBcrypt.mockImplementation((a, b) => `${a} (hashed)` === b); - - await expect(sut.resetPinCode(factory.auth({ user }), { pinCode: '000000' })).rejects.toThrow('Wrong PIN code'); - }); - }); -}); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index a6580f89dd..66d583ebfe 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -48,7 +48,6 @@ export type ValidateRequest = { headers: IncomingHttpHeaders; queryParams: Record; metadata: { - sharedLinkRoute: boolean; adminRoute: boolean; /** `false` explicitly means no permission is required, which otherwise defaults to `all` */ permission?: Permission | false; @@ -188,7 +187,7 @@ export class AuthService extends BaseService { async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise { const authDto = await this.validate({ headers, queryParams }); - const { adminRoute, sharedLinkRoute, uri } = metadata; + const { adminRoute, uri } = metadata; const requestedPermission = metadata.permission ?? Permission.All; if (!authDto.user.isAdmin && adminRoute) { @@ -196,11 +195,6 @@ export class AuthService extends BaseService { throw new ForbiddenException('Forbidden'); } - if (authDto.sharedLink && !sharedLinkRoute) { - this.logger.warn(`Denied access to non-shared route: ${uri}`); - throw new ForbiddenException('Forbidden'); - } - if ( authDto.apiKey && requestedPermission !== false && diff --git a/server/src/services/backup.service.spec.ts b/server/src/services/backup.service.spec.ts deleted file mode 100644 index ea80dd5759..0000000000 --- a/server/src/services/backup.service.spec.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { DateTime } from 'luxon'; -import { PassThrough } from 'node:stream'; -import { defaults, SystemConfig } from 'src/config'; -import { StorageCore } from 'src/cores/storage.core'; -import { ImmichWorker, JobStatus, StorageFolder } from 'src/enum'; -import { BackupService } from 'src/services/backup.service'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { mockDuplex, mockSpawn, newTestService, ServiceMocks } from 'test/utils'; -import { describe } from 'vitest'; - -describe(BackupService.name, () => { - let sut: BackupService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(BackupService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onBootstrapEvent', () => { - it('should init cron job and handle config changes', async () => { - mocks.database.tryLock.mockResolvedValue(true); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); - - expect(mocks.cron.create).toHaveBeenCalled(); - }); - - it('should not initialize backup database cron job when lock is taken', async () => { - mocks.database.tryLock.mockResolvedValue(false); - - await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); - - expect(mocks.cron.create).not.toHaveBeenCalled(); - }); - - it('should not initialise backup database job when running on microservices', async () => { - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - await sut.onConfigInit({ newConfig: systemConfigStub.backupEnabled as SystemConfig }); - - expect(mocks.cron.create).not.toHaveBeenCalled(); - }); - }); - - describe('onConfigUpdateEvent', () => { - beforeEach(async () => { - mocks.database.tryLock.mockResolvedValue(true); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: defaults }); - }); - - it('should update cron job if backup is enabled', () => { - mocks.cron.update.mockResolvedValue(); - - sut.onConfigUpdate({ - oldConfig: defaults, - newConfig: { - backup: { - database: { - enabled: true, - cronExpression: '0 1 * * *', - }, - }, - } as SystemConfig, - }); - - expect(mocks.cron.update).toHaveBeenCalledWith({ name: 'backupDatabase', expression: '0 1 * * *', start: true }); - expect(mocks.cron.update).toHaveBeenCalled(); - }); - - it('should do nothing if instance does not have the backup database lock', async () => { - mocks.database.tryLock.mockResolvedValue(false); - await sut.onConfigInit({ newConfig: defaults }); - sut.onConfigUpdate({ newConfig: systemConfigStub.backupEnabled as SystemConfig, oldConfig: defaults }); - expect(mocks.cron.update).not.toHaveBeenCalled(); - }); - }); - - describe('cleanupDatabaseBackups', () => { - it('should do nothing if not reached keepLastAmount', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled); - mocks.storage.readdir.mockResolvedValue(['immich-db-backup-1.sql.gz']); - await sut.cleanupDatabaseBackups(); - expect(mocks.storage.unlink).not.toHaveBeenCalled(); - }); - - it('should remove failed backup files', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled); - //`immich-db-backup-${DateTime.now().toFormat("yyyyLLdd'T'HHmmss")}-v${serverVersion.toString()}-pg${databaseVersion.split(' ')[0]}.sql.gz.tmp`, - mocks.storage.readdir.mockResolvedValue([ - 'immich-db-backup-123.sql.gz.tmp', - `immich-db-backup-${DateTime.fromISO('2025-07-25T11:02:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz.tmp`, - `immich-db-backup-${DateTime.fromISO('2025-07-27T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - `immich-db-backup-${DateTime.fromISO('2025-07-29T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz.tmp`, - ]); - await sut.cleanupDatabaseBackups(); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(3); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-123.sql.gz.tmp`, - ); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-20250725T110216-v1.234.5-pg14.5.sql.gz.tmp`, - ); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-20250729T110116-v1.234.5-pg14.5.sql.gz.tmp`, - ); - }); - - it('should remove old backup files over keepLastAmount', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled); - mocks.storage.readdir.mockResolvedValue(['immich-db-backup-1.sql.gz', 'immich-db-backup-2.sql.gz']); - await sut.cleanupDatabaseBackups(); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-1.sql.gz`, - ); - }); - - it('should remove old backup files over keepLastAmount and failed backups', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled); - mocks.storage.readdir.mockResolvedValue([ - `immich-db-backup-${DateTime.fromISO('2025-07-25T11:02:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz.tmp`, - `immich-db-backup-${DateTime.fromISO('2025-07-27T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - 'immich-db-backup-1753789649000.sql.gz', - `immich-db-backup-${DateTime.fromISO('2025-07-29T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - ]); - await sut.cleanupDatabaseBackups(); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(3); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-1753789649000.sql.gz`, - ); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-20250725T110216-v1.234.5-pg14.5.sql.gz.tmp`, - ); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/immich-db-backup-20250727T110116-v1.234.5-pg14.5.sql.gz`, - ); - }); - }); - - describe('handleBackupDatabase', () => { - beforeEach(() => { - mocks.storage.readdir.mockResolvedValue([]); - mocks.process.spawn.mockReturnValue(mockSpawn(0, 'data', '')); - mocks.process.spawnDuplexStream.mockImplementation(() => mockDuplex('command', 0, 'data', '')); - mocks.storage.rename.mockResolvedValue(); - mocks.storage.unlink.mockResolvedValue(); - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled); - mocks.storage.createWriteStream.mockReturnValue(new PassThrough()); - }); - - it('should sanitize DB_URL (remove uselibpqcompat) before calling pg_dumpall', async () => { - // create a service instance with a URL connection that includes libpqcompat - const dbUrl = 'postgresql://postgres:pwd@host:5432/immich?sslmode=require&uselibpqcompat=true'; - const configMock = { - getEnv: () => ({ database: { config: { connectionType: 'url', url: dbUrl }, skipMigrations: false } }), - getWorker: () => ImmichWorker.Api, - isDev: () => false, - } as unknown as any; - - ({ sut, mocks } = newTestService(BackupService, { config: configMock })); - - mocks.storage.readdir.mockResolvedValue([]); - mocks.process.spawnDuplexStream.mockImplementation(() => mockDuplex('command', 0, 'data', '')); - mocks.storage.rename.mockResolvedValue(); - mocks.storage.unlink.mockResolvedValue(); - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.backupEnabled); - mocks.storage.createWriteStream.mockReturnValue(new PassThrough()); - mocks.database.getPostgresVersion.mockResolvedValue('14.10'); - - await sut.handleBackupDatabase(); - - expect(mocks.process.spawnDuplexStream).toHaveBeenCalled(); - const call = mocks.process.spawnDuplexStream.mock.calls[0]; - const args = call[1] as string[]; - expect(args).toMatchInlineSnapshot(` - [ - "postgresql://postgres:pwd@host:5432/immich?sslmode=require", - "--clean", - "--if-exists", - ] - `); - }); - - it('should run a database backup successfully', async () => { - const result = await sut.handleBackupDatabase(); - expect(result).toBe(JobStatus.Success); - expect(mocks.storage.createWriteStream).toHaveBeenCalled(); - }); - - it('should rename file on success', async () => { - const result = await sut.handleBackupDatabase(); - expect(result).toBe(JobStatus.Success); - expect(mocks.storage.rename).toHaveBeenCalled(); - }); - - it('should fail if pg_dump fails', async () => { - mocks.process.spawnDuplexStream.mockReturnValueOnce(mockDuplex('pg_dump', 1, '', 'error')); - await expect(sut.handleBackupDatabase()).rejects.toThrow('pg_dump non-zero exit code (1)'); - }); - - it('should not rename file if pgdump fails and gzip succeeds', async () => { - mocks.process.spawnDuplexStream.mockReturnValueOnce(mockDuplex('pg_dump', 1, '', 'error')); - await expect(sut.handleBackupDatabase()).rejects.toThrow('pg_dump non-zero exit code (1)'); - expect(mocks.storage.rename).not.toHaveBeenCalled(); - }); - - it('should fail if gzip fails', async () => { - mocks.process.spawnDuplexStream.mockReturnValueOnce(mockDuplex('pg_dump', 0, 'data', '')); - mocks.process.spawnDuplexStream.mockReturnValueOnce(mockDuplex('gzip', 1, '', 'error')); - await expect(sut.handleBackupDatabase()).rejects.toThrow('gzip non-zero exit code (1)'); - }); - - it('should fail if write stream fails', async () => { - mocks.storage.createWriteStream.mockImplementation(() => { - throw new Error('error'); - }); - await expect(sut.handleBackupDatabase()).rejects.toThrow('error'); - }); - - it('should fail if rename fails', async () => { - mocks.storage.rename.mockRejectedValue(new Error('error')); - await expect(sut.handleBackupDatabase()).rejects.toThrow('error'); - }); - - it('should ignore unlink failing and still return failed job status', async () => { - mocks.process.spawnDuplexStream.mockReturnValueOnce(mockDuplex('pg_dump', 1, '', 'error')); - mocks.storage.unlink.mockRejectedValue(new Error('error')); - await expect(sut.handleBackupDatabase()).rejects.toThrow('pg_dump non-zero exit code (1)'); - expect(mocks.storage.unlink).toHaveBeenCalled(); - }); - - it.each` - postgresVersion | expectedVersion - ${'14.10'} | ${14} - ${'14.10.3'} | ${14} - ${'14.10 (Debian 14.10-1.pgdg120+1)'} | ${14} - ${'15.3.3'} | ${15} - ${'16.4.2'} | ${16} - ${'17.15.1'} | ${17} - ${'18.0.0'} | ${18} - `( - `should use pg_dump $expectedVersion with postgres version $postgresVersion`, - async ({ postgresVersion, expectedVersion }) => { - mocks.database.getPostgresVersion.mockResolvedValue(postgresVersion); - await sut.handleBackupDatabase(); - expect(mocks.process.spawnDuplexStream).toHaveBeenCalledWith( - `/usr/lib/postgresql/${expectedVersion}/bin/pg_dump`, - expect.any(Array), - expect.any(Object), - ); - }, - ); - it.each` - postgresVersion - ${'13.99.99'} - ${'19.0.0'} - `(`should fail if postgres version $postgresVersion is not supported`, async ({ postgresVersion }) => { - mocks.database.getPostgresVersion.mockResolvedValue(postgresVersion); - const result = await sut.handleBackupDatabase(); - expect(mocks.process.spawn).not.toHaveBeenCalled(); - expect(result).toBe(JobStatus.Failed); - }); - }); -}); diff --git a/server/src/services/backup.service.ts b/server/src/services/backup.service.ts deleted file mode 100644 index 637e968929..0000000000 --- a/server/src/services/backup.service.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import path from 'node:path'; -import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent, OnJob } from 'src/decorators'; -import { DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName, StorageFolder } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { - createDatabaseBackup, - isFailedDatabaseBackupName, - isValidDatabaseRoutineBackupName, - UnsupportedPostgresError, -} from 'src/utils/database-backups'; -import { handlePromiseError } from 'src/utils/misc'; - -@Injectable() -export class BackupService extends BaseService { - private backupLock = false; - - @OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] }) - async onConfigInit({ - newConfig: { - backup: { database }, - }, - }: ArgOf<'ConfigInit'>) { - this.backupLock = await this.databaseRepository.tryLock(DatabaseLock.BackupDatabase); - - if (this.backupLock) { - this.cronRepository.create({ - name: 'backupDatabase', - expression: database.cronExpression, - onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.DatabaseBackup }), this.logger), - start: database.enabled, - }); - } - } - - @OnEvent({ name: 'ConfigUpdate', server: true }) - onConfigUpdate({ newConfig: { backup } }: ArgOf<'ConfigUpdate'>) { - if (!this.backupLock) { - return; - } - - this.cronRepository.update({ - name: 'backupDatabase', - expression: backup.database.cronExpression, - start: backup.database.enabled, - }); - } - - async cleanupDatabaseBackups() { - this.logger.debug(`Database Backup Cleanup Started`); - const { - backup: { database: config }, - } = await this.getConfig({ withCache: false }); - - const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); - const files = await this.storageRepository.readdir(backupsFolder); - const backups = files - .filter((filename) => isValidDatabaseRoutineBackupName(filename)) - .toSorted() - .toReversed(); - const failedBackups = files.filter((filename) => isFailedDatabaseBackupName(filename)); - - const toDelete = backups.slice(config.keepLastAmount); - toDelete.push(...failedBackups); - - for (const file of toDelete) { - await this.storageRepository.unlink(path.join(backupsFolder, file)); - } - this.logger.debug(`Database Backup Cleanup Finished, deleted ${toDelete.length} backups`); - } - - @OnJob({ name: JobName.DatabaseBackup, queue: QueueName.BackupDatabase }) - async handleBackupDatabase(): Promise { - try { - await createDatabaseBackup(this.backupRepos); - } catch (error) { - if (error instanceof UnsupportedPostgresError) { - return JobStatus.Failed; - } - - throw error; - } - - await this.cleanupDatabaseBackups(); - return JobStatus.Success; - } - - private get backupRepos() { - return { - logger: this.logger, - storage: this.storageRepository, - config: this.configRepository, - process: this.processRepository, - database: this.databaseRepository, - }; - } -} diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index b3a50a07ae..0c5c266995 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -1,190 +1,61 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { Insertable } from 'kysely'; -import sanitize from 'sanitize-filename'; import { SystemConfig } from 'src/config'; import { SALT_ROUNDS } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; import { UserAdmin } from 'src/database'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { ActivityRepository } from 'src/repositories/activity.repository'; -import { AlbumUserRepository } from 'src/repositories/album-user.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; import { ApiKeyRepository } from 'src/repositories/api-key.repository'; import { AppRepository } from 'src/repositories/app.repository'; -import { AssetEditRepository } from 'src/repositories/asset-edit.repository'; -import { AssetJobRepository } from 'src/repositories/asset-job.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { AuditRepository } from 'src/repositories/audit.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; -import { CronRepository } from 'src/repositories/cron.repository'; import { CryptoRepository } from 'src/repositories/crypto.repository'; import { DatabaseRepository } from 'src/repositories/database.repository'; -import { DownloadRepository } from 'src/repositories/download.repository'; -import { DuplicateRepository } from 'src/repositories/duplicate.repository'; -import { EmailRepository } from 'src/repositories/email.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { JobRepository } from 'src/repositories/job.repository'; -import { LibraryRepository } from 'src/repositories/library.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; -import { MachineLearningRepository } from 'src/repositories/machine-learning.repository'; -import { MapRepository } from 'src/repositories/map.repository'; -import { MediaRepository } from 'src/repositories/media.repository'; -import { MemoryRepository } from 'src/repositories/memory.repository'; -import { MetadataRepository } from 'src/repositories/metadata.repository'; -import { MoveRepository } from 'src/repositories/move.repository'; -import { NotificationRepository } from 'src/repositories/notification.repository'; -import { OAuthRepository } from 'src/repositories/oauth.repository'; -import { OcrRepository } from 'src/repositories/ocr.repository'; -import { PartnerRepository } from 'src/repositories/partner.repository'; -import { PersonRepository } from 'src/repositories/person.repository'; -import { PluginRepository } from 'src/repositories/plugin.repository'; import { ProcessRepository } from 'src/repositories/process.repository'; -import { SearchRepository } from 'src/repositories/search.repository'; -import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { SessionRepository } from 'src/repositories/session.repository'; -import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository'; -import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; -import { StackRepository } from 'src/repositories/stack.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; -import { SyncCheckpointRepository } from 'src/repositories/sync-checkpoint.repository'; -import { SyncRepository } from 'src/repositories/sync.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; -import { TagRepository } from 'src/repositories/tag.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; -import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; -import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; -import { ViewRepository } from 'src/repositories/view-repository'; import { WebsocketRepository } from 'src/repositories/websocket.repository'; -import { WorkflowRepository } from 'src/repositories/workflow.repository'; import { UserTable } from 'src/schema/tables/user.table'; -import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access'; import { getConfig, updateConfig } from 'src/utils/config'; export const BASE_SERVICE_DEPENDENCIES = [ LoggingRepository, - AccessRepository, - ActivityRepository, - AlbumRepository, - AlbumUserRepository, ApiKeyRepository, AppRepository, - AssetRepository, - AssetEditRepository, - AssetJobRepository, - AuditRepository, ConfigRepository, - CronRepository, CryptoRepository, DatabaseRepository, - DownloadRepository, - DuplicateRepository, - EmailRepository, EventRepository, JobRepository, - LibraryRepository, - MachineLearningRepository, - MapRepository, - MediaRepository, - MemoryRepository, - MetadataRepository, - MoveRepository, - NotificationRepository, - OAuthRepository, - OcrRepository, - PartnerRepository, - PersonRepository, - PluginRepository, ProcessRepository, - SearchRepository, - ServerInfoRepository, SessionRepository, - SharedLinkRepository, - SharedLinkAssetRepository, - StackRepository, - StorageRepository, - SyncRepository, - SyncCheckpointRepository, SystemMetadataRepository, - TagRepository, TelemetryRepository, - TrashRepository, UserRepository, - VersionHistoryRepository, - ViewRepository, WebsocketRepository, - WorkflowRepository, ]; @Injectable() export class BaseService { - protected storageCore: StorageCore; - constructor( protected logger: LoggingRepository, - protected accessRepository: AccessRepository, - protected activityRepository: ActivityRepository, - protected albumRepository: AlbumRepository, - protected albumUserRepository: AlbumUserRepository, protected apiKeyRepository: ApiKeyRepository, protected appRepository: AppRepository, - protected assetRepository: AssetRepository, - protected assetEditRepository: AssetEditRepository, - protected assetJobRepository: AssetJobRepository, - protected auditRepository: AuditRepository, protected configRepository: ConfigRepository, - protected cronRepository: CronRepository, protected cryptoRepository: CryptoRepository, protected databaseRepository: DatabaseRepository, - protected downloadRepository: DownloadRepository, - protected duplicateRepository: DuplicateRepository, - protected emailRepository: EmailRepository, protected eventRepository: EventRepository, protected jobRepository: JobRepository, - protected libraryRepository: LibraryRepository, - protected machineLearningRepository: MachineLearningRepository, - protected mapRepository: MapRepository, - protected mediaRepository: MediaRepository, - protected memoryRepository: MemoryRepository, - protected metadataRepository: MetadataRepository, - protected moveRepository: MoveRepository, - protected notificationRepository: NotificationRepository, - protected oauthRepository: OAuthRepository, - protected ocrRepository: OcrRepository, - protected partnerRepository: PartnerRepository, - protected personRepository: PersonRepository, - protected pluginRepository: PluginRepository, protected processRepository: ProcessRepository, - protected searchRepository: SearchRepository, - protected serverInfoRepository: ServerInfoRepository, protected sessionRepository: SessionRepository, - protected sharedLinkRepository: SharedLinkRepository, - protected sharedLinkAssetRepository: SharedLinkAssetRepository, - protected stackRepository: StackRepository, - protected storageRepository: StorageRepository, - protected syncRepository: SyncRepository, - protected syncCheckpointRepository: SyncCheckpointRepository, protected systemMetadataRepository: SystemMetadataRepository, - protected tagRepository: TagRepository, protected telemetryRepository: TelemetryRepository, - protected trashRepository: TrashRepository, protected userRepository: UserRepository, - protected versionRepository: VersionHistoryRepository, - protected viewRepository: ViewRepository, protected websocketRepository: WebsocketRepository, - protected workflowRepository: WorkflowRepository, ) { this.logger.setContext(this.constructor.name); - this.storageCore = StorageCore.create( - assetRepository, - configRepository, - cryptoRepository, - moveRepository, - personRepository, - storageRepository, - systemMetadataRepository, - this.logger, - ); } get worker() { @@ -207,14 +78,6 @@ export class BaseService { return updateConfig(this.configRepos, newConfig); } - requireAccess(request: AccessRequest) { - return requireAccess(this.accessRepository, request); - } - - checkAccess(request: AccessRequest) { - return checkAccess(this.accessRepository, request); - } - async createUser(dto: Insertable & { email: string }): Promise { const exists = await this.userRepository.getByEmail(dto.email); if (exists) { @@ -222,9 +85,9 @@ export class BaseService { } if (!dto.isAdmin) { - const localAdmin = await this.userRepository.getAdmin(); - if (!localAdmin) { - throw new BadRequestException('The first registered account must the administrator.'); + const hasAdmin = await this.userRepository.hasAdmin(); + if (!hasAdmin) { + throw new BadRequestException('The first registered account must be the administrator.'); } } @@ -232,12 +95,8 @@ export class BaseService { if (payload.password) { payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS); } - if (payload.storageLabel) { - payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', '')); - } const user = await this.userRepository.create(payload); - await this.eventRepository.emit('UserCreate', user); return user; diff --git a/server/src/services/cli.service.spec.ts b/server/src/services/cli.service.spec.ts deleted file mode 100644 index 36a3d2eb2c..0000000000 --- a/server/src/services/cli.service.spec.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { jwtVerify } from 'jose'; -import { MaintenanceAction, SystemMetadataKey } from 'src/enum'; -import { CliService } from 'src/services/cli.service'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; -import { describe, it } from 'vitest'; - -describe(CliService.name, () => { - let sut: CliService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(CliService)); - }); - - describe('listUsers', () => { - it('should list users', async () => { - mocks.user.getList.mockResolvedValue([factory.userAdmin({ isAdmin: true })]); - await expect(sut.listUsers()).resolves.toEqual([expect.objectContaining({ isAdmin: true })]); - expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: true }); - }); - }); - - describe('resetAdminPassword', () => { - it('should only work when there is an admin account', async () => { - mocks.user.getAdmin.mockResolvedValue(void 0); - const ask = vitest.fn().mockResolvedValue('new-password'); - - await expect(sut.resetAdminPassword(ask)).rejects.toThrowError('Admin account does not exist'); - - expect(ask).not.toHaveBeenCalled(); - }); - - it('should default to a random password', async () => { - const admin = factory.userAdmin({ isAdmin: true }); - - mocks.user.getAdmin.mockResolvedValue(admin); - mocks.user.update.mockResolvedValue(factory.userAdmin({ isAdmin: true })); - - const ask = vitest.fn().mockImplementation(() => {}); - - const response = await sut.resetAdminPassword(ask); - - const [id, update] = mocks.user.update.mock.calls[0]; - - expect(response.provided).toBe(false); - expect(ask).toHaveBeenCalled(); - expect(id).toEqual(admin.id); - expect(update.password).toBeDefined(); - }); - - it('should use the supplied password', async () => { - const admin = factory.userAdmin({ isAdmin: true }); - - mocks.user.getAdmin.mockResolvedValue(admin); - mocks.user.update.mockResolvedValue(admin); - - const ask = vitest.fn().mockResolvedValue('new-password'); - - const response = await sut.resetAdminPassword(ask); - - const [id, update] = mocks.user.update.mock.calls[0]; - - expect(response.provided).toBe(true); - expect(ask).toHaveBeenCalled(); - expect(id).toEqual(admin.id); - expect(update.password).toBeDefined(); - }); - }); - - describe('disablePasswordLogin', () => { - it('should disable password login', async () => { - await sut.disablePasswordLogin(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith('system-config', { passwordLogin: { enabled: false } }); - }); - }); - - describe('enablePasswordLogin', () => { - it('should enable password login', async () => { - await sut.enablePasswordLogin(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith('system-config', {}); - }); - }); - - describe('disableMaintenanceMode', () => { - it('should not do anything if not in maintenance mode', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false }); - await expect(sut.disableMaintenanceMode()).resolves.toEqual({ - alreadyDisabled: true, - }); - - expect(mocks.app.sendOneShotAppRestart).toHaveBeenCalledTimes(0); - expect(mocks.systemMetadata.set).toHaveBeenCalledTimes(0); - expect(mocks.event.emit).toHaveBeenCalledTimes(0); - }); - - it('should disable maintenance mode', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - await expect(sut.disableMaintenanceMode()).resolves.toEqual({ - alreadyDisabled: false, - }); - - expect(mocks.app.sendOneShotAppRestart).toHaveBeenCalled(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: false, - }); - }); - }); - - describe('enableMaintenanceMode', () => { - it('should not do anything if in maintenance mode', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - await expect(sut.enableMaintenanceMode()).resolves.toEqual( - expect.objectContaining({ - alreadyEnabled: true, - }), - ); - - expect(mocks.app.sendOneShotAppRestart).toHaveBeenCalledTimes(0); - expect(mocks.systemMetadata.set).toHaveBeenCalledTimes(0); - expect(mocks.event.emit).toHaveBeenCalledTimes(0); - }); - - it('should enable maintenance mode', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false }); - await expect(sut.enableMaintenanceMode()).resolves.toEqual( - expect.objectContaining({ - alreadyEnabled: false, - }), - ); - - expect(mocks.app.sendOneShotAppRestart).toHaveBeenCalled(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: true, - secret: expect.stringMatching(/^\w{128}$/), - action: { - action: 'start', - }, - }); - }); - - const RE_LOGIN_URL = /https:\/\/my.immich.app\/maintenance\?token=([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)/; - - it('should return a valid login URL', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - const result = await sut.enableMaintenanceMode(); - - expect(result).toEqual( - expect.objectContaining({ - authUrl: expect.stringMatching(RE_LOGIN_URL), - alreadyEnabled: true, - }), - ); - - const token = RE_LOGIN_URL.exec(result.authUrl)![1]; - - await expect(jwtVerify(token, new TextEncoder().encode('secret'))).resolves.toEqual( - expect.objectContaining({ - payload: expect.objectContaining({ - username: 'cli-admin', - }), - }), - ); - }); - }); - - describe('disableOAuthLogin', () => { - it('should disable oauth login', async () => { - await sut.disableOAuthLogin(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith('system-config', {}); - }); - }); - - describe('enableOAuthLogin', () => { - it('should enable oauth login', async () => { - await sut.enableOAuthLogin(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith('system-config', { oauth: { enabled: true } }); - }); - }); -}); diff --git a/server/src/services/cli.service.ts b/server/src/services/cli.service.ts deleted file mode 100644 index ce62f98aa1..0000000000 --- a/server/src/services/cli.service.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { isAbsolute } from 'node:path'; -import { SALT_ROUNDS } from 'src/constants'; -import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto'; -import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; -import { MaintenanceAction, SystemMetadataKey } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { createMaintenanceLoginUrl, generateMaintenanceSecret } from 'src/utils/maintenance'; -import { getExternalDomain } from 'src/utils/misc'; - -@Injectable() -export class CliService extends BaseService { - async listUsers(): Promise { - const users = await this.userRepository.getList({ withDeleted: true }); - return users.map((user) => mapUserAdmin(user)); - } - - async resetAdminPassword(ask: (admin: UserAdminResponseDto) => Promise) { - const admin = await this.userRepository.getAdmin(); - if (!admin) { - throw new Error('Admin account does not exist'); - } - - const providedPassword = await ask(mapUserAdmin(admin)); - const password = providedPassword || this.cryptoRepository.randomBytesAsText(24); - const hashedPassword = await this.cryptoRepository.hashBcrypt(password, SALT_ROUNDS); - - await this.userRepository.update(admin.id, { password: hashedPassword }); - - return { admin, password, provided: !!providedPassword }; - } - - async disablePasswordLogin(): Promise { - const config = await this.getConfig({ withCache: false }); - config.passwordLogin.enabled = false; - await this.updateConfig(config); - } - - async enablePasswordLogin(): Promise { - const config = await this.getConfig({ withCache: false }); - config.passwordLogin.enabled = true; - await this.updateConfig(config); - } - - async disableMaintenanceMode(): Promise<{ alreadyDisabled: boolean }> { - const currentState = await this.systemMetadataRepository - .get(SystemMetadataKey.MaintenanceMode) - .then((state) => state ?? { isMaintenanceMode: false as const }); - - if (!currentState.isMaintenanceMode) { - return { - alreadyDisabled: true, - }; - } - - const state = { isMaintenanceMode: false as const }; - await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, state); - await this.appRepository.sendOneShotAppRestart(state); - - return { - alreadyDisabled: false, - }; - } - - async enableMaintenanceMode(): Promise<{ authUrl: string; alreadyEnabled: boolean }> { - const { server } = await this.getConfig({ withCache: true }); - const baseUrl = getExternalDomain(server); - - const payload: MaintenanceAuthDto = { - username: 'cli-admin', - }; - - const state = await this.systemMetadataRepository - .get(SystemMetadataKey.MaintenanceMode) - .then((state) => state ?? { isMaintenanceMode: false as const }); - - if (state.isMaintenanceMode) { - return { - authUrl: await createMaintenanceLoginUrl(baseUrl, payload, state.secret), - alreadyEnabled: true, - }; - } - - const secret = generateMaintenanceSecret(); - - await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: true, - secret, - action: { - action: MaintenanceAction.Start, - }, - }); - - await this.appRepository.sendOneShotAppRestart({ - isMaintenanceMode: true, - }); - - return { - authUrl: await createMaintenanceLoginUrl(baseUrl, payload, secret), - alreadyEnabled: false, - }; - } - - async grantAdminAccess(email: string): Promise { - const user = await this.userRepository.getByEmail(email); - if (!user) { - throw new Error('User does not exist'); - } - - await this.userRepository.update(user.id, { isAdmin: true }); - } - - async revokeAdminAccess(email: string): Promise { - const user = await this.userRepository.getByEmail(email); - if (!user) { - throw new Error('User does not exist'); - } - - await this.userRepository.update(user.id, { isAdmin: false }); - } - - async disableOAuthLogin(): Promise { - const config = await this.getConfig({ withCache: false }); - config.oauth.enabled = false; - await this.updateConfig(config); - } - - async enableOAuthLogin(): Promise { - const config = await this.getConfig({ withCache: false }); - config.oauth.enabled = true; - await this.updateConfig(config); - } - - async getSampleFilePaths(): Promise { - const [assets, people, users] = await Promise.all([ - this.assetRepository.getFileSamples(), - this.personRepository.getFileSamples(), - this.userRepository.getFileSamples(), - ]); - - const paths = []; - - for (const person of people) { - paths.push(person.thumbnailPath); - } - - for (const user of users) { - paths.push(user.profileImagePath); - } - - for (const asset of assets) { - paths.push(asset.path); - } - - return paths.filter(Boolean) as string[]; - } - - async migrateFilePaths({ - oldValue, - newValue, - confirm, - }: { - oldValue: string; - newValue: string; - confirm: (data: { sourceFolder: string; targetFolder: string }) => Promise; - }): Promise { - let sourceFolder = oldValue; - if (sourceFolder.startsWith('./')) { - sourceFolder = sourceFolder.slice(2); - } - - const targetFolder = newValue; - if (!isAbsolute(targetFolder)) { - throw new Error('Target media location must be an absolute path'); - } - - if (!(await confirm({ sourceFolder, targetFolder }))) { - return false; - } - - await this.databaseRepository.migrateFilePaths(sourceFolder, targetFolder); - - return true; - } - - cleanup() { - return this.databaseRepository.shutdown(); - } -} diff --git a/server/src/services/database-backup.service.spec.ts b/server/src/services/database-backup.service.spec.ts deleted file mode 100644 index 4d68b02325..0000000000 --- a/server/src/services/database-backup.service.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { DateTime } from 'luxon'; -import { StorageCore } from 'src/cores/storage.core'; -import { StorageFolder } from 'src/enum'; -import { DatabaseBackupService } from 'src/services/database-backup.service'; -import { MaintenanceService } from 'src/services/maintenance.service'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(MaintenanceService.name, () => { - let sut: DatabaseBackupService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(DatabaseBackupService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('listBackups', () => { - it('should give us all backups', async () => { - mocks.storage.readdir.mockResolvedValue([ - `immich-db-backup-${DateTime.fromISO('2025-07-25T11:02:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz.tmp`, - `immich-db-backup-${DateTime.fromISO('2025-07-27T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - 'immich-db-backup-1753789649000.sql.gz', - `immich-db-backup-${DateTime.fromISO('2025-07-29T11:01:16Z').toFormat("yyyyLLdd'T'HHmmss")}-v1.234.5-pg14.5.sql.gz`, - ]); - mocks.storage.stat.mockResolvedValue({ size: 1024 } as any); - - await expect(sut.listBackups()).resolves.toMatchObject({ - backups: [ - { filename: 'immich-db-backup-20250729T110116-v1.234.5-pg14.5.sql.gz', filesize: 1024 }, - { filename: 'immich-db-backup-20250727T110116-v1.234.5-pg14.5.sql.gz', filesize: 1024 }, - { filename: 'immich-db-backup-1753789649000.sql.gz', filesize: 1024 }, - ], - }); - }); - }); - - describe('deleteBackup', () => { - it('should reject invalid file names', async () => { - await expect(sut.deleteBackup(['filename'])).rejects.toThrowError( - new BadRequestException('Invalid backup name!'), - ); - }); - - it('should unlink the target file', async () => { - await sut.deleteBackup(['filename.sql']); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); - expect(mocks.storage.unlink).toHaveBeenCalledWith( - `${StorageCore.getBaseFolder(StorageFolder.Backups)}/filename.sql`, - ); - }); - }); - - describe('uploadBackup', () => { - it('should reject invalid file names', async () => { - await expect(sut.uploadBackup({ originalname: 'invalid backup' } as never)).rejects.toThrowError( - new BadRequestException('Invalid backup name!'), - ); - }); - - it('should write file', async () => { - await sut.uploadBackup({ originalname: 'path.sql.gz', buffer: 'buffer' } as never); - expect(mocks.storage.createOrOverwriteFile).toBeCalledWith('/data/backups/uploaded-path.sql.gz', 'buffer'); - }); - }); - - describe('downloadBackup', () => { - it('should reject invalid file names', () => { - expect(() => sut.downloadBackup('invalid backup')).toThrowError(new BadRequestException('Invalid backup name!')); - }); - - it('should get backup path', () => { - expect(sut.downloadBackup('hello.sql.gz')).toEqual( - expect.objectContaining({ - path: '/data/backups/hello.sql.gz', - }), - ); - }); - }); -}); diff --git a/server/src/services/database-backup.service.ts b/server/src/services/database-backup.service.ts deleted file mode 100644 index 542e961b43..0000000000 --- a/server/src/services/database-backup.service.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { DatabaseBackupListResponseDto } from 'src/dtos/database-backup.dto'; -import { BaseService } from 'src/services/base.service'; -import { - deleteDatabaseBackup, - downloadDatabaseBackup, - listDatabaseBackups, - uploadDatabaseBackup, -} from 'src/utils/database-backups'; -import { ImmichFileResponse } from 'src/utils/file'; - -/** - * This service is available outside of maintenance mode to manage maintenance mode - */ -@Injectable() -export class DatabaseBackupService extends BaseService { - async listBackups(): Promise { - const backups = await listDatabaseBackups(this.backupRepos); - return { backups }; - } - - deleteBackup(files: string[]): Promise { - return deleteDatabaseBackup(this.backupRepos, files); - } - - async uploadBackup(file: Express.Multer.File): Promise { - return uploadDatabaseBackup(this.backupRepos, file); - } - - downloadBackup(fileName: string): ImmichFileResponse { - return downloadDatabaseBackup(fileName); - } - - private get backupRepos() { - return { - logger: this.logger, - storage: this.storageRepository, - config: this.configRepository, - process: this.processRepository, - database: this.databaseRepository, - }; - } -} diff --git a/server/src/services/database.service.spec.ts b/server/src/services/database.service.spec.ts deleted file mode 100644 index e30722d3d7..0000000000 --- a/server/src/services/database.service.spec.ts +++ /dev/null @@ -1,441 +0,0 @@ -import { EXTENSION_NAMES } from 'src/constants'; -import { DatabaseExtension, VectorIndex } from 'src/enum'; -import { DatabaseService } from 'src/services/database.service'; -import { VectorExtension } from 'src/types'; -import { mockEnvData } from 'test/repositories/config.repository.mock'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(DatabaseService.name, () => { - let sut: DatabaseService; - let mocks: ServiceMocks; - - let extensionRange: string; - let versionBelowRange: string; - let minVersionInRange: string; - let updateInRange: string; - let versionAboveRange: string; - - beforeEach(() => { - ({ sut, mocks } = newTestService(DatabaseService)); - - extensionRange = '0.2.x'; - mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.VectorChord); - mocks.database.getExtensionVersionRange.mockReturnValue(extensionRange); - - versionBelowRange = '0.1.0'; - minVersionInRange = '0.2.0'; - updateInRange = '0.2.1'; - versionAboveRange = '0.3.0'; - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: DatabaseExtension.VectorChord, - installedVersion: null, - availableVersion: minVersionInRange, - }, - ]); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onBootstrap', () => { - it('should throw an error if PostgreSQL version is below minimum supported version', async () => { - mocks.database.getPostgresVersion.mockResolvedValueOnce('13.10.0'); - - await expect(sut.onBootstrap()).rejects.toThrow('Invalid PostgreSQL version. Found 13.10.0'); - - expect(mocks.database.getPostgresVersion).toHaveBeenCalledTimes(1); - }); - - describe.each(>[ - { extension: DatabaseExtension.Vector, extensionName: EXTENSION_NAMES[DatabaseExtension.Vector] }, - { extension: DatabaseExtension.Vectors, extensionName: EXTENSION_NAMES[DatabaseExtension.Vectors] }, - { extension: DatabaseExtension.VectorChord, extensionName: EXTENSION_NAMES[DatabaseExtension.VectorChord] }, - ])('should work with $extensionName', ({ extension, extensionName }) => { - beforeEach(() => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - ]); - mocks.database.getVectorExtension.mockResolvedValue(extension); - mocks.config.getEnv.mockReturnValue( - mockEnvData({ - database: { - config: { - connectionType: 'parts', - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - database: 'immich', - }, - skipMigrations: false, - vectorExtension: extension, - }, - }), - ); - }); - - it(`should start up successfully with ${extension}`, async () => { - mocks.database.getPostgresVersion.mockResolvedValue('14.0.0'); - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - installedVersion: null, - availableVersion: minVersionInRange, - }, - ]); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.getPostgresVersion).toHaveBeenCalled(); - expect(mocks.database.createExtension).toHaveBeenCalledWith(extension); - expect(mocks.database.createExtension).toHaveBeenCalledTimes(1); - expect(mocks.database.getExtensionVersions).toHaveBeenCalled(); - expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should throw an error if the ${extension} extension is not installed`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([]); - const message = `The ${extensionName} extension is not available in this Postgres instance. - If using a container image, ensure the image has the extension installed.`; - await expect(sut.onBootstrap()).rejects.toThrow(message); - - expect(mocks.database.createExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - }); - - it(`should throw an error if the ${extension} extension version is below minimum supported version`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - installedVersion: versionBelowRange, - availableVersion: versionBelowRange, - }, - ]); - - await expect(sut.onBootstrap()).rejects.toThrow( - `The ${extensionName} extension version is ${versionBelowRange}, but Immich only supports ${extensionRange}`, - ); - - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - }); - - it(`should throw an error if ${extension} extension version is a nightly`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - installedVersion: '0.0.0', - availableVersion: '0.0.0', - }, - ]); - - await expect(sut.onBootstrap()).rejects.toThrow( - `The ${extensionName} extension version is 0.0.0, which means it is a nightly release.`, - ); - - expect(mocks.database.createExtension).not.toHaveBeenCalled(); - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - }); - - it(`should do in-range update for ${extension} extension`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: updateInRange, - installedVersion: minVersionInRange, - }, - ]); - mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: false }); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.updateVectorExtension).toHaveBeenCalledWith(extension, updateInRange); - expect(mocks.database.updateVectorExtension).toHaveBeenCalledTimes(1); - expect(mocks.database.getExtensionVersions).toHaveBeenCalled(); - expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should not upgrade ${extension} if same version`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: minVersionInRange, - installedVersion: minVersionInRange, - }, - ]); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should throw error if ${extension} available version is below range`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: versionBelowRange, - installedVersion: null, - }, - ]); - - await expect(sut.onBootstrap()).rejects.toThrow(); - - expect(mocks.database.createExtension).not.toHaveBeenCalled(); - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should throw error if ${extension} available version is above range`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: versionAboveRange, - installedVersion: minVersionInRange, - }, - ]); - - await expect(sut.onBootstrap()).rejects.toThrow(); - - expect(mocks.database.createExtension).not.toHaveBeenCalled(); - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it('should throw error if available version is below installed version', async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: minVersionInRange, - installedVersion: updateInRange, - }, - ]); - - await expect(sut.onBootstrap()).rejects.toThrow( - `The database currently has ${extensionName} ${updateInRange} activated, but the Postgres instance only has ${minVersionInRange} available.`, - ); - - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it('should throw error if installed version is not in version range', async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: minVersionInRange, - installedVersion: versionAboveRange, - }, - ]); - - await expect(sut.onBootstrap()).rejects.toThrow( - `The ${extensionName} extension version is ${versionAboveRange}, but Immich only supports`, - ); - - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should raise error if ${extension} extension upgrade failed`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: updateInRange, - installedVersion: minVersionInRange, - }, - ]); - mocks.database.updateVectorExtension.mockRejectedValue(new Error('Failed to update extension')); - - await expect(sut.onBootstrap()).rejects.toThrow('Failed to update extension'); - - expect(mocks.logger.warn.mock.calls).toEqual( - expect.arrayContaining([ - expect.arrayContaining([ - expect.stringContaining(`The ${extensionName} extension can be updated to ${updateInRange}.`), - ]), - ]), - ); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - expect(mocks.database.updateVectorExtension).toHaveBeenCalledWith(extension, updateInRange); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - }); - - it(`should warn if ${extension} extension update requires restart`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: extension, - availableVersion: updateInRange, - installedVersion: minVersionInRange, - }, - ]); - mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: true }); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.logger.warn.mock.calls).toEqual( - expect.arrayContaining([expect.arrayContaining([expect.stringContaining(extensionName)])]), - ); - - expect(mocks.database.updateVectorExtension).toHaveBeenCalledWith(extension, updateInRange); - expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should reindex ${extension} indices if needed`, async () => { - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([ - VectorIndex.Clip, - VectorIndex.Face, - ]); - expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledTimes(1); - expect(mocks.database.runMigrations).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - }); - - it(`should throw an error if reindexing fails`, async () => { - mocks.database.reindexVectorsIfNeeded.mockRejectedValue(new Error('Error reindexing')); - - await expect(sut.onBootstrap()).rejects.toBeDefined(); - - expect(mocks.database.reindexVectorsIfNeeded).toHaveBeenCalledExactlyOnceWith([ - VectorIndex.Clip, - VectorIndex.Face, - ]); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - expect(mocks.logger.fatal).not.toHaveBeenCalled(); - expect(mocks.logger.warn).toHaveBeenCalledWith( - expect.stringContaining('Could not run vector reindexing checks.'), - ); - }); - }); - - it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => { - mocks.config.getEnv.mockReturnValue( - mockEnvData({ - database: { - config: { - connectionType: 'parts', - host: 'database', - port: 5432, - username: 'postgres', - password: 'postgres', - database: 'immich', - }, - skipMigrations: true, - vectorExtension: DatabaseExtension.Vectors, - }, - }), - ); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - }); - - it(`should throw error if extension could not be created`, async () => { - mocks.database.updateVectorExtension.mockResolvedValue({ restartRequired: false }); - mocks.database.createExtension.mockRejectedValue(new Error('Failed to create extension')); - - await expect(sut.onBootstrap()).rejects.toThrow('Failed to create extension'); - - expect(mocks.logger.fatal).toHaveBeenCalledTimes(1); - expect(mocks.logger.fatal.mock.calls[0][0]).toContain('CREATE EXTENSION IF NOT EXISTS vchord CASCADE'); - expect(mocks.database.createExtension).toHaveBeenCalledTimes(1); - expect(mocks.database.updateVectorExtension).not.toHaveBeenCalled(); - expect(mocks.database.runMigrations).not.toHaveBeenCalled(); - }); - - it(`should drop unused extension`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: DatabaseExtension.Vectors, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - { - name: DatabaseExtension.VectorChord, - installedVersion: null, - availableVersion: minVersionInRange, - }, - ]); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord); - expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors); - }); - - it(`should warn if unused extension could not be dropped`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: DatabaseExtension.Vectors, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - { - name: DatabaseExtension.VectorChord, - installedVersion: null, - availableVersion: minVersionInRange, - }, - ]); - mocks.database.dropExtension.mockRejectedValue(new Error('Failed to drop extension')); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.createExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.VectorChord); - expect(mocks.database.dropExtension).toHaveBeenCalledExactlyOnceWith(DatabaseExtension.Vectors); - expect(mocks.logger.warn).toHaveBeenCalledTimes(1); - expect(mocks.logger.warn.mock.calls[0][0]).toContain('DROP EXTENSION vectors'); - }); - - it(`should not try to drop pgvector when using vectorchord`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: DatabaseExtension.Vector, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - { - name: DatabaseExtension.VectorChord, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - ]); - mocks.database.dropExtension.mockRejectedValue(new Error('Failed to drop extension')); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.database.dropExtension).not.toHaveBeenCalled(); - }); - - it(`should warn if using pgvecto.rs`, async () => { - mocks.database.getExtensionVersions.mockResolvedValue([ - { - name: DatabaseExtension.Vectors, - installedVersion: minVersionInRange, - availableVersion: minVersionInRange, - }, - ]); - mocks.database.getVectorExtension.mockResolvedValue(DatabaseExtension.Vectors); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.logger.warn).toHaveBeenCalledTimes(1); - expect(mocks.logger.warn.mock.calls[0][0]).toContain('DEPRECATION WARNING'); - }); - }); -}); diff --git a/server/src/services/database.service.ts b/server/src/services/database.service.ts index 2ff0e0ca27..00b1acb333 100644 --- a/server/src/services/database.service.ts +++ b/server/src/services/database.service.ts @@ -1,166 +1,37 @@ import { Injectable } from '@nestjs/common'; import semver from 'semver'; -import { EXTENSION_NAMES, VECTOR_EXTENSIONS } from 'src/constants'; +import { EXTENSION_NAMES, POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE } from 'src/constants'; import { OnEvent } from 'src/decorators'; -import { BootstrapEventPriority, DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum'; +import { BootstrapEventPriority, DatabaseExtension, DatabaseLock } from 'src/enum'; import { BaseService } from 'src/services/base.service'; -import { VectorExtension } from 'src/types'; - -type CreateFailedArgs = { name: string; extension: string }; -type UpdateFailedArgs = { name: string; extension: string; availableVersion: string }; -type DropFailedArgs = { name: string; extension: string }; -type RestartRequiredArgs = { name: string; availableVersion: string }; -type NightlyVersionArgs = { name: string; extension: string; version: string }; -type OutOfRangeArgs = { name: string; extension: string; version: string; range: string }; -type InvalidDowngradeArgs = { name: string; extension: string; installedVersion: string; availableVersion: string }; - -const messages = { - notInstalled: (name: string) => - `The ${name} extension is not available in this Postgres instance. - If using a container image, ensure the image has the extension installed.`, - nightlyVersion: ({ name, extension, version }: NightlyVersionArgs) => ` - The ${name} extension version is ${version}, which means it is a nightly release. - - Please run 'DROP EXTENSION IF EXISTS ${extension}' and switch to a release version. - See https://docs.immich.app/guides/database-queries for how to query the database.`, - outOfRange: ({ name, version, range }: OutOfRangeArgs) => - `The ${name} extension version is ${version}, but Immich only supports ${range}. - Please change ${name} to a compatible version in the Postgres instance.`, - createFailed: ({ name, extension }: CreateFailedArgs) => - `Failed to activate ${name} extension. - Please ensure the Postgres instance has ${name} installed. - - If the Postgres instance already has ${name} installed, Immich may not have the necessary permissions to activate it. - In this case, please run 'CREATE EXTENSION IF NOT EXISTS ${extension} CASCADE' manually as a superuser. - See https://docs.immich.app/guides/database-queries for how to query the database.`, - updateFailed: ({ name, extension, availableVersion }: UpdateFailedArgs) => - `The ${name} extension can be updated to ${availableVersion}. - Immich attempted to update the extension, but failed to do so. - This may be because Immich does not have the necessary permissions to update the extension. - - Please run 'ALTER EXTENSION ${extension} UPDATE' manually as a superuser. - See https://docs.immich.app/guides/database-queries for how to query the database.`, - dropFailed: ({ name, extension }: DropFailedArgs) => - `The ${name} extension is no longer needed, but could not be dropped. - This may be because Immich does not have the necessary permissions to drop the extension. - - Please run 'DROP EXTENSION ${extension};' manually as a superuser. - See https://docs.immich.app/guides/database-queries for how to query the database.`, - restartRequired: ({ name, availableVersion }: RestartRequiredArgs) => - `The ${name} extension has been updated to ${availableVersion}. - Please restart the Postgres instance to complete the update.`, - invalidDowngrade: ({ name, installedVersion, availableVersion }: InvalidDowngradeArgs) => - `The database currently has ${name} ${installedVersion} activated, but the Postgres instance only has ${availableVersion} available. - This most likely means the extension was downgraded. - If ${name} ${installedVersion} is compatible with Immich, please ensure the Postgres instance has this available.`, - deprecatedExtension: (name: string) => - `DEPRECATION WARNING: The ${name} extension is deprecated and support for it will be removed very soon. - See https://docs.immich.app/install/upgrading#migrating-to-vectorchord in order to switch to the VectorChord extension instead.`, -}; @Injectable() export class DatabaseService extends BaseService { - @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.DatabaseService }) + @OnEvent({ name: 'app.bootstrap', priority: BootstrapEventPriority.DatabaseService }) async onBootstrap() { const version = await this.databaseRepository.getPostgresVersion(); const current = semver.coerce(version); - const postgresRange = this.databaseRepository.getPostgresVersionRange(); - if (!current || !semver.satisfies(current, postgresRange)) { + if (!current || !semver.satisfies(current, POSTGRES_VERSION_RANGE)) { throw new Error( - `Invalid PostgreSQL version. Found ${version}, but needed ${postgresRange}. Please use a supported version.`, + `Invalid PostgreSQL version. Found ${version}, but needed ${POSTGRES_VERSION_RANGE}. Please use a supported version.`, ); } await this.databaseRepository.withLock(DatabaseLock.Migrations, async () => { - const extension = await this.databaseRepository.getVectorExtension(); - const name = EXTENSION_NAMES[extension]; - if (extension === DatabaseExtension.Vectors) { - this.logger.warn(messages.deprecatedExtension(name)); - } - const extensionRange = this.databaseRepository.getExtensionVersionRange(extension); - - const extensionVersions = await this.databaseRepository.getExtensionVersions(VECTOR_EXTENSIONS); - const { installedVersion, availableVersion } = extensionVersions.find((v) => v.name === extension) ?? {}; - if (!availableVersion) { - throw new Error(messages.notInstalled(name)); - } - - if ([availableVersion, installedVersion].some((version) => version && semver.eq(version, '0.0.0'))) { - throw new Error(messages.nightlyVersion({ name, extension, version: '0.0.0' })); - } - - if (!semver.satisfies(availableVersion, extensionRange)) { - throw new Error(messages.outOfRange({ name, extension, version: availableVersion, range: extensionRange })); - } - - if (!installedVersion) { - await this.createExtension(extension); - } - - if (installedVersion && semver.gt(availableVersion, installedVersion)) { - await this.updateExtension(extension, availableVersion); - } else if (installedVersion && !semver.satisfies(installedVersion, extensionRange)) { - throw new Error(messages.outOfRange({ name, extension, version: installedVersion, range: extensionRange })); - } else if (installedVersion && semver.lt(availableVersion, installedVersion)) { - throw new Error(messages.invalidDowngrade({ name, extension, availableVersion, installedVersion })); - } - + // Ensure pgvector extension is installed try { - await this.databaseRepository.reindexVectorsIfNeeded([VectorIndex.Clip, VectorIndex.Face]); + await this.databaseRepository.createExtension(DatabaseExtension.Vector); } catch (error) { - this.logger.warn( - 'Could not run vector reindexing checks. If the extension was updated, please restart the Postgres instance. If you are upgrading directly from a version below 1.107.2, please upgrade to 1.107.2 first.', - ); + const name = EXTENSION_NAMES[DatabaseExtension.Vector]; + this.logger.fatal(`Failed to activate ${name} extension. Please ensure pgvector is installed.`); throw error; } - for (const { name: dbName, installedVersion } of extensionVersions) { - const isDepended = dbName === DatabaseExtension.Vector && extension === DatabaseExtension.VectorChord; - if (dbName !== extension && installedVersion && !isDepended) { - await this.dropExtension(dbName); - } - } - + // Run migrations const { database } = this.configRepository.getEnv(); if (!database.skipMigrations) { await this.databaseRepository.runMigrations(); } - await Promise.all([ - this.databaseRepository.prewarm(VectorIndex.Clip), - this.databaseRepository.prewarm(VectorIndex.Face), - ]); }); } - - private async createExtension(extension: DatabaseExtension) { - try { - await this.databaseRepository.createExtension(extension); - } catch (error) { - const name = EXTENSION_NAMES[extension]; - this.logger.fatal(messages.createFailed({ name, extension })); - throw error; - } - } - - private async updateExtension(extension: VectorExtension, availableVersion: string) { - this.logger.log(`Updating ${EXTENSION_NAMES[extension]} extension to ${availableVersion}`); - try { - const { restartRequired } = await this.databaseRepository.updateVectorExtension(extension, availableVersion); - if (restartRequired) { - this.logger.warn(messages.restartRequired({ name: EXTENSION_NAMES[extension], availableVersion })); - } - } catch (error) { - this.logger.warn(messages.updateFailed({ name: EXTENSION_NAMES[extension], extension, availableVersion })); - throw error; - } - } - - private async dropExtension(extension: DatabaseExtension) { - try { - await this.databaseRepository.dropExtension(extension); - } catch (error) { - const name = EXTENSION_NAMES[extension]; - this.logger.warn(messages.dropFailed({ name, extension }), error); - } - } } diff --git a/server/src/services/download.service.spec.ts b/server/src/services/download.service.spec.ts deleted file mode 100644 index 86d0bda7f8..0000000000 --- a/server/src/services/download.service.spec.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { Readable } from 'node:stream'; -import { DownloadResponseDto } from 'src/dtos/download.dto'; -import { DownloadService } from 'src/services/download.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; -import { vitest } from 'vitest'; - -const downloadResponse: DownloadResponseDto = { - totalSize: 105_000, - archives: [ - { - assetIds: ['asset-1', 'asset-2'], - size: 105_000, - }, - ], -}; - -describe(DownloadService.name, () => { - let sut: DownloadService; - let mocks: ServiceMocks; - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - beforeEach(() => { - ({ sut, mocks } = newTestService(DownloadService)); - }); - - describe('downloadArchive', () => { - it('should skip asset ids that could not be found', async () => { - const archiveMock = { - addFile: vitest.fn(), - finalize: vitest.fn(), - stream: new Readable(), - }; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - mocks.asset.getByIds.mockResolvedValue([{ ...assetStub.noResizePath, id: 'asset-1' }]); - mocks.storage.createZipStream.mockReturnValue(archiveMock); - - await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ - stream: archiveMock.stream, - }); - - expect(archiveMock.addFile).toHaveBeenCalledTimes(1); - expect(archiveMock.addFile).toHaveBeenNthCalledWith( - 1, - expect.stringContaining('/data/library/IMG_123.jpg'), - 'IMG_123.jpg', - ); - }); - - it('should log a warning if the original path could not be resolved', async () => { - const archiveMock = { - addFile: vitest.fn(), - finalize: vitest.fn(), - stream: new Readable(), - }; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - mocks.storage.realpath.mockRejectedValue(new Error('Could not read file')); - mocks.asset.getByIds.mockResolvedValue([ - { ...assetStub.noResizePath, id: 'asset-1' }, - { ...assetStub.noWebpPath, id: 'asset-2' }, - ]); - mocks.storage.createZipStream.mockReturnValue(archiveMock); - - await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ - stream: archiveMock.stream, - }); - - expect(mocks.logger.warn).toHaveBeenCalledTimes(2); - expect(archiveMock.addFile).toHaveBeenCalledTimes(2); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(1, '/data/library/IMG_123.jpg', 'IMG_123.jpg'); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(2, '/data/library/IMG_456.jpg', 'IMG_456.jpg'); - }); - - it('should download an archive', async () => { - const archiveMock = { - addFile: vitest.fn(), - finalize: vitest.fn(), - stream: new Readable(), - }; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - mocks.asset.getByIds.mockResolvedValue([ - { ...assetStub.noResizePath, id: 'asset-1' }, - { ...assetStub.noWebpPath, id: 'asset-2' }, - ]); - mocks.storage.createZipStream.mockReturnValue(archiveMock); - - await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ - stream: archiveMock.stream, - }); - - expect(archiveMock.addFile).toHaveBeenCalledTimes(2); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(1, '/data/library/IMG_123.jpg', 'IMG_123.jpg'); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(2, '/data/library/IMG_456.jpg', 'IMG_456.jpg'); - }); - - it('should handle duplicate file names', async () => { - const archiveMock = { - addFile: vitest.fn(), - finalize: vitest.fn(), - stream: new Readable(), - }; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - mocks.asset.getByIds.mockResolvedValue([ - { ...assetStub.noResizePath, id: 'asset-1' }, - { ...assetStub.noResizePath, id: 'asset-2' }, - ]); - mocks.storage.createZipStream.mockReturnValue(archiveMock); - - await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ - stream: archiveMock.stream, - }); - - expect(archiveMock.addFile).toHaveBeenCalledTimes(2); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(1, '/data/library/IMG_123.jpg', 'IMG_123.jpg'); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(2, '/data/library/IMG_123.jpg', 'IMG_123+1.jpg'); - }); - - it('should be deterministic', async () => { - const archiveMock = { - addFile: vitest.fn(), - finalize: vitest.fn(), - stream: new Readable(), - }; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2'])); - mocks.asset.getByIds.mockResolvedValue([ - { ...assetStub.noResizePath, id: 'asset-2' }, - { ...assetStub.noResizePath, id: 'asset-1' }, - ]); - mocks.storage.createZipStream.mockReturnValue(archiveMock); - - await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1', 'asset-2'] })).resolves.toEqual({ - stream: archiveMock.stream, - }); - - expect(archiveMock.addFile).toHaveBeenCalledTimes(2); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(1, '/data/library/IMG_123.jpg', 'IMG_123.jpg'); - expect(archiveMock.addFile).toHaveBeenNthCalledWith(2, '/data/library/IMG_123.jpg', 'IMG_123+1.jpg'); - }); - - it('should resolve symlinks', async () => { - const archiveMock = { - addFile: vitest.fn(), - finalize: vitest.fn(), - stream: new Readable(), - }; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1'])); - mocks.asset.getByIds.mockResolvedValue([ - { ...assetStub.noResizePath, id: 'asset-1', originalPath: '/path/to/symlink.jpg' }, - ]); - mocks.storage.realpath.mockResolvedValue('/path/to/realpath.jpg'); - mocks.storage.createZipStream.mockReturnValue(archiveMock); - - await expect(sut.downloadArchive(authStub.admin, { assetIds: ['asset-1'] })).resolves.toEqual({ - stream: archiveMock.stream, - }); - - expect(archiveMock.addFile).toHaveBeenCalledWith('/path/to/realpath.jpg', 'IMG_123.jpg'); - }); - }); - - describe('getDownloadInfo', () => { - it('should throw an error for an invalid dto', async () => { - await expect(sut.getDownloadInfo(authStub.admin, {})).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should return a list of archives (assetIds)', async () => { - const assetIds = ['asset-1', 'asset-2']; - - mocks.user.getMetadata.mockResolvedValue([]); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); - mocks.downloadRepository.downloadAssetIds.mockReturnValue( - makeStream([ - { id: 'asset-1', livePhotoVideoId: null, size: 100_000 }, - { id: 'asset-2', livePhotoVideoId: null, size: 5000 }, - ]), - ); - - await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual(downloadResponse); - - expect(mocks.downloadRepository.downloadAssetIds).toHaveBeenCalledWith(['asset-1', 'asset-2']); - }); - - it('should return a list of archives (albumId)', async () => { - mocks.user.getMetadata.mockResolvedValue([]); - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-1'])); - mocks.downloadRepository.downloadAlbumId.mockReturnValue( - makeStream([ - { id: 'asset-1', livePhotoVideoId: null, size: 100_000 }, - { id: 'asset-2', livePhotoVideoId: null, size: 5000 }, - ]), - ); - - await expect(sut.getDownloadInfo(authStub.admin, { albumId: 'album-1' })).resolves.toEqual(downloadResponse); - - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-1'])); - expect(mocks.downloadRepository.downloadAlbumId).toHaveBeenCalledWith('album-1'); - }); - - it('should return a list of archives (userId)', async () => { - mocks.user.getMetadata.mockResolvedValue([]); - mocks.downloadRepository.downloadUserId.mockReturnValue( - makeStream([ - { id: 'asset-1', livePhotoVideoId: null, size: 100_000 }, - { id: 'asset-2', livePhotoVideoId: null, size: 5000 }, - ]), - ); - - await expect(sut.getDownloadInfo(authStub.admin, { userId: authStub.admin.user.id })).resolves.toEqual( - downloadResponse, - ); - - expect(mocks.downloadRepository.downloadUserId).toHaveBeenCalledWith(authStub.admin.user.id); - }); - - it('should split archives by size', async () => { - mocks.user.getMetadata.mockResolvedValue([]); - mocks.downloadRepository.downloadUserId.mockReturnValue( - makeStream([ - { id: 'asset-1', livePhotoVideoId: null, size: 5000 }, - { id: 'asset-2', livePhotoVideoId: null, size: 100_000 }, - { id: 'asset-3', livePhotoVideoId: null, size: 23_456 }, - { id: 'asset-4', livePhotoVideoId: null, size: 123_000 }, - ]), - ); - - await expect( - sut.getDownloadInfo(authStub.admin, { - userId: authStub.admin.user.id, - archiveSize: 30_000, - }), - ).resolves.toEqual({ - totalSize: 251_456, - archives: [ - { assetIds: ['asset-1', 'asset-2'], size: 105_000 }, - { assetIds: ['asset-3', 'asset-4'], size: 146_456 }, - ], - }); - }); - - it('should include the video portion of a live photo', async () => { - const assetIds = ['asset-1', 'asset-2']; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); - mocks.user.getMetadata.mockResolvedValue([]); - mocks.downloadRepository.downloadAssetIds.mockReturnValue( - makeStream([ - { id: 'asset-1', livePhotoVideoId: 'asset-3', size: 5000 }, - { id: 'asset-2', livePhotoVideoId: 'asset-4', size: 100_000 }, - ]), - ); - mocks.downloadRepository.downloadMotionAssetIds.mockReturnValue( - makeStream([ - { id: 'asset-3', livePhotoVideoId: null, size: 23_456, originalPath: '/path/to/file.mp4' }, - { id: 'asset-4', livePhotoVideoId: null, size: 123_000, originalPath: '/path/to/file.mp4' }, - ]), - ); - - await expect(sut.getDownloadInfo(authStub.admin, { assetIds, archiveSize: 30_000 })).resolves.toEqual({ - totalSize: 251_456, - archives: [ - { assetIds: ['asset-1', 'asset-2'], size: 105_000 }, - { assetIds: ['asset-3', 'asset-4'], size: 146_456 }, - ], - }); - }); - - it('should skip the video portion of an android live photo by default', async () => { - const assetIds = ['asset-1']; - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds)); - mocks.user.getMetadata.mockResolvedValue([]); - mocks.downloadRepository.downloadAssetIds.mockReturnValue( - makeStream([{ id: 'asset-1', livePhotoVideoId: 'asset-3', size: 5000 }]), - ); - - mocks.downloadRepository.downloadMotionAssetIds.mockReturnValue( - makeStream([ - { - id: 'asset-2', - livePhotoVideoId: null, - size: 23_456, - originalPath: '/data/encoded-video/uuid-MP.mp4', - }, - ]), - ); - - await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({ - totalSize: 5000, - archives: [ - { - assetIds: ['asset-1'], - size: 5000, - }, - ], - }); - }); - }); -}); diff --git a/server/src/services/download.service.ts b/server/src/services/download.service.ts deleted file mode 100644 index a5f734e59c..0000000000 --- a/server/src/services/download.service.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { parse } from 'node:path'; -import { StorageCore } from 'src/cores/storage.core'; -import { AssetIdsDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; -import { Permission } from 'src/enum'; -import { ImmichReadStream } from 'src/repositories/storage.repository'; -import { BaseService } from 'src/services/base.service'; -import { HumanReadableSize } from 'src/utils/bytes'; -import { getPreferences } from 'src/utils/preferences'; - -@Injectable() -export class DownloadService extends BaseService { - async getDownloadInfo(auth: AuthDto, dto: DownloadInfoDto): Promise { - let assets; - - if (dto.assetIds) { - const assetIds = dto.assetIds; - await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: assetIds }); - assets = this.downloadRepository.downloadAssetIds(assetIds); - } else if (dto.albumId) { - const albumId = dto.albumId; - await this.requireAccess({ auth, permission: Permission.AlbumDownload, ids: [albumId] }); - assets = this.downloadRepository.downloadAlbumId(albumId); - } else if (dto.userId) { - const userId = dto.userId; - await this.requireAccess({ auth, permission: Permission.TimelineDownload, ids: [userId] }); - assets = this.downloadRepository.downloadUserId(userId); - } else { - throw new BadRequestException('assetIds, albumId, or userId is required'); - } - - const targetSize = dto.archiveSize || HumanReadableSize.GiB * 4; - const metadata = await this.userRepository.getMetadata(auth.user.id); - const preferences = getPreferences(metadata); - const motionIds = new Set(); - const archives: DownloadArchiveInfo[] = []; - let archive: DownloadArchiveInfo = { size: 0, assetIds: [] }; - - const addToArchive = ({ id, size }: { id: string; size: number | null }) => { - archive.assetIds.push(id); - archive.size += Number(size || 0); - - if (archive.size > targetSize) { - archives.push(archive); - archive = { size: 0, assetIds: [] }; - } - }; - - for await (const asset of assets) { - // motion part of live photos - if (asset.livePhotoVideoId) { - motionIds.add(asset.livePhotoVideoId); - } - - addToArchive(asset); - } - - if (motionIds.size > 0) { - const motionAssets = this.downloadRepository.downloadMotionAssetIds([...motionIds]); - for await (const motionAsset of motionAssets) { - if (StorageCore.isAndroidMotionPath(motionAsset.originalPath) && !preferences.download.includeEmbeddedVideos) { - continue; - } - - addToArchive(motionAsset); - } - } - - if (archive.assetIds.length > 0) { - archives.push(archive); - } - - let totalSize = 0; - for (const archive of archives) { - totalSize += archive.size; - } - - return { totalSize, archives }; - } - - async downloadArchive(auth: AuthDto, dto: AssetIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetDownload, ids: dto.assetIds }); - - const zip = this.storageRepository.createZipStream(); - const assets = await this.assetRepository.getByIds(dto.assetIds); - const assetMap = new Map(assets.map((asset) => [asset.id, asset])); - const paths: Record = {}; - - for (const assetId of dto.assetIds) { - const asset = assetMap.get(assetId); - if (!asset) { - continue; - } - - const { originalPath, originalFileName } = asset; - - let filename = originalFileName; - const count = paths[filename] || 0; - paths[filename] = count + 1; - if (count !== 0) { - const parsedFilename = parse(originalFileName); - filename = `${parsedFilename.name}+${count}${parsedFilename.ext}`; - } - - let realpath = originalPath; - try { - realpath = await this.storageRepository.realpath(originalPath); - } catch { - this.logger.warn('Unable to resolve realpath', { originalPath }); - } - - zip.addFile(realpath, filename); - } - - void zip.finalize(); - - return { stream: zip.stream }; - } -} diff --git a/server/src/services/duplicate.service.spec.ts b/server/src/services/duplicate.service.spec.ts deleted file mode 100644 index e5ac9f82ba..0000000000 --- a/server/src/services/duplicate.service.spec.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { AssetType, AssetVisibility, JobName, JobStatus } from 'src/enum'; -import { DuplicateService } from 'src/services/duplicate.service'; -import { SearchService } from 'src/services/search.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; -import { beforeEach, vitest } from 'vitest'; - -vitest.useFakeTimers(); - -const hasEmbedding = { - id: 'asset-1', - ownerId: 'user-id', - stackId: null, - type: AssetType.Image, - duplicateId: null, - embedding: '[1, 2, 3, 4]', - visibility: AssetVisibility.Timeline, -}; - -const hasDupe = { - ...hasEmbedding, - id: 'asset-2', - duplicateId: 'duplicate-id', -}; - -describe(SearchService.name, () => { - let sut: DuplicateService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(DuplicateService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getDuplicates', () => { - it('should get duplicates', async () => { - mocks.duplicateRepository.getAll.mockResolvedValue([ - { - duplicateId: 'duplicate-id', - assets: [assetStub.image, assetStub.image], - }, - ]); - await expect(sut.getDuplicates(authStub.admin)).resolves.toEqual([ - { - duplicateId: 'duplicate-id', - assets: [ - expect.objectContaining({ id: assetStub.image.id }), - expect.objectContaining({ id: assetStub.image.id }), - ], - }, - ]); - }); - }); - - describe('handleQueueSearchDuplicates', () => { - beforeEach(() => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: true, - duplicateDetection: { - enabled: true, - }, - }, - }); - }); - - it('should skip if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: false, - duplicateDetection: { - enabled: true, - }, - }, - }); - - await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.Skipped); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should skip if duplicate detection is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: true, - duplicateDetection: { - enabled: false, - }, - }, - }); - - await expect(sut.handleQueueSearchDuplicates({})).resolves.toBe(JobStatus.Skipped); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should queue missing assets', async () => { - mocks.assetJob.streamForSearchDuplicates.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueSearchDuplicates({}); - - expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(undefined); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDetectDuplicates, - data: { id: assetStub.image.id }, - }, - ]); - }); - - it('should queue all assets', async () => { - mocks.assetJob.streamForSearchDuplicates.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueSearchDuplicates({ force: true }); - - expect(mocks.assetJob.streamForSearchDuplicates).toHaveBeenCalledWith(true); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDetectDuplicates, - data: { id: assetStub.image.id }, - }, - ]); - }); - }); - - describe('handleSearchDuplicates', () => { - beforeEach(() => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: true, - duplicateDetection: { - enabled: true, - }, - }, - }); - }); - - it('should skip if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: false, - duplicateDetection: { - enabled: true, - }, - }, - }); - const id = assetStub.livePhotoMotionAsset.id; - - const result = await sut.handleSearchDuplicates({ id }); - - expect(result).toBe(JobStatus.Skipped); - expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled(); - }); - - it('should skip if duplicate detection is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: true, - duplicateDetection: { - enabled: false, - }, - }, - }); - const id = assetStub.livePhotoMotionAsset.id; - - const result = await sut.handleSearchDuplicates({ id }); - - expect(result).toBe(JobStatus.Skipped); - expect(mocks.assetJob.getForSearchDuplicatesJob).not.toHaveBeenCalled(); - }); - - it('should fail if asset is not found', async () => { - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue(void 0); - - const result = await sut.handleSearchDuplicates({ id: assetStub.image.id }); - - expect(result).toBe(JobStatus.Failed); - expect(mocks.logger.error).toHaveBeenCalledWith(`Asset ${assetStub.image.id} not found`); - }); - - it('should skip if asset is part of stack', async () => { - const id = assetStub.primaryImage.id; - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, stackId: 'stack-id' }); - - const result = await sut.handleSearchDuplicates({ id }); - - expect(result).toBe(JobStatus.Skipped); - expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${id} is part of a stack, skipping`); - }); - - it('should skip if asset is not visible', async () => { - const id = assetStub.livePhotoMotionAsset.id; - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({ - ...hasEmbedding, - visibility: AssetVisibility.Hidden, - }); - - const result = await sut.handleSearchDuplicates({ id }); - - expect(result).toBe(JobStatus.Skipped); - expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${id} is not visible, skipping`); - }); - - it('should fail if asset is missing embedding', async () => { - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue({ ...hasEmbedding, embedding: null }); - - const result = await sut.handleSearchDuplicates({ id: assetStub.image.id }); - - expect(result).toBe(JobStatus.Failed); - expect(mocks.logger.debug).toHaveBeenCalledWith(`Asset ${assetStub.image.id} is missing embedding`); - }); - - it('should search for duplicates and update asset with duplicateId', async () => { - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue(hasEmbedding); - mocks.duplicateRepository.search.mockResolvedValue([ - { assetId: assetStub.image.id, distance: 0.01, duplicateId: null }, - ]); - mocks.duplicateRepository.merge.mockResolvedValue(); - const expectedAssetIds = [assetStub.image.id, hasEmbedding.id]; - - const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id }); - - expect(result).toBe(JobStatus.Success); - expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({ - assetId: hasEmbedding.id, - embedding: hasEmbedding.embedding, - maxDistance: 0.01, - type: hasEmbedding.type, - userIds: [hasEmbedding.ownerId], - }); - expect(mocks.duplicateRepository.merge).toHaveBeenCalledWith({ - assetIds: expectedAssetIds, - targetId: expect.any(String), - sourceIds: [], - }); - expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith( - ...expectedAssetIds.map((assetId) => ({ assetId, duplicatesDetectedAt: expect.any(Date) })), - ); - }); - - it('should use existing duplicate ID among matched duplicates', async () => { - const duplicateId = hasDupe.duplicateId; - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue(hasEmbedding); - mocks.duplicateRepository.search.mockResolvedValue([{ assetId: hasDupe.id, distance: 0.01, duplicateId }]); - mocks.duplicateRepository.merge.mockResolvedValue(); - const expectedAssetIds = [hasEmbedding.id]; - - const result = await sut.handleSearchDuplicates({ id: hasEmbedding.id }); - - expect(result).toBe(JobStatus.Success); - expect(mocks.duplicateRepository.search).toHaveBeenCalledWith({ - assetId: hasEmbedding.id, - embedding: hasEmbedding.embedding, - maxDistance: 0.01, - type: hasEmbedding.type, - userIds: [hasEmbedding.ownerId], - }); - expect(mocks.duplicateRepository.merge).toHaveBeenCalledWith({ - assetIds: expectedAssetIds, - targetId: duplicateId, - sourceIds: [], - }); - expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith( - ...expectedAssetIds.map((assetId) => ({ assetId, duplicatesDetectedAt: expect.any(Date) })), - ); - }); - - it('should remove duplicateId if no duplicates found and asset has duplicateId', async () => { - mocks.assetJob.getForSearchDuplicatesJob.mockResolvedValue(hasDupe); - mocks.duplicateRepository.search.mockResolvedValue([]); - - const result = await sut.handleSearchDuplicates({ id: hasDupe.id }); - - expect(result).toBe(JobStatus.Success); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: hasDupe.id, duplicateId: null }); - expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({ - assetId: hasDupe.id, - duplicatesDetectedAt: expect.any(Date), - }); - }); - }); -}); diff --git a/server/src/services/duplicate.service.ts b/server/src/services/duplicate.service.ts deleted file mode 100644 index 618754ff74..0000000000 --- a/server/src/services/duplicate.service.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { OnJob } from 'src/decorators'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { DuplicateResponseDto } from 'src/dtos/duplicate.dto'; -import { AssetVisibility, JobName, JobStatus, QueueName } from 'src/enum'; -import { AssetDuplicateResult } from 'src/repositories/search.repository'; -import { BaseService } from 'src/services/base.service'; -import { JobItem, JobOf } from 'src/types'; -import { isDuplicateDetectionEnabled } from 'src/utils/misc'; - -@Injectable() -export class DuplicateService extends BaseService { - async getDuplicates(auth: AuthDto): Promise { - const duplicates = await this.duplicateRepository.getAll(auth.user.id); - return duplicates.map(({ duplicateId, assets }) => ({ - duplicateId, - assets: assets.map((asset) => mapAsset(asset, { auth })), - })); - } - - async delete(auth: AuthDto, id: string): Promise { - await this.duplicateRepository.delete(auth.user.id, id); - } - - async deleteAll(auth: AuthDto, dto: BulkIdsDto) { - await this.duplicateRepository.deleteAll(auth.user.id, dto.ids); - } - - @OnJob({ name: JobName.AssetDetectDuplicatesQueueAll, queue: QueueName.DuplicateDetection }) - async handleQueueSearchDuplicates({ force }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: false }); - if (!isDuplicateDetectionEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - let jobs: JobItem[] = []; - const queueAll = async () => { - await this.jobRepository.queueAll(jobs); - jobs = []; - }; - - const assets = this.assetJobRepository.streamForSearchDuplicates(force); - for await (const asset of assets) { - jobs.push({ name: JobName.AssetDetectDuplicates, data: { id: asset.id } }); - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await queueAll(); - } - } - - await queueAll(); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetDetectDuplicates, queue: QueueName.DuplicateDetection }) - async handleSearchDuplicates({ id }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: true }); - if (!isDuplicateDetectionEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - const asset = await this.assetJobRepository.getForSearchDuplicatesJob(id); - if (!asset) { - this.logger.error(`Asset ${id} not found`); - return JobStatus.Failed; - } - - if (asset.stackId) { - this.logger.debug(`Asset ${id} is part of a stack, skipping`); - return JobStatus.Skipped; - } - - if (asset.visibility === AssetVisibility.Hidden) { - this.logger.debug(`Asset ${id} is not visible, skipping`); - return JobStatus.Skipped; - } - - if (asset.visibility === AssetVisibility.Locked) { - this.logger.debug(`Asset ${id} is locked, skipping`); - return JobStatus.Skipped; - } - - if (!asset.embedding) { - this.logger.debug(`Asset ${id} is missing embedding`); - return JobStatus.Failed; - } - - const duplicateAssets = await this.duplicateRepository.search({ - assetId: asset.id, - embedding: asset.embedding, - maxDistance: machineLearning.duplicateDetection.maxDistance, - type: asset.type, - userIds: [asset.ownerId], - }); - - let assetIds = [asset.id]; - if (duplicateAssets.length > 0) { - this.logger.debug( - `Found ${duplicateAssets.length} duplicate${duplicateAssets.length === 1 ? '' : 's'} for asset ${asset.id}`, - ); - assetIds = await this.updateDuplicates(asset, duplicateAssets); - } else if (asset.duplicateId) { - this.logger.debug(`No duplicates found for asset ${asset.id}, removing duplicateId`); - await this.assetRepository.update({ id: asset.id, duplicateId: null }); - } - - const duplicatesDetectedAt = new Date(); - await this.assetRepository.upsertJobStatus(...assetIds.map((assetId) => ({ assetId, duplicatesDetectedAt }))); - - return JobStatus.Success; - } - - private async updateDuplicates( - asset: { id: string; duplicateId: string | null }, - duplicateAssets: AssetDuplicateResult[], - ): Promise { - const duplicateIds = [ - ...new Set( - duplicateAssets - .filter((asset): asset is AssetDuplicateResult & { duplicateId: string } => !!asset.duplicateId) - .map((duplicate) => duplicate.duplicateId), - ), - ]; - - const targetDuplicateId = asset.duplicateId ?? duplicateIds.shift() ?? this.cryptoRepository.randomUUID(); - const assetIdsToUpdate = duplicateAssets - .filter((asset) => asset.duplicateId !== targetDuplicateId) - .map((duplicate) => duplicate.assetId); - assetIdsToUpdate.push(asset.id); - - await this.duplicateRepository.merge({ - targetId: targetDuplicateId, - assetIds: assetIdsToUpdate, - sourceIds: duplicateIds, - }); - return assetIdsToUpdate; - } -} diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 2c2fb995c8..9f918f664e 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -1,101 +1,19 @@ -import { ActivityService } from 'src/services/activity.service'; -import { AlbumService } from 'src/services/album.service'; import { ApiKeyService } from 'src/services/api-key.service'; import { ApiService } from 'src/services/api.service'; -import { AssetMediaService } from 'src/services/asset-media.service'; -import { AssetService } from 'src/services/asset.service'; -import { AuditService } from 'src/services/audit.service'; -import { AuthAdminService } from 'src/services/auth-admin.service'; import { AuthService } from 'src/services/auth.service'; -import { BackupService } from 'src/services/backup.service'; -import { CliService } from 'src/services/cli.service'; -import { DatabaseBackupService } from 'src/services/database-backup.service'; import { DatabaseService } from 'src/services/database.service'; -import { DownloadService } from 'src/services/download.service'; -import { DuplicateService } from 'src/services/duplicate.service'; -import { JobService } from 'src/services/job.service'; -import { LibraryService } from 'src/services/library.service'; -import { MaintenanceService } from 'src/services/maintenance.service'; -import { MapService } from 'src/services/map.service'; -import { MediaService } from 'src/services/media.service'; -import { MemoryService } from 'src/services/memory.service'; -import { MetadataService } from 'src/services/metadata.service'; -import { NotificationAdminService } from 'src/services/notification-admin.service'; -import { NotificationService } from 'src/services/notification.service'; -import { OcrService } from 'src/services/ocr.service'; -import { PartnerService } from 'src/services/partner.service'; -import { PersonService } from 'src/services/person.service'; -import { PluginService } from 'src/services/plugin.service'; import { QueueService } from 'src/services/queue.service'; -import { SearchService } from 'src/services/search.service'; import { ServerService } from 'src/services/server.service'; import { SessionService } from 'src/services/session.service'; -import { SharedLinkService } from 'src/services/shared-link.service'; -import { SmartInfoService } from 'src/services/smart-info.service'; -import { StackService } from 'src/services/stack.service'; -import { StorageTemplateService } from 'src/services/storage-template.service'; -import { StorageService } from 'src/services/storage.service'; -import { SyncService } from 'src/services/sync.service'; -import { SystemConfigService } from 'src/services/system-config.service'; -import { SystemMetadataService } from 'src/services/system-metadata.service'; -import { TagService } from 'src/services/tag.service'; -import { TelemetryService } from 'src/services/telemetry.service'; -import { TimelineService } from 'src/services/timeline.service'; -import { TrashService } from 'src/services/trash.service'; -import { UserAdminService } from 'src/services/user-admin.service'; import { UserService } from 'src/services/user.service'; -import { VersionService } from 'src/services/version.service'; -import { ViewService } from 'src/services/view.service'; -import { WorkflowService } from 'src/services/workflow.service'; export const services = [ ApiKeyService, - ActivityService, - AlbumService, ApiService, - AssetMediaService, - AssetService, - AuditService, AuthService, - AuthAdminService, - BackupService, - CliService, - DatabaseBackupService, DatabaseService, - DownloadService, - DuplicateService, - JobService, - LibraryService, - MaintenanceService, - MapService, - MediaService, - MemoryService, - MetadataService, - NotificationService, - NotificationAdminService, - OcrService, - PartnerService, - PersonService, - PluginService, QueueService, - SearchService, ServerService, SessionService, - SharedLinkService, - SmartInfoService, - StackService, - StorageService, - StorageTemplateService, - SyncService, - SystemConfigService, - SystemMetadataService, - TagService, - TelemetryService, - TimelineService, - TrashService, - UserAdminService, UserService, - VersionService, - ViewService, - WorkflowService, ]; diff --git a/server/src/services/job.service.spec.ts b/server/src/services/job.service.spec.ts deleted file mode 100644 index c23b4f05df..0000000000 --- a/server/src/services/job.service.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum'; -import { JobService } from 'src/services/job.service'; -import { JobItem } from 'src/types'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(JobService.name, () => { - let sut: JobService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(JobService)); - - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onJobRun', () => { - it('should process a successful job', async () => { - mocks.job.run.mockResolvedValue(JobStatus.Success); - - const job: JobItem = { name: JobName.FileDelete, data: { files: ['path/to/file'] } }; - await sut.onJobRun(QueueName.BackgroundTask, job); - - expect(mocks.event.emit).toHaveBeenCalledWith('JobStart', QueueName.BackgroundTask, job); - expect(mocks.event.emit).toHaveBeenCalledWith('JobSuccess', { job, response: JobStatus.Success }); - expect(mocks.event.emit).toHaveBeenCalledWith('JobComplete', QueueName.BackgroundTask, job); - expect(mocks.logger.error).not.toHaveBeenCalled(); - }); - - const tests: Array<{ item: JobItem; jobs: JobName[]; stub?: any }> = [ - { - item: { name: JobName.SidecarCheck, data: { id: 'asset-1' } }, - jobs: [JobName.AssetExtractMetadata], - }, - { - item: { name: JobName.SidecarCheck, data: { id: 'asset-1' } }, - jobs: [JobName.AssetExtractMetadata], - }, - { - item: { name: JobName.StorageTemplateMigrationSingle, data: { id: 'asset-1', source: 'upload' } }, - jobs: [JobName.AssetGenerateThumbnails], - }, - { - item: { name: JobName.StorageTemplateMigrationSingle, data: { id: 'asset-1' } }, - jobs: [], - }, - { - item: { name: JobName.PersonGenerateThumbnail, data: { id: 'asset-1' } }, - jobs: [], - }, - { - item: { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1' } }, - jobs: [], - stub: [assetStub.image], - }, - { - item: { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1' } }, - jobs: [], - stub: [assetStub.video], - }, - { - item: { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1', source: 'upload' } }, - jobs: [JobName.SmartSearch, JobName.AssetDetectFaces, JobName.Ocr], - stub: [assetStub.livePhotoStillAsset], - }, - { - item: { name: JobName.AssetGenerateThumbnails, data: { id: 'asset-1', source: 'upload' } }, - jobs: [JobName.SmartSearch, JobName.AssetDetectFaces, JobName.Ocr, JobName.AssetEncodeVideo], - stub: [assetStub.video], - }, - { - item: { name: JobName.SmartSearch, data: { id: 'asset-1' } }, - jobs: [], - }, - { - item: { name: JobName.AssetDetectFaces, data: { id: 'asset-1' } }, - jobs: [], - }, - { - item: { name: JobName.FacialRecognition, data: { id: 'asset-1' } }, - jobs: [], - }, - ]; - - for (const { item, jobs, stub } of tests) { - it(`should queue ${jobs.length} jobs when a ${item.name} job finishes successfully`, async () => { - if (stub) { - mocks.asset.getByIdsWithAllRelationsButStacks.mockResolvedValue(stub); - } - - mocks.job.run.mockResolvedValue(JobStatus.Success); - - await sut.onJobRun(QueueName.BackgroundTask, item); - - if (jobs.length > 1) { - expect(mocks.job.queueAll).toHaveBeenCalledWith( - jobs.map((jobName) => ({ name: jobName, data: expect.anything() })), - ); - } else { - expect(mocks.job.queue).toHaveBeenCalledTimes(jobs.length); - for (const jobName of jobs) { - expect(mocks.job.queue).toHaveBeenCalledWith({ name: jobName, data: expect.anything() }); - } - } - }); - - it(`should not queue any jobs when ${item.name} fails`, async () => { - mocks.job.run.mockResolvedValue(JobStatus.Failed); - - await sut.onJobRun(QueueName.BackgroundTask, item); - - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - } - }); -}); diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts deleted file mode 100644 index 2a47745a6c..0000000000 --- a/server/src/services/job.service.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { OnEvent } from 'src/decorators'; -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { JobCreateDto } from 'src/dtos/job.dto'; -import { AssetType, AssetVisibility, JobName, JobStatus, ManualJobName } from 'src/enum'; -import { ArgsOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { JobItem } from 'src/types'; -import { hexOrBufferToBase64 } from 'src/utils/bytes'; - -const asJobItem = (dto: JobCreateDto): JobItem => { - switch (dto.name) { - case ManualJobName.TagCleanup: { - return { name: JobName.TagCleanup }; - } - - case ManualJobName.PersonCleanup: { - return { name: JobName.PersonCleanup }; - } - - case ManualJobName.UserCleanup: { - return { name: JobName.UserDeleteCheck }; - } - - case ManualJobName.MemoryCleanup: { - return { name: JobName.MemoryCleanup }; - } - - case ManualJobName.MemoryCreate: { - return { name: JobName.MemoryGenerate }; - } - - case ManualJobName.BackupDatabase: { - return { name: JobName.DatabaseBackup }; - } - - default: { - throw new BadRequestException('Invalid job name'); - } - } -}; - -@Injectable() -export class JobService extends BaseService { - async create(dto: JobCreateDto): Promise { - await this.jobRepository.queue(asJobItem(dto)); - } - - @OnEvent({ name: 'JobRun' }) - async onJobRun(...[queueName, job]: ArgsOf<'JobRun'>) { - try { - await this.eventRepository.emit('JobStart', queueName, job); - const response = await this.jobRepository.run(job); - await this.eventRepository.emit('JobSuccess', { job, response }); - if (response && typeof response === 'string' && [JobStatus.Success, JobStatus.Skipped].includes(response)) { - await this.onDone(job); - } - } catch (error: Error | any) { - await this.eventRepository.emit('JobError', { job, error }); - } finally { - await this.eventRepository.emit('JobComplete', queueName, job); - } - } - - /** - * Queue follow up jobs - */ - private async onDone(item: JobItem) { - switch (item.name) { - case JobName.SidecarCheck: { - await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: item.data }); - break; - } - - case JobName.SidecarWrite: { - await this.jobRepository.queue({ - name: JobName.AssetExtractMetadata, - data: { id: item.data.id, source: 'sidecar-write' }, - }); - break; - } - - case JobName.StorageTemplateMigrationSingle: { - if (item.data.source === 'upload' || item.data.source === 'copy') { - await this.jobRepository.queue({ name: JobName.AssetGenerateThumbnails, data: item.data }); - } - break; - } - - case JobName.PersonGenerateThumbnail: { - const { id } = item.data; - const person = await this.personRepository.getById(id); - if (person) { - this.websocketRepository.clientSend('on_person_thumbnail', person.ownerId, person.id); - } - break; - } - - case JobName.AssetEditThumbnailGeneration: { - const asset = await this.assetRepository.getById(item.data.id); - - if (asset) { - this.websocketRepository.clientSend('AssetEditReadyV1', asset.ownerId, { - asset: { - id: asset.id, - ownerId: asset.ownerId, - originalFileName: asset.originalFileName, - thumbhash: asset.thumbhash ? hexOrBufferToBase64(asset.thumbhash) : null, - checksum: hexOrBufferToBase64(asset.checksum), - fileCreatedAt: asset.fileCreatedAt, - fileModifiedAt: asset.fileModifiedAt, - localDateTime: asset.localDateTime, - duration: asset.duration, - type: asset.type, - deletedAt: asset.deletedAt, - isFavorite: asset.isFavorite, - visibility: asset.visibility, - livePhotoVideoId: asset.livePhotoVideoId, - stackId: asset.stackId, - libraryId: asset.libraryId, - width: asset.width, - height: asset.height, - isEdited: asset.isEdited, - }, - }); - } - - break; - } - - case JobName.AssetGenerateThumbnails: { - if (!item.data.notify && item.data.source !== 'upload') { - break; - } - - const [asset] = await this.assetRepository.getByIdsWithAllRelationsButStacks([item.data.id]); - if (!asset) { - this.logger.warn(`Could not find asset ${item.data.id} after generating thumbnails`); - break; - } - - const jobs: JobItem[] = [ - { name: JobName.SmartSearch, data: item.data }, - { name: JobName.AssetDetectFaces, data: item.data }, - { name: JobName.Ocr, data: item.data }, - ]; - - if (asset.type === AssetType.Video) { - jobs.push({ name: JobName.AssetEncodeVideo, data: item.data }); - } - - await this.jobRepository.queueAll(jobs); - if (asset.visibility === AssetVisibility.Timeline || asset.visibility === AssetVisibility.Archive) { - this.websocketRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset)); - if (asset.exifInfo) { - const exif = asset.exifInfo; - this.websocketRepository.clientSend('AssetUploadReadyV1', asset.ownerId, { - // TODO remove `on_upload_success` and then modify the query to select only the required fields) - asset: { - id: asset.id, - ownerId: asset.ownerId, - originalFileName: asset.originalFileName, - thumbhash: asset.thumbhash ? hexOrBufferToBase64(asset.thumbhash) : null, - checksum: hexOrBufferToBase64(asset.checksum), - fileCreatedAt: asset.fileCreatedAt, - fileModifiedAt: asset.fileModifiedAt, - localDateTime: asset.localDateTime, - duration: asset.duration, - type: asset.type, - deletedAt: asset.deletedAt, - isFavorite: asset.isFavorite, - visibility: asset.visibility, - livePhotoVideoId: asset.livePhotoVideoId, - stackId: asset.stackId, - libraryId: asset.libraryId, - width: asset.width, - height: asset.height, - isEdited: asset.isEdited, - }, - exif: { - assetId: exif.assetId, - description: exif.description, - exifImageWidth: exif.exifImageWidth, - exifImageHeight: exif.exifImageHeight, - fileSizeInByte: exif.fileSizeInByte, - orientation: exif.orientation, - dateTimeOriginal: exif.dateTimeOriginal, - modifyDate: exif.modifyDate, - timeZone: exif.timeZone, - latitude: exif.latitude, - longitude: exif.longitude, - projectionType: exif.projectionType, - city: exif.city, - state: exif.state, - country: exif.country, - make: exif.make, - model: exif.model, - lensModel: exif.lensModel, - fNumber: exif.fNumber, - focalLength: exif.focalLength, - iso: exif.iso, - exposureTime: exif.exposureTime, - profileDescription: exif.profileDescription, - rating: exif.rating, - fps: exif.fps, - }, - }); - } - } - - break; - } - - case JobName.SmartSearch: { - if (item.data.source === 'upload') { - await this.jobRepository.queue({ name: JobName.AssetDetectDuplicates, data: item.data }); - } - break; - } - } - } -} diff --git a/server/src/services/library.service.spec.ts b/server/src/services/library.service.spec.ts deleted file mode 100644 index dbff1ca467..0000000000 --- a/server/src/services/library.service.spec.ts +++ /dev/null @@ -1,1294 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { Stats } from 'node:fs'; -import { defaults, SystemConfig } from 'src/config'; -import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants'; -import { mapLibrary } from 'src/dtos/library.dto'; -import { AssetType, CronJob, ImmichWorker, JobName, JobStatus } from 'src/enum'; -import { LibraryService } from 'src/services/library.service'; -import { ILibraryBulkIdsJob, ILibraryFileJob } from 'src/types'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { makeMockWatcher } from 'test/repositories/storage.repository.mock'; -import { factory, newUuid } from 'test/small.factory'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; -import { vitest } from 'vitest'; - -async function* mockWalk() { - yield await Promise.resolve(['/data/user1/photo.jpg']); -} - -describe(LibraryService.name, () => { - let sut: LibraryService; - - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(LibraryService)); - - mocks.database.tryLock.mockResolvedValue(true); - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onConfigInit', () => { - it('should init cron job and handle config changes', async () => { - mocks.cron.create.mockResolvedValue(); - mocks.cron.update.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: defaults }); - - expect(mocks.cron.create).toHaveBeenCalled(); - - await sut.onConfigUpdate({ - oldConfig: defaults, - newConfig: { - library: { - scan: { - enabled: true, - cronExpression: '0 1 * * *', - }, - watch: { enabled: false }, - }, - } as SystemConfig, - }); - - expect(mocks.cron.update).toHaveBeenCalledWith({ - name: CronJob.LibraryScan, - expression: '0 1 * * *', - start: true, - }); - }); - - it('should initialize watcher for all external libraries', async () => { - const library1 = factory.library({ importPaths: ['/foo', '/bar'] }); - const library2 = factory.library({ importPaths: ['/xyz', '/asdf'] }); - - mocks.library.getAll.mockResolvedValue([library1, library2]); - - mocks.library.get.mockImplementation((id) => - Promise.resolve([library1, library2].find((library) => library.id === id)), - ); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - - expect(mocks.storage.watch.mock.calls).toEqual( - expect.arrayContaining([(library1.importPaths, expect.anything()), (library2.importPaths, expect.anything())]), - ); - }); - - it('should not initialize watcher when watching is disabled', async () => { - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig }); - - expect(mocks.storage.watch).not.toHaveBeenCalled(); - }); - - it('should not initialize watcher when lock is taken', async () => { - mocks.database.tryLock.mockResolvedValue(false); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - - expect(mocks.storage.watch).not.toHaveBeenCalled(); - }); - - it('should not initialize library scan cron job when lock is taken', async () => { - mocks.database.tryLock.mockResolvedValue(false); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - - expect(mocks.cron.create).not.toHaveBeenCalled(); - }); - }); - - describe('onConfigUpdateEvent', () => { - beforeEach(async () => { - mocks.database.tryLock.mockResolvedValue(true); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: defaults }); - }); - - it('should do nothing if instance does not have the watch lock', async () => { - mocks.database.tryLock.mockResolvedValue(false); - await sut.onConfigInit({ newConfig: defaults }); - await sut.onConfigUpdate({ newConfig: systemConfigStub.libraryScan as SystemConfig, oldConfig: defaults }); - expect(mocks.cron.update).not.toHaveBeenCalled(); - }); - - it('should update cron job and enable watching', async () => { - mocks.library.getAll.mockResolvedValue([]); - mocks.cron.create.mockResolvedValue(); - mocks.cron.update.mockResolvedValue(); - - await sut.onConfigUpdate({ - newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig, - oldConfig: defaults, - }); - - expect(mocks.cron.update).toHaveBeenCalledWith({ - name: CronJob.LibraryScan, - expression: systemConfigStub.libraryScan.library.scan.cronExpression, - start: systemConfigStub.libraryScan.library.scan.enabled, - }); - }); - - it('should update cron job and disable watching', async () => { - mocks.library.getAll.mockResolvedValue([]); - mocks.cron.create.mockResolvedValue(); - mocks.cron.update.mockResolvedValue(); - - await sut.onConfigUpdate({ - newConfig: systemConfigStub.libraryScanAndWatch as SystemConfig, - oldConfig: defaults, - }); - await sut.onConfigUpdate({ - newConfig: systemConfigStub.libraryScan as SystemConfig, - oldConfig: defaults, - }); - - expect(mocks.cron.update).toHaveBeenCalledWith({ - name: CronJob.LibraryScan, - expression: systemConfigStub.libraryScan.library.scan.cronExpression, - start: systemConfigStub.libraryScan.library.scan.enabled, - }); - }); - }); - - describe('handleQueueSyncFiles', () => { - it('should queue refresh of a new asset', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.storage.walk.mockImplementation(mockWalk); - mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); - mocks.storage.checkFileExists.mockResolvedValue(true); - mocks.asset.filterNewExternalAssetPaths.mockResolvedValue(['/data/user1/photo.jpg']); - - await sut.handleQueueSyncFiles({ id: library.id }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibrarySyncFiles, - data: { - libraryId: library.id, - paths: ['/data/user1/photo.jpg'], - progressCounter: 1, - }, - }); - }); - - it('should fail when library is not found', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - await expect(sut.handleQueueSyncFiles({ id: library.id })).resolves.toBe(JobStatus.Skipped); - }); - - it('should ignore import paths that do not exist', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - mocks.storage.stat.mockImplementation((path): Promise => { - if (path === library.importPaths[0]) { - const error = { code: 'ENOENT' } as any; - throw error; - } - return Promise.resolve({ - isDirectory: () => true, - } as Stats); - }); - - mocks.storage.checkFileExists.mockResolvedValue(true); - - mocks.library.get.mockResolvedValue(library); - - await sut.handleQueueSyncFiles({ id: library.id }); - - expect(mocks.storage.walk).toHaveBeenCalledWith({ - pathsToCrawl: [library.importPaths[1]], - exclusionPatterns: [], - includeHidden: false, - take: JOBS_LIBRARY_PAGINATION_SIZE, - }); - }); - }); - - describe('handleQueueSyncFiles', () => { - it('should queue refresh of a new asset', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.storage.walk.mockImplementation(mockWalk); - mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); - mocks.storage.checkFileExists.mockResolvedValue(true); - mocks.asset.filterNewExternalAssetPaths.mockResolvedValue(['/data/user1/photo.jpg']); - - await sut.handleQueueSyncFiles({ id: library.id }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibrarySyncFiles, - data: { - libraryId: library.id, - paths: ['/data/user1/photo.jpg'], - progressCounter: 1, - }, - }); - }); - - it("should fail when library can't be found", async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - await expect(sut.handleQueueSyncFiles({ id: library.id })).resolves.toBe(JobStatus.Skipped); - }); - - it('should ignore import paths that do not exist', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.storage.stat.mockImplementation((path): Promise => { - if (path === library.importPaths[0]) { - const error = { code: 'ENOENT' } as any; - throw error; - } - return Promise.resolve({ - isDirectory: () => true, - } as Stats); - }); - - mocks.storage.checkFileExists.mockResolvedValue(true); - - mocks.library.get.mockResolvedValue(library); - - await sut.handleQueueSyncFiles({ id: library.id }); - - expect(mocks.storage.walk).toHaveBeenCalledWith({ - pathsToCrawl: [library.importPaths[1]], - exclusionPatterns: [], - includeHidden: false, - take: JOBS_LIBRARY_PAGINATION_SIZE, - }); - }); - }); - - describe('handleQueueSyncAssets', () => { - it('should call the offline check', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - mocks.storage.walk.mockImplementation(async function* generator() {}); - mocks.asset.getLibraryAssetCount.mockResolvedValue(1); - mocks.asset.detectOfflineExternalAssets.mockResolvedValue({ numUpdatedRows: 1n }); - - const response = await sut.handleQueueSyncAssets({ id: library.id }); - - expect(response).toBe(JobStatus.Success); - expect(mocks.asset.detectOfflineExternalAssets).toHaveBeenCalledWith( - library.id, - library.importPaths, - library.exclusionPatterns, - ); - }); - - it('should skip an empty library', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - mocks.storage.walk.mockImplementation(async function* generator() {}); - mocks.asset.getLibraryAssetCount.mockResolvedValue(0); - mocks.asset.detectOfflineExternalAssets.mockResolvedValue({ numUpdatedRows: 1n }); - - const response = await sut.handleQueueSyncAssets({ id: library.id }); - - expect(response).toBe(JobStatus.Success); - expect(mocks.asset.detectOfflineExternalAssets).not.toHaveBeenCalled(); - }); - - it('should queue asset sync', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.storage.walk.mockImplementation(async function* generator() {}); - mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.external])); - mocks.asset.getLibraryAssetCount.mockResolvedValue(1); - mocks.asset.detectOfflineExternalAssets.mockResolvedValue({ numUpdatedRows: 0n }); - mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.external])); - - const response = await sut.handleQueueSyncAssets({ id: library.id }); - - expect(mocks.job.queue).toBeCalledWith({ - name: JobName.LibrarySyncAssets, - data: { - libraryId: library.id, - importPaths: library.importPaths, - exclusionPatterns: library.exclusionPatterns, - assetIds: [assetStub.external.id], - progressCounter: 1, - totalAssets: 1, - }, - }); - - expect(response).toBe(JobStatus.Success); - expect(mocks.asset.detectOfflineExternalAssets).toHaveBeenCalledWith( - library.id, - library.importPaths, - library.exclusionPatterns, - ); - }); - - it("should fail if library can't be found", async () => { - await expect(sut.handleQueueSyncAssets({ id: newUuid() })).resolves.toBe(JobStatus.Skipped); - }); - }); - - describe('handleSyncAssets', () => { - it('should offline assets no longer on disk', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.external.id], - libraryId: newUuid(), - importPaths: ['/'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.external]); - mocks.storage.stat.mockRejectedValue(new Error('ENOENT, no such file or directory')); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { - isOffline: true, - deletedAt: expect.anything(), - }); - }); - - it('should set assets deleted from disk as offline', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.external.id], - libraryId: newUuid(), - importPaths: ['/data/user2'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.external]); - mocks.storage.stat.mockRejectedValue(new Error('Could not read file')); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { - isOffline: true, - deletedAt: expect.anything(), - }); - }); - - it('should do nothing with offline assets deleted from disk', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.trashedOffline.id], - libraryId: newUuid(), - importPaths: ['/data/user2'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.trashedOffline]); - mocks.storage.stat.mockRejectedValue(new Error('Could not read file')); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).not.toHaveBeenCalled(); - }); - - it('should un-trash an asset previously marked as offline', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.trashedOffline.id], - libraryId: newUuid(), - importPaths: ['/original/'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.trashedOffline]); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith([assetStub.external.id], { - isOffline: false, - deletedAt: null, - }); - }); - - it('should do nothing with offline asset if covered by exclusion pattern', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.trashedOffline.id], - libraryId: newUuid(), - importPaths: ['/original/'], - exclusionPatterns: ['**/path.jpg'], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.trashedOffline]); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).not.toHaveBeenCalled(); - - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should do nothing with offline asset if not in import path', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.trashedOffline.id], - libraryId: newUuid(), - importPaths: ['/import/'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.trashedOffline]); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).not.toHaveBeenCalled(); - - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should do nothing with unchanged online assets', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.external.id], - libraryId: newUuid(), - importPaths: ['/'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.external]); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.external.fileModifiedAt } as Stats); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).not.toHaveBeenCalled(); - }); - - it('should not touch fileCreatedAt when un-trashing an asset previously marked as offline', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.trashedOffline.id], - libraryId: newUuid(), - importPaths: ['/'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.trashedOffline]); - mocks.storage.stat.mockResolvedValue({ mtime: assetStub.trashedOffline.fileModifiedAt } as Stats); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.updateAll).toHaveBeenCalledWith( - [assetStub.trashedOffline.id], - expect.not.objectContaining({ - fileCreatedAt: expect.anything(), - }), - ); - }); - - it('should update with online assets that have changed', async () => { - const mockAssetJob: ILibraryBulkIdsJob = { - assetIds: [assetStub.external.id], - libraryId: newUuid(), - importPaths: ['/'], - exclusionPatterns: [], - totalAssets: 1, - progressCounter: 0, - }; - - if (assetStub.external.fileModifiedAt == null) { - throw new Error('fileModifiedAt is null'); - } - - const mtime = new Date(assetStub.external.fileModifiedAt.getDate() + 1); - - mocks.assetJob.getForSyncAssets.mockResolvedValue([assetStub.external]); - mocks.storage.stat.mockResolvedValue({ mtime } as Stats); - - await expect(sut.handleSyncAssets(mockAssetJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.SidecarCheck, - data: { - id: assetStub.external.id, - source: 'upload', - }, - }, - ]); - }); - }); - - describe('handleSyncFiles', () => { - beforeEach(() => { - mocks.storage.stat.mockResolvedValue({ - size: 100, - mtime: new Date('2023-01-01'), - ctime: new Date('2023-01-01'), - } as Stats); - }); - - it('should import a new asset', async () => { - const library = factory.library(); - - const mockLibraryJob: ILibraryFileJob = { - libraryId: library.id, - paths: ['/data/user1/photo.jpg'], - }; - - mocks.asset.createAll.mockResolvedValue([assetStub.image]); - mocks.library.get.mockResolvedValue(library); - - await expect(sut.handleSyncFiles(mockLibraryJob)).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.createAll).toHaveBeenCalledWith([ - expect.objectContaining({ - ownerId: library.ownerId, - libraryId: library.id, - originalPath: '/data/user1/photo.jpg', - deviceId: 'Library Import', - type: AssetType.Image, - originalFileName: 'photo.jpg', - isExternal: true, - }), - ]); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.SidecarCheck, - data: { - id: assetStub.image.id, - source: 'upload', - }, - }, - ]); - }); - - it('should not import an asset to a soft deleted library', async () => { - const library = factory.library({ deletedAt: new Date() }); - - const mockLibraryJob: ILibraryFileJob = { - libraryId: library.id, - paths: ['/data/user1/photo.jpg'], - }; - - mocks.library.get.mockResolvedValue(library); - - await expect(sut.handleSyncFiles(mockLibraryJob)).resolves.toBe(JobStatus.Failed); - - expect(mocks.asset.createAll.mock.calls).toEqual([]); - }); - }); - - describe('delete', () => { - it('should delete a library', async () => { - const library = factory.library(); - - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(library); - - await sut.delete(library.id); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.LibraryDelete, data: { id: library.id } }); - expect(mocks.library.softDelete).toHaveBeenCalledWith(library.id); - }); - - it('should allow an external library to be deleted', async () => { - const library = factory.library(); - - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(library); - - await sut.delete(library.id); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibraryDelete, - data: { id: library.id }, - }); - - expect(mocks.library.softDelete).toHaveBeenCalledWith(library.id); - }); - - it('should unwatch an external library when deleted', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - - const mockClose = vitest.fn(); - mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose })); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - await sut.delete(library.id); - - expect(mockClose).toHaveBeenCalled(); - }); - }); - - describe('get', () => { - it('should return a library', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - - await expect(sut.get(library.id)).resolves.toEqual( - expect.objectContaining({ - id: library.id, - name: library.name, - ownerId: library.ownerId, - }), - ); - - expect(mocks.library.get).toHaveBeenCalledWith(library.id); - }); - - it('should throw an error when a library is not found', async () => { - const library = factory.library(); - - await expect(sut.get(library.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.library.get).toHaveBeenCalledWith(library.id); - }); - }); - - describe('getStatistics', () => { - it('should return library statistics', async () => { - const library = factory.library(); - - mocks.library.getStatistics.mockResolvedValue({ photos: 10, videos: 0, total: 10, usage: 1337 }); - await expect(sut.getStatistics(library.id)).resolves.toEqual({ - photos: 10, - videos: 0, - total: 10, - usage: 1337, - }); - - expect(mocks.library.getStatistics).toHaveBeenCalledWith(library.id); - }); - }); - - describe('create', () => { - describe('external library', () => { - it('should create with default settings', async () => { - const library = factory.library(); - - mocks.library.create.mockResolvedValue(library); - await expect(sut.create({ ownerId: authStub.admin.user.id })).resolves.toEqual( - expect.objectContaining({ - id: library.id, - name: library.name, - ownerId: library.ownerId, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - createdAt: library.createdAt, - updatedAt: library.updatedAt, - refreshedAt: null, - }), - ); - - expect(mocks.library.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: expect.any(String), - importPaths: [], - exclusionPatterns: expect.any(Array), - }), - ); - }); - - it('should create with name', async () => { - const library = factory.library(); - - mocks.library.create.mockResolvedValue(library); - - await expect(sut.create({ ownerId: authStub.admin.user.id, name: 'My Awesome Library' })).resolves.toEqual( - expect.objectContaining({ - id: library.id, - name: library.name, - ownerId: library.ownerId, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - createdAt: library.createdAt, - updatedAt: library.updatedAt, - refreshedAt: null, - }), - ); - - expect(mocks.library.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: 'My Awesome Library', - importPaths: [], - exclusionPatterns: expect.any(Array), - }), - ); - }); - - it('should create with import paths', async () => { - const library = factory.library(); - - mocks.library.create.mockResolvedValue(library); - await expect( - sut.create({ - ownerId: authStub.admin.user.id, - importPaths: ['/data/images', '/data/videos'], - }), - ).resolves.toEqual( - expect.objectContaining({ - id: library.id, - name: library.name, - ownerId: library.ownerId, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - createdAt: library.createdAt, - updatedAt: library.updatedAt, - refreshedAt: null, - }), - ); - - expect(mocks.library.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: expect.any(String), - importPaths: ['/data/images', '/data/videos'], - exclusionPatterns: expect.any(Array), - }), - ); - }); - - it('should create watched with import paths', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.create.mockResolvedValue(library); - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([]); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - await sut.create({ ownerId: authStub.admin.user.id, importPaths: library.importPaths }); - }); - - it('should create with exclusion patterns', async () => { - const library = factory.library(); - - mocks.library.create.mockResolvedValue(library); - await expect( - sut.create({ - ownerId: authStub.admin.user.id, - exclusionPatterns: ['*.tmp', '*.bak'], - }), - ).resolves.toEqual( - expect.objectContaining({ - id: library.id, - name: library.name, - ownerId: library.ownerId, - assetCount: 0, - importPaths: [], - exclusionPatterns: [], - createdAt: library.createdAt, - updatedAt: library.updatedAt, - refreshedAt: null, - }), - ); - - expect(mocks.library.create).toHaveBeenCalledWith( - expect.objectContaining({ - name: expect.any(String), - importPaths: [], - exclusionPatterns: ['*.tmp', '*.bak'], - }), - ); - }); - }); - }); - - describe('getAll', () => { - it('should get all libraries', async () => { - const library = factory.library(); - - mocks.library.getAll.mockResolvedValue([library]); - - await expect(sut.getAll()).resolves.toEqual([expect.objectContaining({ id: library.id })]); - }); - }); - - describe('handleQueueCleanup', () => { - it('should queue cleanup jobs', async () => { - const library1 = factory.library({ deletedAt: new Date() }); - const library2 = factory.library({ deletedAt: new Date() }); - - mocks.library.getAllDeleted.mockResolvedValue([library1, library2]); - await expect(sut.handleQueueCleanup()).resolves.toBe(JobStatus.Success); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.LibraryDelete, data: { id: library1.id } }, - { name: JobName.LibraryDelete, data: { id: library2.id } }, - ]); - }); - }); - - describe('update', () => { - beforeEach(async () => { - mocks.library.getAll.mockResolvedValue([]); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - }); - - it('should throw an error if an import path is invalid', async () => { - const library = factory.library(); - - mocks.library.update.mockResolvedValue(library); - mocks.library.get.mockResolvedValue(library); - - await expect(sut.update('library-id', { importPaths: ['foo/bar'] })).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.library.update).not.toHaveBeenCalled(); - }); - - it('should update library', async () => { - const library = factory.library(); - - mocks.library.update.mockResolvedValue(library); - mocks.library.get.mockResolvedValue(library); - mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); - mocks.storage.checkFileExists.mockResolvedValue(true); - - const cwd = process.cwd(); - - await expect(sut.update('library-id', { importPaths: [`${cwd}/foo/bar`] })).resolves.toEqual(mapLibrary(library)); - expect(mocks.library.update).toHaveBeenCalledWith( - 'library-id', - expect.objectContaining({ importPaths: [`${cwd}/foo/bar`] }), - ); - }); - }); - - describe('onShutdown', () => { - it('should do nothing if instance does not have the watch lock', async () => { - await sut.onShutdown(); - }); - }); - - describe('watchAll', () => { - it('should return false if instance does not have the watch lock', async () => { - await expect(sut.watchAll()).resolves.toBe(false); - }); - - describe('watching disabled', () => { - beforeEach(async () => { - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchDisabled as SystemConfig }); - }); - - it('should not watch library', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.getAll.mockResolvedValue([library]); - - await sut.watchAll(); - - expect(mocks.storage.watch).not.toHaveBeenCalled(); - }); - }); - - describe('watching enabled', () => { - beforeEach(async () => { - mocks.library.getAll.mockResolvedValue([]); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - }); - - it('should watch library', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - - await sut.watchAll(); - - expect(mocks.storage.watch).toHaveBeenCalledWith(library.importPaths, expect.anything(), expect.anything()); - }); - - it('should watch and unwatch library', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.getAll.mockResolvedValue([library]); - mocks.library.get.mockResolvedValue(library); - const mockClose = vitest.fn(); - mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose })); - - await sut.watchAll(); - await sut.unwatch(library.id); - - expect(mockClose).toHaveBeenCalled(); - }); - - it('should not watch library without import paths', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - - await sut.watchAll(); - - expect(mocks.storage.watch).not.toHaveBeenCalled(); - }); - - it('should handle a new file event', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.storage.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.jpg' }] })); - - await sut.watchAll(); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibrarySyncFiles, - data: { - libraryId: library.id, - paths: ['/foo/photo.jpg'], - }, - }); - }); - - it('should handle a file change event', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.storage.watch.mockImplementation( - makeMockWatcher({ items: [{ event: 'change', value: '/foo/photo.jpg' }] }), - ); - - await sut.watchAll(); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibrarySyncFiles, - data: { - libraryId: library.id, - paths: ['/foo/photo.jpg'], - }, - }); - }); - - it('should handle a file unlink event', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image); - mocks.storage.watch.mockImplementation( - makeMockWatcher({ items: [{ event: 'unlink', value: assetStub.image.originalPath }] }), - ); - - await sut.watchAll(); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibraryRemoveAsset, - data: { - libraryId: library.id, - paths: [assetStub.image.originalPath], - }, - }); - }); - - it('should handle an error event', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.asset.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.external); - mocks.library.getAll.mockResolvedValue([library]); - mocks.storage.watch.mockImplementation( - makeMockWatcher({ - items: [{ event: 'error', value: 'Error!' }], - }), - ); - - await expect(sut.watchAll()).resolves.toBeUndefined(); - }); - - it('should not import a file with unknown extension', async () => { - const library = factory.library({ importPaths: ['/foo', '/bar'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - mocks.storage.watch.mockImplementation(makeMockWatcher({ items: [{ event: 'add', value: '/foo/photo.xyz' }] })); - - await sut.watchAll(); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should ignore excluded paths', async () => { - const library = factory.library({ importPaths: ['/xyz', '/asdf'], exclusionPatterns: ['**/dir1/**'] }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - mocks.storage.watch.mockImplementation( - makeMockWatcher({ items: [{ event: 'add', value: '/dir1/photo.txt' }] }), - ); - - await sut.watchAll(); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should ignore excluded paths without case sensitivity', async () => { - const library = factory.library({ - importPaths: ['/xyz', '/asdf'], - exclusionPatterns: ['**/dir1/**'], - }); - - mocks.library.get.mockResolvedValue(library); - mocks.library.getAll.mockResolvedValue([library]); - mocks.storage.watch.mockImplementation( - makeMockWatcher({ items: [{ event: 'add', value: '/DIR1/photo.txt' }] }), - ); - - await sut.watchAll(); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - }); - }); - - describe('teardown', () => { - it('should tear down all watchers', async () => { - const library1 = factory.library({ importPaths: ['/foo', '/bar'] }); - const library2 = factory.library({ importPaths: ['/xyz', '/asdf'] }); - - mocks.library.getAll.mockResolvedValue([library1, library2]); - mocks.library.get.mockImplementation((id) => - Promise.resolve([library1, library2].find((library) => library.id === id)), - ); - - const mockClose = vitest.fn(); - mocks.storage.watch.mockImplementation(makeMockWatcher({ close: mockClose })); - mocks.cron.create.mockResolvedValue(); - - await sut.onConfigInit({ newConfig: systemConfigStub.libraryWatchEnabled as SystemConfig }); - await sut.onShutdown(); - - expect(mockClose).toHaveBeenCalledTimes(2); - }); - }); - - describe('handleDeleteLibrary', () => { - it('should delete an empty library', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - mocks.library.streamAssetIds.mockReturnValue(makeStream([])); - - await expect(sut.handleDeleteLibrary({ id: library.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.library.delete).toHaveBeenCalled(); - }); - - it('should delete all assets in a library', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - mocks.library.streamAssetIds.mockReturnValue(makeStream([assetStub.image1])); - - await expect(sut.handleDeleteLibrary({ id: library.id })).resolves.toBe(JobStatus.Success); - }); - }); - - describe('queueScan', () => { - it('should queue a library scan', async () => { - const library = factory.library(); - - mocks.library.get.mockResolvedValue(library); - - await sut.queueScan(library.id); - - expect(mocks.job.queue).toHaveBeenCalledTimes(2); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibrarySyncFilesQueueAll, - data: { id: library.id }, - }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibrarySyncAssetsQueueAll, - data: { id: library.id }, - }); - }); - }); - - describe('handleQueueAllScan', () => { - it('should queue the refresh job', async () => { - const library = factory.library(); - - mocks.library.getAll.mockResolvedValue([library]); - - await expect(sut.handleQueueScanAll()).resolves.toBe(JobStatus.Success); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.LibraryDeleteCheck, - data: {}, - }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.LibrarySyncFilesQueueAll, data: { id: library.id } }, - ]); - }); - }); - - describe('validate', () => { - it('should not require import paths', async () => { - await expect(sut.validate('library-id', {})).resolves.toEqual({ importPaths: [] }); - }); - - it('should validate directory', async () => { - mocks.storage.stat.mockResolvedValue({ - isDirectory: () => true, - } as Stats); - - mocks.storage.checkFileExists.mockResolvedValue(true); - - await expect(sut.validate('library-id', { importPaths: ['/external/user1/'] })).resolves.toEqual({ - importPaths: [ - { - importPath: '/external/user1/', - isValid: true, - message: undefined, - }, - ], - }); - }); - - it('should detect when path does not exist', async () => { - mocks.storage.stat.mockImplementation(() => { - const error = { code: 'ENOENT' } as any; - throw error; - }); - - await expect(sut.validate('library-id', { importPaths: ['/external/user1/'] })).resolves.toEqual({ - importPaths: [ - { - importPath: '/external/user1/', - isValid: false, - message: 'Path does not exist (ENOENT)', - }, - ], - }); - }); - - it('should detect when path is not a directory', async () => { - mocks.storage.stat.mockResolvedValue({ - isDirectory: () => false, - } as Stats); - - await expect(sut.validate('library-id', { importPaths: ['/external/user1/file'] })).resolves.toEqual({ - importPaths: [ - { - importPath: '/external/user1/file', - isValid: false, - message: 'Not a directory', - }, - ], - }); - }); - - it('should return an unknown exception from stat', async () => { - mocks.storage.stat.mockImplementation(() => { - throw new Error('Unknown error'); - }); - - await expect(sut.validate('library-id', { importPaths: ['/external/user1/'] })).resolves.toEqual({ - importPaths: [ - { - importPath: '/external/user1/', - isValid: false, - message: 'Error: Unknown error', - }, - ], - }); - }); - - it('should detect when access rights are missing', async () => { - mocks.storage.stat.mockResolvedValue({ - isDirectory: () => true, - } as Stats); - - mocks.storage.checkFileExists.mockResolvedValue(false); - - await expect(sut.validate('library-id', { importPaths: ['/external/user1/'] })).resolves.toEqual({ - importPaths: [ - { - importPath: '/external/user1/', - isValid: false, - message: 'Lacking read permission for folder', - }, - ], - }); - }); - - it('should detect when import path is not absolute', async () => { - const cwd = process.cwd(); - - await expect(sut.validate('library-id', { importPaths: ['relative/path'] })).resolves.toEqual({ - importPaths: [ - { - importPath: 'relative/path', - isValid: false, - message: `Import path must be absolute, try ${cwd}/relative/path`, - }, - ], - }); - }); - - it('should detect when import path is in immich media folder', async () => { - const importPaths = ['/data/thumbs', `${process.cwd()}/xyz`, '/data/library']; - const library = factory.library({ importPaths }); - - mocks.storage.stat.mockResolvedValue({ isDirectory: () => true } as Stats); - - mocks.storage.checkFileExists.mockImplementation((importPath) => Promise.resolve(importPath === importPaths[1])); - - await expect(sut.validate(library.id, { importPaths })).resolves.toEqual({ - importPaths: [ - { - importPath: importPaths[0], - isValid: false, - message: 'Cannot use media upload folder for external libraries', - }, - { - importPath: importPaths[1], - isValid: true, - }, - { - importPath: importPaths[2], - isValid: false, - message: 'Cannot use media upload folder for external libraries', - }, - ], - }); - }); - }); -}); diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts deleted file mode 100644 index 841fa4743c..0000000000 --- a/server/src/services/library.service.ts +++ /dev/null @@ -1,781 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { Insertable } from 'kysely'; -import { R_OK } from 'node:constants'; -import { Stats } from 'node:fs'; -import path, { basename, isAbsolute, parse } from 'node:path'; -import picomatch from 'picomatch'; -import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent, OnJob } from 'src/decorators'; -import { - CreateLibraryDto, - LibraryResponseDto, - LibraryStatsResponseDto, - mapLibrary, - UpdateLibraryDto, - ValidateLibraryDto, - ValidateLibraryImportPathResponseDto, - ValidateLibraryResponseDto, -} from 'src/dtos/library.dto'; -import { AssetStatus, AssetType, CronJob, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { AssetSyncResult } from 'src/repositories/library.repository'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { BaseService } from 'src/services/base.service'; -import { JobOf } from 'src/types'; -import { mimeTypes } from 'src/utils/mime-types'; -import { handlePromiseError } from 'src/utils/misc'; - -@Injectable() -export class LibraryService extends BaseService { - private watchLibraries = false; - private lock = false; - private watchers: Record Promise> = {}; - - @OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] }) - async onConfigInit({ - newConfig: { - library: { watch, scan }, - }, - }: ArgOf<'ConfigInit'>) { - // This ensures that library watching only occurs in one microservice - this.lock = await this.databaseRepository.tryLock(DatabaseLock.Library); - - this.watchLibraries = this.lock && watch.enabled; - - if (this.lock) { - this.cronRepository.create({ - name: CronJob.LibraryScan, - expression: scan.cronExpression, - onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.LibraryScanQueueAll }), this.logger), - start: scan.enabled, - }); - } - - if (this.watchLibraries) { - await this.watchAll(); - } - } - - @OnEvent({ name: 'ConfigUpdate', server: true }) - async onConfigUpdate({ newConfig: { library } }: ArgOf<'ConfigUpdate'>) { - if (!this.lock) { - return; - } - - this.cronRepository.update({ - name: CronJob.LibraryScan, - expression: library.scan.cronExpression, - start: library.scan.enabled, - }); - - if (library.watch.enabled !== this.watchLibraries) { - // Watch configuration changed, update accordingly - this.watchLibraries = library.watch.enabled; - await (this.watchLibraries ? this.watchAll() : this.unwatchAll()); - } - } - - private async watch(id: string): Promise { - if (!this.watchLibraries) { - return false; - } - - const library = await this.findOrFail(id); - if (library.importPaths.length === 0) { - return false; - } - - await this.unwatch(id); - - this.logger.log(`Starting to watch library ${library.id} with import path(s) ${library.importPaths}`); - - const matcher = picomatch(`**/*{${mimeTypes.getSupportedFileExtensions().join(',')}}`, { - nocase: true, - ignore: library.exclusionPatterns, - }); - - let _resolve: () => void; - const ready$ = new Promise((resolve) => (_resolve = resolve)); - - const handler = async (event: string, path: string) => { - if (matcher(path)) { - this.logger.debug(`File ${event} event received for ${path} in library ${library.id}}`); - await this.jobRepository.queue({ - name: JobName.LibrarySyncFiles, - data: { libraryId: library.id, paths: [path] }, - }); - } else { - this.logger.verbose(`Ignoring file ${event} event for ${path} in library ${library.id}`); - } - }; - - const deletionHandler = async (path: string) => { - this.logger.debug(`File unlink event received for ${path} in library ${library.id}}`); - await this.jobRepository.queue({ - name: JobName.LibraryRemoveAsset, - data: { libraryId: library.id, paths: [path] }, - }); - }; - - this.watchers[id] = this.storageRepository.watch( - library.importPaths, - { - usePolling: false, - ignoreInitial: true, - awaitWriteFinish: { - stabilityThreshold: 5000, - pollInterval: 1000, - }, - }, - { - onReady: () => _resolve(), - onAdd: (path) => { - return handlePromiseError(handler('add', path), this.logger); - }, - onChange: (path) => { - return handlePromiseError(handler('change', path), this.logger); - }, - onUnlink: (path) => { - return handlePromiseError(deletionHandler(path), this.logger); - }, - onError: (error) => { - this.logger.error(`Library watcher for library ${library.id} encountered error: ${error}`); - }, - }, - ); - - // Wait for the watcher to initialize before returning - await ready$; - - return true; - } - - async unwatch(id: string) { - if (this.watchers[id]) { - await this.watchers[id](); - delete this.watchers[id]; - } - } - - @OnEvent({ name: 'AppShutdown' }) - async onShutdown() { - await this.unwatchAll(); - } - - private async unwatchAll() { - if (!this.lock) { - return false; - } - - for (const id in this.watchers) { - await this.unwatch(id); - } - } - - async watchAll() { - if (!this.lock) { - return false; - } - - const libraries = await this.libraryRepository.getAll(false); - for (const library of libraries) { - await this.watch(library.id); - } - } - - async getStatistics(id: string): Promise { - const statistics = await this.libraryRepository.getStatistics(id); - if (!statistics) { - throw new BadRequestException(`Library ${id} not found`); - } - return statistics; - } - - async get(id: string): Promise { - const library = await this.findOrFail(id); - return mapLibrary(library); - } - - async getAll(): Promise { - const libraries = await this.libraryRepository.getAll(false); - return libraries.map((library) => mapLibrary(library)); - } - - @OnJob({ name: JobName.LibraryDeleteCheck, queue: QueueName.Library }) - async handleQueueCleanup(): Promise { - this.logger.log('Checking for any libraries pending deletion...'); - const pendingDeletions = await this.libraryRepository.getAllDeleted(); - if (pendingDeletions.length > 0) { - const libraryString = pendingDeletions.length === 1 ? 'library' : 'libraries'; - this.logger.log(`Found ${pendingDeletions.length} ${libraryString} pending deletion, cleaning up...`); - - await this.jobRepository.queueAll( - pendingDeletions.map((libraryToDelete) => ({ name: JobName.LibraryDelete, data: { id: libraryToDelete.id } })), - ); - } - - return JobStatus.Success; - } - - async create(dto: CreateLibraryDto): Promise { - const library = await this.libraryRepository.create({ - ownerId: dto.ownerId, - name: dto.name ?? 'New External Library', - importPaths: dto.importPaths ?? [], - exclusionPatterns: dto.exclusionPatterns ?? [ - '**/@eaDir/**', - '**/._*', - '**/#recycle/**', - '**/#snapshot/**', - '**/.stversions/**', - '**/.stfolder/**', - ], - }); - return mapLibrary(library); - } - - @OnJob({ name: JobName.LibrarySyncFiles, queue: QueueName.Library }) - async handleSyncFiles(job: JobOf): Promise { - const library = await this.libraryRepository.get(job.libraryId); - // We need to check if the library still exists as it could have been deleted after the scan was queued - if (!library) { - this.logger.debug(`Library ${job.libraryId} not found, skipping file import`); - return JobStatus.Failed; - } else if (library.deletedAt) { - this.logger.debug(`Library ${job.libraryId} is deleted, won't import assets into it`); - return JobStatus.Failed; - } - - const assetImports: Insertable[] = []; - await Promise.all( - job.paths.map((path) => - this.processEntity(path, library.ownerId, job.libraryId) - .then((asset) => assetImports.push(asset)) - .catch((error: any) => this.logger.error(`Error processing ${path} for library ${job.libraryId}: ${error}`)), - ), - ); - - const assetIds: string[] = []; - - for (let i = 0; i < assetImports.length; i += 5000) { - // Chunk the imports to avoid the postgres limit of max parameters at once - const chunk = assetImports.slice(i, i + 5000); - await this.assetRepository.createAll(chunk).then((assets) => assetIds.push(...assets.map((asset) => asset.id))); - } - - const progressMessage = - job.progressCounter && job.totalAssets - ? `(${job.progressCounter} of ${job.totalAssets})` - : `(${job.progressCounter} done so far)`; - - this.logger.log(`Imported ${assetIds.length} ${progressMessage} file(s) into library ${job.libraryId}`); - - await this.queuePostSyncJobs(assetIds); - - return JobStatus.Success; - } - - private async validateImportPath(importPath: string): Promise { - const validation = new ValidateLibraryImportPathResponseDto(); - validation.importPath = importPath; - - if (StorageCore.isImmichPath(importPath)) { - validation.message = 'Cannot use media upload folder for external libraries'; - return validation; - } - - if (!isAbsolute(importPath)) { - validation.message = `Import path must be absolute, try ${path.resolve(importPath)}`; - return validation; - } - - try { - const stat = await this.storageRepository.stat(importPath); - if (!stat.isDirectory()) { - validation.message = 'Not a directory'; - return validation; - } - } catch (error: any) { - if (error.code === 'ENOENT') { - validation.message = 'Path does not exist (ENOENT)'; - return validation; - } - validation.message = String(error); - return validation; - } - - const access = await this.storageRepository.checkFileExists(importPath, R_OK); - - if (!access) { - validation.message = 'Lacking read permission for folder'; - return validation; - } - - validation.isValid = true; - return validation; - } - - async validate(id: string, dto: ValidateLibraryDto): Promise { - const importPaths = await Promise.all( - (dto.importPaths || []).map((importPath) => this.validateImportPath(importPath)), - ); - return { importPaths }; - } - - async update(id: string, dto: UpdateLibraryDto): Promise { - await this.findOrFail(id); - - if (dto.importPaths) { - const validation = await this.validate(id, { importPaths: dto.importPaths }); - if (validation.importPaths) { - for (const path of validation.importPaths) { - if (!path.isValid) { - throw new BadRequestException(`Invalid import path: ${path.message}`); - } - } - } - } - - const library = await this.libraryRepository.update(id, dto); - return mapLibrary(library); - } - - async delete(id: string) { - await this.findOrFail(id); - - if (this.watchLibraries) { - await this.unwatch(id); - } - - await this.libraryRepository.softDelete(id); - await this.jobRepository.queue({ name: JobName.LibraryDelete, data: { id } }); - } - - @OnJob({ name: JobName.LibraryDelete, queue: QueueName.Library }) - async handleDeleteLibrary(job: JobOf): Promise { - const libraryId = job.id; - - await this.assetRepository.updateByLibraryId(libraryId, { deletedAt: new Date() }); - - let assetsFound = false; - let chunk: string[] = []; - - const queueChunk = async () => { - if (chunk.length > 0) { - assetsFound = true; - this.logger.debug(`Queueing deletion of ${chunk.length} asset(s) in library ${libraryId}`); - await this.jobRepository.queueAll( - chunk.map((id) => ({ name: JobName.AssetDelete, data: { id, deleteOnDisk: false } })), - ); - chunk = []; - } - }; - - this.logger.debug(`Will delete all assets in library ${libraryId}`); - const assets = this.libraryRepository.streamAssetIds(libraryId); - for await (const asset of assets) { - chunk.push(asset.id); - - if (chunk.length >= JOBS_LIBRARY_PAGINATION_SIZE) { - await queueChunk(); - } - } - - await queueChunk(); - - if (!assetsFound) { - this.logger.log(`Deleting library ${libraryId}`); - await this.libraryRepository.delete(libraryId); - } - - return JobStatus.Success; - } - - private async processEntity(filePath: string, ownerId: string, libraryId: string) { - const assetPath = path.normalize(filePath); - const stat = await this.storageRepository.stat(assetPath); - - return { - ownerId, - libraryId, - checksum: this.cryptoRepository.hashSha1(`path:${assetPath}`), - originalPath: assetPath, - - fileCreatedAt: stat.mtime, - fileModifiedAt: stat.mtime, - localDateTime: stat.mtime, - // TODO: device asset id is deprecated, remove it - deviceAssetId: `${basename(assetPath)}`.replaceAll(/\s+/g, ''), - deviceId: 'Library Import', - type: mimeTypes.isVideo(assetPath) ? AssetType.Video : AssetType.Image, - originalFileName: parse(assetPath).base, - isExternal: true, - livePhotoVideoId: null, - }; - } - - async queuePostSyncJobs(assetIds: string[]) { - this.logger.debug(`Queuing sidecar discovery for ${assetIds.length} asset(s)`); - - // We queue a sidecar discovery which, in turn, queues metadata extraction - await this.jobRepository.queueAll( - assetIds.map((assetId) => ({ - name: JobName.SidecarCheck, - data: { id: assetId, source: 'upload' }, - })), - ); - } - - async queueScan(id: string) { - await this.findOrFail(id); - - this.logger.log(`Starting to scan library ${id}`); - - await this.jobRepository.queue({ - name: JobName.LibrarySyncFilesQueueAll, - data: { - id, - }, - }); - - await this.jobRepository.queue({ name: JobName.LibrarySyncAssetsQueueAll, data: { id } }); - } - - async queueScanAll() { - await this.jobRepository.queue({ name: JobName.LibraryScanQueueAll, data: {} }); - } - - @OnJob({ name: JobName.LibraryScanQueueAll, queue: QueueName.Library }) - async handleQueueScanAll(): Promise { - this.logger.log(`Initiating scan of all external libraries...`); - - await this.jobRepository.queue({ name: JobName.LibraryDeleteCheck, data: {} }); - - const libraries = await this.libraryRepository.getAll(true); - - await this.jobRepository.queueAll( - libraries.map((library) => ({ - name: JobName.LibrarySyncFilesQueueAll, - data: { - id: library.id, - }, - })), - ); - await this.jobRepository.queueAll( - libraries.map((library) => ({ - name: JobName.LibrarySyncAssetsQueueAll, - data: { - id: library.id, - }, - })), - ); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.LibrarySyncAssets, queue: QueueName.Library }) - async handleSyncAssets(job: JobOf): Promise { - const assets = await this.assetJobRepository.getForSyncAssets(job.assetIds); - - const assetIdsToOffline: string[] = []; - const trashedAssetIdsToOffline: string[] = []; - const assetIdsToOnline: string[] = []; - const trashedAssetIdsToOnline: string[] = []; - const assetIdsToUpdate: string[] = []; - - this.logger.debug(`Checking batch of ${assets.length} existing asset(s) in library ${job.libraryId}`); - - const stats = await Promise.all( - assets.map((asset) => this.storageRepository.stat(asset.originalPath).catch(() => null)), - ); - - for (let i = 0; i < assets.length; i++) { - const asset = assets[i]; - const stat = stats[i]; - const action = this.checkExistingAsset(asset, stat); - switch (action) { - case AssetSyncResult.OFFLINE: { - if (asset.status === AssetStatus.Trashed) { - trashedAssetIdsToOffline.push(asset.id); - } else { - assetIdsToOffline.push(asset.id); - } - break; - } - case AssetSyncResult.UPDATE: { - assetIdsToUpdate.push(asset.id); - break; - } - case AssetSyncResult.CHECK_OFFLINE: { - const isInImportPath = job.importPaths.find((path) => asset.originalPath.startsWith(path)); - - if (!isInImportPath) { - this.logger.verbose( - `Offline asset ${asset.originalPath} is still not in any import path, keeping offline in library ${job.libraryId}`, - ); - break; - } - - const isExcluded = job.exclusionPatterns.some((pattern) => picomatch.isMatch(asset.originalPath, pattern)); - - if (!isExcluded) { - this.logger.debug(`Offline asset ${asset.originalPath} is now online in library ${job.libraryId}`); - if (asset.status === AssetStatus.Trashed) { - trashedAssetIdsToOnline.push(asset.id); - } else { - assetIdsToOnline.push(asset.id); - } - break; - } - - this.logger.verbose( - `Offline asset ${asset.originalPath} is in an import path but still covered by exclusion pattern, keeping offline in library ${job.libraryId}`, - ); - - break; - } - } - } - - const promises = []; - if (assetIdsToOffline.length > 0) { - promises.push(this.assetRepository.updateAll(assetIdsToOffline, { isOffline: true, deletedAt: new Date() })); - } - - if (trashedAssetIdsToOffline.length > 0) { - promises.push(this.assetRepository.updateAll(trashedAssetIdsToOffline, { isOffline: true })); - } - - if (assetIdsToOnline.length > 0) { - promises.push(this.assetRepository.updateAll(assetIdsToOnline, { isOffline: false, deletedAt: null })); - } - - if (trashedAssetIdsToOnline.length > 0) { - promises.push(this.assetRepository.updateAll(trashedAssetIdsToOnline, { isOffline: false })); - } - - if (assetIdsToUpdate.length > 0) { - promises.push(this.queuePostSyncJobs(assetIdsToUpdate)); - } - - await Promise.all(promises); - - const remainingCount = assets.length - assetIdsToOffline.length - assetIdsToUpdate.length - assetIdsToOnline.length; - const cumulativePercentage = ((100 * job.progressCounter) / job.totalAssets).toFixed(1); - this.logger.log( - `Checked existing asset(s): ${assetIdsToOffline.length + trashedAssetIdsToOffline.length} offlined, ${assetIdsToOnline.length + trashedAssetIdsToOnline.length} onlined, ${assetIdsToUpdate.length} updated, ${remainingCount} unchanged of current batch of ${assets.length} (Total progress: ${job.progressCounter} of ${job.totalAssets}, ${cumulativePercentage} %) in library ${job.libraryId}.`, - ); - - return JobStatus.Success; - } - - private checkExistingAsset( - asset: { - isOffline: boolean; - libraryId: string | null; - originalPath: string; - status: AssetStatus; - fileModifiedAt: Date; - }, - stat: Stats | null, - ): AssetSyncResult { - if (!stat) { - // File not found on disk or permission error - if (asset.isOffline) { - this.logger.verbose( - `Asset ${asset.originalPath} is still not accessible, keeping offline in library ${asset.libraryId}`, - ); - return AssetSyncResult.DO_NOTHING; - } - - this.logger.debug( - `Asset ${asset.originalPath} is no longer on disk or is inaccessible because of permissions, marking offline in library ${asset.libraryId}`, - ); - return AssetSyncResult.OFFLINE; - } - - if (asset.isOffline && asset.status !== AssetStatus.Deleted) { - // Only perform the expensive check if the asset is offline - return AssetSyncResult.CHECK_OFFLINE; - } - - if (stat.mtime.valueOf() !== asset.fileModifiedAt.valueOf()) { - this.logger.verbose(`Asset ${asset.originalPath} needs metadata extraction in library ${asset.libraryId}`); - - return AssetSyncResult.UPDATE; - } - - return AssetSyncResult.DO_NOTHING; - } - - @OnJob({ name: JobName.LibrarySyncFilesQueueAll, queue: QueueName.Library }) - async handleQueueSyncFiles(job: JobOf): Promise { - const library = await this.libraryRepository.get(job.id); - if (!library) { - this.logger.debug(`Library ${job.id} not found, skipping refresh`); - return JobStatus.Skipped; - } - - this.logger.debug(`Validating import paths for library ${library.id}...`); - - const validImportPaths: string[] = []; - - for (const importPath of library.importPaths) { - const validation = await this.validateImportPath(importPath); - if (validation.isValid) { - validImportPaths.push(path.normalize(importPath)); - } else { - this.logger.warn(`Skipping invalid import path: ${importPath}. Reason: ${validation.message}`); - } - } - - if (validImportPaths.length === 0) { - this.logger.warn(`No valid import paths found for library ${library.id}`); - - return JobStatus.Skipped; - } - - const pathsOnDisk = this.storageRepository.walk({ - pathsToCrawl: validImportPaths, - includeHidden: false, - exclusionPatterns: library.exclusionPatterns, - take: JOBS_LIBRARY_PAGINATION_SIZE, - }); - - let importCount = 0; - let crawlCount = 0; - - this.logger.log(`Starting disk crawl of ${validImportPaths.length} import path(s) for library ${library.id}...`); - - for await (const pathBatch of pathsOnDisk) { - crawlCount += pathBatch.length; - const paths = await this.assetRepository.filterNewExternalAssetPaths(library.id, pathBatch); - - if (paths.length > 0) { - importCount += paths.length; - - await this.jobRepository.queue({ - name: JobName.LibrarySyncFiles, - data: { - libraryId: library.id, - paths, - progressCounter: crawlCount, - }, - }); - } - - this.logger.log( - `Crawled ${crawlCount} file(s) so far: ${paths.length} of current batch of ${pathBatch.length} will be imported to library ${library.id}...`, - ); - } - - this.logger.log( - `Finished disk crawl, ${crawlCount} file(s) found on disk and queued ${importCount} file(s) for import into ${library.id}`, - ); - - await this.libraryRepository.update(job.id, { refreshedAt: new Date() }); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.LibraryRemoveAsset, queue: QueueName.Library }) - async handleAssetRemoval(job: JobOf): Promise { - // This is only for handling file unlink events via the file watcher - this.logger.verbose(`Deleting asset(s) ${job.paths} from library ${job.libraryId}`); - for (const assetPath of job.paths) { - const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.libraryId, assetPath); - if (asset) { - await this.assetRepository.remove(asset); - } - } - - return JobStatus.Success; - } - - @OnJob({ name: JobName.LibrarySyncAssetsQueueAll, queue: QueueName.Library }) - async handleQueueSyncAssets(job: JobOf): Promise { - const library = await this.libraryRepository.get(job.id); - if (!library) { - return JobStatus.Skipped; - } - - const assetCount = await this.assetRepository.getLibraryAssetCount(job.id); - if (!assetCount) { - this.logger.log(`Library ${library.id} is empty, no need to check assets`); - return JobStatus.Success; - } - - this.logger.log( - `Checking ${assetCount} asset(s) against import paths and exclusion patterns in library ${library.id}...`, - ); - - const offlineResult = await this.assetRepository.detectOfflineExternalAssets( - library.id, - library.importPaths, - library.exclusionPatterns, - ); - - const affectedAssetCount = Number(offlineResult.numUpdatedRows); - - this.logger.log( - `${affectedAssetCount} asset(s) out of ${assetCount} were offlined due to import paths and/or exclusion pattern(s) in library ${library.id}`, - ); - - if (affectedAssetCount === assetCount) { - return JobStatus.Success; - } - - let chunk: string[] = []; - let count = 0; - - const queueChunk = async () => { - if (chunk.length > 0) { - count += chunk.length; - - await this.jobRepository.queue({ - name: JobName.LibrarySyncAssets, - data: { - libraryId: library.id, - importPaths: library.importPaths, - exclusionPatterns: library.exclusionPatterns, - assetIds: chunk.map((id) => id), - progressCounter: count, - totalAssets: assetCount, - }, - }); - chunk = []; - - const completePercentage = ((100 * count) / assetCount).toFixed(1); - - this.logger.log( - `Queued check of ${count} of ${assetCount} (${completePercentage} %) existing asset(s) so far in library ${library.id}`, - ); - } - }; - - this.logger.log(`Scanning library ${library.id} for assets missing from disk...`); - const existingAssets = this.libraryRepository.streamAssetIds(library.id); - - for await (const asset of existingAssets) { - chunk.push(asset.id); - if (chunk.length === JOBS_LIBRARY_PAGINATION_SIZE) { - await queueChunk(); - } - } - - await queueChunk(); - - this.logger.log(`Finished queuing ${count} asset check(s) for library ${library.id}`); - - return JobStatus.Success; - } - - private async findOrFail(id: string) { - const library = await this.libraryRepository.get(id); - if (!library) { - throw new BadRequestException('Library not found'); - } - return library; - } -} diff --git a/server/src/services/maintenance.service.spec.ts b/server/src/services/maintenance.service.spec.ts deleted file mode 100644 index e598f1c71d..0000000000 --- a/server/src/services/maintenance.service.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { MaintenanceAction, SystemMetadataKey } from 'src/enum'; -import { MaintenanceService } from 'src/services/maintenance.service'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(MaintenanceService.name, () => { - let sut: MaintenanceService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(MaintenanceService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getMaintenanceMode', () => { - it('should return false if state unknown', async () => { - mocks.systemMetadata.get.mockResolvedValue(null); - - await expect(sut.getMaintenanceMode()).resolves.toEqual({ - isMaintenanceMode: false, - }); - - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should return false if disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false }); - - await expect(sut.getMaintenanceMode()).resolves.toEqual({ - isMaintenanceMode: false, - }); - - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should return true if enabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: '', - action: { action: MaintenanceAction.Start }, - }); - - await expect(sut.getMaintenanceMode()).resolves.toEqual({ - isMaintenanceMode: true, - secret: '', - action: { - action: 'start', - }, - }); - - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - }); - - describe('integrityCheck', () => { - it('generate integrity report', async () => { - mocks.storage.readdir.mockResolvedValue(['.immich', 'file1', 'file2']); - mocks.storage.readFile.mockResolvedValue(undefined as never); - mocks.storage.overwriteFile.mockRejectedValue(undefined as never); - - await expect(sut.detectPriorInstall()).resolves.toMatchInlineSnapshot(` - { - "storage": [ - { - "files": 2, - "folder": "encoded-video", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "library", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "upload", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "profile", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "thumbs", - "readable": true, - "writable": false, - }, - { - "files": 2, - "folder": "backups", - "readable": true, - "writable": false, - }, - ], - } - `); - }); - }); - - describe('startMaintenance', () => { - it('should set maintenance mode and return a secret', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false }); - - await expect( - sut.startMaintenance( - { - action: MaintenanceAction.Start, - }, - 'admin', - ), - ).resolves.toMatchObject({ - jwt: expect.any(String), - }); - - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: true, - secret: expect.stringMatching(/^\w{128}$/), - action: { - action: 'start', - }, - }); - - expect(mocks.event.emit).toHaveBeenCalledWith('AppRestart', { - isMaintenanceMode: true, - }); - }); - }); - - describe('createLoginUrl', () => { - it('should fail outside of maintenance mode without secret', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false }); - - await expect( - sut.createLoginUrl({ - username: '', - }), - ).rejects.toThrowError('Not in maintenance mode'); - }); - - it('should generate a login url with JWT', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - isMaintenanceMode: true, - secret: 'secret', - action: { - action: MaintenanceAction.Start, - }, - }); - - await expect( - sut.createLoginUrl({ - username: '', - }), - ).resolves.toEqual( - expect.stringMatching( - /^https:\/\/my.immich.app\/maintenance\?token=[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*$/, - ), - ); - - expect(mocks.systemMetadata.get).toHaveBeenCalledTimes(2); - }); - - it('should use the given secret', async () => { - await expect( - sut.createLoginUrl( - { - username: '', - }, - 'secret', - ), - ).resolves.toEqual(expect.stringMatching(/./)); - - expect(mocks.systemMetadata.get).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/server/src/services/maintenance.service.ts b/server/src/services/maintenance.service.ts deleted file mode 100644 index 8e711ef380..0000000000 --- a/server/src/services/maintenance.service.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { OnEvent } from 'src/decorators'; -import { - MaintenanceAuthDto, - MaintenanceDetectInstallResponseDto, - MaintenanceStatusResponseDto, - SetMaintenanceModeDto, -} from 'src/dtos/maintenance.dto'; -import { MaintenanceAction, SystemMetadataKey } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { MaintenanceModeState } from 'src/types'; -import { - createMaintenanceLoginUrl, - detectPriorInstall, - generateMaintenanceSecret, - signMaintenanceJwt, -} from 'src/utils/maintenance'; -import { getExternalDomain } from 'src/utils/misc'; - -/** - * This service is available outside of maintenance mode to manage maintenance mode - */ -@Injectable() -export class MaintenanceService extends BaseService { - getMaintenanceMode(): Promise { - return this.systemMetadataRepository - .get(SystemMetadataKey.MaintenanceMode) - .then((state) => state ?? { isMaintenanceMode: false }); - } - - getMaintenanceStatus(): MaintenanceStatusResponseDto { - return { - active: false, - action: MaintenanceAction.End, - }; - } - - detectPriorInstall(): Promise { - return detectPriorInstall(this.storageRepository); - } - - async startMaintenance(action: SetMaintenanceModeDto, username: string): Promise<{ jwt: string }> { - const secret = generateMaintenanceSecret(); - await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, { - isMaintenanceMode: true, - secret, - action, - }); - - await this.eventRepository.emit('AppRestart', { isMaintenanceMode: true }); - - return { - jwt: await signMaintenanceJwt(secret, { - username, - }), - }; - } - - async startRestoreFlow(): Promise<{ jwt: string }> { - const adminUser = await this.userRepository.getAdmin(); - if (adminUser) { - throw new BadRequestException('The server already has an admin'); - } - - return this.startMaintenance( - { - action: MaintenanceAction.SelectDatabaseRestore, - }, - 'admin', - ); - } - - @OnEvent({ name: 'AppRestart', server: true }) - onRestart(event: ArgOf<'AppRestart'>, ack?: (ok: 'ok') => void): void { - this.logger.log(`Restarting due to event... ${JSON.stringify(event)}`); - - ack?.('ok'); - this.appRepository.exitApp(); - } - - async createLoginUrl(auth: MaintenanceAuthDto, secret?: string): Promise { - const { server } = await this.getConfig({ withCache: true }); - const baseUrl = getExternalDomain(server); - - if (!secret) { - const state = await this.getMaintenanceMode(); - if (!state.isMaintenanceMode) { - throw new Error('Not in maintenance mode'); - } - - secret = state.secret; - } - - return await createMaintenanceLoginUrl(baseUrl, auth, secret); - } -} diff --git a/server/src/services/map.service.spec.ts b/server/src/services/map.service.spec.ts deleted file mode 100644 index 6dc56abf44..0000000000 --- a/server/src/services/map.service.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { MapService } from 'src/services/map.service'; -import { albumStub } from 'test/fixtures/album.stub'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(MapService.name, () => { - let sut: MapService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(MapService)); - }); - - describe('getMapMarkers', () => { - it('should get geo information of assets', async () => { - const asset = assetStub.withLocation; - const marker = { - id: asset.id, - lat: asset.exifInfo!.latitude!, - lon: asset.exifInfo!.longitude!, - city: asset.exifInfo!.city, - state: asset.exifInfo!.state, - country: asset.exifInfo!.country, - }; - mocks.partner.getAll.mockResolvedValue([]); - mocks.map.getMapMarkers.mockResolvedValue([marker]); - - const markers = await sut.getMapMarkers(authStub.user1, {}); - - expect(markers).toHaveLength(1); - expect(markers[0]).toEqual(marker); - }); - - it('should include partner assets', async () => { - const partner = factory.partner(); - const auth = factory.auth({ user: { id: partner.sharedWithId } }); - - const asset = assetStub.withLocation; - const marker = { - id: asset.id, - lat: asset.exifInfo!.latitude!, - lon: asset.exifInfo!.longitude!, - city: asset.exifInfo!.city, - state: asset.exifInfo!.state, - country: asset.exifInfo!.country, - }; - mocks.partner.getAll.mockResolvedValue([partner]); - mocks.map.getMapMarkers.mockResolvedValue([marker]); - - const markers = await sut.getMapMarkers(auth, { withPartners: true }); - - expect(mocks.map.getMapMarkers).toHaveBeenCalledWith( - [auth.user.id, partner.sharedById], - expect.arrayContaining([]), - { withPartners: true }, - ); - expect(markers).toHaveLength(1); - expect(markers[0]).toEqual(marker); - }); - - it('should include assets from shared albums', async () => { - const asset = assetStub.withLocation; - const marker = { - id: asset.id, - lat: asset.exifInfo!.latitude!, - lon: asset.exifInfo!.longitude!, - city: asset.exifInfo!.city, - state: asset.exifInfo!.state, - country: asset.exifInfo!.country, - }; - mocks.partner.getAll.mockResolvedValue([]); - mocks.map.getMapMarkers.mockResolvedValue([marker]); - mocks.album.getOwned.mockResolvedValue([albumStub.empty]); - mocks.album.getShared.mockResolvedValue([albumStub.sharedWithUser]); - - const markers = await sut.getMapMarkers(authStub.user1, { withSharedAlbums: true }); - - expect(markers).toHaveLength(1); - expect(markers[0]).toEqual(marker); - }); - }); - - describe('reverseGeocode', () => { - it('should reverse geocode a location', async () => { - mocks.map.reverseGeocode.mockResolvedValue({ city: 'foo', state: 'bar', country: 'baz' }); - - await expect(sut.reverseGeocode({ lat: 42, lon: 69 })).resolves.toEqual([ - { city: 'foo', state: 'bar', country: 'baz' }, - ]); - - expect(mocks.map.reverseGeocode).toHaveBeenCalledWith({ latitude: 42, longitude: 69 }); - }); - }); -}); diff --git a/server/src/services/map.service.ts b/server/src/services/map.service.ts deleted file mode 100644 index 94eca77a60..0000000000 --- a/server/src/services/map.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { MapMarkerDto, MapMarkerResponseDto, MapReverseGeocodeDto } from 'src/dtos/map.dto'; -import { BaseService } from 'src/services/base.service'; -import { getMyPartnerIds } from 'src/utils/asset.util'; - -@Injectable() -export class MapService extends BaseService { - async getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise { - const userIds = [auth.user.id]; - if (options.withPartners) { - const partnerIds = await getMyPartnerIds({ userId: auth.user.id, repository: this.partnerRepository }); - userIds.push(...partnerIds); - } - - // TODO convert to SQL join - const albumIds: string[] = []; - if (options.withSharedAlbums) { - const [ownedAlbums, sharedAlbums] = await Promise.all([ - this.albumRepository.getOwned(auth.user.id), - this.albumRepository.getShared(auth.user.id), - ]); - albumIds.push(...ownedAlbums.map((album) => album.id), ...sharedAlbums.map((album) => album.id)); - } - - return this.mapRepository.getMapMarkers(userIds, albumIds, options); - } - - async reverseGeocode(dto: MapReverseGeocodeDto) { - const { lat: latitude, lon: longitude } = dto; - // eventually this should probably return an array of results - const result = await this.mapRepository.reverseGeocode({ latitude, longitude }); - return result ? [result] : []; - } -} diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts deleted file mode 100644 index 75812e2fcb..0000000000 --- a/server/src/services/media.service.spec.ts +++ /dev/null @@ -1,3837 +0,0 @@ -import { OutputInfo } from 'sharp'; -import { SystemConfig } from 'src/config'; -import { Exif } from 'src/database'; -import { - AssetFileType, - AssetPathType, - AssetType, - AudioCodec, - Colorspace, - ExifOrientation, - ImageFormat, - JobName, - JobStatus, - RawExtractedFormat, - TranscodeHardwareAcceleration, - TranscodePolicy, - VideoCodec, -} from 'src/enum'; -import { MediaService } from 'src/services/media.service'; -import { JobCounts, RawImageInfo } from 'src/types'; -import { assetStub, previewFile } from 'test/fixtures/asset.stub'; -import { faceStub } from 'test/fixtures/face.stub'; -import { probeStub } from 'test/fixtures/media.stub'; -import { personStub, personThumbnailStub } from 'test/fixtures/person.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { factory } from 'test/small.factory'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -const filesNoFullsize = [ - factory.assetFile({ type: AssetFileType.Preview }), - factory.assetFile({ type: AssetFileType.Thumbnail }), -]; - -const fullsizeBuffer = Buffer.from('embedded image data'); -const rawBuffer = Buffer.from('raw image data'); -const extractedBuffer = Buffer.from('embedded image file'); - -describe(MediaService.name, () => { - let sut: MediaService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(MediaService)); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('handleQueueGenerateThumbnails', () => { - it('should queue all assets', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.image])); - - mocks.person.getAll.mockReturnValue(makeStream([personStub.newThumbnail])); - - await sut.handleQueueGenerateThumbnails({ force: true }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: true, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.image.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith(undefined); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.PersonGenerateThumbnail, - data: { id: personStub.newThumbnail.id }, - }, - ]); - }); - - it('should queue trashed assets when force is true', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.archived])); - mocks.person.getAll.mockReturnValue(makeStream()); - - await sut.handleQueueGenerateThumbnails({ force: true }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: true, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.trashed.id }, - }, - ]); - }); - - it('should queue archived assets when force is true', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.archived])); - mocks.person.getAll.mockReturnValue(makeStream()); - - await sut.handleQueueGenerateThumbnails({ force: true }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: true, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.archived.id }, - }, - ]); - }); - - it('should queue all people with missing thumbnail path', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.image])); - mocks.person.getAll.mockReturnValue(makeStream([personStub.noThumbnail, personStub.noThumbnail])); - mocks.person.getRandomFace.mockResolvedValueOnce(faceStub.face1); - - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - expect(mocks.person.getRandomFace).toHaveBeenCalled(); - expect(mocks.person.update).toHaveBeenCalledTimes(1); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.PersonGenerateThumbnail, - data: { - id: personStub.newThumbnail.id, - }, - }, - ]); - }); - - it('should queue all assets with missing resize path', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.noResizePath])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.image.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should queue all assets with missing webp path', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.noWebpPath])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.image.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should queue all assets with missing thumbhash', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.noThumbhash])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.image.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should queue all assets with missing fullsize when feature is enabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: true } } }); - const asset = { id: factory.uuid(), thumbhash: factory.buffer(), edits: [], files: filesNoFullsize }; - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([asset])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: true }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: asset.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should not queue assets with missing fullsize when feature is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } }); - const asset = { id: factory.uuid(), thumbhash: factory.buffer(), edits: [], files: filesNoFullsize }; - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([asset])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should queue assets with edits but missing edited thumbnails', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.withCropEdit])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetEditThumbnailGeneration, - data: { id: assetStub.withCropEdit.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should not queue assets with missing edited fullsize when feature is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } }); - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.withCropEdit])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: false }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: false, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([]); - - expect(mocks.person.getAll).toHaveBeenCalledWith({ thumbnailPath: '' }); - }); - - it('should queue assets with missing fullsize when force is true, regardless of setting', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } }); - const asset = { id: factory.uuid(), thumbhash: Buffer.from('thumbhash'), edits: [], files: filesNoFullsize }; - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([asset])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: true }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: true, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: asset.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalled(); - }); - - it('should queue both regular and edited thumbnails for assets with edits when force is true', async () => { - mocks.assetJob.streamForThumbnailJob.mockReturnValue(makeStream([assetStub.withCropEdit])); - mocks.person.getAll.mockReturnValue(makeStream()); - await sut.handleQueueGenerateThumbnails({ force: true }); - - expect(mocks.assetJob.streamForThumbnailJob).toHaveBeenCalledWith({ force: true, fullsizeEnabled: false }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetGenerateThumbnails, - data: { id: assetStub.withCropEdit.id }, - }, - { - name: JobName.AssetEditThumbnailGeneration, - data: { id: assetStub.withCropEdit.id }, - }, - ]); - - expect(mocks.person.getAll).toHaveBeenCalledWith(undefined); - }); - }); - - describe('handleQueueMigration', () => { - it('should remove empty directories and queue jobs', async () => { - mocks.assetJob.streamForMigrationJob.mockReturnValue(makeStream([assetStub.image])); - mocks.job.getJobCounts.mockResolvedValue({ active: 1, waiting: 0 } as JobCounts); - mocks.person.getAll.mockReturnValue(makeStream([personStub.withName])); - - await expect(sut.handleQueueMigration()).resolves.toBe(JobStatus.Success); - - expect(mocks.storage.removeEmptyDirs).toHaveBeenCalledTimes(2); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetFileMigration, data: { id: assetStub.image.id } }, - ]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.PersonFileMigration, data: { id: personStub.withName.id } }, - ]); - }); - }); - - describe('handleAssetMigration', () => { - it('should fail if asset does not exist', async () => { - mocks.assetJob.getForMigrationJob.mockResolvedValue(void 0); - await expect(sut.handleAssetMigration({ id: assetStub.image.id })).resolves.toBe(JobStatus.Failed); - - expect(mocks.move.getByEntity).not.toHaveBeenCalled(); - }); - - it('should move asset files', async () => { - mocks.assetJob.getForMigrationJob.mockResolvedValue(assetStub.image); - mocks.move.create.mockResolvedValue({ - entityId: assetStub.image.id, - id: 'move-id', - newPath: '/new/path', - oldPath: '/old/path', - pathType: AssetPathType.Original, - }); - - await expect(sut.handleAssetMigration({ id: assetStub.image.id })).resolves.toBe(JobStatus.Success); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: assetStub.image.id, - pathType: AssetFileType.FullSize, - oldPath: '/uploads/user-id/fullsize/path.webp', - newPath: expect.stringContaining('/data/thumbs/user-id/as/se/asset-id_fullsize.jpeg'), - }); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: assetStub.image.id, - pathType: AssetFileType.Preview, - oldPath: '/uploads/user-id/thumbs/path.jpg', - newPath: expect.stringContaining('/data/thumbs/user-id/as/se/asset-id_preview.jpeg'), - }); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: assetStub.image.id, - pathType: AssetFileType.Thumbnail, - oldPath: '/uploads/user-id/webp/path.ext', - newPath: expect.stringContaining('/data/thumbs/user-id/as/se/asset-id_thumbnail.webp'), - }); - expect(mocks.move.create).toHaveBeenCalledTimes(3); - }); - }); - - describe('handleGenerateThumbnails', () => { - let rawInfo: RawImageInfo; - - beforeEach(() => { - rawInfo = { width: 100, height: 100, channels: 3 }; - mocks.person.getFaces.mockResolvedValue([]); - mocks.ocr.getByAssetId.mockResolvedValue([]); - mocks.media.decodeImage.mockImplementation((input) => - Promise.resolve( - typeof input === 'string' - ? { data: rawBuffer, info: rawInfo as OutputInfo } // string implies original file - : { data: fullsizeBuffer, info: rawInfo as OutputInfo }, // buffer implies embedded image extracted - ), - ); - }); - - it('should skip thumbnail generation if asset not found', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(void 0); - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalledWith(); - }); - - it('should skip thumbnail generation if asset type is unknown', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ ...assetStub.image, type: 'foo' as AssetType }); - - await expect(sut.handleGenerateThumbnails({ id: assetStub.image.id })).resolves.toBe(JobStatus.Skipped); - expect(mocks.media.probe).not.toHaveBeenCalled(); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalledWith(); - }); - - it('should skip video thumbnail generation if no video stream', async () => { - mocks.media.probe.mockResolvedValue(probeStub.noVideoStreams); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await expect(sut.handleGenerateThumbnails({ id: assetStub.video.id })).rejects.toThrowError(); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalledWith(); - }); - - it('should skip invisible assets', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.livePhotoMotionAsset); - - expect(await sut.handleGenerateThumbnails({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.Skipped); - - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalledWith(); - }); - - it('should delete previous preview if different path', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { thumbnail: { format: ImageFormat.Webp } } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { - files: expect.arrayContaining([previewFile.path]), - }, - }); - }); - - it('should generate P3 thumbnails for a wide gamut image', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.image, - exifInfo: { profileDescription: 'Adobe RGB', bitsPerSample: 14 } as Exif, - }); - const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.image.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - size: 1440, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(2); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - size: 1440, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Webp, - size: 250, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - - expect(mocks.media.generateThumbhash).toHaveBeenCalledOnce(); - expect(mocks.media.generateThumbhash).toHaveBeenCalledWith(rawBuffer, { - colorspace: Colorspace.P3, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - type: AssetFileType.Preview, - path: expect.any(String), - isEdited: false, - isProgressive: false, - }, - { - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: expect.any(String), - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer }); - }); - - it('should generate a thumbnail for a video', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'], - outputOptions: [ - '-fps_mode vfr', - '-frames:v 1', - '-update 1', - '-v verbose', - String.raw`-vf fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,scale=-2:1440:flags=lanczos+accurate_rnd+full_chroma_int:out_range=pc`, - ], - twoPass: false, - }), - ); - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - type: AssetFileType.Preview, - path: expect.any(String), - isEdited: false, - isProgressive: false, - }, - { - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: expect.any(String), - isEdited: false, - isProgressive: false, - }, - ]); - }); - - it('should tonemap thumbnail for hdr video', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'], - outputOptions: [ - '-fps_mode vfr', - '-frames:v 1', - '-update 1', - '-v verbose', - String.raw`-vf fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, - ], - twoPass: false, - }), - ); - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - type: AssetFileType.Preview, - path: expect.any(String), - isEdited: false, - isProgressive: false, - }, - { - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: expect.any(String), - isEdited: false, - isProgressive: false, - }, - ]); - }); - - it('should always generate video thumbnail in one pass', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { twoPass: true, maxBitrate: '5000k' }, - }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'], - outputOptions: [ - '-fps_mode vfr', - '-frames:v 1', - '-update 1', - '-v verbose', - String.raw`-vf fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, - ], - twoPass: false, - }), - ); - }); - - it('should not skip intra frames for MTS file', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamMTS); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: ['-sws_flags accurate_rnd+full_chroma_int'], - outputOptions: expect.any(Array), - progress: expect.any(Object), - twoPass: false, - }), - ); - }); - - it('should override reserved color metadata', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamReserved); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-bsf:v hevc_metadata=colour_primaries=1:matrix_coefficients=1:transfer_characteristics=1', - ]), - outputOptions: expect.any(Array), - progress: expect.any(Object), - twoPass: false, - }), - ); - }); - - it('should use scaling divisible by 2 even when using quick sync', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([expect.stringContaining('scale=-2:1440')]), - twoPass: false, - }), - ); - }); - - it.each(Object.values(ImageFormat))('should generate an image preview in %s format', async (format) => { - mocks.systemMetadata.get.mockResolvedValue({ image: { preview: { format } } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer); - const previewPath = `/data/thumbs/user-id/as/se/asset-id_preview.${format}`; - const thumbnailPath = `/data/thumbs/user-id/as/se/asset-id_thumbnail.webp`; - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.image.originalPath, { - colorspace: Colorspace.Srgb, - processInvalidImages: false, - size: 1440, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(2); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.Srgb, - format, - size: 1440, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - previewPath, - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.Srgb, - format: ImageFormat.Webp, - size: 250, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - thumbnailPath, - ); - }); - - it.each(Object.values(ImageFormat))('should generate an image thumbnail in %s format', async (format) => { - mocks.systemMetadata.get.mockResolvedValue({ image: { thumbnail: { format } } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer); - const previewPath = expect.stringContaining(`/data/thumbs/user-id/as/se/asset-id_preview.jpeg`); - const thumbnailPath = expect.stringContaining(`/data/thumbs/user-id/as/se/asset-id_thumbnail.${format}`); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.image.originalPath, { - colorspace: Colorspace.Srgb, - processInvalidImages: false, - size: 1440, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(2); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.Srgb, - format: ImageFormat.Jpeg, - size: 1440, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - previewPath, - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.Srgb, - format, - size: 250, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - thumbnailPath, - ); - }); - - it('should generate progressive JPEG for preview when enabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - image: { preview: { progressive: true }, thumbnail: { progressive: false } }, - }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ - format: ImageFormat.Jpeg, - progressive: true, - }), - expect.stringContaining('preview.jpeg'), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ - format: ImageFormat.Webp, - progressive: false, - }), - expect.stringContaining('thumbnail.webp'), - ); - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - expect.objectContaining({ - type: AssetFileType.Preview, - isProgressive: true, - }), - expect.objectContaining({ - type: AssetFileType.Thumbnail, - isProgressive: false, - }), - ]); - }); - - it('should generate progressive JPEG for thumbnail when enabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - image: { preview: { progressive: false }, thumbnail: { format: ImageFormat.Jpeg, progressive: true } }, - }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ - format: ImageFormat.Jpeg, - progressive: false, - }), - expect.stringContaining('preview.jpeg'), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ - format: ImageFormat.Jpeg, - progressive: true, - }), - expect.stringContaining('thumbnail.jpeg'), - ); - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - expect.objectContaining({ - type: AssetFileType.Preview, - isProgressive: false, - }), - expect.objectContaining({ - type: AssetFileType.Thumbnail, - isProgressive: true, - }), - ]); - }); - - it('should never set isProgressive for videos', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - image: { preview: { progressive: true }, thumbnail: { progressive: true } }, - }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - - await sut.handleGenerateThumbnails({ id: assetStub.video.id }); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - expect.objectContaining({ - type: AssetFileType.Preview, - isProgressive: false, - }), - expect.objectContaining({ - type: AssetFileType.Thumbnail, - isProgressive: false, - }), - ]); - }); - - it('should delete previous thumbnail if different path', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { thumbnail: { format: ImageFormat.Webp } } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { - files: expect.arrayContaining([previewFile.path]), - }, - }); - }); - - it('should extract embedded image if enabled and available', async () => { - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: true } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(extractedBuffer, { - colorspace: Colorspace.P3, - processInvalidImages: false, - size: 1440, - }); - }); - - it('should resize original image if embedded image is too small', async () => { - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 1000, height: 1000 }); - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: true } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - size: 1440, - }); - }); - - it('should resize original image if embedded image not found', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: true } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - size: 1440, - }); - }); - - it('should resize original image if embedded image extraction is not enabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: false } }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.extract).not.toHaveBeenCalled(); - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - size: 1440, - }); - }); - - it('should process invalid images if enabled', async () => { - vi.stubEnv('IMMICH_PROCESS_INVALID_IMAGES', 'true'); - - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith( - assetStub.imageDng.originalPath, - expect.objectContaining({ processInvalidImages: true }), - ); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(2); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ processInvalidImages: false }), - expect.any(String), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ processInvalidImages: false }), - expect.any(String), - ); - - expect(mocks.media.generateThumbhash).toHaveBeenCalledOnce(); - expect(mocks.media.generateThumbhash).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ processInvalidImages: false }), - ); - - vi.unstubAllEnvs(); - }); - - it('should extract full-size JPEG preview from RAW', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - image: { fullsize: { enabled: true, format: ImageFormat.Webp }, extractEmbedded: true }, - }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(extractedBuffer, { - colorspace: Colorspace.P3, - processInvalidImages: false, - size: 1440, // capped to preview size as fullsize conversion is skipped - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(2); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - fullsizeBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - size: 1440, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - }); - - it('should convert full-size WEBP preview from JXL preview of RAW', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - image: { fullsize: { enabled: true, format: ImageFormat.Webp }, extractEmbedded: true }, - }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jxl }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(extractedBuffer, { - colorspace: Colorspace.P3, - processInvalidImages: false, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - fullsizeBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Webp, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - fullsizeBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - size: 1440, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - }); - - it('should generate full-size preview directly from RAW images when extractEmbedded is false', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: true }, extractEmbedded: false } }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageDng); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.imageDng.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - size: 1440, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - }); - - it('should generate full-size preview from non-web-friendly images', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: true } } }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - // HEIF/HIF image taken by cameras are not web-friendly, only has limited support on Safari. - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageHif); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.imageHif.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - }); - - it('should skip generating full-size preview for web-friendly images', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: true } } }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.image); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.image.originalPath, { - colorspace: Colorspace.Srgb, - processInvalidImages: false, - size: 1440, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(2); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - expect.stringContaining('fullsize.jpeg'), - ); - }); - - it('should always generate full-size preview from non-web-friendly panoramas', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { fullsize: { enabled: false } } }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.media.copyTagGroup.mockResolvedValue(true); - - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.panoramaTif); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.panoramaTif.originalPath, { - colorspace: Colorspace.Srgb, - orientation: undefined, - processInvalidImages: false, - size: undefined, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.Srgb, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - - expect(mocks.media.copyTagGroup).toHaveBeenCalledTimes(2); - expect(mocks.media.copyTagGroup).toHaveBeenCalledWith( - 'XMP-GPano', - assetStub.panoramaTif.originalPath, - expect.any(String), - ); - }); - - it('should respect encoding options when generating full-size preview', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - image: { fullsize: { enabled: true, format: ImageFormat.Webp, quality: 90 } }, - }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - // HEIF/HIF image taken by cameras are not web-friendly, only has limited support on Safari. - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageHif); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.decodeImage).toHaveBeenCalledOnce(); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(assetStub.imageHif.originalPath, { - colorspace: Colorspace.P3, - processInvalidImages: false, - }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - { - colorspace: Colorspace.P3, - format: ImageFormat.Webp, - quality: 90, - progressive: false, - processInvalidImages: false, - raw: rawInfo, - edits: [], - }, - expect.any(String), - ); - }); - - it('should generate progressive JPEG for fullsize when enabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - image: { fullsize: { enabled: true, format: ImageFormat.Jpeg, progressive: true } }, - }); - mocks.media.extract.mockResolvedValue({ buffer: extractedBuffer, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue({ width: 3840, height: 2160 }); - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.imageHif); - - await sut.handleGenerateThumbnails({ id: assetStub.image.id }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ - format: ImageFormat.Jpeg, - progressive: true, - }), - expect.stringContaining('fullsize.jpeg'), - ); - }); - }); - - describe('handleAssetEditThumbnailGeneration', () => { - let rawInfo: RawImageInfo; - - beforeEach(() => { - rawInfo = { width: 100, height: 100, channels: 3 }; - mocks.person.getFaces.mockResolvedValue([]); - mocks.ocr.getByAssetId.mockResolvedValue([]); - mocks.media.decodeImage.mockImplementation((input) => - Promise.resolve( - typeof input === 'string' - ? { data: rawBuffer, info: rawInfo as OutputInfo } // string implies original file - : { data: fullsizeBuffer, info: rawInfo as OutputInfo }, // buffer implies embedded image extracted - ), - ); - }); - - it('should skip videos', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue(assetStub.video); - - await expect(sut.handleAssetEditThumbnailGeneration({ id: assetStub.video.id })).resolves.toBe(JobStatus.Success); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - }); - - it('should upsert 3 edited files for edit jobs', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.withCropEdit, - }); - const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer); - mocks.person.getFaces.mockResolvedValue([]); - mocks.ocr.getByAssetId.mockResolvedValue([]); - - await sut.handleAssetEditThumbnailGeneration({ id: assetStub.image.id }); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ type: AssetFileType.FullSize, isEdited: true }), - expect.objectContaining({ type: AssetFileType.Preview, isEdited: true }), - expect.objectContaining({ type: AssetFileType.Thumbnail, isEdited: true }), - ]), - ); - }); - - it('should apply edits when generating thumbnails', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.withCropEdit, - }); - mocks.person.getFaces.mockResolvedValue([]); - mocks.ocr.getByAssetId.mockResolvedValue([]); - - await sut.handleAssetEditThumbnailGeneration({ id: assetStub.image.id }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.objectContaining({ - edits: [ - { - action: 'crop', - parameters: { height: 1152, width: 1512, x: 216, y: 1512 }, - }, - ], - }), - expect.any(String), - ); - }); - - it('should clean up edited files if an asset has no edits', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.withoutEdits, - }); - - const status = await sut.handleAssetEditThumbnailGeneration({ id: assetStub.image.id }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { - files: expect.arrayContaining([ - '/uploads/user-id/fullsize/path_edited.jpg', - '/uploads/user-id/preview/path_edited.jpg', - '/uploads/user-id/thumbnail/path_edited.jpg', - ]), - }, - }); - - expect(mocks.asset.deleteFiles).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ path: '/uploads/user-id/preview/path_edited.jpg' }), - expect.objectContaining({ path: '/uploads/user-id/thumbnail/path_edited.jpg' }), - expect.objectContaining({ path: '/uploads/user-id/fullsize/path_edited.jpg' }), - ]), - ); - - expect(status).toBe(JobStatus.Success); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - expect(mocks.asset.upsertFiles).not.toHaveBeenCalled(); - }); - - it('should generate all 3 edited files if an asset has edits', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.withCropEdit, - }); - mocks.person.getFaces.mockResolvedValue([]); - mocks.ocr.getByAssetId.mockResolvedValue([]); - - await sut.handleAssetEditThumbnailGeneration({ id: assetStub.image.id }); - - expect(mocks.media.generateThumbnail).toHaveBeenCalledTimes(3); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.anything(), - expect.stringContaining('preview_edited.jpeg'), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.anything(), - expect.stringContaining('thumbnail_edited.webp'), - ); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - rawBuffer, - expect.anything(), - expect.stringContaining('fullsize_edited.jpeg'), - ); - }); - - it('should generate the original thumbhash if no edits exist', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.withoutEdits, - }); - const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer); - - await sut.handleAssetEditThumbnailGeneration({ id: assetStub.image.id, source: 'upload' }); - - expect(mocks.media.generateThumbhash).toHaveBeenCalled(); - }); - - it('should apply thumbhash if job source is edit and edits exist', async () => { - mocks.assetJob.getForGenerateThumbnailJob.mockResolvedValue({ - ...assetStub.withCropEdit, - }); - const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); - mocks.media.generateThumbhash.mockResolvedValue(thumbhashBuffer); - mocks.person.getFaces.mockResolvedValue([]); - mocks.ocr.getByAssetId.mockResolvedValue([]); - - await sut.handleAssetEditThumbnailGeneration({ id: assetStub.image.id }); - - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - thumbhash: thumbhashBuffer, - }), - ); - }); - }); - - describe('handleGeneratePersonThumbnail', () => { - it('should skip if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await expect(sut.handleGeneratePersonThumbnail({ id: 'person-1' })).resolves.toBe(JobStatus.Skipped); - expect(mocks.asset.getByIds).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should skip a person not found', async () => { - await sut.handleGeneratePersonThumbnail({ id: 'person-1' }); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - }); - - it('should skip a person without a face asset id', async () => { - mocks.person.getById.mockResolvedValue(personStub.noThumbnail); - await sut.handleGeneratePersonThumbnail({ id: 'person-1' }); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - }); - - it('should skip a person with face not found', async () => { - await sut.handleGeneratePersonThumbnail({ id: 'person-1' }); - expect(mocks.media.generateThumbnail).not.toHaveBeenCalled(); - }); - - it('should generate a thumbnail', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.newThumbnailMiddle); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 1000, height: 1000 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.person.getDataForThumbnailGenerationJob).toHaveBeenCalledWith(personStub.primaryPerson.id); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.newThumbnailMiddle.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 274, - width: 274, - x: 238, - y: 163, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', thumbnailPath: expect.any(String) }); - }); - - it('should use preview path if video', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.videoThumbnail); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 1000, height: 1000 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.person.getDataForThumbnailGenerationJob).toHaveBeenCalledWith(personStub.primaryPerson.id); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.any(String)); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.newThumbnailMiddle.previewPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 274, - width: 274, - x: 238, - y: 163, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', thumbnailPath: expect.any(String) }); - }); - - it('should generate a thumbnail without going negative', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.newThumbnailStart); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 2160, height: 3840 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.newThumbnailStart.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 510, - width: 510, - x: 0, - y: 85, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - }); - - it('should generate a thumbnail without overflowing', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.newThumbnailEnd); - mocks.person.update.mockResolvedValue(personStub.primaryPerson); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 1000, height: 1000 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.newThumbnailEnd.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 408, - width: 408, - x: 591, - y: 591, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - }); - - it('should handle negative coordinates', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.negativeCoordinate); - mocks.person.update.mockResolvedValue(personStub.primaryPerson); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 4624, height: 3080 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.negativeCoordinate.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 412, - width: 412, - x: 0, - y: 62, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - }); - - it('should handle overflowing coordinate', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.overflowingCoordinate); - mocks.person.update.mockResolvedValue(personStub.primaryPerson); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 4624, height: 3080 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.overflowingCoordinate.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 138, - width: 138, - x: 4485, - y: 94, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - }); - - it('should use embedded preview if enabled and raw image', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: true } }); - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.rawEmbeddedThumbnail); - mocks.person.update.mockResolvedValue(personStub.primaryPerson); - mocks.media.generateThumbnail.mockResolvedValue(); - const extracted = Buffer.from(''); - const data = Buffer.from(''); - const info = { width: 2160, height: 3840 } as OutputInfo; - mocks.media.extract.mockResolvedValue({ buffer: extracted, format: RawExtractedFormat.Jpeg }); - mocks.media.decodeImage.mockResolvedValue({ data, info }); - mocks.media.getImageDimensions.mockResolvedValue(info); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.extract).toHaveBeenCalledWith(personThumbnailStub.rawEmbeddedThumbnail.originalPath); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(extracted, { - colorspace: Colorspace.P3, - orientation: ExifOrientation.Horizontal, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalledWith( - data, - { - colorspace: Colorspace.P3, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - edits: [ - { - action: 'crop', - parameters: { - height: 844, - width: 844, - x: 388, - y: 730, - }, - }, - ], - raw: info, - processInvalidImages: false, - size: 250, - }, - expect.any(String), - ); - }); - - it('should not use embedded preview if enabled and not raw image', async () => { - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.newThumbnailMiddle); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 2160, height: 3840 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.extract).not.toHaveBeenCalled(); - expect(mocks.media.generateThumbnail).toHaveBeenCalled(); - }); - - it('should not use embedded preview if enabled and raw image if not exists', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: true } }); - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.rawEmbeddedThumbnail); - mocks.media.generateThumbnail.mockResolvedValue(); - const data = Buffer.from(''); - const info = { width: 2160, height: 3840 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.extract).toHaveBeenCalledWith(personThumbnailStub.rawEmbeddedThumbnail.originalPath); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.rawEmbeddedThumbnail.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalled(); - }); - - it('should not use embedded preview if enabled and raw image if low resolution', async () => { - mocks.systemMetadata.get.mockResolvedValue({ image: { extractEmbedded: true } }); - mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.rawEmbeddedThumbnail); - mocks.media.generateThumbnail.mockResolvedValue(); - const extracted = Buffer.from(''); - const data = Buffer.from(''); - const info = { width: 1000, height: 1000 } as OutputInfo; - mocks.media.decodeImage.mockResolvedValue({ data, info }); - mocks.media.extract.mockResolvedValue({ buffer: extracted, format: RawExtractedFormat.Jpeg }); - mocks.media.getImageDimensions.mockResolvedValue(info); - - await expect(sut.handleGeneratePersonThumbnail({ id: personStub.primaryPerson.id })).resolves.toBe( - JobStatus.Success, - ); - - expect(mocks.media.extract).toHaveBeenCalledWith(personThumbnailStub.rawEmbeddedThumbnail.originalPath); - expect(mocks.media.decodeImage).toHaveBeenCalledWith(personThumbnailStub.rawEmbeddedThumbnail.originalPath, { - colorspace: Colorspace.P3, - orientation: undefined, - processInvalidImages: false, - }); - expect(mocks.media.generateThumbnail).toHaveBeenCalled(); - }); - }); - - describe('handleQueueVideoConversion', () => { - it('should queue all video assets', async () => { - mocks.assetJob.streamForVideoConversion.mockReturnValue(makeStream([assetStub.video])); - mocks.person.getAll.mockReturnValue(makeStream()); - - await sut.handleQueueVideoConversion({ force: true }); - - expect(mocks.assetJob.streamForVideoConversion).toHaveBeenCalledWith(true); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetEncodeVideo, - data: { id: assetStub.video.id }, - }, - ]); - }); - - it('should queue all video assets without encoded videos', async () => { - mocks.assetJob.streamForVideoConversion.mockReturnValue(makeStream([assetStub.video])); - - await sut.handleQueueVideoConversion({}); - - expect(mocks.assetJob.streamForVideoConversion).toHaveBeenCalledWith(void 0); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetEncodeVideo, - data: { id: assetStub.video.id }, - }, - ]); - }); - }); - - describe('handleVideoConversion', () => { - beforeEach(() => { - mocks.assetJob.getForVideoConversion.mockResolvedValue(assetStub.video); - sut.videoInterfaces = { dri: ['renderD128'], mali: true }; - }); - - it('should skip transcoding if asset not found', async () => { - mocks.assetJob.getForVideoConversion.mockResolvedValue(void 0); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.probe).not.toHaveBeenCalled(); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should transcode the highest bitrate video stream', async () => { - mocks.logger.isLevelEnabled.mockReturnValue(false); - mocks.media.probe.mockResolvedValue(probeStub.multipleVideoStreams); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.probe).toHaveBeenCalledWith('/original/path.ext', { countFrames: false }); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - expect(mocks.storage.mkdirSync).toHaveBeenCalled(); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-map 0:1', '-map 0:3']), - twoPass: false, - }), - ); - }); - - it('should transcode the highest bitrate audio stream', async () => { - mocks.logger.isLevelEnabled.mockReturnValue(false); - mocks.media.probe.mockResolvedValue(probeStub.multipleAudioStreams); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.probe).toHaveBeenCalledWith('/original/path.ext', { countFrames: false }); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - expect(mocks.storage.mkdirSync).toHaveBeenCalled(); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-map 0:0', '-map 0:2']), - twoPass: false, - }), - ); - }); - - it('should skip a video without any streams', async () => { - mocks.media.probe.mockResolvedValue(probeStub.noVideoStreams); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should skip a video without any height', async () => { - mocks.media.probe.mockResolvedValue(probeStub.noHeight); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should throw an error if an unknown transcode policy is configured', async () => { - mocks.media.probe.mockResolvedValue(probeStub.noAudioStreams); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: 'foo' } } as never as SystemConfig); - - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrowError(); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should throw an error if transcoding fails and hw acceleration is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.multipleVideoStreams); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { transcode: TranscodePolicy.All, accel: TranscodeHardwareAcceleration.Disabled }, - }); - mocks.media.transcode.mockRejectedValue(new Error('Error transcoding video')); - - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).resolves.toBe(JobStatus.Failed); - expect(mocks.media.transcode).toHaveBeenCalledTimes(1); - }); - - it('should transcode when set to all', async () => { - mocks.media.probe.mockResolvedValue(probeStub.multipleVideoStreams); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.All } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should transcode when optimal and too big', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Optimal } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should transcode when policy bitrate and bitrate higher than max bitrate', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream40Mbps); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Bitrate, maxBitrate: '30M' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should transcode when max bitrate is not a number', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream40Mbps); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Bitrate, maxBitrate: 'foo' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should not scale resolution if no target resolution', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { transcode: TranscodePolicy.All, targetResolution: 'original' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining([expect.stringContaining('scale')]), - twoPass: false, - }), - ); - }); - - it('should scale horizontally when video is horizontal', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Optimal } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([expect.stringMatching(/scale(_.+)?=-2:720/)]), - twoPass: false, - }), - ); - }); - - it('should scale vertically when video is vertical', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Optimal } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([expect.stringMatching(/scale(_.+)?=720:-2/)]), - twoPass: false, - }), - ); - }); - - it('should always scale video if height is uneven', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamOddHeight); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { transcode: TranscodePolicy.All, targetResolution: 'original' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([expect.stringMatching(/scale(_.+)?=-2:354/)]), - twoPass: false, - }), - ); - }); - - it('should always scale video if width is uneven', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamOddWidth); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { transcode: TranscodePolicy.All, targetResolution: 'original' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([expect.stringMatching(/scale(_.+)?=354:-2/)]), - twoPass: false, - }), - ); - }); - - it('should copy video stream when video matches target', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { targetVideoCodec: VideoCodec.Hevc, acceptedAudioCodecs: [AudioCodec.Aac] }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v copy', '-c:a aac']), - twoPass: false, - }), - ); - }); - - it('should not include hevc tag when target is hevc and video stream is copied from a different codec', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamH264); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - targetVideoCodec: VideoCodec.Hevc, - acceptedVideoCodecs: [VideoCodec.H264, VideoCodec.Hevc], - acceptedAudioCodecs: [AudioCodec.Aac], - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining(['-tag:v hvc1']), - twoPass: false, - }), - ); - }); - - it('should include hevc tag when target is hevc and copying hevc video stream', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - targetVideoCodec: VideoCodec.Hevc, - acceptedVideoCodecs: [VideoCodec.H264, VideoCodec.Hevc], - acceptedAudioCodecs: [AudioCodec.Aac], - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v copy', '-tag:v hvc1']), - twoPass: false, - }), - ); - }); - - it('should copy audio stream when audio matches target', async () => { - mocks.media.probe.mockResolvedValue(probeStub.audioStreamAac); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Optimal } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy']), - twoPass: false, - }), - ); - }); - - it('should remux when input is not an accepted container', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamAvi); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v copy', '-c:a copy']), - twoPass: false, - }), - ); - }); - - it('should throw an exception if transcode value is invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: 'invalid' as any } }); - - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrowError(); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should not transcode if transcoding is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Disabled } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should not remux when input is not an accepted container and transcoding is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Disabled } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should not transcode if target codec is invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: 'invalid' as any } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should delete existing transcode if current policy does not require transcoding', async () => { - const asset = assetStub.hasEncodedVideo; - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Disabled } }); - mocks.assetJob.getForVideoConversion.mockResolvedValue(asset); - - await sut.handleVideoConversion({ id: asset.id }); - - expect(mocks.media.transcode).not.toHaveBeenCalled(); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: [asset.encodedVideoPath] }, - }); - }); - - it('should set max bitrate if above 0', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { maxBitrate: '4500k' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-maxrate 4500k', '-bufsize 9000k']), - twoPass: false, - }), - ); - }); - - it('should default max bitrate to kbps if no unit is provided', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { maxBitrate: '4500' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-maxrate 4500k', '-bufsize 9000k']), - twoPass: false, - }), - ); - }); - - it('should transcode in two passes for h264/h265 when enabled and max bitrate is above 0', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { twoPass: true, maxBitrate: '4500k' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-b:v 3104k', '-minrate 1552k', '-maxrate 4500k']), - twoPass: true, - }), - ); - }); - - it('should fallback to one pass for h264/h265 if two-pass is enabled but no max bitrate is set', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { twoPass: true } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy']), - twoPass: false, - }), - ); - }); - - it('should transcode by bitrate in two passes for vp9 when two pass mode and max bitrate are enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - maxBitrate: '4500k', - twoPass: true, - targetVideoCodec: VideoCodec.Vp9, - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-b:v 3104k', '-minrate 1552k', '-maxrate 4500k']), - twoPass: true, - }), - ); - }); - - it('should transcode by crf in two passes for vp9 when two pass mode is enabled and max bitrate is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - maxBitrate: '0', - twoPass: true, - targetVideoCodec: VideoCodec.Vp9, - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-maxrate')]), - twoPass: true, - }), - ); - }); - - it('should configure preset for vp9', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: VideoCodec.Vp9, preset: 'slow' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-cpu-used 2']), - twoPass: false, - }), - ); - }); - - it('should not configure preset for vp9 if invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { preset: 'invalid', targetVideoCodec: VideoCodec.Vp9 } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-cpu-used')]), - twoPass: false, - }), - ); - }); - - it('should configure threads if above 0', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: VideoCodec.Vp9, threads: 2 } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-threads 2']), - twoPass: false, - }), - ); - }); - - it('should disable thread pooling for h264 if thread limit is 1', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { threads: 1 } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-threads 1', '-x264-params frame-threads=1:pools=none']), - twoPass: false, - }), - ); - }); - - it('should omit thread flags for h264 if thread limit is at or below 0', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { threads: 0 } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-threads')]), - twoPass: false, - }), - ); - }); - - it('should disable thread pooling for hevc if thread limit is 1', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { threads: 1, targetVideoCodec: VideoCodec.Hevc } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v hevc', '-threads 1', '-x265-params frame-threads=1:pools=none']), - twoPass: false, - }), - ); - }); - - it('should omit thread flags for hevc if thread limit is at or below 0', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { threads: 0, targetVideoCodec: VideoCodec.Hevc } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-threads')]), - twoPass: false, - }), - ); - }); - - it('should use av1 if specified', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: VideoCodec.Av1 } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([ - '-c:v libsvtav1', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-v verbose', - '-vf scale=-2:720', - '-preset 12', - '-crf 23', - ]), - twoPass: false, - }), - ); - }); - - it('should map `veryslow` preset to 4 for av1', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: VideoCodec.Av1, preset: 'veryslow' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-preset 4']), - twoPass: false, - }), - ); - }); - - it('should set max bitrate for av1 if specified', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: VideoCodec.Av1, maxBitrate: '2M' } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-svtav1-params mbr=2M']), - twoPass: false, - }), - ); - }); - - it('should set threads for av1 if specified', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { targetVideoCodec: VideoCodec.Av1, threads: 4 } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-svtav1-params lp=4']), - twoPass: false, - }), - ); - }); - - it('should set both bitrate and threads for av1 if specified', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { targetVideoCodec: VideoCodec.Av1, threads: 4, maxBitrate: '2M' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-svtav1-params lp=4:mbr=2M']), - twoPass: false, - }), - ); - }); - - it('should skip transcoding for audioless videos with optimal policy if video codec is correct', async () => { - mocks.media.probe.mockResolvedValue(probeStub.noAudioStreams); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - targetVideoCodec: VideoCodec.Hevc, - transcode: TranscodePolicy.Optimal, - targetResolution: '1080p', - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should fail if hwaccel is enabled for an unsupported codec', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, targetVideoCodec: VideoCodec.Vp9 }, - }); - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrowError(); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should fail if hwaccel option is invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: 'invalid' as any } }); - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrowError(); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should set options for nvenc', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.arrayContaining([ - '-tune hq', - '-qmin 0', - '-rc-lookahead 20', - '-i_qfactor 0.75', - `-c:v h264_nvenc`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-g 256', - '-v verbose', - '-vf hwupload_cuda,scale_cuda=-2:720:format=nv12', - '-preset p1', - '-cq:v 23', - ]), - twoPass: false, - }), - ); - }); - - it('should set two pass options for nvenc when enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - accel: TranscodeHardwareAcceleration.Nvenc, - maxBitrate: '10000k', - twoPass: true, - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.arrayContaining([expect.stringContaining('-multipass')]), - twoPass: false, - }), - ); - }); - - it('should set vbr options for nvenc when max bitrate is enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, maxBitrate: '10000k' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.arrayContaining(['-cq:v 23', '-maxrate 10000k', '-bufsize 6897k']), - twoPass: false, - }), - ); - }); - - it('should set cq options for nvenc when max bitrate is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, maxBitrate: '10000k' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.not.stringContaining('-maxrate'), - twoPass: false, - }), - ); - }); - - it('should omit preset for nvenc if invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, preset: 'invalid' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-preset')]), - twoPass: false, - }), - ); - }); - - it('should ignore two pass for nvenc if max bitrate is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-multipass')]), - twoPass: false, - }), - ); - }); - - it('should use hardware decoding for nvenc if enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, accelDecode: true }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-hwaccel cuda', - '-hwaccel_output_format cuda', - '-noautorotate', - '-threads 1', - ]), - outputOptions: expect.arrayContaining([expect.stringContaining('scale_cuda=-2:720:format=nv12')]), - twoPass: false, - }), - ); - }); - - it('should use hardware tone-mapping for nvenc if hardware decoding is enabled and should tone map', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, accelDecode: true }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), - outputOptions: expect.arrayContaining([ - expect.stringContaining( - 'tonemap_cuda=desat=0:matrix=bt709:primaries=bt709:range=pc:tonemap=hable:tonemap_mode=lum:transfer=bt709:peak=100:format=nv12', - ), - ]), - twoPass: false, - }), - ); - }); - - it('should set format to nv12 for nvenc if input is not yuv420p', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream10Bit); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Nvenc, accelDecode: true }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), - outputOptions: expect.arrayContaining([expect.stringContaining('scale_cuda=-2:720:format=nv12')]), - twoPass: false, - }), - ); - }); - - it('should set options for qsv', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, maxBitrate: '10000k' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', - ]), - outputOptions: expect.arrayContaining([ - `-c:v h264_qsv`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-bf 7', - '-refs 5', - '-g 256', - '-v verbose', - '-vf hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq:format=nv12', - '-preset 7', - '-global_quality:v 23', - '-maxrate 10000k', - '-bufsize 20000k', - ]), - twoPass: false, - }), - ); - }); - - it('should set options for qsv with custom dri node', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - accel: TranscodeHardwareAcceleration.Qsv, - maxBitrate: '10000k', - preferredHwDevice: '/dev/dri/renderD128', - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', - ]), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should omit preset for qsv if invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, preset: 'invalid' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', - ]), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-preset')]), - twoPass: false, - }), - ); - }); - - it('should set low power mode for qsv if target video codec is vp9', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, targetVideoCodec: VideoCodec.Vp9 }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', - ]), - outputOptions: expect.arrayContaining(['-low_power 1']), - twoPass: false, - }), - ); - }); - - it('should fail for qsv if no hw devices', async () => { - sut.videoInterfaces = { dri: [], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv } }); - - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrowError(); - - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should prefer higher index renderD* device for qsv', async () => { - sut.videoInterfaces = { dri: ['card1', 'renderD129', 'card0', 'renderD128'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD129', - '-filter_hw_device hw', - ]), - outputOptions: expect.arrayContaining([`-c:v h264_qsv`]), - twoPass: false, - }), - ); - }); - - it('should use hardware decoding for qsv if enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, accelDecode: true }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', - '-noautorotate', - '-threads 1', - '-qsv_device /dev/dri/renderD128', - ]), - outputOptions: expect.arrayContaining([expect.stringContaining('scale_qsv=-1:720:async_depth=4:mode=hq')]), - twoPass: false, - }), - ); - }); - - it('should use hardware tone-mapping for qsv if hardware decoding is enabled and should tone map', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, accelDecode: true }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', - '-threads 1', - ]), - outputOptions: expect.arrayContaining([ - expect.stringContaining( - 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:transfer=bt709:range=pc:tonemap=hable:tonemap_mode=lum:peak=100,hwmap=derive_device=qsv:reverse=1,format=qsv', - ), - ]), - twoPass: false, - }), - ); - }); - - it('should use preferred device for qsv when hardware decoding', async () => { - sut.videoInterfaces = { dri: ['renderD128', 'renderD129', 'renderD130'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, accelDecode: true, preferredHwDevice: 'renderD129' }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel qsv', '-qsv_device /dev/dri/renderD129']), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should set format to nv12 for qsv if input is not yuv420p', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream10Bit); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Qsv, accelDecode: true }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', - '-threads 1', - ]), - outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), - twoPass: false, - }), - ); - }); - - it('should set options for vaapi', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', - ]), - outputOptions: expect.arrayContaining([ - `-c:v h264_vaapi`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-g 256', - '-v verbose', - '-vf hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12', - '-compression_level 7', - '-rc_mode 1', - ]), - twoPass: false, - }), - ); - }); - - it('should set vbr options for vaapi when max bitrate is enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, maxBitrate: '10000k' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', - ]), - outputOptions: expect.arrayContaining([ - `-c:v h264_vaapi`, - '-b:v 6897k', - '-maxrate 10000k', - '-minrate 3448.5k', - '-rc_mode 3', - ]), - twoPass: false, - }), - ); - }); - - it('should set cq options for vaapi when max bitrate is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', - ]), - outputOptions: expect.arrayContaining([ - `-c:v h264_vaapi`, - '-c:a copy', - '-qp:v 23', - '-global_quality:v 23', - '-rc_mode 1', - ]), - twoPass: false, - }), - ); - }); - - it('should omit preset for vaapi if invalid', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, preset: 'invalid' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', - ]), - outputOptions: expect.not.arrayContaining([expect.stringContaining('-compression_level')]), - twoPass: false, - }), - ); - }); - - it('should prefer higher index renderD* device for vaapi', async () => { - sut.videoInterfaces = { dri: ['card1', 'renderD129', 'card0', 'renderD128'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD129', - '-filter_hw_device accel', - ]), - outputOptions: expect.arrayContaining([`-c:v h264_vaapi`]), - twoPass: false, - }), - ); - }); - - it('should select specific gpu node if selected', async () => { - sut.videoInterfaces = { dri: ['renderD129', 'card1', 'card0', 'renderD128'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, preferredHwDevice: '/dev/dri/renderD128' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', - ]), - outputOptions: expect.arrayContaining([`-c:v h264_vaapi`]), - twoPass: false, - }), - ); - }); - - it('should use hardware decoding for vaapi if enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: true }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-hwaccel vaapi', - '-hwaccel_output_format vaapi', - '-noautorotate', - '-threads 1', - '-hwaccel_device /dev/dri/renderD128', - ]), - outputOptions: expect.arrayContaining([expect.stringContaining('scale_vaapi=-2:720:mode=hq:out_range=pc')]), - twoPass: false, - }), - ); - }); - - it('should use hardware tone-mapping for vaapi if hardware decoding is enabled and should tone map', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: true }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_output_format vaapi', '-threads 1']), - outputOptions: expect.arrayContaining([ - expect.stringContaining( - 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:transfer=bt709:range=pc:tonemap=hable:tonemap_mode=lum:peak=100,hwmap=derive_device=vaapi:reverse=1,format=vaapi', - ), - ]), - twoPass: false, - }), - ); - }); - - it('should set format to nv12 for vaapi if input is not yuv420p', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream10Bit); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: true }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_output_format vaapi', '-threads 1']), - outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), - twoPass: false, - }), - ); - }); - - it('should use preferred device for vaapi when hardware decoding', async () => { - sut.videoInterfaces = { dri: ['renderD128', 'renderD129', 'renderD130'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: true, preferredHwDevice: 'renderD129' }, - }); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_device /dev/dri/renderD129']), - outputOptions: expect.any(Array), - twoPass: false, - }), - ); - }); - - it('should fallback to hw encoding and sw decoding if hw transcoding fails and hw decoding is enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: true }, - }); - mocks.media.transcode.mockRejectedValueOnce(new Error('error')); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledTimes(2); - expect(mocks.media.transcode).toHaveBeenLastCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', - ]), - outputOptions: expect.arrayContaining([`-c:v h264_vaapi`]), - twoPass: false, - }), - ); - }); - - it('should fallback to sw decoding if fallback to sw decoding + hw encoding fails', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi, accelDecode: true }, - }); - mocks.media.transcode.mockRejectedValueOnce(new Error('error')); - mocks.media.transcode.mockRejectedValueOnce(new Error('error')); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledTimes(3); - expect(mocks.media.transcode).toHaveBeenLastCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264']), - twoPass: false, - }), - ); - }); - - it('should fallback to sw transcoding if hw transcoding fails and hw decoding is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); - mocks.media.transcode.mockRejectedValueOnce(new Error('error')); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledTimes(2); - expect(mocks.media.transcode).toHaveBeenLastCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264']), - twoPass: false, - }), - ); - }); - - it('should fail for vaapi if no hw devices', async () => { - sut.videoInterfaces = { dri: [], mali: true }; - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { accel: TranscodeHardwareAcceleration.Vaapi } }); - await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrowError(); - expect(mocks.media.transcode).not.toHaveBeenCalled(); - }); - - it('should set options for rkmpp', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Rkmpp, accelDecode: true }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining([ - '-hwaccel rkmpp', - '-hwaccel_output_format drm_prime', - '-afbc rga', - '-noautorotate', - ]), - outputOptions: expect.arrayContaining([ - `-c:v h264_rkmpp`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-g 256', - '-v verbose', - '-vf scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4', - '-level 51', - '-rc_mode CQP', - '-qp_init 23', - ]), - twoPass: false, - }), - ); - }); - - it('should set vbr options for rkmpp when max bitrate is enabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVp9); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { - accel: TranscodeHardwareAcceleration.Rkmpp, - accelDecode: true, - maxBitrate: '10000k', - targetVideoCodec: VideoCodec.Hevc, - }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), - outputOptions: expect.arrayContaining([`-c:v hevc_rkmpp`, '-level 153', '-rc_mode AVBR', '-b:v 10000k']), - twoPass: false, - }), - ); - }); - - it('should set cqp options for rkmpp when max bitrate is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Rkmpp, accelDecode: true, crf: 30, maxBitrate: '0' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), - outputOptions: expect.arrayContaining([`-c:v h264_rkmpp`, '-level 51', '-rc_mode CQP', '-qp_init 30']), - twoPass: false, - }), - ); - }); - - it('should set OpenCL tonemapping options for rkmpp when OpenCL is available', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Rkmpp, accelDecode: true, crf: 30, maxBitrate: '0' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), - outputOptions: expect.arrayContaining([ - expect.stringContaining( - 'scale_rkrga=-2:720:format=p010:afbc=1:async_depth=4,hwmap=derive_device=opencl:mode=read,tonemap_opencl=format=nv12:r=pc:p=bt709:t=bt709:m=bt709:tonemap=hable:desat=0:tonemap_mode=lum:peak=100,hwmap=derive_device=rkmpp:mode=write:reverse=1,format=drm_prime', - ), - ]), - twoPass: false, - }), - ); - }); - - it('should set hardware decoding options for rkmpp when hardware decoding is enabled with no OpenCL on non-HDR file', async () => { - sut.videoInterfaces = { dri: ['renderD128'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.noAudioStreams); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Rkmpp, accelDecode: true, crf: 30, maxBitrate: '0' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), - outputOptions: expect.arrayContaining([ - expect.stringContaining('scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4'), - ]), - twoPass: false, - }), - ); - }); - - it('should use software decoding and tone-mapping if hardware decoding is disabled', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Rkmpp, accelDecode: false, crf: 30, maxBitrate: '0' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: [], - outputOptions: expect.arrayContaining([ - expect.stringContaining( - 'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', - ), - ]), - twoPass: false, - }), - ); - }); - - it('should use software tone-mapping if opencl is not available', async () => { - sut.videoInterfaces = { dri: ['renderD128'], mali: false }; - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { accel: TranscodeHardwareAcceleration.Rkmpp, accelDecode: true, crf: 30, maxBitrate: '0' }, - }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([ - expect.stringContaining( - 'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', - ), - ]), - twoPass: false, - }), - ); - }); - - it('should tonemap when policy is required and video is hdr', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Required } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([ - '-c:v h264', - '-c:a copy', - '-vf tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', - ]), - twoPass: false, - }), - ); - }); - - it('should tonemap when policy is optimal and video is hdr', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamHDR); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Optimal } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining([ - '-c:v h264', - '-c:a copy', - '-vf tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', - ]), - twoPass: false, - }), - ); - }); - - it('should transcode when policy is required and video is not yuv420p', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream10Bit); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Required } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy', '-vf format=yuv420p']), - twoPass: false, - }), - ); - }); - - it('should convert to yuv420p when scaling without tone-mapping', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream4K10Bit); - mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.Required } }); - await sut.handleVideoConversion({ id: assetStub.video.id }); - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - expect.any(String), - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy', '-vf scale=-2:720,format=yuv420p']), - twoPass: false, - }), - ); - }); - - it('should count frames for progress when log level is debug', async () => { - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - mocks.logger.isLevelEnabled.mockReturnValue(true); - - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.probe).toHaveBeenCalledWith(assetStub.video.originalPath, { countFrames: true }); - expect(mocks.media.transcode).toHaveBeenCalledWith(assetStub.video.originalPath, expect.any(String), { - inputOptions: expect.any(Array), - outputOptions: expect.any(Array), - twoPass: false, - progress: { - frameCount: probeStub.videoStream2160p.videoStreams[0].frameCount, - percentInterval: expect.any(Number), - }, - }); - }); - - it('should not count frames for progress when log level is not debug', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStream2160p); - mocks.logger.isLevelEnabled.mockReturnValue(false); - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.probe).toHaveBeenCalledWith(assetStub.video.originalPath, { countFrames: false }); - }); - - it('should process unknown audio stream', async () => { - mocks.media.probe.mockResolvedValue(probeStub.audioStreamUnknown); - mocks.asset.getByIds.mockResolvedValue([assetStub.video]); - await sut.handleVideoConversion({ id: assetStub.video.id }); - - expect(mocks.media.transcode).toHaveBeenCalledWith( - '/original/path.ext', - '/data/encoded-video/user-id/as/se/asset-id.mp4', - expect.objectContaining({ - inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:a copy']), - twoPass: false, - }), - ); - }); - }); - - describe('isSRGB', () => { - it('should return true for srgb colorspace', () => { - expect(sut.isSRGB({ colorspace: 'sRGB' } as Exif)).toEqual(true); - }); - - it('should return true for srgb profile description', () => { - expect(sut.isSRGB({ profileDescription: 'sRGB v1.31' } as Exif)).toEqual(true); - }); - - it('should return true for 8-bit image with no colorspace metadata', () => { - expect(sut.isSRGB({ bitsPerSample: 8 } as Exif)).toEqual(true); - }); - - it('should return true for image with no colorspace or bit depth metadata', () => { - expect(sut.isSRGB({} as Exif)).toEqual(true); - }); - - it('should return false for non-srgb colorspace', () => { - expect(sut.isSRGB({ colorspace: 'Adobe RGB' } as Exif)).toEqual(false); - }); - - it('should return false for non-srgb profile description', () => { - expect(sut.isSRGB({ profileDescription: 'sP3C' } as Exif)).toEqual(false); - }); - - it('should return false for 16-bit image with no colorspace metadata', () => { - expect(sut.isSRGB({ bitsPerSample: 16 } as Exif)).toEqual(false); - }); - - it('should return true for 16-bit image with sRGB colorspace', () => { - expect(sut.isSRGB({ colorspace: 'sRGB', bitsPerSample: 16 } as Exif)).toEqual(true); - }); - - it('should return true for 16-bit image with sRGB profile', () => { - expect(sut.isSRGB({ profileDescription: 'sRGB', bitsPerSample: 16 } as Exif)).toEqual(true); - }); - }); - - describe('syncFiles', () => { - it('should upsert new files when they do not exist', async () => { - const asset = { - id: 'asset-id', - files: [], - }; - - await sut['syncFiles'](asset.files, [ - { - assetId: asset.id, - type: AssetFileType.Preview, - path: '/new/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - assetId: asset.id, - type: AssetFileType.Thumbnail, - path: '/new/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - path: '/new/preview.jpg', - type: AssetFileType.Preview, - isEdited: false, - isProgressive: false, - }, - { - assetId: 'asset-id', - path: '/new/thumbnail.jpg', - type: AssetFileType.Thumbnail, - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.asset.deleteFiles).not.toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should replace existing files with new paths', async () => { - const asset = { - id: 'asset-id', - files: [ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ], - }; - - await sut['syncFiles'](asset.files, [ - { - assetId: asset.id, - type: AssetFileType.Preview, - path: '/new/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - assetId: asset.id, - type: AssetFileType.Thumbnail, - path: '/new/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - path: '/new/preview.jpg', - type: AssetFileType.Preview, - isEdited: false, - isProgressive: false, - }, - { - assetId: 'asset-id', - path: '/new/thumbnail.jpg', - type: AssetFileType.Thumbnail, - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.asset.deleteFiles).not.toHaveBeenCalled(); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: ['/old/preview.jpg', '/old/thumbnail.jpg'] }, - }); - }); - - it('should delete files when newPath is not provided', async () => { - const asset = { - id: 'asset-id', - files: [ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ], - }; - - await sut['syncFiles'](asset.files, []); - - expect(mocks.asset.upsertFiles).not.toHaveBeenCalled(); - expect(mocks.asset.deleteFiles).toHaveBeenCalledWith([ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: ['/old/preview.jpg', '/old/thumbnail.jpg'] }, - }); - }); - - it('should not make changes when file paths already match', async () => { - const asset = { - id: 'asset-id', - files: [ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/same/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/same/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ], - }; - - await sut['syncFiles'](asset.files, [ - { - assetId: asset.id, - type: AssetFileType.Preview, - path: '/same/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - assetId: asset.id, - type: AssetFileType.Thumbnail, - path: '/same/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - - expect(mocks.asset.upsertFiles).not.toHaveBeenCalled(); - expect(mocks.asset.deleteFiles).not.toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should handle mixed operations (upsert, replace, delete)', async () => { - const asset = { - id: 'asset-id', - files: [ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ], - }; - - await sut['syncFiles'](asset.files, [ - { - assetId: asset.id, - type: AssetFileType.Preview, - path: '/new/preview.jpg', - isEdited: false, - isProgressive: false, - }, // replace - { - assetId: asset.id, - type: AssetFileType.FullSize, - path: '/new/fullsize.jpg', - isEdited: false, - isProgressive: false, - }, // new - ]); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - path: '/new/preview.jpg', - type: AssetFileType.Preview, - isEdited: false, - isProgressive: false, - }, - { - assetId: 'asset-id', - path: '/new/fullsize.jpg', - type: AssetFileType.FullSize, - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.asset.deleteFiles).toHaveBeenCalledWith([ - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: ['/old/preview.jpg', '/old/thumbnail.jpg'] }, - }); - }); - - it('should handle empty file list', async () => { - const asset = { - id: 'asset-id', - files: [], - }; - - await sut['syncFiles'](asset.files, []); - - expect(mocks.asset.upsertFiles).not.toHaveBeenCalled(); - expect(mocks.asset.deleteFiles).not.toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should delete non-existent file types when newPath is not provided', async () => { - const asset = { - id: 'asset-id', - files: [ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - ], - }; - - await sut['syncFiles'](asset.files, []); - - expect(mocks.asset.upsertFiles).not.toHaveBeenCalled(); - expect(mocks.asset.deleteFiles).toHaveBeenCalledWith([ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FileDelete, - data: { files: ['/old/preview.jpg'] }, - }); - }); - - it('should update database when isProgressive changes', async () => { - const asset = { - id: 'asset-id', - files: [ - { - id: 'file-1', - assetId: 'asset-id', - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: false, - }, - { - id: 'file-2', - assetId: 'asset-id', - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ], - }; - - await sut['syncFiles'](asset.files, [ - { - assetId: asset.id, - type: AssetFileType.Preview, - path: '/old/preview.jpg', - isEdited: false, - isProgressive: true, - }, - { - assetId: asset.id, - type: AssetFileType.Thumbnail, - path: '/old/thumbnail.jpg', - isEdited: false, - isProgressive: false, - }, - ]); - - expect(mocks.asset.upsertFiles).toHaveBeenCalledWith([ - { - assetId: 'asset-id', - path: '/old/preview.jpg', - type: AssetFileType.Preview, - isEdited: false, - isProgressive: true, - }, - ]); - expect(mocks.asset.deleteFiles).not.toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts deleted file mode 100644 index 00bd0305dd..0000000000 --- a/server/src/services/media.service.ts +++ /dev/null @@ -1,876 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { SystemConfig } from 'src/config'; -import { FACE_THUMBNAIL_SIZE, JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { ImagePathOptions, StorageCore, ThumbnailPathEntity } from 'src/cores/storage.core'; -import { AssetFile, Exif } from 'src/database'; -import { OnEvent, OnJob } from 'src/decorators'; -import { AssetEditAction, CropParameters } from 'src/dtos/editing.dto'; -import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; -import { - AssetFileType, - AssetType, - AssetVisibility, - AudioCodec, - Colorspace, - ImageFormat, - JobName, - JobStatus, - LogLevel, - QueueName, - RawExtractedFormat, - StorageFolder, - TranscodeHardwareAcceleration, - TranscodePolicy, - TranscodeTarget, - VideoCodec, - VideoContainer, -} from 'src/enum'; -import { AssetJobRepository } from 'src/repositories/asset-job.repository'; -import { BoundingBox } from 'src/repositories/machine-learning.repository'; -import { BaseService } from 'src/services/base.service'; -import { - AudioStreamInfo, - DecodeToBufferOptions, - GenerateThumbnailOptions, - ImageDimensions, - JobItem, - JobOf, - VideoFormat, - VideoInterfaces, - VideoStreamInfo, -} from 'src/types'; -import { getAssetFiles, getDimensions } from 'src/utils/asset.util'; -import { checkFaceVisibility, checkOcrVisibility } from 'src/utils/editor'; -import { BaseConfig, ThumbnailConfig } from 'src/utils/media'; -import { mimeTypes } from 'src/utils/mime-types'; -import { clamp, isFaceImportEnabled, isFacialRecognitionEnabled } from 'src/utils/misc'; -import { getOutputDimensions } from 'src/utils/transform'; - -interface UpsertFileOptions { - assetId: string; - type: AssetFileType; - path: string; - isEdited: boolean; - isProgressive: boolean; -} - -type ThumbnailAsset = NonNullable>>; - -@Injectable() -export class MediaService extends BaseService { - videoInterfaces: VideoInterfaces = { dri: [], mali: false }; - - @OnEvent({ name: 'AppBootstrap' }) - async onBootstrap() { - const [dri, mali] = await Promise.all([this.getDevices(), this.hasMaliOpenCL()]); - this.videoInterfaces = { dri, mali }; - } - - @OnJob({ name: JobName.AssetGenerateThumbnailsQueueAll, queue: QueueName.ThumbnailGeneration }) - async handleQueueGenerateThumbnails({ force }: JobOf): Promise { - const config = await this.getConfig({ withCache: true }); - let jobs: JobItem[] = []; - - const queueAll = async () => { - await this.jobRepository.queueAll(jobs); - jobs = []; - }; - - const fullsizeEnabled = config.image.fullsize.enabled; - for await (const asset of this.assetJobRepository.streamForThumbnailJob({ force, fullsizeEnabled })) { - const { previewFile, thumbnailFile, fullsizeFile, editedPreviewFile, editedThumbnailFile, editedFullsizeFile } = - getAssetFiles(asset.files); - - if (force || !previewFile || !thumbnailFile || !asset.thumbhash || (fullsizeEnabled && !fullsizeFile)) { - jobs.push({ name: JobName.AssetGenerateThumbnails, data: { id: asset.id } }); - } - - if ( - asset.edits.length > 0 && - (force || !editedPreviewFile || !editedThumbnailFile || (fullsizeEnabled && !editedFullsizeFile)) - ) { - jobs.push({ name: JobName.AssetEditThumbnailGeneration, data: { id: asset.id } }); - } - - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await queueAll(); - } - } - - await queueAll(); - - const people = this.personRepository.getAll(force ? undefined : { thumbnailPath: '' }); - - for await (const person of people) { - if (!person.faceAssetId) { - const face = await this.personRepository.getRandomFace(person.id); - if (!face) { - continue; - } - - await this.personRepository.update({ id: person.id, faceAssetId: face.id }); - } - - jobs.push({ name: JobName.PersonGenerateThumbnail, data: { id: person.id } }); - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await queueAll(); - } - } - - await queueAll(); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.FileMigrationQueueAll, queue: QueueName.Migration }) - async handleQueueMigration(): Promise { - const { active, waiting } = await this.jobRepository.getJobCounts(QueueName.Migration); - if (active === 1 && waiting === 0) { - await this.storageCore.removeEmptyDirs(StorageFolder.Thumbnails); - await this.storageCore.removeEmptyDirs(StorageFolder.EncodedVideo); - } - - let jobs: JobItem[] = []; - const assets = this.assetJobRepository.streamForMigrationJob(); - for await (const asset of assets) { - jobs.push({ name: JobName.AssetFileMigration, data: { id: asset.id } }); - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(jobs); - jobs = []; - } - } - - await this.jobRepository.queueAll(jobs); - jobs = []; - - for await (const person of this.personRepository.getAll()) { - jobs.push({ name: JobName.PersonFileMigration, data: { id: person.id } }); - - if (jobs.length === JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(jobs); - jobs = []; - } - } - - await this.jobRepository.queueAll(jobs); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetFileMigration, queue: QueueName.Migration }) - async handleAssetMigration({ id }: JobOf): Promise { - const { image } = await this.getConfig({ withCache: true }); - const asset = await this.assetJobRepository.getForMigrationJob(id); - if (!asset) { - return JobStatus.Failed; - } - - await this.storageCore.moveAssetImage(asset, AssetFileType.FullSize, image.fullsize.format); - await this.storageCore.moveAssetImage(asset, AssetFileType.Preview, image.preview.format); - await this.storageCore.moveAssetImage(asset, AssetFileType.Thumbnail, image.thumbnail.format); - await this.storageCore.moveAssetVideo(asset); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetEditThumbnailGeneration, queue: QueueName.Editor }) - async handleAssetEditThumbnailGeneration({ id }: JobOf): Promise { - const asset = await this.assetJobRepository.getForGenerateThumbnailJob(id); - const config = await this.getConfig({ withCache: true }); - - if (!asset) { - this.logger.warn(`Thumbnail generation failed for asset ${id}: not found in database or missing metadata`); - return JobStatus.Failed; - } - - const generated = await this.generateEditedThumbnails(asset, config); - await this.syncFiles( - asset.files.filter((asset) => asset.isEdited), - generated?.files ?? [], - ); - - let thumbhash: Buffer | undefined = generated?.thumbhash; - if (!thumbhash) { - const extractedImage = await this.extractOriginalImage(asset, config.image); - const { info, data, colorspace } = extractedImage; - - thumbhash = await this.mediaRepository.generateThumbhash(data, { - colorspace, - processInvalidImages: false, - raw: info, - edits: [], - }); - } - - if (!asset.thumbhash || Buffer.compare(asset.thumbhash, thumbhash) !== 0) { - await this.assetRepository.update({ id: asset.id, thumbhash }); - } - - const fullsizeDimensions = generated?.fullsizeDimensions ?? getDimensions(asset.exifInfo!); - await this.assetRepository.update({ id: asset.id, ...fullsizeDimensions }); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetGenerateThumbnails, queue: QueueName.ThumbnailGeneration }) - async handleGenerateThumbnails({ id }: JobOf): Promise { - const asset = await this.assetJobRepository.getForGenerateThumbnailJob(id); - const config = await this.getConfig({ withCache: true }); - - if (!asset) { - this.logger.warn(`Thumbnail generation failed for asset ${id}: not found in database or missing metadata`); - return JobStatus.Failed; - } - - if (asset.visibility === AssetVisibility.Hidden) { - this.logger.verbose(`Thumbnail generation skipped for asset ${id}: not visible`); - return JobStatus.Skipped; - } - - let generated: Awaited>; - if (asset.type === AssetType.Video || asset.originalFileName.toLowerCase().endsWith('.gif')) { - this.logger.verbose(`Thumbnail generation for video ${id} ${asset.originalPath}`); - generated = await this.generateVideoThumbnails(asset, config); - } else if (asset.type === AssetType.Image) { - this.logger.verbose(`Thumbnail generation for image ${id} ${asset.originalPath}`); - generated = await this.generateImageThumbnails(asset, config); - } else { - this.logger.warn(`Skipping thumbnail generation for asset ${id}: ${asset.type} is not an image or video`); - return JobStatus.Skipped; - } - - const editedGenerated = await this.generateEditedThumbnails(asset, config); - if (editedGenerated) { - generated.files.push(...editedGenerated.files); - } - - await this.syncFiles(asset.files, generated.files); - const thumbhash = editedGenerated?.thumbhash || generated.thumbhash; - - if (!asset.thumbhash || Buffer.compare(asset.thumbhash, thumbhash) !== 0) { - await this.assetRepository.update({ id: asset.id, thumbhash }); - } - - return JobStatus.Success; - } - - private async extractImage(originalPath: string, minSize: number) { - let extracted = await this.mediaRepository.extract(originalPath); - if (extracted && !(await this.shouldUseExtractedImage(extracted.buffer, minSize))) { - extracted = null; - } - - return extracted; - } - - private async decodeImage(thumbSource: string | Buffer, exifInfo: Exif, targetSize?: number) { - const { image } = await this.getConfig({ withCache: true }); - const colorspace = this.isSRGB(exifInfo) ? Colorspace.Srgb : image.colorspace; - const decodeOptions: DecodeToBufferOptions = { - colorspace, - processInvalidImages: process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true', - size: targetSize, - orientation: exifInfo.orientation ? Number(exifInfo.orientation) : undefined, - }; - - const { info, data } = await this.mediaRepository.decodeImage(thumbSource, decodeOptions); - return { info, data, colorspace }; - } - - private async extractOriginalImage(asset: ThumbnailAsset, image: SystemConfig['image'], useEdits = false) { - const extractEmbedded = image.extractEmbedded && mimeTypes.isRaw(asset.originalFileName); - const extracted = extractEmbedded ? await this.extractImage(asset.originalPath, image.preview.size) : null; - const generateFullsize = - ((image.fullsize.enabled || asset.exifInfo.projectionType === 'EQUIRECTANGULAR') && - !mimeTypes.isWebSupportedImage(asset.originalPath)) || - useEdits; - const convertFullsize = generateFullsize && (!extracted || !mimeTypes.isWebSupportedImage(` .${extracted.format}`)); - - const { data, info, colorspace } = await this.decodeImage( - extracted ? extracted.buffer : asset.originalPath, - // only specify orientation to extracted images which don't have EXIF orientation data - // or it can double rotate the image - extracted ? asset.exifInfo : { ...asset.exifInfo, orientation: null }, - convertFullsize ? undefined : image.preview.size, - ); - - return { - extracted, - data, - info, - colorspace, - convertFullsize, - generateFullsize, - }; - } - - private async generateImageThumbnails(asset: ThumbnailAsset, { image }: SystemConfig, useEdits: boolean = false) { - const previewFile = this.getImageFile(asset, { - fileType: AssetFileType.Preview, - format: image.preview.format, - isEdited: useEdits, - isProgressive: !!image.preview.progressive && image.preview.format !== ImageFormat.Webp, - }); - const thumbnailFile = this.getImageFile(asset, { - fileType: AssetFileType.Thumbnail, - format: image.thumbnail.format, - isEdited: useEdits, - isProgressive: !!image.thumbnail.progressive && image.thumbnail.format !== ImageFormat.Webp, - }); - this.storageCore.ensureFolders(previewFile.path); - - // Handle embedded preview extraction for RAW files - const extractedImage = await this.extractOriginalImage(asset, image, useEdits); - const { info, data, colorspace, generateFullsize, convertFullsize, extracted } = extractedImage; - - // generate final images - const thumbnailOptions = { colorspace, processInvalidImages: false, raw: info, edits: useEdits ? asset.edits : [] }; - const promises = [ - this.mediaRepository.generateThumbhash(data, thumbnailOptions), - this.mediaRepository.generateThumbnail(data, { ...image.thumbnail, ...thumbnailOptions }, thumbnailFile.path), - this.mediaRepository.generateThumbnail(data, { ...image.preview, ...thumbnailOptions }, previewFile.path), - ]; - - let fullsizeFile: UpsertFileOptions | undefined; - if (convertFullsize) { - // convert a new fullsize image from the same source as the thumbnail - fullsizeFile = this.getImageFile(asset, { - fileType: AssetFileType.FullSize, - format: image.fullsize.format, - isEdited: useEdits, - isProgressive: !!image.fullsize.progressive && image.fullsize.format !== ImageFormat.Webp, - }); - const fullsizeOptions = { - format: image.fullsize.format, - quality: image.fullsize.quality, - progressive: image.fullsize.progressive, - ...thumbnailOptions, - }; - promises.push(this.mediaRepository.generateThumbnail(data, fullsizeOptions, fullsizeFile.path)); - } else if (generateFullsize && extracted && extracted.format === RawExtractedFormat.Jpeg) { - fullsizeFile = this.getImageFile(asset, { - fileType: AssetFileType.FullSize, - format: extracted.format, - isEdited: false, - isProgressive: !!image.fullsize.progressive && image.fullsize.format !== ImageFormat.Webp, - }); - this.storageCore.ensureFolders(fullsizeFile.path); - - // Write the buffer to disk with essential EXIF data - await this.storageRepository.createOrOverwriteFile(fullsizeFile.path, extracted.buffer); - await this.mediaRepository.writeExif( - { - orientation: asset.exifInfo.orientation, - colorspace: asset.exifInfo.colorspace, - }, - fullsizeFile.path, - ); - } - - const outputs = await Promise.all(promises); - - if (asset.exifInfo.projectionType === 'EQUIRECTANGULAR') { - const promises = [ - this.mediaRepository.copyTagGroup('XMP-GPano', asset.originalPath, previewFile.path), - fullsizeFile - ? this.mediaRepository.copyTagGroup('XMP-GPano', asset.originalPath, fullsizeFile.path) - : Promise.resolve(), - ]; - await Promise.all(promises); - } - - const decodedDimensions = { width: info.width, height: info.height }; - const fullsizeDimensions = useEdits ? getOutputDimensions(asset.edits, decodedDimensions) : decodedDimensions; - - return { - files: fullsizeFile ? [previewFile, thumbnailFile, fullsizeFile] : [previewFile, thumbnailFile], - thumbhash: outputs[0] as Buffer, - fullsizeDimensions, - }; - } - - @OnJob({ name: JobName.PersonGenerateThumbnail, queue: QueueName.ThumbnailGeneration }) - async handleGeneratePersonThumbnail({ id }: JobOf): Promise { - const { machineLearning, metadata, image } = await this.getConfig({ withCache: true }); - if (!isFacialRecognitionEnabled(machineLearning) && !isFaceImportEnabled(metadata)) { - return JobStatus.Skipped; - } - - const data = await this.personRepository.getDataForThumbnailGenerationJob(id); - if (!data) { - this.logger.error(`Could not generate person thumbnail for ${id}: missing data`); - return JobStatus.Failed; - } - - const { ownerId, x1, y1, x2, y2, oldWidth, oldHeight, exifOrientation, previewPath, originalPath } = data; - let inputImage: string | Buffer; - if (data.type === AssetType.Video) { - if (!previewPath) { - this.logger.error(`Could not generate person thumbnail for video ${id}: missing preview path`); - return JobStatus.Failed; - } - inputImage = previewPath; - } else if (image.extractEmbedded && mimeTypes.isRaw(originalPath)) { - const extracted = await this.extractImage(originalPath, image.preview.size); - inputImage = extracted ? extracted.buffer : originalPath; - } else { - inputImage = originalPath; - } - - const { data: decodedImage, info } = await this.mediaRepository.decodeImage(inputImage, { - colorspace: image.colorspace, - processInvalidImages: process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true', - // if this is an extracted image, it may not have orientation metadata - orientation: Buffer.isBuffer(inputImage) && exifOrientation ? Number(exifOrientation) : undefined, - }); - - const thumbnailPath = StorageCore.getPersonThumbnailPath({ id, ownerId }); - this.storageCore.ensureFolders(thumbnailPath); - - const thumbnailOptions: GenerateThumbnailOptions = { - colorspace: image.colorspace, - format: ImageFormat.Jpeg, - raw: info, - quality: image.thumbnail.quality, - progressive: false, - processInvalidImages: false, - size: FACE_THUMBNAIL_SIZE, - edits: [ - { - action: AssetEditAction.Crop, - parameters: this.getCrop( - { old: { width: oldWidth, height: oldHeight }, new: { width: info.width, height: info.height } }, - { x1, y1, x2, y2 }, - ), - }, - ], - }; - - await this.mediaRepository.generateThumbnail(decodedImage, thumbnailOptions, thumbnailPath); - await this.personRepository.update({ id, thumbnailPath }); - - return JobStatus.Success; - } - - private getCrop( - dims: { old: ImageDimensions; new: ImageDimensions }, - { x1, y1, x2, y2 }: BoundingBox, - ): CropParameters { - // face bounding boxes can spill outside the image dimensions - const clampedX1 = clamp(x1, 0, dims.old.width); - const clampedY1 = clamp(y1, 0, dims.old.height); - const clampedX2 = clamp(x2, 0, dims.old.width); - const clampedY2 = clamp(y2, 0, dims.old.height); - - const widthScale = dims.new.width / dims.old.width; - const heightScale = dims.new.height / dims.old.height; - - const halfWidth = (widthScale * (clampedX2 - clampedX1)) / 2; - const halfHeight = (heightScale * (clampedY2 - clampedY1)) / 2; - - const middleX = Math.round(widthScale * clampedX1 + halfWidth); - const middleY = Math.round(heightScale * clampedY1 + halfHeight); - - // zoom out 10% - const targetHalfSize = Math.floor(Math.max(halfWidth, halfHeight) * 1.1); - - // get the longest distance from the center of the image without overflowing - const newHalfSize = Math.min( - middleX - Math.max(0, middleX - targetHalfSize), - middleY - Math.max(0, middleY - targetHalfSize), - Math.min(dims.new.width - 1, middleX + targetHalfSize) - middleX, - Math.min(dims.new.height - 1, middleY + targetHalfSize) - middleY, - ); - - return { - x: middleX - newHalfSize, - y: middleY - newHalfSize, - width: newHalfSize * 2, - height: newHalfSize * 2, - }; - } - - private async generateVideoThumbnails( - asset: ThumbnailPathEntity & { originalPath: string }, - { ffmpeg, image }: SystemConfig, - ) { - const previewFile = this.getImageFile(asset, { - fileType: AssetFileType.Preview, - format: image.preview.format, - isEdited: false, - isProgressive: false, - }); - const thumbnailFile = this.getImageFile(asset, { - fileType: AssetFileType.Thumbnail, - format: image.thumbnail.format, - isEdited: false, - isProgressive: false, - }); - this.storageCore.ensureFolders(previewFile.path); - - const { format, audioStreams, videoStreams } = await this.mediaRepository.probe(asset.originalPath); - const mainVideoStream = this.getMainStream(videoStreams); - if (!mainVideoStream) { - throw new Error(`No video streams found for asset ${asset.id}`); - } - const mainAudioStream = this.getMainStream(audioStreams); - - const previewConfig = ThumbnailConfig.create({ ...ffmpeg, targetResolution: image.preview.size.toString() }); - const thumbnailConfig = ThumbnailConfig.create({ ...ffmpeg, targetResolution: image.thumbnail.size.toString() }); - const previewOptions = previewConfig.getCommand(TranscodeTarget.Video, mainVideoStream, mainAudioStream, format); - const thumbnailOptions = thumbnailConfig.getCommand( - TranscodeTarget.Video, - mainVideoStream, - mainAudioStream, - format, - ); - - await this.mediaRepository.transcode(asset.originalPath, previewFile.path, previewOptions); - await this.mediaRepository.transcode(asset.originalPath, thumbnailFile.path, thumbnailOptions); - - const thumbhash = await this.mediaRepository.generateThumbhash(previewFile.path, { - colorspace: image.colorspace, - processInvalidImages: process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true', - }); - - return { - files: [previewFile, thumbnailFile], - thumbhash, - fullsizeDimensions: { width: mainVideoStream.width, height: mainVideoStream.height }, - }; - } - - @OnJob({ name: JobName.AssetEncodeVideoQueueAll, queue: QueueName.VideoConversion }) - async handleQueueVideoConversion(job: JobOf): Promise { - const { force } = job; - - let queue: { name: JobName.AssetEncodeVideo; data: { id: string } }[] = []; - for await (const asset of this.assetJobRepository.streamForVideoConversion(force)) { - queue.push({ name: JobName.AssetEncodeVideo, data: { id: asset.id } }); - - if (queue.length >= JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(queue); - queue = []; - } - } - - await this.jobRepository.queueAll(queue); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetEncodeVideo, queue: QueueName.VideoConversion }) - async handleVideoConversion({ id }: JobOf): Promise { - const asset = await this.assetJobRepository.getForVideoConversion(id); - if (!asset) { - return JobStatus.Failed; - } - - const input = asset.originalPath; - const output = StorageCore.getEncodedVideoPath(asset); - this.storageCore.ensureFolders(output); - - const { videoStreams, audioStreams, format } = await this.mediaRepository.probe(input, { - countFrames: this.logger.isLevelEnabled(LogLevel.Debug), // makes frame count more reliable for progress logs - }); - const videoStream = this.getMainStream(videoStreams); - const audioStream = this.getMainStream(audioStreams); - if (!videoStream || !format.formatName) { - return JobStatus.Failed; - } - - if (!videoStream.height || !videoStream.width) { - this.logger.warn(`Skipped transcoding for asset ${asset.id}: no video streams found`); - return JobStatus.Failed; - } - - let { ffmpeg } = await this.getConfig({ withCache: true }); - const target = this.getTranscodeTarget(ffmpeg, videoStream, audioStream); - if (target === TranscodeTarget.None && !this.isRemuxRequired(ffmpeg, format)) { - if (asset.encodedVideoPath) { - this.logger.log(`Transcoded video exists for asset ${asset.id}, but is no longer required. Deleting...`); - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [asset.encodedVideoPath] } }); - await this.assetRepository.update({ id: asset.id, encodedVideoPath: null }); - } else { - this.logger.verbose(`Asset ${asset.id} does not require transcoding based on current policy, skipping`); - } - - return JobStatus.Skipped; - } - - const command = BaseConfig.create(ffmpeg, this.videoInterfaces).getCommand(target, videoStream, audioStream); - if (ffmpeg.accel === TranscodeHardwareAcceleration.Disabled) { - this.logger.log(`Transcoding video ${asset.id} without hardware acceleration`); - } else { - this.logger.log( - `Transcoding video ${asset.id} with ${ffmpeg.accel.toUpperCase()}-accelerated encoding and${ffmpeg.accelDecode ? '' : ' software'} decoding`, - ); - } - - try { - await this.mediaRepository.transcode(input, output, command); - } catch (error: any) { - this.logger.error(`Error occurred during transcoding: ${error.message}`); - if (ffmpeg.accel === TranscodeHardwareAcceleration.Disabled) { - return JobStatus.Failed; - } - - let partialFallbackSuccess = false; - if (ffmpeg.accelDecode) { - try { - this.logger.error(`Retrying with ${ffmpeg.accel.toUpperCase()}-accelerated encoding and software decoding`); - ffmpeg = { ...ffmpeg, accelDecode: false }; - const command = BaseConfig.create(ffmpeg, this.videoInterfaces).getCommand(target, videoStream, audioStream); - await this.mediaRepository.transcode(input, output, command); - partialFallbackSuccess = true; - } catch (error: any) { - this.logger.error(`Error occurred during transcoding: ${error.message}`); - } - } - - if (!partialFallbackSuccess) { - this.logger.error(`Retrying with ${ffmpeg.accel.toUpperCase()} acceleration disabled`); - ffmpeg = { ...ffmpeg, accel: TranscodeHardwareAcceleration.Disabled }; - const command = BaseConfig.create(ffmpeg, this.videoInterfaces).getCommand(target, videoStream, audioStream); - await this.mediaRepository.transcode(input, output, command); - } - } - - this.logger.log(`Successfully encoded ${asset.id}`); - - await this.assetRepository.update({ id: asset.id, encodedVideoPath: output }); - - return JobStatus.Success; - } - - private getMainStream(streams: T[]): T { - return streams - .filter((stream) => stream.codecName !== 'unknown') - .toSorted((stream1, stream2) => stream2.bitrate - stream1.bitrate)[0]; - } - - private getTranscodeTarget( - config: SystemConfigFFmpegDto, - videoStream: VideoStreamInfo, - audioStream?: AudioStreamInfo, - ): TranscodeTarget { - const isAudioTranscodeRequired = this.isAudioTranscodeRequired(config, audioStream); - const isVideoTranscodeRequired = this.isVideoTranscodeRequired(config, videoStream); - - if (isAudioTranscodeRequired && isVideoTranscodeRequired) { - return TranscodeTarget.All; - } - - if (isAudioTranscodeRequired) { - return TranscodeTarget.Audio; - } - - if (isVideoTranscodeRequired) { - return TranscodeTarget.Video; - } - - return TranscodeTarget.None; - } - - private isAudioTranscodeRequired(ffmpegConfig: SystemConfigFFmpegDto, stream?: AudioStreamInfo): boolean { - if (!stream) { - return false; - } - - switch (ffmpegConfig.transcode) { - case TranscodePolicy.Disabled: { - return false; - } - case TranscodePolicy.All: { - return true; - } - case TranscodePolicy.Required: - case TranscodePolicy.Optimal: - case TranscodePolicy.Bitrate: { - return !ffmpegConfig.acceptedAudioCodecs.includes(stream.codecName as AudioCodec); - } - default: { - throw new Error(`Unsupported transcode policy: ${ffmpegConfig.transcode}`); - } - } - } - - private isVideoTranscodeRequired(ffmpegConfig: SystemConfigFFmpegDto, stream: VideoStreamInfo): boolean { - const scalingEnabled = ffmpegConfig.targetResolution !== 'original'; - const targetRes = Number.parseInt(ffmpegConfig.targetResolution); - const isLargerThanTargetRes = scalingEnabled && Math.min(stream.height, stream.width) > targetRes; - const isLargerThanTargetBitrate = stream.bitrate > this.parseBitrateToBps(ffmpegConfig.maxBitrate); - - const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(stream.codecName as VideoCodec); - const isRequired = !isTargetVideoCodec || !stream.pixelFormat.endsWith('420p'); - - switch (ffmpegConfig.transcode) { - case TranscodePolicy.Disabled: { - return false; - } - case TranscodePolicy.All: { - return true; - } - case TranscodePolicy.Required: { - return isRequired; - } - case TranscodePolicy.Optimal: { - return isRequired || isLargerThanTargetRes; - } - case TranscodePolicy.Bitrate: { - return isRequired || isLargerThanTargetBitrate; - } - default: { - throw new Error(`Unsupported transcode policy: ${ffmpegConfig.transcode}`); - } - } - } - - private isRemuxRequired(ffmpegConfig: SystemConfigFFmpegDto, { formatName, formatLongName }: VideoFormat): boolean { - if (ffmpegConfig.transcode === TranscodePolicy.Disabled) { - return false; - } - - const name = formatLongName === 'QuickTime / MOV' ? VideoContainer.Mov : (formatName as VideoContainer); - return name !== VideoContainer.Mp4 && !ffmpegConfig.acceptedContainers.includes(name); - } - - isSRGB({ colorspace, profileDescription, bitsPerSample }: Exif): boolean { - if (colorspace || profileDescription) { - return [colorspace, profileDescription].some((s) => s?.toLowerCase().includes('srgb')); - } else if (bitsPerSample) { - // assume sRGB for 8-bit images with no color profile or colorspace metadata - return bitsPerSample === 8; - } else { - // assume sRGB for images with no relevant metadata - return true; - } - } - - private parseBitrateToBps(bitrateString: string) { - const bitrateValue = Number.parseInt(bitrateString); - - if (Number.isNaN(bitrateValue)) { - return 0; - } - - if (bitrateString.toLowerCase().endsWith('k')) { - return bitrateValue * 1000; // Kilobits per second to bits per second - } else if (bitrateString.toLowerCase().endsWith('m')) { - return bitrateValue * 1_000_000; // Megabits per second to bits per second - } else { - return bitrateValue; - } - } - - private async shouldUseExtractedImage(extractedPathOrBuffer: string | Buffer, targetSize: number) { - const { width, height } = await this.mediaRepository.getImageDimensions(extractedPathOrBuffer); - const extractedSize = Math.min(width, height); - return extractedSize >= targetSize; - } - - private async getDevices() { - try { - return await this.storageRepository.readdir('/dev/dri'); - } catch { - this.logger.debug('No devices found in /dev/dri.'); - return []; - } - } - - private async hasMaliOpenCL() { - try { - const [maliIcdStat, maliDeviceStat] = await Promise.all([ - this.storageRepository.stat('/etc/OpenCL/vendors/mali.icd'), - this.storageRepository.stat('/dev/mali0'), - ]); - return maliIcdStat.isFile() && maliDeviceStat.isCharacterDevice(); - } catch { - this.logger.debug('OpenCL not available for transcoding, so RKMPP acceleration will use CPU tonemapping'); - return false; - } - } - - private async syncFiles(oldFiles: (AssetFile & { isProgressive: boolean })[], newFiles: UpsertFileOptions[]) { - const toUpsert: UpsertFileOptions[] = []; - const pathsToDelete: string[] = []; - const toDelete = new Set(oldFiles); - - for (const newFile of newFiles) { - const existingFile = oldFiles.find((file) => file.type === newFile.type && file.isEdited === newFile.isEdited); - if (existingFile) { - toDelete.delete(existingFile); - } - - // upsert new file path - if (existingFile?.path !== newFile.path || existingFile.isProgressive !== newFile.isProgressive) { - toUpsert.push(newFile); - - // delete old file from disk - if (existingFile && existingFile.path !== newFile.path) { - this.logger.debug( - `Deleting old ${newFile.type} image for asset ${newFile.assetId} in favor of a replacement`, - ); - pathsToDelete.push(existingFile.path); - } - } - } - - if (toUpsert.length > 0) { - await this.assetRepository.upsertFiles(toUpsert); - } - - if (toDelete.size > 0) { - const toDeleteArray = [...toDelete]; - for (const file of toDeleteArray) { - pathsToDelete.push(file.path); - } - await this.assetRepository.deleteFiles(toDeleteArray); - } - - if (pathsToDelete.length > 0) { - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: pathsToDelete } }); - } - } - - private async generateEditedThumbnails(asset: ThumbnailAsset, config: SystemConfig) { - if (asset.type !== AssetType.Image || (asset.files.length === 0 && asset.edits.length === 0)) { - return; - } - - const generated = asset.edits.length > 0 ? await this.generateImageThumbnails(asset, config, true) : undefined; - - const crop = asset.edits.find((e) => e.action === AssetEditAction.Crop); - const cropBox = crop - ? { - x1: crop.parameters.x, - y1: crop.parameters.y, - x2: crop.parameters.x + crop.parameters.width, - y2: crop.parameters.y + crop.parameters.height, - } - : undefined; - - const originalDimensions = getDimensions(asset.exifInfo!); - const assetFaces = await this.personRepository.getFaces(asset.id, {}); - const ocrData = await this.ocrRepository.getByAssetId(asset.id, {}); - - const faceStatuses = checkFaceVisibility(assetFaces, originalDimensions, cropBox); - await this.personRepository.updateVisibility(faceStatuses.visible, faceStatuses.hidden); - - const ocrStatuses = checkOcrVisibility(ocrData, originalDimensions, cropBox); - await this.ocrRepository.updateOcrVisibilities(asset.id, ocrStatuses.visible, ocrStatuses.hidden); - - return generated; - } - - private getImageFile(asset: ThumbnailPathEntity, options: ImagePathOptions & { isProgressive: boolean }) { - const path = StorageCore.getImagePath(asset, options); - return { - assetId: asset.id, - type: options.fileType, - path, - isEdited: options.isEdited, - isProgressive: options.isProgressive, - }; - } -} diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts deleted file mode 100644 index 44929f2bbf..0000000000 --- a/server/src/services/memory.service.spec.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { MemoryService } from 'src/services/memory.service'; -import { OnThisDayData } from 'src/types'; -import { factory, newUuid, newUuids } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(MemoryService.name, () => { - let sut: MemoryService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(MemoryService)); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('onMemoryCleanup', () => { - it('should clean up memories', async () => { - mocks.memory.cleanup.mockResolvedValue([]); - await sut.onMemoriesCleanup(); - expect(mocks.memory.cleanup).toHaveBeenCalled(); - }); - }); - - describe('search', () => { - it('should search memories', async () => { - const [userId] = newUuids(); - const asset = factory.asset(); - const memory1 = factory.memory({ ownerId: userId, assets: [asset] }); - const memory2 = factory.memory({ ownerId: userId }); - - mocks.memory.search.mockResolvedValue([memory1, memory2]); - - await expect(sut.search(factory.auth({ user: { id: userId } }), {})).resolves.toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: memory1.id, assets: [expect.objectContaining({ id: asset.id })] }), - expect.objectContaining({ id: memory2.id, assets: [] }), - ]), - ); - }); - - it('should map ', async () => { - mocks.memory.search.mockResolvedValue([]); - - await expect(sut.search(factory.auth(), {})).resolves.toEqual([]); - }); - }); - - describe('get', () => { - it('should throw an error when no access', async () => { - await expect(sut.get(factory.auth(), 'not-found')).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should throw an error when the memory is not found', async () => { - const [memoryId] = newUuids(); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memoryId])); - mocks.memory.get.mockResolvedValue(void 0); - - await expect(sut.get(factory.auth(), memoryId)).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should get a memory by id', async () => { - const userId = newUuid(); - const memory = factory.memory({ ownerId: userId }); - - mocks.memory.get.mockResolvedValue(memory); - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); - - await expect(sut.get(factory.auth({ user: { id: userId } }), memory.id)).resolves.toMatchObject({ - id: memory.id, - }); - - expect(mocks.memory.get).toHaveBeenCalledWith(memory.id); - expect(mocks.access.memory.checkOwnerAccess).toHaveBeenCalledWith(memory.ownerId, new Set([memory.id])); - }); - }); - - describe('create', () => { - it('should skip assets the user does not have access to', async () => { - const [assetId, userId] = newUuids(); - const memory = factory.memory({ ownerId: userId }); - - mocks.memory.create.mockResolvedValue(memory); - - await expect( - sut.create(factory.auth({ user: { id: userId } }), { - type: memory.type, - data: memory.data as OnThisDayData, - memoryAt: memory.memoryAt, - isSaved: memory.isSaved, - assetIds: [assetId], - }), - ).resolves.toMatchObject({ assets: [] }); - - expect(mocks.memory.create).toHaveBeenCalledWith( - { - type: memory.type, - data: memory.data, - ownerId: memory.ownerId, - memoryAt: memory.memoryAt, - isSaved: memory.isSaved, - }, - new Set(), - ); - }); - - it('should create a memory', async () => { - const [assetId, userId] = newUuids(); - const asset = factory.asset({ id: assetId, ownerId: userId }); - const memory = factory.memory({ assets: [asset] }); - - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - mocks.memory.create.mockResolvedValue(memory); - - await expect( - sut.create(factory.auth({ user: { id: userId } }), { - type: memory.type, - data: memory.data as OnThisDayData, - assetIds: memory.assets.map((asset) => asset.id), - memoryAt: memory.memoryAt, - }), - ).resolves.toBeDefined(); - - expect(mocks.memory.create).toHaveBeenCalledWith( - expect.objectContaining({ ownerId: userId }), - new Set([assetId]), - ); - }); - - it('should create a memory without assets', async () => { - const memory = factory.memory(); - - mocks.memory.create.mockResolvedValue(memory); - - await expect( - sut.create(factory.auth(), { - type: memory.type, - data: memory.data as OnThisDayData, - memoryAt: memory.memoryAt, - }), - ).resolves.toBeDefined(); - }); - }); - - describe('update', () => { - it('should require access', async () => { - await expect(sut.update(factory.auth(), 'not-found', { isSaved: true })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.memory.update).not.toHaveBeenCalled(); - }); - - it('should update a memory', async () => { - const memory = factory.memory(); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); - mocks.memory.update.mockResolvedValue(memory); - - await expect(sut.update(factory.auth(), memory.id, { isSaved: true })).resolves.toBeDefined(); - - expect(mocks.memory.update).toHaveBeenCalledWith(memory.id, expect.objectContaining({ isSaved: true })); - }); - }); - - describe('remove', () => { - it('should require access', async () => { - await expect(sut.remove(factory.auth(), newUuid())).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.memory.delete).not.toHaveBeenCalled(); - }); - - it('should delete a memory', async () => { - const memoryId = newUuid(); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memoryId])); - mocks.memory.delete.mockResolvedValue(); - - await expect(sut.remove(factory.auth(), memoryId)).resolves.toBeUndefined(); - - expect(mocks.memory.delete).toHaveBeenCalledWith(memoryId); - }); - }); - - describe('addAssets', () => { - it('should require memory access', async () => { - const [memoryId, assetId] = newUuids(); - - await expect(sut.addAssets(factory.auth(), memoryId, { ids: [assetId] })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.memory.addAssetIds).not.toHaveBeenCalled(); - }); - - it('should require asset access', async () => { - const assetId = newUuid(); - const memory = factory.memory(); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); - mocks.memory.get.mockResolvedValue(memory); - mocks.memory.getAssetIds.mockResolvedValue(new Set()); - - await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([ - { error: 'no_permission', id: assetId, success: false }, - ]); - - expect(mocks.memory.addAssetIds).not.toHaveBeenCalled(); - }); - - it('should skip assets already in the memory', async () => { - const asset = factory.asset(); - const memory = factory.memory({ assets: [asset] }); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); - mocks.memory.get.mockResolvedValue(memory); - mocks.memory.getAssetIds.mockResolvedValue(new Set([asset.id])); - - await expect(sut.addAssets(factory.auth(), memory.id, { ids: [asset.id] })).resolves.toEqual([ - { error: 'duplicate', id: asset.id, success: false }, - ]); - - expect(mocks.memory.addAssetIds).not.toHaveBeenCalled(); - }); - - it('should add assets', async () => { - const assetId = newUuid(); - const memory = factory.memory(); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); - mocks.memory.get.mockResolvedValue(memory); - mocks.memory.update.mockResolvedValue(memory); - mocks.memory.getAssetIds.mockResolvedValue(new Set()); - mocks.memory.addAssetIds.mockResolvedValue(); - - await expect(sut.addAssets(factory.auth(), memory.id, { ids: [assetId] })).resolves.toEqual([ - { id: assetId, success: true }, - ]); - - expect(mocks.memory.addAssetIds).toHaveBeenCalledWith(memory.id, [assetId]); - }); - }); - - describe('removeAssets', () => { - it('should require memory access', async () => { - await expect(sut.removeAssets(factory.auth(), 'not-found', { ids: ['asset1'] })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.memory.removeAssetIds).not.toHaveBeenCalled(); - }); - - it('should skip assets not in the memory', async () => { - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); - mocks.memory.getAssetIds.mockResolvedValue(new Set()); - - await expect(sut.removeAssets(factory.auth(), 'memory1', { ids: ['not-found'] })).resolves.toEqual([ - { error: 'not_found', id: 'not-found', success: false }, - ]); - - expect(mocks.memory.removeAssetIds).not.toHaveBeenCalled(); - }); - - it('should remove assets', async () => { - const memory = factory.memory(); - const asset = factory.asset(); - - mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([asset.id])); - mocks.memory.getAssetIds.mockResolvedValue(new Set([asset.id])); - mocks.memory.removeAssetIds.mockResolvedValue(); - mocks.memory.update.mockResolvedValue(memory); - - await expect(sut.removeAssets(factory.auth(), memory.id, { ids: [asset.id] })).resolves.toEqual([ - { id: asset.id, success: true }, - ]); - - expect(mocks.memory.removeAssetIds).toHaveBeenCalledWith(memory.id, [asset.id]); - }); - }); -}); diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts deleted file mode 100644 index db682b6393..0000000000 --- a/server/src/services/memory.service.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { DateTime } from 'luxon'; -import { OnJob } from 'src/decorators'; -import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { MemoryCreateDto, MemoryResponseDto, MemorySearchDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto'; -import { DatabaseLock, JobName, MemoryType, Permission, QueueName, SystemMetadataKey } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { addAssets, removeAssets } from 'src/utils/asset.util'; - -const DAYS = 3; - -@Injectable() -export class MemoryService extends BaseService { - @OnJob({ name: JobName.MemoryGenerate, queue: QueueName.BackgroundTask }) - async onMemoriesCreate() { - const users = await this.userRepository.getList({ withDeleted: false }); - - await this.databaseRepository.withLock(DatabaseLock.MemoryCreation, async () => { - const state = await this.systemMetadataRepository.get(SystemMetadataKey.MemoriesState); - const start = DateTime.utc().startOf('day').minus({ days: DAYS }); - const lastOnThisDayDate = state?.lastOnThisDayDate ? DateTime.fromISO(state.lastOnThisDayDate) : start; - - // generate a memory +/- X days from today - for (let i = 0; i <= DAYS * 2; i++) { - const target = start.plus({ days: i }); - if (lastOnThisDayDate >= target) { - continue; - } - - this.logger.log(`Creating memories for ${target.toISO()}`); - try { - await Promise.all(users.map((owner) => this.createOnThisDayMemories(owner.id, target))); - } catch (error) { - this.logger.error(`Failed to create memories for ${target.toISO()}: ${error}`); - } - // update system metadata even when there is an error to minimize the chance of duplicates - await this.systemMetadataRepository.set(SystemMetadataKey.MemoriesState, { - ...state, - lastOnThisDayDate: target.toISO(), - }); - } - }); - } - - private async createOnThisDayMemories(ownerId: string, target: DateTime) { - const showAt = target.startOf('day').toISO(); - const hideAt = target.endOf('day').toISO(); - const memories = await this.assetRepository.getByDayOfYear([ownerId], target); - await Promise.all( - memories.map(({ year, assets }) => - this.memoryRepository.create( - { - ownerId, - type: MemoryType.OnThisDay, - data: { year }, - memoryAt: target.set({ year }).toISO()!, - showAt, - hideAt, - }, - new Set(assets.map(({ id }) => id)), - ), - ), - ); - } - - @OnJob({ name: JobName.MemoryCleanup, queue: QueueName.BackgroundTask }) - async onMemoriesCleanup() { - await this.memoryRepository.cleanup(); - } - - async search(auth: AuthDto, dto: MemorySearchDto) { - const memories = await this.memoryRepository.search(auth.user.id, dto); - return memories.map((memory) => mapMemory(memory, auth)); - } - - statistics(auth: AuthDto, dto: MemorySearchDto) { - return this.memoryRepository.statistics(auth.user.id, dto); - } - - async get(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.MemoryRead, ids: [id] }); - const memory = await this.findOrFail(id); - return mapMemory(memory, auth); - } - - async create(auth: AuthDto, dto: MemoryCreateDto) { - // TODO validate type/data combination - - const assetIds = dto.assetIds || []; - const allowedAssetIds = await this.checkAccess({ - auth, - permission: Permission.AssetShare, - ids: assetIds, - }); - const memory = await this.memoryRepository.create( - { - ownerId: auth.user.id, - type: dto.type, - data: dto.data, - isSaved: dto.isSaved, - memoryAt: dto.memoryAt, - seenAt: dto.seenAt, - }, - allowedAssetIds, - ); - - return mapMemory(memory, auth); - } - - async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.MemoryUpdate, ids: [id] }); - - const memory = await this.memoryRepository.update(id, { - isSaved: dto.isSaved, - memoryAt: dto.memoryAt, - seenAt: dto.seenAt, - }); - - return mapMemory(memory, auth); - } - - async remove(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.MemoryDelete, ids: [id] }); - await this.memoryRepository.delete(id); - } - - async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.MemoryRead, ids: [id] }); - - const repos = { access: this.accessRepository, bulk: this.memoryRepository }; - const results = await addAssets(auth, repos, { parentId: id, assetIds: dto.ids }); - - const hasSuccess = results.find(({ success }) => success); - if (hasSuccess) { - await this.memoryRepository.update(id, { updatedAt: new Date() }); - } - - return results; - } - - async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.MemoryUpdate, ids: [id] }); - - const repos = { access: this.accessRepository, bulk: this.memoryRepository }; - const results = await removeAssets(auth, repos, { - parentId: id, - assetIds: dto.ids, - canAlwaysRemove: Permission.MemoryDelete, - }); - - const hasSuccess = results.find(({ success }) => success); - if (hasSuccess) { - await this.memoryRepository.update(id, { id, updatedAt: new Date() }); - } - - return results; - } - - private async findOrFail(id: string) { - const memory = await this.memoryRepository.get(id); - if (!memory) { - throw new BadRequestException('Memory not found'); - } - return memory; - } -} diff --git a/server/src/services/metadata.service.spec.ts b/server/src/services/metadata.service.spec.ts deleted file mode 100644 index eda4e1a063..0000000000 --- a/server/src/services/metadata.service.spec.ts +++ /dev/null @@ -1,1887 +0,0 @@ -import { BinaryField, ExifDateTime } from 'exiftool-vendored'; -import { DateTime } from 'luxon'; -import { randomBytes } from 'node:crypto'; -import { Stats } from 'node:fs'; -import { defaults } from 'src/config'; -import { MapAsset } from 'src/dtos/asset-response.dto'; -import { - AssetFileType, - AssetType, - AssetVisibility, - ExifOrientation, - ImmichWorker, - JobName, - JobStatus, - SourceType, -} from 'src/enum'; -import { ImmichTags } from 'src/repositories/metadata.repository'; -import { firstDateTime, MetadataService } from 'src/services/metadata.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { fileStub } from 'test/fixtures/file.stub'; -import { probeStub } from 'test/fixtures/media.stub'; -import { personStub } from 'test/fixtures/person.stub'; -import { tagStub } from 'test/fixtures/tag.stub'; -import { factory } from 'test/small.factory'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -const removeNonSidecarFiles = (asset: any) => { - return { - ...asset, - files: asset.files.filter((file: any) => file.type === AssetFileType.Sidecar), - }; -}; - -const forSidecarJob = ( - asset: { - id?: string; - originalPath?: string; - files?: { id: string; type: AssetFileType; path: string; isEdited: boolean }[]; - } = {}, -) => { - return { - id: factory.uuid(), - originalPath: '/path/to/IMG_123.jpg', - files: [], - ...asset, - }; -}; - -const makeFaceTags = (face: Partial<{ Name: string }> = {}, orientation?: ImmichTags['Orientation']) => ({ - Orientation: orientation, - RegionInfo: { - AppliedToDimensions: { W: 1000, H: 100, Unit: 'pixel' }, - RegionList: [ - { - Type: 'face', - Area: { - X: 0.1, - Y: 0.4, - W: 0.2, - H: 0.4, - Unit: 'normalized', - }, - ...face, - }, - ], - }, -}); - -describe(MetadataService.name, () => { - let sut: MetadataService; - let mocks: ServiceMocks; - - const mockReadTags = (exifData?: Partial, sidecarData?: Partial) => { - mocks.metadata.readTags.mockReset(); - mocks.metadata.readTags.mockResolvedValueOnce(exifData ?? {}); - mocks.metadata.readTags.mockResolvedValueOnce(sidecarData ?? {}); - }; - - beforeEach(() => { - ({ sut, mocks } = newTestService(MetadataService)); - - mockReadTags(); - - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - - delete process.env.TZ; - }); - - afterEach(async () => { - await sut.onShutdown(); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('onBootstrapEvent', () => { - it('should pause and resume queue during init', async () => { - mocks.job.pause.mockResolvedValue(); - mocks.map.init.mockResolvedValue(); - mocks.job.resume.mockResolvedValue(); - - await sut.onBootstrap(); - - expect(mocks.job.pause).toHaveBeenCalledTimes(1); - expect(mocks.map.init).toHaveBeenCalledTimes(1); - expect(mocks.job.resume).toHaveBeenCalledTimes(1); - }); - }); - - describe('onConfigInit', () => { - it('should update metadata processing concurrency', () => { - sut.onConfigInit({ newConfig: defaults }); - - expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledWith(defaults.job.metadataExtraction.concurrency); - expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledTimes(1); - }); - }); - - describe('onConfigUpdate', () => { - it('should update metadata processing concurrency', () => { - const newConfig = structuredClone(defaults); - newConfig.job.metadataExtraction.concurrency = 10; - - sut.onConfigUpdate({ oldConfig: defaults, newConfig }); - - expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledWith(newConfig.job.metadataExtraction.concurrency); - expect(mocks.metadata.setMaxConcurrency).toHaveBeenCalledTimes(1); - }); - }); - - describe('handleQueueMetadataExtraction', () => { - it('should queue metadata extraction for all assets without exif values', async () => { - mocks.assetJob.streamForMetadataExtraction.mockReturnValue(makeStream([assetStub.image])); - - await expect(sut.handleQueueMetadataExtraction({ force: false })).resolves.toBe(JobStatus.Success); - expect(mocks.assetJob.streamForMetadataExtraction).toHaveBeenCalledWith(false); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetExtractMetadata, - data: { id: assetStub.image.id }, - }, - ]); - }); - - it('should queue metadata extraction for all assets', async () => { - mocks.assetJob.streamForMetadataExtraction.mockReturnValue(makeStream([assetStub.image])); - - await expect(sut.handleQueueMetadataExtraction({ force: true })).resolves.toBe(JobStatus.Success); - expect(mocks.assetJob.streamForMetadataExtraction).toHaveBeenCalledWith(true); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetExtractMetadata, - data: { id: assetStub.image.id }, - }, - ]); - }); - }); - - describe('handleMetadataExtraction', () => { - beforeEach(() => { - const time = new Date('2022-01-01T00:00:00.000Z'); - const timeMs = time.valueOf(); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: time, - mtimeMs: timeMs, - birthtimeMs: timeMs, - } as Stats); - }); - - it('should handle an asset that could not be found', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(void 0); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should handle a date in a sidecar file', async () => { - const originalDate = new Date('2023-11-21T16:13:17.517Z'); - const sidecarDate = new Date('2022-01-01T00:00:00.000Z'); - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.sidecar)); - mockReadTags({ CreationDate: originalDate.toISOString() }, { CreationDate: sidecarDate.toISOString() }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.sidecar.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ dateTimeOriginal: sidecarDate }), { - lockedPropertiesBehavior: 'skip', - }); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: assetStub.image.id, - duration: null, - fileCreatedAt: sidecarDate, - localDateTime: sidecarDate, - }), - ); - }); - - it('should take the file modification date when missing exif and earlier than creation date', async () => { - const fileCreatedAt = new Date('2022-01-01T00:00:00.000Z'); - const fileModifiedAt = new Date('2021-01-01T00:00:00.000Z'); - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: fileModifiedAt, - mtimeMs: fileModifiedAt.valueOf(), - birthtimeMs: fileCreatedAt.valueOf(), - } as Stats); - mockReadTags(); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ dateTimeOriginal: fileModifiedAt }), - { lockedPropertiesBehavior: 'skip' }, - ); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - duration: null, - fileCreatedAt: fileModifiedAt, - fileModifiedAt, - localDateTime: fileModifiedAt, - width: null, - height: null, - }); - }); - - it('should take the file creation date when missing exif and earlier than modification date', async () => { - const fileCreatedAt = new Date('2021-01-01T00:00:00.000Z'); - const fileModifiedAt = new Date('2022-01-01T00:00:00.000Z'); - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: fileModifiedAt, - mtimeMs: fileModifiedAt.valueOf(), - birthtimeMs: fileCreatedAt.valueOf(), - } as Stats); - mockReadTags(); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ dateTimeOriginal: fileCreatedAt }), - { lockedPropertiesBehavior: 'skip' }, - ); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - duration: null, - fileCreatedAt, - fileModifiedAt, - localDateTime: fileCreatedAt, - width: null, - height: null, - }); - }); - - it('should determine dateTimeOriginal regardless of the server time zone', async () => { - process.env.TZ = 'America/Los_Angeles'; - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.sidecar)); - mockReadTags({ DateTimeOriginal: '2022:01:01 00:00:00' }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - dateTimeOriginal: new Date('2022-01-01T00:00:00.000Z'), - }), - { lockedPropertiesBehavior: 'skip' }, - ); - - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - localDateTime: new Date('2022-01-01T00:00:00.000Z'), - }), - ); - }); - - it('should handle lists of numbers', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: assetStub.image.fileModifiedAt, - mtimeMs: assetStub.image.fileModifiedAt.valueOf(), - birthtimeMs: assetStub.image.fileCreatedAt.valueOf(), - } as Stats); - mockReadTags({ - ISO: [160], - }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining({ iso: 160 }), { - lockedPropertiesBehavior: 'skip', - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.image.id, - duration: null, - fileCreatedAt: assetStub.image.fileCreatedAt, - fileModifiedAt: assetStub.image.fileCreatedAt, - localDateTime: assetStub.image.fileCreatedAt, - width: null, - height: null, - }); - }); - - it('should not delete latituide and longitude without reverse geocode', async () => { - // regression test for issue 17511 - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.withLocation); - mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: false } }); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: assetStub.withLocation.fileModifiedAt, - mtimeMs: assetStub.withLocation.fileModifiedAt.valueOf(), - birthtimeMs: assetStub.withLocation.fileCreatedAt.valueOf(), - } as Stats); - mockReadTags({ - GPSLatitude: assetStub.withLocation.exifInfo!.latitude!, - GPSLongitude: assetStub.withLocation.exifInfo!.longitude!, - }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ city: null, state: null, country: null }), - { lockedPropertiesBehavior: 'skip' }, - ); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.withLocation.id, - duration: null, - fileCreatedAt: assetStub.withLocation.fileCreatedAt, - fileModifiedAt: assetStub.withLocation.fileModifiedAt, - localDateTime: new Date('2023-02-22T05:06:29.716Z'), - width: null, - height: null, - }); - }); - - it('should apply reverse geocoding', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.withLocation)); - mocks.systemMetadata.get.mockResolvedValue({ reverseGeocoding: { enabled: true } }); - mocks.map.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' }); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: assetStub.withLocation.fileModifiedAt, - mtimeMs: assetStub.withLocation.fileModifiedAt.valueOf(), - birthtimeMs: assetStub.withLocation.fileCreatedAt.valueOf(), - } as Stats); - mockReadTags({ - GPSLatitude: assetStub.withLocation.exifInfo!.latitude!, - GPSLongitude: assetStub.withLocation.exifInfo!.longitude!, - }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ city: 'City', state: 'State', country: 'Country' }), - { lockedPropertiesBehavior: 'skip' }, - ); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.withLocation.id, - duration: null, - fileCreatedAt: assetStub.withLocation.fileCreatedAt, - fileModifiedAt: assetStub.withLocation.fileModifiedAt, - localDateTime: new Date('2023-02-22T05:06:29.716Z'), - width: null, - height: null, - }); - }); - - it('should discard latitude and longitude on null island', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.withLocation)); - mockReadTags({ - GPSLatitude: 0, - GPSLongitude: 0, - }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ latitude: null, longitude: null }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should extract tags from TagsList', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent'] }) }); - mockReadTags({ TagsList: ['Parent'] }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: 'Parent', parent: undefined }); - }); - - it('should extract hierarchy from TagsList', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent/Child'] }) }); - mockReadTags({ TagsList: ['Parent/Child'] }); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - userId: 'user-id', - value: 'Parent', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - userId: 'user-id', - value: 'Parent/Child', - parentId: 'tag-parent', - }); - }); - - it('should extract tags from Keywords as a string', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent'] }) }); - mockReadTags({ Keywords: 'Parent' }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: 'Parent', parent: undefined }); - }); - - it('should extract tags from Keywords as a list', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent'] }) }); - mockReadTags({ Keywords: ['Parent'] }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: 'Parent', parent: undefined }); - }); - - it('should extract tags from Keywords as a list with a number', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - exifInfo: factory.exif({ tags: ['Parent', '2024'] }), - }); - mockReadTags({ Keywords: ['Parent', 2024] }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: 'Parent', parent: undefined }); - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: '2024', parent: undefined }); - }); - - it('should extract hierarchal tags from Keywords', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Parent/Child'] }) }); - mockReadTags({ Keywords: 'Parent/Child' }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - userId: 'user-id', - value: 'Parent', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - userId: 'user-id', - value: 'Parent/Child', - parentId: 'tag-parent', - }); - }); - - it('should ignore Keywords when TagsList is present', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - exifInfo: factory.exif({ tags: ['Parent/Child', 'Child'] }), - }); - mockReadTags({ Keywords: 'Child', TagsList: ['Parent/Child'] }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - userId: 'user-id', - value: 'Parent', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - userId: 'user-id', - value: 'Parent/Child', - parentId: 'tag-parent', - }); - }); - - it('should extract hierarchy from HierarchicalSubject', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - exifInfo: factory.exif({ tags: ['Parent/Child', 'TagA'] }), - }); - mockReadTags({ HierarchicalSubject: ['Parent|Child', 'TagA'] }); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - userId: 'user-id', - value: 'Parent', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - userId: 'user-id', - value: 'Parent/Child', - parentId: 'tag-parent', - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(3, { userId: 'user-id', value: 'TagA', parent: undefined }); - }); - - it('should extract tags from HierarchicalSubject as a list with a number', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(removeNonSidecarFiles(assetStub.image)); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - exifInfo: factory.exif({ tags: ['Parent', '2024'] }), - }); - mockReadTags({ HierarchicalSubject: ['Parent', 2024] }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: 'Parent', parent: undefined }); - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ userId: 'user-id', value: '2024', parent: undefined }); - }); - - it('should extract ignore / characters in a HierarchicalSubject tag', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mocks.asset.getById.mockResolvedValue({ ...factory.asset(), exifInfo: factory.exif({ tags: ['Mom|Dad'] }) }); - mockReadTags({ HierarchicalSubject: ['Mom/Dad'] }); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ - userId: 'user-id', - value: 'Mom|Dad', - parent: undefined, - }); - }); - - it('should ignore HierarchicalSubject when TagsList is present', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - exifInfo: factory.exif({ tags: ['Parent/Child', 'Parent2/Child2'] }), - }); - mockReadTags({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] }); - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - userId: 'user-id', - value: 'Parent', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - userId: 'user-id', - value: 'Parent/Child', - parentId: 'tag-parent', - }); - }); - - it('should remove existing tags', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({}); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.tag.replaceAssetTags).toHaveBeenCalledWith('asset-id', []); - }); - - it('should not apply motion photos if asset is video', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoMotionAsset, - visibility: AssetVisibility.Timeline, - }); - mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id); - expect(mocks.storage.createOrOverwriteFile).not.toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalledWith( - expect.objectContaining({ assetType: AssetType.Video, visibility: AssetVisibility.Hidden }), - ); - }); - - it('should handle an invalid Directory Item', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ - MotionPhoto: 1, - ContainerDirectory: [{ Foo: 100 }], - }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - }); - - it('should extract the correct video orientation', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video); - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p); - mockReadTags({}); - - await sut.handleMetadataExtraction({ id: assetStub.video.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.video.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ orientation: ExifOrientation.Rotate270CW.toString() }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should extract the MotionPhotoVideo tag from Samsung HEIC motion photos', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoWithOriginalFileName, - livePhotoVideoId: null, - libraryId: null, - }); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt, - mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(), - birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(), - } as Stats); - mockReadTags({ - Directory: 'foo/bar/', - MotionPhoto: 1, - MotionPhotoVideo: new BinaryField(0, ''), - // The below two are included to ensure that the MotionPhotoVideo tag is extracted - // instead of the EmbeddedVideoFile, since HEIC MotionPhotos include both - EmbeddedVideoFile: new BinaryField(0, ''), - EmbeddedVideoType: 'MotionPhoto_Data', - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); - mocks.crypto.randomUUID.mockReturnValue(fileStub.livePhotoMotion.uuid); - const video = randomBytes(512); - mocks.metadata.extractBinaryTag.mockResolvedValue(video); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id }); - expect(mocks.metadata.extractBinaryTag).toHaveBeenCalledWith( - assetStub.livePhotoWithOriginalFileName.originalPath, - 'MotionPhotoVideo', - ); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.livePhotoWithOriginalFileName.id); - expect(mocks.asset.create).toHaveBeenCalledWith({ - checksum: expect.any(Buffer), - deviceAssetId: 'NONE', - deviceId: 'NONE', - fileCreatedAt: assetStub.livePhotoWithOriginalFileName.fileCreatedAt, - fileModifiedAt: assetStub.livePhotoWithOriginalFileName.fileModifiedAt, - id: fileStub.livePhotoMotion.uuid, - visibility: AssetVisibility.Hidden, - libraryId: assetStub.livePhotoWithOriginalFileName.libraryId, - localDateTime: assetStub.livePhotoWithOriginalFileName.fileCreatedAt, - originalFileName: 'asset_1.mp4', - originalPath: expect.stringContaining('/data/encoded-video/user-id/li/ve/live-photo-motion-asset-MP.mp4'), - ownerId: assetStub.livePhotoWithOriginalFileName.ownerId, - type: AssetType.Video, - }); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); - expect(mocks.storage.createFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoWithOriginalFileName.id, - livePhotoVideoId: fileStub.livePhotoMotion.uuid, - }); - expect(mocks.asset.update).toHaveBeenCalledTimes(3); - expect(mocks.job.queue).toHaveBeenCalledExactlyOnceWith({ - name: JobName.AssetEncodeVideo, - data: { id: assetStub.livePhotoMotionAsset.id }, - }); - }); - - it('should extract the EmbeddedVideo tag from Samsung JPEG motion photos', async () => { - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt, - mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(), - birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(), - } as Stats); - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoWithOriginalFileName, - livePhotoVideoId: null, - libraryId: null, - }); - mockReadTags({ - Directory: 'foo/bar/', - EmbeddedVideoFile: new BinaryField(0, ''), - EmbeddedVideoType: 'MotionPhoto_Data', - MotionPhoto: 1, - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); - mocks.crypto.randomUUID.mockReturnValue(fileStub.livePhotoMotion.uuid); - const video = randomBytes(512); - mocks.metadata.extractBinaryTag.mockResolvedValue(video); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id }); - expect(mocks.metadata.extractBinaryTag).toHaveBeenCalledWith( - assetStub.livePhotoWithOriginalFileName.originalPath, - 'EmbeddedVideoFile', - ); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.livePhotoWithOriginalFileName.id); - expect(mocks.asset.create).toHaveBeenCalledWith({ - checksum: expect.any(Buffer), - deviceAssetId: 'NONE', - deviceId: 'NONE', - fileCreatedAt: assetStub.livePhotoWithOriginalFileName.fileCreatedAt, - fileModifiedAt: assetStub.livePhotoWithOriginalFileName.fileModifiedAt, - id: fileStub.livePhotoMotion.uuid, - visibility: AssetVisibility.Hidden, - libraryId: assetStub.livePhotoWithOriginalFileName.libraryId, - localDateTime: assetStub.livePhotoWithOriginalFileName.fileCreatedAt, - originalFileName: 'asset_1.mp4', - originalPath: expect.stringContaining('/data/encoded-video/user-id/li/ve/live-photo-motion-asset-MP.mp4'), - ownerId: assetStub.livePhotoWithOriginalFileName.ownerId, - type: AssetType.Video, - }); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); - expect(mocks.storage.createFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoWithOriginalFileName.id, - livePhotoVideoId: fileStub.livePhotoMotion.uuid, - }); - expect(mocks.asset.update).toHaveBeenCalledTimes(3); - expect(mocks.job.queue).toHaveBeenCalledExactlyOnceWith({ - name: JobName.AssetEncodeVideo, - data: { id: assetStub.livePhotoMotionAsset.id }, - }); - }); - - it('should extract the motion photo video from the XMP directory entry ', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoWithOriginalFileName, - livePhotoVideoId: null, - libraryId: null, - }); - mocks.storage.stat.mockResolvedValue({ - size: 123_456, - mtime: assetStub.livePhotoWithOriginalFileName.fileModifiedAt, - mtimeMs: assetStub.livePhotoWithOriginalFileName.fileModifiedAt.valueOf(), - birthtimeMs: assetStub.livePhotoWithOriginalFileName.fileCreatedAt.valueOf(), - } as Stats); - mockReadTags({ - Directory: 'foo/bar/', - MotionPhoto: 1, - MicroVideo: 1, - MicroVideoOffset: 1, - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); - mocks.crypto.randomUUID.mockReturnValue(fileStub.livePhotoMotion.uuid); - const video = randomBytes(512); - mocks.storage.readFile.mockResolvedValue(video); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.livePhotoWithOriginalFileName.id); - expect(mocks.storage.readFile).toHaveBeenCalledWith( - assetStub.livePhotoWithOriginalFileName.originalPath, - expect.any(Object), - ); - expect(mocks.asset.create).toHaveBeenCalledWith({ - checksum: expect.any(Buffer), - deviceAssetId: 'NONE', - deviceId: 'NONE', - fileCreatedAt: assetStub.livePhotoWithOriginalFileName.fileCreatedAt, - fileModifiedAt: assetStub.livePhotoWithOriginalFileName.fileModifiedAt, - id: fileStub.livePhotoMotion.uuid, - visibility: AssetVisibility.Hidden, - libraryId: assetStub.livePhotoWithOriginalFileName.libraryId, - localDateTime: assetStub.livePhotoWithOriginalFileName.fileCreatedAt, - originalFileName: 'asset_1.mp4', - originalPath: expect.stringContaining('/data/encoded-video/user-id/li/ve/live-photo-motion-asset-MP.mp4'), - ownerId: assetStub.livePhotoWithOriginalFileName.ownerId, - type: AssetType.Video, - }); - expect(mocks.user.updateUsage).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.ownerId, 512); - expect(mocks.storage.createFile).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.originalPath, video); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoWithOriginalFileName.id, - livePhotoVideoId: fileStub.livePhotoMotion.uuid, - }); - expect(mocks.asset.update).toHaveBeenCalledTimes(3); - expect(mocks.job.queue).toHaveBeenCalledExactlyOnceWith({ - name: JobName.AssetEncodeVideo, - data: { id: assetStub.livePhotoMotionAsset.id }, - }); - }); - - it('should delete old motion photo video assets if they do not match what is extracted', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.livePhotoWithOriginalFileName); - mockReadTags({ - Directory: 'foo/bar/', - MotionPhoto: 1, - MicroVideo: 1, - MicroVideoOffset: 1, - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.create.mockImplementation( - (asset) => Promise.resolve({ ...assetStub.livePhotoMotionAsset, ...asset }) as Promise, - ); - const video = randomBytes(512); - mocks.storage.readFile.mockResolvedValue(video); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoWithOriginalFileName.id }); - expect(mocks.job.queue).toHaveBeenNthCalledWith(1, { - name: JobName.AssetDelete, - data: { id: assetStub.livePhotoWithOriginalFileName.livePhotoVideoId, deleteOnDisk: true }, - }); - }); - - it('should not create a new motion photo video asset if the hash of the extracted video matches an existing asset', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.livePhotoStillAsset); - mockReadTags({ - Directory: 'foo/bar/', - MotionPhoto: 1, - MicroVideo: 1, - MicroVideoOffset: 1, - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.getByChecksum.mockResolvedValue(assetStub.livePhotoMotionAsset); - const video = randomBytes(512); - mocks.storage.readFile.mockResolvedValue(video); - mocks.storage.checkFileExists.mockResolvedValue(true); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - expect(mocks.asset.create).not.toHaveBeenCalled(); - expect(mocks.storage.createOrOverwriteFile).not.toHaveBeenCalled(); - // The still asset gets saved by handleMetadataExtraction, but not the video - expect(mocks.asset.update).toHaveBeenCalledTimes(1); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should link and hide motion video asset to still asset if the hash of the extracted video matches an existing asset', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoStillAsset, - livePhotoVideoId: null, - }); - mockReadTags({ - Directory: 'foo/bar/', - MotionPhoto: 1, - MicroVideo: 1, - MicroVideoOffset: 1, - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.getByChecksum.mockResolvedValue({ - ...assetStub.livePhotoMotionAsset, - visibility: AssetVisibility.Timeline, - }); - const video = randomBytes(512); - mocks.storage.readFile.mockResolvedValue(video); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: AssetVisibility.Hidden, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - expect(mocks.asset.update).toHaveBeenCalledTimes(4); - }); - - it('should not update storage usage if motion photo is external', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoStillAsset, - livePhotoVideoId: null, - isExternal: true, - }); - mockReadTags({ - Directory: 'foo/bar/', - MotionPhoto: 1, - MicroVideo: 1, - MicroVideoOffset: 1, - }); - mocks.crypto.hashSha1.mockReturnValue(randomBytes(512)); - mocks.asset.create.mockResolvedValue(assetStub.livePhotoMotionAsset); - const video = randomBytes(512); - mocks.storage.readFile.mockResolvedValue(video); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - expect(mocks.user.updateUsage).not.toHaveBeenCalled(); - }); - - it('should save all metadata', async () => { - const dateForTest = new Date('1970-01-01T00:00:00.000-11:30'); - - const tags: ImmichTags = { - BitsPerSample: 1, - ComponentBitDepth: 1, - ImagePixelDepth: '1', - BitDepth: 1, - ColorBitDepth: 1, - ColorSpace: '1', - DateTimeOriginal: ExifDateTime.fromISO(dateForTest.toISOString()), - ExposureTime: '100ms', - FocalLength: 20, - ImageDescription: 'test description', - ISO: 100, - LensModel: 'test lens', - MediaGroupUUID: 'livePhoto', - Make: 'test-factory', - Model: "'mockel'", - ModifyDate: ExifDateTime.fromISO(dateForTest.toISOString()), - Orientation: 0, - ProfileDescription: 'extensive description', - ProjectionType: 'equirectangular', - tz: 'UTC-11:30', - TagsList: ['parent/child'], - Rating: 3, - }; - - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags(tags); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { - assetId: assetStub.image.id, - bitsPerSample: expect.any(Number), - autoStackId: null, - colorspace: tags.ColorSpace, - dateTimeOriginal: dateForTest, - description: tags.ImageDescription, - exifImageHeight: null, - exifImageWidth: null, - exposureTime: tags.ExposureTime, - fNumber: null, - fileSizeInByte: 123_456, - focalLength: tags.FocalLength, - fps: null, - iso: tags.ISO, - latitude: null, - lensModel: tags.LensModel, - livePhotoCID: tags.MediaGroupUUID, - longitude: null, - make: tags.Make, - model: tags.Model, - modifyDate: expect.any(Date), - orientation: tags.Orientation?.toString(), - profileDescription: tags.ProfileDescription, - projectionType: 'EQUIRECTANGULAR', - timeZone: tags.tz, - rating: tags.Rating, - country: null, - state: null, - city: null, - tags: ['parent/child'], - }, - { lockedPropertiesBehavior: 'skip' }, - ); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: assetStub.image.id, - duration: null, - fileCreatedAt: dateForTest, - localDateTime: DateTime.fromISO('1970-01-01T00:00:00.000Z').toJSDate(), - }), - ); - }); - - it('should extract +00:00 timezone from raw value', async () => { - // exiftool-vendored returns "no timezone" information even though "+00:00" might be set explicitly - // https://github.com/photostructure/exiftool-vendored.js/issues/203 - - // this only tests our assumptions of exiftool-vendored, demonstrating the issue - const someDate = '2024-09-01T00:00:00.000'; - expect(ExifDateTime.fromISO(someDate + 'Z')?.zone).toBe('UTC'); - expect(ExifDateTime.fromISO(someDate + '+00:00')?.zone).toBe('UTC'); // this is the issue, should be UTC+0 - expect(ExifDateTime.fromISO(someDate + '+04:00')?.zone).toBe('UTC+4'); - - const tags: ImmichTags = { - DateTimeOriginal: ExifDateTime.fromISO(someDate + '+00:00'), - tz: undefined, - }; - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags(tags); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - timeZone: 'UTC+0', - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should extract duration', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video); - mocks.media.probe.mockResolvedValue({ - ...probeStub.videoStreamH264, - format: { - ...probeStub.videoStreamH264.format, - duration: 6.21, - }, - }); - - await sut.handleMetadataExtraction({ id: assetStub.video.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.video.id); - expect(mocks.asset.upsertExif).toHaveBeenCalled(); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: assetStub.image.id, - duration: '00:00:06.210', - }), - ); - }); - - it('should only extract duration for videos', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mocks.media.probe.mockResolvedValue({ - ...probeStub.videoStreamH264, - format: { - ...probeStub.videoStreamH264.format, - duration: 6.21, - }, - }); - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.upsertExif).toHaveBeenCalled(); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: assetStub.image.id, - duration: null, - }), - ); - }); - - it('should omit duration of zero', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video); - mocks.media.probe.mockResolvedValue({ - ...probeStub.videoStreamH264, - format: { - ...probeStub.videoStreamH264.format, - duration: 0, - }, - }); - - await sut.handleMetadataExtraction({ id: assetStub.video.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.video.id); - expect(mocks.asset.upsertExif).toHaveBeenCalled(); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: assetStub.image.id, - duration: null, - }), - ); - }); - - it('should a handle duration of 1 week', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video); - mocks.media.probe.mockResolvedValue({ - ...probeStub.videoStreamH264, - format: { - ...probeStub.videoStreamH264.format, - duration: 604_800, - }, - }); - - await sut.handleMetadataExtraction({ id: assetStub.video.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.video.id); - expect(mocks.asset.upsertExif).toHaveBeenCalled(); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: assetStub.video.id, - duration: '168:00:00.000', - }), - ); - }); - - it('should use Duration from exif', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.image, - originalPath: '/original/path.webp', - }); - mockReadTags({ Duration: 123 }, {}); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' })); - }); - - it('should prefer Duration from exif over sidecar', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.image, - originalPath: '/original/path.webp', - files: [ - { - id: 'some-id', - type: AssetFileType.Sidecar, - path: '/path/to/something', - isEdited: false, - }, - ], - }); - - mockReadTags({ Duration: 123 }, { Duration: 456 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.metadata.readTags).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' })); - }); - - it('should ignore all Duration tags for definitely static images', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.imageDng); - mockReadTags({ Duration: 123 }, { Duration: 456 }); - - await sut.handleMetadataExtraction({ id: assetStub.imageDng.id }); - - expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: null })); - }); - - it('should ignore Duration from exif for videos', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.video); - mockReadTags({ Duration: 123 }, {}); - mocks.media.probe.mockResolvedValue({ - ...probeStub.videoStreamH264, - format: { - ...probeStub.videoStreamH264.format, - duration: 456, - }, - }); - - await sut.handleMetadataExtraction({ id: assetStub.video.id }); - - expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:07:36.000' })); - }); - - it('should trim whitespace from description', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ Description: '\t \v \f \n \r' }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - description: '', - }), - { lockedPropertiesBehavior: 'skip' }, - ); - - mockReadTags({ ImageDescription: ' my\n description' }); - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - description: 'my\n description', - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should handle a numeric description', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ Description: 1000 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - description: '1000', - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should skip importing metadata when the feature is disabled', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: false } } }); - mockReadTags(makeFaceTags({ Name: 'Person 1' })); - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.person.getDistinctNames).not.toHaveBeenCalled(); - }); - - it('should skip importing metadata face for assets without tags.RegionInfo', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } }); - mockReadTags(); - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.person.getDistinctNames).not.toHaveBeenCalled(); - }); - - it('should skip importing faces without name', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } }); - mockReadTags(makeFaceTags()); - mocks.person.getDistinctNames.mockResolvedValue([]); - mocks.person.createAll.mockResolvedValue([]); - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.person.createAll).not.toHaveBeenCalled(); - expect(mocks.person.refreshFaces).not.toHaveBeenCalled(); - expect(mocks.person.updateAll).not.toHaveBeenCalled(); - }); - - it('should skip importing faces with empty name', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } }); - mockReadTags(makeFaceTags({ Name: '' })); - mocks.person.getDistinctNames.mockResolvedValue([]); - mocks.person.createAll.mockResolvedValue([]); - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.person.createAll).not.toHaveBeenCalled(); - expect(mocks.person.refreshFaces).not.toHaveBeenCalled(); - expect(mocks.person.updateAll).not.toHaveBeenCalled(); - }); - - it('should apply metadata face tags creating new persons', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } }); - mockReadTags(makeFaceTags({ Name: personStub.withName.name })); - mocks.person.getDistinctNames.mockResolvedValue([]); - mocks.person.createAll.mockResolvedValue([personStub.withName.id]); - mocks.person.update.mockResolvedValue(personStub.withName); - await sut.handleMetadataExtraction({ id: assetStub.primaryImage.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.primaryImage.id); - expect(mocks.person.getDistinctNames).toHaveBeenCalledWith(assetStub.primaryImage.ownerId, { withHidden: true }); - expect(mocks.person.createAll).toHaveBeenCalledWith([ - expect.objectContaining({ name: personStub.withName.name }), - ]); - expect(mocks.person.refreshFaces).toHaveBeenCalledWith( - [ - { - id: 'random-uuid', - assetId: assetStub.primaryImage.id, - personId: 'random-uuid', - imageHeight: 100, - imageWidth: 1000, - boundingBoxX1: 0, - boundingBoxX2: 200, - boundingBoxY1: 20, - boundingBoxY2: 60, - sourceType: SourceType.Exif, - }, - ], - [], - ); - expect(mocks.person.updateAll).toHaveBeenCalledWith([ - { id: 'random-uuid', ownerId: 'admin-id', faceAssetId: 'random-uuid' }, - ]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.PersonGenerateThumbnail, - data: { id: personStub.withName.id }, - }, - ]); - }); - - it('should assign metadata face tags to existing persons', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } }); - mockReadTags(makeFaceTags({ Name: personStub.withName.name })); - mocks.person.getDistinctNames.mockResolvedValue([{ id: personStub.withName.id, name: personStub.withName.name }]); - mocks.person.createAll.mockResolvedValue([]); - mocks.person.update.mockResolvedValue(personStub.withName); - await sut.handleMetadataExtraction({ id: assetStub.primaryImage.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.primaryImage.id); - expect(mocks.person.getDistinctNames).toHaveBeenCalledWith(assetStub.primaryImage.ownerId, { withHidden: true }); - expect(mocks.person.createAll).not.toHaveBeenCalled(); - expect(mocks.person.refreshFaces).toHaveBeenCalledWith( - [ - { - id: 'random-uuid', - assetId: assetStub.primaryImage.id, - personId: personStub.withName.id, - imageHeight: 100, - imageWidth: 1000, - boundingBoxX1: 0, - boundingBoxX2: 200, - boundingBoxY1: 20, - boundingBoxY2: 60, - sourceType: SourceType.Exif, - }, - ], - [], - ); - expect(mocks.person.updateAll).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalledWith(); - }); - - describe('handleFaceTagOrientation', () => { - const orientationTests = [ - { - description: 'undefined', - orientation: undefined, - expected: { imgW: 1000, imgH: 100, x1: 0, x2: 200, y1: 20, y2: 60 }, - }, - { - description: 'Horizontal = 1', - orientation: ExifOrientation.Horizontal, - expected: { imgW: 1000, imgH: 100, x1: 0, x2: 200, y1: 20, y2: 60 }, - }, - { - description: 'MirrorHorizontal = 2', - orientation: ExifOrientation.MirrorHorizontal, - expected: { imgW: 1000, imgH: 100, x1: 800, x2: 1000, y1: 20, y2: 60 }, - }, - { - description: 'Rotate180 = 3', - orientation: ExifOrientation.Rotate180, - expected: { imgW: 1000, imgH: 100, x1: 800, x2: 1000, y1: 40, y2: 80 }, - }, - { - description: 'MirrorVertical = 4', - orientation: ExifOrientation.MirrorVertical, - expected: { imgW: 1000, imgH: 100, x1: 0, x2: 200, y1: 40, y2: 80 }, - }, - { - description: 'MirrorHorizontalRotate270CW = 5', - orientation: ExifOrientation.MirrorHorizontalRotate270CW, - expected: { imgW: 100, imgH: 1000, x1: 20, x2: 60, y1: 0, y2: 200 }, - }, - { - description: 'Rotate90CW = 6', - orientation: ExifOrientation.Rotate90CW, - expected: { imgW: 100, imgH: 1000, x1: 40, x2: 80, y1: 0, y2: 200 }, - }, - { - description: 'MirrorHorizontalRotate90CW = 7', - orientation: ExifOrientation.MirrorHorizontalRotate90CW, - expected: { imgW: 100, imgH: 1000, x1: 40, x2: 80, y1: 800, y2: 1000 }, - }, - { - description: 'Rotate270CW = 8', - orientation: ExifOrientation.Rotate270CW, - expected: { imgW: 100, imgH: 1000, x1: 20, x2: 60, y1: 800, y2: 1000 }, - }, - ]; - - it.each(orientationTests)( - 'should transform RegionInfo geometry according to exif orientation $description', - async ({ orientation, expected }) => { - const { imgW, imgH, x1, x2, y1, y2 } = expected; - - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.primaryImage); - mocks.systemMetadata.get.mockResolvedValue({ metadata: { faces: { import: true } } }); - mockReadTags(makeFaceTags({ Name: personStub.withName.name }, orientation)); - mocks.person.getDistinctNames.mockResolvedValue([]); - mocks.person.createAll.mockResolvedValue([personStub.withName.id]); - mocks.person.update.mockResolvedValue(personStub.withName); - await sut.handleMetadataExtraction({ id: assetStub.primaryImage.id }); - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.primaryImage.id); - expect(mocks.person.getDistinctNames).toHaveBeenCalledWith(assetStub.primaryImage.ownerId, { - withHidden: true, - }); - expect(mocks.person.createAll).toHaveBeenCalledWith([ - expect.objectContaining({ name: personStub.withName.name }), - ]); - expect(mocks.person.refreshFaces).toHaveBeenCalledWith( - [ - { - id: 'random-uuid', - assetId: assetStub.primaryImage.id, - personId: 'random-uuid', - imageWidth: imgW, - imageHeight: imgH, - boundingBoxX1: x1, - boundingBoxX2: x2, - boundingBoxY1: y1, - boundingBoxY2: y2, - sourceType: SourceType.Exif, - }, - ], - [], - ); - expect(mocks.person.updateAll).toHaveBeenCalledWith([ - { id: 'random-uuid', ownerId: 'admin-id', faceAssetId: 'random-uuid' }, - ]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.PersonGenerateThumbnail, - data: { id: personStub.withName.id }, - }, - ]); - }, - ); - }); - - it('should handle invalid modify date', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ ModifyDate: '00:00:00.000' }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - modifyDate: expect.any(Date), - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should handle invalid rating value', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ Rating: 6 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - rating: null, - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should handle valid rating value', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ Rating: 5 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - rating: 5, - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should handle valid negative rating value', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ Rating: -1 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - rating: -1, - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should handle livePhotoCID not set', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.image.id); - expect(mocks.asset.findLivePhotoMatch).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalledWith( - expect.objectContaining({ visibility: AssetVisibility.Hidden }), - ); - expect(mocks.album.removeAssetsFromAll).not.toHaveBeenCalled(); - }); - - it('should handle not finding a match', async () => { - mocks.media.probe.mockResolvedValue(probeStub.videoStreamVertical2160p); - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.livePhotoMotionAsset); - mockReadTags({ ContentIdentifier: 'CID' }); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id); - expect(mocks.asset.findLivePhotoMatch).toHaveBeenCalledWith({ - livePhotoCID: 'CID', - ownerId: assetStub.livePhotoMotionAsset.ownerId, - otherAssetId: assetStub.livePhotoMotionAsset.id, - libraryId: null, - type: AssetType.Image, - }); - expect(mocks.asset.update).not.toHaveBeenCalledWith( - expect.objectContaining({ visibility: AssetVisibility.Hidden }), - ); - expect(mocks.album.removeAssetsFromAll).not.toHaveBeenCalled(); - }); - - it('should link photo and video', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.livePhotoStillAsset); - mocks.asset.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset); - mockReadTags({ ContentIdentifier: 'CID' }); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - - expect(mocks.assetJob.getForMetadataExtraction).toHaveBeenCalledWith(assetStub.livePhotoStillAsset.id); - expect(mocks.asset.findLivePhotoMatch).toHaveBeenCalledWith({ - livePhotoCID: 'CID', - ownerId: assetStub.livePhotoStillAsset.ownerId, - otherAssetId: assetStub.livePhotoStillAsset.id, - type: AssetType.Video, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoStillAsset.id, - livePhotoVideoId: assetStub.livePhotoMotionAsset.id, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: assetStub.livePhotoMotionAsset.id, - visibility: AssetVisibility.Hidden, - }); - expect(mocks.album.removeAssetsFromAll).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id]); - }); - - it('should notify clients on live photo link', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoStillAsset, - }); - mocks.asset.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset); - mockReadTags({ ContentIdentifier: 'CID' }); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - - expect(mocks.event.emit).toHaveBeenCalledWith('AssetHide', { - userId: assetStub.livePhotoMotionAsset.ownerId, - assetId: assetStub.livePhotoMotionAsset.id, - }); - }); - - it('should search by libraryId', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.livePhotoStillAsset, - libraryId: 'library-id', - }); - mocks.asset.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset); - mockReadTags({ ContentIdentifier: 'CID' }); - - await sut.handleMetadataExtraction({ id: assetStub.livePhotoStillAsset.id }); - - expect(mocks.event.emit).toHaveBeenCalledWith('AssetMetadataExtracted', { - assetId: assetStub.livePhotoStillAsset.id, - userId: assetStub.livePhotoStillAsset.ownerId, - }); - expect(mocks.asset.findLivePhotoMatch).toHaveBeenCalledWith({ - ownerId: 'user-id', - otherAssetId: 'live-photo-still-asset', - livePhotoCID: 'CID', - libraryId: 'library-id', - type: 'VIDEO', - }); - }); - - it.each([ - { - exif: { - Make: '1', - Model: '2', - Device: { Manufacturer: '3', ModelName: '4' }, - AndroidMake: '4', - AndroidModel: '5', - }, - expected: { make: '1', model: '2' }, - }, - { - exif: { Device: { Manufacturer: '1', ModelName: '2' }, AndroidMake: '3', AndroidModel: '4' }, - expected: { make: '1', model: '2' }, - }, - { exif: { AndroidMake: '1', AndroidModel: '2' }, expected: { make: '1', model: '2' } }, - ])('should read camera make and model $exif -> $expected', async ({ exif, expected }) => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags(exif); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith(expect.objectContaining(expected), { - lockedPropertiesBehavior: 'skip', - }); - }); - - it.each([ - { exif: {}, expected: null }, - { exif: { LensID: '1', LensSpec: '2', LensType: '3', LensModel: '4' }, expected: '1' }, - { exif: { LensSpec: '2', LensType: '3', LensModel: '4' }, expected: '3' }, - { exif: { LensSpec: '2', LensModel: '4' }, expected: '2' }, - { exif: { LensModel: '4' }, expected: '4' }, - { exif: { LensID: '----' }, expected: null }, - { exif: { LensID: 'Unknown (0 ff ff)' }, expected: null }, - { - exif: { LensID: 'Unknown (E1 40 19 36 2C 35 DF 0E) Tamron 10-24mm f/3.5-4.5 Di II VC HLD (B023) ?' }, - expected: null, - }, - { exif: { LensID: ' Unknown 6-30mm' }, expected: null }, - { exif: { LensID: '' }, expected: null }, - ])('should read camera lens information $exif -> $expected', async ({ exif, expected }) => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags(exif); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - expect.objectContaining({ - lensModel: expected, - }), - { lockedPropertiesBehavior: 'skip' }, - ); - }); - - it('should properly set width/height for normal images', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ ImageWidth: 1000, ImageHeight: 2000 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - width: 1000, - height: 2000, - }), - ); - }); - - it('should properly swap asset width/height for rotated images', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue(assetStub.image); - mockReadTags({ ImageWidth: 1000, ImageHeight: 2000, Orientation: 6 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.update).toHaveBeenCalledWith( - expect.objectContaining({ - width: 2000, - height: 1000, - }), - ); - }); - - it('should not overwrite existing width/height if they already exist', async () => { - mocks.assetJob.getForMetadataExtraction.mockResolvedValue({ - ...assetStub.image, - width: 1920, - height: 1080, - }); - mockReadTags({ ImageWidth: 1280, ImageHeight: 720 }); - - await sut.handleMetadataExtraction({ id: assetStub.image.id }); - expect(mocks.asset.update).not.toHaveBeenCalledWith( - expect.objectContaining({ - width: 1280, - height: 720, - }), - ); - }); - }); - - describe('handleQueueSidecar', () => { - it('should queue assets with sidecar files', async () => { - mocks.assetJob.streamForSidecar.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueSidecar({ force: true }); - expect(mocks.assetJob.streamForSidecar).toHaveBeenCalledWith(true); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.SidecarCheck, - data: { id: assetStub.sidecar.id }, - }, - ]); - }); - - it('should queue assets without sidecar files', async () => { - mocks.assetJob.streamForSidecar.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueSidecar({ force: false }); - - expect(mocks.assetJob.streamForSidecar).toHaveBeenCalledWith(false); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.SidecarCheck, - data: { id: assetStub.image.id }, - }, - ]); - }); - }); - - describe('handleSidecarCheck', () => { - it('should do nothing if asset could not be found', async () => { - mocks.assetJob.getForSidecarCheckJob.mockResolvedValue(void 0); - - await expect(sut.handleSidecarCheck({ id: assetStub.image.id })).resolves.toBeUndefined(); - - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should detect a new sidecar at .jpg.xmp', async () => { - const asset = forSidecarJob({ originalPath: '/path/to/IMG_123.jpg', files: [] }); - - mocks.assetJob.getForSidecarCheckJob.mockResolvedValue(asset); - mocks.storage.checkFileExists.mockResolvedValueOnce(true); - - await expect(sut.handleSidecarCheck({ id: asset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.upsertFile).toHaveBeenCalledWith({ - assetId: asset.id, - type: AssetFileType.Sidecar, - path: '/path/to/IMG_123.jpg.xmp', - }); - }); - - it('should detect a new sidecar at .xmp', async () => { - const asset = forSidecarJob({ - originalPath: '/path/to/IMG_123.jpg', - files: [], - }); - - mocks.assetJob.getForSidecarCheckJob.mockResolvedValue(asset); - mocks.storage.checkFileExists.mockResolvedValueOnce(false); - mocks.storage.checkFileExists.mockResolvedValueOnce(true); - - await expect(sut.handleSidecarCheck({ id: asset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.upsertFile).toHaveBeenCalledWith({ - assetId: asset.id, - type: AssetFileType.Sidecar, - path: '/path/to/IMG_123.xmp', - }); - }); - - it('should unset sidecar path if file no longer exist', async () => { - const asset = forSidecarJob({ - originalPath: '/path/to/IMG_123.jpg', - files: [{ id: 'sidecar', path: '/path/to/IMG_123.jpg.xmp', type: AssetFileType.Sidecar, isEdited: false }], - }); - mocks.assetJob.getForSidecarCheckJob.mockResolvedValue(asset); - mocks.storage.checkFileExists.mockResolvedValue(false); - - await expect(sut.handleSidecarCheck({ id: asset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.asset.deleteFile).toHaveBeenCalledWith({ assetId: asset.id, type: AssetFileType.Sidecar }); - }); - - it('should do nothing if the sidecar file still exists', async () => { - const asset = forSidecarJob({ - originalPath: '/path/to/IMG_123.jpg', - files: [{ id: 'sidecar', path: '/path/to/IMG_123.jpg.xmp', type: AssetFileType.Sidecar, isEdited: false }], - }); - - mocks.assetJob.getForSidecarCheckJob.mockResolvedValue(asset); - mocks.storage.checkFileExists.mockResolvedValueOnce(true); - - await expect(sut.handleSidecarCheck({ id: asset.id })).resolves.toBe(JobStatus.Skipped); - - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.asset.deleteFile).not.toHaveBeenCalled(); - }); - }); - - describe('handleSidecarWrite', () => { - it('should skip assets that no longer exist', async () => { - mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([]); - mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(void 0); - await expect(sut.handleSidecarWrite({ id: 'asset-123' })).resolves.toBe(JobStatus.Failed); - expect(mocks.metadata.writeTags).not.toHaveBeenCalled(); - }); - - it('should skip jobs with no metadata', async () => { - mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([]); - const asset = factory.jobAssets.sidecarWrite(); - mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset); - await expect(sut.handleSidecarWrite({ id: asset.id })).resolves.toBe(JobStatus.Skipped); - expect(mocks.metadata.writeTags).not.toHaveBeenCalled(); - }); - - it('should write tags', async () => { - const asset = factory.jobAssets.sidecarWrite(); - const description = 'this is a description'; - const gps = 12; - const date = '2023-11-21T22:56:12.196-06:00'; - - mocks.assetJob.getLockedPropertiesForMetadataExtraction.mockResolvedValue([ - 'description', - 'latitude', - 'longitude', - 'dateTimeOriginal', - 'timeZone', - ]); - mocks.assetJob.getForSidecarWriteJob.mockResolvedValue(asset); - await expect( - sut.handleSidecarWrite({ - id: asset.id, - }), - ).resolves.toBe(JobStatus.Success); - expect(mocks.metadata.writeTags).toHaveBeenCalledWith(asset.files[0].path, { - DateTimeOriginal: date, - Description: description, - ImageDescription: description, - GPSLatitude: gps, - GPSLongitude: gps, - }); - expect(mocks.asset.unlockProperties).toHaveBeenCalledWith(asset.id, [ - 'description', - 'latitude', - 'longitude', - 'dateTimeOriginal', - 'timeZone', - ]); - }); - }); - - describe('firstDateTime', () => { - it('should ignore date-only tags like GPSDateStamp', () => { - const tags = { - GPSDateStamp: '2023:08:08', // Date-only tag, should be ignored - SonyDateTime2: '2023:07:07 07:00:00', - }; - - const result = firstDateTime(tags); - expect(result?.tag).toBe('SonyDateTime2'); - expect(result?.dateTime?.toISOString()).toBe('2023-07-07T07:00:00'); - }); - - it('should respect full priority order with all date tags present', () => { - const tags = { - // SubSec and standard EXIF date tags - SubSecDateTimeOriginal: '2023:01:01 01:00:00', - SubSecCreateDate: '2023:02:02 02:00:00', - SubSecMediaCreateDate: '2023:03:03 03:00:00', - DateTimeOriginal: '2023:04:04 04:00:00', - CreateDate: '2023:05:05 05:00:00', - MediaCreateDate: '2023:06:06 06:00:00', - CreationDate: '2023:07:07 07:00:00', - DateTimeCreated: '2023:08:08 08:00:00', - - // Additional date tags - TimeCreated: '2023:09:09 09:00:00', - GPSDateTime: '2023:10:10 10:00:00', - DateTimeUTC: '2023:11:11 11:00:00', - GPSDateStamp: '2023:12:12', // Date-only tag, should be ignored - SonyDateTime2: '2023:13:13 13:00:00', - - // Non-standard tag - SourceImageCreateTime: '2023:14:14 14:00:00', - }; - - const result = firstDateTime(tags); - // Should use SubSecDateTimeOriginal as it has highest priority - expect(result?.tag).toBe('SubSecDateTimeOriginal'); - expect(result?.dateTime?.toISOString()).toBe('2023-01-01T01:00:00'); - }); - - it('should handle missing SubSec tags and use available date tags', () => { - const tags = { - // Standard date tags - CreationDate: '2023:07:07 07:00:00', - DateTimeCreated: '2023:08:08 08:00:00', - - // Additional date tags - TimeCreated: '2023:09:09 09:00:00', - GPSDateTime: '2023:10:10 10:00:00', - DateTimeUTC: '2023:11:11 11:00:00', - GPSDateStamp: '2023:12:12', // Date-only tag, should be ignored - SonyDateTime2: '2023:13:13 13:00:00', - }; - - const result = firstDateTime(tags); - // Should use CreationDate when available - expect(result?.tag).toBe('CreationDate'); - expect(result?.dateTime?.toISOString()).toBe('2023-07-07T07:00:00'); - }); - - it('should handle invalid date formats gracefully', () => { - const tags = { - TimeCreated: 'invalid-date', - GPSDateTime: '2023:10:10 10:00:00', - DateTimeUTC: 'also-invalid', - SonyDateTime2: '2023:13:13 13:00:00', - }; - - const result = firstDateTime(tags); - // Should skip invalid dates and use the first valid one - expect(result?.tag).toBe('GPSDateTime'); - expect(result?.dateTime?.toISOString()).toBe('2023-10-10T10:00:00'); - }); - - it('should prefer CreationDate over CreateDate', () => { - const tags = { - CreationDate: '2025:05:24 18:26:20+02:00', - CreateDate: '2025:08:27 08:45:40', - }; - - const result = firstDateTime(tags); - expect(result?.tag).toBe('CreationDate'); - expect(result?.dateTime?.toDate()?.toISOString()).toBe('2025-05-24T16:26:20.000Z'); - }); - }); -}); diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts deleted file mode 100644 index 4113025914..0000000000 --- a/server/src/services/metadata.service.ts +++ /dev/null @@ -1,1037 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ContainerDirectoryItem, ExifDateTime, Tags } from 'exiftool-vendored'; -import { Insertable } from 'kysely'; -import _ from 'lodash'; -import { DateTime, Duration } from 'luxon'; -import { Stats } from 'node:fs'; -import { constants } from 'node:fs/promises'; -import { join, parse } from 'node:path'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; -import { Asset, AssetFace, AssetFile } from 'src/database'; -import { OnEvent, OnJob } from 'src/decorators'; -import { - AssetFileType, - AssetType, - AssetVisibility, - DatabaseLock, - ExifOrientation, - ImmichWorker, - JobName, - JobStatus, - QueueName, - SourceType, -} from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { ReverseGeocodeResult } from 'src/repositories/map.repository'; -import { ImmichTags } from 'src/repositories/metadata.repository'; -import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { PersonTable } from 'src/schema/tables/person.table'; -import { BaseService } from 'src/services/base.service'; -import { JobItem, JobOf } from 'src/types'; -import { getAssetFiles } from 'src/utils/asset.util'; -import { isAssetChecksumConstraint } from 'src/utils/database'; -import { mergeTimeZone } from 'src/utils/date'; -import { mimeTypes } from 'src/utils/mime-types'; -import { isFaceImportEnabled } from 'src/utils/misc'; -import { upsertTags } from 'src/utils/tag'; - -/** look for a date from these tags (in order) */ -const EXIF_DATE_TAGS: Array = [ - 'SubSecDateTimeOriginal', - 'SubSecCreateDate', - 'DateTimeOriginal', - 'CreationDate', - 'CreateDate', - 'MediaCreateDate', - 'DateTimeCreated', - 'GPSDateTime', - 'DateTimeUTC', - 'SonyDateTime2', - // Undocumented, non-standard tag from insta360 in xmp.GPano namespace - 'SourceImageCreateTime' as keyof ImmichTags, -]; - -export function firstDateTime(tags: ImmichTags) { - for (const tag of EXIF_DATE_TAGS) { - const tagValue = tags?.[tag]; - - if (tagValue instanceof ExifDateTime) { - return { - tag, - dateTime: tagValue, - }; - } - - if (typeof tagValue !== 'string') { - continue; - } - - const exifDateTime = ExifDateTime.fromEXIF(tagValue); - if (exifDateTime) { - return { - tag, - dateTime: exifDateTime, - }; - } - } -} - -const validate = (value: T): NonNullable | null => { - // handle lists of numbers - if (Array.isArray(value)) { - value = value[0]; - } - - if (typeof value === 'string') { - // string means a failure to parse a number, throw out result - return null; - } - - if (typeof value === 'number' && (Number.isNaN(value) || !Number.isFinite(value))) { - return null; - } - - return value ?? null; -}; - -const validateRange = (value: number | undefined, min: number, max: number): NonNullable | null => { - // reutilizes the validate function - const val = validate(value); - - // check if the value is within the range - if (val == null || val < min || val > max) { - return null; - } - - return Math.round(val); -}; - -const getLensModel = (exifTags: ImmichTags): string | null => { - const lensModel = String( - exifTags.LensID ?? exifTags.LensType ?? exifTags.LensSpec ?? exifTags.LensModel ?? '', - ).trim(); - if (lensModel === '----') { - return null; - } - if (lensModel.startsWith('Unknown')) { - return null; - } - return lensModel || null; -}; - -type ImmichTagsWithFaces = ImmichTags & { RegionInfo: NonNullable }; - -type Dates = { - dateTimeOriginal: Date; - localDateTime: Date; -}; - -@Injectable() -export class MetadataService extends BaseService { - @OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Microservices] }) - async onBootstrap() { - this.logger.log('Bootstrapping metadata service'); - await this.init(); - } - - @OnEvent({ name: 'AppShutdown' }) - async onShutdown() { - await this.metadataRepository.teardown(); - } - - @OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] }) - onConfigInit({ newConfig }: ArgOf<'ConfigInit'>) { - this.metadataRepository.setMaxConcurrency(newConfig.job.metadataExtraction.concurrency); - } - - @OnEvent({ name: 'ConfigUpdate', workers: [ImmichWorker.Microservices], server: true }) - onConfigUpdate({ newConfig }: ArgOf<'ConfigUpdate'>) { - this.metadataRepository.setMaxConcurrency(newConfig.job.metadataExtraction.concurrency); - } - - private async init() { - this.logger.log('Initializing metadata service'); - - try { - await this.jobRepository.pause(QueueName.MetadataExtraction); - await this.databaseRepository.withLock(DatabaseLock.GeodataImport, () => this.mapRepository.init()); - await this.jobRepository.resume(QueueName.MetadataExtraction); - - this.logger.log(`Initialized local reverse geocoder`); - } catch (error: Error | any) { - this.logger.error(`Unable to initialize reverse geocoding: ${error}`, error?.stack); - throw new Error(`Metadata service init failed`); - } - } - - private async linkLivePhotos( - asset: { id: string; type: AssetType; ownerId: string; libraryId: string | null }, - exifInfo: Insertable, - ): Promise { - if (!exifInfo.livePhotoCID) { - return; - } - - const otherType = asset.type === AssetType.Video ? AssetType.Image : AssetType.Video; - const match = await this.assetRepository.findLivePhotoMatch({ - livePhotoCID: exifInfo.livePhotoCID, - ownerId: asset.ownerId, - libraryId: asset.libraryId, - otherAssetId: asset.id, - type: otherType, - }); - - if (!match) { - return; - } - - const [photoAsset, motionAsset] = asset.type === AssetType.Image ? [asset, match] : [match, asset]; - await Promise.all([ - this.assetRepository.update({ id: photoAsset.id, livePhotoVideoId: motionAsset.id }), - this.assetRepository.update({ id: motionAsset.id, visibility: AssetVisibility.Hidden }), - this.albumRepository.removeAssetsFromAll([motionAsset.id]), - ]); - - await this.eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId: motionAsset.ownerId }); - } - - private isOrientationSidewards(orientation: ExifOrientation | number): boolean { - return [ - ExifOrientation.MirrorHorizontalRotate270CW, - ExifOrientation.Rotate90CW, - ExifOrientation.MirrorHorizontalRotate90CW, - ExifOrientation.Rotate270CW, - ].includes(orientation); - } - - @OnJob({ name: JobName.AssetExtractMetadataQueueAll, queue: QueueName.MetadataExtraction }) - async handleQueueMetadataExtraction(job: JobOf): Promise { - const { force } = job; - - let queue: { name: JobName.AssetExtractMetadata; data: { id: string } }[] = []; - for await (const asset of this.assetJobRepository.streamForMetadataExtraction(force)) { - queue.push({ name: JobName.AssetExtractMetadata, data: { id: asset.id } }); - - if (queue.length >= JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(queue); - queue = []; - } - } - - await this.jobRepository.queueAll(queue); - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetExtractMetadata, queue: QueueName.MetadataExtraction }) - async handleMetadataExtraction(data: JobOf) { - const [{ metadata, reverseGeocoding }, asset] = await Promise.all([ - this.getConfig({ withCache: true }), - this.assetJobRepository.getForMetadataExtraction(data.id), - ]); - - if (!asset) { - return; - } - - const [exifTags, stats] = await Promise.all([ - this.getExifTags(asset), - this.storageRepository.stat(asset.originalPath), - ]); - this.logger.verbose('Exif Tags', exifTags); - - const dates = this.getDates(asset, exifTags, stats); - - const { width, height } = this.getImageDimensions(exifTags); - let geo: ReverseGeocodeResult = { country: null, state: null, city: null }, - latitude: number | null = null, - longitude: number | null = null; - if (this.hasGeo(exifTags)) { - latitude = Number(exifTags.GPSLatitude); - longitude = Number(exifTags.GPSLongitude); - if (reverseGeocoding.enabled) { - geo = await this.mapRepository.reverseGeocode({ latitude, longitude }); - } - } - - const tags = this.getTagList(exifTags); - - const exifData: Insertable = { - assetId: asset.id, - - // dates - dateTimeOriginal: dates.dateTimeOriginal, - modifyDate: stats.mtime, - timeZone: dates.timeZone, - - // gps - latitude, - longitude, - country: geo.country, - state: geo.state, - city: geo.city, - - // image/file - fileSizeInByte: stats.size, - exifImageHeight: validate(height), - exifImageWidth: validate(width), - orientation: validate(exifTags.Orientation)?.toString() ?? null, - projectionType: exifTags.ProjectionType ? String(exifTags.ProjectionType).toUpperCase() : null, - bitsPerSample: this.getBitsPerSample(exifTags), - colorspace: exifTags.ColorSpace ?? null, - - // camera - make: exifTags.Make ?? exifTags?.Device?.Manufacturer ?? exifTags.AndroidMake ?? null, - model: exifTags.Model ?? exifTags?.Device?.ModelName ?? exifTags.AndroidModel ?? null, - fps: validate(Number.parseFloat(exifTags.VideoFrameRate!)), - iso: validate(exifTags.ISO) as number, - exposureTime: exifTags.ExposureTime ?? null, - lensModel: getLensModel(exifTags), - fNumber: validate(exifTags.FNumber), - focalLength: validate(exifTags.FocalLength), - - // comments - description: String(exifTags.ImageDescription || exifTags.Description || '').trim(), - profileDescription: exifTags.ProfileDescription || null, - rating: validateRange(exifTags.Rating, -1, 5), - - // grouping - livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null, - autoStackId: this.getAutoStackId(exifTags), - - tags: tags.length > 0 ? tags : null, - }; - - const isSidewards = exifTags.Orientation && this.isOrientationSidewards(exifTags.Orientation); - const assetWidth = isSidewards ? validate(height) : validate(width); - const assetHeight = isSidewards ? validate(width) : validate(height); - - const promises: Promise[] = [ - this.assetRepository.update({ - id: asset.id, - duration: this.getDuration(exifTags), - localDateTime: dates.localDateTime, - fileCreatedAt: dates.dateTimeOriginal ?? undefined, - fileModifiedAt: stats.mtime, - - // only update the dimensions if they don't already exist - // we don't want to overwrite width/height that are modified by edits - width: asset.width == null ? assetWidth : undefined, - height: asset.height == null ? assetHeight : undefined, - }), - ]; - - await this.assetRepository.upsertExif(exifData, { lockedPropertiesBehavior: 'skip' }); - await this.applyTagList(asset); - - if (this.isMotionPhoto(asset, exifTags)) { - promises.push(this.applyMotionPhotos(asset, exifTags, dates, stats)); - } - - if (isFaceImportEnabled(metadata) && this.hasTaggedFaces(exifTags)) { - promises.push(this.applyTaggedFaces(asset, exifTags)); - } - - await Promise.all(promises); - if (exifData.livePhotoCID) { - await this.linkLivePhotos(asset, exifData); - } - - await this.assetRepository.upsertJobStatus({ assetId: asset.id, metadataExtractedAt: new Date() }); - - await this.eventRepository.emit('AssetMetadataExtracted', { - assetId: asset.id, - userId: asset.ownerId, - source: data.source, - }); - } - - @OnJob({ name: JobName.SidecarQueueAll, queue: QueueName.Sidecar }) - async handleQueueSidecar({ force }: JobOf): Promise { - let jobs: JobItem[] = []; - const queueAll = async () => { - await this.jobRepository.queueAll(jobs); - jobs = []; - }; - - const assets = this.assetJobRepository.streamForSidecar(force); - for await (const asset of assets) { - jobs.push({ name: JobName.SidecarCheck, data: { id: asset.id } }); - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await queueAll(); - } - } - - await queueAll(); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.SidecarCheck, queue: QueueName.Sidecar }) - async handleSidecarCheck({ id }: JobOf): Promise { - const asset = await this.assetJobRepository.getForSidecarCheckJob(id); - if (!asset) { - return; - } - - let sidecarPath = null; - for (const candidate of this.getSidecarCandidates(asset)) { - const exists = await this.storageRepository.checkFileExists(candidate, constants.R_OK); - if (!exists) { - continue; - } - - sidecarPath = candidate; - break; - } - - const { sidecarFile } = getAssetFiles(asset.files); - - const isChanged = sidecarPath !== sidecarFile?.path; - - if (sidecarFile?.path || sidecarPath) { - this.logger.debug( - `Sidecar check found old=${sidecarFile?.path}, new=${sidecarPath} will ${isChanged ? 'update' : 'do nothing for'} asset ${asset.id}: ${asset.originalPath}`, - ); - } else { - this.logger.verbose(`No sidecars found for asset ${asset.id}: ${asset.originalPath}`); - } - - if (!isChanged) { - return JobStatus.Skipped; - } - - await (sidecarPath === null - ? this.assetRepository.deleteFile({ assetId: asset.id, type: AssetFileType.Sidecar }) - : this.assetRepository.upsertFile({ assetId: asset.id, type: AssetFileType.Sidecar, path: sidecarPath })); - - return JobStatus.Success; - } - - @OnEvent({ name: 'AssetTag' }) - async handleTagAsset({ assetId }: ArgOf<'AssetTag'>) { - await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id: assetId } }); - } - - @OnEvent({ name: 'AssetUntag' }) - async handleUntagAsset({ assetId }: ArgOf<'AssetUntag'>) { - await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id: assetId } }); - } - - @OnJob({ name: JobName.SidecarWrite, queue: QueueName.Sidecar }) - async handleSidecarWrite(job: JobOf): Promise { - const { id } = job; - const asset = await this.assetJobRepository.getForSidecarWriteJob(id); - if (!asset) { - return JobStatus.Failed; - } - - const lockedProperties = await this.assetJobRepository.getLockedPropertiesForMetadataExtraction(id); - - const { sidecarFile } = getAssetFiles(asset.files); - const sidecarPath = sidecarFile?.path || `${asset.originalPath}.xmp`; - - const { description, dateTimeOriginal, latitude, longitude, rating, tags, timeZone } = _.pick( - { - description: asset.exifInfo.description, - // the kysely type is wrong here; fixed in 0.28.3 - dateTimeOriginal: asset.exifInfo.dateTimeOriginal as string | null, - latitude: asset.exifInfo.latitude, - longitude: asset.exifInfo.longitude, - rating: asset.exifInfo.rating, - tags: asset.exifInfo.tags, - timeZone: asset.exifInfo.timeZone, - }, - lockedProperties, - ); - - const exif = _.omitBy( - { - Description: description, - ImageDescription: description, - DateTimeOriginal: mergeTimeZone(dateTimeOriginal, timeZone)?.toISO(), - GPSLatitude: latitude, - GPSLongitude: longitude, - Rating: rating, - TagsList: tags?.length ? tags : undefined, - }, - _.isUndefined, - ); - - if (Object.keys(exif).length === 0) { - return JobStatus.Skipped; - } - - await this.metadataRepository.writeTags(sidecarPath, exif); - - if (asset.files.length === 0) { - await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Sidecar, path: sidecarPath }); - } - - await this.assetRepository.unlockProperties(asset.id, lockedProperties); - - return JobStatus.Success; - } - - private getSidecarCandidates({ files, originalPath }: { files: AssetFile[]; originalPath: string }) { - const candidates: string[] = []; - - const { sidecarFile } = getAssetFiles(files); - if (sidecarFile?.path) { - candidates.push(sidecarFile.path); - } - - const assetPath = parse(originalPath); - - candidates.push( - // IMG_123.jpg.xmp - `${originalPath}.xmp`, - // IMG_123.xmp - `${join(assetPath.dir, assetPath.name)}.xmp`, - ); - - return candidates; - } - - private getImageDimensions(exifTags: ImmichTags): { width?: number; height?: number } { - /* - * The "true" values for width and height are a bit hidden, depending on the camera model and file format. - * For RAW images in the CR2 or RAF format, the "ImageSize" value seems to be correct, - * but ImageWidth and ImageHeight are not correct (they contain the dimensions of the preview image). - */ - let [width, height] = - exifTags.ImageSize?.toString() - ?.split('x') - ?.map((dim) => Number.parseInt(dim) || undefined) ?? []; - if (!width || !height) { - [width, height] = [exifTags.ImageWidth, exifTags.ImageHeight]; - } - return { width, height }; - } - - private async getExifTags(asset: { originalPath: string; files: AssetFile[]; type: AssetType }): Promise { - const { sidecarFile } = getAssetFiles(asset.files); - - const [mediaTags, sidecarTags, videoTags] = await Promise.all([ - this.metadataRepository.readTags(asset.originalPath), - sidecarFile ? this.metadataRepository.readTags(sidecarFile.path) : null, - asset.type === AssetType.Video ? this.getVideoTags(asset.originalPath) : null, - ]); - - // prefer dates from sidecar tags - if (sidecarTags) { - const result = firstDateTime(sidecarTags); - const sidecarDate = result?.dateTime; - if (sidecarDate) { - for (const tag of EXIF_DATE_TAGS) { - delete mediaTags[tag]; - } - } - } - - // prefer duration from video tags - // don't save duration if asset is definitely not an animated image (see e.g. CR3 with Duration: 1s) - if (videoTags || !mimeTypes.isPossiblyAnimatedImage(asset.originalPath)) { - delete mediaTags.Duration; - } - - // never use duration from sidecar - delete sidecarTags?.Duration; - - return { ...mediaTags, ...videoTags, ...sidecarTags }; - } - - private getTagList(exifTags: ImmichTags): string[] { - let tags: string[]; - if (exifTags.TagsList) { - tags = exifTags.TagsList.map(String); - } else if (exifTags.HierarchicalSubject) { - tags = exifTags.HierarchicalSubject.map((tag) => - // convert | to / - typeof tag === 'number' - ? String(tag) - : tag - .split('|') - .map((tag) => tag.replaceAll('/', '|')) - .join('/'), - ); - } else if (exifTags.Keywords) { - let keywords = exifTags.Keywords; - if (!Array.isArray(keywords)) { - keywords = [keywords]; - } - tags = keywords.map(String); - } else { - tags = []; - } - return tags; - } - - private async applyTagList({ id, ownerId }: { id: string; ownerId: string }) { - const asset = await this.assetRepository.getById(id, { exifInfo: true }); - const results = await upsertTags(this.tagRepository, { - userId: ownerId, - tags: asset?.exifInfo?.tags ?? [], - }); - await this.tagRepository.replaceAssetTags( - id, - results.map((tag) => tag.id), - ); - } - - private isMotionPhoto(asset: { type: AssetType }, tags: ImmichTags): boolean { - return asset.type === AssetType.Image && !!(tags.MotionPhoto || tags.MicroVideo); - } - - private async applyMotionPhotos(asset: Asset, tags: ImmichTags, dates: Dates, stats: Stats) { - const isMotionPhoto = tags.MotionPhoto; - const isMicroVideo = tags.MicroVideo; - const videoOffset = tags.MicroVideoOffset; - const hasMotionPhotoVideo = tags.MotionPhotoVideo; - const hasEmbeddedVideoFile = tags.EmbeddedVideoType === 'MotionPhoto_Data' && tags.EmbeddedVideoFile; - const directory = Array.isArray(tags.ContainerDirectory) - ? (tags.ContainerDirectory as ContainerDirectoryItem[]) - : null; - - let length = 0; - let padding = 0; - - if (isMotionPhoto && directory) { - for (const entry of directory) { - if (entry?.Item?.Semantic === 'MotionPhoto') { - length = entry.Item.Length ?? 0; - padding = entry.Item.Padding ?? 0; - break; - } - } - } - - if (isMicroVideo && typeof videoOffset === 'number') { - length = videoOffset; - } - - if (!length && !hasEmbeddedVideoFile && !hasMotionPhotoVideo) { - return; - } - - this.logger.debug(`Starting motion photo video extraction for asset ${asset.id}: ${asset.originalPath}`); - - try { - const position = stats.size - length - padding; - let video: Buffer; - // Samsung MotionPhoto video extraction - // HEIC-encoded - if (hasMotionPhotoVideo) { - video = await this.metadataRepository.extractBinaryTag(asset.originalPath, 'MotionPhotoVideo'); - } - // JPEG-encoded; HEIC also contains these tags, so this conditional must come second - else if (hasEmbeddedVideoFile) { - video = await this.metadataRepository.extractBinaryTag(asset.originalPath, 'EmbeddedVideoFile'); - } - // Default video extraction - else { - video = await this.storageRepository.readFile(asset.originalPath, { - buffer: Buffer.alloc(length), - position, - length, - }); - } - const checksum = this.cryptoRepository.hashSha1(video); - const checksumQuery = { ownerId: asset.ownerId, libraryId: asset.libraryId ?? undefined, checksum }; - - let motionAsset = await this.assetRepository.getByChecksum(checksumQuery); - let isNewMotionAsset = false; - - if (!motionAsset) { - try { - const motionAssetId = this.cryptoRepository.randomUUID(); - motionAsset = await this.assetRepository.create({ - id: motionAssetId, - libraryId: asset.libraryId, - type: AssetType.Video, - fileCreatedAt: dates.dateTimeOriginal, - fileModifiedAt: stats.mtime, - localDateTime: dates.localDateTime, - checksum, - ownerId: asset.ownerId, - originalPath: StorageCore.getAndroidMotionPath(asset, motionAssetId), - originalFileName: `${parse(asset.originalFileName).name}.mp4`, - visibility: AssetVisibility.Hidden, - deviceAssetId: 'NONE', - deviceId: 'NONE', - }); - - isNewMotionAsset = true; - - if (!asset.isExternal) { - await this.userRepository.updateUsage(asset.ownerId, video.byteLength); - } - } catch (error) { - if (!isAssetChecksumConstraint(error)) { - throw error; - } - - motionAsset = await this.assetRepository.getByChecksum(checksumQuery); - if (!motionAsset) { - this.logger.warn(`Unable to find existing motion video asset for ${asset.id}: ${asset.originalPath}`); - return; - } - } - } - - if (!isNewMotionAsset) { - this.logger.debugFn(() => { - const base64Checksum = checksum.toString('base64'); - return `Motion asset with checksum ${base64Checksum} already exists for asset ${asset.id}: ${asset.originalPath}`; - }); - } - - // Hide the motion photo video asset if it's not already hidden to prepare for linking - if (motionAsset.visibility === AssetVisibility.Timeline) { - await this.assetRepository.update({ - id: motionAsset.id, - visibility: AssetVisibility.Hidden, - }); - this.logger.log(`Hid unlinked motion photo video asset (${motionAsset.id})`); - } - - if (asset.livePhotoVideoId !== motionAsset.id) { - await this.assetRepository.update({ id: asset.id, livePhotoVideoId: motionAsset.id }); - - // If the asset already had an associated livePhotoVideo, delete it, because - // its checksum doesn't match the checksum of the motionAsset we just extracted - // (if it did, getByChecksum() would've returned a motionAsset with the same ID as livePhotoVideoId) - // note asset.livePhotoVideoId is not motionAsset.id yet - if (asset.livePhotoVideoId) { - await this.jobRepository.queue({ - name: JobName.AssetDelete, - data: { id: asset.livePhotoVideoId, deleteOnDisk: true }, - }); - this.logger.log(`Removed old motion photo video asset (${asset.livePhotoVideoId})`); - } - } - - // write extracted motion video to disk, especially if the encoded-video folder has been deleted - const existsOnDisk = await this.storageRepository.checkFileExists(motionAsset.originalPath); - if (!existsOnDisk) { - this.storageCore.ensureFolders(motionAsset.originalPath); - await this.storageRepository.createFile(motionAsset.originalPath, video); - this.logger.log(`Wrote motion photo video to ${motionAsset.originalPath}`); - - await this.handleMetadataExtraction({ id: motionAsset.id }); - await this.jobRepository.queue({ name: JobName.AssetEncodeVideo, data: { id: motionAsset.id } }); - } - - this.logger.debug(`Finished motion photo video extraction for asset ${asset.id}: ${asset.originalPath}`); - } catch (error: Error | any) { - this.logger.error( - `Failed to extract motion video for ${asset.id}: ${asset.originalPath}: ${error}`, - error?.stack, - ); - } - } - - private hasTaggedFaces(tags: ImmichTags): tags is ImmichTagsWithFaces { - return ( - tags.RegionInfo !== undefined && tags.RegionInfo.AppliedToDimensions && tags.RegionInfo.RegionList.length > 0 - ); - } - - private orientRegionInfo( - regionInfo: ImmichTagsWithFaces['RegionInfo'], - orientation: ExifOrientation | undefined, - ): ImmichTagsWithFaces['RegionInfo'] { - // skip default Orientation - if (orientation === undefined || orientation === ExifOrientation.Horizontal) { - return regionInfo; - } - - const isSidewards = this.isOrientationSidewards(orientation); - - // swap image dimensions in AppliedToDimensions if orientation is sidewards - const adjustedAppliedToDimensions = isSidewards - ? { - ...regionInfo.AppliedToDimensions, - W: regionInfo.AppliedToDimensions.H, - H: regionInfo.AppliedToDimensions.W, - } - : regionInfo.AppliedToDimensions; - - // update area coordinates and dimensions in RegionList assuming "normalized" unit as per MWG guidelines - const adjustedRegionList = regionInfo.RegionList.map((region) => { - let { X, Y, W, H } = region.Area; - switch (orientation) { - case ExifOrientation.MirrorHorizontal: { - X = 1 - X; - break; - } - case ExifOrientation.Rotate180: { - [X, Y] = [1 - X, 1 - Y]; - break; - } - case ExifOrientation.MirrorVertical: { - Y = 1 - Y; - break; - } - case ExifOrientation.MirrorHorizontalRotate270CW: { - [X, Y] = [Y, X]; - break; - } - case ExifOrientation.Rotate90CW: { - [X, Y] = [1 - Y, X]; - break; - } - case ExifOrientation.MirrorHorizontalRotate90CW: { - [X, Y] = [1 - Y, 1 - X]; - break; - } - case ExifOrientation.Rotate270CW: { - [X, Y] = [Y, 1 - X]; - break; - } - } - if (isSidewards) { - [W, H] = [H, W]; - } - return { - ...region, - Area: { ...region.Area, X, Y, W, H }, - }; - }); - - return { - ...regionInfo, - AppliedToDimensions: adjustedAppliedToDimensions, - RegionList: adjustedRegionList, - }; - } - - private async applyTaggedFaces( - asset: { id: string; ownerId: string; faces: AssetFace[]; originalPath: string }, - tags: ImmichTags, - ) { - if (!tags.RegionInfo?.AppliedToDimensions || tags.RegionInfo.RegionList.length === 0) { - return; - } - - const facesToAdd: (Insertable & { assetId: string })[] = []; - const existingNames = await this.personRepository.getDistinctNames(asset.ownerId, { withHidden: true }); - const existingNameMap = new Map(existingNames.map(({ id, name }) => [name.toLowerCase(), id])); - const missing: (Insertable & { ownerId: string })[] = []; - const missingWithFaceAsset: { id: string; ownerId: string; faceAssetId: string }[] = []; - - const adjustedRegionInfo = this.orientRegionInfo(tags.RegionInfo, tags.Orientation); - const imageWidth = adjustedRegionInfo.AppliedToDimensions.W; - const imageHeight = adjustedRegionInfo.AppliedToDimensions.H; - - for (const region of adjustedRegionInfo.RegionList) { - if (!region.Name) { - continue; - } - - const loweredName = region.Name.toLowerCase(); - const personId = existingNameMap.get(loweredName) || this.cryptoRepository.randomUUID(); - - const face = { - id: this.cryptoRepository.randomUUID(), - personId, - assetId: asset.id, - imageWidth, - imageHeight, - boundingBoxX1: Math.floor((region.Area.X - region.Area.W / 2) * imageWidth), - boundingBoxY1: Math.floor((region.Area.Y - region.Area.H / 2) * imageHeight), - boundingBoxX2: Math.floor((region.Area.X + region.Area.W / 2) * imageWidth), - boundingBoxY2: Math.floor((region.Area.Y + region.Area.H / 2) * imageHeight), - sourceType: SourceType.Exif, - }; - - facesToAdd.push(face); - if (!existingNameMap.has(loweredName)) { - missing.push({ id: personId, ownerId: asset.ownerId, name: region.Name }); - missingWithFaceAsset.push({ id: personId, ownerId: asset.ownerId, faceAssetId: face.id }); - } - } - - if (missing.length > 0) { - this.logger.debugFn(() => `Creating missing persons: ${missing.map((p) => `${p.name}/${p.id}`)}`); - const newPersonIds = await this.personRepository.createAll(missing); - const jobs = newPersonIds.map((id) => ({ name: JobName.PersonGenerateThumbnail, data: { id } }) as const); - await this.jobRepository.queueAll(jobs); - } - - const facesToRemove = asset.faces.filter((face) => face.sourceType === SourceType.Exif).map((face) => face.id); - if (facesToRemove.length > 0) { - this.logger.debug(`Removing ${facesToRemove.length} faces for asset ${asset.id}: ${asset.originalPath}`); - } - - if (facesToAdd.length > 0) { - this.logger.debug( - `Creating ${facesToAdd.length} faces from metadata for asset ${asset.id}: ${asset.originalPath}`, - ); - } - - if (facesToRemove.length > 0 || facesToAdd.length > 0) { - await this.personRepository.refreshFaces(facesToAdd, facesToRemove); - } - - if (missingWithFaceAsset.length > 0) { - await this.personRepository.updateAll(missingWithFaceAsset); - } - } - - private getDates( - asset: { id: string; originalPath: string; fileCreatedAt: Date }, - exifTags: ImmichTags, - stats: Stats, - ) { - const result = firstDateTime(exifTags); - const tag = result?.tag; - const dateTime = result?.dateTime; - if (dateTime) { - this.logger.verbose( - `Date and time is ${dateTime} using exifTag ${tag} for asset ${asset.id}: ${asset.originalPath}`, - ); - } else { - this.logger.verbose(`No exif date time information found for asset ${asset.id}: ${asset.originalPath}`); - } - - // timezone - let timeZone = exifTags.tz ?? null; - if (timeZone == null && dateTime?.rawValue?.endsWith('+00:00')) { - // exiftool-vendored returns "no timezone" information even though "+00:00" might be set explicitly - // https://github.com/photostructure/exiftool-vendored.js/issues/203 - timeZone = 'UTC+0'; - } - - if (timeZone) { - this.logger.verbose( - `Found timezone ${timeZone} via ${exifTags.tzSource} for asset ${asset.id}: ${asset.originalPath}`, - ); - } else { - this.logger.debug(`No timezone information found for asset ${asset.id}: ${asset.originalPath}`); - } - - let dateTimeOriginal = dateTime?.toDateTime(); - - // do not let JavaScript use local timezone - if (dateTimeOriginal && !dateTime?.hasZone) { - dateTimeOriginal = dateTimeOriginal.setZone('UTC', { keepLocalTime: true }); - } - - // align with whatever timeZone we chose - dateTimeOriginal = dateTimeOriginal?.setZone(timeZone ?? 'UTC'); - - // store as "local time" - let localDateTime = dateTimeOriginal?.setZone('UTC', { keepLocalTime: true }); - - if (!localDateTime || !dateTimeOriginal) { - // FileCreateDate is not available on linux, likely because exiftool hasn't integrated the statx syscall yet - // birthtime is not available in Docker on macOS, so it appears as 0 - const earliestDate = DateTime.fromMillis( - Math.min( - asset.fileCreatedAt.getTime(), - stats.birthtimeMs ? Math.min(stats.mtimeMs, stats.birthtimeMs) : stats.mtime.getTime(), - ), - ); - this.logger.debug( - `No exif date time found, falling back on ${earliestDate.toISO()}, earliest of file creation and modification for asset ${asset.id}: ${asset.originalPath}`, - ); - dateTimeOriginal = localDateTime = earliestDate; - } - - this.logger.verbose(`Found local date time ${localDateTime.toISO()} for asset ${asset.id}: ${asset.originalPath}`); - - return { - timeZone, - localDateTime: localDateTime.toJSDate(), - dateTimeOriginal: dateTimeOriginal.toJSDate(), - }; - } - - private hasGeo(tags: ImmichTags) { - const lat = Number(tags.GPSLatitude); - const lng = Number(tags.GPSLongitude); - return !Number.isNaN(lat) && !Number.isNaN(lng) && (lat !== 0 || lng !== 0); - } - - private getAutoStackId(tags: ImmichTags | null): string | null { - if (!tags) { - return null; - } - return tags.BurstID ?? tags.BurstUUID ?? tags.CameraBurstID ?? tags.MediaUniqueID ?? null; - } - - private getBitsPerSample(tags: ImmichTags): number | null { - const bitDepthTags = [ - tags.BitsPerSample, - tags.ComponentBitDepth, - tags.ImagePixelDepth, - tags.BitDepth, - tags.ColorBitDepth, - // `numericTags` doesn't parse values like '12 12 12' - ].map((tag) => (typeof tag === 'string' ? Number.parseInt(tag) : tag)); - - let bitsPerSample = bitDepthTags.find((tag) => typeof tag === 'number' && !Number.isNaN(tag)) ?? null; - if (bitsPerSample && bitsPerSample >= 24 && bitsPerSample % 3 === 0) { - bitsPerSample /= 3; // converts per-pixel bit depth to per-channel - } - - return bitsPerSample; - } - - private getDuration(tags: ImmichTags): string | null { - const duration = tags.Duration; - - if (typeof duration === 'string') { - return duration; - } - - if (typeof duration === 'number') { - return Duration.fromObject({ seconds: duration }).toFormat('hh:mm:ss.SSS'); - } - - return null; - } - - private async getVideoTags(originalPath: string) { - const { videoStreams, format } = await this.mediaRepository.probe(originalPath); - - const tags: Pick = {}; - - if (videoStreams[0]) { - // Set video dimensions - if (videoStreams[0].width) { - tags.ImageWidth = videoStreams[0].width; - } - if (videoStreams[0].height) { - tags.ImageHeight = videoStreams[0].height; - } - - switch (videoStreams[0].rotation) { - case -90: { - tags.Orientation = ExifOrientation.Rotate90CW; - break; - } - case 0: { - tags.Orientation = ExifOrientation.Horizontal; - break; - } - case 90: { - tags.Orientation = ExifOrientation.Rotate270CW; - break; - } - case 180: { - tags.Orientation = ExifOrientation.Rotate180; - break; - } - } - } - - if (format.duration) { - tags.Duration = format.duration; - } - - return tags; - } -} diff --git a/server/src/services/notification-admin.service.spec.ts b/server/src/services/notification-admin.service.spec.ts deleted file mode 100644 index c200897719..0000000000 --- a/server/src/services/notification-admin.service.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { defaults, SystemConfig } from 'src/config'; -import { EmailTemplate } from 'src/repositories/email.repository'; -import { NotificationService } from 'src/services/notification.service'; -import { userStub } from 'test/fixtures/user.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const smtpTransport = Object.freeze({ - ...defaults, - notifications: { - smtp: { - ...defaults.notifications.smtp, - enabled: true, - transport: { - ignoreCert: false, - host: 'localhost', - port: 587, - secure: false, - username: 'test', - password: 'test', - }, - }, - }, -}); - -describe(NotificationService.name, () => { - let sut: NotificationService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(NotificationService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('sendTestEmail', () => { - it('should throw error if user could not be found', async () => { - await expect(sut.sendTestEmail('', smtpTransport.notifications.smtp)).rejects.toThrow('User not found'); - }); - - it('should throw error if smtp validation fails', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - mocks.email.verifySmtp.mockRejectedValue(''); - - await expect(sut.sendTestEmail('', smtpTransport.notifications.smtp)).rejects.toThrow( - 'Failed to verify SMTP configuration', - ); - }); - - it('should send email to default domain', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - mocks.email.verifySmtp.mockResolvedValue(true); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.email.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' }); - - await expect(sut.sendTestEmail('', smtpTransport.notifications.smtp)).resolves.not.toThrow(); - expect(mocks.email.renderEmail).toHaveBeenCalledWith({ - template: EmailTemplate.TEST_EMAIL, - data: { baseUrl: 'https://my.immich.app', displayName: userStub.admin.name }, - }); - expect(mocks.email.sendEmail).toHaveBeenCalledWith( - expect.objectContaining({ - subject: 'Test email from Immich', - smtp: smtpTransport.notifications.smtp.transport, - }), - ); - }); - - it('should send email to external domain', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - mocks.email.verifySmtp.mockResolvedValue(true); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.systemMetadata.get.mockResolvedValue({ server: { externalDomain: 'https://demo.immich.app' } }); - mocks.email.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' }); - - await expect(sut.sendTestEmail('', smtpTransport.notifications.smtp)).resolves.not.toThrow(); - expect(mocks.email.renderEmail).toHaveBeenCalledWith({ - template: EmailTemplate.TEST_EMAIL, - data: { baseUrl: 'https://demo.immich.app', displayName: userStub.admin.name }, - }); - expect(mocks.email.sendEmail).toHaveBeenCalledWith( - expect.objectContaining({ - subject: 'Test email from Immich', - smtp: smtpTransport.notifications.smtp.transport, - }), - ); - }); - - it('should send email with replyTo', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - mocks.email.verifySmtp.mockResolvedValue(true); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.email.sendEmail.mockResolvedValue({ messageId: 'message-1', response: '' }); - - await expect( - sut.sendTestEmail('', { ...smtpTransport.notifications.smtp, replyTo: 'demo@immich.app' }), - ).resolves.not.toThrow(); - expect(mocks.email.renderEmail).toHaveBeenCalledWith({ - template: EmailTemplate.TEST_EMAIL, - data: { baseUrl: 'https://my.immich.app', displayName: userStub.admin.name }, - }); - expect(mocks.email.sendEmail).toHaveBeenCalledWith( - expect.objectContaining({ - subject: 'Test email from Immich', - smtp: smtpTransport.notifications.smtp.transport, - replyTo: 'demo@immich.app', - }), - ); - }); - }); -}); diff --git a/server/src/services/notification-admin.service.ts b/server/src/services/notification-admin.service.ts deleted file mode 100644 index bf0d2bba41..0000000000 --- a/server/src/services/notification-admin.service.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { mapNotification, NotificationCreateDto } from 'src/dtos/notification.dto'; -import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; -import { NotificationLevel, NotificationType } from 'src/enum'; -import { EmailTemplate } from 'src/repositories/email.repository'; -import { BaseService } from 'src/services/base.service'; -import { getExternalDomain } from 'src/utils/misc'; - -@Injectable() -export class NotificationAdminService extends BaseService { - async create(auth: AuthDto, dto: NotificationCreateDto) { - const item = await this.notificationRepository.create({ - userId: dto.userId, - type: dto.type ?? NotificationType.Custom, - level: dto.level ?? NotificationLevel.Info, - title: dto.title, - description: dto.description, - data: dto.data, - }); - - return mapNotification(item); - } - - async sendTestEmail(id: string, dto: SystemConfigSmtpDto, tempTemplate?: string) { - const user = await this.userRepository.get(id, { withDeleted: false }); - if (!user) { - throw new Error('User not found'); - } - - try { - await this.emailRepository.verifySmtp(dto.transport); - } catch (error) { - throw new BadRequestException('Failed to verify SMTP configuration', { cause: error }); - } - - const { server } = await this.getConfig({ withCache: false }); - const { html, text } = await this.emailRepository.renderEmail({ - template: EmailTemplate.TEST_EMAIL, - data: { - baseUrl: getExternalDomain(server), - displayName: user.name, - }, - customTemplate: tempTemplate!, - }); - const { messageId } = await this.emailRepository.sendEmail({ - to: user.email, - subject: 'Test email from Immich', - html, - text, - from: dto.from, - replyTo: dto.replyTo || dto.from, - smtp: dto.transport, - }); - - return { messageId }; - } - - async getTemplate(name: EmailTemplate, customTemplate: string) { - const { server, templates } = await this.getConfig({ withCache: false }); - - let templateResponse = ''; - - switch (name) { - case EmailTemplate.WELCOME: { - const { html: _welcomeHtml } = await this.emailRepository.renderEmail({ - template: EmailTemplate.WELCOME, - data: { - baseUrl: getExternalDomain(server), - displayName: 'John Doe', - username: 'john@doe.com', - password: 'thisIsAPassword123', - }, - customTemplate: customTemplate || templates.email.welcomeTemplate, - }); - - templateResponse = _welcomeHtml; - break; - } - case EmailTemplate.ALBUM_UPDATE: { - const { html: _updateAlbumHtml } = await this.emailRepository.renderEmail({ - template: EmailTemplate.ALBUM_UPDATE, - data: { - baseUrl: getExternalDomain(server), - albumId: '1', - albumName: 'Favorite Photos', - recipientName: 'Jane Doe', - cid: undefined, - }, - customTemplate: customTemplate || templates.email.albumInviteTemplate, - }); - templateResponse = _updateAlbumHtml; - break; - } - - case EmailTemplate.ALBUM_INVITE: { - const { html } = await this.emailRepository.renderEmail({ - template: EmailTemplate.ALBUM_INVITE, - data: { - baseUrl: getExternalDomain(server), - albumId: '1', - albumName: "John Doe's Favorites", - senderName: 'John Doe', - recipientName: 'Jane Doe', - cid: undefined, - }, - customTemplate: customTemplate || templates.email.albumInviteTemplate, - }); - templateResponse = html; - break; - } - default: { - templateResponse = ''; - break; - } - } - - return { name, html: templateResponse }; - } -} diff --git a/server/src/services/notification.service.spec.ts b/server/src/services/notification.service.spec.ts deleted file mode 100644 index 6627ffea8a..0000000000 --- a/server/src/services/notification.service.spec.ts +++ /dev/null @@ -1,553 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { defaults, SystemConfig } from 'src/config'; -import { AlbumUser } from 'src/database'; -import { SystemConfigDto } from 'src/dtos/system-config.dto'; -import { AssetFileType, JobName, JobStatus, UserMetadataKey } from 'src/enum'; -import { NotificationService } from 'src/services/notification.service'; -import { INotifyAlbumUpdateJob } from 'src/types'; -import { albumStub } from 'test/fixtures/album.stub'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { notificationStub } from 'test/fixtures/notification.stub'; -import { userStub } from 'test/fixtures/user.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const configs = { - smtpDisabled: Object.freeze({ - ...defaults, - notifications: { - smtp: { - ...defaults.notifications.smtp, - enabled: false, - }, - }, - }), - smtpEnabled: Object.freeze({ - ...defaults, - notifications: { - smtp: { - ...defaults.notifications.smtp, - enabled: true, - }, - }, - }), - smtpTransport: Object.freeze({ - ...defaults, - notifications: { - smtp: { - ...defaults.notifications.smtp, - enabled: true, - transport: { - ignoreCert: false, - host: 'localhost', - port: 587, - secure: false, - username: 'test', - password: 'test', - }, - }, - }, - }), -}; - -describe(NotificationService.name, () => { - let sut: NotificationService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(NotificationService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onConfigUpdate', () => { - it('should emit client and server events', () => { - const update = { oldConfig: defaults, newConfig: defaults }; - expect(sut.onConfigUpdate(update)).toBeUndefined(); - expect(mocks.websocket.clientBroadcast).toHaveBeenCalledWith('on_config_update'); - expect(mocks.websocket.serverSend).toHaveBeenCalledWith('ConfigUpdate', update); - }); - }); - - describe('onConfigValidateEvent', () => { - it('validates smtp config when enabling smtp', async () => { - const oldConfig = configs.smtpDisabled; - const newConfig = configs.smtpEnabled; - - mocks.email.verifySmtp.mockResolvedValue(true); - await expect(sut.onConfigValidate({ oldConfig, newConfig })).resolves.not.toThrow(); - expect(mocks.email.verifySmtp).toHaveBeenCalledWith(newConfig.notifications.smtp.transport); - }); - - it('validates smtp config when transport changes', async () => { - const oldConfig = configs.smtpEnabled; - const newConfig = configs.smtpTransport; - - mocks.email.verifySmtp.mockResolvedValue(true); - await expect(sut.onConfigValidate({ oldConfig, newConfig })).resolves.not.toThrow(); - expect(mocks.email.verifySmtp).toHaveBeenCalledWith(newConfig.notifications.smtp.transport); - }); - - it('skips smtp validation when there are no changes', async () => { - const oldConfig = { ...configs.smtpEnabled }; - const newConfig = { ...configs.smtpEnabled }; - - await expect(sut.onConfigValidate({ oldConfig, newConfig })).resolves.not.toThrow(); - expect(mocks.email.verifySmtp).not.toHaveBeenCalled(); - }); - - it('skips smtp validation with DTO when there are no changes', async () => { - const oldConfig = { ...configs.smtpEnabled }; - const newConfig = plainToInstance(SystemConfigDto, configs.smtpEnabled); - - await expect(sut.onConfigValidate({ oldConfig, newConfig })).resolves.not.toThrow(); - expect(mocks.email.verifySmtp).not.toHaveBeenCalled(); - }); - - it('skips smtp validation when smtp is disabled', async () => { - const oldConfig = { ...configs.smtpEnabled }; - const newConfig = { ...configs.smtpDisabled }; - - await expect(sut.onConfigValidate({ oldConfig, newConfig })).resolves.not.toThrow(); - expect(mocks.email.verifySmtp).not.toHaveBeenCalled(); - }); - - it('should fail if smtp configuration is invalid', async () => { - const oldConfig = configs.smtpDisabled; - const newConfig = configs.smtpEnabled; - - mocks.email.verifySmtp.mockRejectedValue(new Error('Failed validating smtp')); - await expect(sut.onConfigValidate({ oldConfig, newConfig })).rejects.toBeInstanceOf(Error); - }); - }); - - describe('onAssetHide', () => { - it('should send connected clients an event', () => { - sut.onAssetHide({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_hidden', 'user-id', 'asset-id'); - }); - }); - - describe('onAssetShow', () => { - it('should queue the generate thumbnail job', async () => { - await sut.onAssetShow({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.AssetGenerateThumbnails, - data: { id: 'asset-id', notify: true }, - }); - }); - }); - - describe('onUserSignupEvent', () => { - it('skips when notify is false', async () => { - await sut.onUserSignup({ id: '', notify: false }); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should queue notify signup event if notify is true', async () => { - await sut.onUserSignup({ id: '', notify: true }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.NotifyUserSignup, - data: { id: '', password: undefined }, - }); - }); - }); - - describe('onAlbumUpdateEvent', () => { - it('should queue notify album update event', async () => { - await sut.onAlbumUpdate({ id: 'album', recipientId: '42' }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.NotifyAlbumUpdate, - data: { id: 'album', recipientId: '42', delay: 300_000 }, - }); - }); - }); - - describe('onAlbumInviteEvent', () => { - it('should queue notify album invite event', async () => { - await sut.onAlbumInvite({ id: '', userId: '42' }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.NotifyAlbumInvite, - data: { id: '', recipientId: '42' }, - }); - }); - }); - - describe('onSessionDeleteEvent', () => { - it('should send a on_session_delete client event', () => { - vi.useFakeTimers(); - sut.onSessionDelete({ sessionId: 'id' }); - expect(mocks.websocket.clientSend).not.toHaveBeenCalled(); - - vi.advanceTimersByTime(500); - - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_session_delete', 'id', 'id'); - }); - }); - - describe('onAssetTrash', () => { - it('should send connected clients an websocket', () => { - sut.onAssetTrash({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']); - }); - }); - - describe('onAssetDelete', () => { - it('should send connected clients an event', () => { - sut.onAssetDelete({ assetId: 'asset-id', userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_delete', 'user-id', 'asset-id'); - }); - }); - - describe('onAssetsTrash', () => { - it('should send connected clients an event', () => { - sut.onAssetsTrash({ assetIds: ['asset-id'], userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_trash', 'user-id', ['asset-id']); - }); - }); - - describe('onAssetsRestore', () => { - it('should send connected clients an event', () => { - sut.onAssetsRestore({ assetIds: ['asset-id'], userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_restore', 'user-id', ['asset-id']); - }); - }); - - describe('onStackCreate', () => { - it('should send connected clients an event', () => { - sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); - }); - }); - - describe('onStackUpdate', () => { - it('should send connected clients an event', () => { - sut.onStackUpdate({ stackId: 'stack-id', userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); - }); - }); - - describe('onStackDelete', () => { - it('should send connected clients an event', () => { - sut.onStackDelete({ stackId: 'stack-id', userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); - }); - }); - - describe('onStacksDelete', () => { - it('should send connected clients an event', () => { - sut.onStacksDelete({ stackIds: ['stack-id'], userId: 'user-id' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id'); - }); - }); - - describe('handleUserSignup', () => { - it('should skip if user could not be found', async () => { - await expect(sut.handleUserSignup({ id: '' })).resolves.toBe(JobStatus.Skipped); - }); - - it('should be successful', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - mocks.systemMetadata.get.mockResolvedValue({ server: {} }); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - - await expect(sut.handleUserSignup({ id: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.SendMail, - data: expect.objectContaining({ subject: 'Welcome to Immich' }), - }); - }); - }); - - describe('handleAlbumInvite', () => { - it('should skip if album could not be found', async () => { - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); - expect(mocks.user.get).not.toHaveBeenCalled(); - }); - - it('should skip if recipient could not be found', async () => { - mocks.album.getById.mockResolvedValue(albumStub.empty); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should skip if the recipient has email notifications disabled', async () => { - mocks.album.getById.mockResolvedValue(albumStub.empty); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: false, albumInvite: true } }, - }, - ], - }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); - }); - - it('should skip if the recipient has email notifications for album invite disabled', async () => { - mocks.album.getById.mockResolvedValue(albumStub.empty); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: true, albumInvite: false } }, - }, - ], - }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Skipped); - }); - - it('should send invite email', async () => { - mocks.album.getById.mockResolvedValue(albumStub.empty); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: true, albumInvite: true } }, - }, - ], - }); - mocks.systemMetadata.get.mockResolvedValue({ server: {} }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.SendMail, - data: expect.objectContaining({ subject: expect.stringContaining('You have been added to a shared album') }), - }); - }); - - it('should send invite email without album thumbnail if thumbnail asset does not exist', async () => { - mocks.album.getById.mockResolvedValue(albumStub.emptyWithValidThumbnail); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: true, albumInvite: true } }, - }, - ], - }); - mocks.systemMetadata.get.mockResolvedValue({ server: {} }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith( - albumStub.emptyWithValidThumbnail.albumThumbnailAssetId, - AssetFileType.Thumbnail, - ); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.SendMail, - data: expect.objectContaining({ - subject: expect.stringContaining('You have been added to a shared album'), - imageAttachments: undefined, - }), - }); - }); - - it('should send invite email with album thumbnail as jpeg', async () => { - mocks.album.getById.mockResolvedValue(albumStub.emptyWithValidThumbnail); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: true, albumInvite: true } }, - }, - ], - }); - mocks.systemMetadata.get.mockResolvedValue({ server: {} }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([ - { id: '1', type: AssetFileType.Thumbnail, path: 'path-to-thumb.jpg', isEdited: false }, - ]); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith( - albumStub.emptyWithValidThumbnail.albumThumbnailAssetId, - AssetFileType.Thumbnail, - ); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.SendMail, - data: expect.objectContaining({ - subject: expect.stringContaining('You have been added to a shared album'), - imageAttachments: [{ filename: 'album-thumbnail.jpg', path: expect.anything(), cid: expect.anything() }], - }), - }); - }); - - it('should send invite email with album thumbnail and arbitrary extension', async () => { - mocks.album.getById.mockResolvedValue(albumStub.emptyWithValidThumbnail); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: true, albumInvite: true } }, - }, - ], - }); - mocks.systemMetadata.get.mockResolvedValue({ server: {} }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([{ ...assetStub.image.files[2], isEdited: false }]); - - await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith( - albumStub.emptyWithValidThumbnail.albumThumbnailAssetId, - AssetFileType.Thumbnail, - ); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.SendMail, - data: expect.objectContaining({ - subject: expect.stringContaining('You have been added to a shared album'), - imageAttachments: [{ filename: 'album-thumbnail.ext', path: expect.anything(), cid: expect.anything() }], - }), - }); - }); - }); - - describe('handleAlbumUpdate', () => { - it('should skip if album could not be found', async () => { - await expect(sut.handleAlbumUpdate({ id: '', recipientId: '1' })).resolves.toBe(JobStatus.Skipped); - expect(mocks.user.get).not.toHaveBeenCalled(); - }); - - it('should skip if owner could not be found', async () => { - mocks.album.getById.mockResolvedValue(albumStub.emptyWithValidThumbnail); - - await expect(sut.handleAlbumUpdate({ id: '', recipientId: '1' })).resolves.toBe(JobStatus.Skipped); - expect(mocks.systemMetadata.get).not.toHaveBeenCalled(); - }); - - it('should skip recipient that could not be looked up', async () => { - mocks.album.getById.mockResolvedValue({ - ...albumStub.emptyWithValidThumbnail, - albumUsers: [{ user: { id: userStub.user1.id } } as AlbumUser], - }); - mocks.user.get.mockResolvedValueOnce(userStub.user1); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); - - await sut.handleAlbumUpdate({ id: '', recipientId: userStub.user1.id }); - expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false }); - expect(mocks.email.renderEmail).not.toHaveBeenCalled(); - }); - - it('should skip recipient with disabled email notifications', async () => { - mocks.album.getById.mockResolvedValue({ - ...albumStub.emptyWithValidThumbnail, - albumUsers: [{ user: { id: userStub.user1.id } } as AlbumUser], - }); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: false, albumUpdate: true } }, - }, - ], - }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); - - await sut.handleAlbumUpdate({ id: '', recipientId: userStub.user1.id }); - expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false }); - expect(mocks.email.renderEmail).not.toHaveBeenCalled(); - }); - - it('should skip recipient with disabled email notifications for the album update event', async () => { - mocks.album.getById.mockResolvedValue({ - ...albumStub.emptyWithValidThumbnail, - albumUsers: [{ user: { id: userStub.user1.id } } as AlbumUser], - }); - mocks.user.get.mockResolvedValue({ - ...userStub.user1, - metadata: [ - { - key: UserMetadataKey.Preferences, - value: { emailNotifications: { enabled: true, albumUpdate: false } }, - }, - ], - }); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); - - await sut.handleAlbumUpdate({ id: '', recipientId: userStub.user1.id }); - expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false }); - expect(mocks.email.renderEmail).not.toHaveBeenCalled(); - }); - - it('should send email', async () => { - mocks.album.getById.mockResolvedValue({ - ...albumStub.emptyWithValidThumbnail, - albumUsers: [{ user: { id: userStub.user1.id } } as AlbumUser], - }); - mocks.user.get.mockResolvedValue(userStub.user1); - mocks.notification.create.mockResolvedValue(notificationStub.albumEvent); - mocks.email.renderEmail.mockResolvedValue({ html: '', text: '' }); - mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]); - - await sut.handleAlbumUpdate({ id: '', recipientId: userStub.user1.id }); - expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false }); - expect(mocks.email.renderEmail).toHaveBeenCalled(); - expect(mocks.job.queue).toHaveBeenCalled(); - }); - - it('should add new recipients for new images if job is already queued', async () => { - await sut.onAlbumUpdate({ id: '1', recipientId: '2' } as INotifyAlbumUpdateJob); - expect(mocks.job.removeJob).toHaveBeenCalledWith(JobName.NotifyAlbumUpdate, '1/2'); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.NotifyAlbumUpdate, - data: { - id: '1', - delay: 300_000, - recipientId: '2', - }, - }); - }); - }); - - describe('handleSendEmail', () => { - it('should skip if smtp notifications are disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ notifications: { smtp: { enabled: false } } }); - await expect(sut.handleSendEmail({ html: '', subject: '', text: '', to: '' })).resolves.toBe(JobStatus.Skipped); - }); - - it('should send mail successfully', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - notifications: { smtp: { enabled: true, from: 'test@immich.app' } }, - }); - mocks.email.sendEmail.mockResolvedValue({ messageId: '', response: '' }); - - await expect(sut.handleSendEmail({ html: '', subject: '', text: '', to: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.email.sendEmail).toHaveBeenCalledWith(expect.objectContaining({ replyTo: 'test@immich.app' })); - }); - - it('should send mail with replyTo successfully', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - notifications: { smtp: { enabled: true, from: 'test@immich.app', replyTo: 'demo@immich.app' } }, - }); - mocks.email.sendEmail.mockResolvedValue({ messageId: '', response: '' }); - - await expect(sut.handleSendEmail({ html: '', subject: '', text: '', to: '' })).resolves.toBe(JobStatus.Success); - expect(mocks.email.sendEmail).toHaveBeenCalledWith(expect.objectContaining({ replyTo: 'demo@immich.app' })); - }); - }); -}); diff --git a/server/src/services/notification.service.ts b/server/src/services/notification.service.ts deleted file mode 100644 index ee87fcf775..0000000000 --- a/server/src/services/notification.service.ts +++ /dev/null @@ -1,478 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { OnEvent, OnJob } from 'src/decorators'; -import { MapAlbumDto } from 'src/dtos/album.dto'; -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - mapNotification, - NotificationDeleteAllDto, - NotificationDto, - NotificationSearchDto, - NotificationUpdateAllDto, - NotificationUpdateDto, -} from 'src/dtos/notification.dto'; -import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto'; -import { - AssetFileType, - JobName, - JobStatus, - NotificationLevel, - NotificationType, - Permission, - QueueName, -} from 'src/enum'; -import { EmailTemplate } from 'src/repositories/email.repository'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { EmailImageAttachment, JobOf } from 'src/types'; -import { getFilenameExtension } from 'src/utils/file'; -import { getExternalDomain } from 'src/utils/misc'; -import { isEqualObject } from 'src/utils/object'; -import { getPreferences } from 'src/utils/preferences'; - -@Injectable() -export class NotificationService extends BaseService { - private static albumUpdateEmailDelayMs = 300_000; - - async search(auth: AuthDto, dto: NotificationSearchDto): Promise { - const items = await this.notificationRepository.search(auth.user.id, dto); - return items.map((item) => mapNotification(item)); - } - - async updateAll(auth: AuthDto, dto: NotificationUpdateAllDto) { - await this.requireAccess({ auth, ids: dto.ids, permission: Permission.NotificationUpdate }); - await this.notificationRepository.updateAll(dto.ids, { - readAt: dto.readAt, - }); - } - - async deleteAll(auth: AuthDto, dto: NotificationDeleteAllDto) { - await this.requireAccess({ auth, ids: dto.ids, permission: Permission.NotificationDelete }); - await this.notificationRepository.deleteAll(dto.ids); - } - - async get(auth: AuthDto, id: string) { - await this.requireAccess({ auth, ids: [id], permission: Permission.NotificationRead }); - const item = await this.notificationRepository.get(id); - if (!item) { - throw new BadRequestException('Notification not found'); - } - return mapNotification(item); - } - - async update(auth: AuthDto, id: string, dto: NotificationUpdateDto) { - await this.requireAccess({ auth, ids: [id], permission: Permission.NotificationUpdate }); - const item = await this.notificationRepository.update(id, { - readAt: dto.readAt, - }); - return mapNotification(item); - } - - async delete(auth: AuthDto, id: string) { - await this.requireAccess({ auth, ids: [id], permission: Permission.NotificationDelete }); - await this.notificationRepository.delete(id); - } - - @OnJob({ name: JobName.NotificationsCleanup, queue: QueueName.BackgroundTask }) - async onNotificationsCleanup() { - await this.notificationRepository.cleanup(); - } - - @OnEvent({ name: 'JobError' }) - async onJobError({ job, error }: ArgOf<'JobError'>) { - const admin = await this.userRepository.getAdmin(); - if (!admin) { - return; - } - - this.logger.error(`Unable to run job handler (${job.name}): ${error}`, error?.stack, JSON.stringify(job.data)); - - switch (job.name) { - case JobName.DatabaseBackup: { - const errorMessage = error instanceof Error ? error.message : error; - const item = await this.notificationRepository.create({ - userId: admin.id, - type: NotificationType.JobFailed, - level: NotificationLevel.Error, - title: 'Job Failed', - description: `Job ${[job.name]} failed with error: ${errorMessage}`, - }); - - this.websocketRepository.clientSend('on_notification', admin.id, mapNotification(item)); - break; - } - - default: { - return; - } - } - } - - @OnEvent({ name: 'ConfigUpdate' }) - onConfigUpdate({ oldConfig, newConfig }: ArgOf<'ConfigUpdate'>) { - this.websocketRepository.clientBroadcast('on_config_update'); - this.websocketRepository.serverSend('ConfigUpdate', { oldConfig, newConfig }); - } - - @OnEvent({ name: 'AppRestart' }) - onAppRestart(state: ArgOf<'AppRestart'>) { - this.websocketRepository.clientBroadcast('AppRestartV1', { - isMaintenanceMode: state.isMaintenanceMode, - }); - - this.websocketRepository.serverSend('AppRestart', state); - } - - @OnEvent({ name: 'ConfigValidate', priority: -100 }) - async onConfigValidate({ oldConfig, newConfig }: ArgOf<'ConfigValidate'>) { - try { - if ( - newConfig.notifications.smtp.enabled && - !isEqualObject(oldConfig.notifications.smtp, newConfig.notifications.smtp) - ) { - await this.emailRepository.verifySmtp(newConfig.notifications.smtp.transport); - } - } catch (error: Error | any) { - this.logger.error(`Failed to validate SMTP configuration: ${error}`, error?.stack); - throw new Error(`Invalid SMTP configuration: ${error}`); - } - } - - @OnEvent({ name: 'AssetHide' }) - onAssetHide({ assetId, userId }: ArgOf<'AssetHide'>) { - this.websocketRepository.clientSend('on_asset_hidden', userId, assetId); - } - - @OnEvent({ name: 'AssetShow' }) - async onAssetShow({ assetId }: ArgOf<'AssetShow'>) { - await this.jobRepository.queue({ name: JobName.AssetGenerateThumbnails, data: { id: assetId, notify: true } }); - } - - @OnEvent({ name: 'AssetTrash' }) - onAssetTrash({ assetId, userId }: ArgOf<'AssetTrash'>) { - this.websocketRepository.clientSend('on_asset_trash', userId, [assetId]); - } - - @OnEvent({ name: 'AssetDelete' }) - onAssetDelete({ assetId, userId }: ArgOf<'AssetDelete'>) { - this.websocketRepository.clientSend('on_asset_delete', userId, assetId); - } - - @OnEvent({ name: 'AssetTrashAll' }) - onAssetsTrash({ assetIds, userId }: ArgOf<'AssetTrashAll'>) { - this.websocketRepository.clientSend('on_asset_trash', userId, assetIds); - } - - @OnEvent({ name: 'AssetMetadataExtracted' }) - async onAssetMetadataExtracted({ assetId, userId, source }: ArgOf<'AssetMetadataExtracted'>) { - if (source !== 'sidecar-write') { - return; - } - - const [asset] = await this.assetRepository.getByIdsWithAllRelationsButStacks([assetId]); - if (asset) { - this.websocketRepository.clientSend( - 'on_asset_update', - userId, - mapAsset(asset, { auth: { user: { id: userId } } as AuthDto }), - ); - } - } - - @OnEvent({ name: 'AssetRestoreAll' }) - onAssetsRestore({ assetIds, userId }: ArgOf<'AssetRestoreAll'>) { - this.websocketRepository.clientSend('on_asset_restore', userId, assetIds); - } - - @OnEvent({ name: 'StackCreate' }) - onStackCreate({ userId }: ArgOf<'StackCreate'>) { - this.websocketRepository.clientSend('on_asset_stack_update', userId); - } - - @OnEvent({ name: 'StackUpdate' }) - onStackUpdate({ userId }: ArgOf<'StackUpdate'>) { - this.websocketRepository.clientSend('on_asset_stack_update', userId); - } - - @OnEvent({ name: 'StackDelete' }) - onStackDelete({ userId }: ArgOf<'StackDelete'>) { - this.websocketRepository.clientSend('on_asset_stack_update', userId); - } - - @OnEvent({ name: 'StackDeleteAll' }) - onStacksDelete({ userId }: ArgOf<'StackDeleteAll'>) { - this.websocketRepository.clientSend('on_asset_stack_update', userId); - } - - @OnEvent({ name: 'UserSignup' }) - async onUserSignup({ notify, id, password: password }: ArgOf<'UserSignup'>) { - if (notify) { - await this.jobRepository.queue({ name: JobName.NotifyUserSignup, data: { id, password } }); - } - } - - @OnEvent({ name: 'UserDelete' }) - onUserDelete({ id }: ArgOf<'UserDelete'>) { - this.websocketRepository.clientBroadcast('on_user_delete', id); - } - - @OnEvent({ name: 'AlbumUpdate' }) - async onAlbumUpdate({ id, recipientId }: ArgOf<'AlbumUpdate'>) { - await this.jobRepository.removeJob(JobName.NotifyAlbumUpdate, `${id}/${recipientId}`); - await this.jobRepository.queue({ - name: JobName.NotifyAlbumUpdate, - data: { id, recipientId, delay: NotificationService.albumUpdateEmailDelayMs }, - }); - } - - @OnEvent({ name: 'AlbumInvite' }) - async onAlbumInvite({ id, userId }: ArgOf<'AlbumInvite'>) { - await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId } }); - } - - @OnEvent({ name: 'SessionDelete' }) - onSessionDelete({ sessionId }: ArgOf<'SessionDelete'>) { - // after the response is sent - setTimeout(() => this.websocketRepository.clientSend('on_session_delete', sessionId, sessionId), 500); - } - - async sendTestEmail(id: string, dto: SystemConfigSmtpDto, tempTemplate?: string) { - const user = await this.userRepository.get(id, { withDeleted: false }); - if (!user) { - throw new Error('User not found'); - } - - try { - await this.emailRepository.verifySmtp(dto.transport); - } catch (error) { - throw new BadRequestException('Failed to verify SMTP configuration', { cause: error }); - } - - const { server } = await this.getConfig({ withCache: false }); - const { html, text } = await this.emailRepository.renderEmail({ - template: EmailTemplate.TEST_EMAIL, - data: { - baseUrl: getExternalDomain(server), - displayName: user.name, - }, - customTemplate: tempTemplate!, - }); - const { messageId } = await this.emailRepository.sendEmail({ - to: user.email, - subject: 'Test email from Immich', - html, - text, - from: dto.from, - replyTo: dto.replyTo || dto.from, - smtp: dto.transport, - }); - - return { messageId }; - } - - @OnJob({ name: JobName.NotifyUserSignup, queue: QueueName.Notification }) - async handleUserSignup({ id, password }: JobOf) { - const user = await this.userRepository.get(id, { withDeleted: false }); - if (!user) { - return JobStatus.Skipped; - } - - const { server, templates } = await this.getConfig({ withCache: true }); - const { html, text } = await this.emailRepository.renderEmail({ - template: EmailTemplate.WELCOME, - data: { - baseUrl: getExternalDomain(server), - displayName: user.name, - username: user.email, - password, - }, - customTemplate: templates.email.welcomeTemplate, - }); - - await this.jobRepository.queue({ - name: JobName.SendMail, - data: { - to: user.email, - subject: 'Welcome to Immich', - html, - text, - }, - }); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.NotifyAlbumInvite, queue: QueueName.Notification }) - async handleAlbumInvite({ id, recipientId }: JobOf) { - const album = await this.albumRepository.getById(id, { withAssets: false }); - if (!album) { - return JobStatus.Skipped; - } - - const recipient = await this.userRepository.get(recipientId, { withDeleted: false }); - if (!recipient) { - return JobStatus.Skipped; - } - - await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumInvite, album.owner.name); - - const { emailNotifications } = getPreferences(recipient.metadata); - - if (!emailNotifications.enabled || !emailNotifications.albumInvite) { - return JobStatus.Skipped; - } - - const attachment = await this.getAlbumThumbnailAttachment(album); - - const { server, templates } = await this.getConfig({ withCache: false }); - const { html, text } = await this.emailRepository.renderEmail({ - template: EmailTemplate.ALBUM_INVITE, - data: { - baseUrl: getExternalDomain(server), - albumId: album.id, - albumName: album.albumName, - senderName: album.owner.name, - recipientName: recipient.name, - cid: attachment ? attachment.cid : undefined, - }, - customTemplate: templates.email.albumInviteTemplate, - }); - - await this.jobRepository.queue({ - name: JobName.SendMail, - data: { - to: recipient.email, - subject: `You have been added to a shared album - ${album.albumName}`, - html, - text, - imageAttachments: attachment ? [attachment] : undefined, - }, - }); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.NotifyAlbumUpdate, queue: QueueName.Notification }) - async handleAlbumUpdate({ id, recipientId }: JobOf) { - const album = await this.albumRepository.getById(id, { withAssets: false }); - - if (!album) { - return JobStatus.Skipped; - } - - const owner = await this.userRepository.get(album.ownerId, { withDeleted: false }); - if (!owner) { - return JobStatus.Skipped; - } - - await this.sendAlbumLocalNotification(album, recipientId, NotificationType.AlbumUpdate); - - const attachment = await this.getAlbumThumbnailAttachment(album); - - const { server, templates } = await this.getConfig({ withCache: false }); - - const user = await this.userRepository.get(recipientId, { withDeleted: false }); - if (!user) { - return JobStatus.Skipped; - } - - const { emailNotifications } = getPreferences(user.metadata); - - if (!emailNotifications.enabled || !emailNotifications.albumUpdate) { - return JobStatus.Skipped; - } - - const { html, text } = await this.emailRepository.renderEmail({ - template: EmailTemplate.ALBUM_UPDATE, - data: { - baseUrl: getExternalDomain(server), - albumId: album.id, - albumName: album.albumName, - recipientName: user.name, - cid: attachment ? attachment.cid : undefined, - }, - customTemplate: templates.email.albumUpdateTemplate, - }); - - await this.jobRepository.queue({ - name: JobName.SendMail, - data: { - to: user.email, - subject: `New media has been added to an album - ${album.albumName}`, - html, - text, - imageAttachments: attachment ? [attachment] : undefined, - }, - }); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.SendMail, queue: QueueName.Notification }) - async handleSendEmail(data: JobOf): Promise { - const { notifications } = await this.getConfig({ withCache: false }); - if (!notifications.smtp.enabled) { - return JobStatus.Skipped; - } - - const { to, subject, html, text: plain } = data; - const response = await this.emailRepository.sendEmail({ - to, - subject, - html, - text: plain, - from: notifications.smtp.from, - replyTo: notifications.smtp.replyTo || notifications.smtp.from, - smtp: notifications.smtp.transport, - imageAttachments: data.imageAttachments, - }); - - this.logger.log(`Sent mail with id: ${response.messageId} status: ${response.response}`); - - return JobStatus.Success; - } - - private async getAlbumThumbnailAttachment(album: { - albumThumbnailAssetId: string | null; - }): Promise { - if (!album.albumThumbnailAssetId) { - return; - } - - const albumThumbnailFiles = await this.assetJobRepository.getAlbumThumbnailFiles( - album.albumThumbnailAssetId, - AssetFileType.Thumbnail, - ); - - if (albumThumbnailFiles.length !== 1) { - return; - } - - return { - filename: `album-thumbnail${getFilenameExtension(albumThumbnailFiles[0].path)}`, - path: albumThumbnailFiles[0].path, - cid: 'album-thumbnail', - }; - } - - private async sendAlbumLocalNotification( - album: MapAlbumDto, - userId: string, - type: NotificationType.AlbumInvite | NotificationType.AlbumUpdate, - senderName?: string, - ) { - const isInvite = type === NotificationType.AlbumInvite; - const item = await this.notificationRepository.create({ - userId, - type, - level: isInvite ? NotificationLevel.Success : NotificationLevel.Info, - title: isInvite ? 'Shared Album Invitation' : 'Shared Album Update', - description: isInvite - ? `${senderName} shared an album (${album.albumName}) with you` - : `New media has been added to the album (${album.albumName})`, - data: JSON.stringify({ albumId: album.id }), - }); - - this.websocketRepository.clientSend('on_notification', userId, mapNotification(item)); - } -} diff --git a/server/src/services/ocr.service.spec.ts b/server/src/services/ocr.service.spec.ts deleted file mode 100644 index 404f423cac..0000000000 --- a/server/src/services/ocr.service.spec.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { AssetVisibility, ImmichWorker, JobName, JobStatus } from 'src/enum'; -import { OcrService } from 'src/services/ocr.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -describe(OcrService.name, () => { - let sut: OcrService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(OcrService)); - - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - mocks.assetJob.getForOcr.mockResolvedValue({ - visibility: AssetVisibility.Timeline, - previewFile: assetStub.image.files[1].path, - }); - }); - - const mockOcrResult = (...texts: string[]) => { - mocks.machineLearning.ocr.mockResolvedValue({ - box: texts.flatMap((_, i) => Array.from({ length: 8 }, (_, j) => i * 10 + j)), - boxScore: texts.map(() => 0.9), - text: texts, - textScore: texts.map(() => 0.95), - }); - }; - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('handleQueueOcr', () => { - it('should do nothing if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await sut.handleQueueOcr({ force: false }); - - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - }); - - it('should queue the assets without ocr', async () => { - mocks.assetJob.streamForOcrJob.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueOcr({ force: false }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.Ocr, data: { id: assetStub.image.id } }]); - expect(mocks.assetJob.streamForOcrJob).toHaveBeenCalledWith(false); - }); - - it('should queue all the assets', async () => { - mocks.assetJob.streamForOcrJob.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueOcr({ force: true }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.Ocr, data: { id: assetStub.image.id } }]); - expect(mocks.assetJob.streamForOcrJob).toHaveBeenCalledWith(true); - }); - }); - - describe('handleOcr', () => { - it('should do nothing if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - expect(await sut.handleOcr({ id: '123' })).toEqual(JobStatus.Skipped); - - expect(mocks.asset.getByIds).not.toHaveBeenCalled(); - expect(mocks.machineLearning.encodeImage).not.toHaveBeenCalled(); - }); - - it('should skip assets without a resize path', async () => { - mocks.assetJob.getForOcr.mockResolvedValue({ visibility: AssetVisibility.Timeline, previewFile: null }); - - expect(await sut.handleOcr({ id: assetStub.noResizePath.id })).toEqual(JobStatus.Failed); - - expect(mocks.ocr.upsert).not.toHaveBeenCalled(); - expect(mocks.machineLearning.ocr).not.toHaveBeenCalled(); - }); - - it('should save the returned objects', async () => { - mocks.machineLearning.ocr.mockResolvedValue({ - box: [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160], - boxScore: [0.9, 0.8], - text: ['One Two Three', 'Four Five'], - textScore: [0.95, 0.85], - }); - - expect(await sut.handleOcr({ id: assetStub.image.id })).toEqual(JobStatus.Success); - - expect(mocks.machineLearning.ocr).toHaveBeenCalledWith( - '/uploads/user-id/thumbs/path.jpg', - expect.objectContaining({ - modelName: 'PP-OCRv5_mobile', - minDetectionScore: 0.5, - minRecognitionScore: 0.8, - maxResolution: 736, - }), - ); - expect(mocks.ocr.upsert).toHaveBeenCalledWith( - assetStub.image.id, - [ - { - assetId: assetStub.image.id, - boxScore: 0.9, - text: 'One Two Three', - textScore: 0.95, - x1: 10, - y1: 20, - x2: 30, - y2: 40, - x3: 50, - y3: 60, - x4: 70, - y4: 80, - }, - { - assetId: assetStub.image.id, - boxScore: 0.8, - text: 'Four Five', - textScore: 0.85, - x1: 90, - y1: 100, - x2: 110, - y2: 120, - x3: 130, - y3: 140, - x4: 150, - y4: 160, - }, - ], - 'One Two Three Four Five', - ); - }); - - it('should apply config settings', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { - enabled: true, - ocr: { - modelName: 'PP-OCRv5_server', - enabled: true, - minDetectionScore: 0.8, - minRecognitionScore: 0.9, - maxResolution: 1500, - }, - }, - }); - mockOcrResult(); - - expect(await sut.handleOcr({ id: assetStub.image.id })).toEqual(JobStatus.Success); - - expect(mocks.machineLearning.ocr).toHaveBeenCalledWith( - '/uploads/user-id/thumbs/path.jpg', - expect.objectContaining({ - modelName: 'PP-OCRv5_server', - minDetectionScore: 0.8, - minRecognitionScore: 0.9, - maxResolution: 1500, - }), - ); - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, [], ''); - }); - - it('should skip invisible assets', async () => { - mocks.assetJob.getForOcr.mockResolvedValue({ - visibility: AssetVisibility.Hidden, - previewFile: assetStub.image.files[1].path, - }); - - expect(await sut.handleOcr({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.Skipped); - - expect(mocks.machineLearning.ocr).not.toHaveBeenCalled(); - expect(mocks.ocr.upsert).not.toHaveBeenCalled(); - }); - - it('should fail if asset could not be found', async () => { - mocks.assetJob.getForOcr.mockResolvedValue(void 0); - - expect(await sut.handleOcr({ id: assetStub.image.id })).toEqual(JobStatus.Failed); - - expect(mocks.machineLearning.ocr).not.toHaveBeenCalled(); - expect(mocks.ocr.upsert).not.toHaveBeenCalled(); - }); - - describe('search tokenization', () => { - it('should generate bigrams for Chinese text', async () => { - mockOcrResult('機器學習'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), '機器 器學 學習'); - }); - - it('should generate bigrams for Japanese text', async () => { - mockOcrResult('テスト'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), 'テス スト'); - }); - - it('should generate bigrams for Korean text', async () => { - mockOcrResult('한국어'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), '한국 국어'); - }); - - it('should pass through Latin text unchanged', async () => { - mockOcrResult('Hello World'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), 'Hello World'); - }); - - it('should handle mixed CJK and Latin text', async () => { - mockOcrResult('機器學習Model'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), '機器 器學 學習 Model'); - }); - - it('should handle year followed by CJK', async () => { - mockOcrResult('2024年レポート'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith( - assetStub.image.id, - expect.any(Array), - '2024 年レ レポ ポー ート', - ); - }); - - it('should join multiple OCR boxes', async () => { - mockOcrResult('機器', 'Learning'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), '機器 Learning'); - }); - - it('should normalize whitespace', async () => { - mockOcrResult(' Hello World '); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), 'Hello World'); - }); - - it('should keep single CJK characters', async () => { - mockOcrResult('A', '中', 'B'); - - await sut.handleOcr({ id: assetStub.image.id }); - - expect(mocks.ocr.upsert).toHaveBeenCalledWith(assetStub.image.id, expect.any(Array), 'A 中 B'); - }); - }); - }); -}); diff --git a/server/src/services/ocr.service.ts b/server/src/services/ocr.service.ts deleted file mode 100644 index d92d399dba..0000000000 --- a/server/src/services/ocr.service.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { OnJob } from 'src/decorators'; -import { AssetVisibility, JobName, JobStatus, QueueName } from 'src/enum'; -import { OCR } from 'src/repositories/machine-learning.repository'; -import { BaseService } from 'src/services/base.service'; -import { JobItem, JobOf } from 'src/types'; -import { tokenizeForSearch } from 'src/utils/database'; -import { isOcrEnabled } from 'src/utils/misc'; - -@Injectable() -export class OcrService extends BaseService { - @OnJob({ name: JobName.OcrQueueAll, queue: QueueName.Ocr }) - async handleQueueOcr({ force }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: false }); - if (!isOcrEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - if (force) { - await this.ocrRepository.deleteAll(); - } - - let jobs: JobItem[] = []; - const assets = this.assetJobRepository.streamForOcrJob(force); - - for await (const asset of assets) { - jobs.push({ name: JobName.Ocr, data: { id: asset.id } }); - - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(jobs); - jobs = []; - } - } - - await this.jobRepository.queueAll(jobs); - return JobStatus.Success; - } - - @OnJob({ name: JobName.Ocr, queue: QueueName.Ocr }) - async handleOcr({ id }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: true }); - if (!isOcrEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - const asset = await this.assetJobRepository.getForOcr(id); - if (!asset || !asset.previewFile) { - return JobStatus.Failed; - } - - if (asset.visibility === AssetVisibility.Hidden) { - return JobStatus.Skipped; - } - - const ocrResults = await this.machineLearningRepository.ocr(asset.previewFile, machineLearning.ocr); - const { ocrDataList, searchText } = this.parseOcrResults(id, ocrResults); - await this.ocrRepository.upsert(id, ocrDataList, searchText); - - await this.assetRepository.upsertJobStatus({ assetId: id, ocrAt: new Date() }); - - this.logger.debug(`Processed ${ocrResults.text.length} OCR result(s) for ${id}`); - return JobStatus.Success; - } - - private parseOcrResults(id: string, { box, boxScore, text, textScore }: OCR) { - const ocrDataList = []; - const searchTokens = []; - for (let i = 0; i < text.length; i++) { - const rawText = text[i]; - const boxOffset = i * 8; - ocrDataList.push({ - assetId: id, - x1: box[boxOffset], - y1: box[boxOffset + 1], - x2: box[boxOffset + 2], - y2: box[boxOffset + 3], - x3: box[boxOffset + 4], - y3: box[boxOffset + 5], - x4: box[boxOffset + 6], - y4: box[boxOffset + 7], - boxScore: boxScore[i], - textScore: textScore[i], - text: rawText, - }); - searchTokens.push(...tokenizeForSearch(rawText)); - } - - return { ocrDataList, searchText: searchTokens.join(' ') }; - } -} diff --git a/server/src/services/partner.service.spec.ts b/server/src/services/partner.service.spec.ts deleted file mode 100644 index db057a453a..0000000000 --- a/server/src/services/partner.service.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { PartnerDirection } from 'src/repositories/partner.repository'; -import { PartnerService } from 'src/services/partner.service'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(PartnerService.name, () => { - let sut: PartnerService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(PartnerService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('search', () => { - it("should return a list of partners with whom I've shared my library", async () => { - const user1 = factory.user(); - const user2 = factory.user(); - const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 }); - const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 }); - const auth = factory.auth({ user: { id: user1.id } }); - - mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]); - - await expect(sut.search(auth, { direction: PartnerDirection.SharedBy })).resolves.toBeDefined(); - expect(mocks.partner.getAll).toHaveBeenCalledWith(user1.id); - }); - - it('should return a list of partners who have shared their libraries with me', async () => { - const user1 = factory.user(); - const user2 = factory.user(); - const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 }); - const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 }); - const auth = factory.auth({ user: { id: user1.id } }); - - mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]); - await expect(sut.search(auth, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined(); - expect(mocks.partner.getAll).toHaveBeenCalledWith(user1.id); - }); - }); - - describe('create', () => { - it('should create a new partner', async () => { - const user1 = factory.user(); - const user2 = factory.user(); - const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); - const auth = factory.auth({ user: { id: user1.id } }); - - mocks.partner.get.mockResolvedValue(void 0); - mocks.partner.create.mockResolvedValue(partner); - - await expect(sut.create(auth, { sharedWithId: user2.id })).resolves.toBeDefined(); - - expect(mocks.partner.create).toHaveBeenCalledWith({ - sharedById: partner.sharedById, - sharedWithId: partner.sharedWithId, - }); - }); - - it('should throw an error when the partner already exists', async () => { - const user1 = factory.user(); - const user2 = factory.user(); - const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); - const auth = factory.auth({ user: { id: user1.id } }); - - mocks.partner.get.mockResolvedValue(partner); - - await expect(sut.create(auth, { sharedWithId: user2.id })).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.partner.create).not.toHaveBeenCalled(); - }); - }); - - describe('remove', () => { - it('should remove a partner', async () => { - const user1 = factory.user(); - const user2 = factory.user(); - const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); - const auth = factory.auth({ user: { id: user1.id } }); - - mocks.partner.get.mockResolvedValue(partner); - - await sut.remove(auth, user2.id); - - expect(mocks.partner.remove).toHaveBeenCalledWith({ sharedById: user1.id, sharedWithId: user2.id }); - }); - - it('should throw an error when the partner does not exist', async () => { - const user2 = factory.user(); - const auth = factory.auth(); - - mocks.partner.get.mockResolvedValue(void 0); - - await expect(sut.remove(auth, user2.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.partner.remove).not.toHaveBeenCalled(); - }); - }); - - describe('update', () => { - it('should require access', async () => { - const user2 = factory.user(); - const auth = factory.auth(); - - await expect(sut.update(auth, user2.id, { inTimeline: false })).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should update partner', async () => { - const user1 = factory.user(); - const user2 = factory.user(); - const partner = factory.partner({ sharedBy: user1, sharedWith: user2 }); - const auth = factory.auth({ user: { id: user1.id } }); - - mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set([user2.id])); - mocks.partner.update.mockResolvedValue(partner); - - await expect(sut.update(auth, user2.id, { inTimeline: true })).resolves.toBeDefined(); - expect(mocks.partner.update).toHaveBeenCalledWith( - { sharedById: user2.id, sharedWithId: user1.id }, - { inTimeline: true }, - ); - }); - }); -}); diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts deleted file mode 100644 index 628efa9d49..0000000000 --- a/server/src/services/partner.service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { Partner } from 'src/database'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { PartnerCreateDto, PartnerResponseDto, PartnerSearchDto, PartnerUpdateDto } from 'src/dtos/partner.dto'; -import { mapUser } from 'src/dtos/user.dto'; -import { Permission } from 'src/enum'; -import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class PartnerService extends BaseService { - async create(auth: AuthDto, { sharedWithId }: PartnerCreateDto): Promise { - const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId }; - const exists = await this.partnerRepository.get(partnerId); - if (exists) { - throw new BadRequestException(`Partner already exists`); - } - - const partner = await this.partnerRepository.create(partnerId); - return this.mapPartner(partner, PartnerDirection.SharedBy); - } - - async remove(auth: AuthDto, sharedWithId: string): Promise { - const partnerId: PartnerIds = { sharedById: auth.user.id, sharedWithId }; - const partner = await this.partnerRepository.get(partnerId); - if (!partner) { - throw new BadRequestException('Partner not found'); - } - - await this.partnerRepository.remove(partnerId); - } - - async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise { - const partners = await this.partnerRepository.getAll(auth.user.id); - const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId'; - return partners - .filter((partner): partner is Partner => !!(partner.sharedBy && partner.sharedWith)) // Filter out soft deleted users - .filter((partner) => partner[key] === auth.user.id) - .map((partner) => this.mapPartner(partner, direction)); - } - - async update(auth: AuthDto, sharedById: string, dto: PartnerUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.PartnerUpdate, ids: [sharedById] }); - const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id }; - - const entity = await this.partnerRepository.update(partnerId, { inTimeline: dto.inTimeline }); - return this.mapPartner(entity, PartnerDirection.SharedWith); - } - - private mapPartner(partner: Partner, direction: PartnerDirection): PartnerResponseDto { - // this is opposite to return the non-me user of the "partner" - const user = mapUser( - direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy, - ) as PartnerResponseDto; - - return { ...user, inTimeline: partner.inTimeline }; - } -} diff --git a/server/src/services/person.service.spec.ts b/server/src/services/person.service.spec.ts deleted file mode 100644 index b57a5e1072..0000000000 --- a/server/src/services/person.service.spec.ts +++ /dev/null @@ -1,1177 +0,0 @@ -import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; -import { mapFaces, mapPerson, PersonResponseDto } from 'src/dtos/person.dto'; -import { CacheControl, JobName, JobStatus, SourceType, SystemMetadataKey } from 'src/enum'; -import { DetectedFaces } from 'src/repositories/machine-learning.repository'; -import { FaceSearchResult } from 'src/repositories/search.repository'; -import { PersonService } from 'src/services/person.service'; -import { ImmichFileResponse } from 'src/utils/file'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { faceStub } from 'test/fixtures/face.stub'; -import { personStub } from 'test/fixtures/person.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { factory } from 'test/small.factory'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -const responseDto: PersonResponseDto = { - id: 'person-1', - name: 'Person 1', - birthDate: null, - thumbnailPath: '/path/to/thumbnail.jpg', - isHidden: false, - updatedAt: expect.any(Date), - isFavorite: false, - color: expect.any(String), -}; - -const statistics = { assets: 3 }; - -const faceId = 'face-id'; -const face = { - id: faceId, - assetId: 'asset-id', - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageHeight: 500, - imageWidth: 400, -}; -const faceSearch = { faceId, embedding: '[1, 2, 3, 4]' }; -const detectFaceMock: DetectedFaces = { - faces: [ - { - boundingBox: { - x1: face.boundingBoxX1, - y1: face.boundingBoxY1, - x2: face.boundingBoxX2, - y2: face.boundingBoxY2, - }, - embedding: faceSearch.embedding, - score: 0.2, - }, - ], - imageHeight: face.imageHeight, - imageWidth: face.imageWidth, -}; - -describe(PersonService.name, () => { - let sut: PersonService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(PersonService)); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('getAll', () => { - it('should get all hidden and visible people with thumbnails', async () => { - mocks.person.getAllForUser.mockResolvedValue({ - items: [personStub.withName, personStub.hidden], - hasNextPage: false, - }); - mocks.person.getNumberOfPeople.mockResolvedValue({ total: 2, hidden: 1 }); - await expect(sut.getAll(authStub.admin, { withHidden: true, page: 1, size: 10 })).resolves.toEqual({ - hasNextPage: false, - total: 2, - hidden: 1, - people: [ - responseDto, - { - id: 'person-1', - name: '', - birthDate: null, - thumbnailPath: '/path/to/thumbnail.jpg', - isHidden: true, - isFavorite: false, - updatedAt: expect.any(Date), - color: expect.any(String), - }, - ], - }); - expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, authStub.admin.user.id, { - minimumFaceCount: 3, - withHidden: true, - }); - }); - - it('should get all visible people and favorites should be first in the array', async () => { - mocks.person.getAllForUser.mockResolvedValue({ - items: [personStub.isFavorite, personStub.withName], - hasNextPage: false, - }); - mocks.person.getNumberOfPeople.mockResolvedValue({ total: 2, hidden: 1 }); - await expect(sut.getAll(authStub.admin, { withHidden: false, page: 1, size: 10 })).resolves.toEqual({ - hasNextPage: false, - total: 2, - hidden: 1, - people: [ - { - id: 'person-4', - name: personStub.isFavorite.name, - birthDate: null, - thumbnailPath: '/path/to/thumbnail.jpg', - isHidden: false, - isFavorite: true, - updatedAt: expect.any(Date), - color: personStub.isFavorite.color, - }, - responseDto, - ], - }); - expect(mocks.person.getAllForUser).toHaveBeenCalledWith({ skip: 0, take: 10 }, authStub.admin.user.id, { - minimumFaceCount: 3, - withHidden: false, - }); - }); - }); - - describe('getById', () => { - it('should require person.read permission', async () => { - mocks.person.getById.mockResolvedValue(personStub.withName); - await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should throw a bad request when person is not found', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.getById(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should get a person by id', async () => { - mocks.person.getById.mockResolvedValue(personStub.withName); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.getById(authStub.admin, 'person-1')).resolves.toEqual(responseDto); - expect(mocks.person.getById).toHaveBeenCalledWith('person-1'); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - }); - - describe('getThumbnail', () => { - it('should require person.read permission', async () => { - mocks.person.getById.mockResolvedValue(personStub.noName); - await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.storage.createReadStream).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should throw an error when personId is invalid', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException); - expect(mocks.storage.createReadStream).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should throw an error when person has no thumbnail', async () => { - mocks.person.getById.mockResolvedValue(personStub.noThumbnail); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.getThumbnail(authStub.admin, 'person-1')).rejects.toBeInstanceOf(NotFoundException); - expect(mocks.storage.createReadStream).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should serve the thumbnail', async () => { - mocks.person.getById.mockResolvedValue(personStub.noName); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.getThumbnail(authStub.admin, 'person-1')).resolves.toEqual( - new ImmichFileResponse({ - path: '/path/to/thumbnail.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.PrivateWithoutCache, - }), - ); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - }); - - describe('update', () => { - it('should require person.write permission', async () => { - mocks.person.getById.mockResolvedValue(personStub.noName); - await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).rejects.toBeInstanceOf( - BadRequestException, - ); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should throw an error when personId is invalid', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set()); - await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).rejects.toBeInstanceOf( - BadRequestException, - ); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it("should update a person's name", async () => { - mocks.person.update.mockResolvedValue(personStub.withName); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect(sut.update(authStub.admin, 'person-1', { name: 'Person 1' })).resolves.toEqual(responseDto); - - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', name: 'Person 1' }); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it("should update a person's date of birth", async () => { - mocks.person.update.mockResolvedValue(personStub.withBirthDate); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect(sut.update(authStub.admin, 'person-1', { birthDate: new Date('1976-06-30') })).resolves.toEqual({ - id: 'person-1', - name: 'Person 1', - birthDate: '1976-06-30', - thumbnailPath: '/path/to/thumbnail.jpg', - isHidden: false, - isFavorite: false, - updatedAt: expect.any(Date), - color: expect.any(String), - }); - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', birthDate: new Date('1976-06-30') }); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should update a person visibility', async () => { - mocks.person.update.mockResolvedValue(personStub.withName); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect(sut.update(authStub.admin, 'person-1', { isHidden: false })).resolves.toEqual(responseDto); - - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', isHidden: false }); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should update a person favorite status', async () => { - mocks.person.update.mockResolvedValue(personStub.withName); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect(sut.update(authStub.admin, 'person-1', { isFavorite: true })).resolves.toEqual(responseDto); - - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', isFavorite: true }); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it("should update a person's thumbnailPath", async () => { - mocks.person.update.mockResolvedValue(personStub.withName); - mocks.person.getFacesByIds.mockResolvedValue([faceStub.face1]); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect( - sut.update(authStub.admin, 'person-1', { featureFaceAssetId: faceStub.face1.assetId }), - ).resolves.toEqual(responseDto); - - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'person-1', faceAssetId: faceStub.face1.id }); - expect(mocks.person.getFacesByIds).toHaveBeenCalledWith([ - { - assetId: faceStub.face1.assetId, - personId: 'person-1', - }, - ]); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.PersonGenerateThumbnail, - data: { id: 'person-1' }, - }); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should throw an error when the face feature assetId is invalid', async () => { - mocks.person.getById.mockResolvedValue(personStub.withName); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect(sut.update(authStub.admin, 'person-1', { featureFaceAssetId: '-1' })).rejects.toThrow( - BadRequestException, - ); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - }); - - describe('updateAll', () => { - it('should throw an error when personId is invalid', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set()); - - await expect(sut.updateAll(authStub.admin, { people: [{ id: 'person-1', name: 'Person 1' }] })).resolves.toEqual([ - { error: BulkIdErrorReason.UNKNOWN, id: 'person-1', success: false }, - ]); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - }); - - describe('reassignFaces', () => { - it('should throw an error if user has no access to the person', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set()); - - await expect( - sut.reassignFaces(authStub.admin, personStub.noName.id, { - data: [{ personId: 'asset-face-1', assetId: '' }], - }), - ).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.job.queue).not.toHaveBeenCalledWith(); - expect(mocks.job.queueAll).not.toHaveBeenCalledWith(); - }); - it('should reassign a face', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.withName.id])); - mocks.person.getById.mockResolvedValue(personStub.noName); - mocks.access.person.checkFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id])); - mocks.person.getFacesByIds.mockResolvedValue([faceStub.face1]); - mocks.person.reassignFace.mockResolvedValue(1); - mocks.person.getRandomFace.mockResolvedValue(faceStub.primaryFace1); - mocks.person.refreshFaces.mockResolvedValue(); - mocks.person.reassignFace.mockResolvedValue(5); - mocks.person.update.mockResolvedValue(personStub.noName); - - await expect( - sut.reassignFaces(authStub.admin, personStub.noName.id, { - data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }], - }), - ).resolves.toBeDefined(); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.PersonGenerateThumbnail, - data: { id: personStub.newThumbnail.id }, - }, - ]); - }); - }); - - describe('handlePersonMigration', () => { - it('should not move person files', async () => { - await expect(sut.handlePersonMigration(personStub.noName)).resolves.toBe(JobStatus.Failed); - }); - }); - - describe('getFacesById', () => { - it('should get the bounding boxes for an asset', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([faceStub.face1.assetId])); - mocks.person.getFaces.mockResolvedValue([faceStub.primaryFace1]); - mocks.asset.getById.mockResolvedValue(assetStub.image); - await expect(sut.getFacesById(authStub.admin, { id: faceStub.face1.assetId })).resolves.toStrictEqual([ - mapFaces(faceStub.primaryFace1, authStub.admin), - ]); - }); - it('should reject if the user has not access to the asset', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set()); - mocks.person.getFaces.mockResolvedValue([faceStub.primaryFace1]); - await expect(sut.getFacesById(authStub.admin, { id: faceStub.primaryFace1.assetId })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - }); - - describe('createNewFeaturePhoto', () => { - it('should change person feature photo', async () => { - mocks.person.getRandomFace.mockResolvedValue(faceStub.primaryFace1); - await sut.createNewFeaturePhoto([personStub.newThumbnail.id]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.PersonGenerateThumbnail, - data: { id: personStub.newThumbnail.id }, - }, - ]); - }); - }); - - describe('reassignFacesById', () => { - it('should create a new person', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id])); - mocks.access.person.checkFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id])); - mocks.person.getFaceById.mockResolvedValue(faceStub.face1); - mocks.person.reassignFace.mockResolvedValue(1); - mocks.person.getById.mockResolvedValue(personStub.noName); - await expect( - sut.reassignFacesById(authStub.admin, personStub.noName.id, { - id: faceStub.face1.id, - }), - ).resolves.toEqual({ - birthDate: personStub.noName.birthDate, - isHidden: personStub.noName.isHidden, - isFavorite: personStub.noName.isFavorite, - id: personStub.noName.id, - name: personStub.noName.name, - thumbnailPath: personStub.noName.thumbnailPath, - updatedAt: expect.any(Date), - color: personStub.noName.color, - }); - - expect(mocks.job.queue).not.toHaveBeenCalledWith(); - expect(mocks.job.queueAll).not.toHaveBeenCalledWith(); - }); - - it('should fail if user has not the correct permissions on the asset', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id])); - mocks.person.getFaceById.mockResolvedValue(faceStub.face1); - mocks.person.reassignFace.mockResolvedValue(1); - mocks.person.getById.mockResolvedValue(personStub.noName); - await expect( - sut.reassignFacesById(authStub.admin, personStub.noName.id, { - id: faceStub.face1.id, - }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.job.queue).not.toHaveBeenCalledWith(); - expect(mocks.job.queueAll).not.toHaveBeenCalledWith(); - }); - }); - - describe('createPerson', () => { - it('should create a new person', async () => { - mocks.person.create.mockResolvedValue(personStub.primaryPerson); - - await expect(sut.create(authStub.admin, {})).resolves.toBeDefined(); - - expect(mocks.person.create).toHaveBeenCalledWith({ ownerId: authStub.admin.user.id }); - }); - }); - - describe('handlePersonCleanup', () => { - it('should delete people without faces', async () => { - mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.noName]); - - await sut.handlePersonCleanup(); - - expect(mocks.person.delete).toHaveBeenCalledWith([personStub.noName.id]); - expect(mocks.storage.unlink).toHaveBeenCalledWith(personStub.noName.thumbnailPath); - }); - }); - - describe('handleQueueDetectFaces', () => { - it('should skip if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await expect(sut.handleQueueDetectFaces({})).resolves.toBe(JobStatus.Skipped); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should queue missing assets', async () => { - mocks.assetJob.streamForDetectFacesJob.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueDetectFaces({ force: false }); - - expect(mocks.assetJob.streamForDetectFacesJob).toHaveBeenCalledWith(false); - expect(mocks.person.vacuum).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDetectFaces, - data: { id: assetStub.image.id }, - }, - ]); - }); - - it('should queue all assets', async () => { - mocks.assetJob.streamForDetectFacesJob.mockReturnValue(makeStream([assetStub.image])); - mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.withName]); - - await sut.handleQueueDetectFaces({ force: true }); - - expect(mocks.person.deleteFaces).toHaveBeenCalledWith({ sourceType: SourceType.MachineLearning }); - expect(mocks.person.delete).toHaveBeenCalledWith([personStub.withName.id]); - expect(mocks.person.vacuum).toHaveBeenCalledWith({ reindexVectors: true }); - expect(mocks.storage.unlink).toHaveBeenCalledWith(personStub.withName.thumbnailPath); - expect(mocks.assetJob.streamForDetectFacesJob).toHaveBeenCalledWith(true); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDetectFaces, - data: { id: assetStub.image.id }, - }, - ]); - }); - - it('should refresh all assets', async () => { - mocks.assetJob.streamForDetectFacesJob.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueDetectFaces({ force: undefined }); - - expect(mocks.person.delete).not.toHaveBeenCalled(); - expect(mocks.person.deleteFaces).not.toHaveBeenCalled(); - expect(mocks.person.vacuum).not.toHaveBeenCalled(); - expect(mocks.storage.unlink).not.toHaveBeenCalled(); - expect(mocks.assetJob.streamForDetectFacesJob).toHaveBeenCalledWith(undefined); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDetectFaces, - data: { id: assetStub.image.id }, - }, - ]); - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.PersonCleanup }); - }); - - it('should delete existing people and faces if forced', async () => { - mocks.person.getAll.mockReturnValue(makeStream([faceStub.face1.person, personStub.randomPerson])); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.assetJob.streamForDetectFacesJob.mockReturnValue(makeStream([assetStub.image])); - mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]); - mocks.person.deleteFaces.mockResolvedValue(); - - await sut.handleQueueDetectFaces({ force: true }); - - expect(mocks.assetJob.streamForDetectFacesJob).toHaveBeenCalledWith(true); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDetectFaces, - data: { id: assetStub.image.id }, - }, - ]); - expect(mocks.person.delete).toHaveBeenCalledWith([personStub.randomPerson.id]); - expect(mocks.storage.unlink).toHaveBeenCalledWith(personStub.randomPerson.thumbnailPath); - expect(mocks.person.vacuum).toHaveBeenCalledWith({ reindexVectors: true }); - }); - }); - - describe('handleQueueRecognizeFaces', () => { - it('should skip if machine learning is disabled', async () => { - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - waiting: 0, - paused: 0, - completed: 0, - failed: 0, - delayed: 0, - }); - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await expect(sut.handleQueueRecognizeFaces({})).resolves.toBe(JobStatus.Skipped); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - }); - - it('should skip if recognition jobs are already queued', async () => { - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - waiting: 1, - paused: 0, - completed: 0, - failed: 0, - delayed: 0, - }); - - await expect(sut.handleQueueRecognizeFaces({})).resolves.toBe(JobStatus.Skipped); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - }); - - it('should queue missing assets', async () => { - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - waiting: 0, - paused: 0, - completed: 0, - failed: 0, - delayed: 0, - }); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.person.getAllWithoutFaces.mockResolvedValue([]); - - await sut.handleQueueRecognizeFaces({}); - - expect(mocks.person.getAllFaces).toHaveBeenCalledWith({ - personId: null, - sourceType: SourceType.MachineLearning, - }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.FacialRecognition, - data: { id: faceStub.face1.id, deferred: false }, - }, - ]); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.FacialRecognitionState, { - lastRun: expect.any(String), - }); - expect(mocks.person.vacuum).not.toHaveBeenCalled(); - }); - - it('should queue all assets', async () => { - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - waiting: 0, - paused: 0, - completed: 0, - failed: 0, - delayed: 0, - }); - mocks.person.getAll.mockReturnValue(makeStream()); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.person.getAllWithoutFaces.mockResolvedValue([]); - - await sut.handleQueueRecognizeFaces({ force: true }); - - expect(mocks.person.getAllFaces).toHaveBeenCalledWith(undefined); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.FacialRecognition, - data: { id: faceStub.face1.id, deferred: false }, - }, - ]); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.FacialRecognitionState, { - lastRun: expect.any(String), - }); - expect(mocks.person.vacuum).toHaveBeenCalledWith({ reindexVectors: false }); - }); - - it('should run nightly if new face has been added since last run', async () => { - mocks.person.getLatestFaceDate.mockResolvedValue(new Date().toISOString()); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - waiting: 0, - paused: 0, - completed: 0, - failed: 0, - delayed: 0, - }); - mocks.person.getAll.mockReturnValue(makeStream()); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.person.getAllWithoutFaces.mockResolvedValue([]); - mocks.person.unassignFaces.mockResolvedValue(); - - await sut.handleQueueRecognizeFaces({ force: false, nightly: true }); - - expect(mocks.systemMetadata.get).toHaveBeenCalledWith(SystemMetadataKey.FacialRecognitionState); - expect(mocks.person.getLatestFaceDate).toHaveBeenCalledOnce(); - expect(mocks.person.getAllFaces).toHaveBeenCalledWith({ - personId: null, - sourceType: SourceType.MachineLearning, - }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.FacialRecognition, - data: { id: faceStub.face1.id, deferred: false }, - }, - ]); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.FacialRecognitionState, { - lastRun: expect.any(String), - }); - expect(mocks.person.vacuum).not.toHaveBeenCalled(); - }); - - it('should skip nightly if no new face has been added since last run', async () => { - const lastRun = new Date(); - - mocks.systemMetadata.get.mockResolvedValue({ lastRun: lastRun.toISOString() }); - mocks.person.getLatestFaceDate.mockResolvedValue(new Date(lastRun.getTime() - 1).toISOString()); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.person.getAllWithoutFaces.mockResolvedValue([]); - - await sut.handleQueueRecognizeFaces({ force: true, nightly: true }); - - expect(mocks.systemMetadata.get).toHaveBeenCalledWith(SystemMetadataKey.FacialRecognitionState); - expect(mocks.person.getLatestFaceDate).toHaveBeenCalledOnce(); - expect(mocks.person.getAllFaces).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - expect(mocks.person.vacuum).not.toHaveBeenCalled(); - }); - - it('should delete existing people if forced', async () => { - mocks.job.getJobCounts.mockResolvedValue({ - active: 1, - waiting: 0, - paused: 0, - completed: 0, - failed: 0, - delayed: 0, - }); - mocks.person.getAll.mockReturnValue(makeStream([faceStub.face1.person, personStub.randomPerson])); - mocks.person.getAllFaces.mockReturnValue(makeStream([faceStub.face1])); - mocks.person.getAllWithoutFaces.mockResolvedValue([personStub.randomPerson]); - mocks.person.unassignFaces.mockResolvedValue(); - - await sut.handleQueueRecognizeFaces({ force: true }); - - expect(mocks.person.deleteFaces).not.toHaveBeenCalled(); - expect(mocks.person.unassignFaces).toHaveBeenCalledWith({ sourceType: SourceType.MachineLearning }); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.FacialRecognition, - data: { id: faceStub.face1.id, deferred: false }, - }, - ]); - expect(mocks.person.delete).toHaveBeenCalledWith([personStub.randomPerson.id]); - expect(mocks.storage.unlink).toHaveBeenCalledWith(personStub.randomPerson.thumbnailPath); - expect(mocks.person.vacuum).toHaveBeenCalledWith({ reindexVectors: false }); - }); - }); - - describe('handleDetectFaces', () => { - beforeEach(() => { - mocks.crypto.randomUUID.mockReturnValue(faceId); - }); - - it('should skip if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await expect(sut.handleDetectFaces({ id: 'foo' })).resolves.toBe(JobStatus.Skipped); - expect(mocks.asset.getByIds).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - - it('should skip when no resize path', async () => { - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ ...assetStub.noResizePath, files: [] }); - await sut.handleDetectFaces({ id: assetStub.noResizePath.id }); - expect(mocks.machineLearning.detectFaces).not.toHaveBeenCalled(); - }); - - it('should handle no results', async () => { - const start = Date.now(); - - mocks.machineLearning.detectFaces.mockResolvedValue({ imageHeight: 500, imageWidth: 400, faces: [] }); - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ ...assetStub.image, files: [assetStub.image.files[1]] }); - await sut.handleDetectFaces({ id: assetStub.image.id }); - expect(mocks.machineLearning.detectFaces).toHaveBeenCalledWith( - '/uploads/user-id/thumbs/path.jpg', - expect.objectContaining({ minScore: 0.7, modelName: 'buffalo_l' }), - ); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - - expect(mocks.asset.upsertJobStatus).toHaveBeenCalledWith({ - assetId: assetStub.image.id, - facesRecognizedAt: expect.any(Date), - }); - const facesRecognizedAt = mocks.asset.upsertJobStatus.mock.calls[0][0].facesRecognizedAt as Date; - expect(facesRecognizedAt.getTime()).toBeGreaterThan(start); - }); - - it('should create a face with no person and queue recognition job', async () => { - mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); - mocks.search.searchFaces.mockResolvedValue([{ ...faceStub.face1, distance: 0.7 }]); - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ ...assetStub.image, files: [assetStub.image.files[1]] }); - mocks.person.refreshFaces.mockResolvedValue(); - - await sut.handleDetectFaces({ id: assetStub.image.id }); - - expect(mocks.person.refreshFaces).toHaveBeenCalledWith([face], [], [faceSearch]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.FacialRecognitionQueueAll, data: { force: false } }, - { name: JobName.FacialRecognition, data: { id: faceId } }, - ]); - expect(mocks.person.reassignFace).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - - it('should delete an existing face not among the new detected faces', async () => { - mocks.machineLearning.detectFaces.mockResolvedValue({ faces: [], imageHeight: 500, imageWidth: 400 }); - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ - ...assetStub.image, - faces: [faceStub.primaryFace1], - files: [assetStub.image.files[1]], - }); - - await sut.handleDetectFaces({ id: assetStub.image.id }); - - expect(mocks.person.refreshFaces).toHaveBeenCalledWith([], [faceStub.primaryFace1.id], []); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.person.reassignFace).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - - it('should add new face and delete an existing face not among the new detected faces', async () => { - mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ - ...assetStub.image, - faces: [faceStub.primaryFace1], - files: [assetStub.image.files[1]], - }); - mocks.person.refreshFaces.mockResolvedValue(); - - await sut.handleDetectFaces({ id: assetStub.image.id }); - - expect(mocks.person.refreshFaces).toHaveBeenCalledWith([face], [faceStub.primaryFace1.id], [faceSearch]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.FacialRecognitionQueueAll, data: { force: false } }, - { name: JobName.FacialRecognition, data: { id: faceId } }, - ]); - expect(mocks.person.reassignFace).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - - it('should add embedding to matching metadata face', async () => { - mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ - ...assetStub.image, - faces: [faceStub.fromExif1], - files: [assetStub.image.files[1]], - }); - mocks.person.refreshFaces.mockResolvedValue(); - - await sut.handleDetectFaces({ id: assetStub.image.id }); - - expect(mocks.person.refreshFaces).toHaveBeenCalledWith( - [], - [], - [{ faceId: faceStub.fromExif1.id, embedding: faceSearch.embedding }], - ); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - expect(mocks.person.reassignFace).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - - it('should not add embedding to non-matching metadata face', async () => { - mocks.machineLearning.detectFaces.mockResolvedValue(detectFaceMock); - mocks.assetJob.getForDetectFacesJob.mockResolvedValue({ - ...assetStub.image, - faces: [faceStub.fromExif2], - files: [assetStub.image.files[1]], - }); - - await sut.handleDetectFaces({ id: assetStub.image.id }); - - expect(mocks.person.refreshFaces).toHaveBeenCalledWith([face], [], [faceSearch]); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.FacialRecognitionQueueAll, data: { force: false } }, - { name: JobName.FacialRecognition, data: { id: faceId } }, - ]); - expect(mocks.person.reassignFace).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - }); - - describe('handleRecognizeFaces', () => { - it('should fail if face does not exist', async () => { - expect(await sut.handleRecognizeFaces({ id: faceStub.face1.id })).toBe(JobStatus.Failed); - - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - expect(mocks.person.create).not.toHaveBeenCalled(); - }); - - it('should fail if face does not have asset', async () => { - const face = { ...faceStub.face1, asset: null }; - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(face); - - expect(await sut.handleRecognizeFaces({ id: faceStub.face1.id })).toBe(JobStatus.Failed); - - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - expect(mocks.person.create).not.toHaveBeenCalled(); - }); - - it('should skip if face already has an assigned person', async () => { - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.face1); - - expect(await sut.handleRecognizeFaces({ id: faceStub.face1.id })).toBe(JobStatus.Skipped); - - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - expect(mocks.person.create).not.toHaveBeenCalled(); - }); - - it('should match existing person', async () => { - if (!faceStub.primaryFace1.person) { - throw new Error('faceStub.primaryFace1.person is null'); - } - - const faces = [ - { ...faceStub.noPerson1, distance: 0 }, - { ...faceStub.primaryFace1, distance: 0.2 }, - { ...faceStub.noPerson2, distance: 0.3 }, - { ...faceStub.face1, distance: 0.4 }, - ] as FaceSearchResult[]; - - mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } }); - mocks.search.searchFaces.mockResolvedValue(faces); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); - - expect(mocks.person.create).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: expect.arrayContaining([faceStub.noPerson1.id]), - newPersonId: faceStub.primaryFace1.person.id, - }); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: expect.not.arrayContaining([faceStub.face1.id]), - newPersonId: faceStub.primaryFace1.person.id, - }); - }); - - it('should match existing person if their birth date is unknown', async () => { - if (!faceStub.primaryFace1.person) { - throw new Error('faceStub.primaryFace1.person is null'); - } - - const faces = [ - { ...faceStub.noPerson1, distance: 0 }, - { ...faceStub.primaryFace1, distance: 0.2 }, - { ...faceStub.withBirthDate, distance: 0.3 }, - ] as FaceSearchResult[]; - - mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } }); - mocks.search.searchFaces.mockResolvedValue(faces); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); - - expect(mocks.person.create).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: expect.arrayContaining([faceStub.noPerson1.id]), - newPersonId: faceStub.primaryFace1.person.id, - }); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: expect.not.arrayContaining([faceStub.face1.id]), - newPersonId: faceStub.primaryFace1.person.id, - }); - }); - - it('should match existing person if their birth date is before file creation', async () => { - if (!faceStub.primaryFace1.person) { - throw new Error('faceStub.primaryFace1.person is null'); - } - - const faces = [ - { ...faceStub.noPerson1, distance: 0 }, - { ...faceStub.withBirthDate, distance: 0.2 }, - { ...faceStub.primaryFace1, distance: 0.3 }, - ] as FaceSearchResult[]; - - mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } }); - mocks.search.searchFaces.mockResolvedValue(faces); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(faceStub.primaryFace1.person); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); - - expect(mocks.person.create).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).toHaveBeenCalledTimes(1); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: expect.arrayContaining([faceStub.noPerson1.id]), - newPersonId: faceStub.withBirthDate.person?.id, - }); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: expect.not.arrayContaining([faceStub.face1.id]), - newPersonId: faceStub.withBirthDate.person?.id, - }); - }); - - it('should create a new person if the face is a core point with no person', async () => { - const faces = [ - { ...faceStub.noPerson1, distance: 0 }, - { ...faceStub.noPerson2, distance: 0.3 }, - ] as FaceSearchResult[]; - - mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 1 } } }); - mocks.search.searchFaces.mockResolvedValue(faces); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(personStub.withName); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); - - expect(mocks.person.create).toHaveBeenCalledWith({ - ownerId: faceStub.noPerson1.asset.ownerId, - faceAssetId: faceStub.noPerson1.id, - }); - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - faceIds: [faceStub.noPerson1.id], - newPersonId: personStub.withName.id, - }); - }); - - it('should not queue face with no matches', async () => { - const faces = [{ ...faceStub.noPerson1, distance: 0 }] as FaceSearchResult[]; - - mocks.search.searchFaces.mockResolvedValue(faces); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(personStub.withName); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.search.searchFaces).toHaveBeenCalledTimes(1); - expect(mocks.person.create).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - - it('should defer non-core faces to end of queue', async () => { - const faces = [ - { ...faceStub.noPerson1, distance: 0 }, - { ...faceStub.noPerson2, distance: 0.4 }, - ] as FaceSearchResult[]; - - mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 3 } } }); - mocks.search.searchFaces.mockResolvedValue(faces); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(personStub.withName); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.FacialRecognition, - data: { id: faceStub.noPerson1.id, deferred: true }, - }); - expect(mocks.search.searchFaces).toHaveBeenCalledTimes(1); - expect(mocks.person.create).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - - it('should not assign person to deferred non-core face with no matching person', async () => { - const faces = [ - { ...faceStub.noPerson1, distance: 0 }, - { ...faceStub.noPerson2, distance: 0.4 }, - ] as FaceSearchResult[]; - - mocks.systemMetadata.get.mockResolvedValue({ machineLearning: { facialRecognition: { minFaces: 3 } } }); - mocks.search.searchFaces.mockResolvedValueOnce(faces).mockResolvedValueOnce([]); - mocks.person.getFaceForFacialRecognitionJob.mockResolvedValue(faceStub.noPerson1); - mocks.person.create.mockResolvedValue(personStub.withName); - - await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id, deferred: true }); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.search.searchFaces).toHaveBeenCalledTimes(2); - expect(mocks.person.create).not.toHaveBeenCalled(); - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - }); - }); - - describe('mergePerson', () => { - it('should require person.write and person.merge permission', async () => { - mocks.person.getById.mockResolvedValueOnce(personStub.primaryPerson); - mocks.person.getById.mockResolvedValueOnce(personStub.mergePerson); - - await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - - expect(mocks.person.delete).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should merge two people without smart merge', async () => { - mocks.person.getById.mockResolvedValueOnce(personStub.primaryPerson); - mocks.person.getById.mockResolvedValueOnce(personStub.mergePerson); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1'])); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2'])); - - await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ - { id: 'person-2', success: true }, - ]); - - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - newPersonId: personStub.primaryPerson.id, - oldPersonId: personStub.mergePerson.id, - }); - - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should merge two people with smart merge', async () => { - mocks.person.getById.mockResolvedValueOnce(personStub.randomPerson); - mocks.person.getById.mockResolvedValueOnce(personStub.primaryPerson); - mocks.person.update.mockResolvedValue({ ...personStub.randomPerson, name: personStub.primaryPerson.name }); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-3'])); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1'])); - - await expect(sut.mergePerson(authStub.admin, 'person-3', { ids: ['person-1'] })).resolves.toEqual([ - { id: 'person-1', success: true }, - ]); - - expect(mocks.person.reassignFaces).toHaveBeenCalledWith({ - newPersonId: personStub.randomPerson.id, - oldPersonId: personStub.primaryPerson.id, - }); - - expect(mocks.person.update).toHaveBeenCalledWith({ - id: personStub.randomPerson.id, - name: personStub.primaryPerson.name, - }); - - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should throw an error when the primary person is not found', async () => { - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - - await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.person.delete).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should handle invalid merge ids', async () => { - mocks.person.getById.mockResolvedValueOnce(personStub.primaryPerson); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1'])); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2'])); - - await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ - { id: 'person-2', success: false, error: BulkIdErrorReason.NOT_FOUND }, - ]); - - expect(mocks.person.reassignFaces).not.toHaveBeenCalled(); - expect(mocks.person.delete).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should handle an error reassigning faces', async () => { - mocks.person.getById.mockResolvedValueOnce(personStub.primaryPerson); - mocks.person.getById.mockResolvedValueOnce(personStub.mergePerson); - mocks.person.reassignFaces.mockRejectedValue(new Error('update failed')); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1'])); - mocks.access.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2'])); - - await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([ - { id: 'person-2', success: false, error: BulkIdErrorReason.UNKNOWN }, - ]); - - expect(mocks.person.delete).not.toHaveBeenCalled(); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - }); - - describe('getStatistics', () => { - it('should get correct number of person', async () => { - mocks.person.getById.mockResolvedValue(personStub.primaryPerson); - mocks.person.getStatistics.mockResolvedValue(statistics); - mocks.access.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1'])); - await expect(sut.getStatistics(authStub.admin, 'person-1')).resolves.toEqual({ assets: 3 }); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - - it('should require person.read permission', async () => { - mocks.person.getById.mockResolvedValue(personStub.primaryPerson); - await expect(sut.getStatistics(authStub.admin, 'person-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.access.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['person-1'])); - }); - }); - - describe('mapFace', () => { - it('should map a face', () => { - const authDto = factory.auth({ user: { id: faceStub.face1.person.ownerId } }); - expect(mapFaces(faceStub.face1, authDto)).toEqual({ - boundingBoxX1: 0, - boundingBoxX2: 1, - boundingBoxY1: 0, - boundingBoxY2: 1, - id: faceStub.face1.id, - imageHeight: 1024, - imageWidth: 1024, - sourceType: SourceType.MachineLearning, - person: mapPerson(personStub.withName), - }); - }); - - it('should not map person if person is null', () => { - expect(mapFaces({ ...faceStub.face1, person: null }, authStub.user1).person).toBeNull(); - }); - - it('should not map person if person does not match auth user id', () => { - expect(mapFaces(faceStub.face1, authStub.user1).person).toBeNull(); - }); - }); -}); diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts deleted file mode 100644 index 52a4e6048f..0000000000 --- a/server/src/services/person.service.ts +++ /dev/null @@ -1,702 +0,0 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -import { Insertable, Updateable } from 'kysely'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { Person } from 'src/database'; -import { Chunked, OnJob } from 'src/decorators'; -import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - AssetFaceCreateDto, - AssetFaceDeleteDto, - AssetFaceResponseDto, - AssetFaceUpdateDto, - FaceDto, - mapFaces, - mapPerson, - MergePersonDto, - PeopleResponseDto, - PeopleUpdateDto, - PersonCreateDto, - PersonResponseDto, - PersonSearchDto, - PersonStatisticsResponseDto, - PersonUpdateDto, -} from 'src/dtos/person.dto'; -import { - AssetVisibility, - CacheControl, - JobName, - JobStatus, - Permission, - PersonPathType, - QueueName, - SourceType, - SystemMetadataKey, - VectorIndex, -} from 'src/enum'; -import { BoundingBox } from 'src/repositories/machine-learning.repository'; -import { UpdateFacesData } from 'src/repositories/person.repository'; -import { AssetFaceTable } from 'src/schema/tables/asset-face.table'; -import { FaceSearchTable } from 'src/schema/tables/face-search.table'; -import { BaseService } from 'src/services/base.service'; -import { JobItem, JobOf } from 'src/types'; -import { getDimensions } from 'src/utils/asset.util'; -import { ImmichFileResponse } from 'src/utils/file'; -import { mimeTypes } from 'src/utils/mime-types'; -import { isFacialRecognitionEnabled } from 'src/utils/misc'; -import { Point, transformPoints } from 'src/utils/transform'; - -@Injectable() -export class PersonService extends BaseService { - async getAll(auth: AuthDto, dto: PersonSearchDto): Promise { - const { withHidden = false, closestAssetId, closestPersonId, page, size } = dto; - let closestFaceAssetId = closestAssetId; - const pagination = { - take: size, - skip: (page - 1) * size, - }; - - if (closestPersonId) { - const person = await this.personRepository.getById(closestPersonId); - if (!person?.faceAssetId) { - throw new NotFoundException('Person not found'); - } - closestFaceAssetId = person.faceAssetId; - } - const { machineLearning } = await this.getConfig({ withCache: false }); - const { items, hasNextPage } = await this.personRepository.getAllForUser(pagination, auth.user.id, { - minimumFaceCount: machineLearning.facialRecognition.minFaces, - withHidden, - closestFaceAssetId, - }); - const { total, hidden } = await this.personRepository.getNumberOfPeople(auth.user.id); - - return { - people: items.map((person) => mapPerson(person)), - hasNextPage, - total, - hidden, - }; - } - - async reassignFaces(auth: AuthDto, personId: string, dto: AssetFaceUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [personId] }); - const person = await this.findOrFail(personId); - const result: PersonResponseDto[] = []; - const changeFeaturePhoto: string[] = []; - for (const data of dto.data) { - const faces = await this.personRepository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]); - - for (const face of faces) { - await this.requireAccess({ auth, permission: Permission.PersonCreate, ids: [face.id] }); - if (person.faceAssetId === null) { - changeFeaturePhoto.push(person.id); - } - if (face.person && face.person.faceAssetId === face.id) { - changeFeaturePhoto.push(face.person.id); - } - - await this.personRepository.reassignFace(face.id, personId); - } - - result.push(mapPerson(person)); - } - if (changeFeaturePhoto.length > 0) { - // Remove duplicates - await this.createNewFeaturePhoto([...new Set(changeFeaturePhoto)]); - } - return result; - } - - async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise { - await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [personId] }); - await this.requireAccess({ auth, permission: Permission.PersonCreate, ids: [dto.id] }); - const face = await this.personRepository.getFaceById(dto.id); - const person = await this.findOrFail(personId); - - await this.personRepository.reassignFace(face.id, personId); - if (person.faceAssetId === null) { - await this.createNewFeaturePhoto([person.id]); - } - if (face.person && face.person.faceAssetId === face.id) { - await this.createNewFeaturePhoto([face.person.id]); - } - - return await this.findOrFail(personId).then(mapPerson); - } - - async getFacesById(auth: AuthDto, dto: FaceDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.id] }); - const faces = await this.personRepository.getFaces(dto.id); - const asset = await this.assetRepository.getById(dto.id, { edits: true, exifInfo: true }); - const assetDimensions = getDimensions(asset!.exifInfo!); - - return faces.map((face) => mapFaces(face, auth, asset!.edits!, assetDimensions)); - } - - async createNewFeaturePhoto(changeFeaturePhoto: string[]) { - this.logger.debug( - `Changing feature photos for ${changeFeaturePhoto.length} ${changeFeaturePhoto.length > 1 ? 'people' : 'person'}`, - ); - - const jobs: JobItem[] = []; - for (const personId of changeFeaturePhoto) { - const assetFace = await this.personRepository.getRandomFace(personId); - - if (assetFace) { - await this.personRepository.update({ id: personId, faceAssetId: assetFace.id }); - jobs.push({ name: JobName.PersonGenerateThumbnail, data: { id: personId } }); - } - } - - await this.jobRepository.queueAll(jobs); - } - - async getById(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] }); - return this.findOrFail(id).then(mapPerson); - } - - async getStatistics(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] }); - return this.personRepository.getStatistics(id); - } - - async getThumbnail(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.PersonRead, ids: [id] }); - const person = await this.personRepository.getById(id); - if (!person || !person.thumbnailPath) { - throw new NotFoundException(); - } - - return new ImmichFileResponse({ - path: person.thumbnailPath, - contentType: mimeTypes.lookup(person.thumbnailPath), - cacheControl: CacheControl.PrivateWithoutCache, - }); - } - - async create(auth: AuthDto, dto: PersonCreateDto): Promise { - const person = await this.personRepository.create({ - ownerId: auth.user.id, - name: dto.name, - birthDate: dto.birthDate, - isHidden: dto.isHidden, - isFavorite: dto.isFavorite, - color: dto.color, - }); - - return mapPerson(person); - } - - async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [id] }); - - const { name, birthDate, isHidden, featureFaceAssetId: assetId, isFavorite, color } = dto; - // TODO: set by faceId directly - let faceId: string | undefined = undefined; - if (assetId) { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [assetId] }); - const [face] = await this.personRepository.getFacesByIds([{ personId: id, assetId }]); - if (!face) { - throw new BadRequestException('Invalid assetId for feature face'); - } - - if (face.asset.isOffline) { - throw new BadRequestException('An offline asset cannot be used for feature face'); - } - - faceId = face.id; - } - - const person = await this.personRepository.update({ - id, - faceAssetId: faceId, - name, - birthDate, - isHidden, - isFavorite, - color, - }); - - if (assetId) { - await this.jobRepository.queue({ name: JobName.PersonGenerateThumbnail, data: { id } }); - } - - return mapPerson(person); - } - - delete(auth: AuthDto, id: string): Promise { - return this.deleteAll(auth, { ids: [id] }); - } - - async updateAll(auth: AuthDto, dto: PeopleUpdateDto): Promise { - const results: BulkIdResponseDto[] = []; - for (const person of dto.people) { - try { - await this.update(auth, person.id, { - isHidden: person.isHidden, - name: person.name, - birthDate: person.birthDate, - featureFaceAssetId: person.featureFaceAssetId, - isFavorite: person.isFavorite, - }); - results.push({ id: person.id, success: true }); - } catch (error: Error | any) { - this.logger.error(`Unable to update ${person.id} : ${error}`, error?.stack); - results.push({ id: person.id, success: false, error: BulkIdErrorReason.UNKNOWN }); - } - } - return results; - } - - async deleteAll(auth: AuthDto, { ids }: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.PersonDelete, ids }); - const people = await this.personRepository.getForPeopleDelete(ids); - await this.removeAllPeople(people); - } - - @Chunked() - private async removeAllPeople(people: { id: string; thumbnailPath: string }[]) { - await Promise.all(people.map((person) => this.storageRepository.unlink(person.thumbnailPath))); - await this.personRepository.delete(people.map((person) => person.id)); - this.logger.debug(`Deleted ${people.length} people`); - } - - @OnJob({ name: JobName.PersonCleanup, queue: QueueName.BackgroundTask }) - async handlePersonCleanup(): Promise { - const people = await this.personRepository.getAllWithoutFaces(); - await this.removeAllPeople(people); - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetDetectFacesQueueAll, queue: QueueName.FaceDetection }) - async handleQueueDetectFaces({ force }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: false }); - if (!isFacialRecognitionEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - if (force) { - await this.personRepository.deleteFaces({ sourceType: SourceType.MachineLearning }); - await this.handlePersonCleanup(); - await this.personRepository.vacuum({ reindexVectors: true }); - } - - let jobs: JobItem[] = []; - const assets = this.assetJobRepository.streamForDetectFacesJob(force); - for await (const asset of assets) { - jobs.push({ name: JobName.AssetDetectFaces, data: { id: asset.id } }); - - if (jobs.length >= JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(jobs); - jobs = []; - } - } - - await this.jobRepository.queueAll(jobs); - - if (force === undefined) { - await this.jobRepository.queue({ name: JobName.PersonCleanup }); - } - - return JobStatus.Success; - } - - @OnJob({ name: JobName.AssetDetectFaces, queue: QueueName.FaceDetection }) - async handleDetectFaces({ id }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: true }); - if (!isFacialRecognitionEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - const asset = await this.assetJobRepository.getForDetectFacesJob(id); - const previewFile = asset?.files[0]; - if (!asset || asset.files.length !== 1 || !previewFile) { - return JobStatus.Failed; - } - - if (asset.visibility === AssetVisibility.Hidden) { - return JobStatus.Skipped; - } - - const { imageHeight, imageWidth, faces } = await this.machineLearningRepository.detectFaces( - previewFile.path, - machineLearning.facialRecognition, - ); - this.logger.debug(`${faces.length} faces detected in ${previewFile.path}`); - - const facesToAdd: (Insertable & { id: string })[] = []; - const embeddings: FaceSearchTable[] = []; - const mlFaceIds = new Set(); - - for (const face of asset.faces) { - if (face.sourceType === SourceType.MachineLearning) { - mlFaceIds.add(face.id); - } - } - - const heightScale = imageHeight / (asset.faces[0]?.imageHeight || 1); - const widthScale = imageWidth / (asset.faces[0]?.imageWidth || 1); - for (const { boundingBox, embedding } of faces) { - const scaledBox = { - x1: boundingBox.x1 * widthScale, - y1: boundingBox.y1 * heightScale, - x2: boundingBox.x2 * widthScale, - y2: boundingBox.y2 * heightScale, - }; - const match = asset.faces.find((face) => this.iou(face, scaledBox) > 0.5); - - if (match && !mlFaceIds.delete(match.id)) { - embeddings.push({ faceId: match.id, embedding }); - } else if (!match) { - const faceId = this.cryptoRepository.randomUUID(); - facesToAdd.push({ - id: faceId, - assetId: asset.id, - imageHeight, - imageWidth, - boundingBoxX1: boundingBox.x1, - boundingBoxY1: boundingBox.y1, - boundingBoxX2: boundingBox.x2, - boundingBoxY2: boundingBox.y2, - }); - embeddings.push({ faceId, embedding }); - } - } - const faceIdsToRemove = [...mlFaceIds]; - - if (facesToAdd.length > 0 || faceIdsToRemove.length > 0 || embeddings.length > 0) { - await this.personRepository.refreshFaces(facesToAdd, faceIdsToRemove, embeddings); - } - - if (faceIdsToRemove.length > 0) { - this.logger.log(`Removed ${faceIdsToRemove.length} faces below detection threshold in asset ${id}`); - } - - if (facesToAdd.length > 0) { - this.logger.log(`Detected ${facesToAdd.length} new faces in asset ${id}`); - const jobs = facesToAdd.map((face) => ({ name: JobName.FacialRecognition, data: { id: face.id } }) as const); - await this.jobRepository.queueAll([{ name: JobName.FacialRecognitionQueueAll, data: { force: false } }, ...jobs]); - } else if (embeddings.length > 0) { - this.logger.log(`Added ${embeddings.length} face embeddings for asset ${id}`); - } - - await this.assetRepository.upsertJobStatus({ assetId: asset.id, facesRecognizedAt: new Date() }); - - return JobStatus.Success; - } - - private iou( - face: { boundingBoxX1: number; boundingBoxY1: number; boundingBoxX2: number; boundingBoxY2: number }, - newBox: BoundingBox, - ): number { - const x1 = Math.max(face.boundingBoxX1, newBox.x1); - const y1 = Math.max(face.boundingBoxY1, newBox.y1); - const x2 = Math.min(face.boundingBoxX2, newBox.x2); - const y2 = Math.min(face.boundingBoxY2, newBox.y2); - - const intersection = Math.max(0, x2 - x1) * Math.max(0, y2 - y1); - const area1 = (face.boundingBoxX2 - face.boundingBoxX1) * (face.boundingBoxY2 - face.boundingBoxY1); - const area2 = (newBox.x2 - newBox.x1) * (newBox.y2 - newBox.y1); - const union = area1 + area2 - intersection; - - return intersection / union; - } - - @OnJob({ name: JobName.FacialRecognitionQueueAll, queue: QueueName.FacialRecognition }) - async handleQueueRecognizeFaces({ force, nightly }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: false }); - if (!isFacialRecognitionEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - await this.jobRepository.waitForQueueCompletion(QueueName.ThumbnailGeneration, QueueName.FaceDetection); - - if (nightly) { - const [state, latestFaceDate] = await Promise.all([ - this.systemMetadataRepository.get(SystemMetadataKey.FacialRecognitionState), - this.personRepository.getLatestFaceDate(), - ]); - - if (state?.lastRun && latestFaceDate && state.lastRun > latestFaceDate) { - this.logger.debug('Skipping facial recognition nightly since no face has been added since the last run'); - return JobStatus.Skipped; - } - } - - const { waiting } = await this.jobRepository.getJobCounts(QueueName.FacialRecognition); - - if (force) { - await this.personRepository.unassignFaces({ sourceType: SourceType.MachineLearning }); - await this.handlePersonCleanup(); - await this.personRepository.vacuum({ reindexVectors: false }); - } else if (waiting) { - this.logger.debug( - `Skipping facial recognition queueing because ${waiting} job${waiting > 1 ? 's are' : ' is'} already queued`, - ); - return JobStatus.Skipped; - } - - await this.databaseRepository.prewarm(VectorIndex.Face); - - const lastRun = new Date().toISOString(); - const facePagination = this.personRepository.getAllFaces( - force ? undefined : { personId: null, sourceType: SourceType.MachineLearning }, - ); - - let jobs: { name: JobName.FacialRecognition; data: { id: string; deferred: false } }[] = []; - for await (const face of facePagination) { - jobs.push({ name: JobName.FacialRecognition, data: { id: face.id, deferred: false } }); - - if (jobs.length === JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(jobs); - jobs = []; - } - } - - await this.jobRepository.queueAll(jobs); - - await this.systemMetadataRepository.set(SystemMetadataKey.FacialRecognitionState, { lastRun }); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.FacialRecognition, queue: QueueName.FacialRecognition }) - async handleRecognizeFaces({ id, deferred }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: true }); - if (!isFacialRecognitionEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - const face = await this.personRepository.getFaceForFacialRecognitionJob(id); - if (!face || !face.asset) { - this.logger.warn(`Face ${id} not found`); - return JobStatus.Failed; - } - - if (face.sourceType !== SourceType.MachineLearning) { - this.logger.warn(`Skipping face ${id} due to source ${face.sourceType}`); - return JobStatus.Skipped; - } - - if (!face.faceSearch?.embedding) { - this.logger.warn(`Face ${id} does not have an embedding`); - return JobStatus.Failed; - } - - if (face.personId) { - this.logger.debug(`Face ${id} already has a person assigned`); - return JobStatus.Skipped; - } - - const matches = await this.searchRepository.searchFaces({ - userIds: [face.asset.ownerId], - embedding: face.faceSearch.embedding, - maxDistance: machineLearning.facialRecognition.maxDistance, - numResults: machineLearning.facialRecognition.minFaces, - minBirthDate: face.asset.fileCreatedAt ?? undefined, - }); - - // `matches` also includes the face itself - if (machineLearning.facialRecognition.minFaces > 1 && matches.length <= 1) { - this.logger.debug(`Face ${id} only matched the face itself, skipping`); - return JobStatus.Skipped; - } - - this.logger.debug(`Face ${id} has ${matches.length} matches`); - - const isCore = - matches.length >= machineLearning.facialRecognition.minFaces && - face.asset.visibility === AssetVisibility.Timeline; - if (!isCore && !deferred) { - this.logger.debug(`Deferring non-core face ${id} for later processing`); - await this.jobRepository.queue({ name: JobName.FacialRecognition, data: { id, deferred: true } }); - return JobStatus.Skipped; - } - - let personId = matches.find((match) => match.personId)?.personId; - if (!personId) { - const matchWithPerson = await this.searchRepository.searchFaces({ - userIds: [face.asset.ownerId], - embedding: face.faceSearch.embedding, - maxDistance: machineLearning.facialRecognition.maxDistance, - numResults: 1, - hasPerson: true, - minBirthDate: face.asset.fileCreatedAt ?? undefined, - }); - - if (matchWithPerson.length > 0) { - personId = matchWithPerson[0].personId; - } - } - - if (isCore && !personId) { - this.logger.log(`Creating new person for face ${id}`); - const newPerson = await this.personRepository.create({ ownerId: face.asset.ownerId, faceAssetId: face.id }); - await this.jobRepository.queue({ name: JobName.PersonGenerateThumbnail, data: { id: newPerson.id } }); - personId = newPerson.id; - } - - if (personId) { - this.logger.debug(`Assigning face ${id} to person ${personId}`); - await this.personRepository.reassignFaces({ faceIds: [id], newPersonId: personId }); - } - - return JobStatus.Success; - } - - @OnJob({ name: JobName.PersonFileMigration, queue: QueueName.Migration }) - async handlePersonMigration({ id }: JobOf): Promise { - const person = await this.personRepository.getById(id); - if (!person) { - return JobStatus.Failed; - } - - await this.storageCore.movePersonFile(person, PersonPathType.Face); - - return JobStatus.Success; - } - - async mergePerson(auth: AuthDto, id: string, dto: MergePersonDto): Promise { - const mergeIds = dto.ids; - if (mergeIds.includes(id)) { - throw new BadRequestException('Cannot merge a person into themselves'); - } - - await this.requireAccess({ auth, permission: Permission.PersonUpdate, ids: [id] }); - let primaryPerson = await this.findOrFail(id); - const primaryName = primaryPerson.name || primaryPerson.id; - - const results: BulkIdResponseDto[] = []; - - const allowedIds = await this.checkAccess({ - auth, - permission: Permission.PersonMerge, - ids: mergeIds, - }); - - for (const mergeId of mergeIds) { - const hasAccess = allowedIds.has(mergeId); - if (!hasAccess) { - results.push({ id: mergeId, success: false, error: BulkIdErrorReason.NO_PERMISSION }); - continue; - } - - try { - const mergePerson = await this.personRepository.getById(mergeId); - if (!mergePerson) { - results.push({ id: mergeId, success: false, error: BulkIdErrorReason.NOT_FOUND }); - continue; - } - - const update: Updateable & { id: string } = { id: primaryPerson.id }; - if (!primaryPerson.name && mergePerson.name) { - update.name = mergePerson.name; - } - - if (!primaryPerson.birthDate && mergePerson.birthDate) { - update.birthDate = mergePerson.birthDate; - } - - if (Object.keys(update).length > 0) { - primaryPerson = await this.personRepository.update(update); - } - - const mergeName = mergePerson.name || mergePerson.id; - const mergeData: UpdateFacesData = { oldPersonId: mergeId, newPersonId: id }; - this.logger.log(`Merging ${mergeName} into ${primaryName}`); - - await this.personRepository.reassignFaces(mergeData); - await this.removeAllPeople([mergePerson]); - - this.logger.log(`Merged ${mergeName} into ${primaryName}`); - results.push({ id: mergeId, success: true }); - } catch (error: Error | any) { - this.logger.error(`Unable to merge ${mergeId} into ${id}: ${error}`, error?.stack); - results.push({ id: mergeId, success: false, error: BulkIdErrorReason.UNKNOWN }); - } - } - return results; - } - - private async findOrFail(id: string) { - const person = await this.personRepository.getById(id); - if (!person) { - throw new BadRequestException('Person not found'); - } - return person; - } - - // TODO return a asset face response - async createFace(auth: AuthDto, dto: AssetFaceCreateDto): Promise { - await Promise.all([ - this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.assetId] }), - this.requireAccess({ auth, permission: Permission.PersonRead, ids: [dto.personId] }), - ]); - - const asset = await this.assetRepository.getById(dto.assetId, { edits: true, exifInfo: true }); - if (!asset) { - throw new NotFoundException('Asset not found'); - } - - const edits = asset.edits || []; - - let topLeft: Point = { x: dto.x, y: dto.y }; - let bottomRight: Point = { x: dto.x + dto.width, y: dto.y + dto.height }; - - // the coordinates received from the client are based on the edited preview image - // we need to convert them to the coordinate space of the original unedited image - if (edits.length > 0) { - if (!asset.width || !asset.height || !asset.exifInfo?.exifImageWidth || !asset.exifInfo?.exifImageHeight) { - throw new BadRequestException('Asset does not have valid dimensions'); - } - - // convert from preview to full dimensions - const scaleFactor = asset.width / dto.imageWidth; - topLeft = { x: topLeft.x * scaleFactor, y: topLeft.y * scaleFactor }; - bottomRight = { x: bottomRight.x * scaleFactor, y: bottomRight.y * scaleFactor }; - - const { - points: [invertedTopLeft, invertedBottomRight], - } = transformPoints( - [topLeft, bottomRight], - edits, - { width: asset.width, height: asset.height }, - { inverse: true }, - ); - - // make sure topLeft is top-left and bottomRight is bottom-right - topLeft = { - x: Math.min(invertedTopLeft.x, invertedBottomRight.x), - y: Math.min(invertedTopLeft.y, invertedBottomRight.y), - }; - bottomRight = { - x: Math.max(invertedTopLeft.x, invertedBottomRight.x), - y: Math.max(invertedTopLeft.y, invertedBottomRight.y), - }; - - // now coordinates are in original image space - dto.imageHeight = asset.exifInfo.exifImageHeight; - dto.imageWidth = asset.exifInfo.exifImageWidth; - } - - await this.personRepository.createAssetFace({ - personId: dto.personId, - assetId: dto.assetId, - imageHeight: dto.imageHeight, - imageWidth: dto.imageWidth, - boundingBoxX1: Math.round(topLeft.x), - boundingBoxX2: Math.round(bottomRight.x), - boundingBoxY1: Math.round(topLeft.y), - boundingBoxY2: Math.round(bottomRight.y), - sourceType: SourceType.Manual, - }); - } - - async deleteFace(auth: AuthDto, id: string, dto: AssetFaceDeleteDto): Promise { - await this.requireAccess({ auth, permission: Permission.FaceDelete, ids: [id] }); - - return dto.force ? this.personRepository.deleteAssetFace(id) : this.personRepository.softDeleteAssetFaces(id); - } -} diff --git a/server/src/services/plugin-host.functions.ts b/server/src/services/plugin-host.functions.ts deleted file mode 100644 index 50b1052b54..0000000000 --- a/server/src/services/plugin-host.functions.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { CurrentPlugin } from '@extism/extism'; -import { UnauthorizedException } from '@nestjs/common'; -import { Updateable } from 'kysely'; -import { Permission } from 'src/enum'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { AlbumRepository } from 'src/repositories/album.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { CryptoRepository } from 'src/repositories/crypto.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { AssetTable } from 'src/schema/tables/asset.table'; -import { requireAccess } from 'src/utils/access'; - -/** - * Plugin host functions that are exposed to WASM plugins via Extism. - * These functions allow plugins to interact with the Immich system. - */ -export class PluginHostFunctions { - constructor( - private assetRepository: AssetRepository, - private albumRepository: AlbumRepository, - private accessRepository: AccessRepository, - private cryptoRepository: CryptoRepository, - private logger: LoggingRepository, - private pluginJwtSecret: string, - ) {} - - /** - * Creates Extism host function bindings for the plugin. - * These are the functions that WASM plugins can call. - */ - getHostFunctions() { - return { - 'extism:host/user': { - updateAsset: (cp: CurrentPlugin, offs: bigint) => this.handleUpdateAsset(cp, offs), - addAssetToAlbum: (cp: CurrentPlugin, offs: bigint) => this.handleAddAssetToAlbum(cp, offs), - }, - }; - } - - /** - * Host function wrapper for updateAsset. - * Reads the input from the plugin, parses it, and calls the actual update function. - */ - private async handleUpdateAsset(cp: CurrentPlugin, offs: bigint) { - const input = JSON.parse(cp.read(offs)!.text()); - await this.updateAsset(input); - } - - /** - * Host function wrapper for addAssetToAlbum. - * Reads the input from the plugin, parses it, and calls the actual add function. - */ - private async handleAddAssetToAlbum(cp: CurrentPlugin, offs: bigint) { - const input = JSON.parse(cp.read(offs)!.text()); - await this.addAssetToAlbum(input); - } - - /** - * Validates the JWT token and returns the auth context. - */ - private validateToken(authToken: string): { userId: string } { - try { - const auth = this.cryptoRepository.verifyJwt<{ userId: string }>(authToken, this.pluginJwtSecret); - if (!auth.userId) { - throw new UnauthorizedException('Invalid token: missing userId'); - } - return auth; - } catch (error) { - this.logger.error('Token validation failed:', error); - throw new UnauthorizedException('Invalid token'); - } - } - - /** - * Updates an asset with the given properties. - */ - async updateAsset(input: { authToken: string } & Updateable & { id: string }) { - const { authToken, id, ...assetData } = input; - - // Validate token - const auth = this.validateToken(authToken); - - // Check access to the asset - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AssetUpdate, - ids: [id], - }); - - this.logger.log(`Updating asset ${id} -- ${JSON.stringify(assetData)}`); - await this.assetRepository.update({ id, ...assetData }); - } - - /** - * Adds an asset to an album. - */ - async addAssetToAlbum(input: { authToken: string; assetId: string; albumId: string }) { - const { authToken, assetId, albumId } = input; - - // Validate token - const auth = this.validateToken(authToken); - - // Check access to both the asset and the album - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AssetRead, - ids: [assetId], - }); - - await requireAccess(this.accessRepository, { - auth: { user: { id: auth.userId } } as any, - permission: Permission.AlbumUpdate, - ids: [albumId], - }); - - this.logger.log(`Adding asset ${assetId} to album ${albumId}`); - await this.albumRepository.addAssetIds(albumId, [assetId]); - return 0; - } -} diff --git a/server/src/services/plugin.service.ts b/server/src/services/plugin.service.ts deleted file mode 100644 index d78b8940d3..0000000000 --- a/server/src/services/plugin.service.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { Plugin as ExtismPlugin, newPlugin } from '@extism/extism'; -import { BadRequestException, Injectable } from '@nestjs/common'; -import { plainToInstance } from 'class-transformer'; -import { validateOrReject } from 'class-validator'; -import { join } from 'node:path'; -import { Asset, WorkflowAction, WorkflowFilter } from 'src/database'; -import { OnEvent, OnJob } from 'src/decorators'; -import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; -import { mapPlugin, PluginResponseDto, PluginTriggerResponseDto } from 'src/dtos/plugin.dto'; -import { JobName, JobStatus, PluginTriggerType, QueueName } from 'src/enum'; -import { pluginTriggers } from 'src/plugins'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { PluginHostFunctions } from 'src/services/plugin-host.functions'; -import { IWorkflowJob, JobItem, JobOf, WorkflowData } from 'src/types'; - -interface WorkflowContext { - authToken: string; - asset: Asset; -} - -interface PluginInput { - authToken: string; - config: T; - data: { - asset: Asset; - }; -} - -@Injectable() -export class PluginService extends BaseService { - private pluginJwtSecret!: string; - private loadedPlugins: Map = new Map(); - private hostFunctions!: PluginHostFunctions; - - @OnEvent({ name: 'AppBootstrap' }) - async onBootstrap() { - this.pluginJwtSecret = this.cryptoRepository.randomBytesAsText(32); - - await this.loadPluginsFromManifests(); - - this.hostFunctions = new PluginHostFunctions( - this.assetRepository, - this.albumRepository, - this.accessRepository, - this.cryptoRepository, - this.logger, - this.pluginJwtSecret, - ); - - await this.loadPlugins(); - } - - getTriggers(): PluginTriggerResponseDto[] { - return pluginTriggers; - } - - // - // CRUD operations for plugins - // - async getAll(): Promise { - const plugins = await this.pluginRepository.getAllPlugins(); - return plugins.map((plugin) => mapPlugin(plugin)); - } - - async get(id: string): Promise { - const plugin = await this.pluginRepository.getPlugin(id); - if (!plugin) { - throw new BadRequestException('Plugin not found'); - } - return mapPlugin(plugin); - } - - /////////////////////////////////////////// - // Plugin Loader - ////////////////////////////////////////// - async loadPluginsFromManifests(): Promise { - // Load core plugin - const { resourcePaths, plugins } = this.configRepository.getEnv(); - const coreManifestPath = `${resourcePaths.corePlugin}/manifest.json`; - - const coreManifest = await this.readAndValidateManifest(coreManifestPath); - await this.loadPluginToDatabase(coreManifest, resourcePaths.corePlugin); - - this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`); - - // Load external plugins - if (plugins.external.allow && plugins.external.installFolder) { - await this.loadExternalPlugins(plugins.external.installFolder); - } - } - - private async loadExternalPlugins(installFolder: string): Promise { - try { - const entries = await this.pluginRepository.readDirectory(installFolder); - - for (const entry of entries) { - if (!entry.isDirectory()) { - continue; - } - - const pluginFolder = join(installFolder, entry.name); - const manifestPath = join(pluginFolder, 'manifest.json'); - try { - const manifest = await this.readAndValidateManifest(manifestPath); - await this.loadPluginToDatabase(manifest, pluginFolder); - - this.logger.log(`Successfully processed external plugin: ${manifest.name} (version ${manifest.version})`); - } catch (error) { - this.logger.warn(`Failed to load external plugin from ${manifestPath}:`, error); - } - } - } catch (error) { - this.logger.error(`Failed to scan external plugins folder ${installFolder}:`, error); - } - } - - private async loadPluginToDatabase(manifest: PluginManifestDto, basePath: string): Promise { - const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name); - if (currentPlugin != null && currentPlugin.version === manifest.version) { - this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`); - return; - } - - const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath); - - this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`); - - for (const filter of filters) { - this.logger.log(`Upserted plugin filter: ${filter.methodName} (ID: ${filter.id})`); - } - - for (const action of actions) { - this.logger.log(`Upserted plugin action: ${action.methodName} (ID: ${action.id})`); - } - } - - private async readAndValidateManifest(manifestPath: string): Promise { - const content = await this.storageRepository.readTextFile(manifestPath); - const manifestData = JSON.parse(content); - const manifest = plainToInstance(PluginManifestDto, manifestData); - - await validateOrReject(manifest, { - whitelist: true, - forbidNonWhitelisted: true, - }); - - return manifest; - } - - /////////////////////////////////////////// - // Plugin Execution - /////////////////////////////////////////// - private async loadPlugins() { - const plugins = await this.pluginRepository.getAllPlugins(); - for (const plugin of plugins) { - try { - this.logger.debug(`Loading plugin: ${plugin.name} from ${plugin.wasmPath}`); - - const extismPlugin = await newPlugin(plugin.wasmPath, { - useWasi: true, - functions: this.hostFunctions.getHostFunctions(), - }); - - this.loadedPlugins.set(plugin.id, extismPlugin); - this.logger.log(`Successfully loaded plugin: ${plugin.name}`); - } catch (error) { - this.logger.error(`Failed to load plugin ${plugin.name}:`, error); - } - } - } - - @OnEvent({ name: 'AssetCreate' }) - async handleAssetCreate({ asset }: ArgOf<'AssetCreate'>) { - await this.handleTrigger(PluginTriggerType.AssetCreate, { - ownerId: asset.ownerId, - event: { userId: asset.ownerId, asset }, - }); - } - - private async handleTrigger( - triggerType: T, - params: { ownerId: string; event: WorkflowData[T] }, - ): Promise { - const workflows = await this.workflowRepository.getWorkflowByOwnerAndTrigger(params.ownerId, triggerType); - if (workflows.length === 0) { - return; - } - - const jobs: JobItem[] = workflows.map((workflow) => ({ - name: JobName.WorkflowRun, - data: { - id: workflow.id, - type: triggerType, - event: params.event, - } as IWorkflowJob, - })); - - await this.jobRepository.queueAll(jobs); - this.logger.debug(`Queued ${jobs.length} workflow execution jobs for trigger ${triggerType}`); - } - - @OnJob({ name: JobName.WorkflowRun, queue: QueueName.Workflow }) - async handleWorkflowRun({ id: workflowId, type, event }: JobOf): Promise { - try { - const workflow = await this.workflowRepository.getWorkflow(workflowId); - if (!workflow) { - this.logger.error(`Workflow ${workflowId} not found`); - return JobStatus.Failed; - } - - const workflowFilters = await this.workflowRepository.getFilters(workflowId); - const workflowActions = await this.workflowRepository.getActions(workflowId); - - switch (type) { - case PluginTriggerType.AssetCreate: { - const data = event as WorkflowData[PluginTriggerType.AssetCreate]; - const asset = data.asset; - - const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret); - - const context = { - authToken, - asset, - }; - - const filtersPassed = await this.executeFilters(workflowFilters, context); - if (!filtersPassed) { - return JobStatus.Skipped; - } - - await this.executeActions(workflowActions, context); - this.logger.debug(`Workflow ${workflowId} executed successfully`); - return JobStatus.Success; - } - - case PluginTriggerType.PersonRecognized: { - this.logger.error('unimplemented'); - return JobStatus.Skipped; - } - - default: { - this.logger.error(`Unknown workflow trigger type: ${type}`); - return JobStatus.Failed; - } - } - } catch (error) { - this.logger.error(`Error executing workflow ${workflowId}:`, error); - return JobStatus.Failed; - } - } - - private async executeFilters(workflowFilters: WorkflowFilter[], context: WorkflowContext): Promise { - for (const workflowFilter of workflowFilters) { - const filter = await this.pluginRepository.getFilter(workflowFilter.pluginFilterId); - if (!filter) { - this.logger.error(`Filter ${workflowFilter.pluginFilterId} not found`); - return false; - } - - const pluginInstance = this.loadedPlugins.get(filter.pluginId); - if (!pluginInstance) { - this.logger.error(`Plugin ${filter.pluginId} not loaded`); - return false; - } - - const filterInput: PluginInput = { - authToken: context.authToken, - config: workflowFilter.filterConfig, - data: { - asset: context.asset, - }, - }; - - this.logger.debug(`Calling filter ${filter.methodName} with input: ${JSON.stringify(filterInput)}`); - - const filterResult = await pluginInstance.call( - filter.methodName, - new TextEncoder().encode(JSON.stringify(filterInput)), - ); - - if (!filterResult) { - this.logger.error(`Filter ${filter.methodName} returned null`); - return false; - } - - const result = JSON.parse(filterResult.text()); - if (result.passed === false) { - this.logger.debug(`Filter ${filter.methodName} returned false, stopping workflow execution`); - return false; - } - } - - return true; - } - - private async executeActions(workflowActions: WorkflowAction[], context: WorkflowContext): Promise { - for (const workflowAction of workflowActions) { - const action = await this.pluginRepository.getAction(workflowAction.pluginActionId); - if (!action) { - throw new Error(`Action ${workflowAction.pluginActionId} not found`); - } - - const pluginInstance = this.loadedPlugins.get(action.pluginId); - if (!pluginInstance) { - throw new Error(`Plugin ${action.pluginId} not loaded`); - } - - const actionInput: PluginInput = { - authToken: context.authToken, - config: workflowAction.actionConfig, - data: { - asset: context.asset, - }, - }; - - this.logger.debug(`Calling action ${action.methodName} with input: ${JSON.stringify(actionInput)}`); - - await pluginInstance.call(action.methodName, JSON.stringify(actionInput)); - } - } -} diff --git a/server/src/services/queue.service.spec.ts b/server/src/services/queue.service.spec.ts deleted file mode 100644 index 2c76fee877..0000000000 --- a/server/src/services/queue.service.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { defaults, SystemConfig } from 'src/config'; -import { ImmichWorker, JobName, QueueCommand, QueueName } from 'src/enum'; -import { QueueService } from 'src/services/queue.service'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(QueueService.name, () => { - let sut: QueueService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(QueueService)); - - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onConfigUpdate', () => { - it('should update concurrency', () => { - sut.onConfigUpdate({ newConfig: defaults, oldConfig: {} as SystemConfig }); - - expect(mocks.job.setConcurrency).toHaveBeenCalledTimes(18); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(5, QueueName.FacialRecognition, 1); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(7, QueueName.DuplicateDetection, 1); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(8, QueueName.BackgroundTask, 5); - expect(mocks.job.setConcurrency).toHaveBeenNthCalledWith(9, QueueName.StorageTemplateMigration, 1); - }); - }); - - describe('handleNightlyJobs', () => { - it('should run the scheduled jobs', async () => { - await sut.handleNightlyJobs(); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.AssetDeleteCheck }, - { name: JobName.UserDeleteCheck }, - { name: JobName.PersonCleanup }, - { name: JobName.MemoryCleanup }, - { name: JobName.SessionCleanup }, - { name: JobName.AuditTableCleanup }, - { name: JobName.AuditLogCleanup }, - { name: JobName.MemoryGenerate }, - { name: JobName.UserSyncUsage }, - { name: JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } }, - { name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }, - ]); - }); - }); - - describe('getAllJobStatus', () => { - it('should get all job statuses', async () => { - const stats = factory.queueStatistics({ active: 1 }); - const expected = { jobCounts: stats, queueStatus: { isActive: true, isPaused: true } }; - - mocks.job.getJobCounts.mockResolvedValue(stats); - mocks.job.isPaused.mockResolvedValue(true); - - await expect(sut.getAllLegacy(factory.auth())).resolves.toEqual({ - [QueueName.BackgroundTask]: expected, - [QueueName.DuplicateDetection]: expected, - [QueueName.SmartSearch]: expected, - [QueueName.MetadataExtraction]: expected, - [QueueName.Search]: expected, - [QueueName.StorageTemplateMigration]: expected, - [QueueName.Migration]: expected, - [QueueName.ThumbnailGeneration]: expected, - [QueueName.VideoConversion]: expected, - [QueueName.FaceDetection]: expected, - [QueueName.FacialRecognition]: expected, - [QueueName.Sidecar]: expected, - [QueueName.Library]: expected, - [QueueName.Notification]: expected, - [QueueName.BackupDatabase]: expected, - [QueueName.Ocr]: expected, - [QueueName.Workflow]: expected, - [QueueName.Editor]: expected, - }); - }); - }); - - describe('handleCommand', () => { - it('should handle a pause command', async () => { - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Pause, force: false }); - - expect(mocks.job.pause).toHaveBeenCalledWith(QueueName.MetadataExtraction); - }); - - it('should handle a resume command', async () => { - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Resume, force: false }); - - expect(mocks.job.resume).toHaveBeenCalledWith(QueueName.MetadataExtraction); - }); - - it('should handle an empty command', async () => { - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Empty, force: false }); - - expect(mocks.job.empty).toHaveBeenCalledWith(QueueName.MetadataExtraction); - }); - - it('should not start a job that is already running', async () => { - mocks.job.isActive.mockResolvedValue(true); - - await expect( - sut.runCommandLegacy(QueueName.VideoConversion, { command: QueueCommand.Start, force: false }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should handle a start video conversion command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.VideoConversion, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetEncodeVideoQueueAll, data: { force: false } }); - }); - - it('should handle a start storage template migration command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.StorageTemplateMigration, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.StorageTemplateMigration }); - }); - - it('should handle a start smart search command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.SmartSearch, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SmartSearchQueueAll, data: { force: false } }); - }); - - it('should handle a start metadata extraction command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.MetadataExtraction, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.AssetExtractMetadataQueueAll, - data: { force: false }, - }); - }); - - it('should handle a start sidecar command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.Sidecar, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.SidecarQueueAll, data: { force: false } }); - }); - - it('should handle a start thumbnail generation command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.ThumbnailGeneration, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.AssetGenerateThumbnailsQueueAll, - data: { force: false }, - }); - }); - - it('should handle a start face detection command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.FaceDetection, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetDetectFacesQueueAll, data: { force: false } }); - }); - - it('should handle a start facial recognition command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.FacialRecognition, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.FacialRecognitionQueueAll, data: { force: false } }); - }); - - it('should handle a start backup database command', async () => { - mocks.job.isActive.mockResolvedValue(false); - mocks.job.getJobCounts.mockResolvedValue(factory.queueStatistics()); - - await sut.runCommandLegacy(QueueName.BackupDatabase, { command: QueueCommand.Start, force: false }); - - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.DatabaseBackup, data: { force: false } }); - }); - - it('should throw a bad request when an invalid queue is used', async () => { - mocks.job.isActive.mockResolvedValue(false); - - await expect( - sut.runCommandLegacy(QueueName.BackgroundTask, { command: QueueCommand.Start, force: false }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/services/queue.service.ts b/server/src/services/queue.service.ts index cdfa2ad2ed..df47ee3fb0 100644 --- a/server/src/services/queue.service.ts +++ b/server/src/services/queue.service.ts @@ -1,296 +1,16 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { ClassConstructor } from 'class-transformer'; -import { SystemConfig } from 'src/config'; +import { Injectable } from '@nestjs/common'; import { OnEvent } from 'src/decorators'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - mapQueueLegacy, - mapQueuesLegacy, - QueueResponseLegacyDto, - QueuesResponseLegacyDto, -} from 'src/dtos/queue-legacy.dto'; -import { - QueueCommandDto, - QueueDeleteDto, - QueueJobResponseDto, - QueueJobSearchDto, - QueueResponseDto, - QueueUpdateDto, -} from 'src/dtos/queue.dto'; -import { - BootstrapEventPriority, - CronJob, - DatabaseLock, - ImmichWorker, - JobName, - QueueCleanType, - QueueCommand, - QueueName, -} from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; +import { BootstrapEventPriority, ImmichWorker, QueueName } from 'src/enum'; import { BaseService } from 'src/services/base.service'; -import { ConcurrentQueueName, JobItem } from 'src/types'; -import { handlePromiseError } from 'src/utils/misc'; - -const asNightlyTasksCron = (config: SystemConfig) => { - const [hours, minutes] = config.nightlyTasks.startTime.split(':').map(Number); - return `${minutes} ${hours} * * *`; -}; @Injectable() export class QueueService extends BaseService { - private services: ClassConstructor[] = []; - private nightlyJobsLock = false; - - @OnEvent({ name: 'ConfigInit' }) - async onConfigInit({ newConfig: config }: ArgOf<'ConfigInit'>) { - if (this.worker === ImmichWorker.Microservices) { - this.updateConcurrency(config); - return; - } - - this.nightlyJobsLock = await this.databaseRepository.tryLock(DatabaseLock.NightlyJobs); - if (this.nightlyJobsLock) { - const cronExpression = asNightlyTasksCron(config); - this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); - this.cronRepository.create({ - name: CronJob.NightlyJobs, - expression: cronExpression, - start: true, - onTick: () => handlePromiseError(this.handleNightlyJobs(), this.logger), - }); - } - } - - @OnEvent({ name: 'ConfigUpdate', server: true }) - onConfigUpdate({ newConfig: config }: ArgOf<'ConfigUpdate'>) { - if (this.worker === ImmichWorker.Microservices) { - this.updateConcurrency(config); - return; - } - - if (this.nightlyJobsLock) { - const cronExpression = asNightlyTasksCron(config); - this.logger.debug(`Scheduling nightly jobs for ${cronExpression}`); - this.cronRepository.update({ name: CronJob.NightlyJobs, expression: cronExpression, start: true }); - } - } - - @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.JobService }) - onBootstrap() { - this.jobRepository.setup(this.services); - if (this.worker === ImmichWorker.Microservices) { - this.jobRepository.startWorkers(); - } - } - - private updateConcurrency(config: SystemConfig) { - this.logger.debug(`Updating queue concurrency settings`); - for (const queueName of Object.values(QueueName)) { - let concurrency = 1; - if (this.isConcurrentQueue(queueName)) { - concurrency = config.job[queueName].concurrency; - } - this.logger.debug(`Setting ${queueName} concurrency to ${concurrency}`); - this.jobRepository.setConcurrency(queueName, concurrency); - } - } - - setServices(services: ClassConstructor[]) { - this.services = services; - } - - async runCommandLegacy(name: QueueName, dto: QueueCommandDto): Promise { - this.logger.debug(`Handling command: queue=${name},command=${dto.command},force=${dto.force}`); - - switch (dto.command) { - case QueueCommand.Start: { - await this.start(name, dto); - break; - } - - case QueueCommand.Pause: { - await this.jobRepository.pause(name); - break; - } - - case QueueCommand.Resume: { - await this.jobRepository.resume(name); - break; - } - - case QueueCommand.Empty: { - await this.jobRepository.empty(name); - break; - } - - case QueueCommand.ClearFailed: { - const failedJobs = await this.jobRepository.clear(name, QueueCleanType.Failed); - this.logger.debug(`Cleared failed jobs: ${failedJobs}`); - break; - } - } - - const response = await this.getByName(name); - - return mapQueueLegacy(response); - } - - async getAll(_auth: AuthDto): Promise { - return Promise.all(Object.values(QueueName).map((name) => this.getByName(name))); - } - - async getAllLegacy(auth: AuthDto): Promise { - const responses = await this.getAll(auth); - return mapQueuesLegacy(responses); - } - - get(auth: AuthDto, name: QueueName): Promise { - return this.getByName(name); - } - - async update(auth: AuthDto, name: QueueName, dto: QueueUpdateDto): Promise { - if (dto.isPaused === true) { - if (name === QueueName.BackgroundTask) { - throw new BadRequestException(`The BackgroundTask queue cannot be paused`); - } - await this.jobRepository.pause(name); - } - - if (dto.isPaused === false) { - await this.jobRepository.resume(name); - } - - return this.getByName(name); - } - - searchJobs(auth: AuthDto, name: QueueName, dto: QueueJobSearchDto): Promise { - return this.jobRepository.searchJobs(name, dto); - } - - async emptyQueue(auth: AuthDto, name: QueueName, dto: QueueDeleteDto) { - await this.jobRepository.empty(name); - if (dto.failed) { - await this.jobRepository.clear(name, QueueCleanType.Failed); - } - } - - private async getByName(name: QueueName): Promise { - const [statistics, isPaused] = await Promise.all([ - this.jobRepository.getJobCounts(name), - this.jobRepository.isPaused(name), - ]); - return { name, isPaused, statistics }; - } - - private async start(name: QueueName, { force }: QueueCommandDto): Promise { - const isActive = await this.jobRepository.isActive(name); - if (isActive) { - throw new BadRequestException(`Job is already running`); - } - - await this.eventRepository.emit('QueueStart', { name }); - - switch (name) { - case QueueName.VideoConversion: { - return this.jobRepository.queue({ name: JobName.AssetEncodeVideoQueueAll, data: { force } }); - } - - case QueueName.StorageTemplateMigration: { - return this.jobRepository.queue({ name: JobName.StorageTemplateMigration }); - } - - case QueueName.Migration: { - return this.jobRepository.queue({ name: JobName.FileMigrationQueueAll }); - } - - case QueueName.SmartSearch: { - return this.jobRepository.queue({ name: JobName.SmartSearchQueueAll, data: { force } }); - } - - case QueueName.DuplicateDetection: { - return this.jobRepository.queue({ name: JobName.AssetDetectDuplicatesQueueAll, data: { force } }); - } - - case QueueName.MetadataExtraction: { - return this.jobRepository.queue({ name: JobName.AssetExtractMetadataQueueAll, data: { force } }); - } - - case QueueName.Sidecar: { - return this.jobRepository.queue({ name: JobName.SidecarQueueAll, data: { force } }); - } - - case QueueName.ThumbnailGeneration: { - return this.jobRepository.queue({ name: JobName.AssetGenerateThumbnailsQueueAll, data: { force } }); - } - - case QueueName.FaceDetection: { - return this.jobRepository.queue({ name: JobName.AssetDetectFacesQueueAll, data: { force } }); - } - - case QueueName.FacialRecognition: { - return this.jobRepository.queue({ name: JobName.FacialRecognitionQueueAll, data: { force } }); - } - - case QueueName.Library: { - return this.jobRepository.queue({ name: JobName.LibraryScanQueueAll, data: { force } }); - } - - case QueueName.BackupDatabase: { - return this.jobRepository.queue({ name: JobName.DatabaseBackup, data: { force } }); - } - - case QueueName.Ocr: { - return this.jobRepository.queue({ name: JobName.OcrQueueAll, data: { force } }); - } - - default: { - throw new BadRequestException(`Invalid job name: ${name}`); - } - } - } - - private isConcurrentQueue(name: QueueName): name is ConcurrentQueueName { - return ![ - QueueName.FacialRecognition, - QueueName.StorageTemplateMigration, - QueueName.DuplicateDetection, - QueueName.BackupDatabase, - ].includes(name); - } - - async handleNightlyJobs() { - const config = await this.getConfig({ withCache: false }); - const jobs: JobItem[] = []; - - if (config.nightlyTasks.databaseCleanup) { - jobs.push( - { name: JobName.AssetDeleteCheck }, - { name: JobName.UserDeleteCheck }, - { name: JobName.PersonCleanup }, - { name: JobName.MemoryCleanup }, - { name: JobName.SessionCleanup }, - { name: JobName.AuditTableCleanup }, - { name: JobName.AuditLogCleanup }, - ); - } - - if (config.nightlyTasks.generateMemories) { - jobs.push({ name: JobName.MemoryGenerate }); - } - - if (config.nightlyTasks.syncQuotaUsage) { - jobs.push({ name: JobName.UserSyncUsage }); - } - - if (config.nightlyTasks.missingThumbnails) { - jobs.push({ name: JobName.AssetGenerateThumbnailsQueueAll, data: { force: false } }); - } - - if (config.nightlyTasks.clusterNewFaces) { - jobs.push({ name: JobName.FacialRecognitionQueueAll, data: { force: false, nightly: true } }); - } - - await this.jobRepository.queueAll(jobs); + @OnEvent({ + name: 'app.bootstrap', + priority: BootstrapEventPriority.JobService, + workers: [ImmichWorker.Microservices], + }) + async onBootstrap() { + this.jobRepository.setConcurrency(QueueName.BackgroundTask, 5); } } diff --git a/server/src/services/search.service.spec.ts b/server/src/services/search.service.spec.ts deleted file mode 100644 index 0dec02f18f..0000000000 --- a/server/src/services/search.service.spec.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { SearchSuggestionType } from 'src/dtos/search.dto'; -import { SearchService } from 'src/services/search.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { personStub } from 'test/fixtures/person.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; -import { beforeEach, vitest } from 'vitest'; - -vitest.useFakeTimers(); - -describe(SearchService.name, () => { - let sut: SearchService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SearchService)); - mocks.partner.getAll.mockResolvedValue([]); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('searchPerson', () => { - it('should pass options to search', async () => { - const { name } = personStub.withName; - - mocks.person.getByName.mockResolvedValue([]); - - await sut.searchPerson(authStub.user1, { name, withHidden: false }); - - expect(mocks.person.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: false }); - - await sut.searchPerson(authStub.user1, { name, withHidden: true }); - - expect(mocks.person.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: true }); - }); - }); - - describe('searchPlaces', () => { - it('should search places', async () => { - mocks.search.searchPlaces.mockResolvedValue([ - { - id: 42, - name: 'my place', - latitude: 420, - longitude: 69, - admin1Code: null, - admin1Name: null, - admin2Code: null, - admin2Name: null, - alternateNames: null, - countryCode: 'US', - modificationDate: new Date(), - }, - ]); - - await sut.searchPlaces({ name: 'place' }); - expect(mocks.search.searchPlaces).toHaveBeenCalledWith('place'); - }); - }); - - describe('getExploreData', () => { - it('should get assets by city and tag', async () => { - mocks.asset.getAssetIdByCity.mockResolvedValue({ - fieldName: 'exifInfo.city', - items: [{ value: 'test-city', data: assetStub.withLocation.id }], - }); - mocks.asset.getByIdsWithAllRelationsButStacks.mockResolvedValue([assetStub.withLocation]); - const expectedResponse = [ - { fieldName: 'exifInfo.city', items: [{ value: 'test-city', data: mapAsset(assetStub.withLocation) }] }, - ]; - - const result = await sut.getExploreData(authStub.user1); - - expect(result).toEqual(expectedResponse); - }); - }); - - describe('getSearchSuggestions', () => { - it('should return search suggestions for country', async () => { - mocks.search.getCountries.mockResolvedValue(['USA']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.COUNTRY }), - ).resolves.toEqual(['USA']); - expect(mocks.search.getCountries).toHaveBeenCalledWith([authStub.user1.user.id]); - }); - - it('should return search suggestions for country (including null)', async () => { - mocks.search.getCountries.mockResolvedValue(['USA']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.COUNTRY }), - ).resolves.toEqual(['USA', null]); - expect(mocks.search.getCountries).toHaveBeenCalledWith([authStub.user1.user.id]); - }); - - it('should return search suggestions for state', async () => { - mocks.search.getStates.mockResolvedValue(['California']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.STATE }), - ).resolves.toEqual(['California']); - expect(mocks.search.getStates).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for state (including null)', async () => { - mocks.search.getStates.mockResolvedValue(['California']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.STATE }), - ).resolves.toEqual(['California', null]); - expect(mocks.search.getStates).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for city', async () => { - mocks.search.getCities.mockResolvedValue(['Denver']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CITY }), - ).resolves.toEqual(['Denver']); - expect(mocks.search.getCities).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for city (including null)', async () => { - mocks.search.getCities.mockResolvedValue(['Denver']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CITY }), - ).resolves.toEqual(['Denver', null]); - expect(mocks.search.getCities).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for camera make', async () => { - mocks.search.getCameraMakes.mockResolvedValue(['Nikon']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_MAKE }), - ).resolves.toEqual(['Nikon']); - expect(mocks.search.getCameraMakes).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for camera make (including null)', async () => { - mocks.search.getCameraMakes.mockResolvedValue(['Nikon']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_MAKE }), - ).resolves.toEqual(['Nikon', null]); - expect(mocks.search.getCameraMakes).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for camera model', async () => { - mocks.search.getCameraModels.mockResolvedValue(['Fujifilm X100VI']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_MODEL }), - ).resolves.toEqual(['Fujifilm X100VI']); - expect(mocks.search.getCameraModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for camera model (including null)', async () => { - mocks.search.getCameraModels.mockResolvedValue(['Fujifilm X100VI']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_MODEL }), - ).resolves.toEqual(['Fujifilm X100VI', null]); - expect(mocks.search.getCameraModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for camera lens model', async () => { - mocks.search.getCameraLensModels.mockResolvedValue(['10-24mm']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.CAMERA_LENS_MODEL }), - ).resolves.toEqual(['10-24mm']); - expect(mocks.search.getCameraLensModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - - it('should return search suggestions for camera lens model (including null)', async () => { - mocks.search.getCameraLensModels.mockResolvedValue(['10-24mm']); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.CAMERA_LENS_MODEL }), - ).resolves.toEqual(['10-24mm', null]); - expect(mocks.search.getCameraLensModels).toHaveBeenCalledWith([authStub.user1.user.id], expect.anything()); - }); - }); - - describe('searchSmart', () => { - beforeEach(() => { - mocks.search.searchSmart.mockResolvedValue({ hasNextPage: false, items: [] }); - mocks.machineLearning.encodeText.mockResolvedValue('[1, 2, 3]'); - }); - - it('should raise a BadRequestException if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { enabled: false }, - }); - - await expect(sut.searchSmart(authStub.user1, { query: 'test' })).rejects.toThrowError( - new BadRequestException('Smart search is not enabled'), - ); - }); - - it('should raise a BadRequestException if smart search is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { clip: { enabled: false } }, - }); - - await expect(sut.searchSmart(authStub.user1, { query: 'test' })).rejects.toThrowError( - new BadRequestException('Smart search is not enabled'), - ); - }); - - it('should work', async () => { - await sut.searchSmart(authStub.user1, { query: 'test' }); - - expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith( - 'test', - expect.objectContaining({ modelName: expect.any(String) }), - ); - expect(mocks.search.searchSmart).toHaveBeenCalledWith( - { page: 1, size: 100 }, - { query: 'test', embedding: '[1, 2, 3]', userIds: [authStub.user1.user.id] }, - ); - }); - - it('should consider page and size parameters', async () => { - await sut.searchSmart(authStub.user1, { query: 'test', page: 2, size: 50 }); - - expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith( - 'test', - expect.objectContaining({ modelName: expect.any(String) }), - ); - expect(mocks.search.searchSmart).toHaveBeenCalledWith( - { page: 2, size: 50 }, - expect.objectContaining({ query: 'test', embedding: '[1, 2, 3]', userIds: [authStub.user1.user.id] }), - ); - }); - - it('should use clip model specified in config', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - machineLearning: { clip: { modelName: 'ViT-B-16-SigLIP__webli' } }, - }); - - await sut.searchSmart(authStub.user1, { query: 'test' }); - - expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith( - 'test', - expect.objectContaining({ modelName: 'ViT-B-16-SigLIP__webli' }), - ); - }); - - it('should use language specified in request', async () => { - await sut.searchSmart(authStub.user1, { query: 'test', language: 'de' }); - - expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith( - 'test', - expect.objectContaining({ language: 'de' }), - ); - }); - }); -}); diff --git a/server/src/services/search.service.ts b/server/src/services/search.service.ts deleted file mode 100644 index 9a6f8321a9..0000000000 --- a/server/src/services/search.service.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { LRUMap } from 'mnemonist'; -import { AssetMapOptions, AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { mapPerson, PersonResponseDto } from 'src/dtos/person.dto'; -import { - LargeAssetSearchDto, - mapPlaces, - MetadataSearchDto, - PlacesResponseDto, - RandomSearchDto, - SearchPeopleDto, - SearchPlacesDto, - SearchResponseDto, - SearchStatisticsResponseDto, - SearchSuggestionRequestDto, - SearchSuggestionType, - SmartSearchDto, - StatisticsSearchDto, -} from 'src/dtos/search.dto'; -import { AssetOrder, AssetVisibility, Permission } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { requireElevatedPermission } from 'src/utils/access'; -import { getMyPartnerIds } from 'src/utils/asset.util'; -import { isSmartSearchEnabled } from 'src/utils/misc'; - -@Injectable() -export class SearchService extends BaseService { - private embeddingCache = new LRUMap(100); - - async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise { - const people = await this.personRepository.getByName(auth.user.id, dto.name, { withHidden: dto.withHidden }); - return people.map((person) => mapPerson(person)); - } - - async searchPlaces(dto: SearchPlacesDto): Promise { - const places = await this.searchRepository.searchPlaces(dto.name); - return places.map((place) => mapPlaces(place)); - } - - async getExploreData(auth: AuthDto) { - const options = { maxFields: 12, minAssetsPerField: 5 }; - const cities = await this.assetRepository.getAssetIdByCity(auth.user.id, options); - const assets = await this.assetRepository.getByIdsWithAllRelationsButStacks(cities.items.map(({ data }) => data)); - const items = assets.map((asset) => ({ value: asset.exifInfo!.city!, data: mapAsset(asset, { auth }) })); - return [{ fieldName: cities.fieldName, items }]; - } - - async searchMetadata(auth: AuthDto, dto: MetadataSearchDto): Promise { - if (dto.visibility === AssetVisibility.Locked) { - requireElevatedPermission(auth); - } - - let checksum: Buffer | undefined; - if (dto.checksum) { - const encoding = dto.checksum.length === 28 ? 'base64' : 'hex'; - checksum = Buffer.from(dto.checksum, encoding); - } - - const page = dto.page ?? 1; - const size = dto.size || 250; - const userIds = await this.getUserIdsToSearch(auth); - const { hasNextPage, items } = await this.searchRepository.searchMetadata( - { page, size }, - { - ...dto, - checksum, - userIds, - orderDirection: dto.order ?? AssetOrder.Desc, - }, - ); - - return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth }); - } - - async searchStatistics(auth: AuthDto, dto: StatisticsSearchDto): Promise { - const userIds = await this.getUserIdsToSearch(auth); - - return await this.searchRepository.searchStatistics({ - ...dto, - userIds, - }); - } - - async searchRandom(auth: AuthDto, dto: RandomSearchDto): Promise { - if (dto.visibility === AssetVisibility.Locked) { - requireElevatedPermission(auth); - } - - const userIds = await this.getUserIdsToSearch(auth); - const items = await this.searchRepository.searchRandom(dto.size || 250, { ...dto, userIds }); - return items.map((item) => mapAsset(item, { auth })); - } - - async searchLargeAssets(auth: AuthDto, dto: LargeAssetSearchDto): Promise { - if (dto.visibility === AssetVisibility.Locked) { - requireElevatedPermission(auth); - } - - const userIds = await this.getUserIdsToSearch(auth); - const items = await this.searchRepository.searchLargeAssets(dto.size || 250, { ...dto, userIds }); - return items.map((item) => mapAsset(item, { auth })); - } - - async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise { - if (dto.visibility === AssetVisibility.Locked) { - requireElevatedPermission(auth); - } - - const { machineLearning } = await this.getConfig({ withCache: false }); - if (!isSmartSearchEnabled(machineLearning)) { - throw new BadRequestException('Smart search is not enabled'); - } - - const userIds = this.getUserIdsToSearch(auth); - let embedding; - if (dto.query) { - const key = machineLearning.clip.modelName + dto.query + dto.language; - embedding = this.embeddingCache.get(key); - if (!embedding) { - embedding = await this.machineLearningRepository.encodeText(dto.query, { - modelName: machineLearning.clip.modelName, - language: dto.language, - }); - this.embeddingCache.set(key, embedding); - } - } else if (dto.queryAssetId) { - await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.queryAssetId] }); - const getEmbeddingResponse = await this.searchRepository.getEmbedding(dto.queryAssetId); - const assetEmbedding = getEmbeddingResponse?.embedding; - if (!assetEmbedding) { - throw new BadRequestException(`Asset ${dto.queryAssetId} has no embedding`); - } - embedding = assetEmbedding; - } else { - throw new BadRequestException('Either `query` or `queryAssetId` must be set'); - } - const page = dto.page ?? 1; - const size = dto.size || 100; - const { hasNextPage, items } = await this.searchRepository.searchSmart( - { page, size }, - { ...dto, userIds: await userIds, embedding }, - ); - - return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth }); - } - - async getAssetsByCity(auth: AuthDto): Promise { - const userIds = await this.getUserIdsToSearch(auth); - const assets = await this.searchRepository.getAssetsByCity(userIds); - return assets.map((asset) => mapAsset(asset)); - } - - async getSearchSuggestions(auth: AuthDto, dto: SearchSuggestionRequestDto) { - const userIds = await this.getUserIdsToSearch(auth); - const suggestions = await this.getSuggestions(userIds, dto); - if (dto.includeNull) { - suggestions.push(null); - } - return suggestions; - } - - private getSuggestions(userIds: string[], dto: SearchSuggestionRequestDto): Promise> { - switch (dto.type) { - case SearchSuggestionType.COUNTRY: { - return this.searchRepository.getCountries(userIds); - } - case SearchSuggestionType.STATE: { - return this.searchRepository.getStates(userIds, dto); - } - case SearchSuggestionType.CITY: { - return this.searchRepository.getCities(userIds, dto); - } - case SearchSuggestionType.CAMERA_MAKE: { - return this.searchRepository.getCameraMakes(userIds, dto); - } - case SearchSuggestionType.CAMERA_MODEL: { - return this.searchRepository.getCameraModels(userIds, dto); - } - case SearchSuggestionType.CAMERA_LENS_MODEL: { - return this.searchRepository.getCameraLensModels(userIds, dto); - } - default: { - return Promise.resolve([]); - } - } - } - - private async getUserIdsToSearch(auth: AuthDto): Promise { - const partnerIds = await getMyPartnerIds({ - userId: auth.user.id, - repository: this.partnerRepository, - timelineEnabled: true, - }); - return [auth.user.id, ...partnerIds]; - } - - private mapResponse(assets: MapAsset[], nextPage: string | null, options: AssetMapOptions): SearchResponseDto { - return { - albums: { total: 0, count: 0, items: [], facets: [] }, - assets: { - total: assets.length, - count: assets.length, - items: assets.map((asset) => mapAsset(asset, options)), - facets: [], - nextPage, - }, - }; - } -} diff --git a/server/src/services/server.service.spec.ts b/server/src/services/server.service.spec.ts deleted file mode 100644 index 6e1187a900..0000000000 --- a/server/src/services/server.service.spec.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { SystemMetadataKey } from 'src/enum'; -import { ServerService } from 'src/services/server.service'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(ServerService.name, () => { - let sut: ServerService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(ServerService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getStorage', () => { - it('should return the disk space as B', async () => { - mocks.storage.checkDiskUsage.mockResolvedValue({ free: 200, available: 300, total: 500 }); - - await expect(sut.getStorage()).resolves.toEqual({ - diskAvailable: '300 B', - diskAvailableRaw: 300, - diskSize: '500 B', - diskSizeRaw: 500, - diskUsagePercentage: 60, - diskUse: '300 B', - diskUseRaw: 300, - }); - - expect(mocks.storage.checkDiskUsage).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - }); - - it('should return the disk space as KiB', async () => { - mocks.storage.checkDiskUsage.mockResolvedValue({ free: 200_000, available: 300_000, total: 500_000 }); - - await expect(sut.getStorage()).resolves.toEqual({ - diskAvailable: '293.0 KiB', - diskAvailableRaw: 300_000, - diskSize: '488.3 KiB', - diskSizeRaw: 500_000, - diskUsagePercentage: 60, - diskUse: '293.0 KiB', - diskUseRaw: 300_000, - }); - - expect(mocks.storage.checkDiskUsage).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - }); - - it('should return the disk space as MiB', async () => { - mocks.storage.checkDiskUsage.mockResolvedValue({ free: 200_000_000, available: 300_000_000, total: 500_000_000 }); - - await expect(sut.getStorage()).resolves.toEqual({ - diskAvailable: '286.1 MiB', - diskAvailableRaw: 300_000_000, - diskSize: '476.8 MiB', - diskSizeRaw: 500_000_000, - diskUsagePercentage: 60, - diskUse: '286.1 MiB', - diskUseRaw: 300_000_000, - }); - - expect(mocks.storage.checkDiskUsage).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - }); - - it('should return the disk space as GiB', async () => { - mocks.storage.checkDiskUsage.mockResolvedValue({ - free: 200_000_000_000, - available: 300_000_000_000, - total: 500_000_000_000, - }); - - await expect(sut.getStorage()).resolves.toEqual({ - diskAvailable: '279.4 GiB', - diskAvailableRaw: 300_000_000_000, - diskSize: '465.7 GiB', - diskSizeRaw: 500_000_000_000, - diskUsagePercentage: 60, - diskUse: '279.4 GiB', - diskUseRaw: 300_000_000_000, - }); - - expect(mocks.storage.checkDiskUsage).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - }); - - it('should return the disk space as TiB', async () => { - mocks.storage.checkDiskUsage.mockResolvedValue({ - free: 200_000_000_000_000, - available: 300_000_000_000_000, - total: 500_000_000_000_000, - }); - - await expect(sut.getStorage()).resolves.toEqual({ - diskAvailable: '272.8 TiB', - diskAvailableRaw: 300_000_000_000_000, - diskSize: '454.7 TiB', - diskSizeRaw: 500_000_000_000_000, - diskUsagePercentage: 60, - diskUse: '272.8 TiB', - diskUseRaw: 300_000_000_000_000, - }); - - expect(mocks.storage.checkDiskUsage).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - }); - - it('should return the disk space as PiB', async () => { - mocks.storage.checkDiskUsage.mockResolvedValue({ - free: 200_000_000_000_000_000, - available: 300_000_000_000_000_000, - total: 500_000_000_000_000_000, - }); - - await expect(sut.getStorage()).resolves.toEqual({ - diskAvailable: '266.5 PiB', - diskAvailableRaw: 300_000_000_000_000_000, - diskSize: '444.1 PiB', - diskSizeRaw: 500_000_000_000_000_000, - diskUsagePercentage: 60, - diskUse: '266.5 PiB', - diskUseRaw: 300_000_000_000_000_000, - }); - - expect(mocks.storage.checkDiskUsage).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - }); - }); - - describe('ping', () => { - it('should respond with pong', () => { - expect(sut.ping()).toEqual({ res: 'pong' }); - }); - }); - - describe('getFeatures', () => { - it('should respond the server features', async () => { - await expect(sut.getFeatures()).resolves.toEqual({ - smartSearch: true, - duplicateDetection: true, - facialRecognition: true, - importFaces: false, - map: true, - reverseGeocoding: true, - oauth: false, - oauthAutoLaunch: false, - ocr: true, - passwordLogin: true, - search: true, - sidecar: true, - configFile: false, - trash: true, - email: false, - }); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - }); - - describe('getSystemConfig', () => { - it('should respond the server configuration', async () => { - await expect(sut.getSystemConfig()).resolves.toEqual({ - loginPageMessage: '', - oauthButtonText: 'Login with OAuth', - trashDays: 30, - userDeleteDelay: 7, - isInitialized: undefined, - isOnboarded: false, - externalDomain: '', - publicUsers: true, - mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', - mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', - maintenanceMode: false, - }); - expect(mocks.systemMetadata.get).toHaveBeenCalled(); - }); - }); - - describe('getStats', () => { - it('should total up usage by user', async () => { - mocks.user.getUserStats.mockResolvedValue([ - { - userId: 'user1', - userName: '1 User', - photos: 10, - videos: 11, - usage: 12_345, - usagePhotos: 1, - usageVideos: 11_345, - quotaSizeInBytes: 0, - }, - { - userId: 'user2', - userName: '2 User', - photos: 10, - videos: 20, - usage: 123_456, - usagePhotos: 100, - usageVideos: 23_456, - quotaSizeInBytes: 0, - }, - { - userId: 'user3', - userName: '3 User', - photos: 100, - videos: 0, - usage: 987_654, - usagePhotos: 900, - usageVideos: 87_654, - quotaSizeInBytes: 0, - }, - ]); - - await expect(sut.getStatistics()).resolves.toEqual({ - photos: 120, - videos: 31, - usage: 1_123_455, - usagePhotos: 1001, - usageVideos: 122_455, - usageByUser: [ - { - photos: 10, - quotaSizeInBytes: 0, - usage: 12_345, - usagePhotos: 1, - usageVideos: 11_345, - userName: '1 User', - userId: 'user1', - videos: 11, - }, - { - photos: 10, - quotaSizeInBytes: 0, - usage: 123_456, - usagePhotos: 100, - usageVideos: 23_456, - userName: '2 User', - userId: 'user2', - videos: 20, - }, - { - photos: 100, - quotaSizeInBytes: 0, - usage: 987_654, - usagePhotos: 900, - usageVideos: 87_654, - userName: '3 User', - userId: 'user3', - videos: 0, - }, - ], - }); - - expect(mocks.user.getUserStats).toHaveBeenCalled(); - }); - }); - - describe('setLicense', () => { - it('should save license if valid', async () => { - mocks.systemMetadata.set.mockResolvedValue(); - - const license = { licenseKey: 'IMSV-license-key', activationKey: 'activation-key' }; - await sut.setLicense(license); - - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.License, expect.any(Object)); - }); - - it('should not save license if invalid', async () => { - mocks.user.upsertMetadata.mockResolvedValue(); - - const license = { licenseKey: 'license-key', activationKey: 'activation-key' }; - const call = sut.setLicense(license); - await expect(call).rejects.toThrowError('Invalid license key'); - expect(mocks.user.upsertMetadata).not.toHaveBeenCalled(); - }); - }); - - describe('deleteLicense', () => { - it('should delete license', async () => { - mocks.user.upsertMetadata.mockResolvedValue(); - - await sut.deleteLicense(); - expect(mocks.user.upsertMetadata).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/services/server.service.ts b/server/src/services/server.service.ts index 30bc1f1f0d..3ce8cc16f4 100644 --- a/server/src/services/server.service.ts +++ b/server/src/services/server.service.ts @@ -1,201 +1,46 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { serverVersion } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent } from 'src/decorators'; -import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; import { ServerAboutResponseDto, - ServerApkLinksDto, ServerConfigDto, ServerFeaturesDto, - ServerMediaTypesResponseDto, ServerPingResponse, - ServerStatsResponseDto, - ServerStorageResponseDto, - UsageByUserDto, + ServerVersionResponseDto, } from 'src/dtos/server.dto'; -import { StorageFolder, SystemMetadataKey } from 'src/enum'; -import { UserStatsQueryResponse } from 'src/repositories/user.repository'; import { BaseService } from 'src/services/base.service'; -import { asHumanReadable } from 'src/utils/bytes'; -import { mimeTypes } from 'src/utils/mime-types'; -import { - isDuplicateDetectionEnabled, - isFacialRecognitionEnabled, - isOcrEnabled, - isSmartSearchEnabled, -} from 'src/utils/misc'; @Injectable() export class ServerService extends BaseService { - @OnEvent({ name: 'AppBootstrap' }) - async onBootstrap(): Promise { - const featureFlags = await this.getFeatures(); - if (featureFlags.configFile) { - await this.systemMetadataRepository.set(SystemMetadataKey.AdminOnboarding, { - isOnboarded: true, - }); - } - this.logger.log(`Feature Flags: ${JSON.stringify(await this.getFeatures(), null, 2)}`); - } - - async getAboutInfo(): Promise { - const version = `v${serverVersion.toString()}`; - const { buildMetadata } = this.configRepository.getEnv(); - const buildVersions = await this.serverInfoRepository.getBuildVersions(); - const licensed = await this.systemMetadataRepository.get(SystemMetadataKey.License); - - return { - version, - versionUrl: `https://github.com/immich-app/immich/releases/tag/${version}`, - licensed: !!licensed, - ...buildMetadata, - ...buildVersions, - }; - } - - getApkLinks(): ServerApkLinksDto { - const baseUrl = `https://github.com/immich-app/immich/releases/download/v${serverVersion.toString()}`; - return { - arm64v8a: `${baseUrl}/app-arm64-v8a-release.apk`, - armeabiv7a: `${baseUrl}/app-armeabi-v7a-release.apk`, - universal: `${baseUrl}/app-release.apk`, - x86_64: `${baseUrl}/app-x86_64-release.apk`, - }; - } - - async getStorage(): Promise { - const libraryBase = StorageCore.getBaseFolder(StorageFolder.Library); - const diskInfo = await this.storageRepository.checkDiskUsage(libraryBase); - - const usagePercentage = (((diskInfo.total - diskInfo.free) / diskInfo.total) * 100).toFixed(2); - - const serverInfo = new ServerStorageResponseDto(); - serverInfo.diskAvailable = asHumanReadable(diskInfo.available); - serverInfo.diskSize = asHumanReadable(diskInfo.total); - serverInfo.diskUse = asHumanReadable(diskInfo.total - diskInfo.free); - serverInfo.diskAvailableRaw = diskInfo.available; - serverInfo.diskSizeRaw = diskInfo.total; - serverInfo.diskUseRaw = diskInfo.total - diskInfo.free; - serverInfo.diskUsagePercentage = Number.parseFloat(usagePercentage); - return serverInfo; - } - ping(): ServerPingResponse { return { res: 'pong' }; } - async getFeatures(): Promise { - const { reverseGeocoding, metadata, map, machineLearning, trash, oauth, passwordLogin, notifications } = - await this.getConfig({ withCache: false }); - const { configFile } = this.configRepository.getEnv(); + getVersion(): ServerVersionResponseDto { + return ServerVersionResponseDto.fromSemVer(serverVersion); + } + async getAboutInfo(): Promise { return { - smartSearch: isSmartSearchEnabled(machineLearning), - facialRecognition: isFacialRecognitionEnabled(machineLearning), - duplicateDetection: isDuplicateDetectionEnabled(machineLearning), - map: map.enabled, - reverseGeocoding: reverseGeocoding.enabled, - importFaces: metadata.faces.import, - sidecar: true, - search: true, - trash: trash.enabled, - oauth: oauth.enabled, - oauthAutoLaunch: oauth.autoLaunch, - ocr: isOcrEnabled(machineLearning), - passwordLogin: passwordLogin.enabled, - configFile: !!configFile, - email: notifications.smtp.enabled, + version: serverVersion.toString(), + versionUrl: `https://github.com/immich-app/immich/releases/tag/v${serverVersion.toString()}`, + nodejs: process.version, }; } - async getTheme() { - const { theme } = await this.getConfig({ withCache: false }); - return theme; + async getFeatures(): Promise { + const config = await this.getConfig({ withCache: false }); + return { + passwordLogin: config.passwordLogin.enabled, + }; } async getSystemConfig(): Promise { - const { setup } = this.configRepository.getEnv(); const config = await this.getConfig({ withCache: false }); - const isInitialized = !setup.allow || (await this.userRepository.hasAdmin()); - const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding); - + const isInitialized = await this.userRepository.hasAdmin(); return { loginPageMessage: config.server.loginPageMessage, - trashDays: config.trash.days, userDeleteDelay: config.user.deleteDelay, - oauthButtonText: config.oauth.buttonText, isInitialized, - isOnboarded: onboarding?.isOnboarded || false, - externalDomain: config.server.externalDomain, - publicUsers: config.server.publicUsers, - mapDarkStyleUrl: config.map.darkStyle, - mapLightStyleUrl: config.map.lightStyle, - maintenanceMode: false, }; } - - async getStatistics(): Promise { - const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats(); - const serverStats = new ServerStatsResponseDto(); - - for (const user of userStats) { - const usage = new UsageByUserDto(); - usage.userId = user.userId; - usage.userName = user.userName; - usage.photos = user.photos; - usage.videos = user.videos; - usage.usage = user.usage; - usage.usagePhotos = user.usagePhotos; - usage.usageVideos = user.usageVideos; - usage.quotaSizeInBytes = user.quotaSizeInBytes; - - serverStats.photos += usage.photos; - serverStats.videos += usage.videos; - serverStats.usage += usage.usage; - serverStats.usagePhotos += usage.usagePhotos; - serverStats.usageVideos += usage.usageVideos; - - serverStats.usageByUser.push(usage); - } - - return serverStats; - } - - getSupportedMediaTypes(): ServerMediaTypesResponseDto { - return { - video: Object.keys(mimeTypes.video), - image: Object.keys(mimeTypes.image), - sidecar: Object.keys(mimeTypes.sidecar), - }; - } - - async deleteLicense(): Promise { - await this.systemMetadataRepository.delete(SystemMetadataKey.License); - } - - async getLicense(): Promise { - const license = await this.systemMetadataRepository.get(SystemMetadataKey.License); - if (!license) { - throw new NotFoundException(); - } - return license; - } - - async setLicense(dto: LicenseKeyDto): Promise { - if (!dto.licenseKey.startsWith('IMSV-')) { - throw new BadRequestException('Invalid license key'); - } - const { licensePublicKey } = this.configRepository.getEnv(); - const licenseValid = this.cryptoRepository.verifySha256(dto.licenseKey, dto.activationKey, licensePublicKey.server); - if (!licenseValid) { - throw new BadRequestException('Invalid license key'); - } - - const licenseData = { ...dto, activatedAt: new Date() }; - - await this.systemMetadataRepository.set(SystemMetadataKey.License, licenseData); - - return licenseData; - } } diff --git a/server/src/services/session.service.spec.ts b/server/src/services/session.service.spec.ts deleted file mode 100644 index 7eacd148ad..0000000000 --- a/server/src/services/session.service.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { JobStatus } from 'src/enum'; -import { SessionService } from 'src/services/session.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe('SessionService', () => { - let sut: SessionService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SessionService)); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('handleCleanup', () => { - it('should clean sessions', async () => { - mocks.session.cleanup.mockResolvedValue([]); - await expect(sut.handleCleanup()).resolves.toEqual(JobStatus.Success); - }); - }); - - describe('getAll', () => { - it('should get the devices', async () => { - const currentSession = factory.session(); - const otherSession = factory.session(); - const auth = factory.auth({ session: currentSession }); - - mocks.session.getByUserId.mockResolvedValue([currentSession, otherSession]); - - await expect(sut.getAll(auth)).resolves.toEqual([ - expect.objectContaining({ current: true, id: currentSession.id }), - expect.objectContaining({ current: false, id: otherSession.id }), - ]); - - expect(mocks.session.getByUserId).toHaveBeenCalledWith(auth.user.id); - }); - }); - - describe('logoutDevices', () => { - it('should logout all devices', async () => { - const currentSession = factory.session(); - const auth = factory.auth({ session: currentSession }); - - mocks.session.invalidate.mockResolvedValue(); - - await sut.deleteAll(auth); - - expect(mocks.session.invalidate).toHaveBeenCalledWith({ userId: auth.user.id, excludeId: currentSession.id }); - }); - }); - - describe('logoutDevice', () => { - it('should logout the device', async () => { - mocks.access.authDevice.checkOwnerAccess.mockResolvedValue(new Set(['token-1'])); - mocks.session.delete.mockResolvedValue(); - - await sut.delete(authStub.user1, 'token-1'); - - expect(mocks.access.authDevice.checkOwnerAccess).toHaveBeenCalledWith( - authStub.user1.user.id, - new Set(['token-1']), - ); - expect(mocks.session.delete).toHaveBeenCalledWith('token-1'); - }); - }); -}); diff --git a/server/src/services/session.service.ts b/server/src/services/session.service.ts index 2f477c0d6a..a7f5b7d2e4 100644 --- a/server/src/services/session.service.ts +++ b/server/src/services/session.service.ts @@ -1,16 +1,14 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { OnEvent, OnJob } from 'src/decorators'; +import { OnJob } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { SessionCreateDto, SessionCreateResponseDto, SessionResponseDto, - SessionUpdateDto, mapSession, } from 'src/dtos/session.dto'; -import { JobName, JobStatus, Permission, QueueName } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; +import { JobName, JobStatus, QueueName } from 'src/enum'; import { BaseService } from 'src/services/base.service'; @Injectable() @@ -21,9 +19,7 @@ export class SessionService extends BaseService { for (const session of sessions) { this.logger.verbose(`Deleted expired session token: ${session.deviceOS}/${session.deviceType}`); } - this.logger.log(`Deleted ${sessions.length} expired session tokens`); - return JobStatus.Success; } @@ -51,22 +47,7 @@ export class SessionService extends BaseService { return sessions.map((session) => mapSession(session, auth.session?.id)); } - async update(auth: AuthDto, id: string, dto: SessionUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.SessionUpdate, ids: [id] }); - - if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) { - throw new BadRequestException('No fields to update'); - } - - const session = await this.sessionRepository.update(id, { - isPendingSyncReset: dto.isPendingSyncReset, - }); - - return mapSession(session); - } - async delete(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.AuthDeviceDelete, ids: [id] }); await this.sessionRepository.delete(id); } @@ -75,14 +56,4 @@ export class SessionService extends BaseService { const currentSessionId = auth.session?.id; await this.sessionRepository.invalidate({ userId, excludeId: currentSessionId }); } - - async lock(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.SessionLock, ids: [id] }); - await this.sessionRepository.update(id, { pinExpiresAt: null }); - } - - @OnEvent({ name: 'AuthChangePassword' }) - async onAuthChangePassword({ userId, currentSessionId }: ArgOf<'AuthChangePassword'>): Promise { - await this.sessionRepository.invalidate({ userId, excludeId: currentSessionId }); - } } diff --git a/server/src/services/shared-link.service.spec.ts b/server/src/services/shared-link.service.spec.ts deleted file mode 100644 index 90c212650e..0000000000 --- a/server/src/services/shared-link.service.spec.ts +++ /dev/null @@ -1,356 +0,0 @@ -import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common'; -import _ from 'lodash'; -import { AssetIdErrorReason } from 'src/dtos/asset-ids.response.dto'; -import { SharedLinkType } from 'src/enum'; -import { SharedLinkService } from 'src/services/shared-link.service'; -import { albumStub } from 'test/fixtures/album.stub'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { sharedLinkResponseStub, sharedLinkStub } from 'test/fixtures/shared-link.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(SharedLinkService.name, () => { - let sut: SharedLinkService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SharedLinkService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getAll', () => { - it('should return all shared links for a user', async () => { - mocks.sharedLink.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]); - await expect(sut.getAll(authStub.user1, {})).resolves.toEqual([ - sharedLinkResponseStub.expired, - sharedLinkResponseStub.valid, - ]); - expect(mocks.sharedLink.getAll).toHaveBeenCalledWith({ userId: authStub.user1.user.id }); - }); - }); - - describe('getMine', () => { - it('should only work for a public user', async () => { - await expect(sut.getMine(authStub.admin, {})).rejects.toBeInstanceOf(ForbiddenException); - expect(mocks.sharedLink.get).not.toHaveBeenCalled(); - }); - - it('should return the shared link for the public user', async () => { - const authDto = authStub.adminSharedLink; - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); - await expect(sut.getMine(authDto, {})).resolves.toEqual(sharedLinkResponseStub.valid); - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id); - }); - - it('should not return metadata', async () => { - const authDto = factory.auth({ - sharedLink: { - showExif: false, - allowDownload: true, - allowUpload: true, - }, - }); - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.readonlyNoExif); - const response = await sut.getMine(authDto, {}); - expect(response.assets[0]).toMatchObject({ hasMetadata: false }); - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id); - }); - - it('should throw an error for an invalid password protected shared link', async () => { - const authDto = authStub.adminSharedLink; - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.passwordRequired); - await expect(sut.getMine(authDto, {})).rejects.toBeInstanceOf(UnauthorizedException); - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id); - }); - - it('should allow a correct password on a password protected shared link', async () => { - mocks.sharedLink.get.mockResolvedValue({ ...sharedLinkStub.individual, password: '123' }); - await expect(sut.getMine(authStub.adminSharedLink, { password: '123' })).resolves.toBeDefined(); - expect(mocks.sharedLink.get).toHaveBeenCalledWith( - authStub.adminSharedLink.user.id, - authStub.adminSharedLink.sharedLink?.id, - ); - }); - }); - - describe('get', () => { - it('should throw an error for an invalid shared link', async () => { - mocks.sharedLink.get.mockResolvedValue(void 0); - - await expect(sut.get(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); - expect(mocks.sharedLink.update).not.toHaveBeenCalled(); - }); - - it('should get a shared link by id', async () => { - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); - await expect(sut.get(authStub.user1, sharedLinkStub.valid.id)).resolves.toEqual(sharedLinkResponseStub.valid); - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id); - }); - }); - - describe('create', () => { - it('should not allow an album shared link without an albumId', async () => { - await expect(sut.create(authStub.admin, { type: SharedLinkType.Album, assetIds: [] })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - - it('should not allow non-owners to create album shared links', async () => { - await expect( - sut.create(authStub.admin, { type: SharedLinkType.Album, assetIds: [], albumId: 'album-1' }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should not allow individual shared links with no assets', async () => { - await expect( - sut.create(authStub.admin, { type: SharedLinkType.Individual, assetIds: [] }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should require asset ownership to make an individual shared link', async () => { - await expect( - sut.create(authStub.admin, { type: SharedLinkType.Individual, assetIds: ['asset-1'] }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should create an album shared link', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumStub.oneAsset.id])); - mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.valid); - - await sut.create(authStub.admin, { type: SharedLinkType.Album, albumId: albumStub.oneAsset.id }); - - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set([albumStub.oneAsset.id]), - ); - expect(mocks.sharedLink.create).toHaveBeenCalledWith({ - type: SharedLinkType.Album, - userId: authStub.admin.user.id, - albumId: albumStub.oneAsset.id, - allowDownload: true, - allowUpload: true, - description: null, - expiresAt: null, - slug: null, - showExif: true, - key: Buffer.from('random-bytes', 'utf8'), - }); - }); - - it('should create an individual shared link', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); - - await sut.create(authStub.admin, { - type: SharedLinkType.Individual, - assetIds: [assetStub.image.id], - showMetadata: true, - allowDownload: true, - allowUpload: true, - }); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set([assetStub.image.id]), - false, - ); - expect(mocks.sharedLink.create).toHaveBeenCalledWith({ - type: SharedLinkType.Individual, - userId: authStub.admin.user.id, - albumId: null, - allowDownload: true, - slug: null, - allowUpload: true, - assetIds: [assetStub.image.id], - description: null, - expiresAt: null, - showExif: true, - key: Buffer.from('random-bytes', 'utf8'), - }); - }); - - it('should create a shared link with allowDownload set to false when showMetadata is false', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); - mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); - - await sut.create(authStub.admin, { - type: SharedLinkType.Individual, - assetIds: [assetStub.image.id], - showMetadata: false, - allowDownload: true, - allowUpload: true, - }); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith( - authStub.admin.user.id, - new Set([assetStub.image.id]), - false, - ); - expect(mocks.sharedLink.create).toHaveBeenCalledWith({ - type: SharedLinkType.Individual, - userId: authStub.admin.user.id, - albumId: null, - allowDownload: false, - allowUpload: true, - assetIds: [assetStub.image.id], - description: null, - expiresAt: null, - showExif: false, - slug: null, - key: Buffer.from('random-bytes', 'utf8'), - }); - }); - }); - - describe('update', () => { - it('should throw an error for an invalid shared link', async () => { - mocks.sharedLink.get.mockResolvedValue(void 0); - - await expect(sut.update(authStub.user1, 'missing-id', {})).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); - expect(mocks.sharedLink.update).not.toHaveBeenCalled(); - }); - - it('should update a shared link', async () => { - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); - mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.valid); - - await sut.update(authStub.user1, sharedLinkStub.valid.id, { allowDownload: false }); - - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id); - expect(mocks.sharedLink.update).toHaveBeenCalledWith({ - id: sharedLinkStub.valid.id, - slug: null, - userId: authStub.user1.user.id, - allowDownload: false, - }); - }); - }); - - describe('remove', () => { - it('should throw an error for an invalid shared link', async () => { - mocks.sharedLink.get.mockResolvedValue(void 0); - - await expect(sut.remove(authStub.user1, 'missing-id')).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, 'missing-id'); - expect(mocks.sharedLink.update).not.toHaveBeenCalled(); - }); - - it('should remove a key', async () => { - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); - mocks.sharedLink.remove.mockResolvedValue(); - - await sut.remove(authStub.user1, sharedLinkStub.valid.id); - - expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id); - expect(mocks.sharedLink.remove).toHaveBeenCalledWith(sharedLinkStub.valid.id); - }); - }); - - describe('addAssets', () => { - it('should not work on album shared links', async () => { - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); - - await expect(sut.addAssets(authStub.admin, 'link-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - - it('should add assets to a shared link', async () => { - mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual)); - mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); - mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-3'])); - - await expect( - sut.addAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2', 'asset-3'] }), - ).resolves.toEqual([ - { assetId: assetStub.image.id, success: false, error: AssetIdErrorReason.DUPLICATE }, - { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NO_PERMISSION }, - { assetId: 'asset-3', success: true }, - ]); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledTimes(1); - expect(mocks.sharedLink.update).toHaveBeenCalled(); - expect(mocks.sharedLink.update).toHaveBeenCalledWith({ - ...sharedLinkStub.individual, - slug: null, - assetIds: ['asset-3'], - }); - }); - }); - - describe('removeAssets', () => { - it('should not work on album shared links', async () => { - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid); - - await expect(sut.removeAssets(authStub.admin, 'link-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf( - BadRequestException, - ); - }); - - it('should remove assets from a shared link', async () => { - mocks.sharedLink.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual)); - mocks.sharedLink.create.mockResolvedValue(sharedLinkStub.individual); - mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.individual); - mocks.sharedLinkAsset.remove.mockResolvedValue([assetStub.image.id]); - - await expect( - sut.removeAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2'] }), - ).resolves.toEqual([ - { assetId: assetStub.image.id, success: true }, - { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND }, - ]); - - expect(mocks.sharedLinkAsset.remove).toHaveBeenCalledWith('link-1', [assetStub.image.id, 'asset-2']); - expect(mocks.sharedLink.update).toHaveBeenCalledWith({ ...sharedLinkStub.individual, assets: [] }); - }); - }); - - describe('getMetadataTags', () => { - it('should return null when auth is not a shared link', async () => { - await expect(sut.getMetadataTags(authStub.admin)).resolves.toBe(null); - - expect(mocks.sharedLink.get).not.toHaveBeenCalled(); - }); - - it('should return null when shared link has a password', async () => { - const auth = factory.auth({ user: {}, sharedLink: { password: 'password' } }); - - await expect(sut.getMetadataTags(auth)).resolves.toBe(null); - - expect(mocks.sharedLink.get).not.toHaveBeenCalled(); - }); - - it('should return metadata tags', async () => { - mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.individual); - - await expect(sut.getMetadataTags(authStub.adminSharedLink)).resolves.toEqual({ - description: '1 shared photos & videos', - imageUrl: `https://my.immich.app/api/assets/asset-id/thumbnail?key=LCtkaJX4R1O_9D-2lq0STzsPryoL1UdAbyb6Sna1xxmQCSuqU2J1ZUsqt6GR-yGm1s0`, - title: 'Public Share', - }); - - expect(mocks.sharedLink.get).toHaveBeenCalled(); - }); - - it('should return metadata tags with a default image path if the asset id is not set', async () => { - mocks.sharedLink.get.mockResolvedValue({ ...sharedLinkStub.individual, album: null, assets: [] }); - await expect(sut.getMetadataTags(authStub.adminSharedLink)).resolves.toEqual({ - description: '0 shared photos & videos', - imageUrl: `https://my.immich.app/feature-panel.png`, - title: 'Public Share', - }); - - expect(mocks.sharedLink.get).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/services/shared-link.service.ts b/server/src/services/shared-link.service.ts deleted file mode 100644 index 1440598084..0000000000 --- a/server/src/services/shared-link.service.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { BadRequestException, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common'; -import { PostgresError } from 'postgres'; -import { SharedLink } from 'src/database'; -import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto'; -import { AssetIdsDto } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - mapSharedLink, - SharedLinkCreateDto, - SharedLinkEditDto, - SharedLinkPasswordDto, - SharedLinkResponseDto, - SharedLinkSearchDto, -} from 'src/dtos/shared-link.dto'; -import { Permission, SharedLinkType } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { getExternalDomain, OpenGraphTags } from 'src/utils/misc'; - -@Injectable() -export class SharedLinkService extends BaseService { - async getAll(auth: AuthDto, { id, albumId }: SharedLinkSearchDto): Promise { - return this.sharedLinkRepository - .getAll({ userId: auth.user.id, id, albumId }) - .then((links) => links.map((link) => mapSharedLink(link, { stripAssetMetadata: false }))); - } - - async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise { - if (!auth.sharedLink) { - throw new ForbiddenException(); - } - - const sharedLink = await this.findOrFail(auth.user.id, auth.sharedLink.id); - const response = mapSharedLink(sharedLink, { stripAssetMetadata: !sharedLink.showExif }); - if (sharedLink.password) { - response.token = this.validateAndRefreshToken(sharedLink, dto); - } - - return response; - } - - async get(auth: AuthDto, id: string): Promise { - const sharedLink = await this.findOrFail(auth.user.id, id); - return mapSharedLink(sharedLink, { stripAssetMetadata: false }); - } - - async create(auth: AuthDto, dto: SharedLinkCreateDto): Promise { - switch (dto.type) { - case SharedLinkType.Album: { - if (!dto.albumId) { - throw new BadRequestException('Invalid albumId'); - } - await this.requireAccess({ auth, permission: Permission.AlbumShare, ids: [dto.albumId] }); - break; - } - - case SharedLinkType.Individual: { - if (!dto.assetIds || dto.assetIds.length === 0) { - throw new BadRequestException('Invalid assetIds'); - } - - await this.requireAccess({ auth, permission: Permission.AssetShare, ids: dto.assetIds }); - - break; - } - } - - try { - const sharedLink = await this.sharedLinkRepository.create({ - key: this.cryptoRepository.randomBytes(50), - userId: auth.user.id, - type: dto.type, - albumId: dto.albumId || null, - assetIds: dto.assetIds, - description: dto.description || null, - password: dto.password, - expiresAt: dto.expiresAt || null, - allowUpload: dto.allowUpload ?? true, - allowDownload: dto.showMetadata === false ? false : (dto.allowDownload ?? true), - showExif: dto.showMetadata ?? true, - slug: dto.slug || null, - }); - - return mapSharedLink(sharedLink, { stripAssetMetadata: false }); - } catch (error) { - this.handleError(error); - } - } - - private handleError(error: unknown): never { - if ((error as PostgresError).constraint_name === 'shared_link_slug_uq') { - throw new BadRequestException('Shared link with this slug already exists'); - } - throw error; - } - - async update(auth: AuthDto, id: string, dto: SharedLinkEditDto) { - await this.findOrFail(auth.user.id, id); - try { - const sharedLink = await this.sharedLinkRepository.update({ - id, - userId: auth.user.id, - description: dto.description, - password: dto.password, - expiresAt: dto.changeExpiryTime && !dto.expiresAt ? null : dto.expiresAt, - allowUpload: dto.allowUpload, - allowDownload: dto.allowDownload, - showExif: dto.showMetadata, - slug: dto.slug || null, - }); - return mapSharedLink(sharedLink, { stripAssetMetadata: false }); - } catch (error) { - this.handleError(error); - } - } - - async remove(auth: AuthDto, id: string): Promise { - const sharedLink = await this.findOrFail(auth.user.id, id); - await this.sharedLinkRepository.remove(sharedLink.id); - } - - // TODO: replace `userId` with permissions and access control checks - private async findOrFail(userId: string, id: string) { - const sharedLink = await this.sharedLinkRepository.get(userId, id); - if (!sharedLink) { - throw new BadRequestException('Shared link not found'); - } - return sharedLink; - } - - async addAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise { - const sharedLink = await this.findOrFail(auth.user.id, id); - - if (sharedLink.type !== SharedLinkType.Individual) { - throw new BadRequestException('Invalid shared link type'); - } - - const existingAssetIds = new Set(sharedLink.assets.map((asset) => asset.id)); - const notPresentAssetIds = dto.assetIds.filter((assetId) => !existingAssetIds.has(assetId)); - const allowedAssetIds = await this.checkAccess({ - auth, - permission: Permission.AssetShare, - ids: notPresentAssetIds, - }); - - const results: AssetIdsResponseDto[] = []; - for (const assetId of dto.assetIds) { - const hasAsset = existingAssetIds.has(assetId); - if (hasAsset) { - results.push({ assetId, success: false, error: AssetIdErrorReason.DUPLICATE }); - continue; - } - - const hasAccess = allowedAssetIds.has(assetId); - if (!hasAccess) { - results.push({ assetId, success: false, error: AssetIdErrorReason.NO_PERMISSION }); - continue; - } - - results.push({ assetId, success: true }); - } - - await this.sharedLinkRepository.update({ - ...sharedLink, - assetIds: results.filter(({ success }) => success).map(({ assetId }) => assetId), - }); - - return results; - } - - async removeAssets(auth: AuthDto, id: string, dto: AssetIdsDto): Promise { - const sharedLink = await this.findOrFail(auth.user.id, id); - - if (sharedLink.type !== SharedLinkType.Individual) { - throw new BadRequestException('Invalid shared link type'); - } - - const removedAssetIds = await this.sharedLinkAssetRepository.remove(id, dto.assetIds); - - const results: AssetIdsResponseDto[] = []; - for (const assetId of dto.assetIds) { - const wasRemoved = removedAssetIds.find((id) => id === assetId); - if (!wasRemoved) { - results.push({ assetId, success: false, error: AssetIdErrorReason.NOT_FOUND }); - continue; - } - - results.push({ assetId, success: true }); - sharedLink.assets = sharedLink.assets.filter((asset) => asset.id !== assetId); - } - - await this.sharedLinkRepository.update(sharedLink); - - return results; - } - - async getMetadataTags(auth: AuthDto, defaultDomain?: string): Promise { - if (!auth.sharedLink || auth.sharedLink.password) { - return null; - } - - const config = await this.getConfig({ withCache: true }); - const sharedLink = await this.findOrFail(auth.sharedLink.userId, auth.sharedLink.id); - const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id; - const assetCount = sharedLink.assets.length > 0 ? sharedLink.assets.length : sharedLink.album?.assets?.length || 0; - const imagePath = assetId - ? `/api/assets/${assetId}/thumbnail?key=${sharedLink.key.toString('base64url')}` - : '/feature-panel.png'; - - return { - title: sharedLink.album ? sharedLink.album.albumName : 'Public Share', - description: sharedLink.description || `${assetCount} shared photos & videos`, - imageUrl: new URL(imagePath, getExternalDomain(config.server, defaultDomain)).href, - }; - } - - private validateAndRefreshToken(sharedLink: SharedLink, dto: SharedLinkPasswordDto): string { - const token = this.cryptoRepository.hashSha256(`${sharedLink.id}-${sharedLink.password}`); - const sharedLinkTokens = dto.token?.split(',') || []; - if (sharedLink.password !== dto.password && !sharedLinkTokens.includes(token)) { - throw new UnauthorizedException('Invalid password'); - } - - if (!sharedLinkTokens.includes(token)) { - sharedLinkTokens.push(token); - } - return sharedLinkTokens.join(','); - } -} diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts deleted file mode 100644 index b3af5cd15f..0000000000 --- a/server/src/services/smart-info.service.spec.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { SystemConfig } from 'src/config'; -import { ImmichWorker, JobName, JobStatus } from 'src/enum'; -import { SmartInfoService } from 'src/services/smart-info.service'; -import { getCLIPModelInfo } from 'src/utils/misc'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -describe(SmartInfoService.name, () => { - let sut: SmartInfoService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SmartInfoService)); - - mocks.asset.getByIds.mockResolvedValue([assetStub.image]); - mocks.config.getWorker.mockReturnValue(ImmichWorker.Microservices); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onConfigValidateEvent', () => { - it('should allow a valid model', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { machineLearning: { clip: { modelName: 'ViT-B-16__openai' } } } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).not.toThrow(); - }); - - it('should allow including organization', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { machineLearning: { clip: { modelName: 'immich-app/ViT-B-16__openai' } } } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).not.toThrow(); - }); - - it('should fail for an unsupported model', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { machineLearning: { clip: { modelName: 'test-model' } } } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).toThrow('Unknown CLIP model: test-model'); - }); - }); - - describe('onConfigInit', () => { - it('should return if machine learning is disabled', async () => { - await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningDisabled as SystemConfig }); - - expect(mocks.database.getDimensionSize).not.toHaveBeenCalled(); - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - expect(mocks.database.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); - }); - - it('should return if model and DB dimension size are equal', async () => { - mocks.database.getDimensionSize.mockResolvedValue(512); - - await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningEnabled as SystemConfig }); - - expect(mocks.database.getDimensionSize).toHaveBeenCalledTimes(1); - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - expect(mocks.database.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); - }); - - it('should update DB dimension size if model and DB have different values', async () => { - mocks.database.getDimensionSize.mockResolvedValue(768); - - await sut.onConfigInit({ newConfig: systemConfigStub.machineLearningEnabled as SystemConfig }); - - expect(mocks.database.getDimensionSize).toHaveBeenCalledTimes(1); - expect(mocks.database.setDimensionSize).toHaveBeenCalledWith(512); - }); - }); - - describe('onConfigUpdateEvent', () => { - it('should return if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await sut.onConfigUpdate({ - newConfig: systemConfigStub.machineLearningDisabled as SystemConfig, - oldConfig: systemConfigStub.machineLearningDisabled as SystemConfig, - }); - - expect(mocks.systemMetadata.get).not.toHaveBeenCalled(); - expect(mocks.database.getDimensionSize).not.toHaveBeenCalled(); - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - expect(mocks.database.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); - }); - - it('should return if model and DB dimension size are equal', async () => { - mocks.database.getDimensionSize.mockResolvedValue(512); - - await sut.onConfigUpdate({ - newConfig: { - machineLearning: { clip: { modelName: 'ViT-B-16__openai', enabled: true }, enabled: true }, - } as SystemConfig, - oldConfig: { - machineLearning: { clip: { modelName: 'ViT-B-16__openai', enabled: true }, enabled: true }, - } as SystemConfig, - }); - - expect(mocks.database.getDimensionSize).toHaveBeenCalledTimes(1); - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - expect(mocks.database.deleteAllSearchEmbeddings).not.toHaveBeenCalled(); - }); - - it('should update DB dimension size if model and DB have different values', async () => { - mocks.database.getDimensionSize.mockResolvedValue(512); - - await sut.onConfigUpdate({ - newConfig: { - machineLearning: { clip: { modelName: 'ViT-L-14-quickgelu__dfn2b', enabled: true }, enabled: true }, - } as SystemConfig, - oldConfig: { - machineLearning: { clip: { modelName: 'ViT-B-16__openai', enabled: true }, enabled: true }, - } as SystemConfig, - }); - - expect(mocks.database.getDimensionSize).toHaveBeenCalledTimes(1); - expect(mocks.database.setDimensionSize).toHaveBeenCalledWith(768); - }); - - it('should clear embeddings if old and new models are different', async () => { - mocks.database.getDimensionSize.mockResolvedValue(512); - - await sut.onConfigUpdate({ - newConfig: { - machineLearning: { clip: { modelName: 'ViT-B-32__openai', enabled: true }, enabled: true }, - } as SystemConfig, - oldConfig: { - machineLearning: { clip: { modelName: 'ViT-B-16__openai', enabled: true }, enabled: true }, - } as SystemConfig, - }); - - expect(mocks.database.deleteAllSearchEmbeddings).toHaveBeenCalled(); - expect(mocks.database.getDimensionSize).toHaveBeenCalledTimes(1); - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - }); - }); - - describe('handleQueueEncodeClip', () => { - it('should do nothing if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - await sut.handleQueueEncodeClip({}); - - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - }); - - it('should queue the assets without clip embeddings', async () => { - mocks.assetJob.streamForEncodeClip.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueEncodeClip({ force: false }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.SmartSearch, data: { id: assetStub.image.id } }, - ]); - expect(mocks.assetJob.streamForEncodeClip).toHaveBeenCalledWith(false); - expect(mocks.database.setDimensionSize).not.toHaveBeenCalled(); - }); - - it('should queue all the assets', async () => { - mocks.assetJob.streamForEncodeClip.mockReturnValue(makeStream([assetStub.image])); - - await sut.handleQueueEncodeClip({ force: true }); - - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { name: JobName.SmartSearch, data: { id: assetStub.image.id } }, - ]); - expect(mocks.assetJob.streamForEncodeClip).toHaveBeenCalledWith(true); - expect(mocks.database.setDimensionSize).toHaveBeenCalledExactlyOnceWith(512); - }); - }); - - describe('handleEncodeClip', () => { - it('should do nothing if machine learning is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled); - - expect(await sut.handleEncodeClip({ id: '123' })).toEqual(JobStatus.Skipped); - - expect(mocks.asset.getByIds).not.toHaveBeenCalled(); - expect(mocks.machineLearning.encodeImage).not.toHaveBeenCalled(); - }); - - it('should skip assets without a resize path', async () => { - mocks.assetJob.getForClipEncoding.mockResolvedValue({ ...assetStub.noResizePath, files: [] }); - - expect(await sut.handleEncodeClip({ id: assetStub.noResizePath.id })).toEqual(JobStatus.Failed); - - expect(mocks.search.upsert).not.toHaveBeenCalled(); - expect(mocks.machineLearning.encodeImage).not.toHaveBeenCalled(); - }); - - it('should save the returned objects', async () => { - mocks.machineLearning.encodeImage.mockResolvedValue('[0.01, 0.02, 0.03]'); - mocks.assetJob.getForClipEncoding.mockResolvedValue({ ...assetStub.image, files: [assetStub.image.files[1]] }); - - expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.Success); - - expect(mocks.machineLearning.encodeImage).toHaveBeenCalledWith( - '/uploads/user-id/thumbs/path.jpg', - expect.objectContaining({ modelName: 'ViT-B-32__openai' }), - ); - expect(mocks.search.upsert).toHaveBeenCalledWith(assetStub.image.id, '[0.01, 0.02, 0.03]'); - }); - - it('should skip invisible assets', async () => { - mocks.assetJob.getForClipEncoding.mockResolvedValue({ - ...assetStub.livePhotoMotionAsset, - files: [assetStub.image.files[1]], - }); - - expect(await sut.handleEncodeClip({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.Skipped); - - expect(mocks.machineLearning.encodeImage).not.toHaveBeenCalled(); - expect(mocks.search.upsert).not.toHaveBeenCalled(); - }); - - it('should fail if asset could not be found', async () => { - mocks.assetJob.getForClipEncoding.mockResolvedValue(void 0); - - expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.Failed); - - expect(mocks.machineLearning.encodeImage).not.toHaveBeenCalled(); - expect(mocks.search.upsert).not.toHaveBeenCalled(); - }); - - it('should wait for database', async () => { - mocks.machineLearning.encodeImage.mockResolvedValue('[0.01, 0.02, 0.03]'); - mocks.database.isBusy.mockReturnValue(true); - mocks.assetJob.getForClipEncoding.mockResolvedValue({ ...assetStub.image, files: [assetStub.image.files[1]] }); - - expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.Success); - - expect(mocks.database.wait).toHaveBeenCalledWith(512); - expect(mocks.machineLearning.encodeImage).toHaveBeenCalledWith( - '/uploads/user-id/thumbs/path.jpg', - expect.objectContaining({ modelName: 'ViT-B-32__openai' }), - ); - expect(mocks.search.upsert).toHaveBeenCalledWith(assetStub.image.id, '[0.01, 0.02, 0.03]'); - }); - }); - - describe('getCLIPModelInfo', () => { - it('should return the model info', () => { - expect(getCLIPModelInfo('ViT-B-32__openai')).toEqual({ dimSize: 512 }); - expect(getCLIPModelInfo('M-CLIP/XLM-Roberta-Large-Vit-L-14')).toEqual({ dimSize: 768 }); - }); - - it('should clean the model name', () => { - expect(getCLIPModelInfo('ViT-B-32::openai')).toEqual({ dimSize: 512 }); - }); - - it('should throw an error if the model is not present', () => { - expect(() => getCLIPModelInfo('test-model')).toThrow('Unknown CLIP model: test-model'); - }); - }); -}); diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts deleted file mode 100644 index d484fe8b6a..0000000000 --- a/server/src/services/smart-info.service.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { SystemConfig } from 'src/config'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { OnEvent, OnJob } from 'src/decorators'; -import { AssetVisibility, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { JobItem, JobOf } from 'src/types'; -import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc'; - -@Injectable() -export class SmartInfoService extends BaseService { - @OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] }) - async onConfigInit({ newConfig }: ArgOf<'ConfigInit'>) { - await this.init(newConfig); - } - - @OnEvent({ name: 'ConfigUpdate', workers: [ImmichWorker.Microservices], server: true }) - async onConfigUpdate({ oldConfig, newConfig }: ArgOf<'ConfigUpdate'>) { - await this.init(newConfig, oldConfig); - } - - @OnEvent({ name: 'ConfigValidate' }) - onConfigValidate({ newConfig }: ArgOf<'ConfigValidate'>) { - try { - getCLIPModelInfo(newConfig.machineLearning.clip.modelName); - } catch { - throw new Error( - `Unknown CLIP model: ${newConfig.machineLearning.clip.modelName}. Please check the model name for typos and confirm this is a supported model.`, - ); - } - } - - private async init(newConfig: SystemConfig, oldConfig?: SystemConfig) { - if (!isSmartSearchEnabled(newConfig.machineLearning)) { - return; - } - - await this.databaseRepository.withLock(DatabaseLock.CLIPDimSize, async () => { - const { dimSize } = getCLIPModelInfo(newConfig.machineLearning.clip.modelName); - const dbDimSize = await this.databaseRepository.getDimensionSize('smart_search'); - this.logger.verbose(`Current database CLIP dimension size is ${dbDimSize}`); - - const modelChange = - oldConfig && oldConfig.machineLearning.clip.modelName !== newConfig.machineLearning.clip.modelName; - const dimSizeChange = dbDimSize !== dimSize; - if (!modelChange && !dimSizeChange) { - return; - } - - if (dimSizeChange) { - this.logger.log( - `Dimension size of model ${newConfig.machineLearning.clip.modelName} is ${dimSize}, but database expects ${dbDimSize}.`, - ); - this.logger.log(`Updating database CLIP dimension size to ${dimSize}.`); - await this.databaseRepository.setDimensionSize(dimSize); - this.logger.log(`Successfully updated database CLIP dimension size from ${dbDimSize} to ${dimSize}.`); - } else { - await this.databaseRepository.deleteAllSearchEmbeddings(); - } - - // TODO: A job to reindex all assets should be scheduled, though user - // confirmation should probably be requested before doing that. - }); - } - - @OnJob({ name: JobName.SmartSearchQueueAll, queue: QueueName.SmartSearch }) - async handleQueueEncodeClip({ force }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: false }); - if (!isSmartSearchEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - if (force) { - const { dimSize } = getCLIPModelInfo(machineLearning.clip.modelName); - // in addition to deleting embeddings, update the dimension size in case it failed earlier - await this.databaseRepository.setDimensionSize(dimSize); - } - - let queue: JobItem[] = []; - const assets = this.assetJobRepository.streamForEncodeClip(force); - for await (const asset of assets) { - queue.push({ name: JobName.SmartSearch, data: { id: asset.id } }); - if (queue.length >= JOBS_ASSET_PAGINATION_SIZE) { - await this.jobRepository.queueAll(queue); - queue = []; - } - } - - await this.jobRepository.queueAll(queue); - - return JobStatus.Success; - } - - @OnJob({ name: JobName.SmartSearch, queue: QueueName.SmartSearch }) - async handleEncodeClip({ id }: JobOf): Promise { - const { machineLearning } = await this.getConfig({ withCache: true }); - if (!isSmartSearchEnabled(machineLearning)) { - return JobStatus.Skipped; - } - - const asset = await this.assetJobRepository.getForClipEncoding(id); - if (!asset || asset.files.length !== 1) { - return JobStatus.Failed; - } - - if (asset.visibility === AssetVisibility.Hidden) { - return JobStatus.Skipped; - } - - const embedding = await this.machineLearningRepository.encodeImage(asset.files[0].path, machineLearning.clip); - - if (this.databaseRepository.isBusy(DatabaseLock.CLIPDimSize)) { - this.logger.verbose(`Waiting for CLIP dimension size to be updated`); - await this.databaseRepository.wait(DatabaseLock.CLIPDimSize); - } - - const newConfig = await this.getConfig({ withCache: true }); - if (machineLearning.clip.modelName !== newConfig.machineLearning.clip.modelName) { - // Skip the job if the model has changed since the embedding was generated. - return JobStatus.Skipped; - } - - await this.searchRepository.upsert(asset.id, embedding); - - return JobStatus.Success; - } -} diff --git a/server/src/services/stack.service.spec.ts b/server/src/services/stack.service.spec.ts deleted file mode 100644 index 5517cf17f8..0000000000 --- a/server/src/services/stack.service.spec.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { StackService } from 'src/services/stack.service'; -import { assetStub, stackStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(StackService.name, () => { - let sut: StackService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(StackService)); - }); - - it('should be defined', () => { - expect(sut).toBeDefined(); - }); - - describe('search', () => { - it('should search stacks', async () => { - mocks.stack.search.mockResolvedValue([stackStub('stack-id', [assetStub.image])]); - - await sut.search(authStub.admin, { primaryAssetId: assetStub.image.id }); - expect(mocks.stack.search).toHaveBeenCalledWith({ - ownerId: authStub.admin.user.id, - primaryAssetId: assetStub.image.id, - }); - }); - }); - - describe('create', () => { - it('should require asset.update permissions', async () => { - await expect( - sut.create(authStub.admin, { assetIds: [assetStub.image.id, assetStub.image1.id] }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalled(); - expect(mocks.stack.create).not.toHaveBeenCalled(); - }); - - it('should create a stack', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id, assetStub.image1.id])); - mocks.stack.create.mockResolvedValue(stackStub('stack-id', [assetStub.image, assetStub.image1])); - await expect( - sut.create(authStub.admin, { assetIds: [assetStub.image.id, assetStub.image1.id] }), - ).resolves.toEqual({ - id: 'stack-id', - primaryAssetId: assetStub.image.id, - assets: [ - expect.objectContaining({ id: assetStub.image.id }), - expect.objectContaining({ id: assetStub.image1.id }), - ], - }); - - expect(mocks.event.emit).toHaveBeenCalledWith('StackCreate', { - stackId: 'stack-id', - userId: authStub.admin.user.id, - }); - expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalled(); - }); - }); - - describe('get', () => { - it('should require stack.read permissions', async () => { - await expect(sut.get(authStub.admin, 'stack-id')).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.access.stack.checkOwnerAccess).toHaveBeenCalled(); - expect(mocks.stack.getById).not.toHaveBeenCalled(); - }); - - it('should fail if stack could not be found', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - - await expect(sut.get(authStub.admin, 'stack-id')).rejects.toBeInstanceOf(Error); - - expect(mocks.access.stack.checkOwnerAccess).toHaveBeenCalled(); - expect(mocks.stack.getById).toHaveBeenCalledWith('stack-id'); - }); - - it('should get stack', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.getById.mockResolvedValue(stackStub('stack-id', [assetStub.image, assetStub.image1])); - - await expect(sut.get(authStub.admin, 'stack-id')).resolves.toEqual({ - id: 'stack-id', - primaryAssetId: assetStub.image.id, - assets: [ - expect.objectContaining({ id: assetStub.image.id }), - expect.objectContaining({ id: assetStub.image1.id }), - ], - }); - expect(mocks.access.stack.checkOwnerAccess).toHaveBeenCalled(); - expect(mocks.stack.getById).toHaveBeenCalledWith('stack-id'); - }); - }); - - describe('update', () => { - it('should require stack.update permissions', async () => { - await expect(sut.update(authStub.admin, 'stack-id', {})).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.stack.getById).not.toHaveBeenCalled(); - expect(mocks.stack.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should fail if stack could not be found', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - - await expect(sut.update(authStub.admin, 'stack-id', {})).rejects.toBeInstanceOf(Error); - - expect(mocks.stack.getById).toHaveBeenCalledWith('stack-id'); - expect(mocks.stack.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should fail if the provided primary asset id is not in the stack', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.getById.mockResolvedValue(stackStub('stack-id', [assetStub.image, assetStub.image1])); - - await expect(sut.update(authStub.admin, 'stack-id', { primaryAssetId: 'unknown-asset' })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.stack.getById).toHaveBeenCalledWith('stack-id'); - expect(mocks.stack.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should update stack', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.getById.mockResolvedValue(stackStub('stack-id', [assetStub.image, assetStub.image1])); - mocks.stack.update.mockResolvedValue(stackStub('stack-id', [assetStub.image, assetStub.image1])); - - await sut.update(authStub.admin, 'stack-id', { primaryAssetId: assetStub.image1.id }); - - expect(mocks.stack.getById).toHaveBeenCalledWith('stack-id'); - expect(mocks.stack.update).toHaveBeenCalledWith('stack-id', { - id: 'stack-id', - primaryAssetId: assetStub.image1.id, - }); - expect(mocks.event.emit).toHaveBeenCalledWith('StackUpdate', { - stackId: 'stack-id', - userId: authStub.admin.user.id, - }); - }); - }); - - describe('delete', () => { - it('should require stack.delete permissions', async () => { - await expect(sut.delete(authStub.admin, 'stack-id')).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.stack.delete).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should delete stack', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.delete.mockResolvedValue(); - - await sut.delete(authStub.admin, 'stack-id'); - - expect(mocks.stack.delete).toHaveBeenCalledWith('stack-id'); - expect(mocks.event.emit).toHaveBeenCalledWith('StackDelete', { - stackId: 'stack-id', - userId: authStub.admin.user.id, - }); - }); - }); - - describe('deleteAll', () => { - it('should require stack.delete permissions', async () => { - await expect(sut.deleteAll(authStub.admin, { ids: ['stack-id'] })).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.stack.deleteAll).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should delete all stacks', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.deleteAll.mockResolvedValue(); - - await sut.deleteAll(authStub.admin, { ids: ['stack-id'] }); - - expect(mocks.stack.deleteAll).toHaveBeenCalledWith(['stack-id']); - expect(mocks.event.emit).toHaveBeenCalledWith('StackDeleteAll', { - stackIds: ['stack-id'], - userId: authStub.admin.user.id, - }); - }); - }); - - describe('removeAsset', () => { - it('should require stack.update permissions', async () => { - await expect(sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: 'asset-id' })).rejects.toBeInstanceOf( - BadRequestException, - ); - - expect(mocks.stack.getForAssetRemoval).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should fail if the asset is not in the stack', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.getForAssetRemoval.mockResolvedValue({ id: null, primaryAssetId: null }); - - await expect( - sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: assetStub.imageFrom2015.id }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it('should fail if the assetId is the primaryAssetId', async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.getForAssetRemoval.mockResolvedValue({ id: 'stack-id', primaryAssetId: assetStub.image.id }); - - await expect( - sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: assetStub.image.id }), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.event.emit).not.toHaveBeenCalled(); - }); - - it("should update the asset to nullify it's stack-id", async () => { - mocks.access.stack.checkOwnerAccess.mockResolvedValue(new Set(['stack-id'])); - mocks.stack.getForAssetRemoval.mockResolvedValue({ id: 'stack-id', primaryAssetId: assetStub.image.id }); - - await sut.removeAsset(authStub.admin, { id: 'stack-id', assetId: assetStub.image1.id }); - - expect(mocks.asset.update).toHaveBeenCalledWith({ id: assetStub.image1.id, stackId: null }); - expect(mocks.event.emit).toHaveBeenCalledWith('StackUpdate', { - stackId: 'stack-id', - userId: authStub.admin.user.id, - }); - }); - }); -}); diff --git a/server/src/services/stack.service.ts b/server/src/services/stack.service.ts deleted file mode 100644 index c84ec70fbf..0000000000 --- a/server/src/services/stack.service.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { StackCreateDto, StackResponseDto, StackSearchDto, StackUpdateDto, mapStack } from 'src/dtos/stack.dto'; -import { Permission } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { UUIDAssetIDParamDto } from 'src/validation'; - -@Injectable() -export class StackService extends BaseService { - async search(auth: AuthDto, dto: StackSearchDto): Promise { - const stacks = await this.stackRepository.search({ - ownerId: auth.user.id, - primaryAssetId: dto.primaryAssetId, - }); - - return stacks.map((stack) => mapStack(stack, { auth })); - } - - async create(auth: AuthDto, dto: StackCreateDto): Promise { - await this.requireAccess({ auth, permission: Permission.AssetUpdate, ids: dto.assetIds }); - - const stack = await this.stackRepository.create({ ownerId: auth.user.id }, dto.assetIds); - - await this.eventRepository.emit('StackCreate', { stackId: stack.id, userId: auth.user.id }); - - return mapStack(stack, { auth }); - } - - async get(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.StackRead, ids: [id] }); - const stack = await this.findOrFail(id); - return mapStack(stack, { auth }); - } - - async update(auth: AuthDto, id: string, dto: StackUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.StackUpdate, ids: [id] }); - const stack = await this.findOrFail(id); - if (dto.primaryAssetId && !stack.assets.some(({ id }) => id === dto.primaryAssetId)) { - throw new BadRequestException('Primary asset must be in the stack'); - } - - const updatedStack = await this.stackRepository.update(id, { id, primaryAssetId: dto.primaryAssetId }); - - await this.eventRepository.emit('StackUpdate', { stackId: id, userId: auth.user.id }); - - return mapStack(updatedStack, { auth }); - } - - async delete(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.StackDelete, ids: [id] }); - await this.stackRepository.delete(id); - await this.eventRepository.emit('StackDelete', { stackId: id, userId: auth.user.id }); - } - - async deleteAll(auth: AuthDto, dto: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.StackDelete, ids: dto.ids }); - await this.stackRepository.deleteAll(dto.ids); - await this.eventRepository.emit('StackDeleteAll', { stackIds: dto.ids, userId: auth.user.id }); - } - - async removeAsset(auth: AuthDto, dto: UUIDAssetIDParamDto): Promise { - const { id: stackId, assetId } = dto; - await this.requireAccess({ auth, permission: Permission.StackUpdate, ids: [stackId] }); - - const stack = await this.stackRepository.getForAssetRemoval(assetId); - - if (!stack?.id || stack.id !== stackId) { - throw new BadRequestException('Asset not in stack'); - } - - if (stack.primaryAssetId === assetId) { - throw new BadRequestException("Cannot remove stack's primary asset"); - } - - await this.assetRepository.update({ id: assetId, stackId: null }); - await this.eventRepository.emit('StackUpdate', { stackId, userId: auth.user.id }); - } - - private async findOrFail(id: string) { - const stack = await this.stackRepository.getById(id); - if (!stack) { - throw new Error('Asset stack not found'); - } - - return stack; - } -} diff --git a/server/src/services/storage-template.service.spec.ts b/server/src/services/storage-template.service.spec.ts deleted file mode 100644 index 0b5d538cea..0000000000 --- a/server/src/services/storage-template.service.spec.ts +++ /dev/null @@ -1,759 +0,0 @@ -import { Stats } from 'node:fs'; -import { defaults, SystemConfig } from 'src/config'; -import { AssetPathType, JobStatus } from 'src/enum'; -import { StorageTemplateService } from 'src/services/storage-template.service'; -import { albumStub } from 'test/fixtures/album.stub'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { userStub } from 'test/fixtures/user.stub'; -import { factory } from 'test/small.factory'; -import { makeStream, newTestService, ServiceMocks } from 'test/utils'; - -const motionAsset = assetStub.storageAsset({}); -const stillAsset = assetStub.storageAsset({ livePhotoVideoId: motionAsset.id }); - -describe(StorageTemplateService.name, () => { - let sut: StorageTemplateService; - let mocks: ServiceMocks; - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - beforeEach(() => { - ({ sut, mocks } = newTestService(StorageTemplateService)); - - mocks.systemMetadata.get.mockResolvedValue({ storageTemplate: { enabled: true } }); - - sut.onConfigInit({ newConfig: defaults }); - }); - - describe('onConfigValidate', () => { - it('should allow valid templates', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { - storageTemplate: { - template: - '{{y}}{{M}}{{W}}{{d}}{{h}}{{m}}{{s}}{{filename}}{{ext}}{{filetype}}{{filetypefull}}{{assetId}}{{#if album}}{{album}}{{else}}other{{/if}}', - }, - } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).not.toThrow(); - }); - - it('should fail for an invalid template', () => { - expect(() => - sut.onConfigValidate({ - newConfig: { - storageTemplate: { - template: '{{foo}}', - }, - } as SystemConfig, - oldConfig: {} as SystemConfig, - }), - ).toThrow(/Invalid storage template.*/); - }); - }); - - describe('getStorageTemplateOptions', () => { - it('should send back the datetime variables', () => { - expect(sut.getStorageTemplateOptions()).toEqual({ - dayOptions: ['d', 'dd'], - hourOptions: ['h', 'hh', 'H', 'HH'], - minuteOptions: ['m', 'mm'], - monthOptions: ['M', 'MM', 'MMM', 'MMMM'], - presetOptions: [ - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}/{{filename}}', - '{{y}}/{{#if album}}{{album}}{{else}}Other/{{MM}}{{/if}}/{{filename}}', - '{{#if album}}{{album-startDate-y}}/{{album}}{{else}}{{y}}/Other/{{MM}}{{/if}}/{{filename}}', - '{{y}}/{{MMM}}/{{filename}}', - '{{y}}/{{MMMM}}/{{filename}}', - '{{y}}/{{MM}}/{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}/{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{filename}}', - '{{y}}/{{y}}-{{WW}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', - '{{y}}/{{y}}-{{MM}}/{{assetId}}', - '{{y}}/{{y}}-{{WW}}/{{assetId}}', - '{{album}}/{{filename}}', - '{{make}}/{{model}}/{{lensModel}}/{{filename}}', - ], - secondOptions: ['s', 'ss', 'SSS'], - weekOptions: ['W', 'WW'], - yearOptions: ['y', 'yy'], - }); - }); - }); - - describe('handleMigrationSingle', () => { - it('should skip when storage template is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ storageTemplate: { enabled: false } }); - - await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.Skipped); - - expect(mocks.asset.getByIds).not.toHaveBeenCalled(); - expect(mocks.storage.checkFileExists).not.toHaveBeenCalled(); - expect(mocks.storage.rename).not.toHaveBeenCalled(); - expect(mocks.storage.copyFile).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.move.create).not.toHaveBeenCalled(); - expect(mocks.move.update).not.toHaveBeenCalled(); - expect(mocks.storage.stat).not.toHaveBeenCalled(); - }); - - it('should migrate single moving picture', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - const newMotionPicturePath = `/data/library/${motionAsset.ownerId}/2022/2022-06-19/${motionAsset.originalFileName}`; - const newStillPicturePath = `/data/library/${stillAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName}`; - - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(stillAsset); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(motionAsset); - - mocks.move.create.mockResolvedValueOnce({ - id: '123', - entityId: stillAsset.id, - pathType: AssetPathType.Original, - oldPath: stillAsset.originalPath, - newPath: newStillPicturePath, - }); - - mocks.move.create.mockResolvedValueOnce({ - id: '124', - entityId: motionAsset.id, - pathType: AssetPathType.Original, - oldPath: motionAsset.originalPath, - newPath: newMotionPicturePath, - }); - - await expect(sut.handleMigrationSingle({ id: stillAsset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: stillAsset.id, originalPath: newStillPicturePath }); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: motionAsset.id, originalPath: newMotionPicturePath }); - }); - - it('should use handlebar if condition for album', async () => { - const asset = assetStub.storageAsset(); - const user = userStub.user1; - const album = albumStub.oneAsset; - const config = structuredClone(defaults); - config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other/{{MM}}{{/if}}/{{filename}}'; - - sut.onConfigInit({ newConfig: config }); - - mocks.user.get.mockResolvedValue(user); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset); - mocks.album.getByAssetId.mockResolvedValueOnce([album]); - - expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success); - - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: asset.id, - newPath: expect.stringContaining( - `/data/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/${album.albumName}/${asset.originalFileName}`, - ), - oldPath: asset.originalPath, - pathType: AssetPathType.Original, - }); - }); - - it('should use handlebar else condition for album', async () => { - const asset = assetStub.storageAsset(); - const user = userStub.user1; - const config = structuredClone(defaults); - config.storageTemplate.template = '{{y}}/{{#if album}}{{album}}{{else}}other//{{MM}}{{/if}}/{{filename}}'; - sut.onConfigInit({ newConfig: config }); - - mocks.user.get.mockResolvedValue(user); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset); - - expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success); - - const month = (asset.fileCreatedAt.getMonth() + 1).toString().padStart(2, '0'); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: asset.id, - newPath: expect.stringContaining( - `/data/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/other/${month}/${asset.originalFileName}`, - ), - oldPath: asset.originalPath, - pathType: AssetPathType.Original, - }); - }); - - it('should handle album startDate', async () => { - const asset = assetStub.storageAsset(); - const user = userStub.user1; - const album = albumStub.oneAsset; - const config = structuredClone(defaults); - config.storageTemplate.template = - '{{#if album}}{{album-startDate-y}}/{{album-startDate-MM}} - {{album}}{{else}}{{y}}/{{MM}}/{{/if}}/{{filename}}'; - - sut.onConfigInit({ newConfig: config }); - - mocks.user.get.mockResolvedValue(user); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset); - mocks.album.getByAssetId.mockResolvedValueOnce([album]); - mocks.album.getMetadataForIds.mockResolvedValueOnce([ - { - startDate: asset.fileCreatedAt, - endDate: asset.fileCreatedAt, - albumId: album.id, - assetCount: 1, - lastModifiedAssetTimestamp: null, - }, - ]); - - expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success); - - const month = (asset.fileCreatedAt.getMonth() + 1).toString().padStart(2, '0'); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: asset.id, - newPath: expect.stringContaining( - `/data/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/${month} - ${album.albumName}/${asset.originalFileName}`, - ), - oldPath: asset.originalPath, - pathType: AssetPathType.Original, - }); - }); - - it('should handle else condition from album startDate', async () => { - const asset = assetStub.storageAsset(); - const user = userStub.user1; - const config = structuredClone(defaults); - config.storageTemplate.template = - '{{#if album}}{{album-startDate-y}}/{{album-startDate-MM}} - {{album}}{{else}}{{y}}/{{MM}}/{{/if}}/{{filename}}'; - - sut.onConfigInit({ newConfig: config }); - - mocks.user.get.mockResolvedValue(user); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(asset); - - expect(await sut.handleMigrationSingle({ id: asset.id })).toBe(JobStatus.Success); - - const month = (asset.fileCreatedAt.getMonth() + 1).toString().padStart(2, '0'); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: asset.id, - newPath: `/data/library/${user.id}/${asset.fileCreatedAt.getFullYear()}/${month}/${asset.originalFileName}`, - oldPath: asset.originalPath, - pathType: AssetPathType.Original, - }); - }); - - it('should migrate previously failed move from original path when it still exists', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - - const asset = assetStub.storageAsset(); - const previousFailedNewPath = `/data/library/${userStub.user1.id}/2023/Feb/${asset.originalFileName}`; - const newPath = `/data/library/${userStub.user1.id}/2022/2022-06-19/${asset.originalFileName}`; - - mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(path === asset.originalPath)); - mocks.move.getByEntity.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: asset.originalPath, - newPath: previousFailedNewPath, - }); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(asset); - mocks.move.update.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: asset.originalPath, - newPath, - }); - - await expect(sut.handleMigrationSingle({ id: asset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(asset.id); - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3); - expect(mocks.storage.rename).toHaveBeenCalledWith(asset.originalPath, newPath); - expect(mocks.move.update).toHaveBeenCalledWith('123', { - id: '123', - oldPath: asset.originalPath, - newPath, - }); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: asset.id, - originalPath: newPath, - }); - }); - - it('should migrate previously failed move from previous new path when old path no longer exists, should validate file size still matches before moving', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - - const asset = assetStub.storageAsset({ fileSizeInByte: 5000 }); - const previousFailedNewPath = `/data/library/${asset.ownerId}/2022/June/${asset.originalFileName}`; - const newPath = `/data/library/${asset.ownerId}/2022/2022-06-19/${asset.originalFileName}`; - - mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(path === previousFailedNewPath)); - mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats); - mocks.crypto.hashFile.mockResolvedValue(asset.checksum); - mocks.move.getByEntity.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: asset.originalPath, - newPath: previousFailedNewPath, - }); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(asset); - mocks.move.update.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: previousFailedNewPath, - newPath, - }); - - await expect(sut.handleMigrationSingle({ id: asset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(asset.id); - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3); - expect(mocks.storage.stat).toHaveBeenCalledWith(previousFailedNewPath); - expect(mocks.storage.rename).toHaveBeenCalledWith(previousFailedNewPath, newPath); - expect(mocks.storage.copyFile).not.toHaveBeenCalled(); - expect(mocks.move.update).toHaveBeenCalledWith('123', { id: '123', oldPath: previousFailedNewPath, newPath }); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath }); - }); - - it('should fail move if copying and hash of asset and the new file do not match', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - const newPath = `/data/library/${userStub.user1.id}/2022/2022-06-19/${testAsset.originalFileName}`; - - mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' }); - mocks.storage.stat.mockResolvedValue({ size: 5000 } as Stats); - mocks.crypto.hashFile.mockResolvedValue(Buffer.from('different-hash', 'utf8')); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(testAsset); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: testAsset.id, - pathType: AssetPathType.Original, - oldPath: testAsset.originalPath, - newPath, - }); - - await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(testAsset.id); - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(1); - expect(mocks.storage.stat).toHaveBeenCalledWith(newPath); - expect(mocks.move.create).toHaveBeenCalledWith({ - entityId: testAsset.id, - pathType: AssetPathType.Original, - oldPath: testAsset.originalPath, - newPath, - }); - expect(mocks.storage.rename).toHaveBeenCalledWith(testAsset.originalPath, newPath); - expect(mocks.storage.copyFile).toHaveBeenCalledWith(testAsset.originalPath, newPath); - expect(mocks.storage.unlink).toHaveBeenCalledWith(newPath); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - const testAsset = assetStub.storageAsset(); - - it.each` - failedPathChecksum | failedPathSize | reason - ${testAsset.checksum} | ${500} | ${'file size'} - ${Buffer.from('bad checksum', 'utf8')} | ${testAsset.fileSizeInByte} | ${'checksum'} - `( - 'should fail to migrate previously failed move from previous new path when old path no longer exists if $reason validation fails', - async ({ failedPathChecksum, failedPathSize }) => { - mocks.user.get.mockResolvedValue(userStub.user1); - const previousFailedNewPath = `/data/library/${userStub.user1.id}/2023/Feb/${testAsset.originalFileName}`; - const newPath = `/data/library/${userStub.user1.id}/2023/2023-02-23/${testAsset.originalFileName}`; - - mocks.storage.checkFileExists.mockImplementation((path) => Promise.resolve(previousFailedNewPath === path)); - mocks.storage.stat.mockResolvedValue({ size: failedPathSize } as Stats); - mocks.crypto.hashFile.mockResolvedValue(failedPathChecksum); - mocks.move.getByEntity.mockResolvedValue({ - id: '123', - entityId: testAsset.id, - pathType: AssetPathType.Original, - oldPath: testAsset.originalPath, - newPath: previousFailedNewPath, - }); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValue(testAsset); - mocks.move.update.mockResolvedValue({ - id: '123', - entityId: testAsset.id, - pathType: AssetPathType.Original, - oldPath: previousFailedNewPath, - newPath, - }); - - await expect(sut.handleMigrationSingle({ id: testAsset.id })).resolves.toBe(JobStatus.Success); - - expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(testAsset.id); - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(3); - expect(mocks.storage.stat).toHaveBeenCalledWith(previousFailedNewPath); - expect(mocks.storage.rename).not.toHaveBeenCalled(); - expect(mocks.storage.copyFile).not.toHaveBeenCalled(); - expect(mocks.move.update).not.toHaveBeenCalled(); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }, - ); - }); - - describe('handle template migration', () => { - it('should handle no assets', async () => { - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([])); - mocks.user.getList.mockResolvedValue([]); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - }); - - it('should handle an asset with a duplicate destination', async () => { - const asset = assetStub.storageAsset(); - const oldPath = asset.originalPath; - const newPath = `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`; - const newPath2 = newPath.replace('.jpg', '+1.jpg'); - - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([userStub.user1]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath, - newPath, - }); - - mocks.storage.checkFileExists.mockResolvedValueOnce(true); - mocks.storage.checkFileExists.mockResolvedValueOnce(false); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath2 }); - expect(mocks.user.getList).toHaveBeenCalled(); - }); - - it('should skip when an asset already matches the template', async () => { - const asset = assetStub.storageAsset({ originalPath: '/data/library/user-id/2023/2023-02-23/asset-id.jpg' }); - - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([userStub.user1]); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).not.toHaveBeenCalled(); - expect(mocks.storage.copyFile).not.toHaveBeenCalled(); - expect(mocks.storage.checkFileExists).not.toHaveBeenCalledTimes(2); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should skip when an asset is probably a duplicate', async () => { - const asset = assetStub.storageAsset({ originalPath: '/data/library/user-id/2023/2023-02-23/asset-id+1.jpg' }); - - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([userStub.user1]); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).not.toHaveBeenCalled(); - expect(mocks.storage.copyFile).not.toHaveBeenCalled(); - expect(mocks.storage.checkFileExists).not.toHaveBeenCalledTimes(2); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should move an asset', async () => { - const asset = assetStub.storageAsset(); - const oldPath = asset.originalPath; - const newPath = `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`; - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([userStub.user1]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: assetStub.image.id, - pathType: AssetPathType.Original, - oldPath: assetStub.image.originalPath, - newPath, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith(oldPath, newPath); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath }); - }); - - it('should use the user storage label', async () => { - const user = factory.userAdmin({ storageLabel: 'label-1' }); - const asset = assetStub.storageAsset({ ownerId: user.id }); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([user]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: asset.originalPath, - newPath: `/data/library/${user.storageLabel}/2023/2023-02-23/${asset.originalFileName}`, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - '/original/path.jpg', - expect.stringContaining(`/data/library/${user.storageLabel}/2022/2022-06-19/${asset.originalFileName}`), - ); - expect(mocks.asset.update).toHaveBeenCalledWith({ - id: asset.id, - originalPath: expect.stringContaining( - `/data/library/${user.storageLabel}/2022/2022-06-19/${asset.originalFileName}`, - ), - }); - }); - - it('should copy the file if rename fails due to EXDEV (rename across filesystems)', async () => { - const asset = assetStub.storageAsset({ originalPath: '/path/to/original.jpg', fileSizeInByte: 5000 }); - const oldPath = asset.originalPath; - const newPath = `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`; - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' }); - mocks.user.getList.mockResolvedValue([userStub.user1]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath, - newPath, - }); - mocks.storage.stat.mockResolvedValueOnce({ - atime: new Date(), - mtime: new Date(), - } as Stats); - mocks.storage.stat.mockResolvedValueOnce({ - size: 5000, - } as Stats); - mocks.storage.stat.mockResolvedValueOnce({ - size: 5000, - atime: new Date(), - mtime: new Date(), - } as Stats); - mocks.crypto.hashFile.mockResolvedValue(asset.checksum); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith(oldPath, newPath); - expect(mocks.storage.copyFile).toHaveBeenCalledWith(oldPath, newPath); - expect(mocks.storage.stat).toHaveBeenCalledWith(oldPath); - expect(mocks.storage.stat).toHaveBeenCalledWith(newPath); - expect(mocks.storage.utimes).toHaveBeenCalledWith(newPath, expect.any(Date), expect.any(Date)); - expect(mocks.storage.unlink).toHaveBeenCalledWith(oldPath); - expect(mocks.storage.unlink).toHaveBeenCalledTimes(1); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: asset.id, originalPath: newPath }); - }); - - it('should not update the database if the move fails due to incorrect newPath filesize', async () => { - const asset = assetStub.storageAsset(); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.storage.rename.mockRejectedValue({ code: 'EXDEV' }); - mocks.user.getList.mockResolvedValue([userStub.user1]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: asset.originalPath, - newPath: `/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`, - }); - mocks.storage.stat.mockResolvedValue({ - size: 100, - } as Stats); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - '/original/path.jpg', - expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`), - ); - expect(mocks.storage.copyFile).toHaveBeenCalledWith( - '/original/path.jpg', - expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`), - ); - expect(mocks.storage.stat).toHaveBeenCalledWith( - expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`), - ); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should not update the database if the move fails', async () => { - const asset = assetStub.storageAsset(); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.storage.rename.mockRejectedValue(new Error('Read only system')); - mocks.storage.copyFile.mockRejectedValue(new Error('Read only system')); - mocks.move.create.mockResolvedValue({ - id: 'move-123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: asset.originalPath, - newPath: '', - }); - mocks.user.getList.mockResolvedValue([userStub.user1]); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - '/original/path.jpg', - expect.stringContaining(`/data/library/user-id/2022/2022-06-19/${asset.originalFileName}`), - ); - expect(mocks.asset.update).not.toHaveBeenCalled(); - }); - - it('should migrate live photo motion video alongside the still image', async () => { - const newMotionPicturePath = `/data/library/${motionAsset.ownerId}/2022/2022-06-19/${motionAsset.originalFileName}`; - const newStillPicturePath = `/data/library/${stillAsset.ownerId}/2022/2022-06-19/${stillAsset.originalFileName}`; - - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([stillAsset])); - mocks.user.getList.mockResolvedValue([userStub.user1]); - mocks.assetJob.getForStorageTemplateJob.mockResolvedValueOnce(motionAsset); - - mocks.move.create.mockResolvedValueOnce({ - id: '123', - entityId: stillAsset.id, - pathType: AssetPathType.Original, - oldPath: stillAsset.originalPath, - newPath: newStillPicturePath, - }); - - mocks.move.create.mockResolvedValueOnce({ - id: '124', - entityId: motionAsset.id, - pathType: AssetPathType.Original, - oldPath: motionAsset.originalPath, - newPath: newMotionPicturePath, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.assetJob.getForStorageTemplateJob).toHaveBeenCalledWith(motionAsset.id); - expect(mocks.storage.checkFileExists).toHaveBeenCalledTimes(2); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: stillAsset.id, originalPath: newStillPicturePath }); - expect(mocks.asset.update).toHaveBeenCalledWith({ id: motionAsset.id, originalPath: newMotionPicturePath }); - }); - }); - - describe('file rename correctness', () => { - it('should not create double extensions when filename has lower extension', async () => { - const user = factory.userAdmin({ storageLabel: 'label-1' }); - const asset = assetStub.storageAsset({ - ownerId: user.id, - originalPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.heic`, - originalFileName: 'IMG_7065.HEIC', - }); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([user]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.heic`, - newPath: `/data/library/${user.id}/2023/2023-02-23/IMG_7065.heic`, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.heic`), - expect.stringContaining(`/data/library/${user.storageLabel}/2022/2022-06-19/IMG_7065.heic`), - ); - }); - - it('should not create double extensions when filename has uppercase extension', async () => { - const user = factory.userAdmin(); - const asset = assetStub.storageAsset({ - ownerId: user.id, - originalPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`, - originalFileName: 'IMG_7065.HEIC', - }); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([user]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`, - newPath: `/data/library/${user.id}/2023/2023-02-23/IMG_7065.heic`, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`), - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.heic`), - ); - }); - - it('should normalize the filename to lowercase (JPEG > jpg)', async () => { - const user = factory.userAdmin(); - const asset = assetStub.storageAsset({ - ownerId: user.id, - originalPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`, - originalFileName: 'IMG_7065.JPEG', - }); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([user]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`, - newPath: `/data/library/${user.id}/2023/2023-02-23/IMG_7065.jpg`, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`), - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.jpg`), - ); - }); - - it('should normalize the filename to lowercase (JPG > jpg)', async () => { - const user = factory.userAdmin(); - const asset = assetStub.storageAsset({ - ownerId: user.id, - originalPath: '/data/library/user-id/2022/2022-06-19/IMG_7065.JPG', - originalFileName: 'IMG_7065.JPG', - }); - mocks.assetJob.streamForStorageTemplateJob.mockReturnValue(makeStream([asset])); - mocks.user.getList.mockResolvedValue([user]); - mocks.move.create.mockResolvedValue({ - id: '123', - entityId: asset.id, - pathType: AssetPathType.Original, - oldPath: `/data/library/${user.id}/2022/2022-06-19/IMG_7065.JPG`, - newPath: `/data/library/${user.id}/2023/2023-02-23/IMG_7065.jpg`, - }); - - await sut.handleMigration(); - - expect(mocks.assetJob.streamForStorageTemplateJob).toHaveBeenCalled(); - expect(mocks.storage.rename).toHaveBeenCalledWith( - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.JPG`), - expect.stringContaining(`/data/library/${user.id}/2022/2022-06-19/IMG_7065.jpg`), - ); - }); - }); -}); diff --git a/server/src/services/storage-template.service.ts b/server/src/services/storage-template.service.ts deleted file mode 100644 index d5020a9c5e..0000000000 --- a/server/src/services/storage-template.service.ts +++ /dev/null @@ -1,421 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import handlebar from 'handlebars'; -import { DateTime } from 'luxon'; -import path from 'node:path'; -import sanitize from 'sanitize-filename'; -import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent, OnJob } from 'src/decorators'; -import { SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; -import { - AssetFileType, - AssetPathType, - AssetType, - DatabaseLock, - JobName, - JobStatus, - QueueName, - StorageFolder, -} from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { JobOf, StorageAsset } from 'src/types'; -import { getAssetFile } from 'src/utils/asset.util'; -import { getLivePhotoMotionFilename } from 'src/utils/file'; - -const storageTokens = { - secondOptions: ['s', 'ss', 'SSS'], - minuteOptions: ['m', 'mm'], - dayOptions: ['d', 'dd'], - weekOptions: ['W', 'WW'], - hourOptions: ['h', 'hh', 'H', 'HH'], - yearOptions: ['y', 'yy'], - monthOptions: ['M', 'MM', 'MMM', 'MMMM'], -}; - -const storagePresets = [ - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}-{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{MM}}/{{filename}}', - '{{y}}/{{#if album}}{{album}}{{else}}Other/{{MM}}{{/if}}/{{filename}}', - '{{#if album}}{{album-startDate-y}}/{{album}}{{else}}{{y}}/Other/{{MM}}{{/if}}/{{filename}}', - '{{y}}/{{MMM}}/{{filename}}', - '{{y}}/{{MMMM}}/{{filename}}', - '{{y}}/{{MM}}/{{dd}}/{{filename}}', - '{{y}}/{{MMMM}}/{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMM}}-{{dd}}/{{filename}}', - '{{y}}-{{MMMM}}-{{dd}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}/{{filename}}', - '{{y}}/{{y}}-{{WW}}/{{filename}}', - '{{y}}/{{y}}-{{MM}}-{{dd}}/{{assetId}}', - '{{y}}/{{y}}-{{MM}}/{{assetId}}', - '{{y}}/{{y}}-{{WW}}/{{assetId}}', - '{{album}}/{{filename}}', - '{{make}}/{{model}}/{{lensModel}}/{{filename}}', -]; - -export interface MoveAssetMetadata { - storageLabel: string | null; - filename: string; -} - -interface RenderMetadata { - asset: StorageAsset; - filename: string; - extension: string; - albumName: string | null; - albumStartDate: Date | null; - albumEndDate: Date | null; - make: string | null; - model: string | null; - lensModel: string | null; -} - -@Injectable() -export class StorageTemplateService extends BaseService { - private _template: { - compiled: HandlebarsTemplateDelegate; - raw: string; - needsAlbum: boolean; - needsAlbumMetadata: boolean; - } | null = null; - - private get template() { - if (!this._template) { - throw new Error('Template not initialized'); - } - return this._template; - } - - @OnEvent({ name: 'ConfigInit' }) - onConfigInit({ newConfig }: ArgOf<'ConfigInit'>) { - const template = newConfig.storageTemplate.template; - if (!this._template || template !== this.template.raw) { - this.logger.debug(`Compiling new storage template: ${template}`); - this._template = this.compile(template); - } - } - - @OnEvent({ name: 'ConfigUpdate', server: true }) - onConfigUpdate({ newConfig }: ArgOf<'ConfigUpdate'>) { - this.onConfigInit({ newConfig }); - } - - @OnEvent({ name: 'ConfigValidate' }) - onConfigValidate({ newConfig }: ArgOf<'ConfigValidate'>) { - try { - const { compiled } = this.compile(newConfig.storageTemplate.template); - this.render(compiled, { - asset: { - fileCreatedAt: new Date(), - originalPath: '/upload/test/IMG_123.jpg', - type: AssetType.Image, - id: 'd587e44b-f8c0-4832-9ba3-43268bbf5d4e', - } as StorageAsset, - filename: 'IMG_123', - extension: 'jpg', - albumName: 'album', - albumStartDate: new Date(), - albumEndDate: new Date(), - make: 'FUJIFILM', - model: 'X-T50', - lensModel: 'XF27mm F2.8 R WR', - }); - } catch (error) { - this.logger.warn(`Storage template validation failed: ${JSON.stringify(error)}`); - throw new Error(`Invalid storage template: ${error}`); - } - } - - getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { - return { ...storageTokens, presetOptions: storagePresets }; - } - - @OnEvent({ name: 'AssetMetadataExtracted' }) - async onAssetMetadataExtracted({ source, assetId }: ArgOf<'AssetMetadataExtracted'>) { - await this.jobRepository.queue({ name: JobName.StorageTemplateMigrationSingle, data: { source, id: assetId } }); - } - - @OnJob({ name: JobName.StorageTemplateMigrationSingle, queue: QueueName.StorageTemplateMigration }) - async handleMigrationSingle({ id }: JobOf): Promise { - const config = await this.getConfig({ withCache: true }); - const storageTemplateEnabled = config.storageTemplate.enabled; - if (!storageTemplateEnabled) { - return JobStatus.Skipped; - } - - const asset = await this.assetJobRepository.getForStorageTemplateJob(id); - if (!asset) { - return JobStatus.Failed; - } - - const user = await this.userRepository.get(asset.ownerId, {}); - const storageLabel = user?.storageLabel || null; - const filename = asset.originalFileName || asset.id; - await this.moveAsset(asset, { storageLabel, filename }); - - // move motion part of live photo - if (asset.livePhotoVideoId) { - const livePhotoVideo = await this.assetJobRepository.getForStorageTemplateJob(asset.livePhotoVideoId); - if (!livePhotoVideo) { - return JobStatus.Failed; - } - const motionFilename = getLivePhotoMotionFilename(filename, livePhotoVideo.originalPath); - await this.moveAsset(livePhotoVideo, { storageLabel, filename: motionFilename }); - } - return JobStatus.Success; - } - - @OnJob({ name: JobName.StorageTemplateMigration, queue: QueueName.StorageTemplateMigration }) - async handleMigration(): Promise { - this.logger.log('Starting storage template migration'); - const { storageTemplate } = await this.getConfig({ withCache: true }); - const { enabled } = storageTemplate; - if (!enabled) { - this.logger.log('Storage template migration disabled, skipping'); - return JobStatus.Skipped; - } - - await this.moveRepository.cleanMoveHistory(); - - const assets = this.assetJobRepository.streamForStorageTemplateJob(); - const users = await this.userRepository.getList(); - - for await (const asset of assets) { - const user = users.find((user) => user.id === asset.ownerId); - const storageLabel = user?.storageLabel || null; - const filename = asset.originalFileName || asset.id; - await this.moveAsset(asset, { storageLabel, filename }); - - // move motion part of live photo - if (asset.livePhotoVideoId) { - const livePhotoVideo = await this.assetJobRepository.getForStorageTemplateJob(asset.livePhotoVideoId); - if (livePhotoVideo) { - const motionFilename = getLivePhotoMotionFilename(filename, livePhotoVideo.originalPath); - await this.moveAsset(livePhotoVideo, { storageLabel, filename: motionFilename }); - } - } - } - - this.logger.debug('Cleaning up empty directories...'); - const libraryFolder = StorageCore.getBaseFolder(StorageFolder.Library); - await this.storageRepository.removeEmptyDirs(libraryFolder); - - this.logger.log('Finished storage template migration'); - - return JobStatus.Success; - } - - @OnEvent({ name: 'AssetDelete' }) - async handleMoveHistoryCleanup({ assetId }: ArgOf<'AssetDelete'>) { - this.logger.debug(`Cleaning up move history for asset ${assetId}`); - await this.moveRepository.cleanMoveHistorySingle(assetId); - } - - async moveAsset(asset: StorageAsset, metadata: MoveAssetMetadata) { - if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) { - // External assets are not affected by storage template - // TODO: shouldn't this only apply to external assets? - return; - } - - return this.databaseRepository.withLock(DatabaseLock.StorageTemplateMigration, async () => { - const { id, originalPath, checksum, fileSizeInByte } = asset; - const oldPath = originalPath; - const newPath = await this.getTemplatePath(asset, metadata); - - if (!fileSizeInByte) { - this.logger.error(`Asset ${id} missing exif info, skipping storage template migration`); - return; - } - - try { - await this.storageCore.moveFile({ - entityId: id, - pathType: AssetPathType.Original, - oldPath, - newPath, - assetInfo: { sizeInBytes: fileSizeInByte, checksum }, - }); - - const sidecarPath = getAssetFile(asset.files, AssetFileType.Sidecar, { isEdited: false })?.path; - if (sidecarPath) { - await this.storageCore.moveFile({ - entityId: id, - pathType: AssetFileType.Sidecar, - oldPath: sidecarPath, - newPath: `${newPath}.xmp`, - }); - } - } catch (error: any) { - this.logger.error(`Problem applying storage template`, error?.stack, { id, oldPath, newPath }); - } - }); - } - - private async getTemplatePath(asset: StorageAsset, metadata: MoveAssetMetadata): Promise { - const { storageLabel, filename } = metadata; - - try { - const filenameWithoutExtension = path.basename(filename, path.extname(filename)); - - const source = asset.originalPath; - let extension = path.extname(source).split('.').pop() as string; - const sanitized = sanitize(path.basename(filenameWithoutExtension, `.${extension}`)); - extension = extension?.toLowerCase(); - const rootPath = StorageCore.getLibraryFolder({ id: asset.ownerId, storageLabel }); - - switch (extension) { - case 'jpeg': - case 'jpe': { - extension = 'jpg'; - break; - } - case 'tif': { - extension = 'tiff'; - break; - } - case '3gpp': { - extension = '3gp'; - break; - } - case 'mpeg': - case 'mpe': { - extension = 'mpg'; - break; - } - case 'm2ts': - case 'm2t': { - extension = 'mts'; - break; - } - } - - let albumName = null; - let albumStartDate = null; - let albumEndDate = null; - if (this.template.needsAlbum) { - const albums = await this.albumRepository.getByAssetId(asset.ownerId, asset.id); - const album = albums?.[0]; - if (album) { - albumName = album.albumName || null; - - if (this.template.needsAlbumMetadata) { - const [metadata] = await this.albumRepository.getMetadataForIds([album.id]); - albumStartDate = metadata?.startDate || null; - albumEndDate = metadata?.endDate || null; - } - } - } - - const storagePath = this.render(this.template.compiled, { - asset, - filename: sanitized, - extension, - albumName, - albumStartDate, - albumEndDate, - make: asset.make, - model: asset.model, - lensModel: asset.lensModel, - }); - const fullPath = path.normalize(path.join(rootPath, storagePath)); - let destination = `${fullPath}.${extension}`; - - if (!fullPath.startsWith(rootPath)) { - this.logger.warn(`Skipped attempt to access an invalid path: ${fullPath}. Path should start with ${rootPath}`); - return source; - } - - if (source === destination) { - return source; - } - - /** - * In case of migrating duplicate filename to a new path, we need to check if it is already migrated - * Due to the mechanism of appending +1, +2, +3, etc to the filename - * - * Example: - * Source = upload/abc/def/FullSizeRender+7.heic - * Expected Destination = upload/abc/def/FullSizeRender.heic - * - * The file is already at the correct location, but since there are other FullSizeRender.heic files in the - * destination, it was renamed to FullSizeRender+7.heic. - * - * The lines below will be used to check if the differences between the source and destination is only the - * +7 suffix, and if so, it will be considered as already migrated. - */ - if (source.startsWith(fullPath) && source.endsWith(`.${extension}`)) { - const diff = source.replace(fullPath, '').replace(`.${extension}`, ''); - const hasDuplicationAnnotation = /^\+\d+$/.test(diff); - if (hasDuplicationAnnotation) { - return source; - } - } - - let duplicateCount = 0; - - while (true) { - const exists = await this.storageRepository.checkFileExists(destination); - if (!exists) { - break; - } - - duplicateCount++; - destination = `${fullPath}+${duplicateCount}.${extension}`; - } - - return destination; - } catch (error: any) { - this.logger.error(`Unable to get template path for ${filename}: ${error}`); - return asset.originalPath; - } - } - - private compile(template: string) { - return { - raw: template, - compiled: handlebar.compile(template, { knownHelpers: undefined, strict: true }), - needsAlbum: template.includes('album'), - needsAlbumMetadata: template.includes('album-startDate') || template.includes('album-endDate'), - }; - } - - private render(template: HandlebarsTemplateDelegate, options: RenderMetadata) { - const { filename, extension, asset, albumName, albumStartDate, albumEndDate, make, model, lensModel } = options; - const substitutions: Record = { - filename, - ext: extension, - filetype: asset.type == AssetType.Image ? 'IMG' : 'VID', - filetypefull: asset.type == AssetType.Image ? 'IMAGE' : 'VIDEO', - assetId: asset.id, - assetIdShort: asset.id.slice(-12), - //just throw into the root if it doesn't belong to an album - album: (albumName && sanitize(albumName.replaceAll(/\.+/g, ''))) || '', - make: make ?? '', - model: model ?? '', - lensModel: lensModel ?? '', - }; - - const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const zone = asset.timeZone || systemTimeZone; - const dt = DateTime.fromJSDate(asset.fileCreatedAt, { zone }); - - for (const token of Object.values(storageTokens).flat()) { - substitutions[token] = dt.toFormat(token); - if (albumName) { - // Use system time zone for album dates to ensure all assets get the exact same date. - substitutions['album-startDate-' + token] = albumStartDate - ? DateTime.fromJSDate(albumStartDate, { zone: systemTimeZone }).toFormat(token) - : ''; - substitutions['album-endDate-' + token] = albumEndDate - ? DateTime.fromJSDate(albumEndDate, { zone: systemTimeZone }).toFormat(token) - : ''; - } - } - - return template(substitutions).replaceAll(/\/{2,}/gm, '/'); - } -} diff --git a/server/src/services/storage.service.spec.ts b/server/src/services/storage.service.spec.ts deleted file mode 100644 index 3ca9cd7ce2..0000000000 --- a/server/src/services/storage.service.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { SystemMetadataKey } from 'src/enum'; -import { StorageService } from 'src/services/storage.service'; -import { ImmichStartupError } from 'src/utils/misc'; -import { mockEnvData } from 'test/repositories/config.repository.mock'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(StorageService.name, () => { - let sut: StorageService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(StorageService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onBootstrap', () => { - it('should enable mount folder checking', async () => { - mocks.systemMetadata.get.mockResolvedValue(null); - mocks.asset.getFileSamples.mockResolvedValue([]); - mocks.config.getEnv.mockReturnValue( - mockEnvData({ - storage: { - ignoreMountCheckErrors: false, - mediaLocation: '/data', - }, - }), - ); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.SystemFlags, { - mountChecks: { - backups: true, - 'encoded-video': true, - library: true, - profile: true, - thumbs: true, - upload: true, - }, - }); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/encoded-video')); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/profile')); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/thumbs')); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/upload')); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/backups')); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/encoded-video/.immich'), - expect.any(Buffer), - ); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/library/.immich'), - expect.any(Buffer), - ); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/profile/.immich'), - expect.any(Buffer), - ); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/thumbs/.immich'), - expect.any(Buffer), - ); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/upload/.immich'), - expect.any(Buffer), - ); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/backups/.immich'), - expect.any(Buffer), - ); - }); - - it('should enable mount folder checking for a new folder type', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - mountChecks: { - backups: false, - 'encoded-video': true, - library: false, - profile: true, - thumbs: true, - upload: true, - }, - }); - mocks.asset.getFileSamples.mockResolvedValue([]); - mocks.config.getEnv.mockReturnValue( - mockEnvData({ - storage: { - ignoreMountCheckErrors: false, - mediaLocation: '/data', - }, - }), - ); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.SystemFlags, { - mountChecks: { - backups: true, - 'encoded-video': true, - library: true, - profile: true, - thumbs: true, - upload: true, - }, - }); - expect(mocks.storage.mkdirSync).toHaveBeenCalledTimes(2); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/library')); - expect(mocks.storage.mkdirSync).toHaveBeenCalledWith(expect.stringContaining('/data/backups')); - expect(mocks.storage.createFile).toHaveBeenCalledTimes(2); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/library/.immich'), - expect.any(Buffer), - ); - expect(mocks.storage.createFile).toHaveBeenCalledWith( - expect.stringContaining('/data/backups/.immich'), - expect.any(Buffer), - ); - }); - - it('should throw an error if .immich is missing', async () => { - mocks.systemMetadata.get.mockResolvedValue({ mountChecks: { upload: true } }); - mocks.storage.readFile.mockRejectedValue(new Error("ENOENT: no such file or directory, open '/app/.immich'")); - - await expect(sut.onBootstrap()).rejects.toThrow('Failed to read'); - - expect(mocks.storage.createOrOverwriteFile).not.toHaveBeenCalled(); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - }); - - it('should throw an error if .immich is present but read-only', async () => { - mocks.systemMetadata.get.mockResolvedValue({ mountChecks: { upload: true } }); - mocks.storage.overwriteFile.mockRejectedValue( - new Error("ENOENT: no such file or directory, open '/app/.immich'"), - ); - - await expect(sut.onBootstrap()).rejects.toThrow('Failed to write'); - - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - }); - - it('should skip mount file creation if file already exists', async () => { - const error = new Error('Error creating file') as any; - error.code = 'EEXIST'; - mocks.systemMetadata.get.mockResolvedValue({ mountChecks: {} }); - mocks.storage.createFile.mockRejectedValue(error); - mocks.asset.getFileSamples.mockResolvedValue([]); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.logger.warn).toHaveBeenCalledWith('Found existing mount file, skipping creation'); - }); - - it('should throw an error if mount file could not be created', async () => { - mocks.systemMetadata.get.mockResolvedValue({ mountChecks: {} }); - mocks.storage.createFile.mockRejectedValue(new Error('Error creating file')); - - await expect(sut.onBootstrap()).rejects.toBeInstanceOf(ImmichStartupError); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - }); - - it('should startup if checks are disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ mountChecks: { upload: true } }); - mocks.config.getEnv.mockReturnValue( - mockEnvData({ - storage: { ignoreMountCheckErrors: true }, - }), - ); - mocks.asset.getFileSamples.mockResolvedValue([]); - mocks.storage.overwriteFile.mockRejectedValue( - new Error("ENOENT: no such file or directory, open '/app/.immich'"), - ); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.systemMetadata.set).not.toHaveBeenCalledWith(SystemMetadataKey.SystemFlags, expect.anything()); - }); - }); - - describe('handleDeleteFiles', () => { - it('should handle null values', async () => { - await sut.handleDeleteFiles({ files: [undefined, null] }); - - expect(mocks.storage.unlink).not.toHaveBeenCalled(); - }); - - it('should handle an error removing a file', async () => { - mocks.storage.unlink.mockRejectedValue(new Error('something-went-wrong')); - - await sut.handleDeleteFiles({ files: ['path/to/something'] }); - - expect(mocks.storage.unlink).toHaveBeenCalledWith('path/to/something'); - }); - - it('should remove the file', async () => { - await sut.handleDeleteFiles({ files: ['path/to/something'] }); - - expect(mocks.storage.unlink).toHaveBeenCalledWith('path/to/something'); - }); - }); -}); diff --git a/server/src/services/storage.service.ts b/server/src/services/storage.service.ts deleted file mode 100644 index 71cf0d0ce8..0000000000 --- a/server/src/services/storage.service.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { join } from 'node:path'; -import { StorageCore } from 'src/cores/storage.core'; -import { OnEvent, OnJob } from 'src/decorators'; -import { - BootstrapEventPriority, - DatabaseLock, - JobName, - JobStatus, - QueueName, - StorageFolder, - SystemMetadataKey, -} from 'src/enum'; -import { BaseService } from 'src/services/base.service'; -import { JobOf, SystemFlags } from 'src/types'; -import { ImmichStartupError } from 'src/utils/misc'; - -const docsMessage = `Please see https://docs.immich.app/administration/system-integrity#folder-checks for more information.`; - -@Injectable() -export class StorageService extends BaseService { - private detectMediaLocation(): string { - const envData = this.configRepository.getEnv(); - if (envData.storage.mediaLocation) { - return envData.storage.mediaLocation; - } - - const targets: string[] = []; - const candidates = ['/data', '/usr/src/app/upload']; - - for (const candidate of candidates) { - const exists = this.storageRepository.existsSync(candidate); - if (exists) { - targets.push(candidate); - } - } - - if (targets.length === 1) { - return targets[0]; - } - - return '/usr/src/app/upload'; - } - - @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.StorageService }) - async onBootstrap() { - StorageCore.setMediaLocation(this.detectMediaLocation()); - - await this.databaseRepository.withLock(DatabaseLock.SystemFileMounts, async () => { - const flags = - (await this.systemMetadataRepository.get(SystemMetadataKey.SystemFlags)) || - ({ mountChecks: {} } as SystemFlags); - - if (!flags.mountChecks) { - flags.mountChecks = {}; - } - - let updated = false; - - this.logger.log(`Verifying system mount folder checks, current state: ${JSON.stringify(flags)}`); - - try { - // check each folder exists and is writable - for (const folder of Object.values(StorageFolder)) { - if (!flags.mountChecks[folder]) { - this.logger.log(`Writing initial mount file for the ${folder} folder`); - await this.createMountFile(folder); - } - - await this.verifyReadAccess(folder); - await this.verifyWriteAccess(folder); - - if (!flags.mountChecks[folder]) { - flags.mountChecks[folder] = true; - updated = true; - } - } - - if (updated) { - await this.systemMetadataRepository.set(SystemMetadataKey.SystemFlags, flags); - this.logger.log('Successfully enabled system mount folders checks'); - } - - this.logger.log('Successfully verified system mount folder checks'); - } catch (error) { - const envData = this.configRepository.getEnv(); - if (envData.storage.ignoreMountCheckErrors) { - this.logger.error(error as Error); - this.logger.warn('Ignoring mount folder errors'); - } else { - throw error; - } - } - }); - - await this.databaseRepository.withLock(DatabaseLock.MediaLocation, async () => { - const current = StorageCore.getMediaLocation(); - const samples = await this.assetRepository.getFileSamples(); - const savedValue = await this.systemMetadataRepository.get(SystemMetadataKey.MediaLocation); - if (samples.length > 0) { - const path = samples[0].path; - - let previous = savedValue?.location || ''; - - if (!previous && this.configRepository.getEnv().storage.mediaLocation) { - previous = current; - } - - if (!previous) { - previous = path.startsWith('upload/') ? 'upload' : '/usr/src/app/upload'; - } - - if (previous !== current) { - this.logger.log(`Media location changed (from=${previous}, to=${current})`); - - if (!path.startsWith(previous)) { - throw new Error( - 'Detected an inconsistent media location. For more information, see https://docs.immich.app/errors#inconsistent-media-location', - ); - } - - this.logger.warn( - `Detected a change to media location, performing an automatic migration of file paths from ${previous} to ${current}, this may take awhile`, - ); - await this.databaseRepository.migrateFilePaths(previous, current); - } - } - - // Only set MediaLocation in systemMetadataRepository if needed - if (savedValue?.location !== current) { - await this.systemMetadataRepository.set(SystemMetadataKey.MediaLocation, { location: current }); - } - }); - } - - @OnJob({ name: JobName.FileDelete, queue: QueueName.BackgroundTask }) - async handleDeleteFiles(job: JobOf): Promise { - const { files } = job; - - // TODO: one job per file - for (const file of files) { - if (!file) { - continue; - } - - try { - await this.storageRepository.unlink(file); - } catch (error: any) { - this.logger.warn('Unable to remove file from disk', error); - } - } - - return JobStatus.Success; - } - - private async verifyReadAccess(folder: StorageFolder) { - const { internalPath, externalPath } = this.getMountFilePaths(folder); - try { - await this.storageRepository.readFile(internalPath); - } catch (error) { - this.logger.error(`Failed to read (${internalPath}): ${error}`); - throw new ImmichStartupError(`Failed to read: "${externalPath} (${internalPath}) - ${docsMessage}"`); - } - } - - private async createMountFile(folder: StorageFolder) { - const { folderPath, internalPath, externalPath } = this.getMountFilePaths(folder); - try { - this.storageRepository.mkdirSync(folderPath); - await this.storageRepository.createFile(internalPath, Buffer.from(`${Date.now()}`)); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'EEXIST') { - this.logger.warn('Found existing mount file, skipping creation'); - return; - } - this.logger.error(`Failed to create ${internalPath}: ${error}`); - throw new ImmichStartupError(`Failed to create "${externalPath} - ${docsMessage}"`); - } - } - - private async verifyWriteAccess(folder: StorageFolder) { - const { internalPath, externalPath } = this.getMountFilePaths(folder); - try { - await this.storageRepository.overwriteFile(internalPath, Buffer.from(`${Date.now()}`)); - } catch (error) { - this.logger.error(`Failed to write ${internalPath}: ${error}`); - throw new ImmichStartupError(`Failed to write "${externalPath} - ${docsMessage}"`); - } - } - - private getMountFilePaths(folder: StorageFolder) { - const folderPath = StorageCore.getBaseFolder(folder); - const internalPath = join(folderPath, '.immich'); - const externalPath = `/${folder}/.immich`; - - return { folderPath, internalPath, externalPath }; - } -} diff --git a/server/src/services/sync.service.spec.ts b/server/src/services/sync.service.spec.ts deleted file mode 100644 index 5b50340a9f..0000000000 --- a/server/src/services/sync.service.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { SyncService } from 'src/services/sync.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const untilDate = new Date(2024); -const mapAssetOpts = { auth: authStub.user1, stripMetadata: false, withStack: true }; - -describe(SyncService.name, () => { - let sut: SyncService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SyncService)); - }); - - it('should exist', () => { - expect(sut).toBeDefined(); - }); - - describe('getAllAssetsForUserFullSync', () => { - it('should return a list of all assets owned by the user', async () => { - mocks.asset.getAllForUserFullSync.mockResolvedValue([assetStub.external, assetStub.hasEncodedVideo]); - await expect(sut.getFullSync(authStub.user1, { limit: 2, updatedUntil: untilDate })).resolves.toEqual([ - mapAsset(assetStub.external, mapAssetOpts), - mapAsset(assetStub.hasEncodedVideo, mapAssetOpts), - ]); - expect(mocks.asset.getAllForUserFullSync).toHaveBeenCalledWith({ - ownerId: authStub.user1.user.id, - updatedUntil: untilDate, - limit: 2, - }); - }); - }); - - describe('getChangesForDeltaSync', () => { - it('should return a response requiring a full sync when partners are out of sync', async () => { - const partner = factory.partner(); - const auth = factory.auth({ user: { id: partner.sharedWithId } }); - - mocks.partner.getAll.mockResolvedValue([partner]); - - await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [auth.user.id] }), - ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); - - expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(0); - expect(mocks.audit.getAfter).toHaveBeenCalledTimes(0); - }); - - it('should return a response requiring a full sync when last sync was too long ago', async () => { - mocks.partner.getAll.mockResolvedValue([]); - await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(2000), userIds: [authStub.user1.user.id] }), - ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); - expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(0); - expect(mocks.audit.getAfter).toHaveBeenCalledTimes(0); - }); - - it('should return a response requiring a full sync when there are too many changes', async () => { - mocks.partner.getAll.mockResolvedValue([]); - mocks.asset.getChangedDeltaSync.mockResolvedValue( - Array.from({ length: 10_000 }).fill(assetStub.image), - ); - await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), - ).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] }); - expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(1); - expect(mocks.audit.getAfter).toHaveBeenCalledTimes(0); - }); - - it('should return a response with changes and deletions', async () => { - mocks.partner.getAll.mockResolvedValue([]); - mocks.asset.getChangedDeltaSync.mockResolvedValue([assetStub.image1]); - mocks.audit.getAfter.mockResolvedValue([assetStub.external.id]); - await expect( - sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }), - ).resolves.toEqual({ - needsFullSync: false, - upserted: [mapAsset(assetStub.image1, mapAssetOpts)], - deleted: [assetStub.external.id], - }); - expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(1); - expect(mocks.audit.getAfter).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts deleted file mode 100644 index f354a71791..0000000000 --- a/server/src/services/sync.service.ts +++ /dev/null @@ -1,918 +0,0 @@ -import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common'; -import { Insertable } from 'kysely'; -import { DateTime, Duration } from 'luxon'; -import { Writable } from 'node:stream'; -import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; -import { OnJob } from 'src/decorators'; -import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - AssetDeltaSyncDto, - AssetDeltaSyncResponseDto, - AssetFullSyncDto, - SyncAckDeleteDto, - SyncAckSetDto, - SyncAssetV1, - SyncItem, - SyncStreamDto, -} from 'src/dtos/sync.dto'; -import { - AssetVisibility, - DatabaseAction, - EntityType, - JobName, - Permission, - QueueName, - SyncEntityType, - SyncRequestType, -} from 'src/enum'; -import { SyncQueryOptions } from 'src/repositories/sync.repository'; -import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table'; -import { BaseService } from 'src/services/base.service'; -import { SyncAck } from 'src/types'; -import { getMyPartnerIds } from 'src/utils/asset.util'; -import { hexOrBufferToBase64 } from 'src/utils/bytes'; -import { setIsEqual } from 'src/utils/set'; -import { fromAck, serialize, SerializeOptions, toAck } from 'src/utils/sync'; - -type CheckpointMap = Partial>; -type AssetLike = Omit & { - checksum: Buffer; - thumbhash: Buffer | null; -}; - -const COMPLETE_ID = 'complete'; -const MAX_DAYS = 30; -const MAX_DURATION = Duration.fromObject({ days: MAX_DAYS }); - -const mapSyncAssetV1 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV1 => ({ - ...data, - checksum: hexOrBufferToBase64(checksum), - thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null, -}); - -const isEntityBackfillComplete = (createId: string, checkpoint: SyncAck | undefined): boolean => - createId === checkpoint?.updateId && checkpoint.extraId === COMPLETE_ID; - -const getStartId = (createId: string, checkpoint: SyncAck | undefined): string | undefined => - createId === checkpoint?.updateId ? checkpoint?.extraId : undefined; - -const send = (response: Writable, item: SerializeOptions) => { - response.write(serialize(item)); -}; - -const sendEntityBackfillCompleteAck = (response: Writable, ackType: SyncEntityType, id: string) => { - send(response, { type: SyncEntityType.SyncAckV1, data: {}, ackType, ids: [id, COMPLETE_ID] }); -}; - -const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] }; -export const SYNC_TYPES_ORDER = [ - SyncRequestType.AuthUsersV1, - SyncRequestType.UsersV1, - SyncRequestType.PartnersV1, - SyncRequestType.AssetsV1, - SyncRequestType.StacksV1, - SyncRequestType.PartnerAssetsV1, - SyncRequestType.PartnerStacksV1, - SyncRequestType.AlbumAssetsV1, - SyncRequestType.AlbumsV1, - SyncRequestType.AlbumUsersV1, - SyncRequestType.AlbumToAssetsV1, - SyncRequestType.AssetExifsV1, - SyncRequestType.AlbumAssetExifsV1, - SyncRequestType.PartnerAssetExifsV1, - SyncRequestType.MemoriesV1, - SyncRequestType.MemoryToAssetsV1, - SyncRequestType.PeopleV1, - SyncRequestType.AssetFacesV1, - SyncRequestType.UserMetadataV1, - SyncRequestType.AssetMetadataV1, -]; - -const throwSessionRequired = () => { - throw new ForbiddenException('Sync endpoints cannot be used with API keys'); -}; - -@Injectable() -export class SyncService extends BaseService { - getAcks(auth: AuthDto) { - const sessionId = auth.session?.id; - if (!sessionId) { - return throwSessionRequired(); - } - - return this.syncCheckpointRepository.getAll(sessionId); - } - - async setAcks(auth: AuthDto, dto: SyncAckSetDto) { - const sessionId = auth.session?.id; - if (!sessionId) { - return throwSessionRequired(); - } - - const checkpoints: Record> = {}; - for (const ack of dto.acks) { - const { type } = fromAck(ack); - if (type === SyncEntityType.SyncResetV1) { - await this.sessionRepository.resetSyncProgress(sessionId); - return; - } - // TODO proper ack validation via class validator - if (!Object.values(SyncEntityType).includes(type)) { - throw new BadRequestException(`Invalid ack type: ${type}`); - } - - // TODO pick the latest ack for each type, instead of using the last one - checkpoints[type] = { sessionId, type, ack }; - } - - await this.syncCheckpointRepository.upsertAll(Object.values(checkpoints)); - } - - async deleteAcks(auth: AuthDto, dto: SyncAckDeleteDto) { - const sessionId = auth.session?.id; - if (!sessionId) { - return throwSessionRequired(); - } - - await this.syncCheckpointRepository.deleteAll(sessionId, dto.types); - } - - async stream(auth: AuthDto, response: Writable, dto: SyncStreamDto) { - const session = auth.session; - if (!session) { - return throwSessionRequired(); - } - - if (dto.reset) { - await this.sessionRepository.resetSyncProgress(session.id); - } - - const isPendingSyncReset = await this.sessionRepository.isPendingSyncReset(session.id); - if (isPendingSyncReset) { - send(response, { type: SyncEntityType.SyncResetV1, ids: ['reset'], data: {} }); - response.end(); - return; - } - - const checkpoints = await this.syncCheckpointRepository.getAll(session.id); - const checkpointMap: CheckpointMap = Object.fromEntries(checkpoints.map(({ type, ack }) => [type, fromAck(ack)])); - - if (this.needsFullSync(checkpointMap)) { - send(response, { type: SyncEntityType.SyncResetV1, ids: ['reset'], data: {} }); - response.end(); - return; - } - - const { nowId } = await this.syncCheckpointRepository.getNow(); - const options: SyncQueryOptions = { nowId, userId: auth.user.id }; - - const handlers: Record Promise> = { - [SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap), - [SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap), - [SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap), - [SyncRequestType.AssetsV1]: () => this.syncAssetsV1(options, response, checkpointMap), - [SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(options, response, checkpointMap), - [SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(options, response, checkpointMap, session.id), - [SyncRequestType.AssetMetadataV1]: () => this.syncAssetMetadataV1(options, response, checkpointMap, auth), - [SyncRequestType.PartnerAssetExifsV1]: () => - this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id), - [SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap), - [SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id), - [SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(options, response, checkpointMap, session.id), - [SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id), - [SyncRequestType.AlbumAssetExifsV1]: () => - this.syncAlbumAssetExifsV1(options, response, checkpointMap, session.id), - [SyncRequestType.MemoriesV1]: () => this.syncMemoriesV1(options, response, checkpointMap), - [SyncRequestType.MemoryToAssetsV1]: () => this.syncMemoryAssetsV1(options, response, checkpointMap), - [SyncRequestType.StacksV1]: () => this.syncStackV1(options, response, checkpointMap), - [SyncRequestType.PartnerStacksV1]: () => this.syncPartnerStackV1(options, response, checkpointMap, session.id), - [SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap), - [SyncRequestType.AssetFacesV1]: async () => this.syncAssetFacesV1(options, response, checkpointMap), - [SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap), - }; - - for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) { - const handler = handlers[type]; - await handler(); - } - - send(response, { type: SyncEntityType.SyncCompleteV1, ids: [nowId], data: {} }); - - response.end(); - } - - @OnJob({ name: JobName.AuditTableCleanup, queue: QueueName.BackgroundTask }) - async onAuditTableCleanup() { - const pruneThreshold = MAX_DAYS + 1; - - await this.syncRepository.album.cleanupAuditTable(pruneThreshold); - await this.syncRepository.albumUser.cleanupAuditTable(pruneThreshold); - await this.syncRepository.albumToAsset.cleanupAuditTable(pruneThreshold); - await this.syncRepository.asset.cleanupAuditTable(pruneThreshold); - await this.syncRepository.assetFace.cleanupAuditTable(pruneThreshold); - await this.syncRepository.assetMetadata.cleanupAuditTable(pruneThreshold); - await this.syncRepository.memory.cleanupAuditTable(pruneThreshold); - await this.syncRepository.memoryToAsset.cleanupAuditTable(pruneThreshold); - await this.syncRepository.partner.cleanupAuditTable(pruneThreshold); - await this.syncRepository.person.cleanupAuditTable(pruneThreshold); - await this.syncRepository.stack.cleanupAuditTable(pruneThreshold); - await this.syncRepository.user.cleanupAuditTable(pruneThreshold); - await this.syncRepository.userMetadata.cleanupAuditTable(pruneThreshold); - } - - private needsFullSync(checkpointMap: CheckpointMap) { - const completeAck = checkpointMap[SyncEntityType.SyncCompleteV1]; - if (!completeAck) { - return false; - } - - const milliseconds = Number.parseInt(completeAck.updateId.replaceAll('-', '').slice(0, 12), 16); - - return DateTime.fromMillis(milliseconds) < DateTime.now().minus(MAX_DURATION); - } - - private async syncAuthUsersV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const upsertType = SyncEntityType.AuthUserV1; - const upserts = this.syncRepository.authUser.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, profileImagePath, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data: { ...data, hasProfileImage: !!profileImagePath } }); - } - } - - private async syncUsersV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.UserDeleteV1; - const deletes = this.syncRepository.user.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.UserV1; - const upserts = this.syncRepository.user.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, profileImagePath, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data: { ...data, hasProfileImage: !!profileImagePath } }); - } - } - - private async syncPartnersV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.PartnerDeleteV1; - const deletes = this.syncRepository.partner.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.PartnerV1; - const upserts = this.syncRepository.partner.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncAssetsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.AssetDeleteV1; - const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.AssetV1; - const upserts = this.syncRepository.asset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) }); - } - } - - private async syncPartnerAssetsV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const deleteType = SyncEntityType.PartnerAssetDeleteV1; - const deletes = this.syncRepository.partnerAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const backfillType = SyncEntityType.PartnerAssetBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const partners = await this.syncRepository.partner.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - const upsertType = SyncEntityType.PartnerAssetV1; - const upsertCheckpoint = checkpointMap[upsertType]; - if (upsertCheckpoint) { - const endId = upsertCheckpoint.updateId; - - for (const partner of partners) { - const createId = partner.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.partnerAsset.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - partner.sharedById, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { - type: backfillType, - ids: [createId, updateId], - data: mapSyncAssetV1(data), - }); - } - - sendEntityBackfillCompleteAck(response, backfillType, createId); - } - } else if (partners.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: partners.at(-1)!.createId, - }); - } - - const upserts = this.syncRepository.partnerAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) }); - } - } - - private async syncAssetExifsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const upsertType = SyncEntityType.AssetExifV1; - const upserts = this.syncRepository.assetExif.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncPartnerAssetExifsV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const backfillType = SyncEntityType.PartnerAssetExifBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const partners = await this.syncRepository.partner.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - - const upsertType = SyncEntityType.PartnerAssetExifV1; - const upsertCheckpoint = checkpointMap[upsertType]; - if (upsertCheckpoint) { - const endId = upsertCheckpoint.updateId; - - for (const partner of partners) { - const createId = partner.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.partnerAssetExif.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - partner.sharedById, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { type: backfillType, ids: [partner.createId, updateId], data }); - } - - sendEntityBackfillCompleteAck(response, backfillType, partner.createId); - } - } else if (partners.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: partners.at(-1)!.createId, - }); - } - - const upserts = this.syncRepository.partnerAssetExif.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncAlbumsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.AlbumDeleteV1; - const deletes = this.syncRepository.album.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.AlbumV1; - const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncAlbumUsersV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const deleteType = SyncEntityType.AlbumUserDeleteV1; - const deletes = this.syncRepository.albumUser.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const backfillType = SyncEntityType.AlbumUserBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const albums = await this.syncRepository.album.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - const upsertType = SyncEntityType.AlbumUserV1; - const upsertCheckpoint = checkpointMap[upsertType]; - if (upsertCheckpoint) { - const endId = upsertCheckpoint.updateId; - - for (const album of albums) { - const createId = album.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.albumUser.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - album.id, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { type: backfillType, ids: [createId, updateId], data }); - } - - sendEntityBackfillCompleteAck(response, backfillType, createId); - } - } else if (albums.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: albums.at(-1)!.createId, - }); - } - - const upserts = this.syncRepository.albumUser.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncAlbumAssetsV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const backfillType = SyncEntityType.AlbumAssetBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const albums = await this.syncRepository.album.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - const updateType = SyncEntityType.AlbumAssetUpdateV1; - const createType = SyncEntityType.AlbumAssetCreateV1; - const updateCheckpoint = checkpointMap[updateType]; - const createCheckpoint = checkpointMap[createType]; - if (createCheckpoint) { - const endId = createCheckpoint.updateId; - - for (const album of albums) { - const createId = album.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.albumAsset.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - album.id, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV1(data) }); - } - - sendEntityBackfillCompleteAck(response, backfillType, createId); - } - } else if (albums.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: albums.at(-1)!.createId, - }); - } - - if (createCheckpoint) { - const updates = this.syncRepository.albumAsset.getUpdates( - { ...options, ack: updateCheckpoint }, - createCheckpoint, - ); - for await (const { updateId, ...data } of updates) { - send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV1(data) }); - } - } - - const creates = this.syncRepository.albumAsset.getCreates({ ...options, ack: createCheckpoint }); - let first = true; - for await (const { updateId, ...data } of creates) { - if (first) { - send(response, { - type: SyncEntityType.SyncAckV1, - data: {}, - ackType: SyncEntityType.AlbumAssetUpdateV1, - ids: [options.nowId], - }); - first = false; - } - send(response, { type: createType, ids: [updateId], data: mapSyncAssetV1(data) }); - } - } - - private async syncAlbumAssetExifsV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const backfillType = SyncEntityType.AlbumAssetExifBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const albums = await this.syncRepository.album.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - const updateType = SyncEntityType.AlbumAssetExifUpdateV1; - const createType = SyncEntityType.AlbumAssetExifCreateV1; - const upsertCheckpoint = checkpointMap[updateType]; - const createCheckpoint = checkpointMap[createType]; - if (createCheckpoint) { - const endId = createCheckpoint.updateId; - - for (const album of albums) { - const createId = album.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.albumAssetExif.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - album.id, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { type: backfillType, ids: [createId, updateId], data }); - } - - sendEntityBackfillCompleteAck(response, backfillType, createId); - } - } else if (albums.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: albums.at(-1)!.createId, - }); - } - - if (createCheckpoint) { - const updates = this.syncRepository.albumAssetExif.getUpdates( - { ...options, ack: upsertCheckpoint }, - createCheckpoint, - ); - for await (const { updateId, ...data } of updates) { - send(response, { type: updateType, ids: [updateId], data }); - } - } - - const creates = this.syncRepository.albumAssetExif.getCreates({ ...options, ack: createCheckpoint }); - let first = true; - for await (const { updateId, ...data } of creates) { - if (first) { - send(response, { - type: SyncEntityType.SyncAckV1, - data: {}, - ackType: SyncEntityType.AlbumAssetExifUpdateV1, - ids: [options.nowId], - }); - first = false; - } - send(response, { type: createType, ids: [updateId], data }); - } - } - - private async syncAlbumToAssetsV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const deleteType = SyncEntityType.AlbumToAssetDeleteV1; - const deletes = this.syncRepository.albumToAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const backfillType = SyncEntityType.AlbumToAssetBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const albums = await this.syncRepository.album.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - const upsertType = SyncEntityType.AlbumToAssetV1; - const upsertCheckpoint = checkpointMap[upsertType]; - if (upsertCheckpoint) { - const endId = upsertCheckpoint.updateId; - - for (const album of albums) { - const createId = album.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.albumToAsset.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - album.id, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { type: backfillType, ids: [createId, updateId], data }); - } - - sendEntityBackfillCompleteAck(response, backfillType, createId); - } - } else if (albums.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: albums.at(-1)!.createId, - }); - } - - const upserts = this.syncRepository.albumToAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncMemoriesV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.MemoryDeleteV1; - const deletes = this.syncRepository.memory.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.MemoryV1; - const upserts = this.syncRepository.memory.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncMemoryAssetsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.MemoryToAssetDeleteV1; - const deletes = this.syncRepository.memoryToAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.MemoryToAssetV1; - const upserts = this.syncRepository.memoryToAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncStackV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.StackDeleteV1; - const deletes = this.syncRepository.stack.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.StackV1; - const upserts = this.syncRepository.stack.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncPartnerStackV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - sessionId: string, - ) { - const deleteType = SyncEntityType.PartnerStackDeleteV1; - const deletes = this.syncRepository.partnerStack.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const backfillType = SyncEntityType.PartnerStackBackfillV1; - const backfillCheckpoint = checkpointMap[backfillType]; - const partners = await this.syncRepository.partner.getCreatedAfter({ - ...options, - afterCreateId: backfillCheckpoint?.updateId, - }); - const upsertType = SyncEntityType.PartnerStackV1; - const upsertCheckpoint = checkpointMap[upsertType]; - if (upsertCheckpoint) { - const endId = upsertCheckpoint.updateId; - - for (const partner of partners) { - const createId = partner.createId; - if (isEntityBackfillComplete(createId, backfillCheckpoint)) { - continue; - } - - const startId = getStartId(createId, backfillCheckpoint); - const backfill = this.syncRepository.partnerStack.getBackfill( - { ...options, afterUpdateId: startId, beforeUpdateId: endId }, - partner.sharedById, - ); - - for await (const { updateId, ...data } of backfill) { - send(response, { - type: backfillType, - ids: [createId, updateId], - data, - }); - } - - sendEntityBackfillCompleteAck(response, backfillType, createId); - } - } else if (partners.length > 0) { - await this.upsertBackfillCheckpoint({ - type: backfillType, - sessionId, - createId: partners.at(-1)!.createId, - }); - } - - const upserts = this.syncRepository.partnerStack.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncPeopleV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.PersonDeleteV1; - const deletes = this.syncRepository.person.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.PersonV1; - const upserts = this.syncRepository.person.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncAssetFacesV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.AssetFaceDeleteV1; - const deletes = this.syncRepository.assetFace.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.AssetFaceV1; - const upserts = this.syncRepository.assetFace.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncUserMetadataV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { - const deleteType = SyncEntityType.UserMetadataDeleteV1; - const deletes = this.syncRepository.userMetadata.getDeletes({ ...options, ack: checkpointMap[deleteType] }); - - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.UserMetadataV1; - const upserts = this.syncRepository.userMetadata.getUpserts({ ...options, ack: checkpointMap[upsertType] }); - - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async syncAssetMetadataV1( - options: SyncQueryOptions, - response: Writable, - checkpointMap: CheckpointMap, - auth: AuthDto, - ) { - const deleteType = SyncEntityType.AssetMetadataDeleteV1; - const deletes = this.syncRepository.assetMetadata.getDeletes( - { ...options, ack: checkpointMap[deleteType] }, - auth.user.id, - ); - - for await (const { id, ...data } of deletes) { - send(response, { type: deleteType, ids: [id], data }); - } - - const upsertType = SyncEntityType.AssetMetadataV1; - const upserts = this.syncRepository.assetMetadata.getUpserts( - { ...options, ack: checkpointMap[upsertType] }, - auth.user.id, - ); - - for await (const { updateId, ...data } of upserts) { - send(response, { type: upsertType, ids: [updateId], data }); - } - } - - private async upsertBackfillCheckpoint(item: { type: SyncEntityType; sessionId: string; createId: string }) { - const { type, sessionId, createId } = item; - await this.syncCheckpointRepository.upsertAll([ - { - type, - sessionId, - ack: toAck({ - type, - updateId: createId, - extraId: COMPLETE_ID, - }), - }, - ]); - } - - async getFullSync(auth: AuthDto, dto: AssetFullSyncDto): Promise { - // mobile implementation is faster if this is a single id - const userId = dto.userId || auth.user.id; - await this.requireAccess({ auth, permission: Permission.TimelineRead, ids: [userId] }); - const assets = await this.assetRepository.getAllForUserFullSync({ - ownerId: userId, - updatedUntil: dto.updatedUntil, - lastId: dto.lastId, - limit: dto.limit, - }); - return assets.map((a) => mapAsset(a, { auth, stripMetadata: false, withStack: true })); - } - - async getDeltaSync(auth: AuthDto, dto: AssetDeltaSyncDto): Promise { - // app has not synced in the last 100 days - const duration = DateTime.now().diff(DateTime.fromJSDate(dto.updatedAfter)); - if (duration > AUDIT_LOG_MAX_DURATION) { - return FULL_SYNC; - } - - // app does not have the correct partners synced - const partnerIds = await getMyPartnerIds({ userId: auth.user.id, repository: this.partnerRepository }); - const userIds = [auth.user.id, ...partnerIds]; - if (!setIsEqual(new Set(userIds), new Set(dto.userIds))) { - return FULL_SYNC; - } - - await this.requireAccess({ auth, permission: Permission.TimelineRead, ids: dto.userIds }); - - const limit = 10_000; - const upserted = await this.assetRepository.getChangedDeltaSync({ limit, updatedAfter: dto.updatedAfter, userIds }); - - // too many changes, need to do a full sync - if (upserted.length === limit) { - return FULL_SYNC; - } - - const deleted = await this.auditRepository.getAfter(dto.updatedAfter, { - userIds, - entityType: EntityType.Asset, - action: DatabaseAction.Delete, - }); - - const result = { - needsFullSync: false, - upserted: upserted - // do not return archived assets for partner users - .filter( - (a) => - a.ownerId === auth.user.id || (a.ownerId !== auth.user.id && a.visibility === AssetVisibility.Timeline), - ) - .map((a) => - mapAsset(a, { - auth, - stripMetadata: false, - // ignore stacks for non partner users - withStack: a.ownerId === auth.user.id, - }), - ), - deleted, - }; - return result; - } -} diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts deleted file mode 100644 index 1c93c9d7d3..0000000000 --- a/server/src/services/system-config.service.spec.ts +++ /dev/null @@ -1,451 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { defaults, SystemConfig } from 'src/config'; -import { - AudioCodec, - Colorspace, - CQMode, - ImageFormat, - LogLevel, - OAuthTokenEndpointAuthMethod, - QueueName, - ToneMapping, - TranscodeHardwareAcceleration, - TranscodePolicy, - VideoCodec, - VideoContainer, -} from 'src/enum'; -import { SystemConfigService } from 'src/services/system-config.service'; -import { DeepPartial } from 'src/types'; -import { mockEnvData } from 'test/repositories/config.repository.mock'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const partialConfig = { - ffmpeg: { crf: 30 }, - oauth: { autoLaunch: true }, - trash: { days: 10 }, - user: { deleteDelay: 15 }, -} satisfies DeepPartial; - -const updatedConfig = Object.freeze({ - job: { - [QueueName.BackgroundTask]: { concurrency: 5 }, - [QueueName.SmartSearch]: { concurrency: 2 }, - [QueueName.MetadataExtraction]: { concurrency: 5 }, - [QueueName.FaceDetection]: { concurrency: 2 }, - [QueueName.Search]: { concurrency: 5 }, - [QueueName.Sidecar]: { concurrency: 5 }, - [QueueName.Library]: { concurrency: 5 }, - [QueueName.Migration]: { concurrency: 5 }, - [QueueName.ThumbnailGeneration]: { concurrency: 3 }, - [QueueName.VideoConversion]: { concurrency: 1 }, - [QueueName.Notification]: { concurrency: 5 }, - [QueueName.Ocr]: { concurrency: 1 }, - [QueueName.Workflow]: { concurrency: 5 }, - [QueueName.Editor]: { concurrency: 2 }, - }, - backup: { - database: { - enabled: true, - cronExpression: '0 02 * * *', - keepLastAmount: 14, - }, - }, - ffmpeg: { - crf: 30, - threads: 0, - preset: 'ultrafast', - targetAudioCodec: AudioCodec.Aac, - acceptedAudioCodecs: [AudioCodec.Aac, AudioCodec.Mp3, AudioCodec.LibOpus], - targetResolution: '720', - targetVideoCodec: VideoCodec.H264, - acceptedVideoCodecs: [VideoCodec.H264], - acceptedContainers: [VideoContainer.Mov, VideoContainer.Ogg, VideoContainer.Webm], - maxBitrate: '0', - bframes: -1, - refs: 0, - gopSize: 0, - temporalAQ: false, - cqMode: CQMode.Auto, - twoPass: false, - preferredHwDevice: 'auto', - transcode: TranscodePolicy.Required, - accel: TranscodeHardwareAcceleration.Disabled, - accelDecode: false, - tonemap: ToneMapping.Hable, - }, - logging: { - enabled: true, - level: LogLevel.Log, - }, - metadata: { - faces: { - import: false, - }, - }, - machineLearning: { - enabled: true, - urls: ['http://immich-machine-learning:3003'], - availabilityChecks: { - enabled: true, - interval: 30_000, - timeout: 2000, - }, - clip: { - enabled: true, - modelName: 'ViT-B-32__openai', - }, - duplicateDetection: { - enabled: true, - maxDistance: 0.01, - }, - facialRecognition: { - enabled: true, - modelName: 'buffalo_l', - minScore: 0.7, - maxDistance: 0.5, - minFaces: 3, - }, - ocr: { - enabled: true, - modelName: 'PP-OCRv5_mobile', - minDetectionScore: 0.5, - minRecognitionScore: 0.8, - maxResolution: 736, - }, - }, - map: { - enabled: true, - lightStyle: 'https://tiles.immich.cloud/v1/style/light.json', - darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json', - }, - nightlyTasks: { - startTime: '00:00', - databaseCleanup: true, - clusterNewFaces: true, - missingThumbnails: true, - generateMemories: true, - syncQuotaUsage: true, - }, - reverseGeocoding: { - enabled: true, - }, - oauth: { - autoLaunch: true, - autoRegister: true, - buttonText: 'Login with OAuth', - clientId: '', - clientSecret: '', - defaultStorageQuota: null, - enabled: false, - issuerUrl: '', - mobileOverrideEnabled: false, - mobileRedirectUri: '', - scope: 'openid email profile', - signingAlgorithm: 'RS256', - profileSigningAlgorithm: 'none', - tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.ClientSecretPost, - timeout: 30_000, - storageLabelClaim: 'preferred_username', - storageQuotaClaim: 'immich_quota', - roleClaim: 'immich_role', - }, - passwordLogin: { - enabled: true, - }, - server: { - externalDomain: '', - loginPageMessage: '', - publicUsers: true, - }, - storageTemplate: { - enabled: false, - hashVerificationEnabled: true, - template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}', - }, - image: { - thumbnail: { - size: 250, - format: ImageFormat.Webp, - quality: 80, - progressive: false, - }, - preview: { - size: 1440, - format: ImageFormat.Jpeg, - quality: 80, - progressive: false, - }, - fullsize: { enabled: false, format: ImageFormat.Jpeg, quality: 80, progressive: false }, - colorspace: Colorspace.P3, - extractEmbedded: false, - }, - newVersionCheck: { - enabled: true, - }, - trash: { - enabled: true, - days: 10, - }, - theme: { - customCss: '', - }, - library: { - scan: { - enabled: true, - cronExpression: '0 0 * * *', - }, - watch: { - enabled: false, - }, - }, - user: { - deleteDelay: 15, - }, - notifications: { - smtp: { - enabled: false, - from: '', - replyTo: '', - transport: { - host: '', - port: 587, - secure: false, - username: '', - password: '', - ignoreCert: false, - }, - }, - }, - templates: { - email: { - albumInviteTemplate: '', - welcomeTemplate: '', - albumUpdateTemplate: '', - }, - }, -}); - -describe(SystemConfigService.name, () => { - let sut: SystemConfigService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SystemConfigService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getDefaults', () => { - it('should return the default config', () => { - mocks.systemMetadata.get.mockResolvedValue(partialConfig); - - expect(sut.getDefaults()).toEqual(defaults); - expect(mocks.systemMetadata.get).not.toHaveBeenCalled(); - }); - }); - - describe('getConfig', () => { - it('should return the default config', async () => { - mocks.systemMetadata.get.mockResolvedValue({}); - - await expect(sut.getSystemConfig()).resolves.toEqual(defaults); - }); - - it('should merge the overrides', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - ffmpeg: { crf: 30 }, - oauth: { autoLaunch: true }, - trash: { days: 10 }, - user: { deleteDelay: 15 }, - }); - - await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig); - }); - - it('should load the config from a json file', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify(partialConfig)); - - await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig); - - expect(mocks.systemMetadata.readFile).toHaveBeenCalledWith('immich-config.json'); - }); - - it('should transform booleans', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { twoPass: 'false' } })); - - await expect(sut.getSystemConfig()).resolves.toMatchObject({ - ffmpeg: expect.objectContaining({ twoPass: false }), - }); - }); - - it('should transform numbers', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { threads: '42' } })); - - await expect(sut.getSystemConfig()).resolves.toMatchObject({ - ffmpeg: expect.objectContaining({ threads: 42 }), - }); - }); - - it('should accept valid cron expressions', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue( - JSON.stringify({ library: { scan: { cronExpression: '0 0 * * *' } } }), - ); - - await expect(sut.getSystemConfig()).resolves.toMatchObject({ - library: { - scan: { - enabled: true, - cronExpression: '0 0 * * *', - }, - }, - }); - }); - - it('should reject invalid cron expressions', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify({ library: { scan: { cronExpression: 'foo' } } })); - - await expect(sut.getSystemConfig()).rejects.toThrow( - 'library.scan.cronExpression has failed the following constraints: cronValidator', - ); - }); - - it('should log errors with the config file', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - - mocks.systemMetadata.readFile.mockResolvedValue(`{ "ffmpeg2": true, "ffmpeg2": true }`); - - await expect(sut.getSystemConfig()).rejects.toBeInstanceOf(Error); - - expect(mocks.systemMetadata.readFile).toHaveBeenCalledWith('immich-config.json'); - expect(mocks.logger.error).toHaveBeenCalledTimes(2); - expect(mocks.logger.error.mock.calls[0][0]).toEqual('Unable to load configuration file: immich-config.json'); - expect(mocks.logger.error.mock.calls[1][0].toString()).toEqual( - expect.stringContaining('YAMLException: duplicated mapping key (1:20)'), - ); - }); - - it('should load the config from a yaml file', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.yaml' })); - const partialConfig = ` - ffmpeg: - crf: 30 - oauth: - autoLaunch: true - trash: - days: 10 - user: - deleteDelay: 15 - `; - mocks.systemMetadata.readFile.mockResolvedValue(partialConfig); - - await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig); - - expect(mocks.systemMetadata.readFile).toHaveBeenCalledWith('immich-config.yaml'); - }); - - it('should accept an empty configuration file', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify({})); - - await expect(sut.getSystemConfig()).resolves.toEqual(defaults); - - expect(mocks.systemMetadata.readFile).toHaveBeenCalledWith('immich-config.json'); - }); - - it('should allow underscores in the machine learning url', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - const partialConfig = { machineLearning: { urls: ['immich_machine_learning'] } }; - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify(partialConfig)); - - const config = await sut.getSystemConfig(); - expect(config.machineLearning.urls).toEqual(['immich_machine_learning']); - }); - - const externalDomainTests = [ - { should: 'with a trailing slash', externalDomain: 'https://demo.immich.app/' }, - { should: 'without a trailing slash', externalDomain: 'https://demo.immich.app' }, - { should: 'with a port', externalDomain: 'https://demo.immich.app:42', result: 'https://demo.immich.app:42' }, - { - should: 'with basic auth', - externalDomain: 'https://user:password@example.com:123', - result: 'https://user:password@example.com:123', - }, - ]; - - for (const { should, externalDomain, result } of externalDomainTests) { - it(`should normalize an external domain ${should}`, async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - const partialConfig = { server: { externalDomain } }; - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify(partialConfig)); - - const config = await sut.getSystemConfig(); - expect(config.server.externalDomain).toEqual(result ?? 'https://demo.immich.app'); - }); - } - - it('should warn for unknown options in yaml', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.yaml' })); - const partialConfig = ` - unknownOption: true - `; - mocks.systemMetadata.readFile.mockResolvedValue(partialConfig); - - await sut.getSystemConfig(); - expect(mocks.logger.warn).toHaveBeenCalled(); - }); - - const tests = [ - { should: 'validate numbers', config: { ffmpeg: { crf: 'not-a-number' } } }, - { should: 'validate booleans', config: { oauth: { enabled: 'invalid' } } }, - { should: 'validate enums', config: { ffmpeg: { transcode: 'unknown' } } }, - { should: 'validate required oauth fields', config: { oauth: { enabled: true } } }, - { should: 'warn for top level unknown options', warn: true, config: { unknownOption: true } }, - { should: 'warn for nested unknown options', warn: true, config: { ffmpeg: { unknownOption: true } } }, - ]; - - for (const test of tests) { - it(`should ${test.should}`, async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify(test.config)); - - if (test.warn) { - await sut.getSystemConfig(); - expect(mocks.logger.warn).toHaveBeenCalled(); - } else { - await expect(sut.getSystemConfig()).rejects.toBeInstanceOf(Error); - } - }); - } - }); - - describe('updateConfig', () => { - it('should update the config and emit an event', async () => { - mocks.systemMetadata.get.mockResolvedValue(partialConfig); - await expect(sut.updateSystemConfig(updatedConfig)).resolves.toEqual(updatedConfig); - expect(mocks.event.emit).toHaveBeenCalledWith( - 'ConfigUpdate', - expect.objectContaining({ oldConfig: expect.any(Object), newConfig: updatedConfig }), - ); - }); - - it('should throw an error if a config file is in use', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); - mocks.systemMetadata.readFile.mockResolvedValue(JSON.stringify({})); - await expect(sut.updateSystemConfig(defaults)).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - }); - }); - - describe('getCustomCss', () => { - it('should return the default theme', async () => { - await expect(sut.getCustomCss()).resolves.toEqual(defaults.theme.customCss); - }); - }); -}); diff --git a/server/src/services/system-config.service.ts b/server/src/services/system-config.service.ts deleted file mode 100644 index ea95b4df24..0000000000 --- a/server/src/services/system-config.service.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { instanceToPlain } from 'class-transformer'; -import _ from 'lodash'; -import { defaults } from 'src/config'; -import { OnEvent } from 'src/decorators'; -import { mapConfig, SystemConfigDto } from 'src/dtos/system-config.dto'; -import { BootstrapEventPriority } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { clearConfigCache } from 'src/utils/config'; -import { toPlainObject } from 'src/utils/object'; - -@Injectable() -export class SystemConfigService extends BaseService { - @OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.SystemConfig }) - async onBootstrap() { - const config = await this.getConfig({ withCache: false }); - await this.eventRepository.emit('ConfigInit', { newConfig: config }); - - if ( - process.env.IMMICH_MACHINE_LEARNING_PING_TIMEOUT || - process.env.IMMICH_MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME - ) { - this.logger.deprecate( - 'IMMICH_MACHINE_LEARNING_PING_TIMEOUT and MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME have been moved to system config(`machineLearning.availabilityChecks`) and will be removed in a future release.', - ); - } - } - - @OnEvent({ name: 'AppShutdown' }) - onShutdown() { - this.machineLearningRepository.teardown(); - } - - async getSystemConfig(): Promise { - const config = await this.getConfig({ withCache: false }); - return mapConfig(config); - } - - getDefaults(): SystemConfigDto { - return mapConfig(defaults); - } - - @OnEvent({ name: 'ConfigInit', priority: -100 }) - onConfigInit({ newConfig: { logging, machineLearning } }: ArgOf<'ConfigInit'>) { - const { logLevel: envLevel } = this.configRepository.getEnv(); - const configLevel = logging.enabled ? logging.level : false; - const level = envLevel ?? configLevel; - this.logger.setLogLevel(level); - this.logger.log(`LogLevel=${level} ${envLevel ? '(set via IMMICH_LOG_LEVEL)' : '(set via system config)'}`); - - this.machineLearningRepository.setup(machineLearning); - } - - @OnEvent({ name: 'ConfigUpdate', server: true }) - onConfigUpdate({ newConfig }: ArgOf<'ConfigUpdate'>) { - this.onConfigInit({ newConfig }); - clearConfigCache(); - } - - @OnEvent({ name: 'ConfigValidate' }) - onConfigValidate({ newConfig, oldConfig }: ArgOf<'ConfigValidate'>) { - const { logLevel } = this.configRepository.getEnv(); - if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && logLevel) { - throw new Error('Logging cannot be changed while the environment variable IMMICH_LOG_LEVEL is set.'); - } - } - - async updateSystemConfig(dto: SystemConfigDto): Promise { - const { configFile } = this.configRepository.getEnv(); - if (configFile) { - throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use'); - } - - const oldConfig = await this.getConfig({ withCache: false }); - - try { - await this.eventRepository.emit('ConfigValidate', { newConfig: toPlainObject(dto), oldConfig }); - } catch (error) { - this.logger.warn(`Unable to save system config due to a validation error: ${error}`); - throw new BadRequestException(error instanceof Error ? error.message : error); - } - - const newConfig = await this.updateConfig(dto); - - await this.eventRepository.emit('ConfigUpdate', { newConfig, oldConfig }); - - return mapConfig(newConfig); - } - - async getCustomCss(): Promise { - const { theme } = await this.getConfig({ withCache: false }); - return theme.customCss; - } -} diff --git a/server/src/services/system-metadata.service.spec.ts b/server/src/services/system-metadata.service.spec.ts deleted file mode 100644 index f5bdcde7b4..0000000000 --- a/server/src/services/system-metadata.service.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { SystemMetadataKey } from 'src/enum'; -import { SystemMetadataService } from 'src/services/system-metadata.service'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(SystemMetadataService.name, () => { - let sut: SystemMetadataService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(SystemMetadataService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getAdminOnboarding', () => { - it('should get isOnboarded state', async () => { - mocks.systemMetadata.get.mockResolvedValue({ isOnboarded: true }); - await expect(sut.getAdminOnboarding()).resolves.toEqual({ isOnboarded: true }); - expect(mocks.systemMetadata.get).toHaveBeenCalledWith('admin-onboarding'); - }); - - it('should default isOnboarded to false', async () => { - await expect(sut.getAdminOnboarding()).resolves.toEqual({ isOnboarded: false }); - expect(mocks.systemMetadata.get).toHaveBeenCalledWith('admin-onboarding'); - }); - }); - - describe('updateAdminOnboarding', () => { - it('should update isOnboarded to true', async () => { - await expect(sut.updateAdminOnboarding({ isOnboarded: true })).resolves.toBeUndefined(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.AdminOnboarding, { isOnboarded: true }); - }); - - it('should update isOnboarded to false', async () => { - await expect(sut.updateAdminOnboarding({ isOnboarded: false })).resolves.toBeUndefined(); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.AdminOnboarding, { isOnboarded: false }); - }); - }); - - describe('getReverseGeocodingState', () => { - it('should get reverse geocoding state', async () => { - mocks.systemMetadata.get.mockResolvedValue({ lastUpdate: '2024-01-01', lastImportFileName: 'foo.bar' }); - await expect(sut.getReverseGeocodingState()).resolves.toEqual({ - lastUpdate: '2024-01-01', - lastImportFileName: 'foo.bar', - }); - }); - - it('should default reverse geocoding state to null', async () => { - await expect(sut.getReverseGeocodingState()).resolves.toEqual({ - lastUpdate: null, - lastImportFileName: null, - }); - }); - }); -}); diff --git a/server/src/services/system-metadata.service.ts b/server/src/services/system-metadata.service.ts deleted file mode 100644 index 30af715379..0000000000 --- a/server/src/services/system-metadata.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { - AdminOnboardingResponseDto, - AdminOnboardingUpdateDto, - ReverseGeocodingStateResponseDto, - VersionCheckStateResponseDto, -} from 'src/dtos/system-metadata.dto'; -import { SystemMetadataKey } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class SystemMetadataService extends BaseService { - async getAdminOnboarding(): Promise { - const value = await this.systemMetadataRepository.get(SystemMetadataKey.AdminOnboarding); - return { isOnboarded: false, ...value }; - } - - async updateAdminOnboarding(dto: AdminOnboardingUpdateDto): Promise { - await this.systemMetadataRepository.set(SystemMetadataKey.AdminOnboarding, { - isOnboarded: dto.isOnboarded, - }); - } - - async getReverseGeocodingState(): Promise { - const value = await this.systemMetadataRepository.get(SystemMetadataKey.ReverseGeocodingState); - return { lastUpdate: null, lastImportFileName: null, ...value }; - } - - async getVersionCheckState(): Promise { - const value = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState); - return { checkedAt: null, releaseVersion: null, ...value }; - } -} diff --git a/server/src/services/tag.service.spec.ts b/server/src/services/tag.service.spec.ts deleted file mode 100644 index f42f40940d..0000000000 --- a/server/src/services/tag.service.spec.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto'; -import { JobStatus } from 'src/enum'; -import { TagService } from 'src/services/tag.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(TagService.name, () => { - let sut: TagService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(TagService)); - - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-1'])); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getAll', () => { - it('should return all tags for a user', async () => { - mocks.tag.getAll.mockResolvedValue([tagStub.tag]); - await expect(sut.getAll(authStub.admin)).resolves.toEqual([tagResponseStub.tag1]); - expect(mocks.tag.getAll).toHaveBeenCalledWith(authStub.admin.user.id); - }); - }); - - describe('get', () => { - it('should throw an error for an invalid id', async () => { - await expect(sut.get(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.tag.get).toHaveBeenCalledWith('tag-1'); - }); - - it('should return a tag for a user', async () => { - mocks.tag.get.mockResolvedValue(tagStub.tag); - await expect(sut.get(authStub.admin, 'tag-1')).resolves.toEqual(tagResponseStub.tag1); - expect(mocks.tag.get).toHaveBeenCalledWith('tag-1'); - }); - }); - - describe('create', () => { - it('should throw an error for no parent tag access', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set()); - await expect(sut.create(authStub.admin, { name: 'tag', parentId: 'tag-parent' })).rejects.toBeInstanceOf( - BadRequestException, - ); - expect(mocks.tag.create).not.toHaveBeenCalled(); - }); - - it('should create a tag with a parent', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-parent'])); - mocks.tag.create.mockResolvedValue(tagStub.tagCreate); - mocks.tag.get.mockResolvedValueOnce(tagStub.parentUpsert); - mocks.tag.get.mockResolvedValueOnce(tagStub.childUpsert); - await expect(sut.create(authStub.admin, { name: 'tagA', parentId: 'tag-parent' })).resolves.toBeDefined(); - expect(mocks.tag.create).toHaveBeenCalledWith(expect.objectContaining({ value: 'Parent/tagA' })); - }); - - it('should handle invalid parent ids', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-parent'])); - await expect(sut.create(authStub.admin, { name: 'tagA', parentId: 'tag-parent' })).rejects.toBeInstanceOf( - BadRequestException, - ); - expect(mocks.tag.create).not.toHaveBeenCalled(); - }); - }); - - describe('create', () => { - it('should throw an error for a duplicate tag', async () => { - mocks.tag.getByValue.mockResolvedValue(tagStub.tag); - await expect(sut.create(authStub.admin, { name: 'tag-1' })).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.tag.getByValue).toHaveBeenCalledWith(authStub.admin.user.id, 'tag-1'); - expect(mocks.tag.create).not.toHaveBeenCalled(); - }); - - it('should create a new tag', async () => { - mocks.tag.create.mockResolvedValue(tagStub.tagCreate); - await expect(sut.create(authStub.admin, { name: 'tag-1' })).resolves.toEqual(tagResponseStub.tag1); - expect(mocks.tag.create).toHaveBeenCalledWith({ - userId: authStub.admin.user.id, - value: 'tag-1', - }); - }); - - it('should create a new tag with optional color', async () => { - mocks.tag.create.mockResolvedValue(tagStub.colorCreate); - mocks.tag.getByValue.mockResolvedValue(void 0); - - await expect(sut.create(authStub.admin, { name: 'tag-1', color: '#000000' })).resolves.toEqual( - tagResponseStub.color1, - ); - - expect(mocks.tag.create).toHaveBeenCalledWith({ - userId: authStub.admin.user.id, - value: 'tag-1', - color: '#000000', - }); - }); - }); - - describe('update', () => { - it('should throw an error for no update permission', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set()); - await expect(sut.update(authStub.admin, 'tag-1', { color: '#000000' })).rejects.toBeInstanceOf( - BadRequestException, - ); - expect(mocks.tag.update).not.toHaveBeenCalled(); - }); - - it('should update a tag', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-1'])); - mocks.tag.update.mockResolvedValue(tagStub.colorCreate); - await expect(sut.update(authStub.admin, 'tag-1', { color: '#000000' })).resolves.toEqual(tagResponseStub.color1); - expect(mocks.tag.update).toHaveBeenCalledWith('tag-1', { color: '#000000' }); - }); - }); - - describe('upsert', () => { - it('should upsert a new tag', async () => { - mocks.tag.upsertValue.mockResolvedValue(tagStub.parentUpsert); - await expect(sut.upsert(authStub.admin, { tags: ['Parent'] })).resolves.toBeDefined(); - expect(mocks.tag.upsertValue).toHaveBeenCalledWith({ - value: 'Parent', - userId: 'admin_id', - parentId: undefined, - }); - }); - - it('should upsert a nested tag', async () => { - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert); - await expect(sut.upsert(authStub.admin, { tags: ['Parent/Child'] })).resolves.toBeDefined(); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - value: 'Parent', - userId: 'admin_id', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - value: 'Parent/Child', - userId: 'admin_id', - parentId: 'tag-parent', - }); - }); - - it('should upsert a tag and ignore leading and trailing slashes', async () => { - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.parentUpsert); - mocks.tag.upsertValue.mockResolvedValueOnce(tagStub.childUpsert); - await expect(sut.upsert(authStub.admin, { tags: ['/Parent/Child/'] })).resolves.toBeDefined(); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(1, { - value: 'Parent', - userId: 'admin_id', - parentId: undefined, - }); - expect(mocks.tag.upsertValue).toHaveBeenNthCalledWith(2, { - value: 'Parent/Child', - userId: 'admin_id', - parentId: 'tag-parent', - }); - }); - }); - - describe('remove', () => { - it('should throw an error for an invalid id', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set()); - await expect(sut.remove(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException); - expect(mocks.tag.delete).not.toHaveBeenCalled(); - }); - - it('should remove a tag', async () => { - mocks.tag.get.mockResolvedValue(tagStub.tag); - mocks.tag.delete.mockResolvedValue(); - - await sut.remove(authStub.admin, 'tag-1'); - expect(mocks.tag.delete).toHaveBeenCalledWith('tag-1'); - }); - }); - - describe('bulkTagAssets', () => { - it('should handle invalid requests', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set()); - mocks.tag.upsertAssetIds.mockResolvedValue([]); - await expect(sut.bulkTagAssets(authStub.admin, { tagIds: ['tag-1'], assetIds: ['asset-1'] })).resolves.toEqual({ - count: 0, - }); - expect(mocks.tag.upsertAssetIds).toHaveBeenCalledWith([]); - }); - - it('should upsert records', async () => { - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-1', 'tag-2'])); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3'])); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - tags: [factory.tag({ value: 'tag-1' }), factory.tag({ value: 'tag-2' })], - }); - mocks.tag.upsertAssetIds.mockResolvedValue([ - { tagId: 'tag-1', assetId: 'asset-1' }, - { tagId: 'tag-1', assetId: 'asset-2' }, - { tagId: 'tag-1', assetId: 'asset-3' }, - { tagId: 'tag-2', assetId: 'asset-1' }, - { tagId: 'tag-2', assetId: 'asset-2' }, - { tagId: 'tag-2', assetId: 'asset-3' }, - ]); - await expect( - sut.bulkTagAssets(authStub.admin, { tagIds: ['tag-1', 'tag-2'], assetIds: ['asset-1', 'asset-2', 'asset-3'] }), - ).resolves.toEqual({ - count: 6, - }); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-1', lockedProperties: ['tags'], tags: ['tag-1', 'tag-2'] }, - { lockedPropertiesBehavior: 'append' }, - ); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-2', lockedProperties: ['tags'], tags: ['tag-1', 'tag-2'] }, - { lockedPropertiesBehavior: 'append' }, - ); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-3', lockedProperties: ['tags'], tags: ['tag-1', 'tag-2'] }, - { lockedPropertiesBehavior: 'append' }, - ); - expect(mocks.tag.upsertAssetIds).toHaveBeenCalledWith([ - { tagId: 'tag-1', assetId: 'asset-1' }, - { tagId: 'tag-1', assetId: 'asset-2' }, - { tagId: 'tag-1', assetId: 'asset-3' }, - { tagId: 'tag-2', assetId: 'asset-1' }, - { tagId: 'tag-2', assetId: 'asset-2' }, - { tagId: 'tag-2', assetId: 'asset-3' }, - ]); - }); - }); - - describe('addAssets', () => { - it('should handle invalid ids', async () => { - mocks.tag.getAssetIds.mockResolvedValue(new Set()); - await expect(sut.addAssets(authStub.admin, 'tag-1', { ids: ['asset-1'] })).resolves.toEqual([ - { id: 'asset-1', success: false, error: 'no_permission' }, - ]); - expect(mocks.tag.getAssetIds).toHaveBeenCalledWith('tag-1', ['asset-1']); - expect(mocks.tag.addAssetIds).not.toHaveBeenCalled(); - }); - - it('should accept accept ids that are new and reject the rest', async () => { - mocks.tag.get.mockResolvedValue(tagStub.tag); - mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1'])); - mocks.tag.addAssetIds.mockResolvedValue(); - mocks.asset.getById.mockResolvedValue({ - ...factory.asset(), - tags: [factory.tag({ value: 'tag-1' })], - }); - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-2'])); - - await expect( - sut.addAssets(authStub.admin, 'tag-1', { - ids: ['asset-1', 'asset-2'], - }), - ).resolves.toEqual([ - { id: 'asset-1', success: false, error: BulkIdErrorReason.DUPLICATE }, - { id: 'asset-2', success: true }, - ]); - - expect(mocks.asset.upsertExif).not.toHaveBeenCalledWith( - { assetId: 'asset-1', lockedProperties: ['tags'], tags: ['tag-1'] }, - { lockedPropertiesBehavior: 'append' }, - ); - expect(mocks.asset.upsertExif).toHaveBeenCalledWith( - { assetId: 'asset-2', lockedProperties: ['tags'], tags: ['tag-1'] }, - { lockedPropertiesBehavior: 'append' }, - ); - expect(mocks.tag.getAssetIds).toHaveBeenCalledWith('tag-1', ['asset-1', 'asset-2']); - expect(mocks.tag.addAssetIds).toHaveBeenCalledWith('tag-1', ['asset-2']); - }); - }); - - describe('removeAssets', () => { - it('should throw an error for an invalid id', async () => { - mocks.tag.getAssetIds.mockResolvedValue(new Set()); - mocks.tag.removeAssetIds.mockResolvedValue(); - - await expect(sut.removeAssets(authStub.admin, 'tag-1', { ids: ['asset-1'] })).resolves.toEqual([ - { id: 'asset-1', success: false, error: 'not_found' }, - ]); - }); - - it('should accept accept ids that are tagged and reject the rest', async () => { - mocks.tag.get.mockResolvedValue(tagStub.tag); - mocks.tag.getAssetIds.mockResolvedValue(new Set(['asset-1'])); - mocks.tag.removeAssetIds.mockResolvedValue(); - - await expect( - sut.removeAssets(authStub.admin, 'tag-1', { - ids: ['asset-1', 'asset-2'], - }), - ).resolves.toEqual([ - { id: 'asset-1', success: true }, - { id: 'asset-2', success: false, error: BulkIdErrorReason.NOT_FOUND }, - ]); - - expect(mocks.tag.getAssetIds).toHaveBeenCalledWith('tag-1', ['asset-1', 'asset-2']); - expect(mocks.tag.removeAssetIds).toHaveBeenCalledWith('tag-1', ['asset-1']); - }); - }); - - describe('handleTagCleanup', () => { - it('should delete empty tags', async () => { - mocks.tag.deleteEmptyTags.mockResolvedValue(); - - await expect(sut.handleTagCleanup()).resolves.toBe(JobStatus.Success); - - expect(mocks.tag.deleteEmptyTags).toHaveBeenCalled(); - }); - }); -}); diff --git a/server/src/services/tag.service.ts b/server/src/services/tag.service.ts deleted file mode 100644 index 20303421c1..0000000000 --- a/server/src/services/tag.service.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { Insertable } from 'kysely'; -import { OnJob } from 'src/decorators'; -import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - TagBulkAssetsDto, - TagBulkAssetsResponseDto, - TagCreateDto, - TagResponseDto, - TagUpdateDto, - TagUpsertDto, - mapTag, -} from 'src/dtos/tag.dto'; -import { JobName, JobStatus, Permission, QueueName } from 'src/enum'; -import { TagAssetTable } from 'src/schema/tables/tag-asset.table'; -import { BaseService } from 'src/services/base.service'; -import { addAssets, removeAssets } from 'src/utils/asset.util'; -import { updateLockedColumns } from 'src/utils/database'; -import { upsertTags } from 'src/utils/tag'; - -@Injectable() -export class TagService extends BaseService { - async getAll(auth: AuthDto) { - const tags = await this.tagRepository.getAll(auth.user.id); - return tags.map((tag) => mapTag(tag)); - } - - async get(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.TagRead, ids: [id] }); - const tag = await this.findOrFail(id); - return mapTag(tag); - } - - async create(auth: AuthDto, dto: TagCreateDto) { - let parent; - if (dto.parentId) { - await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.parentId] }); - parent = await this.tagRepository.get(dto.parentId); - if (!parent) { - throw new BadRequestException('Tag not found'); - } - } - - const userId = auth.user.id; - const value = parent ? `${parent.value}/${dto.name}` : dto.name; - const duplicate = await this.tagRepository.getByValue(userId, value); - if (duplicate) { - throw new BadRequestException(`A tag with that name already exists`); - } - - const { color } = dto; - const tag = await this.tagRepository.create({ userId, value, color, parentId: parent?.id }); - - return mapTag(tag); - } - - async update(auth: AuthDto, id: string, dto: TagUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.TagUpdate, ids: [id] }); - - const { color } = dto; - const tag = await this.tagRepository.update(id, { color }); - return mapTag(tag); - } - - async upsert(auth: AuthDto, dto: TagUpsertDto) { - const tags = await upsertTags(this.tagRepository, { userId: auth.user.id, tags: dto.tags }); - return tags.map((tag) => mapTag(tag)); - } - - async remove(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.TagDelete, ids: [id] }); - - // TODO sync tag changes for affected assets - - await this.tagRepository.delete(id); - } - - async bulkTagAssets(auth: AuthDto, dto: TagBulkAssetsDto): Promise { - const [tagIds, assetIds] = await Promise.all([ - this.checkAccess({ auth, permission: Permission.TagAsset, ids: dto.tagIds }), - this.checkAccess({ auth, permission: Permission.AssetUpdate, ids: dto.assetIds }), - ]); - - const items: Insertable[] = []; - for (const tagId of tagIds) { - for (const assetId of assetIds) { - items.push({ tagId, assetId }); - } - } - - const results = await this.tagRepository.upsertAssetIds(items); - for (const assetId of new Set(results.map((item) => item.assetId))) { - await this.updateTags(assetId); - await this.eventRepository.emit('AssetTag', { assetId }); - } - - return { count: results.length }; - } - - async addAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.TagAsset, ids: [id] }); - - const results = await addAssets( - auth, - { access: this.accessRepository, bulk: this.tagRepository }, - { parentId: id, assetIds: dto.ids }, - ); - - for (const { id: assetId, success } of results) { - if (success) { - await this.updateTags(assetId); - await this.eventRepository.emit('AssetTag', { assetId }); - } - } - - return results; - } - - async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise { - await this.requireAccess({ auth, permission: Permission.TagAsset, ids: [id] }); - - const results = await removeAssets( - auth, - { access: this.accessRepository, bulk: this.tagRepository }, - { parentId: id, assetIds: dto.ids, canAlwaysRemove: Permission.TagDelete }, - ); - - for (const { id: assetId, success } of results) { - if (success) { - await this.updateTags(assetId); - await this.eventRepository.emit('AssetUntag', { assetId }); - } - } - - return results; - } - - @OnJob({ name: JobName.TagCleanup, queue: QueueName.BackgroundTask }) - async handleTagCleanup() { - await this.tagRepository.deleteEmptyTags(); - return JobStatus.Success; - } - - private async findOrFail(id: string) { - const tag = await this.tagRepository.get(id); - if (!tag) { - throw new BadRequestException('Tag not found'); - } - return tag; - } - - private async updateTags(assetId: string) { - const asset = await this.assetRepository.getById(assetId, { tags: true }); - await this.assetRepository.upsertExif( - updateLockedColumns({ assetId, tags: asset?.tags?.map(({ value }) => value) ?? [] }), - { lockedPropertiesBehavior: 'append' }, - ); - } -} diff --git a/server/src/services/telemetry.service.ts b/server/src/services/telemetry.service.ts deleted file mode 100644 index 7c4fe43214..0000000000 --- a/server/src/services/telemetry.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { snakeCase } from 'lodash'; -import { OnEvent } from 'src/decorators'; -import { ImmichWorker, JobStatus } from 'src/enum'; -import { ArgOf, ArgsOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; - -export class TelemetryService extends BaseService { - @OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Api] }) - async onBootstrap(): Promise { - const userCount = await this.userRepository.getCount(); - this.telemetryRepository.api.addToGauge('immich.users.total', userCount); - } - - @OnEvent({ name: 'UserCreate' }) - onUserCreate() { - this.telemetryRepository.api.addToGauge(`immich.users.total`, 1); - } - - @OnEvent({ name: 'UserTrash' }) - onUserTrash() { - this.telemetryRepository.api.addToGauge(`immich.users.total`, -1); - } - - @OnEvent({ name: 'UserRestore' }) - onUserRestore() { - this.telemetryRepository.api.addToGauge(`immich.users.total`, 1); - } - - @OnEvent({ name: 'JobStart' }) - onJobStart(...[queueName]: ArgsOf<'JobStart'>) { - const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; - this.telemetryRepository.jobs.addToGauge(queueMetric, 1); - } - - @OnEvent({ name: 'JobSuccess' }) - onJobSuccess({ job, response }: ArgOf<'JobSuccess'>) { - if (response && Object.values(JobStatus).includes(response as JobStatus)) { - const jobMetric = `immich.jobs.${snakeCase(job.name)}.${response}`; - this.telemetryRepository.jobs.addToCounter(jobMetric, 1); - } - } - - @OnEvent({ name: 'JobError' }) - onJobError({ job }: ArgOf<'JobError'>) { - const jobMetric = `immich.jobs.${snakeCase(job.name)}.${JobStatus.Failed}`; - this.telemetryRepository.jobs.addToCounter(jobMetric, 1); - } - - @OnEvent({ name: 'JobComplete' }) - onJobComplete(...[queueName]: ArgsOf<'JobComplete'>) { - const queueMetric = `immich.queues.${snakeCase(queueName)}.active`; - this.telemetryRepository.jobs.addToGauge(queueMetric, -1); - } - - @OnEvent({ name: 'QueueStart' }) - onQueueStart({ name }: ArgOf<'QueueStart'>) { - this.telemetryRepository.jobs.addToCounter(`immich.queues.${snakeCase(name)}.started`, 1); - } -} diff --git a/server/src/services/timeline.service.spec.ts b/server/src/services/timeline.service.spec.ts deleted file mode 100644 index 3301e61318..0000000000 --- a/server/src/services/timeline.service.spec.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { AssetVisibility } from 'src/enum'; -import { TimelineService } from 'src/services/timeline.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(TimelineService.name, () => { - let sut: TimelineService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(TimelineService)); - }); - - describe('getTimeBuckets', () => { - it("should return buckets if userId and albumId aren't set", async () => { - mocks.asset.getTimeBuckets.mockResolvedValue([{ timeBucket: 'bucket', count: 1 }]); - - await expect(sut.getTimeBuckets(authStub.admin, {})).resolves.toEqual( - expect.arrayContaining([{ timeBucket: 'bucket', count: 1 }]), - ); - expect(mocks.asset.getTimeBuckets).toHaveBeenCalledWith({ - userIds: [authStub.admin.user.id], - }); - }); - }); - - describe('getTimeBucket', () => { - it('should return the assets for a album time bucket if user has album.read', async () => { - mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id'])); - const json = `[{ id: ['asset-id'] }]`; - mocks.asset.getTimeBucket.mockResolvedValue({ assets: json }); - - await expect(sut.getTimeBucket(authStub.admin, { timeBucket: 'bucket', albumId: 'album-id' })).resolves.toEqual( - json, - ); - - expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-id'])); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( - 'bucket', - { - timeBucket: 'bucket', - albumId: 'album-id', - }, - authStub.admin, - ); - }); - - it('should return the assets for a archive time bucket if user has archive.read', async () => { - const json = `[{ id: ['asset-id'] }]`; - mocks.asset.getTimeBucket.mockResolvedValue({ assets: json }); - - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - visibility: AssetVisibility.Archive, - userId: authStub.admin.user.id, - }), - ).resolves.toEqual(json); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( - 'bucket', - expect.objectContaining({ - timeBucket: 'bucket', - visibility: AssetVisibility.Archive, - userIds: [authStub.admin.user.id], - }), - authStub.admin, - ); - }); - - it('should include partner shared assets', async () => { - const json = `[{ id: ['asset-id'] }]`; - mocks.asset.getTimeBucket.mockResolvedValue({ assets: json }); - mocks.partner.getAll.mockResolvedValue([]); - - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - visibility: AssetVisibility.Timeline, - userId: authStub.admin.user.id, - withPartners: true, - }), - ).resolves.toEqual(json); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( - 'bucket', - { - timeBucket: 'bucket', - visibility: AssetVisibility.Timeline, - withPartners: true, - userIds: [authStub.admin.user.id], - }, - authStub.admin, - ); - }); - - it('should check permissions to read tag', async () => { - const json = `[{ id: ['asset-id'] }]`; - mocks.asset.getTimeBucket.mockResolvedValue({ assets: json }); - mocks.access.tag.checkOwnerAccess.mockResolvedValue(new Set(['tag-123'])); - - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - userId: authStub.admin.user.id, - tagId: 'tag-123', - }), - ).resolves.toEqual(json); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( - 'bucket', - { - tagId: 'tag-123', - timeBucket: 'bucket', - userIds: [authStub.admin.user.id], - }, - authStub.admin, - ); - }); - - it('should return the assets for a library time bucket if user has library.read', async () => { - const json = `[{ id: ['asset-id'] }]`; - mocks.asset.getTimeBucket.mockResolvedValue({ assets: json }); - - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - userId: authStub.admin.user.id, - }), - ).resolves.toEqual(json); - expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith( - 'bucket', - expect.objectContaining({ - timeBucket: 'bucket', - userIds: [authStub.admin.user.id], - }), - authStub.admin, - ); - }); - - it('should throw an error if withParners is true and visibility true or undefined', async () => { - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - visibility: AssetVisibility.Archive, - withPartners: true, - userId: authStub.admin.user.id, - }), - ).rejects.toThrow(BadRequestException); - - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - visibility: undefined, - withPartners: true, - userId: authStub.admin.user.id, - }), - ).rejects.toThrow(BadRequestException); - }); - - it('should throw an error if withParners is true and isFavorite is either true or false', async () => { - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - isFavorite: true, - withPartners: true, - userId: authStub.admin.user.id, - }), - ).rejects.toThrow(BadRequestException); - - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - isFavorite: false, - withPartners: true, - userId: authStub.admin.user.id, - }), - ).rejects.toThrow(BadRequestException); - }); - - it('should throw an error if withParners is true and isTrash is true', async () => { - await expect( - sut.getTimeBucket(authStub.admin, { - timeBucket: 'bucket', - isTrashed: true, - withPartners: true, - userId: authStub.admin.user.id, - }), - ).rejects.toThrow(BadRequestException); - }); - }); -}); diff --git a/server/src/services/timeline.service.ts b/server/src/services/timeline.service.ts deleted file mode 100644 index b840883fa9..0000000000 --- a/server/src/services/timeline.service.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { TimeBucketAssetDto, TimeBucketDto, TimeBucketsResponseDto } from 'src/dtos/time-bucket.dto'; -import { AssetVisibility, Permission } from 'src/enum'; -import { TimeBucketOptions } from 'src/repositories/asset.repository'; -import { BaseService } from 'src/services/base.service'; -import { requireElevatedPermission } from 'src/utils/access'; -import { getMyPartnerIds } from 'src/utils/asset.util'; - -@Injectable() -export class TimelineService extends BaseService { - async getTimeBuckets(auth: AuthDto, dto: TimeBucketDto): Promise { - await this.timeBucketChecks(auth, dto); - const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto); - return await this.assetRepository.getTimeBuckets(timeBucketOptions); - } - - // pre-jsonified response - async getTimeBucket(auth: AuthDto, dto: TimeBucketAssetDto): Promise { - await this.timeBucketChecks(auth, dto); - const timeBucketOptions = await this.buildTimeBucketOptions(auth, { ...dto }); - - // TODO: use id cursor for pagination - const bucket = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions, auth); - return bucket.assets; - } - - private async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise { - const { userId, ...options } = dto; - let userIds: string[] | undefined = undefined; - - if (userId) { - userIds = [userId]; - if (dto.withPartners) { - const partnerIds = await getMyPartnerIds({ - userId: auth.user.id, - repository: this.partnerRepository, - timelineEnabled: true, - }); - userIds.push(...partnerIds); - } - } - - return { ...options, userIds }; - } - - private async timeBucketChecks(auth: AuthDto, dto: TimeBucketDto) { - if (dto.visibility === AssetVisibility.Locked) { - requireElevatedPermission(auth); - } - - if (dto.albumId) { - await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [dto.albumId] }); - } else { - dto.userId = dto.userId || auth.user.id; - } - - if (dto.userId) { - await this.requireAccess({ auth, permission: Permission.TimelineRead, ids: [dto.userId] }); - if (dto.visibility === AssetVisibility.Archive) { - await this.requireAccess({ auth, permission: Permission.ArchiveRead, ids: [dto.userId] }); - } - } - - if (dto.tagId) { - await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.tagId] }); - } - - if (dto.withPartners) { - const requestedArchived = dto.visibility === AssetVisibility.Archive || dto.visibility === undefined; - const requestedFavorite = dto.isFavorite === true || dto.isFavorite === false; - const requestedTrash = dto.isTrashed === true; - - if (requestedArchived || requestedFavorite || requestedTrash) { - throw new BadRequestException( - 'withPartners is only supported for non-archived, non-trashed, non-favorited assets', - ); - } - } - } -} diff --git a/server/src/services/trash.service.spec.ts b/server/src/services/trash.service.spec.ts deleted file mode 100644 index e43c49e543..0000000000 --- a/server/src/services/trash.service.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { JobName, JobStatus } from 'src/enum'; -import { TrashService } from 'src/services/trash.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -async function* makeAssetIdStream(count: number): AsyncIterableIterator<{ id: string }> { - for (let i = 0; i < count; i++) { - await Promise.resolve(); - yield { id: `asset-${i + 1}` }; - } -} - -describe(TrashService.name, () => { - let sut: TrashService; - let mocks: ServiceMocks; - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - beforeEach(() => { - ({ sut, mocks } = newTestService(TrashService)); - }); - - describe('restoreAssets', () => { - it('should require asset restore access for all ids', async () => { - await expect( - sut.restoreAssets(authStub.user1, { - ids: ['asset-1'], - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should handle an empty list', async () => { - await expect(sut.restoreAssets(authStub.user1, { ids: [] })).resolves.toEqual({ count: 0 }); - expect(mocks.access.asset.checkOwnerAccess).not.toHaveBeenCalled(); - }); - - it('should restore a batch of assets', async () => { - mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2'])); - mocks.trash.restoreAll.mockResolvedValue(0); - - await sut.restoreAssets(authStub.user1, { ids: ['asset1', 'asset2'] }); - - expect(mocks.trash.restoreAll).toHaveBeenCalledWith(['asset1', 'asset2']); - expect(mocks.job.queue.mock.calls).toEqual([]); - }); - }); - - describe('restore', () => { - it('should handle an empty trash', async () => { - mocks.trash.getDeletedIds.mockResolvedValue(makeAssetIdStream(0)); - mocks.trash.restore.mockResolvedValue(0); - await expect(sut.restore(authStub.user1)).resolves.toEqual({ count: 0 }); - expect(mocks.trash.restore).toHaveBeenCalledWith('user-id'); - }); - - it('should restore', async () => { - mocks.trash.getDeletedIds.mockResolvedValue(makeAssetIdStream(1)); - mocks.trash.restore.mockResolvedValue(1); - await expect(sut.restore(authStub.user1)).resolves.toEqual({ count: 1 }); - expect(mocks.trash.restore).toHaveBeenCalledWith('user-id'); - }); - }); - - describe('empty', () => { - it('should handle an empty trash', async () => { - mocks.trash.getDeletedIds.mockResolvedValue(makeAssetIdStream(0)); - mocks.trash.empty.mockResolvedValue(0); - await expect(sut.empty(authStub.user1)).resolves.toEqual({ count: 0 }); - expect(mocks.job.queue).not.toHaveBeenCalled(); - }); - - it('should empty the trash', async () => { - mocks.trash.getDeletedIds.mockResolvedValue(makeAssetIdStream(1)); - mocks.trash.empty.mockResolvedValue(1); - await expect(sut.empty(authStub.user1)).resolves.toEqual({ count: 1 }); - expect(mocks.trash.empty).toHaveBeenCalledWith('user-id'); - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetEmptyTrash, data: {} }); - }); - }); - - describe('onAssetsDelete', () => { - it('should queue the empty trash job', async () => { - await expect(sut.onAssetsDelete()).resolves.toBeUndefined(); - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.AssetEmptyTrash, data: {} }); - }); - }); - - describe('handleQueueEmptyTrash', () => { - it('should queue asset delete jobs', async () => { - mocks.trash.getDeletedIds.mockReturnValue(makeAssetIdStream(1)); - await expect(sut.handleEmptyTrash()).resolves.toEqual(JobStatus.Success); - expect(mocks.job.queueAll).toHaveBeenCalledWith([ - { - name: JobName.AssetDelete, - data: { id: 'asset-1', deleteOnDisk: true }, - }, - ]); - }); - }); -}); diff --git a/server/src/services/trash.service.ts b/server/src/services/trash.service.ts deleted file mode 100644 index f1d368111e..0000000000 --- a/server/src/services/trash.service.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { OnEvent, OnJob } from 'src/decorators'; -import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { TrashResponseDto } from 'src/dtos/trash.dto'; -import { JobName, JobStatus, Permission, QueueName } from 'src/enum'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class TrashService extends BaseService { - async restoreAssets(auth: AuthDto, dto: BulkIdsDto): Promise { - const { ids } = dto; - if (ids.length === 0) { - return { count: 0 }; - } - - await this.requireAccess({ auth, permission: Permission.AssetDelete, ids }); - await this.trashRepository.restoreAll(ids); - await this.eventRepository.emit('AssetRestoreAll', { assetIds: ids, userId: auth.user.id }); - - this.logger.log(`Restored ${ids.length} asset(s) from trash`); - - return { count: ids.length }; - } - - async restore(auth: AuthDto): Promise { - const count = await this.trashRepository.restore(auth.user.id); - if (count > 0) { - this.logger.log(`Restored ${count} asset(s) from trash`); - } - return { count }; - } - - async empty(auth: AuthDto): Promise { - const count = await this.trashRepository.empty(auth.user.id); - if (count > 0) { - await this.jobRepository.queue({ name: JobName.AssetEmptyTrash, data: {} }); - } - return { count }; - } - - @OnEvent({ name: 'AssetDeleteAll' }) - async onAssetsDelete() { - await this.jobRepository.queue({ name: JobName.AssetEmptyTrash, data: {} }); - } - - @OnJob({ name: JobName.AssetEmptyTrash, queue: QueueName.BackgroundTask }) - async handleEmptyTrash() { - const assets = this.trashRepository.getDeletedIds(); - - let count = 0; - const batch: string[] = []; - for await (const { id } of assets) { - batch.push(id); - - if (batch.length === JOBS_ASSET_PAGINATION_SIZE) { - await this.handleBatch(batch); - count += batch.length; - batch.length = 0; - } - } - - await this.handleBatch(batch); - count += batch.length; - batch.length = 0; - - this.logger.log(`Queued ${count} asset(s) for deletion from the trash`); - - return JobStatus.Success; - } - - private async handleBatch(ids: string[]) { - this.logger.debug(`Queueing ${ids.length} asset(s) for deletion from the trash`); - await this.jobRepository.queueAll( - ids.map((assetId) => ({ - name: JobName.AssetDelete, - data: { - id: assetId, - deleteOnDisk: true, - }, - })), - ); - } -} diff --git a/server/src/services/user-admin.service.spec.ts b/server/src/services/user-admin.service.spec.ts deleted file mode 100644 index d8e13fcfbd..0000000000 --- a/server/src/services/user-admin.service.spec.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { BadRequestException, ForbiddenException } from '@nestjs/common'; -import { mapUserAdmin } from 'src/dtos/user.dto'; -import { JobName, UserStatus } from 'src/enum'; -import { UserAdminService } from 'src/services/user-admin.service'; -import { authStub } from 'test/fixtures/auth.stub'; -import { userStub } from 'test/fixtures/user.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; -import { describe } from 'vitest'; - -describe(UserAdminService.name, () => { - let sut: UserAdminService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(UserAdminService)); - - mocks.user.get.mockImplementation((userId) => - Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), - ); - }); - - describe('create', () => { - it('should not create a user if there is no local admin account', async () => { - mocks.user.getAdmin.mockResolvedValueOnce(void 0); - - await expect( - sut.create({ - email: 'john_smith@email.com', - name: 'John Smith', - password: 'password', - }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - - it('should create user', async () => { - mocks.user.getAdmin.mockResolvedValue(userStub.admin); - mocks.user.create.mockResolvedValue(userStub.user1); - - await expect( - sut.create({ - email: userStub.user1.email, - name: userStub.user1.name, - password: 'password', - storageLabel: 'label', - }), - ).resolves.toEqual(mapUserAdmin(userStub.user1)); - - expect(mocks.user.getAdmin).toBeCalled(); - expect(mocks.user.create).toBeCalledWith({ - email: userStub.user1.email, - name: userStub.user1.name, - storageLabel: 'label', - password: expect.anything(), - }); - }); - }); - - describe('update', () => { - it('should update the user', async () => { - const update = { - shouldChangePassword: true, - email: 'immich@test.com', - storageLabel: 'storage_label', - }; - mocks.user.getByEmail.mockResolvedValue(void 0); - mocks.user.getByStorageLabel.mockResolvedValue(void 0); - mocks.user.update.mockResolvedValue(userStub.user1); - - await sut.update(authStub.user1, userStub.user1.id, update); - - expect(mocks.user.getByEmail).toHaveBeenCalledWith(update.email); - expect(mocks.user.getByStorageLabel).toHaveBeenCalledWith(update.storageLabel); - }); - - it('should not set an empty string for storage label', async () => { - mocks.user.update.mockResolvedValue(userStub.user1); - await sut.update(authStub.admin, userStub.user1.id, { storageLabel: '' }); - expect(mocks.user.update).toHaveBeenCalledWith(userStub.user1.id, { - storageLabel: null, - updatedAt: expect.any(Date), - }); - }); - - it('should not change an email to one already in use', async () => { - const dto = { id: userStub.user1.id, email: 'updated@test.com' }; - - mocks.user.get.mockResolvedValue(userStub.user1); - mocks.user.getByEmail.mockResolvedValue(userStub.admin); - - await expect(sut.update(authStub.admin, userStub.user1.id, dto)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should not let the admin change the storage label to one already in use', async () => { - const dto = { id: userStub.user1.id, storageLabel: 'admin' }; - - mocks.user.get.mockResolvedValue(userStub.user1); - mocks.user.getByStorageLabel.mockResolvedValue(userStub.admin); - - await expect(sut.update(authStub.admin, userStub.user1.id, dto)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('update user information should throw error if user not found', async () => { - mocks.user.get.mockResolvedValueOnce(void 0); - - await expect( - sut.update(authStub.admin, userStub.user1.id, { shouldChangePassword: true }), - ).rejects.toBeInstanceOf(BadRequestException); - }); - }); - - describe('delete', () => { - it('should throw error if user could not be found', async () => { - mocks.user.get.mockResolvedValue(void 0); - - await expect(sut.delete(authStub.admin, 'not-found', {})).rejects.toThrowError(BadRequestException); - expect(mocks.user.delete).not.toHaveBeenCalled(); - }); - - it('cannot delete admin user', async () => { - await expect(sut.delete(authStub.admin, userStub.admin.id, {})).rejects.toBeInstanceOf(ForbiddenException); - }); - - it('should not allow deleting own account', async () => { - const user = factory.userAdmin({ isAdmin: false }); - const auth = factory.auth({ user }); - mocks.user.get.mockResolvedValue(user); - await expect(sut.delete(auth, user.id, {})).rejects.toBeInstanceOf(ForbiddenException); - - expect(mocks.user.delete).not.toHaveBeenCalled(); - }); - - it('should delete user', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - mocks.user.update.mockResolvedValue(userStub.user1); - - await expect(sut.delete(authStub.admin, userStub.user1.id, {})).resolves.toEqual(mapUserAdmin(userStub.user1)); - expect(mocks.user.update).toHaveBeenCalledWith(userStub.user1.id, { - status: UserStatus.Deleted, - deletedAt: expect.any(Date), - }); - }); - - it('should force delete user', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - mocks.user.update.mockResolvedValue(userStub.user1); - - await expect(sut.delete(authStub.admin, userStub.user1.id, { force: true })).resolves.toEqual( - mapUserAdmin(userStub.user1), - ); - - expect(mocks.user.update).toHaveBeenCalledWith(userStub.user1.id, { - status: UserStatus.Removing, - deletedAt: expect.any(Date), - }); - expect(mocks.job.queue).toHaveBeenCalledWith({ - name: JobName.UserDelete, - data: { id: userStub.user1.id, force: true }, - }); - }); - }); - - describe('restore', () => { - it('should throw error if user could not be found', async () => { - mocks.user.get.mockResolvedValue(void 0); - await expect(sut.restore(authStub.admin, userStub.admin.id)).rejects.toThrowError(BadRequestException); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should restore an user', async () => { - mocks.user.get.mockResolvedValue(userStub.user1); - mocks.user.restore.mockResolvedValue(userStub.user1); - await expect(sut.restore(authStub.admin, userStub.user1.id)).resolves.toEqual(mapUserAdmin(userStub.user1)); - expect(mocks.user.restore).toHaveBeenCalledWith(userStub.user1.id); - }); - }); -}); diff --git a/server/src/services/user-admin.service.ts b/server/src/services/user-admin.service.ts deleted file mode 100644 index 58b4221cc9..0000000000 --- a/server/src/services/user-admin.service.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common'; -import { SALT_ROUNDS } from 'src/constants'; -import { AssetStatsDto, AssetStatsResponseDto, mapStats } from 'src/dtos/asset.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { SessionResponseDto, mapSession } from 'src/dtos/session.dto'; -import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto'; -import { - UserAdminCreateDto, - UserAdminDeleteDto, - UserAdminResponseDto, - UserAdminSearchDto, - UserAdminUpdateDto, - mapUserAdmin, -} from 'src/dtos/user.dto'; -import { JobName, UserMetadataKey, UserStatus } from 'src/enum'; -import { UserFindOptions } from 'src/repositories/user.repository'; -import { BaseService } from 'src/services/base.service'; -import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; - -@Injectable() -export class UserAdminService extends BaseService { - async search(auth: AuthDto, dto: UserAdminSearchDto): Promise { - const users = await this.userRepository.getList({ - id: dto.id, - withDeleted: dto.withDeleted, - }); - return users.map((user) => mapUserAdmin(user)); - } - - async create(dto: UserAdminCreateDto): Promise { - const { notify, ...userDto } = dto; - const config = await this.getConfig({ withCache: false }); - if (!config.oauth.enabled && !userDto.password) { - throw new BadRequestException('password is required'); - } - - const user = await this.createUser(userDto); - - await this.eventRepository.emit('UserSignup', { - notify: !!notify, - id: user.id, - password: userDto.password, - }); - - return mapUserAdmin(user); - } - - async get(auth: AuthDto, id: string): Promise { - const user = await this.findOrFail(id, { withDeleted: true }); - return mapUserAdmin(user); - } - - async update(auth: AuthDto, id: string, dto: UserAdminUpdateDto): Promise { - const user = await this.findOrFail(id, {}); - - if (dto.isAdmin !== undefined && dto.isAdmin !== auth.user.isAdmin && auth.user.id === id) { - throw new BadRequestException('Admin status can only be changed by another admin'); - } - - if (dto.quotaSizeInBytes && user.quotaSizeInBytes !== dto.quotaSizeInBytes) { - await this.userRepository.syncUsage(id); - } - - if (dto.email) { - const duplicate = await this.userRepository.getByEmail(dto.email); - if (duplicate && duplicate.id !== id) { - throw new BadRequestException('Email already in use by another account'); - } - } - - if (dto.storageLabel) { - const duplicate = await this.userRepository.getByStorageLabel(dto.storageLabel); - if (duplicate && duplicate.id !== id) { - throw new BadRequestException('Storage label already in use by another account'); - } - } - - if (dto.password) { - dto.password = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS); - } - - if (dto.pinCode) { - dto.pinCode = await this.cryptoRepository.hashBcrypt(dto.pinCode, SALT_ROUNDS); - } - - if (dto.storageLabel === '') { - dto.storageLabel = null; - } - - const updatedUser = await this.userRepository.update(id, { ...dto, updatedAt: new Date() }); - - return mapUserAdmin(updatedUser); - } - - async delete(auth: AuthDto, id: string, dto: UserAdminDeleteDto): Promise { - const { force } = dto; - await this.findOrFail(id, {}); - if (auth.user.id === id) { - throw new ForbiddenException('Cannot delete your own account'); - } - - await this.albumRepository.softDeleteAll(id); - - const status = force ? UserStatus.Removing : UserStatus.Deleted; - const user = await this.userRepository.update(id, { status, deletedAt: new Date() }); - - await this.eventRepository.emit('UserTrash', user); - - if (force) { - await this.jobRepository.queue({ name: JobName.UserDelete, data: { id: user.id, force } }); - } - - return mapUserAdmin(user); - } - - async restore(auth: AuthDto, id: string): Promise { - await this.findOrFail(id, { withDeleted: true }); - await this.albumRepository.restoreAll(id); - const user = await this.userRepository.restore(id); - await this.eventRepository.emit('UserRestore', user); - return mapUserAdmin(user); - } - - async getSessions(auth: AuthDto, id: string): Promise { - const sessions = await this.sessionRepository.getByUserId(id); - return sessions.map((session) => mapSession(session)); - } - - async getStatistics(auth: AuthDto, id: string, dto: AssetStatsDto): Promise { - const stats = await this.assetRepository.getStatistics(id, dto); - return mapStats(stats); - } - - async getPreferences(auth: AuthDto, id: string): Promise { - await this.findOrFail(id, { withDeleted: true }); - const metadata = await this.userRepository.getMetadata(id); - return mapPreferences(getPreferences(metadata)); - } - - async updatePreferences(auth: AuthDto, id: string, dto: UserPreferencesUpdateDto) { - await this.findOrFail(id, { withDeleted: false }); - const metadata = await this.userRepository.getMetadata(id); - const newPreferences = mergePreferences(getPreferences(metadata), dto); - - await this.userRepository.upsertMetadata(id, { - key: UserMetadataKey.Preferences, - value: getPreferencesPartial(newPreferences), - }); - - return mapPreferences(newPreferences); - } - - private async findOrFail(id: string, options: UserFindOptions) { - const user = await this.userRepository.get(id, options); - if (!user) { - throw new BadRequestException('User not found'); - } - return user; - } -} diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts deleted file mode 100644 index bd896ffc24..0000000000 --- a/server/src/services/user.service.spec.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { UserAdmin } from 'src/database'; -import { CacheControl, JobName, UserMetadataKey } from 'src/enum'; -import { UserService } from 'src/services/user.service'; -import { ImmichFileResponse } from 'src/utils/file'; -import { authStub } from 'test/fixtures/auth.stub'; -import { systemConfigStub } from 'test/fixtures/system-config.stub'; -import { userStub } from 'test/fixtures/user.stub'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const makeDeletedAt = (daysAgo: number) => { - const deletedAt = new Date(); - deletedAt.setDate(deletedAt.getDate() - daysAgo); - return deletedAt; -}; - -describe(UserService.name, () => { - let sut: UserService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(UserService)); - mocks.user.get.mockImplementation((userId) => - Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), - ); - }); - - describe('getAll', () => { - it('admin should get all users', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - - mocks.user.getList.mockResolvedValue([user]); - - await expect(sut.search(auth)).resolves.toEqual([expect.objectContaining({ id: user.id, email: user.email })]); - - expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: false }); - }); - - it('non-admin should get all users when publicUsers enabled', async () => { - const user = factory.userAdmin(); - const auth = factory.auth({ user }); - - mocks.user.getList.mockResolvedValue([user]); - - await expect(sut.search(auth)).resolves.toEqual([expect.objectContaining({ id: user.id, email: user.email })]); - - expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: false }); - }); - - it('non-admin user should only receive itself when publicUsers is disabled', async () => { - mocks.user.getList.mockResolvedValue([userStub.user1]); - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.publicUsersDisabled); - - await expect(sut.search(authStub.user1)).resolves.toEqual([ - expect.objectContaining({ - id: authStub.user1.user.id, - email: authStub.user1.user.email, - }), - ]); - - expect(mocks.user.getList).not.toHaveBeenCalledWith({ withDeleted: false }); - }); - }); - - describe('get', () => { - it('should get a user by id', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - - await sut.get(authStub.admin.user.id); - - expect(mocks.user.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false }); - }); - - it('should throw an error if a user is not found', async () => { - mocks.user.get.mockResolvedValue(void 0); - - await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false }); - }); - }); - - describe('getMe', () => { - it("should get the auth user's info", async () => { - const user = authStub.admin.user; - - await expect(sut.getMe(authStub.admin)).resolves.toMatchObject({ - id: user.id, - email: user.email, - }); - }); - }); - - describe('createProfileImage', () => { - it('should throw an error if the user does not exist', async () => { - const file = { path: '/profile/path' } as Express.Multer.File; - - mocks.user.get.mockResolvedValue(void 0); - mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); - - await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(BadRequestException); - }); - - it('should throw an error if the user profile could not be updated with the new image', async () => { - const file = { path: '/profile/path' } as Express.Multer.File; - const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' }); - mocks.user.get.mockResolvedValue(user); - mocks.user.update.mockRejectedValue(new InternalServerErrorException('mocked error')); - - await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(InternalServerErrorException); - }); - - it('should delete the previous profile image', async () => { - const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' }); - const file = { path: '/profile/path' } as Express.Multer.File; - const files = [user.profileImagePath]; - - mocks.user.get.mockResolvedValue(user); - mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); - - await sut.createProfileImage(authStub.admin, file); - - expect(mocks.job.queue.mock.calls).toEqual([[{ name: JobName.FileDelete, data: { files } }]]); - }); - - it('should not delete the profile image if it has not been set', async () => { - const file = { path: '/profile/path' } as Express.Multer.File; - - mocks.user.get.mockResolvedValue(userStub.admin); - mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); - - await sut.createProfileImage(authStub.admin, file); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - }); - - describe('deleteProfileImage', () => { - it('should send an http error has no profile image', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - - await expect(sut.deleteProfileImage(authStub.admin)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).not.toHaveBeenCalled(); - }); - - it('should delete the profile image if user has one', async () => { - const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' }); - const files = [user.profileImagePath]; - - mocks.user.get.mockResolvedValue(user); - - await sut.deleteProfileImage(authStub.admin); - - expect(mocks.job.queue.mock.calls).toEqual([[{ name: JobName.FileDelete, data: { files } }]]); - }); - }); - - describe('getUserProfileImage', () => { - it('should throw an error if the user does not exist', async () => { - mocks.user.get.mockResolvedValue(void 0); - - await expect(sut.getProfileImage(userStub.admin.id)).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.user.get).toHaveBeenCalledWith(userStub.admin.id, {}); - }); - - it('should throw an error if the user does not have a picture', async () => { - mocks.user.get.mockResolvedValue(userStub.admin); - - await expect(sut.getProfileImage(userStub.admin.id)).rejects.toBeInstanceOf(NotFoundException); - - expect(mocks.user.get).toHaveBeenCalledWith(userStub.admin.id, {}); - }); - - it('should return the profile picture', async () => { - const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' }); - mocks.user.get.mockResolvedValue(user); - - await expect(sut.getProfileImage(user.id)).resolves.toEqual( - new ImmichFileResponse({ - path: '/path/to/profile.jpg', - contentType: 'image/jpeg', - cacheControl: CacheControl.None, - }), - ); - - expect(mocks.user.get).toHaveBeenCalledWith(user.id, {}); - }); - }); - - describe('handleQueueUserDelete', () => { - it('should skip users not ready for deletion', async () => { - mocks.user.getDeletedAfter.mockResolvedValue([]); - - await sut.handleUserDeleteCheck(); - - expect(mocks.user.getDeletedAfter).toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([]); - }); - - it('should queue user ready for deletion', async () => { - const user = factory.user(); - mocks.user.getDeletedAfter.mockResolvedValue([{ id: user.id }]); - - await sut.handleUserDeleteCheck(); - - expect(mocks.user.getDeletedAfter).toHaveBeenCalled(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.UserDelete, data: { id: user.id } }]); - }); - }); - - describe('handleUserDelete', () => { - it('should skip users not ready for deletion', async () => { - const user = { id: 'user-1', deletedAt: makeDeletedAt(5) } as UserAdmin; - - mocks.user.get.mockResolvedValue(user); - - await sut.handleUserDelete({ id: user.id }); - - expect(mocks.storage.unlinkDir).not.toHaveBeenCalled(); - expect(mocks.user.delete).not.toHaveBeenCalled(); - }); - - it('should delete the user and associated assets', async () => { - const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) } as UserAdmin; - const options = { force: true, recursive: true }; - - mocks.user.get.mockResolvedValue(user); - - await sut.handleUserDelete({ id: user.id }); - - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith( - expect.stringContaining('/data/library/deleted-user'), - options, - ); - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith( - expect.stringContaining('/data/upload/deleted-user'), - options, - ); - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith( - expect.stringContaining('/data/profile/deleted-user'), - options, - ); - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith( - expect.stringContaining('/data/thumbs/deleted-user'), - options, - ); - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith( - expect.stringContaining('/data/encoded-video/deleted-user'), - options, - ); - expect(mocks.album.deleteAll).toHaveBeenCalledWith(user.id); - expect(mocks.user.delete).toHaveBeenCalledWith(user, true); - }); - - it('should delete the library path for a storage label', async () => { - const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10), storageLabel: 'admin' } as UserAdmin; - - mocks.user.get.mockResolvedValue(user); - - await sut.handleUserDelete({ id: user.id }); - - const options = { force: true, recursive: true }; - - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith(expect.stringContaining('data/library/admin'), options); - }); - }); - - describe('setLicense', () => { - it('should save client license if valid', async () => { - const license = { licenseKey: 'IMCL-license-key', activationKey: 'activation-key' }; - - mocks.user.upsertMetadata.mockResolvedValue(); - - await sut.setLicense(authStub.user1, license); - - expect(mocks.user.upsertMetadata).toHaveBeenCalledWith(authStub.user1.user.id, { - key: UserMetadataKey.License, - value: expect.any(Object), - }); - }); - - it('should save server license as client if valid', async () => { - const license = { licenseKey: 'IMSV-license-key', activationKey: 'activation-key' }; - - mocks.user.upsertMetadata.mockResolvedValue(); - - await sut.setLicense(authStub.user1, license); - - expect(mocks.user.upsertMetadata).toHaveBeenCalledWith(authStub.user1.user.id, { - key: UserMetadataKey.License, - value: expect.any(Object), - }); - }); - - it('should not save license if invalid', async () => { - const license = { licenseKey: 'license-key', activationKey: 'activation-key' }; - const call = sut.setLicense(authStub.admin, license); - - mocks.user.upsertMetadata.mockResolvedValue(); - - await expect(call).rejects.toThrowError('Invalid license key'); - - expect(mocks.user.upsertMetadata).not.toHaveBeenCalled(); - }); - }); - - describe('deleteLicense', () => { - it('should delete license', async () => { - mocks.user.upsertMetadata.mockResolvedValue(); - - await sut.deleteLicense(authStub.admin); - - expect(mocks.user.upsertMetadata).not.toHaveBeenCalled(); - }); - }); - - describe('handleUserSyncUsage', () => { - it('should sync usage', async () => { - await sut.handleUserSyncUsage(); - - expect(mocks.user.syncUsage).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 9fb1f45e54..1ea78cb0b2 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -1,281 +1,33 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -import { Updateable } from 'kysely'; -import { DateTime } from 'luxon'; -import { SALT_ROUNDS } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; -import { OnJob } from 'src/decorators'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { AuthDto } from 'src/dtos/auth.dto'; -import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto'; -import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto'; -import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto'; -import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto'; import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto'; -import { CacheControl, JobName, JobStatus, QueueName, StorageFolder, UserMetadataKey } from 'src/enum'; -import { UserFindOptions } from 'src/repositories/user.repository'; -import { UserTable } from 'src/schema/tables/user.table'; import { BaseService } from 'src/services/base.service'; -import { JobOf, UserMetadataItem } from 'src/types'; -import { ImmichFileResponse } from 'src/utils/file'; -import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences'; @Injectable() export class UserService extends BaseService { async search(auth: AuthDto): Promise { - const config = await this.getConfig({ withCache: false }); - - let users; - if (auth.user.isAdmin || config.server.publicUsers) { - users = await this.userRepository.getList({ withDeleted: false }); - } else { - const authUser = await this.userRepository.get(auth.user.id, {}); - users = authUser ? [authUser] : []; - } - + const users = await this.userRepository.getList({ withDeleted: false }); return users.map((user) => mapUser(user)); } async getMe(auth: AuthDto): Promise { - const user = await this.userRepository.get(auth.user.id, {}); + const user = await this.userRepository.getAdmin(auth.user.id); if (!user) { - throw new BadRequestException('User not found'); + throw new NotFoundException('User not found'); } - return mapUserAdmin(user); } - async updateMe({ user }: AuthDto, dto: UserUpdateMeDto): Promise { - if (dto.email) { - const duplicate = await this.userRepository.getByEmail(dto.email); - if (duplicate && duplicate.id !== user.id) { - throw new BadRequestException('Email already in use by another account'); - } - } - - const update: Updateable = { - email: dto.email, - name: dto.name, - avatarColor: dto.avatarColor, - }; - - if (dto.password) { - const hashedPassword = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS); - update.password = hashedPassword; - update.shouldChangePassword = false; - } - - const updatedUser = await this.userRepository.update(user.id, update); - - return mapUserAdmin(updatedUser); - } - - async getMyPreferences(auth: AuthDto): Promise { - const metadata = await this.userRepository.getMetadata(auth.user.id); - return mapPreferences(getPreferences(metadata)); - } - - async updateMyPreferences(auth: AuthDto, dto: UserPreferencesUpdateDto) { - const metadata = await this.userRepository.getMetadata(auth.user.id); - const updated = mergePreferences(getPreferences(metadata), dto); - - await this.userRepository.upsertMetadata(auth.user.id, { - key: UserMetadataKey.Preferences, - value: getPreferencesPartial(updated), - }); - - return mapPreferences(updated); - } - async get(id: string): Promise { - const user = await this.findOrFail(id, { withDeleted: false }); + const user = await this.userRepository.get(id, { withDeleted: false }); + if (!user) { + throw new NotFoundException('User not found'); + } return mapUser(user); } - async createProfileImage(auth: AuthDto, file: Express.Multer.File): Promise { - const { profileImagePath: oldpath } = await this.findOrFail(auth.user.id, { withDeleted: false }); - - const user = await this.userRepository.update(auth.user.id, { - profileImagePath: file.path, - profileChangedAt: new Date(), - }); - - if (oldpath !== '') { - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [oldpath] } }); - } - - return { - userId: user.id, - profileImagePath: user.profileImagePath, - profileChangedAt: user.profileChangedAt, - }; - } - - async deleteProfileImage(auth: AuthDto): Promise { - const user = await this.findOrFail(auth.user.id, { withDeleted: false }); - if (user.profileImagePath === '') { - throw new BadRequestException("Can't delete a missing profile Image"); - } - await this.userRepository.update(auth.user.id, { profileImagePath: '', profileChangedAt: new Date() }); - await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [user.profileImagePath] } }); - } - - async getProfileImage(id: string): Promise { - const user = await this.findOrFail(id, {}); - if (!user.profileImagePath) { - throw new NotFoundException('User does not have a profile image'); - } - - return new ImmichFileResponse({ - path: user.profileImagePath, - contentType: 'image/jpeg', - cacheControl: CacheControl.None, - }); - } - - async getLicense(auth: AuthDto): Promise { - const metadata = await this.userRepository.getMetadata(auth.user.id); - - const license = metadata.find( - (item): item is UserMetadataItem => item.key === UserMetadataKey.License, - ); - if (!license) { - throw new NotFoundException(); - } - return { ...license.value, activatedAt: new Date(license.value.activatedAt) }; - } - - async deleteLicense({ user }: AuthDto): Promise { - await this.userRepository.deleteMetadata(user.id, UserMetadataKey.License); - } - - async setLicense(auth: AuthDto, license: LicenseKeyDto): Promise { - if (!license.licenseKey.startsWith('IMCL-') && !license.licenseKey.startsWith('IMSV-')) { - throw new BadRequestException('Invalid license key'); - } - - const { licensePublicKey } = this.configRepository.getEnv(); - - const clientLicenseValid = this.cryptoRepository.verifySha256( - license.licenseKey, - license.activationKey, - licensePublicKey.client, - ); - - const serverLicenseValid = this.cryptoRepository.verifySha256( - license.licenseKey, - license.activationKey, - licensePublicKey.server, - ); - - if (!clientLicenseValid && !serverLicenseValid) { - throw new BadRequestException('Invalid license key'); - } - - const activatedAt = new Date(); - - await this.userRepository.upsertMetadata(auth.user.id, { - key: UserMetadataKey.License, - value: { ...license, activatedAt: activatedAt.toISOString() }, - }); - - return { ...license, activatedAt }; - } - - async getOnboarding(auth: AuthDto): Promise { - const metadata = await this.userRepository.getMetadata(auth.user.id); - - const onboardingData = metadata.find( - (item): item is UserMetadataItem => item.key === UserMetadataKey.Onboarding, - )?.value; - - if (!onboardingData) { - return { isOnboarded: false }; - } - - return { - isOnboarded: onboardingData.isOnboarded, - }; - } - - async deleteOnboarding({ user }: AuthDto): Promise { - await this.userRepository.deleteMetadata(user.id, UserMetadataKey.Onboarding); - } - - async setOnboarding(auth: AuthDto, onboarding: OnboardingDto): Promise { - await this.userRepository.upsertMetadata(auth.user.id, { - key: UserMetadataKey.Onboarding, - value: { - isOnboarded: onboarding.isOnboarded, - }, - }); - - return { - isOnboarded: onboarding.isOnboarded, - }; - } - - @OnJob({ name: JobName.UserSyncUsage, queue: QueueName.BackgroundTask }) - async handleUserSyncUsage(): Promise { - await this.userRepository.syncUsage(); - return JobStatus.Success; - } - - @OnJob({ name: JobName.UserDeleteCheck, queue: QueueName.BackgroundTask }) - async handleUserDeleteCheck(): Promise { - const config = await this.getConfig({ withCache: false }); - const users = await this.userRepository.getDeletedAfter(DateTime.now().minus({ days: config.user.deleteDelay })); - await this.jobRepository.queueAll(users.map((user) => ({ name: JobName.UserDelete, data: { id: user.id } }))); - return JobStatus.Success; - } - - @OnJob({ name: JobName.UserDelete, queue: QueueName.BackgroundTask }) - async handleUserDelete({ id, force }: JobOf) { - const config = await this.getConfig({ withCache: false }); - const user = await this.userRepository.get(id, { withDeleted: true }); - if (!user) { - return; - } - - // just for extra protection here - if (!force && !this.isReadyForDeletion(user, config.user.deleteDelay)) { - this.logger.warn(`Skipped user that was not ready for deletion: id=${id}`); - return; - } - - this.logger.log(`Deleting user: ${user.id}`); - - const folders = [ - StorageCore.getLibraryFolder(user), - StorageCore.getFolderLocation(StorageFolder.Upload, user.id), - StorageCore.getFolderLocation(StorageFolder.Profile, user.id), - StorageCore.getFolderLocation(StorageFolder.Thumbnails, user.id), - StorageCore.getFolderLocation(StorageFolder.EncodedVideo, user.id), - ]; - - for (const folder of folders) { - this.logger.warn(`Removing user from filesystem: ${folder}`); - await this.storageRepository.unlinkDir(folder, { recursive: true, force: true }); - } - - this.logger.warn(`Removing user from database: ${user.id}`); - await this.albumRepository.deleteAll(user.id); - await this.userRepository.delete(user, true); - - await this.eventRepository.emit('UserDelete', user); - } - - private isReadyForDeletion(user: { id: string; deletedAt?: Date | null }, deleteDelay: number): boolean { - if (!user.deletedAt) { - return false; - } - - return DateTime.now().minus({ days: deleteDelay }) > DateTime.fromJSDate(user.deletedAt); - } - - private async findOrFail(id: string, options: UserFindOptions) { - const user = await this.userRepository.get(id, options); - if (!user) { - throw new BadRequestException('User not found'); - } - return user; + async updateMe(auth: AuthDto, dto: UserUpdateMeDto): Promise { + const user = await this.userRepository.update(auth.user.id, dto); + return mapUserAdmin(user); } } diff --git a/server/src/services/version.service.spec.ts b/server/src/services/version.service.spec.ts deleted file mode 100644 index 7872f720a9..0000000000 --- a/server/src/services/version.service.spec.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { DateTime } from 'luxon'; -import { SemVer } from 'semver'; -import { serverVersion } from 'src/constants'; -import { ImmichEnvironment, JobName, JobStatus, SystemMetadataKey } from 'src/enum'; -import { VersionService } from 'src/services/version.service'; -import { mockEnvData } from 'test/repositories/config.repository.mock'; -import { factory } from 'test/small.factory'; -import { newTestService, ServiceMocks } from 'test/utils'; - -const mockRelease = (version: string) => ({ - id: 1, - url: 'https://api.github.com/repos/owner/repo/releases/1', - tag_name: version, - name: 'Release 1000', - created_at: DateTime.utc().toISO(), - published_at: DateTime.utc().toISO(), - body: '', -}); - -describe(VersionService.name, () => { - let sut: VersionService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(VersionService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('onBootstrap', () => { - it('should record a new version', async () => { - mocks.versionHistory.getAll.mockResolvedValue([]); - mocks.versionHistory.getLatest.mockResolvedValue(void 0); - mocks.versionHistory.create.mockResolvedValue(factory.versionHistory()); - - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - - expect(mocks.versionHistory.create).toHaveBeenCalledWith({ version: expect.any(String) }); - }); - - it('should skip a duplicate version', async () => { - mocks.versionHistory.getLatest.mockResolvedValue({ - id: 'version-1', - createdAt: new Date(), - version: serverVersion.toString(), - }); - await expect(sut.onBootstrap()).resolves.toBeUndefined(); - expect(mocks.versionHistory.create).not.toHaveBeenCalled(); - }); - }); - - describe('getVersion', () => { - it('should respond the server version', () => { - expect(sut.getVersion()).toEqual({ - major: serverVersion.major, - minor: serverVersion.minor, - patch: serverVersion.patch, - }); - }); - }); - - describe('getVersionHistory', () => { - it('should respond the server version history', async () => { - const upgrade = { id: 'upgrade-1', createdAt: new Date(), version: '1.0.0' }; - mocks.versionHistory.getAll.mockResolvedValue([upgrade]); - await expect(sut.getVersionHistory()).resolves.toEqual([upgrade]); - }); - }); - - describe('handQueueVersionCheck', () => { - it('should queue a version check job', async () => { - await expect(sut.handleQueueVersionCheck()).resolves.toBeUndefined(); - expect(mocks.job.queue).toHaveBeenCalledWith({ name: JobName.VersionCheck, data: {} }); - }); - }); - - describe('handVersionCheck', () => { - beforeEach(() => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ environment: ImmichEnvironment.Production })); - }); - - it('should not run in dev mode', async () => { - mocks.config.getEnv.mockReturnValue(mockEnvData({ environment: ImmichEnvironment.Development })); - await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Skipped); - }); - - it('should not run if the last check was < 60 minutes ago', async () => { - mocks.systemMetadata.get.mockResolvedValue({ - checkedAt: DateTime.utc().minus({ minutes: 5 }).toISO(), - releaseVersion: '1.0.0', - }); - await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Skipped); - }); - - it('should not run if version check is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValue({ newVersionCheck: { enabled: false } }); - await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Skipped); - }); - - it('should run if it has been > 60 minutes', async () => { - mocks.serverInfo.getGitHubRelease.mockResolvedValue(mockRelease('v100.0.0')); - mocks.systemMetadata.get.mockResolvedValue({ - checkedAt: DateTime.utc().minus({ minutes: 65 }).toISO(), - releaseVersion: '1.0.0', - }); - await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success); - expect(mocks.systemMetadata.set).toHaveBeenCalled(); - expect(mocks.logger.log).toHaveBeenCalled(); - expect(mocks.websocket.clientBroadcast).toHaveBeenCalled(); - }); - - it('should not notify if the version is equal', async () => { - mocks.serverInfo.getGitHubRelease.mockResolvedValue(mockRelease(serverVersion.toString())); - await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success); - expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.VersionCheckState, { - checkedAt: expect.any(String), - releaseVersion: serverVersion.toString(), - }); - expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled(); - }); - - it('should handle a github error', async () => { - mocks.serverInfo.getGitHubRelease.mockRejectedValue(new Error('GitHub is down')); - await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Failed); - expect(mocks.systemMetadata.set).not.toHaveBeenCalled(); - expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled(); - expect(mocks.logger.warn).toHaveBeenCalled(); - }); - }); - - describe('onWebsocketConnection', () => { - it('should send on_server_version client event', async () => { - await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); - expect(mocks.websocket.clientSend).toHaveBeenCalledTimes(1); - }); - - it('should also send a new release notification', async () => { - mocks.systemMetadata.get.mockResolvedValue({ checkedAt: '2024-01-01', releaseVersion: 'v1.42.0' }); - await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_new_release', '42', expect.any(Object)); - }); - - it('should not send a release notification when the version check is disabled', async () => { - mocks.systemMetadata.get.mockResolvedValueOnce({ newVersionCheck: { enabled: false } }); - await sut.onWebsocketConnection({ userId: '42' }); - expect(mocks.websocket.clientSend).toHaveBeenCalledWith('on_server_version', '42', expect.any(SemVer)); - expect(mocks.websocket.clientSend).not.toHaveBeenCalledWith('on_new_release', '42', expect.any(Object)); - }); - }); -}); diff --git a/server/src/services/version.service.ts b/server/src/services/version.service.ts deleted file mode 100644 index fd51fa9adf..0000000000 --- a/server/src/services/version.service.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { DateTime } from 'luxon'; -import semver, { SemVer } from 'semver'; -import { serverVersion } from 'src/constants'; -import { OnEvent, OnJob } from 'src/decorators'; -import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; -import { DatabaseLock, ImmichEnvironment, JobName, JobStatus, QueueName, SystemMetadataKey } from 'src/enum'; -import { ArgOf } from 'src/repositories/event.repository'; -import { BaseService } from 'src/services/base.service'; -import { VersionCheckMetadata } from 'src/types'; - -const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => { - return { - isAvailable: semver.gt(releaseVersion, serverVersion), - checkedAt, - serverVersion: ServerVersionResponseDto.fromSemVer(serverVersion), - releaseVersion: ServerVersionResponseDto.fromSemVer(new SemVer(releaseVersion)), - }; -}; - -@Injectable() -export class VersionService extends BaseService { - @OnEvent({ name: 'AppBootstrap' }) - async onBootstrap(): Promise { - await this.handleVersionCheck(); - - await this.databaseRepository.withLock(DatabaseLock.VersionHistory, async () => { - const previous = await this.versionRepository.getLatest(); - const current = serverVersion.toString(); - - if (!previous) { - await this.versionRepository.create({ version: current }); - return; - } - - if (previous.version !== current) { - const previousVersion = new SemVer(previous.version); - - this.logger.log(`Adding ${current} to upgrade history`); - await this.versionRepository.create({ version: current }); - - const needsNewMemories = semver.lt(previousVersion, '1.129.0'); - if (needsNewMemories) { - await this.jobRepository.queue({ name: JobName.MemoryGenerate }); - } - } - }); - } - - getVersion() { - return ServerVersionResponseDto.fromSemVer(serverVersion); - } - - getVersionHistory() { - return this.versionRepository.getAll(); - } - - async handleQueueVersionCheck() { - await this.jobRepository.queue({ name: JobName.VersionCheck, data: {} }); - } - - @OnJob({ name: JobName.VersionCheck, queue: QueueName.BackgroundTask }) - async handleVersionCheck(): Promise { - try { - this.logger.debug('Running version check'); - - const { environment } = this.configRepository.getEnv(); - if (environment === ImmichEnvironment.Development) { - return JobStatus.Skipped; - } - - const { newVersionCheck } = await this.getConfig({ withCache: true }); - if (!newVersionCheck.enabled) { - return JobStatus.Skipped; - } - - const versionCheck = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState); - if (versionCheck?.checkedAt) { - const lastUpdate = DateTime.fromISO(versionCheck.checkedAt); - const elapsedTime = DateTime.now().diff(lastUpdate).as('minutes'); - // check once per hour (max) - if (elapsedTime < 60) { - return JobStatus.Skipped; - } - } - - const { tag_name: releaseVersion, published_at: publishedAt } = - await this.serverInfoRepository.getGitHubRelease(); - const metadata: VersionCheckMetadata = { checkedAt: DateTime.utc().toISO(), releaseVersion }; - - await this.systemMetadataRepository.set(SystemMetadataKey.VersionCheckState, metadata); - - if (semver.gt(releaseVersion, serverVersion)) { - this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`); - this.websocketRepository.clientBroadcast('on_new_release', asNotification(metadata)); - } - } catch (error: Error | any) { - this.logger.warn(`Unable to run version check: ${error}\n${error?.stack}`); - return JobStatus.Failed; - } - - return JobStatus.Success; - } - - @OnEvent({ name: 'WebsocketConnect' }) - async onWebsocketConnection({ userId }: ArgOf<'WebsocketConnect'>) { - this.websocketRepository.clientSend('on_server_version', userId, serverVersion); - - const { newVersionCheck } = await this.getConfig({ withCache: true }); - if (!newVersionCheck.enabled) { - return; - } - - const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState); - if (metadata) { - this.websocketRepository.clientSend('on_new_release', userId, asNotification(metadata)); - } - } -} diff --git a/server/src/services/view.service.spec.ts b/server/src/services/view.service.spec.ts deleted file mode 100644 index 86bfcef734..0000000000 --- a/server/src/services/view.service.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { mapAsset } from 'src/dtos/asset-response.dto'; -import { ViewService } from 'src/services/view.service'; -import { assetStub } from 'test/fixtures/asset.stub'; -import { authStub } from 'test/fixtures/auth.stub'; -import { newTestService, ServiceMocks } from 'test/utils'; - -describe(ViewService.name, () => { - let sut: ViewService; - let mocks: ServiceMocks; - - beforeEach(() => { - ({ sut, mocks } = newTestService(ViewService)); - }); - - it('should work', () => { - expect(sut).toBeDefined(); - }); - - describe('getUniqueOriginalPaths', () => { - it('should return unique original paths', async () => { - const mockPaths = ['path1', 'path2', 'path3']; - mocks.view.getUniqueOriginalPaths.mockResolvedValue(mockPaths); - - const result = await sut.getUniqueOriginalPaths(authStub.admin); - - expect(result).toEqual(mockPaths); - expect(mocks.view.getUniqueOriginalPaths).toHaveBeenCalledWith(authStub.admin.user.id); - }); - }); - - describe('getAssetsByOriginalPath', () => { - it('should return assets by original path', async () => { - const path = '/asset'; - - const asset1 = { ...assetStub.image, originalPath: '/asset/path1' }; - const asset2 = { ...assetStub.image, originalPath: '/asset/path2' }; - - const mockAssets = [asset1, asset2]; - - const mockAssetReponseDto = mockAssets.map((a) => mapAsset(a, { auth: authStub.admin })); - - mocks.view.getAssetsByOriginalPath.mockResolvedValue(mockAssets as any); - - const result = await sut.getAssetsByOriginalPath(authStub.admin, path); - expect(result).toEqual(mockAssetReponseDto); - await expect(mocks.view.getAssetsByOriginalPath(authStub.admin.user.id, path)).resolves.toEqual(mockAssets); - }); - }); -}); diff --git a/server/src/services/view.service.ts b/server/src/services/view.service.ts deleted file mode 100644 index 9d1ee3cf89..0000000000 --- a/server/src/services/view.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class ViewService extends BaseService { - getUniqueOriginalPaths(auth: AuthDto): Promise { - return this.viewRepository.getUniqueOriginalPaths(auth.user.id); - } - - async getAssetsByOriginalPath(auth: AuthDto, path: string): Promise { - const assets = await this.viewRepository.getAssetsByOriginalPath(auth.user.id, path); - return assets.map((asset) => mapAsset(asset, { auth })); - } -} diff --git a/server/src/services/workflow.service.ts b/server/src/services/workflow.service.ts deleted file mode 100644 index 1a65182b1f..0000000000 --- a/server/src/services/workflow.service.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { Workflow } from 'src/database'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { - mapWorkflowAction, - mapWorkflowFilter, - WorkflowCreateDto, - WorkflowResponseDto, - WorkflowUpdateDto, -} from 'src/dtos/workflow.dto'; -import { Permission, PluginContext, PluginTriggerType } from 'src/enum'; -import { pluginTriggers } from 'src/plugins'; - -import { BaseService } from 'src/services/base.service'; - -@Injectable() -export class WorkflowService extends BaseService { - async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { - const context = this.getContextForTrigger(dto.triggerType); - - const filterInserts = await this.validateAndMapFilters(dto.filters, context); - const actionInserts = await this.validateAndMapActions(dto.actions, context); - - const workflow = await this.workflowRepository.createWorkflow( - { - ownerId: auth.user.id, - triggerType: dto.triggerType, - name: dto.name, - description: dto.description || '', - enabled: dto.enabled ?? true, - }, - filterInserts, - actionInserts, - ); - - return this.mapWorkflow(workflow); - } - - async getAll(auth: AuthDto): Promise { - const workflows = await this.workflowRepository.getWorkflowsByOwner(auth.user.id); - - return Promise.all(workflows.map((workflow) => this.mapWorkflow(workflow))); - } - - async get(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); - const workflow = await this.findOrFail(id); - return this.mapWorkflow(workflow); - } - - async update(auth: AuthDto, id: string, dto: WorkflowUpdateDto): Promise { - await this.requireAccess({ auth, permission: Permission.WorkflowUpdate, ids: [id] }); - - if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) { - throw new BadRequestException('No fields to update'); - } - - const workflow = await this.findOrFail(id); - const context = this.getContextForTrigger(dto.triggerType ?? workflow.triggerType); - - const { filters, actions, ...workflowUpdate } = dto; - const filterInserts = filters && (await this.validateAndMapFilters(filters, context)); - const actionInserts = actions && (await this.validateAndMapActions(actions, context)); - - const updatedWorkflow = await this.workflowRepository.updateWorkflow( - id, - workflowUpdate, - filterInserts, - actionInserts, - ); - - return this.mapWorkflow(updatedWorkflow); - } - - async delete(auth: AuthDto, id: string): Promise { - await this.requireAccess({ auth, permission: Permission.WorkflowDelete, ids: [id] }); - await this.workflowRepository.deleteWorkflow(id); - } - - private async validateAndMapFilters( - filters: Array<{ pluginFilterId: string; filterConfig?: any }>, - requiredContext: PluginContext, - ) { - for (const dto of filters) { - const filter = await this.pluginRepository.getFilter(dto.pluginFilterId); - if (!filter) { - throw new BadRequestException(`Invalid filter ID: ${dto.pluginFilterId}`); - } - - if (!filter.supportedContexts.includes(requiredContext)) { - throw new BadRequestException( - `Filter "${filter.title}" does not support ${requiredContext} context. Supported contexts: ${filter.supportedContexts.join(', ')}`, - ); - } - } - - return filters.map((dto, index) => ({ - pluginFilterId: dto.pluginFilterId, - filterConfig: dto.filterConfig || null, - order: index, - })); - } - - private async validateAndMapActions( - actions: Array<{ pluginActionId: string; actionConfig?: any }>, - requiredContext: PluginContext, - ) { - for (const dto of actions) { - const action = await this.pluginRepository.getAction(dto.pluginActionId); - if (!action) { - throw new BadRequestException(`Invalid action ID: ${dto.pluginActionId}`); - } - if (!action.supportedContexts.includes(requiredContext)) { - throw new BadRequestException( - `Action "${action.title}" does not support ${requiredContext} context. Supported contexts: ${action.supportedContexts.join(', ')}`, - ); - } - } - - return actions.map((dto, index) => ({ - pluginActionId: dto.pluginActionId, - actionConfig: dto.actionConfig || null, - order: index, - })); - } - - private getContextForTrigger(type: PluginTriggerType) { - const trigger = pluginTriggers.find((t) => t.type === type); - if (!trigger) { - throw new BadRequestException(`Invalid trigger type: ${type}`); - } - return trigger.contextType; - } - - private async findOrFail(id: string) { - const workflow = await this.workflowRepository.getWorkflow(id); - if (!workflow) { - throw new BadRequestException('Workflow not found'); - } - return workflow; - } - - private async mapWorkflow(workflow: Workflow): Promise { - const filters = await this.workflowRepository.getFilters(workflow.id); - const actions = await this.workflowRepository.getActions(workflow.id); - - return { - id: workflow.id, - ownerId: workflow.ownerId, - triggerType: workflow.triggerType, - name: workflow.name, - description: workflow.description, - createdAt: workflow.createdAt.toISOString(), - enabled: workflow.enabled, - filters: filters.map((f) => mapWorkflowFilter(f)), - actions: actions.map((a) => mapWorkflowAction(a)), - }; - } -} diff --git a/server/src/types.ts b/server/src/types.ts index 3e9ea25957..6cb5ea78c4 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,27 +1,6 @@ import { SystemConfig } from 'src/config'; import { VECTOR_EXTENSIONS } from 'src/constants'; -import { Asset, AssetFile } from 'src/database'; -import { UploadFieldName } from 'src/dtos/asset-media.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { SetMaintenanceModeDto } from 'src/dtos/maintenance.dto'; -import { - AssetOrder, - AssetType, - DatabaseSslMode, - ExifOrientation, - ImageFormat, - JobName, - MemoryType, - PluginTriggerType, - QueueName, - StorageFolder, - SyncEntityType, - SystemMetadataKey, - TranscodeTarget, - UserMetadataKey, - VideoCodec, -} from 'src/enum'; +import { DatabaseExtension, JobName, QueueName, SystemMetadataKey } from 'src/enum'; export type DeepPartial = T extends Record @@ -32,537 +11,43 @@ export type DeepPartial = export type RepositoryInterface = Pick; -export type FullsizeImageOptions = { - format: ImageFormat; - quality: number; - enabled: boolean; - progressive?: boolean; +export type SystemFlags = { + mountFiles?: boolean; }; -export type ImageOptions = { - format: ImageFormat; - quality: number; - size: number; - progressive?: boolean; +export type SystemMetadata = Record> & { + [SystemMetadataKey.AdminOnboarding]: { isOnboarded: boolean }; + [SystemMetadataKey.SystemConfig]: SystemConfig; + [SystemMetadataKey.SystemFlags]: SystemFlags; }; -export type RawImageInfo = { - width: number; - height: number; - channels: 1 | 2 | 3 | 4; -}; - -type DecodeImageOptions = { - colorspace: string; - processInvalidImages: boolean; - raw?: RawImageInfo; - edits?: AssetEditActionItem[]; -}; - -export interface DecodeToBufferOptions extends DecodeImageOptions { - size?: number; - orientation?: ExifOrientation; -} - -export type GenerateThumbnailOptions = Pick & DecodeToBufferOptions; - -export type GenerateThumbnailFromBufferOptions = GenerateThumbnailOptions & { raw: RawImageInfo }; - -export type GenerateThumbhashOptions = DecodeImageOptions; - -export type GenerateThumbhashFromBufferOptions = GenerateThumbhashOptions & { raw: RawImageInfo }; - -export interface GenerateThumbnailsOptions { - colorspace: string; - preview?: ImageOptions; - processInvalidImages: boolean; - thumbhash?: boolean; - thumbnail?: ImageOptions; -} - -export interface VideoStreamInfo { - index: number; - height: number; - width: number; - rotation: number; - codecName?: string; - frameCount: number; - isHDR: boolean; - bitrate: number; - pixelFormat: string; - colorPrimaries?: string; - colorSpace?: string; - colorTransfer?: string; -} - -export interface AudioStreamInfo { - index: number; - codecName?: string; - bitrate: number; -} - -export interface VideoFormat { - formatName?: string; - formatLongName?: string; - duration: number; - bitrate: number; -} - -export interface ImageDimensions { - width: number; - height: number; -} - -export interface InputDimensions extends ImageDimensions { - inputPath: string; -} - -export interface VideoInfo { - format: VideoFormat; - videoStreams: VideoStreamInfo[]; - audioStreams: AudioStreamInfo[]; -} - -export interface TranscodeCommand { - inputOptions: string[]; - outputOptions: string[]; - twoPass: boolean; - progress: { - frameCount: number; - percentInterval: number; - }; -} - -export interface BitrateDistribution { - max: number; - target: number; - min: number; - unit: string; -} - -export interface ImageBuffer { - data: Buffer; - info: RawImageInfo; -} - -export interface VideoCodecSWConfig { - getCommand( - target: TranscodeTarget, - videoStream: VideoStreamInfo, - audioStream: AudioStreamInfo, - format?: VideoFormat, - ): TranscodeCommand; -} - -export interface VideoCodecHWConfig extends VideoCodecSWConfig { - getSupportedCodecs(): Array; -} - -export interface ProbeOptions { - countFrames: boolean; -} - -export interface VideoInterfaces { - dri: string[]; - mali: boolean; -} - -export type ConcurrentQueueName = Exclude< - QueueName, - | QueueName.StorageTemplateMigration - | QueueName.FacialRecognition - | QueueName.DuplicateDetection - | QueueName.BackupDatabase ->; - -export type Jobs = { [K in JobItem['name']]: (JobItem & { name: K })['data'] }; -export type JobOf = Jobs[T]; - -export interface IBaseJob { - force?: boolean; -} - -export interface IDelayedJob extends IBaseJob { - /** The minimum time to wait to execute this job, in milliseconds. */ - delay?: number; -} - -export type JobSource = 'upload' | 'sidecar-write' | 'copy' | 'edit'; -export interface IEntityJob extends IBaseJob { - id: string; - source?: JobSource; - notify?: boolean; -} - -export interface IAssetDeleteJob extends IEntityJob { - deleteOnDisk: boolean; -} - -export interface ILibraryFileJob { - libraryId: string; - paths: string[]; - progressCounter?: number; - totalAssets?: number; -} - -export interface ILibraryBulkIdsJob { - libraryId: string; - importPaths: string[]; - exclusionPatterns: string[]; - assetIds: string[]; - progressCounter: number; - totalAssets: number; -} - -export interface IBulkEntityJob { - ids: string[]; -} - -export interface IDeleteFilesJob extends IBaseJob { - files: Array; -} - -export interface ISidecarWriteJob extends IEntityJob { - tags?: true; -} - -export interface IDeferrableJob extends IEntityJob { - deferred?: boolean; -} - -export interface INightlyJob extends IBaseJob { - nightly?: boolean; -} - -export type EmailImageAttachment = { - filename: string; - path: string; - cid: string; -}; - -export interface IEmailJob { - to: string; - subject: string; - html: string; - text: string; - imageAttachments?: EmailImageAttachment[]; -} - -export interface INotifySignupJob extends IEntityJob { - password?: string; -} - -export interface INotifyAlbumInviteJob extends IEntityJob { - recipientId: string; -} - -export interface INotifyAlbumUpdateJob extends IEntityJob, IDelayedJob { - recipientId: string; -} - -export interface WorkflowData { - [PluginTriggerType.AssetCreate]: { - userId: string; - asset: Asset; - }; - [PluginTriggerType.PersonRecognized]: { - personId: string; - assetId: string; - }; -} - -export interface IWorkflowJob { - id: string; - type: T; - event: WorkflowData[T]; -} - -export interface JobCounts { - active: number; - completed: number; - failed: number; - delayed: number; - waiting: number; - paused: number; -} - -export type JobItem = - // Audit - | { name: JobName.AuditTableCleanup; data?: IBaseJob } - - // Backups - | { name: JobName.DatabaseBackup; data?: IBaseJob } - - // Transcoding - | { name: JobName.AssetEncodeVideoQueueAll; data: IBaseJob } - | { name: JobName.AssetEncodeVideo; data: IEntityJob } - - // Thumbnails - | { name: JobName.AssetGenerateThumbnailsQueueAll; data: IBaseJob } - | { name: JobName.AssetGenerateThumbnails; data: IEntityJob } - - // User - | { name: JobName.UserDeleteCheck; data?: IBaseJob } - | { name: JobName.UserDelete; data: IEntityJob } - | { name: JobName.UserSyncUsage; data?: IBaseJob } - - // Storage Template - | { name: JobName.StorageTemplateMigration; data?: IBaseJob } - | { name: JobName.StorageTemplateMigrationSingle; data: IEntityJob } - - // Migration - | { name: JobName.FileMigrationQueueAll; data?: IBaseJob } - | { name: JobName.AssetFileMigration; data: IEntityJob } - | { name: JobName.PersonFileMigration; data: IEntityJob } - - // Metadata Extraction - | { name: JobName.AssetExtractMetadataQueueAll; data: IBaseJob } - | { name: JobName.AssetExtractMetadata; data: IEntityJob } - - // Notifications - | { name: JobName.NotificationsCleanup; data?: IBaseJob } - - // Sidecar Scanning - | { name: JobName.SidecarQueueAll; data: IBaseJob } - | { name: JobName.SidecarCheck; data: IEntityJob } - | { name: JobName.SidecarWrite; data: IEntityJob } - - // Facial Recognition - | { name: JobName.AssetDetectFacesQueueAll; data: IBaseJob } - | { name: JobName.AssetDetectFaces; data: IEntityJob } - | { name: JobName.FacialRecognitionQueueAll; data: INightlyJob } - | { name: JobName.FacialRecognition; data: IDeferrableJob } - | { name: JobName.PersonGenerateThumbnail; data: IEntityJob } - - // Smart Search - | { name: JobName.SmartSearchQueueAll; data: IBaseJob } - | { name: JobName.SmartSearch; data: IEntityJob } - | { name: JobName.AssetEmptyTrash; data?: IBaseJob } - - // Duplicate Detection - | { name: JobName.AssetDetectDuplicatesQueueAll; data: IBaseJob } - | { name: JobName.AssetDetectDuplicates; data: IEntityJob } - - // Memories - | { name: JobName.MemoryCleanup; data?: IBaseJob } - | { name: JobName.MemoryGenerate; data?: IBaseJob } - - // Filesystem - | { name: JobName.FileDelete; data: IDeleteFilesJob } - - // Cleanup - | { name: JobName.AuditLogCleanup; data?: IBaseJob } - | { name: JobName.SessionCleanup; data?: IBaseJob } - - // Tags - | { name: JobName.TagCleanup; data?: IBaseJob } - - // Asset Deletion - | { name: JobName.PersonCleanup; data?: IBaseJob } - | { name: JobName.AssetDelete; data: IAssetDeleteJob } - | { name: JobName.AssetDeleteCheck; data?: IBaseJob } - - // Library Management - | { name: JobName.LibrarySyncFiles; data: ILibraryFileJob } - | { name: JobName.LibrarySyncFilesQueueAll; data: IEntityJob } - | { name: JobName.LibrarySyncAssetsQueueAll; data: IEntityJob } - | { name: JobName.LibrarySyncAssets; data: ILibraryBulkIdsJob } - | { name: JobName.LibraryRemoveAsset; data: ILibraryFileJob } - | { name: JobName.LibraryDelete; data: IEntityJob } - | { name: JobName.LibraryScanQueueAll; data?: IBaseJob } - | { name: JobName.LibraryDeleteCheck; data: IBaseJob } - - // Notification - | { name: JobName.SendMail; data: IEmailJob } - | { name: JobName.NotifyAlbumInvite; data: INotifyAlbumInviteJob } - | { name: JobName.NotifyAlbumUpdate; data: INotifyAlbumUpdateJob } - | { name: JobName.NotifyUserSignup; data: INotifySignupJob } - - // Version check - | { name: JobName.VersionCheck; data: IBaseJob } - - // OCR - | { name: JobName.OcrQueueAll; data: IBaseJob } - | { name: JobName.Ocr; data: IEntityJob } - - // Workflow - | { name: JobName.WorkflowRun; data: IWorkflowJob } - - // Editor - | { name: JobName.AssetEditThumbnailGeneration; data: IEntityJob }; +type VersionNumber = { major: number; minor: number; patch: number }; export type VectorExtension = (typeof VECTOR_EXTENSIONS)[number]; -export type DatabaseConnectionURL = { - connectionType: 'url'; - url: string; +export type DatabaseExtensionInfo = { + availableVersion: VersionNumber | null; + installedVersion: VersionNumber | null; }; -export type DatabaseConnectionParts = { - connectionType: 'parts'; - host: string; - port: number; - username: string; - password: string; - database: string; - ssl?: DatabaseSslMode; +export type DatabaseExtensionStatus = Record; + +export type JobItem = { name: JobName; data?: Record }; +export type JobHandler = (item: JobItem) => Promise<{ status: string }>; + +export type QueueOptions = { + name: QueueName; + concurrency: number; }; -export type DatabaseConnectionParams = DatabaseConnectionURL | DatabaseConnectionParts; - -export interface ExtensionVersion { - name: VectorExtension; - availableVersion: string | null; - installedVersion: string | null; -} - -export interface VectorUpdateResult { - restartRequired: boolean; -} - -export interface ImmichFile extends Express.Multer.File { - uuid: string; - /** sha1 hash of file */ - checksum: Buffer; -} - -export interface UploadFile { - uuid: string; - checksum: Buffer; - originalPath: string; - originalName: string; - size: number; -} - -export interface UploadBody { - filename?: string; - [key: string]: unknown; -} - -export type UploadRequest = { - auth: AuthDto | null; - fieldName: UploadFieldName; - file: UploadFile; - body: UploadBody; -}; - -export interface UploadFiles { - assetData: ImmichFile[]; - sidecarData: ImmichFile[]; -} - -export interface IBulkAsset { - getAssetIds: (id: string, assetIds: string[]) => Promise>; - addAssetIds: (id: string, assetIds: string[]) => Promise; - removeAssetIds: (id: string, assetIds: string[]) => Promise; -} - -export type SyncAck = { - type: SyncEntityType; - updateId: string; - extraId?: string; -}; - -export type StorageAsset = { - id: string; - ownerId: string; - livePhotoVideoId: string | null; - type: AssetType; - isExternal: boolean; - checksum: Buffer; - timeZone: string | null; - fileCreatedAt: Date; - originalPath: string; - originalFileName: string; - fileSizeInByte: number | null; - files: AssetFile[]; - make: string | null; - model: string | null; - lensModel: string | null; -}; - -export type OnThisDayData = { year: number }; - -export interface MemoryData { - [MemoryType.OnThisDay]: OnThisDayData; -} - -export type VersionCheckMetadata = { checkedAt: string; releaseVersion: string }; -export type SystemFlags = { mountChecks: Record }; -export type MaintenanceModeState = - | { isMaintenanceMode: true; secret: string; action?: SetMaintenanceModeDto } - | { isMaintenanceMode: false }; -export type MemoriesState = { - /** memories have already been created through this date */ - lastOnThisDayDate: string; -}; -export type MediaLocation = { location: string }; - -export interface SystemMetadata extends Record> { - [SystemMetadataKey.AdminOnboarding]: { isOnboarded: boolean }; - [SystemMetadataKey.FacialRecognitionState]: { lastRun?: string }; - [SystemMetadataKey.License]: { licenseKey: string; activationKey: string; activatedAt: Date }; - [SystemMetadataKey.MaintenanceMode]: MaintenanceModeState; - [SystemMetadataKey.MediaLocation]: MediaLocation; - [SystemMetadataKey.ReverseGeocodingState]: { lastUpdate?: string; lastImportFileName?: string }; - [SystemMetadataKey.SystemConfig]: DeepPartial; - [SystemMetadataKey.SystemFlags]: DeepPartial; - [SystemMetadataKey.VersionCheckState]: VersionCheckMetadata; - [SystemMetadataKey.MemoriesState]: MemoriesState; -} - -export type UserPreferences = { - albums: { - defaultAssetOrder: AssetOrder; - }; - folders: { - enabled: boolean; - sidebarWeb: boolean; - }; - memories: { - enabled: boolean; - duration: number; - }; - people: { - enabled: boolean; - sidebarWeb: boolean; - }; - ratings: { - enabled: boolean; - }; - sharedLinks: { - enabled: boolean; - sidebarWeb: boolean; - }; - tags: { - enabled: boolean; - sidebarWeb: boolean; - }; - emailNotifications: { - enabled: boolean; - albumInvite: boolean; - albumUpdate: boolean; - }; - download: { - archiveSize: number; - includeEmbeddedVideos: boolean; - }; - purchase: { - showSupportBadge: boolean; - hideBuyButtonUntil: string; - }; - cast: { - gCastEnabled: boolean; - }; -}; - -export type UserMetadataItem = { - key: T; - value: UserMetadata[T]; -}; - -export interface UserMetadata extends Record> { - [UserMetadataKey.Preferences]: DeepPartial; - [UserMetadataKey.License]: { licenseKey: string; activationKey: string; activatedAt: string }; - [UserMetadataKey.Onboarding]: { isOnboarded: boolean }; -} +export type DatabaseConnectionParams = + | { connectionType: 'url'; url: string } + | { + connectionType: 'parts'; + host: string; + port: number; + username: string; + password: string; + database: string; + ssl?: string; + }; diff --git a/server/src/types/plugin-schema.types.ts b/server/src/types/plugin-schema.types.ts deleted file mode 100644 index 793bb3c1ff..0000000000 --- a/server/src/types/plugin-schema.types.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * JSON Schema types for plugin configuration schemas - * Based on JSON Schema Draft 7 - */ - -export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'; - -export interface JSONSchemaProperty { - type?: JSONSchemaType | JSONSchemaType[]; - description?: string; - default?: any; - enum?: any[]; - items?: JSONSchemaProperty; - properties?: Record; - required?: string[]; - additionalProperties?: boolean | JSONSchemaProperty; -} - -export interface JSONSchema { - type: 'object'; - properties?: Record; - required?: string[]; - additionalProperties?: boolean; - description?: string; -} - -export type ConfigValue = string | number | boolean | null | ConfigValue[] | { [key: string]: ConfigValue }; - -export interface FilterConfig { - [key: string]: ConfigValue; -} - -export interface ActionConfig { - [key: string]: ConfigValue; -} diff --git a/server/src/utils/access.ts b/server/src/utils/access.ts index 7431cb3293..601ecc05f5 100644 --- a/server/src/utils/access.ts +++ b/server/src/utils/access.ts @@ -1,9 +1,5 @@ -import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { AuthSharedLink } from 'src/database'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { AlbumUserRole, Permission } from 'src/enum'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { setDifference, setIsEqual, setIsSuperset, setUnion } from 'src/utils/set'; +import { Permission } from 'src/enum'; +import { setIsSuperset } from 'src/utils/set'; export type GrantedRequest = { requested: Permission[]; @@ -17,313 +13,3 @@ export const isGranted = ({ requested, current }: GrantedRequest) => { return setIsSuperset(new Set(current), new Set(requested)); }; - -export type AccessRequest = { - auth: AuthDto; - permission: Permission; - ids: Set | string[]; -}; - -type SharedLinkAccessRequest = { sharedLink: AuthSharedLink; permission: Permission; ids: Set }; -type OtherAccessRequest = { auth: AuthDto; permission: Permission; ids: Set }; - -export const requireUploadAccess = (auth: AuthDto | null): AuthDto => { - if (!auth || (auth.sharedLink && !auth.sharedLink.allowUpload)) { - throw new UnauthorizedException(); - } - return auth; -}; - -export const requireAccess = async (access: AccessRepository, request: AccessRequest) => { - const allowedIds = await checkAccess(access, request); - if (!setIsEqual(new Set(request.ids), allowedIds)) { - throw new BadRequestException(`Not found or no ${request.permission} access`); - } -}; - -export const checkAccess = async ( - access: AccessRepository, - { ids, auth, permission }: AccessRequest, -): Promise> => { - const idSet = Array.isArray(ids) ? new Set(ids) : ids; - if (idSet.size === 0) { - return new Set(); - } - - return auth.sharedLink - ? checkSharedLinkAccess(access, { sharedLink: auth.sharedLink, permission, ids: idSet }) - : checkOtherAccess(access, { auth, permission, ids: idSet }); -}; - -const checkSharedLinkAccess = async ( - access: AccessRepository, - request: SharedLinkAccessRequest, -): Promise> => { - const { sharedLink, permission, ids } = request; - const sharedLinkId = sharedLink.id; - - switch (permission) { - case Permission.AssetRead: { - return await access.asset.checkSharedLinkAccess(sharedLinkId, ids); - } - - case Permission.AssetView: { - return await access.asset.checkSharedLinkAccess(sharedLinkId, ids); - } - - case Permission.AssetDownload: { - return sharedLink.allowDownload ? await access.asset.checkSharedLinkAccess(sharedLinkId, ids) : new Set(); - } - - case Permission.AssetUpload: { - return sharedLink.allowUpload ? ids : new Set(); - } - - case Permission.AssetShare: { - // TODO: fix this to not use sharedLink.userId for access control - return await access.asset.checkOwnerAccess(sharedLink.userId, ids, false); - } - - case Permission.AlbumRead: { - return await access.album.checkSharedLinkAccess(sharedLinkId, ids); - } - - case Permission.AlbumDownload: { - return sharedLink.allowDownload ? await access.album.checkSharedLinkAccess(sharedLinkId, ids) : new Set(); - } - - case Permission.AlbumAssetCreate: { - return sharedLink.allowUpload ? await access.album.checkSharedLinkAccess(sharedLinkId, ids) : new Set(); - } - - default: { - return new Set(); - } - } -}; - -const checkOtherAccess = async (access: AccessRepository, request: OtherAccessRequest): Promise> => { - const { auth, permission, ids } = request; - - switch (permission) { - // uses album id - case Permission.ActivityCreate: { - return await access.activity.checkCreateAccess(auth.user.id, ids); - } - - // uses activity id - case Permission.ActivityDelete: { - const isOwner = await access.activity.checkOwnerAccess(auth.user.id, ids); - const isAlbumOwner = await access.activity.checkAlbumOwnerAccess(auth.user.id, setDifference(ids, isOwner)); - return setUnion(isOwner, isAlbumOwner); - } - - case Permission.AssetRead: { - const isOwner = await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - const isAlbum = await access.asset.checkAlbumAccess(auth.user.id, setDifference(ids, isOwner)); - const isPartner = await access.asset.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner, isAlbum)); - return setUnion(isOwner, isAlbum, isPartner); - } - - case Permission.AssetShare: { - const isOwner = await access.asset.checkOwnerAccess(auth.user.id, ids, false); - const isPartner = await access.asset.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner)); - return setUnion(isOwner, isPartner); - } - - case Permission.AssetView: { - const isOwner = await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - const isAlbum = await access.asset.checkAlbumAccess(auth.user.id, setDifference(ids, isOwner)); - const isPartner = await access.asset.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner, isAlbum)); - return setUnion(isOwner, isAlbum, isPartner); - } - - case Permission.AssetDownload: { - const isOwner = await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - const isAlbum = await access.asset.checkAlbumAccess(auth.user.id, setDifference(ids, isOwner)); - const isPartner = await access.asset.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner, isAlbum)); - return setUnion(isOwner, isAlbum, isPartner); - } - - case Permission.AssetUpdate: { - return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - } - - case Permission.AssetDelete: { - return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - } - - case Permission.AssetCopy: { - return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - } - - case Permission.AssetEditGet: { - return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - } - - case Permission.AssetEditCreate: { - return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - } - - case Permission.AssetEditDelete: { - return await access.asset.checkOwnerAccess(auth.user.id, ids, auth.session?.hasElevatedPermission); - } - - case Permission.AlbumRead: { - const isOwner = await access.album.checkOwnerAccess(auth.user.id, ids); - const isShared = await access.album.checkSharedAlbumAccess( - auth.user.id, - setDifference(ids, isOwner), - AlbumUserRole.Viewer, - ); - return setUnion(isOwner, isShared); - } - - case Permission.AlbumAssetCreate: { - const isOwner = await access.album.checkOwnerAccess(auth.user.id, ids); - const isShared = await access.album.checkSharedAlbumAccess( - auth.user.id, - setDifference(ids, isOwner), - AlbumUserRole.Editor, - ); - return setUnion(isOwner, isShared); - } - - case Permission.AlbumUpdate: { - return await access.album.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.AlbumDelete: { - return await access.album.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.AlbumShare: { - return await access.album.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.AlbumDownload: { - const isOwner = await access.album.checkOwnerAccess(auth.user.id, ids); - const isShared = await access.album.checkSharedAlbumAccess( - auth.user.id, - setDifference(ids, isOwner), - AlbumUserRole.Viewer, - ); - return setUnion(isOwner, isShared); - } - - case Permission.AlbumAssetDelete: { - const isOwner = await access.album.checkOwnerAccess(auth.user.id, ids); - const isShared = await access.album.checkSharedAlbumAccess( - auth.user.id, - setDifference(ids, isOwner), - AlbumUserRole.Editor, - ); - return setUnion(isOwner, isShared); - } - - case Permission.AssetUpload: { - return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set(); - } - - case Permission.ArchiveRead: { - return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set(); - } - - case Permission.AuthDeviceDelete: { - return await access.authDevice.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.FaceDelete: { - return access.person.checkFaceOwnerAccess(auth.user.id, ids); - } - - case Permission.NotificationRead: - case Permission.NotificationUpdate: - case Permission.NotificationDelete: { - return access.notification.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.TagAsset: - case Permission.TagRead: - case Permission.TagUpdate: - case Permission.TagDelete: { - return await access.tag.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.TimelineRead: { - const isOwner = ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set(); - const isPartner = await access.timeline.checkPartnerAccess(auth.user.id, setDifference(ids, isOwner)); - return setUnion(isOwner, isPartner); - } - - case Permission.TimelineDownload: { - return ids.has(auth.user.id) ? new Set([auth.user.id]) : new Set(); - } - - case Permission.MemoryRead: { - return access.memory.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.MemoryUpdate: { - return access.memory.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.MemoryDelete: { - return access.memory.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.PersonCreate: { - return access.person.checkFaceOwnerAccess(auth.user.id, ids); - } - - case Permission.PersonRead: - case Permission.PersonUpdate: - case Permission.PersonDelete: - case Permission.PersonMerge: { - return await access.person.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.PersonReassign: { - return access.person.checkFaceOwnerAccess(auth.user.id, ids); - } - - case Permission.PartnerUpdate: { - return await access.partner.checkUpdateAccess(auth.user.id, ids); - } - - case Permission.SessionRead: - case Permission.SessionUpdate: - case Permission.SessionDelete: - case Permission.SessionLock: { - return access.session.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.StackRead: { - return access.stack.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.StackUpdate: { - return access.stack.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.StackDelete: { - return access.stack.checkOwnerAccess(auth.user.id, ids); - } - - case Permission.WorkflowRead: - case Permission.WorkflowUpdate: - case Permission.WorkflowDelete: { - return access.workflow.checkOwnerAccess(auth.user.id, ids); - } - - default: { - return new Set(); - } - } -}; - -export const requireElevatedPermission = (auth: AuthDto) => { - if (!auth.session?.hasElevatedPermission) { - throw new UnauthorizedException('Elevated permission is required'); - } -}; diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts deleted file mode 100644 index f8fb3d215d..0000000000 --- a/server/src/utils/asset.util.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { StorageCore } from 'src/cores/storage.core'; -import { AssetFile, Exif } from 'src/database'; -import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto'; -import { UploadFieldName } from 'src/dtos/asset-media.dto'; -import { AuthDto } from 'src/dtos/auth.dto'; -import { ExifResponseDto } from 'src/dtos/exif.dto'; -import { AssetFileType, AssetType, AssetVisibility, Permission } from 'src/enum'; -import { AuthRequest } from 'src/middleware/auth.guard'; -import { AccessRepository } from 'src/repositories/access.repository'; -import { AssetRepository } from 'src/repositories/asset.repository'; -import { EventRepository } from 'src/repositories/event.repository'; -import { PartnerRepository } from 'src/repositories/partner.repository'; -import { IBulkAsset, ImmichFile, UploadFile, UploadRequest } from 'src/types'; -import { checkAccess } from 'src/utils/access'; - -export const getAssetFile = (files: AssetFile[], type: AssetFileType, { isEdited }: { isEdited: boolean }) => { - return files.find((file) => file.type === type && file.isEdited === isEdited); -}; - -export const getAssetFiles = (files: AssetFile[]) => ({ - fullsizeFile: getAssetFile(files, AssetFileType.FullSize, { isEdited: false }), - previewFile: getAssetFile(files, AssetFileType.Preview, { isEdited: false }), - thumbnailFile: getAssetFile(files, AssetFileType.Thumbnail, { isEdited: false }), - sidecarFile: getAssetFile(files, AssetFileType.Sidecar, { isEdited: false }), - - editedFullsizeFile: getAssetFile(files, AssetFileType.FullSize, { isEdited: true }), - editedPreviewFile: getAssetFile(files, AssetFileType.Preview, { isEdited: true }), - editedThumbnailFile: getAssetFile(files, AssetFileType.Preview, { isEdited: true }), -}); - -export const addAssets = async ( - auth: AuthDto, - repositories: { access: AccessRepository; bulk: IBulkAsset }, - dto: { parentId: string; assetIds: string[] }, -) => { - const { access, bulk } = repositories; - const existingAssetIds = await bulk.getAssetIds(dto.parentId, dto.assetIds); - const notPresentAssetIds = dto.assetIds.filter((id) => !existingAssetIds.has(id)); - const allowedAssetIds = await checkAccess(access, { - auth, - permission: Permission.AssetShare, - ids: notPresentAssetIds, - }); - - const results: BulkIdResponseDto[] = []; - for (const assetId of dto.assetIds) { - const hasAsset = existingAssetIds.has(assetId); - if (hasAsset) { - results.push({ id: assetId, success: false, error: BulkIdErrorReason.DUPLICATE }); - continue; - } - - const hasAccess = allowedAssetIds.has(assetId); - if (!hasAccess) { - results.push({ id: assetId, success: false, error: BulkIdErrorReason.NO_PERMISSION }); - continue; - } - - existingAssetIds.add(assetId); - results.push({ id: assetId, success: true }); - } - - const newAssetIds = results.filter(({ success }) => success).map(({ id }) => id); - if (newAssetIds.length > 0) { - await bulk.addAssetIds(dto.parentId, newAssetIds); - } - - return results; -}; - -export const removeAssets = async ( - auth: AuthDto, - repositories: { access: AccessRepository; bulk: IBulkAsset }, - dto: { parentId: string; assetIds: string[]; canAlwaysRemove: Permission }, -) => { - const { access, bulk } = repositories; - - // check if the user can always remove from the parent album, memory, etc. - const canAlwaysRemove = await checkAccess(access, { auth, permission: dto.canAlwaysRemove, ids: [dto.parentId] }); - const existingAssetIds = await bulk.getAssetIds(dto.parentId, dto.assetIds); - const allowedAssetIds = canAlwaysRemove.has(dto.parentId) - ? existingAssetIds - : await checkAccess(access, { auth, permission: Permission.AssetShare, ids: existingAssetIds }); - - const results: BulkIdResponseDto[] = []; - for (const assetId of dto.assetIds) { - const hasAsset = existingAssetIds.has(assetId); - if (!hasAsset) { - results.push({ id: assetId, success: false, error: BulkIdErrorReason.NOT_FOUND }); - continue; - } - - const hasAccess = allowedAssetIds.has(assetId); - if (!hasAccess) { - results.push({ id: assetId, success: false, error: BulkIdErrorReason.NO_PERMISSION }); - continue; - } - - existingAssetIds.delete(assetId); - results.push({ id: assetId, success: true }); - } - - const removedIds = results.filter(({ success }) => success).map(({ id }) => id); - if (removedIds.length > 0) { - await bulk.removeAssetIds(dto.parentId, removedIds); - } - - return results; -}; - -export type PartnerIdOptions = { - userId: string; - repository: PartnerRepository; - /** only include partners with `inTimeline: true` */ - timelineEnabled?: boolean; -}; -export const getMyPartnerIds = async ({ userId, repository, timelineEnabled }: PartnerIdOptions) => { - const partnerIds = new Set(); - const partners = await repository.getAll(userId); - for (const partner of partners) { - // ignore deleted users - if (!partner.sharedBy || !partner.sharedWith) { - continue; - } - - // wrong direction - if (partner.sharedWithId !== userId) { - continue; - } - - if (timelineEnabled && !partner.inTimeline) { - continue; - } - - partnerIds.add(partner.sharedById); - } - - return [...partnerIds]; -}; - -export type AssetHookRepositories = { asset: AssetRepository; event: EventRepository }; - -export const onBeforeLink = async ( - { asset: assetRepository, event: eventRepository }: AssetHookRepositories, - { userId, livePhotoVideoId }: { userId: string; livePhotoVideoId: string }, -) => { - const motionAsset = await assetRepository.getById(livePhotoVideoId); - if (!motionAsset) { - throw new BadRequestException('Live photo video not found'); - } - if (motionAsset.type !== AssetType.Video) { - throw new BadRequestException('Live photo video must be a video'); - } - if (motionAsset.ownerId !== userId) { - throw new BadRequestException('Live photo video does not belong to the user'); - } - - if (motionAsset && motionAsset.visibility === AssetVisibility.Timeline) { - await assetRepository.update({ id: livePhotoVideoId, visibility: AssetVisibility.Hidden }); - await eventRepository.emit('AssetHide', { assetId: motionAsset.id, userId }); - } -}; - -export const onBeforeUnlink = async ( - { asset: assetRepository }: AssetHookRepositories, - { livePhotoVideoId }: { livePhotoVideoId: string }, -) => { - const motion = await assetRepository.getById(livePhotoVideoId); - if (!motion) { - return null; - } - - if (StorageCore.isAndroidMotionPath(motion.originalPath)) { - throw new BadRequestException('Cannot unlink Android motion photos'); - } - - return motion; -}; - -export const onAfterUnlink = async ( - { asset: assetRepository, event: eventRepository }: AssetHookRepositories, - { userId, livePhotoVideoId, visibility }: { userId: string; livePhotoVideoId: string; visibility: AssetVisibility }, -) => { - await assetRepository.update({ id: livePhotoVideoId, visibility }); - await eventRepository.emit('AssetShow', { assetId: livePhotoVideoId, userId }); -}; - -export function mapToUploadFile(file: ImmichFile): UploadFile { - return { - uuid: file.uuid, - checksum: file.checksum, - originalPath: file.path, - originalName: Buffer.from(file.originalname, 'latin1').toString('utf8'), - size: file.size, - }; -} - -export const asUploadRequest = (request: AuthRequest, file: Express.Multer.File): UploadRequest => { - return { - auth: request.user || null, - body: request.body, - fieldName: file.fieldname as UploadFieldName, - file: mapToUploadFile(file as ImmichFile), - }; -}; - -const isFlipped = (orientation?: string | null) => { - const value = Number(orientation); - return value && [5, 6, 7, 8, -90, 90].includes(value); -}; - -export const getDimensions = (exifInfo: ExifResponseDto | Exif) => { - const { exifImageWidth: width, exifImageHeight: height } = exifInfo; - - if (!width || !height) { - return { width: 0, height: 0 }; - } - - if (isFlipped(exifInfo.orientation)) { - return { width: height, height: width }; - } - - return { width, height }; -}; - -export const isPanorama = (asset: { exifInfo?: Exif | null; originalFileName: string }) => { - return asset.exifInfo?.projectionType === 'EQUIRECTANGULAR' || asset.originalFileName.toLowerCase().endsWith('.insp'); -}; diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts index a669af31cf..93d60dbb49 100644 --- a/server/src/utils/config.ts +++ b/server/src/utils/config.ts @@ -1,18 +1,11 @@ import AsyncLock from 'async-lock'; -import { instanceToPlain, plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; -import { load as loadYaml } from 'js-yaml'; import * as _ from 'lodash'; import { SystemConfig, defaults } from 'src/config'; -import { SystemConfigDto } from 'src/dtos/system-config.dto'; import { DatabaseLock, SystemMetadataKey } from 'src/enum'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { DeepPartial } from 'src/types'; -import { getKeysDeep, unsetDeep } from 'src/utils/misc'; - -export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise; type RepoDeps = { configRepo: ConfigRepository; @@ -29,110 +22,32 @@ export const clearConfigCache = () => { lastUpdated = null; }; -export const getConfig = async (repos: RepoDeps, { withCache }: { withCache: boolean }): Promise => { - if (!withCache || !config) { - const timestamp = lastUpdated; - await asyncLock.acquire(DatabaseLock[DatabaseLock.GetSystemConfig], async () => { - if (timestamp === lastUpdated) { - config = await buildConfig(repos); - lastUpdated = Date.now(); - } - }); +export const getConfig = async ( + repos: RepoDeps, + options: { withCache?: boolean; withRecordEnabled?: boolean } = {}, +): Promise => { + const { withCache = true } = options; + + if (withCache && config) { + return config; } - return config!; + return asyncLock.acquire('getConfig', async () => { + if (withCache && config) { + return config; + } + + const stored = await repos.metadataRepo.get(SystemMetadataKey.SystemConfig); + const result = _.defaultsDeep(stored || {}, defaults) as SystemConfig; + config = result; + lastUpdated = Date.now(); + return result; + }); }; export const updateConfig = async (repos: RepoDeps, newConfig: SystemConfig): Promise => { - const { metadataRepo } = repos; - // get the difference between the new config and the default config - const partialConfig: DeepPartial = {}; - for (const property of getKeysDeep(defaults)) { - const newValue = _.get(newConfig, property); - const isEmpty = newValue === undefined || newValue === null || newValue === ''; - const defaultValue = _.get(defaults, property); - const isEqual = newValue === defaultValue || _.isEqual(newValue, defaultValue); - - if (isEmpty || isEqual) { - continue; - } - - _.set(partialConfig, property, newValue); - } - - await metadataRepo.set(SystemMetadataKey.SystemConfig, partialConfig); - - return getConfig(repos, { withCache: false }); -}; - -const loadFromFile = async ({ metadataRepo, logger }: RepoDeps, filepath: string) => { - try { - const file = await metadataRepo.readFile(filepath); - return loadYaml(file.toString()) as unknown; - } catch (error: Error | any) { - logger.error(`Unable to load configuration file: ${filepath}`); - logger.error(error); - throw error; - } -}; - -const buildConfig = async (repos: RepoDeps) => { - const { configRepo, metadataRepo, logger } = repos; - const { configFile } = configRepo.getEnv(); - - // load partial - const partial = configFile - ? await loadFromFile(repos, configFile) - : await metadataRepo.get(SystemMetadataKey.SystemConfig); - - // merge with defaults - const rawConfig = _.cloneDeep(defaults); - for (const property of getKeysDeep(partial)) { - _.set(rawConfig, property, _.get(partial, property)); - } - - // check for extra properties - const unknownKeys = _.cloneDeep(rawConfig); - for (const property of getKeysDeep(defaults)) { - unsetDeep(unknownKeys, property); - } - - if (!_.isEmpty(unknownKeys)) { - logger.warn(`Unknown keys found: ${JSON.stringify(unknownKeys, null, 2)}`); - } - - // validate full config - const instance = plainToInstance(SystemConfigDto, rawConfig); - const errors = await validate(instance); - if (errors.length > 0) { - if (configFile) { - throw new Error(`Invalid value(s) in file: ${errors}`); - } else { - logger.error('Validation error', errors); - } - } - - // return config with class-transform changes - const config = instanceToPlain(instance) as SystemConfig; - - if (config.server.externalDomain.length > 0) { - const domain = new URL(config.server.externalDomain); - - let externalDomain = domain.origin; - if (domain.password && domain.username) { - externalDomain = `${domain.protocol}//${domain.username}:${domain.password}@${domain.host}`; - } - - config.server.externalDomain = externalDomain; - } - - if (!config.ffmpeg.acceptedVideoCodecs.includes(config.ffmpeg.targetVideoCodec)) { - config.ffmpeg.acceptedVideoCodecs.push(config.ffmpeg.targetVideoCodec); - } - - if (!config.ffmpeg.acceptedAudioCodecs.includes(config.ffmpeg.targetAudioCodec)) { - config.ffmpeg.acceptedAudioCodecs.push(config.ffmpeg.targetAudioCodec); - } - - return config; + await repos.metadataRepo.set(SystemMetadataKey.SystemConfig, newConfig); + config = newConfig; + lastUpdated = Date.now(); + return newConfig; }; diff --git a/server/src/utils/database-backups.ts b/server/src/utils/database-backups.ts deleted file mode 100644 index 1d508e2a7d..0000000000 --- a/server/src/utils/database-backups.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; -import { debounce } from 'lodash'; -import { DateTime } from 'luxon'; -import path, { basename, join } from 'node:path'; -import { PassThrough, Readable, Writable } from 'node:stream'; -import { pipeline } from 'node:stream/promises'; -import semver from 'semver'; -import { serverVersion } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; -import { CacheControl, StorageFolder } from 'src/enum'; -import { MaintenanceHealthRepository } from 'src/maintenance/maintenance-health.repository'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { DatabaseRepository } from 'src/repositories/database.repository'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { ProcessRepository } from 'src/repositories/process.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; - -export function isValidDatabaseBackupName(filename: string) { - return filename.match(/^[\d\w-.]+\.sql(?:\.gz)?$/); -} - -export function isValidDatabaseRoutineBackupName(filename: string) { - const oldBackupStyle = filename.match(/^immich-db-backup-\d+\.sql\.gz$/); - //immich-db-backup-20250729T114018-v1.136.0-pg14.17.sql.gz - const newBackupStyle = filename.match(/^immich-db-backup-\d{8}T\d{6}-v.*-pg.*\.sql\.gz$/); - return oldBackupStyle || newBackupStyle; -} - -export function isFailedDatabaseBackupName(filename: string) { - return filename.match(/^immich-db-backup-.*\.sql\.gz\.tmp$/); -} - -export function findVersion(filename: string) { - return /-v(.*)-/.exec(filename)?.[1]; -} - -type BackupRepos = { - logger: LoggingRepository; - storage: StorageRepository; - config: ConfigRepository; - process: ProcessRepository; - database: DatabaseRepository; - health: MaintenanceHealthRepository; -}; - -export class UnsupportedPostgresError extends Error { - constructor(databaseVersion: string) { - super(`Unsupported PostgreSQL version: ${databaseVersion}`); - } -} - -export async function buildPostgresLaunchArguments( - { logger, config, database }: Pick, - bin: 'pg_dump' | 'pg_dumpall' | 'psql', - options: { - singleTransaction?: boolean; - username?: string; - } = {}, -): Promise<{ - bin: string; - args: string[]; - databaseUsername: string; - databasePassword: string; - databaseVersion: string; - databaseMajorVersion?: number; -}> { - const { - database: { config: databaseConfig }, - } = config.getEnv(); - const isUrlConnection = databaseConfig.connectionType === 'url'; - - const databaseVersion = await database.getPostgresVersion(); - const databaseSemver = semver.coerce(databaseVersion); - const databaseMajorVersion = databaseSemver?.major; - - const args: string[] = []; - let databaseUsername; - - if (isUrlConnection) { - if (bin !== 'pg_dump') { - args.push('--dbname'); - } - - let url = databaseConfig.url; - if (URL.canParse(databaseConfig.url)) { - const parsedUrl = new URL(databaseConfig.url); - // remove known bad parameters - parsedUrl.searchParams.delete('uselibpqcompat'); - - databaseUsername = parsedUrl.username; - url = parsedUrl.toString(); - } - - // assume typical values if we can't parse URL or not present - databaseUsername ??= 'postgres'; - - args.push(url); - } else { - databaseUsername = databaseConfig.username; - - args.push('--username', databaseUsername, '--host', databaseConfig.host, '--port', databaseConfig.port.toString()); - - switch (bin) { - case 'pg_dumpall': { - args.push('--database'); - break; - } - case 'psql': { - args.push('--dbname'); - break; - } - } - - args.push(databaseConfig.database); - } - - switch (bin) { - case 'pg_dump': - case 'pg_dumpall': { - args.push('--clean', '--if-exists'); - break; - } - case 'psql': { - if (options.singleTransaction) { - args.push( - // don't commit any transaction on failure - '--single-transaction', - // exit with non-zero code on error - '--set', - 'ON_ERROR_STOP=on', - ); - } - - args.push( - // used for progress monitoring - '--echo-all', - '--output=/dev/null', - ); - break; - } - } - - if (!databaseMajorVersion || !databaseSemver || !semver.satisfies(databaseSemver, '>=14.0.0 <19.0.0')) { - logger.error(`Database Restore Failure: Unsupported PostgreSQL version: ${databaseVersion}`); - throw new UnsupportedPostgresError(databaseVersion); - } - - return { - bin: `/usr/lib/postgresql/${databaseMajorVersion}/bin/${bin}`, - args, - databaseUsername, - databasePassword: isUrlConnection ? new URL(databaseConfig.url).password : databaseConfig.password, - databaseVersion, - databaseMajorVersion, - }; -} - -export async function createDatabaseBackup( - { logger, storage, process: processRepository, ...pgRepos }: Omit, - filenamePrefix: string = '', -): Promise { - logger.debug(`Database Backup Started`); - - const { bin, args, databasePassword, databaseVersion, databaseMajorVersion } = await buildPostgresLaunchArguments( - { logger, ...pgRepos }, - 'pg_dump', - ); - - logger.log(`Database Backup Starting. Database Version: ${databaseMajorVersion}`); - - const filename = `${filenamePrefix}immich-db-backup-${DateTime.now().toFormat("yyyyLLdd'T'HHmmss")}-v${serverVersion.toString()}-pg${databaseVersion.split(' ')[0]}.sql.gz`; - const backupFilePath = join(StorageCore.getBaseFolder(StorageFolder.Backups), filename); - const temporaryFilePath = `${backupFilePath}.tmp`; - - try { - const pgdump = processRepository.spawnDuplexStream(bin, args, { - env: { - PATH: process.env.PATH, - PGPASSWORD: databasePassword, - }, - }); - - const gzip = processRepository.spawnDuplexStream('gzip', ['--rsyncable']); - const fileStream = storage.createWriteStream(temporaryFilePath); - - await pipeline(pgdump, gzip, fileStream); - await storage.rename(temporaryFilePath, backupFilePath); - } catch (error) { - logger.error(`Database Backup Failure: ${error}`); - await storage - .unlink(temporaryFilePath) - .catch((error) => logger.error(`Failed to delete failed backup file: ${error}`)); - throw error; - } - - logger.log(`Database Backup Success`); - return backupFilePath; -} - -const SQL_DROP_CONNECTIONS = ` - -- drop all other database connections - SELECT pg_terminate_backend(pid) - FROM pg_stat_activity - WHERE datname = current_database() - AND pid <> pg_backend_pid(); -`; - -const SQL_RESET_SCHEMA = (username: string) => ` - -- re-create the default schema - DROP SCHEMA public CASCADE; - CREATE SCHEMA public; - - -- restore access to schema - GRANT ALL ON SCHEMA public TO "${username}"; - GRANT ALL ON SCHEMA public TO public; -`; - -async function* sql(inputStream: Readable, databaseUsername: string, isPgClusterDump: boolean) { - yield SQL_DROP_CONNECTIONS; - yield isPgClusterDump - ? // it is likely the dump contains SQL to try to drop the currently active - // database to ensure we have a fresh slate; if the `postgres` database exists - // then prefer to switch before continuing otherwise this will just silently fail - String.raw` - \c postgres - ` - : SQL_RESET_SCHEMA(databaseUsername); - - for await (const chunk of inputStream) { - yield chunk; - } -} - -async function* sqlRollback(inputStream: Readable, databaseUsername: string) { - yield SQL_DROP_CONNECTIONS; - yield SQL_RESET_SCHEMA(databaseUsername); - - for await (const chunk of inputStream) { - yield chunk; - } -} - -export async function restoreDatabaseBackup( - { logger, storage, process: processRepository, database: databaseRepository, health, ...pgRepos }: BackupRepos, - filename: string, - progressCb?: (action: 'backup' | 'restore' | 'migrations' | 'rollback', progress: number) => void, -): Promise { - logger.debug(`Database Restore Started`); - - let complete = false; - try { - if (!isValidDatabaseBackupName(filename)) { - throw new Error('Invalid backup file format!'); - } - - const backupFilePath = path.join(StorageCore.getBaseFolder(StorageFolder.Backups), filename); - await storage.stat(backupFilePath); // => check file exists - - let isPgClusterDump = false; - const version = findVersion(filename); - if (version && semver.satisfies(version, '<= 2.4')) { - isPgClusterDump = true; - } - - const { bin, args, databaseUsername, databasePassword, databaseMajorVersion } = await buildPostgresLaunchArguments( - { logger, database: databaseRepository, ...pgRepos }, - 'psql', - { - singleTransaction: !isPgClusterDump, - }, - ); - - progressCb?.('backup', 0.05); - - const restorePointFilePath = await createDatabaseBackup( - { logger, storage, process: processRepository, database: databaseRepository, ...pgRepos }, - 'restore-point-', - ); - - logger.log(`Database Restore Starting. Database Version: ${databaseMajorVersion}`); - - let inputStream: Readable; - if (backupFilePath.endsWith('.gz')) { - const fileStream = storage.createPlainReadStream(backupFilePath); - const gunzip = storage.createGunzip(); - fileStream.pipe(gunzip); - inputStream = gunzip; - } else { - inputStream = storage.createPlainReadStream(backupFilePath); - } - - const sqlStream = Readable.from(sql(inputStream, databaseUsername, isPgClusterDump)); - const psql = processRepository.spawnDuplexStream(bin, args, { - env: { - PATH: process.env.PATH, - PGPASSWORD: databasePassword, - }, - }); - - const [progressSource, progressSink] = createSqlProgressStreams((progress) => { - if (complete) { - return; - } - - logger.log(`Restore progress ~ ${(progress * 100).toFixed(2)}%`); - progressCb?.('restore', progress); - }); - - await pipeline(sqlStream, progressSource, psql, progressSink); - - try { - progressCb?.('migrations', 0.9); - await databaseRepository.runMigrations(); - await health.checkApiHealth(); - } catch (error) { - progressCb?.('rollback', 0); - - const fileStream = storage.createPlainReadStream(restorePointFilePath); - const gunzip = storage.createGunzip(); - fileStream.pipe(gunzip); - inputStream = gunzip; - - const sqlStream = Readable.from(sqlRollback(inputStream, databaseUsername)); - const psql = processRepository.spawnDuplexStream(bin, args, { - env: { - PATH: process.env.PATH, - PGPASSWORD: databasePassword, - }, - }); - - const [progressSource, progressSink] = createSqlProgressStreams((progress) => { - if (complete) { - return; - } - - logger.log(`Rollback progress ~ ${(progress * 100).toFixed(2)}%`); - progressCb?.('rollback', progress); - }); - - await pipeline(sqlStream, progressSource, psql, progressSink); - - throw error; - } - } catch (error) { - logger.error(`Database Restore Failure: ${error}`); - throw error; - } finally { - complete = true; - } - - logger.log(`Database Restore Success`); -} - -export async function deleteDatabaseBackup({ storage }: Pick, files: string[]): Promise { - const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); - - if (files.some((filename) => !isValidDatabaseBackupName(filename))) { - throw new BadRequestException('Invalid backup name!'); - } - - await Promise.all(files.map((filename) => storage.unlink(path.join(backupsFolder, filename)))); -} - -export async function listDatabaseBackups({ - storage, -}: Pick): Promise<{ filename: string; filesize: number }[]> { - const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); - const files = await storage.readdir(backupsFolder); - - const validFiles = files - .filter((fn) => isValidDatabaseBackupName(fn)) - .toSorted((a, b) => (a.startsWith('uploaded-') === b.startsWith('uploaded-') ? a.localeCompare(b) : 1)) - .toReversed(); - - const backups = await Promise.all( - validFiles.map(async (filename) => { - const stats = await storage.stat(path.join(backupsFolder, filename)); - return { filename, filesize: stats.size }; - }), - ); - - return backups; -} - -export async function uploadDatabaseBackup( - { storage }: Pick, - file: Express.Multer.File, -): Promise { - const backupsFolder = StorageCore.getBaseFolder(StorageFolder.Backups); - const fn = basename(file.originalname); - if (!isValidDatabaseBackupName(fn)) { - throw new BadRequestException('Invalid backup name!'); - } - - const path = join(backupsFolder, `uploaded-${fn}`); - await storage.createOrOverwriteFile(path, file.buffer); -} - -export function downloadDatabaseBackup(fileName: string) { - if (!isValidDatabaseBackupName(fileName)) { - throw new BadRequestException('Invalid backup name!'); - } - - const path = join(StorageCore.getBaseFolder(StorageFolder.Backups), fileName); - - return { - path, - fileName, - cacheControl: CacheControl.PrivateWithoutCache, - contentType: fileName.endsWith('.gz') ? 'application/gzip' : 'application/sql', - }; -} - -function createSqlProgressStreams(cb: (progress: number) => void) { - const STDIN_START_MARKER = new TextEncoder().encode('FROM stdin'); - const STDIN_END_MARKER = new TextEncoder().encode(String.raw`\.`); - - let readingStdin = false; - let sequenceIdx = 0; - - let linesSent = 0; - let linesProcessed = 0; - - const startedAt = +Date.now(); - const cbDebounced = debounce( - () => { - const progress = source.writableEnded - ? Math.min(1, linesProcessed / linesSent) - : // progress simulation while we're in an indeterminate state - Math.min(0.3, 0.1 + (Date.now() - startedAt) / 1e4); - cb(progress); - }, - 100, - { - maxWait: 100, - }, - ); - - let lastByte = -1; - const source = new PassThrough({ - transform(chunk, _encoding, callback) { - for (const byte of chunk) { - if (!readingStdin && byte === 10 && lastByte !== 10) { - linesSent += 1; - } - - lastByte = byte; - - const sequence = readingStdin ? STDIN_END_MARKER : STDIN_START_MARKER; - if (sequence[sequenceIdx] === byte) { - sequenceIdx += 1; - - if (sequence.length === sequenceIdx) { - sequenceIdx = 0; - readingStdin = !readingStdin; - } - } else { - sequenceIdx = 0; - } - } - - cbDebounced(); - this.push(chunk); - callback(); - }, - }); - - const sink = new Writable({ - write(chunk, _encoding, callback) { - for (const byte of chunk) { - if (byte === 10) { - linesProcessed++; - } - } - - cbDebounced(); - callback(); - }, - }); - - return [source, sink]; -} diff --git a/server/src/utils/database.spec.ts b/server/src/utils/database.spec.ts deleted file mode 100644 index 4c6a82ad8f..0000000000 --- a/server/src/utils/database.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { asPostgresConnectionConfig } from 'src/utils/database'; - -describe('database utils', () => { - describe('asPostgresConnectionConfig', () => { - it('should handle sslmode=require', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=require', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=prefer', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=prefer', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=verify-ca', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-ca', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=verify-full', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=verify-full', - }), - ).toMatchObject({ ssl: {} }); - }); - - it('should handle sslmode=no-verify', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?sslmode=no-verify', - }), - ).toMatchObject({ ssl: { rejectUnauthorized: false } }); - }); - - it('should handle ssl=true', () => { - expect( - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?ssl=true', - }), - ).toMatchObject({ ssl: true }); - }); - - it('should reject invalid ssl', () => { - expect(() => - asPostgresConnectionConfig({ - connectionType: 'url', - url: 'postgres://postgres1:postgres2@database1:54320/immich?ssl=invalid', - }), - ).toThrowError('Invalid ssl option'); - }); - - it('should handle socket: URLs', () => { - expect( - asPostgresConnectionConfig({ connectionType: 'url', url: 'socket:/run/postgresql?db=database1' }), - ).toMatchObject({ host: '/run/postgresql', database: 'database1' }); - }); - - it('should handle sockets in postgres: URLs', () => { - expect( - asPostgresConnectionConfig({ connectionType: 'url', url: 'postgres:///database2?host=/path/to/socket' }), - ).toMatchObject({ - host: '/path/to/socket', - database: 'database2', - }); - }); - }); -}); diff --git a/server/src/utils/database.ts b/server/src/utils/database.ts index a041946a28..f5c47c80b9 100644 --- a/server/src/utils/database.ts +++ b/server/src/utils/database.ts @@ -1,517 +1,16 @@ -import { - AliasedRawBuilder, - DeduplicateJoinsPlugin, - Expression, - ExpressionBuilder, - ExpressionWrapper, - Kysely, - KyselyConfig, - Nullable, - Selectable, - SelectQueryBuilder, - Simplify, - sql, -} from 'kysely'; -import { PostgresJSDialect } from 'kysely-postgres-js'; -import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres'; -import { parse } from 'pg-connection-string'; -import postgres, { Notice, PostgresError } from 'postgres'; -import { columns, Exif, lockableProperties, LockableProperty, Person } from 'src/database'; -import { AssetEditActionItem } from 'src/dtos/editing.dto'; -import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum'; -import { AssetSearchBuilderOptions } from 'src/repositories/search.repository'; -import { DB } from 'src/schema'; -import { DatabaseConnectionParams, VectorExtension } from 'src/types'; +import { KyselyConfig, PostgresDialect } from 'kysely'; +import { Pool } from 'pg'; +import { DatabaseConnectionParams } from 'src/types'; -type Ssl = 'require' | 'allow' | 'prefer' | 'verify-full' | boolean | object; - -const isValidSsl = (ssl?: string | boolean | object): ssl is Ssl => - typeof ssl !== 'string' || ssl === 'require' || ssl === 'allow' || ssl === 'prefer' || ssl === 'verify-full'; - -export const asPostgresConnectionConfig = (params: DatabaseConnectionParams) => { - if (params.connectionType === 'parts') { - return { - host: params.host, - port: params.port, - username: params.username, - password: params.password, - database: params.database, - ssl: params.ssl === DatabaseSslMode.Disable ? false : params.ssl, - }; - } - - const { host, port, user, password, database, ...rest } = parse(params.url); - let ssl: Ssl | undefined; - if (rest.ssl) { - if (!isValidSsl(rest.ssl)) { - throw new Error(`Invalid ssl option: ${rest.ssl}`); - } - ssl = rest.ssl; - } +export function getKyselyConfig(config: DatabaseConnectionParams): KyselyConfig { + const connectionString = + config.connectionType === 'url' + ? config.url + : `postgresql://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`; return { - host: host ?? undefined, - port: port ? Number(port) : undefined, - username: user, - password, - database: database ?? undefined, - ssl, - }; -}; - -export const getKyselyConfig = ( - params: DatabaseConnectionParams, - options: Partial>> = {}, -): KyselyConfig => { - const config = asPostgresConnectionConfig(params); - - return { - dialect: new PostgresJSDialect({ - postgres: postgres({ - onnotice: (notice: Notice) => { - if (notice['severity'] !== 'NOTICE') { - console.warn('Postgres notice:', notice); - } - }, - max: 10, - types: { - date: { - to: 1184, - from: [1082, 1114, 1184], - serialize: (x: Date | string) => (x instanceof Date ? x.toISOString() : x), - parse: (x: string) => new Date(x), - }, - bigint: { - to: 20, - from: [20, 1700], - parse: (value: string) => Number.parseInt(value), - serialize: (value: number) => value.toString(), - }, - }, - connection: { - TimeZone: 'UTC', - }, - host: config.host, - port: config.port, - username: config.username, - password: config.password, - database: config.database, - ssl: config.ssl, - ...options, - }), + dialect: new PostgresDialect({ + pool: new Pool({ connectionString }), }), - log(event) { - if (event.level === 'error') { - console.error('Query failed :', { - durationMs: event.queryDurationMillis, - error: event.error, - sql: event.query.sql, - params: event.query.parameters, - }); - } - }, }; -}; - -export const asUuid = (id: string | Expression) => sql`${id}::uuid`; - -export const anyUuid = (ids: string[]) => sql`any(${`{${ids}}`}::uuid[])`; - -export const asVector = (embedding: number[]) => sql`${`[${embedding}]`}::vector`; - -export const unnest = (array: string[]) => sql>`unnest(array[${sql.join(array)}]::text[])`; - -export const removeUndefinedKeys = (update: T, template: unknown) => { - for (const key in update) { - if ((template as T)[key] === undefined) { - delete update[key]; - } - } - - return update; -}; - -/** Modifies toJson return type to not set all properties as nullable */ -export function toJson>( - eb: ExpressionBuilder, - table: T, -) { - return eb.fn.toJson(table) as ExpressionWrapper< - DB, - TB, - Simplify< - T extends TB - ? Selectable extends Nullable - ? N | null - : Selectable - : T extends Expression - ? O extends Nullable - ? N | null - : O - : never - > - >; } - -export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum'; - -export const isAssetChecksumConstraint = (error: unknown) => { - return (error as PostgresError)?.constraint_name === 'UQ_assets_owner_checksum'; -}; - -export function withDefaultVisibility(qb: SelectQueryBuilder) { - return qb.where('asset.visibility', 'in', [sql.lit(AssetVisibility.Archive), sql.lit(AssetVisibility.Timeline)]); -} - -// TODO come up with a better query that only selects the fields we need -export function withExif(qb: SelectQueryBuilder) { - return qb - .leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .select((eb) => eb.fn.toJson(eb.table('asset_exif')).$castTo().as('exifInfo')); -} - -export function withExifInner(qb: SelectQueryBuilder) { - return qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .select((eb) => eb.fn.toJson(eb.table('asset_exif')).$castTo().as('exifInfo')); -} - -export function withSmartSearch(qb: SelectQueryBuilder) { - return qb - .leftJoin('smart_search', 'asset.id', 'smart_search.assetId') - .select((eb) => toJson(eb, 'smart_search').as('smartSearch')); -} - -export function withFaces(eb: ExpressionBuilder, withHidden?: boolean, withDeletedFace?: boolean) { - return jsonArrayFrom( - eb - .selectFrom('asset_face') - .selectAll('asset_face') - .whereRef('asset_face.assetId', '=', 'asset.id') - .$if(!withDeletedFace, (qb) => qb.where('asset_face.deletedAt', 'is', null)) - .$if(!withHidden, (qb) => qb.where('asset_face.isVisible', '=', true)), - ).as('faces'); -} - -export function withFiles(eb: ExpressionBuilder, type?: AssetFileType) { - return jsonArrayFrom( - eb - .selectFrom('asset_file') - .select(columns.assetFiles) - .whereRef('asset_file.assetId', '=', 'asset.id') - .$if(!!type, (qb) => qb.where('asset_file.type', '=', type!)), - ).as('files'); -} - -export function withFilePath(eb: ExpressionBuilder, type: AssetFileType) { - return eb - .selectFrom('asset_file') - .select('asset_file.path') - .whereRef('asset_file.assetId', '=', 'asset.id') - .where('asset_file.type', '=', type); -} - -export function withFacesAndPeople( - eb: ExpressionBuilder, - withHidden?: boolean, - withDeletedFace?: boolean, -) { - return jsonArrayFrom( - eb - .selectFrom('asset_face') - .leftJoinLateral( - (eb) => - eb.selectFrom('person').selectAll('person').whereRef('asset_face.personId', '=', 'person.id').as('person'), - (join) => join.onTrue(), - ) - .selectAll('asset_face') - .select((eb) => eb.table('person').$castTo().as('person')) - .whereRef('asset_face.assetId', '=', 'asset.id') - .$if(!withDeletedFace, (qb) => qb.where('asset_face.deletedAt', 'is', null)) - .$if(!withHidden, (qb) => qb.where('asset_face.isVisible', 'is', true)), - ).as('faces'); -} - -export function hasPeople(qb: SelectQueryBuilder, personIds: string[]) { - return qb.innerJoin( - (eb) => - eb - .selectFrom('asset_face') - .select('assetId') - .where('personId', '=', anyUuid(personIds!)) - .where('deletedAt', 'is', null) - .where('isVisible', 'is', true) - .groupBy('assetId') - .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length) - .as('has_people'), - (join) => join.onRef('has_people.assetId', '=', 'asset.id'), - ); -} - -export function inAlbums(qb: SelectQueryBuilder, albumIds: string[]) { - return qb.innerJoin( - (eb) => - eb - .selectFrom('album_asset') - .select('assetId') - .where('albumId', '=', anyUuid(albumIds!)) - .groupBy('assetId') - .having((eb) => eb.fn.count('albumId').distinct(), '=', albumIds.length) - .as('has_album'), - (join) => join.onRef('has_album.assetId', '=', 'asset.id'), - ); -} - -export function hasTags(qb: SelectQueryBuilder, tagIds: string[]) { - return qb.innerJoin( - (eb) => - eb - .selectFrom('tag_asset') - .select('assetId') - .innerJoin('tag_closure', 'tag_asset.tagId', 'tag_closure.id_descendant') - .where('tag_closure.id_ancestor', '=', anyUuid(tagIds)) - .groupBy('assetId') - .having((eb) => eb.fn.count('tag_closure.id_ancestor').distinct(), '>=', tagIds.length) - .as('has_tags'), - (join) => join.onRef('has_tags.assetId', '=', 'asset.id'), - ); -} - -export function withOwner(eb: ExpressionBuilder) { - return jsonObjectFrom(eb.selectFrom('user').select(columns.user).whereRef('user.id', '=', 'asset.ownerId')).as( - 'owner', - ); -} - -export function withLibrary(eb: ExpressionBuilder) { - return jsonObjectFrom( - eb.selectFrom('library').selectAll('library').whereRef('library.id', '=', 'asset.libraryId'), - ).as('library'); -} - -export function withTags(eb: ExpressionBuilder) { - return jsonArrayFrom( - eb - .selectFrom('tag') - .select(columns.tag) - .innerJoin('tag_asset', 'tag.id', 'tag_asset.tagId') - .whereRef('asset.id', '=', 'tag_asset.assetId'), - ).as('tags'); -} - -export function truncatedDate() { - return sql`date_trunc(${sql.lit('MONTH')}, "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`; -} - -export function withTagId(qb: SelectQueryBuilder, tagId: string) { - return qb.where((eb) => - eb.exists( - eb - .selectFrom('tag_closure') - .innerJoin('tag_asset', 'tag_asset.tagId', 'tag_closure.id_descendant') - .whereRef('tag_asset.assetId', '=', 'asset.id') - .where('tag_closure.id_ancestor', '=', tagId), - ), - ); -} - -const isCJK = (c: number): boolean => - (c >= 0x4e_00 && c <= 0x9f_ff) || - (c >= 0xac_00 && c <= 0xd7_af) || - (c >= 0x30_40 && c <= 0x30_9f) || - (c >= 0x30_a0 && c <= 0x30_ff) || - (c >= 0x34_00 && c <= 0x4d_bf); - -export const tokenizeForSearch = (text: string): string[] => { - /* eslint-disable unicorn/prefer-code-point */ - const tokens: string[] = []; - let i = 0; - while (i < text.length) { - const c = text.charCodeAt(i); - if (c <= 32) { - i++; - continue; - } - - const start = i; - if (isCJK(c)) { - while (i < text.length && isCJK(text.charCodeAt(i))) { - i++; - } - if (i - start === 1) { - tokens.push(text[start]); - } else { - for (let k = start; k < i - 1; k++) { - tokens.push(text[k] + text[k + 1]); - } - } - } else { - while (i < text.length && text.charCodeAt(i) > 32 && !isCJK(text.charCodeAt(i))) { - i++; - } - tokens.push(text.slice(start, i)); - } - } - return tokens; -}; - -// needed to properly type the return with the EditActionItem discriminated union type -type AliasedEditActions = AliasedRawBuilder; -export function withEdits(eb: ExpressionBuilder): AliasedEditActions { - return jsonArrayFrom( - eb - .selectFrom('asset_edit') - .select(['asset_edit.action', 'asset_edit.parameters']) - .whereRef('asset_edit.assetId', '=', 'asset.id'), - ).as('edits') as AliasedEditActions; -} - -const joinDeduplicationPlugin = new DeduplicateJoinsPlugin(); -/** TODO: This should only be used for search-related queries, not as a general purpose query builder */ - -export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuilderOptions) { - options.withDeleted ||= !!(options.trashedAfter || options.trashedBefore || options.isOffline); - const visibility = options.visibility == null ? AssetVisibility.Timeline : options.visibility; - - return kysely - .withPlugin(joinDeduplicationPlugin) - .selectFrom('asset') - .where('asset.visibility', '=', visibility) - .$if(!!options.albumIds && options.albumIds.length > 0, (qb) => inAlbums(qb, options.albumIds!)) - .$if(!!options.tagIds && options.tagIds.length > 0, (qb) => hasTags(qb, options.tagIds!)) - .$if(options.tagIds === null, (qb) => - qb.where((eb) => eb.not(eb.exists((eb) => eb.selectFrom('tag_asset').whereRef('assetId', '=', 'asset.id')))), - ) - .$if(!!options.personIds && options.personIds.length > 0, (qb) => hasPeople(qb, options.personIds!)) - .$if(!!options.createdBefore, (qb) => qb.where('asset.createdAt', '<=', options.createdBefore!)) - .$if(!!options.createdAfter, (qb) => qb.where('asset.createdAt', '>=', options.createdAfter!)) - .$if(!!options.updatedBefore, (qb) => qb.where('asset.updatedAt', '<=', options.updatedBefore!)) - .$if(!!options.updatedAfter, (qb) => qb.where('asset.updatedAt', '>=', options.updatedAfter!)) - .$if(!!options.trashedBefore, (qb) => qb.where('asset.deletedAt', '<=', options.trashedBefore!)) - .$if(!!options.trashedAfter, (qb) => qb.where('asset.deletedAt', '>=', options.trashedAfter!)) - .$if(!!options.takenBefore, (qb) => qb.where('asset.fileCreatedAt', '<=', options.takenBefore!)) - .$if(!!options.takenAfter, (qb) => qb.where('asset.fileCreatedAt', '>=', options.takenAfter!)) - .$if(options.city !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.city', options.city === null ? 'is' : '=', options.city!), - ) - .$if(options.state !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.state', options.state === null ? 'is' : '=', options.state!), - ) - .$if(options.country !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.country', options.country === null ? 'is' : '=', options.country!), - ) - .$if(options.make !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.make', options.make === null ? 'is' : '=', options.make!), - ) - .$if(options.model !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.model', options.model === null ? 'is' : '=', options.model!), - ) - .$if(options.lensModel !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.lensModel', options.lensModel === null ? 'is' : '=', options.lensModel!), - ) - .$if(options.rating !== undefined, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where('asset_exif.rating', options.rating === null ? 'is' : '=', options.rating!), - ) - .$if(!!options.checksum, (qb) => qb.where('asset.checksum', '=', options.checksum!)) - .$if(!!options.deviceAssetId, (qb) => qb.where('asset.deviceAssetId', '=', options.deviceAssetId!)) - .$if(!!options.deviceId, (qb) => qb.where('asset.deviceId', '=', options.deviceId!)) - .$if(!!options.id, (qb) => qb.where('asset.id', '=', asUuid(options.id!))) - .$if(!!options.libraryId, (qb) => qb.where('asset.libraryId', '=', asUuid(options.libraryId!))) - .$if(!!options.userIds, (qb) => qb.where('asset.ownerId', '=', anyUuid(options.userIds!))) - .$if(!!options.encodedVideoPath, (qb) => qb.where('asset.encodedVideoPath', '=', options.encodedVideoPath!)) - .$if(!!options.originalPath, (qb) => - qb.where(sql`f_unaccent(asset."originalPath")`, 'ilike', sql`'%' || f_unaccent(${options.originalPath}) || '%'`), - ) - .$if(!!options.originalFileName, (qb) => - qb.where( - sql`f_unaccent(asset."originalFileName")`, - 'ilike', - sql`'%' || f_unaccent(${options.originalFileName}) || '%'`, - ), - ) - .$if(!!options.description, (qb) => - qb - .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId') - .where(sql`f_unaccent(asset_exif.description)`, 'ilike', sql`'%' || f_unaccent(${options.description}) || '%'`), - ) - .$if(!!options.ocr, (qb) => - qb - .innerJoin('ocr_search', 'asset.id', 'ocr_search.assetId') - .where(() => sql`f_unaccent(ocr_search.text) %>> f_unaccent(${tokenizeForSearch(options.ocr!).join(' ')})`), - ) - .$if(!!options.type, (qb) => qb.where('asset.type', '=', options.type!)) - .$if(options.isFavorite !== undefined, (qb) => qb.where('asset.isFavorite', '=', options.isFavorite!)) - .$if(options.isOffline !== undefined, (qb) => qb.where('asset.isOffline', '=', options.isOffline!)) - .$if(options.isEncoded !== undefined, (qb) => - qb.where('asset.encodedVideoPath', options.isEncoded ? 'is not' : 'is', null), - ) - .$if(options.isMotion !== undefined, (qb) => - qb.where('asset.livePhotoVideoId', options.isMotion ? 'is not' : 'is', null), - ) - .$if(!!options.isNotInAlbum && (!options.albumIds || options.albumIds.length === 0), (qb) => - qb.where((eb) => eb.not(eb.exists((eb) => eb.selectFrom('album_asset').whereRef('assetId', '=', 'asset.id')))), - ) - .$if(!!options.withExif, withExifInner) - .$if(!!(options.withFaces || options.withPeople), (qb) => qb.select(withFacesAndPeople)) - .$if(!options.withDeleted, (qb) => qb.where('asset.deletedAt', 'is', null)); -} - -export type ReindexVectorIndexOptions = { indexName: string; lists?: number }; - -type VectorIndexQueryOptions = { table: string; vectorExtension: VectorExtension } & ReindexVectorIndexOptions; - -export function vectorIndexQuery({ vectorExtension, table, indexName, lists }: VectorIndexQueryOptions): string { - switch (vectorExtension) { - case DatabaseExtension.VectorChord: { - return ` - CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} USING vchordrq (embedding vector_cosine_ops) WITH (options = $$ - residual_quantization = false - [build.internal] - lists = [${lists ?? 1}] - spherical_centroids = true - build_threads = 4 - sampling_factor = 1024 - $$)`; - } - case DatabaseExtension.Vectors: { - return ` - CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} - USING vectors (embedding vector_cos_ops) WITH (options = $$ - optimizing.optimizing_threads = 4 - [indexing.hnsw] - m = 16 - ef_construction = 300 - $$)`; - } - case DatabaseExtension.Vector: { - return ` - CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} - USING hnsw (embedding vector_cosine_ops) - WITH (ef_construction = 300, m = 16)`; - } - default: { - throw new Error(`Unsupported vector extension: '${vectorExtension}'`); - } - } -} - -export const updateLockedColumns = & { lockedProperties?: LockableProperty[] }>( - exif: T, -) => { - exif.lockedProperties = lockableProperties.filter((property) => property in exif); - return exif; -}; diff --git a/server/src/utils/date.ts b/server/src/utils/date.ts deleted file mode 100644 index 6cef48ecf8..0000000000 --- a/server/src/utils/date.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DateTime } from 'luxon'; - -export const asDateString = (x: Date | string | null): string | null => { - return x instanceof Date ? x.toISOString().split('T')[0] : x; -}; - -export const extractTimeZone = (dateTimeOriginal?: string | null) => { - const extractedTimeZone = dateTimeOriginal ? DateTime.fromISO(dateTimeOriginal, { setZone: true }).zone : undefined; - return extractedTimeZone?.type === 'fixed' ? extractedTimeZone : undefined; -}; - -export const mergeTimeZone = (dateTimeOriginal?: string | null, timeZone?: string | null) => { - return dateTimeOriginal - ? DateTime.fromISO(dateTimeOriginal, { zone: 'UTC' }).setZone(timeZone ?? undefined) - : undefined; -}; diff --git a/server/src/utils/editor.spec.ts b/server/src/utils/editor.spec.ts deleted file mode 100644 index 17db0d9da3..0000000000 --- a/server/src/utils/editor.spec.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { AssetFace } from 'src/database'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { SourceType } from 'src/enum'; -import { boundingBoxOverlap, checkFaceVisibility, checkOcrVisibility } from 'src/utils/editor'; -import { describe, expect, it } from 'vitest'; - -describe('boundingBoxOverlap', () => { - it('should return 1 for identical boxes', () => { - const box = { x1: 0, y1: 0, x2: 100, y2: 100 }; - expect(boundingBoxOverlap(box, box)).toBe(1); - }); - - it('should return 0 for non-overlapping boxes', () => { - const boxA = { x1: 0, y1: 0, x2: 100, y2: 100 }; - const boxB = { x1: 200, y1: 200, x2: 300, y2: 300 }; - expect(boundingBoxOverlap(boxA, boxB)).toBe(0); - }); - - it('should return 0.5 for 50% overlap', () => { - const boxA = { x1: 0, y1: 0, x2: 100, y2: 100 }; - const boxB = { x1: 50, y1: 0, x2: 150, y2: 100 }; - expect(boundingBoxOverlap(boxA, boxB)).toBe(0.5); - }); - - it('should return 0.25 for 25% overlap', () => { - const boxA = { x1: 0, y1: 0, x2: 100, y2: 100 }; - const boxB = { x1: 50, y1: 50, x2: 150, y2: 150 }; - expect(boundingBoxOverlap(boxA, boxB)).toBe(0.25); - }); - - it('should return 1 when boxA is fully contained in boxB', () => { - const boxA = { x1: 25, y1: 25, x2: 75, y2: 75 }; - const boxB = { x1: 0, y1: 0, x2: 100, y2: 100 }; - expect(boundingBoxOverlap(boxA, boxB)).toBe(1); - }); - - it('should handle partial containment correctly', () => { - const boxA = { x1: 0, y1: 0, x2: 100, y2: 100 }; - const boxB = { x1: 25, y1: 25, x2: 75, y2: 75 }; - // boxB is fully inside boxA, so overlap area is 50*50=2500, boxA area is 10000 - expect(boundingBoxOverlap(boxA, boxB)).toBe(0.25); - }); - - it('should handle boxes that touch at edges (no overlap)', () => { - const boxA = { x1: 0, y1: 0, x2: 100, y2: 100 }; - const boxB = { x1: 100, y1: 0, x2: 200, y2: 100 }; - expect(boundingBoxOverlap(boxA, boxB)).toBe(0); - }); - - it('should handle vertical partial overlap', () => { - const boxA = { x1: 0, y1: 0, x2: 100, y2: 100 }; - const boxB = { x1: 0, y1: 50, x2: 100, y2: 150 }; - expect(boundingBoxOverlap(boxA, boxB)).toBe(0.5); - }); -}); - -const createFace = (params: Partial = {}): AssetFace => ({ - id: 'face-id', - deletedAt: null, - assetId: 'asset-id', - boundingBoxX1: 100, - boundingBoxX2: 200, - boundingBoxY1: 100, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 1000, - personId: null, - sourceType: SourceType.MachineLearning, - person: null, - updatedAt: new Date(), - updateId: 'update-id', - isVisible: true, - ...params, -}); - -describe('checkFaceVisibility', () => { - const assetDimensions = { width: 1000, height: 1000 }; - - it('should return only non-visible faces when no crop is provided', () => { - const faces = [ - createFace({ id: 'face-1', isVisible: true }), - createFace({ id: 'face-2', isVisible: false }), - createFace({ id: 'face-3', isVisible: false }), - ]; - const result = checkFaceVisibility(faces, assetDimensions); - - expect(result.visible).toHaveLength(2); - expect(result.hidden).toHaveLength(0); - expect(result.visible.map((f) => f.id)).toEqual(['face-2', 'face-3']); - }); - - it('should return all faces as visible when all are marked not visible and no crop provided', () => { - const faces = [createFace({ id: 'face-1', isVisible: false }), createFace({ id: 'face-2', isVisible: false })]; - const result = checkFaceVisibility(faces, assetDimensions); - - expect(result.visible).toHaveLength(2); - expect(result.hidden).toHaveLength(0); - }); - - it('should return empty visible array when all faces are already visible and no crop provided', () => { - const faces = [createFace({ id: 'face-1', isVisible: true }), createFace({ id: 'face-2', isVisible: true })]; - const result = checkFaceVisibility(faces, assetDimensions); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(0); - }); - - it('should return empty arrays when no faces provided', () => { - const result = checkFaceVisibility([], assetDimensions); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(0); - }); - - it('should mark face as visible when fully inside crop area', () => { - const faces = [createFace({ boundingBoxX1: 100, boundingBoxY1: 100, boundingBoxX2: 200, boundingBoxY2: 200 })]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should mark face as hidden when fully outside crop area', () => { - const faces = [createFace({ boundingBoxX1: 600, boundingBoxY1: 600, boundingBoxX2: 700, boundingBoxY2: 700 })]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(1); - }); - - it('should mark face as visible when at least 50% overlaps with crop', () => { - // Face spans 100-200 (100px), crop starts at 150, so 50% overlap - const faces = [createFace({ boundingBoxX1: 100, boundingBoxY1: 100, boundingBoxX2: 200, boundingBoxY2: 200 })]; - const crop = { x1: 150, y1: 100, x2: 500, y2: 500 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should mark face as hidden when less than 50% overlaps with crop', () => { - // Face spans 100-200 (100px), crop starts at 160, so 40% overlap - const faces = [createFace({ boundingBoxX1: 100, boundingBoxY1: 100, boundingBoxX2: 200, boundingBoxY2: 200 })]; - const crop = { x1: 160, y1: 100, x2: 500, y2: 500 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(1); - }); - - it('should correctly categorize multiple faces', () => { - const faces = [ - createFace({ id: 'face-inside', boundingBoxX1: 100, boundingBoxY1: 100, boundingBoxX2: 200, boundingBoxY2: 200 }), - createFace({ - id: 'face-outside', - boundingBoxX1: 800, - boundingBoxY1: 800, - boundingBoxX2: 900, - boundingBoxY2: 900, - }), - // face-partial: 400-500 overlaps with crop (100x100=10000 overlap, face is 200x200=40000, so 25% - hidden) - createFace({ - id: 'face-partial', - boundingBoxX1: 400, - boundingBoxY1: 400, - boundingBoxX2: 600, - boundingBoxY2: 600, - }), - ]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - // face-inside is fully visible, face-partial has 25% overlap (hidden), face-outside is fully hidden - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(2); - expect(result.visible.map((f) => f.id)).toContain('face-inside'); - expect(result.hidden.map((f) => f.id)).toContain('face-partial'); - expect(result.hidden.map((f) => f.id)).toContain('face-outside'); - }); - - it('should handle face coordinates scaled to different image dimensions', () => { - // Face stored at 50-100 in a 500x500 image, scaled to 1000x1000 becomes 100-200 - const faces = [ - createFace({ - boundingBoxX1: 50, - boundingBoxY1: 50, - boundingBoxX2: 100, - boundingBoxY2: 100, - imageWidth: 500, - imageHeight: 500, - }), - ]; - const crop = { x1: 0, y1: 0, x2: 200, y2: 200 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should categorize based on crop overlap when crop is provided, regardless of isVisible property', () => { - const faces = [ - createFace({ - id: 'face-inside-visible', - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - isVisible: true, - }), - createFace({ - id: 'face-inside-not-visible', - boundingBoxX1: 250, - boundingBoxY1: 250, - boundingBoxX2: 350, - boundingBoxY2: 350, - isVisible: false, - }), - createFace({ - id: 'face-outside-visible', - boundingBoxX1: 800, - boundingBoxY1: 800, - boundingBoxX2: 900, - boundingBoxY2: 900, - isVisible: true, - }), - createFace({ - id: 'face-outside-not-visible', - boundingBoxX1: 700, - boundingBoxY1: 700, - boundingBoxX2: 800, - boundingBoxY2: 800, - isVisible: false, - }), - ]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkFaceVisibility(faces, assetDimensions, crop); - - // When crop is provided, only overlap matters, not isVisible property - expect(result.visible).toHaveLength(2); - expect(result.hidden).toHaveLength(2); - expect(result.visible.map((f) => f.id)).toContain('face-inside-visible'); - expect(result.visible.map((f) => f.id)).toContain('face-inside-not-visible'); - expect(result.hidden.map((f) => f.id)).toContain('face-outside-visible'); - expect(result.hidden.map((f) => f.id)).toContain('face-outside-not-visible'); - }); - - it('should handle mixed visibility states with partial overlap and crop', () => { - const faces = [ - createFace({ - id: 'face-partial-50', - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - isVisible: true, - }), - createFace({ - id: 'face-partial-40', - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - isVisible: false, - }), - ]; - const crop1 = { x1: 150, y1: 100, x2: 500, y2: 500 }; // 50% overlap - const crop2 = { x1: 160, y1: 100, x2: 500, y2: 500 }; // 40% overlap - - const result1 = checkFaceVisibility([faces[0]], assetDimensions, crop1); - const result2 = checkFaceVisibility([faces[1]], assetDimensions, crop2); - - // 50% overlap should be visible - expect(result1.visible).toHaveLength(1); - expect(result1.hidden).toHaveLength(0); - - // 40% overlap should be hidden - expect(result2.visible).toHaveLength(0); - expect(result2.hidden).toHaveLength(1); - }); -}); - -const createOcr = ( - params: Partial = {}, -): AssetOcrResponseDto & { isVisible: boolean } => ({ - id: 'ocr-id', - assetId: 'asset-id', - x1: 0.1, - y1: 0.1, - x2: 0.2, - y2: 0.1, - x3: 0.2, - y3: 0.2, - x4: 0.1, - y4: 0.2, - boxScore: 0.9, - textScore: 0.9, - text: 'Sample Text', - isVisible: true, - ...params, -}); - -describe('checkOcrVisibility', () => { - const assetDimensions = { width: 1000, height: 1000 }; - - it('should return only non-visible OCR entries when no crop is provided', () => { - const ocrs = [ - createOcr({ id: 'ocr-1', isVisible: true }), - createOcr({ id: 'ocr-2', isVisible: false }), - createOcr({ id: 'ocr-3', isVisible: false }), - ]; - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toHaveLength(2); - expect(result.hidden).toHaveLength(0); - expect(result.visible.map((o) => o.id)).toEqual(['ocr-2', 'ocr-3']); - }); - - it('should return all OCR entries as visible when all are marked not visible and no crop provided', () => { - const ocrs = [createOcr({ id: 'ocr-1', isVisible: false }), createOcr({ id: 'ocr-2', isVisible: false })]; - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toHaveLength(2); - expect(result.hidden).toHaveLength(0); - }); - - it('should return empty visible array when all OCR entries are already visible and no crop provided', () => { - const ocrs = [createOcr({ id: 'ocr-1', isVisible: true }), createOcr({ id: 'ocr-2', isVisible: true })]; - const result = checkOcrVisibility(ocrs, assetDimensions); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(0); - }); - - it('should return empty arrays when no OCR entries provided', () => { - const result = checkOcrVisibility([], assetDimensions); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(0); - }); - - it('should mark OCR as visible when fully inside crop area', () => { - // OCR box at normalized coords 0.1-0.2 = 100-200px in 1000x1000 image - const ocrs = [createOcr()]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should mark OCR as hidden when fully outside crop area', () => { - // OCR box at normalized coords 0.8-0.9 = 800-900px - const ocrs = [createOcr({ x1: 0.8, y1: 0.8, x2: 0.9, y2: 0.8, x3: 0.9, y3: 0.9, x4: 0.8, y4: 0.9 })]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(1); - }); - - it('should mark OCR as visible when at least 50% overlaps with crop', () => { - // OCR at 100-200px (0.1-0.2 normalized), crop starts at 150 - const ocrs = [createOcr()]; - const crop = { x1: 150, y1: 100, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should mark OCR as hidden when less than 50% overlaps with crop', () => { - // OCR at 100-200px, crop starts at 160 = 40% overlap - const ocrs = [createOcr()]; - const crop = { x1: 160, y1: 100, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toHaveLength(0); - expect(result.hidden).toHaveLength(1); - }); - - it('should correctly categorize multiple OCR entries', () => { - const ocrs = [ - createOcr({ id: 'ocr-inside', x1: 0.1, y1: 0.1, x2: 0.2, y2: 0.1, x3: 0.2, y3: 0.2, x4: 0.1, y4: 0.2 }), - createOcr({ id: 'ocr-outside', x1: 0.8, y1: 0.8, x2: 0.9, y2: 0.8, x3: 0.9, y3: 0.9, x4: 0.8, y4: 0.9 }), - ]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(1); - expect(result.visible[0].id).toBe('ocr-inside'); - expect(result.hidden[0].id).toBe('ocr-outside'); - }); - - it('should handle rotated/skewed OCR polygons by using bounding box', () => { - // Rotated rectangle - the function should compute the bounding box correctly - const ocrs = [ - createOcr({ - id: 'ocr-rotated', - x1: 0.15, - y1: 0.1, // top - x2: 0.2, - y2: 0.15, // right - x3: 0.15, - y3: 0.2, // bottom - x4: 0.1, - y4: 0.15, // left - }), - ]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should handle different asset dimensions', () => { - const smallDimensions = { width: 500, height: 500 }; - // OCR at 0.1-0.2 normalized = 50-100px in 500x500 image - const ocrs = [createOcr()]; - const crop = { x1: 0, y1: 0, x2: 200, y2: 200 }; - - const result = checkOcrVisibility(ocrs, smallDimensions, crop); - - expect(result.visible).toHaveLength(1); - expect(result.hidden).toHaveLength(0); - }); - - it('should categorize based on crop overlap when crop is provided, regardless of isVisible property', () => { - const ocrs = [ - createOcr({ id: 'ocr-inside-visible', isVisible: true }), // Inside crop, already visible - createOcr({ id: 'ocr-inside-not-visible', isVisible: false }), // Inside crop, not visible - createOcr({ - id: 'ocr-outside-visible', - x1: 0.8, - y1: 0.8, - x2: 0.9, - y2: 0.8, - x3: 0.9, - y3: 0.9, - x4: 0.8, - y4: 0.9, - isVisible: true, - }), // Outside crop, already visible - createOcr({ - id: 'ocr-outside-not-visible', - x1: 0.8, - y1: 0.8, - x2: 0.9, - y2: 0.8, - x3: 0.9, - y3: 0.9, - x4: 0.8, - y4: 0.9, - isVisible: false, - }), // Outside crop, not visible - ]; - const crop = { x1: 0, y1: 0, x2: 500, y2: 500 }; - - const result = checkOcrVisibility(ocrs, assetDimensions, crop); - - // When crop is provided, only overlap matters, not isVisible property - expect(result.visible).toHaveLength(2); - expect(result.hidden).toHaveLength(2); - expect(result.visible.map((o) => o.id)).toContain('ocr-inside-visible'); - expect(result.visible.map((o) => o.id)).toContain('ocr-inside-not-visible'); - expect(result.hidden.map((o) => o.id)).toContain('ocr-outside-visible'); - expect(result.hidden.map((o) => o.id)).toContain('ocr-outside-not-visible'); - }); - - it('should handle mixed visibility states with partial overlap and crop', () => { - const ocrs = [ - createOcr({ id: 'ocr-partial-50', isVisible: true }), // 50% overlap - createOcr({ id: 'ocr-partial-40', isVisible: false }), // 40% overlap - ]; - const crop1 = { x1: 150, y1: 100, x2: 500, y2: 500 }; // 50% overlap - const crop2 = { x1: 160, y1: 100, x2: 500, y2: 500 }; // 40% overlap - - const result1 = checkOcrVisibility([ocrs[0]], assetDimensions, crop1); - const result2 = checkOcrVisibility([ocrs[1]], assetDimensions, crop2); - - // 50% overlap should be visible - expect(result1.visible).toHaveLength(1); - expect(result1.hidden).toHaveLength(0); - - // 40% overlap should be hidden - expect(result2.visible).toHaveLength(0); - expect(result2.hidden).toHaveLength(1); - }); -}); diff --git a/server/src/utils/editor.ts b/server/src/utils/editor.ts deleted file mode 100644 index 21678f2a82..0000000000 --- a/server/src/utils/editor.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { AssetFace } from 'src/database'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { ImageDimensions } from 'src/types'; - -type BoundingBox = { - x1: number; - y1: number; - x2: number; - y2: number; -}; - -export const boundingBoxOverlap = (boxA: BoundingBox, boxB: BoundingBox) => { - const overlapX1 = Math.max(boxA.x1, boxB.x1); - const overlapY1 = Math.max(boxA.y1, boxB.y1); - const overlapX2 = Math.min(boxA.x2, boxB.x2); - const overlapY2 = Math.min(boxA.y2, boxB.y2); - - const overlapArea = Math.max(0, overlapX2 - overlapX1) * Math.max(0, overlapY2 - overlapY1); - const faceArea = (boxA.x2 - boxA.x1) * (boxA.y2 - boxA.y1); - return overlapArea / faceArea; -}; - -const scale = (box: BoundingBox, target: ImageDimensions, source?: ImageDimensions) => { - const { width: sourceWidth = 1, height: sourceHeight = 1 } = source ?? {}; - - return { - x1: (box.x1 / sourceWidth) * target.width, - y1: (box.y1 / sourceHeight) * target.height, - x2: (box.x2 / sourceWidth) * target.width, - y2: (box.y2 / sourceHeight) * target.height, - }; -}; - -export const checkFaceVisibility = ( - faces: AssetFace[], - originalAssetDimensions: ImageDimensions, - crop?: BoundingBox, -): { visible: AssetFace[]; hidden: AssetFace[] } => { - if (!crop) { - return { - visible: faces.filter((face) => !face.isVisible), - hidden: [], - }; - } - - const status = faces.map((face) => { - const scaledFace = scale( - { - x1: face.boundingBoxX1, - y1: face.boundingBoxY1, - x2: face.boundingBoxX2, - y2: face.boundingBoxY2, - }, - originalAssetDimensions, - { width: face.imageWidth, height: face.imageHeight }, - ); - - const overlapPercentage = boundingBoxOverlap(scaledFace, crop); - - return { - face, - isVisible: overlapPercentage >= 0.5, - }; - }); - - return { - visible: status.filter((s) => s.isVisible).map((s) => s.face), - hidden: status.filter((s) => !s.isVisible).map((s) => s.face), - }; -}; - -export const checkOcrVisibility = ( - ocrs: (AssetOcrResponseDto & { isVisible: boolean })[], - originalAssetDimensions: ImageDimensions, - crop?: BoundingBox, -): { visible: AssetOcrResponseDto[]; hidden: AssetOcrResponseDto[] } => { - if (!crop) { - return { - visible: ocrs.filter((ocr) => !ocr.isVisible), - hidden: [], - }; - } - - const status = ocrs.map((ocr) => { - const ocrBox = scale( - { - x1: Math.min(ocr.x1, ocr.x2, ocr.x3, ocr.x4), - y1: Math.min(ocr.y1, ocr.y2, ocr.y3, ocr.y4), - x2: Math.max(ocr.x1, ocr.x2, ocr.x3, ocr.x4), - y2: Math.max(ocr.y1, ocr.y2, ocr.y3, ocr.y4), - }, - originalAssetDimensions, - ); - - const overlapPercentage = boundingBoxOverlap(ocrBox, crop); - - return { - ocr, - isVisible: overlapPercentage >= 0.5, - }; - }); - - return { - visible: status.filter((s) => s.isVisible).map((s) => s.ocr), - hidden: status.filter((s) => !s.isVisible).map((s) => s.ocr), - }; -}; diff --git a/server/src/utils/file.ts b/server/src/utils/file.ts deleted file mode 100644 index 04f1ce48d9..0000000000 --- a/server/src/utils/file.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { HttpException, StreamableFile } from '@nestjs/common'; -import { NextFunction, Response } from 'express'; -import { access, constants } from 'node:fs/promises'; -import { basename, extname } from 'node:path'; -import { promisify } from 'node:util'; -import { CacheControl } from 'src/enum'; -import { LoggingRepository } from 'src/repositories/logging.repository'; -import { ImmichReadStream } from 'src/repositories/storage.repository'; -import { isConnectionAborted } from 'src/utils/misc'; - -export function getFileNameWithoutExtension(path: string): string { - return basename(path, extname(path)); -} - -export function getFilenameExtension(path: string): string { - return extname(path); -} - -export function getLivePhotoMotionFilename(stillName: string, motionName: string) { - return getFileNameWithoutExtension(stillName) + extname(motionName); -} - -export class ImmichFileResponse { - public readonly path!: string; - public readonly contentType!: string; - public readonly cacheControl!: CacheControl; - public readonly fileName?: string; - - constructor(response: ImmichFileResponse) { - Object.assign(this, response); - } -} -type SendFile = Parameters; -type SendFileOptions = SendFile[1]; - -const cacheControlHeaders: Record = { - [CacheControl.PrivateWithCache]: - 'private, max-age=86400, no-transform, stale-while-revalidate=2592000, stale-if-error=2592000', - [CacheControl.PrivateWithoutCache]: 'private, no-cache, no-transform', - [CacheControl.None]: null, // falsy value to prevent adding Cache-Control header -}; - -export const sendFile = async ( - res: Response, - next: NextFunction, - handler: () => Promise | ImmichFileResponse, - logger: LoggingRepository, -): Promise => { - // promisified version of 'res.sendFile' for cleaner async handling - const _sendFile = (path: string, options: SendFileOptions) => - promisify(res.sendFile).bind(res)(path, options); - - try { - const file = await handler(); - const cacheControlHeader = cacheControlHeaders[file.cacheControl]; - if (cacheControlHeader) { - // set the header to Cache-Control - res.set('Cache-Control', cacheControlHeader); - } - - res.header('Content-Type', file.contentType); - if (file.fileName) { - res.header('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.fileName)}`); - } - - await access(file.path, constants.R_OK); - - return await _sendFile(file.path, { dotfiles: 'allow' }); - } catch (error: Error | any) { - // ignore client-closed connection - if (isConnectionAborted(error) || res.headersSent) { - return; - } - - // log non-http errors - if (error instanceof HttpException === false) { - logger.error(`Unable to send file: ${error}`, error.stack); - } - - res.header('Cache-Control', 'none'); - next(error); - } -}; - -export const asStreamableFile = ({ stream, type, length }: ImmichReadStream) => { - return new StreamableFile(stream, { type, length }); -}; diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts deleted file mode 100644 index ecc8847043..0000000000 --- a/server/src/utils/logger.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { HttpException } from '@nestjs/common'; -import { LoggingRepository } from 'src/repositories/logging.repository'; - -export const logGlobalError = (logger: LoggingRepository, error: Error) => { - if (error instanceof HttpException) { - const status = error.getStatus(); - const response = error.getResponse(); - logger.debug(`HttpException(${status}): ${JSON.stringify(response)}`); - return; - } - - if (error instanceof Error) { - logger.error(`Unknown error: ${error}`, error?.stack); - return; - } -}; diff --git a/server/src/utils/maintenance.ts b/server/src/utils/maintenance.ts deleted file mode 100644 index 47abb0ab89..0000000000 --- a/server/src/utils/maintenance.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { createAdapter } from '@socket.io/redis-adapter'; -import Redis from 'ioredis'; -import { SignJWT } from 'jose'; -import { randomBytes } from 'node:crypto'; -import { join } from 'node:path'; -import { Server as SocketIO } from 'socket.io'; -import { StorageCore } from 'src/cores/storage.core'; -import { MaintenanceAuthDto, MaintenanceDetectInstallResponseDto } from 'src/dtos/maintenance.dto'; -import { StorageFolder } from 'src/enum'; -import { ConfigRepository } from 'src/repositories/config.repository'; -import { AppRestartEvent } from 'src/repositories/event.repository'; -import { StorageRepository } from 'src/repositories/storage.repository'; - -export function sendOneShotAppRestart(state: AppRestartEvent): void { - const server = new SocketIO(); - const { redis } = new ConfigRepository().getEnv(); - const pubClient = new Redis(redis); - const subClient = pubClient.duplicate(); - server.adapter(createAdapter(pubClient, subClient)); - - /** - * Keep trying until we manage to stop Immich - * - * Sometimes there appear to be communication - * issues between to the other servers. - * - * This issue only occurs with this method. - */ - async function tryTerminate() { - while (true) { - try { - const responses = await server.serverSideEmitWithAck('AppRestart', state); - if (responses.length > 0) { - return; - } - } catch (error) { - console.error(error); - console.error('Encountered an error while telling Immich to stop.'); - } - - console.info( - "\nIt doesn't appear that Immich stopped, trying again in a moment.\nIf Immich is already not running, you can ignore this error.", - ); - - await new Promise((r) => setTimeout(r, 1e3)); - } - } - - // => corresponds to notification.service.ts#onAppRestart - server.emit('AppRestartV1', state, () => { - void tryTerminate().finally(() => { - pubClient.disconnect(); - subClient.disconnect(); - }); - }); -} - -export async function createMaintenanceLoginUrl( - baseUrl: string, - auth: MaintenanceAuthDto, - secret: string, -): Promise { - return `${baseUrl}/maintenance?token=${await signMaintenanceJwt(secret, auth)}`; -} - -export async function signMaintenanceJwt(secret: string, data: MaintenanceAuthDto): Promise { - const alg = 'HS256'; - - return await new SignJWT({ ...data }) - .setProtectedHeader({ alg }) - .setIssuedAt() - .setExpirationTime('4h') - .sign(new TextEncoder().encode(secret)); -} - -export function generateMaintenanceSecret(): string { - return randomBytes(64).toString('hex'); -} - -export async function detectPriorInstall( - storageRepository: StorageRepository, -): Promise { - return { - storage: await Promise.all( - Object.values(StorageFolder).map(async (folder) => { - const path = StorageCore.getBaseFolder(folder); - const files = await storageRepository.readdir(path); - const filename = join(StorageCore.getBaseFolder(folder), '.immich'); - - let readable = false, - writable = false; - - try { - await storageRepository.readFile(filename); - readable = true; - - await storageRepository.overwriteFile(filename, Buffer.from(`${Date.now()}`)); - writable = true; - } catch { - // no-op - } - - return { - folder, - readable, - writable, - files: files.filter((fn) => fn !== '.immich').length, - }; - }), - ), - }; -} diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts deleted file mode 100644 index b2ffb9ac8b..0000000000 --- a/server/src/utils/media.ts +++ /dev/null @@ -1,983 +0,0 @@ -import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto'; -import { CQMode, ToneMapping, TranscodeHardwareAcceleration, TranscodeTarget, VideoCodec } from 'src/enum'; -import { - AudioStreamInfo, - BitrateDistribution, - TranscodeCommand, - VideoCodecHWConfig, - VideoCodecSWConfig, - VideoFormat, - VideoInterfaces, - VideoStreamInfo, -} from 'src/types'; - -export class BaseConfig implements VideoCodecSWConfig { - readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast']; - protected constructor(protected config: SystemConfigFFmpegDto) {} - - static create(config: SystemConfigFFmpegDto, interfaces: VideoInterfaces): VideoCodecSWConfig { - if (config.accel === TranscodeHardwareAcceleration.Disabled) { - return this.getSWCodecConfig(config); - } - return this.getHWCodecConfig(config, interfaces); - } - - private static getSWCodecConfig(config: SystemConfigFFmpegDto) { - switch (config.targetVideoCodec) { - case VideoCodec.H264: { - return new H264Config(config); - } - case VideoCodec.Hevc: { - return new HEVCConfig(config); - } - case VideoCodec.Vp9: { - return new VP9Config(config); - } - case VideoCodec.Av1: { - return new AV1Config(config); - } - default: { - throw new Error(`Codec '${config.targetVideoCodec}' is unsupported`); - } - } - } - - private static getHWCodecConfig(config: SystemConfigFFmpegDto, interfaces: VideoInterfaces) { - let handler: VideoCodecHWConfig; - switch (config.accel) { - case TranscodeHardwareAcceleration.Nvenc: { - handler = config.accelDecode - ? new NvencHwDecodeConfig(config, interfaces) - : new NvencSwDecodeConfig(config, interfaces); - break; - } - case TranscodeHardwareAcceleration.Qsv: { - handler = config.accelDecode - ? new QsvHwDecodeConfig(config, interfaces) - : new QsvSwDecodeConfig(config, interfaces); - break; - } - case TranscodeHardwareAcceleration.Vaapi: { - handler = config.accelDecode - ? new VaapiHwDecodeConfig(config, interfaces) - : new VaapiSwDecodeConfig(config, interfaces); - break; - } - case TranscodeHardwareAcceleration.Rkmpp: { - handler = config.accelDecode - ? new RkmppHwDecodeConfig(config, interfaces) - : new RkmppSwDecodeConfig(config, interfaces); - break; - } - default: { - throw new Error(`${config.accel.toUpperCase()} acceleration is unsupported`); - } - } - if (!handler.getSupportedCodecs().includes(config.targetVideoCodec)) { - throw new Error( - `${config.accel.toUpperCase()} acceleration does not support codec '${config.targetVideoCodec.toUpperCase()}'. Supported codecs: ${handler.getSupportedCodecs()}`, - ); - } - - return handler; - } - - getCommand( - target: TranscodeTarget, - videoStream: VideoStreamInfo, - audioStream?: AudioStreamInfo, - format?: VideoFormat, - ) { - const options = { - inputOptions: this.getBaseInputOptions(videoStream, format), - outputOptions: [...this.getBaseOutputOptions(target, videoStream, audioStream), '-v verbose'], - twoPass: this.eligibleForTwoPass(), - progress: { frameCount: videoStream.frameCount, percentInterval: 5 }, - } as TranscodeCommand; - if ([TranscodeTarget.All, TranscodeTarget.Video].includes(target)) { - const filters = this.getFilterOptions(videoStream); - if (filters.length > 0) { - options.outputOptions.push(`-vf ${filters.join(',')}`); - } - } - - options.outputOptions.push( - ...this.getPresetOptions(), - ...this.getOutputThreadOptions(), - ...this.getBitrateOptions(), - ); - - return options; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getBaseInputOptions(videoStream: VideoStreamInfo, format?: VideoFormat): string[] { - return this.getInputThreadOptions(); - } - - getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { - const videoCodec = [TranscodeTarget.All, TranscodeTarget.Video].includes(target) ? this.getVideoCodec() : 'copy'; - const audioCodec = [TranscodeTarget.All, TranscodeTarget.Audio].includes(target) ? this.getAudioCodec() : 'copy'; - - const options = [ - `-c:v ${videoCodec}`, - `-c:a ${audioCodec}`, - // Makes a second pass moving the moov atom to the - // beginning of the file for improved playback speed. - '-movflags faststart', - '-fps_mode passthrough', - // explicitly selects the video stream instead of leaving it up to FFmpeg - `-map 0:${videoStream.index}`, - // Strip metadata like capture date, camera, and GPS - '-map_metadata -1', - ]; - - if (audioStream) { - options.push(`-map 0:${audioStream.index}`); - } - if (this.getBFrames() > -1) { - options.push(`-bf ${this.getBFrames()}`); - } - if (this.getRefs() > 0) { - options.push(`-refs ${this.getRefs()}`); - } - if (this.getGopSize() > 0) { - options.push(`-g ${this.getGopSize()}`); - } - - if ( - this.config.targetVideoCodec === VideoCodec.Hevc && - (videoCodec !== 'copy' || videoStream.codecName === 'hevc') - ) { - options.push('-tag:v hvc1'); - } - - return options; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = []; - if (this.shouldScale(videoStream)) { - options.push(`scale=${this.getScaling(videoStream)}`); - } - - const tonemapOptions = this.getToneMapping(videoStream); - if (tonemapOptions.length > 0) { - options.push(...tonemapOptions); - } else if (!videoStream.pixelFormat.endsWith('420p')) { - options.push('format=yuv420p'); - } - - return options; - } - - getPresetOptions() { - return [`-preset ${this.config.preset}`]; - } - - getBitrateOptions() { - const bitrates = this.getBitrateDistribution(); - if (this.eligibleForTwoPass()) { - return [ - `-b:v ${bitrates.target}${bitrates.unit}`, - `-minrate ${bitrates.min}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - ]; - } else if (bitrates.max > 0) { - // -bufsize is the peak possible bitrate at any moment, while -maxrate is the max rolling average bitrate - return [ - `-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-bufsize ${bitrates.max * 2}${bitrates.unit}`, - ]; - } else { - return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`]; - } - } - - getInputThreadOptions(): Array { - return []; - } - - getOutputThreadOptions(): Array { - if (this.config.threads <= 0) { - return []; - } - return [`-threads ${this.config.threads}`]; - } - - eligibleForTwoPass() { - if (!this.config.twoPass || this.config.accel !== TranscodeHardwareAcceleration.Disabled) { - return false; - } - - return this.isBitrateConstrained(); - } - - getBitrateDistribution() { - const max = this.getMaxBitrateValue(); - const target = Math.ceil(max / 1.45); // recommended by https://developers.google.com/media/vp9/settings/vod - const min = target / 2; - const unit = this.getBitrateUnit(); - - return { max, target, min, unit } as BitrateDistribution; - } - - getTargetResolution(videoStream: VideoStreamInfo) { - let target; - target = - this.config.targetResolution === 'original' - ? Math.min(videoStream.height, videoStream.width) - : Number.parseInt(this.config.targetResolution); - - if (target % 2 !== 0) { - target -= 1; - } - - return target; - } - - shouldScale(videoStream: VideoStreamInfo) { - const oddDimensions = videoStream.height % 2 !== 0 || videoStream.width % 2 !== 0; - const largerThanTarget = Math.min(videoStream.height, videoStream.width) > this.getTargetResolution(videoStream); - return oddDimensions || largerThanTarget; - } - - shouldToneMap(videoStream: VideoStreamInfo) { - return videoStream.isHDR && this.config.tonemap !== ToneMapping.Disabled; - } - - getScaling(videoStream: VideoStreamInfo, mult = 2) { - const targetResolution = this.getTargetResolution(videoStream); - return this.isVideoVertical(videoStream) ? `${targetResolution}:-${mult}` : `-${mult}:${targetResolution}`; - } - - getSize(videoStream: VideoStreamInfo) { - const smaller = this.getTargetResolution(videoStream); - const factor = Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width); - let larger = Math.round(smaller * factor); - if (larger % 2 !== 0) { - larger -= 1; - } - return this.isVideoVertical(videoStream) ? { width: smaller, height: larger } : { width: larger, height: smaller }; - } - - isVideoRotated(videoStream: VideoStreamInfo) { - return Math.abs(videoStream.rotation) === 90; - } - - isVideoVertical(videoStream: VideoStreamInfo) { - return videoStream.height > videoStream.width || this.isVideoRotated(videoStream); - } - - isBitrateConstrained() { - return this.getMaxBitrateValue() > 0; - } - - getBitrateUnit() { - const maxBitrate = this.getMaxBitrateValue(); - return this.config.maxBitrate.trim().slice(maxBitrate.toString().length) || 'k'; // use inputted unit if provided, else default to kbps - } - - getMaxBitrateValue() { - return Number.parseInt(this.config.maxBitrate) || 0; - } - - getPresetIndex() { - return this.presets.indexOf(this.config.preset); - } - - getColors() { - return { - primaries: 'bt709', - transfer: 'bt709', - matrix: 'bt709', - }; - } - - getToneMapping(videoStream: VideoStreamInfo) { - if (!this.shouldToneMap(videoStream)) { - return []; - } - - const { primaries, transfer, matrix } = this.getColors(); - const options = `tonemapx=tonemap=${this.config.tonemap}:desat=0:p=${primaries}:t=${transfer}:m=${matrix}:r=pc:peak=100:format=yuv420p`; - return [options]; - } - - getAudioCodec(): string { - return this.config.targetAudioCodec; - } - - getVideoCodec(): string { - return this.config.targetVideoCodec; - } - - getBFrames() { - return this.config.bframes; - } - - getRefs() { - return this.config.refs; - } - - getGopSize() { - return this.config.gopSize; - } - - useCQP() { - return this.config.cqMode === CQMode.Cqp; - } -} - -export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig { - protected device: string; - protected interfaces: VideoInterfaces; - - constructor( - protected config: SystemConfigFFmpegDto, - interfaces: VideoInterfaces, - ) { - super(config); - this.interfaces = interfaces; - this.device = this.getDevice(interfaces); - } - - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc]; - } - - validateDevices(devices: string[]) { - if (devices.length === 0) { - throw new Error('No /dev/dri devices found. If using Docker, make sure at least one /dev/dri device is mounted'); - } - - return devices.filter(function (device) { - return device.startsWith('renderD') || device.startsWith('card'); - }); - } - - getDevice({ dri }: VideoInterfaces) { - if (this.config.preferredHwDevice === 'auto') { - // eslint-disable-next-line unicorn/no-array-reduce - return `/dev/dri/${this.validateDevices(dri).reduce(function (a, b) { - return a.localeCompare(b) < 0 ? b : a; - })}`; - } - - const deviceName = this.config.preferredHwDevice.replace('/dev/dri/', ''); - if (!dri.includes(deviceName)) { - throw new Error(`Device '${deviceName}' does not exist. If using Docker, make sure this device is mounted`); - } - - return `/dev/dri/${deviceName}`; - } - - getVideoCodec(): string { - return `${this.config.targetVideoCodec}_${this.config.accel}`; - } - - getGopSize() { - if (this.config.gopSize <= 0) { - return 256; - } - return this.config.gopSize; - } -} - -export class ThumbnailConfig extends BaseConfig { - static create(config: SystemConfigFFmpegDto): VideoCodecSWConfig { - return new ThumbnailConfig(config); - } - - getBaseInputOptions(videoStream: VideoStreamInfo, format?: VideoFormat): string[] { - // skip_frame nointra skips all frames for some MPEG-TS files. Look at ffmpeg tickets 7950 and 7895 for more details. - const options = - format?.formatName === 'mpegts' - ? ['-sws_flags accurate_rnd+full_chroma_int'] - : ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int']; - - const metadataOverrides = []; - if (videoStream.colorPrimaries === 'reserved') { - metadataOverrides.push('colour_primaries=1'); - } - - if (videoStream.colorSpace === 'reserved') { - metadataOverrides.push('matrix_coefficients=1'); - } - - if (videoStream.colorTransfer === 'reserved') { - metadataOverrides.push('transfer_characteristics=1'); - } - - if (metadataOverrides.length > 0) { - // workaround for https://fftrac-bg.ffmpeg.org/ticket/11020 - options.push(`-bsf:v ${videoStream.codecName}_metadata=${metadataOverrides.join(':')}`); - } - - return options; - } - - getBaseOutputOptions() { - return ['-fps_mode vfr', '-frames:v 1', '-update 1']; - } - - getFilterOptions(videoStream: VideoStreamInfo): string[] { - return [ - 'fps=12:start_time=0:eof_action=pass:round=down', - 'thumbnail=12', - String.raw`select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20)`, - 'trim=end_frame=2', - 'reverse', - ...super.getFilterOptions(videoStream), - ]; - } - - getPresetOptions() { - return []; - } - - getBitrateOptions() { - return []; - } - - eligibleForTwoPass() { - return false; - } - - getScaling(videoStream: VideoStreamInfo) { - return super.getScaling(videoStream) + ':flags=lanczos+accurate_rnd+full_chroma_int:out_range=pc'; - } -} - -export class H264Config extends BaseConfig { - getOutputThreadOptions() { - const options = super.getOutputThreadOptions(); - if (this.config.threads === 1) { - options.push('-x264-params frame-threads=1:pools=none'); - } - - return options; - } -} - -export class HEVCConfig extends BaseConfig { - getOutputThreadOptions() { - const options = super.getOutputThreadOptions(); - if (this.config.threads === 1) { - options.push('-x265-params frame-threads=1:pools=none'); - } - - return options; - } -} - -export class VP9Config extends BaseConfig { - getPresetOptions() { - const speed = Math.min(this.getPresetIndex(), 5); // values over 5 require realtime mode, which is its own can of worms since it overrides -crf and -threads - if (speed >= 0) { - return [`-cpu-used ${speed}`]; - } - return []; - } - - getBitrateOptions() { - const bitrates = this.getBitrateDistribution(); - if (bitrates.max > 0 && this.eligibleForTwoPass()) { - return [ - `-b:v ${bitrates.target}${bitrates.unit}`, - `-minrate ${bitrates.min}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - ]; - } - - return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`, `-b:v ${bitrates.max}${bitrates.unit}`]; - } - - getOutputThreadOptions() { - return ['-row-mt 1', ...super.getOutputThreadOptions()]; - } - - eligibleForTwoPass() { - return this.config.twoPass; - } -} - -export class AV1Config extends BaseConfig { - getVideoCodec(): string { - return 'libsvtav1'; - } - - getPresetOptions() { - const speed = this.getPresetIndex() + 4; // Use 4 as slowest, giving us an effective range of 4-12 which is far more useful than 0-8 - if (speed >= 0) { - return [`-preset ${speed}`]; - } - return []; - } - - getBitrateOptions() { - const options = [`-crf ${this.config.crf}`]; - const bitrates = this.getBitrateDistribution(); - const svtparams = []; - if (this.config.threads > 0) { - svtparams.push(`lp=${this.config.threads}`); - } - if (bitrates.max > 0) { - svtparams.push(`mbr=${bitrates.max}${bitrates.unit}`); - } - if (svtparams.length > 0) { - options.push(`-svtav1-params ${svtparams.join(':')}`); - } - return options; - } - - getOutputThreadOptions() { - return []; // Already set above with svtav1-params - } - - eligibleForTwoPass() { - return this.config.twoPass; - } -} - -export class NvencSwDecodeConfig extends BaseHWConfig { - getDevice() { - return '0'; - } - - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Av1]; - } - - getBaseInputOptions() { - return [`-init_hw_device cuda=cuda:${this.device}`, '-filter_hw_device cuda']; - } - - getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { - const options = [ - // below settings recommended from https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/ffmpeg-with-nvidia-gpu/index.html#command-line-for-latency-tolerant-high-quality-transcoding - '-tune hq', - '-qmin 0', - '-rc-lookahead 20', - '-i_qfactor 0.75', - ...super.getBaseOutputOptions(target, videoStream, audioStream), - ]; - if (this.getBFrames() > 0) { - options.push('-b_ref_mode middle', '-b_qfactor 1.1'); - } - if (this.config.temporalAQ) { - options.push('-temporal-aq 1'); - } - return options; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = this.getToneMapping(videoStream); - options.push('hwupload_cuda'); - if (this.shouldScale(videoStream)) { - options.push(`scale_cuda=${this.getScaling(videoStream)}:format=nv12`); - } - - return options; - } - - getPresetOptions() { - let presetIndex = this.getPresetIndex(); - if (presetIndex < 0) { - return []; - } - presetIndex = 7 - Math.min(6, presetIndex); // map to p1-p7; p7 is the highest quality, so reverse index - return [`-preset p${presetIndex}`]; - } - - getBitrateOptions() { - const bitrates = this.getBitrateDistribution(); - if (bitrates.max > 0 && this.config.twoPass) { - return [ - `-b:v ${bitrates.target}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-bufsize ${bitrates.target}${bitrates.unit}`, - '-multipass 2', - ]; - } else if (bitrates.max > 0) { - return [ - `-cq:v ${this.config.crf}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-bufsize ${bitrates.target}${bitrates.unit}`, - ]; - } else { - return [`-cq:v ${this.config.crf}`]; - } - } - - getThreadOptions() { - return []; - } - - getRefs() { - const bframes = this.getBFrames(); - if (bframes > 0 && bframes < 3 && this.config.refs < 3) { - return 0; - } - return this.config.refs; - } -} - -export class NvencHwDecodeConfig extends NvencSwDecodeConfig { - getBaseInputOptions() { - return ['-hwaccel cuda', '-hwaccel_output_format cuda', '-noautorotate', ...this.getInputThreadOptions()]; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = []; - const tonemapOptions = this.getToneMapping(videoStream); - if (this.shouldScale(videoStream) || (tonemapOptions.length === 0 && !videoStream.pixelFormat.endsWith('420p'))) { - options.push(`scale_cuda=${this.getScaling(videoStream)}`); - } - options.push(...tonemapOptions); - if (options.length > 0) { - options[options.length - 1] += ':format=nv12'; - } - return options; - } - - getToneMapping(videoStream: VideoStreamInfo) { - if (!this.shouldToneMap(videoStream)) { - return []; - } - - const { matrix, primaries, transfer } = this.getColors(); - const tonemapOptions = [ - 'desat=0', - `matrix=${matrix}`, - `primaries=${primaries}`, - 'range=pc', - `tonemap=${this.config.tonemap}`, - 'tonemap_mode=lum', - `transfer=${transfer}`, - 'peak=100', - ]; - - return [`tonemap_cuda=${tonemapOptions.join(':')}`]; - } - - getInputThreadOptions() { - return [`-threads 1`]; - } - - getOutputThreadOptions() { - return []; - } -} - -export class QsvSwDecodeConfig extends BaseHWConfig { - getBaseInputOptions() { - return [`-init_hw_device qsv=hw,child_device=${this.device}`, '-filter_hw_device hw']; - } - - getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { - const options = super.getBaseOutputOptions(target, videoStream, audioStream); - // VP9 requires enabling low power mode https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/33583803e107b6d532def0f9d949364b01b6ad5a - if (this.config.targetVideoCodec === VideoCodec.Vp9) { - options.push('-low_power 1'); - } - return options; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = this.getToneMapping(videoStream); - options.push('hwupload=extra_hw_frames=64'); - if (this.shouldScale(videoStream)) { - options.push(`scale_qsv=${this.getScaling(videoStream)}:mode=hq:format=nv12`); - } - return options; - } - - getPresetOptions() { - let presetIndex = this.getPresetIndex(); - if (presetIndex < 0) { - return []; - } - presetIndex = Math.min(6, presetIndex) + 1; // 1 to 7 - return [`-preset ${presetIndex}`]; - } - - getBitrateOptions() { - const options = [`-${this.useCQP() ? 'q:v' : 'global_quality:v'} ${this.config.crf}`]; - const bitrates = this.getBitrateDistribution(); - if (bitrates.max > 0) { - options.push(`-maxrate ${bitrates.max}${bitrates.unit}`, `-bufsize ${bitrates.max * 2}${bitrates.unit}`); - } - return options; - } - - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1]; - } - - // recommended from https://github.com/intel/media-delivery/blob/master/doc/benchmarks/intel-iris-xe-max-graphics/intel-iris-xe-max-graphics.md - getBFrames() { - if (this.config.bframes < 0) { - return 7; - } - return this.config.bframes; - } - - getRefs() { - if (this.config.refs <= 0) { - return 5; - } - return this.config.refs; - } - - useCQP() { - return this.config.cqMode === CQMode.Cqp || this.config.targetVideoCodec === VideoCodec.Vp9; - } - - getScaling(videoStream: VideoStreamInfo): string { - return super.getScaling(videoStream, 1); - } -} - -export class QsvHwDecodeConfig extends QsvSwDecodeConfig { - getBaseInputOptions() { - return [ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', - '-noautorotate', - `-qsv_device ${this.device}`, - ...this.getInputThreadOptions(), - ]; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = []; - const tonemapOptions = this.getToneMapping(videoStream); - if (tonemapOptions.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { - options.push(`scale_qsv=${this.getScaling(videoStream)}:async_depth=4:mode=hq:format=nv12`); - } else if (this.shouldScale(videoStream)) { - options.push(`scale_qsv=${this.getScaling(videoStream)}:async_depth=4:mode=hq`); - } - options.push(...tonemapOptions); - return options; - } - - getToneMapping(videoStream: VideoStreamInfo): string[] { - if (!this.shouldToneMap(videoStream)) { - return []; - } - - const { matrix, primaries, transfer } = this.getColors(); - const tonemapOptions = [ - 'desat=0', - 'format=nv12', - `matrix=${matrix}`, - `primaries=${primaries}`, - `transfer=${transfer}`, - 'range=pc', - `tonemap=${this.config.tonemap}`, - 'tonemap_mode=lum', - 'peak=100', - ]; - - return [ - 'hwmap=derive_device=opencl', - `tonemap_opencl=${tonemapOptions.join(':')}`, - 'hwmap=derive_device=qsv:reverse=1,format=qsv', - ]; - } - - getInputThreadOptions() { - return [`-threads 1`]; - } -} - -export class VaapiSwDecodeConfig extends BaseHWConfig { - getBaseInputOptions() { - return [`-init_hw_device vaapi=accel:${this.device}`, '-filter_hw_device accel']; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = this.getToneMapping(videoStream); - options.push('hwupload=extra_hw_frames=64'); - if (this.shouldScale(videoStream)) { - options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc:format=nv12`); - } - - return options; - } - - getPresetOptions() { - let presetIndex = this.getPresetIndex(); - if (presetIndex < 0) { - return []; - } - presetIndex = Math.min(6, presetIndex) + 1; // 1 to 7 - return [`-compression_level ${presetIndex}`]; - } - - getBitrateOptions() { - const bitrates = this.getBitrateDistribution(); - const options = []; - - if (this.config.targetVideoCodec === VideoCodec.Vp9) { - options.push('-bsf:v vp9_raw_reorder,vp9_superframe'); - } - - // VAAPI doesn't allow setting both quality and max bitrate - if (bitrates.max > 0) { - options.push( - `-b:v ${bitrates.target}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-minrate ${bitrates.min}${bitrates.unit}`, - '-rc_mode 3', - ); // variable bitrate - } else if (this.useCQP()) { - options.push(`-qp:v ${this.config.crf}`, `-global_quality:v ${this.config.crf}`, '-rc_mode 1'); - } else { - options.push(`-global_quality:v ${this.config.crf}`, '-rc_mode 4'); - } - - return options; - } - - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc, VideoCodec.Vp9, VideoCodec.Av1]; - } - - useCQP() { - return this.config.cqMode !== CQMode.Icq || this.config.targetVideoCodec === VideoCodec.Vp9; - } -} - -export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { - getBaseInputOptions() { - return [ - '-hwaccel vaapi', - '-hwaccel_output_format vaapi', - '-noautorotate', - `-hwaccel_device ${this.device}`, - ...this.getInputThreadOptions(), - ]; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - const options = []; - const tonemapOptions = this.getToneMapping(videoStream); - if (tonemapOptions.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { - options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc:format=nv12`); - } else if (this.shouldScale(videoStream)) { - options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc`); - } - options.push(...tonemapOptions); - return options; - } - - getToneMapping(videoStream: VideoStreamInfo): string[] { - if (!this.shouldToneMap(videoStream)) { - return []; - } - - const { matrix, primaries, transfer } = this.getColors(); - const tonemapOptions = [ - 'desat=0', - 'format=nv12', - `matrix=${matrix}`, - `primaries=${primaries}`, - `transfer=${transfer}`, - 'range=pc', - `tonemap=${this.config.tonemap}`, - 'tonemap_mode=lum', - 'peak=100', - ]; - - return [ - 'hwmap=derive_device=opencl', - `tonemap_opencl=${tonemapOptions.join(':')}`, - 'hwmap=derive_device=vaapi:reverse=1,format=vaapi', - ]; - } - - getInputThreadOptions() { - return [`-threads 1`]; - } -} - -export class RkmppSwDecodeConfig extends BaseHWConfig { - eligibleForTwoPass(): boolean { - return false; - } - - getBaseInputOptions(): string[] { - return []; - } - - getPresetOptions() { - switch (this.config.targetVideoCodec) { - case VideoCodec.H264: { - // from ffmpeg_mpp help, commonly referred to as H264 level 5.1 - return ['-level 51']; - } - case VideoCodec.Hevc: { - // from ffmpeg_mpp help, commonly referred to as HEVC level 5.1 - return ['-level 153']; - } - default: { - throw new Error(`Incompatible video codec for RKMPP: ${this.config.targetVideoCodec}`); - } - } - } - - getBitrateOptions() { - const bitrate = this.getMaxBitrateValue(); - if (bitrate > 0) { - // -b:v specifies max bitrate, average bitrate is derived automatically... - return ['-rc_mode AVBR', `-b:v ${bitrate}${this.getBitrateUnit()}`]; - } - // use CRF value as QP value - return ['-rc_mode CQP', `-qp_init ${this.config.crf}`]; - } - - getSupportedCodecs() { - return [VideoCodec.H264, VideoCodec.Hevc]; - } - - getVideoCodec(): string { - return `${this.config.targetVideoCodec}_rkmpp`; - } -} - -export class RkmppHwDecodeConfig extends RkmppSwDecodeConfig { - getBaseInputOptions() { - return ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga', '-noautorotate']; - } - - getFilterOptions(videoStream: VideoStreamInfo) { - if (this.shouldToneMap(videoStream)) { - const { primaries, transfer, matrix } = this.getColors(); - if (this.interfaces.mali) { - return [ - // use RKMPP for scaling, OpenCL for tone mapping - `scale_rkrga=${this.getScaling(videoStream)}:format=p010:afbc=1:async_depth=4`, - 'hwmap=derive_device=opencl:mode=read', - `tonemap_opencl=format=nv12:r=pc:p=${primaries}:t=${transfer}:m=${matrix}:tonemap=${this.config.tonemap}:desat=0:tonemap_mode=lum:peak=100`, - 'hwmap=derive_device=rkmpp:mode=write:reverse=1', - 'format=drm_prime', - ]; - } - return [ - // use RKMPP for scaling, CPU for tone mapping (only works on RK3588, which supports 10-bit output) - `scale_rkrga=${this.getScaling(videoStream)}:format=p010:afbc=1:async_depth=4`, - 'hwdownload', - 'format=p010', - `tonemapx=tonemap=${this.config.tonemap}:desat=0:p=${primaries}:t=${transfer}:m=${matrix}:r=pc:peak=100:format=yuv420p`, - 'hwupload', - ]; - } else if (this.shouldScale(videoStream)) { - return [`scale_rkrga=${this.getScaling(videoStream)}:format=nv12:afbc=1:async_depth=4`]; - } - return []; - } -} diff --git a/server/src/utils/mime-types.spec.ts b/server/src/utils/mime-types.spec.ts deleted file mode 100644 index c09f3a381b..0000000000 --- a/server/src/utils/mime-types.spec.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { mimeTypes } from 'src/utils/mime-types'; - -describe('mimeTypes', () => { - for (const { mimetype, extension } of [ - // Please ensure this list is sorted. - { mimetype: 'image/3fr', extension: '.3fr' }, - { mimetype: 'image/ari', extension: '.ari' }, - { mimetype: 'image/arw', extension: '.arw' }, - { mimetype: 'image/avif', extension: '.avif' }, - { mimetype: 'image/bmp', extension: '.bmp' }, - { mimetype: 'image/cap', extension: '.cap' }, - { mimetype: 'image/cin', extension: '.cin' }, - { mimetype: 'image/cr2', extension: '.cr2' }, - { mimetype: 'image/cr3', extension: '.cr3' }, - { mimetype: 'image/crw', extension: '.crw' }, - { mimetype: 'image/dcr', extension: '.dcr' }, - { mimetype: 'image/dng', extension: '.dng' }, - { mimetype: 'image/erf', extension: '.erf' }, - { mimetype: 'image/fff', extension: '.fff' }, - { mimetype: 'image/gif', extension: '.gif' }, - { mimetype: 'image/heic', extension: '.heic' }, - { mimetype: 'image/heif', extension: '.heif' }, - { mimetype: 'image/hif', extension: '.hif' }, - { mimetype: 'image/iiq', extension: '.iiq' }, - { mimetype: 'image/jp2', extension: '.jp2' }, - { mimetype: 'image/jpeg', extension: '.jpe' }, - { mimetype: 'image/jpeg', extension: '.jpeg' }, - { mimetype: 'image/jpeg', extension: '.jpg' }, - { mimetype: 'image/jxl', extension: '.jxl' }, - { mimetype: 'image/k25', extension: '.k25' }, - { mimetype: 'image/kdc', extension: '.kdc' }, - { mimetype: 'image/mrw', extension: '.mrw' }, - { mimetype: 'image/nef', extension: '.nef' }, - { mimetype: 'image/nrw', extension: '.nrw' }, - { mimetype: 'image/orf', extension: '.orf' }, - { mimetype: 'image/ori', extension: '.ori' }, - { mimetype: 'image/pef', extension: '.pef' }, - { mimetype: 'image/png', extension: '.png' }, - { mimetype: 'image/psd', extension: '.psd' }, - { mimetype: 'image/raf', extension: '.raf' }, - { mimetype: 'image/raw', extension: '.raw' }, - { mimetype: 'image/rwl', extension: '.rwl' }, - { mimetype: 'image/sr2', extension: '.sr2' }, - { mimetype: 'image/srf', extension: '.srf' }, - { mimetype: 'image/srw', extension: '.srw' }, - { mimetype: 'image/svg', extension: '.svg' }, - { mimetype: 'image/tiff', extension: '.tif' }, - { mimetype: 'image/tiff', extension: '.tiff' }, - { mimetype: 'image/webp', extension: '.webp' }, - { mimetype: 'image/vnd.adobe.photoshop', extension: '.psd' }, - { mimetype: 'image/x-adobe-dng', extension: '.dng' }, - { mimetype: 'image/x-arriflex-ari', extension: '.ari' }, - { mimetype: 'image/x-canon-cr2', extension: '.cr2' }, - { mimetype: 'image/x-canon-cr3', extension: '.cr3' }, - { mimetype: 'image/x-canon-crw', extension: '.crw' }, - { mimetype: 'image/x-epson-erf', extension: '.erf' }, - { mimetype: 'image/x-fuji-raf', extension: '.raf' }, - { mimetype: 'image/x-hasselblad-3fr', extension: '.3fr' }, - { mimetype: 'image/x-hasselblad-fff', extension: '.fff' }, - { mimetype: 'image/x-kodak-dcr', extension: '.dcr' }, - { mimetype: 'image/x-kodak-k25', extension: '.k25' }, - { mimetype: 'image/x-kodak-kdc', extension: '.kdc' }, - { mimetype: 'image/x-leica-rwl', extension: '.rwl' }, - { mimetype: 'image/x-minolta-mrw', extension: '.mrw' }, - { mimetype: 'image/x-nikon-nef', extension: '.nef' }, - { mimetype: 'image/x-olympus-orf', extension: '.orf' }, - { mimetype: 'image/x-olympus-ori', extension: '.ori' }, - { mimetype: 'image/x-panasonic-raw', extension: '.raw' }, - { mimetype: 'image/x-pentax-pef', extension: '.pef' }, - { mimetype: 'image/x-phantom-cin', extension: '.cin' }, - { mimetype: 'image/x-phaseone-cap', extension: '.cap' }, - { mimetype: 'image/x-phaseone-iiq', extension: '.iiq' }, - { mimetype: 'image/x-samsung-srw', extension: '.srw' }, - { mimetype: 'image/x-sigma-x3f', extension: '.x3f' }, - { mimetype: 'image/x-sony-arw', extension: '.arw' }, - { mimetype: 'image/x-sony-sr2', extension: '.sr2' }, - { mimetype: 'image/x-sony-srf', extension: '.srf' }, - { mimetype: 'image/x3f', extension: '.x3f' }, - { mimetype: 'video/3gpp', extension: '.3gp' }, - { mimetype: 'video/3gpp', extension: '.3gpp' }, - { mimetype: 'video/avi', extension: '.avi' }, - { mimetype: 'video/mp2t', extension: '.m2t' }, - { mimetype: 'video/mp2t', extension: '.m2ts' }, - { mimetype: 'video/mp2t', extension: '.mts' }, - { mimetype: 'video/mp4', extension: '.mp4' }, - { mimetype: 'video/mpeg', extension: '.mpe' }, - { mimetype: 'video/mpeg', extension: '.mpeg' }, - { mimetype: 'video/mpeg', extension: '.mpg' }, - { mimetype: 'video/msvideo', extension: '.avi' }, - { mimetype: 'video/quicktime', extension: '.mov' }, - { mimetype: 'video/vnd.avi', extension: '.avi' }, - { mimetype: 'video/webm', extension: '.webm' }, - { mimetype: 'video/x-flv', extension: '.flv' }, - { mimetype: 'video/x-matroska', extension: '.mkv' }, - { mimetype: 'video/x-ms-wmv', extension: '.wmv' }, - { mimetype: 'video/x-msvideo', extension: '.avi' }, - { mimetype: 'video/mpeg', extension: '.vob' }, - ]) { - it(`should map ${extension} to ${mimetype}`, () => { - expect({ ...mimeTypes.image, ...mimeTypes.video }[extension]).toContain(mimetype); - }); - } - - describe('toExtension', () => { - it('should get an extension for a png file', () => { - expect(mimeTypes.toExtension('image/png')).toEqual('.png'); - }); - - it('should get an extension for a jpeg file', () => { - expect(mimeTypes.toExtension('image/jpeg')).toEqual('.jpg'); - }); - - it('should get an extension from a webp file', () => { - expect(mimeTypes.toExtension('image/webp')).toEqual('.webp'); - }); - }); - - describe('profile', () => { - it('should contain only lowercase mime types', () => { - const keys = Object.keys(mimeTypes.profile); - expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase())); - - const values = Object.values(mimeTypes.profile).flat(); - expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase())); - }); - - for (const [extension, v] of Object.entries(mimeTypes.profile)) { - it(`should lookup ${extension}`, () => { - expect(mimeTypes.lookup(`test.${extension}`)).toEqual(v[0]); - }); - } - }); - - describe('image', () => { - it('should contain only lowercase mime types', () => { - const keys = Object.keys(mimeTypes.image); - expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase())); - - const values = Object.values(mimeTypes.image).flat(); - expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase())); - }); - - it('should contain only image mime types', () => { - const values = Object.values(mimeTypes.image).flat(); - expect(values).toEqual(values.filter((mimeType) => mimeType.startsWith('image/'))); - }); - - for (const [extension, v] of Object.entries(mimeTypes.image)) { - it(`should lookup ${extension}`, () => { - expect(mimeTypes.lookup(`test.${extension}`)).toEqual(v[0]); - }); - } - }); - - describe('animated image', () => { - for (const img of ['a.avif', 'a.gif', 'a.webp']) { - it('should identify animated image mime types as such', () => { - expect(mimeTypes.isPossiblyAnimatedImage(img)).toBeTruthy(); - }); - } - - for (const img of ['a.cr3', 'a.jpg', 'a.tiff']) { - it('should identify static image mime types as such', () => { - expect(mimeTypes.isPossiblyAnimatedImage(img)).toBeFalsy(); - }); - } - - for (const extension of Object.keys(mimeTypes.video)) { - it('should not identify video mime types as animated', () => { - expect(mimeTypes.isPossiblyAnimatedImage(extension)).toBeFalsy(); - }); - } - }); - - describe('video', () => { - it('should contain only lowercase mime types', () => { - const keys = Object.keys(mimeTypes.video); - expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase())); - - const values = Object.values(mimeTypes.video).flat(); - expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase())); - }); - - it('should be a sorted list', () => { - const keys = Object.keys(mimeTypes.video); - expect(keys).toEqual(keys.toSorted()); - }); - - it('should contain only video mime types', () => { - const values = Object.values(mimeTypes.video).flat(); - expect(values).toEqual(values.filter((mimeType) => mimeType.startsWith('video/'))); - }); - - for (const [extension, v] of Object.entries(mimeTypes.video)) { - it(`should lookup ${extension}`, () => { - expect(mimeTypes.lookup(`test.${extension}`)).toEqual(v[0]); - }); - } - }); - - describe('sidecar', () => { - it('should contain only lowercase mime types', () => { - const keys = Object.keys(mimeTypes.sidecar); - expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase())); - - const values = Object.values(mimeTypes.sidecar).flat(); - expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase())); - }); - - it('should be a sorted list', () => { - const keys = Object.keys(mimeTypes.sidecar); - expect(keys).toEqual(keys.toSorted()); - }); - - it('should contain only xml mime types', () => { - expect(Object.values(mimeTypes.sidecar).flat()).toEqual(['application/xml', 'text/xml']); - }); - - for (const [extension, v] of Object.entries(mimeTypes.sidecar)) { - it(`should lookup ${extension}`, () => { - expect(mimeTypes.lookup(`it.${extension}`)).toEqual(v[0]); - }); - } - }); - - describe('raw', () => { - it('should contain only lowercase mime types', () => { - const keys = Object.keys(mimeTypes.raw); - expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase())); - - const values = Object.values(mimeTypes.raw).flat(); - expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase())); - }); - - for (const [extension, v] of Object.entries(mimeTypes.video)) { - it(`should lookup ${extension}`, () => { - expect(mimeTypes.lookup(`test.${extension}`)).toEqual(v[0]); - }); - } - }); -}); diff --git a/server/src/utils/mime-types.ts b/server/src/utils/mime-types.ts deleted file mode 100644 index d15c1f078c..0000000000 --- a/server/src/utils/mime-types.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { extname } from 'node:path'; -import { AssetType } from 'src/enum'; - -const raw: Record = { - '.3fr': ['image/3fr', 'image/x-hasselblad-3fr'], - '.ari': ['image/ari', 'image/x-arriflex-ari'], - '.arw': ['image/arw', 'image/x-sony-arw'], - '.cap': ['image/cap', 'image/x-phaseone-cap'], - '.cin': ['image/cin', 'image/x-phantom-cin'], - '.cr2': ['image/cr2', 'image/x-canon-cr2'], - '.cr3': ['image/cr3', 'image/x-canon-cr3'], - '.crw': ['image/crw', 'image/x-canon-crw'], - '.dcr': ['image/dcr', 'image/x-kodak-dcr'], - '.dng': ['image/dng', 'image/x-adobe-dng'], - '.erf': ['image/erf', 'image/x-epson-erf'], - '.fff': ['image/fff', 'image/x-hasselblad-fff'], - '.iiq': ['image/iiq', 'image/x-phaseone-iiq'], - '.k25': ['image/k25', 'image/x-kodak-k25'], - '.kdc': ['image/kdc', 'image/x-kodak-kdc'], - '.mrw': ['image/mrw', 'image/x-minolta-mrw'], - '.nef': ['image/nef', 'image/x-nikon-nef'], - '.nrw': ['image/nrw', 'image/x-nikon-nrw'], - '.orf': ['image/orf', 'image/x-olympus-orf'], - '.ori': ['image/ori', 'image/x-olympus-ori'], - '.pef': ['image/pef', 'image/x-pentax-pef'], - '.psd': ['image/psd', 'image/vnd.adobe.photoshop'], - '.raf': ['image/raf', 'image/x-fuji-raf'], - '.raw': ['image/raw', 'image/x-panasonic-raw'], - '.rw2': ['image/rw2', 'image/x-panasonic-rw2'], - '.rwl': ['image/rwl', 'image/x-leica-rwl'], - '.sr2': ['image/sr2', 'image/x-sony-sr2'], - '.srf': ['image/srf', 'image/x-sony-srf'], - '.srw': ['image/srw', 'image/x-samsung-srw'], - '.x3f': ['image/x3f', 'image/x-sigma-x3f'], -}; - -/** - * list of supported image extensions from https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types excluding svg - * @TODO share with the client - * @see {@link web/src/lib/utils/asset-utils.ts#L329} - **/ -const webSupportedImage = { - '.avif': ['image/avif'], - '.gif': ['image/gif'], - '.jpeg': ['image/jpeg'], - '.jpg': ['image/jpeg'], - '.png': ['image/png', 'image/apng'], - '.webp': ['image/webp'], -}; - -const image: Record = { - ...raw, - ...webSupportedImage, - '.bmp': ['image/bmp'], - '.heic': ['image/heic'], - '.heif': ['image/heif'], - '.hif': ['image/hif'], - '.insp': ['image/jpeg'], - '.jp2': ['image/jp2'], - '.jpe': ['image/jpeg'], - '.jxl': ['image/jxl'], - '.svg': ['image/svg'], - '.tif': ['image/tiff'], - '.tiff': ['image/tiff'], -}; - -const possiblyAnimatedImageExtensions = new Set(['.avif', '.gif', '.heic', '.heif', '.jxl', '.png', '.webp']); -const possiblyAnimatedImage: Record = Object.fromEntries( - Object.entries(image).filter(([key]) => possiblyAnimatedImageExtensions.has(key)), -); - -const extensionOverrides: Record = { - 'image/jpeg': '.jpg', -}; - -const profileExtensions = new Set(['.avif', '.dng', '.heic', '.heif', '.jpeg', '.jpg', '.png', '.webp', '.svg']); -const profile: Record = Object.fromEntries( - Object.entries(image).filter(([key]) => profileExtensions.has(key)), -); - -const video: Record = { - '.3gp': ['video/3gpp'], - '.3gpp': ['video/3gpp'], - '.avi': ['video/avi', 'video/msvideo', 'video/vnd.avi', 'video/x-msvideo'], - '.flv': ['video/x-flv'], - '.insv': ['video/mp4'], - '.m2t': ['video/mp2t'], - '.m2ts': ['video/mp2t'], - '.m4v': ['video/x-m4v'], - '.mkv': ['video/x-matroska'], - '.mov': ['video/quicktime'], - '.mp4': ['video/mp4'], - '.mpe': ['video/mpeg'], - '.mpeg': ['video/mpeg'], - '.mpg': ['video/mpeg'], - '.mts': ['video/mp2t'], - '.vob': ['video/mpeg'], - '.webm': ['video/webm'], - '.wmv': ['video/x-ms-wmv'], -}; - -const sidecar: Record = { - '.xmp': ['application/xml', 'text/xml'], -}; - -const types = { ...image, ...video, ...sidecar }; - -const isType = (filename: string, r: Record) => extname(filename).toLowerCase() in r; - -const lookup = (filename: string) => types[extname(filename).toLowerCase()]?.[0] ?? 'application/octet-stream'; -const toExtension = (mimeType: string) => { - return ( - extensionOverrides[mimeType] || Object.entries(types).find(([, mimeTypes]) => mimeTypes.includes(mimeType))?.[0] - ); -}; - -export const mimeTypes = { - image, - profile, - sidecar, - video, - raw, - - isAsset: (filename: string) => isType(filename, image) || isType(filename, video), - isImage: (filename: string) => isType(filename, image), - isWebSupportedImage: (filename: string) => isType(filename, webSupportedImage), - isPossiblyAnimatedImage: (filename: string) => isType(filename, possiblyAnimatedImage), - isProfile: (filename: string) => isType(filename, profile), - isSidecar: (filename: string) => isType(filename, sidecar), - isVideo: (filename: string) => isType(filename, video), - isRaw: (filename: string) => isType(filename, raw), - lookup, - /** return an extension (including a leading `.`) for a mime-type */ - toExtension, - assetType: (filename: string) => { - const contentType = lookup(filename); - if (contentType.startsWith('image/')) { - return AssetType.Image; - } else if (contentType.startsWith('video/')) { - return AssetType.Video; - } - return AssetType.Other; - }, - getSupportedFileExtensions: () => [...Object.keys(image), ...Object.keys(video)], -}; diff --git a/server/src/utils/misc.spec.ts b/server/src/utils/misc.spec.ts deleted file mode 100644 index 3c45482938..0000000000 --- a/server/src/utils/misc.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { getKeysDeep, globToSqlPattern, unsetDeep } from 'src/utils/misc'; -import { describe, expect, it } from 'vitest'; - -describe('getKeysDeep', () => { - it('should handle an empty object', () => { - expect(getKeysDeep({})).toEqual([]); - }); - - it('should list properties', () => { - expect( - getKeysDeep({ - foo: 'bar', - flag: true, - count: 42, - date: new Date(), - }), - ).toEqual(['foo', 'flag', 'count', 'date']); - }); - - it('should skip undefined properties', () => { - expect(getKeysDeep({ foo: 'bar', hello: undefined })).toEqual(['foo']); - }); - - it('should skip array indices', () => { - expect(getKeysDeep({ foo: 'bar', hello: ['foo', 'bar'] })).toEqual(['foo', 'hello']); - expect(getKeysDeep({ foo: 'bar', nested: { hello: ['foo', 'bar'] } })).toEqual(['foo', 'nested.hello']); - }); - - it('should list nested properties', () => { - expect(getKeysDeep({ foo: 'bar', hello: { world: true } })).toEqual(['foo', 'hello.world']); - }); -}); - -describe('unsetDeep', () => { - it('should remove a property', () => { - expect(unsetDeep({ hello: 'world', foo: 'bar' }, 'foo')).toEqual({ hello: 'world' }); - }); - - it('should remove the last property', () => { - expect(unsetDeep({ foo: 'bar' }, 'foo')).toBeUndefined(); - }); - - it('should remove a nested property', () => { - expect(unsetDeep({ foo: 'bar', nested: { enabled: true, count: 42 } }, 'nested.enabled')).toEqual({ - foo: 'bar', - nested: { count: 42 }, - }); - }); - - it('should clean up an empty property', () => { - expect(unsetDeep({ foo: 'bar', nested: { enabled: true } }, 'nested.enabled')).toEqual({ foo: 'bar' }); - }); -}); - -describe('globToSqlPattern', () => { - const testCases = [ - ['**/Raw/**', '%/Raw/%'], - ['**/abc/*.tif', '%/abc/%.tif'], - ['**/*.tif', '%/%.tif'], - ['**/*.jp?', '%/%.jp_'], - ['**/@eaDir/**', '%/@eaDir/%'], - ['**/._*', `%/._%`], - ['/absolute/path/**', `/absolute/path/%`], - ]; - - it.each(testCases)('should convert %s to %s', (input, expected) => { - expect(globToSqlPattern(input)).toEqual(expected); - }); -}); diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 7d2e99a215..2a87df352b 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -14,11 +14,7 @@ import { import _ from 'lodash'; import { writeFileSync } from 'node:fs'; import path from 'node:path'; -import picomatch from 'picomatch'; -import parse from 'picomatch/lib/parse'; -import { SystemConfig } from 'src/config'; -import { CLIP_MODEL_INFO, endpointTags, serverVersion } from 'src/constants'; -import { extraSyncModels } from 'src/dtos/sync.dto'; +import { endpointTags, serverVersion } from 'src/constants'; import { ApiCustomExtension, ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; import { LoggingRepository } from 'src/repositories/logging.repository'; @@ -29,217 +25,29 @@ export const getKeyByValue = (object: Record, value: unknown) = Object.keys(object).find((key) => object[key] === value); export const getMethodNames = (instance: any) => { - const ctx = Object.getPrototypeOf(instance); - const methods: string[] = []; - for (const property of Object.getOwnPropertyNames(ctx)) { - const descriptor = Object.getOwnPropertyDescriptor(ctx, property); - if (!descriptor || descriptor.get || descriptor.set) { - continue; - } - - const handler = instance[property]; - if (typeof handler !== 'function') { - continue; - } - - methods.push(property); - } - - return methods; + const prototype = Object.getPrototypeOf(instance); + const propertyNames = Object.getOwnPropertyNames(prototype); + return propertyNames.filter((name) => name !== 'constructor' && typeof instance[name] === 'function'); }; -export const getExternalDomain = (server: SystemConfig['server'], defaultDomain = 'https://my.immich.app') => - server.externalDomain || defaultDomain; - -/** - * @returns a list of strings representing the keys of the object in dot notation - */ -export const getKeysDeep = (target: unknown, path: string[] = []) => { - if (!target || typeof target !== 'object') { - return []; - } - - const obj = target as object; - - const properties: string[] = []; - for (const key of Object.keys(obj as object)) { - const value = obj[key as keyof object]; - if (value === undefined) { - continue; - } - - if (_.isObject(value) && !_.isArray(value) && !_.isDate(value)) { - properties.push(...getKeysDeep(value, [...path, key])); - continue; - } - - properties.push([...path, key].join('.')); - } - - return properties; -}; - -export const unsetDeep = (object: unknown, key: string) => { - const parts = key.split('.'); - while (parts.length > 0) { - _.unset(object, parts); - parts.pop(); - if (!_.isEmpty(_.get(object, parts))) { - break; - } - } - - return _.isEmpty(object) ? undefined : object; -}; - -const isMachineLearningEnabled = (machineLearning: SystemConfig['machineLearning']) => machineLearning.enabled; -export const isSmartSearchEnabled = (machineLearning: SystemConfig['machineLearning']) => - isMachineLearningEnabled(machineLearning) && machineLearning.clip.enabled; -export const isOcrEnabled = (machineLearning: SystemConfig['machineLearning']) => - isMachineLearningEnabled(machineLearning) && machineLearning.ocr.enabled; -export const isFacialRecognitionEnabled = (machineLearning: SystemConfig['machineLearning']) => - isMachineLearningEnabled(machineLearning) && machineLearning.facialRecognition.enabled; -export const isDuplicateDetectionEnabled = (machineLearning: SystemConfig['machineLearning']) => - isSmartSearchEnabled(machineLearning) && machineLearning.duplicateDetection.enabled; -export const isFaceImportEnabled = (metadata: SystemConfig['metadata']) => metadata.faces.import; - export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; export const handlePromiseError = (promise: Promise, logger: LoggingRepository): void => { promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); }; -export interface OpenGraphTags { - title: string; - description: string; - imageUrl?: string; -} - -function cleanModelName(modelName: string): string { - const token = modelName.split('/').at(-1); - if (!token) { - throw new Error(`Invalid model name: ${modelName}`); - } - - return token.replaceAll(':', '_'); -} - -export function getCLIPModelInfo(modelName: string) { - const modelInfo = CLIP_MODEL_INFO[cleanModelName(modelName)]; - if (!modelInfo) { - throw new Error(`Unknown CLIP model: ${modelName}`); - } - - return modelInfo; -} - -function sortKeys(target: T): T { - if (!target || typeof target !== 'object' || Array.isArray(target)) { - return target; - } - - const result: Partial = {}; - const keys = Object.keys(target).toSorted() as Array; - for (const key of keys) { - result[key] = sortKeys(target[key]); - } - return result as T; -} - export const routeToErrorMessage = (methodName: string) => - 'Failed to ' + methodName.replaceAll(/[A-Z]+/g, (letter) => ` ${letter.toLowerCase()}`); - -const isSchema = (schema: string | ReferenceObject | SchemaObject): schema is SchemaObject => { - if (typeof schema === 'string' || '$ref' in schema) { - return false; - } - - return true; -}; - -const patchOpenAPI = (document: OpenAPIObject) => { - document.paths = sortKeys(document.paths); - - if (document.components?.schemas) { - const schemas = document.components.schemas as Record; - - document.components.schemas = sortKeys(schemas); - - for (const [schemaName, schema] of Object.entries(schemas)) { - if (schema.properties) { - schema.properties = sortKeys(schema.properties); - - for (const [key, value] of Object.entries(schema.properties)) { - if (typeof value === 'string') { - continue; - } - - if (isSchema(value) && value.type === 'number' && value.format === 'float') { - throw new Error(`Invalid number format: ${schemaName}.${key}=float (use double instead). `); - } - } - schema.required?.sort(); - } - } - } - - for (const [key, value] of Object.entries(document.paths)) { - const newKey = key.replace('/api/', '/'); - delete document.paths[key]; - document.paths[newKey] = value; - } - - for (const path of Object.values(document.paths)) { - const operations = { - get: path.get, - put: path.put, - post: path.post, - delete: path.delete, - options: path.options, - head: path.head, - patch: path.patch, - trace: path.trace, - }; - - for (const operation of Object.values(operations) as Array< - OperationObject & { - [ApiCustomExtension.AdminOnly]?: boolean; - [ApiCustomExtension.Permission]?: string; - } - >) { - if (!operation) { - continue; - } - - if (operation.summary === '') { - delete operation.summary; - } - - if (operation.description === '') { - delete operation.description; - } - - if (operation.operationId) { - // console.log(`${routeToErrorMessage(operation.operationId).padEnd(40)} (${operation.operationId})`); - } - - if (operation.parameters) { - operation.parameters = _.orderBy(operation.parameters, 'name'); - } - } - } - - return document; -}; + 'Failed to ' + methodName.replaceAll(/([a-z])([A-Z])/g, '$1 $2').toLowerCase(); export const useSwagger = (app: INestApplication, { write }: { write: boolean }) => { - const builder = new DocumentBuilder() - .setTitle('Immich') - .setDescription('Immich API') + const config = new DocumentBuilder() + .setTitle('App API') + .setDescription('App API') .setVersion(serverVersion.toString()) .addBearerAuth({ type: 'http', scheme: 'Bearer', + bearerFormat: 'JWT', in: 'header', }) .addCookieAuth(ImmichCookie.AccessToken) @@ -251,71 +59,46 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) }, MetadataKey.ApiKeySecurity, ) - .addServer('/api'); - - for (const [tag, description] of Object.entries(endpointTags)) { - builder.addTag(tag, description); - } - const config = builder.build(); + .addServer('/api') + .build(); const options: SwaggerDocumentOptions = { operationIdFactory: (controllerKey: string, methodKey: string) => methodKey, - extraModels: extraSyncModels, - ignoreGlobalPrefix: true, + extraModels: [], }; const specification = SwaggerModule.createDocument(app, config, options); + // Add endpoint tags + specification.tags = (specification.tags || []).map((tag) => ({ + ...tag, + description: endpointTags[tag.name as keyof typeof endpointTags] || tag.description, + })); + + // Add custom extensions from metadata + for (const path of Object.values(specification.paths)) { + for (const key of ['get', 'post', 'put', 'delete', 'patch'] as const) { + const operation = path?.[key] as OperationObject; + if (!operation) continue; + + // Set operationId to method name + if (operation.operationId) { + const methodName = operation.operationId; + operation.description = operation.description || routeToErrorMessage(methodName); + } + } + } + const customOptions: SwaggerCustomOptions = { swaggerOptions: { persistAuthorization: true, }, - jsonDocumentUrl: '/api/spec.json', - yamlDocumentUrl: '/api/spec.yaml', - customSiteTitle: 'Immich API Documentation', }; SwaggerModule.setup('doc', app, specification, customOptions); if (write) { - // Generate API Documentation only in development mode - const outputPath = path.resolve(process.cwd(), '../open-api/immich-openapi-specs.json'); - writeFileSync(outputPath, JSON.stringify(patchOpenAPI(specification), null, 2), { encoding: 'utf8' }); + const outputPath = path.resolve(process.cwd(), 'immich-openapi-specs.json'); + writeFileSync(outputPath, JSON.stringify(specification, null, 2), { encoding: 'utf8' }); } }; - -const convertTokenToSqlPattern = (token: parse.Token): string => { - switch (token.type) { - case 'slash': { - return '/'; - } - case 'text': { - return token.value; - } - case 'globstar': - case 'star': { - return '%'; - } - case 'underscore': { - return String.raw`\_`; - } - case 'qmark': { - return '_'; - } - case 'dot': { - return '.'; - } - default: { - return ''; - } - } -}; - -export const globToSqlPattern = (glob: string) => { - const tokens = picomatch.parse(glob).tokens; - return tokens.map((token) => convertTokenToSqlPattern(token)).join(''); -}; - -export function clamp(value: number, min: number, max: number) { - return Math.max(min, Math.min(max, value)); -} diff --git a/server/src/utils/object.ts b/server/src/utils/object.ts deleted file mode 100644 index 25ae42cba8..0000000000 --- a/server/src/utils/object.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { isEqual, isPlainObject } from 'lodash'; - -/** - * Deeply clones and converts a class instance to a plain object. - */ -export function toPlainObject(obj: T): T { - return isPlainObject(obj) ? obj : structuredClone(obj); -} - -/** - * Performs a deep comparison between objects, converting them to plain objects first if needed. - */ -export function isEqualObject(value: object, other: object): boolean { - return isEqual(toPlainObject(value), toPlainObject(other)); -} diff --git a/server/src/utils/pagination.ts b/server/src/utils/pagination.ts deleted file mode 100644 index e440638a72..0000000000 --- a/server/src/utils/pagination.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface PaginationOptions { - take: number; - skip?: number; -} - -export interface PaginationResult { - items: T[]; - hasNextPage: boolean; -} - -export function paginationHelper(items: Entity[], take: number): PaginationResult { - const hasNextPage = items.length > take; - items.splice(take); - - return { items, hasNextPage }; -} diff --git a/server/src/utils/preferences.ts b/server/src/utils/preferences.ts deleted file mode 100644 index b25369670a..0000000000 --- a/server/src/utils/preferences.ts +++ /dev/null @@ -1,91 +0,0 @@ -import _ from 'lodash'; -import { UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto'; -import { AssetOrder, UserMetadataKey } from 'src/enum'; -import { DeepPartial, UserMetadataItem, UserPreferences } from 'src/types'; -import { HumanReadableSize } from 'src/utils/bytes'; -import { getKeysDeep } from 'src/utils/misc'; - -const getDefaultPreferences = (): UserPreferences => { - return { - albums: { - defaultAssetOrder: AssetOrder.Desc, - }, - folders: { - enabled: false, - sidebarWeb: false, - }, - memories: { - enabled: true, - duration: 5, - }, - people: { - enabled: true, - sidebarWeb: false, - }, - sharedLinks: { - enabled: true, - sidebarWeb: false, - }, - ratings: { - enabled: false, - }, - tags: { - enabled: false, - sidebarWeb: false, - }, - emailNotifications: { - enabled: true, - albumInvite: true, - albumUpdate: true, - }, - download: { - archiveSize: HumanReadableSize.GiB * 4, - includeEmbeddedVideos: false, - }, - purchase: { - showSupportBadge: true, - hideBuyButtonUntil: new Date(2022, 1, 12).toISOString(), - }, - cast: { - gCastEnabled: false, - }, - }; -}; - -export const getPreferences = (metadata: UserMetadataItem[]): UserPreferences => { - const preferences = getDefaultPreferences(); - const item = metadata.find(({ key }) => key === UserMetadataKey.Preferences); - const partial = item?.value || {}; - for (const property of getKeysDeep(partial)) { - _.set(preferences, property, _.get(partial, property)); - } - - return preferences; -}; - -export const getPreferencesPartial = (newPreferences: UserPreferences) => { - const defaultPreferences = getDefaultPreferences(); - const partial: DeepPartial = {}; - for (const property of getKeysDeep(defaultPreferences)) { - const newValue = _.get(newPreferences, property); - const isEmpty = newValue === undefined || newValue === null || newValue === ''; - const defaultValue = _.get(defaultPreferences, property); - const isEqual = newValue === defaultValue || _.isEqual(newValue, defaultValue); - - if (isEmpty || isEqual) { - continue; - } - - _.set(partial, property, newValue); - } - - return partial; -}; - -export const mergePreferences = (preferences: UserPreferences, dto: UserPreferencesUpdateDto) => { - for (const key of getKeysDeep(dto)) { - _.set(preferences, key, _.get(dto, key)); - } - - return preferences; -}; diff --git a/server/src/utils/replace-template-tags.ts b/server/src/utils/replace-template-tags.ts deleted file mode 100644 index 70333d7dff..0000000000 --- a/server/src/utils/replace-template-tags.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const replaceTemplateTags = (template: string, variables: Record) => { - return template.replaceAll(/{(.*?)}/g, (_, key) => { - return variables[key] || `{${key}}`; - }); -}; diff --git a/server/src/utils/response.ts b/server/src/utils/response.ts index d5356285f0..883a4eb603 100644 --- a/server/src/utils/response.ts +++ b/server/src/utils/response.ts @@ -15,12 +15,8 @@ export const respondWithCookie = (res: Response, body: T, { isSecure, values const cookieOptions: Record = { [ImmichCookie.AuthType]: defaults, [ImmichCookie.AccessToken]: defaults, - [ImmichCookie.MaintenanceToken]: { ...defaults, maxAge: Duration.fromObject({ days: 1 }).toMillis() }, - [ImmichCookie.OAuthState]: defaults, - [ImmichCookie.OAuthCodeVerifier]: defaults, // no httpOnly so that the client can know the auth state [ImmichCookie.IsAuthenticated]: { ...defaults, httpOnly: false }, - [ImmichCookie.SharedLinkToken]: { ...defaults, maxAge: Duration.fromObject({ days: 1 }).toMillis() }, }; for (const { key, value } of values) { diff --git a/server/src/utils/sync.ts b/server/src/utils/sync.ts deleted file mode 100644 index 82222708af..0000000000 --- a/server/src/utils/sync.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SyncItem } from 'src/dtos/sync.dto'; -import { SyncEntityType } from 'src/enum'; -import { SyncAck } from 'src/types'; - -type Impossible = { - [P in K]: never; -}; - -type Exact = U & Impossible>; - -export const fromAck = (ack: string): SyncAck => { - const [type, updateId, extraId] = ack.split('|'); - return { type: type as SyncEntityType, updateId, extraId }; -}; - -export const toAck = ({ type, updateId, extraId }: SyncAck) => - [type, updateId, extraId].filter((v) => v !== undefined).join('|'); - -export const mapJsonLine = (object: unknown) => JSON.stringify(object) + '\n'; - -export type SerializeOptions = { - type: T; - data: Exact; - ids: [string] | [string, string]; - ackType?: SyncEntityType; -}; - -export const serialize = ({ - type, - data, - ids, - ackType, -}: SerializeOptions) => - mapJsonLine({ type, data, ack: toAck({ type: ackType ?? type, updateId: ids[0], extraId: ids[1] }) }); diff --git a/server/src/utils/tag.ts b/server/src/utils/tag.ts deleted file mode 100644 index 4e8a86a7f6..0000000000 --- a/server/src/utils/tag.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Tag } from 'src/database'; -import { TagRepository } from 'src/repositories/tag.repository'; - -type UpsertRequest = { userId: string; tags: string[] }; -export const upsertTags = async (repository: TagRepository, { userId, tags }: UpsertRequest) => { - tags = [...new Set(tags)]; - - const results: Tag[] = []; - - for (const tag of tags) { - const parts = tag.split('/').filter(Boolean); - let parent: Tag | undefined; - - for (const part of parts) { - const value = parent ? `${parent.value}/${part}` : part; - parent = await repository.upsertValue({ userId, value, parentId: parent?.id }); - } - - if (parent) { - results.push(parent); - } - } - - return results; -}; diff --git a/server/src/utils/transform.spec.ts b/server/src/utils/transform.spec.ts deleted file mode 100644 index 5efeac02a6..0000000000 --- a/server/src/utils/transform.spec.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { AssetEditAction, AssetEditActionItem, MirrorAxis } from 'src/dtos/editing.dto'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { transformFaceBoundingBox, transformOcrBoundingBox } from 'src/utils/transform'; -import { describe, expect, it } from 'vitest'; - -describe('transformFaceBoundingBox', () => { - const baseFace = { - boundingBoxX1: 100, - boundingBoxY1: 100, - boundingBoxX2: 200, - boundingBoxY2: 200, - imageWidth: 1000, - imageHeight: 800, - }; - - const baseDimensions = { width: 1000, height: 800 }; - - describe('with no edits', () => { - it('should return unchanged bounding box', () => { - const result = transformFaceBoundingBox(baseFace, [], baseDimensions); - expect(result).toEqual(baseFace); - }); - }); - - describe('with crop edit', () => { - it('should adjust bounding box for crop offset', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 50, y: 50, width: 400, height: 300 } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.boundingBoxX1).toBe(50); - expect(result.boundingBoxY1).toBe(50); - expect(result.boundingBoxX2).toBe(150); - expect(result.boundingBoxY2).toBe(150); - expect(result.imageWidth).toBe(400); - expect(result.imageHeight).toBe(300); - }); - - it('should handle face partially outside crop area', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 150, y: 150, width: 400, height: 300 } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.boundingBoxX1).toBe(-50); - expect(result.boundingBoxY1).toBe(-50); - expect(result.boundingBoxX2).toBe(50); - expect(result.boundingBoxY2).toBe(50); - }); - }); - - describe('with rotate edit', () => { - it('should rotate 90 degrees clockwise', () => { - const edits: AssetEditActionItem[] = [{ action: AssetEditAction.Rotate, parameters: { angle: 90 } }]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.imageWidth).toBe(800); - expect(result.imageHeight).toBe(1000); - - expect(result.boundingBoxX1).toBe(600); - expect(result.boundingBoxY1).toBe(100); - expect(result.boundingBoxX2).toBe(700); - expect(result.boundingBoxY2).toBe(200); - }); - - it('should rotate 180 degrees', () => { - const edits: AssetEditActionItem[] = [{ action: AssetEditAction.Rotate, parameters: { angle: 180 } }]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.imageWidth).toBe(1000); - expect(result.imageHeight).toBe(800); - - expect(result.boundingBoxX1).toBe(800); - expect(result.boundingBoxY1).toBe(600); - expect(result.boundingBoxX2).toBe(900); - expect(result.boundingBoxY2).toBe(700); - }); - - it('should rotate 270 degrees', () => { - const edits: AssetEditActionItem[] = [{ action: AssetEditAction.Rotate, parameters: { angle: 270 } }]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.imageWidth).toBe(800); - expect(result.imageHeight).toBe(1000); - }); - }); - - describe('with mirror edit', () => { - it('should mirror horizontally', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.boundingBoxX1).toBe(800); - expect(result.boundingBoxY1).toBe(100); - expect(result.boundingBoxX2).toBe(900); - expect(result.boundingBoxY2).toBe(200); - expect(result.imageWidth).toBe(1000); - expect(result.imageHeight).toBe(800); - }); - - it('should mirror vertically', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.boundingBoxX1).toBe(100); - expect(result.boundingBoxY1).toBe(600); - expect(result.boundingBoxX2).toBe(200); - expect(result.boundingBoxY2).toBe(700); - expect(result.imageWidth).toBe(1000); - expect(result.imageHeight).toBe(800); - }); - }); - - describe('with combined edits', () => { - it('should apply crop then rotate', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 50, y: 50, width: 400, height: 300 } }, - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.imageWidth).toBe(300); - expect(result.imageHeight).toBe(400); - }); - - it('should apply crop then mirror', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 400 } }, - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, baseDimensions); - - expect(result.boundingBoxX1).toBe(100); - expect(result.boundingBoxX2).toBe(200); - expect(result.boundingBoxY1).toBe(200); - expect(result.boundingBoxY2).toBe(300); - }); - }); - - describe('with scaled dimensions', () => { - it('should scale face to match different image dimensions', () => { - const scaledDimensions = { width: 500, height: 400 }; // Half the original size - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 50, y: 50, width: 200, height: 150 } }, - ]; - const result = transformFaceBoundingBox(baseFace, edits, scaledDimensions); - - expect(result.boundingBoxX1).toBe(0); - expect(result.boundingBoxY1).toBe(0); - expect(result.boundingBoxX2).toBe(50); - expect(result.boundingBoxY2).toBe(50); - }); - }); -}); - -describe('transformOcrBoundingBox', () => { - const baseOcr: AssetOcrResponseDto = { - id: 'ocr-1', - assetId: 'asset-1', - x1: 0.1, - y1: 0.1, - x2: 0.2, - y2: 0.1, - x3: 0.2, - y3: 0.2, - x4: 0.1, - y4: 0.2, - boxScore: 0.9, - textScore: 0.85, - text: 'Test OCR', - }; - - const baseDimensions = { width: 1000, height: 800 }; - - describe('with no edits', () => { - it('should return unchanged bounding box', () => { - const result = transformOcrBoundingBox(baseOcr, [], baseDimensions); - expect(result).toEqual(baseOcr); - }); - }); - - describe('with crop edit', () => { - it('should adjust normalized coordinates for crop', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 100, y: 80, width: 400, height: 320 } }, - ]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - // Original OCR: (0.1,0.1)-(0.2,0.2) on 1000x800 = (100,80)-(200,160) - // After crop offset (100,80): (0,0)-(100,80) - // Normalized to 400x320: (0,0)-(0.25,0.25) - expect(result.x1).toBeCloseTo(0, 5); - expect(result.y1).toBeCloseTo(0, 5); - expect(result.x2).toBeCloseTo(0.25, 5); - expect(result.y2).toBeCloseTo(0, 5); - expect(result.x3).toBeCloseTo(0.25, 5); - expect(result.y3).toBeCloseTo(0.25, 5); - expect(result.x4).toBeCloseTo(0, 5); - expect(result.y4).toBeCloseTo(0.25, 5); - }); - }); - - describe('with rotate edit', () => { - it('should rotate normalized coordinates 90 degrees and reorder points', () => { - const edits: AssetEditActionItem[] = [{ action: AssetEditAction.Rotate, parameters: { angle: 90 } }]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - expect(result.id).toBe(baseOcr.id); - expect(result.text).toBe(baseOcr.text); - expect(result.x1).toBeCloseTo(0.8, 5); - expect(result.y1).toBeCloseTo(0.1, 5); - expect(result.x2).toBeCloseTo(0.9, 5); - expect(result.y2).toBeCloseTo(0.1, 5); - expect(result.x3).toBeCloseTo(0.9, 5); - expect(result.y3).toBeCloseTo(0.2, 5); - expect(result.x4).toBeCloseTo(0.8, 5); - expect(result.y4).toBeCloseTo(0.2, 5); - }); - - it('should rotate 180 degrees and reorder points', () => { - const edits: AssetEditActionItem[] = [{ action: AssetEditAction.Rotate, parameters: { angle: 180 } }]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - expect(result.x1).toBeCloseTo(0.8, 5); - expect(result.y1).toBeCloseTo(0.8, 5); - expect(result.x2).toBeCloseTo(0.9, 5); - expect(result.y2).toBeCloseTo(0.8, 5); - expect(result.x3).toBeCloseTo(0.9, 5); - expect(result.y3).toBeCloseTo(0.9, 5); - expect(result.x4).toBeCloseTo(0.8, 5); - expect(result.y4).toBeCloseTo(0.9, 5); - }); - - it('should rotate 270 degrees and reorder points', () => { - const edits: AssetEditActionItem[] = [{ action: AssetEditAction.Rotate, parameters: { angle: 270 } }]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - expect(result.id).toBe(baseOcr.id); - expect(result.text).toBe(baseOcr.text); - expect(result.x1).toBeCloseTo(0.1, 5); - expect(result.y1).toBeCloseTo(0.8, 5); - expect(result.x2).toBeCloseTo(0.2, 5); - expect(result.y2).toBeCloseTo(0.8, 5); - expect(result.x3).toBeCloseTo(0.2, 5); - expect(result.y3).toBeCloseTo(0.9, 5); - expect(result.x4).toBeCloseTo(0.1, 5); - expect(result.y4).toBeCloseTo(0.9, 5); - }); - }); - - describe('with mirror edit', () => { - it('should mirror horizontally', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Horizontal } }, - ]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - expect(result.x1).toBeCloseTo(0.9, 5); - expect(result.y1).toBeCloseTo(0.1, 5); - }); - - it('should mirror vertically', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Mirror, parameters: { axis: MirrorAxis.Vertical } }, - ]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - expect(result.x1).toBeCloseTo(0.1, 5); - expect(result.y1).toBeCloseTo(0.9, 5); - }); - }); - - describe('with combined edits', () => { - it('should preserve OCR metadata through transforms', () => { - const edits: AssetEditActionItem[] = [ - { action: AssetEditAction.Crop, parameters: { x: 0, y: 0, width: 500, height: 400 } }, - { action: AssetEditAction.Rotate, parameters: { angle: 90 } }, - ]; - const result = transformOcrBoundingBox(baseOcr, edits, baseDimensions); - - expect(result.id).toBe(baseOcr.id); - expect(result.assetId).toBe(baseOcr.assetId); - expect(result.boxScore).toBe(baseOcr.boxScore); - expect(result.textScore).toBe(baseOcr.textScore); - expect(result.text).toBe(baseOcr.text); - }); - }); -}); diff --git a/server/src/utils/transform.ts b/server/src/utils/transform.ts deleted file mode 100644 index 261595eb66..0000000000 --- a/server/src/utils/transform.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { AssetEditAction, AssetEditActionItem } from 'src/dtos/editing.dto'; -import { AssetOcrResponseDto } from 'src/dtos/ocr.dto'; -import { ImageDimensions } from 'src/types'; -import { applyToPoint, compose, flipX, flipY, identity, Matrix, rotate, scale, translate } from 'transformation-matrix'; - -export const getOutputDimensions = ( - edits: AssetEditActionItem[], - startingDimensions: ImageDimensions, -): ImageDimensions => { - let { width, height } = startingDimensions; - - const crop = edits.find((edit) => edit.action === AssetEditAction.Crop); - if (crop) { - width = crop.parameters.width; - height = crop.parameters.height; - } - - for (const edit of edits) { - if (edit.action === AssetEditAction.Rotate) { - const angleDegrees = edit.parameters.angle; - if (angleDegrees === 90 || angleDegrees === 270) { - [width, height] = [height, width]; - } - } - } - - return { width, height }; -}; - -export const createAffineMatrix = ( - edits: AssetEditActionItem[], - scalingParameters?: { - pointSpace: ImageDimensions; - targetSpace: ImageDimensions; - }, -): Matrix => { - let scalingMatrix: Matrix = identity(); - - if (scalingParameters) { - const { pointSpace, targetSpace } = scalingParameters; - const scaleX = targetSpace.width / pointSpace.width; - scalingMatrix = scale(scaleX); - } - - return compose( - scalingMatrix, - ...edits.map((edit) => { - switch (edit.action) { - case 'rotate': { - const angleInRadians = (-edit.parameters.angle * Math.PI) / 180; - return rotate(angleInRadians); - } - case 'mirror': { - return edit.parameters.axis === 'horizontal' ? flipY() : flipX(); - } - default: { - return identity(); - } - } - }), - ); -}; - -export type Point = { x: number; y: number }; - -type TransformState = { - points: Point[]; - currentWidth: number; - currentHeight: number; -}; - -/** - * Transforms an array of points through a series of edit operations (crop, rotate, mirror). - * Points should be in absolute pixel coordinates relative to the starting dimensions. - */ -export const transformPoints = ( - points: Point[], - edits: AssetEditActionItem[], - startingDimensions: ImageDimensions, - { inverse = false } = {}, -): TransformState => { - let currentWidth = startingDimensions.width; - let currentHeight = startingDimensions.height; - let transformedPoints = [...points]; - - // Handle crop first if not inverting - if (!inverse) { - const crop = edits.find((edit) => edit.action === 'crop'); - if (crop) { - const { x: cropX, y: cropY, width: cropWidth, height: cropHeight } = crop.parameters; - transformedPoints = transformedPoints.map((p) => ({ - x: p.x - cropX, - y: p.y - cropY, - })); - currentWidth = cropWidth; - currentHeight = cropHeight; - } - } - - // Apply rotate and mirror transforms - const editSequence = inverse ? edits.toReversed() : edits; - for (const edit of editSequence) { - let matrix: Matrix = identity(); - if (edit.action === 'rotate') { - const angleDegrees = edit.parameters.angle; - const angleRadians = (angleDegrees * Math.PI) / 180; - const newWidth = angleDegrees === 90 || angleDegrees === 270 ? currentHeight : currentWidth; - const newHeight = angleDegrees === 90 || angleDegrees === 270 ? currentWidth : currentHeight; - - matrix = compose( - translate(newWidth / 2, newHeight / 2), - rotate(inverse ? -angleRadians : angleRadians), - translate(-currentWidth / 2, -currentHeight / 2), - ); - - currentWidth = newWidth; - currentHeight = newHeight; - } else if (edit.action === 'mirror') { - matrix = compose( - translate(currentWidth / 2, currentHeight / 2), - edit.parameters.axis === 'horizontal' ? flipY() : flipX(), - translate(-currentWidth / 2, -currentHeight / 2), - ); - } else { - // Skip non-affine transformations - continue; - } - - transformedPoints = transformedPoints.map((p) => applyToPoint(matrix, p)); - } - - // Handle crop last if inverting - if (inverse) { - const crop = edits.find((edit) => edit.action === 'crop'); - if (crop) { - const { x: cropX, y: cropY } = crop.parameters; - transformedPoints = transformedPoints.map((p) => ({ - x: p.x + cropX, - y: p.y + cropY, - })); - } - } - - return { - points: transformedPoints, - currentWidth, - currentHeight, - }; -}; - -type FaceBoundingBox = { - boundingBoxX1: number; - boundingBoxX2: number; - boundingBoxY1: number; - boundingBoxY2: number; - imageWidth: number; - imageHeight: number; -}; - -export const transformFaceBoundingBox = ( - box: FaceBoundingBox, - edits: AssetEditActionItem[], - imageDimensions: ImageDimensions, -): FaceBoundingBox => { - if (edits.length === 0) { - return box; - } - - const scaleX = imageDimensions.width / box.imageWidth; - const scaleY = imageDimensions.height / box.imageHeight; - - const points: Point[] = [ - { x: box.boundingBoxX1 * scaleX, y: box.boundingBoxY1 * scaleY }, - { x: box.boundingBoxX2 * scaleX, y: box.boundingBoxY2 * scaleY }, - ]; - - const { points: transformedPoints, currentWidth, currentHeight } = transformPoints(points, edits, imageDimensions); - - // Ensure x1,y1 is top-left and x2,y2 is bottom-right - const [p1, p2] = transformedPoints; - return { - boundingBoxX1: Math.min(p1.x, p2.x), - boundingBoxY1: Math.min(p1.y, p2.y), - boundingBoxX2: Math.max(p1.x, p2.x), - boundingBoxY2: Math.max(p1.y, p2.y), - imageWidth: currentWidth, - imageHeight: currentHeight, - }; -}; - -const reorderQuadPointsForRotation = (points: Point[], rotationDegrees: number): Point[] => { - const [p1, p2, p3, p4] = points; - switch (rotationDegrees) { - case 90: { - return [p4, p1, p2, p3]; - } - case 180: { - return [p3, p4, p1, p2]; - } - case 270: { - return [p2, p3, p4, p1]; - } - default: { - return points; - } - } -}; - -export const transformOcrBoundingBox = ( - box: AssetOcrResponseDto, - edits: AssetEditActionItem[], - imageDimensions: ImageDimensions, -): AssetOcrResponseDto => { - if (edits.length === 0) { - return box; - } - - const points: Point[] = [ - { x: box.x1 * imageDimensions.width, y: box.y1 * imageDimensions.height }, - { x: box.x2 * imageDimensions.width, y: box.y2 * imageDimensions.height }, - { x: box.x3 * imageDimensions.width, y: box.y3 * imageDimensions.height }, - { x: box.x4 * imageDimensions.width, y: box.y4 * imageDimensions.height }, - ]; - - const { points: transformedPoints, currentWidth, currentHeight } = transformPoints(points, edits, imageDimensions); - - // Reorder points to maintain semantic ordering (topLeft, topRight, bottomRight, bottomLeft) - const netRotation = edits.find((e) => e.action == AssetEditAction.Rotate)?.parameters.angle ?? 0 % 360; - const reorderedPoints = reorderQuadPointsForRotation(transformedPoints, netRotation); - - const [p1, p2, p3, p4] = reorderedPoints; - return { - ...box, - x1: p1.x / currentWidth, - y1: p1.y / currentHeight, - x2: p2.x / currentWidth, - y2: p2.y / currentHeight, - x3: p3.x / currentWidth, - y3: p3.y / currentHeight, - x4: p4.x / currentWidth, - y4: p4.y / currentHeight, - }; -}; diff --git a/server/src/validation.spec.ts b/server/src/validation.spec.ts deleted file mode 100644 index 631ba60a60..0000000000 --- a/server/src/validation.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; -import { DateTime } from 'luxon'; -import { IsDateStringFormat, IsNotSiblingOf, MaxDateString, Optional } from 'src/validation'; -import { describe } from 'vitest'; - -describe('Validation', () => { - describe('MaxDateString', () => { - const maxDate = DateTime.fromISO('2000-01-01', { zone: 'utc' }); - - class MyDto { - @MaxDateString(maxDate) - date!: string; - } - - it('passes when date is before maxDate', async () => { - const dto = plainToInstance(MyDto, { date: '1999-12-31' }); - await expect(validate(dto)).resolves.toHaveLength(0); - }); - - it('passes when date is equal to maxDate', async () => { - const dto = plainToInstance(MyDto, { date: '2000-01-01' }); - await expect(validate(dto)).resolves.toHaveLength(0); - }); - - it('fails when date is after maxDate', async () => { - const dto = plainToInstance(MyDto, { date: '2010-01-01' }); - await expect(validate(dto)).resolves.toHaveLength(1); - }); - }); - - describe('IsDateStringFormat', () => { - class MyDto { - @IsDateStringFormat('yyyy-MM-dd') - date!: string; - } - - it('passes when date is valid', async () => { - const dto = plainToInstance(MyDto, { date: '1999-12-31' }); - await expect(validate(dto)).resolves.toHaveLength(0); - }); - - it('fails when date has invalid format', async () => { - const dto = plainToInstance(MyDto, { date: '2000-01-01T00:00:00Z' }); - await expect(validate(dto)).resolves.toHaveLength(1); - }); - - it('fails when empty string', async () => { - const dto = plainToInstance(MyDto, { date: '' }); - await expect(validate(dto)).resolves.toHaveLength(1); - }); - - it('fails when undefined', async () => { - const dto = plainToInstance(MyDto, {}); - await expect(validate(dto)).resolves.toHaveLength(1); - }); - }); - - describe('IsNotSiblingOf', () => { - class MyDto { - @IsNotSiblingOf(['attribute2']) - @Optional() - attribute1?: string; - - @IsNotSiblingOf(['attribute1', 'attribute3']) - @Optional() - attribute2?: string; - - @IsNotSiblingOf(['attribute2']) - @Optional() - attribute3?: string; - - @Optional() - unrelatedAttribute?: string; - } - - it('passes when only one attribute is present', async () => { - const dto = plainToInstance(MyDto, { attribute1: 'value1', unrelatedAttribute: 'value2' }); - await expect(validate(dto)).resolves.toHaveLength(0); - }); - - it('fails when colliding attributes are present', async () => { - const dto = plainToInstance(MyDto, { attribute1: 'value1', attribute2: 'value2' }); - await expect(validate(dto)).resolves.toHaveLength(2); - }); - - it('passes when no colliding attributes are present', async () => { - const dto = plainToInstance(MyDto, { attribute1: 'value1', attribute3: 'value2' }); - await expect(validate(dto)).resolves.toHaveLength(0); - }); - }); -}); diff --git a/server/src/workers/api.ts b/server/src/workers/api.ts index 99c08c0fa7..c39eac200d 100644 --- a/server/src/workers/api.ts +++ b/server/src/workers/api.ts @@ -1,22 +1,16 @@ import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; -import { configureExpress, configureTelemetry } from 'src/app.common'; import { ApiModule } from 'src/app.module'; import { AppRepository } from 'src/repositories/app.repository'; -import { ApiService } from 'src/services/api.service'; import { isStartUpError } from 'src/utils/misc'; async function bootstrap() { process.title = 'immich-api'; - configureTelemetry(); - const app = await NestFactory.create(ApiModule, { bufferLogs: true }); app.get(AppRepository).setCloseFn(() => app.close()); - void configureExpress(app, { - ssr: ApiService, - }); + await app.listen(3001); } bootstrap().catch((error) => { diff --git a/server/src/workers/maintenance.ts b/server/src/workers/maintenance.ts deleted file mode 100644 index 035ec600af..0000000000 --- a/server/src/workers/maintenance.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NestFactory } from '@nestjs/core'; -import { NestExpressApplication } from '@nestjs/platform-express'; -import { configureExpress, configureTelemetry } from 'src/app.common'; -import { MaintenanceModule } from 'src/app.module'; -import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; -import { AppRepository } from 'src/repositories/app.repository'; -import { isStartUpError } from 'src/utils/misc'; - -async function bootstrap() { - process.title = 'immich-maintenance'; - configureTelemetry(); - - const app = await NestFactory.create(MaintenanceModule, { bufferLogs: true }); - app.get(AppRepository).setCloseFn(() => app.close()); - - void configureExpress(app, { - permitSwaggerWrite: false, - ssr: MaintenanceWorkerService, - }); -} - -bootstrap().catch((error) => { - if (!isStartUpError(error)) { - console.error(error); - } - // eslint-disable-next-line unicorn/no-process-exit - process.exit(1); -}); diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index 0ff869ca28..9188cade0d 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -5,25 +5,17 @@ import { Mocked, vitest } from 'vitest'; export const newDatabaseRepositoryMock = (): Mocked> => { return { shutdown: vitest.fn(), - getExtensionVersions: vitest.fn(), - getVectorExtension: vitest.fn(), + getExtensionVersion: vitest.fn(), getExtensionVersionRange: vitest.fn(), getPostgresVersion: vitest.fn().mockResolvedValue('14.10 (Debian 14.10-1.pgdg120+1)'), getPostgresVersionRange: vitest.fn().mockReturnValue('>=14.0.0'), createExtension: vitest.fn().mockResolvedValue(void 0), dropExtension: vitest.fn(), - updateVectorExtension: vitest.fn(), - reindexVectorsIfNeeded: vitest.fn(), - getDimensionSize: vitest.fn(), - setDimensionSize: vitest.fn(), - deleteAllSearchEmbeddings: vitest.fn(), - prewarm: vitest.fn(), runMigrations: vitest.fn(), revertLastMigration: vitest.fn(), withLock: vitest.fn().mockImplementation((_, function_: () => Promise) => function_()), tryLock: vitest.fn(), isBusy: vitest.fn(), wait: vitest.fn(), - migrateFilePaths: vitest.fn(), }; }; diff --git a/server/test/repositories/job.repository.mock.ts b/server/test/repositories/job.repository.mock.ts index 4fc5460c8a..6fa8f8fbac 100644 --- a/server/test/repositories/job.repository.mock.ts +++ b/server/test/repositories/job.repository.mock.ts @@ -11,14 +11,10 @@ export const newJobRepositoryMock = (): Mocked Promise.resolve()), queueAll: vitest.fn().mockImplementation(() => Promise.resolve()), isActive: vitest.fn(), isPaused: vitest.fn(), - getJobCounts: vitest.fn(), - clear: vitest.fn(), waitForQueueCompletion: vitest.fn(), - removeJob: vitest.fn(), }; }; diff --git a/web/src/lib/attachments/drag-and-drop.svelte.ts b/web/src/lib/attachments/drag-and-drop.svelte.ts deleted file mode 100644 index 950e8e5b80..0000000000 --- a/web/src/lib/attachments/drag-and-drop.svelte.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { Attachment } from 'svelte/attachments'; - -export interface DragAndDropOptions { - index: number; - onDragStart?: (index: number) => void; - onDragEnter?: (index: number) => void; - onDrop?: (e: DragEvent, index: number) => void; - onDragEnd?: () => void; - isDragging?: boolean; - isDragOver?: boolean; -} - -export function dragAndDrop(options: DragAndDropOptions): Attachment { - return (node: Element) => { - const element = node as HTMLElement; - const { index, onDragStart, onDragEnter, onDrop, onDragEnd, isDragging, isDragOver } = options; - - const isFormElement = (el: HTMLElement) => { - return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT'; - }; - - const handleDragStart = (e: DragEvent) => { - // Prevent drag if it originated from an input, textarea, or select element - const target = e.target as HTMLElement; - if (isFormElement(target)) { - e.preventDefault(); - return; - } - onDragStart?.(index); - }; - - const handleDragEnter = () => { - onDragEnter?.(index); - }; - - const handleDragOver = (e: DragEvent) => { - e.preventDefault(); - }; - - const handleDrop = (e: DragEvent) => { - onDrop?.(e, index); - }; - - const handleDragEnd = () => { - onDragEnd?.(); - }; - - // Disable draggable when focusing on form elements (fixes Firefox input interaction) - const handleFocusIn = (e: FocusEvent) => { - const target = e.target as HTMLElement; - if (isFormElement(target)) { - element.setAttribute('draggable', 'false'); - } - }; - - const handleFocusOut = (e: FocusEvent) => { - const target = e.target as HTMLElement; - if (isFormElement(target)) { - element.setAttribute('draggable', 'true'); - } - }; - - // Update classes based on drag state - const updateClasses = (dragging: boolean, dragOver: boolean) => { - // Remove all drag-related classes first - element.classList.remove('opacity-50', 'border-light-500', 'border-solid'); - - // Add back only the active ones - if (dragging) { - element.classList.add('opacity-50'); - } - - if (dragOver) { - element.classList.add('border-light-500', 'border-solid'); - element.classList.remove('border-transparent'); - } else { - element.classList.add('border-transparent'); - } - }; - - element.setAttribute('draggable', 'true'); - element.setAttribute('role', 'button'); - element.setAttribute('tabindex', '0'); - - element.addEventListener('dragstart', handleDragStart); - element.addEventListener('dragenter', handleDragEnter); - element.addEventListener('dragover', handleDragOver); - element.addEventListener('drop', handleDrop); - element.addEventListener('dragend', handleDragEnd); - element.addEventListener('focusin', handleFocusIn); - element.addEventListener('focusout', handleFocusOut); - - updateClasses(isDragging || false, isDragOver || false); - - return () => { - element.removeEventListener('dragstart', handleDragStart); - element.removeEventListener('dragenter', handleDragEnter); - element.removeEventListener('dragover', handleDragOver); - element.removeEventListener('drop', handleDrop); - element.removeEventListener('dragend', handleDragEnd); - element.removeEventListener('focusin', handleFocusIn); - element.removeEventListener('focusout', handleFocusOut); - }; - }; -} diff --git a/web/src/lib/components/admin-settings/AuthSettings.svelte b/web/src/lib/components/admin-settings/AuthSettings.svelte deleted file mode 100644 index aec1761998..0000000000 --- a/web/src/lib/components/admin-settings/AuthSettings.svelte +++ /dev/null @@ -1,291 +0,0 @@ - - -
-
-
e.preventDefault()}> -
- -
- - - {#snippet children({ message })} - - {message} - - {/snippet} - - - - - - {#if configToEdit.oauth.enabled} -
- -
- {$t('admin.unlink_all_oauth_accounts_description')} - -
- - - - - - - - {#if configToEdit.oauth.clientSecret} - - {/if} - - - - - - - - - - - - - - - - - - - - - - - - handleToggleOverride()} - bind:checked={configToEdit.oauth.mobileOverrideEnabled} - /> - - {#if configToEdit.oauth.mobileOverrideEnabled} - - {/if} - {/if} -
-
- - -
-
- -
-
-
- - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/BackupSettings.svelte b/web/src/lib/components/admin-settings/BackupSettings.svelte deleted file mode 100644 index fc374ddd6f..0000000000 --- a/web/src/lib/components/admin-settings/BackupSettings.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - - - - - {#snippet descriptionSnippet()} -

- - {#snippet children({ message })} - - {message} -
-
- {/snippet} -
-

- {/snippet} -
- - - - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/FFmpegSettings.svelte b/web/src/lib/components/admin-settings/FFmpegSettings.svelte deleted file mode 100644 index 83596069f9..0000000000 --- a/web/src/lib/components/admin-settings/FFmpegSettings.svelte +++ /dev/null @@ -1,404 +0,0 @@ - - -
-
-
event.preventDefault()}> -
-

- - - {#snippet children({ tag, message })} - {#if tag === 'h264-link'} - - {message} - - {:else if tag === 'hevc-link'} - - {message} - - {:else if tag === 'vp9-link'} - - {message} - - {/if} - {/snippet} - -

- - -
- - - - - - - -
-
- - -
- (configToEdit.ffmpeg.acceptedVideoCodecs = [configToEdit.ffmpeg.targetVideoCodec])} - /> - - - - configToEdit.ffmpeg.acceptedAudioCodecs.includes(configToEdit.ffmpeg.targetAudioCodec) - ? null - : configToEdit.ffmpeg.acceptedAudioCodecs.push(configToEdit.ffmpeg.targetAudioCodec)} - /> - - - - - - - - - - - - - - -
-
- - -
- - - - - - - - - -
-
- - -
- - - - - -
-
-
- -
- -
-
-
-
diff --git a/web/src/lib/components/admin-settings/ImageSettings.svelte b/web/src/lib/components/admin-settings/ImageSettings.svelte deleted file mode 100644 index e1f4ec5008..0000000000 --- a/web/src/lib/components/admin-settings/ImageSettings.svelte +++ /dev/null @@ -1,224 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - { - if (value === ImageFormat.Webp) { - configToEdit.image.thumbnail.progressive = false; - } - }} - /> - - - - - - (configToEdit.image.thumbnail.progressive = isChecked)} - isEdited={configToEdit.image.thumbnail.progressive !== config.image.thumbnail.progressive} - disabled={disabled || configToEdit.image.thumbnail.format === ImageFormat.Webp} - /> - - - - { - if (value === ImageFormat.Webp) { - configToEdit.image.preview.progressive = false; - } - }} - /> - - - - - - (configToEdit.image.preview.progressive = isChecked)} - isEdited={configToEdit.image.preview.progressive !== config.image.preview.progressive} - disabled={disabled || configToEdit.image.preview.format === ImageFormat.Webp} - /> - - - - (configToEdit.image.fullsize.enabled = isChecked)} - isEdited={configToEdit.image.fullsize.enabled !== config.image.fullsize.enabled} - {disabled} - /> - -
- - { - if (value === ImageFormat.Webp) { - configToEdit.image.fullsize.progressive = false; - } - }} - /> - - - - (configToEdit.image.fullsize.progressive = isChecked)} - isEdited={configToEdit.image.fullsize.progressive !== config.image.fullsize.progressive} - disabled={disabled || - !configToEdit.image.fullsize.enabled || - configToEdit.image.fullsize.format === ImageFormat.Webp} - /> -
- -
- (configToEdit.image.colorspace = isChecked ? Colorspace.P3 : Colorspace.Srgb)} - isEdited={configToEdit.image.colorspace !== config.image.colorspace} - {disabled} - /> -
- -
- (configToEdit.image.extractEmbedded = !configToEdit.image.extractEmbedded)} - isEdited={configToEdit.image.extractEmbedded !== config.image.extractEmbedded} - {disabled} - /> -
-
- -
- -
-
-
-
diff --git a/web/src/lib/components/admin-settings/JobSettings.svelte b/web/src/lib/components/admin-settings/JobSettings.svelte deleted file mode 100644 index 94b4426dbb..0000000000 --- a/web/src/lib/components/admin-settings/JobSettings.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - -
-
-
event.preventDefault()}> - {#each queueNames as queueName (queueName)} -
- {#if isSystemConfigJobDto(queueName)} - - {:else} - - {/if} -
- {/each} - -
- -
-
-
-
diff --git a/web/src/lib/components/admin-settings/LibrarySettings.svelte b/web/src/lib/components/admin-settings/LibrarySettings.svelte deleted file mode 100644 index a91a5eb97a..0000000000 --- a/web/src/lib/components/admin-settings/LibrarySettings.svelte +++ /dev/null @@ -1,96 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- -
- -
-
- - -
- - - - - - {#snippet descriptionSnippet()} -

- - {#snippet children({ message })} - - {message} - - {/snippet} - -

- {/snippet} -
-
-
- - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/LoggingSettings.svelte b/web/src/lib/components/admin-settings/LoggingSettings.svelte deleted file mode 100644 index 6052b8ea9f..0000000000 --- a/web/src/lib/components/admin-settings/LoggingSettings.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - - - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/MachineLearningSettings.svelte b/web/src/lib/components/admin-settings/MachineLearningSettings.svelte deleted file mode 100644 index 579efef916..0000000000 --- a/web/src/lib/components/admin-settings/MachineLearningSettings.svelte +++ /dev/null @@ -1,331 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - -
- -
- {#each configToEdit.machineLearning.urls as _, i (i)} - - {#snippet trailingSnippet()} - {#if configToEdit.machineLearning.urls.length > 1} - configToEdit.machineLearning.urls.splice(i, 1)} - icon={mdiTrashCanOutline} - color="danger" - /> - {/if} - {/snippet} - - {/each} -
- -
- -
-
- - -
- - -
- - - - -
-
- - -
- - -
- - - {#snippet descriptionSnippet()} -

- - {#snippet children({ message })} - {message} - {/snippet} - -

- {/snippet} -
-
-
- - -
- - -
- - -
-
- - -
- - -
- - - - - - - - -
-
- - -
- - -
- - - - - - - - -
-
- - -
-
diff --git a/web/src/lib/components/admin-settings/MapSettings.svelte b/web/src/lib/components/admin-settings/MapSettings.svelte deleted file mode 100644 index 692a5cfcf5..0000000000 --- a/web/src/lib/components/admin-settings/MapSettings.svelte +++ /dev/null @@ -1,82 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- -
- - -
- - - -
- - - {#snippet subtitleSnippet()} -

- - {#snippet children({ message })} - - {message} - - {/snippet} - -

- {/snippet} -
- -
- - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/MetadataSettings.svelte b/web/src/lib/components/admin-settings/MetadataSettings.svelte deleted file mode 100644 index 0db36e0e82..0000000000 --- a/web/src/lib/components/admin-settings/MetadataSettings.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- -
- - - -
-
diff --git a/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte b/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte deleted file mode 100644 index d8a79d6236..0000000000 --- a/web/src/lib/components/admin-settings/NewVersionCheckSettings.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte b/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte deleted file mode 100644 index 9647f0c7c3..0000000000 --- a/web/src/lib/components/admin-settings/NightlyTasksSettings.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - - - - - -
- - - -
-
diff --git a/web/src/lib/components/admin-settings/NotificationSettings.svelte b/web/src/lib/components/admin-settings/NotificationSettings.svelte deleted file mode 100644 index e97af356df..0000000000 --- a/web/src/lib/components/admin-settings/NotificationSettings.svelte +++ /dev/null @@ -1,166 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- -
- - -
- - - - - - - - - - - - - -
- - - -
- - {#if isSending} - - {/if} -
-
-
-
-
-
- - - -
diff --git a/web/src/lib/components/admin-settings/ServerSettings.svelte b/web/src/lib/components/admin-settings/ServerSettings.svelte deleted file mode 100644 index 936c4f406e..0000000000 --- a/web/src/lib/components/admin-settings/ServerSettings.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - - - - - -
- -
-
-
-
-
diff --git a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte b/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte deleted file mode 100644 index 7018bc5d04..0000000000 --- a/web/src/lib/components/admin-settings/StorageTemplateSettings.svelte +++ /dev/null @@ -1,283 +0,0 @@ - - -
-
-

- - {#snippet children({ tag, message })} - {#if tag === 'template-link'} - - {message} - - {:else if tag === 'implications-link'} - - {message} - - {/if} - {/snippet} - -

-
- {#await getTemplateOptions() then} -
- - - {#if !minified} - - {/if} - - {#if configToEdit.storageTemplate.enabled} -
- - - {$t('variables')} - - -
- {#await getSupportDateTimeFormat()} - - {:then options} -
- -
- {/await} -
- -
- -
- -
- - - {$t('template')} - - -
- {$t('preview')} -
- -

- - {#snippet children({ message })} - {message} - {/snippet} - -

- -

- - {#snippet children({ message })} - {message} - {/snippet} - -

- -

- UPLOAD_LOCATION/library/{$user.storageLabel || $user.id}/{parsedTemplate()}.jpg -

- -
-
- {#if templateOptions} - - - {/if} -
- -
- - -
- -
-
- - {#if !minified} -
- - {$t('notes')} - -
-

- - {#snippet children({ message })} - {message} - {/snippet} - -

-
-
- {/if} -
-
- {/if} - - {#if !minified} - - {/if} -
- {/await} -
diff --git a/web/src/lib/components/admin-settings/SupportedDatetimePanel.svelte b/web/src/lib/components/admin-settings/SupportedDatetimePanel.svelte deleted file mode 100644 index e88734c7d9..0000000000 --- a/web/src/lib/components/admin-settings/SupportedDatetimePanel.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - -{$t('date_and_time')} - - - - - {$t('admin.storage_template_date_time_description')} - {$t('admin.storage_template_date_time_sample', { values: { date: '2022-02-03T20:03:05.250' } })} - - -
-
- {$t('year')} -
    - {#each options.yearOptions as yearFormat, index (index)} -
  • {'{{'}{yearFormat}{'}}'} - {getLuxonExample(yearFormat)}
  • - {/each} -
-
- -
- {$t('month')} -
    - {#each options.monthOptions as monthFormat, index (index)} -
  • {'{{'}{monthFormat}{'}}'} - {getLuxonExample(monthFormat)}
  • - {/each} -
-
- -
- {$t('week')} -
    - {#each options.weekOptions as weekFormat, index (index)} -
  • {'{{'}{weekFormat}{'}}'} - {getLuxonExample(weekFormat)}
  • - {/each} -
-
- -
- {$t('day')} -
    - {#each options.dayOptions as dayFormat, index (index)} -
  • {'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}
  • - {/each} -
-
- -
- {$t('hour')} -
    - {#each options.hourOptions as dayFormat, index (index)} -
  • {'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}
  • - {/each} -
-
- -
- {$t('minute')} -
    - {#each options.minuteOptions as dayFormat, index (index)} -
  • {'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}
  • - {/each} -
-
- -
- {$t('second')} -
    - {#each options.secondOptions as dayFormat, index (index)} -
  • {'{{'}{dayFormat}{'}}'} - {getLuxonExample(dayFormat)}
  • - {/each} -
-
-
-
-
diff --git a/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte b/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte deleted file mode 100644 index f46f33860a..0000000000 --- a/web/src/lib/components/admin-settings/SupportedVariablesPanel.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - -{$t('other_variables')} - - - -
-
- {$t('filename')} -
    -
  • {`{{filename}}`} - IMG_123
  • -
  • {`{{ext}}`} - jpg
  • -
-
- -
- {$t('filetype')} -
    -
  • {`{{filetype}}`} - VID or IMG
  • -
  • {`{{filetypefull}}`} - VIDEO or IMAGE
  • -
-
- -
- {$t('camera')} -
    -
  • {`{{make}}`} - FUJIFILM
  • -
  • {`{{model}}`} - X-T50
  • -
  • {`{{lensModel}}`} - XF27mm F2.8 R WR
  • -
-
- -
- {$t('album')} -
    -
  • {`{{album}}`} - Album Name
  • -
  • - {`{{album-startDate-x}}`} - Album Start Date and Time (e.g. album-startDate-yy). - {$t('admin.storage_template_date_time_sample', { values: { date: '2021-12-31T05:32:41.750' } })} -
  • -
  • - {`{{album-endDate-x}}`} - Album End Date and Time (e.g. album-endDate-MM). - {$t('admin.storage_template_date_time_sample', { values: { date: '2023-05-06T09:15:17.100' } })} -
  • -
-
-
- {$t('other')} -
    -
  • {`{{assetId}}`} - Asset ID
  • -
  • {`{{assetIdShort}}`} - Asset ID (last 12 characters)
  • -
-
-
-
-
diff --git a/web/src/lib/components/admin-settings/TemplateSettings.svelte b/web/src/lib/components/admin-settings/TemplateSettings.svelte deleted file mode 100644 index 7ef1bcde0d..0000000000 --- a/web/src/lib/components/admin-settings/TemplateSettings.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -
-
-
- -
-

- - {$t('admin.template_email_if_empty')} - -

-
- {#if loadingPreview} - - {/if} - - {#each templateConfigs as { label, templateKey, descriptionTags, templateName } (templateKey)} - -
- -
- {/each} -
-
-
-
-
diff --git a/web/src/lib/components/admin-settings/ThemeSettings.svelte b/web/src/lib/components/admin-settings/ThemeSettings.svelte deleted file mode 100644 index f2e9b51f11..0000000000 --- a/web/src/lib/components/admin-settings/ThemeSettings.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/TrashSettings.svelte b/web/src/lib/components/admin-settings/TrashSettings.svelte deleted file mode 100644 index 9e71d0abc1..0000000000 --- a/web/src/lib/components/admin-settings/TrashSettings.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - -
-
-
event.preventDefault()}> -
- - -
- - - - -
-
-
-
diff --git a/web/src/lib/components/admin-settings/UserSettings.svelte b/web/src/lib/components/admin-settings/UserSettings.svelte deleted file mode 100644 index 23ea052b52..0000000000 --- a/web/src/lib/components/admin-settings/UserSettings.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -
-
-
e.preventDefault()}> -
- -
- -
- -
-
-
-
diff --git a/web/src/lib/components/album-page/__tests__/album-card.spec.ts b/web/src/lib/components/album-page/__tests__/album-card.spec.ts deleted file mode 100644 index 67ae2daf2e..0000000000 --- a/web/src/lib/components/album-page/__tests__/album-card.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { sdkMock } from '$lib/__mocks__/sdk.mock'; -import { renderWithTooltips } from '$tests/helpers'; -import { albumFactory } from '@test-data/factories/album-factory'; -import '@testing-library/jest-dom'; -import { render, waitFor, type RenderResult } from '@testing-library/svelte'; -import userEvent from '@testing-library/user-event'; -import { init, register, waitLocale } from 'svelte-i18n'; -import AlbumCard from '../album-card.svelte'; - -const onShowContextMenu = vi.fn(); - -describe('AlbumCard component', () => { - let sut: RenderResult; - - beforeAll(async () => { - await init({ fallbackLocale: 'en-US' }); - register('en-US', () => import('$i18n/en.json')); - await waitLocale('en-US'); - }); - - it.each([ - { - album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 0 }), - count: 0, - shared: false, - }, - { - album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 0 }), - count: 0, - shared: true, - }, - { - album: albumFactory.build({ albumThumbnailAssetId: null, shared: false, assetCount: 5 }), - count: 5, - shared: false, - }, - { - album: albumFactory.build({ albumThumbnailAssetId: null, shared: true, assetCount: 2 }), - count: 2, - shared: true, - }, - ])('shows album data without thumbnail with count $count - shared: $shared', async ({ album, count, shared }) => { - sut = render(AlbumCard, { album, showItemCount: true }); - - const albumImgElement = sut.getByTestId('album-image'); - const albumNameElement = sut.getByTestId('album-name'); - const albumDetailsElement = sut.getByTestId('album-details'); - const detailsText = `${count} items` + (shared ? ' . Shared' : ''); - - expect(albumImgElement).toHaveAttribute('src'); - expect(albumImgElement).toHaveAttribute('alt', album.albumName); - - await waitFor(() => expect(albumImgElement).toHaveAttribute('src')); - - expect(albumImgElement).toHaveAttribute('alt', album.albumName); - expect(sdkMock.viewAsset).not.toHaveBeenCalled(); - - expect(albumNameElement).toHaveTextContent(album.albumName); - expect(albumDetailsElement).toHaveTextContent(new RegExp(detailsText)); - }); - - it('shows album data', () => { - const album = albumFactory.build({ - shared: false, - albumName: 'some album name', - }); - sut = render(AlbumCard, { album, showItemCount: true }); - - const albumImgElement = sut.getByTestId('album-image'); - const albumNameElement = sut.getByTestId('album-name'); - const albumDetailsElement = sut.getByTestId('album-details'); - - expect(albumImgElement).toHaveAttribute('alt', album.albumName); - expect(albumImgElement).toHaveAttribute('src'); - - expect(albumNameElement).toHaveTextContent('some album name'); - expect(albumDetailsElement).toHaveTextContent('0 item'); - }); - - it('hides context menu when "onShowContextMenu" is undefined', () => { - const album = Object.freeze(albumFactory.build({ albumThumbnailAssetId: null })); - sut = render(AlbumCard, { album }); - - const contextButtonParent = sut.queryByTestId('context-button-parent'); - expect(contextButtonParent).not.toBeInTheDocument(); - }); - - describe('with rendered component - no thumbnail', () => { - const album = Object.freeze(albumFactory.build({ albumThumbnailAssetId: null })); - - beforeEach(async () => { - sut = renderWithTooltips(AlbumCard, { album, onShowContextMenu }); - - const albumImgElement = sut.getByTestId('album-image'); - await waitFor(() => expect(albumImgElement).toHaveAttribute('src')); - }); - - it('dispatches "onShowContextMenu" event on context menu click with mouse coordinates', async () => { - const contextMenuButton = sut.getByTestId('context-button-parent').children[0]; - expect(contextMenuButton).toBeDefined(); - - // Mock getBoundingClientRect to return a bounding rectangle that will result in the expected position - contextMenuButton.getBoundingClientRect = () => ({ - x: 123, - y: 456, - width: 0, - height: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - toJSON: () => ({}), - }); - - const user = userEvent.setup(); - await user.click(contextMenuButton); - - expect(onShowContextMenu).toHaveBeenCalledTimes(1); - expect(onShowContextMenu).toHaveBeenCalledWith(expect.objectContaining({ x: 123, y: 456 })); - }); - }); -}); diff --git a/web/src/lib/components/album-page/__tests__/album-cover.spec.ts b/web/src/lib/components/album-page/__tests__/album-cover.spec.ts deleted file mode 100644 index 3d081a3d2d..0000000000 --- a/web/src/lib/components/album-page/__tests__/album-cover.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import AlbumCover from '$lib/components/album-page/album-cover.svelte'; -import { getAssetMediaUrl } from '$lib/utils'; -import { albumFactory } from '@test-data/factories/album-factory'; -import { render } from '@testing-library/svelte'; - -vi.mock('$lib/utils'); - -describe('AlbumCover component', () => { - it('renders an image when the album has a thumbnail', () => { - vi.mocked(getAssetMediaUrl).mockReturnValue('/asdf'); - const component = render(AlbumCover, { - album: albumFactory.build({ - albumName: 'someName', - albumThumbnailAssetId: '123', - }), - preload: false, - class: 'text', - }); - const img = component.getByTestId('album-image') as HTMLImageElement; - expect(img.alt).toBe('someName'); - expect(img.getAttribute('loading')).toBe('lazy'); - expect(img.className).toBe('size-full rounded-xl object-cover aspect-square text'); - expect(img.getAttribute('src')).toBe('/asdf'); - expect(getAssetMediaUrl).toHaveBeenCalledWith({ id: '123' }); - }); - - it('renders an image when the album has no thumbnail', () => { - const component = render(AlbumCover, { - album: albumFactory.build({ - albumName: '', - albumThumbnailAssetId: null, - }), - preload: true, - class: 'asdf', - }); - const img = component.getByTestId('album-image') as HTMLImageElement; - expect(img.alt).toBe('unnamed_album'); - expect(img.getAttribute('loading')).toBe('eager'); - expect(img.className).toBe('size-full rounded-xl object-cover aspect-square asdf'); - expect(img.getAttribute('src')).toStrictEqual(expect.any(String)); - }); -}); diff --git a/web/src/lib/components/album-page/album-card-group.svelte b/web/src/lib/components/album-page/album-card-group.svelte deleted file mode 100644 index 99aa8f2b71..0000000000 --- a/web/src/lib/components/album-page/album-card-group.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - -{#if group} -
- -
-
-{/if} - -
- {#if !isCollapsed} - - {/if} -
diff --git a/web/src/lib/components/album-page/album-card.svelte b/web/src/lib/components/album-page/album-card.svelte deleted file mode 100644 index 081e247982..0000000000 --- a/web/src/lib/components/album-page/album-card.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - -
- {#if onShowContextMenu} -
- -
- {/if} - - - -
-

- {album.albumName} -

- - {#if showDateRange && album.startDate && album.endDate} -

- {getShortDateRange(album.startDate, album.endDate)} -

- {/if} - - - {#if showItemCount} -

- {$t('items_count', { values: { count: album.assetCount } })} -

- {/if} - - {#if (showOwner || album.shared) && showItemCount} -

- {/if} - - {#if showOwner} - {#if $user.id === album.ownerId} -

{$t('owned')}

- {:else if album.owner} -

{$t('shared_by_user', { values: { user: album.owner.name } })}

- {:else} -

{$t('shared')}

- {/if} - {:else if album.shared} -

{$t('shared')}

- {/if} -
-
-
diff --git a/web/src/lib/components/album-page/album-cover.svelte b/web/src/lib/components/album-page/album-cover.svelte deleted file mode 100644 index c6242c5fad..0000000000 --- a/web/src/lib/components/album-page/album-cover.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -{#if thumbnailUrl} - -{:else} - -{/if} diff --git a/web/src/lib/components/album-page/album-description.spec.ts b/web/src/lib/components/album-page/album-description.spec.ts deleted file mode 100644 index 1c069de2bc..0000000000 --- a/web/src/lib/components/album-page/album-description.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import AlbumDescription from '$lib/components/album-page/album-description.svelte'; -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/svelte'; -import { describe } from 'vitest'; - -describe('AlbumDescription component', () => { - it('shows an AutogrowTextarea component when isOwned is true', () => { - render(AlbumDescription, { isOwned: true, id: '', description: '' }); - const autogrowTextarea = screen.getByTestId('autogrow-textarea'); - expect(autogrowTextarea).toBeInTheDocument(); - }); - - it('does not show an AutogrowTextarea component when isOwned is false', () => { - render(AlbumDescription, { isOwned: false, id: '', description: '' }); - const autogrowTextarea = screen.queryByTestId('autogrow-textarea'); - expect(autogrowTextarea).not.toBeInTheDocument(); - }); -}); diff --git a/web/src/lib/components/album-page/album-description.svelte b/web/src/lib/components/album-page/album-description.svelte deleted file mode 100644 index 00744832a7..0000000000 --- a/web/src/lib/components/album-page/album-description.svelte +++ /dev/null @@ -1,50 +0,0 @@ - - -{#if isOwned} -